Reading pixels set using Graphics object - c#

I know several methods for reading Bitmap pixels or to get a Graphic object from an image, but what I am trying to understand is how to know what pixels have been drawn by the user by means of a Graphics object. Example: the user draw a line (but it could be any possible shape) using something like:
surface.DrawLine(aPen, X0, Y0, X1, Y1);
I need to know what pixels have been set by the user to perform some processing. This could be done quite easily for simple shapes using math (i.e. X = X0 + (X1-X0)*t) , but it seems to me possibly unefficient (specially for complex shapes). A solution I would like is to read a Bitmap looking for the pixels that have been set, but I do not know methods for getting a Bitmap image (or whatever relevant data structure) to work on from a Graphics object. Because this is an obvious need for any program allowing to the user do draw, I am for sure missing some points. Someone has a hint about this?
P.S. I am using Graphics object over a 8Bpp indexed Bitmap in a Windows Form and I need all the pixel coordinates and, possibly, the pixel values (they could be deduced from pixel coordinates, I guess)
Proposed solution
The best solution I can figure out after the contributions in this post is something similar to this (being sourceImage the image I want to draw on and surface the picturebox control where sourceImage is rendered):
private void DrawOverlay()
{
using (var tmpImg = new Bitmap (SourceImage.Width,SourceImage.Height, PixelFormat .Format32bppArgb))
{
var g = Graphics.FromImage(tmpImg);
g.DrawLine(pen1, Startx, Starty, Endx, Endy);
tmpImg.MakeTransparent(Color.FromArgb(0, 0, 0));
surface.DrawImage(tmpImg, new Point (0, 0));
// process here the tmpImg pixels drawn by the user
}
}
If someone has better answer, please, if you like, let us know; otherwise I'll close the post answering to my own question as above.

I suggest making a copy of the image before creating the Graphics context. After you have processed all paintings with the Graphics context, you can compare the image with the first one (backup) using XOR (see this example). The resulting contains only those pixels that have changed.
backupImage XOR drawnImage = changes
You can also use OpenCV wrapped by AForge.NET and it's Difference and Substract classes. They do exactly what you want.

As far as I know the proposed solution draft in the question after the feedback received seems to be the an acceptable solution to have an easy access to the pixels drawn.

Related

Creating different brush patterns in c#

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.

How can I Reset Bitmap Pixels in SKBitmap SKiaSharp

I am trying to clear a bitmap in SkiaSharp in Xamarin, the bitmap is drawn by a SkCanvas,
new SKCanvas(SkBitmap bitmap)
but at times i need to clear the bitmap data.
SkCanvas.Clear()
tries to fill the bitmap with SKColor(0,0,0,0) pixels, but premul with the existing pixels it doesn't affect it.
SKBitmap.Reset()
doesnt work because it resets the object completely including the size.
Any idea how can i achieve this in an efficient way, not by going through all the pixels?
If you are trying to clear the pixel data, you WILL have to clear each pixel. There is no way around this.
To clear the bitmap/canvas, you should use SKCanvas.Clear(), but remember, you can pass in a color. So if you want the pixel data to be all zeroes, you can just SKCanvas.Clear(0).
I think this is what you are asking, if not I may need more info.
EDIT
Another thing that just occurred is that there is a SKBitmap.Erase(SKColor). This may be better as there is no need for a canvas.

Clipping a graphic in memory before drawing on form

