WriteableBitmap PixelBuffer Stream Length Too Small - c#

I'm running into an issue where I'm trying to copy the pixel buffer for one WriteableBitmap over to another WriteableBitmap essentially giving a copy of the WriteableBitmap object. However, when I try to do this I run into an issue where the second WriteableBitmap's stream length is too short to hold all the values of the first WriteableBitmap.
I posted my code below. Keep in mind that I'm capturing the original data from a webcam. However, when I compare the "ps" object's stream size to wb1 and wb2, ps's size is much smaller than both of them. What I'm confused about is why wb2 stream size is smaller than wb1's. Thanks for any help.
private MemoryStream originalStream = new MemoryStream();
WriteableBitmap wb1 = new WriteableBitmap((int)photoBox.Width, (int)photoBox.Height);
WriteableBitmap wb2 = new WriteableBitmap((int)photoBox.Width, (int)photoBox.Height);
ImageEncodingProperties imageProperties = ImageEncodingProperties.CreateJpeg();
var ps = new InMemoryRandomAccessStream();
await mc.CapturePhotoToStreamAsync(imageProperties, ps);
await ps.FlushAsync();
ps.Seek(0);
wb1.SetSource(ps);
(wb1.PixelBuffer.AsStream()).CopyTo(originalStream); // this works
originalStream.Position = 0;
originalStream.CopyTo(wb2.PixelBuffer.AsStream()); // this line gives me the error: "Unable to expand length of this stream beyond its capacity"
Image img = new Image();
img.Source = wb2; // my hope is to treat this as it's own entity and modify this image independently of wb1 or originalStream
photoBox.Source =wb1;

Note that when you do new WriteableBitmap(w, h) and then call SetSource() to an image of a different resolution - the bitmap's size will change (it won't be the w x h passed in the constructor). It's likely that your photoBox.Width/Height are different than what your CapturePhotoToStreamAsync() call returns (I am assuming the image is captured at the default or preconfigured camera settings, while photoBox is just a control on screen).
How about just doing someting like this
ps.Seek(0);
wb1.SetSource(ps);
ps.Seek(0);
wb2.SetSource(ps);

I think you should create a writter from the PixelBuffer and use it to copy the stream.
The AsStream method should be used to read the buffer, not to write into it.
Have a look to
http://social.msdn.microsoft.com/Forums/en-NZ/winappswithcsharp/thread/2b499ac5-8bc8-4259-a144-842bd756bfe2
for a piece of code

Related

Why does writing an image to a stream give me a different result than directly saving to a file?

I am trying to add some rectangles to an existing image. When using the following code, everything works fine:
var bytes = File.ReadAllBytes("myPath\\input.jpg");
var stream = new MemoryStream(bytes);
using (var i = new Bitmap(stream))
{
using (var graphics = Graphics.FromImage(i))
{
var selPen = new Pen(Color.Blue);
graphics.DrawRectangle(selPen, 10, 10, 50, 50);
i.Save("myPath\\output.jpg", ImageFormat.Jpeg);
}
}
But saving the image to the same MemoryStream and then later writing all bytes to a file gives me an almost grey-only image.
This does not work:
var bytes = File.ReadAllBytes("myPath\\input.jpg");
var stream = new MemoryStream(bytes);
using (var i = new Bitmap(stream))
{
using (var graphics = Graphics.FromImage(i))
{
var selPen = new Pen(Color.Blue);
graphics.DrawRectangle(selPen, 10, 10, 50, 50);
i.Save(stream, ImageFormat.Jpeg);
}
}
File.WriteAllBytes("myPath\\output.jpg", stream.ToArray());
The (wrong) image looks like this:
As you can see, only part of the image is grey. There still is some part visible (the white one) of the actual image.
Why is this happening and what is the correct solution?
Thanks!
You've double-written to the Stream in the second example; it still contains the original data, and then you've appended more data with the Save. Stream works like a video tape (sort of). If you want to overwrite the stream, you need to do that very carefully (and: not all streams even support that concept - think "network stream", "encryption stream", etc). Note that ToArray (and the GetBuffer / TryGetBuffer methods) see all the data, not just what you're thinking of as the "new" data (a concept that doesn't even exist, really - like a video tape, you only have the "current" position and the length - if you need to know where the first show ends and the second show starts, you need to note that yourself, manually). In this case, adding:
stream.Position = 0; // rewind
stream.SetLength(0); // truncate (important in case the new data is *shorter* than the old)
after reading it and before Save, should fix it.
You are saving the image to an already initialized stream from a byte array.
Create a new stream and save to it.
var stream2 = new MemoryStream();
i.Save(stream2, ImageFormat.Jpeg);
Or simply reset the previous one
memoryStream = new MemoryStream(stream.Capacity());

