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.Iterator;
050import java.util.List;
051
052import javax.imageio.metadata.IIOInvalidTreeException;
053import javax.imageio.metadata.IIOMetadataFormatImpl;
054import javax.imageio.metadata.IIOMetadataNode;
055
056import org.w3c.dom.Node;
057
058/**
059 * @version 0.5
060 */
061public class GIFImageMetadata extends GIFMetadata {
062
063    // package scope
064    static final String
065    nativeMetadataFormatName = "javax_imageio_gif_image_1.0";
066
067    static final String[] disposalMethodNames = {
068        "none",
069        "doNotDispose",
070        "restoreToBackgroundColor",
071        "restoreToPrevious",
072        "undefinedDisposalMethod4",
073        "undefinedDisposalMethod5",
074        "undefinedDisposalMethod6",
075        "undefinedDisposalMethod7"
076    };
077
078    // Fields from Image Descriptor
079    public int imageLeftPosition;
080    public int imageTopPosition;
081    public int imageWidth;
082    public int imageHeight;
083    public boolean interlaceFlag = false;
084    public boolean sortFlag = false;
085    public byte[] localColorTable = null;
086
087    // Fields from Graphic Control Extension
088    public int disposalMethod = 0;
089    public boolean userInputFlag = false;
090    public boolean transparentColorFlag = false;
091    public int delayTime = 0;
092    public int transparentColorIndex = 0;
093
094    // Fields from Plain Text Extension
095    public boolean hasPlainTextExtension = false;
096    public int textGridLeft;
097    public int textGridTop;
098    public int textGridWidth;
099    public int textGridHeight;
100    public int characterCellWidth;
101    public int characterCellHeight;
102    public int textForegroundColor;
103    public int textBackgroundColor;
104    public byte[] text;
105
106    // Fields from ApplicationExtension
107    // List of byte[]
108    public List applicationIDs = null; // new ArrayList();
109
110    // List of byte[]
111    public List authenticationCodes = null; // new ArrayList();
112
113    // List of byte[]
114    public List applicationData = null; // new ArrayList();
115
116    // Fields from CommentExtension
117    // List of byte[]
118    public List comments = null; // new ArrayList();
119
120    protected GIFImageMetadata(boolean standardMetadataFormatSupported,
121                               String nativeMetadataFormatName,
122                               String nativeMetadataFormatClassName,
123                               String[] extraMetadataFormatNames,
124                               String[] extraMetadataFormatClassNames)
125    {
126        super(standardMetadataFormatSupported,
127              nativeMetadataFormatName,
128              nativeMetadataFormatClassName,
129              extraMetadataFormatNames,
130              extraMetadataFormatClassNames);
131    }
132    
133    public GIFImageMetadata() {
134        this(true,
135             nativeMetadataFormatName,
136             "com.github.jaiimageio.impl.plugins.gif.GIFImageMetadataFormat",
137             null, null);
138    }
139    
140    public boolean isReadOnly() {
141        return true;
142    }
143
144    public Node getAsTree(String formatName) {
145        if (formatName.equals(nativeMetadataFormatName)) {
146            return getNativeTree();
147        } else if (formatName.equals
148                   (IIOMetadataFormatImpl.standardMetadataFormatName)) {
149            return getStandardTree();
150        } else {
151            throw new IllegalArgumentException("Not a recognized format!");
152        }
153    }
154
155    private String toISO8859(byte[] data) {
156        try {
157            return new String(data, "ISO-8859-1");
158        } catch (UnsupportedEncodingException e) {
159            return "";
160        }
161    }
162
163    private Node getNativeTree() {
164        IIOMetadataNode node; // scratch node
165        IIOMetadataNode root =
166            new IIOMetadataNode(nativeMetadataFormatName);
167
168        // Image descriptor
169        node = new IIOMetadataNode("ImageDescriptor");
170        node.setAttribute("imageLeftPosition",
171                          Integer.toString(imageLeftPosition));
172        node.setAttribute("imageTopPosition",
173                          Integer.toString(imageTopPosition));
174        node.setAttribute("imageWidth", Integer.toString(imageWidth));
175        node.setAttribute("imageHeight", Integer.toString(imageHeight));
176        node.setAttribute("interlaceFlag",
177                          interlaceFlag ? "true" : "false");
178        root.appendChild(node);
179
180        // Local color table
181        if (localColorTable != null) {
182            node = new IIOMetadataNode("LocalColorTable");
183            int numEntries = localColorTable.length/3;
184            node.setAttribute("sizeOfLocalColorTable",
185                              Integer.toString(numEntries));
186            node.setAttribute("sortFlag",
187                              sortFlag ? "TRUE" : "FALSE");
188            
189            for (int i = 0; i < numEntries; i++) {
190                IIOMetadataNode entry =
191                    new IIOMetadataNode("ColorTableEntry");
192                entry.setAttribute("index", Integer.toString(i));
193                int r = localColorTable[3*i] & 0xff;
194                int g = localColorTable[3*i + 1] & 0xff;
195                int b = localColorTable[3*i + 2] & 0xff;
196                entry.setAttribute("red", Integer.toString(r));
197                entry.setAttribute("green", Integer.toString(g));
198                entry.setAttribute("blue", Integer.toString(b));
199                node.appendChild(entry);
200            }
201            root.appendChild(node);
202        }
203
204        // Graphic control extension
205        node = new IIOMetadataNode("GraphicControlExtension");
206        node.setAttribute("disposalMethod",
207                          disposalMethodNames[disposalMethod]);
208        node.setAttribute("userInputFlag",
209                          userInputFlag ? "true" : "false");
210        node.setAttribute("transparentColorFlag",
211                          transparentColorFlag ? "true" : "false");
212        node.setAttribute("delayTime", 
213                          Integer.toString(delayTime));
214        node.setAttribute("transparentColorIndex",
215                          Integer.toString(transparentColorIndex));
216        root.appendChild(node);
217
218        if (hasPlainTextExtension) {
219            node = new IIOMetadataNode("PlainTextExtension");
220            node.setAttribute("textGridLeft",
221                              Integer.toString(textGridLeft));
222            node.setAttribute("textGridTop",
223                              Integer.toString(textGridTop));
224            node.setAttribute("textGridWidth",
225                              Integer.toString(textGridWidth));
226            node.setAttribute("textGridHeight",
227                              Integer.toString(textGridHeight));
228            node.setAttribute("characterCellWidth",
229                              Integer.toString(characterCellWidth));
230            node.setAttribute("characterCellHeight",
231                              Integer.toString(characterCellHeight));
232            node.setAttribute("textForegroundColor",
233                              Integer.toString(textForegroundColor));
234            node.setAttribute("textBackgroundColor",
235                              Integer.toString(textBackgroundColor));
236            node.setAttribute("text", toISO8859(text));
237
238            root.appendChild(node);
239        }
240
241        // Application extensions
242        int numAppExtensions = applicationIDs == null ?
243            0 : applicationIDs.size();
244        if (numAppExtensions > 0) {
245            node = new IIOMetadataNode("ApplicationExtensions");
246            for (int i = 0; i < numAppExtensions; i++) {
247                IIOMetadataNode appExtNode =
248                    new IIOMetadataNode("ApplicationExtension");
249                byte[] applicationID = (byte[])applicationIDs.get(i);
250                appExtNode.setAttribute("applicationID",
251                                        toISO8859(applicationID));
252                byte[] authenticationCode = (byte[])authenticationCodes.get(i);
253                appExtNode.setAttribute("authenticationCode",
254                                        toISO8859(authenticationCode));
255                byte[] appData = (byte[])applicationData.get(i);
256                appExtNode.setUserObject((byte[])appData.clone());
257                node.appendChild(appExtNode);
258            }
259
260            root.appendChild(node);
261        }
262
263        // Comment extensions
264        int numComments = comments == null ? 0 : comments.size();
265        if (numComments > 0) {
266            node = new IIOMetadataNode("CommentExtensions");
267            for (int i = 0; i < numComments; i++) {
268                IIOMetadataNode commentNode =
269                    new IIOMetadataNode("CommentExtension");
270                byte[] comment = (byte[])comments.get(i);
271                commentNode.setAttribute("value", toISO8859(comment));
272                node.appendChild(commentNode);
273            }
274
275            root.appendChild(node);
276        }
277
278        return root;
279    }
280
281    public IIOMetadataNode getStandardChromaNode() {
282        IIOMetadataNode chroma_node = new IIOMetadataNode("Chroma");
283        IIOMetadataNode node = null; // scratch node
284
285        node = new IIOMetadataNode("ColorSpaceType");
286        node.setAttribute("name", "RGB");
287        chroma_node.appendChild(node);
288
289        node = new IIOMetadataNode("NumChannels");
290        node.setAttribute("value", transparentColorFlag ? "4" : "3");
291        chroma_node.appendChild(node);
292
293        // Gamma not in format
294
295        node = new IIOMetadataNode("BlackIsZero");
296        node.setAttribute("value", "TRUE");
297        chroma_node.appendChild(node);
298
299        if (localColorTable != null) {
300            node = new IIOMetadataNode("Palette");
301            int numEntries = localColorTable.length/3;
302            for (int i = 0; i < numEntries; i++) {
303                IIOMetadataNode entry =
304                    new IIOMetadataNode("PaletteEntry");
305                entry.setAttribute("index", Integer.toString(i));
306                entry.setAttribute("red",
307                           Integer.toString(localColorTable[3*i] & 0xff));
308                entry.setAttribute("green",
309                           Integer.toString(localColorTable[3*i + 1] & 0xff));
310                entry.setAttribute("blue",
311                           Integer.toString(localColorTable[3*i + 2] & 0xff));
312                node.appendChild(entry);
313            }
314            chroma_node.appendChild(node);
315        }
316
317        // BackgroundIndex not in image
318        // BackgroundColor not in format
319
320        return chroma_node;
321    }
322
323    public IIOMetadataNode getStandardCompressionNode() {
324        IIOMetadataNode compression_node = new IIOMetadataNode("Compression");
325        IIOMetadataNode node = null; // scratch node
326
327        node = new IIOMetadataNode("CompressionTypeName");
328        node.setAttribute("value", "lzw");
329        compression_node.appendChild(node);
330
331        node = new IIOMetadataNode("Lossless");
332        node.setAttribute("value", "TRUE");
333        compression_node.appendChild(node);
334
335        node = new IIOMetadataNode("NumProgressiveScans");
336        node.setAttribute("value", interlaceFlag ? "4" : "1");
337        compression_node.appendChild(node);
338
339        // BitRate not in format
340
341        return compression_node;
342    }
343
344    public IIOMetadataNode getStandardDataNode() {
345        IIOMetadataNode data_node = new IIOMetadataNode("Data");
346        IIOMetadataNode node = null; // scratch node
347
348        // PlanarConfiguration not in format
349
350        node = new IIOMetadataNode("SampleFormat");
351        node.setAttribute("value", "Index");
352        data_node.appendChild(node);
353
354        // BitsPerSample not in image
355        // SignificantBitsPerSample not in format
356        // SampleMSB not in format
357        
358        return data_node;
359    }
360
361    public IIOMetadataNode getStandardDimensionNode() {
362        IIOMetadataNode dimension_node = new IIOMetadataNode("Dimension");
363        IIOMetadataNode node = null; // scratch node
364
365        // PixelAspectRatio not in image
366
367        node = new IIOMetadataNode("ImageOrientation");
368        node.setAttribute("value", "Normal");
369        dimension_node.appendChild(node);
370
371        // HorizontalPixelSize not in format
372        // VerticalPixelSize not in format
373        // HorizontalPhysicalPixelSpacing not in format
374        // VerticalPhysicalPixelSpacing not in format
375        // HorizontalPosition not in format
376        // VerticalPosition not in format
377
378        node = new IIOMetadataNode("HorizontalPixelOffset");
379        node.setAttribute("value", Integer.toString(imageLeftPosition));
380        dimension_node.appendChild(node);
381
382        node = new IIOMetadataNode("VerticalPixelOffset");
383        node.setAttribute("value", Integer.toString(imageTopPosition));
384        dimension_node.appendChild(node);
385
386        // HorizontalScreenSize not in image
387        // VerticalScreenSize not in image
388
389        return dimension_node;
390    }
391
392    // Document not in image
393
394    public IIOMetadataNode getStandardTextNode() {
395        if (comments == null) {
396            return null;
397        }
398        Iterator commentIter = comments.iterator();
399        if (!commentIter.hasNext()) {
400            return null;
401        }
402
403        IIOMetadataNode text_node = new IIOMetadataNode("Text");
404        IIOMetadataNode node = null; // scratch node
405        
406        while (commentIter.hasNext()) {
407            byte[] comment = (byte[])commentIter.next();
408            String s = null;
409            try {
410                s = new String(comment, "ISO-8859-1");
411            } catch (UnsupportedEncodingException e) {
412                throw new RuntimeException("Encoding ISO-8859-1 unknown!");
413            }
414
415            node = new IIOMetadataNode("TextEntry");
416            node.setAttribute("value", s);
417            node.setAttribute("encoding", "ISO-8859-1");
418            node.setAttribute("compression", "none");
419            text_node.appendChild(node);
420        }
421
422        return text_node;
423    }
424
425    public IIOMetadataNode getStandardTransparencyNode() {
426        if (!transparentColorFlag) {
427            return null;
428        }
429        
430        IIOMetadataNode transparency_node =
431            new IIOMetadataNode("Transparency");
432        IIOMetadataNode node = null; // scratch node
433
434        // Alpha not in format
435
436        node = new IIOMetadataNode("TransparentIndex");
437        node.setAttribute("value",
438                          Integer.toString(transparentColorIndex));
439        transparency_node.appendChild(node);
440
441        // TransparentColor not in format
442        // TileTransparencies not in format
443        // TileOpacities not in format
444
445        return transparency_node;
446    }
447
448    public void setFromTree(String formatName, Node root) 
449        throws IIOInvalidTreeException
450    {
451        throw new IllegalStateException("Metadata is read-only!");
452    }
453
454    protected void mergeNativeTree(Node root) throws IIOInvalidTreeException
455    {
456        throw new IllegalStateException("Metadata is read-only!");
457    }
458
459    protected void mergeStandardTree(Node root) throws IIOInvalidTreeException
460    {
461        throw new IllegalStateException("Metadata is read-only!");
462    }
463
464    public void reset() {
465        throw new IllegalStateException("Metadata is read-only!");
466    }
467}