1 | // Aseprite Document Library |
2 | // Copyright (c) 2019-2020 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/shift_image.h" |
13 | |
14 | #include "base/pi.h" |
15 | #include "doc/algorithm/shrink_bounds.h" |
16 | #include "doc/cel.h" |
17 | #include "doc/image.h" |
18 | #include "doc/layer.h" |
19 | #include "doc/mask.h" |
20 | #include "doc/primitives.h" |
21 | #include "gfx/rect.h" |
22 | |
23 | #include <vector> |
24 | |
25 | namespace doc { |
26 | namespace algorithm { |
27 | |
28 | void shift_image(Image* image, int dx, int dy, double angle) |
29 | { |
30 | gfx::Rect bounds(image->bounds()); |
31 | if (cos(angle) < -sqrt(2)/2) { |
32 | dx = -dx; |
33 | dy = -dy; |
34 | } |
35 | else if (sin(angle) >= sqrt(2)/2) { |
36 | double aux; |
37 | aux = dx; |
38 | dx = -dy; |
39 | dy = aux; |
40 | } |
41 | else if (sin(angle) < -sqrt(2)/2) { |
42 | double aux; |
43 | aux = dx; |
44 | dx = dy; |
45 | dy = -aux; |
46 | } |
47 | // To simplify the algorithm we use a copy of the original image, we |
48 | // could avoid this copy swapping rows and columns. |
49 | ImageRef crop(crop_image(image, bounds.x, bounds.y, bounds.w, bounds.h, |
50 | image->maskColor())); |
51 | |
52 | for (int y=0; y<bounds.h; ++y) { |
53 | for (int x=0; x<bounds.w; ++x) { |
54 | put_pixel(image, |
55 | (bounds.w + dx + x) % bounds.w, |
56 | (bounds.h + dy + y) % bounds.h, |
57 | get_pixel(crop.get(), x, y)); |
58 | } |
59 | } |
60 | } |
61 | |
62 | ImageRef shift_image_with_mask(const Cel* cel, |
63 | const Mask* mask, |
64 | const int dx, |
65 | const int dy, |
66 | gfx::Rect& newCelBounds) |
67 | { |
68 | ASSERT(!cel->bounds().isEmpty()); |
69 | ASSERT(!mask->bounds().isEmpty()); |
70 | |
71 | // Making a image which bounds are equal to the UNION of cel->bounds() and mask->bounds() |
72 | gfx::Rect compCelBounds = cel->bounds() | mask->bounds(); |
73 | ImageRef compImage(Image::create(cel->image()->pixelFormat(), compCelBounds.w, compCelBounds.h)); |
74 | |
75 | // Making a Rect which represents the mask bounds of the original MASK relative to |
76 | // the new COMPOUND IMAGE (compImage) |
77 | gfx::Point maskCelGap(0, 0); |
78 | if (cel->bounds().x < mask->bounds().x) |
79 | maskCelGap.x = mask->bounds().x - cel->bounds().x; |
80 | if (cel->bounds().y < mask->bounds().y) |
81 | maskCelGap.y = mask->bounds().y - cel->bounds().y; |
82 | gfx::Rect maskedBounds(maskCelGap.x, maskCelGap.y, mask->bounds().w, mask->bounds().h); |
83 | |
84 | // Making one combined image: Image with the Mask Bounds (unfilled spaces were filled with mask color) |
85 | compImage->copy(cel->image(), gfx::Clip(cel->position().x-compCelBounds.x, |
86 | cel->position().y-compCelBounds.y, |
87 | 0, 0, |
88 | cel->bounds().w, cel->bounds().h)); |
89 | |
90 | // Making a copy of only the image which will be shiftted |
91 | ImageRef imageToShift(Image::create(compImage->pixelFormat(), maskedBounds.w, maskedBounds.h)); |
92 | imageToShift->copy(compImage.get(), gfx::Clip(0, 0, maskedBounds)); |
93 | |
94 | // Shifting the masked area of the COMPOUND IMAGE (compImage). |
95 | const int xInitial = maskedBounds.x; |
96 | const int yInitial = maskedBounds.y; |
97 | const int wMask = maskedBounds.w; |
98 | const int hMask = maskedBounds.h; |
99 | for (int y=0; y<hMask; ++y) { |
100 | for (int x=0; x<wMask; ++x) { |
101 | // Use floor modulo (Euclidean remainder). |
102 | // Shifts are broken out and stored in separate variables |
103 | // to make them easier to recognize and change in the event |
104 | // that rem_eucl is implemented formally in the future. |
105 | const int xShift = ((dx + x) % wMask + wMask) % wMask; |
106 | const int yShift = ((dy + y) % hMask + hMask) % hMask; |
107 | |
108 | put_pixel( |
109 | compImage.get(), |
110 | xInitial + xShift, |
111 | yInitial + yShift, |
112 | get_pixel(imageToShift.get(), x, y)); |
113 | } |
114 | } |
115 | |
116 | // Bounds and Image shrinking (we have to fit compound image (compImage) and bounds (compCelBounds)) |
117 | gfx::Rect newBounds = compImage->bounds(); |
118 | if (algorithm::shrink_bounds( |
119 | compImage.get(), |
120 | compImage->maskColor(), |
121 | // TODO adding the layer for tilemaps (so the program doesn't |
122 | // crash), but anyway it the Shift algorithm is not |
123 | // working yet for tilemaps |
124 | cel->layer(), |
125 | newBounds)) { |
126 | compCelBounds.offset(newBounds.x, newBounds.y); |
127 | compCelBounds.setSize(newBounds.size()); |
128 | } |
129 | ImageRef finalImage(Image::create(compImage->pixelFormat(), compCelBounds.w, compCelBounds.h)); |
130 | finalImage->copy( |
131 | compImage.get(), |
132 | gfx::Clip(0, 0, newBounds.x, newBounds.y, |
133 | compCelBounds.w, compCelBounds.h)); |
134 | |
135 | // Final cel content assign |
136 | newCelBounds = compCelBounds; |
137 | return finalImage; |
138 | } |
139 | |
140 | } // namespace algorithm |
141 | } // namespace doc |
142 | |