Converting a Win2D CanvasRenderTarget to a BitmapImage? - c#

I'm trying to write a function which accepts my ICanvasEffect as a parameter (which in my case is a Win2D BlendEffect), and I want to convert the CanvasRenderTarget to a BitmapImage so that I can use it in a UWP Image control:
private async Task<BitmapImage> GetBitmapImage(CancellationToken ct, ICanvasImage effect)
{
using (var target = new CanvasRenderTarget(CanvasDevice.GetSharedDevice(), 320f, 240f, 96))
{
using (var ds = target.CreateDrawingSession())
{
// Draw the image with the supplied ICanvasImage
ds.DrawImage(effect);
}
//await target.SaveAsync(outputStream, CanvasBitmapFileFormat.Jpeg).AsTask(ct);
}
}
As you see in the commented code, CanvasRenderTarget has a SaveAsync method I can use to save it to a Stream, but how?

Figured it out:
using (var stream = new InMemoryRandomAccessStream())
{
await target.SaveAsync(stream, CanvasBitmapFileFormat.Jpeg).AsTask(ct);
var bmp = new BitmapImage();
stream.Seek(0);
await bmp.SetSourceAsync(stream);
}

As you see in the commented code, CanvasRenderTarget has a SaveAsync method I can use to save it to a Stream, but how?
If you want to save it to stream, you could refer to Win2D samples.
There's many samples show that how to use CanvasRenderTarget.SaveAsync() method.
For example, Win2D-Samples/ExampleGallery/Infrastructure/AppIconGenerator.cs#L217

Related

How to save media element's current frame?

I am developing a media player like vlc. One of it's features is saving snapshot during playback. I am able to successfully save the snapshot of any element using rendertargetbitmap class, using the following c# code.
RenderTargetBitmap renderTargetBitmap = new RenderTargetBitmap();
await renderTargetBitmap.RenderAsync(RenderedGrid, width, height);
RenderedImage.Source = renderTargetBitmap;
But it does not allow to capture media element. Please help me with a workaround. I guess, I am not sure, may it be possible by extracting a thumbnail from a specific position using position of StorageItemThumbnail Class. If even so, it will be a thumbnail not an image of current frame. Someone help please. It is essential feature of my app!
in a digital signage uwp app that I developed I had also a similar requirement. Searching the web on how I can do I found that in UWP you can't directly take a screenshot of a media element due to a protection policy (for example you could create a small app that captures all the frames of a video file without DRM management).
I came up with a workaround: I have the video file locally so I can extract a single frame at a certain point and save it to disk with the same dimensions of the video rendered on screen (in this way I had a full image of the frame to use later). To doing this I wrote this function:
private async Task<string> Capture(StorageFile file, TimeSpan timeOfFrame, Size imageDimension)
{
if (file == null)
{
return null;
}
//Get FrameWidth & FrameHeight
List<string> encodingPropertiesToRetrieve = new List<string>();
encodingPropertiesToRetrieve.Add("System.Video.FrameHeight");
encodingPropertiesToRetrieve.Add("System.Video.FrameWidth");
IDictionary<string, object> encodingProperties = await file.Properties.RetrievePropertiesAsync(encodingPropertiesToRetrieve);
uint frameHeight = (uint)encodingProperties["System.Video.FrameHeight"];
uint frameWidth = (uint)encodingProperties["System.Video.FrameWidth"];
//Get image stream
var clip = await MediaClip.CreateFromFileAsync(file);
var composition = new MediaComposition();
composition.Clips.Add(clip);
var imageStream = await composition.GetThumbnailAsync(timeOfFrame, (int)frameWidth, (int)frameHeight, VideoFramePrecision.NearestFrame);
//Create BMP
var writableBitmap = new WriteableBitmap((int)frameWidth, (int)frameHeight);
writableBitmap.SetSource(imageStream);
//Get stream from BMP
string mediaCaptureFileName = "IMG" + Guid.NewGuid().ToString().Substring(0, 4) + ".jpg";
var saveAsTarget = await CreateMediaFile(mediaCaptureFileName);
Stream stream = writableBitmap.PixelBuffer.AsStream();
byte[] pixels = new byte[(uint)stream.Length];
await stream.ReadAsync(pixels, 0, pixels.Length);
using (var writeStream = await saveAsTarget.OpenAsync(FileAccessMode.ReadWrite))
{
var encoder = await BitmapEncoder.CreateAsync(BitmapEncoder.JpegEncoderId, writeStream);
encoder.SetPixelData(
BitmapPixelFormat.Bgra8,
BitmapAlphaMode.Premultiplied,
(uint)writableBitmap.PixelWidth,
(uint)writableBitmap.PixelHeight,
96,
96,
pixels);
await encoder.FlushAsync();
using (var outputStream = writeStream.GetOutputStreamAt(0))
{
await outputStream.FlushAsync();
}
}
return saveAsTarget.Name;
}
This function save the frame to disk as image and return his filename.
In my application I had more "widget" (some of those with images, some with video) so I used this function to get the video frames and put a new image on top of the media element with the same dimensions and a higher Canvas Z-Index in order to use RenderTargetBitmap for take the final screenshot.
It's not a clean solution but it was a workaround for RenderTargetBitmap not considering MediaElement.
Edit:
I forgot a reference to an internal function in my application so I canghed the previous code and the line var file = await _mediaEngine.CreateMediaFile($"{Guid.NewGuid().ToString()}.jpg"); is now var file = await CreateMediaFile($"{Guid.NewGuid().ToString()}.jpg");
The async function CreateMediaFile(string fileName) simply create a new file on the disk:
public async Task<StorageFile> CreateMediaFile(string filename)
{
StorageFolder _mediaFolder = KnownFolders.PicturesLibrary;
return await _mediaFolder.CreateFileAsync(filename);
}

