0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

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

Last updated at Posted at 2025-05-17

WebpIO

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

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

CopyMemory に関しては下のサイトを参考にしました
https://gazoyaro.com/cs_copy_memory_dotnetframework_dotnetcore/

WebpIO.cs
#define USE_KERNEL32_COPYMEMORY
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 int WebPGetInfo(IntPtr data, UInt32 data_size, out int width, out int height);

        [DllImport("libwebp.dll", CallingConvention = CallingConvention.Cdecl)]
        public static extern IntPtr WebPDecodeBGR(IntPtr data, UInt32 data_size, ref int width, ref int height);

        [DllImport("libwebp.dll", CallingConvention = CallingConvention.Cdecl)]
        public static extern IntPtr WebPDecodeBGRA(IntPtr data, UInt32 data_size, ref int width, ref int height);

        [DllImport("libwebp.dll", CallingConvention = CallingConvention.Cdecl)]
        public static extern IntPtr WebPDecodeBGRInto(IntPtr data, UInt32 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, UInt32 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, UInt32 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

#if USE_KERNEL32_COPYMEMORY
        // .NET Framework で成功、.NET Core で失敗
        //[DllImport("kernel32.dll")]
        //public static unsafe extern void CopyMemory(IntPtr dst, IntPtr src, int bytes);

        // .NET Framework で成功、.NET Core で失敗
        //[DllImport("kernel32.dll", EntryPoint = "CopyMemory")]
        //public static unsafe extern void CopyMemory(IntPtr dst, IntPtr src, int bytes);

        // .NET Framework で失敗、.NET Core で成功.
        [DllImport("kernel32.dll", EntryPoint = "RtlCopyMemory")]
        public static unsafe extern void CopyMemory(IntPtr dst, IntPtr src, int bytes);

        [DllImport("kernel32.dll", EntryPoint = "RtlCopyMemory")]
        public static unsafe extern void CopyMemory(IntPtr dst, byte* src, int bytes);

        [DllImport("kernel32.dll", EntryPoint = "RtlCopyMemory")]
        public static unsafe extern void CopyMemory(byte* dst, IntPtr src, int bytes);

        [DllImport("kernel32.dll", EntryPoint = "RtlCopyMemory")]
        public static unsafe extern void CopyMemory(byte* dst, byte* src, int bytes);
#endif

        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);

#if USE_KERNEL32_COPYMEMORY
                unsafe
                {
                    fixed (byte* src = webp_data)
                    {
                        CopyMemory(webp_data_ptr, src, webp_data.Length);
                    }
                }
#else
                Marshal.Copy(webp_data, 0, webp_data_ptr, webp_data.Length);
#endif
                UInt32 webp_data_size = (UInt32)webp_data.Length;

                features_data_ptr = Marshal.AllocHGlobal(sizeof_WebPBitstreamFeatures);

                var vp8statuscode = (VP8StatusCode)WebPGetFeaturesInternal(webp_data_ptr, webp_data_size, features_data_ptr, WEBP_DECODER_ABI_VERSION);

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

                    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, webp_data_size, bgra_data_ptr, bgra_data_size, bitmap_data.Stride);
                }
                else
                {
                    WebPDecodeBGRInto(webp_data_ptr, webp_data_size, bgra_data_ptr, bgra_data_size, bitmap_data.Stride);
                }

#if USE_KERNEL32_COPYMEMORY
                CopyMemory(bitmap_data.Scan0, bgra_data_ptr, bgra_data_size);
#else
                var copy_buffer = new byte[bgra_data_size];
                Marshal.Copy(bgra_data_ptr, copy_buffer, 0, bgra_data_size);
                Marshal.Copy(copy_buffer, 0, bitmap_data.Scan0, bgra_data_size);
#endif
                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];

#if USE_KERNEL32_COPYMEMORY
                unsafe
                {
                    fixed (byte* dst = webp_data)
                    {
                        CopyMemory(dst, webp_data_ptr, webp_data_size);
                    }
                }
#else
                Marshal.Copy(webp_data_ptr, webp_data, 0, webp_data_size);
#endif
                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];

#if USE_KERNEL32_COPYMEMORY
                unsafe
                {
                    fixed (byte* dst = webp_data)
                    {
                        CopyMemory(dst, webp_data_ptr, webp_data_size);
                    }
                }
#else
                Marshal.Copy(webp_data_ptr, webp_data, 0, webp_data_size);
#endif
                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);
            }
        }
    }
}
0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?