2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

HTMLでありPNGであるファイルを作ってみた

Posted at

事の経緯

最近話題になっているこちらのレポジトリを知りまして、すごく面白い発想ですよね

PNGでありHTMLでありZIPでもある単一のファイルらしくてGildas Lormeauさんという方が作られたらしいです。
実際に拡張子を変えるとちゃんと動きます。
そこで私でもpngとhtmlを融合するぐらいなら出来るのではないかと言うことで、作り始めました。
制作期間は2日くらいです

制作物

その結果できたものがこちらです。

HTMLにした場合
スクリーンショット 2024-12-27 125629.png
PNGにした場合
スクリーンショット 2024-12-27 125759.png

クオリティは低いですがちゃんと動作するようになっています。
拡張子を変えると画像が表示されたり、htmlが動作するのがわかると思います
htmlで画像を表現したい場合はbase64形式に変換して直でいれるかurlを入れてください
ちゃんとしたサイトも作ろうと思えることができれば作れます多分
※「テンプレどん」様のコードをこのファイルに反映させた図
スクリーンショット 2024-12-27 130509.png

仕組み

仕組みとしては単純でpngのバイナリにあるtEXtチャンクとiTXtチャンクにhtmlコードを記述しただけです。
tEXtチャンクやiTXtチャンクとはpngファイルのメタデータの一つで制作者名や著作権表示など、比較的自由に書けるチャンクのことです。
(上の説明に関しては専門外なので間違ってるかもしれません)

ここにhtmlコードを書き込むことによって
・pngにした場合: メタデータは表示されないので画像として表示される
・htmlにした場合: pngのメタデータが表示されるようになるのでそのメタデータがhtmlとして機能する

といった感じで機能します

実際のコード

実際のコードというと語弊がありますがこのファイルを生成できるコードがこちらです。

from PIL import Image
from PIL import PngImagePlugin

# htmlコード
tEXt_str = '<body><h1>hello world</h1><div id="delete_texts"'.encode('utf-8')
iTXt_str = "</div></body><script>document.getElementById('delete_texts').remove();</script></html>".encode('utf-8')

# 画像パスを取得
original_img_path = "/usr/src/app/img/sample.png"
new_img_path = "/usr/src/app/img/new_image.png"

# 画像ファイルを開く
im = Image.open(original_img_path)

# メタデータを取得(PngInfoオブジェクト)
metadata = PngImagePlugin.PngInfo()

# HTMLデータをiEXt,iTXtチャンクに追加
metadata.add_text('', tEXt_str)
metadata.add_itxt('', iTXt_str)

# 画像を保存
im.save(new_img_path, pnginfo=metadata)

※tEXt_strの中身は簡略化のためh1タグと途中までしか書いてないbodyとdivタグのみにしました。

省略しなかった場合
tEXt_str = '<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>hello png and html</title></head><body><h1>hello world</h1><div id="delete_texts"'.encode('utf-8')

このコードの中で核となる重要な処理は

metadata.add_text('', tEXt_html_str)
metadata.add_itxt('', iTXt_html_str)

↑ ここのtEXtチャンクとiTXtチャンクにhtmlコードを挿入するコードと、
(これによりhtmlを画像に挿入できます。)

tEXt_str = '<body><h1>hello world</h1><div id="delete_texts"'.encode('utf-8')
iTXt_str = "</div></body><script>document.getElementById('delete_texts').remove();</script></html>".encode('utf-8')

↑ ここのpngバイナリに入れるhtmlコードの中身を示すコードです。

実はこのコードには一見意味のないようである記述があります。
それはtEXtチャンクとiTXtチャンクの前後にdelete_textID属性がつけられたdivタグが記述されていて、それをjsで要素ごと削除されている点です。

これはhtmlコード以外の文字が画面に表示されるのを阻止するための対策です。
pngバイナリから無理やりhtmlとして画面に表示させるので当然ながらpng自体のバイナリデータという名の余計な文字列が画面に表示されてしまいます。

スクリーンショット 2024-12-27 160857.png

この余計な文字列、原理はわかりませんがすべてbodyタグの中に記述されているので、
(htmlの仕様なのかはわかりません)
書きたい記述が書き終わったらそれ以降の文字列をこのdelete_textID属性があるdiv要素で囲んで、それをjsで丸ごと削除することにより実質的にpngバイナリを消し去ることができます。ゴリ押しではありますけどね
しかしそれでもファイルの一番最初に書くpngファイルであることを明示する記述(IHDR)は、このdivタグの外にあるので上で表示されてしまいます。

これについては放置しました。

なぜかそれがfontタグで囲まれていて、じゃあと思ってfontタグ要素をすべて削除するコードを書いたんですけどなぜか機能しなかったんですよね、、、
しかもほんの数文字なので見て見ぬふりをすることにしました。
(もし解決方法をご存じの方がいれば、ぜひ教えていただけると嬉しいです!)

まとめ

こんな感じで仕組み自体は単純です。
(Gildas Lormeauさんのやり方は見ていないのでわかりませんが私のより複雑だと思います)
誰でも作れますし、発想が面白いので自由研究の題材としても結構いいと思います。

それはそうともうすぐお正月ですね。
体調にはくれぐれも気をつけてくださいね♪(現コロナ感染者は言う)

2
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?