org.pushingpixels.flamingo.api.common.icon.IcoWrapperIcon Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of flamengo Show documentation
Show all versions of flamengo Show documentation
Flamingo Swing component suite
The newest version!
/*
* Copyright (c) 2005-2010 Flamingo Kirill Grouchnikov. All Rights Reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* o Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* o Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* o Neither the name of Flamingo Kirill Grouchnikov nor the names of
* its contributors may be used to endorse or promote products derived
* from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
* THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
* OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
* OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package org.pushingpixels.flamingo.api.common.icon;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.*;
import java.net.URL;
import java.util.*;
import javax.imageio.ImageIO;
import javax.swing.Icon;
import javax.swing.SwingWorker;
import javax.swing.event.EventListenerList;
import org.pushingpixels.flamingo.api.common.AsynchronousLoadListener;
import org.pushingpixels.flamingo.api.common.AsynchronousLoading;
import org.pushingpixels.flamingo.internal.utils.FlamingoUtilities;
/**
* Helper class to load image planes from .ICO files.
*
* @author Kirill Grouchnikov
*/
abstract class IcoWrapperIcon implements Icon, AsynchronousLoading {
/**
* The input stream of the original image.
*/
protected InputStream icoInputStream;
/**
* Image planes of the original ICO image.
*/
protected Map icoPlaneMap;
/**
* Contains all precomputed images.
*/
protected Map cachedImages;
/**
* The width of the current image.
*/
protected int width;
/**
* The height of the current image.
*/
protected int height;
/**
* The listeners.
*/
protected EventListenerList listenerList = new EventListenerList();
/**
* Create a new SVG icon.
*
* @param inputStream
* The input stream to read the SVG document from.
* @param w
* The width of the icon.
* @param h
* The height of the icon.
*/
public IcoWrapperIcon(InputStream inputStream, int w, int h) {
this.icoInputStream = inputStream;
this.width = w;
this.height = h;
this.listenerList = new EventListenerList();
this.cachedImages = new LinkedHashMap() {
@Override
protected boolean removeEldestEntry(
Map.Entry eldest) {
return size() > 5;
};
};
this.renderImage(this.width, this.height);
}
/*
* (non-Javadoc)
*
* @see
* org.jvnet.flamingo.common.AsynchronousLoading#addAsynchronousLoadListener
* (org.jvnet.flamingo.common.AsynchronousLoadListener)
*/
public void addAsynchronousLoadListener(AsynchronousLoadListener l) {
this.listenerList.add(AsynchronousLoadListener.class, l);
}
/*
* (non-Javadoc)
*
* @see
* org.jvnet.flamingo.common.AsynchronousLoading#removeAsynchronousLoadListener
* (org.jvnet.flamingo.common.AsynchronousLoadListener)
*/
public void removeAsynchronousLoadListener(AsynchronousLoadListener l) {
this.listenerList.remove(AsynchronousLoadListener.class, l);
}
/*
* (non-Javadoc)
*
* @see javax.swing.Icon#getIconWidth()
*/
public int getIconWidth() {
return width;
}
/*
* (non-Javadoc)
*
* @see javax.swing.Icon#getIconHeight()
*/
public int getIconHeight() {
return height;
}
/*
* (non-Javadoc)
*
* @see javax.swing.Icon#paintIcon(java.awt.Component, java.awt.Graphics,
* int, int)
*/
public void paintIcon(Component c, Graphics g, int x, int y) {
BufferedImage image = this.cachedImages.get(this.getIconWidth() + ":"
+ this.getIconHeight());
if (image != null) {
int dx = (this.width - image.getWidth()) / 2;
int dy = (this.height - image.getHeight()) / 2;
g.drawImage(image, x + dx, y + dy, null);
}
}
/**
* Sets the preferred size for this
icon. The rendering is
* scheduled automatically.
*
* @param dim
* Preferred size.
*/
public synchronized void setPreferredSize(Dimension dim) {
if ((dim.width == this.width) && (dim.height == this.height))
return;
this.width = dim.width;
this.height = dim.height;
this.renderImage(this.width, this.height);
}
/**
* Renders the image.
*
* @param renderWidth
* Requested rendering width.
* @param renderHeight
* Requested rendering height.
*/
protected synchronized void renderImage(final int renderWidth,
final int renderHeight) {
String key = renderWidth + ":" + renderHeight;
if (this.cachedImages.containsKey(key)) {
fireAsyncCompleted(true);
return;
}
SwingWorker worker = new SwingWorker() {
@Override
protected BufferedImage doInBackground() throws Exception {
synchronized (icoInputStream) {
if (icoPlaneMap == null) {
// read original ICO image
Ico ico = new Ico(icoInputStream);
icoPlaneMap = new TreeMap();
Set widths = new HashSet();
for (int i = 0; i < ico.getNumImages(); i++) {
BufferedImage icoPlane = ico.getImage(i);
widths.add(icoPlane.getWidth());
}
for (int width : widths) {
// find the ico plane with the largest color count
BufferedImage bestMatch = null;
int bestColorCount = -1;
for (int i = 0; i < ico.getNumImages(); i++) {
BufferedImage icoPlane = ico.getImage(i);
if (icoPlane.getWidth() != width)
continue;
int icoPlaneColorCount = ico.getNumColors(i);
if (icoPlaneColorCount == 0) {
bestMatch = icoPlane;
bestColorCount = 0;
} else {
if (bestColorCount == 0)
continue;
if (icoPlaneColorCount > bestColorCount) {
bestMatch = icoPlane;
bestColorCount = icoPlaneColorCount;
}
}
}
icoPlaneMap.put(width, bestMatch);
}
}
}
// find the best match
int indexOfBestMatch = -1;
int bestMatchWidth = -1;
for (Map.Entry icoPlaneMapEntry : icoPlaneMap
.entrySet()) {
BufferedImage icoPlane = icoPlaneMapEntry.getValue();
int icoPlaneWidth = icoPlane.getWidth();
if (icoPlaneWidth > renderWidth) {
// check if the ICO plane width is closer
// to the required width than the best match so far
if (bestMatchWidth < 0) {
bestMatchWidth = icoPlaneWidth;
} else {
if (bestMatchWidth > icoPlaneWidth) {
bestMatchWidth = icoPlaneWidth;
}
}
}
}
// if at this point the best match is not found, it
// means that the requested width is bigger than
// any of the ICO planes. Take the biggest ICO plane
// available
if (indexOfBestMatch < 0) {
for (Map.Entry icoPlaneMapEntry : icoPlaneMap
.entrySet()) {
BufferedImage icoPlane = icoPlaneMapEntry.getValue();
int icoPlaneWidth = icoPlane.getWidth();
if (icoPlaneWidth > bestMatchWidth) {
bestMatchWidth = icoPlaneWidth;
}
}
}
BufferedImage bestMatchPlane = icoPlaneMap.get(bestMatchWidth);
BufferedImage result = bestMatchPlane;
float scaleX = (float) bestMatchPlane.getWidth()
/ (float) renderWidth;
float scaleY = (float) bestMatchPlane.getHeight()
/ (float) renderHeight;
float scale = Math.max(scaleX, scaleY);
if (scale > 1.0f) {
int finalWidth = (int) (bestMatchPlane.getWidth() / scale);
result = FlamingoUtilities.createThumbnail(bestMatchPlane,
finalWidth);
}
return result;
}
@Override
protected void done() {
try {
BufferedImage bufferedImage = get();
cachedImages.put(renderWidth + ":" + renderHeight,
bufferedImage);
fireAsyncCompleted(true);
} catch (Exception exc) {
fireAsyncCompleted(false);
}
}
};
worker.execute();
}
/**
* Fires the asynchronous load event.
*
* @param event
* Event object.
*/
protected void fireAsyncCompleted(Boolean event) {
// Guaranteed to return a non-null array
Object[] listeners = listenerList.getListenerList();
// Process the listeners last to first, notifying
// those that are interested in this event
for (int i = listeners.length - 2; i >= 0; i -= 2) {
if (listeners[i] == AsynchronousLoadListener.class) {
((AsynchronousLoadListener) listeners[i + 1]).completed(event);
}
}
}
/*
* (non-Javadoc)
*
* @see org.jvnet.flamingo.common.AsynchronousLoading#isLoading()
*/
@Override
public synchronized boolean isLoading() {
BufferedImage image = this.cachedImages.get(this.getIconWidth() + ":"
+ this.getIconHeight());
return (image == null);
}
}
/**
* The code below is copyrighted by Jeff Friesen and first appeared on InformIT.com at this
* location. This code is licensed under BSD license and can be reused as
* long as the credit is given to Jeff Friesen, InformIT.com and the original
* URL above.
*
* @author Jeff Friesen
*/
class Ico {
private final static int FDE_OFFSET = 6; // first directory entry offset
private final static int DE_LENGTH = 16; // directory entry length
private final static int BMIH_LENGTH = 40; // BITMAPINFOHEADER length
private byte[] icoimage = new byte[0]; // new byte [0] facilitates read()
private int numImages;
private BufferedImage[] bi;
private int[] colorCount;
public Ico(File file) throws BadIcoResException, IOException {
this(file.getAbsolutePath());
}
public Ico(InputStream is) throws BadIcoResException, IOException {
try {
read(is);
parseICOImage();
} finally {
try {
is.close();
} catch (IOException ioe) {
}
}
}
public Ico(String filename) throws BadIcoResException, IOException {
this(new FileInputStream(filename));
}
public Ico(URL url) throws BadIcoResException, IOException {
this(url.openStream());
}
public BufferedImage getImage(int index) {
if (index < 0 || index >= numImages)
throw new IllegalArgumentException("index out of range");
return bi[index];
}
public int getNumColors(int index) {
if (index < 0 || index >= numImages)
throw new IllegalArgumentException("index out of range");
return colorCount[index];
}
public int getNumImages() {
return numImages;
}
private int calcScanlineBytes(int width, int bitCount) {
// Calculate minimum number of double-words required to store width
// pixels where each pixel occupies bitCount bits. XOR and AND bitmaps
// are stored such that each scanline is aligned on a double-word
// boundary.
return (((width * bitCount) + 31) / 32) * 4;
}
private void parseICOImage() throws BadIcoResException, IOException {
// Check resource type field.
if (icoimage[2] != 1 || icoimage[3] != 0)
throw new BadIcoResException("Not an ICO resource");
numImages = ubyte(icoimage[5]);
numImages <<= 8;
numImages |= icoimage[4];
bi = new BufferedImage[numImages];
colorCount = new int[numImages];
for (int i = 0; i < numImages; i++) {
int width = ubyte(icoimage[FDE_OFFSET + i * DE_LENGTH]);
int height = ubyte(icoimage[FDE_OFFSET + i * DE_LENGTH + 1]);
colorCount[i] = ubyte(icoimage[FDE_OFFSET + i * DE_LENGTH + 2]);
int bytesInRes = ubyte(icoimage[FDE_OFFSET + i * DE_LENGTH + 11]);
bytesInRes <<= 8;
bytesInRes |= ubyte(icoimage[FDE_OFFSET + i * DE_LENGTH + 10]);
bytesInRes <<= 8;
bytesInRes |= ubyte(icoimage[FDE_OFFSET + i * DE_LENGTH + 9]);
bytesInRes <<= 8;
bytesInRes |= ubyte(icoimage[FDE_OFFSET + i * DE_LENGTH + 8]);
int imageOffset = ubyte(icoimage[FDE_OFFSET + i * DE_LENGTH + 15]);
imageOffset <<= 8;
imageOffset |= ubyte(icoimage[FDE_OFFSET + i * DE_LENGTH + 14]);
imageOffset <<= 8;
imageOffset |= ubyte(icoimage[FDE_OFFSET + i * DE_LENGTH + 13]);
imageOffset <<= 8;
imageOffset |= ubyte(icoimage[FDE_OFFSET + i * DE_LENGTH + 12]);
if (icoimage[imageOffset] == 40 && icoimage[imageOffset + 1] == 0
&& icoimage[imageOffset + 2] == 0
&& icoimage[imageOffset + 3] == 0) {
// BITMAPINFOHEADER detected
int _width = ubyte(icoimage[imageOffset + 7]);
_width <<= 8;
_width |= ubyte(icoimage[imageOffset + 6]);
_width <<= 8;
_width |= ubyte(icoimage[imageOffset + 5]);
_width <<= 8;
_width |= ubyte(icoimage[imageOffset + 4]);
// If width is 0 (for 256 pixels or higher), _width contains
// actual width.
if (width == 0)
width = _width;
int _height = ubyte(icoimage[imageOffset + 11]);
_height <<= 8;
_height |= ubyte(icoimage[imageOffset + 10]);
_height <<= 8;
_height |= ubyte(icoimage[imageOffset + 9]);
_height <<= 8;
_height |= ubyte(icoimage[imageOffset + 8]);
// If height is 0 (for 256 pixels or higher), _height contains
// actual height times 2.
if (height == 0)
height = _height >> 1; // Divide by 2.
int planes = ubyte(icoimage[imageOffset + 13]);
planes <<= 8;
planes |= ubyte(icoimage[imageOffset + 12]);
int bitCount = ubyte(icoimage[imageOffset + 15]);
bitCount <<= 8;
bitCount |= ubyte(icoimage[imageOffset + 14]);
// If colorCount [i] is 0, the number of colors is determined
// from the planes and bitCount values. For example, the number
// of colors is 256 when planes is 1 and bitCount is 8. Leave
// colorCount [i] set to 0 when planes is 1 and bitCount is 32.
if (colorCount[i] == 0) {
if (planes == 1) {
if (bitCount == 1)
colorCount[i] = 2;
else if (bitCount == 4)
colorCount[i] = 16;
else if (bitCount == 8)
colorCount[i] = 256;
else if (bitCount != 32)
colorCount[i] = (int) Math.pow(2, bitCount);
} else
colorCount[i] = (int) Math.pow(2, bitCount * planes);
}
bi[i] = new BufferedImage(width, height,
BufferedImage.TYPE_INT_ARGB);
// Parse image to image buffer.
int colorTableOffset = imageOffset + BMIH_LENGTH;
if (colorCount[i] == 2) {
int xorImageOffset = colorTableOffset + 2 * 4;
int scanlineBytes = calcScanlineBytes(width, 1);
int andImageOffset = xorImageOffset + scanlineBytes
* height;
int[] masks = { 128, 64, 32, 16, 8, 4, 2, 1 };
for (int row = 0; row < height; row++)
for (int col = 0; col < width; col++) {
int index;
if ((ubyte(icoimage[xorImageOffset + row
* scanlineBytes + col / 8]) & masks[col % 8]) != 0)
index = 1;
else
index = 0;
int rgb = 0;
rgb |= (ubyte(icoimage[colorTableOffset + index * 4
+ 2]));
rgb <<= 8;
rgb |= (ubyte(icoimage[colorTableOffset + index * 4
+ 1]));
rgb <<= 8;
rgb |= (ubyte(icoimage[colorTableOffset + index * 4]));
if ((ubyte(icoimage[andImageOffset + row
* scanlineBytes + col / 8]) & masks[col % 8]) != 0)
bi[i].setRGB(col, height - 1 - row, rgb);
else
bi[i].setRGB(col, height - 1 - row,
0xff000000 | rgb);
}
} else if (colorCount[i] == 16) {
int xorImageOffset = colorTableOffset + 16 * 4;
int scanlineBytes = calcScanlineBytes(width, 4);
int andImageOffset = xorImageOffset + scanlineBytes
* height;
int[] masks = { 128, 64, 32, 16, 8, 4, 2, 1 };
for (int row = 0; row < height; row++)
for (int col = 0; col < width; col++) {
int index;
if ((col & 1) == 0) // even
{
index = ubyte(icoimage[xorImageOffset + row
* scanlineBytes + col / 2]);
index >>= 4;
} else {
index = ubyte(icoimage[xorImageOffset + row
* scanlineBytes + col / 2]) & 15;
}
int rgb = 0;
rgb |= (ubyte(icoimage[colorTableOffset + index * 4
+ 2]));
rgb <<= 8;
rgb |= (ubyte(icoimage[colorTableOffset + index * 4
+ 1]));
rgb <<= 8;
rgb |= (ubyte(icoimage[colorTableOffset + index * 4]));
if ((ubyte(icoimage[andImageOffset + row
* calcScanlineBytes(width, 1) + col / 8]) & masks[col % 8]) != 0)
bi[i].setRGB(col, height - 1 - row, rgb);
else
bi[i].setRGB(col, height - 1 - row,
0xff000000 | rgb);
}
} else if (colorCount[i] == 256) {
int xorImageOffset = colorTableOffset + 256 * 4;
int scanlineBytes = calcScanlineBytes(width, 8);
int andImageOffset = xorImageOffset + scanlineBytes
* height;
int[] masks = { 128, 64, 32, 16, 8, 4, 2, 1 };
for (int row = 0; row < height; row++)
for (int col = 0; col < width; col++) {
int index;
index = ubyte(icoimage[xorImageOffset + row
* scanlineBytes + col]);
int rgb = 0;
rgb |= (ubyte(icoimage[colorTableOffset + index * 4
+ 2]));
rgb <<= 8;
rgb |= (ubyte(icoimage[colorTableOffset + index * 4
+ 1]));
rgb <<= 8;
rgb |= (ubyte(icoimage[colorTableOffset + index * 4]));
if ((ubyte(icoimage[andImageOffset + row
* calcScanlineBytes(width, 1) + col / 8]) & masks[col % 8]) != 0)
bi[i].setRGB(col, height - 1 - row, rgb);
else
bi[i].setRGB(col, height - 1 - row,
0xff000000 | rgb);
}
} else if (colorCount[i] == 0) {
int scanlineBytes = calcScanlineBytes(width, 32);
for (int row = 0; row < height; row++)
for (int col = 0; col < width; col++) {
int rgb = ubyte(icoimage[colorTableOffset + row
* scanlineBytes + col * 4 + 3]);
rgb <<= 8;
rgb |= ubyte(icoimage[colorTableOffset + row
* scanlineBytes + col * 4 + 2]);
rgb <<= 8;
rgb |= ubyte(icoimage[colorTableOffset + row
* scanlineBytes + col * 4 + 1]);
rgb <<= 8;
rgb |= ubyte(icoimage[colorTableOffset + row
* scanlineBytes + col * 4]);
bi[i].setRGB(col, height - 1 - row, rgb);
}
}
} else if (ubyte(icoimage[imageOffset]) == 0x89
&& icoimage[imageOffset + 1] == 0x50
&& icoimage[imageOffset + 2] == 0x4e
&& icoimage[imageOffset + 3] == 0x47
&& icoimage[imageOffset + 4] == 0x0d
&& icoimage[imageOffset + 5] == 0x0a
&& icoimage[imageOffset + 6] == 0x1a
&& icoimage[imageOffset + 7] == 0x0a) {
// PNG detected
ByteArrayInputStream bais;
bais = new ByteArrayInputStream(icoimage, imageOffset,
bytesInRes);
bi[i] = ImageIO.read(bais);
} else
throw new BadIcoResException("BITMAPINFOHEADER or PNG "
+ "expected");
}
icoimage = null; // This array can now be garbage collected.
}
private void read(InputStream is) throws IOException {
int bytesToRead;
while ((bytesToRead = is.available()) != 0) {
byte[] icoimage2 = new byte[icoimage.length + bytesToRead];
System.arraycopy(icoimage, 0, icoimage2, 0, icoimage.length);
is.read(icoimage2, icoimage.length, bytesToRead);
icoimage = icoimage2;
}
}
private int ubyte(byte b) {
return (b < 0) ? 256 + b : b; // Convert byte to unsigned byte.
}
}
class BadIcoResException extends Exception {
public BadIcoResException(String message) {
super(message);
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy