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(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 }