BlendComposite.java revision c0edf09b80fc3180e29632e208775e58a24e04fb
1/* 2 * Copyright (C) 2014 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17package android.graphics; 18 19import java.awt.Composite; 20import java.awt.CompositeContext; 21import java.awt.RenderingHints; 22import java.awt.image.ColorModel; 23import java.awt.image.DataBuffer; 24import java.awt.image.Raster; 25import java.awt.image.WritableRaster; 26 27/* 28 * (non-Javadoc) 29 * The class is adapted from a demo tool for Blending Modes written by 30 * Romain Guy (romainguy@android.com). The tool is available at 31 * http://www.curious-creature.org/2006/09/20/new-blendings-modes-for-java2d/ 32 * 33 * This class has been adapted for applying color filters. When applying color filters, the src 34 * image should not extend beyond the dest image, but in our implementation of the filters, it does. 35 * To compensate for the effect, we recompute the alpha value of the src image before applying 36 * the color filter as it should have been applied. 37 */ 38public final class BlendComposite implements Composite { 39 public enum BlendingMode { 40 MULTIPLY(Multiply), 41 SCREEN(Screen), 42 DARKEN(Darken), 43 LIGHTEN(Lighten), 44 OVERLAY(Overlay), 45 ADD(Add); 46 47 private BlendComposite mComposite; 48 49 BlendingMode(BlendComposite composite) { 50 mComposite = composite; 51 } 52 53 BlendComposite getBlendComposite() { 54 return mComposite; 55 } 56 } 57 58 public static final BlendComposite Multiply = new BlendComposite(BlendingMode.MULTIPLY); 59 public static final BlendComposite Screen = new BlendComposite(BlendingMode.SCREEN); 60 public static final BlendComposite Darken = new BlendComposite(BlendingMode.DARKEN); 61 public static final BlendComposite Lighten = new BlendComposite(BlendingMode.LIGHTEN); 62 public static final BlendComposite Overlay = new BlendComposite(BlendingMode.OVERLAY); 63 public static final BlendComposite Add = new BlendComposite(BlendingMode.ADD); 64 65 private float alpha; 66 private BlendingMode mode; 67 68 private BlendComposite(BlendingMode mode) { 69 this(mode, 1.0f); 70 } 71 72 private BlendComposite(BlendingMode mode, float alpha) { 73 this.mode = mode; 74 setAlpha(alpha); 75 } 76 77 public static BlendComposite getInstance(BlendingMode mode) { 78 return mode.getBlendComposite(); 79 } 80 81 public static BlendComposite getInstance(BlendingMode mode, float alpha) { 82 if (alpha > 0.9999f) { 83 return getInstance(mode); 84 } 85 return new BlendComposite(mode, alpha); 86 } 87 88 public float getAlpha() { 89 return alpha; 90 } 91 92 public BlendingMode getMode() { 93 return mode; 94 } 95 96 private void setAlpha(float alpha) { 97 if (alpha < 0.0f || alpha > 1.0f) { 98 throw new IllegalArgumentException( 99 "alpha must be comprised between 0.0f and 1.0f"); 100 } 101 102 this.alpha = alpha; 103 } 104 105 @Override 106 public int hashCode() { 107 return Float.floatToIntBits(alpha) * 31 + mode.ordinal(); 108 } 109 110 @Override 111 public boolean equals(Object obj) { 112 if (!(obj instanceof BlendComposite)) { 113 return false; 114 } 115 116 BlendComposite bc = (BlendComposite) obj; 117 118 return mode == bc.mode && alpha == bc.alpha; 119 } 120 121 public CompositeContext createContext(ColorModel srcColorModel, 122 ColorModel dstColorModel, 123 RenderingHints hints) { 124 return new BlendingContext(this); 125 } 126 127 private static final class BlendingContext implements CompositeContext { 128 private final Blender blender; 129 private final BlendComposite composite; 130 131 private BlendingContext(BlendComposite composite) { 132 this.composite = composite; 133 this.blender = Blender.getBlenderFor(composite); 134 } 135 136 public void dispose() { 137 } 138 139 public void compose(Raster src, Raster dstIn, WritableRaster dstOut) { 140 if (src.getSampleModel().getDataType() != DataBuffer.TYPE_INT || 141 dstIn.getSampleModel().getDataType() != DataBuffer.TYPE_INT || 142 dstOut.getSampleModel().getDataType() != DataBuffer.TYPE_INT) { 143 throw new IllegalStateException( 144 "Source and destination must store pixels as INT."); 145 } 146 147 int width = Math.min(src.getWidth(), dstIn.getWidth()); 148 int height = Math.min(src.getHeight(), dstIn.getHeight()); 149 150 float alpha = composite.getAlpha(); 151 152 int[] srcPixel = new int[4]; 153 int[] dstPixel = new int[4]; 154 int[] result = new int[4]; 155 int[] srcPixels = new int[width]; 156 int[] dstPixels = new int[width]; 157 158 for (int y = 0; y < height; y++) { 159 dstIn.getDataElements(0, y, width, 1, dstPixels); 160 if (alpha != 0) { 161 src.getDataElements(0, y, width, 1, srcPixels); 162 for (int x = 0; x < width; x++) { 163 // pixels are stored as INT_ARGB 164 // our arrays are [R, G, B, A] 165 int pixel = srcPixels[x]; 166 srcPixel[0] = (pixel >> 16) & 0xFF; 167 srcPixel[1] = (pixel >> 8) & 0xFF; 168 srcPixel[2] = (pixel ) & 0xFF; 169 srcPixel[3] = (pixel >> 24) & 0xFF; 170 171 pixel = dstPixels[x]; 172 dstPixel[0] = (pixel >> 16) & 0xFF; 173 dstPixel[1] = (pixel >> 8) & 0xFF; 174 dstPixel[2] = (pixel ) & 0xFF; 175 dstPixel[3] = (pixel >> 24) & 0xFF; 176 177 // ---- Modified from original ---- 178 // recompute src pixel for transparency. 179 srcPixel[3] *= dstPixel[3] / 0xFF; 180 // ---- Modification ends ---- 181 182 result = blender.blend(srcPixel, dstPixel, result); 183 184 // mixes the result with the opacity 185 if (alpha == 1) { 186 dstPixels[x] = (result[3] & 0xFF) << 24 | 187 (result[0] & 0xFF) << 16 | 188 (result[1] & 0xFF) << 8 | 189 result[2] & 0xFF; 190 } else { 191 dstPixels[x] = 192 ((int) (dstPixel[3] + (result[3] - dstPixel[3]) * alpha) & 0xFF) << 24 | 193 ((int) (dstPixel[0] + (result[0] - dstPixel[0]) * alpha) & 0xFF) << 16 | 194 ((int) (dstPixel[1] + (result[1] - dstPixel[1]) * alpha) & 0xFF) << 8 | 195 (int) (dstPixel[2] + (result[2] - dstPixel[2]) * alpha) & 0xFF; 196 } 197 198 } 199 } 200 dstOut.setDataElements(0, y, width, 1, dstPixels); 201 } 202 } 203 } 204 205 private static abstract class Blender { 206 public abstract int[] blend(int[] src, int[] dst, int[] result); 207 208 public static Blender getBlenderFor(BlendComposite composite) { 209 switch (composite.getMode()) { 210 case ADD: 211 return new Blender() { 212 @Override 213 public int[] blend(int[] src, int[] dst, int[] result) { 214 for (int i = 0; i < 4; i++) { 215 result[i] = Math.min(255, src[i] + dst[i]); 216 } 217 return result; 218 } 219 }; 220 case DARKEN: 221 return new Blender() { 222 @Override 223 public int[] blend(int[] src, int[] dst, int[] result) { 224 for (int i = 0; i < 3; i++) { 225 result[i] = Math.min(src[i], dst[i]); 226 } 227 result[3] = Math.min(255, src[3] + dst[3]); 228 return result; 229 } 230 }; 231 case LIGHTEN: 232 return new Blender() { 233 @Override 234 public int[] blend(int[] src, int[] dst, int[] result) { 235 for (int i = 0; i < 3; i++) { 236 result[i] = Math.max(src[i], dst[i]); 237 } 238 result[3] = Math.min(255, src[3] + dst[3]); 239 return result; 240 } 241 }; 242 case MULTIPLY: 243 return new Blender() { 244 @Override 245 public int[] blend(int[] src, int[] dst, int[] result) { 246 for (int i = 0; i < 3; i++) { 247 result[i] = (src[i] * dst[i]) >> 8; 248 } 249 result[3] = Math.min(255, src[3] + dst[3] - (src[3] * dst[3]) / 255); 250 return result; 251 } 252 }; 253 case OVERLAY: 254 return new Blender() { 255 @Override 256 public int[] blend(int[] src, int[] dst, int[] result) { 257 for (int i = 0; i < 3; i++) { 258 result[i] = dst[i] < 128 ? dst[i] * src[i] >> 7 : 259 255 - ((255 - dst[i]) * (255 - src[i]) >> 7); 260 } 261 result[3] = Math.min(255, src[3] + dst[3]); 262 return result; 263 } 264 }; 265 case SCREEN: 266 return new Blender() { 267 @Override 268 public int[] blend(int[] src, int[] dst, int[] result) { 269 result[0] = 255 - ((255 - src[0]) * (255 - dst[0]) >> 8); 270 result[1] = 255 - ((255 - src[1]) * (255 - dst[1]) >> 8); 271 result[2] = 255 - ((255 - src[2]) * (255 - dst[2]) >> 8); 272 result[3] = Math.min(255, src[3] + dst[3]); 273 return result; 274 } 275 }; 276 } 277 throw new IllegalArgumentException("Blender not implement for " + 278 composite.getMode().name()); 279 } 280 } 281} 282