rmagick question

J

Joe Van Dyk

I'm trying to set the pixels of an image:

require 'RMagick'

width =3D 512
height =3D 256

image =3D Magick::Image.new width, height

width.times do |x|
height.times do |y|
pixel =3D Magick::pixel.new rand(256), rand(256), rand(256)
image.pixel_color x, y, pixel=20
end
end

image.write ARGV.shift

But the resulting image is all black. Why?
 
J

Joe Van Dyk

I'm trying to set the pixels of an image:
=20
require 'RMagick'
=20
width =3D 512
height =3D 256
=20
image =3D Magick::Image.new width, height
=20
width.times do |x|
height.times do |y|
pixel =3D Magick::pixel.new rand(256), rand(256), rand(256)
image.pixel_color x, y, pixel
end
end
=20
image.write ARGV.shift
=20
But the resulting image is all black. Why?

And here's some code from my actual application:
=20
pixel =3D Magick::pixel.new(*rgb)
puts "The image pixel was: <#{ image.pixel_color(e, n) }>"
image.pixel_color(e, n, pixel)
puts "The pixel was <#{ pixel }>"
puts "The image pixel is now: <#{ image.pixel_color(e, n) }>"

And here's the result:
The image pixel was: <red=3D65535, green=3D65535, blue=3D65535, opacity=
=3D0>
The pixel was <red=3D175, green=3D206, blue=3D117, opacity=3D0>
The image pixel is now: <red=3D65535, green=3D65535, blue=3D65535, opac=
ity=3D0>=20

Why on earth isn't it setting the pixel color on the image?
 
B

Brian Schröder

I'm trying to set the pixels of an image:
=20
require 'RMagick'
=20
width =3D 512
height =3D 256
=20
image =3D Magick::Image.new width, height
=20
width.times do |x|
height.times do |y|
pixel =3D Magick::pixel.new rand(256), rand(256), rand(256)
image.pixel_color x, y, pixel
end
end
=20
image.write ARGV.shift
=20
But the resulting image is all black. Why?
=20
=20

Use Magick::MaxRGB+1 instead of 256. RGB Values range from 0...2**16.
And be shure to check out the View funktionality.

regards,

Brian

--=20
http://ruby.brian-schroeder.de/

Stringed instrument chords: http://chordlist.brian-schroeder.de/
 
T

Timothy Hunter

Joe said:
Thanks... this is on a imagemagick installation compiled from scratch
last night though. 6.2.4. So I'm not sure what's going on.

If I have a bunch of RGB values from 0-256, do you know a way to
create an image for them? Apparently the MaxRGB on my installation is
around 65000 or so. The image is around 10kx10k pixels, so speed is
sorta important.

If speed is important then the best thing to do is to build a new
ImageMagick using the --with-quantum-depth=8 option. Using 8-bit depth
images considerably reduces IM's memory and CPU requirements and it
makes the channel intensities range from 0-255 instead of 0-65535, more
in line with your expectations. For 100-million-pixel images I think it
would be worth the trouble.

However, if you don't want to re-install IM and you don't mind paying
for a couple extra bit operations per channel, you can convert 8-bit
channels to 16-bit channels like this:

red16 = (red8 << 8) | red8

Lastly, take a look at the #store_pixels method. This method lets you
replace pixels in an image a section at a time, where a section can be a
single row or column of pixels, or for that matter any rectangle. This
might be a good compromise between pixel_color and constitute.
 
J

Joe Van Dyk

=20
If speed is important then the best thing to do is to build a new
ImageMagick using the --with-quantum-depth=3D8 option. Using 8-bit depth
images considerably reduces IM's memory and CPU requirements and it
makes the channel intensities range from 0-255 instead of 0-65535, more
in line with your expectations. For 100-million-pixel images I think it
would be worth the trouble.
=20
However, if you don't want to re-install IM and you don't mind paying
for a couple extra bit operations per channel, you can convert 8-bit
channels to 16-bit channels like this:
=20
red16 =3D (red8 << 8) | red8
=20
Lastly, take a look at the #store_pixels method. This method lets you
replace pixels in an image a section at a time, where a section can be a
single row or column of pixels, or for that matter any rectangle. This
might be a good compromise between pixel_color and constitute.

