From 22bcc878955b6d2087c9c2b1030f20df753d6d43 Mon Sep 17 00:00:00 2001 From: Koji Wakamiya Date: Thu, 11 Dec 2025 23:01:37 +0900 Subject: [PATCH] fix: nullable --- README.md | 6 +-- example/integration_test/app_test.dart | 36 +++++++++++++++++ example/lib/main.dart | 4 +- lib/src/android/native.dart | 53 +++++++++++++++++++------- lib/src/darwin/native.dart | 47 +++++++++++++++++------ lib/src/output_resize.dart | 7 ++-- lib/src/web/web.dart | 50 +++++++++++++++++++----- 7 files changed, 161 insertions(+), 42 deletions(-) diff --git a/README.md b/README.md index f75ff35..13c3196 100644 --- a/README.md +++ b/README.md @@ -47,11 +47,11 @@ final jpegData = await ImageConverter.convert( quality: 90, ); -// Convert and resize an image to fit within 200x200 +// Convert and resize an image to fit within a width of 200, scaling height proportionally final resizedData = await ImageConverter.convert( inputData: imageData, format: OutputFormat.png, - resizeMode: const FitResizeMode(width: 200, height: 200), + resizeMode: const FitResizeMode(width: 200), ); // Convert any format to PNG @@ -117,7 +117,7 @@ A sealed class representing different ways to resize an image. - **`OriginalResizeMode()`**: Keeps the original dimensions of the image. - **`ExactResizeMode({required int width, required int height})`**: Resizes the image to exact dimensions, possibly changing the aspect ratio. -- **`FitResizeMode({required int width, required int height})`**: Fits the image within the specified dimensions while maintaining the aspect ratio. If the image is smaller than the specified dimensions, it will not be scaled up. +- **`FitResizeMode({int? width, int? height})`**: Fits the image within the specified dimensions while maintaining the aspect ratio. At least one of `width` or `height` must be provided. If only one dimension is provided, the other is scaled proportionally. If the image is smaller than the specified dimensions, it will not be scaled up. ## Implementation Details diff --git a/example/integration_test/app_test.dart b/example/integration_test/app_test.dart index 60b21e4..9734878 100644 --- a/example/integration_test/app_test.dart +++ b/example/integration_test/app_test.dart @@ -90,6 +90,42 @@ void main() { expect(resizedImage.width, equals(originalWidth)); expect(resizedImage.height, equals(originalHeight)); }); + + test('FitResizeMode with only width maintains aspect ratio', () async { + // Original jpeg.jpg is 1502x2000 + final targetWidth = 150; + final expectedWidth = 150; + final expectedHeight = 200; + + final converted = await ImageConverter.convert( + inputData: jpegData, + format: OutputFormat.jpeg, + resizeMode: FitResizeMode(width: targetWidth), + ); + + final resizedImage = img.decodeImage(converted)!; + // Allow for small rounding differences + expect(resizedImage.width, closeTo(expectedWidth, 1)); + expect(resizedImage.height, closeTo(expectedHeight, 1)); + }); + + test('FitResizeMode with only height maintains aspect ratio', () async { + // Original jpeg.jpg is 1502x2000 + final targetHeight = 200; + final expectedWidth = 150; + final expectedHeight = 200; + + final converted = await ImageConverter.convert( + inputData: jpegData, + format: OutputFormat.jpeg, + resizeMode: FitResizeMode(height: targetHeight), + ); + + final resizedImage = img.decodeImage(converted)!; + // Allow for small rounding differences + expect(resizedImage.width, closeTo(expectedWidth, 1)); + expect(resizedImage.height, closeTo(expectedHeight, 1)); + }); }); group('File size consistency tests', () { diff --git a/example/lib/main.dart b/example/lib/main.dart index 2ce1f83..89289c9 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -70,8 +70,8 @@ class _MainPageState extends State { final resizeMode = switch ((width, height)) { (null, null) => const OriginalResizeMode(), (final w?, final h?) => ExactResizeMode(width: w, height: h), - (final w?, null) => FitResizeMode(width: w, height: 1 << 30), - (null, final h?) => FitResizeMode(width: 1 << 30, height: h), + (final w?, null) => FitResizeMode(width: w), + (null, final h?) => FitResizeMode(height: h), }; final converted = await ImageConverter.convert( diff --git a/lib/src/android/native.dart b/lib/src/android/native.dart index c9eaf29..48addf4 100644 --- a/lib/src/android/native.dart +++ b/lib/src/android/native.dart @@ -72,26 +72,51 @@ final class ImageConverterAndroid implements ImageConverterPlatform { final originalWidth = originalBitmap.getWidth(); final originalHeight = originalBitmap.getHeight(); - if (originalWidth <= width && originalHeight <= height) { - bitmapToCompress = originalBitmap; - } else { - final aspectRatio = originalWidth / originalHeight; - var newWidth = width.toDouble(); - var newHeight = newWidth / aspectRatio; + double newWidth; + double newHeight; + if (width != null && height != null) { + if (originalWidth <= width && originalHeight <= height) { + bitmapToCompress = originalBitmap; + break; + } + final aspectRatio = originalWidth / originalHeight; + newWidth = width.toDouble(); + newHeight = newWidth / aspectRatio; if (newHeight > height) { newHeight = height.toDouble(); newWidth = newHeight * aspectRatio; } - - scaledBitmap = Bitmap.createScaledBitmap( - originalBitmap, - newWidth.round(), - newHeight.round(), - true, // filter - ); - bitmapToCompress = scaledBitmap; + } else if (width != null) { + if (originalWidth <= width) { + bitmapToCompress = originalBitmap; + break; + } + newWidth = width.toDouble(); + final aspectRatio = originalWidth / originalHeight; + newHeight = newWidth / aspectRatio; + } else if (height != null) { + if (originalHeight <= height) { + bitmapToCompress = originalBitmap; + break; + } + newHeight = height.toDouble(); + final aspectRatio = originalWidth / originalHeight; + newWidth = newHeight * aspectRatio; + } else { + // This case should not be reachable due to the assertion + // in the FitResizeMode constructor. + bitmapToCompress = originalBitmap; + break; } + + scaledBitmap = Bitmap.createScaledBitmap( + originalBitmap, + newWidth.round(), + newHeight.round(), + true, // filter + ); + bitmapToCompress = scaledBitmap; } if (bitmapToCompress == null) { diff --git a/lib/src/darwin/native.dart b/lib/src/darwin/native.dart index fac9482..9228bee 100644 --- a/lib/src/darwin/native.dart +++ b/lib/src/darwin/native.dart @@ -82,23 +82,48 @@ final class ImageConverterDarwin implements ImageConverterPlatform { final originalWidth = CGImageGetWidth(originalImage); final originalHeight = CGImageGetHeight(originalImage); - if (originalWidth <= width && originalHeight <= height) { - imageToEncode = originalImage; - } else { - final aspectRatio = originalWidth / originalHeight; - var newWidth = width.toDouble(); - var newHeight = newWidth / aspectRatio; + double newWidth; + double newHeight; + if (width != null && height != null) { + if (originalWidth <= width && originalHeight <= height) { + imageToEncode = originalImage; + break; + } + final aspectRatio = originalWidth / originalHeight; + newWidth = width.toDouble(); + newHeight = newWidth / aspectRatio; if (newHeight > height) { newHeight = height.toDouble(); newWidth = newHeight * aspectRatio; } - imageToEncode = _resizeImage( - originalImage, - newWidth.round(), - newHeight.round(), - ); + } else if (width != null) { + if (originalWidth <= width) { + imageToEncode = originalImage; + break; + } + newWidth = width.toDouble(); + final aspectRatio = originalWidth / originalHeight; + newHeight = newWidth / aspectRatio; + } else if (height != null) { + if (originalHeight <= height) { + imageToEncode = originalImage; + break; + } + newHeight = height.toDouble(); + final aspectRatio = originalWidth / originalHeight; + newWidth = newHeight * aspectRatio; + } else { + // This case should not be reachable due to the assertion + // in the FitResizeMode constructor. + imageToEncode = originalImage; + break; } + imageToEncode = _resizeImage( + originalImage, + newWidth.round(), + newHeight.round(), + ); } if (imageToEncode == nullptr) { throw Exception('Failed to prepare image for encoding.'); diff --git a/lib/src/output_resize.dart b/lib/src/output_resize.dart index 0f0ae4c..5f2dc79 100644 --- a/lib/src/output_resize.dart +++ b/lib/src/output_resize.dart @@ -26,11 +26,12 @@ class ExactResizeMode extends ResizeMode { /// If the image is smaller than the specified dimensions, it will not be /// scaled up. class FitResizeMode extends ResizeMode { - const FitResizeMode({required this.width, required this.height}); + const FitResizeMode({this.width, this.height}) + : assert(width != null || height != null); /// The maximum width for the resized image. - final int width; + final int? width; /// The maximum height for the resized image. - final int height; + final int? height; } diff --git a/lib/src/web/web.dart b/lib/src/web/web.dart index c083f2f..f6f5803 100644 --- a/lib/src/web/web.dart +++ b/lib/src/web/web.dart @@ -70,21 +70,53 @@ final class ImageConverterWeb implements ImageConverterPlatform { destWidth = w; destHeight = h; case FitResizeMode(:final width, :final height): - if (img.width <= width && img.height <= height) { - destWidth = img.width; - destHeight = img.height; - } else { - final aspectRatio = img.width / img.height; - var newWidth = width.toDouble(); - var newHeight = newWidth / aspectRatio; + final originalWidth = img.width; + final originalHeight = img.height; + double newWidth; + double newHeight; + + if (width != null && height != null) { + if (originalWidth <= width && originalHeight <= height) { + destWidth = originalWidth; + destHeight = originalHeight; + break; + } + final aspectRatio = originalWidth / originalHeight; + newWidth = width.toDouble(); + newHeight = newWidth / aspectRatio; if (newHeight > height) { newHeight = height.toDouble(); newWidth = newHeight * aspectRatio; } - destWidth = newWidth.round(); - destHeight = newHeight.round(); + } else if (width != null) { + if (originalWidth <= width) { + destWidth = originalWidth; + destHeight = originalHeight; + break; + } + newWidth = width.toDouble(); + final aspectRatio = originalWidth / originalHeight; + newHeight = newWidth / aspectRatio; + } else if (height != null) { + if (originalHeight <= height) { + destWidth = originalWidth; + destHeight = originalHeight; + break; + } + newHeight = height.toDouble(); + final aspectRatio = originalWidth / originalHeight; + newWidth = newHeight * aspectRatio; + } else { + // This case should not be reachable due to the assertion + // in the FitResizeMode constructor. + destWidth = originalWidth; + destHeight = originalHeight; + break; } + + destWidth = newWidth.round(); + destHeight = newHeight.round(); } canvas.width = destWidth;