Pendahuluan

Perusahaan sering kali perlu menandai atau melindungi ribuan file – kontrak, presentasi, faktur – dalam satu operasi. Melakukan ini secara manual berarti membuka setiap dokumen, menyisipkan logo atau catatan kerahasiaan, dan menyimpannya kembali. Tidak hanya prosesnya memakan waktu, tetapi juga memperkenalkan kesalahan manusia dan menimbulkan risiko tanda air duplikat atau file yang terlewat.

GroupDocs.Watermark for .NET menyelesaikan masalah ini dengan API terpadu yang bekerja pada PDF, DOCX, PPTX, XLSX, dan format gambar umum. Proyek contoh menyertakan empat jenis dokumen (DOCX, PDF, XLSX, PPTX) sehingga setiap mode pipeline dijalankan pada format dunia nyata. Dalam tutorial ini kami akan menelusuri pipeline tanda air batch lengkap yang:

  1. Memuat lisensi (atau kembali ke mode evaluasi).
  2. Memindai folder dan menyaring hanya format yang dapat diproses oleh perpustakaan.
  3. Menerapkan tanda air teks berulang pada setiap dokumen.
  4. Menerapkan tanda air logo berulang dengan opasitas dan rotasi khusus.
  5. Menambahkan tanda air hanya bila belum ada (pemrosesan idempoten).
  6. Menemukan dan mengganti logo perusahaan yang usang dengan yang baru.

Pada akhir tutorial Anda akan memiliki solusi siap‑jalankan yang dapat dimasukkan ke proyek .NET apa pun.

Mengapa Pencantuman Tanda Air Massal Penting

  • Skalabilitas – Memproses puluhan atau ribuan file dengan satu loop.
  • Konsistensi – Gaya visual yang sama diterapkan pada setiap dokumen, menghilangkan pergeseran merek.
  • Keamanan – Logika idempoten mencegah tanda air duplikat ketika pipeline dijalankan kembali.
  • Masa Depan – Kode penggantian logo memungkinkan Anda meluncurkan re‑branding tanpa menyentuh setiap file secara manual.

Prasyarat

  • .NET 6.0 atau lebih baru.
  • Paket NuGet GroupDocs.Watermark (dotnet add package GroupDocs.Watermark).
  • File lisensi (sementara atau permanen). Contoh bekerja dalam mode evaluasi jika file tidak ada.
  • Dua folder di disk:
    • InputFolder – berisi dokumen sumber.
    • OutputFolder – tempat salinan ber‑tanda air akan ditulis.

Langkah 1 – Memuat Lisensi

Perpustakaan memerlukan lisensi untuk berjalan tanpa batasan evaluasi. Potongan kode di bawah mencoba memuat file lisensi dan kembali diam jika file tidak ditemukan.

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

Poin penting: Variabel LicensePath harus mengarah ke file .lic Anda. Jika file tidak ada, kode tetap berjalan, yang berguna untuk pengujian cepat.


Langkah 2 – Menemukan File yang Didukung

GroupDocs.Watermark hanya dapat memproses sekumpulan ekstensi tertentu. Pembantu di bawah ini memindai folder, membangun himpunan hash ekstensi yang didukung melalui FileType.GetSupportedFileTypes(), dan mengembalikan hanya file yang cocok.

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;

Poin penting: Metode ini menjamin bahwa loop penandaan selanjutnya tidak pernah menemui format yang tidak didukung, yang sebaliknya akan menyebabkan pengecualian runtime.


Langkah 3 – Menerapkan Tanda Air Teks Berulang

Kode berikut membuat tanda air “CONFIDENTIAL” berwarna merah, semi‑transparan, memutar ‑30°, dan menatanya berulang di setiap halaman menggunakan 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");

Poin penting:

  • TileOptions menciptakan pola seperti bata, membuat tanda air sulit dihapus tanpa memengaruhi konten di bawahnya.
  • Potongan yang sama bekerja untuk PDF, file Word, spreadsheet, dan gambar karena Watermarker mengabstraksi format.

Tanda air teks diterapkan pada dokumen


Langkah 4 – Menerapkan Tanda Air Logo Berulang

