I want to display a lengthy rotated string in the background of one of my rows in a DataGridView. However, this:
private void dataGridView1_RowPostPaint(object sender, DataGridViewRowPostPaintEventArgs e)
{
if (e.RowIndex == 0)
{
...
//Draw the string
Graphics g = dataGridView1.CreateGraphics();
g.Clip = new Region(e.RowBounds);
g.RotateTransform(-45);
g.DrawString(printMe, font, brush, e.RowBounds, format);
}
}
does not work because text is clipped before it's rotated.
I've also tried painting on a Bitmap first, but there seems to be a problem painting transparent Bitmaps - the text comes out pure black.
Any ideas?
I figured it out. The problem was that Bitmaps apparently don't have transparency, even when you use PixelFormat.Format32bppArgb. Drawing the string caused it to draw over a black background, which is why it was so dark.
The solution was to copy the row from the screen onto a bitmap, draw onto the bitmap, and copy that back to the screen.
g.CopyFromScreen(absolutePosition, Point.Empty, args.RowBounds.Size);
//Draw the rotated string here
args.Graphics.DrawImageUnscaledAndClipped(buffer, args.RowBounds);
Here is the full code listing for reference:
private void dataGridView1_RowPostPaint(object sender, DataGridViewRowPostPaintEventArgs args)
{
if(args.RowIndex == 0)
{
Font font = new Font("Verdana", 11);
Brush brush = new SolidBrush(Color.FromArgb(70, Color.DarkGreen));
StringFormat format = new StringFormat
{
FormatFlags = StringFormatFlags.NoWrap | StringFormatFlags.NoClip,
Trimming = StringTrimming.None,
};
//Setup the string to be printed
string printMe = String.Join(" ", Enumerable.Repeat("RUNNING", 10).ToArray());
printMe = String.Join(Environment.NewLine, Enumerable.Repeat(printMe, 50).ToArray());
//Draw string onto a bitmap
Bitmap buffer = new Bitmap(args.RowBounds.Width, args.RowBounds.Height);
Graphics g = Graphics.FromImage(buffer);
Point absolutePosition = dataGridView1.PointToScreen(args.RowBounds.Location);
g.CopyFromScreen(absolutePosition, Point.Empty, args.RowBounds.Size);
g.RotateTransform(-45, MatrixOrder.Append);
g.TranslateTransform(-50, 0, MatrixOrder.Append); //So we don't see the corner of the rotated rectangle
g.DrawString(printMe, font, brush, args.RowBounds, format);
//Draw the bitmap onto the table
args.Graphics.DrawImageUnscaledAndClipped(buffer, args.RowBounds);
}
}
Related
There is only one picturebox in my form and I want to drawcircle with a method on this picturebox but I cant do that and not working.The method is:
private Bitmap Circle()
{
Bitmap bmp;
Graphics gfx;
SolidBrush firca_dis=new SolidBrush(Color.FromArgb(192,0,192));
bmp = new Bitmap(40, 40);
gfx = Graphics.FromImage(bmp);
gfx.FillRectangle(firca_dis, 0, 0, 40, 40);
return bmp;
}
Picturebox
private void pictureBox2_Paint(object sender, PaintEventArgs e)
{
Graphics gfx= Graphics.FromImage(Circle());
gfx=e.Graphics;
}
You need to decide what you want to do:
Draw into the Image or
draw onto the Control?
Your code is a mix of both, which is why it doesn't work.
Here is how to draw onto the Control:
private void pictureBox1_Paint(object sender, PaintEventArgs e)
{
e.Graphics.DrawEllipse(Pens.Red, new Rectangle(3, 4, 44, 44));
..
}
Here is how to draw into the Image of the PictureBox:
void drawIntoImage()
{
using (Graphics G = Graphics.FromImage(pictureBox1.Image))
{
G.DrawEllipse(Pens.Orange, new Rectangle(13, 14, 44, 44));
..
}
// when done with all drawing you can enforce the display update by calling:
pictureBox1.Refresh();
}
Both ways to draw are persistent. The latter changes to pixels of the Image, the former doesn't.
So if the pixels are drawn into the Image and you zoom, stretch or shift the Image the pixel will go with it. Pixels drawn onto the Top of the PictureBox control won't do that!
Of course for both ways to draw, you can alter all the usual parts like the drawing command, maybe add a FillEllipse before the DrawEllipse, the Pens and Brushes with their Brush type and Colors and the dimensions.
private static void DrawCircle(Graphics gfx)
{
SolidBrush firca_dis = new SolidBrush(Color.FromArgb(192, 0, 192));
Rectangle rec = new Rectangle(0, 0, 40, 40); //Size and location of the Circle
gfx.FillEllipse(firca_dis, rec); //Draw a Circle and fill it
gfx.DrawEllipse(new Pen(firca_dis), rec); //draw a the border of the cicle your choice
}
I try to do double buffer using BufferedGraphics. When i use BufferedGraphics.Render method background of my image changes to black. Here the simple code, that illustrate my issue
public partial class Form1 : Form {
public Form1() {
InitializeComponent();
this.Load += Form1_Load;
}
private void Form1_Load(object sender, EventArgs e) {
Paint += new PaintEventHandler(Form1_Paint);
}
private void print(Bitmap image, PaintEventArgs e) {
Graphics graphicsObj = e.Graphics;
graphicsObj.DrawImage(image, 60, 10);
graphicsObj.Dispose();
}
private void Form1_Paint(object sender, PaintEventArgs e) {
Rectangle rect = Screen.PrimaryScreen.Bounds;
PixelFormat pf;
pf = PixelFormat.Format32bppArgb;
Bitmap image = new Bitmap(rect.Width, rect.Height, pf);
Graphics g = Graphics.FromImage(image);
g.Clear(Color.Orange);
BufferedGraphicsContext context = new BufferedGraphicsContext();
BufferedGraphics buffer = context.Allocate(g, new Rectangle(0, 0, rect.Width + 20, rect.Height + 20));
buffer.Render(g);
print(image, e);
}
}
I expect to see orange rectangle on my screen, but it's black. I can't understand why this happen. Help me please :)
buffer.Render(g) renders the contents of the buffer to the graphics object. This means that the orange color is being overwritten by the empty buffer.
You'll have to choose between using BufferedGraphicsContext or creating a buffer yourself (the image).
The following would fix your issue using just the image:
...
Bitmap image = new Bitmap(rect.Width, rect.Height, pf);
using (Graphics g = Graphics.FromImage(image))
{
g.Clear(Color.Orange);
}
print(image, e);
You could also still use BufferedGraphicsContext, but you'd have to write the image to its Graphics property:
print(image, buffer.Graphics); // render your image to the buffer
buffer.Render(e.Graphics); // render the buffer to the paint event graphics
By the way, don't Dispose the graphics object provided by Form1_Paint (you currently do that in the print() method.
As a response to your comment, BufferedGraphicsContext seems to not support transparency when rendering it to the "main" graphics object, but you can draw transparent images to it correctly. The following example shows how the buffer is filled with a red color, and then a transparent image with a blue line is drawn to it:
protected override void OnPaint(PaintEventArgs e)
{
base.OnPaint(e);
using (BufferedGraphicsContext context = new BufferedGraphicsContext())
using (BufferedGraphics buffer = context.Allocate(e.Graphics, new Rectangle(0, 0, 120, 120)))
{
// Create a bitmap with just a blue line on it
Bitmap bmp = new Bitmap(100, 100, PixelFormat.Format32bppArgb);
using (Graphics g = Graphics.FromImage(bmp))
{
g.DrawLine(Pens.Blue, 0, 0, 100, 100);
}
// Fill a red square
buffer.Graphics.FillRectangle(Brushes.Red, 5, 5, 110, 110);
// Draw the blue-line image over the red square area
buffer.Graphics.DrawImage(bmp, 10, 10);
// Render the buffer to the underlying graphics
buffer.Render(e.Graphics);
}
}
In the result you can clearly see the blue line from the image over the red color in the background buffer (the red background is not overwritten) and there's a black border around the red rectangle where no background pixels where drawn.
Let me preface this with a real life product; You may remember in Elementary school, they had scratch paper which basically consisted of a rainbow-colored sheet of paper with a black film on top. You would take a sharp object and peel away the black film to expose the colored paper.
I am attempting to do the same thing using images in a picture box.
My idea consists of these things:
A textured image.
A black rectangle the size of the picture box.
A circle image.
What I am trying to achieve is to open a program, have an image drawn to a picture box with the black rectangle on top of it. Upon clicking the picture box it uses the circle to invert the alpha of the rectangle where I click using the circle as a reference.
My Problem-
I cannot figure out any way to erase (set the transparency of) a part of the black rectangle where I click.
For the life of me, I do not know of any method to cut a window in an image. It is almost like a reverse crop, where I keep the exterior elements rather than the interior, exposing the textured image below.
Can WinForms not do this? Am I crazy? Should I just give up?
I should mention that I prefer not to have to change alpha on a pixel per pixel basis. It would slow the program down far too much to be used as a pseudo-painter. If that is the only way, however, feel free to show.
Here is an image of what I'm trying to achieve:
This is not really hard:
Set the colored image as a PictureBox's BackgroundImage.
Set a black image as its Image.
And draw into the image using the normal mouse events and a transparent Pen..
We need a point list to use DrawCurve:
List<Point> currentLine = new List<Point>();
We need to prepare and clear the the black layer:
private void ClearSheet()
{
if (pictureBox1.Image != null) pictureBox1.Image.Dispose();
Bitmap bmp = new Bitmap(pictureBox1.ClientSize.Width, pictureBox1.ClientSize.Height);
using (Graphics G = Graphics.FromImage(bmp)) G.Clear(Color.Black);
pictureBox1.Image = bmp;
currentLine.Clear();
}
private void cb_clear_Click(object sender, EventArgs e)
{
ClearSheet();
}
To draw into the Image we need to use an associated Graphics object..:
void drawIntoImage()
{
using (Graphics G = Graphics.FromImage(pictureBox1.Image))
{
// we want the tranparency to copy over the black pixels
G.CompositingMode = System.Drawing.Drawing2D.CompositingMode.SourceCopy;
G.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.AntiAlias;
G.CompositingQuality = System.Drawing.Drawing2D.CompositingQuality.HighQuality;
using (Pen somePen = new Pen(Color.Transparent, penWidth))
{
somePen.MiterLimit = penWidth / 2;
somePen.EndCap = System.Drawing.Drawing2D.LineCap.Round;
somePen.LineJoin = System.Drawing.Drawing2D.LineJoin.Round;
somePen.StartCap = System.Drawing.Drawing2D.LineCap.Round;
if (currentLine.Count > 1)
G.DrawCurve(somePen, currentLine.ToArray());
}
}
// enforce the display:
pictureBox1.Image = pictureBox1.Image;
}
The usual mouse events:
private void pictureBox1_MouseDown(object sender, MouseEventArgs e)
{
currentLine.Add(e.Location);
}
private void pictureBox1_MouseMove(object sender, MouseEventArgs e)
{
if (e.Button == System.Windows.Forms.MouseButtons.Left)
{
currentLine.Add(e.Location);
drawIntoImage();
}
}
private void pictureBox1_MouseUp(object sender, MouseEventArgs e)
{
currentLine.Clear();
}
That's all that's needed. Make sure to keep the PB's SizeMode = Normal or else the pixels won't match..!
Note that there are a few challenges when you want to get soft edges, more painting tools, letting a simple click paint a dot or an undo or other finer details to work. But the basics are not hard at all..
Btw, changing Alpha is not any different from changing the color channels.
As an alternative you may want to play with a TextureBrush:
TextureBrush brush = new TextureBrush(pictureBox1.BackgroundImage);
using (Pen somePen = new Pen(brush) )
{
// basically
// the same drawing code..
}
But I found this to be rather slow.
Update:
Using a png-file as a custom tip is a little harder; the main reason is that the drawing is reversed: We don't want to draw the pixels, we want to clear them. GDI+ doesn't support any such composition modes, so we need to do it in code.
To be fast we use two tricks: LockBits will be as fast as it gets and restricting the area to our custom brush tip will prevent wasting time.
Let's assume you have a file to use and load it into a bitmap:
string stampFile = #"yourStampFile.png";
Bitmap stamp = null;
private void Form1_Load(object sender, EventArgs e)
{
stamp = (Bitmap) Bitmap.FromFile(stampFile);
}
Now we need a new function to draw it into our Image; instead of DrawCurve we need to use DrawImage:
void stampIntoImage(Point pt)
{
Point point = new Point(pt.X - stamp.Width / 2, pt.Y - stamp.Height / 2);
using (Bitmap stamped = new Bitmap(stamp.Width, stamp.Height) )
{
using (Graphics G = Graphics.FromImage(stamped))
{
stamp.SetResolution(stamped.HorizontalResolution, stamped.VerticalResolution);
G.CompositingMode = System.Drawing.Drawing2D.CompositingMode.SourceOver;
G.DrawImage(pictureBox1.Image, 0, 0,
new Rectangle(point, stamped.Size), GraphicsUnit.Pixel);
writeAlpha(stamped, stamp);
}
using (Graphics G = Graphics.FromImage(pictureBox1.Image))
{
G.CompositingMode = System.Drawing.Drawing2D.CompositingMode.SourceCopy;
G.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.AntiAlias;
G.CompositingQuality =
System.Drawing.Drawing2D.CompositingQuality.HighQuality;
G.DrawImage(stamped, point);
}
}
pictureBox1.Image = pictureBox1.Image;
}
A few notes: I found that I hat to do an explicit SetResolution since the stamp file I photoshopped was 72dpi and the default Bitmaps in my program were 120dpi. Watch out for these differences!
I start the Bitmap to be drawn by copying the right part of the current Image.
Then I call a fast routine that applies the alpha of the stamp to it:
void writeAlpha(Bitmap target, Bitmap source)
{
// this method assumes the bitmaps both are 32bpp and have the same size
int Bpp = 4;
var bmpData0 = target.LockBits(
new Rectangle(0, 0, target.Width, target.Height),
ImageLockMode.ReadWrite, target.PixelFormat);
var bmpData1 = source.LockBits(
new Rectangle(0, 0, source.Width, source.Height),
ImageLockMode.ReadOnly, source.PixelFormat);
int len = bmpData0.Height * bmpData0.Stride;
byte[] data0 = new byte[len];
byte[] data1 = new byte[len];
Marshal.Copy(bmpData0.Scan0, data0, 0, len);
Marshal.Copy(bmpData1.Scan0, data1, 0, len);
for (int i = 0; i < len; i += Bpp)
{
int tgtA = data0[i+3]; // opacity
int srcA = 255 - data1[i+3]; // transparency
if (srcA > 0) data0[i + 3] = (byte)(tgtA < srcA ? 0 : tgtA - srcA);
}
Marshal.Copy(data0, 0, bmpData0.Scan0, len);
target.UnlockBits(bmpData0);
source.UnlockBits(bmpData1);
}
I use a simple rule: Reduce target opacity by the source transparency and make sure we don't get negative.. You may want to play around with it.
Now all we need is to adapt the MouseMove; for my tests I have added two RadioButtons to switch between the original round pen and the custom stamp tip:
private void pictureBox1_MouseMove(object sender, MouseEventArgs e)
{
if (e.Button == System.Windows.Forms.MouseButtons.Left)
{
if (rb_pen.Checked)
{
currentLine.Add(e.Location);
drawIntoImage();
}
else if (rb_stamp.Checked) { stampIntoImage(e.Location); };
}
}
I didn't use a fish but you can see the soft edges:
Update 2: Here is a MouseDown that allows for simple clicks:
private void pictureBox1_MouseDown(object sender, MouseEventArgs e)
{
if (rb_pen.Checked) currentLine.Add(e.Location);
else if (rb_stamp.Checked)
{
{ stampIntoImage(e.Location); };
}
}
There is only one picturebox in my form and I want to drawcircle with a method on this picturebox but I cant do that and not working.The method is:
private Bitmap Circle()
{
Bitmap bmp;
Graphics gfx;
SolidBrush firca_dis=new SolidBrush(Color.FromArgb(192,0,192));
bmp = new Bitmap(40, 40);
gfx = Graphics.FromImage(bmp);
gfx.FillRectangle(firca_dis, 0, 0, 40, 40);
return bmp;
}
Picturebox
private void pictureBox2_Paint(object sender, PaintEventArgs e)
{
Graphics gfx= Graphics.FromImage(Circle());
gfx=e.Graphics;
}
You need to decide what you want to do:
Draw into the Image or
draw onto the Control?
Your code is a mix of both, which is why it doesn't work.
Here is how to draw onto the Control:
private void pictureBox1_Paint(object sender, PaintEventArgs e)
{
e.Graphics.DrawEllipse(Pens.Red, new Rectangle(3, 4, 44, 44));
..
}
Here is how to draw into the Image of the PictureBox:
void drawIntoImage()
{
using (Graphics G = Graphics.FromImage(pictureBox1.Image))
{
G.DrawEllipse(Pens.Orange, new Rectangle(13, 14, 44, 44));
..
}
// when done with all drawing you can enforce the display update by calling:
pictureBox1.Refresh();
}
Both ways to draw are persistent. The latter changes to pixels of the Image, the former doesn't.
So if the pixels are drawn into the Image and you zoom, stretch or shift the Image the pixel will go with it. Pixels drawn onto the Top of the PictureBox control won't do that!
Of course for both ways to draw, you can alter all the usual parts like the drawing command, maybe add a FillEllipse before the DrawEllipse, the Pens and Brushes with their Brush type and Colors and the dimensions.
private static void DrawCircle(Graphics gfx)
{
SolidBrush firca_dis = new SolidBrush(Color.FromArgb(192, 0, 192));
Rectangle rec = new Rectangle(0, 0, 40, 40); //Size and location of the Circle
gfx.FillEllipse(firca_dis, rec); //Draw a Circle and fill it
gfx.DrawEllipse(new Pen(firca_dis), rec); //draw a the border of the cicle your choice
}
What Ive done is put an image in picturebox1 background image then drawn a cover image over top of the image. With picturebox1 mouse move if the mouse is down it erases parts of the cover to reveal the bottom image basically like a scratch off ticket. I cant figure out how to judge if MOST of the cover image is erased. This is what I have so far
bmp1 = new Bitmap(coverimage);
tb = new TextureBrush(pictureBox1.BackgroundImage);
private void pictureBox1_Paint(object sender, PaintEventArgs e)
{
base.OnPaint(e);
e.Graphics.PixelOffsetMode = System.Drawing.Drawing2D.PixelOffsetMode.HighQuality;
e.Graphics.InterpolationMode = InterpolationMode.HighQualityBilinear;
e.Graphics.DrawImage(bmp1, 0, 0, 400, 325);
}
private void pictureBox1_MouseMove(object sender, MouseEventArgs e)
{
if (e.Button == MouseButtons.Left)
{
if (!_LastPoint.IsEmpty)
{
using (Graphics g = Graphics.FromImage(bmp1))
using (Pen p = new Pen(tb, 50))
{
p.StartCap = LineCap.Round;
p.EndCap = LineCap.Round;
g.DrawLine(p, _LastPoint, e.Location);
if (!g.Equals (bmp1))
{
MessageBox.Show("done");
}
}
}
_LastPoint = e.Location;
pictureBox1.Refresh();
}
}
the !g.Equals (bmp1) notifies me when the image is altered but I cant find a way to make it only notify me if the image changes a drastic amount. Is there anyway to judge this?
update:::
static int flags = 0;
public static void ImageCompareString(Bitmap firstImage, Bitmap secondImage)
{
MemoryStream ms = new MemoryStream();
firstImage.Save(ms, System.Drawing.Imaging.ImageFormat.Png);
String firstBitmap = Convert.ToBase64String(ms.ToArray());
ms.Position = 0;
secondImage.Save(ms, System.Drawing.Imaging.ImageFormat.Png);
String secondBitmap = Convert.ToBase64String(ms.ToArray());
if (firstBitmap.Equals(secondBitmap))
{
flags = flags + 1;
}
else
{
}
Some how I got the above to work for what I needed by counting the flags and when they were >= 50 allowing the next step and clearing the mask
You could create a 'mask' image to find out the percentage uncovered by the user:
To do so, create an image with a white background on which you'll also draw these lines but with a black pen, then from this hidden image you can easily find the percentage uncovered by counting the number of pixels that aren't white using Bitmap.GetPixel function.
Be careful when comparing colors with Color.Equals (taken from Remarks section) :
To compare colors based solely on their ARGB values, you should use the ToArgb method. This is because the Equals and Equality members determine equivalency using more than just the ARGB value of the colors.