Skip to content

Commit

Permalink
Add resize function for in-place resizing
Browse files Browse the repository at this point in the history
  • Loading branch information
brendan-duncan committed Jan 16, 2025
1 parent 12f3e66 commit cf5f6d9
Show file tree
Hide file tree
Showing 4 changed files with 370 additions and 2 deletions.
1 change: 1 addition & 0 deletions lib/image.dart
Original file line number Diff line number Diff line change
Expand Up @@ -227,6 +227,7 @@ export 'src/transform/copy_resize.dart';
export 'src/transform/copy_resize_crop_square.dart';
export 'src/transform/copy_rotate.dart';
export 'src/transform/flip.dart';
export 'src/transform/resize.dart';
export 'src/transform/trim.dart';
export 'src/util/binary_quantizer.dart';
export 'src/util/clip_line.dart';
Expand Down
4 changes: 2 additions & 2 deletions lib/src/image/image_data.dart
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ import 'palette.dart';
import 'pixel.dart';

abstract class ImageData extends Iterable<Pixel> {
final int width;
final int height;
int width;
int height;
final int numChannels;

ImageData(this.width, this.height, this.numChannels);
Expand Down
190 changes: 190 additions & 0 deletions lib/src/transform/resize.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,190 @@
import 'dart:typed_data';

import '../color/color.dart';
import '../image/image.dart';
import '../image/interpolation.dart';
import '../util/image_exception.dart';
import 'bake_orientation.dart';
import 'copy_resize.dart';

Image resize(Image src,
{int? width,
int? height,
bool? maintainAspect,
Color? backgroundColor,
Interpolation interpolation = Interpolation.nearest}) {
if (width == null && height == null) {
throw ImageException('Invalid size');
}

// You can't interpolate index pixels
if (src.hasPalette) {
interpolation = Interpolation.nearest;
}

if (src.exif.imageIfd.hasOrientation && src.exif.imageIfd.orientation != 1) {
src = bakeOrientation(src);
}

var x1 = 0;
var y1 = 0;
var x2 = 0;
var y2 = 0;

// this block sets [width] and [height] if null or negative.
if (width != null && height != null && maintainAspect == true) {
x1 = 0;
x2 = width;
final srcAspect = src.height / src.width;
final h = (width * srcAspect).toInt();
final dy = (height - h) ~/ 2;
y1 = dy;
y2 = y1 + h;
if (y1 < 0 || y2 > height) {
y1 = 0;
y2 = height;
final srcAspect = src.width / src.height;
final w = (height * srcAspect).toInt();
final dx = (width - w) ~/ 2;
x1 = dx;
x2 = x1 + w;
}
} else {
maintainAspect = false;
}

if (height == null || height <= 0) {
height = (width! * (src.height / src.width)).round();
}
if (width == null || width <= 0) {
width = (height * (src.width / src.height)).round();
}

final w = maintainAspect! ? x2 - x1 : width;
final h = maintainAspect ? y2 - y1 : height;

if (!maintainAspect) {
x1 = 0;
x2 = width;
y1 = 0;
y2 = height;
}

if (width == src.width && height == src.height) {
return src;
}

if ((width * height) > (src.width * src.height)) {
return copyResize(src, width: width, height: height,
maintainAspect: maintainAspect, backgroundColor: backgroundColor,
interpolation: interpolation);
}

final scaleX = Int32List(w);
final dx = src.width / w;
for (var x = 0; x < w; ++x) {
scaleX[x] = (x * dx).toInt();
}

final origWidth = src.width;
final origHeight = src.height;

final numFrames = src.numFrames;
for (var i = 0; i < numFrames; ++i) {
final frame = src.frames[i];
final dst = frame;

final dy = frame.height / h;
final dx = frame.width / w;

if (maintainAspect && backgroundColor != null) {
dst.clear(backgroundColor);
}

if (interpolation == Interpolation.average) {
for (var y = 0; y < h; ++y) {
final ay1 = (y * dy).toInt();
var ay2 = ((y + 1) * dy).toInt();
if (ay2 == ay1) {
ay2++;
}

for (var x = 0; x < w; ++x) {
final ax1 = (x * dx).toInt();
var ax2 = ((x + 1) * dx).toInt();
if (ax2 == ax1) {
ax2++;
}

num r = 0;
num g = 0;
num b = 0;
num a = 0;
var np = 0;
for (var sy = ay1; sy < ay2; ++sy) {
for (var sx = ax1; sx < ax2; ++sx, ++np) {
final s = frame.getPixel(sx, sy);
r += s.r;
g += s.g;
b += s.b;
a += s.a;
}
}
final c = dst.getColor(r / np, g / np, b / np, a / np);

dst.data!.width = width;
dst.data!.height = height;
dst.setPixel(x1 + x, y1 + y, c);
dst.data!.width = origWidth;
dst.data!.height = origHeight;
}
}
} else if (interpolation == Interpolation.nearest) {
if (frame.hasPalette) {
for (var y = 0; y < h; ++y) {
final y2 = (y * dy).toInt();
for (var x = 0; x < w; ++x) {
final p = frame.getPixelIndex(scaleX[x], y2);
dst.data!.width = width;
dst.data!.height = height;
dst.setPixelIndex(x1 + x, y1 + y, p);
dst.data!.width = origWidth;
dst.data!.height = origHeight;
}
}
} else {
for (var y = 0; y < h; ++y) {
final y2 = (y * dy).toInt();
for (var x = 0; x < w; ++x) {
final p = frame.getPixel(scaleX[x], y2);
dst.data!.width = width;
dst.data!.height = height;
dst.setPixel(x1 + x, y1 + y, p);
dst.data!.width = origWidth;
dst.data!.height = origHeight;
}
}
}
} else {
// Copy the pixels from this image to the new image.
for (var y = 0; y < h; ++y) {
final sy2 = y * dy;
for (var x = 0; x < w; ++x) {
final sx2 = x * dx;
final p = frame.getPixelInterpolate(x1 + sx2, y1 + sy2,
interpolation: interpolation);
dst.data!.width = width;
dst.data!.height = height;
dst.setPixel(x, y, p);
dst.data!.width = origWidth;
dst.data!.height = origHeight;
}
}
}

dst.data!.width = width;
dst.data!.height = height;
}

return src;
}
177 changes: 177 additions & 0 deletions test/transform/resize_test.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
import 'dart:io';
import 'package:image/image.dart';
import 'package:test/test.dart';

import '../_test_util.dart';

void main() {
group('Transform', () {
test('resize nearest', () {
final img =
decodePng(File('test/_data/png/buck_24.png').readAsBytesSync())!;
final i0 = resize(img, width: 64);
expect(i0.width, equals(64));
expect(i0.height, equals(40));
File('$testOutputPath/transform/resize.png')
..createSync(recursive: true)
..writeAsBytesSync(encodePng(i0));
});

test('resize average', () {
final img =
decodePng(File('test/_data/png/buck_24.png').readAsBytesSync())!;
final i0 =
resize(img, width: 64, interpolation: Interpolation.average);
expect(i0.width, equals(64));
expect(i0.height, equals(40));
File('$testOutputPath/transform/resize_average.png')
..createSync(recursive: true)
..writeAsBytesSync(encodePng(i0));
});

test('resize linear', () {
final img =
decodePng(File('test/_data/png/buck_24.png').readAsBytesSync())!;
final i0 =
resize(img, width: 64, interpolation: Interpolation.linear);
expect(i0.width, equals(64));
expect(i0.height, equals(40));
File('$testOutputPath/transform/resize_linear.png')
..createSync(recursive: true)
..writeAsBytesSync(encodePng(i0));
});

test('resize cubic', () {
final img =
decodePng(File('test/_data/png/buck_24.png').readAsBytesSync())!;
final i0 = resize(img, width: 64, interpolation: Interpolation.cubic);
expect(i0.width, equals(64));
expect(i0.height, equals(40));
File('$testOutputPath/transform/resize_cubic.png')
..createSync(recursive: true)
..writeAsBytesSync(encodePng(i0));
});

test('resize maintainAspect', () {
final img =
decodePng(File('test/_data/png/buck_24.png').readAsBytesSync())!;
final i0 = resize(img,
width: 640,
height: 640,
maintainAspect: true,
backgroundColor: ColorRgb8(0, 0, 255));
expect(i0.width, equals(640));
expect(i0.height, equals(640));
File('$testOutputPath/transform/resize_maintainAspect.png')
..createSync(recursive: true)
..writeAsBytesSync(encodePng(i0));
});

test('resize maintainAspect palette', () {
final img =
decodePng(File('test/_data/png/buck_8.png').readAsBytesSync())!;
final i0 = resize(img,
width: 640,
height: 640,
maintainAspect: true,
backgroundColor: ColorRgb8(0, 0, 255));
expect(i0.width, equals(640));
expect(i0.height, equals(640));
File('$testOutputPath/transform/resize_maintainAspect_palette.png')
..createSync(recursive: true)
..writeAsBytesSync(encodePng(i0));
});

test('resize maintainAspect 2', () {
final i0 = Image(width: 100, height: 50)..clear(ColorRgb8(255, 0, 0));
final i1 = resize(i0,
width: 200,
height: 200,
maintainAspect: true,
backgroundColor: ColorRgb8(0, 0, 255));
expect(i1.width, equals(200));
expect(i1.height, equals(200));
File('$testOutputPath/transform/resize_maintainAspect_2.png')
..createSync(recursive: true)
..writeAsBytesSync(encodePng(i1));
});

test('resize maintainAspect 3', () {
final i0 = Image(width: 50, height: 100)..clear(ColorRgb8(0, 255, 0));
final i1 = resize(i0,
width: 200,
height: 200,
maintainAspect: true,
backgroundColor: ColorRgb8(0, 0, 255));
expect(i1.width, equals(200));
expect(i1.height, equals(200));
File('$testOutputPath/transform/resize_maintainAspect_3.png')
..createSync(recursive: true)
..writeAsBytesSync(encodePng(i1));
});

test('resize maintainAspect 4', () {
final i0 = Image(width: 100, height: 50)..clear(ColorRgb8(255, 0, 0));
final i1 = resize(i0,
width: 50,
height: 100,
maintainAspect: true,
backgroundColor: ColorRgb8(0, 0, 255));
expect(i1.width, equals(50));
expect(i1.height, equals(100));
File('$testOutputPath/transform/resize_maintainAspect_4.png')
..createSync(recursive: true)
..writeAsBytesSync(encodePng(i1));
});

test('resize maintainAspect 5', () {
final i0 = Image(width: 50, height: 100)..clear(ColorRgb8(0, 255, 0));
final i1 = resize(i0,
width: 100,
height: 50,
maintainAspect: true,
backgroundColor: ColorRgb8(0, 0, 255));
expect(i1.width, equals(100));
expect(i1.height, equals(50));
File('$testOutputPath/transform/resize_maintainAspect_5.png')
..createSync(recursive: true)
..writeAsBytesSync(encodePng(i1));
});

test('resize maintainAspect 5', () {
final i0 = Image(width: 50, height: 100)..clear(ColorRgb8(0, 255, 0));
final i1 = resize(i0,
width: 100,
height: 500,
maintainAspect: true,
backgroundColor: ColorRgb8(0, 0, 255));
expect(i1.width, equals(100));
expect(i1.height, equals(500));
File('$testOutputPath/transform/resize_maintainAspect_5.png')
..createSync(recursive: true)
..writeAsBytesSync(encodePng(i1));
});

test('resize maintainAspect 6', () {
final i0 = Image(width: 100, height: 50)..clear(ColorRgb8(0, 255, 0));
final i1 = resize(i0,
width: 500,
height: 100,
maintainAspect: true,
backgroundColor: ColorRgb8(0, 0, 255));
expect(i1.width, equals(500));
expect(i1.height, equals(100));
File('$testOutputPath/transform/resize_maintainAspect_6.png')
..createSync(recursive: true)
..writeAsBytesSync(encodePng(i1));
});

test('resize palette', () async {
final img = await decodePngFile('test/_data/png/test.png');
final i0 =
resize(img!, width: 64, interpolation: Interpolation.cubic);
await encodePngFile(
'$testOutputPath/transform/resize_palette.png', i0);
});
});
}

0 comments on commit cf5f6d9

Please sign in to comment.