directshow Renderstream fails with grayscale bitmaps - c#

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

Related

How to query the bit depth of a PNG?

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.

How to crop image that is a byte array

I have an Azure Function and would want to crop an image that is inside a byte array, and then save it to a blob. I am not sure how to do the cropping part, because Bitmap is not available in Azure Functions. Here is my saving to blob:
private async static Task<string> CreateBlob(string name, byte[] data, TraceWriter log)
which works fine for what it does. Also, I have the URL of the image I want to crop, if that helps. Just don't know how to achieve this with an Azure Function. Any ideas?
EDIT
this is the original image, that I have inside a byte array. I want to crop another one from it with the size - 650x290
int cropStartX = 0;
int cropStartY = 0;
int cropWidth = 650;
int cropHeight = 290;
int multiply = cropWidth * cropHeight;
byte[] croppedImage = new byte[multiply];
for (int j = cropStartX; j < cropStartX + cropWidth; j++)
{
for (int k = cropStartY; k < cropStartY + cropHeight; k++)
{
if ((j + k * cropWidth) >= ((byte[])OriginalPic).Length)
break;
croppedImage[j + k * cropWidth] = ((byte[])OriginalPic)[j + k * cropWidth];
}
}
This is the size of the OriginalPic - byte[49121]
This is the output if I set:
croppedImage[j + k * cropWidth] = ((byte[])OriginalPic)[j + k * (cropWidth + 20)];
idk if this is what you are after, but assuming you know the dimensions of your picture, you just have to encode that into your index :
pseudocode
int cropWidth = 100;
int cropHeight = 50;
byte[cropWidth*cropHeight] croppedImage;
for(int i=cropStartX;i<cropStartX+cropWidth;i++)
{
for(int ii = cropStartY;ii<cropStartY+cropHeight;ii++)
{
croppedImage[i + ii*cropWidth] = image[i + ii*mainImageWidth];
}
}
I know this isnt as fast as if you can actually just do a memory copy function... but will do what you need.
Update: after looking at more of your information I am afraid that this method will not work for you as you have compressed image data. Basically jpeg compresses data at a size of about 1:10 which makes more sense now that your image byte array is only 49k as that is almost exactly the file size, which contains jpeg compressed information for a 1024x434 picture of rgb. Long story short you can not simply query pixel locations from that array to get meaningful data unless you were to uncompressed the image prior to cropping. Apologies as I should have seen this as soon as I noticed a "byte" was supposedly representing an rgb pixel (which does not match).

Converting CDF of histogram to c# from matlab?

How can I convert this matlab code to AForge.net+c# code?
cdf1 = cumsum(hist1) / numel(aa);
I found that there is Histogram.cumulative method is present in Accord.net.
But I dont know how to use.
Please teaching how to convert.
% Histogram Matching
%
clear
clc
close all
pkg load image
% 이미지 로딩
aa=imread('2.bmp');
ref=imread('ref2.png');
figure(1); imshow(aa); colormap(gray)
figure(2); imshow(ref); colormap(gray)
M = zeros(256,1,'uint8'); % Store mapping - Cast to uint8 to respect data type
hist1 = imhist(aa); % Compute histograms
hist2 = imhist(ref);
cdf1 = cumsum(hist1) / numel(aa); % Compute CDFs
cdf2 = cumsum(hist2) / numel(ref);
% Compute the mapping
for idx = 1 : 256
[~,ind] = min(abs(cdf1(idx) - cdf2));
M(idx) = ind-1;
end
% Now apply the mapping to get first image to make
% the image look like the distribution of the second image
out = M(double(aa)+1);
figure(3); imshow(out); colormap(gray)
Actually, I don't have a great knowledge of Accord.NET, but reading the documentation I think that ImageStatistics class is what you are looking for (reference here). The problem is that it cannot build a single histogram for the image and you have to do it by yourself. imhist in Matlab just merges the three channels and then counts the overall pixel occurrences so this is what you should do:
Bitmap image = new Bitmap(#"C:\Path\To\Image.bmp");
ImageStatistics statistics = new ImageStatistics(image);
Double imagePixels = (Double)statistics.PixelsCount;
Int32[] histR = statistics.Red.Values.ToArray();
Int32[] histG = statistics.Green.Values.ToArray();
Int32[] histB = statistics.Blue.Values.ToArray();
Int32[] histImage = new Int32[256];
for (Int32 i = 0; i < 256; ++i)
histImage[i] = histR[i] + histG[i] + histB[i];
Double cdf = new Double[256];
cdf[0] = (Double)histImage[0];
for (Int32 i = 1; i < 256; ++i)
cdf[i] = (Double)(cdf[i] + cdf[i - 1]);
for (Int32 i = 0; i < 256; ++i)
cdf[i] = cdf[i] / imagePixels;
In C#, an RGB value can be built from R, G and B channel values as follows:
public static int ChannelsToRGB(Int32 red, Int32 green, Int32 blue)
{
return ((red << 0) | (green << 8) | (blue << 16));
}

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);

