15 if (tool !=
"paint" && tool !=
"erase" && tool !=
"fill" && tool !=
"line" && tool !=
"rect" &&
17 throw Exception(
"Brush::setTool: expected paint|erase|fill|line|rect|stamp");
23 if (size < 1)
throw Exception(
"Brush::setSize: size must be >= 1");
28 if (shape !=
"square" && shape !=
"circle") {
29 throw Exception(
"Brush::setShape: expected square|circle");
42 stamp_.assign(
static_cast<size_t>(
width) *
static_cast<size_t>(
height), 0);
46 if (lx < 0 || ly < 0 || lx >= stampW_ || ly >= stampH_) {
47 throw Exception(
"Brush::setStampTile: out of stamp bounds");
49 stamp_[
static_cast<size_t>(ly * stampW_ + lx)] = gid;
53 if (lx < 0 || ly < 0 || lx >= stampW_ || ly >= stampH_) {
54 throw Exception(
"Brush::getStampTile: out of stamp bounds");
56 return stamp_[
static_cast<size_t>(ly * stampW_ + lx)];
65void Brush::clearChanges() { changes_.clear(); }
67void Brush::clearPreview() { preview_.clear(); }
69void Brush::collectBrushCells(
int tx,
int ty, std::vector<std::pair<int, int>> &out)
const {
72 for (
int dy = -r; dy <= r; ++dy) {
73 for (
int dx = -r; dx <= r; ++dx) {
74 if (shape_ ==
"circle") {
75 float d = std::sqrt(
static_cast<float>(dx * dx + dy * dy));
76 if (
d >
static_cast<float>(r) + 0.01f)
continue;
78 out.emplace_back(tx + dx, ty + dy);
83void Brush::collectLineCells(
int x0,
int y0,
int x1,
int y1,
84 std::vector<std::pair<int, int>> &out)
const {
86 int dx = std::abs(x1 - x0), sx = x0 < x1 ? 1 : -1;
87 int dy = -std::abs(y1 - y0), sy = y0 < y1 ? 1 : -1;
91 std::vector<std::pair<int, int>> brush;
92 collectBrushCells(
x,
y, brush);
93 out.insert(out.end(), brush.begin(), brush.end());
94 if (
x == x1 &&
y == y1)
break;
107void Brush::collectRectCells(
int x0,
int y0,
int x1,
int y1,
bool filled,
108 std::vector<std::pair<int, int>> &out)
const {
110 int minX = std::min(x0, x1), maxX = std::max(x0, x1);
111 int minY = std::min(y0, y1), maxY = std::max(y0, y1);
112 for (
int y = minY;
y <= maxY; ++
y) {
113 for (
int x = minX;
x <= maxX; ++
x) {
114 if (!filled &&
x != minX &&
x != maxX &&
y != minY &&
y != maxY)
continue;
115 out.emplace_back(
x,
y);
120int Brush::applyCells(TileBuffer *buffer,
const std::vector<std::pair<int, int>> &cells,
int gid) {
121 if (!buffer)
throw Exception(
"Brush: null TileBuffer");
124 std::vector<std::pair<int, int>> unique;
125 unique.reserve(cells.size());
126 for (
const auto &
c : cells) {
128 for (
const auto &
u : unique) {
129 if (
u.first ==
c.first &&
u.second ==
c.second) {
134 if (!found) unique.push_back(
c);
136 for (
const auto &
c : unique) {
137 if (!buffer->inBounds(
c.first,
c.second))
continue;
138 int old = buffer->getGid(
c.first,
c.second);
139 if (old == gid)
continue;
140 buffer->setGid(
c.first,
c.second, gid);
146 changes_.push_back(ch);
148 return static_cast<int>(changes_.size());
151int Brush::applyStamp(TileBuffer *buffer,
int tx,
int ty) {
152 if (!buffer)
throw Exception(
"Brush: null TileBuffer");
153 if (stampW_ <= 0 || stampH_ <= 0)
return 0;
155 int ox = tx - stampW_ / 2;
156 int oy = ty - stampH_ / 2;
157 for (
int ly = 0; ly < stampH_; ++ly) {
158 for (
int lx = 0; lx < stampW_; ++lx) {
159 int gid = stamp_[
static_cast<size_t>(ly * stampW_ + lx)];
162 if (!buffer->inBounds(
x,
y))
continue;
163 int old = buffer->getGid(
x,
y);
164 if (old == gid)
continue;
165 buffer->setGid(
x,
y, gid);
171 changes_.push_back(ch);
174 return static_cast<int>(changes_.size());
177int Brush::previewCells(TileBuffer *buffer,
const std::vector<std::pair<int, int>> &cells,
int gid) {
178 if (!buffer)
throw Exception(
"Brush: null TileBuffer");
180 for (
const auto &
c : cells) {
181 if (!buffer->inBounds(
c.first,
c.second))
continue;
186 p.oldGid = buffer->getGid(
c.first,
c.second);
187 preview_.push_back(
p);
189 return static_cast<int>(preview_.size());
193 if (tool_ ==
"stamp")
return applyStamp(buffer, tx, ty);
194 if (tool_ ==
"erase")
return eraseAt(buffer, tx, ty);
195 if (tool_ ==
"fill")
return floodFill(buffer, tx, ty);
196 std::vector<std::pair<int, int>> cells;
197 collectBrushCells(tx, ty, cells);
198 return applyCells(buffer, cells, tile_);
202 std::vector<std::pair<int, int>> cells;
203 collectBrushCells(tx, ty, cells);
204 return applyCells(buffer, cells, eraseTile_);
208 if (!buffer)
throw Exception(
"Brush: null TileBuffer");
210 if (!buffer->
inBounds(tx, ty))
return 0;
211 int target = buffer->
getGid(tx, ty);
212 if (target == tile_)
return 0;
213 std::vector<char> seen(
static_cast<size_t>(buffer->
getWidth() * buffer->
getHeight()), 0);
214 std::queue<std::pair<int, int>> q;
217 seen[
static_cast<size_t>(
idx(tx, ty))] = 1;
218 const int dirs[4][2] = {{1, 0}, {-1, 0}, {0, 1}, {0, -1}};
220 auto [
x,
y] = q.front();
229 changes_.push_back(ch);
230 for (
auto &
d : dirs) {
231 int nx =
x +
d[0], ny =
y +
d[1];
232 if (!buffer->
inBounds(nx, ny))
continue;
233 size_t i =
static_cast<size_t>(
idx(nx, ny));
234 if (seen[i])
continue;
235 if (buffer->
getGid(nx, ny) != target)
continue;
240 return static_cast<int>(changes_.size());
244 std::vector<std::pair<int, int>> cells;
245 collectLineCells(x0, y0, x1, y1, cells);
246 int gid = (tool_ ==
"erase") ? eraseTile_ : tile_;
247 return applyCells(buffer, cells, gid);
251 std::vector<std::pair<int, int>> cells;
252 collectRectCells(x0, y0, x1, y1, filled, cells);
253 int gid = (tool_ ==
"erase") ? eraseTile_ : tile_;
254 return applyCells(buffer, cells, gid);
258 if (tool_ ==
"stamp") {
260 if (stampW_ <= 0 || stampH_ <= 0 || !buffer)
return 0;
261 int ox = tx - stampW_ / 2;
262 int oy = ty - stampH_ / 2;
263 for (
int ly = 0; ly < stampH_; ++ly) {
264 for (
int lx = 0; lx < stampW_; ++lx) {
265 int x = ox + lx,
y = oy + ly;
270 p.gid = stamp_[
static_cast<size_t>(ly * stampW_ + lx)];
272 preview_.push_back(
p);
275 return static_cast<int>(preview_.size());
277 std::vector<std::pair<int, int>> cells;
278 collectBrushCells(tx, ty, cells);
279 int gid = (tool_ ==
"erase") ? eraseTile_ : tile_;
280 return previewCells(buffer, cells, gid);
284 std::vector<std::pair<int, int>> cells;
285 collectLineCells(x0, y0, x1, y1, cells);
286 int gid = (tool_ ==
"erase") ? eraseTile_ : tile_;
287 return previewCells(buffer, cells, gid);
291 std::vector<std::pair<int, int>> cells;
292 collectRectCells(x0, y0, x1, y1, filled, cells);
293 int gid = (tool_ ==
"erase") ? eraseTile_ : tile_;
294 return previewCells(buffer, cells, gid);
297bool Brush::validChange(
int index)
const {
298 return index >= 0 && index < static_cast<int>(changes_.size());
300bool Brush::validPreview(
int index)
const {
301 return index >= 0 && index < static_cast<int>(preview_.size());
305 if (!validPreview(index))
throw Exception(
"Brush::getPreviewX: bad index");
306 return preview_[index].x;
309 if (!validPreview(index))
throw Exception(
"Brush::getPreviewY: bad index");
310 return preview_[index].y;
313 if (!validPreview(index))
throw Exception(
"Brush::getPreviewGid: bad index");
314 return preview_[index].gid;
317 if (!validChange(index))
throw Exception(
"Brush::getChangeX: bad index");
318 return changes_[index].x;
321 if (!validChange(index))
throw Exception(
"Brush::getChangeY: bad index");
322 return changes_[index].y;
325 if (!validChange(index))
throw Exception(
"Brush::getChangeOldGid: bad index");
326 return changes_[index].oldGid;
329 if (!validChange(index))
throw Exception(
"Brush::getChangeNewGid: bad index");
330 return changes_[index].gid;