Introduction

Підприємствам часто потрібно брендувати або захищати тисячі файлів – контракти, презентації, рахунки – в одній операції. Робити це вручну означає відкривати кожний документ, вставляти логотип або повідомлення про конфіденційність і зберігати його знову. Це не лише забирає багато часу, а й створює ризик людської помилки та дублікатів водяних знаків або пропущених файлів.

GroupDocs.Watermark for .NET вирішує проблему за допомогою уніфікованого API, який працює з PDF, DOCX, PPTX, XLSX та поширеними форматами зображень. Прикладний проєкт постачається з чотирма типами документів (DOCX, PDF, XLSX, PPTX), тому кожен режим конвеєра працює з реальними форматами. У цьому посібнику ми пройдемо повний конвеєр пакетного водяного знака, який:

  1. Завантажує ліцензію (або переходить у режим оцінки).
  2. Сканує папку і фільтрує лише формати, які підтримує бібліотека.
  3. Накладає плитковий текстовий водяний знак на кожен документ.
  4. Накладає плитковий логотип з налаштованою прозорістю та обертанням.
  5. Додає водяний знак тільки коли його ще немає (ідемпотентна обробка).
  6. Знаходить і замінює застарілий логотип компанії новим.

Після завершення у вас буде готове рішення, яке можна вставити в будь‑який .NET‑проєкт.

Why Batch Watermarking Matters

  • Масштабованість – обробка десятків або тисяч файлів у одному циклі.
  • Послідовність – один і той самий візуальний стиль застосовується до кожного документа, усуваючи розбіжності бренду.
  • Безпека – ідемпотентна логіка запобігає дублюванню водяних знаків при повторному запуску конвеєра.
  • Підготовка до майбутнього – код заміни логотипу дозволяє провести ребрендинг без ручного редагування кожного файлу.

Prerequisites

  • .NET 6.0 або новіша версія.
  • GroupDocs.Watermark пакет NuGet (dotnet add package GroupDocs.Watermark).
  • Файл ліцензії (тимчасовий або постійний). Приклади працюють у режимі оцінки, якщо файл відсутній.
  • Дві папки на диску:
    • InputFolder – містить вихідні документи.
    • OutputFolder – куди будуть записані копії з водяними знаками.

Step 1 – Load the License

Бібліотека вимагає ліцензію для роботи без обмежень оцінки. Нижче наведений фрагмент коду намагається завантажити файл ліцензії і тихо переходить у режим оцінки, якщо файл не знайдено.

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

Key point: Змінна LicensePath повинна вказувати на ваш файл .lic. Якщо файл відсутній, код продовжує виконання, що зручно для швидкого тестування.


Step 2 – Discover Supported Files

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;

Key point: Метод гарантує, що подальші цикли водяного знака ніколи не зіткнуться з непідтримуваним форматом, що могло б викликати виключення під час виконання.


Step 3 – Apply a Tiled Text Watermark

Наступний код створює червоний, напівпрозорий «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");

Key points:

  • TileOptions створює шаблон у вигляді цеглин, що ускладнює видалення водяного знака без пошкодження вмісту.
  • Той самий фрагмент працює з PDF, Word, електронними таблицями та зображеннями, оскільки Watermarker абстрагує формат.

Text watermark applied to a document


Step 4 – Apply a Tiled Logo Watermark

Якщо ви віддаєте перевагу візуальному бренду, замініть текстовий водяний знак на зображення. Нижче код перевіряє наявність файлу логотипу, а потім розташовує його плиткою з прозорістю 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");

Key points:

  • Така ж логіка TileOptions, що використовується для тексту, працює і для зображень, забезпечуючи однорідний вигляд у всіх сторінках.
  • Opacity дозволяє залишити вміст читабельним, одночасно демонструючи бренд.

Tiled logo watermark applied to a document


Step 5 – Idempotent Watermarking (Skip Existing Marks)

Запуск конвеєра кілька разів не повинен накладати водяні знаки один на одного. Цей фрагмент шукає точну копію тексту водяного знака перед додаванням нового.

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

Key point:TextSearchCriteria з параметром false (чутливість до регістру) гарантує, що ми пропускаємо лише ті документи, які вже містять точно той водяний знак, який плануємо додати.


Step 6 – Replace an Outdated Logo Across the Folder

Коли компанія змінює бренд, може знадобитися замінити кожен старий логотип новим. Код комбінує два підходи пошуку зображень – DCT‑hash для точності та колірну гістограму для допуску – а потім перезаписує дані зображення у кожному знайденому випадку.

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

Key points:

  • WatermarkerSettings з PdfSearchableObjects.All дозволяє пошукати логотипи, збережені у PDF як артефакти.
  • Комбінація критеріїв DCT‑hash і колірної гістограми охоплює як точні векторні логотипи (Office), так і растрові версії (PDF).

Old logo replaced with new logo


Best Practices & Tips

  • Створюйте папку виводу один раз (Directory.CreateDirectory) – метод ідемпотентний і запобігає стану гонки.
  • Логуйте прогрес – вивід у консоль на кожному кроці полегшує відстеження успішних та невдалих файлів.
  • Налаштуйте Opacity та RotateAngle згідно з вимогами бренду; значення між 0.3–0.5 зазвичай достатньо, щоб бути помітним, але не нав’язливим.
  • Використовуйте ідемпотентний «smart batch» для будь‑якої повторюваної задачі (наприклад, нічних оновлень бренду).
  • Тестуйте заміну логотипу на невеликій вибірці перед запуском на всій колекції, щоб переконатися у правильності налаштувань пошуку.

Troubleshooting Common Issues

Symptom Likely Cause Fix
Не обробляються файли ScanFolderForSupportedFiles повернув порожній список Перевірте шлях InputFolder і чи містить папка підтримувані формати (PDF, DOCX, PPTX, XLSX, PNG, JPG тощо)
Водяний знак не видно Прозорість встановлена занадто низько або колір зливається з фоном Збільшіть Opacity (наприклад, 0.5) або змініть ForegroundColor на контрастний колір
Логотипи у PDF не знайдено під час заміни Логотипи додані як оператори потоку вмісту (не підлягають пошуку) При додаванні логотипів використовуйте PdfArtifactWatermarkOptions, щоб вони стали пошуковими артефактами
Виключення System.Drawing.Common у Linux Відсутні нативні бібліотеки GDI+ Встановіть libgdiplus на цільовій Linux‑машині або увімкніть підтримку Unix у .csproj (<RuntimeHostConfigurationOption Include="System.Drawing.EnableUnixSupport" Value="true" />).

Conclusion

Тепер у вас є повний, готовий до продакшну конвеєр, який може:

  • Ліцензувати бібліотеку.
  • Автоматично виявляти підтримувані документи.
  • Накладати плиткові текстові або логотипні водяні знаки.
  • Безпечно працювати багаторазово без створення дублікатів.
  • Замінювати старий корпоративний логотип у всій папці.

Ці будівельні блоки можна комбінувати та адаптувати під будь‑який процес брендування або захисту документів у .NET.

Additional Resources