Photo to grayscale in C#.NET and Windows Forms - c#

How do I make this into a grayscale?
After much googling it seems the best I can do is change the tint of the picture (in the code bellow, it's a green tint). How can I do it?
byte[] redGreenBlueVal = new byte[numBytes];
for (int i = 0; i < redGreenBlueVal .Length; i += 4)
{
redGreenBlueVal [i + 0] = (byte)(.114 * redGreenBlueVal [i + 0]); --> blue
redGreenBlueVal [i + 1] = (byte)(.587 * redGreenBlueVal [i + 1]); --> green
redGreenBlueVal [i + 2] = (byte)(.299 * redGreenBlueVal [i + 2]); --> red
}

In effect you're adjusting HSB so this should get you a better grayscale image:
30% RED
59% GREEN
11% BLUE
byte[] redGreenBlueVal = new byte[numBytes];
for (int i = 0; i < redGreenBlueVal .Length; i += 4)
{
gray = (byte)(.11 * redGreenBlueVal [i + 0]);
gray += (byte)(.59 * redGreenBlueVal [i + 1]);
gray += (byte)(.3 * redGreenBlueVal [i + 2]);
redGreenBlueVal [i + 0] = gray;
redGreenBlueVal [i + 1] = gray;
redGreenBlueVal [i + 2] = gray;
}

You could try setting each pixels colour channels to the average value for that pixel.
i.e.
for( int i = 0; i < redGreenBlueVal.Length; += 4 )
{
int average = (redGreenBlueVal[i + 0] + redGreenBlueVal[i + 1] + redGreenBlueVal[i + 2])/3;
redGreenBlueVal[i + 0] = average;
redGreenBlueVal[i + 1] = average;
redGreenBlueVal[i + 2] = average;
}

To get a grayscale image, the basic idea is to have all the 3 channels hold the same value. There are many ways to do this, the simplest ones are:
use only one of the channels (it will look like when you select individual channels in photoshop)
calculate an average of the 3 channels

Related

How do I merge layers using raw pixel data in C# UWP?

Because the Universal Windows Platform does not support flattening Bitmap "layers" by default, I have been creating an algorithm (based on one found here) to take each individual pixels of each layer, and overlay them one over another. The problem is that because I'm using Alpha to allow images to have transparent pixels, I am unsure how to properly merge the pixels so it looks as though one is being places on top of the other.
Here is my code so far:
private unsafe SoftwareBitmap CompileImage()
{
SoftwareBitmap softwareBitmap = new SoftwareBitmap(BitmapPixelFormat.Bgra8, Width, Height);
using (BitmapBuffer buffer = softwareBitmap.LockBuffer(BitmapBufferAccessMode.Write))
{
using (var reference = buffer.CreateReference())
{
byte* dataInBytes;
uint capacity;
((IMemoryBufferByteAccess)reference).GetBuffer(out dataInBytes, out capacity);
BitmapPlaneDescription bufferLayout = buffer.GetPlaneDescription(0);
for (int index = 0; index < layers.Length; index++)
{
for (int i = 0; i < bufferLayout.Height; i++)
{
for (int j = 0; j < bufferLayout.Width; j++)
{
if(index == 0)
{
dataInBytes[bufferLayout.StartIndex + bufferLayout.Stride * i + 4 * j + 0] =
layers[index].pixelData[bufferLayout.StartIndex + bufferLayout.Stride * i + 4 * j + 0]; //B
dataInBytes[bufferLayout.StartIndex + bufferLayout.Stride * i + 4 * j + 1] =
layers[index].pixelData[bufferLayout.StartIndex + bufferLayout.Stride * i + 4 * j + 1]; //G
dataInBytes[bufferLayout.StartIndex + bufferLayout.Stride * i + 4 * j + 2] =
layers[index].pixelData[bufferLayout.StartIndex + bufferLayout.Stride * i + 4 * j + 2]; //R
dataInBytes[bufferLayout.StartIndex + bufferLayout.Stride * i + 4 * j + 3] =
layers[index].pixelData[bufferLayout.StartIndex + bufferLayout.Stride * i + 4 * j + 3]; //A
}
else if(index > 0)
{
//Attempts to "Average" pixel data
dataInBytes[bufferLayout.StartIndex + bufferLayout.Stride * i + 4 * j + 0] =
(byte)(dataInBytes[bufferLayout.StartIndex + bufferLayout.Stride * i + 4 * j + 0] *
layers[index].pixelData[bufferLayout.StartIndex + bufferLayout.Stride * i + 4 * j + 0] / 2);
dataInBytes[bufferLayout.StartIndex + bufferLayout.Stride * i + 4 * j + 1] =
(byte)(dataInBytes[bufferLayout.StartIndex + bufferLayout.Stride * i + 4 * j + 1] *
layers[index].pixelData[bufferLayout.StartIndex + bufferLayout.Stride * i + 4 * j + 1] / 2);
dataInBytes[bufferLayout.StartIndex + bufferLayout.Stride * i + 4 * j + 2] =
(byte)(dataInBytes[bufferLayout.StartIndex + bufferLayout.Stride * i + 4 * j + 2] *
layers[index].pixelData[bufferLayout.StartIndex + bufferLayout.Stride * i + 4 * j + 2] / 2);
dataInBytes[bufferLayout.StartIndex + bufferLayout.Stride * i + 4 * j + 3] =
(byte)(dataInBytes[bufferLayout.StartIndex + bufferLayout.Stride * i + 4 * j + 3] *
layers[index].pixelData[bufferLayout.StartIndex + bufferLayout.Stride * i + 4 * j + 3] / 2);
}
}
}
}
}
}
return softwareBitmap;
}
The current algorithm will take the average BGRA values of each pixel in every layer, and return the result. This is not what I intended it to do. How do I go about correcting it? Thanks in advance
To apply an alpha mix you multiply the previous color by the inverse alpha and the current color by the alpha, of course you should do it on a 0-1 scale (that's why graphic cards work with floats for colors).
Per example, suppose you have three layers, A, B and C, you use A as a base, then mix it with B: (pseudocode)
//bAlpha is thge alpha value of the current pixel from B in byte value
var alpha = bAlpha / 255.0f;
var invAlpha = 1.0f - alpha;
//aRGB is the RGB value of the pixel on the layer A
//bRGB is the RGB value of the pixel on the layer B
var abRGB = aRGB * invAlpha + bRGB * alpha;
And now you apply the third layer:
//cAlpha is thge alpha value of the current pixel from C in byte value
alpha = cAlpha / 255.0f;
invAlpha = 1.0f - alpha;
//abRGB is the RGB value of the previously mixed layers
//cRGB is the RGB value of the pixel on the layer C
var abcRGB = abRGB * invAlpha + cRGB * alpha;
Of course all of this is on ARGB mode, there's also pARGB in which the colors are already premultiplied, so you only need to multiply the previous color by the inverse alpha and add the current color.
//bAlpha is the alpha value of the current pixel from B in byte value
var invAlpha = 1.0f - (bAlpha / 255.0f);
//aRGB is the RGB value of the pixel on the layer A
//bRGB is the RGB value of the pixel on the layer B
var abRGB = aRGB * invAlpha + bRGB;
invAlpha = 1.0f - (cAlpha / 255.0f);
//abRGB is the RGB value of the previously mixed layers
//cRGB is the RGB value of the pixel on the layer C
var abcRGB = abRGB * invAlpha + cRGB;

Bitmap outside array bounds

I have the following code:
if (source != null)
{
int count = 0;
int stride = (source.PixelWidth * source.Format.BitsPerPixel + 7) / 8;
byte[] pixels = new byte[source.PixelHeight * stride];
source.CopyPixels(pixels, stride, 0);
for (int y = 0; y < source.PixelHeight; y = y + 2)
{
for (int x = 0; x < source.PixelWidth; x = x + 2)
{
int index = y * stride + 4 * x;
count = index;
byte red = pixels[index];
byte green = pixels[index + 1];
byte blue = pixels[index + 2];
byte alpha = pixels[index + 3];
}
}
MessageBox.Show("Array Length, pixels: " + pixels.Count() + "," + count);
}
However, i am having an issue where certain bitmap images, when stepped through throw an exception
"System.IndexOutOfRangeException" as the index passes the pixel [ ] array count, does anyone know how to solve this efficiently without oversizing the array?
I want to display the progress as i go along hence the need for an accurate array :)
Thanks in advance.
You should be able to step through your code and figure out when index is larger than your pixels buffer size.
Add some debugging output to your code and step through it. Something like:
if (source != null)
{
int count = 0;
int stride = (source.PixelWidth * source.Format.BitsPerPixel + 7) / 8;
byte[] pixels = new byte[source.PixelHeight * stride];
source.CopyPixels(pixels, stride, 0);
for (int y = 0; y < source.PixelHeight; y = y + 2)
{
for (int x = 0; x < source.PixelWidth; x = x + 2)
{
int index = y * stride + 4 * x;
count = index;
int bufsize = source.PixelHeight * stride;
System.Diagnostics.Debug.WriteLine($"bufsize={bufsize}, index={index}, x={x}, y={y}");
System.Diagnostics.Debug.Assert((index+3) <= bufsize);
byte red = pixels[index];
byte green = pixels[index + 1];
byte blue = pixels[index + 2];
byte alpha = pixels[index + 3];
}
}
MessageBox.Show("Array Length, pixels: " + pixels.Count() + "," + count);
}
A big part about writing code is learning how to debug, use the debugger, and to verify the correctness of your algorithms. Good luck with your project.
This code will crash on any image where Format.BitsPerPixel is less than 32 e.g. 24-bit RGB with no alpha. You also shouldn't assume that stride is what you think it is, you should use the value returned from LockBits.

mipmapping producing wrong results

I'm trying to generate the mipmap of an image. The pixels are stored as a byte[] and the format is {r,g,b,a,r,g,b,a,r,g,b,a ... }
What this is trying to do is get each group of four pixels in the image and find the average of those four pixels, then put that into a new image.
The result of creating all the mipmaps for a sample texture are here: http://imgur.com/KdEEzAw
If there is a way to create the mipmaps without using my own algorithm, and without directx or anything (i'm not using the mipmaps for rendering, i'm saving them to a file) that would be good
public static byte[] mipmap(byte[] inPixels, int width, int height)
{
// add one to width and height incase they are 1
byte[] outPixels = new byte[((width + 1) / 2) * ((height + 1) / 2) * 4];
for (int y = 0; y < height; y += 2)
{
for (int x = 0; x < width; x += 2)
{
// get the four red values
int[] r = new int[4];
r[0] = (int)inPixels[x + y * width + 0]; // top left
r[1] = (int)inPixels[(x + 1) + y * width + 0]; // top right
r[2] = (int)inPixels[(x + 1) + (y + 1) * width + 0]; // bottom right
r[3] = (int)inPixels[x + (y + 1) * width + 0]; // bottom left
// get the four green values
int[] g = new int[4];
g[0] = (int)inPixels[x + y * width + 1]; // top left
g[1] = (int)inPixels[(x + 1) + y * width + 1]; // top right
g[2] = (int)inPixels[(x + 1) + (y + 1) * width + 1]; // bottom right
g[3] = (int)inPixels[x + (y + 1) * width + 1]; // bottom left
// get the four blue values
int[] b = new int[4];
b[0] = (int)inPixels[x + y * width + 2]; // top left
b[1] = (int)inPixels[(x + 1) + y * width + 2]; // top right
b[2] = (int)inPixels[(x + 1) + (y + 1) * width + 2]; // bottom right
b[3] = (int)inPixels[x + (y + 1) * width + 2]; // bottom left
// get the four alpha values
int[] a = new int[4];
a[0] = (int)inPixels[x + y * width + 3]; // top left
a[1] = (int)inPixels[(x + 1) + y * width + 3]; // top right
a[2] = (int)inPixels[(x + 1) + (y + 1) * width + 3]; // bottom right
a[3] = (int)inPixels[x + (y + 1) * width + 3]; // bottom left
// the index in the new image, we divide by 2 because the image is half the size of the original image
int index = (x + y * width) / 2;
outPixels[index + 0] = (byte)((r[0] + r[1] + r[2] + r[3]) / 4);
outPixels[index + 1] = (byte)((g[0] + g[1] + g[2] + g[3]) / 4);
outPixels[index + 2] = (byte)((b[0] + b[1] + b[2] + b[3]) / 4);
outPixels[index + 3] = (byte)((a[0] + a[1] + a[2] + a[3]) / 4);
}
}
return outPixels;
}
I think the problem lies here:
inPixels[x + y * width + 0]
normally this runs correct when one array element is one pixel, only one element is one channel of one pixel. So each pixel starts a (x + (y * width)) * 4 so it should be something like this:
inPixels[((x + y * width) * 4) + 0]
I wrote some code for optimalization maybe you get some extra ideas, but i did not test it:
public static byte[] mipmap(byte[] inPixels, int width, int height)
{
// add one to width and height incase they are 0
byte[] outPixels = new byte[((width / 2) * (height / 2)) * 4];
// the offsets of the neighbor pixels (with *4 for the channel)
// this will automatically select a channel of a pixel one line down.
int[] neighborOffsets = new int[] { 0 * 4, 1 * 4, width * 4, (width + 1) * 4 };
// an 'offset for output buffer'
int outputOffset = 0;
// an 'offset for input buffer'
int inputOffset = 0;
for (int y = 0; y < height / 2; y++)
{
for (int x = 0; x < width / 2; x++)
{
// calculate the average of each channel
for (int channelIndex = 0; channelIndex < 4; channelIndex++)
{
int totalValue = 0;
for (int offset = 0; offset < 4; offset++)
totalValue = (int)inPixels[inputOffset + neighborOffsets[offset] + channelIndex];
// write it to the ouput buffer and increase the offset.
outPixels[outputOffset++] = (byte)(totalValue / 4);
}
// set the input offset on the next pixel. The next pixel is current + 2.
inputOffset += 2 * 4; // *4 for the channelcount (*4 will be optimized by the compiler)
}
inputOffset += width * 4; // skip an extra line. *4 for the channelcount (*4 will be optimized by the compiler)
}
return outPixels;
}
There maybe some little mistakes in it, but it probably run 4 times as fast. Try to avoid redundancy on multiplications.

Memory Object Allocation failure using c# and opencl

I am writing an image processing program with the express purpose to alter large images, the one I'm working with is 8165 pixels by 4915 pixels. I was told to implement gpu processing, so after some research I decided to go with OpenCL. I started implementing the OpenCL C# wrapper OpenCLTemplate.
My code takes in a bitmap and uses lockbits to lock its memory location. I then copy the order of each bit into an array, run the array through the openCL kernel, and it inverts each bit in the array. I then run the inverted bits back into the memory location of the image. I split this process into ten chunks so that i can increment a progress bar.
My code works perfectly with smaller images, but when I try to run it with my big image I keep getting a MemObjectAllocationFailure when trying to execute the kernel. I don't know why its doing this and i would appreciate any help in figuring out why or how to fix it.
using OpenCLTemplate;
public static void Invert(Bitmap image, ToolStripProgressBar progressBar)
{
string openCLInvert = #"
__kernel void Filter(__global uchar * Img0,
__global float * ImgF)
{
// Gets information about work-item
int x = get_global_id(0);
int y = get_global_id(1);
// Gets information about work size
int width = get_global_size(0);
int height = get_global_size(1);
int ind = 4 * (x + width * y );
// Inverts image colors
ImgF[ind]= 255.0f - (float)Img0[ind];
ImgF[1 + ind]= 255.0f - (float)Img0[1 + ind];
ImgF[2 + ind]= 255.0f - (float)Img0[2 + ind];
// Leave alpha component equal
ImgF[ind + 3] = (float)Img0[ind + 3];
}";
//Lock the image in memory and get image lock data
var imageData = image.LockBits(new Rectangle(0, 0, image.Width, image.Height), ImageLockMode.ReadWrite, PixelFormat.Format32bppArgb);
CLCalc.InitCL();
for (int i = 0; i < 10; i++)
{
unsafe
{
int adjustedHeight = (((i + 1) * imageData.Height) / 10) - ((i * imageData.Height) / 10);
int count = 0;
byte[] Data = new byte[(4 * imageData.Stride * adjustedHeight)];
var startPointer = (byte*)imageData.Scan0;
for (int y = ((i * imageData.Height) / 10); y < (((i + 1) * imageData.Height) / 10); y++)
{
for (int x = 0; x < imageData.Width; x++)
{
byte* Byte = (byte*)(startPointer + (y * imageData.Stride) + (x * 4));
Data[count] = *Byte;
Data[count + 1] = *(Byte + 1);
Data[count + 2] = *(Byte + 2);
Data[count + 3] = *(Byte + 3);
count += 4;
}
}
CLCalc.Program.Compile(openCLInvert);
CLCalc.Program.Kernel kernel = new CLCalc.Program.Kernel("Filter");
CLCalc.Program.Variable CLData = new CLCalc.Program.Variable(Data);
float[] imgProcessed = new float[Data.Length];
CLCalc.Program.Variable CLFiltered = new CLCalc.Program.Variable(imgProcessed);
CLCalc.Program.Variable[] args = new CLCalc.Program.Variable[] { CLData, CLFiltered };
kernel.Execute(args, new int[] { imageData.Width, adjustedHeight });
CLCalc.Program.Sync();
CLFiltered.ReadFromDeviceTo(imgProcessed);
count = 0;
for (int y = ((i * imageData.Height) / 10); y < (((i + 1) * imageData.Height) / 10); y++)
{
for (int x = 0; x < imageData.Width; x++)
{
byte* Byte = (byte*)(startPointer + (y * imageData.Stride) + (x * 4));
*Byte = (byte)imgProcessed[count];
*(Byte + 1) = (byte)imgProcessed[count + 1];
*(Byte + 2) = (byte)imgProcessed[count + 2];
*(Byte + 3) = (byte)imgProcessed[count + 3];
count += 4;
}
}
}
progressBar.Owner.Invoke((Action)progressBar.PerformStep);
}
//Unlock image
image.UnlockBits(imageData);
}
You may have reached a memory allocation limit of your OpenCL driver/device. Check the values returned by clGetDeviceInfo. There is a limit for the size of one single memory object. The OpenCL driver may allow the total size of all allocated memory objects to exceed the memory size on your device, and will copy them to/from host memory when needed.
To process large images, you may have to split them into smaller pieces, and process them separately.

c# - Bitmap Palette throws index out of bounds exception when the index is not out of bounds

I know you must here the IndexOutOfBoundsException loads, and I wouldn't normally post stuff about it, but I have just come across it whilst trying to parse an array as a palette using the following code. It throws the exception when i = 0 and palette.Length = 768, I can't see why and I'm sure this code worked before:
ColorPalette palette1 = bmp.Palette;
for (int i = 0; i < palette.Length; i += 3)
{
if (i != 0)
{
Color b = Color.FromArgb(255, palette[i], palette[i + 1], palette[i + 2]);
palette1.Entries[i/3] = b;
}
else
{
Color b = Color.FromArgb(255, palette[i], palette[i + 1], palette[i + 2]);
palette1.Entries[i] = b;
}
}
bmp.Palette = palette1;
The following code DOES work, but uses a smaller palette in a separate function:
ColorPalette palette1 = bmp.Palette;
for (int i = 0; i < 48; i += 3)
{
if (i != 0)
{
Color b = Color.FromArgb(255, palette[i], palette[i + 1], palette[i + 2]);
palette1.Entries[i / 3] = b;
}
else
{
Color b = Color.FromArgb(255, palette[i], palette[i + 1], palette[i + 2]);
palette1.Entries[i] = b;
}
}
bmp.Palette = palette1;
Your loop variable i is bounded by palette.Length, but you're trying to assign to palette1. There's no guarantee that palette1 has the same length as palette, and my guess is that it does not -- hence your problem.

Categories