1. /*
  2. * @(#)RescaleOp.java 1.38 01/11/29
  3. *
  4. * Copyright 2002 Sun Microsystems, Inc. All rights reserved.
  5. * SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
  6. */
  7. package java.awt.image;
  8. import java.awt.color.ColorSpace;
  9. import java.awt.geom.Rectangle2D;
  10. import java.awt.Rectangle;
  11. import java.awt.geom.Point2D;
  12. import java.awt.RenderingHints;
  13. import sun.awt.image.ImagingLib;
  14. /**
  15. * This class performs a pixel-by-pixel rescaling of the data in the
  16. * source image by multiplying the sample values for each pixel by a scale
  17. * factor and then adding an offset. The scaled sample values are clipped
  18. * to the minimum/maximum representable in the destination image.
  19. * <p>
  20. * The pseudo code for the rescaling operation is as follows:
  21. * <pre>
  22. *for each pixel from Source object {
  23. * for each band/component of the pixel {
  24. * dstElement = (srcElement*scaleFactor) + offset
  25. * }
  26. *}
  27. * </pre>
  28. * <p>
  29. * For Rasters, rescaling operates on bands. The number of
  30. * sets of scaling constants may be one, in which case the same constants
  31. * are applied to all bands, or it must equal the number of Source
  32. * Raster bands.
  33. * <p>
  34. * For BufferedImages, rescaling operates on color and alpha components.
  35. * The number of sets of scaling constants may be one, in which case the
  36. * same constants are applied to all color (but not alpha) components.
  37. * Otherwise, the number of sets of scaling constants may
  38. * equal the number of Source color components, in which case no
  39. * rescaling of the alpha component (if present) is performed.
  40. * If neither of these cases apply, the number of sets of scaling constants
  41. * must equal the number of Source color components plus alpha components,
  42. * in which case all color and alpha components are rescaled.
  43. * <p>
  44. * BufferedImage sources with premultiplied alpha data are treated in the same
  45. * manner as non-premultiplied images for purposes of rescaling. That is,
  46. * the rescaling is done per band on the raw data of the BufferedImage source
  47. * without regard to whether the data is premultiplied. If a color conversion
  48. * is required to the destination ColorModel, the premultiplied state of
  49. * both source and destination will be taken into account for this step.
  50. * <p>
  51. * Images with an IndexColorModel cannot be rescaled.
  52. * <p>
  53. * If a RenderingHints object is specified in the constructor, the
  54. * color rendering hint and the dithering hint may be used when color
  55. * conversion is required.
  56. * <p>
  57. * Note that in-place operation is allowed (i.e. the source and destination can
  58. * be the same object).
  59. * @version 10 Feb 1997
  60. * @see java.awt.RenderingHints#KEY_COLOR_RENDERING
  61. * @see java.awt.RenderingHints#KEY_DITHERING
  62. */
  63. public class RescaleOp implements BufferedImageOp, RasterOp {
  64. float[] scaleFactors;
  65. float[] offsets;
  66. int length = 0;
  67. RenderingHints hints;
  68. private int srcNbits;
  69. private int dstNbits;
  70. /**
  71. * Constructs a new RescaleOp with the desired scale factors
  72. * and offsets. The length of the scaleFactor and offset arrays
  73. * must meet the restrictions stated in the class comments above.
  74. * The RenderingHints argument may be null.
  75. */
  76. public RescaleOp (float[] scaleFactors, float[] offsets,
  77. RenderingHints hints) {
  78. length = scaleFactors.length;
  79. if (length > offsets.length) length = offsets.length;
  80. this.scaleFactors = new float[length];
  81. this.offsets = new float[length];
  82. for (int i=0; i < length; i++) {
  83. this.scaleFactors[i] = scaleFactors[i];
  84. this.offsets[i] = offsets[i];
  85. }
  86. this.hints = hints;
  87. }
  88. /**
  89. * Constructs a new RescaleOp with the desired scale factor
  90. * and offset. The scaleFactor and offset will be applied to
  91. * all bands in a source Raster and to all color (but not alpha)
  92. * components in a BufferedImage.
  93. * The RenderingHints argument may be null.
  94. */
  95. public RescaleOp (float scaleFactor, float offset, RenderingHints hints) {
  96. length = 1;
  97. this.scaleFactors = new float[1];
  98. this.offsets = new float[1];
  99. this.scaleFactors[0] = scaleFactor;
  100. this.offsets[0] = offset;
  101. this.hints = hints;
  102. }
  103. /**
  104. * Returns the scale factors in the given array. The array is also
  105. * returned for convenience. If scaleFactors is null, a new array
  106. * will be allocated.
  107. */
  108. final public float[] getScaleFactors (float scaleFactors[]) {
  109. if (scaleFactors == null) {
  110. return (float[]) this.scaleFactors.clone();
  111. }
  112. System.arraycopy (this.scaleFactors, 0, scaleFactors, 0,
  113. Math.min(this.scaleFactors.length,
  114. scaleFactors.length));
  115. return scaleFactors;
  116. }
  117. /**
  118. * Returns the offsets in the given array. The array is also returned
  119. * for convenience. If offsets is null, a new array
  120. * will be allocated.
  121. */
  122. final public float[] getOffsets(float offsets[]) {
  123. if (offsets == null) {
  124. return (float[]) this.offsets.clone();
  125. }
  126. System.arraycopy (this.offsets, 0, offsets, 0,
  127. Math.min(this.offsets.length, offsets.length));
  128. return offsets;
  129. }
  130. /**
  131. * Returns the number of scaling factors and offsets used in this
  132. * RescaleOp.
  133. */
  134. final public int getNumFactors() {
  135. return length;
  136. }
  137. /**
  138. * Creates a ByteLookupTable to implement the rescale.
  139. * The table may have either a SHORT or BYTE input.
  140. * @param nElems Number of elements the table is to have.
  141. * This will generally be 256 for byte and
  142. * 65536 for short.
  143. */
  144. private ByteLookupTable createByteLut(float scale[],
  145. float off[],
  146. int nBands,
  147. int nElems) {
  148. byte[][] lutData = new byte[scale.length][nElems];
  149. for (int band=0; band<scale.length; band++) {
  150. float bandScale = scale[band];
  151. float bandOff = off[band];
  152. byte[] bandLutData = lutData[band];
  153. for (int i=0; i<nElems; i++) {
  154. int val = (int)(i*bandScale + bandOff);
  155. if ((val & 0xffffff00) != 0) {
  156. if (val < 0) {
  157. val = 0;
  158. } else {
  159. val = 255;
  160. }
  161. }
  162. bandLutData[i] = (byte)val;
  163. }
  164. }
  165. return new ByteLookupTable(0, lutData);
  166. }
  167. /**
  168. * Creates a ShortLookupTable to implement the rescale.
  169. * The table may have either a SHORT or BYTE input.
  170. * @param nElems Number of elements the table is to have.
  171. * This will generally be 256 for byte and
  172. * 65536 for short.
  173. */
  174. private ShortLookupTable createShortLut(float scale[],
  175. float off[],
  176. int nBands,
  177. int nElems) {
  178. short[][] lutData = new short[scale.length][nElems];
  179. for (int band=0; band<scale.length; band++) {
  180. float bandScale = scale[band];
  181. float bandOff = off[band];
  182. short[] bandLutData = lutData[band];
  183. for (int i=0; i<nElems; i++) {
  184. int val = (int)(i*bandScale + bandOff);
  185. if ((val & 0xffff0000) != 0) {
  186. if (val < 0) {
  187. val = 0;
  188. } else {
  189. val = 65535;
  190. }
  191. }
  192. bandLutData[i] = (short)val;
  193. }
  194. }
  195. return new ShortLookupTable(0, lutData);
  196. }
  197. /**
  198. * Determines if the rescale can be performed as a lookup.
  199. * The dst must be a byte or short type.
  200. * The src must be less than 16 bits.
  201. * All source band sizes must be the same and all dst band sizes
  202. * must be the same.
  203. */
  204. private boolean canUseLookup(Raster src, Raster dst) {
  205. //
  206. // Check that the src datatype is either a BYTE or SHORT
  207. //
  208. int datatype = src.getDataBuffer().getDataType();
  209. if(datatype != DataBuffer.TYPE_BYTE &&
  210. datatype != DataBuffer.TYPE_USHORT) {
  211. return false;
  212. }
  213. //
  214. // Check dst sample sizes. All must be 8 or 16 bits.
  215. //
  216. SampleModel dstSM = dst.getSampleModel();
  217. dstNbits = dstSM.getSampleSize(0);
  218. if (!(dstNbits == 8 || dstNbits == 16)) {
  219. return false;
  220. }
  221. for (int i=1; i<src.getNumBands(); i++) {
  222. int bandSize = dstSM.getSampleSize(i);
  223. if (bandSize != dstNbits) {
  224. return false;
  225. }
  226. }
  227. //
  228. // Check src sample sizes. All must be the same size
  229. //
  230. SampleModel srcSM = src.getSampleModel();
  231. srcNbits = srcSM.getSampleSize(0);
  232. if (srcNbits > 16) {
  233. return false;
  234. }
  235. for (int i=1; i<src.getNumBands(); i++) {
  236. int bandSize = srcSM.getSampleSize(i);
  237. if (bandSize != srcNbits) {
  238. return false;
  239. }
  240. }
  241. return true;
  242. }
  243. /**
  244. * Rescales the source BufferedImage.
  245. * If the color model in the source image is not the same as that
  246. * in the destination image, the pixels will be converted
  247. * in the destination. If the destination image is null,
  248. * a BufferedImage will be created with the source ColorModel.
  249. * An IllegalArgumentException may be thrown if the number of
  250. * scaling factors/offsets in this object does not meet the
  251. * restrictions stated in the class comments above, or if the
  252. * source image has an IndexColorModel.
  253. */
  254. public final BufferedImage filter (BufferedImage src, BufferedImage dst) {
  255. ColorModel srcCM = src.getColorModel();
  256. ColorModel dstCM;
  257. int numBands = srcCM.getNumColorComponents();
  258. if (srcCM instanceof IndexColorModel) {
  259. throw new
  260. IllegalArgumentException("Rescaling cannot be "+
  261. "performed on an indexed image");
  262. }
  263. if (length != 1 && length != numBands &&
  264. length != srcCM.getNumComponents())
  265. {
  266. throw new IllegalArgumentException("Number of scaling constants "+
  267. "does not equal the number of"+
  268. " of color or color/alpha "+
  269. " components");
  270. }
  271. boolean needToConvert = false;
  272. // Include alpha
  273. if (length > numBands && srcCM.hasAlpha()) {
  274. length = numBands+1;
  275. }
  276. int width = src.getWidth();
  277. int height = src.getHeight();
  278. if (dst == null) {
  279. dst = createCompatibleDestImage(src, null);
  280. dstCM = srcCM;
  281. }
  282. else {
  283. if (width != dst.getWidth()) {
  284. throw new
  285. IllegalArgumentException("Src width ("+width+
  286. ") not equal to dst width ("+
  287. dst.getWidth()+")");
  288. }
  289. if (height != dst.getHeight()) {
  290. throw new
  291. IllegalArgumentException("Src height ("+height+
  292. ") not equal to dst height ("+
  293. dst.getHeight()+")");
  294. }
  295. dstCM = dst.getColorModel();
  296. if(srcCM.getColorSpace().getType() !=
  297. dstCM.getColorSpace().getType()) {
  298. needToConvert = true;
  299. dst = createCompatibleDestImage(src, null);
  300. }
  301. }
  302. BufferedImage origDst = dst;
  303. //
  304. // Try to use a native BI rescale operation first
  305. //
  306. if (ImagingLib.filter(this, src, dst) == null) {
  307. //
  308. // Native BI rescale failed - convert to rasters
  309. //
  310. WritableRaster srcRaster = src.getRaster();
  311. WritableRaster dstRaster = dst.getRaster();
  312. if (srcCM.hasAlpha()) {
  313. if (numBands-1 == length || length == 1) {
  314. int minx = srcRaster.getMinX();
  315. int miny = srcRaster.getMinY();
  316. int[] bands = new int[numBands-1];
  317. for (int i=0; i < numBands-1; i++) {
  318. bands[i] = i;
  319. }
  320. srcRaster =
  321. srcRaster.createWritableChild(minx, miny,
  322. srcRaster.getWidth(),
  323. srcRaster.getHeight(),
  324. minx, miny,
  325. bands);
  326. }
  327. }
  328. if (dstCM.hasAlpha()) {
  329. int dstNumBands = dstRaster.getNumBands();
  330. if (dstNumBands-1 == length || length == 1) {
  331. int minx = dstRaster.getMinX();
  332. int miny = dstRaster.getMinY();
  333. int[] bands = new int[numBands-1];
  334. for (int i=0; i < numBands-1; i++) {
  335. bands[i] = i;
  336. }
  337. dstRaster =
  338. dstRaster.createWritableChild(minx, miny,
  339. dstRaster.getWidth(),
  340. dstRaster.getHeight(),
  341. minx, miny,
  342. bands);
  343. }
  344. }
  345. //
  346. // Call the raster filter method
  347. //
  348. filter(srcRaster, dstRaster);
  349. }
  350. if (needToConvert) {
  351. // ColorModels are not the same
  352. ColorConvertOp ccop = new ColorConvertOp(hints);
  353. ccop.filter(dst, origDst);
  354. }
  355. return origDst;
  356. }
  357. /**
  358. * Rescales the pixel data in the source Raster.
  359. * If the destination Raster is null, a new Raster will be created.
  360. * The source and destination must have the same number of bands.
  361. * Otherwise, an IllegalArgumentException is thrown.
  362. * Note that the number of scaling factors/offsets in this object must
  363. * meet the restrictions stated in the class comments above.
  364. * Otherwise, an IllegalArgumentException is thrown.
  365. */
  366. public final WritableRaster filter (Raster src, WritableRaster dst) {
  367. int numBands = src.getNumBands();
  368. int width = src.getWidth();
  369. int height = src.getHeight();
  370. int[] srcPix = null;
  371. int step = 0;
  372. int tidx = 0;
  373. // Create a new destination Raster, if needed
  374. if (dst == null) {
  375. dst = createCompatibleDestRaster(src);
  376. }
  377. else if (height != dst.getHeight() || width != dst.getWidth()) {
  378. throw new
  379. IllegalArgumentException("Width or height of Rasters do not "+
  380. "match");
  381. }
  382. else if (numBands != dst.getNumBands()) {
  383. // Make sure that the number of bands are equal
  384. throw new IllegalArgumentException("Number of bands in src "
  385. + numBands
  386. + " does not equal number of bands in dest "
  387. + dst.getNumBands());
  388. }
  389. // Make sure that the arrays match
  390. // Make sure that the low/high/constant arrays match
  391. if (length != 1 && length != src.getNumBands()) {
  392. throw new IllegalArgumentException("Number of scaling constants "+
  393. "does not equal the number of"+
  394. " of bands in the src raster");
  395. }
  396. //
  397. // Try for a native raster rescale first
  398. //
  399. if (ImagingLib.filter(this, src, dst) != null) {
  400. return dst;
  401. }
  402. //
  403. // Native raster rescale failed.
  404. // Try to see if a lookup operation can be used
  405. //
  406. if (canUseLookup(src, dst)) {
  407. int srcNgray = (1 << srcNbits);
  408. int dstNgray = (1 << dstNbits);
  409. if (dstNgray == 256) {
  410. ByteLookupTable lut = createByteLut(scaleFactors, offsets,
  411. numBands, srcNgray);
  412. LookupOp op = new LookupOp(lut, hints);
  413. op.filter(src, dst);
  414. } else {
  415. ShortLookupTable lut = createShortLut(scaleFactors, offsets,
  416. numBands, srcNgray);
  417. LookupOp op = new LookupOp(lut, hints);
  418. op.filter(src, dst);
  419. }
  420. } else {
  421. //
  422. // Fall back to the slow code
  423. //
  424. if (length > 1) {
  425. step = 1;
  426. }
  427. int sminX = src.getMinX();
  428. int sY = src.getMinY();
  429. int dminX = dst.getMinX();
  430. int dY = dst.getMinY();
  431. int sX;
  432. int dX;
  433. //
  434. // Determine bits per band to determine maxval for clamps.
  435. // The min is assumed to be zero.
  436. // REMIND: This must change if we ever support signed data types.
  437. //
  438. int nbits;
  439. int dstMax[] = new int[numBands];
  440. int dstMask[] = new int[numBands];
  441. SampleModel dstSM = dst.getSampleModel();
  442. for (int z=0; z<numBands; z++) {
  443. nbits = dstSM.getSampleSize(z);
  444. dstMax[z] = (1 << nbits) - 1;
  445. dstMask[z] = ~(dstMax[z]);
  446. }
  447. int val;
  448. for (int y=0; y < height; y++, sY++, dY++) {
  449. dX = dminX;
  450. sX = sminX;
  451. for (int x = 0; x < width; x++, sX++, dX++) {
  452. // Get data for all bands at this x,y position
  453. srcPix = src.getPixel(sX, sY, srcPix);
  454. tidx = 0;
  455. for (int z=0; z<numBands; z++, tidx += step) {
  456. val = (int)(srcPix[z]*scaleFactors[tidx]
  457. + offsets[tidx]);
  458. // Clamp
  459. if ((val & dstMask[z]) != 0) {
  460. if (val < 0) {
  461. val = 0;
  462. } else {
  463. val = dstMax[z];
  464. }
  465. }
  466. srcPix[z] = val;
  467. }
  468. // Put it back for all bands
  469. dst.setPixel(dX, dY, srcPix);
  470. }
  471. }
  472. }
  473. return dst;
  474. }
  475. /**
  476. * Returns the bounding box of the rescaled destination image. Since
  477. * this is not a geometric operation, the bounding box does not
  478. * change.
  479. */
  480. public final Rectangle2D getBounds2D (BufferedImage src) {
  481. return getBounds2D(src.getRaster());
  482. }
  483. /**
  484. * Returns the bounding box of the rescaled destination Raster. Since
  485. * this is not a geometric operation, the bounding box does not
  486. * change.
  487. */
  488. public final Rectangle2D getBounds2D (Raster src) {
  489. return src.getBounds();
  490. }
  491. /**
  492. * Creates a zeroed destination image with the correct size and number of
  493. * bands.
  494. * @param src Source image for the filter operation.
  495. * @param destCM ColorModel of the destination. If null, the
  496. * ColorModel of the source will be used.
  497. */
  498. public BufferedImage createCompatibleDestImage (BufferedImage src,
  499. ColorModel destCM) {
  500. BufferedImage image;
  501. if (destCM == null) {
  502. ColorModel cm = src.getColorModel();
  503. image = new BufferedImage(cm,
  504. src.getRaster().createCompatibleWritableRaster(),
  505. cm.isAlphaPremultiplied(),
  506. null);
  507. }
  508. else {
  509. int w = src.getWidth();
  510. int h = src.getHeight();
  511. image = new BufferedImage (destCM,
  512. destCM.createCompatibleWritableRaster(w, h),
  513. destCM.isAlphaPremultiplied(), null);
  514. }
  515. return image;
  516. }
  517. /**
  518. * Creates a zeroed destination Raster with the correct size and number
  519. * of bands, given this source.
  520. */
  521. public WritableRaster createCompatibleDestRaster (Raster src) {
  522. return src.createCompatibleWritableRaster(src.getWidth(), src.getHeight());
  523. }
  524. /**
  525. * Returns the location of the destination point given a
  526. * point in the source. If dstPt is non-null, it will
  527. * be used to hold the return value. Since this is not a geometric
  528. * operation, the srcPt will equal the dstPt.
  529. */
  530. public final Point2D getPoint2D (Point2D srcPt, Point2D dstPt) {
  531. if (dstPt == null) {
  532. dstPt = new Point2D.Float();
  533. }
  534. dstPt.setLocation(srcPt.getX(), srcPt.getY());
  535. return dstPt;
  536. }
  537. /**
  538. * Returns the rendering hints for this op.
  539. */
  540. public final RenderingHints getRenderingHints() {
  541. return hints;
  542. }
  543. }