i am creating png image which painted on my base, from the base i can save a png image, for your reference
Graphics g = e.Graphics;
....
g.DrawLine(pen, new Point(x, y), new Point(x1, y1));
.....
base.OnPaint(e);
using (var bmp = new Bitmap(500, 50))
{
base.DrawToBitmap(bmp, new Rectangle(0, 0, 500, 50));
bmp.Save(outPath);
}
this is single color transparency image, now how do i can inverse this image like png filled with any color and the real image portion should be transparent, is there any possibilities?
bit detail : so transparent will go nontransparent and where there is fill will go to transparent
There's a faster way if you're willing to use unsafe code:
private unsafe void Invert(Bitmap bmp)
{
int w = bmp.Width, h = bmp.Height;
BitmapData data = bmp.LockBits(new Rectangle(0, 0, w, h), ImageLockMode.ReadWrite, PixelFormat.Format32bppArgb);
int* bytes = (int*)data.Scan0;
for ( int i = w*h-1; i >= 0; i-- )
bytes[i] = ~bytes[i];
bmp.UnlockBits(data);
}
Note that this doesn't care about the colors and will invert those as well. If you wish to use a specific color, then the code will have to be modified a bit.
EDIT (thanks for Thomas notation)
public void ApplyInvert()
{
byte A, R, G, B;
Color pixelColor;
for (int y = 0; y < bitmapImage.Height; y++)
{
for (int x = 0; x < bitmapImage.Width; x++)
{
pixelColor = bitmapImage.GetPixel(x, y);
A = (byte)(255 - pixelColor.A);
R = pixelColor.R;
G = pixelColor.G;
B = pixelColor.B;
bitmapImage.SetPixel(x, y, Color.FromArgb((int)A, (int)R, (int)G, (int)B));
}
}
}
from here : Image Processing in C#: Inverting an image
For anyone who wants a fast method for inverting Bitmap colors without using unsafe:
public static void BitmapInvertColors(Bitmap bitmapImage)
{
var bitmapRead = bitmapImage.LockBits(new Rectangle(0, 0, bitmapImage.Width, bitmapImage.Height), ImageLockMode.ReadOnly, PixelFormat.Format32bppPArgb);
var bitmapLength = bitmapRead.Stride * bitmapRead.Height;
var bitmapBGRA = new byte[bitmapLength];
Marshal.Copy(bitmapRead.Scan0, bitmapBGRA, 0, bitmapLength);
bitmapImage.UnlockBits(bitmapRead);
for (int i = 0; i < bitmapLength; i += 4)
{
bitmapBGRA[i] = (byte)(255 - bitmapBGRA[i]);
bitmapBGRA[i + 1] = (byte)(255 - bitmapBGRA[i + 1]);
bitmapBGRA[i + 2] = (byte)(255 - bitmapBGRA[i + 2]);
// [i + 3] = ALPHA.
}
var bitmapWrite = bitmapImage.LockBits(new Rectangle(0, 0, bitmapImage.Width, bitmapImage.Height), ImageLockMode.WriteOnly, PixelFormat.Format32bppPArgb);
Marshal.Copy(bitmapBGRA, 0, bitmapWrite.Scan0, bitmapLength);
bitmapImage.UnlockBits(bitmapWrite);
}
Bitmap GetPixel and SetPixel are extremly slow, this method works by copying the Bitmap pixels into a byte array, which you can then loop through and change, before finally copying the pixels back.
When you say invert the transparent sections into color, are you storing the real colors in the PNG image just set to full transparency? A lot of programs will optimize a png by removing the color data from transparency so you can't reverse it.
Colors can be converted to transparency
But transparency (without underlying colors) cannot be converted to color.
If your lucky your PNG will be non optimized and still have the original color data intact, but if your doing this from user input then it won't work for a high percentage of cases.
Related
In C#, how do I generate a binary mask which has no gray values/noise in the image?
Right now I am able to generate a fairly simple output which looks very close to what I want but has noise around the edges both inside and outside of the white blob (You need to zoom all the way in to see the noise). I am planning to use the image later for image processing but cannot have anything other than black and white values in the image.
Pictures:
Crop 1
Crop 2
Code:
public CropedImage(List<Node> nodes)
{
InitializeComponent();
//List<PointF> listpoints = new List<PointF>();
nodes.ToArray();
PointF[] points = new PointF[nodes.Count];
for(int i = 0; i < nodes.Count; ++i)
{
points[i].X = nodes[i].X;
points[i].Y = nodes[i].Y;
}
Image SourceImage = ImageOperations.GetImage(MainForm.imageMatrix, pictureBox1);
using (Graphics g = Graphics.FromImage(SourceImage))
{
Color black = ColorTranslator.FromHtml("#000000");
Color white = ColorTranslator.FromHtml("#FFFFFF");
using (Brush b = new SolidBrush(black))
{
Rectangle rect = new Rectangle();
g.FillRectangle(b, 0, 0, MainForm.WIDTH, MainForm.HEIGHT);
}
using (Brush b2 = new SolidBrush(white))
{
g.SmoothingMode = SmoothingMode.AntiAlias;
g.FillClosedCurve(b2, points, 0);
}
}
}
How about changing
g.SmoothingMode = SmoothingMode.AntiAlias;
to
g.SmoothingMode = SmoothingMode.None;
If I understand correctly, that would solve your problem.
As eocron said you have to check all the pixels and round them to black or white. the most simple way of doing it (if only you are creating a temp application to do this for a few images) is to use GetPixel() and SetPixel() methods:
Bitmap bmp = Bitmap.FromFile("image.png");
for(int i=0;i<bmp.Width;i++)
for(int j=0;j<bmp.Height;j++)
bmp.SetPixel(i,j, bmp.GetPixel(i,j).R > 127? Color.White: Color.Black); // as its gray I'm only checking Red
However if you are dealing with lots of images especially large ones, or its not a temp application to deal with a few images, and its going to be a feature of your application, the above code is not what you would like to use as it is very slow, and you should use LockBits as it is way faster:
Bitmap bmp = Bitmap.FromFile("image.png");
Rectangle rect = new Rectangle(0, 0, bmp.Width, bmp.Height);
System.Drawing.Imaging.BitmapData bmpData =
bmp.LockBits(rect,System.Drawing.Imaging.ImageLockMode.ReadWrite,bmp.PixelFormat;
IntPtr ptr = bmpData.Scan0;
int bytes = Math.Abs(bmpData.Stride) * bmp.Height;
byte[] rgbValues = new byte[bytes];
System.Runtime.InteropServices.Marshal.Copy(ptr, rgbValues, 0, bytes);
for (int counter = 0; counter < rgbValues.Length; counter += 4)
{
int c = rgbValues[counter] > 127 ? 255: 0;
rgbValues[counter] = c;
rgbValues[counter+1] = c;
rgbValues[counter+2] = c;
}
System.Runtime.InteropServices.Marshal.Copy(rgbValues, 0, ptr, bytes);
bmp.UnlockBits(bmpData);
This simple code gives me very bad result.
Image global_src_img = Image.FromFile(Application.StartupPath + "\\src.png");
Image src_img = ((Bitmap)global_src_img).Clone(new Rectangle(160, 29, 8, 14), PixelFormat.Format32bppArgb);
src_img.Save(Application.StartupPath + "\\src_clonned.png", ImageFormat.Png);
Bitmap trg_bmp = new Bitmap(100, 100);
Graphics gfx = Graphics.FromImage(trg_bmp);
gfx.FillRectangle(new Pen(Color.FromArgb(255, 0, 0, 0)).Brush, 0, 0, trg_bmp.Width, trg_bmp.Height);
gfx.DrawImageUnscaled(src_img, (trg_bmp.Width / 2) - (src_img.Width / 2), (trg_bmp.Height / 2) - (src_img.Height / 2));
gfx.Dispose();
trg_bmp.Save(Application.StartupPath + "\\trg.png", ImageFormat.Png);
It clones part of the big image with letters and writes it to another image.
Written image has extra pixels at outer borders that does not present in source image same as in cloned image.
How to avoid it?
This is cloned image (src_clonned.png) that later will be drawn on graphics.
This is saved resulting image (trg.png).
Draw directly on bitmap to avoid any pixels modifications with this:
public static void DrawImgOnImg(Image src, Bitmap trg, int x, int y)
{
for (int i = x; i < x + src.Width; i++)
for (int j = y; j < y + src.Height; j++)
{
Color src_px = ((Bitmap)src).GetPixel(i - x, j - y);
trg.SetPixel(i, j, src_px);
}
}
I'm trying to make color picker using LockBits so when I move the cursor over picturebox it shows color located at cursor position. Approach with GetPixel works however I'm interested how to do this using LockBits.
My try, unfortunately shows white all the time:
void pictureBox1_MouseMove(object sender, MouseEventArgs e)
{
Bitmap bmp = new Bitmap(pictureBox1.Image);
// we will try to get the pixel using raw data and make color from it
BitmapData data = bmp.LockBits(new Rectangle(0, 0, bmp.Width, bmp.Height), ImageLockMode.ReadOnly, bmp.PixelFormat);
// default format is 32bpp argb ( 4 bytes per pixel)
unsafe
{
byte* scanline = (byte*)data.Scan0;
for(int y = 0; y < data.Height; y++)
{
// row
for (int x = 0; x < data.Width; x+=4)
{
int r = scanline[x];
int g = scanline[x+1];
int b = scanline[x+2];
//int a = scanline[x+3];
Color color = Color.FromArgb(255, r, g, b);
pictureBox2.BackColor = color;
}
}
}
bmp.UnlockBits(data);
//Color color = bmp.GetPixel(e.X, e.Y);
}
Here is the solution..:
unsafe Color getPixel(Bitmap bmp, int x, int y)
{
BitmapData bmData = bmp.LockBits( new Rectangle(0, 0, bmp.Width, bmp.Height),
System.Drawing.Imaging.ImageLockMode.ReadWrite, bmp.PixelFormat);
// not a complete check, but a start on how to use different pixelformats
int pixWidth = bmp.PixelFormat == PixelFormat.Format24bppRgb ? 3 :
bmp.PixelFormat == PixelFormat.Format32bppArgb ? 4 : 4;
IntPtr scan0 = bmData.Scan0;
int stride = bmData.Stride;
byte* p = (byte*)scan0.ToPointer() + y * stride;
int px = x * pixWidth;
byte alpha = (byte) (pixWidth == 4 ? p[px + 3] : 255);
Color color = Color.FromArgb(alpha , p[px + 2], p[px + 1], p[px + 0]);
bmp.UnlockBits(bmData);
return color;
}
This is how you could call it:
private void panel1_MouseClick(object sender, MouseEventArgs e)
{
panel2.BackColor = getPixel((Bitmap)panel1.BackgroundImage, e.X, e.Y);
}
Of course you can use any sort of Bitmap source, like Label.Image ot PictureBox.Image...
The color channels are called ARGB but actually ordered BGRA.
Note that stride is the physical width of a bitmap pixel row, including possible etra bytes to fill to a multiple of 4 bytes.
Also note that other solutions using lockbit work without pointers..
As noted in the comments above this is actually slower than using GetPixel, since setting up the unsafe access vector eats up any gain, even when reading out several pixels.
For Bitmap, there is a MakeTransparent method, is there one similar for changing one color to another?
// This sets Color.White to transparent
Bitmap myBitmap = new Bitmap(sr.Stream);
myBitmap.MakeTransparent(System.Drawing.Color.White);
Is there something that can do something like this?
Bitmap myBitmap = new Bitmap(sr.Stream);
myBitmap.ChangeColor(System.Drawing.Color.Black, System.Drawing.Color.Gray);
Through curiosity to Yorye Nathan's comment, This is an extension that I created by modifying http://msdn.microsoft.com/en-GB/library/ms229672(v=vs.90).aspx.
It can turn all pixels in a bitmap from one colour to another.
public static class BitmapExt
{
public static void ChangeColour(this Bitmap bmp, byte inColourR, byte inColourG, byte inColourB, byte outColourR, byte outColourG, byte outColourB)
{
// Specify a pixel format.
PixelFormat pxf = PixelFormat.Format24bppRgb;
// Lock the bitmap's bits.
Rectangle rect = new Rectangle(0, 0, bmp.Width, bmp.Height);
BitmapData bmpData =
bmp.LockBits(rect, ImageLockMode.ReadWrite,
pxf);
// Get the address of the first line.
IntPtr ptr = bmpData.Scan0;
// Declare an array to hold the bytes of the bitmap.
// int numBytes = bmp.Width * bmp.Height * 3;
int numBytes = bmpData.Stride * bmp.Height;
byte[] rgbValues = new byte[numBytes];
// Copy the RGB values into the array.
Marshal.Copy(ptr, rgbValues, 0, numBytes);
// Manipulate the bitmap
for (int counter = 0; counter < rgbValues.Length; counter += 3)
{
if (rgbValues[counter] == inColourR &&
rgbValues[counter + 1] == inColourG &&
rgbValues[counter + 2] == inColourB)
{
rgbValues[counter] = outColourR;
rgbValues[counter + 1] = outColourG;
rgbValues[counter + 2] = outColourB;
}
}
// Copy the RGB values back to the bitmap
Marshal.Copy(rgbValues, 0, ptr, numBytes);
// Unlock the bits.
bmp.UnlockBits(bmpData);
}
}
called by bmp.ChangeColour(0,128,0,0,0,0);
Lifting the code from this answer:
public static class BitmapExtensions
{
public static Bitmap ChangeColor(this Bitmap image, Color fromColor, Color toColor)
{
ImageAttributes attributes = new ImageAttributes();
attributes.SetRemapTable(new ColorMap[]
{
new ColorMap()
{
OldColor = fromColor,
NewColor = toColor,
}
}, ColorAdjustType.Bitmap);
using (Graphics g = Graphics.FromImage(image))
{
g.DrawImage(
image,
new Rectangle(Point.Empty, image.Size),
0, 0, image.Width, image.Height,
GraphicsUnit.Pixel,
attributes);
}
return image;
}
}
While I haven't benchmarked it, this should be faster than any solution that's doing GetPixel/SetPixel in a loop. It's also a bit more straightforward.
You can use SetPixel for that:
private void ChangeColor(Bitmap s, System.Drawing.Color source, System.Drawing.Color target)
{
for (int x = 0; x < s.Width; x++)
{
for (int y = 0; y < s.Height; y++)
{
if (s.GetPixel(x, y) == source)
s.SetPixel(x, y, target);
}
}
}
GetPixel and SetPixel are wrappers around gdiplus.dll functions GdipBitmapGetPixel and GdipBitmapSetPixel accordingly
Remarks:
Depending on the format of the bitmap, GdipBitmapGetPixel might not
return the same value as was set by GdipBitmapSetPixel. For example,
if you call GdipBitmapSetPixel on a Bitmap object whose pixel format
is 32bppPARGB, the pixel's RGB components are premultiplied. A
subsequent call to GdipBitmapGetPixel might return a different value
because of rounding. Also, if you call GdipBitmapSetPixel on a Bitmap
object whose color depth is 16 bits per pixel, information could be
lost during the conversion from 32 to 16 bits, and a subsequent call
to GdipBitmapGetPixel might return a different value.
You need a library that provides a way to modify the color space of an image without having to work with pixels. LeadTools has a pretty extensive image library that you can use that supports color space modifications, including swapping colors.
In Photoshop you can select "Color" (the second from the bottom) to set the blending mode to the next lower layer:
If you have just a gradient on top of an image the result could look like this:
The description of the color blending mode I found somewhere is:
Color changes the hue and saturation of the lower layer to the hue and saturation of the upper layer but leaves luminosity alone.
My code so far is:
using(var g = Graphics.FromImage(canvas))
{
// draw the lower image
g.DrawImage(lowerImg, left, top);
// creating a gradient and draw on top
using (Brush brush = new LinearGradientBrush(new Rectangle(0, 0, canvasWidth, canvasHeight), Color.Violet, Color.Red, 20))
{
g.FillRectangle(brush, 0, 0, canvasWidth, canvasHeight);
}
}
But that is - of course - just painting over the lower image.
So the question is:
How can I draw an image on top of another image using the blending mode "color" as available in Photoshop?
EDIT:
To make it a bit more clear of what I want to achieve:
And if someone wants to use the images for testing:
Here is my solution. I've used Rich Newman's HSLColor class to convert between RGB and HSL values.
using (Bitmap lower = new Bitmap("lower.png"))
using (Bitmap upper = new Bitmap("upper.png"))
using (Bitmap output = new Bitmap(lower.Width, lower.Height))
{
int width = lower.Width;
int height = lower.Height;
var rect = new Rectangle(0, 0, width, height);
BitmapData lowerData = lower.LockBits(rect, ImageLockMode.ReadOnly, PixelFormat.Format24bppRgb);
BitmapData upperData = upper.LockBits(rect, ImageLockMode.ReadOnly, PixelFormat.Format24bppRgb);
BitmapData outputData = output.LockBits(rect, ImageLockMode.WriteOnly, PixelFormat.Format24bppRgb);
unsafe
{
byte* lowerPointer = (byte*) lowerData.Scan0;
byte* upperPointer = (byte*) upperData.Scan0;
byte* outputPointer = (byte*) outputData.Scan0;
for (int i = 0; i < height; i++)
{
for (int j = 0; j < width; j++)
{
HSLColor lowerColor = new HSLColor(lowerPointer[2], lowerPointer[1], lowerPointer[0]);
HSLColor upperColor = new HSLColor(upperPointer[2], upperPointer[1], upperPointer[0]);
upperColor.Luminosity = lowerColor.Luminosity;
Color outputColor = (Color) upperColor;
outputPointer[0] = outputColor.B;
outputPointer[1] = outputColor.G;
outputPointer[2] = outputColor.R;
// Moving the pointers by 3 bytes per pixel
lowerPointer += 3;
upperPointer += 3;
outputPointer += 3;
}
// Moving the pointers to the next pixel row
lowerPointer += lowerData.Stride - (width * 3);
upperPointer += upperData.Stride - (width * 3);
outputPointer += outputData.Stride - (width * 3);
}
}
lower.UnlockBits(lowerData);
upper.UnlockBits(upperData);
output.UnlockBits(outputData);
// Drawing the output image
}
You will have to restructure your code so that you draw your gradient on a temporary bitmap, read each pixel from the temporary bitmap and canvas, and write a composed pixel to canvas. You should be able to find code converting between RGB and HSL colors, and once you can do that, setting the hue and saturation of pixels in canvas to the values from your temporary bitmap is trivial (though it's a bit harder if you want to use alpha values).
Here's a safe (and slower) version of the accepted answer for completeness.
using (var lower = new Bitmap(#"lower.png"))
using (var upper = new Bitmap(#"upper.png"))
using (var output = new Bitmap(lower.Width, lower.Height))
{
var width = lower.Width;
var height = lower.Height;
for (var i = 0; i < height; i++)
{
for (var j = 0; j < width; j++)
{
var upperPixel = upper.GetPixel(j, i);
var lowerPixel = lower.GetPixel(j, i);
var lowerColor = new HSLColor(lowerPixel.R, lowerPixel.G, lowerPixel.B);
var upperColor = new HSLColor(upperPixel.R, upperPixel.G, upperPixel.B) {Luminosity = lowerColor.Luminosity};
var outputColor = (Color)upperColor;
output.SetPixel(j, i, outputColor);
}
}
// Drawing the output image
}