2009-12-07 Rolf Bjarne Kvinge <RKvinge@novell.com>
[moon.git] / src / multiscaleimage.cpp
blobe3e11c792408a92be0e16957fab264c468179950
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
2 /*
3 * multiscaleimage.cpp:
5 * Contact:
6 * Moonlight List (moonlight-list@lists.ximian.com)
8 * Copyright 2008-2009 Novell, Inc. (http://www.novell.com)
10 * See the LICENSE file included with the distribution for details.
13 //TODO
15 //- only invalidate regions
16 //- only render changed regions
18 #include <config.h>
20 #include <string.h>
21 #include <sys/types.h>
22 #include <sys/stat.h>
23 #include <unistd.h>
24 #include <errno.h>
25 #include <math.h>
27 #include "cbinding.h"
28 #include "multiscaleimage.h"
29 #include "tilesource.h"
30 #include "deepzoomimagetilesource.h"
31 #include "file-downloader.h"
32 #include "multiscalesubimage.h"
33 #include "bitmapimage.h"
34 #include "ptr.h"
36 #if LOGGING
37 #include "clock.h"
38 #define MSI_STARTTIMER(id) if (G_UNLIKELY (debug_flags & RUNTIME_DEBUG_MSI)) TimeSpan id##_t_start = get_now()
39 #define MSI_ENDTIMER(id,str) if (G_UNLIKELY (debug_flags & RUNTIME_DEBUG_MSI)) TimeSpan id##_t_end = get_now(); printf ("timing of '%s' ended took (%f ms)\n", str, id##_t_end, (double)(id##_t_end - id##_t_start) / 10000)
40 #else
41 #define STATTIMER(id)
42 #define ENDTIMER(id,str)
43 #endif
45 inline
46 guint64 pow2(int pow) {
47 return ((guint64) 1 << pow);
51 * Q(uad)Tree.
54 struct QTree {
55 bool has_value;
56 void *data;
57 QTree* l0; //N-E
58 QTree* l1; //N-W
59 QTree* l2; //S-E
60 QTree* l3; //S-W
61 QTree* parent;
64 static QTree*
65 qtree_new (void)
67 return g_new0 (QTree, 1);
70 static QTree*
71 qtree_insert (QTree* root, int level, guint64 x, guint64 y)
73 if (x >= (pow2 (level)) || y >= (pow2 (level))) {
74 #if DEBUG
75 abort ();
76 #endif
77 g_warning ("QuadTree index out of range.");
78 return NULL;
81 if (!root) {
82 g_warning ("passing a NULL QTree to qtree_insert");
83 return NULL;
86 QTree *node = root;
87 while (level-- > 0) {
88 if (y < (pow2 (level))) {
89 if (x < (pow2 (level))) {
90 if (!node->l0) {
91 node->l0 = g_new0 (QTree, 1);
92 node->l0->parent = node;
94 node = node->l0;
95 } else {
96 if (!node->l1) {
97 node->l1 = g_new0 (QTree, 1);
98 node->l1->parent = node;
100 node = node->l1;
101 x -= (pow2 (level));
103 } else {
104 if (x < (pow2 (level))) {
105 if (!node->l2) {
106 node->l2 = g_new0 (QTree, 1);
107 node->l2->parent = node;
109 node = node->l2;
110 y -= (pow2 (level));
111 } else {
112 if (!node->l3) {
113 node->l3 = g_new0 (QTree, 1);
114 node->l3->parent = node;
116 node = node->l3;
117 x -= (pow2 (level));
118 y -= (pow2 (level));
122 return node;
125 static void
126 qtree_set_value (QTree* node, void *data)
128 //FIXME: the destroy method should be a ctor argument
129 if (node->has_value && node->data)
130 cairo_surface_destroy ((cairo_surface_t*)node->data);
132 node->has_value = true;
133 node->data = data;
136 static QTree *
137 qtree_lookup (QTree* root, int level, guint64 x, guint64 y)
139 if (x >= (pow2 (level)) || y >= (pow2 (level))) {
140 #if DEBUG
141 // we seem to run into an infinite loop sporadically here for drt #2014 completely spamming the test output.
142 // abort to get a stack trace.
143 abort ();
144 #endif
145 g_warning ("QuadTree index out of range.");
146 return NULL;
149 while (level-- > 0) {
150 if (!root)
151 return NULL;
153 if (y < (pow2 (level))) {
154 if (x < (pow2 (level))) {
155 root = root->l0;
156 } else {
157 root = root->l1;
158 x -= (pow2 (level));
160 } else {
161 if (x < (pow2 (level))) {
162 root = root->l2;
163 y -= (pow2 (level));
164 } else {
165 root = root->l3;
166 x -= (pow2 (level));
167 y -= (pow2 (level));
171 return root;
174 static void *
175 qtree_lookup_data (QTree* root, int level, guint64 x, guint64 y)
177 QTree *node = qtree_lookup (root, level, x, y);
178 if (node && node->has_value)
179 return node->data;
180 return NULL;
183 //FIXME: merge qtree_next_sibling and _qtree_next_sibling in a single
184 //function, with an elegant loop to avoid recursion.
185 static QTree*
186 _qtree_next_sibling (QTree *node, guint64 *i, guint64 *j, int l)
188 if (!node) {
189 #if DEBUG
190 abort ();
191 #endif
192 g_warning ("Empty node");
193 return NULL;
196 if (!node->parent) //no parent, we're probably at the root
197 return NULL;
199 if (node == node->parent->l0) {
200 *i += pow2 (l);
201 return node->parent->l1;
203 if (node == node->parent->l1) {
204 *i -= pow2 (l);
205 *j += pow2 (l);
206 return node->parent->l2;
208 if (node == node->parent->l2) {
209 *i += pow2 (l);
210 return node->parent->l3;
212 if (node == node->parent->l3) {
213 *i -= pow2 (l);
214 *j -= pow2 (l);
215 QTree *next_parent = _qtree_next_sibling (node->parent, i, j, l + 1);
216 if (!next_parent)
217 return NULL;
218 return next_parent->l0;
220 #if DEBUG
221 abort ();
222 #endif
223 g_warning ("Broken parent link, this is bad");
224 return NULL;
227 static void
228 qtree_remove (QTree* node, int depth)
230 if (node && node->has_value) {
231 node->has_value = false;
232 if (node->data) {
233 cairo_surface_destroy ((cairo_surface_t*)node->data);
234 node->data = NULL;
238 if (depth <= 0)
239 return;
241 qtree_remove (node->l0, depth - 1);
242 qtree_remove (node->l1, depth - 1);
243 qtree_remove (node->l2, depth - 1);
244 qtree_remove (node->l3, depth - 1);
248 static void
249 qtree_remove_at (QTree* root, int level, guint64 x, guint64 y, int depth)
251 QTree* node = qtree_lookup (root, level, x, y);
252 qtree_remove (node, depth);
255 static inline bool
256 qtree_has_value (QTree* node)
258 return node->has_value;
261 static void
262 qtree_destroy (QTree *root)
264 if (!root)
265 return;
267 //FIXME: the destroy func should be a qtree ctor option
268 if (root->data) {
269 cairo_surface_destroy ((cairo_surface_t*)(root->data));
270 root->data = NULL;
273 qtree_destroy (root->l0);
274 qtree_destroy (root->l1);
275 qtree_destroy (root->l2);
276 qtree_destroy (root->l3);
277 g_free (root);
281 * BitmapImageContext
284 enum BitmapImageStatus {
285 BitmapImageFree = 0,
286 BitmapImageBusy,
287 BitmapImageDone
290 struct BitmapImageContext
292 BitmapImageStatus state;
293 BitmapImage *bitmapimage;
294 QTree *node;
295 int retry;
299 * Morton layout
302 #if 0
303 static void
304 morton (int n, int *x, int *y) {
305 n = (n & 0x99999999) + ((n & 0x22222222) << 1) + ((n & 0x44444444) >> 1);
306 n = (n & 0xc3c3c3c3) + ((n & 0x0c0c0c0c) << 2) + ((n & 0x30303030) >> 2);
307 n = (n & 0xf00ff00f) + ((n & 0x00f000f0) << 4) + ((n & 0x0f000f00) >> 4);
308 n = (n & 0xff0000ff) + ((n & 0x0000ff00) << 8) + ((n & 0x00ff0000) >> 8);
309 *x = n & 0x0000ffff;
310 *y = n >> 16;
312 #endif
314 static inline int
315 morton_x (int n)
317 n = (n & 0x11111111) + ((n & 0x44444444) >> 1);
318 n = (n & 0x03030303) + ((n & 0x30303030) >> 2);
319 n = (n & 0x000f000f) + ((n & 0x0f000f00) >> 4);
320 return (n & 0x000000ff) + ((n & 0x00ff0000) >> 8);
323 static inline int
324 morton_y (int n)
326 n = (n & 0x88888888) + ((n & 0x22222222) << 1);
327 n = (n & 0xc0c0c0c0) + ((n & 0x0c0c0c0c) << 2);
328 n = (n & 0xf000f000) + ((n & 0x00f000f0) << 4);
329 n = (n & 0xff000000) + ((n & 0x0000ff00) << 8);
331 return n >> 16;
336 * MultiScaleImage
339 MultiScaleImage::MultiScaleImage () :
340 subimages_sorted(false),
341 pending_motion_completed(false),
342 bitmapimages(NULL),
343 is_fading(false),
344 is_zooming(false),
345 is_panning(false)
347 // static bool init = true;
348 // if (init) {
349 // init = false;
350 // MultiScaleImage::SubImagesProperty->SetValueValidator (MultiScaleSubImageCollectionValidator);
351 // }
352 providers [PropertyPrecedence_DynamicValue] = new MultiScaleImagePropertyValueProvider (this, PropertyPrecedence_DynamicValue);
354 SetObjectType (Type::MULTISCALEIMAGE);
355 cache = g_hash_table_new_full (g_int_hash, g_int_equal, g_free, (GDestroyNotify)qtree_destroy);
358 MultiScaleImage::~MultiScaleImage ()
360 StopDownloading ();
361 if (cache)
362 g_hash_table_destroy (cache);
363 cache = NULL;
366 void
367 MultiScaleImage::ZoomAboutLogicalPoint (double zoomIncrementFactor, double zoomCenterLogicalX, double zoomCenterLogicalY)
369 LOG_MSI ("\nzoomabout logical %f (%f, %f)\n", zoomIncrementFactor, zoomCenterLogicalX, zoomCenterLogicalY);
371 if (zoom_sb)
372 zoom_sb->PauseWithError (NULL);
373 if (pan_sb)
374 pan_sb->PauseWithError (NULL);
376 double viewport_width;
377 Point viewport_origin;
379 if (GetUseSprings () && zoom_sb && pan_sb) {
380 viewport_width = zoom_target;
381 viewport_origin = pan_target;
382 } else {
383 viewport_width = GetViewportWidth ();
384 viewport_origin = *GetViewportOrigin ();
387 double width = viewport_width / zoomIncrementFactor;
388 SetViewportWidth (width);
389 if (!isnan(zoomCenterLogicalX) && !isnan(zoomCenterLogicalY)) {
390 SetViewportOrigin (new Point (zoomCenterLogicalX - (zoomCenterLogicalX - viewport_origin.x) / zoomIncrementFactor,
391 zoomCenterLogicalY - (zoomCenterLogicalY - viewport_origin.y) / zoomIncrementFactor));
395 Point
396 MultiScaleImage::ElementToLogicalPoint (Point elementPoint)
398 Point *vp_origin = GetViewportOrigin ();
399 double vp_width = GetViewportWidth ();
400 double actual_width = GetActualWidth ();
401 return Point (vp_origin->x + (double)elementPoint.x * vp_width / actual_width,
402 vp_origin->y + (double)elementPoint.y * vp_width / actual_width);
405 Point
406 MultiScaleImage::LogicalToElementPoint (Point logicalPoint)
408 Point *vp_origin = GetViewportOrigin ();
409 double vp_width = GetViewportWidth ();
410 double actual_width = GetActualWidth ();
411 return Point ((logicalPoint.x - vp_origin->x) * actual_width / vp_width,
412 (logicalPoint.y - vp_origin->y) * actual_width / vp_width);
415 void
416 MultiScaleImage::DownloadTile (BitmapImageContext *bictx, Uri *tile, void *user_data)
418 GList *list;
419 BitmapImageContext *ctx;
420 for (list = g_list_first (bitmapimages); list && (ctx = (BitmapImageContext *)list->data); list = list->next) {
421 if (ctx->state != BitmapImageFree && ctx->bitmapimage->GetUriSource()->operator==(*tile)) {
422 //LOG_MSI ("Tile %s is already being downloaded\n", tile->ToString ());
423 return;
427 //LOG_MSI ("downloading tile %s\n", tile->ToString ());
429 bictx->state = BitmapImageBusy;
430 bictx->node = (QTree *)user_data;
431 bictx->retry = 0;
432 SetIsDownloading (true);
433 bictx->bitmapimage->SetDownloadPolicy (MsiPolicy);
434 bictx->bitmapimage->SetUriSource (tile);
437 //Only used for DeepZoom sources
438 void
439 MultiScaleImage::HandleDzParsed ()
441 //if the source is a collection, fill the subimages list
442 MultiScaleTileSource *source = GetSource ();
443 MultiScaleSubImageCollection *subs = GetSubImages ();
445 if (source->GetImageWidth () >= 0 && source->GetImageHeight () >= 0)
446 SetValue (MultiScaleImage::AspectRatioProperty, Value ((double)source->GetImageWidth () / (double)source->GetImageHeight ()));
448 DeepZoomImageTileSource *dsource;
450 if (source->Is (Type::DEEPZOOMIMAGETILESOURCE) &&
451 (dsource = (DeepZoomImageTileSource *)source)) {
452 int i;
453 MultiScaleSubImage *si;
454 for (i = 0; (si = (MultiScaleSubImage*)g_list_nth_data (dsource->subimages, i)); i++) {
455 if (!subs)
456 SetValue (MultiScaleImage::SubImagesProperty, new MultiScaleSubImageCollection ());
458 subs->Add (si);
461 Invalidate ();
463 //Get the first tiles
464 BitmapImageContext *bitmapimagectx;
466 int shared_index = -1;
468 QTree *shared_cache = (QTree*)g_hash_table_lookup (cache, &shared_index);
469 if (!shared_cache)
470 g_hash_table_insert (cache, new int(shared_index), (shared_cache = qtree_new ()));
472 int layer = 0;
473 while ((bitmapimagectx = GetFreeBitmapImageContext ())) {
474 Uri *tile = new Uri ();
475 if (source->get_tile_func (layer, 0, 0, tile, source)) {
476 QTree *node = qtree_insert (shared_cache, layer, 0, 0);
477 DownloadTile (bitmapimagectx, tile, node);
479 delete tile;
480 layer ++;
483 EmitImageOpenSucceeded ();
486 static void
487 fade_finished (EventObject *sender, EventArgs *calldata, gpointer closure)
489 MultiScaleImage *msi = (MultiScaleImage *) closure;
490 msi->FadeFinished ();
493 void
494 MultiScaleImage::FadeFinished ()
496 is_fading = false;
497 if (!is_fading && !is_zooming && !is_panning)
498 EmitMotionFinished ();
501 static void
502 zoom_finished (EventObject *sender, EventArgs *calldata, gpointer closure)
504 MultiScaleImage *msi = (MultiScaleImage *) closure;
505 msi->ZoomFinished ();
508 void
509 MultiScaleImage::ZoomFinished ()
511 is_zooming = false;
512 if (!is_fading && !is_zooming && !is_panning)
513 EmitMotionFinished ();
516 static void
517 pan_finished (EventObject *sender, EventArgs *calldata, gpointer closure)
519 MultiScaleImage *msi = (MultiScaleImage *) closure;
520 msi->PanFinished ();
523 void
524 MultiScaleImage::PanFinished ()
526 is_panning = false;
527 if (!is_fading && !is_zooming && !is_panning)
528 EmitMotionFinished ();
531 void
532 tile_available (EventObject *sender, EventArgs *calldata, gpointer closure)
534 // LOG_MSI ("Tile downloaded %s\n", ((BitmapImage *)sender)->GetUriSource ()->ToString ());
535 ((MultiScaleImage *)closure)->TileOpened ((BitmapImage *)sender);
538 void
539 MultiScaleImage::TileOpened (BitmapImage *bitmapimage)
541 BitmapImageContext *ctx = GetBitmapImageContext (bitmapimage);
542 ctx->state = BitmapImageDone;
543 GList *list;
544 bool is_downloading = false;
545 for (list = g_list_first (bitmapimages); list && (ctx = (BitmapImageContext *)list->data); list = list->next)
546 is_downloading |= (ctx->state == BitmapImageBusy);
547 SetIsDownloading (is_downloading);
548 Invalidate ();
551 void
552 tile_failed (EventObject *sender, EventArgs *calldata, gpointer closure)
554 // LOG_MSI ("Failed to download tile %s\n", ((BitmapImage *)sender)->GetUriSource ()->ToString ());
555 ((MultiScaleImage *)closure)->TileFailed ((BitmapImage *)sender);
558 void
559 MultiScaleImage::TileFailed (BitmapImage *bitmapimage)
561 BitmapImageContext *ctx = GetBitmapImageContext (bitmapimage);
562 if (ctx->retry < 5) {
563 bitmapimage->SetUriSource (bitmapimage->GetUriSource ());
564 ctx->retry = ctx->retry + 1;
565 } else {
566 ctx->state = BitmapImageFree;
568 LOG_MSI ("caching a NULL for %s\n", ctx->bitmapimage->GetUriSource()->ToString ());
569 qtree_set_value (ctx->node, NULL);
571 GList *list;
572 bool is_downloading = false;
573 for (list = g_list_first (bitmapimages); list && (ctx = (BitmapImageContext *)list->data); list = list->next)
574 is_downloading |= (ctx->state == BitmapImageBusy);
575 SetIsDownloading (is_downloading);
577 Invalidate ();
578 EmitImageFailed ();
581 BitmapImageContext *
582 MultiScaleImage::GetBitmapImageContext (BitmapImage *bitmapimage)
584 BitmapImageContext *ctx;
585 GList *list;
586 for (list = g_list_first (bitmapimages); list && (ctx = (BitmapImageContext *)list->data); list = list->next)
587 if (ctx->bitmapimage == bitmapimage)
588 return ctx;
589 return NULL;
592 void
593 MultiScaleImage::StopDownloading ()
595 BitmapImageContext *ctx;
596 GList *list;
597 for (list = g_list_first (bitmapimages); list && (ctx = (BitmapImageContext *)list->data); list = list->next) {
598 ctx->bitmapimage->Abort ();
599 ctx->bitmapimage->Dispose ();
600 ctx->bitmapimage->unref ();
601 ctx->state = BitmapImageFree;
602 delete ctx;
605 if (bitmapimages)
606 g_list_free (bitmapimages);
607 bitmapimages = NULL;
610 BitmapImageContext *
611 MultiScaleImage::GetFreeBitmapImageContext ()
613 guint num_dl = 6;
614 BitmapImageContext *ctx;
615 GList *list;
616 for (list = g_list_first (bitmapimages); list && (ctx = (BitmapImageContext *)list->data); list = list->next)
617 if (ctx->state == BitmapImageFree)
618 return ctx;
620 if (g_list_length (bitmapimages) < num_dl) {
621 ctx = new BitmapImageContext ();
622 ctx->state = BitmapImageFree;
623 ctx->bitmapimage = new BitmapImage ();
624 ctx->bitmapimage->AddHandler (ctx->bitmapimage->ImageOpenedEvent, tile_available, this);
625 ctx->bitmapimage->AddHandler (ctx->bitmapimage->ImageFailedEvent, tile_failed, this);
626 bitmapimages = g_list_append (bitmapimages, ctx);
627 return ctx;
630 return NULL;
633 void
634 MultiScaleImage::Render (cairo_t *cr, Region *region, bool path_only)
636 LOG_MSI ("MSI::Render\n");
638 MultiScaleTileSource *source = GetSource ();
639 if (!source) {
640 LOG_MSI ("no sources set, nothing to render\n");
641 return;
644 //Process the downloaded tile
645 GList *list;
646 BitmapImageContext *ctx;
647 for (list = g_list_first (bitmapimages); list && (ctx = (BitmapImageContext *)list->data); list = list->next) {
648 cairo_surface_t *surface;
650 if (ctx->state != BitmapImageDone || !(surface = cairo_surface_reference (ctx->bitmapimage->GetSurface (cr))))
651 continue;
653 // Uri *tile = ctx->bitmapimage->GetUriSource ();
654 cairo_surface_set_user_data (surface, &width_key, new int (ctx->bitmapimage->GetPixelWidth ()), g_free);
655 cairo_surface_set_user_data (surface, &height_key, new int (ctx->bitmapimage->GetPixelHeight ()), g_free);
657 if (!fadein_sb) {
658 fadein_sb = new Storyboard ();
659 fadein_sb->SetManualTarget (this);
660 fadein_sb->SetTargetProperty (fadein_sb, new PropertyPath ("(MultiScaleImage.TileFade)"));
661 fadein_animation = new DoubleAnimation ();
662 fadein_animation->SetDuration (Duration (source->GetTileBlendTime ()));
663 TimelineCollection *tlc = new TimelineCollection ();
664 tlc->Add (static_cast<DoubleAnimation*> (fadein_animation));
665 fadein_sb->SetChildren(tlc);
666 fadein_sb->AddHandler (Storyboard::CompletedEvent, fade_finished, this);
667 #if DEBUG
668 fadein_sb->SetName ("Multiscale Fade-In");
669 #endif
670 } else {
671 fadein_sb->PauseWithError (NULL);
674 //LOG_MSI ("animating Fade from %f to %f\n\n", GetValue(MultiScaleImage::TileFadeProperty)->AsDouble(), GetValue(MultiScaleImage::TileFadeProperty)->AsDouble() + 0.9);
675 double *to = new double (GetValue(MultiScaleImage::TileFadeProperty)->AsDouble() + 0.9);
676 fadein_animation->SetFrom (GetValue(MultiScaleImage::TileFadeProperty)->AsDouble());
677 fadein_animation->SetTo (*to);
679 is_fading = true;
681 fadein_sb->BeginWithError(NULL);
683 cairo_surface_set_user_data (surface, &full_opacity_at_key, to, g_free);
684 LOG_MSI ("caching %s\n", ctx->bitmapimage->GetUriSource()->ToString ());
685 qtree_set_value (ctx->node, surface);
687 ctx->bitmapimage->SetUriSource (NULL);
688 ctx->state = BitmapImageFree;
691 bool is_collection = source &&
692 source->Is (Type::DEEPZOOMIMAGETILESOURCE) &&
693 ((DeepZoomImageTileSource *)source)->IsCollection () &&
694 GetSubImages ();
696 if (source->GetImageWidth () < 0 && !is_collection) {
697 LOG_MSI ("nothing to render so far...\n");
698 if (source->Is (Type::DEEPZOOMIMAGETILESOURCE)) {
699 ((DeepZoomImageTileSource*)source)->set_callbacks (multi_scale_image_handle_dz_parsed, multi_scale_image_emit_image_open_failed, multi_scale_image_on_source_property_changed, this);
700 ((DeepZoomImageTileSource*)source)->Download ();
702 return;
705 #if DEBUG
706 if (!source->get_tile_func) {
707 g_warning ("no get_tile_func set\n");
708 return;
710 #endif
712 if (is_collection)
713 RenderCollection (cr, region);
714 else
715 RenderSingle (cr, region);
718 void
719 MultiScaleImage::RenderCollection (cairo_t *cr, Region *region)
721 LOG_MSI ("\nMSI::RenderCollection\n");
723 double msi_w = GetActualWidth ();
724 double msi_h = GetActualHeight ();
725 double msi_ar = GetAspectRatio();
726 double msivp_ox = GetViewportOrigin()->x;
727 double msivp_oy = GetViewportOrigin()->y;
728 double msivp_w = GetViewportWidth();
730 if (!GetSource ()->Is (Type::DEEPZOOMIMAGETILESOURCE)) {
731 g_warning ("RenderCollection called for a non deepzoom tile source. this should not happen");
732 return;
734 DeepZoomImageTileSource *dzits = (DeepZoomImageTileSource *)GetSource ();
736 Rect viewport = Rect (msivp_ox, msivp_oy, msivp_w, msivp_w/msi_ar);
738 MultiScaleSubImageCollection *subs = GetSubImages ();
739 if (!subimages_sorted) {
740 subs->ResortByZIndex ();
741 subimages_sorted = true;
744 //using the "-1" index for the shared cache
745 int shared_index = -1;
746 QTree *shared_cache = (QTree*)g_hash_table_lookup (cache, &shared_index);
747 if (!shared_cache)
748 g_hash_table_insert (cache, new int(shared_index), (shared_cache = qtree_new ()));
750 int i;
751 for (i = 0; i < subs->GetCount (); i++) {
752 MultiScaleSubImage *sub_image = (MultiScaleSubImage*)g_ptr_array_index (subs->z_sorted, i);
754 int index = sub_image->GetId();
755 QTree *subimage_cache = (QTree*)g_hash_table_lookup (cache, &index);
756 if (!subimage_cache)
757 g_hash_table_insert (cache, new int(index), (subimage_cache = qtree_new ()));
759 double subvp_ox = sub_image->GetViewportOrigin()->x;
760 double subvp_oy = sub_image->GetViewportOrigin()->y;
761 double subvp_w = sub_image->GetViewportWidth();
762 double sub_w = sub_image->source->GetImageWidth ();
763 double sub_h = sub_image->source->GetImageHeight ();
764 double sub_ar = sub_image->GetAspectRatio();
767 //expressing the subimage viewport in main viewport coordinates.
768 Rect sub_vp = Rect (-subvp_ox / subvp_w, -subvp_oy / subvp_w, 1.0/subvp_w, 1.0/(sub_ar * subvp_w));
770 //render only if the subimage viewport intersects with this viewport
771 if (!sub_vp.IntersectsWith (viewport))
772 continue;
773 LOG_MSI ("Intersects with main viewport...rendering\n");
775 int layers;
776 if (frexp (MAX (sub_w, sub_h), &layers) == 0.5)
777 layers --;
779 int optimal_layer;
780 frexp (msi_w / (subvp_w * msivp_w * MIN (1.0, sub_ar)), &optimal_layer);
781 optimal_layer = MIN (optimal_layer, layers);
782 LOG_MSI ("number of layers: %d\toptimal layer for this: %d\n", layers, optimal_layer);
784 int to_layer = -1;
785 int from_layer = optimal_layer;
786 while (from_layer >= 0) {
787 int count = 0;
788 int found = 0;
789 bool blending = FALSE; //means at least a tile is not yet fully blended
791 int tile_width = (from_layer > dzits->GetMaxLevel () && ((DeepZoomImageTileSource*)sub_image->source)->IsParsed ()) ? sub_image->source->GetTileWidth () : dzits->GetTileWidth ();
792 int tile_height = (from_layer > dzits->GetMaxLevel () && ((DeepZoomImageTileSource*)sub_image->source)->IsParsed ()) ? sub_image->source->GetTileHeight (): dzits->GetTileHeight ();
794 //in msi relative coord
795 double v_tile_w = tile_width * (double) (pow2 (layers - from_layer)) * sub_vp.width / sub_w;
796 double v_tile_h = tile_height * (double)(pow2 (layers - from_layer)) * sub_vp.width / sub_w;
797 //LOG_MSI ("virtual tile size at layer %d; %fx%f\n", from_layer, v_tile_w, v_tile_h);
799 guint64 i, j;
800 for (i = (int)((MAX(msivp_ox, sub_vp.x) - sub_vp.x)/v_tile_w); i * v_tile_w < MIN(msivp_ox + msivp_w, sub_vp.x + sub_vp.width) - sub_vp.x;i++) {
801 for (j = (int)((MAX(msivp_oy, sub_vp.y) - sub_vp.y)/v_tile_h); j * v_tile_h < MIN(msivp_oy + msivp_w/msi_ar, sub_vp.y + sub_vp.width/sub_ar) - sub_vp.y;j++) {
802 count++;
803 cairo_surface_t* image = NULL;
806 if (from_layer > dzits->GetMaxLevel ()) {
807 if ((image = (cairo_surface_t*)qtree_lookup_data (subimage_cache, from_layer, i, j)))
808 found ++;
809 } else if ((image = (cairo_surface_t*)qtree_lookup_data (shared_cache, from_layer,
810 morton_x (sub_image->n) * (pow2 (from_layer)) / tile_width,
811 morton_y (sub_image->n) * (pow2 (from_layer)) / tile_height)))
812 found ++;
814 if (image && *(double*)(cairo_surface_get_user_data (image, &full_opacity_at_key)) > GetValue(MultiScaleImage::TileFadeProperty)->AsDouble ())
815 blending = TRUE;
818 if (found > 0 && to_layer < from_layer)
819 to_layer = from_layer;
820 if (found == count && (!blending || from_layer == 0))
821 break;
823 from_layer --;
826 //render here
827 LOG_MSI ("rendering layers from %d to %d\n", from_layer, to_layer);
828 double fade = GetValue (MultiScaleImage::TileFadeProperty)->AsDouble();
829 if (from_layer >= 0) {
831 cairo_save (cr);
832 cairo_rectangle (cr, 0, 0, msi_w, msi_h);
833 cairo_clip (cr);
834 cairo_scale (cr, msi_w / msivp_w, msi_w / msivp_w); //scale to widget
836 cairo_translate (cr,
837 -msivp_ox + sub_vp.x,
838 -msivp_oy + sub_vp.y);
840 cairo_scale (cr,
841 sub_vp.width/sub_w,
842 sub_vp.width/sub_w);
844 cairo_rectangle (cr, 0, 0, sub_w, sub_h);
845 cairo_clip (cr);
847 if (IS_TRANSLUCENT (sub_image->GetOpacity ()))
848 cairo_push_group (cr);
850 int layer_to_render = from_layer;
851 while (layer_to_render <= to_layer) {
852 int tile_width = (from_layer > dzits->GetMaxLevel () && ((DeepZoomImageTileSource*)sub_image->source)->IsParsed ()) ?sub_image->source->GetTileWidth () : dzits->GetTileWidth ();
853 int tile_height = (from_layer > dzits->GetMaxLevel () && ((DeepZoomImageTileSource*)sub_image->source)->IsParsed ()) ? sub_image->source->GetTileHeight () : dzits->GetTileHeight ();
855 double v_tile_w = tile_width * (double)(pow2 (layers - layer_to_render)) * sub_vp.width / sub_w;
856 double v_tile_h = tile_height * (double)(pow2 (layers - layer_to_render)) * sub_vp.width / sub_w;
858 int i, j;
859 for (i = (int)((MAX(msivp_ox, sub_vp.x) - sub_vp.x)/v_tile_w); i * v_tile_w < MIN(msivp_ox + msivp_w, sub_vp.x + sub_vp.width) - sub_vp.x;i++) {
860 for (j = (int)((MAX(msivp_oy, sub_vp.y) - sub_vp.y)/v_tile_h); j * v_tile_h < MIN(msivp_oy + msivp_w/msi_ar, sub_vp.y + sub_vp.width/sub_ar) - sub_vp.y;j++) {
861 cairo_surface_t *image = NULL;
862 bool shared_tile = false;
863 if (layer_to_render > dzits->GetMaxLevel())
864 image = (cairo_surface_t*)qtree_lookup_data (subimage_cache, layer_to_render, i, j);
865 else {
866 //Check in the shared levels
867 shared_tile = true;
869 image = (cairo_surface_t*)qtree_lookup_data (shared_cache, layer_to_render,
870 morton_x(sub_image->n) * pow2 (layer_to_render) / tile_width,
871 morton_y(sub_image->n) * pow2 (layer_to_render) / tile_height);
874 if (!image)
875 continue;
877 LOG_MSI ("rendering subimage %d %d %d %d\n", sub_image->id, layer_to_render, i, j);
878 cairo_save (cr);
880 cairo_scale (cr,
881 pow2 (layers - layer_to_render),
882 pow2 (layers - layer_to_render));
884 cairo_translate (cr,
885 i * tile_width,
886 j * tile_height);
888 if (shared_tile) {
889 cairo_translate (cr,
890 (int)(-morton_x(sub_image->n) * (pow2 (layer_to_render))) % tile_width,
891 (int)(-morton_y(sub_image->n) * (pow2 (layer_to_render))) % tile_height);
895 cairo_set_source_surface (cr, image, 0, 0);
897 double *opacity = (double*)(cairo_surface_get_user_data (image, &full_opacity_at_key));
898 double combined = 1.0;
900 if (opacity && *opacity > fade)
901 combined = MIN(1.0 - *opacity + fade, 1.0);
903 if (IS_TRANSLUCENT (combined))
904 cairo_paint_with_alpha (cr, combined);
905 else
906 cairo_paint (cr);
908 cairo_restore (cr);
911 layer_to_render++;
914 if (IS_TRANSLUCENT (sub_image->GetOpacity ())) {
915 cairo_pattern_t *data = cairo_pop_group (cr);
916 if (cairo_pattern_status (data) == CAIRO_STATUS_SUCCESS) {
917 cairo_set_source (cr, data);
918 cairo_paint_with_alpha (cr, sub_image->GetOpacity ());
920 cairo_pattern_destroy (data);
923 cairo_restore (cr);
926 if (!GetAllowDownloading ())
927 continue;
929 BitmapImageContext *bitmapimagectx;
930 if (!(bitmapimagectx = GetFreeBitmapImageContext ()))
931 continue;
933 //Get the next tiles..
934 while (from_layer < optimal_layer) {
935 from_layer ++;
937 //if the subimage is unparsed, trigger the download
938 if (from_layer > dzits->GetMaxLevel () && !((DeepZoomImageTileSource *)sub_image->source)->IsDownloaded () ) {
939 ((DeepZoomImageTileSource*)sub_image->source)->set_callbacks ((void(*)(MultiScaleImage*))uielement_invalidate, multi_scale_image_emit_image_failed, NULL, this);
940 ((DeepZoomImageTileSource*)sub_image->source)->Download ();
941 break;
944 int tile_width = (from_layer > dzits->GetMaxLevel () && ((DeepZoomImageTileSource*)sub_image->source)->IsParsed ()) ?sub_image->source->GetTileWidth () : dzits->GetTileWidth ();
945 int tile_height = (from_layer > dzits->GetMaxLevel () && ((DeepZoomImageTileSource*)sub_image->source)->IsParsed ()) ? sub_image->source->GetTileHeight (): dzits->GetTileHeight ();
947 double v_tile_w = tile_width * (double)(pow2 (layers - from_layer)) * sub_vp.width / sub_w;
948 double v_tile_h = tile_height * (double)(pow2 (layers - from_layer)) * sub_vp.width / sub_w;
950 int i, j;
951 for (i = (int)((MAX(msivp_ox, sub_vp.x) - sub_vp.x)/v_tile_w); i * v_tile_w < MIN(msivp_ox + msivp_w, sub_vp.x + sub_vp.width) - sub_vp.x;i++) {
952 if (!(bitmapimagectx = GetFreeBitmapImageContext ()))
953 break;
954 for (j = (int)((MAX(msivp_oy, sub_vp.y) - sub_vp.y)/v_tile_h); j * v_tile_h < MIN(msivp_oy + msivp_w/msi_ar, sub_vp.y + sub_vp.width/sub_ar) - sub_vp.y;j++) {
955 if (!(bitmapimagectx = GetFreeBitmapImageContext ()))
956 break;
957 Uri *tile = new Uri ();
958 if (from_layer <= dzits->GetMaxLevel ()) {
959 QTree *node = qtree_insert (shared_cache,
960 from_layer,
961 morton_x(sub_image->n) * (pow2 (from_layer)) / tile_width,
962 morton_y(sub_image->n) * (pow2 (from_layer)) / tile_height);
963 if (!qtree_has_value (node)
964 && dzits->get_tile_func (from_layer,
965 morton_x(sub_image->n) * (pow2 (from_layer)) / tile_width,
966 morton_y(sub_image->n) * (pow2 (from_layer)) / tile_height,
967 tile, dzits))
968 DownloadTile (bitmapimagectx, tile, node);
969 } else {
970 QTree *node = qtree_insert (subimage_cache, from_layer, i, j);
971 if (!qtree_has_value (node)
972 && dzits->get_tile_func (from_layer, i, j, tile, sub_image->source))
973 DownloadTile (bitmapimagectx, tile, node);
975 delete tile;
982 void
983 MultiScaleImage::RenderSingle (cairo_t *cr, Region *region)
985 MultiScaleTileSource *source = GetSource ();
986 double msi_w = GetActualWidth ();
987 double msi_h = GetActualHeight ();
988 double msi_ar = GetAspectRatio ();
989 double im_w = source->GetImageWidth ();
990 double im_h = source->GetImageHeight ();
991 int tile_width = source->GetTileWidth ();
992 int tile_height = source->GetTileHeight ();
993 double vp_ox = GetViewportOrigin()->x;
994 double vp_oy = GetViewportOrigin()->y;
995 double vp_w = GetViewportWidth ();
997 if (msi_w <= 0.0 || msi_h <= 0.0)
998 return; //invisible widget, nothing to render
1000 //Number of layers in the MSI, aka the lowest powerof2 that's bigger than width and height
1001 int layers;
1002 if (frexp (MAX (im_w, im_h), &layers) == 0.5)
1003 layers --;
1005 //optimal layer for this... aka "best viewed at"
1006 int optimal_layer;
1007 if (frexp (msi_w / (vp_w * MIN (1.0, msi_ar)) , &optimal_layer) == 0.5)
1008 optimal_layer--;
1009 optimal_layer = MIN (optimal_layer, layers);
1010 LOG_MSI ("number of layers: %d\toptimal layer for this: %d\n", layers, optimal_layer);
1012 //We have to figure all the layers that we'll have to render:
1013 //- from_layer is the highest COMPLETE layer that we can display (all tiles are there and blended (except for level 0, where it might not be blended yet))
1014 //- to_layer is the highest PARTIAL layer that we can display (contains at least 1 tiles partially blended)
1016 int to_layer = -1;
1017 int from_layer = optimal_layer;
1019 //using the "-1" index for the single image case
1020 int index = -1;
1021 QTree *subimage_cache = (QTree*)g_hash_table_lookup (cache, &index);
1022 if (!subimage_cache)
1023 g_hash_table_insert (cache, new int(index), (subimage_cache = qtree_new ()));
1025 while (from_layer >= 0) {
1026 int count = 0;
1027 int found = 0;
1028 bool blending = FALSE; //means at least a tile is not yet fully blended
1030 //v_tile_X is the virtual tile size at this layer in relative coordinates
1031 double v_tile_w = tile_width * (double)(pow2 (layers - from_layer)) / im_w;
1032 double v_tile_h = tile_height * (double)(pow2 (layers - from_layer)) / im_w;
1033 int i, j;
1034 //This double loop iterate over the displayed part of the image and find all (i,j) being top-left corners of tiles
1035 for (i = MAX(0, (int)(vp_ox / v_tile_w)); i * v_tile_w < MIN(vp_ox + vp_w, 1.0); i++) {
1036 for (j = MAX(0, (int)(vp_oy / v_tile_h)); j * v_tile_h < MIN(vp_oy + vp_w / msi_w * msi_h, 1.0 / msi_ar); j++) {
1037 count++;
1038 cairo_surface_t *image = (cairo_surface_t*)qtree_lookup_data (subimage_cache, from_layer, i, j);
1040 if (image)
1041 found ++;
1042 if (image && *(double*)(cairo_surface_get_user_data (image, &full_opacity_at_key)) > GetValue(MultiScaleImage::TileFadeProperty)->AsDouble ())
1043 blending = TRUE;
1047 if (found > 0 && to_layer < from_layer)
1048 to_layer = from_layer;
1049 if (found == count && (!blending || from_layer == 0))
1050 break;
1051 from_layer --;
1054 //render here
1055 //cairo_push_group (cr);
1058 cairo_save (cr);
1059 cairo_matrix_t render_xform;
1060 cairo_matrix_init_identity (&render_xform);
1061 cairo_matrix_scale (&render_xform, msi_w / vp_w, msi_w / vp_w);
1062 cairo_matrix_translate (&render_xform, -vp_ox, -vp_oy);
1063 cairo_matrix_scale (&render_xform, 1.0 / im_w, 1.0 / im_w);
1066 * here we want to clip to the bounds of the image to ensure we don't
1067 * have scaling artifacts on the edges but the image potentially has bounds
1068 * larger that cairo can handle right now so we transform the image
1069 * bounds to the viewport coordinate space and do intersection and clipping
1070 * there to work around the cairo coordinate limitations.
1072 Rect vp_bounds (0, 0, msi_w, msi_h);
1073 Rect im_bounds (0, 0, im_w, im_h);
1074 im_bounds = im_bounds.Transform (&render_xform);
1075 Rect render_region = vp_bounds.Intersection (im_bounds);
1076 render_region.Draw (cr);
1077 cairo_clip (cr);
1079 cairo_transform (cr, &render_xform);
1081 LOG_MSI ("rendering layers from %d to %d\n", from_layer, to_layer);
1083 double fade = GetValue (MultiScaleImage::TileFadeProperty)->AsDouble();
1084 int layer_to_render = MAX (0, from_layer);
1085 while (layer_to_render <= to_layer) {
1086 int i, j;
1087 double v_tile_w = tile_width * (double)(pow2 (layers - layer_to_render)) / im_w;
1088 double v_tile_h = tile_height * (double)(pow2 (layers - layer_to_render)) / im_w;
1089 for (i = MAX(0, (int)(vp_ox / v_tile_w)); i * v_tile_w < MIN(vp_ox + vp_w, 1.0); i++) {
1090 for (j = MAX(0, (int)(vp_oy / v_tile_h)); j * v_tile_h < MIN(vp_oy + vp_w / msi_w * msi_h, 1.0 / msi_ar); j++) {
1091 cairo_surface_t *image = (cairo_surface_t*)qtree_lookup_data (subimage_cache, layer_to_render, i, j);
1092 if (!image)
1093 continue;
1095 LOG_MSI ("rendering %d %d %d\n", layer_to_render, i, j);
1096 cairo_save (cr);
1098 cairo_scale (cr, (pow2 (layers - layer_to_render)), (pow2 (layers - layer_to_render))); //scale to image size
1100 cairo_translate (cr, i * tile_width, j * tile_height);
1102 cairo_set_source_surface (cr, image, 0, 0);
1104 double *opacity = (double*)(cairo_surface_get_user_data (image, &full_opacity_at_key));
1105 double combined = 1.0;
1107 if (opacity && *opacity > fade)
1108 combined = MIN(1.0 - *opacity + fade, 1.0);
1110 if (IS_TRANSLUCENT (combined))
1111 cairo_paint_with_alpha (cr, combined);
1112 else
1113 cairo_paint (cr);
1115 cairo_restore (cr);
1118 layer_to_render++;
1120 cairo_restore (cr);
1121 // cairo_pop_group_to_source (cr);
1123 if (!GetAllowDownloading ())
1124 return;
1126 BitmapImageContext *bitmapimagectx;
1127 if (!(bitmapimagectx = GetFreeBitmapImageContext ()))
1128 return;
1130 //Get the next tile(s)...
1131 while (from_layer < optimal_layer) {
1132 from_layer ++;
1134 double v_tile_w = tile_width * (double)(pow2 (layers - from_layer)) / im_w;
1135 double v_tile_h = tile_height * (double)(pow2 (layers - from_layer)) / im_w;
1136 int i, j;
1138 for (i = MAX(0, (int)(vp_ox / v_tile_w)); i * v_tile_w < MIN(vp_ox + vp_w, 1.0); i++) {
1139 if (!(bitmapimagectx = GetFreeBitmapImageContext ()))
1140 return;
1141 for (j = MAX(0, (int)(vp_oy / v_tile_h)); j * v_tile_h < MIN(vp_oy + vp_w / msi_w * msi_h, 1.0 / msi_ar); j++) {
1142 if (!(bitmapimagectx = GetFreeBitmapImageContext ()))
1143 return;
1144 Uri *tile = new Uri ();
1145 QTree *node = qtree_insert (subimage_cache, from_layer, i, j);
1146 if (!qtree_has_value (node)) {
1147 if (source->get_tile_func (from_layer, i, j, tile, source))
1148 DownloadTile (bitmapimagectx, tile, node);
1149 else
1150 qtree_set_value (node, NULL);
1152 delete tile;
1158 void
1159 MultiScaleImage::OnSourcePropertyChanged ()
1161 //abort all downloaders
1162 StopDownloading ();
1164 DeepZoomImageTileSource *newsource;
1165 if (GetSource ()) {
1166 if (GetSource ()->Is (Type::DEEPZOOMIMAGETILESOURCE)) {
1167 if ((newsource = GetValue (MultiScaleImage::SourceProperty)->AsDeepZoomImageTileSource ())) {
1168 newsource->set_callbacks (multi_scale_image_handle_dz_parsed, multi_scale_image_emit_image_open_failed, multi_scale_image_on_source_property_changed, this);
1169 newsource->Download ();
1171 } else {
1172 EmitImageOpenSucceeded ();
1176 //Reset the viewport
1177 ClearValue (MultiScaleImage::InternalViewportWidthProperty, true);
1178 ClearValue (MultiScaleImage::InternalViewportOriginProperty, true);
1179 //SetValue (MultiScaleImage::ViewportOriginProperty, Deployment::GetCurrent ()->GetTypes ()->GetProperty (MultiScaleImage::ViewportOriginProperty)->GetDefaultValue());
1180 //SetValue (MultiScaleImage::ViewportWidthProperty, Deployment::GetCurrent ()->GetTypes ()->GetProperty (MultiScaleImage::ViewportWidthProperty)->GetDefaultValue());
1182 //Invalidate the whole cache
1183 if (cache) {
1184 g_hash_table_destroy (cache);
1185 cache = g_hash_table_new_full (g_int_hash, g_int_equal, g_free, (GDestroyNotify)qtree_destroy);
1188 //Reset the subimages
1189 GetSubImages()->Clear ();
1191 //register the callback for InvalidateTileLayers
1192 if (GetSource ())
1193 GetSource ()->set_invalidate_tile_layer_func (multi_scale_image_invalidate_tile_layer, this);
1195 Invalidate ();
1198 void
1199 MultiScaleImage::OnPropertyChanged (PropertyChangedEventArgs *args, MoonError *error)
1201 if (args->GetId () == MultiScaleImage::AllowDownloadingProperty) {
1202 if (args->GetNewValue()->AsBool ())
1203 Invalidate();
1204 else
1205 StopDownloading ();
1208 if (args->GetId () == MultiScaleImage::InternalViewportOriginProperty) {
1209 Emit (MultiScaleImage::ViewportChangedEvent);
1210 Invalidate ();
1213 if (args->GetId () == MultiScaleImage::InternalViewportWidthProperty) {
1214 Emit (MultiScaleImage::ViewportChangedEvent);
1215 Invalidate ();
1218 if (args->GetId () == MultiScaleImage::ViewportOriginProperty) {
1219 pan_target = Point (args->GetNewValue ()->AsPoint ()->x, args->GetNewValue ()->AsPoint ()->y);
1220 SetInternalViewportOrigin (args->GetNewValue ()->AsPoint ());
1221 ClearValue (MultiScaleImage::ViewportOriginProperty, false);
1224 if (args->GetId () == MultiScaleImage::ViewportWidthProperty) {
1225 zoom_target = args->GetNewValue ()->AsDouble ();
1226 SetInternalViewportWidth (args->GetNewValue ()->AsDouble ());
1227 ClearValue (MultiScaleImage::ViewportWidthProperty, false);
1230 if (args->GetId () == MultiScaleImage::TileFadeProperty) {
1231 //There's 2 options here,
1232 // - loop all the tiles, update their opacity, and only invalidate a subregion
1233 // - Invalidate all, and compute the new opacity on the tiles that needs to be rendered.
1234 //Both options are unfortunately quite expensive :(
1235 //LOG_MSI ("TileFade changed to %f\n", args->GetNewValue()->AsDouble ());
1236 Invalidate ();
1239 if (args->GetId () == MultiScaleImage::SourceProperty) {
1240 OnSourcePropertyChanged ();
1243 if (args->GetId () == MultiScaleImage::UseSpringsProperty) {
1244 if (!args->GetNewValue()->AsBool ()) {
1245 if (zoom_sb) {
1246 double *endpoint = GetZoomAnimationEndPoint ();
1247 zoom_sb->StopWithError (NULL);
1248 SetViewportWidth (*endpoint);
1250 if (pan_sb) {
1251 Point *endpoint = GetPanAnimationEndPoint ();
1252 pan_sb->StopWithError (NULL);
1253 SetViewportOrigin (endpoint);
1258 if (args->GetProperty ()->GetOwnerType () != Type::MULTISCALEIMAGE) {
1259 MediaBase::OnPropertyChanged (args, error);
1260 return;
1263 NotifyListenersOfPropertyChange (args, error);
1266 void
1267 MultiScaleImage::OnCollectionChanged (Collection *col, CollectionChangedEventArgs *args)
1269 subimages_sorted = false;
1270 Invalidate ();
1273 void
1274 MultiScaleImage::OnCollectionItemChanged (Collection *col, DependencyObject *obj, PropertyChangedEventArgs *args)
1276 if (args->GetId () == MultiScaleSubImage::ViewportWidthProperty)
1277 Invalidate ();
1278 if (args->GetId () == MultiScaleSubImage::ViewportOriginProperty)
1279 Invalidate ();
1280 if (args->GetId () == MultiScaleSubImage::ZIndexProperty) {
1281 subimages_sorted = false;
1282 Invalidate ();
1286 void
1287 MultiScaleImage::EmitImageFailed ()
1289 LOG_MSI ("MSI::Emitting image failed\n");
1290 Emit (MultiScaleImage::ImageFailedEvent);
1293 void
1294 MultiScaleImage::EmitImageOpenFailed ()
1296 LOG_MSI ("MSI::Emitting image open failed\n");
1297 MoonError moon_error;
1298 MoonError::FillIn (&moon_error, MoonError::EXCEPTION, -2147467259, "");
1299 Emit (MultiScaleImage::ImageOpenFailedEvent, new ErrorEventArgs (UnknownError, moon_error));
1302 void
1303 MultiScaleImage::EmitImageOpenSucceeded ()
1305 LOG_MSI ("\nMSI::Emitting open suceeded\n");
1306 Emit (MultiScaleImage::ImageOpenSucceededEvent);
1307 // This is a hack that removes at least one timeout (#291),
1308 // possibly because an invalidation gets lost somehow.
1309 // Since we only start downloading when we try to
1310 // render the msi, the test effectively hangs.
1311 FullInvalidate (true);
1314 void
1315 MultiScaleImage::EmitMotionFinished ()
1317 LOG_MSI ("Emitting MotionFinished\n");
1318 pending_motion_completed = false;
1319 Emit (MultiScaleImage::MotionFinishedEvent);
1322 Point*
1323 MultiScaleImage::GetPanAnimationEndPoint ()
1325 return pan_animation->GetKeyFrames ()->GetValueAt (0)->AsSplinePointKeyFrame ()->GetValue ();
1328 void
1329 MultiScaleImage::SetPanAnimationEndPoint (Point value)
1331 pan_animation->GetKeyFrames ()->GetValueAt (0)->AsSplinePointKeyFrame ()->SetValue (value);
1334 double*
1335 MultiScaleImage::GetZoomAnimationEndPoint ()
1337 return zoom_animation->GetKeyFrames ()->GetValueAt (0)->AsSplineDoubleKeyFrame ()->GetValue ();
1340 void
1341 MultiScaleImage::SetZoomAnimationEndPoint (double value)
1343 zoom_animation->GetKeyFrames ()->GetValueAt (0)->AsSplineDoubleKeyFrame ()->SetValue (value);
1346 void
1347 MultiScaleImage::SetInternalViewportWidth (double value)
1349 if (!GetUseSprings ()) {
1350 if (!pending_motion_completed) {
1351 AddTickCall ((TickCallHandler)multi_scale_image_emit_motion_finished);
1352 pending_motion_completed = true;
1354 SetValue (MultiScaleImage::InternalViewportWidthProperty, Value (value));
1355 return;
1358 if (!zoom_sb) {
1359 zoom_sb = new Storyboard ();
1360 zoom_sb->SetManualTarget (this);
1361 zoom_sb->SetTargetProperty (zoom_sb, new PropertyPath ("(MultiScaleImage.InternalViewportWidth)"));
1362 zoom_sb->AddHandler (Storyboard::CompletedEvent, zoom_finished, this);
1363 zoom_animation = new DoubleAnimationUsingKeyFrames ();
1364 zoom_animation->SetDuration (Duration::FromSeconds (4));
1365 zoom_animation->SetKeyFrames (DOPtr<DoubleKeyFrameCollection> (new DoubleKeyFrameCollection ()));
1366 DOPtr<SplineDoubleKeyFrame> keyframe (new SplineDoubleKeyFrame ());
1367 keyframe->SetKeySpline (DOPtr<KeySpline> (new KeySpline (.05, .5, 0, 1.0)));
1368 keyframe->SetKeyTime (KeyTime::FromPercent (1.0));
1369 zoom_animation->GetKeyFrames ()->Add (static_cast<SplineDoubleKeyFrame*>(keyframe));
1371 DOPtr<TimelineCollection> tlc (new TimelineCollection ());
1372 tlc->Add (static_cast<DoubleAnimationUsingKeyFrames*>(zoom_animation));
1373 zoom_sb->SetChildren(tlc);
1374 #if DEBUG
1375 zoom_sb->SetName ("Multiscale Zoom");
1376 #endif
1377 } else {
1378 zoom_sb->PauseWithError (NULL);
1381 LOG_MSI ("animating zoom from %f to %f\n\n", GetInternalViewportWidth(), value)
1383 is_zooming = true;
1385 SetZoomAnimationEndPoint (value);
1386 zoom_sb->BeginWithError (NULL);
1389 void
1390 MultiScaleImage::SetInternalViewportOrigin (Point* value)
1392 if (!GetUseSprings ()) {
1393 if (!pending_motion_completed) {
1394 AddTickCall ((TickCallHandler)multi_scale_image_emit_motion_finished);
1395 pending_motion_completed = true;
1397 SetValue (MultiScaleImage::InternalViewportOriginProperty, Value (*value));
1398 return;
1401 if (!pan_sb) {
1402 pan_sb = new Storyboard ();
1403 pan_sb->SetManualTarget (this);
1404 pan_sb->SetTargetProperty (pan_sb, new PropertyPath ("(MultiScaleImage.InternalViewportOrigin)"));
1405 pan_sb->AddHandler (Storyboard::CompletedEvent, pan_finished, this);
1406 pan_animation = new PointAnimationUsingKeyFrames ();
1407 pan_animation->SetDuration (Duration::FromSeconds (4));
1408 pan_animation->SetKeyFrames (DOPtr<PointKeyFrameCollection> (new PointKeyFrameCollection ()));
1409 SplinePointKeyFrame *keyframe = new SplinePointKeyFrame ();
1410 keyframe->SetKeySpline (DOPtr<KeySpline> (new KeySpline (.05, .5, 0, 1.0)));
1411 keyframe->SetKeyTime (KeyTime::FromPercent (1.0));
1412 pan_animation->GetKeyFrames ()->Add (keyframe);
1414 TimelineCollection *tlc = new TimelineCollection ();
1415 tlc->Add (static_cast<PointAnimationUsingKeyFrames*> (pan_animation));
1416 pan_sb->SetChildren(tlc);
1417 #if DEBUG
1418 pan_sb->SetName ("Multiscale Pan");
1419 #endif
1420 } else
1421 pan_sb->PauseWithError (NULL);
1423 is_panning = true;
1424 SetPanAnimationEndPoint (*value);
1425 pan_sb->BeginWithError (NULL);
1428 void
1429 MultiScaleImage::SetIsIdle (bool value)
1431 SetValue (MultiScaleImage::IsIdleProperty, Value (value));
1434 void
1435 MultiScaleImage::SetIsDownloading (bool value)
1437 SetValue (MultiScaleImage::IsDownloadingProperty, Value (value));
1441 MultiScaleImage::LogicalToElementX (int x, int y)
1443 return LogicalToElementPoint (Point (x, y)).x;
1447 MultiScaleImage::LogicalToElementY (int x, int y)
1449 return LogicalToElementPoint (Point (x, y)).y;
1452 MultiScaleSubImage *
1453 MultiScaleImage::GetIthSubImage (int index)
1455 MultiScaleSubImageCollection *sub_images = GetSubImages ();
1456 Value *value;
1458 if (sub_images == NULL)
1459 return NULL;
1461 value = sub_images->GetValueAt (index);
1463 if (value == NULL)
1464 return NULL;
1466 return value->AsMultiScaleSubImage ();
1470 MultiScaleImage::GetSubImageCount ()
1472 MultiScaleSubImageCollection *sub_images = GetSubImages ();
1474 if (sub_images == NULL)
1475 return 0;
1476 return sub_images->GetCount ();
1479 void
1480 MultiScaleImage::InvalidateTileLayer (int level, int tilePositionX, int tilePositionY, int tileLayer)
1482 if (GetSource ()->Is (Type::DEEPZOOMIMAGETILESOURCE)) {
1483 g_warning ("calling InvalidateTileLayer on DeepZoom Images makes no sense\n");
1484 return;
1487 StopDownloading ();
1489 int index = -1;
1490 QTree *subimage_cache = (QTree*)g_hash_table_lookup (cache, &index);
1491 if (subimage_cache)
1492 qtree_remove_at (subimage_cache, level, tilePositionX, tilePositionY, 0);
1494 Invalidate ();
1498 * MultiScaleImagePropertyValueProvider
1501 MultiScaleImagePropertyValueProvider::MultiScaleImagePropertyValueProvider (MultiScaleImage *msi, PropertyPrecedence precedence)
1502 : FrameworkElementProvider (msi, precedence)
1504 viewport_origin = NULL;
1505 viewport_width = NULL;
1508 MultiScaleImagePropertyValueProvider::~MultiScaleImagePropertyValueProvider ()
1510 delete viewport_origin;
1511 delete viewport_width;
1514 Value *
1515 MultiScaleImagePropertyValueProvider::GetPropertyValue (DependencyProperty *property)
1517 // We verify main thread here too in case some object in the pipeline happens to want a property on the media element
1518 VERIFY_MAIN_THREAD;
1520 if (property->GetId () == MultiScaleImage::ViewportOriginProperty)
1521 return GetViewportOrigin ();
1522 if (property->GetId () == MultiScaleImage::ViewportWidthProperty)
1523 return GetViewportWidth ();
1524 return FrameworkElementProvider::GetPropertyValue (property);
1527 Value *
1528 MultiScaleImagePropertyValueProvider::GetViewportOrigin ()
1530 MultiScaleImage *msi = (MultiScaleImage *) obj;
1532 delete viewport_origin;
1533 viewport_origin = new Value (*(msi->GetInternalViewportOrigin ()));
1534 return viewport_origin;
1537 Value *
1538 MultiScaleImagePropertyValueProvider::GetViewportWidth ()
1540 MultiScaleImage *msi = (MultiScaleImage *) obj;
1542 delete viewport_width;
1543 viewport_width = new Value (msi->GetInternalViewportWidth ());
1544 return viewport_width;