michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: package org.mozilla.gecko.favicons.decoders; michael@0: michael@0: /** michael@0: * Representation of an ICO file ICONDIRENTRY structure. michael@0: */ michael@0: public class IconDirectoryEntry implements Comparable { michael@0: michael@0: public static int maxBPP; michael@0: michael@0: int width; michael@0: int height; michael@0: int paletteSize; michael@0: int bitsPerPixel; michael@0: int payloadSize; michael@0: int payloadOffset; michael@0: boolean payloadIsPNG; michael@0: michael@0: // Tracks the index in the Icon Directory of this entry. Useful only for pruning. michael@0: int index; michael@0: boolean isErroneous; michael@0: michael@0: public IconDirectoryEntry(int width, int height, int paletteSize, int bitsPerPixel, int payloadSize, int payloadOffset, boolean payloadIsPNG) { michael@0: this.width = width; michael@0: this.height = height; michael@0: this.paletteSize = paletteSize; michael@0: this.bitsPerPixel = bitsPerPixel; michael@0: this.payloadSize = payloadSize; michael@0: this.payloadOffset = payloadOffset; michael@0: this.payloadIsPNG = payloadIsPNG; michael@0: } michael@0: michael@0: /** michael@0: * Method to get a dummy Icon Directory Entry with the Erroneous bit set. michael@0: * michael@0: * @return An erroneous placeholder Icon Directory Entry. michael@0: */ michael@0: public static IconDirectoryEntry getErroneousEntry() { michael@0: IconDirectoryEntry ret = new IconDirectoryEntry(-1, -1, -1, -1, -1, -1, false); michael@0: ret.isErroneous = true; michael@0: michael@0: return ret; michael@0: } michael@0: michael@0: /** michael@0: * Create an IconDirectoryEntry object from a byte[]. Interprets the buffer starting at the given michael@0: * offset as an IconDirectoryEntry and returns the result. michael@0: * michael@0: * @param buffer Byte array containing the icon directory entry to decode. michael@0: * @param regionOffset Offset into the byte array of the valid region of the buffer. michael@0: * @param regionLength Length of the valid region in the buffer. michael@0: * @param entryOffset Offset of the icon directory entry to decode within the buffer. michael@0: * @return An IconDirectoryEntry object representing the entry specified, or null if the entry michael@0: * is obviously invalid. michael@0: */ michael@0: public static IconDirectoryEntry createFromBuffer(byte[] buffer, int regionOffset, int regionLength, int entryOffset) { michael@0: // Verify that the reserved field is really zero. michael@0: if (buffer[entryOffset + 3] != 0) { michael@0: return getErroneousEntry(); michael@0: } michael@0: michael@0: // Verify that the entry points to a region that actually exists in the buffer, else bin it. michael@0: int fieldPtr = entryOffset + 8; michael@0: int entryLength = (buffer[fieldPtr] & 0xFF) | michael@0: (buffer[fieldPtr + 1] & 0xFF) << 8 | michael@0: (buffer[fieldPtr + 2] & 0xFF) << 16 | michael@0: (buffer[fieldPtr + 3] & 0xFF) << 24; michael@0: michael@0: // Advance to the offset field. michael@0: fieldPtr += 4; michael@0: michael@0: int payloadOffset = (buffer[fieldPtr] & 0xFF) | michael@0: (buffer[fieldPtr + 1] & 0xFF) << 8 | michael@0: (buffer[fieldPtr + 2] & 0xFF) << 16 | michael@0: (buffer[fieldPtr + 3] & 0xFF) << 24; michael@0: michael@0: // Fail if the entry describes a region outside the buffer. michael@0: if (payloadOffset < 0 || entryLength < 0 || payloadOffset + entryLength > regionOffset + regionLength) { michael@0: return getErroneousEntry(); michael@0: } michael@0: michael@0: // Extract the image dimensions. michael@0: int imageWidth = buffer[entryOffset] & 0xFF; michael@0: int imageHeight = buffer[entryOffset+1] & 0xFF; michael@0: michael@0: // Because Microsoft, a size value of zero represents an image size of 256. michael@0: if (imageWidth == 0) { michael@0: imageWidth = 256; michael@0: } michael@0: michael@0: if (imageHeight == 0) { michael@0: imageHeight = 256; michael@0: } michael@0: michael@0: // If the image uses a colour palette, this is the number of colours, otherwise this is zero. michael@0: int paletteSize = buffer[entryOffset + 2] & 0xFF; michael@0: michael@0: // The plane count - usually 0 or 1. When > 1, taken as multiplier on bitsPerPixel. michael@0: int colorPlanes = buffer[entryOffset + 4] & 0xFF; michael@0: michael@0: int bitsPerPixel = (buffer[entryOffset + 6] & 0xFF) | michael@0: (buffer[entryOffset + 7] & 0xFF) << 8; michael@0: michael@0: if (colorPlanes > 1) { michael@0: bitsPerPixel *= colorPlanes; michael@0: } michael@0: michael@0: // Look for PNG magic numbers at the start of the payload. michael@0: boolean payloadIsPNG = FaviconDecoder.bufferStartsWith(buffer, FaviconDecoder.ImageMagicNumbers.PNG.value, regionOffset + payloadOffset); michael@0: michael@0: return new IconDirectoryEntry(imageWidth, imageHeight, paletteSize, bitsPerPixel, entryLength, payloadOffset, payloadIsPNG); michael@0: } michael@0: michael@0: /** michael@0: * Get the number of bytes from the start of the ICO file to the beginning of this entry. michael@0: */ michael@0: public int getOffset() { michael@0: return ICODecoder.ICO_HEADER_LENGTH_BYTES + (index * ICODecoder.ICO_ICONDIRENTRY_LENGTH_BYTES); michael@0: } michael@0: michael@0: @Override michael@0: public int compareTo(IconDirectoryEntry another) { michael@0: if (width > another.width) { michael@0: return 1; michael@0: } michael@0: michael@0: if (width < another.width) { michael@0: return -1; michael@0: } michael@0: michael@0: // Where both images exceed the max BPP, take the smaller of the two BPP values. michael@0: if (bitsPerPixel >= maxBPP && another.bitsPerPixel >= maxBPP) { michael@0: if (bitsPerPixel < another.bitsPerPixel) { michael@0: return 1; michael@0: } michael@0: michael@0: if (bitsPerPixel > another.bitsPerPixel) { michael@0: return -1; michael@0: } michael@0: } michael@0: michael@0: // Otherwise, take the larger of the BPP values. michael@0: if (bitsPerPixel > another.bitsPerPixel) { michael@0: return 1; michael@0: } michael@0: michael@0: if (bitsPerPixel < another.bitsPerPixel) { michael@0: return -1; michael@0: } michael@0: michael@0: // Prefer large palettes. michael@0: if (paletteSize > another.paletteSize) { michael@0: return 1; michael@0: } michael@0: michael@0: if (paletteSize < another.paletteSize) { michael@0: return -1; michael@0: } michael@0: michael@0: // Prefer smaller payloads. michael@0: if (payloadSize < another.payloadSize) { michael@0: return 1; michael@0: } michael@0: michael@0: if (payloadSize > another.payloadSize) { michael@0: return -1; michael@0: } michael@0: michael@0: // If all else fails, prefer PNGs over BMPs. They tend to be smaller. michael@0: if (payloadIsPNG && !another.payloadIsPNG) { michael@0: return 1; michael@0: } michael@0: michael@0: if (!payloadIsPNG && another.payloadIsPNG) { michael@0: return -1; michael@0: } michael@0: michael@0: return 0; michael@0: } michael@0: michael@0: public static void setMaxBPP(int maxBPP) { michael@0: IconDirectoryEntry.maxBPP = maxBPP; michael@0: } michael@0: michael@0: @Override michael@0: public String toString() { michael@0: return "IconDirectoryEntry{" + michael@0: "\nwidth=" + width + michael@0: ", \nheight=" + height + michael@0: ", \npaletteSize=" + paletteSize + michael@0: ", \nbitsPerPixel=" + bitsPerPixel + michael@0: ", \npayloadSize=" + payloadSize + michael@0: ", \npayloadOffset=" + payloadOffset + michael@0: ", \npayloadIsPNG=" + payloadIsPNG + michael@0: ", \nindex=" + index + michael@0: '}'; michael@0: } michael@0: }