001/*
002 * $RCSfile: FileFormatReader.java,v $
003 * $Revision: 1.2 $
004 * $Date: 2005/04/28 01:25:38 $
005 * $State: Exp $
006 *
007 * Class:                   FileFormatReader
008 *
009 * Description:             Read J2K file stream
010 *
011 * COPYRIGHT:
012 *
013 * This software module was originally developed by Raphaël Grosbois and
014 * Diego Santa Cruz (Swiss Federal Institute of Technology-EPFL); Joel
015 * Askelöf (Ericsson Radio Systems AB); and Bertrand Berthelot, David
016 * Bouchard, Félix Henry, Gerard Mozelle and Patrice Onno (Canon Research
017 * Centre France S.A) in the course of development of the JPEG2000
018 * standard as specified by ISO/IEC 15444 (JPEG 2000 Standard). This
019 * software module is an implementation of a part of the JPEG 2000
020 * Standard. Swiss Federal Institute of Technology-EPFL, Ericsson Radio
021 * Systems AB and Canon Research Centre France S.A (collectively JJ2000
022 * Partners) agree not to assert against ISO/IEC and users of the JPEG
023 * 2000 Standard (Users) any of their rights under the copyright, not
024 * including other intellectual property rights, for this software module
025 * with respect to the usage by ISO/IEC and Users of this software module
026 * or modifications thereof for use in hardware or software products
027 * claiming conformance to the JPEG 2000 Standard. Those intending to use
028 * this software module in hardware or software products are advised that
029 * their use may infringe existing patents. The original developers of
030 * this software module, JJ2000 Partners and ISO/IEC assume no liability
031 * for use of this software module or modifications thereof. No license
032 * or right to this software module is granted for non JPEG 2000 Standard
033 * conforming products. JJ2000 Partners have full right to use this
034 * software module for his/her own purpose, assign or donate this
035 * software module to any third party and to inhibit third parties from
036 * using this software module for non JPEG 2000 Standard conforming
037 * products. This copyright notice must be included in all copies or
038 * derivative works of this software module.
039 *
040 * Copyright (c) 1999/2000 JJ2000 Partners.
041 * */
042package jj2000.j2k.fileformat.reader;
043
044import java.awt.Transparency;
045import java.awt.color.ColorSpace;
046import java.awt.color.ICC_ColorSpace;
047import java.awt.color.ICC_Profile;
048import java.awt.image.ColorModel;
049import java.awt.image.ComponentColorModel;
050import java.awt.image.DataBuffer;
051import java.awt.image.IndexColorModel;
052import java.io.EOFException;
053import java.io.IOException;
054import java.util.Vector;
055
056import jj2000.j2k.codestream.Markers;
057import jj2000.j2k.fileformat.FileFormatBoxes;
058import jj2000.j2k.io.RandomAccessIO;
059
060import com.github.jaiimageio.jpeg2000.impl.BitsPerComponentBox;
061import com.github.jaiimageio.jpeg2000.impl.Box;
062import com.github.jaiimageio.jpeg2000.impl.ChannelDefinitionBox;
063import com.github.jaiimageio.jpeg2000.impl.ColorSpecificationBox;
064import com.github.jaiimageio.jpeg2000.impl.ComponentMappingBox;
065import com.github.jaiimageio.jpeg2000.impl.DataEntryURLBox;
066import com.github.jaiimageio.jpeg2000.impl.FileTypeBox;
067import com.github.jaiimageio.jpeg2000.impl.HeaderBox;
068import com.github.jaiimageio.jpeg2000.impl.J2KMetadata;
069import com.github.jaiimageio.jpeg2000.impl.PaletteBox;
070import com.github.jaiimageio.jpeg2000.impl.ResolutionBox;
071import com.github.jaiimageio.jpeg2000.impl.SignatureBox;
072import com.github.jaiimageio.jpeg2000.impl.UUIDBox;
073import com.github.jaiimageio.jpeg2000.impl.UUIDListBox;
074import com.github.jaiimageio.jpeg2000.impl.XMLBox;
075
076/**
077 * This class reads the file format wrapper that may or may not exist around a
078 * valid JPEG 2000 codestream. Since no information from the file format is
079 * used in the actual decoding, this class simply goes through the file and
080 * finds the first valid codestream.
081 *
082 * @see jj2000.j2k.fileformat.writer.FileFormatWriter
083 * */
084public class FileFormatReader implements FileFormatBoxes{
085
086    /** The random access from which the file format boxes are read */
087    private RandomAccessIO in;
088
089    /** The positions of the codestreams in the fileformat*/
090    private Vector codeStreamPos;
091
092    /** The lengths of the codestreams in the fileformat*/
093    private Vector codeStreamLength;
094
095    /** Create a IndexColorModel from the palette box if there is one */
096    private ColorModel colorModel = null;
097
098    /** The meta data */
099    private J2KMetadata metadata;
100
101    /** Parameters in header box */
102    private int width;
103    private int height;
104    private int numComp;
105    private int bitDepth;
106    private int compressionType;
107    private int unknownColor;
108    private int intelProp;
109
110    /** Various bit depth */
111    private byte[] bitDepths;
112
113    /** The lut in the palette box */
114    private byte[][] lut;
115    private byte[] compSize;
116
117    /** Component mapping */
118    private short[] comps;
119    private byte[] type;
120    private byte[] maps;
121
122    /** Channel definitions */
123    private short[] channels ;
124    private short[] cType;
125    private short[] associations;
126
127    /** Color specification */
128    private int colorSpaceType;
129
130    /** ICC profile */
131    private ICC_Profile profile;
132
133    /**
134     * The constructor of the FileFormatReader
135     *
136     * @param in The RandomAccessIO from which to read the file format
137     * */
138    public FileFormatReader(RandomAccessIO in, J2KMetadata metadata){
139        this.in = in;
140        this.metadata = metadata;
141    }
142
143
144    /**
145     * This method checks whether the given RandomAccessIO is a valid JP2 file
146     * and if so finds the first codestream in the file. Currently, the
147     * information in the codestream is not used
148     *
149     * @param in The RandomAccessIO from which to read the file format
150     *
151     * @exception java.io.IOException If an I/O error ocurred.
152     *
153     * @exception java.io.EOFException If end of file is reached
154     * */
155    public void readFileFormat() throws IOException, EOFException {
156
157        int foundCodeStreamBoxes=0;
158        int box;
159        int length;
160        long longLength=0;
161        int pos;
162        short marker;
163        boolean jp2HeaderBoxFound=false;
164        boolean lastBoxFound = false;
165
166        try{
167
168            // Go through the randomaccessio and find the first
169            // contiguous codestream box. Check also that the File Format is
170            // correct
171
172            pos = in.getPos();
173
174            // Make sure that the first 12 bytes is the JP2_SIGNATURE_BOX
175            // or if not that the first 2 bytes is the SOC marker
176            if(in.readInt() != 0x0000000c ||
177               in.readInt() != JP2_SIGNATURE_BOX ||
178               in.readInt() != 0x0d0a870a){ // Not a JP2 file
179                in.seek(pos);
180
181                marker = (short)in.readShort();
182                if(marker != Markers.SOC) //Standard syntax marker found
183                    throw new Error("File is neither valid JP2 file nor "+
184                                    "valid JPEG 2000 codestream");
185                in.seek(pos);
186                if(codeStreamPos == null)
187                    codeStreamPos = new Vector();
188                codeStreamPos.addElement(new Integer(pos));
189                return;
190            }
191
192            if (metadata != null)
193                metadata.addNode(new SignatureBox());
194
195            // Read all remaining boxes
196            while(!lastBoxFound){
197                pos = in.getPos();
198                length = in.readInt();
199                if((pos+length) == in.length())
200                    lastBoxFound = true;
201
202                box = in.readInt();
203                if (length == 0) {
204                    lastBoxFound = true;
205                    length = in.length()-in.getPos();
206                } else if(length == 1) {
207                    longLength = in.readLong();
208                    throw new IOException("File too long.");
209                } else longLength = (long) 0;
210
211                pos = in.getPos();
212                length -= 8;
213
214                switch(box){
215                case FILE_TYPE_BOX:
216                    readFileTypeBox(length + 8, longLength);
217                    break;
218                case CONTIGUOUS_CODESTREAM_BOX:
219                    if(!jp2HeaderBoxFound)
220                        throw new Error("Invalid JP2 file: JP2Header box not "+
221                                        "found before Contiguous codestream "+
222                                        "box ");
223                    readContiguousCodeStreamBox(length + 8, longLength);
224                    break;
225                case JP2_HEADER_BOX:
226                    if(jp2HeaderBoxFound)
227                        throw new Error("Invalid JP2 file: Multiple "+
228                                        "JP2Header boxes found");
229                    readJP2HeaderBox(length + 8);
230                    jp2HeaderBoxFound = true;
231                    length = 0;
232                    break;
233                case IMAGE_HEADER_BOX:
234                    readImageHeaderBox(length);
235                    break;
236                case INTELLECTUAL_PROPERTY_BOX:
237                    readIntPropertyBox(length);
238                    break;
239                case XML_BOX:
240                    readXMLBox(length);
241                    break;
242                case UUID_INFO_BOX:
243                    length = 0;
244                    break;
245                case UUID_BOX:
246                    readUUIDBox(length);
247                    break;
248                case UUID_LIST_BOX:
249                    readUUIDListBox(length);
250                    break;
251                case URL_BOX:
252                    readURLBox(length);
253                    break;
254                case PALETTE_BOX:
255                    readPaletteBox(length + 8);
256                    break;
257                case BITS_PER_COMPONENT_BOX:
258                    readBitsPerComponentBox(length);
259                    break;
260                case COMPONENT_MAPPING_BOX:
261                    readComponentMappingBox(length);
262                    break;
263                case COLOUR_SPECIFICATION_BOX:
264                    readColourSpecificationBox(length);
265                    break;
266                case CHANNEL_DEFINITION_BOX:
267                    readChannelDefinitionBox(length);
268                    break;
269                case RESOLUTION_BOX:
270                    length = 0;
271                    break;
272                case CAPTURE_RESOLUTION_BOX:
273                case DEFAULT_DISPLAY_RESOLUTION_BOX:
274                    readResolutionBox(box, length);
275                    break;
276                default:
277                    if (metadata != null) {
278                        byte[] data = new byte[length];
279                        in.readFully(data, 0, length);
280                        metadata.addNode(new Box(length + 8,
281                                                 box,
282                                                 longLength,
283                                                 data));
284                    }
285                }
286                if(!lastBoxFound)
287                    in.seek(pos+length);
288            }
289        }catch( EOFException e ){
290            throw new Error("EOF reached before finding Contiguous "+
291                            "Codestream Box");
292        }
293
294        if(codeStreamPos.size() == 0){
295          // Not a valid JP2 file or codestream
296          throw new Error("Invalid JP2 file: Contiguous codestream box "+
297                          "missing");
298        }
299
300        return;
301    }
302
303    /**
304     * This method reads the File Type box
305     *
306     * @return false if the File Type box was not found or invalid else true
307     *
308     * @exception java.io.IOException If an I/O error ocurred.
309     *
310     * @exception java.io.EOFException If the end of file was reached
311     * */
312    public boolean readFileTypeBox(int length, long longLength)
313        throws IOException, EOFException {
314        int nComp;
315        boolean foundComp=false;
316
317        // Check for XLBox
318        if(length == 1) { // Box has 8 byte length;
319            longLength = in.readLong();
320            throw new IOException("File too long.");
321        }
322
323        // Check that this is a correct DBox value
324        // Read Brand field
325        if(in.readInt() != FT_BR)
326            return false;
327
328        // Read MinV field
329        int minorVersion = in.readInt();
330
331        // Check that there is at least one FT_BR entry in in
332        // compatibility list
333        nComp = (length - 16)/4; // Number of compatibilities.
334        int[] comp = new int[nComp];
335        for(int i=0; i < nComp; i++){
336            if((comp[i] = in.readInt()) == FT_BR)
337                foundComp = true;
338        }
339        if(!foundComp)
340            return false;
341
342        if (metadata != null)
343            metadata.addNode(new FileTypeBox(FT_BR, minorVersion, comp));
344
345        return true;
346    }
347
348    /**
349     * This method reads the JP2Header box
350     *
351     * @param pos The position in the file
352     *
353     * @param length The length of the JP2Header box
354     *
355     * @param long length The length of the JP2Header box if greater than
356     * 1<<32
357     *
358     * @return false if the JP2Header box was not found or invalid else true
359     *
360     * @exception java.io.IOException If an I/O error ocurred.
361     *
362     * @exception java.io.EOFException If the end of file was reached
363     * */
364    public boolean readJP2HeaderBox(int length)
365        throws IOException, EOFException {
366
367        if(length == 0) // This can not be last box
368            throw new Error("Zero-length of JP2Header Box");
369
370        // Here the JP2Header data (DBox) would be read if we were to use it
371        return true;
372    }
373
374   /**
375     * This method reads the Image Header box
376     * @param length The length of the JP2Header box
377     *
378     * @return false if the JP2Header box was not found or invalid else true
379     *
380     * @exception java.io.IOException If an I/O error ocurred.
381     *
382     * @exception java.io.EOFException If the end of file was reached
383     * */
384    public boolean readImageHeaderBox(int length)
385        throws IOException, EOFException {
386
387        if(length == 0) // This can not be last box
388            throw new Error("Zero-length of JP2Header Box");
389
390        // Here the JP2Header data (DBox) would be read if we were to use it
391
392        height = in.readInt();
393        width = in.readInt();
394        numComp = in.readShort();
395        bitDepth = in.readByte();
396
397        compressionType = in.readByte();
398        unknownColor = in.readByte();
399        intelProp = in.readByte();
400
401        if (metadata != null) {
402
403            metadata.addNode(new HeaderBox(height, width, numComp, bitDepth,
404                                           compressionType, unknownColor,
405                                           intelProp));
406        }
407        return true;
408    }
409
410     /**
411     * This method skips the Contiguous codestream box and adds position
412     * of contiguous codestream to a vector
413     *
414     * @param pos The position in the file
415     *
416     * @param length The length of the JP2Header box
417     *
418     * @param long length The length of the JP2Header box if greater than 1<<32
419     *
420     * @return false if the Contiguous codestream box was not found or invalid
421     * else true
422     *
423     * @exception java.io.IOException If an I/O error ocurred.
424     *
425     * @exception java.io.EOFException If the end of file was reached
426     * */
427    public boolean readContiguousCodeStreamBox(int length,
428                                               long longLength)
429        throws IOException, EOFException {
430
431        // Add new codestream position to position vector
432        int ccpos = in.getPos();
433
434        if(codeStreamPos == null)
435            codeStreamPos = new Vector();
436        codeStreamPos.addElement(new Integer(ccpos));
437
438        // Add new codestream length to length vector
439        if(codeStreamLength == null)
440            codeStreamLength = new Vector();
441        codeStreamLength.addElement(new Integer(length));
442
443        return true;
444    }
445
446    /**
447     * This method reads the contents of the Intellectual property box
448     * */
449    public void readIntPropertyBox(int length) throws IOException {
450        if (metadata != null) {
451            byte[] data = new byte[length];
452            in.readFully(data, 0, length);
453            metadata.addNode(new Box(length + 8, 0x6A703269, data));
454        }
455    }
456
457    /**
458     * This method reads the contents of the XML box
459     */
460    public void readXMLBox(int length) throws IOException {
461        if (metadata != null) {
462            byte[] data = new byte[length];
463            in.readFully(data, 0, length);
464            metadata.addNode(new XMLBox(data));
465        }
466    }
467
468    /**
469     * This method reads the contents of the XML box
470     */
471    public void readURLBox(int length) throws IOException {
472        if (metadata != null) {
473            byte[] data = new byte[length];
474            in.readFully(data, 0, length);
475            metadata.addNode(new DataEntryURLBox(data));
476        }
477    }
478
479    /**
480     * This method reads the contents of the Intellectual property box
481     */
482    public void readUUIDBox(int length) throws IOException {
483        if (metadata != null) {
484            byte[] data = new byte[length];
485            in.readFully(data, 0, length);
486            metadata.addNode(new UUIDBox(data));
487        }
488    }
489
490    /**
491     * This method reads the contents of the UUID List box
492     * */
493    public void readUUIDListBox(int length) throws IOException {
494        if (metadata != null) {
495            byte[] data = new byte[length];
496            in.readFully(data, 0, length);
497            metadata.addNode(new UUIDListBox(data));
498        }
499    }
500
501    /** This method reads the content of the palette box */
502    public void readPaletteBox(int length) throws IOException {
503        // Get current position in file
504        int pos = in.getPos();
505
506        int lutSize = in.readShort();
507        int numComp = in.readByte();
508        compSize = new byte[numComp];
509
510        for (int i = 0; i < numComp; i++) {
511            compSize[i] = (byte)in.readByte();
512        }
513
514        lut = new byte[numComp][lutSize];
515
516        for (int n=0; n < lutSize; n++) {
517            for (int c = 0; c < numComp; c++) {
518                int depth = 1 + (compSize[c] & 0x7F);
519                if (depth > 32)
520                    depth = 32;
521                int numBytes = (depth + 7)>>3;
522                int mask = (1 << depth) - 1;
523                byte[] buf = new byte[numBytes];
524                in.readFully(buf, 0, numBytes);
525
526                int val = 0;
527
528                for (int k = 0; k < numBytes; k++) {
529                    val = buf[k] + (val << 8);
530                }
531                lut[c][n] = (byte)val;
532            }
533        }
534        if (metadata != null) {
535            metadata.addNode(new PaletteBox(length, compSize, lut));
536        }
537    }
538
539    /** Read the component mapping channel.
540     */
541    public void readComponentMappingBox(int length)throws IOException {
542        int num = length / 4;
543
544        comps = new short[num];
545        type = new byte[num];
546        maps = new byte[num];
547
548        for (int i = 0; i < num; i++) {
549            comps[i] = in.readShort();
550            type[i] = in.readByte();
551            maps[i] = in.readByte();
552        }
553
554        if (metadata != null) {
555            metadata.addNode(new ComponentMappingBox(comps, type, maps));
556        }
557    }
558
559    /**
560     * This method reads the Channel Definition box
561     *
562     * @exception java.io.IOException If an I/O error ocurred.
563     *
564     */
565    public void readChannelDefinitionBox(int length)throws IOException {
566        int num = in.readShort();
567        channels = new short[num];
568        cType = new short[num];
569        associations = new short[num];
570
571        for (int i = 0; i < num; i++) {
572            channels[i] = in.readShort();
573            cType[i] = in.readShort();
574            associations[i] = in.readShort();
575        }
576        if (metadata != null) {
577            metadata.addNode(new ChannelDefinitionBox(channels, cType, associations));
578        }
579    }
580
581    /** Read the bits per component.
582     */
583    public void readBitsPerComponentBox(int length)throws IOException {
584        bitDepths = new byte[length];
585        in.readFully(bitDepths, 0, length);
586
587        if (metadata != null) {
588            metadata.addNode(new BitsPerComponentBox(bitDepths));
589        }
590    }
591
592    /** Read the color specifications.
593     */
594    public void readColourSpecificationBox(int length)throws IOException {
595        // read METHOD field
596        byte method = (byte)in.readByte();
597
598        // read PREC field
599        byte prec = (byte)in.readByte();
600
601        // read APPROX field
602        byte approx = (byte)in.readByte();
603
604        if (method == 2) {
605            byte[] data = new byte[length - 3];
606            in.readFully(data, 0, data.length);
607            profile = ICC_Profile.getInstance(data);
608        } else  // read EnumCS field
609            colorSpaceType = in.readInt();
610
611        if (metadata != null) {
612            metadata.addNode(new ColorSpecificationBox(method, prec, approx,
613                                                       colorSpaceType,
614                                                       profile));
615        }
616    }
617
618    /** Read the resolution.
619     */
620    public void readResolutionBox(int type, int length)throws IOException {
621        byte[] data = new byte[length];
622        in.readFully(data, 0, length);
623        if (metadata != null) {
624            metadata.addNode(new ResolutionBox(type, data));
625        }
626    }
627
628    /**
629     * This method creates and returns an array of positions to contiguous
630     * codestreams in the file
631     *
632     * @return The positions of the contiguous codestreams in the file
633     * */
634    public long[] getCodeStreamPos(){
635        int size = codeStreamPos.size();
636        long[] pos = new long[size];
637        for(int i=0 ; i<size ; i++)
638            pos[i]=((Integer)(codeStreamPos.elementAt(i))).longValue();
639        return pos;
640    }
641
642    /**
643     * This method returns the position of the first contiguous codestreams in
644     * the file
645     *
646     * @return The position of the first contiguous codestream in the file
647     * */
648    public int getFirstCodeStreamPos(){
649        return ((Integer)(codeStreamPos.elementAt(0))).intValue();
650    }
651
652    /**
653     * This method returns the length of the first contiguous codestreams in
654     * the file
655     *
656     * @return The length of the first contiguous codestream in the file
657     * */
658    public int getFirstCodeStreamLength(){
659        return ((Integer)(codeStreamLength.elementAt(0))).intValue();
660    }
661
662    /**
663     * Returns the color model created from the palette box.
664     */
665    public ColorModel getColorModel() {
666        // Check 'numComp' instance variable here in case there is an
667        // embedded palette such as in the pngsuite images pp0n2c16.png
668        // and pp0n6a08.png.
669        if (lut != null && numComp == 1) {
670            int numComp = lut.length;
671
672            int maxDepth = 1 + (bitDepth & 0x7F);
673
674            if (maps == null) {
675                maps = new byte[numComp];
676                for (int i = 0; i < numComp; i++)
677                    maps[i] = (byte)i;
678            }
679            if (numComp == 3)
680                colorModel = new IndexColorModel(maxDepth, lut[0].length,
681                                                 lut[maps[0]],
682                                                 lut[maps[1]],
683                                                 lut[maps[2]]);
684            else if (numComp == 4)
685                colorModel = new IndexColorModel(maxDepth, lut[0].length,
686                                                 lut[maps[0]],
687                                                 lut[maps[1]],
688                                                 lut[maps[2]],
689                                                 lut[maps[3]]);
690        } else if (channels != null){
691            boolean hasAlpha = false;
692            int alphaChannel = numComp - 1;
693
694            for (int i = 0; i < channels.length; i++) {
695                if (cType[i] == 1 && channels[i] == alphaChannel)
696                    hasAlpha = true;
697            }
698
699            boolean[] isPremultiplied = new boolean[] {false};
700
701            if (hasAlpha) {
702                isPremultiplied = new boolean[alphaChannel];
703
704                for (int i = 0; i < alphaChannel; i++)
705                    isPremultiplied[i] = false;
706
707                for (int i = 0; i < channels.length; i++) {
708                    if (cType[i] == 2)
709                        isPremultiplied[associations[i] - 1] = true;
710                }
711
712                for (int i = 1; i < alphaChannel; i++)
713                    isPremultiplied[0] &= isPremultiplied[i];
714            }
715
716            ColorSpace cs = null;
717            if (profile != null)
718                cs = new ICC_ColorSpace(profile);
719            else if (colorSpaceType == CSB_ENUM_SRGB)
720                cs = ColorSpace.getInstance(ColorSpace.CS_sRGB);
721            else if (colorSpaceType == CSB_ENUM_GREY)
722                cs = ColorSpace.getInstance(ColorSpace.CS_GRAY);
723            else if (colorSpaceType == CSB_ENUM_YCC)
724                cs = ColorSpace.getInstance(ColorSpace.CS_PYCC);
725
726            int[] bits = new int[numComp];
727            for (int i = 0; i < numComp; i++)
728                if (bitDepths != null)
729                    bits[i] = (bitDepths[i] & 0x7F) + 1;
730                else
731                    bits[i] = (bitDepth &0x7F) + 1;
732
733            int maxBitDepth = 1 + (bitDepth & 0x7F);
734            boolean isSigned = (bitDepth & 0x80) == 0x80;
735            if (bitDepths != null)
736                isSigned = (bitDepths[0]  & 0x80) == 0x80;
737
738            if (bitDepths != null)
739                for (int i = 0; i < numComp; i++)
740                    if (bits[i] > maxBitDepth)
741                        maxBitDepth = bits[i];
742
743            int type = -1;
744
745            if (maxBitDepth <= 8)
746                type = DataBuffer.TYPE_BYTE;
747            else if (maxBitDepth <= 16)
748                type = isSigned ? DataBuffer.TYPE_SHORT : DataBuffer.TYPE_USHORT;
749            else if (maxBitDepth <= 32)
750                type = DataBuffer.TYPE_INT;
751
752            if (type == -1)
753                return null;
754
755            if (cs != null) {
756                colorModel = new ComponentColorModel(cs,
757                                                 bits,
758                                                 hasAlpha,
759                                                 isPremultiplied[0],
760                                                 hasAlpha ? Transparency.TRANSLUCENT : Transparency.OPAQUE ,
761                                                 type);
762            }
763        }
764        return colorModel;
765    }
766}