Merge pull request #14 from koji-1009/feat/exception_type

refactor: ImageConversionException
This commit is contained in:
Koji Wakamiya 2025-12-12 22:23:26 +09:00 committed by GitHub
commit fca8763ca6
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 97 additions and 29 deletions

View File

@ -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(

View File

@ -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());

View File

@ -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<Uint8>().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 {

View File

@ -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;
}

View File

@ -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<Uint8List> convert({
required Uint8List inputData,
OutputFormat format = OutputFormat.jpeg,

View File

@ -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<void>();
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,