2021年1月5日火曜日

年初の雑記

 あけました。
ええ、寝正月していたらこんな日になっていましたよ。

  • 証券口座の話
大昔に自社株の持ち株会に入っていた関係で口座を持っていたのですが、年末までにマイナンバー登録の書類手続きをしてくれと通知が来ていまして。
丁度休みだったので電話してみたのですが
「登録されていますので手続きしなくて大丈夫ですよ」
…書類手続きした記憶がないのですが…どこから登録されたんだ、そのマイナンバー
「今後証券取引をされるご予定はありますか?」
「いや、会社の持ち株会で口座を作っただけなので、十数年ほったらかしてましたし」そもそも財産と呼べる物を持っていない
「では、特定口座がありましたので解約しておきますね」
…口座ってリスクを説明しないで解約できるものなのか?
ちなみに、特定口座というのは証券のやりとりをした際の税金関係の書類を年末調整前に送ってくれる物らしい。
事務手続き面倒だからこの際バッサリ切ったるわ…という事なんでしょうけど、顧客から電話を受けてすかさず自分の利益の為に切ってくるという。
証券取引というものが怖いのか、野村證券が特別なのか。いきなり年初から魔物を見てしまいました。

  • Grezoo Electronic 製ノートパソコン続報
ひとまず動いてますが…。充電しながら動作使用していると偶に充電が勝手に切れる事があるようです。満充電しない安全機構かと思いきや、それでバッテリ切れ警告をしてきたりするので、普通に接触不良かもしれません。爆発しなけば良いデス、爆発しなければ
カメラも試してみました。
…うん、このくらいのクオリティなんですが、取り込みがとっても遅いデス。
山岳隊の登頂中継かってくらいにとぎれとぎれの絵になるため、とてもコレで映像中継はできませんね。

※ 1/7 追記
元々軽いゲームとは言え、SUperTuxKart(マリオカートのコピーゲーム)くらいなら余裕で動きます。
そして、衝撃的な事実に気が付きました。
私の買ったこのノート、音が出なかったのですが、イヤホンを刺すとスピーカーから音が出ます。
…と、言う事は…恐らくイヤホンを刺していないとイヤホン側に音の信号が流れているのでしょう。
つまり、イヤホンとスピーカーの切り替え回路が逆に接続されているようです。
流石のチャイナクオリティ。ネタ的に美味しかったので満足です。


  • vivo book flip14
前回書いた通り、Blender のアドオン移植ができていないので起動していなかったのですが、流石に初期不良のチェックはしないといけないのでチェックだけ。まあ、正直 Windows10 の認証のくどさに辟易したのですが。ちなみに SSD を換装して Linux Mint にする予定。
で、チェック時に手持ちの Wacom BAMBOO Tip でタッチを試したのですが…相性が悪いのか、設定が悪いのか反応がかなり悪かったです。なんなら指で画を書いた方がマシなレベル。
前述の通り Windows10 で使うつもりがないので設定を詰めるつもりもないのですが、画が画けないなら予定が崩壊するのでなんとかしたい所。

  • イベント
…うーん。正直昨年の運営はゲームを良く知らない所にアウトソーシングしてるんじゃないかと思っていましたが…新年クエストはゲームを解っていて放り込んできた〇ソクエストですねえ。
まず、イベントマップをクエストに放り込む事は過去にもあったのですが、当時は次のマップを出すための条件で今で言う所のギミックだったハズ。
クリア後に既に忘れかけている攻略編成をもう一度組めとか言い出すのはどうかと思います。
そもそも、低難度マップで攻略した人ほど有利になるクエストってなんなんでしょうね。
更に輸送マップが中心だったとは言え「札のついていない弱小艦(空母/水母以外)を3隻ずつ計6隻組み込め」…この時点で下手すると詰みます。
3-4も妙に条件が厳しくて、私は軽、雷x2、水母x2、空母で抜けましたが…下手な組み方をするとボスに行く前に燃料が尽きる厄介なマップなんですよねココ。
5-5が入っていないから手加減した、とか言う認識なんでしょうか。E4削り中断してこんな事やらされると萎えるのですが。
昨年末の時点でイベント突破者が35%しか居ないと認識した上で投入しているのだから正気を疑います。

  • 抱負
