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