Class: Libpng::StandardDecoder
- Inherits:
-
Object
- Object
- Libpng::StandardDecoder
- Defined in:
- lib/libpng/standard_decoder.rb
Overview
Decodes a PNG byte buffer into raw pixels via libpng's standard read API (png_create_read_struct -> png_set_read_fn -> png_read_info -> optional transforms -> png_read_image). This is the read-side counterpart to StandardEncoder; together they expose libpng's full low-level read/write surface, complementing the simplified API.
Why use this instead of Libpng.decode (the simplified path)?
- Explicit control over which transforms apply (the simplified API auto-applies a fixed set).
- Useful when the caller wants to inspect the source pixel format before deciding how to expand it (e.g. skip palette expansion for performance on already-RGBA images).
Options:
pixel_format: The desired output format. One of:
:gray, :ga, :rgb, :rgba (default).
Determines which expansion / stripping
transforms get applied.
bit_depth: 8 (default) or 16. 16-bit output preserves
the source's full range when the source is
also 16-bit; otherwise the source is
promoted (8 -> 16 by zero-extension) or
demoted (16 -> 8 by png_set_strip_16).
Output:
Returns a Libpng::DecodedImage. The :text, :color, :phys
metadata fields are populated via ChunkWalker (same as the
simplified path) so callers get the same metadata story
regardless of which decode path they use.
One instance per decode call. Ractor-safe.
Defined Under Namespace
Classes: ReadState
Constant Summary collapse
- SIGNATURE_BYTES =
PNG file signature: 8 bytes.
[137, 80, 78, 71, 13, 10, 26, 10].freeze
Instance Method Summary collapse
-
#call ⇒ Object
Returns a Libpng::DecodedImage.
-
#initialize(png, pixel_format: 'RGBA', bit_depth: 8) ⇒ StandardDecoder
constructor
A new instance of StandardDecoder.
Constructor Details
#initialize(png, pixel_format: 'RGBA', bit_depth: 8) ⇒ StandardDecoder
Returns a new instance of StandardDecoder.
47 48 49 50 51 52 53 54 55 56 57 |
# File 'lib/libpng/standard_decoder.rb', line 47 def initialize(png, pixel_format: 'RGBA', bit_depth: 8) raise Error, 'input PNG buffer is nil' if png.nil? raise Error, 'input PNG buffer must be a String' unless png.is_a?(String) raise Error, 'input is too short to be a PNG' if png.bytesize < 8 raise Error, 'input is not a PNG (bad signature)' unless png.bytes.first(8) == SIGNATURE_BYTES @png = png @pixel_format_sym = coerce_pixel_format(pixel_format) @target_bit_depth = bit_depth raise Error, 'bit_depth must be 8 or 16' unless [8, 16].include?(@target_bit_depth) end |
Instance Method Details
#call ⇒ Object
Returns a Libpng::DecodedImage.
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 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 |
# File 'lib/libpng/standard_decoder.rb', line 60 def call output = String.new.force_encoding('ASCII-8BIT') state = ReadState.new(bytes: @png, offset: 0) read_fn = make_read_fn(state) error_fn = make_error_fn png_ptr = FFI::Pointer.new(0) info_ptr = FFI::Pointer.new(0) begin png_ptr = Libpng::Binding.png_create_read_struct(LIBPNG_VER_STRING_C, nil, error_fn, nil) raise Error, 'png_create_read_struct returned NULL' if png_ptr.null? info_ptr = Libpng::Binding.png_create_info_struct(png_ptr) raise Error, 'png_create_info_struct returned NULL' if info_ptr.null? # Read callback + flush (NULL); we read from memory, not a file. Libpng::Binding.png_set_read_fn(png_ptr, nil, read_fn, nil) Libpng::Binding.png_read_info(png_ptr, info_ptr) width = Libpng::Binding.png_get_image_width(png_ptr, info_ptr) height = Libpng::Binding.png_get_image_height(png_ptr, info_ptr) src_bit_depth = Libpng::Binding.png_get_bit_depth(png_ptr, info_ptr) src_color_type = Libpng::Binding.png_get_color_type(png_ptr, info_ptr) apply_transforms(png_ptr, info_ptr, src_bit_depth, src_color_type) # png_set_interlace_handling returns the pass count (1 for # non-interlaced, 7 for Adam7). png_read_image handles the # multi-pass accumulation automatically once this is set. Libpng::Binding.png_set_interlace_handling(png_ptr) Libpng::Binding.png_read_update_info(png_ptr, info_ptr) row_bytes = Libpng::Binding.png_get_rowbytes(png_ptr, info_ptr) out_size = row_bytes * height FFI::MemoryPointer.new(:uint8, out_size) do |out_buf| FFI::MemoryPointer.new(:pointer, height) do |rows| height.times do |y| rows.put_pointer(y * FFI.type_size(:pointer), out_buf + (y * row_bytes)) end Libpng::Binding.png_read_image(png_ptr, rows) end output << out_buf.read_bytes(out_size) end Libpng::Binding.png_read_end(png_ptr, info_ptr) = DecodedImage.new( width: width, height: height, format: @pixel_format_sym.upcase.to_s, pixels: output, bit_depth: [:bit_depth], color_type: [:color_type], interlace: [:interlace], text: [:text], color: [:color], phys: [:phys] ) ensure destroy(png_ptr, info_ptr) end end |