はじめに

企業はしばしば、何千ものファイル(契約書、プレゼンテーション、請求書など)に対して、1 回の操作でブランド付与や保護を行う必要があります。手作業で行う場合、各ドキュメントを開き、ロゴや機密性通知を挿入し、再度保存しなければなりません。プロセスは時間がかかるだけでなく、人為的ミスが発生しやすく、重複した透かしや抜け漏れのリスクが生じます。

GroupDocs.Watermark for .NET は、PDF、DOCX、PPTX、XLSX および一般的な画像形式に対応した統一 API でこの問題を解決します。サンプルプロジェクトには 4 種類のドキュメント(DOCX、PDF、XLSX、PPTX) が同梱されており、すべてのパイプラインモードを実際のフォーマットでテストできます。このチュートリアルでは、次の バッチ透かしパイプライン を順に解説します。

  1. ライセンスをロード(または評価モードにフォールバック)。
  2. フォルダーをスキャンし、ライブラリが処理できる形式だけをフィルタリング。
  3. すべてのドキュメントにタイル状の テキスト 透かしを適用。
  4. カスタム不透明度と回転角を持つタイル状の ロゴ 透かしを適用。
  5. 既に透かしが存在する場合は追加しない(冪等処理)。
  6. 古い会社ロゴを新しいロゴに置換

最後まで実行すれば、任意の .NET プロジェクトにすぐ組み込めるソリューションが完成します。

バッチ透かしが重要な理由

  • スケーラビリティ – 1 つのループで数十から数千のファイルを処理。
  • 一貫性 – すべてのドキュメントに同じビジュアルスタイルが適用され、ブランドのブレを防止。
  • 安全性 – 冪等ロジックにより、パイプラインを再実行しても透かしが重複しない。
  • 将来への備え – ロゴ置換コードにより、手作業でファイルを開かずにリブランディングが可能。

前提条件

  • .NET 6.0 以降。
  • GroupDocs.Watermark NuGet パッケージ (dotnet add package GroupDocs.Watermark)。
  • ライセンス ファイル(一時的または永続的)。ファイルが無い場合は評価モードで動作します。
  • ディスク上の 2 つのフォルダー:
    • InputFolder – ソース ドキュメントを格納。
    • OutputFolder – 透かしを付与したコピーを書き出す先。

手順 1 – ライセンスのロード

ライブラリは評価制限なしで実行するためにライセンスが必要です。以下のスニペットはライセンス ファイルのロードを試み、見つからなければ静かにフォールバックします。

try
{
    var license = new License();
    license.SetLicense(LicensePath);
    Console.WriteLine("License applied successfully.");
}
catch
{
    Console.WriteLine("Warning: License not found. Running in evaluation mode.");
}

重要ポイント:LicensePath 変数は .lic ファイルを指すように設定してください。ファイルが無い場合でもコードは続行されるため、手軽にテストできます。


手順 2 – 対応ファイルの検出

GroupDocs.Watermark が処理できるのは特定の拡張子だけです。以下のヘルパーはフォルダーを走査し、FileType.GetSupportedFileTypes() で取得した拡張子集合をハッシュセットに変換して、該当するファイルのみを返します。

if (!Directory.Exists(folderPath))
{
    Console.WriteLine($"Folder not found: {folderPath}");
    return new List<string>();
}

var supportedExtensions = FileType.GetSupportedFileTypes()
    .Select(ft => ft.Extension.ToLowerInvariant())
    .ToHashSet();

var supportedFiles = Directory.GetFiles(folderPath, "*.*", SearchOption.AllDirectories)
    .Where(f => supportedExtensions.Contains(Path.GetExtension(f).ToLowerInvariant()))
    .ToList();

Console.WriteLine($"Found {supportedFiles.Count} supported file(s) in '{folderPath}'.");
return supportedFiles;

重要ポイント: このメソッドにより、後続の透かし処理ループで未対応形式に遭遇し、実行時例外が発生することを防げます。


手順 3 – タイル状テキスト透かしの適用

以下のコードは赤色・半透明の 「CONFIDENTIAL」 透かしを作成し、‑30° 回転させて TileOptions でページ全体にタイル配置します。

Directory.CreateDirectory(outputFolder);
int processed = 0, failed = 0;

