I'm trying to make something similar to paint. I'm trying to figure out how make different brush styles. Like in Paint 3D you get a certain line fills when using the pen tool vs using the paint brush tool.
I have no idea where to even start. I've spent a good portion of the day looking through documentations, and watching YouTube videos. I'm more lost than when I started. The closest thing I came across was line caps, but that's definitely not what I'm looking for.
!!See the UPDATE below!!
Hans' link should point you in the right direction, namely toward TextureBrushes.
To help you further here a few points to observe:
TextureBrush is a brush, not a pen. So you can't follow a path, like the mouse movements to draw along that curve. Instead, you need to find an area to fill with the brush.
This also implies that you need to decide how and when to trigger the drawing; basic options are by time and/or by distance. Usually, the user can set parameters for these often called 'flow' and 'distance'..
Instead of filling a simple shape and drawing many of those, you can keep adding the shapes to a GraphicsPath and fill that path.
To create a TextureBrush you need a pattern file that has transparency. You can either make some or download them from the web where loads of them are around, many for free.
Most are in the Photoshop Brush format 'abr'; if they are not too recent (<=CS5) you can use abrMate to convert them to png files.
You can load a set of brushes to an ImageList, set up for large enough size (max 256x256) and 32bpp to allow alpha.
Most patterns are black with alpha, so if you want color you need to create a colored version of the current brush image (maybe using a ColorMatrix).
You may also want to change its transparency (best also with the ColorMatrix).
And you will want to change the size to the current brush size.
Update
After doing a few tests I have to retract the original assumption that a TextureBrush is a suitable tool for drawing with textured tips.
It is OK for filling areas, but for drawing free-hand style it will not work properly. There are several reasons..:
one is that the TextureBrush will always tile the pattern in some way, flipped or not and this will always look like you are revealing one big underlying pattern instead of piling paint with several strokes.
Another is that finding the area to fill is rather problematic.
Also, tips may or may not be square but unless you fill with a rectangle there will be gaps.
See here for an example of what you don't want at work.
The solution is really simple and much of the above still applies:
What you do is pretty much regular drawing but in the end, you do a DrawImage with the prepared 'brush' pattern.
Regular drawing involves:
A List<List<Point>> curves that hold all the finished mouse paths
A List<Point> curentCurve for the current path
In the Paint event you draw all the curves and, if it has any points, also the current path.
For drawing with a pattern, it is necessary to also know when to draw which pattern version.
If we make sure not to leak them we can cache the brush patterns..:
Bitmap brushPattern = null;
List<Tuple<Bitmap,List<Point>>> curves = new List<Tuple<Bitmap,List<Point>>>();
Tuple<Bitmap, List<Point>> curCurve = null;
This is a simple/simplistic caching method. For better efficiency you could use a Dictionary<string, Bitmap> with a naming scheme that produces a string from the pattern index, size, color, alpha and maybe a rotation angle; this way each pattern would be stored only once.
Here is an example at work:
A few notes:
In the MouseDown we create a new current curve:
curCurve = new Tuple<Bitmap, List<Point>>(brushPattern, new List<Point>());
curCurve.Item2.Add(e.Location);
In the MouseUp I add the current curve to the curves list:
curves.Add(new Tuple<Bitmap, List<Point>>(curCurve.Item1, curCurve.Item2.ToList()));
Since we want to clear the current curve, we need to copy its points list; this is achieved by the ToList() call!
In the MouseMove we simply add a new point to it:
if (e.Button == MouseButtons.Left)
{
curCurve.Item2.Add(e.Location);
panel1.Invalidate();
}
The Paint goes over all curves including the current one:
for (int c = 0; c < curves.Count; c++)
{
e.Graphics.TranslateTransform(-curves[c].Item1.Width / 2, -curves[c].Item1.Height / 2);
foreach (var p in curves[c].Item2)
e.Graphics.DrawImage(curves[c].Item1, p);
e.Graphics.ResetTransform();
}
if (curCurve != null && curCurve.Item2.Count > 0)
{
e.Graphics.TranslateTransform(-curCurve.Item1.Width / 2, -curCurve.Item1.Height / 2);
foreach (var p in curCurve.Item2)
e.Graphics.DrawImage(curCurve.Item1, p);
e.Graphics.ResetTransform();
}
It makes sure the patterns are drawn centered.
The ListView is set to SmallIcons and its SmallImageList points to a smaller copy of the original ImageList.
It is important to make the Panel Doublebuffered! to avoid flicker!
Update: Instead of a Panel, which is a Container control and not really meant to draw onto you can use a Picturebox or a Label (with Autosize=false); both have the DoubleBuffered property turned on out of the box and support drawing better than Panels do.
Btw: The above quick and dirty example has only 200 (uncommented) lines. Adding brush rotation, preview, a stepping distance, a save button and implementing the brushes cache takes it to 300 lines.
Related
I was doing a game project in Windows Forms and really loved how it turned out, except for one thing that was bugging me: the new picturebox's I am adding are "eating" away from the one behind it, showind the background of its parent and not showing the image behind him, as I thought it will. Apparently that's how transparency works in Windows Forms, it copies the colors behind him, basically.
This is how it looks, and I want the animals to be seen fully.
I also tried this from another post here, but it turned out like this.
There might be no solution to this, I have other things in this little game I made. There is another picturebox with other buttons and stuff, that represents the shop. And also you can see in both images that there is a pannel in the bottom section with some details. In that case, I would leave it as it is and maybe try another time to move it to WPF.
=================== EDIT ===================
The accepted answer helped me switch from a game with overlaying PictureBoxes to a game where I "paint" each frame of the game on the background. Check that answer's comment for more details about this :) This is how it turned out.
This is specifically for my code, where I have a static Resources class. Yours could look a lot cleaner, maybe you have this Render function where you have every other rectangle and image. I hope this helps everyone that visits this page :)
// ================ SOLUTION ================
public static void Render()
{
//draw the background again. This is efficient enough, maybe because the pixels that did not changed won't be redrawn
grp.DrawImage(Resources.gameBackground, 0, 0);
//draw the squirrel image on the position and length of the "squirrel" Rectangle
grp.DrawImage(Resources.currentSquirrelImage, Resources.squirrel.X, Resources.squirrel.Y, Resources.squirrel.Width, Resources.squirrel.Height);
//after that, draw each projectile (acorns, wallnuts) the same way
foreach (Projectile projectile in Resources.projectiles)
{
grp.DrawImage(projectile.image, projectile.rect.X, projectile.rect.Y, projectile.rect.Width, projectile.rect.Height);
}
//then draw each animal
foreach (Enemy animal in Resources.enemies)
{
grp.DrawImage(animal.image, animal.rect.X, animal.rect.Y, animal.rect.Width, animal.rect.Height);
}
//and finally, the image that shows where the squirrel is shooting
grp.DrawImage(Resources.selectionImge, Resources.selection.X, Resources.selection.Y, Resources.Selection.Width, Resources.Selection.Height);
//update the image of the game picturebox
form.TheGame.Image = bmp;
}
As you have noticed .net control transparency is not real transparency, it copies it's parent background, so if you have other sibiling controls the one with the higher Z index will occlude the others.
If you want to create a game avoid the usage of picture boxes, there are many options: use a game engine like Unity or roll your own.
Something easy to do is to create a Bitmap, render your game in it and then present it in your form, but beware, that can be slow.
EDIT: As you requested here is an example on how to use the Intersect function of the Rectangle struct to determine which parts of two rectangles overlap.
Rectangle R1 = new Rectangle (0,0,32,32);
Rectangle R2 = new Rectangle (16,16,32,32);
//To test if a rectangle intersects with another...
bool intersects = R1.IntersectsWith(R2); //If does not intersect then there's nothing to update
//To determine the area that two rectangles intersect
Rectangle intersection = Rectangle.Intersect(R1, R2); //In this example that would return a rectangle with (16,16,16,16).
i am refactoring some code that used System.Drawing classes and GDi in order to draw a complex map image. The image consists of objects like lines and polygons.
In order to clip the objects, we used a implementation of a clipping algorithm, that first checked wether the object ist within the borders of a bitmap or not. If it was intersecting, the object got clipped.
We now want to draw the same map image with WPF methods. I am using drawinggroups to create the geometry drawings. DrawingGroups offer the possibility of adding a ClipGeometry, like that:
TileSize = new RectangleGeometry(new Rect(tileOrigin, new Size(Width, Height)));
mapDrawing.ClipGeometry = TileSize;
The result looks good, and i can "feel" no difference in performance. Problem is, i have a hard time to measure that. I can definetly say how long it took to clip the objects with our algorithem, but i cant measure what time is needed for the clipping with the ClipGeometry. I guess the clipping is done when the UI starts rendering the drawings.
My questions:
How could i measure the time needed for clipping with WPF ClipGeometry?
Does anyone know how exactly ClipGeometry does the clipping?
Thanx for your input!
I'm making a graphics editor for my class project and i want to make so that when, for example a user loads a picture in to the editor or or draw something in the PictureBox, all the alpha parts are shown the chessboard like background.
My idea is that when I create a PictureBox with transparent background set, I create another one behind it, set its BackColor to white and add grey images 50x50, alternately horizontally and vertically. Is that a good approach to the problem? If, not do You have any suggestions?
In Photoshop, for example, I create image 1600x1600. When I zoom to a certain level, it shrinks the boxes and adds more of them to fill the image. If You'we used Photoshop of similar program you know what I mean. Now, how would I go about achieving the same effect?
Creating a Photoshop-like program is a fun project.
There will be many challenges along your way and it is well worth thinking ahead a little..
Here is a short and incomplete list of things to keep in mind:
Draw- and paint actions
Undo, redo, edit
Multiple layers
Zooming and scrolling
Saving and printing
So getting a checkerboard background is only the start of a long journey..
Using a PictureBox as the base canvas is a very good choice, as its several layers will help. Here is a piece of code that will provide you with a flexible checkerboard background, that will keep its size even when you scale the real graphics:
void setBackGround(PictureBox pb, int size, Color col)
{
if (size == 0 && pb.BackgroundImage != null)
{
pb.BackgroundImage.Dispose();
pb.BackgroundImage = null;
return;
}
Bitmap bmp = new Bitmap(size * 2, size * 2);
using (SolidBrush brush = new SolidBrush(col))
using (Graphics G = Graphics.FromImage(bmp) )
{
G.FillRectangle(brush, 0,0,size, size);
G.FillRectangle(brush, size,size, size, size);
}
pb.BackgroundImage = bmp;
pb.BackgroundImageLayout = ImageLayout.Tile;
}
Load an Image for testing and this is what you'll get, left normal, right zoomed in:
Yes, for saving this background should be removed; as you can see in the code, passing in a size = 0 will do that.
What next? Let me give you a few hints on how to approach the various tasks from above:
Scrolling: Picturebox can't scroll. Instead place it in a Panel with AutoScroll = true and make it as large as needed.
Zooming: Playing with its Size and the SizeMode will let you zoom in and out the Image without problems. The BackgroundImage will stay unscaled, just as it does in Photoshop. You will have to add some more code however to zoom in on the graphics you draw on top of the PB or on the layers. The key here is scaling the Graphics object using a Graphics.MultiplyTransform(Matrix).
Layers: Layers are imo the single most useful feature in PhotoShop (and other quality programs). They can be achieved by nesting transparent drawing canvases. Panels can be used, I prefer Labels. If each is sitting inside the one below it and the one at the bottom has the PB as its Parent, all their contents will be shown combined.
Don't use the Label directly but create a subclass to hold additional data and know-how!
Changing their order is not very hard, just keep the nested structure in mind and intact!
Hiding a layer is done by setting a flag and checking that flag in the painting actions
Other data can include a Name, Opacity, maybe an overlay color..
The layers should also be shown in a Layers Palette, best by creating a thumbnail and inserting a layer userobject in a FlowLayoutPanel
Draw Actions: These are always the key to any drawing in WinForms. When using the mouse to draw, each such activity creates an object of a DrawAction class you need to design, which holds all info needed to do the actual drawing, like:
Type (Rectangle, filledRectangle, Line, FreeHandLine (a series of Points), Text, etc.etc..)
Colors
Points
Widths
Text
The layer to draw on
maybe even a rotation
Along with the LayerCanvas class the DrawAction class will be the most important class in the project, so getting its design right is worth some work!
Only the topmost layer will receive the mouse events. So you need to keep track which layer is the active one and add the action to its actions list. Of course the active layer must also be indicated in the Layers Palette.
Since all drawing is stored in List(s), implementing a unlimited undo and redo is simple. To allow for effective drawing and undo, maybe a common action list and an individual list for each layer is the best design..
Undo and Redo are just matter of removing the last list element and pushing it onto a redo-stack.
Editing actions is also possible, including changing the parameters, moving them up or down the actions list or removing one from the middle of the list. It help to show an Actions Palette, like F9 in PhotoShop.
To flatten two or more layers together all you need is to combine their action lists.
To flatten all layers into the Image you only need to draw them not onto their canvas but into the Image. For the difference of drawing onto a control or into a Bitmap see here! Here we have the PictureBox.Image as the second level of a PB's structure above the Background.Image. (The 3rd is the Control surface, but with the multiple layers on top we don't really need it..)
Saving can be done by either by Image.Save() after flattening all Layers or after you have switched off the BackgroundImage by telling the PB to draw itself into a Bitmap ( control.DrawToBitmap() ) which you can then save.
Have fun!
I am drawing lines on a background image in a c# panel. The panel is anchored to the form so as the form resizes the panel resizes. The background image is set to be stretched so all you see as you resize the form is the background image.
My initial problem:
The lines drawn on the panel (via the OnPaint event) stay where they were originally drawn as the image resizes.
My current solution:
Record the location of the line and redraw it on a new bitmap by scaling the X and Y coordinates (works fine).
My new problem:
As you continually resize the window and draw lines you can't calculate the scaling factor from any point in time and apply it to all lines since the lines were originall drawn in different size images.
The two options I think I have:
After I redraw the line go through my array of lines and update the coordinate information so it now matches the current scale.
Or
In addition to storing the coordinate information of the line also store the size information of the panel at the time it was drawn so I can always calculate the scale for each line based on when it was drawn and the new panel size.
What I'm hoping for:
If you have thoughts on either of the two approaches that would be greatly appreciated....Even better would be to point me in the direction of a far better method to do this (I am fairly new to graphics processing in c#).
Can't write a comment, much as I want to. You do have a few options:
Draw your lines directly on the original Bitmap. This might not be an option for you, depending on the task.
Do it as you're doing now, keeping track of the lines' coordinates, updating them on resize, and redrawing them on Paint - if you use this, you'll be able to move and delete them, too,
Or do it by introducing a "scale factor" (float) which you update on every resize, and in your Paint event handler you draw everything using that scale factor. As you create a line, you calculate its coordinates using the scale factor BACK TO an unified coordinate system (scale factor 1) and then you don't have to modify your coordinates at all. This might be easy to debug due to that unified coordinate system. This is what I'd recommend, but it again depends on your task.
Draw to a full transparent Bitmap of the same size as your original image, use a scale factor like in the previous option. On creating a line, calculate its coordinates in the unified coordinate system, draw it on the Bitmap, and then on every Paint, draw the entire Bitmap over your original one. This, again, might not be an option if you need to delete or move your lines, or if you're tight on memory, or you don't want your lines to be blurred when you scale up, but somehow many ppl like this because it's like a "layer in Photoshop". :)
I have polygons of various shapes and sizes. They have a solid fill and currently a solid border.
I would like to give the polygons a gradient on their edge to soften them.
So far I've tried using a Pen with a LinearGradientBrush and whilst the effect it produces is very interesting it's most definitely not what I want ;)
I've looked through the System.Drawing.Drawing2D namespace but there didn't seem to be any other classes that would be applicable for this purpose.
I've had a search around and the articles that I can find are mostly about creating borders for rectangles, which are mush easier, or are irrelevant.
So to summarize, does anyone have a way of drawing a gradient border in on a polygon using GDI+?
Perhaps a screen shot of what your previous attempt produced and a mock up of what you would like would help?
Though I suspect the issue you're running into is that the direction and offset of the gradient is consistent throughout the entire shape and does not change with the orientation of the lines of the polygon.
Have you taken a look instead at the PathGradientBrush? (Examples) If you can't achieve the effect using it with a Pen for the stroke of the shape, perhaps you could do it with two "fills" with the first (the border) being slightly larger than the second (the interior).
I think I have done exactly what you're asking for, but in my case I have used it for creating soft shadows on text.
I do the following:
Draw the text (in your case:
polygon) to a Bitmap
Apply a
softening filter on the alpha
channel only
Iterate step 2 as
many times needed to get the desired
gradient width
Finally draw the
result onto the resulting
bitmap/screen