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
19namespace doc {
20namespace 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
26template<typename ImageTraits>
27static 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
157static 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
167void 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