Not getting the appropriate bounds after an image is rotated in WPF - c#

I've tried several methods here on stackoverflow, and the closest one is the following:
Border DesignBorder = new Border();
DesignBorder.BorderThickness = new System.Windows.Thickness(2.0);
DesignBorder.BorderBrush = Brushes.Black;
GeneralTransform transform = Selected.TransformToVisual(canvasDrawingArea);
Rect R = transform.TransformBounds(new Rect(0, 0, Selected.ActualWidth, Selected.ActualHeight));
DesignBorder.SetValue(Canvas.LeftProperty, R.Left);
DesignBorder.SetValue(Canvas.TopProperty, R.Top);
DesignBorder.SetValue(Canvas.WidthProperty, R.Width);
DesignBorder.SetValue(Canvas.HeightProperty, R.Height);
canvasDrawingArea.Children.Add(DesignBorder);
Selected is the image that has been rotated.
When the Image is rotated in increments of 90 degrees, everything looks great. However, at 45, 135, etc. degrees, the bounding box is not nearly close enough
picture link for 90 degrees
picture link for 45 degrees
Is this just the best that can be done? Or is there a way to improve the bounding box?
EDIT:
I thought it was working find for 90 degree rotates, but it only gave the appearance of working as the image I was working with was nearly a square. As soon as I put a more rectangular image in, it became obvious that 90 degree increments also do not work.

Related

Diagonal Shadow with GDI+

I am trying to draw a diagonal shadow.
First I make all pixel to black:
Next with a simple for cicle this is are the result
Now I want to stretch this image diagonally to simulate a shadow.
I have tried:
Bitmap b = new Bitmap(tImage.Width + 100, tImage.Height);
Graphics p = Graphics.FromImage(b);
p.RotateTransform(30f);
p.TranslateTransform(100f, -200f);
p.DrawImage(tImage, new Rectangle(0, -20, b.Width+20, b.Height));
but the images are rotated and translated.
Please anyone have a solution for me?
I need it to look like this (created in Photoshop):
Creating a nice dropshadow is quite a task using Winforms and GDI+.
It features neither polygon scaling nor blurring; and let's not even think about 3D..! - But we can at least do a few things without too much work and get a nice result for many images..
Let's assume you already have an image that is cut out from its background.
The next step would be to turn all colors into black.
Then we most likely would want to add some level of transparency, so that the background the shadow falls on, still shines through.
Both task are done quite effectively by using a suitable ColorMatrix.
With a very transparent version we can also create simple blurring by drawing the image with offsets. For best results I would draw it nine times with 3 differents weights/alpha values..
High quality blurring is an art as you can see by even just looking at the filters and adjustments in pro software like Adobe Photoshop or Affinity Photo. Here is a nice set of interesting links..
But since we are only dealing with a b/w bitmap a simplitstic appraoch is good enough.. I use 3 alpha values of 5%, 10% and 20% for the 4 corner, the 4 edge and the 1 center drawings.
The final step is drawing the shadow with some skewing.
This is explained here; but while this is seemingly very simple it is also somewhat impractical. The three points the DrawImage overlay expects need to be calculated.
So here is a method that does just that; do note that is is a strongly simplified method:
The overlay takes three points, that is 6 floats. We only use 3 numbers:
one for the amount of skewing; 0.5 means the top is shifted to the right by half the width of the bitmap.
the other two are the scaling of the resulting bounding box. 1 and 0.5 mean that the width is unchanged and the height is reduced to 50%.
Here is the function:
public Bitmap SkewBitmap(Bitmap inMap, float skewX, float ratioX, float ratioY )
{
int nWidth = (int)(inMap.Width * (skewX + ratioX));
int nHeight = (int)(Math.Max(inMap.Height, inMap.Height * ratioY));
int yOffset = inMap.Height - nHeight;
Bitmap outMap = new Bitmap(nWidth, nHeight);
Point[] destinationPoints = {
new Point((int)(inMap.Width * skewX), (int)(inMap.Height * ratioY) + yOffset),
new Point((int)(inMap.Width * skewX + inMap.Width * ratioX),
(int)(inMap.Height * ratioY) + yOffset),
new Point(0, inMap.Height + yOffset ) };
using (Graphics g = Graphics.FromImage(outMap))
g.DrawImage(inMap, destinationPoints);
return outMap;
}
Note a few simplifications:
If you want to drop the shadow to the left you will need to not just move the first two points to the left but also to adapt the calculation of the width and also the way you overlay the object over the shadow.
If you study the MSDN example you will see that the DrawImage overlay also allows to do a rotation. I didn't add this to our function, as it is a good deal more complicated to calculate and even to just to write a signature.
If you wonder where the info of the six numbers go, here is the full layout:
3 go into our parameters
1 would be the angle of the rotation we don't do
2 could be either the rotation center point or a point (deltaX&Y) the by which the result is translated
If you look closely you can see the shadow of the left foot is a little below the foot. This is because the feet are not at the same level and with the vertical compression the base lines drift apart. To correct that we would either modify the image or add a tiny rotation after all.
Looking at your example imag it is clear that you will nee to take it apart and treat 'house' and 'tree' separately!
The signature is kept simple; this always a balance between ease of use and effort in coding. On could wish for a paramter the takes angle to control the skewing. Feel free to work out the necessary calculations..
Note that adding the functions behind the other buttons would go beyond the scope of the question. Suffice it to say that most are just one line to do the drawing and a dozen or so to set up the colormatrix..
Here is the code in the 'Skew' button:
Bitmap bmp = SkewBitmap((Bitmap)pictureBox4.Image, 0.5f, 1f, 0.5f);
pictureBox5.Image = pictureBox1.Image;
pictureBox5.BackgroundImage = bmp;
pictureBox5.ClientSize = new Size(bmp.Width, bmp.Height);
Instead of drawing the object over the shadow I make use of the extra layer of the PictureBox. You would of course combine the two Bitmaps..

