mirror of
https://github.com/oven-sh/bun
synced 2026-02-10 02:48:50 +00:00
ok
This commit is contained in:
@@ -34,11 +34,12 @@ Run `zig test src/image/pixel_format.zig` to test the pixel format conversion.
|
||||
- [x] src/image/encoder.zig: Platform-agnostic encoder interface
|
||||
- [x] src/image/encoder_darwin.zig: macOS encoder using CoreGraphics and ImageIO
|
||||
- [ ] src/image/encoder_windows.zig: Windows encoder using WIC
|
||||
- [ ] src/image/encoder_linux.zig: Linux encoder using libpng, libjpeg, etc.
|
||||
- [x] src/image/encoder_linux.zig: Linux encoder using dynamically-loaded libpng, libjpeg, libwebp
|
||||
- [x] Direct transcoding between formats (PNG ↔ JPEG, etc.) without pixel decoding
|
||||
- [ ] src/image/decoder.zig: Platform-agnostic decoder interface
|
||||
|
||||
Run `zig test src/image/streaming_tests.zig` to test the streaming and encoder functionality.
|
||||
Run `zig test src/image/encoder_tests.zig` to test the encoding and transcoding functionality.
|
||||
|
||||
Use Zig's @Vector intrinsics for SIMD. Here's a couple examples:
|
||||
|
||||
|
||||
@@ -2,8 +2,263 @@ const std = @import("std");
|
||||
const pixel_format = @import("pixel_format.zig");
|
||||
const PixelFormat = pixel_format.PixelFormat;
|
||||
const EncodingOptions = @import("encoder.zig").EncodingOptions;
|
||||
const ImageFormat = @import("encoder.zig").ImageFormat;
|
||||
const libjpeg = @import("libjpeg.zig");
|
||||
const libpng = @import("libpng.zig");
|
||||
const libwebp = @import("libwebp.zig");
|
||||
|
||||
/// Linux implementation using appropriate libraries
|
||||
// Custom write struct for PNG memory writing
|
||||
const PngWriteState = struct {
|
||||
data: std.ArrayList(u8),
|
||||
|
||||
pub fn write(png_ptr: libpng.png_structp, data_ptr: libpng.png_const_bytep, length: usize) callconv(.C) void {
|
||||
const write_state = @as(?*PngWriteState, @ptrCast(libpng.png_get_io_ptr(png_ptr))) orelse return;
|
||||
write_state.data.appendSlice(data_ptr[0..length]) catch return;
|
||||
}
|
||||
|
||||
pub fn flush(png_ptr: libpng.png_structp) callconv(.C) void {
|
||||
_ = png_ptr;
|
||||
// No flushing needed for memory output
|
||||
}
|
||||
};
|
||||
|
||||
// Encode to PNG
|
||||
fn encodePNG(
|
||||
allocator: std.mem.Allocator,
|
||||
source: []const u8,
|
||||
width: usize,
|
||||
height: usize,
|
||||
pixel_fmt: PixelFormat,
|
||||
options: EncodingOptions,
|
||||
) ![]u8 {
|
||||
_ = options; // PNG doesn't use quality settings
|
||||
|
||||
// Initialize libpng
|
||||
try libpng.init();
|
||||
|
||||
// Create write structure
|
||||
const png_ptr = libpng.png_create_write_struct("1.6.37", null, null, null);
|
||||
if (png_ptr == null) {
|
||||
return error.PngCreateWriteStructFailed;
|
||||
}
|
||||
|
||||
// Create info structure
|
||||
const info_ptr = libpng.png_create_info_struct(png_ptr);
|
||||
if (info_ptr == null) {
|
||||
libpng.png_destroy_write_struct(&png_ptr, null);
|
||||
return error.PngCreateInfoStructFailed;
|
||||
}
|
||||
|
||||
// Initialize output
|
||||
var write_state = PngWriteState{
|
||||
.data = std.ArrayList(u8).init(allocator),
|
||||
};
|
||||
defer write_state.data.deinit();
|
||||
|
||||
// Set up custom write function
|
||||
libpng.png_set_write_fn(png_ptr, &write_state, PngWriteState.write, PngWriteState.flush);
|
||||
|
||||
// Set image info
|
||||
const bit_depth: i32 = 8;
|
||||
const color_type: i32 = switch (pixel_fmt) {
|
||||
.Gray => libpng.PNG_COLOR_TYPE_GRAY,
|
||||
.RGB => libpng.PNG_COLOR_TYPE_RGB,
|
||||
.RGBA => libpng.PNG_COLOR_TYPE_RGBA,
|
||||
else => {
|
||||
libpng.png_destroy_write_struct(&png_ptr, &info_ptr);
|
||||
return error.UnsupportedPixelFormat;
|
||||
},
|
||||
};
|
||||
|
||||
libpng.png_set_IHDR(
|
||||
png_ptr,
|
||||
info_ptr,
|
||||
@as(u32, @intCast(width)),
|
||||
@as(u32, @intCast(height)),
|
||||
bit_depth,
|
||||
color_type,
|
||||
libpng.PNG_INTERLACE_NONE,
|
||||
libpng.PNG_COMPRESSION_TYPE_DEFAULT,
|
||||
libpng.PNG_FILTER_TYPE_DEFAULT
|
||||
);
|
||||
|
||||
libpng.png_write_info(png_ptr, info_ptr);
|
||||
|
||||
// Create row pointers
|
||||
const bytes_per_pixel = pixel_fmt.getBytesPerPixel();
|
||||
const bytes_per_row = width * bytes_per_pixel;
|
||||
|
||||
var row_pointers = try allocator.alloc([*]u8, height);
|
||||
defer allocator.free(row_pointers);
|
||||
|
||||
for (0..height) |y| {
|
||||
row_pointers[y] = @as([*]u8, @ptrCast(@constCast(&source[y * bytes_per_row])));
|
||||
}
|
||||
|
||||
// Write image data
|
||||
libpng.png_write_image(png_ptr, row_pointers.ptr);
|
||||
|
||||
// Finish writing
|
||||
libpng.png_write_end(png_ptr, null);
|
||||
|
||||
// Clean up
|
||||
libpng.png_destroy_write_struct(&png_ptr, &info_ptr);
|
||||
|
||||
// Return the encoded data
|
||||
return try write_state.data.toOwnedSlice();
|
||||
}
|
||||
|
||||
// Encode to JPEG
|
||||
fn encodeJPEG(
|
||||
allocator: std.mem.Allocator,
|
||||
source: []const u8,
|
||||
width: usize,
|
||||
height: usize,
|
||||
pixel_fmt: PixelFormat,
|
||||
options: EncodingOptions,
|
||||
) ![]u8 {
|
||||
// Initialize libjpeg
|
||||
try libjpeg.init();
|
||||
|
||||
// Initialize the JPEG compression structure and error manager
|
||||
var cinfo: libjpeg.jpeg_compress_struct = undefined;
|
||||
var jerr: libjpeg.jpeg_error_mgr = undefined;
|
||||
|
||||
cinfo.err = libjpeg.jpeg_std_error(&jerr);
|
||||
libjpeg.jpeg_CreateCompress(&cinfo);
|
||||
|
||||
// Set up memory destination
|
||||
var jpeg_buffer: [*]u8 = null;
|
||||
var jpeg_buffer_size: c_ulong = 0;
|
||||
libjpeg.jpeg_mem_dest.?(&cinfo, &jpeg_buffer, &jpeg_buffer_size);
|
||||
|
||||
// Configure compression parameters
|
||||
cinfo.image_width = @as(c_uint, @intCast(width));
|
||||
cinfo.image_height = @as(c_uint, @intCast(height));
|
||||
|
||||
// Set colorspace based on pixel format
|
||||
switch (pixel_fmt) {
|
||||
.Gray => {
|
||||
cinfo.input_components = 1;
|
||||
cinfo.in_color_space = libjpeg.JCS_GRAYSCALE;
|
||||
},
|
||||
.RGB, .BGR => {
|
||||
cinfo.input_components = 3;
|
||||
cinfo.in_color_space = libjpeg.JCS_RGB;
|
||||
},
|
||||
.RGBA, .BGRA => {
|
||||
// JPEG doesn't support alpha, we'll need to convert or strip it
|
||||
// For now, just try to encode it and let libjpeg handle it
|
||||
cinfo.input_components = 4;
|
||||
cinfo.in_color_space = libjpeg.JCS_RGB; // Most libjpeg implementations will just use the RGB part
|
||||
},
|
||||
else => {
|
||||
libjpeg.jpeg_destroy_compress(&cinfo);
|
||||
return error.UnsupportedPixelFormat;
|
||||
},
|
||||
}
|
||||
|
||||
// Set defaults and quality
|
||||
libjpeg.jpeg_set_defaults(&cinfo);
|
||||
libjpeg.jpeg_set_quality(&cinfo, @as(c_int, @intCast(options.quality.quality)), true);
|
||||
|
||||
// Start compression
|
||||
libjpeg.jpeg_start_compress(&cinfo, true);
|
||||
|
||||
// Write scanlines
|
||||
const bytes_per_pixel = pixel_fmt.getBytesPerPixel();
|
||||
const row_stride = width * bytes_per_pixel;
|
||||
|
||||
var row_pointer: [1][*]u8 = undefined;
|
||||
while (cinfo.next_scanline < cinfo.image_height) {
|
||||
const row_offset = cinfo.next_scanline * row_stride;
|
||||
row_pointer[0] = @as([*]u8, @ptrCast(@constCast(&source[row_offset])));
|
||||
_ = libjpeg.jpeg_write_scanlines(&cinfo, &row_pointer[0], 1);
|
||||
}
|
||||
|
||||
// Finish compression
|
||||
libjpeg.jpeg_finish_compress(&cinfo);
|
||||
|
||||
// Copy the JPEG data to our own buffer
|
||||
var result = try allocator.alloc(u8, jpeg_buffer_size);
|
||||
@memcpy(result, jpeg_buffer[0..jpeg_buffer_size]);
|
||||
|
||||
// Clean up
|
||||
libjpeg.jpeg_destroy_compress(&cinfo);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// Encode to WebP
|
||||
fn encodeWebP(
|
||||
allocator: std.mem.Allocator,
|
||||
source: []const u8,
|
||||
width: usize,
|
||||
height: usize,
|
||||
pixel_fmt: PixelFormat,
|
||||
options: EncodingOptions,
|
||||
) ![]u8 {
|
||||
// Initialize libwebp
|
||||
try libwebp.init();
|
||||
|
||||
// Check if we need to convert to BGRA
|
||||
var converted_data: ?[]u8 = null;
|
||||
defer if (converted_data) |data| allocator.free(data);
|
||||
|
||||
var actual_source = source;
|
||||
var actual_format = pixel_fmt;
|
||||
|
||||
if (pixel_fmt != .BGRA) {
|
||||
// Need to convert to BGRA
|
||||
converted_data = try pixel_format.convert(allocator, source, width, height, pixel_fmt, .BGRA);
|
||||
actual_source = converted_data.?;
|
||||
actual_format = .BGRA;
|
||||
}
|
||||
|
||||
const stride = width * actual_format.getBytesPerPixel();
|
||||
var output: [*]u8 = undefined;
|
||||
var output_size: usize = 0;
|
||||
|
||||
// Check if lossless is requested (quality 100)
|
||||
if (options.quality.quality >= 100) {
|
||||
// Use lossless encoding
|
||||
output_size = libwebp.WebPEncodeLosslessBGRA(
|
||||
actual_source.ptr,
|
||||
@as(c_int, @intCast(width)),
|
||||
@as(c_int, @intCast(height)),
|
||||
@as(c_int, @intCast(stride)),
|
||||
&output
|
||||
);
|
||||
} else {
|
||||
// Use lossy encoding with specified quality
|
||||
const quality = @as(f32, @floatFromInt(options.quality.quality)) * 0.01;
|
||||
output_size = libwebp.WebPEncodeBGRA(
|
||||
actual_source.ptr,
|
||||
@as(c_int, @intCast(width)),
|
||||
@as(c_int, @intCast(height)),
|
||||
@as(c_int, @intCast(stride)),
|
||||
quality,
|
||||
&output
|
||||
);
|
||||
}
|
||||
|
||||
if (output_size == 0) {
|
||||
return error.WebPEncodingFailed;
|
||||
}
|
||||
|
||||
// Copy to our own buffer
|
||||
var result = try allocator.alloc(u8, output_size);
|
||||
@memcpy(result, output[0..output_size]);
|
||||
|
||||
// Free WebP's output buffer
|
||||
if (libwebp.WebPFree) |free_fn| {
|
||||
free_fn(output);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// Linux implementation using dynamically loaded libraries
|
||||
pub fn encode(
|
||||
allocator: std.mem.Allocator,
|
||||
source: []const u8,
|
||||
@@ -12,11 +267,32 @@ pub fn encode(
|
||||
format: PixelFormat,
|
||||
options: EncodingOptions,
|
||||
) ![]u8 {
|
||||
_ = allocator;
|
||||
_ = source;
|
||||
_ = width;
|
||||
_ = height;
|
||||
_ = format;
|
||||
return switch (options.format) {
|
||||
.PNG => try encodePNG(allocator, source, width, height, format, options),
|
||||
.JPEG => try encodeJPEG(allocator, source, width, height, format, options),
|
||||
.WEBP => try encodeWebP(allocator, source, width, height, format, options),
|
||||
.AVIF => error.NotImplemented, // AVIF not yet implemented
|
||||
};
|
||||
}
|
||||
|
||||
/// Transcode directly between image formats
|
||||
/// For Linux, this is not directly implemented yet - we need to implement a
|
||||
/// decode function first to complete this functionality
|
||||
pub fn transcode(
|
||||
allocator: std.mem.Allocator,
|
||||
source_data: []const u8,
|
||||
source_format: ImageFormat,
|
||||
target_format: ImageFormat,
|
||||
options: EncodingOptions,
|
||||
) ![]u8 {
|
||||
// For Linux, we currently need to decode and re-encode
|
||||
// since we don't have direct transcoding capabilities.
|
||||
// This is a placeholder that will be improved in the future.
|
||||
_ = source_format;
|
||||
_ = target_format;
|
||||
_ = options;
|
||||
_ = source_data;
|
||||
_ = allocator;
|
||||
|
||||
return error.NotImplemented;
|
||||
}
|
||||
213
src/image/libjpeg.zig
Normal file
213
src/image/libjpeg.zig
Normal file
@@ -0,0 +1,213 @@
|
||||
const std = @import("std");
|
||||
|
||||
/// Library name and path
|
||||
pub const library_name = "libjpeg.so";
|
||||
|
||||
/// JPEG types and callbacks
|
||||
pub const jpeg_compress_struct = extern struct {
|
||||
err: ?*jpeg_error_mgr,
|
||||
mem: ?*anyopaque,
|
||||
progress: ?*anyopaque,
|
||||
client_data: ?*anyopaque,
|
||||
is_decompressor: bool,
|
||||
// Note: we access the remaining fields through function calls
|
||||
// instead of directly defining them all here
|
||||
// Fields like next_scanline, image_width, etc. are accessed via pointers
|
||||
next_scanline: c_uint = 0, // Allow simple access to this common field
|
||||
image_width: c_uint = 0, // Allow simple access to this common field
|
||||
image_height: c_uint = 0, // Allow simple access to this common field
|
||||
input_components: c_int = 0, // Allow simple access to this common field
|
||||
in_color_space: c_int = 0, // Allow simple access to this common field
|
||||
};
|
||||
|
||||
pub const jpeg_error_mgr = extern struct {
|
||||
error_exit: ?*const fn(?*jpeg_error_mgr) callconv(.C) void,
|
||||
emit_message: ?*const fn(?*jpeg_error_mgr, c_int) callconv(.C) void,
|
||||
output_message: ?*const fn(?*jpeg_error_mgr) callconv(.C) void,
|
||||
format_message: ?*const fn(?*jpeg_error_mgr, [*]u8) callconv(.C) void,
|
||||
reset_error_mgr: ?*const fn(?*jpeg_error_mgr) callconv(.C) void,
|
||||
msg_code: c_int,
|
||||
msg_parm: extern union {
|
||||
i: [8]c_int,
|
||||
s: [80]u8,
|
||||
},
|
||||
trace_level: c_int,
|
||||
num_warnings: c_long,
|
||||
jpeg_message_table: [*][*]u8,
|
||||
last_jpeg_message: c_int,
|
||||
addon_message_table: [*][*]u8,
|
||||
first_addon_message: c_int,
|
||||
last_addon_message: c_int,
|
||||
};
|
||||
|
||||
// JPEG constants
|
||||
pub const JCS_UNKNOWN = 0;
|
||||
pub const JCS_GRAYSCALE = 1;
|
||||
pub const JCS_RGB = 2;
|
||||
pub const JCS_YCbCr = 3;
|
||||
pub const JCS_CMYK = 4;
|
||||
pub const JCS_YCCK = 5;
|
||||
pub const JCS_EXT_RGB = 6;
|
||||
pub const JCS_EXT_RGBX = 7;
|
||||
pub const JCS_EXT_BGR = 8;
|
||||
pub const JCS_EXT_BGRX = 9;
|
||||
pub const JCS_EXT_XBGR = 10;
|
||||
pub const JCS_EXT_XRGB = 11;
|
||||
pub const JCS_EXT_RGBA = 12;
|
||||
pub const JCS_EXT_BGRA = 13;
|
||||
pub const JCS_EXT_ABGR = 14;
|
||||
pub const JCS_EXT_ARGB = 15;
|
||||
pub const JCS_RGB565 = 16;
|
||||
|
||||
/// JPEG function pointer types
|
||||
pub const JpegStdErrorFn = fn ([*]jpeg_error_mgr) callconv(.C) [*]jpeg_error_mgr;
|
||||
pub const JpegCreateCompressFn = fn ([*]jpeg_compress_struct) callconv(.C) void;
|
||||
pub const JpegStdioDestFn = fn ([*]jpeg_compress_struct, ?*anyopaque) callconv(.C) void;
|
||||
pub const JpegMemDestFn = fn ([*]jpeg_compress_struct, [*][*]u8, [*]c_ulong) callconv(.C) void;
|
||||
pub const JpegSetDefaultsFn = fn ([*]jpeg_compress_struct) callconv(.C) void;
|
||||
pub const JpegSetQualityFn = fn ([*]jpeg_compress_struct, c_int, bool) callconv(.C) void;
|
||||
pub const JpegStartCompressFn = fn ([*]jpeg_compress_struct, bool) callconv(.C) void;
|
||||
pub const JpegWriteScanlinesFn = fn ([*]jpeg_compress_struct, [*][*]u8, c_uint) callconv(.C) c_uint;
|
||||
pub const JpegFinishCompressFn = fn ([*]jpeg_compress_struct) callconv(.C) void;
|
||||
pub const JpegDestroyCompressFn = fn ([*]jpeg_compress_struct) callconv(.C) void;
|
||||
|
||||
/// Function pointers - will be initialized by init()
|
||||
pub var jpeg_std_error: JpegStdErrorFn = undefined;
|
||||
pub var jpeg_CreateCompress: JpegCreateCompressFn = undefined;
|
||||
pub var jpeg_stdio_dest: JpegStdioDestFn = undefined;
|
||||
pub var jpeg_mem_dest: ?JpegMemDestFn = null; // Optional, not all implementations have this
|
||||
pub var jpeg_set_defaults: JpegSetDefaultsFn = undefined;
|
||||
pub var jpeg_set_quality: JpegSetQualityFn = undefined;
|
||||
pub var jpeg_start_compress: JpegStartCompressFn = undefined;
|
||||
pub var jpeg_write_scanlines: JpegWriteScanlinesFn = undefined;
|
||||
pub var jpeg_finish_compress: JpegFinishCompressFn = undefined;
|
||||
pub var jpeg_destroy_compress: JpegDestroyCompressFn = undefined;
|
||||
|
||||
/// Library handle
|
||||
var lib_handle: ?*anyopaque = null;
|
||||
var init_guard = std.once(initialize);
|
||||
var is_initialized = false;
|
||||
|
||||
/// Initialize the library - called once via std.once
|
||||
fn initialize() void {
|
||||
lib_handle = std.c.dlopen(library_name, std.c.RTLD_NOW);
|
||||
if (lib_handle == null) {
|
||||
// Library not available, leave is_initialized as false
|
||||
return;
|
||||
}
|
||||
|
||||
// Load all required function pointers
|
||||
if (loadSymbol(JpegStdErrorFn, "jpeg_std_error")) |fn_ptr| {
|
||||
jpeg_std_error = fn_ptr;
|
||||
} else {
|
||||
closeLib();
|
||||
return;
|
||||
}
|
||||
|
||||
if (loadSymbol(JpegCreateCompressFn, "jpeg_CreateCompress")) |fn_ptr| {
|
||||
jpeg_CreateCompress = fn_ptr;
|
||||
} else {
|
||||
closeLib();
|
||||
return;
|
||||
}
|
||||
|
||||
if (loadSymbol(JpegStdioDestFn, "jpeg_stdio_dest")) |fn_ptr| {
|
||||
jpeg_stdio_dest = fn_ptr;
|
||||
} else {
|
||||
closeLib();
|
||||
return;
|
||||
}
|
||||
|
||||
// mem_dest is optional, so we don't fail if it's missing
|
||||
jpeg_mem_dest = loadSymbol(JpegMemDestFn, "jpeg_mem_dest");
|
||||
|
||||
if (loadSymbol(JpegSetDefaultsFn, "jpeg_set_defaults")) |fn_ptr| {
|
||||
jpeg_set_defaults = fn_ptr;
|
||||
} else {
|
||||
closeLib();
|
||||
return;
|
||||
}
|
||||
|
||||
if (loadSymbol(JpegSetQualityFn, "jpeg_set_quality")) |fn_ptr| {
|
||||
jpeg_set_quality = fn_ptr;
|
||||
} else {
|
||||
closeLib();
|
||||
return;
|
||||
}
|
||||
|
||||
if (loadSymbol(JpegStartCompressFn, "jpeg_start_compress")) |fn_ptr| {
|
||||
jpeg_start_compress = fn_ptr;
|
||||
} else {
|
||||
closeLib();
|
||||
return;
|
||||
}
|
||||
|
||||
if (loadSymbol(JpegWriteScanlinesFn, "jpeg_write_scanlines")) |fn_ptr| {
|
||||
jpeg_write_scanlines = fn_ptr;
|
||||
} else {
|
||||
closeLib();
|
||||
return;
|
||||
}
|
||||
|
||||
if (loadSymbol(JpegFinishCompressFn, "jpeg_finish_compress")) |fn_ptr| {
|
||||
jpeg_finish_compress = fn_ptr;
|
||||
} else {
|
||||
closeLib();
|
||||
return;
|
||||
}
|
||||
|
||||
if (loadSymbol(JpegDestroyCompressFn, "jpeg_destroy_compress")) |fn_ptr| {
|
||||
jpeg_destroy_compress = fn_ptr;
|
||||
} else {
|
||||
closeLib();
|
||||
return;
|
||||
}
|
||||
|
||||
// All required functions loaded successfully
|
||||
is_initialized = true;
|
||||
}
|
||||
|
||||
/// Helper to load a symbol from the library
|
||||
fn loadSymbol(comptime T: type, name: [:0]const u8) ?T {
|
||||
if (lib_handle) |handle| {
|
||||
const symbol = std.c.dlsym(handle, name.ptr);
|
||||
if (symbol == null) return null;
|
||||
return @as(T, @ptrCast(symbol));
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/// Close the library handle
|
||||
fn closeLib() void {
|
||||
if (lib_handle) |handle| {
|
||||
_ = std.c.dlclose(handle);
|
||||
lib_handle = null;
|
||||
}
|
||||
is_initialized = false;
|
||||
}
|
||||
|
||||
/// Initialize the library if not already initialized
|
||||
pub fn init() !void {
|
||||
// Call once-guard to ensure initialization happens only once
|
||||
init_guard.call();
|
||||
|
||||
// Check if initialization was successful
|
||||
if (!is_initialized) {
|
||||
return error.LibraryNotFound;
|
||||
}
|
||||
|
||||
// Check for required mem_dest function
|
||||
if (jpeg_mem_dest == null) {
|
||||
return error.JpegMemoryDestinationNotSupported;
|
||||
}
|
||||
}
|
||||
|
||||
/// Check if the library is initialized
|
||||
pub fn isInitialized() bool {
|
||||
return is_initialized;
|
||||
}
|
||||
|
||||
/// Deinitialize and free resources
|
||||
pub fn deinit() void {
|
||||
closeLib();
|
||||
}
|
||||
190
src/image/libpng.zig
Normal file
190
src/image/libpng.zig
Normal file
@@ -0,0 +1,190 @@
|
||||
const std = @import("std");
|
||||
|
||||
/// Library name and path
|
||||
pub const library_name = "libpng.so";
|
||||
|
||||
/// PNG types and callbacks
|
||||
pub const png_structp = ?*anyopaque;
|
||||
pub const png_infop = ?*anyopaque;
|
||||
pub const png_const_bytep = [*]const u8;
|
||||
pub const png_bytep = [*]u8;
|
||||
pub const png_bytepp = [*][*]u8;
|
||||
|
||||
// PNG constants
|
||||
pub const PNG_COLOR_TYPE_GRAY = 0;
|
||||
pub const PNG_COLOR_TYPE_PALETTE = 3;
|
||||
pub const PNG_COLOR_TYPE_RGB = 2;
|
||||
pub const PNG_COLOR_TYPE_RGB_ALPHA = 6;
|
||||
pub const PNG_COLOR_TYPE_GRAY_ALPHA = 4;
|
||||
pub const PNG_COLOR_TYPE_RGBA = PNG_COLOR_TYPE_RGB_ALPHA;
|
||||
pub const PNG_COLOR_TYPE_GA = PNG_COLOR_TYPE_GRAY_ALPHA;
|
||||
|
||||
pub const PNG_INTERLACE_NONE = 0;
|
||||
pub const PNG_COMPRESSION_TYPE_DEFAULT = 0;
|
||||
pub const PNG_FILTER_TYPE_DEFAULT = 0;
|
||||
|
||||
pub const PNG_TRANSFORM_IDENTITY = 0;
|
||||
pub const PNG_TRANSFORM_STRIP_16 = 1;
|
||||
pub const PNG_TRANSFORM_STRIP_ALPHA = 2;
|
||||
pub const PNG_TRANSFORM_PACKING = 4;
|
||||
pub const PNG_TRANSFORM_PACKSWAP = 8;
|
||||
pub const PNG_TRANSFORM_EXPAND = 16;
|
||||
pub const PNG_TRANSFORM_INVERT_MONO = 32;
|
||||
pub const PNG_TRANSFORM_SHIFT = 64;
|
||||
pub const PNG_TRANSFORM_BGR = 128;
|
||||
pub const PNG_TRANSFORM_SWAP_ALPHA = 256;
|
||||
pub const PNG_TRANSFORM_SWAP_ENDIAN = 512;
|
||||
pub const PNG_TRANSFORM_INVERT_ALPHA = 1024;
|
||||
pub const PNG_TRANSFORM_STRIP_FILLER = 2048;
|
||||
|
||||
// Function pointer types for PNG
|
||||
pub const PngCreateWriteStructFn = fn ([*:0]const u8, ?*anyopaque, ?*anyopaque, ?*anyopaque) callconv(.C) png_structp;
|
||||
pub const PngCreateInfoStructFn = fn (png_structp) callconv(.C) png_infop;
|
||||
pub const PngSetWriteFnFn = fn (png_structp, ?*anyopaque, ?*const fn (png_structp, png_bytep, usize) callconv(.C) void, ?*const fn (png_structp) callconv(.C) void) callconv(.C) void;
|
||||
pub const PngInitIoFn = fn (png_structp, ?*anyopaque) callconv(.C) void;
|
||||
pub const PngSetIHDRFn = fn (png_structp, png_infop, u32, u32, i32, i32, i32, i32, i32) callconv(.C) void;
|
||||
pub const PngWriteInfoFn = fn (png_structp, png_infop) callconv(.C) void;
|
||||
pub const PngWriteImageFn = fn (png_structp, png_bytepp) callconv(.C) void;
|
||||
pub const PngWriteEndFn = fn (png_structp, png_infop) callconv(.C) void;
|
||||
pub const PngDestroyWriteStructFn = fn ([*]png_structp, [*]png_infop) callconv(.C) void;
|
||||
pub const PngGetIoPtr = fn (png_structp) callconv(.C) ?*anyopaque;
|
||||
|
||||
/// Function pointers - will be initialized by init()
|
||||
pub var png_create_write_struct: PngCreateWriteStructFn = undefined;
|
||||
pub var png_create_info_struct: PngCreateInfoStructFn = undefined;
|
||||
pub var png_set_write_fn: PngSetWriteFnFn = undefined;
|
||||
pub var png_init_io: PngInitIoFn = undefined;
|
||||
pub var png_set_IHDR: PngSetIHDRFn = undefined;
|
||||
pub var png_write_info: PngWriteInfoFn = undefined;
|
||||
pub var png_write_image: PngWriteImageFn = undefined;
|
||||
pub var png_write_end: PngWriteEndFn = undefined;
|
||||
pub var png_destroy_write_struct: PngDestroyWriteStructFn = undefined;
|
||||
pub var png_get_io_ptr: PngGetIoPtr = undefined;
|
||||
|
||||
/// Library handle
|
||||
var lib_handle: ?*anyopaque = null;
|
||||
var init_guard = std.once(initialize);
|
||||
var is_initialized = false;
|
||||
|
||||
/// Initialize the library - called once via std.once
|
||||
fn initialize() void {
|
||||
lib_handle = std.c.dlopen(library_name, std.c.RTLD_NOW);
|
||||
if (lib_handle == null) {
|
||||
// Library not available, leave is_initialized as false
|
||||
return;
|
||||
}
|
||||
|
||||
// Load all required function pointers
|
||||
if (loadSymbol(PngCreateWriteStructFn, "png_create_write_struct")) |fn_ptr| {
|
||||
png_create_write_struct = fn_ptr;
|
||||
} else {
|
||||
closeLib();
|
||||
return;
|
||||
}
|
||||
|
||||
if (loadSymbol(PngCreateInfoStructFn, "png_create_info_struct")) |fn_ptr| {
|
||||
png_create_info_struct = fn_ptr;
|
||||
} else {
|
||||
closeLib();
|
||||
return;
|
||||
}
|
||||
|
||||
if (loadSymbol(PngSetWriteFnFn, "png_set_write_fn")) |fn_ptr| {
|
||||
png_set_write_fn = fn_ptr;
|
||||
} else {
|
||||
closeLib();
|
||||
return;
|
||||
}
|
||||
|
||||
if (loadSymbol(PngInitIoFn, "png_init_io")) |fn_ptr| {
|
||||
png_init_io = fn_ptr;
|
||||
} else {
|
||||
closeLib();
|
||||
return;
|
||||
}
|
||||
|
||||
if (loadSymbol(PngSetIHDRFn, "png_set_IHDR")) |fn_ptr| {
|
||||
png_set_IHDR = fn_ptr;
|
||||
} else {
|
||||
closeLib();
|
||||
return;
|
||||
}
|
||||
|
||||
if (loadSymbol(PngWriteInfoFn, "png_write_info")) |fn_ptr| {
|
||||
png_write_info = fn_ptr;
|
||||
} else {
|
||||
closeLib();
|
||||
return;
|
||||
}
|
||||
|
||||
if (loadSymbol(PngWriteImageFn, "png_write_image")) |fn_ptr| {
|
||||
png_write_image = fn_ptr;
|
||||
} else {
|
||||
closeLib();
|
||||
return;
|
||||
}
|
||||
|
||||
if (loadSymbol(PngWriteEndFn, "png_write_end")) |fn_ptr| {
|
||||
png_write_end = fn_ptr;
|
||||
} else {
|
||||
closeLib();
|
||||
return;
|
||||
}
|
||||
|
||||
if (loadSymbol(PngDestroyWriteStructFn, "png_destroy_write_struct")) |fn_ptr| {
|
||||
png_destroy_write_struct = fn_ptr;
|
||||
} else {
|
||||
closeLib();
|
||||
return;
|
||||
}
|
||||
|
||||
if (loadSymbol(PngGetIoPtr, "png_get_io_ptr")) |fn_ptr| {
|
||||
png_get_io_ptr = fn_ptr;
|
||||
} else {
|
||||
closeLib();
|
||||
return;
|
||||
}
|
||||
|
||||
// All required functions loaded successfully
|
||||
is_initialized = true;
|
||||
}
|
||||
|
||||
/// Helper to load a symbol from the library
|
||||
fn loadSymbol(comptime T: type, name: [:0]const u8) ?T {
|
||||
if (lib_handle) |handle| {
|
||||
const symbol = std.c.dlsym(handle, name.ptr);
|
||||
if (symbol == null) return null;
|
||||
return @ptrCast(T, symbol);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/// Close the library handle
|
||||
fn closeLib() void {
|
||||
if (lib_handle) |handle| {
|
||||
_ = std.c.dlclose(handle);
|
||||
lib_handle = null;
|
||||
}
|
||||
is_initialized = false;
|
||||
}
|
||||
|
||||
/// Initialize the library if not already initialized
|
||||
pub fn init() !void {
|
||||
// Call once-guard to ensure initialization happens only once
|
||||
init_guard.call();
|
||||
|
||||
// Check if initialization was successful
|
||||
if (!is_initialized) {
|
||||
return error.LibraryNotFound;
|
||||
}
|
||||
}
|
||||
|
||||
/// Check if the library is initialized
|
||||
pub fn isInitialized() bool {
|
||||
return is_initialized;
|
||||
}
|
||||
|
||||
/// Deinitialize and free resources
|
||||
pub fn deinit() void {
|
||||
closeLib();
|
||||
}
|
||||
116
src/image/libwebp.zig
Normal file
116
src/image/libwebp.zig
Normal file
@@ -0,0 +1,116 @@
|
||||
const std = @import("std");
|
||||
|
||||
/// Library name and path
|
||||
pub const library_name = "libwebp.so";
|
||||
|
||||
/// WebP function pointer types
|
||||
pub const WebPEncodeBGRAFn = fn([*]const u8, c_int, c_int, c_int, f32, [*][*]u8) callconv(.C) usize;
|
||||
pub const WebPEncodeLosslessBGRAFn = fn([*]const u8, c_int, c_int, c_int, [*][*]u8) callconv(.C) usize;
|
||||
pub const WebPEncodeRGBAFn = fn([*]const u8, c_int, c_int, c_int, f32, [*][*]u8) callconv(.C) usize;
|
||||
pub const WebPEncodeLosslessRGBAFn = fn([*]const u8, c_int, c_int, c_int, [*][*]u8) callconv(.C) usize;
|
||||
pub const WebPEncodeRGBFn = fn([*]const u8, c_int, c_int, c_int, f32, [*][*]u8) callconv(.C) usize;
|
||||
pub const WebPEncodeLosslessRGBFn = fn([*]const u8, c_int, c_int, c_int, [*][*]u8) callconv(.C) usize;
|
||||
pub const WebPGetEncoderVersionFn = fn() callconv(.C) c_int;
|
||||
pub const WebPFreeFn = fn(?*anyopaque) callconv(.C) void;
|
||||
|
||||
/// Function pointers - will be initialized by init()
|
||||
pub var WebPEncodeBGRA: WebPEncodeBGRAFn = undefined;
|
||||
pub var WebPEncodeLosslessBGRA: WebPEncodeLosslessBGRAFn = undefined;
|
||||
pub var WebPEncodeRGBA: ?WebPEncodeRGBAFn = null; // Optional, may not be in all versions
|
||||
pub var WebPEncodeLosslessRGBA: ?WebPEncodeLosslessRGBAFn = null; // Optional
|
||||
pub var WebPEncodeRGB: ?WebPEncodeRGBFn = null; // Optional
|
||||
pub var WebPEncodeLosslessRGB: ?WebPEncodeLosslessRGBFn = null; // Optional
|
||||
pub var WebPGetEncoderVersion: WebPGetEncoderVersionFn = undefined;
|
||||
pub var WebPFree: ?WebPFreeFn = null; // Optional, older versions may not have this
|
||||
|
||||
/// Library handle
|
||||
var lib_handle: ?*anyopaque = null;
|
||||
var init_guard = std.once(initialize);
|
||||
var is_initialized = false;
|
||||
|
||||
/// Initialize the library - called once via std.once
|
||||
fn initialize() void {
|
||||
lib_handle = std.c.dlopen(library_name, std.c.RTLD_NOW);
|
||||
if (lib_handle == null) {
|
||||
// Library not available, leave is_initialized as false
|
||||
return;
|
||||
}
|
||||
|
||||
// Load required function pointers (core functions that must be present)
|
||||
if (loadSymbol(WebPEncodeBGRAFn, "WebPEncodeBGRA")) |fn_ptr| {
|
||||
WebPEncodeBGRA = fn_ptr;
|
||||
} else {
|
||||
closeLib();
|
||||
return;
|
||||
}
|
||||
|
||||
if (loadSymbol(WebPEncodeLosslessBGRAFn, "WebPEncodeLosslessBGRA")) |fn_ptr| {
|
||||
WebPEncodeLosslessBGRA = fn_ptr;
|
||||
} else {
|
||||
closeLib();
|
||||
return;
|
||||
}
|
||||
|
||||
if (loadSymbol(WebPGetEncoderVersionFn, "WebPGetEncoderVersion")) |fn_ptr| {
|
||||
WebPGetEncoderVersion = fn_ptr;
|
||||
} else {
|
||||
closeLib();
|
||||
return;
|
||||
}
|
||||
|
||||
// Load optional function pointers (don't fail if these aren't present)
|
||||
WebPEncodeRGBA = loadSymbol(WebPEncodeRGBAFn, "WebPEncodeRGBA");
|
||||
WebPEncodeLosslessRGBA = loadSymbol(WebPEncodeLosslessRGBAFn, "WebPEncodeLosslessRGBA");
|
||||
WebPEncodeRGB = loadSymbol(WebPEncodeRGBFn, "WebPEncodeRGB");
|
||||
WebPEncodeLosslessRGB = loadSymbol(WebPEncodeLosslessRGBFn, "WebPEncodeLosslessRGB");
|
||||
WebPFree = loadSymbol(WebPFreeFn, "WebPFree");
|
||||
|
||||
// All required functions loaded successfully
|
||||
is_initialized = true;
|
||||
}
|
||||
|
||||
/// Helper to load a symbol from the library
|
||||
fn loadSymbol(comptime T: type, name: [:0]const u8) ?T {
|
||||
if (lib_handle) |handle| {
|
||||
const symbol = std.c.dlsym(handle, name.ptr);
|
||||
if (symbol == null) return null;
|
||||
return @as(T, @ptrCast(symbol));
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/// Close the library handle
|
||||
fn closeLib() void {
|
||||
if (lib_handle) |handle| {
|
||||
_ = std.c.dlclose(handle);
|
||||
lib_handle = null;
|
||||
}
|
||||
is_initialized = false;
|
||||
}
|
||||
|
||||
/// Initialize the library if not already initialized
|
||||
pub fn init() !void {
|
||||
// Call once-guard to ensure initialization happens only once
|
||||
init_guard.call();
|
||||
|
||||
// Check if initialization was successful
|
||||
if (!is_initialized) {
|
||||
return error.LibraryNotFound;
|
||||
}
|
||||
}
|
||||
|
||||
/// Check if the library is initialized
|
||||
pub fn isInitialized() bool {
|
||||
return is_initialized;
|
||||
}
|
||||
|
||||
/// Get WebP encoder version
|
||||
pub fn getEncoderVersion() !c_int {
|
||||
if (!is_initialized) return error.LibraryNotInitialized;
|
||||
return WebPGetEncoderVersion();
|
||||
}
|
||||
|
||||
/// Deinitialize and free resources
|
||||
pub fn deinit() void {
|
||||
closeLib();
|
||||
}
|
||||
Reference in New Issue
Block a user