package org.jdesktop.swingx.graphics; import java.awt.Composite; import java.awt.CompositeContext; import java.awt.RenderingHints; import java.awt.image.BufferedImage; import java.awt.image.BufferedImageOp; import java.awt.image.ColorModel; import java.awt.image.Raster; import java.awt.image.WritableRaster; import org.jdesktop.swingx.util.Contract; /** * A {@code FilterComposite} allows the inclusion of arbitrary image filters during the paint * processing of {@link java.awt.Graphics2D} events. By adding a filter composite, the src and * destination images are render using a delegated {@code Composite}, then post-processed with the * filters before returning the result back to the graphics context. This process adds overhead to * the painting both is terms of time (the actual processing time) and memory (as a temporary raster * must be created to store the intermediate state). Since it is possible to delegate to a filter * composite from a filter composite, this may result slow or unresponsive painting. If you are * attempting to render with many different filters, it may be better to have one filter composite * with many filters (using a compound filter). * <p> * It was decided to use {@link BufferedImageOp} as the filter because many of these filters already * exist. This gives high reusability to code. * * @author Karl Schaefer * @see org.jdesktop.swingx.image.AbstractFilter */ @SuppressWarnings("nls") public class FilterComposite implements Composite { private static class FilterContext implements CompositeContext { private ColorModel dstModel; private CompositeContext ctx; private BufferedImageOp filter; public FilterContext(ColorModel dstModel, CompositeContext ctx, BufferedImageOp filter) { Contract.asNotNull(dstModel, "dstModel cannot be null"); Contract.asNotNull(ctx, "context cannot be null"); this.dstModel = dstModel; this.ctx = ctx; this.filter = filter; } @Override public void compose(Raster src, Raster dstIn, WritableRaster dstOut) { if (filter == null) { ctx.compose(src, dstIn, dstOut); } else { WritableRaster tempOut = dstModel.createCompatibleWritableRaster(dstOut.getWidth(), dstOut.getHeight()); ctx.compose(src, dstIn, tempOut); filter.filter(new BufferedImage(dstModel, tempOut, dstModel.isAlphaPremultiplied(), null), new BufferedImage(dstModel, dstOut, dstModel.isAlphaPremultiplied(), null)); } } @Override public void dispose() { ctx = null; } } private final Composite composite; private BufferedImageOp filter; /** * Creates an empty filter composite for the specified composite. * * @param composite * the composite operation to perform prior to filtering * @throws NullPointerException * if {@code composite} is {@code null} */ public FilterComposite(Composite composite) { this(composite, null); } /** * Creates a filter for the specified composite. * * @param composite * the composite operation to perform prior to filtering * @param filter * the filter to apply to the composite result * @throws NullPointerException * if {@code composite} is {@code null} */ public FilterComposite(Composite composite, BufferedImageOp filter) { Contract.asNotNull(composite, "composite cannot be null"); this.composite = composite; this.filter = filter; } /** * The filter to apply to the graphics context. * * @return the current filter */ public BufferedImageOp getFilter() { return filter; } /** * Sets the filter for manipulating the graphics composites. * <p> * A {@code null} filter will result in no filtering. * * @param filter * the new filter */ public void setFilter(BufferedImageOp filter) { this.filter = filter; } /** * {@inheritDoc} */ @Override public CompositeContext createContext(ColorModel srcColorModel, ColorModel dstColorModel, RenderingHints hints) { return new FilterContext(dstColorModel, composite.createContext(srcColorModel, dstColorModel, hints), filter); } }