[]
        
(Showing Draft Content)

Transparency Mask

DsPdfJS allows you to control the visibility of parts of an image or drawing operations by using transparency masks. A transparency mask determines which pixels of the target image are affected during drawing or processing. Transparent areas allow drawing to appear normally, while masked areas prevent the underlying pixels from being modified.

Apply Transparency Mask

Transparency masks are commonly used to hide specific portions of an image while keeping the rest visible. In DsPdfJS, you can apply a mask directly to an image using the applyGrayscaleTransparencyMask or applyBilevelTransparencyMask methods of the Bitmap class.

A mask is typically created from another image and converted into a GrayscaleBitmap or BilevelBitmap. The mask is then applied to the target bitmap, controlling which pixels are affected.

applyMask.jpeg

To apply a transparency mask:

  1. Load the image that will be used as the mask.

  2. Convert the mask image to a grayscale bitmap.

  3. Load the image on which the transparency mask will be applied.

  4. Apply the transparency mask using the applyGrayscaleTransparencyMask or applyBilevelTransparencyMask method.

  5. Optionally convert the result to an opaque image using convertToOpaque if you want to remove transparency.

const mask = Image.load(await Util.loadFile("images/logo.png")).toBitmap();
const target = Image.load(await Util.loadFile("images/tudor.jpg")).toBitmap();

const gsMask = mask.toGrayscaleBitmap(ColorChannel.Blue, true);

target.applyGrayscaleTransparencyMask(gsMask);
target.convertToOpaque("Beige");

const imgBytes = target.saveAsJpeg();
Util.saveFile("applyMask.jpeg", imgBytes, 'image/jpeg');

Use Transparency Mask for Drawing

Instead of applying a mask directly to an image, you can assign a transparency mask to a drawing context. When a transparency mask is set on the BmpContext, it affects all subsequent drawing and filling operations.

Pixels in the mask control the transparency of drawing operations:

  • Pixels with value 0 completely mask drawing, leaving the original image unchanged.

  • Pixels with value 255 allow drawing to appear normally.

  • Values between 0 and 255 produce partial transparency.

gradientMask.png

To use a transparency mask for drawing:

  1. Create or load an image that will serve as the transparency mask.

  2. Convert the mask image to a GrayscaleBitmap.

  3. Assign the mask to the transparencyMask property of the drawing context.

  4. Perform drawing operations on the bitmap.

  5. Reset the transparencyMask property when you no longer need the mask.

Note: The mask bitmap must have the same pixel dimensions as the target bitmap.

const h = 512;
const ctxMask = new BmpContext(h, h);
const grad = new LinearGradientBrush({ startColor: "Black", endColor: "White" });
ctxMask.drawRect(0, 0, ctxMask.width, ctxMask.height, { fillBrush: grad });
const gsb = ctxMask.bitmap.toGrayscaleBitmap();

const bmp = new Bitmap(h + h, h);

const bmpTemp = new Bitmap(h, h);
const ctxTemp = bmpTemp.context;

const fillCircles = () => {
    bmpTemp.clear("Yellow");
    const sz = h * 0.7;
    const d = h / 25;
    const wh = sz - d * 2;
    ctxTemp.drawEllipse((h - sz) / 2 + d, d, wh, wh, { fillColor: "Red" });
    ctxTemp.drawEllipse(h - sz + d, h - sz + d, wh, wh, { fillColor: "Green" });
    ctxTemp.drawEllipse(d, h - sz + d, wh, wh, { fillColor: "Blue" });
};

ctxTemp.transparencyMask = gsb;
fillCircles();
bmp.bitBlt(bmpTemp, 0, 0);

ctxTemp.transparencyMask = null;
fillCircles();
bmp.bitBlt(bmpTemp, h, 0);

const imgBytes = bmp.saveAsPng();
Util.saveFile("gradientMask.png", imgBytes, 'image/png');

Convert Transparent Image to Opaque

Images may contain transparent or semi‑transparent pixels. In some scenarios, you may want to remove transparency by replacing it with a solid background color. In DsPdfJS, this can be done using the convertToOpaque method of the Bitmap class.

The convertToOpaque method replaces transparent pixels with a specified background color, effectively converting the image to a fully opaque bitmap.

convertToOpaque (3).png

To convert a transparent image to opaque:

  1. Load the image using the Image.load method.

  2. Convert the image to a bitmap using toBitmap.

  3. Call the convertToOpaque method and specify the background color to replace transparent pixels.

  4. Save or display the resulting image.

const img = Image.load(await Util.loadFile("images/demo.png"));
const bmpOriginal = img.toBitmap();

const bmpOpaque = img.toBitmap();
bmpOpaque.convertToOpaque("Yellow");

