diff --git a/native/src/api.rs b/native/src/api.rs index 6566a78..1710603 100644 --- a/native/src/api.rs +++ b/native/src/api.rs @@ -40,6 +40,19 @@ impl ImageFormatEnum { ImageFormatEnum::Tiff => ImageFormat::Tiff, } } + + pub fn from_image_format(format: ImageFormat) -> Option { + match format { + ImageFormat::Png => Some(ImageFormatEnum::Png), + ImageFormat::Jpeg => Some(ImageFormatEnum::Jpeg), + ImageFormat::Gif => Some(ImageFormatEnum::Gif), + ImageFormat::WebP => Some(ImageFormatEnum::WebP), + ImageFormat::Bmp => Some(ImageFormatEnum::Bmp), + ImageFormat::Ico => Some(ImageFormatEnum::Ico), + ImageFormat::Tiff => Some(ImageFormatEnum::Tiff), + _ => None, + } + } } #[allow(dead_code)] @@ -216,6 +229,18 @@ pub fn get_metadata(img: &DynamicImage) -> ImageMetadata { } } +/// Guess image format from byte data +pub fn guess_image_format(data: &[u8]) -> Result { + let format = image::guess_format(data)?; + ImageFormatEnum::from_image_format(format) + .ok_or_else(|| { + ImageError::Unsupported(image::error::UnsupportedError::from_format_and_kind( + image::error::ImageFormatHint::Unknown, + image::error::UnsupportedErrorKind::Format(image::error::ImageFormatHint::Unknown), + )) + }) +} + /// Convert ImageError to error code pub fn error_to_code(err: &ImageError) -> ImageErrorCode { match err { diff --git a/native/src/ffi.rs b/native/src/ffi.rs index 432c016..a8fef49 100644 --- a/native/src/ffi.rs +++ b/native/src/ffi.rs @@ -206,6 +206,35 @@ pub extern "C" fn pixer_load_from_memory_with_format_and_error( } } +// ============================================================================ +// Format Detection +// ============================================================================ + +/// Guess image format from byte data +/// Returns the format enum value or ImageErrorCode on error +#[unsafe(no_mangle)] +pub extern "C" fn pixer_guess_format( + data: *const u8, + len: usize, + out_format: *mut u32, +) -> ImageErrorCode { + if data.is_null() || len == 0 || out_format.is_null() { + return ImageErrorCode::InvalidPointer; + } + + let buffer = unsafe { slice::from_raw_parts(data, len) }; + + match guess_image_format(buffer) { + Ok(format) => { + unsafe { + *out_format = format as u32; + } + ImageErrorCode::Success + } + Err(e) => error_to_code(&e), + } +} + // ============================================================================ // Image Saving // ============================================================================ diff --git a/packages/pixer/lib/src/bindings/bindings.dart b/packages/pixer/lib/src/bindings/bindings.dart index 92e22ac..dbb1063 100644 --- a/packages/pixer/lib/src/bindings/bindings.dart +++ b/packages/pixer/lib/src/bindings/bindings.dart @@ -22,6 +22,21 @@ external void pixer_free_buffer(ffi.Pointer ptr, int len); @ffi.Native)>(isLeaf: true) external void pixer_free(ffi.Pointer handle); +/// Guess image format from byte data +/// Returns the format enum value or ImageErrorCode on error +@ffi.Native< + ImageErrorCode$1 Function( + ffi.Pointer, + ffi.UintPtr, + ffi.Pointer, + ) +>() +external int pixer_guess_format( + ffi.Pointer data, + int len, + ffi.Pointer out_format, +); + /// Load an image from a file path /// Returns null on error @ffi.Native Function(ffi.Pointer)>( diff --git a/packages/pixer/lib/src/pixer_base.dart b/packages/pixer/lib/src/pixer_base.dart index 3ff50a5..604cea1 100644 --- a/packages/pixer/lib/src/pixer_base.dart +++ b/packages/pixer/lib/src/pixer_base.dart @@ -40,6 +40,36 @@ final class Pixer { /// Whether the native resources have been disposed. bool get isDisposed => _isDisposed; + /// Guesses the image format from byte data. + /// + /// This is useful for detecting the format before encoding, to check + /// if the image is already in the desired format. + /// + /// Throws [PixerException] if the format cannot be detected. + static ImageFormatEnum guessFormat(Uint8List data) { + final dataPtr = malloc.allocate(data.length); + final outFormatPtr = malloc.allocate(ffi.sizeOf()); + + try { + dataPtr.asTypedList(data.length).setAll(0, data); + final errorCode = pixer_guess_format( + dataPtr, + data.length, + outFormatPtr, + ); + final error = ImageErrorCode.fromValue(errorCode); + if (error != ImageErrorCode.Success) { + throw PixerException.fromCode(error); + } + + final formatValue = outFormatPtr.value; + return ImageFormatEnum.fromValue(formatValue); + } finally { + malloc.free(dataPtr); + malloc.free(outFormatPtr); + } + } + /// Loads an image from a file path /// /// Throws [InvalidPathException] if the path is empty or invalid.