From 570ca352cf89b90c88162ad001a8fe0669bce07a Mon Sep 17 00:00:00 2001 From: Koji Wakamiya Date: Fri, 12 Dec 2025 11:51:30 +0900 Subject: [PATCH] refactor: Resizing --- lib/src/android/native.dart | 75 ++++++---------------------- lib/src/darwin/native.dart | 61 ++++------------------- lib/src/output_resize.dart | 35 +++++++++++++ lib/src/web/web.dart | 63 ++--------------------- test/output_resize_test.dart | 97 ++++++++++++++++++++++++++++++++++++ 5 files changed, 163 insertions(+), 168 deletions(-) create mode 100644 test/output_resize_test.dart diff --git a/lib/src/android/native.dart b/lib/src/android/native.dart index 48addf4..4a93e5d 100644 --- a/lib/src/android/native.dart +++ b/lib/src/android/native.dart @@ -57,66 +57,23 @@ final class ImageConverterAndroid implements ImageConverterPlatform { throw Exception('Failed to decode image. Invalid image data.'); } - switch (resizeMode) { - case OriginalResizeMode(): - bitmapToCompress = originalBitmap; - case ExactResizeMode(width: final w, height: final h): - scaledBitmap = Bitmap.createScaledBitmap( - originalBitmap, - w, - h, - true, // filter - ); - bitmapToCompress = scaledBitmap; - case FitResizeMode(:final width, :final height): - final originalWidth = originalBitmap.getWidth(); - final originalHeight = originalBitmap.getHeight(); + final originalWidth = originalBitmap.getWidth(); + final originalHeight = originalBitmap.getHeight(); + final (newWidth, newHeight) = resizeMode.calculateSize( + originalWidth, + originalHeight, + ); - 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; - } - } 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 (newWidth == originalWidth && newHeight == originalHeight) { + bitmapToCompress = originalBitmap; + } else { + scaledBitmap = Bitmap.createScaledBitmap( + originalBitmap, + newWidth, + newHeight, + true, // filter + ); + bitmapToCompress = scaledBitmap; } if (bitmapToCompress == null) { diff --git a/lib/src/darwin/native.dart b/lib/src/darwin/native.dart index bb2874e..e00310a 100644 --- a/lib/src/darwin/native.dart +++ b/lib/src/darwin/native.dart @@ -73,58 +73,19 @@ final class ImageConverterDarwin implements ImageConverterPlatform { throw Exception('Failed to decode image.'); } - switch (resizeMode) { - case OriginalResizeMode(): - imageToEncode = originalImage; - case ExactResizeMode(width: final w, height: final h): - imageToEncode = _resizeImage(originalImage, w, h); - case FitResizeMode(:final width, :final height): - final originalWidth = CGImageGetWidth(originalImage); - final originalHeight = CGImageGetHeight(originalImage); + final originalWidth = CGImageGetWidth(originalImage); + final originalHeight = CGImageGetHeight(originalImage); + final (newWidth, newHeight) = resizeMode.calculateSize( + originalWidth, + originalHeight, + ); - 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; - } - } 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 (newWidth == originalWidth && newHeight == originalHeight) { + imageToEncode = originalImage; + } else { + imageToEncode = _resizeImage(originalImage, newWidth, newHeight); } + 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 5f2dc79..177580c 100644 --- a/lib/src/output_resize.dart +++ b/lib/src/output_resize.dart @@ -1,11 +1,21 @@ +import 'dart:math'; + /// A sealed class representing different ways to resize an image. sealed class ResizeMode { const ResizeMode(); + + /// Calculates the target size for the image based on the original dimensions. + (int, int) calculateSize(int originalWidth, int originalHeight); } /// A resize mode that keeps the original dimensions of the image. class OriginalResizeMode extends ResizeMode { const OriginalResizeMode(); + + @override + (int, int) calculateSize(int originalWidth, int originalHeight) { + return (originalWidth, originalHeight); + } } /// A resize mode that resizes the image to exact dimensions, @@ -18,6 +28,11 @@ class ExactResizeMode extends ResizeMode { /// The target height for the resized image. final int height; + + @override + (int, int) calculateSize(int originalWidth, int originalHeight) { + return (width, height); + } } /// A resize mode that fits the image within the specified dimensions while @@ -34,4 +49,24 @@ class FitResizeMode extends ResizeMode { /// The maximum height for the resized image. final int? height; + + @override + (int, int) calculateSize(int originalWidth, int originalHeight) { + double scale = 1.0; + if (width != null && height != null) { + final widthScale = width! / originalWidth; + final heightScale = height! / originalHeight; + scale = min(widthScale, heightScale); + } else if (width != null) { + scale = width! / originalWidth; + } else if (height != null) { + scale = height! / originalHeight; + } + + if (scale >= 1.0) { + return (originalWidth, originalHeight); + } + + return ((originalWidth * scale).round(), (originalHeight * scale).round()); + } } diff --git a/lib/src/web/web.dart b/lib/src/web/web.dart index dec8dfb..a772db9 100644 --- a/lib/src/web/web.dart +++ b/lib/src/web/web.dart @@ -59,65 +59,10 @@ final class ImageConverterWeb implements ImageConverterPlatform { final canvas = HTMLCanvasElement(); - final int destWidth; - final int destHeight; - - switch (resizeMode) { - case OriginalResizeMode(): - destWidth = img.width; - destHeight = img.height; - case ExactResizeMode(width: final w, height: final h): - destWidth = w; - destHeight = h; - case FitResizeMode(:final width, :final height): - 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; - } - } 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(); - } + final (destWidth, destHeight) = resizeMode.calculateSize( + img.width, + img.height, + ); canvas.width = destWidth; canvas.height = destHeight; diff --git a/test/output_resize_test.dart b/test/output_resize_test.dart new file mode 100644 index 0000000..bfe105b --- /dev/null +++ b/test/output_resize_test.dart @@ -0,0 +1,97 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:platform_image_converter/src/output_resize.dart'; + +void main() { + group('ResizeMode', () { + const originalWidth = 1000; + const originalHeight = 500; + + group('OriginalResizeMode', () { + test('should return original dimensions', () { + const resizeMode = OriginalResizeMode(); + final (width, height) = resizeMode.calculateSize( + originalWidth, + originalHeight, + ); + expect(width, originalWidth); + expect(height, originalHeight); + }); + }); + + group('ExactResizeMode', () { + test('should return exact dimensions', () { + const resizeMode = ExactResizeMode(width: 300, height: 200); + final (width, height) = resizeMode.calculateSize( + originalWidth, + originalHeight, + ); + expect(width, 300); + expect(height, 200); + }); + }); + + group('FitResizeMode', () { + test('downscales with both width and height, respecting aspect ratio', () { + // Aspect ratio is 2:1 (1000x500) + // Target is 500x500, so scale should be based on width (min(500/1000, 500/500) = 0.5) + const resizeMode = FitResizeMode(width: 500, height: 500); + final (width, height) = resizeMode.calculateSize( + originalWidth, + originalHeight, + ); + expect(width, 500); // 1000 * 0.5 + expect(height, 250); // 500 * 0.5 + }); + + test('does not upscale if image is smaller than target dimensions', () { + const resizeMode = FitResizeMode(width: 2000, height: 1000); + final (width, height) = resizeMode.calculateSize( + originalWidth, + originalHeight, + ); + expect(width, originalWidth); + expect(height, originalHeight); + }); + + test('downscales with only width, respecting aspect ratio', () { + const resizeMode = FitResizeMode(width: 500); + final (width, height) = resizeMode.calculateSize( + originalWidth, + originalHeight, + ); + expect(width, 500); // 1000 * 0.5 + expect(height, 250); // 500 * 0.5 + }); + + test('does not upscale with only width', () { + const resizeMode = FitResizeMode(width: 2000); + final (width, height) = resizeMode.calculateSize( + originalWidth, + originalHeight, + ); + expect(width, originalWidth); + expect(height, originalHeight); + }); + + test('downscales with only height, respecting aspect ratio', () { + const resizeMode = FitResizeMode(height: 250); + final (width, height) = resizeMode.calculateSize( + originalWidth, + originalHeight, + ); + expect(width, 500); // 1000 * 0.5 + expect(height, 250); // 500 * 0.5 + }); + + test('does not upscale with only height', () { + const resizeMode = FitResizeMode(height: 1000); + final (width, height) = resizeMode.calculateSize( + originalWidth, + originalHeight, + ); + expect(width, originalWidth); + expect(height, originalHeight); + }); + }); + }); +}