1 /*
2  * Copyright (c) 2000, 2005, Oracle and/or its affiliates. All rights reserved.
3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4  *
5  * This code is free software; you can redistribute it and/or modify it
6  * under the terms of the GNU General Public License version 2 only, as
7  * published by the Free Software Foundation.  Oracle designates this
8  * particular file as subject to the "Classpath" exception as provided
9  * by Oracle in the LICENSE file that accompanied this code.
10  *
11  * This code is distributed in the hope that it will be useful, but WITHOUT
12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14  * version 2 for more details (a copy is included in the LICENSE file that
15  * accompanied this code).
16  *
17  * You should have received a copy of the GNU General Public License version
18  * 2 along with this work; if not, write to the Free Software Foundation,
19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20  *
21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22  * or visit www.oracle.com if you need additional information or have any
23  * questions.
24  */
25 
26 package com.sun.imageio.plugins.gif;
27 
28 import java.io.UnsupportedEncodingException;
29 import java.util.ArrayList;
30 import java.util.Iterator;
31 import java.util.List;
32 import javax.imageio.ImageTypeSpecifier;
33 import javax.imageio.metadata.IIOInvalidTreeException;
34 import javax.imageio.metadata.IIOMetadata;
35 import javax.imageio.metadata.IIOMetadataNode;
36 import javax.imageio.metadata.IIOMetadataFormat;
37 import javax.imageio.metadata.IIOMetadataFormatImpl;
38 import org.w3c.dom.Node;
39 
40 public class GIFImageMetadata extends GIFMetadata {
41 
42     // package scope
43     static final String
44         nativeMetadataFormatName = "javax_imageio_gif_image_1.0";
45 
46     static final String[] disposalMethodNames = {
47         "none",
48         "doNotDispose",
49         "restoreToBackgroundColor",
50         "restoreToPrevious",
51         "undefinedDisposalMethod4",
52         "undefinedDisposalMethod5",
53         "undefinedDisposalMethod6",
54         "undefinedDisposalMethod7"
55     };
56 
57     // Fields from Image Descriptor
58     public int imageLeftPosition;
59     public int imageTopPosition;
60     public int imageWidth;
61     public int imageHeight;
62     public boolean interlaceFlag = false;
63     public boolean sortFlag = false;
64     public byte[] localColorTable = null;
65 
66     // Fields from Graphic Control Extension
67     public int disposalMethod = 0;
68     public boolean userInputFlag = false;
69     public boolean transparentColorFlag = false;
70     public int delayTime = 0;
71     public int transparentColorIndex = 0;
72 
73     // Fields from Plain Text Extension
74     public boolean hasPlainTextExtension = false;
75     public int textGridLeft;
76     public int textGridTop;
77     public int textGridWidth;
78     public int textGridHeight;
79     public int characterCellWidth;
80     public int characterCellHeight;
81     public int textForegroundColor;
82     public int textBackgroundColor;
83     public byte[] text;
84 
85     // Fields from ApplicationExtension
86     // List of byte[]
87     public List applicationIDs = null; // new ArrayList();
88 
89     // List of byte[]
90     public List authenticationCodes = null; // new ArrayList();
91 
92     // List of byte[]
93     public List applicationData = null; // new ArrayList();
94 
95     // Fields from CommentExtension
96     // List of byte[]
97     public List comments = null; // new ArrayList();
98 
GIFImageMetadata(boolean standardMetadataFormatSupported, String nativeMetadataFormatName, String nativeMetadataFormatClassName, String[] extraMetadataFormatNames, String[] extraMetadataFormatClassNames)99     protected GIFImageMetadata(boolean standardMetadataFormatSupported,
100                                String nativeMetadataFormatName,
101                                String nativeMetadataFormatClassName,
102                                String[] extraMetadataFormatNames,
103                                String[] extraMetadataFormatClassNames)
104     {
105         super(standardMetadataFormatSupported,
106               nativeMetadataFormatName,
107               nativeMetadataFormatClassName,
108               extraMetadataFormatNames,
109               extraMetadataFormatClassNames);
110     }
111 
GIFImageMetadata()112     public GIFImageMetadata() {
113         this(true,
114               nativeMetadataFormatName,
115               "com.sun.imageio.plugins.gif.GIFImageMetadataFormat",
116               null, null);
117     }
118 
isReadOnly()119     public boolean isReadOnly() {
120         return true;
121     }
122 
getAsTree(String formatName)123     public Node getAsTree(String formatName) {
124         if (formatName.equals(nativeMetadataFormatName)) {
125             return getNativeTree();
126         } else if (formatName.equals
127                    (IIOMetadataFormatImpl.standardMetadataFormatName)) {
128             return getStandardTree();
129         } else {
130             throw new IllegalArgumentException("Not a recognized format!");
131         }
132     }
133 
toISO8859(byte[] data)134     private String toISO8859(byte[] data) {
135         try {
136             return new String(data, "ISO-8859-1");
137         } catch (UnsupportedEncodingException e) {
138             return "";
139         }
140     }
141 
getNativeTree()142     private Node getNativeTree() {
143         IIOMetadataNode node; // scratch node
144         IIOMetadataNode root =
145             new IIOMetadataNode(nativeMetadataFormatName);
146 
147         // Image descriptor
148         node = new IIOMetadataNode("ImageDescriptor");
149         node.setAttribute("imageLeftPosition",
150                           Integer.toString(imageLeftPosition));
151         node.setAttribute("imageTopPosition",
152                           Integer.toString(imageTopPosition));
153         node.setAttribute("imageWidth", Integer.toString(imageWidth));
154         node.setAttribute("imageHeight", Integer.toString(imageHeight));
155         node.setAttribute("interlaceFlag",
156                           interlaceFlag ? "TRUE" : "FALSE");
157         root.appendChild(node);
158 
159         // Local color table
160         if (localColorTable != null) {
161             node = new IIOMetadataNode("LocalColorTable");
162             int numEntries = localColorTable.length/3;
163             node.setAttribute("sizeOfLocalColorTable",
164                               Integer.toString(numEntries));
165             node.setAttribute("sortFlag",
166                               sortFlag ? "TRUE" : "FALSE");
167 
168             for (int i = 0; i < numEntries; i++) {
169                 IIOMetadataNode entry =
170                     new IIOMetadataNode("ColorTableEntry");
171                 entry.setAttribute("index", Integer.toString(i));
172                 int r = localColorTable[3*i] & 0xff;
173                 int g = localColorTable[3*i + 1] & 0xff;
174                 int b = localColorTable[3*i + 2] & 0xff;
175                 entry.setAttribute("red", Integer.toString(r));
176                 entry.setAttribute("green", Integer.toString(g));
177                 entry.setAttribute("blue", Integer.toString(b));
178                 node.appendChild(entry);
179             }
180             root.appendChild(node);
181         }
182 
183         // Graphic control extension
184         node = new IIOMetadataNode("GraphicControlExtension");
185         node.setAttribute("disposalMethod",
186                           disposalMethodNames[disposalMethod]);
187         node.setAttribute("userInputFlag",
188                           userInputFlag ? "TRUE" : "FALSE");
189         node.setAttribute("transparentColorFlag",
190                           transparentColorFlag ? "TRUE" : "FALSE");
191         node.setAttribute("delayTime",
192                           Integer.toString(delayTime));
193         node.setAttribute("transparentColorIndex",
194                           Integer.toString(transparentColorIndex));
195         root.appendChild(node);
196 
197         if (hasPlainTextExtension) {
198             node = new IIOMetadataNode("PlainTextExtension");
199             node.setAttribute("textGridLeft",
200                               Integer.toString(textGridLeft));
201             node.setAttribute("textGridTop",
202                               Integer.toString(textGridTop));
203             node.setAttribute("textGridWidth",
204                               Integer.toString(textGridWidth));
205             node.setAttribute("textGridHeight",
206                               Integer.toString(textGridHeight));
207             node.setAttribute("characterCellWidth",
208                               Integer.toString(characterCellWidth));
209             node.setAttribute("characterCellHeight",
210                               Integer.toString(characterCellHeight));
211             node.setAttribute("textForegroundColor",
212                               Integer.toString(textForegroundColor));
213             node.setAttribute("textBackgroundColor",
214                               Integer.toString(textBackgroundColor));
215             node.setAttribute("text", toISO8859(text));
216 
217             root.appendChild(node);
218         }
219 
220         // Application extensions
221         int numAppExtensions = applicationIDs == null ?
222             0 : applicationIDs.size();
223         if (numAppExtensions > 0) {
224             node = new IIOMetadataNode("ApplicationExtensions");
225             for (int i = 0; i < numAppExtensions; i++) {
226                 IIOMetadataNode appExtNode =
227                     new IIOMetadataNode("ApplicationExtension");
228                 byte[] applicationID = (byte[])applicationIDs.get(i);
229                 appExtNode.setAttribute("applicationID",
230                                         toISO8859(applicationID));
231                 byte[] authenticationCode = (byte[])authenticationCodes.get(i);
232                 appExtNode.setAttribute("authenticationCode",
233                                         toISO8859(authenticationCode));
234                 byte[] appData = (byte[])applicationData.get(i);
235                 appExtNode.setUserObject((byte[])appData.clone());
236                 node.appendChild(appExtNode);
237             }
238 
239             root.appendChild(node);
240         }
241 
242         // Comment extensions
243         int numComments = comments == null ? 0 : comments.size();
244         if (numComments > 0) {
245             node = new IIOMetadataNode("CommentExtensions");
246             for (int i = 0; i < numComments; i++) {
247                 IIOMetadataNode commentNode =
248                     new IIOMetadataNode("CommentExtension");
249                 byte[] comment = (byte[])comments.get(i);
250                 commentNode.setAttribute("value", toISO8859(comment));
251                 node.appendChild(commentNode);
252             }
253 
254             root.appendChild(node);
255         }
256 
257         return root;
258     }
259 
getStandardChromaNode()260     public IIOMetadataNode getStandardChromaNode() {
261         IIOMetadataNode chroma_node = new IIOMetadataNode("Chroma");
262         IIOMetadataNode node = null; // scratch node
263 
264         node = new IIOMetadataNode("ColorSpaceType");
265         node.setAttribute("name", "RGB");
266         chroma_node.appendChild(node);
267 
268         node = new IIOMetadataNode("NumChannels");
269         node.setAttribute("value", transparentColorFlag ? "4" : "3");
270         chroma_node.appendChild(node);
271 
272         // Gamma not in format
273 
274         node = new IIOMetadataNode("BlackIsZero");
275         node.setAttribute("value", "TRUE");
276         chroma_node.appendChild(node);
277 
278         if (localColorTable != null) {
279             node = new IIOMetadataNode("Palette");
280             int numEntries = localColorTable.length/3;
281             for (int i = 0; i < numEntries; i++) {
282                 IIOMetadataNode entry =
283                     new IIOMetadataNode("PaletteEntry");
284                 entry.setAttribute("index", Integer.toString(i));
285                 entry.setAttribute("red",
286                            Integer.toString(localColorTable[3*i] & 0xff));
287                 entry.setAttribute("green",
288                            Integer.toString(localColorTable[3*i + 1] & 0xff));
289                 entry.setAttribute("blue",
290                            Integer.toString(localColorTable[3*i + 2] & 0xff));
291                 node.appendChild(entry);
292             }
293             chroma_node.appendChild(node);
294         }
295 
296         // BackgroundIndex not in image
297         // BackgroundColor not in format
298 
299         return chroma_node;
300     }
301 
getStandardCompressionNode()302     public IIOMetadataNode getStandardCompressionNode() {
303         IIOMetadataNode compression_node = new IIOMetadataNode("Compression");
304         IIOMetadataNode node = null; // scratch node
305 
306         node = new IIOMetadataNode("CompressionTypeName");
307         node.setAttribute("value", "lzw");
308         compression_node.appendChild(node);
309 
310         node = new IIOMetadataNode("Lossless");
311         node.setAttribute("value", "TRUE");
312         compression_node.appendChild(node);
313 
314         node = new IIOMetadataNode("NumProgressiveScans");
315         node.setAttribute("value", interlaceFlag ? "4" : "1");
316         compression_node.appendChild(node);
317 
318         // BitRate not in format
319 
320         return compression_node;
321     }
322 
getStandardDataNode()323     public IIOMetadataNode getStandardDataNode() {
324         IIOMetadataNode data_node = new IIOMetadataNode("Data");
325         IIOMetadataNode node = null; // scratch node
326 
327         // PlanarConfiguration not in format
328 
329         node = new IIOMetadataNode("SampleFormat");
330         node.setAttribute("value", "Index");
331         data_node.appendChild(node);
332 
333         // BitsPerSample not in image
334         // SignificantBitsPerSample not in format
335         // SampleMSB not in format
336 
337         return data_node;
338     }
339 
getStandardDimensionNode()340     public IIOMetadataNode getStandardDimensionNode() {
341         IIOMetadataNode dimension_node = new IIOMetadataNode("Dimension");
342         IIOMetadataNode node = null; // scratch node
343 
344         // PixelAspectRatio not in image
345 
346         node = new IIOMetadataNode("ImageOrientation");
347         node.setAttribute("value", "Normal");
348         dimension_node.appendChild(node);
349 
350         // HorizontalPixelSize not in format
351         // VerticalPixelSize not in format
352         // HorizontalPhysicalPixelSpacing not in format
353         // VerticalPhysicalPixelSpacing not in format
354         // HorizontalPosition not in format
355         // VerticalPosition not in format
356 
357         node = new IIOMetadataNode("HorizontalPixelOffset");
358         node.setAttribute("value", Integer.toString(imageLeftPosition));
359         dimension_node.appendChild(node);
360 
361         node = new IIOMetadataNode("VerticalPixelOffset");
362         node.setAttribute("value", Integer.toString(imageTopPosition));
363         dimension_node.appendChild(node);
364 
365         // HorizontalScreenSize not in image
366         // VerticalScreenSize not in image
367 
368         return dimension_node;
369     }
370 
371     // Document not in image
372 
getStandardTextNode()373     public IIOMetadataNode getStandardTextNode() {
374         if (comments == null) {
375             return null;
376         }
377         Iterator commentIter = comments.iterator();
378         if (!commentIter.hasNext()) {
379             return null;
380         }
381 
382         IIOMetadataNode text_node = new IIOMetadataNode("Text");
383         IIOMetadataNode node = null; // scratch node
384 
385         while (commentIter.hasNext()) {
386             byte[] comment = (byte[])commentIter.next();
387             String s = null;
388             try {
389                 s = new String(comment, "ISO-8859-1");
390             } catch (UnsupportedEncodingException e) {
391                 throw new RuntimeException("Encoding ISO-8859-1 unknown!");
392             }
393 
394             node = new IIOMetadataNode("TextEntry");
395             node.setAttribute("value", s);
396             node.setAttribute("encoding", "ISO-8859-1");
397             node.setAttribute("compression", "none");
398             text_node.appendChild(node);
399         }
400 
401         return text_node;
402     }
403 
getStandardTransparencyNode()404     public IIOMetadataNode getStandardTransparencyNode() {
405         if (!transparentColorFlag) {
406             return null;
407         }
408 
409         IIOMetadataNode transparency_node =
410             new IIOMetadataNode("Transparency");
411         IIOMetadataNode node = null; // scratch node
412 
413         // Alpha not in format
414 
415         node = new IIOMetadataNode("TransparentIndex");
416         node.setAttribute("value",
417                           Integer.toString(transparentColorIndex));
418         transparency_node.appendChild(node);
419 
420         // TransparentColor not in format
421         // TileTransparencies not in format
422         // TileOpacities not in format
423 
424         return transparency_node;
425     }
426 
setFromTree(String formatName, Node root)427     public void setFromTree(String formatName, Node root)
428         throws IIOInvalidTreeException
429     {
430         throw new IllegalStateException("Metadata is read-only!");
431     }
432 
mergeNativeTree(Node root)433     protected void mergeNativeTree(Node root) throws IIOInvalidTreeException
434     {
435         throw new IllegalStateException("Metadata is read-only!");
436     }
437 
mergeStandardTree(Node root)438     protected void mergeStandardTree(Node root) throws IIOInvalidTreeException
439     {
440         throw new IllegalStateException("Metadata is read-only!");
441     }
442 
reset()443     public void reset() {
444         throw new IllegalStateException("Metadata is read-only!");
445     }
446 }
447