1. /*
  2. * @(#)LookupOp.java 1.45 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.RenderingHints;
  15. import java.awt.geom.Point2D;
  16. import sun.awt.image.ImagingLib;
  17. /**
  18. * This class implements a lookup operation from the source
  19. * to the destination. The LookupTable object may contain a single array
  20. * or multiple arrays, subject to the restrictions below.
  21. * <p>
  22. * For Rasters, the lookup operates on bands. The number of
  23. * lookup arrays may be one, in which case the same array is
  24. * applied to all bands, or it must equal the number of Source
  25. * Raster bands.
  26. * <p>
  27. * For BufferedImages, the lookup operates on color and alpha components.
  28. * The number of lookup arrays may be one, in which case the
  29. * same array is applied to all color (but not alpha) components.
  30. * Otherwise, the number of lookup arrays may
  31. * equal the number of Source color components, in which case no
  32. * lookup of the alpha component (if present) is performed.
  33. * If neither of these cases apply, the number of lookup arrays
  34. * must equal the number of Source color components plus alpha components,
  35. * in which case lookup is performed for all color and alpha components.
  36. * This allows non-uniform rescaling of multi-band BufferedImages.
  37. * <p>
  38. * BufferedImage sources with premultiplied alpha data are treated in the same
  39. * manner as non-premultiplied images for purposes of the lookup. That is,
  40. * the lookup is done per band on the raw data of the BufferedImage source
  41. * without regard to whether the data is premultiplied. If a color conversion
  42. * is required to the destination ColorModel, the premultiplied state of
  43. * both source and destination will be taken into account for this step.
  44. * <p>
  45. * Images with an IndexColorModel cannot be used.
  46. * <p>
  47. * If a RenderingHints object is specified in the constructor, the
  48. * color rendering hint and the dithering hint may be used when color
  49. * conversion is required.
  50. * <p>
  51. * This class allows the Source to be the same as the Destination.
  52. *
  53. * @version 10 Feb 1997
  54. * @see LookupTable
  55. * @see java.awt.RenderingHints#KEY_COLOR_RENDERING
  56. * @see java.awt.RenderingHints#KEY_DITHERING
  57. */
  58. public class LookupOp implements BufferedImageOp, RasterOp {
  59. private LookupTable ltable;
  60. private int numComponents;
  61. RenderingHints hints;
  62. /**
  63. * Constructs a LookupOp object given the lookup table and
  64. * a RenderingHints object (which may be null).
  65. */
  66. public LookupOp(LookupTable lookup, RenderingHints hints) {
  67. this.ltable = lookup;
  68. this.hints = hints;
  69. numComponents = ltable.getNumComponents();
  70. }
  71. /**
  72. * Returns the LookupTable.
  73. */
  74. public final LookupTable getTable() {
  75. return ltable;
  76. }
  77. /**
  78. * Performs a lookup operation on a BufferedImage.
  79. * If the color model in the source image is not the same as that
  80. * in the destination image, the pixels will be converted
  81. * in the destination. If the destination image is null,
  82. * a BufferedImage will be created with an appropriate ColorModel.
  83. * An IllegalArgumentException may be thrown if the number of
  84. * arrays in the LookupTable does not meet the restrictions
  85. * stated in the class comment above, or if the source image
  86. * has an IndexColorModel.
  87. */
  88. public final BufferedImage filter(BufferedImage src, BufferedImage dst) {
  89. ColorModel srcCM = src.getColorModel();
  90. int numBands = srcCM.getNumColorComponents();
  91. ColorModel dstCM;
  92. if (srcCM instanceof IndexColorModel) {
  93. throw new
  94. IllegalArgumentException("LookupOp cannot be "+
  95. "performed on an indexed image");
  96. }
  97. int numComponents = ltable.getNumComponents();
  98. if (numComponents != 1 &&
  99. numComponents != srcCM.getNumComponents() &&
  100. numComponents != srcCM.getNumColorComponents())
  101. {
  102. throw new IllegalArgumentException("Number of arrays in the "+
  103. " lookup table ("+
  104. numComponents+
  105. " is not compatible with the "+
  106. " src image: "+src);
  107. }
  108. boolean needToConvert = false;
  109. int width = src.getWidth();
  110. int height = src.getHeight();
  111. if (dst == null) {
  112. dst = createCompatibleDestImage(src, null);
  113. dstCM = srcCM;
  114. }
  115. else {
  116. if (width != dst.getWidth()) {
  117. throw new
  118. IllegalArgumentException("Src width ("+width+
  119. ") not equal to dst width ("+
  120. dst.getWidth()+")");
  121. }
  122. if (height != dst.getHeight()) {
  123. throw new
  124. IllegalArgumentException("Src height ("+height+
  125. ") not equal to dst height ("+
  126. dst.getHeight()+")");
  127. }
  128. dstCM = dst.getColorModel();
  129. if (srcCM.getColorSpace().getType() !=
  130. dstCM.getColorSpace().getType())
  131. {
  132. needToConvert = true;
  133. dst = createCompatibleDestImage(src, null);
  134. }
  135. }
  136. BufferedImage origDst = dst;
  137. if (ImagingLib.filter(this, src, dst) == null) {
  138. // Do it the slow way
  139. WritableRaster srcRaster = src.getRaster();
  140. WritableRaster dstRaster = dst.getRaster();
  141. if (srcCM.hasAlpha()) {
  142. if (numBands-1 == numComponents || numComponents == 1) {
  143. int minx = srcRaster.getMinX();
  144. int miny = srcRaster.getMinY();
  145. int[] bands = new int[numBands-1];
  146. for (int i=0; i < numBands-1; i++) {
  147. bands[i] = i;
  148. }
  149. srcRaster =
  150. srcRaster.createWritableChild(minx, miny,
  151. srcRaster.getWidth(),
  152. srcRaster.getHeight(),
  153. minx, miny,
  154. bands);
  155. }
  156. }
  157. if (dstCM.hasAlpha()) {
  158. int dstNumBands = dstRaster.getNumBands();
  159. if (dstNumBands-1 == numComponents || numComponents == 1) {
  160. int minx = dstRaster.getMinX();
  161. int miny = dstRaster.getMinY();
  162. int[] bands = new int[numBands-1];
  163. for (int i=0; i < numBands-1; i++) {
  164. bands[i] = i;
  165. }
  166. dstRaster =
  167. dstRaster.createWritableChild(minx, miny,
  168. dstRaster.getWidth(),
  169. dstRaster.getHeight(),
  170. minx, miny,
  171. bands);
  172. }
  173. }
  174. filter(srcRaster, dstRaster);
  175. }
  176. if (needToConvert) {
  177. // ColorModels are not the same
  178. ColorConvertOp ccop = new ColorConvertOp(hints);
  179. ccop.filter(dst, origDst);
  180. }
  181. return origDst;
  182. }
  183. /**
  184. * Performs a lookup operation on a Raster. If the destination Raster is
  185. * null, a new Raster will be created.
  186. * The IllegalArgumentException may be thrown if the source and
  187. * destination Rasters have different numbers of bands or if
  188. * the number of arrays in the LookupTable does not meet the
  189. * restrictions stated in the class comment above.
  190. */
  191. public final WritableRaster filter (Raster src, WritableRaster dst) {
  192. int numBands = src.getNumBands();
  193. int dstLength = dst.getNumBands();
  194. int height = src.getHeight();
  195. int width = src.getWidth();
  196. int srcPix[] = new int[numBands];
  197. // Create a new destination Raster, if needed
  198. if (dst == null) {
  199. dst = createCompatibleDestRaster(src);
  200. }
  201. else if (height != dst.getHeight() || width != dst.getWidth()) {
  202. throw new
  203. IllegalArgumentException ("Width or height of Rasters do not "+
  204. "match");
  205. }
  206. dstLength = dst.getNumBands();
  207. if (numBands != dstLength) {
  208. throw new
  209. IllegalArgumentException ("Number of channels in the src ("
  210. + numBands +
  211. ") does not match number of channels"
  212. + " in the destination ("
  213. + dstLength + ")");
  214. }
  215. int numComponents = ltable.getNumComponents();
  216. if (numComponents != 1 && numComponents != src.getNumBands()) {
  217. throw new IllegalArgumentException("Number of arrays in the "+
  218. " lookup table ("+
  219. numComponents+
  220. " is not compatible with the "+
  221. " src Raster: "+src);
  222. }
  223. if (ImagingLib.filter(this, src, dst) != null) {
  224. return dst;
  225. }
  226. // Optimize for cases we know about
  227. if (ltable instanceof ByteLookupTable) {
  228. byteFilter ((ByteLookupTable) ltable, src, dst,
  229. width, height, numBands);
  230. }
  231. else if (ltable instanceof ShortLookupTable) {
  232. shortFilter ((ShortLookupTable) ltable, src, dst, width,
  233. height, numBands);
  234. }
  235. else {
  236. // Not one we recognize so do it slowly
  237. int sminX = src.getMinX();
  238. int sY = src.getMinY();
  239. int dminX = dst.getMinX();
  240. int dY = dst.getMinY();
  241. for (int y=0; y < height; y++, sY++, dY++) {
  242. int sX = sminX;
  243. int dX = dminX;
  244. for (int x=0; x < width; x++, sX++, dX++) {
  245. // Find data for all bands at this x,y position
  246. src.getPixel(sX, sY, srcPix);
  247. // Lookup the data for all bands at this x,y position
  248. ltable.lookupPixel(srcPix, srcPix);
  249. // Put it back for all bands
  250. dst.setPixel(dX, dY, srcPix);
  251. }
  252. }
  253. }
  254. return dst;
  255. }
  256. /**
  257. * Returns the bounding box of the filtered destination image. Since
  258. * this is not a geometric operation, the bounding box does not
  259. * change.
  260. */
  261. public final Rectangle2D getBounds2D (BufferedImage src) {
  262. return getBounds2D(src.getRaster());
  263. }
  264. /**
  265. * Returns the bounding box of the filtered destination Raster. Since
  266. * this is not a geometric operation, the bounding box does not
  267. * change.
  268. */
  269. public final Rectangle2D getBounds2D (Raster src) {
  270. return src.getBounds();
  271. }
  272. /**
  273. * Creates a zeroed destination image with the correct size and number of
  274. * bands. If destCM is null, an appropriate ColorModel will be used.
  275. * @param src Source image for the filter operation.
  276. * @param destCM ColorModel of the destination. Can be null.
  277. */
  278. public BufferedImage createCompatibleDestImage (BufferedImage src,
  279. ColorModel destCM) {
  280. BufferedImage image;
  281. int w = src.getWidth();
  282. int h = src.getHeight();
  283. int transferType = DataBuffer.TYPE_BYTE;
  284. if (destCM == null) {
  285. ColorModel cm = src.getColorModel();
  286. Raster raster = src.getRaster();
  287. if (cm instanceof ComponentColorModel) {
  288. DataBuffer db = raster.getDataBuffer();
  289. boolean hasAlpha = cm.hasAlpha();
  290. boolean isPre = cm.isAlphaPremultiplied();
  291. int trans = cm.getTransparency();
  292. int[] nbits = null;
  293. if (ltable instanceof ByteLookupTable) {
  294. if (db.getDataType() == db.TYPE_USHORT) {
  295. // Dst raster should be of type byte
  296. if (hasAlpha) {
  297. nbits = new int[2];
  298. if (trans == cm.BITMASK) {
  299. nbits[1] = 1;
  300. }
  301. else {
  302. nbits[1] = 8;
  303. }
  304. }
  305. else {
  306. nbits = new int[1];
  307. }
  308. nbits[0] = 8;
  309. }
  310. // For byte, no need to change the cm
  311. }
  312. else if (ltable instanceof ShortLookupTable) {
  313. transferType = DataBuffer.TYPE_USHORT;
  314. if (db.getDataType() == db.TYPE_BYTE) {
  315. if (hasAlpha) {
  316. nbits = new int[2];
  317. if (trans == cm.BITMASK) {
  318. nbits[1] = 1;
  319. }
  320. else {
  321. nbits[1] = 16;
  322. }
  323. }
  324. else {
  325. nbits = new int[1];
  326. }
  327. nbits[0] = 16;
  328. }
  329. }
  330. if (nbits != null) {
  331. cm = new ComponentColorModel(cm.getColorSpace(),
  332. nbits, hasAlpha, isPre,
  333. trans, transferType);
  334. }
  335. }
  336. image = new BufferedImage(cm,
  337. cm.createCompatibleWritableRaster(w, h),
  338. cm.isAlphaPremultiplied(),
  339. null);
  340. }
  341. else {
  342. image = new BufferedImage(destCM,
  343. destCM.createCompatibleWritableRaster(w,
  344. h),
  345. destCM.isAlphaPremultiplied(),
  346. null);
  347. }
  348. return image;
  349. }
  350. /**
  351. * Creates a zeroed destination Raster with the correct size and number of
  352. * bands, given this source.
  353. */
  354. public WritableRaster createCompatibleDestRaster (Raster src) {
  355. return src.createCompatibleWritableRaster();
  356. }
  357. /**
  358. * Returns the location of the destination point given a
  359. * point in the source. If dstPt is non-null, it will
  360. * be used to hold the return value. Since this is not a geometric
  361. * operation, the srcPt will equal the dstPt.
  362. */
  363. public final Point2D getPoint2D (Point2D srcPt, Point2D dstPt) {
  364. if (dstPt == null) {
  365. dstPt = new Point2D.Float();
  366. }
  367. dstPt.setLocation(srcPt.getX(), srcPt.getY());
  368. return dstPt;
  369. }
  370. /**
  371. * Returns the rendering hints for this op.
  372. */
  373. public final RenderingHints getRenderingHints() {
  374. return hints;
  375. }
  376. private final void byteFilter(ByteLookupTable lookup, Raster src,
  377. WritableRaster dst,
  378. int width, int height, int numBands) {
  379. int[] srcPix = null;
  380. // Find the ref to the table and the offset
  381. byte[][] table = lookup.getTable();
  382. int offset = lookup.getOffset();
  383. int tidx;
  384. int step=1;
  385. // Check if it is one lookup applied to all bands
  386. if (table.length == 1) {
  387. step=0;
  388. }
  389. int x;
  390. int y;
  391. int band;
  392. int len = table[0].length;
  393. // Loop through the data
  394. for ( y=0; y < height; y++) {
  395. tidx = 0;
  396. for ( band=0; band < numBands; band++, tidx+=step) {
  397. // Find data for this band, scanline
  398. srcPix = src.getSamples(0, y, width, 1, band, srcPix);
  399. for ( x=0; x < width; x++) {
  400. int index = srcPix[x]-offset;
  401. if (index < 0 || index > len) {
  402. throw new
  403. IllegalArgumentException("index ("+index+
  404. "(out of range: "+
  405. " srcPix["+x+
  406. "]="+ srcPix[x]+
  407. " offset="+ offset);
  408. }
  409. // Do the lookup
  410. srcPix[x] = table[tidx][index];
  411. }
  412. // Put it back
  413. dst.setSamples(0, y, width, 1, band, srcPix);
  414. }
  415. }
  416. }
  417. private final void shortFilter(ShortLookupTable lookup, Raster src,
  418. WritableRaster dst,
  419. int width, int height, int numBands) {
  420. int band;
  421. int[] srcPix = null;
  422. // Find the ref to the table and the offset
  423. short[][] table = lookup.getTable();
  424. int offset = lookup.getOffset();
  425. int tidx;
  426. int step=1;
  427. // Check if it is one lookup applied to all bands
  428. if (table.length == 1) {
  429. step=0;
  430. }
  431. int x = 0;
  432. int y = 0;
  433. int index;
  434. int maxShort = (1<<16)-1;
  435. // Loop through the data
  436. for (y=0; y < height; y++) {
  437. tidx = 0;
  438. for ( band=0; band < numBands; band++, tidx+=step) {
  439. // Find data for this band, scanline
  440. srcPix = src.getSamples(0, y, width, 1, band, srcPix);
  441. for ( x=0; x < width; x++) {
  442. index = srcPix[x]-offset;
  443. if (index < 0 || index > maxShort) {
  444. throw new
  445. IllegalArgumentException("index out of range "+
  446. index+" x is "+x+
  447. "srcPix[x]="+srcPix[x]
  448. +" offset="+ offset);
  449. }
  450. // Do the lookup
  451. srcPix[x] = table[tidx][index];
  452. }
  453. // Put it back
  454. dst.setSamples(0, y, width, 1, band, srcPix);
  455. }
  456. }
  457. }
  458. }