Convert a RenderTargetBitmap to a byte[] to be displayed on a embedded screen?

I'm trying to convert a RenderTargetBitmap to a byte array that will then get sent off to an external monochrome OLED screen. I know that for the bitmap to display correctly the bit/byte alignment should be LSB to MSB & Top to Bottom:
But I can't figure out how to get the RenderTargetBitmap's pixeldata in that format.
For the moment I've got:
RenderTargetBitmap renderTargetBitmap; //This is already set higher up
DataReader reader = DataReader.FromBuffer(await renderTargetBitmap.GetPixelsAsync());
// Placeholder for reading pixels
byte[] pixel = new byte[4]; // RGBA8
// Write out pixels
int index = 0;
byte[] array = new byte[renderTargetBitmap.PixelWidth*renderTargetBitmap.PixelHeight];
using (reader)
{
//THIS IS WHERE I THINK I'M SCREWING UP
for (int x = 0; x < rHeight; x++)
{
for (int y = 0; x < rWidth; y++)
{
reader.ReadBytes(pixel);
if (pixel[2] == 255)
array[index] = 0xff;
else
array[index] = 0x00;
index++;
}
}
}
sh1106.ShowBitmap(buffer); //Send off the byte array
I faced the same issue, this is what I did to get it working (It will convert the BGRA8 output to a 1BPP output that can then be used on a monochrome display, SSD1306 in my case)
At 1BPP output means that 8 pixels are stored in 1 byte. So you need to convert every 4 bytes into 1 bit.
public async Task Draw()
{
ActiveCanvas.UpdateLayout();
ActiveCanvas.Measure(ActiveCanvas.DesiredSize);
ActiveCanvas.Arrange(new Rect(new Point(0, 0), ActiveCanvas.DesiredSize));
// Create a render bitmap and push the surface to it
RenderTargetBitmap renderBitmap = new RenderTargetBitmap();
await renderBitmap.RenderAsync(ActiveCanvas, (int)ActiveCanvas.DesiredSize.Width, (int)ActiveCanvas.DesiredSize.Height);
DataReader bitmapStream = DataReader.FromBuffer(await renderBitmap.GetPixelsAsync());
if (_device != null)
{
byte[] pixelBuffer_1BPP = new byte[(int)(renderBitmap.PixelWidth * renderBitmap.PixelHeight) / 32];
pixelBuffer_1BPP.Initialize();
using (bitmapStream)
{
while (bitmapStream.UnconsumedBufferLength > 0)
{
uint index = (uint)(renderBitmap.PixelWidth * renderBitmap.PixelHeight * 4) - bitmapStream.UnconsumedBufferLength;
for (int bit = 0; bit < 8; bit++)
{
bitmapStream.ReadBytes(BGRA8);
byte value = (byte)(((BGRA8[0] & 0x80) | (BGRA8[1] & 0x80) | (BGRA8[2] & 0x80)) == 0x80 ? 1 : 0);
pixelBuffer_1BPP[index] |= (byte)(value << (7 - bit));
}
}
_device.DrawBitmap(0, 0, pixelBuffer_1BPP, (short)renderBitmap.PixelWidth, (short)renderBitmap.PixelHeight, Colors.White);
}
}
}

Categories