Module: Pdfrb::ImageLoader::PNG
- Defined in:
- lib/pdfrb/image_loader/png.rb
Class Method Summary collapse
- .call(document, data, **_opts) ⇒ Object
-
.color_key_mask(info) ⇒ Object
Build a PDF /Mask color-key array from the tRNS chunk.
- .parse_header(data) ⇒ Object
-
.read_trns_chunk(data, color_type) ⇒ Object
Walk the PNG chunks looking for tRNS.
- .read_u16(data, off) ⇒ Object
- .read_u32(data, off) ⇒ Object
Class Method Details
.call(document, data, **_opts) ⇒ Object
8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
# File 'lib/pdfrb/image_loader/png.rb', line 8 def call(document, data, **_opts) data = data.read if data.is_a?(IO) || data.is_a?(StringIO) info = parse_header(data) return nil if info.empty? dict = { Type: :XObject, Subtype: :Image, Width: info[:width], Height: info[:height], ColorSpace: info[:color_space], BitsPerComponent: info[:bits], Filter: :FlateDecode, DecodeParms: { Predictor: 15, Columns: info[:width] }, Length: 0, } mask = color_key_mask(info) dict[:Mask] = mask if mask image = document.add(dict, type: Pdfrb::Model::Type::XObjectImage) image.stream = "" image end |
.color_key_mask(info) ⇒ Object
Build a PDF /Mask color-key array from the tRNS chunk. Each entry is [min, max] per channel. Returns nil if tRNS is absent or for indexed color (which uses /SMask instead).
81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 |
# File 'lib/pdfrb/image_loader/png.rb', line 81 def color_key_mask(info) trns = info[:trns] return nil unless trns return nil if info[:color_type] == 3 # palette uses /SMask case info[:color_type] when 0 # grayscale: 1 two-byte value v = read_u16(trns, 0) [v, v] when 2, 6 # RGB: 3 two-byte values r = read_u16(trns, 0) g = read_u16(trns, 2) b = read_u16(trns, 4) [r, r, g, g, b, b] when 4 # grayscale+alpha: same as grayscale v = read_u16(trns, 0) [v, v] end end |
.parse_header(data) ⇒ Object
32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 |
# File 'lib/pdfrb/image_loader/png.rb', line 32 def parse_header(data) return {} unless data.is_a?(::String) && data.bytesize >= 24 return {} unless data.start_with?("\x89PNG\r\n\x1A\n".b) return {} unless data.bytes[12, 4].pack("C*") == "IHDR" off = 16 color_type = data.getbyte(off + 9) result = { width: read_u32(data, off), height: read_u32(data, off + 4), bits: data.getbyte(off + 8), color_type: color_type, color_space: case color_type when 0, 4 then :DeviceGray when 2, 6 then :DeviceRGB when 3 then :Indexed else :DeviceRGB end, } result[:trns] = read_trns_chunk(data, color_type) result end |
.read_trns_chunk(data, color_type) ⇒ Object
Walk the PNG chunks looking for tRNS. Returns the raw tRNS payload bytes, or nil if absent. Stops at IDAT to avoid scanning the whole file.
58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 |
# File 'lib/pdfrb/image_loader/png.rb', line 58 def read_trns_chunk(data, color_type) offset = 8 # skip PNG signature while offset + 8 <= data.bytesize length = read_u32(data, offset) chunk_type = data.byteslice(offset + 4, 4) offset += 8 case chunk_type when "IHDR" # already parsed; skip offset += length + 4 when "tRNS" return data.byteslice(offset, length) when "IDAT", "IEND" return nil else offset += length + 4 end end nil end |
.read_u16(data, off) ⇒ Object
101 102 103 104 105 |
# File 'lib/pdfrb/image_loader/png.rb', line 101 def read_u16(data, off) return 0 unless data && data.bytesize >= off + 2 (data.getbyte(off) << 8) | data.getbyte(off + 1) end |
.read_u32(data, off) ⇒ Object
107 108 109 110 |
# File 'lib/pdfrb/image_loader/png.rb', line 107 def read_u32(data, off) (data.getbyte(off) << 24) | (data.getbyte(off + 1) << 16) | (data.getbyte(off + 2) << 8) | data.getbyte(off + 3) end |