You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

UWP中如何高效从已有SoftwareBitmap的区域创建新的深拷贝实例

高效裁剪并深拷贝UWP SoftwareBitmap指定区域的最优方案

我刚好研究过这个痛点——UWP原生的SoftwareBitmap确实没提供直接裁剪指定区域并深拷贝的方法,之前用WriteableBitmap的两次拷贝加Blit的方式确实有点冗余。这里有两个更高效的方案,你可以根据自己的场景选:

方案一:纯UWP原生API(无额外依赖)

这个方案直接操作像素缓冲区,只拷贝你需要的区域,避免全图拷贝的开销。核心思路是锁定源和目标的缓冲区,逐行拷贝指定区域的像素数据:

实现步骤

  1. 锁定源SoftwareBitmap的缓冲区,获取像素数据的内存访问权限;
  2. 根据指定的BitmapBounds计算源区域的起始偏移、每行的字节跨度(Stride);
  3. 创建匹配源格式、尺寸为裁剪区域大小的目标SoftwareBitmap;
  4. 锁定目标缓冲区,将源区域的像素数据逐行拷贝到目标缓冲区。

代码示例

using System.Runtime.InteropServices;
using Windows.Graphics.Imaging;

public static SoftwareBitmap CropSoftwareBitmap(SoftwareBitmap sourceBitmap, BitmapBounds cropBounds)
{
    // 校验参数:确保裁剪区域在源位图范围内
    if (cropBounds.X < 0 || cropBounds.Y < 0 || 
        cropBounds.X + cropBounds.Width > sourceBitmap.PixelWidth || 
        cropBounds.Y + cropBounds.Height > sourceBitmap.PixelHeight)
    {
        throw new ArgumentOutOfRangeException(nameof(cropBounds), "裁剪区域超出源位图范围");
    }

    // 创建目标SoftwareBitmap
    var targetBitmap = new SoftwareBitmap(
        sourceBitmap.BitmapPixelFormat,
        (int)cropBounds.Width,
        (int)cropBounds.Height,
        sourceBitmap.BitmapAlphaMode);

    // 锁定源和目标缓冲区
    using (var sourceBuffer = sourceBitmap.LockBuffer(BitmapBufferAccessMode.Read))
    using (var targetBuffer = targetBitmap.LockBuffer(BitmapBufferAccessMode.Write))
    {
        // 获取源缓冲区的内存引用
        var sourceReference = sourceBuffer.CreateReference();
        byte* sourceBytes;
        uint sourceCapacity;
        ((IMemoryBufferByteAccess)sourceReference).GetBuffer(out sourceBytes, out sourceCapacity);

        // 获取目标缓冲区的内存引用
        var targetReference = targetBuffer.CreateReference();
        byte* targetBytes;
        uint targetCapacity;
        ((IMemoryBufferByteAccess)targetReference).GetBuffer(out targetBytes, out targetCapacity);

        // 计算源和目标的每行字节数(Stride)
        var sourceStride = sourceBuffer.GetPlaneDescription(0).Stride;
        var targetStride = targetBuffer.GetPlaneDescription(0).Stride;
        var bytesPerPixel = sourceBuffer.GetPlaneDescription(0).BytesPerPixel;

        // 计算源区域的起始字节偏移
        var sourceStartOffset = (int)(cropBounds.Y * sourceStride + cropBounds.X * bytesPerPixel);

        // 逐行拷贝像素数据
        for (int y = 0; y < cropBounds.Height; y++)
        {
            // 拷贝当前行的指定区域像素
            Buffer.MemoryCopy(
                sourceBytes + sourceStartOffset + y * sourceStride,
                targetBytes + y * targetStride,
                targetCapacity - (uint)(y * targetStride),
                (uint)(cropBounds.Width * bytesPerPixel));
        }
    }

    return targetBitmap;
}

// 需要引入COM接口以访问内存缓冲区
[ComImport]
[Guid("5B0D3235-4DBA-4D44-865E-8F1D0E4FD04D")]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
internal interface IMemoryBufferByteAccess
{
    void GetBuffer(out byte* buffer, out uint capacity);
}

注意:这个方法需要在项目中启用“允许不安全代码”(在项目属性的生成选项里勾选),因为用到了指针操作。

方案二:使用Win2D库(更高效简洁)

如果可以引入微软官方的Win2D图形库,这会是更优的选择——Win2D基于Direct2D,底层硬件加速,代码也更简洁,不需要手动操作内存指针。

实现步骤

  1. 安装Win2D.uwp NuGet包;
  2. 将源SoftwareBitmap转换为CanvasBitmap;
  3. 创建一个尺寸为裁剪区域大小的CanvasRenderTarget;
  4. 在CanvasRenderTarget上绘制源CanvasBitmap的指定区域;
  5. 将CanvasRenderTarget转换回SoftwareBitmap。

代码示例

using Microsoft.Graphics.Canvas;
using Windows.Graphics.Imaging;

public static async Task<SoftwareBitmap> CropSoftwareBitmapWithWin2D(SoftwareBitmap sourceBitmap, BitmapBounds cropBounds)
{
    // 校验参数
    if (cropBounds.X < 0 || cropBounds.Y < 0 || 
        cropBounds.X + cropBounds.Width > sourceBitmap.PixelWidth || 
        cropBounds.Y + cropBounds.Height > sourceBitmap.PixelHeight)
    {
        throw new ArgumentOutOfRangeException(nameof(cropBounds), "裁剪区域超出源位图范围");
    }

    // 创建CanvasDevice(Win2D的设备上下文)
    using var device = CanvasDevice.GetSharedDevice();
    // 将源SoftwareBitmap转换为CanvasBitmap
    using var sourceCanvasBitmap = await CanvasBitmap.CreateFromSoftwareBitmapAsync(device, sourceBitmap);
    // 创建目标尺寸的CanvasRenderTarget
    using var renderTarget = new CanvasRenderTarget(
        device,
        (float)cropBounds.Width,
        (float)cropBounds.Height,
        sourceCanvasBitmap.Dpi);

    // 在RenderTarget上绘制裁剪区域
    using var ds = renderTarget.CreateDrawingSession();
    ds.DrawImage(
        sourceCanvasBitmap,
        new Rect(0, 0, cropBounds.Width, cropBounds.Height),
        new Rect(cropBounds.X, cropBounds.Y, cropBounds.Width, cropBounds.Height));

    // 转换回SoftwareBitmap
    return await renderTarget.CreateSoftwareBitmapAsync(sourceBitmap.BitmapPixelFormat);
}

这个方案不仅代码更简洁,而且因为Win2D利用了硬件加速,性能比原生手动拷贝更高,适合对性能要求高的场景。

方案对比

  • 纯原生API:无需额外依赖,适合不能引入第三方库的场景,但代码稍复杂,需要处理指针和内存拷贝;
  • Win2D方案:代码简洁,性能更优,官方推荐的图形库,大多数UWP图形场景都适用。

内容的提问来源于stack exchange,提问作者Pawel

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.20 09:20:01