質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.35%
Python 3.x

Python 3はPythonプログラミング言語の最新バージョンであり、2008年12月3日にリリースされました。

Python

Pythonは、コードの読みやすさが特徴的なプログラミング言語の1つです。 強い型付け、動的型付けに対応しており、後方互換性がないバージョン2系とバージョン3系が使用されています。 商用製品の開発にも無料で使用でき、OSだけでなく仮想環境にも対応。Unicodeによる文字列操作をサポートしているため、日本語処理も標準で可能です。

Q&A

解決済

1回答

4224閲覧

wxpythonで透過gifと背景gifを同時に重ねて再生表示する方法

dendenmushi

総合スコア98

Python 3.x

Python 3はPythonプログラミング言語の最新バージョンであり、2008年12月3日にリリースされました。

Python

Pythonは、コードの読みやすさが特徴的なプログラミング言語の1つです。 強い型付け、動的型付けに対応しており、後方互換性がないバージョン2系とバージョン3系が使用されています。 商用製品の開発にも無料で使用でき、OSだけでなく仮想環境にも対応。Unicodeによる文字列操作をサポートしているため、日本語処理も標準で可能です。

1グッド

1クリップ

投稿2020/08/01 15:57

編集2020/08/02 04:51

前提・実現したいこと

wxpythonでgif動画を再生するデスクトップアプリを作っています。
gif動画の一方を透過色が入ったもので、2重に再生し、ボタンも表示したい。

理想は以下のイメージになります。
イメージ説明

ソースコード

同フォルダにあるgif画像を10秒おきに入れ替え再生していますが、同時に再生したいと思っております。

python

1import os 2import sys 3import time 4from threading import Thread 5 6import wx 7from wx.adv import Animation, AnimationCtrl 8from wx.lib.newevent import NewEvent 9 10UpdateImageEvent, EVT_UPDATE_IMAGE = NewEvent() 11 12 13class CountTimer(Thread): 14 daemon = True 15 16 def __init__(self, win): 17 super().__init__() 18 19 # イベント通知対象のウィジェット。(MainWindow) 20 self.win = win 21 22 # 次の画像ファイルを所得する関数 (仮: カレントディレクトリの gif ファイルが対象) 23 from glob import glob 24 from itertools import cycle 25 from functools import partial 26 self.nextImageFile = partial(next, cycle(glob("*.gif"))) 27 28 def run(self): 29 num = 0 30 while True: 31 num += 1 32 if num % 10 == 0: # 剰余で十秒毎を判別 33 34 # 次に表示する画像ファイル 35 filepath = self.nextImageFile() 36 37 # イベント通知する為、引数となるイベントオブジェクトを作成 38 event = UpdateImageEvent(filepath=filepath) 39 40 # 通知 (MainLoopのキューにイベントを入れる) 41 wx.PostEvent(self.win, event) 42 43 del event, filepath 44 time.sleep(1) 45 46 47class MainWindow(wx.Frame): 48 def __init__(self, parent=None, ID=wx.ID_ANY, title="", *args, **kw): 49 super().__init__(parent, ID, title, *args, **kw) 50 self.animeCtrl = AnimationCtrl(self) 51 52 # イベントとイベント通知受信時に呼び出されるコールバック関数の結び付け 53 self.Bind(EVT_UPDATE_IMAGE, self.OnUpdateImage) 54 55 def OnUpdateImage(self, event): 56 # PostEvent での通知により *メインスレッドで* 実行される 57 print("OnUpdateImage", event.filepath) 58 self.SetImageFile(event.filepath) 59 60 def SetImageFile(self, filepath): 61 # 画像・動画を変更し再生する 62 assert os.path.isfile(filepath) 63 self.animeCtrl.SetAnimation(Animation(filepath)) 64 self.animeCtrl.Play() 65 66 67def main(): 68 app = wx.App() 69 win = MainWindow() 70 win.SetSize(400, 400) 71 win.Centre() 72 win.Show() 73 74 thread = CountTimer(win) 75 thread.start() 76 77 app.MainLoop() 78 79 80if __name__ == '__main__': 81 main()

Architectureと発生している問題・エラーメッセージ

wx.Posteventの方法で試した場合、ひとつのeventのみ呼び出されてしまい、レイヤーのように別々に操作することができませんでした。

使用ファイル

以下に、gif動画ひとつと透過pngをgif化したものを配置しました。(透過gifになっていなかったらすみません。)
Google Drive

参考サイト

gif画像を一枚ずつの画像へ分解するサイト
透過画像作成サイト
透過pngをgif動画にするサイト
アニメーションについて調べたサイト
アニメーションについて調べたサイト2
過去teratailでの似ている質問(透過gif)

