Permalink
Browse files

add support for 16bit PNGs, types 4 and 6

  • Loading branch information...
1 parent 09f19fa commit a1c0c2874d84c13157e6aa6326603d7c5299f5cc @yob yob committed May 25, 2009
Binary file not shown.
View
Binary file not shown.
View
@@ -185,19 +185,17 @@ def build_png_object(data, png)
raise Errors::UnsupportedImageType, 'PNG uses unsupported interlace method'
end
- if png.bits > 8
- raise Errors::UnsupportedImageType, 'PNG uses more than 8 bits'
- end
-
# some PNG types store the colour and alpha channel data together,
# which the PDF spec doesn't like, so split it out.
png.split_alpha_channel!
- case png.pixel_bytes
+ case png.colors
when 1
color = :DeviceGray
when 3
color = :DeviceRGB
+ else
+ raise Errors::UnsupportedImageType, "PNG uses an unsupported number of colors (#{png.colors})"
end
# build the image dict
@@ -212,7 +210,7 @@ def build_png_object(data, png)
unless png.alpha_channel
obj.data[:DecodeParms] = {:Predictor => 15,
- :Colors => png.pixel_bytes,
+ :Colors => png.colors,
:BitsPerComponent => png.bits,
:Columns => png.width}
end
@@ -269,7 +267,7 @@ def build_png_object(data, png)
:Subtype => :Image,
:Height => png.height,
:Width => png.width,
- :BitsPerComponent => 8,
+ :BitsPerComponent => png.bits,
:Length => png.alpha_channel.size,
:Filter => :FlateDecode,
:ColorSpace => :DeviceGray,
@@ -14,13 +14,13 @@ module Prawn
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
+ class PNG
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
-
+
# Process a new PNG image
#
# <tt>data</tt>:: A string containing a full PNG file
@@ -88,13 +88,30 @@ def initialize(data)
end
end
- def pixel_bytes
- @pixel_bytes ||= case @color_type
- when 0, 3, 4 then 1
- when 1, 2, 6 then 3
+ # number of color components to each pixel
+ #
+ def colors
+ case self.color_type
+ when 0, 3, 4
+ return 1
+ 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?
end
@@ -110,11 +127,10 @@ def unfilter_image_data
@img_data = ""
@alpha_channel = ""
- # each pixel has the color bytes, plus a byte of alpha channel
- pixel_length = pixel_bytes + 1
- scanline_length = pixel_length * @width + 1 # for filter
+ pixel_bytes = pixel_bitlength / 8
+ scanline_length = pixel_bytes * self.width + 1
row = 0
- pixels = []
+ pixels = []
paeth, pa, pb, pc = nil
until data.empty? do
row_data = data.slice! 0, scanline_length
@@ -123,42 +139,42 @@ def unfilter_image_data
when 0 # None
when 1 # Sub
row_data.each_with_index do |byte, index|
- left = index < pixel_length ? 0 : row_data[index - pixel_length]
+ 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_length
- upper = row == 0 ? 0 : pixels[row-1][col][index % pixel_length]
+ 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_length
- upper = row == 0 ? 0 : pixels[row-1][col][index % pixel_length]
- left = index < pixel_length ? 0 : row_data[index - pixel_length]
+ 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]
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_length
+ col = index / pixel_bytes
- left = index < pixel_length ? 0 : row_data[index - pixel_length]
+ 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_length]
+ upper = pixels[row-1][col][index % pixel_bytes]
upper_left = col.zero? ? 0 :
- pixels[row-1][col-1][index % pixel_length]
+ pixels[row-1][col-1][index % pixel_bytes]
end
p = left + upper - upper_left
pa = (p - left).abs
pb = (p - upper).abs
- pc = (p - upper_left).abs
+ pc = (p - upper_left).abs
paeth = if pa <= pb && pa <= pc
left
@@ -167,27 +183,28 @@ def unfilter_image_data
else
upper_left
end
-
+
row_data[index] = (byte + paeth) % 256
- #p [byte, paeth, row_data[index]]
end
else
raise ArgumentError, "Invalid filter algorithm #{filter}"
end
s = []
- row_data.each_slice pixel_length do |slice|
+ row_data.each_slice pixel_bytes do |slice|
s << slice
end
pixels << s
row += 1
end
# convert the pixel data to seperate strings for colours and alpha
+ color_byte_size = self.colors * self.bits / 8
+ alpha_byte_size = self.bits / 8
pixels.each do |row|
row.each do |pixel|
- @img_data << pixel[0,pixel_bytes].pack("C*")
- @alpha_channel << pixel.last
+ @img_data << pixel[0, color_byte_size].pack("C*")
+ @alpha_channel << pixel[color_byte_size, alpha_byte_size].pack("C*")
end
end
View
@@ -45,11 +45,6 @@
filename = "#{Prawn::BASEDIR}/data/images/dice_interlaced.png"
lambda { @pdf.image filename, :at => [100,100] }.should.raise(Prawn::Errors::UnsupportedImageType)
end
-
- it "should raise an UnsupportedImageType if passed a 16 bit PNG" do
- filename = "#{Prawn::BASEDIR}/data/images/16bit.png"
- lambda { @pdf.image filename, :at => [100,100] }.should.raise(Prawn::Errors::UnsupportedImageType)
- end
describe ":fit option" do
it "should fit inside the defined constraints" do
View
@@ -14,7 +14,7 @@
before(:each) do
@filename = "#{Prawn::BASEDIR}/data/images/web-links.png"
@data_filename = "#{Prawn::BASEDIR}/data/images/web-links.dat"
- @img_data = File.binread(@filename)
+ @img_data = File.binread(@filename)
end
it "should read the attributes from the header chunk correctly" do
@@ -105,7 +105,7 @@
before(:each) do
@filename = "#{Prawn::BASEDIR}/data/images/rails.png"
@data_filename = "#{Prawn::BASEDIR}/data/images/rails.dat"
- @img_data = File.binread(@filename)
+ @img_data = File.binread(@filename)
end
it "should read the attributes from the header chunk correctly" do
@@ -158,7 +158,7 @@
it "should correctly extract the alpha channel data from the image data chunk" do
png = Prawn::Images::PNG.new(@img_data)
png.split_alpha_channel!
- data = File.binread(@alpha_data_filename)
+ data = File.binread(@alpha_data_filename)
png.alpha_channel.should == data
end
end
@@ -169,7 +169,7 @@
@filename = "#{Prawn::BASEDIR}/data/images/dice.png"
@data_filename = "#{Prawn::BASEDIR}/data/images/dice.dat"
@alpha_data_filename = "#{Prawn::BASEDIR}/data/images/dice.alpha"
- @img_data = File.binread(@filename)
+ @img_data = File.binread(@filename)
end
it "should read the attributes from the header chunk correctly" do
@@ -187,7 +187,43 @@
it "should correctly return the raw image data (with no alpha channel) from the image data chunk" do
png = Prawn::Images::PNG.new(@img_data)
png.split_alpha_channel!
- data = File.binread(@data_filename)
+ data = File.binread(@data_filename)
+ png.img_data.should == data
+ end
+
+ it "should correctly extract the alpha channel data from the image data chunk" do
+ png = Prawn::Images::PNG.new(@img_data)
+ png.split_alpha_channel!
+ data = File.binread(@alpha_data_filename)
+ png.alpha_channel.should == data
+ end
+end
+
+describe "When reading a 16bit RGB+alpha PNG file (color type 6)" do
+
+ before(:each) do
+ @filename = "#{Prawn::BASEDIR}/data/images/16bit.png"
+ @data_filename = "#{Prawn::BASEDIR}/data/images/16bit.dat"
+ @alpha_data_filename = "#{Prawn::BASEDIR}/data/images/16bit.alpha"
+ @img_data = File.binread(@filename)
+ end
+
+ it "should read the attributes from the header chunk correctly" do
+ png = Prawn::Images::PNG.new(@img_data)
+
+ png.width.should == 32
+ png.height.should == 32
+ png.bits.should == 16
+ png.color_type.should == 6
+ png.compression_method.should == 0
+ png.filter_method.should == 0
+ png.interlace_method.should == 0
+ end
+
+ it "should correctly return the raw image data (with no alpha channel) from the image data chunk" do
+ png = Prawn::Images::PNG.new(@img_data)
+ png.split_alpha_channel!
+ data = File.binread(@data_filename)
png.img_data.should == data
end

0 comments on commit a1c0c28

Please sign in to comment.