ImageGear for C and C++ on Windows v19.1 - Updated
Watermarks and Annotations in a PDF
User Guide > How to Work with... > Formats with Additional Functionality > PDF > How to... > Manage PDF Content > Watermarks and Annotations in a PDF

Before working with a PDF document, make sure to initialize the PDF component (see Getting Started with PDF).

Refer to the AddNewPageWithImage Sample and the AddWatermarksToPDF Sample for complete sample code that illustrates how to use these capabilities.

A digital watermark is any auxiliary data embedded into a document used for subsequent identification. Watermarks may provide contextual support to an author's content, but are otherwise independent of that content. For example, stamping a page as "DRAFT" or "CONFIDENTIAL" imparts meaning beyond any text or drawings present on that page.

PDF documents may express a digital watermark using any of the following techniques:

Each of these techniques can be accomplished using ImageGear, with varying complexity and completeness. See the following subsections:

Annotations

Annotations are added to PDF pages by using the ImageGear ART or ArtX Components, which support a subset of available PDF annotations. Refer to PDF Marks for the mapping used with PDF annotations.

ImageGear does not export ART Marks to PDF Watermark annotations. Applications that require explicit PDF Watermark annotation support may use the basic object methods to create a PDF Watermark annotation object and appearance streams, as outlined in the PDF Reference under topic "Watermark Annotations".  However, ImageGear ART can convert ART marks to PDF annotations and PDF annotations to ART marks. Please see PDF Marks for more information.

The following code snippet demonstrates adding a Text ART mark and exporting to all of the pages in a PDF document as a FreeText annotation:

