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

bdsup2sub.tools.EnhancedPngEncoder Maven / Gradle / Ivy

The newest version!
/*
 * Copyright 2014 J. David Eisenberg / 0xdeadbeef / Miklos Juhasz (mjuhasz)
 *
 * Licensed 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 bdsup2sub.tools;

import java.awt.image.BufferedImage;
import java.awt.image.DataBuffer;
import java.awt.image.IndexColorModel;
import java.awt.image.WritableRaster;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.zip.Deflater;
import java.util.zip.DeflaterOutputStream;

/**
 * @author J. David Eisenberg - modified by 0xdeadbeef (added tRNS tag for transparency in palette mode)
 * @version 1.5, 19 Oct 2003 (modified by 0xdeadbeef in 2009 and mjuhasz in 2012)
 *
 * CHANGES:
* --------
* 19-Sep-2003 : Fix for platforms using EBCDIC (contributed by Paulo Soares);
* 19-Oct-2003 : Change private fields to protected fields so that
* PngEncoderB can inherit them (JDE)
* Fixed bug with calculation of nRows
* Added modifications for unsigned short images
* (contributed by Christian at xpogen.com)
* 10-Nov-2012 : Removed unused constants and useless method override. (mjuhasz) */ public class EnhancedPngEncoder extends PngEncoder { /** PLTE tag. */ private static final byte PLTE[] = { 80, 76, 84, 69 }; /** tRNS tag. */ private static final byte TRNS[] = {0x74, 0x52, 0x4e, 0x53 }; private BufferedImage image; private WritableRaster wRaster; private int tType; public EnhancedPngEncoder() { this(null, false, FILTER_NONE, 0); } /** * Class constructor specifying BufferedImage to encode, with no alpha channel encoding. * * @param image A Java BufferedImage object */ public EnhancedPngEncoder(BufferedImage image) { this(image, false, FILTER_NONE, 0); } /** * Class constructor specifying BufferedImage to encode, and whether to encode alpha. * * @param image A Java BufferedImage object * @param encodeAlpha Encode the alpha channel? false=no; true=yes */ public EnhancedPngEncoder(BufferedImage image, boolean encodeAlpha) { this(image, encodeAlpha, FILTER_NONE, 0); } /** * Class constructor specifying BufferedImage to encode, whether to encode alpha, and filter to use. * * @param image A Java BufferedImage object * @param encodeAlpha Encode the alpha channel? false=no; true=yes * @param whichFilter 0=none, 1=sub, 2=up */ public EnhancedPngEncoder(BufferedImage image, boolean encodeAlpha, int whichFilter) { this(image, encodeAlpha, whichFilter, 0); } /** * Class constructor specifying BufferedImage source to encode, whether to encode alpha, * filter to use, and compression level. * * @param image A Java BufferedImage object * @param encodeAlpha Encode the alpha channel? false=no; true=yes * @param whichFilter 0=none, 1=sub, 2=up * @param compLevel 0..9 */ private EnhancedPngEncoder(BufferedImage image, boolean encodeAlpha, int whichFilter, int compLevel) { this.image = image; this.encodeAlpha = encodeAlpha; setFilter(whichFilter); if (compLevel >=0 && compLevel <= 9) { this.compressionLevel = compLevel; } } /** * Set the BufferedImage to be encoded. * @param image A Java BufferedImage object */ public void setImage(BufferedImage image) { this.image = image; pngBytes = null; } /** * Creates an array of bytes that is the PNG equivalent of the current image. * * @return an array of bytes, or null if there was a problem */ @Override public byte[] pngEncode() { byte[] pngIdBytes = { -119, 80, 78, 71, 13, 10, 26, 10 }; if (image == null) { System.err.println("pngEncode: image is null; returning null"); return null; } width = image.getWidth(null); height = image.getHeight(null); if (!establishStorageInfo()) { System.err.println("pngEncode: cannot establish storage info"); return null; } /* start with an array that is big enough to hold all the pixels * (plus filter bytes), and an extra 200 bytes for header info */ pngBytes = new byte[((width+1) * height * 3) + 200]; /* keep track of largest byte written to the array */ maxPos = 0; bytePos = writeBytes(pngIdBytes, 0); // hdrPos = bytePos; writeHeader(); // dataPos = bytePos; if (writeImageData()) { writeEnd(); pngBytes = resizeByteArray(pngBytes, maxPos); } else { System.err.println("pngEncode: writeImageData failed => null"); pngBytes = null; } return pngBytes; } /** * Get and set variables that determine how picture is stored. * * Retrieves the writable raster of the buffered image, * as well its transfer type. * * Sets number of output bytes per pixel, and, if only * eight-bit bytes, turns off alpha encoding. * @return true if 1-byte or 4-byte data, false otherwise */ boolean establishStorageInfo() { int dataBytes; wRaster = image.getRaster(); dataBytes = wRaster.getNumDataElements(); tType = wRaster.getTransferType(); if (((tType == DataBuffer.TYPE_BYTE) && (dataBytes == 4)) || ((tType == DataBuffer.TYPE_INT) && (dataBytes == 1)) || // on Win 2k/ME, tType == 1, dataBytes == 1 ((tType == DataBuffer.TYPE_USHORT) && (dataBytes == 1))) { bytesPerPixel = (encodeAlpha) ? 4 : 3; } else if ((tType == DataBuffer.TYPE_BYTE) && (dataBytes == 1)) { bytesPerPixel = 1; encodeAlpha = false; // one-byte samples } else { System.err.println("PNG encoder cannot establish storage info:"); System.err.println(" TransferType == " + tType); System.err.println(" NumDataElements == " + dataBytes); return false; } return true; } /** * Write a PNG "IHDR" chunk into the pngBytes array. */ @Override protected void writeHeader() { int startPos; startPos = bytePos = writeInt4(13, bytePos); bytePos = writeBytes(IHDR, bytePos); width = image.getWidth(null); height = image.getHeight(null); bytePos = writeInt4(width, bytePos); bytePos = writeInt4(height, bytePos); bytePos = writeByte(8, bytePos); // bit depth if (bytesPerPixel != 1) { bytePos = writeByte((encodeAlpha) ? 6 : 2, bytePos); // direct model } else { bytePos = writeByte(3, bytePos); // indexed } bytePos = writeByte(0, bytePos); // compression method bytePos = writeByte(0, bytePos); // filter method bytePos = writeByte(0, bytePos); // no interlace crc.reset(); crc.update(pngBytes, startPos, bytePos-startPos); crcValue = crc.getValue(); bytePos = writeInt4((int) crcValue, bytePos); } void writePalette(IndexColorModel icm) { byte[] redPal = new byte[256]; byte[] greenPal = new byte[256]; byte[] bluePal = new byte[256]; byte[] allPal = new byte[768]; int i; icm.getReds(redPal); icm.getGreens(greenPal); icm.getBlues(bluePal); for (i=0; i<256; i++) { allPal[i*3 ] = redPal[i]; allPal[i*3+1] = greenPal[i]; allPal[i*3+2] = bluePal[i]; } bytePos = writeInt4(768, bytePos); bytePos = writeBytes(PLTE, bytePos); crc.reset(); crc.update(PLTE); bytePos = writeBytes(allPal, bytePos); crc.update(allPal); crcValue = crc.getValue(); bytePos = writeInt4((int) crcValue, bytePos); } void writeAlpha(IndexColorModel icm) { byte[] alpha = new byte[256]; icm.getAlphas(alpha); bytePos = writeInt4(256, bytePos); bytePos = writeBytes(TRNS, bytePos); crc.reset(); crc.update(TRNS); bytePos = writeBytes(alpha, bytePos); crc.update(alpha); crcValue = crc.getValue(); bytePos = writeInt4((int) crcValue, bytePos); } /** * Write the image data into the pngBytes array. * This will write one or more PNG "IDAT" chunks. In order * to conserve memory, this method grabs as many rows as will * fit into 32K bytes, or the whole image; whichever is less. * * * @return true if no errors; false if error grabbing pixels */ @Override protected boolean writeImageData() { int rowsLeft = height; // number of rows remaining to write int startRow = 0; // starting row to process this time through int nRows; // how many rows to grab at a time byte[] scanLines; // the scan lines to be compressed int scanPos; // where we are in the scan lines int startPos; // where this line's actual pixels start (used for filtering) int readPos; // position from which source pixels are read byte[] compressedLines; // the resultant compressed lines int nCompressed; // how big is the compressed area? byte[] pixels; // storage area for byte-sized pixels int[] iPixels; // storage area for int-sized pixels short[] sPixels; // for Win 2000/ME ushort pixels final int type = image.getType(); // TYPE_INT_RGB = 1 // TYPE_INT_ARGB = 2 // TYPE_INT_ARGB_PRE = 3 // TYPE_INT_BGR = 4 // TYPE_3BYTE_BGR = 5 // TYPE_4BYTE_ABGR = 6 // TYPE_4BYTE_ABGR_PRE = 7 // TYPE_BYTE_GRAY = 10 // TYPE_BYTE_BINARY = 12 // TYPE_BYTE_INDEXED = 13 // TYPE_USHORT_GRAY = 11 // TYPE_USHORT_565_RGB = 8 // TYPE_USHORT_555_RGB = 9 // TYPE_CUSTOM = 0. Deflater scrunch = new Deflater(compressionLevel); ByteArrayOutputStream outBytes = new ByteArrayOutputStream(1024); DeflaterOutputStream compBytes = new DeflaterOutputStream(outBytes, scrunch); if (bytesPerPixel == 1) { writePalette((IndexColorModel) image.getColorModel()); writeAlpha((IndexColorModel) image.getColorModel()); } try { while (rowsLeft > 0) { nRows = Math.min(32767 / (width*(bytesPerPixel+1)), rowsLeft); nRows = Math.max(nRows, 1); /* * Create a data chunk. scanLines adds "nRows" for * the filter bytes. */ scanLines = new byte[width * nRows * bytesPerPixel + nRows]; if (filter == FILTER_SUB) { leftBytes = new byte[16]; } if (filter == FILTER_UP) { priorRow = new byte[width*bytesPerPixel]; } final Object data = wRaster.getDataElements(0, startRow, width, nRows, null); pixels = null; iPixels = null; sPixels = null; if (tType == DataBuffer.TYPE_BYTE) { pixels = (byte[]) data; } else if (tType == DataBuffer.TYPE_INT) { iPixels = (int[]) data; } else if (tType == DataBuffer.TYPE_USHORT) { sPixels = (short[]) data; } scanPos = 0; readPos = 0; startPos = 1; for (int i=0; i> 8) & 0xf8); scanLines[scanPos++] = (byte) ((pxl >> 2) & 0xfc); } else { // assume USHORT_555_RGB scanLines[scanPos++] = (byte) ((pxl >> 7) & 0xf8); scanLines[scanPos++] = (byte) ((pxl >> 2) & 0xf8); } scanLines[scanPos++] = (byte) ((pxl << 3) & 0xf8); } else { // assume tType INT and type RGB or ARGB int pxl = iPixels[readPos++]; scanLines[scanPos++] = (byte) ((pxl >> 16) & 0xff); scanLines[scanPos++] = (byte) ((pxl >> 8) & 0xff); scanLines[scanPos++] = (byte) ((pxl ) & 0xff); if (encodeAlpha) { scanLines[scanPos++] = (byte) ((pxl >> 24) & 0xff); } } if ((i % width == width-1) && (filter != FILTER_NONE)) { if (filter == FILTER_SUB) { filterSub(scanLines, startPos, width); } if (filter == FILTER_UP) { filterUp(scanLines, startPos, width); } } } /* * Write these lines to the output area */ compBytes.write(scanLines, 0, scanPos); startRow += nRows; rowsLeft -= nRows; } compBytes.close(); /* * Write the compressed bytes */ compressedLines = outBytes.toByteArray(); nCompressed = compressedLines.length; crc.reset(); bytePos = writeInt4(nCompressed, bytePos); bytePos = writeBytes(IDAT, bytePos); crc.update(IDAT); bytePos = writeBytes(compressedLines, nCompressed, bytePos); crc.update(compressedLines, 0, nCompressed); crcValue = crc.getValue(); bytePos = writeInt4((int) crcValue, bytePos); scrunch.finish(); return true; } catch (IOException e) { System.err.println(e.toString()); return false; } } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy