swingx-master/swingx-graphics/src/main/java/org/jdesktop/swingx/graphics/FilterComposite.java
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);
    }
}
