ruby-****@lists*****
ruby-****@lists*****
2003年 5月 26日 (月) 23:35:40 JST
------------------------- REMOTE_ADDR = 210.249.193.205 REMOTE_HOST = URL = http://ruby-gnome2.sourceforge.jp/?%A5%AB%A5%E9%A1%BC%A4%CE%B0%B7%A4%A4 ------------------------- ------------------------- = カラーの扱い ((*まだRuby/GTK(1)用です*)) ここではRuby/GTK(というか、X Window System)でのカラーの扱いとそれに関するクラスについてまとめてみました。 なお、このページは元々<A HREF="http://www5b.biglobe.ne.jp/~kurakami/rg/ruby_gtk.html">クラカの Ruby/Gtk 奮戦記</A>に触発されて書き始めました。内容が重複している部分も多いですが、視点が違いますので、是非そちらも参考にしてください。 == Gdk::Visual カラーは個々のスクリーンデバイス環境に強く依存します。どんなカラーでも同時に表示できたり、白黒しか表示できなかったり、カラーでも同時に256色までしか表示できなかったり....。 ここでは、それらの環境(の情報)のことをVisualと言い、その種別のことをVisualTypeと言う事にします。 まず、VisualTypeは、大きく分けて以下の2つになります。 * Pseudo Color系 同時発色数が決まっていてカラーマップを使ってその中にある色を選択して用いる方法です。pixel値はカラーマップのインデックス値となります。カラーマップというのはカラーの集合のことです。詳しくは<A HREF="#colormap">Gdk::Colormap</A>のところを参照してください 同時発色数が限られたなかで色を最大限に利用するための仕組で(1677万色のうちから同時n色まで発色というやつ)、例えば、同時発色数が256の場合、256色分定義できるカラーマップへカラーを登録・参照したり、あるいは更新して、その範囲内でカラーを使いまわします。固定のカラーマップを持つものは参照のみです。まぁ、最近のグラフィックカードではPseudo Color系はあまり使わないかもしれませんね。 * Direct Color系 特にカラーの発色数に制限のない方法です。Red/Green/Blueの全ての組み合わせでカラーを表現できます。pixel値はRed/Green/Blueから計算された値となります。Pseudo Color系にあるような制限がないのでPseudo Color系よりも多くの色数を使うことができます。カラーマップを意識することはほぼありません。 ※pixel値は画面上のある1点を示し、その1点の色情報を含む値のことです。 VisualTypeは以下の6通りがあります。 <TABLE ALIGN="center" CELLSPACING=0 BORDER=1 WIDTH=95%> <TR> <TH>ID</TH><TH>VisualType</TH><TH>read/write</TH><TH>説明</TH> </TR> <TR><TD>0</TD><TD CLASS="code">Gdk::Visual::STATIC_GRAY</TD><TD>read-only</TD><TD>Pseudo Color系。あらかじめ定義された固定のカラーマップを持ちます。定義色はグレー系のみです。</TD></TR> <TR><TD>1</TD><TD CLASS="code">Gdk::Visual::GRAYSCALE</TD><TD>read/write</TD><TD>Pseudo Color系。グレー系の値(red = green = blue)のみ任意に設定可能なカラーマップを持ちます。</TD></TR> <TR><TD>2</TD><TD CLASS="code">Gdk::Visual::STATIC_COLOR</TD><TD>read-only</TD><TD>Pseudo Color系。あらかじめ定義された固定のカラーマップを持ちます。</TD></TR> <TR><TD>3</TD><TD CLASS="code">Gdk::Visual::PSEUDO_COLOR</TD><TD>read/write</TD><TD>Pseudo Color系。カラーは任意に設定可能なカラーマップを持ちます。</TD></TR> <TR><TD>4</TD><TD CLASS="code">Gdk::Visual::TRUE_COLOR</TD><TD>read-only</TD><TD>Direct Color系。あらかじめ定義された固定のカラーマップを持つこと以外はDIRECT_COLORと同じです。pixel値が直接Red/Green/Blueの要素を含みます。</TD></TR> <TR><TD>5</TD><TD CLASS="code">Gdk::Visual::DIRECT_COLOR</TD><TD>read/write</TD><TD>Direct Color系。Red/Green/Blueの全ての組み合わせでカラーを表現できます。pixel値が直接Red/Green/Blueの要素を含みます。</TD></TR> </TABLE> それでは実際にGdk::Visualの情報を見てみます。 require 'gtk' visual = Gdk::Visual.get_best printf("visual_type = %d\n", visual.visual_type) printf("bits_per_rgb = %d\n", visual.bits_per_rgb) printf("byte_order = %d\n", visual.byte_order) printf("colormap_size = %d\n", visual.colormap_size) printf("red_mask = %x, red_prec = %x, red_shift = %x\n", visual.red_mask, visual.red_prec, visual.red_shift) printf("green_mask = %x, green_prec = %x, green_shift = %x\n", visual.green_mask, visual.green_prec, visual.green_shift) printf("blue_mask = %x, blue_prec = %x, blue_shift = %x\n", visual.blue_mask, visual.blue_prec, visual.blue_shift) 上記の結果はvisual_typeによって変わってきます。私が通常使用している環境(Matrox G400, 1600x1200, 24bitカラー)では以下のような結果になります。 $ruby visual.rb visual_type = 5 bits_per_rgb = 8 byte_order = 0 colormap_size = 256 red_mask = ff0000, red_prec = 8, red_shift = 10 green_mask = ff00, green_prec = 8, green_shift = 8 blue_mask = ff, blue_prec = 8, blue_shift = 0 * Gdk::Visual.get_best 現在のスクリーン環境で使うことのできる一番適切なVisualを取得します。 環境によっては同時に複数のVisualをサポートできるようです。現在使用可能なVisualはGdk::Visual.list_visualsで取得できますので、その中から使用するVisualを選んでも良いでしょう。さらに同様なメソッドとしてGdk::Visual.list_visualsの他に、Gtk::Visual.get_with_depth(depth), Gdk::Visual.get_with_type(visual_type), Gdk::Visual.get_with_both(depth, visual_type)等のメソッドもあるのですが、これらは極めて環境に依存するメソッドなのでなるべく使わない方が良いと思います(実際、サポートしていない情報を設定してしまうとnilを返します)。 #というか、そもそもあまり使わないか...。 * Gdk::Visual#visual_type 上記例ではGdk::Visual::DIRECT_COLORですね。 * Gdk::Visual#bits_per_rgb 1色あたり何bit使うかどうかが返ります。注意しなければいけないのが必ずしも、RGBが同じ数にはならないということです(16bitカラーとか32bitカラーとかは3つに割れませんよね)。そんなわけでこのメソッドはあまりアテになりません...ってホントか?(^^;)。 * Gdk::Visual#byte_order メモリ内のpixel値のバイトの並び方を返します。これには以下の2つがあります。 * Gdk::LSB_FIRST Least Significant Bit。リトル・エンディアン(Intel系)。データを小さい方から並べる。 * Gdk::MSB_FIRST Most Significant Bit。ビッグ・エンディアン(Motoroller系)。データを大きい方から並べる。 例えば、32bitの0x00ffeeccは、Gdk::LSB_FIRSTでは 0xcc 0xee 0xff 0x00と格納され、Gdk::MSB_FIRSTでは 0x00 0xff 0xee 0xcc と格納されます.....。といってもGdk::LSB_FIRSTな石しか使ったことのないオイラにはこれがカラー関係にどう影響するのかよくわからないです。Macとかでも使えるようにするためには意識する必要があるのかな。イメージ的にはRedとBlueが逆になるのかなぁ。わからん。以上、詳しい情報求ム。 * Gdk::Visual#colormap_size Pseudo Color系の場合、カラーマップのエントリ数を返します。Direct Color系の場合はRGB各色が使用できる値の範囲を返します。24bitカラーでは0x00〜0xffまでの256段階なので256になります。 * Gdk::Visual#red_mask, Gdk::Visual#red_prec, Gdk::Visual#red_shift Gdk::Visual#green_mask, Gdk::Visual#green_prec, Gdk::Visual#green_shift Gdk::Visual#blue_mask, Gdk::Visual#blue_prec, Gdk::Visual#blue_shift これらのメソッドはDirect Color系のみで使われるメソッドで、pixel値とRed/Green/Blueの値の相互変換を行う際に使用します。 詳しくは<A HREF="#convert_direct">こちら</A>を参照してください。 === pixel値とカラー 上記環境では、24bitカラーでRGB各色8bitとなり、1pixelはRRRRRRRRGGGGGGGGBBBBBBBBの24bitで表せます(R,G,Bそれぞれを1bitで表しています)。これを16進数(10進数)で表すと各色とも0x00(0)〜0xff(255)まで、1色が0x000000 〜 0xffffffで表現されることになります。つまり、10進数で表すと0 〜 16,777,215色が表せるわけです。いわゆる1677万色というやつですね。 余談になりますが、Ruby/GTK(X Window System)ではRGBのそれぞれが0x0000〜0xffff(65535)まで指定できることになっていますので0xffffffffffff色、つまり、281,474,976,710,655色が使えるように設計されています。48bitカラーというやつですね。ちなみに最近のグラフィックボードは32bitカラーまでがせいぜいですけど48bitなんて使う時代はホントに来るのでしょうか?....ま、きっと来るんだろうな....(^^;)。 == Gdk::Color Gdk::ColorはRGB(Red/Green/Blue)の各要素からRuby/GTK上で扱うことのできる「カラー」を生成します。例としてGtk::CListを使って色を配置してみます。 require 'gtk' black = Gdk::Color.new(0, 0, 0) white = Gdk::Color.new(65535, 65535, 65535) red = Gdk::Color.new(65535, 0, 0) yellow = Gdk::Color.new(65535, 65535, 0) list = Gtk::CList.new(1) list.append(["red and yellow"]) list.append(["white and black"]) list.set_foreground(0, red) list.set_background(0, yellow) list.set_foreground(1, white) list.set_background(1, black) window = Gtk::Window.new window.add(list) window.set_usize(200,200) window.show_all printf("pixel = %x, red = %x, green = %x, blue = %x\n", yellow.pixel, yellow.red, yellow.green, yellow.blue) * Gdk::Color.new(red, green, blue) 各値は、0 〜 65535(0xffff)の範囲で指定します。値が大きいほど白っぽく(明るく)なります(ってあまり適切な表現じゃないかも(^^;))。全部0で黒、全部65535で白になります。 * Gdk::Color#pixel カラーのpixel値を取得します。pixel値はVisualTypeによって扱いが異なります。実は、上記例では常に0が返ってあまり意味がありません。 <A HREF="#colormap">Gdk::Colormap</A>のところで活躍しますのでそちらを参照してください。 * Gdk::Color#red, Gdk::Color#green, Gdk::Color#blue カラーのRed/Green/Blue要素をそれぞれ取得します。 <A HREF="#visual_color">先ほどの余談</A>で48bitまでカラーを表現できると書きましたが、Gdk::Color自体は各色0xffffまでしかサポートしませんので注意が必要です。実は、例えば実際は0xffまでしかサポートしない場合(24bitの場合など)もGdk::Colorでは0x0000〜0xffffまでの範囲で指定します。その辺の環境依存部分をGdk::Color(というかX)が吸収してくれているわけです。 === Gdk::Colormap(Pseudo Color系) 最近は少なくなってきたと思うのですが、グラフィックカードによっては「1677万色のうち同時発色数は255色」というように同時発色数を制限する場合があります。つまりPseudo Color系のことなのですが、この場合、プログラムが個々にGdk::Color.new()してしまうとあっという間に同時発色数をオーバーしてしまいます。(1つのスクリーン上に256色ですので、他のアプリケーションが使っている色の数も関係してきます。強引に1ウインドウ(アプリケーション?)に256色割り当てることもできるようですので一概には言えないんですけどね)。 そこで、 X Windows Systemでは、システムで共通のカラーマップというのを定義して、それを使いまわす形を取ります。Ruby/GTKではGdk::Colormapです。 Direct Color系の場合は、使いまわす必要がないのでカラーマップは意味がありません(nilを返す)。 require 'gtk' colormap = Gdk::Colormap.new(Gdk::Visual.get_best, true) colormap.colors.each do |color| printf("pixel = %x, red = %x, green = %x, blue = %x\n", color.pixel, color.red, color.green, color.blue) end Gdk::Colormap.new(visual, allocate)で、カラーマップを生成します。 * visual - Gdk::Visual * allocate - プライベート(アプリケーション固有の)カラーマップを新しく生成する場合はtrueにし、システム固有のカラーマップを使う場合はfalseにします。 とあるのですが....イマイチうまく使えません。なんでだろ。 カラーマップはカラーの配列になっていますので上記のようにカラーマップ内のGdk::Colorを取得できます。 Pseudo Color系では、pixel値がインデックス値になっていることがわかります。 ちなみに、Gdk::Colormap.new()の代わりにGdk::Colormap.get_systemでシステムが現在使用しているカラーマップを取得することができます。 なお、Direct Color系ではcolormap.colorsがnilを返します(for文の行でエラーが発生します)。 == 指定したPixmapをちょっとだけ明るく表示する(Direct Color系) さて、カラーについて一通り勉強したところで、実際にこれらを使ってみます。その例として、カラーのPixmapをちょっとだけ明るく変換してみます。 なお、例題という意味でなるべく簡単化しています。例えば、RGBの原色しか使っていないような画像は変わりません。それから、説明のためにわざわざ遅くなるような処理を書いているところもあるので高速化が可能です。実用化するときにはその辺を工夫してみてください。 それから、たいていの場合はこちらになると思いますが、使っているスクリーンデバイスのVisualTypeがPseudo Color系の場合は<A HREF="#convert_pseudo">次のプログラム</A>しか動かない場合があります。自分の環境がどちらなのか、xwininfoを実行して、Depth, VisualClassのところを確認してください。 require 'gtk' window = Gtk::Window.new window.realize visual = Gdk::Visual.get_best pixmap, mask = Gdk::Pixmap::create_from_xpm(window.window, nil, ARGV[0]) pixmap_brighter, mask_brighter = Gdk::Pixmap::create_from_xpm(window.window, nil, ARGV[0]) geometry = pixmap.get_geometry width = geometry[2] height = geometry[3] image = Gdk::Image.get(pixmap_brighter, 0, 0, width, height) (0...width).each do |x| (0...height).each do |y| pixel = image.get_pixel(x,y) red = (pixel & visual.red_mask) >> visual.red_shift #(1) green = (pixel & visual.green_mask) >> visual.green_shift #(2) blue = (pixel & visual.blue_mask) >> visual.blue_shift #(3) red = (red * 1.2).to_i #(4) green = (green * 1.2).to_i blue = (blue * 1.2).to_i red = (visual.red_mask >> visual.red_shift) if red > (visual.red_mask >> visual.red_shift) #(5) green = (visual.green_mask >> visual.green_shift) if green > (visual.green_mask >> visual.green_shift) blue = (visual.blue_mask >> visual.blue_shift) if blue > (visual.blue_mask >> visual.blue_shift) pixel = (red << visual.red_shift) + (green << visual.green_shift) + (blue << visual.blue_shift) #(6) image.put_pixel(x, y, pixel) end end pixmap_brighter.draw_image(Gdk::GC.new(window.window), image, 0, 0, 0, 0, width, height) image.destroy box = Gtk::HBox.new box.add(Gtk::Pixmap.new(pixmap, mask)) box.add(Gtk::Pixmap.new(pixmap_brighter, mask_brighter)) window.add(box) window.set_usize(200,200) window.show_all Gtk.main 実行方法はXPMファイル名を引数に与えてください。上記をbrighter.rbというファイルにしたとして、以下のようにします。 $ruby brighter.rb fuga.xpm 24bitのDirect Colorを例に説明します。24bitのDirect Colorの場合は、1pixelを16進数で表すとff ff ffとなります(これは最大値ですからwhiteになります)。左からRed/Green/Blueです。以下、対象となるpixel値が0x112233だとします。 * (1)visual.red_maskは0xff0000でvisual.red_shiftは10です。*_shiftは何bit右にシフトすればそのカラーだけ取り出せるのかという情報が入っています。 pixel & visual.red_maskで、0x110000となりますので、これを 0x110000 >> visual.red_shiftすることで0x11となります。これで無事、Redの部分だけ取得できました。 * (2)visual.green_maskは0xff00でvisual.green_shiftは8です。こちらも同様で、(2)の式で 0x22が取得できます。 * (3)visual.green_maskは0xffでvisual.green_shiftは0です。こちらは0x33を取得できます。 * (4)Red/Green/Blueの各値を1.2倍しています。厳密には、ここはblack(値が0)の場合はずっと黒のままになってしまいますのでもっと工夫が必要です。 * (5)各色が最大値を超えないように調整しています。各色のmaskが最大値になりますのでそれを(1),(2),(3)と同じ方法で取得しています。 * (6)今度は分解したRed/Green/Blueをpixelに戻します。処理は(1),(2),(3)の逆を行います。 もしかしたら、visual.red_maskが0xff0000で、各色の最大値は0xff(256)だということがわかっているので、何もvisualのメソッドを使う必要は無いのではないか思う人もいるかもしれません。でも、このようにしておくことで、Direct Color系の他のスクリーンに対応できますので、これらのメソッドを使った方が良いでしょう。ただし、少しでも速度アップを図る場合、この辺はべた書きにしてしまう場合もあります。Rubyはスクリプト言語なので、こういった箇所でべた書きをすることでかなりの速度アップが期待できる場合があります。 それから、勘の鋭い人ならもう気づいてしまったかもしれませんが、16bitの場合、Red = 5bit, Green = 6bit, Blue = 5bitになり、各色の最大値が31, 63, 31になります。Gdk::Color自体は0 〜 65535の範囲を指定できるのですが、pixel値ではそれぞれの解像度に合わせた適切な範囲内の数値を使う必要があります。 == 指定したPixmapをちょっとだけ明るく表示する(Pseudo Color系) Pseudo Color系ではColormapを使う点でDirect Color系とは異なります。 こちらはDirect Color系のスクリーン環境では実行できませんが、大抵のグラフィックカードであればXの起動オプションを調整するとPseudo Color等で実行できると思います。 $xinit -- -depth 8 require 'gtk' window = Gtk::Window.new window.realize visual = Gdk::Visual.get_best colormap = Gdk::Colormap.get_system #(1) pixmap, mask = Gdk::Pixmap::create_from_xpm(window.window, nil, ARGV[0]) pixmap_brighter, mask_brighter = Gdk::Pixmap::create_from_xpm(window.window, nil, ARGV[0]) geometry = pixmap.get_geometry width = geometry[2] height = geometry[3] image = Gdk::Image.get(pixmap_brighter, 0, 0, width, height) (0...width).each do |x| (0...height).each do |y| pixel = image.get_pixel(x,y) color = colormap.colors[pixel] #(2) red = (color.red * 1.2).to_i #(3) green = (color.green * 1.2).to_i blue = (color.blue * 1.2).to_i red = 65535 if red > 65535 #(4) green = 65535 if green > 65535 blue = 65535 if blue > 65535 pixel = colormap.alloc_color(Gdk::Color.new(red, green, blue), false, true) #(5) pixel = 0 if ! pixel #(6) image.put_pixel(x,y, pixel) end end pixmap_brighter.draw_image(Gdk::GC.new(window.window), image, 0, 0, 0, 0 ,width, height) image.destroy box = Gtk::HBox.new box.add(Gtk::Pixmap.new(pixmap, mask)) box.add(Gtk::Pixmap.new(pixmap_brighter, mask_brighter)) window.add(box) window.set_usize(200,200) window.show_all Gtk.main * (1)システムが使用するカラーマップを取得します。 * (2)colormapからpixelをインデックスにGdk::Colorを取得します。 * (3)Red/Green/Blueの各値を1.2倍しています。 * (4)各色が最大値を超えないように調整しています。Gdk::Colorの最大値は65,535になります。Direct Colorの時のpixelから取得したRed/Green/Blueの最大値とは異なることに注意が必要です。 * (5)Gdk::Colormap#alloc_color(color, writeable, bestmatch)でシステムにカラーを割り当てます。 * color - 使用したいカラーを指定します。カラーマップに空きがある場合はそこにcolorを割り当ててそのインデックス値(pixel)を返します。 * writeable - 書き込み可能かどうか指定します。 カラーマップをGdk::Colormap.new(visual, true)とした場合に関係してきます。今回は(1)でシステムが使用するカラーマップを取得しますのでfalseを指定します。 * bestmatch - 使用したいカラーを割り当てられなかった場合に、できるだけ近いカラーを返します。 * (7)pixel値がnilを返した場合(カラーの割り当てができなかった場合)は、0を指定します。ホントはwhiteかなんかを取得してあげた方が良いのかな。 == 指定したPixmapをグレースケールにする 4,5と同様にGdk::PixmapからグレースケールのGdk::Pixmapを生成させることができます。これは<A HREF="http://www5b.biglobe.ne.jp/~kurakami/rg/ruby_gtk.html">クラカの Ruby/Gtk 奮戦記</A>の受け売りなんですが、Red/Green/Blueから輝度を計算しそれをグレーの値として用います。 Gray = 0.299 * Red + 0.587 * Green + 0.114 * Blue この方法はYIQと言うそうです。で、輝度であるYを計算する式が上の式になります。 この辺、詳しく知りたい方は<A HREF="http://www5b.biglobe.ne.jp/~kurakami/rg/ruby_gtk.html">クラカの Ruby/Gtk 奮戦記</A>か、検索エンジンなどで探してみてください。結構ひっかかりますよ。