foreach (var filePath in files)
{
    try
    {
        using var watermarker = new Watermarker(filePath);
        var watermark = new TextWatermark(watermarkText,
            new Font("Arial", 19, FontStyle.Bold))
        {
            ForegroundColor = Color.Red,
            Opacity = 0.3,
            RotateAngle = -30,
            TileOptions = new TileOptions
            {
                LineSpacing = new MeasureValue
                {
                    MeasureType = TileMeasureType.Percent,
                    Value = 12
                },
                WatermarkSpacing = new MeasureValue
                {
                    MeasureType = TileMeasureType.Percent,
                    Value = 10
                }
            }
        };

        watermarker.Add(watermark);
        var outPath = Path.Combine(outputFolder, Path.GetFileName(filePath));
        watermarker.Save(outPath);
        processed++;
        Console.WriteLine($"[OK] {Path.GetFileName(filePath)}");
    }
    catch (Exception ex)
    {
        failed++;
        Console.WriteLine($"[FAIL] {Path.GetFileName(filePath)}: {ex.Message}");
    }
}

Console.WriteLine($"Text watermark: {processed} processed, {failed} failed");

重要ポイント:

  • TileOptions がレンガ状パターンを生成し、透かしを除去しにくくします。
  • Watermarker がフォーマットを抽象化しているため、PDF、Word、スプレッドシート、画像すべてで同一コードが機能します。

テキスト透かしが適用されたドキュメント


手順 4 – タイル状ロゴ透かしの適用

テキスト透かしの代わりに画像でブランドマークを入れたい場合は、以下のコードでロゴ画像をタイル配置します。ロゴファイルの存在を確認し、40 % の不透明度と ‑30° の回転でタイル化します。

if (!File.Exists(logoPath))
{
    Console.WriteLine($"Logo not found: {logoPath}. Skipping image mode.");
    return;
}

Directory.CreateDirectory(outputFolder);
int processed = 0, failed = 0;

foreach (var filePath in files)
{
    try
    {
        using var watermarker = new Watermarker(filePath);
        using var watermark = new ImageWatermark(logoPath)
        {
            Opacity = 0.4,
            RotateAngle = -30,
            TileOptions = new TileOptions
            {
                LineSpacing = new MeasureValue
                {
                    MeasureType = TileMeasureType.Percent,
                    Value = 15
                },
                WatermarkSpacing = new MeasureValue
                {
                    MeasureType = TileMeasureType.Percent,
                    Value = 12
                }
            }
        };

        watermarker.Add(watermark);
        var outPath = Path.Combine(outputFolder, Path.GetFileName(filePath));
        watermarker.Save(outPath);
        processed++;
        Console.WriteLine($"[OK] {Path.GetFileName(filePath)} - logo applied");
    }
    catch (Exception ex)
    {
        failed++;
        Console.WriteLine($"[FAIL] {Path.GetFileName(filePath)}: {ex.Message}");
    }
}

Console.WriteLine($"Logo watermark: {processed} processed, {failed} failed");

重要ポイント:

  • テキスト用に使用した TileOptions が画像にもそのまま適用でき、ページ全体で統一感のある見た目になります。
  • Opacity を調整すれば、コンテンツの可読性を保ちつつブランドを表示できます。

タイル状ロゴ透かしが適用されたドキュメント


手順 5 – 冪等透かし (既存透かしはスキップ)

パイプラインを複数回実行しても透かしが重なることはありません。このスニペットは、透かしテキストが 正確に 既に存在するかどうかを検索し、存在すれば追加をスキップします。

Directory.CreateDirectory(outputFolder);
int applied = 0, skipped = 0, failed = 0;

foreach (var filePath in files)
{
    try
    {
        using var watermarker = new Watermarker(filePath);
        var criteria = new TextSearchCriteria(watermarkText, false);
        var existing = watermarker.Search(criteria);

        if (existing.Count > 0)
        {
            skipped++;
            Console.WriteLine($"[SKIP] {Path.GetFileName(filePath)} – already contains watermark");
            continue;
        }

        var watermark = new TextWatermark(watermarkText,
            new Font("Arial", 19, FontStyle.Bold))
        {
            ForegroundColor = Color.Red,
            Opacity = 0.3,
            RotateAngle = -30,
            TileOptions = new TileOptions
            {
                LineSpacing = new MeasureValue
                {
                    MeasureType = TileMeasureType.Percent,
                    Value = 12
                },
                WatermarkSpacing = new MeasureValue
                {
                    MeasureType = TileMeasureType.Percent,
                    Value = 10
                }
            }
        };

        watermarker.Add(watermark);
        var outPath = Path.Combine(outputFolder, Path.GetFileName(filePath));
        watermarker.Save(outPath);
        applied++;
        Console.WriteLine($"[OK] {Path.GetFileName(filePath)} – watermark applied");
    }
    catch (Exception ex)
    {
        failed++;
        Console.WriteLine($"[FAIL] {Path.GetFileName(filePath)}: {ex.Message}");
    }
}

Console.WriteLine($"Smart batch: {applied} applied, {skipped} skipped, {failed} failed");

重要ポイント:TextSearchCriteria の第2引数に false を渡すことで大文字小文字を区別せず検索し、正確に同じ 透かしがある場合だけスキップします。


