1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/mobile/android/base/favicons/decoders/IconDirectoryEntry.java Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,201 @@ 1.4 +/* This Source Code Form is subject to the terms of the Mozilla Public 1.5 + * License, v. 2.0. If a copy of the MPL was not distributed with this 1.6 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.7 + 1.8 +package org.mozilla.gecko.favicons.decoders; 1.9 + 1.10 +/** 1.11 + * Representation of an ICO file ICONDIRENTRY structure. 1.12 + */ 1.13 +public class IconDirectoryEntry implements Comparable<IconDirectoryEntry> { 1.14 + 1.15 + public static int maxBPP; 1.16 + 1.17 + int width; 1.18 + int height; 1.19 + int paletteSize; 1.20 + int bitsPerPixel; 1.21 + int payloadSize; 1.22 + int payloadOffset; 1.23 + boolean payloadIsPNG; 1.24 + 1.25 + // Tracks the index in the Icon Directory of this entry. Useful only for pruning. 1.26 + int index; 1.27 + boolean isErroneous; 1.28 + 1.29 + public IconDirectoryEntry(int width, int height, int paletteSize, int bitsPerPixel, int payloadSize, int payloadOffset, boolean payloadIsPNG) { 1.30 + this.width = width; 1.31 + this.height = height; 1.32 + this.paletteSize = paletteSize; 1.33 + this.bitsPerPixel = bitsPerPixel; 1.34 + this.payloadSize = payloadSize; 1.35 + this.payloadOffset = payloadOffset; 1.36 + this.payloadIsPNG = payloadIsPNG; 1.37 + } 1.38 + 1.39 + /** 1.40 + * Method to get a dummy Icon Directory Entry with the Erroneous bit set. 1.41 + * 1.42 + * @return An erroneous placeholder Icon Directory Entry. 1.43 + */ 1.44 + public static IconDirectoryEntry getErroneousEntry() { 1.45 + IconDirectoryEntry ret = new IconDirectoryEntry(-1, -1, -1, -1, -1, -1, false); 1.46 + ret.isErroneous = true; 1.47 + 1.48 + return ret; 1.49 + } 1.50 + 1.51 + /** 1.52 + * Create an IconDirectoryEntry object from a byte[]. Interprets the buffer starting at the given 1.53 + * offset as an IconDirectoryEntry and returns the result. 1.54 + * 1.55 + * @param buffer Byte array containing the icon directory entry to decode. 1.56 + * @param regionOffset Offset into the byte array of the valid region of the buffer. 1.57 + * @param regionLength Length of the valid region in the buffer. 1.58 + * @param entryOffset Offset of the icon directory entry to decode within the buffer. 1.59 + * @return An IconDirectoryEntry object representing the entry specified, or null if the entry 1.60 + * is obviously invalid. 1.61 + */ 1.62 + public static IconDirectoryEntry createFromBuffer(byte[] buffer, int regionOffset, int regionLength, int entryOffset) { 1.63 + // Verify that the reserved field is really zero. 1.64 + if (buffer[entryOffset + 3] != 0) { 1.65 + return getErroneousEntry(); 1.66 + } 1.67 + 1.68 + // Verify that the entry points to a region that actually exists in the buffer, else bin it. 1.69 + int fieldPtr = entryOffset + 8; 1.70 + int entryLength = (buffer[fieldPtr] & 0xFF) | 1.71 + (buffer[fieldPtr + 1] & 0xFF) << 8 | 1.72 + (buffer[fieldPtr + 2] & 0xFF) << 16 | 1.73 + (buffer[fieldPtr + 3] & 0xFF) << 24; 1.74 + 1.75 + // Advance to the offset field. 1.76 + fieldPtr += 4; 1.77 + 1.78 + int payloadOffset = (buffer[fieldPtr] & 0xFF) | 1.79 + (buffer[fieldPtr + 1] & 0xFF) << 8 | 1.80 + (buffer[fieldPtr + 2] & 0xFF) << 16 | 1.81 + (buffer[fieldPtr + 3] & 0xFF) << 24; 1.82 + 1.83 + // Fail if the entry describes a region outside the buffer. 1.84 + if (payloadOffset < 0 || entryLength < 0 || payloadOffset + entryLength > regionOffset + regionLength) { 1.85 + return getErroneousEntry(); 1.86 + } 1.87 + 1.88 + // Extract the image dimensions. 1.89 + int imageWidth = buffer[entryOffset] & 0xFF; 1.90 + int imageHeight = buffer[entryOffset+1] & 0xFF; 1.91 + 1.92 + // Because Microsoft, a size value of zero represents an image size of 256. 1.93 + if (imageWidth == 0) { 1.94 + imageWidth = 256; 1.95 + } 1.96 + 1.97 + if (imageHeight == 0) { 1.98 + imageHeight = 256; 1.99 + } 1.100 + 1.101 + // If the image uses a colour palette, this is the number of colours, otherwise this is zero. 1.102 + int paletteSize = buffer[entryOffset + 2] & 0xFF; 1.103 + 1.104 + // The plane count - usually 0 or 1. When > 1, taken as multiplier on bitsPerPixel. 1.105 + int colorPlanes = buffer[entryOffset + 4] & 0xFF; 1.106 + 1.107 + int bitsPerPixel = (buffer[entryOffset + 6] & 0xFF) | 1.108 + (buffer[entryOffset + 7] & 0xFF) << 8; 1.109 + 1.110 + if (colorPlanes > 1) { 1.111 + bitsPerPixel *= colorPlanes; 1.112 + } 1.113 + 1.114 + // Look for PNG magic numbers at the start of the payload. 1.115 + boolean payloadIsPNG = FaviconDecoder.bufferStartsWith(buffer, FaviconDecoder.ImageMagicNumbers.PNG.value, regionOffset + payloadOffset); 1.116 + 1.117 + return new IconDirectoryEntry(imageWidth, imageHeight, paletteSize, bitsPerPixel, entryLength, payloadOffset, payloadIsPNG); 1.118 + } 1.119 + 1.120 + /** 1.121 + * Get the number of bytes from the start of the ICO file to the beginning of this entry. 1.122 + */ 1.123 + public int getOffset() { 1.124 + return ICODecoder.ICO_HEADER_LENGTH_BYTES + (index * ICODecoder.ICO_ICONDIRENTRY_LENGTH_BYTES); 1.125 + } 1.126 + 1.127 + @Override 1.128 + public int compareTo(IconDirectoryEntry another) { 1.129 + if (width > another.width) { 1.130 + return 1; 1.131 + } 1.132 + 1.133 + if (width < another.width) { 1.134 + return -1; 1.135 + } 1.136 + 1.137 + // Where both images exceed the max BPP, take the smaller of the two BPP values. 1.138 + if (bitsPerPixel >= maxBPP && another.bitsPerPixel >= maxBPP) { 1.139 + if (bitsPerPixel < another.bitsPerPixel) { 1.140 + return 1; 1.141 + } 1.142 + 1.143 + if (bitsPerPixel > another.bitsPerPixel) { 1.144 + return -1; 1.145 + } 1.146 + } 1.147 + 1.148 + // Otherwise, take the larger of the BPP values. 1.149 + if (bitsPerPixel > another.bitsPerPixel) { 1.150 + return 1; 1.151 + } 1.152 + 1.153 + if (bitsPerPixel < another.bitsPerPixel) { 1.154 + return -1; 1.155 + } 1.156 + 1.157 + // Prefer large palettes. 1.158 + if (paletteSize > another.paletteSize) { 1.159 + return 1; 1.160 + } 1.161 + 1.162 + if (paletteSize < another.paletteSize) { 1.163 + return -1; 1.164 + } 1.165 + 1.166 + // Prefer smaller payloads. 1.167 + if (payloadSize < another.payloadSize) { 1.168 + return 1; 1.169 + } 1.170 + 1.171 + if (payloadSize > another.payloadSize) { 1.172 + return -1; 1.173 + } 1.174 + 1.175 + // If all else fails, prefer PNGs over BMPs. They tend to be smaller. 1.176 + if (payloadIsPNG && !another.payloadIsPNG) { 1.177 + return 1; 1.178 + } 1.179 + 1.180 + if (!payloadIsPNG && another.payloadIsPNG) { 1.181 + return -1; 1.182 + } 1.183 + 1.184 + return 0; 1.185 + } 1.186 + 1.187 + public static void setMaxBPP(int maxBPP) { 1.188 + IconDirectoryEntry.maxBPP = maxBPP; 1.189 + } 1.190 + 1.191 + @Override 1.192 + public String toString() { 1.193 + return "IconDirectoryEntry{" + 1.194 + "\nwidth=" + width + 1.195 + ", \nheight=" + height + 1.196 + ", \npaletteSize=" + paletteSize + 1.197 + ", \nbitsPerPixel=" + bitsPerPixel + 1.198 + ", \npayloadSize=" + payloadSize + 1.199 + ", \npayloadOffset=" + payloadOffset + 1.200 + ", \npayloadIsPNG=" + payloadIsPNG + 1.201 + ", \nindex=" + index + 1.202 + '}'; 1.203 + } 1.204 +}