I'm trying to generate a custom Bitmap through code at a very small size and display it to a PictureBox, upscaled to fit said PictureBox. I am using the graphics object to do this in order to use NearestNeighbor interpolation to upscale single pixels perfectly.
I'm using the graphics object of a temporary default image that is in the PictureBoxs "Image" component on Form.Load, which is sized to be the perfect width and height to maintain the correct aspect ratio from the original Bitmap.
Here is the relevant code:
private void Form1_Load(object sender, EventArgs e)
{
bmp = new Bitmap(16, 9, PixelFormat.Format24bppRgb);
rnd = new Random();
GenerateImage();
}
private void GenerateImage()
{
for (int x = 0; x < bmp.Width; x++)
{
for (int y = 0; y < bmp.Height; y++)
{
int num = rnd.Next(2);
if (num == 0)
{
bmp.SetPixel(x, y, Color.White);
}
else
{
bmp.SetPixel(x, y, Color.Gold);
}
}
}
Bitmap image = new Bitmap(picOutput.Image);
grp = Graphics.FromImage(image);
grp.InterpolationMode = InterpolationMode.NearestNeighbor;
grp.DrawImage(
bmp,
new Rectangle(0, 0, image.Width, image.Height),
0,
0,
bmp.Width,
bmp.Height,
GraphicsUnit.Pixel
);
grp.Dispose();
picOutput.Image = image;
}
The problem is that the Bitmap seems to be drawn incorrectly. About half a pixel from the original Bitmap is cut off on the left and top edges of the Bitmap when displayed through the PictureBox, and that roughly half a pixel shows up as the original default image on the right and bottom edges. It's almost like the Bitmap was offset up and to the left while being drawn by the graphics object, it doesn't perfectly cover up the original default image like it was supposed to.
My first thought was the PictureBoxs SizeMode, which is still set to "Normal," but none of them change the problem at all. Here is a picture of the problem. The black edges on the right and bottom are part of the temporary default image (the image I used graphics from), which is completely black and covers the entire PictureBox area.
Can anyone offer some insight?
As user Jimi pointed out in a comment, grp.PixelOffsetMode = PixelOffsetMode.Half from this post solved the issue.
Related
I try to draw a string (single character) in C# into a Bitmap at an exact position with:
Bitmap bmp = new Bitmap(64, 64);
Graphics g = Graphics.FromImage(bmp);
g.DrawString("W", font1, new SolidBrush(myColor), new Point(32,32);
There is so much empty space rendered around a single letter, that I can not guess the "needed" position to draw the character to have it at the correct position at the end.
By now I have the pixel exact dimension of the character (looking at bits in a separately rendered bitmap). But this information is useless, if I cannot draw the character at an exact position (e.g. center or top right corner or ....).
Are there other methods to draw text in C# on a bitmap? Or are there any converting methods to convert the real pixel position in something DrawString needs?
No need to look at the pixels or start working with your own font..
You can use a GraphicsPath instead of DrawString or TextRenderer, as it will let you know its net bounds rectangle with GraphicsPath.GetBounds() .
When you know it, you can calculate how to move the Graphics object using TranslateTransform:
private void button1_Click(object sender, EventArgs e)
{
string text = "Y"; // whatever
Bitmap bmp = new Bitmap(64, 64); // whatever
bmp.SetResolution(96, 96); // whatever
float fontSize = 32f; // whatever
using ( Graphics g = Graphics.FromImage(bmp))
using ( GraphicsPath GP = new GraphicsPath())
using ( FontFamily fontF = new FontFamily("Arial"))
{
testPattern(g, bmp.Size); // optional
GP.AddString(text, fontF, 0, fontSize, Point.Empty,
StringFormat.GenericTypographic);
// this is the net bounds without any whitespace:
Rectangle br = Rectangle.Round(GP.GetBounds());
g.DrawRectangle(Pens.Red,br); // just for testing
// now we center:
g.TranslateTransform( (bmp.Width - br.Width ) / 2 - br.X,
(bmp.Height - br.Height )/ 2 - br.Y);
// and fill
g.FillPath(Brushes.Black, GP);
g.ResetTransform();
}
// whatever you want to do..
pictureBox1.Image = bmp;
bmp.Save("D:\\__test.png", ImageFormat.Png);
}
A small test routine to let us see the centering better:
void testPattern(Graphics g, Size sz)
{
List<Brush> brushes = new List<Brush>()
{ Brushes.SlateBlue, Brushes.Yellow,
Brushes.DarkGoldenrod, Brushes.Lavender };
int bw2 = sz.Width / 2;
int bh2 = sz.Height / 2;
for (int i = bw2; i > 0; i--)
g.FillRectangle(brushes[i%4],bw2 - i, bh2 - i, i + i, i + i );
}
The GetBounds method returns a RectangleF; in my example it is {X=0.09375, Y=6.0625, Width=21, Height=22.90625}. Do note that due to rounding things can always be off by one..
You may or may not want to change the Graphics setting to special Smoothingmodes etc..
Also it should be noted that this will do automatic ie mechanical centering by the bounds rectangle. This may be quite different from 'optical or visual centering', which is rather hard to code and to some extent a matter of personal taste. But typography is as much an art as a profession..
I'm wondering if that's possible:
I got a c# application with something like a display consisting of about 11000 circles drawn on the Form.
What I want to achieve is to be able to draw text on that display, but not using the "real" pixels, but using the circles (rectangles) drawn on the form as pixels.
Edit 1:
When drawing text in c#, you would i.e. use something like Graphics.DrawString(...), giving the method a rectangle (so coordinates) in which the text should be drawn in. That text then is drawn in that rectangle using the screen pixels. What I want to do is draw text as well but not using the screen pixels but my custom pixels of which my display consists.
Edit 2
Method used to draw the circles on the Form; Circles is a list consisting of Circle objects, where circleRectangle returns the coordinates in which the circle should be drawn and Filled tells the method if the circle should be filled or not.
public void DrawCircles(Graphics g)
{
graphics = g;
graphics.SmoothingMode =System.Drawing.Drawing2D.SmoothingMode.AntiAlias;
Pen pen = new Pen(Color.Black, penthickness);
SolidBrush brush = new SolidBrush(Color.White);
for (int j = 0; j < Circles.Count;j++ )
{
graphics.DrawEllipse(pen, Circles[j].CircleRectangle);
if (Circles[j].Filled)
brush.Color = fillColor;
else
brush.Color = Color.White;
graphics.FillEllipse(brush, Circles[j].CircleRectangle);
}
}
Is this possible and if yes, how would I do that?
You could write on an invisible BitMap with the DrawText method and then scan the bitmap's pixels and turn the corresponding circles on.
Did that last week with the cells of DataGridView. Real easy.
Here is some code:
public void drawText(string text, Font drawFont)
{
Bitmap bmp = new Bitmap(canvasWidth, canvasHeight);
Graphics G = Graphics.FromImage(bmp);
SolidBrush brush = new SolidBrush(paintColor);
Point point = new Point( yourOriginX, yourOriginY );
G.DrawString(text, drawFont, brush, point);
for (int x = 0; x < canvasWidth; x++)
for (int y = 0; y < canvasHeight; y++)
{
Color pix = bmp.GetPixel(x, y);
setCell(x, y, pix); //< -- set your custom pixels here!
}
bmp.Dispose();
brush.Dispose();
G.Dispose();
}
Edit: You would use your dimensions and your origin for the DrawString, of course
The image has 200px width and the pictureBox has 400px.
Which property of pictureBox should I set to display the image repeat-x?
I don't think there is any property which makes the image display x times repeatedly horizontally. But a little custom code can help, here is my code:
public static class PictureBoxExtension
{
public static void SetImage(this PictureBox pic, Image image, int repeatX)
{
Bitmap bm = new Bitmap(image.Width * repeatX, image.Height);
Graphics gp = Graphics.FromImage(bm);
for (int x = 0; x <= bm.Width - image.Width; x += image.Width)
{
gp.DrawImage(image, new Point(x, 0));
}
pic.Image = bm;
}
}
//Now you can use the extension method SetImage to make the PictureBox display the image x-repeatedly
myPictureBox.SetImage(myImage, 3);//repeat 3 images horizontally.
You can customize the code to make it repeat vertically or look like a check board.
Hope it helps!
Hello i am trying to draw about 1000 images and between 10 to 100 rectangels and elipses.
But i need all of them to show up on screen only when they done loading(not in a loading screen but in a game or slideshow). so for example
texturegrass = MyApp.Properties.Resources.Grass
Rectangle[] rects;
recs = new Rectangle[1000]
for (int i = 0; i < rects.Length; i++)
{
g.DrawImage(texturegrass,rects[i]);
}
this is what i done so far but every rectangle is been drawn by it own what cause a flickering problam.
I have double bufferd the app.
I tried using parallel but the application keep crashing
I hope one of you guys can help me...
##*
You can use a Graphics object to create the image off-screen, and then draw the image on the screen using the screen's Graphics object.
var bmp = new Bitmap(MyWidth, CMyHeight);
var gOff = Graphics.FromImage(bmp);
gOff.FillRectangle(new SolidBrush(Color.White), 0, 0, bmp.Width, bmp.Height);
texturegrass = MyApp.Properties.Resources.Grass
Rectangle[] rects = ...;
recs = new Rectangle[1000]
for (int i = 0; i < rects.Length; i++) {
gOff.DrawImage(texturegrass,rects[i]);
}
At this point you can draw bmp all at once on the screen's Graphic.
Microsoft: How to Draw Images Off-Screen
Using GDI+ I've made a heatmap bmp and I'd like to superimpose it on top of my bmp map. I've saved the two bmps to disk, and they look good, I just need a way to put them together. Is there any way to do this, perhaps using the Graphics object? How is transparency/alpa involved?
I'm very new to GDI programming so please be as specific as possible.
OK - here's an answer. At some point I need to learn how GDI+ works...
I couldn't get around the transarency issues, but this works. It just copies the non-white pixels from the overlay to the map:
for (int x = 0; x < map.Width; x++)
for (int y = 0; y < map.Height; y++) {
Color c = overlay.GetPixel(x, y);
if ((c.A != 255) || (c.B != 255) || (c.G != 255) || (c.R != 255))
map.SetPixel(x, y, c);
This should do the job...
At the moment the Image you want to superimpose onto the main image will be located in the top left corner of the main Image, hence the new Point(0,0). However you could change this to locate the image anywhere you want.
void SuperimposeImage()
{
//load both images
Image mainImage = Bitmap.FromFile("PathOfImageGoesHere");
Image imposeImage = Bitmap.FromFile("PathOfImageGoesHere");
//create graphics from main image
using (Graphics g = Graphics.FromImage(mainImage))
{
//draw other image on top of main Image
g.DrawImage(imposeImage, new Point(0, 0));
//save new image
mainImage.Save("OutputFileName");
}
}