Image rotation moving resulting image unpredictably

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.

Is there bug in the Matrix.RotateAt method for certain angles? .Net Winforms

Here's the code i'm using to rotate:
Dim m As New System.Drawing.Drawing2D.Matrix
Dim size = image.Size
m.RotateAt(degreeAngle, New PointF(CSng(size.Width / 2), CSng(size.Height / 2)))
Dim temp As New Bitmap(600, 600, Imaging.PixelFormat.Format32bppPArgb)
Dim g As Graphics = Graphics.FromImage(temp)
g.Transform = m
g.DrawImage(image, 0, 0)
(1) Disposals removed for brevity.
(2) I test the code with a 200 x 200 rectangle.
(3) Size 600,600 it just an arbitrary large value that I know will fit the right and bottom sides of the rotated image for testing purposes.
(4) I know, with this code, the top and left edges will be clipped because I'm not transforming the orgin after the rotate.
The problem only occurs at certain angles:
(1) At 90, the right hand edge disappears completely.
(2) At 180, the right and bottom edges are there, but very faded.
(3) At 270, the bottom edge disappears completely.
Is this a known bug?
If I manually rotate the corners an draw the image by specifying an output rectangle, I don't get the same problem - though it is slightly slower than using RotateAt.
RotateAt uses formulas and sadness to do rotations, because this is necessary when rotating by anything other than 90 degree increments. If you're going to rotate in 90 degree increments, use RotateFlip, which is much more efficient and more accurate.
I had the very same issue, when rotating images, GDI+ shifting the image by 1 pixel, or cropping a 1 pixel border if I try to "fix" the coordinates manually.
Found the solution:
graphics.SetPixelOffsetMode( Gdiplus::PixelOffsetMode::PixelOffsetModeHalf );

Rotating a full 360 degrees in WPF 3D