2つのgifを重ねて一枚一枚保存したものをgifにしてそれを1つのイベントで実行という方法がありますが、
私は、あるイベント発生(クリックなど)によって一枚だけのレイヤーに存在する透過gifを他のものに変えたり最終的にはしたいため、イベント自体を2個同時に実行するのがよいと考えております。そもそもイベントを重ねてgifを表示することじたいが調べているのですがでてこなく、知見ある方何かアドバイス頂けないでしょうか。
よろしくお願い致します。

補足情報(FW/ツールのバージョンなど)

win10
python3.7
wxpython

追記 2020/08/02

以下など試してみました。
《別方法1》

python

1class MainWindow(wx.Frame): 2 def __init__(self, parent=None, ID=wx.ID_ANY, title="", *args, **kw): 3 super().__init__(parent, ID, title, *args, **kw) 4 self.animeCtrl = AnimationCtrl(self) 5 6 # 20200801 add 7 panel1 = wx.Panel(self, wx.ID_ANY) 8 v_layout = wx.BoxSizer(wx.VERTICAL) 9 # ButtonA 10 feature_button = wx.Button(panel1, wx.ID_ANY, "Feature") 11 feature_button.Bind(wx.EVT_BUTTON, self.feature_method) 12 v_layout.Add(feature_button, proportion=0, flag=wx.ALIGN_CENTER_HORIZONTAL) 13 panel1.SetSizer(v_layout) 14 15 def feature_method(self, event): 16 wx.MessageBox('正解です!')

《別方法2》

python

1def main(): 2 app = wx.App() 3 win = MainWindow() 4 win.SetSize(800, 800) 5 win.Centre() 6 win.Show() 7 # 20200801 add 8 win_sub = SubWindow() 9 win_sub.SetSize(800, 800) 10 win_sub.Centre() 11 win_sub.Show()

SubWindowクラスを作るなど試行錯誤しましたが同時に表示はできませんでした。

《別方法3》

python

1import wx 2import wx.adv 3 4class BG(object):#背景の表示 5 def __init__(self, MainPanel,x,y): 6 BGimg =wx.Image("1.png") 7 self.bitmap = BGimg.ConvertToBitmap() 8 wx.StaticBitmap(MainPanel, -1, self.bitmap, (x,y)) 9 10class MyWindow(wx.Frame): 11 def __init__(self, parent=None, id=-1, title=None): 12 wx.Frame.__init__(self, parent, id, title) 13 self.MainPanel = wx.Panel(self, size=(1200, 800))#メイン画面の大きさ 14 self.MainPanel.SetBackgroundColour("Black") 15 self.BackG = BG(self.MainPanel, 0, 0) 16 self.anime = wx.Panel(self.MainPanel, pos=(350, 0), size=(450, 800), 17 style=wx.MINIMIZE_BOX | wx.MAXIMIZE_BOX | wx.SYSTEM_MENU | wx.CAPTION | wx.CLOSE_BOX | wx.CLIP_CHILDREN) 18 self.file = "sakura.gif" 19 self.ani = wx.adv.Animation(self.file) 20 self.ctrl = wx.adv.AnimationCtrl(self.anime, -1, self.ani, pos=(10, 10), style=wx.TRANSPARENT) 21 self.ctrl.Play() 22 self.Fit() 23 24if __name__ == "__main__": 25 app = wx.App() 26 w = MyWindow(title="test") 27 w.Center 28 w.Show() 29 app.MainLoop()

イメージ説明

この方法でも重ねることはできませんでした。

《別方法4》

python

1 2import wx 3import wx.adv 4 5GIFNames = [ 6 'sakura.gif' 7 ] 8 9 10class TestPanel(wx.Panel): 11 def __init__(self, parent): 12 13 wx.Panel.__init__(self, parent, -1) 14 15 sizer = wx.FlexGridSizer(2,3,5,5) 16 17 for name in GIFNames: 18 ani = wx.adv.Animation(name) 19 ctrl = wx.adv.AnimationCtrl(self, -1, ani) 20 ctrl.SetBackgroundColour('black') 21 ctrl.Play() 22 23 #sizer.AddF(ctrl, wx.SizerFlags().Border(wx.ALL, 10)) 24 25 border = wx.BoxSizer() 26 #border.AddF(sizer, wx.SizerFlags(1).Expand().Border(wx.ALL, 20)) 27 self.SetSizer(border) 28 29app = wx.App() 30frame = wx.Frame(None) 31panel = TestPanel(frame) 32frame.Show() 33app.MainLoop()

イメージ説明
イメージ説明

透過はできるようにはなったようなのですが、その背景に別gifを入れること(まずはバックグラウンドを黒)に挑戦していますが、黒背景もまだでませんしもう少しのようです。
そもそも、黒背景ではなく、もう一つの背景gifじたいもユーザーのアクションによって変えたいので端的に背景に挿入しても変更できないのであれば問題ですが…

《別方法5》

python

