1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

JPEGファイルのロード・セーブ (libjpeg-turbo)

1
Last updated at Posted at 2026-08-08

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

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?