I'm creating a uwp app where text a user types in is converted into a video overlay. The user loads the video, types in their text, gets a preview of the video with the text, then they can save their video with the overlay.
Right now it works with an image overlay, but not text. I'm trying to use win2d to format the text. However, I can't figure out how to get the win2d canvas control into a windows.media.editing media clip.
I thought I could go CanvasControl > Bitmap of some sort > Image > MediaClip, but media clips can only be created through storage files or direct 3d surfaces. Anything would be helpful! This is my first time trying to write something outside of a game engine.
The part that currently converts the canvas control to a bitmap:
private async void btnAddOverlay_Click(object sender, RoutedEventArgs e)
{
var bitmap = new RenderTargetBitmap();
await bitmap.RenderAsync(canvasControl);
IBuffer pixelBuffer = await bitmap.GetPixelsAsync();
byte[] pixels = pixelBuffer.ToArray();
SoftwareBitmap outputBitmap = SoftwareBitmap.CreateCopyFromBuffer
(
pixelBuffer,
BitmapPixelFormat.Bgra8,
bitmap.PixelWidth,
bitmap.PixelHeight
);
}
The old part that added the image overlay to the video. (overlayImageFile is the storage file. I just don't know how to convert outputBitmap to a storage file to make a media clip from it.)
private async void CreateOverlays()
{
// Create a clip from video and add it to the composition
var baseVideoClip = await MediaClip.CreateFromFileAsync(pickedFile);
composition = new MediaComposition();
composition.Clips.Add(baseVideoClip);
// Create a clip from image
var overlayImageClip = await MediaClip.CreateFromImageFileAsync(overlayImageFile,timeSpan);
// Put image in upper left corner, retain its native aspect ratio
Rect imageOverlayPosition;
imageOverlayPosition.Height = mediaElement.ActualHeight / 3;
imageOverlayPosition.Width = (double)imageBitmap.PixelWidth / (double)imageBitmap.PixelHeight * imageOverlayPosition.Height;
imageOverlayPosition.X = 0;
imageOverlayPosition.Y = 0;
// Make the clip from the image an overlay
var imageOverlay = new MediaOverlay(overlayImageClip);
imageOverlay.Position = imageOverlayPosition;
imageOverlay.Opacity = 0.8;
// Make a new overlay layer and add the overlay to it
var overlayLayer = new MediaOverlayLayer();
overlayLayer.Overlays.Add(imageOverlay);
composition.OverlayLayers.Add(overlayLayer);
//Render to MediaElement
mediaElement.Position = TimeSpan.Zero;
mediaStreamSource = composition.GeneratePreviewMediaStreamSource((int)mediaElement.ActualWidth, (int)mediaElement.ActualHeight);
mediaElement.SetMediaStreamSource(mediaStreamSource);
txbNotification.Text = "Overlay creaeted";
}
Windows MediaClip Class: https://learn.microsoft.com/en-us/uwp/api/windows.media.editing.mediaclip?view=winrt-19041
I just don't know how to convert outputBitmap to a storage file to make a media clip from it.
After getting the RenderTargetBitmap, there is no need to convert to SoftwareBitmap. You can store the pixel data as a file in a specified format through BitmapEncoder. This is the method:
private async void btnAddOverlay_Click(object sender, RoutedEventArgs e)
{
RenderTargetBitmap renderTargetBitmap = new RenderTargetBitmap();
await renderTargetBitmap.RenderAsync(canvasControl);
var pixelBuffer = await renderTargetBitmap.GetPixelsAsync();
var pixels = pixelBuffer.ToArray();
var displayInformation = DisplayInformation.GetForCurrentView();
// Create the file in local folder
var file = await ApplicationData.Current.LocalFolder.CreateFileAsync("TempCanvas.png", CreationCollisionOption.ReplaceExisting);
// Write data
using (var stream = await file.OpenAsync(FileAccessMode.ReadWrite))
{
var encoder = await BitmapEncoder.CreateAsync(BitmapEncoder.PngEncoderId, stream);
encoder.SetPixelData(BitmapPixelFormat.Bgra8, BitmapAlphaMode.Premultiplied, (uint)renderTargetBitmap.PixelWidth, (uint)renderTargetBitmap.PixelHeight, displayInformation.RawDpiX, displayInformation.RawDpiY, pixels);
await encoder.FlushAsync();
}
}
When needed, you can use the following code to get the saved picture:
var file = await ApplicationData.Current.LocalFolder.GetFileAsync("TempCanvas.png");
it's just another possible way
If you are using win2d than you can use
MediaClip.CreateFromSurface(IDirect3DSurface, TimeSpan) function and
you can pass a canvas render target directly to this method
this way you don't have to save bitmap to file
so you have to create an offline canvas device
then a canvas render target
then draw text over it
then pass that render target to function
and it will generate the media clip for overlay
Related
I have a big svg image.
I would like to crop it to a rectangle, using coordinates, and convert it to png image.
I have to say that I'm not used to drawing with c#.
Surface, Canvas and other notions are new to me.
I have figured out how to load the svg, using SkiaShark and SkiaShark.Svg:
var svg = new SkiaSharp.Extended.Svg.SKSvg();
svg.Load(tmpPath);
And found a gist that saves a png. But this is "Chinese" for me.
var imageInfo = new SKImageInfo(100, 100);
using (var surface = SKSurface.Create(imageInfo))
using (var canvas = surface.Canvas)
{
// calculate the scaling need to fit to screen
var scaleX = 100 / svg.Picture.CullRect.Width;
var scaleY = 100 / svg.Picture.CullRect.Height;
var matrix = SKMatrix.CreateScale((float)scaleX, (float)scaleY);
// draw the svg
canvas.Clear(SKColors.Transparent);
canvas.DrawPicture(svg.Picture, ref matrix);
canvas.Flush();
using (var data = surface.Snapshot())
using (var pngImage = data.Encode(SKEncodedImageFormat.Png, 100))
{
tmpPath = Path.ChangeExtension(tmpPath, "PNG");
using var imageStream = new FileStream(tmpPath, FileMode.Create);
pngImage.SaveTo(imageStream);
}
}
If someone could show me the directions, it would be much appreciated.
EDIT
I've come to this implentation myself, though it is not working... The result bitmap is transparent and empty.
private string ConvertSVGToPNGRectangleWithSkiaSharpExtended(string path, double left, double top, double right, double bottom)
{
var svg = new SkiaSharp.Extended.Svg.SKSvg();
var picture = svg.Load(path);
// Get the initial map size
var source = new SKRect(picture.CullRect.Left, picture.CullRect.Top, picture.CullRect.Width, picture.CullRect.Height);
// Cropping Rect
var cropRect = new SKRect((int)left, (int)top, (int)right, (int)bottom);
var croppedBitmap = new SKBitmap((int)cropRect.Width, (int)cropRect.Height);
using var canvas = new SKCanvas(croppedBitmap);
canvas.Clear(SKColors.Transparent);
canvas.DrawBitmap(croppedBitmap, source, cropRect);
var data = croppedBitmap.Encode(SKEncodedImageFormat.Png, 100);
path = Path.ChangeExtension(path, "PNG");
using var imageStream = new FileStream(path, FileMode.Create);
data.SaveTo(imageStream);
return path;
}
EDIT 2:
I'm sorry , if it is not clear. You can refer to this question for doing so on Android.
Disclaimer: I'm not an expert with SkiaSharp, but I do know about drawing and canvases and I looked up the documentation at https://learn.microsoft.com/en-us/dotnet/api/skiasharp.skcanvas.drawpicture
I think the problem is this line:
canvas.DrawBitmap(croppedBitmap, source, cropRect);
I think you need:
canvas.DrawPicture(picture, -cropRect.Left, -cropRect.Top);
canvas.Flush();
I might have the coordinates wrong, but the problem behind your failure is that you are never drawing picture to the canvas.
To help with your confusion about what a canvas is: SKCanvas(croppedBitmap) is making it so that every time you draw to canvas, what you are actually drawing on is the bitmap. So in order to draw your picture onto the bitmap, you have to draw picture onto canvas. Using negative coordinates make it so the top and left of the svg picture are above and to the left of the top-left corner of the bitmap, which is the equivalent of having the bitmap start somewhere in the middle of the svg picture.
Hope this helps!
I'm creating an application that takes a video and overlays sensor data from a drone.
Please, watch it in action here:
https://youtu.be/eAOjImJci3M
But that doesn't edit the video, it's just on the application.
My goal is to produce a new video with the overlaid data on it. How can I capture an element of the UI over time and make a MediaClip out of it?
Short answer: Take a screenshot of the UI with a certain interval, and then accumulate them to produce a video:
Long answer: Let's name the UI you want to record as myGrid, to get screenshots in an interval you can use a DispatcerTimer, and handle the Tick event like this:
private async void Tm_Tick(object sender, object e)
{
RenderTargetBitmap rendertargetBitmap = new RenderTargetBitmap();
await rendertargetBitmap.RenderAsync(myGrid);
var bfr = await rendertargetBitmap.GetPixelsAsync();
CanvasRenderTarget rendertarget = null
using (CanvasBitmap canvas = CanvasBitmap.CreateFromBytes(CanvasDevice.GetSharedDevice(), bfr, rendertargetBitmap.PixelWidth, rendertargetBitmap.PixelHeight, Windows.Graphics.DirectX.DirectXPixelFormat.B8G8R8A8UIntNormalized))
{
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 m = MediaClip.CreateFromSurface(rendertarget, TimeSpan.FromMilliseconds(80));
mc.Clips.Add(m);
}
mc is the MediaComposition object I declared earlier .
When you are done recording, stop the DispatcherTimer and save the video on the disk like this:
tm.Stop();
await mc.RenderToFileAsync(file, MediaTrimmingPreference.Precise, MediaEncodingProfile.CreateMp4(VideoEncodingQuality.Vga));
tm is the DispatcherTimer I declared earlier and file is a StorageFile with mp4 extension.
This procedure doesn't require you to save each screenshot to disk .
If you wonder why I used an additional CanvasRenderTarget, it's because of this problem.
Hope that helps.
I'm trying to crop an image from webcam and display right next to the camera preview.
Cropping an image should be carried with 3 considerations.
Output of cropped image should be in form of VideoFrame
The above output, VideoFrame, needs to be displayed (on XAML)
The target crop image is in the middle of original image
I found RenderTargetBitmap would help me to get an cropped image.
But still I have no idea how to display VideoFrame (without saving an image), set the position where to crop.
I got stuck below...
public async Task<VideoFrame> CroppingImage(Grid grid)
{
RenderTargetBitmap renderBitmap = new RenderTargetBitmap();
await renderBitmap.RenderAsync(grid);
var buffer = await renderBitmap.GetPixelsAsync();
var softwareBitmap = SoftwareBitmap.CreateCopyFromBuffer(buffer, BitmapPixelFormat.Bgra8, renderBitmap.PixelWidth, renderBitmap.PixelHeight, BitmapAlphaMode.Ignore);
buffer = null;
renderBitmap = null;
VideoFrame vf = VideoFrame.CreateWithSoftwareBitmap(softwareBitmap);
await CropAndDisplayInputImageAsync(vf);
return cropped_vf;
}
private async Task CropAndDisplayInputImageAsync(VideoFrame inputVideoFrame)
{
//some cropping algorithm here
//i have a rectangle on a canvas(camera preview is on CaptureElement)
//I know the left top position and width and height but no idea how to use
}
Any help?
This is what i found and done :)
(assume that there is a videoframe which name is croppedface)
croppedFace = new VideoFrame(BitmapPixelFormat.Bgra8, (int)width, (int)height, BitmapAlphaMode.Ignore);
await inputVideoFrame.CopyToAsync(croppedFace, cropBounds, null);
SoftwareBitmap asdf = croppedFace.SoftwareBitmap;
asdf = SoftwareBitmap.Convert(asdf, BitmapPixelFormat.Bgra8, BitmapAlphaMode.Ignore);
var qwer = new SoftwareBitmapSource();
await qwer.SetBitmapAsync(asdf);
CroppedFaceImage.Source = qwer;
But still I have no idea how to display VideoFrame(without saving an image), set the position where to crop.
If you want to show the frame on the xaml, you need to convert the frame to a displayable format and rendering it to the screen. Please check the FrameRender class in the official Camera frames sample. It has a ConvertToDisplayableImage method that should be what you want.
Then, you could show it in Image control. After that, you could use Image.Clip to set the position where you want to crop.
I am generating a tile from within my application and when its displayed the background image that its using as the basis fro the the tile has lost its transparency (and therefore its not picking up the theme color.
The background image has an icon on it and is transparent - when I use it as the standard Tile (i.e. not generate an image with it ) thens its fine and the transparency is all good..
But when I use it as the background image and add my own container over it then its not transparent the background is showing as black.
The relevant code is as follows:
// [...]
var container = new Grid();
if (isWide)
{
container = CreateContainerWide(tileInfo);
}
else
{
container = CreateContainerMedium(tileInfo);
}
// Add the background
container.Background = new ImageBrush
{
ImageSource = background,
Opacity = opacity
};
// Force the container to render itself
container.Arrange(new Rect(0, 0, width, height));
// Write the image to disk and return the filename
return WriteShellTileUIElementToDisk(container, baseFileName);
}
static string WriteShellTileUIElementToDisk(UIElement element, string baseFileName)
{
var wb = new WriteableBitmap(element, null);
// All content must be in this sub-folder of IsoStore
string fileName = SharedImagePath + baseFileName + ImageExtension;
var stream = new IsolatedStorageFileStream(fileName, System.IO.FileMode.Create, Isf);
// Write the JPEG using the standard tile size
// Sometimes the bitmap has (0,0) size and this fails for unknown reasons with an argument exception
if (wb.PixelHeight > 0)
wb.SaveJpeg(stream, wb.PixelWidth, wb.PixelHeight, 0, JpegQuality);
else
{
Debug.WriteLine("Can't write out file because bitmap had 0,0 size; not sure why");
// indicate that there is an issue
fileName = null;
}
stream.Close();
// Return the filename
return fileName;
}
Doesn't seem to make any difference as to what I set the Opacity of the ImageBrush to.
If I used a solid color rataher than a transparent layer then its all fine. Somehow the creation of the png is losing the transparency.
Any Ideas?
thanks
This answer might be helpful. Instead of saving a JPG you can save the image as a PNG which does support transparency. The library mentioned in the answer is quite useful.
Is there a way to capture each WPF MediaElement frame? Like an event that fires at each rendered frame and allows me to access it. If MediaElement does not provide such functionality, how could it be implemented or what other control could I use? On a side note, is there such a control or method that would allow for off-screen fast rendering of media clips with frame capture? (so I could process frames as fast as possible)
Try out my WPF MediaKit project. Allows you to do pretty much anything in WPF with Media. Try out the MediaDetector.cs, it allows you to extract out frames from any time in the media. It's a little buggy as I've never put a lot of time in it, but should work for what you need.
There is no built-in WPF way:
MediaElement does not have this ability.
BitmapDecoder has the API to request this but using BitmapDecoder to extract frames from arbitrary media is not implemented: It can only extract frames from a few animated bitmap formats like .gif.
I was able to get frame images from .mpg, .wmv, .mov, .flv, .avi and other movie formats using DirectShow. I constructed a filter graph using DirectShow's COM graph builder interfaces. The resulting filter graph decoded the movie and connected it to a custom renderer filter written in C#. My custom filter received the frame data and converted it into BitmapSource objects for display using BitmapSource.Create.
The DirectShow solution performed quite well, and the managed to unmanaged transition was no big deal, but it took a while to figure out the details of the DirectShow graph building.
If you use your imagination perhaps this snippet can give you some ideas:
MediaPlayer player = new MediaPlayer();
player.Open(new Uri(_inputFilename));
player.ScrubbingEnabled = true;
DrawingVisual dv = new DrawingVisual();
for (int i = 0; i < session.FramesList.Count; i++)
{
Frame f = session.FramesList[i];
player.Position = new TimeSpan((long)(f.Time * 10000000));
using (DrawingContext dc = dv.RenderOpen())
{
dc.DrawVideo(player, new Rect(0, 0, 1024, 576));
}
RenderTargetBitmap bmp = new RenderTargetBitmap(1024, 576, 96, 96, PixelFormats.Pbgra32);
bmp.Render(dv);
f.Thumbnail = bmp.GetAsFrozen() as ImageSource;
framesListView.Dispatcher.Invoke(() => FramesList.Add(f));
}
you can try this method to output any UI Element including the MediaElement in WPF
public static void ConvertUiElementToBitmap(UIElement elt, string path)
{
double h = elt.RenderSize.Height;
double w = elt.RenderSize.Width;
if (h > 0)
{
PresentationSource source = PresentationSource.FromVisual(elt);
RenderTargetBitmap rtb = new RenderTargetBitmap((int)w, (int)h, 96, 96, PixelFormats.Default);
VisualBrush sourceBrush = new VisualBrush(elt);
DrawingVisual drawingVisual = new DrawingVisual();
DrawingContext drawingContext = drawingVisual.RenderOpen();
using (drawingContext)
{
drawingContext.DrawRectangle(sourceBrush, null, new Rect(new Point(0, 0),
new Point(w, h)));
}
rtb.Render(drawingVisual);
// return rtb;
var encoder = new PngBitmapEncoder();
var outputFrame = BitmapFrame.Create(rtb);
encoder.Frames.Add(outputFrame);
using (var file = System.IO.File.OpenWrite(path))
{
encoder.Save(file);
}
}
}