// Copyright 2000, CERN, Geneva, Switzerland and University of Santa Cruz, California, U.S.A. package org.freehep.graphicsio.ps; import java.awt.*; import java.awt.image.*; import java.io.*; import java.util.*; import org.freehep.graphicsio.ImageEncoder; /** * * @author Charles Loomis * @version $Id: EPSIEncoder.java,v 1.2 2003/04/30 00:24:08 duns Exp $ */ public class EPSIEncoder extends ImageEncoder { // Number of bits per byte. final static int maxBitsPerByte = 8; // Number of bytes per scan line (maximum here is 254). final static int maxBytesPerScan = 128; // The number of bits to use to represent the grayscale. private int grayscaleBits; // Boolean which gives the orientation of the image. private boolean portrait; // The width and height of the image. int width, height; // The array to hold the pixels. byte[][] grayPixels; // An array which hold enough bytes for one scan line. Scanline scanline; // Private conversion of bytes to hex digits. private static char[] hexDigit = {'0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F'}; // Masks to strip off last n bits. final private static byte[] lowBitMask = {(byte) 0, (byte) 1, (byte) 3, (byte) 7, (byte) 15, (byte) 31, (byte) 63, (byte) 127, (byte) 255}; /** * Constructor from Image with number of grayscale bits to use. * @param img The image to encode. * @param out The stream to write the GIF to. * @param grayscaleBits Number of grayscale bits to use. * @param portrait Flag indicating a portrait orientation. */ public EPSIEncoder(Image img, OutputStream out, int grayscaleBits, boolean portrait ) throws IOException { super( img, new DataOutputStream(out) ); this.grayscaleBits = grayscaleBits; this.portrait = portrait; } /** * Constructor from ImageProducer with number of grayscale bits to use. * @param prod The ImageProducer to encode. * @param out The stream to write the GIF to. * @param grayscaleBits Number of grayscale bits to use. * @param portrait Flag indicating a portrait orientation. */ public EPSIEncoder( ImageProducer prod, OutputStream out, int grayscaleBits, boolean portrait ) throws IOException { super( prod, new DataOutputStream(out) ); this.grayscaleBits = grayscaleBits; this.portrait = portrait; } protected void encodeStart( int width, int height ) throws IOException { this.width = width; this.height = height; grayPixels = new byte[height][width]; // Use the appropriate width and height depending on the orientation. int w, h; if (portrait) { w = width; h = height; } else { w = height; h = width; } // Calculate the number of lines in the image. int bitsPerScan = w*grayscaleBits; int bytesPerScan = (bitsPerScan/maxBitsPerByte) + ((bitsPerScan%maxBitsPerByte==0) ? 0 : 1 ); int linesPerScan = (bytesPerScan/maxBytesPerScan) + ((bytesPerScan%maxBytesPerScan==0) ? 0 : 1 ); int lines = linesPerScan*h; // Make a byte array which holds the information for one scan line. scanline = new Scanline(bytesPerScan, grayscaleBits); // Write out the header. putString("%%BeginPreview "+width+" "+height+" "+ grayscaleBits+" "+lines+"\n"); } protected void encodePixels(int x, int y, int w, int h, int[] rgbPixels, int off, int scansize ) throws IOException { // Save the pixels as a grayscale value. for ( int row = 0; row < h; ++row ) { for (int column=0; column> 0) & mask))/255.; double green = ((double) ((argb>> 8) & mask))/255.; double red = ((double) ((argb>>16) & mask))/255.; return (byte) (255.*Math.max(0.,(1.-(0.3*red+0.59*green+0.11*blue)))); } protected void encodeDone() throws IOException { if (portrait) { for (int row=height-1; row>=0 ; row--) { scanline.reset(); for (int col=0; col=0; col--) { scanline.reset(); for (int row=height-1; row>=0 ; row--) { byte gray = grayPixels[row][col]; scanline.add(gray); } scanline.put(); } } // Write out the trailer. putString("%%EndPreview\n"); } // Write a string to the file. void putString(String s) throws IOException { out.write(s.getBytes()); } // Write out a character to the file. void putChar(char c) throws IOException { out.write(c); } // Write out a byte to the GIF file void putByte( byte b) throws IOException { int highNibble = (b>>4) & 0xF; int lowNibble = b & 0xF; out.write(hexDigit[highNibble]); out.write(hexDigit[lowNibble]); } // This class handles packing the scan data into a hexadecimal byte array. // This only works if the number of bits is 1, 2, 4, or 8. private class Scanline { private byte[] line; private int nbits; private int currentByte; private int currentOffset; public Scanline(int size, int nbits) { line = new byte[size]; this.nbits = nbits; reset(); } // Reset the scan line. Always zero the last byte in case the // scan line isn't fully used by the image. public void reset() { currentByte = 0; currentOffset = 0; for (int i=0; i>= (maxBitsPerByte-nbits); b &= lowBitMask[nbits]; // OR in the information. line[currentByte] |= b; // Update the offsets. if (maxBitsPerByte-nbits-currentOffset == 0) { // We're reached the end of a byte, just reset the counters. currentOffset = 0; currentByte++; } else { // Increment the offset and shift the word for the next // bits of information. currentOffset += nbits; line[currentByte] <<= nbits; } } public void put() throws IOException { // Check the last byte. If currentOffset isn't zero then // the last byte hasn't been fully shifted into place. // Do it now! while (maxBitsPerByte-nbits-currentOffset > 0) { line[line.length-1] <<= nbits; currentOffset += nbits; } for (int i=0; i