How to query the bit depth of a PNG? - c#

I have a PNG file which has 8-bit color depth as evidenced by file properties:
Yes, when I open the file
var filePath = "00050-w600.png";
var bitmap = new Bitmap(filePath);
Console.WriteLine(bitmap.PixelFormat);
I get Format32bppArgb. I also looked in the PropertyIdList and PropertyItems properties but didn't see anything obvious.
So how do I extract the Bit Depth from the PNG?
P.S. None of the framework methods seem to work. System.Windows.Media.Imaging.BitmapSource might work but it's only in WPF and .NET Core 3. I need this for .NET 4.x and .NET Core 2.x.
P.P.S. I just needed to know whether the PNG is 8 bit or not, so I wrote a sure fire method to check if anyone needs it - should work in any framework.
public static bool IsPng8BitColorDepth(string filePath)
{
const int COLOR_TYPE_BITS_8 = 3;
const int COLOR_DEPTH_8 = 8;
int startReadPosition = 24;
int colorDepthPositionOffset = 0;
int colorTypePositionOffset = 1;
try
{
using (var fs = new FileStream(filePath, FileMode.Open, FileAccess.Read))
{
fs.Position = startReadPosition;
byte[] buffer = new byte[2];
fs.Read(buffer, 0, 2);
int colorDepthValue = buffer[colorDepthPositionOffset];
int colorTypeValue = buffer[colorTypePositionOffset];
return colorDepthValue == COLOR_DEPTH_8 && colorTypeValue == COLOR_TYPE_BITS_8;
}
}
catch (Exception)
{
return false;
}
}
Color Allowed Interpretation
Type Bit Depths
0 1,2,4,8,16 Each pixel value is a grayscale level.
2 8,16 Each pixel value is an R,G,B series.
3 1,2,4,8 Each pixel value is a palette index;
a PLTE chunk must appear.
4 8,16 Each pixel value is a grayscale level,
followed by an alpha channel level.
6 8,16 Each pixel value is an R,G,B series,
followed by an alpha channel level.

Related

Reading QR code in Xamarin Forms With Zxing

