Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 25 additions & 0 deletions native/src/api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,19 @@ impl ImageFormatEnum {
ImageFormatEnum::Tiff => ImageFormat::Tiff,
}
}

pub fn from_image_format(format: ImageFormat) -> Option<Self> {
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)]
Expand Down Expand Up @@ -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<ImageFormatEnum, ImageError> {
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 {
Expand Down
29 changes: 29 additions & 0 deletions native/src/ffi.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
// ============================================================================
Expand Down
15 changes: 15 additions & 0 deletions packages/pixer/lib/src/bindings/bindings.dart
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,21 @@ external void pixer_free_buffer(ffi.Pointer<ffi.Uint8> ptr, int len);
@ffi.Native<ffi.Void Function(ffi.Pointer<ImageHandle>)>(isLeaf: true)
external void pixer_free(ffi.Pointer<ImageHandle> handle);

/// Guess image format from byte data
/// Returns the format enum value or ImageErrorCode on error
@ffi.Native<
ImageErrorCode$1 Function(
ffi.Pointer<ffi.Uint8>,
ffi.UintPtr,
ffi.Pointer<ffi.Uint32>,
)
>()
external int pixer_guess_format(
ffi.Pointer<ffi.Uint8> data,
int len,
ffi.Pointer<ffi.Uint32> out_format,
);

/// Load an image from a file path
/// Returns null on error
@ffi.Native<ffi.Pointer<ImageHandle> Function(ffi.Pointer<ffi.Char>)>(
Expand Down
30 changes: 30 additions & 0 deletions packages/pixer/lib/src/pixer_base.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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<ffi.Uint8>(data.length);
final outFormatPtr = malloc.allocate<ffi.Uint32>(ffi.sizeOf<ffi.Uint32>());

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.
Expand Down