All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.apache.myfaces.trinidadinternal.image.encode.PNGEncoder Maven / Gradle / Ivy

The newest version!
/*
 *  Licensed to the Apache Software Foundation (ASF) under one
 *  or more contributor license agreements.  See the NOTICE file
 *  distributed with this work for additional information
 *  regarding copyright ownership.  The ASF licenses this file
 *  to you under the Apache License, Version 2.0 (the
 *  "License"); you may not use this file except in compliance
 *  with the License.  You may obtain a copy of the License at
 * 
 *  http://www.apache.org/licenses/LICENSE-2.0
 * 
 *  Unless required by applicable law or agreed to in writing,
 *  software distributed under the License is distributed on an
 *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 *  KIND, either express or implied.  See the License for the
 *  specific language governing permissions and limitations
 *  under the License.
 */
package org.apache.myfaces.trinidadinternal.image.encode;

import java.awt.Color;
import java.awt.Image;
import java.awt.image.ImageObserver;
import java.awt.image.PixelGrabber;
import java.io.ByteArrayOutputStream;
import java.io.OutputStream;
import java.io.IOException;
import java.util.Map;
import java.util.Iterator;
import java.util.Hashtable;
import java.util.zip.CRC32;
import java.util.zip.DeflaterOutputStream;

import org.apache.myfaces.trinidadinternal.image.painter.ImageLoader;
import org.apache.myfaces.trinidad.logging.TrinidadLogger;

