Fast image conversion to 8 bit palette format in memory - c#

I'm writing an application that has critical requirements for fast image processing. There will be a huge amount of images. I need to save them in fast-processible format. So, I decided to save colorful mipmaps with 8-bit color depth (using palette) and then save it as a byte array. So, I need the way to convert formats very fast and in-memory. Also, it must be cross-platform as far as possible (i write code on Windows and will deploy to AWS).
I have tried ImageSharp and .NET Core System.Drawing.Common packages. In the case of ImageSharp, I got almost an acceptable result: the quality of the resized image is perfect, but it works really slowly... I got 12 seconds for 4096x4096 24-bit full palette image. There are 256 distinct colors in the resulting palette, that is perfect for me.
In the case of System.Drawing.Common library I got much faster conversion speed:
900 ms for tiff format
2500 ms for gif format.
But, the number of distinct colors in the resulting palette:
for tiff: 224 (not so good for my solution)
for gif: 252 (good)
Also, the resulting image in tiff format has very poor quality, so probably it can't be used (maybe I am wrong).
System.Drawing.Common implementation:
var parameters = new EncoderParameters(1);
parameters.Param[0] = new EncoderParameter(Encoder.ColorDepth, 8L);
ImageCodecInfo bmpEncoder = ImageCodecInfo.GetImageEncoders()
.FirstOrDefault(x => x.FormatID == ImageFormat.Gif.Guid);
Stopwatch sw = Stopwatch.StartNew();
var allColors = new Bitmap(#"16777216colors.png");
Console.WriteLine("Loaded: " + sw.ElapsedMilliseconds); // 462
using (MemoryStream ms = new MemoryStream())
{
allColors.Save(ms, bmpEncoder, parameters);
Console.WriteLine("Saved: " + sw.ElapsedMilliseconds); // 2104
ms.Position = 0;
Bitmap bitmap = new Bitmap(ms);
Console.WriteLine("Loaded: " + sw.ElapsedMilliseconds); // 2383
// set to determine palette size
HashSet<int> set = new HashSet<int>();
// Temporary solution. It is needed to replace GetPixel to faster analog
for (int i = 0; i < bitmap.Width; i++)
{
for (int j = 0; j < bitmap.Height; j++)
{
var val = bitmap.GetPixel(i, j).ToArgb();
set.Add(val);
}
}
Console.WriteLine("Palette size: " + set.Count); // 252
Console.WriteLine("All done: " + sw.ElapsedMilliseconds); // 26345
}
/*
Output:
Loaded: 462
Saved: 2104
Loaded: 2383
Palette size: 252
All done: 26345
*/
ImageSharp implementation:
PngEncoder encoder = new PngEncoder()
{
ColorType = PngColorType.Palette,
BitDepth = PngBitDepth.Bit8,
CompressionLevel = 1
};
Image<Rgba32> img;
Stopwatch sw = Stopwatch.StartNew();
using (var file = File.OpenRead(#"16777216colors.png"))
{
img = Image.Load(file);
}
Console.WriteLine("Loaded: " + sw.ElapsedMilliseconds); // 1302
using (MemoryStream ms = new MemoryStream())
{
img.Save(ms, encoder);
Console.WriteLine("Saved: " + sw.ElapsedMilliseconds); // 12608
ms.Position = 0;
img = Image.Load(ms);
Console.WriteLine("Loaded: " + sw.ElapsedMilliseconds); // 12813
}
// set to determine palette size
HashSet<uint> set = new HashSet<uint>();
for (int i = 0; i < img.Width; i++)
{
for (int j = 0; j < img.Height; j++)
{
var val = img[i, j].PackedValue;
set.Add(val);
}
}
Console.WriteLine("Palette size: " + set.Count); // 256
Console.WriteLine("All done: " + sw.ElapsedMilliseconds); // 15807
/*
Output:
Loaded: 1302
Saved: 12608
Loaded: 12813
Palette size: 256
All done: 15807
*/
So, the best way seems to be to use System.Drawing.Common implementation, but there is probably will be overhead related to slow pixel reading.
So, questions:
I expected high performance form ImageSharp, so I guess I doing
something wrong... It would be cool to know how to improve the performance! If anybody knows what I doing wrong please tell me.
Does it guaranteed that palette colors will be the
same for any image, so, I will be able to cache any-to-any color difference between these 256 colors?
Does anybody know the best way to solve my problem in other ways? I'll be glad to see suggestions. The solution must be cross-platform and fast as it possible up to byte array data representation

Try to use Accord for it. Its hard to say if it is fast. You Need to try it in your scenerio.
You want to have 8bpp image? So you want it to be GreyScale? Then, use this function from using Accord.Imaging.Filters;.
private Bitmap Create8bppGreyscaleImage(Bitmap bitmap)
=> Grayscale.CommonAlgorithms.BT709.Apply(bitmap);
This convertion on This jpg image take 500ms. Produced image look like this:
As you can see, Grayscale.CommonAlghoritms.BT709 takes Bitmap as argument. If you want to byte array, just use Marshall.Copy:
BitmapData bitmapData = this.Bitmap.LockBits(
new Rectangle(0, 0, this.Bitmap.Width, this.Bitmap.Height),
ImageLockMode.ReadWrite,
this.Bitmap.PixelFormat);
int pixels = bitmapData.Stride * this.Bitmap.Height;
byte[] bytes = new byte[pixels];
Marshal.Copy(bitmapData.Scan0, bytes, 0, pixels);
this.Bitmap.UnlockBits(bitmapData);
this.Bytes = bytes;
Loading image as 8bpp byte array by this method (image above) take 18ms.
It will save byte array to memory. Then, you can unlock bits to your bitmap and save it for example as .png:
BitmapData bitmapData = this.Bitmap.LockBits(
new Rectangle(0, 0, this.Bitmap.Width, this.Bitmap.Height),
ImageLockMode.ReadWrite,
this.Bitmap.PixelFormat);
int stride = bitmapData.Stride * this.Height;
Marshal.Copy(bytes, 0, bitmapData.Scan0, stride);
this.Bitmap.UnlockBits(bitmapData);
this.Bitmap.Save("test.png");
Tells me if my answer was helpful to you. You dont have reference to System.Drawing.dll out of the box in .NET Core, you need to add it on yourself

Related

Image from byte array creating bad image when using Marshalling [duplicate]

I searched all question about byte array but i always failed. I have never coded c# i am new in this side. Could you help me how to make image file from byte array.
Here is my function which stores byte in array named imageData
public void imageReady( byte[] imageData, int fWidth, int fHeight))
You'll need to get those bytes into a MemoryStream:
Bitmap bmp;
using (var ms = new MemoryStream(imageData))
{
bmp = new Bitmap(ms);
}
That uses the Bitmap(Stream stream) constructor overload.
UPDATE: keep in mind that according to the documentation, and the source code I've been reading through, an ArgumentException will be thrown on these conditions:
stream does not contain image data or is null.
-or-
stream contains a PNG image file with a single dimension greater than 65,535 pixels.
Guys thank you for your help. I think all of this answers works. However i think my byte array contains raw bytes. That's why all of those solutions didnt work for my code.
However i found a solution. Maybe this solution helps other coders who have problem like mine.
static byte[] PadLines(byte[] bytes, int rows, int columns) {
int currentStride = columns; // 3
int newStride = columns; // 4
byte[] newBytes = new byte[newStride * rows];
for (int i = 0; i < rows; i++)
Buffer.BlockCopy(bytes, currentStride * i, newBytes, newStride * i, currentStride);
return newBytes;
}
int columns = imageWidth;
int rows = imageHeight;
int stride = columns;
byte[] newbytes = PadLines(imageData, rows, columns);
Bitmap im = new Bitmap(columns, rows, stride,
PixelFormat.Format8bppIndexed,
Marshal.UnsafeAddrOfPinnedArrayElement(newbytes, 0));
im.Save("C:\\Users\\musa\\Documents\\Hobby\\image21.bmp");
This solutions works for 8bit 256 bpp (Format8bppIndexed). If your image has another format you should change PixelFormat .
And there is a problem with colors right now. As soon as i solved this one i will edit my answer for other users.
*PS = I am not sure about stride value but for 8bit it should be equal to columns.
And also this function Works for me.. This function copies 8 bit greyscale image into a 32bit layout.
public void SaveBitmap(string fileName, int width, int height, byte[] imageData)
{
byte[] data = new byte[width * height * 4];
int o = 0;
for (int i = 0; i < width * height; i++)
{
byte value = imageData[i];
data[o++] = value;
data[o++] = value;
data[o++] = value;
data[o++] = 0;
}
unsafe
{
fixed (byte* ptr = data)
{
using (Bitmap image = new Bitmap(width, height, width * 4,
PixelFormat.Format32bppRgb, new IntPtr(ptr)))
{
image.Save(Path.ChangeExtension(fileName, ".jpg"));
}
}
}
}
Can be as easy as:
var ms = new MemoryStream(imageData);
System.Drawing.Image image = Image.FromStream(ms);
image.Save("c:\\image.jpg");
Testing it out:
byte[] imageData;
// Create the byte array.
var originalImage = Image.FromFile(#"C:\original.jpg");
using (var ms = new MemoryStream())
{
originalImage.Save(ms, ImageFormat.Jpeg);
imageData = ms.ToArray();
}
// Convert back to image.
using (var ms = new MemoryStream(imageData))
{
Image image = Image.FromStream(ms);
image.Save(#"C:\newImage.jpg");
}
In addition, you can simply convert byte array to Bitmap.
var bmp = new Bitmap(new MemoryStream(imgByte));
You can also get Bitmap from file Path directly.
Bitmap bmp = new Bitmap(Image.FromFile(filePath));
This was helpful to me: https://www.tek-tips.com/viewthread.cfm?qid=1264492 (Reference answer)
I understand the question as follows:
I have a byte array that contains pixel data e.g. in RGB format (24bit/pixel)
From this raw pixel data I want to create a Bitmap
This code worked for me:
int width = ...;
int height = ...;
byte[] pixelArray = new byte[] {
// Creation of the actual data is not in the scope of this answer
};
Bitmap bmp = new Bitmap(width, height, System.Drawing.Imaging.PixelFormat.Format32bppRgb);
// Create a BitmapData and lock all pixels to be written
BitmapData bmpData = bmp.LockBits(
new Rectangle(0, 0, bmp.Width, bmp.Height),
ImageLockMode.WriteOnly, bmp.PixelFormat);
// Copy the data from the byte array into BitmapData.Scan0
Marshal.Copy(pixelArray, 0, bmpData.Scan0, pixelArray.Length);
// Unlock the pixels
bmp.UnlockBits(bmpData);
// Do something with your image, e.g. save it to disc
bmp.Save("c:\\temp\\mybmp.bmp", ImageFormat.Bmp);
Based on the accepted answer the OP wanted to interpret imageData byte array as the pixel buffer, rather than an already encoded bitmap stream as the most upvoted answer suggests. And though it works, it contains a lot of copies, as well as palette issues ("And there is a problem with colors right now").
I actually happen to have a drawing library exactly for this purpose (among others). The platform-independent core library allows you to interpret any array of primitive types as a bitmap data:
// Unlike in the accepted answer, no extra buffer allocation or
// array copy happens in the background. Note that we can specify
// a palette for the indexed format so the colors will be interpreted correctly
using var myBitmap = BitmapDataFactory.CreateBitmapData(imageData, new Size(fWidth, fHeight),
stride: fWidth, // stride is same as width because of the 8bpp pixel format
pixelFormat: KnownPixelFormat.Format8bppIndexed,
palette: Palette.Grayscale256());
myBitmap is now an IReadWriteBitmapData instance, allowing a lot of operations (just see the available extension methods). It also offers a pretty fast SetPixel method, which respects the palette so in this particular case it turns any color to grayscale. But if you know the actual pixel format you can also can use the WriteRaw<T> method to access the pixels directly.
And if you use the technology-specific packages such as the one for GDI+ or WPF, then you can simply convert your buffer into known bitmap types such as System.Drawing.Bitmap or System.Windows.Media.WriteableBitmap:
// the accepted answer creates two bitmaps due to the color problems where
// the 2nd one is a 32 bpp image. This solution is much faster, simpler, it avoids
// unnecessary allocations and uses parallel processing internally if possible
var systemBitmap = myBitmap.ToBitmap(); // or ToBitmapAsync, ToWriteableBitmap, etc.

What byte[] must have in order to be "savable" into bitmap? [duplicate]

I searched all question about byte array but i always failed. I have never coded c# i am new in this side. Could you help me how to make image file from byte array.
Here is my function which stores byte in array named imageData
public void imageReady( byte[] imageData, int fWidth, int fHeight))
You'll need to get those bytes into a MemoryStream:
Bitmap bmp;
using (var ms = new MemoryStream(imageData))
{
bmp = new Bitmap(ms);
}
That uses the Bitmap(Stream stream) constructor overload.
UPDATE: keep in mind that according to the documentation, and the source code I've been reading through, an ArgumentException will be thrown on these conditions:
stream does not contain image data or is null.
-or-
stream contains a PNG image file with a single dimension greater than 65,535 pixels.
Guys thank you for your help. I think all of this answers works. However i think my byte array contains raw bytes. That's why all of those solutions didnt work for my code.
However i found a solution. Maybe this solution helps other coders who have problem like mine.
static byte[] PadLines(byte[] bytes, int rows, int columns) {
int currentStride = columns; // 3
int newStride = columns; // 4
byte[] newBytes = new byte[newStride * rows];
for (int i = 0; i < rows; i++)
Buffer.BlockCopy(bytes, currentStride * i, newBytes, newStride * i, currentStride);
return newBytes;
}
int columns = imageWidth;
int rows = imageHeight;
int stride = columns;
byte[] newbytes = PadLines(imageData, rows, columns);
Bitmap im = new Bitmap(columns, rows, stride,
PixelFormat.Format8bppIndexed,
Marshal.UnsafeAddrOfPinnedArrayElement(newbytes, 0));
im.Save("C:\\Users\\musa\\Documents\\Hobby\\image21.bmp");
This solutions works for 8bit 256 bpp (Format8bppIndexed). If your image has another format you should change PixelFormat .
And there is a problem with colors right now. As soon as i solved this one i will edit my answer for other users.
*PS = I am not sure about stride value but for 8bit it should be equal to columns.
And also this function Works for me.. This function copies 8 bit greyscale image into a 32bit layout.
public void SaveBitmap(string fileName, int width, int height, byte[] imageData)
{
byte[] data = new byte[width * height * 4];
int o = 0;
for (int i = 0; i < width * height; i++)
{
byte value = imageData[i];
data[o++] = value;
data[o++] = value;
data[o++] = value;
data[o++] = 0;
}
unsafe
{
fixed (byte* ptr = data)
{
using (Bitmap image = new Bitmap(width, height, width * 4,
PixelFormat.Format32bppRgb, new IntPtr(ptr)))
{
image.Save(Path.ChangeExtension(fileName, ".jpg"));
}
}
}
}
Can be as easy as:
var ms = new MemoryStream(imageData);
System.Drawing.Image image = Image.FromStream(ms);
image.Save("c:\\image.jpg");
Testing it out:
byte[] imageData;
// Create the byte array.
var originalImage = Image.FromFile(#"C:\original.jpg");
using (var ms = new MemoryStream())
{
originalImage.Save(ms, ImageFormat.Jpeg);
imageData = ms.ToArray();
}
// Convert back to image.
using (var ms = new MemoryStream(imageData))
{
Image image = Image.FromStream(ms);
image.Save(#"C:\newImage.jpg");
}
In addition, you can simply convert byte array to Bitmap.
var bmp = new Bitmap(new MemoryStream(imgByte));
You can also get Bitmap from file Path directly.
Bitmap bmp = new Bitmap(Image.FromFile(filePath));
This was helpful to me: https://www.tek-tips.com/viewthread.cfm?qid=1264492 (Reference answer)
I understand the question as follows:
I have a byte array that contains pixel data e.g. in RGB format (24bit/pixel)
From this raw pixel data I want to create a Bitmap
This code worked for me:
int width = ...;
int height = ...;
byte[] pixelArray = new byte[] {
// Creation of the actual data is not in the scope of this answer
};
Bitmap bmp = new Bitmap(width, height, System.Drawing.Imaging.PixelFormat.Format32bppRgb);
// Create a BitmapData and lock all pixels to be written
BitmapData bmpData = bmp.LockBits(
new Rectangle(0, 0, bmp.Width, bmp.Height),
ImageLockMode.WriteOnly, bmp.PixelFormat);
// Copy the data from the byte array into BitmapData.Scan0
Marshal.Copy(pixelArray, 0, bmpData.Scan0, pixelArray.Length);
// Unlock the pixels
bmp.UnlockBits(bmpData);
// Do something with your image, e.g. save it to disc
bmp.Save("c:\\temp\\mybmp.bmp", ImageFormat.Bmp);
Based on the accepted answer the OP wanted to interpret imageData byte array as the pixel buffer, rather than an already encoded bitmap stream as the most upvoted answer suggests. And though it works, it contains a lot of copies, as well as palette issues ("And there is a problem with colors right now").
I actually happen to have a drawing library exactly for this purpose (among others). The platform-independent core library allows you to interpret any array of primitive types as a bitmap data:
// Unlike in the accepted answer, no extra buffer allocation or
// array copy happens in the background. Note that we can specify
// a palette for the indexed format so the colors will be interpreted correctly
using var myBitmap = BitmapDataFactory.CreateBitmapData(imageData, new Size(fWidth, fHeight),
stride: fWidth, // stride is same as width because of the 8bpp pixel format
pixelFormat: KnownPixelFormat.Format8bppIndexed,
palette: Palette.Grayscale256());
myBitmap is now an IReadWriteBitmapData instance, allowing a lot of operations (just see the available extension methods). It also offers a pretty fast SetPixel method, which respects the palette so in this particular case it turns any color to grayscale. But if you know the actual pixel format you can also can use the WriteRaw<T> method to access the pixels directly.
And if you use the technology-specific packages such as the one for GDI+ or WPF, then you can simply convert your buffer into known bitmap types such as System.Drawing.Bitmap or System.Windows.Media.WriteableBitmap:
// the accepted answer creates two bitmaps due to the color problems where
// the 2nd one is a 32 bpp image. This solution is much faster, simpler, it avoids
// unnecessary allocations and uses parallel processing internally if possible
var systemBitmap = myBitmap.ToBitmap(); // or ToBitmapAsync, ToWriteableBitmap, etc.

Load 32-bit greyscale TIFF image in C#

I'm developing a small C# tool that must be able to load a TIFF image, crop the image to a certain size, and save it as a PNG file.
I have large greyscale TIFF images of about 28000x256 pixels with 32-bit bit depth. When I try to process the images with my tool, it just outputs a blank white image.
Also, when I try to open the original TIFF images (not the ones processed with my tool) with the Windows Photo Viewer, it also shows a blank white image. Some other applications, e.g. ImageJ, display the image correctly. What is the problem here?
My code to load the images looks as follows:
Image image = Bitmap.FromFile(path.LocalPath);
int width = image.Width;
int height = image.Height;
Bitmap bmp = new Bitmap(width, height);
Graphics g = Graphics.FromImage(bmp);
The problem is that C# (or better said the underlying API) can't handle Greyscale images with a Colordepth greater than 8bit.
I'd suggest using LibTiff.NET for Handling TIFF images.
When i faced such an problem, i loaded the TIFF image raw Data into an array
using (var inputImage = Tiff.Open(image, "r"))
{
width = inputImage.GetField(TiffTag.IMAGEWIDTH)[0].ToInt();
height = inputImage.GetField(TiffTag.IMAGELENGTH)[0].ToInt();
inputImageData = new byte[width * height * bytePerPixel];
var offset = 0;
for (int i = 0; i < inputImage.NumberOfStrips(); i++)
{
offset += inputImage.ReadRawStrip(i, inputImageData, offset, (int)inputImage.RawStripSize(i));
}
}
The bytes then have to be converted into an array of uint (in my case, imagedata was only 16 bit, so i used ushort) Remember to take care of Endianness of the data!
// has to be done by hand to ensure endiannes is kept correctly.
var outputImageData = new ushort[inputImageData.Length / 2];
for (var i = 0; i < outputImageData.Length; i++)
{
outputImageData[i] = (ushort)((inputImageData[i * 2 + 1]) + (ushort)(inputImageData[i * 2] << 8));
}
You can then manipulate the image using normal Array Operations. I'd suggest you to use normal Array operations and not Lambda-Expressions, as they are much faster. (in My Scenario 100s vs 2s Runtime)
Finally you can save the image using LibTiff again
using (var output = Tiff.Open(imageout, "w"))
{
output.SetField(TiffTag.IMAGEWIDTH, width);
output.SetField(TiffTag.IMAGELENGTH, height);
output.SetField(TiffTag.SAMPLESPERPIXEL, 1);
output.SetField(TiffTag.BITSPERSAMPLE, 16);
output.SetField(TiffTag.ROWSPERSTRIP, height);
output.SetField(TiffTag.PHOTOMETRIC, Photometric.MINISBLACK);
output.SetField(TiffTag.FILLORDER, FillOrder.MSB2LSB);
// Transform to Byte-Array
var buffer = new byte[outputImageData.Length * sizeof(ushort)];
Buffer.BlockCopy(outputImageData, 0, buffer, 0, buffer.Length);
// Write it to Image
output.WriteRawStrip(0, buffer, buffer.Length);
}

Importing JPEG 8BppIndexed image shows only 16 greyscale values C#

So here is my problem
I've used a scanner to scan an object in greyscale and convert it into a JPEG format to be analyzed by a C# program. The image's pixelformat is 8BppIndexed.
When I import this image into C# and draw a histogram of it, I only see 16 grayscale values, like this:
All the values in between these peaks are 0.
This is what the normal histogram should look like (don't mind the colors, this histogram is made with another tool):
The first histogram (int[]) is formed with this code:
public static int[] GetHistogram(Bitmap b)
{
int[] myHistogram = new int[256];
for (int i = 0; i < myHistogram.Length; i++)
myHistogram[i] = 0;
BitmapData bmData = null;
try
{
//Lock it fixed with 32bpp
bmData = b.LockBits(new Rectangle(0, 0, b.Width, b.Height), ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb);
int scanline = bmData.Stride;
System.IntPtr Scan0 = bmData.Scan0;
unsafe
{
byte* p = (byte*)(void*)Scan0;
int nWidth = b.Width;
int nHeight = b.Height;
for (int y = 0; y < nHeight; y++)
{
for (int x = 0; x < nWidth; x++)
{
long Temp = 0;
Temp += p[0]; // p[0] - blue, p[1] - green , p[2]-red
Temp += p[1];
Temp += p[2];
Temp = (int)Temp / 3;
myHistogram[Temp]++;
//we do not need to use any offset, we always can increment by pixelsize when
//locking in 32bppArgb - mode
p += 4;
}
}
}
b.UnlockBits(bmData);
}
catch
{
try
{
b.UnlockBits(bmData);
}
catch
{
}
}
return myHistogram;
}
To be sure this code is not the problem, I've tried using the AForge.Math.Histogram way and even a for - in - for loop to iterate through all pixels. Each time I get the same result.
Now here is the funny part(s):
When I draw the histogram with any other tool (used 3 others), I get
a normal histogram. This tells me that the information is within the image, but my code just can't get it out.
When I scan the exact same object and set the settings to export the image into a .bmp file, c# is able to draw a normal histogram
With another random .jpg image I found on my computer, c# is able to draw a normal
histogram.
These points tell me that there is probably something wrong with the way that I import the image into my code, so I tried different ways to import the image:
Bitmap bmp = (Bitmap)Bitmap.FromFile(path);
or
Bitmap bmp = AForge.Imaging.Image.FromFile(path);
or
Stream imageStreamSource = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read);
System.Windows.Media.Imaging.JpegBitmapDecoder decoder = new System.Windows.Media.Imaging.JpegBitmapDecoder(imageStreamSource, System.Windows.Media.Imaging.BitmapCreateOptions.PreservePixelFormat, System.Windows.Media.Imaging.BitmapCacheOption.Default);
System.Windows.Media.Imaging.BitmapSource bitmapSource = decoder.Frames[0];
System.Windows.Controls.Image image = new System.Windows.Controls.Image();
image.Source = bitmapSource;
image.Stretch = System.Windows.Media.Stretch.None;
MemoryStream ms = new MemoryStream();
var encoder = new System.Windows.Media.Imaging.BmpBitmapEncoder();
encoder.Frames.Add(System.Windows.Media.Imaging.BitmapFrame.Create(image.Source as System.Windows.Media.Imaging.BitmapSource));
encoder.Save(ms);
ms.Flush();
System.Drawing.Image myImage = System.Drawing.Image.FromStream(ms);
Bitmap bmp = (Bitmap)Bitmap.FromStream(ms);
None of which gave a different histogram than the one with just 16 results.
I can not use the .bmp extension in my scanner, because I need to make a great many images and one .bmp image is around 200mb (yea, the images need a high resolution), while the .jpg is only around 30mb. Plus I've already made many .jpg images that can not be remade because the objects that have been scanned no longer exist.
NOTE: I know that using the .jpg extension is a lossy way to compress the images. That is not the current issue.
This is what a histogram, created with the exact same code as the first one, looks like with another random .jpg image from my computer:
Does this sound familiar to anyone? I feel like I've tried everything. Is there another way to solve this problem that I have not yet found?
EDIT
I thought I had found an extremely dirty way to fix my problem, but it does change the histogram:
Bitmap temp = (Bitmap)Bitmap.FromFile(m_sourceImageFileName);
if (temp.PixelFormat == PixelFormat.Format8bppIndexed ||
temp.PixelFormat == PixelFormat.Format4bppIndexed ||
temp.PixelFormat == PixelFormat.Format1bppIndexed ||
temp.PixelFormat == PixelFormat.Indexed)
{
//Change pixelformat to a format that AForge can work with
Bitmap tmp = temp.Clone(new Rectangle(0, 0, temp.Width, temp.Height), PixelFormat.Format24bppRgb);
//This is a super dirty way to make sure the histogram shows more than 16 grey values.
for (int i = 0; true; i++)
{
if (!File.Exists(m_sourceImageFileName + i + ".jpg"))
{
tmp.Save(m_sourceImageFileName + i + ".jpg");
tmp.Dispose();
temp = AForge.Imaging.Image.FromFile(m_sourceImageFileName + i + ".jpg");
File.Delete(m_sourceImageFileName + i + ".jpg");
break;
}
}
}
Bitmap properImage = temp;
This is the new histogram:
As you can see, it's not the same as what the histogram should look like.
I found out that the problem might be because the image is an 8bppIndexed jpeg image, and jpeg only supports 24bppRgb images. Any solutions?
I think the clue is in the type being "indexed" in your second line. There are probably only 16 colours in the lookup table. Can you post your original scanned image so we can see if there are really more shades in it? If not, try using ImageMagick to count the colours
Like this to get a histogram:
convert yourimage.jpg -format %c histogram:info:-
convert yourimage.jpg -colorspace rgb -colors 256 -depth 8 -format "%c" histogram:info:
Or count the unique colours like this:
identify -verbose yourimage.jpg | grep -i colors:
Or dump all the pixels like this:
convert yourimage.jpg -colorspace rgb -colors 256 -depth 8 txt:
Well, I solved it by opening the JPEG and saving it as bmp with the ImageJ library in java. I've made a .jar file from the code and I use this code to get the bmp into my c# code:
string extension = m_sourceImageFileName.Substring(m_sourceImageFileName.LastIndexOf("."), m_sourceImageFileName.Length - m_sourceImageFileName.LastIndexOf("."));
int exitcode;
ProcessStartInfo ProcessInfo;
Process process;
ProcessInfo = new ProcessStartInfo("java.exe", #"-jar ""C:\Users\stevenh\Documents\Visual Studio 2010\Projects\BlackSpotDetection V2.0\ConvertToBmp\dist\ConvertToBmp.jar"" " + extension + " " + m_sourceImageFileName + " " + m_addedImageName);
ProcessInfo.CreateNoWindow = true;
ProcessInfo.UseShellExecute = false;
// redirecting standard output and error
ProcessInfo.RedirectStandardError = true;
ProcessInfo.RedirectStandardOutput = true;
process = Process.Start(ProcessInfo);
process.WaitForExit();
//Reading output and error
string output = process.StandardOutput.ReadToEnd();
string error = process.StandardError.ReadToEnd();
exitcode = process.ExitCode;
if (exitcode != 0)
{
statusLabel.Text = output;
MessageBox.Show("Error in external process: converting image to bmp.\n" + error);
//Exit code '0' denotes success and '1' denotes failure
return;
}
else
statusLabel.Text = "Awesomeness";
process.Close();
Bitmap realImage = AForge.Imaging.Image.FromFile(m_addedImageName);
File.Delete(m_addedImageName);
The jar will receive the extension, m_sourceImageFileName and m_addedImageFileName. It will open the sourceImage and save it under the name of m_addedImageFileName
I'm using the AForge library to open the image, because this library doesn't lock the image while it's opened, which makes me able to delete the 'home-made' image.

Using WritePixels when using a writeable bitmap from a intptr to create a bitmap.

Im currently trying to use writeablebitmap to take a IntPtr of a scan of images and turn each one into a Bitmap. Im wanting to use writeablebitmap because im having an issue with standard gdi
GDI+ System.Drawing.Bitmap gives error Parameter is not valid intermittently
There is a method on a WriteableBitmap that called WritePixels
http://msdn.microsoft.com/en-us/library/aa346817.aspx
Im not sure what I set for the buffer and the stride every example I find it shows the stride as 0 although that throws an error. When I set the stride to 5 the image appear black. I know this may not be the most efficient code but any help would be appreciated.
//create bitmap header
bmi = new BITMAPINFOHEADER();
//create initial rectangle
Int32Rect rect = new Int32Rect(0, 0, 0, 0);
//create duplicate intptr to use while in global lock
dibhand = dibhandp;
bmpptr = GlobalLock(dibhand);
//get the pixel sizes
pixptr = GetPixelInfo(bmpptr);
//create writeable bitmap
var wbitm = new WriteableBitmap(bmprect.Width, bmprect.Height, 96.0, 96.0, System.Windows.Media.PixelFormats.Bgr32, null);
//draw the image
wbitm.WritePixels(rect, dibhandp, 10, 0);
//convert the writeable bitmap to bitmap
var stream = new MemoryStream();
var encoder = new JpegBitmapEncoder();
encoder.Frames.Add(BitmapFrame.Create(wbitm));
encoder.Save(stream);
byte[] buffer = stream.GetBuffer();
var bitmap = new System.Drawing.Bitmap(new MemoryStream(buffer));
GlobalUnlock(dibhand);
GlobalFree(dibhand);
GlobalFree(dibhandp);
GlobalFree(bmpptr);
dibhand = IntPtr.Zero;
return bitmap;
An efficient way to work on Bitmaps in C# is to pass temporarily in unsafe mode (I know I don't answer the question exactly but I think the OP did not manage to use Bitmap, so this could be a solution anyway). You just have to lock bits and you're done:
unsafe private void GaussianFilter()
{
// Working images
using (Bitmap newImage = new Bitmap(width, height))
{
// Lock bits for performance reason
BitmapData newImageData = newImage.LockBits(new Rectangle(0, 0, newImage.Width,
newImage.Height), ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb);
byte* pointer = (byte*)newImageData.Scan0;
int offset = newImageData.Stride - newImageData.Width * 4;
// Compute gaussian filter on temp image
for (int j = 0; j < InputData.Height - 1; ++j)
{
for (int 0 = 1; i < InputData.Width - 1; ++i)
{
// You browse 4 bytes per 4 bytes
// The 4 bytes are: B G R A
byte blue = pointer[0];
byte green = pointer[1];
byte red = pointer[2];
byte alpha = pointer[3];
// Your business here by setting pointer[i] = ...
// If you don't use alpha don't forget to set it to 255 else your whole image will be black !!
// Go to next pixels
pointer += 4;
}
// Go to next line: do not forget pixel at last and first column
pointer += offset;
}
// Unlock image
newImage.UnlockBits(newImageData);
newImage.Save("D:\temp\OCR_gray_gaussian.tif");
}
}
This is really much more efficient than SetPixel(i, j), you just have to be careful about pointer limits (and not forget to unlock data when you're done).
Now to answer your question about stride: the stride is the length in bytes of a line, it is a multiple of 4. In my exemple I use the format Format32bppArgb which uses 4 bytes per pixel (R, G, B and alpha), so newImageData.Stride and newImageData.Width * 4 are always the same. I use the offset in my loops only to show where it would be necessary.
But if you use another format, for instance Format24bppRgb which uses 3 bytes per pixel (R, G and B only), then there may be an offset between stride and width. For an image 10 * 10 pixels in this format, you will have a stride of 10 * 3 = 30, + 2 to reach nearest multiple of 4, i.e. 32.

Categories

Resources