I'll try the bitshifting approach, thanks. =20

Previously, I had builtup a lookup array that looked like (I think):
color_lookup_table =3D Array.new
256.times { |i| color_lookup_table << Magick::MaxRGB / i }

And then did a lookup on that table for each color. You think the
bitshifting approach will be faster than an array lookup?
 
T

Timothy Hunter

Joe said:
I'll try the bitshifting approach, thanks.

Previously, I had builtup a lookup array that looked like (I think):
color_lookup_table = Array.new
256.times { |i| color_lookup_table << Magick::MaxRGB / i }

And then did a lookup on that table for each color. You think the
bitshifting approach will be faster than an array lookup?
I don't know. My gut feel is yes but if you're going to be working on
100,000,000 pixel images then it would be worth the trouble to actually
compare the two approaches. A little bit of difference would mount up
quickly :)

Of course 100,000,000 pixel images are going to have resource
constraints besides CPU. At 16-pixels per channel each pixel will
require 8 bytes plus some per-image overhead, so each image will occupy
a bit over 800MB of memory. Using quantum depth=8 cuts the memory
requirement in half.

No matter which approach you take let me know how it goes so I'll be
able to make recommendations to other RMagick users who are working with
very large images. Thanks!
 
J

Joe Van Dyk

I don't know. My gut feel is yes but if you're going to be working on
100,000,000 pixel images then it would be worth the trouble to actually
compare the two approaches. A little bit of difference would mount up
quickly :)
=20
Of course 100,000,000 pixel images are going to have resource
constraints besides CPU. At 16-pixels per channel each pixel will
require 8 bytes plus some per-image overhead, so each image will occupy
a bit over 800MB of memory. Using quantum depth=3D8 cuts the memory
requirement in half.
=20
No matter which approach you take let me know how it goes so I'll be
able to make recommendations to other RMagick users who are working with
very large images. Thanks!

Yes, my Ruby program was taking up about 500 MB of memory (for a
8700x6000 pixel image). Memory's not a problem though, all of our
machines have more than 2 gigabytes.

I'll report back tomorrow after I try the bitshifting and 8-bit
imagemagick approach instead of the current array lookup.

Unit tests are really coming in handy on this type of application.=20
Especially the benchmark library. It's awesome to make a change and
then build up some sample data and do automated tests and benchmarking
on it.
 
T

Timothy Hunter

Joe said:
Yes, my Ruby program was taking up about 500 MB of memory (for a
8700x6000 pixel image). Memory's not a problem though, all of our
machines have more than 2 gigabytes.

Okay, first recommendation: have a honkin' great big machine :)

Thanks!
 
J

Joe Van Dyk

=20
Okay, first recommendation: have a honkin' great big machine :)

(in case people forgot, the color values in @tad_data are from 0-255,
so conversion is needed if ImageMagick is using 16 bit color pixels)

The inner code loop looked something like this:

(height - 1).downto(0) do |n|
# Code here that displayed percent-done status to user
width.times do |e|
# Lookup approach with 16 bit pixel ImageMagick
#rgb =3D @tad_data.read(8).unpack(TAD_FORMAT).collect! { |c|
lookup_table[c] }
=20
# Bitshifting approach with 16 bit pixel ImageMagick
#rgb =3D @tad_data.read(8).unpack(TAD_FORMAT).collect! { |c| (c
<< 8) | c }

# 8 bit ImageMagick, no conversion necessary
rgb =3D @tad_data.read(8).unpack(TAD_FORMAT)

