Permalink
Browse files

greatly increased spec coverage of Prawn::Images::PNG

- we now test all 5 PNG color types to ensure the necesary data is
  extracted correctly
  - Turns out they all worked, except type 4 (greyscale with alpha).
  - Type 4 now works
- Small simplification of the code that embeds the PNG data into the PDF
  • Loading branch information...
1 parent 9e4646f commit cae3d826242512726e4b3b79d5a43b3c71fe1724 @yob yob committed with practicingruby Aug 11, 2008
View
Binary file not shown.
View
Binary file not shown.
Binary file not shown.
Binary file not shown.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
View
Binary file not shown.
View
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
@@ -0,0 +1 @@
+x^E�A�1DQ$ !p$DB$D������3�_���,(�d�_ˣ��j�wa����W9$촙w�աɳ�Km��Wg��۹#���M�h�D�#C�_[A���ş�z��r<:�<l�5V�(�L����c�2w?��s3 Q���b ��;��O���ݼUD�W�
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
View
@@ -0,0 +1,23 @@
+# encoding: utf-8
+
+# PNG files come in different flavours - 5 of them. This example embeds
+# one of each type as proof that they all work.
+
+$LOAD_PATH << File.join(File.dirname(__FILE__), '..', 'lib')
+require "prawn"
+
+images = [
+ ["Type 0", "#{Prawn::BASEDIR}/data/images/web-links.png"],
+ ["Type 2", "#{Prawn::BASEDIR}/data/images/ruport.png"],
+ ["Type 3", "#{Prawn::BASEDIR}/data/images/rails.png"],
+ ["Type 4", "#{Prawn::BASEDIR}/data/images/page_white_text.png"],
+ ["Type 6", "#{Prawn::BASEDIR}/data/images/dice.png"],
+]
+
+Prawn::Document.generate("png_types.pdf", :page_size => "A5") do
+ images.each do |header, file|
+ start_new_page unless header.include?("0")
+ text header
+ image file, :at => [50,450]
+ end
+end
View
@@ -145,22 +145,12 @@ def build_png_object(data, png)
raise ArgumentError, 'PNG uses more than 8 bits'
end
- case png.color_type
- when 0
- ncolor = 1
+ case png.pixel_bytes
+ when 1
color = :DeviceGray
- when 2
- ncolor = 3
- color = :DeviceRGB
when 3
- ncolor = 1
- color = :DeviceRGB
- when 6
- ncolor = 3
- color = :DeviceRGB
- else
- raise ArgumentError, "PNG has unsupported color type"
- end
+ color = :DeviceRGB
+ end
# build the image dict
obj = ref(:Type => :XObject,
@@ -175,7 +165,7 @@ def build_png_object(data, png)
unless png.alpha_channel
obj.data[:DecodeParms] = {:Predictor => 15,
- :Colors => ncolor,
+ :Colors => png.pixel_bytes,
:Columns => png.width}
end
@@ -84,11 +84,22 @@ def initialize(data)
end
# if our img_data contains alpha channel data, split it out
- unfilter_image_data if @color_type == 6
+ unfilter_image_data if alpha_channel?
+ end
+
+ def pixel_bytes
+ case @color_type
+ when 0, 4 then 1
+ when 1, 2, 6 then 3
+ end
end
private
+ def alpha_channel?
+ @color_type == 4 || @color_type == 6
+ end
+
def paeth(a, b, c) # left, above, upper left
p = a + b - c
pa = (p - a).abs
@@ -104,7 +115,10 @@ def unfilter_image_data
data = Zlib::Inflate.inflate(@img_data).unpack 'C*'
@img_data = ""
@alpha_channel = ""
- scanline_length = 4 * @width + 1 # for filter
+
+ # 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
row = 0
pixels = []
until data.empty? do
@@ -114,36 +128,36 @@ def unfilter_image_data
when 0 then # None
when 1 then # Sub
row_data.each_with_index do |byte, index|
- left = index < 4 ? 0 : row_data[index - 4]
+ left = index < pixel_length ? 0 : row_data[index - pixel_length]
row_data[index] = (byte + left) % 256
#p [byte, left, row_data[index]]
end
when 2 then # Up
row_data.each_with_index do |byte, index|
- col = index / 4
- upper = row == 0 ? 0 : pixels[row-1][col][index % 4]
+ col = index / pixel_length
+ upper = row == 0 ? 0 : pixels[row-1][col][index % pixel_length]
row_data[index] = (upper + byte) % 256
end
when 3 then # Average
row_data.each_with_index do |byte, index|
- col = index / 4
- upper = row == 0 ? 0 : pixels[row-1][col][index % 4]
- left = index < 4 ? 0 : row_data[index - 4]
+ 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]
row_data[index] = (byte + ((left + upper)/2).floor) % 256
end
when 4 then # Paeth
left = upper = upper_left = nil
row_data.each_with_index do |byte, index|
- col = index / 4
+ col = index / pixel_length
- left = index < 4 ? 0 : row_data[index - 4]
+ left = index < pixel_length ? 0 : row_data[index - pixel_length]
if row == 0 then
upper = upper_left = 0
else
- upper = pixels[row-1][col][index % 4]
+ upper = pixels[row-1][col][index % pixel_length]
upper_left = col == 0 ? 0 :
- pixels[row-1][col-1][index % 4]
+ pixels[row-1][col-1][index % pixel_length]
end
paeth = paeth left, upper, upper_left
@@ -155,7 +169,7 @@ def unfilter_image_data
end
pixels << []
- row_data.each_slice 4 do |slice|
+ row_data.each_slice pixel_length do |slice|
pixels.last << slice
end
row += 1
@@ -164,8 +178,8 @@ def unfilter_image_data
# convert the pixel data to seperate strings for colours and alpha
pixels.each do |row|
row.each do |pixel|
- @img_data << pixel[0,3].pack("C*")
- @alpha_channel << pixel[3]
+ @img_data << pixel[0,pixel_bytes].pack("C*")
+ @alpha_channel << pixel.last
end
end
View
@@ -3,20 +3,50 @@
# Spec'ing the PNG class. Not complete yet - still needs to check the
# contents of palette and transparency to ensure they're correct.
# Need to find files that have these sections first.
+#
+# see http://www.w3.org/TR/PNG/ for a detailed description of the PNG spec,
+# particuarly Table 11.1 for the different color types
-require File.join(File.expand_path(File.dirname(__FILE__)), "spec_helper")
+require File.join(File.expand_path(File.dirname(__FILE__)), "spec_helper")
-describe "When reading an RGB PNG file" do
+describe "When reading a greyscale PNG file (color type 0)" do
+
+ before(:each) do
+ @filename = "#{Prawn::BASEDIR}/data/images/web-links.png"
+ @data_filename = "#{Prawn::BASEDIR}/data/images/web-links.dat"
+ @img_data = File.open(@filename, "rb") { |f| f.read }
+ end
+
+ it "should read the attributes from the header chunk correctly" do
+ png = Prawn::Images::PNG.new(@img_data)
+
+ png.width.should == 21
+ png.height.should == 14
+ png.bits.should == 8
+ png.color_type.should == 0
+ png.compression_method.should == 0
+ png.filter_method.should == 0
+ png.interlace_method.should == 0
+ end
+
+ it "should read the image data chunk correctly" do
+ png = Prawn::Images::PNG.new(@img_data)
+ data = File.open(@data_filename, "rb") { |f| f.read }
+ png.img_data.should == data
+ end
+end
+
+describe "When reading an RGB PNG file (color type 2)" do
before(:each) do
@filename = "#{Prawn::BASEDIR}/data/images/ruport.png"
@data_filename = "#{Prawn::BASEDIR}/data/images/ruport_data.dat"
@img_data = File.open(@filename, "rb") { |f| f.read }
end
-
+
it "should read the attributes from the header chunk correctly" do
png = Prawn::Images::PNG.new(@img_data)
-
+
png.width.should == 258
png.height.should == 105
png.bits.should == 8
@@ -30,6 +60,102 @@
png = Prawn::Images::PNG.new(@img_data)
data = File.open(@data_filename, "rb") { |f| f.read }
png.img_data.should == data
- end
+ end
+end
+
+# TODO: describe "When reading an indexed color PNG file wiih transparency (color type 3)"
+
+describe "When reading an indexed color PNG file (color type 3)" do
+
+ before(:each) do
+ @filename = "#{Prawn::BASEDIR}/data/images/rails.png"
+ @data_filename = "#{Prawn::BASEDIR}/data/images/rails.dat"
+ @img_data = File.open(@filename, "rb") { |f| f.read }
+ end
+
+ it "should read the attributes from the header chunk correctly" do
+ png = Prawn::Images::PNG.new(@img_data)
+
+ png.width.should == 50
+ png.height.should == 64
+ png.bits.should == 8
+ png.color_type.should == 3
+ png.compression_method.should == 0
+ png.filter_method.should == 0
+ png.interlace_method.should == 0
+ end
+
+ it "should read the image data chunk correctly" do
+ png = Prawn::Images::PNG.new(@img_data)
+ data = File.open(@data_filename, "rb") { |f| f.read }
+ png.img_data.should == data
+ end
end
+describe "When reading a greyscale+alpha PNG file (color type 4)" do
+
+ before(:each) do
+ @filename = "#{Prawn::BASEDIR}/data/images/page_white_text.png"
+ @data_filename = "#{Prawn::BASEDIR}/data/images/page_white_text.dat"
+ @alpha_data_filename = "#{Prawn::BASEDIR}/data/images/page_white_text.alpha"
+ @img_data = File.open(@filename, "rb") { |f| f.read }
+ end
+
+ it "should read the attributes from the header chunk correctly" do
+ png = Prawn::Images::PNG.new(@img_data)
+
+ png.width.should == 16
+ png.height.should == 16
+ png.bits.should == 8
+ png.color_type.should == 4
+ 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)
+ data = File.open(@data_filename, "rb") { |f| f.read }
+ 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)
+ data = File.open(@alpha_data_filename, "rb") { |f| f.read }
+ png.alpha_channel.should == data
+ end
+end
+
+describe "When reading an RGB+alpha PNG file (color type 6)" do
+
+ before(:each) do
+ @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.open(@filename, "rb") { |f| f.read }
+ end
+
+ it "should read the attributes from the header chunk correctly" do
+ png = Prawn::Images::PNG.new(@img_data)
+
+ png.width.should == 320
+ png.height.should == 240
+ png.bits.should == 8
+ 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)
+ data = File.open(@data_filename, "rb") { |f| f.read }
+ 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)
+ data = File.open(@alpha_data_filename, "rb") { |f| f.read }
+ png.alpha_channel.should == data
+ end
+end

0 comments on commit cae3d82

Please sign in to comment.