Adapt Rectangle's region to a different size - c#

Under Windows Forms, I take a full size screenshot of a specific window that has a specific window size, I save it into a Bitmap object, then, I declared a Rectangle structure to crop a region of that Bitmap, because later I need to manipulate only a very specific part/region of the screenshot...
To make things simpler for this question, lets say the window and bitmap size is 640x480, the Rectangle's X,Y is: 436,150 and the Width,Height is: 146,170, and what I crop from the screenshot (the bitmap) is a balloon image. The window is a videogame.
The problem is that when the window size increase, the balloon image increase too, as obvious, so the x,y and width/height of my rectangle for a window size of 640x480 will not properly capture/crop the entire balloon image when the window of the game has a bigger size...
I need to know how can I calculate the x,y width/height that my rectangle should have to properly crop the balloon image when the window size changes. I need to adapt the rectangle.
So, if this is the predefined size and rectangle I have:
{ new Size(640, 480), new Rectangle(436, 150, 146, 170) }
From that, the approximated adapted values that the rectangle should have to properly crop the same equivalent area in a window size of 800x600 and 1280x768 it would be more or less these:
{ new Size(800, 600), new Rectangle(546, 186, 186, 212) }
{ new Size(1280, 768), new Rectangle(830, 232, 240, 274) }
...are just approximated values, but not perfect, because I did it manually since Im not sure which is the way to calculate and automate this math operation.
I hope my question and problem was understood. Thankyou in advance.

Maybe you're over-thinking it, but all you need to do is capture the percentage change between the original size and the new size (for both X and Y), and then apply that percentage to the properties of the original rectangle to get the new rectangle.
For example:
public static Rectangle GetNewRectangle(Size oldSize, Rectangle oldRectangle,
Size newSize)
{
var percentChangeX = (double)newSize.Width / oldSize.Width;
var percentChangeY = (double)newSize.Height / oldSize.Height;
return new Rectangle
{
X = (int)(oldRectangle.X * percentChangeX),
Y = (int)(oldRectangle.Y * percentChangeY),
Width = (int)(oldRectangle.Width * percentChangeX),
Height = (int)(oldRectangle.Height * percentChangeY)
};
}
Example usage:
// Helper method to display size and rectangle properties
private static string GetDisplayValues(Size size, Rectangle rect)
{
return $" - size: {size.Width} x {size.Height}\n" +
$" - rect: {rect.X}, {rect.Y} : {rect.Width} x {rect.Height}\n";
}
private static void Main()
{
var size = new Size(640, 480);
var rect = new Rectangle(436, 150, 146, 170);
Console.WriteLine($"Original:\n{GetDisplayValues(size, rect)}");
var newSize = new Size(800, 600);
var newRect = GetNewRectangle(size, rect, newSize);
Console.WriteLine($"Resized:\n{GetDisplayValues(newSize, newRect)}");
GetKeyFromUser("\nDone! Press any key to exit...");
}
Output

Try this:
if width 640:
X = 436 / 640 = 0.68125 (68.125%)
W = 146 / 640 = 0.22125 (22.125%)
if heigth 480:
Y = 150 / 480 = 0.3125 (31.25%)
H = 170 / 480 = 0.3541666666666666666666666667 (35.41666666666666666666666667%)
Considering the size of the form as this.Width, and the height as this.Height:
decimal pX = 0.68125;
decimal pW = 0.22125;
decimal pY = 0.3125;
decimal pH = 0.3541666666666666666666666667;
Rectangle rect = new Rectangle(this.Width * pX, this.Height * pY, this.Width * pW, this.Height * pH);

Given a source Bitmap and a selection Rectangle inside its boundaries:
RectangleF SourceRect = new Rectangle(Point.Empty, SourceBitmap.Size);
Rectangle SelectionRect = new Rectangle([Point], [Size]);
When the SourceBitmap changes its size, the new size of the selection rectangle is calculated using the scale factor given by the relation between the old size and the new size of the SourceBitmap:
RectangleF DestinationRect = new RectangleF(Point.Empty, InflatedBitmap.Size);
SizeF ScaleFactor = new SizeF(DestinationRect.Width / SourceRect.Width,
DestinationRect.Height / SourceRect.Height);
PointF NewPosition = new PointF(SelectionRect.X * ScaleFactor.Width, SelectionRect.Y * ScaleFactor.Height);
SizeF NewSize = new SizeF(SelectionRect.Width * ScaleFactor.Width, SelectionRect.Height * ScaleFactor.Height);
RectangleF InflatedSelection = new RectangleF(NewPosition, NewSize);
With a SourceBitmap and a selection rectangle sized as:
RectangleF SourceRect = new RectangleF(0, 0, 640, 480);
RectangleF SelectionRect = new RectangleF(436, 150, 146, 170);
If the inflated bitmaps are sized as:
RectangleF DestinationRect1 = new RectangleF(0, 0, 800, 600);
RectangleF DestinationRect2 = new RectangleF(0, 0, 1280, 768);
The Inflated selection with a scale factor of (1.25, 1.25) and (2, 1.6) will be (rounded down):
RectangleF InflatedSelection1 = new RectangleF(545, 187, 182, 212);
RectangleF InflatedSelection2 = new RectangleF(872, 240, 292, 272);

