Thu, 22 Jan 2015 13:21:57 +0100
Incorporate requested changes from Mozilla in review:
https://bugzilla.mozilla.org/show_bug.cgi?id=1123480#c6
1 /* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
5 package org.mozilla.gecko.favicons.decoders;
7 /**
8 * Representation of an ICO file ICONDIRENTRY structure.
9 */
10 public class IconDirectoryEntry implements Comparable<IconDirectoryEntry> {
12 public static int maxBPP;
14 int width;
15 int height;
16 int paletteSize;
17 int bitsPerPixel;
18 int payloadSize;
19 int payloadOffset;
20 boolean payloadIsPNG;
22 // Tracks the index in the Icon Directory of this entry. Useful only for pruning.
23 int index;
24 boolean isErroneous;
26 public IconDirectoryEntry(int width, int height, int paletteSize, int bitsPerPixel, int payloadSize, int payloadOffset, boolean payloadIsPNG) {
27 this.width = width;
28 this.height = height;
29 this.paletteSize = paletteSize;
30 this.bitsPerPixel = bitsPerPixel;
31 this.payloadSize = payloadSize;
32 this.payloadOffset = payloadOffset;
33 this.payloadIsPNG = payloadIsPNG;
34 }
36 /**
37 * Method to get a dummy Icon Directory Entry with the Erroneous bit set.
38 *
39 * @return An erroneous placeholder Icon Directory Entry.
40 */
41 public static IconDirectoryEntry getErroneousEntry() {
42 IconDirectoryEntry ret = new IconDirectoryEntry(-1, -1, -1, -1, -1, -1, false);
43 ret.isErroneous = true;
45 return ret;
46 }
48 /**
49 * Create an IconDirectoryEntry object from a byte[]. Interprets the buffer starting at the given
50 * offset as an IconDirectoryEntry and returns the result.
51 *
52 * @param buffer Byte array containing the icon directory entry to decode.
53 * @param regionOffset Offset into the byte array of the valid region of the buffer.
54 * @param regionLength Length of the valid region in the buffer.
55 * @param entryOffset Offset of the icon directory entry to decode within the buffer.
56 * @return An IconDirectoryEntry object representing the entry specified, or null if the entry
57 * is obviously invalid.
58 */
59 public static IconDirectoryEntry createFromBuffer(byte[] buffer, int regionOffset, int regionLength, int entryOffset) {
60 // Verify that the reserved field is really zero.
61 if (buffer[entryOffset + 3] != 0) {
62 return getErroneousEntry();
63 }
65 // Verify that the entry points to a region that actually exists in the buffer, else bin it.
66 int fieldPtr = entryOffset + 8;
67 int entryLength = (buffer[fieldPtr] & 0xFF) |
68 (buffer[fieldPtr + 1] & 0xFF) << 8 |
69 (buffer[fieldPtr + 2] & 0xFF) << 16 |
70 (buffer[fieldPtr + 3] & 0xFF) << 24;
72 // Advance to the offset field.
73 fieldPtr += 4;
75 int payloadOffset = (buffer[fieldPtr] & 0xFF) |
76 (buffer[fieldPtr + 1] & 0xFF) << 8 |
77 (buffer[fieldPtr + 2] & 0xFF) << 16 |
78 (buffer[fieldPtr + 3] & 0xFF) << 24;
80 // Fail if the entry describes a region outside the buffer.
81 if (payloadOffset < 0 || entryLength < 0 || payloadOffset + entryLength > regionOffset + regionLength) {
82 return getErroneousEntry();
83 }
85 // Extract the image dimensions.
86 int imageWidth = buffer[entryOffset] & 0xFF;
87 int imageHeight = buffer[entryOffset+1] & 0xFF;
89 // Because Microsoft, a size value of zero represents an image size of 256.
90 if (imageWidth == 0) {
91 imageWidth = 256;
92 }
94 if (imageHeight == 0) {
95 imageHeight = 256;
96 }
98 // If the image uses a colour palette, this is the number of colours, otherwise this is zero.
99 int paletteSize = buffer[entryOffset + 2] & 0xFF;
101 // The plane count - usually 0 or 1. When > 1, taken as multiplier on bitsPerPixel.
102 int colorPlanes = buffer[entryOffset + 4] & 0xFF;
104 int bitsPerPixel = (buffer[entryOffset + 6] & 0xFF) |
105 (buffer[entryOffset + 7] & 0xFF) << 8;
107 if (colorPlanes > 1) {
108 bitsPerPixel *= colorPlanes;
109 }
111 // Look for PNG magic numbers at the start of the payload.
112 boolean payloadIsPNG = FaviconDecoder.bufferStartsWith(buffer, FaviconDecoder.ImageMagicNumbers.PNG.value, regionOffset + payloadOffset);
114 return new IconDirectoryEntry(imageWidth, imageHeight, paletteSize, bitsPerPixel, entryLength, payloadOffset, payloadIsPNG);
115 }
117 /**
118 * Get the number of bytes from the start of the ICO file to the beginning of this entry.
119 */
120 public int getOffset() {
121 return ICODecoder.ICO_HEADER_LENGTH_BYTES + (index * ICODecoder.ICO_ICONDIRENTRY_LENGTH_BYTES);
122 }
124 @Override
125 public int compareTo(IconDirectoryEntry another) {
126 if (width > another.width) {
127 return 1;
128 }
130 if (width < another.width) {
131 return -1;
132 }
134 // Where both images exceed the max BPP, take the smaller of the two BPP values.
135 if (bitsPerPixel >= maxBPP && another.bitsPerPixel >= maxBPP) {
136 if (bitsPerPixel < another.bitsPerPixel) {
137 return 1;
138 }
140 if (bitsPerPixel > another.bitsPerPixel) {
141 return -1;
142 }
143 }
145 // Otherwise, take the larger of the BPP values.
146 if (bitsPerPixel > another.bitsPerPixel) {
147 return 1;
148 }
150 if (bitsPerPixel < another.bitsPerPixel) {
151 return -1;
152 }
154 // Prefer large palettes.
155 if (paletteSize > another.paletteSize) {
156 return 1;
157 }
159 if (paletteSize < another.paletteSize) {
160 return -1;
161 }
163 // Prefer smaller payloads.
164 if (payloadSize < another.payloadSize) {
165 return 1;
166 }
168 if (payloadSize > another.payloadSize) {
169 return -1;
170 }
172 // If all else fails, prefer PNGs over BMPs. They tend to be smaller.
173 if (payloadIsPNG && !another.payloadIsPNG) {
174 return 1;
175 }
177 if (!payloadIsPNG && another.payloadIsPNG) {
178 return -1;
179 }
181 return 0;
182 }
184 public static void setMaxBPP(int maxBPP) {
185 IconDirectoryEntry.maxBPP = maxBPP;
186 }
188 @Override
189 public String toString() {
190 return "IconDirectoryEntry{" +
191 "\nwidth=" + width +
192 ", \nheight=" + height +
193 ", \npaletteSize=" + paletteSize +
194 ", \nbitsPerPixel=" + bitsPerPixel +
195 ", \npayloadSize=" + payloadSize +
196 ", \npayloadOffset=" + payloadOffset +
197 ", \npayloadIsPNG=" + payloadIsPNG +
198 ", \nindex=" + index +
199 '}';
200 }
201 }