title author date CreateTime categories
win10 uwp 如何创建修改保存位图
lindexi
2018-08-10 19:16:50 +0800
2018-05-05 20:23:18 +0800
Win10 UWP

本文告诉大家如何使用 Softwarebitmap 进行创建、修改保存图片。

在 UWP 使用底层的图像渲染就是使用 Softwarebitmap ,这个类提供直接数据修改,可以使用这个类进行软渲染。实际上 Softwarebitmap 和 WriteableBitmap 是差不多的。但是 Softwarebitmap 可以支持 WriteableBitmap 、 Direct3D 和代码修改。通过 Softwarebitmap 可以修改转换不同的像素格式和透明通道,支持低级修改像素。作为一个通用的底层类在很多性能要求比较高的地方用到,如 CapturedFrame、VideoFrame、FaceDetector。下面来告诉大家如何使用。

创建

下面来告诉大家如何读取文件,使用图片数据创建 Softwarebitmap 图片。

首先是需要使用 FileOpenPicker 拿到一张图片,如何读写文件参见:win10 UWP读写文件

因为很简单,下面直接拿到一张 jpg ,当然需要用户点击。下面代码是直接从微软文档复制的,我自己没运行,看起来大家可以直接使用。

FileOpenPicker fileOpenPicker = new FileOpenPicker();
fileOpenPicker.SuggestedStartLocation = PickerLocationId.PicturesLibrary;
fileOpenPicker.FileTypeFilter.Add(".jpg");
fileOpenPicker.ViewMode = PickerViewMode.Thumbnail; var inputFile = await fileOpenPicker.PickSingleFileAsync(); if (inputFile == null)
{
// The user cancelled the picking operation
return;
}

因为需要拿到文件内容,需要使用 OpenAsync 方法获得随机访问流。随机访问流就是可以在随机的地方进行读写,和他不相同的是顺序流,也就是只能顺序读写。使用 BitmapDecoder.CreateAsync 创建一个图片解析,用来拿到图片

SoftwareBitmap softwareBitmap;

using (IRandomAccessStream stream = await inputFile.OpenAsync(FileAccessMode.Read))
{
// Create the decoder from the stream
BitmapDecoder decoder = await BitmapDecoder.CreateAsync(stream); // Get the SoftwareBitmap representation of the file
softwareBitmap = await decoder.GetSoftwareBitmapAsync();
}

保存图片

上面和大家说如何读取文件,现在就可以把刚才读取的图片保存。保存需要用户选择保存在哪

          FileSavePicker fileSavePicker = new FileSavePicker();
fileSavePicker.SuggestedStartLocation = PickerLocationId.PicturesLibrary;
fileSavePicker.FileTypeChoices.Add("png files", new List<string>() { ".png" });
fileSavePicker.SuggestedFileName = "image"; var outputFile = await fileSavePicker.PickSaveFileAsync(); if (outputFile == null)
{
// The user cancelled the picking operation
return;
}

使用 OpenAsync 方法打开文件,转换随机写入流写入数据。使用 BitmapEncoder.CreateAsync 创建 BitmapEncoder 。创建的函数第一个参数是 GUID 表示需要哪个格式,可以通过 BitmapEncoder 输入,下面代码就是把刚才读取的 jpg 图片转换为 Png 格式。

           using (var stream = await outputFile.OpenAsync(FileAccessMode.ReadWrite))
{
// 格式 png 通过把上面打开的图片转换
BitmapEncoder encoder = await BitmapEncoder.CreateAsync(BitmapEncoder.PngEncoderId, stream); encoder.SetSoftwareBitmap(_softwareBitmap);
}

这个方法就是把 softwareBitmap 转换为 Stream 的方法

如果在保存需要对图片进行编辑,可以使用 BitmapTransform 进行变换,请看代码

                encoder.BitmapTransform.ScaledWidth = 320;
encoder.BitmapTransform.ScaledHeight = 240;
// 90度旋转
encoder.BitmapTransform.Rotation = BitmapRotation.Clockwise90Degrees;
// 大的图片转换为小的图片,需要使用插值算法,不然会模糊
encoder.BitmapTransform.InterpolationMode = BitmapInterpolationMode.Fant;
// 创建新的缩略图
encoder.IsThumbnailGenerated = true;

因为不是所有的文件格式都支持缩略图,如果使用了创建新的图就需要 catch 不支持异常。

