1
votes

I have been trying to create a triangle in pdf using the apache pdf box. Using PDShadingType4 class. Below is the code implementation but it created only empty pdf. I didn't found any implementation of PDShadingType4 in examples provided in apache.

The generated triangle should look like the triangle on the bottom left of the pdf at link which is found in apache pdf box issue

I am not able to find any shading example using PDShadingType4.

Is below implementation correct ? or their is some other way to achieve shading(triangular) using PDShadingType4


    import java.io.IOException;
    import org.apache.pdfbox.cos.COSArray;
    import org.apache.pdfbox.cos.COSFloat;
    import org.apache.pdfbox.cos.COSInteger;
    import org.apache.pdfbox.cos.COSName;
    import org.apache.pdfbox.cos.COSStream;
    import org.apache.pdfbox.pdmodel.PDDocument;
    import org.apache.pdfbox.pdmodel.PDPage;
    import org.apache.pdfbox.pdmodel.PDPageContentStream;
    import org.apache.pdfbox.pdmodel.common.function.PDFunctionType2;
    import org.apache.pdfbox.pdmodel.graphics.color.PDDeviceRGB;
    import org.apache.pdfbox.pdmodel.graphics.shading.PDShading;
    import org.apache.pdfbox.pdmodel.graphics.shading.PDShadingType4;

    public class TriangleGraident2 {

        public void create(String file) throws IOException {
            PDDocument document = null;
            try {
                document = new PDDocument();
                PDPage page = new PDPage();
                document.addPage(page);

                PDPageContentStream contentStream = new PDPageContentStream(document, page, PDPageContentStream.AppendMode.APPEND, false);

                contentStream.moveTo(38, 17);

                COSStream fdict = new COSStream();
                fdict.setInt(COSName.FUNCTION_TYPE, 2);

                COSArray cosArray = new COSArray();
                cosArray.add(COSInteger.get(104));
                cosArray.add(COSInteger.get(83));
                cosArray.add(COSInteger.get(170));
                cosArray.add(COSInteger.get(17));
                cosArray.add(COSInteger.get(38));
                cosArray.add(COSInteger.get(17));


                /*Setting color */
                COSArray c0 = new COSArray();
                c0.add(COSFloat.get("1"));
                c0.add(COSFloat.get("0"));
                c0.add(COSFloat.get("0"));
                COSArray c1 = new COSArray();
                c1.add(COSFloat.get("0.5"));
                c1.add(COSFloat.get("1"));
                c1.add(COSFloat.get("0.5"));
                /*Setting color*/


                COSArray decode = new COSArray();
                decode.add(COSFloat.get("0.0"));
                decode.add(COSFloat.get("1.0"));
                decode.add(COSFloat.get("0.0"));
                decode.add(COSFloat.get("1.0"));
                decode.add(COSFloat.get("0.0"));

                fdict.setItem(COSName.C0, c0);
                fdict.setItem(COSName.C1, c1);

                PDFunctionType2 func = new PDFunctionType2(fdict);
                PDShadingType4 shading = new PDShadingType4(fdict);
                shading.setColorSpace(PDDeviceRGB.INSTANCE);
                shading.setShadingType(PDShading.SHADING_TYPE4);

                shading.getCOSObject().setInt(COSName.LENGTH, 32);

                shading.setBitsPerCoordinate(24);
                shading.setBitsPerComponent(16);
                shading.setBitsPerFlag(8);
                shading.getCOSObject().setItem(COSName.COORDS, cosArray);
                shading.setDecodeValues(decode);
                shading.setFunction(func);
                contentStream.shadingFill(shading);
                contentStream.close();
                document.save(file);
                document.close();

            }
            finally {
                if (document != null) {
                    document.close();
                }
            }
        }

        public static void main(String[] args) throws IOException {
            TriangleGraident2 creator = new TriangleGraident2();
            creator.create("C:\\Users\\abc\\Desktop\\triangle_image.pdf");
        }
    }

