Im trying to make a photoshop like application for my college project in c#
So far I,ve created a custom panel called canvas and overloaded the paint method to draw the canvasBuffer.
The project is called paint sharp.
I have an Class PaintSharpFile that stores the various layers of the image.
On the Canvas control, I draw the checked Transparent Background and then the layers in the paint sharp file on to the canvasBuffer. I finally paint this buffer(Double Buffering).
Now, I am writing the code for the brush tool.
I record the previous and the current point and then draw a series of circle between those two points using Bresenham's line algorithm on the canvasBuffer itself. This seems to work fast and fine.
Now since the brush tool will be working on an active layer selected, I tried drawing the points to the buffer of the layer. Then drew all the layer's buffer canvasBuffer. Doing this makes the drawing very slow.
Here's the code
public void PSF_Painted(PSF_PaintEvent e)
{
Graphics g = null;
Bitmap layerBuffer = psf.Layers[0].LayerBuffer;//Get selected layer here
g = Graphics.FromImage(layerBuffer);
Brush blackBrush = new SolidBrush(Color.Black);
Pen blackPen = new Pen(blackBrush);
blackPen.Width = 2;
List<Point> recordedPoints = e.RecordedPoints;
Point currentPoint = new Point(0,0);
Point previousPoint = new Point(0, 0); ;
if(recordedPoints.Count > 0)
{
currentPoint = recordedPoints[recordedPoints.Count - 1];
if(recordedPoints.Count == 1)
{
previousPoint = recordedPoints[0];
}
else
{
previousPoint = recordedPoints[recordedPoints.Count - 2];
}
}
if (e.PaintEventType == PSF_PaintEvent.Painting)
{
List<Point> points = Global.GetPointsOnLine(previousPoint.X, previousPoint.Y, currentPoint.X, currentPoint.Y);
for (int i = 0; i < points.Count ; i++)
{
g.FillEllipse(blackBrush, new Rectangle(points[i].X, points[i].Y, (int)blackPen.Width, (int)blackPen.Width));
}
}
Global.drawToBuffer(canvasBuffer, layerBuffer);//Replaced with redraw the full picture from the List of layer to the canvasBuffer
g.Dispose();
this.Invalidate();
}
And here's my onPaint Code
protected override void OnPaint(PaintEventArgs e)
{
if (canvasBuffer != null)
{
Graphics g = e.Graphics;
g.DrawImage(canvasBuffer, e.ClipRectangle, e.ClipRectangle, GraphicsUnit.Pixel);
g.Dispose();
}
}
Here is the drawing of a picture to the buffer
public static void drawToBuffer(Bitmap buffer, Bitmap image)
{
if (image != null && buffer != null)
{
Graphics g = Graphics.FromImage(buffer);
g.DrawImage(image, new Rectangle(0, 0, buffer.Width, buffer.Height));
g.Dispose();
}
}
Please tell me how do I stop the paint from lagging.
I have tried making several canvases each with a layer image. But since it is not double buffered, it causes flickering.
The main thing that jumps out at me is that you're copying the entire layer on every update. Try limiting the copying to just the affected areas.
Unrelated to performance, you're calling Dispose directly on Graphics objects but then not disposing of the Brush and Pen objects you create. What's wrong with using blocks?
Related
private void Side_pictureBox_Paint(object sender, PaintEventArgs e)
{
if (doubleclicked == true)
{
Side_pictureBox.Refresh();
for (int numbering_for_digram = 1; numbering_for_digram <= No_of_circle; numbering_for_digram++)
{
//MessageBox.Show(numbering_for_digram.ToString());
String drawString = numbering_for_digram.ToString();
// Create font and brush.
Font drawFont = new Font("Calibri (Body)", 20);
SolidBrush drawBrush = new SolidBrush(Color.Blue);
// Create point for upper-left corner of drawing.
//float x = 0;
//float y = 0;
doubleclicked = false;
// Draw string to screen.
e.Graphics.DrawString(drawString, drawFont, drawBrush, lastPoint);
Side_pictureBox.Update();
//MessageBox.Show("passed graphics draw");
}
}
}
}
private void Side_pictureBox_MouseMove(object sender, MouseEventArgs e)
{
if (isMouseDown == true && Edit_Variables.add_remark_now==false)//check to see if the mouse button is down
{
if (lastPoint != null)//if our last point is not null, which in this case we have assigned above
{
if (Side_pictureBox.Image == null)//if no available bitmap exists on the picturebox to draw on
{
//create a new bitmap
Bitmap bmp = new Bitmap(Side_pictureBox.Width, Side_pictureBox.Height);
Side_pictureBox.Image = bmp; //assign the picturebox.Image property to the bitmap created
}
using (Graphics g = Graphics.FromImage(Side_pictureBox.Image))
{//we need to create a Graphics object to draw on the picture box, its our main tool
//when making a Pen object, you can just give it color only or give it color and pen size
g.DrawLine(new Pen(Color.DarkRed, 2), lastPoint, e.Location);
g.SmoothingMode = SmoothingMode.AntiAlias;
//this is to give the drawing a more smoother, less sharper look
}
Side_pictureBox.Invalidate();//refreshes the picturebox
lastPoint = e.Location;//keep assigning the lastPoint to the current mouse position
}
}
This is how the whole process works, firstly I will draw using a "Pen" on an image. This drawing process starts with mousedown and ends with mouseup event. Once mouseup is detected, I am to required to click on a point within the image and it suppose to drawstring(draws "1" on the image) on the image itself. For the first drawstring event it turns out fine. However, once I trigger the mousedown event, it will remove the previous drawstring("1"). Can someone help how can i prevent the "previous" drawstring to be remove? Thank you all alot!
Don't draw directly in the paint box. Draw in the Image of the PaintBox instead. And do this at the end of the your MouseMove event, before calling Side_pictureBox.Invalidate();
Update:
Updated code added.
However for some reason it still doesn't work properly. If the coordinates are Bitmap coordinates, what could be the reason? The first code sample I put here doesn't work properly and the second gives me an OutOfMemoryException.
I've ran into a problem trying to crop an image between two points. In my project I have a pictureBox (named AP), and the general idea is that the user clicks on two points and the program crops the image between these two corners. I've tried two methods, one with Bitmap.Crop and the other one with Graphics.DrawImage, but both seemed to fail for the same reason and didn't work at all (cropped a much smaller portion of the image).
Code:
private void AP_Click(object sender, EventArgs e)
{
// Setting the corners
else if (mark_shape == 0)
{
var mouseEventArgs = e as MouseEventArgs;
if (picture_corners_set == 0)
{
northEast = AP.PointToClient(new Point(mouseEventArgs.X, mouseEventArgs.Y));
picture_corners_set = 1;
}
else if (picture_corners_set == 1)
{
southWest = AP.PointToClient(new Point(mouseEventArgs.X, mouseEventArgs.Y));
Rectangle imageRectangle = new Rectangle(southWest.X, northEast.Y, (northEast.X - southWest.X), (southWest.Y - northEast.Y));
var bmp = new Bitmap(imageRectangle.Width, imageRectangle.Height);
using (var gr = Graphics.FromImage(bmp))
{
gr.DrawImage(AP.Image, 0, 0, imageRectangle, GraphicsUnit.Pixel);
}
AP.Image = bmp;
enableAllButtons();
}
}
}
Since your cropped Bitmap image size is the same as the width/height of the user selection, I'm guessing you want that cropped image to be in the top/left of the new Bitmap and fill it. As it is, you're telling the DrawImage() method to draw that portion of the Bitmap in the same location, albeit in a smaller size Bitmap.
The correct way to do this is to draw the source rectangle image at (0, 0):
private Point pt1, pt2;
private void AP_Click(object sender, EventArgs e)
{
// ... obviously other code here ...
else if (mark_shape == 0) // Setting the corners
{
Point pt = AP.PointToClient(Cursor.Position);
if (picture_corners_set == 0)
{
pt1 = new Point(pt.X, pt.Y);
picture_corners_set = 1;
}
else if (picture_corners_set == 1)
{
pt2 = new Point(pt.X, pt.Y);
picture_corners_set = 0;
Rectangle imageRectangle = new Rectangle(new Point(Math.Min(pt1.X, pt2.X), Math.Min(pt1.Y, pt2.Y)), new Size(Math.Abs(pt2.X - pt1.X) + 1, Math.Abs(pt2.Y - pt1.Y) + 1));
var bmp = new Bitmap(imageRectangle.Width, imageRectangle.Height);
using (var gr = Graphics.FromImage(bmp))
{
gr.DrawImage(AP.Image, 0, 0, imageRectangle, GraphicsUnit.Pixel);
}
AP.Image = bmp;
enableAllButtons();
}
}
}
There are several other overloads you could use to do this, but the one above makes it pretty clear that imageRectangle is being drawn at (0, 0).
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); };
}
}
I'm doing some draws that are repetitive, and each of them gives lot of work.
What i need to do is rotate the drawing half-way its definition, something like this:
using (Graphics g = Graphics.FromImage(bmp))
{
//define area do pictureBox e preenche a branco
Brush brush = new SolidBrush(Color.White);
Rectangle area = new Rectangle(0, 0, 520, 520);
g.FillRectangle(brush, area);
//rotate
g.RotateTransform(some angle, some reference point)
//draw some more lines on the top of the rotated previous ones.
}
I tried using g.RotateTransform(90) as there is that function in Winforms, but it didn't change anything. Why??
Any tip?
Edit: if it helps, i only need to rotate fixed angles, 180ยบ
RotateTransform certainly does change the subsequent drawing.
Note that you usually need a TranslateTransform before to set the rotation point. But it 'it didn't change anything' is certainly wrong. Try again! And yes you can rotate (or scale or move) at any point and move/turn it back or completely reset the Graphics object.
And yes, learning about Matrix and MultiplyTransform is also very helpful..
But: You need to understand the Graphics object does not contain any graphic, a common misconception! It is the tool which does the drawing on a Bitmap, most often the surface of a Control. So the rotation will happen but only for the things you draw after:
private void panel1_Paint(object sender, PaintEventArgs e)
{
Rectangle rect = new Rectangle(25, 25, 25, 25);
e.Graphics.TranslateTransform(25, 25);
e.Graphics.FillRectangle(Brushes.Red, rect);
for (int i = 0; i < 15; i++)
{
rect.Inflate(2, 2);
e.Graphics.TranslateTransform(5, 2);
e.Graphics.RotateTransform(2.5f);
e.Graphics.DrawRectangle(Pens.Blue, rect);
}
}
Try this:
use these references:
using System;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Windows.Forms;
I made windows application and put on form1 picturebox then this is the code in form_load:
//Load an image in from a file
Bitmap pImage = new Bitmap(Environment.CurrentDirectory + #"\Image.bmp", true);
//Set our picture box to that image
pictureBox1.Image = (Bitmap)pImage.Clone();
//Store our old image so we can delete it
Image oldImage = pictureBox1.Image;
//Pass in our original image and return a new image rotated 35 degrees right
pictureBox1.Image = RotateImage(pImage, 270);
if (oldImage != null)
{
oldImage.Dispose();
}
Then make static function with parameters of image and rotation angle return the rotated image and call it from form_load as mentioned before :
if (image == null)
{
throw new ArgumentNullException("image");
}
else
{
//create a new empty bitmap to hold rotated image
Bitmap rotatedBmp = new Bitmap(image.Width, image.Height);
rotatedBmp.SetResolution(image.HorizontalResolution, image.VerticalResolution);
//make a graphics object from the empty bitmap
Graphics g = Graphics.FromImage(rotatedBmp);
//move rotation point to center of image
g.TranslateTransform((float)image.Width / 2, (float)image.Height / 2);
//rotate
g.RotateTransform(angle);
//move image back
g.TranslateTransform(-(float)image.Width / 2, -(float)image.Height / 2);
//draw passed in image onto graphics object
g.DrawImage(image, new PointF(0, 0));
return rotatedBmp;
}
You can also use directly the code in form_load by using ready RotateFlipType (Enumeration type) but this with fixed angles like 90,270,.... but the previous method you can use any integer values to rotate the image :
private void Form1_Load(object sender, EventArgs e)
{
//Load an image in from a file
Bitmap pImage = new Bitmap(Environment.CurrentDirectory + #"\Image.bmp", true);
pImage.RotateFlip(RotateFlipType.Rotate90FlipXY);
pictureBox1.Image = pImage;
}
I have an image .I want to crop 10 px from left and 10px from right of the image.I used the below code to do so
string oldImagePath="D:\\RD\\dotnet\\Images\\photo1.jpg";
Bitmap myOriginalImage = (Bitmap)Bitmap.FromFile(oldImagePath);
int newWidth = myOriginalImage.Width;
int newHeight = myOriginalImage.Height;
Rectangle cropArea = new Rectangle(10,0, newWidth-10, newHeight);
Bitmap target = new Bitmap(cropArea.Width, cropArea.Height);
using (Graphics g = Graphics.FromImage(target))
{
g.DrawImage(myOriginalImage,cropArea);
}
target.Save("D:\\RD\\dotnet\\Images\\test.jpg");
But this is not giving me the results which i expect. This outputs an image which has 10 px cropped from the right and a resized image.Instead of cropiing it is resizing the width i think.So the image is shrinked(by width). Can any one correct me ? Thanks in advance
Your new width should be reduced by twice the crop margin, since you'll be chopping off that amount from both sides.
Next, when drawing the image into the new one, draw it at a negative offset. This causes the area that you aren't interested in to be clipped off.
int cropX = 10;
Bitmap target = new Bitmap(myOriginalImage.Width - 2*cropX, myOriginalImage.Height);
using (Graphics g = Graphics.FromImage(target))
{
g.DrawImage(myOriginalImage, -cropX, 0);
}
My guess is this line
Rectangle cropArea = new Rectangle(10,0, newWidth-10, newHeight);
should be
Rectangle cropArea = new Rectangle(10,0, newWidth-20, newHeight);
Set the width of the new rectangle to be 20 less than the original - 10 for each side.
Some indication what result it is giving you would be helpful in confirming this.
Corey Ross is correct. Alternately, you can translate along the negative x axis and render at 0.0, 0.0. Should produce identical results.
using (Graphics g = Graphics.FromImage(target))
{
g.TranslateTransform(-cropX, 0.0f);
g.DrawImage(myOriginalImage, 0.0f, 0.0f);
}
You need to use the overload that has you specify both Destination Rectangle, and Source Rectangle.
Below is an interactive form of this using a picture box on a form. It allows you to drag the image around. I suggest making the picture box 100 x 100 and have a much larger image such as a full screen window you've captured with alt-prtscr.
class Form1 : Form
{
// ...
Image i = new Bitmap(#"C:\Users\jasond\Pictures\foo.bmp");
Point lastLocation = Point.Empty;
Size delta = Size.Empty;
Point drawLocation = Point.Empty;
bool dragging = false;
private void pictureBox1_MouseMove(object sender, MouseEventArgs e)
{
if (e.Button == MouseButtons.Left)
{
if (!dragging)
{
lastLocation = e.Location;
dragging = true;
}
delta = new Size(lastLocation.X - e.Location.X, lastLocation.Y - e.Location.Y);
lastLocation = e.Location;
if (!delta.IsEmpty)
{
drawLocation.X += delta.Width;
drawLocation.Y += delta.Height;
pictureBox1.Invalidate();
}
}
else
{
dragging = false;
}
}
private void pictureBox1_Paint(object sender, PaintEventArgs e)
{
Rectangle source = new Rectangle(drawLocation,pictureBox1.ClientRectangle.Size);
e.Graphics.DrawImage(i,pictureBox1.ClientRectangle,source, GraphicsUnit.Pixel);
}
//...
Okay, I totally fail at explaining this, but hang on:
The DrawImage function requires the location of the image, as well as it's position. You need a second position for cropping as how the old relates to the new, not vice versa.
That was entirely incomprehensible, but here is the code.
g.DrawImage(myOriginalImage, -cropArea.X, -cropArea.Y);
I hope that explains it more then I did.