Runtime.interop ArgumentNullException using ZXing - c#

I am trying to parse a QR Code in a Windows Store-app using ZXing.Net, but when I try to run it using the latest version from their webpage it gives me a ArgumentNullException in BitmapLuminanceSource.Silverlight.cs on line 50
The line looks like this
var data = System.Runtime.InteropServices.WindowsRuntime.WindowsRuntimeBufferExtensions.ToArray(writeableBitmap.PixelBuffer, 0, (int)writeableBitmap.PixelBuffer.Length);
The WriteableBitmap is not null, so I do not what, what is null.
Can anybody help me?
It is from this method
public BitmapLuminanceSource(WriteableBitmap writeableBitmap)
: base(writeableBitmap.PixelWidth, writeableBitmap.PixelHeight)
{
var height = writeableBitmap.PixelHeight;
var width = writeableBitmap.PixelWidth;
var stride = width * 4;
luminances = new byte[width * height];
Color c;
#if NETFX_CORE
var data = System.Runtime.InteropServices.WindowsRuntime.WindowsRuntimeBufferExtensions.ToArray(writeableBitmap.PixelBuffer, 0, (int)writeableBitmap.PixelBuffer.Length);
for (int y = 0; y < height; y++)
{
int offset = y * stride;
for (int x = 0, xl = 0; x < stride; x += 4, xl++)
{
c = Color.FromArgb(
data[x + offset],
data[x + offset + 1],
data[x + offset + 2],
data[x + offset + 3]);
luminances[y * width + xl] = (byte)(0.3 * c.R + 0.59 * c.G + 0.11 * c.B + 0.01);
}
}
#else
var pixels = writeableBitmap.Pixels;
for (int y = 0; y < height; y++)
{
int offset = y * width;
for (int x = 0; x < width; x++)
{
int srcPixel = pixels[x + offset];
c = Color.FromArgb((byte)((srcPixel >> 0x18) & 0xff),
(byte)((srcPixel >> 0x10) & 0xff),
(byte)((srcPixel >> 8) & 0xff),
(byte)(srcPixel & 0xff));
luminances[offset + x] = (byte)(0.3 * c.R + 0.59 * c.G + 0.11 * c.B + 0.01);
}
}
#endif
}
UPDATE
The WriteableBitmat is created using this code
// Get the File
var File = await FilePick.PickSingleFileAsync();
// Convert the File to a Bitmap
var Stream = await File.OpenAsync(FileAccessMode.Read);
var Bmp = new BitmapImage();
Bmp.SetSource(Stream);
var WBmp = new WriteableBitmap(Bmp.PixelWidth, Bmp.PixelHeight);
WBmp.SetSource(Stream);
By using Damir Arh's answer, the error is moved a bit to the following code
c = Color.FromArgb(
data[x + offset],
data[x + offset + 1],
data[x + offset + 2],
data[x + offset + 3]);
Where I get an IndexOutOfRangeException, when
x = 580
xl = 145
offset = 31104
y = 36
height = 216
width = 216
stride = 864
data = {byte[31684]}
I can of course see why it is out of range, but I can not see how to fix it
It was fixed using Damir Arh's updated answer with Stream.Seek(0)

I made a quick port of the Silverlight example that's available from the project homepage and it worked:
var dlg = new FileOpenPicker();
dlg.FileTypeFilter.Add(".png");
var file = await dlg.PickSingleFileAsync();
if (file != null)
{
currentBarcode = new WriteableBitmap(89, 89);
using (var stream = await file.OpenReadAsync())
{
currentBarcode.SetSource(stream);
}
imgDecoderBarcode.Source = currentBarcode;
var result = reader.Decode(currentBarcode);
if (result != null)
{
txtDecoderType.Text = result.BarcodeFormat.ToString();
txtDecoderContent.Text = result.Text;
}
else
{
txtDecoderType.Text = String.Empty;
txtDecoderContent.Text = "No barcode found.";
}
}
I also tried calling the offending line from the code you posted and there was no exception thrown:
var dlg = new FileOpenPicker();
dlg.FileTypeFilter.Add(".png");
var file = await dlg.PickSingleFileAsync();
if (file != null)
{
currentBarcode = new WriteableBitmap(89, 89);
using (var stream = await file.OpenReadAsync())
{
currentBarcode.SetSource(stream);
var data = System.Runtime.InteropServices.WindowsRuntime.WindowsRuntimeBufferExtensions.ToArray(currentBarcode.PixelBuffer, 0, (int)currentBarcode.PixelBuffer.Length);
}
}
The problem in your case is how you're creating the WriteableBitmap that is being passed to this method. Because you first load a Bitmap from the same stream you're already positioned at its end when you set it as the source for WritableBitmap. You need to reposition yourself to the beginning of the stream so that the data can be loaded once more:
// Convert the File to a Bitmap
var Stream = await File.OpenAsync(FileAccessMode.Read);
var Bmp = new BitmapImage();
Bmp.SetSource(Stream);
Stream.Seek(0);
var WBmp = new WriteableBitmap(Bmp.PixelWidth, Bmp.PixelHeight);
WBmp.SetSource(Stream);

