Introduction
I’m going to discuss the other ways that you can mask an image before revealing the solution and discussing it, because there are lots of ways to mask an image in PDF, so it’s a bit more interesting a topic than you might think. If you search the PDF Specification (as I did just now) you will find that there are essentially two sections that discuss Masking: Section 8.9.6 Masked Images and Section 11.6.5 Specifying Soft Masks, and neither discusses masking with vector paths.
Background
Soft Masks are part of the chapter on transparency and come in two flavors: Images and Dictionaries. If you are familiar with RGBA images, an image soft mask is essentially the Alpha (transparency) channel separated from the RGB image and made to stand-alone within the parent image XObject. Which is essentially what DLE does with PNG files that have RGBA images. SoftMask dictionaries, on the other hand, are part of the extended graphic state, and while they contain a Transparency Group Form XObject, which could contain a vector path, it’s not the mechanism that comes to mind as the ideal way to mask an image with a vector path.
Masked images come in three flavors; Stencil Masking, Explicit Masking, and Colour Key Masking. Explicit Masking is like an image soft mask except that the alpha channel pixel can only be all on or all off, so there’s no degree of transparency. Colour key masking is a bit like how transparency works in GIF files, where one of the indexed colors is designated as the transparency color, but with colour key masking you can specify a range for each color component of the image; if all the components values fall within those ranges, then that pixel is masked (or not depending on the Decode parameter, but that’s the idea). Stencil Masking is like Explicit Masking, except that you have thrown away the primary image and are masking whatever is already in the background.
Using the code
None of these variants allow for vector paths, so how do you mask with a vector path? Masking is the right concept, but the wrong term in this case because to mask an image with a vector path, you simply apply a clipping path to the image. Or at least, that was the theory, and I needed some code to prove it. I decided to clip this image: with a 17 point star created with the following code:
var center = new Point(pageRect.Left+pageRect.Width/2,pageRect.Bottom+pageRect.Height/2);
var smallDim = Math.Min(img2.BoundingBox.Width,img2.BoundingBox.Height);
var cPath = createStar(center,smallDim/2,smallDim/3, 17);
static Path createStar(Point Center, double r1, double r2, int rays)
{
var starPath = new Path();
Point start = new Point(Center.H+ r1 *Math.Cos(0), Center.V+r1*Math.Sin(0));
starPath.MoveTo(start);
double angle =2*Math.PI/rays;
var halfAngle = angle /2.0;
for(int i=0; i < rays; i++)
{
var theta1 = (i * angle) + halfAngle;
var theta2 = theta1 + halfAngle;
var Point1 = new Point(Center.H + r2 * Math.Cos(theta1), Center.V + r2 * Math.Sin(theta1));
var Point2 = new Point(Center.H + r1 * Math.Cos(theta2), Center.V + r1 * Math.Sin(theta2));
starPath.AddLine(Point1);
starPath.AddLine(Point2);
}
starPath.ClosePath();
return starPath;
}
The result was:
However, I was then challenged by my neighbor at the table to turn the clip inside out, or effectively Outside In(!).
var page3 = doc.CreatePage(Document.LastPage, pageRect);
var img3 = newimage.Clone();
var clipPath2 = new Path();
clipPath2.AddRect(new Point(72,72), img3.BoundingBox.Width, img3.BoundingBox.Height);
AppendStar(ref clipPath2, center, smallDim / 2, smallDim / 3, 17);
Clip clip2 = new Clip();
clip2.AddElement(clipPath2);
img3.Clip = clip2;
page3.Content.AddElement(img3);
page3.UpdateContent();
Appending the star to the exist clip path, which contains a rectangle around the entire image, required a slight alteration to how I created the star:
static void AppendStar(ref Path existingPath, Point Center, double r1, double r2, int rays)
{
Point start = new Point(Center.H + r1 * Math.Cos(0), Center.V + r1 * Math.Sin(0));
existingPath.MoveTo(start);
double angle = 2 * Math.PI / rays;
var halfAngle = angle / 2.0;
for (int i = 0; i < rays; i++)
{
var theta1 = (i * angle) + halfAngle;
var theta2 = theta1 + halfAngle;
var Point1 = new Point(Center.H + r2 * Math.Cos(theta1), Center.V + r2 * Math.Sin(theta1));
var Point2 = new Point(Center.H + r1 * Math.Cos(theta2), Center.V + r1 * Math.Sin(theta2));
existingPath.AddLine(Point1);
existingPath.AddLine(Point2);
}
existingPath.ClosePath();
}
And this time the result was:
Mission accomplished: An image masked with a vector path.