From e8f5044051de70a1cdad65df9c5ed6bff3cd1a08 Mon Sep 17 00:00:00 2001 From: adam Date: Fri, 18 Mar 2005 09:11:11 +0000 Subject: [PATCH] tons of changes darcs-hash:20050318091111-5007d-b974593492c381303bfc5fd5b7529bf5376c6b6a.gz --- src/org/ibex/xt/ImageConverter.java | 107 +++---- src/org/ibex/xt/Node.java | 15 +- src/org/ibex/xt/PSDReader.java | 553 ++++++++++++++++++++++++++++++++ src/org/ibex/xt/PngEncoder.java | 600 +++++++++++++++++++++++++++++++++++ src/org/ibex/xt/PngEncoderB.java | 506 +++++++++++++++++++++++++++++ src/org/ibex/xt/Servlet.java | 24 +- src/org/ibex/xt/Template.java | 45 ++- src/org/ibex/xt/Text.java | 63 ---- 8 files changed, 1777 insertions(+), 136 deletions(-) create mode 100644 src/org/ibex/xt/PSDReader.java create mode 100644 src/org/ibex/xt/PngEncoder.java create mode 100644 src/org/ibex/xt/PngEncoderB.java delete mode 100644 src/org/ibex/xt/Text.java diff --git a/src/org/ibex/xt/ImageConverter.java b/src/org/ibex/xt/ImageConverter.java index 49af984..7d1c942 100644 --- a/src/org/ibex/xt/ImageConverter.java +++ b/src/org/ibex/xt/ImageConverter.java @@ -5,77 +5,64 @@ package org.ibex.xt; -import java.awt.Dimension; +import java.awt.*; +import java.awt.image.*; +import org.ibex.util.*; import java.io.*; import javax.servlet.*; import javax.servlet.http.*; -import magick.ImageInfo; -import magick.MagickImage; -public class ImageConverter extends HttpServlet -{ +public class ImageConverter extends HttpServlet { - public ImageConverter() - { - } - - public void init(ServletConfig servletconfig) - { - cx = servletconfig.getServletContext(); - } + public ImageConverter() { } + public void init(ServletConfig servletconfig) { cx = servletconfig.getServletContext(); } public void doPost(HttpServletRequest httpservletrequest, HttpServletResponse httpservletresponse) - throws IOException, ServletException - { - doGet(httpservletrequest, httpservletresponse); - } + throws IOException, ServletException { doGet(httpservletrequest, httpservletresponse); } - public void doGet(HttpServletRequest httpservletrequest, HttpServletResponse httpservletresponse) - throws IOException, ServletException - { - try - { - if(httpservletrequest.getServletPath().indexOf("..") != -1) - throw new IOException(".. not allowed in image paths"); - String s = cx.getRealPath(httpservletrequest.getServletPath()); + public void doGet(HttpServletRequest req, HttpServletResponse resp) + throws IOException, ServletException { + try { + if (req.getServletPath().indexOf("..") != -1) throw new IOException(".. not allowed in image paths"); + String s = cx.getRealPath(req.getServletPath()); String s1 = s.indexOf('.') != -1 ? s.substring(0, s.lastIndexOf('.')) : s; File file = new File((new StringBuilder()).append(s1).append(".png").toString()); - if(!file.exists()) - file = new File((new StringBuilder()).append(s1).append(".xcf").toString()); - if(!file.exists()) - file = new File((new StringBuilder()).append(s1).append(".gif").toString()); - if(!file.exists()) - file = new File((new StringBuilder()).append(s1).append(".jpeg").toString()); - System.out.println((new StringBuilder()).append("path is ").append(file.getAbsolutePath()).toString()); - ImageInfo imageinfo = new ImageInfo(file.getAbsolutePath()); - imageinfo.setColorspace(12); - imageinfo.setSubimage(0); - MagickImage magickimage = new MagickImage(imageinfo); - Dimension dimension = magickimage.getDimension(); - int i = (int)dimension.getWidth(); - int j = (int)dimension.getHeight(); - String s2 = httpservletrequest.getParameter("width"); - String s3 = httpservletrequest.getParameter("height"); - if(s2 != null && s3 != null) - magickimage = magickimage.sampleImage(Integer.parseInt(s2), Integer.parseInt(s3)); - else - if(s2 != null) - { - int k = Integer.parseInt(s2); - magickimage = magickimage.sampleImage(k, (k * j) / i); - } else - if(s3 != null) - { - int l = Integer.parseInt(s3); - magickimage = magickimage.sampleImage((i * l) / j, l); + if (!file.exists()) file = new File((new StringBuilder()).append(s1).append(".psd").toString()); + if (!file.exists()) file = new File((new StringBuilder()).append(s1).append(".gif").toString()); + if (!file.exists()) file = new File((new StringBuilder()).append(s1).append(".jpeg").toString()); + if (file.getAbsolutePath().endsWith(".gif")) { + resp.setContentType("image/gif"); + // FIXME slow + resp.getOutputStream().write(InputStreamToByteArray.convert(new FileInputStream(file))); + } else if (file.getAbsolutePath().endsWith(".jpeg")) { + resp.setContentType("image/jpeg"); + // FIXME slow + resp.getOutputStream().write(InputStreamToByteArray.convert(new FileInputStream(file))); + } else if (file.getAbsolutePath().endsWith(".png")) { + resp.setContentType("image/png"); + // FIXME slow + resp.getOutputStream().write(InputStreamToByteArray.convert(new FileInputStream(file))); + } else if (file.getAbsolutePath().endsWith(".psd")) { + PSDReader psdr = new PSDReader(); + psdr.read(new FileInputStream(file)); + Image img = psdr.getImage(); + if (req.getParameter("height") != null) { + int height = Integer.parseInt(req.getParameter("height")); + int width = (img.getWidth(null) * height) / img.getHeight(null); + img = ((BufferedImage)img).getScaledInstance(width, height, Image.SCALE_SMOOTH); + } + System.out.println("bufferedimage is " + img); + if (!(img instanceof BufferedImage)) { + BufferedImage bi = new BufferedImage(img.getWidth(null), img.getHeight(null), BufferedImage.TYPE_INT_ARGB); + bi.getGraphics().drawImage(img, 0, 0, null); + img = bi; + } + PngEncoderB pngenc = new PngEncoderB((BufferedImage)img, true); + resp.setContentType("image/png"); + // FIXME slow + resp.getOutputStream().write(pngenc.pngEncode(true)); } - magickimage.setImageFormat("png"); - byte abyte0[] = magickimage.imageToBlob(imageinfo); - httpservletresponse.setContentType("image/png"); - httpservletresponse.getOutputStream().write(abyte0); - } - catch(Exception exception) - { + } catch(Exception exception) { exception.printStackTrace(); throw new ServletException(exception); } diff --git a/src/org/ibex/xt/Node.java b/src/org/ibex/xt/Node.java index 04e7ee6..b4c6026 100644 --- a/src/org/ibex/xt/Node.java +++ b/src/org/ibex/xt/Node.java @@ -17,7 +17,7 @@ public class Node { public int numattrs = 0; public String[] attrs = null; public String uri = null; - private int delta = 0; + private int delta = 0; public Node() { } public Node(Node n) { copyFrom(n); } @@ -109,6 +109,19 @@ public class Node { } } + public static class Join extends Node.Stream { + final Node.Stream s1, s2; + boolean s1Done = false; + public Join(Node.Stream s1, Node.Stream s2) { this.s1=s1; this.s2=s2; } + protected boolean _read(Node n) { + if (!s1Done) return s2._read(n); + boolean ret = s1._read(n); + if (ret) return true; + s1Done = true; + return s2._read(n); + } + } + public static class FromXML extends Node.Stream { private final XML.Stream xml; private XML.Elem parent = null; diff --git a/src/org/ibex/xt/PSDReader.java b/src/org/ibex/xt/PSDReader.java new file mode 100644 index 0000000..c661d25 --- /dev/null +++ b/src/org/ibex/xt/PSDReader.java @@ -0,0 +1,553 @@ +package org.ibex.xt; +import java.awt.*; +import java.awt.image.*; +import java.net.*; +import java.io.*; + +/** + * Class PSDReader - Decodes a PhotoShop (.psd) file into one or more frames. + * Supports uncompressed or RLE-compressed RGB files only. Each layer may be + * retrieved as a full frame BufferedImage, or as a smaller image with an + * offset if the layer does not occupy the full frame size. Transparency + * in the original psd file is preserved in the returned BufferedImage's. + * Does not support additional features in PS versions higher than 3.0. + * Example: + *
+ *    PSDReader r = new PSDReader();
+ *    r.read("sample.psd");
+ *    int n = r.getFrameCount();
+ *    for (int i = 0; i < n; i++) {
+ *       BufferedImage image = r.getLayer(i);
+ *       Point offset = r.getLayerOffset(i);
+ *       // do something with image
+ *    }
+ * 
+ * No copyright asserted on the source code of this class. May be used for + * any purpose. Please forward any corrections to kweiner@fmsware.com. + * + * @author Kevin Weiner, FM Software. + * @version 1.1 January 2004 [bug fix; add RLE support] + * + */ +public class PSDReader { + + /** + * File read status: No errors. + */ + public static final int STATUS_OK = 0; + + /** + * File read status: Error decoding file (may be partially decoded) + */ + public static final int STATUS_FORMAT_ERROR = 1; + + /** + * File read status: Unable to open source. + */ + public static final int STATUS_OPEN_ERROR = 2; + + /** + * File read status: Unsupported format + */ + public static final int STATUS_UNSUPPORTED = 3; + + public static int ImageType = BufferedImage.TYPE_INT_ARGB; + + protected BufferedInputStream input; + protected int frameCount; + protected BufferedImage[] frames; + protected int status = 0; + protected int nChan; + protected int width; + protected int height; + protected int nLayers; + protected int miscLen; + protected boolean hasLayers; + protected LayerInfo[] layers; + protected short[] lineLengths; + protected int lineIndex; + protected boolean rleEncoded; + + protected class LayerInfo { + int x, y, w, h; + int nChan; + int[] chanID; + int alpha; + } + + /** + * Gets the number of layers read from file. + * @return frame count + */ + public int getFrameCount() { + return frameCount; + } + + protected void setInput(InputStream stream) { + // open input stream + init(); + if (stream == null) { + status = STATUS_OPEN_ERROR; + } else { + if (stream instanceof BufferedInputStream) + input = (BufferedInputStream) stream; + else + input = new BufferedInputStream(stream); + } + } + + protected void setInput(String name) { + // open input file + init(); + try { + name = name.trim(); + if (name.startsWith("file:")) { + name = name.substring(5); + while (name.startsWith("/")) + name = name.substring(1); + } + if (name.indexOf("://") > 0) { + URL url = new URL(name); + input = new BufferedInputStream(url.openStream()); + } else { + input = new BufferedInputStream(new FileInputStream(name)); + } + } catch (IOException e) { + status = STATUS_OPEN_ERROR; + } + } + + /** + * Gets display duration for specified frame. Always returns 0. + * + */ + public int getDelay(int forFrame) { + return 0; + } + + /** + * Gets the image contents of frame n. Note that this expands the image + * to the full frame size (if the layer was smaller) and any subsequent + * use of getLayer() will return the full image. + * + * @return BufferedImage representation of frame, or null if n is invalid. + */ + public BufferedImage getFrame(int n) { + BufferedImage im = null; + if ((n >= 0) && (n < nLayers)) { + im = frames[n]; + LayerInfo info = layers[n]; + if ((info.w != width) || (info.h != height)) { + BufferedImage temp = + new BufferedImage(width, height, ImageType); + Graphics2D gc = temp.createGraphics(); + gc.drawImage(im, info.x, info.y, null); + gc.dispose(); + im = temp; + frames[n] = im; + } + } + return im; + } + + /** + * Gets maximum image size. Individual layers may be smaller. + * + * @return maximum image dimensions + */ + public Dimension getFrameSize() { + return new Dimension(width, height); + } + + /** + * Gets the first (or only) image read. + * + * @return BufferedImage containing first frame, or null if none. + */ + public BufferedImage getImage() { + return getFrame(0); + } + + /** + * Gets the image contents of layer n. May be smaller than full frame + * size - use getFrameOffset() to obtain position of subimage within + * main image area. + * + * @return BufferedImage representation of layer, or null if n is invalid. + */ + public BufferedImage getLayer(int n) { + BufferedImage im = null; + if ((n >= 0) && (n < nLayers)) { + im = frames[n]; + } + return im; + } + + /** + * Gets the subimage offset of layer n if it is smaller than the + * full frame size. + * + * @return Point indicating offset from upper left corner of frame. + */ + public Point getLayerOffset(int n) { + Point p = null; + if ((n >= 0) && (n < nLayers)) { + int x = layers[n].x; + int y = layers[n].y; + p = new Point(x, y); + } + if (p == null) { + p = new Point(0, 0); + } + return p; + } + + /** + * Reads PhotoShop layers from stream. + * + * @param InputStream in PhotoShop format. + * @return read status code (0 = no errors) + */ + public int read(InputStream stream) { + setInput(stream); + process(); + return status; + } + + /** + * Reads PhotoShop file from specified source (file or URL string) + * + * @param name String containing source + * @return read status code (0 = no errors) + */ + public int read(String name) { + setInput(name); + process(); + return status; + } + + /** + * Closes input stream and discards contents of all frames. + * + */ + public void reset() { + init(); + } + + protected void close() { + if (input != null) { + try { + input.close(); + } catch (Exception e) {} + input = null; + } + } + protected boolean err() { + return status != STATUS_OK; + } + + protected byte[] fillBytes(int size, int value) { + // create byte array filled with given value + byte[] b = new byte[size]; + if (value != 0) { + byte v = (byte) value; + for (int i = 0; i < size; i++) { + b[i] = v; + } + } + return b; + } + + protected void init() { + close(); + frameCount = 0; + frames = null; + layers = null; + hasLayers = true; + status = STATUS_OK; + } + + protected void makeDummyLayer() { + // creat dummy layer for non-layered image + rleEncoded = readShort() == 1; + hasLayers = false; + nLayers = 1; + layers = new LayerInfo[1]; + LayerInfo layer = new LayerInfo(); + layers[0] = layer; + layer.h = height; + layer.w = width; + int nc = Math.min(nChan, 4); + if (rleEncoded) { + // get list of rle encoded line lengths for all channels + readLineLengths(height * nc); + } + layer.nChan = nc; + layer.chanID = new int[nc]; + for (int i = 0; i < nc; i++) { + int id = i; + if (i == 3) id = -1; + layer.chanID[i] = id; + } + } + + protected void readLineLengths(int nLines) { + // read list of rle encoded line lengths + lineLengths = new short[nLines]; + for (int i = 0; i < nLines; i++) { + lineLengths[i] = readShort(); + } + lineIndex = 0; + } + + protected BufferedImage makeImage(int w, int h, byte[] r, byte[] g, byte[] b, byte[] a) { + // create image from given plane data + BufferedImage im = new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB); + int[] data = ((DataBufferInt) im.getRaster().getDataBuffer()).getData(); + int n = w * h; + int j = 0; + while (j < n) { + try { + int ac = a[j] & 0xff; + int rc = r[j] & 0xff; + int gc = g[j] & 0xff; + int bc = b[j] & 0xff; + data[j] = (((((ac << 8) | rc) << 8) | gc) << 8) | bc; + } catch (Exception e) {} + j++; + } + return im; + } + + protected void process() { + // decode PSD file + if (err()) return; + readHeader(); + if (err()) return; + readLayerInfo(); + if (err()) return; + if (nLayers == 0) { + makeDummyLayer(); + if (err()) return; + } + readLayers(); + } + + protected int readByte() { + // read single byte from input + int curByte = 0; + try { + curByte = input.read(); + } catch (IOException e) { + status = STATUS_FORMAT_ERROR; + } + return curByte; + } + + protected int readBytes(byte[] bytes, int n) { + // read multiple bytes from input + if (bytes == null) return 0; + int r = 0; + try { + r = input.read(bytes, 0, n); + } catch (IOException e) { + status = STATUS_FORMAT_ERROR; + } + if (r < n) { + status = STATUS_FORMAT_ERROR; + } + return r; + } + + protected void readHeader() { + // read PSD header info + String sig = readString(4); + int ver = readShort(); + skipBytes(6); + nChan = readShort(); + height = readInt(); + width = readInt(); + int depth = readShort(); + int mode = readShort(); + int cmLen = readInt(); + skipBytes(cmLen); + int imResLen = readInt(); + skipBytes(imResLen); + + // require 8-bit RGB data + if ((!sig.equals("8BPS")) || (ver != 1)) { + status = STATUS_FORMAT_ERROR; + } else if ((depth != 8) || (mode != 3)) { + status = STATUS_UNSUPPORTED; + } + } + + protected int readInt() { + // read big-endian 32-bit integer + return (((((readByte() << 8) | readByte()) << 8) | readByte()) << 8) + | readByte(); + } + + protected void readLayerInfo() { + // read layer header info + miscLen = readInt(); + if (miscLen == 0) { + return; // no layers, only base image + } + int layerInfoLen = readInt(); + nLayers = readShort(); + if (nLayers > 0) { + layers = new LayerInfo[nLayers]; + } + for (int i = 0; i < nLayers; i++) { + LayerInfo info = new LayerInfo(); + layers[i] = info; + info.y = readInt(); + info.x = readInt(); + info.h = readInt() - info.y; + info.w = readInt() - info.x; + info.nChan = readShort(); + info.chanID = new int[info.nChan]; + for (int j = 0; j < info.nChan; j++) { + int id = readShort(); + int size = readInt(); + info.chanID[j] = id; + } + String s = readString(4); + if (!s.equals("8BIM")) { + status = STATUS_FORMAT_ERROR; + return; + } + skipBytes(4); // blend mode + info.alpha = readByte(); + int clipping = readByte(); + int flags = readByte(); + readByte(); // filler + int extraSize = readInt(); + skipBytes(extraSize); + } + } + + protected void readLayers() { + // read and convert each layer to BufferedImage + frameCount = nLayers; + frames = new BufferedImage[nLayers]; + for (int i = 0; i < nLayers; i++) { + LayerInfo info = layers[i]; + byte[] r = null, g = null, b = null, a = null; + for (int j = 0; j < info.nChan; j++) { + int id = info.chanID[j]; + switch (id) { + case 0 : r = readPlane(info.w, info.h); break; + case 1 : g = readPlane(info.w, info.h); break; + case 2 : b = readPlane(info.w, info.h); break; + case -1 : a = readPlane(info.w, info.h); break; + default : readPlane(info.w, info.h); + } + if (err()) break; + } + if (err()) break; + int n = info.w * info.h; + if (r == null) r = fillBytes(n, 0); + if (g == null) g = fillBytes(n, 0); + if (b == null) b = fillBytes(n, 0); + if (a == null) a = fillBytes(n, 255); + + BufferedImage im = makeImage(info.w, info.h, r, g, b, a); + frames[i] = im; + } + lineLengths = null; + if ((miscLen > 0) && !err()) { + int n = readInt(); // global layer mask info len + skipBytes(n); + } + } + + protected byte[] readPlane(int w, int h) { + // read a single color plane + byte[] b = null; + int size = w * h; + if (hasLayers) { + // get RLE compression info for channel + rleEncoded = readShort() == 1; + if (rleEncoded) { + // list of encoded line lengths + readLineLengths(h); + } + } + + if (rleEncoded) { + b = readPlaneCompressed(w, h); + } else { + b = new byte[size]; + readBytes(b, size); + } + + return b; + + } + + protected byte[] readPlaneCompressed(int w, int h) { + byte[] b = new byte[w * h]; + byte[] s = new byte[w * 2]; + int pos = 0; + for (int i = 0; i < h; i++) { + if (lineIndex >= lineLengths.length) { + status = STATUS_FORMAT_ERROR; + return null; + } + int len = lineLengths[lineIndex++]; + readBytes(s, len); + decodeRLE(s, 0, len, b, pos); + pos += w; + } + return b; + } + + protected void decodeRLE(byte[] src, int sindex, int slen, byte[] dst, int dindex) { + try { + int max = sindex + slen; + while (sindex < max) { + byte b = src[sindex++]; + int n = (int) b; + if (n < 0) { + // dup next byte 1-n times + n = 1 - n; + b = src[sindex++]; + for (int i = 0; i < n; i++) { + dst[dindex++] = b; + } + } else { + // copy next n+1 bytes + n = n + 1; + System.arraycopy(src, sindex, dst, dindex, n); + dindex += n; + sindex += n; + } + } + } catch (Exception e) { + status = STATUS_FORMAT_ERROR; + } + } + + protected short readShort() { + // read big-endian 16-bit integer + return (short) ((readByte() << 8) | readByte()); + } + + protected String readString(int len) { + // read string of specified length + String s = ""; + for (int i = 0; i < len; i++) { + s = s + (char) readByte(); + } + return s; + } + + protected void skipBytes(int n) { + // skip over n input bytes + for (int i = 0; i < n; i++) { + readByte(); + } + } +} diff --git a/src/org/ibex/xt/PngEncoder.java b/src/org/ibex/xt/PngEncoder.java new file mode 100644 index 0000000..644fc17 --- /dev/null +++ b/src/org/ibex/xt/PngEncoder.java @@ -0,0 +1,600 @@ +package org.ibex.xt; + +import java.awt.Image; +import java.awt.image.ImageObserver; +import java.awt.image.PixelGrabber; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.util.zip.CRC32; +import java.util.zip.Deflater; +import java.util.zip.DeflaterOutputStream; + +/** + * PngEncoder takes a Java Image object and creates a byte string which can be saved as a PNG file. + * The Image is presumed to use the DirectColorModel. + * + *

Thanks to Jay Denny at KeyPoint Software + * http://www.keypoint.com/ + * who let me develop this code on company time.

+ * + *

You may contact me with (probably very-much-needed) improvements, + * comments, and bug fixes at:

+ * + *

david@catcode.com

+ * + *

This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version.

+ * + *

This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details.

+ * + *

You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * A copy of the GNU LGPL may be found at + * http://www.gnu.org/copyleft/lesser.html

+ * + * @author J. David Eisenberg + * @version 1.5, 19 Oct 2003 + * + * CHANGES: + * -------- + * 19-Nov-2002 : CODING STYLE CHANGES ONLY (by David Gilbert for Object Refinery Limited); + * 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 + */ + +public class PngEncoder extends Object { + + /** Constant specifying that alpha channel should be encoded. */ + public static final boolean ENCODE_ALPHA = true; + + /** Constant specifying that alpha channel should not be encoded. */ + public static final boolean NO_ALPHA = false; + + /** Constants for filter (NONE) */ + public static final int FILTER_NONE = 0; + + /** Constants for filter (SUB) */ + public static final int FILTER_SUB = 1; + + /** Constants for filter (UP) */ + public static final int FILTER_UP = 2; + + /** Constants for filter (LAST) */ + public static final int FILTER_LAST = 2; + + /** IHDR tag. */ + protected static final byte IHDR[] = {73, 72, 68, 82}; + + /** IDAT tag. */ + protected static final byte IDAT[] = {73, 68, 65, 84}; + + /** IEND tag. */ + protected static final byte IEND[] = {73, 69, 78, 68}; + + /** The png bytes. */ + protected byte[] pngBytes; + + /** The prior row. */ + protected byte[] priorRow; + + /** The left bytes. */ + protected byte[] leftBytes; + + /** The image. */ + protected Image image; + + /** The width. */ + protected int width, height; + + /** The byte position. */ + protected int bytePos, maxPos; + + /** CRC. */ + protected CRC32 crc = new CRC32(); + + /** The CRC value. */ + protected long crcValue; + + /** Encode alpha? */ + protected boolean encodeAlpha; + + /** The filter type. */ + protected int filter; + + /** The bytes-per-pixel. */ + protected int bytesPerPixel; + + /** The compression level. */ + protected int compressionLevel; + + /** + * Class constructor + */ + public PngEncoder() { + this(null, false, FILTER_NONE, 0); + } + + /** + * Class constructor specifying Image to encode, with no alpha channel encoding. + * + * @param image A Java Image object which uses the DirectColorModel + * @see java.awt.Image + */ + public PngEncoder(Image image) { + this(image, false, FILTER_NONE, 0); + } + + /** + * Class constructor specifying Image to encode, and whether to encode alpha. + * + * @param image A Java Image object which uses the DirectColorModel + * @param encodeAlpha Encode the alpha channel? false=no; true=yes + * @see java.awt.Image + */ + public PngEncoder(Image image, boolean encodeAlpha) { + this(image, encodeAlpha, FILTER_NONE, 0); + } + + /** + * Class constructor specifying Image to encode, whether to encode alpha, and filter to use. + * + * @param image A Java Image object which uses the DirectColorModel + * @param encodeAlpha Encode the alpha channel? false=no; true=yes + * @param whichFilter 0=none, 1=sub, 2=up + * @see java.awt.Image + */ + public PngEncoder(Image image, boolean encodeAlpha, int whichFilter) { + this(image, encodeAlpha, whichFilter, 0); + } + + + /** + * Class constructor specifying Image source to encode, whether to encode alpha, filter to use, + * and compression level. + * + * @param image A Java Image object + * @param encodeAlpha Encode the alpha channel? false=no; true=yes + * @param whichFilter 0=none, 1=sub, 2=up + * @param compLevel 0..9 + * @see java.awt.Image + */ + public PngEncoder(Image 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 image to be encoded + * + * @param image A Java Image object which uses the DirectColorModel + * @see java.awt.Image + * @see java.awt.image.DirectColorModel + */ + public void setImage(Image image) { + this.image = image; + pngBytes = null; + } + + /** + * Creates an array of bytes that is the PNG equivalent of the current image, specifying + * whether to encode alpha or not. + * + * @param encodeAlpha boolean false=no alpha, true=encode alpha + * @return an array of bytes, or null if there was a problem + */ + public byte[] pngEncode(boolean encodeAlpha) { + byte[] pngIdBytes = {-119, 80, 78, 71, 13, 10, 26, 10}; + + if (image == null) { + return null; + } + width = image.getWidth(null); + height = image.getHeight(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 { + pngBytes = null; + } + return pngBytes; + } + + /** + * Creates an array of bytes that is the PNG equivalent of the current image. + * Alpha encoding is determined by its setting in the constructor. + * + * @return an array of bytes, or null if there was a problem + */ + public byte[] pngEncode() { + return pngEncode(encodeAlpha); + } + + /** + * Set the alpha encoding on or off. + * + * @param encodeAlpha false=no, true=yes + */ + public void setEncodeAlpha(boolean encodeAlpha) { + this.encodeAlpha = encodeAlpha; + } + + /** + * Retrieve alpha encoding status. + * + * @return boolean false=no, true=yes + */ + public boolean getEncodeAlpha() { + return encodeAlpha; + } + + /** + * Set the filter to use + * + * @param whichFilter from constant list + */ + public void setFilter(int whichFilter) { + this.filter = FILTER_NONE; + if (whichFilter <= FILTER_LAST) { + this.filter = whichFilter; + } + } + + /** + * Retrieve filtering scheme + * + * @return int (see constant list) + */ + public int getFilter() { + return filter; + } + + /** + * Set the compression level to use + * + * @param level 0 through 9 + */ + public void setCompressionLevel(int level) { + if (level >= 0 && level <= 9) { + this.compressionLevel = level; + } + } + + /** + * Retrieve compression level + * + * @return int in range 0-9 + */ + public int getCompressionLevel() { + return compressionLevel; + } + + /** + * Increase or decrease the length of a byte array. + * + * @param array The original array. + * @param newLength The length you wish the new array to have. + * @return Array of newly desired length. If shorter than the + * original, the trailing elements are truncated. + */ + protected byte[] resizeByteArray(byte[] array, int newLength) { + byte[] newArray = new byte[newLength]; + int oldLength = array.length; + + System.arraycopy(array, 0, newArray, 0, Math.min(oldLength, newLength)); + return newArray; + } + + /** + * Write an array of bytes into the pngBytes array. + * Note: This routine has the side effect of updating + * maxPos, the largest element written in the array. + * The array is resized by 1000 bytes or the length + * of the data to be written, whichever is larger. + * + * @param data The data to be written into pngBytes. + * @param offset The starting point to write to. + * @return The next place to be written to in the pngBytes array. + */ + protected int writeBytes(byte[] data, int offset) { + maxPos = Math.max(maxPos, offset + data.length); + if (data.length + offset > pngBytes.length) { + pngBytes = resizeByteArray(pngBytes, pngBytes.length + Math.max(1000, data.length)); + } + System.arraycopy(data, 0, pngBytes, offset, data.length); + return offset + data.length; + } + + /** + * Write an array of bytes into the pngBytes array, specifying number of bytes to write. + * Note: This routine has the side effect of updating + * maxPos, the largest element written in the array. + * The array is resized by 1000 bytes or the length + * of the data to be written, whichever is larger. + * + * @param data The data to be written into pngBytes. + * @param nBytes The number of bytes to be written. + * @param offset The starting point to write to. + * @return The next place to be written to in the pngBytes array. + */ + protected int writeBytes(byte[] data, int nBytes, int offset) { + maxPos = Math.max(maxPos, offset + nBytes); + if (nBytes + offset > pngBytes.length) { + pngBytes = resizeByteArray(pngBytes, pngBytes.length + Math.max(1000, nBytes)); + } + System.arraycopy(data, 0, pngBytes, offset, nBytes); + return offset + nBytes; + } + + /** + * Write a two-byte integer into the pngBytes array at a given position. + * + * @param n The integer to be written into pngBytes. + * @param offset The starting point to write to. + * @return The next place to be written to in the pngBytes array. + */ + protected int writeInt2(int n, int offset) { + byte[] temp = {(byte) ((n >> 8) & 0xff), (byte) (n & 0xff)}; + return writeBytes(temp, offset); + } + + /** + * Write a four-byte integer into the pngBytes array at a given position. + * + * @param n The integer to be written into pngBytes. + * @param offset The starting point to write to. + * @return The next place to be written to in the pngBytes array. + */ + protected int writeInt4(int n, int offset) { + byte[] temp = {(byte) ((n >> 24) & 0xff), + (byte) ((n >> 16) & 0xff), + (byte) ((n >> 8) & 0xff), + (byte) (n & 0xff)}; + return writeBytes(temp, offset); + } + + /** + * Write a single byte into the pngBytes array at a given position. + * + * @param b The integer to be written into pngBytes. + * @param offset The starting point to write to. + * @return The next place to be written to in the pngBytes array. + */ + protected int writeByte(int b, int offset) { + byte[] temp = {(byte) b}; + return writeBytes(temp, offset); + } + + /** + * Write a PNG "IHDR" chunk into the pngBytes array. + */ + 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 + bytePos = writeByte((encodeAlpha) ? 6 : 2, bytePos); // direct model + 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); + } + + /** + * Perform "sub" filtering on the given row. + * Uses temporary array leftBytes to store the original values + * of the previous pixels. The array is 16 bytes long, which + * will easily hold two-byte samples plus two-byte alpha. + * + * @param pixels The array holding the scan lines being built + * @param startPos Starting position within pixels of bytes to be filtered. + * @param width Width of a scanline in pixels. + */ + protected void filterSub(byte[] pixels, int startPos, int width) { + int i; + int offset = bytesPerPixel; + int actualStart = startPos + offset; + int nBytes = width * bytesPerPixel; + int leftInsert = offset; + int leftExtract = 0; + + for (i = actualStart; i < startPos + nBytes; i++) { + leftBytes[leftInsert] = pixels[i]; + pixels[i] = (byte) ((pixels[i] - leftBytes[leftExtract]) % 256); + leftInsert = (leftInsert + 1) % 0x0f; + leftExtract = (leftExtract + 1) % 0x0f; + } + } + + /** + * Perform "up" filtering on the given row. + * Side effect: refills the prior row with current row + * + * @param pixels The array holding the scan lines being built + * @param startPos Starting position within pixels of bytes to be filtered. + * @param width Width of a scanline in pixels. + */ + protected void filterUp(byte[] pixels, int startPos, int width) { + int i, nBytes; + byte currentByte; + + nBytes = width * bytesPerPixel; + + for (i = 0; i < nBytes; i++) { + currentByte = pixels[startPos + i]; + pixels[startPos + i] = (byte) ((pixels[startPos + i] - priorRow[i]) % 256); + priorRow[i] = currentByte; + } + } + + /** + * 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 + */ + 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) + + byte[] compressedLines; // the resultant compressed lines + int nCompressed; // how big is the compressed area? + + //int depth; // color depth ( handle only 8 or 32 ) + + PixelGrabber pg; + + bytesPerPixel = (encodeAlpha) ? 4 : 3; + + Deflater scrunch = new Deflater(compressionLevel); + ByteArrayOutputStream outBytes = new ByteArrayOutputStream(1024); + + DeflaterOutputStream compBytes = new DeflaterOutputStream(outBytes, scrunch); + try { + while (rowsLeft > 0) { + nRows = Math.min(32767 / (width * (bytesPerPixel + 1)), rowsLeft); + nRows = Math.max( nRows, 1 ); + + int[] pixels = new int[width * nRows]; + + pg = new PixelGrabber(image, 0, startRow, + width, nRows, pixels, 0, width); + try { + pg.grabPixels(); + } + catch (Exception e) { + System.err.println("interrupted waiting for pixels!"); + return false; + } + if ((pg.getStatus() & ImageObserver.ABORT) != 0) { + System.err.println("image fetch aborted or errored"); + return false; + } + + /* + * 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]; + } + + scanPos = 0; + startPos = 1; + for (int i = 0; i < width * nRows; i++) { + if (i % width == 0) { + scanLines[scanPos++] = (byte) filter; + startPos = scanPos; + } + scanLines[scanPos++] = (byte) ((pixels[i] >> 16) & 0xff); + scanLines[scanPos++] = (byte) ((pixels[i] >> 8) & 0xff); + scanLines[scanPos++] = (byte) ((pixels[i]) & 0xff); + if (encodeAlpha) { + scanLines[scanPos++] = (byte) ((pixels[i] >> 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; + } + } + + /** + * Write a PNG "IEND" chunk into the pngBytes array. + */ + protected void writeEnd() { + bytePos = writeInt4(0, bytePos); + bytePos = writeBytes(IEND, bytePos); + crc.reset(); + crc.update(IEND); + crcValue = crc.getValue(); + bytePos = writeInt4((int) crcValue, bytePos); + } + +} diff --git a/src/org/ibex/xt/PngEncoderB.java b/src/org/ibex/xt/PngEncoderB.java new file mode 100644 index 0000000..44ec6ed --- /dev/null +++ b/src/org/ibex/xt/PngEncoderB.java @@ -0,0 +1,506 @@ +package org.ibex.xt; + +import java.awt.Image; +import java.awt.image.BufferedImage; +import java.awt.image.DataBuffer; +import java.awt.image.IndexColorModel; +import java.awt.image.ImageObserver; +import java.awt.image.PixelGrabber; +import java.awt.image.WritableRaster; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.util.zip.CRC32; +import java.util.zip.Deflater; +import java.util.zip.DeflaterOutputStream; + +/** + * PngEncoderB takes a Java BufferedImage object and creates a byte string which can be saved as a PNG file. + * The encoder will accept BufferedImages with eight-bit samples + * or 4-byte ARGB samples. + * + *

There is also code to handle 4-byte samples returned as + * one int per pixel, but that has not been tested.

+ * + *

Thanks to Jay Denny at KeyPoint Software + * http://www.keypoint.com/ + * who let me develop this code on company time.

+ * + *

You may contact me with (probably very-much-needed) improvements, + * comments, and bug fixes at:

+ * + *

david@catcode.com

+ * + *

This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version.

+ * + *

This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details.

+ * + *

You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * A copy of the GNU LGPL may be found at + * http://www.gnu.org/copyleft/lesser.html

+ * + * @author J. David Eisenberg + * @version 1.5, 19 Oct 2003 + * + * 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) + */ + +public class PngEncoderB extends PngEncoder +{ + + /** PLTE tag. */ + private static final byte PLTE[] = { 80, 76, 84, 69 }; + + protected BufferedImage image; + protected WritableRaster wRaster; + protected int tType; + + /** + * Class constructor + * + */ + public PngEncoderB() + { + this( null, false, FILTER_NONE, 0 ); + } + + /** + * Class constructor specifying BufferedImage to encode, with no alpha channel encoding. + * + * @param image A Java BufferedImage object + */ + public PngEncoderB( 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 PngEncoderB( 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 PngEncoderB( 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 + */ + public PngEncoderB( 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 BufferedImage 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, specifying whether to encode alpha or not. + * + * @param encodeAlpha boolean false=no alpha, true=encode alpha + * @return an array of bytes, or null if there was a problem + */ + public byte[] pngEncode( boolean encodeAlpha ) + { + byte[] pngIdBytes = { -119, 80, 78, 71, 13, 10, 26, 10 }; + int i; + + if (image == null) + { + System.err.println("pngEncode: image is null; returning null"); + return null; + } + width = image.getWidth( null ); + height = image.getHeight( null ); + this.image = image; + + 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; + } + + /** + * Creates an array of bytes that is the PNG equivalent of the current image. + * Alpha encoding is determined by its setting in the constructor. + * + * @return an array of bytes, or null if there was a problem + */ + public byte[] pngEncode() + { + return pngEncode( encodeAlpha ); + } + + /** + * + * 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 + */ + protected 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. + */ + 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 ); + } + + protected 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 ); + } + + /** + * 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 + */ + 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() ); + } + + 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; + } + } + +} + diff --git a/src/org/ibex/xt/Servlet.java b/src/org/ibex/xt/Servlet.java index 7a2b58f..0265e23 100644 --- a/src/org/ibex/xt/Servlet.java +++ b/src/org/ibex/xt/Servlet.java @@ -54,24 +54,26 @@ public class Servlet extends HttpServlet { public class ServletScope extends JSSubProperties { HttpServletRequest request; HttpServletResponse response; + HttpSession session; ServletContext cx; public String getRealPath(String s) { return cx.getRealPath(s); } public ServletScope(ServletRequest request, ServletResponse response, ServletContext cx) { super(); this.request = (HttpServletRequest)request; this.response = (HttpServletResponse)response; + this.session = this.request.getSession(); this.cx = cx; } - // FIXME: setattributes - //#repeat params/cookies/requestHeader/attributes \ - // getParameter/getCookie/getHeader/getAttribute \ - // getParameterNames/getCookieNames/getHeaderNames/getAttributeNames \ - // request/request/request/session/response \ - // setParameter/setCookie/setHeader/setAttribute + // FIXME: setattributes, cookies + //#repeat params/requestHeader/attributes \ + // getParameter/getHeader/getAttribute \ + // getParameterNames/getHeaderNames/getAttributeNames \ + // request/request/session/response \ + // setAttribute/setAttribute/setAttribute private JS params = new JS.Obj() { public JS get(JS key) throws JSExn { return JSU.S(request.getParameter(JSU.toString(key)).toString()); } - public void put(JS key, JS val) throws JSExn { request.setParameter(JSU.toString(key), JSU.toString(val)); } + public void put(JS key, JS val) throws JSExn { request.setAttribute(JSU.toString(key), JSU.toString(val)); } public Enumeration keys() throws JSExn { return new JS.Enumeration.JavaStringEnumeration(request.getParameterNames()); } }; @@ -92,9 +94,9 @@ public class Servlet extends HttpServlet { case "request.ssl": return JSU.B(request.isSecure()); case "request.path": return JSU.S(request.getPathInfo()); case "response": return SUBPROPERTY; - case "response.header": return responseHeader; + case "form": return SUBPROPERTY; + case "form.fields": return currentForm.fields(); case "session": return SUBPROPERTY; - case "session.attr": return sessionAttributes; case "session.created": return new JSDate(request.getSession(true).getCreationTime()); case "session.accessed": return new JSDate(request.getSession(true).getLastAccessedTime()); case "session.invalidate": return METHOD; @@ -103,11 +105,13 @@ public class Servlet extends HttpServlet { case "context": return SUBPROPERTY; case "context.list": return METHOD; case "params": return params; - case "cookie": return cookies; //#end + // cookie, responseheader needed, session.attributes return null; } + Form currentForm = null; + public void put(JS key, JS val) throws JSExn { try { //#switch(JSU.toString(key)) diff --git a/src/org/ibex/xt/Template.java b/src/org/ibex/xt/Template.java index 000670f..e941364 100644 --- a/src/org/ibex/xt/Template.java +++ b/src/org/ibex/xt/Template.java @@ -88,7 +88,8 @@ public class Template extends Node.Stream.Filter implements Node.Stream.Functor public boolean __read(final Node n) { if (!upstreamRead(n)) return false; if (n.cdata != null) return true; - final String uri = n.uri; + String uri = n.uri; + if (uri == null) uri = "http://www.w3.org/1999/xhtml"; // FIXME FIXME FIXME!!!! final String name = n.name; if (uri.indexOf(':') == -1) throw new RuntimeException("uri does not contain a colon: " + uri + " (tag name " + name + ")"); @@ -98,11 +99,15 @@ public class Template extends Node.Stream.Filter implements Node.Stream.Functor } else if (method.equals("webinf")) { return graft(newTemplate(servletscope, copyNodeToScope(transform(n, scope), new Scope(servletscope)), servletscope.getRealPath("/") + "/WEB-INF/" + rest + name), n).upstreamRead(n); + } else if (uri.equals("http://xt.ibex.org/form")) { + return graft(new InputTag(n.name), n).upstreamRead(n); } else if (uri.equals("http://xt.ibex.org/")) { //#switch(name) + case "form": return graft(new FormTag(n.attr("class")), n).upstreamRead(n); + case "input": return graft(new InputTag(n.attr("field")), n).upstreamRead(n); case "if": transform(n, scope); - return graft((Node.Stream.Functor)("true".equals(n.attr("if")) ? new DropTag() : new DropAll()), n).upstreamRead(n); + return graft((Node.Stream.Functor)("true".equals(n.attr("if"))?new DropTag():new DropAll()), n).upstreamRead(n); case "js": return graft(new JsTag(scope), n).upstreamRead(n); case "foreach": return graft(new ForEach(n, scope), n).upstreamRead(n); case "children": @@ -158,6 +163,42 @@ public class Template extends Node.Stream.Filter implements Node.Stream.Functor } } + private class InputTag implements Node.Stream.Functor { + final String fieldName; + public InputTag(String fieldName) { this.fieldName = fieldName; } + public Node.Stream wrap(Node.Stream kids) { + try { + return new Node.Stream.FromXML(new StringReader(servletscope.currentForm.emit(fieldName))); + } catch (Exception e) { Log.warn(this, e); return null; } + } + } + + private class FormTag implements Node.Stream.Functor { + final String classname; + public FormTag(String classname) { this.classname = classname; } + public Node.Stream wrap(final Node.Stream kids) { + try { + servletscope.currentForm = (Form)Class.forName(classname).newInstance(); + Log.warn("emit", servletscope.currentForm.emit()); + return new Node.Stream() { + boolean done = false; + public boolean _read(Node n) { + if (done) return kids._read(n); + done = true; + n.clear(); + n.name = "form"; + n.uri = "http://www.w3.org/1999/xhtml"; + n.numattrs = 1; + n.attrs = new String[] { "action", "/servlet/"+classname }; + return true; + } + }; + } catch (Exception e) { + Log.warn(this, e); + return null; + } + } } + private class DropTag implements Node.Stream.Functor { public Node.Stream wrap(Node.Stream kids) { return kids; diff --git a/src/org/ibex/xt/Text.java b/src/org/ibex/xt/Text.java deleted file mode 100644 index 10ba9e0..0000000 --- a/src/org/ibex/xt/Text.java +++ /dev/null @@ -1,63 +0,0 @@ -// Decompiled by Jad v1.5.8c. Copyright 2001 Pavel Kouznetsov. -// Jad home page: http://www.geocities.com/kpdus/jad.html -// Decompiler options: packimports(3) -// Source File Name: Text.java - -package org.ibex.xt; - -import java.io.IOException; -import javax.servlet.*; -import javax.servlet.http.*; -import magick.*; - -public class Text extends HttpServlet -{ - - public Text() - { - } - - public void init(ServletConfig servletconfig) - { - cx = servletconfig.getServletContext(); - } - - public void doPost(HttpServletRequest httpservletrequest, HttpServletResponse httpservletresponse) - throws IOException, ServletException - { - doGet(httpservletrequest, httpservletresponse); - } - - public void doGet(HttpServletRequest httpservletrequest, HttpServletResponse httpservletresponse) - throws IOException, ServletException - { - try - { - ImageInfo imageinfo = new ImageInfo(); - MagickImage magickimage = new MagickImage(); - DrawInfo drawinfo = new DrawInfo(imageinfo); - int i = Integer.parseInt(httpservletrequest.getParameter("width")); - int j = Integer.parseInt(httpservletrequest.getParameter("height")); - magickimage.constituteImage(i, j, "ARGB", new int[i * j * 4]); - drawinfo.setFill(PixelPacket.queryColorDatabase(httpservletrequest.getParameter("color"))); - drawinfo.setOpacity(0); - drawinfo.setPointsize(Integer.parseInt(httpservletrequest.getParameter("size"))); - drawinfo.setFont((new StringBuilder()).append("/usr/local/fonts/").append(httpservletrequest.getParameter("font")).append(".ttf").toString()); - drawinfo.setTextAntialias(true); - drawinfo.setText(httpservletrequest.getParameter("text")); - drawinfo.setGeometry((new StringBuilder()).append("+0+").append(j / 2).toString()); - magickimage.annotateImage(drawinfo); - magickimage.setImageFormat("png"); - byte abyte0[] = magickimage.imageToBlob(imageinfo); - httpservletresponse.setContentType("image/png"); - httpservletresponse.getOutputStream().write(abyte0); - } - catch(Exception exception) - { - exception.printStackTrace(); - throw new ServletException(exception); - } - } - - private ServletContext cx; -} -- 1.7.10.4