Visual Studio 2019のC#にて、画像をそのままPDFに変換する。コードのサンプルです。

## 概要

表題の通り
Visual Studio 2019のC#にて、画像をそのままPDFに変換する。
コードのサンプルでございます。

特段、NuGetなどで他のプラグインや
別途外部dllを必要としないようにしています。

要は、バイナリを以下のように、直接叩いています。
<b>1.</b>PDFのヘッダーの記述
<b>2.</b>ファイルパスからBitmapで読み込む。
<b>3.</b>Bitmapをメモリ上にJpeg形式で保存。
<b>4.</b>PDFファイルに画像情報と共に、画像バイナリも埋める。
<b>5.</b>PDFのフッターの記述。

※私は素人ですし
今回のコードは、あくまで最低限の動作する部分にとどめていますので、汚いです。
例外処理や、解放など至らぬ点が、多々ございます。ご了承ください。
もし実際に参考にされる際は、必要個所を必ず訂正してからにしてください。
また、元がJpegファイルなら、2~3項も省略できます。

[※Python版はこちらです。](https://qiita.com/oyk3865b/items/78799e7b1860638be341)


以前、
Yahoo知恵ノートに似たものを公開していたのですが、
Yahoo知恵ノートが廃止されてしまったため、
こちらに改めて手を加えて、公開させていただこうと思います。

下の画像は、今回コードでの作成例です。
![参考画像](https://blog-imgs-130.fc2.com/o/y/k/oyk3865b/20190715183338fc8.jpg)


## 参考ページ

・[特に、dllを使わず、、バイナリを直接叩いて、PDFを作れないか、やってみた。結果報告・１
http://oyk3865b.blog13.fc2.com/blog-entry-1127.html](http://oyk3865b.blog13.fc2.com/blog-entry-1127.html)

・[自力で、JPEG画像から、PDFに変換してみた - ニコニコ動画
https://www.nicovideo.jp/watch/sm19664272](https://www.nicovideo.jp/watch/sm19664272)
※リンク先は、私のブログや動画です。気を付けてください。


## 本題

```c#:clsPDF.cs
using System;
using System.Collections;
using System.IO;
using System.Drawing;
using System.Collections.Generic;

public class clsPDF
    {   //画像をPDFにするところ  

        //出力pdfのバイナリ格納用。  
        public List<byte> ary_pdf_file = new List<byte>();

        //出力pdfのバイナリ各オブジェクトの、バイト開始位置を格納。  
        public List<Int64> ary_pdf_byte_head = new List<Int64>();

        //改行用文字列  
        public const string vbCrLf = "\r\n"; //Environment.NewLine  

        //子オブジェクトの位置の指定用  
        public const string pdf_indicate_obj = "NO 0 R ";
        //子オブジェクト名＆番号を格納  
        public const string pdf_obj_Name = "CC 0 obj";

        //△冒頭の構文( header )------------------------------------------------------------------------------------------------------------  
        readonly byte[] pdf_header_start = System.Text.Encoding.ASCII.GetBytes(
            "%PDF-1.5" + vbCrLf + "%TageSP" + vbCrLf);

        //△1番目のオブジェクト( 1 0 obj )------------------------------------------------------  
        readonly byte[] pdf_1st_obj = System.Text.Encoding.ASCII.GetBytes(
            "1 0 obj" + vbCrLf +
            "<<" + vbCrLf + "/Type /Catalog" + vbCrLf +
            "/Pages 2 0 R" + vbCrLf +
            ">>" + vbCrLf + "endobj" + vbCrLf);

        //△2番目のオブジェクト( 2 0 obj )------------------------------------------------------  
        readonly byte[] pdf_2nd_obj_Kids = System.Text.Encoding.ASCII.GetBytes(
            "2 0 obj" + vbCrLf + "<<" +
            "/Type /Pages" + vbCrLf + "/Kids [ ");

        //ページ数指定用  
        readonly byte[] pdf_2nd_obj_Count = System.Text.Encoding.ASCII.GetBytes(
            "]" + vbCrLf + "/Count ");

        //ヘッダーの終端  
        readonly byte[] pdf_2nd_obj_End = System.Text.Encoding.ASCII.GetBytes(
            vbCrLf + ">>" + vbCrLf + "endobj" + vbCrLf);

        //■子オブジェクト関係( Kids )**************************************************  

        //△3番目のオブジェクト( 3 0 obj )------------------------------------------------------  
        //キャンバスのサイズ設定  
        readonly byte[] pdf_3rd_obj_Start = System.Text.Encoding.ASCII.GetBytes(
            vbCrLf + "<<" + vbCrLf + "/Type /Page" + vbCrLf +
            "/Parent 2 0 R" + vbCrLf + "/MediaBox  [ 0 0 ");

        const string pdf_3rd_obj_MediaBoxSize = "WW HH ]";

        //画像の配置指定の設定  
        readonly byte[] pdf_3rd_obj_Resources = System.Text.Encoding.ASCII.GetBytes(
            vbCrLf + "/Resources << /ProcSet [ /PDF /ImageB ]" + vbCrLf);

        const string pdf_3rd_obj_XObject = "/XObject << /Im0 XX 0 R >>" + vbCrLf + ">>";

        readonly byte[] pdf_3rd_obj_Contents =
            System.Text.Encoding.ASCII.GetBytes(
            vbCrLf + "/Contents ");

        readonly byte[] pdf_3rd_obj_End =
            System.Text.Encoding.ASCII.GetBytes(
            vbCrLf + ">>" + vbCrLf +
            "endobj" + vbCrLf);

        //△4番目のオブジェクト( 4 0 obj )------------------------------------------------------  
        //画像配置の前のバイナリ&画像本体のサイズ指定  
        readonly byte[] pdf_4th_obj_Start =
            System.Text.Encoding.ASCII.GetBytes(
            vbCrLf + "<<" + vbCrLf + "/Type /XObject" + vbCrLf +
            "/Subtype /Image" + vbCrLf + "/");

        const string pdf_4th_obj_ImageSize =
            "Width AA /Height BB";

        readonly byte[] pdf_4th_obj_Filter =
            System.Text.Encoding.ASCII.GetBytes(
            vbCrLf + "/BitsPerComponent 8" + vbCrLf +
            "/ColorSpace /DeviceRGB" + vbCrLf +
            "/Filter /DCTDecode" + vbCrLf);

        const string pdf_4th_obj_Length =
            "/Length LLLLL" + vbCrLf + ">>" + vbCrLf +
            "stream" + vbCrLf;

        //画像配置の後のバイナリ&画像の表示サイズの指定  
        readonly byte[] pdf_4th_obj_End =
            System.Text.Encoding.ASCII.GetBytes(
            vbCrLf + "endstream" + vbCrLf + "endobj" + vbCrLf);


        //△5番目のオブジェクト( 5 0 obj )------------------------------------------------------  
        const string pdf_5th_obj_Length =
            vbCrLf + "<< /Length LLLLL >>" +
            vbCrLf + "stream" + vbCrLf;

        const string pdf_5th_obj_ShowSize =
            "q WW 0 0 HH 0 0 cm /Im0 Do Q";

        readonly byte[] pdf_5th_obj_End =
            System.Text.Encoding.ASCII.GetBytes(
            vbCrLf + "endstream" +
            vbCrLf + "endobj" + vbCrLf);


        //■フッター( footer )------------------------------------------------------  

        //△xref関係( xref )------------------------------------------------------  
        readonly byte[] pdf_xref_Start =
            System.Text.Encoding.ASCII.GetBytes("xref" + vbCrLf);

        const string pdf_xref_objCount = "0 MM" + vbCrLf;

        readonly byte[] pdf_xref_ZERO =
            System.Text.Encoding.ASCII.GetBytes(
            "0000000000 65535 f" + vbCrLf);

        const string pdf_xref_objStartPos =
            "QQQQQQQQQQ 00000 n" + vbCrLf;


        //△trailer関係( trailer )------------------------------------------------------  
        const string pdf_trailer =
            "trailer" + vbCrLf + "<<" + vbCrLf +
            "/Size MM" + vbCrLf +
            "/Root 1 0 R" + vbCrLf +
            ">>" + vbCrLf;

        //△EOF関係( EOF )------------------------------------------------------  
        const string pdf_startxref_EOF =
            "startxref" + vbCrLf + "TTT" + vbCrLf +
            "%%EOF" + vbCrLf;



        public void bitmap_to_pdf(string[] input_paths, string Output_Path)
        {
            //■ファイルを作成して書き込む   
            //ファイルが存在しているときは、いったん消す  
            if (System.IO.File.Exists(Output_Path))
            {
                System.IO.File.Delete(Output_Path);
            }

            //出力pdfバイナリの初期化   
            ary_pdf_file = new List<byte>();
            ary_pdf_byte_head = new List<Int64>();

            //■ファイルを作成して書き込む   
            System.IO.FileStream fs = new System.IO.FileStream(Output_Path,
                System.IO.FileMode.Create,
                System.IO.FileAccess.Write);


            //■ヘッダーの作成------------ここから-----------------  

            //冒頭(Add header binary.)  
            ary_pdf_file.AddRange(pdf_header_start);
            //Add 1st object start bytes position.  
            ary_pdf_byte_head.Add(Convert.ToInt64(ary_pdf_file.Count)); //1オブジェクトの開始番号を格納  

            //オブジェクト1(Add 1st object binary.)  
            ary_pdf_file.AddRange(pdf_1st_obj);
            //Add 2nd object start bytes position.  
            ary_pdf_byte_head.Add(Convert.ToInt64(ary_pdf_file.Count)); //2オブジェクトの開始番号を格納  

            //オブジェクト2(Add 2nd object binary.)  
            ary_pdf_file.AddRange(pdf_2nd_obj_Kids);

            byte[] pdf_write_binary;

            //ページ数だけ、子オブジェクトを指定・追加する。  
            for (int i = 3; i <= (input_paths.Length * 3); i += 3)
            {
                //各・子オブジェクトの開始番号を、格納  
                //Set start position of kids object No.  
                string pdf_2nd_obj_Kids_Set = pdf_indicate_obj.Replace("NO", i.ToString());

                //文字列を、バイト配列に変換して、格納する。  
                pdf_write_binary = System.Text.Encoding.ASCII.GetBytes(pdf_2nd_obj_Kids_Set);
                ary_pdf_file.AddRange(pdf_write_binary);

                //配列を初期化  
                pdf_write_binary = new byte[] { };
            }

            //ページ数を格納する。  
            ary_pdf_file.AddRange(pdf_2nd_obj_Count);
            pdf_write_binary = System.Text.Encoding.ASCII.GetBytes(input_paths.Length.ToString());
            ary_pdf_file.AddRange(pdf_write_binary);

            //ヘッダーを閉める。  
            ary_pdf_file.AddRange(pdf_2nd_obj_End);

            //配列を初期化  
            pdf_write_binary = new byte[] { };


            try
            {   //一旦、バイト型配列の内容をすべて上書き   
                fs.Write((byte[])ary_pdf_file.ToArray(), 0, ary_pdf_file.Count);

            }
            catch
            {

            };


            //配列を、一旦、クリアする。  
            ary_pdf_file.Clear();
            //■ヘッダーの作成------------ここまで-----------------  



            //◆各ページ用の、子オブジェクトの作成  
            //ページ数だけ、子オブジェクトを指定・追加する。  
            for (int i = 0; i < input_paths.Length; i += 1)
            {

                //▼まずは、今回の、子オブジェクトの番号を指定  
                Int32 obj_No = (i + 1) * 3;
                string pdf_obj_String = pdf_obj_Name.Replace("CC", obj_No.ToString());

                //3オブジェクトの開始バイト位置を格納  
                ary_pdf_byte_head.Add(Convert.ToInt64(fs.Length));

                //文字列を、バイト配列に変換して、バイナリに追加  
                ary_pdf_file.AddRange(System.Text.Encoding.ASCII.GetBytes(pdf_obj_String));



                //▼キャンバス・サイズを指定  
                ary_pdf_file.AddRange(pdf_3rd_obj_Start);


                //★画像を読み込む
                //※※※今回は、読み取りエラーは想定していないです。※※※
                using (Bitmap bitmap = new Bitmap(input_paths[i])) { 

                    //各設定に、改める。  
                    pdf_obj_String = pdf_3rd_obj_MediaBoxSize;
                    pdf_obj_String = pdf_obj_String.Replace("WW", Convert.ToSingle(bitmap.Width * 0.75).ToString());
                    pdf_obj_String = pdf_obj_String.Replace("HH", Convert.ToSingle(bitmap.Height * 0.75).ToString());

                    //文字列を、バイト配列に変換して、バイナリに追加  
                    ary_pdf_file.AddRange(System.Text.Encoding.ASCII.GetBytes(pdf_obj_String));


                    //▼画像の配置指定の設定  
                    ary_pdf_file.AddRange(pdf_3rd_obj_Resources);
                    pdf_obj_String = pdf_3rd_obj_XObject;
                    pdf_obj_String = pdf_obj_String.Replace("XX", (obj_No + 1).ToString());

                    //文字列を、バイト配列に変換して、バイナリに追加  
                    ary_pdf_file.AddRange(System.Text.Encoding.ASCII.GetBytes(pdf_obj_String));


                    //▼画像の配置指定の設定2  
                    ary_pdf_file.AddRange(pdf_3rd_obj_Contents);
                    pdf_obj_String = pdf_indicate_obj.TrimEnd(); //右端のスペースは、消す  
                    pdf_obj_String = pdf_obj_String.Replace("NO", (obj_No + 2).ToString());

                    //文字列を、バイト配列に変換して、バイナリに追加  
                    ary_pdf_file.AddRange(System.Text.Encoding.ASCII.GetBytes(pdf_obj_String));
                    ary_pdf_file.AddRange(pdf_3rd_obj_End);


                    //▼画像配置の前のオブジェクト・バイナリ  

                    //4オブジェクトの開始バイト位置を格納  
                    ary_pdf_byte_head.Add(Convert.ToInt64(fs.Length + ary_pdf_file.Count));

                    pdf_obj_String = pdf_obj_Name.Replace("CC", (obj_No + 1).ToString());
                    ary_pdf_file.AddRange(System.Text.Encoding.ASCII.GetBytes(pdf_obj_String));

                    ary_pdf_file.AddRange(pdf_4th_obj_Start);


                    //▼画像本体のサイズ指定  
                    pdf_obj_String = pdf_4th_obj_ImageSize;
                    pdf_obj_String = pdf_obj_String.Replace("AA", bitmap.Width.ToString());
                    pdf_obj_String = pdf_obj_String.Replace("BB", bitmap.Height.ToString());
                    ary_pdf_file.AddRange(System.Text.Encoding.ASCII.GetBytes(pdf_obj_String));

                    ary_pdf_file.AddRange(pdf_4th_obj_Filter);

                    //▼今回処理する画像をメモリに格納  
                    //メモリに保存  
                    var baos = new MemoryStream();
                    bitmap.Save(baos, System.Drawing.Imaging.ImageFormat.Jpeg);


                    //▼画像本体の、バイトサイズを指定  
                    pdf_obj_String = pdf_4th_obj_Length;
                    pdf_obj_String = pdf_obj_String.Replace("LLLLL", baos.Length.ToString());
                    ary_pdf_file.AddRange(System.Text.Encoding.ASCII.GetBytes(pdf_obj_String));


                    //▼以下、jpg画像のバイナリを、挿入-----------------------------------------  
                    //画像を、バイト配列に変換したものを、渡す。  
                    //画像のバイナリ取得  
                    ary_pdf_file.AddRange(((MemoryStream)baos).ToArray());


                    //streamを閉じる  
                    ary_pdf_file.AddRange(pdf_4th_obj_End);


                    //▼画像配置の後のバイナリ  

                    //5オブジェクトの開始バイト位置を格納  
                    ary_pdf_byte_head.Add(Convert.ToInt64(fs.Length + ary_pdf_file.Count));

                    pdf_obj_String = pdf_obj_Name.Replace("CC", (obj_No + 2).ToString());
                    ary_pdf_file.AddRange(System.Text.Encoding.ASCII.GetBytes(pdf_obj_String));


                    //▼画像の表示サイズの指定  
                    pdf_obj_String = pdf_5th_obj_ShowSize;
                    pdf_obj_String = pdf_obj_String.Replace("WW", Convert.ToSingle(bitmap.Width * 0.75).ToString());
                    pdf_obj_String = pdf_obj_String.Replace("HH", Convert.ToSingle(bitmap.Height * 0.75).ToString());

                    //とりあえず、stream内部のバイト数を、数えるために、pdf_write_binaryに、移しておく  
                    pdf_write_binary = System.Text.Encoding.ASCII.GetBytes(pdf_obj_String);

                    //stream内部のバイト数を格納  
                    ary_pdf_file.AddRange(System.Text.Encoding.ASCII.GetBytes(
                            pdf_5th_obj_Length.Replace("LLLLL", pdf_write_binary.Length.ToString())));

                    //stream本体を格納  
                    ary_pdf_file.AddRange(pdf_write_binary);
                    pdf_write_binary = new byte[] { }; //消去  

                    //◆子オブジェクトを、閉める  
                    ary_pdf_file.AddRange(pdf_5th_obj_End);


                    try
                    {   //一旦、バイト型配列の内容をすべて上書き   
                        fs.Write((byte[])ary_pdf_file.ToArray(), 0, ary_pdf_file.Count);
                    }
                    catch
                    {

                    };


                    //配列を、一旦、クリアする。  
                    ary_pdf_file.Clear();
                    baos.Close();
                }
            }


            //▼フッター  
            //xrefの開始バイト位置を格納  
            Int64 xref_start_pos = fs.Length;

            //フッターの開始。まずは、xrefから。  
            ary_pdf_file.AddRange(pdf_xref_Start);

            //全オブジェクト数を格納  
            ary_pdf_file.AddRange(System.Text.Encoding.ASCII.GetBytes(
                        pdf_xref_objCount.Replace("MM", (ary_pdf_byte_head.Count + 1).ToString())));

            //0位置指定。  
            ary_pdf_file.AddRange(pdf_xref_ZERO);


            //各・子オブジェクトのバイト位置を、 各10桁で指定していく  
            for (int i = 0; i <= ary_pdf_byte_head.Count - 1; i += 1)
            {
                string fff = pdf_xref_objStartPos;
                //開始位置のバイト数は、10桁表示→つまり、PDFの最大サイズは、10GB程度？  
                fff = fff.Replace("QQQQQQQQQQ", Convert.ToUInt64(ary_pdf_byte_head[i]).ToString("0000000000"));
                ary_pdf_file.AddRange(System.Text.Encoding.ASCII.GetBytes(fff));
            }

            //trailerを格納 / Add trailer.  
            ary_pdf_file.AddRange(System.Text.Encoding.ASCII.GetBytes(
                        pdf_trailer.Replace("MM", (ary_pdf_byte_head.Count + 1).ToString())));

            //startxref～%%EOFを格納  
            ary_pdf_file.AddRange(System.Text.Encoding.ASCII.GetBytes(
                        pdf_startxref_EOF.Replace("TTT", xref_start_pos.ToString())));

            try
            {   //一旦、バイト型配列の内容をすべて上書き   
                fs.Write((byte[])ary_pdf_file.ToArray(), 0, ary_pdf_file.Count);
            }
            catch
            {

            };


            //■後処理  
            fs.Close(); //ファイルを閉じる   
            fs = null;

            //バイナリ・メモリの占有を開放する。  
            ary_pdf_file.Clear();
            ary_pdf_file = null;

            ary_pdf_byte_head.Clear();
            ary_pdf_byte_head = null;

           return;
        }
    }
```

## サンプル

例えば、
以下のようなコードで、ファイルを選択して
変換することが出来ます。


```c#:Form1.cs
        private void Form1_Load(object sender, EventArgs e)
        {   //ここは適当に記述しています。
            using (OpenFileDialog fileDialog = new OpenFileDialog())
            {   //PDFに直接変換したい画像ファイルを複数選択できるようにしている。
                fileDialog.Filter = "画像データ (*.bmp;*jpeg;*.jpg;*.png)|*.bmp;*jpeg;*.jpg;*.png";
                fileDialog.Multiselect = true;

                //「開く」ボタンが選択された時の処理
                if (fileDialog.ShowDialog() == DialogResult.OK)
                {   
                    //変換したい画像のリストを取得
                    string[] fileName = fileDialog.FileNames;  //こんな感じで選択されたファイルのパスが取得できる

                    //PDFは、今回は仮に、最初のファイルと同じフォルダに「test.pdf」として生成される。
                    clsPDF cls = new clsPDF();
                    cls.bitmap_to_pdf(fileName,
                        Path.Combine(Path.GetDirectoryName(fileName[0]), "test.pdf"));

                    //おしまいのメッセージ
                    MessageBox.Show("END");
                }
            }

            //さようなら
            Application.Exit();
        }
```

以上のコードを実行すると
指定した画像ファイルが、そのまま
指定した画像のフォルダに、PDFに変換されてあると思います。