1 | // Aseprite Document Library |
2 | // Copyright (c) 2019 Igara Studio S.A. |
3 | // Copyright (c) 2001-2018 David Capello |
4 | // |
5 | // This file is released under the terms of the MIT license. |
6 | // Read LICENSE.txt for more information. |
7 | |
8 | #ifdef HAVE_CONFIG_H |
9 | #include "config.h" |
10 | #endif |
11 | |
12 | #include "doc/algorithm/resize_image.h" |
13 | |
14 | #include "doc/algorithm/rotsprite.h" |
15 | #include "doc/image_impl.h" |
16 | #include "doc/palette.h" |
17 | #include "doc/primitives_fast.h" |
18 | #include "doc/rgbmap.h" |
19 | #include "gfx/point.h" |
20 | |
21 | #include <cmath> |
22 | |
23 | namespace doc { |
24 | namespace algorithm { |
25 | |
26 | template<typename ImageTraits> |
27 | void resize_image_nearest(const Image* src, Image* dst) |
28 | { |
29 | double x_ratio = double(src->width()) / double(dst->width()); |
30 | double y_ratio = double(src->height()) / double(dst->height()); |
31 | double px, py; |
32 | |
33 | LockImageBits<ImageTraits> dstBits(dst); |
34 | auto dstIt = dstBits.begin(); |
35 | |
36 | for (int y=0; y<dst->height(); ++y) { |
37 | py = std::floor(y * y_ratio); |
38 | for (int x=0; x<dst->width(); ++x, ++dstIt) { |
39 | px = std::floor(x * x_ratio); |
40 | *dstIt = get_pixel_fast<ImageTraits>(src, int(px), int(py)); |
41 | } |
42 | } |
43 | } |
44 | |
45 | void resize_image(const Image* src, |
46 | Image* dst, |
47 | const ResizeMethod method, |
48 | const Palette* pal, |
49 | const RgbMap* rgbmap, |
50 | const color_t maskColor) |
51 | { |
52 | switch (method) { |
53 | |
54 | // TODO optimize this |
55 | case RESIZE_METHOD_NEAREST_NEIGHBOR: { |
56 | ASSERT(src->pixelFormat() == dst->pixelFormat()); |
57 | |
58 | switch (src->pixelFormat()) { |
59 | case IMAGE_RGB: resize_image_nearest<RgbTraits>(src, dst); break; |
60 | case IMAGE_GRAYSCALE: resize_image_nearest<GrayscaleTraits>(src, dst); break; |
61 | case IMAGE_INDEXED: resize_image_nearest<IndexedTraits>(src, dst); break; |
62 | case IMAGE_BITMAP: resize_image_nearest<BitmapTraits>(src, dst); break; |
63 | } |
64 | break; |
65 | } |
66 | |
67 | // TODO optimize this |
68 | case RESIZE_METHOD_BILINEAR: { |
69 | uint32_t color[4], dst_color = 0; |
70 | double u, v, du, dv; |
71 | int u_floor, u_floor2; |
72 | int v_floor, v_floor2; |
73 | int x, y; |
74 | |
75 | // We cannot do interpolations between RGB values on indexed |
76 | // images without a palette/rgbmap. |
77 | if (dst->pixelFormat() == IMAGE_INDEXED && |
78 | (!pal || !rgbmap)) { |
79 | resize_image( |
80 | src, dst, |
81 | RESIZE_METHOD_NEAREST_NEIGHBOR, |
82 | pal, rgbmap, maskColor); |
83 | return; |
84 | } |
85 | |
86 | u = v = 0.0; |
87 | du = (src->width()-1) * 1.0 / (dst->width()-1); |
88 | dv = (src->height()-1) * 1.0 / (dst->height()-1); |
89 | for (y=0; y<dst->height(); ++y) { |
90 | for (x=0; x<dst->width(); ++x) { |
91 | u_floor = (int)std::floor(u); |
92 | v_floor = (int)std::floor(v); |
93 | |
94 | if (u_floor > src->width()-1) { |
95 | u_floor = src->width()-1; |
96 | u_floor2 = src->width()-1; |
97 | } |
98 | else if (u_floor == src->width()-1) |
99 | u_floor2 = u_floor; |
100 | else |
101 | u_floor2 = u_floor+1; |
102 | |
103 | if (v_floor > src->height()-1) { |
104 | v_floor = src->height()-1; |
105 | v_floor2 = src->height()-1; |
106 | } |
107 | else if (v_floor == src->height()-1) |
108 | v_floor2 = v_floor; |
109 | else |
110 | v_floor2 = v_floor+1; |
111 | |
112 | // get the four colors |
113 | color[0] = src->getPixel(u_floor, v_floor); |
114 | color[1] = src->getPixel(u_floor2, v_floor); |
115 | color[2] = src->getPixel(u_floor, v_floor2); |
116 | color[3] = src->getPixel(u_floor2, v_floor2); |
117 | |
118 | // calculate the interpolated color |
119 | double u1 = u - u_floor; |
120 | double v1 = v - v_floor; |
121 | double u2 = 1 - u1; |
122 | double v2 = 1 - v1; |
123 | |
124 | switch (dst->pixelFormat()) { |
125 | case IMAGE_RGB: { |
126 | int r = int((rgba_getr(color[0])*u2 + rgba_getr(color[1])*u1)*v2 + |
127 | (rgba_getr(color[2])*u2 + rgba_getr(color[3])*u1)*v1); |
128 | int g = int((rgba_getg(color[0])*u2 + rgba_getg(color[1])*u1)*v2 + |
129 | (rgba_getg(color[2])*u2 + rgba_getg(color[3])*u1)*v1); |
130 | int b = int((rgba_getb(color[0])*u2 + rgba_getb(color[1])*u1)*v2 + |
131 | (rgba_getb(color[2])*u2 + rgba_getb(color[3])*u1)*v1); |
132 | int a = int((rgba_geta(color[0])*u2 + rgba_geta(color[1])*u1)*v2 + |
133 | (rgba_geta(color[2])*u2 + rgba_geta(color[3])*u1)*v1); |
134 | dst_color = rgba(r, g, b, a); |
135 | break; |
136 | } |
137 | case IMAGE_GRAYSCALE: { |
138 | int v = int((graya_getv(color[0])*u2 + graya_getv(color[1])*u1)*v2 + |
139 | (graya_getv(color[2])*u2 + graya_getv(color[3])*u1)*v1); |
140 | int a = int((graya_geta(color[0])*u2 + graya_geta(color[1])*u1)*v2 + |
141 | (graya_geta(color[2])*u2 + graya_geta(color[3])*u1)*v1); |
142 | dst_color = graya(v, a); |
143 | break; |
144 | } |
145 | case IMAGE_INDEXED: { |
146 | // Convert index to RGBA values |
147 | for (int i=0; i<4; ++i) { |
148 | if (color[i] == maskColor) |
149 | color[i] = pal->getEntry(color[i]) & rgba_rgb_mask; // Set alpha = 0 |
150 | else |
151 | color[i] = pal->getEntry(color[i]); |
152 | } |
153 | |
154 | int r = int((rgba_getr(color[0])*u2 + rgba_getr(color[1])*u1)*v2 + |
155 | (rgba_getr(color[2])*u2 + rgba_getr(color[3])*u1)*v1); |
156 | int g = int((rgba_getg(color[0])*u2 + rgba_getg(color[1])*u1)*v2 + |
157 | (rgba_getg(color[2])*u2 + rgba_getg(color[3])*u1)*v1); |
158 | int b = int((rgba_getb(color[0])*u2 + rgba_getb(color[1])*u1)*v2 + |
159 | (rgba_getb(color[2])*u2 + rgba_getb(color[3])*u1)*v1); |
160 | int a = int((rgba_geta(color[0])*u2 + rgba_geta(color[1])*u1)*v2 + |
161 | (rgba_geta(color[2])*u2 + rgba_geta(color[3])*u1)*v1); |
162 | dst_color = rgbmap->mapColor(r, g, b, a); |
163 | break; |
164 | } |
165 | } |
166 | |
167 | dst->putPixel(x, y, dst_color); |
168 | u += du; |
169 | } |
170 | u = 0.0; |
171 | v += dv; |
172 | } |
173 | break; |
174 | } |
175 | |
176 | case RESIZE_METHOD_ROTSPRITE: { |
177 | rotsprite_image( |
178 | dst, src, nullptr, |
179 | 0, 0, |
180 | dst->width(), 0, |
181 | dst->width(), dst->height(), |
182 | 0, dst->height()); |
183 | break; |
184 | } |
185 | |
186 | } |
187 | } |
188 | |
189 | void fixup_image_transparent_colors(Image* image) |
190 | { |
191 | int x, y; |
192 | |
193 | switch (image->pixelFormat()) { |
194 | |
195 | case IMAGE_RGB: { |
196 | int r, g, b, count; |
197 | LockImageBits<RgbTraits> bits(image); |
198 | LockImageBits<RgbTraits>::iterator it = bits.begin(); |
199 | |
200 | for (y=0; y<image->height(); ++y) { |
201 | for (x=0; x<image->width(); ++x, ++it) { |
202 | uint32_t c = *it; |
203 | |
204 | // if this is a completely-transparent pixel... |
205 | if (rgba_geta(c) == 0) { |
206 | count = 0; |
207 | r = g = b = 0; |
208 | |
209 | gfx::Rect area = gfx::Rect(x-1, y-1, 3, 3).createIntersection(image->bounds()); |
210 | LockImageBits<RgbTraits>::iterator it2 = bits.begin_area(area); |
211 | LockImageBits<RgbTraits>::iterator end2 = bits.end_area(area); |
212 | |
213 | for (; it2 != end2; ++it2) { |
214 | c = *it2; |
215 | if (rgba_geta(c) > 0) { |
216 | r += rgba_getr(c); |
217 | g += rgba_getg(c); |
218 | b += rgba_getb(c); |
219 | ++count; |
220 | } |
221 | } |
222 | |
223 | if (count > 0) { |
224 | r /= count; |
225 | g /= count; |
226 | b /= count; |
227 | *it = rgba(r, g, b, 0); |
228 | } |
229 | } |
230 | } |
231 | } |
232 | break; |
233 | } |
234 | |
235 | case IMAGE_GRAYSCALE: { |
236 | int k, count; |
237 | LockImageBits<GrayscaleTraits> bits(image); |
238 | LockImageBits<GrayscaleTraits>::iterator it = bits.begin(); |
239 | |
240 | for (y=0; y<image->height(); ++y) { |
241 | for (x=0; x<image->width(); ++x, ++it) { |
242 | uint16_t c = *it; |
243 | |
244 | // If this is a completely-transparent pixel... |
245 | if (graya_geta(c) == 0) { |
246 | count = 0; |
247 | k = 0; |
248 | |
249 | gfx::Rect area = gfx::Rect(x-1, y-1, 3, 3).createIntersection(image->bounds()); |
250 | LockImageBits<GrayscaleTraits>::iterator it2 = bits.begin_area(area); |
251 | LockImageBits<GrayscaleTraits>::iterator end2 = bits.end_area(area); |
252 | |
253 | for (; it2 != end2; ++it2) { |
254 | c = *it2; |
255 | if (graya_geta(c) > 0) { |
256 | k += graya_getv(c); |
257 | ++count; |
258 | } |
259 | } |
260 | |
261 | if (count > 0) { |
262 | k /= count; |
263 | *it = graya(k, 0); |
264 | } |
265 | } |
266 | } |
267 | } |
268 | break; |
269 | } |
270 | |
271 | } |
272 | } |
273 | |
274 | } // namespace algorithm |
275 | } // namespace doc |
276 | |