1import wx 2 3class Mywin(wx.Frame): 4 5 def __init__(self, parent, title): 6 super(Mywin, self).__init__(parent, title = title,size = (500,300)) 7 self.InitUI() 8 9 def InitUI(self): 10 self.Bind(wx.EVT_PAINT, self.OnPaint) 11 self.Centre() 12 self.Show(True) 13 14 def OnPaint(self, e): 15 dc = wx.PaintDC(self) 16 brush = wx.Brush("white") 17 dc.SetBackground(brush) 18 dc.Clear() 19 20 dc.DrawBitmap(wx.Bitmap("1.png"),10,10,True) 21 color = wx.Colour(255,0,0) 22 b = wx.Brush(color) 23 24 dc.DrawBitmap(wx.Bitmap("vqkSsK_Z-0.png"),10,10,True) 25 color = wx.Colour(255,0,0) 26 b = wx.Brush(color) 27 28 dc.SetBrush(b) 29 dc.DrawCircle(300,125,50) 30 dc.SetBrush(wx.Brush(wx.Colour(255,255,255))) 31 dc.DrawCircle(300,125,30) 32 33 font = wx.Font(18, wx.ROMAN, wx.ITALIC, wx.NORMAL) 34 dc.SetFont(font) 35 dc.DrawText("Hello wxPython",200,10) 36 37 pen = wx.Pen(wx.Colour(0,0,255)) 38 dc.SetPen(pen) 39 dc.DrawLine(200,50,350,50) 40 dc.SetBrush(wx.Brush(wx.Colour(0,255,0), wx.CROSS_HATCH)) 41 dc.DrawRectangle(380, 15, 90, 60) 42 43ex = wx.App() 44Mywin(None,'Drawing demo') 45ex.MainLoop()

イメージ説明

DrawBitmapというものを使えば重ねて表示ができそうです。ただこの場合ですとgifで対応できないようです。
近づいてはいるとは思いますが、引き続きアドバイスよろしくお願い致します。

teamikl👍を押しています

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答1

0

ベストアンサー

イメージ説明

課題が幾つかありますが、重ね合わせはこんな感じでしょうか。

問題の原因、
AimationCtrl 自身が背景色を持ち、
画像が透過になっても表示されるのは、AnimationCtrl の背景色になるようです。

解決策: 独自に描画する

SubWindow を使う方法でもできそうですが、(shaped windowで検索、少し難しいです)
ウィンドウを動かした時等、他の課題も出てきそうなので、
タイマーを使って自身でアニメーション描画を実装する方法を紹介します。

※ 但し、問題が複雑化するため、画像ファイルの切り替えは、省きました。
以下のコードは透過動画の重ね合わせ部分のみです。

python

1 2import wx 3import wx.adv 4 5 6def callLaterTimedGen(gen, done=None, stop=None): 7 def next_gen(): 8 interval = next(gen, stop) 9 if interval is not stop: 10 wx.CallLater(interval, next_gen) 11 wx.CallAfter(next_gen) 12 return gen 13 14 15class MyFrame(wx.Frame): 16 def __init__(self, parent=None, *args, **kw): 17 super().__init__(parent, *args, **kw) 18 self.bmp1 = None 19 self.bmp2 = None 20 self.Bind(wx.EVT_PAINT, self.OnPaint) 21 self.Bind(wx.EVT_TIMER, lambda _: self.Refresh()) 22 23 self.timer = wx.Timer(self) 24 self.timer.Start(200) 25 26 def SetBitmap1(self, bmp): 27 self.bmp1 = bmp 28 29 def SetBitmap2(self, bmp): 30 self.bmp2 = bmp 31 32 def OnPaint(self, event): 33 dc = wx.PaintDC(self) 34 if self.bmp1: 35 # 背景 (mask: False) 36 dc.DrawBitmap(self.bmp1, 0, 0, False) 37 if self.bmp2: 38 # 透過 (mask: True) 39 dc.DrawBitmap(self.bmp2, 0, 0, True) 40 41 42def gifAnimation(filepath, setBitmap, useMask=None): 43 anim = wx.adv.Animation(filepath) 44 45 from itertools import cycle 46 for idx in cycle(range(anim.GetFrameCount())): 47 delay = anim.GetDelay(idx) 48 frame = anim.GetFrame(idx) 49 if useMask: 50 frame.SetMaskColour(*useMask) 51 yield delay 52 setBitmap(frame.ConvertToBitmap()) 53 54 55def main(): 56 app = wx.App() 57 win = MyFrame(None, wx.ID_ANY, "透過GIF重ね合わせテスト") 58 win.Centre() 59 win.Show() 60 61 button1 = wx.Button(win, label="TEXT", pos=(20, 100)) 62 button2 = wx.Button(win, label="TEXT", pos=(20, 150)) 63 64 gen1 = callLaterTimedGen(gifAnimation("someiyoshino1.gif", win.SetBitmap1)) 65 gen2 = callLaterTimedGen(gifAnimation("index.gif", win.SetBitmap2, useMask=(0, 0, 0))) 66 67 wx.CallLater(3000, gen2.close) # 3秒後に index.gif の再生を停止 68 69 app.MainLoop() 70 71 72if __name__ == '__main__': 73 main() 74 75

