Class: Libpng::StandardEncoder

Inherits:
Object
  • Object
show all
Defined in:
lib/libpng/standard_encoder.rb

Overview

Encodes raw pixel data via libpng's standard write API (png_create_write_struct -> png_set_IHDR -> png_set_rows -> png_write_png(PNG_TRANSFORM_IDENTITY)). This matches the byte output of the "classic" libpng write path used by libemf2svg's rgb2png, and avoids the sRGB/gAMA chunks the simplified API injects by default.

Options:

pixel_format:     :gray, :ga, :rgb, :rgba, or :palette
filter:           :default (adaptive), :none, :sub, :up, :avg,
                :paeth, :all, :adaptive
compression_level: 0..9 (default 6 = Z_DEFAULT_COMPRESSION)
interlace:        :none or :adam7 (default :none)
bit_depth:        8 or 16 (default 8; ignored for :palette which
                is always 8)
palette:          Array of [r, g, b] or [r, g, b, a] for
                pixel_format: :palette. 1..256 entries.

Metadata options (forwarded to MetadataWriter; written between IHDR/PLTE and png_write_png):

text:             Hash<String,String> of tEXt keyword -> value.
                Values with non-ASCII bytes use iTXt (UTF-8).
gamma:            Float file gamma (e.g. 0.45455 for sRGB).
srgb_intent:      Integer 0..3 (perceptual, relative-colorimetric,
                saturation, absolute-colorimetric).
chromaticities:   Hash with :white_point_x/y, :red_x/y,
                :green_x/y, :blue_x/y (each a Float 0..1).
icc_profile:      Hash with :name (String) and :data (binary
                String). libpng compresses via zlib internally.
phys:             Hash with :pixels_per_unit_x, :pixels_per_unit_y,
                and :unit (0 = unknown, 1 = meters).

One instance per encode call. Ractor-safe.

Defined Under Namespace

Classes: CallbackSet

Constant Summary collapse

METADATA_KEYS =
%i[text gamma srgb_intent chromaticities icc_profile phys].freeze

Instance Method Summary collapse

Constructor Details

#initialize(width, height, pixels, pixel_format: 'RGBA', filter: :default, compression_level: 6, interlace: :none, bit_depth: 8, palette: nil, **metadata) ⇒ StandardEncoder

Returns a new instance of StandardEncoder.



40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
# File 'lib/libpng/standard_encoder.rb', line 40

def initialize(width, height, pixels,
               pixel_format: 'RGBA',
               filter: :default,
               compression_level: 6,
               interlace: :none,
               bit_depth: 8,
               palette: nil,
               **)
  @width = width
  @height = height
  @pixels = pixels
  @pixel_format_sym = coerce_pixel_format(pixel_format)
  @filter_sym = filter.to_sym
  @compression_level = compression_level
  @interlace_sym = interlace.to_sym
  @bit_depth = bit_depth
  @palette = palette
  @metadata = 
  validate!
end

Instance Method Details

#callObject

Returns a binary String of PNG file bytes.



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
# File 'lib/libpng/standard_encoder.rb', line 62

def call
  output = String.new.force_encoding('ASCII-8BIT')
  callbacks = CallbackSet.new(output)

  png_ptr = FFI::Pointer.new(0)
  info_ptr = FFI::Pointer.new(0)
  begin
    png_ptr = Libpng::Binding.png_create_write_struct(LIBPNG_VER_STRING_C, nil, callbacks.error_fn, nil)
    raise Error, 'png_create_write_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?

    apply_compression(png_ptr)
    Libpng::Binding.png_set_write_fn(png_ptr, nil, callbacks.write_fn, nil)
    Libpng::Binding.png_set_IHDR(png_ptr, info_ptr, @width, @height, effective_bit_depth,
                                 color_type, interlace_method,
                                 COMPRESSION_TYPE_DEFAULT, FILTER_TYPE_DEFAULT)
    apply_filter(png_ptr)
    apply_palette(png_ptr, info_ptr) if palette_format?
    # Metadata writers run AFTER IHDR/PLTE and BEFORE png_write_png
    # (which is what calls png_write_info -> emits all queued chunks).
    MetadataWriter.apply(png_ptr, info_ptr, @metadata)

    FFI::MemoryPointer.new(:uint8, @pixels.bytesize) do |px|
      px.write_bytes(@pixels)
      FFI::MemoryPointer.new(:pointer, @height) do |rows|
        @height.times do |y|
          rows.put_pointer(y * FFI.type_size(:pointer), px + (y * stride))
        end
        Libpng::Binding.png_set_rows(png_ptr, info_ptr, rows)
        Libpng::Binding.png_write_png(png_ptr, info_ptr, TRANSFORM_IDENTITY, nil)
      end
    end

    output
  ensure
    destroy(png_ptr, info_ptr)
  end
end