WriteableBitmap renders PNG incorrectly

I am having trouble rendering PNGs that use Palette as "Color Type". Here is some simple code to reproduce the issue:
private async System.Threading.Tasks.Task Fetch()
{
HttpClient httpClient = new HttpClient();
Uri uri = new Uri("http://static.splashnology.com/articles/How-to-Optimize-PNG-and-JPEG-without-Quality-Loss/PNG-Palette.png");
HttpResponseMessage response = await httpClient.GetAsync(uri);
if (response.StatusCode == HttpStatusCode.Ok)
{
try
{
var content = await response.Content.ReadAsBufferAsync();
WriteableBitmap image = await BitmapFactory.New(1, 1).FromStream(content.AsStream());
Rect destination = new Rect(0, 0, image.PixelWidth, image.PixelHeight);
Rect source = new Rect(0, 0, image.PixelWidth, image.PixelHeight);
WriteableBitmap canvas = new WriteableBitmap(image.PixelWidth, image.PixelHeight);
canvas.Blit(destination, image, source);
RadarImage.Source = canvas;
}
catch (Exception e)
{
System.Diagnostics.Debug.WriteLine(e.Message);
System.Diagnostics.Debug.WriteLine(e.StackTrace);
}
}
}
If I run that code using Windows Phone 8.1, the image appears using wrong colors. If I do the same test using a PNG that is using RGB as "Color Type", then everything is works fine.
I have looked at Codeplex forum and haven't seen any post related to that. I have reported it as an issue although it might be related to the way I'm rendering it. Is there any mistake in the way I'm using WriteableBitmap that could cause the wrong rendering?
UPDATE
According to this discussion
https://writeablebitmapex.codeplex.com/discussions/274445
the issue is related to an unexpected order of the bytes. These comments are from one and a half years ago, so I think there should be a proper fix somewhere...
The image that is rendered incorrectly is in the code above.
This one, using the same code, is rendered correctly.
http://www.queness.com/resources/images/png/apple_ex.png
The only difference between these two images is the "Color Type" property. The one that fails is set to "Palette" and the one rendered correctly is set to "RGB Alpha".
Thanks!
Carlos.
The problem appears to be in the FromStream extension, which seems to translate the paletted png to RGBA. As you note, WriteableBitmap wants BGRA. I suspect FromStream passes non-palette pngs' pixels unswizzled. This lets the apple start and finish as BGRA while the monkey ends up RGBA and draws with red and blue reversed.
You can bypass this problem by skipping FromStream and using a BitmapDecoder so you specify the format you want it to decode into:
// Read the retrieved image's bytes and write them into an IRandomAccessStream
IBuffer buffer = await response.Content.ReadAsBufferAsync();
var randomAccessStream = new InMemoryRandomAccessStream();
await randomAccessStream.WriteAsync(buffer);
// Decode the downloaded image as Bgra8 with premultiplied alpha
// GetPixelDataAsync lets us pass in the desired format and it'll do the magic to translate as it decodes
BitmapDecoder decoder = await BitmapDecoder.CreateAsync(randomAccessStream);
var pixelData = await decoder.GetPixelDataAsync(BitmapPixelFormat.Bgra8, BitmapAlphaMode.Premultiplied, new BitmapTransform(), ExifOrientationMode.IgnoreExifOrientation, ColorManagementMode.DoNotColorManage);
// Get the decoded bytes
byte[] imageData = pixelData.DetachPixelData();
// And stick them in a WriteableBitmap
WriteableBitmap image = new WriteableBitmap((int)decoder.PixelWidth,(int) decoder.PixelHeight);
Stream pixelStream = image.PixelBuffer.AsStream();
pixelStream.Seek(0, SeekOrigin.Begin);
pixelStream.Write(imageData, 0, imageData.Length);
// And stick it in an Image so we can see it.
RadarImage.Source = image;

Windows Store - Pixels Array