毎年達成できないので抱負は書かないのですが…
特にモデリングは、もう無限に課題が沸くので目標を考えません。そりゃあ昔の人が生涯かけて美術に打ち込む訳ですよ。
で、直近の目標は、最近 Python がダレ気味なので、「MOのスケルトン」を組んでみようと思います。恐らく、我が埼玉も遠からず緊急事態宣言がなされるハズで、出た場合、恐らく解除される春までの間に完成させる予定。
…といっても、正月中設計はしましたが、そんなに大仰なものでは無く「自分を作ってログイン」「他人と同じ画面を共有」「動ける」だけ。
サーバー/クライアント含めて1000行に満たないものになる予定。

それともう一つは
今年はもうすこしマシな誕生日を迎えられるようにしよう。切実に。

2020年12月30日水曜日

現在甲E4の3ゲージ目ですが…

 数年振りにイベント突破を諦めようか考え中。
今回のイベントはいろんな意味で「ベテラン殺し」ですね。低難度を全力で突破した方が楽しめる気がしないでもないです。

  • 甲E1-1のボスに2週間かかってしまいまして
薄っすらとしか覚えていないのですが、イタリア、ポーラ、雪風改、リベッチオ、マエストラーレ、飛竜改二のオールダブり艦で開始。
…2週間後にはイタリア/リベ/マエは改に雪風改は丹陽になっていました。
E1だから様子見にダブり艦を投入した訳ですが、後に判った事はイタリア艦の全力投入をすれば良かったという事…。まさかE1の攻略の為に改装設計図を2枚も投入する事態になるとは。
攻略時の編成はこんな感じですが…
E1-2のボスは倒す必要すらないので余裕でした。

  • E2乙は30時間で攻略
E1に時間をかけすぎたので、乙で攻略。そして既に後段の情報が出ていたのでドイツ全力投入。

…ビスマルク3の2隻投入はやり過ぎな気もしました。
甲でもよかったなと後に後悔しています。

  • E3甲は5日で攻略
一番時間がかかったのは潜水艦マスを使った局地戦闘機の熟練度上げです(小声)

うん、ガングートとタシュケントが分身してますね。
最終ギミックに至っては解かずにそのまま殴って終了。ごらんの通り、ダメージを受けた艦すら4隻しか出ていないという低難度でした。

  • 掘り
E4の評判は聞いていたので、掘りへ。今イベントで恐らく一番人気のシロッコはA勝利14回、S勝利8回の末、S勝利にて12/22に取得。
惰性で行ったシェフィールド掘りはS勝利23回目に12/26に取得。

意外だったのは、前回155回S勝利してもとれなかった迅鯨が獲れた事ですかね。
ドロップに関しては豪華かつ、新規ドロップも緩めという印象でしょうか。
ただ、新規ドロップ艦のあるマスのその他ドロップがほとんどコモンの日本艦なのはどうなんでしょう?しかもだいたい駆逐艦か軽巡用艦なので改修にすら使えない状況です。
新規以外のレアドロップも大半は攻略中の道中ドロップでした。

  • E4最初の地獄と言われる第二ゲージは、こんな編成。

有明と霞は大発がん積み、日進と三隈は水戦がん積みで戦力外です。
全員が対空機銃を持ち、4連続対空戦は自力で抜けます。やりがちな話ですが秋月型は入れない方が良いでしょう。対空はともかく火力は低いですから、ボス戦で困る事になります。
支援航空隊はUマスに4回全部をつぎ込み、決戦支援を砲撃でボスに入れます。ボスマスへの支援砲撃で2隻沈める事ができればA勝利は見えるハズ。
大発が6隻あればA勝利でもTPが70以上減るので、そこまで厳しくはないハズ。
…まあ、ボス及び手前のマスで敵が警戒陣形を使ってきたら全てが台無しになるのですが。

雑感
  • 1カ月経過したけど、突破率の公表なんてできるハズが無い
