サムネイル画像生成システムのこだわりを語らせろ。

## 本題の前に

この記事は、[ジーズアカデミーのカレンダー | Advent Calendar 2022 - Qiita](https://qiita.com/advent-calendar/2022/gsacademy) 10日目です。
昨日の記事は taisuke-m さんによる [学校教育とICT（40代普通の教員の思うこと）](https://qiita.com/taisuke-m/items/9ef67da6fc72222b8d1c)でした。
教育現場でのIT導入の課題と、そんな課題意識を持ったからこそG's ACADEMY に入学した経緯など、アツい内容でしたね！


## 今回語りたいこと

### 自己紹介します！

どーも皆様、こんにちは！どんぶラッコこと、TOKYO LAB6期 卒業の 坂尻愛明です。

私たちは今、「作品の想いと世界を結ぶ」をコンセプトにしたサービス、AnyPaletteを運営しています。

https://any-palette.com/

AnyPaletteとは、クリエイターが描いた作品をインテリア商品として販売できるマーケットプレイスです。

<img width="500" alt="" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/241647/40ad53f3-3f8e-f208-5b71-c97d228c6c32.png">

2022年12月現在、150名ほどのクリエイター様にご登録をいただいており、素敵な作品がたくさん出品されています。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/241647/2e11a7ff-ad14-b8eb-3e0b-a9c04aad9b39.png)

ジーズ卒業生の運営メンバー達と、どのようにしたら作品が広まっていくのか、日々議論しながら進めているので、ぜひ覗いてみてください！

### サムネイル画像生成システムについて語らせてくれ

さて、このAnyPalette、商品詳細ページに飛ぶと実際にアート作品を飾った時のイメージサムネイル画像を確認することができます

![image.png](https://i.gyazo.com/22bbdc3e50d74798b9fa4a1147d55e9a.gif)

今回語りたいのはここ、実際の掲載イメージののサムネイル画像です。

<img width="500" alt="" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/241647/5174316c-1090-88e7-bc0b-d53437d6ac02.png">

こちら、実は Canvas 要素 と JavaScript で画像合成をして出力した画像を使っています。
そして、 **この画像合成の部分に実は色々なこだわりがあるのです** 。

「こんなことまでこだわってるんだよ！（どや）」をしたいのですが、悲しいかな、そんな場がありません。
ジーズアカデミーの授業で毎週自分が取り組んできた課題をキラキラした顔で発表しているみなさんをみていると羨ましくてしょうがありません。

**俺だって課題発表したいんじゃ！！**

ということで、今回2年ぶりのAdvent Calendarの場をお借りして、存分にフラストレーションを解放させたいと思います。

## こだわりポイント紹介

### その1: レイヤー構成 と 位置管理

まずは、当然ですが、画像を合成しているので商品画像と背景画像を Canvas 要素に出力することでサムネイル画像を出力しています。
ここでポイントになるのが、 **フレーム画像と商品画像を別々に管理している** という点です。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/241647/f3d3d0db-ed35-0658-e829-268de107a744.png)

このようにレイヤーを重ねる設計にしておくことで、
今後商品ラインナップを増やし、額縁の種類が増えたとしても画像合成を簡単にできるわけです。

拡張することを見据えた良い設計ですね！（自画自賛）

そして、商品画像、額縁、背景画像の出力位置は全てオブジェクトにて管理しています。
こんな感じです↓↓

```javascript
{
  url: '../img/templates/003.jpg',
  artPosterA2: {
    offset: {
      x: 400,
      y: 210,
      scale: 0.8
    },
    image: {
      coord: {
        x: 16,
        y: 19,
        w: 765,
        h: 1083
      }
    },
    bgImages: [frameA2]
  },
  // ... 略
}
```

つまり、商品画像の位置を変えたり、背景画像を変えたりしたい時は、このオブジェクトのパラメータを変えるだけで良いのです！

<a href="https://gyazo.com/32dae54e9264fe90765c31e9ee3a08b8"><img src="https://i.gyazo.com/32dae54e9264fe90765c31e9ee3a08b8.gif" alt="Image from Gyazo" width="1000"/></a>

当然のことながら、商品画像とフレームの位置や大きさは連動します。

はい、この時点でもう優勝決定です。

ちなみにより突っ込んだ話をすると、商品画像と背景画像の間に差し込む画像（今回であればフレームの画像）については `bgImages` キーの中で **配列** で管理しています。配列ということは、すなわち **2枚以上の画像を重ねて表示することもできる** わけです。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/241647/9230f508-a74b-9572-ee16-8e36e8887046.png)

**拡張することを見据えた良い設計ですね！（自画自賛）**


### その2: 画像の自動クロップ

そして、合成する以上避けて通れないのが **サイズのズレ** です。

