I'm making a paint-like program using Win2D. My CanvasControl contains some text, images and some lines which the user has drawn. I want to save the entire contents of this CanvasControl as a file on disk (in any standard image format). I want to do this as I want to display it (at a later time) inside a standard Image control.
How would I do this? I tried using RenderTargetBitmap to load the CanvasControl (code below) but for some reason it clips the image and only a small horizontal top-portion image is made.
async private void Button_Click(object sender, RoutedEventArgs e)
{
#region (c) rendering UIElement to bitmap code
var bitmap = new RenderTargetBitmap();
await bitmap.RenderAsync(ccDraw); // ccDraw is CanvasControl
// get the pixels
IBuffer pixelBuffer = await bitmap.GetPixelsAsync();
byte[] pixels = pixelBuffer.ToArray();
// write the pixels to a InMemoryRandomAccessStream
var stream = new InMemoryRandomAccessStream();
var encoder = await BitmapEncoder.CreateAsync(BitmapEncoder.BmpEncoderId, stream);
encoder.SetPixelData(BitmapPixelFormat.Bgra8, BitmapAlphaMode.Straight, (uint)bitmap.PixelWidth, (uint)bitmap.PixelHeight, 96, 96, pixels);
await encoder.FlushAsync();
stream.Seek(0);
Image iNew = new Image();
iNew.Stretch = Stretch.None;
iNew.Source = bitmap;
gOuter.Children.Add(iNew);
ccDraw.Visibility = Visibility.Collapsed; // hide CanvasControl so we can see added image
#endregion
}
Here is how i did this for me. imageSize is size of your image, for example var imageSize = new Size(500,500);
var displayInformation = DisplayInformation.GetForCurrentView();
ccDraw.Measure(imageSize);
ccDraw.UpdateLayout();
ccDraw.Arrange(new Rect(0, 0, imageSize.Width, imageSize.Height));
var renderTargetBitmap = new RenderTargetBitmap();
await renderTargetBitmap.RenderAsync(ccDraw, Convert.ToInt32(imageSize.Width), Convert.ToInt32(imageSize.Height));
var pixelBuffer = await renderTargetBitmap.GetPixelsAsync();
var file = await ApplicationData.Current.LocalFolder.CreateFileAsync("Screen.jpg", CreationCollisionOption.ReplaceExisting);
using (var fileStream = await file.OpenAsync(FileAccessMode.ReadWrite))
{
var encoder = await BitmapEncoder.CreateAsync(BitmapEncoder.JpegEncoderId, fileStream);
encoder.SetPixelData(
BitmapPixelFormat.Bgra8,
BitmapAlphaMode.Ignore,
(uint)renderTargetBitmap.PixelWidth,
(uint)renderTargetBitmap.PixelHeight,
displayInformation.LogicalDpi,
displayInformation.LogicalDpi,
pixelBuffer.ToArray());
await encoder.FlushAsync();
}
Try this:
private void SaveCanvasAsImage()
{
Rect yourRect = new Rect(ccDraw.RenderSize);
RenderTargetBitmap rtBitmap = new RenderTargetBitmap((int)yourRect.Right,
(int)yourRect.Bottom, 100d, 100d, System.Windows.Media.PixelFormats.Default);
rtBitmap.Render(ccDraw);
BitmapEncoder pngEncoder = new PngBitmapEncoder();
pngEncoder.Frames.Add(BitmapFrame.Create(rtBitmap));
System.IO.MemoryStream ms = new System.IO.MemoryStream();
pngEncoder.Save(ms);
ms.Close();
System.IO.File.WriteAllBytes("yourPng.png", ms.ToArray());
}
You cannot directly save the contents of a CanvasControl.
However, if you draw to an intermediate CanvasRenderTarget then you can save that using SaveAsync.
More information on drawing offscreen can be found at http://microsoft.github.io/Win2D/html/Offscreen.htm.
There's other benefits to drawing to a CanvasRenderTarget rather than drawing directly to the CanvasControl - the most obvious is that it allows you to do additive rendering, rather than having to redraw the entire display each time.
Related
I need help with saving rotated image in uwp. The code is in C#. The rotation is made by slider and the rotation value is a specific value(not 90,180,270).
This is my code
<Image
Name="RotatingImage"
Margin="0,100,500,100"
HorizontalAlignment="Right"
VerticalAlignment="Center"
RenderTransformOrigin="0.5,0.5"
Stretch="Uniform"
Tapped="RotatingImage_Tapped">
<Image.RenderTransform>
<!-- That's the part which I've added, on top of the auto-generated xaml -->
<RotateTransform />
<!-- because the ThumbnailImageStyle defines width and height as 228 -->
</Image.RenderTransform>
</Image>
<Slider
x:Name="rotationSlider"
Maximum="360"
Margin="1058,120,82,0"
VerticalAlignment="Top"
ValueChanged="RotationSlider_ValueChanged" />
C#
private void RotationSlider_ValueChanged(object sender, RangeBaseValueChangedEventArgs e)
{
Slider slider = sender as Slider;
if (slider != null)
{
RotationAngle = (int)slider.Value;
}
RotatingImage.RenderTransform = new RotateTransform
{
Angle = RotationAngle
};
}
Put the image in a grid named imageGrid, set the grid's background to Transparent.
Then try below code:
InMemoryRandomAccessStream newStream = new InMemoryRandomAccessStream();
RenderTargetBitmap renderTargetBitmap = new RenderTargetBitmap();
await renderTargetBitmap.RenderAsync(this.imageGrid);
var pixelBuffer = await renderTargetBitmap.GetPixelsAsync();
var encoder = await BitmapEncoder.CreateAsync(BitmapEncoder.PngEncoderId, newStream);
encoder.SetPixelData(
BitmapPixelFormat.Bgra8,
BitmapAlphaMode.Straight,
(uint)renderTargetBitmap.PixelWidth,
(uint)renderTargetBitmap.PixelHeight,
DisplayInformation.GetForCurrentView().LogicalDpi,
DisplayInformation.GetForCurrentView().LogicalDpi,
pixelBuffer.ToArray());
await encoder.FlushAsync();
Then you can save the stream.
Hope this can help you.
As has been said above, first you will need to place the image in a Grid or a Canvas.
Then, try the following snippet.
RenderTargetBitmap renderTargetBitmap = new RenderTargetBitmap();
await renderTargetBitmap.RenderAsync(MyGrid, MyGrid.ActualWidth, MyGrid.ActualHeight);
var pixelBuffer = await renderTargetBitmap.GetPixelsAsync();
var pixels = pixelBuffer.ToArray();
var displayInformation = DisplayInformation.GetForCurrentView();
var folder = await ApplicationData.Current.LocalFolder.CreateFolderAsync("Images", CreationCollisionOption.OpenIfExists);
var file = await folder.CreateFileAsync("RotatedImage" + ".png", CreationCollisionOption.ReplaceExisting);
using (var stream = await file.OpenAsync(FileAccessMode.ReadWrite))
{
var encoder = await BitmapEncoder.CreateAsync(BitmapEncoder.PngEncoderId, stream);
encoder.BitmapTransform.InterpolationMode = BitmapInterpolationMode.Cubic;
encoder.SetPixelData(BitmapPixelFormat.Bgra8, BitmapAlphaMode.Premultiplied, (uint)renderTargetBitmap.PixelWidth, (uint)renderTargetBitmap.PixelHeight, displayInformation.RawDpiX, displayInformation.RawDpiY, pixels);
await encoder.FlushAsync();
}
This will save the image into a folder named "Images" within your packages LocalFolder.
Use RenderTargetBitmap to save current view.
for example,
RenderTargetBitmap bmp = new RenderTargetBitmap({Width}, {Height}, {DpiX}, {DpiY}, PixelFormats.Default);
bmp.Render({ControlWhichYouWantToSave});
PngBitmapEncoder encoder = new PngBitmapEncoder();
encover.Frames.Add(BitmapFrame.Create(bmp));
using ( FileStream fs = File.Open({Location}, FileMode.Create))
{
encoder.Save(fs);
fs.Close();
}
if this doesn't work, change
bmp.Render({ControlWhichYouWantToSave});
as
bmp.Render({ContrplParentWhichYouWantToSave});
hope this work.
I want to get real size image when I use RenderTargetBitmap or Capture UI because When I RenderTargetBitmap or Capture UI. Image is blur and don't clear but when I expand Image to original size image don't blur and clear.
RenderTargetBitmap renderTargetBitmap = new RenderTargetBitmap();
await renderTargetBitmap.RenderAsync(ImageAndSticker,2500,3750);
StorageFile file = await KnownFolders.CameraRoll.CreateFileAsync("snapshot" + DateTime.Now.ToString("MM-dd-yyyy h.mm.ss.fff tt") + ".jpg", CreationCollisionOption.GenerateUniqueName);
storageFile = file;
var pixelBuffer = await renderTargetBitmap.GetPixelsAsync();
var pixels = pixelBuffer.ToArray();
var displayInformation = DisplayInformation.GetForCurrentView();
using (var stream = await file.OpenAsync(FileAccessMode.ReadWrite))
{
var encoder = await BitmapEncoder.CreateAsync(BitmapEncoder.JpegEncoderId, stream);
encoder.SetPixelData(BitmapPixelFormat.Bgra8, BitmapAlphaMode.Ignore, (uint)renderTargetBitmap.PixelWidth, (uint)renderTargetBitmap.PixelHeight, displayInformation.RawDpiX, displayInformation.RawDpiY, pixels);
await encoder.FlushAsync();
}
<Viewbox Margin="254.8,8,659,474" Stretch="Uniform" StretchDirection="Both" x:Name="ViewImage" Grid.Column="1" Grid.Row="0">
<Image x:Name="frameimage" Margin="176.8,2,459,135" Grid.Column="1" Height="3750" Width="2500" Canvas.Left="-458" Canvas.Top="-641"/>
</Viewbox>
Use
frameimage.Source.Width
to get source image's width
samething do with Height will get source image's Height value like this
frameImage.Source.Height
I think you wish to save Source image as file.
if then, use this code.
BitmapSource source = frameImage.Source as BitmapSource;
using ( var FileStream = new FileStream( FileLoc, FileMode.Create, FileAccess.ReadWrite) ) {
BitmapEncoder Encoder = new JpegBitmapEncoder();
Encoder.Frames.Add(BitmapFrame.Create(source));
Encoder.Save(FileStream);
FileStream.Dispose();
}
I want to create a video from a few RenderTargetBitmaps in UWP. I am doing that by using MediaClips.
From RenderTargetBitmap i can get an IBuffer or byte array of pixels.
To create a MediaClip I need either an image file or an IDirect3DSurface.
Creating an image just to create a clip is very expensive, so I thought of using IDirect3DSurface.
How can I do this?
I have tried this:
RenderTargetBitmap renderTargetBitmap = new RenderTargetBitmap();
await renderTargetBitmap.RenderAsync(RenderedGrid, 100, 100);
IBuffer pixels = await renderTargetBitmap.GetPixelsAsync();
var values = Enum.GetValues(typeof(DirectXPixelFormat));
CanvasBitmap bitmap=null;
foreach (DirectXPixelFormat format in values)
{
try
{
videoClip = new MediaComposition();
bitmap = CanvasBitmap.CreateFromBytes(myWidget.Device, pixels, renderTargetBitmap.PixelWidth, renderTargetBitmap.PixelHeight, format);
StorageFile video2 = await storageFolder.CreateFileAsync("video2" + ".mp4", CreationCollisionOption.ReplaceExisting);
MediaClip d = MediaClip.CreateFromSurface(bitmap, DateTime.Now - previousFrame+new TimeSpan(100));
videoClip.Clips.Add(d);
await videoClip.RenderToFileAsync(video2);
break;
}
catch(Exception e)
{
}
}
I try all the formats in DirectXPixelFormat but none works.
I have a CanvasControl named myWidget that is empty.
I create a CanvasBitmap from Ibuffer (CanvasBitmap implements IDirect3DSurface)
Create a Mediaclip from CanvasBitmap
Add it to MediaComposition.
Then I try to render to video file.When i try to save to a file it throws an error
System.Runtime.InteropServices.COMException Stream is not in a state
to handle the request.
EDIT:
I figured out where the problem is, but not why and not how to fix it.
await videoClip.SaveAsync(video2);
videoClip= await MediaComposition.LoadAsync(video2);
var x=await videoClip.RenderToFileAsync(video2);
Now with these three lines i can save the video, but using only the third line it throws the error above. I cannot make sense of it. Why does saving and loading fix the problem??
Most probable reason is that, CanvasBitmap has an underlying IDirce3DSurface object as well as image data like byte[] or something else , though I am not sure about this.
If that's true, then creating a CanvasBitmap from byte[] or IBuffer won't effect the underlying IDirect3DSurface, the image part will be constructed only. You can see that by saving that image on the disk, it gives no error.
But I think there's a workaround if you want to skip saving data on the disk:
You can contruct the underlying IDirect3DSurface if you do Offscreen Drawing to a CanvasRenderTarget.
So, you can use the CanvasBitmap to construct a CanvasRenderTarget and then use that CanvasRenderTarget to contruct a MediaClip:
CanvasRenderTarget rendertarget;
using (CanvasBitmap canvas = CanvasBitmap.CreateFromBytes(CanvasDevice.GetSharedDevice(), pixels, renderTargetBitmap.PixelWidth, renderTargetBitmap.PixelHeight, format))
{
rendertarget = new CanvasRenderTarget(CanvasDevice.GetSharedDevice(), canvas.SizeInPixels.Width, canvas.SizeInPixels.Height, 96);
using (CanvasDrawingSession ds = rendertarget.CreateDrawingSession())
{
ds.Clear(Colors.Black);
ds.DrawImage(canvas);
}
}
MediaClip d = MediaClip.CreateFromSurface(renderTarget, TimeSpan.FromMilliseconds(80));
mc.Clips.Add(m);
The MediaComposition.RenderToFileAsync Method saves the composition to a video file that can be played back with standard media players. From the error info, it seems the stream content is not correct media data and can not be render into a video file directly.
So, to create a video from a few RenderTargetBitmaps in UWP, the way to use an image file should be your choice. using MediaClip.CreateFromImageFileAsync method by saving the RenderTargetBitmap into a file then using it to create a video.
private async void CreateVideoByConvertRenderBitmapToFile()
{
var folder = await ApplicationData.Current.LocalFolder.CreateFolderAsync("Test",
CreationCollisionOption.ReplaceExisting);
var composition = new MediaComposition();
for (int i = 0; i < 5; i++)
{
RenderTargetBitmap render = new RenderTargetBitmap();
await render.RenderAsync(RenderGrid);
MyImage.Source = render;
var pixel = await render.GetPixelsAsync();
var file = await folder.CreateFileAsync("test.png", CreationCollisionOption.GenerateUniqueName);
using (IRandomAccessStream stream = await file.OpenAsync(FileAccessMode.ReadWrite))
{
var logicalDpi = DisplayInformation.GetForCurrentView().LogicalDpi;
var encoder = await BitmapEncoder.CreateAsync(BitmapEncoder.PngEncoderId, stream);
encoder.SetPixelData(
BitmapPixelFormat.Bgra8,
BitmapAlphaMode.Ignore,
(uint)render.PixelWidth,
(uint)render.PixelHeight,
logicalDpi,
logicalDpi,
pixel.ToArray());
await encoder.FlushAsync();
stream.Dispose();
MediaClip clip = await MediaClip.CreateFromImageFileAsync(file, TimeSpan.FromSeconds(3));
composition.Clips.Add(clip);
MyText.Text = "First frame >>>" + i;
}
}
var video = await ApplicationData.Current.LocalFolder.CreateFileAsync("test.mp4",
CreationCollisionOption.ReplaceExisting);
var action = await composition.RenderToFileAsync(video, MediaTrimmingPreference.Precise);
await folder.DeleteAsync();
}
I am new to Windows app development. I want to resize the actual image to specific height and width. I have started using the following code but the resulting image is pixelated.
Please suggest for me better approach to resize image.
This is my code:
using (var sourceStream = await file.OpenAsync(FileAccessMode.Read)){
BitmapDecoder decoder = await BitmapDecoder.CreateAsync(sourceStream);
BitmapTransform transform = new BitmapTransform() { ScaledHeight = 560, ScaledWidth = 580 };
PixelDataProvider pixelData = await decoder.GetPixelDataAsync(
BitmapPixelFormat.Rgba8,
BitmapAlphaMode.Straight,
transform,
ExifOrientationMode.RespectExifOrientation,
ColorManagementMode.DoNotColorManage);
using (var destinationStream = await destinationfile.OpenAsync(FileAccessMode.ReadWrite))
{
BitmapEncoder encoder = await BitmapEncoder.CreateAsync(BitmapEncoder.PngEncoderId, destinationStream);
encoder.SetPixelData(BitmapPixelFormat.Rgba8, BitmapAlphaMode.Straight, 580, 560, 96, 96, pixelData.DetachPixelData());
await encoder.FlushAsync();
}
}
Here's solution without use of WritableBitmapEx
How to resize Image in C# WinRT/winmd?
I'm looking for way to save canvas from windows store app, I have found:
private void CreateSaveBitmap(Canvas canvas, string filename)
{
RenderTargetBitmap renderBitmap = new RenderTargetBitmap(
(int)canvas.Width, (int)canvas.Height,
96d, 96d, PixelFormats.Pbgra32);
// needed otherwise the image output is black
canvas.Measure(new Size((int)canvas.Width, (int)canvas.Height));
canvas.Arrange(new Rect(new Size((int)canvas.Width, (int)canvas.Height)));
renderBitmap.Render(canvas);
//JpegBitmapEncoder encoder = new JpegBitmapEncoder();
PngBitmapEncoder encoder = new PngBitmapEncoder();
encoder.Frames.Add(BitmapFrame.Create(renderBitmap));
using (FileStream file = File.Create(filename))
{
encoder.Save(file);
}
}
But that method won't work in windows store app (there is no 5 argument constructor for RenderTargetBitmap no PngBitmapEncoder). So my question is how can I save canvas from windows store app as some kind of image file (jpg, png, etc.) is there any way to do this?
Try this
using System.Runtime.InteropServices.WindowsRuntime
private async Task CreateSaveBitmapAsync(Canvas canvas)
{
if (canvas != null)
{
RenderTargetBitmap renderTargetBitmap = new RenderTargetBitmap();
await renderTargetBitmap.RenderAsync(canvas);
var picker = new FileSavePicker();
picker.FileTypeChoices.Add("JPEG Image", new string[] { ".jpg" });
StorageFile file = await picker.PickSaveFileAsync();
if (file != null)
{
var pixels = await renderTargetBitmap.GetPixelsAsync();
using (IRandomAccessStream stream = await file.OpenAsync(FileAccessMode.ReadWrite))
{
var encoder = await
BitmapEncoder.CreateAsync(BitmapEncoder.JpegEncoderId, stream);
byte[] bytes = pixels.ToArray();
encoder.SetPixelData(BitmapPixelFormat.Bgra8,
BitmapAlphaMode.Ignore,
(uint)canvas.Width, (uint)canvas.Height,
96, 96, bytes);
await encoder.FlushAsync();
}
}
}
}
I came across this reply and although it looked exactly what I was looking for it didn't work. Adding an error handler revealed:
Value does not fall within the expected range.
Finally I found that:
(uint)canvas.Width, (uint)canvas.Height,
were both 0. After replacing it with:
(uint)canvas.ActualWidth, (uint)canvas.ActualHeight,
it worked. No idea why it worked for Carlos28 and not for me, but with this change it worked for me too and so I thank Xyriod for the answer
Thank you, Xyroid, for your idea and structure.
To get a version of CreateSaveBitmapAsync() to work on 04/24/20, I had to replace your using block with the following.
The changes of your hard-coded "96"'s, and the replacement of "bytes" with its definition, are optional.
using (Windows.Storage.Streams.IRandomAccessStream stream = await fileToWhichToSave.OpenAsync(Windows.Storage.FileAccessMode.ReadWrite))
{
Windows.Graphics.Imaging.BitmapEncoder encoder = await Windows.Graphics.Imaging.BitmapEncoder.CreateAsync(Windows.Graphics.Imaging.BitmapEncoder.JpegEncoderId, stream);
encoder.SetPixelData(
Windows.Graphics.Imaging.BitmapPixelFormat.Bgra8,
Windows.Graphics.Imaging.BitmapAlphaMode.Ignore,
(uint)renderTargetBitmap.PixelWidth,
(uint)renderTargetBitmap.PixelHeight,
Windows.Graphics.Display.DisplayInformation.GetForCurrentView().LogicalDpi,
Windows.Graphics.Display.DisplayInformation.GetForCurrentView().LogicalDpi,
pixels.ToArray());
await encoder.FlushAsync();
}