001: /*
002: * $RCSfile: SubsampleBinaryToGray2x2OpImage.java,v $
003: *
004: * Copyright (c) 2005 Sun Microsystems, Inc. All rights reserved.
005: *
006: * Use is subject to license terms.
007: *
008: * $Revision: 1.1 $
009: * $Date: 2005/02/11 04:56:44 $
010: * $State: Exp $
011: */
012: package com.sun.media.jai.opimage;
013:
014: import java.awt.Rectangle;
015: import java.awt.geom.Point2D;
016: import java.awt.image.Raster;
017: import java.awt.image.RenderedImage;
018: import java.awt.image.WritableRaster;
019: import java.awt.Point;
020: import java.util.Hashtable;
021: import java.awt.Rectangle;
022: import java.awt.image.DataBuffer;
023: import java.awt.image.DataBufferByte;
024: import java.awt.image.DataBufferInt;
025: import java.awt.image.DataBufferUShort;
026: import java.awt.image.IndexColorModel;
027: import java.awt.image.MultiPixelPackedSampleModel;
028: import java.awt.image.PixelInterleavedSampleModel;
029: import java.awt.image.SinglePixelPackedSampleModel;
030: import java.awt.image.SampleModel;
031: import java.awt.image.renderable.ParameterBlock;
032: import javax.media.jai.ImageLayout;
033: import java.util.Map;
034: import javax.media.jai.GeometricOpImage;
035: import javax.media.jai.PackedImageData;
036: import javax.media.jai.PixelAccessor;
037:
038: /**
039: * A class extending <code>GeometricOpImage</code> to
040: * subsample binary images to gray scale images.
041: * This class provides an acceleration for a special
042: * case of SubsampleBinaryToGrayOpImage, when the
043: * scaling factors in x and y directions are 1/2.
044: *
045: * <code>
046: * dst minX = floor(src minX /2 )
047: * dst minY = floor(src minY /2 )
048: * dst width = floor(src width / 2)
049: * dst height = floor(src height / 2)
050: * </code>
051: *
052: * @see ScaleOpImage
053: * @see SubsampleBinaryToGrayOpImage
054: *
055: */
056: class SubsampleBinaryToGray2x2OpImage extends GeometricOpImage {
057:
058: /** block pixel size; to shrink to one pixel */
059: private int blockX;
060: private int blockY;
061:
062: /** destination image width */
063: private int dWidth;
064: /** destination image height*/
065: private int dHeight;
066:
067: /** the 1st pixel location for destination pixels, i.e.,
068: * the source pixel matrix
069: * [yValues[j] yValues[j]+blockY] by [xValues[i] xValues[i]+blockX]
070: * will be condensed to form pixel <code>i</code>th pixel in row <code>j</code>
071: */
072:
073: // a look up table; lut[i] counts 1s in binary expression of i
074: // lut4_45 counts 1s in bit 4,5 in i&0x0f, the last 8 bits of i
075: // lut4_67 counts 1s in bit 6,7 in i&0x0f, the last 8 bits of i
076: private int[] lut4_45;
077: private int[] lut4_67;
078:
079: // convert from number of bits on count to gray value, with
080: // scaling, i.e. if invScaleX,Y=3,3, then the possible bit
081: // counts are 0..9, hence the lookup tables are [0..9] * 255/9.
082: // there are 4 kinds of scaling, depending on area size
083: // when invScaleX,Y are non integers,
084: // [floor(invScaleY), ceil(invScaleY)] x [floor(invScaleX), ceil(invScaleX)]
085: private byte[] lutGray;
086:
087: /**
088: * Constructs a <code>SubsampleBinaryToGray2x2OpImage</code> from a <code>RenderedImage</code>
089: * source, an optional <code>BorderExtender</code>, x and y scale
090: * and translation factors, and an <code>Interpolation</code>
091: * object. The image dimensions are determined by forward-mapping
092: * the source bounds, and are passed to the superclass constructor
093: * by means of the <code>layout</code> parameter. Other fields of
094: * the layout are passed through unchanged. If
095: * <code>layout</code> is <code>null</code>, a new
096: * <code>ImageLayout</code> will be constructor to hold the bounds
097: * information.
098: *
099: * Note that the scale factors are represented internally as Rational
100: * numbers in order to workaround inexact device specific representation
101: * of floating point numbers. For instance the floating point number 1.2
102: * is internally represented as 1.200001, which can throw the
103: * calculations off during a forward/backward map.
104: *
105: * <p> The Rational approximation is valid upto the sixth decimal place.
106: *
107: * @param layout an <code>ImageLayout</code> optionally containing
108: * the tile grid layout, <code>SampleModel</code>, and
109: * <code>ColorModel</code>, or <code>null</code>.
110: * @param source a <code>RenderedImage</code>.
111:
112: * from this <code>OpImage</code>, or <code>null</code>. If
113: * <code>null</code>, no caching will be performed.
114: * @param cobbleSources a boolean indicating whether
115: * <code>computeRect</code> expects contiguous sources.
116: * @param extender a <code>BorderExtender</code>, or <code>null</code>.
117: * @param interp an <code>Interpolation</code> object to use for
118: * resampling.
119: * @param scaleX scale factor along x axis.
120: * @param scaleY scale factor along y axis.
121: *
122: * @throws IllegalArgumentException if combining the
123: * source bounds with the layout parameter results in negative
124: * output width or height.
125: */
126: public SubsampleBinaryToGray2x2OpImage(RenderedImage source,
127: ImageLayout layout, Map config) {
128:
129: super (vectorize(source), SubsampleBinaryToGrayOpImage
130: .layoutHelper(source, 1.0F / 2, 1.0F / 2, layout,
131: config), config, true, // cobbleSources,
132: null, // extender
133: null, // interpolation
134: null);
135:
136: blockX = 2;
137: blockY = 2;
138: int srcWidth = source.getWidth();
139: int srcHeight = source.getHeight();
140:
141: dWidth = srcWidth / blockX;
142: dHeight = srcHeight / blockY;
143:
144: if (extender == null) {
145: computableBounds = new Rectangle(0, 0, dWidth, dHeight);
146: } else {
147: // If extender is present we can write the entire destination.
148: computableBounds = getBounds();
149: }
150:
151: // these can be delayed, such as placed in computeRect()
152: buildLookupTables();
153: }
154:
155: /**
156: * Computes the source point corresponding to the supplied point.
157: *
158: * @param destPt the position in destination image coordinates
159: * to map to source image coordinates.
160: *
161: * @return a <code>Point2D</code> of the same class as
162: * <code>destPt</code>.
163: *
164: * @throws IllegalArgumentException if <code>destPt</code> is
165: * <code>null</code>.
166: *
167: * @since JAI 1.1.2
168: */
169: public Point2D mapDestPoint(Point2D destPt) {
170: if (destPt == null) {
171: throw new IllegalArgumentException(JaiI18N
172: .getString("Generic0"));
173: }
174:
175: Point2D pt = (Point2D) destPt.clone();
176:
177: pt.setLocation(destPt.getX() * 2.0, destPt.getY() * 2.0);
178:
179: return pt;
180: }
181:
182: /**
183: * Computes the destination point corresponding to the supplied point.
184: *
185: * @param sourcePt the position in source image coordinates
186: * to map to destination image coordinates.
187: *
188: * @return a <code>Point2D</code> of the same class as
189: * <code>sourcePt</code>.
190: *
191: * @throws IllegalArgumentException if <code>sourcePt</code> is
192: * <code>null</code>.
193: *
194: * @since JAI 1.1.2
195: */
196: public Point2D mapSourcePoint(Point2D sourcePt) {
197: if (sourcePt == null) {
198: throw new IllegalArgumentException(JaiI18N
199: .getString("Generic0"));
200: }
201:
202: Point2D pt = (Point2D) sourcePt.clone();
203:
204: pt.setLocation(sourcePt.getX() / 2.0, sourcePt.getY() / 2.0);
205:
206: return pt;
207: }
208:
209: /**
210: * Returns the minimum bounding box of the region of the destination
211: * to which a particular <code>Rectangle</code> of the specified source
212: * will be mapped.
213: *
214: * @param sourceRect the <code>Rectangle</code> in source coordinates.
215: * @param sourceIndex the index of the source image.
216: *
217: * @return a <code>Rectangle</code> indicating the destination
218: * bounding box, or <code>null</code> if the bounding box
219: * is unknown.
220: *
221: * @throws IllegalArgumentException if <code>sourceIndex</code> is
222: * negative or greater than the index of the last source.
223: * @throws IllegalArgumentException if <code>sourceRect</code> is
224: * <code>null</code>.
225: */
226: protected Rectangle forwardMapRect(Rectangle sourceRect,
227: int sourceIndex) {
228:
229: if (sourceRect == null) {
230: throw new IllegalArgumentException(JaiI18N
231: .getString("Generic0"));
232: }
233:
234: if (sourceIndex != 0) {
235: throw new IllegalArgumentException(JaiI18N
236: .getString("Generic1"));
237: }
238:
239: // Get the source dimensions
240:
241: int dx0 = sourceRect.x / blockX;
242: int dy0 = sourceRect.y / blockY;
243: int dx1 = (sourceRect.x + sourceRect.width - 1) / blockX;
244: int dy1 = (sourceRect.y + sourceRect.height - 1) / blockY;
245:
246: return new Rectangle(dx0, dy0, dx1 - dx0 + 1, dy1 - dy0 + 1);
247: }
248:
249: /**
250: * Returns the minimum bounding box of the region of the specified
251: * source to which a particular <code>Rectangle</code> of the
252: * destination will be mapped.
253: *
254: * @param destRect the <code>Rectangle</code> in destination coordinates.
255: * @param sourceIndex the index of the source image.
256: *
257: * @return a <code>Rectangle</code> indicating the source bounding box,
258: * or <code>null</code> if the bounding box is unknown.
259: *
260: * @throws IllegalArgumentException if <code>sourceIndex</code> is
261: * negative or greater than the index of the last source.
262: * @throws IllegalArgumentException if <code>destRect</code> is
263: * <code>null</code>.
264: */
265: protected Rectangle backwardMapRect(Rectangle destRect,
266: int sourceIndex) {
267:
268: if (destRect == null) {
269: throw new IllegalArgumentException(JaiI18N
270: .getString("Generic0"));
271: }
272:
273: if (sourceIndex != 0) {
274: throw new IllegalArgumentException(JaiI18N
275: .getString("Generic1"));
276: }
277:
278: // Get the destination rectangle coordinates and dimensions
279: int sx0 = destRect.x * blockX;
280: int sy0 = destRect.y * blockY;
281: int sx1 = (destRect.x + destRect.width - 1) * blockX;
282: int sy1 = (destRect.y + destRect.height - 1) * blockY;
283:
284: return new Rectangle(sx0, sy0, sx1 - sx0 + blockX, sy1 - sy0
285: + blockY);
286: }
287:
288: /**
289: * Performs a subsamplebinarytogray operation on a specified rectangle.
290: * The sources are cobbled.
291: *
292: * @param sources an array of source Rasters, guaranteed to provide all
293: * necessary source data for computing the output.
294: * @param dest a WritableRaster containing the area to be computed.
295: * @param destRect the rectangle within dest to be processed.
296: */
297: protected void computeRect(Raster[] sources, WritableRaster dest,
298: Rectangle destRect) {
299: Raster source = sources[0];
300:
301: switch (source.getSampleModel().getDataType()) {
302: case DataBuffer.TYPE_BYTE:
303: case DataBuffer.TYPE_SHORT:
304: case DataBuffer.TYPE_USHORT:
305: case DataBuffer.TYPE_INT:
306: byteLoop2x2(source, dest, destRect);
307: break;
308: default:
309: throw new RuntimeException(JaiI18N
310: .getString("SubsampleBinaryToGrayOpImage0"));
311: }
312: }
313:
314: private void byteLoop2x2(Raster source, WritableRaster dest,
315: Rectangle destRect) {
316: PixelAccessor pa = new PixelAccessor(source.getSampleModel(),
317: null);
318: PackedImageData pid = pa.getPackedPixels(source, source
319: .getBounds(), false, false);
320: byte[] sourceData = pid.data;
321: int sourceDBOffset = pid.offset;
322: int dx = destRect.x;
323: int dy = destRect.y;
324: int dwi = destRect.width;
325: int dhi = destRect.height;
326: int sourceTransX = pid.rect.x; // source.getSampleModelTranslateX();
327: int sourceTransY = pid.rect.y; // source.getSampleModelTranslateY();
328: int sourceDataBitOffset = pid.bitOffset;
329: int sourceScanlineStride = pid.lineStride;
330:
331: PixelInterleavedSampleModel destSM = (PixelInterleavedSampleModel) dest
332: .getSampleModel();
333: DataBufferByte destDB = (DataBufferByte) dest.getDataBuffer();
334: int destTransX = dest.getSampleModelTranslateX();
335: int destTransY = dest.getSampleModelTranslateY();
336: int destScanlineStride = destSM.getScanlineStride();
337:
338: byte[] destData = destDB.getData();
339: int destDBOffset = destDB.getOffset();
340:
341: int[] sAreaBitsOn = new int[4];
342:
343: if ((sourceDataBitOffset & 0x01) == 0) {
344:
345: for (int j = 0; j < dhi; j++) {
346: int y = (dy + j) << 1; // y = (dy+j) * blockY;
347: int sourceYOffset = (y - sourceTransY)
348: * sourceScanlineStride + sourceDBOffset;
349: int sourceYOffset2 = sourceYOffset
350: + sourceScanlineStride;
351:
352: int destYOffset = (j + dy - destTransY)
353: * destScanlineStride + destDBOffset;
354: destYOffset += dx - destTransX;
355:
356: int selement, sbitnumi, sstartbiti, sbytenumi;
357: // sbitnumi - the 1st bit position from the minX of the raster
358: // sstartbiti - the 1st bit position in the byte data
359: //sbitnumi = blockX * dx - sourceTransX + sourceDataBitOffset;
360: sbitnumi = (dx << 1) - sourceTransX
361: + sourceDataBitOffset;
362: for (int i = 0; i < dwi;) {
363: sbytenumi = sbitnumi >> 3;
364:
365: sstartbiti = sbitnumi % 8;
366: selement = 0x00ff & (int) sourceData[sourceYOffset
367: + sbytenumi];
368:
369: sAreaBitsOn[2] = lut4_45[selement & 0x000f];
370: sAreaBitsOn[3] = lut4_67[selement & 0x000f];
371: selement >>= 4;
372: sAreaBitsOn[0] = lut4_45[selement];
373: sAreaBitsOn[1] = lut4_67[selement];
374:
375: // next line
376: selement = 0x00ff & (int) sourceData[sourceYOffset2
377: + sbytenumi];
378: sAreaBitsOn[2] += lut4_45[selement & 0x000f];
379: sAreaBitsOn[3] += lut4_67[selement & 0x000f];
380: selement >>= 4;
381: sAreaBitsOn[0] += lut4_45[selement];
382: sAreaBitsOn[1] += lut4_67[selement];
383:
384: // set dest elements
385: // count in 2s
386: // sstartbiti = 0 means the 0th of sAreaBitsOn is added to
387: // current dest position, i.e. destYOffset + i;
388: // sstartbiti = 2 means the 1th of sAreaBitsOn is added to
389: // current dest position, i.e. destYOffset + i;
390: // sstartbiti now means different
391: sstartbiti >>= 1; // sstartbiti = sstartbiti / 2;
392:
393: while (sstartbiti < 4 && i < dwi) {
394: destData[destYOffset + i] = lutGray[sAreaBitsOn[sstartbiti]];
395: sstartbiti++;
396: i++;
397: sbitnumi += blockX;
398: }
399: }
400: }
401: } else {
402: // need to shift one bit a lot of the time
403: for (int j = 0; j < dhi; j++) {
404: int y = (dy + j) << 1; // y = (dy+j) * blockY;
405: int sourceYOffset = (y - sourceTransY)
406: * sourceScanlineStride + sourceDBOffset;
407: int sourceYOffset2 = sourceYOffset
408: + sourceScanlineStride;
409:
410: int destYOffset = (j + dy - destTransY)
411: * destScanlineStride + destDBOffset;
412: destYOffset += dx - destTransX;
413:
414: int selement, sbitnumi, sstartbiti, sbytenumi;
415: // sbitnumi - the 1st bit position from the minX of the raster
416: // sstartbiti - the 1st bit position in the byte data
417: // sbitnumi = blockX * dx - sourceTransX + sourceDataBitOffset;
418: sbitnumi = (dx << 1) - sourceTransX
419: + sourceDataBitOffset;
420:
421: for (int i = 0; i < dwi;) {
422: sbytenumi = sbitnumi >> 3;
423:
424: sstartbiti = sbitnumi % 8;
425: // shift one bit, so that we can use almost the same code
426: // as even bitOffset cases as above
427: selement = 0x00ff & (sourceData[sourceYOffset
428: + sbytenumi] << 1);
429:
430: sAreaBitsOn[2] = lut4_45[selement & 0x000f];
431: sAreaBitsOn[3] = lut4_67[selement & 0x000f];
432: selement >>= 4;
433: sAreaBitsOn[0] = lut4_45[selement];
434: sAreaBitsOn[1] = lut4_67[selement];
435:
436: // next line
437: // shift one bit
438: selement = 0x00ff & (sourceData[sourceYOffset2
439: + sbytenumi] << 1);
440: sAreaBitsOn[2] += lut4_45[selement & 0x000f];
441: sAreaBitsOn[3] += lut4_67[selement & 0x000f];
442: selement >>= 4;
443: sAreaBitsOn[0] += lut4_45[selement];
444: sAreaBitsOn[1] += lut4_67[selement];
445:
446: // taking care the extra bit that is in the next byte (<0 means 1 for the 1st bit)
447: // when there is one more byte to go on this line
448: // as long as there is more data in the buffer
449: // adding to the last one is ok; will not be used if out side of raster bounds
450: sbytenumi += 1; // move to next byte
451: if (sbytenumi < sourceData.length - sourceYOffset2) {
452: sAreaBitsOn[3] += sourceData[sourceYOffset
453: + sbytenumi] < 0 ? 1 : 0;
454: sAreaBitsOn[3] += sourceData[sourceYOffset2
455: + sbytenumi] < 0 ? 1 : 0;
456: }
457:
458: // set dest elements
459: // count in 2s, this corresponds to i th dest
460: // sstartbiti now means different
461:
462: sstartbiti >>= 1; // sstartbiti = sstartbiti / 2;
463:
464: while (sstartbiti < 4 && i < dwi) {
465: destData[destYOffset + i] = lutGray[sAreaBitsOn[sstartbiti]];
466: sstartbiti++;
467: i++;
468: sbitnumi += blockX;
469: }
470:
471: }
472: }
473: }
474: }
475:
476: // shortLoop and intLoop are not needed, due to PixelAccessor or RasterAccessor's
477: // returns byte packing for binary data
478: // private void shortLoop(Raster source, WritableRaster dest, Rectangle destRect) {;}
479: // private void intLoop(Raster source, WritableRaster dest, Rectangle destRect) {;}
480:
481: // buildLookupTables()
482: // initializes variabes bitSet and lut
483: // to be called mainly in the constructor
484: private final void buildLookupTables() {
485: lut4_45 = new int[16];
486: lut4_67 = new int[16];
487: // 6-7th bits on
488: lut4_67[0] = 0;
489: lut4_67[1] = 1;
490: lut4_67[2] = 1;
491: lut4_67[3] = 2;
492: for (int i = 4; i < 16; i++)
493: lut4_67[i] = lut4_67[i & 0x03];
494: // 4 and 5 th bits
495: for (int i = 0; i < 16; i++)
496: lut4_45[i] = lut4_67[i >> 2];
497:
498: // lutGray
499: if (lutGray != null)
500: return;
501: lutGray = new byte[blockX * blockY + 1];
502: for (int i = 0; i < lutGray.length; i++) {
503: int tmp = (int) Math.round(255.0F * i
504: / (lutGray.length - 1.0F));
505: lutGray[i] = tmp > 255 ? (byte) 0xff : (byte) tmp;
506: }
507:
508: // switch black-white if needed
509: if (SubsampleBinaryToGrayOpImage.isMinWhite(this
510: .getSourceImage(0).getColorModel())) {
511: for (int i = 0; i < lutGray.length; i++)
512: lutGray[i] = (byte) (255 - (0xff & lutGray[i]));
513: }
514: }
515: }
|