关于 Avalonia 中的图片

016 关于 Avalonia 中的图片

0. 引用

  1. https://docs.avaloniaui.net/zh-Hans/docs/basics/user-interface/assets#资产类型转换

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 的继承者们

  1. DrawingImage
  2. Bitmap
  3. CroppedBitmap
  4. RenderTargetBitmap
  5. 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 和这里的代码

https://github.com/Gillibald/Avalonia/blob/fdffe1b1242798cbac96cfec41794b160c1805fc/src/Avalonia.Visuals/Platform/LockedFramebufferExtensions.cs#L30

在 Avalonia 现行的版本中,WriteableBitmap 的访问可以直接用这一坨代码来进行:

注意

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

  1. Bitmap
  2. CroppedBitmap
  3. RenderTargetBitmap
  4. 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;
}
posted @ 2024-07-10 12:27  fanbal  阅读(1451)  评论(0)    收藏  举报