I'm trying to use the following code to copy a portion of the screen to a new location on my Windows form.
private void Form1_Paint(object sender, PaintEventArgs e)
{
var srcPoint = new Point(0,0);
var dstPoint = new Point(Screen.PrimaryScreen.Bounds.Width/2, Screen.PrimaryScreen.Bounds.Height/2);
var copySize = new Size(100, 100);
e.Graphics.CopyFromScreen(srcPoint, dstPoint, copySize, CopyPixelOperation.SourceCopy);
}
The CopyFromScreen function appears to ignore any clips set before it.
e.SetClip(new Rectangle(srcPoint.X, srcPoint.Y, 20, 20));
Am I doing something wrong or is this just the wrong approach.
For context: I'm trying to mitigate a UI widescreen game issue by copying the HUD at the edges and to be centered closer to the middle.
I am aware of FlawlessWidescreen, but it doesn't support many less popular games. I suppose poking around in memory (what flawless does) could also work but is almost always against TOS.
Edit: final goal is to copy some arbitrary path as the shape rather than a simple rectangle (I was hoping from an image mask).
Edit #2:
So I have an irregular shape being drawn every 100ms. It turns out it just bogs the game down until I slow it down to every 500ms. But still the game isn't smooth. Is this operation of copying and drawing an image just going to be too heavy of an operation in GDI+? I was thinking it was simple enough to not bog anything down.
Thoughts before I mark the answer as accepted?
I guess it is indeed the wrong approach.
The ClippingRegion is only used for clipping the DrawXXX and FillXXX commands, including DrawImage (!).
The CopyFromScreen however will use the given Points and Size and not clip the source.
For a Rectangle region this is no problem since you can achieve the same result by choosing the right Point and Size values.
But once you aim at using more interesting clipping regions you will have to use an intermediate Bitmap onto which you copy from the screen and from which you can then use DrawImage into the clipped region.
For this you can create more or less complicated GraphicsPaths.
Here is a code example:
After putting the cliping coordinates into a Rectangleor GraphicsPath clip you can write something like this:
e.Graphics.SetClip(clip);
using (Bitmap bitmap = new Bitmap(ClientSize.Width, ClientSize.Height))
{
using (Graphics G = Graphics.FromImage(bitmap))
G.CopyFromScreen(dstPoint, srcPoint,
copySize, CopyPixelOperation.SourceCopy);
e.Graphics.DrawImage(bitmap, 0, 0);
}
Shouldn't it be
e.Graphics.Clip = myRegion;

Why does System.Drawing.Graphics blank the RGB channels when Alpha==0?

This has become a serious blocker for a program I'm working on to manipulate images that have Alpha channels.
Many of the images I have contain color information where an Alpha channel is completely transparent, and yet as soon as I try to load them into System.Drawing.Graphics, it changes anything with of Alpha of 0, into Black with an Alpha of 0.
Here is a basic sample of the issue.
I have looked around trying to find a reason, answer, or workaround, but I haven't found anything that even alludes to this issue.
Any help would be appreciated at this point.
var myTestTransparentColor = Color.FromArgb(0, 255, 128, 64);
var image = new Bitmap(135, 135, PixelFormat.Format32bppArgb);
using (var g = Graphics.FromImage(image))
{
g.Clear(myTestTransparentColor);
}
var color = image.GetPixel(0, 0);
Debug.Assert(color == myTestTransparentColor, "channels must match original");
EDIT:
After further testing I don't really see a way ahead by using System.Drawing.Graphics, so my only solution which is not really an answer, is to avoid System.Drawing.Graphics entirely. Looking through my code, it looks like I can avoid it.
Its just after years of using System.Drawing.Graphics for drawing shapes, planting text over images, I find it irritating for System.Drawing.Graphics to have a significant drawback like this.
I still would like to know if I can use System.Drawing.Graphics and keep my ARGB intact, but I guess I can live without it for now.
I think Vincent Povirk has answered my question appropriately here: Drawing PixelFormat32bppPARGB images with GDI+ uses conventional formula instead of premultiplied one
"The format of your foreground image doesn't matter (given that it has alpha) because you're setting it to a Gdiplus::Color. Color values are defined as non-premultiplied, so gdiplus multiplies the components by the alpha value when it clears the foreground image. The alternative would be for Color values to have different meaning depending on the format of the render target, and that way lies madness."
"If you really want this level of control over the rendering, you'll have to lock the bitmap bits and do it yourself."
So, I am doing it myself.

