Insufficient buffer size using WriteableBitmap? - c#

I am modifying the ColorBasic Kinect example in order to display an image overlaid to the video stream. So what I've done is to load an image with transparent background (now a GIF but it may change), and write to the displayed bitmap.
The error I'm getting is that the buffer I'm writing to is too small.
I cannot see what the actual error is (I'm a complete newbie in XAML/C#/Kinect), but the WriteableBitmap is 1920x1080, and the bitmap I want to copy is 200x200, so why am I getting this error? I cannot see how a transparent background could be of any harm, but I am beginning to suspect that...
Note that without the last WritePixels, the code works and I see the webcam's output. My code follows.
The overlay image:
public BitmapImage overlay = new BitmapImage(new Uri("C:\\users\\user\\desktop\\something.gif"));
The callback function that displays the Kinect's webcam (see the default example ColorBasic) with my very small modifications:
private void Reader_ColorFrameArrived(object sender, ColorFrameArrivedEventArgs e)
{
// ColorFrame is IDisposable
using (ColorFrame colorFrame = e.FrameReference.AcquireFrame())
{
if (colorFrame != null)
{
FrameDescription colorFrameDescription = colorFrame.FrameDescription;
using (KinectBuffer colorBuffer = colorFrame.LockRawImageBuffer())
{
this.colorBitmap.Lock();
// verify data and write the new color frame data to the display bitmap
if ((colorFrameDescription.Width == this.colorBitmap.PixelWidth) && (colorFrameDescription.Height == this.colorBitmap.PixelHeight))
{
colorFrame.CopyConvertedFrameDataToIntPtr(
this.colorBitmap.BackBuffer,
(uint)(colorFrameDescription.Width * colorFrameDescription.Height * 4),
ColorImageFormat.Bgra);
this.colorBitmap.AddDirtyRect(new Int32Rect(0, 0, this.colorBitmap.PixelWidth, this.colorBitmap.PixelHeight));
}
if(this.overlay != null)
{
// Calculate stride of source
int stride = overlay.PixelWidth * (overlay.Format.BitsPerPixel / 8);
// Create data array to hold source pixel data
byte[] data = new byte[stride * overlay.PixelHeight];
// Copy source image pixels to the data array
overlay.CopyPixels(data, stride, 0);
this.colorBitmap.WritePixels(new Int32Rect(0, 0, overlay.PixelWidth, overlay.PixelHeight), data, stride, 0);
}
this.colorBitmap.Unlock();
}
}
}
}

Your overlay.Format.BitsPerPixel / 8 will be 1 (because it's a gif), but you're trying to copy it to something that is not a gif, probably BGRA (32 bit). Thus you got a huge difference in size (4x).
.WritePixels should take in the stride value of the destination buffer, but you past it the stride value of the overlay (this can cause weird problems as well).
And finally, even if it went 100% smooth your overlay will not actually "overlay" anything, it will replace -- since I don't see any alpha bending math in your code.
Switch your .gif to a .png (32bit) and see if that helps.
Also, if you're looking for an AlphaBltMerge type code: I wrote the entire thing here.. it's very easy to understand.
Merge 2 - 32bit Images with Alpha Channels

Related

Show byte array image in WPF with high refresh rate

Context
I have a Basler camera that throw an event when a new image is captured.
In the event arg, I can get the image grabbed as a byte array.
I have to do computation on this image and then show it in a WPF application. The camera refresh rate is up to 40FPS.
Issue and found solution
A solution to convert a byte array to a WPF image can be found here : Convert byte array to image in wpf
This solution is great to convert only one time the byte array, however I feel like there is a lot of memory loss to do it at 40FPS. A new BitmapImage() is created every time and can't be disposed.
Would there be a better solution to display in WPF a byte array that changes up to 40 FPS ? (the way the problem is handled can be completely rethought)
Code
This solution to show the camera stream in WPF works, but the BitmapImage image = new BitmapImage(); line doesn't look good to me.
private void OnImageGrabbed(object sender, ImageGrabbedEventArgs e)
{
// Get the result
IGrabResult grabResult = e.GrabResult;
if (!grabResult.GrabSucceeded)
{
throw new Exception($"Grab error: {grabResult.ErrorCode} {grabResult.ErrorDescription}");
}
// Make process on the image
imageProcessor.Process(grabResult);
// Convert grabResult in BGR 8bit format
using Bitmap bitmap = new Bitmap(grabResult.Width, grabResult.Height, PixelFormat.Format32bppRgb);
BitmapData bmpData = bitmap.LockBits(new Rectangle(0, 0, bitmap.Width, bitmap.Height), ImageLockMode.ReadWrite, bitmap.PixelFormat);
IntPtr ptrBmp = bmpData.Scan0;
converter.Convert(ptrBmp, bmpData.Stride * bitmap.Height, grabResult);
bitmap.UnlockBits(bmpData);
// Creat the BitmapImage
BitmapImage image = new BitmapImage(); // <-- never Disposed !
using (MemoryStream memory = new MemoryStream())
{
bitmap.Save(memory, ImageFormat.Bmp);
memory.Position = 0;
image.BeginInit();
image.StreamSource = memory;
image.CacheOption = BitmapCacheOption.OnLoad;
image.EndInit();
image.Freeze();
}
LastFrame = image; // View is binded to LastFrame
}
I would suggest that you use a WriteableBitmap to display the result. This avoids the need to reallocate the UI image. If the pixel format in your source matches the one in the bitmap you can simply use WritePixels to update the image.
Note that you can only modify WriteableBitmap from the main thread, and the ImageGrabbed event will be raised on a background thread. And the grabResult will be disposed of once the event handler returns. So you will need to ask the UI thread to do the actual updating, and you will need a intermediate buffer for this. But this buffer can be pooled if needed.
An alternative might be to write your own loop, calling RetrieveResult repeatedly, this would let you dispose the grab results manually, after the UI has been updated. It might also be possible to keep a pool of WriteableBitmaps, I guess it should be safe to write to if it is not actually used by the UI.
On each frame, you are
creating a Bitmap
encoding it into a MemoryStream
creating a BitmapImage
decoding the MemoryStream into the BitmapImage
Better create a WritableBitmap once, and repeatedly call its WritePixels method.
You may still need to convert the raw buffer, since WPF does not seem to have an equivalent for PixelFormat.Format32bppRgb - or it is perhaps PixelFormats.Bgr32.
var wb = LastFrame as WriteableBitmap;
if (wb == null)
{
wb = new WriteableBitmap(
grabResult.Width, grabResult.Height,
96, 96, PixelFormats.Bgr32, null);
LastFrame = wb;
}
wb.WritePixels(...);
I am in a similar situation: Pulling live images off a camera and dumping them to the UI for "live" view. I spent a good deal of time trying to find the most efficient solution. For me, the turned out to be BitmapSource.Create. I take the raw array of bytes (plus a structure describing image characteristics like width, height, etc) and use one function call to convert it to a BitmapSource.
Now in my case, the images are greyscale, 8-bit images so if you're trying to show colors, your arguments would be different. But here's a snippet of what I do.
public class XimeaCameraImage : ICameraImage
{
public unsafe XimeaCameraImage(byte[] imageData, ImgParams imageParams)
{
Data = imageData;
var fmt = PixelFormats.Gray8;
var width = imageParams.GetWidth();
var bitsPerPixel = 8; // TODO: Get ready for other image formats
var height = imageParams.GetHeight();
var stride = (((bitsPerPixel * width) + 31) / 32) * 4;
var dpi = 96.0;
// Copy the raw, unmanaged, image data from the Sdk.Image object.
Source = BitmapSource.Create(
width,
height,
dpi,
dpi,
fmt,
BitmapPalettes.Gray256,
imageData,
stride);
Source.Freeze();
}
public byte[] Data { get; }
public BitmapSource Source { get; }
}

Implementing streaming video for Windows 10 UAP

I need to display in XAML a video stream coming from some network source. Video frames can come at undefined intervals. They're already assembled, decoded and presented in BGRA8 form in memory mapped file. XAML frontend is in C#, backend is written in C using WinAPI.
In C# I have a handle of this file.
Previously in .NET 4.5 I was creating InteropBitmap from this handle with System.Windows.Interop.Imaging.CreateBitmapSourceFromMemorySection and called Invalidate on arriving of new frame. Than I used this InteropBitmap as Source for XAML Image.
Now I need to do the same but for Windows 10 UAP platform.
There are no memory mapped files in .NET Core so I created a CX Windows Runtime Component. Here's most important part of it.
static byte* GetPointerToPixelData(IBuffer^ pixelBuffer, unsigned int *length)
{
if (length != nullptr)
{
*length = pixelBuffer->Length;
}
// Query the IBufferByteAccess interface.
ComPtr<IBufferByteAccess> bufferByteAccess;
reinterpret_cast<IInspectable*>(pixelBuffer)->QueryInterface(IID_PPV_ARGS(&bufferByteAccess));
// Retrieve the buffer data.
byte* pixels = nullptr;
bufferByteAccess->Buffer(&pixels);
return pixels;
}
void Adapter::Invalidate()
{
memcpy(m_bitmap_ptr, m_image, m_sz);
m_bitmap->Invalidate();
}
Adapter::Adapter(int handle, int width, int height)
{
m_sz = width * height * 32 / 8;
// Read access to mapped file
m_image = MapViewOfFile((HANDLE)handle, FILE_MAP_READ, 0, 0, m_sz);
m_bitmap = ref new WriteableBitmap(width, height);
m_bitmap_ptr = GetPointerToPixelData(m_bitmap->PixelBuffer, 0);
}
Adapter::~Adapter()
{
if ( m_image != NULL )
UnmapViewOfFile(m_image);
}
Now I can use m_bitmap as Source for XAML Image ( and don't forget to raise property change on invalidate otherwise image won't update ).
Is there a better or more standard way? How can I create WriteableBitmap from m_image so I won't need additional memcpy on invalidate?
UPDATE: I wonder if I can use MediaElement to display sequence of uncompressed bitmaps and get any benefits from it? MediaElement supports filters which is a very nice feature.

Zxing weird image reverse

I am writing C# lib for very simple recognize image to use it in monodroid and also using zxing port to C#. But after I read image bytes from file I do such thing, same as in zxing barcode scanning.
binaryBitmap = new BinaryBitmap(new HybridBinarizer(new RGBLuminanceSource(rawRgb, width, height, format)));
But somehow it reverse image by vertical. I just saving binaryBitmap as bitmap to file by pixels.
Please help me understand why it's happen? What am I doing wrong?
#Michael am using Zxing.Net.Mobile port, from here https://github.com/Redth/ZXing.Net.Mobile. It's very weird for me it I am using PlanarYUVLuminanceSource - then I get such image http://i.imgur.com/OlwqC0I.png, but if I am using RGBLuminanceSource then I get full almost normal image, see example image. so now I have even 2 questions:
why planar take only part of image and have "layer on layer" effect? and
ok if I will use RGBLuminanceSource then, why it have some invertion of colors, I mean somewhere rectangles border is black and somewhere they are white. because it real image they all black?
UPDATE:
Here is how I get bytes from device and also as you see I set nv21 format, so it must be YUV, no? I wonder, what I am doing wrong that rgb source work(at list image is ok) and PLanarYUV not :((
BTW, original byte from preview frame have result and same file size.
Any suggestion?
public void OnPreviewFrame(byte[] bytes, Android.Hardware.Camera camera)
{
var img = new YuvImage(bytes, ImageFormatType.Nv21, cameraParameters.PreviewSize.Width, cameraParameters.PreviewSize.Height, null); string _fileName2 = "YUV_BYtes_"+ DateTime.Now.Ticks +".txt";
string pathToFile2 = Path.Combine(Android.OS.Environment.ExternalStorageDirectory.AbsolutePath, _fileName2);
using (var fileStream = new FileStream(pathToFile2, FileMode.Append, FileAccess.Write, FileShare.None))
{
fileStream.Write(img.GetYuvData(), 0, img.GetYuvData().Length);
}
}
public void SurfaceChanged(ISurfaceHolder holder, global::Android.Graphics.Format format, int width, int height)
{
if (camera == null)
return;
var parameters = camera.GetParameters();
width = parameters.PreviewSize.Width;
height = parameters.PreviewSize.Height;
parameters.PreviewFormat = ImageFormatType.Nv21;
//parameters.PreviewFrameRate = 15;
//this.height = size.height;
//this.width = size.width;
//camera.setParameters( params );
//parameters.PreviewFormat = ImageFormatType.;
camera.SetParameters(parameters);
camera.SetDisplayOrientation(90);
camera.StartPreview();
cameraResolution = new Size(parameters.PreviewSize.Width, parameters.PreviewSize.Height);
AutoFocus();
}
I think I know what you have done. The data looks like RGB565 bitmap data (or something similar). You can't put such a byte array into the PlanarYUVLuminanceSource. You have to make sure that the byte array which you use with the planar source is really a array with only yuv data, not RGB565.
The rules are easy:
if you use the following code snippet
new RGBLuminanceSource(rawRgb, width, height, format)
make sure that the value of format matches the layout and data of the parameter rawRgb.
if you use somethin glike the following
new PlanarYUVLuminanceSource(yuvBytes, 640, 960, 0, 0, 640, 960, false);
make sure that yuvBytes only contains real yuv data.
I can only give a better answer if you post a more complete code sample.

Is there a way to resize an image using GPU?

Is there a way to resize an image using GPU (graphic card) that is consumable through a .NET application?
I am looking for an extremely performant way to resize images and have heard that the GPU could do it much quicker than CPU (GDI+ using C#). Are there known implementations or sample code using the GPU to resize images that I could consume in .NET?
Have you thought about using XNA to resize your images? Here you can find out how to use XNA to save image as a png/jpeg to a MemoryStream and later reuse it a Bitmap object:
EDIT: I will post an example here (taken from the link above) on how you can possibly use XNA.
public static Image Texture2Image(Texture2D texture)
{
Image img;
using (MemoryStream MS = new MemoryStream())
{
texture.SaveAsPng(MS, texture.Width, texture.Height);
//Go To the beginning of the stream.
MS.Seek(0, SeekOrigin.Begin);
//Create the image based on the stream.
img = Bitmap.FromStream(MS);
}
return img;
}
I also found out today that you can OpenCV to use GPU/multicore CPUs. You can for example choose to use a .NET wrapper such as Emgu and and use its Image class to manipulate with your picture and return a .NET Bitmap class:
public static Bitmap ResizeBitmap(Bitmap sourceBM, int width, int height)
{
// Initialize Emgu Image object
Image<Bgr, Byte> img = new Image<Bgr, Byte>(sourceBM);
// Resize using liniear interpolation
img.Resize(width, height, INTER.CV_INTER_LINEAR);
// Return .NET Bitmap object
return img.ToBitmap();
}
I wrote a quick spike to check performance using WPF, though I cannot for sure say that its using the GPU.
Still, see below. This scales an image to 33.5 (or whatever) times its original size.
public void Resize()
{
double scaleFactor = 33.5;
var originalFileStream = System.IO.File.OpenRead(#"D:\SkyDrive\Pictures\Random\Misc\DoIt.jpg");
var originalBitmapDecoder = JpegBitmapDecoder.Create(originalFileStream, BitmapCreateOptions.None, BitmapCacheOption.OnLoad);
BitmapFrame originalBitmapFrame = originalBitmapDecoder.Frames.First();
var originalPixelFormat = originalBitmapFrame.Format;
TransformedBitmap transformedBitmap =
new TransformedBitmap(originalBitmapFrame, new System.Windows.Media.ScaleTransform()
{
ScaleX = scaleFactor,
ScaleY = scaleFactor
});
int stride = ((transformedBitmap.PixelWidth * transformedBitmap.Format.BitsPerPixel) + 7) / 8;
int pixelCount = (stride * (transformedBitmap.PixelHeight - 1)) + stride;
byte[] buffer = new byte[pixelCount];
transformedBitmap.CopyPixels(buffer, stride, 0);
WriteableBitmap transformedWriteableBitmap = new WriteableBitmap(transformedBitmap.PixelWidth, transformedBitmap.PixelHeight, transformedBitmap.DpiX, transformedBitmap.DpiY, transformedBitmap.Format, transformedBitmap.Palette);
transformedWriteableBitmap.WritePixels(new Int32Rect(0, 0, transformedBitmap.PixelWidth, transformedBitmap.PixelHeight), buffer, stride, 0);
BitmapFrame transformedFrame = BitmapFrame.Create(transformedWriteableBitmap);
var jpegEncoder = new JpegBitmapEncoder();
jpegEncoder.Frames.Add(transformedFrame);
using (var outputFileStream = System.IO.File.OpenWrite(#"C:\DATA\Scrap\WPF.jpg"))
{
jpegEncoder.Save(outputFileStream);
}
}
The image I was testing was 495 x 360. It resized it to over 16k x 12k in a couple of seconds, including save out.
It resizes to 1.5x around 165 times a second in a single-core run. On an i7 and the GPU seemingly doing nothing, CPU at 20% I'd expect to get 5x more when multithreaded.
Performance profiling shows a hot path to wpfgfx_v0400.dll which is the native WPF graphics library and is close to DirectX (look-up 'milcore' in Google).
So it might be accelerated, I don't know.
Luke
Yes, it is possible to use GPU to resize your images. This can be done using DirectX Surfaces (for example using SlimDx in C#). You should create a surface and move your image to it, and then you can stretch this surface to another target surface of your desired size using only GPU, and finally get back the resized image from the target surface. In these scenario, pixel format of the surfaces can be different and the GPU automatically handles it. But here there are things that can affect the performance of this operation. Moving data between GPU and CPU is a time consuming process. You can apply some techniques to boost performance based on your situation, and avoiding extra data transfer between CPU and GPU memory.

what is the reason of increasing image size in rotation?

I have a c# application that contains an image gallery where I display some pictures.
This gallery have some features including left and right rotation.
everything is perfect but when I choose a picture from gallery and press rotation button (regardless left or right rotation), size of the picture increase significantly.
It should be mentioned that the picture's format is JPEG.
Size of picture before rotation : 278 kb
Size of picture after rotation : 780 kb
My code for rotation is like bellow :
public Image apply(Image img)
{
Image im = img;
if (rotate == 1) im.RotateFlip(RotateFlipType.Rotate90FlipNone);
if (rotate == 2) im.RotateFlip(RotateFlipType.Rotate180FlipNone);
if (rotate == 3) im.RotateFlip(RotateFlipType.Rotate270FlipNone);
//file size is increasing after RotateFlip method
if (brigh != DEFAULT_BRIGH ||
contr != DEFAULT_CONTR ||
gamma != DEFAULT_GAMMA)
{
using (Graphics g = Graphics.FromImage(im))
{
float b = _brigh;
float c = _contr;
ImageAttributes derp = new ImageAttributes();
derp.SetColorMatrix(new ColorMatrix(new float[][]{
new float[]{c, 0, 0, 0, 0},
new float[]{0, c, 0, 0, 0},
new float[]{0, 0, c, 0, 0},
new float[]{0, 0, 0, 1, 0},
new float[]{b, b, b, 0, 1}}));
derp.SetGamma(_gamma);
g.DrawImage(img, new Rectangle(Point.Empty, img.Size),
0, 0, img.Width, img.Height, GraphicsUnit.Pixel, derp);
}
}
return im;
}
What is the problem?
In your case applying RotateFlip on im is changing the ImageFormat from Jpeg to MemoryBmp.
By Default when you save the image it is going to make use of the default ImageFormat. This will be the format returned by im.RawFormat
if you check the GUID im.RawFormat.Guid
Before RotateFlip
{b96b3cae-0728-11d3-9d7b-0000f81ef32e}
which is same as ImageFormat.Jpeg.Guid
After RotateFlip
{b96b3caa-0728-11d3-9d7b-0000f81ef32e}
which is same as ImageFormat.MemoryBmp.Guid
At the time of saving the image pass the ImageFormat as the second parameter which will ensure that it uses the correct format. If not mentioned it is going to be the one in im.RawFormat
So If you want to save as jpeg at the time of saving call
im.Save("filename.jpg", ImageFormat.Jpeg);
This time the file size should be less than the original size.
Also note ImageFormat is in System.Drawing.Imaging namespace
NOTE
To control the quality of the jpeg make use of the overloaded Save method as mentioned in this MSDN Link
EDIT Based On Comment
OK assuming you are using SQL Server you must be having a image datatype column (it is recommended to use varbinary(max) instead of image as in future it is going to be obselete (Read MSDN Post)
Now to the steps
1) read the contents as a stream / byte[] array
2) convert this to Image
3) perform rotate operation on the Image
4) convert this Image back to stream / byte[] array
5) Update the database column with the new value
Two reasons:
The JPEG compression/encoding/sampling is not optimized as the original JPEG.
JPEG is not transparent. When an image is not rotated 90/180/270 degrees, the rectangular boundary of the image becomes larger.
You should to keep raw ImageFormat before change the image, and save to file by raw image format. Like bellow code :
using (Image image = Image.FromFile(filePath))
{
var rawFormat = image.RawFormat;
image.RotateFlip(angel);
image.Save(filePath, rawFormat);
}

Categories

Resources