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);
}
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); }
}
}
+ 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;
--- /dev/null
+package org.ibex.xt;\r
+import java.awt.*;\r
+import java.awt.image.*;\r
+import java.net.*;\r
+import java.io.*;\r
+\r
+/**\r
+ * Class PSDReader - Decodes a PhotoShop (.psd) file into one or more frames.\r
+ * Supports uncompressed or RLE-compressed RGB files only. Each layer may be\r
+ * retrieved as a full frame BufferedImage, or as a smaller image with an\r
+ * offset if the layer does not occupy the full frame size. Transparency\r
+ * in the original psd file is preserved in the returned BufferedImage's.\r
+ * Does not support additional features in PS versions higher than 3.0.\r
+ * Example:\r
+ * <br><pre>\r
+ * PSDReader r = new PSDReader();\r
+ * r.read("sample.psd");\r
+ * int n = r.getFrameCount();\r
+ * for (int i = 0; i < n; i++) {\r
+ * BufferedImage image = r.getLayer(i);\r
+ * Point offset = r.getLayerOffset(i);\r
+ * // do something with image\r
+ * }\r
+ * </pre>\r
+ * No copyright asserted on the source code of this class. May be used for\r
+ * any purpose. Please forward any corrections to kweiner@fmsware.com.\r
+ *\r
+ * @author Kevin Weiner, FM Software.\r
+ * @version 1.1 January 2004 [bug fix; add RLE support]\r
+ *\r
+ */\r
+public class PSDReader {\r
+\r
+ /**\r
+ * File read status: No errors.\r
+ */\r
+ public static final int STATUS_OK = 0;\r
+\r
+ /**\r
+ * File read status: Error decoding file (may be partially decoded)\r
+ */\r
+ public static final int STATUS_FORMAT_ERROR = 1;\r
+\r
+ /**\r
+ * File read status: Unable to open source.\r
+ */\r
+ public static final int STATUS_OPEN_ERROR = 2;\r
+\r
+ /**\r
+ * File read status: Unsupported format\r
+ */\r
+ public static final int STATUS_UNSUPPORTED = 3;\r
+\r
+ public static int ImageType = BufferedImage.TYPE_INT_ARGB;\r
+\r
+ protected BufferedInputStream input;\r
+ protected int frameCount;\r
+ protected BufferedImage[] frames;\r
+ protected int status = 0;\r
+ protected int nChan;\r
+ protected int width;\r
+ protected int height;\r
+ protected int nLayers;\r
+ protected int miscLen;\r
+ protected boolean hasLayers;\r
+ protected LayerInfo[] layers;\r
+ protected short[] lineLengths;\r
+ protected int lineIndex;\r
+ protected boolean rleEncoded;\r
+ \r
+ protected class LayerInfo {\r
+ int x, y, w, h;\r
+ int nChan;\r
+ int[] chanID;\r
+ int alpha;\r
+ }\r
+\r
+ /**\r
+ * Gets the number of layers read from file.\r
+ * @return frame count\r
+ */\r
+ public int getFrameCount() {\r
+ return frameCount;\r
+ }\r
+ \r
+ protected void setInput(InputStream stream) {\r
+ // open input stream\r
+ init();\r
+ if (stream == null) {\r
+ status = STATUS_OPEN_ERROR;\r
+ } else {\r
+ if (stream instanceof BufferedInputStream)\r
+ input = (BufferedInputStream) stream;\r
+ else\r
+ input = new BufferedInputStream(stream);\r
+ }\r
+ }\r
+ \r
+ protected void setInput(String name) {\r
+ // open input file\r
+ init();\r
+ try {\r
+ name = name.trim();\r
+ if (name.startsWith("file:")) {\r
+ name = name.substring(5);\r
+ while (name.startsWith("/"))\r
+ name = name.substring(1);\r
+ }\r
+ if (name.indexOf("://") > 0) {\r
+ URL url = new URL(name);\r
+ input = new BufferedInputStream(url.openStream());\r
+ } else {\r
+ input = new BufferedInputStream(new FileInputStream(name));\r
+ }\r
+ } catch (IOException e) {\r
+ status = STATUS_OPEN_ERROR;\r
+ }\r
+ }\r
+ \r
+ /**\r
+ * Gets display duration for specified frame. Always returns 0.\r
+ *\r
+ */\r
+ public int getDelay(int forFrame) {\r
+ return 0;\r
+ }\r
+ \r
+ /**\r
+ * Gets the image contents of frame n. Note that this expands the image\r
+ * to the full frame size (if the layer was smaller) and any subsequent\r
+ * use of getLayer() will return the full image.\r
+ *\r
+ * @return BufferedImage representation of frame, or null if n is invalid.\r
+ */\r
+ public BufferedImage getFrame(int n) {\r
+ BufferedImage im = null;\r
+ if ((n >= 0) && (n < nLayers)) {\r
+ im = frames[n];\r
+ LayerInfo info = layers[n];\r
+ if ((info.w != width) || (info.h != height)) {\r
+ BufferedImage temp =\r
+ new BufferedImage(width, height, ImageType);\r
+ Graphics2D gc = temp.createGraphics();\r
+ gc.drawImage(im, info.x, info.y, null);\r
+ gc.dispose();\r
+ im = temp;\r
+ frames[n] = im;\r
+ }\r
+ }\r
+ return im;\r
+ }\r
+ \r
+ /**\r
+ * Gets maximum image size. Individual layers may be smaller.\r
+ *\r
+ * @return maximum image dimensions\r
+ */\r
+ public Dimension getFrameSize() {\r
+ return new Dimension(width, height);\r
+ }\r
+ \r
+ /**\r
+ * Gets the first (or only) image read.\r
+ *\r
+ * @return BufferedImage containing first frame, or null if none.\r
+ */\r
+ public BufferedImage getImage() {\r
+ return getFrame(0);\r
+ }\r
+ \r
+ /**\r
+ * Gets the image contents of layer n. May be smaller than full frame\r
+ * size - use getFrameOffset() to obtain position of subimage within\r
+ * main image area.\r
+ *\r
+ * @return BufferedImage representation of layer, or null if n is invalid.\r
+ */\r
+ public BufferedImage getLayer(int n) {\r
+ BufferedImage im = null;\r
+ if ((n >= 0) && (n < nLayers)) {\r
+ im = frames[n];\r
+ }\r
+ return im;\r
+ }\r
+ \r
+ /**\r
+ * Gets the subimage offset of layer n if it is smaller than the\r
+ * full frame size.\r
+ *\r
+ * @return Point indicating offset from upper left corner of frame.\r
+ */\r
+ public Point getLayerOffset(int n) {\r
+ Point p = null;\r
+ if ((n >= 0) && (n < nLayers)) {\r
+ int x = layers[n].x;\r
+ int y = layers[n].y;\r
+ p = new Point(x, y);\r
+ }\r
+ if (p == null) {\r
+ p = new Point(0, 0);\r
+ }\r
+ return p;\r
+ }\r
+ \r
+ /**\r
+ * Reads PhotoShop layers from stream.\r
+ *\r
+ * @param InputStream in PhotoShop format.\r
+ * @return read status code (0 = no errors)\r
+ */\r
+ public int read(InputStream stream) {\r
+ setInput(stream);\r
+ process();\r
+ return status;\r
+ }\r
+ \r
+ /**\r
+ * Reads PhotoShop file from specified source (file or URL string)\r
+ *\r
+ * @param name String containing source\r
+ * @return read status code (0 = no errors)\r
+ */\r
+ public int read(String name) {\r
+ setInput(name);\r
+ process();\r
+ return status;\r
+ }\r
+ \r
+ /**\r
+ * Closes input stream and discards contents of all frames.\r
+ *\r
+ */\r
+ public void reset() {\r
+ init();\r
+ }\r
+ \r
+ protected void close() {\r
+ if (input != null) {\r
+ try {\r
+ input.close();\r
+ } catch (Exception e) {}\r
+ input = null;\r
+ }\r
+ }\r
+ protected boolean err() {\r
+ return status != STATUS_OK;\r
+ }\r
+ \r
+ protected byte[] fillBytes(int size, int value) {\r
+ // create byte array filled with given value\r
+ byte[] b = new byte[size];\r
+ if (value != 0) {\r
+ byte v = (byte) value;\r
+ for (int i = 0; i < size; i++) {\r
+ b[i] = v;\r
+ }\r
+ }\r
+ return b;\r
+ }\r
+ \r
+ protected void init() {\r
+ close();\r
+ frameCount = 0;\r
+ frames = null;\r
+ layers = null;\r
+ hasLayers = true;\r
+ status = STATUS_OK;\r
+ }\r
+ \r
+ protected void makeDummyLayer() {\r
+ // creat dummy layer for non-layered image\r
+ rleEncoded = readShort() == 1;\r
+ hasLayers = false;\r
+ nLayers = 1;\r
+ layers = new LayerInfo[1];\r
+ LayerInfo layer = new LayerInfo();\r
+ layers[0] = layer;\r
+ layer.h = height;\r
+ layer.w = width;\r
+ int nc = Math.min(nChan, 4);\r
+ if (rleEncoded) {\r
+ // get list of rle encoded line lengths for all channels\r
+ readLineLengths(height * nc);\r
+ }\r
+ layer.nChan = nc;\r
+ layer.chanID = new int[nc];\r
+ for (int i = 0; i < nc; i++) {\r
+ int id = i;\r
+ if (i == 3) id = -1;\r
+ layer.chanID[i] = id;\r
+ }\r
+ }\r
+\r
+ protected void readLineLengths(int nLines) {\r
+ // read list of rle encoded line lengths\r
+ lineLengths = new short[nLines];\r
+ for (int i = 0; i < nLines; i++) {\r
+ lineLengths[i] = readShort();\r
+ }\r
+ lineIndex = 0;\r
+ }\r
+\r
+ protected BufferedImage makeImage(int w, int h, byte[] r, byte[] g, byte[] b, byte[] a) {\r
+ // create image from given plane data\r
+ BufferedImage im = new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB);\r
+ int[] data = ((DataBufferInt) im.getRaster().getDataBuffer()).getData();\r
+ int n = w * h;\r
+ int j = 0;\r
+ while (j < n) {\r
+ try {\r
+ int ac = a[j] & 0xff;\r
+ int rc = r[j] & 0xff;\r
+ int gc = g[j] & 0xff;\r
+ int bc = b[j] & 0xff;\r
+ data[j] = (((((ac << 8) | rc) << 8) | gc) << 8) | bc;\r
+ } catch (Exception e) {}\r
+ j++;\r
+ }\r
+ return im;\r
+ }\r
+ \r
+ protected void process() {\r
+ // decode PSD file\r
+ if (err()) return;\r
+ readHeader();\r
+ if (err()) return;\r
+ readLayerInfo();\r
+ if (err()) return;\r
+ if (nLayers == 0) {\r
+ makeDummyLayer();\r
+ if (err()) return;\r
+ }\r
+ readLayers();\r
+ }\r
+ \r
+ protected int readByte() {\r
+ // read single byte from input\r
+ int curByte = 0;\r
+ try {\r
+ curByte = input.read();\r
+ } catch (IOException e) {\r
+ status = STATUS_FORMAT_ERROR;\r
+ }\r
+ return curByte;\r
+ }\r
+\r
+ protected int readBytes(byte[] bytes, int n) {\r
+ // read multiple bytes from input\r
+ if (bytes == null) return 0;\r
+ int r = 0;\r
+ try {\r
+ r = input.read(bytes, 0, n);\r
+ } catch (IOException e) {\r
+ status = STATUS_FORMAT_ERROR;\r
+ }\r
+ if (r < n) {\r
+ status = STATUS_FORMAT_ERROR;\r
+ }\r
+ return r;\r
+ }\r
+ \r
+ protected void readHeader() {\r
+ // read PSD header info\r
+ String sig = readString(4);\r
+ int ver = readShort();\r
+ skipBytes(6);\r
+ nChan = readShort();\r
+ height = readInt();\r
+ width = readInt();\r
+ int depth = readShort();\r
+ int mode = readShort();\r
+ int cmLen = readInt();\r
+ skipBytes(cmLen);\r
+ int imResLen = readInt();\r
+ skipBytes(imResLen);\r
+\r
+ // require 8-bit RGB data\r
+ if ((!sig.equals("8BPS")) || (ver != 1)) {\r
+ status = STATUS_FORMAT_ERROR;\r
+ } else if ((depth != 8) || (mode != 3)) {\r
+ status = STATUS_UNSUPPORTED;\r
+ }\r
+ }\r
+ \r
+ protected int readInt() {\r
+ // read big-endian 32-bit integer\r
+ return (((((readByte() << 8) | readByte()) << 8) | readByte()) << 8)\r
+ | readByte();\r
+ }\r
+ \r
+ protected void readLayerInfo() {\r
+ // read layer header info\r
+ miscLen = readInt();\r
+ if (miscLen == 0) {\r
+ return; // no layers, only base image\r
+ }\r
+ int layerInfoLen = readInt();\r
+ nLayers = readShort();\r
+ if (nLayers > 0) {\r
+ layers = new LayerInfo[nLayers];\r
+ }\r
+ for (int i = 0; i < nLayers; i++) {\r
+ LayerInfo info = new LayerInfo();\r
+ layers[i] = info;\r
+ info.y = readInt();\r
+ info.x = readInt();\r
+ info.h = readInt() - info.y;\r
+ info.w = readInt() - info.x;\r
+ info.nChan = readShort();\r
+ info.chanID = new int[info.nChan];\r
+ for (int j = 0; j < info.nChan; j++) {\r
+ int id = readShort();\r
+ int size = readInt();\r
+ info.chanID[j] = id;\r
+ }\r
+ String s = readString(4);\r
+ if (!s.equals("8BIM")) {\r
+ status = STATUS_FORMAT_ERROR;\r
+ return;\r
+ }\r
+ skipBytes(4); // blend mode\r
+ info.alpha = readByte();\r
+ int clipping = readByte();\r
+ int flags = readByte();\r
+ readByte(); // filler\r
+ int extraSize = readInt();\r
+ skipBytes(extraSize);\r
+ }\r
+ }\r
+ \r
+ protected void readLayers() {\r
+ // read and convert each layer to BufferedImage\r
+ frameCount = nLayers;\r
+ frames = new BufferedImage[nLayers];\r
+ for (int i = 0; i < nLayers; i++) {\r
+ LayerInfo info = layers[i];\r
+ byte[] r = null, g = null, b = null, a = null;\r
+ for (int j = 0; j < info.nChan; j++) {\r
+ int id = info.chanID[j];\r
+ switch (id) {\r
+ case 0 : r = readPlane(info.w, info.h); break;\r
+ case 1 : g = readPlane(info.w, info.h); break;\r
+ case 2 : b = readPlane(info.w, info.h); break;\r
+ case -1 : a = readPlane(info.w, info.h); break;\r
+ default : readPlane(info.w, info.h);\r
+ }\r
+ if (err()) break;\r
+ }\r
+ if (err()) break;\r
+ int n = info.w * info.h;\r
+ if (r == null) r = fillBytes(n, 0);\r
+ if (g == null) g = fillBytes(n, 0);\r
+ if (b == null) b = fillBytes(n, 0);\r
+ if (a == null) a = fillBytes(n, 255);\r
+\r
+ BufferedImage im = makeImage(info.w, info.h, r, g, b, a);\r
+ frames[i] = im;\r
+ }\r
+ lineLengths = null;\r
+ if ((miscLen > 0) && !err()) {\r
+ int n = readInt(); // global layer mask info len\r
+ skipBytes(n);\r
+ }\r
+ }\r
+ \r
+ protected byte[] readPlane(int w, int h) {\r
+ // read a single color plane\r
+ byte[] b = null;\r
+ int size = w * h;\r
+ if (hasLayers) {\r
+ // get RLE compression info for channel\r
+ rleEncoded = readShort() == 1;\r
+ if (rleEncoded) {\r
+ // list of encoded line lengths\r
+ readLineLengths(h);\r
+ }\r
+ }\r
+\r
+ if (rleEncoded) {\r
+ b = readPlaneCompressed(w, h);\r
+ } else {\r
+ b = new byte[size];\r
+ readBytes(b, size);\r
+ }\r
+\r
+ return b;\r
+\r
+ }\r
+\r
+ protected byte[] readPlaneCompressed(int w, int h) {\r
+ byte[] b = new byte[w * h];\r
+ byte[] s = new byte[w * 2];\r
+ int pos = 0;\r
+ for (int i = 0; i < h; i++) {\r
+ if (lineIndex >= lineLengths.length) {\r
+ status = STATUS_FORMAT_ERROR;\r
+ return null;\r
+ }\r
+ int len = lineLengths[lineIndex++];\r
+ readBytes(s, len);\r
+ decodeRLE(s, 0, len, b, pos);\r
+ pos += w;\r
+ }\r
+ return b;\r
+ }\r
+\r
+ protected void decodeRLE(byte[] src, int sindex, int slen, byte[] dst, int dindex) {\r
+ try {\r
+ int max = sindex + slen;\r
+ while (sindex < max) {\r
+ byte b = src[sindex++];\r
+ int n = (int) b;\r
+ if (n < 0) {\r
+ // dup next byte 1-n times\r
+ n = 1 - n;\r
+ b = src[sindex++];\r
+ for (int i = 0; i < n; i++) {\r
+ dst[dindex++] = b;\r
+ }\r
+ } else {\r
+ // copy next n+1 bytes\r
+ n = n + 1;\r
+ System.arraycopy(src, sindex, dst, dindex, n);\r
+ dindex += n;\r
+ sindex += n;\r
+ }\r
+ }\r
+ } catch (Exception e) {\r
+ status = STATUS_FORMAT_ERROR;\r
+ }\r
+ }\r
+ \r
+ protected short readShort() {\r
+ // read big-endian 16-bit integer\r
+ return (short) ((readByte() << 8) | readByte());\r
+ }\r
+ \r
+ protected String readString(int len) {\r
+ // read string of specified length\r
+ String s = "";\r
+ for (int i = 0; i < len; i++) {\r
+ s = s + (char) readByte();\r
+ }\r
+ return s;\r
+ }\r
+ \r
+ protected void skipBytes(int n) {\r
+ // skip over n input bytes\r
+ for (int i = 0; i < n; i++) {\r
+ readByte();\r
+ }\r
+ }\r
+}\r
--- /dev/null
+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.
+ *
+ * <p>Thanks to Jay Denny at KeyPoint Software
+ * http://www.keypoint.com/
+ * who let me develop this code on company time.</p>
+ *
+ * <p>You may contact me with (probably very-much-needed) improvements,
+ * comments, and bug fixes at:</p>
+ *
+ * <p><code>david@catcode.com</code></p>
+ *
+ * <p>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.</p>
+ *
+ * <p>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.</p>
+ *
+ * <p>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
+ * <code>http://www.gnu.org/copyleft/lesser.html</code></p>
+ *
+ * @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);
+ }
+
+}
--- /dev/null
+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.
+ *
+ * <p>There is also code to handle 4-byte samples returned as
+ * one int per pixel, but that has not been tested.</p>
+ *
+ * <p>Thanks to Jay Denny at KeyPoint Software
+ * <code>http://www.keypoint.com/</code>
+ * who let me develop this code on company time.</p>
+ *
+ * <p>You may contact me with (probably very-much-needed) improvements,
+ * comments, and bug fixes at:</p>
+ *
+ * <p><code>david@catcode.com</code></p>
+ *
+ * <p>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.<p>
+ *
+ * <p>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.</p>
+ *
+ * <p>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
+ * <code>http://www.gnu.org/copyleft/lesser.html</code></p>
+ *
+ * @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<width*nRows; i++)
+ {
+ if (i % width == 0)
+ {
+ scanLines[scanPos++] = (byte) filter;
+ startPos = scanPos;
+ }
+
+ if (bytesPerPixel == 1) // assume TYPE_BYTE, indexed
+ {
+ scanLines[scanPos++] = pixels[readPos++];
+ }
+ else if (tType == DataBuffer.TYPE_BYTE)
+ {
+ scanLines[scanPos++] = pixels[readPos++];
+ scanLines[scanPos++] = pixels[readPos++];
+ scanLines[scanPos++] = pixels[readPos++];
+ if (encodeAlpha)
+ {
+ scanLines[scanPos++] = pixels[readPos++];
+ }
+ else
+ {
+ readPos++;
+ }
+ }
+ else if (tType == DataBuffer.TYPE_USHORT)
+ {
+ short pxl = sPixels[readPos++];
+ if (type == BufferedImage.TYPE_USHORT_565_RGB) {
+ scanLines[scanPos++] = (byte) ((pxl >> 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;
+ }
+ }
+
+}
+
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()); }
};
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;
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))
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 + ")");
} 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":
}
}
+ 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;
+++ /dev/null
-// 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;
-}