のあの

のあの日常ブログ

画像ファイルのエンコード

以前作成したSTGもそうですが、友達に配布する時に使用している画像が丸見えになっています。

ゲームを作成した、作成したい人なら一度は検索するワードかもしれません。

exeひとまとめ 画像隠す などなど。

exeの中にひとまとめにする方法は良くわからなかったので、
画像ファイルをエンコードして見えないようにしたいと思います。
今回は簡単なものを自作します。

pygameを使っているのでpythonで行います

そもそもエンコードってなんだというお話し

エンコード(英: encode)、符号化(ふごうか)とは、アナログ信号やデジタルデータに特定の方法で、後に元の(あるいは類似の)信号またはデータに戻せるような変換を加えることである。

エンコード - Wikipedia

今回で言うと 画像データに特定の方法で、元に戻せるような変換を加えること まずは変換した画像データは一般ユーザーに画像として認識されない事を目標とします。

最近流行りのいらすとやからお借りしたこの画像でテストしてみたいと思います

f:id:nor24:20180822232311p:plain

目次

単一画像データの決め打ちでエンコード

import struct

key = 0b11111111   # keyは一例

infile = open('syachiku.png', 'rb')
outfile = open('img.en', 'wb')
data = infile.read()
for i in range(len(data)):
    outfile.write(struct.pack("B", (data[i] ^ key)))
infile.close()
outfile.close()

今回はstract.packを利用しました。

プログラム自体はとても簡単なものとなっています

data として画像データを読み込みます。(1Byteの配列)
その data ひとつひとつに対し適当なkeyでxorします(今回はビット反転)
あとはそのデータを outfile に書き出して終了です

生成された img.en を開いてみると

f:id:nor24:20180822235120p:plain

読めなくなりました

拡張子をpngに変更してみても

f:id:nor24:20180822235814p:plain

何の画像か分かりません

この訳の分からないデータから元の画像に戻せないとエンコードとは言えません。
次はデコーダを作成しましょう

単一画像データの決め打ちでデコーダ

作成するといってもエンコーダが簡単だったのでデコーダも簡単です。

バイナリを反転するエンコーダを作成したので、バイナリを反転するデコーダを作成すればいいわけです。

import struct

key = 0b11111111    # keyはエンコーダで使ったモノと一緒のモノ

infile = open('img.en', 'rb')
outfile = open('out_img.png', 'wb')
data = infile.read()
for i in range(len(data)):
    outfile.write(struct.pack("B", (data[i] ^ key)))
infile.close()
outfile.close()

完成しました。 入力ファイルと出力ファイルの名前が変わっただけです。

きちんと元に戻せているのか確認しましょう

f:id:nor24:20180823001330p:plain

元の画像に戻せていました

エンコーダとデコーダkey の部分を変更することでいろいろなエンコーダ/デコーダとして利用できます。 当然ですがエンコーダと違う keyデコーダで使うと、きちんとデコードできません。

そんな立派なものではないので全く知らない人でも key の特定ができそうですが、 友達に配布する程度でしか考えてないのでこれで十分です。

複数画像のエンコード

さて、エンコード/デコードができるようになりました。 が、先ほどのプログラムでは以下の問題点があります

  1. 1つしかエンコード/デコードできない
  2. ファイル名がべた書きなので、いちいちプログラムを修正する必要がある

ゲーム作成で画像ファイルが一つという事は滅多にないと思うので、 特定のフォルダ以下の画像ファイルを全てエンコードするようにしましょう。

今回のフォルダ構成は以下です

program
 ├─src
 │  └─main.py #本プログラム
 └─img
    ├─player
    │  ├─player1.jpg
    │  └─player2.jpg
    ├─enemy
    │  ├─enemy1.png
    │  ├─enemy2.png
    │  └─enemy2.png
    └─test.txt

imgディレクトリ以下に画像がまとまっているので、これらすべてをエンコードします。 複数の拡張子も同時に出来るようにplayerとenemyで敢えて拡張子を変えています。
test.txtも変換しないように置いておきます

パスの取得

画像データを取得するにはパスがわからないとどうしようもないのでまずはパスを取得します。

qiita.com

pathlibを使えば簡単に取得できるとのこと

from pathlib import Path

p = Path("./../img/")

for file_path in list(p.glob("**/*")):
    print(file_path)
[出力結果]
..\img\enemy
..\img\player
..\img\test.txt
..\img\enemy\enemy1.png
..\img\enemy\enemy2.png
..\img\enemy\enemy3.png
..\img\player\player1.jpg
..\img\player\player2.jpg

