なんとなく書くことにしました。
画像は8x8 bit の画像ファイル z.png(32倍に拡大してます)↓ を使います。
読み込み
> require 'RMagick' > img_z = Magick::ImageList.new('z.png') > # または img_z = Magick::Image.read('z.png')
ImageとImageListの違い
ImageListはImageの集合体である。 複数のファイルを同時に読み込む場合があることや、GIFやTIFFなど1ファイルに複数の画像を含む画像フォーマットがあるため、このような仕組みになっている。
ImageとImageListクラスは密接に関連している。Imageオブジェクトは一枚の画像か、もしくは複数のフレームを持つ画像の一フレームをあらわす。(複数フレームを持つ画像の例としては、アニメーションGIFや、複数レイヤをもつPhotoshopイメージがある。)ImageオブジェクトはGIFやPNG、JPEGなどの画像から生成できる。大きさを指定してスクラッチから画像を生成してもいい。画像はディスクに書き込んだり、スクリーンに表示したり、サイズや傾きを変更したり、フォーマットを変更したり、100を超えるメソッドを使ってその他いろいろ修正できる。
ImageListオブジェクトは画像のリストで、ゼロ個以上の画像とシーン番号を持つ。シーン番号は現在のイメージがどれかを示す。ImageListクラスはリストに含まれる全画像を操作するメソッドを持ち、例外もあるがImageクラスで定義される全てのメソッドも実行できる。Imageのメソッドは画像一つだけに有効なので、ImageのメソッドがImagelistに対して呼び出されたときは、シーン番号で示される現在の画像に渡される。
ImageListクラスはArrayクラスのサブクラスなので、ほとんどのArrayメソッドを利用してimagelistに含まれる画像を操作できる。例えば、<<メソッドを使ってリストに画像を追加できる。
画像情報取得
(なお、img = Magick::ImageList.new("img_1.jpg").first
が正しい)
画像を繋げる
とりあえずやりたかった一つとして、2つの画像を横に並べるというのがあります。実際にコードはこれ。
require 'rmagick' # require してライブラリを読み込み img_append = Magick::ImageList.new("sample01.jpg","sample02.jpg") img_append = img_append.append(false) img_append.write("composite.jpg")ハマった点としてはappendのところでのfalseとtrueの設定。
true 画像を上下に追加 false 画像を左右に追加
ちなみに ImageMagick の convert
コマンドだと、上下に積み重ねるのが-append
、右に繋げていくのが+append
、のように -/+ で挙動を変えてたりします。
-append
Join current images vertically or horizontally.
This option creates a single longer image, by joining all the current images in sequence top-to-bottom. Use+append
to stack images left-to-right.
メモリリーク対策(いまは要らないのかなあ…分からん)
RMagickはImageMagickのobj(mallocで確保した)を扱っていて、これはRubyのobjではありません。そのため、GCの対象にならず、メモリリークの危険性をはらむ事になります。
対策
- RMagick 2.10.0(ImageMagick 6.5.3-10)で変更されたバージョンを使う
- Magick::Image#destroy!を明示的に呼ぶ。
- MiniMagick や MagickWand を使う
- RMagickでメモリリークが発生する - diaの備忘録(2009年記事)
TL;DR for hurry people:
- Call Magick::Image#destroy! (ensure block is a damn good idea)(destroy! メソッドを使う)
- Use methods with exclamation mark as much as possible(出来るだけ破壊的メソッドを使う)
ファイルではなくオンメモリで画像処理
RMagickとImageMagickのAPIを眺めていたら、from_blobとto_blobというAPIを使えばオンメモリで処理が可能だとわかった…変換元の画像がリモートに存在する場合や、出力先がファイルではなくデータベースである場合などで、中間ファイルを生成せずにRMagickによる画像処理を行う際に利用できる。
画素値の配列化
軽く検索した範囲だと Magick::Image#pixel_color でいちいち画素毎にデータを取得してループさせるひとが多いのだけど、それはいくら何でも手間だろう…しかも返されるのはMagick::Pixel クラスなので、RGB 値を取得するのにいちいちメソッドを使わないといけない。
調べてみると Magick::Image#export_pixels という複数画素取得&画素値の成分を配列にして出力してくれる、ずっとマシなメソッドがあることに気付きます。
ただし。このメソッドの出力は RGB しかも16 bit なので、そのまま出すとこうなります。
> img_z.export_pixels => [0, 65535, 0, 0, 65535, 0, 0, 65535, 0, 0, 65535, 0, 0, 65535, 0, 0, 65535, 0, 0, 65535, 0, 34695, 42662, 11822, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 51143, 51143, 65535, 5654, 5654, 65535, 46260, 46260, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 50372, 50372, 65535, 5911, 5911, 65535, 47031, 47031, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 50115, 50115, 65535, 5911, 5911, 65535, 47288, 47288, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 49601, 49601, 65535, 5397, 5397, 65535, 48059, 48059, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 48830, 48830, 65535, 5140, 5140, 65535, 49087, 49087, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 48059, 48059, 65535, 5654, 5654, 65535, 49858, 49858, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535, 23130, 4112, 46517, 0, 0, 65535, 0, 0, 65535, 0, 0, 65535, 0, 0, 65535, 0, 0, 65535, 0, 0, 65535, 11565, 11565, 65535]
…少なくともRGBの3つを区切って、さらに8 bit にしないと分からないですよね。
> img_z.export_pixels.map { |pix| pix/257 }.each_slice(3).to_a => [[0, 255, 0], [0, 255, 0], [0, 255, 0], [0, 255, 0], [0, 255, 0], [0, 255, 0], [0, 255, 0], [135, 166, 46], [255, 255, 255], [255, 255, 255], [255, 255, 255], [255, 255, 255], [255, 255, 255], [255, 199, 199], [255, 22, 22], [255, 180, 180], [255, 255, 255], [255, 255, 255], [255, 255, 255], [255, 255, 255], [255, 196, 196], [255, 23, 23], [255, 183, 183], [255, 255, 255], [255, 255, 255], [255, 255, 255], [255, 255, 255], [255, 195, 195], [255, 23, 23], [255, 184, 184], [255, 255, 255], [255, 255, 255], [255, 255, 255], [255, 255, 255], [255, 193, 193], [255, 21, 21], [255, 187, 187], [255, 255, 255], [255, 255, 255], [255, 255, 255], [255, 255, 255], [255, 190, 190], [255, 20, 20], [255, 191, 191], [255, 255, 255], [255, 255, 255], [255, 255, 255], [255, 255, 255], [255, 187, 187], [255, 22, 22], [255, 194, 194], [255, 255, 255], [255, 255, 255], [255, 255, 255], [255, 255, 255], [255, 255, 255], [90, 16, 181], [0, 0, 255], [0, 0, 255], [0, 0, 255], [0, 0, 255], [0, 0, 255], [0, 0, 255], [45, 45, 255]]
もう一段階、X軸の繰り返しを区切れば理解しやすいですよね。
> img_z.export_pixels.map { |pix| pix/257 }.each_slice(3).each_slice(img_z.columns).to_a => [ [[0, 255, 0], [0, 255, 0], [0, 255, 0], [0, 255, 0], [0, 255, 0], [0, 255, 0], [0, 255, 0], [135, 166, 46]], [[255, 255, 255], [255, 255, 255], [255, 255, 255], [255, 255, 255], [255, 255, 255], [255, 199, 199], [255, 22, 22], [255, 180, 180]], [[255, 255, 255], [255, 255, 255], [255, 255, 255], [255, 255, 255], [255, 196, 196], [255, 23, 23], [255, 183, 183], [255, 255, 255]], [[255, 255, 255], [255, 255, 255], [255, 255, 255], [255, 195, 195], [255, 23, 23], [255, 184, 184], [255, 255, 255], [255, 255, 255]], [[255, 255, 255], [255, 255, 255], [255, 193, 193], [255, 21, 21], [255, 187, 187], [255, 255, 255], [255, 255, 255], [255, 255, 255]], [[255, 255, 255], [255, 190, 190], [255, 20, 20], [255, 191, 191], [255, 255, 255], [255, 255, 255], [255, 255, 255], [255, 255, 255]], [[255, 187, 187], [255, 22, 22], [255, 194, 194], [255, 255, 255], [255, 255, 255], [255, 255, 255], [255, 255, 255], [255, 255, 255]], [[90, 16, 181], [0, 0, 255], [0, 0, 255], [0, 0, 255], [0, 0, 255], [0, 0, 255], [0, 0, 255], [45, 45, 255]] ]
画素情報を取得するメソッドとして最初に挙げた Magick::Image#pixel_color メソッドは出力が Magick::Pixel クラスなのですが、複数画素を取得出来る Magick::Image#get_pixels メソッドもあります。あとで扱うときに Magick::Pixel クラスが良いのであればこれでも良いでしょう。
Magick::Pixel クラスのメソッド
> ls Magick::Pixel Magick::Pixel.methods: from_HSL from_color from_hsla Magick::Pixel#methods: <=> black= clone dup green intensity marshal_dump opacity= to_HSL to_s === blue cyan eql? green= magenta marshal_load red to_color yellow black blue= cyan= fcmp hash magenta= opacity red= to_hsla yellow=
red
・green
・blue
、cyan
・magenta
・yellow
・black
はそれぞれ RGB と CMYK の値が返るゲッターメソッドです。セッターメソッドはそれぞれの値をセットします。
intensity
メソッドは輝度 Y を返します。YUV 色空間などの Y です。ちなみに RGB からの算出方法は
Y = 0.299 × R + 0.587 × G + 0.114 × B
です。
to_color
メソッドは色名を返します。色名にならない場合は #
を付けて画素値を返します。to_s
メソッドと関連してますね。
> img_z.pixel_color(0, 0).to_color => "green" > img_z.pixel_color(0, 0).to_s => "red=0, green=65535, blue=0, opacity=0" > img_z.pixel_color(1, 7).to_color => "blue" > img_z.pixel_color(1, 7).to_s => "red=0, green=0, blue=65535, opacity=0" > img_z.pixel_color(6, 1).to_color => "#FFFF16161616" > img_z.pixel_color(6, 1).to_s => "red=65535, green=5654, blue=5654, opacity=0"
グレースケールにする
RMagick 2.12.0: Common Tasks によると Magick::Image#quantize メソッドで2番目の引数(colorspace)に Magick::GRAYColorspace
を渡せば良いようです。
> img_z_gray = img_z.quantize(256, Magick::GRAYColorspace)
なお1つ目の引数は量子化数(幾つ区切りにするか)なので、8bit = 256にしました。
出力するとこうなります(32倍に拡大)↓
ただし、 Magick::Image#export_pixels メソッドで画素値を配列化すると、デフォルトの RGB 出力でやはり 16bit のままなので
> img_z_gray.export_pixels => [46868, 46868, 46868, 46868, 46868, 46868, …
となってしまいます。読めません。なのでやはり 8bit にした上で3つずつに区切りましょう。
> img_z_gray.export_pixels.map { |pix| pix/257 }.each_slice(3).each_slice(img_z_gray.columns).to_a => [ [[182, 182, 182], [182, 182, 182], [182, 182, 182], [182, 182, 182], [182, 182, 182], [182, 182, 182], [182, 182, 182], [150, 150, 150]], [[255, 255, 255], [255, 255, 255], [255, 255, 255], [255, 255, 255], [255, 255, 255], [210, 210, 210], [71, 71, 71], [195, 195, 195]], [[255, 255, 255], [255, 255, 255], [255, 255, 255], [255, 255, 255], [208, 208, 208], [71, 71, 71], [198, 198, 198], [255, 255, 255]], [[255, 255, 255], [255, 255, 255], [255, 255, 255], [207, 207, 207], [71, 71, 71], [199, 199, 199], [255, 255, 255], [255, 255, 255]], [[255, 255, 255], [255, 255, 255], [206, 206, 206], [70, 70, 70], [201, 201, 201], [255, 255, 255], [255, 255, 255], [255, 255, 255]], [[255, 255, 255], [203, 203, 203], [69, 69, 69], [204, 204, 204], [255, 255, 255], [255, 255, 255], [255, 255, 255], [255, 255, 255]], [[201, 201, 201], [71, 71, 71], [206, 206, 206], [255, 255, 255], [255, 255, 255], [255, 255, 255], [255, 255, 255], [255, 255, 255]], [[43, 43, 43], [18, 18, 18], [18, 18, 18], [18, 18, 18], [18, 18, 18], [18, 18, 18], [18, 18, 18], [60, 60, 60]] ]
でも。RGB がいちいち同じ値で3つ並んでるのはどうかと思いますね。Magick::Image#export_pixels メソッドには map
という出力向けパラメータがありますので、出力するのをRGBじゃなくグレースケール成分だけにしてみましょう。
> img_z_gray.export_pixels(0, 0, img_z_gray.columns, img_z_gray.rows, 'i').map { |pix| pix/257 }.each_slice(img_z_gray.columns).to_a => [ [182, 182, 182, 182, 182, 182, 182, 150], [255, 255, 255, 255, 255, 210, 71, 195], [255, 255, 255, 255, 208, 71, 198, 255], [255, 255, 255, 207, 71, 199, 255, 255], [255, 255, 206, 70, 201, 255, 255, 255], [255, 203, 69, 204, 255, 255, 255, 255], [201, 71, 206, 255, 255, 255, 255, 255], [43, 18, 18, 18, 18, 18, 18, 60] ]
グレースケールの画像が不要なのであれば、上記の手順で元データのグレースケール成分を直接取得することが出来ます。
> img_z.export_pixels(0, 0, img_z.columns, img_z.rows, 'i').map { |pix| pix/257 }.each_slice(img_z.columns).to_a => [ [182, 182, 182, 182, 182, 182, 182, 150], [255, 255, 255, 255, 255, 210, 71, 195], [255, 255, 255, 255, 208, 72, 198, 255], [255, 255, 255, 207, 72, 199, 255, 255], [255, 255, 206, 70, 201, 255, 255, 255], [255, 203, 69, 204, 255, 255, 255, 255], [201, 71, 206, 255, 255, 255, 255, 255], [43, 18, 18, 18, 18, 18, 18, 60] ]
なお map
パラメータに指定できるのはこちらに記載がありました。引数には文字列で与えてください。
-map components
pixel map.
Here are the valid components of a map:
- r
- red pixel component
- g
- green pixel component
- b
- blue pixel component
- a
- alpha pixel component (0 is transparent)
- o
- opacity pixel component (0 is opaque)
- i
- grayscale intensity pixel component
- c
- cyan pixel component
- m
- magenta pixel component
- y
- yellow pixel component
- k
- black pixel component
- p
- pad component (always 0)
You can specify as many of these components as needed in any order (e.g. bgr). The components can repeat as well (e.g. rgbr).
おまけ:16bit を 8bit にするときになぜ257で割るのか
(分かりやすいように16進数表記にします)
8bit なので 00 から FF まで256段階です。等間隔で 16bit にするには、0000、0101、0202…FEFE、FFFF とすればいいことに気付くかと思います。つまり 16bit の0101=10進数の257の間隔で増加していきます。これが 8bit から 16bit への増やし方です。逆に257で割れば 16bit から 8bit にできます。