Azure FunctionsとDioDocsで実現するサーバーレス画像処理-PDFを画像に変換してリサイズまで自動化する

PDFファイルから画像ファイルへの変換処理は、帳票のプレビュー、AIやOCRの前処理など文書管理システムをはじめとするさまざまな業務システムで必要とされるケースがあります。大量のPDFファイルやサイズの大きいPDFファイルを変換する場合は、クライアントサイドではなくサーバーサイドで処理することを検討するかと思いますが、その際には処理パフォーマンスに合わせたスケーリングなどサーバーの管理面での課題も出てきます。

このような要件では、サーバーレスに扱えるAzure Functionsで処理の実装を検討するケースも多く見られます。そこで本記事では、Azure FunctionsとDioDocs(ディオドック)(for PDF/for Imaging)を使用して、Azure Blob StorageへPDFをアップロードすると自動的にPNGへ変換し、さらに画像サイズを最適化して保存する、といった画像処理を実装する方法を紹介します。

実装する内容

Azure FunctionsアプリケーションでBlob Storageトリガーを使用する関数を準備します。この関数はPDFファイルのAzure Blob Storageへのアップロードをトリガーとして実行されます。関数内では、DioDocs for PDFを使用してPDFファイルを読み込んで各ページをPNGファイルに出力します。さらに、DioDocs for Imagingを使用してPNGファイルのリサイズを実行します。その後にPNGファイルをAzure Blob Storageへ出力します。

PDFファイルをアップロード
      │
      ▼
Azure Blob Storage
(pdf-input)
      │
      ▼
Azure Functions
(Blob Trigger & EventGrid)
      │
      ├─ PDFファイルを読み込み、ページ単位でPNGファイルに変換(DioDocs for PDF)
      ├─ PNGファイルをリサイズ(DioDocs for Imaging)
      ├─ Blob StorageへPNGファイルをアップロード
      │
      ▼
Azure Blob Storage
(image-output)

このように、PDFファイルのアップロードを起点にしてAzure Functionsでの処理が自動で実行されます。

アプリケーションを作成

今回は、以下のドキュメントを参考にAzure Functionsアプリケーションを作成します。

Visual Studioでプロジェクトテンプレート「Azure Functions」を選択して[次へ]をクリックします。

アプリケーションを作成

プロジェクト名「DioDocsFunctionApp1」を入力して[次へ]をクリックします。

アプリケーションを作成

Azure Functionsの追加情報を入力します。「Functions worker」は「.NET 10 Isolated (長期的なサポート)」を選択、「Function」は「Blob trigger (using Event Grid)」を選択します。「Path」には「pdf-input」を設定して[作成]をクリックします。

アプリケーションを作成

DioDocsFunctionApp1プロジェクトが作成されます。

アプリケーションを作成

Event GridベースのBlob Storageトリガー

Function1関数ではSource = BlobTriggerSource.EventGridが定義されており、BLOBコンテナpdf-inputで発生するイベントがトリガーになって関数を開始するように設定されています。

[Function(nameof(Function1))]
public async Task Run([BlobTrigger("pdf-input/{name}", Source = BlobTriggerSource.EventGrid, Connection = "")] Stream stream, string name)
{
    using var blobStreamReader = new StreamReader(stream);
    var content = await blobStreamReader.ReadToEndAsync();
    _logger.LogInformation("C# Blob Trigger (using Event Grid) processed blob\n Name: {name} \n Data: {content}", name, content);
}

NuGetパッケージの追加

Visual Studioの「NuGet パッケージ マネージャー」からDioDocs for PDFとDioDocs for ImagingのパッケージDioDocs.Pdf.jaDioDocs.Imaging.jaをインストールします。

NuGetパッケージの追加

Azure Blob StorageとDioDocsを使うコードを追加

Function1を以下のように更新します。Azure Blob Storageの出力先コンテナimage-outputを読み取るための入力バインドBlobInputの設定と、DioDocs for PDF/Imagingを使用してPDFファイルを読み込んでPNGファイルに変換、PNGファイルを読み込んでリサイズを実行するコードを追加します。

