简体   繁体   中英

How to create an image from canvas data?

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) 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?).

Edit

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:

  1. Create a new canvas of the size of the subregion.
  2. Use context.drawImage() to copy the sub-region from the original to the new canvas.
  3. Call Canvas.toDataURL on the new canvas to get the base64-serialized data.

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 ) }

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM