C#でPDFファイルから画像を抜き出す モノクロTIFF編 (iTextSharp)

C#でPDFファイルから画像を抜き出す 導入編 (iTextSharp)」ではPDFファイルに埋め込まれている画像を取り出す事に挑戦しました。

しかし、この方法では一部の画像しか正しく抜き出す事が出来ません。

 

抜き出す事が出来なかったものの1つに、モノクロTIFF画像があります。

今回はモノクロのTIFF画像を抜き出せるようにしてみます。


PDFの画像データの仕組み

画像データは辞書とストリームの2つで構成されています。

辞書には画像の情報が格納され、ストリームに画像が格納されます。

参照:https://ja.wikipedia.org/wiki/Portable_Document_Format

 

 

ストリームはいくつかの圧縮形式で格納する事ができます。

この中で「CCITTFaxDecode」という圧縮形式が、モノクロのTIFF画像で使われている圧縮形式になります。

 


モノクロTIFF画像の取り出し方

C#でPDFファイルから画像を抜き出す 導入編 (iTextSharp)」では画像データのストリーム部分をそのままファイルへ書き出していました。

しかし、圧縮形式が「CCITTFaxDecode」の場合、ストリーム部分はそのままTIFF画像のデータ部として利用できますがヘッダー部分がありません。

 

そこで、画像データの辞書部分から必要な情報を取得し、LibTiff というオープンソースを使ってTIFF画像のヘッダー部分を作成するという方法をとります。


画像抜き出しのおさらい

まず必要なオープンソースをインストールします。

iTextSharp については「C#でPDFファイルを操作する 準備編 (iTextSharp)」を参照してください。

LibTiff はNuGetパッケージマネージャーから「BitMiracle.LibTiff.NET」をインストールします。

 

また、PDFファイルから画像オブジェクトを収取する部分は「C#でPDFファイルから画像を抜き出す 導入編 (iTextSharp)」と変わりありません。


サンプルコード

using System;
using System.Collections.Generic;
using System.IO;
using System.Drawing;
using System.Drawing.Imaging;
using iTextSharp.text.pdf;


namespace ConsoleApplication1
{
    class Program
    {
        static void Main()
        {
            PDFtoImage(@"C:\test.pdf", @"C:\images\");
        }

        static void PDFtoImage(string pdfName, string imgFolder)
        {
            var imgName = Path.GetFileNameWithoutExtension(pdfName);

            var pdf = new PdfReader(pdfName);
            var images = new List<PdfObject>();
            int n = pdf.NumberOfPages;
            for (int i = 1; i <= n; i++)
            {
                PdfDictionary pg = pdf.GetPageN(i);
                CollectImage(pg, images);
            }

            for (int i = 0; i < images.Count; ++i)
            {
                var obj = (PRIndirectReference)images[i];
                var pfdStream = (PRStream)pdf.GetPdfObject(obj.Number);
                byte[] bytes = PdfReader.GetStreamBytesRaw(pfdStream);

                var tg = (PdfDictionary)PdfReader.GetPdfObject(images[i]);
                var filter = (PdfName)tg.Get(PdfName.FILTER);
                if (PdfName.CCITTFAXDECODE.Equals(filter))
                {
                    var wid = UInt32.Parse(tg.Get(PdfName.WIDTH).ToString());
                    var hei = UInt32.Parse(tg.Get(PdfName.HEIGHT).ToString());
                    var bpc = UInt32.Parse(tg.Get(PdfName.BITSPERCOMPONENT).ToString());
                    var cmp = Compression.CCITTFAX3;
                    var parms = (PdfDictionary)tg.Get(PdfName.DECODEPARMS);
                    var kobj = parms.Get(PdfName.K);
                    if (null != kobj)
                    {
                        if (-1 == ((PdfNumber)kobj).IntValue)
                            cmp = Compression.CCITTFAX4;
                    }

                    var name = string.Format("{0}{1}_{2}.tif", imgFolder, imgName, i);
                    Tiff tiff = Tiff.Open(name, "w");
                    tiff.SetField(TiffTag.IMAGEWIDTH, wid);
                    tiff.SetField(TiffTag.IMAGELENGTH, hei);
                    tiff.SetField(TiffTag.COMPRESSION, cmp);
                    tiff.SetField(TiffTag.BITSPERSAMPLE, bpc);
                    tiff.SetField(TiffTag.SAMPLESPERPIXEL, 1);
                    tiff.WriteRawStrip(0, bytes, bytes.Length);
                    tiff.Close();
                }
            }
        }

        static void CollectImage(PdfDictionary dic, List<PdfObject> images)
        {
            var res  = (PdfDictionary)PdfReader.GetPdfObject(dic.Get(PdfName.RESOURCES));
            var xobj = (PdfDictionary)PdfReader.GetPdfObject(res.Get(PdfName.XOBJECT));
            if (null != xobj)
            {
                foreach (PdfName name in xobj.Keys)
                {
                    PdfObject obj = xobj.Get(name);
                    if (obj.IsIndirect())
                    {
                        var tg = (PdfDictionary)PdfReader.GetPdfObject(obj);
                        var type = (PdfName)PdfReader.GetPdfObject(tg.Get(PdfName.SUBTYPE));
                        if (PdfName.IMAGE.Equals(type))
                        {
                            images.Add(obj);
                        }
                        else if (PdfName.FORM.Equals(type))
                        {
                            CollectImage(tg, images);
                        }
                        else if (PdfName.GROUP.Equals(type))
                        {
                            CollectImage(tg, images);
                        }
                    }
                }
            }
        }
    }
}

変更したのは37~62行目です。

FILTER情報を取得する (38行目)

画像オブジェクトの辞書から圧縮形式を表す FILTER を取得します。

FILTER の値が「CCITTFaxDecode」の場合にTIFF画像として保存しています。

ヘッダー情報を収集する (41~51行目)

画像オブジェクトの辞書からTIFF画像のヘッダーとして必要な情報を取得します。

画像の幅、高さ、1色成分あたりのBit数の他に、圧縮形式の詳細について取得しています。

 

「CCITTFaxDecode」には「Group 3」と「Group 4」の2種類があり、

その情報が DECODEPARMS の K というキーワードを使って取得する事ができます。

K の値が -1 の時は「Group 4」です。

 

TIFFを作成する (53~61行目)

Tiffオブジェクトを作成し、SetFieldメソッドでヘッダー情報をセットしていきます。

さらに、WriteRawStripメソッドでストリーム部分をセットすればTIFF画像の完成です。

 

関連記事