C
Copy Code
void ApplyTextWatermarkToAllPages(LPCSTR fileIn)
{
    const int OpacityTranslucent = 100;
    const AT_DIMENSION MaxHeightInImagePixels = 200;
    const UINT firstPage = 0;

    HMIGEAR document = 0;
    unsigned int pageCount = 0;
    unsigned int pageNumber = 0;
    HIGEAR page = 0;
    AT_DIMENSION imageWidth = 0;
    AT_DIMENSION imageHeight = 0;
    UINT bitsPerPixel = 0;

    HIG_ARTX_PAGE artPage = 0;
    AT_RECTANGLE rectangle = { 0 };
    AT_ARTX_FONT font = { 10.0f, IG_ARTX_FONT_REGULAR, FALSE, "Arial" };
    AT_ARTX_BORDER border = { IG_ARTX_PEN_SOLID, 2, { 128, 128, 128, 0 } };
    AT_BOOL borderShading = FALSE;
    AT_RGBQUAD fillColor = { 0, 0, 255, 0 };
    AT_RGBQUAD textColor = { 0, 0, 0, 0 };
    HIG_ARTX_TEXT mark = NULL;
    HIG_ARTX_TEXT markInPage = NULL;
    
    // Load the multi-page document.
    IG_mpi_create(&document, 0);
    IG_mpi_file_open((LPSTR)fileIn, document, IG_FORMAT_PDF, IG_MP_OPENMODE_READONLY);

    // Get the page count.
    IG_mpf_page_count_get(document, &pageCount);

    for (pageNumber = 0; pageNumber < pageCount; pageNumber++)
    {
        // Get a handle to the page.
        IG_mpi_page_get(document, pageNumber, &page);

        // Get the dimensions of the image and watermark text.
        IG_image_dimensions_get(page, &imageWidth, &imageHeight, &bitsPerPixel);

        // Create the ArtX page.
        IG_ARTX_page_create(&artPage);

        // Create the text mark.
        rectangle.width = imageWidth;
        rectangle.height = min(MaxHeightInImagePixels, imageHeight);
        rectangle.x = 1;
        rectangle.y = rectangle.height / 2;
        // Choose a font size that is 85% height of the box height.
        font.fSize = rectangle.height * 0.85f; 

        IG_ARTX_text_create(&rectangle, "Draft", IG_ARTX_TEXT_DIRECT_TEXT, &font, &border,
            borderShading, &fillColor, &textColor, OpacityTranslucent, NULL, NULL, &mark);

        // Add the text mark to the page.
        IG_ARTX_page_mark_add(artPage, mark, IG_ARTX_COORD_IMAGE, &markInPage);
        IG_ARTX_page_save(artPage, page);
        IG_ARTX_page_delete(artPage);
        IG_ARTX_mark_delete(mark);
    }

    // Save the file out with the new text watermark.
    IG_mpi_file_save("WatermarkText.pdf", document, firstPage, 0, pageCount,
        IG_FORMAT_PDF, IG_MPI_SAVE_OVERWRITE);

    // Cleanup the document and its pages.
    IG_mpi_delete(document);
}
C++
Copy Code
void ApplyTextWatermarkToAllPages(const string& fileIn)
{
    const int BorderThicknessInPixels = 2;
    const int OpacityTranslucent = 100;
    const AT_DIMENSION MaxHeightInImagePixels = 200;

    // Load the multi-page document.
    HMIGEAR document = 0;
    IG_mpi_create(&document, 0);
    IG_mpi_file_open((LPSTR)fileIn.c_str(), document, IG_FORMAT_PDF, IG_MP_OPENMODE_READONLY);

    // Get the page count.
    unsigned int pageCount = 0;
    IG_mpf_page_count_get(document, &pageCount);

    for (unsigned int pageNumber = 0; pageNumber < pageCount; pageNumber++)
    {
        // Get a handle to the page.
        HIGEAR page = 0;
        IG_mpi_page_get(document, pageNumber, &page);

        // Get the dimensions of the image and watermark text.
        AT_DIMENSION imageWidth = 0;
        AT_DIMENSION imageHeight = 0;
        UINT bitsPerPixel = 0;
        IG_image_dimensions_get(page, &imageWidth, &imageHeight, &bitsPerPixel);

        AT_DIMENSION boxWidth = imageWidth;
        AT_DIMENSION boxHeight = (std::min)(MaxHeightInImagePixels, imageHeight);
        // Choose a font size that is 85% height of the box height.
        float fontSize = boxHeight * 0.85f;

        // Create the ArtX page.
        HIG_ARTX_PAGE artPage = 0;
        IG_ARTX_page_create(&artPage);

        // Create the text mark.
        // X and Y coordinates to place image.
        AT_DIMENSION x = 1;
        AT_DIMENSION y = (imageHeight - boxHeight) / 2;
        AT_RECTANGLE rectangle = {x, y, boxWidth, boxHeight};
        AT_ARTX_FONT font = {fontSize, IG_ARTX_FONT_REGULAR, FALSE, "Arial"};
        AT_ARTX_BORDER border = {IG_ARTX_PEN_SOLID, BorderThicknessInPixels, {128, 128, 128, 0}};
        AT_BOOL borderShading = FALSE;
        AT_RGBQUAD fillColor = {0, 0, 255, 0};
        AT_RGBQUAD textColor = {0, 0, 0, 0};
        HIG_ARTX_TEXT mark = NULL;

        IG_ARTX_text_create(&rectangle, "Draft", IG_ARTX_TEXT_DIRECT_TEXT, &font, &border,
            borderShading, &fillColor, &textColor, OpacityTranslucent, NULL, NULL, &mark);

        // Add the text mark to the page.
        HIG_ARTX_TEXT markInPage = NULL;
        IG_ARTX_page_mark_add(artPage, mark, IG_ARTX_COORD_IMAGE, &markInPage);
        IG_ARTX_page_save(artPage, page);
        IG_ARTX_page_delete(artPage);
        IG_ARTX_mark_delete(mark);
    }

    // Save the file out with the new text watermark.
    const UINT firstPage = 0;
    IG_mpi_file_save("WatermarkText.pdf", document, firstPage, 0, pageCount,
        IG_FORMAT_PDF, IG_MPI_SAVE_OVERWRITE);

    // Cleanup the document and its pages.
    IG_mpi_delete(document);
}

Page Graphics

Watermarks expressed as PDF page graphics are added using ImageGear’s PDF page objects and elements methods. These methods provide for editing the content of graphics objects and operators described in the PDF Reference.