頭でベテラン殺しと書いていますが、これまでの欧州遠征の場合、情報が出るまで欧州勢を温存して、安牌である日本勢を適宜投入するのがセオリーでした。
特に今回はほとんど輸送作戦なので、揚陸艇搭載可能の日本艦を投入した方も居たんじゃないかと思いますが…E4のバナーを後出しした挙句、欧州遠征からまさかのトンボ帰りで「E4の主役は日米艦」。
私は有明と霞改二を温存していたのでなんとかなっていますが、先行組が地獄を観たのでは。一度イベントステージに出撃させると、次以降のイベントステージに出撃できなくなる「札」というシステムが有る以上、公式情報を元に傾向を予測して出撃させるキャラを決めなくてはいけない本ゲーム。
後出しで全く異なる情報を出した以上「今後は最終情報が出るまではイベントで遊ばないでください」と言っているに等しいのだけど理解しているのだろうか?

  • 彼我で警戒陣の価値は異なる
駆逐/軽巡洋艦を的かつ回避盾にして、昼戦の攻撃力を下げる代わりに生存率を上げ、夜戦に賭ける警戒陣。
イベント時にボス戦になんとしても主力を送り届ける為…と、通常のウィークリークエストを楽にクリアする為に利用されてきましたが…今イベントでは敵が多用します。
何隻轟沈しようと昼戦を小破以上で生き延びて雷撃を放てば良い敵方と、1隻でも大破が出たら撤退しないといけない味方では価値が異なります。
E4を地獄に変えている要因の半分が敵が使う警戒陣形だったりします。なにせ対処法もありません。

  • 出撃させたら待つだけ
本ゲームは制空値計算、索敵値計算、CI率の計算など出撃前のセットアップにはやたらに重い計算を求められますが、出撃後は基本的に介入はできません。
あまりに主体性が無い為か、航路選択、戦闘糧食、司令部撤退等が追加されましたが…
航路選択は最初に選択した航路しか基本的に選択しませんし、おにぎり1個分の価値も無いほど勝率が低くない限り糧食は採りますし、司令部撤退が提案される事態になって撤退しない事は稀です。
そんな訳で、疑似的にキャラクターが主体となり、勝ったり負けたりの戦果を持ち帰ってくる訳ですが…おびただしい数の敗北を持ち帰るキャラクターを待ち続ける耐久戦となった最終海域…
キャラは悪くないという擁護もありますし、その通りなんですが、キャラ人気が生命線のゲームでこの現状はどうなのか?

  • 良かった事を探そうか
リビクル…はしなかったのですが、狙って実装したなら面白い試みでしたね(ナーフされましたが)。
消費は大分控えめ。小型艦艇中心の輸送作戦ですから当然と言えば当然ですが。特にボーキは30万以下になった事がありません。
中間ボスを倒しても「あ号」任務のボス撃破にカウントされなかったハズですが、今回は中間ボス撃破後にあ号が完了した事があったんですよね。これが事実なら歓迎できるかもしれません。もっとも、イベント戦に空母と輸送艦が異常に少ないので他のウィークリーが進まないのですが。
北方限定の空母制限は割とアリ。割と妥当な理由で制限があった事と制限解除アイテムを同時実装した事はゲームとしてはアリかと思いました。その内、南方制限等増えるかと思いますがそれはそれで。
ちなみに、海面が凪いでいないと着水できず、着水後はクレーンで釣り上げてもらわないといけない水上機に制限が無いのは何故なんでしょうね?空母以上に制約があるんですが。
あと、北方迷彩って装備は覚えていらっしゃいますでしょうか?

  • 開発運営はこの1年でテレワークに変わったと以前報じていましたが…。
ユンケルの売り上げに記録的な貢献をした鹿島や、前代未聞のバンジージャンプとのコラボなど元々オフイベントで話題を作ってきた本作。
オフイベントを封じられた事でゲーム本体の実力が問われましたが…目立った変化も無く年間通してイベント戦は徒労感を訴える人が多かったですね。

今イベントも最終海域に関しては擁護する声が皆無でした。ニコニコ静画に至っては、公式アンソロに漫画を寄稿している方以外は軒並み難色を示す有様。
まあ、例え難関でも育てたキャラでボスを打ち倒すカタルシスが有れば良いのですが、
「主人公(決戦支援艦隊本隊=NPC)が来るまでボスには手を出すな」
という注意喚起が溢れる始末。
信用できない運営情報、キャラクターにヘイトが集まりやすいゲーム性、「自分の育てた娘は主人公では無い」最終決戦等、問題点が目立つ1年でしたね。

