Add the image at filename to the current page. Currently only JPG and PNG files are supported.
Arguments:
file | path to file or an object that responds to # |
Options:
:at | an array [x,y] with the location of the top left corner of the image. |
:position | One of (:left, :center, :right) or an x-offset |
:vposition | One of (:top, :center, :center) or an y-offset |
:height | the height of the image [actual height of the image] |
:width | the width of the image [actual width of the image] |
:scale | scale the dimensions of the image proportionally |
:fit | scale the dimensions of the image proportionally to fit inside [width,height] |
Prawn::Document.generate("image2.pdf", :page_layout => :landscape) do pigs = "#{Prawn::BASEDIR}/data/images/pigs.jpg" image pigs, :at => [50,450], :width => 450 dice = "#{Prawn::BASEDIR}/data/images/dice.png" image dice, :at => [50, 450], :scale => 0.75 end
If only one of :width / :height are provided, the image will be scaled proportionally. When both are provided, the image will be stretched to fit the dimensions without maintaining the aspect ratio.
If :at is provided, the image will be place in the current page but the text position will not be changed.
If instead of an explicit filename, an object with a read method is passed as file, you can embed images from IO objects and things that act like them (including Tempfiles and open-uri objects).
require "open-uri" Prawn::Document.generate("remote_images.pdf") do image open("http://prawn.majesticseacreature.com/media/prawn_logo.png") end
This method returns an image info object which can be used to check the dimensions of an image object if needed. (See also: Prawn::Images::PNG , Prawn::Images::JPG)
# File lib/prawn/images.rb, line 60 60: def image(file, options={}) 61: Prawn.verify_options [:at, :position, :vposition, :height, 62: :width, :scale, :fit], options 63: 64: if file.respond_to?(:read) 65: image_content = file.read 66: else 67: raise ArgumentError, "#{file} not found" unless File.file?(file) 68: image_content = File.binread(file) 69: end 70: 71: image_sha1 = Digest::SHA1.hexdigest(image_content) 72: 73: # if this image has already been embedded, just reuse it 74: if image_registry[image_sha1] 75: info = image_registry[image_sha1][:info] 76: image_obj = image_registry[image_sha1][:obj] 77: else 78: # build the image object and embed the raw data 79: image_obj = case detect_image_format(image_content) 80: when :jpg then 81: info = Prawn::Images::JPG.new(image_content) 82: build_jpg_object(image_content, info) 83: when :png then 84: info = Prawn::Images::PNG.new(image_content) 85: build_png_object(image_content, info) 86: end 87: image_registry[image_sha1] = {:obj => image_obj, :info => info} 88: end 89: 90: # find where the image will be placed and how big it will be 91: w,h = calc_image_dimensions(info, options) 92: 93: if options[:at] 94: x,y = map_to_absolute(options[:at]) 95: else 96: x,y = image_position(w,h,options) 97: move_text_position h 98: end 99: 100: # add a reference to the image object to the current page 101: # resource list and give it a label 102: label = "I#{next_image_id}" 103: page.xobjects.merge!( label => image_obj ) 104: 105: # add the image to the current page 106: instruct = "\nq\n%.3f 0 0 %.3f %.3f %.3f cm\n/%s Do\nQ" 107: add_content instruct % [ w, h, x, y - h, label ] 108: 109: return info 110: end
# File lib/prawn/images.rb, line 143 143: def build_jpg_object(data, jpg) 144: color_space = case jpg.channels 145: when 1 146: :DeviceGray 147: when 3 148: :DeviceRGB 149: when 4 150: :DeviceCMYK 151: else 152: raise ArgumentError, 'JPG uses an unsupported number of channels' 153: end 154: obj = ref!(:Type => :XObject, 155: :Subtype => :Image, 156: :Filter => :DCTDecode, 157: :ColorSpace => color_space, 158: :BitsPerComponent => jpg.bits, 159: :Width => jpg.width, 160: :Height => jpg.height, 161: :Length => data.size ) 162: 163: # add extra decode params for CMYK images. By swapping the 164: # min and max values from the default, we invert the colours. See 165: # section 4.8.4 of the spec. 166: if color_space == :DeviceCMYK 167: obj.data[:Decode] = [ 1.0, 0.0, 1.0, 0.0, 1.0, 0.0, 1.0, 0.0 ] 168: end 169: 170: obj << data 171: return obj 172: end
# File lib/prawn/images.rb, line 174 174: def build_png_object(data, png) 175: 176: if png.compression_method != 0 177: raise Errors::UnsupportedImageType, 'PNG uses an unsupported compression method' 178: end 179: 180: if png.filter_method != 0 181: raise Errors::UnsupportedImageType, 'PNG uses an unsupported filter method' 182: end 183: 184: if png.interlace_method != 0 185: raise Errors::UnsupportedImageType, 'PNG uses unsupported interlace method' 186: end 187: 188: # some PNG types store the colour and alpha channel data together, 189: # which the PDF spec doesn't like, so split it out. 190: png.split_alpha_channel! 191: 192: case png.colors 193: when 1 194: color = :DeviceGray 195: when 3 196: color = :DeviceRGB 197: else 198: raise Errors::UnsupportedImageType, "PNG uses an unsupported number of colors (#{png.colors})" 199: end 200: 201: # build the image dict 202: obj = ref!(:Type => :XObject, 203: :Subtype => :Image, 204: :Height => png.height, 205: :Width => png.width, 206: :BitsPerComponent => png.bits, 207: :Length => png.img_data.size, 208: :Filter => :FlateDecode 209: ) 210: 211: unless png.alpha_channel 212: obj.data[:DecodeParms] = {:Predictor => 15, 213: :Colors => png.colors, 214: :BitsPerComponent => png.bits, 215: :Columns => png.width} 216: end 217: 218: # append the actual image data to the object as a stream 219: obj << png.img_data 220: 221: # sort out the colours of the image 222: if png.palette.empty? 223: obj.data[:ColorSpace] = color 224: else 225: # embed the colour palette in the PDF as a object stream 226: palette_obj = ref!(:Length => png.palette.size) 227: palette_obj << png.palette 228: 229: # build the color space array for the image 230: obj.data[:ColorSpace] = [:Indexed, 231: :DeviceRGB, 232: (png.palette.size / 3) -1, 233: palette_obj] 234: end 235: 236: # ************************************* 237: # add transparency data if necessary 238: # ************************************* 239: 240: # For PNG color types 0, 2 and 3, the transparency data is stored in 241: # a dedicated PNG chunk, and is exposed via the transparency attribute 242: # of the PNG class. 243: if png.transparency[:grayscale] 244: # Use Color Key Masking (spec section 4.8.5) 245: # - An array with N elements, where N is two times the number of color 246: # components. 247: val = png.transparency[:grayscale] 248: obj.data[:Mask] = [val, val] 249: elsif png.transparency[:rgb] 250: # Use Color Key Masking (spec section 4.8.5) 251: # - An array with N elements, where N is two times the number of color 252: # components. 253: rgb = png.transparency[:rgb] 254: obj.data[:Mask] = rgb.collect { |x| [x,x] }.flatten 255: elsif png.transparency[:indexed] 256: # TODO: broken. I was attempting to us Color Key Masking, but I think 257: # we need to construct an SMask i think. Maybe do it inside 258: # the PNG class, and store it in alpha_channel 259: #obj.data[:Mask] = png.transparency[:indexed] 260: end 261: 262: # For PNG color types 4 and 6, the transparency data is stored as a alpha 263: # channel mixed in with the main image data. The PNG class seperates 264: # it out for us and makes it available via the alpha_channel attribute 265: if png.alpha_channel 266: min_version 1.4 267: smask_obj = ref!(:Type => :XObject, 268: :Subtype => :Image, 269: :Height => png.height, 270: :Width => png.width, 271: :BitsPerComponent => png.bits, 272: :Length => png.alpha_channel.size, 273: :Filter => :FlateDecode, 274: :ColorSpace => :DeviceGray, 275: :Decode => [0, 1] 276: ) 277: smask_obj << png.alpha_channel 278: obj.data[:SMask] = smask_obj 279: end 280: 281: return obj 282: end
# File lib/prawn/images.rb, line 284 284: def calc_image_dimensions(info, options) 285: w = options[:width] || info.width 286: h = options[:height] || info.height 287: 288: if options[:width] && !options[:height] 289: wp = w / info.width.to_f 290: w = info.width * wp 291: h = info.height * wp 292: elsif options[:height] && !options[:width] 293: hp = h / info.height.to_f 294: w = info.width * hp 295: h = info.height * hp 296: elsif options[:scale] 297: w = info.width * options[:scale] 298: h = info.height * options[:scale] 299: elsif options[:fit] 300: bw, bh = options[:fit] 301: bp = bw / bh.to_f 302: ip = info.width / info.height.to_f 303: if ip > bp 304: w = bw 305: h = bw / ip 306: else 307: h = bh 308: w = bh * ip 309: end 310: end 311: info.scaled_width = w 312: info.scaled_height = h 313: [w,h] 314: end
# File lib/prawn/images.rb, line 316 316: def detect_image_format(content) 317: top = content[0,128] 318: 319: if top[0, 3] == "\xff\xd8\xff" 320: return :jpg 321: elsif top[0, 8] == "\x89PNG\x0d\x0a\x1a\x0a" 322: return :png 323: else 324: raise Errors::UnsupportedImageType, "image file is an unrecognised format" 325: end 326: end
# File lib/prawn/images.rb, line 114 114: def image_position(w,h,options) 115: options[:position] ||= :left 116: 117: x = case options[:position] 118: when :left 119: bounds.absolute_left 120: when :center 121: bounds.absolute_left + (bounds.width - w) / 2.0 122: when :right 123: bounds.absolute_right - w 124: when Numeric 125: options[:position] + bounds.absolute_left 126: end 127: 128: y = case options[:vposition] 129: when :top 130: bounds.absolute_top 131: when :center 132: bounds.absolute_top - (bounds.height - h) / 2.0 133: when :bottom 134: bounds.absolute_bottom + h 135: when Numeric 136: bounds.absolute_top - options[:vposition] 137: else 138: self.y 139: end 140: return [x,y] 141: end
Disabled; run with --debug to generate this.
Generated with the Darkfish Rdoc Generator 1.1.6.