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