Related

How to draw a rectangle using WriteableBitmap?

I created a WPF project which just includes an Image control.
<Image
x:Name='img'
Width='256'
Height='256'
MouseDown='img_MouseDown' />
My goal is to click the image and draw a 10 pixel side square, of white color, at the specific position where the click happened.
At the begining I tried to draw 1 pixel sized squares and worked as expected.
Here is that code:
public partial class MainWindow : Window
{
WriteableBitmap wb;
public MainWindow()
{
InitializeComponent();
wb = new WriteableBitmap(256, 256, 96d, 96d, PixelFormats.Bgr24, null);
img.Source = wb;
}
private void img_MouseDown(object sender, MouseButtonEventArgs e)
{
Point p = e.GetPosition(img);
Int32Rect rect = new Int32Rect((int)p.X, (int)p.Y, 1, 1);
int stride = wb.PixelWidth * wb.Format.BitsPerPixel / 8;
byte[] buffer = { 255, 255, 255 };
wb.WritePixels(rect, buffer, stride, 0);
}
}
Now that I want to draw a 10 pixel size square I am initializing the rect with 10 pixels width and height,
Int32Rect rect = new Int32Rect((int)p.X, (int)p.Y, 10, 10);
,but WritePixels() throws an Exception saying "Buffer size is not sufficient." Out of desperation I've changed the buffer to have 10 size but still getting the same error.
What is the problem here?
The stride argument is meant to be that of the input buffer, i.e. 3 x 10 here:
var width = 10;
var height = 10;
var stride = (width * wb.Format.BitsPerPixel + 7) / 8;
var rect = new Int32Rect((int)p.X, (int)p.Y, width, height);
var buffer = Enumerable.Range(0, stride * height).Select(i => (byte)255).ToArray();
wb.WritePixels(rect, buffer, stride, 0);

Calculating text width for DXF

