| 1 | // Aseprite Render Library |
| 2 | // Copyright (C) 2019-2022 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 "render/render.h" |
| 13 | |
| 14 | #include "doc/blend_internals.h" |
| 15 | #include "doc/blend_mode.h" |
| 16 | #include "doc/doc.h" |
| 17 | #include "doc/handle_anidir.h" |
| 18 | #include "doc/image_impl.h" |
| 19 | #include "doc/layer_tilemap.h" |
| 20 | #include "doc/tileset.h" |
| 21 | #include "doc/tilesets.h" |
| 22 | #include "gfx/clip.h" |
| 23 | #include "gfx/region.h" |
| 24 | |
| 25 | #include <cmath> |
| 26 | |
| 27 | #define TRACE_RENDER_CEL(...) // TRACE |
| 28 | |
| 29 | namespace render { |
| 30 | |
| 31 | namespace { |
| 32 | |
| 33 | ////////////////////////////////////////////////////////////////////// |
| 34 | // Scaled composite |
| 35 | |
| 36 | template<class DstTraits, class SrcTraits> |
| 37 | class BlenderHelper { |
| 38 | BlendFunc m_blendFunc; |
| 39 | color_t m_mask_color; |
| 40 | public: |
| 41 | BlenderHelper(const Image* src, const Palette* pal, BlendMode blendMode, const bool newBlend) |
| 42 | { |
| 43 | m_blendFunc = SrcTraits::get_blender(blendMode, newBlend); |
| 44 | m_mask_color = src->maskColor(); |
| 45 | } |
| 46 | inline typename DstTraits::pixel_t |
| 47 | operator()(const typename DstTraits::pixel_t& dst, |
| 48 | const typename SrcTraits::pixel_t& src, |
| 49 | const int opacity) |
| 50 | { |
| 51 | if (src != m_mask_color) |
| 52 | return (*m_blendFunc)(dst, src, opacity); |
| 53 | else |
| 54 | return dst; |
| 55 | } |
| 56 | }; |
| 57 | |
| 58 | template<> |
| 59 | class BlenderHelper<RgbTraits, GrayscaleTraits> { |
| 60 | BlendFunc m_blendFunc; |
| 61 | color_t m_mask_color; |
| 62 | public: |
| 63 | BlenderHelper(const Image* src, const Palette* pal, BlendMode blendMode, const bool newBlend) |
| 64 | { |
| 65 | m_blendFunc = RgbTraits::get_blender(blendMode, newBlend); |
| 66 | m_mask_color = src->maskColor(); |
| 67 | } |
| 68 | inline RgbTraits::pixel_t |
| 69 | operator()(const RgbTraits::pixel_t& dst, |
| 70 | const GrayscaleTraits::pixel_t& src, |
| 71 | const int opacity) |
| 72 | { |
| 73 | if (src != m_mask_color) { |
| 74 | int v = graya_getv(src); |
| 75 | return (*m_blendFunc)(dst, rgba(v, v, v, graya_geta(src)), opacity); |
| 76 | } |
| 77 | else |
| 78 | return dst; |
| 79 | } |
| 80 | }; |
| 81 | |
| 82 | template<> |
| 83 | class BlenderHelper<RgbTraits, IndexedTraits> { |
| 84 | const Palette* m_pal; |
| 85 | BlendMode m_blendMode; |
| 86 | BlendFunc m_blendFunc; |
| 87 | color_t m_mask_color; |
| 88 | public: |
| 89 | BlenderHelper(const Image* src, const Palette* pal, BlendMode blendMode, const bool newBlend) |
| 90 | { |
| 91 | m_blendMode = blendMode; |
| 92 | m_blendFunc = RgbTraits::get_blender(blendMode, newBlend); |
| 93 | m_mask_color = src->maskColor(); |
| 94 | m_pal = pal; |
| 95 | } |
| 96 | inline RgbTraits::pixel_t |
| 97 | operator()(const RgbTraits::pixel_t& dst, |
| 98 | const IndexedTraits::pixel_t& src, |
| 99 | const int opacity) |
| 100 | { |
| 101 | if (m_blendMode == BlendMode::SRC) { |
| 102 | return m_pal->getEntry(src); |
| 103 | } |
| 104 | else { |
| 105 | if (src != m_mask_color) { |
| 106 | return (*m_blendFunc)(dst, m_pal->getEntry(src), opacity); |
| 107 | } |
| 108 | else |
| 109 | return dst; |
| 110 | } |
| 111 | } |
| 112 | }; |
| 113 | |
| 114 | template<> |
| 115 | class BlenderHelper<IndexedTraits, IndexedTraits> { |
| 116 | BlendMode m_blendMode; |
| 117 | color_t m_maskColor; |
| 118 | int m_paletteSize; |
| 119 | public: |
| 120 | BlenderHelper(const Image* src, const Palette* pal, BlendMode blendMode, const bool newBlend) |
| 121 | { |
| 122 | m_blendMode = blendMode; |
| 123 | m_maskColor = src->maskColor(); |
| 124 | m_paletteSize = pal->size(); |
| 125 | } |
| 126 | inline IndexedTraits::pixel_t |
| 127 | operator()(const IndexedTraits::pixel_t& dst, |
| 128 | const IndexedTraits::pixel_t& src, |
| 129 | const int opacity) |
| 130 | { |
| 131 | if (m_blendMode == BlendMode::SRC) { |
| 132 | return src; |
| 133 | } |
| 134 | else if (m_blendMode == BlendMode::DST_OVER) { |
| 135 | if (dst != m_maskColor) |
| 136 | return dst; |
| 137 | else |
| 138 | return src; |
| 139 | } |
| 140 | else { |
| 141 | if (src != m_maskColor && src < m_paletteSize) |
| 142 | return src; |
| 143 | else |
| 144 | return dst; |
| 145 | } |
| 146 | } |
| 147 | }; |
| 148 | |
| 149 | template<class DstTraits, class SrcTraits> |
| 150 | void composite_image_without_scale( |
| 151 | Image* dst, const Image* src, const Palette* pal, |
| 152 | const gfx::ClipF& areaF, |
| 153 | const int opacity, |
| 154 | const BlendMode blendMode, |
| 155 | const double sx, |
| 156 | const double sy, |
| 157 | const bool newBlend) |
| 158 | { |
| 159 | ASSERT(dst); |
| 160 | ASSERT(src); |
| 161 | ASSERT(DstTraits::pixel_format == dst->pixelFormat()); |
| 162 | ASSERT(SrcTraits::pixel_format == src->pixelFormat()); |
| 163 | |
| 164 | BlenderHelper<DstTraits, SrcTraits> blender(src, pal, blendMode, newBlend); |
| 165 | |
| 166 | gfx::Clip area(areaF); |
| 167 | if (!area.clip(dst->width(), dst->height(), |
| 168 | src->width(), src->height())) |
| 169 | return; |
| 170 | |
| 171 | gfx::Rect srcBounds = area.srcBounds(); |
| 172 | gfx::Rect dstBounds = area.dstBounds(); |
| 173 | int bottom = dstBounds.y2()-1; |
| 174 | |
| 175 | ASSERT(!srcBounds.isEmpty()); |
| 176 | |
| 177 | // Lock all necessary bits |
| 178 | const LockImageBits<SrcTraits> srcBits(src, srcBounds); |
| 179 | LockImageBits<DstTraits> dstBits(dst, dstBounds); |
| 180 | typename LockImageBits<SrcTraits>::const_iterator src_it = srcBits.begin(); |
| 181 | #ifdef _DEBUG |
| 182 | typename LockImageBits<SrcTraits>::const_iterator src_end = srcBits.end(); |
| 183 | #endif |
| 184 | typename LockImageBits<DstTraits>::iterator dst_it, dst_end; |
| 185 | |
| 186 | // For each line to draw of the source image... |
| 187 | dstBounds.h = 1; |
| 188 | for (int y=0; y<srcBounds.h; ++y) { |
| 189 | dst_it = dstBits.begin_area(dstBounds); |
| 190 | dst_end = dstBits.end_area(dstBounds); |
| 191 | |
| 192 | for (int x=0; x<srcBounds.w; ++x) { |
| 193 | ASSERT(src_it >= srcBits.begin() && src_it < src_end); |
| 194 | ASSERT(dst_it >= dstBits.begin() && dst_it < dst_end); |
| 195 | *dst_it = blender(*dst_it, *src_it, opacity); |
| 196 | ++src_it; |
| 197 | ++dst_it; |
| 198 | } |
| 199 | |
| 200 | if (++dstBounds.y > bottom) |
| 201 | break; |
| 202 | } |
| 203 | } |
| 204 | |
| 205 | template<class DstTraits, class SrcTraits> |
| 206 | void composite_image_scale_up( |
| 207 | Image* dst, const Image* src, const Palette* pal, |
| 208 | const gfx::ClipF& areaF, |
| 209 | const int opacity, |
| 210 | const BlendMode blendMode, |
| 211 | const double sx, |
| 212 | const double sy, |
| 213 | const bool newBlend) |
| 214 | { |
| 215 | ASSERT(dst); |
| 216 | ASSERT(src); |
| 217 | ASSERT(DstTraits::pixel_format == dst->pixelFormat()); |
| 218 | ASSERT(SrcTraits::pixel_format == src->pixelFormat()); |
| 219 | |
| 220 | gfx::Clip area(areaF); |
| 221 | if (!area.clip(dst->width(), dst->height(), |
| 222 | int(sx*double(src->width())), |
| 223 | int(sy*double(src->height())))) |
| 224 | return; |
| 225 | |
| 226 | BlenderHelper<DstTraits, SrcTraits> blender(src, pal, blendMode, newBlend); |
| 227 | int px_x, px_y; |
| 228 | int px_w = int(sx); |
| 229 | int px_h = int(sy); |
| 230 | |
| 231 | // We've received crash reports about these values being 0 when it's |
| 232 | // called from Render::renderImage() when the projection is scaled |
| 233 | // to the cel bounds (this can happen only when a reference layer is |
| 234 | // scaled, but when a reference layer is visible we shouldn't be |
| 235 | // here, we should be using the composite_image_general(), see the |
| 236 | // "finegrain" var in Render::getImageComposition()). |
| 237 | ASSERT(px_w > 0); |
| 238 | ASSERT(px_h > 0); |
| 239 | if (px_w <= 0 || px_h <= 0) |
| 240 | return; |
| 241 | |
| 242 | int first_px_w = px_w - (area.src.x % px_w); |
| 243 | int first_px_h = px_h - (area.src.y % px_h); |
| 244 | |
| 245 | gfx::Rect srcBounds = area.srcBounds(); |
| 246 | srcBounds.w = (srcBounds.x+srcBounds.w)/px_w - srcBounds.x/px_w; |
| 247 | srcBounds.h = (srcBounds.y+srcBounds.h)/px_h - srcBounds.y/px_h; |
| 248 | srcBounds.x /= px_w; |
| 249 | srcBounds.y /= px_h; |
| 250 | if ((area.src.x+area.size.w) % px_w > 0) ++srcBounds.w; |
| 251 | if ((area.src.y+area.size.h) % px_h > 0) ++srcBounds.h; |
| 252 | if (srcBounds.isEmpty()) |
| 253 | return; |
| 254 | |
| 255 | gfx::Rect dstBounds = area.dstBounds(); |
| 256 | int bottom = dstBounds.y2()-1; |
| 257 | int line_h; |
| 258 | |
| 259 | // the scanline variable is used to blend src/dst pixels one time for each pixel |
| 260 | typedef std::vector<typename DstTraits::pixel_t> Scanline; |
| 261 | Scanline scanline(srcBounds.w); |
| 262 | typename Scanline::iterator scanline_it; |
| 263 | #ifdef _DEBUG |
| 264 | typename Scanline::iterator scanline_end = scanline.end(); |
| 265 | #endif |
| 266 | |
| 267 | // Lock all necessary bits |
| 268 | const LockImageBits<SrcTraits> srcBits(src, srcBounds); |
| 269 | LockImageBits<DstTraits> dstBits(dst, dstBounds); |
| 270 | typename LockImageBits<SrcTraits>::const_iterator src_it = srcBits.begin(); |
| 271 | #ifdef _DEBUG |
| 272 | typename LockImageBits<SrcTraits>::const_iterator src_end = srcBits.end(); |
| 273 | #endif |
| 274 | typename LockImageBits<DstTraits>::iterator dst_it, dst_end; |
| 275 | |
| 276 | // For each line to draw of the source image... |
| 277 | dstBounds.h = 1; |
| 278 | for (int y=0; y<srcBounds.h; ++y) { |
| 279 | dst_it = dstBits.begin_area(dstBounds); |
| 280 | dst_end = dstBits.end_area(dstBounds); |
| 281 | |
| 282 | // Read 'src' and 'dst' and blend them, put the result in `scanline' |
| 283 | scanline_it = scanline.begin(); |
| 284 | for (int x=0; x<srcBounds.w; ++x) { |
| 285 | ASSERT(src_it >= srcBits.begin() && src_it < src_end); |
| 286 | ASSERT(dst_it >= dstBits.begin() && dst_it < dst_end); |
| 287 | ASSERT(scanline_it >= scanline.begin() && scanline_it < scanline_end); |
| 288 | |
| 289 | *scanline_it = blender(*dst_it, *src_it, opacity); |
| 290 | ++src_it; |
| 291 | |
| 292 | int delta; |
| 293 | if (x == 0) |
| 294 | delta = first_px_w; |
| 295 | else |
| 296 | delta = px_w; |
| 297 | |
| 298 | while (dst_it != dst_end && delta-- > 0) |
| 299 | ++dst_it; |
| 300 | |
| 301 | ++scanline_it; |
| 302 | } |
| 303 | |
| 304 | // Get the 'height' of the line to be painted in 'dst' |
| 305 | if ((y == 0) && (first_px_h > 0)) |
| 306 | line_h = first_px_h; |
| 307 | else |
| 308 | line_h = px_h; |
| 309 | |
| 310 | // Draw the line in 'dst' |
| 311 | for (px_y=0; px_y<line_h; ++px_y) { |
| 312 | dst_it = dstBits.begin_area(dstBounds); |
| 313 | dst_end = dstBits.end_area(dstBounds); |
| 314 | scanline_it = scanline.begin(); |
| 315 | |
| 316 | int x = 0; |
| 317 | |
| 318 | // first pixel |
| 319 | for (px_x=0; px_x<first_px_w; ++px_x) { |
| 320 | ASSERT(scanline_it != scanline_end); |
| 321 | ASSERT(dst_it != dst_end); |
| 322 | |
| 323 | *dst_it = *scanline_it; |
| 324 | |
| 325 | ++dst_it; |
| 326 | if (dst_it == dst_end) |
| 327 | goto done_with_line; |
| 328 | } |
| 329 | |
| 330 | ++scanline_it; |
| 331 | ++x; |
| 332 | |
| 333 | // the rest of the line |
| 334 | for (; x<srcBounds.w; ++x) { |
| 335 | for (px_x=0; px_x<px_w; ++px_x) { |
| 336 | ASSERT(dst_it != dst_end); |
| 337 | |
| 338 | *dst_it = *scanline_it; |
| 339 | |
| 340 | ++dst_it; |
| 341 | if (dst_it == dst_end) |
| 342 | goto done_with_line; |
| 343 | } |
| 344 | |
| 345 | ++scanline_it; |
| 346 | } |
| 347 | |
| 348 | done_with_line:; |
| 349 | if (++dstBounds.y > bottom) |
| 350 | goto done_with_blit; |
| 351 | } |
| 352 | } |
| 353 | |
| 354 | done_with_blit:; |
| 355 | } |
| 356 | |
| 357 | template<class DstTraits, class SrcTraits> |
| 358 | void composite_image_scale_down( |
| 359 | Image* dst, const Image* src, const Palette* pal, |
| 360 | const gfx::ClipF& areaF, |
| 361 | const int opacity, |
| 362 | const BlendMode blendMode, |
| 363 | const double sx, |
| 364 | const double sy, |
| 365 | const bool newBlend) |
| 366 | { |
| 367 | ASSERT(dst); |
| 368 | ASSERT(src); |
| 369 | ASSERT(DstTraits::pixel_format == dst->pixelFormat()); |
| 370 | ASSERT(SrcTraits::pixel_format == src->pixelFormat()); |
| 371 | |
| 372 | gfx::Clip area(areaF); |
| 373 | if (!area.clip(dst->width(), dst->height(), |
| 374 | int(sx*double(src->width())), |
| 375 | int(sy*double(src->height())))) |
| 376 | return; |
| 377 | |
| 378 | BlenderHelper<DstTraits, SrcTraits> blender(src, pal, blendMode, newBlend); |
| 379 | int step_w = int(1.0 / sx); |
| 380 | int step_h = int(1.0 / sy); |
| 381 | if (step_w < 1 || step_h < 1) |
| 382 | return; |
| 383 | |
| 384 | gfx::Rect srcBounds = area.srcBounds(); |
| 385 | srcBounds.w = (srcBounds.x+srcBounds.w)*step_w - srcBounds.x*step_w; |
| 386 | srcBounds.h = (srcBounds.y+srcBounds.h)*step_h - srcBounds.y*step_h; |
| 387 | srcBounds.x *= step_w; |
| 388 | srcBounds.y *= step_h; |
| 389 | if (srcBounds.isEmpty()) |
| 390 | return; |
| 391 | |
| 392 | gfx::Rect dstBounds = area.dstBounds(); |
| 393 | |
| 394 | // Lock all necessary bits |
| 395 | const LockImageBits<SrcTraits> srcBits(src, srcBounds); |
| 396 | LockImageBits<DstTraits> dstBits(dst, dstBounds); |
| 397 | auto src_it = srcBits.begin(); |
| 398 | auto dst_it = dstBits.begin(); |
| 399 | #ifdef _DEBUG |
| 400 | auto src_end = srcBits.end(); |
| 401 | auto dst_end = dstBits.end(); |
| 402 | #endif |
| 403 | |
| 404 | // Adjust to src_it for each line |
| 405 | int adjust_per_line = (dstBounds.w*step_w)*(step_h-1); |
| 406 | |
| 407 | // For each line to draw of the source image... |
| 408 | for (int y=0; y<dstBounds.h; ++y) { |
| 409 | for (int x=0; x<dstBounds.w; ++x) { |
| 410 | ASSERT(src_it >= srcBits.begin() && src_it < src_end); |
| 411 | ASSERT(dst_it >= dstBits.begin() && dst_it < dst_end); |
| 412 | |
| 413 | *dst_it = blender(*dst_it, *src_it, opacity); |
| 414 | |
| 415 | // Skip columns |
| 416 | src_it += step_w; |
| 417 | ++dst_it; |
| 418 | } |
| 419 | |
| 420 | // Skip rows |
| 421 | src_it += adjust_per_line; |
| 422 | } |
| 423 | } |
| 424 | |
| 425 | template<class DstTraits, class SrcTraits> |
| 426 | void composite_image_general( |
| 427 | Image* dst, const Image* src, const Palette* pal, |
| 428 | const gfx::ClipF& areaF, |
| 429 | const int opacity, |
| 430 | const BlendMode blendMode, |
| 431 | const double sx, |
| 432 | const double sy, |
| 433 | const bool newBlend) |
| 434 | { |
| 435 | ASSERT(dst); |
| 436 | ASSERT(src); |
| 437 | ASSERT(DstTraits::pixel_format == dst->pixelFormat()); |
| 438 | ASSERT(SrcTraits::pixel_format == src->pixelFormat()); |
| 439 | |
| 440 | gfx::ClipF area(areaF); |
| 441 | if (!area.clip(dst->width(), dst->height(), |
| 442 | sx*src->width(), sy*src->height())) |
| 443 | return; |
| 444 | |
| 445 | BlenderHelper<DstTraits, SrcTraits> blender(src, pal, blendMode, newBlend); |
| 446 | |
| 447 | gfx::Rect dstBounds( |
| 448 | area.dstBounds().x, area.dstBounds().y, |
| 449 | int(std::ceil(area.dstBounds().w)), |
| 450 | int(std::ceil(area.dstBounds().h))); |
| 451 | gfx::RectF srcBounds = area.srcBounds(); |
| 452 | |
| 453 | dstBounds &= dst->bounds(); |
| 454 | |
| 455 | int dstY = dstBounds.y; |
| 456 | double srcXStart = srcBounds.x / sx; |
| 457 | double srcXDelta = 1.0 / sx; |
| 458 | int srcWidth = src->width(); |
| 459 | for (int y=0; y<dstBounds.h; ++y, ++dstY) { |
| 460 | int srcY = int((srcBounds.y+double(y)) / sy); |
| 461 | double srcX = srcXStart; |
| 462 | int oldSrcX; |
| 463 | |
| 464 | // Out of bounds |
| 465 | if (srcY >= src->height()) |
| 466 | break; |
| 467 | |
| 468 | ASSERT(srcY >= 0 && srcY < src->height()); |
| 469 | ASSERT(dstY >= 0 && dstY < dst->height()); |
| 470 | |
| 471 | auto dstPtr = get_pixel_address_fast<DstTraits>(dst, dstBounds.x, dstY); |
| 472 | auto srcPtr = get_pixel_address_fast<SrcTraits>(src, int(srcX), srcY); |
| 473 | |
| 474 | #if _DEBUG |
| 475 | int dstX = dstBounds.x; |
| 476 | #endif |
| 477 | |
| 478 | for (int x=0; x<dstBounds.w; ++dstPtr) { |
| 479 | ASSERT(dstX >= 0 && dstX < dst->width()); |
| 480 | ASSERT(srcX >= 0 && srcX < src->width()); |
| 481 | |
| 482 | *dstPtr = blender(*dstPtr, *srcPtr, opacity); |
| 483 | ++x; |
| 484 | |
| 485 | oldSrcX = int(srcX); |
| 486 | srcX = srcXStart + srcXDelta*x; |
| 487 | // Out of bounds |
| 488 | if (srcX >= srcWidth) |
| 489 | break; |
| 490 | srcPtr += int(srcX - oldSrcX); |
| 491 | |
| 492 | #if _DEBUG |
| 493 | ++dstX; |
| 494 | #endif |
| 495 | } |
| 496 | } |
| 497 | } |
| 498 | |
| 499 | template<class DstTraits, class SrcTraits> |
| 500 | CompositeImageFunc get_fastest_composition_path(const Projection& proj, |
| 501 | const bool finegrain) |
| 502 | { |
| 503 | if (finegrain || !proj.zoom().isSimpleZoomLevel()) { |
| 504 | return composite_image_general<DstTraits, SrcTraits>; |
| 505 | } |
| 506 | else if (proj.applyX(1) == 1 && proj.applyY(1) == 1) { |
| 507 | return composite_image_without_scale<DstTraits, SrcTraits>; |
| 508 | } |
| 509 | else if (proj.scaleX() >= 1.0 && proj.scaleY() >= 1.0) { |
| 510 | return composite_image_scale_up<DstTraits, SrcTraits>; |
| 511 | } |
| 512 | // Slower composite function for special cases with odd zoom and non-square pixel ratio |
| 513 | else if (((proj.removeX(1) > 1) && (proj.removeX(1) & 1)) || |
| 514 | ((proj.removeY(1) > 1) && (proj.removeY(1) & 1))) { |
| 515 | return composite_image_general<DstTraits, SrcTraits>; |
| 516 | } |
| 517 | else { |
| 518 | return composite_image_scale_down<DstTraits, SrcTraits>; |
| 519 | } |
| 520 | } |
| 521 | |
| 522 | bool has_visible_reference_layers(const LayerGroup* group) |
| 523 | { |
| 524 | for (const Layer* child : group->layers()) { |
| 525 | if (!child->isVisible()) |
| 526 | continue; |
| 527 | |
| 528 | if (child->isReference()) |
| 529 | return true; |
| 530 | |
| 531 | if (child->isGroup() && |
| 532 | has_visible_reference_layers(static_cast<const LayerGroup*>(child))) |
| 533 | return true; |
| 534 | } |
| 535 | return false; |
| 536 | } |
| 537 | |
| 538 | } // anonymous namespace |
| 539 | |
| 540 | Render::Render() |
| 541 | : m_flags(0) |
| 542 | , m_nonactiveLayersOpacity(255) |
| 543 | , m_sprite(nullptr) |
| 544 | , m_currentLayer(nullptr) |
| 545 | , m_currentFrame(0) |
| 546 | , m_extraType(ExtraType::NONE) |
| 547 | , m_extraCel(nullptr) |
| 548 | , m_extraImage(nullptr) |
| 549 | , m_newBlendMethod(true) |
| 550 | , m_globalOpacity(255) |
| 551 | , m_selectedLayerForOpacity(nullptr) |
| 552 | , m_selectedLayer(nullptr) |
| 553 | , m_selectedFrame(-1) |
| 554 | , m_previewImage(nullptr) |
| 555 | , m_previewTileset(nullptr) |
| 556 | , m_previewBlendMode(BlendMode::NORMAL) |
| 557 | , m_onionskin(OnionskinType::NONE) |
| 558 | { |
| 559 | } |
| 560 | |
| 561 | void Render::setRefLayersVisiblity(const bool visible) |
| 562 | { |
| 563 | if (visible) |
| 564 | m_flags |= Flags::ShowRefLayers; |
| 565 | else |
| 566 | m_flags &= ~Flags::ShowRefLayers; |
| 567 | } |
| 568 | |
| 569 | void Render::setNonactiveLayersOpacity(const int opacity) |
| 570 | { |
| 571 | m_nonactiveLayersOpacity = opacity; |
| 572 | } |
| 573 | |
| 574 | void Render::setNewBlend(const bool newBlend) |
| 575 | { |
| 576 | m_newBlendMethod = newBlend; |
| 577 | } |
| 578 | |
| 579 | void Render::setProjection(const Projection& projection) |
| 580 | { |
| 581 | m_proj = projection; |
| 582 | } |
| 583 | |
| 584 | void Render::setBgOptions(const BgOptions& bg) |
| 585 | { |
| 586 | m_bg = bg; |
| 587 | } |
| 588 | |
| 589 | void Render::setSelectedLayer(const Layer* layer) |
| 590 | { |
| 591 | m_selectedLayerForOpacity = layer; |
| 592 | } |
| 593 | |
| 594 | void Render::setPreviewImage(const Layer* layer, |
| 595 | const frame_t frame, |
| 596 | const Image* image, |
| 597 | const Tileset* tileset, |
| 598 | const gfx::Point& pos, |
| 599 | const BlendMode blendMode) |
| 600 | { |
| 601 | m_selectedLayer = layer; |
| 602 | m_selectedFrame = frame; |
| 603 | m_previewImage = image; |
| 604 | m_previewTileset = tileset; |
| 605 | m_previewPos = pos; |
| 606 | m_previewBlendMode = blendMode; |
| 607 | } |
| 608 | |
| 609 | void Render::( |
| 610 | ExtraType type, |
| 611 | const Cel* cel, const Image* image, BlendMode blendMode, |
| 612 | const Layer* currentLayer, |
| 613 | frame_t currentFrame) |
| 614 | { |
| 615 | m_extraType = type; |
| 616 | m_extraCel = cel; |
| 617 | m_extraImage = image; |
| 618 | m_extraBlendMode = blendMode; |
| 619 | m_currentLayer = currentLayer; |
| 620 | m_currentFrame = currentFrame; |
| 621 | } |
| 622 | |
| 623 | void Render::removePreviewImage() |
| 624 | { |
| 625 | m_previewImage = nullptr; |
| 626 | m_previewTileset = nullptr; |
| 627 | } |
| 628 | |
| 629 | void Render::() |
| 630 | { |
| 631 | m_extraType = ExtraType::NONE; |
| 632 | m_extraCel = nullptr; |
| 633 | } |
| 634 | |
| 635 | void Render::setOnionskin(const OnionskinOptions& options) |
| 636 | { |
| 637 | m_onionskin = options; |
| 638 | } |
| 639 | |
| 640 | void Render::disableOnionskin() |
| 641 | { |
| 642 | m_onionskin.type(OnionskinType::NONE); |
| 643 | } |
| 644 | |
| 645 | void Render::renderSprite( |
| 646 | Image* dstImage, |
| 647 | const Sprite* sprite, |
| 648 | frame_t frame) |
| 649 | { |
| 650 | renderSprite( |
| 651 | dstImage, sprite, frame, |
| 652 | gfx::ClipF(sprite->bounds())); |
| 653 | } |
| 654 | |
| 655 | void Render::renderLayer( |
| 656 | Image* dstImage, |
| 657 | const Layer* layer, |
| 658 | frame_t frame) |
| 659 | { |
| 660 | renderLayer(dstImage, layer, frame, |
| 661 | gfx::Clip(layer->sprite()->bounds())); |
| 662 | } |
| 663 | |
| 664 | void Render::renderLayer( |
| 665 | Image* dstImage, |
| 666 | const Layer* layer, |
| 667 | frame_t frame, |
| 668 | const gfx::Clip& area, |
| 669 | BlendMode blendMode) |
| 670 | { |
| 671 | m_sprite = layer->sprite(); |
| 672 | |
| 673 | CompositeImageFunc compositeImage = |
| 674 | getImageComposition( |
| 675 | (dstImage->pixelFormat() != IMAGE_TILEMAP ? dstImage->pixelFormat(): |
| 676 | m_sprite->pixelFormat()), |
| 677 | m_sprite->pixelFormat(), layer); |
| 678 | if (!compositeImage) |
| 679 | return; |
| 680 | |
| 681 | m_globalOpacity = 255; |
| 682 | renderLayer( |
| 683 | layer, dstImage, area, |
| 684 | frame, compositeImage, |
| 685 | true, true, blendMode, false); |
| 686 | } |
| 687 | |
| 688 | void Render::renderSprite( |
| 689 | Image* dstImage, |
| 690 | const Sprite* sprite, |
| 691 | frame_t frame, |
| 692 | const gfx::ClipF& area) |
| 693 | { |
| 694 | m_sprite = sprite; |
| 695 | |
| 696 | CompositeImageFunc compositeImage = |
| 697 | getImageComposition( |
| 698 | dstImage->pixelFormat(), |
| 699 | m_sprite->pixelFormat(), sprite->root()); |
| 700 | if (!compositeImage) |
| 701 | return; |
| 702 | |
| 703 | const LayerImage* bgLayer = m_sprite->backgroundLayer(); |
| 704 | color_t bg_color = 0; |
| 705 | if (m_sprite->pixelFormat() == IMAGE_INDEXED) { |
| 706 | switch (dstImage->pixelFormat()) { |
| 707 | case IMAGE_RGB: |
| 708 | case IMAGE_GRAYSCALE: |
| 709 | if (bgLayer && bgLayer->isVisible()) |
| 710 | bg_color = m_sprite->palette(frame)->getEntry(m_sprite->transparentColor()); |
| 711 | break; |
| 712 | case IMAGE_INDEXED: |
| 713 | bg_color = m_sprite->transparentColor(); |
| 714 | break; |
| 715 | } |
| 716 | } |
| 717 | |
| 718 | // New Blending Method: |
| 719 | if (m_newBlendMethod) { |
| 720 | // Clear dstImage with the bg_color (if the background is not a |
| 721 | // special background pattern like the checkered background, this |
| 722 | // is enough as a base color). |
| 723 | fill_rect(dstImage, area.dstBounds(), bg_color); |
| 724 | |
| 725 | // Draw the Background layer - Onion skin behind the sprite - Transparent Layers |
| 726 | renderSpriteLayers(dstImage, area, frame, compositeImage); |
| 727 | |
| 728 | // In case that we need a special background (e.g. like the |
| 729 | // checkered pattern), we can draw the background in a temporal |
| 730 | // image and then merge this temporal image with the dstImage. |
| 731 | if (!isSolidBackground(bgLayer, bg_color)) { |
| 732 | if (!m_tmpBuf) |
| 733 | m_tmpBuf.reset(new doc::ImageBuffer); |
| 734 | ImageRef tmpBackground(Image::create(dstImage->spec(), m_tmpBuf)); |
| 735 | renderBackground(tmpBackground.get(), bgLayer, bg_color, area); |
| 736 | |
| 737 | // Draws dstImage over the background on each pixel of dstImage |
| 738 | // with opacity is < 255 (the result is left on dstImage itself) |
| 739 | composite_image(dstImage, tmpBackground.get(), sprite->palette(frame), |
| 740 | 0, 0, 255, BlendMode::DST_OVER); |
| 741 | } |
| 742 | } |
| 743 | // Old Blending Method: |
| 744 | else { |
| 745 | renderBackground(dstImage, bgLayer, bg_color, area); |
| 746 | renderSpriteLayers(dstImage, area, frame, compositeImage); |
| 747 | } |
| 748 | |
| 749 | // Draw onion skin in front of the sprite. |
| 750 | if (m_onionskin.position() == OnionskinPosition::INFRONT) |
| 751 | renderOnionskin(dstImage, area, frame, compositeImage); |
| 752 | |
| 753 | // Overlay preview image |
| 754 | if (m_previewImage && |
| 755 | m_selectedLayer == nullptr && |
| 756 | m_selectedFrame == frame) { |
| 757 | renderImage( |
| 758 | dstImage, |
| 759 | m_previewImage, |
| 760 | m_sprite->palette(frame), |
| 761 | gfx::Rect(m_previewPos.x, m_previewPos.y, |
| 762 | m_previewImage->width(), |
| 763 | m_previewImage->height()), |
| 764 | area, |
| 765 | getImageComposition( |
| 766 | dstImage->pixelFormat(), |
| 767 | m_previewImage->pixelFormat(), |
| 768 | sprite->root()), |
| 769 | 255, |
| 770 | m_previewBlendMode); |
| 771 | } |
| 772 | } |
| 773 | |
| 774 | void Render::renderSpriteLayers(Image* dstImage, |
| 775 | const gfx::ClipF& area, |
| 776 | frame_t frame, |
| 777 | CompositeImageFunc compositeImage) |
| 778 | { |
| 779 | // Draw the background layer. |
| 780 | m_globalOpacity = 255; |
| 781 | renderLayer(m_sprite->root(), dstImage, |
| 782 | area, frame, compositeImage, |
| 783 | true, |
| 784 | false, |
| 785 | BlendMode::UNSPECIFIED, |
| 786 | false); |
| 787 | |
| 788 | // Draw onion skin behind the sprite. |
| 789 | if (m_onionskin.position() == OnionskinPosition::BEHIND) |
| 790 | renderOnionskin(dstImage, area, frame, compositeImage); |
| 791 | |
| 792 | // Draw the transparent layers. |
| 793 | m_globalOpacity = 255; |
| 794 | renderLayer(m_sprite->root(), dstImage, |
| 795 | area, frame, compositeImage, |
| 796 | false, |
| 797 | true, |
| 798 | BlendMode::UNSPECIFIED, false); |
| 799 | } |
| 800 | |
| 801 | void Render::renderBackground(Image* image, |
| 802 | const Layer* bgLayer, |
| 803 | const color_t bg_color, |
| 804 | const gfx::ClipF& area) |
| 805 | { |
| 806 | if (isSolidBackground(bgLayer, bg_color)) { |
| 807 | fill_rect(image, area.dstBounds(), bg_color); |
| 808 | } |
| 809 | else { |
| 810 | switch (m_bg.type) { |
| 811 | case BgType::CHECKERED: |
| 812 | renderCheckeredBackground(image, area); |
| 813 | if (bgLayer && bgLayer->isVisible() && |
| 814 | // TODO Review this: bg_color can be an index (not an rgba()) |
| 815 | // when sprite and dstImage are indexed |
| 816 | rgba_geta(bg_color) > 0) { |
| 817 | blend_rect(image, |
| 818 | int(area.dst.x), |
| 819 | int(area.dst.y), |
| 820 | int(area.dst.x+area.size.w-1), |
| 821 | int(area.dst.y+area.size.h-1), |
| 822 | bg_color, 255); |
| 823 | } |
| 824 | break; |
| 825 | default: |
| 826 | ASSERT(false); // Invalid case, needsBackground() should |
| 827 | // return false in this case |
| 828 | break; |
| 829 | } |
| 830 | } |
| 831 | } |
| 832 | |
| 833 | bool Render::isSolidBackground( |
| 834 | const Layer* bgLayer, |
| 835 | const color_t bg_color) const |
| 836 | { |
| 837 | return |
| 838 | ((m_bg.type != BgType::CHECKERED) || |
| 839 | (bgLayer && bgLayer->isVisible() && |
| 840 | // TODO Review this: bg_color can be an index (not an rgba()) |
| 841 | // when sprite and dstImage are indexed |
| 842 | rgba_geta(bg_color) == 255)); |
| 843 | } |
| 844 | |
| 845 | void Render::renderOnionskin( |
| 846 | Image* dstImage, |
| 847 | const gfx::Clip& area, |
| 848 | const frame_t frame, |
| 849 | const CompositeImageFunc compositeImage) |
| 850 | { |
| 851 | // Onion-skin feature: Draw previous/next frames with different |
| 852 | // opacity (<255) |
| 853 | if (m_onionskin.type() != OnionskinType::NONE) { |
| 854 | Tag* loop = m_onionskin.loopTag(); |
| 855 | Layer* onionLayer = (m_onionskin.layer() ? m_onionskin.layer(): |
| 856 | m_sprite->root()); |
| 857 | frame_t frameIn; |
| 858 | |
| 859 | for (frame_t frameOut = frame - m_onionskin.prevFrames(); |
| 860 | frameOut <= frame + m_onionskin.nextFrames(); |
| 861 | ++frameOut) { |
| 862 | if (loop) { |
| 863 | bool pingPongForward = true; |
| 864 | frameIn = |
| 865 | calculate_next_frame(m_sprite, |
| 866 | frame, frameOut - frame, |
| 867 | loop, pingPongForward); |
| 868 | } |
| 869 | else { |
| 870 | frameIn = frameOut; |
| 871 | } |
| 872 | |
| 873 | if (frameIn == frame || |
| 874 | frameIn < 0 || |
| 875 | frameIn > m_sprite->lastFrame()) { |
| 876 | continue; |
| 877 | } |
| 878 | |
| 879 | if (frameOut < frame) { |
| 880 | m_globalOpacity = m_onionskin.opacityBase() - m_onionskin.opacityStep() * ((frame - frameOut)-1); |
| 881 | } |
| 882 | else { |
| 883 | m_globalOpacity = m_onionskin.opacityBase() - m_onionskin.opacityStep() * ((frameOut - frame)-1); |
| 884 | } |
| 885 | |
| 886 | m_globalOpacity = std::clamp(m_globalOpacity, 0, 255); |
| 887 | if (m_globalOpacity > 0) { |
| 888 | BlendMode blendMode = BlendMode::UNSPECIFIED; |
| 889 | if (m_onionskin.type() == OnionskinType::MERGE) |
| 890 | blendMode = BlendMode::NORMAL; |
| 891 | else if (m_onionskin.type() == OnionskinType::RED_BLUE_TINT) |
| 892 | blendMode = (frameOut < frame ? BlendMode::RED_TINT: BlendMode::BLUE_TINT); |
| 893 | |
| 894 | renderLayer( |
| 895 | onionLayer, dstImage, |
| 896 | area, frameIn, compositeImage, |
| 897 | // Render background only for "in-front" onion skinning and |
| 898 | // when opacity is < 255 |
| 899 | (m_globalOpacity < 255 && |
| 900 | m_onionskin.position() == OnionskinPosition::INFRONT), |
| 901 | true, blendMode, false); |
| 902 | } |
| 903 | } |
| 904 | } |
| 905 | } |
| 906 | |
| 907 | void Render::renderCheckeredBackground( |
| 908 | Image* image, |
| 909 | const gfx::Clip& area) |
| 910 | { |
| 911 | int x, y, u, v; |
| 912 | int tile_w = m_bg.stripeSize.w; |
| 913 | int tile_h = m_bg.stripeSize.h; |
| 914 | |
| 915 | if (m_bg.zoom) { |
| 916 | tile_w = m_proj.zoom().apply(tile_w); |
| 917 | tile_h = m_proj.zoom().apply(tile_h); |
| 918 | } |
| 919 | |
| 920 | // Tile size |
| 921 | if (tile_w < 1) tile_w = 1; |
| 922 | if (tile_h < 1) tile_h = 1; |
| 923 | |
| 924 | // Tile position (u,v) is the number of tile we start in "area.src" coordinate |
| 925 | u = (area.src.x / tile_w); |
| 926 | v = (area.src.y / tile_h); |
| 927 | |
| 928 | // Position where we start drawing the first tile in "image" |
| 929 | int x_start = -(area.src.x % tile_w); |
| 930 | int y_start = -(area.src.y % tile_h); |
| 931 | |
| 932 | gfx::Rect dstBounds = area.dstBounds(); |
| 933 | |
| 934 | // Fix background color (make them opaque) |
| 935 | switch (image->pixelFormat()) { |
| 936 | case IMAGE_RGB: |
| 937 | m_bg.color1 |= doc::rgba_a_mask; |
| 938 | m_bg.color2 |= doc::rgba_a_mask; |
| 939 | break; |
| 940 | case IMAGE_GRAYSCALE: |
| 941 | m_bg.color1 |= doc::graya_a_mask; |
| 942 | m_bg.color2 |= doc::graya_a_mask; |
| 943 | break; |
| 944 | } |
| 945 | |
| 946 | // Draw checkered background (tile by tile) |
| 947 | int u_start = u; |
| 948 | for (y=y_start-tile_h; y<image->height()+tile_h; y+=tile_h) { |
| 949 | for (x=x_start-tile_w; x<image->width()+tile_w; x+=tile_w) { |
| 950 | gfx::Rect fillRc = dstBounds.createIntersection(gfx::Rect(x, y, tile_w, tile_h)); |
| 951 | if (!fillRc.isEmpty()) |
| 952 | fill_rect( |
| 953 | image, fillRc.x, fillRc.y, fillRc.x+fillRc.w-1, fillRc.y+fillRc.h-1, |
| 954 | (((u+v))&1)? m_bg.color2: m_bg.color1); |
| 955 | ++u; |
| 956 | } |
| 957 | u = u_start; |
| 958 | ++v; |
| 959 | } |
| 960 | } |
| 961 | |
| 962 | void Render::renderImage( |
| 963 | Image* dst_image, |
| 964 | const Image* src_image, |
| 965 | const Palette* pal, |
| 966 | const int x, |
| 967 | const int y, |
| 968 | const int opacity, |
| 969 | const BlendMode blendMode) |
| 970 | { |
| 971 | CompositeImageFunc compositeImage = |
| 972 | getImageComposition( |
| 973 | dst_image->pixelFormat(), |
| 974 | src_image->pixelFormat(), nullptr); |
| 975 | if (!compositeImage) |
| 976 | return; |
| 977 | |
| 978 | compositeImage( |
| 979 | dst_image, src_image, pal, |
| 980 | gfx::ClipF(x, y, 0, 0, |
| 981 | m_proj.applyX(src_image->width()), |
| 982 | m_proj.applyY(src_image->height())), |
| 983 | opacity, blendMode, |
| 984 | m_proj.scaleX(), |
| 985 | m_proj.scaleY(), |
| 986 | m_newBlendMethod); |
| 987 | } |
| 988 | |
| 989 | void Render::renderLayer( |
| 990 | const Layer* layer, |
| 991 | Image* image, |
| 992 | const gfx::Clip& area, |
| 993 | const frame_t frame, |
| 994 | const CompositeImageFunc compositeImage, |
| 995 | const bool render_background, |
| 996 | const bool render_transparent, |
| 997 | const BlendMode blendMode, |
| 998 | bool isSelected) |
| 999 | { |
| 1000 | // we can't read from this layer |
| 1001 | if (!layer->isVisible()) |
| 1002 | return; |
| 1003 | |
| 1004 | if (m_selectedLayerForOpacity == layer) |
| 1005 | isSelected = true; |
| 1006 | |
| 1007 | const Cel* cel = nullptr; |
| 1008 | gfx::Rect ; |
| 1009 | bool = false; |
| 1010 | |
| 1011 | if (m_extraCel && |
| 1012 | m_extraImage && |
| 1013 | layer == m_currentLayer && |
| 1014 | ((layer->isBackground() && render_background) || |
| 1015 | (!layer->isBackground() && render_transparent)) && |
| 1016 | // Don't use a tilemap extra cel (IMAGE_TILEMAP) in a |
| 1017 | // non-tilemap layer (in the other hand tilemap layers allow |
| 1018 | // extra cels of any kind). This fixes a crash on renderCel() |
| 1019 | // when we were painting the Preview window using a tilemap |
| 1020 | // extra image to patch a regular layer, when switching from a |
| 1021 | // tilemap layer to a regular layer. |
| 1022 | ((layer->isTilemap()) || |
| 1023 | (!layer->isTilemap() && m_extraImage->pixelFormat() != IMAGE_TILEMAP))) { |
| 1024 | if (frame == m_extraCel->frame() && |
| 1025 | frame == m_currentFrame) { // TODO this double check is not necessary |
| 1026 | drawExtra = true; |
| 1027 | } |
| 1028 | else { |
| 1029 | // Check if we can draw the extra cel when we render a linked |
| 1030 | // frame. |
| 1031 | cel = layer->cel(frame); |
| 1032 | Cel* cel2 = layer->cel(m_extraCel->frame()); |
| 1033 | if (cel && cel2 && |
| 1034 | cel->data() == cel2->data()) { |
| 1035 | drawExtra = true; |
| 1036 | } |
| 1037 | } |
| 1038 | } |
| 1039 | |
| 1040 | if (drawExtra) { |
| 1041 | extraArea = m_extraCel->bounds(); |
| 1042 | extraArea = m_proj.apply(extraArea); |
| 1043 | if (m_proj.scaleX() < 1.0) extraArea.w--; |
| 1044 | if (m_proj.scaleY() < 1.0) extraArea.h--; |
| 1045 | if (extraArea.w < 1) extraArea.w = 1; |
| 1046 | if (extraArea.h < 1) extraArea.h = 1; |
| 1047 | } |
| 1048 | |
| 1049 | switch (layer->type()) { |
| 1050 | |
| 1051 | case ObjectType::LayerImage: |
| 1052 | case ObjectType::LayerTilemap: { |
| 1053 | if ((!render_background && layer->isBackground()) || |
| 1054 | (!render_transparent && !layer->isBackground())) |
| 1055 | break; |
| 1056 | |
| 1057 | // Ignore reference layers |
| 1058 | if (!(m_flags & Flags::ShowRefLayers) && |
| 1059 | layer->isReference()) |
| 1060 | break; |
| 1061 | |
| 1062 | if (!cel) |
| 1063 | cel = layer->cel(frame); |
| 1064 | |
| 1065 | if (cel) { |
| 1066 | Palette* pal = m_sprite->palette(frame); |
| 1067 | const Image* celImage = nullptr; |
| 1068 | gfx::RectF celBounds; |
| 1069 | |
| 1070 | // Is the 'm_previewImage' set to be used with this layer? |
| 1071 | if (m_previewImage && |
| 1072 | checkIfWeShouldUsePreview(cel)) { |
| 1073 | celImage = m_previewImage; |
| 1074 | celBounds = gfx::RectF(m_previewPos.x, |
| 1075 | m_previewPos.y, |
| 1076 | m_previewImage->width(), |
| 1077 | m_previewImage->height()); |
| 1078 | } |
| 1079 | // If not, we use the original cel-image from the images' stock |
| 1080 | else { |
| 1081 | celImage = cel->image(); |
| 1082 | if (cel->layer()->isReference()) |
| 1083 | celBounds = cel->boundsF(); |
| 1084 | else |
| 1085 | celBounds = cel->bounds(); |
| 1086 | } |
| 1087 | |
| 1088 | if (celImage) { |
| 1089 | const LayerImage* imgLayer = static_cast<const LayerImage*>(layer); |
| 1090 | BlendMode layerBlendMode = |
| 1091 | (blendMode == BlendMode::UNSPECIFIED ? |
| 1092 | imgLayer->blendMode(): |
| 1093 | blendMode); |
| 1094 | |
| 1095 | ASSERT(cel->opacity() >= 0); |
| 1096 | ASSERT(cel->opacity() <= 255); |
| 1097 | ASSERT(imgLayer->opacity() >= 0); |
| 1098 | ASSERT(imgLayer->opacity() <= 255); |
| 1099 | |
| 1100 | // Multiple three opacities: cel*layer*global (*nonactive-layer-opacity) |
| 1101 | int t; |
| 1102 | int opacity = cel->opacity(); |
| 1103 | opacity = MUL_UN8(opacity, imgLayer->opacity(), t); |
| 1104 | opacity = MUL_UN8(opacity, m_globalOpacity, t); |
| 1105 | if (!isSelected && m_nonactiveLayersOpacity != 255) |
| 1106 | opacity = MUL_UN8(opacity, m_nonactiveLayersOpacity, t); |
| 1107 | |
| 1108 | // Generally this is just one pass, but if we are using |
| 1109 | // OVER_COMPOSITE extra cel, this will be two passes. |
| 1110 | for (int pass=0; pass<2; ++pass) { |
| 1111 | // Draw parts outside the "m_extraCel" area |
| 1112 | if (drawExtra && m_extraType == ExtraType::PATCH) { |
| 1113 | gfx::Region originalAreas(area.srcBounds()); |
| 1114 | originalAreas.createSubtraction( |
| 1115 | originalAreas, gfx::Region(extraArea)); |
| 1116 | |
| 1117 | for (auto rc : originalAreas) { |
| 1118 | renderCel( |
| 1119 | image, cel, celImage, layer, pal, celBounds, |
| 1120 | gfx::Clip(area.dst.x+rc.x-area.src.x, |
| 1121 | area.dst.y+rc.y-area.src.y, rc), |
| 1122 | compositeImage, opacity, layerBlendMode); |
| 1123 | } |
| 1124 | } |
| 1125 | // Draw the whole cel |
| 1126 | else { |
| 1127 | renderCel( |
| 1128 | image, cel, celImage, layer, pal, |
| 1129 | celBounds, area, compositeImage, |
| 1130 | opacity, layerBlendMode); |
| 1131 | } |
| 1132 | |
| 1133 | if (m_extraType == ExtraType::OVER_COMPOSITE && |
| 1134 | layer == m_currentLayer && |
| 1135 | pass == 0) { |
| 1136 | // Go for second pass with the extra blend mode... |
| 1137 | layerBlendMode = m_extraBlendMode; |
| 1138 | } |
| 1139 | else |
| 1140 | break; |
| 1141 | } |
| 1142 | } |
| 1143 | } |
| 1144 | break; |
| 1145 | } |
| 1146 | |
| 1147 | case ObjectType::LayerGroup: { |
| 1148 | for (const Layer* child : static_cast<const LayerGroup*>(layer)->layers()) { |
| 1149 | renderLayer( |
| 1150 | child, image, |
| 1151 | area, frame, |
| 1152 | compositeImage, |
| 1153 | render_background, |
| 1154 | render_transparent, |
| 1155 | blendMode, |
| 1156 | isSelected); |
| 1157 | } |
| 1158 | break; |
| 1159 | } |
| 1160 | |
| 1161 | } |
| 1162 | |
| 1163 | // Draw extras |
| 1164 | if (drawExtra && m_extraType != ExtraType::NONE) { |
| 1165 | if (m_extraCel->opacity() > 0) { |
| 1166 | renderCel( |
| 1167 | image, |
| 1168 | m_extraCel, |
| 1169 | m_sprite, |
| 1170 | m_extraImage, |
| 1171 | m_currentLayer, // Current layer (useful to use get the tileset if extra cel is a tilemap) |
| 1172 | m_sprite->palette(frame), |
| 1173 | m_extraCel->bounds(), |
| 1174 | gfx::Clip(area.dst.x+extraArea.x-area.src.x, |
| 1175 | area.dst.y+extraArea.y-area.src.y, |
| 1176 | extraArea), |
| 1177 | m_extraCel->opacity(), |
| 1178 | m_extraBlendMode); |
| 1179 | } |
| 1180 | } |
| 1181 | } |
| 1182 | |
| 1183 | void Render::renderCel( |
| 1184 | Image* dst_image, |
| 1185 | const Cel* cel, |
| 1186 | const Sprite* sprite, |
| 1187 | const Image* cel_image, |
| 1188 | const Layer* cel_layer, |
| 1189 | const Palette* pal, |
| 1190 | const gfx::RectF& celBounds, |
| 1191 | const gfx::Clip& area, |
| 1192 | const int opacity, |
| 1193 | const BlendMode blendMode) |
| 1194 | { |
| 1195 | m_sprite = sprite; |
| 1196 | |
| 1197 | CompositeImageFunc compositeImage = |
| 1198 | getImageComposition( |
| 1199 | dst_image->pixelFormat(), |
| 1200 | sprite->pixelFormat(), nullptr); |
| 1201 | if (!compositeImage) |
| 1202 | return; |
| 1203 | |
| 1204 | renderCel( |
| 1205 | dst_image, |
| 1206 | cel, |
| 1207 | cel_image, |
| 1208 | cel_layer, |
| 1209 | pal, |
| 1210 | celBounds, |
| 1211 | area, |
| 1212 | compositeImage, |
| 1213 | opacity, |
| 1214 | blendMode); |
| 1215 | } |
| 1216 | |
| 1217 | void Render::renderCel( |
| 1218 | Image* dst_image, |
| 1219 | const Cel* cel, |
| 1220 | const Image* cel_image, |
| 1221 | const Layer* cel_layer, |
| 1222 | const Palette* pal, |
| 1223 | const gfx::RectF& celBounds, |
| 1224 | const gfx::Clip& area, |
| 1225 | const CompositeImageFunc compositeImage, |
| 1226 | const int opacity, |
| 1227 | const BlendMode blendMode) |
| 1228 | { |
| 1229 | TRACE_RENDER_CEL("dstImage=(%d %d) celImage=(%d %d) celBounds=(%d %d %d %d) clipArea=(src=%d %d dst=%d %d %d %d)\n" , |
| 1230 | dst_image->width(), dst_image->height(), |
| 1231 | cel_image->width(), cel_image->height(), |
| 1232 | int(celBounds.x), int(celBounds.y), |
| 1233 | int(celBounds.w), int(celBounds.h), |
| 1234 | area.src.x, area.src.y, area.dst.x, area.dst.y, area.size.w, area.size.h); |
| 1235 | |
| 1236 | if (cel_layer && |
| 1237 | cel_image->pixelFormat() == IMAGE_TILEMAP) { |
| 1238 | ASSERT(cel_layer->isTilemap()); |
| 1239 | |
| 1240 | if (area.size.w < 1 || |
| 1241 | area.size.h < 1) |
| 1242 | return; |
| 1243 | |
| 1244 | auto tilemapLayer = static_cast<const LayerTilemap*>(cel_layer); |
| 1245 | doc::Grid grid = tilemapLayer->tileset()->grid(); |
| 1246 | grid.origin(grid.origin() + gfx::Point(celBounds.origin())); |
| 1247 | |
| 1248 | // Is the 'm_previewTileset' set to be used with this layer? |
| 1249 | const Tileset* tileset; |
| 1250 | if (m_previewTileset && cel && |
| 1251 | checkIfWeShouldUsePreview(cel)) { |
| 1252 | tileset = m_previewTileset; |
| 1253 | } |
| 1254 | else { |
| 1255 | tileset = tilemapLayer->tileset(); |
| 1256 | ASSERT(tileset); |
| 1257 | if (!tileset) |
| 1258 | return; |
| 1259 | } |
| 1260 | |
| 1261 | gfx::Rect tilesToDraw = grid.canvasToTile( |
| 1262 | m_proj.remove(gfx::Rect(area.src, area.size))); |
| 1263 | |
| 1264 | int yPixelsPerTile = m_proj.applyY(grid.tileSize().h); |
| 1265 | if (yPixelsPerTile > 0 && (area.size.h + area.src.y) % yPixelsPerTile > 0) |
| 1266 | tilesToDraw.h += 1; |
| 1267 | int xPixelsPerTile = m_proj.applyX(grid.tileSize().w); |
| 1268 | if (xPixelsPerTile > 0 && (area.size.w + area.src.x) % xPixelsPerTile > 0) |
| 1269 | tilesToDraw.w += 1; |
| 1270 | |
| 1271 | // As area.size is not empty at this point, we have to draw at |
| 1272 | // least one tile (and the clipping will be performed for the |
| 1273 | // tile pixels later). |
| 1274 | if (tilesToDraw.w < 1) tilesToDraw.w = 1; |
| 1275 | if (tilesToDraw.h < 1) tilesToDraw.h = 1; |
| 1276 | |
| 1277 | tilesToDraw &= cel_image->bounds(); |
| 1278 | |
| 1279 | TRACE_RENDER_CEL("Drawing tilemap (%d %d %d %d)\n" , |
| 1280 | tilesToDraw.x, tilesToDraw.y, tilesToDraw.w, tilesToDraw.h); |
| 1281 | |
| 1282 | for (int v=tilesToDraw.y; v<tilesToDraw.y2(); ++v) { |
| 1283 | for (int u=tilesToDraw.x; u<tilesToDraw.x2(); ++u) { |
| 1284 | auto tileBoundsOnCanvas = grid.tileToCanvas(gfx::Rect(u, v, 1, 1)); |
| 1285 | TRACE_RENDER_CEL(" - tile (%d %d) -> (%d %d %d %d)\n" , u, v, |
| 1286 | tileBoundsOnCanvas.x, tileBoundsOnCanvas.y, |
| 1287 | tileBoundsOnCanvas.w, tileBoundsOnCanvas.h); |
| 1288 | if (!cel_image->bounds().contains(u, v)) |
| 1289 | continue; |
| 1290 | |
| 1291 | const tile_t t = cel_image->getPixel(u, v); |
| 1292 | if (t != doc::notile) { |
| 1293 | const tile_index i = tile_geti(t); |
| 1294 | |
| 1295 | if (dst_image->pixelFormat() == IMAGE_TILEMAP) { |
| 1296 | put_pixel(dst_image, u-area.dst.x, v-area.dst.y, t); |
| 1297 | } |
| 1298 | else { |
| 1299 | const ImageRef tile_image = tileset->get(i); |
| 1300 | if (!tile_image) |
| 1301 | continue; |
| 1302 | |
| 1303 | renderImage(dst_image, tile_image.get(), pal, tileBoundsOnCanvas, |
| 1304 | area, compositeImage, opacity, blendMode); |
| 1305 | } |
| 1306 | } |
| 1307 | } |
| 1308 | } |
| 1309 | } |
| 1310 | else { |
| 1311 | renderImage(dst_image, cel_image, pal, celBounds, |
| 1312 | area, compositeImage, opacity, blendMode); |
| 1313 | } |
| 1314 | } |
| 1315 | |
| 1316 | void Render::renderImage( |
| 1317 | Image* dst_image, |
| 1318 | const Image* cel_image, |
| 1319 | const Palette* pal, |
| 1320 | const gfx::RectF& celBounds, |
| 1321 | const gfx::Clip& area, |
| 1322 | const CompositeImageFunc compositeImage, |
| 1323 | const int opacity, |
| 1324 | const BlendMode blendMode) |
| 1325 | { |
| 1326 | gfx::RectF scaledBounds = m_proj.apply(celBounds); |
| 1327 | gfx::RectF srcBounds = gfx::RectF(area.srcBounds()).createIntersection(scaledBounds); |
| 1328 | if (srcBounds.isEmpty()) |
| 1329 | return; |
| 1330 | |
| 1331 | compositeImage( |
| 1332 | dst_image, cel_image, pal, |
| 1333 | gfx::ClipF( |
| 1334 | double(area.dst.x) + srcBounds.x - double(area.src.x), |
| 1335 | double(area.dst.y) + srcBounds.y - double(area.src.y), |
| 1336 | srcBounds.x - scaledBounds.x, |
| 1337 | srcBounds.y - scaledBounds.y, |
| 1338 | srcBounds.w, |
| 1339 | srcBounds.h), |
| 1340 | opacity, |
| 1341 | blendMode, |
| 1342 | m_proj.scaleX() * celBounds.w / double(cel_image->width()), |
| 1343 | m_proj.scaleY() * celBounds.h / double(cel_image->height()), |
| 1344 | m_newBlendMethod); |
| 1345 | } |
| 1346 | |
| 1347 | CompositeImageFunc Render::getImageComposition( |
| 1348 | const PixelFormat dstFormat, |
| 1349 | const PixelFormat srcFormat, |
| 1350 | const Layer* layer) |
| 1351 | { |
| 1352 | // True if we need blending pixel by pixel. If this is false we can |
| 1353 | // blend src+dst one time and repeat the resulting color in dst |
| 1354 | // image n-times (where n is the zoom scale). |
| 1355 | double intpart; |
| 1356 | const bool finegrain = |
| 1357 | (!m_bg.zoom && (m_bg.stripeSize.w < m_proj.applyX(1) || |
| 1358 | m_bg.stripeSize.h < m_proj.applyY(1) || |
| 1359 | std::modf(double(m_bg.stripeSize.w) / m_proj.applyX(1.0), &intpart) != 0.0 || |
| 1360 | std::modf(double(m_bg.stripeSize.h) / m_proj.applyY(1.0), &intpart) != 0.0)) || |
| 1361 | (layer && |
| 1362 | layer->isGroup() && |
| 1363 | has_visible_reference_layers(static_cast<const LayerGroup*>(layer))); |
| 1364 | |
| 1365 | switch (srcFormat) { |
| 1366 | |
| 1367 | case IMAGE_RGB: |
| 1368 | switch (dstFormat) { |
| 1369 | case IMAGE_RGB: return get_fastest_composition_path<RgbTraits, RgbTraits>(m_proj, finegrain); |
| 1370 | case IMAGE_GRAYSCALE: return get_fastest_composition_path<GrayscaleTraits, RgbTraits>(m_proj, finegrain); |
| 1371 | case IMAGE_INDEXED: return get_fastest_composition_path<IndexedTraits, RgbTraits>(m_proj, finegrain); |
| 1372 | } |
| 1373 | break; |
| 1374 | |
| 1375 | case IMAGE_GRAYSCALE: |
| 1376 | switch (dstFormat) { |
| 1377 | case IMAGE_RGB: return get_fastest_composition_path<RgbTraits, GrayscaleTraits>(m_proj, finegrain); |
| 1378 | case IMAGE_GRAYSCALE: return get_fastest_composition_path<GrayscaleTraits, GrayscaleTraits>(m_proj, finegrain); |
| 1379 | case IMAGE_INDEXED: return get_fastest_composition_path<IndexedTraits, GrayscaleTraits>(m_proj, finegrain); |
| 1380 | } |
| 1381 | break; |
| 1382 | |
| 1383 | case IMAGE_INDEXED: |
| 1384 | switch (dstFormat) { |
| 1385 | case IMAGE_RGB: return get_fastest_composition_path<RgbTraits, IndexedTraits>(m_proj, finegrain); |
| 1386 | case IMAGE_GRAYSCALE: return get_fastest_composition_path<GrayscaleTraits, IndexedTraits>(m_proj, finegrain); |
| 1387 | case IMAGE_INDEXED: return get_fastest_composition_path<IndexedTraits, IndexedTraits>(m_proj, finegrain); |
| 1388 | } |
| 1389 | break; |
| 1390 | |
| 1391 | case IMAGE_TILEMAP: |
| 1392 | switch (dstFormat) { |
| 1393 | case IMAGE_TILEMAP: |
| 1394 | return get_fastest_composition_path<TilemapTraits, TilemapTraits>(m_proj, finegrain); |
| 1395 | } |
| 1396 | break; |
| 1397 | } |
| 1398 | |
| 1399 | TRACE_RENDER_CEL("Render::getImageComposition srcFormat" , srcFormat, "dstFormat" , dstFormat); |
| 1400 | ASSERT(false && "Invalid pixel formats" ); |
| 1401 | return nullptr; |
| 1402 | } |
| 1403 | |
| 1404 | bool Render::checkIfWeShouldUsePreview(const Cel* cel) const |
| 1405 | { |
| 1406 | if ((m_selectedLayer == cel->layer())) { |
| 1407 | if (m_selectedFrame == cel->frame()) { |
| 1408 | return true; |
| 1409 | } |
| 1410 | else if (cel->layer()) { |
| 1411 | // This preview might be useful if we are rendering a linked |
| 1412 | // frame to preview. |
| 1413 | Cel* cel2 = cel->layer()->cel(m_selectedFrame); |
| 1414 | if (cel2 && cel2->data() == cel->data()) |
| 1415 | return true; |
| 1416 | } |
| 1417 | } |
| 1418 | return false; |
| 1419 | } |
| 1420 | |
| 1421 | void composite_image(Image* dst, |
| 1422 | const Image* src, |
| 1423 | const Palette* pal, |
| 1424 | const int x, |
| 1425 | const int y, |
| 1426 | const int opacity, |
| 1427 | const BlendMode blendMode) |
| 1428 | { |
| 1429 | // As the background is not rendered in renderImage(), we don't need |
| 1430 | // to configure the Render instance's BgType. |
| 1431 | Render().renderImage( |
| 1432 | dst, src, pal, x, y, |
| 1433 | opacity, blendMode); |
| 1434 | } |
| 1435 | |
| 1436 | } // namespace render |
| 1437 | |