在转换图片需要调用 FlushAsync 保存图片。

                try
{
await encoder.FlushAsync();
}
catch (Exception exception)
{
const int WINCODEC_ERR_UNSUPPORTEDOPERATION = unchecked((int) 0x88982F81); switch (exception.HResult)
{
case WINCODEC_ERR_UNSUPPORTEDOPERATION:
// 如果格式不支持,就会出现这个异常,需要禁止创建缩略图,然后继续保存
encoder.IsThumbnailGenerated = false;
break;
default:
throw;
}
} if (encoder.IsThumbnailGenerated == false)
{
await encoder.FlushAsync();
}

现在在前台添加两个按钮,一个用于打开文件,另一个用来保存图片

随便选一个 jpg 文件,然后保存,可以看到保存了新的格式

在 UWP 可以使用上面的方法修改图片格式

上面代码只是简单使用,在创建 BitmapEncoder 可以传入 BitmapPropertySet 指定图片质量

      var propertySet = new Windows.Graphics.Imaging.BitmapPropertySet();
var qualityValue = new Windows.Graphics.Imaging.BitmapTypedValue(
1.0, // Maximum quality
Windows.Foundation.PropertyType.Single
); propertySet.Add("ImageQuality", qualityValue); // 格式 png 通过把上面打开的图片转换
BitmapEncoder encoder = await BitmapEncoder.CreateAsync(BitmapEncoder.PngEncoderId, stream, propertySet);

其他的值请看 BitmapEncoder options reference

在 Image 控件使用

刚才的代码没有显示打开的图片,如果要把 SoftwareBitmap 在 Image 使用,就需要使用 SoftwareBitmapSource 转换,因为 Image 控件只支持 BGRA8 格式而且需要先计算透明值,在转换打开 SoftwareBitmap 静态函数 Convert 让格式在 Image 控件支持。

先在界面创建一个 Image 控件,然后在后台添加代码显示

      <Image x:Name="MaixallnayMesejas"></Image>
            if (_softwareBitmap.BitmapPixelFormat != BitmapPixelFormat.Bgra8 ||
_softwareBitmap.BitmapAlphaMode == BitmapAlphaMode.Straight)
{
_softwareBitmap = SoftwareBitmap.Convert(_softwareBitmap, BitmapPixelFormat.Bgra8,
BitmapAlphaMode.Premultiplied);
} var source = new SoftwareBitmapSource();
await source.SetBitmapAsync(_softwareBitmap);
MaixallnayMesejas.Source = source;

尝试运行一下代码就可以看到显示图片在打开文件。

实际上通过 下面代码可以把 SoftwareBitmap 转 ImageBrush 显示

            var imageBrush = new ImageBrush {ImageSource = source};

WriteableBitmap 转换

上面都是读写文件,如果已经使用了 WriteableBitmap 需要把他转换 SoftwareBitmap 可以使用 SoftwareBitmap 的静态函数 SoftwareBitmap.CreateCopyFromBuffer 转换。

SoftwareBitmap outputBitmap = SoftwareBitmap.CreateCopyFromBuffer(
writeableBitmap.PixelBuffer,
BitmapPixelFormat.Bgra8,
writeableBitmap.PixelWidth,
writeableBitmap.PixelHeight
);

通过读写像素

是不是看到上面的教程感觉这个博客很简单,我就来告诉大家很黑的方法,如果看到这里还没有关闭这个网页,那么现在关闭还是可以,不然我就来和大家说很黑科技的写法。

如果大家直接从 SoftwareBitmap 使用 Resharper 无论怎么点都无法找到读写像素的方法。但是我会告诉大家我自己创建了一个接口,使用这个接口就可以读写。

首先引用using System.Runtime.InteropServices;然后创建接口

    [ComImport]
[Guid("5B0D3235-4DBA-4D44-865E-8F1D0E4FD04D")]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
internal unsafe interface IMemoryBufferByteAccess
{
void GetBuffer(out byte* buffer, out uint capacity);
}

创建这个接口有什么用,先不告诉大家,因为用了不安全,需要在项目属性,生成,可以使用不安全

我来告诉大家如何从代码创建 SoftwareBitmap ,读写像素。

创建一个空白的 SoftwareBitmap 需要设置格式

            var softwareBitmap = new SoftwareBitmap(BitmapPixelFormat.Bgra8, 100, 100, BitmapAlphaMode.Straight);

使用 LockBuffer 可以拿到 buffer ,使用 buffer.CreateReference 可以拿到指针

            using (var buffer = softwareBitmap.LockBuffer(BitmapBufferAccessMode.ReadWrite))
{
using (var reference = buffer.CreateReference())
{
}
}

