Ara.T.Howard wrote:
> On Wed, 31 Aug 2005, Timothy Hunter wrote:
> 
>> 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!