要求知識: Pythonのジェネレータについて

  • callLaterTimedGen 関数 (ジェネレータをタイマーで読み出す)
  • gifAnimation 関数 (アニメーション処理をジェネレータとして実装)

wx.adv.Animation で提供されてるメソッド

  • GetFrameCount ... 総フレーム数
  • GetFrame ... 静止画フレーム
  • GetDelay ... アニメーションに用いる遅延時間 (ms)

アニメーションの実装はシンプルに
delay秒待ってから フレームを変換し setBitmap という処理を行ってます。

callLaterTimerGen() 関数は、
wxのタイマーでジェネレーターから値を一つづつ読み出します。
yield で返された値を次の読み出しまでの timeout 時間として用いるので、
yield delay を この関数で実行する タイマー上での sleep として扱えます。

wx については、説明は省略します。概要のみ。

  • wxPython のタイマー wx.CallLater, wx.CallAfter 指定時間後に一度のみ実行
  • wxPytnon のタイマーイベント wx.Timer 一定時間毎に定期実行
  • EVT_PAINT 描画イベントとPaintDC を使った独自描画の方法

課題:

  • 画像の切り替えですが、wx.Timer を使ったほうが簡単で良いかもしれません
    スレッド間通信の必要がなくなります。
  • index.gif は透過になっていませんでした。
  • 黒色をマスク指定しましたが、花びらの周りに淡い紫色?がありました。
  • 事前加工が可能なら、numpy, OpenCV, PIL 等で加工してください。

 参考: 画像のしきい値処理 threshold 辺りを検索

  • 画像ファイルの切り替えは幾つか方法がありますが、1案

 1. 10秒毎にタイマー関数呼び出し
2. 前のアニメーションを止めて (gen.closeで停止)
3. 新アニメーションのタイマーを開始

投稿2020/08/02 05:37

teamikl

総合スコア8760

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

teamikl

2020/08/02 05:43

午前中に書き込んだのですが、投稿エラーになったまま気が付かなかった。 DrawBitmap での描画まではできてる様なので、後少しですね。 回答にも書きましたが、アニメーションの実装方法(要約) - wx.adv.Animation でGIFファイルを読みこみ - GetFrameCount, GetFrame, GetDelay とタイマーを使って  時間毎に bitmap を差し替え - 画像更新時に Refresh を呼ぶ(再描画)
dendenmushi

2020/08/02 06:36

いつも驚かされてしまいます…。今からじっくり見ます。本当にありがとうございます。
dendenmushi

2020/08/02 12:25

非常に勉強になりました。ありがとうございました。ボタンクリックで背景gifを変えようと欲がでてしまいましたが、工夫が必要なのですね。 ''' class MyFrame(wx.Frame): def __init__(self, parent=None, *args, **kw): super().__init__(parent, *args, **kw) self.bmp1 = None self.bmp2 = None self.Bind(wx.EVT_PAINT, self.OnPaint) self.Bind(wx.EVT_TIMER, lambda _: self.Refresh()) self.timer = wx.Timer(self) self.timer.Start(200) # panel = wx.Panel(self) #ボタンの作成 self.Button1=wx.Button(self, label="exit",pos=(20,100)) self.Button2=wx.Button(self, label="print",pos=(20,150)) #ボタンを割り当て self.Bind(wx.EVT_BUTTON, self.close, self.Button1) self.Bind(wx.EVT_BUTTON, self.printer, self.Button2) self.Show(True) def close(self,event): self.Close(True) def printer(self, event): print("Button2") callLaterTimedGen(gifAnimation("someiyoshino1.gif", self.SetBitmap1)).close callLaterTimedGen(gifAnimation("someiyoshino2.gif", self.SetBitmap1)) ''' このコードにして追加で挑戦してみましたがチカチカと完全に元のが消えないですね。 質問の件につきまして本当に助かりました。ありがとうございました。
teamikl

2020/08/02 13:02

最後の2行が self.SetBitmap1 になってますね。同じ画像が交互に切り替わる。 close は 呼び出しのカッコがついてないので、このコードでは無意味になってます。 画像切り替えは、ジェネレータオブジェクトを クラスのインスタンス変数等に格納しておいて、ボタンのイベントで self.gen1.close() self.gen1 = callLaterTimesGen( ... )
dendenmushi

2020/08/02 13:24

全て望みの形ができました。本当にありがとうございました。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.35%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問