今はA2サイズのフレームに画像を合成しています。
なので、もしその上に乗っかっている商品画像が正方形だったら...額縁からはみ出してしまいますよね？

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/241647/586abfda-5906-de0a-8c24-97f75f8ffecc.png)

では、今回使っているどんぶラッコの元画像をみてみましょう。

![スクリーンショット_2022_12_10_22_42.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/241647/41fadeb4-f121-4754-5124-d1c91872b269.png)


![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/241647/56ab17ac-0f18-271f-5d1d-d9b77a8c5958.png)

**正方形の画像だーーー！！！！**

そう、実はフレームに合わない画像がきても対応できるように、フレームの外周に合わせて **画像を自動トリミングする** 機能を搭載しています

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/241647/7aeaa92a-ba44-df92-4a2a-2a2ba962dad8.png)

（注: 実際に販売している作品群はクリエイターの皆様に縦横比を指定してアップロードいただいてるのでサムネイル画像と現物とに差異はありません。ご安心ください！）

これで、もう合成っぽさが消えてきますね！

そして、このクロップするべき縦横比、サイズも **当然** オブジェクトで管理がされています。

**拡張することを見据えた良い設k(略**

### その3: 縦画像 or 横画像の自動判別

作品画像の縦横比に関連した機能です。
作品が縦作品か、横作品かによって、自動でフレームの向きが選択されます。

横長の画像であれば横フレーム
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/241647/c39194f8-ddb4-dff7-37c6-7ab870610da2.png)

縦長の画像であれば縦フレームが選択されます
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/241647/79fbc5e6-2a12-7cee-8a03-e06d7b7de987.png)

はい天才です。

### その4: 影を追加

そして、ここからはよりリアルに見せる（魅せる）ための仕掛け作りです。
まずは、影の処理です。
実は今までお見せしてきたフレーム画像に、影を追加する処理を実施しています。

影がない ver.
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/241647/c135668b-9cb4-b127-51ee-f1f00fbd0acb.png)

影がある ver.
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/241647/ab0fa564-1044-b34c-ffc1-e6bba38334d9.png)

ぐっと本物感が増しますよね？

### その5: 背景色平均のうっすら合成

最後に、背景色の平均色をうっすら合成しています。

...は？

となった皆様、もう少しお付き合いください。
以下の画像サムネイルを見比べてみてください。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/241647/ceb839db-2fa0-9b33-3b3b-8833f1f4a524.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/241647/40a11bd2-f377-e5f9-8e14-39d1e1f334bb.png)

キャンバスの色が若干違うのがおわかりいただけるでしょうか？
オレンジ背景に合成している画像の方が、若干オレンジになっています。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/241647/ca314635-cb9f-7335-421f-dd46906917bb.png)

写真を撮ると、背景色の光の反射を受けるので、必然背景画像の影響を受けます。
今回のサムネイル画像生成では、背景画像のピクセル平均色を取得し、その平均色を若干フィルターとして適用しています。
こうすることで、実際の部屋に飾ったイメージがよりリアルに想像することができるのです！

ちなみに、画像の平均色を取得するロジックはこんな感じです。

```javascript
// 画像の平均色を取得する
const getCanvasAverageColor = () => {
  const data = ctx.getImageData(0, 0, canvas.width, canvas.height).data
  let nums = [0, 0, 0, 0] // [r, g, b, a]
  let counter = 0
  for (let i = 0; i < data.length; i = i + 4) {
    const d = data.slice(i, i + 4)
    nums = nums.map((n, i) => n + d[i])
    counter++
  }
  nums = nums.map(n => Math.round(n / counter))
  return nums
}
```

１ピクセルずつぶん回して、色情報を取得して、最終的に平均値を算出しています。


## あなたの見えないこだわりは、なんですか？

さて、みなさんいかがだったでしょうか。

せっかくなので言い添えておくと、この仕組みは HTML, CSS, JavaScript で作られています。フレームワークを使わず、生JSでゴリゴリ実装しました笑

また、アーティストさんの作品を自動的に取得して各種サムネイル画像を生成し、それを販売システムにアップロード...なんて仕組みも組んでいます。

このように、まだまだこだわりポイントがあるので語りたいところなのですが、投稿する日付を跨いでしまいそうなので、今回はこのへんでお別れしたいと思います。

普段ビジネス側のことに取り組んでいると、実装物のこだわりや、自分の楽しいポイントなどを発表する機会がついついなくなってしまいますが、実はあんまり気づかれないところにこだわっている人、たくさんいると思います。

**みなさんのこだわりはなんですか？**

ぜひ語り合いましょう！

---
明日の記事はyosie_nagatomo さんです！ "壁打ちの話か卒業後のつながりもいいぞ、てきなやつを何か書く" とのことです。楽しみですね♪