Win2D draw rotated image on canvas - c#

I'm trying to automatically create a photo collage with Win2D library. I've defined a couple of methods to do that:
ICanvasImage RotateImage(ICanvasImage photo, Size size, float degrees) returns a rotated image. I need to pass the image, its size and the degrees number. This method works for other things so I don't think it's that the problem.
ICanvasImage AddImage(ICanvasImage image, Size size, float x, float y, float degrees) it's the guilty. It draws the image on a CanvasRenderTarget with the specified position and size. It first call RotateImage to get the photo with the correct rotation and then draw it on the canvas.
At the end I use a method to save the content of the canvas as an image but it's not important, it works...
If I draw an horizontal image (no rotation => 0 degrees) I have no problem because the destination rectangle to pass to renderer.drawImage() is perfectly align with the photo. If I specified any angle the the image comes rotated correctly but the destination rectangle keeps horizontal an then the photo get cropped where it's out of the bounds of the rectangle.
This is my code:
//photo => photo to rotate
//size => size of the photo
//degrees => degrees to rotate the photo
ICanvasImage RotateImage(ICanvasImage photo, Size size, float degrees)
{
double height = Math.Sqrt(size.Width * size.Width + size.Height*size.Height);
//convert degreese to radians
float radians = (float)(degrees * Math.PI / 180d);
//get x,y where to place the rotated image
float y = (float)((height - size.Height) / 2.0f);
float x = (float)((height - size.Width) / 2.0f);
Vector2 endpoint = new Vector2((float)size.Width / 2, (float)size.Height / 2);
ICanvasImage image = new Transform2DEffect
{
Source = photo,
TransformMatrix = Matrix3x2.CreateRotation(radians, endpoint)
};
return image;
}
//size => final size that I want to have
//x,y => position of the photo
ICanvasImage AddImage(ICanvasImage image, Size size, float x, float y, float degrees)
{
//get the rotated image
ICanvasImage rotatedImage = RotateImage(image, size, degrees);
//start to draw in the canvas
using (var ds = renderer.CreateDrawingSession())
{
//destination rectangle with the specified position and size
Rect destRect = new Rect(x,y,size.Width,size.Height);
//source rectangle of the photo
Rect sourceRect = new Rect(0,0,rotatedImage.GetBounds(renderer).Width,rotatedImage.GetBounds(renderer).Height);
//draw the image on the canvas
ds.DrawImage(rotatedImage,destRect, sourceRect);
}
//here I call the method to save canvas' content as an image...
//you can simply try drawing directly on a canvasControl
}

Related

How can i scale a texture to the given size in pixel with SharpDX.Direct3D9?

I'm currently trying to scale a texture to the given size in pixels via SharpDX.Direct3D9.
I have the following code which draws a texture on the screen (2D)
public static bool DrawTexture(IntPtr device, IntPtr txt, RectangleF rect, float rotation, Color tint)
{
try {
Texture texture = (Texture)txt;
Matrix m = Matrix.Identity * Matrix.Translation(-0.5f, -0.5f, 0.0f) * Matrix.Scaling(rect.Width, rect.Height, 1.0f) * Matrix.RotationZ(rotation) * Matrix.Translation(rect.X, rect.Y, 0.0f);
using (Sprite s = new Sprite((Device)device)) {
s.Begin();
s.Transform = m;
s.Draw(texture, tint.ToRawColorBGRA());
s.End();
}
return true;
}
catch (Exception ex) {
Main.managerInstance.console.PrintError(string.Format("[Direct3D9] An error occured while trying to draw texture. Details: {0}", ex.ToString()));
}
return false;
}
Matrix.Scaling(rect.Width, rect.Height, 1.0f) is responsible for scaling my texture to the given size (128x128 pixel).
But as far as i understand, the Matrix.Scaling function takes in a float from 0 - 1 where 1 is the full texture size and 2 would be double the texture size. But i would like to enter the size in pixel and not in units(?).
So i tried the following:
Size res = CGame.Resolution;
float cW = rect.Width / res.Width;
float cH = rect.Height / res.Height;
Matrix m = Matrix.Identity * Matrix.Translation(-0.5f, -0.5f, 0.0f) * Matrix.Scaling(cW, cH, 1.0f) * Matrix.RotationZ(rotation) * Matrix.Translation(rect.X, rect.Y, 0.0f);
I divide the given texture Width and Height (which is 128x128 pixel) by the Width and Height of the current screen resolution (Which in my case is 1920x1080).
This leaves me with the following:
The result of the division by the screen resolution
As you can see there is a red rectangle in the texture, which is actually 128x128 pixel in size, and in the background, there is my texture, which is supposed to be scaled to 128x128 but as you can see, it clearly is larger then the red rectangle which is 128x128.
Here is how i load my texture
// D3DX.DefaultNonPowerOf2 = -2
Texture t = Texture.FromFile(device, filePath, D3DX.DefaultNonPowerOf2, D3DX.DefaultNonPowerOf2, 1, Usage.None, Format.Unknown, Pool.Managed, Filter.None, Filter.None, 0);
If someone could help me out with this problem i would be really grateful!
Got it working!
Needed to divide the target Size of the texture by the actual texture size like so:
SurfaceDescription sd = texture.GetLevelDescription(0);
float cW = rect.Width / sd.Width;
float cH = rect.Height / sd.Height;

