Module: Libpng
- Extended by:
- FFI::Library
- Defined in:
- lib/libpng.rb,
lib/libpng/recipe.rb,
lib/libpng/version.rb
Overview
Libpng is a Ruby binding for libpng (the official PNG reference library) via FFI. The native library is pre-compiled for each target platform and shipped inside the gem, so no C compiler is required at install time.
The API mirrors libpng's "simplified" high-level API:
Libpng.encode(width, height, rgba_bytes, pixel_format: "RGBA")
Libpng.decode(png_bytes, pixel_format: "RGBA")
All encode/decode state is per-call (libpng allocates and frees a png_image internally), so calls from different Ractors do not share state. The module's FFI function table is frozen at load time.
Defined Under Namespace
Classes: DecodedImage, Error, Recipe
Constant Summary collapse
- FORMAT_FLAG_ALPHA =
PNG_IMAGE_FORMAT_* bit flags and named formats from png.h.
0x01- FORMAT_FLAG_COLOR =
0x02- FORMAT_FLAG_LINEAR =
0x04- FORMAT_FLAG_COLORMAP =
0x08- FORMAT_FLAG_BGR =
0x10- FORMAT_FLAG_AFIRST =
0x20- FORMAT_FLAG_ASSOCIATED_ALPHA =
0x40- FORMAT_GRAY =
0- FORMAT_GA =
FORMAT_FLAG_ALPHA- FORMAT_AG =
FORMAT_FLAG_ALPHA | FORMAT_FLAG_AFIRST
- FORMAT_RGB =
FORMAT_FLAG_COLOR- FORMAT_BGR =
FORMAT_FLAG_COLOR | FORMAT_FLAG_BGR
- FORMAT_RGBA =
FORMAT_FLAG_COLOR | FORMAT_FLAG_ALPHA
- FORMAT_ARGB =
FORMAT_FLAG_COLOR | FORMAT_FLAG_ALPHA | FORMAT_FLAG_AFIRST
- FORMAT_BGRA =
FORMAT_FLAG_COLOR | FORMAT_FLAG_ALPHA | FORMAT_FLAG_BGR
- FORMAT_ABGR =
FORMAT_FLAG_COLOR | FORMAT_FLAG_ALPHA | FORMAT_FLAG_AFIRST | FORMAT_FLAG_BGR
- FORMAT_BY_NAME =
{ 'GRAY' => FORMAT_GRAY, 'GRAYSCALE' => FORMAT_GRAY, 'GA' => FORMAT_GA, 'AG' => FORMAT_AG, 'RGB' => FORMAT_RGB, 'BGR' => FORMAT_BGR, 'RGBA' => FORMAT_RGBA, 'ARGB' => FORMAT_ARGB, 'BGRA' => FORMAT_BGRA, 'ABGR' => FORMAT_ABGR }.freeze
- PNG_IMAGE_VERSION =
png_image::version value libpng checks for. Defined in png.h as PNG_IMAGE_VERSION == 1.
1- PNG_IMAGE_MESSAGE_BYTES =
Buffer size for libpng's per-image error message (PNG_IMAGE_MESSAGE_BYTES). The png_image struct stores a fixed-size char buffer of this length.
64- PNG_IMAGE_SIZE =
png_image struct field offsets (bytes). png_image is laid out as:
void* opaque (pointer-width) png_uint_32 version (uint32) png_uint_32 width (uint32) png_uint_32 height (uint32) png_uint_32 format (uint32) png_uint_32 flags (uint32) png_uint_32 colormap_entries (uint32) png_uint_32 warning_or_error (uint32) char[64] messageTotal = pointer-size + 7*4 + 64. The fixed width makes it safe to allocate via FFI::MemoryPointer directly so the wrapper stays Ractor-safe (FFI::Struct has class-level state that isn't shareable across non-main Ractors).
FFI.type_size(:pointer) + (7 * 4) + PNG_IMAGE_MESSAGE_BYTES
- PNG_IMAGE_OFF_OPAQUE =
0- PNG_IMAGE_OFF_VERSION =
FFI.type_size(:pointer)
- PNG_IMAGE_OFF_WIDTH =
PNG_IMAGE_OFF_VERSION + 4
- PNG_IMAGE_OFF_HEIGHT =
PNG_IMAGE_OFF_WIDTH + 4
- PNG_IMAGE_OFF_FORMAT =
PNG_IMAGE_OFF_HEIGHT + 4
- PNG_IMAGE_OFF_FLAGS =
PNG_IMAGE_OFF_FORMAT + 4
- PNG_IMAGE_OFF_COLORMAP_ENTRIES =
PNG_IMAGE_OFF_FLAGS + 4
- PNG_IMAGE_OFF_WARNING_OR_ERROR =
PNG_IMAGE_OFF_COLORMAP_ENTRIES + 4
- PNG_IMAGE_OFF_MESSAGE =
PNG_IMAGE_OFF_WARNING_OR_ERROR + 4
- LIBPNG_VERSION =
Gem version follows the pattern:
{LIBPNG_VERSION}.{LIBPNG_RUBY_ITERATION}where LIBPNG_VERSION is the upstream libpng release this gem is built against, and LIBPNG_RUBY_ITERATION is a counter for Ruby-side changes (recipe bug fixes, CI changes, docs) that bump without a new libpng release. The iteration resets to 0 each time LIBPNG_VERSION bumps.
'1.6.58'- LIBPNG_RUBY_ITERATION =
0- VERSION =
"#{LIBPNG_VERSION}.#{LIBPNG_RUBY_ITERATION}"
Class Method Summary collapse
-
.decode(png, pixel_format: 'RGBA') ⇒ Object
Decode a PNG file into raw pixels.
-
.encode(width, height, pixels, pixel_format: 'RGBA', convert_to_8bit: false, strip_colorspace: true) ⇒ Object
Encode raw pixel data as a PNG.
Class Method Details
.decode(png, pixel_format: 'RGBA') ⇒ Object
Decode a PNG file into raw pixels.
png String containing PNG file bytes
pixel_format desired output format ("RGB", "RGBA", "GRAY", etc.)
(default: "RGBA")
Returns a DecodedImage Struct with #width, #height, #format (String), and #pixels (binary String).
Ractor-safe: same reason as encode.
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 |
# File 'lib/libpng.rb', line 155 def decode(png, pixel_format: 'RGBA') fmt = FORMAT_BY_NAME[pixel_format.to_s.upcase] || raise(Error, "unknown pixel_format #{pixel_format.inspect}") img = FFI::MemoryPointer.new(:uint8, PNG_IMAGE_SIZE) img.clear img.put_uint32(PNG_IMAGE_OFF_VERSION, PNG_IMAGE_VERSION) begin FFI::MemoryPointer.new(:uint8, png.bytesize) do |in_buf| in_buf.write_bytes(png) ok = png_image_begin_read_from_memory(img, in_buf, png.bytesize) if ok.zero? msg = (img) raise Error, "png_image_begin_read_from_memory failed: #{msg}" end img.put_uint32(PNG_IMAGE_OFF_FORMAT, fmt) width = img.get_uint32(PNG_IMAGE_OFF_WIDTH) height = img.get_uint32(PNG_IMAGE_OFF_HEIGHT) stride = width * bytes_per_pixel_for_format(fmt) out_size = stride * height FFI::MemoryPointer.new(:uint8, out_size) do |out_buf| ok = png_image_finish_read(img, nil, out_buf, stride, nil) if ok.zero? msg = (img) raise Error, "png_image_finish_read failed: #{msg}" end return DecodedImage.new(width: width, height: height, format: pixel_format.to_s.upcase, pixels: out_buf.read_bytes(out_size)) end end ensure png_image_free(img) end end |
.encode(width, height, pixels, pixel_format: 'RGBA', convert_to_8bit: false, strip_colorspace: true) ⇒ Object
Encode raw pixel data as a PNG.
width, height image dimensions in pixels
pixels String of raw pixel bytes (row-major, top-down)
pixel_format "RGB", "RGBA", "GRAY", "GA", "BGR", "BGRA"
(default: "RGBA")
convert_to_8bit when true, libpng converts 16-bit input to 8-bit
on write (default: false)
strip_colorspace when true, removes any sRGB/gAMA/cHRM/iCCP chunks
libpng's simplified API emits by default, leaving
only IHDR, IDAT, and IEND. This matches the
"classic" libpng write path (e.g. libemf2svg) and
produces byte-identical output. Default: true.
Returns a String containing the PNG file bytes. Raises Libpng::Error on failure (bad dimensions, insufficient input bytes, invalid pixel_format, or libpng-internal error).
Ractor-safe: every call allocates and frees its own png_image; no shared mutable state.
137 138 139 140 141 142 143 |
# File 'lib/libpng.rb', line 137 def encode(width, height, pixels, pixel_format: 'RGBA', convert_to_8bit: false, strip_colorspace: true) raw = encode_ancillary(width, height, pixels, pixel_format: pixel_format, convert_to_8bit: convert_to_8bit) strip_colorspace ? strip_ancillary_chunks(raw) : raw end |