Hatena::ブログ(Diary)

七誌の開発日記(旧) このページをアンテナに追加 RSSフィード

新ブログ Twitter OneDrive Wiki

2011-02-15

[][][]スキャンPDFモバイル向け調整ツール

f:id:n7shi:20110216132427j:image:medium:right

スキャンで作成したPDFモバイル用に調整するには、トリミングで余白を落として、コントラストを調整して、解像度を落とす必要があります。そのためのツールを作成しました。実行には.NET Framework 2.0が必要です。

JPEG調整ツール(id:n7shi:20110214)からフォークして、PDF解析ツール(id:n7shi:20110208)とPDF変換ツール(id:n7shi:20110206)を取り込んで作成しました。

【注意】ラスターデータしか扱えないため、スキャン以外の方法(PDFプリンタで出力など)で作成したPDFは正常に扱えません。それらを処理する場合、あらかじめJPEGTIFFに変換してから、このツールでフォルダを読み込む必要があります。TIFFに変換できる無料ソフトとしてはPDF-XChange Viewerが手頃だと思います。

2011-02-09

[][][]スキャンPDFからJPEG抽出 (2)

id:n7shi:20110208PDF解析コードを利用して、id:n7shi:20110201JPEG抽出ツールを自前のコードに置き換えました。実行には.NET Framework 2.0が必要です。

必要最低限のコードで構成されるためファイルサイズが小さくなって、ライセンスも変更できるようになりました。

2011-02-08

[][]解析ツール

PDF関連のツールを作ろうと試行錯誤しています。仕様書バイナリエディタテキストエディタを見比べながら開発するのは効率が悪かったため、構造を抽出して表示するツールを作りました。実行には.NET Framework 2.0が必要です。

クロスリファレンスへの対応には苦労しました。一度は挫折しましたが、どうにか動くようになりました。クロスリファレンスが壊れているファイルは頭から読み込みます。テストが不十分なため、ファイルによってはエラーになるかもしれません。

2011-02-07

[][]FlateDecodeと差分圧縮

PDFで/Filter /FlateDecodeが指定されているストリームはdeflate圧縮されています。.NETのDeflateStreamで展開しようとしてもエラーになりますが、最初の2バイトを飛ばすと読み込めます。

/DecodeParmsが指定されていると更に差分圧縮されています。/XRefやPNGなどで使用されているようです。

このようなストリームを展開するため、DeflateStreamをラップしたクラスを作成しました。パブリックドメインで置いておきます。ちなみに差分圧縮つながりでADPCMをいじったことがあります。 ⇒ id:n7shi:20090329

public class PdfDeflateStream : Stream
{
    private DeflateStream ds;
    int columns, position, rowpos;
    byte[] prev, rows;

    public PdfDeflateStream(Stream s, int columns)
    {
        this.columns = columns;
        if (columns > 0)
        {
            prev = new byte[columns];
            rows = new byte[columns];
            rowpos = rows.Length;
        }
        s.ReadByte();
        s.ReadByte();
        ds = new DeflateStream(s, CompressionMode.Decompress);
    }

    public override long Length
    {
        get { throw new NotImplementedException(); }
    }
    public override bool CanRead { get { return true; } }
    public override bool CanWrite { get { return false; } }
    public override bool CanSeek { get { return false; } }
    public override void Flush() { }

    public override long Position
    {
        get { return position; }
        set { throw new NotImplementedException(); }
    }

    public override int Read(byte[] buffer, int offset, int count)
    {
        int ret = 0;
        if (columns == 0)
            ret = ds.Read(buffer, offset, count);
        else
        {
            while (ret < count)
            {
                if (rowpos >= rows.Length)
                {
                    if (ds.ReadByte() != 2)
                        throw new Exception("unknown predictor type");
                    Array.Copy(rows, prev, rows.Length);
                    int len = ds.Read(rows, offset, rows.Length);
                    if (len < rows.Length) break;
                    for (int i = 0; i < rows.Length; i++)
                        rows[i] += prev[i];
                    rowpos = 0;
                }
                int rlen = Math.Min(count - ret, rows.Length - rowpos);
                Array.Copy(rows, rowpos, buffer, ret, rlen);
                ret += rlen;
                rowpos += rlen;
            }
        }
        position += ret;
        return ret;
    }

