just kick off another build
[moon.git] / src / multiscaleimage.cpp
blob12bbb4a6d34fe2595736ce3fc457afad9c0639e6
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 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 typedef QTree QTreeNode;
66 static QTree*
67 qtree_new (void)
69 return g_new0 (QTree, 1);
72 static QTreeNode*
73 qtree_insert (QTree* root, int level, guint64 x, guint64 y)
75 if (x >= (pow2 (level)) || y >= (pow2 (level))) {
76 g_warning ("QuadTree index out of range.");
77 return NULL;
80 if (!root) {
81 g_warning ("passing a NULL QTree to qtree_insert");
82 return NULL;
85 QTreeNode *node = root;
86 while (level-- > 0) {
87 if (y < (pow2 (level))) {
88 if (x < (pow2 (level))) {
89 if (!node->l0) {
90 node->l0 = g_new0 (QTreeNode, 1);
91 node->l0->parent = node;
93 node = node->l0;
94 } else {
95 if (!node->l1) {
96 node->l1 = g_new0 (QTreeNode, 1);
97 node->l1->parent = node;
99 node = node->l1;
100 x -= (pow2 (level));
102 } else {
103 if (x < (pow2 (level))) {
104 if (!node->l2) {
105 node->l2 = g_new0 (QTreeNode, 1);
106 node->l2->parent = node;
108 node = node->l2;
109 y -= (pow2 (level));
110 } else {
111 if (!node->l3) {
112 node->l3 = g_new0 (QTreeNode, 1);
113 node->l3->parent = node;
115 node = node->l3;
116 x -= (pow2 (level));
117 y -= (pow2 (level));
121 return node;
124 static QTreeNode*
125 qtree_insert_with_value (QTree* root, void *data, int level, guint64 x, guint64 y)
127 QTreeNode *node = qtree_insert (root, level, x, y);
128 node->has_value = true;
129 node->data = data;
130 return node;
133 static QTree *
134 qtree_lookup (QTree* root, int level, guint64 x, guint64 y)
136 if (x >= (pow2 (level)) || y >= (pow2 (level))) {
137 #if DEBUG
138 // we seem to run into an infinite loop sporadically here for drt #2014 completely spamming the test output.
139 // abort to get a stack trace.
140 abort ();
141 #endif
142 g_warning ("QuadTree index out of range.");
143 return NULL;
146 while (level-- > 0) {
147 if (!root)
148 return NULL;
150 if (y < (pow2 (level))) {
151 if (x < (pow2 (level))) {
152 root = root->l0;
153 } else {
154 root = root->l1;
155 x -= (pow2 (level));
157 } else {
158 if (x < (pow2 (level))) {
159 root = root->l2;
160 y -= (pow2 (level));
161 } else {
162 root = root->l3;
163 x -= (pow2 (level));
164 y -= (pow2 (level));
168 return root;
171 static void *
172 qtree_lookup_data (QTree* root, int level, guint64 x, guint64 y)
174 QTree *node = qtree_lookup (root, level, x, y);
175 if (node && node->has_value)
176 return node->data;
177 return NULL;
180 static void
181 qtree_remove (QTree* node, int depth)
183 if (node && node->has_value) {
184 node->has_value = false;
185 cairo_surface_destroy ((cairo_surface_t*)node->data);
188 if (depth <= 0)
189 return;
191 qtree_remove (node->l0, depth - 1);
192 qtree_remove (node->l1, depth - 1);
193 qtree_remove (node->l2, depth - 1);
194 qtree_remove (node->l3, depth - 1);
198 static void
199 qtree_remove_at (QTree* root, int level, int x, int y, int depth)
201 QTree* node = qtree_lookup (root, level, x, y);
202 qtree_remove (node, depth);
205 static bool
206 qtree_has_value_at (QTree* root, int level, guint64 x, guint64 y)
208 QTree *node = qtree_lookup (root, level, x, y);
209 if (node)
210 return node->has_value;
211 return false;
214 static void
215 qtree_destroy (QTree *root)
217 if (!root)
218 return;
220 //FIXME: the destroy func should be a qtree ctor option
221 if (root->data)
222 cairo_surface_destroy ((cairo_surface_t*)(root->data));
224 qtree_destroy (root->l0);
225 qtree_destroy (root->l1);
226 qtree_destroy (root->l2);
227 qtree_destroy (root->l3);
228 g_free (root);
232 * BitmapImageContext
235 enum BitmapImageStatus {
236 BitmapImageFree = 0,
237 BitmapImageBusy,
238 BitmapImageDone
241 struct BitmapImageContext
243 BitmapImageStatus state;
244 BitmapImage *bitmapimage;
245 int subimage;
246 int level;
247 int x;
248 int y;
249 int retry;
253 * Morton layout
256 #if 0
257 static void
258 morton (int n, int *x, int *y) {
259 n = (n & 0x99999999) + ((n & 0x22222222) << 1) + ((n & 0x44444444) >> 1);
260 n = (n & 0xc3c3c3c3) + ((n & 0x0c0c0c0c) << 2) + ((n & 0x30303030) >> 2);
261 n = (n & 0xf00ff00f) + ((n & 0x00f000f0) << 4) + ((n & 0x0f000f00) >> 4);
262 n = (n & 0xff0000ff) + ((n & 0x0000ff00) << 8) + ((n & 0x00ff0000) >> 8);
263 *x = n & 0x0000ffff;
264 *y = n >> 16;
266 #endif
268 static inline int
269 morton_x (int n)
271 n = (n & 0x11111111) + ((n & 0x44444444) >> 1);
272 n = (n & 0x03030303) + ((n & 0x30303030) >> 2);
273 n = (n & 0x000f000f) + ((n & 0x0f000f00) >> 4);
274 return (n & 0x000000ff) + ((n & 0x00ff0000) >> 8);
277 static inline int
278 morton_y (int n)
280 n = (n & 0x88888888) + ((n & 0x22222222) << 1);
281 n = (n & 0xc0c0c0c0) + ((n & 0x0c0c0c0c) << 2);
282 n = (n & 0xf000f000) + ((n & 0x00f000f0) << 4);
283 n = (n & 0xff000000) + ((n & 0x0000ff00) << 8);
285 return n >> 16;
290 * MultiScaleImage
293 MultiScaleImage::MultiScaleImage () :
294 subimages_sorted(false),
295 pending_motion_completed(false),
296 bitmapimages(NULL),
297 is_fading(false),
298 is_zooming(false),
299 is_panning(false)
301 // static bool init = true;
302 // if (init) {
303 // init = false;
304 // MultiScaleImage::SubImagesProperty->SetValueValidator (MultiScaleSubImageCollectionValidator);
305 // }
306 providers [PropertyPrecedence_DynamicValue] = new MultiScaleImagePropertyValueProvider (this, PropertyPrecedence_DynamicValue);
308 SetObjectType (Type::MULTISCALEIMAGE);
309 cache = g_hash_table_new_full (g_int_hash, g_int_equal, g_free, (GDestroyNotify)qtree_destroy);
312 MultiScaleImage::~MultiScaleImage ()
314 if (cache)
315 g_hash_table_destroy (cache);
316 cache = NULL;
317 if (bitmapimages)
318 g_list_free (bitmapimages);
319 bitmapimages = NULL;
322 void
323 MultiScaleImage::ZoomAboutLogicalPoint (double zoomIncrementFactor, double zoomCenterLogicalX, double zoomCenterLogicalY)
325 LOG_MSI ("\nzoomabout logical %f (%f, %f)\n", zoomIncrementFactor, zoomCenterLogicalX, zoomCenterLogicalY);
327 if (zoom_sb)
328 zoom_sb->PauseWithError (NULL);
329 if (pan_sb)
330 pan_sb->PauseWithError (NULL);
332 double viewport_width;
333 Point viewport_origin;
335 if (GetUseSprings () && zoom_sb && pan_sb) {
336 viewport_width = zoom_target;
337 viewport_origin = pan_target;
338 } else {
339 viewport_width = GetViewportWidth ();
340 viewport_origin = *GetViewportOrigin ();
343 double width = viewport_width / zoomIncrementFactor;
344 SetViewportWidth (width);
345 if (!isnan(zoomCenterLogicalX) && !isnan(zoomCenterLogicalY)) {
346 SetViewportOrigin (new Point (zoomCenterLogicalX - (zoomCenterLogicalX - viewport_origin.x) / zoomIncrementFactor,
347 zoomCenterLogicalY - (zoomCenterLogicalY - viewport_origin.y) / zoomIncrementFactor));
351 Point
352 MultiScaleImage::ElementToLogicalPoint (Point elementPoint)
354 Point *vp_origin = GetViewportOrigin ();
355 double vp_width = GetViewportWidth ();
356 double actual_width = GetActualWidth ();
357 return Point (vp_origin->x + (double)elementPoint.x * vp_width / actual_width,
358 vp_origin->y + (double)elementPoint.y * vp_width / actual_width);
361 Point
362 MultiScaleImage::LogicalToElementPoint (Point logicalPoint)
364 Point *vp_origin = GetViewportOrigin ();
365 double vp_width = GetViewportWidth ();
366 double actual_width = GetActualWidth ();
367 return Point ((logicalPoint.x - vp_origin->x) * actual_width / vp_width,
368 (logicalPoint.y - vp_origin->y) * actual_width / vp_width);
371 void
372 MultiScaleImage::DownloadTile (BitmapImageContext *bictx, Uri *tile, int subimage, int level, int x, int y)
374 GList *list;
375 BitmapImageContext *ctx;
376 for (list = g_list_first (bitmapimages); list && (ctx = (BitmapImageContext *)list->data); list = list->next) {
377 if (ctx->state != BitmapImageFree && ctx->bitmapimage->GetUriSource()->operator==(*tile)) {
378 //LOG_MSI ("Tile %s is already being downloaded\n", tile->ToString ());
379 return;
383 //LOG_MSI ("downloading tile %s\n", tile->ToString ());
385 bictx->state = BitmapImageBusy;
386 bictx->subimage = subimage;
387 bictx->level = level;
388 bictx->x = x;
389 bictx->y = y;
390 bictx->retry = 0;
391 SetIsDownloading (true);
392 bictx->bitmapimage->SetDownloadPolicy (MsiPolicy);
393 bictx->bitmapimage->SetUriSource (tile);
396 //multi_scale_image_handle_dz_parsed is only used for DeepZoom sources
397 void
398 multi_scale_image_handle_dz_parsed (void *userdata)
400 MultiScaleImage *msi = (MultiScaleImage*)userdata;
401 //if the source is a collection, fill the subimages list
402 MultiScaleTileSource *source = msi->GetSource ();
403 MultiScaleSubImageCollection *subs = msi->GetSubImages ();
405 if (source->GetImageWidth () >= 0 && source->GetImageHeight () >= 0)
406 msi->SetValue (MultiScaleImage::AspectRatioProperty, Value ((double)source->GetImageWidth () / (double)source->GetImageHeight ()));
408 DeepZoomImageTileSource *dsource;
410 if (source->Is (Type::DEEPZOOMIMAGETILESOURCE) &&
411 (dsource = (DeepZoomImageTileSource *)source)) {
412 int i;
413 MultiScaleSubImage *si;
414 for (i = 0; (si = (MultiScaleSubImage*)g_list_nth_data (dsource->subimages, i)); i++) {
415 if (!subs)
416 msi->SetValue (MultiScaleImage::SubImagesProperty, new MultiScaleSubImageCollection ());
418 subs->Add (si);
421 msi->Invalidate ();
423 //try to get the first tiles
424 BitmapImageContext *bitmapimagectx;
425 int layer = 0;
426 //Get the first tiles
427 while ((bitmapimagectx = msi->GetFreeBitmapImageContext ())) {
428 Uri *tile = new Uri ();
429 if (source->get_tile_func (layer, 0, 0, tile, source))
430 msi->DownloadTile (bitmapimagectx, tile, -1, layer, 0, 0);
431 delete tile;
432 layer ++;
435 msi->EmitImageOpenSucceeded ();
438 //multi_scale_image_handle_dz_failes is only used for DeepZoom sources
439 static void
440 multi_scale_image_handle_dz_failed (void *userdata)
442 MultiScaleImage *msi = (MultiScaleImage*)userdata;
443 msi->EmitImageOpenFailed ();
446 static void
447 multi_scale_image_handle_dz_urisource_changed (void *userdata)
449 MultiScaleImage *msi = (MultiScaleImage*)userdata;
450 msi->OnSourcePropertyChanged ();
453 static void
454 multi_scale_subimage_handle_failed (void *userdata)
456 MultiScaleImage *msi = (MultiScaleImage*)userdata;
457 msi->EmitImageFailed ();
460 void
461 multi_scale_subimage_handle_parsed (void *userdata)
463 MultiScaleImage *msi = (MultiScaleImage*)userdata;
464 msi->Invalidate ();
467 static void
468 fade_finished (EventObject *sender, EventArgs *calldata, gpointer closure)
470 MultiScaleImage *msi = (MultiScaleImage *) closure;
471 msi->FadeFinished ();
474 void
475 MultiScaleImage::FadeFinished ()
477 is_fading = false;
478 if (!is_fading && !is_zooming && !is_panning)
479 EmitMotionFinished ();
482 static void
483 zoom_finished (EventObject *sender, EventArgs *calldata, gpointer closure)
485 MultiScaleImage *msi = (MultiScaleImage *) closure;
486 msi->ZoomFinished ();
489 void
490 MultiScaleImage::ZoomFinished ()
492 is_zooming = false;
493 if (!is_fading && !is_zooming && !is_panning)
494 EmitMotionFinished ();
497 static void
498 pan_finished (EventObject *sender, EventArgs *calldata, gpointer closure)
500 MultiScaleImage *msi = (MultiScaleImage *) closure;
501 msi->PanFinished ();
504 void
505 MultiScaleImage::PanFinished ()
507 is_panning = false;
508 if (!is_fading && !is_zooming && !is_panning)
509 EmitMotionFinished ();
512 static void
513 motion_finished_delayed (EventObject *sender)
515 LOG_MSI ("MSI::motion_finished_delayed ()\n");
516 ((MultiScaleImage *) sender)->EmitMotionFinished ();
519 void
520 multi_scale_image_invalidate_tile_layer (int level, int tilePositionX, int tilePositionY, int tileLayer, void *userdata)
522 MultiScaleImage *msi = (MultiScaleImage *)userdata;
523 msi->InvalidateTileLayer (level, tilePositionX, tilePositionY, tileLayer);
526 void
527 tile_available (EventObject *sender, EventArgs *calldata, gpointer closure)
529 // LOG_MSI ("Tile downloaded %s\n", ((BitmapImage *)sender)->GetUriSource ()->ToString ());
530 ((MultiScaleImage *)closure)->TileOpened ((BitmapImage *)sender);
533 void
534 MultiScaleImage::TileOpened (BitmapImage *bitmapimage)
536 BitmapImageContext *ctx = GetBitmapImageContext (bitmapimage);
537 ctx->state = BitmapImageDone;
538 GList *list;
539 bool is_downloading = false;
540 for (list = g_list_first (bitmapimages); list && (ctx = (BitmapImageContext *)list->data); list = list->next)
541 is_downloading |= (ctx->state == BitmapImageBusy);
542 SetIsDownloading (is_downloading);
543 Invalidate ();
546 void
547 tile_failed (EventObject *sender, EventArgs *calldata, gpointer closure)
549 // LOG_MSI ("Failed to download tile %s\n", ((BitmapImage *)sender)->GetUriSource ()->ToString ());
550 ((MultiScaleImage *)closure)->TileFailed ((BitmapImage *)sender);
553 void
554 MultiScaleImage::TileFailed (BitmapImage *bitmapimage)
556 BitmapImageContext *ctx = GetBitmapImageContext (bitmapimage);
557 if (ctx->retry < 5) {
558 bitmapimage->SetUriSource (bitmapimage->GetUriSource ());
559 ctx->retry = ctx->retry + 1;
560 } else {
561 ctx->state = BitmapImageFree;
563 LOG_MSI ("caching a NULL for %s\n", ctx->bitmapimage->GetUriSource()->ToString ());
564 QTree *subimage_cache = (QTree*)g_hash_table_lookup (cache, &(ctx->subimage));
565 if (!subimage_cache)
566 g_hash_table_insert (cache, new int(ctx->subimage), (subimage_cache = qtree_new ()));
567 qtree_insert_with_value (subimage_cache, NULL, ctx->level, ctx->x, ctx->y);
569 GList *list;
570 bool is_downloading = false;
571 for (list = g_list_first (bitmapimages); list && (ctx = (BitmapImageContext *)list->data); list = list->next)
572 is_downloading |= (ctx->state == BitmapImageBusy);
573 SetIsDownloading (is_downloading);
575 Invalidate ();
576 EmitImageFailed ();
579 BitmapImageContext *
580 MultiScaleImage::GetBitmapImageContext (BitmapImage *bitmapimage)
582 BitmapImageContext *ctx;
583 GList *list;
584 for (list = g_list_first (bitmapimages); list && (ctx = (BitmapImageContext *)list->data); list = list->next)
585 if (ctx->bitmapimage == bitmapimage)
586 return ctx;
587 return NULL;
590 BitmapImageContext *
591 MultiScaleImage::GetFreeBitmapImageContext ()
593 guint num_dl = 6;
594 BitmapImageContext *ctx;
595 GList *list;
596 for (list = g_list_first (bitmapimages); list && (ctx = (BitmapImageContext *)list->data); list = list->next)
597 if (ctx->state == BitmapImageFree)
598 return ctx;
600 if (g_list_length (bitmapimages) < num_dl) {
601 ctx = new BitmapImageContext ();
602 ctx->state = BitmapImageFree;
603 ctx->bitmapimage = new BitmapImage ();
604 ctx->bitmapimage->AddHandler (ctx->bitmapimage->ImageOpenedEvent, tile_available, this);
605 ctx->bitmapimage->AddHandler (ctx->bitmapimage->ImageFailedEvent, tile_failed, this);
606 bitmapimages = g_list_append (bitmapimages, ctx);
607 return ctx;
610 return NULL;
613 void
614 MultiScaleImage::Render (cairo_t *cr, Region *region, bool path_only)
616 LOG_MSI ("MSI::Render\n");
618 //Process the downloaded tile
619 GList *list;
620 BitmapImageContext *ctx;
621 for (list = g_list_first (bitmapimages); list && (ctx = (BitmapImageContext *)list->data); list = list->next) {
622 cairo_surface_t *surface;
624 if (ctx->state != BitmapImageDone || !(surface = cairo_surface_reference (ctx->bitmapimage->GetSurface (cr))))
625 continue;
627 // Uri *tile = ctx->bitmapimage->GetUriSource ();
628 cairo_surface_set_user_data (surface, &width_key, new int (ctx->bitmapimage->GetPixelWidth ()), g_free);
629 cairo_surface_set_user_data (surface, &height_key, new int (ctx->bitmapimage->GetPixelHeight ()), g_free);
631 if (!fadein_sb) {
632 fadein_sb = new Storyboard ();
633 fadein_sb->SetManualTarget (this);
634 fadein_sb->SetTargetProperty (fadein_sb, new PropertyPath ("(MultiScaleImage.TileFade)"));
635 fadein_animation = new DoubleAnimation ();
636 fadein_animation->SetDuration (Duration::FromSecondsFloat (0.5));
637 TimelineCollection *tlc = new TimelineCollection ();
638 tlc->Add (static_cast<DoubleAnimation*> (fadein_animation));
639 fadein_sb->SetChildren(tlc);
640 fadein_sb->AddHandler (Storyboard::CompletedEvent, fade_finished, this);
641 #if DEBUG
642 fadein_sb->SetName ("Multiscale Fade-In");
643 #endif
644 } else {
645 fadein_sb->PauseWithError (NULL);
648 //LOG_MSI ("animating Fade from %f to %f\n\n", GetValue(MultiScaleImage::TileFadeProperty)->AsDouble(), GetValue(MultiScaleImage::TileFadeProperty)->AsDouble() + 0.9);
649 double *to = new double (GetValue(MultiScaleImage::TileFadeProperty)->AsDouble() + 0.9);
650 fadein_animation->SetFrom (GetValue(MultiScaleImage::TileFadeProperty)->AsDouble());
651 fadein_animation->SetTo (*to);
653 is_fading = true;
655 fadein_sb->BeginWithError(NULL);
657 cairo_surface_set_user_data (surface, &full_opacity_at_key, to, g_free);
658 LOG_MSI ("caching %s\n", ctx->bitmapimage->GetUriSource()->ToString ());
659 QTree *subimage_cache = (QTree*)g_hash_table_lookup (cache, &(ctx->subimage));
660 if (!subimage_cache)
661 g_hash_table_insert (cache, new int(ctx->subimage), (subimage_cache = qtree_new ()));
662 qtree_insert_with_value (subimage_cache, surface, ctx->level, ctx->x, ctx->y);
664 ctx->bitmapimage->SetUriSource (NULL);
665 ctx->state = BitmapImageFree;
668 MultiScaleTileSource *source = GetSource ();
669 if (!source) {
670 LOG_MSI ("no sources set, nothing to render\n");
671 return;
674 bool is_collection = source &&
675 source->Is (Type::DEEPZOOMIMAGETILESOURCE) &&
676 ((DeepZoomImageTileSource *)source)->IsCollection () &&
677 GetSubImages ();
679 if (source->GetImageWidth () < 0 && !is_collection) {
680 LOG_MSI ("nothing to render so far...\n");
681 if (source->Is (Type::DEEPZOOMIMAGETILESOURCE)) {
682 ((DeepZoomImageTileSource*)source)->set_callbacks (multi_scale_image_handle_dz_parsed, multi_scale_image_handle_dz_failed, multi_scale_image_handle_dz_urisource_changed, this);
683 ((DeepZoomImageTileSource*)source)->Download ();
685 return;
688 #if DEBUG
689 if (!source->get_tile_func) {
690 g_warning ("no get_tile_func set\n");
691 return;
693 #endif
695 if (is_collection)
696 RenderCollection (cr, region);
697 else
698 RenderSingle (cr, region);
701 void
702 MultiScaleImage::RenderCollection (cairo_t *cr, Region *region)
704 LOG_MSI ("\nMSI::RenderCollection\n");
706 double msi_w = GetActualWidth ();
707 double msi_h = GetActualHeight ();
708 double msi_ar = GetAspectRatio();
709 double msivp_ox = GetViewportOrigin()->x;
710 double msivp_oy = GetViewportOrigin()->y;
711 double msivp_w = GetViewportWidth();
713 if (!GetSource ()->Is (Type::DEEPZOOMIMAGETILESOURCE)) {
714 g_warning ("RenderCollection called for a non deepzoom tile source. this should not happen");
715 return;
717 DeepZoomImageTileSource *dzits = (DeepZoomImageTileSource *)GetSource ();
719 Rect viewport = Rect (msivp_ox, msivp_oy, msivp_w, msivp_w/msi_ar);
721 MultiScaleSubImageCollection *subs = GetSubImages ();
722 if (!subimages_sorted) {
723 subs->ResortByZIndex ();
724 subimages_sorted = true;
727 //using the "-1" index for the shared cache
728 int shared_index = -1;
729 QTree *shared_cache = (QTree*)g_hash_table_lookup (cache, &shared_index);
730 if (!shared_cache)
731 g_hash_table_insert (cache, new int(shared_index), (shared_cache = qtree_new ()));
733 int i;
734 for (i = 0; i < subs->GetCount (); i++) {
735 MultiScaleSubImage *sub_image = (MultiScaleSubImage*)g_ptr_array_index (subs->z_sorted, i);
737 int index = sub_image->GetId();
738 QTree *subimage_cache = (QTree*)g_hash_table_lookup (cache, &index);
739 if (!subimage_cache)
740 g_hash_table_insert (cache, new int(index), (subimage_cache = qtree_new ()));
742 double subvp_ox = sub_image->GetViewportOrigin()->x;
743 double subvp_oy = sub_image->GetViewportOrigin()->y;
744 double subvp_w = sub_image->GetViewportWidth();
745 double sub_w = sub_image->source->GetImageWidth ();
746 double sub_h = sub_image->source->GetImageHeight ();
747 double sub_ar = sub_image->GetAspectRatio();
750 //expressing the subimage viewport in main viewport coordinates.
751 Rect sub_vp = Rect (-subvp_ox / subvp_w, -subvp_oy / subvp_w, 1.0/subvp_w, 1.0/(sub_ar * subvp_w));
753 //render only if the subimage viewport intersects with this viewport
754 if (!sub_vp.IntersectsWith (viewport))
755 continue;
756 LOG_MSI ("Intersects with main viewport...rendering\n");
758 int layers;
759 if (frexp (MAX (sub_w, sub_h), &layers) == 0.5)
760 layers --;
762 int optimal_layer;
763 frexp (msi_w / (subvp_w * msivp_w * MIN (1.0, sub_ar)), &optimal_layer);
764 optimal_layer = MIN (optimal_layer, layers);
765 LOG_MSI ("number of layers: %d\toptimal layer for this: %d\n", layers, optimal_layer);
767 int to_layer = -1;
768 int from_layer = optimal_layer;
769 while (from_layer >= 0) {
770 int count = 0;
771 int found = 0;
772 bool blending = FALSE; //means at least a tile is not yet fully blended
774 int tile_width = (from_layer > dzits->GetMaxLevel () && ((DeepZoomImageTileSource*)sub_image->source)->IsParsed ()) ? sub_image->source->GetTileWidth () : dzits->GetTileWidth ();
775 int tile_height = (from_layer > dzits->GetMaxLevel () && ((DeepZoomImageTileSource*)sub_image->source)->IsParsed ()) ? sub_image->source->GetTileHeight (): dzits->GetTileHeight ();
777 //in msi relative coord
778 double v_tile_w = tile_width * (double) (pow2 (layers - from_layer)) * sub_vp.width / sub_w;
779 double v_tile_h = tile_height * (double)(pow2 (layers - from_layer)) * sub_vp.width / sub_w;
780 //LOG_MSI ("virtual tile size at layer %d; %fx%f\n", from_layer, v_tile_w, v_tile_h);
782 guint64 i, j;
783 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++) {
784 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++) {
785 count++;
786 cairo_surface_t* image = NULL;
789 if (from_layer > dzits->GetMaxLevel ()) {
790 if ((image = (cairo_surface_t*)qtree_lookup_data (subimage_cache, from_layer, i, j)))
791 found ++;
792 } else if ((image = (cairo_surface_t*)qtree_lookup_data (shared_cache, from_layer,
793 morton_x (sub_image->n) * (pow2 (from_layer)) / tile_width,
794 morton_y (sub_image->n) * (pow2 (from_layer)) / tile_height)))
795 found ++;
797 if (image && *(double*)(cairo_surface_get_user_data (image, &full_opacity_at_key)) > GetValue(MultiScaleImage::TileFadeProperty)->AsDouble ())
798 blending = TRUE;
801 if (found > 0 && to_layer < from_layer)
802 to_layer = from_layer;
803 if (found == count && (!blending || from_layer == 0))
804 break;
806 from_layer --;
809 //render here
810 LOG_MSI ("rendering layers from %d to %d\n", from_layer, to_layer);
811 double fade = GetValue (MultiScaleImage::TileFadeProperty)->AsDouble();
812 if (from_layer >= 0) {
814 cairo_save (cr);
815 cairo_rectangle (cr, 0, 0, msi_w, msi_h);
816 cairo_clip (cr);
817 cairo_scale (cr, msi_w / msivp_w, msi_w / msivp_w); //scale to widget
819 cairo_translate (cr,
820 -msivp_ox + sub_vp.x,
821 -msivp_oy + sub_vp.y);
823 cairo_scale (cr,
824 sub_vp.width/sub_w,
825 sub_vp.width/sub_w);
827 cairo_rectangle (cr, 0, 0, sub_w, sub_h);
828 cairo_clip (cr);
830 if (IS_TRANSLUCENT (sub_image->GetOpacity ()))
831 cairo_push_group (cr);
833 int layer_to_render = from_layer;
834 while (layer_to_render <= to_layer) {
835 int tile_width = (from_layer > dzits->GetMaxLevel () && ((DeepZoomImageTileSource*)sub_image->source)->IsParsed ()) ?sub_image->source->GetTileWidth () : dzits->GetTileWidth ();
836 int tile_height = (from_layer > dzits->GetMaxLevel () && ((DeepZoomImageTileSource*)sub_image->source)->IsParsed ()) ? sub_image->source->GetTileHeight () : dzits->GetTileHeight ();
838 double v_tile_w = tile_width * (double)(pow2 (layers - layer_to_render)) * sub_vp.width / sub_w;
839 double v_tile_h = tile_height * (double)(pow2 (layers - layer_to_render)) * sub_vp.width / sub_w;
841 int i, j;
842 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++) {
843 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++) {
844 cairo_surface_t *image = NULL;
845 bool shared_tile = false;
846 if (layer_to_render > dzits->GetMaxLevel())
847 image = (cairo_surface_t*)qtree_lookup_data (subimage_cache, layer_to_render, i, j);
848 else {
849 //Check in the shared levels
850 shared_tile = true;
852 image = (cairo_surface_t*)qtree_lookup_data (shared_cache, layer_to_render,
853 morton_x(sub_image->n) * pow2 (layer_to_render) / tile_width,
854 morton_y(sub_image->n) * pow2 (layer_to_render) / tile_height);
857 if (!image)
858 continue;
860 LOG_MSI ("rendering subimage %d %d %d %d\n", sub_image->id, layer_to_render, i, j);
861 cairo_save (cr);
863 cairo_scale (cr,
864 pow2 (layers - layer_to_render),
865 pow2 (layers - layer_to_render));
867 cairo_translate (cr,
868 i * tile_width,
869 j * tile_height);
871 if (shared_tile) {
872 cairo_translate (cr,
873 (int)(-morton_x(sub_image->n) * (pow2 (layer_to_render))) % tile_width,
874 (int)(-morton_y(sub_image->n) * (pow2 (layer_to_render))) % tile_height);
878 cairo_set_source_surface (cr, image, 0, 0);
880 double *opacity = (double*)(cairo_surface_get_user_data (image, &full_opacity_at_key));
881 double combined = 1.0;
883 if (opacity && *opacity > fade)
884 combined = MIN(1.0 - *opacity + fade, 1.0);
886 if (IS_TRANSLUCENT (combined))
887 cairo_paint_with_alpha (cr, combined);
888 else
889 cairo_paint (cr);
891 cairo_restore (cr);
894 layer_to_render++;
897 if (IS_TRANSLUCENT (sub_image->GetOpacity ())) {
898 cairo_pop_group_to_source (cr);
899 cairo_paint_with_alpha (cr, sub_image->GetOpacity ());
902 cairo_restore (cr);
905 if (!GetAllowDownloading ())
906 continue;
908 BitmapImageContext *bitmapimagectx;
909 if (!(bitmapimagectx = GetFreeBitmapImageContext ()))
910 continue;
912 //Get the next tiles..
913 while (from_layer < optimal_layer) {
914 from_layer ++;
916 //if the subimage is unparsed, trigger the download
917 if (from_layer > dzits->GetMaxLevel () && !((DeepZoomImageTileSource *)sub_image->source)->IsDownloaded () ) {
918 ((DeepZoomImageTileSource*)sub_image->source)->set_callbacks (multi_scale_subimage_handle_parsed, multi_scale_subimage_handle_failed, NULL, this);
919 ((DeepZoomImageTileSource*)sub_image->source)->Download ();
920 break;
923 int tile_width = (from_layer > dzits->GetMaxLevel () && ((DeepZoomImageTileSource*)sub_image->source)->IsParsed ()) ?sub_image->source->GetTileWidth () : dzits->GetTileWidth ();
924 int tile_height = (from_layer > dzits->GetMaxLevel () && ((DeepZoomImageTileSource*)sub_image->source)->IsParsed ()) ? sub_image->source->GetTileHeight (): dzits->GetTileHeight ();
926 double v_tile_w = tile_width * (double)(pow2 (layers - from_layer)) * sub_vp.width / sub_w;
927 double v_tile_h = tile_height * (double)(pow2 (layers - from_layer)) * sub_vp.width / sub_w;
929 int i, j;
930 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++) {
931 if (!(bitmapimagectx = GetFreeBitmapImageContext ()))
932 break;
933 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++) {
934 if (!(bitmapimagectx = GetFreeBitmapImageContext ()))
935 break;
936 Uri *tile = new Uri ();
937 if (from_layer <= dzits->GetMaxLevel ()) {
938 if (!qtree_has_value_at (shared_cache, from_layer,
939 morton_x(sub_image->n) * (pow2 (from_layer)) / tile_width,
940 morton_y(sub_image->n) * (pow2 (from_layer)) / tile_height)
941 && dzits->get_tile_func (from_layer,
942 morton_x(sub_image->n) * (pow2 (from_layer)) / tile_width,
943 morton_y(sub_image->n) * (pow2 (from_layer)) / tile_height,
944 tile, dzits))
945 DownloadTile (bitmapimagectx, tile, shared_index, from_layer,
946 morton_x(sub_image->n) * (pow2 (from_layer)) / tile_width,
947 morton_y(sub_image->n) * (pow2 (from_layer)) / tile_height);
948 } else {
949 if (!qtree_has_value_at (subimage_cache, from_layer, i, j)
950 && dzits->get_tile_func (from_layer, i, j, tile, sub_image->source))
951 DownloadTile (bitmapimagectx, tile, index, from_layer, i, j);
953 delete tile;
960 void
961 MultiScaleImage::RenderSingle (cairo_t *cr, Region *region)
963 MultiScaleTileSource *source = GetSource ();
964 double msi_w = GetActualWidth ();
965 double msi_h = GetActualHeight ();
966 double msi_ar = GetAspectRatio ();
967 double im_w = source->GetImageWidth ();
968 double im_h = source->GetImageHeight ();
969 int tile_width = source->GetTileWidth ();
970 int tile_height = source->GetTileHeight ();
971 double vp_ox = GetViewportOrigin()->x;
972 double vp_oy = GetViewportOrigin()->y;
973 double vp_w = GetViewportWidth ();
975 if (msi_w <= 0.0 || msi_h <= 0.0)
976 return; //invisible widget, nothing to render
978 //Number of layers in the MSI, aka the lowest powerof2 that's bigger than width and height
979 int layers;
980 if (frexp (MAX (im_w, im_h), &layers) == 0.5)
981 layers --;
983 //optimal layer for this... aka "best viewed at"
984 int optimal_layer;
985 if (frexp (msi_w / (vp_w * MIN (1.0, msi_ar)) , &optimal_layer) == 0.5)
986 optimal_layer--;
987 optimal_layer = MIN (optimal_layer, layers);
988 LOG_MSI ("number of layers: %d\toptimal layer for this: %d\n", layers, optimal_layer);
990 //We have to figure all the layers that we'll have to render:
991 //- 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))
992 //- to_layer is the highest PARTIAL layer that we can display (contains at least 1 tiles partially blended)
994 int to_layer = -1;
995 int from_layer = optimal_layer;
997 //using the "-1" index for the single image case
998 int index = -1;
999 QTree *subimage_cache = (QTree*)g_hash_table_lookup (cache, &index);
1000 if (!subimage_cache)
1001 g_hash_table_insert (cache, new int(index), (subimage_cache = qtree_new ()));
1003 while (from_layer >= 0) {
1004 int count = 0;
1005 int found = 0;
1006 bool blending = FALSE; //means at least a tile is not yet fully blended
1008 //v_tile_X is the virtual tile size at this layer in relative coordinates
1009 double v_tile_w = tile_width * (double)(pow2 (layers - from_layer)) / im_w;
1010 double v_tile_h = tile_height * (double)(pow2 (layers - from_layer)) / im_w;
1012 int i, j;
1013 //This double loop iterate over the displayed part of the image and find all (i,j) being top-left corners of tiles
1014 for (i = MAX(0, (int)(vp_ox / v_tile_w)); i * v_tile_w < MIN(vp_ox + vp_w, 1.0); i++) {
1015 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++) {
1016 count++;
1017 cairo_surface_t *image = (cairo_surface_t*)qtree_lookup_data (subimage_cache, from_layer, i, j);
1019 if (image)
1020 found ++;
1021 if (image && *(double*)(cairo_surface_get_user_data (image, &full_opacity_at_key)) > GetValue(MultiScaleImage::TileFadeProperty)->AsDouble ())
1022 blending = TRUE;
1026 if (found > 0 && to_layer < from_layer)
1027 to_layer = from_layer;
1028 if (found == count && (!blending || from_layer == 0))
1029 break;
1030 from_layer --;
1033 //render here
1034 //cairo_push_group (cr);
1037 cairo_save (cr);
1039 cairo_rectangle (cr, 0, 0, msi_w, msi_h);
1040 cairo_clip (cr);
1041 cairo_paint (cr);
1043 cairo_scale (cr, msi_w / vp_w, msi_w / vp_w); //scale to viewport
1044 cairo_translate (cr, -vp_ox, -vp_oy);
1045 cairo_scale (cr, 1.0 / im_w, 1.0 / im_w);
1047 cairo_rectangle (cr, 0, 0, im_w, im_h); // clip to image bounds
1048 cairo_clip (cr);
1050 LOG_MSI ("rendering layers from %d to %d\n", from_layer, to_layer);
1052 double fade = GetValue (MultiScaleImage::TileFadeProperty)->AsDouble();
1053 int layer_to_render = MAX (0, from_layer);
1054 while (layer_to_render <= to_layer) {
1055 int i, j;
1056 double v_tile_w = tile_width * (double)(pow2 (layers - layer_to_render)) / im_w;
1057 double v_tile_h = tile_height * (double)(pow2 (layers - layer_to_render)) / im_w;
1058 for (i = MAX(0, (int)(vp_ox / v_tile_w)); i * v_tile_w < MIN(vp_ox + vp_w, 1.0); i++) {
1059 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++) {
1060 cairo_surface_t *image = (cairo_surface_t*)qtree_lookup_data (subimage_cache, layer_to_render, i, j);
1061 if (!image)
1062 continue;
1064 LOG_MSI ("rendering %d %d %d\n", layer_to_render, i, j);
1065 cairo_save (cr);
1067 cairo_scale (cr, (pow2 (layers - layer_to_render)), (pow2 (layers - layer_to_render))); //scale to image size
1069 cairo_translate (cr, i * tile_width, j * tile_height);
1071 cairo_set_source_surface (cr, image, 0, 0);
1073 double *opacity = (double*)(cairo_surface_get_user_data (image, &full_opacity_at_key));
1074 double combined = 1.0;
1076 if (opacity && *opacity > fade)
1077 combined = MIN(1.0 - *opacity + fade, 1.0);
1079 if (IS_TRANSLUCENT (combined))
1080 cairo_paint_with_alpha (cr, combined);
1081 else
1082 cairo_paint (cr);
1084 cairo_restore (cr);
1087 layer_to_render++;
1089 cairo_restore (cr);
1090 // cairo_pop_group_to_source (cr);
1092 if (!GetAllowDownloading ())
1093 return;
1095 BitmapImageContext *bitmapimagectx;
1096 if (!(bitmapimagectx = GetFreeBitmapImageContext ()))
1097 return;
1099 //Get the next tile(s)...
1100 while (from_layer < optimal_layer) {
1101 from_layer ++;
1103 double v_tile_w = tile_width * (double)(pow2 (layers - from_layer)) / im_w;
1104 double v_tile_h = tile_height * (double)(pow2 (layers - from_layer)) / im_w;
1105 int i, j;
1107 for (i = MAX(0, (int)(vp_ox / v_tile_w)); i * v_tile_w < MIN(vp_ox + vp_w, 1.0); i++) {
1108 if (!(bitmapimagectx = GetFreeBitmapImageContext ()))
1109 return;
1110 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++) {
1111 if (!(bitmapimagectx = GetFreeBitmapImageContext ()))
1112 return;
1113 Uri *tile = new Uri ();
1114 if (!qtree_has_value_at (subimage_cache, from_layer, i, j)) {
1115 if (source->get_tile_func (from_layer, i, j, tile, source))
1116 DownloadTile (bitmapimagectx, tile, index, from_layer, i, j);
1117 else
1118 qtree_insert_with_value (subimage_cache, NULL, from_layer, i, j);
1120 delete tile;
1126 void
1127 MultiScaleImage::OnSourcePropertyChanged ()
1129 //abort all downloaders
1130 BitmapImageContext *ctx;
1131 GList *list;
1132 for (list = g_list_first (bitmapimages); list && (ctx = (BitmapImageContext *)list->data); list = list->next) {
1133 ctx->bitmapimage->Abort ();
1134 ctx->state = BitmapImageFree;
1137 DeepZoomImageTileSource *newsource;
1138 if (GetSource ()) {
1139 if (GetSource ()->Is (Type::DEEPZOOMIMAGETILESOURCE)) {
1140 if ((newsource = GetValue (MultiScaleImage::SourceProperty)->AsDeepZoomImageTileSource ())) {
1141 newsource->set_callbacks (multi_scale_image_handle_dz_parsed, multi_scale_image_handle_dz_failed, multi_scale_image_handle_dz_urisource_changed, this);
1142 newsource->Download ();
1144 } else {
1145 EmitImageOpenSucceeded ();
1149 //Reset the viewport
1150 ClearValue (MultiScaleImage::InternalViewportWidthProperty, true);
1151 ClearValue (MultiScaleImage::InternalViewportOriginProperty, true);
1152 //SetValue (MultiScaleImage::ViewportOriginProperty, Deployment::GetCurrent ()->GetTypes ()->GetProperty (MultiScaleImage::ViewportOriginProperty)->GetDefaultValue());
1153 //SetValue (MultiScaleImage::ViewportWidthProperty, Deployment::GetCurrent ()->GetTypes ()->GetProperty (MultiScaleImage::ViewportWidthProperty)->GetDefaultValue());
1155 //Invalidate the whole cache
1156 if (cache) {
1157 g_hash_table_destroy (cache);
1158 cache = g_hash_table_new_full (g_int_hash, g_int_equal, g_free, (GDestroyNotify)qtree_destroy);
1161 //register the callback for InvalidateTileLayers
1162 if (GetSource ())
1163 GetSource ()->set_invalidate_tile_layer_func (multi_scale_image_invalidate_tile_layer, this);
1165 Invalidate ();
1168 void
1169 MultiScaleImage::OnPropertyChanged (PropertyChangedEventArgs *args, MoonError *error)
1171 if (args->GetId () == MultiScaleImage::AllowDownloadingProperty && args->GetNewValue () && args->GetNewValue()->AsBool ())
1172 Invalidate();
1174 if (args->GetId () == MultiScaleImage::InternalViewportOriginProperty) {
1175 Emit (MultiScaleImage::ViewportChangedEvent);
1176 Invalidate ();
1179 if (args->GetId () == MultiScaleImage::InternalViewportWidthProperty) {
1180 Emit (MultiScaleImage::ViewportChangedEvent);
1181 Invalidate ();
1184 if (args->GetId () == MultiScaleImage::ViewportOriginProperty) {
1185 pan_target = Point (args->GetNewValue ()->AsPoint ()->x, args->GetNewValue ()->AsPoint ()->y);
1186 SetInternalViewportOrigin (args->GetNewValue ()->AsPoint ());
1187 ClearValue (MultiScaleImage::ViewportOriginProperty, false);
1190 if (args->GetId () == MultiScaleImage::ViewportWidthProperty) {
1191 zoom_target = args->GetNewValue ()->AsDouble ();
1192 SetInternalViewportWidth (args->GetNewValue ()->AsDouble ());
1193 ClearValue (MultiScaleImage::ViewportWidthProperty, false);
1196 if (args->GetId () == MultiScaleImage::TileFadeProperty) {
1197 //There's 2 options here,
1198 // - loop all the tiles, update their opacity, and only invalidate a subregion
1199 // - Invalidate all, and compute the new opacity on the tiles that needs to be rendered.
1200 //Both options are unfortunately quite expensive :(
1201 //LOG_MSI ("TileFade changed to %f\n", args->GetNewValue()->AsDouble ());
1202 Invalidate ();
1205 if (args->GetId () == MultiScaleImage::SourceProperty) {
1206 OnSourcePropertyChanged ();
1209 if (args->GetProperty ()->GetOwnerType () != Type::MULTISCALEIMAGE) {
1210 DependencyObject::OnPropertyChanged (args, error);
1211 return;
1214 NotifyListenersOfPropertyChange (args, error);
1217 void
1218 MultiScaleImage::OnCollectionChanged (Collection *col, CollectionChangedEventArgs *args)
1220 subimages_sorted = false;
1221 Invalidate ();
1224 void
1225 MultiScaleImage::OnCollectionItemChanged (Collection *col, DependencyObject *obj, PropertyChangedEventArgs *args)
1227 if (args->GetId () == MultiScaleSubImage::ViewportWidthProperty)
1228 Invalidate ();
1229 if (args->GetId () == MultiScaleSubImage::ViewportOriginProperty)
1230 Invalidate ();
1231 if (args->GetId () == MultiScaleSubImage::ZIndexProperty) {
1232 subimages_sorted = false;
1233 Invalidate ();
1237 void
1238 MultiScaleImage::EmitImageFailed ()
1240 LOG_MSI ("MSI::Emitting image failed\n");
1241 Emit (MultiScaleImage::ImageFailedEvent);
1244 void
1245 MultiScaleImage::EmitImageOpenFailed ()
1247 LOG_MSI ("MSI::Emitting image open failed\n");
1248 Emit (MultiScaleImage::ImageOpenFailedEvent);
1251 void
1252 MultiScaleImage::EmitImageOpenSucceeded ()
1254 LOG_MSI ("\nMSI::Emitting open suceeded\n");
1255 Emit (MultiScaleImage::ImageOpenSucceededEvent);
1256 // This is a hack that removes at least one timeout (#291),
1257 // possibly because an invalidation gets lost somehow.
1258 // Since we only start downloading when we try to
1259 // render the msi, the test effectively hangs.
1260 FullInvalidate (true);
1263 void
1264 MultiScaleImage::EmitMotionFinished ()
1266 LOG_MSI ("Emitting MotionFinished\n");
1267 pending_motion_completed = false;
1268 Emit (MultiScaleImage::MotionFinishedEvent);
1271 void
1272 MultiScaleImage::SetInternalViewportWidth (double value)
1274 if (!GetUseSprings ()) {
1275 if (!pending_motion_completed) {
1276 AddTickCall (motion_finished_delayed);
1277 pending_motion_completed = true;
1279 SetValue (MultiScaleImage::InternalViewportWidthProperty, Value (value));
1280 return;
1283 if (!zoom_sb) {
1284 zoom_sb = new Storyboard ();
1285 zoom_sb->SetManualTarget (this);
1286 zoom_sb->SetTargetProperty (zoom_sb, new PropertyPath ("(MultiScaleImage.InternalViewportWidth)"));
1287 zoom_sb->AddHandler (Storyboard::CompletedEvent, zoom_finished, this);
1288 zoom_animation = new DoubleAnimationUsingKeyFrames ();
1289 zoom_animation->SetDuration (Duration::FromSeconds (4));
1290 zoom_animation->SetKeyFrames (DOPtr<DoubleKeyFrameCollection> (new DoubleKeyFrameCollection ()));
1291 DOPtr<SplineDoubleKeyFrame> keyframe (new SplineDoubleKeyFrame ());
1292 keyframe->SetKeySpline (DOPtr<KeySpline> (new KeySpline (.05, .5, 0, 1.0)));
1293 keyframe->SetKeyTime (KeyTime::FromPercent (1.0));
1294 zoom_animation->GetKeyFrames ()->Add (static_cast<SplineDoubleKeyFrame*>(keyframe));
1296 DOPtr<TimelineCollection> tlc (new TimelineCollection ());
1297 tlc->Add (static_cast<DoubleAnimationUsingKeyFrames*>(zoom_animation));
1298 zoom_sb->SetChildren(tlc);
1299 #if DEBUG
1300 zoom_sb->SetName ("Multiscale Zoom");
1301 #endif
1302 } else {
1303 zoom_sb->PauseWithError (NULL);
1306 LOG_MSI ("animating zoom from %f to %f\n\n", GetViewportWidth(), value)
1308 is_zooming = true;
1310 zoom_animation->GetKeyFrames ()->GetValueAt (0)->AsSplineDoubleKeyFrame ()->SetValue (value);
1311 zoom_sb->BeginWithError (NULL);
1314 void
1315 MultiScaleImage::SetInternalViewportOrigin (Point* value)
1317 if (!GetUseSprings ()) {
1318 if (!pending_motion_completed) {
1319 AddTickCall (motion_finished_delayed);
1320 pending_motion_completed = true;
1322 SetValue (MultiScaleImage::InternalViewportOriginProperty, Value (*value));
1323 return;
1326 if (!pan_sb) {
1327 pan_sb = new Storyboard ();
1328 pan_sb->SetManualTarget (this);
1329 pan_sb->SetTargetProperty (pan_sb, new PropertyPath ("(MultiScaleImage.InternalViewportOrigin)"));
1330 pan_sb->AddHandler (Storyboard::CompletedEvent, pan_finished, this);
1331 pan_animation = new PointAnimationUsingKeyFrames ();
1332 pan_animation->SetDuration (Duration::FromSeconds (4));
1333 pan_animation->SetKeyFrames (DOPtr<PointKeyFrameCollection> (new PointKeyFrameCollection ()));
1334 SplinePointKeyFrame *keyframe = new SplinePointKeyFrame ();
1335 keyframe->SetKeySpline (DOPtr<KeySpline> (new KeySpline (.05, .5, 0, 1.0)));
1336 keyframe->SetKeyTime (KeyTime::FromPercent (1.0));
1337 pan_animation->GetKeyFrames ()->Add (keyframe);
1339 TimelineCollection *tlc = new TimelineCollection ();
1340 tlc->Add (static_cast<PointAnimationUsingKeyFrames*> (pan_animation));
1341 pan_sb->SetChildren(tlc);
1342 #if DEBUG
1343 pan_sb->SetName ("Multiscale Pan");
1344 #endif
1345 } else
1346 pan_sb->PauseWithError (NULL);
1348 is_panning = true;
1349 pan_animation->GetKeyFrames ()->GetValueAt (0)->AsSplinePointKeyFrame ()->SetValue (*value);
1350 pan_sb->BeginWithError (NULL);
1353 void
1354 MultiScaleImage::SetIsIdle (bool value)
1356 SetValue (MultiScaleImage::IsIdleProperty, Value (value));
1359 void
1360 MultiScaleImage::SetIsDownloading (bool value)
1362 SetValue (MultiScaleImage::IsDownloadingProperty, Value (value));
1366 MultiScaleImage::LogicalToElementX (int x, int y)
1368 return LogicalToElementPoint (Point (x, y)).x;
1372 MultiScaleImage::LogicalToElementY (int x, int y)
1374 return LogicalToElementPoint (Point (x, y)).y;
1377 MultiScaleSubImage *
1378 MultiScaleImage::GetIthSubImage (int index)
1380 MultiScaleSubImageCollection *sub_images = GetSubImages ();
1381 Value *value;
1383 if (sub_images == NULL)
1384 return NULL;
1386 value = sub_images->GetValueAt (index);
1388 if (value == NULL)
1389 return NULL;
1391 return value->AsMultiScaleSubImage ();
1395 MultiScaleImage::GetSubImageCount ()
1397 MultiScaleSubImageCollection *sub_images = GetSubImages ();
1399 if (sub_images == NULL)
1400 return 0;
1401 return sub_images->GetCount ();
1404 void
1405 MultiScaleImage::InvalidateTileLayer (int level, int tilePositionX, int tilePositionY, int tileLayer)
1407 if (GetSource ()->Is (Type::DEEPZOOMIMAGETILESOURCE)) {
1408 g_warning ("calling InvalidateTileLayer on DeepZoom Images makes no sense\n");
1409 return;
1412 int index = -1;
1413 QTree *subimage_cache = (QTree*)g_hash_table_lookup (cache, &index);
1414 if (subimage_cache)
1415 qtree_remove_at (subimage_cache, level, tilePositionX, tilePositionY, 0);
1417 Invalidate ();
1421 * MultiScaleImagePropertyValueProvider
1424 MultiScaleImagePropertyValueProvider::MultiScaleImagePropertyValueProvider (MultiScaleImage *msi, PropertyPrecedence precedence)
1425 : FrameworkElementProvider (msi, precedence)
1427 viewport_origin = NULL;
1428 viewport_width = NULL;
1431 MultiScaleImagePropertyValueProvider::~MultiScaleImagePropertyValueProvider ()
1433 delete viewport_origin;
1434 delete viewport_width;
1437 Value *
1438 MultiScaleImagePropertyValueProvider::GetPropertyValue (DependencyProperty *property)
1440 // We verify main thread here too in case some object in the pipeline happens to want a property on the media element
1441 VERIFY_MAIN_THREAD;
1443 if (property->GetId () == MultiScaleImage::ViewportOriginProperty)
1444 return GetViewportOrigin ();
1445 if (property->GetId () == MultiScaleImage::ViewportWidthProperty)
1446 return GetViewportWidth ();
1447 return FrameworkElementProvider::GetPropertyValue (property);
1450 Value *
1451 MultiScaleImagePropertyValueProvider::GetViewportOrigin ()
1453 MultiScaleImage *msi = (MultiScaleImage *) obj;
1455 delete viewport_origin;
1456 viewport_origin = new Value (*(msi->GetInternalViewportOrigin ()));
1457 return viewport_origin;
1460 Value *
1461 MultiScaleImagePropertyValueProvider::GetViewportWidth ()
1463 MultiScaleImage *msi = (MultiScaleImage *) obj;
1465 delete viewport_width;
1466 viewport_width = new Value (msi->GetInternalViewportWidth ());
1467 return viewport_width;