使用 PDFBox 在背景中创建渐变

Creating a gradient in background with PDFBox

如何在 PDFBox 中创建渐变?或者 "can I?".

我不想创建它们并导出为 jpeg 或其他格式。我需要一个简单的文档,因此必须以某种方式对其进行编程。

有什么想法吗?

经过多方研究,终于创建了一个小"creator of my own gradient"!它看起来像这样:

COSDictionary fdict = new COSDictionary();

fdict.setInt(COSName.FUNCTION_TYPE, 2); // still not understaning that...

COSArray domain = new COSArray();
domain.add(COSInteger.get(0));
domain.add(COSInteger.get(1));

COSArray c0 = new COSArray();
c0.add(COSFloat.get("0.64176"));
c0.add(COSFloat.get("0.72588"));
c0.add(COSFloat.get("0.78078"));

COSArray c1 = new COSArray();
c1.add(COSFloat.get("0.57176"));
c1.add(COSFloat.get("0.62588"));
c1.add(COSFloat.get("0.70078"));

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

PDShadingType2 axialShading = new PDShadingType2(new COSDictionary());

axialShading.setColorSpace(PDDeviceRGB.INSTANCE);
axialShading.setShadingType(PDShading.SHADING_TYPE2);

COSArray coords1 = new COSArray();
coords1.add(COSInteger.get(0));
coords1.add(COSInteger.get(0));
coords1.add(COSInteger.get(850)); // size of my page
coords1.add(COSInteger.get(600));

axialShading.setCoords(coords1); // so this sets the bounds of my gradient
axialShading.setFunction(func); // and this determines all the curves etc?

CStr.shadingFill(axialShading); // where CStr is a ContentStream for my PDDocument

我会把这个留给其他人。留下您的意见,并随时向我展示一些改进此代码的巧妙想法:)

这是我制作的 class 以简化渐变的创建。它支持多种颜色的轴向渐变。它使用 java.awt.Color 指定颜色,但可以轻松替换。

public class PDGradient extends PDShadingType2 {

    public PDGradient(List<GradientPart> parts) {
        super(new COSDictionary());

        // PDF 1.7 - 8.7.4.5.3 Type 2 (Axial) Shadings
        setColorSpace(PDDeviceRGB.INSTANCE);
        setShadingType(PDShadingType2.SHADING_TYPE2);
        setFunction(createGradientFunction(parts));
    }

    private static PDFunction createGradientFunction(List<GradientPart> parts) {
        if (parts.size() < 2) {
            throw new IllegalArgumentException("Gradient must have at least 2 colors.");
        }

        GradientPart first = parts.get(0);
        GradientPart last = parts.get(parts.size() - 1);
        if (first.ratio != 0f) {
            throw new IllegalArgumentException("Gradient first color ratio must be 0.");
        } else if (last.ratio != 1f) {
            throw new IllegalArgumentException("Gradient last color ratio must be 1.");
        }
        if (parts.size() == 2) {
            // Only two colors, use exponential function.
            return createColorFunction(first.color, last.color);
        }

        // Multiple colors, use stitching function to combine exponential functions
        // PDF 1.7 - 7.10.4 Type 3 (Stitching) Functions
        COSDictionary dict = new COSDictionary();
        COSArray functions = new COSArray();
        COSArray bounds = new COSArray();
        COSArray encode = new COSArray();
        GradientPart lastPart = first;
        for (int i = 1; i < parts.size(); i++) {
            GradientPart part = parts.get(i);

            // Add exponential function for interpolating between these two colors.
            functions.add(createColorFunction(lastPart.color, part.color));

            // Specify function bounds, except for first and last, which are specified by domain.
            if (i != parts.size() - 1) {
                bounds.add(new COSFloat(part.ratio));
            }

            // Used to interpolate stitching function subdomain (eg: [0.2 0.5] 
            // to the exponential function domain, which is always [0.0 1.0].
            encode.add(COSInteger.ZERO);
            encode.add(COSInteger.ONE);

            lastPart = part;
        }

        dict.setInt(COSName.FUNCTION_TYPE, 3);
        dict.setItem(COSName.DOMAIN, new PDRange());  // [0.0 1.0]
        dict.setItem(COSName.FUNCTIONS, functions);
        dict.setItem(COSName.BOUNDS, bounds);
        dict.setItem(COSName.ENCODE, encode);

        return new PDFunctionType3(dict);
    }

    private static PDFunction createColorFunction(Color start, Color end) {
        // PDF 1.7 - 7.10.3 Type 2 (Exponential Interpolation) Functions
        COSDictionary dict = new COSDictionary();
        dict.setInt(COSName.FUNCTION_TYPE, 2);
        dict.setItem(COSName.DOMAIN, new PDRange());  // [0.0 1.0]
        dict.setItem(COSName.C0, createColorCOSArray(start));
        dict.setItem(COSName.C1, createColorCOSArray(end));
        dict.setInt(COSName.N, 1);  // Linear interpolation
        return new PDFunctionType2(dict);
    }

    private static COSArray createColorCOSArray(Color color) {
        // Create a COSArray for a color. 
        // java.awt.Color uses 0-255 values while PDF uses 0-1.
        COSArray a = new COSArray();
        a.add(new COSFloat(color.getRed() / 255f));
        a.add(new COSFloat(color.getGreen() / 255f));
        a.add(new COSFloat(color.getBlue() / 255f));
        return a;
    }

    /**
     * Specifies a color and its position in a {@link PDGradient}.
     */
    public static class GradientPart {

        public final Color color;
        public final float ratio;

        public GradientPart(Color color, float ratio) {
            this.color = color;
            this.ratio = ratio;
        }
    }
}

用法示例:

List<GradientPart> parts = new ArrayList<>();
parts.add(new GradientPart(Color.RED, 0.0f));
parts.add(new GradientPart(Color.YELLOW, 0.5f));
parts.add(new GradientPart(Color.GREEN, 1.0f));
PDGradient gradient = new PDGradient(parts);
gradient.setCoords(...);
pdfStream.shadingFill(gradient)

这与两种颜色渐变的其他答案基本相同,使用指数函数(类型 2)在两种颜色之间进行线性插值。如果有更多颜色,则使用拼接(类型 3)函数将具有不同子域的多个指数函数组合起来。