How to zoom image inside rectangle WPF - wpf

I am trying to zoom in and out image inside Rectangle control. But while doing so my entire Rectangle is getting zoomed in instead of just image inside that. for doing so I am using ScaleTransform and TranslateTranform on Rectangle. I should do the same on image instead of Rectangle but I dont know how? Could anyone please help me out.
XAML :
<Rectangle x:Name="LiveViewWindow" Fill="#FFF4F4F5" HorizontalAlignment="Right"
ClipToBounds="True" />
Code:
InteropBitmap m_LiveViewBitmapSource =Imaging.CreateBitmapSourceFromMemorySection(
section, width, height, PixelFormats.Bgr32, width*4, 0) as InteropBitmap;
ImageBrush m_BackgroundFrame = new ImageBrush(m_LiveViewBitmapSource);
RenderOptions.SetBitmapScalingMode(m_BackgroundFrame, BitmapScalingMode.LowQuality);
LiveViewWindow.Fill = m_BackgroundFrame;
n then for I am using Invalidate() property to render InteropBitmap to Rectangle.

You may set the Transform property of the ImageBrush. Get more information in Brush Transformation Overview.

Related

WPF Transform Rectangle in Canvas to Selection in Image

I have a rectangle on a canvas that the user can resize, move and so on to make a selection.
I also have an image the size of the screen behind the canvas (basically a screenshot).
I'd like to translate the selection (the rectangle) in the canvas to a 1:1 selection in the image (I want the image directly behind the rectangle) given I have the rectangle's Canvas.Top, Canvas.Left, Width, Height.
<Grid Name="MainGrid" SnapsToDevicePixels="False" HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
<Image x:Name="MainImage" Stretch="None" RenderOptions.BitmapScalingMode="HighQuality"/>
<Border Background="Black" Opacity="0.4" HorizontalAlignment="Stretch" VerticalAlignment="Stretch"/>
<Canvas Name="MainCanvas" Width="{Binding Source={x:Static SystemParameters.PrimaryScreenWidth}}" Height="{Binding Source={x:Static SystemParameters.PrimaryScreenHeight}}" Background="Transparent">
<ContentControl Name="SelectionRect" />
</ContentControl>
</Canvas>
</Grid>
I tried doing this: (MainImage is the image under the canvas)
Rect rect = new Rect(Canvas.GetLeft(SelectionRect), Canvas.GetTop(SelectionRect), SelectionRect.Width, SelectionRect.Height);
Rect from_rect = SelectionRect.TransformToVisual(this).TransformBounds(rect);
BitmapSource cropped_bitmap = new CroppedBitmap(MainImage.Source as BitmapSource,
new Int32Rect((int)from_rect.X, (int)from_rect.Y, (int)from_rect.Width, (int)from_rect.Height));
SelectionRectImageSource = cropped_bitmap;
But the image I get (SelectionRectImageSource) is a moved aside version of the actual pixels behind the selection rectangle.
So basically, I don't understand how these transformations work and how I should use them if at all.
Example:
Thanks a lot!
Dolev.
Looks like you need to correct for the DPI difference between the image (usually 72dpi) and the presentation source (usually 96dpi). Additionally, your first Rect should not be offset by Canvas.Left and Canvas.Top; TransformToVisual will take care of the relative offset for you.
var source = (BitmapSource)MainImage.Source;
var selectionRect = new Rect(SelectionRect.RenderSize);
var sourceRect = SelectionRect.TransformToVisual(MainImage)
.TransformBounds(selectionRect);
var xMultiplier = source.PixelWidth / MainImage.ActualWidth;
var yMultiplier = source.PixelHeight / MainImage.ActualHeight;
sourceRect.Scale(xMultiplier, yMultiplier);
var croppedBitmap = new CroppedBitmap(
source,
new Int32Rect(
(int)sourceRect.X,
(int)sourceRect.Y,
(int)sourceRect.Width,
(int)sourceRect.Height));
SelectionRectImageSource= croppedBitmap;
Depending on where this code resides, you may also need to transform the selection rectangle to MainImage instead of this (as I did).
Also, in case MainImage.Source is smaller than the actual MainImage control, you should probably set the horizontal and vertical alignments of MainImage to Left and Top, respectively, less your translated rectangle end up outside the bounds of the source image. You'll need to clamp the selection rectangle to the dimensions of MainImage too.

WPF ZoomControl and Adorners

This my first post on stack overflow, I hope I get it right. I am using the ZoomControl from WPF Extensions to display an image with pan and zoom support:
<DockPanel Grid.Row="1" x:Name="canvas">
<Controls:ZoomControl x:Name="zoomControl">
<Canvas x:Name="canvas">
<Image x:Name="imageControl" Stretch="None" />
</Canvas>
</Controls:ZoomControl>
</DockPanel>
When the user selects an image with a bowse dialog, I load that image like so:
bmp = new BitmapImage(new Uri(fileName));
this.imageControul.Source = bmp;
I would like to added rectangles\adorners to specific locations (pixel coordinates) on the image the user loaded based on some image processing.
var r = new Rectangle();
r.StrokeThickness = 5;
r.Stroke = Brushes.Black;
r.Fill = Brushes.Transparent;
r.Width = width;
r.Height = height;
Canvas.SetLeft(r, y);
Canvas.SetTop(r, x);
canvas.Children.Add(r);
However, the rectangles are not placed in the expected locations? Wrong scale and location.
Thanks,
John
I expect the problem is that your Canvas is expanding to fill the space rather than being locked to the rectangle. Have a look with a tool like Snoop and see what the bounding boxes of the two are.
You might be able to fix it with Horizontal and VercticalAlignment on the canvas, set them to anything other than Stretch.
If that doesn't work restructure it like this
<ZoomBox>
<Grid>
<Image/>
<Canvas/>
</Grid>
</ZoomBox>
So the Image and the canvas are grouped by the parent Grid which is being transformed.