年末ジャンク宝くじ2020

ジャンクの時点で宝じゃないって言うの禁止。…って去年も言いましたね。
何気に昨年動作しなかったCPU+マザボセットもM2SSDソケットが不調なだけで動いたんですけどね…。

VivoBookFlip14 買いました。Ryzen7 版で税込み9.2万円。
この価格と性能を超えるのは Lenovo だけですが、納期が1カ月以上待ちといういい加減さですからね。
ただ、開封はしましたが、けじめとして Blender2.8 用アドオンの開発が終るまでは起動しない…と決めたのが、納品された26日。
…で、去る28日、仕事を納めて早出、早帰りだったのでハードオフに寄り道してしまったのですよ。
高い買い物をした直後、絶対に金を使えない状態だったのですけどね…

禍々しいオーラを放つマシンがあったのですよ。
埃をかぶっているものの、どう見ても新品のプラスティッキーなボディ。メーカーロゴどころか、ステッカーすら貼っておらず、全く情報の無い筐体。
日本製ではありえないUSキーボード。注意書きに CPU Atom x5 z8350 / 2GB RAM / 32GB eMMC の表記のみで、機種名すら不明。
…どう見たって、中国の零細企業が作ったヤバいマシンですし、ブロガーかユーチューバーがネタで買ってスグに売り払った物にしか見えません。

しかもお値段税込み 4950 円。
絶対罠って判っていたんですけどね。

実用品を入手したから、ジャンクには手を出さない?
…実に空虚な人生じゃあありゃあせんか?

…明日から何食って生きていくんだ俺。という訳で買ってしまいました。
外観。
キーボード…ほぼ新品。タッチパッド…厚紙にビニールを張ったような手作り感。ちなみに画面はバックライトが強い事もあって案外見やすいです。
ヒンジは180度まで開口。閉める時はかっちり閉まりますが…開ける時はミシミシ音がします。ヒンジ金具の剛性があっても筐体がプラスチックなので、そちらが先に割れるかも。
欲を言えば、こんだけベゼルが広いなら左右のベゼルにスピーカーを付けた方が良かったんじゃないですかね。

検索したら案の定 Alibaba.com にて
Thin Ultrabook Z8350 2GB 32GB 10.1 inch Win 10 Mini laptop
という名前でヒット。…にも関わらず作ったメーカーは最後まで不明。
※追記 後日調べた所 Shenzhen Grezoo Electronic Technology Co., Ltd. 製と判明
解像度…聞いた事も無いですね。
3週間前にWin10をセットアップされています。この機械にインストールするにはさぞや苦労をされた事と思いますが…

当然の権利のように LinuxMint をインストール。
丁度出ていた 20.1 のβ版。そう、x5 及び x7 シリーズの Atom は以前のイオシスの奴とは違い 64Bit OS が乗ります。
※要、/efi/boot への bootia32.efi 追加。判らない人は手を出さない方が無難です

当初、変な解像度のマシンにありがちな縦画面出力になっていまして、起動時に90度回転させる設定作りに苦労しました。
先人の方々が有用な記事を残していますので、丸パクりな記事を書く事ははばかられますが…
Linux Mint Mate 20.x で行う場合

1.xrandr で右に90度回転させるコマンドスクリプトをファイル(~.sh)を作る
2.メニュー→設定→自動起動するアプリ で起動時に呼び出す
以上の操作をすれば実現可能でした。

で、実際に動作せてみると…OSにしろ、アプリにしろ、起動は遅いです。シングルチャンネル2GBのメモリと eMMC の組み合わせに期待してはいけない。

とりあえず、Geany と kivy の組み合わせで軽いアプリ開発ができれば…
できる。
まさか Blender が動くなんて事は
…なん・・・だと・・・
ほぼ 24fps で動作チェックができる。そんなバカなと思ってタスクマネージャーで確認したら、メモリはカツカツなもののCPUパワーに余裕がある。
まあ、普段使っている Blender2.7 系と Blender2.8 以降はGPUの依存度が違うので iGPU で勝るからだとは思いますが…まさか動くとは。

