TurboJpegIO
turbojpeg.dll をC#から使うためのラッパークラスです
turbojpeg.dll の作成方法はソース中に記述しています
NuGet にも同様なラッパークラスがあるようですが
ダウンロードしても使い方がわからず
自分で作成することにしました ※丸1日かかりました
対応している PixelFormat は
PixelFormat.Format24bppRgb
PixelFormat.Format32bppRgb
のみです
TurboJpegIO.cs
using System.Drawing.Imaging;
using System.Runtime.InteropServices;
namespace System.Drawing
{
//忘備録
//Visual Studio で 新しいプロジェクトの作成 → クラスライブラリ を作成する時
//NuGet から System.Drawing.Common を検索してインストール
//依存関係にパッケージ → System.Drawing.Common が追加される
//で System.Drawing が参照できるようになる
public static class TurboJpegIO
{
//2026/08/08
//CMake のインストール
//公式サイト(https://cmake.org/) の DOWNLOAD (https://cmake.org/download/) から Windows x64 Installer : cmake-4.4.2-windows-x86_64.msi ダウンロードしてインストールする
//※環境変数 PATH に自動的に追加される
//NASM(Netwide Assembler)のインストール
//公式サイト(https://www.nasm.us/) の Download (https://www.nasm.us/pub/nasm/releasebuilds/3.02rc13/win64/) から nasm-3.02rc13-installer-x64.exe ダウンロードしてインストールする
//デフォルトで C:\Users\ユーザー名\AppData\Local\bin\NASM にインストールされるので 環境変数 PATH に追加する
//x64版 turbojpeg.dll の作成方法
//公式サイト(https://libjpeg-turbo.org/) の Downloads (https://github.com/libjpeg-turbo/libjpeg-turbo/releases) から
//Source code(zip) libjpeg-turbo-3.2.0.zip をダウンロードして展開する
//展開したフォルダ下に build フォルダを作成する
//コマンドプロンプト(ターミナル)を開き作成した build フォルダに移動する
//※インストールされている Visual Studio が 2022 の場合
//コマンドプロンプト(ターミナル)から
//cmake -G "Visual Studio 17 2022" -A x64 ..
//を実行すると build フォルダに libjpeg-turbo.sln が作成される
//libjpeg-turbo.sln を Visual Studio で開きソリューションをビルドする(Debug, Release)
//build\Debug\turbojpeg.dll が作成される
//build\Release\turbojpeg.dll が作成される
//turbojpeg.dll をアプリケーションの exe ファイルと同じフォルダにコピーする
[DllImport("turbojpeg.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern IntPtr tjInitDecompress();
[DllImport("turbojpeg.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern int tjDecompressHeader2(IntPtr handle, IntPtr jpegBuf, ulong jpegSize, out int width, out int height, out int tjsamp);
[DllImport("turbojpeg.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern int tjDecompress2(IntPtr handle, IntPtr jpegBuf, ulong jpegSize, IntPtr dstBuf, int width, int pitch, int height, int pixelFormat, int flags);
[DllImport("turbojpeg.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern int tjDestroy(IntPtr handle);
[DllImport("turbojpeg.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern IntPtr tjInitCompress();
[DllImport("turbojpeg.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern int tjCompress2(IntPtr handle, byte[] srcBuf, int width, int pitch, int height, int tjpf, ref IntPtr jpegBuf, ref ulong jpegSize, int tjsamp, int jpegQual, int flags);
[DllImport("turbojpeg.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern void tjFree(IntPtr buffer);
[DllImport("turbojpeg.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern IntPtr tjGetErrorStr();
const int TJPF_RGB = 0;
const int TJPF_BGR = 1;
const int TJPF_RGBX = 2;
const int TJPF_BGRX = 3;
const int TJPF_XBGR = 4;
const int TJPF_BGRA = 8;
public enum SubSampling : int
{
TJSAMP_444 = 0,
TJSAMP_422,
TJSAMP_420,
TJSAMP_GRAY,
TJSAMP_440,
TJSAMP_411,
};
const string format1_unsupported_pixelformat = "未対応のピクセルフォーマットです {0}";
const string format0_handle_error = "ハンドルの初期化に失敗しました";
const string format0_unknown_error = "未知のエラーが発生しました";
public static Bitmap? Load(out CustomError? ce, string path, PixelFormat pixel_format = PixelFormat.Format24bppRgb)
{
if (pixel_format != PixelFormat.Format24bppRgb && pixel_format != PixelFormat.Format32bppRgb)
{
ce = new CustomError(format1_unsupported_pixelformat, pixel_format.ToString());
return null;
}
IntPtr handle = 0;
IntPtr jpeg_data_ptr = 0;
IntPtr rgb_data_ptr = 0;
Bitmap? bitmap = null;
try
{
var jpeg_data = File.ReadAllBytes(path);
jpeg_data_ptr = Marshal.AllocHGlobal(jpeg_data.Length);
Marshal.Copy(jpeg_data, 0, jpeg_data_ptr, jpeg_data.Length);
handle = tjInitDecompress();
if (handle == 0)
{
ce = new CustomError(format0_handle_error);
return null;
}
int width, height, tjsamp;
int result = tjDecompressHeader2(handle, jpeg_data_ptr, (ulong)jpeg_data.Length, out width, out height, out tjsamp);
if (result != 0)
{
IntPtr ptr = tjGetErrorStr();
string? text = Marshal.PtrToStringAnsi(ptr);
if (string.IsNullOrEmpty(text))
{
text = format0_unknown_error;
}
ce = new CustomError(text);
return null;
}
int tjpf, pitch;
if (pixel_format == PixelFormat.Format24bppRgb)
{
tjpf = TJPF_BGR;
pitch = ((width * 3) + 3) & ~3;
}
else //if (pixel_format == PixelFormat.Format32bppRgb)
{
tjpf = TJPF_BGRX;
pitch = width * 4;
}
int rgb_data_size = height * pitch;
rgb_data_ptr = Marshal.AllocHGlobal(rgb_data_size);
result = tjDecompress2(handle, jpeg_data_ptr, (ulong)jpeg_data.Length, rgb_data_ptr, width, pitch, height, tjpf, 0);
if (result != 0)
{
IntPtr ptr = tjGetErrorStr();
string? text = Marshal.PtrToStringAnsi(ptr);
if (string.IsNullOrEmpty(text))
{
text = format0_unknown_error;
}
ce = new CustomError(text);
return null;
}
bitmap = new Bitmap(width, height, pixel_format);
var bitmap_data = bitmap.LockBits(new Rectangle(new Point(0, 0), bitmap.Size), ImageLockMode.WriteOnly, pixel_format);
unsafe
{
NativeMemory.Copy(rgb_data_ptr.ToPointer(), bitmap_data.Scan0.ToPointer(), (nuint)rgb_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 (rgb_data_ptr != 0)
{
Marshal.FreeHGlobal(rgb_data_ptr);
}
if (jpeg_data_ptr != 0)
{
Marshal.FreeHGlobal(jpeg_data_ptr);
}
if (handle != 0)
{
tjDestroy(handle);
}
}
}
public const int quality_default = 85;
public const SubSampling tjsamp_default = SubSampling.TJSAMP_420;
public static CustomError? Save(Bitmap bitmap, string path, int quality = quality_default, SubSampling tjsamp = tjsamp_default)
{
if (bitmap.PixelFormat != PixelFormat.Format24bppRgb && bitmap.PixelFormat != PixelFormat.Format32bppRgb)
{
return new CustomError(format1_unsupported_pixelformat, bitmap.PixelFormat.ToString());
}
IntPtr handle = 0;
IntPtr jpeg_data_ptr = 0;
try
{
int tjpf, pitch;
if (bitmap.PixelFormat == PixelFormat.Format24bppRgb)
{
tjpf = TJPF_BGR;
pitch = ((bitmap.Width * 3) + 3) & ~3;
}
else //if (bitmap.PixelFormat == PixelFormat.Format32bppRgb)
{
tjpf = TJPF_BGRX;
pitch = bitmap.Width * 4;
}
int rgb_data_size = bitmap.Height * pitch;
byte[] rgb_data = new byte[rgb_data_size];
var bitmap_data = bitmap.LockBits(new Rectangle(new Point(0, 0), bitmap.Size), ImageLockMode.ReadOnly, bitmap.PixelFormat);
Marshal.Copy(bitmap_data.Scan0, rgb_data, 0, rgb_data.Length);
bitmap.UnlockBits(bitmap_data);
handle = tjInitCompress();
if (handle == 0)
{
return new CustomError(format0_handle_error);
}
ulong jpeg_data_size = 0;
int result = tjCompress2(handle, rgb_data, bitmap.Width, pitch, bitmap.Height, tjpf, ref jpeg_data_ptr, ref jpeg_data_size, (int)tjsamp, quality, 0);
if (result != 0)
{
IntPtr ptr = tjGetErrorStr();
string? text = Marshal.PtrToStringAnsi(ptr);
if (string.IsNullOrEmpty(text))
{
text = format0_unknown_error;
}
return new CustomError(text);
}
byte[] jpeg_data = new byte[(int)jpeg_data_size];
Marshal.Copy(jpeg_data_ptr, jpeg_data, 0, jpeg_data.Length);
File.WriteAllBytes(path, jpeg_data);
return null;
}
catch (Exception ex)
{
return new CustomException(ex);
}
finally
{
if (jpeg_data_ptr != 0)
{
tjFree(jpeg_data_ptr);
}
if (handle != 0)
{
tjDestroy(handle);
}
}
}
public static Bitmap? ConvertPixelFormat(out CustomError? ce, Bitmap bitmap, int back_rgb = 0)
{
if (bitmap.PixelFormat == PixelFormat.Format24bppRgb || bitmap.PixelFormat == PixelFormat.Format32bppRgb)
{
ce = null;
return null;
}
Bitmap? _bitmap = null;
try
{
_bitmap = new Bitmap(bitmap.Width, bitmap.Height, PixelFormat.Format24bppRgb);
using (Graphics g = Graphics.FromImage(_bitmap))
{
if (bitmap.PixelFormat == PixelFormat.Format16bppArgb1555
|| bitmap.PixelFormat == PixelFormat.Format32bppArgb
|| bitmap.PixelFormat == PixelFormat.Format64bppArgb)
{
g.Clear(Color.FromArgb((int)(0xff000000 | back_rgb)));
}
g.DrawImage(bitmap, 0, 0, _bitmap.Width, _bitmap.Height);
}
ce = null;
return _bitmap;
}
catch (Exception ex)
{
ce = new CustomException(ex);
if (_bitmap != null)
{
_bitmap.Dispose();
}
return null;
}
}
}
}