Related

C# memory access optimization that offset large array (Why is this code slow)

I'm working on image processing in C#.
I have a problem getting the performance of offset large memory access in C#.
The speed is significantly different from the same size of zero offset memory.
In the case of C ++, the difference was not as great as in C#.
Can you please tell me why my code has this problem ?
Also, are there any solutions ?
Source
using System;
using System.Runtime.InteropServices;
using System.Diagnostics;
using System.Numerics;
namespace Test
{
class Program
{
unsafe static void Main(string[] args)
{
var width = 8000;
var height = 8000;
// var data = new Vector4[height * width]; <- similar problem occur
var data = (Vector4*)Marshal.AllocHGlobal(height * width * sizeof(Vector4));
var data2 = (Vector4*)Marshal.AllocHGlobal(height * width * sizeof(Vector4));
// MATRIX
float m11 = .7297023F, m12 = 0, m13 = 0, m14 = 0, m21 = 0, m22 = .6109577F,
m23 = 0, m24 = 0, m31 = 0, m33 = .597218F, m32 = 0, m34 = 0, m41 = 0, m42 = 0,
m43 = 0, m44 = 1F, m51 = .105F, m52 = .145F, m53 = .155F, m54 = 0;
var sw = new Stopwatch();
sw.Start();
for (int y = 0; y < height; ++y)
{
var offset = width * y;
for (int x = 0; x < width; ++x)
{
// Slow ( 600ms )
ref var sData = ref data[offset + x];
ref var dData = ref data2[offset + x];
// Fast ( 200ms )
// ref var sData = ref data[x];
// ref var dData = ref data2[x];
float b = sData.X;
float g = sData.Y;
float r = sData.Z;
float a = sData.W;
dData.X = (b * m11) + (g * m21) + (r * m31) + (a * m41) + m51;
dData.Y = (b * m12) + (g * m22) + (r * m32) + (a * m42) + m52;
dData.Z = (b * m13) + (g * m23) + (r * m33) + (a * m43) + m53;
dData.W = (b * m14) + (g * m24) + (r * m34) + (a * m44) + m54;
}
}
sw.Stop();
Console.WriteLine(sw.ElapsedMilliseconds);
Marshal.FreeHGlobal((IntPtr)data);
Marshal.FreeHGlobal((IntPtr)data2);
}
}
}
When using managed array pointer
var array1 = new Vector4[width * height];
var array2 = new Vector4[width * height];
fixed (Vector4* data = &array1[0])
fixed (Vector4* data2 = &array2[0])
for (int y = 0; y < height; ++y)
{
for (int x = 0; x < width; ++x)
{
// Slow ( 600ms )
ref var sData = ref data[width * y + x];
ref var dData = ref data2[width * y + x];
Offsetting the pointer in the outer loop
(A little improvement)
for (int y = 0; y < height; ++y)
{
var offsetData1 = data + width * y;
var offsetData2 = data2 + width * y;
for (int x = 0; x < width; ++x)
{
// Slow ( 470ms )
ref var sData = ref offsetData1[x];
ref var dData = ref offsetData2[x];
C++ ver
#include <iostream>
#include <chrono>
struct Vector4 {
float X = 0;
float Y = 0;
float Z = 0;
float W = 0;
};
int main()
{
long width = 8000;
long height = 8000;
auto buffer = new Vector4[width * height];
auto buffer2 = new Vector4[width * height];
// MATRIX
float m11 = .7297023F, m12 = 0, m13 = 0, m14 = 0, m21 = 0, m22 = .6109577F,
m23 = 0, m24 = 0, m31 = 0, m33 = .597218F, m32 = 0, m34 = 0, m41 = 0, m42 = 0,
m43 = 0, m44 = 1, m51 = .105F, m52 = .145F, m53 = .155F, m54 = 0;
std::chrono::system_clock::time_point start, end;
start = std::chrono::system_clock::now();
for (int y = 0; y < height; ++y)
{
int offset = width * y;
for (int x = 0; x < width; ++x)
{
Vector4& sData = buffer[offset + x];
Vector4& dData = buffer2[offset + x];
float b = sData.X;
float g = sData.Y;
float r = sData.Z;
float a = sData.W;
dData.X = (b * m11) + (g * m21) + (r * m31) + (a * m41) + m51;
dData.Y = (b * m12) + (g * m22) + (r * m32) + (a * m42) + m52;
dData.Z = (b * m13) + (g * m23) + (r * m33) + (a * m43) + m53;
dData.W = (b * m14) + (g * m24) + (r * m34) + (a * m44) + m54;
}
}
end = std::chrono::system_clock::now();
double elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(end - start).count();
std::cout << elapsed << "\n";
delete[] buffer;
delete[] buffer2;
}
Benchmarks
Lang
description
time (ms)
C#
Zero offset pointer
600ms
C#
Offset pointer
200ms
C++
Zero offset pointer
190ms
C++
Offset pointer
260ms
C#
Offsetting the pointer in the outer loop
370ms
C#
Managed array pointer with offset
990ms
Other info
IL in SharpLab
CPU
Intel Core i7-6700k
Memory
DDR4 16GB
OS
Windows 10 20H2
Runtime
.NET 5
Lang ver
C# 9
Platform
X64

Fast data manipulation in C# .NET: RRGGBB to Bitmap / C# Video / Image Processing

I am getting image data from a camera which comes as an array of ushorts from an unmanaged dll.
I have managed to get the data into managed land at a speed which is pretty good.
// cpp .NET
static void imageCallback(unsigned short * rawData, unsigned int length) {
array<unsigned short>^ imageData = gcnew array<unsigned short>(length);
unsigned int headLength = 512; // header length in shorts
pin_ptr<unsigned short> imageDataStart = &imageData[0];
memcpy(imageDataStart, rawData + headLength, length);
callBackDelegate(imageData);
}
The data comes ordered as "RGBRGBRGB...." ushorts for each color channel.
The managed array is then sent to C# via a delegate. Then I have to convert the raw data and stuff it into a (8 bit valued) bitmap via the usual methods like so:
public static Bitmap RGBDataToBitmap(ushort[] data, int Width, int Height, int bitDepth)
{
Bitmap bmp = new Bitmap(Width, Height, PixelFormat.Format32bppArgb);
var rawdata = bmp.LockBits(new Rectangle(Point.Empty, bmp.Size), ImageLockMode.ReadWrite, bmp.PixelFormat);
var pixelSize = rawdata.PixelFormat == PixelFormat.Format32bppArgb ? 4 : 3; // only works with 32 or 24 pixel-size bitmap!
var padding = rawdata.Stride - (rawdata.Width * pixelSize);
var bytes = new byte[rawdata.Height * rawdata.Stride];
var index = 0;
var pixel = 0;
// scale to 8 bits
var scalar = Math.Pow(2, -(bitDepth - 8));
for (var y = 0; y < Height; y++)
{
for (var x = 0; x < Width; x++)
{
int Rlevel = (int)Math.Round(data[pixel + 0] * scalar);
int Glevel = (int)Math.Round(data[pixel + 1] * scalar);
int Blevel = (int)Math.Round(data[pixel + 2] * scalar);
pixel += 3;
bytes[index + 3] = 255; // A component
bytes[index + 2] = Convert.ToByte(Blevel); // B component
bytes[index + 1] = Convert.ToByte(Glevel); // G component
bytes[index + 0] = Convert.ToByte(Rlevel); // R component
index += pixelSize;
}
index += padding;
}
// copy back the bytes from array to the bitmap
System.Runtime.InteropServices.Marshal.Copy(bytes, 0, rawdata.Scan0, bytes.Length);
bmp.UnlockBits(rawdata);
return bmp;
}
If I time this operation (rawdata to bitmap) it takes ~0.5 seconds. The frames are coming in at ~12 times per second so this is too slow.
Does anyone see a way that I make this operation faster in C#? Or does anyone have any guidance for another method?
The goal is to have a live video image in C#.
Thanks!
EDIT:
Per the suggestions below, if I change the for loop to this:
// scale to 8 bits
var bitminus8 = bitDepth - 8;
var scalar = Math.Pow(2, -(bitminus8));
Parallel.For(0, Height, y =>
{
var index = y * Width;
for (var x = 0; x < Width; x++)
{
var idx = index + x;
byte Rlevel = (byte)(data[idx * 3 + 0] >> bitminus8);
byte Glevel = (byte)(data[idx * 3 + 1] >> bitminus8);
byte Blevel = (byte)(data[idx * 3 + 2] >> bitminus8);
bytes[idx * 4 + 3] = 255; // A component
bytes[idx * 4 + 2] = Blevel; // B component
bytes[idx * 4 + 1] = Glevel; // G component
bytes[idx * 4 + 0] = Rlevel; // R component
}
});
This goes from 0.5 seconds to 0.04 seconds. Nice bit about the byte conversion, that made a big difference.

How to set/get pixel from Softwarebitmap

I am trying to change pixels from a Softwarebitmap.
I want to know if there is an equivalent for Bitmap.Get/SetPixel(x,y,color) in Softwarebitmap UWP.
If you want to read and write softwareBitmap that you should use unsafe code.
To use softwareBitmap is hard that you should write some code.
First using some code.
using System.Runtime.InteropServices;
Then create an interface
[ComImport]
[Guid("5B0D3235-4DBA-4D44-865E-8F1D0E4FD04D")]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
unsafe interface IMemoryBufferByteAccess
{
void GetBuffer(out byte* buffer, out uint capacity);
}
You can use this code to change the pixel.
Creating the soft bitmap.
var softwareBitmap = new SoftwareBitmap(BitmapPixelFormat.Bgra8, 100, 100, BitmapAlphaMode.Straight);
Writing pixel.
using (var buffer = softwareBitmap.LockBuffer(BitmapBufferAccessMode.ReadWrite))
{
using (var reference = buffer.CreateReference())
{
unsafe
{
((IMemoryBufferByteAccess) reference).GetBuffer(out var dataInBytes, out _);
// Fill-in the BGRA plane
BitmapPlaneDescription bufferLayout = buffer.GetPlaneDescription(0);
for (int i = 0; i < bufferLayout.Height; i++)
{
for (int j = 0; j < bufferLayout.Width; j++)
{
byte value = (byte) ((float) j / bufferLayout.Width * 255);
dataInBytes[bufferLayout.StartIndex + bufferLayout.Stride * i + 4 * j + 0] = value; // B
dataInBytes[bufferLayout.StartIndex + bufferLayout.Stride * i + 4 * j + 1] = value; // G
dataInBytes[bufferLayout.StartIndex + bufferLayout.Stride * i + 4 * j + 2] = value; // R
dataInBytes[bufferLayout.StartIndex + bufferLayout.Stride * i + 4 * j + 3] = (byte) 255; // A
}
}
}
}
}
You can write the pixel for write the dataInBytes and you should use byte.
For the pixel is BGRA and you should write this byte.
If you want to show it, you need to Convert when the BitmapPixelFormat isnt Bgra8 and the BitmapAlphaMode is Straight and you can use this code.
if (softwareBitmap.BitmapPixelFormat != BitmapPixelFormat.Bgra8 ||
softwareBitmap.BitmapAlphaMode == BitmapAlphaMode.Straight)
{
softwareBitmap = SoftwareBitmap.Convert(softwareBitmap, BitmapPixelFormat.Bgra8, BitmapAlphaMode.Premultiplied);
}
This code can show it to Image.
var source = new SoftwareBitmapSource();
await source.SetBitmapAsync(softwareBitmap);
Image.Source = source;
See: Create, edit, and save bitmap images - UWP app developer

System.OverflowException in one project -- while works in another project

I have a weird problem. I have been searching the internet for a good and fast Gaussian blur algorithm. And I finally found one!
So what I did was to try it in a new project - it worked just fine.
When I then was going to import the class into my main project and then tried it, I got a System.OverflowException. I'm finding this really weird that it works in one project, but not in another.
Here's the algorithm:
(I am calling the function almost the same. I tried calling it the exact same way, but I still got an exception)
public static void FastBlur(Bitmap SourceImage, int radius)
{
var rct = new Rectangle(0, 0, SourceImage.Width, SourceImage.Height);
var dest = new int[rct.Width * rct.Height];
var source = new int[rct.Width * rct.Height];
var bits = SourceImage.LockBits(rct, ImageLockMode.ReadWrite, PixelFormat.Format32bppArgb);
Marshal.Copy(bits.Scan0, source, 0, source.Length);
SourceImage.UnlockBits(bits);
if (radius < 1) return;
int w = rct.Width;
int h = rct.Height;
int wm = w - 1;
int hm = h - 1;
int wh = w * h;
int div = radius + radius + 1;
var r = new int[wh];
var g = new int[wh];
var b = new int[wh];
int rsum, gsum, bsum, x, y, i, p1, p2, yi;
var vmin = new int[max(w, h)];
var vmax = new int[max(w, h)];
var dv = new int[256 * div];
for (i = 0; i < 256 * div; i++)
{
dv[i] = (i / div);
}
int yw = yi = 0;
for (y = 0; y < h; y++)
{ // blur horizontal
rsum = gsum = bsum = 0;
for (i = -radius; i <= radius; i++)
{
int p = source[yi + min(wm, max(i, 0))];
rsum += (p & 0xff0000) >> 16;
gsum += (p & 0x00ff00) >> 8;
bsum += p & 0x0000ff;
}
for (x = 0; x < w; x++)
{
r[yi] = dv[rsum];
g[yi] = dv[gsum];
b[yi] = dv[bsum];
if (y == 0)
{
vmin[x] = min(x + radius + 1, wm);
vmax[x] = max(x - radius, 0);
}
p1 = source[yw + vmin[x]];
p2 = source[yw + vmax[x]];
rsum += ((p1 & 0xff0000) - (p2 & 0xff0000)) >> 16;
gsum += ((p1 & 0x00ff00) - (p2 & 0x00ff00)) >> 8;
bsum += (p1 & 0x0000ff) - (p2 & 0x0000ff);
yi++;
}
yw += w;
}
for (x = 0; x < w; x++)
{ // blur vertical
rsum = gsum = bsum = 0;
int yp = -radius * w;
for (i = -radius; i <= radius; i++)
{
yi = max(0, yp) + x;
rsum += r[yi];
gsum += g[yi];
bsum += b[yi];
yp += w;
}
yi = x;
for (y = 0; y < h; y++)
{
dest[yi] = (int)(0xff000000u | (uint)(dv[rsum] << 16) | (uint)(dv[gsum] << 8) | (uint)dv[bsum]); // <--- Here's where I get the exception
if (x == 0)
{
vmin[y] = min(y + radius + 1, hm) * w;
vmax[y] = max(y - radius, 0) * w;
}
p1 = x + vmin[y];
p2 = x + vmax[y];
rsum += r[p1] - r[p2];
gsum += g[p1] - g[p2];
bsum += b[p1] - b[p2];
yi += w;
}
}
// copy back to image
var bits2 = SourceImage.LockBits(rct, ImageLockMode.ReadWrite, PixelFormat.Format32bppArgb);
Marshal.Copy(dest, 0, bits2.Scan0, dest.Length);
SourceImage.UnlockBits(bits);
}
Here's how I call the function:
public Bitmap DownloadBlurredImage(List<string> uris, int blurradius)
{
Uri urlUri = new Uri(uris.ElementAt(0));
if (RandomSelect == true)
{
Random r = new Random();
urlUri = new Uri(uris.ElementAt(r.Next(0, uris.Count - 1)));
}
WebRequest webRequest = WebRequest.CreateDefault(urlUri);
webRequest.ContentType = "image/jpeg";
WebResponse webResponse = webRequest.GetResponse();
Stream mystream;
if ((mystream = webResponse.GetResponseStream()) != null)
{
Bitmap b = new Bitmap(mystream);
imageFilters.FastBlur(b, 10); //<--- here's where I'm calling
return b;
}
else
{
return null;
}
}
Does anyone know why this is acting up? I myself have NO clue..
You're using signed 'ints' to hold unsigned data - if the MSB is set, the overflow will occur. Use unsigned ints (for example for dest), or surround in an unchecked { } directive.

Converting a bitmap to monochrome

I am trying to save an image as monochrome (black&white, 1 bit-depth) but I'm coming up lost how to do it.
I am starting with a png and converting to a bitmap for printing (it's a thermal printer and only supports black anyway - plus its slow as hell for large images if I try to send them as color/grayscale).
My code so far is dead simple to convert it to a bitmap, but it is retaining the original colour depth.
Image image = Image.FromFile("C:\\test.png");
byte[] bitmapFileData = null;
int bitsPerPixel = 1;
int bitmapDataLength;
using (MemoryStream str = new MemoryStream())
{
image.Save(str, ImageFormat.Bmp);
bitmapFileData = str.ToArray();
}
Here's some code I put together that takes a full colour (24 bits/pixel) image, and converts it to a 1 bit/pixel output bitmap, applying a standard RGB to greyscale conversion, and then using Floyd-Steinberg to convert greyscale to the 1 bit/pixel output.
Note that this should by no means be considered an "ideal" implementation, but it does work. There are a number of improvements that could be applied if you wanted. For example, it copies the entire input image into the data array, whereas we really only need to keep two lines in memory (the "current" and "next" lines) for accumulating the error data. Despite this, performance seems acceptable.
public static Bitmap ConvertTo1Bit(Bitmap input)
{
var masks = new byte[] { 0x80, 0x40, 0x20, 0x10, 0x08, 0x04, 0x02, 0x01 };
var output = new Bitmap(input.Width, input.Height, PixelFormat.Format1bppIndexed);
var data = new sbyte[input.Width, input.Height];
var inputData = input.LockBits(new Rectangle(0, 0, input.Width, input.Height), ImageLockMode.ReadOnly, PixelFormat.Format24bppRgb);
try
{
var scanLine = inputData.Scan0;
var line = new byte[inputData.Stride];
for (var y = 0; y < inputData.Height; y++, scanLine += inputData.Stride)
{
Marshal.Copy(scanLine, line, 0, line.Length);
for (var x = 0; x < input.Width; x++)
{
data[x, y] = (sbyte)(64 * (GetGreyLevel(line[x * 3 + 2], line[x * 3 + 1], line[x * 3 + 0]) - 0.5));
}
}
}
finally
{
input.UnlockBits(inputData);
}
var outputData = output.LockBits(new Rectangle(0, 0, output.Width, output.Height), ImageLockMode.WriteOnly, PixelFormat.Format1bppIndexed);
try
{
var scanLine = outputData.Scan0;
for (var y = 0; y < outputData.Height; y++, scanLine += outputData.Stride)
{
var line = new byte[outputData.Stride];
for (var x = 0; x < input.Width; x++)
{
var j = data[x, y] > 0;
if (j) line[x / 8] |= masks[x % 8];
var error = (sbyte)(data[x, y] - (j ? 32 : -32));
if (x < input.Width - 1) data[x + 1, y] += (sbyte)(7 * error / 16);
if (y < input.Height - 1)
{
if (x > 0) data[x - 1, y + 1] += (sbyte)(3 * error / 16);
data[x, y + 1] += (sbyte)(5 * error / 16);
if (x < input.Width - 1) data[x + 1, y + 1] += (sbyte)(1 * error / 16);
}
}
Marshal.Copy(line, 0, scanLine, outputData.Stride);
}
}
finally
{
output.UnlockBits(outputData);
}
return output;
}
public static double GetGreyLevel(byte r, byte g, byte b)
{
return (r * 0.299 + g * 0.587 + b * 0.114) / 255;
}
What you want is a good dithering algorithm like Floyd-Steinberg or Bayer ordered. You can either implement the binarization yourself or use a library like AForge.NET to do it for you (download the image processing samples). You can find the binarization documentation here.

Categories