Introduction

Les entreprises ont souvent besoin de marquer ou de protéger des milliers de fichiers – contrats, présentations, factures – en une seule opération. Le faire manuellement signifie ouvrir chaque document, insérer un logo ou un avis de confidentialité, puis le sauvegarder à nouveau. Non seulement le processus est chronophage, il introduit des erreurs humaines et crée le risque de filigranes en double ou de fichiers oubliés.

GroupDocs.Watermark for .NET résout ce problème avec une API unifiée qui fonctionne sur PDF, DOCX, PPTX, XLSX et les formats d’image courants. Le projet d’exemple comprend quatre types de documents (DOCX, PDF, XLSX, PPTX) afin que chaque mode de pipeline s’exécute sur des formats réels. Dans ce tutoriel, nous parcourrons un pipeline complet de filigrane par lots qui :

  1. Charge une licence (ou revient en mode d’évaluation).
  2. Parcourt un dossier et ne retient que les formats pris en charge par la bibliothèque.
  3. Applique un filigrane texte en mosaïque à chaque document.
  4. Applique un filigrane logo en mosaïque avec une opacité et une rotation personnalisées.
  5. Ajoute un filigrane uniquement lorsqu’il n’est pas déjà présent (traitement idempotent).
  6. Recherche et remplace un logo d’entreprise obsolète par un nouveau.

À la fin, vous disposerez d’une solution prête à l’emploi qui peut être intégrée à n’importe quel projet .NET.

Why Batch Watermarking Matters

  • Scalabilité – Traitez des dizaines ou des milliers de fichiers avec une seule boucle.
  • Cohérence – Le même style visuel est appliqué à chaque document, éliminant les dérives de marque.
  • Sécurité – La logique idempotente empêche les filigranes en double lorsque le pipeline est relancé.
  • Préparation au futur – Le code de remplacement de logo vous permet de lancer une refonte de marque sans toucher chaque fichier manuellement.

Prerequisites

  • .NET 6.0 ou version ultérieure.
  • GroupDocs.Watermark package NuGet (dotnet add package GroupDocs.Watermark).
  • Un fichier de licence (temporaire ou permanent). Les exemples fonctionnent en mode d’évaluation si le fichier est absent.
  • Deux dossiers sur le disque :
    • InputFolder – contient les documents source.
    • OutputFolder – où les copies filigranées seront écrites.

Step 1 – Load the License

La bibliothèque nécessite une licence pour fonctionner sans les limites d’évaluation. L’extrait ci‑dessous tente de charger un fichier de licence et revient silencieusement en mode d’évaluation si le fichier est introuvable.

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

Point clé : La variable LicensePath doit pointer vers votre fichier .lic. Si le fichier manque, le code continue, ce qui est pratique pour les tests rapides.


Step 2 – Discover Supported Files

GroupDocs.Watermark ne peut traiter qu’un ensemble précis d’extensions. L’assistant ci‑dessous parcourt un dossier, construit un ensemble de hachage des extensions prises en charge via FileType.GetSupportedFileTypes(), et ne renvoie que les fichiers correspondants.

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;

Point clé : La méthode garantit que les boucles de filigrane ultérieures ne rencontreront jamais un format non pris en charge, ce qui éviterait autrement une exception d’exécution.


Step 3 – Apply a Tiled Text Watermark

Le code suivant crée un filigrane « CONFIDENTIAL » rouge, semi‑transparent, le fait pivoter de ‑30° et le répète sur chaque page à l’aide de 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");

Points clés :

  • TileOptions crée un motif en briques, rendant le filigrane difficile à supprimer sans altérer le contenu sous‑jacent.
  • Le même extrait fonctionne pour les PDF, les fichiers Word, les feuilles de calcul et les images parce que Watermarker abstrait le format.

Filigrane texte appliqué à un document


Step 4 – Apply a Tiled Logo Watermark

Si vous préférez une marque visuelle, remplacez le filigrane texte par une image. Le code ci‑dessous vérifie que le fichier logo existe, puis le répète avec une opacité de 40 % et une rotation de ‑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");

Points clés :

  • La même logique TileOptions utilisée pour le texte fonctionne pour les images, offrant un rendu cohérent sur toutes les pages.
  • Opacity permet au contenu sous‑jacent de rester lisible tout en affichant la marque.

Filigrane logo en mosaïque appliqué à un document


Step 5 – Idempotent Watermarking (Skip Existing Marks)

Exécuter le pipeline plusieurs fois ne doit pas empiler les filigranes les uns sur les autres. Cet extrait recherche une instance exacte du texte du filigrane avant d’en ajouter un nouveau.

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");

Point clé :TextSearchCriteria avec false pour la sensibilité à la casse garantit que nous ne sautons que les documents contenant exactement le filigrane que nous prévoyons d’ajouter.


Step 6 – Replace an Outdated Logo Across the Folder

Lorsqu’une entreprise change de logo, il peut être nécessaire d’échanger chaque ancien logo par le nouveau. Le code combine deux stratégies de recherche d’image – hash DCT pour la précision et histogramme de couleur pour la tolérance – puis écrase les données d’image de chaque correspondance.

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");

Points clés :

  • WatermarkerSettings avec PdfSearchableObjects.All rend la recherche capable de voir les logos stockés comme artefacts PDF.
  • La combinaison des critères DCT‑hash et histogramme de couleur capture à la fois les logos vectoriels exacts (Office) et les versions rasterisées (PDF).

Ancien logo remplacé par le nouveau logo


Best Practices & Tips

  • Créez le dossier de sortie une seule fois (Directory.CreateDirectory) – la méthode est idempotente et évite les conditions de concurrence.
  • Consignez la progression – la sortie console dans chaque étape facilite le suivi des fichiers réussis ou échoués.
  • Ajustez Opacity et RotateAngle selon les directives de votre marque ; une valeur entre 0,3 et 0,5 est généralement suffisante pour être visible sans être intrusive.
  • Utilisez le batch intelligent idempotent pour toute tâche récurrente (par ex., mises à jour de marque nocturnes).
  • Testez le remplacement de logo sur un petit échantillon avant de l’appliquer à l’ensemble du référentiel afin de vous assurer que les critères de recherche sont correctement réglés.

Troubleshooting Common Issues

Symptom Likely Cause Fix
No files are processed ScanFolderForSupportedFiles returned an empty list Verify InputFolder path and that the folder contains supported formats (PDF, DOCX, PPTX, XLSX, PNG, JPG, etc.)
Watermark not visible Opacity set too low or colour blends with background Increase Opacity (e.g., 0.5) or switch ForegroundColor to a contrasting hue
PDF logos not found during replacement Logos added as content‑stream draw operators (not searchable) When seeding logos, add them with PdfArtifactWatermarkOptions so they become searchable artifacts
Exception System.Drawing.Common on Linux Missing native GDI+ libraries Install libgdiplus on the target Linux machine or enable Unix support in the .csproj (<RuntimeHostConfigurationOption Include="System.Drawing.EnableUnixSupport" Value="true" />).

Conclusion

You now have a complete, production‑ready pipeline that can:

  • License the library.
  • Detect supported documents automatically.
  • Apply tiled text or logo watermarks.
  • Run safely multiple times without creating duplicates.
  • Replace an old corporate logo across an entire folder.

These building blocks can be mixed and matched to fit any branding or document‑protection workflow in .NET.

Additional Resources