refactor: Resizing

This commit is contained in:
Koji Wakamiya 2025-12-12 11:51:30 +09:00
parent 43d2003e6c
commit 570ca352cf
No known key found for this signature in database
5 changed files with 163 additions and 168 deletions

View File

@ -57,63 +57,20 @@ final class ImageConverterAndroid implements ImageConverterPlatform {
throw Exception('Failed to decode image. Invalid image data.'); 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 originalWidth = originalBitmap.getWidth();
final originalHeight = originalBitmap.getHeight(); final originalHeight = originalBitmap.getHeight();
final (newWidth, newHeight) = resizeMode.calculateSize(
originalWidth,
originalHeight,
);
double newWidth; if (newWidth == originalWidth && newHeight == originalHeight) {
double newHeight;
if (width != null && height != null) {
if (originalWidth <= width && originalHeight <= height) {
bitmapToCompress = originalBitmap; 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 { } else {
// This case should not be reachable due to the assertion
// in the FitResizeMode constructor.
bitmapToCompress = originalBitmap;
break;
}
scaledBitmap = Bitmap.createScaledBitmap( scaledBitmap = Bitmap.createScaledBitmap(
originalBitmap, originalBitmap,
newWidth.round(), newWidth,
newHeight.round(), newHeight,
true, // filter true, // filter
); );
bitmapToCompress = scaledBitmap; bitmapToCompress = scaledBitmap;

View File

@ -73,58 +73,19 @@ final class ImageConverterDarwin implements ImageConverterPlatform {
throw Exception('Failed to decode image.'); 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 originalWidth = CGImageGetWidth(originalImage);
final originalHeight = CGImageGetHeight(originalImage); final originalHeight = CGImageGetHeight(originalImage);
final (newWidth, newHeight) = resizeMode.calculateSize(
double newWidth; originalWidth,
double newHeight; originalHeight,
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) { if (imageToEncode == nullptr) {
throw Exception('Failed to prepare image for encoding.'); throw Exception('Failed to prepare image for encoding.');
} }

View File

@ -1,11 +1,21 @@
import 'dart:math';
/// A sealed class representing different ways to resize an image. /// A sealed class representing different ways to resize an image.
sealed class ResizeMode { sealed class ResizeMode {
const 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. /// A resize mode that keeps the original dimensions of the image.
class OriginalResizeMode extends ResizeMode { class OriginalResizeMode extends ResizeMode {
const OriginalResizeMode(); const OriginalResizeMode();
@override
(int, int) calculateSize(int originalWidth, int originalHeight) {
return (originalWidth, originalHeight);
}
} }
/// A resize mode that resizes the image to exact dimensions, /// 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. /// The target height for the resized image.
final int height; 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 /// 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. /// The maximum height for the resized image.
final int? height; 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());
}
} }

View File

@ -59,65 +59,10 @@ final class ImageConverterWeb implements ImageConverterPlatform {
final canvas = HTMLCanvasElement(); final canvas = HTMLCanvasElement();
final int destWidth; final (destWidth, destHeight) = resizeMode.calculateSize(
final int destHeight; img.width,
img.height,
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();
}
canvas.width = destWidth; canvas.width = destWidth;
canvas.height = destHeight; canvas.height = destHeight;

View File

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