    public override long Seek(long offset, SeekOrigin origin)
    {
        throw new NotImplementedException();
    }

    public override void Write(byte[] buffer, int offset, int count)
    {
        throw new NotImplementedException();
    }

    public override void SetLength(long value)
    {
        throw new NotImplementedException();
    }
}

2011-02-06

[][][]アーカイブ

C#で任意の数のJPEGを埋め込んだPDFを生成してみました。id:n7shi:20110205に掲載したPDFを複数ページに拡張して出力しています。以下にコードを掲載します(パブリックドメイン)。コードから呼び出しているGetJpegSizeはid:n7shi:20110204に掲載しています。

public static void MakePDF(string pdf, string[] jpgs)
{
    var sizes = new Size[jpgs.Length];
    for (int i = 0; i < jpgs.Length; i++)
    {
        sizes[i] = GetJpegSize(jpgs[i]);
    }

    using (var fs = new FileStream(pdf, FileMode.Create))
    using (var sw = new StreamWriter(fs) { AutoFlush = true })
    {
        var objp = new List<long>();
        var no_j = 3 + jpgs.Length * 2;

        sw.WriteLine("%PDF-1.2");

        sw.WriteLine();
        objp.Add(fs.Position);
        sw.WriteLine("1 0 obj");
        sw.WriteLine("<< /Type /Catalog /Pages 2 0 R >>");
        sw.WriteLine("endobj");

        sw.WriteLine();
        objp.Add(fs.Position);
        sw.WriteLine("2 0 obj");
        sw.WriteLine("<<");
        sw.WriteLine("  /Type /Pages /Count {0}", jpgs.Length);
        sw.WriteLine("  /Kids");
        sw.Write("  [");
        for (int i = 0; i < jpgs.Length; i++)
        {
            if ((i & 7) == 0)
            {
                sw.WriteLine();
                sw.Write("   ");
            }
            sw.Write(" {0} 0 R", 3 + i * 2);
        }
        sw.WriteLine();
        sw.WriteLine("  ]");
        sw.WriteLine(">>");
        sw.WriteLine("endobj");

        for (int i = 0; i < jpgs.Length; i++)
        {
            var no_p = 3 + i * 2;
            var no_c = no_p + 1;
            var name = "/Jpeg" + (i + 1);
            var sz = sizes[i];

            sw.WriteLine();
            objp.Add(fs.Position);
            sw.WriteLine("{0} 0 obj", no_p);
            sw.WriteLine("<<");
            sw.WriteLine("  /Type /Page /Parent 2 0 R /Contents {0} 0 R", no_c);
            sw.WriteLine("  /MediaBox [ 0 0 {0} {1} ]", sz.Width, sz.Height);
            sw.WriteLine("  /Resources");
            sw.WriteLine("  <<");
            sw.WriteLine("    /ProcSet [ /PDF /ImageB /ImageC /ImageI ]");
            sw.WriteLine("    /XObject << {0} {1} 0 R >>", name, no_j + i);
            sw.WriteLine("  >>");
            sw.WriteLine(">>");
            sw.WriteLine("endobj");

            sw.WriteLine();
            objp.Add(fs.Position);
            sw.WriteLine("{0} 0 obj", no_c);
            var st4 = string.Format("q {0} 0 0 {1} 0 0 cm {2} Do Q",
                sz.Width, sz.Height, name);
            sw.WriteLine("<< /Length {0} >>", st4.Length);
            sw.WriteLine("stream");
            sw.WriteLine(st4);
            sw.WriteLine("endstream");
            sw.WriteLine("endobj");
        }

        for (int i = 0; i < jpgs.Length; i++)
        {
            using (var fsj = new FileStream(jpgs[i], FileMode.Open))
            {
                var name = "/Jpeg" + (i + 1);
                var sz = sizes[i];

                sw.WriteLine();
                objp.Add(fs.Position);
                sw.WriteLine("{0} 0 obj", no_j + i);
                sw.WriteLine("<<");
                sw.WriteLine("  /Type /XObject /Subtype /Image /Name {0}", name);
                sw.WriteLine("  /Filter /DCTDecode /BitsPerComponent 8 /ColorSpace /DeviceRGB");
                sw.WriteLine("  /Width {0} /Height {1} /Length {2}", sz.Width, sz.Height, fsj.Length);
                sw.WriteLine(">>");
                sw.WriteLine("stream");
                var buf = new byte[4096];
                int len;
                while ((len = fsj.Read(buf, 0, buf.Length)) > 0)
                    fs.Write(buf, 0, len);
                sw.WriteLine();
                sw.WriteLine("endstream");
                sw.WriteLine("endobj");
            }
        }

        sw.WriteLine();
        var xref = fs.Position;
        sw.WriteLine("xref");
        var size = objp.Count + 1;
        sw.WriteLine("0 {0}", size);
        sw.WriteLine("{0:0000000000} {1:00000} f", 0, 65535);
        foreach (var p in objp)
            sw.WriteLine("{0:0000000000} {1:00000} n", p, 0);
        sw.WriteLine("trailer");
        sw.WriteLine("<< /Root 1 0 R /Size {0} >>", size);
        sw.WriteLine("startxref");
        sw.WriteLine("{0}", xref);
        sw.WriteLine("%%EOF");
    }
}

