1// Aseprite Document Library
2// Copyright (c) 2018-2021 Igara Studio S.A.
3// Copyright (c) 2001-2016 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/primitives.h"
13
14#include "doc/algo.h"
15#include "doc/brush.h"
16#include "doc/image_impl.h"
17#include "doc/palette.h"
18#include "doc/remap.h"
19#include "doc/rgbmap.h"
20#include "doc/tile.h"
21#include "gfx/region.h"
22
23#include <city.h>
24
25#include <stdexcept>
26
27namespace doc {
28
29color_t get_pixel(const Image* image, int x, int y)
30{
31 ASSERT(image);
32
33 if ((x >= 0) && (y >= 0) && (x < image->width()) && (y < image->height()))
34 return image->getPixel(x, y);
35 else
36 return -1;
37}
38
39void put_pixel(Image* image, int x, int y, color_t color)
40{
41 ASSERT(image);
42
43 if ((x >= 0) && (y >= 0) && (x < image->width()) && (y < image->height()))
44 image->putPixel(x, y, color);
45}
46
47void clear_image(Image* image, color_t color)
48{
49 ASSERT(image);
50
51 image->clear(color);
52}
53
54void copy_image(Image* dst, const Image* src)
55{
56 ASSERT(dst);
57 ASSERT(src);
58
59 dst->copy(src, gfx::Clip(0, 0, 0, 0, src->width(), src->height()));
60}
61
62void copy_image(Image* dst, const Image* src, int x, int y)
63{
64 ASSERT(dst);
65 ASSERT(src);
66
67 dst->copy(src, gfx::Clip(x, y, 0, 0, src->width(), src->height()));
68}
69
70void copy_image(Image* dst, const Image* src, const gfx::Region& rgn)
71{
72 for (const gfx::Rect& rc : rgn)
73 dst->copy(src, gfx::Clip(rc));
74}
75
76Image* crop_image(const Image* image, int x, int y, int w, int h, color_t bg, const ImageBufferPtr& buffer)
77{
78 ASSERT(image);
79
80 if (w < 1) throw std::invalid_argument("crop_image: Width is less than 1");
81 if (h < 1) throw std::invalid_argument("crop_image: Height is less than 1");
82
83 Image* trim = Image::create(image->pixelFormat(), w, h, buffer);
84 trim->setMaskColor(image->maskColor());
85
86 clear_image(trim, bg);
87 trim->copy(image, gfx::Clip(0, 0, x, y, w, h));
88
89 return trim;
90}
91
92Image* crop_image(const Image* image, const gfx::Rect& bounds, color_t bg, const ImageBufferPtr& buffer)
93{
94 return crop_image(image, bounds.x, bounds.y, bounds.w, bounds.h, bg, buffer);
95}
96
97void rotate_image(const Image* src, Image* dst, int angle)
98{
99 ASSERT(src);
100 ASSERT(dst);
101 int x, y;
102
103 switch (angle) {
104
105 case 180:
106 ASSERT(dst->width() == src->width());
107 ASSERT(dst->height() == src->height());
108
109 for (y=0; y<src->height(); ++y)
110 for (x=0; x<src->width(); ++x)
111 dst->putPixel(src->width() - x - 1,
112 src->height() - y - 1, src->getPixel(x, y));
113 break;
114
115 case 90:
116 ASSERT(dst->width() == src->height());
117 ASSERT(dst->height() == src->width());
118
119 for (y=0; y<src->height(); ++y)
120 for (x=0; x<src->width(); ++x)
121 dst->putPixel(src->height() - y - 1, x, src->getPixel(x, y));
122 break;
123
124 case -90:
125 ASSERT(dst->width() == src->height());
126 ASSERT(dst->height() == src->width());
127
128 for (y=0; y<src->height(); ++y)
129 for (x=0; x<src->width(); ++x)
130 dst->putPixel(y, src->width() - x - 1, src->getPixel(x, y));
131 break;
132
133 // bad angle
134 default:
135 throw std::invalid_argument("Invalid angle specified to rotate the image");
136 }
137}
138
139void draw_hline(Image* image, int x1, int y, int x2, color_t color)
140{
141 ASSERT(image);
142 int t;
143
144 if (x1 > x2) {
145 t = x1;
146 x1 = x2;
147 x2 = t;
148 }
149
150 if ((x2 < 0) || (x1 >= image->width()) || (y < 0) || (y >= image->height()))
151 return;
152
153 if (x1 < 0) x1 = 0;
154 if (x2 >= image->width()) x2 = image->width()-1;
155
156 image->drawHLine(x1, y, x2, color);
157}
158
159void draw_vline(Image* image, int x, int y1, int y2, color_t color)
160{
161 ASSERT(image);
162 int t;
163
164 if (y1 > y2) {
165 t = y1;
166 y1 = y2;
167 y2 = t;
168 }
169
170 if ((y2 < 0) || (y1 >= image->height()) || (x < 0) || (x >= image->width()))
171 return;
172
173 if (y1 < 0) y1 = 0;
174 if (y2 >= image->height()) y2 = image->height()-1;
175
176 for (t=y1; t<=y2; t++)
177 image->putPixel(x, t, color);
178}
179
180void draw_rect(Image* image, int x1, int y1, int x2, int y2, color_t color)
181{
182 ASSERT(image);
183 int t;
184
185 if (x1 > x2) {
186 t = x1;
187 x1 = x2;
188 x2 = t;
189 }
190
191 if (y1 > y2) {
192 t = y1;
193 y1 = y2;
194 y2 = t;
195 }
196
197 if ((x2 < 0) || (x1 >= image->width()) || (y2 < 0) || (y1 >= image->height()))
198 return;
199
200 draw_hline(image, x1, y1, x2, color);
201 draw_hline(image, x1, y2, x2, color);
202 if (y2-y1 > 1) {
203 draw_vline(image, x1, y1+1, y2-1, color);
204 draw_vline(image, x2, y1+1, y2-1, color);
205 }
206}
207
208void fill_rect(Image* image, int x1, int y1, int x2, int y2, color_t color)
209{
210 ASSERT(image);
211 int t;
212
213 if (x1 > x2) {
214 t = x1;
215 x1 = x2;
216 x2 = t;
217 }
218
219 if (y1 > y2) {
220 t = y1;
221 y1 = y2;
222 y2 = t;
223 }
224
225 if ((x2 < 0) || (x1 >= image->width()) || (y2 < 0) || (y1 >= image->height()))
226 return;
227
228 if (x1 < 0) x1 = 0;
229 if (y1 < 0) y1 = 0;
230 if (x2 >= image->width()) x2 = image->width()-1;
231 if (y2 >= image->height()) y2 = image->height()-1;
232
233 image->fillRect(x1, y1, x2, y2, color);
234}
235
236void fill_rect(Image* image, const gfx::Rect& rc, color_t c)
237{
238 ASSERT(image);
239
240 gfx::Rect clip = rc.createIntersection(image->bounds());
241 if (!clip.isEmpty())
242 image->fillRect(clip.x, clip.y,
243 clip.x+clip.w-1, clip.y+clip.h-1, c);
244}
245
246void blend_rect(Image* image, int x1, int y1, int x2, int y2, color_t color, int opacity)
247{
248 ASSERT(image);
249 int t;
250
251 if (x1 > x2) {
252 t = x1;
253 x1 = x2;
254 x2 = t;
255 }
256
257 if (y1 > y2) {
258 t = y1;
259 y1 = y2;
260 y2 = t;
261 }
262
263 if ((x2 < 0) || (x1 >= image->width()) || (y2 < 0) || (y1 >= image->height()))
264 return;
265
266 if (x1 < 0) x1 = 0;
267 if (y1 < 0) y1 = 0;
268 if (x2 >= image->width()) x2 = image->width()-1;
269 if (y2 >= image->height()) y2 = image->height()-1;
270
271 image->blendRect(x1, y1, x2, y2, color, opacity);
272}
273
274struct Data {
275 Image* image;
276 color_t color;
277};
278
279static void pixel_for_image(int x, int y, Data* data)
280{
281 put_pixel(data->image, x, y, data->color);
282}
283
284static void hline_for_image(int x1, int y, int x2, Data* data)
285{
286 draw_hline(data->image, x1, y, x2, data->color);
287}
288
289void draw_line(Image* image, int x1, int y1, int x2, int y2, color_t color)
290{
291 Data data = { image, color };
292 algo_line_continuous(x1, y1, x2, y2, &data, (AlgoPixel)pixel_for_image);
293}
294
295void draw_ellipse(Image* image, int x1, int y1, int x2, int y2, int extraXPxs, int extraYPxs, color_t color)
296{
297 Data data = { image, color };
298 algo_ellipse(x1, y1, x2, y2, extraXPxs, extraYPxs, &data, (AlgoPixel)pixel_for_image);
299}
300
301void fill_ellipse(Image* image, int x1, int y1, int x2, int y2, int extraXPxs, int extraYPxs, color_t color)
302{
303 Data data = { image, color };
304 algo_ellipsefill(x1, y1, x2, y2, extraXPxs, extraYPxs, &data, (AlgoHLine)hline_for_image);
305}
306
307namespace {
308
309template<typename ImageTraits>
310bool is_plain_image_templ(const Image* img, const color_t color)
311{
312 const LockImageBits<ImageTraits> bits(img);
313 typename LockImageBits<ImageTraits>::const_iterator it, end;
314 for (it=bits.begin(), end=bits.end(); it!=end; ++it) {
315 if (!ImageTraits::same_color(*it, color))
316 return false;
317 }
318 ASSERT(it == end);
319 return true;
320}
321
322template<typename ImageTraits>
323int count_diff_between_images_templ(const Image* i1, const Image* i2)
324{
325 int diff = 0;
326 const LockImageBits<ImageTraits> bits1(i1);
327 const LockImageBits<ImageTraits> bits2(i2);
328 typename LockImageBits<ImageTraits>::const_iterator it1, it2, end1, end2;
329 for (it1 = bits1.begin(), end1 = bits1.end(),
330 it2 = bits2.begin(), end2 = bits2.end();
331 it1 != end1 && it2 != end2; ++it1, ++it2) {
332 if (!ImageTraits::same_color(*it1, *it2))
333 diff++;
334 }
335 ASSERT(it1 == end1);
336 ASSERT(it2 == end2);
337 return diff;
338}
339
340template<typename ImageTraits>
341int is_same_image_templ(const Image* i1, const Image* i2)
342{
343 const LockImageBits<ImageTraits> bits1(i1);
344 const LockImageBits<ImageTraits> bits2(i2);
345 typename LockImageBits<ImageTraits>::const_iterator it1, it2, end1, end2;
346 for (it1 = bits1.begin(), end1 = bits1.end(),
347 it2 = bits2.begin(), end2 = bits2.end();
348 it1 != end1 && it2 != end2; ++it1, ++it2) {
349 if (!ImageTraits::same_color(*it1, *it2))
350 return false;
351 }
352 ASSERT(it1 == end1);
353 ASSERT(it2 == end2);
354 return true;
355}
356
357} // anonymous namespace
358
359bool is_plain_image(const Image* img, color_t c)
360{
361 switch (img->pixelFormat()) {
362 case IMAGE_RGB: return is_plain_image_templ<RgbTraits>(img, c);
363 case IMAGE_GRAYSCALE: return is_plain_image_templ<GrayscaleTraits>(img, c);
364 case IMAGE_INDEXED: return is_plain_image_templ<IndexedTraits>(img, c);
365 case IMAGE_BITMAP: return is_plain_image_templ<BitmapTraits>(img, c);
366 case IMAGE_TILEMAP: return is_plain_image_templ<TilemapTraits>(img, c);
367 }
368 return false;
369}
370
371bool is_empty_image(const Image* img)
372{
373 color_t c = 0; // alpha = 0
374 if (img->colorMode() == ColorMode::INDEXED)
375 c = img->maskColor();
376 return is_plain_image(img, c);
377}
378
379int count_diff_between_images(const Image* i1, const Image* i2)
380{
381 if ((i1->pixelFormat() != i2->pixelFormat()) ||
382 (i1->width() != i2->width()) ||
383 (i1->height() != i2->height()))
384 return -1;
385
386 switch (i1->pixelFormat()) {
387 case IMAGE_RGB: return count_diff_between_images_templ<RgbTraits>(i1, i2);
388 case IMAGE_GRAYSCALE: return count_diff_between_images_templ<GrayscaleTraits>(i1, i2);
389 case IMAGE_INDEXED: return count_diff_between_images_templ<IndexedTraits>(i1, i2);
390 case IMAGE_BITMAP: return count_diff_between_images_templ<BitmapTraits>(i1, i2);
391 case IMAGE_TILEMAP: return count_diff_between_images_templ<TilemapTraits>(i1, i2);
392 }
393
394 ASSERT(false);
395 return -1;
396}
397
398bool is_same_image(const Image* i1, const Image* i2)
399{
400 if ((i1->pixelFormat() != i2->pixelFormat()) ||
401 (i1->width() != i2->width()) ||
402 (i1->height() != i2->height()))
403 return false;
404
405 switch (i1->pixelFormat()) {
406 case IMAGE_RGB: return is_same_image_templ<RgbTraits>(i1, i2);
407 case IMAGE_GRAYSCALE: return is_same_image_templ<GrayscaleTraits>(i1, i2);
408 case IMAGE_INDEXED: return is_same_image_templ<IndexedTraits>(i1, i2);
409 case IMAGE_BITMAP: return is_same_image_templ<BitmapTraits>(i1, i2);
410 case IMAGE_TILEMAP: return is_same_image_templ<TilemapTraits>(i1, i2);
411 }
412
413 ASSERT(false);
414 return false;
415}
416
417void remap_image(Image* image, const Remap& remap)
418{
419 ASSERT(image->pixelFormat() == IMAGE_INDEXED ||
420 image->pixelFormat() == IMAGE_TILEMAP);
421
422 switch (image->pixelFormat()) {
423 case IMAGE_INDEXED:
424 transform_image<IndexedTraits>(
425 image, [&remap](color_t c) -> color_t {
426 auto to = remap[c];
427 if (to != Remap::kUnused)
428 return to;
429 else
430 return c;
431 });
432 break;
433 case IMAGE_TILEMAP:
434 transform_image<TilemapTraits>(
435 image, [&remap](color_t c) -> color_t {
436 auto to = remap[c];
437 if (c == notile || to == Remap::kNoTile)
438 return notile;
439 else if (to != Remap::kUnused)
440 return to;
441 else
442 return c;
443 });
444 break;
445 }
446}
447
448// TODO test this hash routine and find a better alternative
449
450template <typename ImageTraits, uint32_t Mask>
451static uint32_t calculate_image_hash_templ(const Image* image,
452 const gfx::Rect& bounds)
453{
454#if defined(__LP64__) || defined(__x86_64__) || defined(_WIN64)
455 #define CITYHASH(buf, len) (CityHash64(buf, len) & 0xffffffff)
456 static_assert(sizeof(void*) == 8, "This CPU is not 64-bit");
457#else
458 #define CITYHASH(buf, len) CityHash32(buf, len)
459 static_assert(sizeof(void*) == 4, "This CPU is not 32-bit");
460#endif
461
462 const uint32_t rowlen = ImageTraits::getRowStrideBytes(bounds.w);
463 const uint32_t len = rowlen * bounds.h;
464 if (bounds == image->bounds()) {
465 return CITYHASH((const char*)image->getPixelAddress(0, 0), len);
466 }
467 else {
468 ASSERT(false); // TODO not used at this moment
469
470 std::vector<uint8_t> buf(len);
471 uint8_t* dst = &buf[0];
472 for (int y=0; y<bounds.h; ++y, dst+=rowlen) {
473 auto src = image->getPixelAddress(bounds.x, bounds.y+y);
474 std::copy(dst, dst+rowlen, src);
475 }
476 return CITYHASH((const char*)&buf[0], buf.size());
477 }
478}
479
480uint32_t calculate_image_hash(const Image* img, const gfx::Rect& bounds)
481{
482 switch (img->pixelFormat()) {
483 case IMAGE_RGB: return calculate_image_hash_templ<RgbTraits, rgba_rgb_mask>(img, bounds);
484 case IMAGE_GRAYSCALE: return calculate_image_hash_templ<GrayscaleTraits, graya_v_mask>(img, bounds);
485 case IMAGE_INDEXED: return calculate_image_hash_templ<IndexedTraits, 0xff>(img, bounds);
486 case IMAGE_BITMAP: return calculate_image_hash_templ<BitmapTraits, 1>(img, bounds);
487 }
488 ASSERT(false);
489 return 0;
490}
491
492void preprocess_transparent_pixels(Image* image)
493{
494 switch (image->pixelFormat()) {
495
496 case IMAGE_RGB: {
497 LockImageBits<RgbTraits> bits(image);
498 auto it = bits.begin(), end = bits.end();
499 for (; it != end; ++it) {
500 if (rgba_geta(*it) == 0)
501 *it = 0;
502 }
503 break;
504 }
505
506 case IMAGE_GRAYSCALE: {
507 LockImageBits<GrayscaleTraits> bits(image);
508 auto it = bits.begin(), end = bits.end();
509 for (; it != end; ++it) {
510 if (graya_geta(*it) == 0)
511 *it = 0;
512 }
513 break;
514 }
515
516 }
517}
518
519} // namespace doc
520