001/*
002 * $RCSfile: GIFImageMetadata.java,v $
003 *
004 * 
005 * Copyright (c) 2005 Sun Microsystems, Inc. All  Rights Reserved.
006 * 
007 * Redistribution and use in source and binary forms, with or without
008 * modification, are permitted provided that the following conditions
009 * are met: 
010 * 
011 * - Redistribution of source code must retain the above copyright 
012 *   notice, this  list of conditions and the following disclaimer.
013 * 
014 * - Redistribution in binary form must reproduce the above copyright
015 *   notice, this list of conditions and the following disclaimer in 
016 *   the documentation and/or other materials provided with the
017 *   distribution.
018 * 
019 * Neither the name of Sun Microsystems, Inc. or the names of 
020 * contributors may be used to endorse or promote products derived 
021 * from this software without specific prior written permission.
022 * 
023 * This software is provided "AS IS," without a warranty of any 
024 * kind. ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND 
025 * WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, 
026 * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY
027 * EXCLUDED. SUN MIDROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL 
028 * NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF 
029 * USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS
030 * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR 
031 * ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL,
032 * CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND
033 * REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF OR
034 * INABILITY TO USE THIS SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE
035 * POSSIBILITY OF SUCH DAMAGES. 
036 * 
037 * You acknowledge that this software is not designed or intended for 
038 * use in the design, construction, operation or maintenance of any 
039 * nuclear facility. 
040 *
041 * $Revision: 1.1 $
042 * $Date: 2006/03/24 22:30:09 $
043 * $State: Exp $
044 */
045
046package com.github.jaiimageio.impl.plugins.gif;
047
048import java.io.UnsupportedEncodingException;
049import java.util.ArrayList;
050import java.util.Iterator;
051import java.util.List;
052import javax.imageio.ImageTypeSpecifier;
053import javax.imageio.metadata.IIOInvalidTreeException;
054import javax.imageio.metadata.IIOMetadata;
055import javax.imageio.metadata.IIOMetadataNode;
056import javax.imageio.metadata.IIOMetadataFormat;
057import javax.imageio.metadata.IIOMetadataFormatImpl;
058import org.w3c.dom.Node;
059
060/**
061 * @version 0.5
062 */
063public class GIFImageMetadata extends GIFMetadata {
064
065    // package scope
066    static final String
067    nativeMetadataFormatName = "javax_imageio_gif_image_1.0";
068
069    static final String[] disposalMethodNames = {
070        "none",
071        "doNotDispose",
072        "restoreToBackgroundColor",
073        "restoreToPrevious",
074        "undefinedDisposalMethod4",
075        "undefinedDisposalMethod5",
076        "undefinedDisposalMethod6",
077        "undefinedDisposalMethod7"
078    };
079
080    // Fields from Image Descriptor
081    public int imageLeftPosition;
082    public int imageTopPosition;
083    public int imageWidth;
084    public int imageHeight;
085    public boolean interlaceFlag = false;
086    public boolean sortFlag = false;
087    public byte[] localColorTable = null;
088
089    // Fields from Graphic Control Extension
090    public int disposalMethod = 0;
091    public boolean userInputFlag = false;
092    public boolean transparentColorFlag = false;
093    public int delayTime = 0;
094    public int transparentColorIndex = 0;
095
096    // Fields from Plain Text Extension
097    public boolean hasPlainTextExtension = false;
098    public int textGridLeft;
099    public int textGridTop;
100    public int textGridWidth;
101    public int textGridHeight;
102    public int characterCellWidth;
103    public int characterCellHeight;
104    public int textForegroundColor;
105    public int textBackgroundColor;
106    public byte[] text;
107
108    // Fields from ApplicationExtension
109    // List of byte[]
110    public List applicationIDs = null; // new ArrayList();
111
112    // List of byte[]
113    public List authenticationCodes = null; // new ArrayList();
114
115    // List of byte[]
116    public List applicationData = null; // new ArrayList();
117
118    // Fields from CommentExtension
119    // List of byte[]
120    public List comments = null; // new ArrayList();
121
122    protected GIFImageMetadata(boolean standardMetadataFormatSupported,
123                               String nativeMetadataFormatName,
124                               String nativeMetadataFormatClassName,
125                               String[] extraMetadataFormatNames,
126                               String[] extraMetadataFormatClassNames)
127    {
128        super(standardMetadataFormatSupported,
129              nativeMetadataFormatName,
130              nativeMetadataFormatClassName,
131              extraMetadataFormatNames,
132              extraMetadataFormatClassNames);
133    }
134    
135    public GIFImageMetadata() {
136        this(true,
137             nativeMetadataFormatName,
138             "com.github.jaiimageio.impl.plugins.gif.GIFImageMetadataFormat",
139             null, null);
140    }
141    
142    public boolean isReadOnly() {
143        return true;
144    }
145
146    public Node getAsTree(String formatName) {
147        if (formatName.equals(nativeMetadataFormatName)) {
148            return getNativeTree();
149        } else if (formatName.equals
150                   (IIOMetadataFormatImpl.standardMetadataFormatName)) {
151            return getStandardTree();
152        } else {
153            throw new IllegalArgumentException("Not a recognized format!");
154        }
155    }
156
157    private String toISO8859(byte[] data) {
158        try {
159            return new String(data, "ISO-8859-1");
160        } catch (UnsupportedEncodingException e) {
161            return "";
162        }
163    }
164
165    private Node getNativeTree() {
166        IIOMetadataNode node; // scratch node
167        IIOMetadataNode root =
168            new IIOMetadataNode(nativeMetadataFormatName);
169
170        // Image descriptor
171        node = new IIOMetadataNode("ImageDescriptor");
172        node.setAttribute("imageLeftPosition",
173                          Integer.toString(imageLeftPosition));
174        node.setAttribute("imageTopPosition",
175                          Integer.toString(imageTopPosition));
176        node.setAttribute("imageWidth", Integer.toString(imageWidth));
177        node.setAttribute("imageHeight", Integer.toString(imageHeight));
178        node.setAttribute("interlaceFlag",
179                          interlaceFlag ? "true" : "false");
180        root.appendChild(node);
181
182        // Local color table
183        if (localColorTable != null) {
184            node = new IIOMetadataNode("LocalColorTable");
185            int numEntries = localColorTable.length/3;
186            node.setAttribute("sizeOfLocalColorTable",
187                              Integer.toString(numEntries));
188            node.setAttribute("sortFlag",
189                              sortFlag ? "TRUE" : "FALSE");
190            
191            for (int i = 0; i < numEntries; i++) {
192                IIOMetadataNode entry =
193                    new IIOMetadataNode("ColorTableEntry");
194                entry.setAttribute("index", Integer.toString(i));
195                int r = localColorTable[3*i] & 0xff;
196                int g = localColorTable[3*i + 1] & 0xff;
197                int b = localColorTable[3*i + 2] & 0xff;
198                entry.setAttribute("red", Integer.toString(r));
199                entry.setAttribute("green", Integer.toString(g));
200                entry.setAttribute("blue", Integer.toString(b));
201                node.appendChild(entry);
202            }
203            root.appendChild(node);
204        }
205
206        // Graphic control extension
207        node = new IIOMetadataNode("GraphicControlExtension");
208        node.setAttribute("disposalMethod",
209                          disposalMethodNames[disposalMethod]);
210        node.setAttribute("userInputFlag",
211                          userInputFlag ? "true" : "false");
212        node.setAttribute("transparentColorFlag",
213                          transparentColorFlag ? "true" : "false");
214        node.setAttribute("delayTime", 
215                          Integer.toString(delayTime));
216        node.setAttribute("transparentColorIndex",
217                          Integer.toString(transparentColorIndex));
218        root.appendChild(node);
219
220        if (hasPlainTextExtension) {
221            node = new IIOMetadataNode("PlainTextExtension");
222            node.setAttribute("textGridLeft",
223                              Integer.toString(textGridLeft));
224            node.setAttribute("textGridTop",
225                              Integer.toString(textGridTop));
226            node.setAttribute("textGridWidth",
227                              Integer.toString(textGridWidth));
228            node.setAttribute("textGridHeight",
229                              Integer.toString(textGridHeight));
230            node.setAttribute("characterCellWidth",
231                              Integer.toString(characterCellWidth));
232            node.setAttribute("characterCellHeight",
233                              Integer.toString(characterCellHeight));
234            node.setAttribute("textForegroundColor",
235                              Integer.toString(textForegroundColor));
236            node.setAttribute("textBackgroundColor",
237                              Integer.toString(textBackgroundColor));
238            node.setAttribute("text", toISO8859(text));
239
240            root.appendChild(node);
241        }
242
243        // Application extensions
244        int numAppExtensions = applicationIDs == null ?
245            0 : applicationIDs.size();
246        if (numAppExtensions > 0) {
247            node = new IIOMetadataNode("ApplicationExtensions");
248            for (int i = 0; i < numAppExtensions; i++) {
249                IIOMetadataNode appExtNode =
250                    new IIOMetadataNode("ApplicationExtension");
251                byte[] applicationID = (byte[])applicationIDs.get(i);
252                appExtNode.setAttribute("applicationID",
253                                        toISO8859(applicationID));
254                byte[] authenticationCode = (byte[])authenticationCodes.get(i);
255                appExtNode.setAttribute("authenticationCode",
256                                        toISO8859(authenticationCode));
257                byte[] appData = (byte[])applicationData.get(i);
258                appExtNode.setUserObject((byte[])appData.clone());
259                node.appendChild(appExtNode);
260            }
261
262            root.appendChild(node);
263        }
264
265        // Comment extensions
266        int numComments = comments == null ? 0 : comments.size();
267        if (numComments > 0) {
268            node = new IIOMetadataNode("CommentExtensions");
269            for (int i = 0; i < numComments; i++) {
270                IIOMetadataNode commentNode =
271                    new IIOMetadataNode("CommentExtension");
272                byte[] comment = (byte[])comments.get(i);
273                commentNode.setAttribute("value", toISO8859(comment));
274                node.appendChild(commentNode);
275            }
276
277            root.appendChild(node);
278        }
279
280        return root;
281    }
282
283    public IIOMetadataNode getStandardChromaNode() {
284        IIOMetadataNode chroma_node = new IIOMetadataNode("Chroma");
285        IIOMetadataNode node = null; // scratch node
286
287        node = new IIOMetadataNode("ColorSpaceType");
288        node.setAttribute("name", "RGB");
289        chroma_node.appendChild(node);
290
291        node = new IIOMetadataNode("NumChannels");
292        node.setAttribute("value", transparentColorFlag ? "4" : "3");
293        chroma_node.appendChild(node);
294
295        // Gamma not in format
296
297        node = new IIOMetadataNode("BlackIsZero");
298        node.setAttribute("value", "TRUE");
299        chroma_node.appendChild(node);
300
301        if (localColorTable != null) {
302            node = new IIOMetadataNode("Palette");
303            int numEntries = localColorTable.length/3;
304            for (int i = 0; i < numEntries; i++) {
305                IIOMetadataNode entry =
306                    new IIOMetadataNode("PaletteEntry");
307                entry.setAttribute("index", Integer.toString(i));
308                entry.setAttribute("red",
309                           Integer.toString(localColorTable[3*i] & 0xff));
310                entry.setAttribute("green",
311                           Integer.toString(localColorTable[3*i + 1] & 0xff));
312                entry.setAttribute("blue",
313                           Integer.toString(localColorTable[3*i + 2] & 0xff));
314                node.appendChild(entry);
315            }
316            chroma_node.appendChild(node);
317        }
318
319        // BackgroundIndex not in image
320        // BackgroundColor not in format
321
322        return chroma_node;
323    }
324
325    public IIOMetadataNode getStandardCompressionNode() {
326        IIOMetadataNode compression_node = new IIOMetadataNode("Compression");
327        IIOMetadataNode node = null; // scratch node
328
329        node = new IIOMetadataNode("CompressionTypeName");
330        node.setAttribute("value", "lzw");
331        compression_node.appendChild(node);
332
333        node = new IIOMetadataNode("Lossless");
334        node.setAttribute("value", "TRUE");
335        compression_node.appendChild(node);
336
337        node = new IIOMetadataNode("NumProgressiveScans");
338        node.setAttribute("value", interlaceFlag ? "4" : "1");
339        compression_node.appendChild(node);
340
341        // BitRate not in format
342
343        return compression_node;
344    }
345
346    public IIOMetadataNode getStandardDataNode() {
347        IIOMetadataNode data_node = new IIOMetadataNode("Data");
348        IIOMetadataNode node = null; // scratch node
349
350        // PlanarConfiguration not in format
351
352        node = new IIOMetadataNode("SampleFormat");
353        node.setAttribute("value", "Index");
354        data_node.appendChild(node);
355
356        // BitsPerSample not in image
357        // SignificantBitsPerSample not in format
358        // SampleMSB not in format
359        
360        return data_node;
361    }
362
363    public IIOMetadataNode getStandardDimensionNode() {
364        IIOMetadataNode dimension_node = new IIOMetadataNode("Dimension");
365        IIOMetadataNode node = null; // scratch node
366
367        // PixelAspectRatio not in image
368
369        node = new IIOMetadataNode("ImageOrientation");
370        node.setAttribute("value", "Normal");
371        dimension_node.appendChild(node);
372
373        // HorizontalPixelSize not in format
374        // VerticalPixelSize not in format
375        // HorizontalPhysicalPixelSpacing not in format
376        // VerticalPhysicalPixelSpacing not in format
377        // HorizontalPosition not in format
378        // VerticalPosition not in format
379
380        node = new IIOMetadataNode("HorizontalPixelOffset");
381        node.setAttribute("value", Integer.toString(imageLeftPosition));
382        dimension_node.appendChild(node);
383
384        node = new IIOMetadataNode("VerticalPixelOffset");
385        node.setAttribute("value", Integer.toString(imageTopPosition));
386        dimension_node.appendChild(node);
387
388        // HorizontalScreenSize not in image
389        // VerticalScreenSize not in image
390
391        return dimension_node;
392    }
393
394    // Document not in image
395
396    public IIOMetadataNode getStandardTextNode() {
397        if (comments == null) {
398            return null;
399        }
400        Iterator commentIter = comments.iterator();
401        if (!commentIter.hasNext()) {
402            return null;
403        }
404
405        IIOMetadataNode text_node = new IIOMetadataNode("Text");
406        IIOMetadataNode node = null; // scratch node
407        
408        while (commentIter.hasNext()) {
409            byte[] comment = (byte[])commentIter.next();
410            String s = null;
411            try {
412                s = new String(comment, "ISO-8859-1");
413            } catch (UnsupportedEncodingException e) {
414                throw new RuntimeException("Encoding ISO-8859-1 unknown!");
415            }
416
417            node = new IIOMetadataNode("TextEntry");
418            node.setAttribute("value", s);
419            node.setAttribute("encoding", "ISO-8859-1");
420            node.setAttribute("compression", "none");
421            text_node.appendChild(node);
422        }
423
424        return text_node;
425    }
426
427    public IIOMetadataNode getStandardTransparencyNode() {
428        if (!transparentColorFlag) {
429            return null;
430        }
431        
432        IIOMetadataNode transparency_node =
433            new IIOMetadataNode("Transparency");
434        IIOMetadataNode node = null; // scratch node
435
436        // Alpha not in format
437
438        node = new IIOMetadataNode("TransparentIndex");
439        node.setAttribute("value",
440                          Integer.toString(transparentColorIndex));
441        transparency_node.appendChild(node);
442
443        // TransparentColor not in format
444        // TileTransparencies not in format
445        // TileOpacities not in format
446
447        return transparency_node;
448    }
449
450    public void setFromTree(String formatName, Node root) 
451        throws IIOInvalidTreeException
452    {
453        throw new IllegalStateException("Metadata is read-only!");
454    }
455
456    protected void mergeNativeTree(Node root) throws IIOInvalidTreeException
457    {
458        throw new IllegalStateException("Metadata is read-only!");
459    }
460
461    protected void mergeStandardTree(Node root) throws IIOInvalidTreeException
462    {
463        throw new IllegalStateException("Metadata is read-only!");
464    }
465
466    public void reset() {
467        throw new IllegalStateException("Metadata is read-only!");
468    }
469}