pixel =3D Magick::pixel.new(*rgb)
@image.pixel_color(e, n, pixel)
end
end

Doing a 10000x10 pixel image (only 10 pixels high for unit testing
purposes), I could process:

13k pixels/second with the bitshifting approach (and 16 bit IM)
13k pixels/second with the array lookup approach (and 16 bit IM)
21k pixels/second with 256 bit IM.

I also found that building up an array of colors for one row and then
doing a @image.import_pixels had no speed improvements and the code
was uglier.
 
J

Joe Van Dyk

Okay, first recommendation: have a honkin' great big machine :)
=20
(in case people forgot, the color values in @tad_data are from 0-255,
so conversion is needed if ImageMagick is using 16 bit color pixels)
=20
The inner code loop looked something like this:
=20
(height - 1).downto(0) do |n|
# Code here that displayed percent-done status to user
width.times do |e|
# Lookup approach with 16 bit pixel ImageMagick
#rgb =3D @tad_data.read(8).unpack(TAD_FORMAT).collect! { |c|
lookup_table[c] }
=20
# Bitshifting approach with 16 bit pixel ImageMagick
#rgb =3D @tad_data.read(8).unpack(TAD_FORMAT).collect! { |c| (c
<< 8) | c }
=20
# 8 bit ImageMagick, no conversion necessary
rgb =3D @tad_data.read(8).unpack(TAD_FORMAT)
=20
pixel =3D Magick::pixel.new(*rgb)
@image.pixel_color(e, n, pixel)
end
end
=20
Doing a 10000x10 pixel image (only 10 pixels high for unit testing
purposes), I could process:
=20
13k pixels/second with the bitshifting approach (and 16 bit IM)
13k pixels/second with the array lookup approach (and 16 bit IM)
21k pixels/second with 256 bit IM.
=20
I also found that building up an array of colors for one row and then
doing a @image.import_pixels had no speed improvements and the code
was uglier.

Hm... I don't think that those pixels/second numbers are correct. I
was doing the timings inside a Benchmark.measure { ... } block and
perhaps that makes things slower?

I was able to process a 8700x6000 pixel image when using 'time ./script.rb'
440.005u 9.917s 8:27.20 88.7% 0+0k 0+0io 52012pf+0w

So, around 7 minutes for 5 million pixels.. that's around 118000
pixels per second. Hm. Oh well, it's fast enough for me. This
operation isn't done all that often. Although, a while ago I did
something like this in C with the GD library, and it took around 10-20
seconds to process a similar sized image.
 
T

Timothy Hunter

Joe said:
Hm... I don't think that those pixels/second numbers are correct. I
was doing the timings inside a Benchmark.measure { ... } block and
perhaps that makes things slower?

I was able to process a 8700x6000 pixel image when using 'time ./script.rb'
440.005u 9.917s 8:27.20 88.7% 0+0k 0+0io 52012pf+0w

So, around 7 minutes for 5 million pixels.. that's around 118000
pixels per second. Hm. Oh well, it's fast enough for me. This
operation isn't done all that often. Although, a while ago I did
something like this in C with the GD library, and it took around 10-20
seconds to process a similar sized image.

I'm not surprised that the 256-color IM is the fastest solution. I'm
also not surprised that a pure-C solution is faster than a Ruby
solution. All those channel values have to be converted from integers to
Fixnums and back to integers, not to mention constructing 5M
Magick::pixel objects.

Thanks for sharing what you learned. This'll be useful for the next guy
to face a similar problem.
 
A

Ara.T.Howard

I'm not surprised that the 256-color IM is the fastest solution. I'm also
not surprised that a pure-C solution is faster than a Ruby solution. All
those channel values have to be converted from integers to Fixnums and back
to integers, not to mention constructing 5M Magick::pixel objects.

Thanks for sharing what you learned. This'll be useful for the next guy to
face a similar problem.

hi tim-

i was playing with something along the lines of

na = NArray::byte 1000, 1000

