moduleChunkyPNGclassCanvas# Methods for encoding a Canvas into a PNG datastream#modulePNGEncodingTRUECOLOR_ENCODER=lambda{|color|Color.to_truecolor_bytes(color)}TRUECOLOR_ALPHA_ENCODER=lambda{|color|Color.to_truecolor_alpha_bytes(color)}GRAYSCALE_ENCODER=lambda{|color|Color.to_grayscale_bytes(color)}GRAYSCALE_ALPHA_ENCODER=lambda{|color|Color.to_grayscale_alpha_bytes(color)}defwrite(io,constraints={})to_datastream(constraints).write(io)enddefsave(filename,constraints={})File.open(filename,'wb'){|io|write(io,constraints)}enddefto_blob(constraints={})to_datastream(constraints).to_blobendalias:to_string:to_blobalias:to_s:to_blob# Converts this Canvas to a datastream, so that it can be saved as a PNG image.# @param [Hash] constraints The constraints to use when encoding the canvas.defto_datastream(constraints={})data=encode_png(constraints)ds=Datastream.newds.header_chunk=Chunk::Header.new(data[:header])ds.palette_chunk=data[:palette_chunk]ifdata[:palette_chunk]ds.transparency_chunk=data[:transparency_chunk]ifdata[:transparency_chunk]ds.data_chunks=Chunk::ImageData.split_in_chunks(data[:pixelstream])ds.end_chunk=Chunk::End.newreturndsendprotecteddefencode_png(constraints={})encoding=determine_png_encoding(constraints)result={}result[:header]={:width=>width,:height=>height,:color=>encoding[:color_mode],:interlace=>encoding[:interlace]}ifencoding[:color_mode]==ChunkyPNG::COLOR_INDEXEDresult[:palette_chunk]=encoding[:palette].to_plte_chunkresult[:transparency_chunk]=encoding[:palette].to_trns_chunkunlessencoding[:palette].opaque?endresult[:pixelstream]=encode_png_pixelstream(encoding[:color_mode],encoding[:palette],encoding[:interlace])returnresultenddefdetermine_png_encoding(constraints={})ifconstraints==:fast_rgbencoding={:color_mode=>ChunkyPNG::COLOR_TRUECOLOR}elsifconstraints==:fast_rgbaencoding={:color_mode=>ChunkyPNG::COLOR_TRUECOLOR_ALPHA}elseencoding=constraintsend# Do not create a pallete when the encoding is given and does not require a palette.ifencoding[:color_mode]encoding[:palette]||=paletteifencoding[:color_mode]==ChunkyPNG::COLOR_INDEXEDelseencoding[:palette]||=paletteencoding[:color_mode]||=encoding[:palette].best_colormodeendencoding[:interlace]=caseencoding[:interlace]whennil,false,ChunkyPNG::INTERLACING_NONEthenChunkyPNG::INTERLACING_NONEwhentrue,ChunkyPNG::INTERLACING_ADAM7thenChunkyPNG::INTERLACING_ADAM7elseencoding[:interlace]endreturnencodingenddefencode_png_pixelstream(color_mode=ChunkyPNG::COLOR_TRUECOLOR,palette=nil,interlace=ChunkyPNG::INTERLACING_NONE)ifcolor_mode==ChunkyPNG::COLOR_INDEXED&&!palette.can_encode?raise"This palette is not suitable for encoding!"endpixel_size=Color.bytesize(color_mode)pixel_encoder=casecolor_modewhenChunkyPNG::COLOR_TRUECOLORthenTRUECOLOR_ENCODERwhenChunkyPNG::COLOR_TRUECOLOR_ALPHAthenTRUECOLOR_ALPHA_ENCODERwhenChunkyPNG::COLOR_GRAYSCALEthenGRAYSCALE_ENCODERwhenChunkyPNG::COLOR_GRAYSCALE_ALPHAthenGRAYSCALE_ALPHA_ENCODERwhenChunkyPNG::COLOR_INDEXEDthenlambda{|color|[palette.index(color)]}elseraise"Cannot encode pixels for this mode: #{color_mode}!"endcaseinterlacewhenChunkyPNG::INTERLACING_NONEthenencode_png_image_without_interlacing(pixel_size,pixel_encoder)whenChunkyPNG::INTERLACING_ADAM7thenencode_png_image_with_interlacing(pixel_size,pixel_encoder)elseraise"Unknown interlacing method!"endenddefencode_png_image_without_interlacing(pixel_size,pixel_encoder)stream=""encode_png_image_pass_to_stream(stream,pixel_size,pixel_encoder)streamenddefencode_png_image_with_interlacing(pixel_size,pixel_encoder)stream=""0.upto(6)do|pass|subcanvas=self.class.adam7_extract_pass(pass,self)subcanvas.encode_png_image_pass_to_stream(stream,pixel_size,pixel_encoder)endstreamenddefencode_png_image_pass_to_stream(stream,pixel_size,pixel_encoder)casepixel_encoderwhenTRUECOLOR_ALPHA_ENCODERstream<<pixels.pack("xN#{width}"*height)whenTRUECOLOR_ENCODERline_packer='x'+('NX'*width)stream<<pixels.pack(line_packer*height)elseprevious_bytes=Array.new(pixel_size*width,0)each_scanlinedo|line|unencoded_bytes=line.map(&pixel_encoder).flattenstream<<encode_png_scanline_up(unencoded_bytes,previous_bytes,pixel_size).pack('C*')previous_bytes=unencoded_bytesendendend# Passes to this canvas of pixel values line by line.# @yield [Array<Fixnum>] An line of fixnums reprsenting pixelsdefeach_scanline(&block)forline_noin0...heightdoscanline=pixels[width*line_no,width]yield(scanline)endenddefencode_png_scanline(filter,bytes,previous_bytes=nil,pixelsize=3)casefilterwhenChunkyPNG::FILTER_NONEthenencode_png_scanline_none(bytes,previous_bytes,pixelsize)whenChunkyPNG::FILTER_SUBthenencode_png_scanline_sub(bytes,previous_bytes,pixelsize)whenChunkyPNG::FILTER_UPthenencode_png_scanline_up(bytes,previous_bytes,pixelsize)whenChunkyPNG::FILTER_AVERAGEthenencode_png_scanline_average(bytes,previous_bytes,pixelsize)whenChunkyPNG::FILTER_PAETHthenencode_png_scanline_paeth(bytes,previous_bytes,pixelsize)elseraise"Unknown filter type"endenddefencode_png_scanline_none(original_bytes,previous_bytes=nil,pixelsize=3)[ChunkyPNG::FILTER_NONE]+original_bytesenddefencode_png_scanline_sub(original_bytes,previous_bytes=nil,pixelsize=3)encoded_bytes=[]forindexin0...original_bytes.lengthdoa=(index>=pixelsize)?original_bytes[index-pixelsize]:0encoded_bytes[index]=(original_bytes[index]-a)%256end[ChunkyPNG::FILTER_SUB]+encoded_bytesenddefencode_png_scanline_up(original_bytes,previous_bytes,pixelsize=3)encoded_bytes=[]forindexin0...original_bytes.lengthdob=previous_bytes[index]encoded_bytes[index]=(original_bytes[index]-b)%256end[ChunkyPNG::FILTER_UP]+encoded_bytesenddefencode_png_scanline_average(original_bytes,previous_bytes,pixelsize=3)encoded_bytes=[]forindexin0...original_bytes.lengthdoa=(index>=pixelsize)?original_bytes[index-pixelsize]:0b=previous_bytes[index]encoded_bytes[index]=(original_bytes[index]-(a+b/2).floor)%256end[ChunkyPNG::FILTER_AVERAGE]+encoded_bytesenddefencode_png_scanline_paeth(original_bytes,previous_bytes,pixelsize=3)encoded_bytes=[]foriin0...original_bytes.lengthdoa=(i>=pixelsize)?original_bytes[i-pixelsize]:0b=previous_bytes[i]c=(i>=pixelsize)?previous_bytes[i-pixelsize]:0p=a+b-cpa=(p-a).abspb=(p-b).abspc=(p-c).abspr=(pa<=pb&&pa<=pc)?a:(pb<=pc?b:c)encoded_bytes[i]=(original_bytes[i]-pr)%256end[ChunkyPNG::FILTER_PAETH]+encoded_bytesendendendend