(C#)
- PDFを読み込んで、埋め込まれたTiff画像/PNG画像を書き出す
- 受信したFAXがPDFとして保存されている想定
- CCITTFaxDecode
受信したFAXから生成されたPDFから、Tiff画像/PNG画像を書き出すサンプルソースです。
Tiff画像の情報は、幅高さ以外は、すべて決め打ち(固定値)にしているので、対象のPDFの内容に応じて、変更してください。(解像度など)
※生成されるPDF次第、という部分が、多数あります。
Tiffのタグについて
タグ数15個(固定)
ImageWidth[256] = ※画像より取得
ImageLength[257] = ※画像より取得
BitsPerSample[258] = 1(固定)
Compression[259] = 4(固定)
PhotometricInterpretation[262] = 0(固定)
FillOrder[266] = 1(固定)
StripOffsets[273] = 8(固定)
Orientation[274] = 1(固定)
SamplesPerPixel[277] = 1(固定)
RowsPerStrip[278] = ※画像より取得
StripByteCounts[279] = ※画像より取得
XResolution[282] (192/1 = 192.000000)(固定)*適宜修正 …アドレス値
YResolution[283] (192/1 = 192.000000)(固定)*適宜修正 …アドレス値
PlanarConfiguration[284] = 1(固定)
ResolutionUnit[296] = 2(固定)
※型
3: SHORT型(2バイト短整数)
4: LONG型(4バイト長整数)
5: RATIONAL型(はじめの4バイトが分子で、残り4バイトが分母)
//c:\Windows\Microsoft.NET\Framework\v4.0.30319\csc.exe pdf_2_tif_png.cs
using System;
using System.IO;
using System.Diagnostics;
using System.Drawing;
using System.Drawing.Imaging;
//==================================================
class pdf_2_tif_png { //*1
// メイン ////============================
static void Main(string[] args) { //*2
//対象となるPDF
string file_path = @"" + args[0];
//対象となるPDFの存在チェック
System.IO.FileInfo fi = new System.IO.FileInfo(file_path);
if(!fi.Exists){
Console.WriteLine("File is not exists! {0}", file_path);
return;
}
//対象となるPDFの読み込み
byte[] data = File.ReadAllBytes(@"" + fi.FullName);
Console.WriteLine("===================");
Console.WriteLine("PDF file : " + fi.FullName);
////------------
/*
Console.WriteLine("FileInfo Name={0}", fi.Name);
Console.WriteLine("FileInfo FullName={0}", fi.FullName);
Console.WriteLine("FileInfo DirectoryName={0}", fi.DirectoryName);
Console.WriteLine("FileInfo Directory.FullName={0}", fi.Directory.FullName);
Console.WriteLine("FileInfo Directory.Name={0}", fi.Directory.Name);
Console.WriteLine("FileInfo Extension={0}", fi.Extension);
Console.WriteLine("FileInfo Name only={0}", fi.Name.Replace(fi.Extension, ""));
*/
//TIFフォルダのチェックと作成
string tif_path = fi.Directory.FullName + @"\tif\";
if(!Directory.Exists(tif_path))
{
Directory.CreateDirectory(tif_path);
}
string tif_filename0 = tif_path + fi.Name.Replace(fi.Extension, "") + @"_";// + ImgCnt + @".tif";
//PNGフォルダのチェックと作成
string png_path = fi.Directory.FullName + @"\png\";
if(!Directory.Exists(png_path))
{
Directory.CreateDirectory(png_path);
}
string png_filename0 = png_path + fi.Name.Replace(fi.Extension, "") + @"_";// + ImgCnt + @".png"
//======================================================
//------------------------読み込み1週目↓ //**//**
// 各ページの MediaBox と Rotate の読み取り
int MdBxCnt = 0;
int ARCN2 = 3; //array_count
string[] st2 = new string[ARCN2];
st2[0] = "endobj";
st2[1] = "/MediaBox [";
st2[2] = "/Rotate ";
//256ページ以上のFAXは、無いと踏んでいる
double[,] dMdBx = new double[4,256];
int[] dRotate = new int[256];
byte[][] d = new byte[ARCN2][];
for(int i=0; i<ARCN2; ++i)
d[i] = System.Text.Encoding.ASCII.GetBytes(st2[i]);
byte[][] dm = new byte[ARCN2][];
for(int i=0; i<ARCN2; ++i)
dm[i] = new byte[d[i].Length];
//値初期化
int intRotate = 0;
bool outFlg = false;
//バイト内回遊(1)↓
for(int i=0;i<data.Length-st2[1].Length;i++){ //***1b
for(int j=0; j<ARCN2; ++j)
Array.Copy(data,i,dm[j],0,d[j].Length);
//--- 各文字、真偽判定のループ↓
bool[] aEq = new bool[ARCN2];
for(int j=0; j<ARCN2; ++j){
////---文字列と一致する場合↓
aEq[j] = System.Linq.Enumerable.SequenceEqual(d[j], dm[j]);
if(aEq[j]){
//string tx = System.Text.Encoding.ASCII.GetString(dm[j]);
//Console.WriteLine(tx);
//"endobj"
if(j==0){//***0
if(outFlg){
//Console.WriteLine("_out " +MdBxCnt+ "");
MdBxCnt++;
}
//値初期化
intRotate = 0;
outFlg = false;
} //***0
//MediaBox
if(j==1){// --- MediaBox *====================
outFlg = true;
//Console.WriteLine("MediaBox_1");
//int r = BitConverter.ToInt32(data[i+1], 0);
int c=0;
for(int c1=0;c1<40;c1++){
int c2 =(int)data[c1+i+d[j].Length];
//数字なら文字コードが48~57 *改行考慮せず
if(c2==0x5D){
c=c1;break;
}else{
}
}
byte[] b = new byte[c];
Array.Copy(data,i+d[j].Length,b,0,c);
string tx1 = System.Text.Encoding.ASCII.GetString(b);
//Console.WriteLine("MediaBox: " + tx1);
string[] tx1d = tx1.Split(' ');
int cn = 0;
foreach (string s in tx1d)
{
if(s.Length>0){
double d1 = double.Parse(s);
//Console.WriteLine("_ " + d1);
if(cn>=0&&cn<=3&&MdBxCnt<=255){
dMdBx[cn,MdBxCnt] = d1;
cn++;
}
}
}
}// --- MediaBox *====================
//Rotate なら、数値を拾いにいく
if(j>=2){// --- Rotate *====================
//int r = BitConverter.ToInt32(data[i+1], 0);
int c=0;
for(int c1=0;c1<20;c1++){
int c2 =(int)data[c1+i+d[j].Length];
//数字なら文字コードが48~57 *改行考慮せず
if(c2>=48&&c2<=57){
}else{c=c1;break;}
}
byte[] b = new byte[c];
Array.Copy(data,i+d[j].Length,b,0,c);
string tx1 = System.Text.Encoding.ASCII.GetString(b);
//Console.WriteLine("rotate: " + tx1);
switch (j) // **switch
{
case 2:
intRotate = Int32.Parse(tx1);
dRotate[MdBxCnt] = intRotate;
break;
default:
break;
} // **switch
}// --- Rotate *====================
}
////---文字列と一致する場合↑
}
//--- 各文字、真偽判定のループ↑
} //***1b
//バイト内回遊(1)↑
for(int s=0;s<MdBxCnt;s++){
Console.WriteLine("---");
for(int s1=0;s1<4;s1++){
Console.WriteLine("dMdBx: " + s1 + "_" + s + "_" + dMdBx[s1,s]);
}
if(dMdBx[3,s]!=0){
Console.WriteLine("rate1: " + (double)(dMdBx[2,s]/dMdBx[3,s]));
}else{
Console.WriteLine("rate1: div/zero");
}
Console.WriteLine("dRotate: " + dRotate[s]);
}
//------------------------読み込み1週目↑ //**//**
//------------------------読み込み2週目↓ //**//**
// メイン処理
int ImgCnt = 1;
int ARCN = 9; //array_count
string[] st = new string[ARCN];
st[0] = "/CCITTFaxDecode";
st[1] = " obj";
st[2] = "endobj";
st[3] = ">stream";
st[4] = "endstream";
st[5] = "/Columns ";
st[6] = "/Rows ";
st[7] = "/Width ";
st[8] = "/Height ";
//byte[][]
d = new byte[ARCN][];
for(int i=0; i<ARCN; ++i)
d[i] = System.Text.Encoding.ASCII.GetBytes(st[i]);
//streamの前の文字を、改行0Aに変換
d[3][0] = (byte)0x0A;
//byte[][]
dm = new byte[ARCN][];
for(int i=0; i<ARCN; ++i)
dm[i] = new byte[d[i].Length];
int stream_stt = 0;
int stream_size = 0;
int intColumns = 0;
int intRows = 0;
int intWidth = 0;
int intHeight = 0;
//bool
outFlg = false;
byte[] TiffHeader = {0x49,0x49,0x2A,0x00};
//バイト内回遊(2)↓
for(int i=0;i<data.Length-st[0].Length;i++){
for(int j=0; j<ARCN; ++j)
Array.Copy(data,i,dm[j],0,d[j].Length);
//--- 各文字、真偽判定のループ↓
bool[] aEq = new bool[ARCN];
for(int j=0; j<ARCN; ++j){
////---文字列と一致する場合↓
aEq[j] = System.Linq.Enumerable.SequenceEqual(d[j], dm[j]);
if(aEq[j]){
//string tx = System.Text.Encoding.ASCII.GetString(dm[j]);
//Console.Write(tx);
//" obj"
if(j==1){
outFlg = false; //リセット
}
//"/CCITTFaxDecode"
if(j==0){
outFlg = true;
}
//">stream"
if(j==3){
stream_stt = (int)(i+(int)d[j].Length+2);
}
//"endstream"
if(j==4){ //***_endstream ↓
stream_size = (int)((int)i-3) - stream_stt +1;
/////***** 出力処理↓
if(outFlg){ // --out flag
Console.WriteLine("-=-=-=");
Console.WriteLine("ImgCnt= " + ImgCnt);
Console.WriteLine("-----------------");
Console.WriteLine("intColumns= " + intColumns);
Console.WriteLine("intRows= " + intRows);
Console.WriteLine("intWidth= " + intWidth);
Console.WriteLine("intHeight= " + intHeight);
//// PDFのWidth、Height、MediaBoxのW,Hから、縦横比と、解像度を算出↓
double pngRate = 1;
if(intHeight!=0){
Console.WriteLine("rate2: " + (double)((double)intWidth/(double)intHeight));
}else{
Console.WriteLine("rate2: div/zero");
}
if(intHeight!=0 && dMdBx[3,(ImgCnt-1)]!=0){
double r3 = (double)(dMdBx[2,(ImgCnt-1)]/dMdBx[3,(ImgCnt-1)]);
if(r3!=0){
Console.WriteLine("rate3: " + ((double)((double)intWidth/(double)intHeight))/r3);
pngRate = ((double)((double)intWidth/(double)intHeight))/r3;
}else{
Console.WriteLine("rate3-2: div/zero");
}
}else{
Console.WriteLine("rate3-1: div/zero");
}
//// PDFのWidth、Height、MediaBoxのW,Hから、縦横比と、解像度を算出↑
//書き出し用の、バイト列
byte[] data_out = new byte[stream_size];
Array.Copy(data,stream_stt,data_out,0,stream_size);
//----- TIFファイル書き出し↓
var tif_filename = tif_filename0 + ImgCnt + @".tif";
using (var writer = new BinaryWriter(new FileStream(tif_filename, FileMode.Create)))
{
writer.Write(TiffHeader);
writer.Write(rtIIBytes(stream_size+8));
//65536byteを超える場合、2byteで収まらないので、4byteへ
int us = 0;
us = (int)Math.Floor(((double)((double)stream_size+8)/65536));
writer.Write(rtIIBytes(us));
//writer.Write((byte)0x00);
//writer.Write((byte)0x00);
writer.Write(data_out);
writer.Write(rtIIBytes(15));//タグ数
writer.Write(rtTag(256,3,1,0,intWidth,0));//ImageWidth[256]
writer.Write(rtTag(257,3,1,0,intHeight,0));//ImageLength[257]
writer.Write(rtTag(258,3,1,0,1,0));//BitsPerSample[258]
writer.Write(rtTag(259,3,1,0,4,0));//Compression[259]
writer.Write(rtTag(262,3,1,0,0,0));//PhotometricInterpretation[262]
writer.Write(rtTag(266,3,1,0,1,0));//FillOrder[266]
writer.Write(rtTag(273,4,1,0,8,0));//StripOffsets[273]
writer.Write(rtTag(274,3,1,0,1,0));//Orientation[274]
writer.Write(rtTag(277,3,1,0,1,0));//SamplesPerPixel[277]
writer.Write(rtTag(278,3,1,0,intHeight,0));//RowsPerStrip[278]
//65536byteを超える場合、2byteで収まらないので、4byteへ
int us1 = 0;
us1 = (int)Math.Floor(((double)((double)stream_size)/65536));
writer.Write(rtTag(279,4,1,0,stream_size,us1));//StripByteCounts[279]
writer.Write(rtTag(282,5,1,0,stream_size+8+2+180+4,us1));//XResolution[282]
writer.Write(rtTag(282,5,1,0,stream_size+8+2+180+4+8,us1));//YResolution[283]
writer.Write(rtTag(284,3,1,0,1,0));//PlanarConfiguration[284]
writer.Write(rtTag(296,3,1,0,2,0));//ResolutionUnit[296]
writer.Write(rtIIBytes(0));
writer.Write(rtIIBytes(0));
//XResolution
writer.Write(rtIIBytes(192));//204
writer.Write(rtIIBytes(0));
writer.Write(rtIIBytes(1));
writer.Write(rtIIBytes(0));
//YResolution
writer.Write(rtIIBytes(192));//98
writer.Write(rtIIBytes(0));
writer.Write(rtIIBytes(1));
writer.Write(rtIIBytes(0));
}
//----- TIFファイル書き出し↑
Bitmap image1;
image1 = new Bitmap(tif_filename, true);
//Console.WriteLine("Original width " + image1.Width);
//Console.WriteLine("Original height " + image1.Height);
//拡大縮小処理
int w = image1.Width;
int h = (int)((double)image1.Height * (double)pngRate);
Bitmap bitmap = new Bitmap(w, h);
Graphics g = Graphics.FromImage(bitmap);
System.Drawing.Imaging.ImageAttributes wrapMode = new System.Drawing.Imaging.ImageAttributes();
wrapMode.SetWrapMode(System.Drawing.Drawing2D.WrapMode.TileFlipXY);
g.DrawImage(image1, new Rectangle(0, 0, w, h), 0, 0, image1.Width, image1.Height, GraphicsUnit.Pixel, wrapMode);
//回転処理
switch (dRotate[(ImgCnt-1)]) // **switch
{
case 0:
bitmap.RotateFlip(RotateFlipType.RotateNoneFlipNone);
Console.WriteLine("rotate[0]");
break;
case 90:
bitmap.RotateFlip(RotateFlipType.Rotate90FlipNone);
Console.WriteLine("rotate[90]");
break;
case 180:
bitmap.RotateFlip(RotateFlipType.Rotate180FlipNone);
Console.WriteLine("rotate[180]");
break;
case 270:
bitmap.RotateFlip(RotateFlipType.Rotate270FlipNone);
Console.WriteLine("rotate[270]");
break;
default:
break;
} // **switch
//----- PNGファイル書き出し↓
var png_filename = png_filename0 + ImgCnt + @".png";
bitmap.Save(png_filename, System.Drawing.Imaging.ImageFormat.Png);
//----- PNGファイル書き出し↑
ImgCnt++;
}// --out flag
/////***** 出力処理↑
} //***_endstream ↑
//Columns Rows Width Height なら、数値を拾いにいく
if(j>=5){// --- CRWH
//int r = BitConverter.ToInt32(data[i+1], 0);
int c=0;
for(int c1=0;c1<20;c1++){
int c2 =(int)data[c1+i+d[j].Length];
//数字なら文字コードが48~57 *改行考慮せず
if(c2>=48&&c2<=57){
}else{c=c1;break;}
}
byte[] b = new byte[c];
Array.Copy(data,i+d[j].Length,b,0,c);
string tx1 = System.Text.Encoding.ASCII.GetString(b);
//Console.Write(tx1);
switch (j) // **switch
{
case 5:
intColumns = Int32.Parse(tx1);
break;
case 6:
intRows = Int32.Parse(tx1);
break;
case 7:
intWidth = Int32.Parse(tx1);
break;
case 8:
intHeight = Int32.Parse(tx1);
break;
default:
break;
} // **switch
}// --- CRWH
//Console.WriteLine("_");
}
////---文字列と一致する場合↑
}
//--- 各文字、真偽判定のループ↑
}
//バイト内回遊(2)↑
//------------------------読み込み2週目↑ //**//**
return;
} //*2
// メイン ////============================
///// Tiffタグ用、1////////////
static byte[] rtIIBytes(int i){ //*3
byte[] rtBt = new byte[2];
string t0 = "0000" + Convert.ToString(i, 16);
string t1 = t0.Substring(t0.Length - 4, 4);
string t2_r = t1.Substring(t1.Length - 2, 2);
string t2_l = t1.Substring(0, 2);
rtBt[0] = Convert.ToByte(t2_r,16);
rtBt[1] = Convert.ToByte(t2_l,16);
return rtBt;
} //*3
///// Tiffタグ用、2////////////
static byte[] rtTag(int i1,int i2,int i3,int i4,int i5,int i6){ //*4
byte[] rtBt = new byte[12];
Array.Copy(rtIIBytes(i1),0,rtBt,0,2);
Array.Copy(rtIIBytes(i2),0,rtBt,2,2);
Array.Copy(rtIIBytes(i3),0,rtBt,4,2);
Array.Copy(rtIIBytes(i4),0,rtBt,6,2);
Array.Copy(rtIIBytes(i5),0,rtBt,8,2);
Array.Copy(rtIIBytes(i6),0,rtBt,10,2);
return rtBt;
} //*4
} //*1
//==================================================
2021.8.4追記
FAXの解像度は、まちまち。
・w:204dpi x h:98dpi (A) <比:2.081632>
・w:204dpi x h:196dpi (B) <比:1.040816>
・w:204dpi x h:391dpi (C) <比:0.521739>
など。
stream中のデータからは判別つかない。
PDFのMediaBox[0 0 w h]の数値から類推するしかない。
例1)
Width 1728 (a)
Height 579 (b)
Mediabox w 609.88 (c)
Mediabox h 425.39 (d)
(a/b) 2.9844
(c/d) 1.4337
(a/b) / (c/d) -> 2.08 -> (A)
例2)
Width 1728 (a)
Height 2310 (b)
Mediabox w 609.88 (c)
Mediabox h 848.57 (d)
(a/b) 0.748052
(c/d) 0.718715
(a/b) / (c/d) -> 1.040 -> (B)
例3)
Width 1728 (a)
Height 4559 (b)
Mediabox w 609.88 (c)
Mediabox h 839.51 (d)
(a/b) 0.37903
(c/d) 0.72647
(a/b) / (c/d) -> 0.521742 -> (C)
過去投稿
(C#) PDFを読み込んで、埋め込まれた画像をJPGファイルとして書き出す(DCTDecodeのStream内をママ書き出しているだけなのでかなり雑)
https://qiita.com/santarou6/items/5f011e266929558ede34
(C#) PDFを読み込んで、Stream内をすべて別ファイルに書き出す、サンプルソース
https://qiita.com/santarou6/items/5272458b1e9ff2058327
C# JpgからPDFへ変換(1Jpgファイルを1ページとしたPDFファイルの作成)
https://qiita.com/santarou6/items/ff24500c13d05b12a940
外部サイト
TIFFのフォーマット
TIFFのフォーマット(その1)
https://jprogramer.com/libtiffcate/3188
TIFFのフォーマット(その2)
https://jprogramer.com/libtiffcate/3211
iTextSharp
https://itextpdf.com/en/products/itext-7
https://github.com/itext/itext7-dotnet
iText 7 Community for .NET (former iTextSharp) consists of several dlls.
C#でPDFファイルから画像を抜き出す モノクロTIFF編 (iTextSharp)
https://araramistudio.jimdo.com/2021/03/19/c-%E3%81%A7pdf%E3%83%95%E3%82%A1%E3%82%A4%E3%83%AB%E3%81%8B%E3%82%89%E7%94%BB%E5%83%8F%E3%82%92%E6%8A%9C%E3%81%8D%E5%87%BA%E3%81%99-%E3%83%A2%E3%83%8E%E3%82%AF%E3%83%ADtiff%E7%B7%A8-itextsharp/
Tiff Analyzer
Tiff解析ソフト「Tiff Analyzer」
https://www.vector.co.jp/soft/dl/winnt/art/se251005.html
TiffファイルのTAGを検出して表示とCSVファイルに保存