Class: HexaPDF::ImageLoader::PNG
- Inherits:
-
Object
- Object
- HexaPDF::ImageLoader::PNG
- Defined in:
- lib/hexapdf/image_loader/png.rb
Overview
This class is used for loading images in the PNG format from files or IO streams.
It can handle all five types of PNG images: greyscale w/wo alpha, truecolor w/wo alpha and indexed-color. Furthermore, it recognizes the gAMA, cHRM, sRGB and tRNS chunks and handles them appropriately. However, Adam7 interlaced images are not supported!
Note that greyscale, truecolor and indexed-color images with alpha need to be decoded to get the alpha channel which takes time.
All PNG specification section references are in reference to www.w3.org/TR/PNG/.
See: PDF2.0 s7.4.4., s8.9
Constant Summary collapse
- MAGIC_FILE_MARKER =
The magic marker that tells us if the file/IO contains an image in PNG format.
See: PNG s5.2
"\x89PNG\r\n\x1A\n".b
- GREYSCALE =
The color type for PNG greyscale images without alpha, see PNG s11.2.2
0
- TRUECOLOR =
The color type for PNG truecolor images without alpha, see PNG s11.2.2
2
- INDEXED =
The color type for PNG indexed images with/without alpha, see PNG s11.2.2
3
- GREYSCALE_ALPHA =
The color type for PNG greyscale images with alpha, see PNG s11.2.2
4
- TRUECOLOR_ALPHA =
The color type for PNG truecolor images with alpha, see PNG s11.2.2
6
- RENDERING_INTENT_MAP =
Mapping from sRGB chunk rendering intent byte to PDF rendering intent name.
{ 0 => Content::RenderingIntent::PERCEPTUAL, 1 => Content::RenderingIntent::RELATIVE_COLORIMETRIC, 2 => Content::RenderingIntent::SATURATION, 3 => Content::RenderingIntent::ABSOLUTE_COLORIMETRIC, }.freeze
- SRGB_CHRM =
The primary chromaticities and white point used by the sRGB specification.
[0.3127, 0.329, 0.64, 0.33, 0.3, 0.6, 0.15, 0.06].freeze
Class Method Summary collapse
-
.handles?(file_or_io) ⇒ Boolean
:call-seq: PNG.handles?(filename) -> true or false PNG.handles?(io) -> true or false.
-
.load(document, file_or_io) ⇒ Object
:call-seq: PNG.load(document, filename) -> image_obj PNG.load(document, io) -> image_obj.
Instance Method Summary collapse
-
#initialize(document, io) ⇒ PNG
constructor
:nodoc:.
-
#load ⇒ Object
:nodoc:.
Constructor Details
#initialize(document, io) ⇒ PNG
:nodoc:
113 114 115 116 117 118 119 120 121 |
# File 'lib/hexapdf/image_loader/png.rb', line 113 def initialize(document, io) #:nodoc: @document = document @io = io @color_type = nil @intent = nil @chrm = nil @gamma = nil end |
Class Method Details
.handles?(file_or_io) ⇒ Boolean
:call-seq:
PNG.handles?(filename) -> true or false
PNG.handles?(io) -> true or false
Returns true
if the given file or IO stream can be handled, ie. if it contains an image in PNG format.
95 96 97 98 99 100 101 102 |
# File 'lib/hexapdf/image_loader/png.rb', line 95 def self.handles?(file_or_io) if file_or_io.kind_of?(String) File.read(file_or_io, 8, mode: 'rb') == MAGIC_FILE_MARKER else file_or_io.rewind file_or_io.read(8) == MAGIC_FILE_MARKER end end |
.load(document, file_or_io) ⇒ Object
:call-seq:
PNG.load(document, filename) -> image_obj
PNG.load(document, io) -> image_obj
Creates a PDF image object from the PNG file or IO stream.
109 110 111 |
# File 'lib/hexapdf/image_loader/png.rb', line 109 def self.load(document, file_or_io) new(document, file_or_io).load end |
Instance Method Details
#load ⇒ Object
:nodoc:
123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 |
# File 'lib/hexapdf/image_loader/png.rb', line 123 def load #:nodoc: with_io do |io| io.seek(8, IO::SEEK_SET) dict = { Type: :XObject, Subtype: :Image, } while true length, type = io.read(8).unpack('Na4') # PNG s5.3 case type when 'IDAT' # PNG s11.2.4 idat_offset = io.pos - 8 break when 'IHDR' # PNG s11.2.2 values = io.read(length).unpack('NNC5') dict[:Width] = values[0] dict[:Height] = values[1] dict[:BitsPerComponent] = values[2] @color_type = values[3] if values[4] != 0 raise HexaPDF::Error, "Unsupported PNG compression method" elsif values[5] != 0 raise HexaPDF::Error, "Unsupported PNG filter method" elsif values[6] != 0 raise HexaPDF::Error, "Unsupported PNG interlace method" end when 'PLTE' # PNG s11.2.3 if @color_type == INDEXED palette = io.read(length) hival = (palette.size / 3) - 1 if dict[:BitsPerComponent] == 8 palette = @document.add({Filter: :FlateDecode}, stream: palette) end dict[:ColorSpace] = [:Indexed, color_space, hival, palette] else io.seek(length, IO::SEEK_CUR) end when 'tRNS' # PNG s11.3.2 case @color_type when INDEXED trns = io.read(length).unpack('C*') when TRUECOLOR, GREYSCALE dict[:Mask] = io.read(length).unpack('n*').map {|val| [val, val] }.flatten else io.seek(length, IO::SEEK_CUR) end when 'sRGB' # PNG s11.3.3.5 @intent = io.read(length).unpack1('C') dict[:Intent] = RENDERING_INTENT_MAP[@intent] @chrm = SRGB_CHRM @gamma = 2.2 when 'gAMA' # PNG s11.3.3.2 gamma = 100_000.0 / io.read(length).unpack1('N') unless @intent || gamma == 1.0 # sRGB trumps gAMA @gamma = gamma @chrm ||= SRGB_CHRM # don't overwrite data from a cHRM chunk end when 'cHRM' # PNG s11.3.3.1 chrm = io.read(length) @chrm = chrm.unpack('N8').map {|v| v / 100_000.0 } unless @intent # sRGB trumps cHRM else io.seek(length, IO::SEEK_CUR) end io.seek(4, IO::SEEK_CUR) # don't check the CRC end dict[:ColorSpace] ||= color_space decode_parms = { Predictor: 15, Colors: @color_type == TRUECOLOR || @color_type == TRUECOLOR_ALPHA ? 3 : 1, BitsPerComponent: dict[:BitsPerComponent], Columns: dict[:Width], } if @color_type == TRUECOLOR_ALPHA || @color_type == GREYSCALE_ALPHA image_data, mask_data = separate_alpha_channel(idat_offset, decode_parms) add_smask_image(dict, mask_data) stream = HexaPDF::StreamData.new(lambda { image_data }, filter: :FlateDecode, decode_parms: decode_parms) else if @color_type == INDEXED && trns mask_data = alpha_mask_for_indexed_image(idat_offset, decode_parms, trns) add_smask_image(dict, mask_data, from_indexed: true) end stream = HexaPDF::StreamData.new(image_data_proc(idat_offset), filter: :FlateDecode, decode_parms: decode_parms) end obj = @document.add(dict, stream: stream) obj.set_filter(:FlateDecode, decode_parms) obj end end |