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

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

新規登録して質問してみよう
ただいま回答率
85.48%
GTK+

GTK+とはGUIを作るために作られた、とても使いやすく機能に富んだツールキットです。クロスプラットフォームによる互換性と、使いやすいAPIを備えています。

OpenGL

OpenGLは、プラットフォームから独立した、デスクトップやワークステーション、モバイルサービスで使用可能な映像処理用のAPIです。

Rust

Rustは、MoFoが支援するプログラミング言語。高速性を維持しつつも、メモリ管理を安全に行うことが可能な言語です。同じコンパイル言語であるC言語やC++では困難だったマルチスレッドを実装しやすく、並行性という点においても優れています。

Q&A

解決済

3回答

2180閲覧

Rustを使ってOpenGLで1ピクセルを描画したい

nanikamado

総合スコア13

GTK+

GTK+とはGUIを作るために作られた、とても使いやすく機能に富んだツールキットです。クロスプラットフォームによる互換性と、使いやすいAPIを備えています。

OpenGL

OpenGLは、プラットフォームから独立した、デスクトップやワークステーション、モバイルサービスで使用可能な映像処理用のAPIです。

Rust

Rustは、MoFoが支援するプログラミング言語。高速性を維持しつつも、メモリ管理を安全に行うことが可能な言語です。同じコンパイル言語であるC言語やC++では困難だったマルチスレッドを実装しやすく、並行性という点においても優れています。

1グッド

0クリップ

投稿2021/03/25 12:56

編集2021/03/25 21:15

OpenGLで、1ピクセルの色を変えたいです。C言語ならglDrawPixels()という関数でできるようなのですが、Rustでのやり方がわかりません。

glgliumglutin などの、関連しそうなクレートのドキュメントで「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にかなりの時間がかかっています。
flamegraph

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()
tatsuya6502👍を押しています

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

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

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

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

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

guest

回答3

0

ベストアンサー

どうやらcairoには1ピクセルを描画する関数は無いようで、

ワンショットでピクセルを描画する関数がないだけで、幅・高さを1pixelの矩形を描画すればいいのではないでしょうか。
(以下はPythonですが、Pythonを知らなくても何をやっているかは理解できると思います)

python

1# coding: utf-8 2 3import gi 4gi.require_version('Gtk', '3.0') 5from gi.repository import Gtk 6 7class App(Gtk.Application): 8 def do_activate(self): 9 window = Gtk.Window(application=self, default_width=400, default_height=300) 10 drawing_area = Gtk.DrawingArea(visible=True) 11 drawing_area.connect('draw', self.on_draw) 12 window.add(drawing_area) 13 window.present() 14 15 def on_draw(self, widget, cr): 16 cr.save() 17 18 cr.set_source_rgb(0, 0, 0) 19 cr.rectangle(100, 100, 1, 1) 20 cr.fill() 21 22 cr.restore() 23 24if __name__ == '__main__': 25 App().run()

イメージ説明
拡大してみないと分かりづらいですが、GIMPで開いて拡大すれば、ちゃんと1pixel書かれていることを確認できます。


というわけで、cairoで十分かと思いますが、補足。
GTK+でOpenGLを試してみたことがありますが、どうも不安定、という印象があり、OpenGLを使うのであればQtの方がいいなぁ、というのが自分の考えです。
まぁ、あくまで自分の考え、ですが。


失礼、よく読むと試してみて遅い、と書かれてますね。

もし、cairo_rectanglecairo_fillをピクセル数分呼んでいるのであれば、cairo_fillは最後に一回だけ呼ぶようにしてもダメですか?

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.dragging = False 13 14 def do_activate(self): 15 window = Gtk.Window(application=self, default_width=400, default_height=300) 16 drawing_area = Gtk.DrawingArea(visible=True) 17 drawing_area.add_events(Gdk.EventMask.BUTTON_PRESS_MASK | Gdk.EventMask.POINTER_MOTION_MASK | Gdk.EventMask.BUTTON_RELEASE_MASK) 18 drawing_area.connect('draw', self.on_draw) 19 drawing_area.connect('button-press-event', self.on_button_press_event) 20 drawing_area.connect('motion-notify-event', self.on_motion_notify_event) 21 drawing_area.connect('button-release-event', self.on_button_release_event) 22 window.add(drawing_area) 23 window.present() 24 25 def on_draw(self, widget, cr): 26 cr.save() 27 28 #self.draw1(cr) 29 self.draw2(cr) 30 31 cr.restore() 32 33 def draw1(self, cr): 34 cr.set_source_rgb(0, 0, 0) 35 for x, y in self.points: 36 cr.rectangle(x, y, 1, 1) 37 cr.fill() 38 39 def draw2(self, cr): 40 cr.set_source_rgb(0, 0, 0) 41 for x, y in self.points: 42 cr.rectangle(x, y, 1, 1) 43 cr.fill() 44 45 def on_button_press_event(self, widget, ev): 46 if ev.type == Gdk.EventType.BUTTON_PRESS and ev.button == Gdk.BUTTON_PRIMARY: 47 self.points += [(int(ev.x), int(ev.y))] 48 widget.queue_draw() 49 self.dragging = True 50 return True 51 return False 52 53 def on_motion_notify_event(self, widget, ev): 54 if self.dragging: 55 self.points += [(int(ev.x), int(ev.y))] 56 widget.queue_draw() 57 return True 58 return False 59 60 def on_button_release_event(self, widget, ev): 61 if ev.type == Gdk.EventType.BUTTON_RELEASE and ev.button == Gdk.BUTTON_PRIMARY: 62 self.dragging = False 63 return True 64 return False 65 66if __name__ == '__main__': 67 App().run()

