1 /*
2  * $RCSfile: BinarizeOpImage.java,v $
3  *
4  * Copyright (c) 2005 Sun Microsystems, Inc. All rights reserved.
5  *
6  * Use is subject to license terms.
7  *
8  * $Revision: 1.1 $
9  * $Date: 2005/02/11 04:56:15 $
10  * $State: Exp $
11  */
12 package com.lightcrafts.media.jai.opimage;
13 import java.awt.Rectangle;
14 import java.awt.image.DataBuffer;
15 import java.awt.image.Raster;
16 import java.awt.image.RenderedImage;
17 import java.awt.image.SampleModel;
18 import java.awt.image.MultiPixelPackedSampleModel;
19 import java.awt.image.ColorModel;
20 import java.awt.image.WritableRaster;
21 import java.util.Map;
22 import com.lightcrafts.mediax.jai.ImageLayout;
23 import com.lightcrafts.mediax.jai.PointOpImage;
24 import com.lightcrafts.mediax.jai.PixelAccessor;
25 import com.lightcrafts.mediax.jai.PackedImageData;
26 import com.lightcrafts.mediax.jai.UnpackedImageData;
27 import com.lightcrafts.media.jai.util.JDKWorkarounds;
28 import com.lightcrafts.media.jai.util.ImageUtil;
29 
30 /**
31  * An <code>OpImage</code> implementing the "Binarize" operation as
32  * described in <code>com.lightcrafts.mediax.jai.operator.BinarizeDescriptor</code>.
33  *
34  * <p>This <code>OpImage</code> maps all the pixels of an image
35  * whose value falls within a given range to a constant on a per-band basis.
36  * Each of the lower bound, upper bound, and constant arrays may have only
37  * one value in it. If that is the case, that value is used for all bands.
38  *
39  * @see com.lightcrafts.mediax.jai.operator.BinarizeDescriptor
40  * @see BinarizeCRIF
41  *
42  * @since version 1.1
43  */
44 final class BinarizeOpImage extends PointOpImage {
45 
46     /**
47      * Lookup table for ORing bytes of output.
48      */
49     private static byte[] byteTable = new byte[] {
50         (byte)0x80, (byte)0x40, (byte)0x20, (byte)0x10,
51         (byte)0x08, (byte)0x04, (byte)0x02, (byte)0x01,
52     };
53 
54     /**
55      *  bitsOn[j + (i<<3)]
56      *  sets bits on from i to j
57      */
58     private static int[] bitsOn = null;
59 
60     /** The threshold. */
61     private double threshold;
62 
63     /**
64      * Constructor.
65      *
66      * @param source     The source image.
67      * @param layout     The destination image layout.
68      * @param threshold  The threshold value for binarization.
69      */
BinarizeOpImage(RenderedImage source, Map config, ImageLayout layout, double threshold)70     public BinarizeOpImage(RenderedImage source,
71 			   Map config,
72 			   ImageLayout layout,
73 			   double threshold) {
74         super(source, layoutHelper(source, layout, config), config, true);
75 
76         if(source.getSampleModel().getNumBands() != 1) {
77 	    throw new IllegalArgumentException(JaiI18N.getString("BinarizeOpImage0"));
78         }
79 
80 	this.threshold = threshold;
81     }
82 
83     // set the OpImage's SM to be MultiPixelPackedSampleModel
layoutHelper(RenderedImage source, ImageLayout il, Map config)84     private static ImageLayout layoutHelper(RenderedImage source,
85                                             ImageLayout il,
86                                             Map config) {
87 
88         ImageLayout layout = (il == null) ?
89             new ImageLayout() : (ImageLayout)il.clone();
90 
91         SampleModel sm = layout.getSampleModel(source);
92         if(!ImageUtil.isBinary(sm)) {
93             sm = new MultiPixelPackedSampleModel(DataBuffer.TYPE_BYTE,
94                                                  layout.getTileWidth(source),
95                                                  layout.getTileHeight(source),
96                                                  1);
97             layout.setSampleModel(sm);
98         }
99 
100         ColorModel cm = layout.getColorModel(null);
101         if(cm == null ||
102            !JDKWorkarounds.areCompatibleDataModels(sm, cm)) {
103             layout.setColorModel(ImageUtil.getCompatibleColorModel(sm,
104                                                                    config));
105         }
106 
107         return layout;
108     }
109 
110 
111     /**
112      * Map the pixels inside a specified rectangle whose value is within a
113      * rang to a constant on a per-band basis.
114      *
115      * @param sources   Cobbled sources, guaranteed to provide all the
116      *                  source data necessary for computing the rectangle.
117      * @param dest      The tile containing the rectangle to be computed.
118      * @param destRect  The rectangle within the tile to be computed.
119      */
computeRect(Raster[] sources, WritableRaster dest, Rectangle destRect)120     protected void computeRect(Raster[] sources,
121                                WritableRaster dest,
122                                Rectangle destRect) {
123         switch (sources[0].getSampleModel().getDataType()) {
124         case DataBuffer.TYPE_BYTE:
125 	     byteLoop(sources[0], dest, destRect);
126 	     break;
127 
128         case DataBuffer.TYPE_SHORT:
129 	     shortLoop(sources[0], dest, destRect);
130 	     break;
131         case DataBuffer.TYPE_USHORT:
132 	     ushortLoop(sources[0], dest, destRect);
133 	     break;
134         case DataBuffer.TYPE_INT:
135 	     intLoop(sources[0], dest, destRect);
136 	     break;
137 
138         case DataBuffer.TYPE_FLOAT:
139 	     floatLoop(sources[0], dest, destRect);
140 	     break;
141         case DataBuffer.TYPE_DOUBLE:
142 	     doubleLoop(sources[0], dest, destRect);
143 	     break;
144 
145         default:
146 	    throw new RuntimeException(JaiI18N.getString("BinarizeOpImage1"));
147         }
148     }
149 
byteLoop(Raster source, WritableRaster dest, Rectangle destRect)150     private void byteLoop(Raster source,
151 			  WritableRaster dest,
152 			  Rectangle destRect){
153 
154         if(threshold <= 0.0D){
155 	    // every bit is 1
156 	    setTo1(dest, destRect);
157 	    return;
158 	}else if (threshold > 255.0D){
159 	    //every bit is zeros;
160 	    return;
161 	}
162 
163 	short thresholdI = (short)Math.ceil(threshold);
164 	// computation can be done in integer
165 	// even though threshold is of double type
166 	// int thresholdI = (int)Math.ceil(this.threshold);
167 	// or through a lookup table for byte case
168 
169 	Rectangle srcRect = mapDestRect(destRect,0); // should be identical to destRect
170 
171         PixelAccessor   pa  = new PixelAccessor(dest.getSampleModel(), null);
172         PackedImageData pid = pa.getPackedPixels(dest, destRect, true, false);
173 	int offset = pid.offset;
174         PixelAccessor   srcPa  = new PixelAccessor(source.getSampleModel(), null);
175 
176         UnpackedImageData srcImD = srcPa.getPixels(source, srcRect, DataBuffer.TYPE_BYTE, false);
177 	int srcOffset  = srcImD.bandOffsets[0];
178 	byte[] srcData = ((byte[][])srcImD.data)[0];
179 	int pixelStride= srcImD.pixelStride;
180 
181 	int ind0 = pid.bitOffset;
182 	for(int h = 0; h < destRect.height; h++){
183 	   int indE = ind0 + destRect.width;
184 	   for(int b = ind0, s = srcOffset; b < indE; b++, s += pixelStride){
185                if((srcData[s]&0xFF) >= thresholdI) {
186                    pid.data[offset + (b >> 3)] |= byteTable[b%8];
187                }
188 	   }
189 	   offset += pid.lineStride;
190 	   srcOffset += srcImD.lineStride;
191 	}
192 	pa.setPackedPixels(pid);
193     }
194 
195 
196     // computation in short
shortLoop(Raster source, WritableRaster dest, Rectangle destRect)197     private void shortLoop(Raster source,
198 			  WritableRaster dest,
199 			  Rectangle destRect){
200 
201         if(threshold <= Short.MIN_VALUE){
202 	    // every bit is 1
203 	    setTo1(dest, destRect);
204 	    return;
205 	}else if (threshold > Short.MAX_VALUE){
206 	    //every bit is zeros;
207 	    return;
208 	}
209 
210 	short thresholdS = (short)( Math.ceil(threshold));
211 	// computation can be done in integer
212 	// even though threshold is of double type
213 	// int thresholdI = (int)Math.ceil(this.threshold);
214 	// or through a lookup table for byte case
215 
216 	Rectangle srcRect = mapDestRect(destRect,0); // should be identical to destRect
217 
218         PixelAccessor   pa  = new PixelAccessor(dest.getSampleModel(), null);
219         PackedImageData pid = pa.getPackedPixels(dest, destRect, true, false);
220 	int offset = pid.offset;
221         PixelAccessor   srcPa  = new PixelAccessor(source.getSampleModel(), null);
222 
223         UnpackedImageData srcImD = srcPa.getPixels(source, srcRect, DataBuffer.TYPE_SHORT, false);
224 	int srcOffset  = srcImD.bandOffsets[0];
225 	short[] srcData = ((short[][])srcImD.data)[0];
226 	int pixelStride= srcImD.pixelStride;
227 
228 	int ind0 = pid.bitOffset;
229 	for(int h = 0; h < destRect.height; h++){
230 	   int indE = ind0 + destRect.width;
231 	   for(int b = ind0, s = srcOffset; b < indE; b++, s += pixelStride){
232                if(srcData[s] >= thresholdS) {
233                    pid.data[offset + (b >> 3)] |= byteTable[b%8];
234                }
235 	   }
236 	   offset += pid.lineStride;
237 	   srcOffset += srcImD.lineStride;
238 	}
239 	pa.setPackedPixels(pid);
240     }
241 
242 
243     // computation in short
ushortLoop(Raster source, WritableRaster dest, Rectangle destRect)244     private void ushortLoop(Raster source,
245 			  WritableRaster dest,
246 			  Rectangle destRect){
247 
248         if(threshold <= 0.0D){
249 	    // every bit is 1
250 	    setTo1(dest, destRect);
251 	    return;
252 	}else if (threshold > (double)(0xFFFF)){
253 	    //every bit is zeros;
254 	    return;
255 	}
256 
257 	int thresholdI = (int)( Math.ceil(threshold));
258 	// computation can be done in integer
259 	// even though threshold is of double type
260 	// int thresholdI = (int)Math.ceil(this.threshold);
261 	// or through a lookup table for byte case
262 
263 	Rectangle srcRect = mapDestRect(destRect,0); // should be identical to destRect
264 
265         PixelAccessor   pa  = new PixelAccessor(dest.getSampleModel(), null);
266         PackedImageData pid = pa.getPackedPixels(dest, destRect, true, false);
267 	int offset = pid.offset;
268         PixelAccessor   srcPa  = new PixelAccessor(source.getSampleModel(), null);
269 
270         UnpackedImageData srcImD = srcPa.getPixels(source, srcRect, DataBuffer.TYPE_USHORT, false);
271 	int srcOffset  = srcImD.bandOffsets[0];
272 	short[] srcData = ((short[][])srcImD.data)[0];
273 	int pixelStride= srcImD.pixelStride;
274 
275 	int ind0 = pid.bitOffset;
276 	for(int h = 0; h < destRect.height; h++){
277 	   int indE = ind0 + destRect.width;
278 	   for(int b = ind0, s = srcOffset; b < indE; b++, s += pixelStride){
279                if((srcData[s]&0xFFFF) >= thresholdI) {
280                    pid.data[offset + (b >> 3)] |= byteTable[b%8];
281                }
282 	   }
283 	   offset += pid.lineStride;
284 	   srcOffset += srcImD.lineStride;
285 	}
286 	pa.setPackedPixels(pid);
287     }
288 
289 
290 
intLoop(Raster source, WritableRaster dest, Rectangle destRect)291     private void intLoop(Raster source,
292 			  WritableRaster dest,
293 			  Rectangle destRect){
294 
295         if(threshold <= Integer.MIN_VALUE){
296 	    // every bit is 1
297 	    setTo1(dest, destRect);
298 	    return;
299 	}else if (threshold > (double)Integer.MAX_VALUE){
300 	    //every bit is zeros;
301 	    return;
302 	}
303 
304 	// computation can be done in integer
305 	// even though threshold is of double type
306 	int thresholdI = (int)Math.ceil(this.threshold);
307 
308 	// computation can be done in integer
309 	// even though threshold is of double type
310 	// int thresholdI = (int)Math.ceil(this.threshold);
311 
312 	Rectangle srcRect = mapDestRect(destRect,0); // should be identical to destRect
313 
314         PixelAccessor   pa  = new PixelAccessor(dest.getSampleModel(), null);
315         PackedImageData pid = pa.getPackedPixels(dest, destRect, true, false);
316 	int offset = pid.offset;
317         PixelAccessor   srcPa  = new PixelAccessor(source.getSampleModel(), null);
318 
319         UnpackedImageData srcImD = srcPa.getPixels(source, srcRect, DataBuffer.TYPE_INT, false);
320 	int srcOffset  = srcImD.bandOffsets[0];
321 	int[] srcData = ((int[][])srcImD.data)[0];
322 	int pixelStride= srcImD.pixelStride;
323 
324 	int ind0 = pid.bitOffset;
325 	for(int h = 0; h < destRect.height; h++){
326 	   int indE = ind0 + destRect.width;
327 	   for(int b = ind0, s = srcOffset; b < indE; b++, s += pixelStride){
328                if(srcData[s] >= threshold) {
329                    pid.data[offset + (b >> 3)] |= byteTable[b%8];
330                }
331            }
332 	   offset += pid.lineStride;
333 	   srcOffset += srcImD.lineStride;
334 	}
335 	pa.setPackedPixels(pid);
336     }
337 
338 
339     // computation in float
floatLoop(Raster source, WritableRaster dest, Rectangle destRect)340     private void floatLoop(Raster source,
341 			  WritableRaster dest,
342 			  Rectangle destRect){
343 
344 	Rectangle srcRect = mapDestRect(destRect,0); // should be identical to destRect
345 
346         PixelAccessor   pa  = new PixelAccessor(dest.getSampleModel(), null);
347         PackedImageData pid = pa.getPackedPixels(dest, destRect, true, false);
348 	int offset = pid.offset;
349         PixelAccessor   srcPa  = new PixelAccessor(source.getSampleModel(), null);
350 
351         UnpackedImageData srcImD = srcPa.getPixels(source, srcRect, DataBuffer.TYPE_FLOAT, false);
352 	int srcOffset  = srcImD.bandOffsets[0];
353 	float[] srcData = ((float[][])srcImD.data)[0];
354 	int pixelStride= srcImD.pixelStride;
355 
356 	int ind0 = pid.bitOffset;
357 	for(int h = 0; h < destRect.height; h++){
358 	   int indE = ind0 + destRect.width;
359 	   for(int b = ind0, s = srcOffset; b < indE; b++, s += pixelStride){
360 	     if (srcData[s]>threshold) {
361                  pid.data[offset + (b >> 3)] |= byteTable[b%8];
362              }
363 	   }
364 	   offset += pid.lineStride;
365 	   srcOffset += srcImD.lineStride;
366 	}
367 	pa.setPackedPixels(pid);
368     }
369 
370     // computation in double
doubleLoop(Raster source, WritableRaster dest, Rectangle destRect)371     private void doubleLoop(Raster source,
372 			  WritableRaster dest,
373 			  Rectangle destRect){
374 
375 	Rectangle srcRect = mapDestRect(destRect,0); // should be identical to destRect
376 
377         PixelAccessor   pa  = new PixelAccessor(dest.getSampleModel(), null);
378         PackedImageData pid = pa.getPackedPixels(dest, destRect, true, false);
379 	int offset = pid.offset;
380         PixelAccessor   srcPa  = new PixelAccessor(source.getSampleModel(), null);
381 
382         UnpackedImageData srcImD = srcPa.getPixels(source, srcRect, DataBuffer.TYPE_DOUBLE, false);
383 	int srcOffset  = srcImD.bandOffsets[0];
384 	double[] srcData = ((double[][])srcImD.data)[0];
385 	int pixelStride= srcImD.pixelStride;
386 
387 	int ind0 = pid.bitOffset;
388 	for(int h = 0; h < destRect.height; h++){
389 	   int indE = ind0 + destRect.width;
390 	   for(int b = ind0, s = srcOffset; b < indE; b++, s += pixelStride){
391 	     if (srcData[s]>threshold) {
392                  pid.data[offset + (b >> 3)] |= byteTable[b%8];
393              }
394 	   }
395 	   offset += pid.lineStride;
396 	   srcOffset += srcImD.lineStride;
397 	}
398 	pa.setPackedPixels(pid);
399     }
400 
401     // set all bits in a rectangular region to be 1
402     // need to be sure that paddings not changing
setTo1(Raster dest, Rectangle destRect)403     private void setTo1(Raster dest, Rectangle destRect){
404         initBitsOn();
405         PixelAccessor   pa  = new PixelAccessor(dest.getSampleModel(), null);
406         PackedImageData pid = pa.getPackedPixels(dest, destRect, true, false);
407 	int offset = pid.offset;
408 
409 	for(int h = 0; h < destRect.height; h++){
410 	   int ind0 = pid.bitOffset;
411 	   int indE = ind0 + destRect.width - 1;
412 	   if (indE < 8){
413 	      // the entire row in data[offset]
414 	      pid.data[offset] = (byte)(pid.data[offset] | bitsOn[indE]); // (0<<3) + indE
415 	   }else{
416     	      //1st byte
417 	      pid.data[offset] = (byte)(pid.data[offset] | bitsOn[7]); // (0<<3) + 7
418 	      //middle bytes
419 	      for(int b = offset + 1; b <= offset +  (indE-7)/8; b++){
420 		 pid.data[b] = (byte)(0xff);
421 	      }
422 	      //last byte
423 
424 	      int remBits = indE % 8;
425 	      if(remBits % 8 != 7){
426 		  indE = offset + indE/8;
427 		  pid.data[indE] = (byte)(pid.data[indE] | bitsOn[remBits]); // (0<<3)+remBits
428 	      }
429 	   }
430 	   offset += pid.lineStride;
431 	}
432 	pa.setPackedPixels(pid);
433     }
434 
435     // setting bits i to j to 1;
436     //  i <= j
initBitsOn()437     private static synchronized void initBitsOn() {
438 
439        if(bitsOn != null)
440 	  return;
441 
442        bitsOn = new int[64];
443        for(int i = 0; i < 8; i++){
444 	  for(int j = i; j< 8; j++){
445 	     int bi = (0x00ff) >> i;
446 	     int bj = (0x00ff) << (7-j);
447 	     bitsOn[j + (i<<3)] = bi & bj;
448 	  }
449        }
450     }
451 }
452