Aspose.PDF 23.2 - Drawing rectangles with opacity does not produce the same result multiple times

When I am drawing rectangles with the same or diferent opacity in the same page and multiple times, I am not getting the same result. The problem seems to happen when the library is traying to calculate the right color of the intersection of shapes.

Here I am adding a project to reproduce the problem. As you can see, It produces 10 documents and there is not always the same output. I am executing the code in:
openjdk version “11.0.16.1” or openjdk 17.0.5

package com.mycompany.asposeissues;

import com.aspose.pdf.*;
import com.aspose.pdf.drawing.Graph;
import com.aspose.pdf.drawing.Rectangle;
import com.mycompany.asposeissues.util.RandomUtilities;

import java.awt.geom.Rectangle2D;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;

public class Main {
    private static final Color COLOR = Color.fromArgb(51, 0, 0, 255);

    public static void main(String[] args) throws Exception {
        Locale.setDefault(Locale.US);

        // Initialize Aspose Pdf License
        final var pdfLicense = new License();
        final var stream =
                Thread.currentThread()
                        .getContextClassLoader()
                        .getResourceAsStream("Aspose.Total.Product.Family.lic");
        pdfLicense.setLicense(stream);

        var document = new Document();
        var page1 = document.getPages().add();

        drawRectangles(page1, generateNRectangles(page1, 30));
        document.save("./test.pdf");

        var randomRectangles = generateNRectangles(page1, 30);
        for (int i = 0; i < 10; i++) {
            document = new Document("./test.pdf");
            page1 = document.getPages().get_Item(1);
            drawRectangles(page1, randomRectangles);
            document.save("random" + i + ".pdf");
        }

    }

    private static List<Rectangle2D.Double> generateNRectangles(Page page, int count) {
        var pageWidth = (float) page.getPageRect(true).getWidth();
        var pageHeight = (float) page.getPageRect(true).getHeight();
        List<Rectangle2D.Double> rectangles = new ArrayList<>();
        for (int i = 0; i < count; i++) {
            rectangles.add(RandomUtilities.generateRectangle(pageWidth, pageHeight, 50));
        }
        return rectangles;
    }

    private static void drawRectangles(Page page, List<Rectangle2D.Double> rectangles) {
        final var pageInfo = page.getPageInfo();
        final var marginInfo = page.getPageInfo().getMargin();
        var graph = new Graph(
                (float) (pageInfo.getWidth()),
                (float) (pageInfo.getHeight()));
        graph.setLeft(marginInfo.getLeft() * -1);
        graph.setTop(marginInfo.getTop() * -1);
        page.getParagraphs().add(graph);

        for (var rect : rectangles) {
            final var rectangle = new Rectangle((float) rect.getX(), (float) rect.getY(), (float) rect.getWidth(), (float) rect.getHeight());
            rectangle.getGraphInfo().setFillColor(COLOR);
            graph.getShapes().add(rectangle);
        }
    }
}

package com.mycompany.asposeissues.util;

import java.awt.geom.Rectangle2D;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.Random;

public final class RandomUtilities {

    public static final double BORDER_TOLERANCE_POINTS = 0.1d;

    private static final int MINIMUM_SIZE_OF_RECTANGLE_SIDE = 5;

    private static final Random RANDOM = new Random();

    public static int getRandomInt(int max) {
        return max == 0 ? 0 : RANDOM.nextInt(max);
    }

    public static int getRandomInt(int min, int max) {
        return (int) ((Math.random() * ((max - min) + 1)) + min);
    }

    public static double getRandomDouble(double min, double max) {
        return BigDecimal
                .valueOf((Math.abs(RANDOM.nextDouble() * (max - min) + min)))
                .setScale(2, RoundingMode.FLOOR)
                .floatValue();
    }

    public static Rectangle2D.Double generateRectangle(float maxWidth, float maxHeight, final int borderFactor) {

        if (getRandomInt(100) < borderFactor)
            return generateRectangleInBorder(MINIMUM_SIZE_OF_RECTANGLE_SIDE, maxWidth, MINIMUM_SIZE_OF_RECTANGLE_SIDE, maxHeight);
        else
            return generateRectangleOutsideBorder(MINIMUM_SIZE_OF_RECTANGLE_SIDE, maxWidth, MINIMUM_SIZE_OF_RECTANGLE_SIDE, maxHeight);
    }

    public static Rectangle2D.Double generateRectangleOutsideBorder(float minWidth, float maxWidth,
                                                                    float minHeight, float maxHeight) {

        final var width = RandomUtilities.getRandomDouble(minWidth, maxWidth - BORDER_TOLERANCE_POINTS);
        final var height = RandomUtilities.getRandomDouble(minHeight, maxHeight - BORDER_TOLERANCE_POINTS);
        final var x = RandomUtilities.getRandomDouble(0, maxWidth - width - BORDER_TOLERANCE_POINTS);
        final var y = RandomUtilities.getRandomDouble(0, maxHeight - height - BORDER_TOLERANCE_POINTS);

        return new Rectangle2D.Double(x, y, width, height);
    }