Change background colour of an anti-aliased image using C# with anti-aliasing

I have an image where I need to change the background colour (E.g. changing the background of the example image below to blue).
However, the image is anti-aliased so I cannot simply do a replace of the background colour with a different colour.
One way I have tried is creating a second image that is just the background and changing the colour of that and merging the two images into one, however this does not work as the border between the two images is fuzzy.
Is there any way to do this, or some other way to achieve this that I have no considered?
Example image
Just using GDI+
Image image = Image.FromFile("cloud.png");
Bitmap bmp = new Bitmap(image.Width, image.Height);
using (Graphics g = Graphics.FromImage(bmp)) {
g.Clear(Color.SkyBlue);
g.InterpolationMode = InterpolationMode.NearestNeighbor;
g.PixelOffsetMode = PixelOffsetMode.None;
g.DrawImage(image, Point.Empty);
}
resulted in:
Abstractly
Each pixel in your image is a (R, G, B) vector, where each component is in the range [0, 1]. You want a transform, T, that will convert all of the pixels in your image to a new (R', G', B') under the following constraints:
black should stay black
T(0, 0, 0) = (0, 0, 0)
white should become your chosen color C*
T(1, 1, 1) = C*
A straightforward way to do this is to choose the following transform T:
T(c) = C* .* c (where .* denotes element-wise multiplication)
This is just standard image multiplication.
Concretely
If you're not worried about performance, you can use the (very slow) methods GetPixel and SetPixel on your Bitmap to apply this transform for each pixel in it. If it's not clear how to do this, just say so in a comment and I'll add a detailed explanation for that part.
Comparison
Compare this to the method presented by LarsTech. The method presented here is on the top; the method presented by LarsTech is on the bottom. Notice the undesirable edge effects on the bottom icon (white haze on the edges).
And here is the image difference of the two:
Afterthought
If your source image has a transparent (i.e. transparent-white) background and black foreground (as in your example), then you can simply make your transform T(a, r, g, b) = (a, 0, 0, 0) then draw your image on top of whatever background color you want, as LarsTech suggested.
If it is a uniform colour you want to replace you could convert this to an alpha. I wouldn't like to code it myself!
You could use GIMP's Color To Alpha source code (It's GPL), here's a version of it
P.S. Not sure how to get the latest.
Background removal /replacement, IMO is more art than science, you’ll not find one algorithm fit all solution for this BUT depending on how desperate or interested you are in solving this problem, you may want to consider the following explanation:
Let’s assume you have a color image.
Use your choice of decoding mechanism and generate a gray scale / luminosity image of your color image.
Plot a graph (metaphorically speaking) of numeric value of the pixel(x) vs number of pixels in the image for that value(y). Aka. a luminosity histogram.
Now if your background is large enough (or small), you’d see a part of the graph representing the distribution of a range of pixels which constitute your background. You may want to select a slightly wider range to handle the anti-aliasing (based on a fixed offset that you define if you are dealing with similar images) and call it the luminosity range for your background.
It would make your life easier if you know at least one pixel (sample/median pixel value) out of the range of pixels which defines your background, that way you can ‘look up’ the part of the graph which defines your background.
Once you have the range of luminosity pixels for the background, you may run through the original image pixels, compare their luminosity values with the range you have, if it falls within, replace the pixel in the original image with the desired color, preferably luminosity shifted based on the original pixel and the sample pixel, so that the replaced background looks anti-aliased too.
This is not a perfect solution and there are a lot of scenarios where it might fail / partially fail, but again it would work for the sample image that you had attached with your question.
Also there are a lot of performance improvement opportunities, including GPGPU etc.
Another possible solution would be to use some of the pre-built third party image processing libraries, there are a few open source such as Camellia but I am not sure of what features are provided and how sophisticated they are.

Categories

Resources