2009-10-09 Chris Toshok <toshok@ximian.com>
[moon.git] / src / brush.cpp
blobc91a4f07b5654ce6af765d78cc46e0abccdcbac8
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
2 /*
3 * brush.cpp: Brushes
5 * Contact:
6 * Moonlight List (moonlight-list@lists.ximian.com)
8 * Copyright 2007, 2008 Novell, Inc. (http://www.novell.com)
10 * See the LICENSE file included with the distribution for details.
14 #include <config.h>
16 #include <cairo.h>
17 #include <glib.h>
19 #include "brush.h"
20 #include "media.h"
21 #include "mediaelement.h"
22 #include "color.h"
23 #include "transform.h"
24 #include "mediaplayer.h"
25 #include "bitmapimage.h"
28 // SL-Cairo convertion and helper routines
31 static cairo_extend_t
32 convert_gradient_spread_method (GradientSpreadMethod method)
34 switch (method) {
35 case GradientSpreadMethodPad:
36 return CAIRO_EXTEND_PAD;
37 case GradientSpreadMethodReflect:
38 return CAIRO_EXTEND_REFLECT;
39 // unknown (e.g. bad) values are considered to be Repeat by Silverlight
40 // even if the default, i.e. *no* value) is Pad
41 case GradientSpreadMethodRepeat:
42 default:
43 return CAIRO_EXTEND_REPEAT;
47 static void
48 brush_matrix_invert (cairo_matrix_t *matrix)
50 cairo_status_t status = cairo_matrix_invert (matrix);
51 if (status != CAIRO_STATUS_SUCCESS) {
52 printf ("Moonlight: Error inverting matrix falling back\n");
53 cairo_matrix_init_identity (matrix);
58 // Brush
62 Brush::Brush()
64 SetObjectType (Type::BRUSH);
67 void
68 Brush::SetupBrush (cairo_t *cr, const Rect &area)
70 g_warning ("Brush:SetupBrush has been called. The derived class should have overridden it.");
73 void
74 Brush::Fill (cairo_t *cr, bool preserve)
76 if (preserve)
77 cairo_fill_preserve (cr);
78 else
79 cairo_fill (cr);
82 void
83 Brush::Stroke (cairo_t *cr, bool preserve)
85 if (preserve)
86 cairo_stroke_preserve (cr);
87 else
88 cairo_stroke (cr);
91 void
92 Brush::OnSubPropertyChanged (DependencyProperty *prop, DependencyObject *obj, PropertyChangedEventArgs *subobj_args)
94 // if our transforms change in some fashion, we need to redraw
95 // the element.
96 NotifyListenersOfPropertyChange (Brush::ChangedProperty, NULL);
98 DependencyObject::OnSubPropertyChanged (prop, obj, subobj_args);
101 bool
102 Brush::IsOpaque ()
104 return !IS_TRANSLUCENT (GetOpacity ());
107 bool
108 Brush::IsAnimating ()
110 return FALSE;
113 static void
114 transform_get_absolute_transform (Transform *relative_transform, double width, double height, cairo_matrix_t *result)
116 cairo_matrix_t tm;
118 cairo_matrix_init_scale (result, width, height);
119 relative_transform->GetTransform (&tm);
120 cairo_matrix_multiply (result, &tm, result);
121 cairo_matrix_scale (result, 1.0/width, 1.0/height);
127 // SolidColorBrush
130 SolidColorBrush::SolidColorBrush ()
132 SetObjectType (Type::SOLIDCOLORBRUSH);
135 SolidColorBrush::SolidColorBrush (const char *color)
137 SetObjectType (Type::SOLIDCOLORBRUSH);
138 Color *c = color_from_str (color);
139 SetColor (c);
140 delete c;
143 void
144 SolidColorBrush::SetupBrush (cairo_t *cr, const Rect &area)
146 double opacity = GetOpacity ();
147 Color *color = GetColor ();
149 cairo_set_source_rgba (cr, color->r, color->g, color->b, opacity * color->a);
152 bool
153 SolidColorBrush::IsOpaque ()
155 return Brush::IsOpaque () && !IS_TRANSLUCENT (GetColor ()->a);
160 // GradientBrush
163 GradientBrush::GradientBrush ()
165 SetObjectType (Type::GRADIENTBRUSH);
168 GradientBrush::~GradientBrush ()
172 void
173 GradientBrush::OnCollectionChanged (Collection *col, CollectionChangedEventArgs *args)
175 if (col != GetValue (GradientBrush::GradientStopsProperty)->AsCollection ()) {
176 Brush::OnCollectionChanged (col, args);
177 return;
180 NotifyListenersOfPropertyChange (GradientBrush::GradientStopsProperty, NULL);
183 void
184 GradientBrush::OnCollectionItemChanged (Collection *col, DependencyObject *obj, PropertyChangedEventArgs *args)
186 if (col != GetValue (GradientBrush::GradientStopsProperty)->AsCollection ()) {
187 Brush::OnCollectionItemChanged (col, obj, args);
188 return;
191 NotifyListenersOfPropertyChange (GradientBrush::GradientStopsProperty, NULL);
194 void
195 GradientBrush::SetupGradient (cairo_pattern_t *pattern, const Rect &area, bool single)
197 GradientStopCollection *children = GetGradientStops ();
198 GradientSpreadMethod gsm = GetSpreadMethod ();
199 double opacity = GetOpacity ();
200 GradientStop *stop;
201 double offset;
202 int index;
204 cairo_pattern_set_extend (pattern, convert_gradient_spread_method (gsm));
206 // TODO - ColorInterpolationModeProperty is ignored (map to ?)
207 if (single) {
208 // if a single color is shown (e.g. start == end point) Cairo will,
209 // by default, use the start color while SL use the end color
210 index = children->GetCount () - 1;
211 } else {
212 index = 0;
215 GradientStop *negative_stop = NULL; //the biggest negative stop
216 double neg_offset = 0.0; //the cached associated offset
217 GradientStop *first_stop = NULL; //the smallest positive stop
218 double first_offset = 0.0; //idem
219 GradientStop *last_stop = NULL; //the biggest stop <= 1
220 double last_offset = 0.0; //idem
221 GradientStop *outofbounds_stop = NULL; //the smallest stop > 1
222 double out_offset = 0.0; //idem
224 for ( ; index < children->GetCount (); index++) {
225 stop = children->GetValueAt (index)->AsGradientStop ();
226 offset = stop->GetOffset ();
228 if (offset >= 0.0 && offset <= 1.0) {
229 Color *color = stop->GetColor ();
231 cairo_pattern_add_color_stop_rgba (pattern, offset, color->r, color->g, color->b, color->a * opacity);
233 if (!first_stop || (first_offset != 0.0 && offset < first_offset)) {
234 first_offset = offset;
235 first_stop = stop;
238 if (!last_stop || (last_offset != 1.0 && offset > last_offset)) {
239 last_offset = offset;
240 last_stop = stop;
242 } else if (offset < 0.0 && (!negative_stop || offset > neg_offset)) {
243 negative_stop = stop;
244 neg_offset = offset;
245 } else if (offset > 1.0 && (!outofbounds_stop || offset < out_offset)) {
246 outofbounds_stop = stop;
247 out_offset = offset;
251 if (negative_stop && first_stop && first_offset != 0.0) { //take care of the negative stop
252 Color *neg_color = negative_stop->GetColor ();
253 Color *first_color = first_stop->GetColor ();
254 double ratio = neg_offset / (neg_offset - first_offset);
256 cairo_pattern_add_color_stop_rgba (pattern, 0.0,
257 neg_color->r + ratio * (first_color->r - neg_color->r),
258 neg_color->g + ratio * (first_color->g - neg_color->g),
259 neg_color->b + ratio * (first_color->b - neg_color->b),
260 (neg_color->a + ratio * (first_color->a - neg_color->a)) * opacity);
263 if (outofbounds_stop && last_stop && last_offset != 1.0) { //take care of the >1 stop
264 Color *last_color = last_stop->GetColor ();
265 Color *out_color = outofbounds_stop->GetColor ();
266 double ratio = (1.0 - last_offset) / (out_offset - last_offset);
268 cairo_pattern_add_color_stop_rgba (pattern, 1.0,
269 last_color->r + ratio * (out_color->r - last_color->r),
270 last_color->g + ratio * (out_color->g - last_color->g),
271 last_color->b + ratio * (out_color->b - last_color->b),
272 (last_color->a + ratio * (out_color->a - last_color->a)) * opacity);
275 if (negative_stop && outofbounds_stop && !first_stop && !last_stop) { //only 2 stops, one < 0, the other > 1
276 Color *neg_color = negative_stop->GetColor ();
277 Color *out_color = outofbounds_stop->GetColor ();
278 double ratio = neg_offset / (neg_offset - out_offset);
280 cairo_pattern_add_color_stop_rgba (pattern, 0.0,
281 neg_color->r + ratio * (out_color->r - neg_color->r),
282 neg_color->g + ratio * (out_color->g - neg_color->g),
283 neg_color->b + ratio * (out_color->b - neg_color->b),
284 (neg_color->a + ratio * (out_color->a - neg_color->a)) * opacity);
286 ratio = (1.0 - neg_offset) / (out_offset - neg_offset);
288 cairo_pattern_add_color_stop_rgba (pattern, 1.0,
289 neg_color->r + ratio * (out_color->r - neg_color->r),
290 neg_color->g + ratio * (out_color->g - neg_color->g),
291 neg_color->b + ratio * (out_color->b - neg_color->b),
292 (neg_color->a + ratio * (out_color->a - neg_color->a)) * opacity);
295 if (negative_stop && !outofbounds_stop && !first_stop && !last_stop) { //only negative stops
296 Color *color = negative_stop->GetColor ();
298 cairo_pattern_add_color_stop_rgba (pattern, 0.0, color->r, color->g, color->b, color->a * opacity);
301 if (outofbounds_stop && !negative_stop && !first_stop && !last_stop) { //only > 1 stops
302 Color *color = outofbounds_stop->GetColor ();
304 cairo_pattern_add_color_stop_rgba (pattern, 1.0, color->r, color->g, color->b, color->a * opacity);
308 bool
309 GradientBrush::IsOpaque ()
311 if (!Brush::IsOpaque ())
312 return false;
314 GradientStopCollection *stops = GetGradientStops ();
315 GradientStop *stop;
316 Color *c;
318 for (int i = 0; i < stops->GetCount (); i++) {
319 stop = stops->GetValueAt (i)->AsGradientStop ();
320 c = stop->GetColor ();
321 if (IS_TRANSLUCENT (c->a))
322 return false;
325 return true;
329 // GradientStopCollection
332 GradientStopCollection::GradientStopCollection ()
334 SetObjectType (Type::GRADIENTSTOP_COLLECTION);
337 GradientStopCollection::~GradientStopCollection ()
343 // GradientStop
346 GradientStop::GradientStop()
348 SetObjectType (Type::GRADIENTSTOP);
351 GradientStop::~GradientStop()
356 // LinearGradientBrush
359 LinearGradientBrush::LinearGradientBrush ()
361 SetObjectType (Type::LINEARGRADIENTBRUSH);
364 LinearGradientBrush::~LinearGradientBrush ()
368 void
369 LinearGradientBrush::SetupBrush (cairo_t *cr, const Rect &area)
371 Point *start = GetStartPoint ();
372 Point *end = GetEndPoint ();
373 double x0, y0, x1, y1;
374 cairo_matrix_t offset_matrix;
375 Point p = area.GetTopLeft ();
377 switch (GetMappingMode ()) {
378 // unknown (e.g. bad) values are considered to be Absolute to Silverlight
379 // even if the default, i.e. *no* value) is RelativeToBoundingBox
380 case BrushMappingModeAbsolute:
381 default:
382 y0 = start ? start->y : 0.0;
383 x0 = start ? start->x : 0.0;
384 y1 = end ? end->y : area.height;
385 x1 = end ? end->x : area.width;
386 break;
387 case BrushMappingModeRelativeToBoundingBox:
388 y0 = start ? (start->y * area.height) : 0.0;
389 x0 = start ? (start->x * area.width) : 0.0;
390 y1 = end ? (end->y * area.height) : area.height;
391 x1 = end ? (end->x * area.width) : area.width;
392 break;
395 cairo_pattern_t *pattern = cairo_pattern_create_linear (x0, y0, x1, y1);
397 cairo_matrix_t matrix;
398 cairo_matrix_init_identity (&matrix);
400 Transform *transform = GetTransform ();
401 if (transform) {
402 cairo_matrix_t tm;
404 transform->GetTransform (&tm);
405 // TODO - optimization, check for empty/identity matrix too ?
406 cairo_matrix_multiply (&matrix, &matrix, &tm);
409 Transform *relative_transform = GetRelativeTransform ();
410 if (relative_transform) {
411 cairo_matrix_t tm;
412 transform_get_absolute_transform (relative_transform, area.width, area.height, &tm);
413 cairo_matrix_multiply (&matrix, &matrix, &tm);
416 if (p.x != 0.0 && p.y != 0.0) {
417 cairo_matrix_init_translate (&offset_matrix, p.x, p.y);
418 cairo_matrix_multiply (&matrix, &matrix, &offset_matrix);
421 brush_matrix_invert (&matrix);
422 cairo_pattern_set_matrix (pattern, &matrix);
424 bool only_start = (x0 == x1 && y0 == y1);
425 GradientBrush::SetupGradient (pattern, area, only_start);
427 cairo_set_source (cr, pattern);
428 cairo_pattern_destroy (pattern);
432 // RadialGradientBrush
435 RadialGradientBrush::RadialGradientBrush ()
437 SetObjectType (Type::RADIALGRADIENTBRUSH);
440 RadialGradientBrush::~RadialGradientBrush()
444 void
445 RadialGradientBrush::SetupBrush (cairo_t *cr, const Rect &area)
447 Point *origin = GetGradientOrigin ();
448 double ox = (origin ? origin->x : 0.5);
449 double oy = (origin ? origin->y : 0.5);
450 cairo_matrix_t offset_matrix;
452 Point *center = GetCenter ();
453 double cx = (center ? center->x : 0.5);
454 double cy = (center ? center->y : 0.5);
456 double rx = GetRadiusX ();
457 double ry = GetRadiusY ();
459 cairo_pattern_t *pattern = cairo_pattern_create_radial (ox/rx, oy/ry, 0.0, cx/rx, cy/ry, 1);
461 cairo_matrix_t matrix;
462 switch (GetMappingMode ()) {
463 // unknown (e.g. bad) values are considered to be Absolute to Silverlight
464 // even if the default, i.e. *no* value) is RelativeToBoundingBox
465 case BrushMappingModeAbsolute:
466 default:
467 cairo_matrix_init_translate (&matrix, cx, cy);
468 cairo_matrix_scale (&matrix, rx, ry);
469 cairo_matrix_translate (&matrix, -cx/rx, -cy/ry);
470 break;
471 case BrushMappingModeRelativeToBoundingBox:
472 cairo_matrix_init_translate (&matrix, cx * area.width, cy * area.height);
473 cairo_matrix_scale (&matrix, area.width * rx, area.height * ry );
474 cairo_matrix_translate (&matrix, -cx/rx, -cy/ry);
475 break;
478 Transform *transform = GetTransform ();
479 if (transform) {
480 cairo_matrix_t tm;
482 transform->GetTransform (&tm);
483 // TODO - optimization, check for empty/identity matrix too ?
484 cairo_matrix_multiply (&matrix, &matrix, &tm);
487 Transform *relative_transform = GetRelativeTransform ();
488 if (relative_transform) {
489 cairo_matrix_t tm;
490 transform_get_absolute_transform (relative_transform, area.width, area.height, &tm);
491 // TODO - optimization, check for empty/identity matrix too ?
492 cairo_matrix_multiply (&matrix, &matrix, &tm);
495 if (area.x != 0.0 || area.y != 0.0) {
496 cairo_matrix_init_translate (&offset_matrix, area.x, area.y);
497 cairo_matrix_multiply (&matrix, &matrix, &offset_matrix);
500 brush_matrix_invert (&matrix);
502 cairo_pattern_set_matrix (pattern, &matrix);
503 GradientBrush::SetupGradient (pattern, area);
505 cairo_set_source (cr, pattern);
506 cairo_pattern_destroy (pattern);
510 // ImageBrush
513 ImageBrush::ImageBrush ()
515 SetObjectType (Type::IMAGEBRUSH);
518 ImageBrush::~ImageBrush ()
522 void
523 ImageBrush::Dispose ()
525 BitmapImage *source = (BitmapImage *) GetImageSource ();
527 if (source) {
528 source->RemoveHandler (BitmapImage::DownloadProgressEvent, download_progress, this);
529 source->RemoveHandler (BitmapImage::ImageOpenedEvent, image_opened, this);
530 source->RemoveHandler (BitmapImage::ImageFailedEvent, image_failed, this);
531 source->RemoveHandler (BitmapSource::PixelDataChangedEvent, source_pixel_data_changed, this);
534 TileBrush::Dispose ();
537 void
538 ImageBrush::download_progress (EventObject *sender, EventArgs *calldata, gpointer closure)
540 ImageBrush *media = (ImageBrush *) closure;
542 media->DownloadProgress ();
545 void
546 ImageBrush::image_opened (EventObject *sender, EventArgs *calldata, gpointer closure)
548 ImageBrush *media = (ImageBrush *) closure;
550 media->ImageOpened ();
553 void
554 ImageBrush::image_failed (EventObject *sender, EventArgs *calldata, gpointer closure)
556 ImageBrush *media = (ImageBrush *) closure;
558 media->ImageFailed ((ImageErrorEventArgs*) calldata);
561 void
562 ImageBrush::source_pixel_data_changed (EventObject *sender, EventArgs *calldata, gpointer closure)
564 ImageBrush *media = (ImageBrush *) closure;
566 media->SourcePixelDataChanged ();
569 void
570 ImageBrush::DownloadProgress ()
572 BitmapImage *source = (BitmapImage *) GetImageSource ();
574 SetDownloadProgress (source->GetProgress ());
575 Emit (DownloadProgressChangedEvent);
578 void
579 ImageBrush::ImageOpened ()
581 BitmapImage *source = (BitmapImage*)GetImageSource ();
583 source->RemoveHandler (BitmapImage::DownloadProgressEvent, download_progress, this);
584 source->RemoveHandler (BitmapImage::ImageOpenedEvent, image_opened, this);
585 source->RemoveHandler (BitmapImage::ImageFailedEvent, image_failed, this);
588 void
589 ImageBrush::ImageFailed (ImageErrorEventArgs *args)
591 BitmapImage *source = (BitmapImage*)GetImageSource ();
593 source->RemoveHandler (BitmapImage::DownloadProgressEvent, download_progress, this);
594 source->RemoveHandler (BitmapImage::ImageOpenedEvent, image_opened, this);
595 source->RemoveHandler (BitmapImage::ImageFailedEvent, image_failed, this);
597 args->ref (); // to counter the unref in Emit
598 Emit (ImageFailedEvent, args);
601 void
602 ImageBrush::SourcePixelDataChanged ()
604 NotifyListenersOfPropertyChange (Brush::ChangedProperty, NULL);
607 void
608 ImageBrush::SetSource (Downloader *downloader, const char *PartName)
610 BitmapImage *source = (BitmapImage *) GetImageSource ();
612 if (source == NULL) {
613 source = new BitmapImage ();
614 SetImageSource (source);
617 source->AddHandler (BitmapImage::DownloadProgressEvent, download_progress, this);
618 source->AddHandler (BitmapImage::ImageOpenedEvent, image_opened, this);
619 source->AddHandler (BitmapImage::ImageFailedEvent, image_failed, this);
621 source->SetDownloader (downloader, NULL, PartName);
624 void
625 ImageBrush::OnPropertyChanged (PropertyChangedEventArgs *args, MoonError *error)
627 if (args->GetProperty ()->GetOwnerType() != Type::IMAGEBRUSH) {
628 TileBrush::OnPropertyChanged (args, error);
629 return;
630 } else if (args->GetId () == ImageSourceProperty) {
631 ImageSource *source = args->GetNewValue () ? args->GetNewValue ()->AsImageSource () : NULL;
632 ImageSource *old = args->GetOldValue () ? args->GetOldValue ()->AsImageSource () : NULL;
634 if (old && old->Is(Type::BITMAPSOURCE)) {
635 old->RemoveHandler (BitmapSource::PixelDataChangedEvent, source_pixel_data_changed, this);
637 if (source && source->Is(Type::BITMAPSOURCE)) {
638 source->AddHandler (BitmapSource::PixelDataChangedEvent, source_pixel_data_changed, this);
641 if (old && old->Is(Type::BITMAPIMAGE)) {
642 old->RemoveHandler (BitmapImage::DownloadProgressEvent, download_progress, this);
643 old->RemoveHandler (BitmapImage::ImageOpenedEvent, image_opened, this);
644 old->RemoveHandler (BitmapImage::ImageFailedEvent, image_failed, this);
646 if (source && source->Is(Type::BITMAPIMAGE)) {
647 source->AddHandler (BitmapImage::DownloadProgressEvent, download_progress, this);
648 source->AddHandler (BitmapImage::ImageOpenedEvent, image_opened, this);
649 source->AddHandler (BitmapImage::ImageFailedEvent, image_failed, this);
653 NotifyListenersOfPropertyChange (args, error);
656 bool
657 ImageBrush::IsOpaque ()
659 // XXX punt for now and return false here.
660 return false;
663 cairo_surface_t *
664 image_brush_create_similar (cairo_t *cairo, int width, int height)
666 #if USE_OPT_IMAGE_ONLY
667 return cairo_image_surface_create (CAIRO_FORMAT_ARGB32, width, height);
668 #else
669 return cairo_surface_create_similar (cairo_get_group_target (cairo),
670 CAIRO_CONTENT_COLOR_ALPHA,
671 width,
672 height);
673 #endif
676 void
677 image_brush_compute_pattern_matrix (cairo_matrix_t *matrix, double width, double height, int sw, int sh,
678 Stretch stretch, AlignmentX align_x, AlignmentY align_y, Transform *transform, Transform *relative_transform)
680 // scale required to "fit" for both axes
681 double sx = sw / width;
682 double sy = sh / height;
685 if (width == 0)
686 sx = 1.0;
688 if (height == 0)
689 sy = 1.0;
691 // Fill is the simplest case because AlignementX and AlignmentY don't matter in this case
692 if (stretch == StretchFill) {
693 // fill extents in both axes
694 cairo_matrix_init_scale (matrix, sx, sy);
695 } else {
696 double scale = 1.0;
697 double dx = 0.0;
698 double dy = 0.0;
700 switch (stretch) {
701 case StretchUniform:
702 // fill without cuting the image, center the other axes
703 scale = (sx < sy) ? sy : sx;
704 break;
705 case StretchUniformToFill:
706 // fill by, potentially, cuting the image on one axe, center on both axes
707 scale = (sx < sy) ? sx : sy;
708 break;
709 case StretchNone:
710 break;
711 default:
712 g_warning ("Invalid Stretch value (%d).", stretch);
713 break;
716 switch (align_x) {
717 case AlignmentXLeft:
718 dx = 0.0;
719 break;
720 case AlignmentXCenter:
721 dx = (sw - (scale * width)) / 2;
722 break;
723 // Silverlight+Javascript default to AlignmentXRight for (some) invalid values (others results in an alert)
724 case AlignmentXRight:
725 default:
726 dx = (sw - (scale * width));
727 break;
730 switch (align_y) {
731 case AlignmentYTop:
732 dy = 0.0;
733 break;
734 case AlignmentYCenter:
735 dy = (sh - (scale * height)) / 2;
736 break;
737 // Silverlight+Javascript default to AlignmentXBottom for (some) invalid values (others results in an alert)
738 case AlignmentYBottom:
739 default:
740 dy = (sh - (scale * height));
741 break;
744 if (stretch == StretchNone) {
745 // no strech, no scale
746 cairo_matrix_init_translate (matrix, dx, dy);
747 } else {
748 // otherwise there's both a scale and translation to be done
749 cairo_matrix_init (matrix, scale, 0, 0, scale, dx, dy);
753 if (transform || relative_transform) {
754 if (transform) {
755 cairo_matrix_t tm;
757 transform->GetTransform (&tm);
758 brush_matrix_invert (&tm);
759 cairo_matrix_multiply (matrix, &tm, matrix);
762 if (relative_transform) {
763 cairo_matrix_t tm;
765 transform_get_absolute_transform (relative_transform, width, height, &tm);
766 brush_matrix_invert (&tm);
767 cairo_matrix_multiply (matrix, &tm, matrix);
772 static bool
773 is_stretch_valid (Stretch stretch)
775 switch (stretch) {
776 case StretchNone:
777 case StretchFill:
778 case StretchUniform:
779 case StretchUniformToFill:
780 return true;
781 default:
782 return false;
786 void
787 ImageBrush::SetupBrush (cairo_t *cr, const Rect &area)
789 ImageSource *source = GetImageSource ();
790 cairo_surface_t *surface;
791 cairo_pattern_t *pattern;
792 cairo_matrix_t matrix;
793 Transform *transform;
794 Transform *relative_transform;
795 AlignmentX ax;
796 AlignmentY ay;
797 Stretch stretch;
799 if (!source) goto failed;
801 source->Lock ();
803 surface = source->GetSurface (cr);
805 stretch = GetStretch ();
807 if (!surface || !is_stretch_valid (stretch)) goto failed;
809 ax = GetAlignmentX ();
810 ay = GetAlignmentY ();
812 transform = GetTransform ();
813 relative_transform = GetRelativeTransform ();
815 pattern = cairo_pattern_create_for_surface (surface);
817 image_brush_compute_pattern_matrix (&matrix, area.width, area.height, source->GetPixelWidth (), source->GetPixelHeight (), stretch, ax, ay, transform, relative_transform);
818 cairo_matrix_translate (&matrix, -area.x, -area.y);
819 cairo_pattern_set_matrix (pattern, &matrix);
821 cairo_set_source (cr, pattern);
822 cairo_pattern_destroy (pattern);
824 source->Unlock ();
826 return;
828 failed:
829 cairo_set_source_rgba (cr, 0.0, 0.0, 0.0, 0.0);
830 return;
834 // TileBrush
837 TileBrush::TileBrush ()
839 SetObjectType (Type::TILEBRUSH);
842 TileBrush::~TileBrush ()
846 void
847 TileBrush::Fill (cairo_t *cr, bool preserve)
849 double opacity = GetOpacity ();
851 if (IS_INVISIBLE (opacity)) {
852 if (!preserve)
853 cairo_new_path (cr);
854 return;
857 if (!IS_TRANSLUCENT (opacity)) {
858 Brush::Fill (cr, preserve);
859 return;
862 cairo_save (cr);
863 cairo_clip (cr);
864 cairo_paint_with_alpha (cr, opacity);
865 cairo_restore (cr);
867 if (!preserve)
868 cairo_new_path (cr);
871 void
872 TileBrush::Stroke (cairo_t *cr, bool preserve)
874 double opacity = GetOpacity ();
876 if (IS_INVISIBLE (opacity)) {
877 if (!preserve)
878 cairo_new_path (cr);
879 return;
882 if (!IS_TRANSLUCENT (opacity)) {
883 Brush::Stroke (cr, preserve);
884 return;
887 cairo_save (cr);
888 cairo_push_group_with_content (cr, CAIRO_CONTENT_ALPHA);
889 cairo_set_source_rgba (cr, 1.0, 1.0, 1.0, opacity);
890 cairo_stroke (cr);
892 cairo_pattern_t *mask = cairo_pop_group (cr);
893 cairo_restore (cr);
894 cairo_mask (cr, mask);
895 cairo_pattern_destroy (mask);
897 if (!preserve)
898 cairo_new_path (cr);
902 // VideoBrush
905 VideoBrush::VideoBrush ()
907 SetObjectType (Type::VIDEOBRUSH);
908 media = NULL;
911 VideoBrush::~VideoBrush ()
913 if (media != NULL) {
914 media->RemovePropertyChangeListener (this);
915 media->RemoveHandler (MediaElement::MediaInvalidatedEvent, update_brush, this);
916 media->unref ();
920 void
921 VideoBrush::SetupBrush (cairo_t *cr, const Rect &area)
923 Stretch stretch = GetStretch ();
924 if (!is_stretch_valid (stretch)) {
925 // bad enum value for stretch, nothing should be drawn
926 // XXX Removing this _source_set at all?
927 cairo_set_source_rgba (cr, 0.0, 0.0, 0.0, 0.0);
928 return;
931 MediaPlayer *mplayer = media ? media->GetMediaPlayer () : NULL;
932 Transform *transform = GetTransform ();
933 Transform *relative_transform = GetRelativeTransform ();
934 AlignmentX ax = GetAlignmentX ();
935 AlignmentY ay = GetAlignmentY ();
936 cairo_surface_t *surface;
937 cairo_pattern_t *pattern;
938 cairo_matrix_t matrix;
940 if (media == NULL) {
941 DependencyObject *obj;
942 const char *name;
944 name = GetSourceName ();
946 if (name == NULL || *name == '\0')
947 return;
949 if ((obj = FindName (name)) && obj->Is (Type::MEDIAELEMENT)) {
950 obj->AddPropertyChangeListener (this);
951 media = (MediaElement *) obj;
952 media->AddHandler (MediaElement::MediaInvalidatedEvent, update_brush, this);
953 mplayer = media->GetMediaPlayer ();
954 obj->ref ();
955 } else if (obj == NULL) {
956 printf ("could not find element `%s'\n", name);
957 } else {
958 printf ("obj %p is not of type MediaElement (it is %s)\n", obj,
959 obj->GetTypeName ());
963 if (!mplayer || !(surface = mplayer->GetCairoSurface ())) {
964 // not yet available, draw gray-ish shadow where the brush should be applied
965 cairo_set_source_rgba (cr, 0.5, 0.5, 0.5, 0.5);
966 return;
969 pattern = cairo_pattern_create_for_surface (surface);
970 cairo_pattern_set_filter (pattern, CAIRO_FILTER_FAST);
972 image_brush_compute_pattern_matrix (&matrix, area.width, area.height, mplayer->GetVideoWidth (),
973 mplayer->GetVideoHeight (), stretch, ax, ay,
974 transform, relative_transform);
976 cairo_matrix_translate (&matrix, -area.x, -area.y);
977 cairo_pattern_set_matrix (pattern, &matrix);
979 cairo_set_source (cr, pattern);
980 cairo_pattern_destroy (pattern);
983 void
984 VideoBrush::OnPropertyChanged (PropertyChangedEventArgs *args, MoonError *error)
986 if (args->GetProperty ()->GetOwnerType() != Type::VIDEOBRUSH) {
987 TileBrush::OnPropertyChanged (args, error);
988 return;
991 if (args->GetId () == VideoBrush::SourceNameProperty) {
992 char *name = args->GetNewValue() ? args->GetNewValue()->AsString () : NULL;
993 DependencyObject *obj;
995 if (media != NULL) {
996 media->RemovePropertyChangeListener (this);
997 media->RemoveHandler (MediaElement::MediaInvalidatedEvent, update_brush, this);
998 media->unref ();
999 media = NULL;
1002 if (name && (obj = FindName (name)) && obj->Is (Type::MEDIAELEMENT)) {
1003 obj->AddPropertyChangeListener (this);
1004 media = (MediaElement *) obj;
1005 media->AddHandler (MediaElement::MediaInvalidatedEvent, update_brush, this);
1006 obj->ref ();
1007 } else {
1008 // Note: This may have failed because the parser hasn't set the
1009 // toplevel element yet, we'll try again in SetupBrush()
1013 NotifyListenersOfPropertyChange (args, error);
1016 void
1017 VideoBrush::OnSubPropertyChanged (DependencyProperty *prop, DependencyObject *obj, PropertyChangedEventArgs *subobj_args)
1019 /* this is being handled in the base class */
1021 if (subobj_args->GetId () == MediaElement::PositionProperty) {
1022 // We to changes in this MediaElement property so we
1023 // can notify whoever is using us to paint that they
1024 // need to redraw themselves.
1025 NotifyListenersOfPropertyChange (Brush::ChangedProperty);
1029 TileBrush::OnSubPropertyChanged (prop, obj, subobj_args);
1032 void
1033 VideoBrush::SetSource (MediaElement *source)
1035 if (source) {
1036 source->ref ();
1037 source->AddHandler (MediaElement::MediaInvalidatedEvent, update_brush, this);
1040 SetSourceName ("");
1042 if (media != NULL) {
1043 media->RemovePropertyChangeListener (this);
1044 media->RemoveHandler (MediaElement::MediaInvalidatedEvent, update_brush, this);
1045 media->unref ();
1046 media = NULL;
1049 media = source;
1052 bool
1053 VideoBrush::IsOpaque ()
1055 // XXX punt for now and return false here.
1056 return false;
1059 bool
1060 VideoBrush::IsAnimating ()
1062 if (media && media->IsPlaying ())
1063 return true;
1065 return TileBrush::IsAnimating ();
1068 void
1069 VideoBrush::update_brush (EventObject *, EventArgs *, gpointer closure)
1071 VideoBrush *b = (VideoBrush*)closure;
1072 b->NotifyListenersOfPropertyChange (Brush::ChangedProperty, NULL);
1076 // VisualBrush
1079 VisualBrush::VisualBrush ()
1081 SetObjectType (Type::VISUALBRUSH);
1084 VisualBrush::~VisualBrush ()
1088 void
1089 VisualBrush::SetupBrush (cairo_t *cr, const Rect &area)
1091 UIElement *ui = (UIElement *) GetVisual ();
1092 if (!ui) {
1093 // not yet available, draw gray-ish shadow where the brush should be applied
1094 cairo_set_source_rgba (cr, 0.5, 0.5, 0.5, 0.5);
1095 return;
1098 // XXX we should cache the surface so that it can be
1099 // used multiple times without having to re-render each time.
1100 Rect bounds = ui->GetSubtreeBounds().RoundOut ();
1102 surface = image_brush_create_similar (cr, (int) bounds.width, (int) bounds.height);
1104 cairo_t *surface_cr = cairo_create (surface);
1105 Region region = Region (0, 0, bounds.width, bounds.height);
1106 ui->Render (surface_cr, &region);
1107 cairo_destroy (surface_cr);
1109 Stretch stretch = GetStretch ();
1111 AlignmentX ax = GetAlignmentX ();
1112 AlignmentY ay = GetAlignmentY ();
1114 Transform *transform = GetTransform ();
1115 Transform *relative_transform = GetRelativeTransform ();
1117 cairo_pattern_t *pattern = cairo_pattern_create_for_surface (surface);
1118 cairo_matrix_t matrix;
1119 image_brush_compute_pattern_matrix (&matrix, area.width, area.height,
1120 (int) bounds.width, (int) bounds.height,
1121 stretch, ax, ay, transform, relative_transform);
1123 cairo_matrix_translate (&matrix, -area.x, -area.y);
1124 cairo_pattern_set_matrix (pattern, &matrix);
1126 cairo_set_source (cr, pattern);
1127 cairo_pattern_destroy (pattern);
1129 cairo_surface_destroy (surface);
1132 void
1133 VisualBrush::update_brush (EventObject *, EventArgs *, gpointer closure)
1135 VisualBrush *b = (VisualBrush*)closure;
1136 b->NotifyListenersOfPropertyChange (Brush::ChangedProperty, NULL);
1139 void
1140 VisualBrush::OnPropertyChanged (PropertyChangedEventArgs *args, MoonError *error)
1142 if (args->GetProperty ()->GetOwnerType() != Type::VISUALBRUSH) {
1143 TileBrush::OnPropertyChanged (args, error);
1144 return;
1147 if (args->GetId () == VisualBrush::VisualProperty) {
1148 // XXX we really need a way to disconnect from the preview visual
1149 UIElement *v = args->GetNewValue()->AsUIElement();
1150 v->AddHandler (((UIElement*)v)->InvalidatedEvent, update_brush, this);
1153 NotifyListenersOfPropertyChange (args, error);
1156 bool
1157 VisualBrush::IsOpaque ()
1159 // XXX punt for now and return false here.
1160 return false;