I don't have a great concept on matrix math, so the other questions I have found have confused me, and I would like to know how to use Win2D-provided items to solve this issue.
I am creating a pixel art editor. I have an image, for simplicity sake, 2x2 pixels. In the editor, this image is scaled and rotated based on user interaction so that they may edit it. To make it rotate about it's center, I have also applied a translation. Here is my code to transform a 2x2 image into a rotated, scaled image which is rendered on the screen:
Matrix3x2 rotateMatrix = Matrix3x2.CreateRotation((float)(Rotation * Math.PI / 180));
Matrix3x2 scaleMatrix = Matrix3x2.CreateScale(Scale);
Matrix3x2 translationMatrix = Matrix3x2.CreateTranslation(-(float)layer.Height / 2, -(float)layer.Width / 2);
Matrix3x2 transformation = translationMatrix * rotateMatrix * scaleMatrix;
This correctly draws my image on the screen. To allow the user to pan around the editor, there are also two additional translations happening with the rendered image, OffsetX and OffsetY, as seen here:
ICanvasImage image = new Transform2DEffect
{
Source = source,
TransformMatrix = transformation,
InterpolationMode = CanvasImageInterpolation.NearestNeighbor
};
ctx.DrawImage(image, new Vector2(OffsetX, OffsetY));
So the rotated, scaled image is drawn at center (OffsetX, OffsetY). All works fine.
Now I need to translate a screenspace click into an imagespace click. For example, if a user clicks the canvas at (300, 400), and the image is rotated and scaled and translated in such a way that the pixel at (0, 1) is under the cursor visually, I need to be able to translate (300, 400) into (0, 1).
Related
As I have specified in the title, I have started to work on a simple application what contains in the main frame window, a double buffered panel. Inside this panel some graphics can be drawn, let's consider this panel is a simple viewport for the elements drawn inside.
Two functionalities were added here, pan and zoom what can scale transform and translate transform inside paint event using a delta updated on MouseDown and Move events and OnMouseWheel for updating the scale transform.
The real problem has arrived after trying to add a functionality to support creation of a Node (graphic element) inside viewport to a precise location when zoom is greater than 1 (scale 100%).
Scenario 1 -> The yellow rectangle was created correctly and is positioned exactly at the mouse pointer location, as in shown the image below (scale == 1).
Scenario 2 -> The yellow rectangle is highly shifted relative to mouse position with a viewport scale of aprox. 40%, as in the image below (scale == 1.4). The red filled circle was the mouse pressed (the cursor position remains unchanged, only a zoom was made).
Test scenario -> I have tried a lot of methods without success before posting there, this is one of them:
I really appreciate any kind of inputs, even ideas related to changing the OXY graph approach (as the translate functions use relative coordinates).
Maybe this will help:
PointF ScaleUnscale(PointF p, float offX, float offY, float sX, float sY, bool scale)
{
PointF pf = p;
if (scale)
{
pf = new PointF( ( p.X - offX )/ sX, (p.Y - offY)/ sY) ;
}
else
{
pf = new PointF( p.X * sX + offX, p.Y * sY + offY);
}
return pf;
}
This scales a mouse point to a canvas point or back:
cPoints.Add(ScaleUnscale(e.Location, .., true));
Tested with a translated and scaled Graphics object:
g.TranslateTransform(offX, offY);
g.ScaleTransform(scaleX, scaleY);
I've been looking all over SO today and I can't get anything to work for my needs.
I have a web application that let's users drag and drop text/images and then it sends the details to the server to draw those to a pdf. I'm trying to enable rotation, but I can't get a hold of the translatetransform stuff. My image in testing prints out great, rotated well, but it is not in the correct location. I'm missing how the intitial translatetransform changes things and my mind is shot at the end of the day. Do I have to draw this as a bitmap first using a different graphics instance, and then draw that bitmap to my background? Any help on this would be great! Thanks!
CODE:
i is the image object from the browser
coord is the x & y of the upper corner of the image on the canvas (990wx1100h) on the browser
size is the h & w of the element on the browser
Bitmap b = new Bitmap(wc.OpenRead(i.img));
if (i.rotation != 0)
{
g.TranslateTransform(this.CanvasDetails.size.width/2, this.CanvasDetails.size.height/2);
g.RotateTransform(i.rotation);
g.DrawImage(b, new Rectangle(- i.coord.x/2, -i.coord.y/2, i.size.width, i.size.height), 0, 0, b.Width, b.Height, GraphicsUnit.Pixel, ia);
}
else
{
g.DrawImage(b, new Rectangle(i.coord.x, i.coord.y, i.size.width, i.size.height), 0, 0, b.Width, b.Height, GraphicsUnit.Pixel, ia);
}
EDIT
I added the translatransform reversal as suggested by Adam, but the image is still drawn in a different location.
g.TranslateTransform(this.CanvasDetails.size.width / 2, this.CanvasDetails.size.height / 2);
g.RotateTransform(i.rotation);
g.TranslateTransform(-this.CanvasDetails.size.width / 2, -this.CanvasDetails.size.height / 2);
g.DrawImage(b, new Rectangle(-i.coord.x / 2, -i.coord.y / 2, i.size.width, i.size.height), 0, 0, b.Width, b.Height, GraphicsUnit.Pixel, ia);
Examples:
Browser View
.NET drawn version
Ok, completely reworking this answer to try to explain it clearer. A couple of things to know are that transformations 'accumulate' and rotation transforms happen around the origin. So to just explain the affect of accumulating (multiplying) transforms, look at this example:
//draw an ellipse centered at 200,200
g.DrawEllipse(Pens.Red, 195, 195, 10, 10);
//apply translate transform - shifts origin to 200,200
g.TranslateTransform(200, 200);
//draw another ellipse, should draw around first ellipse
//because translate tranforms essentially moves our coordinates 200,200
g.DrawEllipse(Pens.Blue, -7, -7, 14, 14);
//now do rotate transform
g.RotateTransform(90f); //degree to rotate object
//now, anything we draw with coordinates 0,0 is actually going to be draw at 200,200 AND be rotated by 45*
//this line will be vertical, through 200,200, instead of horizontal through 0,0
g.DrawLine(Pens.Green, -20,0,20,0);
//If we add another translate, this time 50x, it would normally translate by 50 in the X direction
//BUT - because we already have transforms applied, including the 90 rotate, it affects this translation
//so this in effect because a 50px translation in Y, because it's rotated 90*
g.TranslateTransform(50, 0);
//so even though we translated 50x, this line will draw 50px below the last line
g.DrawLine(Pens.Green, -20, 0, 20, 0);
So for your case, you want to draw an object Centered at CenterPoint and rotated by Angle. So you would do:
g.TranslateTransform(-CenterPoint.X, -CenterPoint.Y);
g.RotateTransform(Angle);
g.DrawImage(b, -ImageSize/2, -ImageSize/2, ImageSize, ImageSize);
You'd then need to reset the transforms for additional drawing, which you can do with:
g.ResetTransform();
If that doesn't leave the image where you want it, then you'll need to check the values you're using to position it. Are you storing it's center? Or top left? Etc.
I am drawing a rectangle with primitives in XNA. The width is:
width = GraphicsDevice.Viewport.Width
and the height is
height = GraphicsDevice.Viewport.Height
I am trying to fit this rectangle in the screen (using different screens and devices) but I am not sure where to put the camera on the Z-axis. Sometimes the camera is too close and sometimes to far.
This is what I am using to get the camera distance:
//Height of piramid
float alpha = 0;
float beta = 0;
float gamma = 0;
alpha = (float)Math.Sqrt((width / 2 * width/2) + (height / 2 * height / 2));
beta = height / ((float)Math.Cos(MathHelper.ToRadians(67.5f)) * 2);
gamma = (float)Math.Sqrt(beta*beta - alpha*alpha);
position = new Vector3(0, 0, gamma);
Any idea where to put the camera on the Z-axis?
The trick to doing this is to draw the rectangle directly in raster space. This is the space that the GPU works in when actually rendering stuff.
Normally your model starts in its own model space, you apply a world transform to get it into world space. You then apply a view transform to move the points in the world around the camera (to get the effect of the camera moving around the world). Then you apply a projection matrix to get all those points into the space that the GPU uses for drawing.
It just so happens that this space is always - no matter the screen size - from (-1,-1) in the bottom left corner of the viewport, to (1,1) in the top right (and from 0 to 1 on the Z axis for depth).
So the trick is to set all your matrices (world, view, project) to Matrix.Identity, and then draw a rectangle from (-1,-1,0) to (1,1,0). You probably also want to set DepthStencilState.None so that it doesn't affect the depth buffer.
An alternative method is to just use SpriteBatch and draw to a rectangle that is the same size as the Viewport.
I declared a device + sprite in a Windows.Form like this
PresentParameters presentParameters = new PresentParameters();
presentParameters.Windowed = true;
presentParameters.SwapEffect = SwapEffect.Copy;
var device = new Device(Manager.Adapters.Default.Adapter, DeviceType.Hardware, this, CreateFlags.HardwareVertexProcessing, presentParameters);
var sprite = new Sprite(device);
I loaded a texture via TextureLoader.FromFile(device, "image.png");
In my Draw method i startet the device scene, then the sprite scene, then i wrote
sprite.Draw2D(texture, PointF.Empty, 0, PointF.Empty, Color.White);
the drawing itself works, but it draws only a big portion of the image scaled up to the screen (like 90%)
i tried it with a source rectangle with the given texture size too, but the same bug occurred
any suggestions?
I am experienced in C++ DirectX, but not C# DirectX, so take this with a grain of salt.
In my experiences with the Sprite interface, you need to scale, rotate, and translate just like you need to with 3D objects. You may be forgetting to scale. Here is the code of my Update function.
void Button::Update()
{
Sprite->Begin(D3DXSPRITE_ALPHABLEND);
D3DXMATRIX trans;
D3DXMATRIX scale;
D3DXMATRIX world;
D3DXMatrixIdentity(&world);
D3DXMatrixTranslation(&trans, pos.x, pos.y, 0.0f);
D3DXMatrixScaling(&scale, scaleFactor, scaleFactor, 1.0f);
world = scale * trans;
Sprite->SetTransform(&world);
Sprite->Draw(buttonTexture, NULL, NULL, &D3DXVECTOR3(-width2, -height2, 0.0), whitecol);
Sprite->End();
}
Admittedly, this isn't a very object-oriented way of doing things, but it suits my needs.
Caveat: I am not an DirectX expert, but I had the same problem.
When you load the sprite it expands the sprite to fit a size where each dimension is a power of 2. For example, If you sprite was 200 x 65, the sprite will have a width of 256 (and the image will be expanded to a width of 256, increasing it slightly) by 128 (almost doubling the height).
When you draw the image, it will be almost twice the height you expected.
My solution was to modify my image file to have a height and width of a factor of 2 and then only draw the portion that was the original size.
I have an application that displays an image inside of a Windows Forms PictureBox control. The SizeMode of the control is set to Zoom so that the image contained in the PictureBox will be displayed in an aspect-correct way regardless of the dimensions of the PictureBox.
This is great for the visual appearance of the application because you can size the window however you want and the image will always be displayed using its best fit. Unfortunately, I also need to handle mouse click events on the picture box and need to be able to translate from screen-space coordinates to image-space coordinates.
It looks like it's easy to translate from screen space to control space, but I don't see any obvious way to translate from control space to image space (i.e. the pixel coordinate in the source image that has been scaled in the picture box).
Is there an easy way to do this, or should I just duplicate the scaling math that they're using internally to position the image and do the translation myself?
I wound up just implementing the translation manually. The code's not too bad, but it did leave me wishing that they provided support for it directly. I could see such a method being useful in a lot of different circumstances.
I guess that's why they added extension methods :)
In pseudocode:
// Recompute the image scaling the zoom mode uses to fit the image on screen
imageScale ::= min(pictureBox.width / image.width, pictureBox.height / image.height)
scaledWidth ::= image.width * imageScale
scaledHeight ::= image.height * imageScale
// Compute the offset of the image to center it in the picture box
imageX ::= (pictureBox.width - scaledWidth) / 2
imageY ::= (pictureBox.height - scaledHeight) / 2
// Test the coordinate in the picture box against the image bounds
if pos.x < imageX or imageX + scaledWidth < pos.x then return null
if pos.y < imageY or imageY + scaledHeight < pos.y then return null
// Compute the normalized (0..1) coordinates in image space
u ::= (pos.x - imageX) / imageScale
v ::= (pos.y - imageY) / imageScale
return (u, v)
To get the pixel position in the image, you'd just multiply by the actual image pixel dimensions, but the normalized coordinates allow you to address the original responder's point about resolving ambiguity on a case-by-case basis.
Depending on the scaling, the relative image pixel could be anywhere in a number of pixels. For example, if the image is scaled down significantly, pixel 2, 10 could represent 2, 10 all the way up to 20, 100), so you'll have to do the math yourself and take full responsibility for any inaccuracies! :-)