帆软使用的第三方框架。
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.

907 lines
23 KiB

/*
* 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.
*/
package com.fr.third.JAI;
import java.io.OutputStream;
import java.io.IOException;
import java.awt.image.Raster;
import java.awt.image.DataBuffer;
import java.awt.image.RenderedImage;
import java.awt.image.SampleModel;
import java.awt.Rectangle;
import java.awt.image.ColorModel;
import java.awt.image.IndexColorModel;
/**
* An ImageEncoder for the various versions of the BMP image file format.
*
* Unless specified otherwise by the BMPDecodeParam object passed to the
* constructor, Version 3 will be the default version used.
*
* <p>If the image to be encoded has an IndexColorModel and can be encoded
* using upto 8 bits per pixel, the image will be written out as a Palette
* color image with an appropriate number of bits per pixel. For example an
* image having a 256 color IndexColorModel will be written out as a Palette
* image with 8 bits per pixel while one with a 16 color palette will be
* written out as a Palette image with 4 bits per pixel. For all other images,
* the 24 bit image format will be used.
*
*
*/
public class BMPImageEncoder extends ImageEncoderImpl {
private OutputStream output;
private int version;
private boolean isCompressed, isTopDown;
private int w, h;
private int compImageSize = 0;
/**
* An ImageEncoder for the BMP file format.
*
* @param output The OutputStream to write to.
* @param param The BMPEncodeParam object.
*/
public BMPImageEncoder(OutputStream output, ImageEncodeParam param) {
super(output, param);
this.output = output;
BMPEncodeParam bmpParam;
if (param == null) {
// Use default valued BMPEncodeParam
bmpParam = new BMPEncodeParam();
} else {
bmpParam = (BMPEncodeParam)param;
}
this.version = bmpParam.getVersion();
this.isCompressed = bmpParam.isCompressed();
if(isCompressed && !(output instanceof SeekableOutputStream)){
throw new
IllegalArgumentException(JaiI18N.getString("BMPImageEncoder6"));
}
this.isTopDown = bmpParam.isTopDown();
}
/**
* Encodes a RenderedImage and writes the output to the
* OutputStream associated with this ImageEncoder.
*/
public void encode(RenderedImage im) throws IOException {
// Get image dimensions
int minX = im.getMinX();
int minY = im.getMinY();
w = im.getWidth();
h = im.getHeight();
// Default is using 24 bits per pixel.
int bitsPerPixel = 24;
boolean isPalette = false;
int paletteEntries = 0;
IndexColorModel icm = null;
SampleModel sm = im.getSampleModel();
int numBands = sm.getNumBands();
ColorModel cm = im.getColorModel();
if (numBands != 1 && numBands != 3) {
throw new
IllegalArgumentException(JaiI18N.getString("BMPImageEncoder1"));
}
int sampleSize[] = sm.getSampleSize();
if (sampleSize[0] > 8) {
throw new RuntimeException(JaiI18N.getString("BMPImageEncoder2"));
}
for (int i=1; i<sampleSize.length; i++) {
if (sampleSize[i] != sampleSize[0]) {
throw
new RuntimeException(JaiI18N.getString("BMPImageEncoder3"));
}
}
// Float and Double data cannot be written in a BMP format.
int dataType = sm.getTransferType();
if ((dataType == DataBuffer.TYPE_USHORT) ||
(dataType == DataBuffer.TYPE_SHORT) ||
(dataType == DataBuffer.TYPE_INT) ||
(dataType == DataBuffer.TYPE_FLOAT) ||
(dataType == DataBuffer.TYPE_DOUBLE)) {
throw new RuntimeException(JaiI18N.getString("BMPImageEncoder0"));
}
// Number of bytes that a scanline for the image written out will have.
int destScanlineBytes = w * numBands;
int compression = 0;
byte r[] = null, g[] = null, b[] = null, a[] = null;
if (cm instanceof IndexColorModel) {
isPalette = true;
icm = (IndexColorModel)cm;
paletteEntries = icm.getMapSize();
if (paletteEntries <= 2) {
bitsPerPixel = 1;
destScanlineBytes = (int)Math.ceil((double)w/8.0);
} else if (paletteEntries <= 16) {
bitsPerPixel = 4;
destScanlineBytes = (int)Math.ceil((double)w/2.0);
} else if (paletteEntries <= 256) {
bitsPerPixel = 8;
} else {
// Cannot be written as a Palette image. So write out as
// 24 bit image.
bitsPerPixel = 24;
isPalette = false;
paletteEntries = 0;
destScanlineBytes = w * 3;
}
if (isPalette == true) {
r = new byte[paletteEntries];
g = new byte[paletteEntries];
b = new byte[paletteEntries];
a = new byte[paletteEntries];
icm.getAlphas(a);
icm.getReds(r);
icm.getGreens(g);
icm.getBlues(b);
}
} else {
// Grey scale images
if (numBands == 1) {
isPalette = true;
paletteEntries = 256;
// int sampleSize[] = sm.getSampleSize();
bitsPerPixel = sampleSize[0];
destScanlineBytes = (int)Math.ceil((double)(w * bitsPerPixel) /
8.0);
r = new byte[256];
g = new byte[256];
b = new byte[256];
a = new byte[256];
for (int i = 0; i < 256; i++) {
r[i] = (byte)i;
g[i] = (byte)i;
b[i] = (byte)i;
a[i] = (byte)i;
}
}
}
// actual writing of image data
int fileSize = 0;
int offset = 0;
int headerSize = 0;
int imageSize = 0;
int xPelsPerMeter = 0;
int yPelsPerMeter = 0;
int colorsUsed = 0;
int colorsImportant = paletteEntries;
int padding = 0;
// Calculate padding for each scanline
int remainder = destScanlineBytes % 4;
if (remainder != 0) {
padding = 4 - remainder;
}
switch (version) {
case BMPEncodeParam.VERSION_2:
offset = 26 + paletteEntries * 3;
headerSize = 12;
imageSize = (destScanlineBytes + padding) * h;
fileSize = imageSize + offset;
throw new
RuntimeException(JaiI18N.getString("BMPImageEncoder5"));
//break;
case BMPEncodeParam.VERSION_3:
// FileHeader is 14 bytes, BitmapHeader is 40 bytes,
// add palette size and that is where the data will begin
if (isCompressed && bitsPerPixel == 8) {
compression = 1;
} else if (isCompressed && bitsPerPixel == 4) {
compression = 2;
}
offset = 54 + paletteEntries * 4;
imageSize = (destScanlineBytes + padding) * h;
fileSize = imageSize + offset;
headerSize = 40;
break;
case BMPEncodeParam.VERSION_4:
headerSize = 108;
throw new
RuntimeException(JaiI18N.getString("BMPImageEncoder5"));
// break;
}
writeFileHeader(fileSize, offset);
writeInfoHeader(headerSize, bitsPerPixel);
// compression
writeDWord(compression);
// imageSize
writeDWord(imageSize);
// xPelsPerMeter
writeDWord(xPelsPerMeter);
// yPelsPerMeter
writeDWord(yPelsPerMeter);
// Colors Used
writeDWord(colorsUsed);
// Colors Important
writeDWord(colorsImportant);
// palette
if (isPalette == true) {
// write palette
switch(version) {
// has 3 field entries
case BMPEncodeParam.VERSION_2:
for (int i=0; i<paletteEntries; i++) {
output.write(b[i]);
output.write(g[i]);
output.write(r[i]);
}
break;
// has 4 field entries
default:
for (int i=0; i<paletteEntries; i++) {
output.write(b[i]);
output.write(g[i]);
output.write(r[i]);
output.write(a[i]);
}
break;
}
} // else no palette
// Writing of actual image data
int scanlineBytes = w * numBands;
// Buffer for up to 8 rows of pixels
int[] pixels = new int[8 * scanlineBytes];
// Also create a buffer to hold one line of the data
// to be written to the file, so we can use array writes.
byte[] bpixels = new byte[destScanlineBytes];
int l;
if (!isTopDown) {
// Process 8 rows at a time so all but the first will have a
// multiple of 8 rows.
int lastRow = minY + h;
for (int row = (lastRow-1); row >= minY; row -= 8) {
// Number of rows being read
int rows = Math.min(8, row - minY + 1);
// Get the pixels
Raster src = im.getData(new Rectangle(minX, row - rows + 1,
w, rows));
src.getPixels(minX, row - rows + 1, w, rows, pixels);
l = 0;
// Last possible position in the pixels array
int max = scanlineBytes * rows - 1;
for (int i=0; i<rows; i++) {
// Beginning of each scanline in the pixels array
l = max - (i+1) * scanlineBytes + 1;
writePixels(l, scanlineBytes, bitsPerPixel, pixels,
bpixels, padding, numBands, icm);
}
}
} else {
// Process 8 rows at a time so all but the last will have a
// multiple of 8 rows.
int lastRow = minY + h;
for (int row = minY; row < lastRow; row += 8) {
int rows = Math.min(8, lastRow - row);
// Get the pixels
Raster src = im.getData(new Rectangle(minX, row,
w, rows));
src.getPixels(minX, row, w, rows, pixels);
l=0;
for (int i=0; i<rows; i++) {
writePixels(l, scanlineBytes, bitsPerPixel, pixels,
bpixels, padding, numBands, icm);
}
}
}
if(isCompressed &&( bitsPerPixel == 4 || bitsPerPixel == 8)){
// Write the RLE EOF marker and
output.write(0);
output.write(1);
incCompImageSize(2);
// update the file/image Size
imageSize = compImageSize;
fileSize = compImageSize + offset;
writeSize(fileSize, 2);
writeSize(imageSize, 34);
}
}
private void writePixels(int l, int scanlineBytes, int bitsPerPixel,
int pixels[], byte bpixels[],
int padding, int numBands,
IndexColorModel icm) throws IOException {
int pixel = 0;
int k = 0;
switch (bitsPerPixel) {
case 1:
for (int j=0; j<scanlineBytes/8; j++) {
bpixels[k++] = (byte)((pixels[l++] << 7) |
(pixels[l++] << 6) |
(pixels[l++] << 5) |
(pixels[l++] << 4) |
(pixels[l++] << 3) |
(pixels[l++] << 2) |
(pixels[l++] << 1) |
pixels[l++]);
}
// Partially filled last byte, if any
if (scanlineBytes%8 > 0) {
pixel = 0;
for (int j=0; j<scanlineBytes%8; j++) {
pixel |= (pixels[l++] << (7 - j));
}
bpixels[k++] = (byte)pixel;
}
output.write(bpixels, 0, (scanlineBytes+7)/8);
break;
case 4:
if (isCompressed){
byte[] bipixels = new byte[scanlineBytes];
for (int h=0; h<scanlineBytes; h++) {
bipixels[h] = (byte)pixels[l++];
}
encodeRLE4(bipixels, scanlineBytes);
}else {
for (int j=0; j<scanlineBytes/2; j++) {
pixel = (pixels[l++] << 4) | pixels[l++];
bpixels[k++] = (byte)pixel;
}
// Put the last pixel of odd-length lines in the 4 MSBs
if ((scanlineBytes%2) == 1) {
pixel = pixels[l] << 4;
bpixels[k++] = (byte)pixel;
}
output.write(bpixels, 0, (scanlineBytes+1)/2);
}
break;
case 8:
if(isCompressed) {
for (int h=0; h<scanlineBytes; h++) {
bpixels[h] = (byte)pixels[l++];
}
encodeRLE8(bpixels, scanlineBytes);
}else {
for (int j=0; j<scanlineBytes; j++) {
bpixels[j] = (byte)pixels[l++];
}
output.write(bpixels, 0, scanlineBytes);
}
break;
case 24:
if (numBands == 3) {
for (int j=0; j<scanlineBytes; j+=3) {
// Since BMP needs BGR format
bpixels[k++] = (byte)(pixels[l+2]);
bpixels[k++] = (byte)(pixels[l+1]);
bpixels[k++] = (byte)(pixels[l]);
l+=3;
}
output.write(bpixels, 0, scanlineBytes);
} else {
// Case where IndexColorModel had > 256 colors.
int entries = icm.getMapSize();
byte r[] = new byte[entries];
byte g[] = new byte[entries];
byte b[] = new byte[entries];
icm.getReds(r);
icm.getGreens(g);
icm.getBlues(b);
int index;
for (int j=0; j<scanlineBytes; j++) {
index = pixels[l];
bpixels[k++] = b[index];
bpixels[k++] = g[index];
bpixels[k++] = b[index];
l++;
}
output.write(bpixels, 0, scanlineBytes*3);
}
break;
}
// Write out the padding
if (!(isCompressed && (bitsPerPixel == 8 || bitsPerPixel == 4))){
for(k=0; k<padding; k++) {
output.write(0);
}
}
}
private void encodeRLE8(byte[] bpixels, int scanlineBytes)
throws IOException{
int runCount = 1, absVal = -1, j = -1;
byte runVal = 0, nextVal =0 ;
runVal = bpixels[++j];
byte[] absBuf = new byte[256];
while (j < scanlineBytes-1) {
nextVal = bpixels[++j];
if (nextVal == runVal ){
if(absVal >= 3 ){
/// Check if there was an existing Absolute Run
output.write(0);
output.write(absVal);
incCompImageSize(2);
for(int a=0; a<absVal;a++){
output.write(absBuf[a]);
incCompImageSize(1);
}
if (!isEven(absVal)){
//Padding
output.write(0);
incCompImageSize(1);
}
}
else if(absVal > -1){
/// Absolute Encoding for less than 3
/// treated as regular encoding
/// Do not include the last element since it will
/// be inclued in the next encoding/run
for (int b=0;b<absVal;b++){
output.write(1);
output.write(absBuf[b]);
incCompImageSize(2);
}
}
absVal = -1;
runCount++;
if (runCount == 256){
/// Only 255 values permitted
output.write(runCount-1);
output.write(runVal);
incCompImageSize(2);
runCount = 1;
}
}
else {
if (runCount > 1){
/// If there was an existing run
output.write(runCount);
output.write(runVal);
incCompImageSize(2);
} else if (absVal < 0){
// First time..
absBuf[++absVal] = runVal;
absBuf[++absVal] = nextVal;
} else if (absVal < 254){
// 0-254 only
absBuf[++absVal] = nextVal;
} else {
output.write(0);
output.write(absVal+1);
incCompImageSize(2);
for(int a=0; a<=absVal;a++){
output.write(absBuf[a]);
incCompImageSize(1);
}
// padding since 255 elts is not even
output.write(0);
incCompImageSize(1);
absVal = -1;
}
runVal = nextVal;
runCount = 1;
}
if (j == scanlineBytes-1){ // EOF scanline
// Write the run
if (absVal == -1){
output.write(runCount);
output.write(runVal);
incCompImageSize(2);
runCount = 1;
}
else {
// write the Absolute Run
if(absVal >= 2){
output.write(0);
output.write(absVal+1);
incCompImageSize(2);
for(int a=0; a<=absVal;a++){
output.write(absBuf[a]);
incCompImageSize(1);
}
if (!isEven(absVal+1)){
//Padding
output.write(0);
incCompImageSize(1);
}
}
else if(absVal > -1){
for (int b=0;b<=absVal;b++){
output.write(1);
output.write(absBuf[b]);
incCompImageSize(2);
}
}
}
/// EOF scanline
output.write(0);
output.write(0);
incCompImageSize(2);
}
}
}
private void encodeRLE4(byte[] bipixels, int scanlineBytes)
throws IOException {
int runCount=2, absVal=-1, j=-1, pixel=0, q=0;
byte runVal1=0, runVal2=0, nextVal1=0, nextVal2=0;
byte[] absBuf = new byte[256];
runVal1 = bipixels[++j];
runVal2 = bipixels[++j];
while (j < scanlineBytes-2){
nextVal1 = bipixels[++j];
nextVal2 = bipixels[++j];
if (nextVal1 == runVal1 ) {
//Check if there was an existing Absolute Run
if(absVal >= 4){
output.write(0);
output.write(absVal - 1);
incCompImageSize(2);
// we need to exclude last 2 elts, similarity of
// which caused to enter this part of the code
for(int a=0; a<absVal-2;a+=2){
pixel = (absBuf[a] << 4) | absBuf[a+1];
output.write((byte)pixel);
incCompImageSize(1);
}
// if # of elts is odd - read the last element
if(!(isEven(absVal-1))){
q = absBuf[absVal-2] << 4| 0;
output.write(q);
incCompImageSize(1);
}
// Padding to word align absolute encoding
if ( !isEven((int)Math.ceil((absVal-1)/2)) ) {
output.write(0);
incCompImageSize(1);
}
} else if (absVal > -1){
output.write(2);
pixel = (absBuf[0] << 4) | absBuf[1];
output.write(pixel);
incCompImageSize(2);
}
absVal = -1;
if (nextVal2 == runVal2){
// Even runlength
runCount+=2;
if(runCount == 256){
output.write(runCount-1);
pixel = ( runVal1 << 4) | runVal2;
output.write(pixel);
incCompImageSize(2);
runCount =2;
if(j< scanlineBytes - 1){
runVal1 = runVal2;
runVal2 = bipixels[++j];
} else {
output.write(01);
int r = runVal2 << 4 | 0;
output.write(r);
incCompImageSize(2);
runCount = -1;/// Only EOF required now
}
}
} else {
// odd runlength and the run ends here
// runCount wont be > 254 since 256/255 case will
// be taken care of in above code.
runCount++;
pixel = ( runVal1 << 4) | runVal2;
output.write(runCount);
output.write(pixel);
incCompImageSize(2);
runCount = 2;
runVal1 = nextVal2;
// If end of scanline
if (j < scanlineBytes -1){
runVal2 = bipixels[++j];
}else {
output.write(01);
int r = nextVal2 << 4 | 0;
output.write(r);
incCompImageSize(2);
runCount = -1;/// Only EOF required now
}
}
} else{
// Check for existing run
if (runCount > 2){
pixel = ( runVal1 << 4) | runVal2;
output.write(runCount);
output.write(pixel);
incCompImageSize(2);
} else if (absVal < 0){ // first time
absBuf[++absVal] = runVal1;
absBuf[++absVal] = runVal2;
absBuf[++absVal] = nextVal1;
absBuf[++absVal] = nextVal2;
} else if (absVal < 253){ // only 255 elements
absBuf[++absVal] = nextVal1;
absBuf[++absVal] = nextVal2;
} else {
output.write(0);
output.write(absVal+1);
incCompImageSize(2);
for(int a=0; a<absVal;a+=2){
pixel = (absBuf[a] << 4) | absBuf[a+1];
output.write((byte)pixel);
incCompImageSize(1);
}
// Padding for word align
// since it will fit into 127 bytes
output.write(0);
incCompImageSize(1);
absVal = -1;
}
runVal1 = nextVal1;
runVal2 = nextVal2;
runCount = 2;
}
// Handle the End of scanline for the last 2 4bits
if (j >= scanlineBytes-2 ) {
if (absVal == -1 && runCount >= 2){
if (j == scanlineBytes-2){
if(bipixels[++j] == runVal1){
runCount++;
pixel = ( runVal1 << 4) | runVal2;
output.write(runCount);
output.write(pixel);
incCompImageSize(2);
} else {
pixel = ( runVal1 << 4) | runVal2;
output.write(runCount);
output.write(pixel);
output.write(01);
pixel = bipixels[j]<<4 |0;
output.write(pixel);
int n = bipixels[j]<<4|0;
incCompImageSize(4);
}
} else {
output.write(runCount);
pixel =( runVal1 << 4) | runVal2 ;
output.write(pixel);
incCompImageSize(2);
}
} else if(absVal > -1){
if (j == scanlineBytes-2){
absBuf[++absVal] = bipixels[++j];
}
if (absVal >=2){
output.write(0);
output.write(absVal+1);
incCompImageSize(2);
for(int a=0; a<absVal;a+=2){
pixel = (absBuf[a] << 4) | absBuf[a+1];
output.write((byte)pixel);
incCompImageSize(1);
}
if(!(isEven(absVal+1))){
q = absBuf[absVal] << 4|0;
output.write(q);
incCompImageSize(1);
}
// Padding
if ( !isEven((int)Math.ceil((absVal+1)/2)) ) {
output.write(0);
incCompImageSize(1);
}
} else {
switch (absVal){
case 0:
output.write(1);
int n = absBuf[0]<<4 | 0;
output.write(n);
incCompImageSize(2);
break;
case 1:
output.write(2);
pixel = (absBuf[0] << 4) | absBuf[1];
output.write(pixel);
incCompImageSize(2);
break;
}
}
}
output.write(0);
output.write(0);
incCompImageSize(2);
}
}
}
private synchronized void incCompImageSize(int value){
compImageSize = compImageSize + value;
}
private boolean isEven(int number) {
return (number%2 == 0 ? true : false);
}
private void writeFileHeader(int fileSize, int offset) throws IOException {
// magic value
output.write('B');
output.write('M');
// File size
writeDWord(fileSize);
// reserved1 and reserved2
output.write(0);
output.write(0);
output.write(0);
output.write(0);
// offset to image data
writeDWord(offset);
}
private void writeInfoHeader(int headerSize, int bitsPerPixel)
throws IOException {
// size of header
writeDWord(headerSize);
// width
writeDWord(w);
// height
writeDWord(h);
// number of planes
writeWord(1);
// Bits Per Pixel
writeWord(bitsPerPixel);
}
// Methods for little-endian writing
public void writeWord(int word) throws IOException {
output.write(word & 0xff);
output.write((word & 0xff00) >> 8);
}
public void writeDWord(int dword) throws IOException {
output.write(dword & 0xff);
output.write((dword & 0xff00) >> 8);
output.write((dword & 0xff0000) >> 16);
output.write((dword & 0xff000000) >> 24);
}
private void writeSize(int dword, int offset) throws IOException {
((SeekableOutputStream)output).seek(offset);
writeDWord(dword);
}
}