Skip to content

Commit cf5f6d9

Browse files
Add resize function for in-place resizing
1 parent 12f3e66 commit cf5f6d9

File tree

4 files changed

+370
-2
lines changed

4 files changed

+370
-2
lines changed

lib/image.dart

+1
Original file line numberDiff line numberDiff line change
@@ -227,6 +227,7 @@ export 'src/transform/copy_resize.dart';
227227
export 'src/transform/copy_resize_crop_square.dart';
228228
export 'src/transform/copy_rotate.dart';
229229
export 'src/transform/flip.dart';
230+
export 'src/transform/resize.dart';
230231
export 'src/transform/trim.dart';
231232
export 'src/util/binary_quantizer.dart';
232233
export 'src/util/clip_line.dart';

lib/src/image/image_data.dart

+2-2
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,8 @@ import 'palette.dart';
77
import 'pixel.dart';
88

99
abstract class ImageData extends Iterable<Pixel> {
10-
final int width;
11-
final int height;
10+
int width;
11+
int height;
1212
final int numChannels;
1313

1414
ImageData(this.width, this.height, this.numChannels);

lib/src/transform/resize.dart

+190
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,190 @@
1+
import 'dart:typed_data';
2+
3+
import '../color/color.dart';
4+
import '../image/image.dart';
5+
import '../image/interpolation.dart';
6+
import '../util/image_exception.dart';
7+
import 'bake_orientation.dart';
8+
import 'copy_resize.dart';
9+
10+
Image resize(Image src,
11+
{int? width,
12+
int? height,
13+
bool? maintainAspect,
14+
Color? backgroundColor,
15+
Interpolation interpolation = Interpolation.nearest}) {
16+
if (width == null && height == null) {
17+
throw ImageException('Invalid size');
18+
}
19+
20+
// You can't interpolate index pixels
21+
if (src.hasPalette) {
22+
interpolation = Interpolation.nearest;
23+
}
24+
25+
if (src.exif.imageIfd.hasOrientation && src.exif.imageIfd.orientation != 1) {
26+
src = bakeOrientation(src);
27+
}
28+
29+
var x1 = 0;
30+
var y1 = 0;
31+
var x2 = 0;
32+
var y2 = 0;
33+
34+
// this block sets [width] and [height] if null or negative.
35+
if (width != null && height != null && maintainAspect == true) {
36+
x1 = 0;
37+
x2 = width;
38+
final srcAspect = src.height / src.width;
39+
final h = (width * srcAspect).toInt();
40+
final dy = (height - h) ~/ 2;
41+
y1 = dy;
42+
y2 = y1 + h;
43+
if (y1 < 0 || y2 > height) {
44+
y1 = 0;
45+
y2 = height;
46+
final srcAspect = src.width / src.height;
47+
final w = (height * srcAspect).toInt();
48+
final dx = (width - w) ~/ 2;
49+
x1 = dx;
50+
x2 = x1 + w;
51+
}
52+
} else {
53+
maintainAspect = false;
54+
}
55+
56+
if (height == null || height <= 0) {
57+
height = (width! * (src.height / src.width)).round();
58+
}
59+
if (width == null || width <= 0) {
60+
width = (height * (src.width / src.height)).round();
61+
}
62+
63+
final w = maintainAspect! ? x2 - x1 : width;
64+
final h = maintainAspect ? y2 - y1 : height;
65+
66+
if (!maintainAspect) {
67+
x1 = 0;
68+
x2 = width;
69+
y1 = 0;
70+
y2 = height;
71+
}
72+
73+
if (width == src.width && height == src.height) {
74+
return src;
75+
}
76+
77+
if ((width * height) > (src.width * src.height)) {
78+
return copyResize(src, width: width, height: height,
79+
maintainAspect: maintainAspect, backgroundColor: backgroundColor,
80+
interpolation: interpolation);
81+
}
82+
83+
final scaleX = Int32List(w);
84+
final dx = src.width / w;
85+
for (var x = 0; x < w; ++x) {
86+
scaleX[x] = (x * dx).toInt();
87+
}
88+
89+
final origWidth = src.width;
90+
final origHeight = src.height;
91+
92+
final numFrames = src.numFrames;
93+
for (var i = 0; i < numFrames; ++i) {
94+
final frame = src.frames[i];
95+
final dst = frame;
96+
97+
final dy = frame.height / h;
98+
final dx = frame.width / w;
99+
100+
if (maintainAspect && backgroundColor != null) {
101+
dst.clear(backgroundColor);
102+
}
103+
104+
if (interpolation == Interpolation.average) {
105+
for (var y = 0; y < h; ++y) {
106+
final ay1 = (y * dy).toInt();
107+
var ay2 = ((y + 1) * dy).toInt();
108+
if (ay2 == ay1) {
109+
ay2++;
110+
}
111+
112+
for (var x = 0; x < w; ++x) {
113+
final ax1 = (x * dx).toInt();
114+
var ax2 = ((x + 1) * dx).toInt();
115+
if (ax2 == ax1) {
116+
ax2++;
117+
}
118+
119+
num r = 0;
120+
num g = 0;
121+
num b = 0;
122+
num a = 0;
123+
var np = 0;
124+
for (var sy = ay1; sy < ay2; ++sy) {
125+
for (var sx = ax1; sx < ax2; ++sx, ++np) {
126+
final s = frame.getPixel(sx, sy);
127+
r += s.r;
128+
g += s.g;
129+
b += s.b;
130+
a += s.a;
131+
}
132+
}
133+
final c = dst.getColor(r / np, g / np, b / np, a / np);
134+
135+
dst.data!.width = width;
136+
dst.data!.height = height;
137+
dst.setPixel(x1 + x, y1 + y, c);
138+
dst.data!.width = origWidth;
139+
dst.data!.height = origHeight;
140+
}
141+
}
142+
} else if (interpolation == Interpolation.nearest) {
143+
if (frame.hasPalette) {
144+
for (var y = 0; y < h; ++y) {
145+
final y2 = (y * dy).toInt();
146+
for (var x = 0; x < w; ++x) {
147+
final p = frame.getPixelIndex(scaleX[x], y2);
148+
dst.data!.width = width;
149+
dst.data!.height = height;
150+
dst.setPixelIndex(x1 + x, y1 + y, p);
151+
dst.data!.width = origWidth;
152+
dst.data!.height = origHeight;
153+
}
154+
}
155+
} else {
156+
for (var y = 0; y < h; ++y) {
157+
final y2 = (y * dy).toInt();
158+
for (var x = 0; x < w; ++x) {
159+
final p = frame.getPixel(scaleX[x], y2);
160+
dst.data!.width = width;
161+
dst.data!.height = height;
162+
dst.setPixel(x1 + x, y1 + y, p);
163+
dst.data!.width = origWidth;
164+
dst.data!.height = origHeight;
165+
}
166+
}
167+
}
168+
} else {
169+
// Copy the pixels from this image to the new image.
170+
for (var y = 0; y < h; ++y) {
171+
final sy2 = y * dy;
172+
for (var x = 0; x < w; ++x) {
173+
final sx2 = x * dx;
174+
final p = frame.getPixelInterpolate(x1 + sx2, y1 + sy2,
175+
interpolation: interpolation);
176+
dst.data!.width = width;
177+
dst.data!.height = height;
178+
dst.setPixel(x, y, p);
179+
dst.data!.width = origWidth;
180+
dst.data!.height = origHeight;
181+
}
182+
}
183+
}
184+
185+
dst.data!.width = width;
186+
dst.data!.height = height;
187+
}
188+
189+
return src;
190+
}

test/transform/resize_test.dart

+177
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,177 @@
1+
import 'dart:io';
2+
import 'package:image/image.dart';
3+
import 'package:test/test.dart';
4+
5+
import '../_test_util.dart';
6+
7+
void main() {
8+
group('Transform', () {
9+
test('resize nearest', () {
10+
final img =
11+
decodePng(File('test/_data/png/buck_24.png').readAsBytesSync())!;
12+
final i0 = resize(img, width: 64);
13+
expect(i0.width, equals(64));
14+
expect(i0.height, equals(40));
15+
File('$testOutputPath/transform/resize.png')
16+
..createSync(recursive: true)
17+
..writeAsBytesSync(encodePng(i0));
18+
});
19+
20+
test('resize average', () {
21+
final img =
22+
decodePng(File('test/_data/png/buck_24.png').readAsBytesSync())!;
23+
final i0 =
24+
resize(img, width: 64, interpolation: Interpolation.average);
25+
expect(i0.width, equals(64));
26+
expect(i0.height, equals(40));
27+
File('$testOutputPath/transform/resize_average.png')
28+
..createSync(recursive: true)
29+
..writeAsBytesSync(encodePng(i0));
30+
});
31+
32+
test('resize linear', () {
33+
final img =
34+
decodePng(File('test/_data/png/buck_24.png').readAsBytesSync())!;
35+
final i0 =
36+
resize(img, width: 64, interpolation: Interpolation.linear);
37+
expect(i0.width, equals(64));
38+
expect(i0.height, equals(40));
39+
File('$testOutputPath/transform/resize_linear.png')
40+
..createSync(recursive: true)
41+
..writeAsBytesSync(encodePng(i0));
42+
});
43+
44+
test('resize cubic', () {
45+
final img =
46+
decodePng(File('test/_data/png/buck_24.png').readAsBytesSync())!;
47+
final i0 = resize(img, width: 64, interpolation: Interpolation.cubic);
48+
expect(i0.width, equals(64));
49+
expect(i0.height, equals(40));
50+
File('$testOutputPath/transform/resize_cubic.png')
51+
..createSync(recursive: true)
52+
..writeAsBytesSync(encodePng(i0));
53+
});
54+
55+
test('resize maintainAspect', () {
56+
final img =
57+
decodePng(File('test/_data/png/buck_24.png').readAsBytesSync())!;
58+
final i0 = resize(img,
59+
width: 640,
60+
height: 640,
61+
maintainAspect: true,
62+
backgroundColor: ColorRgb8(0, 0, 255));
63+
expect(i0.width, equals(640));
64+
expect(i0.height, equals(640));
65+
File('$testOutputPath/transform/resize_maintainAspect.png')
66+
..createSync(recursive: true)
67+
..writeAsBytesSync(encodePng(i0));
68+
});
69+
70+
test('resize maintainAspect palette', () {
71+
final img =
72+
decodePng(File('test/_data/png/buck_8.png').readAsBytesSync())!;
73+
final i0 = resize(img,
74+
width: 640,
75+
height: 640,
76+
maintainAspect: true,
77+
backgroundColor: ColorRgb8(0, 0, 255));
78+
expect(i0.width, equals(640));
79+
expect(i0.height, equals(640));
80+
File('$testOutputPath/transform/resize_maintainAspect_palette.png')
81+
..createSync(recursive: true)
82+
..writeAsBytesSync(encodePng(i0));
83+
});
84+
85+
test('resize maintainAspect 2', () {
86+
final i0 = Image(width: 100, height: 50)..clear(ColorRgb8(255, 0, 0));
87+
final i1 = resize(i0,
88+
width: 200,
89+
height: 200,
90+
maintainAspect: true,
91+
backgroundColor: ColorRgb8(0, 0, 255));
92+
expect(i1.width, equals(200));
93+
expect(i1.height, equals(200));
94+
File('$testOutputPath/transform/resize_maintainAspect_2.png')
95+
..createSync(recursive: true)
96+
..writeAsBytesSync(encodePng(i1));
97+
});
98+
99+
test('resize maintainAspect 3', () {
100+
final i0 = Image(width: 50, height: 100)..clear(ColorRgb8(0, 255, 0));
101+
final i1 = resize(i0,
102+
width: 200,
103+
height: 200,
104+
maintainAspect: true,
105+
backgroundColor: ColorRgb8(0, 0, 255));
106+
expect(i1.width, equals(200));
107+
expect(i1.height, equals(200));
108+
File('$testOutputPath/transform/resize_maintainAspect_3.png')
109+
..createSync(recursive: true)
110+
..writeAsBytesSync(encodePng(i1));
111+
});
112+
113+
test('resize maintainAspect 4', () {
114+
final i0 = Image(width: 100, height: 50)..clear(ColorRgb8(255, 0, 0));
115+
final i1 = resize(i0,
116+
width: 50,
117+
height: 100,
118+
maintainAspect: true,
119+
backgroundColor: ColorRgb8(0, 0, 255));
120+
expect(i1.width, equals(50));
121+
expect(i1.height, equals(100));
122+
File('$testOutputPath/transform/resize_maintainAspect_4.png')
123+
..createSync(recursive: true)
124+
..writeAsBytesSync(encodePng(i1));
125+
});
126+
127+
test('resize maintainAspect 5', () {
128+
final i0 = Image(width: 50, height: 100)..clear(ColorRgb8(0, 255, 0));
129+
final i1 = resize(i0,
130+
width: 100,
131+
height: 50,
132+
maintainAspect: true,
133+
backgroundColor: ColorRgb8(0, 0, 255));
134+
expect(i1.width, equals(100));
135+
expect(i1.height, equals(50));
136+
File('$testOutputPath/transform/resize_maintainAspect_5.png')
137+
..createSync(recursive: true)
138+
..writeAsBytesSync(encodePng(i1));
139+
});
140+
141+
test('resize maintainAspect 5', () {
142+
final i0 = Image(width: 50, height: 100)..clear(ColorRgb8(0, 255, 0));
143+
final i1 = resize(i0,
144+
width: 100,
145+
height: 500,
146+
maintainAspect: true,
147+
backgroundColor: ColorRgb8(0, 0, 255));
148+
expect(i1.width, equals(100));
149+
expect(i1.height, equals(500));
150+
File('$testOutputPath/transform/resize_maintainAspect_5.png')
151+
..createSync(recursive: true)
152+
..writeAsBytesSync(encodePng(i1));
153+
});
154+
155+
test('resize maintainAspect 6', () {
156+
final i0 = Image(width: 100, height: 50)..clear(ColorRgb8(0, 255, 0));
157+
final i1 = resize(i0,
158+
width: 500,
159+
height: 100,
160+
maintainAspect: true,
161+
backgroundColor: ColorRgb8(0, 0, 255));
162+
expect(i1.width, equals(500));
163+
expect(i1.height, equals(100));
164+
File('$testOutputPath/transform/resize_maintainAspect_6.png')
165+
..createSync(recursive: true)
166+
..writeAsBytesSync(encodePng(i1));
167+
});
168+
169+
test('resize palette', () async {
170+
final img = await decodePngFile('test/_data/png/test.png');
171+
final i0 =
172+
resize(img!, width: 64, interpolation: Interpolation.cubic);
173+
await encodePngFile(
174+
'$testOutputPath/transform/resize_palette.png', i0);
175+
});
176+
});
177+
}

0 commit comments

Comments
 (0)