I have a ModelVisual3D of a cube and I want to animate it to rotate around its axis for 360 degrees. I make a RoationTransform3D which I tell to rotate 360 but it doesn't rotate at all, also if you say 270 degrees it rotates only 90 degrees but in the opposite direction. I guess he computer calculates the "shortest path" of the rotation. The best solution I have come up with is to make one animation turn 180 and after it finishes call another 180 to complete the full rotation. Is there a way to do it in one animation?
RotateTransform3D rotateTransform = new RotateTransform3D();
myCube.Model.Transform = rotateTransform;
AxisAngleRotation3D rotateAxis =
new AxisAngleRotation3D(new Vector3D(0, 1, 0), 180/*or 360*/);
Rotation3DAnimation rotateAnimation =
new Rotation3DAnimation(rotateAxis, TimeSpan.FromSeconds(2));
rotateTransform.BeginAnimation(RotateTransform3D.RotationProperty,
rotateAnimation);
My understanding is that the Rotation3DAnimation uses a Spherical Linear interpolation, so it will always find the shortest path.
One workaround is to use Rotation3DAnimationUsingKeyFrames: setup a key frame at 120, 240, and 360 and you should be good.
Sorry no code right now, I don't have WPF on this machine...
-Jason
I know this was already answered but in my search for a better way to do this I found this alternative:
You can set the rotation value to 180 (or half of what you want to rotate) and then set the repeat behavior to repeat twice and "IsCummulative" to true.

Transformation (rotation) of image using matrix - design-time vs. run-time rendering results

I have a 48x48 image which is rotated using a transformation matrix.
For some reason, the rotated image in design-time differs from the rotated image in run-time as you can see from this screenshot (link dead) (design-time on the left, run-time on the right):
It might be a little bit difficult to spot, but if you look closely at the right edge of the blue circle, it is about a pixel wider in the image to the right. Note that the image is layered - the white glow in the foreground is the part that's being rotated, while the blue ball in the background is static.
It seems like the image is offset 1 pixel in run-time, when rotating exactly 90 degrees (as in the screenshot), 180 degrees and probably also 270 degrees. The image looks the same with any other rotation angle, as far as I can see.
Here's a snippet:
protected static Image RotateImage(Image pImage, Single pAngle)
{
Matrix lMatrix = new Matrix();
lMatrix.RotateAt(pAngle, new PointF(pImage.Width / 2, pImage.Height / 2));
Bitmap lNewBitmap = new Bitmap(pImage.Width, pImage.Height);
lNewBitmap.SetResolution(pImage.HorizontalResolution, pImage.VerticalResolution);
Graphics lGraphics = Graphics.FromImage(lNewBitmap);
lGraphics.Transform = lMatrix;
lGraphics.DrawImage(pImage, 0, 0);
lGraphics.Dispose();
lMatrix.Dispose();
return lNewBitmap;
}
void SomeMethod()
{
// Same results in design-time and run-time:
PictureBox1.Image = RotateImage(PictureBox2.Image, 18)
// Different results in design-time and run-time.
PictureBox1.Image = RotateImage(PictureBox2.Image, 90)
}
Can anyone explain the reason for this behaviour? Or better yet, a solution to make run-time results look like design-time results?
It's important for me because this image is part of an animation which is generated from code based on a single image which is then rotated in small steps. In design-time, the animation looks smooth and nice. In run-time it looks like it's jumping around :/
I'm using Visual Studio 2005 on Windows Vista Business SP2.
It might have something to do with differences in the Graphics object used at design time versus run time like the PixelOffsetMode.
How are you specifying your angle of rotation? in Degrees or in Radians?
Sometimes I get results like those you describe when I've accidentally specified my angle in degrees when it should've been radians (and as a result the object has been rotated several hundred degrees instead of a few)
I don't know for sure given your specific example, but its worth suggesting.
Hm, my consideration is to try:
lMatrix.RotateAt(pAngle, new PointF((pImage.Width + 1)/2.0f, (pImage.Height + 1)/ 2.0f));
I think that should be a problem about the math being performed in floating point; when rounding back to integer coordinates.
One question that might be relevant... is your image completely square and the width and height an even number? Maybe the problem lies there.
Edit: In the first read I passed through the dimensions of the image... If that wasn't the problem maybe you could do a simple rotation by yourself:
angle = 90 : img[i][j] = img[j][w-i]
angle = 180: img[i][j] = img[w-i][w-j]
angle = 270: img[i][j] = img[w-j][i]
(I think the index swappings are ok, but a double check won't be bad)

Categories