Using matrices to normalize transformed images in C# - c#

Consider the two images below (original and transformed respectively). The three blue squares (markers) are used for orientation.
Original Image:
We know the width, height
We know the (x,y) coordinates of all three markers.
Transformed Image:
We can detect the (x,y) coordinates of all three markers.
As a result, we can calculate the angle of rotation, the amount of (x,y) translation and the (x,y) scaling factor.
I now want to use the System.Drawing.Graphics object to perform RotateTransform, TranslateTransform and ScaleTransform. The trouble is, the resulting image is NEVER like the original.
I've been told on stack overflow that the order of applying transformations does not matter but my observation is different. Below is some code that generates an original image and attempts to draw it on a new canvas after introducing some transformations. You can change the order of the transformations to see different results.
public static void GenerateImages ()
{
int width = 200;
int height = 200;
string filename = "";
System.Drawing.Bitmap original = null; // Original image.
System.Drawing.Bitmap transformed = null; // Transformed image.
System.Drawing.Graphics graphics = null; // Drawing context.
// Generate original image.
original = new System.Drawing.Bitmap(width, height);
graphics = System.Drawing.Graphics.FromImage(original);
graphics.Clear(System.Drawing.Color.MintCream);
graphics.DrawRectangle(System.Drawing.Pens.Red, 0, 0, original.Width - 1, original.Height - 1);
graphics.FillRectangle(System.Drawing.Brushes.Blue, 10, 10, 20, 20);
graphics.FillRectangle(System.Drawing.Brushes.Blue, original.Width - 31, 10, 20, 20);
graphics.FillRectangle(System.Drawing.Brushes.Blue, original.Width - 31, original.Height - 31, 20, 20);
filename = System.IO.Path.Combine(System.IO.Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location), "Original.png");
original.Save(filename, System.Drawing.Imaging.ImageFormat.Png);
graphics.Dispose();
// Generate transformed images.
transformed = new System.Drawing.Bitmap(width, height);
graphics = System.Drawing.Graphics.FromImage(transformed);
graphics.Clear(System.Drawing.Color.LightBlue);
graphics.ScaleTransform(0.5F, 0.7F); // Add arbitrary transformation.
graphics.RotateTransform(8); // Add arbitrary transformation.
graphics.TranslateTransform(100, 50); // Add arbitrary transformation.
graphics.DrawImage(original, 0, 0);
filename = System.IO.Path.Combine(System.IO.Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location), "Transformed.png");
transformed.Save(filename, System.Drawing.Imaging.ImageFormat.Png);
graphics.Dispose();
transformed.Dispose();
original.Dispose();
System.Diagnostics.Process.Start(filename);
}
I can see two potential issues here:
Since the transformations are being applies one after another, they render the originally calculated values useless.
The graphics object applies rotation at the (0, 0) coordinate where as I should be doing something different. Not sure what.

From what I understand from here, here, and here, the Graphics.Drawing transformations are performed by multiplying matrices together in the order in which you apply the transformations.
With integers, a*b*c = b*a*c
However, with matricies, ABC almost never equals BAC.
So, it appears the order of transformations does matter, since matrix multiplication is not commutative.
Put another way, it seems that if I do the following on your picture:
case 1:
translate (100,50)
scale (0.5,0.7)
picture ends up with top-left corner at: (100,50)
and bottom-right corner at: (200,190)
case 2:
scale (0.5,0.7)
translate (100,50)
picture ends up with top-left corner at: (50,35)
and bottom-right corner at: (150,174)
This means that by scaling first, and then translating, that the scaling will also scale the amount of translation, that is why in case two the the picture ended up at (50,35) for the top left corner, half of the translated X and .7 of the translated Y.

Related

itextsharp rotate image at point [duplicate]

double degPi = degrees * Math.PI / 180;
double a = Math.cos(degPi)*tImgCover.getScaledHeight();
double b = Math.sin(degPi)*tImgCover.getScaledWidth();
double c = -Math.sin(degPi) * tImgCover.getScaledHeight();
double d = Math.cos(degPi)* tImgCover.getScaledWidth();
double e = absX;
double f = absY;
contentByte.addImage(imgae, a, b, c, d, e, f);/*add image*/
How to rotate around the image center by itext?
If we have an Image image and coordinates x, y, we can draw the image without rotation with its lower left corner at the given coordinates like this
contentByte.addImage(image, image.getWidth(), 0, 0, image.getHeight(), x, y);
A bitmap image from the resources has a size of 1x1 with the coordinate origin at its lower left. Thus, this operation stretches the image to its correct size and moves it so its lower left is at the given coordinates.
If we want to draw the same image as if the one drawn above was rotated around its center by an angle rotate, therefore, we can do this by moving the 1x1 image so that the origin is in its center, stretch it to its correct size, rotate it, and then move the origin (which still is at the center of the rotated image) to the center of the unrotated image. These operations are easier to express using AffineTransform instances (from package com.itextpdf.awt.geom) instead number tupels. Thus:
// Draw image as if the previous image was rotated around its center
// Image starts out being 1x1 with origin in lower left
// Move origin to center of image
AffineTransform A = AffineTransform.getTranslateInstance(-0.5, -0.5);
// Stretch it to its dimensions
AffineTransform B = AffineTransform.getScaleInstance(image.getWidth(), image.getHeight());
// Rotate it
AffineTransform C = AffineTransform.getRotateInstance(rotate);
// Move it to have the same center as above
AffineTransform D = AffineTransform.getTranslateInstance(x + image.getWidth()/2, y + image.getHeight()/2);
// Concatenate
AffineTransform M = (AffineTransform) A.clone();
M.preConcatenate(B);
M.preConcatenate(C);
M.preConcatenate(D);
//Draw
contentByte.addImage(image, M);
(AddRotatedImage.java test method testAddRotatedImage)
For example drawing both images using
int x = 200;
int y = 300;
float rotate = (float) Math.PI / 3;
results in something like this:
With a Flip
The OP asked in a comment
how to add rotate and flip image?
For this you simply insert a mirroring affine transformation into the sequence of transformations above.
Unfortunately the OP did not mention which he meant a horizontal or a vertical flip. But as changing the rotation angle accordingly transforms one in the other, that isn't really necessary, either.
// Draw image as if the previous image was flipped and rotated around its center
// Image starts out being 1x1 with origin in lower left
// Move origin to center of image
AffineTransform A = AffineTransform.getTranslateInstance(-0.5, -0.5);
// Flip it horizontally
AffineTransform B = new AffineTransform(-1, 0, 0, 1, 0, 0);
// Stretch it to its dimensions
AffineTransform C = AffineTransform.getScaleInstance(image.getWidth(), image.getHeight());
// Rotate it
AffineTransform D = AffineTransform.getRotateInstance(rotate);
// Move it to have the same center as above
AffineTransform E = AffineTransform.getTranslateInstance(x + image.getWidth()/2, y + image.getHeight()/2);
// Concatenate
AffineTransform M = (AffineTransform) A.clone();
M.preConcatenate(B);
M.preConcatenate(C);
M.preConcatenate(D);
M.preConcatenate(E);
//Draw
contentByte.addImage(image, M);
(AddRotatedImage.java test method testAddRotatedFlippedImage)
The result with the same image as above:
With Interpolation
The OP asked in a yet another comment
How anti aliasing ?
The iText Image class knows an Interpolation property. By setting it to true (before adding the image to the document, obviously),
image.setInterpolation(true);
low resolution images are subject to interpolation when drawn.
E.g. using a 2x2 image with differently colored pixels instead of the image of Willi, you get the following results, first without interpolation, then with interpolation:
Confer the AddRotatedImage.java test testAddRotatedInterpolatedImage which adds this image:
Beware: iText Image property Interpolation effectively sets the Interpolate entry in the PDF image dictionary. The PDF specification notes in this context:
NOTE A conforming Reader may choose to not implement this feature of PDF, or may use any specific implementation of interpolation that it wishes.
Thus, on some viewers interpolation may occur differently than in your viewer, maybe even not at all. If you need a specific kind of interpolation on every viewer, upscale the image with the desired amount of interpolation / anti-aliasing before loading it into an iText Image.
public static BufferedImage rotateClockwise90( BufferedImage inputImage ){
int width = inputImage.getWidth();
int height = inputImage.getHeight();
BufferedImage returnImage = new BufferedImage( height, width , inputImage.getType() );
for( int x = 0; x < width; x++ ) {
for( int y = 0; y < height; y++ ) {
returnImage.setRGB( height-y-1, x, inputImage.getRGB( x, y ) );
}
}
return returnImage;
}

C# fonts as 2D points

I am programming in C# and I need a way to get fonts as points in 2D. I basically want fonts converted to a mesh so that I can render it in 2D/3D. Are there any ways to do that? Or is it better to just download the meshes or something and then load them into my program.
In winforms you can use a GraphicsPath from System.Drawing.Drawing2D to
either acces the Bezier curves in the PathPoints and PathTypes data
or, after Flattening the path to acces the PathPoints array that now makes up an array of line segments.
Use one of the GraphicsPath.AddString methods to create the path..!
You may also want to look into the GraphicsPathIterator class, which..
Provides the ability to iterate through subpaths in a GraphicsPath and
test the types of shapes contained in each subpath..
Here is an example of drawing the flattened segment points:
Bitmap bmp = new Bitmap(400, 400);
GraphicsPath gp = new GraphicsPath();
using (Graphics g = Graphics.FromImage(bmp))
using (Font f = new Font("Tahoma", 40f))
{
g.ScaleTransform(4,4);
gp.AddString("Y?", f.FontFamily, 0, 40f, new Point(0, 0), StringFormat.GenericDefault);
g.DrawPath(Pens.Gray, gp);
gp.Flatten(new Matrix(), 0.2f); // <<== *
g.DrawPath(Pens.DarkSlateBlue, gp);
for (int i = 0; i < gp.PathPoints.Length; i++)
{
PointF p = gp.PathPoints[i];
g.FillEllipse(Brushes.DarkOrange, p.X-1, p.Y - 1, 2, 2);
}
pictureBox1.Image = bmp;
}
Note the 2nd Flatten parameter that lets you control how tight i.e. how closely the curve is approximated by the lines. The smaller the value the more 2d points are created..
To use the unflattended path you need to combine the PathPoints with their respective PathTypes; this is basically the same as creating Bezier curves: Two control points go between each pair of points. The types tell you where a figure starts/ends and where a line starts or a curve..
You can use the GlyphTypeface.GetGlyphOutline method to return the glyphs which make up the curves of the lettering. Note this is part of WPF.
MSDN:
Returns a Geometry value describing the path for a single glyph in the font
Under the hood I suspect it is calling the native function GetGlyphOutline
...which you could p-invoke from a WinForms/XNA app.

C# Pen.DashPattern