I am working at an app where I need to get the array of pixels from an Image and to edit the Image using the pixels array.
I am using the next code for getting the pixels array from the StorageFile object which indicates the image:
public static async Task<byte[]> GetPixelsArrayFromStorageFileAsync(
IRandomAccessStreamReference file)
{
using (IRandomAccessStream stream = await file.OpenReadAsync())
{
using (var reader = new DataReader(stream.GetInputStreamAt(0)))
{
await reader.LoadAsync((uint)stream.Size);
var pixelByte = new byte[stream.Size];
reader.ReadBytes(pixelByte);
return pixelByte;
}
}
}
Now, my questions are:
Why if I load a image which is 6000 x 4000 pixels I have an array of just 8,941,799 which is actually the size of my image on disk?
How can I access the RGBA channels of the pixels?
Your file has a compressed version of the bitmap, so you need to decode it first. I'd suggest loading it into a WriteableBitmap since you need to display it anyway and then access the PixelBuffer property of the bitmap to get the actual pixels. You could do something like this:
var writeableBitmap = new WriteableBitmap(1, 1);
await writeableBitmap.SetSourceAsync(yourFileStream);
var pixelStream = writeableBitmap.PixelBuffer.AsStream();
var bytes = new byte[pixelStream.Length];
pixelStream.Seek(0, SeekOrigin.Begin);
pixelStream.Read(bytes, 0, Bytes.Length);
// Update the bytes here. I think they follow the BGRA pixel format.
pixelStream.Seek(0, SeekOrigin.Begin);
pixelStream.Write(bytes, 0, bytes.Length);
writeableBitmap.Invalidate();
You can check the extension methods here and here to see how to work with the pixels.

Copy MemoryStream and recreate System.Drawing.Image fails with "Parameter is not valid"

I am receiving an ArgumentException (Parameter is not valid) when trying to recreate an image from a memory stream. I have distilled it down to this example where I load up an image, copy to a stream, replicate the stream and attempt to recreate the System.Drawing.Image object.
im1 can be saved back out fine, after the MemoryStream copy the stream is the same length as the original stream.
I am assuming that the ArgumentException means that System.Drawing.Image doesnt think my stream is an image.
Why is the copy altering my bytes?
// open image
var im1 = System.Drawing.Image.FromFile(#"original.JPG");
// save into a stream
MemoryStream stream = new MemoryStream();
im1.Save(stream, System.Drawing.Imaging.ImageFormat.Jpeg);
// try saving - succeeds
im1.Save(#"im1.JPG");
// check length
Console.WriteLine(stream.Length);
// copy stream to new stream - this code seems to screw up my image bytes
byte[] allbytes = new byte[stream.Length];
using (var reader = new System.IO.BinaryReader(stream))
{
reader.Read(allbytes, 0, allbytes.Length);
}
MemoryStream copystream = new MemoryStream(allbytes);
// check length - matches im1.Length
Console.WriteLine(copystream.Length);
// reset position in case this is an issue (doesnt seem to make a difference)
copystream.Position = 0;
// recreate image - why does this fail with "Parameter is not valid"?
var im2 = System.Drawing.Image.FromStream(copystream);
// save out im2 - doesnt get to here
im2.Save(#"im2.JPG");
Before reading from the stream you need to rewind its position to zero. You are doing that for the copy right now, but also need to do that for the original.
Also, you don't need to copy to a new stream at all.
I usually resolve such problems by stepping through the program and looking at runtime state to see whether it matches my expectation or not.

ArgumentException when loading image from byte array using JpegBitmapDecoder

I'm having some trouble reading JPEG files in my class. I need to load metadata and bitmap from a JPEG file. So far, I have this:
public void Load()
{
using (Stream imageStream = File.Open(this.FilePath, FileMode.Open, FileAccess.Read, FileShare.Read))
{
BitmapDecoder decoder = new JpegBitmapDecoder(imageStream, BitmapCreateOptions.PreservePixelFormat, BitmapCacheOption.OnLoad);
BitmapSource source = decoder.Frames[0];
// load metadata
this.metadata = source.Metadata as BitmapMetadata;
// prepare buffer
int octetsPerPixel = source.Format.BitsPerPixel / 8;
byte[] pixelBuffer = new byte[source.PixelWidth * source.PixelHeight * octetsPerPixel];
source.CopyPixels(pixelBuffer, source.PixelWidth * octetsPerPixel, 0);
Stream pixelStream = new MemoryStream(pixelBuffer);
// load bitmap
this.bitmap = new Bitmap(pixelStream); // throws ArgumentException
}
this.status = PhotoStatus.Loaded;
}
But the Bitmap constructor throws an ArgumentException when trying to create a Bitmap instance from a stream.
The documentation says:
System.ArgumentException
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.
I'm not sure, what I did wrong. Can you please help me?
You're using the Bitmap constructor which is usually used to load an image file in a known format - JPEG, PNG etc. Instead, you've just got a bunch of bytes, and you're not telling it anything about the format you want to use them in.
It's not clear why you want to use BitmapDecoder and BitmapSource at all - why aren't you just using:
Stream imageStream = File.Open(this.FilePath, FileMode.Open,
FileAccess.Read, FileShare.Read));
this.bitmap = new Bitmap(imageStream);
Note that you mustn't use a using statement here - the Bitmap "owns" the stream after you've called the constructor.
Aside from all of this, you seem to be trying to mix WPF and WinForms ideas of images, which I suspect is a generally bad idea :(

Categories