Search code examples
c++libharu

Add transparency to PDF with libharu


I want to create a pdf with partially transparent polygons as the ellipse in the below image using libharu. The polygons come as RGBA, but libharu only has these fill methods:

HPDF_Page_SetCMYKFill()
HPDF_Page_SetGrayFill()
HPDF_Page_SetRGBFill() 

There is no "A" channel for rgb. (There is a transparency mask for the whole image, but as I understand, this only makes a range of colors invisible in the whole image, which is not what I want.)

I am new to libharu, and not an expert in color schemes or PDF, so solutions on all these levels might work...

Partially transparent ellipse


Solution

  • The following code works as a general solution as proven by Duke (the Original Poster).

    The generic means to add variability in a PDF file is, first to declare a set of optional "Graphics States" and transparency was added in version 5 (%PDF-1.4) as an Extended Graphics State (/ExtGState) commonly assigned to /GS0 /GS1 /GS2.... but the declarative naming scheme is optional.

    Thus in the PDF internal document resources you add for example <</ExtGState<</GS0 0.25/GS1 0.5 /GS...>>>> as required. Then whilst building a page those states can be introduced (usually seen as q /GS0 gs for any following objects until reset when required (usually Q for previous) for the subsequent page objects.

    Every PDF builder will have some similar methodology and during discussion the following was proposed as the method for a minimal example using LibHaru. using HPDF_CreateExtGState(pdf_document_) and HPDF_Page_SetExtGState(pdf_page_, gstate)

    #include ".../hpdf.h"
    #include <vector>
    
    int main(int argc, char* argv[])
    {
    std::vector<std::pair<float, float>> pol1{ {100, 100}, {900, 900}, {900, 100} };
    std::vector<std::pair<float, float>> pol2{ {10, 10}, {10, 900}, {900, 10} };
    
    auto pdf_document_ = HPDF_New([](HPDF_STATUS error_no, HPDF_STATUS detail_no, void* user_data) {
    // Do some error handling
    }, NULL);
    
    auto pdf_page_ = HPDF_AddPage(pdf_document_);
    HPDF_Page_SetHeight(pdf_page_, 1000);
    HPDF_Page_SetWidth(pdf_page_, 1000);
    
    // Polygon 1 (no transparency)
    HPDF_Page_SetRGBFill(pdf_page_, 1, 1, 0); // Set color to yellow
    
    HPDF_Page_MoveTo(pdf_page_, pol1[0].first, pol1[0].second);
    for (int i = 1; i < pol1.size(); i++) {
    HPDF_Page_LineTo(pdf_page_, pol1[i].first, pol1[i].second);
    }
    
    HPDF_Page_ClosePathFillStroke(pdf_page_);
    
    // Polygon 2 (opacity 0.45)
    HPDF_Page_SetRGBFill(pdf_page_, 1, 0, 1); // Set color to purple - because... why not ;-)
    
    HPDF_ExtGState gstate{ HPDF_CreateExtGState(pdf_document_) }; // Create extended graphics state
    HPDF_ExtGState_SetAlphaFill(gstate, 0.45); // Set the opacity
    HPDF_Page_SetExtGState(pdf_page_, gstate); // Apply the gstate property to the page
    
    HPDF_Page_MoveTo(pdf_page_, pol2[0].first, pol2[0].second);
    for (int i = 1; i < pol2.size(); i++) {
    HPDF_Page_LineTo(pdf_page_, pol2[i].first, pol2[i].second);
    }
    
    HPDF_Page_ClosePathFillStroke(pdf_page_);
    
    HPDF_SaveToFile(pdf_document_, "my.pdf");
    }
    

    The result will be a Page Definition with /ExtGState labled /E1

    4 0 obj
    <</Type/Page/MediaBox [ 0 0 1000 1000 ]/Contents 5 0 R
    /Resources<</ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
    /ExtGState <</E1 7 0 R>>>>
    /Parent 2 0 R>>
    endobj
    

    /E1 points to the Extended Graphic State, in this case just a change in color alpha but could have other extended attributes

    7 0 obj
    <</Type/ExtGState/ca 0.45>>
    endobj
    

    then later in page contents, that named state can be applied at that time as if /ca 0.45 was included

    5 0 obj
    <</Length 6 0 R>>
    stream
    % set color to yellow
    1 1 0 rg
    % draw solid state triangle diagonally right and up from bottom left
    100 100 m
    900 900 l
    900 100 l
    % close and fill
    b
    % Change colour to magenta
    1 0 1 rg
    % set gs to Extended Graphic State (Opacity 45%)
    /E1 gs
    10 10 m
    10 900 l
    900 10 l
    b
    

    enter image description here

    I have condensed the true PDF result here to show them in less lines. So object 7 in notepad is laid out like below, and it is easy to edit from 0.45 to 0.75 opacity to experiment as seen on the above right in a real time preview (not Acrobat).

    7 0 obj
    <<
    /Type /ExtGState
    /ca 0.75
    >>
    endobj
    

    NOTE: I use the term opacity for the alpha channel since 0 with infinite floats to 1 is a range from fully transparent (0) to fully opaque (1).

    This can mean in RGBA terms that [0 0 0 0] is no color at all (totally translucent) while [1 1 1 1] is full color (totally solid white). But more confusing it means as a single "color" transparency is [# # # 0] to [# # # 1] thus on a scale of 256 bytes in images, all are shown as in effect a mono black softmask when stored and extracted independently, but that's a different very broad topic!