2018-8-10-win10-uwp-如何创建修改保存位图
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 |
2018-8-10-win10-uwp-如何创建修改保存位图的更多相关文章
- win10 uwp 使用动画修改 Grid column 的宽度
今天 wurstmitbrot 问如何通过动画修改 Grid 的 column ,虽然 column 是一个依赖属性,可以绑定,但是做出动画还是比较难的. 本文告诉大家如何对 Grid 做动画. 首先 ...
- win10 UWP 动画
原文:win10 UWP 动画 本文告诉大家如何写同一个简单的动画. 动画入门 本文开始写一个简单的动画,只是移动矩形作为本文的例子. 在 UWP 移动元素的动画,可以使用 RenderTransfo ...
- win10 uwp 使用 Microsoft.Graph 发送邮件
在 2018 年 10 月 13 号参加了 张队长 的 Office 365 训练营 学习如何开发 Office 365 插件和 OAuth 2.0 开发,于是我就使用 UWP 尝试使用 Micros ...
- win10 uwp 列表模板选择器
本文主要讲ListView等列表可以根据内容不同,使用不同模板的列表模板选择器,DataTemplateSelector. 如果在 UWP 需要定义某些列的显示和其他列不同,或者某些行的显示和其他行不 ...
- win10 uwp 毛玻璃
毛玻璃在UWP很简单,不会和WPF那样伤性能. 本文告诉大家,如何在 UWP 使用 win2d 做毛玻璃. 毛玻璃可以使用 win2D 方法,也可以使用 Compositor . 使用 win2d 得 ...
- win10 uwp 商业游戏 1.2.1
上一个游戏已经告诉大家如何写多个游戏,现在继续写这个无聊的游戏 希望大家在看这篇文章之前先看win10 uwp 商业游戏,在这个文章告诉了大家如何创建游戏. 修改数值 可以从上一篇的博客的游戏看到升级 ...
- win10 uwp 渲染原理 DirectComposition 渲染
本文来告诉大家一个新的技术DirectComposition,在 win7 之后(实际上是 vista),微软正在考虑一个新的渲染机制 在 Windows Vista 就引入了一个服务,桌面窗口管理器 ...
- win10 uwp 商业游戏
本文告诉大家去做一个商业游戏,游戏很简单,几乎没有什么技术 游戏的开始,需要添加框架库,于是引用我自己写的库. 首先是创建一个启动页面,这个页面是显示启动的. 在显示启动的时候,是需要加载游戏需要使用 ...
- win10 uwp 线程池
原文:win10 uwp 线程池 如果大家有开发 WPF 或以前的程序,大概知道线程池不是 UWP 创造的,实际上在很多技术都用到线程池. 为什么需要线程池,他是什么?如何在 UWP 使用线程池,本文 ...
随机推荐
- storm-jdbc的使用
最近项目组分配到研究storm-jdbc用法 发现网上关于insert和query方法挺多的,但是自定义方法很少.而且用法上也挺多缺陷.在此自己总结记录一下 JdbcInsertBolt 的核心代码 ...
- Redis源码解析:25集群(一)握手、心跳消息以及下线检测
Redis集群是Redis提供的分布式数据库方案,通过分片来进行数据共享,并提供复制和故障转移功能. 一:初始化 1:数据结构 在源码中,通过server.cluster记录整个集群当前的状态,比如集 ...
- 洛谷P5071 此时此刻的光辉
2s512M. 解:先分解质因数.考虑按照质因数大小是否大于√分类. 大于的就是一个数颜色个数,莫队即可n√m. 小于的直接枚举质因数做前缀和然后O(1)查询.总时间复杂度n(√m + σ(√V)). ...
- 【python之路32】python异常处理
一.捕获异常 1.try except #!usr/bin/env python # -*- coding:utf-8 -*- num = input("请输入一个数字:") t ...
- TZ_13_负载均衡-Robbin
1.但是实际环境中,我们往往会开启很多个user-service的集群.此时我们获取的服务列表中就会有多个,到底该访问哪一个呢? 一般这种情况下我们就需要编写负载均衡算法,在多个实例列表中进行选择. ...
- GIT → 11:Git 工作流与实战演练
GIT → 11:Git 工作流与实战演练
- Vue. 之 Element table 单元格内容隐藏
Vue. 之 Element table 单元格内容隐藏 在table显示数据时,若某个单元格的内容过多,需要进行隐层,在这一列的单元格属性添加::show-overflow-tooltip=&quo ...
- Uva437 The Tower of Babylon
https://odzkskevi.qnssl.com/5e1fdf8cae5d11a8f572bae96d6095c0?v=1507521965 Perhaps you have heard of ...
- Javascript-选择器集合调用方法
<script type="text/javascript"> function uu(namePd) { //判断id var reId = new RegExp(/ ...
- Oracle存储1.1
1.生成一个表的简单sql语句 CREATE OR REPLACE PROCEDURE proc_AutoGenerateSQL( tableName VARCHAR2 ,--参数 需要操作的表 ...