1// Aseprite Document Library
2// Copyright (c) 2019-2021 Igara Studio S.A.
3//
4// This file is released under the terms of the MIT license.
5// Read LICENSE.txt for more information.
6
7#ifdef HAVE_CONFIG_H
8#include "config.h"
9#endif
10
11#include "doc/grid.h"
12
13#include "doc/image.h"
14#include "doc/image_impl.h"
15#include "doc/image_ref.h"
16#include "doc/primitives.h"
17#include "gfx/point.h"
18#include "gfx/rect.h"
19#include "gfx/region.h"
20#include "gfx/size.h"
21
22#include <algorithm>
23#include <cmath>
24#include <limits>
25#include <vector>
26
27namespace doc {
28
29// static
30Grid Grid::MakeRect(const gfx::Size& sz)
31{
32 return Grid(sz);
33}
34
35// Converts a tile position into a canvas position
36gfx::Point Grid::tileToCanvas(const gfx::Point& tile) const
37{
38 gfx::Point result;
39 result.x = tile.x * m_tileOffset.x + m_origin.x;
40 result.y = tile.y * m_tileOffset.y + m_origin.y;
41 if (tile.y & 1) // Odd row
42 result += m_oddRowOffset;
43 if (tile.x & 1) // Odd column
44 result += m_oddColOffset;
45 return result;
46}
47
48gfx::Rect Grid::tileToCanvas(const gfx::Rect& tileBounds) const
49{
50 gfx::Point pt1 = tileToCanvas(tileBounds.origin());
51 gfx::Point pt2 = tileToCanvas(tileBounds.point2());
52 return gfx::Rect(pt1, pt2);
53}
54
55gfx::Region Grid::tileToCanvas(const gfx::Region& tileRgn)
56{
57 gfx::Region canvasRgn;
58 for (const gfx::Rect& rc : tileRgn) {
59 canvasRgn |= gfx::Region(tileToCanvas(rc));
60 }
61 return canvasRgn;
62}
63
64gfx::Point Grid::canvasToTile(const gfx::Point& canvasPoint) const
65{
66 ASSERT(m_tileSize.w > 0);
67 ASSERT(m_tileSize.h > 0);
68 if (m_tileSize.w < 1 ||
69 m_tileSize.h < 1)
70 return canvasPoint;
71
72 gfx::Point tile;
73 std::div_t divx = std::div((canvasPoint.x - m_origin.x), m_tileSize.w);
74 std::div_t divy = std::div((canvasPoint.y - m_origin.y), m_tileSize.h);
75 tile.x = divx.quot;
76 tile.y = divy.quot;
77 if (canvasPoint.x < m_origin.x && divx.rem) --tile.x;
78 if (canvasPoint.y < m_origin.y && divy.rem) --tile.y;
79
80 if (m_oddRowOffset.x != 0 || m_oddRowOffset.y != 0 ||
81 m_oddColOffset.x != 0 || m_oddColOffset.y != 0) {
82 gfx::Point bestTile = tile;
83 int bestDist = std::numeric_limits<int>::max();
84 for (int v=-1; v<=2; ++v) {
85 for (int u=-1; u<=2; ++u) {
86 gfx::Point neighbor(tile.x+u, tile.y+v);
87 gfx::Point neighborCanvas = tileToCanvas(neighbor);
88
89 if (hasMask()) {
90 if (doc::get_pixel(m_mask.get(),
91 canvasPoint.x-neighborCanvas.x,
92 canvasPoint.y-neighborCanvas.y))
93 return neighbor;
94 }
95
96 gfx::Point delta = neighborCanvas+m_tileCenter-canvasPoint;
97 int dist = delta.x*delta.x + delta.y*delta.y;
98 if (bestDist > dist) {
99 bestDist = dist;
100 bestTile = neighbor;
101 }
102 }
103 }
104 tile = bestTile;
105 }
106
107 return tile;
108}
109
110gfx::Rect Grid::canvasToTile(const gfx::Rect& canvasBounds) const
111{
112 gfx::Point pt1 = canvasToTile(canvasBounds.origin());
113 gfx::Point pt2 = canvasToTile(gfx::Point(canvasBounds.x2()-1,
114 canvasBounds.y2()-1));
115 return gfx::Rect(pt1, gfx::Size(pt2.x - pt1.x + 1,
116 pt2.y - pt1.y + 1));
117}
118
119gfx::Region Grid::canvasToTile(const gfx::Region& canvasRgn)
120{
121 gfx::Region tilesRgn;
122 for (const gfx::Rect& rc : canvasRgn) {
123 tilesRgn |= gfx::Region(canvasToTile(rc));
124 }
125 return tilesRgn;
126
127}
128
129gfx::Size Grid::tilemapSizeToCanvas(const gfx::Size& tilemapSize) const
130{
131 return gfx::Size(tilemapSize.w * m_tileSize.w,
132 tilemapSize.h * m_tileSize.h);
133}
134
135gfx::Rect Grid::tileBoundsInCanvas(const gfx::Point& tile) const
136{
137 return gfx::Rect(tileToCanvas(tile), m_tileSize);
138}
139
140gfx::Rect Grid::alignBounds(const gfx::Rect& bounds) const
141{
142 gfx::Point pt1 = canvasToTile(bounds.origin());
143 gfx::Point pt2 = canvasToTile(gfx::Point(bounds.x2()-1,
144 bounds.y2()-1));
145 return
146 tileBoundsInCanvas(pt1) |
147 tileBoundsInCanvas(pt2);
148}
149
150std::vector<gfx::Point> Grid::tilesInCanvasRegion(const gfx::Region& rgn) const
151{
152 std::vector<gfx::Point> result;
153 if (rgn.isEmpty())
154 return result;
155
156 const gfx::Rect bounds = canvasToTile(rgn.bounds());
157 if (bounds.w < 1 ||
158 bounds.h < 1)
159 return result;
160
161 ImageRef tmp(Image::create(IMAGE_BITMAP, bounds.w, bounds.h));
162 const gfx::Rect tmpBounds = tmp->bounds();
163 tmp->clear(0);
164
165 for (const gfx::Rect& rc : rgn) {
166 gfx::Rect tileBounds = canvasToTile(rc);
167 tileBounds.x -= bounds.x;
168 tileBounds.y -= bounds.y;
169 tileBounds &= tmpBounds;
170 if (!tileBounds.isEmpty())
171 tmp->fillRect(tileBounds.x,
172 tileBounds.y,
173 tileBounds.x2()-1,
174 tileBounds.y2()-1, 1);
175 }
176
177 const LockImageBits<BitmapTraits> bits(tmp.get());
178 for (auto it=bits.begin(), end=bits.end(); it!=end; ++it) {
179 if (*it)
180 result.push_back(gfx::Point(it.x()+bounds.x,
181 it.y()+bounds.y));
182 }
183 return result;
184}
185
186} // namespace doc
187