lib/prawn/images.rb in prawn-0.11.1.pre vs lib/prawn/images.rb in prawn-0.11.1
- old
+ new
@@ -66,37 +66,34 @@
Prawn.verify_options [:at, :position, :vposition, :height,
:width, :scale, :fit], options
if file.respond_to?(:read)
image_content = file.read
- else
+ else
raise ArgumentError, "#{file} not found" unless File.file?(file)
- image_content = File.binread(file)
+ image_content = File.binread(file)
end
image_sha1 = Digest::SHA1.hexdigest(image_content)
# if this image has already been embedded, just reuse it
if image_registry[image_sha1]
info = image_registry[image_sha1][:info]
image_obj = image_registry[image_sha1][:obj]
else
- # build the image object and embed the raw data
- image_obj = case detect_image_format(image_content)
- when :jpg then
- info = Prawn::Images::JPG.new(image_content)
- build_jpg_object(image_content, info)
- when :png then
- info = Prawn::Images::PNG.new(image_content)
+ # Build the image object
+ klass = case detect_image_format(image_content)
+ when :jpg then Prawn::Images::JPG
+ when :png then Prawn::Images::PNG
+ end
+ info = klass.new(image_content)
- # 16-bit color only supported in 1.5+ (ISO 32000-1:2008 8.9.5.1)
- if info.bits > 8
- min_version 1.5
- end
+ # Bump PDF version if the image requires it
+ min_version(info.min_pdf_version) if info.respond_to?(:min_pdf_version)
- build_png_object(image_content, info)
- end
+ # Add the image to the PDF and register it in case we see it again.
+ image_obj = info.build_pdf_object(self)
image_registry[image_sha1] = {:obj => image_obj, :info => info}
end
# find where the image will be placed and how big it will be
w,h = calc_image_dimensions(info, options)
@@ -162,151 +159,10 @@
def overruns_page?(h)
(self.y - h) < bounds.absolute_bottom
end
- def build_jpg_object(data, jpg)
- color_space = case jpg.channels
- when 1
- :DeviceGray
- when 3
- :DeviceRGB
- when 4
- :DeviceCMYK
- else
- raise ArgumentError, 'JPG uses an unsupported number of channels'
- end
- obj = ref!(:Type => :XObject,
- :Subtype => :Image,
- :Filter => :DCTDecode,
- :ColorSpace => color_space,
- :BitsPerComponent => jpg.bits,
- :Width => jpg.width,
- :Height => jpg.height,
- :Length => data.size )
-
- # add extra decode params for CMYK images. By swapping the
- # min and max values from the default, we invert the colours. See
- # section 4.8.4 of the spec.
- if color_space == :DeviceCMYK
- obj.data[:Decode] = [ 1.0, 0.0, 1.0, 0.0, 1.0, 0.0, 1.0, 0.0 ]
- end
-
- obj << data
- return obj
- end
-
- def build_png_object(data, png)
-
- if png.compression_method != 0
- raise Errors::UnsupportedImageType, 'PNG uses an unsupported compression method'
- end
-
- if png.filter_method != 0
- raise Errors::UnsupportedImageType, 'PNG uses an unsupported filter method'
- end
-
- if png.interlace_method != 0
- raise Errors::UnsupportedImageType, 'PNG uses unsupported interlace method'
- 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.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
- obj = ref!(:Type => :XObject,
- :Subtype => :Image,
- :Height => png.height,
- :Width => png.width,
- :BitsPerComponent => png.bits,
- :Length => png.img_data.size,
- :Filter => :FlateDecode
- )
-
- unless png.alpha_channel
- obj.data[:DecodeParms] = {:Predictor => 15,
- :Colors => png.colors,
- :BitsPerComponent => png.bits,
- :Columns => png.width}
- end
-
- # append the actual image data to the object as a stream
- obj << png.img_data
-
- # sort out the colours of the image
- if png.palette.empty?
- obj.data[:ColorSpace] = color
- else
- # embed the colour palette in the PDF as a object stream
- palette_obj = ref!(:Length => png.palette.size)
- palette_obj << png.palette
-
- # build the color space array for the image
- obj.data[:ColorSpace] = [:Indexed,
- :DeviceRGB,
- (png.palette.size / 3) -1,
- palette_obj]
- end
-
- # *************************************
- # add transparency data if necessary
- # *************************************
-
- # For PNG color types 0, 2 and 3, the transparency data is stored in
- # a dedicated PNG chunk, and is exposed via the transparency attribute
- # of the PNG class.
- if png.transparency[:grayscale]
- # Use Color Key Masking (spec section 4.8.5)
- # - An array with N elements, where N is two times the number of color
- # components.
- val = png.transparency[:grayscale]
- obj.data[:Mask] = [val, val]
- elsif png.transparency[:rgb]
- # Use Color Key Masking (spec section 4.8.5)
- # - An array with N elements, where N is two times the number of color
- # components.
- rgb = png.transparency[:rgb]
- obj.data[:Mask] = rgb.collect { |x| [x,x] }.flatten
- elsif png.transparency[:indexed]
- # TODO: broken. I was attempting to us Color Key Masking, but I think
- # we need to construct an SMask i think. Maybe do it inside
- # the PNG class, and store it in alpha_channel
- #obj.data[:Mask] = png.transparency[:indexed]
- end
-
- # For PNG color types 4 and 6, the transparency data is stored as a alpha
- # channel mixed in with the main image data. The PNG class seperates
- # it out for us and makes it available via the alpha_channel attribute
- if png.alpha_channel
- min_version 1.4
- smask_obj = ref!(:Type => :XObject,
- :Subtype => :Image,
- :Height => png.height,
- :Width => png.width,
- :BitsPerComponent => png.alpha_channel_bits,
- :Length => png.alpha_channel.size,
- :Filter => :FlateDecode,
- :ColorSpace => :DeviceGray,
- :Decode => [0, 1]
- )
- smask_obj << png.alpha_channel
- obj.data[:SMask] = smask_obj
- end
-
- return obj
- end
-
def calc_image_dimensions(info, options)
w = options[:width] || info.width
h = options[:height] || info.height
if options[:width] && !options[:height]
@@ -338,12 +194,14 @@
end
def detect_image_format(content)
top = content[0,128]
- if top[0, 3] == "\xff\xd8\xff"
+ # Unpack before comparing for JPG header, so as to avoid having to worry
+ # about the source string encoding. We just want a byte-by-byte compare.
+ if top[0, 3].unpack("C*") == [255, 216, 255]
return :jpg
- elsif top[0, 8] == "\x89PNG\x0d\x0a\x1a\x0a"
+ elsif top[0, 8].unpack("C*") == [137, 80, 78, 71, 13, 10, 26, 10]
return :png
else
raise Errors::UnsupportedImageType, "image file is an unrecognised format"
end
end