1 /* 2 * Copyright (c) 2001, 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.jpeg; 27 28 import javax.imageio.IIOException; 29 import javax.imageio.metadata.IIOInvalidTreeException; 30 import javax.imageio.metadata.IIOMetadataNode; 31 import javax.imageio.stream.ImageOutputStream; 32 import javax.imageio.plugins.jpeg.JPEGQTable; 33 34 import java.io.IOException; 35 import java.util.List; 36 import java.util.ArrayList; 37 import java.util.Iterator; 38 39 import org.w3c.dom.Node; 40 import org.w3c.dom.NodeList; 41 import org.w3c.dom.NamedNodeMap; 42 43 /** 44 * A DQT (Define Quantization Table) marker segment. 45 */ 46 class DQTMarkerSegment extends MarkerSegment { 47 List<Qtable> tables = new ArrayList<>(); // Could be 1 to 4 48 DQTMarkerSegment(float quality, boolean needTwo)49 DQTMarkerSegment(float quality, boolean needTwo) { 50 super(JPEG.DQT); 51 tables.add(new Qtable(true, quality)); 52 if (needTwo) { 53 tables.add(new Qtable(false, quality)); 54 } 55 } 56 DQTMarkerSegment(JPEGBuffer buffer)57 DQTMarkerSegment(JPEGBuffer buffer) throws IOException { 58 super(buffer); 59 int count = length; 60 while (count > 0) { 61 Qtable newGuy = new Qtable(buffer); 62 tables.add(newGuy); 63 count -= newGuy.data.length+1; 64 } 65 buffer.bufAvail -= length; 66 } 67 DQTMarkerSegment(JPEGQTable[] qtables)68 DQTMarkerSegment(JPEGQTable[] qtables) { 69 super(JPEG.DQT); 70 for (int i = 0; i < qtables.length; i++) { 71 tables.add(new Qtable(qtables[i], i)); 72 } 73 } 74 DQTMarkerSegment(Node node)75 DQTMarkerSegment(Node node) throws IIOInvalidTreeException { 76 super(JPEG.DQT); 77 NodeList children = node.getChildNodes(); 78 int size = children.getLength(); 79 if ((size < 1) || (size > 4)) { 80 throw new IIOInvalidTreeException("Invalid DQT node", node); 81 } 82 for (int i = 0; i < size; i++) { 83 tables.add(new Qtable(children.item(i))); 84 } 85 } 86 clone()87 protected Object clone() { 88 DQTMarkerSegment newGuy = (DQTMarkerSegment) super.clone(); 89 newGuy.tables = new ArrayList<>(tables.size()); 90 Iterator<Qtable> iter = tables.iterator(); 91 while (iter.hasNext()) { 92 Qtable table = iter.next(); 93 newGuy.tables.add((Qtable) table.clone()); 94 } 95 return newGuy; 96 } 97 getNativeNode()98 IIOMetadataNode getNativeNode() { 99 IIOMetadataNode node = new IIOMetadataNode("dqt"); 100 for (int i= 0; i<tables.size(); i++) { 101 Qtable table = tables.get(i); 102 node.appendChild(table.getNativeNode()); 103 } 104 return node; 105 } 106 107 /** 108 * Writes the data for this segment to the stream in 109 * valid JPEG format. 110 */ write(ImageOutputStream ios)111 void write(ImageOutputStream ios) throws IOException { 112 // We don't write DQT segments; the IJG library does. 113 } 114 print()115 void print() { 116 printTag("DQT"); 117 System.out.println("Num tables: " 118 + Integer.toString(tables.size())); 119 for (int i= 0; i<tables.size(); i++) { 120 Qtable table = tables.get(i); 121 table.print(); 122 } 123 System.out.println(); 124 } 125 126 /** 127 * Assuming the given table was generated by scaling the "standard" 128 * visually lossless luminance table, extract the scale factor that 129 * was used. 130 */ getChromaForLuma(Qtable luma)131 Qtable getChromaForLuma(Qtable luma) { 132 Qtable newGuy = null; 133 // Determine if the table is all the same values 134 // if so, use the same table 135 boolean allSame = true; 136 for (int i = 1; i < luma.QTABLE_SIZE; i++) { 137 if (luma.data[i] != luma.data[i-1]) { 138 allSame = false; 139 break; 140 } 141 } 142 if (allSame) { 143 newGuy = (Qtable) luma.clone(); 144 newGuy.tableID = 1; 145 } else { 146 // Otherwise, find the largest coefficient less than 255. This is 147 // the largest value that we know did not clamp on scaling. 148 int largestPos = 0; 149 for (int i = 1; i < luma.QTABLE_SIZE; i++) { 150 if (luma.data[i] > luma.data[largestPos]) { 151 largestPos = i; 152 } 153 } 154 // Compute the scale factor by dividing it by the value in the 155 // same position from the "standard" table. 156 // If the given table was not generated by scaling the standard, 157 // the resulting table will still be reasonable, as it will reflect 158 // a comparable scaling of chrominance frequency response of the 159 // eye. 160 float scaleFactor = ((float)(luma.data[largestPos])) 161 / ((float)(JPEGQTable.K1Div2Luminance.getTable()[largestPos])); 162 // generate a new table 163 JPEGQTable jpegTable = 164 JPEGQTable.K2Div2Chrominance.getScaledInstance(scaleFactor, 165 true); 166 newGuy = new Qtable(jpegTable, 1); 167 } 168 return newGuy; 169 } 170 getQtableFromNode(Node node)171 Qtable getQtableFromNode(Node node) throws IIOInvalidTreeException { 172 return new Qtable(node); 173 } 174 175 /** 176 * A quantization table within a DQT marker segment. 177 */ 178 class Qtable implements Cloneable { 179 int elementPrecision; 180 int tableID; 181 final int QTABLE_SIZE = 64; 182 int [] data; // 64 elements, in natural order 183 184 /** 185 * The zigzag-order position of the i'th element 186 * of a DCT block read in natural order. 187 */ 188 private final int [] zigzag = { 189 0, 1, 5, 6, 14, 15, 27, 28, 190 2, 4, 7, 13, 16, 26, 29, 42, 191 3, 8, 12, 17, 25, 30, 41, 43, 192 9, 11, 18, 24, 31, 40, 44, 53, 193 10, 19, 23, 32, 39, 45, 52, 54, 194 20, 22, 33, 38, 46, 51, 55, 60, 195 21, 34, 37, 47, 50, 56, 59, 61, 196 35, 36, 48, 49, 57, 58, 62, 63 197 }; 198 Qtable(boolean wantLuma, float quality)199 Qtable(boolean wantLuma, float quality) { 200 elementPrecision = 0; 201 JPEGQTable base = null; 202 if (wantLuma) { 203 tableID = 0; 204 base = JPEGQTable.K1Div2Luminance; 205 } else { 206 tableID = 1; 207 base = JPEGQTable.K2Div2Chrominance; 208 } 209 if (quality != JPEG.DEFAULT_QUALITY) { 210 quality = JPEG.convertToLinearQuality(quality); 211 if (wantLuma) { 212 base = JPEGQTable.K1Luminance.getScaledInstance 213 (quality, true); 214 } else { 215 base = JPEGQTable.K2Div2Chrominance.getScaledInstance 216 (quality, true); 217 } 218 } 219 data = base.getTable(); 220 } 221 Qtable(JPEGBuffer buffer)222 Qtable(JPEGBuffer buffer) throws IIOException { 223 elementPrecision = buffer.buf[buffer.bufPtr] >>> 4; 224 tableID = buffer.buf[buffer.bufPtr++] & 0xf; 225 if (elementPrecision != 0) { 226 // IJG is compiled for 8-bits, so this shouldn't happen 227 throw new IIOException ("Unsupported element precision"); 228 } 229 data = new int [QTABLE_SIZE]; 230 // Read from zig-zag order to natural order 231 for (int i = 0; i < QTABLE_SIZE; i++) { 232 data[i] = buffer.buf[buffer.bufPtr+zigzag[i]] & 0xff; 233 } 234 buffer.bufPtr += QTABLE_SIZE; 235 } 236 Qtable(JPEGQTable table, int id)237 Qtable(JPEGQTable table, int id) { 238 elementPrecision = 0; 239 tableID = id; 240 data = table.getTable(); 241 } 242 Qtable(Node node)243 Qtable(Node node) throws IIOInvalidTreeException { 244 if (node.getNodeName().equals("dqtable")) { 245 NamedNodeMap attrs = node.getAttributes(); 246 int count = attrs.getLength(); 247 if ((count < 1) || (count > 2)) { 248 throw new IIOInvalidTreeException 249 ("dqtable node must have 1 or 2 attributes", node); 250 } 251 elementPrecision = 0; 252 tableID = getAttributeValue(node, attrs, "qtableId", 0, 3, true); 253 if (node instanceof IIOMetadataNode) { 254 IIOMetadataNode ourNode = (IIOMetadataNode) node; 255 JPEGQTable table = (JPEGQTable) ourNode.getUserObject(); 256 if (table == null) { 257 throw new IIOInvalidTreeException 258 ("dqtable node must have user object", node); 259 } 260 data = table.getTable(); 261 } else { 262 throw new IIOInvalidTreeException 263 ("dqtable node must have user object", node); 264 } 265 } else { 266 throw new IIOInvalidTreeException 267 ("Invalid node, expected dqtable", node); 268 } 269 } 270 clone()271 protected Object clone() { 272 Qtable newGuy = null; 273 try { 274 newGuy = (Qtable) super.clone(); 275 } catch (CloneNotSupportedException e) {} // won't happen 276 if (data != null) { 277 newGuy.data = data.clone(); 278 } 279 return newGuy; 280 } 281 getNativeNode()282 IIOMetadataNode getNativeNode() { 283 IIOMetadataNode node = new IIOMetadataNode("dqtable"); 284 node.setAttribute("elementPrecision", 285 Integer.toString(elementPrecision)); 286 node.setAttribute("qtableId", 287 Integer.toString(tableID)); 288 node.setUserObject(new JPEGQTable(data)); 289 return node; 290 } 291 print()292 void print() { 293 System.out.println("Table id: " + Integer.toString(tableID)); 294 System.out.println("Element precision: " 295 + Integer.toString(elementPrecision)); 296 297 (new JPEGQTable(data)).toString(); 298 /* 299 for (int i = 0; i < 64; i++) { 300 if (i % 8 == 0) { 301 System.out.println(); 302 } 303 System.out.print(" " + Integer.toString(data[i])); 304 } 305 System.out.println(); 306 */ 307 } 308 } 309 } 310