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
23namespace doc {
24namespace algorithm {
25
26template<typename ImageTraits>
27void 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
45void 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
189void 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