    public static Rectangle2D.Double generateRectangleInBorder(float minWidth, float maxWidth,
                                                               float minHeight, float maxHeight) {

        final var border = RandomUtilities.getRandomInt(1, 4);

        final var rectangle = generateRectangleOutsideBorder(minWidth, maxWidth, minHeight, maxHeight);
        double x = rectangle.x, y = rectangle.y;

        switch (border) {
            case 1: // Left
                x = 0;
                break;
            case 2: // Top
                y = 0;
                break;
            case 3: // Right
                x = Math.max(0f, BigDecimal
                        .valueOf(maxWidth - rectangle.width - BORDER_TOLERANCE_POINTS) // contemplate rectangle border
                        .setScale(2, RoundingMode.FLOOR)
                        .floatValue());
                break;
            default: // Bottom
                y = Math.max(0f, BigDecimal
                        .valueOf(maxHeight - rectangle.height - BORDER_TOLERANCE_POINTS) // contemplate rectangle border
                        .setScale(2, RoundingMode.FLOOR)
                        .floatValue());
                break;
        }

        return new Rectangle2D.Double(x, y, rectangle.width, rectangle.height);
    }
}

Here you have some examples
random6.pdf (5.2 KB)
random7.pdf (5.2 KB)

diff.jpg (665.2 KB)

@rmachincsd,

Just to let you know. If there is an issue in that specific version, it will not be corrected there. So for starters, can you please update your library?

You are using a version that is behind several major releases. I want you to test whether the issue is still present in version 23.2. If it is, I will create a ticket for the dev team.

There is no bug correction in an older version of the API, that’s why I am suggesting you update it.

So please let me know how it goes.

Hi,

The problem still happens in 23.2 version. Let me share a project and the output.

aspose-pdf-rectangles.zip (26.7 KB)

@rmachincsd,

I was reading the code, and there is too much business logic.

Can you do a quick snippet where you draw a rectangle and fails?

Here you have a simplified example.

public class Main {

    private static final Color[] COLORS = {
            Color.fromArgb(51, 143, 101, 133), //PURPLE
            Color.fromArgb(51, 228, 102, 75), //RED
            Color.fromArgb(51, 247, 142, 82), //ORANGE
            Color.fromArgb(0, 255, 255, 255), //NONE
            Color.fromArgb(51, 255, 212, 89), //YELLOW
            Color.fromArgb(51, 76, 169, 124), //GREEN
            Color.fromArgb(51, 66, 138, 223), //BLUE
    };

    public static void main(String[] args) throws Exception {

        // Initialize Aspose Pdf License
        final var pdfLicense = new License();
        final var stream =
                Thread.currentThread()
                        .getContextClassLoader()
                        .getResourceAsStream("Aspose.Total.Product.Family.lic");
        pdfLicense.setLicense(stream);

        // Create 10 documents
        for (int docNumber = 1; docNumber <= 10; docNumber++) {

            // Load document input
            var document = new Document("./color.pdf");

            // Calculate rectangles size
            final var page = document.getPages().get_Item(1);
            var pageSize = page.getPageRect(true);
            var rectWidth = pageSize.getWidth() / 7;
            var rectHeight = pageSize.getHeight() / 7;
            float y = 0;
            int colorNumber = 0;

            // Draw rectangles
            while (y <= pageSize.getHeight()) {
                float x = 0;
                while (x <= pageSize.getWidth()) {

                    drawRectangle(page, (float) rectWidth, (float) rectHeight,
                            x, y,
                            COLORS[colorNumber++ % COLORS.length]);

                    x += rectWidth;
                }
                y += rectHeight;
            }

            // Save output
            document.save("./output-" + docNumber + ".pdf");
        }
    }

    private static void drawRectangle(final Page page,
                                      final float width, final float height,
                                      final float x, final float y,
                                      final Color background) {

        final var graph = getPageGraph(page);
        final var rectangle = new Rectangle(x, y, width, height);
        rectangle.getGraphInfo().setFillColor(background);
        rectangle.getGraphInfo().setColor(background);
        graph.getShapes().add(rectangle);
    }

    private static Graph getPageGraph(final Page page) {

        final var pageInfo = page.getPageInfo();
        final var marginInfo = page.getPageInfo().getMargin();
        var graph = new Graph(
                (float) (pageInfo.getWidth()),
                (float) (pageInfo.getHeight()));
        graph.setLeft(marginInfo.getLeft() * -1);
        graph.setTop(marginInfo.getTop() * -1);
        page.getParagraphs().add(graph);

        return graph;
    }

}

Even better, try to open color.pdf and draw 10 rectangles with drawRectangle method whatever you prefer several times. You are able to reproduce it if you use as a bacground a color with alpha 51, for instance.

@rmachincsd,

I am confused with the question because you have rectagles on top of each other with opacity.

Can you do some simple code that draw 10 rectangles that do not stack on each other and see if the problem persist?

I think the problem is that one. You have semi transparent rectangles on top of each other.

Hi carlos,
This is the problem, we have to stack and overdraw several times semi transparent rectangles in the page. We execute the same code: the same rectangles in the same positions with the same color and it produce different outputs.
It is like the intersections colors are not propertly calculated.

Saludos!

@rmachincsd,

I finally was able to reproduce the error. The issue is not the opacity, but it seems some rectangles are not drawn in some pages. I will create a ticket for the dev team.

@rmachincsd
We have opened the following new ticket(s) in our internal issue tracking system and will deliver their fixes according to the terms mentioned in Free Support Policies.

Issue ID(s): PDFNET-53938

You can obtain Paid Support Services if you need support on a priority basis, along with the direct access to our Paid Support management team.

The issues you have found earlier (filed as PDFNET-53938) have been fixed in Aspose.PDF for .NET 23.9.