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 
  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 
  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 
  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 
  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 
  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 
  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      */
 111     void write(ImageOutputStream ios) throws IOException {
 112         // We don't write DQT segments; the IJG library does.
 113     }
 114 
 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      */
 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 
 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 
 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 
 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 
 237         Qtable(JPEGQTable table, int id) {
 238             elementPrecision = 0;
 239             tableID = id;
 240             data = table.getTable();
 241         }
 242 
 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 
 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 
 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 
 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 }