WPFヘルパー:ImageBufferHelper.cs – 画像のピクセル構造を byte 配列で扱う ImageBuffer ヘルパー

コンピュータ

ImageBufferHelper.cs

using System.Windows.Media;
using System.Windows.Media.Imaging;

namespace Maywork.WPF.Helpers;

public static partial class ImageBufferHelper
{
    // 画像バッファ
    public readonly struct ImageBuffer
    {
        public int Width { get; }
        public int Height { get; }
        public int Stride { get; }
        public int Channels { get; }
        public byte[] Pixels { get; }

        public ImageBuffer(int width, int height, int stride, int channels, byte[] pixels)
        {
            Width = width;
            Height = height;
            Stride = stride;
            Channels = channels;
            Pixels = pixels;
        }
    }
    // 初期化
    public static ImageBuffer Create(int width, int height, int channels = 1, byte fill = 0)
    {
        if (width <= 0 || height <= 0)
            throw new ArgumentException("Invalid image size.");

        if (channels <= 0 || channels > 4)
            throw new ArgumentException("Invalid channels.");

        int stride = width * channels;

        byte[] pixels = new byte[stride * height];

        if (fill != 0)
            Array.Fill(pixels, fill);

        return new ImageBuffer(width, height, stride, channels, pixels);
    }  
    // BitmapSourceからImageBufferを生成
    public static ImageBuffer FromBitmapSource(BitmapSource bmp)
    {
        int width = bmp.PixelWidth;
        int height = bmp.PixelHeight;

        int channels = bmp.Format.BitsPerPixel switch
        {
            8  => 1,
            24 => 3,
            32 => 4,
            _  => throw new NotSupportedException(bmp.Format.ToString())
        };

        int stride = (width * bmp.Format.BitsPerPixel + 7) / 8;

        byte[] pixels = new byte[stride * height];
        bmp.CopyPixels(pixels, stride, 0);

        return new ImageBuffer(width, height, stride, channels, pixels);
    }
    // チャンネル分割
    public static ImageBuffer[] SplitChannels(ImageBuffer src)
    {
        if (src.Channels == 1)
            return [src];

        int width = src.Width;
        int height = src.Height;

        var result = new ImageBuffer[src.Channels];

        for (int c = 0; c < src.Channels; c++)
        {
            byte[] pixels = new byte[width * height];

            result[c] = new ImageBuffer(
                width,
                height,
                width,   // stride
                1,
                pixels);
        }

        var srcPixels = src.Pixels;
        int stride = src.Stride;

        for (int y = 0; y < height; y++)
        {
            int row = y * stride;

            for (int x = 0; x < width; x++)
            {
                int i = row + x * src.Channels;

                for (int c = 0; c < src.Channels; c++)
                {
                    result[c].Pixels[y * width + x] = srcPixels[i + c];
                }
            }
        }

        return result;
    }
    // チャンネルマージ
    public static ImageBuffer ChannelMerge(params ImageBuffer[] channels)
    {
        if (channels == null || channels.Length == 0)
            throw new ArgumentException("channels is empty.");

        int width = channels[0].Width;
        int height = channels[0].Height;

        int ch = channels.Length;

        // チェック
        foreach (var c in channels)
        {
            if (c.Width != width || c.Height != height)
                throw new ArgumentException("Image size mismatch.");

            if (c.Channels != 1)
                throw new ArgumentException("All inputs must be 1 channel images.");
        }

        int stride = width * ch;
        byte[] pixels = new byte[stride * height];

        for (int y = 0; y < height; y++)
        {
            int dstRow = y * stride;

            for (int x = 0; x < width; x++)
            {
                int dstIndex = dstRow + x * ch;

                for (int c = 0; c < ch; c++)
                {
                    pixels[dstIndex + c] = channels[c].Pixels[y * width + x];
                }
            }
        }

        return new ImageBuffer(width, height, stride, ch, pixels);
    }

    // ImageBufferからBitmapSourceを生成
    public static BitmapSource ToBitmapSource(ImageBuffer img)
    {
        PixelFormat format = img.Channels switch
        {
            1 => PixelFormats.Gray8,
            3 => PixelFormats.Bgr24,
            4 => PixelFormats.Bgra32,
            _ => throw new NotSupportedException($"Channels: {img.Channels}")
        };

        var bmp = BitmapSource.Create(
            img.Width,
            img.Height,
            96,                 // dpiX
            96,                 // dpiY
            format,
            null,
            img.Pixels,
            img.Stride);

        bmp.Freeze(); // WPFで扱いやすくする

        return bmp;
    }
    // ImageBufferのクローン
    public static ImageBuffer Clone(ImageBuffer src)
    {
        byte[] pixels = new byte[src.Pixels.Length];

        Buffer.BlockCopy(src.Pixels, 0, pixels, 0, pixels.Length);

        return new ImageBuffer(
            src.Width,
            src.Height,
            src.Stride,
            src.Channels,
            pixels);
    }
    // クリア
    public static void Clear(ImageBuffer img)=> Array.Clear(img.Pixels);

    // Fill
    public static void Fill(ImageBuffer img, byte value) => Array.Fill(img.Pixels, value);
}

/*
// 使用例
string path = @"C:\Users\karet\Pictures\e850658b-0768-4f8d-a136-65f7edaa134e.png";

BitmapSource bmp = ImageHelper.Load(path);
ImageBufferHelper.ImageBuffer imgBuff = ImageBufferHelper.FromBitmapSource(bmp);

Debug.Print($"{path} W:{imgBuff.Width} H:{imgBuff.Height} Channel:{imgBuff.Channels} Stride{imgBuff.Stride}");

ImageBufferHelper.ImageBuffer[] sc = ImageBufferHelper.SplitChannels(imgBuff);

Debug.Print($"W:{sc[1].Width} H:{sc[1].Height} Channel:{sc[1].Channels} Stride{sc[1].Stride}");

// 緑色チャンネルをグレースケールとして表示
Image1.Source = ImageBufferHelper.ToBitmapSource(sc[1]);

// 100x100の画像を表示
Image1.Source = ImageBufferHelper.ToBitmapSource(ImageBufferHelper.Create(100, 100));
*/
Download

コメント