const result = await Util.combineTwoImages(
    bmpOriginal, "Original Image (with transparency)",
    bmpOpaque, "Image after convertToOpaque"
);

const imgBytes = result.saveAsPng();
Util.saveFile("convertToOpaque.png", imgBytes, "image/png");


// Helper method used in this sample to display images side by side.
static async combineTwoImages(img1, text1, img2, text2) {

    const om = getCurrentObjectManager();
    pushObjectManager();

    const img1W = img1.width;
    const img1H = img1.height;
    const img2W = img2.width;
    const img2H = img2.height;

    const margin = 10;
    const m2 = margin * 2;
    const header = 40;
    const lw = 1;

    const fmt = new Format({
        font: Font.load(await Util.loadFile("fonts/segoeuib.ttf")),
        fontSize: 16
    });

    const tl1 = new Layout({ runs: [{ text: text1, format: fmt }] });
    const tl2 = new Layout({ runs: [{ text: text2, format: fmt }] });
    tl1.performLayout();
    tl2.performLayout();

    const txt1W = tl1.contentWidth;
    const txt2W = tl2.contentWidth;
    const max1W = img1W > txt1W ? img1W : txt1W;
    const max2W = img2W > txt2W ? img2W : txt2W;
    const maxH = img1H > img2H ? img1H : img2H;

    const ctx = new BmpContext(om, max1W + max2W + lw * 3 + m2 * 2, maxH + m2 + lw * 3 + header, 1, "DarkGray");

    let x1 = lw;
    let x2 = x1 + m2 + max1W + lw;
    let y1 = header + lw * 2;

    ctx.drawRect(x1, lw, max1W + m2, header, { fillColor: "LightGray" });
    ctx.drawRect(x2, lw, max2W + m2, header, { fillColor: "LightGray" });

    ctx.drawRect(x1, y1, max1W + m2, maxH + m2, { fillColor: "GhostWhite" });
    ctx.drawRect(x2, y1, max2W + m2, maxH + m2, { fillColor: "GhostWhite" });

    x1 += margin;
    x2 += margin;
    y1 += margin;

    ctx.drawImage(img1, x1, y1 + (maxH - img1H) / 2, img1W, img1H);
    ctx.drawImage(img2, x2, y1 + (maxH - img2H) / 2, img2W, img2H);

    const y0 = 10;
    ctx.drawLayout(tl1, x1, y0);
    ctx.drawLayout(tl2, x2, y0);

    popObjectManager();
    return ctx.bitmap;
}

Background Bitmaps

When drawing semi‑transparent shapes or images, the final color of a pixel is normally determined by blending the drawing with the pixels already present in the target bitmap. DsPdfJS allows you to change this behavior by specifying a background bitmap.

The backgroundBitmap property of the drawing context defines an image that will be used as the backdrop when blending semi‑transparent drawing operations. This allows you to control how transparency interacts with the background content.

backgroundBitmap (2).png

To use a background bitmap:

  1. Load or create an image that will be used as the background bitmap.

  2. Assign the bitmap to the backgroundBitmap property of the drawing context.

  3. Perform drawing operations such as shapes or text.

  4. Save the resulting image.

Note: The background bitmap must have the same pixel dimensions as the target bitmap.

const w = 600;
const h = 600;
const bkBmp = new Bitmap(w, h);
bkBmp.context.drawImage(img, 0, 0, w, h);

const bmp = new Bitmap(w, h, "MistyRose");
const ctx = bmp.context;

ctx.backgroundBitmap = bkBmp;

ctx.drawEllipse(20, 20, w - 40, h - 40, { lineColor: "rgb(White, 50%)", lineWidth: 30 });

ctx.rotate(-30, AngleUnits.Degrees, w / 2, h / 2);
ctx.drawLayout({
    maxWidth: w,
    maxHeight: h,
    textAlignment: TextAlignment.Center,
    paragraphAlignment: ParagraphAlignment.Center,
    runs: [{
        text: "DsPdfJS",
        font: Font.load(await Util.loadFile("fonts/tahoma.ttf")),
        fontSize: 140,
        foreColor: "rgb(Black, 30%)"
    }]
}, 0, 0);

const imgBytes = bmp.saveAsPng();
Util.saveFile("backgroundBitmap.png", imgBytes, 'image/png');

Advanced Compositing

Advanced compositing allows you to control how semi‑transparent graphics interact with existing pixels on an image. In DsPdfJS, this behavior can be controlled using the backgroundBitmap property of the BmpContext class.

When a background bitmap is assigned, drawing operations use the pixels of the background bitmap as the backdrop when blending colors. This allows you to reproduce rendering scenarios such as isolated and knockout groups, concepts defined in the PDF specification for controlling how overlapping graphics interact.

