LoginSignup
0
0

More than 1 year has passed since last update.

(C#) PDFを読み込んで、埋め込まれたTiff画像/PNG画像を書き出す(受信したFAXがPDFとして保存されている想定 #CCITTFaxDecode)

Last updated at Posted at 2021-07-25

(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バイトが分母)

pdf_2_tif_png.cs

//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ファイルに保存

0
0
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
0
0