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 | |
27 | namespace doc { |
28 | |
29 | color_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 | |
39 | void 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 | |
47 | void clear_image(Image* image, color_t color) |
48 | { |
49 | ASSERT(image); |
50 | |
51 | image->clear(color); |
52 | } |
53 | |
54 | void 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 | |
62 | void 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 | |
70 | void 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 | |
76 | Image* 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 | |
92 | Image* 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 | |
97 | void 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 | |
139 | void 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 | |
159 | void 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 | |
180 | void 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 | |
208 | void 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 | |
236 | void 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 | |
246 | void 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 | |
274 | struct Data { |
275 | Image* image; |
276 | color_t color; |
277 | }; |
278 | |
279 | static void pixel_for_image(int x, int y, Data* data) |
280 | { |
281 | put_pixel(data->image, x, y, data->color); |
282 | } |
283 | |
284 | static 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 | |
289 | void 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 | |
295 | void draw_ellipse(Image* image, int x1, int y1, int x2, int y2, int , int , 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 | |
301 | void fill_ellipse(Image* image, int x1, int y1, int x2, int y2, int , int , 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 | |
307 | namespace { |
308 | |
309 | template<typename ImageTraits> |
310 | bool 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 | |
322 | template<typename ImageTraits> |
323 | int 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 | |
340 | template<typename ImageTraits> |
341 | int 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 | |
359 | bool 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 | |
371 | bool 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 | |
379 | int 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 | |
398 | bool 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 | |
417 | void 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 | |
450 | template <typename ImageTraits, uint32_t Mask> |
451 | static 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 | |
480 | uint32_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 | |
492 | void 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 | |