Class: Pura::Png::Decoder

Inherits:
Object
  • Object
show all
Defined in:
lib/pura/png/decoder.rb

Constant Summary collapse

PNG_SIGNATURE =
[137, 80, 78, 71, 13, 10, 26, 10].pack("C8")
GRAYSCALE =

Color types

0
RGB =
2
INDEXED =
3
GRAYSCALE_ALPHA =
4
RGBA =
6
DEFAULT_MAX_INPUT_BYTES =
64 * 1024 * 1024
DEFAULT_MAX_PIXELS =
40_000_000
DEFAULT_MAX_DECODED_BYTES =
256 * 1024 * 1024
BIT_DEPTHS =
{ 0 => [1, 2, 4, 8, 16], 2 => [8, 16], 3 => [1, 2, 4, 8],
4 => [8, 16], 6 => [8, 16] }.freeze

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(data, max_pixels: DEFAULT_MAX_PIXELS, max_decoded_bytes: DEFAULT_MAX_DECODED_BYTES) ⇒ Decoder

Returns a new instance of Decoder.



41
42
43
44
45
46
47
48
49
50
# File 'lib/pura/png/decoder.rb', line 41

def initialize(data, max_pixels: DEFAULT_MAX_PIXELS, max_decoded_bytes: DEFAULT_MAX_DECODED_BYTES)
  unless [max_pixels, max_decoded_bytes].all? { |value| value.is_a?(Integer) && value.positive? }
    raise ArgumentError, "decode limits must be positive integers"
  end

  @max_pixels = max_pixels
  @max_decoded_bytes = max_decoded_bytes
  @data = data
  @pos = 0
end

Class Method Details

.decode(input, max_input_bytes: DEFAULT_MAX_INPUT_BYTES, **options) ⇒ Object

Raises:



26
27
28
29
30
31
32
33
34
35
36
37
38
39
# File 'lib/pura/png/decoder.rb', line 26

def self.decode(input, max_input_bytes: DEFAULT_MAX_INPUT_BYTES, **options)
  unless max_input_bytes.is_a?(Integer) && max_input_bytes.positive?
    raise ArgumentError, "max_input_bytes must be a positive integer"
  end

  data = if input.is_a?(String) && !input.include?("\x00") && input.bytesize < 4096 && File.exist?(input)
           File.binread(input, max_input_bytes + 1)
         else
           input.b
         end
  raise LimitExceeded, "PNG input limit exceeded" if data.bytesize > max_input_bytes

  new(data, **options).decode
end

Instance Method Details

#decodeObject

Raises:



52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
# File 'lib/pura/png/decoder.rb', line 52

def decode
  read_signature

  ihdr = nil
  palette = nil
  transparency = nil
  idat_chunks = []

  loop do
    length, type = read_chunk_header
    chunk_data = read_bytes(length)
    crc = read_uint32
    raise DecodeError, "Invalid PNG CRC for #{type}" unless crc == Zlib.crc32(type + chunk_data)
    raise DecodeError, "IHDR must be the first chunk" if !ihdr && type != "IHDR"

    case type
    when "IHDR"
      raise DecodeError, "Duplicate IHDR chunk" if ihdr

      ihdr = parse_ihdr(chunk_data)
    when "PLTE"
      palette = parse_plte(chunk_data)
    when "tRNS"
      transparency = chunk_data
    when "IDAT"
      idat_chunks << chunk_data
    when "IEND"
      raise DecodeError, "IEND must be empty" unless chunk_data.empty?

      break
    else
      raise DecodeError, "Unknown critical PNG chunk: #{type}" if type.getbyte(0).allbits?(0x20) == false
    end
  end

  raise DecodeError, "Missing IHDR chunk" unless ihdr
  raise DecodeError, "Missing IDAT chunk" if idat_chunks.empty?

  compressed = idat_chunks.join
  raw = inflate_scanlines(compressed, ihdr)

  pixels = reconstruct(raw, ihdr, palette, transparency)
  Image.new(ihdr[:width], ihdr[:height], pixels)
end