Using Graphics.FillRectangle Method on 8-bit image - c#

I am trying to add a black banner on the top and bottom of an image. I can add the banner but the pixel format of the resulted bitmap is changed to 32-bit. Is there any way to get an 8-bit bitmap as a result.
As mentioned here, If I set the 8-bit pixelFormat in the constructor, creating a graphics will raise an exception.
I read that if I convert from 32 to 8, maybe the pixel values will be different than original ones. Don't know if I can create a new bitmap with the desired height and add the black banners's pixels using for loops. Anyone has a better and simple way?
My code is as below:
Bitmap img = image as Bitmap;
using (Bitmap bitmap = new Bitmap(img.Width, img.Height + 200*2)) // create blank bitmap of desired size
using (Graphics graphics = Graphics.FromImage(bitmap))
{
SolidBrush brush = new SolidBrush(Color.White);
graphics.FillRectangle(brush, 0, 0, img.Width,200);
// draw existing image onto new blank bitmap
graphics.DrawImage(img, 0, 200, img.Width, img.Height);
// draw your rectangle below the original image
graphics.FillRectangle(brush, 0, 200 + img.Height, img.Width, 200);
// bitmap.Save(#"c:\Test1.bmp");
}

Here is a routine that does a FillRectangle on a bmp8bpp bitmap.
You pass in an index which, if positive will be used to put the color into the palette. If you pass in a negative index it will try to find the color and if it doesn't find it it will place the color at the end of the index. This will overwrite whatever color was there before!
void FillIndexedRectangle(Bitmap bmp8bpp, Rectangle rect, Color col, int index)
{
var pal = bmp8bpp.Palette;
int idx = -1;
if (index >= 0) idx = index;
else
{
if (pal.Entries.Where(x => x.ToArgb() == col.ToArgb()).Any())
idx = pal.Entries.ToList().FindIndex(x => x.ToArgb() == col.ToArgb());
if (idx < 0) idx = pal.Entries.Length - 1;
}
pal.Entries[idx] = col;
bmp8bpp.Palette = pal;
var bitmapData =
bmp8bpp.LockBits(new Rectangle(Point.Empty, bmp8bpp.Size),
ImageLockMode.ReadWrite, bmp8bpp.PixelFormat);
byte[] buffer=new byte[bmp8bpp.Width*bmp8bpp.Height];
Marshal.Copy(bitmapData.Scan0, buffer,0, buffer.Length);
for (int y = rect.Y; y < rect.Bottom; y++)
for (int x = rect.X; x < rect.Right; x++)
{
buffer[x + y * bmp8bpp.Width] = (byte)idx;
}
Marshal.Copy(buffer, 0, bitmapData.Scan0,buffer.Length);
bmp8bpp.UnlockBits(bitmapData);
}
Example calls:
Bitmap img = new Bitmap(200, 200, PixelFormat.Format8bppIndexed);
FillIndexedRectangle(img, new Rectangle(0,0,200, 200), Color.Silver, 21);
FillIndexedRectangle(img, new Rectangle(23, 23, 55, 99), Color.Red, 22);
FillIndexedRectangle(img, new Rectangle(123, 123, 55, 33), Color.Black, 23);
FillIndexedRectangle(img, new Rectangle(1, 1, 123, 22), Color.Orange, 34);
FillIndexedRectangle(img, new Rectangle(27, 27, 16, 12), Color.Black, -1);
img.Save("D:\\__bmp8bpp.png");
Result:
There is room for improvement:
All error checking in the lockbits is missing, both wrt pixelformat and rectangle data
Adding colors with a more dynamical scheme could be nice instead of adding to the end
A scheme for finding the closest color already in the palette could also be nice
A scheme for drawing with transparency would also be nice. For this all necessary new colors would have to be determined first; also the tranparency mode.
Maybe one should return the index used from the method so the next calls can refer to it..
For other shapes than rectangles one could use a copy routine that first draws them onto a 'normal' 32bpp bitmap and then transfers the pixels to the buffer..
Update: Here are a few lines to add (**) or change (*) to allow drawing unfilled rectangles; stroke 0 fills the rectangle..:
void FillIndexedRectangle(Bitmap bmp8bpp, Rectangle rect,
Color col, int index, int stroke) // *
...
...
Marshal.Copy(bitmapData.Scan0, buffer,0, buffer.Length);
Rectangle ri = rect; //**
if (stroke > 0) ri.Inflate(-stroke, -stroke); //**
for (int y = rect.Y; y < rect.Bottom; y++)
for (int x = rect.X; x < rect.Right; x++)
{
if (ri == rect || !ri.Contains(x,y)) //**
buffer[x + y * bmp8bpp.Width] = (byte)idx;
}
Marshal.Copy(buffer, 0, bitmapData.Scan0,buffer.Length);

Related

C# generate a binary mask without gray values/noise in the image?

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);

How to draw image on Graphics without extra pixels at outer borders?

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);
}
}

png how to keep alpha channel but change background color

I have a png image with an alpha channel, for one of our tool which does not support alpha I need to edit the image so that every pixel which is transparent gets a specific color but keeps the alpha so the png still works for all the tools who supports alpha.
Here's my code so far, but it doesn't work, anyone can help? Picture is my source png and I chose red as the color.
Thanks
using (Bitmap ImageAttachedConverted = new Bitmap(Picture.Width, Picture.Height, System.Drawing.Imaging.PixelFormat.Format32bppRgb))
{
using (Graphics GraphicsPicture = Graphics.FromImage(ImageAttachedConverted))
{
GraphicsPicture.InterpolationMode = InterpolationMode.HighQualityBicubic;
GraphicsPicture.CompositingQuality = CompositingQuality.HighQuality;
//GraphicsPicture.Clear(Color.Red);
//GraphicsPicture.DrawImage(
// Picture,
// new Rectangle(0, 0, ImageAttachedConverted.Width, ImageAttachedConverted.Height),
// new Rectangle(0, 0, Picture.Width, Picture.Height),
// GraphicsUnit.Pixel);
// 2nd method pixel bypixel
Bitmap img = Picture as Bitmap;
for (int y = 0; y < ImageAttachedConverted.Height; y++)
{
for (int x = 0; x < ImageAttachedConverted.Width; x++)
{
//Get Colours at the pixel point
Color col1 = img.GetPixel(x, y);
Color temp = Color.FromArgb(
0,
255,
0,
0);
if (col1.A < 100)
ImageAttachedConverted.SetPixel(x, y, temp);
else ImageAttachedConverted.SetPixel(x, y, col1);
}
}
Color temp2 = Color.FromArgb(
0,
255,
0,
0);
//ImageAttachedConverted.MakeTransparent(temp2);
}
ImageAttachedConverted.Save(Destination.FullName, ImageFormat.Png);
}

How to create inverse png image?

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.

Draw image on top of another image with blending mode color

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
}

Categories

Resources