user3194123
user3194123

Reputation: 41

Implement Type 4 shadings using PDShadingType4 Apache pdf box

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");
        }
    }

Upvotes: 1

Views: 674

Answers (1)

Tilman Hausherr
Tilman Hausherr

Reputation: 18926

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.

Upvotes: 2

Related Questions