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
のみです

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

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);
            }
        }
    }
}
0
0
2

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?