Class: Prawn::Images::PNG

Inherits:
Image
  • Object
show all
Defined in:
lib/prawn/images/png.rb

Overview

A convenience class that wraps the logic for extracting the parts of a PNG image that we need to embed them in a PDF.

Extension API collapse

Extension API collapse

Constructor Details

#initialize(data) ⇒ PNG

Process a new PNG image

Parameters:

  • data (String)

    A binary string of PNG data.

Source Code
lib/prawn/images/png.rb, line 75
75
def initialize(data)
76
  super()
77
  data = StringIO.new(data.dup)
78
79
  data.read(8) # Skip the default header
80
81
  @palette = +''
82
  @img_data = +''
83
  @transparency = {}
84
85
  loop do
86
    chunk_size = data.read(4).unpack1('N')
87
    section = data.read(4)
88
    case section
89
    when 'IHDR'
90
      # we can grab other interesting values from here (like width,
91
      # height, etc)
92
      values = data.read(chunk_size).unpack('NNCCCCC')
93
94
      @width = values[0]
95
      @height = values[1]
96
      @bits = values[2]
97
      @color_type = values[3]
98
      @compression_method = values[4]
99
      @filter_method = values[5]
100
      @interlace_method = values[6]
101
    when 'PLTE'
102
      @palette << data.read(chunk_size)
103
    when 'IDAT'
104
      @img_data << data.read(chunk_size)
105
    when 'tRNS'
106
      # This chunk can only occur once and it must occur after the
107
      # PLTE chunk and before the IDAT chunk
108
      @transparency = {}
109
      case @color_type
110
      when 3
111
        @transparency[:palette] = data.read(chunk_size).unpack('C*')
112
      when 0
113
        # Greyscale. Corresponding to entries in the PLTE chunk.
114
        # Grey is two bytes, range 0 .. (2 ^ bit-depth) - 1
115
        grayval = data.read(chunk_size).unpack1('n')
116
        @transparency[:grayscale] = grayval
117
      when 2
118
        # True colour with proper alpha channel.
119
        @transparency[:rgb] = data.read(chunk_size).unpack('nnn')
120
      end
121
    when 'IEND'
122
      # we've got everything we need, exit the loop
123
      break
124
    else
125
      # unknown (or un-important) section, skip over it
126
      data.seek(data.pos + chunk_size)
127
    end
128
129
    data.read(4) # Skip the CRC
130
  end
131
132
  @img_data = Zlib::Inflate.inflate(@img_data)
133
end

Instance Attribute Details

#alpha_channelString? (readonly)

Extracted alpha-channel.

Returns:

  • (String, nil)
Source Code
lib/prawn/images/png.rb, line 54
54
def alpha_channel
55
  @alpha_channel
56
end

#bitsInteger (readonly)

Bits per sample or per palette index.

Returns:

  • (Integer)
Source Code
lib/prawn/images/png.rb, line 34
34
def bits
35
  @bits
36
end

#color_typeInteger (readonly)

Color type.

Returns:

  • (Integer)
Source Code
lib/prawn/images/png.rb, line 38
38
def color_type
39
  @color_type
40
end

#compression_methodInteger (readonly)

Compression method.

Returns:

  • (Integer)
Source Code
lib/prawn/images/png.rb, line 42
42
def compression_method
43
  @compression_method
44
end

#filter_methodInteger (readonly)

Filter method.

Returns:

  • (Integer)
Source Code
lib/prawn/images/png.rb, line 46
46
def filter_method
47
  @filter_method
48
end

#heightInteger (readonly)

Image height in pixels.

Returns:

  • (Integer)
Source Code
lib/prawn/images/png.rb, line 30
30
def height
31
  @height
32
end

#img_dataString (readonly)

Image data.

Returns:

  • (String)
Source Code
lib/prawn/images/png.rb, line 18
18
def img_data
19
  @img_data
20
end

#interlace_methodInteger (readonly)

Interlace method.

Returns:

  • (Integer)
Source Code
lib/prawn/images/png.rb, line 50
50
def interlace_method
51
  @interlace_method
52
end

#paletteString (readonly)

Palette data.

Returns:

  • (String)
Source Code
lib/prawn/images/png.rb, line 14
14
def palette
15
  @palette
16
end

#scaled_heightNumber

Scaled height of the image in PDF points.

Returns:

  • (Number)
Source Code
lib/prawn/images/png.rb, line 62
62
def scaled_height
63
  @scaled_height
64
end

#scaled_widthNumber

Scaled width of the image in PDF points.

Returns:

  • (Number)
Source Code
lib/prawn/images/png.rb, line 58
58
def scaled_width
59
  @scaled_width
60
end

#transparencyHash{Symbol => String} (readonly)

Transparency data.

Returns:

  • (Hash{Symbol => String})
Source Code
lib/prawn/images/png.rb, line 22
22
def transparency
23
  @transparency
24
end

#widthInteger (readonly)

Image width in pixels.

Returns:

  • (Integer)
Source Code
lib/prawn/images/png.rb, line 26
26
def width
27
  @width
28
end

Class Method Details

.can_render?(image_blob) ⇒ Boolean

Can this image handler process this image?

Parameters:

  • image_blob (String)

Returns:

  • (Boolean)