/**
 * Generates a PNG graphics file given pixel data.
 * 

* @version $Name: $ ($Revision: adfrt/faces/adf-faces-impl/src/main/java/oracle/adfinternal/view/faces/image/encode/PNGEncoder.java#0 $) $Date: 10-nov-2005.19:05:21 $ * @since 0.1.4 */ final class PNGEncoder { // =-=ags Todo: Support 2-bit, 4-bit pixel depth for palette images. // Also, crank up Deflater compression level /** * Encodes the Image to the specified OutputStream in PNG format */ public static void encode(Image image, OutputStream out) throws IOException { // First make sure the image is loaded ImageLoader loader = new ImageLoader(image); loader.start(); if(!loader.waitFor()) { throw new IllegalArgumentException(_LOG.getMessage( "PROBLEM_LOADING")); } int width = image.getWidth(loader); int height = image.getHeight(loader); int[] pixels = new int[width*height]; // all the image's pixels // Use a PixelGrabber to get the pixel values PixelGrabber grabber = new PixelGrabber(image.getSource(), 0, 0, width, height, pixels, 0, width); try { grabber.grabPixels(); } catch (InterruptedException e) { throw new IllegalArgumentException(_LOG.getMessage( "GRABBING_PIXELS")); } if ((grabber.getStatus() & ImageObserver.ABORT) != 0) { throw new IllegalArgumentException(_LOG.getMessage( "ERROR_FETCHING_IMAGE", new Object[]{pixels.length, width, height})); } // -= Simon Lessard =- // FIXME: HashMap please if synchronization is not required... Hashtable colors = new Hashtable(); int count = 0; Color lastColor = null; // Use -2 instead of -1 for last pixel because -1 is tranparent white int lastPixel = -2; // We currently only support a single fully-transparent Color, due to // limitations with browser transparency support. We place the first // fully transparent color that we find at index zero in the palette, // so that we can have a single entry in the tRNS chunk. Color firstColor = _createColor(pixels[0]); Color transparentColor = null; for (int i = 0; i < pixels.length; i++) { int pixel = pixels[i]; if ((pixel != lastPixel) || (lastColor == null)) { Color color = _createColor(pixel); if (!colors.containsKey(color)) { // Check for first transparent color if ((color.getAlpha() == 0) && (transparentColor == null)) { // Put the transparent color at index zero. // Put the old zero index color at the current index. colors.put(color, 0); colors.put(firstColor, count); transparentColor = color; } else { colors.put(color, count); } count++; } lastPixel = pixel; lastColor = color; } } boolean transparent = (transparentColor != null); // Write the PNG signature _writeSignature(out); // Write the IHDR and IDAT chunks. If we have 256 colors or less, we // can use a color palette, otherwise we write out RGB triplets if (count <= 256) _writePaletteImage(width, height, pixels, colors, transparent, out); else _writeRGBImage(width, height, pixels, out); // Write the IEND chunk _writeEnd(out); } private PNGEncoder() {} // Write out header and image data as palette image private static void _writePaletteImage( int width, int height, int[] pixels, Map colors, boolean transparent, OutputStream out ) throws IOException { int count = colors.size(); assert (count <= 256); // Before we write the header, we need to determine the optimal bit depth // =-=ags Just hardcode 8 for now // int depth = (count <= 4) ? 2 (count <= 16) ? 4 : 8; int depth = 8; // Write out the IHDR chunk _writeHeader(width, height, (byte)depth, (byte)3, out); // Write out the PLTE chunk _writePalette(colors, out); // Write out the tRNS chunk if (transparent) { _writeTransparency(out); } // Convert the pixels array into an array of palette indices. byte[] data = _getIndexedData(width, height, depth, pixels, colors); // Write out the IDAT chunk _writeData(data, out); } // Write out header and image data as RGB triplets private static void _writeRGBImage( int width, int height, int[] pixels, OutputStream out ) throws IOException { _writeHeader(width, height, (byte)8, (byte)2, out); // Convert pixels to an array of RGB byte triplets. Each scanline // starts with a filter byte of zero. byte[] data = new byte[(pixels.length * 3) + height]; int sourceLine = 0; // Offset of the source scan line int targetLine = 0; // Offset of the target scan line for (int i = 0; i < height; i++) { // Write out the filter byte for this scan line data[targetLine] = (byte)0; for (int j = 0; j < width; j++) { int pixel = pixels[sourceLine + j]; int target = targetLine + (j * 3) + 1; data[target] = _getRed(pixel); data[target + 1] = _getGreen(pixel); data[target + 2] = _getBlue(pixel); } sourceLine += width; targetLine += ((width * 3) + 1); } _writeData(data, out); } // Write out the PNG IDAT chunk private static void _writeData( byte[] data, OutputStream out ) throws IOException { // Compress the data ByteArrayOutputStream baos = new ByteArrayOutputStream(); OutputStream deflater = new DeflaterOutputStream(baos); deflater.write(data); deflater.flush(); deflater.close(); // Write out the IDAT chunk _writeChunk(_IDAT, baos.toByteArray(), out); } // Write out the PNG IEND chunk private static void _writeEnd(OutputStream out) throws IOException { _writeChunk(_IEND, null, out); } // Write out the PNG IHDR chunk private static void _writeHeader( int width, int height, byte depth, byte type, OutputStream out ) throws IOException { byte[] data = new byte[13]; // Write the width/height _writeInt(width, 0, data); _writeInt(height, 4, data); data[8] = depth; // bit depth data[9] = type; // color type data[10] = (byte)0; // Compression method data[11] = (byte)0; // Filter method data[12] = (byte)0; // Interlace method _writeChunk(_IHDR, data, out); } // Write out the PLTE chunk private static void _writePalette( Map colors, OutputStream out ) throws IOException { // Convert the colors Map into the palette data, sorted by index int count = colors.size(); byte[] data = new byte[count * 3]; for (Map.Entry entry : colors.entrySet()) { Color color = entry.getKey(); int index = entry.getValue().intValue() * 3; int rgb = color.getRGB(); data[index] = _getRed(rgb); data[index + 1] = _getGreen(rgb); data[index + 2] = _getBlue(rgb); } _writeChunk(_PLTE, data, out); } // Write out the PNG signature private static void _writeSignature(OutputStream out) throws IOException { out.write(_SIGNATURE); } // Write out the tRNS chunk if neccessary private static void _writeTransparency( OutputStream out ) throws IOException { // We only support a single fully transparent pixel, which we force // into palette index zero so that we can have a single entry in our // tRNS chunk. _writeChunk(_tRNS, _TRANSPARENT_DATA, out); } private static void _writeChunk( int type, byte[] data, OutputStream out ) throws IOException { int length = (data == null) ? 0 : data.length; _writeInt(length, out); _writeInt(type, out); if (data != null) out.write(data); _write32(_getCRC(type, data), out); } // Write an MSB int private static void _writeInt(int i, OutputStream out) throws IOException { out.write((i >> 24) & 0x000000ff); out.write((i >> 16) & 0x000000ff); out.write((i >> 8) & 0x000000ff); out.write(i & 0x000000ff); } // Writes an MSB int into the data buffer at the specified offset private static void _writeInt(int i, int offset, byte[]buffer) throws IOException { buffer[offset] = (byte)((i >> 24) & 0x000000ff); buffer[offset + 1] = (byte)((i >> 16) & 0x000000ff); buffer[offset + 2] = (byte)((i >> 8) & 0x000000ff); buffer[offset + 3] = (byte)(i & 0x000000ff); } // Write 32 bits from a long private static void _write32(long l, OutputStream out) throws IOException { out.write((int)(l >> 24) & 0x000000ff); out.write((int)(l >> 16) & 0x000000ff); out.write((int)(l >> 8) & 0x000000ff); out.write((int)l & 0x000000ff); } // Computes the CRC for the given type and data private static long _getCRC(int type, byte[] data) { CRC32 crc = new CRC32(); // First add in the type bytes crc.update((type >> 24) & 0x000000ff); crc.update((type >> 16) & 0x000000ff); crc.update((type >> 8) & 0x000000ff); crc.update(type & 0x000000ff); // Now, add in the data if (data != null) crc.update(data); return crc.getValue(); } // Converts the pixels array into palette indices. // Also, inserts the filter byte at the start of each scanline, // so the data is ready for compression private static byte[] _getIndexedData( int width, int height, int depth, int[] pixels, Map colors ) { // =-=ags At the moment we only support 8-bit pixel depths // if (depth == 2) // return _getIndexedData2(width, height, pixels, colors); // if (depth == 4) // return _getIndexedData4(width, height, pixels, colors); assert (depth == 8); return _getIndexedData8(width, height, pixels, colors); } // 2-bit version of _getIndexedData() :-) // private static byte[] _getIndexedData2( // int width, // int height, // int[] pixels, // Dictionary colors // ) // { // if (Assert.DEBUG) Assert.assertion(false); // return null; // } // 4-bit version of _getIndexedData() :-) // private static byte[] _getIndexedData4( // int width, // int height, // int[] pixels, // Dictionary colors // ) // { // if (Assert.DEBUG) Assert.assertion(false); // return null; // } // 8-bit version of _getIndexedData() private static byte[] _getIndexedData8( int width, int height, int[] pixels, Map colors ) { // Convert pixels to an array of palette indices. Each scanline // starts with a filter byte of zero. byte[] data = new byte[pixels.length + height]; int sourceLine = 0; // Offset of the source scan line int targetLine = 0; // Offset of the target scan line // Track last pixel/Color as an optimization Color lastColor = null; // Use -2 instead of -1 for last pixel because -1 is tranparent white int lastPixel = -2; for (int i = 0; i < height; i++) { // Write out the filter byte for this scan line data[targetLine] = (byte)0; for (int j = 0; j < width; j++) { int pixel = pixels[sourceLine + j]; Color color; if ((pixel == lastPixel) && (lastColor != null)) color = lastColor; else color = _createColor(pixel); int index = colors.get(color).intValue(); data[targetLine + j + 1] = (byte)index; lastPixel = pixel; lastColor = color; } sourceLine += width; targetLine += (width + 1); } return data; } // Creates a Color, preserving fully transparent alpha values private static final Color _createColor(int argb) { if ((argb & 0xff000000) != 0) return new Color(argb | 0xff000000); // If we're fully tranparent, make sure that we preserve the alpha value int a = ((argb >> 24) & 0xff); int r = ((argb >> 16) & 0xff); int g = ((argb >> 8) & 0xff); int b = (argb & 0xff); return new Color(r, g, b, a); } // convenience methods for getting red, green, blue elements from an int private static byte _getRed(int c) { return (byte)((c>>16)&255); } private static byte _getGreen(int c) { return (byte)((c>>8)&255); } private static byte _getBlue(int c) { return (byte)(c&255); } // The PNG signature private static final byte[] _SIGNATURE = { (byte)137, // non-ascii (byte)80, // 'P' (byte)78, // 'N' (byte)71, // 'G' (byte)13, // '\r' (byte)10, // '\n' (byte)26, // control-Z (byte)10 // '\n' }; private static final int _IHDR = 0x49484452; private static final int _PLTE = 0x504c5445; private static final int _IDAT = 0x49444154; private static final int _IEND = 0x49454e44; private static final int _tRNS = 0x74524e53; private static final byte[] _TRANSPARENT_DATA = new byte[] { (byte)0 }; private static final TrinidadLogger _LOG = TrinidadLogger.createTrinidadLogger( PNGEncoder.class); }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy