| 1 | // Aseprite | 
|---|
| 2 | // Copyright (C) 2019-2021  Igara Studio S.A. | 
|---|
| 3 | // Copyright (C) 2001-2018  David Capello | 
|---|
| 4 | // | 
|---|
| 5 | // This program is distributed under the terms of | 
|---|
| 6 | // the End-User License Agreement for Aseprite. | 
|---|
| 7 |  | 
|---|
| 8 | #ifdef HAVE_CONFIG_H | 
|---|
| 9 | #include "config.h" | 
|---|
| 10 | #endif | 
|---|
| 11 |  | 
|---|
| 12 | #include "app/util/cel_ops.h" | 
|---|
| 13 |  | 
|---|
| 14 | #include "app/cmd/add_tile.h" | 
|---|
| 15 | #include "app/cmd/clear_cel.h" | 
|---|
| 16 | #include "app/cmd/clear_mask.h" | 
|---|
| 17 | #include "app/cmd/copy_region.h" | 
|---|
| 18 | #include "app/cmd/remap_tilemaps.h" | 
|---|
| 19 | #include "app/cmd/remap_tileset.h" | 
|---|
| 20 | #include "app/cmd/remove_tile.h" | 
|---|
| 21 | #include "app/cmd/replace_image.h" | 
|---|
| 22 | #include "app/cmd/set_cel_position.h" | 
|---|
| 23 | #include "app/cmd_sequence.h" | 
|---|
| 24 | #include "app/doc.h" | 
|---|
| 25 | #include "doc/algorithm/fill_selection.h" | 
|---|
| 26 | #include "doc/algorithm/resize_image.h" | 
|---|
| 27 | #include "doc/algorithm/shrink_bounds.h" | 
|---|
| 28 | #include "doc/cel.h" | 
|---|
| 29 | #include "doc/grid.h" | 
|---|
| 30 | #include "doc/image.h" | 
|---|
| 31 | #include "doc/layer.h" | 
|---|
| 32 | #include "doc/layer_tilemap.h" | 
|---|
| 33 | #include "doc/mask.h" | 
|---|
| 34 | #include "doc/palette.h" | 
|---|
| 35 | #include "doc/primitives.h" | 
|---|
| 36 | #include "doc/sprite.h" | 
|---|
| 37 | #include "doc/tileset.h" | 
|---|
| 38 | #include "doc/tilesets.h" | 
|---|
| 39 | #include "gfx/region.h" | 
|---|
| 40 | #include "render/dithering.h" | 
|---|
| 41 | #include "render/ordered_dither.h" | 
|---|
| 42 | #include "render/quantization.h" | 
|---|
| 43 | #include "render/render.h" | 
|---|
| 44 |  | 
|---|
| 45 | #include <algorithm> | 
|---|
| 46 | #include <cmath> | 
|---|
| 47 | #include <memory> | 
|---|
| 48 | #include <vector> | 
|---|
| 49 |  | 
|---|
| 50 | #define OPS_TRACE(...) // TRACE(__VA_ARGS__) | 
|---|
| 51 |  | 
|---|
| 52 | namespace app { | 
|---|
| 53 |  | 
|---|
| 54 | using namespace doc; | 
|---|
| 55 |  | 
|---|
| 56 | namespace { | 
|---|
| 57 |  | 
|---|
| 58 | template<typename ImageTraits> | 
|---|
| 59 | void mask_image_templ(Image* image, const Image* bitmap) | 
|---|
| 60 | { | 
|---|
| 61 | LockImageBits<ImageTraits> bits1(image); | 
|---|
| 62 | const LockImageBits<BitmapTraits> bits2(bitmap); | 
|---|
| 63 | typename LockImageBits<ImageTraits>::iterator it1, end1; | 
|---|
| 64 | LockImageBits<BitmapTraits>::const_iterator it2, end2; | 
|---|
| 65 | for (it1 = bits1.begin(), end1 = bits1.end(), | 
|---|
| 66 | it2 = bits2.begin(), end2 = bits2.end(); | 
|---|
| 67 | it1 != end1 && it2 != end2; ++it1, ++it2) { | 
|---|
| 68 | if (!*it2) | 
|---|
| 69 | *it1 = image->maskColor(); | 
|---|
| 70 | } | 
|---|
| 71 | ASSERT(it1 == end1); | 
|---|
| 72 | ASSERT(it2 == end2); | 
|---|
| 73 | } | 
|---|
| 74 |  | 
|---|
| 75 | void mask_image(Image* image, Image* bitmap) | 
|---|
| 76 | { | 
|---|
| 77 | ASSERT(image->bounds() == bitmap->bounds()); | 
|---|
| 78 | switch (image->pixelFormat()) { | 
|---|
| 79 | case IMAGE_RGB:       return mask_image_templ<RgbTraits>(image, bitmap); | 
|---|
| 80 | case IMAGE_GRAYSCALE: return mask_image_templ<GrayscaleTraits>(image, bitmap); | 
|---|
| 81 | case IMAGE_INDEXED:   return mask_image_templ<IndexedTraits>(image, bitmap); | 
|---|
| 82 | } | 
|---|
| 83 | } | 
|---|
| 84 |  | 
|---|
| 85 | template<typename ImageTraits> | 
|---|
| 86 | void create_region_with_differences_templ(const Image* a, | 
|---|
| 87 | const Image* b, | 
|---|
| 88 | const gfx::Rect& bounds, | 
|---|
| 89 | gfx::Region& output) | 
|---|
| 90 | { | 
|---|
| 91 | for (int y=bounds.y; y<bounds.y2(); ++y) { | 
|---|
| 92 | for (int x=bounds.x; x<bounds.x2(); ++x) { | 
|---|
| 93 | if (get_pixel_fast<ImageTraits>(a, x, y) != | 
|---|
| 94 | get_pixel_fast<ImageTraits>(b, x, y)) { | 
|---|
| 95 | output.createUnion(output, gfx::Region(gfx::Rect(x, y, 1, 1))); | 
|---|
| 96 | } | 
|---|
| 97 | } | 
|---|
| 98 | } | 
|---|
| 99 | } | 
|---|
| 100 |  | 
|---|
| 101 | // TODO merge this with Sprite::getTilemapsByTileset() | 
|---|
| 102 | template<typename UnaryFunction> | 
|---|
| 103 | void for_each_tile_using_tileset(Tileset* tileset, UnaryFunction f) | 
|---|
| 104 | { | 
|---|
| 105 | for (Cel* cel : tileset->sprite()->uniqueCels()) { | 
|---|
| 106 | if (!cel->layer()->isTilemap() || | 
|---|
| 107 | static_cast<LayerTilemap*>(cel->layer())->tileset() != tileset) | 
|---|
| 108 | continue; | 
|---|
| 109 |  | 
|---|
| 110 | Image* tilemapImage = cel->image(); | 
|---|
| 111 | for_each_pixel<TilemapTraits>(tilemapImage, f); | 
|---|
| 112 | } | 
|---|
| 113 | } | 
|---|
| 114 |  | 
|---|
| 115 | struct Mod { | 
|---|
| 116 | tile_index tileIndex; | 
|---|
| 117 | ImageRef tileDstImage; | 
|---|
| 118 | ImageRef tileImage; | 
|---|
| 119 | gfx::Region tileRgn; | 
|---|
| 120 | }; | 
|---|
| 121 |  | 
|---|
| 122 | } // anonymous namespace | 
|---|
| 123 |  | 
|---|
| 124 | void create_region_with_differences(const Image* a, | 
|---|
| 125 | const Image* b, | 
|---|
| 126 | const gfx::Rect& bounds, | 
|---|
| 127 | gfx::Region& output) | 
|---|
| 128 | { | 
|---|
| 129 | ASSERT(a->pixelFormat() == b->pixelFormat()); | 
|---|
| 130 | switch (a->pixelFormat()) { | 
|---|
| 131 | case IMAGE_RGB: create_region_with_differences_templ<RgbTraits>(a, b, bounds, output); break; | 
|---|
| 132 | case IMAGE_GRAYSCALE: create_region_with_differences_templ<GrayscaleTraits>(a, b, bounds, output); break; | 
|---|
| 133 | case IMAGE_INDEXED: create_region_with_differences_templ<IndexedTraits>(a, b, bounds, output); break; | 
|---|
| 134 | } | 
|---|
| 135 | } | 
|---|
| 136 |  | 
|---|
| 137 | static void remove_unused_tiles_from_tileset( | 
|---|
| 138 | CmdSequence* cmds, | 
|---|
| 139 | doc::Tileset* tileset, | 
|---|
| 140 | std::vector<size_t>& tilesHistogram, | 
|---|
| 141 | const std::vector<bool>& modifiedTileIndexes); | 
|---|
| 142 |  | 
|---|
| 143 | doc::ImageRef crop_cel_image( | 
|---|
| 144 | const doc::Cel* cel, | 
|---|
| 145 | const color_t bgcolor) | 
|---|
| 146 | { | 
|---|
| 147 | doc::Sprite* sprite = cel->sprite(); | 
|---|
| 148 |  | 
|---|
| 149 | if (cel->layer()->isTilemap()) { | 
|---|
| 150 | doc::ImageRef dstImage(doc::Image::create(sprite->spec())); | 
|---|
| 151 |  | 
|---|
| 152 | render::Render().renderCel( | 
|---|
| 153 | dstImage.get(), | 
|---|
| 154 | cel, | 
|---|
| 155 | sprite, | 
|---|
| 156 | cel->image(), | 
|---|
| 157 | cel->layer(), | 
|---|
| 158 | sprite->palette(cel->frame()), | 
|---|
| 159 | dstImage->bounds(), | 
|---|
| 160 | gfx::Clip(cel->position(), dstImage->bounds()), | 
|---|
| 161 | 255, BlendMode::NORMAL); | 
|---|
| 162 |  | 
|---|
| 163 | return dstImage; | 
|---|
| 164 | } | 
|---|
| 165 | else { | 
|---|
| 166 | return doc::ImageRef( | 
|---|
| 167 | doc::crop_image( | 
|---|
| 168 | cel->image(), | 
|---|
| 169 | gfx::Rect(sprite->bounds()).offset(-cel->position()), | 
|---|
| 170 | bgcolor)); | 
|---|
| 171 | } | 
|---|
| 172 | } | 
|---|
| 173 |  | 
|---|
| 174 | Cel* create_cel_copy(CmdSequence* cmds, | 
|---|
| 175 | const Cel* srcCel, | 
|---|
| 176 | const Sprite* dstSprite, | 
|---|
| 177 | Layer* dstLayer, | 
|---|
| 178 | const frame_t dstFrame) | 
|---|
| 179 | { | 
|---|
| 180 | const Image* srcImage = srcCel->image(); | 
|---|
| 181 | doc::PixelFormat dstPixelFormat = | 
|---|
| 182 | (dstLayer->isTilemap() ? IMAGE_TILEMAP: | 
|---|
| 183 | dstSprite->pixelFormat()); | 
|---|
| 184 | gfx::Size dstSize(srcImage->width(), | 
|---|
| 185 | srcImage->height()); | 
|---|
| 186 |  | 
|---|
| 187 | // From Tilemap -> Image | 
|---|
| 188 | if (srcCel->layer()->isTilemap() && !dstLayer->isTilemap()) { | 
|---|
| 189 | auto layerTilemap = static_cast<doc::LayerTilemap*>(srcCel->layer()); | 
|---|
| 190 | dstSize = layerTilemap->tileset()->grid().tilemapSizeToCanvas(dstSize); | 
|---|
| 191 | } | 
|---|
| 192 | // From Image or Tilemap -> Tilemap | 
|---|
| 193 | else if (dstLayer->isTilemap()) { | 
|---|
| 194 | auto dstLayerTilemap = static_cast<doc::LayerTilemap*>(dstLayer); | 
|---|
| 195 |  | 
|---|
| 196 | // Tilemap -> Tilemap | 
|---|
| 197 | Grid grid; | 
|---|
| 198 | if (srcCel->layer()->isTilemap()) { | 
|---|
| 199 | grid = dstLayerTilemap->tileset()->grid(); | 
|---|
| 200 | if (srcCel->layer()->isTilemap()) | 
|---|
| 201 | grid.origin(srcCel->position()); | 
|---|
| 202 | } | 
|---|
| 203 | // Image -> Tilemap | 
|---|
| 204 | else { | 
|---|
| 205 | auto gridBounds = dstLayerTilemap->sprite()->gridBounds(); | 
|---|
| 206 | grid.origin(gridBounds.origin()); | 
|---|
| 207 | grid.tileSize(gridBounds.size()); | 
|---|
| 208 | } | 
|---|
| 209 |  | 
|---|
| 210 | const gfx::Rect tilemapBounds = grid.canvasToTile(srcCel->bounds()); | 
|---|
| 211 | dstSize = tilemapBounds.size(); | 
|---|
| 212 | } | 
|---|
| 213 |  | 
|---|
| 214 | // New cel | 
|---|
| 215 | std::unique_ptr<Cel> dstCel( | 
|---|
| 216 | new Cel(dstFrame, ImageRef(Image::create(dstPixelFormat, dstSize.w, dstSize.h)))); | 
|---|
| 217 |  | 
|---|
| 218 | dstCel->setOpacity(srcCel->opacity()); | 
|---|
| 219 | dstCel->data()->setUserData(srcCel->data()->userData()); | 
|---|
| 220 |  | 
|---|
| 221 | // Special case were we copy from a tilemap... | 
|---|
| 222 | if (srcCel->layer()->isTilemap()) { | 
|---|
| 223 | if (dstLayer->isTilemap()) { | 
|---|
| 224 | // Tilemap -> Tilemap (with same tileset) | 
|---|
| 225 | // Best case, copy a cel in the same layer (we have the same | 
|---|
| 226 | // tileset available, so we just copy the tilemap as it is). | 
|---|
| 227 | if (srcCel->layer() == dstLayer) { | 
|---|
| 228 | dstCel->image()->copy(srcImage, gfx::Clip(0, 0, srcImage->bounds())); | 
|---|
| 229 | } | 
|---|
| 230 | // Tilemap -> Tilemap (with different tilesets) | 
|---|
| 231 | else { | 
|---|
| 232 | doc::ImageSpec spec = dstSprite->spec(); | 
|---|
| 233 | spec.setSize(srcCel->bounds().size()); | 
|---|
| 234 | doc::ImageRef tmpImage(doc::Image::create(spec)); | 
|---|
| 235 | render::Render().renderCel( | 
|---|
| 236 | tmpImage.get(), | 
|---|
| 237 | srcCel, | 
|---|
| 238 | dstSprite, | 
|---|
| 239 | srcImage, | 
|---|
| 240 | srcCel->layer(), | 
|---|
| 241 | dstSprite->palette(dstCel->frame()), | 
|---|
| 242 | gfx::Rect(gfx::Point(0, 0), srcCel->bounds().size()), | 
|---|
| 243 | gfx::Clip(0, 0, tmpImage->bounds()), | 
|---|
| 244 | 255, BlendMode::NORMAL); | 
|---|
| 245 |  | 
|---|
| 246 | doc::ImageRef tilemap = dstCel->imageRef(); | 
|---|
| 247 |  | 
|---|
| 248 | draw_image_into_new_tilemap_cel( | 
|---|
| 249 | cmds, static_cast<doc::LayerTilemap*>(dstLayer), dstCel.get(), | 
|---|
| 250 | tmpImage.get(), | 
|---|
| 251 | srcCel->bounds().origin(), | 
|---|
| 252 | srcCel->bounds().origin(), | 
|---|
| 253 | srcCel->bounds(), | 
|---|
| 254 | tilemap); | 
|---|
| 255 | } | 
|---|
| 256 | dstCel->setPosition(srcCel->position()); | 
|---|
| 257 | } | 
|---|
| 258 | // Tilemap -> Image (so we convert the tilemap to a regular image) | 
|---|
| 259 | else { | 
|---|
| 260 | render::Render().renderCel( | 
|---|
| 261 | dstCel->image(), | 
|---|
| 262 | srcCel, | 
|---|
| 263 | dstSprite, | 
|---|
| 264 | srcImage, | 
|---|
| 265 | srcCel->layer(), | 
|---|
| 266 | dstSprite->palette(dstCel->frame()), | 
|---|
| 267 | gfx::Rect(gfx::Point(0, 0), srcCel->bounds().size()), | 
|---|
| 268 | gfx::Clip(0, 0, dstCel->image()->bounds()), | 
|---|
| 269 | 255, BlendMode::NORMAL); | 
|---|
| 270 |  | 
|---|
| 271 | // Shrink image | 
|---|
| 272 | if (dstLayer->isTransparent()) { | 
|---|
| 273 | auto bg = dstCel->image()->maskColor(); | 
|---|
| 274 | gfx::Rect bounds; | 
|---|
| 275 | if (algorithm::shrink_bounds(dstCel->image(), bg, dstLayer, bounds)) { | 
|---|
| 276 | ImageRef trimmed(doc::crop_image(dstCel->image(), bounds, bg)); | 
|---|
| 277 | dstCel->data()->setImage(trimmed, dstLayer); | 
|---|
| 278 | dstCel->setPosition(srcCel->position() + bounds.origin()); | 
|---|
| 279 | return dstCel.release(); | 
|---|
| 280 | } | 
|---|
| 281 | } | 
|---|
| 282 | } | 
|---|
| 283 | } | 
|---|
| 284 | // Image -> Tilemap (we'll need to generate new tilesets) | 
|---|
| 285 | else if (dstLayer->isTilemap()) { | 
|---|
| 286 | doc::ImageRef tilemap = dstCel->imageRef(); | 
|---|
| 287 | draw_image_into_new_tilemap_cel( | 
|---|
| 288 | cmds, static_cast<doc::LayerTilemap*>(dstLayer), dstCel.get(), | 
|---|
| 289 | srcImage, | 
|---|
| 290 | // Use the grid origin of the sprite | 
|---|
| 291 | srcCel->sprite()->gridBounds().origin(), | 
|---|
| 292 | srcCel->bounds().origin(), | 
|---|
| 293 | srcCel->bounds(), | 
|---|
| 294 | tilemap); | 
|---|
| 295 | } | 
|---|
| 296 | else if ((dstSprite->pixelFormat() != srcImage->pixelFormat()) || | 
|---|
| 297 | // If both images are indexed but with different palette, we can | 
|---|
| 298 | // convert the source cel to RGB first. | 
|---|
| 299 | (dstSprite->pixelFormat() == IMAGE_INDEXED && | 
|---|
| 300 | srcImage->pixelFormat() == IMAGE_INDEXED && | 
|---|
| 301 | srcCel->sprite()->palette(srcCel->frame())->countDiff( | 
|---|
| 302 | dstSprite->palette(dstFrame), nullptr, nullptr))) { | 
|---|
| 303 | ImageRef tmpImage(Image::create(IMAGE_RGB, srcImage->width(), srcImage->height())); | 
|---|
| 304 | tmpImage->clear(0); | 
|---|
| 305 |  | 
|---|
| 306 | render::convert_pixel_format( | 
|---|
| 307 | srcImage, | 
|---|
| 308 | tmpImage.get(), | 
|---|
| 309 | IMAGE_RGB, | 
|---|
| 310 | render::Dithering(), | 
|---|
| 311 | srcCel->sprite()->rgbMap(srcCel->frame()), | 
|---|
| 312 | srcCel->sprite()->palette(srcCel->frame()), | 
|---|
| 313 | srcCel->layer()->isBackground(), | 
|---|
| 314 | 0); | 
|---|
| 315 |  | 
|---|
| 316 | render::convert_pixel_format( | 
|---|
| 317 | tmpImage.get(), | 
|---|
| 318 | dstCel->image(), | 
|---|
| 319 | IMAGE_INDEXED, | 
|---|
| 320 | render::Dithering(), | 
|---|
| 321 | dstSprite->rgbMap(dstFrame), | 
|---|
| 322 | dstSprite->palette(dstFrame), | 
|---|
| 323 | srcCel->layer()->isBackground(), | 
|---|
| 324 | dstSprite->transparentColor()); | 
|---|
| 325 | } | 
|---|
| 326 | // Simple case, where we copy both images | 
|---|
| 327 | else { | 
|---|
| 328 | render::composite_image( | 
|---|
| 329 | dstCel->image(), | 
|---|
| 330 | srcImage, | 
|---|
| 331 | srcCel->sprite()->palette(srcCel->frame()), | 
|---|
| 332 | 0, 0, 255, BlendMode::SRC); | 
|---|
| 333 | } | 
|---|
| 334 |  | 
|---|
| 335 | // Resize a referece cel to a non-reference layer | 
|---|
| 336 | if (srcCel->layer()->isReference() && !dstLayer->isReference()) { | 
|---|
| 337 | gfx::RectF srcBounds = srcCel->boundsF(); | 
|---|
| 338 |  | 
|---|
| 339 | std::unique_ptr<Cel> dstCel2( | 
|---|
| 340 | new Cel(dstFrame, | 
|---|
| 341 | ImageRef(Image::create(dstSprite->pixelFormat(), | 
|---|
| 342 | std::ceil(srcBounds.w), | 
|---|
| 343 | std::ceil(srcBounds.h))))); | 
|---|
| 344 | algorithm::resize_image( | 
|---|
| 345 | dstCel->image(), dstCel2->image(), | 
|---|
| 346 | algorithm::RESIZE_METHOD_NEAREST_NEIGHBOR, | 
|---|
| 347 | nullptr, nullptr, 0); | 
|---|
| 348 |  | 
|---|
| 349 | dstCel.reset(dstCel2.release()); | 
|---|
| 350 | dstCel->setPosition(gfx::Point(srcBounds.origin())); | 
|---|
| 351 | } | 
|---|
| 352 | // Copy original cel bounds | 
|---|
| 353 | else if (!dstLayer->isTilemap()) { | 
|---|
| 354 | if (srcCel->layer() && | 
|---|
| 355 | srcCel->layer()->isReference()) { | 
|---|
| 356 | dstCel->setBoundsF(srcCel->boundsF()); | 
|---|
| 357 | } | 
|---|
| 358 | else { | 
|---|
| 359 | dstCel->setPosition(srcCel->position()); | 
|---|
| 360 | } | 
|---|
| 361 | } | 
|---|
| 362 |  | 
|---|
| 363 | return dstCel.release(); | 
|---|
| 364 | } | 
|---|
| 365 |  | 
|---|
| 366 | void draw_image_into_new_tilemap_cel( | 
|---|
| 367 | CmdSequence* cmds, | 
|---|
| 368 | doc::LayerTilemap* dstLayer, | 
|---|
| 369 | doc::Cel* dstCel, | 
|---|
| 370 | const doc::Image* srcImage, | 
|---|
| 371 | const gfx::Point& gridOrigin, | 
|---|
| 372 | const gfx::Point& srcImagePos, | 
|---|
| 373 | const gfx::Rect& canvasBounds, | 
|---|
| 374 | doc::ImageRef& newTilemap) | 
|---|
| 375 | { | 
|---|
| 376 | ASSERT(dstLayer->isTilemap()); | 
|---|
| 377 |  | 
|---|
| 378 | doc::Tileset* tileset = dstLayer->tileset(); | 
|---|
| 379 | doc::Grid grid = tileset->grid(); | 
|---|
| 380 | grid.origin(gridOrigin); | 
|---|
| 381 |  | 
|---|
| 382 | gfx::Size tileSize = grid.tileSize(); | 
|---|
| 383 | const gfx::Rect tilemapBounds = grid.canvasToTile(canvasBounds); | 
|---|
| 384 |  | 
|---|
| 385 | if (!newTilemap) { | 
|---|
| 386 | newTilemap.reset(doc::Image::create(IMAGE_TILEMAP, | 
|---|
| 387 | tilemapBounds.w, | 
|---|
| 388 | tilemapBounds.h)); | 
|---|
| 389 | newTilemap->setMaskColor(doc::notile); | 
|---|
| 390 | newTilemap->clear(doc::notile); | 
|---|
| 391 | } | 
|---|
| 392 | else { | 
|---|
| 393 | ASSERT(tilemapBounds.w == newTilemap->width()); | 
|---|
| 394 | ASSERT(tilemapBounds.h == newTilemap->height()); | 
|---|
| 395 | } | 
|---|
| 396 |  | 
|---|
| 397 | for (const gfx::Point& tilePt : grid.tilesInCanvasRegion(gfx::Region(canvasBounds))) { | 
|---|
| 398 | const gfx::Point tilePtInCanvas = grid.tileToCanvas(tilePt); | 
|---|
| 399 | doc::ImageRef tileImage( | 
|---|
| 400 | doc::crop_image(srcImage, | 
|---|
| 401 | tilePtInCanvas.x-srcImagePos.x, | 
|---|
| 402 | tilePtInCanvas.y-srcImagePos.y, | 
|---|
| 403 | tileSize.w, tileSize.h, | 
|---|
| 404 | srcImage->maskColor())); | 
|---|
| 405 | if (grid.hasMask()) | 
|---|
| 406 | mask_image(tileImage.get(), grid.mask().get()); | 
|---|
| 407 |  | 
|---|
| 408 | preprocess_transparent_pixels(tileImage.get()); | 
|---|
| 409 |  | 
|---|
| 410 | doc::tile_index tileIndex; | 
|---|
| 411 | if (!tileset->findTileIndex(tileImage, tileIndex)) { | 
|---|
| 412 | auto addTile = new cmd::AddTile(tileset, tileImage); | 
|---|
| 413 |  | 
|---|
| 414 | if (cmds) | 
|---|
| 415 | cmds->executeAndAdd(addTile); | 
|---|
| 416 | else { | 
|---|
| 417 | // TODO a little hacky | 
|---|
| 418 | addTile->execute( | 
|---|
| 419 | static_cast<Doc*>(dstLayer->sprite()->document())->context()); | 
|---|
| 420 | } | 
|---|
| 421 |  | 
|---|
| 422 | tileIndex = addTile->tileIndex(); | 
|---|
| 423 |  | 
|---|
| 424 | if (!cmds) | 
|---|
| 425 | delete addTile; | 
|---|
| 426 | } | 
|---|
| 427 |  | 
|---|
| 428 | // We were using newTilemap->putPixel() directly but received a | 
|---|
| 429 | // crash report about an "access violation". So now we've added | 
|---|
| 430 | // some checks to the operation. | 
|---|
| 431 | { | 
|---|
| 432 | const int u = tilePt.x-tilemapBounds.x; | 
|---|
| 433 | const int v = tilePt.y-tilemapBounds.y; | 
|---|
| 434 | ASSERT((u >= 0) && (v >= 0) && (u < newTilemap->width()) && (v < newTilemap->height())); | 
|---|
| 435 | doc::put_pixel(newTilemap.get(), u, v, tileIndex); | 
|---|
| 436 | } | 
|---|
| 437 | } | 
|---|
| 438 |  | 
|---|
| 439 | static_cast<Doc*>(dstLayer->sprite()->document()) | 
|---|
| 440 | ->notifyTilesetChanged(tileset); | 
|---|
| 441 |  | 
|---|
| 442 | dstCel->data()->setImage(newTilemap, dstLayer); | 
|---|
| 443 | dstCel->setPosition(grid.tileToCanvas(tilemapBounds.origin())); | 
|---|
| 444 | } | 
|---|
| 445 |  | 
|---|
| 446 | void modify_tilemap_cel_region( | 
|---|
| 447 | CmdSequence* cmds, | 
|---|
| 448 | doc::Cel* cel, | 
|---|
| 449 | doc::Tileset* tileset, | 
|---|
| 450 | const gfx::Region& region, | 
|---|
| 451 | const TilesetMode tilesetMode, | 
|---|
| 452 | const GetTileImageFunc& getTileImage, | 
|---|
| 453 | const gfx::Region& forceRegion) | 
|---|
| 454 | { | 
|---|
| 455 | OPS_TRACE( "modify_tilemap_cel_region %d %d %d %d\n", | 
|---|
| 456 | region.bounds().x, region.bounds().y, | 
|---|
| 457 | region.bounds().w, region.bounds().h); | 
|---|
| 458 |  | 
|---|
| 459 | if (region.isEmpty()) | 
|---|
| 460 | return; | 
|---|
| 461 |  | 
|---|
| 462 | ASSERT(cel->layer() && cel->layer()->isTilemap()); | 
|---|
| 463 | ASSERT(cel->image()->pixelFormat() == IMAGE_TILEMAP); | 
|---|
| 464 |  | 
|---|
| 465 | doc::LayerTilemap* tilemapLayer = static_cast<doc::LayerTilemap*>(cel->layer()); | 
|---|
| 466 |  | 
|---|
| 467 | Doc* doc = static_cast<Doc*>(tilemapLayer->sprite()->document()); | 
|---|
| 468 | bool addUndoToTileset = false; | 
|---|
| 469 | if (!tileset) { | 
|---|
| 470 | tileset = tilemapLayer->tileset(); | 
|---|
| 471 | addUndoToTileset = true; | 
|---|
| 472 | } | 
|---|
| 473 | doc::Grid grid = tileset->grid(); | 
|---|
| 474 | grid.origin(grid.origin() + cel->position()); | 
|---|
| 475 |  | 
|---|
| 476 | const gfx::Size tileSize = grid.tileSize(); | 
|---|
| 477 | const gfx::Rect oldTilemapBounds(grid.canvasToTile(cel->position()), | 
|---|
| 478 | cel->image()->bounds().size()); | 
|---|
| 479 | const gfx::Rect patchTilemapBounds = grid.canvasToTile(region.bounds()); | 
|---|
| 480 | const gfx::Rect newTilemapBounds = (oldTilemapBounds | patchTilemapBounds); | 
|---|
| 481 |  | 
|---|
| 482 | OPS_TRACE( "modify_tilemap_cel_region:\n" | 
|---|
| 483 | " - grid.origin       =%d %d\n" | 
|---|
| 484 | " - cel.position      =%d %d\n" | 
|---|
| 485 | " - oldTilemapBounds  =%d %d %d %d\n" | 
|---|
| 486 | " - patchTilemapBounds=%d %d %d %d (region.bounds = %d %d %d %d)\n" | 
|---|
| 487 | " - newTilemapBounds  =%d %d %d %d\n", | 
|---|
| 488 | grid.origin().x, grid.origin().y, | 
|---|
| 489 | cel->position().x, cel->position().y, | 
|---|
| 490 | oldTilemapBounds.x, oldTilemapBounds.y, oldTilemapBounds.w, oldTilemapBounds.h, | 
|---|
| 491 | patchTilemapBounds.x, patchTilemapBounds.y, patchTilemapBounds.w, patchTilemapBounds.h, | 
|---|
| 492 | region.bounds().x, region.bounds().y, region.bounds().w, region.bounds().h, | 
|---|
| 493 | newTilemapBounds.x, newTilemapBounds.y, newTilemapBounds.w, newTilemapBounds.h); | 
|---|
| 494 |  | 
|---|
| 495 | // Autogenerate tiles | 
|---|
| 496 | if (tilesetMode == TilesetMode::Auto || | 
|---|
| 497 | tilesetMode == TilesetMode::Stack) { | 
|---|
| 498 | // TODO create a smaller image | 
|---|
| 499 | doc::ImageRef newTilemap( | 
|---|
| 500 | doc::Image::create(IMAGE_TILEMAP, | 
|---|
| 501 | newTilemapBounds.w, | 
|---|
| 502 | newTilemapBounds.h)); | 
|---|
| 503 |  | 
|---|
| 504 | newTilemap->setMaskColor(doc::notile); | 
|---|
| 505 | newTilemap->clear(doc::notile);   // TODO find the tile with empty content? | 
|---|
| 506 | newTilemap->copy( | 
|---|
| 507 | cel->image(), | 
|---|
| 508 | gfx::Clip(oldTilemapBounds.x-newTilemapBounds.x, | 
|---|
| 509 | oldTilemapBounds.y-newTilemapBounds.y, 0, 0, | 
|---|
| 510 | oldTilemapBounds.w, oldTilemapBounds.h)); | 
|---|
| 511 |  | 
|---|
| 512 | gfx::Region tilePtsRgn; | 
|---|
| 513 |  | 
|---|
| 514 | // This region includes the modified region by the user + the | 
|---|
| 515 | // extra region added as we've incremented the tilemap size | 
|---|
| 516 | // (newTilemapBounds). | 
|---|
| 517 | gfx::Region regionToPatch(grid.tileToCanvas(newTilemapBounds)); | 
|---|
| 518 | regionToPatch -= gfx::Region(grid.tileToCanvas(oldTilemapBounds)); | 
|---|
| 519 | regionToPatch |= region; | 
|---|
| 520 |  | 
|---|
| 521 | std::vector<bool> modifiedTileIndexes(tileset->size(), false); | 
|---|
| 522 | std::vector<size_t> tilesHistogram(tileset->size(), 0); | 
|---|
| 523 | if (tilesetMode == TilesetMode::Auto) { | 
|---|
| 524 | for_each_tile_using_tileset( | 
|---|
| 525 | tileset, [tileset, &tilesHistogram](const doc::tile_t t){ | 
|---|
| 526 | if (t != doc::notile) { | 
|---|
| 527 | doc::tile_index ti = doc::tile_geti(t); | 
|---|
| 528 | if (ti >= 0 && ti < tileset->size()) | 
|---|
| 529 | ++tilesHistogram[ti]; | 
|---|
| 530 | } | 
|---|
| 531 | }); | 
|---|
| 532 | } | 
|---|
| 533 |  | 
|---|
| 534 | for (const gfx::Point& tilePt : grid.tilesInCanvasRegion(regionToPatch)) { | 
|---|
| 535 | const int u = tilePt.x-newTilemapBounds.x; | 
|---|
| 536 | const int v = tilePt.y-newTilemapBounds.y; | 
|---|
| 537 | OPS_TRACE( " - modify tile xy=%d %d uv=%d %d\n", tilePt.x, tilePt.y, u, v); | 
|---|
| 538 | if (!newTilemap->bounds().contains(u, v)) | 
|---|
| 539 | continue; | 
|---|
| 540 |  | 
|---|
| 541 | const doc::tile_t t = newTilemap->getPixel(u, v); | 
|---|
| 542 | const doc::tile_index ti = (t != doc::notile ? doc::tile_geti(t): doc::notile); | 
|---|
| 543 | const doc::ImageRef existentTileImage = tileset->get(ti); | 
|---|
| 544 |  | 
|---|
| 545 | const gfx::Rect tileInCanvasRc(grid.tileToCanvas(tilePt), tileSize); | 
|---|
| 546 | ImageRef tileImage(getTileImage(existentTileImage, tileInCanvasRc)); | 
|---|
| 547 | if (grid.hasMask()) | 
|---|
| 548 | mask_image(tileImage.get(), grid.mask().get()); | 
|---|
| 549 |  | 
|---|
| 550 | preprocess_transparent_pixels(tileImage.get()); | 
|---|
| 551 |  | 
|---|
| 552 | tile_index tileIndex; | 
|---|
| 553 | if (tileset->findTileIndex(tileImage, tileIndex)) { | 
|---|
| 554 | // We can re-use an existent tile (tileIndex) from the tileset | 
|---|
| 555 | } | 
|---|
| 556 | else if (tilesetMode == TilesetMode::Auto && | 
|---|
| 557 | t != doc::notile && | 
|---|
| 558 | ti >= 0 && ti < tilesHistogram.size() && | 
|---|
| 559 | // If the tile is just used once, we can modify this | 
|---|
| 560 | // same tile | 
|---|
| 561 | tilesHistogram[ti] == 1) { | 
|---|
| 562 | // Common case: Re-utilize the same tile in Auto mode. | 
|---|
| 563 | tileIndex = ti; | 
|---|
| 564 | cmds->executeAndAdd( | 
|---|
| 565 | new cmd::CopyTileRegion( | 
|---|
| 566 | existentTileImage.get(), | 
|---|
| 567 | tileImage.get(), | 
|---|
| 568 | gfx::Region(tileImage->bounds()), // TODO calculate better region | 
|---|
| 569 | gfx::Point(0, 0), | 
|---|
| 570 | false, | 
|---|
| 571 | tileIndex, | 
|---|
| 572 | tileset)); | 
|---|
| 573 | } | 
|---|
| 574 | else { | 
|---|
| 575 | auto addTile = new cmd::AddTile(tileset, tileImage); | 
|---|
| 576 | cmds->executeAndAdd(addTile); | 
|---|
| 577 |  | 
|---|
| 578 | tileIndex = addTile->tileIndex(); | 
|---|
| 579 | } | 
|---|
| 580 |  | 
|---|
| 581 | // If the tile changed, we have to remove the old tile index | 
|---|
| 582 | // (ti) from the histogram count. | 
|---|
| 583 | if (tilesetMode == TilesetMode::Auto && | 
|---|
| 584 | t != doc::notile && | 
|---|
| 585 | ti >= 0 && ti < tilesHistogram.size() && | 
|---|
| 586 | ti != tileIndex) { | 
|---|
| 587 | --tilesHistogram[ti]; | 
|---|
| 588 |  | 
|---|
| 589 | // It indicates that the tile "ti" was modified to | 
|---|
| 590 | // "tileIndex", so then, in case that we have to remove tiles, | 
|---|
| 591 | // we can check the ones that were modified & are unused. | 
|---|
| 592 | modifiedTileIndexes[ti] = true; | 
|---|
| 593 | } | 
|---|
| 594 |  | 
|---|
| 595 | OPS_TRACE( " - tile %d -> %d\n", | 
|---|
| 596 | (t == doc::notile ? -1: ti), | 
|---|
| 597 | tileIndex); | 
|---|
| 598 |  | 
|---|
| 599 | const doc::tile_t tile = doc::tile(tileIndex, 0); | 
|---|
| 600 | if (t != tile) { | 
|---|
| 601 | newTilemap->putPixel(u, v, tile); | 
|---|
| 602 | tilePtsRgn |= gfx::Region(gfx::Rect(u, v, 1, 1)); | 
|---|
| 603 |  | 
|---|
| 604 | // We add the new one tileIndex in the histogram count. | 
|---|
| 605 | if (tilesetMode == TilesetMode::Auto && | 
|---|
| 606 | tileIndex != doc::notile && | 
|---|
| 607 | tileIndex >= 0 && tileIndex < tilesHistogram.size()) { | 
|---|
| 608 | ++tilesHistogram[tileIndex]; | 
|---|
| 609 | } | 
|---|
| 610 | } | 
|---|
| 611 | } | 
|---|
| 612 |  | 
|---|
| 613 | if (newTilemap->width() != cel->image()->width() || | 
|---|
| 614 | newTilemap->height() != cel->image()->height()) { | 
|---|
| 615 | gfx::Point newPos = grid.tileToCanvas(newTilemapBounds.origin()); | 
|---|
| 616 | if (cel->position() != newPos) { | 
|---|
| 617 | cmds->executeAndAdd( | 
|---|
| 618 | new cmd::SetCelPosition(cel, newPos.x, newPos.y)); | 
|---|
| 619 | } | 
|---|
| 620 | cmds->executeAndAdd( | 
|---|
| 621 | new cmd::ReplaceImage(cel->sprite(), cel->imageRef(), newTilemap)); | 
|---|
| 622 | } | 
|---|
| 623 | else if (!tilePtsRgn.isEmpty()) { | 
|---|
| 624 | cmds->executeAndAdd( | 
|---|
| 625 | new cmd::CopyRegion( | 
|---|
| 626 | cel->image(), | 
|---|
| 627 | newTilemap.get(), | 
|---|
| 628 | tilePtsRgn, | 
|---|
| 629 | gfx::Point(0, 0))); | 
|---|
| 630 | } | 
|---|
| 631 |  | 
|---|
| 632 | // Remove unused tiles | 
|---|
| 633 | if (tilesetMode == TilesetMode::Auto) { | 
|---|
| 634 | remove_unused_tiles_from_tileset(cmds, tileset, | 
|---|
| 635 | tilesHistogram, | 
|---|
| 636 | modifiedTileIndexes); | 
|---|
| 637 | } | 
|---|
| 638 |  | 
|---|
| 639 | doc->notifyTilesetChanged(tileset); | 
|---|
| 640 | } | 
|---|
| 641 | // Modify active set of tiles manually / don't auto-generate new tiles | 
|---|
| 642 | else if (tilesetMode == TilesetMode::Manual) { | 
|---|
| 643 | std::vector<Mod> mods; | 
|---|
| 644 |  | 
|---|
| 645 | for (const gfx::Point& tilePt : grid.tilesInCanvasRegion(region)) { | 
|---|
| 646 | // Ignore modifications outside the tilemap | 
|---|
| 647 | if (!cel->image()->bounds().contains(tilePt.x, tilePt.y)) | 
|---|
| 648 | continue; | 
|---|
| 649 |  | 
|---|
| 650 | const doc::tile_t t = cel->image()->getPixel(tilePt.x, tilePt.y); | 
|---|
| 651 | if (t == doc::notile) | 
|---|
| 652 | continue; | 
|---|
| 653 |  | 
|---|
| 654 | const doc::tile_index ti = doc::tile_geti(t); | 
|---|
| 655 | const doc::ImageRef existentTileImage = tileset->get(ti); | 
|---|
| 656 | if (!existentTileImage) { | 
|---|
| 657 | // TODO add support to fill the tileset with the tile "ti" | 
|---|
| 658 | continue; | 
|---|
| 659 | } | 
|---|
| 660 |  | 
|---|
| 661 | const gfx::Rect tileInCanvasRc(grid.tileToCanvas(tilePt), tileSize); | 
|---|
| 662 | ImageRef tileImage(getTileImage(existentTileImage, tileInCanvasRc)); | 
|---|
| 663 | if (grid.hasMask()) | 
|---|
| 664 | mask_image(tileImage.get(), grid.mask().get()); | 
|---|
| 665 |  | 
|---|
| 666 | gfx::Region tileRgn(tileInCanvasRc); | 
|---|
| 667 | tileRgn.createIntersection(tileRgn, region); | 
|---|
| 668 | tileRgn.offset(-tileInCanvasRc.origin()); | 
|---|
| 669 |  | 
|---|
| 670 | ImageRef tileDstImage = tileset->get(ti); | 
|---|
| 671 |  | 
|---|
| 672 | // Compare with the original tile from the original tileset | 
|---|
| 673 | gfx::Region diffRgn; | 
|---|
| 674 | create_region_with_differences(tilemapLayer->tileset()->get(ti).get(), | 
|---|
| 675 | tileImage.get(), | 
|---|
| 676 | tileRgn.bounds(), | 
|---|
| 677 | diffRgn); | 
|---|
| 678 |  | 
|---|
| 679 | // Keep only the modified region for this specific modification | 
|---|
| 680 | tileRgn &= diffRgn; | 
|---|
| 681 |  | 
|---|
| 682 | if (!forceRegion.isEmpty()) { | 
|---|
| 683 | gfx::Region fr(forceRegion); | 
|---|
| 684 | fr.offset(-tileInCanvasRc.origin()); | 
|---|
| 685 | tileRgn |= fr; | 
|---|
| 686 | } | 
|---|
| 687 |  | 
|---|
| 688 | if (!tileRgn.isEmpty()) { | 
|---|
| 689 | if (addUndoToTileset) { | 
|---|
| 690 | Mod mod; | 
|---|
| 691 | mod.tileIndex = ti; | 
|---|
| 692 | mod.tileDstImage = tileDstImage; | 
|---|
| 693 | mod.tileImage = tileImage; | 
|---|
| 694 | mod.tileRgn = tileRgn; | 
|---|
| 695 | mods.push_back(mod); | 
|---|
| 696 | } | 
|---|
| 697 | else { | 
|---|
| 698 | copy_image(tileDstImage.get(), | 
|---|
| 699 | tileImage.get(), | 
|---|
| 700 | tileRgn); | 
|---|
| 701 | tileset->notifyTileContentChange(ti); | 
|---|
| 702 | } | 
|---|
| 703 | } | 
|---|
| 704 | } | 
|---|
| 705 |  | 
|---|
| 706 | // Apply all modifications to tiles | 
|---|
| 707 | if (addUndoToTileset) { | 
|---|
| 708 | for (auto& mod : mods) { | 
|---|
| 709 | // TODO avoid creating several CopyTileRegion for the same tile, | 
|---|
| 710 | //      merge all mods for the same tile in some way | 
|---|
| 711 | cmds->executeAndAdd( | 
|---|
| 712 | new cmd::CopyTileRegion( | 
|---|
| 713 | mod.tileDstImage.get(), | 
|---|
| 714 | mod.tileImage.get(), | 
|---|
| 715 | mod.tileRgn, | 
|---|
| 716 | gfx::Point(0, 0), | 
|---|
| 717 | false, | 
|---|
| 718 | mod.tileIndex, | 
|---|
| 719 | tileset)); | 
|---|
| 720 | } | 
|---|
| 721 | } | 
|---|
| 722 |  | 
|---|
| 723 | doc->notifyTilesetChanged(tileset); | 
|---|
| 724 | } | 
|---|
| 725 |  | 
|---|
| 726 | #ifdef _DEBUG | 
|---|
| 727 | tileset->assertValidHashTable(); | 
|---|
| 728 | #endif | 
|---|
| 729 | } | 
|---|
| 730 |  | 
|---|
| 731 | void clear_mask_from_cel(CmdSequence* cmds, | 
|---|
| 732 | doc::Cel* cel, | 
|---|
| 733 | const TilemapMode tilemapMode, | 
|---|
| 734 | const TilesetMode tilesetMode) | 
|---|
| 735 | { | 
|---|
| 736 | ASSERT(cmds); | 
|---|
| 737 | ASSERT(cel); | 
|---|
| 738 | ASSERT(cel->layer()); | 
|---|
| 739 |  | 
|---|
| 740 | if (cel->layer()->isTilemap() && tilemapMode == TilemapMode::Pixels) { | 
|---|
| 741 | Doc* doc = static_cast<Doc*>(cel->document()); | 
|---|
| 742 |  | 
|---|
| 743 | // Simple case (there is no visible selection, so we remove the | 
|---|
| 744 | // whole cel) | 
|---|
| 745 | if (!doc->isMaskVisible()) { | 
|---|
| 746 | cmds->executeAndAdd(new cmd::ClearCel(cel)); | 
|---|
| 747 | return; | 
|---|
| 748 | } | 
|---|
| 749 |  | 
|---|
| 750 | color_t bgcolor = doc->bgColor(cel->layer()); | 
|---|
| 751 | doc::Mask* mask = doc->mask(); | 
|---|
| 752 |  | 
|---|
| 753 | modify_tilemap_cel_region( | 
|---|
| 754 | cmds, cel, nullptr, | 
|---|
| 755 | gfx::Region(doc->mask()->bounds()), | 
|---|
| 756 | tilesetMode, | 
|---|
| 757 | [bgcolor, mask](const doc::ImageRef& origTile, | 
|---|
| 758 | const gfx::Rect& tileBoundsInCanvas) -> doc::ImageRef { | 
|---|
| 759 | doc::ImageRef modified(doc::Image::createCopy(origTile.get())); | 
|---|
| 760 | doc::algorithm::fill_selection( | 
|---|
| 761 | modified.get(), | 
|---|
| 762 | tileBoundsInCanvas, | 
|---|
| 763 | mask, | 
|---|
| 764 | bgcolor, | 
|---|
| 765 | nullptr); | 
|---|
| 766 | return modified; | 
|---|
| 767 | }); | 
|---|
| 768 | } | 
|---|
| 769 | else { | 
|---|
| 770 | cmds->executeAndAdd(new cmd::ClearMask(cel)); | 
|---|
| 771 | } | 
|---|
| 772 | } | 
|---|
| 773 |  | 
|---|
| 774 | static void remove_unused_tiles_from_tileset( | 
|---|
| 775 | CmdSequence* cmds, | 
|---|
| 776 | doc::Tileset* tileset, | 
|---|
| 777 | std::vector<size_t>& tilesHistogram, | 
|---|
| 778 | const std::vector<bool>& modifiedTileIndexes) | 
|---|
| 779 | { | 
|---|
| 780 | OPS_TRACE( "remove_unused_tiles_from_tileset\n"); | 
|---|
| 781 |  | 
|---|
| 782 | int n = tileset->size(); | 
|---|
| 783 | #ifdef _DEBUG | 
|---|
| 784 | // Histogram just to check that we've a correct tilesHistogram | 
|---|
| 785 | std::vector<size_t> tilesHistogram2(n, 0); | 
|---|
| 786 | #endif | 
|---|
| 787 |  | 
|---|
| 788 | for_each_tile_using_tileset( | 
|---|
| 789 | tileset, | 
|---|
| 790 | [&n | 
|---|
| 791 | #ifdef _DEBUG | 
|---|
| 792 | , &tilesHistogram2 | 
|---|
| 793 | #endif | 
|---|
| 794 | ](const doc::tile_t t){ | 
|---|
| 795 | if (t != doc::notile) { | 
|---|
| 796 | const doc::tile_index ti = doc::tile_geti(t); | 
|---|
| 797 | n = std::max<int>(n, ti+1); | 
|---|
| 798 | #ifdef _DEBUG | 
|---|
| 799 | // This check is necessary in case the tilemap has a reference | 
|---|
| 800 | // to a tile outside the valid range (e.g. when we resize the | 
|---|
| 801 | // tileset deleting tiles that will not be present anymore) | 
|---|
| 802 | if (ti >= 0 && ti < tilesHistogram2.size()) | 
|---|
| 803 | ++tilesHistogram2[ti]; | 
|---|
| 804 | #endif | 
|---|
| 805 | } | 
|---|
| 806 | }); | 
|---|
| 807 |  | 
|---|
| 808 | #ifdef _DEBUG | 
|---|
| 809 | for (int k=0; k<tilesHistogram.size(); ++k) { | 
|---|
| 810 | OPS_TRACE( "comparing [%d] -> %d vs %d\n", k, tilesHistogram[k], tilesHistogram2[k]); | 
|---|
| 811 | ASSERT(tilesHistogram[k] == tilesHistogram2[k]); | 
|---|
| 812 | } | 
|---|
| 813 | #endif | 
|---|
| 814 |  | 
|---|
| 815 | doc::Remap remap(n); | 
|---|
| 816 | doc::tile_index ti, tj; | 
|---|
| 817 | ti = tj = 0; | 
|---|
| 818 | for (; ti<remap.size(); ++ti) { | 
|---|
| 819 | OPS_TRACE( " - ti=%d tj=%d tilesHistogram[%d]=%d\n", | 
|---|
| 820 | ti, tj, ti, (ti < tilesHistogram.size() ? tilesHistogram[ti]: 0)); | 
|---|
| 821 | if (ti < tilesHistogram.size() && | 
|---|
| 822 | tilesHistogram[ti] == 0 && | 
|---|
| 823 | modifiedTileIndexes[ti]) { | 
|---|
| 824 | cmds->executeAndAdd(new cmd::RemoveTile(tileset, tj)); | 
|---|
| 825 | // Map to nothing, so the map can be invertible | 
|---|
| 826 | remap.notile(ti); | 
|---|
| 827 | } | 
|---|
| 828 | else { | 
|---|
| 829 | remap.map(ti, tj++); | 
|---|
| 830 | } | 
|---|
| 831 | } | 
|---|
| 832 |  | 
|---|
| 833 | if (!remap.isIdentity()) { | 
|---|
| 834 | #ifdef _DEBUG | 
|---|
| 835 | for (ti=0; ti<remap.size(); ++ti) { | 
|---|
| 836 | OPS_TRACE( " - remap tile[%d] -> %d\n", ti, remap[ti]); | 
|---|
| 837 | } | 
|---|
| 838 | #endif | 
|---|
| 839 | cmds->executeAndAdd(new cmd::RemapTilemaps(tileset, remap)); | 
|---|
| 840 | } | 
|---|
| 841 | } | 
|---|
| 842 |  | 
|---|
| 843 | void move_tiles_in_tileset( | 
|---|
| 844 | CmdSequence* cmds, | 
|---|
| 845 | doc::Tileset* tileset, | 
|---|
| 846 | doc::PalettePicks& picks, | 
|---|
| 847 | int& currentEntry, | 
|---|
| 848 | int beforeIndex) | 
|---|
| 849 | { | 
|---|
| 850 | OPS_TRACE( "move_tiles_in_tileset\n"); | 
|---|
| 851 |  | 
|---|
| 852 | // We cannot move the empty tile (index 0) no any place | 
|---|
| 853 | if (beforeIndex == 0) | 
|---|
| 854 | ++beforeIndex; | 
|---|
| 855 | if (picks.size() > 0 && picks[0]) | 
|---|
| 856 | picks[0] = false; | 
|---|
| 857 | if (!picks.picks()) | 
|---|
| 858 | return; | 
|---|
| 859 |  | 
|---|
| 860 | picks.resize(std::max<int>(picks.size(), beforeIndex)); | 
|---|
| 861 |  | 
|---|
| 862 | int n = beforeIndex - tileset->size(); | 
|---|
| 863 | if (n > 0) { | 
|---|
| 864 | while (n-- > 0) | 
|---|
| 865 | cmds->executeAndAdd(new cmd::AddTile(tileset, tileset->makeEmptyTile())); | 
|---|
| 866 | } | 
|---|
| 867 |  | 
|---|
| 868 | Remap remap = create_remap_to_move_picks(picks, beforeIndex); | 
|---|
| 869 | cmds->executeAndAdd(new cmd::RemapTileset(tileset, remap)); | 
|---|
| 870 |  | 
|---|
| 871 | // New selection | 
|---|
| 872 | auto oldPicks = picks; | 
|---|
| 873 | for (int i=0; i<picks.size(); ++i) | 
|---|
| 874 | picks[remap[i]] = oldPicks[i]; | 
|---|
| 875 | currentEntry = remap[currentEntry]; | 
|---|
| 876 | } | 
|---|
| 877 |  | 
|---|
| 878 | void copy_tiles_in_tileset( | 
|---|
| 879 | CmdSequence* cmds, | 
|---|
| 880 | doc::Tileset* tileset, | 
|---|
| 881 | doc::PalettePicks& picks, | 
|---|
| 882 | int& currentEntry, | 
|---|
| 883 | int beforeIndex) | 
|---|
| 884 | { | 
|---|
| 885 | // We cannot move tiles before the empty tile | 
|---|
| 886 | if (beforeIndex == 0) | 
|---|
| 887 | ++beforeIndex; | 
|---|
| 888 |  | 
|---|
| 889 | OPS_TRACE( "copy_tiles_in_tileset beforeIndex=%d npicks=%d\n", beforeIndex, picks.picks()); | 
|---|
| 890 |  | 
|---|
| 891 | std::vector<ImageRef> newTiles; | 
|---|
| 892 | for (int i=0; i<picks.size(); ++i) { | 
|---|
| 893 | if (!picks[i]) | 
|---|
| 894 | continue; | 
|---|
| 895 | else if (i >= 0 && i < tileset->size()) { | 
|---|
| 896 | newTiles.emplace_back(Image::createCopy(tileset->get(i).get())); | 
|---|
| 897 | } | 
|---|
| 898 | else { | 
|---|
| 899 | newTiles.emplace_back(tileset->makeEmptyTile()); | 
|---|
| 900 | } | 
|---|
| 901 | } | 
|---|
| 902 |  | 
|---|
| 903 | int n; | 
|---|
| 904 | if (beforeIndex >= picks.size()) { | 
|---|
| 905 | n = beforeIndex; | 
|---|
| 906 | picks.resize(n); | 
|---|
| 907 | } | 
|---|
| 908 | else { | 
|---|
| 909 | n = tileset->size(); | 
|---|
| 910 | } | 
|---|
| 911 |  | 
|---|
| 912 | const int npicks = picks.picks(); | 
|---|
| 913 | const int m = n + npicks; | 
|---|
| 914 | int j = 0; | 
|---|
| 915 | picks.resize(m); | 
|---|
| 916 | ASSERT(newTiles.size() == npicks); | 
|---|
| 917 | for (int i=0; i<m; ++i) { | 
|---|
| 918 | picks[i] = (i >= beforeIndex && i < beforeIndex + npicks); | 
|---|
| 919 | if (picks[i]) { | 
|---|
| 920 | // Fill the gap between the end of the tileset and the | 
|---|
| 921 | // "beforeIndex" with empty tiles | 
|---|
| 922 | while (tileset->size() < i) | 
|---|
| 923 | cmds->executeAndAdd(new cmd::AddTile(tileset, tileset->makeEmptyTile())); | 
|---|
| 924 |  | 
|---|
| 925 | tileset->insert(i, newTiles[j++]); | 
|---|
| 926 | cmds->executeAndAdd(new cmd::AddTile(tileset, i)); | 
|---|
| 927 | } | 
|---|
| 928 | } | 
|---|
| 929 | } | 
|---|
| 930 |  | 
|---|
| 931 | } // namespace app | 
|---|
| 932 |  | 
|---|