img = RMagick::Image::new # i forget the syntax attm

img.set_pixels na.to_s

but this blew up with a complaint about needing an array. my question - is
there any reason to check the type for these sorts of things? i mean, if it's
only a few methods required why not just call them and blow up if the obj does
not support them? i was hoping to combine NArray and RMagick for the obvious
speed advantage one could gain - for instance being able to 'darken' and image
a little with

na = na - 1

is pretty powerful... but getting the values back out puts you into loop
land... in general it seems like there should be someway, with RMagick, to
say

img.pixels = buffer

and that buffer had better be a binary packed respresentation of the pixels.
this would allow usage with narray and mmap. the nice thing about using mmap
is that you could spawn subprocesses to work on part of an image using shared
memory and, when all children had finished, dump the pixels into your image.
it could be very powerful. the narray lib has a similar approach in that you
can say things like

na = NArray::to_na buffer, NArray::BYTE, 1000, 1000

and the buffer is slurped in at once, i assume using memcpy. might an
approach like this for image magic make faster operations possible? or is the
layout of pixels never inline is a way that could take advantage of something
like that?

cheers.

-a
--
===============================================================================
| email :: ara [dot] t [dot] howard [at] noaa [dot] gov
| phone :: 303.497.6469
| Your life dwells amoung the causes of death
| Like a lamp standing in a strong breeze. --Nagarjuna
===============================================================================
 
T

Timothy Hunter

Ara.T.Howard said:
Darn. After reading Ara's post I started thinking about a way to move
pixels
more-or-less directly from a file into an image, bypassing the need to
create Magick::pixel objects. I emailed the ImageMagick team and asked
for
their recommendation. The absolute fastest way to get pixel data into an
image is via the ImageMagick API that RMagick's Image::import_pixels
method
(http://www.simplesystems.org/RMagick/doc/image2.html#import_pixels)
uses,
called ImportImagePixels. ImportImagePixels expects a C array of pixel
data
in scanline order, top-to-bottom, right-to-left. You can specify the data
type of the array (char, short, int, long) and the order in which the
channel data appear (RGB, RGBA, CMYK, etc.) The IM developers tell me
that
ImportImagePixels is optimized for the RGB case so I suspect that this
would
be a very - I repeat, very - fast way to load pixels into an image.


plus plus on the 'very' ;-)
Currently #import_pixels wants a Ruby array of Fixnums which it then
converts to a C array. Lotsa overhead.

Following Ara's suggestion, I was thinking about changing
#import_pixels to
accept a string (to be exact, any object that responds to to_str) in
place
of the array. In this case #import_pixels would simply call to_str and
assume that the result is a C array of the correct type and size and with
the channel data in the specified order and hand it off to
ImportImagePixels
directly.


so

pixels, is_string =
case obj
when Array
[ obj, false ]
else
[ obj.to_str, true ]
end

...
...

??
I realize this doesn't really help you since your input data isn't in the
format ImportImagePixels wants. However, if you (or Ara, or anybody else
with an interest) have an opinion about the usefulness of this idea
I'd like
to hear it.


it sounds brilliant! but:

jib:~ > irb -r mmap -r narray

irb(main):001:0> NArray::byte(42,42).methods.grep /to_s/
=> ["to_s", "to_string"]

irb(main):002:0> Mmap::new('/home/ahoward/.bashrc').methods.grep /to_s/
=> ["to_sym", "to_str", "to_s"]

so maybe something like

cast = %w( pixels to_str to_s to_string ).select{|m| obj.respond_to? m
}.first

pixels = obj.send(cast) if cast

eg. include other likely candidates in addition to to_str. i think
it's safe
to say that any user capable of setting up a region of memory
representing an
inline image will be willing to accept any consequences of doing it
improperly
so doing a blind read is fine in this case - but that's obviously my
opinion.

Actually I was thinking that NArray would meet me half-way.

