1 /*
   2  * Copyright (c) 2000, 2014, 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<byte[]> applicationIDs = null; // new ArrayList();
  88 
  89     // List of byte[]
  90     public List<byte[]> authenticationCodes = null; // new ArrayList();
  91 
  92     // List of byte[]
  93     public List<byte[]> applicationData = null; // new ArrayList();
  94 
  95     // Fields from CommentExtension
  96     // List of byte[]
  97     public List<byte[]> comments = null; // new ArrayList();
  98 
  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 
 112     public GIFImageMetadata() {
 113         this(true,
 114               nativeMetadataFormatName,
 115               "com.sun.imageio.plugins.gif.GIFImageMetadataFormat",
 116               null, null);
 117     }
 118 
 119     public boolean isReadOnly() {
 120         return true;
 121     }
 122 
 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 
 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 
 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 = applicationIDs.get(i);
 229                 appExtNode.setAttribute("applicationID",
 230                                         toISO8859(applicationID));
 231                 byte[] authenticationCode = authenticationCodes.get(i);
 232                 appExtNode.setAttribute("authenticationCode",
 233                                         toISO8859(authenticationCode));
 234                 byte[] appData = applicationData.get(i);
 235                 appExtNode.setUserObject(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 = 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 
 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 
 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 
 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 
 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 
 373     public IIOMetadataNode getStandardTextNode() {
 374         if (comments == null) {
 375             return null;
 376         }
 377         Iterator<byte[]> 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 = 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 
 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 
 427     public void setFromTree(String formatName, Node root)
 428         throws IIOInvalidTreeException
 429     {
 430         throw new IllegalStateException("Metadata is read-only!");
 431     }
 432 
 433     protected void mergeNativeTree(Node root) throws IIOInvalidTreeException
 434     {
 435         throw new IllegalStateException("Metadata is read-only!");
 436     }
 437 
 438     protected void mergeStandardTree(Node root) throws IIOInvalidTreeException
 439     {
 440         throw new IllegalStateException("Metadata is read-only!");
 441     }
 442 
 443     public void reset() {
 444         throw new IllegalStateException("Metadata is read-only!");
 445     }
 446 }