Calculate Crop Rectangle that has been zoomed and resized

I'm working on an image viewer in C# WPF that can zoom and crop images. I'd like to be able to make the two work together, but I don't know how to calculate that.
To zoom an image, it is done by modifying TranslateTransform and ScaleTransform X & Y values. Inspired by this answer https://stackoverflow.com/a/6782715/2923736
The crop function is done by calculating TopLeftX, TopRightY, BottomRightX, BottomLeftY that has been drawn on screen.
The size and aspect ratio calculation is done like so:
maxWidth = Math.Min(ScreenWidth, imageWidth);
maxHeight = Math.Min(ScreenHeight, imageHeight);
AspectRatio = Math.Min(maxWidth / imageWidth, maxHeight / imageHeight);
ImageWidth = imageWidth * AspectRatio;
ImageHeight = imageHeight * AspectRatio;
I use this code to calculate the cropped area, but don't know how to put TranslateTransform and ScaleTransform X & Y values into the formula.
internal static Int32Rect? GetCrop()
{
// Contains the dimensions and coordinates of cropped area
var cropArea = CropService.GetCroppedArea();
if (cropArea == null) { return null; }
int x, y, width, height;
x = Convert.ToInt32(cropArea.CroppedRectAbsolute.X / AspectRatio);
y = Convert.ToInt32(cropArea.CroppedRectAbsolute.Y / AspectRatio);
switch (Rotateint) // Degress the image has been rotated by
{
case 0:
case 180:
width = Convert.ToInt32(cropArea.CroppedRectAbsolute.Width / AspectRatio);
height = Convert.ToInt32(cropArea.CroppedRectAbsolute.Height / AspectRatio);
break;
default:
width = Convert.ToInt32(cropArea.CroppedRectAbsolute.Height / AspectRatio);
height = Convert.ToInt32(cropArea.CroppedRectAbsolute.Width / AspectRatio);
break;
}
return new Int32Rect(x, y, width, height);
}
Example of image being cropped, where it has been resized to fit the window and zoomed in by 20%.
I need to calculate that, so it can be saved on the user's hard disk.

Transforming Rectangle Coordinates to a Large Resolution Image

I'm using the following code to Transform a small rectangle coordinates to a larger one ie: A rectangle position on a small image to the same position on the larger resolution of the same image
Rectangle ConvertToLargeRect(Rectangle smallRect, Size largeImageSize, Size smallImageSize)
{
double xScale = (double)largeImageSize.Width / smallImageSize.Width;
double yScale = (double)largeImageSize.Height / smallImageSize.Height;
int x = (int)(smallRect.X * xScale + 0.5);
int y = (int)(smallRect.Y * yScale + 0.5);
int right = (int)(smallRect.Right * xScale + 0.5);
int bottom = (int)(smallRect.Bottom * yScale + 0.5);
return new Rectangle(x, y, right - x, bottom - y);
}
But there seems to be a problem with some images.The transformed rectangle coordinates seems to be off the image.
UPDATE:
img.Draw(rect, new Bgr(232, 3, 3), 2);
Rectangle transret= ConvertToLargeRect(rect, orgbitmap.Size, bit.Size);
target = new Bitmap(transret.Width, transret.Height);
using (Graphics g = Graphics.FromImage(target))
{
g.SmoothingMode = SmoothingMode.HighQuality;
g.DrawImage(orgbitmap, new Rectangle(0, 0, target.Width, target.Height),
transret, GraphicsUnit.Pixel);
}
Rectangle Drawn on small resolution Image
{X=190,Y=2,Width=226,Height=286}
Rectangle Transformed into Orginal Large Resolution Image {X=698,Y=7,Width=830,Height=931}
Original Image
First of all, if you resize the shape it shouldn't move position. That's not what one would expect out of enlarging a shape. This means the X,Y point of the top-left corner shouldn't be transformed.
Second, you shouldn't be adding 0.5 manually to operations, that's not a clean way to proceed. Use the ceiling function as suggested by #RezaAghaei
Third, you should not substract X/Y from the height/width, your calculations should be done as width * scale.
Please correct those mistakes, and if it doesn't work I'll update the answer with extra steps.

