以前作った、任意の色で四角形のBMPファイルを作る無意味アプリ。コレを Python3 で実行すると動かない。
bytearay の扱いが変わったからだそうで、正直そんなものをホイホイ変えてくれるなと思ったのですが、仕方ないので修正…したんですけどね。
なんというか、 Python は C 言語に恨みでもあるんですかね?私はC及びC++の知識を下敷きに Python のコードを書いていますが、今回は完全に仇になりました。
コード
## make bmp ##
from kivy.app import App
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.gridlayout import GridLayout
from kivy.uix.button import Button
from kivy.uix.label import Label
from kivy.uix.textinput import TextInput
from kivy.uix.slider import Slider
from kivy.uix.floatlayout import FloatLayout
import os
from os.path import join,isdir
from kivy.uix.filechooser import FileChooserIconView
from kivy.uix.filechooser import FileChooserListView
from ctypes import *
#class HEADER(Structure):
class HEADER(LittleEndianStructure):
_pack_ = 1
_fields_ = [
# BITMAPFILEHEADER (14byte)
('bfType', c_char * 2),
('bfSize', c_uint32),
('bfReserved1', c_uint16),
('bfReserved2', c_uint16),
('bfOffBits', c_uint32),
# BITMAPINFOHEADER (40byte)
('biSize', c_uint32),
('biWidth', c_uint32),
('biHeight', c_uint32),
('biPlanes', c_uint16),
('biBitCount', c_uint16),
('biCompression', c_uint32),
('biSizeImage', c_uint32),
('biXPelsPerMeter', c_uint32),
('biYPelsPerMeter', c_uint32),
('biClrUsed', c_uint32),
('biClrImportant', c_uint32)]
### Input Directory
#class DirectoryChooser(FileChooserListView):
class DirectoryChooser(FileChooserIconView):
def is_dir(self,directory,filename):
self.parent.parent.t1.text = directory
return isdir(join(directory,filename))
class BoxSelectFile(BoxLayout):
orientation='vertical'
def __init__(self,**kwargs):
super(BoxSelectFile,self).__init__(**kwargs)
self.dc =DirectoryChooser(size_hint_y=1)
self.dc.filters=[self.dc.is_dir]
self.add_widget(self.dc)
### Input FileName
class BtSave(Button):
def on_press(self):
self.parent.parent.MakeFile()
class BoxSaveFileName(BoxLayout):
orientation='horizontal'
def __init__(self,**kwargs):
super(BoxSaveFileName,self).__init__(**kwargs)
self.t1 = TextInput(text='',size_hint_x=5,multiline=False)
self.l1 = Label(text='.bmp',size_hint_x=2)
self.b1 = BtSave(text='CREATE',size_hint_x=3)
self.add_widget(self.t1)
self.add_widget(self.l1)
self.add_widget(self.b1)
### Input Color Value
class SlParam(Slider):
def on_touch_up(self,touch):
self.parent.t.text = str(int(self.value))
class TxParam(TextInput):
def on_text_validate(self):
value = int(self.text)
if value <= self.parent.max and value >= self.parent.min:
self.parent.s.value = value
else:
self.text=str(int(self.parent.s.value))
class GridParam(GridLayout):
def __init__(self,**kwargs):
super(GridParam,self).__init__(**kwargs)
self.SetUp()
def SetUp(self):
self.clear_widgets()
self.cols=3
self.min = 0
self.max = 100
self.l = Label(text='none',size_hint_x=1)
self.s = SlParam(min=self.min, max=self.max, value=self.min,size_hint_x=7)
self.t = TxParam(text=str(self.min),multiline=False,size_hint_x=2)
self.add_widget(self.l)
self.add_widget(self.s)
self.add_widget(self.t)
def SetName(self,name):
self.l.text = name
def GetValue(self):
return int(self.parent.s.value)
def SetRange(self,range_min,range_max):
if range_max<range_min:
self.min = range_max
self.max = range_min
else:
self.min = range_min
self.max = range_max
self.s.min = self.min
self.s.max = self.max
self.s.value = self.min
self.t.text=str(self.min)
### Pixel
class Pixel():
def __init__(self):
self.r = 0;
self.g = 0;
self.b = 0;
def Set(r,g,b):
self.r = r;
self.g = g;
self.b = b;
### BMP File cotrol
class BMPFileControl():
def __init__(self):
self.header = HEADER()
# BITMAPFILEHEADER
self.header.bfType=b"BM"
self.header.bfSize=14+40+4
self.header.bfReserved1=0
self.header.bfReserved2=0
self.header.bfOffBits=14+40
# BITMAPINFOHEADER
self.header.biSize=40
self.header.biWidth=1
self.header.biHeight=1
self.header.biPlanes=0
self.header.biBitCount=24
self.header.biCompression=0
self.header.biSizeImage=4
self.header.biXPelsPerMeter=0
self.header.biYPelsPerMeter=0
self.header.biClrUsed=0
self.header.biClrImportant=0
self.image=bytearray(b'\x00\x00\x00\x00')
def GetWidth(self):
return int(self.header.biWidth)
def GetHeight(self):
return int(self.header.biHeight)
def GetPadding(self):
return int((4-(self.GetWidth()*3)%4)%4)
def GetPixel(self,destPixel,x,y):
if x < 0 or x >= self.GetWidth() or y < 0 or y >= self.GetHeight():
return False
linesize = GetWidth()*3+GetPadding()
index = y * linesize + x*3
destPixel.Set(self.image[index+2],self.image[index+1],self.image[index])
return True
def Fill(self,r,g,b):
w = self.GetWidth()
h = self.GetHeight()
padding = self.GetPadding()
self.image=bytearray()
for y in range(h):
for x in range(w):
self.image+=c_int8(b)
self.image+=c_int8(g)
self.image+=c_int8(r)
for p in range(padding):
self.image+=b'\x00'
def ReSize(self,w,h):
if w <= 0 or h <= 0:
return False
self.header.biWidth = w
self.header.biHeight = h
imagesize = int((w*3+self.GetPadding())*h)
filesize = 14+40+imagesize
self.header.bfSize = filesize
self.header.biSizeImage = imagesize
self.Fill(0xff,0xff,0xff)
return True
def FileWrite(self,filepath):
with open(filepath,'wb') as f:
f.write(self.header)
f.write(self.image)
def MakeBMPFile(self,filepath,r,g,b):
self.Fill(r,g,b)
self.FileWrite(filepath)
class Display(BoxLayout):
orientation='vertical'
def __init__(self,**kwargs):
super(Display,self).__init__(**kwargs)
self.t1 = TextInput(text="",size_hint_y=1,multiline=False,readonly=True)
self.add_widget(self.t1)
self.boxSave = BoxSaveFileName(size_hint_y=1)
self.add_widget(self.boxSave)
self.boxFile = BoxSelectFile(size_hint_y=15)
self.add_widget(self.boxFile)
self.gridR = GridParam(size_hint_y=1)
self.gridG = GridParam(size_hint_y=1)
self.gridB = GridParam(size_hint_y=1)
self.gridR.SetName('R')
self.gridG.SetName('G')
self.gridB.SetName('B')
self.gridR.SetRange(0,255)
self.gridG.SetRange(0,255)
self.gridB.SetRange(255,0)
self.add_widget(self.gridR)
self.add_widget(self.gridG)
self.add_widget(self.gridB)
def MakeFile(self):
txtFilepath=self.t1.text+'/'+self.boxSave.t1.text+self.boxSave.l1.text
print(txtFilepath)
bmp = BMPFileControl()
if bmp.ReSize(15,15):
bmp.MakeBMPFile(txtFilepath,int(self.gridR.s.value),int(self.gridG.s.value),int(self.gridB.s.value))
class MainApp(App):
def build(self):
layout = Display()
return layout
if __name__=='__main__':
MainApp().run()
LinuxMint20 AMD64+Python3 で動作を確認しています
※追記:MakeFile 関数の末尾の表示が崩れたので、ひとまず字を小さくしてみました
- C言語の配列と Python のリストは違う
どっちも記述すると
table[100]
とかになりますがね。
例えばC言語で short 型で配列を作れば、2バイト毎の変数のブロックが出来て、バイナリファイルに出力しても同じものができるのですが…
Python の list はどうやらこんな形っぽい。
四半世紀前の情報科で勉強するような奴ですね、この構造は間に物を挟んだり並べ直したりする処理がしやすいですが、ノード同志がメモリ上のどこに有るのかは不明です。
私としては、連続するバイト配列を作ってバイナリ出力したいだけなんですが…
候補は array bytes bytearray とありまして、当初 array を使ったのですが
srcList=[c_byte(b),c_byte(g),c_byte(r)]
destList=array.array('B')
destList.fromlist(srcList)
等と書いていたのですが、最後まで型が違うと怒られまして、埒が明かないので取り扱いを辞めました。
次は bytes bytearray なんですが…。
2020年11月現在、現役エンジニアが解説する某サイトにて bytes をミュータブル、bytearray をイミュータブルと解説してますね。
大石ちゃんは田島さんをグーで殴って良いと思う。
という訳で、結局元通り、bytearray に帰ってきました。
BMPFileControl クラスの Fill 関数の中ですね…1バイトずつ追加…なんとも鈍重です。
- ctypes.Struct を使ってみたのだが…
前回ヘッダ内容をいちいち1バイトずつ入力していましたが、流石に面倒になって、 Python で構造体を使おうとしたのですけどね…これもなぜか struct と ctypes.Strcutre があります。
前述の配列も実は array 系では無く、拡張の NumPy を使えという記事の方が圧倒的に多かったですが…なんで Python のデータの取り扱いってこんなにとっ散らかってるんでしょうかね?
バージョン間の互換性も吹っ飛んでるし、まともな団体が音頭をとっているとは思えません。C言語のK&Rのような(初代は何年か前に亡くなられましたが)団体は居ないんでしょうか。
話が逸れましたが、最初に struct …は仕様を見て使う事を辞めました。
メンバー変数毎のサイズを示す文字列を定義して最初に登録するのですが、ビットマップのヘッダは全部で16個の変数があり、可読性が悪すぎる。
という訳で ctypes.Structure を使って、構造体を定義し、バイナリ出力も出来た…のですが。
どうも正しく表示されない、バイナリエディタで確認したら最初の2バイトの'BM'の後にパディングが2バイト追加されてる…
勝手に4バイトアライメントされている
…余計な事するなと。バイナリファイルを出力するプログラマーで「勝手にパディングを挟んで欲しい」という層って現在多数派なんでしょうか?
ちなみに LinuxMint20 AMD64版 での出力ですが、環境によってアライメントのされ方は違うそうです。
この余計なお世話をとっ外す為には構造体定義の _fields_ の前に _pack_ でアライメントのバイト数を定義してやる必要があります。
これを1にすればアライメントが無いのと同意になるわけです。
ちなみにここら辺は日本語の記述がほとんど無くて海外のサイトで見つけてきたのですが、
_pack_ = True
等と記載されているサイトも有りましたが…この値に3とかを入れてみると3バイトアライメントになる事から、やっぱり bool では無いと思のですがどうなんでしょう?
…以上、Python って短い記述でいろいろできるけど、細かい所はとっ散らかってるなあと愚痴を言いながら書いたコードでした。
0 件のコメント:
コメントを投稿