gpt4 book ai didi

c# - 使用 InkCanvas 中的笔触裁剪 BitmapImage

转载 作者:可可西里 更新时间:2023-11-01 09:08:51 35 4
gpt4 key购买 nike

我的任务是创建一个“Cinemagraph”功能,用户必须使用 InkCanvas 选择所需的区域来绘制选定的像素,这些像素在其余的动画/视频中应该保持不变(或者,选择应该“活着”的像素)。

示例:From Johan Blomström

我正在考虑从 InkCanvas 中获取 Stroke 集合,并使用它来裁剪图像并与未触及的图像合并。

我该怎么做?我可以轻松地从磁盘加载图像,但如何根据笔画裁剪图像?

更多详情:

绘制并选择应保持静态的像素后,我有一个 Stroke 集合。我可以获得每个单独的 StrokeGeometry,但我可能需要合并所有几何图形。

基于合并的Geometry,我需要反转(Geometry)并用于剪辑我的第一帧,稍后准备好剪辑图像后,我需要合并与所有其他框架。

到目前为止我的代码:

//Gets the BitmapSource from a String path:
var image = ListFrames[0].ImageLocation.SourceFrom();
var rectangle = new RectangleGeometry(new Rect(new System.Windows.Point(0, 0), new System.Windows.Size(image.Width, image.Height)));
Geometry geometry = Geometry.Empty;

foreach(Stroke stroke in CinemagraphInkCanvas.Strokes)
{
geometry = Geometry.Combine(geometry, stroke.GetGeometry(), GeometryCombineMode.Union, null);
}

//Inverts the geometry, to clip the other unselect pixels of the BitmapImage.
geometry = Geometry.Combine(geometry, rectangle, GeometryCombineMode.Exclude, null);

//This here is UIElement, I can't use this control, I need a way to clip the image without using the UI.
var clippedImage = new System.Windows.Controls.Image();
clippedImage.Source = image;
clippedImage.Clip = geometry;

//I can't get the render of the clippedImage control because I'm not displaying that control.

有没有什么方法可以在不使用 UIElement 的情况下裁剪 BitmapSource?

也许,也许

我正在考虑 OpacityMask 和画笔...但是我不能使用 UIElement,我需要将 OpacityMask 直接应用到 位图源.

最佳答案

我成功了! (You can see the result here, ScreenToGif > Editor > Image Tab > Cinemagraph)


代码

SourceFrom()DpiOf()ScaledSize():

/// <summary>
/// Gets the BitmapSource from the source and closes the file usage.
/// </summary>
/// <param name="fileSource">The file to open.</param>
/// <param name="size">The maximum height of the image.</param>
/// <returns>The open BitmapSource.</returns>
public static BitmapSource SourceFrom(this string fileSource, Int32? size = null)
{
using (var stream = new FileStream(fileSource, FileMode.Open))
{
var bitmapImage = new BitmapImage();
bitmapImage.BeginInit();
bitmapImage.CacheOption = BitmapCacheOption.OnLoad;

if (size.HasValue)
bitmapImage.DecodePixelHeight = size.Value;

//DpiOf() and ScaledSize() uses the same principles of this extension.

bitmapImage.StreamSource = stream;
bitmapImage.EndInit();

//Just in case you want to load the image in another thread.
bitmapImage.Freeze();
return bitmapImage;
}
}

GetRender():

/// <summary>
/// Gets a render of the current UIElement
/// </summary>
/// <param name="source">UIElement to screenshot</param>
/// <param name="dpi">The DPI of the source.</param>
/// <returns>An ImageSource</returns>
public static RenderTargetBitmap GetRender(this UIElement source, double dpi)
{
Rect bounds = VisualTreeHelper.GetDescendantBounds(source);

var scale = dpi / 96.0;
var width = (bounds.Width + bounds.X) * scale;
var height = (bounds.Height + bounds.Y) * scale;

#region If no bounds

if (bounds.IsEmpty)
{
var control = source as Control;

if (control != null)
{
width = control.ActualWidth * scale;
height = control.ActualHeight * scale;
}

bounds = new Rect(new System.Windows.Point(0d, 0d),
new System.Windows.Point(width, height));
}

#endregion

var roundWidth = (int)Math.Round(width, MidpointRounding.AwayFromZero);
var roundHeight = (int)Math.Round(height, MidpointRounding.AwayFromZero);

var rtb = new RenderTargetBitmap(roundWidth, roundHeight, dpi, dpi,
PixelFormats.Pbgra32);

DrawingVisual dv = new DrawingVisual();
using (DrawingContext ctx = dv.RenderOpen())
{
VisualBrush vb = new VisualBrush(source);

var locationRect = new System.Windows.Point(bounds.X, bounds.Y);
var sizeRect = new System.Windows.Size(bounds.Width, bounds.Height);

ctx.DrawRectangle(vb, null, new Rect(locationRect, sizeRect));
}

rtb.Render(dv);
return (RenderTargetBitmap)rtb.GetAsFrozen();
}

