DEV Community

Cover image for Making a watermark for PDF.
Serhii Korol
Serhii Korol

Posted on

Making a watermark for PDF.

Hello folks. In this article I want to show how to apply a watermark to your PDF document. I'll show the approach that it'll work not only on Windows but and on Mac OS or Linux. This project you can download and run on your PC and it'll be work.

Preconditions

You need to create a simple Web API application. And also you need to install one NuGet package - PDFtoImage.

<PackageReference Include="PDFtoImage" Version="2.3.0" />
Enter fullscreen mode Exit fullscreen mode

Implementation

Please, create WatermarkController in the Controller folder. Next, you should add a base route:

[ApiController]
[Route("api/v1/pdf")]
public class WatermarkController : ControllerBase
Enter fullscreen mode Exit fullscreen mode

And sure, add one endpoint:

[HttpPost]
[Route("watermark")]
public IActionResult AddWatermark(IFormFile? watermark, IFormFile? document)
{
}
Enter fullscreen mode Exit fullscreen mode

If you are familiar with Windows realization, there exists multipart data and no need for any parameters. Here I used IFormFile. Add this code:

[HttpPost]
[Route("watermark")]
public IActionResult AddWatermark(IFormFile? watermark, IFormFile? document)
{
    try
    {
        if (watermark is null || document is null)
        {
            return BadRequest("Both PNG and PDF images are required.");
        }

        byte[]? pngBytes = null;
        byte[]? pdfBytes = null;

        if (watermark.ContentType == "image/png")
        {
            using var memoryStream = new MemoryStream();
            watermark.CopyTo(memoryStream);
            pngBytes = memoryStream.ToArray();
        }
        else
        {
            return BadRequest("The watermark should be a .PNG format");
        }

        if (document.ContentType == "application/pdf")
        {
            using var memoryStream = new MemoryStream();
            document.CopyTo(memoryStream);
            pdfBytes = memoryStream.ToArray();
        }
        else
        {
            return BadRequest("The document should be a .PDF format");
        }

        var mergedImages = MergeImages(pngBytes, pdfBytes);

        var pdfDocument = ConvertToPdf(mergedImages);

        return File(pdfDocument, "application/pdf", "watermarked.pdf");
        }
        catch (Exception ex)
        {
            return BadRequest(ex);
        }
    }
Enter fullscreen mode Exit fullscreen mode

Let's find out what's going on in this code. From entry params, we check out file types and copy bytes. For adding the watermark, it needs to merge files. For this, you need to add this method:

private IEnumerable<SKBitmap> MergeImages(byte[] pngBytes, byte[] pdfBytes)
{
    var pngImage = MakeBackgroundTransparent(pngBytes);

    foreach (var pdfImage in PDFtoImage.Conversion.ToImages(pdfBytes))
    {
        var mergedImage = new SKBitmap(pdfImage.Width, pdfImage.Height);
        using (var canvas = new SKCanvas(mergedImage))
        {
            canvas.DrawBitmap(pdfImage, 0, 0);
            int x = pdfImage.Width - pngImage.Width - 5;
            int y = pdfImage.Height - pngImage.Height - 5;
            canvas.DrawBitmap(pngImage, x, y);
        }

        yield return mergedImage;
    }
}
Enter fullscreen mode Exit fullscreen mode

The first row refers to another method. Why does it need? The watermark is an ordinary monochrome PNG picture on a white background. If we'll skip this method then the picture will be merged with a white background and it'll look not great. For this reason let's add another method:

private SKBitmap MakeBackgroundTransparent(byte[] pngBytes)
{
    using var pngStream = new MemoryStream(pngBytes);
    var image = SKBitmap.Decode(pngStream);
    var transparentImage = new SKBitmap(image.Width, image.Height, SKColorType.Bgra8888, SKAlphaType.Premul);

    for (var x = 0; x < image.Width; x++)
    {
        for (var y = 0; y < image.Height; y++)
        {
            var pixelColor = image.GetPixel(x, y);

            if (pixelColor.Red == 255 && pixelColor.Green == 255 && pixelColor.Blue == 255)
            {
                pixelColor = new SKColor(255, 255, 255, 0);
            }
            else
            {
                pixelColor = new SKColor(0, 0, 0, 64);
            }

            transparentImage.SetPixel(x, y, pixelColor);
        }
    }

    return transparentImage;
}
Enter fullscreen mode Exit fullscreen mode

We decode pictures to the image and check each pixel, and if they are white, then we set the opacity to 0, else 64. And now, let's return to the MergeImages method. Next, we need split PDF documents into multiple separated images. For this, we used the static class PDFtoImage from the installed package. It contains under the hood SkiaSharp and Pdfium. Sure, you can make convert it by yourself with these packages, but it's not a trivial task. This package is the simplest way to resolve this problem. This method returns image collection. We iterated each PDF image and merge our watermark with a padding of 5 px. We also return images collection. All received images we need to convert back to PDF. For this, add the following method:

private byte[] ConvertToPdf(IEnumerable<SKBitmap> images)
{
    using var stream = new MemoryStream();
    using (var document = SKDocument.CreatePdf(stream))
    {
        foreach (var image in images)
        {
            using var canvas = document.BeginPage(image.Width, image.Height);
            canvas.DrawBitmap(image, 0, 0);
            document.EndPage();
        }
    }

    return stream.ToArray();
}
Enter fullscreen mode Exit fullscreen mode

Received bytes convert to the PDF file on the fly.
Let's check this out. I'll be using Postman, but you can use any other REST client. I also downloaded a PDF document and created a facsimile. If you'll post a request by this URI:
http://localhost:5273/api/v1/pdf/watermark, then you should get something like this.

first pic

Or this.

second pic

As you can see, the watermark doesn't have a background and also has opacity which you can set for your needs.

Conclusions

This approach can make your application really cross-platform for different OSs with minimal complexity.
See you in the next article, set likes, make subscribe, and happy coding.

The source code you can find by this link.

Buy Me A Beer

Top comments (0)