I know this is mostly an image question not code, but I'll give it a shot here.
So first I have a 8bits/pixel grayscale image(bitmap). Which means that each pixel is represented into 1
byte. This means that the the pixel value is the byte value. Clear enough.
But then...
I have a 16bits/pixel grayscale image (bitmap). Which means that each pixel is represented into 2 bytes. This is clear for me. Now I create a byte[] array that will hold each byte value.
For an 1024x1024 image I will have 2097152 bytes. It's 1024*1024*2.
My question is now, how do I get the pixel value for a specific pixel.
Let's say for pixel at position(X|Y) the bytes are 84 and 77. How do I transform these 2 values into the pixel value.
Firstly I need this for some calculation where I need the pixel value, then I want to change the color palette of the bitmap and it works fine with 8 bitsperpixel images, but doesn't for 16bitsperpixel images.
Any help would be nice.
var width = bitmap.PixelWidth;
var height = bitmap.PixelHeight;
var dpiX = bitmap.DpiX;
var dpiY = bitmap.DpiY;
byte[] pixels = new byte[
bitmap.PixelHeight * bitmap.PixelWidth *
bitmap.Format.BitsPerPixel / 8];
bitmap.CopyPixels(pixels, bitmap.PixelWidth * bitmap.Format.BitsPerPixel / 8, 0);
This is how I create the array of pixels.
It may be easier to use a 2-byte type for the pixel array:
var width = bitmap.PixelWidth;
var height = bitmap.PixelHeight;
var pixels = new ushort[width * height];
bitmap.CopyPixels(pixels, 2 * width, 0);
Access an individual pixel value directly from that array:
var x = 100;
var y = 200;
var pixel = (int)pixels[width * y + x];
In order to convert the 16bpp pixel array into a 8bpp array, just divide each pixel value by 256
var pixels8bpp = pixels.Select(p => (byte)(p / 256)).ToArray();
and create a 8bpp BitmapSource by
var bitmap8bpp = BitmapSource.Create(
width, height, 96, 96, PixelFormats.Gray8, null, pixels8bpp, width);
Related
I am writing an application that requires the conversion of BitmapSource to byte array of pixels. Byte array's length should be equal to Bitmap's width x Bitmap's height.
I have tried bmp.CopyPixels() function in the namespace System.Windows.Media.Imaging. But it output an array which is larger than width x height of bitmap.
private byte[] GetBytesFromBitmapSource(System.Windows.Media.Imaging.BitmapSource bmp)
{
int width = bmp.PixelWidth;
int height = bmp.PixelHeight;
int stride = width * ((bmp.Format.BitsPerPixel + 7) / 8);
byte[] pixels = new byte[height * stride];
bmp.CopyPixels(pixels, stride, 0);
return pixels;
}
My image (BitmapSouce) size is 2686 x 2686. I am expecting a pixel array having length 2686 x 2686 . (byte[2686 x 2686])
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.
I have some images that have more than, let's say 6.000.000 pixels and I want to scale them to be somewhere around that value.
public void downscaleByCalculateInSampleSize(string filePath, string newPath)
{
int reqNumberOfPixels = 6000000;
double inSampleSize = 1;
using (System.Drawing.Image oImage = System.Drawing.Image.FromFile(filePath))
{
int newWidth = oImage.Width;
int newHeight = oImage.Height;
int actualNumberofPixels = oImage.Width * oImage.Height;
if (actualNumberofPixels > reqNumberOfPixels)
{
inSampleSize = Math.Sqrt(actualNumberofPixels / reqNumberOfPixels);
newWidth = Convert.ToInt32(Math.Round((float)oImage.Width / inSampleSize));
newHeight = Convert.ToInt32(Math.Round((float)oImage.Height / inSampleSize));
}
var newImage = new Bitmap(newWidth, newHeight);
Graphics graphics = Graphics.FromImage(newImage);
graphics.DrawImage(oImage, 0, 0, newWidth, newHeight);
newImage.Save(newPath);
}
}
I've tried to downscale an image that had 6367 x 4751 pixels and 72 dpi resolution (24 bit depth) with the size of 8.03 MB. I've resized this image and I was expecting to be a much more smaller one in size (bellow 8 MB) but mine has 17. The scaled image is 2847 x 2125 (96 dpi with 32 Bit depth). Why is this happening?
Is there a way to downscale an image to a requested number of pixels and the result to have the size much more smaller? I don't care about the resolution...
You are using integer division and truncating results at:
inSampleSize = Math.Sqrt(actualNumberofPixels / reqNumberOfPixels);
Try instead:
inSampleSize = Math.Sqrt((double)actualNumberofPixels / (double)reqNumberOfPixels);
Also, save with:
newImage.Save(newPath, ImageFormat.Jpeg);
As the sizes you are getting seem much too large if you are saving with a lossy format with that many pixels
I am using the MakeTransparent() function call on the c# Bitmap object to convert images to transparent images. When this method is called, it will convert a background color to be transparent by setting the alpha channel, but it then converts the background color to black.
I need to find a fast way of converting this background color back to white or whatever the original color was, as occasionally I need to flatten the image to a non-alpha channel enabled format.
Make transparent doesn't seem to have any flags or overloads that allows you to tell it to leave the background color alone, and altering the image pixel by pixel is way to inefficient. Anyone have any suggestions or GDI tricks to solve this problem?
There doesn't seem to be a fast way to do this using the managed code interface. Using individual pixel manipulation, or using unmanaged code to update the pixels seem to be the only real options.
This is actually possible in managed code, by using Marshal.Copy to copy the backing byte array out of a bitmap object, then editing it, and then copying it back.
So basically, with that general method in mind, you just go over the pixels, line by line, detect which pixels have the colour you want replaced, and set their alpha byte to 0.
Note that "ARGB" refers to the order of the components inside the Int32 value of one read pixel. Since this value is little-endian, the actual order of the bytes at a given offset is the reverse; B = offset + 0, G = offset + 1, R = offset + 2, A = offset + 3.
/// <summary>
/// Clears the alpha value of all pixels matching the given colour.
/// </summary>
public static Bitmap MakeTransparentKeepColour(Bitmap image, Color clearColour)
{
Int32 width = image.Width;
Int32 height = image.Height;
// Paint on 32bppargb, so we're sure of the byte data format
Bitmap bm32 = new Bitmap(width, height, PixelFormat.Format32bppArgb);
using (Graphics gr = Graphics.FromImage(bm32))
gr.DrawImage(image, new Rectangle(0, 0, width, height));
BitmapData sourceData = bm32.LockBits(new Rectangle(0, 0, width, height), ImageLockMode.ReadOnly, bm32.PixelFormat);
Int32 stride = sourceData.Stride;
// Copy the image data into a local array so we can use managed functions to manipulate it.
Byte[] data = new Byte[stride * height];
Marshal.Copy(sourceData.Scan0, data, 0, data.Length);
Byte colR = clearColour.R;
Byte colG = clearColour.G;
Byte colB = clearColour.B;
for (Int32 y = 0; y < height; y++)
{
Int32 inputOffs = y * stride;
for (Int32 x = 0; x < width; x++)
{
if (data[inputOffs + 2] == colR && data[inputOffs + 1] == colG && data[inputOffs] == colB)
data[inputOffs + 3] = 0;
inputOffs += 4;
}
}
// Copy the edited image data back.
Marshal.Copy(data, 0, sourceData.Scan0, data.Length);
bm32.UnlockBits(sourceData);
return bm32;
}
This can easily be enhanced with a tolerance level instead of an exact match, with something like Math.Abs(data[inputOffs + 2] - colR) < tolerance, or by actually converting the bytes to a colour object and doing some other kind of approximation (like hue/saturation/brightness).
I am trying to create a BitMap from a string array of pixel color values. The array contains 76800 elements (320 x 240) which have the pixels in decimal format (e.g. "11452343").
I am trying to use this function to create my BitMap
var b = new Bitmap(320, 240, PixelFormat.Format8bppIndexed);
var ncp = b.Palette;
for (int i = 0; i < 256; i++)
ncp.Entries[i] = Color.FromArgb(255, i, i, i);
b.Palette = ncp;
var BoundsRect = new Rectangle(0, 0, 320, 240);
var bmpData = b.LockBits(BoundsRect, ImageLockMode.WriteOnly, b.PixelFormat);
var ptr = bmpData.Scan0;
var bytes = bmpData.Stride * b.Height;
var rgbValues = new byte[bytes];
for (var i = 0; i < pixelArray.Length; i++)
{
// ISSUE OCCURS HERE
rgbValues[i] = byte.Parse(pixelArray[i]);
}
Marshal.Copy(rgbValues, 0, ptr, bytes);
b.UnlockBits(bmpData);
My issue occurs inside the for loop where I try to convert the decimal value to a byte value to add to the rgbValues array. How can I convert that decimal color to a byte value to add to the array?
From what I hope I understand I think you are not doing this right..
I see you are building a greyscale palette but the ARGB values will not point into that palette..
Assuming that you have a decimal number for each pixel, I think it would be easier to first create a Bitmap with default ARGB color depth.
After you have painted its pixels you could change its format to Format8bppIndexed.
Creating the palette and the pixel index values will be best done by system routines.
Edit: If you really want to create the mapping yourself, you can use this line to create a greyscale 8-bit value from a decimal ARGB value decVal:
int grey = (byte)(Color.FromArgb(decVal).GetBrightness() * 255);
Setting the pixels to these byte values should create a working mapping into your greyscale palette but it I haven't tried it myself yet..