Smooth image clipping while scrolling - c#

So I'm trying to achieve an affect like the native cropper when you use a PhotoChooserTask. If you aren't familiar, there's an image in the background with a border over top, and the image appears to change opacity when in and out of the border. The following code works fine, but it's not terribly smooth (the clipped image slightly lags behind the dragging movement). Is there any way to speed up the clipping or change things so that there's less lag? I have the following XAML:
<Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0" HorizontalAlignment="Center" VerticalAlignment="Center">
<Grid x:Name="cropContainer" Width="375" MouseLeftButtonDown="cropContainer_MouseLeftButtonDown" MouseLeftButtonUp="cropContainer_MouseLeftButtonUp" MouseMove="cropContainer_MouseMove">
<Grid.RenderTransform>
<TranslateTransform x:Name="cropTransform" />
</Grid.RenderTransform>
<Image x:Name="fullImage" Source="{Binding Image}" Opacity=".5" />
<Image x:Name="clippedImage" Source="{Binding Image}">
<Image.Clip>
<RectangleGeometry Rect="0,0,375,267" />
</Image.Clip>
</Image>
</Grid>
</Grid>
<Border x:Name="cropBorder" Grid.Row="1" BorderThickness="1" BorderBrush="White" Opacity="1" Height="267.90571169537624660018132366274" Width="375">
</Border>
and here is the code for tracking and clipping during the sliding:
private Point _cropBorderOffset, _original, _newPosition;
private bool _moving;
private double _maxUp, _maxDown;
void Page_Loaded(object sender, RoutedEventArgs e)
{
var transform = cropBorder.TransformToVisual(Application.Current.RootVisual);
_cropBorderOffset = transform.Transform(new Point(0, 0));
_maxDown = ((fullImage.ActualHeight - cropBorder.ActualHeight) / 2);
_maxUp = -_maxDown;
var rect = (clippedImage.Clip as RectangleGeometry).Rect;
(clippedImage.Clip as RectangleGeometry).Rect = new Rect(rect.X, (fullImage.ActualHeight / 2) - 134, rect.Width, rect.Height);
}
private void cropContainer_MouseLeftButtonDown(object sender, System.Windows.Input.MouseButtonEventArgs e)
{
cropContainer.CaptureMouse();
_original = e.GetPosition(cropBorder);
_moving = true;
}
private void cropContainer_MouseLeftButtonUp(object sender, System.Windows.Input.MouseButtonEventArgs e)
{
cropContainer.ReleaseMouseCapture();
_moving = false;
}
private void cropContainer_MouseMove(object sender, System.Windows.Input.MouseEventArgs e)
{
_newPosition = e.GetPosition(cropBorder);
if(_moving)
{
var deltaY = _newPosition.Y - _original.Y;
var transform = fullImage.TransformToVisual(Application.Current.RootVisual);
var fullImageOffset = transform.Transform(new Point(0, 0));
var marginTop = deltaY;
//Too far down
if (marginTop > _maxDown)
{
marginTop = _maxDown;
}
//Too far up
else if (marginTop < _maxUp)
{
marginTop = _maxUp;
}
cropTransform.Y = marginTop;
var rect = (clippedImage.Clip as RectangleGeometry).Rect;
(clippedImage.Clip as RectangleGeometry).Rect = new Rect(rect.X, _cropBorderOffset.Y - fullImageOffset.Y, rect.Width, rect.Height);
}
}

Instead of drawing the image twice, once with clipping and once without, could you not simply draw semi-transparent rectangles to block out the sides and draw a white outlined for the center? That should reduce lag considerably.
You could then use the left, top, width and height of the clipping rectangle once the position is chosen to actually do the crop in-memory.

Related

Move an image over a canvas in windows store app