Convert Sprite x,y, width, height into view port rect

I have this function where i want to convert my sprite x, y, width, height into view port rect so that i can set it to my camera.
void SetImageRectToCameraRect() {
float x = sideCameraMask.rectTransform.rect.x;
float y = sideCameraMask.rectTransform.rect.y;
float width = sideCameraMask.rectTransform.rect.width;
float height = sideCameraMask.rectTransform.rect.height;
Debug.Log(x);
Debug.Log(y);
Debug.Log(width);
Debug.Log(height);
Debug.Log(sideCamGUI.bounds.size.x);
Debug.Log(sideCamGUI.bounds.size.y);
Debug.Log(sideCamGUI.bounds.size);
Vector3 v3 = GetComponent<Camera>().ScreenToViewportPoint(new Vector3(x,y,0));//x,y,0f
Debug.Log("ScreenToViewportPoint::" + v3);
camComponent.rect = new Rect(x, y, width, height);
}
The problem is that how do I convert width and height into camera view port width height
I'm assuming that you're working in 2D and using an orthographic camera. A perspective camera makes things more complicated.
To get the percentage (fraction) of the viewport that the sprite takes up, you'd divide width and height by the camera's width and height respectively.
float camHeight = camera.orthographicSize * 2;
float camWidth = camHeight * camera.aspect;
float heightFraction = height / camHeight;
float widthFraction = width / camWidth;
If you're using Camera.rect (which I think is the case) just use widthFraction and heightFraction directly. If you're using Camera.pixelRect, multiply widthFraction and heightFraction by Camera.pixelWidth and Camera.pixelHeight respectively.
If you want the width and height only of the area visible by the camera, you'll have to perform a bounds intersection which should be trivial to implement.

Get resulting size of RotateTransform

I've got the following code for rotating an image in C#:
private Bitmap RotateImage(Bitmap b, float angle)
{
//create a new empty bitmap to hold rotated image
Bitmap returnBitmap = new Bitmap(b.Width, b.Height);
//make a graphics object from the empty bitmap
Graphics g = Graphics.FromImage(returnBitmap);
//move rotation point to center of image
g.TranslateTransform((float)returnBitmap.Width / 2, (float)returnBitmap.Height / 2);
//rotate
g.RotateTransform(angle);
//move image back
g.TranslateTransform(-(float)b.Width / 2, -(float)b.Height / 2);
//draw passed in image onto graphics object
g.DrawImage(b, new Rectangle(new Point(0, 0), new Size(b.Width, b.Height)));
return returnBitmap;
}
It works very well, except that it clips the result when it exceeds original bounds.
As I understood, I have to set returnBitmap's size to the size of image after rotation. But how do I find how big the result will be, to set size of the new bitmap accordingly?
You need to rotate the four corners of your original image and calculate the bounding box for the new coordinates:
private static Bitmap RotateImage(Image b, float angle)
{
var corners = new[]
{new PointF(0, 0), new Point(b.Width, 0), new PointF(0, b.Height), new PointF(b.Width, b.Height)};
var xc = corners.Select(p => Rotate(p, angle).X);
var yc = corners.Select(p => Rotate(p, angle).Y);
//create a new empty bitmap to hold rotated image
Bitmap returnBitmap = new Bitmap((int)Math.Abs(xc.Max() - xc.Min()), (int)Math.Abs(yc.Max() - yc.Min()));
...
}
/// <summary>
/// Rotates a point around the origin (0,0)
/// </summary>
private static PointF Rotate(PointF p, float angle)
{
// convert from angle to radians
var theta = Math.PI*angle/180;
return new PointF(
(float) (Math.Cos(theta)*(p.X) - Math.Sin(theta)*(p.Y)),
(float) (Math.Sin(theta)*(p.X) + Math.Cos(theta)*(p.Y)));
}
Pythagoras. It is anywhere from original to sqrt(w^2 + h^2) at 90/270 angle. And I d bet it is determined by sine (max at 90).

Categories