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 17 package android.graphics; 18 19 import java.awt.Composite; 20 import java.awt.CompositeContext; 21 import java.awt.RenderingHints; 22 import java.awt.image.ColorModel; 23 import java.awt.image.DataBuffer; 24 import java.awt.image.Raster; 25 import 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 */ 38 public final class BlendComposite implements Composite { 39 public enum BlendingMode { 40 MULTIPLY(), 41 SCREEN(), 42 DARKEN(), 43 LIGHTEN(), 44 OVERLAY(), 45 ADD(); 46 47 private final BlendComposite mComposite; 48 BlendingMode()49 BlendingMode() { 50 mComposite = new BlendComposite(this); 51 } 52 getBlendComposite()53 BlendComposite getBlendComposite() { 54 return mComposite; 55 } 56 } 57 58 private float alpha; 59 private BlendingMode mode; 60 BlendComposite(BlendingMode mode)61 private BlendComposite(BlendingMode mode) { 62 this(mode, 1.0f); 63 } 64 BlendComposite(BlendingMode mode, float alpha)65 private BlendComposite(BlendingMode mode, float alpha) { 66 this.mode = mode; 67 setAlpha(alpha); 68 } 69 getInstance(BlendingMode mode)70 public static BlendComposite getInstance(BlendingMode mode) { 71 return mode.getBlendComposite(); 72 } 73 getInstance(BlendingMode mode, float alpha)74 public static BlendComposite getInstance(BlendingMode mode, float alpha) { 75 if (alpha > 0.9999f) { 76 return getInstance(mode); 77 } 78 return new BlendComposite(mode, alpha); 79 } 80 getAlpha()81 public float getAlpha() { 82 return alpha; 83 } 84 getMode()85 public BlendingMode getMode() { 86 return mode; 87 } 88 setAlpha(float alpha)89 private void setAlpha(float alpha) { 90 if (alpha < 0.0f || alpha > 1.0f) { 91 assert false : "alpha must be comprised between 0.0f and 1.0f"; 92 alpha = Math.min(alpha, 1.0f); 93 alpha = Math.max(alpha, 0.0f); 94 } 95 96 this.alpha = alpha; 97 } 98 99 @Override hashCode()100 public int hashCode() { 101 return Float.floatToIntBits(alpha) * 31 + mode.ordinal(); 102 } 103 104 @Override equals(Object obj)105 public boolean equals(Object obj) { 106 if (!(obj instanceof BlendComposite)) { 107 return false; 108 } 109 110 BlendComposite bc = (BlendComposite) obj; 111 112 return mode == bc.mode && alpha == bc.alpha; 113 } 114 createContext(ColorModel srcColorModel, ColorModel dstColorModel, RenderingHints hints)115 public CompositeContext createContext(ColorModel srcColorModel, 116 ColorModel dstColorModel, 117 RenderingHints hints) { 118 return new BlendingContext(this); 119 } 120 121 private static final class BlendingContext implements CompositeContext { 122 private final Blender blender; 123 private final BlendComposite composite; 124 BlendingContext(BlendComposite composite)125 private BlendingContext(BlendComposite composite) { 126 this.composite = composite; 127 this.blender = Blender.getBlenderFor(composite); 128 } 129 dispose()130 public void dispose() { 131 } 132 compose(Raster src, Raster dstIn, WritableRaster dstOut)133 public void compose(Raster src, Raster dstIn, WritableRaster dstOut) { 134 if (src.getSampleModel().getDataType() != DataBuffer.TYPE_INT || 135 dstIn.getSampleModel().getDataType() != DataBuffer.TYPE_INT || 136 dstOut.getSampleModel().getDataType() != DataBuffer.TYPE_INT) { 137 throw new IllegalStateException( 138 "Source and destination must store pixels as INT."); 139 } 140 141 int width = Math.min(src.getWidth(), dstIn.getWidth()); 142 int height = Math.min(src.getHeight(), dstIn.getHeight()); 143 144 float alpha = composite.getAlpha(); 145 146 int[] srcPixel = new int[4]; 147 int[] dstPixel = new int[4]; 148 int[] result = new int[4]; 149 int[] srcPixels = new int[width]; 150 int[] dstPixels = new int[width]; 151 152 for (int y = 0; y < height; y++) { 153 dstIn.getDataElements(0, y, width, 1, dstPixels); 154 if (alpha != 0) { 155 src.getDataElements(0, y, width, 1, srcPixels); 156 for (int x = 0; x < width; x++) { 157 // pixels are stored as INT_ARGB 158 // our arrays are [R, G, B, A] 159 int pixel = srcPixels[x]; 160 srcPixel[0] = (pixel >> 16) & 0xFF; 161 srcPixel[1] = (pixel >> 8) & 0xFF; 162 srcPixel[2] = (pixel ) & 0xFF; 163 srcPixel[3] = (pixel >> 24) & 0xFF; 164 165 pixel = dstPixels[x]; 166 dstPixel[0] = (pixel >> 16) & 0xFF; 167 dstPixel[1] = (pixel >> 8) & 0xFF; 168 dstPixel[2] = (pixel ) & 0xFF; 169 dstPixel[3] = (pixel >> 24) & 0xFF; 170 171 // ---- Modified from original ---- 172 // recompute src pixel for transparency. 173 srcPixel[3] *= dstPixel[3] / 0xFF; 174 // ---- Modification ends ---- 175 176 result = blender.blend(srcPixel, dstPixel, result); 177 178 // mixes the result with the opacity 179 if (alpha == 1) { 180 dstPixels[x] = (result[3] & 0xFF) << 24 | 181 (result[0] & 0xFF) << 16 | 182 (result[1] & 0xFF) << 8 | 183 result[2] & 0xFF; 184 } else { 185 dstPixels[x] = 186 ((int) (dstPixel[3] + (result[3] - dstPixel[3]) * alpha) & 0xFF) << 24 | 187 ((int) (dstPixel[0] + (result[0] - dstPixel[0]) * alpha) & 0xFF) << 16 | 188 ((int) (dstPixel[1] + (result[1] - dstPixel[1]) * alpha) & 0xFF) << 8 | 189 (int) (dstPixel[2] + (result[2] - dstPixel[2]) * alpha) & 0xFF; 190 } 191 192 } 193 } 194 dstOut.setDataElements(0, y, width, 1, dstPixels); 195 } 196 } 197 } 198 199 private static abstract class Blender { blend(int[] src, int[] dst, int[] result)200 public abstract int[] blend(int[] src, int[] dst, int[] result); 201 getBlenderFor(BlendComposite composite)202 public static Blender getBlenderFor(BlendComposite composite) { 203 switch (composite.getMode()) { 204 case ADD: 205 return new Blender() { 206 @Override 207 public int[] blend(int[] src, int[] dst, int[] result) { 208 for (int i = 0; i < 4; i++) { 209 result[i] = Math.min(255, src[i] + dst[i]); 210 } 211 return result; 212 } 213 }; 214 case DARKEN: 215 return new Blender() { 216 @Override 217 public int[] blend(int[] src, int[] dst, int[] result) { 218 for (int i = 0; i < 3; i++) { 219 result[i] = Math.min(src[i], dst[i]); 220 } 221 result[3] = Math.min(255, src[3] + dst[3]); 222 return result; 223 } 224 }; 225 case LIGHTEN: 226 return new Blender() { 227 @Override 228 public int[] blend(int[] src, int[] dst, int[] result) { 229 for (int i = 0; i < 3; i++) { 230 result[i] = Math.max(src[i], dst[i]); 231 } 232 result[3] = Math.min(255, src[3] + dst[3]); 233 return result; 234 } 235 }; 236 case MULTIPLY: 237 return new Blender() { 238 @Override 239 public int[] blend(int[] src, int[] dst, int[] result) { 240 for (int i = 0; i < 3; i++) { 241 result[i] = (src[i] * dst[i]) >> 8; 242 } 243 result[3] = Math.min(255, src[3] + dst[3] - (src[3] * dst[3]) / 255); 244 return result; 245 } 246 }; 247 case OVERLAY: 248 return new Blender() { 249 @Override 250 public int[] blend(int[] src, int[] dst, int[] result) { 251 for (int i = 0; i < 3; i++) { 252 result[i] = dst[i] < 128 ? dst[i] * src[i] >> 7 : 253 255 - ((255 - dst[i]) * (255 - src[i]) >> 7); 254 } 255 result[3] = Math.min(255, src[3] + dst[3]); 256 return result; 257 } 258 }; 259 case SCREEN: 260 return new Blender() { 261 @Override 262 public int[] blend(int[] src, int[] dst, int[] result) { 263 result[0] = 255 - ((255 - src[0]) * (255 - dst[0]) >> 8); 264 result[1] = 255 - ((255 - src[1]) * (255 - dst[1]) >> 8); 265 result[2] = 255 - ((255 - src[2]) * (255 - dst[2]) >> 8); 266 result[3] = Math.min(255, src[3] + dst[3]); 267 return result; 268 } 269 }; 270 default: 271 assert false : "Blender not implement for " + composite.getMode().name(); 272 273 // Ignore the blend 274 return new Blender() { 275 @Override 276 public int[] blend(int[] src, int[] dst, int[] result) { 277 result[0] = dst[0]; 278 result[1] = dst[1]; 279 result[2] = dst[2]; 280 result[3] = dst[3]; 281 return result; 282 } 283 }; 284 } 285 } 286 } 287 } 288