I need to move an image over a canvas on tap and slide. How can I achieve this. I tried the following. The image is moving but not as the user moves it.
XAML
<Canvas Background="White">
<Image Name="img" Width="200" Height="200" Source="Assets/11.png" ManipulationMode="All" ManipulationStarted="img_ManipulationStarted" ManipulationDelta="img_ManipulationDelta"/>
</Canvas>
C#
private Point initialPt;
private void img_ManipulationStarted(object sender, ManipulationStartedRoutedEventArgs e)
{
initialPt = e.Position;
}
private void img_ManipulationDelta(object sender, ManipulationDeltaRoutedEventArgs e)
{
Point currentPt = e.Position;
double x = currentPt.X - initialPt.X;
double y = currentPt.Y - initialPt.Y;
if (x != 0 || y != 0)
{
TranslateTransform posTransform = new TranslateTransform();
posTransform.X = currentPt.X;
posTransform.Y = currentPt.Y;
img.RenderTransform = posTransform;
e.Complete();
}
}
Instead of using a TranslateTransform, you should directly set the absolute position in the canvas, so you have to bind the ManipulationDelta event to the Canvas, and detect if the point of impact is inside of the image.
<Canvas Background="White" ManipulationMode="All" ManipulationDelta="canvas_ManipulationDelta">
<Image Name="img" Width="200" Height="200" Source="Assets/11.png"/>
</Canvas>
Here is the new event handling function:
private void canvas_ManipulationDelta(object sender, ManipulationDeltaRoutedEventArgs e)
{
if ((e.Position.X > Canvas.GetLeft(img) && e.Position.X < Canvas.GetLeft(img) + img.Width)
|| (e.Position.Y > Canvas.GetTop(img) && e.Position.Y < Canvas.GetTop(img) + img.Height)) {
{
Canvas.SetLeft(img, e.Position.X);
Canvas.SetTop(img, e.Position.Y);
}
}
Simple as pie. You can remove initialPt and img_ManipulationStarted.

Windows phone 8 apps Image pinch/zoom in data template using canvas

Windows Phone 8 apps i am using image in pivot control inside data template and then using pinch/zoom image But My Problem is if i am minimize image so small then it not zooming image..How to use canvas in data template.
MY XAML
<phone:Pivot x:Name="pivot" ItemsSource="{Binding}" VerticalAlignment="Stretch" HorizontalAlignment="Stretch" >
<phone:Pivot.HeaderTemplate>
<DataTemplate>
<TextBlock Name="title" FontWeight="ExtraBold" Text="{Binding _textview}" HorizontalAlignment="Center" FontSize="30" Foreground="Orange"></TextBlock>
</DataTemplate>
</phone:Pivot.HeaderTemplate>
<phone:Pivot.ItemTemplate>
<DataTemplate>
<Grid>
<Image Margin="0,-30,0,80" Name="img" Stretch="Fill" Source="{Binding _images}" RenderTransformOrigin="0.5, 0.5" CacheMode="BitmapCache">
<Image.RenderTransform>
<CompositeTransform x:Name="transform" />
</Image.RenderTransform>
<toolkit:GestureService.GestureListener>
<toolkit:GestureListener PinchStarted="OnPinchStarted" PinchDelta="OnPinchDelta" DoubleTap="GestureListener_DoubleTap" DragDelta="GestureListener_DragDelta" />
</toolkit:GestureService.GestureListener>
</Image>
</Grid>
</DataTemplate>
</phone:Pivot.ItemTemplate>
</phone:Pivot>
MY CS
private void OnPinchStarted(object sender, PinchStartedGestureEventArgs e)
{
var image = sender as Image;
var transform = image.RenderTransform as CompositeTransform;
angle = transform.Rotation;
scale = transform.ScaleX;
}
private void OnPinchDelta(object sender, PinchGestureEventArgs e)
{
var image = sender as Image;
var transform = image.RenderTransform as CompositeTransform;
transform.Rotation = angle + e.TotalAngleDelta;
transform.ScaleX = scale * e.DistanceRatio;
transform.ScaleY = scale * e.DistanceRatio;
}
private void GestureListener_DoubleTap(object sender, GestureEventArgs e)
{
var image = sender as Image;
var transform = image.RenderTransform as CompositeTransform;
transform.ScaleX = transform.ScaleY = 1;
}
private void GestureListener_DragDelta(object sender, DragDeltaGestureEventArgs e)
{
var image = sender as Image;
var transform = image.RenderTransform as CompositeTransform;
transform.TranslateX += e.HorizontalChange;
transform.TranslateY += e.VerticalChange;
}
when you need to show an image in canvas,
<Canvas>
<Image Margin="0,-30,0,80" Name="img" Stretch="Fill" Source="{Binding _images}" RenderTransformOrigin="0.5, 0.5" CacheMode="BitmapCache">
<Image.RenderTransform>
<CompositeTransform x:Name="transform" />
</Image.RenderTransform>
</Image>
</Canvas>
For further reference, here is a great official sample
You can prevent pinching the image too small by limiting the scale to a minimum size.
// Don't shrink the image to less than 1/4 normal size.
const double minScaleX = 0.25;
const double minScaleY = 0.25;
private void OnPinchDelta(object sender, PinchGestureEventArgs e)
{
var image = sender as Image;
var transform = image.RenderTransform as CompositeTransform;
transform.Rotation = angle + e.TotalAngleDelta;
transform.ScaleX = scale * e.DistanceRatio;
transform.ScaleY = scale * e.DistanceRatio;
if (transform.ScaleX < minScaleX)
{
transform.ScaleX = minScaleX;
}
if (transform.ScaleY < minScaleY)
{
transform.ScaleY = minScaleY;
}
}

How to get the portion of image inside rectangle for cropping , windows phone 8

I have an image, and a rectangle on top of it.The image can be zoomed and dragged .I need to get the portion of the image inside the rectangle after these operations and save it as a jpeg.
This is my code,
private void OnPinchStarted(object sender, PinchStartedGestureEventArgs e)
{
initialAngle = compositeTransform.Rotation;
initialScale = compositeTransform.ScaleX;
}
private void OnPinchDelta(object sender, PinchGestureEventArgs e)
{
if (1.0 <= (initialScale * e.DistanceRatio))
{
compositeTransform.ScaleX = initialScale * e.DistanceRatio;
compositeTransform.ScaleY = initialScale * e.DistanceRatio;
}
}
private void OnDragDelta(object sender, DragDeltaGestureEventArgs e)
{
compositeTransform.TranslateX += e.HorizontalChange;
compositeTransform.TranslateY += e.VerticalChange;
}
and my XAML is
<Image x:Name="mypic" RenderTransformOrigin="0.5, 0.5" CacheMode="BitmapCache" HorizontalAlignment="Left" Margin="79,0,0,0" VerticalAlignment="Top" Height="345" >
<Image.RenderTransform>
<TransformGroup>
<ScaleTransform x:Name="scale" />
<TranslateTransform x:Name="transform" />
<CompositeTransform x:Name="compositeTransform"/>
</TransformGroup>
</Image.RenderTransform>
<toolkit:GestureService.GestureListener>
<toolkit:GestureListener DragDelta="OnDragDelta" PinchStarted="OnPinchStarted" PinchDelta="OnPinchDelta" />
</toolkit:GestureService.GestureListener>
</Image>
<Rectangle x:Name="rect" Fill="#FFF4F4F5" HorizontalAlignment="Left" Height="250" Margin="111,26,0,0" Stroke="Black" VerticalAlignment="Top" Width="233" Opacity="0.1" StrokeThickness="3"/>
<Button Content="Button" HorizontalAlignment="Left" Margin="275,396,0,0" VerticalAlignment="Top" Click="ClipImage"/>
In the ClipImage method how do I get the cropped image? Im not using canvas here.. Is using canvas mandatory for this?In case Im moving the rectangle i can call CompositionTarget.Rendering to get the current x,y points .How do I proceed in this scenario?
EDIT:
Im now able to get portion of image inside rectangle with the following code..
void ClipImage()
{
RectangleGeometry geo = new RectangleGeometry();
r = (Rectangle)rect;
GeneralTransform gt = r.TransformToVisual(LayoutRoot);
Point p = gt.Transform(new Point(0, 0));
geo.Rect = new Rect(p.X, p.Y, (r.Width) + 50, (r.Height) + 50);
mypic.Clip = geo;
r.Visibility = System.Windows.Visibility.Collapsed;
TranslateTransform t = new TranslateTransform();
t.X = -p.X;
t.Y = -p.Y;
mypic.RenderTransform = t;
}
This works fine when I don't pan or zoom the image, but when the image is zoomed by user, this doesnt work.Can some one pls help..

How to animate image zoom change in WPF?

I am building an image viewer.
I have created a functionality that when the user moves the slider, it changes the ZoomLevel property value. This subsequently zooms the image based on the value provided by the ZoomLevel property.
<Image
Name="image"
Source="Sample1.jpg"
Opacity="1"
VerticalAlignment="Center"
HorizontalAlignment="Center"
Stretch="None"
RenderTransformOrigin="0.5,0.5"
RenderOptions.BitmapScalingMode="HighQuality">
<Image.RenderTransform>
<TransformGroup>
<ScaleTransform
ScaleX="{Binding Path=ZoomLevel}"
ScaleY="{Binding Path=ZoomLevel}" />
<TranslateTransform />
</TransformGroup>
</Image.RenderTransform>
</Image>
I wish to be able to animate the zooming with a DoubleAnimation. Animation is supposed to start from the current zoom level and then get to the zoom specified in the bound ZoomLevel property. It would be great if someone could provide some help in XAML. Thanks!
Mouse wheel code:
private void border_MouseWheel(object sender, MouseWheelEventArgs e)
{
int delta;
delta = e.Delta;
zoom(delta);
}
private void zoom(int delta)
{
double zoomIncrement;
//Different zoom levels at different zoom stages
if (ZoomLevel > 2)
{
zoomIncrement = Math.Sign(delta) * 0.2;
}
else if (ZoomLevel >= 1 && ZoomLevel <= 2)
{
zoomIncrement = Math.Sign(delta) * 0.1;
}
else
{
zoomIncrement = Math.Sign(delta) * 0.06;
}
//Rounding zoom level to boundary values
//Zooming is allowed from 10% to 600%
if (ZoomLevel + zoomIncrement > 6)
{
ZoomLevel = 6;
}
else if (ZoomLevel + zoomIncrement < 0.1)
{
ZoomLevel = 0.1;
}
else
{
ZoomLevel += zoomIncrement;
}
}
It is a little messy but try this.
namespace Zoom
{
public partial class Window1
{
public double ZoomLevel { get; set; }
public double SlideLevel { get; set; }
public Window1()
{
InitializeComponent();
ZoomLevel = 1.0;
SlideLevel = 1.0;
image.MouseWheel += image_MouseWheel;
}
private void image_MouseWheel(object sender, MouseWheelEventArgs e)
{
double zoom = e.Delta > 0 ? .1 : -.1;
slider.Value = (SlideLevel + zoom);
}
private void ZoomImage(double zoom)
{
Storyboard storyboardh = new Storyboard();
Storyboard storyboardv = new Storyboard();
ScaleTransform scale = new ScaleTransform(ZoomLevel, ZoomLevel);
image.RenderTransformOrigin = new Point(0.5, 0.5);
image.RenderTransform = scale;
double startNum = ZoomLevel;
double endNum = (ZoomLevel += zoom);
if (endNum > 1.0)
{
endNum = 1.0;
ZoomLevel = 1.0;
}
DoubleAnimation growAnimation = new DoubleAnimation();
growAnimation.Duration = TimeSpan.FromMilliseconds(300);
growAnimation.From = startNum;
growAnimation.To = endNum;
storyboardh.Children.Add(growAnimation);
storyboardv.Children.Add(growAnimation);
Storyboard.SetTargetProperty(growAnimation, new PropertyPath("RenderTransform.ScaleX"));
Storyboard.SetTarget(growAnimation, image);
storyboardh.Begin();
Storyboard.SetTargetProperty(growAnimation, new PropertyPath("RenderTransform.ScaleY"));
Storyboard.SetTarget(growAnimation, image);
storyboardv.Begin();
}
private void slider_ValueChanged(object sender, System.Windows.RoutedPropertyChangedEventArgs<double> e)
{
double zoomChange = (SlideLevel - slider.Value) * -1;
SlideLevel = SlideLevel + zoomChange;
ZoomImage(zoomChange);
}
}
}
I found this other stack question to be quite helpful
Here is the current setup of XAML that I have as well.
<Border MaxWidth="500"
MaxHeight="500"
Height="500"
Width="500"
Name="border">
<Image
Name="image"
Source="picture1.png"
Opacity="1"
VerticalAlignment="Center"
HorizontalAlignment="Center"
Stretch="Fill"
RenderTransformOrigin="0.5,0.5"
RenderOptions.BitmapScalingMode="HighQuality"
ClipToBounds="True">
</Image>
</Border>
<Slider
HorizontalAlignment="Left"
Margin="44,70,0,148"
Name="slider"
Width="24"
Value="1.0"
Minimum="0"
Maximum="1.0"
LargeChange="0.1"
Orientation="Vertical"
FlowDirection="LeftToRight"
TickFrequency="0.1"
IsSnapToTickEnabled="False"
ValueChanged="slider_ValueChanged" />
Have you tried using a ViewBox? You can bind the final width to any value you need, here's a quick sample:
<Window.Resources>
<Storyboard x:Key="ZoomIn">
<DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="(FrameworkElement.Width)" Storyboard.TargetName="ImageContainer">
<EasingDoubleKeyFrame KeyTime="0:0:0.5" Value="400"/>
</DoubleAnimationUsingKeyFrames>
</Storyboard>
</Window.Resources>
<Grid x:Name="LayoutRoot">
<Viewbox x:Name="ImageContainer" Width="200">
<Image Source="http://images.wikia.com/lossimpson/es/images/a/a7/Homer_Simpson2.png"/>
</Viewbox>
</Grid>

Pinch-to-zoom on huge images?

I found this Pinch-to-zoom example at http://forums.create.msdn.com
Here is the xaml:
<Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0">
<StackPanel>
<TextBlock Text="Tap to center" Style="{StaticResource PhoneTextNormalStyle}"/>
<TextBlock Text="Tap and hold to reset" Style="{StaticResource PhoneTextNormalStyle}"/>
<TextBlock Text="Touch and move to drag" Style="{StaticResource PhoneTextNormalStyle}"/>
<TextBlock Text="Pinch (touch with two fingers) to scale and rotate" Style="{StaticResource PhoneTextNormalStyle}" TextWrapping="Wrap"/>
<TextBlock Text="Flick (drag and release the touch while still moving) will show flick data on bottom of screen." Style="{StaticResource PhoneTextNormalStyle}" TextWrapping="Wrap"/>
</StackPanel>
<TextBlock x:Name="flickData" Text="Flick:" Style="{StaticResource PhoneTextNormalStyle}" VerticalAlignment="Bottom"/>
<Image x:Name="image" Source="/map.jpg" RenderTransformOrigin="0.5,0.5" CacheMode="BitmapCache">
<Image.RenderTransform>
<CompositeTransform x:Name="transform"/>
</Image.RenderTransform>
<toolkit:GestureService.GestureListener>
<toolkit:GestureListener
Tap="OnTap" Hold="OnHold"
DragStarted="OnDragStarted" DragDelta="OnDragDelta" DragCompleted="OnDragCompleted"
Flick="OnFlick"
PinchStarted="OnPinchStarted" PinchDelta="OnPinchDelta" PinchCompleted="OnPinchCompleted"/>
</toolkit:GestureService.GestureListener>
</Image>
</Grid>
And the cs source:
public partial class GestureSample : PhoneApplicationPage
{
double initialAngle;
double initialScale;
public GestureSample()
{
InitializeComponent();
}
private void OnTap(object sender, GestureEventArgs e)
{
transform.TranslateX = transform.TranslateY = 0;
}
private void OnDoubleTap(object sender, GestureEventArgs e)
{
transform.ScaleX = transform.ScaleY = 1;
}
private void OnHold(object sender, GestureEventArgs e)
{
transform.TranslateX = transform.TranslateY = 0;
transform.ScaleX = transform.ScaleY = 1;
transform.Rotation = 0;
}
private void OnDragStarted(object sender, DragStartedGestureEventArgs e)
{
image.Opacity = 0.3;
}
private void OnDragDelta(object sender, DragDeltaGestureEventArgs e)
{
transform.TranslateX += e.HorizontalChange;
transform.TranslateY += e.VerticalChange;
}
private void OnDragCompleted(object sender, DragCompletedGestureEventArgs e)
{
image.Opacity = 1.0;
}
private void OnPinchStarted(object sender, PinchStartedGestureEventArgs e)
{
Point point0 = e.GetPosition(image, 0);
Point point1 = e.GetPosition(image, 1);
Point midpoint = new Point((point0.X + point1.X) / 2, (point0.Y + point1.Y) / 2);
image.RenderTransformOrigin = new Point(midpoint.X / image.ActualWidth, midpoint.Y / image.ActualHeight);
initialAngle = transform.Rotation;
initialScale = transform.ScaleX;
image.Opacity = 0.8;
}
private void OnPinchDelta(object sender, PinchGestureEventArgs e)
{
transform.Rotation = initialAngle + e.TotalAngleDelta;
transform.ScaleX = transform.ScaleY = initialScale * e.DistanceRatio;
}
private void OnPinchCompleted(object sender, PinchGestureEventArgs e)
{
image.Opacity = 1.0;
}
private void OnFlick(object sender, FlickGestureEventArgs e)
{
flickData.Text = string.Format("{0} Flick: Angle {1} Velocity {2},{3}",
e.Direction, Math.Round(e.Angle), e.HorizontalVelocity, e.VerticalVelocity);
}
}
It works pretty well for small images (less then 2000x2000 pixels). But in my example, i have this huge metro map (http://www.vasttrafik.se/upload/Linjekartor_hogupplost/Goteborg2010/Linjen%C3%A4tskarta-101212.png or vector http://www.vasttrafik.se/upload/Linjekartor_hogupplost/Goteborg2010/Linjen%C3%A4tskarta-101212.pdf). It would be even nicer if the user could scale a vector image but even importing such a huge vector is a serious performance issue.
Maybe i could split the image up into several "multi-scale images" and use this http://dotnetbyexample.blogspot.com/2010/08/windows-phone-7-multi-touch-panzoom.html, but i don't really know how to use his class :(
Any ideas? How would you guys solve this problem?
Thanks
Richard
The ideal approach for your solution is to use MultiScaleImage, which is specifically designed to display large image data. However, in order to work with MultiScaleImage you need to get your iamge data prepared int he right format. Basically, you need the image sliced up and rescaled, etc so that the user loads as little information as possible while they zoom in and out of your image.
The DeepZoom documentation describes the process and has links to the DeepZoom Composer tool, which you use to prepare your image data.
Once you've got the MultiScaleImage approach working, you can then look at using Laurent's Multitouch Behavior (if necessary) to provide additional user interactions.
Have you heard of Silverlight Deep-Zoom?
http://msdn.microsoft.com/en-us/library/cc645050(v=vs.95).aspx
There is a size limit on Silverlight UIElements on the phone. As you have discovered, this is 2000x2000 pixels. No single control can be larger than this—hence your issue.
If you must use an image larger than this look at the MultiScaleImage.
Also be aware of the potential for memory issues if you're using very large image files.

Categories