lib/prawn/images/png.rb in prawn-1.0.0.rc2 vs lib/prawn/images/png.rb in prawn-1.0.0
- old
+ new
@@ -15,16 +15,22 @@
module Images
# A convenience class that wraps the logic for extracting the parts
# of a PNG image that we need to embed them in a PDF
#
class PNG < Image
+ # @group Extension API
+
attr_reader :palette, :img_data, :transparency
attr_reader :width, :height, :bits
attr_reader :color_type, :compression_method, :filter_method
attr_reader :interlace_method, :alpha_channel
attr_accessor :scaled_width, :scaled_height
+ def self.can_render?(image_blob)
+ image_blob[0, 8].unpack("C*") == [137, 80, 78, 71, 13, 10, 26, 10]
+ end
+
# Process a new PNG image
#
# <tt>data</tt>:: A binary string of PNG data
#
def initialize(data)
@@ -86,10 +92,12 @@
data.seek(data.pos + chunk_size)
end
data.read(4) # Skip the CRC
end
+
+ @img_data = Zlib::Inflate.inflate(@img_data)
end
# number of color components to each pixel
#
def colors
@@ -99,38 +107,21 @@
when 2, 6
return 3
end
end
- # number of bits used per pixel
- #
- def pixel_bitlength
- if alpha_channel?
- self.bits * (self.colors + 1)
- else
- self.bits * self.colors
- end
- end
-
# split the alpha channel data from the raw image data in images
# where it's required.
#
def split_alpha_channel!
- unfilter_image_data if alpha_channel?
+ split_image_data if alpha_channel?
end
def alpha_channel?
@color_type == 4 || @color_type == 6
end
- # Adobe Reader can't handle 16-bit png channels -- chop off the second
- # byte (least significant)
- #
- def alpha_channel_bits
- 8
- end
-
# Build a PDF object representing this image in +document+, and return
# a Reference to it.
#
def build_pdf_object(document)
if compression_method != 0
@@ -166,24 +157,25 @@
obj = document.ref!(
:Type => :XObject,
:Subtype => :Image,
:Height => height,
:Width => width,
- :BitsPerComponent => bits,
- :Filter => :FlateDecode
+ :BitsPerComponent => bits
)
- unless alpha_channel
- obj.data[:DecodeParms] = {:Predictor => 15,
- :Colors => colors,
- :BitsPerComponent => bits,
- :Columns => width}
- end
-
# append the actual image data to the object as a stream
obj << img_data
+ obj.stream.filters << {
+ :FlateDecode => {
+ :Predictor => 15,
+ :Colors => colors,
+ :BitsPerComponent => bits,
+ :Columns => width
+ }
+ }
+
# sort out the colours of the image
if palette.empty?
obj.data[:ColorSpace] = color
else
# embed the colour palette in the PDF as a object stream
@@ -230,16 +222,24 @@
smask_obj = document.ref!(
:Type => :XObject,
:Subtype => :Image,
:Height => height,
:Width => width,
- :BitsPerComponent => alpha_channel_bits,
- :Filter => :FlateDecode,
+ :BitsPerComponent => bits,
:ColorSpace => :DeviceGray,
:Decode => [0, 1]
)
- smask_obj << alpha_channel
+ smask_obj.stream << alpha_channel
+
+ smask_obj.stream.filters << {
+ :FlateDecode => {
+ :Predictor => 15,
+ :Colors => 1,
+ :BitsPerComponent => bits,
+ :Columns => width
+ }
+ }
obj.data[:SMask] = smask_obj
end
obj
end
@@ -257,106 +257,43 @@
end
end
private
- def unfilter_image_data
- data = Zlib::Inflate.inflate(@img_data).bytes
- @img_data = ""
- @alpha_channel = ""
+ def split_image_data
+ alpha_bytes = bits / 8
+ color_bytes = colors * bits / 8
- pixel_bytes = pixel_bitlength / 8
- scanline_length = pixel_bytes * self.width + 1
- row = 0
- pixels = []
- row_data = [] # reused for each row of the image
- paeth, pa, pb, pc = nil
+ scanline_length = (color_bytes + alpha_bytes) * self.width + 1
+ scanlines = @img_data.bytesize / scanline_length
+ pixels = self.width * self.height
- data.each do |byte|
- # accumulate a whole scanline of bytes, and then process it all at once
- # we could do this with Enumerable#each_slice, but it allocates memory,
- # and we are trying to avoid that
- row_data << byte
- next if row_data.length < scanline_length
-
- filter = row_data.shift
- case filter
- when 0 # None
- when 1 # Sub
- row_data.each_with_index do |byte, index|
- left = index < pixel_bytes ? 0 : row_data[index - pixel_bytes]
- row_data[index] = (byte + left) % 256
- #p [byte, left, row_data[index]]
- end
- when 2 # Up
- row_data.each_with_index do |byte, index|
- col = (index / pixel_bytes).floor
- upper = row == 0 ? 0 : pixels[row-1][col][index % pixel_bytes]
- row_data[index] = (upper + byte) % 256
- end
- when 3 # Average
- row_data.each_with_index do |byte, index|
- col = (index / pixel_bytes).floor
- upper = row == 0 ? 0 : pixels[row-1][col][index % pixel_bytes]
- left = index < pixel_bytes ? 0 : row_data[index - pixel_bytes]
+ data = StringIO.new(@img_data)
+ data.binmode
- row_data[index] = (byte + ((left + upper)/2).floor) % 256
- end
- when 4 # Paeth
- left = upper = upper_left = nil
- row_data.each_with_index do |byte, index|
- col = (index / pixel_bytes).floor
+ color_data = [0x00].pack('C') * (pixels * color_bytes + scanlines)
+ color = StringIO.new(color_data)
+ color.binmode
- left = index < pixel_bytes ? 0 : row_data[index - pixel_bytes]
- if row.zero?
- upper = upper_left = 0
- else
- upper = pixels[row-1][col][index % pixel_bytes]
- upper_left = col.zero? ? 0 :
- pixels[row-1][col-1][index % pixel_bytes]
- end
+ @alpha_channel = [0x00].pack('C') * (pixels * alpha_bytes + scanlines)
+ alpha = StringIO.new(@alpha_channel)
+ alpha.binmode
- p = left + upper - upper_left
- pa = (p - left).abs
- pb = (p - upper).abs
- pc = (p - upper_left).abs
+ scanlines.times do |line|
+ data.seek(line * scanline_length)
- paeth = if pa <= pb && pa <= pc
- left
- elsif pb <= pc
- upper
- else
- upper_left
- end
+ filter = data.getbyte
- row_data[index] = (byte + paeth) % 256
- end
- else
- raise ArgumentError, "Invalid filter algorithm #{filter}"
- end
+ color.putc filter
+ alpha.putc filter
- s = []
- row_data.each_slice pixel_bytes do |slice|
- s << slice
+ self.width.times do
+ color.write data.read(color_bytes)
+ alpha.write data.read(alpha_bytes)
end
- pixels << s
- row += 1
- row_data.clear
end
- # convert the pixel data to separate strings for colours and alpha
- color_byte_size = self.colors * self.bits / 8
- alpha_byte_size = alpha_channel_bits / 8
- pixels.each do |this_row|
- this_row.each do |pixel|
- @img_data << pixel[0, color_byte_size].pack("C*")
- @alpha_channel << pixel[color_byte_size, alpha_byte_size].pack("C*")
- end
- end
-
- # compress the data
- @img_data = Zlib::Deflate.deflate(@img_data)
- @alpha_channel = Zlib::Deflate.deflate(@alpha_channel)
+ @img_data = color_data
end
end
end
end