The following snippet demonstrates creating a 24-bpp RGB PDEImage from an ImageGear image and setting it up to be placed at the bottom left corner of a PDF page:

Adjustments are required to successfully add images utilizing alpha channels, other pixel formats and color spaces in accordance to the image XObject discussion in the PDF Reference. The AddWatermarksToPDF Sample demonstrates some of these adjustments.

C
Copy Code
void CreatePDEImageFromImageGear(HIG_PDF_DOC pdfDocument, HIGEAR image,
        HIG_PDE_ELEMENT *pdeImage)
{
    // This snippet expects a 24 bits per pixel image.
    const int bitDepth = 24;
    const int bitsPerComponent = 8;
    const int bytesPerPixel = bitDepth / bitsPerComponent;

    HIGDIBINFO dibInfo = NULL;
    AT_DIMENSION imageWidth = 0;
    AT_DIMENSION imageHeight = 0;
    AT_LMODE pixelAccessMode = 0;

    AT_DIMENSION rasterSize = 0;
    size_t imageSizeInBytes = 0;
    LPAT_PIXEL pixels = NULL;
    LPAT_PIXEL rasterLine = NULL;
    AT_DIMENSION nativeRasterSize = 0;
    LPAT_PIXEL nativeRasterLine = NULL;
    int line = 0;

    HIG_PDE_COLORSPACE colorSpace = 0;
    HIG_PDF_ATOM colorSpaceName = IG_PDF_ATOM_NULL;
    AT_PDE_FILTERARRAY filters;
    AT_PDE_FILTERSPEC compressionSpec;
    HIG_PDF_BASOBJ dictionary = 0;
    HIG_PDF_ATOM key = IG_PDF_ATOM_NULL;
    AT_PDE_IMAGEATTRS pdfImageAttributes;
    AT_PDF_FIXEDMATRIX transformMatrix;

    // Get the image's DIB header.
    IG_image_DIB_info_get(image, &dibInfo);
    imageWidth = IG_DIB_width_get(dibInfo);
    imageHeight = IG_DIB_height_get(dibInfo);

    // Set the pixel access mode to new.
    pixelAccessMode = IG_PIX_ACCESS_MODE_NEW;
    IG_gctrl_item_set("DIB.PIX_ACCESS_USE_LEGACY_MODE", AM_TID_AT_LMODE, &pixelAccessMode,
            sizeof(AT_LMODE), NULL);

    // Get the image pixels.
    rasterSize = (imageWidth * bitDepth + (bitsPerComponent - 1)) / bitsPerComponent;
    imageSizeInBytes = rasterSize * imageHeight;
    pixels = (LPAT_PIXEL)malloc(imageSizeInBytes);

    rasterLine = pixels;
    IG_DIB_raster_size_get(image, IG_PIXEL_UNPACKED, &nativeRasterSize);
    nativeRasterLine = (LPAT_PIXEL)malloc((size_t)nativeRasterSize);
    // Retrieve each raster line in its native format.
    for (line = 0; line < imageHeight; line++)
    {
        IG_DIB_raster_get(image, line, nativeRasterLine, IG_PIXEL_UNPACKED);
        memcpy(rasterLine, nativeRasterLine, rasterSize);
        // Go to the next raster line.
        rasterLine += rasterSize;
    }
    free(nativeRasterLine);

    // Create color space.
    IG_PDF_atom_from_string("DeviceRGB", &colorSpaceName);
    IG_PDE_colorspace_create(colorSpaceName, NULL, &colorSpace);

    // Initialize a filter array for the filter specs.
    memset(&filters, 0, sizeof(filters));

    // Set image compression.
    memset(&compressionSpec, 0, sizeof(AT_PDE_FILTERSPEC));
    IG_PDF_basdict_create(pdfDocument, FALSE, 3, &dictionary);
    IG_PDF_atom_from_string("Columns", &key);
    IG_PDF_basdict_put_int(dictionary, key, FALSE, imageWidth);
    IG_PDF_atom_from_string("Rows", &key);
    IG_PDF_basdict_put_int(dictionary, key, FALSE, imageHeight);
    IG_PDF_atom_from_string("Colors", &key);
    IG_PDF_basdict_put_int(dictionary, key, FALSE, bytesPerPixel);
    IG_PDF_atom_from_string("DCTDecode", &(compressionSpec.name));
    compressionSpec.encodeParms = dictionary;
    compressionSpec.decodeParms = dictionary;

    filters.spec[filters.numFilters] = compressionSpec;
    filters.numFilters++;

    // Initialize image attributes.
    memset(&pdfImageAttributes, 0, sizeof(AT_PDE_IMAGEATTRS));
    // A PDF image is an XObject.
    pdfImageAttributes.flags = IG_PDE_IMAGE_EXTERNAL;
    pdfImageAttributes.width = (AT_INT32)imageWidth;
    pdfImageAttributes.height = (AT_INT32)imageHeight;
    pdfImageAttributes.bitsPerComponent = bitsPerComponent;

    // Initialize transformation matrix.
    memset(&transformMatrix, 0, sizeof(AT_PDF_FIXEDMATRIX));
    transformMatrix.a = AM_PDF_DOUBLE_TO_FIXED(imageWidth);
    transformMatrix.b = IG_PDF_FIXED_ZERO;
    transformMatrix.c = IG_PDF_FIXED_ZERO;
    transformMatrix.d = AM_PDF_DOUBLE_TO_FIXED(imageHeight);
    transformMatrix.h = IG_PDF_FIXED_ZERO;
    transformMatrix.v = IG_PDF_FIXED_ZERO;

    // Create the PDEImage.
    IG_PDE_image_create(&pdfImageAttributes, &transformMatrix, 0, colorSpace, NULL, &filters, NULL,
            pixels, (LONG)(imageSizeInBytes), pdeImage);

    // Release resources.
    IG_PDE_colorspace_release(colorSpace);
    free(pixels);
    IG_DIB_info_delete(dibInfo);
}
C++
Copy Code
void CreatePDEImageFromImageGear(HIG_PDF_DOC pdfDocument, HIGEAR image,
        HIG_PDE_ELEMENT &pdeImage)
{
    // This snippet expects a 24 bits per pixel image.
    const int bitDepth = 24;
    const int bitsPerComponent = 8;
    const int bytesPerPixel = bitDepth / bitsPerComponent;

    // Get the image's DIB header.
    HIGDIBINFO dibInfo = NULL;
    IG_image_DIB_info_get(image, &dibInfo);
    AT_DIMENSION imageWidth = IG_DIB_width_get(dibInfo);
    AT_DIMENSION imageHeight = IG_DIB_height_get(dibInfo);

    // Set the pixel access mode to new.
    AT_LMODE pixelAccessMode = IG_PIX_ACCESS_MODE_NEW;
    IG_gctrl_item_set("DIB.PIX_ACCESS_USE_LEGACY_MODE", AM_TID_AT_LMODE, &pixelAccessMode,
            sizeof(AT_LMODE), NULL);

    // Get the image pixels.
    AT_DIMENSION rasterSize = (imageWidth * bitDepth + (bitsPerComponent - 1)) / bitsPerComponent;
    size_t imageSizeInBytes = rasterSize * imageHeight;
    LPAT_PIXEL pixels = new AT_PIXEL[imageSizeInBytes];

    LPAT_PIXEL rasterLine = pixels;
    AT_DIMENSION nativeRasterSize = 0;
    IG_DIB_raster_size_get(image, IG_PIXEL_UNPACKED, &nativeRasterSize);
    LPAT_PIXEL nativeRasterLine = new AT_PIXEL[nativeRasterSize];
    // Retrieve each raster line in its native format.
    for (int line = 0; line < imageHeight; line++)
    {
        IG_DIB_raster_get(image, line, nativeRasterLine, IG_PIXEL_UNPACKED);
        memcpy(rasterLine, nativeRasterLine, rasterSize);
        // Go to the next raster line.
        rasterLine += rasterSize;
    }
    delete [] nativeRasterLine;

    // Create color space.
    HIG_PDE_COLORSPACE colorSpace = 0;
    HIG_PDF_ATOM colorSpaceName = IG_PDF_ATOM_NULL;
    IG_PDF_atom_from_string("DeviceRGB", &colorSpaceName);
    IG_PDE_colorspace_create(colorSpaceName, NULL, &colorSpace);

    // Create a filter array for the filter specs.
    AT_PDE_FILTERARRAY filters;
    memset(&filters, 0, sizeof(filters));

    // Set image compression.
    AT_PDE_FILTERSPEC compressionSpec;
    HIG_PDF_BASOBJ dictionary = 0;
    HIG_PDF_ATOM key = IG_PDF_ATOM_NULL;
    memset(&compressionSpec, 0, sizeof(AT_PDE_FILTERSPEC));
    IG_PDF_basdict_create(pdfDocument, FALSE, 3, &dictionary);
    IG_PDF_atom_from_string("Columns", &key);
    IG_PDF_basdict_put_int(dictionary, key, FALSE, imageWidth);
    IG_PDF_atom_from_string("Rows", &key);
    IG_PDF_basdict_put_int(dictionary, key, FALSE, imageHeight);
    IG_PDF_atom_from_string("Colors", &key);
    IG_PDF_basdict_put_int(dictionary, key, FALSE, bytesPerPixel);
    IG_PDF_atom_from_string("DCTDecode", &(compressionSpec.name));
    compressionSpec.encodeParms = dictionary;
    compressionSpec.decodeParms = dictionary;

    filters.spec[filters.numFilters] = compressionSpec;
    filters.numFilters++;

    // Initialize image attributes.
    AT_PDE_IMAGEATTRS pdfImageAttributes;
    memset(&pdfImageAttributes, 0, sizeof(AT_PDE_IMAGEATTRS));
    // A PDF image is an XObject.
    pdfImageAttributes.flags = IG_PDE_IMAGE_EXTERNAL;
    pdfImageAttributes.width = (AT_INT32)imageWidth;
    pdfImageAttributes.height = (AT_INT32)imageHeight;
    pdfImageAttributes.bitsPerComponent = bitsPerComponent;

    // Initialize transformation matrix.
    AT_PDF_FIXEDMATRIX transformMatrix;
    memset(&transformMatrix, 0, sizeof(AT_PDF_FIXEDMATRIX));
    transformMatrix.a = AM_PDF_DOUBLE_TO_FIXED(imageWidth);
    transformMatrix.b = IG_PDF_FIXED_ZERO;
    transformMatrix.c = IG_PDF_FIXED_ZERO;
    transformMatrix.d = AM_PDF_DOUBLE_TO_FIXED(imageHeight);
    transformMatrix.h = IG_PDF_FIXED_ZERO;
    transformMatrix.v = IG_PDF_FIXED_ZERO;

    // Create the PDEImage.
    IG_PDE_image_create(&pdfImageAttributes, &transformMatrix, 0, colorSpace, NULL, &filters, NULL,
            pixels, (LONG)(imageSizeInBytes), &pdeImage);

    // Release resources.
    IG_PDE_colorspace_release(colorSpace);
    delete [] pixels;
    IG_DIB_info_delete(dibInfo);
}

