WebPファイルのロード・セーブ

# WebpIO
libwebp.dll をC#から使うためのラッパークラスです
libwebp.dll の作成方法はソース中に記述しています

対応している PixelFormat は
PixelFormat.Format24bppRgb
PixelFormat.Format32bppArgb
のみです

※WebPアニメーションに対応していません

```c#:WebpIO.cs
using System.Diagnostics;
using System.Drawing.Imaging;
using System.Runtime.InteropServices;

namespace System.Drawing
{
    public static class WebpIO
    {
        //2025/04/13

        //libwebp.dll の作成方法 ※libwebp-1.5.0の場合
        //https://storage.googleapis.com/downloads.webmproject.org/releases/webp/index.html
        //から libwebp-1.5.0.tar.gz をダウンロードして展開する

        //Developer Command Prompt for VS 2022 を開く ※スタートメニューの Vusual Studio 2022 フォルダ内にある

        //libwebp-1.5.0.tar.gz を展開したフォルダをカレントディレクトリにする ※そこに Makefile.vc が存在するため
        //cd D:\source\repos\libwebp-1.5.0 ※自分の環境

        //先にx86用のdllを作成する
        //1.nmakeの実行
        //nmake /f Makefile.vc CFG= release - dynamic RTLIBCFG=static OBJDIR=output
        //で "D:\source\repos\libwebp-1.5.0\output\release-dynamic\x86\bin" に 4 個の dll が出力される
        //libwebp.dll
        //libsharpyuv.dll
        //libwebpdecoder.dll
        //libwebpdemux.dll

        //次にx64用のdllを作成する
        //1.バッチファイルの実行
        //"C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Auxiliary\Build\vcvarsall.bat" amd64
        //2.nmakeの実行
        //nmake /f Makefile.vc CFG= release - dynamic RTLIBCFG=static OBJDIR=output
        //で "D:\source\repos\libwebp-1.5.0\output\release-dynamic\x64\bin" に 4 個の dll が出力される (x86版と同じ名前)

        //4 個の dll をアプリケーションの exe ファイルと同じディレクトリーにコピーする

        [DllImport("libwebp.dll", CallingConvention = CallingConvention.Cdecl)]
        public static extern IntPtr WebPDecodeBGRInto(IntPtr data, nuint data_size, IntPtr output_buffer, int output_buffer_size, int output_stride);

        [DllImport("libwebp.dll", CallingConvention = CallingConvention.Cdecl)]
        public static extern IntPtr WebPDecodeBGRAInto(IntPtr data, nuint data_size, IntPtr output_buffer, int output_buffer_size, int output_stride);

        [DllImport("libwebp.dll", CallingConvention = CallingConvention.Cdecl)]
        public static extern int WebPEncodeBGR(IntPtr rgb, int width, int height, int stride, float quality_factor, out IntPtr output);

        [DllImport("libwebp.dll", CallingConvention = CallingConvention.Cdecl)]
        public static extern int WebPEncodeBGRA(IntPtr rgba, int width, int height, int stride, float quality_factor, out IntPtr output);

        [DllImport("libwebp.dll", CallingConvention = CallingConvention.Cdecl)]
        public static extern int WebPEncodeLosslessBGR(IntPtr rgb, int width, int height, int stride, out IntPtr output);

        [DllImport("libwebp.dll", CallingConvention = CallingConvention.Cdecl)]
        public static extern int WebPEncodeLosslessBGRA(IntPtr rgba, int width, int height, int stride, out IntPtr output);

        [DllImport("libwebp.dll", CallingConvention = CallingConvention.Cdecl)]
        public static extern int WebPFree(IntPtr p);

        [DllImport("libwebp.dll", CallingConvention = CallingConvention.Cdecl)]
        public static extern int WebPGetFeaturesInternal(IntPtr data, nuint data_size, IntPtr features, int version);

        //"D:\source\repos\libwebp-1.5.0\src\webp\decode.h"

        const int WEBP_DECODER_ABI_VERSION = 0x209;

        [StructLayout(LayoutKind.Sequential, Pack = 4)]
        struct WebPBitstreamFeatures
        {
            public int width;           // Width in pixels, as read from the bitstream.
            public int height;          // Height in pixels, as read from the bitstream.
            public int has_alpha;       // True if the bitstream contains an alpha channel.
            public int has_animation;   // True if the bitstream is an animation.
            public int format;          // 0 = undefined (/mixed), 1 = lossy, 2 = lossless

            UInt32 pad1;                // padding for later use
            UInt32 pad2;                // padding for later use
            UInt32 pad3;                // padding for later use
            UInt32 pad4;                // padding for later use
            UInt32 pad5;                // padding for later use
        };

        enum VP8StatusCode : int
        {
            VP8_STATUS_OK = 0,
            VP8_STATUS_OUT_OF_MEMORY,
            VP8_STATUS_INVALID_PARAM,
            VP8_STATUS_BITSTREAM_ERROR,
            VP8_STATUS_UNSUPPORTED_FEATURE,
            VP8_STATUS_SUSPENDED,
            VP8_STATUS_USER_ABORT,
            VP8_STATUS_NOT_ENOUGH_DATA
        }

        static readonly int sizeof_WebPBitstreamFeatures = Marshal.SizeOf(typeof(WebPBitstreamFeatures));   //40

        const string format1_unsupported_pixel_format = "未対応の PixelFormat です ({0})";

        public static Bitmap? Decode(byte[] webp_data, out CustomError? ce)
        {
            IntPtr webp_data_ptr = 0;
            IntPtr features_data_ptr = 0;
            IntPtr bgra_data_ptr = 0;

            Bitmap? bitmap = null;

            try
            {
                webp_data_ptr = Marshal.AllocHGlobal(webp_data.Length);

                Marshal.Copy(webp_data, 0, webp_data_ptr, webp_data.Length);

                features_data_ptr = Marshal.AllocHGlobal(sizeof_WebPBitstreamFeatures);

                var vp8statuscode = (VP8StatusCode)WebPGetFeaturesInternal(webp_data_ptr, (nuint)webp_data.Length, features_data_ptr, WEBP_DECODER_ABI_VERSION);

                if (vp8statuscode != VP8StatusCode.VP8_STATUS_OK)
                {
                    ce = new CustomError("WebPGetFeaturesInternal でエラーが発生しました ({0})", vp8statuscode);

                    return null;
                }

                int has_animation = Marshal.ReadInt32(features_data_ptr, 12);

                if (has_animation != 0)
                {
                    ce = new CustomError("WebPアニメーションに対応していません");

                    return null;
                }

                int width = Marshal.ReadInt32(features_data_ptr, 0);
                int height = Marshal.ReadInt32(features_data_ptr, 4);
                int has_alpha = Marshal.ReadInt32(features_data_ptr, 8);

                PixelFormat pixel_format = (has_alpha != 0) ? PixelFormat.Format32bppArgb : PixelFormat.Format24bppRgb;

                bitmap = new Bitmap(width, height, pixel_format);

                var bitmap_data = bitmap.LockBits(new Rectangle(Point.Empty, bitmap.Size), ImageLockMode.WriteOnly, pixel_format);

                int bgra_data_size = bitmap_data.Stride * height;

                bgra_data_ptr = Marshal.AllocHGlobal(bgra_data_size);

                if (has_alpha != 0)
                {
                    WebPDecodeBGRAInto(webp_data_ptr, (nuint)webp_data.Length, bgra_data_ptr, bgra_data_size, bitmap_data.Stride);
                }
                else
                {
                    WebPDecodeBGRInto(webp_data_ptr, (nuint)webp_data.Length, bgra_data_ptr, bgra_data_size, bitmap_data.Stride);
                }

                unsafe
                {
                    NativeMemory.Copy(bgra_data_ptr.ToPointer(), bitmap_data.Scan0.ToPointer(), (nuint)bgra_data_size);
                }

                bitmap.UnlockBits(bitmap_data);

                ce = null;

                return bitmap;
            }
            catch (Exception ex)
            {
                ce = new CustomException(ex);

                if (bitmap != null)
                {
                    bitmap.Dispose();
                }

                return null;
            }
            finally
            {
                if (webp_data_ptr != 0)
                {
                    Marshal.FreeHGlobal(webp_data_ptr);
                }

                if (features_data_ptr != 0)
                {
                    Marshal.FreeHGlobal(features_data_ptr);
                }

                if (bgra_data_ptr != 0)
                {
                    Marshal.FreeHGlobal(bgra_data_ptr);
                }
            }
        }

        public static Bitmap? Load(string path, out CustomError? ce)
        {
            try
            {
                var webp_data = File.ReadAllBytes(path);

                return Decode(webp_data, out ce);
            }
            catch (Exception ex)
            {
                ce = new CustomException(ex);

                return null;
            }
        }

        public const int quality_default = 75;

        public static byte[]? EncodeLossly(Bitmap bitmap, out CustomError? ce, int quality = quality_default)
        {
            Debug.Assert(bitmap.PixelFormat == PixelFormat.Format24bppRgb || bitmap.PixelFormat == PixelFormat.Format32bppArgb, string.Format(format1_unsupported_pixel_format, bitmap.PixelFormat));

            IntPtr webp_data_ptr = 0;

            try
            {
                var bitmap_data = bitmap.LockBits(new Rectangle(Point.Empty, bitmap.Size), ImageLockMode.ReadOnly, bitmap.PixelFormat);

                int webp_data_size;

                if (bitmap.PixelFormat == PixelFormat.Format32bppArgb)
                {
                    webp_data_size = WebPEncodeBGRA(bitmap_data.Scan0, bitmap.Width, bitmap.Height, bitmap_data.Stride, quality, out webp_data_ptr);
                }
                else //if (pixel_format == PixelFormat.Format24bppRgb)
                {
                    webp_data_size = WebPEncodeBGR(bitmap_data.Scan0, bitmap.Width, bitmap.Height, bitmap_data.Stride, quality, out webp_data_ptr);
                }

                bitmap.UnlockBits(bitmap_data);

                var webp_data = new byte[webp_data_size];

                Marshal.Copy(webp_data_ptr, webp_data, 0, webp_data_size);

                ce = null;

                return webp_data;
            }
            catch (Exception ex)
            {
                ce = new CustomException(ex);

                return null;
            }
            finally
            {
                if (webp_data_ptr != 0)
                {
                    WebPFree(webp_data_ptr);
                }
            }
        }

        public static byte[]? EncodeLossless(Bitmap bitmap, out CustomError? ce)
        {
            Debug.Assert(bitmap.PixelFormat == PixelFormat.Format24bppRgb || bitmap.PixelFormat == PixelFormat.Format32bppArgb, string.Format(format1_unsupported_pixel_format, bitmap.PixelFormat));

            IntPtr webp_data_ptr = 0;

            try
            {
                var bitmap_data = bitmap.LockBits(new Rectangle(Point.Empty, bitmap.Size), ImageLockMode.ReadOnly, bitmap.PixelFormat);

                int webp_data_size;

                if (bitmap.PixelFormat == PixelFormat.Format32bppArgb)
                {
                    webp_data_size = WebPEncodeLosslessBGRA(bitmap_data.Scan0, bitmap.Width, bitmap.Height, bitmap_data.Stride, out webp_data_ptr);
                }
                else //if (pixel_format == PixelFormat.Format24bppRgb)
                {
                    webp_data_size = WebPEncodeLosslessBGR(bitmap_data.Scan0, bitmap.Width, bitmap.Height, bitmap_data.Stride, out webp_data_ptr);
                }

                bitmap.UnlockBits(bitmap_data);

                var webp_data = new byte[webp_data_size];

                Marshal.Copy(webp_data_ptr, webp_data, 0, webp_data_size);

                ce = null;

                return webp_data;
            }
            catch (Exception ex)
            {
                ce = new CustomException(ex);

                return null;
            }
            finally
            {
                if (webp_data_ptr != 0)
                {
                    WebPFree(webp_data_ptr);
                }
            }
        }

        public static CustomError? Save(string path, Bitmap bitmap, bool lossless = false, int quality = quality_default)
        {
            Debug.Assert(bitmap.PixelFormat == PixelFormat.Format24bppRgb || bitmap.PixelFormat == PixelFormat.Format32bppArgb, string.Format(format1_unsupported_pixel_format, bitmap.PixelFormat));

            byte[]? webp_data;

            CustomError? ce;

            if (lossless)
            {
                webp_data = EncodeLossless(bitmap, out ce);
            }
            else
            {
                webp_data = EncodeLossly(bitmap, out ce, quality);
            }

            if (webp_data == null)
            {
                return ce;
            }

            try
            {
                File.WriteAllBytes(path, webp_data);

                return null;
            }
            catch (Exception ex)
            {
                return new CustomException(ex);
            }
        }
    }
}
```