LoginSignup
6
8

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

Last updated at Posted at 2019-07-15

概要

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

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

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

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

※Python版はこちらです。

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

下の画像は、今回コードでの作成例です。
参考画像

参考ページ

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

自力で、JPEG画像から、PDFに変換してみた - ニコニコ動画
https://www.nicovideo.jp/watch/sm19664272

※リンク先は、私のブログや動画です。気を付けてください。

本題

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

サンプル

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

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に変換されてあると思います。

6
8
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
6
8