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