Convert JPG to PNG with background transparency using ImageMagick.Net

I need to convert a JPG image to PNG and change its white background to transparent instead. I am using ImageMagick.NET and I have found the following ImageMagick command that is supposed to do what I am trying to achieve:
convert image.jpg -fuzz XX% -transparent white result.png
I have tried converting this to c# but all I am getting is a png image with a white background. My code snippet:
using (var img = new MagickImage("image.jpg"))
{
img.Format = MagickFormat.Png;
img.BackgroundColor = MagickColors.White;
img.ColorFuzz = new Percentage(10);
img.BackgroundColor = MagickColors.None;
img.Write("image.png");
}
Any kind of help will be greatly appreciated. Thank you!!
This is a late response as it took me a while to find an answer myself, but this seems to work for me quite well. Look at where the Background property is assigned the Transparent value.
using (var magicImage = new MagickImage())
{
var magicReadSettings = new MagickReadSettings
{
Format = MagickFormat.Svg,
ColorSpace = ColorSpace.Transparent,
BackgroundColor = MagickColors.Transparent,
// increasing the Density here makes a larger and sharper output to PNG
Density = new Density(950, DensityUnit.PixelsPerInch)
};
magicImage.Read("someimage.svg", magicReadSettings);
magicImage.Format = MagickFormat.Png;
magicImage.Write("someimage.png");
}
In my case, I wanted to send this to UWP Image element, so instead of Write(), I did the following after the steps above:
// Create byte array that contains a png file
byte[] imageData = magicImage.ToByteArray();
using (InMemoryRandomAccessStream stream = new InMemoryRandomAccessStream())
{
using (DataWriter writer = new DataWriter(stream.GetOutputStreamAt(0)))
{
writer.WriteBytes(imageData);
await writer.StoreAsync();
}
await bitmapImage.SetSourceAsync(stream);
}
return bitMapImage; // new BitMapImage() was scoped before all of this
Then on the UWP Image element, simply use:
imageElement.Source = bitMapImage;
Most of the arguments on the command line are either properties or method on the MagickImage class. Your command would translate to this:
using (var img = new MagickImage("image.jpg"))
{
// -fuzz XX%
img.ColorFuzz = new Percentage(10);
// -transparent white
img.Transparent(MagickColors.White);
img.Write("image.png");
}

MediaCapture SetEncodingPropertiesAsync returns exception The stream number provided was invalid. PreviewState

