I am using the WriteableBitmapEx extension method to rotate a WriteableBitmap. bi in the code is a WritableBitmap. The RotateFree method rotates the bitmap in any degree and returns a new rotated WritableBitmap. My code:
private void rotate()
{
degree += 1;
var rotate = bi.RotateFree(degree);
ImageControl.Source = rotate;
}
My problem is since the ImageControl size is fixed, it causes the rotated bitmap to be clipped. So what is the best way to prevent this? I guess I am looking for a way to resize the ImageControl during rotation to prevent clipping. Any suggestions?
UPDATE
Based on this useful info Calculate rotated rectangle size from known bounding box coordinates I think I managed to calculate the bounding box width (bx) and height(by) and resize it accordingly during the rotation
double radian = (degree / 180.0) * Math.PI;
double bx = rotate.PixelWidth * Math.Cos(radian) + rotate.PixelHeight * Math.Sin(radian);
double by = rotate.PixelWidth * Math.Sin(radian) + rotate.PixelHeight * Math.Cos(radian);
While it appears that the ImageControl width and height increases/decreases during rotation, the image is still being clipped.
UPDATE 2
Based on #Rene suggestion, I managed to prevent the clipping. Combined with the ImageControl Width/Height calculation, the image size is retained during rotation by also setting its stretch property to NONE.
The issue now is to make sure the ImageControl resize from its center so that it does not appear moving. I can include a sample project if anyone interested
UPDATE 3
For those who might be interested the final solution. This is how I do it. The result is, the image is rotated without clipping and its size is retained during rotation. In addition, the rotation appears to originate from the center.
To adjust the ImageControl position as it's resizing so that the rotation appears to originated from center, I use this code.
var translationDelta = new Point((ImageControl.ActualWidth - bx) / 2.0, (ImageControl.ActualHeight - by) / 2.0);
UpdateImagePosition(translationDelta);
ApplyPosition();
// This code update the ImageControl position on the canvas
public void UpdateImagePosition(Point delta)
{
var newPosition = new Point(ImagePosition.X + delta.X, ImagePosition.Y + delta.Y);
ImagePosition = newPosition;
}
//This apply the new position to make the ImageControl rotate from center
public void ApplyPosition()
{
ObjComposite.TranslateX = ImagePosition.X;
ObjComposite.TranslateY = ImagePosition.Y;
}
Use RotateFree with the crop parameter set to false: RotateFree(degree, false). Also set the ImageControl Stretch property to Uniform: Stretch="Uniform".
rene
Related
Have C# forms application with PictureBox embedded in Panel, to take advantage of Panel AutoScroll as suggested in other posts when image and thus PictureBox need to be scrolled horizontally or vertically. Want to zoom the image and recompute AutoScrollPosition to keep same Point visible after zooming. Can double size of PictureBox, then recopy source image, accomplishing zoom. But AutoScrollPosition remains unchanged, thus what was visible before zoom has moved off screen.
How to recompute AutoScrollPosition to keep image focus after zoom?
There are three typical types of zooming:
zoom into the center, triggered by zoom buttons
zoom into the mouse position, triggered by clicking or scroll-wheeling
zoom into a rectangle, by drawing a rectangle
I assume the typical setup: A PictureBox set to SizeMode=Zoom nested in a Panel with AutoScroll=true and zooming that takes care to keep the aspect ratios of Image and PictureBox equal.
Let's start by introducing terminology:
There is an Image we call bitmap and
it is displayed by a PictureBox; let's call it canvas..
.. which is nested in a Panel we call frame
User-friendly zooming needs a fixed point, that is a point that shall stay put.
For 1) it is the center of the frame, for 2) it is the mouse location and for 3) it is the center of the rectangle.
Before zooming we calculate the old zoom ratio, the fixed point in the frame, the fixed point in the canvas and finally the fixed point in the bitmap.
After zoming we calculate the new zoom ratio and the new fixed point in the canvas. Finally we use it to move the canvas to bring the fixed canvas point to the fixed frame point.
Here is an example for zooming into the (current) center; it is a common click event for two buttons and it only doubles and halves the zoom ratio.
Much finer grained factors are of course simple to implement; even better is a fixed list of zoom levels, like Photoshop has!
private void zoom_Click(object sender, EventArgs e)
{
PictureBox canvas = pictureBox1;
Panel frame = panel1;
// Set new zoom level, depending on the button
float zoom = sender == btn_ZoomIn ? 2f : 0.5f;
// calculate old ratio:
float ratio = 1f * canvas.ClientSize.Width / canvas.Image.Width;
// calculate frame fixed pixel:
Point fFix = new Point( frame.Width / 2, frame.Height / 2);
// calculate the canvas fixed pixel:
Point cFix = new Point(-canvas.Left + fFix.X, -canvas.Top + fFix.Y );
// calculate the bitmap fixed pixel:
Point iFix = new Point((int)(cFix.X / ratio),(int)( cFix.Y / ratio));
// do the zoom
canvas.Size = new Size( (int)(canvas.Width * zoom), (int)(canvas.Height * zoom) );
// calculate new ratio:
float ratio2 = 1f * canvas.ClientSize.Width / canvas.Image.Width;
// calculate the new canvas fixed pixel:
Point cFix2 = new Point((int)(iFix.X * ratio2),(int)( iFix.Y * ratio2));
// move the canvas:
canvas.Location = new Point(-cFix2.X + fFix.X, -cFix2.Y + fFix.Y);
}
Note that while one can try to restore the relative AutoScrollValues this is not only hard, because their values are a little quirky but it is also won't be adaptable to the other zoom types.
I need to pan an image at a preset Scale level (no zoom). When panning from left to right, I need the left-side of the image to disappear as the leading right edge appears. This would give the image the appearance of a video as the camera pans the scene. What I've done so far:
double origX = 0.0;
double origY = 0.5;
double newx;
double newy;
image1.RenderTransformOrgin = new Point(origX, origY);
image1.RenderTransform = new ScaleTransform(2.0, 2.0);
if (newX == 0.0)
{
image1.RenderTransformOrgin = new Point(origX, origY);
}
else
{
image1.RenderTransformOrgin = new Point(newX, newY);
}
Using this code I am able to start the virtual "camera lens" on the left and then move it to any other point of the image. By manually increasing newX's value incrementally I am able to create the effect I need. My question is how best to increase the newX value to produce a smooth, animated appearance? Thank You.
I'm having issues trying to not only clip, but to "extract" a portion of a WPF Canvas. So basically I would like the "Clip" to expand to the full size of the window, or convert the clipped item to separate UI Element for exporting to PNG. I write pseudocode because the real code comes from an Autocad model.
double oPrintWidth=1169;
Canvas c = new Canvas();
c.Width = oPrintWidth * 2.54;
c.Height = c.Width * ratio;
// Define the path to clip
string thisPathData = "M12233 M222333 M3443" // fake
c.Clip = Geometry.Parse(thisPathData);
At this point I have the same size canvas but everything other than my path is now black. And the path is still in the original position. I need to now make the clip the entire canvas.
I have played with RenderTransform but I'm lost as what to do next, I'm not so good with matrix calculations.
Original Canvas (Collection of UI Elements)
AFTER CLIP
DESIRED RESULT
Ultimately this would be printed but would prefer to keep it in WPF until last minute to retain VECTOR properties for translating to SVG/XPS/ETC
To make a Clip of the entire Canvas and then apply that Clip to the Canvas I recommend you let WPF do it for you be setting the ClipToBounds property:
Canvas c = new Canvas();
c.ClipToBounds = true;
If that doesn't suit your needs, I would look at the Margin, ActualWidth, and ActualHeight properties to determine the clip region. Then create a RectangleGeometry that matches the size of your Canvas.
EDIT in response to your comments.
Well, I've had some time to work at it some more. What I have been able to do is create a clip region, then I transformed the canvas so that the clip region filled the canvas as much as possible. I think this is what you are after...
First of all I needed to measure the clipped region:
Rect bounds = canvas.Clip.Bounds;
double scaleX = c.Width / (bounds.Right - bounds.Left);
double scaleY = c.Height / (bounds.Bottom - bounds.Top);
This scaling information is used to make the clipped region fit exactly to the size of the canvas.
Now, we need to apply transformations to the canvas:
TransformGroup group = new TransformGroup();
TranslateTransform move = new TranslateTransform(-bounds.Left, -bounds.Top);
ScaleTransform scale = new ScaleTransform(scaleX, scaleY);
group.Children.Add(move);
group.Children.Add(scale);
canvas.RenderTransform = group;
So what is happening here? First of all, the objective is to apply a couple transformations. We need to center the clipped region (translation) and we need to make the clipped region larger (scale). Now, when I say clipped region, I mean the contents of that region. In actuality, we are moving the canvas's rendered output. Moving the region bounds is not what we want to do.
To do this in WPF, we need to add each transformation we want to a child of a TransformGroup.
In this case, we are translating the canvas's output so that its top-left corner is (0, 0) This is necessary because afterwards we will scale the rendered output. So, now, we need to scale the canvas's output so that the image fits as large as it can. To do this, we need to create a ratio that compares the canvas size to the clipped region size.
Here is the formula for scaling the output:
ratio = canvasSize / clippedSize
scaledSize = clippsedSize * ratio
Now, scaling the canvas's output will allow the clipped region to appear as large as possible.
Take a look at the results. Here are images demonstrating the canvas's output before and after the transformations are applied:
Before
After
I figure I might as well give you all the code I used:
Canvas c = new Canvas();
double oPrintWidth=100;
double ratio = .89;
c.Width = oPrintWidth * 2.54;
c.Height = c.Width * ratio;
c.Background = new ImageBrush((ImageSource)FindResource("TestImage")) { Stretch = Stretch.UniformToFill };
// Define the path to clip
string newPath = "M 64,64 L 64,128 128,128, 128,64 Z";
c.Clip = Geometry.Parse(newPath);
Rect bounds = c.Clip.Bounds;
double scaleX = c.Width / (bounds.Right - bounds.Left);
double scaleY = c.Height / (bounds.Bottom - bounds.Top);
TransformGroup group = new TransformGroup();
TranslateTransform move = new TranslateTransform(-bounds.Left, -bounds.Top);
ScaleTransform scale = new ScaleTransform(scaleX, scaleY);
group.Children.Add(move);
group.Children.Add(scale);
c.RenderTransform = group;
MyBorder.Child = c;
And the XAML:
<Window.Resources>
<BitmapImage UriSource="uvtest.jpg" x:Key="TestImage"/>
</Window.Resources>
<Grid Background="Gray">
<Border x:Name="MyBorder" Background="White" BorderBrush="Black" BorderThickness="2" HorizontalAlignment="Center" VerticalAlignment="Center"/>
</Grid>
I have a WPF Image control already working in my application. Using ScaleTransform and TranslateTransform, the Image control has zooming and panning functionality working very well.
I was wondering if there is any way to display certain rectangle area of the image source in the Image control using ScaleTransform and TranslateTransform. In order to do that, I think I need to get/set rectangle coordinates of the image source in view port of the Image control. But it seems that I can't find any reference on this.
I think CroppedBitmap can help you:
<CroppedBitmap x:Key="croppedImage"
Source="{StaticResource masterImage}" SourceRect="30 20 105 50"/>
Lucky for me, the rectangles have all the same size so I could find easily a fixed scale value for ScaleTrensformation such as 5.0 which will fit each rectangle into the view port. Once that determined, I could come up with following function to calculate values for TranslateTransform in terms of coordinate in the image. Hope it may help people in a similar situation.
public void SetImageCoordinate(double x, double y)
{
TransformGroup transformGroup = (TransformGroup)image.RenderTransform;
ScaleTransform transform = (ScaleTransform)transformGroup.Children[0];
ImageSource imageSource = image.Source;
BitmapImage bitmapImage = (BitmapImage) imageSource ;
//Now since you got the image displayed in Image control. You can easily map the mouse position to the Pixel scale.
var pixelMousePositionX = -(x ) / bitmapImage.PixelWidth * transform.ScaleX * image.ActualWidth;
var pixelMousePositionY = -(y) / bitmapImage.PixelHeight * transform.ScaleY * image.ActualHeight;
//MessageBox.Show("X: " + pixelMousePositionX + "; Y: " + pixelMousePositionY);
var tt = (TranslateTransform)((TransformGroup)image.RenderTransform).Children.First(tr => tr is TranslateTransform);
tt.X = pixelMousePositionX;
tt.Y = pixelMousePositionY;
}
I'm working on plotting program in WPF using the canvas element. What I want to achieve is a scrollbar with draggable endpoints. Example of these kinds of scrollbars are in the After Effects video editing software by Adobe.
Basic functionality of such a scrollbar is that it is able to scroll trough content that is bigger then it's container, but both the left and right endpoint can be dragged to dynamically change the scale of the content.
I have implemented a similar scrollbar in the plotting program; users should be able to drag around the in and outpoint (Rectangles in a canvas), and the plot canvas should respond to this by scaling to the desired range.
Information I need for this:
Width of the total plot (amount of plotpoints)
Width of the container (static, 600px)
Percentage of the in and out points relative to the total width of the scrollbar canvas
Link to current screenshot
With this information I have created a MatrixTransform, using the ScaleAt() method to scale the plot canvas inside the container so that it matches the in and outpoints in the scrollbar below. For this I used the following code. resetTransform gets called FPS times a second to keep up with the incoming data and XMAX and YMAX are updated elsewhere to reflect this.
public void resetTransform(Boolean useSlider = false)
{
//Add transformgroup to plot
double yscale = plot.Height / view.YMAX; //YMAX is maximum plot value received
double xscale = plot.Width / view.XMAX; //XMAX is total ammount of plotted points
Matrix m = new Matrix(1, 0, 0, 1, 0, 0);
if (useSlider)
{
double maxVal = zoomBar.ActualWidth - outPoint.Width;
double outP = Canvas.GetLeft(outPoint); //points position relative to the scrollbar
double inP = Canvas.GetLeft(inPoint);
double center = (((outP + inP) / 2) / maxVal) * plot.ActualWidth;
double delta = (outP-inP);
double factor = (maxVal/delta) * xscale;
double mappedinP = (inP / maxVal) * view.XMAX;
double anchorOut = (outP / maxVal) * view.XMAX;
double anchorIn = (inP / maxVal) * view.XMAX;
m.ScaleAt(factor, -yscale,center,0); //scale around the center point,
m.Translate(0, plot.Height); //to compensate the flipped graph, move it back down
}
scale = new ScaleTransform(m.M11, m.M22, 0, 0); //save scale factors in a scaletransform for reference
signals.scaleSignalStrokes(scale); //Scale the plotlines to compensate for canvas scaling
MatrixTransform matrixTrans = new MatrixTransform(m); //Create matrixtransform
plot.RenderTransform = matrixTrans; //Apply to canvas
}
Expectation: Everything should work and the plotted graph would scale nicely when the amount of plotpoints grows over time. Reality: The graph scales when moving the points around, but it is not representative; Moreover, the more plot points are added, the more the whole canvas shifts to the right and the less control I seem to have over the transformation. The algorithm as it is now is probably to wrong approach to get the result I need, but I have spent quite some time thinking how to do this right.
Update
I have uploaded a video to give a clearer picture on the interaction. In the video you can clearly see the canvas shifting to the right.
Screencapture video
How should I scale the canvas (plot) to fit within two boundaries?
So, after some struggling, I found the right algorithm to solve this problem. I will post the adjusted version of the resetTransform function below:
//Reset graph transform
public void resetTransform(Boolean useSlider = false)
{
double yscale = plot.Height / view.YMAX; //YMAX is maximum plot value received
double xscale = plot.Width / view.XMAX; //XMAX is total ammount of plotted points
Matrix m = new Matrix(1, 0, 0, 1, 0, 0);
if (useSlider)
{
double maxVal = zoomBar.ActualWidth - outPoint.Width;
double outP = Canvas.GetLeft(outPoint); //points position relative to the scrollbar
double inP = Canvas.GetLeft(inPoint);
double delta = (outP-inP);
double factor = (maxVal/delta) * xscale;
anchorOut = (outP / maxVal) * view.XMAX; //Define anchorpoint coordinates
anchorIn = (inP / maxVal) * view.XMAX;
double center = (anchorOut +anchorIn)/2; //Define centerpoint
m.Translate(-anchorIn, 0); //Move graph to inpoint
m.ScaleAt(factor, -yscale,0,0); //scale around the inpoint, with a factor so that outpoint is plot.Height(=600px) further away
m.Translate(0, plot.Height); //to compensate the flipped graph, move it back down
}
scale = new ScaleTransform(m.M11, m.M22, 0, 0); //save scale factors in a scaletransform for reference
signals.scaleSignalStrokes(scale); //Scale the plotlines to compensate for canvas scaling
MatrixTransform matrixTrans = new MatrixTransform(m); //Create matrixtransform
plot.RenderTransform = matrixTrans; //Apply to canvas
}
So rather than scaling around the centre point, I should first translate the image and then scale around the origin of the canvas with a factor. This means the other side of the canvas is exactly plot.Height pixels away (with some added scaling)
Everything seems to work fine now, but because I am using custom controls (draggable Rectangles in a canvas) I notice these rectangles do not always fire the mousse events.
Since it is out of the scope of this question, I've described the issue further in this post