lib/prawn/images/png.rb in prawn-0.15.0 vs lib/prawn/images/png.rb in prawn-1.0.0.rc1
- old
+ new
@@ -15,22 +15,16 @@
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)
@@ -92,12 +86,10 @@
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
@@ -107,21 +99,38 @@
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!
- split_image_data if alpha_channel?
+ unfilter_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
@@ -157,35 +166,35 @@
obj = document.ref!(
:Type => :XObject,
:Subtype => :Image,
:Height => height,
:Width => width,
- :BitsPerComponent => bits
+ :BitsPerComponent => bits,
+ :Length => img_data.size,
+ :Filter => :FlateDecode
)
+ 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
- palette_obj = document.ref!({})
+ palette_obj = document.ref!(:Length => palette.size)
palette_obj << palette
# build the color space array for the image
- obj.data[:ColorSpace] = [:Indexed,
+ obj.data[:ColorSpace] = [:Indexed,
:DeviceRGB,
(palette.size / 3) -1,
palette_obj]
end
@@ -222,24 +231,17 @@
smask_obj = document.ref!(
:Type => :XObject,
:Subtype => :Image,
:Height => height,
:Width => width,
- :BitsPerComponent => bits,
+ :BitsPerComponent => alpha_channel_bits,
+ :Length => alpha_channel.size,
+ :Filter => :FlateDecode,
:ColorSpace => :DeviceGray,
:Decode => [0, 1]
)
- smask_obj.stream << alpha_channel
-
- smask_obj.stream.filters << {
- :FlateDecode => {
- :Predictor => 15,
- :Colors => 1,
- :BitsPerComponent => bits,
- :Columns => width
- }
- }
+ smask_obj << alpha_channel
obj.data[:SMask] = smask_obj
end
obj
end
@@ -257,43 +259,98 @@
end
end
private
- def split_image_data
- alpha_bytes = bits / 8
- color_bytes = colors * bits / 8
+ def unfilter_image_data
+ data = Zlib::Inflate.inflate(@img_data).unpack 'C*'
+ @img_data = ""
+ @alpha_channel = ""
- scanline_length = (color_bytes + alpha_bytes) * self.width + 1
- scanlines = @img_data.bytesize / scanline_length
- pixels = self.width * self.height
+ pixel_bytes = pixel_bitlength / 8
+ scanline_length = pixel_bytes * self.width + 1
+ row = 0
+ pixels = []
+ paeth, pa, pb, pc = nil
+ until data.empty? do
+ row_data = data.slice! 0, 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
+ 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
+ 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
- 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
+ end
+ pixels << s
+ row += 1
+ end
- self.width.times do
- color.write data.read(color_bytes)
- alpha.write data.read(alpha_bytes)
+ # convert the pixel data to seperate 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
- @img_data = color_data
+ # compress the data
+ @img_data = Zlib::Deflate.deflate(@img_data)
+ @alpha_channel = Zlib::Deflate.deflate(@alpha_channel)
end
end
end
end