Source Code
lib/prawn/images/png.rb, line 68
68
def self.can_render?(image_blob)
69
  image_blob[0, 8].unpack('C*') == [137, 80, 78, 71, 13, 10, 26, 10]
70
end

Instance Method Details

#alpha_channel?Boolean

Is there an alpha-channel in this image?

Returns:

  • (Boolean)
Source Code
lib/prawn/images/png.rb, line 165
165
def alpha_channel?
166
  return true if color_type == 4 || color_type == 6
167
  return @transparency.any? if color_type == 3
168
169
  false
170
end

#build_pdf_object(document) ⇒ PDF::Core::Reference

Build a PDF object representing this image in document, and return a Reference to it.

Parameters:

Returns:

  • (PDF::Core::Reference)
Source Code
lib/prawn/images/png.rb, line 177
177
def build_pdf_object(document)
178
  if compression_method != 0
179
    raise Errors::UnsupportedImageType,
180
      'PNG uses an unsupported compression method'
181
  end
182
183
  if filter_method != 0
184
    raise Errors::UnsupportedImageType,
185
      'PNG uses an unsupported filter method'
186
  end
187
188
  if interlace_method != 0
189
    raise Errors::UnsupportedImageType,
190
      'PNG uses unsupported interlace method'
191
  end
192
193
  # some PNG types store the colour and alpha channel data together,
194
  # which the PDF spec doesn't like, so split it out.
195
  split_alpha_channel!
196
197
  case colors
198
  when 1
199
    color = :DeviceGray
200
  when 3
201
    color = :DeviceRGB
202
  else
203
    raise Errors::UnsupportedImageType,
204
      "PNG uses an unsupported number of colors (#{png.colors})"
205
  end
206
207
  # build the image dict
208
  obj = document.ref!(
209
    Type: :XObject,
210
    Subtype: :Image,
211
    Height: height,
212
    Width: width,
213
    BitsPerComponent: bits,
214
  )
215
216
  # append the actual image data to the object as a stream
217
  obj << img_data
218
219
  obj.stream.filters << {
220
    FlateDecode: {
221
      Predictor: 15,
222
      Colors: colors,
223
      BitsPerComponent: bits,
224
      Columns: width,
225
    },
226
  }
227
228
  # sort out the colours of the image
229
  if palette.empty?
230
    obj.data[:ColorSpace] = color
231
  else
232
    # embed the colour palette in the PDF as a object stream
233
    palette_obj = document.ref!({})
234
    palette_obj << palette
235
236
    # build the color space array for the image
237
    obj.data[:ColorSpace] = [
238
      :Indexed,
239
      :DeviceRGB,
240
      (palette.size / 3) - 1,
241
      palette_obj,
242
    ]
243
  end
244
245
  # *************************************
246
  # add transparency data if necessary
247
  # *************************************
248
249
  # For PNG color types 0, 2 and 3, the transparency data is stored in
250
  # a dedicated PNG chunk, and is exposed via the transparency attribute
251
  # of the PNG class.
252
  if transparency[:grayscale]
253
    # Use Color Key Masking (spec section 4.8.5)
254
    # - An array with N elements, where N is two times the number of color
255
    #   components.
256
    val = transparency[:grayscale]
257
    obj.data[:Mask] = [val, val]
258
  elsif transparency[:rgb]
259
    # Use Color Key Masking (spec section 4.8.5)
260
    # - An array with N elements, where N is two times the number of color
261
    #   components.
262
    rgb = transparency[:rgb]
263
    obj.data[:Mask] = rgb.map { |x| [x, x] }.flatten
264
  end
265
266
  # For PNG color types 4 and 6, the transparency data is stored as
267
  # a alpha channel mixed in with the main image data. The PNG class
268
  # separates it out for us and makes it available via the alpha_channel
269
  # attribute
270
  if alpha_channel?
271
    smask_obj = document.ref!(
272
      Type: :XObject,
273
      Subtype: :Image,
274
      Height: height,
275
      Width: width,
276
      BitsPerComponent: bits,
277
      ColorSpace: :DeviceGray,
278
      Decode: [0, 1],
279
    )
280
    smask_obj.stream << alpha_channel
281
282
    smask_obj.stream.filters << {
283
      FlateDecode: {
284
        Predictor: 15,
285
        Colors: 1,
286
        BitsPerComponent: bits,
287
        Columns: width,
288
      },
289
    }
290
    obj.data[:SMask] = smask_obj
291
  end
292
293
  obj
294
end

#colorsInteger

Number of color components to each pixel.

Returns:

  • (Integer)
Source Code
lib/prawn/images/png.rb, line 138
138
def colors
139
  case color_type
140
  when 0, 3, 4
141
    1
142
  when 2, 6
143
    3
144
  end
145
end

#min_pdf_versionFloat

Returns the minimum PDF version required to support this image.

Returns:

  • (Float)
Source Code
lib/prawn/images/png.rb, line 299
299
def min_pdf_version
300
  if bits > 8
301
    # 16-bit color only supported in 1.5+ (ISO 32000-1:2008 8.9.5.1)
302
    1.5
303
  elsif alpha_channel?
304
    # Need transparency for SMask
305
    1.4
306
  else
307
    1.0
308
  end
309
end