bitmap min and max values in c# - c#

I have a bitmap object in C# which is created as follows:
Bitmap bmp = new Bitmap(_currentImage.Width, _currentImage.Height, PixelFormat.Format48bppRgb);
The bitmap gets filled by a third party function call and it is loaded with the correct image.
Now, I want to do some simple image statistics on it. Is there a convenient way to query the minimum and maximum value in the image, say in the RED channel.

Here's a simple version that reads all the 48bpp pixels and does something with Red as an example (not tested)
unsafe static ushort MaxRed(Bitmap bm)
{
var bd = bm.LockBits(new Rectangle(Point.Empty, bm.Size), ImageLockMode.ReadOnly, PixelFormat.Format48bppRgb);
ushort maxRed = 0;
for (int y = 0; y < bm.Height; y++)
{
ushort* ptr = (ushort*)(bd.Scan0 + y * bd.Stride);
for (int x = 0; x < bm.Width; x++)
{
ushort b = *ptr++;
ushort g = *ptr++;
ushort r = *ptr++;
maxRed = Math.Max(maxRed, r);
}
}
bm.UnlockBits(bd);
return maxRed;
}
Unsafe because it's easier than using Marshal, but you can convert it to that, for example using ReadInt16(IntPtr, Int32) or by copying the whole image into an array (which of course doubles the space requirements).