Right now #import_pixels calls Kernal.Array on the `pixels' argument. My
reasoning for doing this is that this approach allows the caller to pass
any object that supports #to_ary or #to_a (including NArray objects) to
#import_pixels. (The downside for NArrays, of course, is that NArray
responds to #to_a by constructing a real Ruby array with a zillion
elements.)

Now I want to filter out strings and treat them specially. Currently a
string is not a reasonable argument since Kernel.Array simply constructs
an array with the string as its only element. Not too useful for
building images. My notion was to do something like this:

if pixels.respond_to?:)to_str)
pixel_buffer = pixels.to_str
# pass the buffer directly to ImageMagick
else
pixel_array = Kernel.Array(pixels)
# convert the array to a buffer and pass it to IM
end

I can't think of a way this would break existing code, can you? You
could use the return value from NArray#to_s, mmap#to_str, or IO.read as
the `pixels' argument.

The upside is that for real String objects, #to_str is a no-op. The
downside, at least for NArray objects, is that #to_s makes a copy of the
data in the NArray. I've perused the NArray source code and I didn't
find a way to directly access the NArray data without making a copy.

One more complication. The ImportImagePixels function in ImageMagick
requires an argument that identifies the type of type of the data in the
pixel buffer as char (8-bit), short (16-bit) or int (32-bit).
ImageMagick will convert the data as necessary to the size it needs. It
seems to me that it would be useful to support the use of data that is
not the same size as the underlying pixel type. That is, you could
reasonably want to construct an image with 8-bit pixel data from an
NArray.sint (16-bit) object, or vice versa. So, I'm thinking that
#import_pixels should accept an optional 7th argument that indicates the
type of the incoming data (CharPixel, ShortPixel, LongPixel enum values,
probably). The default would be CharPixel. This would also make it
possible to use the same script with different configurations of
ImageMagick.

Thoughts? I'll hold off writing any code until we're in agreement.

thanks for keeping tabs on this btw.

Thanks for the idea!
 
A

Ara.T.Howard

Actually I was thinking that NArray would meet me half-way.

Right now #import_pixels calls Kernal.Array on the `pixels' argument. My
reasoning for doing this is that this approach allows the caller to pass any
object that supports #to_ary or #to_a (including NArray objects) to
#import_pixels. (The downside for NArrays, of course, is that NArray
responds to #to_a by constructing a real Ruby array with a zillion
elements.)
right.

Now I want to filter out strings and treat them specially. Currently a
string is not a reasonable argument since Kernel.Array simply constructs an
array with the string as its only element. Not too useful for building
images. My notion was to do something like this:

if pixels.respond_to?:)to_str)
pixel_buffer = pixels.to_str
# pass the buffer directly to ImageMagick
else
pixel_array = Kernel.Array(pixels)
# convert the array to a buffer and pass it to IM
end
check.

