001: /*
002: * $RCSfile: SubsampleBinaryToGray4x4OpImage.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. Image scaling operations
041: * require rectilinear backwards mapping and padding by the resampling
042: * filter dimensions.
043: *
044: * <p> When applying scale factors of scaleX, scaleY to a source image
045: * with width of src_width and height of src_height, the resulting image
046: * is defined to have the following bounds:
047: *
048: * <code>
049: * dst minX = floor(src minX * scaleX + transX)
050: * dst minY = floor(src minY * scaleY + transY)
051: * dst width = floor(src width * scaleX)
052: * dst height = floor(src height * scaleY)
053: * </code>
054: *
055: * <p> When interpolations which require padding the source such as Bilinear
056: * or Bicubic interpolation are specified, the source needs to be extended
057: * such that it has the extra pixels needed to compute all the destination
058: * pixels. This extension is performed via the <code>BorderExtender</code>
059: * class. The type of border extension can be specified as a
060: * <code>RenderingHint</code> to the <code>JAI.create</code> method.
061: *
062: * <p> If no <code>BorderExtender</code> is specified, the source will
063: * not be extended. The scaled image size is still calculated
064: * according to the formula specified above. However since there is not
065: * enough source to compute all the destination pixels, only that
066: * subset of the destination image's pixels which can be computed,
067: * will be written in the destination. The rest of the destination
068: * will be set to zeros.
069: *
070: * @see ScaleOpImage
071: *
072: */
073:
074: class SubsampleBinaryToGray4x4OpImage extends GeometricOpImage {
075: private int blockX = 4;
076: private int blockY = 4;
077:
078: /** destination image width */
079: private int dWidth;
080: /** destination image height*/
081: private int dHeight;
082:
083: /** the 1st pixel location for destination pixels, i.e.,
084: * the source pixel matrix
085: * [yValues[j] yValues[j]+blockY] by [xValues[i] xValues[i]+blockX]
086: * will be condensed to form pixel <code>i</code>th pixel in row <code>j</code>
087: */
088: private int[] xValues;
089: private int[] yValues;
090:
091: // a look up table; lut[i] counts 1s in binary expression of i
092: private int[] lut;
093:
094: // convert from number of bits on count to gray value, with
095: // scaling, i.e. if invScaleX,Y=3,3, then the possible bit
096: // counts are 0..9, hence the lookup tables are [0..9] * 255/9.
097: // there are 4 kinds of scaling, depending on area size
098: // when invScaleX,Y are non integers,
099: // [floor(invScaleY), ceil(invScaleY)] x [floor(invScaleX), ceil(invScaleX)]
100: private byte[] lutGray;
101:
102: /**
103: * Constructs a <code>SubsampleBinaryToGray4x4OpImage</code> from a <code>RenderedImage</code>
104: * source, an optional <code>BorderExtender</code>, x and y scale
105: * and translation factors, and an <code>Interpolation</code>
106: * object. The image dimensions are determined by forward-mapping
107: * the source bounds, and are passed to the superclass constructor
108: * by means of the <code>layout</code> parameter. Other fields of
109: * the layout are passed through unchanged. If
110: * <code>layout</code> is <code>null</code>, a new
111: * <code>ImageLayout</code> will be constructor to hold the bounds
112: * information.
113: *
114: * Note that the scale factors are represented internally as Rational
115: * numbers in order to workaround inexact device specific representation
116: * of floating point numbers. For instance the floating point number 1.2
117: * is internally represented as 1.200001, which can throw the
118: * calculations off during a forward/backward map.
119: *
120: * <p> The Rational approximation is valid upto the sixth decimal place.
121: *
122: * @param layout an <code>ImageLayout</code> optionally containing
123: * the tile grid layout, <code>SampleModel</code>, and
124: * <code>ColorModel</code>, or <code>null</code>.
125: * @param source a <code>RenderedImage</code>.
126:
127: * from this <code>OpImage</code>, or <code>null</code>. If
128: * <code>null</code>, no caching will be performed.
129: * @param cobbleSources a boolean indicating whether
130: * <code>computeRect</code> expects contiguous sources.
131: * @param extender a <code>BorderExtender</code>, or <code>null</code>.
132: * @param interp an <code>Interpolation</code> object to use for
133: * resampling.
134: * @param scaleX scale factor along x axis.
135: * @param scaleY scale factor along y axis.
136: *
137: * @throws IllegalArgumentException if combining the
138: * source bounds with the layout parameter results in negative
139: * output width or height.
140: */
141: public SubsampleBinaryToGray4x4OpImage(RenderedImage source,
142: ImageLayout layout, Map config) {
143:
144: super (vectorize(source), SubsampleBinaryToGrayOpImage
145: .layoutHelper(source, 1.0F / 4, 1.0F / 4, layout,
146: config), config, true, // cobbleSources,
147: null, // extender
148: null, // interpolation
149: null);
150:
151: int srcWidth = source.getWidth();
152: int srcHeight = source.getHeight();
153:
154: blockX = blockY = 4;
155:
156: dWidth = srcWidth / blockX;
157: dHeight = srcHeight / blockY;
158:
159: if (extender == null) {
160: computableBounds = new Rectangle(0, 0, dWidth, dHeight);
161: } else {
162: // If extender is present we can write the entire destination.
163: computableBounds = getBounds();
164: }
165:
166: // these can be delayed, such as placed in computeRect()
167: buildLookupTables();
168:
169: // compute the begining bit position of each row and column
170: computeXYValues(dWidth, dHeight);
171: }
172:
173: /**
174: * Computes the source point corresponding to the supplied point.
175: *
176: * @param destPt the position in destination image coordinates
177: * to map to source image coordinates.
178: *
179: * @return a <code>Point2D</code> of the same class as
180: * <code>destPt</code>.
181: *
182: * @throws IllegalArgumentException if <code>destPt</code> is
183: * <code>null</code>.
184: *
185: * @since JAI 1.1.2
186: */
187: public Point2D mapDestPoint(Point2D destPt) {
188: if (destPt == null) {
189: throw new IllegalArgumentException(JaiI18N
190: .getString("Generic0"));
191: }
192:
193: Point2D pt = (Point2D) destPt.clone();
194:
195: pt.setLocation(destPt.getX() * 4.0, destPt.getY() * 4.0);
196:
197: return pt;
198: }
199:
200: /**
201: * Computes the destination point corresponding to the supplied point.
202: *
203: * @param sourcePt the position in source image coordinates
204: * to map to destination image coordinates.
205: *
206: * @return a <code>Point2D</code> of the same class as
207: * <code>sourcePt</code>.
208: *
209: * @throws IllegalArgumentException if <code>sourcePt</code> is
210: * <code>null</code>.
211: *
212: * @since JAI 1.1.2
213: */
214: public Point2D mapSourcePoint(Point2D sourcePt) {
215: if (sourcePt == null) {
216: throw new IllegalArgumentException(JaiI18N
217: .getString("Generic0"));
218: }
219:
220: Point2D pt = (Point2D) sourcePt.clone();
221:
222: pt.setLocation(sourcePt.getX() / 4.0, sourcePt.getY() / 4.0);
223:
224: return pt;
225: }
226:
227: /**
228: * Returns the minimum bounding box of the region of the destination
229: * to which a particular <code>Rectangle</code> of the specified source
230: * will be mapped.
231: *
232: * @param sourceRect the <code>Rectangle</code> in source coordinates.
233: * @param sourceIndex the index of the source image.
234: *
235: * @return a <code>Rectangle</code> indicating the destination
236: * bounding box, or <code>null</code> if the bounding box
237: * is unknown.
238: *
239: * @throws IllegalArgumentException if <code>sourceIndex</code> is
240: * negative or greater than the index of the last source.
241: * @throws IllegalArgumentException if <code>sourceRect</code> is
242: * <code>null</code>.
243: */
244: protected Rectangle forwardMapRect(Rectangle sourceRect,
245: int sourceIndex) {
246:
247: if (sourceRect == null) {
248: throw new IllegalArgumentException(JaiI18N
249: .getString("Generic0"));
250: }
251:
252: if (sourceIndex != 0) {
253: throw new IllegalArgumentException(JaiI18N
254: .getString("Generic1"));
255: }
256:
257: // Get the source dimensions
258: int x0 = sourceRect.x;
259: int y0 = sourceRect.y;
260: int dx0 = x0 / blockX;
261: int dy0 = y0 / blockY;
262:
263: int x1 = sourceRect.x + sourceRect.width - 1;
264: int y1 = sourceRect.y + sourceRect.height - 1;
265:
266: int dx1 = x1 / blockX;
267: int dy1 = y1 / blockY;
268:
269: // Return the writable destination area
270: return new Rectangle(dx0, dy0, dx1 - dx0 + 1, dy1 - dy0 + 1);
271: }
272:
273: /**
274: * Returns the minimum bounding box of the region of the specified
275: * source to which a particular <code>Rectangle</code> of the
276: * destination will be mapped.
277: *
278: * @param destRect the <code>Rectangle</code> in destination coordinates.
279: * @param sourceIndex the index of the source image.
280: *
281: * @return a <code>Rectangle</code> indicating the source bounding box,
282: * or <code>null</code> if the bounding box is unknown.
283: *
284: * @throws IllegalArgumentException if <code>sourceIndex</code> is
285: * negative or greater than the index of the last source.
286: * @throws IllegalArgumentException if <code>destRect</code> is
287: * <code>null</code>.
288: */
289: protected Rectangle backwardMapRect(Rectangle destRect,
290: int sourceIndex) {
291:
292: if (destRect == null) {
293: throw new IllegalArgumentException(JaiI18N
294: .getString("Generic0"));
295: }
296:
297: if (sourceIndex != 0) {
298: throw new IllegalArgumentException(JaiI18N
299: .getString("Generic1"));
300: }
301:
302: // Get the destination rectangle coordinates and dimensions
303: int sx0 = destRect.x * blockX;
304: int sy0 = destRect.y * blockY;
305: int sx1 = (destRect.x + destRect.width - 1) * blockX;
306: int sy1 = (destRect.y + destRect.height - 1) * blockY;
307:
308: return new Rectangle(sx0, sy0, sx1 - sx0 + blockX, sy1 - sy0
309: + blockY);
310: }
311:
312: /**
313: * Performs a subsamplebinarytogray operation on a specified rectangle.
314: * The sources are cobbled.
315: *
316: * @param sources an array of source Rasters, guaranteed to provide all
317: * necessary source data for computing the output.
318: * @param dest a WritableRaster containing the area to be computed.
319: * @param destRect the rectangle within dest to be processed.
320: */
321: protected void computeRect(Raster[] sources, WritableRaster dest,
322: Rectangle destRect) {
323: Raster source = sources[0];
324:
325: switch (source.getSampleModel().getDataType()) {
326: case DataBuffer.TYPE_BYTE:
327: case DataBuffer.TYPE_SHORT:
328: case DataBuffer.TYPE_USHORT:
329: case DataBuffer.TYPE_INT:
330: byteLoop4x4(source, dest, destRect);
331: break;
332: default:
333: throw new RuntimeException(JaiI18N
334: .getString("SubsampleBinaryToGrayOpImage0"));
335: }
336: }
337:
338: // speed up for the case of 4x4
339: // and data buffer bitOffset is 0 or 4
340: private void byteLoop4x4(Raster source, WritableRaster dest,
341: Rectangle destRect) {
342: PixelAccessor pa = new PixelAccessor(source.getSampleModel(),
343: null);
344: PackedImageData pid = pa.getPackedPixels(source, source
345: .getBounds(), false, false);
346:
347: if (pid.bitOffset % 4 != 0) {
348: // special treatment only for offsets 0 and 4
349: byteLoop(source, dest, destRect);
350: return;
351: }
352:
353: byte[] sourceData = pid.data;
354: int sourceDBOffset = pid.offset;
355: int dx = destRect.x;
356: int dy = destRect.y;
357: int dwi = destRect.width;
358: int dhi = destRect.height;
359: int sourceTransX = pid.rect.x; // source.getSampleModelTranslateX();
360: int sourceTransY = pid.rect.y; // source.getSampleModelTranslateY();
361: int sourceDataBitOffset = pid.bitOffset;
362: int sourceScanlineStride = pid.lineStride;
363:
364: PixelInterleavedSampleModel destSM = (PixelInterleavedSampleModel) dest
365: .getSampleModel();
366: DataBufferByte destDB = (DataBufferByte) dest.getDataBuffer();
367: int destTransX = dest.getSampleModelTranslateX();
368: int destTransY = dest.getSampleModelTranslateY();
369: int destScanlineStride = destSM.getScanlineStride();
370:
371: byte[] destData = destDB.getData();
372: int destDBOffset = destDB.getOffset();
373:
374: int[] sAreaBitsOn = new int[2];
375:
376: for (int j = 0; j < dhi; j++) {
377: int y = (dy + j) << 2; //int y = (dy + j) * blockY;
378: int sourceYOffset = (y - sourceTransY)
379: * sourceScanlineStride + sourceDBOffset;
380:
381: int destYOffset = (j + dy - destTransY)
382: * destScanlineStride + destDBOffset;
383: destYOffset += dx - destTransX;
384:
385: int selement, sbitnumi, sstartbiti, sbytenumi;
386: // sbitnumi - the 1st bit position from the minX of the raster
387: // sstartbiti - the 1st bit position in the byte data
388: //sbitnumi = blockX * dx - sourceTransX + sourceDataBitOffset;
389: sbitnumi = (dx << 2) - sourceTransX + sourceDataBitOffset;
390:
391: for (int i = 0; i < dwi;) {
392: sbytenumi = sbitnumi >> 3;
393: sstartbiti = sbitnumi % 8;
394: int byteindex = sourceYOffset + sbytenumi;
395: sAreaBitsOn[0] = sAreaBitsOn[1] = 0;
396: for (int k = 0; k < 4; k++, byteindex += sourceScanlineStride) {
397: selement = 0x00ff & (int) sourceData[byteindex];
398: sAreaBitsOn[1] += lut[selement & 0x000f];
399: sAreaBitsOn[0] += lut[selement >> 4];
400:
401: }
402: // set dest elements
403: // count in 4s
404: // sstartbiti = 0 means the 0th of sAreaBitsOn is added to
405: // current dest position, ie destYOffset + i;
406: // sstartbiti = 4 means the 1th of sAreaBitsOn is added to
407: // current dest position, ie destYOffset + i;
408: // sstartbiti now means different
409: // sstartbiti = sstartbiti / 4;
410: sstartbiti >>= 2;
411:
412: while (sstartbiti < 2 && i < dwi) {
413: destData[destYOffset + i] = lutGray[sAreaBitsOn[sstartbiti]];
414: sstartbiti++;
415: i++;
416: sbitnumi += blockX;
417: }
418: }
419: }
420: }
421:
422: // same as SubsampleBinaryToGray, and change byteLoop to protected in superclass?
423: // extends that and save this? using prote
424: private void byteLoop(Raster source, WritableRaster dest,
425: Rectangle destRect) {
426: PixelAccessor pa = new PixelAccessor(source.getSampleModel(),
427: null);
428: PackedImageData pid = pa.getPackedPixels(source, source
429: .getBounds(), false, false);
430: byte[] sourceData = pid.data;
431: int sourceDBOffset = pid.offset;
432: int dx = destRect.x;
433: int dy = destRect.y;
434: int dwi = destRect.width;
435: int dhi = destRect.height;
436: int sourceTransX = pid.rect.x; // source.getSampleModelTranslateX();
437: int sourceTransY = pid.rect.y; // source.getSampleModelTranslateY();
438: int sourceDataBitOffset = pid.bitOffset;
439: int sourceScanlineStride = pid.lineStride;
440:
441: PixelInterleavedSampleModel destSM = (PixelInterleavedSampleModel) dest
442: .getSampleModel();
443: DataBufferByte destDB = (DataBufferByte) dest.getDataBuffer();
444: int destTransX = dest.getSampleModelTranslateX();
445: int destTransY = dest.getSampleModelTranslateY();
446: int destScanlineStride = destSM.getScanlineStride();
447:
448: byte[] destData = destDB.getData();
449: int destDBOffset = destDB.getOffset();
450:
451: int[] sbytenum = new int[dwi];
452: int[] sstartbit = new int[dwi];
453: int[] sAreaBitsOn = new int[dwi];
454: for (int i = 0; i < dwi; i++) {
455: int x = xValues[dx + i];
456: int sbitnum = sourceDataBitOffset + (x - sourceTransX);
457: sbytenum[i] = sbitnum >> 3;
458: sstartbit[i] = sbitnum % 8;
459: }
460:
461: for (int j = 0; j < dhi; j++) {
462:
463: for (int i = 0; i < dwi; i++) {
464: sAreaBitsOn[i] = 0;
465: }
466:
467: for (int y = yValues[dy + j]; y < yValues[dy + j] + blockY; y++) {
468:
469: int sourceYOffset = (y - sourceTransY)
470: * sourceScanlineStride + sourceDBOffset;
471:
472: int delement, selement, sendbiti, sendbytenumi;
473: for (int i = 0; i < dwi; i++) {
474: delement = 0;
475: sendbiti = sstartbit[i] + blockX - 1;
476: sendbytenumi = sbytenum[i] + (sendbiti >> 3); // byte num of the end bit
477: sendbiti %= 8; // true src end bit position
478: selement = 0x00ff & (int) sourceData[sourceYOffset
479: + sbytenum[i]];
480:
481: int swingBits = 24 + sstartbit[i];
482: if (sbytenum[i] == sendbytenumi) {
483: // selement <<= 24 + sstartbit[i];
484: selement <<= swingBits;
485: selement >>>= 31 - sendbiti + sstartbit[i];
486: delement += lut[selement];
487: } else {
488: selement <<= swingBits;
489: selement >>>= swingBits;
490: // selement >>>= 24;
491:
492: delement += lut[selement];
493: for (int b = sbytenum[i] + 1; b < sendbytenumi; b++) {
494: selement = 0x00ff & (int) sourceData[sourceYOffset
495: + b];
496: delement += lut[selement];
497: }
498: selement = 0x00ff & (int) sourceData[sourceYOffset
499: + sendbytenumi];
500: selement >>>= 7 - sendbiti;
501: delement += lut[selement];
502: }
503: sAreaBitsOn[i] += delement;
504: }
505: }
506: int destYOffset = (j + dy - destTransY)
507: * destScanlineStride + destDBOffset;
508:
509: destYOffset += dx - destTransX;
510:
511: // update dest values for row j in raster
512:
513: for (int i = 0; i < dwi; i++) {
514: destData[destYOffset + i] = lutGray[sAreaBitsOn[i]];
515: }
516: }
517: }
518:
519: // buildLookupTables()
520: // initializes variabes bitSet and lut
521: // to be called mainly in the constructor
522: private final void buildLookupTables() {
523: // lut
524: lut = new int[16];
525: lut[0] = 0;
526: lut[1] = 1;
527: lut[2] = 1;
528: lut[3] = 2;
529: lut[4] = 1;
530: lut[5] = 2;
531: lut[6] = 2;
532: lut[7] = 3;
533: for (int i = 8; i < 16; i++)
534: lut[i] = 1 + lut[i - 8];
535: //for(int i= 16; i < 256; i++) lut[i] = lut[i&(0x0f)] + lut[(i>>4)&(0x0f)];
536:
537: // lutGray
538: if (lutGray != null)
539: return;
540: lutGray = new byte[blockX * blockY + 1];
541: for (int i = 0; i < lutGray.length; i++) {
542: int tmp = (int) Math.round(255.0F * i
543: / (lutGray.length - 1.0F));
544: lutGray[i] = tmp > 255 ? (byte) 0xff : (byte) tmp;
545: }
546:
547: // switch black-white if needed
548: if (SubsampleBinaryToGrayOpImage.isMinWhite(this
549: .getSourceImage(0).getColorModel())) {
550: for (int i = 0; i < lutGray.length; i++)
551: lutGray[i] = (byte) (255 - (0xff & lutGray[i]));
552: }
553: }
554:
555: private void computeXYValues(int dstWidth, int dstHeight) {
556: if (xValues == null || yValues == null) {
557: xValues = new int[dstWidth];
558: yValues = new int[dstHeight];
559: }
560:
561: for (int i = 0; i < dstWidth; i++) {
562: //xValues[i] = blockX * i;
563: xValues[i] = i << 2;
564: }
565:
566: for (int i = 0; i < dstHeight; i++) {
567: //yValues[i] = blockY * i;
568: yValues[i] = i << 2;
569: }
570: }
571: }
|