tons of changes
authoradam <adam@megacz.com>
Fri, 18 Mar 2005 09:11:11 +0000 (09:11 +0000)
committeradam <adam@megacz.com>
Fri, 18 Mar 2005 09:11:11 +0000 (09:11 +0000)
darcs-hash:20050318091111-5007d-b974593492c381303bfc5fd5b7529bf5376c6b6a.gz

src/org/ibex/xt/ImageConverter.java
src/org/ibex/xt/Node.java
src/org/ibex/xt/PSDReader.java [new file with mode: 0644]
src/org/ibex/xt/PngEncoder.java [new file with mode: 0644]
src/org/ibex/xt/PngEncoderB.java [new file with mode: 0644]
src/org/ibex/xt/Servlet.java
src/org/ibex/xt/Template.java
src/org/ibex/xt/Text.java [deleted file]

index 49af984..7d1c942 100644 (file)
@@ -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);
         }
index 04e7ee6..b4c6026 100644 (file)
@@ -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 (file)
index 0000000..c661d25
--- /dev/null
@@ -0,0 +1,553 @@
+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
diff --git a/src/org/ibex/xt/PngEncoder.java b/src/org/ibex/xt/PngEncoder.java
new file mode 100644 (file)
index 0000000..644fc17
--- /dev/null
@@ -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.
+ *
+ * <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);
+    }
+
+}
diff --git a/src/org/ibex/xt/PngEncoderB.java b/src/org/ibex/xt/PngEncoderB.java
new file mode 100644 (file)
index 0000000..44ec6ed
--- /dev/null
@@ -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. 
+ * 
+ * <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;
+        }
+    }
+
+}
+
index 7a2b58f..0265e23 100644 (file)
@@ -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))
index 000670f..e941364 100644 (file)
@@ -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 (file)
index 10ba9e0..0000000
+++ /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;
-}