I was working on some image editing using System.Drawing, and now ported everything to SkiaSharp in order to use it on Linux / .NET Core. Everything works fine, except I have not yet found a way to programmatically give images rounded corners.
I wrote some code that relies on drawing a path in the form of a circle and then tries to color the outside of the path transparent. This does not work though since it seems like there are multiple layers and making parts of the upper layer transparent does not make the whole region of the image (all layers) transparent. Here is my code:
public static SKBitmap MakeImageRound(SKBitmap image)
{
SKBitmap finishedImage = new SKBitmap(image.Width, image.Height);
using (SKCanvas canvas = new SKCanvas(finishedImage))
{
canvas.Clear(SKColors.Transparent);
canvas.DrawBitmap(image, new SKPoint(0, 0));
SKPath path = new SKPath();
path.AddCircle(image.Width / 2, image.Height / 2, image.Width / 2 - 1f);
path.FillType = SKPathFillType.InverseEvenOdd;
path.Close();
canvas.DrawPath(path, new SKPaint {Color = SKColors.Transparent, Style = SKPaintStyle.Fill });
canvas.ResetMatrix();
return finishedImage;
}
}
I am sorry if this is bad code, this is my first experience with image editing in C#, and therefore I also am an absolute beginner in SkiaSharp. I modified a System.Drawing code I got from here.
I also took a look at this Microsoft document. It shows clipping using paths, but I have not yet been able to get that to work either.
So in conclusion: I am searching for a way to make all layers of an image/the canvas transparent in certain regions.
Any help is greatly appreciated! :D
I think you can do this by setting the SPaint.BlendMode = SKPaintBlendMode.Src. That means that when the canvas is drawing, it just must use the source color, and replace the existing colors.
https://learn.microsoft.com/dotnet/api/skiasharp.skpaint.blendmode
What you are actually doing with
canvas.DrawPath(path, new SKPaint { Color = SKColors.Transparent});
is picking up a brush, dipping it in the transparent paint, and then drawing. So you see nothing. The paint is clear.
But, what you even more want to do is clip before drawing:
https://learn.microsoft.com/dotnet/api/skiasharp.skcanvas.clippath
canvas.Clear(SKColors.Transparent);
// create the circle for the picture
var path = new SKPath();
path.AddCircle(image.Width / 2, image.Height / 2, image.Width / 2 - 1f);
// tell the canvas not to draw outside the circle
canvas.ClipPath(path);
// draw the bitmap
canvas.DrawBitmap(image, new SKPoint(0, 0));
Related
I'm hoping someone can give me some guidance here. I have been gogleing for a while now and I can't come up with anything that suits my needs. I'm a bit of a programmer but not a pro and I have no graphics experience. I am trying to develop a program for my wife to more easily transfer images to her needlepoint drawings. I want to write a C# application that will let me load an image of almost any type and overlay a "grid" on top of it. I want to also be able to implement simple "paint" operations like change the color of a grid square, color selector from the base image, bucket fill, etc. Any suggestions and examples would be greatly appreciated.
Thanks,
Tom
I've implemented something similar for my wife. My basic approach:
1) Scale the image down to the number of necessary pixels. For example, if she's stitching the image on a 10x10 13-mesh canvas, that equates to an image of 130x130 pixels.
Here's some example code to start you off:
// use NearestNeighbor algorithm
public static unsafe Bitmap Reduce(Bitmap source, SizeF toSize, int threadCount)
{
Bitmap reduced = new Bitmap((int)(toSize.Width * threadCount), (int)(toSize.Height * threadCount));
using (Graphics g = Graphics.FromImage(reduced))
{
g.InterpolationMode = InterpolationMode.NearestNeighbor;
g.DrawImage(source, new Rectangle(Point.Empty, reduced.Size));
}
return reduced;
}
2) Display the pixelated image full screen. This will provide a grid-like effect.
3) Display a color palette from DMC's yarn code card, or taken from the image (after down-scaling). Then have the mouse cursor pick up a color by clicking on it, then applying it to the cell that it was subsequently clicked on.
Here's some code for picking up the mouse cursor:
public Point GetPicturePointAtClick()
{
Point p = Cursor.Position;
Point picturePoint = previewBox.PointToClient(p);
if (Zoom != 0)
{
picturePoint.X = (int)(picturePoint.X / Zoom);
picturePoint.Y = (int)(picturePoint.Y / Zoom);
}
return picturePoint;
}
The idea here is to map the clicked area to the correct pixel in the reduced image, math:
Point reducedPoint =
new Point(
(int)(picPoint.X * (_reduced.Width / (float)WorkingBitmap.Width)),
(int)(picPoint.Y * (_reduced.Height / (float)WorkingBitmap.Height)));
There's a lot of code ahead of you. Did you try an online custom needlepoint provider? Try one of these sites, they're both pretty good and customization is free:
http://www.needlepaint.com/
http://www.pepitaneedlepoint.com/
We have a winforms app (Framework v4) that shows an image (via PictureBox) on the screen and allows selection of a rectangular region on that image. During and after the image selection, we show the boundary of the selected area. This is currently done via a DrawRectangle call.
The problem is how to choose the color of this rectangle. Regardless of the chosen color, it is always possible that it will blend into the background (the image). Microsoft paint handles this very well by reversing the colors dynamically on the "selection rectangle". This suits very well to our application, but I have no idea how to do it in winforms.
I also looked around to see if there's a dash-style that would allow two colors to be used (so that I could specify black and white as these colors, making it visible no matter what the background colors are), but I could not find anything of this sort.
Thanks in advance for all the help.
you can use ControlPaint methods to paint a reversible rectangle/frame
ControlPaint.FillReversibleRectangle MSDN
and
ControlPaint.DrawReversibleFrame MSDN
here is a little pseudo code method example
private void DrawReversibleRectangle(int x, int y) {
// Hide the previous rectangle by calling the methods with the same parameters.
var rect = GetSelectionRectangle(this.PointToScreen(this.reversibleRectStartPoint), this.PointToScreen(this.reversibleRectEndPoint));
ControlPaint.FillReversibleRectangle(rect, Color.Black);
ControlPaint.DrawReversibleFrame(rect, Color.Black, FrameStyle.Dashed);
this.reversibleRectEndPoint = new Point(x, y);
// Draw the new rectangle by calling
rect = GetSelectionRectangle(this.PointToScreen(this.reversibleRectStartPoint), this.PointToScreen(this.reversibleRectEndPoint));
ControlPaint.FillReversibleRectangle(rect, Color.Black);
ControlPaint.DrawReversibleFrame(rect, Color.Black, FrameStyle.Dashed);
}
You mention an alternative solution would be to draw a dashed line in two colors, black and white, so that it would be visible on any background.
Fake this by drawing solid line in one color (e.g. black), then draw a dashed line in the other color (e.g. white).
Idea and code from: http://csharphelper.com/blog/2012/09/draw-two-colored-dashed-lines-that-are-visible-on-any-background-in-c/
using (Pen pen1 = new Pen(Color.Black, 2))
{
e.Graphics.DrawRectangle(pen1, rect);
}
using (Pen pen2 = new Pen(Color.White, 2))
{
pen2.DashPattern = new float[] { 5, 5 };
e.Graphics.DrawRectangle(pen2, rect);
}
I have an idea and maybe you guys can give me a good start or an idea in which path might be correct.
I have a picturebox right now loading a specific bmp file. What I want to do is load this bmp file into the picturebox and then load another picture on top of it. The kicker to this all is the 2nd picture must be drawn. The 2nd picture is just a fill in black box. This black box must also overlay on the first image exactly right, the black box has cordinates from paint on it (yes we have the # of the cordaints).
Still think the picturebox is the way to go, or is there a way to load paint into this, and then paint on top of the paint image?
1) Need to load an image
2) Need to read a specific file that has cords
3) Need to draw a black rectangle that matches those coords (Those cords were created in paint).
What do you think the best way to approach this is? A Picture box with code to draw in the cords of the redacted image
Here's a code sample that should do what you're after:
//Load in an image
pbTest.Image = Image.FromFile("c:\\Chrysanthemum.jpg");
//Create the graphics surface to draw on
using (Graphics g = Graphics.FromImage(pbTest.Image))
{
using (SolidBrush brush = new SolidBrush(Color.Black))
{
//Draw a black rectangle at some coordinates
g.FillRectangle(brush, new Rectangle(0, 0, 20, 10));
//Or alternatively, given some points
//I'm manually creating the array here to prove the point, you'll want to create your array from your datasource.
Point[] somePoints = new Point[] { new Point(1,1), new Point(20,25), new Point(35, 50), new Point(90, 100) };
g.FillPolygon(brush, somePoints);
}
}
The finished article:
This answer is written to apply to both web and non-web uses of C# (why I did not give specific examples.)
GDI and other graphics libs all have functions that will paint a filled rectangle on top of an image. This is the way to go. If you use two images there is a good chance for a standard user and a great chance for a hacker they will be able to view just the original image, exposing the information you are trying to hide.
If you only send an image with the areas redacted, you will never have to worry about them being seen.
To zoom images in and out, there is a possible way to resize the pictureBox and showing image in strechmode. Although I can not use it efficiently becauce in general over 8x it gives storage error [think that a pictureBox has the Size(32k, 32k) it needs over 1GB memory !
Is there a special method, or should I zoom only the seen part of the image by using ImageClone ?
Update:
Here is the project at first try to zoom at the project [impossible, storage error] than delete the 41. line in form.cs :
pictureBox1.Image = youPicture;
After deleting this line, the program will work, please move the zoomed image.
Here is the link: http://rapidshare.com/files/265835370/zoomMatrix.rar.html
By using the matrix object and the transform property of your graphics object:
using(Graphics g = this.CreateGraphics())
{
using(Bitmap youPicture = new Bitmap(yourPictureFile))
{
g.DrawImage(youPicture, 0, 0, 300, 100); //set the desired size
//Now you need to create a matrix object to apply transformation on your graphic
Matrix mat = new Matrix();
mat.Scale(1.5f, 1.5f, MatrixOrder.Append); //zoom to 150%
g.Transform = mat;
g.DrawImage(youPicture, new Rectangle(...), 0, 0, youPicture.Width,
youPicture.Height, GraphicsUnit.Pixel) ;
}
}
I personally would just zoom the visible part as the rest is hidden anyway (and thus no use)
See this answer to an earlier question. You definitely don't want to zoom by making the image huge and showing only part of it - you'll run into the memory problem that you've already encountered. Also, the stretch mode of a picture box doesn't use high-quality interpolation, so the result will look pretty crappy.
In the answer I linked here, I included a link to a C# project that shows you how to do this kind of zooming.
Update: here is a direct link to the downloadable project.
I am using some custom controls one of which is a tooltip controller that can display images, so I am using th ebelow code to instantiate it:
Image newImage = Image.FromFile(imagePath);
e.ToolTipImage = newImage;
obviously could inline it but just testing at the moment. The trouble is the image is sometimes the wrong size, is there a way to set the display size. The only way I can currently see is editing the image using GDI+ or something like that. Seems like a lot of extra processing when I am only wanting to adjust display size not affect the actual image.
Once you have an image object loaded from its source, the Height and Width (and Size, and all ancillary properties) are read-only. Therefore, you are stuck with GDI+ methods for resizing it in RAM and then displaying it accordingly.
There are a lot of approaches you can take, but if you were to encapsulate that out to a library which you could reuse should this problem occur again, you'll be set to go. This isn't exactly optimized (IE, may have some bugs), but should give you an idea of how to approach it:
Image newImage = Image.FromFile(myFilePath);
Size outputSize = new Size(200, 200);
Bitmap backgroundBitmap = new Bitmap(outputSize.Width, outputSize.Height);
using (Bitmap tempBitmap = new Bitmap(newImage))
{
using (Graphics g = Graphics.FromImage(backgroundBitmap))
{
g.InterpolationMode = InterpolationMode.HighQualityBicubic;
// Get the set of points that determine our rectangle for resizing.
Point[] corners = {
new Point(0, 0),
new Point(backgroundBitmap.Width, 0),
new Point(0, backgroundBitmap.Height)
};
g.DrawImage(tempBitmap, corners);
}
}
this.BackgroundImage = backgroundBitmap;
I did test this, and it worked. (It created a 200x200 resized version of one of my desktop wallpapers, then set that as the background image of the main form in a scratch WinForms project. You'll need using statements for System.Drawing and System.Drawing.Drawing2D.
In Winforms, if you contain the image inside a PictureBox control, the PictureBox control can be set to zoom to a particular height/width, and the image should conform.
At least that's what happened in my Head First C# book when I did the exercise.