1/*
2 * Copyright (c) 2005, 2008, 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
26package com.sun.imageio.plugins.gif;
27
28import java.io.UnsupportedEncodingException;
29import java.nio.charset.Charset;
30import java.util.ArrayList;
31import java.util.Iterator;
32import java.util.List;
33import javax.imageio.ImageTypeSpecifier;
34import javax.imageio.metadata.IIOInvalidTreeException;
35import javax.imageio.metadata.IIOMetadata;
36import javax.imageio.metadata.IIOMetadataNode;
37import javax.imageio.metadata.IIOMetadataFormat;
38import javax.imageio.metadata.IIOMetadataFormatImpl;
39import org.w3c.dom.Node;
40
41class GIFWritableImageMetadata extends GIFImageMetadata {
42
43    // package scope
44    static final String
45    NATIVE_FORMAT_NAME = "javax_imageio_gif_image_1.0";
46
47    GIFWritableImageMetadata() {
48        super(true,
49              NATIVE_FORMAT_NAME,
50              "com.sun.imageio.plugins.gif.GIFImageMetadataFormat",
51              null, null);
52    }
53
54    public boolean isReadOnly() {
55        return false;
56    }
57
58    public void reset() {
59        // Fields from Image Descriptor
60        imageLeftPosition = 0;
61        imageTopPosition = 0;
62        imageWidth = 0;
63        imageHeight = 0;
64        interlaceFlag = false;
65        sortFlag = false;
66        localColorTable = null;
67
68        // Fields from Graphic Control Extension
69        disposalMethod = 0;
70        userInputFlag = false;
71        transparentColorFlag = false;
72        delayTime = 0;
73        transparentColorIndex = 0;
74
75        // Fields from Plain Text Extension
76        hasPlainTextExtension = false;
77        textGridLeft = 0;
78        textGridTop = 0;
79        textGridWidth = 0;
80        textGridHeight = 0;
81        characterCellWidth = 0;
82        characterCellHeight = 0;
83        textForegroundColor = 0;
84        textBackgroundColor = 0;
85        text = null;
86
87        // Fields from ApplicationExtension
88        applicationIDs = null;
89        authenticationCodes = null;
90        applicationData = null;
91
92        // Fields from CommentExtension
93        // List of byte[]
94        comments = null;
95    }
96
97    private byte[] fromISO8859(String data) {
98        try {
99            return data.getBytes("ISO-8859-1");
100        } catch (UnsupportedEncodingException e) {
101            return "".getBytes();
102        }
103    }
104
105    protected void mergeNativeTree(Node root) throws IIOInvalidTreeException {
106        Node node = root;
107        if (!node.getNodeName().equals(nativeMetadataFormatName)) {
108            fatal(node, "Root must be " + nativeMetadataFormatName);
109        }
110
111        node = node.getFirstChild();
112        while (node != null) {
113            String name = node.getNodeName();
114
115            if (name.equals("ImageDescriptor")) {
116                imageLeftPosition = getIntAttribute(node,
117                                                    "imageLeftPosition",
118                                                    -1, true,
119                                                    true, 0, 65535);
120
121                imageTopPosition = getIntAttribute(node,
122                                                   "imageTopPosition",
123                                                   -1, true,
124                                                   true, 0, 65535);
125
126                imageWidth = getIntAttribute(node,
127                                             "imageWidth",
128                                             -1, true,
129                                             true, 1, 65535);
130
131                imageHeight = getIntAttribute(node,
132                                              "imageHeight",
133                                              -1, true,
134                                              true, 1, 65535);
135
136                interlaceFlag = getBooleanAttribute(node, "interlaceFlag",
137                                                    false, true);
138            } else if (name.equals("LocalColorTable")) {
139                int sizeOfLocalColorTable =
140                    getIntAttribute(node, "sizeOfLocalColorTable",
141                                    true, 2, 256);
142                if (sizeOfLocalColorTable != 2 &&
143                    sizeOfLocalColorTable != 4 &&
144                    sizeOfLocalColorTable != 8 &&
145                    sizeOfLocalColorTable != 16 &&
146                    sizeOfLocalColorTable != 32 &&
147                    sizeOfLocalColorTable != 64 &&
148                    sizeOfLocalColorTable != 128 &&
149                    sizeOfLocalColorTable != 256) {
150                    fatal(node,
151                          "Bad value for LocalColorTable attribute sizeOfLocalColorTable!");
152                }
153
154                sortFlag = getBooleanAttribute(node, "sortFlag", false, true);
155
156                localColorTable = getColorTable(node, "ColorTableEntry",
157                                                true, sizeOfLocalColorTable);
158            } else if (name.equals("GraphicControlExtension")) {
159                String disposalMethodName =
160                    getStringAttribute(node, "disposalMethod", null,
161                                       true, disposalMethodNames);
162                disposalMethod = 0;
163                while(!disposalMethodName.equals(disposalMethodNames[disposalMethod])) {
164                    disposalMethod++;
165                }
166
167                userInputFlag = getBooleanAttribute(node, "userInputFlag",
168                                                    false, true);
169
170                transparentColorFlag =
171                    getBooleanAttribute(node, "transparentColorFlag",
172                                        false, true);
173
174                delayTime = getIntAttribute(node,
175                                            "delayTime",
176                                            -1, true,
177                                            true, 0, 65535);
178
179                transparentColorIndex =
180                    getIntAttribute(node, "transparentColorIndex",
181                                    -1, true,
182                                    true, 0, 65535);
183            } else if (name.equals("PlainTextExtension")) {
184                hasPlainTextExtension = true;
185
186                textGridLeft = getIntAttribute(node,
187                                               "textGridLeft",
188                                               -1, true,
189                                               true, 0, 65535);
190
191                textGridTop = getIntAttribute(node,
192                                              "textGridTop",
193                                              -1, true,
194                                              true, 0, 65535);
195
196                textGridWidth = getIntAttribute(node,
197                                                "textGridWidth",
198                                                -1, true,
199                                                true, 1, 65535);
200
201                textGridHeight = getIntAttribute(node,
202                                                 "textGridHeight",
203                                                 -1, true,
204                                                 true, 1, 65535);
205
206                characterCellWidth = getIntAttribute(node,
207                                                     "characterCellWidth",
208                                                     -1, true,
209                                                     true, 1, 65535);
210
211                characterCellHeight = getIntAttribute(node,
212                                                      "characterCellHeight",
213                                                      -1, true,
214                                                      true, 1, 65535);
215
216                textForegroundColor = getIntAttribute(node,
217                                                      "textForegroundColor",
218                                                      -1, true,
219                                                      true, 0, 255);
220
221                textBackgroundColor = getIntAttribute(node,
222                                                      "textBackgroundColor",
223                                                      -1, true,
224                                                      true, 0, 255);
225
226                // XXX The "text" attribute of the PlainTextExtension element
227                // is not defined in the GIF image metadata format but it is
228                // present in the GIFImageMetadata class. Consequently it is
229                // used here but not required and with a default of "". See
230                // bug 5082763.
231
232                String textString =
233                    getStringAttribute(node, "text", "", false, null);
234                text = fromISO8859(textString);
235            } else if (name.equals("ApplicationExtensions")) {
236                IIOMetadataNode applicationExtension =
237                    (IIOMetadataNode)node.getFirstChild();
238
239                if (!applicationExtension.getNodeName().equals("ApplicationExtension")) {
240                    fatal(node,
241                          "Only a ApplicationExtension may be a child of a ApplicationExtensions!");
242                }
243
244                String applicationIDString =
245                    getStringAttribute(applicationExtension, "applicationID",
246                                       null, true, null);
247
248                String authenticationCodeString =
249                    getStringAttribute(applicationExtension, "authenticationCode",
250                                       null, true, null);
251
252                Object applicationExtensionData =
253                    applicationExtension.getUserObject();
254                if (applicationExtensionData == null ||
255                    !(applicationExtensionData instanceof byte[])) {
256                    fatal(applicationExtension,
257                          "Bad user object in ApplicationExtension!");
258                }
259
260                if (applicationIDs == null) {
261                    applicationIDs = new ArrayList<>();
262                    authenticationCodes = new ArrayList<>();
263                    applicationData = new ArrayList<>();
264                }
265
266                applicationIDs.add(fromISO8859(applicationIDString));
267                authenticationCodes.add(fromISO8859(authenticationCodeString));
268                applicationData.add((byte[]) applicationExtensionData);
269            } else if (name.equals("CommentExtensions")) {
270                Node commentExtension = node.getFirstChild();
271                if (commentExtension != null) {
272                    while(commentExtension != null) {
273                        if (!commentExtension.getNodeName().equals("CommentExtension")) {
274                            fatal(node,
275                                  "Only a CommentExtension may be a child of a CommentExtensions!");
276                        }
277
278                        if (comments == null) {
279                            comments = new ArrayList<>();
280                        }
281
282                        String comment =
283                            getStringAttribute(commentExtension, "value", null,
284                                               true, null);
285
286                        comments.add(fromISO8859(comment));
287
288                        commentExtension = commentExtension.getNextSibling();
289                    }
290                }
291            } else {
292                fatal(node, "Unknown child of root node!");
293            }
294
295            node = node.getNextSibling();
296        }
297    }
298
299    protected void mergeStandardTree(Node root)
300      throws IIOInvalidTreeException {
301        Node node = root;
302        if (!node.getNodeName()
303            .equals(IIOMetadataFormatImpl.standardMetadataFormatName)) {
304            fatal(node, "Root must be " +
305                  IIOMetadataFormatImpl.standardMetadataFormatName);
306        }
307
308        node = node.getFirstChild();
309        while (node != null) {
310            String name = node.getNodeName();
311
312            if (name.equals("Chroma")) {
313                Node childNode = node.getFirstChild();
314                while(childNode != null) {
315                    String childName = childNode.getNodeName();
316                    if (childName.equals("Palette")) {
317                        localColorTable = getColorTable(childNode,
318                                                        "PaletteEntry",
319                                                        false, -1);
320                        break;
321                    }
322                    childNode = childNode.getNextSibling();
323                }
324            } else if (name.equals("Compression")) {
325                Node childNode = node.getFirstChild();
326                while(childNode != null) {
327                    String childName = childNode.getNodeName();
328                    if (childName.equals("NumProgressiveScans")) {
329                        int numProgressiveScans =
330                            getIntAttribute(childNode, "value", 4, false,
331                                            true, 1, Integer.MAX_VALUE);
332                        if (numProgressiveScans > 1) {
333                            interlaceFlag = true;
334                        }
335                        break;
336                    }
337                    childNode = childNode.getNextSibling();
338                }
339            } else if (name.equals("Dimension")) {
340                Node childNode = node.getFirstChild();
341                while(childNode != null) {
342                    String childName = childNode.getNodeName();
343                    if (childName.equals("HorizontalPixelOffset")) {
344                        imageLeftPosition = getIntAttribute(childNode,
345                                                            "value",
346                                                            -1, true,
347                                                            true, 0, 65535);
348                    } else if (childName.equals("VerticalPixelOffset")) {
349                        imageTopPosition = getIntAttribute(childNode,
350                                                           "value",
351                                                           -1, true,
352                                                           true, 0, 65535);
353                    }
354                    childNode = childNode.getNextSibling();
355                }
356            } else if (name.equals("Text")) {
357                Node childNode = node.getFirstChild();
358                while(childNode != null) {
359                    String childName = childNode.getNodeName();
360                    if (childName.equals("TextEntry") &&
361                        getAttribute(childNode, "compression",
362                                     "none", false).equals("none") &&
363                        Charset.isSupported(getAttribute(childNode,
364                                                         "encoding",
365                                                         "ISO-8859-1",
366                                                         false))) {
367                        String value = getAttribute(childNode, "value");
368                        byte[] comment = fromISO8859(value);
369                        if (comments == null) {
370                            comments = new ArrayList<>();
371                        }
372                        comments.add(comment);
373                    }
374                    childNode = childNode.getNextSibling();
375                }
376            } else if (name.equals("Transparency")) {
377                Node childNode = node.getFirstChild();
378                while(childNode != null) {
379                    String childName = childNode.getNodeName();
380                    if (childName.equals("TransparentIndex")) {
381                        transparentColorIndex = getIntAttribute(childNode,
382                                                                "value",
383                                                                -1, true,
384                                                                true, 0, 255);
385                        transparentColorFlag = true;
386                        break;
387                    }
388                    childNode = childNode.getNextSibling();
389                }
390            }
391
392            node = node.getNextSibling();
393        }
394    }
395
396    public void setFromTree(String formatName, Node root)
397        throws IIOInvalidTreeException
398    {
399        reset();
400        mergeTree(formatName, root);
401    }
402}
403