I am trying to import a QR code from a .png file and decode it using Zxing.Net.Mobile and ZXing.Net.Mobile.Forms.
If I scan the QR code using the ZXing.Mobile.MobileBarcodeScanner class, the decoding works as required, however, when importing it from a file, the Qr code reader (ZXing.QrCode.QRCodeReader()) decode function always returns null.
As I'm using Xamarin Forms; each platform handles the bitmap/image creation and the portable part handle the rest (Zxing BinaryBitmap creation and decoding).
//Store rawBytes and image demensions
PotableBitmap bMap = DependencyService.Get<IBitmap>().FileToBitmap(filePath);
RGBLuminanceSource source = new RGBLuminanceSource(bMap.RgbBytes, bMap.Width, bMap.Height, RGBLuminanceSource.BitmapFormat.RGB32);
HybridBinarizer binarized = new HybridBinarizer(source);
BinaryBitmap bitmap = new BinaryBitmap(binarized);
var reader = new ZXing.QrCode.QRCodeReader();
data = reader.decode(qrCodeBitmap); // This is always null
The DependencyService will call the platform specific function, at the moment I am working with Andorid so, the function is as follows:
public PortableBitmap FileToBitmap(string ms)
{
var bytes = File.ReadAllBytes(ms);
Android.Graphics.Bitmap bMap = BitmapFactory.DecodeByteArray(bytes, 0, bytes.Length);
int[] intArray = new int[bMap.Width * bMap.Height];
bMap.GetPixels(intArray, 0, bMap.Width, 0, 0, bMap.Width, bMap.Height)
List<byte> result = new List<byte>();
foreach (int intRgb in intArray)
{
Color pixelColor = new Color(intRgb);
result.Add(pixelColor.R);
result.Add(pixelColor.G);
result.Add(pixelColor.B);
}
return new PortableBitmap(result.ToArray(), bMap.Width, bMap.Height);
}
I have looked through some of the posts on SO that are having the same problem and have tried the following:
Using BitmapLuminanceSource: still returns null and requires the use of another library
Using different bitmap formats for the RGBLuminanceSource: RGB32, BGR32, ARGB32, ABGR32 (each time changing the FileToBitmap function)
Tried the different Binarizer, GlobalHistogramBinarizer()
Checked that the file is being read correctly by reading and wrinting it back to a file.
I have tried using the MultiFormatReader() with the Pure barcode and try harder hints
I have also debugged the libraries source code and from what I understand it just can't find the QR code in the imported image. No exception is thrown.
Here is where the return null is made:
private FinderPattern[] selectBestPatterns()
{
int startSize = possibleCenters.Count;
if (startSize < 3)
{
// Couldn't find enough finder patterns
return null; // It returns here
}
...
The online Zxing decoder can decode the QR code I'm testing correctly. Here is my test QR code:
I solved that problem, with this method in the Android implementation to return an RGBLuminanceSource from the path of the image
public RGBLuminanceSource GetRGBLuminanceSource(string imagePath)
{
if (File.Exists(imagePath))
{
Bitmap bitmap = BitmapFactory.DecodeFile(imagePath);
List<byte> rgbBytesList = new List<byte>();
for (int y = 0; y < bitmap.Height; y++)
{
for (int x = 0; x < bitmap.Width; x++)
{
var c = new Color(bitmap.GetPixel(x, y));
rgbBytesList.AddRange(new[] { c.A, c.R, c.G, c.B });
}
}
byte[] rgbBytes = rgbBytesList.ToArray();
return new RGBLuminanceSource(rgbBytes, bitmap.Height, bitmap.Width, RGBLuminanceSource.BitmapFormat.ARGB32);
}
return null;
}

Reading 32-bit grayscale Tiff using Libtiff.Net

I've tried to read a 32-bit grayscale tiff file which each pixel in the image contains a floating point number. But during the reading process, the buffer array contains 4 values for each pixel. For instance [ pixel value = 43.0 --> byte values for the pixel = {0 , 0 , 44 , 66}]. I can't understand the relation between float pixel value and the byte values. I also wrote the image using the buffer but pixel values for output image are int values like 1073872896. Any suggestion would be appreciated.
using (Tiff input = Tiff.Open(#"E:\Sample_04.tif", "r"))
{
// get properties to use in writing output image file
int width = input.GetField(TiffTag.IMAGEWIDTH)[0].ToInt();
int height = input.GetField(TiffTag.IMAGELENGTH)[0].ToInt();
int samplesPerPixel = input.GetField(TiffTag.SAMPLESPERPIXEL)[0].ToInt();
int bitsPerSample = input.GetField(TiffTag.BITSPERSAMPLE)[0].ToInt();
int photo = input.GetField(TiffTag.PHOTOMETRIC)[0].ToInt();
int scanlineSize = input.ScanlineSize();
byte[][] buffer = new byte[height][];
for (int i = 0; i < height; i++)
{
buffer[i] = new byte[scanlineSize];
input.ReadScanline(buffer[i], i);
}
using (Tiff output = Tiff.Open("output.tif", "w"))
{
output.SetField(TiffTag.SAMPLESPERPIXEL, samplesPerPixel);
output.SetField(TiffTag.IMAGEWIDTH, width);
output.SetField(TiffTag.IMAGELENGTH, height);
output.SetField(TiffTag.BITSPERSAMPLE, bitsPerSample);
output.SetField(TiffTag.ROWSPERSTRIP, output.DefaultStripSize(0));
output.SetField(TiffTag.PHOTOMETRIC, photo);
output.SetField(TiffTag.PLANARCONFIG, PlanarConfig.CONTIG);
output.SetField(TiffTag.COMPRESSION, compression);
int j = 0;
for (int i = 0; i < h; i++)
{
output.WriteScanline(buffer[i], j);
j++;
}
}
}
Update 1:
I found out the relation between four bytes and the pixel value using BitConverter class in c# that is like this:
byte[] a = { 0, 0, 44, 66 } --> 43 = BitConverter.ToSingle(a, 0) and 1110179840 = BitConverter.ToInt32(a, 0). It seem bytes are converted to int32 and now the question is how convert byte values to float?
Update 2:
The original tiff file and the tiff after writing the snippet code have been attached.Why is the output tiff file messed up?
I added this line of code to convert pixel values to floating number and it works fine.
output.SetField(TiffTag.SAMPLEFORMAT, SampleFormat.IEEEFP);

Using TiffBitmapEncoder with Gray32Float

I'm trying to create a 32 BPP gray scale tiff using this code which I found on MSDN
BitmapSource image = BitmapSource.Create(
width,
height,
96,
96,
PixelFormats.Gray32Float,
null,
pixels,
stride);
FileStream stream = new FileStream("test file.tif", FileMode.Create);
TiffBitmapEncoder encoder = new TiffBitmapEncoder();
encoder.Compression = TiffCompressOption.None;
var bitmapFrame = BitmapFrame.Create(image);
encoder.Frames.Add(bitmapFrame);
encoder.Save(stream);
The file gets created and the image looks correct when I open it, but the file properties says that it is a 16 BPP (0-65536) image not a 32 bit floating point as specified by the Gray32Float parameter.
I've confirmed the file format is 16 BPP by looking at the file properties in windows explorer and by opening the file in ImageJ
I can create 32 BPP tiffs in Paint.Net and ImageJ, to confirm that format is supported.
Anyone know why the .Net TiffBitmapEncoder is creating the wrong type?
Under the hood, .Net uses the Windows Imaging Component (WIC). WIC supports reading of TIFFs in Gray32Float (GUID_WICPixelFormat32bppGrayFloat in WIC) but not writing. Take a look at the WIC Native Pixel Formats Overview. I had the same experience discovering the image was written as Gray16.
This is very frustrating. I've been attempting to writes some scientific data using Gray32Float, but I have not been successful.
Old question, but I tried, and almost made it, but still - doesn't work correctly:
What I have here is a solution which saves as 32bit, using TiffLib, but the value range is somehow not correct.
I save an image in float range of -0.5 to 3, and imageJ reads it as 32 bit, BUT the range is ~-1000K to ~3000K...
I tried using TiffLib adding the following functions:
public static void Write32BitTiff_(string path, int W, int H, float[] data, ref byte[] FileData, int numPage = 0)
{
var numBytes = sizeof(float);
var size = H * W * numBytes;
byte[] arr = null;
arr = new byte[size];
var ctr = 0;
byte[] floatVal;
for (int i = 0; i < size; i += numBytes)
{
try
{
float val = data[ctr++];
floatVal = BitConverter.GetBytes(val);
for (int j = 0; j < numBytes; j++)
arr[i + j] = floatVal[j];
}
catch (IndexOutOfRangeException)
{
break;
}
catch (Exception eee) { }
}
Tiff t = openTiff(path, W, H, numPage, numBytes * 8);
t.WriteRawStrip(0, arr, size);
t.Close();
t.Dispose();
}
Where "OpenTiff" looks like this:
private static Tiff openTiff(string path, int W, int H, int pageNum, int numBits, bool overrideFile = false)
{
Tiff t;
int numberOfPages;
if (!File.Exists(path) || overrideFile )
{
t = Tiff.Open(path, "w");
numberOfPages = 1;
}
else
{
var start = DateTime.Now;
t = Tiff.Open(path, "a");
numberOfPages = t.NumberOfDirectories() + 1; ;
numberOfPages = (pageNum > numberOfPages) ? pageNum : numberOfPages;
}
t.SetField(TiffTag.IMAGEWIDTH, W);
t.SetField(TiffTag.IMAGELENGTH, H);
const int NUM_CHANNELS = 1;//for RGB set 3. for ARGB set 4, not sure supported.
t.SetField(TiffTag.SAMPLESPERPIXEL, NUM_CHANNELS);
t.SetField(TiffTag.BITSPERSAMPLE, numBits);
t.SetField(TiffTag.PHOTOMETRIC, Photometric.MINISBLACK);
t.SetField(TiffTag.SUBFILETYPE, FileType.PAGE);
t.SetField(TiffTag.PAGENUMBER, pageNum, numberOfPages);
t.SetDirectory((short)pageNum);
}
So if this might help someone, or if someone could find the "bug" with it - this would be great!

How to display TIFF (in form of Byte[]) on Silverlight Image control

I created a window service to put all of my TIFF files into database and stored them as Byte[].
Now I want to be able to display them through Silverlight Image control
So i use the Converter during binding XAML in order to convert the Byte[] to Bitmap because the Image.Source only accept eitheir URI (I don't have the file stored on server so can't use this method) or Bitmap.
BitmapImage bmi = new BitmapImage();
if (value != null)
{
ImageGallery imageGallery = value as ImageGallery;
byte[] imageContent = imageGallery.ImageContent;
string imageType = imageGallery.ImageType;
using (MemoryStream ms = new MemoryStream(imageContent))
{
bmi.SetSource(ms);
}
}
return bmi;
However, I get the exception at bmi.SetSource(ms) because Silverlight only supports JPEG and PNG images.
So I did more research and knew that i should convert the bytes of TIFF to bytes of JPEG or PNG then it will work.
To do that I tried two methods:
Doing the conversion on server: in my RIA service call, after retrieving the ImageGallery, I loop through the available image to convert the bytes of TIFF to the bytes of JPEG.
BUT IT DOESN'T WORK....
Can you tell me where I did wrong?
public IQueryable<ImageGallery> GetImageGalleries()
{
var imageGalleries = this.ObjectContext.ImageGalleries.OrderBy(i=>i.ImageName);
foreach (ImageGallery imageGallery in imageGalleries)
{
if (imageGallery.ImageType == ".tif" || imageGallery.ImageType == ".tiff")
{
//Convert the Tiff byte array format into JPEG stream format
System.Drawing.Bitmap dImg = new System.Drawing.Bitmap(new MemoryStream(imageGallery.ImageContent));
MemoryStream ms = new MemoryStream();
dImg.Save(ms, System.Drawing.Imaging.ImageFormat.Jpeg);
//then convert the JPEG stream format into JPEG byte array format
byte[] buf = new byte[ms.Length];
ms.Read(buf, 0, buf.Length);
//Changing the format tiff byte[] of ImageGallery to jpeg byte[]
imageGallery.ImageContent = buf;
}
}
return imageGalleries;
}
The other solution is to use LibTiff.Net library to convert directly the Byte[] of TIFF to WritableBitmap directly on Silverlight.
However, after digging through their sample application or using Reflector to see the source code functions, I still can't figure out how to use their library to convert the bytes of TIFF to WritableBitmap JPEG (or PNG) because their sample only show the API for using the search the TIFF in a file directory. In my case, I don't have an existing file on server.
Can someone help me how to show the TIFF file on Image control of Silverlight?
I searched the forum but didn't find any solid answer for this.
thanks
I think the LibTiff will be the way to go. Ulitmately the Tiff.ClientData accepts a Stream that is the tiff data. If your tiff data really is a byte[] then you just need a MemoryStream around it. More likely at some point the byte[] is pulled from a stream so you probably don't even need this intermedatory byte[] / MemoryStream.
Reference LibTiff.net
Add this class:
using System;
using System.Collections.Generic;
using System.IO;
using System.Windows.Media.Imaging;
using BitMiracle.LibTiff.Classic;
namespace CoreTechs.X9
{
public static class TiffUtility
{
public static Tiff CreateTiff(this byte[] bytes)
{
MemoryStream ms = new MemoryStream(bytes);
Tiff tiff = Tiff.ClientOpen("in-memory", "r", ms, new TiffStream());
return tiff;
}
public static IEnumerable<WriteableBitmap> ConvertToWriteableBitmaps(this Tiff tiff)
{
if (tiff == null)
throw new ArgumentNullException("tiff", "tiff is null.");
short dirs = tiff.NumberOfDirectories();
for (int i = 0; i < dirs; i++)
{
if (tiff.SetDirectory((short)i))
{
int tileCount = tiff.NumberOfTiles();
int stripCount = tiff.NumberOfStrips();
var frameWidthField = tiff.GetField(TiffTag.IMAGEWIDTH);
var frameHeightField = tiff.GetField(TiffTag.IMAGELENGTH);
var compressionField = tiff.GetField(TiffTag.COMPRESSION);
var xResolutionField = tiff.GetField(TiffTag.XRESOLUTION);
var yResolutionField = tiff.GetField(TiffTag.YRESOLUTION);
var samplesPerPixelField = tiff.GetField(TiffTag.SAMPLESPERPIXEL);
int frameWidth = frameWidthField != null && frameWidthField.Length > 0 ? frameWidthField[0].ToInt() : 0;
int frameHeight = frameHeightField != null && frameHeightField.Length > 0 ? frameHeightField[0].ToInt() : 0;
var compression = compressionField != null && compressionField.Length > 0 ? (Compression)compressionField[0].Value : Compression.NONE;
var xResolution = xResolutionField != null && xResolutionField.Length > 0 ? new double?(xResolutionField[0].ToDouble()) : null;
var yResolution = yResolutionField != null && yResolutionField.Length > 0 ? new double?(yResolutionField[0].ToDouble()) : null;
var samplesPerPixel = samplesPerPixelField != null && samplesPerPixelField.Length > 0 ? samplesPerPixelField[0].ToString() : String.Empty;
if (xResolution != null && yResolution == null)
{
yResolution = xResolution;
}
var buffer = new int[frameWidth * frameHeight];
tiff.ReadRGBAImage(frameWidth, frameHeight, buffer);
var bmp = new WriteableBitmap(frameWidth, frameHeight);
for (int y = 0; y < frameHeight; y++)
{
var ytif = y * frameWidth;
var ybmp = (frameHeight - y - 1) * frameWidth;
for (int x = 0; x < frameWidth; x++)
{
var currentValue = buffer[ytif + x];
// Shift the Tiff's RGBA format to the Silverlight WriteableBitmap's ARGB format
bmp.Pixels[ybmp + x] = Tiff.GetB(currentValue) | Tiff.GetG(currentValue) << 8 | Tiff.GetR(currentValue) << 16 | Tiff.GetA(currentValue) << 24;
}
}
yield return bmp;
}
}
}
}
}
Use the exension methods like this:
byte[] myHappyTiffData = GetMyTiffBytesFromSomewhere();
WriteableBitmap bmp = myHappyTiffData.CreateTiff().ConvertToWriteableBitmaps().FirstOrDefault();
myImageControl.Source = bmp;
We began with LibTiff as a solution for our media manager. I wouldn't recommend it.
As you can see it creates a WriteableBitmap for each page. WB is the most performance hampering, leaking object you can use in Silverlight, so if you got more then 1 single page tiff your app will run out of memory faster then you can say Avada Kedavra.
There are viewers that appearently can load a large multipage tiff without killing your app (and browser and computer), for a decent license fee, but at this point I got nothing that allows you to decode a tiff an extract the pages.
Runner ups:
http://www.accusoft.com/
http://www.atalasoft.com/products/dotimage

directshow Renderstream fails with grayscale bitmaps

I'm trying to create a directshow graph to playback a video composed of 8bit grayscale bitmaps. (using directshow.net.)
I'm using a source filter and the vmr9 renderer.
The source filter's output pin is defined using the following code :
bmi.Size = Marshal.SizeOf(typeof(BitmapInfoHeader));
bmi.Width = width;
bmi.Height = height;;
bmi.Planes = 1;
bmi.BitCount = (short)bitcount;
bmi.Compression = 0;
bmi.ImageSize = Math.Abs(bmi.Height) * bmi.Width * bmi.BitCount / 8;
bmi.ClrUsed = bmi.BitCount <= 8 ? 256 : 0;
bmi.ClrImportant = 0;
//bmi.XPelsPerMeter = 0;
//bmi.YPelsPerMeter = 0;
bool isGrayScale = bmi.BitCount <= 8 ? true : false;
int formatSize = Marshal.SizeOf(typeof(BitmapInfoHeader));
if (isGrayScale == true)
{
MessageWriter.Log.WriteTrace("Playback is grayscale.");
/// Color table holds an array of 256 RGBQAD values
/// Those are relevant only for grayscale bitmaps
formatSize += Marshal.SizeOf(typeof(RGBQUAD)) * bmi.ClrUsed;
}
IntPtr ptr = Marshal.AllocHGlobal(formatSize);
Marshal.StructureToPtr(bmi, ptr, false);
if (isGrayScale == true)
{
/// Adjust the pointer to the beginning of the
/// ColorTable address and create the grayscale color table
IntPtr ptrNext = (IntPtr)((int)ptr + Marshal.SizeOf(typeof(BitmapInfoHeader)));
for (int i = 0; i < bmi.ClrUsed; i++)
{
RGBQUAD rgbCell = new RGBQUAD();
rgbCell.rgbBlue = rgbCell.rgbGreen = rgbCell.rgbRed = (byte)i;
rgbCell.rgbReserved = 0;
Marshal.StructureToPtr(rgbCell, ptrNext, false);
ptrNext = (IntPtr)((int)ptrNext + Marshal.SizeOf(typeof(RGBQUAD)));
}
}
This causes Renderstream to return "No combination of intermediate filters could be found to make the connection."
Please help!
Thanks.
to be honest, I've not seen any palette handling code for a little while. I'm not that surprised that it no longer works.
The standard way to define grey scale is using YUV with 0 bits for U and V. That takes the same space, but does not require a table lookup, since the Y bits are valid as they are. The standard FOURCC is 'Y800', with a guid created using FOURCCMap as the subtype. I don't know if the VMR will accept this directly, but if not, you could use the YUV transform from www.gdcl.co.uk to convert it to something acceptable, such as YUY2, by inserting zeros (YUY2 is widely accepted but double the space).
G

Categories