然后就是不安全代码,本文的黑科技就是这个代码

       using (var buffer = softwareBitmap.LockBuffer(BitmapBufferAccessMode.ReadWrite))
{
using (var reference = buffer.CreateReference())
{
unsafe
{
((IMemoryBufferByteAccess) reference).GetBuffer(out var dataInBytes, out _);
}
}

把 reference 转换为我自己定义的接口,使用 GetBuffer 拿到数据指针。

这个的原理,本渣在这里不会说。

拿到了 dataInBytes 就是按照 BGRA 的顺序,但是还不知道图片的宽度用了多少个,而且图片如果是分层的,第 n 层是从哪个数据开始。为了知道指针的开始,就使用 BitmapBuffer 的方法

  BitmapPlaneDescription bufferLayout = buffer.GetPlaneDescription(0);

获取图层数量可以使用buffer.GetPlaneCount(),因为第 0 个在这里是有的,所以直接使用

那么图片的宽使用多少个如何拿到,bufferLayout.StartIndex 就是拿到图层开始所在,bufferLayout.Stride 就是一行使用了多少 byte 。所以要访问第 i 行 j 列的像素就可以使用下面的代码

dataInBytes[bufferLayout.StartIndex + bufferLayout.Stride * i + 4 * j + 0] // B

dataInBytes[bufferLayout.StartIndex + bufferLayout.Stride * i + 4 * j + 1] // G

ataInBytes[bufferLayout.StartIndex + bufferLayout.Stride * i + 4 * j + 2] // R

dataInBytes[bufferLayout.StartIndex + bufferLayout.Stride * i + 4 * j + 3] // A

写入的方式就是直接给一个值,读取的方式就是去拿,方法很简单,下面来写一个渐变

      ((IMemoryBufferByteAccess)reference).GetBuffer(out dataInBytes, out capacity);

        // Fill-in the BGRA plane
BitmapPlaneDescription bufferLayout = buffer.GetPlaneDescription(0);
for (int i = 0; i < bufferLayout.Height; i++)
{
for (int j = 0; j < bufferLayout.Width; j++)
{ byte value = (byte)((float)j / bufferLayout.Width * 255);
dataInBytes[bufferLayout.StartIndex + bufferLayout.Stride * i + 4 * j + 0] = value;
dataInBytes[bufferLayout.StartIndex + bufferLayout.Stride * i + 4 * j + 1] = value;
dataInBytes[bufferLayout.StartIndex + bufferLayout.Stride * i + 4 * j + 2] = value;
dataInBytes[bufferLayout.StartIndex + bufferLayout.Stride * i + 4 * j + 3] = (byte)255;
}
}

转换 CanvasBitmap

使用 CanvasBitmap.CreateFromSoftwareBitmap 可以从 SoftwareBitmap 转换为 CanvasBitmap

var canvasBitmap = CanvasBitmap.CreateFromSoftwareBitmap(device, softwareBitmap);

需要注意,如果 SoftwareBitmap 的像素格式比较诡异,那么不一定能创建

sourceBitmap's BitmapPixelFormat CanvasBitmap's Format
BitmapPixelFormat.Unknown unsupported
BitmapPixelFormat.Rgba16 DirectXPixelFormat.R16G16B16A16UIntNormalized
BitmapPixelFormat.Rgba8 DirectXPixelFormat.R8G8B8A8UIntNormalized
BitmapPixelFormat.Gray16 unsupported
BitmapPixelFormat.Gray8 DirectXPixelFormat.A8UIntNormalized
BitmapPixelFormat.Bgra8 DirectXPixelFormat.B8G8R8A8UIntNormalized
BitmapPixelFormat.Nv12 unsupported
BitmapPixelFormat.Yuy2 unsupported

参见:CanvasBitmap.CreateFromSoftwareBitmap Method

Create, edit, and save bitmap images - UWP app developer

2018-8-10-win10-uwp-如何创建修改保存位图的更多相关文章

  1. win10 uwp 使用动画修改 Grid column 的宽度

    今天 wurstmitbrot 问如何通过动画修改 Grid 的 column ,虽然 column 是一个依赖属性,可以绑定,但是做出动画还是比较难的. 本文告诉大家如何对 Grid 做动画. 首先 ...

  2. win10 UWP 动画

    原文:win10 UWP 动画 本文告诉大家如何写同一个简单的动画. 动画入门 本文开始写一个简单的动画,只是移动矩形作为本文的例子. 在 UWP 移动元素的动画,可以使用 RenderTransfo ...

  3. win10 uwp 使用 Microsoft.Graph 发送邮件

    在 2018 年 10 月 13 号参加了 张队长 的 Office 365 训练营 学习如何开发 Office 365 插件和 OAuth 2.0 开发,于是我就使用 UWP 尝试使用 Micros ...

  4. win10 uwp 列表模板选择器

    本文主要讲ListView等列表可以根据内容不同,使用不同模板的列表模板选择器,DataTemplateSelector. 如果在 UWP 需要定义某些列的显示和其他列不同,或者某些行的显示和其他行不 ...

  5. win10 uwp 毛玻璃

    毛玻璃在UWP很简单,不会和WPF那样伤性能. 本文告诉大家,如何在 UWP 使用 win2d 做毛玻璃. 毛玻璃可以使用 win2D 方法,也可以使用 Compositor . 使用 win2d 得 ...

  6. win10 uwp 商业游戏 1.2.1

    上一个游戏已经告诉大家如何写多个游戏,现在继续写这个无聊的游戏 希望大家在看这篇文章之前先看win10 uwp 商业游戏,在这个文章告诉了大家如何创建游戏. 修改数值 可以从上一篇的博客的游戏看到升级 ...

  7. win10 uwp 渲染原理 DirectComposition 渲染

    本文来告诉大家一个新的技术DirectComposition,在 win7 之后(实际上是 vista),微软正在考虑一个新的渲染机制 在 Windows Vista 就引入了一个服务,桌面窗口管理器 ...

  8. win10 uwp 商业游戏

    本文告诉大家去做一个商业游戏,游戏很简单,几乎没有什么技术 游戏的开始,需要添加框架库,于是引用我自己写的库. 首先是创建一个启动页面,这个页面是显示启动的. 在显示启动的时候,是需要加载游戏需要使用 ...

  9. win10 uwp 线程池

    原文:win10 uwp 线程池 如果大家有开发 WPF 或以前的程序,大概知道线程池不是 UWP 创造的,实际上在很多技术都用到线程池. 为什么需要线程池,他是什么?如何在 UWP 使用线程池,本文 ...

随机推荐

  1. HDFS应用实例

  2. C++ 静态绑定与动态绑定------绝不重新定义继承而来的缺省参数

    在了解静态绑定和动态绑定之前,先了解什么是对象的静态类型,什么是对象的动态类型. 对象的静态类型:对象在声明时采用的类型.是在编译器决定的. 对象的动态类型:目前所指对象的类型.是在运行期决定的. 动 ...

  3. Web前端浏览器兼容性个人经验总结

    前言 浏览器兼容是前端开发人员必须掌握的一个技能,但是初入前端的同学或者其他后台web开发同学往往容易选择忽略,而形成两个极端: 我最开始都是使用IE6,IE6上没问题,其它浏览器坑爹(多出现与前端后 ...

  4. AppServer初始化渠道

    1.写了一个拦截器(RequestInterceptor) 2.里边有个有一个doRequest()方法 3.初始化渠道的方法(initRequestContext()) -------------- ...

  5. IDEA设置使空格处显示小点

  6. TZ_14_Zuul网关_过滤器

    1.Zuul作为网关的其中一个重要功能,就是实现请求的鉴权.而这个动作我们往往是通过Zuul提供的过滤器来实现的. 2.自定义过滤器实现用户登陆时需要携带一个Key才可以登陆,否则返回403 1> ...

  7. mac mamp host 配置

    <VirtualHost *:80> DocumentRoot "/Users/xuxu/www" ServerName localhost <Directory ...

  8. 外网如何访问vmware虚拟机的web服务(转载)

    目的: 主机上安装了VMware,VMware上安装了Linux虚拟机(我安装的是Centos7).我想让虚拟机向外提供Web服务.本文记录如何让我的主机和外网用户可以访问VM虚拟机上的Web. 网络 ...

  9. 详解PPP模式下的产业投资基金运作【基金管理】

       详解PPP模式下的产业投资基金运作[基金管理]     点击标题下「搏实资本」可快速关注 搏实资本 研究型的投资机构,实操型的专家团队 ﹌﹌﹌﹌﹌﹌﹌﹌﹌﹌﹌﹌﹌﹌﹌﹌﹌ 一.产业投资基金概述 ...

  10. LintCode_114 不同的路径,115 不同的路径 II

    题目 有一个机器人的位于一个M×N个网格左上角(下图中标记为'Start'). 机器人每一时刻只能向下或者向右移动一步.机器人试图达到网格的右下角(下图中标记为'Finish'). 问有多少条不同的路 ...