As pointed out by #harold the image format you are using prevents you from using GetPixel as this method returns a Color which internaly stores its rgb values as byte's. And as you are using an image with 48 bits per pixel (16 bit = 2 byte per color) a byte is to small.
So you need to work with the LockBits method which returns an BitmapData object. The property Scan0 of this return object represents a pointer to the first byte in the data of the locked range.
I came up with the following method to get the maximum r value. It will work with the two specified formats in the PixelFormats property and more formats can easily be added.
public class PixelFormatData
{
// example => rgb are three values,
// => argb are four values
public int ValueCount { get; set; }
public int BitsPerPixel { get; set; }
public PixelFormatData(int valueCount, int bitsPerPixel)
{
ValueCount = valueCount;
BitsPerPixel = bitsPerPixel;
}
}
public static readonly Dictionary<PixelFormat, PixelFormatData> PixelFormats = new Dictionary<PixelFormat, PixelFormatData>
{
{ PixelFormat.Format24bppRgb, new PixelFormatData(3, 24) },
{ PixelFormat.Format48bppRgb, new PixelFormatData(3, 48) }
};
public static IEnumerable<byte[]> GetBytes(Bitmap image, int bytesPerPixel)
{
var imageData = image.LockBits(new Rectangle(Point.Empty, image.Size), ImageLockMode.ReadOnly, image.PixelFormat);
var ptr = imageData.Scan0;
var imageSize = image.Width * image.Height;
for (int x = 0; x < imageSize; x++)
{
yield return ptr.CopyAndMove(bytesPerPixel);
}
image.UnlockBits(imageData);
}
public static IEnumerable<int> GetValues(Bitmap image, int valueIndex)
{
if (!PixelFormats.ContainsKey(image.PixelFormat))
throw new ArgumentException(nameof(image.PixelFormat));
var pixelFormatData = PixelFormats[image.PixelFormat];
if (valueIndex < 0 || valueIndex >= pixelFormatData.ValueCount)
throw new ArgumentException(nameof(valueIndex));
int bytesPerPixel = pixelFormatData.BitsPerPixel / 8,
bytesPerValue = bytesPerPixel / pixelFormatData.ValueCount;
return GetBytes(image, bytesPerPixel)
.Select(bytes =>
bytes.Skip(bytesPerValue * valueIndex)
.Take(bytesPerValue)
.RightPad(4))
.Select(valueData => BitConverter.ToInt32(valueData.ToArray(), 0));
}
Those two extension methods are required to use the code.
public static class EnumerableExtensions
{
public static List<T> RightPad<T>(this IEnumerable<T> collection, int total)
{
var list = collection.ToList();
while (list.Count < 8)
list.Add(default(T));
return list;
}
}
public static class IntPtrExtensions
{
public static byte[] CopyAndMove(this IntPtr ptr, int count)
{
byte[] bytes = new byte[count];
Marshal.Copy(ptr, bytes, 0, count);
ptr += count;
return bytes;
}
}
And this is how it is used.
using (var file = new FileStream(#"C:\mypath\myPicture.png", FileMode.Open))
{
Bitmap image = new Bitmap(file);
// the color is saved in the followig format (gbr) so the
// red color is index 2
Console.WriteLine(GetValues(image, 2).Max());
}
I've tested it with an Format24bppRgb image.
If the bits per pixel are 8 and bellow you can also use GetPixel to check for every pixel. It is just about 3 times slower then the method above.
byte highestRed = 0;
using (var file = new FileStream(#"C:\mypath\mypicture.jpg", FileMode.Open))
{
Bitmap image = new Bitmap(file);
for (int x = 0; x < image.Width; x++)
{
for (int y = 0; y < image.Height; y++)
{
var color = image.GetPixel(x, y);
if(highestRed < color.R)
highestRed = color.R;
}
}
}

Related

setPixel triggering error: InvalidOperationException: 'SetPixel is not supported for images with indexed pixel formats.'

For use in a really basic steganography tool.
I am trying to change the blue value of each pixel to the ASCII value of each character in a message.
Bitmap bmp = new Bitmap(routeBox.Text);
for (int i = 0; i<bmp.Width; i++)
{
for (int j = 0; j<bmp.Height; j++)
{
Color pixelCol = bmp.GetPixel(i, j);
if (i< 1 && j<textToEmbed.TextLength)
{
char letter = Convert.ToChar(textToEmbed.Text.Substring(j, 1));
int value = Convert.ToInt32(letter);
bmp.SetPixel(i, j, Color.FromArgb(pixelCol.R, pixelCol.G, value));
}
}
}
This works on a jpeg but the blue value comes back as a decrementing number starting around 56 so I'm now trying to do it with a .bmp.
The error is triggered on this line:
bmp.SetPixel(i, j, Color.FromArgb(pixelCol.R, pixelCol.G, value));
To retrieve the message back from the pixels after saving I am doing:
Bitmap bmp = new Bitmap(routeBox.Text);
string message = "";
for (int i = 0; i<bmp.Width; i++)
{
for (int j = 0; j<bmp.Height; j++)
{
Color pixelCol = bmp.GetPixel(i, j);
if (i< 1 && j< 25)
{
int value = pixelCol.B;
char c = Convert.ToChar(value);
string letter = System.Text.Encoding.ASCII.GetString(
new byte[] { Convert.ToByte(c) });
message = message + letter;
}
}
}
The error you’re getting means you can’t call Bitmap.SetPixel() when the image has a PixelFormat of Format8bppIndexed.
One solution is to use a copy of the image converted to 24-bit instead of 8-bit.
This means instead of this line:
Bitmap bmp = new Bitmap(routeBoxText);
Use these 2 lines:
Bitmap bmpOrig = new Bitmap(routeBoxText);
Bitmap bmp = bmpOrig.Clone(new Rectangle(0, 0, bmpOrig.Width, bmpOrig.Height), System.Drawing.Imaging.PixelFormat.Format24bppRgb);
For a bit more information about pixel formats and bits-per-pixel, see this page.

C# - Fastest way of Interpolating a large byte array (RGB to RGBA)

I am uploading frames from a camera to a texture on the GPU for processing (using SharpDX). My issue is ATM is that I have the frames coming in as 24bit RGB, but DX11 no longer has the 24bit RGB texture format, only 32bit RGBA. After each 3 bytes I need to add another byte with the value of 255 (no transparency). I've tried this method of iterating thru the byte array to add it but it's too expensive. Using GDI bitmaps to convert is also very expensive.
int count = 0;
for (int i = 0; i < frameDataBGRA.Length - 3; i+=4)
{
frameDataBGRA[i] = frameData[i - count];
frameDataBGRA[i + 1] = frameData[(i + 1) - count];
frameDataBGRA[i + 2] = frameData[(i + 2) - count];
frameDataBGRA[i + 3] = 255;
count++;
}
Assuming you can compile with unsafe, using pointers in that case will give you significant boost.
First create two structs to hold data in a packed way:
[StructLayout(LayoutKind.Sequential)]
public struct RGBA
{
public byte r;
public byte g;
public byte b;
public byte a;
}
[StructLayout(LayoutKind.Sequential)]
public struct RGB
{
public byte r;
public byte g;
public byte b;
}
First version :
static void Process_Pointer_PerChannel(int pixelCount, byte[] rgbData, byte[] rgbaData)
{
fixed (byte* rgbPtr = &rgbData[0])
{
fixed (byte* rgbaPtr = &rgbaData[0])
{
RGB* rgb = (RGB*)rgbPtr;
RGBA* rgba = (RGBA*)rgbaPtr;
for (int i = 0; i < pixelCount; i++)
{
rgba->r = rgb->r;
rgba->g = rgb->g;
rgba->b = rgb->b;
rgba->a = 255;
rgb++;
rgba++;
}
}
}
}
This avoids a lot of indexing, and passes data directly.
Another version which is slightly faster, to box directly:
static void Process_Pointer_Cast(int pixelCount, byte[] rgbData, byte[] rgbaData)
{
fixed (byte* rgbPtr = &rgbData[0])
{
fixed (byte* rgbaPtr = &rgbaData[0])
{
RGB* rgb = (RGB*)rgbPtr;
RGBA* rgba = (RGBA*)rgbaPtr;
for (int i = 0; i < pixelCount; i++)
{
RGB* cp = (RGB*)rgba;
*cp = *rgb;
rgba->a = 255;
rgb++;
rgba++;
}
}
}
}
One small extra optimization (which is marginal), if you keep the same array all the time and reuse it, you can initialize it once with alpha set to 255 eg :
static void InitRGBA_Alpha(int pixelCount, byte[] rgbaData)
{
for (int i = 0; i < pixelCount; i++)
{
rgbaData[i * 4 + 3] = 255;
}
}
Then as you will never change this channel, other functions do not need to write into it anymore:
static void Process_Pointer_Cast_NoAlpha (int pixelCount, byte[] rgbData, byte[] rgbaData)
{
fixed (byte* rgbPtr = &rgbData[0])
{
fixed (byte* rgbaPtr = &rgbaData[0])
{
RGB* rgb = (RGB*)rgbPtr;
RGBA* rgba = (RGBA*)rgbaPtr;
for (int i = 0; i < pixelCount; i++)
{
RGB* cp = (RGB*)rgba;
*cp = *rgb;
rgb++;
rgba++;
}
}
}
}
In my test (running a 1920*1080 image, 100 iterations), I get (i7, x64 release build, average running time)
Your version : 6.81ms
Process_Pointer_PerChannel : 4.3ms
Process_Pointer_Cast : 3.8ms
Process_Pointer_Cast_NoAlpha : 3.5ms
Please note that of course all those functions can as well be easily chunked and parts run in multi threaded versions.
If you need higher performance, you have two options ( a bit out of scope from the question)
upload your image in a byte address buffer (as rgb), and perform the conversion to texture in a compute shader. That involves some bit shifting and a bit of fiddling with formats, but is reasonably straightforward to achieve.
Generally camera images come in Yuv format (with u and v downsampled), so it's mush faster to upload image in that color space and perform conversion to rgba either in pixel shader or compute shader. If your camera sdk allows to get pixel data in that native format, that's the way to go.
#catflier: good work, but it can go a little faster. ;-)
Reproduced times on my hardware:
Base version: 5.48ms
Process_Pointer_PerChannel: 2.84ms
Process_Pointer_Cast: 2.16ms
Process_Pointer_Cast_NoAlpha: 1.60ms
My experiments:
FastConvert: 1.45ms
FastConvert4: 1.13ms (here: count of pixels must be divisible by 4, but is usually no problem)
Things that have improved speed:
your RGB structure must always read 3 single bytes per pixel, but it is faster to read a whole uint (4 bytes) and simply ignore the last byte
the alpha value can then be added directly to a uint bit calculation
modern processors can often address fixed pointers with offset positions faster than pointers that are incremented themselves.
the offset variables in x64 mode should also directly use a 64-bit data value (long instead of int), which reduces the overhead of the accesses
the partial rolling out of the inner loop increases some performance again
The Code:
static void FastConvert(int pixelCount, byte[] rgbData, byte[] rgbaData)
{
fixed (byte* rgbP = &rgbData[0], rgbaP = &rgbaData[0])
{
for (long i = 0, offsetRgb = 0; i < pixelCount; i++, offsetRgb += 3)
{
((uint*)rgbaP)[i] = *(uint*)(rgbP + offsetRgb) | 0xff000000;
}
}
}
static void FastConvert4Loop(long pixelCount, byte* rgbP, byte* rgbaP)
{
for (long i = 0, offsetRgb = 0; i < pixelCount; i += 4, offsetRgb += 12)
{
uint c1 = *(uint*)(rgbP + offsetRgb);
uint c2 = *(uint*)(rgbP + offsetRgb + 3);
uint c3 = *(uint*)(rgbP + offsetRgb + 6);
uint c4 = *(uint*)(rgbP + offsetRgb + 9);
((uint*)rgbaP)[i] = c1 | 0xff000000;
((uint*)rgbaP)[i + 1] = c2 | 0xff000000;
((uint*)rgbaP)[i + 2] = c3 | 0xff000000;
((uint*)rgbaP)[i + 3] = c4 | 0xff000000;
}
}
static void FastConvert4(int pixelCount, byte[] rgbData, byte[] rgbaData)
{
if ((pixelCount & 3) != 0) throw new ArgumentException();
fixed (byte* rgbP = &rgbData[0], rgbaP = &rgbaData[0])
{
FastConvert4Loop(pixelCount, rgbP, rgbaP);
}
}

Convert RGB array to image in C#

I know the rgb value of every pixel, and how can I create the picture by these values in C#? I've seen some examples like this:
public Bitmap GetDataPicture(int w, int h, byte[] data)
{
Bitmap pic = new Bitmap(this.width, this.height, System.Drawing.Imaging.PixelFormat.Format32bppArgb);
Color c;
for (int i = 0; i < data.length; i++)
{
c = Color.FromArgb(data[i]);
pic.SetPixel(i%w, i/w, c);
}
return pic;
}
But it does not works.
I have a two-dimensional array like this:
1 3 1 2 4 1 3 ...2 3 4 2 4 1 3 ...4 3 1 2 4 1 3 ......
Each number correspond to a rgb value, for example, 1 => {244,166,89}
2=>{54,68,125}.
I'd try the following code, which uses an array of 256 Color entries for the palette (you have to create and fill this in advance):
public Bitmap GetDataPicture(int w, int h, byte[] data)
{
Bitmap pic = new Bitmap(w, h, System.Drawing.Imaging.PixelFormat.Format32bppArgb);
for (int x = 0; x < w; x++)
{
for (int y = 0; y < h; y++)
{
int arrayIndex = y * w + x;
Color c = Color.FromArgb(
data[arrayIndex],
data[arrayIndex + 1],
data[arrayIndex + 2],
data[arrayIndex + 3]
);
pic.SetPixel(x, y, c);
}
}
return pic;
}
I tend to iterate over the pixels, not the array, as I find it easier to read the double loop than the single loop and the modulo/division operation.
Your solution very near to working code. Just you need the "palette" - i.e. set of 3-elements byte array, where each 3-bytes element contains {R, G, B} values.
//palette is a 256x3 table
public static Bitmap GetPictureFromData(int w, int h, byte[] data, byte[][] palette)
{
Bitmap pic = new Bitmap(w, h, System.Drawing.Imaging.PixelFormat.Format32bppArgb);
Color c;
for (int i = 0; i < data.Length; i++)
{
byte[] color_bytes = palette[data[i]];
c = Color.FromArgb(color_bytes[0], color_bytes[1], color_bytes[2]);
pic.SetPixel(i % w, i / w, c);
}
return pic;
}
This code works for me, but it very slow.
If you create in-memory "image" of BMP-file and then use Image.FromStream(MemoryStream("image")), it code will be more faster, but it more complex solution.

OpenCvSharp PCA Exception: Unsupported combination of input and output array formats

I'm using OpenCVSharp (OpenCvSharp3-AnyCPU Version 3.0.0.20150823 running in Visual Studio 2015 and installed via NuGet) to access OpenCV from C#, but when calling Cv2.PCACompute I get a generic OpenCVException stating that I have an Unsupported combination of input and output array formats.
My goal is to use PCA to find the primary axis of a pixel blob. This is my (stripped down) code at present:
using OpenCvSharp;
public struct point2D
{
public int X;
public int Y;
public point2D(int X, int Y)
{
this.X = X;
this.Y = Y;
}
}
public static void PCA2D()
{
int height = 5;
int width = 5;
int[] image = new int[]
{
0,0,0,0,1,
0,0,0,1,0,
0,0,1,0,0,
0,1,0,0,0,
1,0,0,0,0,
}
// extract the datapoints
List<point2D> dataPoints = new List<point2D>();
for (int row = 0; row < height; ++row)
{
for (int col = 0; col < width; ++col)
{
if (image[row * width + col] == 1)
{
dataPoints.Add(new point2D(col, row));
}
}
}
// create the input matrix
Mat input = new Mat(dataPoints.Length, 2, MatType.CV_32SC1);
for (int i = 0; i < dataPoints.Length; ++i)
{
input.Set(i, 0, dataPoints[i].X);
input.Set(i, 1, dataPoints[i].Y);
}
Mat mean = new Mat();
Mat eigenvectors = new Mat();
// OpenCVException occurs here: unsupported combination of input and output array formats
Cv2.PCACompute(input, mean, eigenvectors);
// Code to get orientation from the eigenvectors
}
I haven't been able to find any documentation on how to initialise the mean and eigenvector Mats, or if the way I'm calling PCACompute is even correct. Some insight into the correct procedure for using PCACompute would be immensely helpful.
So it turns out that dataPoints cannot be MatType.CV_32SC1. Changing the code to the following allowed it to work:
// create the input matrix
Mat input = new Mat(dataPoints.Length, 2, MatType.CV_32FC1);
for (int i = 0; i < dataPoints.Length; ++i)
{
input.Set(i, 0, (float)dataPoints[i].X);
input.Set(i, 1, (float)dataPoints[i].Y);
}

32-bit Grayscale Tiff with floating point pixel values to array using LibTIFF.NET C#

I just started using LibTIFF.NET in my c# application to read Tiff images as heightmaps obtained from ArcGIS servers. All I need is to populate an array with image's pixel values for terrain generation based on smooth gradients. The image is a LZW compressed 32-bit Grayscale Tiff with floating point pixel values representing elevaion in meters.
It's been some days now that I struggle to return right values but all I get is just "0" values assuming it's a total black or white image!
Here's the code so far: (Updated - Read Update 1)
using (Tiff inputImage = Tiff.Open(fileName, "r"))
{
int width = inputImage.GetField(TiffTag.IMAGEWIDTH)[0].ToInt();
int height = inputImage.GetField(TiffTag.IMAGELENGTH)[0].ToInt();
int bytesPerPixel = 4;
int count = (int)inputImage.RawTileSize(0); //Has to be: "width * height * bytesPerPixel" ?
int resolution = (int)Math.Sqrt(count);
byte[] inputImageData = new byte[count]; //Has to be: byte[] inputImageData = new byte[width * height * bytesPerPixel];
int offset = 0;
for (int i = 0; i < inputImage.NumberOfTiles(); i++)
{
offset += inputImage.ReadEncodedTile(i, inputImageData, offset, (int)inputImage.RawTileSize(i));
}
float[,] outputImageData = new float[resolution, resolution]; //Has to be: float[,] outputImageData = new float[width * height];
int length = inputImageData.Length;
Buffer.BlockCopy(inputImageData, 0, outputImageData, 0, length);
using (StreamWriter sr = new StreamWriter(fileName.Replace(".tif", ".txt"))) {
string row = "";
for(int i = 0; i < resolution; i++) { //Change "resolution" to "width" in order to have correct array size
for(int j = 0; j < resolution; j++) { //Change "resolution" to "height" in order to have correct array size
row += outputImageData[i, j] + " ";
}
sr.Write(row.Remove(row.Length - 1) + Environment.NewLine);
row = "";
}
}
}
Sample Files & Results: http://terraunity.com/SampleElevationTiff_Results.zip
Already searched everywhere on internet and couldn't find the solution for this specific issue. So I really appreciate the help which makes it useful for others too.
Update 1:
Changed the code based on Antti Leppänen's answer but got weird results which seems to be a bug or am I missing something? Please see uploaded zip file to see the results with new 32x32 tiff images here:
http://terraunity.com/SampleElevationTiff_Results.zip
Results:
LZW Compressed: RawStripSize = ArraySize = 3081 = 55x55 grid
Unompressed: RawStripSize = ArraySize = 65536 = 256x256 grid
Has to be: RawStripSize = ArraySize = 4096 = 32x32 grid
As you see the results, LibTIFF skips some rows and gives irrelevant orderings and it even gets worse if the image size is not power of 2!
Your example file seems to be tiled tiff and not stripped. Console says:
ElevationMap.tif: Can not read scanlines from a tiled image
I changed your code to read tiles. This way it seems to be reading data.
for (int i = 0; i < inputImage.NumberOfTiles(); i++)
{
offset += inputImage.ReadEncodedTile(i, inputImageData, offset, (int)inputImage.RawTileSize(i));
}
I know it could be late, but I had the same mistake recently and I found the solution, so it could be helpful. The mistake is in the parameter count of the function Tiff.ReadEncodedTile(tile, buffer, offset, count). It must be the decompressed bytes size, not the compressed bytes size. That's the reason why you have not all the information, because you are not saving the whole data in your buffer. See how-to-translate-tiff-readencodedtile-to-elevation-terrain-matrix-from-height.
A fast method to read a floating point tiff.
public static unsafe float[,] ReadTiff(Tiff image)
{
const int pixelStride = 4; // bytes per pixel
int imageWidth = image.GetField(TiffTag.IMAGEWIDTH)[0].ToInt();
int imageHeight = image.GetField(TiffTag.IMAGELENGTH)[0].ToInt();
float[,] result = new float[imageWidth, imageHeight];
int tileCount = image.NumberOfTiles();
int tileWidth = image.GetField(TiffTag.TILEWIDTH)[0].ToInt();
int tileHeight = image.GetField(TiffTag.TILELENGTH)[0].ToInt();
int tileStride = (imageWidth + tileWidth - 1) / tileWidth;
int bufferSize = tileWidth * tileHeight * pixelStride;
byte[] buffer = new byte[bufferSize];
fixed (byte* bufferPtr = buffer)
{
float* array = (float*)bufferPtr;
for (int t = 0; t < tileCount; t++)
{
image.ReadEncodedTile(t, buffer, 0, buffer.Length);
int x = tileWidth * (t % tileStride);
int y = tileHeight * (t / tileStride);
var copyWidth = Math.Min(tileWidth, imageWidth - x);
var copyHeight = Math.Min(tileHeight, imageHeight - y);
for (int j = 0; j < copyHeight; j++)
{
for (int i = 0; i < copyWidth; i++)
{
result[x + i, y + j] = array[j * tileWidth + i];
}
}
}
}
return result;
}

Categories

Resources