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