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 | |