Einführung
Unternehmen müssen häufig Tausende von Dateien – Verträge, Präsentationen, Rechnungen – in einem einzigen Durchlauf branden oder schützen. Das manuelle Vorgehen bedeutet, jedes Dokument zu öffnen, ein Logo oder einen Vertraulichkeitsvermerk einzufügen und es erneut zu speichern. Das ist nicht nur zeitaufwendig, sondern führt zu menschlichen Fehlern und birgt das Risiko doppelter Wasserzeichen oder ausgelassener Dateien.
GroupDocs.Watermark für .NET löst das Problem mit einer einheitlichen API, die über PDF, DOCX, PPTX, XLSX und gängige Bildformate hinweg funktioniert. Das Beispielprojekt enthält vier Dokumenttypen (DOCX, PDF, XLSX, PPTX), sodass jeder Pipeline‑Modus an realen Formaten getestet wird. In diesem Tutorial führen wir Sie durch eine vollständige Batch‑Wasserzeichen‑Pipeline, die:
- Eine Lizenz lädt (oder in den Evaluierungs‑Modus wechselt).
- Einen Ordner scannt und nur die Formate filtert, die die Bibliothek verarbeiten kann.
- Ein gekacheltes **Text‑**Wasserzeichen auf jedes Dokument anwendet.
- Ein gekacheltes **Logo‑**Wasserzeichen mit benutzerdefinierter Deckkraft und Drehung anwendet.
- Ein Wasserzeichen nur hinzufügt, wenn es noch nicht vorhanden ist (idempotente Verarbeitung).
- Ein veraltetes Firmenlogo findet und ersetzt.
Am Ende haben Sie eine einsatzbereite Lösung, die in jedes .NET‑Projekt eingebunden werden kann.
Warum Batch‑Wasserzeichen wichtig ist
- Skalierbarkeit – Verarbeiten Sie Dutzende oder Tausende von Dateien mit einer einzigen Schleife.
- Konsistenz – Der gleiche visuelle Stil wird auf jedes Dokument angewendet, wodurch Marken‑Drift vermieden wird.
- Sicherheit – Idempotente Logik verhindert doppelte Wasserzeichen, wenn die Pipeline erneut ausgeführt wird.
- Zukunftssicherheit – Der Logo‑Ersetzungscode ermöglicht ein Re‑Branding, ohne jede Datei manuell zu bearbeiten.
Voraussetzungen
- .NET 6.0 oder höher.
- GroupDocs.Watermark NuGet‑Paket (
dotnet add package GroupDocs.Watermark). - Eine Lizenzdatei (temporär oder permanent). Die Beispiele funktionieren im Evaluierungs‑Modus, wenn die Datei fehlt.
- Zwei Ordner auf dem Datenträger:
InputFolder– enthält die Quell‑Dokumente.OutputFolder– hier werden die wassergezeichneten Kopien abgelegt.
Schritt 1 – Lizenz laden
Die Bibliothek benötigt eine Lizenz, um ohne Evaluierungs‑Beschränkungen zu laufen. Das nachfolgende Snippet versucht, eine Lizenzdatei zu laden, und fällt bei Nicht‑Vorhandensein stillschweigend zurück.
try
{
var license = new License();
license.SetLicense(LicensePath);
Console.WriteLine("License applied successfully.");
}
catch
{
Console.WriteLine("Warning: License not found. Running in evaluation mode.");
}
Wichtiger Hinweis: Die Variable LicensePath muss auf Ihre .lic‑Datei zeigen. Fehlt die Datei, läuft der Code weiter – praktisch für schnelle Tests.
Schritt 2 – Unterstützte Dateien ermitteln
GroupDocs.Watermark kann nur eine bestimmte Menge an Dateierweiterungen verarbeiten. Der Hilfs‑Code scannt einen Ordner, erstellt ein Hash‑Set der unterstützten Erweiterungen über FileType.GetSupportedFileTypes() und gibt nur die passenden Dateien zurück.
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;
Wichtiger Hinweis: Die Methode stellt sicher, dass die nachfolgenden Wasserzeichen‑Schleifen niemals auf ein nicht unterstütztes Format treffen, was sonst eine Laufzeit‑Exception auslösen würde.
Schritt 3 – Gekacheltes Text‑Wasserzeichen anwenden
Der folgende Code erzeugt ein rotes, halbtransparentes „CONFIDENTIAL“‑Wasserzeichen, dreht es um ‑30° und kachelt es über jede Seite hinweg mittels 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");
Wichtige Punkte:
TileOptionserzeugt ein ziegelartiges Muster, das das Wasserzeichen schwer zu entfernen macht, ohne den zugrunde liegenden Inhalt zu beeinträchtigen.- Das gleiche Snippet funktioniert für PDFs, Word‑Dateien, Tabellen und Bilder, weil
Watermarkerdas Format abstrahiert.
Schritt 4 – Gekacheltes Logo‑Wasserzeichen anwenden
Wenn Sie ein visuelles Markenzeichen bevorzugen, ersetzen Sie das Text‑Wasserzeichen durch ein Bild. Der Code prüft, ob die Logodatei existiert, und kachelt sie mit 40 % Deckkraft und ‑30° Drehung.
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");
Wichtige Punkte:
- Die gleiche
TileOptions‑Logik wie beim Text funktioniert auch für Bilder und sorgt für ein einheitliches Erscheinungsbild über alle Seiten hinweg. Opacitylässt den zugrunde liegenden Inhalt lesbar, während das Markenlogo sichtbar bleibt.
Schritt 5 – Idempotentes Wasserzeichen (vorhandene Markierungen überspringen)
Mehrmaliges Ausführen der Pipeline sollte keine Wasserzeichen‑Schichten übereinander legen. Dieses Snippet sucht nach einer exakten Instanz des Wasserzeichen‑Texts, bevor ein neues hinzugefügt wird.
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");
Wichtiger Hinweis: TextSearchCriteria mit false für die Groß‑/Kleinschreibung stellt sicher, dass nur Dokumente übersprungen werden, die bereits das exakt zu setzende Wasserzeichen enthalten.
Schritt 6 – Veraltetes Logo im gesamten Ordner ersetzen
Bei einem Re‑Branding müssen Sie möglicherweise jedes alte Logo durch das neue austauschen. Der Code kombiniert zwei Bild‑Suchstrategien – DCT‑Hash für Präzision und Farb‑Histogramm für Toleranz – und überschreibt die Bilddaten jedes Treffers.
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");
Wichtige Punkte:
WatermarkerSettingsmitPdfSearchableObjects.Allermöglicht das Auffinden von Logos, die als PDF‑Artefakte gespeichert sind.- Die Kombination aus DCT‑Hash‑ und Farb‑Histogramm‑Kriterien erfasst sowohl exakte Vektor‑Logos (Office) als auch gerasterte Varianten (PDF).
bewährte Vorgehensweisen & Tipps
- Ausgabeordner nur einmal erstellen (
Directory.CreateDirectory) – die Methode ist idempotent und verhindert Race‑Conditions. - Fortschritt protokollieren – die Konsolenausgaben in jedem Schritt zeigen sofort, welche Dateien erfolgreich waren und welche fehlgeschlagen sind.
OpacityundRotateAnglean Markenrichtlinien anpassen; ein Wert zwischen 0,3 – 0,5 ist meist gut sichtbar, aber nicht aufdringlich.- Die idempotente Smart‑Batch‑Variante für wiederkehrende Jobs nutzen (z. B. nächtliche Branding‑Updates).
- Logo‑Ersetzung zuerst an einer kleinen Stichprobe testen, bevor Sie das gesamte Repository bearbeiten, um sicherzustellen, dass die Suchkriterien korrekt abgestimmt sind.
Fehlersuche bei häufigen Problemen
| Symptom | Wahrscheinliche Ursache | Lösung |
|---|---|---|
| Es werden keine Dateien verarbeitet | ScanFolderForSupportedFiles liefert eine leere Liste |
Pfad zu InputFolder prüfen und sicherstellen, dass unterstützte Formate (PDF, DOCX, PPTX, XLSX, PNG, JPG usw.) vorhanden sind |
| Wasserzeichen ist nicht sichtbar | Deckkraft zu niedrig oder Farbe verschmilzt mit Hintergrund | Opacity erhöhen (z. B. 0,5) oder ForegroundColor zu einer kontrastreichen Farbe ändern |
| PDF‑Logos werden bei der Ersetzung nicht gefunden | Logos wurden als Content‑Stream‑Draw‑Operatoren eingefügt (nicht durchsuchbar) | Beim Einfügen von Logos PdfArtifactWatermarkOptions verwenden, damit sie durchsuchbare Artefakte werden |
Ausnahme System.Drawing.Common unter Linux |
Fehlende native GDI+‑Bibliotheken | Auf dem Ziel‑Linux‑System libgdiplus installieren oder Unix‑Support im .csproj aktivieren (<RuntimeHostConfigurationOption Include="System.Drawing.EnableUnixSupport" Value="true" />). |
Fazit
Sie verfügen nun über eine vollständige, produktionsreife Pipeline, die:
- Die Bibliothek lizenziert.
- Unterstützte Dokumente automatisch erkennt.
- Kachel‑Text‑ oder Logo‑Wasserzeichen anwendet.
- Sicher mehrfach ausgeführt werden kann, ohne Duplikate zu erzeugen.
- Ein altes Firmenlogo in einem gesamten Ordner ersetzt.
Diese Bausteine lassen sich beliebig kombinieren und passen in jede Branding‑ oder Dokument‑Schutz‑Workflow‑Umgebung in .NET.