このページではビットマップデータのビューワーアプリを tkinter で開発していきたいと思います。
ここでいうビットマップデータとは、Windows Bitmap(.bmp ファイル)ではなく各画素の画素データ(RGB の輝度値)がそのままの形で保存されているデータのことを指します。
画像ファイルを扱う際は、通常は JPEG や PNG などの画像フォーマットを利用すると思います。こういったファイルは Mac のプレビューや Windows のペイントなどで簡単に開くことができるというメリットがあります。
一方で、ビットマップデータはおそらくこういった PC にプリインストールされているようなアプリでは開くことができません(多分…)。
PhotoShop などのアプリを使用すれば開くことができますが、そういったアプリをわざわざインストールする必要があります。
ただ、ビットマップデータを表示したいだけであれば、tkinter と PIL を使うと自作で簡単にビューワーアプリを開発することができます。
ということで、今回はこのビットマップデータを表示するビューワーアプリを tkinter と PIL で作成していきたいと思います。
Contents
アプリにビットマップデータを表示する方法
まずはビットマップデータを tkinter で作成したアプリに表示する方法について解説していきたいと思います。
ビットマップデータをアプリに表示する手順
ビットマップデータを tkinter で作成したアプリに表示するための手順は下記のようになりますきます。
- ビットマップデータファイルを読み込む
- 読み込んだファイルを PIL 画像オブジェクトに変換する
- PIL 画像オブジェクトを tkinter 画像オブジェクト(
PhotoImage
クラスのインスタンス)に変換する - 変換後の画像オブジェクトをキャンバスに描画する
まず tkinter で画像を表示するのに便利なのはキャンバスです。キャンバスを作成し、そのキャンバスで create_image
メソッドを実行することでアプリ上に画像を表示することができます。
ただし、create_image
メソッドでアプリに画像を表示可能なのは tkinter 画像オブジェクト(PhotoImage
クラスのインスタンス)のみです。
一方で、tkinter の PhotoImage
クラスはビットマップデータの読み込みに対応していません。
そのため、ビットマップデータファイルを読み込んだのち、それをいったん PIL 画像オブジェクトに変換します。
さらに、その PIL 画像オブジェクトを tkinter 画像オブジェクトに変換した後に、その画像オブジェクトをキャンバスに描画してアプリに表示する方法をとります。
ここでポイントになるのが「ビットマップデータを PIL 画像オブジェクトに変換する」ところになると思います。
ですが、これも PIL に用意された frombytes
関数を利用すれば簡単に実現することができます。
スポンサーリンク
frombytes
関数
frombytes
関数は、引数で与えられた単なるバイトデータを PIL 画像オブジェクトに変換する関数になります。
PIL 画像オブジェクトに変換されたデータは画像として扱うことが可能で、PIL に用意された関数やメソッドを利用して画像処理などを行うこともできます。
もちろんビットマップデータも frombytes
関数により PIL 画像オブジェクトに変換することが可能ですので、frombytes
を利用して読み込んだビットマップデータを画像として扱うことができます。
frombytes
関数の定義は下記のようになり、戻り値は PIL 画像オブジェクトとなります。
im = frombytes(mode, size, data, decoder_name="raw", *args)
各引数には下記を指定します。
- 第1引数:モード
'RGB'
や'L'
など- 詳細は下記ページに記載
https://pillow.readthedocs.io/en/stable/handbook/concepts.html#concept-modes
- 第2引数:画像のサイズ
(幅, 高さ)
などのようにタプルやリスト形式で指定
- 第3引数:変換するビットマップデータ
- 第4引数:デコーダーの名前
- ビットマップデータの場合は
'raw'
を指定
- ビットマップデータの場合は
- 第5引数:追加のオプション
特にビットマップデータに対して frombytes
関数を実行する際にポイントになるのは第2引数だと思います。
JPEG や PNG などではヘッダーに画像のサイズ(幅と高さ)が記述されていますので、ヘッダーを解析することでこれらの値を取得することが可能です。
ただし、ビットマップデータの場合は画素のデータがそのまま格納されているだけなので画像のサイズの情報が記述されていません。
そのため、このサイズはユーザーから指定してもらう必要があります。そしてユーザーから指定してもらったサイズ(幅と高さ)を frombytes
関数の第2引数に指定します。
ビットマップデータのビューワーアプリ
ビットマップデータのアプリへの表示(キャンバスへの描画)方法を理解していただいたところで、次はビットマップデータのビューワーアプリのスクリプトを紹介していきたいと思います。
スポンサーリンク
スクリプト
ビットマップデータのビューワーアプリのスクリプトは下記のようになります。
# -*- coding:utf-8 -*-
import tkinter
import tkinter.filedialog
import tkinter.messagebox
from PIL import Image, ImageTk
# キャンバスのサイズ設定
CANVAS_WIDTH = 600
CANVAS_HEIGHT = 400
class BitmapViewer():
def __init__(self, master):
'''コンストラクタ'''
# 親ウィジェットを設定
self.master = master
# 変数を作成
self.createVariables()
# ウィジェットを作成
self.createWidgets()
def createVariables(self):
'''アプリで扱う変数を作成する'''
self.image_width = tkinter.StringVar()
self.image_height = tkinter.StringVar()
self.image_mode = tkinter.StringVar()
self.image_path = tkinter.StringVar()
def createWidgets(self):
'''ウィジェットの作成と配置を行う'''
# キャンバスを配置するフレームを作成
main_frame = tkinter.Frame(
self.master,
)
main_frame.grid(column=0, row=0)
# エントリーやボタンを配置するフレームを作成
sub_frame = tkinter.Frame(
self.master,
)
sub_frame.grid(column=1, row=0)
# キャンバスの作成と配置
self.canvas = tkinter.Canvas(
main_frame,
bg="white",
width=CANVAS_WIDTH,
height=CANVAS_HEIGHT,
)
self.canvas.pack()
# 幅入力用のエントリーの作成と配置
width_label = tkinter.Label(
sub_frame,
text="width",
)
width_label.grid(column=0, row=0)
width_entry = tkinter.Entry(
sub_frame,
textvariable=self.image_width,
)
width_entry.grid(column=1, row=0)
# 高さ入力用のエントリーの作成と配置
height_label = tkinter.Label(
sub_frame,
text="height",
)
height_label.grid(column=0, row=1)
height_entry = tkinter.Entry(
sub_frame,
textvariable=self.image_height,
)
height_entry.grid(column=1, row=1)
# ファイルパス入力用のエントリーの作成と配置
file_label = tkinter.Label(
sub_frame,
text="file",
)
file_label.grid(column=0, row=2)
file_entry = tkinter.Entry(
sub_frame,
textvariable=self.image_path,
)
file_entry.grid(column=1, row=2)
# ファイル選択用ボタンの作成と配置
select_button = tkinter.Button(
sub_frame,
text="ファイル選択",
command=self.select_file
)
select_button.grid(column=0,row=3,columnspan=2)
# 画像表示用ボタンの作成と配置
display_button = tkinter.Button(
sub_frame,
text="画像表示",
command=self.display_image
)
display_button.grid(column=0,row=4,columnspan=2)
def select_file(self):
'''ファイル選択ボタンが押された時の処理'''
# ファイル選択ダイアログの表示
path = tkinter.filedialog.askopenfilename()
if len(path) != 0:
# ファイルが選択された場合
# ファイルパスのエントリーを更新
self.image_path.set(path)
def display_image(self):
'''画像表示ボタンが押された時の処理'''
# エントリーから画像の幅と高さを取得
width_str = self.image_width.get()
height_str = self.image_height.get()
if not width_str or not height_str:
tkinter.messagebox.showwarning(message="Inpput Width and Height")
return
width = int(width_str)
height = int(height_str)
# ファイルパスエントリーのパスのデータを開く
try:
with open(self.image_path.get(), mode='rb') as f:
# 1.ビットマップデータファイルを読み込む
bitmap_data = f.read()
# 2.読み込んだファイルを PIL 画像オブジェクトに変換する
pil_img = Image.frombytes(
'RGB',
[width, height],
bitmap_data,
'raw'
)
except Exception:
tkinter.messagebox.showwarning("エラー", "画像が読み込めませんでした")
return
# キャンバスのサイズに合わせるための拡大率を計算
ratio_width = CANVAS_WIDTH / width
ratio_height = CANVAS_HEIGHT / height
ratio = min(ratio_width, ratio_height)
# キャンバスのサイズに合わせてリサイズ
resize_img = pil_img.resize((int(ratio * width), int(ratio * height)))
# 3.PIL 画像オブジェクトを tkinter 画像オブジェクトに変換する
self.img = ImageTk.PhotoImage(resize_img)
# 4.変換後の画像オブジェクトをキャンバスに描画する
self.canvas.create_image(
CANVAS_WIDTH // 2, CANVAS_HEIGHT // 2,
anchor=tkinter.CENTER,
image=self.img
)
app = tkinter.Tk()
app.title("BitmapViewer")
BitmapViewer(app)
app.mainloop()
スポンサーリンク
ビットマップデータの表示
上記のスクリプトを実行すると下の図のようなアプリが起動します。
width
入力欄と height
入力欄と file
入力欄にはそれぞれ下記を入力します。
width
:ビットマップデータの画像としての幅height
:ビットマップデータの画像としての高さfile
:ビットマップデータファイルのパス
file
入力欄に入力するパスは「画像選択」ボタンからファイルを選択することで自動入力することも可能です。
本スクリプトで対応しているビットマップデータのモードは RGB のみです
グレースケールやアルファチャネル付きのデータには対応していないので注意してください
上記を入力した状態で「画像表示」ボタンを押すと、下の図のようにビットマップデータがキャンバスに描画されて表示されます。
サイズ設定がおかしいと下の図のようにうまく画像が表示されないので注意してください。
ビットマップデータなんて持ってないよーという方は、このページの最後に紹介するビットマップデータ作成アプリを活用していただければと思います。
スクリプトの解説
スクリプトのポイントになる部分だけを簡単に解説しておきます。
アプリにビットマップデータを表示する方法で解説した通り、ビットマップデータではデータからは画像のサイズが分からないため、ユーザーからサイズを指定してもらえるように幅入力用のエントリーウィジェット(width_entry
)と高さ入力用のエントリーウィジェット(height_entry
)を用意しています。
さらに、読み込むビットマップデータのファイルを指定できるようにビットマップデータファイルのパス入力用のエントリーウィジェット(file_entry
)も用意しています。
また、このパス入力用のエントリーウィジェットには、「ファイル選択」ボタンからファイルを選択された際にそのファイルのパスを自動的に入力するようにしています。
そして、幅と高さとファイルパスが入力された状態で「画像表示」ボタンが押された際には、ファイルパスに指定されたビットマップデータを読み込んで画像に変換し、キャンバスにその画像を描画することで画像の表示を行なっています。
この「画像表示」ボタンが押された際に実行されるメソッドが display_image
で、ここでアプリにビットマップデータを表示する方法で紹介した下記の処理を実行しています。
- ビットマップデータファイルを読み込む
- 読み込んだファイルを PIL 画像オブジェクトに変換する
- PIL 画像オブジェクトを tkinter 画像オブジェクト(
PhotoImage
クラスのインスタンス)に変換する - 変換後の画像オブジェクトをキャンバスに描画する
特に 2. の部分で、前述で紹介した frombytes
関数を実行してビットマップデータを PIL 画像オブジェクトへの変換を行なっているところがポイントです。
また、3. と 4. の間では画像をキャンバスにサイズに合わせて拡大縮小しています。
この辺りは等倍で表示し、スクロールバーを利用してスクロールするような形式にカスタマイズするようなこともできますので、興味のある方はご自身でカスタマイズしてみてください。
display_image
で実行している処理は、コメントとアプリにビットマップデータを表示する方法を照らし合わせて読んでいただければ分かると思いますので詳しい解説は省略させていただきます。
ビットマップデータ作成アプリ
最後に、ビットマップデータのビューワーアプリに入力するための「ビットマップデータファイル」を持っていない方向けに、ビットマップデータを作成するアプリの紹介をしておきます。
ビットマップデータも Python の PIL を利用すれば簡単に作成することができます。
スポンサーリンク
スクリプト
ビットマップデータ作成アプリのスクリプトは下記のようになります。
# -*- coding:utf-8 -*-
import tkinter
import tkinter.filedialog
import tkinter.messagebox
from PIL import Image, ImageTk
# キャンバスのサイズ設定
CANVAS_WIDTH = 600
CANVAS_HEIGHT = 400
class BitmapCreator():
def __init__(self, master):
'''コンストラクタ'''
# 親ウィジェットを設定
self.master = master
# ウィジェット変数を作成
self.createVariables()
# ウィジェットを作成
self.createWidgets()
def createVariables(self):
'''アプリで扱う変数を作成する'''
self.image_path = tkinter.StringVar()
def createWidgets(self):
'''ウィジェットの作成と配置を行う'''
# キャンバスを配置するフレームを作成
main_frame = tkinter.Frame(
self.master,
)
main_frame.grid(column=0, row=0)
# エントリーやボタンを配置するフレームを作成
sub_frame = tkinter.Frame(
self.master,
)
sub_frame.grid(column=1, row=0)
# キャンバスの作成と配置
self.canvas = tkinter.Canvas(
main_frame,
bg="white",
width=CANVAS_WIDTH,
height=CANVAS_HEIGHT,
)
self.canvas.pack()
# 幅表示用のラベルの作成と配置
width_label = tkinter.Label(
sub_frame,
text="width",
)
width_label.grid(column=0, row=0)
self.width_param = tkinter.Label(
sub_frame,
text="",
)
self.width_param.grid(column=1, row=0)
# 高さ表示用のラベルの作成と配置
height_label = tkinter.Label(
sub_frame,
text="height",
)
height_label.grid(column=0, row=1)
self.height_param = tkinter.Label(
sub_frame,
text="",
)
self.height_param.grid(column=1, row=1)
# ファイルパス表示用のラベルの作成と配置
file_label = tkinter.Label(
sub_frame,
text="file",
)
file_label.grid(column=0, row=2)
self.file_param = tkinter.Label(
sub_frame,
text=""
)
self.file_param.grid(column=1, row=2)
# ファイル選択用ボタンの作成と配置
select_button = tkinter.Button(
sub_frame,
text="ファイル選択",
command=self.select_file
)
select_button.grid(column=0,row=3,columnspan=2)
# ビットマップデータ保存用のボタンの作成と配置
save_button = tkinter.Button(
sub_frame,
text="ビットマップデータを保存",
command=self.save_bitmap
)
save_button.grid(column=0,row=4,columnspan=2)
def select_file(self):
'''ファイル選択ボタンが押された時の処理'''
# ファイル選択ダイアログの表示
path = tkinter.filedialog.askopenfilename()
if len(path) != 0:
# ファイルが選択された場合
# ファイルパスのエントリーを更新
self.image_path.set(path)
# ファイルパスエントリーのパスのデータを開く
try:
self.pil_img = Image.open(self.image_path.get())
except Exception:
tkinter.messagebox.showwarning("エラー", "画像が読み込めませんでした")
return
# キャンバスのサイズに合わせるための拡大率を計算
ratio_width = CANVAS_WIDTH / self.pil_img.width
ratio_height = CANVAS_HEIGHT / self.pil_img.height
ratio = min(ratio_width, ratio_height)
# キャンバスのサイズに合わせてリサイズ
resize_img = self.pil_img.resize((int(ratio * self.pil_img.width), int(ratio * self.pil_img.height)))
# PIL画像をTkinter画像に変換
self.img = ImageTk.PhotoImage(resize_img)
# キャンバスに画像を描画
self.canvas.create_image(
CANVAS_WIDTH // 2, CANVAS_HEIGHT // 2,
anchor=tkinter.CENTER,
image=self.img
)
# ラベルを更新
self.file_param.config(
text=path
)
self.width_param.config(
text=self.pil_img.width
)
self.height_param.config(
text=self.pil_img.height
)
def save_bitmap(self):
'''ビットマップデータ保存ボタンが押された時の処理'''
# ファイル選択ダイアログの表示
path = tkinter.filedialog.asksaveasfilename()
if len(path):
# RGBに変換
rgb_pil_img = self.pil_img.convert('RGB')
# ビットマップデータへ変換
bitmap_data = rgb_pil_img.tobytes()
# ビットマップデータをファイル保存
with open(path, 'wb') as f:
f.write(bitmap_data)
app = tkinter.Tk()
app.title("BitmapCreator")
BitmapCreator(app)
app.mainloop()
ビットマップデータの作成
スクリプトを実行すると下の図のような画面のアプリが起動します。
「ファイル選択」ボタンを押せばファイル選択ダイアログが表示されますので、PNG や JPEG 等の画像ファイルを選択すれば、キャンバスに画像が描画されます。
さらに、width
欄には画像の幅、height
欄には画像の高さ、file
欄には表示中の画像ファイルのパスが表示されます。
画像が表示されている状態で「ビットマップデータ保存」ボタンを押せば、ファイル選択ダイアログが表示され、ファイル名を入力して Save
ボタンを押せば、ビットマップデータが保存されます。
まとめ
このページでは tkinter と PIL を用いたビットマップデータのビューワーアプリの作り方について解説しました。
正直 Python ユーザーの方には使い所はあまりないかもしれません…。Python だと PIL を使えば簡単に PNG や JPEG にエンコードできますので。
ただエンコードするのにちょっと手間のかかるC言語などを利用している場合は、画像処理結果や画像処理途中のデータなどをちょっと確認するのに便利だと思います!是非ビットマップデータの表示にご活用ください!