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