Add initial test for case sensitiveness with mixed version
[gnash.git] / librender / Renderer_cairo.cpp
blobf2c407ee5e40fa51bfb957d79c5946db9263ae9b
1 //
2 // Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010 Free Software
3 // 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
19 // Known bugs:
20 // - Rotation problem in gradient-tests.swf.
22 // TODOs:
23 // - Would be nice to have a header/implementation separation.
24 // - Document workings of Cairo and this renderer.
25 // - Test bitmap implementation correctness.
26 // - Figure out what extend types should be used and when.
27 // - Cleanups.
28 // - Optimizations.
30 // Already implemented:
31 // - outlines
32 // - fills: solid, linear, radial, focal and bitmap
33 // - bitmaps
34 // - fonts
35 // - masks
36 // - video (from old Cairo renderer)
38 #include "Renderer_cairo.h"
40 #include <cmath>
41 #include <cairo/cairo.h>
42 #include <boost/scoped_array.hpp>
43 #include <boost/scoped_ptr.hpp>
44 #include <boost/bind.hpp>
46 #include "smart_ptr.h"
47 #include "Renderer.h"
48 #include "GnashImage.h"
49 #include "PathParser.h"
50 #include "swf/ShapeRecord.h"
51 #include "utility.h"
52 #include "FillStyle.h"
53 #include "Transform.h"
54 #include "ImageIterators.h"
55 #include "CachedBitmap.h"
57 namespace gnash {
59 namespace {
60 void pattern_add_color_stops(const GradientFill& f,
61 cairo_pattern_t* pattern, const SWFCxForm& cx);
62 void init_cairo_matrix(cairo_matrix_t* cairo_matrix,
63 const SWFMatrix& gnash_matrix);
66 namespace {
68 // Converts from RGB image to 32-bit pixels in CAIRO_FORMAT_RGB24 format
69 static void
70 rgb_to_cairo_rgb24(boost::uint8_t* dst, const image::GnashImage* im)
72 boost::uint32_t* dst32 = reinterpret_cast<boost::uint32_t*>(dst);
73 for (size_t y = 0; y < im->height(); y++)
75 const boost::uint8_t* src = scanline(*im, y);
76 for (size_t x = 0; x < im->width(); x++, src += 3) {
77 *dst32++ = (src[0] << 16) | (src[1] << 8) | src[2];
82 // Converts from RGBA image to 32-bit pixels in CAIRO_FORMAT_ARGB32 format
83 static void
84 rgba_to_cairo_argb(boost::uint8_t* dst, const image::GnashImage* im)
86 boost::uint32_t* dst32 = reinterpret_cast<boost::uint32_t*>(dst);
87 for (size_t y = 0; y < im->height(); y++)
89 const boost::uint8_t* src = scanline(*im, y);
90 for (size_t x = 0; x < im->width(); x++, src += 4)
92 const boost::uint8_t& r = src[0],
93 g = src[1],
94 b = src[2],
95 a = src[3];
97 if (a) {
98 *dst32++ = (a << 24) | (r << 16) | (g << 8) | b;
99 } else {
100 *dst32++ = 0;
106 class bitmap_info_cairo : public CachedBitmap, boost::noncopyable
108 public:
109 bitmap_info_cairo(boost::uint8_t* data, int width, int height,
110 size_t bpp, cairo_format_t format)
112 _data(data),
113 _width(width),
114 _height(height),
115 _bytes_per_pixel(bpp),
116 _format(format),
117 _surface(cairo_image_surface_create_for_data(_data.get(),
118 format, width, height, width * bpp)),
119 _pattern(cairo_pattern_create_for_surface (_surface))
122 assert(cairo_surface_status(_surface) == CAIRO_STATUS_SUCCESS);
123 assert(cairo_pattern_status(_pattern) == CAIRO_STATUS_SUCCESS);
126 virtual void dispose() {
127 _image.reset();
128 _data.reset();
131 virtual bool disposed() const {
132 return !_data.get();
136 image::GnashImage& image() {
137 if (_image.get()) return *_image;
139 switch (_format) {
140 case CAIRO_FORMAT_RGB24:
141 _image.reset(new image::ImageRGB(_width, _height));
142 break;
144 case CAIRO_FORMAT_ARGB32:
145 _image.reset(new image::ImageRGBA(_width, _height));
146 break;
148 default:
149 std::abort();
152 // We assume that cairo uses machine-endian order, as that's what
153 // the existing conversion functions do.
154 boost::uint32_t* start =
155 reinterpret_cast<boost::uint32_t*>(_data.get());
156 const size_t sz = _width * _height;
157 std::copy(start, start + sz, image::begin<image::ARGB>(*_image));
158 return *_image;
161 void update() const {
162 if (!_image.get()) return;
163 switch (_format) {
164 case CAIRO_FORMAT_RGB24:
165 rgb_to_cairo_rgb24(_data.get(), _image.get());
166 break;
167 case CAIRO_FORMAT_ARGB32:
168 rgba_to_cairo_argb(_data.get(), _image.get());
169 break;
170 default:
171 break;
173 _image.reset();
176 ~bitmap_info_cairo()
178 cairo_surface_destroy(_surface);
179 cairo_pattern_destroy(_pattern);
182 cairo_pattern_t* apply(const cairo_matrix_t* mat, int /*fill_type*/) const
184 assert(mat);
185 assert(_pattern);
186 update();
187 cairo_pattern_set_matrix(_pattern, mat);
189 cairo_extend_t extend = CAIRO_EXTEND_REPEAT;
191 #if 0
192 // The extend type should probably depend on certain factors, but which?
193 switch(fill_type) {
194 case SWF::FILL_CLIPPED_BITMAP:
195 break;
196 case SWF::FILL_CLIPPED_BITMAP_HARD:
197 case SWF::FILL_TILED_BITMAP_HARD:
198 break;
199 case SWF::FILL_TILED_BITMAP:
200 break;
202 #endif
204 cairo_pattern_set_extend(_pattern, extend);
206 return _pattern;
209 private:
210 mutable boost::scoped_ptr<image::GnashImage> _image;
211 boost::scoped_array<boost::uint8_t> _data;
212 int _width;
213 int _height;
214 size_t _bytes_per_pixel;
215 cairo_format_t _format;
216 cairo_surface_t* _surface;
217 cairo_pattern_t* _pattern;
221 /// Style handler
223 /// Transfer FillStyles to agg styles.
224 struct StyleHandler : boost::static_visitor<cairo_pattern_t*>
226 StyleHandler(const SWFCxForm& c)
228 _cx(c)
231 cairo_pattern_t* operator()(const GradientFill& f) const {
232 const SWFMatrix m = f.matrix();
233 switch (f.type()) {
234 case GradientFill::LINEAR:
236 cairo_matrix_t mat;
237 init_cairo_matrix(&mat, m);
239 cairo_pattern_t* pattern =
240 cairo_pattern_create_linear(0, 0, 256.0, 0);
241 cairo_pattern_set_matrix (pattern, &mat);
243 pattern_add_color_stops(f, pattern, _cx);
244 return pattern;
246 case GradientFill::RADIAL:
249 // Undo the translation our parser applied.
250 gnash::SWFMatrix transl;
251 transl.concatenate(m);
253 cairo_matrix_t mat;
254 init_cairo_matrix(&mat, transl);
256 /// This is 0 for radial gradients.
257 const double focal_pos = 32.0f * f.focalPoint();
259 cairo_pattern_t* pattern =
260 cairo_pattern_create_radial(focal_pos, 0.0, 0.0,
261 0.0, 0.0, 32.0f);
263 cairo_pattern_set_matrix (pattern, &mat);
265 pattern_add_color_stops(f, pattern, _cx);
266 return pattern;
269 // We should never get here.
270 return 0;
273 cairo_pattern_t* operator()(const SolidFill& f) const {
274 rgba c = _cx.transform(f.color());
275 cairo_pattern_t* pattern =
276 cairo_pattern_create_rgba(c.m_r / 255.0, c.m_g / 255.0,
277 c.m_b / 255.0, c.m_a / 255.0);
278 return pattern;
281 cairo_pattern_t* operator()(const BitmapFill& f) const {
282 SWFMatrix m = f.matrix();
284 const bitmap_info_cairo* binfo =
285 dynamic_cast<const bitmap_info_cairo*>(f.bitmap());
287 if (!binfo) return 0;
289 cairo_matrix_t mat;
290 init_cairo_matrix(&mat, m);
292 // TODO: the second argument should be fill_type but is unused.
293 cairo_pattern_t* pattern = binfo->apply(&mat, 0);
294 return pattern;
297 private:
298 const SWFCxForm& _cx;
304 static void
305 snap_to_half_pixel(cairo_t* cr, double& x, double& y)
307 cairo_user_to_device(cr, &x, &y);
309 x = std::floor(x + 0.5) + 0.5;
310 y = std::floor(y + 0.5) + 0.5;
312 cairo_device_to_user(cr, &x, &y);
315 static cairo_pattern_t*
316 get_cairo_pattern(const FillStyle& style, const SWFCxForm& cx)
318 StyleHandler st(cx);
319 cairo_pattern_t* pattern = boost::apply_visitor(st, style.fill);
321 return pattern;
325 class CairoPathRunner : public PathParser
327 public:
328 CairoPathRunner(Renderer_cairo& renderer,
329 const std::vector<Path>& paths,
330 const std::vector<FillStyle>& FillStyles, cairo_t* context)
331 : PathParser(paths, FillStyles.size()),
332 _renderer(renderer),
333 _cr(context),
334 _pattern(0),
335 _FillStyles(FillStyles)
339 virtual void prepareFill(int fill_index, const SWFCxForm& cx)
341 if (!_pattern) {
342 _pattern = get_cairo_pattern(_FillStyles[fill_index-1], cx);
345 virtual void terminateFill(int FillStyle)
347 UNUSED(FillStyle);
349 if (!_pattern) {
350 cairo_new_path(_cr);
351 return;
354 cairo_set_source(_cr, _pattern);
356 cairo_fill(_cr);
358 // Surfaces are owned by const bitmap_info_cairo
359 if (cairo_pattern_get_type(_pattern) != CAIRO_PATTERN_TYPE_SURFACE) {
360 cairo_pattern_destroy(_pattern);
361 _pattern = 0;
365 void moveTo(const point& ap)
367 double x = ap.x, y = ap.y;
368 snap_to_half_pixel(_cr, x, y);
370 cairo_move_to(_cr, x, y);
373 virtual void curveTo(const Edge& cur_edge)
375 const float two_thirds = 2.0/3.0;
376 const float one_third = 1 - two_thirds;
378 double x, y;
379 cairo_get_current_point(_cr, &x, &y);
381 double x1 = x + two_thirds * (cur_edge.cp.x - x);
382 double y1 = y + two_thirds * (cur_edge.cp.y - y);
384 double x2 = cur_edge.cp.x + one_third * (cur_edge.ap.x - cur_edge.cp.x);
385 double y2 = cur_edge.cp.y + one_third * (cur_edge.ap.y - cur_edge.cp.y);
387 x = cur_edge.ap.x;
388 y = cur_edge.ap.y;
390 snap_to_half_pixel(_cr, x1, y1);
391 snap_to_half_pixel(_cr, x2, y2);
392 snap_to_half_pixel(_cr, x, y);
394 cairo_curve_to(_cr, x1, y1, x2, y2, x, y);
398 void lineTo(const point& ap)
400 double x = ap.x;
401 double y = ap.y;
402 snap_to_half_pixel(_cr, x, y);
404 cairo_line_to(_cr, x, y);
407 private:
408 Renderer_cairo& _renderer;
409 cairo_t* _cr;
410 cairo_pattern_t* _pattern;
411 const std::vector<FillStyle>& _FillStyles;
417 /// Transforms the current Cairo SWFMatrix using the given SWFMatrix.
418 /// When it goes out of scope, the SWFMatrix will be reset to what it
419 /// was before the new SWFMatrix was applied.
420 class CairoScopeMatrix : public boost::noncopyable
422 public:
423 CairoScopeMatrix(cairo_t* cr, const SWFMatrix& new_mat)
424 : _cr(cr)
426 cairo_get_matrix(_cr, &old_mat);
428 cairo_matrix_t tmp;
429 init_cairo_matrix(&tmp, new_mat);
430 cairo_transform(_cr, &tmp);
433 ~CairoScopeMatrix()
435 cairo_set_matrix(_cr, &old_mat);
438 private:
439 cairo_t* _cr;
440 cairo_matrix_t old_mat;
444 /// Implementation of Renderer_cairo class
445 Renderer_cairo::Renderer_cairo()
446 : _video_bufsize(0),
447 _drawing_mask(false)
449 _cr = cairo_create(NULL);
450 cairo_matrix_init_scale(&_stage_mat, 1/20.0f, 1/20.0f);
453 Renderer_cairo::~Renderer_cairo()
455 cairo_destroy(_cr);
458 CachedBitmap*
459 Renderer_cairo::createCachedBitmap(std::auto_ptr<image::GnashImage> im)
461 int buf_size = im->width() * im->height() * 4;
462 boost::uint8_t* buffer = new boost::uint8_t[buf_size];
464 switch (im->type())
466 case image::TYPE_RGB:
468 rgb_to_cairo_rgb24(buffer, im.get());
470 return new bitmap_info_cairo(buffer, im->width(), im->height(), 4,
471 CAIRO_FORMAT_RGB24);
474 case image::TYPE_RGBA:
476 rgba_to_cairo_argb(buffer, im.get());
478 return new bitmap_info_cairo(buffer, im->width(), im->height(), 4,
479 CAIRO_FORMAT_ARGB32);
482 default:
483 std::abort();
487 void
488 Renderer_cairo::drawVideoFrame(image::GnashImage* baseframe, const Transform& xform,
489 const SWFRect* bounds, bool /*smooth*/)
492 if (baseframe->type() == image::TYPE_RGBA)
494 LOG_ONCE(log_error(_("Can't render videos with alpha")));
495 return;
498 image::ImageRGB* frame = dynamic_cast<image::ImageRGB*>(baseframe);
500 assert(frame);
502 // Extract frame attributes
503 int w = frame->width();
504 int h = frame->height();
506 // Compute bounding rectangle size relative to video object
507 double w_scale = bounds->width() / w;
508 double h_scale = bounds->height() / h;
510 // Fit video to bounding rectangle
511 cairo_matrix_t mat;
512 cairo_matrix_init_scale(&mat, w_scale, h_scale);
513 cairo_matrix_translate(&mat, bounds->get_x_min(), bounds->get_y_min());
515 // Now apply transformation to video
516 cairo_matrix_t frame_mat;
517 init_cairo_matrix(&frame_mat, xform.matrix);
519 cairo_matrix_multiply(&mat, &mat, &frame_mat);
521 // Inverse the SWFMatrix for pattern space
522 cairo_matrix_invert(&mat);
524 // Convert RGB frame to cairo format
525 size_t buf_size = w * h * 4;
527 if (_video_bufsize < buf_size) {
528 _video_buffer.reset(new boost::uint8_t[buf_size]);
529 _video_bufsize = buf_size;
532 rgb_to_cairo_rgb24(_video_buffer.get(), frame);
534 // Create a pattern from the the RGB frame
535 cairo_surface_t* surface = cairo_image_surface_create_for_data(
536 _video_buffer.get(), CAIRO_FORMAT_RGB24, w, h, w * 4);
537 cairo_pattern_t* pattern = cairo_pattern_create_for_surface(surface);
538 cairo_pattern_set_extend(pattern, CAIRO_EXTEND_NONE);
539 cairo_pattern_set_matrix(pattern, &mat);
541 // Draw the frame now
542 cairo_save(_cr);
543 cairo_set_source(_cr, pattern);
545 geometry::Range2d<boost::int32_t> range = bounds->getRange();
546 xform.matrix.transform(range);
548 cairo_rectangle(_cr, range.getMinX(), range.getMinY(), range.width(),
549 range.height());
550 cairo_clip(_cr);
551 cairo_paint(_cr);
552 cairo_restore(_cr);
554 // Clean up
555 cairo_pattern_destroy(pattern);
556 cairo_surface_destroy(surface);
560 geometry::Range2d<int>
561 Renderer_cairo::world_to_pixel(const SWFRect& worldbounds)
563 double xmin = worldbounds.get_x_min(),
564 ymin = worldbounds.get_y_min(),
565 xmax = worldbounds.get_x_max(),
566 ymax = worldbounds.get_y_max();
568 cairo_matrix_transform_point(&_stage_mat, &xmin, &ymin);
569 cairo_matrix_transform_point(&_stage_mat, &xmax, &ymax);
571 geometry::Range2d<int> ret(xmin, ymin, xmax, ymax);
572 return ret;
575 // FIXME
576 point
577 Renderer_cairo::pixel_to_world(int x, int y)
579 cairo_matrix_t inv_stage = _stage_mat;
580 cairo_matrix_invert(&inv_stage);
582 double xconv = x;
583 double yconv = y;
585 cairo_matrix_transform_point(&inv_stage, &xconv, &yconv);
586 return point(xconv, yconv);
589 void
590 Renderer_cairo::set_color(const rgba& c)
592 cairo_set_source_rgba (_cr, c.m_r / 255.0, c.m_g / 255.0,
593 c.m_b / 255.0, c.m_a / 255.0);
596 void
597 Renderer_cairo::set_invalidated_regions(const InvalidatedRanges& ranges)
599 _invalidated_ranges = ranges;
602 void
603 Renderer_cairo::begin_display(const rgba& bg_color,
604 int /*viewport_width*/, int /*viewport_height*/,
605 float /*x0*/, float /*x1*/, float /*y0*/, float /*y1*/)
607 cairo_identity_matrix(_cr);
609 cairo_save(_cr);
611 if (bg_color.m_a) {
612 set_color(bg_color);
616 for (size_t rno=0; rno < _invalidated_ranges.size(); rno++) {
617 const geometry::Range2d<boost::int32_t>& range =
618 _invalidated_ranges.getRange(rno);
619 if (range.isNull()) {
620 continue;
622 if (range.isWorld()) {
623 cairo_paint(_cr);
624 // reset any rectangles that might have been added to the path...
625 cairo_new_path(_cr);
626 cairo_set_matrix(_cr, &_stage_mat);
627 return;
630 double x = range.getMinX(),
631 y = range.getMinY(),
632 maxx = range.getMaxX(),
633 maxy = range.getMaxY();
635 // Transform to pixels.
636 cairo_matrix_transform_point(&_stage_mat, &x, &y);
637 cairo_matrix_transform_point(&_stage_mat, &maxx, &maxy);
639 cairo_rectangle(_cr, rint(x), rint(y), rint(maxx - x), rint(maxy - y));
642 cairo_clip(_cr);
644 // Paint the background color over the clipped region(s).
645 cairo_paint(_cr);
647 cairo_set_matrix(_cr, &_stage_mat);
650 void
651 Renderer_cairo::end_display()
653 cairo_restore(_cr);
656 void
657 Renderer_cairo::set_scale(float xscale, float yscale)
659 _stage_mat.xx = xscale / 20;
660 _stage_mat.yy = yscale / 20;
663 void
664 Renderer_cairo::set_translation(float xoff, float yoff)
666 _stage_mat.x0 = xoff;
667 _stage_mat.y0 = yoff;
670 void
671 Renderer_cairo::drawLine(const std::vector<point>& coords,
672 const rgba& color, const SWFMatrix& mat)
674 if (coords.empty()) return;
676 CairoScopeMatrix mat_transformer(_cr, mat);
678 std::vector<point>::const_iterator i = coords.begin();
680 double x = i->x, y = i->y;
681 snap_to_half_pixel(_cr, x, y);
683 cairo_move_to(_cr, x, y);
685 for (std::vector<point>::const_iterator e = coords.end(); i != e; ++i) {
686 double x = i->x, y = i->y;
687 snap_to_half_pixel(_cr, x, y);
688 cairo_line_to(_cr, x, y);
691 set_color(color);
692 cairo_set_line_cap(_cr, CAIRO_LINE_CAP_ROUND);
693 cairo_set_line_join(_cr, CAIRO_LINE_JOIN_ROUND);
695 double hwidth = 1.0;
697 cairo_device_to_user_distance(_cr, &hwidth, &hwidth);
698 cairo_set_line_width(_cr, hwidth);
700 cairo_stroke(_cr);
703 void
704 Renderer_cairo::draw_poly(const point* corners, size_t corner_count,
705 const rgba& fill, const rgba& outline,
706 const SWFMatrix& mat, bool /*masked*/)
708 CairoScopeMatrix mat_transformer(_cr, mat);
709 cairo_transform(_cr, &_stage_mat);
711 if (corner_count < 1) {
712 return;
715 cairo_move_to(_cr, corners[0].x, corners[0].y);
717 for (size_t i = 0; i < corner_count; ++i) {
718 cairo_line_to(_cr, corners[i].x, corners[i].y);
721 cairo_close_path(_cr);
723 if (fill.m_a) {
724 set_color(fill);
725 cairo_fill_preserve(_cr);
728 if (outline.m_a) {
729 set_color(outline);
731 // FIXME: coordinate alignment (for single-pixel lines should be in
732 // between two pixels for sharp hair line.
734 cairo_set_line_width(_cr, 1.0);
735 cairo_stroke_preserve(_cr);
738 // Clear the current path which was _preserve()d.
739 cairo_new_path(_cr);
742 void
743 Renderer_cairo::set_antialiased(bool /*enable*/)
745 log_unimpl("set_antialiased");
748 void
749 Renderer_cairo::begin_submit_mask()
751 PathVec mask;
752 _masks.push_back(mask);
754 _drawing_mask = true;
757 void
758 Renderer_cairo::end_submit_mask()
760 _drawing_mask = false;
762 // Load the mask paths into the cairo context.
763 add_paths(_masks.back());
765 // Save the context so we can return to the former clip later.
766 cairo_save(_cr);
768 // Clip the fills defined by the current paths.
769 cairo_clip(_cr);
771 // Remove the current path since we have no further use for it (and may
772 // confuse us later).
773 cairo_new_path(_cr);
776 void
777 Renderer_cairo::disable_mask()
779 // Restore the previous clip.
780 cairo_restore(_cr);
782 _masks.pop_back();
785 void
786 Renderer_cairo::add_path(cairo_t* cr, const Path& cur_path)
788 double x = cur_path.ap.x;
789 double y = cur_path.ap.y;
791 snap_to_half_pixel(cr, x, y);
792 cairo_move_to(cr, x, y);
794 for (std::vector<Edge>::const_iterator it = cur_path.m_edges.begin(),
795 end = cur_path.m_edges.end(); it != end; ++it) {
796 const Edge& cur_edge = *it;
798 if (cur_edge.straight()) {
799 x = cur_edge.ap.x;
800 y = cur_edge.ap.y;
801 snap_to_half_pixel(cr, x, y);
802 cairo_line_to(cr, x, y);
803 } else {
804 // Cairo expects a cubic Bezier curve, while Flash gives us a
805 // quadratic one. We must apply a conversion:
807 const float two_thirds = 2.0/3.0;
808 const float one_third = 1 - two_thirds;
810 double x1 = x + two_thirds * (cur_edge.cp.x - x);
811 double y1 = y + two_thirds * (cur_edge.cp.y - y);
813 double x2 = cur_edge.cp.x
814 + one_third * (cur_edge.ap.x - cur_edge.cp.x);
815 double y2 = cur_edge.cp.y
816 + one_third * (cur_edge.ap.y - cur_edge.cp.y);
818 x = cur_edge.ap.x;
819 y = cur_edge.ap.y;
821 snap_to_half_pixel(cr, x1, y1);
822 snap_to_half_pixel(cr, x2, y2);
823 snap_to_half_pixel(cr, x, y);
825 cairo_curve_to(cr, x1, y1, x2, y2, x, y);
830 void
831 Renderer_cairo::apply_line_style(const LineStyle& style, const SWFCxForm& cx,
832 const SWFMatrix& /*mat*/)
834 cairo_line_join_t join_style = CAIRO_LINE_JOIN_MITER;
835 switch (style.joinStyle()) {
836 case JOIN_ROUND:
837 join_style = CAIRO_LINE_JOIN_ROUND;
838 break;
839 case JOIN_BEVEL:
840 join_style = CAIRO_LINE_JOIN_BEVEL;
841 break;
842 case JOIN_MITER:
843 break;
844 default:
845 log_unimpl("join style");
847 cairo_set_line_join(_cr, join_style);
849 if (style.startCapStyle() != style.endCapStyle()) {
850 log_unimpl("differing start and end cap styles");
853 cairo_line_cap_t cap_style = CAIRO_LINE_CAP_ROUND;
854 switch(style.startCapStyle()) {
855 case CAP_ROUND:
856 break;
857 case CAP_NONE:
858 cap_style = CAIRO_LINE_CAP_BUTT;
859 break;
860 case CAP_SQUARE:
861 cap_style = CAIRO_LINE_CAP_SQUARE;
862 break;
863 default:
864 log_unimpl("cap style");
867 cairo_set_line_cap(_cr, cap_style);
869 // TODO: test that this is correct.
870 cairo_set_miter_limit(_cr, style.miterLimitFactor());
872 float width = style.getThickness();
874 if ( width == 0.0 ) {
876 cairo_matrix_t inv_stage = _stage_mat;
877 cairo_matrix_invert(&inv_stage);
879 double xconv = 1.0;
880 double yconv = 1.0;
882 cairo_matrix_transform_distance(&inv_stage, &xconv, &yconv);
884 cairo_set_line_width(_cr, xconv);
885 } else {
886 // TODO: this is correct for !style.scaleThicknessVertically()
887 // and !style.scaleThicknessHorizontally().
888 // If that's not the case, we should scale the thickness
889 // togheter with the shapes.
890 if (style.scaleThicknessVertically() ||
891 style.scaleThicknessHorizontally()) {
892 LOG_ONCE( log_unimpl(_("Scaled strokes in Cairo renderer")) );
895 cairo_set_line_width(_cr, width);
898 rgba color = cx.transform(style.get_color());
899 set_color(color);
902 void
903 Renderer_cairo::draw_outlines(const PathVec& path_vec,
904 const std::vector<LineStyle>& line_styles,
905 const SWFCxForm& cx,
906 const SWFMatrix& mat)
908 for (PathVec::const_iterator it = path_vec.begin(), end = path_vec.end();
909 it != end; ++it) {
910 const Path& cur_path = *it;
912 if (!cur_path.m_line) {
913 continue;
916 apply_line_style(line_styles[cur_path.m_line-1], cx, mat);
917 add_path(_cr, cur_path);
918 cairo_stroke(_cr);
922 void
923 Renderer_cairo::draw_subshape(const PathVec& path_vec, const SWFMatrix& mat,
924 const SWFCxForm& cx,
925 const std::vector<FillStyle>& FillStyles,
926 const std::vector<LineStyle>& line_styles)
928 CairoPathRunner runner(*this, path_vec, FillStyles, _cr);
929 runner.run(cx, mat);
931 draw_outlines(path_vec, line_styles, cx, mat);
935 std::vector<PathVec::const_iterator>
936 Renderer_cairo::find_subshapes(const PathVec& path_vec)
938 std::vector<PathVec::const_iterator> subshapes;
940 PathVec::const_iterator it = path_vec.begin();
941 PathVec::const_iterator end = path_vec.end();
943 subshapes.push_back(it);
944 ++it;
946 for (;it != end; ++it) {
947 const Path& cur_path = *it;
949 if (cur_path.m_new_shape) {
950 subshapes.push_back(it);
954 subshapes.push_back(end);
956 return subshapes;
959 void
960 Renderer_cairo::draw_mask(const PathVec& path_vec)
962 for (PathVec::const_iterator it = path_vec.begin(), end = path_vec.end();
963 it != end; ++it) {
964 const Path& cur_path = *it;
966 if (cur_path.m_fill0 || cur_path.m_fill1) {
967 _masks.back().push_back(cur_path);
972 void
973 Renderer_cairo::add_paths(const PathVec& path_vec)
975 for (PathVec::const_iterator it = path_vec.begin(), end = path_vec.end();
976 it != end; ++it) {
977 const Path& cur_path = *it;
979 add_path(_cr, cur_path);
983 /// Takes a path and translates it using the given SWFMatrix.
984 void
985 Renderer_cairo::apply_matrix_to_paths(std::vector<Path>& paths,
986 const SWFMatrix& mat)
988 std::for_each(paths.begin(), paths.end(),
989 boost::bind(&Path::transform, _1, boost::ref(mat)));
992 void
993 Renderer_cairo::drawShape(const SWF::ShapeRecord& shape, const Transform& xform)
995 const PathVec& path_vec = shape.paths();
997 if (!path_vec.size()) {
998 return;
1001 cairo_set_fill_rule(_cr, CAIRO_FILL_RULE_EVEN_ODD); // TODO: Move to init
1003 if (_drawing_mask) {
1004 PathVec scaled_path_vec = path_vec;
1006 apply_matrix_to_paths(scaled_path_vec, xform.matrix);
1007 draw_mask(scaled_path_vec);
1008 return;
1011 CairoScopeMatrix mat_transformer(_cr, xform.matrix);
1013 std::vector<PathVec::const_iterator> subshapes = find_subshapes(path_vec);
1015 const std::vector<FillStyle>& FillStyles = shape.fillStyles();
1016 const std::vector<LineStyle>& line_styles = shape.lineStyles();
1018 for (size_t i = 0; i < subshapes.size()-1; ++i) {
1019 PathVec subshape_paths;
1021 if (subshapes[i] != subshapes[i+1]) {
1022 subshape_paths = PathVec(subshapes[i], subshapes[i+1]);
1023 } else {
1024 subshape_paths.push_back(*subshapes[i]);
1027 draw_subshape(subshape_paths, xform.matrix, xform.colorTransform,
1028 FillStyles, line_styles);
1032 void
1033 Renderer_cairo::drawGlyph(const SWF::ShapeRecord& rec, const rgba& color,
1034 const SWFMatrix& mat)
1036 SWFCxForm dummy_cx;
1037 std::vector<FillStyle> glyph_fs;
1039 FillStyle coloring = FillStyle(SolidFill(color));
1041 glyph_fs.push_back(coloring);
1043 const PathVec& path_vec = rec.paths();
1045 std::vector<LineStyle> dummy_ls;
1047 CairoScopeMatrix mat_transformer(_cr, mat);
1049 draw_subshape(path_vec, mat, dummy_cx, glyph_fs, dummy_ls);
1052 void
1053 Renderer_cairo::set_context(cairo_t* context)
1055 if (context == _cr) {
1056 return;
1059 cairo_destroy(_cr);
1060 _cr = context;
1063 bool
1064 Renderer_cairo::initTestBuffer(unsigned width, unsigned height)
1066 cairo_surface_t* test_surface =
1067 cairo_image_surface_create (CAIRO_FORMAT_ARGB32, width, height);
1069 if (cairo_surface_status(test_surface) != CAIRO_STATUS_SUCCESS) {
1070 return false;
1073 cairo_t* context = cairo_create(test_surface);
1074 if (cairo_status(context) == CAIRO_STATUS_NO_MEMORY) {
1075 return false;
1078 cairo_surface_destroy(test_surface);
1079 set_context(context);
1081 return true;
1084 unsigned int
1085 Renderer_cairo::getBitsPerPixel() const
1087 cairo_surface_t* surface = cairo_get_target (_cr);
1088 cairo_format_t format = cairo_image_surface_get_format (surface);
1090 switch(format) {
1091 case CAIRO_FORMAT_ARGB32:
1092 return 32;
1093 case CAIRO_FORMAT_RGB24:
1094 // In practice this is 32 with 8 bits unused...
1095 return 24;
1096 case CAIRO_FORMAT_A8:
1097 return 8;
1098 case CAIRO_FORMAT_A1:
1099 return 1;
1100 default:
1101 return 0;
1105 bool
1106 Renderer_cairo::getPixel(rgba& color_return, int x, int y) const
1108 if (x < 0 || y < 0) {
1109 return false;
1112 cairo_surface_t* surface = cairo_get_target (_cr);
1114 assert(cairo_image_surface_get_format (surface) == CAIRO_FORMAT_ARGB32);
1116 unsigned char* data = cairo_image_surface_get_data (surface);
1117 int width = cairo_image_surface_get_width(surface);
1118 int height = cairo_image_surface_get_height(surface);
1119 int stride = cairo_image_surface_get_stride(surface); // in bytes
1121 if (x >= width || y >= height) {
1122 return false;
1125 unsigned char* ptr = data + y * stride + x * 4;
1127 color_return.m_a = ptr[3];
1128 color_return.m_r = ptr[2];
1129 color_return.m_g = ptr[1];
1130 color_return.m_b = ptr[0];
1132 return true;
1135 namespace {
1137 void
1138 pattern_add_color_stops(const GradientFill& f, cairo_pattern_t* pattern,
1139 const SWFCxForm& cx)
1141 for (size_t index = 0; index < f.recordCount(); ++index) {
1142 const GradientRecord& grad = f.record(index);
1144 rgba c = cx.transform(grad.color);
1146 cairo_pattern_add_color_stop_rgba (pattern,
1147 grad.ratio / 255.0, c.m_r / 255.0, c.m_g / 255.0,
1148 c.m_b / 255.0, c.m_a / 255.0);
1152 void
1153 init_cairo_matrix(cairo_matrix_t* cairo_matrix, const SWFMatrix& gnash_matrix)
1155 cairo_matrix_init(cairo_matrix,
1156 gnash_matrix.sx/65536.0, gnash_matrix.shx/65536.0,
1157 gnash_matrix.shy/65536.0, gnash_matrix.sy/65536.0,
1158 gnash_matrix.tx, gnash_matrix.ty);
1165 namespace renderer {
1166 namespace cairo {
1168 DSOEXPORT Renderer*
1169 create_handler()
1170 // Factory.
1172 return new Renderer_cairo();
1175 DSOEXPORT void
1176 set_context(Renderer* handler, cairo_t* context)
1178 Renderer_cairo* cairo_handler = static_cast<Renderer_cairo*>(handler);
1179 cairo_handler->set_context(context);
1183 } // namespace cairo
1184 } // namespace renderer
1185 } // namespace gnash