1 /* Copyright (C) 2005-2011 Fabio Riccardi */
2 
3 /*
4  * $RCSfile: LCColorConvertOpImage.java,v $
5  *
6  * Copyright (c) 2005 Sun Microsystems, Inc. All rights reserved.
7  *
8  * Use is subject to license terms.
9  *
10  * $Revision: 1.4 $
11  * $Date: 2005/03/28 17:45:12 $
12  * $State: Exp $
13  */
14 
15 package com.lightcrafts.jai.opimage;
16 
17 import com.lightcrafts.jai.operator.LCColorConvertDescriptor;
18 
19 import java.awt.Point;
20 import java.awt.Rectangle;
21 import java.awt.color.ColorSpace;
22 import java.awt.color.ICC_ColorSpace;
23 import java.awt.color.ICC_Profile;
24 import java.awt.image.*;
25 import java.util.ArrayList;
26 import java.util.HashMap;
27 import java.util.Map;
28 import com.lightcrafts.mediax.jai.ColorSpaceJAI;
29 import com.lightcrafts.mediax.jai.ImageLayout;
30 import com.lightcrafts.mediax.jai.PointOpImage;
31 import com.lightcrafts.mediax.jai.RasterFactory;
32 import java.lang.ref.SoftReference;
33 
34 /**
35  * An <code>OpImage</code> implementing the "LCColorConvert" operation as
36  * described in <code>com.lightcrafts.mediax.jai.operator.ColorConvertDescriptor</code>.
37  *
38  * @since EA4
39  *
40  * @see com.lightcrafts.mediax.jai.PointOpImage
41  * @see com.lightcrafts.mediax.jai.operator.ColorConvertDescriptor
42  *
43  */
44 final class LCColorConvertOpImage extends PointOpImage {
45     /** Cache a rgb color space */
46     private static final ColorSpace rgbColorSpace
47 	= ColorSpace.getInstance(ColorSpace.CS_sRGB);
48 
49     private static SoftReference<Map<ArrayList<?>, ColorConvertOp>> softRef = null;
50 
51     /** The source image parameters */
52     private ImageParameters srcParam = null;
53 
54     /** The source image parameters */
55     private ImageParameters dstParam = null;
56 
57     /** The intermediate image parameters */
58     private ImageParameters tempParam = null;
59 
60     /** The Java 2D LCColorConvertOp instance for converting integer type */
61     private ColorConvertOp colorConvertOp = null;
62 
63     /** case number */
64     private int caseNumber;
65 
intFromBigEndian(byte[] array, int index)66     private static int intFromBigEndian(byte[] array, int index) {
67         return (((array[index]   & 0xff) << 24) |
68                 ((array[index+1] & 0xff) << 16) |
69                 ((array[index+2] & 0xff) <<  8) |
70                  (array[index+3] & 0xff));
71     }
72 
73 
intToBigEndian(int value, byte[] array, int index)74     private static void intToBigEndian(int value, byte[] array, int index) {
75             array[index]   = (byte) (value >> 24);
76             array[index+1] = (byte) (value >> 16);
77             array[index+2] = (byte) (value >>  8);
78             array[index+3] = (byte) (value);
79     }
80 
81     /**
82      * Sets the rendering intent of the profile.
83      * This is used to select the proper transform from a profile that
84      * has multiple transforms.
85      */
setRenderingIntent(ICC_Profile profile, int renderingIntent)86     private static void setRenderingIntent(ICC_Profile profile, int renderingIntent) {
87         byte[] theHeader = profile.getData(ICC_Profile.icSigHead); /* getData will activate deferred
88                                                                       profiles if necessary */
89         intToBigEndian (renderingIntent, theHeader, ICC_Profile.icHdrRenderingIntent);
90                                                  /* set the rendering intent */
91         profile.setData (ICC_Profile.icSigHead, theHeader);
92     }
93 
94     /**
95      * Returns the rendering intent of the profile.
96      * This is used to select the proper transform from a profile that
97      * has multiple transforms.  It is typically set in a source profile
98      * to select a transform from an output profile.
99      */
getRenderingIntent(ICC_Profile profile)100     private static int getRenderingIntent(ICC_Profile profile) {
101         byte[] theHeader = profile.getData(ICC_Profile.icSigHead); /* getData will activate deferred
102                                                                       profiles if necessary */
103         int renderingIntent = intFromBigEndian(theHeader, ICC_Profile.icHdrRenderingIntent);
104                                                  /* set the rendering intent */
105         return renderingIntent;
106     }
107 
108     /**
109      * Retrive/cache the ColorConvertOp. Because instantiate a ColorConvertOp
110      * is a time-consuming step, create a hashtable referred to by a
111      * SoftReference to cache the ColorConvertOp for using repeatedly.
112      *
113      * @param src the color space of the source image
114      *	      dst the color space of the destination image
115      * @return The ColorConvertOp to convert from the source color space to
116      *	       the destination color space.
117      */
118     private static synchronized ColorConvertOp
getColorConvertOp(ColorSpace src, ColorSpace dst, LCColorConvertDescriptor.RenderingIntent renderingIntent)119     getColorConvertOp(ColorSpace src, ColorSpace dst, LCColorConvertDescriptor.RenderingIntent renderingIntent) {
120         Map<ArrayList<?>, ColorConvertOp> colorConvertOpBuf;
121 
122         if (softRef == null || ((colorConvertOpBuf = softRef.get()) == null)) {
123             colorConvertOpBuf = new HashMap<ArrayList<?>, ColorConvertOp>();
124             softRef = new SoftReference<Map<ArrayList<?>, ColorConvertOp>>(colorConvertOpBuf);
125         }
126 
127         ArrayList<Object> hashcode = new ArrayList<Object>(2);
128         hashcode.add(0, src);
129         hashcode.add(1, dst);
130         ColorConvertOp op = colorConvertOpBuf.get(hashcode);
131 
132         if (op == null) {
133             if (src instanceof ICC_ColorSpace && dst instanceof ICC_ColorSpace) {
134                 // ICC_Profile srcProfile = ((ICC_ColorSpace) src).getProfile();
135                 ICC_Profile dstProfile = ((ICC_ColorSpace) dst).getProfile();
136 
137                 // srcProfile = ICC_Profile.getInstance(srcProfile.getData());
138                 dstProfile = ICC_Profile.getInstance(dstProfile.getData());
139 
140                 /*if (getRenderingIntent(srcProfile) != renderingIntent.getValue()) {
141                     srcProfile = ICC_Profile.getInstance(srcProfile.getData());
142                     setRenderingIntent(srcProfile, renderingIntent.getValue());
143                     src = new ICC_ColorSpace(srcProfile);
144                 }*/
145 
146                 if (renderingIntent != LCColorConvertDescriptor.DEFAULT
147                         && getRenderingIntent(dstProfile) != renderingIntent.getValue())
148                 {
149                     dstProfile = ICC_Profile.getInstance(dstProfile.getData());
150                     setRenderingIntent(dstProfile, renderingIntent.getValue());
151                     dst = new ICC_ColorSpace(dstProfile);
152                 }
153             }
154 
155             op = new ColorConvertOp(src, dst, null);
156             colorConvertOpBuf.put(hashcode, op);
157         }
158 
159         return op;
160     }
161 
162     /**
163      * Retrieve the minimum value of a data type.
164      *
165      * @param dataType The data type as in DataBuffer.TYPE_*.
166      * @return The minimum value of the specified data type.
167      */
getMinValue(int dataType)168     private static float getMinValue(int dataType) {
169         final float minValue;
170         switch (dataType) {
171         case DataBuffer.TYPE_BYTE:
172             minValue = 0;
173             break;
174         case DataBuffer.TYPE_SHORT:
175             minValue = Short.MIN_VALUE;
176             break;
177         case DataBuffer.TYPE_USHORT:
178             minValue = 0;
179             break;
180         case DataBuffer.TYPE_INT:
181             minValue = Integer.MIN_VALUE;
182             break;
183         default:
184             minValue = 0;
185         }
186 
187         return minValue;
188     }
189 
190     /**
191      * Retrieve the range of a data type.
192      *
193      * @param dataType The data type as in DataBuffer.TYPE_*.
194      * @return The range of the specified data type.
195      */
getRange(int dataType)196     private static float getRange(int dataType) {
197         final float range;
198         switch (dataType) {
199         case DataBuffer.TYPE_BYTE:
200             range = 255;
201             break;
202         case DataBuffer.TYPE_SHORT:
203             range = Short.MAX_VALUE - (int) Short.MIN_VALUE;
204             break;
205         case DataBuffer.TYPE_USHORT:
206             range = Short.MAX_VALUE - (int)Short.MIN_VALUE;
207             break;
208         case DataBuffer.TYPE_INT:
209             range = Integer.MAX_VALUE - (long) Integer.MIN_VALUE;
210             break;
211         default:
212             range = 1;
213         }
214 
215         return range;
216     }
217 
218     /**
219      * Constructor.
220      *
221      * @param source     The source image.
222      * @param config     Configurable attributes of the image including
223      *        configuration variables indexed by
224      *        <code>RenderingHints.Key</code>s and image properties indexed
225      *        by <code>String</code>s or <code>CaselessStringKey</code>s.
226      *        This is simply forwarded to the superclass constructor.
227      * @param layout     The destination image layout.
228      * @param colorModel The destination color model.
229      */
LCColorConvertOpImage(RenderedImage source, Map config, ImageLayout layout, ColorModel colorModel, LCColorConvertDescriptor.RenderingIntent renderingIntent)230     public LCColorConvertOpImage(RenderedImage source,
231                                 Map config,
232                                 ImageLayout layout,
233                                 ColorModel colorModel,
234                                 LCColorConvertDescriptor.RenderingIntent renderingIntent) {
235         super(source, layout, config, true);
236         this.colorModel = colorModel;
237 
238         // Cache the ColorModels.
239         srcParam = new ImageParameters(source.getColorModel(), source.getSampleModel());
240         dstParam = new ImageParameters(colorModel, sampleModel);
241 
242         ColorSpace srcColorSpace = srcParam.getColorModel().getColorSpace();
243         ColorSpace dstColorSpace = dstParam.getColorModel().getColorSpace();
244 
245         // for each case, define the case number; create tempParam
246         // and/or ColorConvertOp if necessary
247         if (srcColorSpace instanceof ColorSpaceJAI &&
248                 dstColorSpace instanceof ColorSpaceJAI) {
249 
250             // when both are ColorSpaceJAI, convert via RGB
251             caseNumber = 1;
252             tempParam = createTempParam();
253         } else if (srcColorSpace instanceof ColorSpaceJAI) {
254 
255             // when source is ColorSpaceJAI, 1. convert via RGB if
256             // the dest isn't RGB; 2. convert to RGB
257             if (dstColorSpace != rgbColorSpace) {
258                 caseNumber = 2;
259                 tempParam = createTempParam();
260                 colorConvertOp = getColorConvertOp(rgbColorSpace, dstColorSpace, renderingIntent);
261             } else {
262                 caseNumber = 3;
263             }
264         } else if (dstColorSpace instanceof ColorSpaceJAI) {
265 
266             // when destination is ColorSpaceJAI, 1. convert via RGB if
267             // source isn't RGB; 2. convert from RGB
268             if (srcColorSpace != rgbColorSpace) {
269                 caseNumber = 4;
270                 tempParam = createTempParam();
271                 colorConvertOp = getColorConvertOp(srcColorSpace, rgbColorSpace, renderingIntent);
272             } else {
273                 caseNumber = 5;
274             }
275         } else {
276             // if all the color space are not ColorSpaceJAI
277             caseNumber = 6;
278             colorConvertOp = getColorConvertOp(srcColorSpace, dstColorSpace, renderingIntent);
279         }
280 
281         // Set flag to permit in-place operation.
282         permitInPlaceOperation();
283     }
284 
285     /**
286      * Computes a tile of the destination image in the destination color space.
287      *
288      * @param sources   Cobbled sources, guaranteed to provide all the
289      *                  source data necessary for computing the rectangle.
290      * @param dest      The tile containing the rectangle to be computed.
291      * @param destRect  The rectangle within the tile to be computed.
292      */
computeRect(Raster[] sources, WritableRaster dest, Rectangle destRect)293     protected void computeRect(Raster[] sources,
294                                WritableRaster dest,
295                                Rectangle destRect) {
296         WritableRaster tempRas;
297 
298         switch (caseNumber) {
299             // 1. When source and destination color spaces are all ColorSpaceJAI,
300             // convert via RGB color space
301             case 1:
302                 tempRas = computeRectColorSpaceJAIToRGB(sources[0], srcParam,
303                         null, tempParam);
304                 computeRectColorSpaceJAIFromRGB(tempRas, tempParam,
305                         dest, dstParam);
306                 break;
307             // when only the source color space is ColorSpaceJAI,
308             // 2. if the destination is not RGB, convert to RGB using
309             //    ColorSpaceJAI; then convert RGB to the destination
310             // 3. if the destination is RGB, convert using ColorSpaceJAI
311             case 2:
312                 tempRas = computeRectColorSpaceJAIToRGB(sources[0], srcParam,
313                         null, tempParam);
314                 computeRectNonColorSpaceJAI(tempRas, tempParam,
315                         dest, dstParam, destRect);
316                 break;
317             case 3:
318                 computeRectColorSpaceJAIToRGB(sources[0], srcParam,
319                         dest, dstParam);
320                 break;
321             // 4, 5. When only the destination color space is ColorSpaceJAI,
322             // similar to the case above.
323             case 4:
324                 tempRas =createTempWritableRaster(sources[0]);
325                 computeRectNonColorSpaceJAI(sources[0], srcParam,
326                         tempRas, tempParam, destRect);
327                 computeRectColorSpaceJAIFromRGB(tempRas, tempParam,
328                         dest, dstParam);
329                 break;
330             case 5:
331                 computeRectColorSpaceJAIFromRGB(sources[0], srcParam,
332                         dest, dstParam);
333                 break;
334             // 6. If all the color space are not ColorSpaceJAI
335             case 6:
336                 computeRectNonColorSpaceJAI(sources[0], srcParam,
337                         dest, dstParam, destRect);
338             default :
339                 break;
340         }
341     }
342 
343     // when the source color space is ColorSpaceJAI, convert it to RGB.
344     // 1. If the source data type is short/int, shift the data to [0,
345     //    MAX-MIN]
346     // 2. Convert to RGB.
347     // 3. Shift back to [MIN, MAX]
computeRectColorSpaceJAIToRGB(Raster src, ImageParameters srcParam, WritableRaster dest, ImageParameters dstParam)348     private WritableRaster computeRectColorSpaceJAIToRGB(Raster src,
349 							 ImageParameters srcParam,
350 							 WritableRaster dest,
351 							 ImageParameters dstParam) {
352 	src = convertRasterToUnsigned(src);
353 
354 	ColorSpaceJAI colorSpaceJAI
355 	    = (ColorSpaceJAI) srcParam.getColorModel().getColorSpace();
356 	dest = colorSpaceJAI.toRGB(src, srcParam.getComponentSize(), dest,
357 			           dstParam.getComponentSize());
358 
359         dest = convertRasterToSigned(dest);
360 	return dest;
361     }
362 
363     // when the source color space is ColorSpaceJAI, convert it from RGB.
364     // 1. If the source data type is short/int, shift the data to [0,
365     //    MAX-MIN]
366     // 2. Convert from RGB.
367     // 3. Shift back to [MIN, MAX]
computeRectColorSpaceJAIFromRGB(Raster src, ImageParameters srcParam, WritableRaster dest, ImageParameters dstParam)368     private WritableRaster computeRectColorSpaceJAIFromRGB(Raster src,
369 							   ImageParameters srcParam,
370 							   WritableRaster dest,
371 							   ImageParameters dstParam){
372 	src = convertRasterToUnsigned(src);
373 	ColorSpaceJAI colorSpaceJAI
374 	    = (ColorSpaceJAI) dstParam.getColorModel().getColorSpace();
375         dest = colorSpaceJAI.fromRGB(src, srcParam.getComponentSize(), dest,
376 				     dstParam.getComponentSize());
377 
378 	dest = convertRasterToSigned(dest);
379 	return dest;
380     }
381 
382     // When the source and destination color spaces are not ColorSpaceJAI,
383     // convert using ColorConvertOp of Java 2D for integer type. For the
384     // floating point, use the following method.
computeRectNonColorSpaceJAI(Raster src, ImageParameters srcParam, WritableRaster dest, ImageParameters dstParam, Rectangle destRect)385     private void computeRectNonColorSpaceJAI(Raster src,
386 					     ImageParameters srcParam,
387 					     WritableRaster dest,
388 					     ImageParameters dstParam,
389 					     Rectangle destRect) {
390         if (!srcParam.isFloat() && !dstParam.isFloat()) {
391             // Create a ColorConvertOp if there are only integral data.
392             // Integral type: use the ColorConvertOp.
393 
394             // Ensure that the Rasters are the same size as apparently
395             // required by ColorConvertOp although not so documented.
396             Raster s = src;
397             if (s.getMinX() != destRect.x ||
398                 s.getMinY() != destRect.y ||
399                 s.getWidth() != destRect.width ||
400                 s.getHeight() != destRect.height) {
401                 s = s.createChild(destRect.x, destRect.y,
402                                   destRect.width, destRect.height,
403                                   destRect.x, destRect.y, null);
404             }
405             WritableRaster d = dest;
406             if (d.getMinX() != destRect.x ||
407                 d.getMinY() != destRect.y ||
408                 d.getWidth() != destRect.width ||
409                 d.getHeight() != destRect.height) {
410                 d = d.createWritableChild(destRect.x, destRect.y,
411                                	          destRect.width, destRect.height,
412                                           destRect.x, destRect.y, null);
413             }
414 
415             // Perform the color conversion on the (possible child) Rasters.
416             synchronized (ColorSpace.class) {
417                 colorConvertOp.filter(s, d);
418             }
419         } else {
420             //For the floating point data types, convert via CIEXYZ color space.
421             //Do it pixel-by-pixel (slow!).
422             ColorSpace srcColorSpace = srcParam.getColorModel().getColorSpace();
423             ColorSpace dstColorSpace = dstParam.getColorModel().getColorSpace();
424 	    boolean srcFloat = srcParam.isFloat();
425 	    float srcMinValue = srcParam.getMinValue();
426 	    float srcRange = srcParam.getRange();
427 
428 	    boolean dstFloat = dstParam.isFloat();
429 	    float dstMinValue = dstParam.getMinValue();
430 	    float dstRange = dstParam.getRange();
431 
432             int rectYMax = destRect.y + destRect.height;
433             int rectXMax = destRect.x + destRect.width;
434             int numComponents = srcColorSpace.getNumComponents();
435             float[] srcPixel = new float[numComponents];
436             float[] xyzPixel;
437             float[] dstPixel;
438             for (int y = destRect.y; y < rectYMax; y++) {
439                 for (int x = destRect.x; x < rectXMax; x++) {
440                     srcPixel = src.getPixel(x, y, srcPixel);
441                     if (!srcFloat) {
442                         // Normalize the source samples.
443                         for (int i = 0; i < numComponents; i++) {
444                             srcPixel[i] = (srcPixel[i] - srcMinValue)/srcRange;
445                         }
446                     }
447 
448                     // Convert src to dst via CIEXYZ.
449                     synchronized (ColorSpace.class) {
450                         xyzPixel = srcColorSpace.toCIEXYZ(srcPixel);
451                         dstPixel = dstColorSpace.fromCIEXYZ(xyzPixel);
452                     }
453 
454                     if (!dstFloat) {
455                         // Scale the destination samples.
456                         for (int i = 0; i < numComponents; i++) {
457                             dstPixel[i] = (dstPixel[i]*dstRange + dstMinValue);
458                         }
459                     }
460                     dest.setPixel(x, y, dstPixel);
461                 }
462             }
463         }
464     }
465 
466     // Back up the destination parameters. Set the destination to the
467     // bridge color space RGB.
createTempParam()468     private ImageParameters createTempParam() {
469         ColorModel cm;
470         SampleModel sm;
471 
472         if (srcParam.getDataType() > dstParam.getDataType()) {
473             cm = srcParam.getColorModel();
474             sm = srcParam.getSampleModel();
475         } else {
476             cm = dstParam.getColorModel();
477             sm = dstParam.getSampleModel();
478         }
479 
480         cm  = new ComponentColorModel(rgbColorSpace,
481                 cm.getComponentSize(),
482                 cm.hasAlpha() ,
483                 cm.isAlphaPremultiplied(),
484                 cm.getTransparency(),
485                 sm.getDataType());
486         return new ImageParameters(cm, sm);
487     }
488 
489     // Create an WritableRaster with the same SampleModel and location
490     // as the passed Raster parameter.
createTempWritableRaster(Raster src)491     private WritableRaster createTempWritableRaster(Raster src) {
492 	Point origin = new Point(src.getMinX(), src.getMinY());
493 	return RasterFactory.createWritableRaster(src.getSampleModel(),
494 						  origin);
495     }
496 
497     // Shift the sample value to [0, MAX-MIN]
convertRasterToUnsigned(Raster ras)498     private Raster convertRasterToUnsigned(Raster ras) {
499         int type = ras.getSampleModel().getDataType();
500 
501         if ((type == DataBuffer.TYPE_INT
502             || type == DataBuffer.TYPE_SHORT)) {
503             int minX = ras.getMinX(), minY = ras.getMinY();
504             int w = ras.getWidth() , h = ras.getHeight();
505 
506             int[] buf = ras.getPixels(minX, minY, w, h, (int[])null);
507             convertBufferToUnsigned(buf, type);
508 
509             WritableRaster tempRas = createTempWritableRaster(ras);
510             tempRas.setPixels(minX, minY, w, h, buf);
511             return tempRas;
512         }
513         return ras;
514     }
515 
516     // Shift the sample value back to [MIN, MAX]
convertRasterToSigned(WritableRaster ras)517     private WritableRaster convertRasterToSigned(WritableRaster ras) {
518         int type = ras.getSampleModel().getDataType();
519 
520         if ((type == DataBuffer.TYPE_INT
521             || type == DataBuffer.TYPE_SHORT)) {
522             int minX = ras.getMinX(), minY = ras.getMinY();
523             int w = ras.getWidth() , h = ras.getHeight();
524 
525             int[] buf = ras.getPixels(minX, minY, w, h, (int[])null);
526             convertBufferToSigned(buf, type);
527 
528             ras.setPixels(minX, minY, w, h, buf);
529         }
530         return ras;
531     }
532 
533     // Shift the value to [MIN, MAX]
convertBufferToSigned(int[] buf, int type)534     private void convertBufferToSigned(int[] buf, int type) {
535         if (buf == null) return;
536 
537         if (type == DataBuffer.TYPE_SHORT)
538             for (int i=0; i < buf.length; i++) {
539                 buf[i] += Short.MIN_VALUE;
540             }
541         else if (type == DataBuffer.TYPE_INT) {
542             for (int i=0; i < buf.length; i++) {
543                 buf[i] = (int) ((buf[i] & 0xFFFFFFFFL) + Integer.MIN_VALUE);
544             }
545         }
546     }
547 
548     // Shift the value to [0, MAX-MIN]
convertBufferToUnsigned(int[] buf, int type)549     private void convertBufferToUnsigned(int[] buf, int type) {
550         if (buf == null) return;
551 
552         if (type == DataBuffer.TYPE_SHORT)
553             for (int i = 0; i < buf.length; i++) {
554                 buf[i] -= Short.MIN_VALUE;
555             }
556         else if (type == DataBuffer.TYPE_INT) {
557             for (int i = 0; i < buf.length; i++) {
558                 buf[i] = (int) ((buf[i] & 0xFFFFFFFFL) - Integer.MIN_VALUE);
559             }
560         }
561     }
562 
563 
564 // define a class to cache the parameters
565     private final class ImageParameters {
566         private boolean isFloat;
567         private ColorModel colorModel;
568         private SampleModel sampleModel;
569         private float minValue;
570         private float range;
571         private int[] componentSize;
572         private int dataType;
573 
ImageParameters(ColorModel cm, SampleModel sm)574         ImageParameters(ColorModel cm, SampleModel sm) {
575 	    this.colorModel = cm;
576 	    this.sampleModel = sm;
577 	    this.dataType = sm.getDataType();
578 	    this.isFloat = this.dataType == DataBuffer.TYPE_FLOAT
579 			   || this.dataType == DataBuffer.TYPE_DOUBLE;
580 	    this.minValue = LCColorConvertOpImage.getMinValue(this.dataType);
581 	    this.range = LCColorConvertOpImage.getRange(this.dataType);
582 	    this.componentSize = cm.getComponentSize();
583         }
584 
isFloat()585         public boolean isFloat() {
586 	    return isFloat;
587         }
588 
getColorModel()589         public ColorModel getColorModel() {
590 	    return colorModel;
591         }
592 
getSampleModel()593         public SampleModel getSampleModel() {
594 	    return sampleModel;
595         }
596 
getMinValue()597         public float getMinValue() {
598 	    return minValue;
599         }
600 
getRange()601         public float getRange() {
602 	    return range;
603         }
604 
getComponentSize()605         public int[] getComponentSize() {
606 	    return componentSize;
607         }
608 
getDataType()609         public int getDataType() {
610 	    return dataType;
611         }
612     }
613 }
614 
615