notebook

都内でWEB系エンジニアやってます。

Google Slideで図形の一部をハイライトする処理

alt

よくある感じではあるがこういうのをサクッとやりたい

パワーポイントだとこういう機能があるようだがGoogle Slideにはなかった

愚直にやるなら図形を切り貼りして画像に合わせて上から被せる必要があるが結構面倒

ということで、なんとかできるように毎度のことながらGASのアドオンという形で書いてみた

使い方

想定として、スクリーンショットなどの一部分が強調されるようにしたい

強調したい部分にtargetという文字列が記述された四角形を用意する

その状態でアドオンを実行する

実際に使ってみたけど結構よい

実装

割とシンプルにできた気がする

function highlightBlack() {
  highlight('#000000')
}

function highlightWhite() {
  highlight('#FFFFFF')
}

// ImageやShape上にあるtargetというテキストのShapeの範囲を強調する
function highlight(color) {
  const presentation = SlidesApp.getActivePresentation();
  const pageIndex = getTargetPageIndex(`Highlight ${color}`)
  const slides = presentation.getSlides();
  const slide = slides[pageIndex];

  const objects = [...(slide.getShapes().filter(s => s.getShapeType() !== SlidesApp.ShapeType.TEXT_BOX)), ...(slide.getImages())]
  const targetShapes = slide.getShapes().filter(s => s.getText().asString() === 'target\n')

  const cordinate = (s) => {
    return {
      top: s.getTop(),
      bottom: s.getTop() + s.getHeight(),
      left: s.getLeft(),
      right: s.getLeft() + s.getWidth()
    }
  }
  const isBehind = (shape, object) => {
    const front = cordinate(shape)
    const back = cordinate(object)

    // frontがbackに内包されている状態
    if (front.top > back.top && front.bottom < back.bottom && front.left > back.left && front.right < back.right) {
      return true
    }
    return false
  }

  targetShapes.forEach(s => {
    const behindObject = objects.find(o => isBehind(s, o))

    const lefts = [
      behindObject.getLeft(),
      s.getLeft(),
      s.getLeft() + s.getWidth()
    ]
    const tops = [
      behindObject.getTop(),
      s.getTop(),
      s.getTop() + s.getHeight()
    ]
    const widths = [
      s.getLeft() - behindObject.getLeft(),
      s.getWidth(),
      (behindObject.getLeft() + behindObject.getWidth()) - (s.getLeft() + s.getWidth())
    ]
    const heights = [
      s.getTop() - behindObject.getTop(),
      s.getHeight(),
      (behindObject.getTop() + behindObject.getHeight()) - (s.getTop() + s.getHeight())
    ]

    const hatchShapes = [[true, true, true], [true, false, true], [true, true, true]].map((line, i) => {
      return line.map((item, j) => {
        if (!item) {
          return false
        }

        const hatchShape = slide.insertShape(
          SlidesApp.ShapeType.RECTANGLE,
          lefts[i],
          tops[j],
          widths[i],
          heights[j]
        )

        const fill = hatchShape.getFill()
        fill.setSolidFill(color, 0.4)
        const border = hatchShape.getBorder()
        border.setTransparent()

        return hatchShape
      }).filter(v => v)
    }).flat()

    slide.group(hatchShapes)
    s.remove()
  })
}

詳細

いくつかつまずたところやポイントの説明を残しておく

  • 図形やイメージの取得

ハイライト対象の図形と、他の図形を特定する際にslide.getShapes(),slide.getImages()両方使用する必要があった

getShapes()で取得できるリストにもイメージのタイプがあるようだったのですべて取得できるのかと思って試してみてたが取得できず、結局ImageはgetImages()で取得した

  • ターゲット図形は、はみ出さない前提

isBehind関数でターゲットの図形は、被せた図形やイメージの中に必ず含まれている(はみ出ていない)状態を前提とした

はみ出ていてもハイライトしたい場合はそんなにないだろうという判断と、分岐も増えるのでこういう仕様でということにした

この前提があれば、ターゲットの図形の周りに半透明の図形を8つつなげて配置してあげるだけでよい

target部分のみ何もしないことでハイライト効果を付与する

まとめ

  • GoogleSlideのGASで、画像などの一部を強調したい場合に半透明の図形を組み合わせて強調表示しているように見せるアドオンを実装した
  • 図形の座標情報などは自動で判定してくれるので手動のときと比べ、ズレがなくてきれい
    • 手動の場合いくつかの図形を配置、グループ化というような手順が必要だが自動で処理されるので便利

図形処理も勘所がわかってきた気がするので面倒に感じたものはどんどんアドオン化しようと思う

最後に、GAS用のコードはGistにあげたので良ければ使ってみてください