I am creating a program that scans all the pixels of an image, and whenever it finds a pixel that contains the color pink. It makes the pixel black. But it doesn't seem to find a pink pixel when there is two of them on the image. I do not know if I am using LockBits correctly, maybe I am using it wrong. Can someone please help me solve this I would greatly appreciate it.
Here is the code below:
Bitmap bitmap = pictureBox1.Image as Bitmap;
System.Drawing.Imaging.BitmapData d = bitmap.LockBits(new Rectangle(0, 0, bitmap.Width, bitmap.Height), System.Drawing.Imaging.ImageLockMode.ReadWrite, bitmap.PixelFormat);
IntPtr ptr = d.Scan0;
byte[] rgbs = new byte[Math.Abs(d.Stride) * bitmap.Height];
Marshal.Copy(ptr, rgbs, 0, rgbs.Length);
Graphics g = pictureBox1.CreateGraphics();
for (int index = 2; index < rgbs.Length; index += 3)
{
if (rgbs[index] == 255 && rgbs[index - 1] == 0 && rgbs[index - 2] == 255) // If color = RGB(255, 0, 255) Then ...
{
// This never gets executed!
rgbs[index] = 0;
rgbs[index - 1] = 0;
rgbs[index - 2] = 0;
}
}
Marshal.Copy(rgbs, 0, ptr, rgbs.Length); // Copy rgb values back to the memory location of the bitmap.
pictureBox1.Image = bitmap;
bitmap.UnlockBits(d);
You don't need to copy the pixel data into an array. The point of LockBits is it gives you direct (unsafe) access the memory. You can just iterate the pixels and change them as you find them. You will need to know the format of the image to do this successfully.
BitmapData bmd=bm.LockBits(new Rectangle(0, 0, 10, 10),
ImageLockMode.ReadOnly, bm.PixelFormat);
// Blue, Green, Red, Alpha (Format32BppArgb)
int pixelSize=4;
for(int y=0; y<bmd.Height; y++)
{
byte* row=(byte *)bmd.Scan0+(y*bmd.Stride);
for(int x=0; x<bmd.Width; x++)
{
int offSet = x*pixelSize;
// read pixels
byte blue = row[offSet];
byte green = row[offSet+1];
byte red = row[offSet+2];
byte alpha = row[offSet+3];
// set blue pixel
row[x*pixelSize]=255;
}
}
It's a little more tricky in VB than C# as VB has no knowledge of pointers and requires the use of the marshal class to access unmanaged data. Here's some sample code. (For some reason I originally though this was a VB question).
Dim x As Integer
Dim y As Integer
' Blue, Green, Red, Alpha (Format32BppArgb)
Dim PixelSize As Integer = 4
Dim bmd As BitmapData = bm.LockBits(new Rectangle(0, 0, 10, 10),
ImageLockMode.ReadOnly, bm.PixelFormat)
For y = 0 To bmd.Height - 1
For x = 0 To bmd.Width - 1
Dim offSet As Int32 = (bmd.Stride * y) + (4 * x)
' read pixel data
Dim blue As Byte = Marshal.ReadByte(bmd.Scan0, offSet)
Dim green As Byte = Marshal.ReadByte(bmd.Scan0, offSet + 1)
Dim red As Byte = Marshal.ReadByte(bmd.Scan0, offSet + 2)
Dim alpha As Byte = Marshal.ReadByte(bmd.Scan0, offSet + 3)
' set blue pixel
Marshal.WriteByte(bmd.Scan0, offSet , 255)
Next
Next
Related
I'm writing in c# and using this to fill my PictureBox with a byte array
var bmp = new Bitmap(48, 32, PixelFormat.Format1bppIndexed);
var bmpData = bmp.LockBits(new Rectangle(0, 0, bmp.Width, bmp.Height),
ImageLockMode.WriteOnly, bmp.PixelFormat);
Marshal.Copy(fileArray, 0, bmpData.Scan0, fileArray.Length);
bmp.UnlockBits(bmpData);
return bmp;
The result is very strange.
It only fills up to the 125th byte. So I tried playing with the data and the 0-125 bytes show as they should but anything after byte 125 is not shown and it overwrites byte 125.
So if I do 0-125 all 0xff I get as solid bar up top. but adding 126 as 0x00 it replaces 125 with 0x00.
Instead of inserting the padding into your data you can move the data row by row:
int w = 48;
int h = 32;
int unpadded = w / 8; // unpadded byte length of one line in your data
Bitmap bmp = new Bitmap(w, h, PixelFormat.Format1bppIndexed);
BitmapData bmpData = bmp.LockBits(
new Rectangle(0, 0, bmp.Width, bmp.Height),
ImageLockMode.WriteOnly, bmp.PixelFormat);
int stride = 8 // padded length of scanline
for (int i = 0; i < h; i++)
{
Marshal.Copy(blob, i * unpadded , bmpData.Scan0 + i * stride, unpadded );
}
bmp.UnlockBits(bmpData);
return bmp;
Note that to make bitmap operations faster all physical rows must have a length that's a multiple of 4 bytes. The resulting, padded length is called stride.
The stride is the width of a single row of pixels (a scan line),
rounded up to a four-byte boundary. If the stride is positive, the
bitmap is top-down. If the stride is negative, the bitmap is
bottom-up.
With a logical length of 48 pixels and 1bpp your logical length is 6 bytes, so it must be padded to 8 bytes internally.
Best not to move your data in managed code to prepare this but to move only the right portions to the right slots..
Here are more examples of calculating stride for other formats
This solution worked for me. Its by far not the best and shows a lack of understanding or simply bitmaps can not support monochrome 1 bit per pixel images. Never the less it works. All this does is adds 2 bytes on each row of 6 bytes. So that the stride can deal with it.
int j = 1;
int k = 0;
for (int i = 0; i < 192; i++)
{
fixArray[k] = blob[i];
j++;
if (j == 7)
{
j = 1;
fixArray[++k] = 0;
fixArray[++k] = 0;
}
k++;
}
I am working on Perceptual image hashes. Firstly, I reduce image size to remove high frequencies. Then, I shrink image to (8*8) so that there are 64 total pixels. I use the following lines of code.
private void button1_Click(object sender, EventArgs e)
{
Image img = pictureBox1.Image;
img = resizeImage(img, new Size(8,8));
pictureBox2.Image = img;
}
public static Image resizeImage(Image imgToResize, Size size)
{
return (Image)(new Bitmap(imgToResize, size));
}
Now I want to reduce color so the tiny (8x8) picture is converted to a gray scale and want to changes the hash from 64 pixels (64 red, 64 green, and 64 blue) to 64 total colors. Here, I'm stuck.
If you're going for 'perceptual' hashes, and only need to process 64 values, it may be interesting to use a more advanced algorithm. The theory behind it is roughly explained here.
Gray = (0.2126×Red2.2 + 0.7152×Green2.2 + 0.0722×Blue2.2)1/2.2
As code, this would become
public static Byte GetGreyValue(Byte red, Byte green, Byte blue)
{
Double redFactor = 0.2126d * Math.Pow(red, 2.2d);
Double grnFactor = 0.7152d * Math.Pow(green, 2.2d);
Double bluFactor = 0.0722d * Math.Pow(blue, 2.2d);
Double grey = Math.Pow(redFactor + grnFactor + bluFactor, 1d / 2.2);
return (Byte)Math.Max(0, Math.Min(255, Math.Round(grey, MidpointRounding.AwayFromZero)));
}
Now you can just go over all the bytes in your resized image and convert them to gray. Using LockBits, that's not too hard. You just copy the bytes out, iterate over them per four (per ARGB bytes quad), get the RGB components out, throw them into the 'make grey' function, put the result in the RGB spots you took them from, and write the byte array back into your image when you're done.
public static Bitmap GetGreyImage(Image img, Int32 width, Int32 height)
{
// get image data
Bitmap b = new Bitmap(img, width, height);
BitmapData sourceData = b.LockBits(new Rectangle(0, 0, b.Width, b.Height), ImageLockMode.ReadWrite, PixelFormat.Format32bppArgb);
Int32 stride = sourceData.Stride;
Byte[] data = new Byte[stride * b.Height];
// Copy bytes from image into data array
Marshal.Copy(sourceData.Scan0, data, 0, data.Length);
// iterate over array and convert to gray
for (Int32 y = 0; y < height; y++)
{
Int32 offset = y * stride;
for (Int32 x = 0; x < width; x++)
{
// "ARGB" is little-endian Int32, so the actual byte order is B,G,R,A
Byte colB = data[offset + 0]; // B
Byte colG = data[offset + 1]; // G
Byte colR = data[offset + 2]; // R
Int32 colA = data[offset + 3]; // A
// Optional improvement: set pixels to black if color
// is considered too transparent to matter.
Byte grayValue = colA < 128 ? 0 : GetGreyValue(colR, colG, colB);
data[offset + 0] = grayValue; // B
data[offset + 1] = grayValue; // G
data[offset + 2] = grayValue; // R
data[offset + 3] = 0xFF; // A
offset += 4;
}
}
// Copy bytes from data array back into image
Marshal.Copy(data, 0, sourceData.Scan0, data.Length);
b.UnlockBits(sourceData);
return b;
}
If with "change from 64 red, 64 green, and 64 blue to 64 total colors" you mean you want an 8-bit (paletted) image where each byte is one pixel, then you're going to have to simply save the values you generate there in a new byte array instead of writing them back, and then create a new 8x8 Bitmap with the Format8bppIndexed format, open it in a second BitmapData object, and write it in there.
Do note that 8-bit images need a colour palette, so you'll need to go over the standard generated 8-bit palette of the image and change it to a fade from (0,0,0) to (255,255,255) with a for loop.
As the subject says, I have a .bmp image and I need to write a code which will be able to get the color of any pixel of the image. It is a 1bpp (indexed) image, so the colour will be either black or white. Here is the code I currently have:
//This method locks the bits of line of pixels
private BitmapData LockLine(Bitmap bmp, int y)
{
Rectangle lineRect = new Rectangle(0, y, bmp.Width, 1);
BitmapData line = bmp.LockBits(lineRect,
ImageLockMode.ReadWrite,
bmp.PixelFormat);
return line;
}
//This method takes the BitmapData of a line of pixels
//and returns the color of one which has the needed x coordinate
private Color GetPixelColor(BitmapData data, int x)
{
//I am not sure if this line is correct
IntPtr pPixel = data.Scan0 + x;
//The following code works for the 24bpp image:
byte[] rgbValues = new byte[3];
System.Runtime.InteropServices.Marshal.Copy(pPixel, rgbValues, 0, 3);
return Color.FromArgb(rgbValues[2], rgbValues[1], rgbValues[0]);
}
But how can I make it work for a 1bpp image? If I read only one byte from the pointer it always has the 255 value, so I assume, I am doing something wrong.
Please, do not suggest to use the System.Drawing.Bitmap.GetPixel method, because it works too slow and I want the code to work as fast as possible.
Thanks in advance.
EDIT:
Here is the code that works fine, just in case someone needs this:
private Color GetPixelColor(BitmapData data, int x)
{
int byteIndex = x / 8;
int bitIndex = x % 8;
IntPtr pFirstPixel = data.Scan0+byteIndex;
byte[] color = new byte[1];
System.Runtime.InteropServices.Marshal.Copy(pFirstPixel, color, 0, 1);
BitArray bits = new BitArray(color);
return bits.Get(bitIndex) ? Color.Black : Color.White;
}
Ok, got it! You need to read the bits from the BitmapData and apply a mask to the bit you want extract the color:
var bm = new Bitmap...
//lock all image bits
var bitmapData = bm.LockBits(new Rectangle(0, 0, bm.Width, bm.Height), ImageLockMode.ReadWrite, PixelFormat.Format1bppIndexed);
// this will return the pixel index in the color pallete
// since is 1bpp it will return 0 or 1
int pixelColorIndex = GetIndexedPixel(50, 30, bitmapData);
// read the color from pallete
Color pixelColor = bm.Pallete.Entries[pixelColorIndex];
And here is the method:
// x, y relative to the locked area
private int GetIndexedPixel(int x, int y, BitmapData bitmapData)
{
var index = y * bitmapData.Stride + (x >> 3);
var chunk = Marshal.ReadByte(bitmapData.Scan0, index);
var mask = (byte)(0x80 >> (x & 0x7));
return (chunk & mask) == mask ? 1 : 0;
}
The pixel position is calculated in 2 rounds:
1) Find the byte where pixel in 'x' is (x / 8): each byte holds 8 pixels, to find the byte divide x/8 rounding down: 58 >> 3 = 7, the pixel is on the byte 7 of the current row (stride)
2) Find the bit on the current byte (x % 8): Do x & 0x7 to get only the 3 leftmost bits (x % 8)
Example:
x = 58
// x / 8 - the pixel is on byte 7
byte = 58 >> 3 = 58 / 8 = 7
// x % 8 - byte 7, bit 2
bitPosition = 58 & 0x7 = 2
// the pixels are read from left to right, so we start with 0x80 and then shift right.
mask = 0x80 >> bitPosition = 1000 0000b >> 2 = 0010 0000b
First of all, if you need to read a single pixel in one operation, then GetPixel will be equivalent in performance. The expensive operation is locking the bits, ie. you should hold on to the BitmapData for doing all the reading you need, and only close it at the end - but remember to close it !
There seems to be some confusion about your pixel format, but let's assume it is correct 1bpp. Then each pixel will occupy one bit, and there will be data for 8 pixels in a byte. Therefore, your indexing calculation is incorrect. The location of the byte would be in x/8, then you need to take bit x%8.
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.
I've got a PNG image that I'm operating on via the System.Drawing API in .NET. It has large transparent regions, and I would like to replace the transparent regions with white fill--so that there are no transparent regions in the image. Easy enough in an image editing program... but so far I've had no success doing this in C#.
Can someone give me some pointers?
I'm not sure how to detect transparent pixel. I know if the Alpha is 0 it's completly transparent and if it's 255 it's opaque. I'm not sure if you should check for Alpha == 0 or Alpha != 255 ; if you can try it and give me a feedback that would be helpful.
From MSDN
The alpha component specifies the
transparency of the color: 0 is fully
transparent, and 255 is fully opaque.
Likewise, an A value of 255 represents
an opaque color. An A value from 1
through 254 represents a
semitransparent color. The color
becomes more opaque as A approaches
255.
void Foo(Bitmap image)
{
for (int y = 0; y < image.Height; ++y)
{
for (int x = 0; x < image.Width; ++x)
{
// not very sure about the condition.
if (image.GetPixel(x, y).A != 255)
{
image.SetPixel(x,y,Color.White);
}
}
}
}
My example:
public void FillPngWhite(Bitmap bmp)
{
if (bmp.PixelFormat != PixelFormat.Format32bppArgb)
throw new ApplicationException("Not supported PNG image!");
// Lock the bitmap's bits.
Rectangle rect = new Rectangle(0, 0, bmp.Width, bmp.Height);
BitmapData bmpData = bmp.LockBits(rect, ImageLockMode.ReadWrite, bmp.PixelFormat);
// Get the address of the first line.
IntPtr ptr = bmpData.Scan0;
// Declare an array to hold the bytes of the bitmap.
int bytes = Math.Abs(bmpData.Stride) * bmp.Height;
byte[] rgbaValues = new byte[bytes];
// Copy the RGB values into the array.
System.Runtime.InteropServices.Marshal.Copy(ptr, rgbaValues, 0, bytes);
// array consists of values RGBARGBARGBA
for (int counter = 0; counter < rgbaValues.Length; counter += 4)
{
double t = rgbaValues[counter + 3]/255.0; // transparency of pixel between 0 .. 1 , easier to do math with this
double rt = 1 - t; // inverted value of transparency
// C = C * t + W * (1-t) // alpha transparency for your case C-color, W-white (255)
// same for each color
rgbaValues[counter] = (byte) (rgbaValues[counter]*t + 255*rt); // R color
rgbaValues[counter + 1] = (byte)(rgbaValues[counter + 1] * t + 255 * rt); // G color
rgbaValues[counter + 2] = (byte)(rgbaValues[counter + 2] * t + 255 * rt); // B color
rgbaValues[counter + 3] = 255; // A = 255 => no transparency
}
// Copy the RGB values back to the bitmap
System.Runtime.InteropServices.Marshal.Copy(rgbaValues, 0, ptr, bytes);
// Unlock the bits.
bmp.UnlockBits(bmpData);
}
This is different bacause:
I use LockBits instead GetPixel and SetPixel. It is much more faster, but little harder to understand. It's a little modified example from : MSDN
I'm taking real aplha value into consideration, as I said in the comment to your question. This will make black with 50% transparency (128) look like gray instead of black. Reason for this is by "replace alpha with white in graphics editor" I imagine creating new layer underneath you image filled with white and then flattening both layers together. This example will have same effect.
Once you have a handle to the bitmap object, just do something like:
Bitmap yourImage = HOWEVER YOU LOAD YOUR IMAGE;
int width = YOUR IMAGE WIDTH;
int height = YOUR IMAGE HEIGHT;
Color c;
Color white = new Color(255,255,255,255)
for(int w = 0; w < width; w++)
for(int h = 0; h < height; h++)
{
c = yourImage.GetPixel(w,h);
yourImage.SetPixel(w,h, ((((short)(c.A)) & 0x00FF) <= 0)? white:c); //replace 0 here with some higher tolerance if needed
}
This may be oversimplifying your problem, but if it's on a form or other readily available control, you could simply paint the background White before placing the Image on top.