1 | // Aseprite Document Library |
2 | // Copyright (c) 2020-2022 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/rotate.h" |
13 | #include "doc/image_impl.h" |
14 | #include "doc/primitives.h" |
15 | |
16 | #include <algorithm> |
17 | #include <memory> |
18 | |
19 | namespace doc { |
20 | namespace algorithm { |
21 | |
22 | // More information about EPX/Scale2x: |
23 | // http://en.wikipedia.org/wiki/Pixel_art_scaling_algorithms#EPX.2FScale2.C3.97.2FAdvMAME2.C3.97 |
24 | // http://scale2x.sourceforge.net/algorithm.html |
25 | // http://scale2x.sourceforge.net/scale2xandepx.html |
26 | template<typename ImageTraits> |
27 | static void image_scale2x_tpl(Image* dst, const Image* src, int src_w, int src_h) |
28 | { |
29 | #if 0 // TODO complete this implementation that should be faster |
30 | // than using a lot of get/put_pixel_fast calls. |
31 | int dst_w = src_w*2; |
32 | int dst_h = src_h*2; |
33 | |
34 | LockImageBits<ImageTraits> dstBits(dst, Image::WriteLock, gfx::Rect(0, 0, dst_w, dst_h)); |
35 | const LockImageBits<ImageTraits> srcBits(src); |
36 | |
37 | LockImageBits<ImageTraits>::iterator dstRow0_it = dstBits.begin(); |
38 | LockImageBits<ImageTraits>::iterator dstRow1_it = dstBits.begin(); |
39 | LockImageBits<ImageTraits>::iterator dstRow0_end = dstBits.end(); |
40 | LockImageBits<ImageTraits>::iterator dstRow1_end = dstBits.end(); |
41 | |
42 | // Iterators: |
43 | // A |
44 | // C P B |
45 | // D |
46 | // |
47 | // These iterators are displaced through src image and are modified in this way: |
48 | // |
49 | // P: is the simplest one, we just start from (0, 0) to srcEnd. |
50 | // A: starts from row 0 (so A = P in the first row), then we start |
51 | // again from the row 0. |
52 | // B: It starts from (1, row) and in the last pixel we don't moved it. |
53 | // C: It starts from (0, 0) and then it is moved with a delay. |
54 | // D: It starts from row 1 and continues until we reach the last |
55 | // row, in that case we start D iterator again. |
56 | // |
57 | LockImageBits<ImageTraits>::const_iterator itP, itA, itB, itC, itD, savedD; |
58 | LockImageBits<ImageTraits>::const_iterator srcEnd = srcBits.end(); |
59 | color_t P, A, B, C, D; |
60 | |
61 | // Adjust iterators |
62 | itP = itA = itB = itC = itD = savedD = srcBits.begin(); |
63 | dstRow1_it += dst_w; |
64 | itD += src->width(); |
65 | |
66 | for (int y=0; y<src_h; ++y) { |
67 | if (y == 1) itA = srcBits.begin(); |
68 | if (y == src_h-2) savedD = itD; |
69 | if (y == src_h-1) itD = savedD; |
70 | ++itB; |
71 | |
72 | for (int x=0; x<src_w; ++x) { |
73 | ASSERT(itP != srcEnd); |
74 | ASSERT(itA != srcEnd); |
75 | ASSERT(itB != srcEnd); |
76 | ASSERT(itC != srcEnd); |
77 | ASSERT(itD != srcEnd); |
78 | ASSERT(dstRow0_it != dstRow0_end); |
79 | ASSERT(dstRow1_it != dstRow1_end); |
80 | |
81 | P = *itP; |
82 | A = *itA; // y-1 |
83 | B = *itB; // x+1 |
84 | C = *itC; // x-1 |
85 | D = *itD; // y+1 |
86 | |
87 | *dstRow0_it = (C == A && C != D && A != B ? A: P); |
88 | ++dstRow0_it; |
89 | *dstRow0_it = (A == B && A != C && B != D ? B: P); |
90 | ++dstRow0_it; |
91 | |
92 | *dstRow1_it = (D == C && D != B && C != A ? C: P); |
93 | ++dstRow1_it; |
94 | *dstRow1_it = (B == D && B != A && D != C ? D: P); |
95 | ++dstRow1_it; |
96 | |
97 | ++itP; |
98 | ++itA; |
99 | if (x < src_w-2) ++itB; |
100 | if (x > 0) ++itC; |
101 | ++itD; |
102 | } |
103 | |
104 | // Adjust iterators for the next two rows. |
105 | ++itB; |
106 | ++itC; |
107 | dstRow0_it += dst_w; |
108 | if (y < src_h-1) |
109 | dstRow1_it += dst_w; |
110 | } |
111 | |
112 | // ASSERT(itP == srcEnd); |
113 | // ASSERT(itA == srcEnd); |
114 | // ASSERT(itB == srcEnd); |
115 | // ASSERT(itC == srcEnd); |
116 | // ASSERT(itD == srcEnd); |
117 | ASSERT(dstRow0_it == dstRow0_end); |
118 | ASSERT(dstRow1_it == dstRow1_end); |
119 | #else |
120 | |
121 | #define A c[0] |
122 | #define B c[1] |
123 | #define C c[2] |
124 | #define D c[3] |
125 | #define P c[4] |
126 | |
127 | LockImageBits<ImageTraits> dstBits(dst, gfx::Rect(0, 0, src_w*2, src_h*2)); |
128 | auto dstIt = dstBits.begin(); |
129 | auto dstIt2 = dstIt; |
130 | |
131 | color_t c[5]; |
132 | for (int y=0; y<src_h; ++y) { |
133 | dstIt2 += src_w*2; |
134 | for (int x=0; x<src_w; ++x) { |
135 | P = get_pixel_fast<ImageTraits>(src, x, y); |
136 | A = (y > 0 ? get_pixel_fast<ImageTraits>(src, x, y-1): P); |
137 | B = (x < src_w-1 ? get_pixel_fast<ImageTraits>(src, x+1, y): P); |
138 | C = (x > 0 ? get_pixel_fast<ImageTraits>(src, x-1, y): P); |
139 | D = (y < src_h-1 ? get_pixel_fast<ImageTraits>(src, x, y+1): P); |
140 | |
141 | *dstIt = (C == A && C != D && A != B ? A: P); |
142 | ++dstIt; |
143 | *dstIt = (A == B && A != C && B != D ? B: P); |
144 | ++dstIt; |
145 | |
146 | *dstIt2 = (D == C && D != B && C != A ? C: P); |
147 | ++dstIt2; |
148 | *dstIt2 = (B == D && B != A && D != C ? D: P); |
149 | ++dstIt2; |
150 | } |
151 | dstIt += src_w*2; |
152 | } |
153 | |
154 | #endif |
155 | } |
156 | |
157 | static void image_scale2x(Image* dst, const Image* src, int src_w, int src_h) |
158 | { |
159 | switch (src->pixelFormat()) { |
160 | case IMAGE_RGB: image_scale2x_tpl<RgbTraits>(dst, src, src_w, src_h); break; |
161 | case IMAGE_GRAYSCALE: image_scale2x_tpl<GrayscaleTraits>(dst, src, src_w, src_h); break; |
162 | case IMAGE_INDEXED: image_scale2x_tpl<IndexedTraits>(dst, src, src_w, src_h); break; |
163 | case IMAGE_BITMAP: image_scale2x_tpl<BitmapTraits>(dst, src, src_w, src_h); break; |
164 | } |
165 | } |
166 | |
167 | void rotsprite_image(Image* bmp, const Image* spr, const Image* mask, |
168 | int x1, int y1, int x2, int y2, |
169 | int x3, int y3, int x4, int y4) |
170 | { |
171 | static ImageBufferPtr buf[3]; // TODO non-thread safe |
172 | |
173 | for (int i=0; i<3; ++i) |
174 | if (!buf[i]) |
175 | buf[i].reset(new ImageBuffer(1)); |
176 | |
177 | int xmin = std::min(x1, std::min(x2, std::min(x3, x4))); |
178 | int xmax = std::max(x1, std::max(x2, std::max(x3, x4))); |
179 | int ymin = std::min(y1, std::min(y2, std::min(y3, y4))); |
180 | int ymax = std::max(y1, std::max(y2, std::max(y3, y4))); |
181 | int rot_width = xmax - xmin; |
182 | int rot_height = ymax - ymin; |
183 | |
184 | if (rot_width == 0 || rot_height == 0) |
185 | return; |
186 | |
187 | int scale = 8; |
188 | std::unique_ptr<Image> bmp_copy(Image::create(bmp->pixelFormat(), rot_width*scale, rot_height*scale, buf[0])); |
189 | std::unique_ptr<Image> tmp_copy(Image::create(spr->pixelFormat(), spr->width()*scale, spr->height()*scale, buf[1])); |
190 | std::unique_ptr<Image> spr_copy(Image::create(spr->pixelFormat(), spr->width()*scale, spr->height()*scale, buf[2])); |
191 | std::unique_ptr<Image> msk_copy; |
192 | |
193 | color_t maskColor = spr->maskColor(); |
194 | |
195 | bmp_copy->setMaskColor(maskColor); |
196 | tmp_copy->setMaskColor(maskColor); |
197 | spr_copy->setMaskColor(maskColor); |
198 | |
199 | spr_copy->clear(maskColor); |
200 | spr_copy->copy(spr, gfx::Clip(spr->bounds())); |
201 | |
202 | for (int i=0; i<3; ++i) { |
203 | // clear_image(tmp_copy, maskColor); |
204 | image_scale2x(tmp_copy.get(), spr_copy.get(), spr->width()*(1<<i), spr->height()*(1<<i)); |
205 | spr_copy->copy(tmp_copy.get(), gfx::Clip(tmp_copy->bounds())); |
206 | } |
207 | |
208 | if (mask) { |
209 | // Same ImageBuffer than tmp_copy |
210 | msk_copy.reset(Image::create(IMAGE_BITMAP, mask->width()*scale, mask->height()*scale, buf[1])); |
211 | clear_image(msk_copy.get(), 0); |
212 | scale_image(msk_copy.get(), mask, |
213 | 0, 0, msk_copy->width(), msk_copy->height(), |
214 | 0, 0, mask->width(), mask->height()); |
215 | } |
216 | |
217 | clear_image(bmp_copy.get(), maskColor); |
218 | parallelogram( |
219 | bmp_copy.get(), spr_copy.get(), msk_copy.get(), |
220 | (x1-xmin)*scale, (y1-ymin)*scale, (x2-xmin)*scale, (y2-ymin)*scale, |
221 | (x3-xmin)*scale, (y3-ymin)*scale, (x4-xmin)*scale, (y4-ymin)*scale); |
222 | |
223 | scale_image(bmp, bmp_copy.get(), |
224 | xmin, ymin, rot_width, rot_height, |
225 | 0, 0, bmp_copy->width(), bmp_copy->height()); |
226 | } |
227 | |
228 | } // namespace algorithm |
229 | } // namespace doc |
230 | |