srcディレクトリの中で実行した結果です。
programディレクトリの中で実行するとうまくいきません `program$py src/main.py'

一旦このままいきます。。

画像ファイ以外は除外する

先ほどのpath listの中にはディレクトリやtxtファイルも含まれています。
これらはエンコードしたくないので除外します。

qiita.com

ぴったりな記事がありました。

from pathlib import Path

p = Path("./../img/")

in_extension = ['.jpg', '.png', '.bmp']
img_files = [img for img in p.glob('**/*') if img.suffix in in_extension]

for file_path in img_files:
    print(file_path)
[出力結果]
..\img\enemy\enemy1.png
..\img\enemy\enemy2.png
..\img\enemy\enemy3.png
..\img\player\player1.jpg
..\img\player\player2.jpg

いけました。

in_extension に取り出したい拡張子を入れることによって、
好きなファイルを取り出すことができます。

ですが、img_filesの要素は WindowsPath クラスのインスタンスとなっています

このままではpathのみ抽出(拡張子の変更)ができないので文字列に変換します。
as_posix という関数が用意されているのでそれを用います。

from pathlib import Path

p = Path("./../img/")

in_extension = ['.jpg', '.png', '.bmp']
img_files = [img for img in p.glob('**/*') if img.suffix in in_extension]

for file_path in img_files:
    file_path = file_path + " "    # error
    print(file_path)
    file_path = file_path.as_posix()
    file_path = file_path + " "    # ok
    print(file_path)

複数画像のエンコード

..\img\enemy\enemy1.png から ..\img\enemy\enemy1.en のように拡張子部分を変更します。
もとの文字列( ..\img\enemy\enemy1.png )を右端から捜査していき、
初めにピリオドが出てきた個所から後ろを全て削除します

qiita.com

こちらの記事を参考にさせていただきました。

右端からのソートには rfind 関数を使います
file_path[:i] で前半部分を抽出し、拡張子を結合します

out_extension = '.en'
for file_path in img_files:
    file_path = file_path.as_posix()
    print(file_path)
    i = file_path.rfind(".")
    en_file_path = file_path[:i] + out_extension
    print(en_file_path)
[出力結果]
../img/enemy/enemy1.png
../img/enemy/enemy1.en
../img/enemy/enemy2.png
../img/enemy/enemy2.en
../img/enemy/enemy3.png
../img/enemy/enemy3.en
../img/player/player1.jpg
../img/player/player1.en
../img/player/player2.jpg
../img/player/player2.en

これをこれまでのソースコードに追加します。

from pathlib import Path
import struct

p = Path("./../img/")
key = 0b11111111   # keyは一例

in_extension = ['.jpg', '.png', '.bmp']
out_extension = '.en'
img_files = [img for img in p.glob('**/*') if img.suffix in in_extension]

for file_path in img_files:
    file_path = file_path.as_posix()

    # 出力先ファイル名の拡張子を変更
    i = file_path.rfind(".")
    en_file_path = file_path[:i] + out_extension

    infile = open(file_path, 'rb')
    outfile = open(en_file_path, 'wb')
    data = infile.read()
    for i in range(len(data)):
        outfile.write(struct.pack("B", (data[i] ^ key)))
    infile.close()
    outfile.close()

このようになりました

これで img フォルダ下の全ての画像ファイルがエンコードされるようになりました。

出力フォルダの変更

全ての画像ファイルがエンコードされるようになりましたが、
個人的にもとの画像とエンコードしたデータは別のフォルダにしたいです。

program
 ├─src
 │  └─main.py #本プログラム
 ├─img
 │  ├─player
 │  │  ├─player1.jpg
 │  │  └─player2.jpg
 │  ├─enemy
 │  │  ├─enemy1.png
 │  │  ├─enemy2.png
 │  │  └─enemy2.png
 │  └─test.txt
 └─en_img
     ├─player
     │  ├─player1.en
     │  └─player2.en
     └─enemy
        ├─enemy1.en
        ├─enemy2.en
        └─enemy2.en

このような感じです。

    i = file_path.rfind(".")
    en_file_path = file_path[:i] + out_extension
+    en_file_path = en_file_path.replace('img', 'en_img')

    infile = open(file_path, 'rb')
    outfile = open(en_file_path, 'wb')

今回のケースであれば replace 関数を用いることで簡単に置換できます これで出力先のパスを変更できたので実行します

program/src$ py path.py
Traceback (most recent call last):
  File "path.py", line 20, in <module>
    outfile = open(en_file_path, 'wb')
FileNotFoundError: [Errno 2] No such file or directory: '../en_img/enemy/enemy1.en'

が、エラーが出るはずです。
en_img というフォルダが存在しないからです。

open は出力先ファイルを作成することはできますが、フォルダは作成できません。
makedirs を使ってフォルダを作成しましょう。

note.nkmk.me

こちらを参考に完成したプログラムがこちらになります

from pathlib import Path
import struct
import os

p = Path("./../img/")
key = 0b11111111   # keyは一例

in_extension = ['.jpg', '.png', '.bmp']
out_extension = '.en'
img_files = [img for img in p.glob('**/*') if img.suffix in in_extension]

for file_full_path in img_files:
    file_full_path = file_full_path.as_posix()

    # 出力先ファイル名の拡張子を変更
    i = file_full_path.rfind(".")
    en_file_full_path = file_full_path[:i] + out_extension
    en_file_full_path = en_file_full_path.replace('img', 'en_img')

    # 出力先フォルダの作成
    i = en_file_full_path.rfind("/")
    en_file_path = en_file_full_path[:i]
    if not os.path.exists(en_file_path):
        os.makedirs(en_file_path)

    infile = open(file_full_path, 'rb')
    outfile = open(en_file_full_path, 'wb')
    data = infile.read()
    for i in range(len(data)):
        outfile.write(struct.pack("B", (data[i] ^ key)))
    infile.close()
    outfile.close()

完成です