I can't think of a way this would break existing code, can you? You could
use the return value from NArray#to_s, mmap#to_str, or IO.read as the
`pixels' argument.

The upside is that for real String objects, #to_str is a no-op. The
downside, at least for NArray objects, is that #to_s makes a copy of the
data in the NArray.

sounds good. mmap.to_str is a no-op too : guy's got a lot of voodoo going on
under the hood - but it sure works.
I've perused the NArray source code and I didn't find a way to directly
access the NArray data without making a copy.

you just have to use my illicit narray extension ;-) works like this:

mmap = Mmap::new 'data', 'rw', Mmap::MAP_SHARED
memory = mmap.to_str
na = NArray::str memory, width, height, NArray::BYTE
na += 1
exit

and the entire file is incremented by one - no explicit io. however, this is
officially (by matz i think) frowned on. i've spoken with masahiro about this
a little and he was interested in doing something... in any case it's fair to
dump that in the narray camp. right new it calls rb_str_new, which does, in
fact, copy data. perhaps something like rb_str_new4, which does not copy data
- but i don't know what the rules are for creating shared strings... in any
case it would be quite useful now even with a copy since explicit loops
would be avoided and it would therefore still be very fast.
One more complication. The ImportImagePixels function in ImageMagick
requires an argument that identifies the type of type of the data in the
pixel buffer as char (8-bit), short (16-bit) or int (32-bit). ImageMagick
will convert the data as necessary to the size it needs. It seems to me that
it would be useful to support the use of data that is not the same size as
the underlying pixel type. That is, you could reasonably want to construct
an image with 8-bit pixel data from an NArray.sint (16-bit) object, or vice
versa. So, I'm thinking that #import_pixels should accept an optional 7th
argument that indicates the type of the incoming data (CharPixel,
ShortPixel, LongPixel enum values, probably). The default would be
CharPixel. This would also make it possible to use the same script with
different configurations of ImageMagick.

hmmm. i'm having thoughts of a 'Memory' or 'Data' class. it could be backed
by file or not, and could have the notion of a 'quanta' or pixel size. i have
some simple mmap'ing c programs that manipulate data in this way : they just
apply operators to a line of memory where the memory is assumed to be of
certain sized quanta or pixels... but this wouldn't really be required -
specifying the type is fine - hopefully ImageMagick doesn't convert when it
doesn't need too...

all this is probably academic though - i'm sure avoiding loops and loads of
ruby object creations will yield a huge boost even with some data
copy/conversion so everything you've said makes good sense.
Thoughts? I'll hold off writing any code until we're in agreement.

i wouldn't mind some opinions from matz, masahiro, and guy about how sharing
memory amoung ruby objects would best be done - obviously this would be
slickest with the minimum about of data being moved.

cheers.

-a
--
===============================================================================
| email :: ara [dot] t [dot] howard [at] noaa [dot] gov
| phone :: 303.497.6469
| Your life dwells amoung the causes of death
| Like a lamp standing in a strong breeze. --Nagarjuna
===============================================================================
 
M

Morgan

Reading this, I'm wondering what how efficient the from_glob and
to_glob functions are?

This was what I used when I had a project needing to move images
between rmagick and other libraries. In that case I used PNG
format, but in a case like the one being talked about I wonder if you
could synthesize a BMP header, load your data into a string after
it, and make an image with from_glob. (Or rather, would doing that
be better than other available alternatives?)

-Morgan, missed the beginning of this thread, thus apologizes if it's
already been suggested.
 
R

rmagick

Actually this probably would be pretty fast. I haven't worked with BMP
images in quite a while but iirc the format is quite simple.

The to_blob and from_blob methods are actually very similar to the
write and read methods. The only difference is that the blob methods
read from/write to memory and the write/read methods operate on files.
 
T

Timothy Hunter

Ara.T.Howard said:
you just have to use my illicit narray extension ;-) works like this:

mmap = Mmap::new 'data', 'rw', Mmap::MAP_SHARED
memory = mmap.to_str
na = NArray::str memory, width, height, NArray::BYTE
na += 1
exit

Sweet! So you load your pixel data into an mmap object, use the mmap
object to create an NArray object, call some NArray methods, then hand
the mmap object to import_pixels, which calls to_str on it to get a
pointer to the pixel data.

That'll do.

ImageMagick _will_ make a copy of the data. There's no way around that.
But we're still way ahead of the game. I think we're in agreement about
the changes to import_pixels so unless I hear different I'll start
coding. I've not scheduled another release of RMagick yet but I'll be
glad to get one in the pipeline if you're anxious to use the new code.
Otherwise I'll hold off a bit.

Let me know if you have any other thoughts/comments/ideas, and thanks
again for suggesting this approach!

P.S. I won't be at Rubyconf this year but if you're there why not tug on
Matz's coat about sharing memory?
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,769
Messages
2,569,580
Members
45,054
Latest member
TrimKetoBoost

Latest Threads

Top