【问题标题】:Clip BitmapImage using Strokes from a InkCanvas使用 InkCanvas 中的 Strokes 剪辑 BitmapImage
【发布时间】:2016-05-16 16:08:22
【问题描述】:

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

示例:

我正在考虑从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 直接应用到BitmapSource

【问题讨论】:

  • 不清楚,请多解释。
  • @AnjumSKhan 我需要根据形状剪辑给定的图像。

标签: c# wpf canvas clip


【解决方案1】:

我成功了!(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);
    }
}

示例:

干净、未经编辑的动画。

应设置动画的选定像素。

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

电影完成了!

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

【讨论】:

  • 这太棒了!请问您可以发布您使用的依赖项吗?
  • @CodeArtist 当然,看看。我就是这么做的。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-12-19
  • 1970-01-01
  • 1970-01-01
  • 2011-11-06
  • 2013-07-13
相关资源
最近更新 更多