From a0bcd464114d116e24758ca4e41922eddbb81df1 Mon Sep 17 00:00:00 2001 From: Jarred Sumner Date: Sun, 23 Mar 2025 10:32:43 -0700 Subject: [PATCH] ok --- src/CLAUDE.md | 3 +- src/image/encoder_linux.zig | 288 +++++++++++++++++++++++++++++++++++- src/image/libjpeg.zig | 213 ++++++++++++++++++++++++++ src/image/libpng.zig | 190 ++++++++++++++++++++++++ src/image/libwebp.zig | 116 +++++++++++++++ 5 files changed, 803 insertions(+), 7 deletions(-) create mode 100644 src/image/libjpeg.zig create mode 100644 src/image/libpng.zig create mode 100644 src/image/libwebp.zig diff --git a/src/CLAUDE.md b/src/CLAUDE.md index e8bbf2c333..e74e0d4454 100644 --- a/src/CLAUDE.md +++ b/src/CLAUDE.md @@ -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: diff --git a/src/image/encoder_linux.zig b/src/image/encoder_linux.zig index 042b5cca25..b40ea0077e 100644 --- a/src/image/encoder_linux.zig +++ b/src/image/encoder_linux.zig @@ -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; } \ No newline at end of file diff --git a/src/image/libjpeg.zig b/src/image/libjpeg.zig new file mode 100644 index 0000000000..b4375ba9a4 --- /dev/null +++ b/src/image/libjpeg.zig @@ -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(); +} \ No newline at end of file diff --git a/src/image/libpng.zig b/src/image/libpng.zig new file mode 100644 index 0000000000..c2b5ca7f6b --- /dev/null +++ b/src/image/libpng.zig @@ -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(); +} diff --git a/src/image/libwebp.zig b/src/image/libwebp.zig new file mode 100644 index 0000000000..05777ed818 --- /dev/null +++ b/src/image/libwebp.zig @@ -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(); +} \ No newline at end of file