1 /* Copyright (C) 2005-2011 Fabio Riccardi */ 2 3 package com.lightcrafts.jai.opimage; 4 5 import com.lightcrafts.media.jai.util.ImageUtil; 6 import com.lightcrafts.media.jai.util.JDKWorkarounds; 7 import com.lightcrafts.jai.LCROIShape; 8 import com.lightcrafts.mediax.jai.*; 9 10 import java.awt.image.*; 11 import java.awt.*; 12 import java.awt.color.ColorSpace; 13 import java.util.Map; 14 import java.util.HashMap; 15 import java.util.Set; 16 17 /** 18 * Created by IntelliJ IDEA. 19 * User: fabio 20 * Date: Apr 1, 2005 21 * Time: 9:54:39 AM 22 * To change this template use File | Settings | File Templates. 23 */ 24 public class BlendOpImage extends PointOpImage { 25 26 /* Source 1 band increment */ 27 private int s1bd = 1; 28 29 /* Source 2 band increment */ 30 private int s2bd = 1; 31 32 /* Bilevel data flag. */ 33 private boolean areBinarySampleModels = false; 34 35 private int blendModeId; 36 37 private double opacity; 38 39 private ROI mask; 40 41 private final RenderedImage colorSelection; 42 43 public enum BlendingMode { 44 NORMAL ("Normal", 0), 45 AVERAGE ("Average", 1), 46 MULTIPLY ("Multiply", 2), 47 SCREEN ("Screen", 3), 48 DARKEN ("Darken", 4), 49 LIGHTEN ("Lighten", 5), 50 DIFFERENCE ("Difference", 6), 51 NEGATION ("Negation", 7), 52 EXCLUSION ("Exclusion", 8), 53 OVERLAY ("Overlay", 9), 54 HARD_LIGHT ("Hard Light", 10), 55 SOFT_LIGHT ("Soft Light", 11), 56 COLOR_DODGE ("Color Dodge", 12), 57 COLOR_BURN ("Color Burn", 13), 58 SOFT_DODGE ("Soft Dodge", 14), 59 SOFT_BURN ("Soft Burn", 15), 60 /* 61 SOFT_LIGHT2 ("Soft Light 2", 16), 62 SOFT_LIGHT3 ("Soft Light 3", 17), 63 SOFT_LIGHT4 ("Soft Light 4", 18), 64 */ 65 SHADOWS ("Shadows", 19), 66 MID_HILIGHTS("Mid+Hilights",20), 67 MIDTONES ("Midtones", 21); 68 BlendingMode(String value, int id)69 BlendingMode(String value, int id) { 70 this.name = value; 71 this.id = id; 72 } 73 74 private final String name; 75 private final int id; 76 getName()77 public String getName() { return name; } 78 } 79 80 private static Map<String, BlendingMode> modes = new HashMap<String, BlendingMode>(); 81 82 static { 83 for (BlendingMode b : BlendingMode.values()) modes.put(b.name, b)84 modes.put(b.name, b); 85 } 86 availableModes()87 public static Set<String> availableModes() { 88 return modes.keySet(); 89 } 90 BlendOpImage(RenderedImage source1, RenderedImage source2, String blendingMode, Double opacity, ROI mask, RenderedImage colorSelection, Map config, ImageLayout layout)91 public BlendOpImage(RenderedImage source1, 92 RenderedImage source2, 93 String blendingMode, 94 Double opacity, 95 ROI mask, 96 RenderedImage colorSelection, 97 Map config, 98 ImageLayout layout) { 99 super(source1, source2, layout, config, true); 100 101 if (source1.getSampleModel().getDataType() != DataBuffer.TYPE_USHORT 102 || source2.getSampleModel().getDataType() != DataBuffer.TYPE_USHORT) { 103 throw new RuntimeException("Unsupported data type, only USHORT allowed."); 104 } 105 106 BlendingMode mode = modes.get(blendingMode); 107 108 if (mode != null) { 109 blendModeId = mode.id; 110 } else { 111 String className = this.getClass().getName(); 112 throw new RuntimeException(className + 113 " unrecognized blending mode: " + blendingMode); 114 } 115 116 this.opacity = opacity.floatValue(); 117 118 if(ImageUtil.isBinary(getSampleModel()) && 119 ImageUtil.isBinary(source1.getSampleModel()) && 120 ImageUtil.isBinary(source2.getSampleModel())) { 121 // Binary processing case: RasterAccessor 122 areBinarySampleModels = true; 123 } else { 124 // Get the source band counts. 125 int numBands1 = source1.getSampleModel().getNumBands(); 126 int numBands2 = source2.getSampleModel().getNumBands(); 127 128 // Handle the special case of adding a single band image to 129 // each band of a multi-band image. 130 int numBandsDst; 131 if(layout != null && layout.isValid(ImageLayout.SAMPLE_MODEL_MASK)) { 132 SampleModel sm = layout.getSampleModel(null); 133 numBandsDst = sm.getNumBands(); 134 135 // One of the sources must be single-banded and the other must 136 // have at most the number of bands in the SampleModel hint. 137 if(numBandsDst > 1 && 138 ((numBands1 == 1 && numBands2 > 1) || 139 (numBands2 == 1 && numBands1 > 1))) { 140 // Clamp the destination band count to the number of 141 // bands in the multi-band source. 142 numBandsDst = Math.min(Math.max(numBands1, numBands2), 143 numBandsDst); 144 145 // Create a new SampleModel if necessary. 146 if(numBandsDst != sampleModel.getNumBands()) { 147 sampleModel = 148 RasterFactory.createComponentSampleModel( 149 sm, 150 sampleModel.getTransferType(), 151 sampleModel.getWidth(), 152 sampleModel.getHeight(), 153 numBandsDst); 154 155 if(colorModel != null && 156 !JDKWorkarounds.areCompatibleDataModels(sampleModel, 157 colorModel)) { 158 colorModel = 159 ImageUtil.getCompatibleColorModel(sampleModel, 160 config); 161 } 162 } 163 164 // Set the source band increments. 165 s1bd = numBands1 == 1 ? 0 : 1; 166 s2bd = numBands2 == 1 ? 0 : 1; 167 } 168 } 169 } 170 171 this.mask = mask; 172 173 // TODO: ensure that the geometry and tiling of the sources and that of the color selection match 174 this.colorSelection = colorSelection; 175 176 // Do not set flag to permit in-place operation, we dpn't produce unique tiles. 177 // permitInPlaceOperation(); 178 } 179 180 // We can return source tiles directly computesUniqueTiles()181 public boolean computesUniqueTiles() { 182 return false; 183 } 184 hasMask()185 public boolean hasMask() { 186 return mask != null; 187 } 188 getTile(int tileX, int tileY)189 public Raster getTile(int tileX, int tileY) { 190 Rectangle destRect = this.getTileRect(tileX, tileY); 191 192 if (hasMask()) 193 if (!mask.intersects(destRect)) { 194 if (opacity > 0) 195 return this.getSourceImage(1).getTile(tileX, tileY); 196 // Not a good idea, can come out with teh wrong number of bands 197 /* else if (opacity == -1) 198 return this.getSourceImage(0).getTile(tileX, tileY); */ 199 } 200 201 return super.getTile(tileX, tileY); 202 } 203 204 ColorModel grayCm = new ComponentColorModel(ColorSpace.getInstance(ColorSpace.CS_GRAY), 205 false, false, 206 Transparency.OPAQUE, 207 DataBuffer.TYPE_BYTE); 208 209 /** 210 * Adds the pixel values of two source images within a specified 211 * rectangle. 212 * 213 * @param sources Cobbled sources, guaranteed to provide all the 214 * source data necessary for computing the rectangle. 215 * @param dest The tile containing the rectangle to be computed. 216 * @param destRect The rectangle within the tile to be computed. 217 */ computeRect(Raster[] sources, WritableRaster dest, Rectangle destRect)218 protected void computeRect(Raster[] sources, 219 WritableRaster dest, 220 Rectangle destRect) { 221 if(areBinarySampleModels) { 222 String className = this.getClass().getName(); 223 throw new RuntimeException(className + 224 " does not implement computeRect" + 225 " for binary data"); 226 } 227 228 // Retrieve format tags. 229 RasterFormatTag[] formatTags = getFormatTags(); 230 231 RasterAccessor s1 = new RasterAccessor(sources[0], destRect, 232 formatTags[0], 233 getSourceImage(0).getColorModel()); 234 RasterAccessor s2 = new RasterAccessor(sources[1], destRect, 235 formatTags[1], 236 getSourceImage(1).getColorModel()); 237 RasterAccessor d = new RasterAccessor(dest, destRect, 238 formatTags[2], getColorModel()); 239 240 Raster roi = null; 241 if (hasMask()) { 242 LCROIShape lcROI = (LCROIShape) mask; 243 244 if (lcROI.intersects(destRect)) 245 roi = lcROI.getData(destRect); 246 else { 247 if (opacity > 0) { 248 assert dest.getBounds().equals(sources[1].getBounds()); 249 250 // dest.setRect(sources[1]); 251 JDKWorkarounds.setRect(dest, sources[1], 0, 0); 252 253 return; 254 } 255 // Not a good idea, can come out with teh wrong number of bands 256 /* else if (opacity == -1) { 257 assert dest.getBounds().equals(sources[0].getBounds()); 258 259 // dest.setRect(sources[0]); 260 JDKWorkarounds.setRect(dest, sources[0], 0, 0); 261 262 return; 263 } */ 264 } 265 } 266 267 RasterAccessor m = null; 268 if (roi != null) { 269 SampleModel roiSM = roi.getSampleModel(); 270 int roiFormatTagID = RasterAccessor.findCompatibleTag(null, roiSM); 271 RasterFormatTag roiFormatTag = new RasterFormatTag(roiSM, roiFormatTagID); 272 m = new RasterAccessor(roi, destRect, roiFormatTag, grayCm); 273 } 274 275 RasterAccessor cs = null; 276 if (colorSelection != null) { 277 int tilex = sources[0].getMinX() / sources[0].getWidth(); 278 int tiley = sources[0].getMinY() / sources[0].getHeight(); 279 Raster csRaster = colorSelection.getTile(tilex, tiley); 280 if (csRaster == null) { 281 csRaster = colorSelection.getData(destRect); 282 } 283 SampleModel csRasterSM = csRaster.getSampleModel(); 284 int csRasterFormatTagID = RasterAccessor.findCompatibleTag(null, csRasterSM); 285 RasterFormatTag csRasterFormatTag = new RasterFormatTag(csRasterSM, csRasterFormatTagID); 286 cs = new RasterAccessor(csRaster, destRect, csRasterFormatTag, grayCm); 287 } 288 289 switch (d.getDataType()) { 290 case DataBuffer.TYPE_USHORT: 291 computeRectUShort(s1, s2, m, cs, d); 292 break; 293 default: 294 throw new UnsupportedOperationException("Unsupported data type: " + d.getDataType()); 295 } 296 297 if (d.needsClamping()) { 298 d.clampDataArrays(); 299 } 300 d.copyDataToRaster(); 301 } 302 303 // TODO: This code only works with Pixel Interleaved sources enforce it! 304 computeRectUShort(RasterAccessor src1, RasterAccessor src2, RasterAccessor mask, RasterAccessor colorSelection, RasterAccessor dst)305 private void computeRectUShort(RasterAccessor src1, 306 RasterAccessor src2, 307 RasterAccessor mask, 308 RasterAccessor colorSelection, 309 RasterAccessor dst) { 310 int s1LineStride = src1.getScanlineStride(); 311 int s1PixelStride = src1.getPixelStride(); 312 int[] s1BandOffsets = src1.getBandOffsets(); 313 short[][] s1Data = src1.getShortDataArrays(); 314 315 int s2LineStride = src2.getScanlineStride(); 316 int s2PixelStride = src2.getPixelStride(); 317 int[] s2BandOffsets = src2.getBandOffsets(); 318 short[][] s2Data = src2.getShortDataArrays(); 319 320 int mLineStride = 0; 321 int mPixelStride = 0; 322 int mBandOffset = 0; 323 byte[] mData = null; 324 if (mask != null) { 325 mLineStride = mask.getScanlineStride(); 326 mPixelStride = mask.getPixelStride(); 327 mBandOffset = mask.getBandOffsets()[0]; 328 mData = mask.getByteDataArrays()[0]; 329 } 330 331 int csLineStride = 0; 332 int csPixelStride = 0; 333 int csBandOffset = 0; 334 byte[] csData = null; 335 if (colorSelection != null) { 336 csLineStride = colorSelection.getScanlineStride(); 337 csPixelStride = colorSelection.getPixelStride(); 338 csBandOffset = colorSelection.getBandOffsets()[0]; 339 csData = colorSelection.getByteDataArrays()[0]; 340 } 341 342 int dwidth = dst.getWidth(); 343 int dheight = dst.getHeight(); 344 int bands = dst.getNumBands(); 345 int dLineStride = dst.getScanlineStride(); 346 int dPixelStride = dst.getPixelStride(); 347 int[] dBandOffsets = dst.getBandOffsets(); 348 short[][] dData = dst.getShortDataArrays(); 349 350 int intOpacity = (int) (0xFFFF * opacity + 0.5); 351 352 short[] s1 = s1Data[0]; 353 short[] s2 = s2Data[0]; 354 byte[] m = mData; 355 byte[] cs = csData; 356 short[] d = dData[0]; 357 358 int s1LineOffset = s1BandOffsets[0]; 359 int s2LineOffset = s2BandOffsets[0]; 360 int mLineOffset = mBandOffset; 361 int csLineOffset = csBandOffset; 362 int dLineOffset = dBandOffsets[0]; 363 364 PixelBlender.cUShortLoopCS(s1, s2, d, m, cs, 365 bands, s1bd, s2bd, 366 s1LineOffset, s2LineOffset, dLineOffset, mLineOffset, csLineOffset, 367 s1LineStride, s2LineStride, dLineStride, mLineStride, csLineStride, 368 s1PixelStride, s2PixelStride, dPixelStride, mPixelStride, csPixelStride, 369 dheight, dwidth, intOpacity, blendModeId); 370 } 371 } 372