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