概要
表題の通り
Visual Studio 2019のC#にて、画像をそのままPDFに変換する。
コードのサンプルでございます。
特段、NuGetなどで他のプラグインや
別途外部dllを必要としないようにしています。
要は、バイナリを以下のように、直接叩いています。
1.PDFのヘッダーの記述
2.ファイルパスからBitmapで読み込む。
3.Bitmapをメモリ上にJpeg形式で保存。
4.PDFファイルに画像情報と共に、画像バイナリも埋める。
5.PDFのフッターの記述。
※私は素人ですし
今回のコードは、あくまで最低限の動作する部分にとどめていますので、汚いです。
例外処理や、解放など至らぬ点が、多々ございます。ご了承ください。
もし実際に参考にされる際は、必要個所を必ず訂正してからにしてください。
また、元がJpegファイルなら、2~3項も省略できます。
以前、
Yahoo知恵ノートに似たものを公開していたのですが、
Yahoo知恵ノートが廃止されてしまったため、
こちらに改めて手を加えて、公開させていただこうと思います。
参考ページ
・特に、dllを使わず、、バイナリを直接叩いて、PDFを作れないか、やってみた。結果報告・1
http://oyk3865b.blog13.fc2.com/blog-entry-1127.html
・自力で、JPEG画像から、PDFに変換してみた - ニコニコ動画
https://www.nicovideo.jp/watch/sm19664272
※リンク先は、私のブログや動画です。気を付けてください。
本題
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;
}
}
サンプル
例えば、
以下のようなコードで、ファイルを選択して
変換することが出来ます。
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に変換されてあると思います。