/// <summary>
/// PDFファイルを受け取り、読み込んで各ページをPNG画像に変換、リサイズしてアップロードします
/// </summary>
/// <param name="stream">PDFファイルのストリーム</param>
/// <param name="blobContainerClient">PNG画像を保存するBlobコンテナ</param>
/// <param name="name">PDFファイルの名前</param>
/// <param name="cancellationToken">キャンセルトークン</param>
[Function(nameof(Function1))]
public async Task Run(
[BlobTrigger("pdf-input/{name}", Source = BlobTriggerSource.EventGrid, Connection = "AzureWebJobsStorage")] Stream stream,
[BlobInput("image-output", Connection = "AzureWebJobsStorage")] BlobContainerClient blobContainerClient,
string name,
CancellationToken cancellationToken)
{
    _logger.LogInformation("PDFを処理します: {Name}", name);

    // ライセンスキーを設定。設定しない場合はPDFファイルを5ページまでしか処理できません。
    //GcPdfDocument.SetLicenseKey("XXXXX");
    //GcBitmap.SetLicenseKey("XXXXX");

    // PDFファイルを読み込む
    var document = new GcPdfDocument();
    document.Load(stream);

    // PDFファイルにページが無い場合は処理をスキップ
    if (document.Pages.Count == 0)
    {
        _logger.LogWarning("PDFにページがありません。処理をスキップします: {Name}", name);
        return;
    }

    // 出力ファイル名のプレフィックス(拡張子を除いたPDFファイル名)
    var sourceName = Path.GetFileNameWithoutExtension(name);

    // PNG画像へのレンダリングオプションを設定
    var renderOptions = new SaveAsImageOptions
    {
        Resolution = 150,                    // 解像度150DPI
        DrawAnnotations = false,             // 注釈は描画しない
        DrawFormFields = false,              // フォームフィールドは描画しない
        BackColor = System.Drawing.Color.White // 背景色は白
    };

    // 正常に処理されたページ数をカウント
    var successCount = 0;

    // PDFドキュメントの各ページを処理
    for (var pageIndex = 0; pageIndex < document.Pages.Count; pageIndex++)
    {
        var pageNumber = pageIndex + 1;

        try
        {
            // ページをPNG画像として出力ストリームに保存
            await using var renderedPng = new MemoryStream();
            document.Pages[pageIndex].SaveAsPng(renderedPng, renderOptions);
            renderedPng.Position = 0;

            // 出力されたPNG画像を読み込む
            using var source = new GcBitmap();
            source.Load(renderedPng);

            // 画像をスケーリング(最大幅1920pxに制限)
            var scale = Math.Min(1d, 1920 / (double)source.PixelWidth);
            var width = Math.Max(1, (int)Math.Round(source.PixelWidth * scale));
            var height = Math.Max(1, (int)Math.Round(source.PixelHeight * scale));

            // 高品質なCubic補間でリサイズ
            using var resized = source.Resize(width, height, InterpolationMode.Cubic);

            // リサイズしたPNG画像を出力ストリームに保存
            await using var output = new MemoryStream();
            resized.SaveAsPng(output);
            output.Position = 0;

            // BLOBコンテナーにアップロードするファイル名を生成
            // 形式: {PDF名}/page-{ページ番号:4桁}.png
            var blobName = $"{sourceName}/page-{pageNumber:D4}.png";

            // BLOBコンテナーにアップロード
            await blobContainerClient.UploadBlobAsync(blobName, output, cancellationToken);

            successCount++;
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "ページの処理に失敗しました: {Name}, ページ番号: {PageNumber}", name, pageNumber);
            throw;
        }
    }

    // 処理完了
    _logger.LogInformation("PDFを {PageCount} ページ処理しました: {Name}", successCount, name);
}

DioDocs for PDFでPDFを読み込む

アップロードされたPDFファイルは、DioDocs for PDFを使用して読み込みます。

// PDFファイルを読み込む
var document = new GcPdfDocument();
document.Load(stream);

ページ単位でPNGへ変換

PDFドキュメントの各ページは以下の処理でPNG画像へ変換されます。

// PNG画像へのレンダリングオプションを設定
var renderOptions = new SaveAsImageOptions
{
    Resolution = 150,                    // 解像度150DPI
    DrawAnnotations = false,             // 注釈は描画しない
    DrawFormFields = false,              // フォームフィールドは描画しない
    BackColor = System.Drawing.Color.White // 背景色は白
};
:
:
// ページをPNG画像として出力ストリームに保存
await using var renderedPng = new MemoryStream();
document.Pages[pageIndex].SaveAsPng(renderedPng, renderOptions);
renderedPng.Position = 0;

DioDocs for ImagingでPNG画像をリサイズ

単純にPNG画像へ変換するだけでは、PDFドキュメントのページサイズによっては非常に大きな画像が生成されることがあります。そこでDioDocs for Imagingを使用してPNG画像をリサイズします。

// 出力されたPNG画像を読み込む
using var source = new GcBitmap();
source.Load(renderedPng);

// 画像をスケーリング(最大幅1920pxに制限)
var scale = Math.Min(1d, 1920 / (double)source.PixelWidth);
var width = Math.Max(1, (int)Math.Round(source.PixelWidth * scale));
var height = Math.Max(1, (int)Math.Round(source.PixelHeight * scale));

