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
29namespace render {
30
31namespace {
32
33//////////////////////////////////////////////////////////////////////
34// Scaled composite
35
36template<class DstTraits, class SrcTraits>
37class BlenderHelper {
38 BlendFunc m_blendFunc;
39 color_t m_mask_color;
40public:
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
58template<>
59class BlenderHelper<RgbTraits, GrayscaleTraits> {
60 BlendFunc m_blendFunc;
61 color_t m_mask_color;
62public:
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
82template<>
83class BlenderHelper<RgbTraits, IndexedTraits> {
84 const Palette* m_pal;
85 BlendMode m_blendMode;
86 BlendFunc m_blendFunc;
87 color_t m_mask_color;
88public:
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
114template<>
115class BlenderHelper<IndexedTraits, IndexedTraits> {
116 BlendMode m_blendMode;
117 color_t m_maskColor;
118 int m_paletteSize;
119public:
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
149template<class DstTraits, class SrcTraits>
150void 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
205template<class DstTraits, class SrcTraits>
206void 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
348done_with_line:;
349 if (++dstBounds.y > bottom)
350 goto done_with_blit;
351 }
352 }
353
354done_with_blit:;
355}
356
357template<class DstTraits, class SrcTraits>
358void 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
425template<class DstTraits, class SrcTraits>
426void 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
499template<class DstTraits, class SrcTraits>
500CompositeImageFunc 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
522bool 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
540Render::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
561void Render::setRefLayersVisiblity(const bool visible)
562{
563 if (visible)
564 m_flags |= Flags::ShowRefLayers;
565 else
566 m_flags &= ~Flags::ShowRefLayers;
567}
568
569void Render::setNonactiveLayersOpacity(const int opacity)
570{
571 m_nonactiveLayersOpacity = opacity;
572}
573
574void Render::setNewBlend(const bool newBlend)
575{
576 m_newBlendMethod = newBlend;
577}
578
579void Render::setProjection(const Projection& projection)
580{
581 m_proj = projection;
582}
583
584void Render::setBgOptions(const BgOptions& bg)
585{
586 m_bg = bg;
587}
588
589void Render::setSelectedLayer(const Layer* layer)
590{
591 m_selectedLayerForOpacity = layer;
592}
593
594void 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
609void Render::setExtraImage(
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
623void Render::removePreviewImage()
624{
625 m_previewImage = nullptr;
626 m_previewTileset = nullptr;
627}
628
629void Render::removeExtraImage()
630{
631 m_extraType = ExtraType::NONE;
632 m_extraCel = nullptr;
633}
634
635void Render::setOnionskin(const OnionskinOptions& options)
636{
637 m_onionskin = options;
638}
639
640void Render::disableOnionskin()
641{
642 m_onionskin.type(OnionskinType::NONE);
643}
644
645void 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
655void 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
664void 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
688void 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
774void 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
801void 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
833bool 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
845void 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
907void 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
962void 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
989void 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 extraArea;
1009 bool drawExtra = 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
1183void 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
1217void 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
1316void 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
1347CompositeImageFunc 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
1404bool 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
1421void 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