I've created one helper app to demonstrate my problem.
I've a rectangle which is filled with an image brush, this brush can be transformed within the rectangle using gesture manipulations.
I'm determining the position of top left corner of the image from the top left corner of the rectangle. I'm getting correct values while (only) translating the image but getting wrong values while using pinch gestures. If you zoom in too much and translate the image, then brush moves in opposite direction.
Below is how you can reproduce my problem with the helper app attached below:
Run the app, get the top left corners of both image and rectangle together by just moving(without pinching) the image until you get the position value as (0,0).
Next Pinch and move the image and get back the top left corners together, now you can see that value is not (0,0).
Download here
Here is my Manipulation Delta Event:
public virtual void Brush_ManipulationDelta(object sender, ManipulationDeltaEventArgs e)
{
if (e.PinchManipulation != null)
{
// Rotate
_currentAngle = previousAngle + AngleOf(e.PinchManipulation.Original) - AngleOf(e.PinchManipulation.Current);
// Scale
_currentScale *= e.PinchManipulation.DeltaScale;
// Translate according to pinch center
double deltaX = (e.PinchManipulation.Current.SecondaryContact.X + e.PinchManipulation.Current.PrimaryContact.X) / 2 -
(e.PinchManipulation.Original.SecondaryContact.X + e.PinchManipulation.Original.PrimaryContact.X) / 2;
double deltaY = (e.PinchManipulation.Current.SecondaryContact.Y + e.PinchManipulation.Current.PrimaryContact.Y) / 2 -
(e.PinchManipulation.Original.SecondaryContact.Y + e.PinchManipulation.Original.PrimaryContact.Y) / 2;
_currentPos.X = previousPos.X + deltaX;
_currentPos.Y = previousPos.Y + deltaY;
}
else
{
// Translate
previousAngle = _currentAngle;
_currentPos.X += e.DeltaManipulation.Translation.X;
_currentPos.Y += e.DeltaManipulation.Translation.Y;
previousPos.X = _currentPos.X;
previousPos.Y = _currentPos.Y;
}
e.Handled = true;
ProcesstTransform();
}
void ProcesstTransform()
{
CompositeTransform gestureTransform = new CompositeTransform();
gestureTransform.CenterX = _currentPos.X;
gestureTransform.CenterY = _currentPos.Y;
gestureTransform.TranslateX = _currentPos.X - outputSize.Width / 2.0;
gestureTransform.TranslateY = _currentPos.Y - outputSize.Height / 2.0;
gestureTransform.Rotation = _currentAngle;
gestureTransform.ScaleX = gestureTransform.ScaleY = _currentScale;
brush.Transform = gestureTransform;
}
First, find the location of the initial upper left corner relative to the center of the transform. This is pretty much straight subtraction. These can be pre-calculated since the before-transformation frame won't change. You do NOT want to pre-scale _brushSize by multiplying in _scale. That will end up scaling the brush twice.
Point origCentre = new Point(ManipulationArea.ActualWidth / 2, ManipulationArea.ActualHeight / 2);
Point origCorner = new Point(origCentre.X - _brushSize.Width / 2, origCentre.Y - _brushSize.Height /2);
Then apply the gestureTransform to the corner point:
Point transCorner = gestureTransform.Transform(origCorner);
XValue.Text = transCorner.X.ToString();
YValue.Text = transCorner.Y.ToString();
This will get things pretty close to accurate, barring some rounding errors and some weirdness from the way the translation is tracked both by changing the position and then by applying the transform. Typically you would only do the latter. I'll leave tracking that down as an exercise for the reader :)
Rob Caplan from Microsoft helped me to solve this issue.
Related
I would like to write an application that will measure fragments of a specimen examined under a microscope. I thought that the best way would be to capture the image and draw on selected parts of the specimen then count the value of the drawn line in pixels (and later to convert this value into the appropriate unit).
Is there anything that helps solve such issue already implemented or any tool/package or something that allows such calculations?
I will also willingly learn about solutions in other programming languages if they allow to solve this problem in a easier way or just in some way.
This is a very basic example of measuring a segmented line drawn onto an image in winforms.
It uses a PictureBox to display the image, a Label to display the current result and for good measure I added two Buttons the clear all points and to undo/remove the last one.
I collect to pixel positions in a List<Point> :
List<Point> points = new List<Point>();
The two edit buttons are rather simple:
private void btn_Clear_Click(object sender, EventArgs e)
{
points.Clear();
pictureBox1.Invalidate();
show_Length();
}
private void btn_Undo_Click(object sender, EventArgs e)
{
if (points.Any())points.Remove(points.Last());
pictureBox1.Invalidate();
show_Length();
}
Note how I trigger the Paint event by invalidating the image whenever the points collection changes..
The rest of the code is also simple; I call a function to calculate and display the sum of all segment lengths. Note that I need at least two points before I can do that or display the first line..
private void pictureBox1_MouseDown(object sender, MouseEventArgs e)
{
points.Add(e.Location);
pictureBox1.Invalidate();
show_Length();
}
private void pictureBox1_Paint(object sender, PaintEventArgs e)
{
if (points.Count > 1) e.Graphics.DrawLines(Pens.Red, points.ToArray());
}
void show_Length()
{
lbl_len.Text = (pointsF.Count) + " point(s), no segments. " ;
if (!(points.Count > 1)) return;
double len = 0;
for (int i = 1; i < points.Count; i++)
{
len += Math.Sqrt((points[i-1].X - points[i].X) * (points[i-1].X - points[i].X)
+ (points[i-1].Y - points[i].Y) * (points[i-1].Y - points[i].Y));
}
lbl_len.Text = (points.Count-1) + " segments, " + (int) len + " pixels";
}
A few notes:
The image is displayed without any zooming. PictureBox has a SizeMode property to make zoomed display simple. In such a case I recommend to store not the direct pixel locations of the mouse but 'unzoomed' values and to use a 'rezoomed' list of values for the display. This way you can zoom in and out and still have the points stick to the right spots.
For this you ought to use a List<PointF> to keep precision.
When zooming e.g. by enlarging the PictureBox, maybe after nesting it in a Panel, make sure to either keep the aspect ratio equal to that of the Image or to do a full calculation to include the extra space left or top; in SizeMode.Normal the image will always sit flush TopLeft but in other modes it will not always do so.
For the calculation of actual i.e. physical distances simply divide by the actual dpi value.
Let's see what we have in action:
Update:
To get a chance to create cloers fits and better precision we obviously need to zoom in on the image.
Here are the necessary changes:
We add a list of 'floating points':
List<PointF> pointsF = new List<PointF>();
And use it to store the un-zoomed mouse positions in the mouse down:
pointsF.Add( scaled( e.Location, false));
We replace all other occurances of points with pointsF.
The Paint event always calculates the scaled points to the current zoom level:
if (pointsF.Count > 1)
{
points = pointsF.Select(x => Point.Round(scaled(x, true))).ToList();
e.Graphics.DrawLines(Pens.Red, points.ToArray());
}
And the function to do the scaling looks like this:
PointF scaled(PointF p, bool scaled)
{
float z = scaled ? 1f * zoom : 1f / zoom;
return new PointF(p.X * z, p.Y * z);
}
It uses a class level variable float zoom = 1f; which gets set along with the picturebox's Clientsize in the Scroll event of a trackbar:
private void trackBar1_Scroll(object sender, EventArgs e)
{
List<float> zooms = new List<float>()
{ 0.1f, 0.2f, 0.5f, 0.75f, 1f, 2, 3, 4, 6, 8, 10};
zoom = zooms[trackBar1.Value];
int w = (int)(pictureBox2.Image.Width * zoom);
int h = (int)(pictureBox2.Image.Height * zoom);
pictureBox2.ClientSize = new Size(w, h);
lbl_zoom.Text = "zoom: " + (zoom*100).ToString("0.0");
}
The picturebox is nested inside a Panel with AutoScroll on. Now we can zoom and scroll while adding segments:
I am writing a desktop WPF app to allow people to open up scanned PDF files, rotate them if need be, and then redact them if need be. The page is rendered to the screen using PDFium so the user can see what needs to be done. If it needs to be rotated, they click the rotate button to rotate it. If it needs to be redacted, they click on the appropriate button and then use the mouse to draw a System.Windows.Shapes.Rectangle on a Canvas. Then they click the save button to save the redaction (or redactions) to the pdf file. The actual changes to the PDF are made using PDFSharp v1.50.4000-beta3b downloaded through NuGet in Visual Studio 2013.
If the page is right side up, IE the rotate value is 0, then everything works fine. I can draw boxes all over the place with no problems. The issue arises when the rotate value is anything other than 0. If I rotate the page 90 degrees in either direction (rotate = 90 or -90), then when I try to draw the box on the page it messes things up. It seems to be swapping the height and width (turning it from landscape to portrait or vice versa) of the page without changing the content of the page. Then it draws the rectangle at the point it would be at if the page was rotated another 90 degrees.
To hopefully better demonstrate what I mean, here's an example:
I've got a pdf page that is the standard size (A4, Letter, doesn't matter). It has a big smiley face on the top third of the file and text on the remainder and the rotate setting is 0 and the orientation is Portrait. I open it up in my program and rotate it 90 degrees. Now it is landscape and the smiley face is sideways on the right third of the page. I try and draw a box in the upper right corner of the page. When I click the save button, it changes the file and now it displays in portrait orientation however the content didn't change so now the Smiley face is invisible off of the right edge of the page. The box I had tried to place in the upper right corner acts as if it has been rotated and it is now in the lower right corner. If I make it a nice oblong rectangle, I can see that it really does look as though it's been rotated with the whole page but without the content. If I do it again, with another box in the upper right corner and then click save, it will swap the height and width again and rotate my box into a position 90 degrees off from where I placed it. Now I can see the smiley face again but the box still isn't where I want it.
Also, if the page rotation is 180, then when it saves the box, it some how rotates the position it's supposed to be in 180 degrees. So if my smiley face is upside down on the bottom of the page and I draw a box over his eyes (at the bottom of the page), it saves the box at the top of the page.
The weirdest part is that it was working perfectly a few weeks ago and now it isn't. From my testing, it appears as though the change is somehow being made in the PdfDocument.Save() method because before that point, the coordinates of the rectangle are what they should be for the current orientation/position of the page.
Anyways, now that I've explained the problem, here's my code.
First we have the code that handles the rotation. It's in a helper class and it stores the path to the file and a total page count. It takes in a list of page numbers to rotate. Also, I have to set the orientation to portrait (whether it should be or not) because PDFSharp is automatically setting that elsewhere but if I set it manually here, it rotates the pages properly and if I don't set it here, the page content will rotate without changing the size/orientation of the page itself.
public bool RotatePages(List<int> pageNums)
{
if (pageNums.Count > 0)
{
PdfDocument currDoc = PdfReader.Open(fullPath, PdfDocumentOpenMode.Modify);
for (int i = 0; i < totalPageCount; i++)
{
PdfPage newPage = currDoc.Pages[i]; //newDoc.AddPage();
if (pageNums.Contains(i))
{
newPage.Orientation = PdfSharp.PageOrientation.Portrait;
newPage.Rotate = (newPage.Rotate + 90) % 360;
}
}
currDoc.Save(fullPath);
return true;
}
else
return false;
}
Next is the code to draw the redaction boxes. It takes in a list of System.Windows.Rect objects, a list of colors, a page number to mark, and a matrix. The matrix is because the pdf is rendered to an image but the rectangles are drawn by a user to a Canvas. The image can be zoomed in on or panned around and the matrix stores those transformations so that I can match the position of the rectangle on the Canvas to the appropriate point on the image/pdf. It works perfectly if the page rotation is 0.
public bool Redact(List<Rect> redactions, List<System.Windows.Media.Color> redactionColors, System.Windows.Media.Matrix matrix, int pageNum)
{
if (pageNum >= 0 && pageNum < totalPageCount && redactions.Count > 0 && redactions.Count == redactionColors.Count)
{
PdfDocument currDoc = PdfReader.Open(fullPath, PdfDocumentOpenMode.Modify);
PdfPage newPage = currDoc.Pages[pageNum];
XGraphics gfx = XGraphics.FromPdfPage(newPage);
XBrush brush = null;
for (int i = 0; i < redactions.Count; i++)
{
Rect redaction = redactions[i];
System.Windows.Media.Color redactionColor = redactionColors[i];
redaction.X = redaction.X / (matrix.OffsetX / newPage.Width);
redaction.Y = redaction.Y / (matrix.OffsetY / newPage.Height);
redaction.Width = redaction.Width / (matrix.OffsetX / newPage.Width);
redaction.Height = redaction.Height / (matrix.OffsetY / newPage.Height);
redaction.Width = redaction.Width / matrix.M11;
redaction.Height = redaction.Height / matrix.M12;
brush = new XSolidBrush(XColor.FromArgb(redactionColor.A, redactionColor));
gfx.DrawRectangle(brush, redaction);
}
gfx.Save();
currDoc.Save(fullPath);
return true;
}
else
return false;
}
In the matrix (and no I'm not using it for matrix math, just using it to pass data around rather than using 6 ints/doubles, yes I am aware that it's lousy coding practice but fixing it is a rather low priority):
M11 = the x scale transform
M12 = the y scale transform
M21 = the x translate transform
M22 = the y translate transform
OffsetX = the actual width of the image control
OffsetY = the actual height of the image control
As near as I can tell by walking through step by step, my math and everything looks and works exactly as it should until currDoc.Save(fullPath); and then it magically gets the wrong values. If I break program execution at any time before that line, the actual file doesn't get a box but the moment it passes that line it messes up.
I have no idea what's going on here or how to fix it. It was previously working and I don't remember what I did to change it so it stopped working. I've been searching for a solution all day with no luck so far. Any help would be greatly appreciated.
So I finally figured it out. Apparently PDFSharp has some issues with how it handles page rotation. To fix it, I first had to tweak the source code for PDFSharp.
I had to comment out the code that was swapping the height/width values when the page was set to landscape. Apparently PDFSharp uses a "orientation" variable to store the orientation despite the fact that PDFs don't have such a setting. By commenting out those lines, I finally started getting the right height and width for rotated pages. This is a change to PdfPage.cs.
public XUnit Height
{
get
{
PdfRectangle rect = MediaBox;
//return _orientation == PageOrientation.Portrait ? rect.Height : rect.Width;
return rect.Height;
}
set
{
PdfRectangle rect = MediaBox;
//if (_orientation == PageOrientation.Portrait)
MediaBox = new PdfRectangle(rect.X1, 0, rect.X2, value);
//else
// MediaBox = new PdfRectangle(0, rect.Y1, value, rect.Y2);
_pageSize = PageSize.Undefined;
}
}
public XUnit Width
{
get
{
PdfRectangle rect = MediaBox;
//return _orientation == PageOrientation.Portrait ? rect.Width : rect.Height;
return rect.Width;
}
set
{
PdfRectangle rect = MediaBox;
//if (_orientation == PageOrientation.Portrait)
MediaBox = new PdfRectangle(0, rect.Y1, value, rect.Y2);
//else
// MediaBox = new PdfRectangle(rect.X1, 0, rect.X2, value);
_pageSize = PageSize.Undefined;
}
}
Then I had to comment out a few lines in the WriteObject method that were flipping the height and width values of the mediabox. These are the lines I commented out. This stopped PDFSharp from flipping my rotated pages size every time I saved it.
//// HACK: temporarily flip media box if Landscape
//PdfRectangle mediaBox = MediaBox;
//// TODO: Take /Rotate into account
//if (_orientation == PageOrientation.Landscape)
// MediaBox = new PdfRectangle(mediaBox.X1, mediaBox.Y1, mediaBox.Y2, mediaBox.X2);
...
//if (_orientation == PageOrientation.Landscape)
// MediaBox
Lastly, in my own code I had to change most of the redaction code to put the boxes in the right spot at the right size. Getting the math right took forever and the code is messy but it works. Any suggestions on how to clean it up would be appreciated.
public bool Redact(List<Rect> redactions, List<System.Windows.Media.Color> redactionColors, System.Windows.Media.Matrix matrix, int pageNum)
{
if (pageNum >= 0 && pageNum < totalPageCount && redactions.Count > 0 && redactions.Count == redactionColors.Count)
{
PdfDocument currDoc = PdfReader.Open(fullPath, PdfDocumentOpenMode.Modify);
int angle = currDoc.Pages[pageNum].Rotate;
PdfPage oldPage = currDoc.Pages[pageNum];
XBrush brush = null;
XGraphics gfx = XGraphics.FromPdfPage(oldPage);
XPoint pagePoint = new XPoint(0, 0);
if (angle == 180)
{
pagePoint.X = oldPage.Width / 2;
pagePoint.Y = oldPage.Height / 2;
gfx.RotateAtTransform(180, pagePoint);
}
for (int i = 0; i < redactions.Count; i++)
{
Rect redaction = redactions[i];
System.Windows.Media.Color redactionColor = redactionColors[i];
double scaleValue = oldPage.Height / matrix.OffsetX;
if (angle == 180 || angle == 0)
{
redaction.X = redaction.X / (matrix.OffsetX / oldPage.Width);
redaction.Y = redaction.Y / (matrix.OffsetY / oldPage.Height);
redaction.Width = redaction.Width / (matrix.OffsetX / oldPage.Width);
redaction.Height = redaction.Height / (matrix.OffsetY / oldPage.Height);
redaction.Width = redaction.Width / matrix.M11;
redaction.Height = redaction.Height / matrix.M12;
}
else if (angle == 90 || angle == 270)
{
Rect tempRect = redaction;
tempRect.X = redaction.X * scaleValue;
tempRect.Y = redaction.Y * scaleValue;
tempRect.Height = redaction.Height * scaleValue;
tempRect.Width = redaction.Width * scaleValue;
redaction.Width = tempRect.Height;
redaction.Height = tempRect.Width;
tempRect.Width = tempRect.Width / matrix.M11;
tempRect.Height = tempRect.Height / matrix.M12;
redaction.X = oldPage.Width - tempRect.Y - tempRect.Height;
redaction.Y = tempRect.X;
if (angle == 90)
gfx.RotateAtTransform(180, new XPoint(oldPage.Width / 2, oldPage.Height / 2));
redaction.Width = tempRect.Height;
redaction.Height = tempRect.Width;
}
brush = new XSolidBrush(XColor.FromArgb(redactionColor.A, redactionColor));
gfx.DrawRectangle(brush, redaction);
}
gfx.Save();
currDoc.Save(fullPath);
return true;
}
else
return false;
}
So that's the solution I found that works for me.
This problem also persists when rotating page using Adobe Libraries, when you rotate a page and add annotations the annotations are all off. The easiest solution I have found is to rotate the page, extract the page, resave the individual page as a PDF/A and then then reinsert back into the pdf. By re-saving as a pdf/A it fixes all the underlying text coordinates on that page to the new rotation so that when you add annotations they are all where the should be. Unfortunately I have not found a way of saving an existing pdf to pdf/A using pdfshap.
My scene is 2048 x 1152, and the camera never moves. When I create a rectangle with the following:
timeBarRect = new Rect(220, 185, Screen.width / 3, Screen.height / 50);
Its position changes depending on the resolution of my game, so I can't figure out how to get it to always land where I want it on the screen. To clarify, if I set the resolution to 16:9, and change the size of the preview window, the game will resize at ratios of 16:9, but the bar will move out from where it's supposed to be.
I have two related questions:
Is it possible to place the Rect at a global coordinate? Since the screen is always 2048 x 1152, if I could just place it at a certain coordinate, it'd be perfect.
Is the Rect a UI element? When it's created, I can't find it in the hierarchy. If it's a UI element, I feel like it should be created relative to a canvas/camera, but I can't figure out a way to do that either.
Update:
I am realizing now that I was unclear about what is actually being visualized. Here is that information: Once the Rect is created, I create a texture, update the size of that texture in Update() and draw it to the Rect in OnGui():
timeTexture = new Texture2D (1, 1);
timeTexture.SetPixel(0,0, Color.green);
timeTexture.Apply();
The texture size being changed:
void Update ()
{
if (time < timerMax) {
playerCanAttack = false;
time = time + (10 * Time.deltaTime);
} else {
time = timerMax;
playerCanAttack = true;
}
The actual visualization of the Rect, which is being drawn in a different spot depending on the size of the screen:
void OnGUI(){
float ratio = time / 500;
float rectWidth = ratio * Screen.width / 1.6f;
timeBarRect.width = rectWidth;
GUI.DrawTexture (timeBarRect, timeTexture);
}
I don't know that I completely understand either of the two questions I posed, but I did discover that the way to get the rect's coordinates to match the screen no matter what resolution was not using global coordinates, but using the camera's coordinates, and placing code in Update() such that the rect's coordinates were updated:
timeBarRect.x = cam.pixelWidth / timerWidth;
timeBarRect.y = cam.pixelHeight / timerHeight;
I have a pretty annoying problem. I would like to create a drawing program, using winform + XNA combo.
The most important part would be to transform the mouse position into the XNA drawn grid - I was able to make it for the translations, but it only work if I don't zoom in - when I do, the coordinates simply went horrible wrong.
And I have no idea what I doing wrong. I tried to transform with scaling matrix, transform with inverse scaling matrix, multiplying with zoom, but none seems to work.
In the beginning (with zoom value = 1) the grid starts from (0,0,0) going to (Width, Height, 0). I was able to get coordinates based on this grid as long as the zoom value didn't changed at all. I using a custom shader, with orthographic projection matrix, identity view matrix, and the transformed world matrix.
Here are the two main methods:
internal void Update(RenderData data)
{
KeyboardState keyS = Keyboard.GetState();
MouseState mouS = Mouse.GetState();
if (ButtonState.Pressed == mouS.RightButton)
{
camTarget.X -= (float)(mouS.X - oldMstate.X) / 2;
camTarget.Y += (float)(mouS.Y - oldMstate.Y) / 2;
}
if (ButtonState.Pressed == mouS.MiddleButton || keyS.IsKeyDown(Keys.Space))
{
zVal += (float)(mouS.Y - oldMstate.Y) / 10;
zoom = (float)Math.Pow(2, zVal);
}
oldKState = keyS;
oldMstate = mouS;
world = Matrix.CreateTranslation(new Vector3(-camTarget.X, -camTarget.Y, 0)) * Matrix.CreateScale(zoom / 2);
}
internal PointF MousePos
{
get
{
Vector2 mousePos = new Vector2(Mouse.GetState().X, Mouse.GetState().Y);
Matrix trans = Matrix.CreateTranslation(new Vector3(camTarget.X - (Width / 2), -camTarget.Y + (Height / 2), 0));
mousePos = Vector2.Transform(mousePos, trans);
return new PointF(mousePos.X, mousePos.Y);
}
}
The second method should return the coordinates of the mouse cursor based on the grid (where the (0,0) point of the grid is the top-left corner.).
But is just don't work. I deleted the zoom transformation from the matrix trans, as I wasn't able to get any useful results (most of the time, the coordinates were horribly wrong, mostly many thousands when the grid's size is 500x500).
Any ideas, or suggestions? I've been trying to solve this simple problem for two days now :\
Take a look at the GraphicsDevice.Viewport.Unproject method for converting screen space locations in to world space, it basically goes through your world, view, projection transformations in reverse order.
as for your zooming issue, instead of scaling the world transform why not move the camera closer to the object that you're viewing?
I am trying to center a rectangle on a point and can't figure out the math needed to do it.
The context this is being used in is a form which paints a bitmap and allows the user to zoom in at a specified point, and pan/scroll while zoomed in.
Here is my code which current works for centering the CanvasBounds on the middle of the ClientRectangle:
private void UpdateCanvas()
{
int canvasWidth = (int)(_bitmap.Width * _zoomRatio);
int canvasHeight = (int)(_bitmap.Height * _zoomRatio);
Point canvasLocation = new Point((ClientRectangle.Width - canvasWidth) / 2, (ClientRectangle.Height - canvasHeight) / 2);
CanvasBounds = new Rectangle(canvasLocation, new Size(canvasWidth, canvasHeight));
}
_zoomRatio is the zoom which adjusts the size of the canvas. 1.0 would be 100%, 2.0 is 200%, etc.
Basically instead I want to feed this function a point taken by mouse input, and use that point as the center for the canvasBounds rectangle. Then when the user manipulates the horizontal and vertical scroll bars, it can change the _centerPoint and update the CanvasBounds.
I think you need to just offset your "point" with your canvas size:
private void UpdateCanvas(Point mousePoint)
{
int canvasWidth = (int)(_bitmap.Width * _zoomRatio);
int canvasHeight = (int)(_bitmap.Height * _zoomRatio);
int canvasX = (mousePoint.X - (canvasWidth / 2));
int canvasY = (mousePoint.Y - (canvasHeight / 2));
CanvasBounds = new Rectangle(canvasX, canvasY, canvasWidth, canvasHeight);
}
If that's not what you are looking for, maybe edit your post with a simple screen shot.