001: /*
002: * Licensed to the Apache Software Foundation (ASF) under one or more
003: * contributor license agreements. See the NOTICE file distributed with
004: * this work for additional information regarding copyright ownership.
005: * The ASF licenses this file to You under the Apache License, Version 2.0
006: * (the "License"); you may not use this file except in compliance with
007: * the License. You may obtain a copy of the License at
008: *
009: * http://www.apache.org/licenses/LICENSE-2.0
010: *
011: * Unless required by applicable law or agreed to in writing, software
012: * distributed under the License is distributed on an "AS IS" BASIS,
013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014: * See the License for the specific language governing permissions and
015: * limitations under the License.
016: */
017: /**
018: * @author Oleg V. Khaschansky
019: * @version $Revision$
020: */package java.awt.image;
021:
022: import java.awt.Graphics2D;
023: import java.awt.Point;
024: import java.awt.RenderingHints;
025: import java.awt.color.ColorSpace;
026: import java.awt.color.ICC_ColorSpace;
027: import java.awt.color.ICC_Profile;
028: import java.awt.geom.Point2D;
029: import java.awt.geom.Rectangle2D;
030: import java.util.ArrayList;
031:
032: import org.apache.harmony.awt.gl.color.ColorConverter;
033: import org.apache.harmony.awt.gl.color.ColorScaler;
034: import org.apache.harmony.awt.gl.color.ICC_Transform;
035: import org.apache.harmony.awt.internal.nls.Messages;
036:
037: public class ColorConvertOp implements BufferedImageOp, RasterOp {
038: // Unused but required by interfaces
039: RenderingHints renderingHints;
040:
041: // Sequence consisting of ColorSpace and ICC_Profile elements
042: Object conversionSequence[] = new ICC_Profile[0]; // To eliminate checks for null
043:
044: // Not null if ColorConvertOp is constructed from the array of ICC profiles
045: private ICC_Profile midProfiles[];
046:
047: private final ColorConverter cc = new ColorConverter();
048: private final ICC_TransfomCreator tCreator = new ICC_TransfomCreator();
049: private boolean isICC = true;
050:
051: // Cached ICC_Transform
052: private class ICC_TransfomCreator {
053: private ICC_Transform transform;
054: private int maxComponents;
055:
056: /**
057: * For the full ICC case
058: * @param src
059: * @param dst
060: * @param convSeq
061: * @return
062: */
063: public ICC_Transform getTransform(ICC_Profile src,
064: ICC_Profile dst, ICC_Profile convSeq[]) {
065: if (transform != null && src == transform.getSrc()
066: && dst == transform.getDst()) {
067: return transform;
068: }
069:
070: int length = convSeq.length;
071: int srcFlg = 0, dstFlg = 0;
072:
073: if (length == 0 || src != convSeq[0]) {
074: if (src != null) {
075: srcFlg = 1; // need src profile
076: }
077: }
078: if (length == 0 || dst != convSeq[length - 1]) {
079: if (dst != null) {
080: dstFlg = 1; // need dst profile
081: }
082: }
083:
084: ICC_Profile profiles[];
085: int nProfiles = length + srcFlg + dstFlg;
086: if (nProfiles == length) {
087: profiles = convSeq;
088: } else {
089: profiles = new ICC_Profile[nProfiles];
090: int pos = 0;
091: if (srcFlg != 0) {
092: profiles[pos++] = src;
093: }
094: for (int i = 0; i < length; i++) {
095: profiles[pos++] = convSeq[i];
096: }
097: if (dstFlg != 0) {
098: profiles[pos++] = dst;
099: }
100: }
101:
102: return transform = new ICC_Transform(profiles);
103: }
104:
105: /**
106: * Used only when there are non-ICC color spaces.
107: * Returns sequence of non-ICC color spaces and ICC transforms
108: * made from src, dst and conversionSequence.
109: * @param src
110: * @param dst
111: * @return
112: */
113: public Object[] getSequence(Object src, Object dst) {
114: ArrayList<Object> profiles = new ArrayList<Object>(10);
115: ArrayList<Object> sequence = new ArrayList<Object>(10);
116:
117: // We need this profile anyway
118: ICC_Profile xyzProfile = ICC_Profile
119: .getInstance(ColorSpace.CS_CIEXYZ);
120:
121: Object conversionFirst = null, conversionLast = null;
122: int conversionLength = conversionSequence.length;
123: if (conversionLength > 0) {
124: conversionFirst = conversionSequence[0];
125: conversionLast = conversionSequence[conversionLength - 1];
126: }
127:
128: boolean iccSequenceStarted = false;
129:
130: if (src != conversionFirst && src != null) {
131: if (src instanceof ICC_Profile) {
132: profiles.add(src);
133: iccSequenceStarted = true;
134: } else {
135: profiles.add(xyzProfile);
136: sequence.add(src); // Add non-ICC color space to the sequence
137: }
138: } else {
139: profiles.add(xyzProfile);
140: }
141:
142: for (int i = 0; i < conversionLength; i++) {
143: if (conversionSequence[i] instanceof ICC_Profile) {
144: profiles.add(conversionSequence[i]);
145: iccSequenceStarted = true;
146: } else if (iccSequenceStarted) {
147: profiles.add(xyzProfile);
148:
149: // Eliminate same profiles if there are any
150: // (e.g. xyzProfile may occur several times)
151: Object prev = profiles.get(0);
152: for (int k = 1; k < profiles.size(); k++) {
153: if (prev == profiles.get(k)) {
154: k--;
155: profiles.remove(k);
156: }
157: prev = profiles.get(k);
158: }
159:
160: // If only one profile left we skip the transform -
161: // it can be only CIEXYZ
162: if (profiles.size() > 1) {
163: sequence.add(new ICC_Transform(profiles
164: .toArray(new ICC_Profile[0])));
165:
166: // Add non-ICC color space to the sequence
167: sequence.add(conversionSequence[i]);
168: }
169:
170: profiles.clear();
171: profiles.add(xyzProfile);
172: iccSequenceStarted = false; // Sequence of ICC profiles is processed
173: } else { // Add non-ICC color space to the sequence
174: sequence.add(conversionSequence[i]);
175: }
176: }
177:
178: if (dst != conversionLast && dst != null) { // Add last profile if needed
179: if (dst instanceof ICC_Profile) {
180: profiles.add(dst);
181: iccSequenceStarted = true;
182: } else if (iccSequenceStarted) {
183: profiles.add(xyzProfile);
184: } else {
185: sequence.add(dst); // Add last non-ICC color space to the sequence
186: }
187: }
188:
189: if (iccSequenceStarted) { // Make last transform if needed
190: sequence.add(new ICC_Transform(profiles
191: .toArray(new ICC_Profile[0])));
192: if (dst != null && !(dst instanceof ICC_Profile)) {
193: sequence.add(dst); // Add last non-ICC color space to the
194: // sequence
195: }
196: }
197:
198: // Calculate max number of components
199: // This number will be used for memory allocation
200: maxComponents = 0;
201: Object o;
202: for (int i = 0, size = sequence.size(); i < size; i++) {
203: o = sequence.get(i);
204: if (o instanceof ICC_Transform) {
205: ICC_Transform t = (ICC_Transform) o;
206: maxComponents = (maxComponents > t
207: .getNumInputChannels() + 1) ? maxComponents
208: : t.getNumInputChannels() + 1;
209: maxComponents = (maxComponents > t
210: .getNumOutputChannels() + 1) ? maxComponents
211: : t.getNumOutputChannels() + 1;
212: } else {
213: ColorSpace cs = (ColorSpace) o;
214: maxComponents = (maxComponents > cs
215: .getNumComponents() + 1) ? maxComponents
216: : cs.getNumComponents() + 1;
217: }
218: }
219:
220: return sequence.toArray();
221: }
222: }
223:
224: public ColorConvertOp(ColorSpace srcCS, ColorSpace dstCS,
225: RenderingHints hints) {
226: if (srcCS == null || dstCS == null) {
227: throw new NullPointerException(Messages
228: .getString("awt.25B")); //$NON-NLS-1$
229: }
230:
231: renderingHints = hints;
232:
233: boolean srcICC = srcCS instanceof ICC_ColorSpace;
234: boolean dstICC = dstCS instanceof ICC_ColorSpace;
235:
236: if (srcICC && dstICC) {
237: conversionSequence = new ICC_Profile[2];
238: } else {
239: conversionSequence = new Object[2];
240: isICC = false;
241: }
242:
243: if (srcICC) {
244: conversionSequence[0] = ((ICC_ColorSpace) srcCS)
245: .getProfile();
246: } else {
247: conversionSequence[0] = srcCS;
248: }
249:
250: if (dstICC) {
251: conversionSequence[1] = ((ICC_ColorSpace) dstCS)
252: .getProfile();
253: } else {
254: conversionSequence[1] = dstCS;
255: }
256: }
257:
258: public ColorConvertOp(ICC_Profile profiles[], RenderingHints hints) {
259: if (profiles == null) {
260: throw new NullPointerException(Messages
261: .getString("awt.25C")); //$NON-NLS-1$
262: }
263:
264: renderingHints = hints;
265:
266: // This array is not used in the program logic, so don't need to copy it
267: // Store it only to return back
268: midProfiles = profiles;
269:
270: conversionSequence = new ICC_Profile[midProfiles.length];
271:
272: // Add profiles to the conversion sequence
273: for (int i = 0, length = midProfiles.length; i < length; i++) {
274: conversionSequence[i] = midProfiles[i];
275: }
276: }
277:
278: public ColorConvertOp(ColorSpace cs, RenderingHints hints) {
279: if (cs == null) {
280: throw new NullPointerException(Messages
281: .getString("awt.25B")); //$NON-NLS-1$
282: }
283:
284: renderingHints = hints;
285:
286: if (cs instanceof ICC_ColorSpace) {
287: conversionSequence = new ICC_Profile[1];
288: conversionSequence[0] = ((ICC_ColorSpace) cs).getProfile();
289: } else {
290: conversionSequence = new Object[1];
291: conversionSequence[0] = cs;
292: isICC = false;
293: }
294: }
295:
296: public ColorConvertOp(RenderingHints hints) {
297: renderingHints = hints;
298: }
299:
300: public final WritableRaster filter(Raster src, WritableRaster dst) {
301: if (conversionSequence.length < 2) {
302: throw new IllegalArgumentException(Messages
303: .getString("awt.25D")); //$NON-NLS-1$
304: }
305:
306: ICC_Profile srcPf = null, dstPf = null; // unused if isICC is false
307: int nSrcColorComps, nDstColorComps;
308: Object first = conversionSequence[0];
309: Object last = conversionSequence[conversionSequence.length - 1];
310:
311: // Get the number of input/output color components
312: if (isICC) {
313: srcPf = (ICC_Profile) first;
314: dstPf = (ICC_Profile) last;
315: nSrcColorComps = srcPf.getNumComponents();
316: nDstColorComps = dstPf.getNumComponents();
317: } else {
318: if (first instanceof ICC_Profile) {
319: srcPf = (ICC_Profile) first;
320: nSrcColorComps = srcPf.getNumComponents();
321: } else {
322: nSrcColorComps = ((ColorSpace) first)
323: .getNumComponents();
324: }
325:
326: if (last instanceof ICC_Profile) {
327: dstPf = (ICC_Profile) last;
328: nDstColorComps = dstPf.getNumComponents();
329: } else {
330: nDstColorComps = ((ColorSpace) last).getNumComponents();
331: }
332: }
333:
334: // Check that source and destination rasters are compatible with
335: // transforms and with each other
336: if (src.getNumBands() != nSrcColorComps) {
337: // awt.25E=Incorrect number of source raster bands. Should be equal
338: // to the number of color components of source colorspace.
339: throw new IllegalArgumentException(Messages
340: .getString("awt.25E")); //$NON-NLS-1$
341: }
342:
343: if (dst != null) { // Check destination raster
344: if (dst.getNumBands() != nDstColorComps) {
345: // awt.25F=Incorrect number of destination raster bands. Should
346: // be equal to the number of color components of destination
347: // colorspace.
348: throw new IllegalArgumentException(Messages
349: .getString("awt.25F")); //$NON-NLS-1$
350: }
351:
352: if (src.getWidth() != dst.getWidth()
353: || src.getHeight() != dst.getHeight()) {
354: throw new IllegalArgumentException(Messages
355: .getString("awt.260")); //$NON-NLS-1$
356: }
357:
358: } else {
359: dst = createCompatibleDestRaster(src);
360: }
361:
362: if (isICC) {
363: // Create transform
364: ICC_Transform t = tCreator.getTransform(srcPf, dstPf,
365: (ICC_Profile[]) conversionSequence);
366: cc.translateColor(t, src, dst);
367: } else {
368: Object[] sequence = tCreator.getSequence(null, null);
369:
370: // Get data from the source raster
371: ColorScaler scaler = new ColorScaler();
372: scaler.loadScalingData(src, null);
373: float tmpData[][] = scaler.scaleNormalize(src);
374:
375: // Get source and destination color spaces
376: ColorSpace srcCS = (srcPf == null) ? (ColorSpace) first
377: : new ICC_ColorSpace(srcPf);
378: ColorSpace dstCS = (dstPf == null) ? (ColorSpace) last
379: : new ICC_ColorSpace(dstPf);
380:
381: applySequence(sequence, tmpData, srcCS, dstCS);
382:
383: scaler.loadScalingData(dst, null);
384: scaler.unscaleNormalized(dst, tmpData);
385: }
386:
387: return dst;
388: }
389:
390: public BufferedImage createCompatibleDestImage(BufferedImage src,
391: ColorModel destCM) {
392: // If destination color model is passed only one line needed
393: if (destCM != null) {
394: return new BufferedImage(destCM, destCM
395: .createCompatibleWritableRaster(src.getWidth(), src
396: .getHeight()), destCM
397: .isAlphaPremultiplied(), null);
398: }
399:
400: int nSpaces = conversionSequence.length;
401:
402: if (nSpaces < 1) {
403: throw new IllegalArgumentException(Messages
404: .getString("awt.261")); //$NON-NLS-1$
405: }
406:
407: // Get destination color space
408: Object destination = conversionSequence[nSpaces - 1];
409: ColorSpace dstCS = (destination instanceof ColorSpace) ? (ColorSpace) destination
410: : new ICC_ColorSpace((ICC_Profile) destination);
411:
412: ColorModel srcCM = src.getColorModel();
413: ColorModel dstCM = new ComponentColorModel(dstCS, srcCM
414: .hasAlpha(), srcCM.isAlphaPremultiplied(), srcCM
415: .getTransparency(), srcCM.getTransferType());
416:
417: return new BufferedImage(dstCM, destCM
418: .createCompatibleWritableRaster(src.getWidth(), src
419: .getHeight()), destCM.isAlphaPremultiplied(),
420: null);
421: }
422:
423: public final BufferedImage filter(BufferedImage src,
424: BufferedImage dst) {
425: if (dst == null && conversionSequence.length < 1) {
426: throw new IllegalArgumentException(Messages
427: .getString("awt.262")); //$NON-NLS-1$
428: }
429:
430: ColorModel srcCM = src.getColorModel();
431: // First handle index color model
432: if (srcCM instanceof IndexColorModel) {
433: src = ((IndexColorModel) srcCM).convertToIntDiscrete(src
434: .getRaster(), false);
435: }
436: ColorSpace srcCS = srcCM.getColorSpace();
437:
438: BufferedImage res;
439: boolean isDstIndex = false;
440: if (dst != null) {
441:
442: if (src.getWidth() != dst.getWidth()
443: || src.getHeight() != dst.getHeight()) {
444: throw new IllegalArgumentException(Messages
445: .getString("awt.263")); //$NON-NLS-1$
446: }
447:
448: if (dst.getColorModel() instanceof IndexColorModel) {
449: isDstIndex = true;
450: res = createCompatibleDestImage(src, null);
451: } else {
452: res = dst;
453: }
454: } else {
455: res = createCompatibleDestImage(src, null);
456: }
457: ColorModel dstCM = res.getColorModel();
458: ColorSpace dstCS = dstCM.getColorSpace();
459:
460: ICC_Profile srcPf = null, dstPf = null;
461: if (srcCS instanceof ICC_ColorSpace) {
462: srcPf = ((ICC_ColorSpace) srcCS).getProfile();
463: }
464: if (dstCS instanceof ICC_ColorSpace) {
465: dstPf = ((ICC_ColorSpace) dstCS).getProfile();
466: }
467:
468: boolean isFullICC = isICC && srcPf != null && dstPf != null;
469:
470: if (isFullICC) {
471: ICC_Transform t = tCreator.getTransform(srcPf, dstPf,
472: (ICC_Profile[]) conversionSequence);
473: cc.translateColor(t, src, res);
474: } else { // Perform non-ICC transform
475: Object sequence[] = tCreator.getSequence(
476: srcPf == null ? (Object) srcCS : srcPf,
477: dstPf == null ? (Object) dstCS : dstPf);
478:
479: int srcW = src.getWidth();
480: int srcH = src.getHeight();
481: int numPixels = srcW * srcH;
482:
483: // Load all pixel data into array tmpData
484: float tmpData[][] = new float[numPixels][tCreator.maxComponents];
485: for (int row = 0, dataPos = 0; row < srcW; row++) {
486: for (int col = 0; col < srcH; col++) {
487: tmpData[dataPos] = srcCM.getNormalizedComponents(
488: src.getRaster().getDataElements(row, col,
489: null), tmpData[dataPos], 0);
490: dataPos++;
491: }
492: }
493:
494: // Copy alpha channel if needed
495: float alpha[] = null;
496: int alphaIdx = srcCM.numComponents - 1;
497: if (srcCM.hasAlpha() && dstCM.hasAlpha()) {
498: alpha = new float[numPixels];
499: for (int i = 0; i < numPixels; i++) {
500: alpha[i] = tmpData[i][alphaIdx];
501: }
502: }
503:
504: // Translate colors
505: applySequence(sequence, tmpData, srcCS, dstCS);
506:
507: // Copy alpha if needed
508: if (dstCM.hasAlpha()) {
509: alphaIdx = dstCM.numComponents - 1;
510: if (alpha != null) {
511: for (int i = 0; i < numPixels; i++) {
512: tmpData[i][alphaIdx] = alpha[i];
513: }
514: } else {
515: for (int i = 0; i < numPixels; i++) {
516: tmpData[i][alphaIdx] = 1f;
517: }
518: }
519: }
520:
521: // Store data back to the image
522: for (int row = 0, dataPos = 0; row < srcW; row++) {
523: for (int col = 0; col < srcH; col++) {
524: res.getRaster().setDataElements(
525: row,
526: col,
527: dstCM.getDataElements(tmpData[dataPos++],
528: 0, null));
529: }
530: }
531: }
532:
533: if (isDstIndex) { // Convert image into indexed color
534: Graphics2D g2d = dst.createGraphics();
535: g2d.drawImage(res, 0, 0, null);
536: g2d.dispose();
537: return dst;
538: }
539:
540: return res;
541: }
542:
543: private void applySequence(Object sequence[], float tmpData[][],
544: ColorSpace srcCS, ColorSpace dstCS) {
545: ColorSpace xyzCS = ColorSpace.getInstance(ColorSpace.CS_CIEXYZ);
546:
547: int numPixels = tmpData.length;
548:
549: // First transform...
550: if (sequence[0] instanceof ICC_Transform) { // ICC
551: ICC_Transform t = (ICC_Transform) sequence[0];
552: cc.translateColor(t, tmpData, srcCS, xyzCS, numPixels);
553: } else { // non ICC
554: for (int k = 0; k < numPixels; k++) {
555: tmpData[k] = srcCS.toCIEXYZ(tmpData[k]);
556: }
557: cc.loadScalingData(xyzCS); // prepare for scaling XYZ
558: }
559:
560: for (Object element : sequence) {
561: if (element instanceof ICC_Transform) {
562: ICC_Transform t = (ICC_Transform) element;
563: cc.translateColor(t, tmpData, null, null, numPixels);
564: } else {
565: ColorSpace cs = (ColorSpace) element;
566: for (int k = 0; k < numPixels; k++) {
567: tmpData[k] = cs.fromCIEXYZ(tmpData[k]);
568: tmpData[k] = cs.toCIEXYZ(tmpData[k]);
569: }
570: }
571: }
572:
573: // Last transform...
574: if (sequence[sequence.length - 1] instanceof ICC_Transform) { // ICC
575: ICC_Transform t = (ICC_Transform) sequence[sequence.length - 1];
576: cc.translateColor(t, tmpData, xyzCS, dstCS, numPixels);
577: } else { // non ICC
578: for (int k = 0; k < numPixels; k++) {
579: tmpData[k] = dstCS.fromCIEXYZ(tmpData[k]);
580: }
581: }
582: }
583:
584: public final Point2D getPoint2D(Point2D srcPt, Point2D dstPt) {
585: if (dstPt != null) {
586: dstPt.setLocation(srcPt);
587: return dstPt;
588: }
589: return new Point2D.Float((float) srcPt.getX(), (float) srcPt
590: .getY());
591: }
592:
593: public WritableRaster createCompatibleDestRaster(Raster src) {
594: int nComps = 0;
595: int nSpaces = conversionSequence.length;
596:
597: if (nSpaces < 2) {
598: throw new IllegalArgumentException(Messages
599: .getString("awt.261")); //$NON-NLS-1$
600: }
601:
602: Object lastCS = conversionSequence[nSpaces - 1];
603: if (lastCS instanceof ColorSpace) {
604: nComps = ((ColorSpace) lastCS).getNumComponents();
605: } else {
606: nComps = ((ICC_Profile) lastCS).getNumComponents();
607: }
608:
609: // Calculate correct data type
610: int dstDataType = src.getDataBuffer().getDataType();
611: if (dstDataType != DataBuffer.TYPE_BYTE
612: && dstDataType != DataBuffer.TYPE_SHORT) {
613: dstDataType = DataBuffer.TYPE_SHORT;
614: }
615:
616: return Raster.createInterleavedRaster(dstDataType, src
617: .getWidth(), src.getHeight(), nComps, new Point(src
618: .getMinX(), src.getMinY()));
619: }
620:
621: public final Rectangle2D getBounds2D(Raster src) {
622: return src.getBounds();
623: }
624:
625: public final Rectangle2D getBounds2D(BufferedImage src) {
626: return src.getRaster().getBounds();
627: }
628:
629: public final ICC_Profile[] getICC_Profiles() {
630: if (midProfiles != null) {
631: return midProfiles;
632: }
633: return null;
634: }
635:
636: public final RenderingHints getRenderingHints() {
637: return renderingHints;
638: }
639: }
|