package games.strategy.triplea.image; import java.awt.CompositeContext; import java.awt.RenderingHints; import java.awt.image.ColorModel; import java.awt.image.DataBuffer; import java.awt.image.Raster; import java.awt.image.WritableRaster; /** * This class handles the various types of blends for base/relief tiles. */ class BlendComposite implements java.awt.Composite { public enum BlendingMode { NORMAL, OVERLAY, MULTIPLY, DIFFERENCE, LINEAR_LIGHT } public static final BlendComposite Normal = new BlendComposite(BlendingMode.NORMAL); public static final BlendComposite Overlay = new BlendComposite(BlendingMode.OVERLAY); public static final BlendComposite Multiply = new BlendComposite(BlendingMode.MULTIPLY); public static final BlendComposite Difference = new BlendComposite(BlendingMode.DIFFERENCE); public static final BlendComposite Linear_Light = new BlendComposite(BlendingMode.LINEAR_LIGHT); private float alpha; private final BlendingMode mode; BlendComposite(final BlendingMode mode) { this(mode, 1.0f); } private BlendComposite(final BlendingMode mode, final float alpha) { this.mode = mode; setAlpha(alpha); } public static BlendComposite getInstance(final BlendingMode mode) { return new BlendComposite(mode); } public static BlendComposite getInstance(final BlendingMode mode, final float alpha) { return new BlendComposite(mode, alpha); } public BlendComposite derive(final BlendingMode mode) { return this.mode == mode ? this : new BlendComposite(mode, getAlpha()); } public BlendComposite derive(final float alpha) { return this.alpha == alpha ? this : new BlendComposite(getMode(), alpha); } public float getAlpha() { return alpha; } public BlendingMode getMode() { return mode; } private void setAlpha(final float alpha) { if (alpha < 0.0f || alpha > 1.0f) { throw new IllegalArgumentException("alpha must be comprised between 0.0f and 1.0f"); } this.alpha = alpha; } @Override public CompositeContext createContext(final ColorModel srcColorModel, final ColorModel dstColorModel, final RenderingHints hints) { return new BlendingContext(this); } private static final class BlendingContext implements CompositeContext { private final Blender blender; private final BlendComposite composite; private BlendingContext(final BlendComposite composite) { this.composite = composite; this.blender = Blender.getBlenderFor(composite); } @Override public void dispose() {} @Override public void compose(final Raster src, final Raster dstIn, final WritableRaster dstOut) { if (src.getSampleModel().getDataType() != DataBuffer.TYPE_INT || dstIn.getSampleModel().getDataType() != DataBuffer.TYPE_INT || dstOut.getSampleModel().getDataType() != DataBuffer.TYPE_INT) { throw new IllegalStateException("Source and destination must store pixels as INT."); } final int width = Math.min(src.getWidth(), dstIn.getWidth()); final int height = Math.min(src.getHeight(), dstIn.getHeight()); final float alpha = composite.getAlpha(); final int[] srcPixel = new int[4]; final int[] dstPixel = new int[4]; final int[] srcPixels = new int[width]; final int[] dstPixels = new int[width]; for (int y = 0; y < height; y++) { src.getDataElements(0, y, width, 1, srcPixels); dstIn.getDataElements(0, y, width, 1, dstPixels); for (int x = 0; x < width; x++) { // pixels are stored as INT_ARGB // our arrays are [R, G, B, A] int pixel = srcPixels[x]; srcPixel[0] = (pixel >> 16) & 0xFF; srcPixel[1] = (pixel >> 8) & 0xFF; srcPixel[2] = (pixel) & 0xFF; srcPixel[3] = (pixel >> 24) & 0xFF; pixel = dstPixels[x]; dstPixel[0] = (pixel >> 16) & 0xFF; dstPixel[1] = (pixel >> 8) & 0xFF; dstPixel[2] = (pixel) & 0xFF; dstPixel[3] = (pixel >> 24) & 0xFF; final int[] result = blender.blend(srcPixel, dstPixel); // mixes the result with the opacity dstPixels[x] = ((int) (dstPixel[3] + (result[3] - dstPixel[3]) * alpha) & 0xFF) << 24 | ((int) (dstPixel[0] + (result[0] - dstPixel[0]) * alpha) & 0xFF) << 16 | ((int) (dstPixel[1] + (result[1] - dstPixel[1]) * alpha) & 0xFF) << 8 | (int) (dstPixel[2] + (result[2] - dstPixel[2]) * alpha) & 0xFF; } dstOut.setDataElements(0, y, width, 1, dstPixels); } } } abstract static class Blender { public abstract int[] blend(int[] src, int[] dst); private static Blender getBlenderFor(final BlendComposite composite) { switch (composite.getMode()) { case NORMAL: return new Blender() { @Override public int[] blend(final int[] src, final int[] dst) { return src; } }; case OVERLAY: return new Blender() { @Override public int[] blend(final int[] src, final int[] dst) { return new int[] {dst[0] < 128 ? dst[0] * src[0] >> 7 : 255 - ((255 - dst[0]) * (255 - src[0]) >> 7), dst[1] < 128 ? dst[1] * src[1] >> 7 : 255 - ((255 - dst[1]) * (255 - src[1]) >> 7), dst[2] < 128 ? dst[2] * src[2] >> 7 : 255 - ((255 - dst[2]) * (255 - src[2]) >> 7), Math.min(255, src[3] + dst[3])}; } }; case LINEAR_LIGHT: return new Blender() { @Override public int[] blend(final int[] src, final int[] dst) { return new int[] {dst[0] < 128 ? dst[0] + src[0] >> 7 - 255 : dst[0] + (src[0] - 128) >> 7, dst[1] < 128 ? dst[1] + src[1] >> 7 - 255 : dst[1] + (src[1] - 128) >> 7, dst[2] < 128 ? dst[2] + src[2] >> 7 - 255 : dst[2] + (src[2] - 128) >> 7, Math.min(255, src[3] + dst[3])}; } }; case MULTIPLY: return new Blender() { @Override public int[] blend(final int[] src, final int[] dst) { return new int[] {(src[0] * dst[0]) >> 8, (src[1] * dst[1]) >> 8, (src[2] * dst[2]) >> 8, Math.min(255, src[3] + dst[3])}; } }; case DIFFERENCE: return new Blender() { @Override public int[] blend(final int[] src, final int[] dst) { return new int[] {Math.abs(dst[0] - src[0]), Math.abs(dst[1] - src[1]), Math.abs(dst[2] - src[2]), Math.min(255, src[3] + dst[3])}; } }; } throw new IllegalArgumentException("Blender not implement for " + composite.getMode().name()); } } }