I'm working on a Universal Windows App for my Windows Phone (Lumia 950 with Windows 10). It's a very simple app which takes a photo using the front camera of the phone.
The problem is that the photo being taken is in landscape mode by default, which isn't great when it's a selfie. I'd like to rotate it 90 degrees so that it becomes a portrait. I use the MediaCapture object to initialize the camera. I've tried the following to rotate the image:
await _mediaCapture.InitializeAsync(settings);
var videoEncodingProperties = _mediaCapture.VideoDeviceController.GetMediaStreamProperties(MediaStreamType.VideoPreview);
videoEncodingProperties.Properties.Add(new Guid("C380465D-2271-428C-9B83-ECEA3B4A85C1"), 90);
await _mediaCapture.SetEncodingPropertiesAsync(MediaStreamType.VideoPreview, videoEncodingProperties, null);
But the last line throws an exception "The stream number provided was invalid. PreviewState". I'm guessing this has to do with the GUID provided, but after countless hours googling I keep finding this value that developers use for this.
I tried another solution to rotate the image:
_mediaCapture.SetPreviewRotation(VideoRotation.Clockwise90Degrees);
But that doesn't do anything.
Any ideas how I can rotate the image? Thanks a lot!
To rotate the preview, you need an active preview. This means you need to attach the mediaCapture to your capture element, then start the preview, and finally set the rotation with SetPreviewRotation.
If you do that, it should work perfectly.
Since pretty much nothing worked for me, I ended up re-writing most of the application in order to rotate the image. Here are the essential parts of my code that worked in the end:
private async void TakePicture()
{
var stream = new InMemoryRandomAccessStream();
try
{
await _mediaCapture.CapturePhotoToStreamAsync(ImageEncodingProperties.CreateJpeg(), stream);
var photoOrientation = _foundFrontCam ? PhotoOrientation.Rotate90 : PhotoOrientation.Normal;
var photoFile = await KnownFolders.PicturesLibrary.CreateFileAsync("Photo.jpeg", CreationCollisionOption.GenerateUniqueName);
await ReencodeAndSavePhotoAsync(stream, photoOrientation, photoFile);
var imageBytes = await ShowPhotoOnScreenThenDeleteAsync(photoFile);
PostToServerAsync(imageBytes);
}
catch (Exception ex)
{
new MessageDialog(ex.Message).ShowAsync();
}
}
private async Task ReencodeAndSavePhotoAsync(IRandomAccessStream stream, PhotoOrientation photoOrientation, StorageFile photoFile)
{
using (var inputStream = stream)
{
var decoder = await BitmapDecoder.CreateAsync(inputStream);
using (var outputStream = await photoFile.OpenAsync(FileAccessMode.ReadWrite))
{
var encoder = await BitmapEncoder.CreateForTranscodingAsync(outputStream, decoder);
var properties = new BitmapPropertySet { { "System.Photo.Orientation", new BitmapTypedValue(photoOrientation, PropertyType.UInt16) } };
await encoder.BitmapProperties.SetPropertiesAsync(properties);
await encoder.FlushAsync();
}
}
}
Creating a PhotoOrientation object and passing that as part of the input stream when saving the image did the trick.

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;

MVC - Load an image from URL into a bitmap. (async await)

I want to load an image from a URL in the server side, and use the await syntax if possible.
In a WPF application I could do that:
var image = new BitmapImage(new Uri("http://..."));
image.ImageOpened += (s, e) =>
{
}
In MVC, instead, I have the by default System.Drawing.Bitmap which dose not have an option to load from a URL.
Should I :
Add reference to System.Windows.Media.Imaging and use BitmapImage
class wrapping it with TaskCompletionSource.
Somehow load the image using the Bitmap class.
A working code using one of the above with async/wait would be great.
Should be quite simple:
private Task<Stream> GetStreamAsync()
{
var httpClient = new HttpClient();
return httpClient.GetStreamAsync(#"http://urlhere");
}
and call it:
private async Task GetImageAndDoStuff()
{
Stream imageStream = await GetStreamAsync();
using (Bitmap bitmap = new Bitmap(Image.FromStream(imageStream)))
{
}
}

Categories