I've been trying to implement pinch-and-zoom using the ViewportControl class in a Windows Phone 8 Silverlight app for quite some time without any success. There are some great samples out there, such as this one, but I haven't been able to map the examples that I found to my scenario.
Pinching and zooming works fine, the problem I'm having is with the alignment of the viewport with the content, after the manipulation has completed.
The main problem I'm facing is that after my manipulation has completed, I've been unable to align the scaled content (a XAML canvas and sub tree which is the child of the ViewportControl) to the viewport. This results in the effective bounds of the viewport (the scrollable area) being offset from my content, resulting in part of my content being unreachable/unscrollable.
Here is my algorithm for the manipulation:
Pinch manipulation starts.
Apply render transform to canvas' sub tree during pinch.
Manipulation completes.
Scale main canvas to the effective size of
the render-transformed sub tree (this works as expected and the canvas is aligned with the render-transformed sub tree).
Obtain a transform between the canvas inside the ViewportControl and the viewport control itself.
Use the transform to obtain a bounding rect which (I expect) should represent a rect which overlays the content I want to scroll inside the ViewportControl,
but in the coordinate space of the hosting ViewportControl.
Apply this rect as the viewport bounds of the ViewportControl.
Set the origin of the viewport to the translated top-left coordinates of the canvas
Here is where I calculate and apply the new bounds, after the manipulation has completed:
// Obtain transform between canvas and ViewportControl
GeneralTransform gt = m_MainCanvas.TransformToVisual(m_ViewportControl);
Rect newBounds = gt.TransformBounds(new Rect(0, 0, m_MainCanvas.Width, m_MainCanvas.Height));
m_ViewportControl.Bounds = newBounds;
// set the origin of the viewport again
m_ViewportControl.SetViewportOrigin(gt.Transform(new Point(0, 0)));
This is resulting in my content being misaligned with the viewport.
Try, try as I might, I haven't been able to figure out what I'm doing wrong here... even after looking at tutorials that show how to solve this... :|
I figure that what is happening is that my rect that I'm setting the bounds to is sized correctly, but it's X and Y coordinates are off. I was hoping that this would be addressed by using the transform between the canvas and the ViewportControl itself, but apparently not.
Question: How do I go about setting the origin of the ViewportControl correctly (how do I calculate the point to pass to the SetViewportOrigin method? Can someone please explain this ratio that people are using between the scaled contents and the viewport that I see in other examples of how to crack this?
7/8/2014 Update
I made some headway here. My approach of getting a transform between the content in the ViewportControl and the control itself, then using this to obtain a rect in the control's space to use as the bounds for the viewport wasn't working. My workaround was to simply wrap the render-transformed content in a canvas which I resized the effective (render transformed) size. Then I set the bounds to that size and I finally had the nice bounce-back effect working.
The problem I'm facing now is that when I resize the canvas and reset the bounds for the viewport, the content snaps to the top-left corner of the viewport, and is no longer centered around the pinch area that the user provided.
Can anyone help me understand how the SetViewportOrigin method works on ViewportControl? I'm seeing some really odd data for the Viewport vs. the canvas after a pinch manipulation:
Canvas Size = 1025.69, 1641.11
Bounds = 0,0,1025.69,1641.11
viewport = -56,41.00,480,698
Why is the viewport offset with non-zero values (x,y = -56,41) when I don't even call SetViewportOrigin(Point)?
Here is how I'm thinking the SetViewportOrigin(Point) method works: let's say my Viewport control itself was sized to 400 x 400 pixels, and my content was 800 x 800 pixels. If I set the origin of the viewport to 100, 100 the content would be scrolled such that the first 100 vertical and 100 horizontal pixels would be clipped/masked/offscreen. Is this not how the ViewportControl works?
I figured this out and am a happy camper now. It turned out that I was setting the viewport origin to a point using the wrong coordinate space. I was thinking that if I wanted to move the content to a certain place, that I would provide that point in the coordinate space of the viewport and the content would scroll (setting the upper-left point of the content). What I figured out is that the Point data that the SetViewportOrigin method takes is in the coordinate space of the content. For example: if your content is 500 x 500 pixels wide, your viewport is 400 x 400 pixels wide, and you'd like the first 100 vertical and 100 horizontal pixels to be masked by the viewport (showing the bottom-right corner of the content), you would set the origin to be 100,100, not -100,100.
I was doing a bunch of useless conversion between coordinate spaces, trying to pass the viewport a point in its coordinate system.
Related
Example
The square is a screen and the diamond is an image
Example
In words
I want my image to be located always at the same point of the screen with any resolution and the image resizes only if the image touches the boundaries of the screen and it always resizes with constant propotions of its width and height (say 16:9) (I have a grid drawed on the image and I need to keep cells of it squared).
Summary
I need to resize the image only if the screen size is smaller in width or height than the image is, else never the image never scales.
What I've tried
For the ideal resolution I took 16:9 and I needed to scale my image, as I described above.
I've tried to use any combination of anchors, and the best one I've got is
use Canvas scaler of the canvas with the image with Scale with screen size checked and
Match width or height equals 1
make anchors of the image located in the center of the image. And everything's good except if the width of the image touches the screen left or right boundaries it does not scale.
Otherwise, if I use Match width or height equals 0 the image does not scale if it touches the screen's upper or lower boundaries.
And, finally, if Match width or height equals 0.5, or other between 0 and 1, the image scales always, but I needed to scale it only if it touches the screen boundaries.
Is it possible to draw a line at the border of the screen, like an "inline" with a consistent width of lets say 10 pixels, so that it aligns to the edges of the screen, even at the rounded corners?
Like this:
normal screen
with the wanted inline (Orange line)
Is there a unity solution? (Otherwise any other solution would be great too! Android studio maybe?)
What I want to achieve is a line that's always the same shape as the screen borders, on every screen, no matter the radius of the corners
I don't think this is possible in normal ways, since Unity considers the screen as a rectangle, so it will give you no information about the shape of the corners of your screen.
However, it is not impossible. You can use SystemInfo.deviceModel to get the model of a device and then you can retrieve information of its screen shape from a server or something like that.
The only necessary information the server needs to store is the radius of the corner. If its 0, means the screen is a rectangle, otherwise the screen is rounded with the given radius r:
Having this information, you can pass it to a post processing shader that will evaluate the minimum distance from each pixel to the corner of the screen, and if this distance is less than some value you defined, you can paint it differently.
I'd have a program that will allow you to draw lines over an image which will eventually be used for calculating distance.
To make things simple, my current image (which is in a PictureBox) is an image of a ruler. When you left click, a path is created and drawn.
Originally, to zoom in, I had it so that a new bitmap would be created with the images new size and I was able to use Graphics.ScaleTransform and it worked fine but it would just crop the image.
I needed the image to actually change width and height so now what I'm doing is just adding/subtracting a constant zoom amount to the width & height when zooming in/out.
With this approach, I can't seem to scale the graphics and the paths are skewed into different directions and not the right size when the image is zoomed in.
I completely understand why this is happening, because the image is getting larger and the graphics are staying the same, I just need whatever math is required to scale the graphics.
I've tried using Graphics.ScaleTransform as well as moving the graphics x & y to their current position + the current zoom amount (offset)
As directed by #TaW I changed the zooming functionality to calculate a new Width & Height based on the whatever zoom was applied then create a new Bitmap which contained the original image with the new width and height.
I am able to add an image to my map just fine via code.
However when I zoom in/out, the image stays the same. I would like it scale relative to the map.
In the WPF version of the Map, you could use an ImageBrush for a MapPolygon and it would be constrained to the bounding box.
I tried the solution from this SO question, but it seems to have no effect on the Image.
imageLayer.Children.Clear();
MapLayer.SetPosition(_vm.RadarImage, new Location(_vm.Overlay.LatN, _vm.Overlay.LonW));
imageLayer.Children.Add(_vm.RadarImage);
shapeLayer.Shapes.Clear();
var rect = new MapPolygon();
rect.Locations.Add(new Location(_vm.Overlay.LatN, _vm.Overlay.LonW));
rect.Locations.Add(new Location(_vm.Overlay.LatS, _vm.Overlay.LonW));
rect.Locations.Add(new Location(_vm.Overlay.LatS, _vm.Overlay.LonE));
rect.Locations.Add(new Location(_vm.Overlay.LatN, _vm.Overlay.LonE));
rect.FillColor = Colors.Green;
shapeLayer.Shapes.Add(rect);
mappy.SetView(new LocationRect(new Location(_vm.Overlay.LatN + 0.0001, _vm.Overlay.LonW + 0.0001), new Location(_vm.Overlay.LatS - 0.0001, _vm.Overlay.LonE - 0.0001)));
This is the correct scaling.
When you zoom once via the Navigation, you can see the image is now larger than the Polygon
There isn't a simple solution for this. I have put together a sample app that shows one approach to do this. You can find it here: http://code.msdn.microsoft.com/Binding-and-Image-to-a-01a56e48 What I did was add a Canvas to the map, and then use the map to calculate the pixel coordinates of the bounding box for the image. I then used these pixel coordinates to scale and position the image on the canvas overtop the map. I've done something similar to create custom polygons that support image brushes in the past but haven't uploaded that code sample yet.
i have a big image about :(14848 PX width * 14336 PX height ) i used deep-zoom tool and exported the files to silverlight, and used a 'multi scale image' control.
the multiscaleimage is 400*400 px .
after the projects starts i want when i click on some where in the image to know the 'real' coordinates for the real image not for the width of the multiscale control,considering the panning and the zooming factor...
so if there is a way to know that.i hope you guys help me.
thanx in advance.
The MSDN docs on this are hopeless. This blog post actually defines the terms:
Logical Coordinates – is a normalized value (0 to 1) representing a coordinate in the image itself (not the control)
Element Coordinates – is the actual control coordinates. For example in a MultiScaleImage of Width=800, Height =400, when the mouse is at the center, the element coordinates are 400,400. These coordinates are not normalized.
[I copied that definition from the blog post, but the example seems to be wrong: the element coordinates should be 400,200.]
It's clear then that you want MultiScaleImage.ElementToLogicalPoint to convert mouse coordinates to image coordinates.
The image coordinates are in the range [0,1]. Simply multiply by the original image width/height to get pixel coordinates in the original image.