/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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.rendering;
import java.awt.Color;
import java.awt.Paint;
import java.awt.PaintContext;
import java.awt.Rectangle;
import java.awt.RenderingHints;
import java.awt.geom.AffineTransform;
import java.awt.geom.Rectangle2D;
import java.awt.image.BufferedImage;
import java.awt.image.ColorModel;
import java.awt.image.Raster;
import java.awt.image.WritableRaster;
import java.io.IOException;
import org.apache.pdfbox.pdmodel.common.function.PDFunction;
import org.apache.pdfbox.pdmodel.common.function.PDFunctionTypeIdentity;
import org.apache.pdfbox.pdmodel.graphics.color.PDColor;
/**
* A Paint which applies a soft mask to an underlying Paint.
*
* @author Petr Slaby
* @author John Hewson
* @author Matthias Bläsing
* @author Tilman Hausherr
*/
class SoftMask implements Paint
{
private static final ColorModel ARGB_COLOR_MODEL =
new BufferedImage(1, 1, BufferedImage.TYPE_INT_ARGB).getColorModel();
private final Paint paint;
private final BufferedImage mask;
private final Rectangle2D bboxDevice;
private int bc = 0;
private final PDFunction transferFunction;
/**
* Creates a new soft mask paint.
*
* @param paint underlying paint.
* @param mask soft mask
* @param bboxDevice bbox of the soft mask in the underlying Graphics2D device space
* @param backdropColor the color to be used outside the transparency group’s bounding box; if
* null, black will be used.
* @param transferFunction the transfer function, may be null.
*/
SoftMask(Paint paint, BufferedImage mask, Rectangle2D bboxDevice, PDColor backdropColor, PDFunction transferFunction)
{
this.paint = paint;
this.mask = mask;
this.bboxDevice = bboxDevice;
if (transferFunction instanceof PDFunctionTypeIdentity)
{
this.transferFunction = null;
}
else
{
this.transferFunction = transferFunction;
}
if (backdropColor != null)
{
try
{
Color color = new Color(backdropColor.toRGB());
// http://stackoverflow.com/a/25463098/535646
bc = (299 * color.getRed() + 587 * color.getGreen() + 114 * color.getBlue()) / 1000;
}
catch (IOException ex)
{
// keep default
}
}
}
@Override
public PaintContext createContext(ColorModel cm, Rectangle deviceBounds,
Rectangle2D userBounds, AffineTransform xform,
RenderingHints hints)
{
PaintContext ctx = paint.createContext(cm, deviceBounds, userBounds, xform, hints);
return new SoftPaintContext(cm, deviceBounds, userBounds, xform, hints, ctx);
}
@Override
public int getTransparency()
{
return TRANSLUCENT;
}
private class SoftPaintContext implements PaintContext
{
private final PaintContext context;
SoftPaintContext(ColorModel cm, Rectangle deviceBounds, Rectangle2D userBounds,
AffineTransform xform, RenderingHints hints, PaintContext context)
{
this.context = context;
}
@Override
public ColorModel getColorModel()
{
return ARGB_COLOR_MODEL;
}
@Override
public Raster getRaster(int x1, int y1, int w, int h)
{
WritableRaster raster = (WritableRaster)context.getRaster(x1, y1, w, h);
ColorModel rasterCM = context.getColorModel();
float[] input = null;
Float[] map = null;
if (transferFunction != null)
{
map = new Float[256];
input = new float[1];
}
// buffer
WritableRaster output = getColorModel().createCompatibleWritableRaster(w, h);
// the soft mask has its own bbox
x1 = x1 - (int)bboxDevice.getX();
y1 = y1 - (int)bboxDevice.getY();
int[] gray = new int[4];
Object pixelInput = null;
int[] pixelOutput = new int[4];
for (int y = 0; y < h; y++)
{
for (int x = 0; x < w; x++)
{
pixelInput = raster.getDataElements(x, y, pixelInput);
pixelOutput[0] = rasterCM.getRed(pixelInput);
pixelOutput[1] = rasterCM.getGreen(pixelInput);
pixelOutput[2] = rasterCM.getBlue(pixelInput);
pixelOutput[3] = rasterCM.getAlpha(pixelInput);
// get the alpha value from the gray mask, if within mask bounds
gray[0] = 0;
if (x1 + x >= 0 && y1 + y >= 0 && x1 + x < mask.getWidth() && y1 + y < mask.getHeight())
{
mask.getRaster().getPixel(x1 + x, y1 + y, gray);
int g = gray[0];
if (transferFunction != null)
{
// apply transfer function
try
{
if (map[g] != null)
{
// was calculated before
pixelOutput[3] = Math.round(pixelOutput[3] * map[g]);
}
else
{
// calculate and store in map
input[0] = g / 255f;
float f = transferFunction.eval(input)[0];
map[g] = f;
pixelOutput[3] = Math.round(pixelOutput[3] * f);
}
}
catch (IOException ex)
{
// ignore exception, treat as outside
pixelOutput[3] = Math.round(pixelOutput[3] * (bc / 255f));
}
}
else
{
pixelOutput[3] = Math.round(pixelOutput[3] * (g / 255f));
}
}
else
{
pixelOutput[3] = Math.round(pixelOutput[3] * (bc / 255f));
}
output.setPixel(x, y, pixelOutput);
}
}
return output;
}
@Override
public void dispose()
{
}
}
}