Jika Anda lebih suka tanda visual, ganti tanda air teks dengan gambar. Kode di bawah memeriksa keberadaan file logo, lalu menatanya berulang dengan opasitas 40 % dan rotasi ‑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");

Poin penting:

  • Logika TileOptions yang sama untuk teks juga berlaku untuk gambar, memberikan tampilan konsisten di semua halaman.
  • Opacity memungkinkan konten di bawahnya tetap dapat dibaca sambil tetap menampilkan merek.

Tanda air logo berulang diterapkan pada dokumen


Langkah 5 – Penandaan Idempoten (Lewati Tanda yang Sudah Ada)

Menjalankan pipeline berulang kali tidak boleh menumpuk tanda air di atas satu sama lain. Potongan ini mencari instance tepat dari teks tanda air sebelum menambahkan yang baru.

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

Poin penting:TextSearchCriteria dengan nilai false untuk sensitivitas huruf memastikan kita hanya melewatkan dokumen yang sudah berisi teks tanda air persis yang ingin ditambahkan.


Langkah 6 – Mengganti Logo Usang di Seluruh Folder

Ketika perusahaan melakukan re‑branding, Anda mungkin perlu menukar setiap logo lama dengan yang baru. Kode ini menggabungkan dua strategi pencarian gambar – DCT‑hash untuk presisi dan histogram warna untuk toleransi – lalu menimpa data gambar pada setiap temuan.

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

Poin penting:

  • WatermarkerSettings dengan PdfSearchableObjects.All memungkinkan pencarian melihat logo yang disimpan sebagai artefak PDF.
  • Menggabungkan kriteria DCT‑hash dan histogram warna menangkap baik logo vektor tepat (Office) maupun versi raster (PDF).

Logo lama diganti dengan logo baru


Praktik Terbaik & Tips

  • Buat folder output sekali (Directory.CreateDirectory) – metode ini idempoten dan menghindari kondisi balapan.
  • Catat kemajuan – output konsol di setiap langkah memudahkan melihat file mana yang berhasil atau gagal.
  • Sesuaikan Opacity dan RotateAngle sesuai pedoman merek; nilai antara 0.3–0.5 biasanya cukup terlihat namun tidak mengganggu.
  • Gunakan batch idempoten untuk pekerjaan berulang (mis., pembaruan merek tiap malam).
  • Uji penggantian logo pada sampel kecil sebelum menjalankan di seluruh repositori untuk memastikan kriteria pencarian sudah tepat.

Memecahkan Masalah Umum

Gejala Penyebab Kemungkinan Solusi
Tidak ada file yang diproses ScanFolderForSupportedFiles mengembalikan daftar kosong Verifikasi jalur InputFolder dan pastikan folder berisi format yang didukung (PDF, DOCX, PPTX, XLSX, PNG, JPG, dll.)
Tanda air tidak terlihat Opasitas terlalu rendah atau warna menyatu dengan latar belakang Tingkatkan Opacity (mis., 0.5) atau ubah ForegroundColor ke warna yang kontras
Logo PDF tidak ditemukan saat penggantian Logo ditambahkan sebagai operator draw pada content‑stream (tidak dapat dicari) Saat menambahkan logo, gunakan PdfArtifactWatermarkOptions agar menjadi artefak yang dapat dicari
Pengecualian System.Drawing.Common di Linux Hilangnya pustaka native GDI+ Instal libgdiplus pada mesin Linux target atau aktifkan dukungan Unix di .csproj (<RuntimeHostConfigurationOption Include="System.Drawing.EnableUnixSupport" Value="true" />).

Kesimpulan

Anda kini memiliki pipeline lengkap yang siap produksi yang dapat:

  • Melisensikan perpustakaan.
  • Mendeteksi dokumen yang didukung secara otomatis.
  • Menerapkan tanda air teks atau logo berulang.
  • Berjalan aman berulang kali tanpa membuat duplikat.
  • Mengganti logo perusahaan lama di seluruh folder.

Blok‑blok bangunan ini dapat dicampur‑aduk untuk memenuhi alur kerja branding atau perlindungan dokumen apa pun di .NET.

Sumber Daya Tambahan