手順 6 – フォルダー内の古いロゴを置換

会社がリブランディングした際、古いロゴをすべて新しいロゴに差し替える必要があります。以下のコードは、DCT‑ハッシュとカラー ヒストグラムという 2 つの画像検索戦略を組み合わせ、マッチした画像データを新ロゴに上書きします。

if (!File.Exists(oldLogoPath) || !File.Exists(newLogoPath))
{
    Console.WriteLine("Old or new logo file missing – aborting replacement.");
    return;
}

Directory.CreateDirectory(outputFolder);
byte[] newLogoData = File.ReadAllBytes(newLogoPath);
int replaced = 0, notFound = 0;

var settings = new WatermarkerSettings
{
    SearchableObjects = new SearchableObjects
    {
        PdfSearchableObjects = PdfSearchableObjects.All
    }
};

foreach (var filePath in files)
{
    try
    {
        using var watermarker = new Watermarker(filePath, settings);
        var dct = new ImageDctHashSearchCriteria(oldLogoPath) { MaxDifference = 0.4 };
        var hist = new ImageColorHistogramSearchCriteria(oldLogoPath) { MaxDifference = 0.5 };
        var criteria = dct.Or(hist);
        var found = watermarker.Search(criteria);

        if (found.Count == 0)
        {
            notFound++;
            Console.WriteLine($"[--] {Path.GetFileName(filePath)} – old logo not found");
            continue;
        }

        foreach (PossibleWatermark wm in found)
        {
            try
            {
                wm.ImageData = newLogoData;
            }
            catch
            {
                // Some watermark types cannot be overwritten – ignore.
            }
        }

        var outPath = Path.Combine(outputFolder, Path.GetFileName(filePath));
        watermarker.Save(outPath);
        replaced++;
        Console.WriteLine($"[OK] {Path.GetFileName(filePath)} – {found.Count} logo(s) replaced");
    }
    catch (Exception ex)
    {
        Console.WriteLine($"[FAIL] {Path.GetFileName(filePath)}: {ex.Message}");
    }
}

Console.WriteLine($"Logo replacement: {replaced} updated, {notFound} without old logo");

重要ポイント:

  • WatermarkerSettingsPdfSearchableObjects.All により、PDF のアーティファクトとして埋め込まれたロゴも検索対象になります。
  • DCT‑ハッシュとカラー ヒストグラムの組み合わせで、Office のベクターロゴと PDF のラスタライズ版の両方を捕捉できます。

古いロゴが新しいロゴに置換された様子


ベストプラクティスとヒント

  • 出力フォルダーは一度だけ作成 (Directory.CreateDirectory) – このメソッドは冪等で、競合状態を防げます。
  • 進捗をログ出力 – 各ステップのコンソール出力により、どのファイルが成功・失敗したかが一目で分かります。
  • OpacityRotateAngle をブランドガイドラインに合わせて調整; 0.3〜0.5 の範囲が視認性と侵入度のバランスとして一般的です。
  • 冪等スマートバッチ を定期ジョブ(例: 夜間のブランド更新)に活用。
  • ロゴ置換はまず小規模サンプルでテスト し、検索条件が適切に調整されていることを確認してから全リポジトリに適用。

よくある問題のトラブルシューティング

症状 考えられる原因 対策
ファイルが一切処理されない ScanFolderForSupportedFiles が空リストを返した InputFolder のパスと、フォルダー内にサポート対象形式(PDF、DOCX、PPTX、XLSX、PNG、JPG など)があるか確認
透かしが見えない 不透明度が低すぎる、または背景と色が混ざっている Opacity を上げる(例: 0.5)か、ForegroundColor を対照的な色に変更
PDF のロゴが置換されない ロゴがコンテンツストリームの描画演算子として埋め込まれており検索不可 ロゴを追加する際に PdfArtifactWatermarkOptions を使用し、検索可能なアーティファクトにする
Linux で System.Drawing.Common の例外が出る ネイティブ GDI+ ライブラリが欠如 ターゲット Linux マシンに libgdiplus をインストールするか、.csproj に Unix サポートを有効化 (<RuntimeHostConfigurationOption Include="System.Drawing.EnableUnixSupport" Value="true" />)

結論

これで 完全な本番レベルのパイプライン が完成しました。以下が実現できます。

  • ライセンスの適用
  • 対応ドキュメントの自動検出
  • タイル状テキストまたはロゴ透かしの適用
  • 冪等処理で重複透かしを防止
  • フォルダー全体の古いロゴ置換

これらの部品は自由に組み合わせて、.NET 環境でのあらゆるブランディング・文書保護ワークフローに活用できます。

追加リソース