OpenGLで、1ピクセルの色を変えたいです。C言語ならglDrawPixels()
という関数でできるようなのですが、Rustでのやり方がわかりません。
gl、glium、glutin などの、関連しそうなクレートのドキュメントで「pixel」と検索してみましたが、それらしきものは見当たりませんでした。
経緯
Rustでお絵かきソフトを作ろうとしています。
まずGtk-rsでGUIをつくり、入力イベントを元に画像を作って、それをGTKのDrawingArea
に表示させてみました。DrawingArea
にはcairoを使って書き込むことになるのですが、どうやらcairoには1ピクセルを描画する関数は無いようで、cairo::Context::rectangle()
とcairo::Context::fill()
を使うか、cairo::Context::move_to()
とcairo::Context::stroke()
を使うかしなければいけないようです。
cairo::Context::rectangle()
とcairo::Context::fill()
を使って書いてみたのですが、これらはかなり遅いです。細い線などを描く場合は問題なさそうなのですが、ウインドウをリサイズして全体を描画し直したりする場合はかくつきます。
C言語で同じような問題に直面している人を見つけて、それによると、OpenGLを使うのが良さそうとのことだったので、GTKのGLArea
にOpenGLで画像を描画する方法を探しています。
なぜGtk-rsなのか
- ウィジットのレイアウトが簡単にできる
- ペンタブから筆圧の情報を渡してくれる
- Rustから使える
これらの条件を満たすGUIライブラリが、GtkとQtしか見つかりませんでした。QtのRustバインディングは更新が止まっていて、ドキュメントもexample以外にはほとんどなかったので、GUI初心者には厳しいと思い、Gtkにしました。
もしGTKのGLArea
にOpenGLで描画する以外におすすめの方法があれば、それも教えてほしいです。
追記
flamegraphで各関数の実行にかかっている時間を調べると、次のようになりました。rectangleとfillにかなりの時間がかかっています。
Pythonによるサンプルも追記します。@katsukoさんが書いてくださったサンプルコードを少し書き換えたものです。私が書いているRustのコードを全部載せても良いのですが、@katsukoさんのPythonによるコードがとても簡潔で、こちらを使った方が分かりやすいと思ったので、このようにしました。
簡単にするために、このサンプルでは点を1つ打つごとに全ピクセルを描画し直していますが、Rustで書いているものでは更新があった場所だけ描画しています。なので点を打つのはもっと速いです。ですが、ウインドウをリサイズした時にはこのサンプルと同じように全ピクセルを描画し直しています。リサイズ時の重さは同じくらいです。
python
1# coding: utf-8 2 3import cairo 4import gi 5gi.require_version('Gtk', '3.0') 6from gi.repository import Gtk, Gdk 7 8class App(Gtk.Application): 9 def __init__(self, **kwargs): 10 super().__init__(**kwargs) 11 self.points = [] 12 self.canvas = [[(1, 1, 1)] * 300 for _ in range(400)] 13 self.dragging = False 14 15 def do_activate(self): 16 window = Gtk.Window(application=self, default_width=400, default_height=300) 17 drawing_area = Gtk.DrawingArea(visible=True) 18 drawing_area.add_events(Gdk.EventMask.BUTTON_PRESS_MASK | Gdk.EventMask.POINTER_MOTION_MASK | Gdk.EventMask.BUTTON_RELEASE_MASK) 19 drawing_area.connect('draw', self.on_draw) 20 drawing_area.connect('button-press-event', self.on_button_press_event) 21 drawing_area.connect('motion-notify-event', self.on_motion_notify_event) 22 drawing_area.connect('button-release-event', self.on_button_release_event) 23 window.add(drawing_area) 24 window.present() 25 26 def on_draw(self, widget, cr): 27 cr.save() 28 29 #self.draw1(cr) 30 #self.draw2(cr) 31 self.draw3(cr) 32 33 cr.restore() 34 35 def draw1(self, cr): 36 cr.set_source_rgb(0, 0, 0) 37 for x, y in self.points: 38 cr.rectangle(x, y, 1, 1) 39 cr.fill() 40 41 def draw2(self, cr): 42 cr.set_source_rgb(0, 0, 0) 43 for x, y in self.points: 44 cr.rectangle(x, y, 1, 1) 45 cr.fill() 46 47 def draw3(self, cr): 48 for x, v in enumerate(self.canvas): 49 for y, color in enumerate(v): 50 cr.set_source_rgb(*color) 51 cr.rectangle(x, y, 1, 1) 52 cr.fill() 53 54 def on_button_press_event(self, widget, ev): 55 if ev.type == Gdk.EventType.BUTTON_PRESS and ev.button == Gdk.BUTTON_PRIMARY: 56 self.points += [(int(ev.x), int(ev.y))] 57 for x in range(int(ev.x), int(ev.x)+5): 58 for y in range(int(ev.y),int(ev.y)+5): 59 try: 60 self.canvas[x][y] = (1, 0, 0) 61 except: 62 pass 63 widget.queue_draw() 64 self.dragging = True 65 return True 66 return False 67 68 def on_motion_notify_event(self, widget, ev): 69 if self.dragging: 70 self.points += [(int(ev.x), int(ev.y))] 71 for x in range(int(ev.x), int(ev.x)+5): 72 for y in range(int(ev.y),int(ev.y)+5): 73 try: 74 self.canvas[x][y] = (1, 0, 0) 75 except: 76 pass 77 widget.queue_draw() 78 return True 79 return False 80 81 def on_button_release_event(self, widget, ev): 82 if ev.type == Gdk.EventType.BUTTON_RELEASE and ev.button == Gdk.BUTTON_PRIMARY: 83 self.dragging = False 84 return True 85 return False 86 87if __name__ == '__main__': 88 App().run()
回答3件
あなたの回答
tips
プレビュー
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
2021/03/25 14:25
2021/03/25 14:36 編集
2021/03/25 19:22
2021/03/26 11:51
2021/03/26 17:38