You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
472 lines
18 KiB
472 lines
18 KiB
5 years ago
|
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.
|
||
|
*
|
||
|
* <p> 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.
|
||
|
*
|
||
|
* <p> The raw file format is used wherever possible, unless the
|
||
|
* PNMEncodeParam object supplied to the constructor returns
|
||
|
* <code>true</code> from its <code>getRaw()</code> 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<size%8; i++) {
|
||
|
b |= pixels[size + i] << (7 - i);
|
||
|
}
|
||
|
bpixels[kdst++] = (byte)b;
|
||
|
}
|
||
|
output.write(bpixels, 0, (size+7)/8);
|
||
|
|
||
|
break;
|
||
|
|
||
|
case PGM_RAW:
|
||
|
for(int i=0; i<size; i++) {
|
||
|
bpixels[i] = (byte)(pixels[i]);
|
||
|
}
|
||
|
output.write(bpixels, 0, size);
|
||
|
break;
|
||
|
|
||
|
case PPM_RAW:
|
||
|
if (reds == null) { // no need to expand
|
||
|
for (int i=0; i<size; i++) {
|
||
|
bpixels[i] = (byte)(pixels[i] & 0xFF);
|
||
|
}
|
||
|
} else {
|
||
|
for (int i=0, j=0; i<size; i++) {
|
||
|
bpixels[j++] = reds[pixels[i]];
|
||
|
bpixels[j++] = greens[pixels[i]];
|
||
|
bpixels[j++] = blues[pixels[i]];
|
||
|
}
|
||
|
}
|
||
|
output.write(bpixels, 0, bpixels.length);
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Force all buffered bytes to be written out.
|
||
|
output.flush();
|
||
|
}
|
||
|
|
||
|
/** Writes an integer to the output in ASCII format. */
|
||
|
private void writeInteger(OutputStream output, int i) throws IOException {
|
||
|
output.write(Integer.toString(i).getBytes());
|
||
|
}
|
||
|
|
||
|
/** Writes a byte to the output in ASCII format. */
|
||
|
private void writeByte(OutputStream output, byte b) throws IOException {
|
||
|
output.write(Byte.toString(b).getBytes());
|
||
|
}
|
||
|
|
||
|
/** Returns true if file variant is raw format, false if ASCII. */
|
||
|
private boolean isRaw(int v) {
|
||
|
return (v >= PBM_RAW);
|
||
|
}
|
||
|
}
|