diff --git a/lib/platform_image_converter.dart b/lib/platform_image_converter.dart index 965bd78..cd0b4bc 100644 --- a/lib/platform_image_converter.dart +++ b/lib/platform_image_converter.dart @@ -10,6 +10,7 @@ import 'package:platform_image_converter/src/output_format.dart'; import 'package:platform_image_converter/src/output_resize.dart'; import 'package:platform_image_converter/src/web/shared.dart'; +export 'src/image_conversion_exception.dart'; export 'src/output_format.dart'; export 'src/output_resize.dart'; @@ -42,8 +43,9 @@ class ImageConverter { /// /// **Throws:** /// - [UnsupportedError]: If the platform or output format is not supported. - /// - [Exception]: If the image decoding or encoding fails. - /// + /// - [ImageDecodingException]: If the input image data cannot be decoded. + /// - [ImageEncodingException]: If the image cannot be encoded to the target format. + /// - [ImageConversionException]: For other general errors during the conversion process. /// /// **Example - Convert HEIC to JPEG:** /// ```dart /// final jpegData = await ImageConverter.convert( diff --git a/lib/src/android/native.dart b/lib/src/android/native.dart index 4a93e5d..911bd3a 100644 --- a/lib/src/android/native.dart +++ b/lib/src/android/native.dart @@ -2,6 +2,7 @@ import 'dart:typed_data'; import 'package:jni/jni.dart'; import 'package:platform_image_converter/src/android/bindings.g.dart'; +import 'package:platform_image_converter/src/image_conversion_exception.dart'; import 'package:platform_image_converter/src/image_converter_platform_interface.dart'; import 'package:platform_image_converter/src/output_format.dart'; import 'package:platform_image_converter/src/output_resize.dart'; @@ -54,7 +55,7 @@ final class ImageConverterAndroid implements ImageConverterPlatform { inputData.length, ); if (originalBitmap == null) { - throw Exception('Failed to decode image. Invalid image data.'); + throw const ImageDecodingException('Invalid image data.'); } final originalWidth = originalBitmap.getWidth(); @@ -78,7 +79,9 @@ final class ImageConverterAndroid implements ImageConverterPlatform { if (bitmapToCompress == null) { // This should not happen if originalBitmap is valid - throw Exception('Bitmap could not be prepared for compression.'); + throw const ImageConversionException( + 'Bitmap could not be prepared for compression.', + ); } compressFormat = switch (format) { @@ -98,12 +101,15 @@ final class ImageConverterAndroid implements ImageConverterPlatform { outputStream, ); if (!success) { - throw Exception('Failed to compress bitmap.'); + throw ImageEncodingException(format, 'Failed to compress bitmap.'); } outputJBytes = outputStream.toByteArray(); if (outputJBytes == null) { - throw Exception('Failed to get byte array from output stream.'); + throw ImageEncodingException( + format, + 'Failed to get byte array from output stream.', + ); } return Uint8List.fromList(outputJBytes.toList()); diff --git a/lib/src/darwin/native.dart b/lib/src/darwin/native.dart index e00310a..aad1322 100644 --- a/lib/src/darwin/native.dart +++ b/lib/src/darwin/native.dart @@ -4,6 +4,7 @@ import 'dart:typed_data'; import 'package:ffi/ffi.dart'; import 'package:objective_c/objective_c.dart'; import 'package:platform_image_converter/src/darwin/bindings.g.dart'; +import 'package:platform_image_converter/src/image_conversion_exception.dart'; import 'package:platform_image_converter/src/image_converter_platform_interface.dart'; import 'package:platform_image_converter/src/output_format.dart'; import 'package:platform_image_converter/src/output_resize.dart'; @@ -60,17 +61,19 @@ final class ImageConverterDarwin implements ImageConverterPlatform { inputData.length, ); if (cfData == nullptr) { - throw Exception('Failed to create CFData from input data.'); + throw const ImageConversionException( + 'Failed to create CFData from input data.', + ); } imageSource = CGImageSourceCreateWithData(cfData, nullptr); if (imageSource == nullptr) { - throw Exception('Failed to create CGImageSource. Invalid image data.'); + throw const ImageDecodingException('Invalid image data.'); } originalImage = CGImageSourceCreateImageAtIndex(imageSource, 0, nullptr); if (originalImage == nullptr) { - throw Exception('Failed to decode image.'); + throw const ImageDecodingException(); } final originalWidth = CGImageGetWidth(originalImage); @@ -87,12 +90,14 @@ final class ImageConverterDarwin implements ImageConverterPlatform { } if (imageToEncode == nullptr) { - throw Exception('Failed to prepare image for encoding.'); + throw const ImageConversionException( + 'Failed to prepare image for encoding. Resizing may have failed.', + ); } outputData = CFDataCreateMutable(kCFAllocatorDefault, 0); if (outputData == nullptr) { - throw Exception('Failed to create output CFData.'); + throw ImageEncodingException(format, 'Failed to create output CFData.'); } final utiStr = switch (format) { @@ -120,7 +125,10 @@ final class ImageConverterDarwin implements ImageConverterPlatform { nullptr, ); if (destination == nullptr) { - throw Exception('Failed to create CGImageDestination.'); + throw ImageEncodingException( + format, + 'Failed to create CGImageDestination.', + ); } properties = _createPropertiesForFormat(format, quality); @@ -132,13 +140,19 @@ final class ImageConverterDarwin implements ImageConverterPlatform { final success = CGImageDestinationFinalize(destination); if (!success) { - throw Exception('Failed to finalize image encoding.'); + throw ImageEncodingException( + format, + 'Failed to finalize image encoding.', + ); } final length = CFDataGetLength(outputData); final bytePtr = CFDataGetBytePtr(outputData); if (bytePtr == nullptr) { - throw Exception('Failed to get output data bytes.'); + throw ImageEncodingException( + format, + 'Failed to get output data bytes.', + ); } return Uint8List.fromList(bytePtr.cast().asTypedList(length)); @@ -193,7 +207,9 @@ final class ImageConverterDarwin implements ImageConverterPlatform { try { final colorSpace = CGImageGetColorSpace(originalImage); if (colorSpace == nullptr) { - throw Exception('Failed to get color space from image.'); + throw const ImageConversionException( + 'Failed to get color space from image for resizing.', + ); } final bitsPerComponent = CGImageGetBitsPerComponent(originalImage); @@ -209,7 +225,9 @@ final class ImageConverterDarwin implements ImageConverterPlatform { bitmapInfo, ); if (context == nullptr) { - throw Exception('Failed to create bitmap context for resizing.'); + throw const ImageConversionException( + 'Failed to create bitmap context for resizing.', + ); } CGContextSetInterpolationQuality( @@ -227,7 +245,9 @@ final class ImageConverterDarwin implements ImageConverterPlatform { final resizedImage = CGBitmapContextCreateImage(context); if (resizedImage == nullptr) { - throw Exception('Failed to create resized image from context.'); + throw const ImageConversionException( + 'Failed to create resized image from context.', + ); } return resizedImage; } finally { diff --git a/lib/src/image_conversion_exception.dart b/lib/src/image_conversion_exception.dart new file mode 100644 index 0000000..25e6f37 --- /dev/null +++ b/lib/src/image_conversion_exception.dart @@ -0,0 +1,30 @@ +import 'package:platform_image_converter/src/output_format.dart'; + +/// Base exception for image conversion errors. +class ImageConversionException implements Exception { + /// Creates an [ImageConversionException] with the given [message]. + const ImageConversionException(this.message); + + /// Error message describing the exception. + final String message; + + @override + String toString() => 'ImageConversionException: $message'; +} + +/// Thrown when the input image data cannot be decoded. +class ImageDecodingException extends ImageConversionException { + /// Creates an [ImageDecodingException] with the given [message]. + const ImageDecodingException([ + super.message = 'Failed to decode image data.', + ]); +} + +/// Thrown when the image cannot be encoded to the target format. +class ImageEncodingException extends ImageConversionException { + /// Creates an [ImageEncodingException] with the given [message]. + ImageEncodingException(this.format, [String? message]) + : super(message ?? 'Failed to encode image to ${format.name}'); + + final OutputFormat format; +} diff --git a/lib/src/image_converter_platform_interface.dart b/lib/src/image_converter_platform_interface.dart index 70368f8..4416240 100644 --- a/lib/src/image_converter_platform_interface.dart +++ b/lib/src/image_converter_platform_interface.dart @@ -1,7 +1,7 @@ import 'dart:typed_data'; -import 'output_format.dart'; -import 'output_resize.dart'; +import 'package:platform_image_converter/src/output_format.dart'; +import 'package:platform_image_converter/src/output_resize.dart'; /// Platform-specific image converter interface. /// @@ -27,7 +27,9 @@ abstract interface class ImageConverterPlatform { /// **Throws:** /// - [UnimplementedError]: If not implemented by platform subclass /// - [UnsupportedError]: If format is not supported - /// - [Exception]: If conversion fails + /// - [ImageDecodingException]: If the input image data cannot be decoded. + /// - [ImageEncodingException]: If the image cannot be encoded to the target format. + /// - [ImageConversionException]: For other general errors during the conversion process. Future convert({ required Uint8List inputData, OutputFormat format = OutputFormat.jpeg, diff --git a/lib/src/web/web.dart b/lib/src/web/web.dart index a772db9..38e2f83 100644 --- a/lib/src/web/web.dart +++ b/lib/src/web/web.dart @@ -2,6 +2,7 @@ import 'dart:async'; import 'dart:js_interop'; import 'dart:typed_data'; +import 'package:platform_image_converter/src/image_conversion_exception.dart'; import 'package:platform_image_converter/src/image_converter_platform_interface.dart'; import 'package:platform_image_converter/src/output_format.dart'; import 'package:platform_image_converter/src/output_resize.dart'; @@ -45,17 +46,24 @@ final class ImageConverterWeb implements ImageConverterPlatform { }) async { final img = HTMLImageElement(); final decodeCompleter = Completer(); - final blob = Blob([inputData.toJS].toJS); final url = URL.createObjectURL(blob); - img.onLoad.listen((_) => decodeCompleter.complete()); - img.onError.listen((e) { - URL.revokeObjectURL(url); - decodeCompleter.completeError('Failed to load image: $e'); - }); + img + ..onLoad.listen((_) { + decodeCompleter.complete(); + }) + ..onError.listen((event) { + decodeCompleter.completeError( + const ImageDecodingException('Failed to load image from data.'), + ); + }); img.src = url; - await decodeCompleter.future; - URL.revokeObjectURL(url); + + try { + await decodeCompleter.future; + } finally { + URL.revokeObjectURL(url); + } final canvas = HTMLCanvasElement(); @@ -86,7 +94,7 @@ final class ImageConverterWeb implements ImageConverterPlatform { encodeCompleter.complete(blob); } else { encodeCompleter.completeError( - 'Failed to convert canvas to JPEG Blob.', + ImageEncodingException(format, 'Canvas toBlob returned null.'), ); } }.toJS,