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
25namespace doc {
26namespace algorithm {
27
28void 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
62ImageRef 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