総評
Z8350 は検索候補に「ゴミ」と出てくるCPUなんですが…使いようかもしれません。少なくとも切れないハサミでは無い。筐体としては、スピーカーとカメラは使っていません。最初から期待するような代物ではありません。Wifi は2.4Ghz止まりで5GHzは繋がりませんでした。タッチパッドは、まあ…そうねえ。

なのですが、基本は押さえていて。
割と見やすい画面。安っぽいけど打ちやすいキーボード、キーの間に2mm弱の間隔が空いています。打鍵感は判りやすく書けば「でかい電卓」。

電源キーが右上にあるので、BSやDelと間違えて打ってしまう事故は多発しますが、慣れでしょう。端子の配置は左がUSB3.0+HDMI、右がUSB2.0、イヤホン、TFであり、右のUSBはほぼマウス用と考えてよく、使いやすい配置です。重量は1kg以下。持ち歩くなら何かしらのケースに入れないと割れそうですが。

数万円もしたらもちろん買いませんが、用途を抑えて1万円以下なら割とアリかも。
ちなみに私は枕元で kivy アプリを作ったり、Blender アドオンを作ったりという「寝る前にやっておこう」的な用途で使う予定です。

2020年12月24日木曜日

腹筋、背筋の修正

 前回腹直筋はまあ、こんなもんかという所までできたのですが、今回は腹側及び背筋です。
まず、背骨が3本のボーンでできていたのですが、6本に増量。肋骨に繋がるボーンは1本ですので、実質は2本から5本に増量。
又、体の中心にあった背骨のボーンを脊椎の位置に沿う形に再配置。
ただし、ひねり動作の関係で初期状態は上向きでないといけないので、このような形に。
before

after

操作系のボーンも拡充しまして、手足、胴体、首及び、体の中心の方向転換まで外部の誘導ボーンで可能になりました。
ただ、ボーン数が片面で440本を超えてエライ事に。

で、実際動かしてみるとこんな感じ。
横っ腹の「キレてる」感じは割と好き

背面は広背筋や、脊柱起立筋の隆起を作ったのですが…それほど作用しないんんですよね。
又、左右に傾けた際に横腹が骨盤に埋没しますが…こればっかりは…対処方法は無いではないんですけどね。

さて、次はアドオンの移植(できれば、当ブログで google drive を使って公開したいけど、方法を調べ中)、そして悲願の2.9への移行ですねえ。
顔の制作は別レイヤーで行う予定ですが、レイヤーの概念が2.7以前と2.8以降では違う為、ココまで来ると変えざるを得ない。
当初今年の夏くらいには移行したかったのですが、来年まで引っ張る事になるとは。
そして、顔を作るという事はテクスチャ描きに挑戦するという事であり、とうとうニューマシンの導入待ったなし。
…財政はかなりひっ迫していますが、せっかくココまで来たので下手な物を買って進歩を止めたくないというのが本音。
願わくば、次のマシンの能力を使い切る頃にはモデリングが完了して欲しいものです。

2020年12月1日火曜日

体中心部の調整

 最近モデリングとプログラミングしかしていないような。
いや、イベントはE-1-1のボスがね…固い上に、イタリア艦をスナイプするのは辞めてくれ…あと、やたらに高い回避も勘弁してくれ…
さて、そんな訳で前回の腹筋の続き…まあ、腹筋はコレで良いのではないでしょうか。
そもそも、このモデル下半身にパースがかかってますし、腹筋が長いのは織り込み済みなので。
ただ、背中の筋肉が無さ過ぎるのが気になりますが。又、三角筋(肩)や大臀筋(尻)も修正していますが、そこらは次に機会があれば。
首は以前書いた通り、襟が通らなくなる為簡素にしています。本来ならこんなに首が細いと格闘には向きませんし、胸鎖乳突筋とかもう少し強調したい所ではありますが。

で、この脊椎と頸椎のボーンですが、それぞれ3本のボーンで行っていますが、操作は各目標ボーンと捻り用ボーンのみ。
…この構造を腕と腿に移植すれば、ほとんどのポーズが目標ボーンの移動だけで表現できるのでは…。
MMDでよく行われている方法で、私はIKが嫌いなのでやっていなかったのですが…

