package org.cdlib.xtf.saxonExt.image; /** * Copyright (c) 2007, Regents of the University of California * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * - Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * - 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. * - Neither the name of the University of California 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. */ import java.awt.image.BufferedImage; import java.awt.image.DataBuffer; import java.awt.image.IndexColorModel; import java.awt.image.WritableRaster; import java.io.File; import javax.imageio.ImageIO; import net.sf.saxon.trans.DynamicError; import org.cdlib.xtf.cache.GeneratingCache; /** * Maintain a cache of palette-mapped images used for image highlighting. * * @author Martin Haye */ class ImageCache extends GeneratingCache<String, BufferedImage> { private int outColorBase; /** Construct the cache */ ImageCache(int outColorBase) { super(20, 300); // Keep up to 20 images, for 5 minutes max. this.outColorBase = outColorBase; } /** Read in and map an image */ @Override protected BufferedImage generate(String filename) throws Exception { // Interesting workaround: using BufferedImage normally results in a Window // being created. However, since we're running in a servlet container, this // isn't generally desirable (and often isn't possible.) So we let AWT know // that it's running in "headless" mode, and this prevents the window from // being created. // System.setProperty("java.awt.headless", "true"); // Okay, load the image. BufferedImage bi = ImageIO.read(new File(filename)); bi = remapPalette(bi); return bi; } /** * Remap the colors in the given image, creating space for highlight * versions of the colors. * * @param inImg The image to remap * @return A new image with reduced and normalized palette */ private BufferedImage remapPalette(BufferedImage inImg) throws DynamicError { // Make sure we know how to deal with this image. if (!(inImg.getColorModel() instanceof IndexColorModel)) throw new RuntimeException("image.output can only handle index color (palette) images"); IndexColorModel inCm = (IndexColorModel) inImg.getColorModel(); // First, get the input palette. int nInColors = inCm.getMapSize(); byte[][] inColors = new byte[3][nInColors]; inCm.getReds (inColors[0]); inCm.getGreens(inColors[1]); inCm.getBlues (inColors[2]); // Create the output palette, which is always fixed (for the moment at least). int nOutColors = outColorBase*4; byte[][] outColors = new byte[3][nOutColors]; for (int i=0; i<outColorBase; i++) { // First comes the base color byte greyVal = (byte) (i * 255 / (outColorBase-1)); outColors[0][i + outColorBase*0] = greyVal; // red outColors[1][i + outColorBase*0] = greyVal; // green outColors[2][i + outColorBase*0] = greyVal; // blue // Make a set: white->yellow, black->black outColors[0][i + outColorBase*1] = greyVal; // red outColors[1][i + outColorBase*1] = greyVal; // green outColors[2][i + outColorBase*1] = 0; // blue // Make a set: white->white, black->red byte redMapped = (byte) Math.max(160, ((int)greyVal) & 0xff); outColors[0][i + outColorBase*2] = redMapped; // red outColors[1][i + outColorBase*2] = greyVal; // green outColors[2][i + outColorBase*2] = greyVal; // blue // And finally, a combination of the two outColors[0][i + outColorBase*3] = redMapped; // red outColors[1][i + outColorBase*3] = greyVal; // green outColors[2][i + outColorBase*3] = 0; // blue } // Form the mapping from the input palette to the output byte[] mapping = new byte[nInColors]; for (int i=0; i<nInColors; i++) { int sum = (((int)inColors[0][i]) & 0xff) + (((int)inColors[1][i]) & 0xff) + (((int)inColors[2][i]) & 0xff); mapping[i] = (byte) (sum * (outColorBase-1) / (255*3)); } // Okay, grab the input pixels WritableRaster inRast = inImg.getRaster(); int w = inRast.getWidth(); int h = inRast.getHeight(); if (inRast.getTransferType() != DataBuffer.TYPE_BYTE) throw new RuntimeException("Unrecognized transfer type"); if (inRast.getNumDataElements() != 1) throw new RuntimeException("How could index color have more than one data element?"); byte[] inPixels = new byte[w*h]; inRast.getDataElements(0, 0, w, h, inPixels); // Make the output bitmap IndexColorModel outCm = new IndexColorModel(8, nOutColors, outColors[0], outColors[1], outColors[2]); BufferedImage outImg = new BufferedImage(w, h, BufferedImage.TYPE_BYTE_INDEXED, outCm); WritableRaster outRast = outImg.getRaster(); if (outRast.getTransferType() != DataBuffer.TYPE_BYTE) throw new RuntimeException("Unrecognized transfer type"); if (outRast.getNumDataElements() != 1) throw new RuntimeException("How could index color have more than one data element?"); byte[] outPixels = new byte[w*h]; // Map all the input pixels to their new values. for (int i=0; i<w*h; i++) outPixels[i] = mapping[((int)inPixels[i]) & 0xFF]; outRast.setDataElements(0, 0, w, h, outPixels); // All done! return outImg; } }