I want to draw a Line between 2 rows while using drag and drop. The function of this is simply visual, so that the user knows, where he is dropping the row. The line should look like the excel onces. Here my code:
Pen _marqueePen = new Pen(Color.Gray, 2);
float[] dashValues = {1f,1f};
_marqueePen.DashPattern = dashValues;
But this looks like that
I want to look it like that:
I'm WinForms and the C1 Flexgrid control.
You can use a Custom Pen like this:
using (Pen pen = new Pen(Color.Gray, 4f) )
{
pen.DashStyle = System.Drawing.Drawing2D.DashStyle.Custom;
pen.DashPattern = new float[] { 0.25F, 0.25F };
// now draw your stuff..
}
Note the doc on MSDN:
The elements in the dashArray array set the length of each dash
and space in the dash pattern. The first element sets the length of a dash,
the second element sets the length of a space, the third element sets
the length of a dash, and so on. Consequently, each element should be a
non-zero positive number.
The length of each dash and space in the dash pattern is the product
of the element value in the array and the width of the Pen.
You can pick any pen width and any dash&gap lengths as long as you keep their relation in mind.. So if you want the finest dashes, make sure they multiply to 1.0 pixels!
Here is the resulting line:
Some options:
You could use a PNG graphic that mimics that excel behaviour and then draw it on the control (you'll have to tile your image vertically).
Draw three lines with your code, with offset of y-axis & x-axis one pixel.
That looks to me more like a rectangle filed with HatchBrush having HatchStyle.Percent50 and height of 3.
You could try
Rectangle rect = new Rectangle(0, 0, 500, 3) //you will use the values here from your cursor but height will be 3
HatchBrush brush = new HatchBrush(HatchStyle.Percent50, Color.Black);
g.FillRectangle(brush, rect);

Cut rectangle area from given image

I have the following problem:
I have an image saved as: Image X; and a list of Point.
point is defined as following:
public struct Point
{
public int X;
public int Y;
}
on the list (which isn't sorted) there are 2 Points. the Points represent cords on the image. these cords define a rectangle shape. for example if cords are: (0,0) and (1,1) then the rectangle edges are: (0,0) - (0,1) - (1,1) - (1,0).
I am suppose to write a method that returns a cropped image where the rectangle lays. in the above example the cropped image will be everything within the boundary of (0,0) - (0,1) - (1,1) - (1,0).
any ideas for simple way to implement it?
note that i can't know where the rectangle lays in the image X. rectangles can have same area or even the exact same shape but in different places on the image.
assume it can be anywhere yet rectangle can not be outside of the image in any way (whole nor partly)
First of all, you need to get the min corner and the max corner, an easy way is:
//Having p1 and p2
Point min = new Point(Math.Min(p1.X, p2.X), Math.Min(p1.Y, p2.Y));
Point max = new Point(Math.Max(p1.X, p2.X), Math.Max(p1.Y, p2.Y));
Once you have max and min you can construct a rectangle for source:
Rectangle srcRect = new Rectangle(min.X, min.Y, max.X - min.X, max.Y - min.Y);
Then you create a Bitmap with the rect size:
Bitmap cropped= new Bitmap(srcRect.Width, srcRect.Height, System.Drawing.Imaging.PixelFormat.Format32bppPArgb);
Create a Graphics object from the image:
Graphics g = Graphics.FromImage(bmp);
And draw the cropped area:
//Having source image SrcImg
g.DrawImage(SrcImage, new Rectangle(Point.Empty, srcRect.Size), srcRect, GraphicsUnit.Pixel);
Now you have your cropped image at "cropped"
Don't forget to dispose graphics!!

How to determine actual object corner points in image with transparent background

I have a set of images of various objects of different shapes and sizes. They have transparent backgrounds set on them but the full dimension of the image is a square. I want to calculate a box of coordinates (upper left x/y, lower right x/y) that encompasses the object in the image while ignoring as much of the transparent background as possible. And I need to do this on the fly in code.
Is there an example, or a library, available for C# that would allow me to do this? I am using these in a website where several objects are dynamically overlaid into a single image and I want to calculate an image map with coordinates for each object in the merged image. Using the full size of the square image creates huge overlaps in the coordinate sets and often the last in coordinates hide the lower object from being clickable.
Well, using System.Drawing.Bitmap this is not too hard (the following certainly is not the most performant way):
// we will store actual bounds in here
int left, right, top, bottom;
using (Bitmap b = ...) // open image here
{
var pixelsX = Enumerable.Range(0, b.Width);
var pixelsY = Enumerable.Range(0, b.Height);
left = pixelsX.FirstOrDefault(
x => pixelsY.Any(y => b.GetPixel(x, y).A != 0));
right = pixelsX.Reverse().FirstOrDefault(
x => pixelsY.Any(y => b.GetPixel(x, y).A != 0));
top = pixelsY.FirstOrDefault(
y => pixelsX.Any(x => b.GetPixel(x, y).A != 0));
bottom = pixelsY.Reverse().FirstOrDefault(
y => pixelsX.Any(x => b.GetPixel(x, y).A != 0));
}
Notice that all these 4 coordinates are "inclusive" bounds (meaning: the row/column of pixels they represent does contain at least one non-transparent pixel), so if you should calculate width and height of your new bounds do it like this:
int width = right - left + 1;
int height = bottom - top + 1;
By the way, for an entirely transparent image, all 4 coordinates should be 0, as a result width and height will both be 1 - I guess this is not a problem for you.

Categories

Resources