…と思って上記のサンプルを書きましたけど、本当に重いですか?
うちのPCはCorei3のそんなにいいPCではありませんですけど、draw1でも全然遅い感じはしなかったのですが。

もしそれでもダメであれば、cairo_image_surfaceに点を書いておいて、通常のdrawシグナルで処理する場合はそのsurfaceを描画するようにするのも常套手段かと思います。
もっとも、ウィンドウリサイズ時の処理がめんどくさそうですが。

投稿2021/03/25 14:20

編集2021/03/25 14:49
katsuko

総合スコア3469

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

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

nanikamado

2021/03/25 14:25

rectangle()とfill()を使って書いてみたのですが、これらはかなり遅いです。細い線などを描く場合は問題なさそうなのですが、ウインドウをリサイズして全体を描画し直したりする場合はかくつきます。 もっと速いやり方を探しています。
nanikamado

2021/03/25 14:36 編集

> GTK+でOpenGLを試してみたことがありますが、どうも不安定、という印象があり、OpenGLを使うのであればQtの方がいいなぁ、というのが自分の考えです。 そうなんですね。参考にします。
nanikamado

2021/03/25 19:22

> もし、cairo_rectangleとcairo_fillをピクセル数分呼んでいるのであれば、cairo_fillは最後に一回だけ呼ぶようにしてもダメですか? 同じ色のピクセルを連続して描く場合にはその方法が使えますが、色がバラバラな場合はだめな気がします。 > 本当に重いですか? katsukoさんが書いてくださったPythonコードを少し書き換えて、私がRustで書いているものに近づけてみました。コメント欄ではMarkdownが使えないようなので、質問本文に追記しました。見てみてください。重いのが分かると思います。 > もしそれでもダメであれば、cairo_image_surfaceに点を書いておいて、通常のdrawシグナルで処理する場合はそのsurfaceを描画するようにするのも常套手段かと思います。 cairo_image_surfaceは試していませんでした。良さそうな予感がします。やってみます。
nanikamado

2021/03/26 11:51

cairo_image_surfaceを使用したところ、遥かに高速になり、全くカクつかなくなりました。ありがとうございます!
katsuko

2021/03/26 17:38

こちらこそ、「1pixelを描画する」というところに目がいってしまい、回りくどい回答になってしまい、申し訳なかったです。
guest

0

OpenGLで1pxを書くには、1pxを表すメッシュを作ってそれをDrawするか、バックバッファを一旦システムメモリに持ってきてから該当ピクセルのデータを書き換えて、再びVRAMに転送する方法しかないと思います。
いずれにしろ重いのと、おそらく最終的にやりたい「お絵かきソフトを作る」にはだいぶ遠い結果になるので、1pxを描くというアプローチは変えたほうが良いかなと思います。

cairoが使えるようなので、cairo::Context::move_toとcairo::Context::strokeで、前のカーソル位置から今のカーソル位置に線を描いた後、それを良いタイミングで何かしらの方法でラスタライズすることでパフォーマンスも良いままに目的に近いことが達成できるのではないかなと思います。
gtkは詳しくないですが、pixbuf_get_from_surfaceとか使えるのではないかなと思います。

投稿2021/03/25 14:45

S.Percentage

総合スコア283

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

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

S.Percentage

2021/03/25 15:08

それもありだと思います。いずれにしろ頂点バッファ作る手間はそこまで変わりませんが......
nanikamado

2021/03/25 15:14 編集

> いずれにしろ重いのと、おそらく最終的にやりたい「お絵かきソフトを作る」にはだいぶ遠い結果になるので、1pxを描くというアプローチは変えたほうが良いかなと思います。 なるほど、ありがとうございます。 > cairoが使えるようなので、cairo::Context::move_toとcairo::Context::strokeで、前のカーソル位置から今のカーソル位置に線を描いた後、それを良いタイミングで何かしらの方法でラスタライズすることでパフォーマンスも良いままに目的に近いことが達成できるのではないかなと思います。 たしかにこれは現実的な解決策です。しかし、この方法では、絵を描いている時には画像を構成するピクセルが見えないので、ドット絵のようなものがうまく描けない気がします。
guest

0

さまざまな複合理由によりPNGで透過を使い
画面サイズと同じPNGを透過で生成してそれを対角線が11-00のポリゴンに貼り付けるが それでも難しいだろうねずれることは起き得るOpenGLを使わずフレームバッファを直接呼び出しVRAMに直接データーを放り込めないとずれる可能性はある。しかもVRAMに放り込んでもおっしゃるように、様々な理由で厳密に1ピクセルはDirectX系でないと厳密性が厳しい

※電子顕微鏡でみても0.3ピクセルもずれてはいけないとする という場合

投稿2021/03/29 23:13

編集2021/03/29 23:15
kokorohamoe

総合スコア190

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

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

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.48%

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

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

質問する

関連した質問