In my application I am trying to save an arbitrary part of a rendered HTML canvas to an image file. In my Javascript I call ctx.getImageData(x, y, w, h)
and pass the resulting object to my macruby code (though if you know a solution in objc I am also very interested).
There I'm trying to create a NSBitmapImageRep object so that I can then save to an image format the user desires.
This is my code so far (the function gets a WebScriptObject as it's argument):
def setimagedata(d)
w = d.valueForKey("width").to_i
h = d.valueForKey("height").to_i
data = Pointer.new(:char, d.valueForKey("data").valueForKey("length").to_i)
d.valueForKey("data").valueForKey("length").to_i.times do |i|
data[i] = d.valueForKey("data").webScriptValueAtIndex(i).to_i
end
puts "data complete" # get's called
@exported_image = NSBitmapImageRep.alloc.initWithBitmapDataPlanes(data,
pixelsWide: w, pixelsHigh:h, bitsPerSample: 32,
samplesPerPixel: 4, hasAlpha: true, isPlanar: false,
colorSpaceName: NSCalibratedRGBColorSpace,
bitmapFormat: NSAlphaNonpremultipliedBitmapFormat,
bytesPerRow: 0, bitsPerPixel: 0)
puts "done" # doesn't get called
end
The code doesn't seem to get through the initWithBitmapDataPlanes
function but gives no error.
My question is: what am I doing wrong? Is this approach reasonable (if not, what would be better?).
Using Phrogz' answer below I got an intermediate solution: I use another canvas, getImageData, putImageData and toDataURL to get a data url of the region needed. In my setimagedata
I simply save the data url and my dataOfType: error:
method looks like this:
def dataOfType(type, error:outError)
workspace = NSWorkspace.sharedWorkspace
if workspace.type(type, conformsToType: "public.image")
@data_url[ /(?<=,).+/ ].unpack("m").first
end
end
The missing secrete sauce it this ugly hax:
class NSString
def writeToURL(url, options: opts, error: error)
File.open(url.path, "w") {|f| f << self }
end
end
It leverages duck typing in Cocoa and defines a selector normally on NSData to write itself to a file.
This seems to work so far and I'm happy I reached a solution. However I would still like to see a solution using NSBitmapImageRep. The next feature I'm implementing is exporting to video and I believe I will need the finer control provided by this class.
Instead of using getImageData
, I would suggest using Canvas.toDataURL. This will give you a binary PNG (or JPEG) that happens to be base64 encoded. Decode the base64 and you'll have a file you can save for the user, or process to transcode to another format.
Edit: I originally deleted my answer because I realized that you wanted to serialize a sub-region of the canvas. Then I realized that if this helps you could instead do this:
To decode the base64 data, you can use the base64
ruby library's decode64 method. Note that the implementation of this method is exceedingly simple, however, and you could just inline it:
def decode64(str)
str.unpack("m").first
end
Edit 2: For example, given the results of a toDataURL
call placed in a Ruby string, simply:
require 'base64'
data_only = data_url[ /(?<=,).+/ ] # Find everything after the first comma
File.open( 'foo.png', 'w' ){ |f| f << Base64.decode64( data_only ) }