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