1
Coincidentally, it was me who created the shading you link to, but with PostScript and then converted that to PDF with ghostscript. Do you really need type 4 shadings? And not type 2 (Axial) or type 3 (radial)? Because these two have an example in the PDFBox source code, and type 4 shadings don't, because it's so difficult... Type 4 shadings are triangles like the one you mention. Usually you'd have tons of them, to create a 3D illusion. Your code misses the stream. See in the PDF specification... it contains colors, coordinates and flags. Very tricky stuff. - Tilman Hausherr
You also don't need the function stuff, it isn't required, you can get more colorful triangles by not using it. (Because if you're using functions, each edge of a triangle would be 1 value, which would then transform into n color components by the function so it is difficult to get 3 "extremes"). The content of the stream is f x y c1…cn, see p190 of the specification. I could probably implement a demo if you're really sure you need that. - Tilman Hausherr
Yes Sir I need type 4 shading.I also tried type 4 shading after reading the pdf specification as mention by you in the link you have created.but it is getting complicated I was not able to find link or documentation on how to set vertices of the triangle. Can you please correct the above code or provide the link to the code for the triangle you have created.It will be very useful to me.I think we will not be able to create triangle using type 2(Axial) or type 3(radial) shading. And after this I also need to draw polygon.So I was working with Type 4 shading - user3194123

1 Answers

2
votes

This code creates a Gouraud shaded triangle on the bottom left:

// See PDF 32000 specification,
// 8.7.4.5.5 Type 4 Shadings (Free-Form Gouraud-Shaded Triangle Meshes)
PDShadingType4 gouraudShading = new PDShadingType4(new COSStream());
gouraudShading.setShadingType(PDShading.SHADING_TYPE4);
// we use multiple of 8, so that no padding is needed
gouraudShading.setBitsPerFlag(8);
gouraudShading.setBitsPerCoordinate(16);
gouraudShading.setBitsPerComponent(8);
COSArray decodeArray = new COSArray();
// coordinates x y map 16 bits 0..FFFF to 0..FFFF to make your life easy
// so no calculation is needed, but you can only use integer coordinates
// for real numbers, you'll need smaller bounds, e.g. 0xFFFF / 0xA = 0x1999
// would allow 1 point decimal result coordinate.
// See in PDF specification: 8.9.5.2 Decode Arrays
decodeArray.add(COSInteger.ZERO);
decodeArray.add(COSInteger.get(0xFFFF));
decodeArray.add(COSInteger.ZERO);
decodeArray.add(COSInteger.get(0xFFFF));
// colors r g b map 8 bits from 0..FF to 0..1
decodeArray.add(COSInteger.ZERO);
decodeArray.add(COSInteger.ONE);
decodeArray.add(COSInteger.ZERO);
decodeArray.add(COSInteger.ONE);
decodeArray.add(COSInteger.ZERO);
decodeArray.add(COSInteger.ONE);
gouraudShading.setDecodeValues(decodeArray);
gouraudShading.setColorSpace(PDDeviceRGB.INSTANCE);

// Function is not required for type 4 shadings and not really useful, 
// because if a function would be used, each edge "color" of a triangle would be one value, 
// which would then transformed into n color components by the function so it is 
// difficult to get 3 "extremes".

OutputStream os = ((COSStream) gouraudShading.getCOSObject()).createOutputStream();
MemoryCacheImageOutputStream mcos = new MemoryCacheImageOutputStream(os);

// Vertex 1, starts with flag1
// (flags always 0 for vertices of start triangle)
mcos.writeByte(0);
// x1 y1 (left corner)
mcos.writeShort(0);
mcos.writeShort(0);
// r1 g1 b1 (red)
mcos.writeByte(0xFF);
mcos.writeByte(0);
mcos.writeByte(0);

// Vertex 2, starts with flag2
mcos.writeByte(0);
// x2 y2 (top corner)
mcos.writeShort(100);
mcos.writeShort(100);
// r2 g2 b2 (green)
mcos.writeByte(0);
mcos.writeByte(0xFF);
mcos.writeByte(0);

// Vertex 3, starts with flag3
mcos.writeByte(0);
// x3 y3 (right corner)
mcos.writeShort(200);
mcos.writeShort(0);
// r3 g3 b3 (blue)
mcos.writeByte(0);
mcos.writeByte(0);
mcos.writeByte(0xFF);

mcos.close();
// outside stream MUST be closed as well, see javadoc of MemoryCacheImageOutputStream
os.close();

to run the shading, call

contentStream.shadingFill(gouraudShading);

Gouraud shaded triangle

here's a different decode array, similar to the one from the example PDF you link to, although I used only 16 bit instead of 24:

COSArray decodeArray = new COSArray();
// coordinates x y map 16 bits 0..FFFF to -16384..16384
// this means that 0x8000 maps to 0
// some other useful values
//  - 0x862C maps to top of A4 page
//  - 0x84C4 maps to right of A4 page
//  - 0x8262 maps to horizontal middle of A4 page
decodeArray.add(COSInteger.get(-16384));
decodeArray.add(COSInteger.get(16384));
decodeArray.add(COSInteger.get(-16384));
decodeArray.add(COSInteger.get(16384));
// colors r g b map 8 bits from 0..FF to 0..1
decodeArray.add(COSInteger.ZERO);
decodeArray.add(COSInteger.ONE);
decodeArray.add(COSInteger.ZERO);
decodeArray.add(COSInteger.ONE);
decodeArray.add(COSInteger.ZERO);
decodeArray.add(COSInteger.ONE);
gouraudShading.setDecodeValues(decodeArray);

The coordinates for the triangle would then be 0x8000 0x8000, 0x8100 0x8100, 0x8200 0x8000.