2009-12-04 Jeffrey Stedfast <fejj@novell.com>
[moon.git] / src / brush.cpp
blob84f9a29477f2ec7c40845a57b1205dc2e1684208
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"
26 #include "uri.h"
29 // SL-Cairo convertion and helper routines
32 static cairo_extend_t
33 convert_gradient_spread_method (GradientSpreadMethod method)
35 switch (method) {
36 case GradientSpreadMethodPad:
37 return CAIRO_EXTEND_PAD;
38 case GradientSpreadMethodReflect:
39 return CAIRO_EXTEND_REFLECT;
40 // unknown (e.g. bad) values are considered to be Repeat by Silverlight
41 // even if the default, i.e. *no* value) is Pad
42 case GradientSpreadMethodRepeat:
43 default:
44 return CAIRO_EXTEND_REPEAT;
48 static void
49 brush_matrix_invert (cairo_matrix_t *matrix)
51 cairo_status_t status = cairo_matrix_invert (matrix);
52 if (status != CAIRO_STATUS_SUCCESS) {
53 printf ("Moonlight: Error inverting matrix falling back\n");
54 cairo_matrix_init_identity (matrix);
59 // Brush
63 Brush::Brush()
65 SetObjectType (Type::BRUSH);
68 void
69 Brush::SetupBrush (cairo_t *cr, const Rect &area)
71 g_warning ("Brush:SetupBrush has been called. The derived class should have overridden it.");
74 void
75 Brush::Fill (cairo_t *cr, bool preserve)
77 if (preserve)
78 cairo_fill_preserve (cr);
79 else
80 cairo_fill (cr);
83 void
84 Brush::Stroke (cairo_t *cr, bool preserve)
86 if (preserve)
87 cairo_stroke_preserve (cr);
88 else
89 cairo_stroke (cr);
92 void
93 Brush::OnSubPropertyChanged (DependencyProperty *prop, DependencyObject *obj, PropertyChangedEventArgs *subobj_args)
95 // if our transforms change in some fashion, we need to redraw
96 // the element.
97 NotifyListenersOfPropertyChange (Brush::ChangedProperty, NULL);
99 DependencyObject::OnSubPropertyChanged (prop, obj, subobj_args);
102 bool
103 Brush::IsOpaque ()
105 return !IS_TRANSLUCENT (GetOpacity ());
108 bool
109 Brush::IsAnimating ()
111 return FALSE;
114 static void
115 transform_get_absolute_transform (Transform *relative_transform, double width, double height, cairo_matrix_t *result)
117 cairo_matrix_t tm;
119 cairo_matrix_init_scale (result, width, height);
120 relative_transform->GetTransform (&tm);
121 cairo_matrix_multiply (result, &tm, result);
122 cairo_matrix_scale (result, 1.0/width, 1.0/height);
128 // SolidColorBrush
131 SolidColorBrush::SolidColorBrush ()
133 SetObjectType (Type::SOLIDCOLORBRUSH);
136 SolidColorBrush::SolidColorBrush (const char *color)
138 SetObjectType (Type::SOLIDCOLORBRUSH);
139 Color *c = color_from_str (color);
140 SetColor (c);
141 delete c;
144 void
145 SolidColorBrush::SetupBrush (cairo_t *cr, const Rect &area)
147 double opacity = GetOpacity ();
148 Color *color = GetColor ();
150 cairo_set_source_rgba (cr, color->r, color->g, color->b, opacity * color->a);
153 bool
154 SolidColorBrush::IsOpaque ()
156 return Brush::IsOpaque () && !IS_TRANSLUCENT (GetColor ()->a);
161 // GradientBrush
164 GradientBrush::GradientBrush ()
166 SetObjectType (Type::GRADIENTBRUSH);
169 GradientBrush::~GradientBrush ()
173 void
174 GradientBrush::OnCollectionChanged (Collection *col, CollectionChangedEventArgs *args)
176 if (col != GetValue (GradientBrush::GradientStopsProperty)->AsCollection ()) {
177 Brush::OnCollectionChanged (col, args);
178 return;
181 NotifyListenersOfPropertyChange (GradientBrush::GradientStopsProperty, NULL);
184 void
185 GradientBrush::OnCollectionItemChanged (Collection *col, DependencyObject *obj, PropertyChangedEventArgs *args)
187 if (col != GetValue (GradientBrush::GradientStopsProperty)->AsCollection ()) {
188 Brush::OnCollectionItemChanged (col, obj, args);
189 return;
192 NotifyListenersOfPropertyChange (GradientBrush::GradientStopsProperty, NULL);
195 void
196 GradientBrush::SetupGradient (cairo_pattern_t *pattern, const Rect &area, bool single)
198 GradientStopCollection *children = GetGradientStops ();
199 GradientSpreadMethod gsm = GetSpreadMethod ();
200 double opacity = GetOpacity ();
201 GradientStop *stop;
202 double offset;
203 int index;
205 cairo_pattern_set_extend (pattern, convert_gradient_spread_method (gsm));
207 // TODO - ColorInterpolationModeProperty is ignored (map to ?)
208 if (single) {
209 // if a single color is shown (e.g. start == end point) Cairo will,
210 // by default, use the start color while SL use the end color
211 index = children->GetCount () - 1;
212 } else {
213 index = 0;
216 GradientStop *negative_stop = NULL; //the biggest negative stop
217 double neg_offset = 0.0; //the cached associated offset
218 GradientStop *first_stop = NULL; //the smallest positive stop
219 double first_offset = 0.0; //idem
220 GradientStop *last_stop = NULL; //the biggest stop <= 1
221 double last_offset = 0.0; //idem
222 GradientStop *outofbounds_stop = NULL; //the smallest stop > 1
223 double out_offset = 0.0; //idem
225 for ( ; index < children->GetCount (); index++) {
226 stop = children->GetValueAt (index)->AsGradientStop ();
227 offset = stop->GetOffset ();
229 if (offset >= 0.0 && offset <= 1.0) {
230 Color *color = stop->GetColor ();
232 cairo_pattern_add_color_stop_rgba (pattern, offset, color->r, color->g, color->b, color->a * opacity);
234 if (!first_stop || (first_offset != 0.0 && offset < first_offset)) {
235 first_offset = offset;
236 first_stop = stop;
239 if (!last_stop || (last_offset != 1.0 && offset > last_offset)) {
240 last_offset = offset;
241 last_stop = stop;
243 } else if (offset < 0.0 && (!negative_stop || offset > neg_offset)) {
244 negative_stop = stop;
245 neg_offset = offset;
246 } else if (offset > 1.0 && (!outofbounds_stop || offset < out_offset)) {
247 outofbounds_stop = stop;
248 out_offset = offset;
252 if (negative_stop && first_stop && first_offset != 0.0) { //take care of the negative stop
253 Color *neg_color = negative_stop->GetColor ();
254 Color *first_color = first_stop->GetColor ();
255 double ratio = neg_offset / (neg_offset - first_offset);
257 cairo_pattern_add_color_stop_rgba (pattern, 0.0,
258 neg_color->r + ratio * (first_color->r - neg_color->r),
259 neg_color->g + ratio * (first_color->g - neg_color->g),
260 neg_color->b + ratio * (first_color->b - neg_color->b),
261 (neg_color->a + ratio * (first_color->a - neg_color->a)) * opacity);
264 if (outofbounds_stop && last_stop && last_offset != 1.0) { //take care of the >1 stop
265 Color *last_color = last_stop->GetColor ();
266 Color *out_color = outofbounds_stop->GetColor ();
267 double ratio = (1.0 - last_offset) / (out_offset - last_offset);
269 cairo_pattern_add_color_stop_rgba (pattern, 1.0,
270 last_color->r + ratio * (out_color->r - last_color->r),
271 last_color->g + ratio * (out_color->g - last_color->g),
272 last_color->b + ratio * (out_color->b - last_color->b),
273 (last_color->a + ratio * (out_color->a - last_color->a)) * opacity);
276 if (negative_stop && outofbounds_stop && !first_stop && !last_stop) { //only 2 stops, one < 0, the other > 1
277 Color *neg_color = negative_stop->GetColor ();
278 Color *out_color = outofbounds_stop->GetColor ();
279 double ratio = neg_offset / (neg_offset - out_offset);
281 cairo_pattern_add_color_stop_rgba (pattern, 0.0,
282 neg_color->r + ratio * (out_color->r - neg_color->r),
283 neg_color->g + ratio * (out_color->g - neg_color->g),
284 neg_color->b + ratio * (out_color->b - neg_color->b),
285 (neg_color->a + ratio * (out_color->a - neg_color->a)) * opacity);
287 ratio = (1.0 - neg_offset) / (out_offset - neg_offset);
289 cairo_pattern_add_color_stop_rgba (pattern, 1.0,
290 neg_color->r + ratio * (out_color->r - neg_color->r),
291 neg_color->g + ratio * (out_color->g - neg_color->g),
292 neg_color->b + ratio * (out_color->b - neg_color->b),
293 (neg_color->a + ratio * (out_color->a - neg_color->a)) * opacity);
296 if (negative_stop && !outofbounds_stop && !first_stop && !last_stop) { //only negative stops
297 Color *color = negative_stop->GetColor ();
299 cairo_pattern_add_color_stop_rgba (pattern, 0.0, color->r, color->g, color->b, color->a * opacity);
302 if (outofbounds_stop && !negative_stop && !first_stop && !last_stop) { //only > 1 stops
303 Color *color = outofbounds_stop->GetColor ();
305 cairo_pattern_add_color_stop_rgba (pattern, 1.0, color->r, color->g, color->b, color->a * opacity);
309 bool
310 GradientBrush::IsOpaque ()
312 if (!Brush::IsOpaque ())
313 return false;
315 GradientStopCollection *stops = GetGradientStops ();
316 GradientStop *stop;
317 Color *c;
319 for (int i = 0; i < stops->GetCount (); i++) {
320 stop = stops->GetValueAt (i)->AsGradientStop ();
321 c = stop->GetColor ();
322 if (IS_TRANSLUCENT (c->a))
323 return false;
326 return true;
330 // GradientStopCollection
333 GradientStopCollection::GradientStopCollection ()
335 SetObjectType (Type::GRADIENTSTOP_COLLECTION);
338 GradientStopCollection::~GradientStopCollection ()
344 // GradientStop
347 GradientStop::GradientStop()
349 SetObjectType (Type::GRADIENTSTOP);
352 GradientStop::~GradientStop()
357 // LinearGradientBrush
360 LinearGradientBrush::LinearGradientBrush ()
362 SetObjectType (Type::LINEARGRADIENTBRUSH);
365 LinearGradientBrush::~LinearGradientBrush ()
369 void
370 LinearGradientBrush::SetupBrush (cairo_t *cr, const Rect &area)
372 Point *start = GetStartPoint ();
373 Point *end = GetEndPoint ();
374 double x0, y0, x1, y1;
375 cairo_matrix_t offset_matrix;
376 Point p = area.GetTopLeft ();
378 switch (GetMappingMode ()) {
379 // unknown (e.g. bad) values are considered to be Absolute to Silverlight
380 // even if the default, i.e. *no* value) is RelativeToBoundingBox
381 case BrushMappingModeAbsolute:
382 default:
383 y0 = start ? start->y : 0.0;
384 x0 = start ? start->x : 0.0;
385 y1 = end ? end->y : area.height;
386 x1 = end ? end->x : area.width;
387 break;
388 case BrushMappingModeRelativeToBoundingBox:
389 y0 = start ? (start->y * area.height) : 0.0;
390 x0 = start ? (start->x * area.width) : 0.0;
391 y1 = end ? (end->y * area.height) : area.height;
392 x1 = end ? (end->x * area.width) : area.width;
393 break;
396 cairo_pattern_t *pattern = cairo_pattern_create_linear (x0, y0, x1, y1);
398 cairo_matrix_t matrix;
399 cairo_matrix_init_identity (&matrix);
401 Transform *transform = GetTransform ();
402 if (transform) {
403 cairo_matrix_t tm;
405 transform->GetTransform (&tm);
406 // TODO - optimization, check for empty/identity matrix too ?
407 cairo_matrix_multiply (&matrix, &matrix, &tm);
410 Transform *relative_transform = GetRelativeTransform ();
411 if (relative_transform) {
412 cairo_matrix_t tm;
413 transform_get_absolute_transform (relative_transform, area.width, area.height, &tm);
414 cairo_matrix_multiply (&matrix, &matrix, &tm);
417 if (p.x != 0.0 && p.y != 0.0) {
418 cairo_matrix_init_translate (&offset_matrix, p.x, p.y);
419 cairo_matrix_multiply (&matrix, &matrix, &offset_matrix);
422 brush_matrix_invert (&matrix);
423 cairo_pattern_set_matrix (pattern, &matrix);
425 bool only_start = (x0 == x1 && y0 == y1);
426 GradientBrush::SetupGradient (pattern, area, only_start);
428 if (cairo_pattern_status (pattern) == CAIRO_STATUS_SUCCESS)
429 cairo_set_source (cr, pattern);
430 else
431 cairo_set_source_rgba (cr, 0.0, 0.0, 0.0, 0.0);
433 cairo_pattern_destroy (pattern);
437 // RadialGradientBrush
440 RadialGradientBrush::RadialGradientBrush ()
442 SetObjectType (Type::RADIALGRADIENTBRUSH);
445 RadialGradientBrush::~RadialGradientBrush()
449 void
450 RadialGradientBrush::SetupBrush (cairo_t *cr, const Rect &area)
452 Point *origin = GetGradientOrigin ();
453 double ox = (origin ? origin->x : 0.5);
454 double oy = (origin ? origin->y : 0.5);
455 cairo_matrix_t offset_matrix;
457 Point *center = GetCenter ();
458 double cx = (center ? center->x : 0.5);
459 double cy = (center ? center->y : 0.5);
461 double rx = GetRadiusX ();
462 double ry = GetRadiusY ();
464 cairo_pattern_t *pattern = cairo_pattern_create_radial (ox/rx, oy/ry, 0.0, cx/rx, cy/ry, 1);
466 cairo_matrix_t matrix;
467 switch (GetMappingMode ()) {
468 // unknown (e.g. bad) values are considered to be Absolute to Silverlight
469 // even if the default, i.e. *no* value) is RelativeToBoundingBox
470 case BrushMappingModeAbsolute:
471 default:
472 cairo_matrix_init_translate (&matrix, cx, cy);
473 cairo_matrix_scale (&matrix, rx, ry);
474 cairo_matrix_translate (&matrix, -cx/rx, -cy/ry);
475 break;
476 case BrushMappingModeRelativeToBoundingBox:
477 cairo_matrix_init_translate (&matrix, cx * area.width, cy * area.height);
478 cairo_matrix_scale (&matrix, area.width * rx, area.height * ry );
479 cairo_matrix_translate (&matrix, -cx/rx, -cy/ry);
480 break;
483 Transform *transform = GetTransform ();
484 if (transform) {
485 cairo_matrix_t tm;
487 transform->GetTransform (&tm);
488 // TODO - optimization, check for empty/identity matrix too ?
489 cairo_matrix_multiply (&matrix, &matrix, &tm);
492 Transform *relative_transform = GetRelativeTransform ();
493 if (relative_transform) {
494 cairo_matrix_t tm;
495 transform_get_absolute_transform (relative_transform, area.width, area.height, &tm);
496 // TODO - optimization, check for empty/identity matrix too ?
497 cairo_matrix_multiply (&matrix, &matrix, &tm);
500 if (area.x != 0.0 || area.y != 0.0) {
501 cairo_matrix_init_translate (&offset_matrix, area.x, area.y);
502 cairo_matrix_multiply (&matrix, &matrix, &offset_matrix);
505 brush_matrix_invert (&matrix);
507 cairo_pattern_set_matrix (pattern, &matrix);
508 GradientBrush::SetupGradient (pattern, area);
510 if (cairo_pattern_status (pattern) == CAIRO_STATUS_SUCCESS)
511 cairo_set_source (cr, pattern);
512 else
513 cairo_set_source_rgba (cr, 0.0, 0.0, 0.0, 0.0);
515 cairo_pattern_destroy (pattern);
519 // ImageBrush
522 ImageBrush::ImageBrush ()
524 SetObjectType (Type::IMAGEBRUSH);
527 ImageBrush::~ImageBrush ()
531 void
532 ImageBrush::Dispose ()
534 BitmapImage *source = (BitmapImage *) GetImageSource ();
536 if (source) {
537 source->RemoveHandler (BitmapImage::DownloadProgressEvent, download_progress, this);
538 source->RemoveHandler (BitmapImage::ImageOpenedEvent, image_opened, this);
539 source->RemoveHandler (BitmapImage::ImageFailedEvent, image_failed, this);
540 source->RemoveHandler (BitmapSource::PixelDataChangedEvent, source_pixel_data_changed, this);
543 TileBrush::Dispose ();
546 void
547 ImageBrush::download_progress (EventObject *sender, EventArgs *calldata, gpointer closure)
549 ImageBrush *media = (ImageBrush *) closure;
551 media->DownloadProgress ();
554 void
555 ImageBrush::image_opened (EventObject *sender, EventArgs *calldata, gpointer closure)
557 ImageBrush *media = (ImageBrush *) closure;
559 media->ImageOpened ((RoutedEventArgs *) calldata);
562 void
563 ImageBrush::image_failed (EventObject *sender, EventArgs *calldata, gpointer closure)
565 ImageBrush *media = (ImageBrush *) closure;
567 media->ImageFailed ((ImageErrorEventArgs*) calldata);
570 void
571 ImageBrush::source_pixel_data_changed (EventObject *sender, EventArgs *calldata, gpointer closure)
573 ImageBrush *media = (ImageBrush *) closure;
575 media->SourcePixelDataChanged ();
578 void
579 ImageBrush::DownloadProgress ()
581 BitmapImage *source = (BitmapImage *) GetImageSource ();
583 SetDownloadProgress (source->GetProgress ());
584 Emit (DownloadProgressChangedEvent);
587 void
588 ImageBrush::ImageOpened (RoutedEventArgs *args)
590 BitmapImage *source = (BitmapImage*)GetImageSource ();
592 source->RemoveHandler (BitmapImage::DownloadProgressEvent, download_progress, this);
593 source->RemoveHandler (BitmapImage::ImageOpenedEvent, image_opened, this);
594 source->RemoveHandler (BitmapImage::ImageFailedEvent, image_failed, this);
596 args->ref (); // to counter the unref in Emit
597 Emit (ImageOpenedEvent, args);
600 void
601 ImageBrush::ImageFailed (ImageErrorEventArgs *args)
603 BitmapImage *source = (BitmapImage*)GetImageSource ();
605 source->RemoveHandler (BitmapImage::DownloadProgressEvent, download_progress, this);
606 source->RemoveHandler (BitmapImage::ImageOpenedEvent, image_opened, this);
607 source->RemoveHandler (BitmapImage::ImageFailedEvent, image_failed, this);
609 args->ref (); // to counter the unref in Emit
610 Emit (ImageFailedEvent, args);
613 void
614 ImageBrush::SourcePixelDataChanged ()
616 NotifyListenersOfPropertyChange (Brush::ChangedProperty, NULL);
619 void
620 ImageBrush::SetSource (Downloader *downloader, const char *PartName)
622 BitmapImage *source = (BitmapImage *) GetImageSource ();
624 if (source == NULL) {
625 source = new BitmapImage ();
626 SetImageSource (source);
629 source->AddHandler (BitmapImage::DownloadProgressEvent, download_progress, this);
630 source->AddHandler (BitmapImage::ImageOpenedEvent, image_opened, this);
631 source->AddHandler (BitmapImage::ImageFailedEvent, image_failed, this);
633 source->SetDownloader (downloader, NULL, PartName);
636 void
637 ImageBrush::OnPropertyChanged (PropertyChangedEventArgs *args, MoonError *error)
639 if (args->GetProperty ()->GetOwnerType() != Type::IMAGEBRUSH) {
640 TileBrush::OnPropertyChanged (args, error);
641 return;
642 } else if (args->GetId () == ImageSourceProperty) {
643 ImageSource *source = args->GetNewValue () ? args->GetNewValue ()->AsImageSource () : NULL;
644 ImageSource *old = args->GetOldValue () ? args->GetOldValue ()->AsImageSource () : NULL;
646 if (old && old->Is(Type::BITMAPSOURCE)) {
647 old->RemoveHandler (BitmapSource::PixelDataChangedEvent, source_pixel_data_changed, this);
649 if (source && source->Is(Type::BITMAPSOURCE)) {
650 source->AddHandler (BitmapSource::PixelDataChangedEvent, source_pixel_data_changed, this);
653 if (old && old->Is(Type::BITMAPIMAGE)) {
654 old->RemoveHandler (BitmapImage::DownloadProgressEvent, download_progress, this);
655 old->RemoveHandler (BitmapImage::ImageOpenedEvent, image_opened, this);
656 old->RemoveHandler (BitmapImage::ImageFailedEvent, image_failed, this);
658 if (source && source->Is(Type::BITMAPIMAGE)) {
659 BitmapImage *bitmap = (BitmapImage *) source;
660 Uri *uri = bitmap->GetUriSource ();
662 source->AddHandler (BitmapImage::DownloadProgressEvent, download_progress, this);
663 source->AddHandler (BitmapImage::ImageOpenedEvent, image_opened, this);
664 source->AddHandler (BitmapImage::ImageFailedEvent, image_failed, this);
666 // can uri ever be null?
667 if (uri != NULL) {
668 ImageErrorEventArgs *args = NULL;
670 if (uri->IsInvalidPath ()) {
671 args = new ImageErrorEventArgs (MoonError (MoonError::ARGUMENT_OUT_OF_RANGE, 0, "invalid path found in uri"));
672 } else if (!bitmap->ValidateDownloadPolicy ()) {
673 args = new ImageErrorEventArgs (MoonError (MoonError::ARGUMENT_OUT_OF_RANGE, 0, "Security Policy Violation"));
676 if (args != NULL) {
677 source->RemoveHandler (BitmapImage::ImageFailedEvent, image_failed, this);
678 EmitAsync (ImageFailedEvent, args);
682 SourcePixelDataChanged ();
685 NotifyListenersOfPropertyChange (args, error);
688 bool
689 ImageBrush::IsOpaque ()
691 // XXX punt for now and return false here.
692 return false;
695 cairo_surface_t *
696 image_brush_create_similar (cairo_t *cairo, int width, int height)
698 #if USE_OPT_IMAGE_ONLY
699 return cairo_image_surface_create (CAIRO_FORMAT_ARGB32, width, height);
700 #else
701 return cairo_surface_create_similar (cairo_get_group_target (cairo),
702 CAIRO_CONTENT_COLOR_ALPHA,
703 width,
704 height);
705 #endif
708 void
709 image_brush_compute_pattern_matrix (cairo_matrix_t *matrix, double width, double height, int sw, int sh,
710 Stretch stretch, AlignmentX align_x, AlignmentY align_y, Transform *transform, Transform *relative_transform)
712 // scale required to "fit" for both axes
713 double sx = sw / width;
714 double sy = sh / height;
717 if (width == 0)
718 sx = 1.0;
720 if (height == 0)
721 sy = 1.0;
723 // Fill is the simplest case because AlignementX and AlignmentY don't matter in this case
724 if (stretch == StretchFill) {
725 // fill extents in both axes
726 cairo_matrix_init_scale (matrix, sx, sy);
727 } else {
728 double scale = 1.0;
729 double dx = 0.0;
730 double dy = 0.0;
732 switch (stretch) {
733 case StretchUniform:
734 // fill without cuting the image, center the other axes
735 scale = (sx < sy) ? sy : sx;
736 break;
737 case StretchUniformToFill:
738 // fill by, potentially, cuting the image on one axe, center on both axes
739 scale = (sx < sy) ? sx : sy;
740 break;
741 case StretchNone:
742 break;
743 default:
744 g_warning ("Invalid Stretch value (%d).", stretch);
745 break;
748 switch (align_x) {
749 case AlignmentXLeft:
750 dx = 0.0;
751 break;
752 case AlignmentXCenter:
753 dx = (sw - (scale * width)) / 2;
754 break;
755 // Silverlight+Javascript default to AlignmentXRight for (some) invalid values (others results in an alert)
756 case AlignmentXRight:
757 default:
758 dx = (sw - (scale * width));
759 break;
762 switch (align_y) {
763 case AlignmentYTop:
764 dy = 0.0;
765 break;
766 case AlignmentYCenter:
767 dy = (sh - (scale * height)) / 2;
768 break;
769 // Silverlight+Javascript default to AlignmentXBottom for (some) invalid values (others results in an alert)
770 case AlignmentYBottom:
771 default:
772 dy = (sh - (scale * height));
773 break;
776 if (stretch == StretchNone) {
777 // no strech, no scale
778 cairo_matrix_init_translate (matrix, dx, dy);
779 } else {
780 // otherwise there's both a scale and translation to be done
781 cairo_matrix_init (matrix, scale, 0, 0, scale, dx, dy);
785 if (transform || relative_transform) {
786 if (transform) {
787 cairo_matrix_t tm;
789 transform->GetTransform (&tm);
790 brush_matrix_invert (&tm);
791 cairo_matrix_multiply (matrix, &tm, matrix);
794 if (relative_transform) {
795 cairo_matrix_t tm;
797 transform_get_absolute_transform (relative_transform, width, height, &tm);
798 brush_matrix_invert (&tm);
799 cairo_matrix_multiply (matrix, &tm, matrix);
804 static bool
805 is_stretch_valid (Stretch stretch)
807 switch (stretch) {
808 case StretchNone:
809 case StretchFill:
810 case StretchUniform:
811 case StretchUniformToFill:
812 return true;
813 default:
814 return false;
818 void
819 ImageBrush::SetupBrush (cairo_t *cr, const Rect &area)
821 ImageSource *source = GetImageSource ();
822 cairo_surface_t *surface;
823 cairo_pattern_t *pattern;
824 cairo_matrix_t matrix;
825 Transform *transform;
826 Transform *relative_transform;
827 AlignmentX ax;
828 AlignmentY ay;
829 Stretch stretch;
831 if (!source) goto failed;
833 source->Lock ();
835 surface = source->GetSurface (cr);
837 stretch = GetStretch ();
839 if (!surface || !is_stretch_valid (stretch)) {
840 source->Unlock ();
841 goto failed;
844 ax = GetAlignmentX ();
845 ay = GetAlignmentY ();
847 transform = GetTransform ();
848 relative_transform = GetRelativeTransform ();
850 pattern = cairo_pattern_create_for_surface (surface);
852 image_brush_compute_pattern_matrix (&matrix, area.width, area.height, source->GetPixelWidth (), source->GetPixelHeight (), stretch, ax, ay, transform, relative_transform);
853 cairo_matrix_translate (&matrix, -area.x, -area.y);
854 cairo_pattern_set_matrix (pattern, &matrix);
856 if (cairo_pattern_status (pattern) == CAIRO_STATUS_SUCCESS)
857 cairo_set_source (cr, pattern);
858 else
859 cairo_set_source_rgba (cr, 0.0, 0.0, 0.0, 0.0);
861 cairo_pattern_destroy (pattern);
863 source->Unlock ();
865 return;
867 failed:
868 cairo_set_source_rgba (cr, 0.0, 0.0, 0.0, 0.0);
869 return;
873 // TileBrush
876 TileBrush::TileBrush ()
878 SetObjectType (Type::TILEBRUSH);
881 TileBrush::~TileBrush ()
885 void
886 TileBrush::Fill (cairo_t *cr, bool preserve)
888 double opacity = GetOpacity ();
890 if (IS_INVISIBLE (opacity)) {
891 if (!preserve)
892 cairo_new_path (cr);
893 return;
896 if (!IS_TRANSLUCENT (opacity)) {
897 Brush::Fill (cr, preserve);
898 return;
901 cairo_save (cr);
902 cairo_clip (cr);
903 cairo_paint_with_alpha (cr, opacity);
904 cairo_restore (cr);
906 if (!preserve)
907 cairo_new_path (cr);
910 void
911 TileBrush::Stroke (cairo_t *cr, bool preserve)
913 double opacity = GetOpacity ();
915 if (IS_INVISIBLE (opacity)) {
916 if (!preserve)
917 cairo_new_path (cr);
918 return;
921 if (!IS_TRANSLUCENT (opacity)) {
922 Brush::Stroke (cr, preserve);
923 return;
926 cairo_save (cr);
927 cairo_push_group_with_content (cr, CAIRO_CONTENT_ALPHA);
928 cairo_set_source_rgba (cr, 1.0, 1.0, 1.0, opacity);
929 cairo_stroke (cr);
931 cairo_pattern_t *mask = cairo_pop_group (cr);
932 cairo_restore (cr);
933 if (cairo_pattern_status (mask) == CAIRO_STATUS_SUCCESS) {
934 cairo_mask (cr, mask);
936 cairo_pattern_destroy (mask);
938 if (!preserve)
939 cairo_new_path (cr);
943 // VideoBrush
946 VideoBrush::VideoBrush ()
948 SetObjectType (Type::VIDEOBRUSH);
949 media = NULL;
952 VideoBrush::~VideoBrush ()
954 if (media != NULL) {
955 media->RemovePropertyChangeListener (this);
956 media->RemoveHandler (MediaElement::MediaInvalidatedEvent, update_brush, this);
957 media->unref ();
961 void
962 VideoBrush::SetupBrush (cairo_t *cr, const Rect &area)
964 Stretch stretch = GetStretch ();
965 if (!is_stretch_valid (stretch)) {
966 // bad enum value for stretch, nothing should be drawn
967 // XXX Removing this _source_set at all?
968 cairo_set_source_rgba (cr, 0.0, 0.0, 0.0, 0.0);
969 return;
972 MediaPlayer *mplayer = media ? media->GetMediaPlayer () : NULL;
973 Transform *transform = GetTransform ();
974 Transform *relative_transform = GetRelativeTransform ();
975 AlignmentX ax = GetAlignmentX ();
976 AlignmentY ay = GetAlignmentY ();
977 cairo_surface_t *surface;
978 cairo_pattern_t *pattern;
979 cairo_matrix_t matrix;
981 if (media == NULL) {
982 DependencyObject *obj;
983 const char *name;
985 name = GetSourceName ();
987 if (name == NULL || *name == '\0')
988 return;
990 if ((obj = FindName (name)) && obj->Is (Type::MEDIAELEMENT)) {
991 obj->AddPropertyChangeListener (this);
992 media = (MediaElement *) obj;
993 media->AddHandler (MediaElement::MediaInvalidatedEvent, update_brush, this);
994 mplayer = media->GetMediaPlayer ();
995 obj->ref ();
996 } else if (obj == NULL) {
997 printf ("could not find element `%s'\n", name);
998 } else {
999 printf ("obj %p is not of type MediaElement (it is %s)\n", obj,
1000 obj->GetTypeName ());
1004 if (!mplayer || !(surface = mplayer->GetCairoSurface ())) {
1005 // not yet available, draw gray-ish shadow where the brush should be applied
1006 cairo_set_source_rgba (cr, 0.5, 0.5, 0.5, 0.5);
1007 return;
1010 pattern = cairo_pattern_create_for_surface (surface);
1011 cairo_filter_t filter;
1012 switch (media ? media->GetQualityLevel (0, 3) : 0) {
1013 case 0: filter = CAIRO_FILTER_FAST; break;
1014 case 1: filter = CAIRO_FILTER_GOOD; break;
1015 case 2: filter = CAIRO_FILTER_BILINEAR; break;
1016 default: filter = CAIRO_FILTER_BEST; break;
1018 cairo_pattern_set_filter (pattern, filter);
1020 image_brush_compute_pattern_matrix (&matrix, area.width, area.height, mplayer->GetVideoWidth (),
1021 mplayer->GetVideoHeight (), stretch, ax, ay,
1022 transform, relative_transform);
1024 cairo_matrix_translate (&matrix, -area.x, -area.y);
1025 cairo_pattern_set_matrix (pattern, &matrix);
1027 if (cairo_pattern_status (pattern) == CAIRO_STATUS_SUCCESS)
1028 cairo_set_source (cr, pattern);
1029 else
1030 cairo_set_source_rgba (cr, 0.0, 0.0, 0.0, 0.0);
1032 cairo_pattern_destroy (pattern);
1035 void
1036 VideoBrush::OnPropertyChanged (PropertyChangedEventArgs *args, MoonError *error)
1038 if (args->GetProperty ()->GetOwnerType() != Type::VIDEOBRUSH) {
1039 TileBrush::OnPropertyChanged (args, error);
1040 return;
1043 if (args->GetId () == VideoBrush::SourceNameProperty) {
1044 char *name = args->GetNewValue() ? args->GetNewValue()->AsString () : NULL;
1045 DependencyObject *obj;
1047 if (media != NULL) {
1048 media->RemovePropertyChangeListener (this);
1049 media->RemoveHandler (MediaElement::MediaInvalidatedEvent, update_brush, this);
1050 media->unref ();
1051 media = NULL;
1054 if (name && (obj = FindName (name)) && obj->Is (Type::MEDIAELEMENT)) {
1055 obj->AddPropertyChangeListener (this);
1056 media = (MediaElement *) obj;
1057 media->AddHandler (MediaElement::MediaInvalidatedEvent, update_brush, this);
1058 obj->ref ();
1059 } else {
1060 // Note: This may have failed because the parser hasn't set the
1061 // toplevel element yet, we'll try again in SetupBrush()
1065 NotifyListenersOfPropertyChange (args, error);
1068 void
1069 VideoBrush::OnSubPropertyChanged (DependencyProperty *prop, DependencyObject *obj, PropertyChangedEventArgs *subobj_args)
1071 /* this is being handled in the base class */
1073 if (subobj_args->GetId () == MediaElement::PositionProperty) {
1074 // We to changes in this MediaElement property so we
1075 // can notify whoever is using us to paint that they
1076 // need to redraw themselves.
1077 NotifyListenersOfPropertyChange (Brush::ChangedProperty);
1081 TileBrush::OnSubPropertyChanged (prop, obj, subobj_args);
1084 void
1085 VideoBrush::SetSource (MediaElement *source)
1087 if (source) {
1088 source->ref ();
1089 source->AddHandler (MediaElement::MediaInvalidatedEvent, update_brush, this);
1092 SetSourceName ("");
1094 if (media != NULL) {
1095 media->RemovePropertyChangeListener (this);
1096 media->RemoveHandler (MediaElement::MediaInvalidatedEvent, update_brush, this);
1097 media->unref ();
1098 media = NULL;
1101 media = source;
1104 bool
1105 VideoBrush::IsOpaque ()
1107 // XXX punt for now and return false here.
1108 return false;
1111 bool
1112 VideoBrush::IsAnimating ()
1114 if (media && media->IsPlaying ())
1115 return true;
1117 return TileBrush::IsAnimating ();
1120 void
1121 VideoBrush::update_brush (EventObject *, EventArgs *, gpointer closure)
1123 VideoBrush *b = (VideoBrush*)closure;
1124 b->NotifyListenersOfPropertyChange (Brush::ChangedProperty, NULL);
1128 // VisualBrush
1131 VisualBrush::VisualBrush ()
1133 SetObjectType (Type::VISUALBRUSH);
1136 VisualBrush::~VisualBrush ()
1140 void
1141 VisualBrush::SetupBrush (cairo_t *cr, const Rect &area)
1143 UIElement *ui = (UIElement *) GetVisual ();
1144 if (!ui) {
1145 // not yet available, draw gray-ish shadow where the brush should be applied
1146 cairo_set_source_rgba (cr, 0.5, 0.5, 0.5, 0.5);
1147 return;
1150 // XXX we should cache the surface so that it can be
1151 // used multiple times without having to re-render each time.
1152 Rect bounds = ui->GetSubtreeBounds().RoundOut ();
1154 surface = image_brush_create_similar (cr, (int) bounds.width, (int) bounds.height);
1156 cairo_t *surface_cr = cairo_create (surface);
1157 Region region = Region (0, 0, bounds.width, bounds.height);
1158 ui->Render (surface_cr, &region);
1159 cairo_destroy (surface_cr);
1161 Stretch stretch = GetStretch ();
1163 AlignmentX ax = GetAlignmentX ();
1164 AlignmentY ay = GetAlignmentY ();
1166 Transform *transform = GetTransform ();
1167 Transform *relative_transform = GetRelativeTransform ();
1169 cairo_pattern_t *pattern = cairo_pattern_create_for_surface (surface);
1170 cairo_matrix_t matrix;
1171 image_brush_compute_pattern_matrix (&matrix, area.width, area.height,
1172 (int) bounds.width, (int) bounds.height,
1173 stretch, ax, ay, transform, relative_transform);
1175 cairo_matrix_translate (&matrix, -area.x, -area.y);
1176 cairo_pattern_set_matrix (pattern, &matrix);
1178 cairo_set_source (cr, pattern);
1179 cairo_pattern_destroy (pattern);
1181 cairo_surface_destroy (surface);
1184 void
1185 VisualBrush::update_brush (EventObject *, EventArgs *, gpointer closure)
1187 VisualBrush *b = (VisualBrush*)closure;
1188 b->NotifyListenersOfPropertyChange (Brush::ChangedProperty, NULL);
1191 void
1192 VisualBrush::OnPropertyChanged (PropertyChangedEventArgs *args, MoonError *error)
1194 if (args->GetProperty ()->GetOwnerType() != Type::VISUALBRUSH) {
1195 TileBrush::OnPropertyChanged (args, error);
1196 return;
1199 if (args->GetId () == VisualBrush::VisualProperty) {
1200 // XXX we really need a way to disconnect from the preview visual
1201 UIElement *v = args->GetNewValue()->AsUIElement();
1202 v->AddHandler (((UIElement*)v)->InvalidatedEvent, update_brush, this);
1205 NotifyListenersOfPropertyChange (args, error);
1208 bool
1209 VisualBrush::IsOpaque ()
1211 // XXX punt for now and return false here.
1212 return false;