The following code snippet calls the CreatePDEImageFromImageGear method from above and places the PDE image created into the content of the PDF page.

C
Copy Code
void AddRGB24ImageToPdfPage(HIG_PDF_DOC pdfDocument, HIG_PDF_PAGE pdfPage, HIGEAR image)
{
    HIG_PDE_CONTENT content = 0;
    HIG_PDE_ELEMENT pdeImage = 0;
    CreatePDEImageFromImageGear(pdfDocument, image, &pdeImage);

    // Acquire the PDF page content.
    IG_PDF_page_get_content(pdfPage, &content);

    IG_PDE_content_add_element(content, IG_PDE_AFTER_LAST, pdeImage);

    // Save the PDF content to the PDF page.
    IG_PDF_page_set_content(pdfPage);
    IG_PDF_page_release_content(pdfPage);

    IG_PDE_element_release(pdeImage);
}
C++
Copy Code
void AddRGB24ImageToPdfPage(HIG_PDF_DOC pdfDocument, HIG_PDF_PAGE pdfPage, HIGEAR image)
{
    HIG_PDE_ELEMENT pdeImage = 0;
    CreatePDEImageFromImageGear(pdfDocument, image, pdeImage);

    // Acquire the PDF page content.
    HIG_PDE_CONTENT content = 0;
    IG_PDF_page_get_content(pdfPage, &content);

    IG_PDE_content_add_element(content, IG_PDE_AFTER_LAST, pdeImage);

    // Save the PDF content to the PDF page.
    IG_PDF_page_set_content(pdfPage);
    IG_PDF_page_release_content(pdfPage);

    IG_PDE_element_release(pdeImage);
}