I am using netDXF (https://netdxf.codeplex.com/) to generate a DXF file for use with AutoCAD. However, I have an issue with getting the width of MText correct. I want to be able to define a width that the text should fit into, and change the width factor of the text (squash it horizontally) so that it fits in the defined area. So if I have a 40mm width to fit the text into and the text is 80mm long, it needs to have a width factor of 0.5. The only problem is that I don't know how to accurately determine the width of the text. I have tried the following methods and was unsuccessful in getting the correct result:
Why is Graphics.MeasureString() returning a higher than expected number?
Measure a String without using a Graphics object?
http://www.codeproject.com/Articles/2118/Bypass-Graphics-MeasureString-limitations
I have attached my code. I am basically printing a horizontal line using each of the 3 methods to calculate text width and comparing it to the actual text width. If I change the font, I get varying results. I have attached two images. One using the code with Calibri and one with Arial. I need the line to be on the edges of the text no matter what font I use.
Here is my code:
public void TestMethod1()
{
Application.SetCompatibleTextRenderingDefault(false);
//text width in mm
float textWidth = 40;
float textHeight = 200;
string labelText = "HELLO WORLD!";
TextStyle textStyle = new TextStyle("Calibri");
DxfDocument dxf = new DxfDocument();
Layer layer1 = new Layer("layer1");
layer1.Color = new AciColor(0, 0, 255);
layer1.Name = "Text";
MText text1 = new MText(new Vector2(0, 0), textHeight, 0, textStyle);
text1.Layer = layer1;
text1.AttachmentPoint = MTextAttachmentPoint.MiddleCenter;
//Will the text fit in the bounds of the rectangle? If not change width factor so it does.
Font f = new Font(textStyle.FontName, textHeight);
Size size = TextRenderer.MeasureText(labelText, f);
SizeF sizeF = graphicsMeasureString(labelText, f);
int width = MeasureDisplayStringWidth(labelText, f);
float widthFactor = Math.Min(1, textWidth / sizeF.Width);
MTextFormattingOptions mtextOptions = new MTextFormattingOptions(text1.Style);
//mtextOptions.WidthFactor = widthFactor;
text1.Write(labelText, mtextOptions);
//Red, g.MeasureString
Line line1 = new Line(new Vector2(0 - sizeF.Width / 2, 0), new Vector2(0 + sizeF.Width / 2, 0));
line1.Color = new AciColor(255, 0, 0);
//Green, TextRenderer
Line line2 = new Line(new Vector2(0 - size.Width / 2, 5), new Vector2(0 + size.Width / 2, 5));
line2.Color = new AciColor(0, 255, 0);
//Yellow, MeasureDisplayStringWidth
Line line3 = new Line(new Vector2(0 - width / 2, -5), new Vector2(0 + width / 2, -5));
line3.Color = new AciColor(255, 255, 0);
dxf.AddEntity(text1);
dxf.AddEntity(line1);
dxf.AddEntity(line2);
dxf.AddEntity(line3);
dxf.Save("Text Width Test.dxf");
}
public SizeF graphicsMeasureString(string text, Font f)
{
Bitmap fakeImage = new Bitmap(1, 1);
Graphics g = Graphics.FromImage(fakeImage);
SizeF sizeF = g.MeasureString(text, f, new PointF(100, 0), StringFormat.GenericTypographic);
return sizeF;
}
public int MeasureDisplayStringWidth(string text, Font f)
{
Size size = TextRenderer.MeasureText(text, f);
Bitmap fakeImage = new Bitmap(1, 1);
Graphics g = Graphics.FromImage(fakeImage);
System.Drawing.StringFormat format = new System.Drawing.StringFormat();
System.Drawing.RectangleF rect = new System.Drawing.RectangleF(0, 0, 1000, 1000);
System.Drawing.CharacterRange[] ranges = { new System.Drawing.CharacterRange(0, text.Length) };
System.Drawing.Region[] regions = new System.Drawing.Region[1];
format.SetMeasurableCharacterRanges(ranges);
regions = g.MeasureCharacterRanges(text, f, rect, format);
rect = regions[0].GetBounds(g);
return (int)(rect.Right + 1.0f);
}

Autoscalable background image

I have got .png HD (1024x768) backgroud image. My users could have different screen sizes, but it should look always the same, so:
Is that possible to automatically scale it size to the background size in xna?
First, you have to render whole scene into RenderTarget and then draw it using rectangle for position and size.
Rectangle dest = new Rectangle (0, 0, graphics.ViewPort.Width, graphics.ViewPort.Height);
spriteBatch.Draw(RenderTarget, dest, Color.White);
it could looks stretched/squeezed if you work in 16:9 and user have 4:3 then:
int height = (int)(graphics.ViewPort.Width * (16.0/9.0));
Rectangle dest = new Rectangle (0, graphics.ViewPort.Height -- (int)(height / 2.0), graphics.ViewPort.Width, graphics.ViewPort.Height);
spriteBatch.Draw(RenderTarget, dest, Color.White);
or in general where width >= height:
double aspectratio = ((double)graphics.ViewPort.Width / (double)graphics.Viewport.Height);
int height = (int)(graphics.ViewPort.Width * aspectratio;
Rectangle dest = new Rectangle(0, graphics.ViewPort.Height - (int)(height / 2.0), graphics.ViewPort.Width, height);
and here is some nice example of independed screen resolution:
http://www.david-amador.com/2010/03/xna-2d-independent-resolution-rendering/

In my heatmap graphing component, the y-axis time text does not exactly align

I have a method that takes in a bitmap, width, height, and a list of strings, overlays it on top of a black background, and adds y-axis text onto it (as demonstrated in the picture below).
In my attempt to align the text so they occupy the exact vertical space of my graph, I took the height and divided it by the number of values in the list:
float verticalDistance = height / times.Count;
However, perhaps of alignment issues, the space between each time value on the y-axis doesn't fix exactly into the height of the original bitmap, which is the variable height.
I thought it was an issue with int and it rounding numbers up or down, but changing it verticalDistance to a float did not ameliorate the issue.
private Bitmap overlayBitmap(Bitmap sourceBMP, int width, int height, List<String> times) {
int newWidth = width + (width / 3);
int newHeight = height + (height / 3);
Bitmap result = new Bitmap(newWidth, newHeight);
using (Graphics g = Graphics.FromImage(result)) {
g.FillRectangle(Brushes.Black, 0, 0, newWidth, newHeight);
g.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.NearestNeighbor;
StringFormat stringFormatX = new StringFormat();
stringFormatX.LineAlignment = StringAlignment.Center;
Font drawFontX = new Font("Whitney", 10);
float verticalDistance = height / times.Count;
for (int i = 0; i < times.Count; i++) {
if (i % 5 == 0) {
g.DrawString(times[i], drawFontX, Brushes.White, 5, ((newHeight - height)/2) + (verticalDistance * i), stringFormatX);
}
}
g.DrawImage(sourceBMP, width / 6, height / 6, width, height);
}
return result;
}
What could be the issue here?
Reduce your problem to the simplest case: What if you had just two values to display on your Y axis? Your vertical distance would be the length of the axis. Your vertical distance formula would not work in that case. Therefore
float verticalDistance = height / (times.Count - 1.0);

(C#) How to draw an "a" with Tahoma with height = 6

I've been trying to do this, but for some reason this is just giving me weird results:
int bpp = Screen.PrimaryScreen.BitsPerPixel;
string fontName = "Tahoma";
Font font = new Font(fontName, 10 * bpp, GraphicsUnit.Point);
Bitmap bm = new Bitmap(20 * bpp, 20 * bpp);
Graphics g = Graphics.FromImage(bm);
TextRenderer.DrawText(g, "a", font, new Rectangle(0, 0, 5 * bpp, 6 * bpp), Color.Black);
g.Flush();
pictureBox1.Image = bm;
What am I doing wrong here? I don't see anything printed on the picture. If I remove all bpp references, I can see it, but it's pretty small.
You are aware that BitsPerPixel describes the color depth (the number of memory bits that are used to describe the color of a pixel), and has nothing to do with resolution?
I assume that what you want to do is to draw the text in a size that is related to the resolution, which you can do by referring to the DpiX and DpiY properties of the Graphics object.
Update
I am not sure if yo need to bring Dpi into the calculation for this. All you need to do is to create a Rectangle that defines the desired size of your text, and then calculate the correct font size to make the text fit inside the rectangle. The following does that (but maximizes the text size both vertical and horizontal direction). It might give you some pointers to solve your problem:
Bitmap bm = new Bitmap(50, 50);
using (Font font = new Font(fontName, 10, GraphicsUnit.Point))
using (Graphics g = Graphics.FromImage(bm))
{
g.TextRenderingHint = System.Drawing.Text.TextRenderingHint.AntiAlias;
StringFormat stringFormat = new StringFormat()
{
Alignment = StringAlignment.Center,
LineAlignment = StringAlignment.Near
};
Rectangle rect = new Rectangle(0, 0, bm.Width, bm.Height);
// measure how large the text is on the Graphics object with the current font size
SizeF s = g.MeasureString(text, font);
// calculate how to scale the font to make the text fit
float fontScale = Math.Max(s.Width / rect.Width, s.Height / rect.Height);
using (Font fontForDrawing = new Font(font.FontFamily, font.SizeInPoints / fontScale, GraphicsUnit.Point))
{
g.DrawString(text, fontForDrawing, Brushes.Black, rect, stringFormat);
}
}
And if you want to print the text with a given point size, you don't need to go about measuring; just set the font size:
Bitmap bm = new Bitmap(20, 20);
using (Font font = new Font(fontName, 6, GraphicsUnit.Point))
using (Graphics g = Graphics.FromImage(bm))
{
g.TextRenderingHint = System.Drawing.Text.TextRenderingHint.AntiAlias;
StringFormat stringFormat = new StringFormat()
{
Alignment = StringAlignment.Center,
LineAlignment = StringAlignment.Near
};
Rectangle rect = new Rectangle(0, 0, bm.Width, bm.Height);
g.DrawString(text, font, Brushes.Black, rect, stringFormat);
}
I would try:
int ImgQual = 600;
int Width = 50;
int Height = 50;
Font TextFont = New Font("Tahoma", 14, FontStyle.Bold)
Bitmap bmp = New Bitmap(Width, Height);
bmp.SetResolution(ImgQual, ImgQual);
System.Drawing.Graphics g = Graphics.FromImage(bmp);
System.Drawing.StringFormat sf = New System.Drawing.StringFormat();
sf.Alignment = StringAlignment.Center;
g.DrawString("a", NumberTextFont, Brushes.Black, New RectangleF(0, 0, Width, Height), sf);
return bmp;
There are two primary reasons the "a" is small:
The height of a font is measured using a point size (which is 1/72nd of a inch, quite different from a number of pixels because different computer screens have different numbers of pixels per inch).
Font height is measured from the top of a line of text to the bottom. As some letters have ascenders (the tall bar in "h") and others have descenders (the dangly bit of a "g"), an "a" will occupy only about a third of the height of the font.
If you wish your "a" to fill the image, then you will need to draw it into a larger rectangle and "clip off" the empty regions above and below the "a". If you're not too worried about screen DPI on different computers, then you can just experiment until you find a font size (and position to draw at) that works.

Categories

Resources