001    /*
002     * ImageCodec
003     * 
004     * Copyright (c) 2000, 2001, 2002, 2003 Marco Schmidt.
005     * All rights reserved.
006     */
007    
008    package net.sourceforge.jiu.codecs;
009    
010    import java.io.BufferedInputStream;
011    import java.io.BufferedOutputStream;
012    import java.io.DataInput;
013    import java.io.DataInputStream;
014    import java.io.DataOutput;
015    import java.io.DataOutputStream;
016    import java.io.File;
017    import java.io.FileInputStream;
018    import java.io.FileOutputStream;
019    import java.io.InputStream;
020    import java.io.IOException;
021    import java.io.OutputStream;
022    import java.io.RandomAccessFile;
023    import java.util.Vector;
024    import net.sourceforge.jiu.ops.MissingParameterException;
025    import net.sourceforge.jiu.ops.Operation;
026    import net.sourceforge.jiu.ops.WrongParameterException;
027    import net.sourceforge.jiu.data.PixelImage;
028    
029    /**
030     * The base class for <em>image codecs</em>, operations to read images from or write them to streams.
031     * A codec should support one file format only.
032     * The word codec is derived from <em>enCOder DECoder</em>.
033     * 
034     * <h3>Usage</h3>
035     * The codecs differ quite a bit in what they support.
036     * But here are two code snippets that demonstrate how to do loading and saving in general.
037     *
038     * <h4>Load image</h4>
039     * <pre>
040     * ImageCodec codec = new BMPCodec(); // BMPCodec is just an example
041     * codec.setFile("image.bmp", CodecMode.LOAD);
042     * codec.process();
043     * PixelImage image = codec.getImage();
044     * </pre>
045     *
046     * <h4>Save image</h4>
047     * <pre>
048     * PixelImage image = ...; // the image to be saved
049     * ImageCodec codec = new BMPCodec(); // BMPCodec is just an example
050     * codec.setFile("image.bmp", CodecMode.SAVE);
051     * codec.setImage(image);
052     * codec.process();
053     * </pre>
054     *
055     * <h3>I/O objects</h3>
056     * There are several set and get methods for I/O objects, including
057     * DataInput, DataOutput, InputStream, OutputStream and RandomAccessFile.
058     * If you are just using the codec (and not developing one) make it easier
059     * for yourself and use {@link #setFile(String, CodecMode)}.
060     * That way the picking of the right type of I/O class and the creation of a 
061     * buffered stream wrapper is done automatically.
062     * <p>
063     * Codecs have different requirements concerning I/O objects.
064     * If an image is to be loaded, it's enough for some formats to linearly read
065     * from an {@link java.io.InputStream} to load the image.
066     * However, some formats (like TIFF) require random access.
067     * <p>
068     * When implementing a codec, take care that as many I/O classes as possible can be used.
069     * If possible, call {@link #getInputAsDataInput} when loading and {@link #getOutputAsDataOutput}
070     * when saving.
071     * That way, input / output streams, RandomAccessFiles and arbitrary DataInput / DataOutput objects
072     * can be used.
073     * <p>
074     * <h3>Mode</h3>
075     * Codecs can be used to save images or load them, or both.
076     * As was g; by default, no mode (of enumeration type {@link CodecMode}) 
077     * is specified and {@link #getMode()} returns <code>null</code>.
078     * Mode only has two possible values, {@link CodecMode#LOAD} and
079     * {@link CodecMode#SAVE}.
080     * In some cases, the codec can find out whether to load or save from the I/O objects
081     * that were given to it; if it has an input stream, something must be loaded,
082     * if it has an output stream, something is to be saved.
083     * If a codec demands a {@link RandomAccessFile}, there is no way to find out
084     * the mode automatically, that is why {@link #setRandomAccessFile} also has an 
085     * argument of type {@link CodecMode}.
086     * <p>
087     * <strong>Bounds</strong>; to load or save only part of an image.
088     * Defining bounds is optional; by default, the complete image is loaded
089     * or saved (no bounds).
090     * Using {@link #setBounds(int, int, int, int)}, one can specify the 
091     * rectangle which will be loaded or saved.
092     * <p>
093     * <strong>PixelImage object</strong>; get and set methods for the image which is to be 
094     * loaded or saved.
095     * If an image is to be loaded, a PixelImage object can optionally be specified so that the image will
096     * be written to that object; image type and resolution must of course match the image
097     * from input.
098     * Normally, the codec will create the appropriate image object
099     * itself.
100     * If an image is to be saved, an image object <em>must</em> be provided, otherwise there
101     * is nothing to do.
102     * <p>
103     * <strong>Image index</strong>; the index of the image that is to be loaded (int value, default
104     * is 0). For image formats that support more than one image in one stream, the index of the
105     * image to be loaded (zero-based) can be specified using {@link #setImageIndex(int)}.
106     *
107     * <h3>Textual comments</h3>
108     * Some file formats allow for the inclusion of textual comments, to
109     * store a description, creator, copyright owner or anything else within the image
110     * file without actually drawing that text on the image itself.
111     * Some codecs support reading and writing of comments.
112     *
113     * <h3>Other methods</h3>
114     * <p>
115     * Each file format must be able to return its name ({@link #getFormatName()}) and
116     * file extensions that are typical for it ({@link #getFileExtensions()}).
117     * <p>
118     * A related method suggests a file extension for a given PixelImage object ({@link #suggestFileExtension(PixelImage)}).
119     * That method need not be implemented, the default version returns simply <code>null</code>.
120     * However, it is encouraged that codec implementors provide this method as well.
121     * Most file formats only have one typical extension (e. g. <code>.bmp</code>).
122     * However, for a file format like PNM, the extension depends on the image type (a grayscale
123     * image would end in <code>.pgm</code>, a color image in <code>.ppm</code>).
124     * <p>
125     * @author Marco Schmidt
126     */
127    public abstract class ImageCodec extends Operation
128    {
129            private int boundsX1;
130            private int boundsY1;
131            private int boundsX2;
132            private int boundsY2;
133            private boolean boundsAvail;
134            private int boundsWidth;
135            private int boundsHeight;
136            private Vector comments;
137            private int dpiX;
138            private int dpiY;
139            private DataInput din;
140            private DataOutput dout;
141            private PixelImage image;
142            private int imageIndex;
143            private InputStream in;
144            private CodecMode mode;
145            private OutputStream out;
146            private RandomAccessFile raf;
147    
148            /**
149             * This constructor will be called by descendants.
150             * The bounds state is initialized to <em>no bounds</em>.
151             */
152            public ImageCodec()
153            {
154                    super();
155                    comments = new Vector();
156                    removeBounds();
157            }
158    
159            /**
160             * Appends a comment to the internal list of comments.
161             * If the argument comment is non-null, it will be added to the internal
162             * list of comment strings.
163             * @param comment the comment to be added
164             */
165            public void appendComment(String comment)
166            {
167                    if (comment != null)
168                    {
169                            comments.addElement(comment);
170                    }
171            }
172    
173            /**
174             * If bounds were defined for this codec, this method tests if the 
175             * bounds rectangle fits into the rectangle <code>(0, 0) / (width - 1, height - 1)</code>.
176             * If the bounds are incorrect, a {@link WrongParameterException} 
177             * is thrown, otherwise nothing happens.
178             * To be used within codecs that support the bounds concept.
179             */
180            public void checkBounds(int width, int height) throws WrongParameterException
181            {
182                    if (!hasBounds())
183                    {
184                            return;
185                    }
186                    int x1 = getBoundsX1();
187                    if (x1 >= width)
188                    {
189                            throw new WrongParameterException("Codec bounds x1 (" + x1 +
190                                    ") must be smaller than image width (" + width + ").");
191                    }
192                    int x2 = getBoundsX2();
193                    if (x2 >= width)
194                    {
195                            throw new WrongParameterException("Codec bounds x2 (" + x2 +
196                                    ") must be smaller than image width (" + width + ").");
197                    }
198                    int y1 = getBoundsY1();
199                    if (y1 >= height)
200                    {
201                            throw new WrongParameterException("Codec bounds y1 (" + y1 +
202                                    ") must be smaller than image height (" + height + ").");
203                    }
204                    int y2 = getBoundsY2();
205                    if (y2 >= height)
206                    {
207                            throw new WrongParameterException("Codec bounds y2 (" + y2 +
208                                    ") must be smaller than image height (" + height + ").");
209                    }
210            }
211    
212            /**
213             * If an image object was provided to be used for loading via {@link #setImage},
214             * this method checks if its resolution is the same as the bounds' resolution.
215             * If the two differ, a {@link net.sourceforge.jiu.ops.WrongParameterException} is thrown.
216             * @throws WrongParameterException if image resolution and bounds dimension differ
217             */
218            public void checkImageResolution() throws WrongParameterException
219            {
220                    PixelImage image = getImage();
221                    if (image != null)
222                    {
223                            if (image.getWidth() != getBoundsWidth())
224                            {
225                                    throw new WrongParameterException("Specified input image must have width equal to getBoundsWidth().");
226                            }
227                            if (image.getHeight() != getBoundsHeight())
228                            {
229                                    throw new WrongParameterException("Specified input image must have height equal to getBoundsHeight().");
230                            }
231                    }
232            }
233    
234            /**
235             * Calls the close method of all input and output I/O objects
236             * that were given to this object.
237             * Catches and ignores any IOException objects that may be
238             * thrown in the process.
239             * Note that not all I/O objects have a close method (e.g. {@link java.io.DataInput}
240             * and {@link java.io.DataOutput} have not).
241             */
242            public void close()
243            {
244                    try
245                    {
246                            if (in != null)
247                            {
248                                    in.close();
249                            }
250                            if (out != null)
251                            {
252                                    out.close();
253                            }
254                            if (raf != null)
255                            {
256                                    raf.close();
257                            }
258                    }
259                    catch (IOException ioe)
260                    {
261                    }
262            }
263    
264            /**
265             * Returns x coordinate of the upper left corner of the bounds.
266             * Bounds must have been specified using {@link #setBounds(int, int, int, int)},
267             * otherwise the return value is undefined.
268             * @return x coordinate of the upper left corner of the bounds
269             */
270            public int getBoundsX1()
271            {
272                    return boundsX1;
273            }
274    
275            /**
276             * Returns x coordinate of the lower right corner of the bounds.
277             * Bounds must have been specified using {@link #setBounds(int, int, int, int)},
278             * otherwise the return value is undefined.
279             * @return x coordinate of the lower right corner of the bounds
280             */
281            public int getBoundsX2()
282            {
283                    return boundsX2;
284            }
285    
286            /**
287             * Returns y coordinate of the upper left corner of the bounds.
288             * Bounds must have been specified using {@link #setBounds(int, int, int, int)},
289             * otherwise the return value is undefined.
290             * @return y coordinate of the upper left corner of the bounds
291             */
292            public int getBoundsY1()
293            {
294                    return boundsY1;
295            }
296    
297            /**
298             * Returns y coordinate of the lower right corner of the bounds.
299             * Bounds must have been specified using {@link #setBounds(int, int, int, int)},
300             * otherwise the return value is undefined.
301             * @return y coordinate of the lower right corner of the bounds
302             */
303            public int getBoundsY2()
304            {
305                    return boundsY2;
306            }
307    
308            /**
309             * Returns the height of the rectangle specified by bounds.
310             * Bounds must have been specified using {@link #setBounds(int, int, int, int)},
311             * otherwise the return value is undefined.
312             * This equals {@link #getBoundsY2()} - {@link #getBoundsY1()} + 1.
313             * @return height of bounds rectangle
314             */
315            public int getBoundsHeight()
316            {
317                    return boundsHeight;
318            }
319    
320            /**
321             * Returns the width of the rectangle specified by bounds.
322             * Bounds must have been specified using {@link #setBounds(int, int, int, int)},
323             * otherwise the return value is undefined.
324             * This equals {@link #getBoundsX2()} - {@link #getBoundsX1()} + 1.
325             * @return width of bounds rectangle
326             */
327            public int getBoundsWidth()
328            {
329                    return boundsWidth;
330            }
331    
332            /**
333             * Returns a comment from the internal list of comments.
334             * @param index the index of the comment to be returned, must be from 
335             *  <code>0</code> to {@link #getNumComments()}<code> - 1</code>; if this is not
336             *  the case, <code>null</code> will be returned
337             * @see #getNumComments
338             * @see #appendComment
339             * @see #removeAllComments
340             */
341            public String getComment(int index)
342            {
343                    if (index >= 0 && index < comments.size())
344                    {
345                            return (String)comments.elementAt(index);
346                    }
347                    else
348                    {
349                            return null;
350                    }
351            }
352    
353            /**
354             * Returns a {@link java.io.DataInput} object if one was provided 
355             * via {@link #setDataInput(DataInput)} or <code>null</code> otherwise.
356             * @return the DataInput object
357             */
358            public DataInput getDataInput()
359            {
360                    return din;
361            }
362    
363            /**
364             * Returns a {@link java.io.DataOutput} object if one was provided 
365             * via {@link #setDataOutput(DataOutput)} or <code>null</code> otherwise.
366             * @return the DataInput object
367             */
368            public DataOutput getDataOutput()
369            {
370                    return dout;
371            }
372    
373            /**
374             * Returns the horizontal physical resolution of the image associated
375             * with this codec.
376             * This resolution value was either retrieved from an image file or 
377             * set via {@link #setDpi(int, int)}.
378             * @return horizontal physical resolution in dpi
379             * @see #getDpiY
380             */
381            public int getDpiX()
382            {
383                    return dpiX;
384            }
385    
386            /**
387             * Returns the vertical physical resolution of the image associated
388             * with this codec.
389             * This resolution value was either retrieved from an image file or 
390             * set via {@link #setDpi(int, int)}.
391             * @return horizontal physical resolution in dpi
392             * @see #getDpiX
393             */
394            public int getDpiY()
395            {
396                    return dpiY;
397            }
398    
399            /** 
400             * Returns all file extensions that are typical for this file format.
401             * The default implementation in ImageCodec returns <code>null</code>.
402             * The file extension strings should include a leading dot
403             * and are supposed to be lower case (if that is allowed for
404             * the given file format).
405             * Example: <code>{".jpg", ".jpeg"}</code> for the JPEG file format.
406             * @return String array with typical file extensions
407             */
408            public String[] getFileExtensions()
409            {
410                    return null;
411            }
412    
413            /**
414             * Returns the name of the file format supported by this codec.
415             * All classes extending {@link ImageCodec} must override this method.
416             * When overriding, leave out any words in a particular language so
417             * that this format name can be understood by everyone.
418             * Usually it is enough to return the format creator plus a typical 
419             * abbreviation, e.g. <code>Microsoft BMP</code> or <code>Portable Anymap (PNM)</code>.
420             * @return name of the file format supported by this codec
421             */
422            public abstract String getFormatName();
423    
424            /**
425             * Returns the image object stored in this codec.
426             * This is either an image given to this object via
427             * {@link #setImage(PixelImage)} or it was created by the codec
428             * itself during a loading operation.
429             * @return PixelImage object stored in this codec
430             */
431            public PixelImage getImage()
432            {
433                    return image;
434            }
435    
436            /**
437             * Returns the zero-based index of the image to be loaded.
438             * Default is zero.
439             * @return zero-based image index value
440             */
441            public int getImageIndex()
442            {
443                    return imageIndex;
444            }
445    
446            /**
447             * Returns a {@link java.io.DataInput} object if one was specified
448             * using {@link #setDataInput(DataInput)}, 
449             * or creates a {@link java.io.DataInputStream} if an 
450             * {@link java.io.InputStream} was specified,
451             * or returns a {@link java.io.RandomAccessFile} if one was specified
452             * (RandomAccessFile implements DataInput).
453             * If neither of those has been given to this object, <code>null</code> is returned.
454             * @return DataInput object or <code>null</code>
455             */
456            public DataInput getInputAsDataInput()
457            {
458                    DataInput din = getDataInput();
459                    if (din != null)
460                    {
461                            return din;
462                    }
463                    RandomAccessFile raf = getRandomAccessFile();
464                    if (getMode() == CodecMode.LOAD && raf != null)
465                    {
466                            return raf;
467                    }
468                    InputStream in = getInputStream();
469                    if (in != null)
470                    {
471                            if (in instanceof DataInput)
472                            {
473                                    return (DataInput)in;
474                            }
475                            else
476                            {
477                                    return new DataInputStream(in);
478                            }
479                    }
480                    return null;
481            }
482    
483            /**
484             * Returns an {@link java.io.InputStream} object that was given to 
485             * this codec via {@link #setInputStream(InputStream)} 
486             * (or <code>null</code> otherwise).
487             * @return InputStream object
488             */
489            public InputStream getInputStream()
490            {
491                    return in;
492            }
493    
494            /**
495             * Return the <a target="_top" href="http://www.faqs.org/rfcs/rfc2045.html">MIME</a> 
496             * (Multipurpose Internet Mail Extensions) type strings for this format, or <code>null</code>
497             * if none are available.
498             * @return MIME type strings or null
499             */
500            public abstract String[] getMimeTypes();
501    
502            /** 
503             * Returns the mode this codec is in.
504             * Can be <code>null</code>, so that the codec will have to find out
505             * itself what to do.
506             * @return codec mode (load or save)
507             */
508            public CodecMode getMode()
509            {
510                    return mode;
511            }
512    
513            /**
514             * Returns the current number of comments in the internal comment list.
515             * @return number of comments in the internal comment list
516             */
517            public int getNumComments()
518            {
519                    return comments.size();
520            }
521    
522            /**
523             * Attempts to return an output object as a {@link java.io.DataOutput} object.
524             * @return a DataOutput object or null if that was not possible
525             */
526            public DataOutput getOutputAsDataOutput()
527            {
528                    DataOutput dout = getDataOutput();
529                    if (dout != null)
530                    {
531                            return dout;
532                    }
533                    OutputStream out = getOutputStream();
534                    if (out != null)
535                    {
536                            if (out instanceof DataOutput)
537                            {
538                                    return (DataOutput)out;
539                            }
540                            else
541                            {
542                                    return new DataOutputStream(out);
543                            }
544                    }
545                    RandomAccessFile raf = getRandomAccessFile();
546                    if (raf != null && getMode() == CodecMode.SAVE)
547                    {
548                            return raf;
549                    }
550                    return null;
551            }
552    
553            /**
554             * Returns an {@link java.io.OutputStream} object that was given to 
555             * this codec via {@link #setOutputStream(OutputStream)} 
556             * (or <code>null</code> otherwise).
557             * @return OutputStream object
558             */
559            public OutputStream getOutputStream()
560            {
561                    return out;
562            }
563    
564            /**
565             * Returns a {@link java.io.RandomAccessFile} object that was given to 
566             * this codec via {@link #setRandomAccessFile(RandomAccessFile, CodecMode)} 
567             * (or <code>null</code> otherwise).
568             * @return RandomAccessFile object
569             */
570            public RandomAccessFile getRandomAccessFile()
571            {
572                    return raf;
573            }
574    
575            /**
576             * Returns if bounds have been specified.
577             * @return if bounds have been specified
578             * @see #removeBounds()
579             * @see #setBounds(int, int, int, int)
580             */
581            public boolean hasBounds()
582            {
583                    return boundsAvail;
584            }
585    
586            protected void initModeFromIOObjects() throws MissingParameterException
587            {
588                    if (getMode() != null)
589                    {
590                            return;
591                    }
592                    if (getInputStream() != null || getDataInput() != null)
593                    {
594                            mode = CodecMode.LOAD;
595                    }
596                    else
597                    if (getOutputStream() != null || getDataOutput() != null)
598                    {
599                            mode = CodecMode.SAVE;
600                    }
601                    else
602                    {
603                            throw new MissingParameterException("No streams or files available.");
604                    }
605            }
606    
607            /**
608             * Returns if this codec is able to load images in the file format supported by this codec.
609             * If <code>true</code> is returned this does not necessarily mean that all files in this
610             * format can be read, but at least some.
611             * @return if loading is supported
612             */
613            public abstract boolean isLoadingSupported();
614    
615            /**
616             * Returns if this codec is able to save images in the file format supported by this codec.
617             * If <code>true</code> is returned this does not necessarily mean that all types files in this
618             * format can be written, but at least some.
619             * @return if saving is supported
620             */
621            public abstract boolean isSavingSupported();
622    
623            private static boolean isPointInRectangle(int x, int y, int x1, int y1, int x2, int y2)
624            {
625                    return (x >= x1 && x <= x2 && y >= y1 && y <= y2);
626            }
627    
628            /**
629             * Returns if an image row given by its number (zero-based) must be loaded
630             * in the context of the current bounds.
631             * <p>
632             * Example: if vertical bounds have been set to 34 and 37, image rows 34 to
633             * 37 as arguments to this method would result in <code>true</code>, anything
634             * else (e.g. 12 or 45) would result in <code>false</code>.
635             *
636             * @param row the number of the row to be checked
637             * @return if row must be loaded, regarding the current bounds
638             */
639            public boolean isRowRequired(int row)
640            {
641                    if (hasBounds())
642                    {
643                            return (row >= boundsY1 && row <= boundsY2);
644                    }
645                    else
646                    {
647                            return (row >= 0 && row < getImage().getHeight());
648                    }
649            }
650    
651            /** 
652             * Returns if the tile formed by the argument coordinates 
653             * form a rectangle that overlaps with the bounds.
654             * If no bounds were defined, returns <code>true</code>.
655             * @param x1 
656             * @param y1 
657             * @param x2 
658             * @param y2 
659             * @return if the argument tile is required
660             */
661            public boolean isTileRequired(int x1, int y1, int x2, int y2)
662            {
663                    if (hasBounds())
664                    {
665                            //System.out.println("x1=" + x1 + " y1=" + y1 + " x2=" + x2 + " y2=" + y2);
666                            return !
667                                    (getBoundsY2() < y1 ||
668                                     getBoundsY1() > y2 ||
669                                     getBoundsX2() < x1 ||
670                                     getBoundsX1() > x2);
671                    }
672                    else
673                    {
674                            return true;
675                    }
676            }
677    
678            /**
679             * Removes all entries from the internal list of comments.
680             */
681            public void removeAllComments()
682            {
683                    comments.removeAllElements();
684            }
685    
686            /**
687             * If bounds were set using {@link #setBounds(int, int, int, int)}, these
688             * bounds are no longer regarded after the call to this method.
689             */
690            public void removeBounds()
691            {
692                    boundsAvail = false;
693            }
694    
695            /**
696             * Sets the bounds of a rectangular part of the image that
697             * is to be loaded or saved, instead of the complete image.
698             */
699            public void setBounds(int x1, int y1, int x2, int y2)
700            {
701                    if (x1 < 0 || y1 < 0 || x2 < x1 || y2 < y1)
702                    {
703                            throw new IllegalArgumentException("Not a valid bounds rectangle: " +
704                                    "x1=" + x1 + ", y1=" + y1 + ", x2=" + x2 + ", y2=" + y2);
705                    }
706                    boundsX1 = x1;
707                    boundsY1 = y1;
708                    boundsX2 = x2;
709                    boundsY2 = y2;
710                    boundsAvail = true;
711                    boundsWidth = x2 - x1 + 1;
712                    boundsHeight = y2 - y1 + 1;
713            }
714    
715            /**
716             * If no bounds have been set ({@link #hasBounds()} returns <code>false</code>),
717             * this method will set the bounds to <code>0, 0, width - 1, height - 1</code>.
718             * By calling this method somewhere in the codec, no distinction has to
719             * be made for the two cases <em>bounds have been defined</em> and 
720             * <em>bounds have not been defined</em>.
721             * @param width width of the image to be loaded or saved
722             * @param height height of the image to be loaded or saved
723             */
724            public void setBoundsIfNecessary(int width, int height)
725            {
726                    if (!hasBounds())
727                    {
728                            setBounds(0, 0, width - 1, height - 1);
729                    }
730            }
731    
732            /**
733             * Specifies a DataInput object to be used for loading.
734             * @param dataInput DataInput object to be used for loading an image
735             */
736            public void setDataInput(DataInput dataInput)
737            {
738                    din = dataInput;
739            }
740    
741            /**
742             * Sets a {@link java.io.DataOutput} object to be used for saving
743             * an image.
744             * @param dataOutput the object to be used for output
745             */
746            public void setDataOutput(DataOutput dataOutput)
747            {
748                    dout = dataOutput;
749            }
750    
751            /**
752             * Sets the DPI values to be stored in the file to the argument values.
753             * @param horizontalDpi horizontal physical resolution in DPI (dots per inch)
754             * @param verticalDpi vertical physical resolution in DPI (dots per inch)
755             * @see #getDpiX
756             * @see #getDpiY
757             */
758            public void setDpi(int horizontalDpi, int verticalDpi)
759            {
760                    dpiX = horizontalDpi;
761                    dpiY = verticalDpi;
762            }
763    
764            /**
765             * Gives a File object and a codec mode to this codec and attempts
766             * to initialize the appropriate I/O objects.
767             * Simply calls {@link #setFile(String, CodecMode)} with the absolute
768             * path of the File object.
769             * @param file File object for the file to be used
770             * @param codecMode defines whether an image is to be loaded from or saved to the file
771             */
772            public void setFile(File file, CodecMode codecMode) throws 
773                    IOException, 
774                    UnsupportedCodecModeException
775            {
776                    setFile(file.getAbsolutePath(), codecMode);
777            }
778    
779            /**
780             * Gives a file name and codec mode to the codec which will then
781             * try to create the corresponding I/O object.
782             * The default implementation in ImageCodec creates a DataInputStream object
783             * wrapped around a BufferedInputStream wrapped around a FileInputStream for
784             * CodecMode.LOAD.
785             * Similar for CodecMode.SAVE: a DataOutputStream around a BufferedOutputStream
786             * object around a FileOutputStream object.
787             * Codecs that need different I/O objects must override this method
788             * (some codecs may need random access and thus require a RandomAccessFile object).
789             * @param fileName name of the file to be used for loading or saving
790             * @param codecMode defines whether file is to be used for loading or saving
791             */
792            public void setFile(String fileName, CodecMode codecMode) throws 
793                    IOException, 
794                    UnsupportedCodecModeException
795            {
796                    if (codecMode == CodecMode.LOAD)
797                    {
798                            if (isLoadingSupported())
799                            {
800                                    setInputStream(new BufferedInputStream(new FileInputStream(fileName)));
801                            }
802                            else
803                            {
804                                    throw new UnsupportedCodecModeException("Loading is not supported for this codec (" + getFormatName() + ").");
805                            }
806                    }
807                    else
808                    {
809                            if (isSavingSupported())
810                            {
811                                    setOutputStream(new BufferedOutputStream(new FileOutputStream(fileName)));
812                            }
813                            else
814                            {
815                                    throw new UnsupportedCodecModeException("Saving is not supported for this codec (" + getFormatName() + ").");
816                            }
817                    }
818            }
819    
820            /**
821             * Give an image to this codec to be used for loading an image into it
822             * or saving the image.
823             * @param img image object to save or to load data into
824             */
825            public void setImage(PixelImage img)
826            {
827                    image = img;
828            }
829    
830            /**
831             * Sets the index of the image to be loaded to the argument value
832             * (which must be zero or larger).
833             * @param index int index value (zero-based) of the image to be loaded
834             * @throws IllegalArgumentException if the argument is negative
835             */
836            public void setImageIndex(int index)
837            {
838                    if (index < 0)
839                    {
840                            throw new IllegalArgumentException("The index must be 0 or larger.");
841                    }
842                    imageIndex = index;
843            }
844    
845            /**
846             * An {@link java.io.InputStream} can be given to this codec using this method.
847             * @param inputStream InputStream object to read from
848             */
849            public void setInputStream(InputStream inputStream)
850            {
851                    in = inputStream;
852            }
853    
854            /**
855             * A method to give an {@link java.io.OutputStream} to this codec to be used 
856             * for saving an image.
857             * @param outputStream the output stream to be used by this codec
858             */
859            public void setOutputStream(OutputStream outputStream)
860            {
861                    out = outputStream;
862            }
863    
864            /**
865             * A method to give a {@link java.io.RandomAccessFile} to this codec to be used 
866             * for loading or saving an image.
867             * It is not possible to determine from a RandomAccessFile object whether it
868             * was opened in read-only or read-and-write mode.
869             * To let the codec know whether the object is to be used for loading or saving
870             * the second argument is of type CodecMode.
871             * @param randomAccessFile the file to be used for loading or saving
872             * @param codecMode tells the codec whether the file is to be used for loading or saving
873             */
874            public void setRandomAccessFile(RandomAccessFile randomAccessFile, CodecMode codecMode)
875            {
876                    if (randomAccessFile == null)
877                    {
878                            throw new IllegalArgumentException("Argument RandomAccessFile must be non-null.");
879                    }
880                    if (codecMode == null)
881                    {
882                            throw new IllegalArgumentException("Argument codec mode must be non-null.");
883                    }
884                    raf = randomAccessFile;
885                    mode = codecMode;
886            }
887    
888            /**
889             * Attempts to suggest a filename extension.
890             * The type of the argument image will be taken into consideration,
891             * although this will be necessary for some file formats only (as an
892             * example, PNM has different extensions for different image types, see
893             * {@link PNMCodec}). 
894             * This default implementation always returns <code>null</code>.
895             * @param image the image that is to be written to a file
896             * @return the file extension, including a leading dot, or <code>null</code> if no file extension can be recommended
897             */
898            public String suggestFileExtension(PixelImage image)
899            {
900                    return null;
901            }
902    }