package com.fr.third.JAI; /* * Copyright (c) 2001 Sun Microsystems, Inc. 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. * * -Redistribution in binary form must reproduct 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 Sun Microsystems, Inc. or the names of contributors may * be used to endorse or promote products derived from this software without * specific prior written permission. * * This software is provided "AS IS," without a warranty of any kind. ALL * EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES, INCLUDING ANY * IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE OR * NON-INFRINGEMENT, ARE HEREBY EXCLUDED. SUN AND ITS LICENSORS SHALL NOT BE * LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF USING, MODIFYING * OR DISTRIBUTING THE SOFTWARE OR ITS DERIVATIVES. IN NO EVENT WILL SUN OR ITS * LICENSORS BE LIABLE FOR ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, * INDIRECT, SPECIAL, CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER * CAUSED AND REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF * OR INABILITY TO USE SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE * POSSIBILITY OF SUCH DAMAGES. * * You acknowledge that Software is not designed,licensed or intended for use in * the design, construction, operation or maintenance of any nuclear facility. */ import java.awt.Rectangle; import java.awt.image.ColorModel; import java.awt.image.ComponentSampleModel; import java.awt.image.DataBuffer; import java.awt.image.DataBufferByte; import java.awt.image.IndexColorModel; import java.awt.image.MultiPixelPackedSampleModel; import java.awt.image.Raster; import java.awt.image.RenderedImage; import java.awt.image.SampleModel; import java.io.IOException; import java.io.OutputStream; /** * An ImageEncoder for the PNM family of file formats. * *

The PNM file format includes PBM for monochrome images, PGM for * grey scale images, and PPM for color images. When writing the * source data out, the encoder chooses the appropriate file variant * based on the actual SampleModel of the source image. In case the * source image data is unsuitable for the PNM file format, for * example when source has 4 bands or float data type, the encoder * throws an Error. * *

The raw file format is used wherever possible, unless the * PNMEncodeParam object supplied to the constructor returns * true from its getRaw() method. * * */ public class PNMImageEncoder extends ImageEncoderImpl { private static final int PBM_ASCII = '1'; private static final int PGM_ASCII = '2'; private static final int PPM_ASCII = '3'; private static final int PBM_RAW = '4'; private static final int PGM_RAW = '5'; private static final int PPM_RAW = '6'; private static final int SPACE = ' '; private static final String COMMENT = "# written by com.sun.media.jai.codecimpl.PNMImageEncoder"; private byte[] lineSeparator; private int variant; private int maxValue; public PNMImageEncoder(OutputStream output, ImageEncodeParam param) { super(output, param); if (this.param == null) { this.param = new PNMEncodeParam(); } } /** * Encodes a RenderedImage and writes the output to the * OutputStream associated with this ImageEncoder. */ public void encode(RenderedImage im) throws IOException { int minX = im.getMinX(); int minY = im.getMinY(); int width = im.getWidth(); int height = im.getHeight(); int tileHeight = im.getTileHeight(); SampleModel sampleModel = im.getSampleModel(); ColorModel colorModel = im.getColorModel(); String ls = (String)java.security.AccessController.doPrivileged( new sun.security.action.GetPropertyAction("line.separator")); lineSeparator = ls.getBytes(); int dataType = sampleModel.getTransferType(); if ((dataType == DataBuffer.TYPE_FLOAT) || (dataType == DataBuffer.TYPE_DOUBLE)) { throw new RuntimeException(JaiI18N.getString("PNMImageEncoder0")); } // Raw data can only handle bytes, everything greater must be ASCII. int[] sampleSize = sampleModel.getSampleSize(); int numBands = sampleModel.getNumBands(); // Colormap populated for non-bilevel IndexColorModel only. byte[] reds = null; byte[] greens = null; byte[] blues = null; // Flag indicating that PB data should be inverted before writing. boolean isPBMInverted = false; if (numBands == 1) { if (colorModel instanceof IndexColorModel) { IndexColorModel icm = (IndexColorModel)colorModel; int mapSize = icm.getMapSize(); if (mapSize < (1 << sampleSize[0])) { throw new RuntimeException( JaiI18N.getString("PNMImageEncoder1")); } if(sampleSize[0] == 1) { variant = PBM_RAW; // Set PBM inversion flag if 1 maps to a higher color // value than 0: PBM expects white-is-zero so if this // does not obtain then inversion needs to occur. isPBMInverted = (icm.getRed(1) + icm.getGreen(1) + icm.getBlue(1)) > (icm.getRed(0) + icm.getGreen(0) + icm.getBlue(0)); } else { variant = PPM_RAW; reds = new byte[mapSize]; greens = new byte[mapSize]; blues = new byte[mapSize]; icm.getReds(reds); icm.getGreens(greens); icm.getBlues(blues); } } else if (sampleSize[0] == 1) { variant = PBM_RAW; } else if (sampleSize[0] <= 8) { variant = PGM_RAW; } else { variant = PGM_ASCII; } } else if (numBands == 3) { if (sampleSize[0] <= 8 && sampleSize[1] <= 8 && sampleSize[2] <= 8) { // all 3 bands must be <= 8 variant = PPM_RAW; } else { variant = PPM_ASCII; } } else { throw new RuntimeException(JaiI18N.getString("PNMImageEncoder2")); } // Read parameters if (((PNMEncodeParam)param).getRaw()) { if (!isRaw(variant)) { boolean canUseRaw = true; // Make sure sampleSize for all bands no greater than 8. for (int i = 0; i < sampleSize.length; i++) { if (sampleSize[i] > 8) { canUseRaw = false; break; } } if (canUseRaw) { variant += 0x3; } } } else { if (isRaw(variant)) { variant -= 0x3; } } maxValue = (1 << sampleSize[0]) - 1; // Write PNM file. output.write('P'); // magic value output.write(variant); output.write(lineSeparator); output.write(COMMENT.getBytes()); // comment line output.write(lineSeparator); writeInteger(output, width); // width output.write(SPACE); writeInteger(output, height); // height // Writ esample max value for non-binary images if ((variant != PBM_RAW) && (variant != PBM_ASCII)) { output.write(lineSeparator); writeInteger(output, maxValue); } // The spec allows a single character between the // last header value and the start of the raw data. if (variant == PBM_RAW || variant == PGM_RAW || variant == PPM_RAW) { output.write('\n'); } // Set flag for optimal image writing case: row-packed data with // correct band order if applicable. boolean writeOptimal = false; if (variant == PBM_RAW && sampleModel.getTransferType() == DataBuffer.TYPE_BYTE && sampleModel instanceof MultiPixelPackedSampleModel) { MultiPixelPackedSampleModel mppsm = (MultiPixelPackedSampleModel)sampleModel; // Must have left-aligned bytes with unity bit stride. if(mppsm.getDataBitOffset() == 0 && mppsm.getPixelBitStride() == 1) { writeOptimal = true; } } else if ((variant == PGM_RAW || variant == PPM_RAW) && sampleModel instanceof ComponentSampleModel && !(colorModel instanceof IndexColorModel)) { ComponentSampleModel csm = (ComponentSampleModel)sampleModel; // Pixel stride must equal band count. if(csm.getPixelStride() == numBands) { writeOptimal = true; // Band offsets must equal band indices. if(variant == PPM_RAW) { int[] bandOffsets = csm.getBandOffsets(); for(int b = 0; b < numBands; b++) { if(bandOffsets[b] != b) { writeOptimal = false; break; } } } } } // Write using an optimal approach if possible. if(writeOptimal) { int bytesPerRow = variant == PBM_RAW ? (width + 7)/8 : width*sampleModel.getNumBands(); int numYTiles = im.getNumYTiles(); Rectangle stripRect = new Rectangle(im.getMinX(), im.getMinY(), im.getWidth(), im.getTileHeight()); byte[] invertedData = null; if(isPBMInverted) { invertedData = new byte[bytesPerRow]; } // Loop over tiles to minimize cobbling. for(int j = 0; j < numYTiles; j++) { // Clamp the strip to the image bounds. if(j == numYTiles - 1) { stripRect.height = im.getHeight() - stripRect.y; } // Get a strip of data. Raster strip = im.getData(stripRect); // Get the data array. byte[] bdata = ((DataBufferByte)strip.getDataBuffer()).getData(); // Get the scanline stride. int rowStride = variant == PBM_RAW ? ((MultiPixelPackedSampleModel)sampleModel).getScanlineStride() : ((ComponentSampleModel)sampleModel).getScanlineStride(); if(rowStride == bytesPerRow && !isPBMInverted) { // Write the entire strip at once. output.write(bdata, 0, bdata.length); } else { // Write the strip row-by-row. int offset = 0; for(int i = 0; i < tileHeight; i++) { if(isPBMInverted) { for(int k = 0; k < bytesPerRow; k++) { invertedData[k] = (byte)(~(bdata[offset+k]&0xff)); } output.write(invertedData, 0, bytesPerRow); } else { output.write(bdata, offset, bytesPerRow); } offset += rowStride; } } // Increment the strip origin. stripRect.y += tileHeight; } // Write all buffered bytes and return. output.flush(); return; } // Buffer for up to 8 rows of pixels int[] pixels = new int[8*width*numBands]; // Also allocate a buffer to hold the data to be written to the file, // so we can use array writes. byte[] bpixels = reds == null ? new byte[8*width*numBands] : new byte[8*width*3]; // The index of the sample being written, used to // place a line separator after every 16th sample in // ASCII mode. Not used in raw mode. int count = 0; // Process 8 rows at a time so all but the last will have // a multiple of 8 pixels. This simplifies PBM_RAW encoding. int lastRow = minY + height; for (int row = minY; row < lastRow; row += 8) { int rows = Math.min(8, lastRow - row); int size = rows*width*numBands; // Grab the pixels Raster src = im.getData(new Rectangle(minX, row, width, rows)); src.getPixels(minX, row, width, rows, pixels); // Invert bits if necessary. if(isPBMInverted) { for(int k = 0; k < size; k++) { pixels[k] ^= 0x00000001; } } switch (variant) { case PBM_ASCII: case PGM_ASCII: for (int i = 0; i < size; i++) { if ((count++ % 16) == 0) { output.write(lineSeparator); } else { output.write(SPACE); } writeInteger(output, pixels[i]); } output.write(lineSeparator); break; case PPM_ASCII: if (reds == null) { // no need to expand for (int i = 0; i < size; i++) { if ((count++ % 16) == 0) { output.write(lineSeparator); } else { output.write(SPACE); } writeInteger(output, pixels[i]); } } else { for (int i = 0; i < size; i++) { if ((count++ % 16) == 0) { output.write(lineSeparator); } else { output.write(SPACE); } writeInteger(output, (reds[pixels[i]] & 0xFF)); output.write(SPACE); writeInteger(output, (greens[pixels[i]] & 0xFF)); output.write(SPACE); writeInteger(output, (blues[pixels[i]] & 0xFF)); } } output.write(lineSeparator); break; case PBM_RAW: // 8 pixels packed into 1 byte, the leftovers are padded. int kdst = 0; int ksrc = 0; for (int i = 0; i < size/8; i++) { int b = (pixels[ksrc++] << 7) | (pixels[ksrc++] << 6) | (pixels[ksrc++] << 5) | (pixels[ksrc++] << 4) | (pixels[ksrc++] << 3) | (pixels[ksrc++] << 2) | (pixels[ksrc++] << 1) | pixels[ksrc++]; bpixels[kdst++] = (byte)b; } // Leftover pixels, only possible at the end of the file. if (size%8 > 0) { int b = 0; for (int i=0; i= PBM_RAW); } }