Refer to the AddWatermarksToPDF Sample.

See the PDF Reference topic “Page Editing Objects and Elements” for additional information.

Optional Content Groups

Watermark optional content can be added to PDF pages using ImageGear’s PDF page objects and elements methods. These methods provide for editing the content of graphics objects and operators described in the PDF Reference, which provides methods for layer management.

To designate a watermark intended for printing, add an Optional Content Print Usage dictionary with Subtype name "Watermark" to that OCG, represented with a PDF layer:

C
Copy Code
void CreateOptionalContentGroup(HIG_PDF_DOC pdfDocument, HIG_PDF_LAYER *layer)
{
    // Create Print Usage dictionary.
    HIG_PDF_BASOBJ dictionary = 0;
    HIG_PDF_ATOM key = IG_PDF_ATOM_NULL;
    HIG_PDF_ATOM name = IG_PDF_ATOM_NULL;
    char layerName[] = "WatermarkLayer";

    IG_PDF_basdict_create(pdfDocument, FALSE, 2, &dictionary);
    IG_PDF_atom_from_string("Subtype", &key);
    IG_PDF_atom_from_string("Watermark", &name);
    IG_PDF_basdict_put_name(dictionary, key, FALSE, name);

    IG_PDF_atom_from_string("PrintState", &key);
    IG_PDF_atom_from_string("ON", &name);
    IG_PDF_basdict_put_name(dictionary, key, FALSE, name);

    IG_PDF_layer_create(pdfDocument, layerName, sizeof(layerName), layer);

    IG_PDF_atom_from_string("Print", &key);
    IG_PDF_layer_set_usage_info(layer, key, dictionary);
}
C++
Copy Code
void CreateOptionalContentGroup(HIG_PDF_DOC pdfDocument, HIG_PDF_LAYER &layer)
{
    // Create Print Usage dictionary.
    HIG_PDF_BASOBJ dictionary = 0;
    HIG_PDF_ATOM key = IG_PDF_ATOM_NULL;
    HIG_PDF_ATOM name = IG_PDF_ATOM_NULL;

    IG_PDF_basdict_create(pdfDocument, FALSE, 2, &dictionary);
    IG_PDF_atom_from_string("Subtype", &key);
    IG_PDF_atom_from_string("Watermark", &name);
    IG_PDF_basdict_put_name(dictionary, key, FALSE, name);

    IG_PDF_atom_from_string("PrintState", &key);
    IG_PDF_atom_from_string("ON", &name);
    IG_PDF_basdict_put_name(dictionary, key, FALSE, name);


    char layerName[] = "WatermarkLayer";
    IG_PDF_layer_create(pdfDocument, layerName, sizeof(layerName), &layer);

    IG_PDF_atom_from_string("Print", &key);
    IG_PDF_layer_set_usage_info(layer, key, dictionary);
}

See the PDF Reference topic "Making Graphical Content Optional" for additional information.

Pagination Artifacts

Watermarks expressed as Pagination Artifacts can be added using the ImageGear’s PDF page objects and elements methods, which provides methods to create a marked-content sequence in accordance with the PDF Reference.

See the PDF Reference topic "Real Content and Artifacts" for additional information