How to render (bitmap) only part of a Visual?

I must print a displayed TreeView.
Rendering the root TreeViewItem to bitmap, gives me an image of the whole (even non visible nodes) tree. Then I split the bitmap in "pages" to be printed. The rendering code:
m_Bitmap = new RenderTargetBitmap((int)l_RootTreeViewItem.ActualHeightDesiredSize.Width,
(int)l_RootTreeViewItem.ActualHeight, 96, 96,
PixelFormats.Pbgra32);
m_Bitmap.Render(l_RootTreeViewItem);
Works well for small size trees. If the tree is large, RenderTargetBitmap results in "Out Of Memory" Exception.
So, the idea is to render only parts of the visual to avoid memory problems. A Render method where I can choose which part of visual to render will be perfect...
m_Bitmap.Render(l_RootTreeViewItem, xOffset, yOffset, width, height);
... but doesn't exist. Is there some way to do that ?
What I'll do :
Create a VisualBrush of your l_RootTreeViewItem
Create a Rectangle and assign the visual brush to the Fill property
Play with VisualBrush.Viewbox and VisualBrush.Viewport to render the part of the tree view I'm interested in
Use RenderTargetBitmap.Render on my rectangle when needed
EDIT
Solution 2
Put l_RootTreeViewItem in a canvas
Set the ClipToBounds property of the canvas to true
Play with Canvas.Width, Canvas.Height properties and Canvas.Left, Canvas.Top attached properties to display only a part of the TreeViewItem
Use PrintDialog.PrintVisual on the canvas as needed.
<Canvas Width="300" Height="300" ClipToBounds="True">
<TreeViewItem Canvas.Left="-200" Canvas.Top="-100">
...
</TreeViewItem>
</Canvas>

WPF - Zoom image (inside an constrained sized item control)

I would like to zoom an image in WPF and that the image visual render be inside a constrained sized item control.
For example:
<Canvas x:Name="m_canvas" MaxWidth="300" MaxHeight="300" >
<Image Source="..."
Width="300"
Height="300" />
</Canvas>
The zoom code:
var matrix = ((MatrixTransform)m_image.RenderTransform).Matrix;
var center = new Point(m_image.ActualWidth / 2, m_image.ActualHeight / 2);
center = matrix.Transform(center);
matrix.ScaleAt(delta.Scale.X, delta.Scale.Y, center.X, center.Y);
((MatrixTransform)m_image.RenderTransform).Matrix = matrix;
The problem is that when I'm zooming the image render size go larger that the canvas limit (300x300). I would like if the image can zoom only in the canvas.
I don't want to limit the max zoom, I want that if the render size of the image is larger that the canvas, it's stay inside. I don't want that it overlap the canvas
You could clip to the bounds of the Canvas:
<Canvas ClipToBounds="True" ...>
But I don't understand why you're using a Canvas in the first place. It's likely that there's a much nicer way to approach your particular problem without the need for hard-coded widths and heights and without any Canvas at all.

Why ActualWidth and ActualHeight start from 0,0?

I want to add WPF Path to InkCanvas and use selection to select WPF Path.
So, I use this code.
System.Windows.Shapes.Path path = drawCanvas.Children[i] as System.Windows.Shapes.Path;
drawCanvas.Children.RemoveAt(i);
inkCanvas.Children.Add(path);
This is the output. I have to select WPF Path from 0,0 because Actualwidth and ActualHeight start from 0,0.
How do I select absolute WPF Path?
Thanks
Edit:
Now, I can select it absolutely by using this code.
System.Windows.Shapes.Path path = drawCanvas.Children[i] as System.Windows.Shapes.Path;
drawCanvas.Children.RemoveAt(i);
path.Margin = new Thickness(-getMinX(path), -getMinY(path), 0, 0);
containPath.Children.Add(path);
containPath.Width = getMaxX(path) - getMinX(path);
containPath.Height = getMaxY(path) - getMinY(path);
containPath.Margin = new Thickness(getMinX(path), getMinY(path), 0, 0);
inkCanvas.Children.Add(containPath);
You can use the UIElement.UpdateLayout Method on the InkCanvas to update the FrameworkElement.ActualWidth Property and ActualHeight. See the ActualWidth link for background information on why this is needed.
Edit:
I misunderstood the question. It wasn't that ActualWidth and ActualHeight were zero but that their values were relative to (0, 0) on the InkCanvas. A pretty good solution to the problem is to wrap the Path in a Canvas and position it using Margin like this:
<Canvas Width="50" Height="50" Margin="200,200,0,0">
<Line
X1="0" Y1="0"
X2="50" Y2="50"
Stroke="Black"
StrokeThickness="4" />
</Canvas>
which behaves like:
The disadvantage of this approach is the user has to lasso the Canvas which is a rectangle, not an arbitrary shape. Nevertheless, it's a lot better than having to lasso the object and the origin.
For elements that can only be defined via control points (eg Line, Path etc as against Rectangle, Ellipse etc) when you add it to an items control (which I assume the InkCanvas is since it supports selection), first a canvas is added to the panel at 0,0. The width and the Height of the canvas are determined from the maximum X and Y coordinates of the control points. After this the element is added as a child of this canvas.
You will also see that whenever you see this kind of behaviors with an element, the element wont support Layout Properties like Horizontal/Vertical alignment etc.
The only way I see around this is to find the ActualWidth and ActualHeight manually from the coordinates in the path.
Edit: This is from personal experience and not from any documentation.

Resources