public void compose(Raster src, Raster dstIn, 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."); } int width = Math.min(src.getWidth(), dstIn.getWidth()); int height = Math.min(src.getHeight(), dstIn.getHeight()); float alpha = composite.getAlpha(); int[] srcPixel = new int[4]; int[] dstPixel = new int[4]; int[] srcPixels = new int[width]; 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; 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); } }
public static Blender getBlenderFor(BlendComposite composite) { switch (composite.getMode()) { case NORMAL: return new Blender() { @Override public int[] blend(int[] src, int[] dst) { if (src[3] == 0) { return dst; } return src; } }; case MULTIPLY: return new Blender() { @Override public int[] blend(int[] src, int[] dst) { // white stays white. if (src[3] == 0) { return 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 ADD: return new Blender() { @Override public int[] blend(int[] src, int[] dst) { return new int[] { Math.min(255, src[0] + dst[0]), Math.min(255, src[1] + dst[1]), Math.min(255, src[2] + dst[2]), Math.min(255, src[3] + dst[3]) }; } }; case AVERAGE: return new Blender() { @Override public int[] blend(int[] src, int[] dst) { return new int[] { (src[0] + dst[0]) >> 1, (src[1] + dst[1]) >> 1, (src[2] + dst[2]) >> 1, Math.min(255, src[3] + dst[3]) }; } }; case BLUE: return new Blender() { @Override public int[] blend(int[] src, int[] dst) { return new int[] {dst[0], src[1], dst[2], Math.min(255, src[3] + dst[3])}; } }; case COLOR: return new Blender() { @Override public int[] blend(int[] src, int[] dst) { float[] srcHSL = new float[3]; RGBtoHSL(src[0], src[1], src[2], srcHSL); float[] dstHSL = new float[3]; RGBtoHSL(dst[0], dst[1], dst[2], dstHSL); int[] result = new int[4]; HSLtoRGB(srcHSL[0], srcHSL[1], dstHSL[2], result); result[3] = Math.min(255, src[3] + dst[3]); return result; } }; case COLOR_BURN: return new Blender() { @Override public int[] blend(int[] src, int[] dst) { return new int[] { src[0] == 0 ? 0 : Math.max(0, 255 - (((255 - dst[0]) << 8) / src[0])), src[1] == 0 ? 0 : Math.max(0, 255 - (((255 - dst[1]) << 8) / src[1])), src[2] == 0 ? 0 : Math.max(0, 255 - (((255 - dst[2]) << 8) / src[2])), Math.min(255, src[3] + dst[3]) }; } }; case COLOR_DODGE: return new Blender() { @Override public int[] blend(int[] src, int[] dst) { return new int[] { src[0] == 255 ? 255 : Math.min((dst[0] << 8) / (255 - src[0]), 255), src[1] == 255 ? 255 : Math.min((dst[1] << 8) / (255 - src[1]), 255), src[2] == 255 ? 255 : Math.min((dst[2] << 8) / (255 - src[2]), 255), Math.min(255, src[3] + dst[3]) }; } }; case DARKEN: return new Blender() { @Override public int[] blend(int[] src, int[] dst) { return new int[] { Math.min(src[0], dst[0]), Math.min(src[1], dst[1]), Math.min(src[2], dst[2]), Math.min(255, src[3] + dst[3]) }; } }; case DIFFERENCE: return new Blender() { @Override public int[] blend(int[] src, 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]) }; } }; case EXCLUSION: return new Blender() { @Override public int[] blend(int[] src, int[] dst) { return new int[] { dst[0] + src[0] - (dst[0] * src[0] >> 7), dst[1] + src[1] - (dst[1] * src[1] >> 7), dst[2] + src[2] - (dst[2] * src[2] >> 7), Math.min(255, src[3] + dst[3]) }; } }; case FREEZE: return new Blender() { @Override public int[] blend(int[] src, int[] dst) { return new int[] { src[0] == 0 ? 0 : Math.max(0, 255 - (255 - dst[0]) * (255 - dst[0]) / src[0]), src[1] == 0 ? 0 : Math.max(0, 255 - (255 - dst[1]) * (255 - dst[1]) / src[1]), src[2] == 0 ? 0 : Math.max(0, 255 - (255 - dst[2]) * (255 - dst[2]) / src[2]), Math.min(255, src[3] + dst[3]) }; } }; case GLOW: return new Blender() { @Override public int[] blend(int[] src, int[] dst) { return new int[] { dst[0] == 255 ? 255 : Math.min(255, src[0] * src[0] / (255 - dst[0])), dst[1] == 255 ? 255 : Math.min(255, src[1] * src[1] / (255 - dst[1])), dst[2] == 255 ? 255 : Math.min(255, src[2] * src[2] / (255 - dst[2])), Math.min(255, src[3] + dst[3]) }; } }; case GREEN: return new Blender() { @Override public int[] blend(int[] src, int[] dst) { return new int[] {dst[0], dst[1], src[2], Math.min(255, src[3] + dst[3])}; } }; case HARD_LIGHT: return new Blender() { @Override public int[] blend(int[] src, int[] dst) { if (src[3] == 0) { return dst; } return new int[] { src[0] < 128 ? dst[0] * src[0] >> 7 : 255 - ((255 - src[0]) * (255 - dst[0]) >> 7), src[1] < 128 ? dst[1] * src[1] >> 7 : 255 - ((255 - src[1]) * (255 - dst[1]) >> 7), src[2] < 128 ? dst[2] * src[2] >> 7 : 255 - ((255 - src[2]) * (255 - dst[2]) >> 7), Math.min(255, src[3] + dst[3]) }; } }; case HEAT: return new Blender() { @Override public int[] blend(int[] src, int[] dst) { return new int[] { dst[0] == 0 ? 0 : Math.max(0, 255 - (255 - src[0]) * (255 - src[0]) / dst[0]), dst[1] == 0 ? 0 : Math.max(0, 255 - (255 - src[1]) * (255 - src[1]) / dst[1]), dst[2] == 0 ? 0 : Math.max(0, 255 - (255 - src[2]) * (255 - src[2]) / dst[2]), Math.min(255, src[3] + dst[3]) }; } }; case HUE: return new Blender() { @Override public int[] blend(int[] src, int[] dst) { float[] srcHSL = new float[3]; RGBtoHSL(src[0], src[1], src[2], srcHSL); float[] dstHSL = new float[3]; RGBtoHSL(dst[0], dst[1], dst[2], dstHSL); int[] result = new int[4]; HSLtoRGB(srcHSL[0], dstHSL[1], dstHSL[2], result); result[3] = Math.min(255, src[3] + dst[3]); return result; } }; case INVERSE_COLOR_BURN: return new Blender() { @Override public int[] blend(int[] src, int[] dst) { return new int[] { dst[0] == 0 ? 0 : Math.max(0, 255 - (((255 - src[0]) << 8) / dst[0])), dst[1] == 0 ? 0 : Math.max(0, 255 - (((255 - src[1]) << 8) / dst[1])), dst[2] == 0 ? 0 : Math.max(0, 255 - (((255 - src[2]) << 8) / dst[2])), Math.min(255, src[3] + dst[3]) }; } }; case INVERSE_COLOR_DODGE: return new Blender() { @Override public int[] blend(int[] src, int[] dst) { return new int[] { dst[0] == 255 ? 255 : Math.min((src[0] << 8) / (255 - dst[0]), 255), dst[1] == 255 ? 255 : Math.min((src[1] << 8) / (255 - dst[1]), 255), dst[2] == 255 ? 255 : Math.min((src[2] << 8) / (255 - dst[2]), 255), Math.min(255, src[3] + dst[3]) }; } }; case LIGHTEN: return new Blender() { @Override public int[] blend(int[] src, int[] dst) { return new int[] { Math.max(src[0], dst[0]), Math.max(src[1], dst[1]), Math.max(src[2], dst[2]), Math.min(255, src[3] + dst[3]) }; } }; case LUMINOSITY: return new Blender() { @Override public int[] blend(int[] src, int[] dst) { float[] srcHSL = new float[3]; RGBtoHSL(src[0], src[1], src[2], srcHSL); float[] dstHSL = new float[3]; RGBtoHSL(dst[0], dst[1], dst[2], dstHSL); int[] result = new int[4]; HSLtoRGB(dstHSL[0], dstHSL[1], srcHSL[2], result); result[3] = Math.min(255, src[3] + dst[3]); return result; } }; case NEGATION: return new Blender() { @Override public int[] blend(int[] src, int[] dst) { return new int[] { 255 - Math.abs(255 - dst[0] - src[0]), 255 - Math.abs(255 - dst[1] - src[1]), 255 - Math.abs(255 - dst[2] - src[2]), Math.min(255, src[3] + dst[3]) }; } }; case OVERLAY: return new Blender() { @Override public int[] blend(int[] src, int[] dst) { // screening with black leaves the underlying colour unchanged. if (src[3] == 0) { return 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, dst[3]) }; // return dst; } }; case RED: return new Blender() { @Override public int[] blend(int[] src, int[] dst) { return new int[] {src[0], dst[1], dst[2], Math.min(255, src[3] + dst[3])}; } }; case REFLECT: return new Blender() { @Override public int[] blend(int[] src, int[] dst) { return new int[] { src[0] == 255 ? 255 : Math.min(255, dst[0] * dst[0] / (255 - src[0])), src[1] == 255 ? 255 : Math.min(255, dst[1] * dst[1] / (255 - src[1])), src[2] == 255 ? 255 : Math.min(255, dst[2] * dst[2] / (255 - src[2])), Math.min(255, src[3] + dst[3]) }; } }; case SATURATION: return new Blender() { @Override public int[] blend(int[] src, int[] dst) { float[] srcHSL = new float[3]; RGBtoHSL(src[0], src[1], src[2], srcHSL); float[] dstHSL = new float[3]; RGBtoHSL(dst[0], dst[1], dst[2], dstHSL); int[] result = new int[4]; HSLtoRGB(dstHSL[0], srcHSL[1], dstHSL[2], result); result[3] = Math.min(255, src[3] + dst[3]); return result; } }; case SCREEN: return new Blender() { @Override public int[] blend(int[] src, int[] dst) { // screening with black leaves the underlying colour unchanged. if ((src[0] == 0 && src[1] == 0 && src[2] == 0)) { return dst; } // screening any colour with white, produces white. if ((dst[0] != 255 && dst[1] != 255 && dst[2] != 255)) { int[] value = new int[] { 255 - ((255 - src[0]) * (255 - dst[0]) >> 8), 255 - ((255 - src[1]) * (255 - dst[1]) >> 8), 255 - ((255 - src[2]) * (255 - dst[2]) >> 8), Math.min(255, src[3] + (dst[3])) }; return value; } return src; } }; case SOFT_BURN: return new Blender() { @Override public int[] blend(int[] src, int[] dst) { return new int[] { dst[0] + src[0] < 256 ? (dst[0] == 255 ? 255 : Math.min(255, (src[0] << 7) / (255 - dst[0]))) : Math.max(0, 255 - (((255 - dst[0]) << 7) / src[0])), dst[1] + src[1] < 256 ? (dst[1] == 255 ? 255 : Math.min(255, (src[1] << 7) / (255 - dst[1]))) : Math.max(0, 255 - (((255 - dst[1]) << 7) / src[1])), dst[2] + src[2] < 256 ? (dst[2] == 255 ? 255 : Math.min(255, (src[2] << 7) / (255 - dst[2]))) : Math.max(0, 255 - (((255 - dst[2]) << 7) / src[2])), Math.min(255, src[3] + dst[3]) }; } }; case SOFT_DODGE: return new Blender() { @Override public int[] blend(int[] src, int[] dst) { if (src[3] == 0) { return dst; } return new int[] { dst[0] + src[0] < 256 ? (src[0] == 255 ? 255 : Math.min(255, (dst[0] << 7) / (255 - src[0]))) : Math.max(0, 255 - (((255 - src[0]) << 7) / dst[0])), dst[1] + src[1] < 256 ? (src[1] == 255 ? 255 : Math.min(255, (dst[1] << 7) / (255 - src[1]))) : Math.max(0, 255 - (((255 - src[1]) << 7) / dst[1])), dst[2] + src[2] < 256 ? (src[2] == 255 ? 255 : Math.min(255, (dst[2] << 7) / (255 - src[2]))) : Math.max(0, 255 - (((255 - src[2]) << 7) / dst[2])), Math.min(255, src[3] + dst[3]) }; } }; case SOFT_LIGHT: break; case STAMP: return new Blender() { @Override public int[] blend(int[] src, int[] dst) { return new int[] { Math.max(0, Math.min(255, dst[0] + 2 * src[0] - 256)), Math.max(0, Math.min(255, dst[1] + 2 * src[1] - 256)), Math.max(0, Math.min(255, dst[2] + 2 * src[2] - 256)), Math.min(255, src[3] + dst[3]) }; } }; case SUBTRACT: return new Blender() { @Override public int[] blend(int[] src, int[] dst) { return new int[] { Math.max(0, src[0] + dst[0] - 256), Math.max(0, src[1] + dst[1] - 256), Math.max(0, src[2] + dst[2] - 256), Math.min(255, src[3] + dst[3]) }; } }; } throw new IllegalArgumentException("Blender not implement for " + composite.getMode().name()); }