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 
  26 package com.sun.imageio.plugins.gif;
  27 
  28 import java.io.UnsupportedEncodingException;
  29 import java.nio.charset.Charset;
  30 import java.util.ArrayList;
  31 import java.util.Iterator;
  32 import java.util.List;
  33 import javax.imageio.ImageTypeSpecifier;
  34 import javax.imageio.metadata.IIOInvalidTreeException;
  35 import javax.imageio.metadata.IIOMetadata;
  36 import javax.imageio.metadata.IIOMetadataNode;
  37 import javax.imageio.metadata.IIOMetadataFormat;
  38 import javax.imageio.metadata.IIOMetadataFormatImpl;
  39 import org.w3c.dom.Node;
  40 
  41 class 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 }