关于 Avalonia 中的图片
016 关于 Avalonia 中的图片
0. 引用
1. 前言
我的一个朋友最近在整一个 Avalonia 的项目,它在自己的项目里整了一个内嵌图片资源,但是用的是 Byte[] 的形式转出来的,他为此非常苦恼,甚至向我要了一个方便的转Base64的工具来解决部分平台识别为 Bitmap,而跨平台的原生 Avalonia 并没有 Bitmap 的问题,以我的直觉,这并不是一个合理的在 C# 中动态获取内部资源的好方式,本文希望能够探讨在 C# 部分获得图片资源,并在图片控件中显示出来的方式,以及探索 Avalonia 中有什么有意思的图片对象。在 WPF 中,本人对 WriteableBitmap 非常留念,本文亦在深入了解 Avalonia 和 WPF 的差异。
2. 使用 AssetLoader 创建 Bitmap 实例
因为文档中没有完整的一篇介绍的,原文在 https://docs.avaloniaui.net/zh-Hans/docs/basics/user-interface/assets#在代码中加载资产 这里。
为此我单独摘了出来。
var bitmap = new Bitmap(AssetLoader.Open(new Uri(uri)));
3. IImage 的继承者们
- DrawingImage
- Bitmap
- CroppedBitmap
- RenderTargetBitmap
- WriteableBitmap
Avalonia 给 Image 的 Source 要求的类型是 IImage,框架中的实现大概有以上几类。
3.1 DrawingImage
https://reference.avaloniaui.net/api/Avalonia.Media/DrawingImage/
DrawingImage 的构造函数可以接收一个 Drawing 对象,具体有 GlyphRunDrawing[https://reference.avaloniaui.net/api/Avalonia.Media/GlyphRunDrawing/]、DrawingGroup[https://reference.avaloniaui.net/api/Avalonia.Media/DrawingGroup/] 和 GeometryDrawing[https://reference.avaloniaui.net/api/Avalonia.Media/GeometryDrawing/],
我们以介绍 GeometryDrawing 作为例子介绍 DrawingImage 的构造用法。

protected override void OnLoaded(RoutedEventArgs e)
{
base.OnLoaded(e);
var geometryDrawing = new GeometryDrawing();
geometryDrawing.Geometry = new EllipseGeometry() { RadiusX = 100, RadiusY = 200};
geometryDrawing.Brush = Brushes.Aqua;
geometryDrawing.Pen = new Pen(brush: Brushes.Red, thickness: 2);
var drawingImage = new DrawingImage(geometryDrawing);
PART_Image.Source = drawingImage;
}
3.2 Bitmap
使用 AssetLoader 加载资源,其中资源位于这个位置:


protected override void OnLoaded(RoutedEventArgs e)
{
base.OnLoaded(e);
var uri = new Uri("avares://ImageControlTest/Assets/img.jpg");
var asset = AssetLoader.Open(uri);
var bitmap = new Bitmap(asset);
PART_Image.Source = bitmap;
}
资源的格式可见 https://www.cnblogs.com/fanbal/articles/18186938 最后一节。
3.3 CroppedBitmap
在 Bitmap 显示图片的基础上,可以进行裁剪操作,需要注意的一点是构造时仍然需要有一个 Bitmap 作为来源。

protected override void OnLoaded(RoutedEventArgs e)
{
base.OnLoaded(e);
var uri = new Uri("avares://ImageControlTest/Assets/img.jpg");
var asset = AssetLoader.Open(uri);
var pixelRect = new PixelRect(x: 0, y: 0, width: 500, height: 500);
var bitmap = new Bitmap(asset);
var croppedBitmap = new CroppedBitmap(bitmap, pixelRect);
PART_Image.Source = croppedBitmap;
}
3.4 RenderTargetBitmap
这个东西说是导出,但是
感觉有点成效?但是还是不知道怎么用,有点抽象?

protected override void OnLoaded(RoutedEventArgs e)
{
base.OnLoaded(e);
var visualSize = new Avalonia.Size(50, 50);
var visualRect = new Rect(size: visualSize);
var textBlock = new TextBlock() { Text = "hello", FontFamily = "Microsoft Tahei" };
textBlock.Measure(availableSize: visualSize);
textBlock.Arrange(rect: visualRect);
var renderTargetBitmap = new RenderTargetBitmap(new PixelSize(100, 100), new Vector(96, 96));
renderTargetBitmap.Render(textBlock);
PART_Image.Source = renderTargetBitmap;
}
3.4.1
RenderTargetBitmap 在控件导出的时候会有用,但是似乎这样的使用就是会有问题,可以看看github上讨论的这个issue,看看大家是怎么发挥 RenderTargetBitmap 的。
https://github.com/AvaloniaUI/Avalonia/discussions/13372
3.5 WriteableBitmap
我们使用的是 Avalonia 11.0.10 的版本,虽然这在目前足够新,但是 WriteableBitmap 的 API 是无法找到修改 Pixel 的内容,可以看一下这篇文章:
https://github.com/AvaloniaUI/Avalonia/discussions/7661 和这里的代码
在 Avalonia 现行的版本中,WriteableBitmap 的访问可以直接用这一坨代码来进行:
注意
- 以下代码仅为一个分支的代码 https://github.com/Gillibald/Avalonia/blob/fdffe1b1242798cbac96cfec41794b160c1805fc/src/Avalonia.Visuals/Platform/LockedFramebufferExtensions.cs#L30,在目前版本的库中找不到
!- 以下代码的诸多部分格式存在问题,为此会在第一块代码后面增加我个人的改造后的版本。
3.5.1 来自 Github 某个分支的代码
// Copyright (c) The Avalonia Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System;
using Avalonia.Media;
namespace Avalonia.Platform
{
public static class LockedFramebufferExtensions
{
public static Span<byte> GetPixels(this ILockedFramebuffer framebuffer)
{
unsafe
{
return new Span<byte>((byte*)framebuffer.Address, framebuffer.RowBytes * framebuffer.Size.Height);
}
}
public static Span<byte> GetPixel(this ILockedFramebuffer framebuffer, int x, int y)
{
unsafe
{
var bytesPerPixel = framebuffer.Format.GetBytesPerPixel();
var zero = (byte*)framebuffer.Address;
var offset = framebuffer.RowBytes * y + bytesPerPixel * x;
return new Span<byte>(zero + offset, bytesPerPixel);
}
}
public static void SetPixel(this ILockedFramebuffer framebuffer, int x, int y, Color color)
{
var pixel = framebuffer.GetPixel(x, y);
var alpha = color.A / 255.0;
switch (framebuffer.Format)
{
case PixelFormat.Rgb565:
var value = (((color.R & 0b11111000) << 8) + ((color.G & 0b11111100) << 3) + (color.B >> 3));
pixel[0] = (byte)value;
pixel[1] = (byte)(value >> 8);
break;
case PixelFormat.Rgba8888:
pixel[0] = (byte)(color.R * alpha);
pixel[1] = (byte)(color.G * alpha);
pixel[2] = (byte)(color.B * alpha);
pixel[3] = color.A;
break;
case PixelFormat.Bgra8888:
pixel[0] = (byte)(color.B * alpha);
pixel[1] = (byte)(color.G * alpha);
pixel[2] = (byte)(color.R * alpha);
pixel[3] = color.A;
break;
default:
throw new ArgumentOutOfRangeException();
}
}
}
}
3.5.2 个人改造后的代码
// Copyright (c) The Avalonia Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System;
using Avalonia.Media;
namespace Avalonia.Platform
{
public static class PixelFormatExtensions
{
public static int GetBytesPerPixel(this PixelFormat pixelFormat)
{
if (PixelFormat.Rgb565.Equals(pixelFormat)) return 2;
if (PixelFormat.Rgba8888.Equals(pixelFormat)) return 4;
if (PixelFormat.Bgra8888.Equals(pixelFormat)) return 4;
throw new ArgumentOutOfRangeException(nameof(pixelFormat), pixelFormat, null);
}
}
public static class LockedFramebufferExtensions
{
public static Span<byte> GetPixels(this ILockedFramebuffer framebuffer)
{
unsafe
{
return new Span<byte>((byte*)framebuffer.Address, framebuffer.RowBytes * framebuffer.Size.Height);
}
}
public static Span<byte> GetPixel(this ILockedFramebuffer framebuffer, int x, int y)
{
unsafe
{
var bytesPerPixel = framebuffer.Format.GetBytesPerPixel();
var zero = (byte*)framebuffer.Address;
var offset = framebuffer.RowBytes * y + bytesPerPixel * x;
return new Span<byte>(zero + offset, bytesPerPixel);
}
}
public static void SetPixel(this ILockedFramebuffer framebuffer, int x, int y, Color color)
{
var pixel = framebuffer.GetPixel(x, y);
var alpha = color.A / 255.0;
var frameBufferFormat = framebuffer.Format;
if (PixelFormat.Rgb565.Equals(frameBufferFormat))
{
var value = (((color.R & 0b11111000) << 8) + ((color.G & 0b11111100) << 3) + (color.B >> 3));
pixel[0] = (byte)value;
pixel[1] = (byte)(value >> 8);
}
else if (PixelFormat.Rgba8888.Equals(frameBufferFormat))
{
pixel[0] = (byte)(color.R * alpha);
pixel[1] = (byte)(color.G * alpha);
pixel[2] = (byte)(color.B * alpha);
pixel[3] = color.A;
}
else if (PixelFormat.Bgra8888.Equals(frameBufferFormat))
{
pixel[0] = (byte)(color.B * alpha);
pixel[1] = (byte)(color.G * alpha);
pixel[2] = (byte)(color.R * alpha);
pixel[3] = color.A;
}
else
{
throw new ArgumentOutOfRangeException();
}
}
}
}
图片结果,注意左上角:

代码如下:
protected override void OnLoaded(RoutedEventArgs e)
{
base.OnLoaded(e);
var uri = new Uri("avares://ImageControlTest/Assets/img.jpg");
var asset = AssetLoader.Open(uri);
var writeableBitmap = WriteableBitmap.Decode(asset);
using (var lockFrameBuffer = writeableBitmap.Lock())
{
for (int i = 0; i < 100; i++)
{
for (int j = 0; j < 100; j++)
{
lockFrameBuffer.SetPixel(i,j, Colors.Red);
}
}
}
PART_Image.Source = writeableBitmap;
}
4. ImageBrush 的简单调用
ImageBrush 的 Source 要求是 IImageBrushSource
IImageBrushSource 的实现者有谁呢?
全员 Bitmap
- Bitmap
- CroppedBitmap
- RenderTargetBitmap
- WriteableBitmap
创建一个 ImageBrush 的代码如下:

protected override void OnLoaded(RoutedEventArgs e)
{
base.OnLoaded(e);
var uri = new Uri("avares://ImageControlTest/Assets/img.jpg");
var asset = AssetLoader.Open(uri);
var bitmap = new Bitmap(asset);
var imageBrush = new ImageBrush(bitmap);
PART_Button.Background = imageBrush;
}

浙公网安备 33010602011771号