1 //
2 // Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012
3 // Free Software Foundation, Inc
4 //
5 // This program is free software; you can redistribute it and/or modify
6 // it under the terms of the GNU General Public License as published by
7 // the Free Software Foundation; either version 3 of the License, or
8 // (at your option) any later version.
9 //
10 // This program is distributed in the hope that it will be useful,
11 // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 // GNU General Public License for more details.
14 //
15 // You should have received a copy of the GNU General Public License
16 // along with this program; if not, write to the Free Software
17 // Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
18
19 // Original version by Udo Giacomozzi and Hannes Mayr,
20 // INDUNET GmbH (www.indunet.it)
21
22
23 /// A Renderer that uses the Anti-Grain Geometry Toolkit (antigrain.com)
24 /// and renders directly to a buffer (for example to the framebuffer). This
25 /// backend is *completely* independent of any hardware. It can be used for
26 /// rendering to the Linux FrameBuffer device, or be blitted inside a
27 /// window (regardless of what operating system). It should also be no problem
28 /// to render into a file...
29 /// This file uses *very* heavily templates and is optimized mainly for speed,
30 /// meaning that the compiler generates very, very, very much specialized
31 /// code. That's good for speed but bloats up the resulting machine code.
32
33 /*
34
35 Status
36 ------
37
38 outlines:
39 solid COMPLETE
40 patterns don't exist (they're converted at compile time by Flash!)
41 widths COMPLETE
42 colors, alpha COMPLETE
43 cap styles DONE, but end cap style is ignored
44 join styles COMPLETE
45 no-close flag COMPLETE
46
47
48 fills:
49 solid fills COMPLETE
50 linear gradients COMPLETE
51 radial gradients COMPLETE
52 focal gradients COMPLETE
53 ext. spread modes COMPLETE
54 linear RGB mode COMPLETE
55 bitmaps, tiled COMPLETE
56 bitmaps, clipped COMPLETE
57 bitmaps, smooth COMPLETE
58 bitmaps, hard COMPLETE
59 color xform COMPLETE
60
61 fonts COMPLETE
62
63 masks COMPLETE
64
65 caching NONE IMPLEMENTED
66
67 video COMPLETE
68
69 Currently the renderer should be able to render everything correctly.
70
71
72 What could and should be /optimized/
73 ------------------------------------
74
75 - EASY: Do not even start rendering shapes that are abviously out of the
76 invalidated bounds!
77
78 - The alpha mask buffers (masks) are allocated and freed for each mask which
79 results in many large-size buffer allocations during a second. Maybe this
80 should be optimized.
81
82 - Matrix-transformed paths (generated before drawing a shape) should be cached
83 and re-used to avoid recalculation of the same coordinates.
84
85 - Characters (or sprites) may be cached as bitmaps with alpha channel (RGBA).
86 The mechanism could be automatically activated when the same character is
87 being rendered the 3rd or 5th time in a row with the same transformations
88 (regardless of the instance itself!). It's not a good idea to always
89 render into a bitmap buffer because this eats up memory and adds an
90 additional pass in rendering (blitting the bitmap buffer). This may be
91 tricky to implement anyway.
92
93 - Masks are a very good candidate for bitmap caching as they do not change
94 that often. With other words, the alpha mask should not be discarded after
95 rendering and should be reused if possible.
96
97 - there are also a few TODO comments in the code!
98
99
100 AGG resources
101 -------------
102 http://www.antigrain.com/
103 http://haiku-os.org/node/86
104
105 */
106
107 #ifdef HAVE_CONFIG_H
108 #include "gnashconfig.h"
109 #endif
110
111 #include "Renderer_agg.h"
112
113 #include <vector>
114 #include <cmath>
115 #include <math.h> // We use round()!
116 #include <climits>
117 #include <functional>
118
119 #pragma GCC diagnostic push
120 #pragma GCC diagnostic ignored "-Wunused-parameter"
121 #pragma GCC diagnostic ignored "-Wsign-compare"
122 #include <agg_rendering_buffer.h>
123 #include <agg_renderer_base.h>
124 #include <agg_pixfmt_gray.h>
125 #include <agg_pixfmt_rgb_packed.h>
126 #include <agg_color_rgba.h>
127 #include <agg_color_gray.h>
128 #include <agg_ellipse.h>
129 #include <agg_conv_transform.h>
130 #include <agg_trans_affine.h>
131 #include <agg_scanline_u.h>
132 #include <agg_scanline_bin.h>
133 #include <agg_scanline_p.h>
134 #include <agg_renderer_scanline.h>
135 #include <agg_rasterizer_scanline_aa.h>
136 #include <agg_rasterizer_compound_aa.h>
137 #include <agg_span_allocator.h>
138 #include <agg_path_storage.h>
139 #include <agg_conv_curve.h>
140 #include <agg_conv_stroke.h>
141 #include <agg_renderer_primitives.h>
142 #include <agg_image_accessors.h>
143 #include <agg_alpha_mask_u8.h>
144 #pragma GCC diagnostic pop
145
146 #include "Renderer_agg_style.h"
147
148 #include "GnashEnums.h"
149 #include "CachedBitmap.h"
150 #include "RGBA.h"
151 #include "GnashImage.h"
152 #include "log.h"
153 #include "Range2d.h"
154 #include "swf/ShapeRecord.h"
155 #include "GnashNumeric.h"
156 #include "SWFCxForm.h"
157 #include "FillStyle.h"
158 #include "Transform.h"
159 #include "IOChannel.h"
160
161 #ifdef HAVE_VA_VA_H
162 #include "GnashVaapiImage.h"
163 #include "GnashVaapiImageProxy.h"
164 #endif
165
166 #include "Renderer_agg_bitmap.h"
167
168 // Print a debugging warning when rendering of a whole character
169 // is skipped
170 //#define GNASH_WARN_WHOLE_CHARACTER_SKIP
171
172
173 #include <boost/numeric/conversion/converter.hpp>
174 #include <boost/ptr_container/ptr_vector.hpp>
175
176 namespace gnash {
177
178 namespace {
179
180 class AlphaMask;
181
182 typedef std::vector<agg::path_storage> AggPaths;
183 typedef std::vector<geometry::Range2d<int> > ClipBounds;
184 typedef boost::ptr_vector<AlphaMask> AlphaMasks;
185 typedef std::vector<Path> GnashPaths;
186
187 // Note: this is here in case ::round doesn't exist. However, it's not
188 // advisable to check using ifdefs (as previously), because ::round is
189 // generally a function not a macro!
190 template<typename T>
round(T t)191 T round(T t)
192 {
193 return ::round(t);
194 }
195
196 template <class Rasterizer>
applyClipBox(Rasterizer & ras,const geometry::Range2d<int> & bounds)197 inline void applyClipBox(Rasterizer& ras, const geometry::Range2d<int>& bounds)
198 {
199 assert(bounds.isFinite());
200 ras.clip_box(static_cast<double>(bounds.getMinX()),
201 static_cast<double>(bounds.getMinY()),
202 static_cast<double>(bounds.getMaxX() + 1),
203 static_cast<double>(bounds.getMaxY() + 1)
204 );
205 }
206
207 /// Analyzes a set of paths to detect real presence of fills and/or outlines
208 /// TODO: This should be something the character tells us and should be
209 /// cached.
210 void
analyzePaths(const GnashPaths & paths,bool & have_shape,bool & have_outline)211 analyzePaths(const GnashPaths &paths, bool& have_shape,
212 bool& have_outline)
213 {
214
215 have_shape = false;
216 have_outline = false;
217
218 const int pcount = paths.size();
219
220 for (int pno=0; pno<pcount; ++pno) {
221
222 const Path &the_path = paths[pno];
223
224 if ((the_path.m_fill0 > 0) || (the_path.m_fill1 > 0)) {
225 have_shape = true;
226 if (have_outline) return; // have both
227 }
228
229 if (the_path.m_line > 0) {
230 have_outline = true;
231 if (have_shape) return; // have both
232 }
233 }
234 }
235
236 class EdgeToPath
237 {
238
239 public:
EdgeToPath(AggPaths::value_type & path,double shift=0)240 EdgeToPath(AggPaths::value_type& path, double shift = 0)
241 :
242 _path(path),
243 _shift(shift)
244 {}
245
operator ()(const Edge & edge)246 void operator()(const Edge& edge)
247 {
248 if (edge.straight()) {
249 _path.line_to(twipsToPixels(edge.ap.x) + _shift,
250 twipsToPixels(edge.ap.y) + _shift);
251 }
252 else {
253 _path.curve3(twipsToPixels(edge.cp.x) + _shift,
254 twipsToPixels(edge.cp.y) + _shift,
255 twipsToPixels(edge.ap.x) + _shift,
256 twipsToPixels(edge.ap.y) + _shift);
257 }
258 }
259
260 private:
261 agg::path_storage& _path;
262 const double _shift;
263 };
264
265 /// In-place transformation of Gnash paths to AGG paths.
266 class GnashToAggPath
267 {
268 public:
269
GnashToAggPath(AggPaths & dest,double shift=0)270 GnashToAggPath(AggPaths& dest, double shift = 0)
271 :
272 _dest(dest),
273 _it(_dest.begin()),
274 _shift(shift)
275 {
276 }
277
operator ()(const Path & in)278 void operator()(const Path& in)
279 {
280 agg::path_storage& p = *_it;
281
282 p.move_to(twipsToPixels(in.ap.x) + _shift,
283 twipsToPixels(in.ap.y) + _shift);
284
285 std::for_each(in.m_edges.begin(), in.m_edges.end(),
286 EdgeToPath(p, _shift));
287 ++_it;
288 }
289
290 private:
291 AggPaths& _dest;
292 AggPaths::iterator _it;
293 const double _shift;
294
295 };
296
297
298 /// Transposes Gnash paths to AGG paths, which can be used for both outlines
299 /// and shapes. Subshapes are ignored (ie. all paths are converted). Converts
300 /// TWIPS to pixels on the fly.
301 inline void
buildPaths(AggPaths & dest,const GnashPaths & paths)302 buildPaths(AggPaths& dest, const GnashPaths& paths)
303 {
304 dest.resize(paths.size());
305 std::for_each(paths.begin(), paths.end(), GnashToAggPath(dest, 0.05));
306 }
307
308 // --- ALPHA MASK BUFFER CONTAINER ---------------------------------------------
309 // How masks are implemented: A mask is basically a full alpha buffer. Each
310 // pixel in the alpha buffer defines the fraction of color values that are
311 // copied to the main buffer. The alpha mask buffer has 256 alpha levels per
312 // pixel, which is good as it allows anti-aliased masks. A full size buffer
313 // is allocated for each mask even if the invalidated bounds may be much
314 // smaller. The advantage of this is that the alpha mask adaptor does not need
315 // to do any clipping which results in better performance.
316 // Masks can be nested, which means the intersection of all masks should be
317 // visible (logical AND). To allow this we hold a stack of alpha masks and the
318 // topmost mask is used itself as a mask to draw any new mask. When rendering
319 // visible shapes only the topmost mask must be used and when a mask should not
320 // be used anymore it's simply discarded so that the next mask becomes active
321 // again.
322 // To be exact, Flash is a bit restrictive regarding to what can be a mask
323 // (dynamic text, shapes, ...) but our renderer can build a mask from
324 // anything we can draw otherwise (except lines, which are excluded
325 // explicitly).
326
327 class AlphaMask
328 {
329
330 typedef agg::renderer_base<agg::pixfmt_gray8> Renderer;
331 typedef agg::alpha_mask_gray8 Mask;
332
333 public:
334
AlphaMask(int width,int height)335 AlphaMask(int width, int height)
336 :
337 _rbuf(nullptr, width, height, width),
338 _pixf(_rbuf),
339 _rbase(_pixf),
340 _amask(_rbuf),
341 _buffer(new std::uint8_t[width * height]())
342 {
343 _rbuf.attach(_buffer.get(), width, height, width);
344 }
345
clear(const geometry::Range2d<int> & region)346 void clear(const geometry::Range2d<int>& region)
347 {
348 if (region.isNull()) return;
349 assert(region.isFinite());
350
351 const agg::gray8 black(0);
352
353 // region can't be world as it should be intersected with
354 // the visible SWFRect
355 assert(!region.isWorld());
356
357 unsigned int left = region.getMinX();
358 unsigned int width = region.width() + 1;
359
360 const unsigned int max_y = region.getMaxY();
361 for (unsigned int y=region.getMinY(); y <= max_y; ++y)
362 {
363 _pixf.copy_hline(left, y, width, black);
364 }
365 }
366
get_rbase()367 Renderer& get_rbase() {
368 return _rbase;
369 }
370
getMask() const371 const Mask& getMask() const {
372 return _amask;
373 }
374
375 private:
376
377 // agg class to access the buffer
378 agg::rendering_buffer _rbuf;
379
380 // pixel access
381 agg::pixfmt_gray8 _pixf;
382
383 // renderer base
384 Renderer _rbase;
385
386 // alpha mask
387 Mask _amask;
388
389 // in-memory buffer
390 std::unique_ptr<std::uint8_t[]> _buffer;
391
392 };
393
394 /// Class for rendering lines.
395 template<typename PixelFormat>
396 class LineRenderer
397 {
398 public:
399 typedef agg::renderer_base<PixelFormat> BaseRenderer;
400 typedef agg::renderer_scanline_aa_solid<BaseRenderer> Renderer;
401 typedef agg::rasterizer_scanline_aa<> Rasterizer;
402 typedef agg::conv_stroke<agg::path_storage> Stroke;
403
LineRenderer(const ClipBounds & clipbounds,BaseRenderer & baseRenderer)404 LineRenderer(const ClipBounds& clipbounds, BaseRenderer& baseRenderer)
405 :
406 _clipbounds(clipbounds),
407 _renderer(baseRenderer)
408 {}
409
410 template<typename ScanLine>
render(ScanLine & sl,Stroke & stroke,const rgba & color)411 void render(ScanLine& sl, Stroke& stroke, const rgba& color)
412 {
413 for (const auto& bounds : _clipbounds) {
414
415 applyClipBox<Rasterizer> (_ras, bounds);
416
417 // The vectorial pipeline
418 _ras.add_path(stroke);
419
420 // Set the color and render the scanlines
421 _renderer.color(agg::rgba8_pre(color.m_r, color.m_g,
422 color.m_b, color.m_a));
423
424 agg::render_scanlines(_ras, sl, _renderer);
425
426 }
427 }
428
429 private:
430
431 const ClipBounds& _clipbounds;
432 Rasterizer _ras;
433 Renderer _renderer;
434
435 };
436
437 /// Class for rendering empty video frames.
438 //
439 /// Templated functions are used to allow using different types,
440 /// particularly for high and low quality rendering.
441 //
442 /// At present, this is bound to the renderer's ClipBounds. In future it
443 /// may be useful to pass BlendMode, Cxform, and custom clipbounds as well
444 /// as a caller-provided rendering buffer. This also applies to the
445 /// rest of the renderer API.
446 //
447 /// @param PixelFormat The format to render to.
448 template <typename PixelFormat>
449 class EmptyVideoRenderer
450 {
451
452 public:
453
454 /// Render the pixels using this renderer
455 typedef typename agg::renderer_base<PixelFormat> Renderer;
456 typedef agg::rasterizer_scanline_aa<> Rasterizer;
457
EmptyVideoRenderer(const ClipBounds & clipbounds)458 EmptyVideoRenderer(const ClipBounds& clipbounds)
459 :
460 _clipbounds(clipbounds)
461 {}
462
render(agg::path_storage & path,Renderer & rbase,const AlphaMasks & masks)463 void render(agg::path_storage& path, Renderer& rbase,
464 const AlphaMasks& masks)
465 {
466 if (masks.empty()) {
467 // No mask active
468 agg::scanline_p8 sl;
469 renderScanlines(path, rbase, sl);
470 }
471 else {
472 // Untested.
473 typedef agg::scanline_u8_am<agg::alpha_mask_gray8> Scanline;
474 Scanline sl(masks.back().getMask());
475 renderScanlines(path, rbase, sl);
476 }
477 }
478
479 private:
480
481 template<typename Scanline>
renderScanlines(agg::path_storage & path,Renderer & rbase,Scanline & sl)482 void renderScanlines(agg::path_storage& path, Renderer& rbase,
483 Scanline& sl)
484 {
485 Rasterizer ras;
486 agg::renderer_scanline_aa_solid<Renderer> ren_sl(rbase);
487
488 for (const auto& cb : _clipbounds)
489 {
490 applyClipBox<Rasterizer>(ras, cb);
491 ras.add_path(path);
492
493 const agg::rgba8 col(agg::argb8_packed(0));
494 ren_sl.color(col);
495
496 // we don't want premultiplied alpha (so, don't use blend functions)
497 //agg::render_scanlines(ras, sl, ren_sl);
498 if (ras.rewind_scanlines()) {
499 sl.reset(ras.min_x(), ras.max_x());
500 ren_sl.prepare();
501 while (ras.sweep_scanline(sl)) {
502 //ren_sl.render(sl);
503 const int y = sl.y();
504 unsigned int num_spans = sl.num_spans();
505 typename Scanline::const_iterator span = sl.begin();
506
507 for (;;) {
508 const int x = span->x;
509 assert(span->len > 0); // XXX: check span->len < 0 case!
510 if (span->len > 0)
511 rbase.copy_hline(x, y, (unsigned)span->len, col);
512 else
513 rbase.copy_hline(x, y,
514 (unsigned)(x - span->len - 1),
515 col);
516 if (--num_spans == 0)
517 break;
518 ++span;
519 }
520 }
521 }
522 }
523 }
524
525 const ClipBounds& _clipbounds;
526 };
527
528 /// Class for rendering video frames.
529 //
530 /// Templated functions are used to allow using different types,
531 /// particularly for high and low quality rendering.
532 //
533 /// At present, this is bound to the renderer's ClipBounds. In future it
534 /// may be useful to pass BlendMode, Cxform, and custom clipbounds as well
535 /// as a caller-provided rendering buffer. This also applies to the
536 /// rest of the renderer API.
537 //
538 /// @param SourceFormat The format of the video frame to be rendered
539 /// @param PixelFormat The format to render to.
540 template <typename PixelFormat, typename SourceFormat = agg::pixfmt_rgb24_pre>
541 class VideoRenderer
542 {
543
544 public:
545
546 /// Fixed types for video frame rendering.
547
548 /// Render the pixels using this renderer
549 typedef typename agg::renderer_base<PixelFormat> Renderer;
550 typedef agg::span_interpolator_linear<> Interpolator;
551 typedef agg::span_allocator<agg::rgba8> SpanAllocator;
552 typedef agg::rasterizer_scanline_aa<> Rasterizer;
553
554 // cloning image accessor is used to avoid disturbing pixels at
555 // the edges for rotated video.
556 typedef agg::image_accessor_clone<SourceFormat> Accessor;
557
558 /// Types used for different quality.
559 //
560 /// This (affects scaling) is only presently used when smoothing is
561 /// requested in high quality.
562 typedef agg::span_image_filter_rgb_nn<Accessor, Interpolator>
563 LowQualityFilter;
564
565 typedef agg::span_image_filter_rgb_bilinear<Accessor, Interpolator>
566 HighQualityFilter;
567
568 typedef agg::trans_affine Matrix;
569
VideoRenderer(const ClipBounds & clipbounds,image::GnashImage & frame,Matrix & mat,Quality quality,bool smooth)570 VideoRenderer(const ClipBounds& clipbounds, image::GnashImage& frame,
571 Matrix& mat, Quality quality, bool smooth)
572 :
573 _buf(frame.begin(), frame.width(), frame.height(),
574 frame.stride()),
575 _pixf(_buf),
576 _accessor(_pixf),
577 _interpolator(mat),
578 _clipbounds(clipbounds),
579 _quality(quality),
580 _smoothing(smooth)
581 {}
582
render(agg::path_storage & path,Renderer & rbase,const AlphaMasks & masks)583 void render(agg::path_storage& path, Renderer& rbase,
584 const AlphaMasks& masks)
585 {
586 switch (_quality)
587 {
588 case QUALITY_BEST:
589 case QUALITY_HIGH:
590 if (_smoothing) {
591 renderFrame<HighQualityFilter>(path, rbase, masks);
592 }
593 else renderFrame<LowQualityFilter>(path, rbase, masks);
594 break;
595 case QUALITY_MEDIUM:
596 case QUALITY_LOW:
597 // FIXME: Should this be still lower quality?
598 renderFrame<LowQualityFilter>(path, rbase, masks);
599 break;
600 }
601 }
602
603 private:
604
605 /// Render a frame with or without alpha masks active.
606 template<typename SpanGenerator>
renderFrame(agg::path_storage & path,Renderer & rbase,const AlphaMasks & masks)607 void renderFrame(agg::path_storage& path, Renderer& rbase,
608 const AlphaMasks& masks)
609 {
610 SpanGenerator sg(_accessor, _interpolator);
611 if (masks.empty()) {
612 // No mask active
613 agg::scanline_u8 sl;
614 renderScanlines(path, rbase, sl, sg);
615 }
616 else {
617 // Untested.
618 typedef agg::scanline_u8_am<agg::alpha_mask_gray8> Scanline;
619 Scanline sl(masks.back().getMask());
620 renderScanlines(path, rbase, sl, sg);
621 }
622 }
623
624 template<typename Scanline, typename SpanGenerator>
renderScanlines(agg::path_storage & path,Renderer & rbase,Scanline & sl,SpanGenerator & sg)625 void renderScanlines(agg::path_storage& path, Renderer& rbase,
626 Scanline& sl, SpanGenerator& sg)
627 {
628 Rasterizer _ras;
629 for (const auto& cb : _clipbounds)
630 {
631 applyClipBox<Rasterizer> (_ras, cb);
632
633 _ras.add_path(path);
634
635 agg::render_scanlines_aa(_ras, sl, rbase, _sa, sg);
636 }
637 }
638
639 // rendering buffer is used to access the frame pixels here
640 agg::rendering_buffer _buf;
641
642 const SourceFormat _pixf;
643
644 Accessor _accessor;
645
646 Interpolator _interpolator;
647
648 SpanAllocator _sa;
649
650 const ClipBounds& _clipbounds;
651
652 /// Quality of renderering
653 const Quality _quality;
654
655 /// Whether smoothing is required.
656 bool _smoothing;
657 };
658
659
660
661 }
662
663
664 // --- RENDER HANDLER ----------------------------------------------------------
665 // The class is implemented using templates so that it supports any kind of
666 // pixel format. LUT (look up tables) are not supported, however.
667
668 // Real AGG handler
669 template <class PixelFormat>
670 class Renderer_agg : public Renderer_agg_base
671 {
672
673 public:
674
description() const675 std::string description() const {
676 // TODO: make an effort to express pixel format
677 return "AGG";
678 }
679
680 // Given an image, returns a pointer to a bitmap_info class
681 // that can later be passed to FillStyleX_bitmap(), to set a
682 // bitmap fill style.
createCachedBitmap(std::unique_ptr<image::GnashImage> im)683 gnash::CachedBitmap* createCachedBitmap(std::unique_ptr<image::GnashImage> im)
684 {
685 return new agg_bitmap_info(std::move(im));
686 }
687
renderToImage(std::unique_ptr<IOChannel> io,FileType type,int quality) const688 virtual void renderToImage(std::unique_ptr<IOChannel> io,
689 FileType type, int quality) const
690 {
691 image::ImageRGBA im(xres, yres);
692 for (int x = 0; x < xres; ++x) {
693 for (int y = 0; y < yres; ++y) {
694 typename PixelFormat::color_type t = m_pixf->pixel(x, y);
695 im.setPixel(x, y, t.r, t.g, t.b, t.a);
696 }
697 }
698
699 image::Output::writeImageData(type, std::move(io), im, quality);
700 }
701
702 template<typename SourceFormat, typename Matrix>
renderVideo(image::GnashImage & frame,Matrix & img_mtx,agg::path_storage path,bool smooth)703 void renderVideo(image::GnashImage& frame, Matrix& img_mtx,
704 agg::path_storage path, bool smooth)
705 {
706
707 // renderer base for the stage buffer (not the frame image!)
708 renderer_base& rbase = *m_rbase;
709
710 VideoRenderer<PixelFormat, SourceFormat> vr(_clipbounds, frame,
711 img_mtx, _quality, smooth);
712
713 // If smoothing is requested and _quality is set to HIGH or BEST,
714 // use high-quality interpolation.
715 vr.render(path, rbase, _alphaMasks);
716 }
717
renderEmptyVideo(agg::path_storage path)718 void renderEmptyVideo(agg::path_storage path)
719 {
720 // renderer base for the stage buffer (not the frame image!)
721 renderer_base& rbase = *m_rbase;
722
723 EmptyVideoRenderer<PixelFormat> vr(_clipbounds);
724 vr.render(path, rbase, _alphaMasks);
725 }
726
drawVideoFrame(image::GnashImage * frame,const Transform & xform,const SWFRect * bounds,bool smooth)727 void drawVideoFrame(image::GnashImage* frame, const Transform& xform,
728 const SWFRect* bounds, bool smooth)
729 {
730
731 // NOTE: Assuming that the source image is RGB 8:8:8
732 // TODO: keep heavy instances alive accross frames for performance!
733 // TODO: Maybe implement specialization for 1:1 scaled videos
734 SWFMatrix mat = stage_matrix;
735 mat.concatenate(xform.matrix);
736
737 // compute video scaling relative to video object size
738 double vscaleX = bounds->width() /
739 static_cast<double>(frame->width());
740
741 double vscaleY = bounds->height() /
742 static_cast<double>(frame->height());
743
744 // convert Gnash SWFMatrix to AGG SWFMatrix and scale down to
745 // pixel coordinates while we're at it
746 agg::trans_affine mtx(mat.a() / 65536.0, mat.b() / 65536.0,
747 mat.c() / 65536.0, mat.d() / 65536.0, mat.tx(), mat.ty());
748
749 // invert SWFMatrix since this is used for the image source
750 mtx.invert();
751
752 // Apply video scale
753 mtx *= agg::trans_affine_scaling(1.0 / vscaleX, 1.0 / vscaleY);
754
755 // make a path for the video outline
756 point a, b, c, d;
757 mat.transform(&a, point(bounds->get_x_min(), bounds->get_y_min()));
758 mat.transform(&b, point(bounds->get_x_max(), bounds->get_y_min()));
759 mat.transform(&c, point(bounds->get_x_max(), bounds->get_y_max()));
760 mat.transform(&d, point(bounds->get_x_min(), bounds->get_y_max()));
761
762 agg::path_storage path;
763 path.move_to(a.x, a.y);
764 path.line_to(b.x, b.y);
765 path.line_to(c.x, c.y);
766 path.line_to(d.x, d.y);
767 path.line_to(a.x, a.y);
768
769 #ifdef HAVE_VA_VA_H
770 if (frame->location() == image::GNASH_IMAGE_GPU) {
771 RenderImage image;
772 image.reset(new GnashVaapiImageProxy(
773 static_cast<GnashVaapiImage *>(frame),
774 a.x, a.y, c.x - a.x, c.y - a.y));
775 _render_images.push_back(image);
776
777 // clear video region with transparent color
778 renderEmptyVideo(path);
779 return;
780 }
781 #endif
782
783 switch (frame->type())
784 {
785 case image::TYPE_RGBA:
786 renderVideo<agg::pixfmt_rgba32_pre>(*frame, mtx, path, smooth);
787 break;
788 case image::TYPE_RGB:
789 renderVideo<agg::pixfmt_rgb24_pre>(*frame, mtx, path, smooth);
790 break;
791 default:
792 log_error(_("Can't render this type of frame"));
793 break;
794 }
795
796 }
797
798 // Constructor
Renderer_agg(int bits_per_pixel)799 Renderer_agg(int bits_per_pixel)
800 :
801 xres(1),
802 yres(1),
803 bpp(bits_per_pixel),
804 scale_set(false),
805 m_drawing_mask(false)
806 {
807 // TODO: we really don't want to set the scale here as the core should
808 // tell us the right values before rendering anything. However this is
809 // currently difficult to implement. Removing the next call will
810 // lead to an assertion failure in begin_display() because we check
811 // whether the scale is known there.
812 set_scale(1.0f, 1.0f);
813 }
814
815 /// Initializes the rendering buffer. The memory pointed by "mem" is not
816 /// owned by the renderer and init_buffer() may be called multiple times
817 /// when the buffer size changes, for example. However, bits_per_pixel must
818 /// remain the same.
819 /// rowstride is the size, in bytes, of one row.
820 /// This method *must* be called prior to any other method of the class!
init_buffer(unsigned char * mem,int,int x,int y,int rowstride)821 void init_buffer(unsigned char *mem, int /*size*/, int x, int y, int rowstride)
822 {
823 assert(x > 0);
824 assert(y > 0);
825
826 xres = x;
827 yres = y;
828
829 m_rbuf.attach(mem, xres, yres, rowstride);
830
831 // allocate pixel format accessor and renderer_base
832 m_pixf.reset(new PixelFormat(m_rbuf));
833 m_rbase.reset(new renderer_base(*m_pixf));
834
835 // by default allow drawing everywhere
836 set_invalidated_region_world();
837 }
838
839
begin_display(const gnash::rgba & bg,int,int,float,float,float,float)840 void begin_display(const gnash::rgba& bg,
841 int /*viewport_width*/, int /*viewport_height*/,
842 float /*x0*/, float /*x1*/, float /*y0*/, float /*y1*/)
843 {
844 assert(m_pixf.get());
845
846 assert(scale_set);
847
848 // Render images list is cleared here because the GUI may want
849 // them for display after ::end_display()
850 _render_images.clear();
851
852 // clear the stage using the background color
853 if ( ! _clipbounds.empty() )
854 {
855 const agg::rgba8& col = agg::rgba8_pre(bg.m_r, bg.m_g, bg.m_b, bg.m_a);
856 for (const auto& bounds : _clipbounds)
857 {
858 clear_framebuffer(bounds, col);
859 }
860 }
861
862 // reset status variables
863 m_drawing_mask = false;
864 }
865
866
startInternalRender(image::GnashImage & im)867 virtual Renderer* startInternalRender(image::GnashImage& im) {
868
869 std::unique_ptr<Renderer_agg_base> in;
870
871 switch (im.type()) {
872 case image::TYPE_RGB:
873 in.reset(new Renderer_agg<typename RGB::PixelFormat>(24));
874 break;
875 case image::TYPE_RGBA:
876 in.reset(new Renderer_agg<typename RGBA::PixelFormat>(32));
877 break;
878 default:
879 std::abort();
880 }
881
882 const size_t width = im.width();
883 const size_t height = im.height();
884 const size_t stride = width * (im.type() == image::TYPE_RGBA ? 4 : 3);
885
886 in->init_buffer(im.begin(), width * height, width, height, stride);
887 _external.reset(in.release());
888 return _external.get();
889 }
890
endInternalRender()891 virtual void endInternalRender() {
892 _external.reset();
893 }
894
895 // renderer_base.clear() does no clipping which clears the
896 // whole framebuffer even if we update just a small portion
897 // of the screen. The result would be still correct, but slower.
898 // This function clears only a certain portion of the screen, while /not/
899 // being notably slower for a fullscreen clear.
clear_framebuffer(const geometry::Range2d<int> & region,const agg::rgba8 & color)900 void clear_framebuffer(const geometry::Range2d<int>& region,
901 const agg::rgba8& color)
902 {
903 assert(region.isFinite());
904
905 // add 1 to width since we have still to draw a pixel when
906 // getMinX==getMaxX
907 unsigned int width=region.width()+1;
908
909 // <Udo> Note: We don't need to check for width/height anymore because
910 // Range2d will take care that getMinX <= getMaxX and it's okay when
911 // region.width()==0 because in that case getMinX==getMaxX and we have
912 // still a pixel to draw.
913
914 const unsigned int left=region.getMinX();
915
916 for (unsigned int y=region.getMinY(), maxy=region.getMaxY();
917 y<=maxy; ++y) {
918 m_pixf->copy_hline(left, y, width, color);
919 }
920 }
921
922 // Clean up after rendering a frame.
end_display()923 void end_display()
924 {
925 if (m_drawing_mask) {
926 log_debug("Warning: rendering ended while drawing a mask");
927 }
928
929 while (! _alphaMasks.empty()) {
930 log_debug("Warning: rendering ended while masks "
931 "were still active");
932 disable_mask();
933 }
934 }
935
936 // Draw the line strip formed by the sequence of points.
drawLine(const std::vector<point> & coords,const rgba & color,const SWFMatrix & line_mat)937 void drawLine(const std::vector<point>& coords, const rgba& color,
938 const SWFMatrix& line_mat)
939 {
940
941 assert(m_pixf.get());
942
943 if (_clipbounds.empty()) return;
944 if (coords.empty()) return;
945
946 SWFMatrix mat = stage_matrix;
947 mat.concatenate(line_mat);
948
949 LineRenderer<PixelFormat> lr(_clipbounds, *m_rbase);
950
951 // -- create path --
952 agg::path_storage path;
953
954 typename LineRenderer<PixelFormat>::Stroke stroke(path);
955 stroke.width(1);
956 stroke.line_cap(agg::round_cap);
957 stroke.line_join(agg::round_join);
958
959 typedef std::vector<point> Points;
960
961 // We've asserted that it has at least one element.
962 Points::const_iterator i = coords.begin();
963
964 point pnt;
965
966 mat.transform(&pnt, *i);
967 path.move_to(pnt.x, pnt.y);
968
969 ++i;
970
971 for (const Points::const_iterator e = coords.end(); i != e; ++i)
972 {
973 mat.transform(&pnt, *i);
974 path.line_to(pnt.x, pnt.y);
975 }
976
977 if (_alphaMasks.empty()) {
978 // No mask active
979 agg::scanline_p8 sl;
980 lr.render(sl, stroke, color);
981 }
982 else {
983 // Mask is active!
984 typedef agg::scanline_u8_am<agg::alpha_mask_gray8> sl_type;
985 sl_type sl(_alphaMasks.back().getMask());
986 lr.render(sl, stroke, color);
987 }
988
989 }
990
991
begin_submit_mask()992 void begin_submit_mask()
993 {
994 // Set flag so that rendering of shapes is simplified (only solid fill)
995 m_drawing_mask = true;
996
997 _alphaMasks.push_back(new AlphaMask(xres, yres));
998 AlphaMask& new_mask = _alphaMasks.back();
999
1000 for (const auto& bounds : _clipbounds ) {
1001 new_mask.clear(bounds);
1002 }
1003
1004 }
1005
end_submit_mask()1006 void end_submit_mask()
1007 {
1008 m_drawing_mask = false;
1009 }
1010
disable_mask()1011 void disable_mask()
1012 {
1013 assert(!_alphaMasks.empty());
1014 _alphaMasks.pop_back();
1015 }
1016
1017
drawGlyph(const SWF::ShapeRecord & shape,const rgba & color,const SWFMatrix & mat)1018 void drawGlyph(const SWF::ShapeRecord& shape, const rgba& color,
1019 const SWFMatrix& mat)
1020 {
1021 if (shape.subshapes().empty()) return;
1022 assert(shape.subshapes().size() == 1);
1023
1024 // select relevant clipping bounds
1025 if (shape.getBounds().is_null()) {
1026 return;
1027 }
1028 select_clipbounds(shape.getBounds(), mat);
1029
1030 if (_clipbounds_selected.empty()) return;
1031
1032 GnashPaths paths;
1033 apply_matrix_to_path(shape.subshapes().front().paths(), paths, mat);
1034
1035 // If it's a mask, we don't need the rest.
1036 if (m_drawing_mask) {
1037 draw_mask_shape(paths, false);
1038 return;
1039 }
1040
1041 // convert gnash paths to agg paths.
1042 AggPaths agg_paths;
1043 buildPaths(agg_paths, paths);
1044
1045 std::vector<FillStyle> v(1, FillStyle(SolidFill(color)));
1046
1047 // prepare style handler
1048 StyleHandler sh;
1049 build_agg_styles(sh, v, mat, SWFCxForm());
1050
1051 draw_shape(paths, agg_paths, sh, false);
1052
1053 // NOTE: Do not use even-odd filling rule for glyphs!
1054
1055 // clear clipping ranges to ease debugging
1056 _clipbounds_selected.clear();
1057 }
1058
1059
1060 /// Fills _clipbounds_selected with pointers to _clipbounds members who
1061 /// intersect with the given character (transformed by mat). This avoids
1062 /// rendering of characters outside a particular clipping range.
1063 /// "_clipbounds_selected" is used by draw_shape() and draw_outline() and
1064 /// *must* be initialized prior to using those function.
select_clipbounds(const SWFRect & objectBounds,const SWFMatrix & source_mat)1065 void select_clipbounds(const SWFRect& objectBounds, const SWFMatrix& source_mat)
1066 {
1067
1068 SWFMatrix mat = stage_matrix;
1069 mat.concatenate(source_mat);
1070
1071 _clipbounds_selected.clear();
1072 _clipbounds_selected.reserve(_clipbounds.size());
1073
1074 if (objectBounds.is_null()) {
1075 log_debug("Warning: select_clipbounds encountered a character "
1076 "definition with null bounds");
1077 return;
1078 }
1079
1080 SWFRect bounds;
1081 bounds.set_null();
1082 bounds.expand_to_transformed_rect(mat, objectBounds);
1083
1084 assert(bounds.getRange().isFinite());
1085
1086 for (const auto& clip : _clipbounds) {
1087
1088 if (clip.intersects(bounds.getRange()))
1089 _clipbounds_selected.push_back(&clip);
1090
1091 }
1092 }
1093
select_all_clipbounds()1094 void select_all_clipbounds() {
1095
1096 if (_clipbounds_selected.size() == _clipbounds.size()) return;
1097
1098 _clipbounds_selected.clear();
1099 _clipbounds_selected.reserve(_clipbounds.size());
1100
1101 for (const auto& clip : _clipbounds)
1102 {
1103 _clipbounds_selected.push_back(&clip);
1104 }
1105 }
1106
drawShape(const SWF::ShapeRecord & shape,const Transform & xform)1107 void drawShape(const SWF::ShapeRecord& shape, const Transform& xform)
1108 {
1109 // check if the character needs to be rendered at all
1110 SWFRect cur_bounds;
1111
1112 cur_bounds.expand_to_transformed_rect(xform.matrix, shape.getBounds());
1113
1114 if (!bounds_in_clipping_area(cur_bounds.getRange()))
1115 {
1116 return; // no need to draw
1117 }
1118
1119 for (const SWF::Subshape& subshape : shape.subshapes()) {
1120
1121 const SWF::ShapeRecord::FillStyles& fillStyles = subshape.fillStyles();
1122 const SWF::ShapeRecord::LineStyles& lineStyles = subshape.lineStyles();
1123 const SWF::ShapeRecord::Paths& paths = subshape.paths();
1124
1125 // select ranges
1126 select_clipbounds(shape.getBounds(), xform.matrix);
1127
1128 // render the DisplayObject's subshape.
1129 drawShape(fillStyles, lineStyles, paths, xform.matrix,
1130 xform.colorTransform);
1131 }
1132 }
1133
drawShape(const std::vector<FillStyle> & FillStyles,const std::vector<LineStyle> & line_styles,const std::vector<Path> & objpaths,const SWFMatrix & mat,const SWFCxForm & cx)1134 void drawShape(const std::vector<FillStyle>& FillStyles,
1135 const std::vector<LineStyle>& line_styles,
1136 const std::vector<Path>& objpaths, const SWFMatrix& mat,
1137 const SWFCxForm& cx)
1138 {
1139
1140 bool have_shape, have_outline;
1141
1142 analyzePaths(objpaths, have_shape, have_outline);
1143
1144 if (!have_shape && !have_outline) {
1145 // Early return for invisible character.
1146 return;
1147 }
1148
1149 GnashPaths paths;
1150 apply_matrix_to_path(objpaths, paths, mat);
1151
1152 // Masks apparently do not use agg_paths, so return
1153 // early
1154 if (m_drawing_mask) {
1155
1156 // Shape is drawn inside a mask, skip sub-shapes handling and
1157 // outlines
1158 draw_mask_shape(paths, false);
1159 return;
1160 }
1161
1162 AggPaths agg_paths;
1163 AggPaths agg_paths_rounded;
1164
1165 // Flash only aligns outlines. Probably this is done at rendering
1166 // level.
1167 if (have_outline) {
1168 buildPaths_rounded(agg_paths_rounded, paths, line_styles);
1169 }
1170
1171 if (have_shape) {
1172 buildPaths(agg_paths, paths);
1173 }
1174
1175
1176 if (_clipbounds_selected.empty()) {
1177 #ifdef GNASH_WARN_WHOLE_CHARACTER_SKIP
1178 log_debug("Warning: AGG renderer skipping a whole character");
1179 #endif
1180 return;
1181 }
1182
1183 // prepare fill styles
1184 StyleHandler sh;
1185 if (have_shape) build_agg_styles(sh, FillStyles, mat, cx);
1186
1187
1188 if (have_shape) {
1189 draw_shape(paths, agg_paths, sh, true);
1190 }
1191 if (have_outline) {
1192 draw_outlines(paths, agg_paths_rounded,
1193 line_styles, cx, mat);
1194 }
1195
1196 // Clear selected clipbounds to ease debugging
1197 _clipbounds_selected.clear();
1198 }
1199
1200 /// Takes a path and translates it using the given SWFMatrix. The new path
1201 /// is stored in paths_out. Both paths_in and paths_out are expected to
1202 /// be in TWIPS.
apply_matrix_to_path(const GnashPaths & paths_in,GnashPaths & paths_out,const SWFMatrix & source_mat)1203 void apply_matrix_to_path(const GnashPaths &paths_in,
1204 GnashPaths& paths_out, const SWFMatrix &source_mat)
1205 {
1206
1207 SWFMatrix mat;
1208 // make sure paths_out is also in TWIPS to keep accuracy.
1209 mat.concatenate_scale(20.0, 20.0);
1210 mat.concatenate(stage_matrix);
1211 mat.concatenate(source_mat);
1212
1213 // Copy paths for in-place transform
1214 paths_out = paths_in;
1215
1216 /// Transform all the paths using the matrix.
1217 std::for_each(paths_out.begin(), paths_out.end(),
1218 std::bind(&Path::transform, std::placeholders::_1,
1219 std::ref(mat)));
1220 }
1221
1222 // Version of buildPaths that uses rounded coordinates (pixel hinting)
1223 // for line styles that want it.
1224 // This is used for outlines which are aligned to the pixel grid to avoid
1225 // anti-aliasing problems (a perfect horizontal line being drawn over two
1226 // lines and looking blurry). The proprietary player does this too.
1227 //
1228 // Not all points are aligned, only those lines that:
1229 // - are straight
1230 // - are pure horizontal or vertical
1231 // Also, single segments of a path may be aligned or not depending on
1232 // the segment properties (this matches MM player behaviour)
1233 //
1234 // This function - in contrast to buildPaths() - also checks noClose
1235 // flag and automatically closes polygons.
1236 //
1237 // TODO: Flash never aligns lines that are wider than 1 pixel on *screen*,
1238 // but we currently don't check the width.
buildPaths_rounded(AggPaths & dest,const GnashPaths & paths,const std::vector<LineStyle> & line_styles)1239 void buildPaths_rounded(AggPaths& dest,
1240 const GnashPaths& paths, const std::vector<LineStyle>& line_styles)
1241 {
1242
1243 const float subpixel_offset = 0.5f;
1244
1245 const size_t pcount = paths.size();
1246
1247 dest.resize(pcount);
1248
1249 for (size_t pno=0; pno<pcount; ++pno) {
1250
1251 const Path& this_path = paths[pno];
1252 agg::path_storage& new_path = dest[pno];
1253
1254 bool hinting=false, closed=false, hairline=false;
1255
1256 if (this_path.m_line) {
1257 const LineStyle& lstyle = line_styles[this_path.m_line-1];
1258
1259 hinting = lstyle.doPixelHinting();
1260 closed = this_path.isClosed() && !lstyle.noClose();
1261
1262 // check if this line is a hairline ON SCREEN
1263 // TODO: we currently only check for hairlines per definiton, not
1264 // for thin lines that become hair lines due to scaling
1265 if (lstyle.getThickness()<=20)
1266 hairline = true;
1267 }
1268
1269 float prev_ax = twipsToPixels(this_path.ap.x);
1270 float prev_ay = twipsToPixels(this_path.ap.y);
1271 bool prev_align_x = true;
1272 bool prev_align_y = true;
1273
1274 size_t ecount = this_path.m_edges.size();
1275
1276 // avoid extra edge when doing implicit close later
1277 if (closed && ecount &&
1278 this_path.m_edges.back().straight()) --ecount;
1279
1280 for (size_t eno=0; eno<ecount; ++eno) {
1281
1282 const Edge& this_edge = this_path.m_edges[eno];
1283
1284 float this_ax = twipsToPixels(this_edge.ap.x);
1285 float this_ay = twipsToPixels(this_edge.ap.y);
1286
1287 if (hinting || this_edge.straight()) {
1288
1289 // candidate for alignment?
1290 bool align_x = hinting || (hairline && (prev_ax == this_ax));
1291 bool align_y = hinting || (hairline && (prev_ay == this_ay));
1292
1293 if (align_x)
1294 this_ax = round(this_ax);
1295
1296 if (align_y)
1297 this_ay = round(this_ay);
1298
1299 // first line?
1300 if (eno==0) {
1301
1302 if (align_x)
1303 prev_ax = round(prev_ax);
1304
1305 if (align_y)
1306 prev_ay = round(prev_ay);
1307
1308 new_path.move_to(prev_ax + subpixel_offset,
1309 prev_ay + subpixel_offset);
1310
1311 } else {
1312
1313 // not the first line, but the previous anchor point
1314 // might belong to a curve and thus may not be aligned.
1315 // We need to have both anchors of this new line to be
1316 // aligned, so it may be neccesary to add a line
1317 if ((align_x && !prev_align_x) || (align_y && !prev_align_y)) {
1318
1319 if (align_x)
1320 prev_ax = round(prev_ax);
1321
1322 if (align_y)
1323 prev_ay = round(prev_ay);
1324
1325 new_path.line_to(prev_ax + subpixel_offset,
1326 prev_ay + subpixel_offset);
1327
1328 }
1329
1330 // TODO: (minor flaw) Flash player never aligns anchor points
1331 // of curves, even if they are attached to straight vertical
1332 // or horizontal lines. It can be seen easily with rounded
1333 // rectangles, where the curves are never aligned and all
1334 // straight lines are. AGG backend will align the curve anchor
1335 // point that follows the straight line. It's not a big problem
1336 // but it's not exact...
1337
1338 }
1339
1340 new_path.line_to(this_ax + subpixel_offset,
1341 this_ay + subpixel_offset);
1342
1343 prev_align_x = align_x;
1344 prev_align_y = align_y;
1345
1346
1347 } else {
1348
1349 // first line?
1350 if (eno==0)
1351 new_path.move_to(prev_ax, prev_ay);
1352
1353 // never align curves!
1354 new_path.curve3(
1355 twipsToPixels(this_edge.cp.x) + subpixel_offset,
1356 twipsToPixels(this_edge.cp.y) + subpixel_offset,
1357 this_ax + subpixel_offset,
1358 this_ay + subpixel_offset);
1359
1360 prev_align_x = false;
1361 prev_align_y = false;
1362
1363 }
1364
1365 prev_ax = this_ax;
1366 prev_ay = this_ay;
1367
1368 } //for
1369
1370 if (closed)
1371 new_path.close_polygon();
1372
1373 }
1374 } //buildPaths_rounded
1375
1376 // Initializes the internal styles class for AGG renderer
build_agg_styles(StyleHandler & sh,const std::vector<FillStyle> & FillStyles,const SWFMatrix & fillstyle_matrix,const SWFCxForm & cx)1377 void build_agg_styles(StyleHandler& sh,
1378 const std::vector<FillStyle>& FillStyles,
1379 const SWFMatrix& fillstyle_matrix, const SWFCxForm& cx) {
1380
1381 SWFMatrix inv_stage_matrix = stage_matrix;
1382 inv_stage_matrix.invert();
1383
1384 const size_t fcount = FillStyles.size();
1385
1386 for (size_t fno = 0; fno < fcount; ++fno) {
1387 const AddStyles st(stage_matrix, fillstyle_matrix, cx, sh,
1388 _quality);
1389 boost::apply_visitor(st, FillStyles[fno].fill);
1390 }
1391 }
1392
1393
1394 /// Draws the given path using the given fill style and color transform.
1395 //
1396 /// Normally, Flash shapes are drawn using even-odd filling rule. However,
1397 /// for glyphs non-zero filling rule should be used (even_odd=0).
1398 /// Note the paths have already been transformed by the SWFMatrix and
1399 /// 'subshape_id' defines which sub-shape should be drawn (-1 means all
1400 /// subshapes).
1401 ///
1402 /// Note the *coordinates* in "paths" are not used because they are
1403 /// already prepared in agg_paths. The (nearly ambiguous) "path" parameter
1404 /// is used to access other properties like fill styles and subshapes.
1405 ///
1406 /// @param subshape_id
1407 /// Defines which subshape to draw. -1 means all subshapes.
1408 ///
draw_shape(const GnashPaths & paths,const AggPaths & agg_paths,StyleHandler & sh,bool even_odd)1409 void draw_shape(const GnashPaths &paths,
1410 const AggPaths& agg_paths,
1411 StyleHandler& sh, bool even_odd) {
1412
1413 if (_alphaMasks.empty()) {
1414
1415 // No mask active, use normal scanline renderer
1416
1417 typedef agg::scanline_u8 scanline_type;
1418
1419 scanline_type sl;
1420
1421 draw_shape_impl<scanline_type> (paths, agg_paths,
1422 sh, even_odd, sl);
1423
1424 } else {
1425
1426 // Mask is active, use alpha mask scanline renderer
1427
1428 typedef agg::scanline_u8_am<agg::alpha_mask_gray8> scanline_type;
1429
1430 scanline_type sl(_alphaMasks.back().getMask());
1431
1432 draw_shape_impl<scanline_type> (paths, agg_paths,
1433 sh, even_odd, sl);
1434
1435 }
1436
1437 }
1438
1439 /// Template for draw_shape(). Two different scanline types are suppored,
1440 /// one with and one without an alpha mask. This makes drawing without masks
1441 /// much faster.
1442 template <class scanline_type>
draw_shape_impl(const GnashPaths & paths,const AggPaths & agg_paths,StyleHandler & sh,bool even_odd,scanline_type & sl)1443 void draw_shape_impl(const GnashPaths &paths,
1444 const AggPaths& agg_paths,
1445 StyleHandler& sh, bool even_odd, scanline_type& sl) {
1446 /*
1447 Fortunately, AGG provides a rasterizer that fits perfectly to the flash
1448 data model. So we just have to feed AGG with all data and we're done. :-)
1449 This is also far better than recomposing the polygons as the rasterizer
1450 can do everything in one pass and it is also better for adjacent edges
1451 (anti aliasing).
1452 Thank to Maxim Shemanarev for providing us such a great tool with AGG...
1453 */
1454
1455 assert(m_pixf.get());
1456
1457 assert(!m_drawing_mask);
1458
1459 if ( _clipbounds.empty() ) return;
1460
1461 // Target renderer
1462 renderer_base& rbase = *m_rbase;
1463
1464 typedef agg::rasterizer_compound_aa<agg::rasterizer_sl_clip_int> ras_type;
1465 ras_type rasc; // flash-like renderer
1466
1467 agg::renderer_scanline_aa_solid<
1468 agg::renderer_base<PixelFormat> > ren_sl(rbase); // solid fills
1469 agg::span_allocator<agg::rgba8> alloc; // span allocator (?)
1470
1471
1472 // activate even-odd filling rule
1473 if (even_odd)
1474 rasc.filling_rule(agg::fill_even_odd);
1475 else
1476 rasc.filling_rule(agg::fill_non_zero);
1477
1478
1479 for (const geometry::Range2d<int>* bounds : _clipbounds_selected) {
1480
1481 applyClipBox<ras_type> (rasc, *bounds);
1482
1483 // push paths to AGG
1484 const size_t pcount = paths.size();
1485
1486 for (size_t pno=0; pno<pcount; ++pno) {
1487
1488 const Path &this_path_gnash = paths[pno];
1489 agg::path_storage &this_path_agg =
1490 const_cast<agg::path_storage&>(agg_paths[pno]);
1491
1492 agg::conv_curve<agg::path_storage> curve(this_path_agg);
1493
1494 if ((this_path_gnash.m_fill0==0) && (this_path_gnash.m_fill1==0)) {
1495 // Skip this path as it contains no fill style
1496 continue;
1497 }
1498
1499 // Tell the rasterizer which styles the following path will use.
1500 // The good thing is, that it already supports two fill styles out of
1501 // the box.
1502 // Flash uses value "0" for "no fill", whereas AGG uses "-1" for that.
1503 rasc.styles(this_path_gnash.m_fill0-1, this_path_gnash.m_fill1-1);
1504
1505 // add path to the compound rasterizer
1506 rasc.add_path(curve);
1507
1508 }
1509
1510 agg::render_scanlines_compound_layered(rasc, sl, rbase, alloc, sh);
1511 }
1512
1513 } // draw_shape_impl
1514
1515
1516
1517
1518 // very similar to draw_shape but used for generating masks. There are no
1519 // fill styles nor subshapes and such. Just render plain solid shapes.
draw_mask_shape(const GnashPaths & paths,bool even_odd)1520 void draw_mask_shape(const GnashPaths& paths, bool even_odd)
1521 {
1522
1523 const AlphaMasks::size_type mask_count = _alphaMasks.size();
1524
1525 if (mask_count < 2) {
1526
1527 // This is the first level mask
1528
1529 typedef agg::scanline_u8 scanline_type;
1530
1531 scanline_type sl;
1532
1533 draw_mask_shape_impl(paths, even_odd, sl);
1534
1535 }
1536 else {
1537
1538 // Woohoo! We're drawing a nested mask! Use the previous mask while
1539 // drawing the new one, the result will be the intersection.
1540
1541 typedef agg::scanline_u8_am<agg::alpha_mask_gray8> scanline_type;
1542
1543 scanline_type sl(_alphaMasks[mask_count - 2].getMask());
1544
1545 draw_mask_shape_impl(paths, even_odd, sl);
1546
1547 }
1548
1549 }
1550
1551
1552 template <class scanline_type>
draw_mask_shape_impl(const GnashPaths & paths,bool even_odd,scanline_type & sl)1553 void draw_mask_shape_impl(const GnashPaths& paths, bool even_odd,
1554 scanline_type& sl) {
1555
1556 typedef agg::pixfmt_gray8 pixfmt;
1557 typedef agg::renderer_base<pixfmt> renderer_base;
1558
1559 assert(!_alphaMasks.empty());
1560
1561 // dummy style handler
1562 typedef agg_mask_style_handler sh_type;
1563 sh_type sh;
1564
1565 // compound rasterizer used for flash shapes
1566 typedef agg::rasterizer_compound_aa<agg::rasterizer_sl_clip_int> rasc_type;
1567 rasc_type rasc;
1568
1569
1570 // activate even-odd filling rule
1571 if (even_odd) rasc.filling_rule(agg::fill_even_odd);
1572 else rasc.filling_rule(agg::fill_non_zero);
1573
1574 // push paths to AGG
1575 agg::path_storage path;
1576 agg::conv_curve<agg::path_storage> curve(path);
1577
1578 for (const Path& this_path : paths) {
1579
1580 path.remove_all();
1581
1582 // reduce everything to just one fill style!
1583 rasc.styles(this_path.m_fill0==0 ? -1 : 0,
1584 this_path.m_fill1==0 ? -1 : 0);
1585
1586 // starting point of path
1587 path.move_to(twipsToPixels(this_path.ap.x),
1588 twipsToPixels(this_path.ap.y));
1589
1590 // Add all edges to the path.
1591 std::for_each(this_path.m_edges.begin(), this_path.m_edges.end(),
1592 EdgeToPath(path));
1593
1594 // add to rasterizer
1595 rasc.add_path(curve);
1596
1597 } // for path
1598
1599 // renderer base
1600 renderer_base& rbase = _alphaMasks.back().get_rbase();
1601
1602 // span allocator
1603 typedef agg::span_allocator<agg::gray8> alloc_type;
1604 alloc_type alloc;
1605
1606
1607 // now render that thing!
1608 agg::render_scanlines_compound_layered (rasc, sl, rbase, alloc, sh);
1609
1610 } // draw_mask_shape
1611
1612
1613
1614 /// Just like draw_shapes() except that it draws an outline.
draw_outlines(const GnashPaths & paths,const AggPaths & agg_paths,const std::vector<LineStyle> & line_styles,const SWFCxForm & cx,const SWFMatrix & linestyle_matrix)1615 void draw_outlines(const GnashPaths &paths,
1616 const AggPaths& agg_paths,
1617 const std::vector<LineStyle> &line_styles, const SWFCxForm& cx,
1618 const SWFMatrix& linestyle_matrix) {
1619
1620 if (_alphaMasks.empty()) {
1621
1622 // No mask active, use normal scanline renderer
1623
1624 typedef agg::scanline_u8 scanline_type;
1625
1626 scanline_type sl;
1627
1628 draw_outlines_impl<scanline_type> (paths, agg_paths,
1629 line_styles, cx, linestyle_matrix, sl);
1630
1631 } else {
1632
1633 // Mask is active, use alpha mask scanline renderer
1634
1635 typedef agg::scanline_u8_am<agg::alpha_mask_gray8> scanline_type;
1636
1637 scanline_type sl(_alphaMasks.back().getMask());
1638
1639 draw_outlines_impl<scanline_type> (paths, agg_paths,
1640 line_styles, cx, linestyle_matrix, sl);
1641
1642 }
1643
1644 } //draw_outlines
1645
1646
1647 /// Template for draw_outlines(), see draw_shapes_impl().
1648 template <class scanline_type>
draw_outlines_impl(const GnashPaths & paths,const AggPaths & agg_paths,const std::vector<LineStyle> & line_styles,const SWFCxForm & cx,const SWFMatrix & linestyle_matrix,scanline_type & sl)1649 void draw_outlines_impl(const GnashPaths &paths,
1650 const AggPaths& agg_paths,
1651 const std::vector<LineStyle> &line_styles, const SWFCxForm& cx,
1652 const SWFMatrix& linestyle_matrix, scanline_type& sl) {
1653
1654 assert(m_pixf.get());
1655
1656 // Flash ignores lines in mask /definitions/
1657 if (m_drawing_mask) return;
1658
1659 if ( _clipbounds.empty() ) return;
1660
1661 // TODO: While walking the paths for filling them, remember when a path
1662 // has a line style associated, so that we avoid walking the paths again
1663 // when there really are no outlines to draw...
1664
1665 // use avg between x and y scale
1666 const float stroke_scale = (std::abs(linestyle_matrix.get_x_scale()) +
1667 std::abs(linestyle_matrix.get_y_scale())) / 2.0f * get_stroke_scale();
1668
1669
1670 // AGG stuff
1671 typedef agg::rasterizer_scanline_aa<> ras_type;
1672 ras_type ras; // anti alias
1673
1674 renderer_base& rbase = *m_rbase;
1675
1676 agg::renderer_scanline_aa_solid<
1677 agg::renderer_base<PixelFormat> > ren_sl(rbase); // solid fills
1678
1679
1680 for (const geometry::Range2d<int>* bounds : _clipbounds_selected) {
1681
1682 applyClipBox<ras_type> (ras, *bounds);
1683
1684 for (size_t pno=0, pcount=paths.size(); pno<pcount; ++pno) {
1685
1686 const Path& this_path_gnash = paths[pno];
1687
1688 agg::path_storage &this_path_agg =
1689 const_cast<agg::path_storage&>(agg_paths[pno]);
1690
1691 if (this_path_gnash.m_line==0) {
1692 // Skip this path as it contains no line style
1693 continue;
1694 }
1695
1696 agg::conv_curve< agg::path_storage > curve(this_path_agg); // to render curves
1697 agg::conv_stroke< agg::conv_curve < agg::path_storage > >
1698 stroke(curve); // to get an outline
1699
1700 const LineStyle& lstyle = line_styles[this_path_gnash.m_line-1];
1701
1702 int thickness = lstyle.getThickness();
1703 if (!thickness) stroke.width(1); // hairline
1704 else if ( (!lstyle.scaleThicknessVertically()) && (!lstyle.scaleThicknessHorizontally()) )
1705 {
1706 stroke.width(twipsToPixels(thickness));
1707 }
1708 else
1709 {
1710 if ((!lstyle.scaleThicknessVertically()) ||
1711 (!lstyle.scaleThicknessHorizontally()))
1712 {
1713 LOG_ONCE(log_unimpl(_("Unidirectionally scaled strokes in "
1714 "AGG renderer (we'll scale by the "
1715 "scalable one)")) );
1716 }
1717 stroke.width(std::max(1.0f, thickness*stroke_scale));
1718 }
1719
1720 // TODO: support endCapStyle
1721
1722 // TODO: When lstyle.noClose==0 and the start and end point matches,
1723 // then render a real join instead of the caps.
1724
1725 switch (lstyle.startCapStyle()) {
1726 case CAP_NONE : stroke.line_cap(agg::butt_cap); break;
1727 case CAP_SQUARE : stroke.line_cap(agg::square_cap); break;
1728 default : case CAP_ROUND : stroke.line_cap(agg::round_cap);
1729 }
1730
1731 switch (lstyle.joinStyle()) {
1732 case JOIN_BEVEL : stroke.line_join(agg::bevel_join); break;
1733 case JOIN_MITER : stroke.line_join(agg::miter_join); break;
1734 default : case JOIN_ROUND : stroke.line_join(agg::round_join);
1735 }
1736
1737 stroke.miter_limit(lstyle.miterLimitFactor());
1738
1739 ras.reset();
1740 ras.add_path(stroke);
1741
1742 rgba color = cx.transform(lstyle.get_color());
1743 ren_sl.color(agg::rgba8_pre(color.m_r, color.m_g, color.m_b, color.m_a));
1744
1745 agg::render_scanlines(ras, sl, ren_sl);
1746
1747 }
1748
1749
1750 }
1751
1752 } // draw_outlines_impl
1753
1754
1755
1756 /// Draws the given polygon.
1757 template <class scanline_type>
draw_poly_impl(const point * corners,size_t corner_count,const rgba & fill,const rgba & outline,scanline_type & sl,const SWFMatrix & poly_mat)1758 void draw_poly_impl(const point* corners, size_t corner_count, const rgba& fill,
1759 const rgba& outline, scanline_type& sl, const SWFMatrix& poly_mat) {
1760
1761 assert(m_pixf.get());
1762
1763 if (corner_count<1) return;
1764
1765 if ( _clipbounds.empty() ) return;
1766
1767 SWFMatrix mat = stage_matrix;
1768 mat.concatenate(poly_mat);
1769
1770 typedef agg::rasterizer_scanline_aa<> ras_type;
1771 renderer_base& rbase = *m_rbase;
1772
1773 ras_type ras;
1774 agg::renderer_scanline_aa_solid<
1775 agg::renderer_base<PixelFormat> > ren_sl(rbase);
1776
1777 // -- create path --
1778 agg::path_storage path;
1779 point pnt, origin;
1780
1781
1782 // Note: The coordinates are rounded and 0.5 is added to snap them to the
1783 // center of the pixel. This avoids blurring caused by anti-aliasing.
1784
1785 // The default conversion of the boost converter is truncation.
1786 boost::numeric::converter<int,float> truncator;
1787
1788 mat.transform(&origin,
1789 point(truncator(corners[0].x), truncator(corners[0].y)));
1790 path.move_to(truncator(origin.x)+0.5, truncator(origin.y)+0.5);
1791
1792 for (unsigned int i=1; i<corner_count; ++i) {
1793
1794 mat.transform(&pnt, point(corners[i].x, corners[i].y));
1795
1796 path.line_to(truncator(pnt.x)+0.5, truncator(pnt.y)+0.5);
1797 }
1798
1799 // close polygon
1800 path.line_to(truncator(origin.x)+0.5, truncator(origin.y)+0.5);
1801
1802
1803
1804 // -- render --
1805
1806 // iterate through clipping bounds
1807 for (const auto& bounds : _clipbounds) {
1808
1809 applyClipBox<ras_type> (ras, bounds);
1810
1811
1812 // fill polygon
1813 if (fill.m_a>0) {
1814 ras.add_path(path);
1815 ren_sl.color(agg::rgba8_pre(fill.m_r, fill.m_g, fill.m_b, fill.m_a));
1816
1817 agg::render_scanlines(ras, sl, ren_sl);
1818 }
1819
1820 // draw outline
1821 if (outline.m_a>0) {
1822 agg::conv_stroke<agg::path_storage> stroke(path);
1823
1824 stroke.width(1);
1825
1826 ren_sl.color(agg::rgba8_pre(outline.m_r, outline.m_g, outline.m_b, outline.m_a));
1827
1828 ras.add_path(stroke);
1829
1830 agg::render_scanlines(ras, sl, ren_sl);
1831 }
1832 }
1833
1834 } //draw_poly_impl
1835
1836
draw_poly(const std::vector<point> & corners,const rgba & fill,const rgba & outline,const SWFMatrix & mat,bool masked)1837 void draw_poly(const std::vector<point>& corners, const rgba& fill,
1838 const rgba& outline, const SWFMatrix& mat, bool masked) {
1839
1840 if (masked && !_alphaMasks.empty()) {
1841
1842 // apply mask
1843
1844 typedef agg::scanline_u8_am<agg::alpha_mask_gray8> sl_type;
1845
1846 sl_type sl(_alphaMasks.back().getMask());
1847
1848 draw_poly_impl<sl_type>(&corners.front(), corners.size(), fill, outline, sl, mat);
1849
1850 } else {
1851
1852 // no mask
1853
1854 typedef agg::scanline_p8 sl_type; // packed scanline (faster for solid fills)
1855
1856 sl_type sl;
1857
1858 draw_poly_impl<sl_type>(&corners.front(), corners.size(), fill, outline, sl, mat);
1859
1860 }
1861
1862 }
1863
1864
get_stroke_scale()1865 inline float get_stroke_scale() {
1866 return (stage_matrix.get_x_scale() + stage_matrix.get_y_scale()) / 2.0f;
1867 }
1868
world_to_pixel(int & x,int & y,float world_x,float world_y) const1869 inline void world_to_pixel(int& x, int& y,
1870 float world_x, float world_y) const
1871 {
1872 // negative pixels seems ok here... we don't
1873 // clip to valid range, use world_to_pixel(SWFRect&)
1874 // and Intersect() against valid range instead.
1875 point p(world_x, world_y);
1876 stage_matrix.transform(p);
1877 x = (int)p.x;
1878 y = (int)p.y;
1879 }
1880
world_to_pixel(const SWFRect & wb) const1881 geometry::Range2d<int> world_to_pixel(const SWFRect& wb) const
1882 {
1883 using namespace gnash::geometry;
1884
1885 if ( wb.is_null() ) return Range2d<int>(nullRange);
1886 if ( wb.is_world() ) return Range2d<int>(worldRange);
1887
1888 int xmin, ymin, xmax, ymax;
1889
1890 world_to_pixel(xmin, ymin, wb.get_x_min(), wb.get_y_min());
1891 world_to_pixel(xmax, ymax, wb.get_x_max(), wb.get_y_max());
1892
1893 return Range2d<int>(xmin, ymin, xmax, ymax);
1894 }
1895
1896 point
pixel_to_world(int x,int y) const1897 pixel_to_world(int x, int y) const
1898 {
1899 point p(x, y);
1900 SWFMatrix mat = stage_matrix;
1901 mat.invert().transform(p);
1902 return p;
1903 };
1904
set_invalidated_region_world()1905 void set_invalidated_region_world() {
1906 InvalidatedRanges ranges;
1907 ranges.setWorld();
1908 set_invalidated_regions(ranges);
1909 }
1910
set_invalidated_regions(const InvalidatedRanges & ranges)1911 virtual void set_invalidated_regions(const InvalidatedRanges& ranges) {
1912 using gnash::geometry::Range2d;
1913
1914 int count=0;
1915
1916 _clipbounds_selected.clear();
1917 _clipbounds.clear();
1918
1919 // TODO: cache 'visiblerect' and maintain in sync with
1920 // xres/yres.
1921 Range2d<int> visiblerect;
1922 if ( xres && yres ) visiblerect = Range2d<int>(0, 0, xres-1, yres-1);
1923
1924 for (size_t rno=0; rno<ranges.size(); ++rno) {
1925
1926 const Range2d<int>& range = ranges.getRange(rno);
1927
1928 Range2d<int> pixbounds = Renderer::world_to_pixel(range);
1929
1930 geometry::Range2d<int> bounds = Intersection(pixbounds, visiblerect);
1931
1932 if (bounds.isNull()) continue; // out of screen
1933
1934 assert(bounds.isFinite());
1935
1936 _clipbounds.push_back(bounds);
1937
1938 ++count;
1939 }
1940 //log_debug(_("%d inv. bounds in frame"), count);
1941
1942 }
1943
1944
bounds_in_clipping_area(const geometry::Range2d<int> & bounds) const1945 virtual bool bounds_in_clipping_area(const geometry::Range2d<int>& bounds)
1946 const {
1947
1948 using gnash::geometry::Range2d;
1949
1950 Range2d<int> pixbounds = Renderer::world_to_pixel(bounds);
1951
1952 for (const auto& bounds : _clipbounds) {
1953 if (Intersect(pixbounds, bounds))
1954 return true;
1955 }
1956 return false;
1957 }
1958
getPixel(rgba & color_return,int x,int y) const1959 bool getPixel(rgba& color_return, int x, int y) const {
1960
1961 if ((x<0) || (y<0) || (x>=xres) || (y>=yres))
1962 return false;
1963
1964 agg::rgba8 color = m_pixf->pixel(x, y);
1965
1966 color_return.m_r = color.r;
1967 color_return.m_g = color.g;
1968 color_return.m_b = color.b;
1969 color_return.m_a = color.a;
1970
1971 return true;
1972 }
1973
set_scale(float new_xscale,float new_yscale)1974 void set_scale(float new_xscale, float new_yscale) {
1975
1976 scale_set=true;
1977 stage_matrix.set_identity();
1978 stage_matrix.set_scale(new_xscale/20.0f, new_yscale/20.0f);
1979 }
1980
set_translation(float xoff,float yoff)1981 void set_translation(float xoff, float yoff) {
1982 stage_matrix.set_translation(xoff, yoff);
1983 }
1984
getBytesPerPixel() const1985 virtual unsigned int getBytesPerPixel() const {
1986 return bpp/8;
1987 }
1988
1989 private: // private variables
1990
1991 typedef agg::renderer_base<PixelFormat> renderer_base;
1992
1993 // renderer base
1994 std::unique_ptr<renderer_base> m_rbase;
1995
1996 // An external renderer.
1997 std::unique_ptr<Renderer> _external;
1998
1999 int xres;
2000 int yres;
2001 int bpp; // bits per pixel
2002 gnash::SWFMatrix stage_matrix; // conversion from TWIPS to pixels
2003 bool scale_set;
2004
2005 agg::rendering_buffer m_rbuf;
2006
2007 std::unique_ptr<PixelFormat> m_pixf;
2008
2009 /// clipping rectangle
2010 ClipBounds _clipbounds;
2011 std::vector< geometry::Range2d<int> const* > _clipbounds_selected;
2012
2013 // this flag is set while a mask is drawn
2014 bool m_drawing_mask;
2015
2016 // Alpha mask stack
2017 AlphaMasks _alphaMasks;
2018
2019 /// Cached fill style list with just one entry used for font rendering
2020 std::vector<FillStyle> m_single_FillStyles;
2021
2022
2023 };
2024
2025
2026
2027 // detect the endianess of the host (would prefer to NOT have this function
2028 // here)
is_little_endian_host()2029 bool is_little_endian_host() {
2030
2031 union {
2032 std::uint16_t word;
2033 struct {
2034 std::uint8_t b1;
2035 std::uint8_t b2;
2036 } s;
2037 } u;
2038
2039 u.s.b1 = 1;
2040 u.s.b2 = 2;
2041
2042 return u.word == 0x0201;
2043
2044 }
2045
2046
create_Renderer_agg(const char * pixelformat)2047 DSOEXPORT Renderer_agg_base* create_Renderer_agg(const char *pixelformat)
2048 {
2049
2050 if (!pixelformat) return nullptr;
2051
2052 if (is_little_endian_host())
2053 log_debug("Framebuffer pixel format is %s (little-endian host)", pixelformat);
2054 else
2055 log_debug("Framebuffer pixel format is %s (big-endian host)", pixelformat);
2056
2057 #ifdef PIXELFORMAT_RGB555
2058 if (!strcmp(pixelformat, "RGB555"))
2059 return new Renderer_agg<agg::pixfmt_rgb555_pre> (16); // yep, 16!
2060
2061 else
2062 #endif
2063 #ifdef PIXELFORMAT_RGB565
2064 if (!strcmp(pixelformat, "RGB565") || !strcmp(pixelformat, "RGBA16"))
2065 return new Renderer_agg<agg::pixfmt_rgb565_pre> (16);
2066 else
2067 #endif
2068 #ifdef PIXELFORMAT_RGB24
2069 if (!strcmp(pixelformat, "RGB24"))
2070 return new Renderer_agg<agg::pixfmt_rgb24_pre> (24);
2071 else
2072 #endif
2073 #ifdef PIXELFORMAT_BGR24
2074 if (!strcmp(pixelformat, "BGR24"))
2075 return new Renderer_agg<agg::pixfmt_bgr24_pre> (24);
2076 else
2077 #endif
2078 #ifdef PIXELFORMAT_RGBA32
2079 if (!strcmp(pixelformat, "RGBA32"))
2080 return new Renderer_agg<agg::pixfmt_rgba32_pre> (32);
2081 else
2082 #endif
2083 #ifdef PIXELFORMAT_BGRA32
2084 if (!strcmp(pixelformat, "BGRA32"))
2085 return new Renderer_agg<agg::pixfmt_bgra32_pre> (32);
2086 #endif
2087 #ifdef PIXELFORMAT_RGBA32
2088 if (!strcmp(pixelformat, "ARGB32"))
2089 return new Renderer_agg<agg::pixfmt_argb32_pre> (32);
2090 else
2091 #endif
2092 #ifdef PIXELFORMAT_BGRA32
2093 if (!strcmp(pixelformat, "ABGR32"))
2094 return new Renderer_agg<agg::pixfmt_abgr32_pre> (32);
2095
2096 else
2097 #endif
2098 {
2099 log_error(_("Unknown pixelformat: %s\n"), pixelformat);
2100 return nullptr;
2101 //abort();
2102 }
2103
2104 return nullptr; // avoid compiler warning
2105 }
2106
2107
agg_detect_pixel_format(unsigned int rofs,unsigned int rsize,unsigned int gofs,unsigned int gsize,unsigned int bofs,unsigned int bsize,unsigned int bpp)2108 DSOEXPORT const char *agg_detect_pixel_format(unsigned int rofs,
2109 unsigned int rsize, unsigned int gofs, unsigned int gsize,
2110 unsigned int bofs, unsigned int bsize, unsigned int bpp)
2111 {
2112
2113 if (!is_little_endian_host() && (bpp>=24)) {
2114
2115 // Swap bits for big endian hosts, because the following tests assume
2116 // little endians. The pixel format string matches the bytes in memory.
2117
2118 // This applies for 24 bpp and 32 bpp modes only because AGG uses arrays
2119 // in the premultiply() implementation for these modes. 16 bpp modes
2120 // instead use bit shifting, which is transparent to host endianess.
2121 // See bug #22799.
2122
2123 rofs = bpp - rofs - rsize;
2124 gofs = bpp - gofs - gsize;
2125 bofs = bpp - bofs - bsize;
2126
2127 }
2128
2129 // 15 bits RGB (hicolor)
2130 if ((rofs==10) && (rsize==5)
2131 && (gofs==5) && (gsize==5)
2132 && (bofs==0) && (bsize==5) ) {
2133
2134 return "RGB555";
2135
2136 } else
2137 // 16 bits RGB (hicolor)
2138 if ((rofs==11) && (rsize==5)
2139 && (gofs==5) && (gsize==6)
2140 && (bofs==0) && (bsize==5) ) {
2141
2142 return "RGB565";
2143
2144 } else
2145
2146 // 24 bits RGB (truecolor)
2147 if ((rofs==16) && (rsize==8)
2148 && (gofs==8) && (gsize==8)
2149 && (bofs==0) && (bsize==8) ) {
2150
2151 if (bpp==24)
2152 return "BGR24";
2153 else
2154 return "BGRA32";
2155
2156 } else
2157 // 24 bits BGR (truecolor)
2158 if ((rofs==0) && (rsize==8)
2159 && (gofs==8) && (gsize==8)
2160 && (bofs==16) && (bsize==8)) {
2161
2162 if (bpp==24)
2163 return "RGB24";
2164 else
2165 return "RGBA32";
2166
2167 } else
2168 // special 32 bits (mostly on big endian hosts)
2169 if ((rofs==8) && (rsize==8)
2170 && (gofs==16) && (gsize==8)
2171 && (bofs==24) && (bsize==8)) {
2172
2173 return "ARGB32";
2174
2175 } else
2176 // special 32 bits (mostly on big endian hosts)
2177 if ((rofs==24) && (rsize==8)
2178 && (gofs==16) && (gsize==8)
2179 && (bofs==8) && (bsize==8)) {
2180
2181 return "ABGR32";
2182
2183 }
2184
2185 return nullptr; // unknown format
2186
2187 }
2188
2189 } // end of namespace gnash
2190
2191
2192 // Local Variables:
2193 // mode: C++
2194 // indent-tabs-mode: t
2195 // End:
2196 /* vim: set cindent tabstop=8 softtabstop=4 shiftwidth=4: */
2197