…いざリギングをしてみて、ボーンの密度がこんなレベルになってしまうと、このなかから動かすボーンをみつけて振り回したり捻ったりの動作をつけるのは至難。
手の指や表情はともかく、手足と胴だけでも外部のボーン移動で制御できるようにしないとモーションが組めない気がします。

あと、モデリング用に使っていたノートのCF-AX2ではそろそろ本当に限界がきそう。
 Blender はボーンの表示がポリゴン表示より何故か重いので、ボーンを表示させると編集にラグが出るし、そのままモーションをさせると 4fps とかになってしまう。
現状の候補は Asus Vivobook flip なのですが…1.55㎏というのが若干気になりますね。

2020年11月22日日曜日

Blender で「オートネーム左/右」の代替アドオンを作ってみた

 …いや、なんでそんなもん作る必要があったのよ?って話なんですが。
以前、左右対称化をしたモデルを再度編集用の片面モデルに戻すアドオンを作りまして、良し、これで全身モデルを見ながら編集作業ができるぞと思ったわけですよ。
で、以前作った、横隔膜の動作を試したのですが…
腹筋半壊。
腹筋操作ボーンは体の中心に作っており、数値上も X の値が0.000になっています。
予定では、この操作ボーンを動作させることで、左右の腹筋が動く予定だったのですが。
…調べてみたら、プログラミングの実数型にありがちな「微妙に数値があっていない」現象により、体の中心に置いたはずの腹筋操作ボーンが微妙にずれて、左右に2本作られた為、左側の腹筋ボーンのみの動作になったようです。
微妙にずれている為、標準のオートネームでは末尾にL/Rが付いてしまう

この現象の一般的な対処法は、3Dカーソルを中心付近に動かして、グリットに吸着させて X=0 の位置に固定し、エディットモードで中心付近のボーンを選択して、3Dカーソルに対してX軸スケール→0と選択する事ですが…ジンバルが起こるらしく見事にモデルが壊れてしまいました。
そもそも、こんな面倒な操作は毎回やっていられないので、アドオンを作成。

コード

import bpy

if bpy.context.mode == 'EDIT_ARMATURE':
    for obj in bpy.context.selected_objects:
        # Use armature only
        if obj.type != 'ARMATURE':
            continue
        for bone in obj.data.edit_bones:
            if bone.select == False:
                continue
            if (bone.name[-2:]=='.R')or(bone.name[-2:]=='.L'):
                bone.name = bone.name[:-2]
            if (bone.head.x<0.0001)and(bone.tail.x<0.0001)and(bone.head.x>-0.0001)and(bone.tail.x>-0.0001):
                bone.head.x=0.0
                bone.tail.x=0.0
                continue
            if (bone.head.x>=0.0)and(bone.tail.x>0.0):
                bone.name = bone.name+'.L'
                continue
            if (bone.head.x<=0.0)and(bone.tail.x<0.0):
                bone.name = bone.name+'.R'

※一部表示が崩れるので文字を小さくしています

ベースになっているのは以前作ったボーンロール値を0にするアドオンです。
操作方法は編集モードでリネームするボーンを選んで実行。
動作は

1.選択されていないボーンを弾く
2.ボーン名の末尾に「.L」「.R」とついていた場合は外す(〜.L.L等、2重にリネームされないようにする為)
3.ボーンのヘッド/テールのX座標が +-0.0001 の範囲に入る場合はリネームせず、 x の値を 0.0 で改めてセットする
4.ボーンのX座標を確認してリネーム

という動作。
で、これで再度やってみると…
できた。
念の為、お腹を傾けて同じ動作をさせてみる。
…まあ、出来はしたんだけど、お腹の形状はいろいろと難ありなので修正は必要かもしれないですね。

2020年11月3日火曜日

Python+Kivy 地獄編 続・無意味なアプリを作って地獄の扉を開けてみる

 以前作った、任意の色で四角形の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 って短い記述でいろいろできるけど、細かい所はとっ散らかってるなあと愚痴を言いながら書いたコードでした。