Remove graphics from PDF

This code sample shows how to partly erase images from a PDF under an explicitly specified rectangle. For brushes and rectangles the namespace is mentioned explicitly to avoid confusion as both classes occur in the System.Drawing namespace as well as in the TallComponents.PDF namespace.

In the main method we replace an existing page with a newly created one. On the new page we add edited collection of shapes from the original page.

const string inputFileName = @"..\..\..\inputDocuments\redaction.pdf";
const string outputFileName = "out.pdf";

using (FileStream input = File.Open(inputFileName, FileMode.Open))
using (FileStream output = File.Create(outputFileName))
{
    // open file for editing
    Document doc = new Document(input);

    // page to edit
    Page page = doc.Pages[0];
    ShapeCollection shapes = page.CreateShapes();

    // create new page there modfied content will be stored
    Page newPage = page.Clone(PageCloneSettings.NoOriginalGraphics);

    // specify the area to clear and a brush to use
    TallComponents.PDF.Rectangle clearRect = new TallComponents.PDF.Rectangle(250, 200, 200, 400);
    System.Drawing.Brush clearBrush = Brushes.Red;

    // run the clear routine
    ClearArea(shapes, clearRect, clearBrush, new Matrix());

    // copy the edited content
    newPage.Overlay.Add(shapes);

    // replace old page with new one
    doc.Pages.RemoveAt(0);
    doc.Pages.Insert(0, newPage);

    doc.Write(output);
}

Process.Start(outputFileName);
        Const inputFileName As String = "..\..\..\inputDocuments\redaction.pdf"
        Const outputFileName As String = "out.pdf"

        Using input As FileStream = File.Open(inputFileName, FileMode.Open)
            Using output As FileStream = File.Create(outputFileName)
                ' open file for editing
                Dim doc As New Document(input)

                ' page to edit
                Dim page As Page = doc.Pages(0)
                Dim shapes As ShapeCollection = page.CreateShapes()

                ' create new page there modfied content will be stored
                Dim newPage As Page = page.Clone(PageCloneSettings.NoOriginalGraphics)

                ' specify the area to clear and a brush to use
                Dim clearRect As New TallComponents.PDF.Rectangle(250, 200, 200, 400)
                Dim clearBrush As System.Drawing.Brush = Brushes.Red

                ' run the clear routine
                ClearArea(shapes, clearRect, clearBrush, New Matrix())

                ' copy the edited content
                newPage.Overlay.Add(shapes)

                ' replace old page with new one
                doc.Pages.RemoveAt(0)
                doc.Pages.Insert(0, newPage)

                doc.Write(output)
            End Using
        End Using

        Process.Start(outputFileName)

The ClearArea method enumerates all the shapes inside the given shape collection. The method replaces all occurrences of the ImageShape(that are overlapped by the given rectangular area) with an edited ImageShape.

public static void ClearArea(ShapeCollection shapes, TallComponents.PDF.Rectangle area,
                        System.Drawing.Brush clearBrush, Matrix transform)
{
    // set the current transform for the collection
    Matrix currentTransform = GetShapeTransform(shapes, transform);

    // examine shapes in order to find the image and edit it.
    for (int i = 0; i < shapes.Count; i++)
    {
        Shape shape = shapes[i];

        if (shape is ImageShape)
        {
            // process the image shape
            ImageShape imageShape = shape as ImageShape;

            // determine whether the imageShape is overlapped by
            // the given rectangle
            TallComponents.PDF.Rectangle imageShapeRect = GetImageShapeRectangle(imageShape, currentTransform);
            RectangleF intersectionRect = IntersectRectangles(area, imageShapeRect);

            if (!intersectionRect.IsEmpty)
            {
                // clear overlapped area of the image shape
                ImageShape clearedImageShape = ClearImageArea(imageShape, intersectionRect,
                                                        clearBrush, currentTransform);

                // replace the old image shape with new one
                shapes.RemoveAt(i);
                shapes.Insert(i, clearedImageShape);
            }
        }
        else if (shape is ShapeCollection) //continue recursively
        {
            ClearArea(shape as ShapeCollection, area, clearBrush, currentTransform);
        }
    }
}


