Help us understand the problem. What is going on with this article?

PythonでGoogleカレンダーのfaviconみたいな画像をたくさん生成してVueのプロジェクトに組み込んでみた

この記事は株式会社クロノス「~2020年春~勝手にやりますアドベントカレンダー」の19日目の記事です。

はじめに

Googleカレンダーのfaviconをご存知でしょうか?
スクリーンショット 2020-03-15 17.56.17.png

この画像は3月15日に取った画像ですが、実はアクセスした日によって中の数字が変わる仕組みになってたりします。

ということで自分も前にカレンダー的なアプリを作ったのでそれに導入してみることにします。

前まではこんな感じでした。

スクリーンショット 2020-03-15 17.59.33.png

はい、デフォルトです。
めっちゃVue.jsで作ってます!!!!っていうのが伝わる感じになっていますね。
技術のアピールは大事ですが、アプリとしては若干かっこ悪いので修正してみます。

開発者ツールでGoogleカレンダーを見てみると、1〜31まで全パターンのfaviconを用意しているみたいなので、Googleにならって全パターン用意して表示を切り替えるような感じにしてみます。

画像を用意する

使用したツール等

  • Google Colaboratory
  • Python 3.6.9(Colaboratoryに入ってたバージョン)
  • PIL(Python Image Library)
  • ICOOON MONO(アイコン)

ベースとなる画像を用意する

今回はICOOON MONOにあるカレンダーのアイコンに数字を重ねて画像を生成してみます。
こちらのアイコンを使用させていただきました。

キャプチャ.PNG

サイズは48×48、カラーはアプリのテーマに合わせて紫(rgb(121, 88, 214))にしました。

Colaboratoryを開く

以下の画像のような状態にします。

スクリーンショット 2020-03-15 18.23.25.png

まずベース画像をアップロードします。今回はbase.pngという名前でアップロードしました。

次に加工後の画像を格納するフォルダを作成します。今回はoutというフォルダを作成しました。

以上の準備ができたらPythonのコードを書いていきます。
真ん中に数字を配置するためにちょっとトリッキーなことやってたりします。

画像を合成するコード
from PIL import Image

# 各種設定
IMAGE_WIDTH = 48
IMAGE_HEIGHT = 48
THEME_COLOR = (121, 88, 214)

# 1〜31までループして作成する
for i in range(1, 32):
  # faviconに表示する数字
  i_str = str(i)

  # Font名、サイズを設定する 
  fnt = ImageFont.truetype('LiberationMono-BoldItalic', 25)
  # 数字を配置する場所を計算するために文字の横幅、縦幅を取得する
  w, h = fnt.getsize(i_str)

  # ベースの画像を読み込む
  im = Image.open('./base.png')
  draw = ImageDraw.Draw(im)
  # 読み込んだ画像にテキストを合成する
  draw.text(
      # こう書くと真ん中に配置できるみたい(高さだけ3px微調整してます)
      xy=((IMAGE_WIDTH - w) / 2, (IMAGE_HEIGHT - h) / 2 + 3 ), 
      text=i_str, 
      fill=THEME_COLOR, 
      font=fnt
  )

  # 保存 ./out/favicon01.pngみたいなファイル名にしています
  im.save("./out/favicon{}.png".format(i_str.zfill(2)))

ちなみにフォントは好きなものをダウンロードしてCalaboratoryにアップロードすれば使えるようになりますが、以下のコードを実行すると組み込まれているフォントを確認できます。
今回は数値を扱うだけだったので組み込みのものを使用しました。

Colaboratoryのフォントを確認するコード
import subprocess

res = subprocess.check_output("fc-list")

print(str(res).replace(":", "\n"))

上手く行けばoutフォルダ内に以下のような画像が生成されるはずです。
favicon01.png
outフォルダ内の画像をダウンロードしましょう。
Colaboratoryだとフォルダ丸ごとダウンロードできなさそうなので
以下のコードでzipにしておくと楽にダウンロードできます。

outフォルダをzipに固める
import shutil

shutil.make_archive('./out', 'zip', root_dir='./out')

Vueのプロジェクトに組み込む

Vueのプロジェクトのpublicフォルダ内に生成した画像を配置しましょう。

index.htmlを修正します。
faviconを設定しているlinkタグのhref属性を書き換えるscriptを追加します。

index.html
<!DOCTYPE html>
<html lang="ja">
  <head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width,initial-scale=1.0">
    <link rel="icon" href="<%= BASE_URL %>favicon.ico">
    <!-- 追加部分 -->
    <script>
      const faviconLink = document.querySelector("link[rel='icon']");
      // 0埋めした日付を取得して favicon01.png みたいな文字列を生成してます
      faviconLink.href = `favicon${("0" + new Date().getDate()).slice(-2)}.png`
    </script>
    <link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.2.0/css/all.css">
    <title><%= htmlWebpackPlugin.options.title %></title>
  </head>
  <body>
    <noscript>
      <strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
    </noscript>
    <div id="app"></div>
    <!-- built files will be auto injected -->
  </body>
</html>

いい感じになりました。(満足)

スクリーンショット 2020-03-15 18.56.02.png

あとはビルドして本番環境にデプロイです。
めでたしめでたし・・・

と思いきや・・・

次の日(16日)にアクセスしてみると、アイコンが15から変わってないじゃないですか。
なんでや…!と思い、トランスパイルされたindex.htmlを見てみると

dist/index.html
<!DOCTYPE html><html lang=ja><head><meta charset=utf-8><meta http-equiv=X-UA-Compatible content="IE=edge"><meta name=viewport content="width=device-width,initial-scale=1"><link rel=icon href=/favicon.ico><script>const faviconLink = document.querySelector("link[rel='icon']");
これ   faviconLink.href =  `favicon15.png`</script><link rel=stylesheet href=https://use.fontawesome.com/releases/v5.2.0/css/all.css><title>ad-calendar</title><link href=/css/app.9c57fa73.css rel=preload as=style><link href=/css/chunk-vendors.a4393e1d.css rel=preload as=style><link href=/js/app.ed32e83e.js rel=preload as=script><link href=/js/chunk-vendors.80e1df9b.js rel=preload as=script><link href=/css/chunk-vendors.a4393e1d.css rel=stylesheet><link href=/css/app.9c57fa73.css rel=stylesheet></head><body><noscript><strong>We're sorry but ad-calendar doesn't work properly without JavaScript enabled. Please enable it to continue.</strong></noscript><div id=app></div><script src=/js/chunk-vendors.80e1df9b.js></script><script src=/js/app.ed32e83e.js></script></body></html>

なんとビルドを実行した日付に固定されてしまってますね。
index.html内でJavaScriptのテンプレート記法を使うと実行時の値に固定されてしまうっぽいです。

最終的には以下のように修正すると期待通り動作するようになりました。

<!DOCTYPE html>
<html lang="ja">
  <head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width,initial-scale=1.0">
    <link rel="icon" href="<%= BASE_URL %>favicon.ico">
    <script>
      const faviconLink = document.querySelector("link[rel='icon']");
      // 変数に格納するようにした
      const now = new Date();
      const nowDate = ("0" + now.getDate()).slice(-2);
      // +演算子で文字列結合する
      faviconLink.href = "<%= BASE_URL %>favicon" + nowDate + ".png";
    </script>
    <link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.2.0/css/all.css">
    <title><%= htmlWebpackPlugin.options.title %></title>
  </head>
  <body>
    <noscript>
      <strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
    </noscript>
    <div id="app"></div>
    <!-- built files will be auto injected -->
  </body>
</html>

今度こそいい感じになりました。(満足)

参考

https://qiita.com/agajo/items/90a29627e7c9a06ec24a
https://www.tech-tech.xyz/drawtext.html
https://icooon-mono.com/license/
https://stackoverflow.com/questions/1970807/center-middle-align-text-with-pil

maroKanatani
カメレオンエンジニア目指してます。 爆速で成長していきたい。 同じ技術ばっかりやるんじゃなくて色々やりたい。 たまに技術者の教育とかもやってる。
https://www.kronos.jp/
kronos-jp
AI開発・WEB開発・システム開発・Android開発・iOS開発・IT研修・トレーニング・新入社員研修などを行う企業です。
https://www.kronos.jp/
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away