// 高品質なCubic補間でリサイズ
using var resized = source.Resize(width, height, InterpolationMode.Cubic);

 // リサイズしたPNG画像を出力ストリームに保存
 await using var output = new MemoryStream();
 resized.SaveAsPng(output);
 output.Position = 0;

出力ストリームに保存したPNG画像は出力先コンテナimage-outputへアップロードされます。

// BLOBコンテナーにアップロードするPNG画像のファイル名を生成
// 形式: {PDFファイル名}/page-{ページ番号:4桁}.png
var blobName = $"{sourceName}/page-{pageNumber:D4}.png";

// BLOBコンテナーにPNG画像をアップロード
await blobContainerClient.UploadBlobAsync(blobName, output, cancellationToken);

例えばsample.pdfをアップロードすると、出力先コンテナimage-outputには以下のような構成でPNGファイルが保存されます。

sample/
 ├─ page-0001.png
 ├─ page-0002.png
 ├─ page-0003.png

ストレージアカウントとBLOBコンテナを作成

AzureポータルでAzure Functionsで使用するストレージアカウントmesciusteststorageとBLOBコンテナpdf-inputimage-outputを作成します。

ストレージアカウントとBLOBコンテナを作成

作成した関数をAzure Functionsにデプロイ

作成したAzure FunctionsアプリケーションをAzureへデプロイして確認します。ソリューションエクスプローラーからDioDocsFunctionApp1プロジェクトを右クリックして[発行]をクリックします。

作成した関数をAzure Functionsにデプロイする

公開するターゲットは「Azure」、特定のターゲットは「Azure 関数アプリ」を選択します。

作成した関数をAzure Functionsにデプロイ
作成した関数をAzure Functionsにデプロイ

アプリケーションの名前やリソースグループなどを入力して新しいインスタンスを作成します。以下の画面に切り替わったら[完了]をクリックします。

作成した関数をAzure Functionsにデプロイ

これで公開の準備が完了しました。[発行]をクリックして作成したAzure FunctionアプリケーションをAzureへデプロイします。

作成した関数をAzure Functionsにデプロイ

Event GridベースのBlob Storageトリガーを使用する準備

エンドポイントURLを作成

イベントサブスクリプションを作成するには、Blob Storageのイベントを報告する特定のエンドポイントのURLを用意して設定する必要があります。こちらの内容を参考に以下のようなエンドポイントURLを作成します。

https://<FUNCTION_APP_NAME>.azurewebsites.net/runtime/webhooks/blobs?functionName=Host.Functions.<FUNCTION_NAME>&code=<BLOB_EXTENSION_KEY>

イベントサブスクリプションを作成

こちらを参考にイベントサブスクリプションを作成します。Azureポータルで使用するストレージアカウントの「イベント」を選択して「イベント サブスクリプション」をクリックします。

イベントサブスクリプションを作成

以下の詳細情報を設定して[作成]をクリックします。

  • 名前:DioDocsEventSub
  • システムトピック名:pdf-input
  • イベントの種類のフィルター:Blob Created
  • エンドポイントの種類:Web hook
  • エンドポイント:作成したエンドポイントURL
イベントサブスクリプションを作成

作成が完了すると以下のように作成したイベントサブスクリプションが表示されます。

アプリケーションを作成

デプロイしたアプリケーションを確認

BLOBコンテナpdf-inputにPDFファイルをアップロードして動作を確認します。ストレージアカウントから「データストレージ」-「コンテナー」を選択してpdf-inputをクリックします。

デプロイしたアプリケーションを確認

[アップロード]をクリックして右側のペインに表示される[ファイルの参照]をクリックしてアップロードするPDFファイルを選択します。最後に[アップロード]をクリックします。

デプロイしたアプリケーションを確認

PNGファイルの出力先BLOBコンテナであるimage-outputを確認します。以下のようにファイル名のディレクトリが作成され、その配下にPDFドキュメントの各ページを変換したPNGファイルが作成されます。

デプロイしたアプリケーションを確認
デプロイしたアプリケーションを確認

さいごに

本記事では、Azure FunctionsとDioDocs for PDF/Imagingを組み合わせたPDFファイルのアップロードを起点としたサーバーレスな画像処理の実装方法を紹介しました。クラウドネイティブなドキュメント処理サービスをシンプルに構築できます。

弊社Webサイトでは、製品の機能を気軽に試せるデモアプリケーションやトライアル版も公開していますので、こちらもご確認いただければと思います。

また、ご導入前の製品に関するご相談やご導入後の各種サービスに関するご質問など、お気軽にお問合せください。

\  この記事をシェアする  /