| 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 | |