StreamWriterでAutoFlushをtrueに設定しているのがポイントです。これをやらないと書き込みがバッファリングされるため、オフセットが正確に求まりません。

サンプル

上のコードを使ったサンプルです。

2011-02-05

[][]アーカイブ

JPEGアーカイブするのに無圧縮ZIPを使っていましたが(id:n7shi:20100923)、PDFの中にはJPEGがそのまま入っているため(id:n7shi:20110201)、PDFはZIPと同じようにコンテナとして扱えることに気付きました。

さっそく試そうと思い、以前id:m107さんに教えていただいたサイトを見返しました。

JPEGの埋め込み方は説明されていませんが、手元にあるPDFファイルなどを参考に補ってみました。最小構成の1ページでは以下のようになります。

※改行コードはCR+LFとしてオフセットを求めています。

%PDF-1.2

1 0 obj
<< /Type /Catalog /Pages 2 0 R >>
endobj

2 0 obj
<<
  /Type /Pages /Count 1
  /Kids
  [
    3 0 R
  ]
>>
endobj

3 0 obj
<<
  /Type /Page /Parent 2 0 R /Contents 4 0 R
  /MediaBox [ 0 0 16 16 ]
  /Resources
  <<
    /ProcSet [ /PDF /ImageB /ImageC /ImageI ]
    /XObject << /Jpeg1 5 0 R >>
  >>
>>
endobj

4 0 obj
<< /Length 30 >>
stream
q 16 0 0 16 0 0 cm /Jpeg1 Do Q
endstream
endobj

5 0 obj
<<
  /Type /XObject /Subtype /Image /Name /Jpeg1
  /Filter /DCTDecode /BitsPerComponent 8 /ColorSpace /DeviceRGB
  /Width 16 /Height 16 /Length 631
>>
stream
※ここにJPEGファイルのバイナリをそのまま埋め込む
endstream
endobj

xref
0 6
0000000000 65535 f
0000000012 00000 n
0000000066 00000 n
0000000148 00000 n
0000000353 00000 n
0000000441 00000 n
trailer
<< /Root 1 0 R /Size 6 >>
startxref
1268
%%EOF

2011-02-01

[][][]スキャンPDFからJPEG抽出 (1)

【追記】自前コードに置き換えてパブリックドメイン化したものを公開しました。 ⇒ id:n7shi:20110209

とりあえず高画質でスキャンしておいて、使うときに用途に応じて加工したいことがあります。そんなときにPDFを一気に変換するツールが欲しかったので自作しました。実行には.NET Framework 2.0が必要です。

【注意】白黒(二値)でスキャンするとG4などで格納されるためJPEGとして抽出できません。

PDFのライブラリとしてiTextSharpを使用しています。当初はPDFを自前で処理しようとしましたが、ページ構造が複雑なファイルには対応できそうもなかったため断念しました。

iTextSharpの使用方法についてはKenさんのブログ記事を参考にさせていただきました。ありがとうございます。