isolatedKnockout.png

To use a background bitmap for advanced compositing:

  1. Load or create a bitmap that will be used as the drawing backdrop.

  2. Assign the bitmap to the backgroundBitmap property of the drawing context.

  3. Perform drawing operations such as shapes or text.

  4. Save the resulting image.

Note: The background bitmap must have the same pixel dimensions as the target bitmap, and the same bitmap cannot be used as both the background and the drawing target.

The following code example demonstrates how different combinations of isolated and knockout rendering affect overlapping graphics.

const img = Image.load(await Util.loadFile("images/spectrum.png"));
const bmp0 = img.toBitmap();
img.free();
const bmp1 = bmp0.resize({ width: 400, height: 400 });
bmp0.free();

const bmpBackdrop = new Bitmap(400, 400);
const gB = bmpBackdrop.context;
gB.aliased = true;
gB.blendMode = BlendMode.Multiply;

const bmpInitial = new Bitmap(400, 400);
const gI = bmpInitial.context;
gI.aliased = true;
gI.blendMode = BlendMode.Multiply;

const bmp2 = new Bitmap(400 * 2, 400 * 2);
const rect1 = { x: 50, y: 50, width: 200, height: 200 };
const rect2 = { x: 150, y: 50, width: 200, height: 200 };
const rect3 = { x: 50, y: 150, width: 200, height: 200 };
const rect4 = { x: 150, y: 150, width: 200, height: 200 };

// Isolated, Knockout
bmpBackdrop.bitBlt(bmp1, 0, 0);
bmpInitial.clear("Transparent");
gB.backgroundBitmap = bmpInitial;
gB.drawEllipse(rect1, { fillColor: "LightGray" });
gB.drawEllipse(rect2, { fillColor: "LightGray" });
gB.drawEllipse(rect3, { fillColor: "LightGray" });
gB.drawEllipse(rect4, { fillColor: "LightGray" });
bmp2.bitBlt(bmpBackdrop, 0, 0);

// Isolated, Non-knockout
gI.drawEllipse(rect1, { fillColor: "LightGray" });
gI.drawEllipse(rect2, { fillColor: "LightGray" });
gI.drawEllipse(rect3, { fillColor: "LightGray" });
gI.drawEllipse(rect4, { fillColor: "LightGray" });
bmpBackdrop.bitBlt(bmp1, 0, 0);
bmpBackdrop.alphaBlend(bmpInitial, 0, 0);
bmp2.bitBlt(bmpBackdrop, 400, 0);

// Non-isolated, Knockout
bmpBackdrop.bitBlt(bmp1, 0, 0);
bmpInitial.bitBlt(bmp1, 0, 0);
gB.drawEllipse(rect1, { fillColor: "LightGray" });
gB.drawEllipse(rect2, { fillColor: "LightGray" });
gB.drawEllipse(rect3, { fillColor: "LightGray" });
gB.drawEllipse(rect4, { fillColor: "LightGray" });
bmp2.bitBlt(bmpBackdrop, 0, 400);

// Non-isolated, Non-knockout
bmpBackdrop.bitBlt(bmp1, 0, 0);
gB.backgroundBitmap = null;
gB.drawEllipse(rect1, { fillColor: "LightGray" });
gB.drawEllipse(rect2, { fillColor: "LightGray" });
gB.drawEllipse(rect3, { fillColor: "LightGray" });
gB.drawEllipse(rect4, { fillColor: "LightGray" });
bmp2.bitBlt(bmpBackdrop, 400, 400);

const ctx = bmp2.context;
ctx.drawLine(0, 400, 800, 400, { color: "Black" });
ctx.drawLine(400, 0, 400, 800, { color: "Black" });
const h = 35;
const tl = new Layout({
    maxWidth: 400,
    maxHeight: h,
    textAlignment: TextAlignment.Center,
    defaultFormat: new Format({
        font: Font.getPdfFont(StandardPdfFont.Helvetica),
        foreColor: "White",
        fontSize: 18
    })
});
ctx.drawLayout(new Layout(tl, { runs: [{ text: "Isolated, Knockout" }] }), 0, 400 - h);
ctx.drawLayout(new Layout(tl, { runs: [{ text: "Isolated, Non-knockout" }] }), 400, 400 - h);
ctx.drawLayout(new Layout(tl, { runs: [{ text: "Non-isolated, Knockout" }] }), 0, 800 - h);
ctx.drawLayout(new Layout(tl, { runs: [{ text: "Non-isolated, Non-knockout" }] }), 400, 800 - h);

const imgBytes = bmp2.saveAsPng();
Util.saveFile("isolatedKnockout.png", imgBytes, 'image/png');