/* * Copyright 2014 The Apache Software Foundation. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.pdfbox.examples.pdmodel; import java.awt.image.BufferedImage; import java.io.File; import java.io.IOException; import java.io.OutputStream; import javax.imageio.ImageIO; import javax.imageio.stream.MemoryCacheImageOutputStream; import org.apache.pdfbox.cos.COSArray; import org.apache.pdfbox.cos.COSDictionary; 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.common.function.PDFunctionType2; import org.apache.pdfbox.pdmodel.PDPageContentStream; import org.apache.pdfbox.pdmodel.PDPageContentStream.AppendMode; import org.apache.pdfbox.pdmodel.graphics.color.PDDeviceRGB; import org.apache.pdfbox.pdmodel.graphics.shading.PDShading; import org.apache.pdfbox.pdmodel.graphics.shading.PDShadingType2; import org.apache.pdfbox.pdmodel.graphics.shading.PDShadingType3; import org.apache.pdfbox.pdmodel.graphics.shading.PDShadingType4; import org.apache.pdfbox.rendering.PDFRenderer; /** * This example creates a PDF with type 2 (axial) and type 3 (radial) shadings with a type 2 * (exponential) function, and a type 4 (gouraud triangle shading) without function. * * @author Tilman Hausherr */ public class CreateGradientShadingPDF { /** * This will create the PDF and write the contents to a file. * * @param file The name of the file to write to. * * @throws IOException If there is an error writing the data. */ public void create(String file) throws IOException { PDDocument document = null; try { document = new PDDocument(); PDPage page = new PDPage(); document.addPage(page); // type 2 (exponential) function with attributes // can be used by both shadings COSDictionary fdict = new COSDictionary(); fdict.setInt(COSName.FUNCTION_TYPE, 2); COSArray domain = new COSArray(); domain.add(COSInteger.get(0)); domain.add(COSInteger.get(1)); 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")); fdict.setItem(COSName.DOMAIN, domain); fdict.setItem(COSName.C0, c0); fdict.setItem(COSName.C1, c1); fdict.setInt(COSName.N, 1); PDFunctionType2 func = new PDFunctionType2(fdict); // axial shading with attributes PDShadingType2 axialShading = new PDShadingType2(new COSDictionary()); axialShading.setColorSpace(PDDeviceRGB.INSTANCE); axialShading.setShadingType(PDShading.SHADING_TYPE2); COSArray coords1 = new COSArray(); coords1.add(COSInteger.get(100)); coords1.add(COSInteger.get(400)); coords1.add(COSInteger.get(400)); coords1.add(COSInteger.get(600)); axialShading.setCoords(coords1); axialShading.setFunction(func); // radial shading with attributes PDShadingType3 radialShading = new PDShadingType3(new COSDictionary()); radialShading.setColorSpace(PDDeviceRGB.INSTANCE); radialShading.setShadingType(PDShading.SHADING_TYPE3); COSArray coords2 = new COSArray(); coords2.add(COSInteger.get(100)); coords2.add(COSInteger.get(400)); coords2.add(COSInteger.get(50)); // radius1 coords2.add(COSInteger.get(400)); coords2.add(COSInteger.get(600)); coords2.add(COSInteger.get(150)); // radius2 radialShading.setCoords(coords2); radialShading.setFunction(func); // Gouraud shading // 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 corner "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". // fill the vertex stream try (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); } // invoke shading from content stream // compress parameter is set to false so that you can see the stream in a text editor try (PDPageContentStream contentStream = new PDPageContentStream(document, page, AppendMode.APPEND, false)) { contentStream.shadingFill(axialShading); contentStream.shadingFill(radialShading); contentStream.shadingFill(gouraudShading); } document.save(file); document.close(); // render the PDF and save it into a PNG file document = PDDocument.load(new File(file)); BufferedImage bim = new PDFRenderer(document).renderImageWithDPI(0, 100); ImageIO.write(bim, "png", new File(file + ".png")); document.close(); } finally { if (document != null) { document.close(); } } } /** * This will create a blank document. * * @param args The command line arguments. * * @throws IOException If there is an error writing the document data. */ public static void main(String[] args) throws IOException { if (args.length != 1) { usage(); } else { CreateGradientShadingPDF creator = new CreateGradientShadingPDF(); creator.create(args[0]); } } /** * This will print the usage of this class. */ private static void usage() { System.err.println("usage: java " + CreateGradientShadingPDF.class.getName() + " <outputfile.pdf>"); } }