``` vb
    Public Sub ClearArea(shapes As ShapeCollection, area As TallComponents.PDF.Rectangle, clearBrush As System.Drawing.Brush, transform As Matrix)
        ' set the current transform for the collection
        Dim currentTransform As Matrix = GetShapeTransform(shapes, transform)

        ' examine shapes in order to find the image and edit it.
        For i As Integer = 0 To shapes.Count - 1
            Dim shape As Shape = shapes(i)

            If TypeOf shape Is ImageShape Then
                ' process the image shape
                Dim imageShape As ImageShape = TryCast(shape, ImageShape)

                ' determine whether the imageShape is overlapped by
                ' the given rectangle
                Dim imageShapeRect As TallComponents.PDF.Rectangle = GetImageShapeRectangle(imageShape, currentTransform)
                Dim intersectionRect As RectangleF = IntersectRectangles(area, imageShapeRect)

                If Not intersectionRect.IsEmpty Then
                    ' clear overlapped area of the image shape
                    Dim clearedImageShape As ImageShape = ClearImageArea(imageShape, intersectionRect, clearBrush, currentTransform)

                    ' replace the old image shape with new one
                    shapes.RemoveAt(i)
                    shapes.Insert(i, clearedImageShape)
                End If
            ElseIf TypeOf shape Is ShapeCollection Then
                'continue recursively
                ClearArea(TryCast(shape, ShapeCollection), area, clearBrush, currentTransform)
            End If
        Next
    End Sub

The ClearImageArea method clears parts of the image shapes that are underneath the given rectangular area using given brush.

private static ImageShape ClearImageArea(ImageShape imageShape, RectangleF area, System.Drawing.Brush clearBrush,
                                    Matrix transform)
{
    TallComponents.PDF.Rectangle shapeRect = GetImageShapeRectangle(imageShape, transform);
    double scaleX = imageShape.Width / shapeRect.Width;
    double scaleY = imageShape.Height / shapeRect.Height;

    area = new RectangleF((float)(area.X * scaleX), (float)(area.Y * scaleY),
        (float)(area.Width * scaleX), (float)(area.Height * scaleY));

    // extract bitmap and prepare for editing
    Bitmap bitmap = imageShape.CreateBitmap();

    bitmap.SetResolution((float)imageShape.HorizontalResolution, (float)imageShape.VerticalResolution);

    // clear a part of the bitmap
    using (Graphics g = Graphics.FromImage(bitmap))
    {
        if (IsFlipped(imageShape, transform))
        {
            g.Transform = new Matrix(1, 0, 0, -1, 0, bitmap.Height);
        }

        g.FillRectangle(clearBrush, area);
    }

    // return a new image shape with the edited bitmap
    return new ImageShape(bitmap) { Transform = imageShape.Transform };
}

``` vb
    Private Function ClearImageArea(imageShape As ImageShape, area As RectangleF, clearBrush As System.Drawing.Brush, transform As Matrix) As ImageShape
        Dim shapeRect As TallComponents.PDF.Rectangle = GetImageShapeRectangle(imageShape, transform)
        Dim scaleX As Double = imageShape.Width / shapeRect.Width
        Dim scaleY As Double = imageShape.Height / shapeRect.Height

        area = New RectangleF(CSng(area.X * scaleX), CSng(area.Y * scaleY), CSng(area.Width * scaleX), CSng(area.Height * scaleY))

        ' extract bitmap and prepare for editing
        Dim bitmap As Bitmap = imageShape.CreateBitmap()

        bitmap.SetResolution(CSng(imageShape.HorizontalResolution), CSng(imageShape.VerticalResolution))

        ' clear a part of the bitmap
        Using g As Graphics = Graphics.FromImage(bitmap)
            If IsFlipped(imageShape, transform) Then
                g.Transform = New Matrix(1, 0, 0, -1, 0, bitmap.Height)
            End If

            g.FillRectangle(clearBrush, area)
        End Using

        ' return a new image shape with the edited bitmap
        Dim shape = New ImageShape(bitmap)
        shape.Transform = imageShape.Transform
        Return shape
    End Function
]]></code>
</codesnippet>

Few utility methods that are used in the code above.