获取ImageSourceGeometry:

//Custom extensions, that using the path of the image, will provide the
//DPI (of the image) and the scaled size (PixelWidth and PixelHeight).
var dpi = ListFrames[0].ImageLocation.DpiOf();
var scaledSize = ListFrames[0].ImageLocation.ScaledSize();

//Custom extension that loads the first frame.
var image = ListFrames[0].ImageLocation.SourceFrom();

//Rectangle with the same size of the image. Used within the Xor operation.
var rectangle = new RectangleGeometry(new Rect(
new System.Windows.Point(0, 0),
new System.Windows.Size(image.PixelWidth, image.PixelHeight)));
Geometry geometry = Geometry.Empty;

//Each Stroke is transformed into a Geometry and combined with an Union operation.
foreach(Stroke stroke in CinemagraphInkCanvas.Strokes)
{
geometry = Geometry.Combine(geometry, stroke.GetGeometry(),
GeometryCombineMode.Union, null);
}

//The rectangle with the same size of the image is combined with all of
//the Strokes using the Xor operation, basically it inverts the Geometry.
geometry = Geometry.Combine(geometry, rectangle, GeometryCombineMode.Xor, null);

Geometry 应用于 Image 元素:

//UIElement used to hold the BitmapSource to be clipped.
var clippedImage = new System.Windows.Controls.Image
{
Height = image.PixelHeight,
Width = image.PixelWidth,
Source = image,
Clip = geometry
};
clippedImage.Measure(scaledSize);
clippedImage.Arrange(new Rect(scaledSize));

//Gets the render of the Image element, already clipped.
var imageRender = clippedImage.GetRender(dpi, scaledSize);

//Merging with all frames:
Overlay(imageRender, dpi, true);

Overlay(),合并帧:

private void Overlay(RenderTargetBitmap render, double dpi, bool forAll = false)
{
//Gets the selected frames based on the selection of a ListView,
//In this case, every frame should be selected.
var frameList = forAll ? ListFrames : SelectedFrames();

int count = 0;
foreach (FrameInfo frame in frameList)
{
var image = frame.ImageLocation.SourceFrom();

var drawingVisual = new DrawingVisual();
using (DrawingContext drawingContext = drawingVisual.RenderOpen())
{
drawingContext.DrawImage(image, new Rect(0, 0, image.Width, image.Height));
drawingContext.DrawImage(render, new Rect(0, 0, render.Width, render.Height));
}

//Converts the Visual (DrawingVisual) into a BitmapSource
var bmp = new RenderTargetBitmap(image.PixelWidth, image.PixelHeight, dpi, dpi, PixelFormats.Pbgra32);
bmp.Render(drawingVisual);

//Creates a BmpBitmapEncoder and adds the BitmapSource to the frames of the encoder
var encoder = new BmpBitmapEncoder();
encoder.Frames.Add(BitmapFrame.Create(bmp));

//Saves the image into a file using the encoder
using (Stream stream = File.Create(frame.ImageLocation))
encoder.Save(stream);
}
}

示例:

干净、未经编辑的动画。

Animation

应设置动画的选定像素。

Green is the pixels that should move

图像已被裁剪(黑色是透明的)。

Clipped image

电影摄影完成!

Only the selected pixels move

如您所见,只有选定的像素可以更改,其他像素保持不变。

关于c# - 使用 InkCanvas 中的笔触裁剪 BitmapImage,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/35245195/

35 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com