``` csharp
private static TallComponents.PDF.Rectangle GetImageShapeRectangle(ImageShape imageShape, Matrix transform)
{
    Matrix shapeTransform = GetShapeTransform(imageShape, transform);

    // get the transformed shape rect
    PointF[] points = new[] { new PointF(0, 0), 
                new PointF((float)(imageShape.Width), (float)(imageShape.Height)) };
    shapeTransform.TransformPoints(points);

    double width = points[1].X - points[0].X;
    double height = points[1].Y - points[0].Y;

    // flipped, so fix the rect
    if (height < 0)
    {
        PointF tmpPoint = points[0];

        points[0] = new PointF(points[0].X, points[1].Y);
        points[1] = new PointF(points[1].X, tmpPoint.Y);

        height = -height;
    }
    return new TallComponents.PDF.Rectangle(points[0].X, points[0].Y, width, height);
}

private static Matrix GetShapeTransform(ContentShape shape, Matrix transform)
{
    Matrix shapeTransform = transform.Clone();

    if (shape.Transform != null)
    {
        shapeTransform.Multiply(shape.Transform.CreateGdiMatrix());
    }
    return shapeTransform;
}

private static bool IsFlipped(ImageShape imageShape, Matrix transform)
{
    Matrix shapeTransform = GetShapeTransform(imageShape, transform);

    // get the transformed shape rect
    PointF[] points = new[] { new PointF(0, 0), 
                new PointF((float)(imageShape.Width), (float)(imageShape.Height)) };
    shapeTransform.TransformPoints(points);

    return (points[1].Y - points[0].Y < 0);
}

static RectangleF IntersectRectangles(TallComponents.PDF.Rectangle rect1, TallComponents.PDF.Rectangle rect2)
{
    double rect2Right = rect2.Left + rect2.Width;
    double rect2Top = rect2.Bottom + rect2.Height;

    double rect1Right = rect1.Left + rect1.Width;
    double rect1Top = rect1.Bottom + rect1.Height;

    if ((rect2Right < rect1.Left || rect2.Left > rect1Right) ||
        (rect2.Bottom > rect1Top || rect2Top < rect1.Bottom))
    {
        return RectangleF.Empty;
    }

    double left = Math.Max(rect1.Left, rect2.Left);
    double bottom = Math.Max(rect1.Bottom, rect2.Bottom);

    double width = Math.Min(rect1Right, rect2Right) - left;
    double height = Math.Min(rect1Top, rect2Top) - bottom;

    float x = (float)((left - rect2.Left));
    float y = (float)(rect2.Height - (bottom + height - rect2.Bottom));

    RectangleF rect = new RectangleF(x, y, (float)width, (float)height);
    return rect;
}
Private Function GetImageShapeRectangle(imageShape As ImageShape, transform As Matrix) As TallComponents.PDF.Rectangle
        Dim shapeTransform As Matrix = GetShapeTransform(imageShape, transform)

        ' get the transformed shape rect
        Dim points As PointF() = {New PointF(0, 0), New PointF(CSng(imageShape.Width), CSng(imageShape.Height))}
        shapeTransform.TransformPoints(points)

        Dim width As Double = points(1).X - points(0).X
        Dim height As Double = points(1).Y - points(0).Y

        ' flipped, so fix the rect
        If height < 0 Then
            Dim tmpPoint As PointF = points(0)

            points(0) = New PointF(points(0).X, points(1).Y)
            points(1) = New PointF(points(1).X, tmpPoint.Y)

            height = -height
        End If
        Return New TallComponents.PDF.Rectangle(points(0).X, points(0).Y, width, height)
    End Function

    Private Function GetShapeTransform(shape As ContentShape, transform As Matrix) As Matrix
        Dim shapeTransform As Matrix = transform.Clone()

        If shape.Transform IsNot Nothing Then
            shapeTransform.Multiply(shape.Transform.CreateGdiMatrix())
        End If
        Return shapeTransform
    End Function

    Private Function IsFlipped(imageShape As ImageShape, transform As Matrix) As Boolean
        Dim shapeTransform As Matrix = GetShapeTransform(imageShape, transform)

        ' get the transformed shape rect
        Dim points As PointF() = {New PointF(0, 0), New PointF(CSng(imageShape.Width), CSng(imageShape.Height))}
        shapeTransform.TransformPoints(points)

        Return (points(1).Y - points(0).Y < 0)
    End Function

    Private Function IntersectRectangles(rect1 As TallComponents.PDF.Rectangle, rect2 As TallComponents.PDF.Rectangle) As RectangleF
        Dim rect2Right As Double = rect2.Left + rect2.Width
        Dim rect2Top As Double = rect2.Bottom + rect2.Height

        Dim rect1Right As Double = rect1.Left + rect1.Width
        Dim rect1Top As Double = rect1.Bottom + rect1.Height

        If (rect2Right < rect1.Left OrElse rect2.Left > rect1Right) OrElse (rect2.Bottom > rect1Top OrElse rect2Top < rect1.Bottom) Then
            Return RectangleF.Empty
        End If

        Dim left As Double = Math.Max(rect1.Left, rect2.Left)
        Dim bottom As Double = Math.Max(rect1.Bottom, rect2.Bottom)

        Dim width As Double = Math.Min(rect1Right, rect2Right) - left
        Dim height As Double = Math.Min(rect1Top, rect2Top) - bottom

        Dim x As Single = CSng((left - rect2.Left))
        Dim y As Single = CSng(rect2.Height - (bottom + height - rect2.Bottom))

        Dim rect As New RectangleF(x, y, CSng(width), CSng(height))
        Return rect
    End Function

The original page:

The edited page. The red cross lines appear when we use the trial version of PDFKit.

output page with rectangle pdf