changeset 2:57151e2793a2

More common modules from dwt-linux
author Frank Benoit <benoit@tionex.de>
date Wed, 23 Jan 2008 12:01:46 +0100
parents 8ac7199abbc4
children 20e70c5494d7
files doc/Common.txt dwt/internal/CloneableCompatibility.d dwt/internal/Compatibility.d dwt/internal/DWTEventListener.d dwt/internal/DWTEventObject.d dwt/internal/Library.d dwt/internal/Lock.d dwt/internal/SerializableCompatibility.d dwt/internal/image/FileFormat.d dwt/internal/image/GIFFileFormat.d dwt/internal/image/JPEGAppn.d dwt/internal/image/JPEGArithmeticConditioningTable.d dwt/internal/image/JPEGComment.d dwt/internal/image/JPEGDecoder.d dwt/internal/image/JPEGEndOfImage.d dwt/internal/image/JPEGFileFormat.d dwt/internal/image/JPEGFixedSizeSegment.d dwt/internal/image/JPEGFrameHeader.d dwt/internal/image/JPEGHuffmanTable.d dwt/internal/image/JPEGQuantizationTable.d dwt/internal/image/JPEGRestartInterval.d dwt/internal/image/JPEGScanHeader.d dwt/internal/image/JPEGSegment.d dwt/internal/image/JPEGStartOfImage.d dwt/internal/image/JPEGVariableSizeSegment.d dwt/internal/image/LEDataInputStream.d dwt/internal/image/LEDataOutputStream.d dwt/internal/image/LZWCodec.d dwt/internal/image/LZWNode.d dwt/internal/image/OS2BMPFileFormat.d dwt/internal/image/PNGFileFormat.d dwt/internal/image/PngChunk.d dwt/internal/image/PngChunkReader.d dwt/internal/image/PngDecodingDataStream.d dwt/internal/image/PngDeflater.d dwt/internal/image/PngEncoder.d dwt/internal/image/PngFileReadState.d dwt/internal/image/PngHuffmanTable.d dwt/internal/image/PngHuffmanTables.d dwt/internal/image/PngIdatChunk.d dwt/internal/image/PngIendChunk.d dwt/internal/image/PngIhdrChunk.d dwt/internal/image/PngInputStream.d dwt/internal/image/PngLzBlockReader.d dwt/internal/image/PngPlteChunk.d dwt/internal/image/PngTrnsChunk.d dwt/internal/image/TIFFDirectory.d dwt/internal/image/TIFFFileFormat.d dwt/internal/image/TIFFModifiedHuffmanCodec.d dwt/internal/image/TIFFRandomFileAccess.d dwt/internal/image/WinBMPFileFormat.d dwt/internal/image/WinICOFileFormat.d
diffstat 52 files changed, 17752 insertions(+), 1 deletions(-) [+]
line wrap: on
line diff
--- a/doc/Common.txt	Mon Jan 21 06:40:44 2008 -0800
+++ b/doc/Common.txt	Wed Jan 23 12:01:46 2008 +0100
@@ -5,4 +5,18 @@
 Dwt-linux
 changeset:   115:52b32f5cb1e0
 
-A list of modules will follow later.
\ No newline at end of file
+A list of modules will follow later.
+
+
+Base on dwt-linux changeset [145:d4b244e1e1ae]
+dwt/internal/image/*
+dwt/internal/CloneableCompatibility.d
+dwt/internal/Compatibility.d
+dwt/internal/DWTEventListener.d
+dwt/internal/DWTEventObject.d
+dwt/internal/image
+dwt/internal/Library.d
+dwt/internal/Lock.d
+dwt/internal/SerializableCompatibility.d
+
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dwt/internal/CloneableCompatibility.d	Wed Jan 23 12:01:46 2008 +0100
@@ -0,0 +1,31 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2003 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *     IBM Corporation - initial API and implementation
+ * Port to the D programming language:
+ *     Frank Benoit <benoit@tionex.de>
+ *******************************************************************************/
+module dwt.internal.CloneableCompatibility;
+
+//PORTING_TYPE
+interface Cloneable{}
+
+/**
+ * This interface is the cross-platform version of the
+ * java.lang.Cloneable interface.
+ * <p>
+ * It is part of our effort to provide support for both J2SE
+ * and J2ME platforms. Under this scheme, classes need to
+ * implement CloneableCompatibility instead of java.lang.Cloneable.
+ * </p>
+ * <p>
+ * Note: java.lang.Cloneable is not part of CLDC.
+ * </p>
+ */
+public interface CloneableCompatibility : Cloneable {
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dwt/internal/Compatibility.d	Wed Jan 23 12:01:46 2008 +0100
@@ -0,0 +1,356 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2006 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *     IBM Corporation - initial API and implementation
+ * Port to the D programming language:
+ *     Frank Benoit <benoit@tionex.de>
+ *******************************************************************************/
+module dwt.internal.Compatibility;
+
+/+
+import java.io.*;
+import java.text.MessageFormat;
+import java.util.MissingResourceException;
+import java.util.ResourceBundle;
+import java.util.zip.InflaterInputStream;
++/
+
+import dwt.DWT;
+public import dwt.dwthelper.FileInputStream;
+public import dwt.dwthelper.FileOutputStream;
+public import dwt.dwthelper.InflaterInputStream;
+
+import Math = tango.math.Math;
+import Unicode = tango.text.Unicode;
+import tango.sys.Process;
+
+/**
+ * This class is a placeholder for utility methods commonly
+ * used on J2SE platforms but not supported on some J2ME
+ * profiles.
+ * <p>
+ * It is part of our effort to provide support for both J2SE
+ * and J2ME platforms.
+ * </p>
+ * <p>
+ * IMPORTANT: some of the methods have been modified from their
+ * J2SE parents. Refer to the description of each method for
+ * specific changes.
+ * </p>
+ * <ul>
+ * <li>Exceptions thrown may differ since J2ME's set of
+ * exceptions is a subset of J2SE's one.
+ * </li>
+ * <li>The range of the mathematic functions is subject to
+ * change.
+ * </li>
+ * </ul>
+ */
+public final class Compatibility {
+
+/**
+ * Returns the PI constant as a double.
+ */
+public static const real PI = Math.PI;
+
+static const real toRadians = PI / 180;
+
+/**
+ * Answers the length of the side adjacent to the given angle
+ * of a right triangle. In other words, it returns the integer
+ * conversion of length * cos (angle).
+ * <p>
+ * IMPORTANT: the j2me version has an additional restriction on
+ * the argument. length must be between -32767 and 32767 (inclusive).
+ * </p>
+ *
+ * @param angle the angle in degrees
+ * @param length the length of the triangle's hypotenuse
+ * @return the integer conversion of length * cos (angle)
+ */
+public static int cos(int angle, int length) {
+    return cast(int)(Math.cos(angle * toRadians) * length);
+}
+
+/**
+ * Answers the length of the side opposite to the given angle
+ * of a right triangle. In other words, it returns the integer
+ * conversion of length * sin (angle).
+ * <p>
+ * IMPORTANT: the j2me version has an additional restriction on
+ * the argument. length must be between -32767 and 32767 (inclusive).
+ * </p>
+ *
+ * @param angle the angle in degrees
+ * @param length the length of the triangle's hypotenuse
+ * @return the integer conversion of length * sin (angle)
+ */
+public static int sin(int angle, int length) {
+    return cast(int)(Math.sin(angle * toRadians) * length);
+}
+
+/**
+ * Answers the most negative (i.e. closest to negative infinity)
+ * integer value which is greater than the number obtained by dividing
+ * the first argument p by the second argument q.
+ *
+ * @param p numerator
+ * @param q denominator (must be different from zero)
+ * @return the ceiling of the rational number p / q.
+ */
+public static int ceil(int p, int q) {
+    return cast(int)Math.ceil(cast(float)p / q);
+}
+
+/**
+ * Answers the most positive (i.e. closest to positive infinity)
+ * integer value which is less than the number obtained by dividing
+ * the first argument p by the second argument q.
+ *
+ * @param p numerator
+ * @param q denominator (must be different from zero)
+ * @return the floor of the rational number p / q.
+ */
+public static int floor(int p, int q) {
+    return cast(int)Math.floor(cast(double)p / q);
+}
+
+/**
+ * Answers the result of rounding to the closest integer the number obtained
+ * by dividing the first argument p by the second argument q.
+ * <p>
+ * IMPORTANT: the j2me version has an additional restriction on
+ * the arguments. p must be within the range 0 - 32767 (inclusive).
+ * q must be within the range 1 - 32767 (inclusive).
+ * </p>
+ *
+ * @param p numerator
+ * @param q denominator (must be different from zero)
+ * @return the closest integer to the rational number p / q
+ */
+public static int round(int p, int q) {
+    return cast(int)Math.round(cast(float)p / q);
+}
+
+/**
+ * Returns 2 raised to the power of the argument.
+ *
+ * @param n an int value between 0 and 30 (inclusive)
+ * @return 2 raised to the power of the argument
+ *
+ * @exception IllegalArgumentException <ul>
+ *    <li>ERROR_INVALID_RANGE - if the argument is not between 0 and 30 (inclusive)</li>
+ * </ul>
+ */
+public static int pow2(int n) {
+    if (n >= 1 && n <= 30)
+        return 2 << (n - 1);
+    else if (n != 0) {
+        DWT.error(DWT.ERROR_INVALID_RANGE);
+    }
+    return 1;
+}
+
+/**
+ * Open a file if such things are supported.
+ *
+ * @param filename the name of the file to open
+ * @return a stream on the file if it could be opened.
+ * @exception IOException
+ */
+public static InputStream newFileInputStream(char[] filename) {
+    return new FileInputStream(filename);
+}
+
+/**
+ * Open a file if such things are supported.
+ *
+ * @param filename the name of the file to open
+ * @return a stream on the file if it could be opened.
+ * @exception IOException
+ */
+public static OutputStream newFileOutputStream(char[] filename) {
+    return new FileOutputStream(filename);
+}
+
+/**
+ * Create an InflaterInputStream if such things are supported.
+ *
+ * @param stream the input stream
+ * @return a inflater stream or <code>null</code>
+ * @exception IOException
+ *
+ * @since 3.3
+ */
+public static InflaterInputStream newInflaterInputStream(InputStream stream) {
+    return new InflaterInputStream(stream);
+}
+
+/**
+ * Answers whether the character is a letter.
+ *
+ * @param c the character
+ * @return true when the character is a letter
+ */
+public static bool isLetter(dchar c) {
+    return Unicode.isLetter(c);
+}
+
+/**
+ * Answers whether the character is a letter or a digit.
+ *
+ * @param c the character
+ * @return true when the character is a letter or a digit
+ */
+public static bool isLetterOrDigit(dchar c) {
+    return Unicode.isLetterOrDigit(c);
+}
+
+/**
+ * Answers whether the character is a Unicode space character.
+ *
+ * @param c  the character
+ * @return true when the character is a Unicode space character
+ */
+public static bool isSpaceChar(dchar c) {
+    return Unicode.isSpace(c);
+}
+
+/**
+ * Answers whether the character is a whitespace character.
+ *
+ * @param c the character to test
+ * @return true if the character is whitespace
+ */
+public static bool isWhitespace(dchar c) {
+    return Unicode.isWhitespace(c);
+}
+
+/**
+ * Execute a program in a separate platform process if the
+ * underlying platform support this.
+ * <p>
+ * The new process inherits the environment of the caller.
+ * </p>
+ *
+ * @param prog the name of the program to execute
+ *
+ * @exception ProcessException
+ *  if the program cannot be executed
+ */
+public static void exec(char[] prog) {
+    auto proc = new Process( prog );
+    proc.execute;
+}
+
+/**
+ * Execute progArray[0] in a separate platform process if the
+ * underlying platform support this.
+ * <p>
+ * The new process inherits the environment of the caller.
+ * <p>
+ *
+ * @param progArray array containing the program to execute and its arguments
+ *
+ * @exception ProcessException
+ *  if the program cannot be executed
+ */
+public static void exec(char[][] progArray) {
+    auto proc = new Process( progArray );
+    proc.execute;
+}
+/++ PORTING_LEFT
+private static ResourceBundle msgs = null;
+
+/**
+ * Returns the NLS'ed message for the given argument. This is only being
+ * called from DWT.
+ *
+ * @param key the key to look up
+ * @return the message for the given key
+ *
+ * @see DWT#getMessage(String)
+ */
+public static String getMessage(String key) {
+    String answer = key;
+
+    if (key == null) {
+        DWT.error (DWT.ERROR_NULL_ARGUMENT);
+    }
+    if (msgs == null) {
+        try {
+            msgs = ResourceBundle.getBundle("dwt.internal.SWTMessages"); //$NON-NLS-1$
+        } catch (MissingResourceException ex) {
+            answer = key + " (no resource bundle)"; //$NON-NLS-1$
+        }
+    }
+    if (msgs != null) {
+        try {
+            answer = msgs.getString(key);
+        } catch (MissingResourceException ex2) {}
+    }
+    return answer;
+}
+
+public static String getMessage(String key, Object[] args) {
+    String answer = key;
+
+    if (key == null || args == null) {
+        DWT.error (DWT.ERROR_NULL_ARGUMENT);
+    }
+    if (msgs == null) {
+        try {
+            msgs = ResourceBundle.getBundle("dwt.internal.SWTMessages"); //$NON-NLS-1$
+        } catch (MissingResourceException ex) {
+            answer = key + " (no resource bundle)"; //$NON-NLS-1$
+        }
+    }
+    if (msgs != null) {
+        try {
+            MessageFormat formatter = new MessageFormat("");
+            formatter.applyPattern(msgs.getString(key));
+            answer = formatter.format(args);
+        } catch (MissingResourceException ex2) {}
+    }
+    return answer;
+}
+++/
+
+
+/**
+ * Interrupt the current thread.
+ * <p>
+ * Note that this is not available on CLDC.
+ * </p>
+ */
+public static void interrupt() {
+    //PORTING_FIXME: how to implement??
+    //Thread.currentThread().interrupt();
+}
+
+/**
+ * Compares two instances of class String ignoring the case of the
+ * characters and answers if they are equal.
+ *
+ * @param s1 string
+ * @param s2 string
+ * @return true if the two instances of class String are equal
+ */
+public static bool equalsIgnoreCase(char[] s1, char[] s2) {
+    char[] s1b = new char[ s1.length ];
+    char[] s2b = new char[ s1.length ];
+    scope(exit){
+        delete s1b;
+        delete s2b;
+    }
+    char[] s1c = Unicode.toFold( s1, s1b );
+    char[] s2c = Unicode.toFold( s2, s2b );
+    return s1c == s2c;
+}
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dwt/internal/DWTEventListener.d	Wed Jan 23 12:01:46 2008 +0100
@@ -0,0 +1,35 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2003 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *     IBM Corporation - initial API and implementation
+ * Port to the D programming language:
+ *     Frank Benoit <benoit@tionex.de>
+ *******************************************************************************/
+module dwt.internal.DWTEventListener;
+
+
+//import java.util.EventListener;
+
+///PORTING_TYPE
+interface EventListener{
+}
+
+/**
+ * This interface is the cross-platform version of the
+ * java.util.EventListener interface.
+ * <p>
+ * It is part of our effort to provide support for both J2SE
+ * and J2ME platforms. Under this scheme, classes need to
+ * implement DWTEventListener instead of java.util.EventListener.
+ * </p>
+ * <p>
+ * Note: java.util.EventListener is not part of CDC and CLDC.
+ * </p>
+ */
+public interface DWTEventListener : EventListener {
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dwt/internal/DWTEventObject.d	Wed Jan 23 12:01:46 2008 +0100
@@ -0,0 +1,65 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2004 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *     IBM Corporation - initial API and implementation
+ * Port to the D programming language:
+ *     Frank Benoit <benoit@tionex.de>
+ *******************************************************************************/
+module dwt.internal.DWTEventObject;
+
+
+//import java.util.EventObject;
+import tango.core.Exception;
+
+///PORTING_TYPE
+class EventObject {
+  protected Object source;
+
+  public this(Object source) {
+    if (source is null)
+      throw new IllegalArgumentException( "null arg" );
+    this.source = source;
+  }
+
+  public Object getSource() {
+    return source;
+  }
+
+  public override char[] toString() {
+    return this.classinfo.name ~ "[source=" ~ source.toString() ~ "]";
+  }
+}
+
+
+
+
+/**
+ * This class is the cross-platform version of the
+ * java.util.EventObject class.
+ * <p>
+ * It is part of our effort to provide support for both J2SE
+ * and J2ME platforms. Under this scheme, classes need to
+ * extend DWTEventObject instead of java.util.EventObject.
+ * </p>
+ * <p>
+ * Note: java.util.EventObject is not part of CDC and CLDC.
+ * </p>
+ */
+public class DWTEventObject : EventObject {
+
+    //static final long serialVersionUID = 3258125873411470903L;
+
+/**
+ * Constructs a new instance of this class.
+ *
+ * @param source the object which fired the event
+ */
+public this(Object source) {
+    super(source);
+}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dwt/internal/Library.d	Wed Jan 23 12:01:46 2008 +0100
@@ -0,0 +1,235 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2007 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *     IBM Corporation - initial API and implementation
+ * Port to the D programming language:
+ *     Frank Benoit <benoit@tionex.de>
+ *******************************************************************************/
+module dwt.internal.Library;
+
+import tango.util.Convert;
+
+// do it here, so it can be evaluated at compile time
+// this saves a static ctor.
+private int buildSWT_VERSION (int major, int minor) {
+    return major * 1000 + minor;
+}
+
+
+public class Library {
+
+    /* DWT Version - Mmmm (M=major, mmm=minor) */
+
+    /**
+     * DWT Major version number (must be >= 0)
+     */
+    static const int MAJOR_VERSION = 3;
+
+    /**
+     * DWT Minor version number (must be in the range 0..999)
+     */
+    static const int MINOR_VERSION = 346;
+
+    /**
+     * DWT revision number (must be >= 0)
+     */
+    static const int REVISION = 0;
+
+    /**
+     * The JAVA and DWT versions
+     */
+    //public static const int JAVA_VERSION;
+    public static const int SWT_VERSION = .buildSWT_VERSION(MAJOR_VERSION, MINOR_VERSION);
+
+    version( linux ){
+        static const char[] SEPARATOR = "\n";
+    }
+    else {
+        static assert( false, "only linux supported for this port" );
+    }
+
+
+static int parseVersion(char[] aVersion) {
+    if (aVersion == null) return 0;
+    int major = 0, minor = 0, micro = 0;
+    int length = aVersion.length, index = 0, start = 0;
+    bool isDigit( char c ){
+        return c >= '0' && c <= '9';
+    }
+    while (index < length && isDigit(aVersion[index])) index++;
+    try {
+        if (start < length) major = to!(int)( aVersion[start .. index] );
+    } catch (ConversionException e) {}
+    start = ++index;
+    while (index < length && isDigit(aVersion[index])) index++;
+    try {
+        if (start < length) minor = to!(int)(aVersion[start .. index]);
+    } catch (ConversionException e) {}
+    start = ++index;
+    while (index < length && isDigit(aVersion[index])) index++;
+    try {
+        if (start < length) micro = to!(int)(aVersion[start .. index]);
+    } catch (ConversionException e) {}
+    return buildJAVA_VERSION(major, minor, micro);
+}
+
+/**
+ * Returns the Java version number as an integer.
+ *
+ * @param major
+ * @param minor
+ * @param micro
+ * @return the version
+ */
+public static int buildJAVA_VERSION (int major, int minor, int micro) {
+    return (major << 16) + (minor << 8) + micro;
+}
+
+/**
+ * Returns the DWT version number as an integer.
+ *
+ * @param major
+ * @param minor
+ * @return the version
+ */
+public static int buildSWT_VERSION (int major, int minor) {
+    return .buildSWT_VERSION(major, minor);
+}
+/+ PORTING_LEFT
+static bool extract (char[] fileName, char[] mappedName) {
+    FileOutputStream os = null;
+    InputStream is = null;
+    File file = new File(fileName);
+    try {
+        if (!file.exists ()) {
+            is = Library.class.getResourceAsStream ("/" + mappedName); //$NON-NLS-1$
+            if (is != null) {
+                int read;
+                byte [] buffer = new byte [4096];
+                os = new FileOutputStream (fileName);
+                while ((read = is.read (buffer)) != -1) {
+                    os.write(buffer, 0, read);
+                }
+                os.close ();
+                is.close ();
+                if (!Platform.PLATFORM.equals ("win32")) { //$NON-NLS-1$
+                    try {
+                        Runtime.getRuntime ().exec (new String []{"chmod", "755", fileName}).waitFor(); //$NON-NLS-1$ //$NON-NLS-2$
+                    } catch (Throwable e) {}
+                }
+                if (load (fileName)) return true;
+            }
+        }
+    } catch (Throwable e) {
+        try {
+            if (os != null) os.close ();
+        } catch (IOException e1) {}
+        try {
+            if (is != null) is.close ();
+        } catch (IOException e1) {}
+    }
+    if (file.exists ()) file.delete ();
+    return false;
+}
+
+static bool load (char[] libName) {
+    try {
+        if (libName.indexOf (SEPARATOR) != -1) {
+            System.load (libName);
+        } else {
+            System.loadLibrary (libName);
+        }
+        return true;
+    } catch (UnsatisfiedLinkError e) {}
+    return false;
+}
+
+/**
+ * Loads the shared library that matches the version of the
+ * Java code which is currently running.  DWT shared libraries
+ * follow an encoding scheme where the major, minor and revision
+ * numbers are embedded in the library name and this along with
+ * <code>name</code> is used to load the library.  If this fails,
+ * <code>name</code> is used in another attempt to load the library,
+ * this time ignoring the DWT version encoding scheme.
+ *
+ * @param name the name of the library to load
+ */
+public static void loadLibrary (char[] name) {
+    loadLibrary (name, true);
+}
+
+/**
+ * Loads the shared library that matches the version of the
+ * Java code which is currently running.  DWT shared libraries
+ * follow an encoding scheme where the major, minor and revision
+ * numbers are embedded in the library name and this along with
+ * <code>name</code> is used to load the library.  If this fails,
+ * <code>name</code> is used in another attempt to load the library,
+ * this time ignoring the DWT version encoding scheme.
+ *
+ * @param name the name of the library to load
+ * @param mapName true if the name should be mapped, false otherwise
+ */
+public static void loadLibrary (char[] name, boolean mapName) {
+
+    /* Compute the library name and mapped name */
+    String libName1, libName2, mappedName1, mappedName2;
+    if (mapName) {
+        String version = System.getProperty ("swt.version"); //$NON-NLS-1$
+        if (version == null) {
+            version = "" + MAJOR_VERSION; //$NON-NLS-1$
+            /* Force 3 digits in minor version number */
+            if (MINOR_VERSION < 10) {
+                version += "00"; //$NON-NLS-1$
+            } else {
+                if (MINOR_VERSION < 100) version += "0"; //$NON-NLS-1$
+            }
+            version += MINOR_VERSION;
+            /* No "r" until first revision */
+            if (REVISION > 0) version += "r" + REVISION; //$NON-NLS-1$
+        }
+        libName1 = name + "-" + Platform.PLATFORM + "-" + version;  //$NON-NLS-1$ //$NON-NLS-2$
+        libName2 = name + "-" + Platform.PLATFORM;  //$NON-NLS-1$
+        mappedName1 = System.mapLibraryName (libName1);
+        mappedName2 = System.mapLibraryName (libName2);
+    } else {
+        libName1 = libName2 = mappedName1 = mappedName2 = name;
+    }
+
+    /* Try loading library from swt library path */
+    String path = System.getProperty ("swt.library.path"); //$NON-NLS-1$
+    if (path != null) {
+        path = new File (path).getAbsolutePath ();
+        if (load (path + SEPARATOR + mappedName1)) return;
+        if (mapName && load (path + SEPARATOR + mappedName2)) return;
+    }
+
+    /* Try loading library from java library path */
+    if (load (libName1)) return;
+    if (mapName && load (libName2)) return;
+
+    /* Try loading library from the tmp directory if swt library path is not specified */
+    if (path == null) {
+        path = System.getProperty ("java.io.tmpdir"); //$NON-NLS-1$
+        path = new File (path).getAbsolutePath ();
+        if (load (path + SEPARATOR + mappedName1)) return;
+        if (mapName && load (path + SEPARATOR + mappedName2)) return;
+    }
+
+    /* Try extracting and loading library from jar */
+    if (path != null) {
+        if (extract (path + SEPARATOR + mappedName1, mappedName1)) return;
+        if (mapName && extract (path + SEPARATOR + mappedName2, mappedName2)) return;
+    }
+
+    /* Failed to find the library */
+    throw new UnsatisfiedLinkError ("no " + libName1 + " or " + libName2 + " in swt.library.path, java.library.path or the jar file"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
+}
++/
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dwt/internal/Lock.d	Wed Jan 23 12:01:46 2008 +0100
@@ -0,0 +1,73 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2005 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *     IBM Corporation - initial API and implementation
+ * Port to the D programming language:
+ *     Frank Benoit <benoit@tionex.de>
+ *******************************************************************************/
+module dwt.internal.Lock;
+
+import tango.core.Thread;
+import tango.core.sync.Mutex;
+import tango.core.sync.Condition;
+
+/**
+ * Instance of this represent a recursive monitor.
+ */
+public class Lock {
+    int count, waitCount;
+    Thread owner;
+    Mutex mutex;
+    Condition cond;
+
+    public this() {
+        mutex = new Mutex;
+        cond = new Condition(mutex);
+    }
+/**
+ * Locks the monitor and returns the lock count. If
+ * the lock is owned by another thread, wait until
+ * the lock is released.
+ *
+ * @return the lock count
+ */
+public int lock() {
+    synchronized (mutex) {
+        Thread current = Thread.getThis();
+        if (owner !is current) {
+            waitCount++;
+            while (count > 0) {
+                try {
+                    cond.wait();
+                } catch (SyncException e) {
+                    /* Wait forever, just like synchronized blocks */
+                }
+            }
+            --waitCount;
+            owner = current;
+        }
+        return ++count;
+    }
+}
+
+/**
+ * Unlocks the monitor. If the current thread is not
+ * the monitor owner, do nothing.
+ */
+public void unlock() {
+    synchronized (mutex) {
+        Thread current = Thread.getThis();
+        if (owner is current) {
+            if (--count is 0) {
+                owner = null;
+                if (waitCount > 0) cond.notifyAll();
+            }
+        }
+    }
+}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dwt/internal/SerializableCompatibility.d	Wed Jan 23 12:01:46 2008 +0100
@@ -0,0 +1,34 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2003 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *     IBM Corporation - initial API and implementation
+ * Port to the D programming language:
+ *     Frank Benoit <benoit@tionex.de>
+ *******************************************************************************/
+module dwt.internal.SerializableCompatibility;
+
+
+//import java.io.Serializable;
+///PORTING_TYPE
+interface Serializable{}
+
+/**
+ * This interface is the cross-platform version of the
+ * java.io.Serializable interface.
+ * <p>
+ * It is part of our effort to provide support for both J2SE
+ * and J2ME platforms. Under this scheme, classes need to
+ * implement SerializableCompatibility instead of
+ * java.io.Serializable.
+ * </p>
+ * <p>
+ * Note: java.io.Serializable is not part of CLDC.
+ * </p>
+ */
+public interface SerializableCompatibility : Serializable {
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dwt/internal/image/FileFormat.d	Wed Jan 23 12:01:46 2008 +0100
@@ -0,0 +1,145 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2006 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *     IBM Corporation - initial API and implementation
+ * Port to the D programming language:
+ *     Frank Benoit <benoit@tionex.de>
+ *******************************************************************************/
+module dwt.internal.image.FileFormat;
+
+public import dwt.graphics.ImageLoader;
+public import dwt.graphics.ImageData;
+public import dwt.internal.image.LEDataInputStream;
+public import dwt.internal.image.LEDataOutputStream;
+
+import dwt.DWT;
+
+public import dwt.dwthelper.InputStream;
+public import dwt.dwthelper.OutputStream;
+
+import dwt.internal.image.GIFFileFormat;
+import dwt.internal.image.WinBMPFileFormat;
+import dwt.internal.image.WinICOFileFormat;
+import dwt.internal.image.TIFFFileFormat;
+import dwt.internal.image.OS2BMPFileFormat;
+import dwt.internal.image.JPEGFileFormat;
+import dwt.internal.image.PNGFileFormat;
+
+import tango.core.Exception;
+import tango.core.Tuple;
+
+/**
+ * Abstract factory class for loading/unloading images from files or streams
+ * in various image file formats.
+ *
+ */
+public abstract class FileFormat {
+    static const char[] FORMAT_PACKAGE = "dwt.internal.image"; //$NON-NLS-1$
+    static const char[] FORMAT_SUFFIX = "FileFormat"; //$NON-NLS-1$
+    static const char[][] FORMATS = [ "WinBMP"[], "WinBMP", "GIF", "WinICO", "JPEG", "PNG", "TIFF", "OS2BMP" ]; //$NON-NLS-1$//$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$//$NON-NLS-5$ //$NON-NLS-6$//$NON-NLS-7$//$NON-NLS-8$
+    alias Tuple!( WinBMPFileFormat, WinBMPFileFormat, GIFFileFormat, WinICOFileFormat, JPEGFileFormat, PNGFileFormat, TIFFFileFormat, OS2BMPFileFormat ) TFormats;
+    LEDataInputStream inputStream;
+    LEDataOutputStream outputStream;
+    ImageLoader loader;
+    int compression;
+
+/**
+ * Return whether or not the specified input stream
+ * represents a supported file format.
+ */
+abstract bool isFileFormat(LEDataInputStream stream);
+
+abstract ImageData[] loadFromByteStream();
+
+/**
+ * Read the specified input stream, and return the
+ * device independent image array represented by the stream.
+ */
+public ImageData[] loadFromStream(LEDataInputStream stream) {
+    try {
+        inputStream = stream;
+        return loadFromByteStream();
+    } catch (IOException e) {
+        DWT.error(DWT.ERROR_IO, e);
+        return null;
+    } catch (TracedException e) {
+        DWT.error(DWT.ERROR_INVALID_IMAGE, e);
+        return null;
+    }
+}
+
+/**
+ * Read the specified input stream using the specified loader, and
+ * return the device independent image array represented by the stream.
+ */
+public static ImageData[] load(InputStream istr, ImageLoader loader) {
+    FileFormat fileFormat = null;
+    LEDataInputStream stream = new LEDataInputStream(istr);
+    bool isSupported = false;
+    foreach( TFormat; TFormats ){
+        try{
+            fileFormat = new TFormat();
+            if (fileFormat.isFileFormat(stream)) {
+                isSupported = true;
+                break;
+            }
+        } catch (TracedException e) {
+        }
+    }
+    if (!isSupported) DWT.error(DWT.ERROR_UNSUPPORTED_FORMAT);
+    fileFormat.loader = loader;
+    return fileFormat.loadFromStream(stream);
+}
+
+/**
+ * Write the device independent image array stored in the specified loader
+ * to the specified output stream using the specified file format.
+ */
+public static void save(OutputStream os, int format, ImageLoader loader) {
+    if (format < 0 || format >= FORMATS.length) DWT.error(DWT.ERROR_UNSUPPORTED_FORMAT);
+    if (FORMATS[format] is null) DWT.error(DWT.ERROR_UNSUPPORTED_FORMAT);
+    if (loader.data is null || loader.data.length < 1) DWT.error(DWT.ERROR_INVALID_ARGUMENT);
+
+    LEDataOutputStream stream = new LEDataOutputStream(os);
+    FileFormat fileFormat = null;
+    try {
+        foreach( idx, TFormat; TFormats ){
+            if( idx is format ){
+                fileFormat = new TFormat();
+            }
+        }
+    } catch (TracedException e) {
+        DWT.error(DWT.ERROR_UNSUPPORTED_FORMAT);
+    }
+    if (format is DWT.IMAGE_BMP_RLE) {
+        switch (loader.data[0].depth) {
+            case 8: fileFormat.compression = 1; break;
+            case 4: fileFormat.compression = 2; break;
+            default:
+        }
+    }
+    fileFormat.unloadIntoStream(loader, stream);
+}
+
+abstract void unloadIntoByteStream(ImageLoader loader);
+
+/**
+ * Write the device independent image array stored in the specified loader
+ * to the specified output stream.
+ */
+public void unloadIntoStream(ImageLoader loader, LEDataOutputStream stream) {
+    try {
+        outputStream = stream;
+        unloadIntoByteStream(loader);
+        outputStream.flush();
+    } catch (TracedException e) {
+        try {outputStream.flush();} catch (Exception f) {}
+        DWT.error(DWT.ERROR_IO, e);
+    }
+}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dwt/internal/image/GIFFileFormat.d	Wed Jan 23 12:01:46 2008 +0100
@@ -0,0 +1,623 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2005 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *     IBM Corporation - initial API and implementation
+ * Port to the D programming language:
+ *     Frank Benoit <benoit@tionex.de>
+ *******************************************************************************/
+module dwt.internal.image.GIFFileFormat;
+
+public import dwt.internal.image.FileFormat;
+public import dwt.graphics.PaletteData;
+import dwt.internal.image.LEDataInputStream;
+import dwt.internal.image.LZWCodec;
+import dwt.graphics.RGB;
+import dwt.DWT;
+import dwt.graphics.ImageData;
+import dwt.graphics.ImageLoaderEvent;
+import dwt.graphics.ImageLoader;
+import tango.core.Exception;
+
+///FORTING_TYPE
+class Image{}
+
+final class GIFFileFormat : FileFormat {
+    char[] signature;
+    int screenWidth, screenHeight, backgroundPixel, bitsPerPixel, defaultDepth;
+    int disposalMethod = 0;
+    int delayTime = 0;
+    int transparentPixel = -1;
+    int repeatCount = 1;
+
+    static final int GIF_APPLICATION_EXTENSION_BLOCK_ID = 0xFF;
+    static final int GIF_GRAPHICS_CONTROL_BLOCK_ID = 0xF9;
+    static final int GIF_PLAIN_TEXT_BLOCK_ID = 0x01;
+    static final int GIF_COMMENT_BLOCK_ID = 0xFE;
+    static final int GIF_EXTENSION_BLOCK_ID = 0x21;
+    static final int GIF_IMAGE_BLOCK_ID = 0x2C;
+    static final int GIF_TRAILER_ID = 0x3B;
+    static final byte[] GIF89a = cast(byte[])"GIF89a";
+    static final byte[] NETSCAPE2_0 = cast(byte[])"NETSCAPE2.0";
+
+    /**
+     * Answer a palette containing numGrays
+     * shades of gray, ranging from black to white.
+     */
+    static PaletteData grayRamp(int numGrays) {
+        int n = numGrays - 1;
+        RGB[] colors = new RGB[numGrays];
+        for (int i = 0; i < numGrays; i++) {
+            int intensity = cast(byte)((i * 3) * 256 / n);
+            colors[i] = new RGB(intensity, intensity, intensity);
+        }
+        return new PaletteData(colors);
+    }
+
+    bool isFileFormat(LEDataInputStream stream) {
+        try {
+            byte[3] signature;
+            stream.read(signature);
+            stream.unread(signature);
+            return cast(char[])signature == "GIF"; //$NON-NLS-1$
+        } catch (Exception e) {
+            return false;
+        }
+    }
+
+    /**
+     * Load the GIF image(s) stored in the input stream.
+     * Return an array of ImageData representing the image(s).
+     */
+    ImageData[] loadFromByteStream() {
+        byte[3] signatureBytes;
+        byte[3] versionBytes;
+        byte[7] block;
+        try {
+            inputStream.read(signatureBytes);
+            signature = cast(char[])signatureBytes.dup;
+            if (signature != "GIF") //$NON-NLS-1$
+                DWT.error(DWT.ERROR_INVALID_IMAGE);
+
+            inputStream.read(versionBytes);
+
+            inputStream.read(block);
+        } catch (IOException e) {
+            DWT.error(DWT.ERROR_IO, e);
+        }
+        screenWidth = (block[0] & 0xFF) | ((block[1] & 0xFF) << 8);
+        loader.logicalScreenWidth = screenWidth;
+        screenHeight = (block[2] & 0xFF) | ((block[3] & 0xFF) << 8);
+        loader.logicalScreenHeight = screenHeight;
+        byte bitField = block[4];
+        backgroundPixel = block[5] & 0xFF;
+        //aspect = block[6] & 0xFF;
+        bitsPerPixel = ((bitField >> 4) & 0x07) + 1;
+        defaultDepth = (bitField & 0x7) + 1;
+        PaletteData palette = null;
+        if ((bitField & 0x80) !is 0) {
+            // Global palette.
+            //sorted = (bitField & 0x8) !is 0;
+            palette = readPalette(1 << defaultDepth);
+        } else {
+            // No global palette.
+            //sorted = false;
+            backgroundPixel = -1;
+            defaultDepth = bitsPerPixel;
+        }
+        loader.backgroundPixel = backgroundPixel;
+
+        getExtensions();
+        int id = readID();
+        ImageData[] images = new ImageData[0];
+        while (id is GIF_IMAGE_BLOCK_ID) {
+            ImageData image = readImageBlock(palette);
+            if (loader.hasListeners()) {
+                loader.notifyListeners(new ImageLoaderEvent(loader, image, 3, true));
+            }
+            ImageData[] oldImages = images;
+            images = new ImageData[oldImages.length + 1];
+            System.arraycopy(oldImages, 0, images, 0, oldImages.length);
+            images[images.length - 1] = image;
+            //images ~= image;
+            try {
+                /* Read the 0-byte terminator at the end of the image. */
+                id = inputStream.read();
+                if (id > 0) {
+                    /* We read the terminator earlier. */
+                    byte[1] arr;
+                    arr[0] = id;
+                    inputStream.unread( arr );
+                }
+            } catch (IOException e) {
+                DWT.error(DWT.ERROR_IO, e);
+            }
+            getExtensions();
+            id = readID();
+        }
+        return images;
+    }
+
+    /**
+     * Read and return the next block or extension identifier from the file.
+     */
+    int readID() {
+        try {
+            return inputStream.read();
+        } catch (IOException e) {
+            DWT.error(DWT.ERROR_IO, e);
+        }
+        return -1;
+    }
+
+    /**
+     * Read extensions until an image descriptor appears.
+     * In the future, if we care about the extensions, they
+     * should be properly grouped with the image data before
+     * which they appeared. Right now, the interesting parts
+     * of some extensions are kept, but the rest is discarded.
+     * Throw an error if an error occurs.
+     */
+    void getExtensions() {
+        int id = readID();
+        while (id !is GIF_IMAGE_BLOCK_ID && id !is GIF_TRAILER_ID && id > 0) {
+            if (id is GIF_EXTENSION_BLOCK_ID) {
+                readExtension();
+            } else {
+                DWT.error(DWT.ERROR_INVALID_IMAGE);
+            }
+            id = readID();
+        }
+        if (id is GIF_IMAGE_BLOCK_ID || id is GIF_TRAILER_ID) {
+            try {
+                byte[1] arr;
+                arr[0] = id;
+                inputStream.unread(arr);
+            } catch (IOException e) {
+                DWT.error(DWT.ERROR_IO, e);
+            }
+        }
+    }
+
+    /**
+     * Read a control extension.
+     * Return the extension block data.
+     */
+    byte[] readExtension() {
+        int extensionID = readID();
+        if (extensionID is GIF_COMMENT_BLOCK_ID)
+            return readCommentExtension();
+        if (extensionID is GIF_PLAIN_TEXT_BLOCK_ID)
+            return readPlainTextExtension();
+        if (extensionID is GIF_GRAPHICS_CONTROL_BLOCK_ID)
+            return readGraphicsControlExtension();
+        if (extensionID is GIF_APPLICATION_EXTENSION_BLOCK_ID)
+            return readApplicationExtension();
+        // Otherwise, we don't recognize the block. If the
+        // field size is correct, we can just skip over
+        // the block contents.
+        try {
+            int extSize = inputStream.read();
+            if (extSize < 0) {
+                DWT.error(DWT.ERROR_INVALID_IMAGE);
+            }
+            byte[] ext = new byte[extSize];
+            inputStream.read(ext, 0, extSize);
+            return ext;
+        } catch (IOException e) {
+            DWT.error(DWT.ERROR_IO, e);
+            return null;
+        }
+    }
+
+    /**
+     * We have just read the Comment extension identifier
+     * from the input stream. Read in the rest of the comment
+     * and return it. GIF comment blocks are variable size.
+     */
+    byte[] readCommentExtension() {
+        try {
+            byte[] comment = new byte[0];
+            byte[] block = new byte[255];
+            int size = inputStream.read();
+            while ((size > 0) && (inputStream.read(block, 0, size) !is -1)) {
+                byte[] oldComment = comment;
+                comment = new byte[oldComment.length + size];
+                System.arraycopy(oldComment, 0, comment, 0, oldComment.length);
+                System.arraycopy(block, 0, comment, oldComment.length, size);
+                //comment ~= block[ 0 .. size ];
+                size = inputStream.read();
+            }
+            return comment;
+        } catch (TracedException e) {
+            DWT.error(DWT.ERROR_IO, e);
+            return null;
+        }
+    }
+
+    /**
+     * We have just read the PlainText extension identifier
+     * from the input stream. Read in the plain text info and text,
+     * and return the text. GIF plain text blocks are variable size.
+     */
+    byte[] readPlainTextExtension() {
+        try {
+            // Read size of block = 0x0C.
+            inputStream.read();
+            // Read the text information (x, y, width, height, colors).
+            byte[] info = new byte[12];
+            inputStream.read(info);
+            // Read the text.
+            byte[] text = new byte[0];
+            byte[] block = new byte[255];
+            int size = inputStream.read();
+            while ((size > 0) && (inputStream.read(block, 0, size) !is -1)) {
+                byte[] oldText = text;
+                text = new byte[oldText.length + size];
+                System.arraycopy(oldText, 0, text, 0, oldText.length);
+                System.arraycopy(block, 0, text, oldText.length, size);
+                //text ~= block[ 0 .. size ];
+                size = inputStream.read();
+            }
+            return text;
+        } catch (TracedException e) {
+            DWT.error(DWT.ERROR_IO, e);
+            return null;
+        }
+    }
+
+    /**
+     * We have just read the GraphicsControl extension identifier
+     * from the input stream. Read in the control information, store
+     * it, and return it.
+     */
+    byte[] readGraphicsControlExtension() {
+        try {
+            // Read size of block = 0x04.
+            inputStream.read();
+            // Read the control block.
+            byte[] controlBlock = new byte[4];
+            inputStream.read(controlBlock);
+            byte bitField = controlBlock[0];
+            // Store the user input field.
+            //userInput = (bitField & 0x02) !is 0;
+            // Store the disposal method.
+            disposalMethod = (bitField >> 2) & 0x07;
+            // Store the delay time.
+            delayTime = (controlBlock[1] & 0xFF) | ((controlBlock[2] & 0xFF) << 8);
+            // Store the transparent color.
+            if ((bitField & 0x01) !is 0) {
+                transparentPixel = controlBlock[3] & 0xFF;
+            } else {
+                transparentPixel = -1;
+            }
+            // Read block terminator.
+            inputStream.read();
+            return controlBlock;
+        } catch (TracedException e) {
+            DWT.error(DWT.ERROR_IO, e);
+            return null;
+        }
+    }
+
+    /**
+     * We have just read the Application extension identifier
+     * from the input stream.  Read in the rest of the extension,
+     * look for and store 'number of repeats', and return the data.
+     */
+    byte[] readApplicationExtension() {
+        try {
+            // Read size of block = 0x0B.
+            inputStream.read();
+            // Read application identifier.
+            byte[] applicationBytes = new byte[8];
+            inputStream.read(applicationBytes);
+            char[] application = cast(char[])(applicationBytes.dup);
+            // Read authentication code.
+            byte[] authenticationBytes = new byte[3];
+            inputStream.read(authenticationBytes);
+            char[] authentication = cast(char[])(authenticationBytes.dup);
+            // Read application data.
+            byte[] data = new byte[0];
+            byte[] block = new byte[255];
+            int size = inputStream.read();
+            while ((size > 0) && (inputStream.read(block, 0, size) !is -1)) {
+                byte[] oldData = data;
+                data = new byte[oldData.length + size];
+                System.arraycopy(oldData, 0, data, 0, oldData.length);
+                System.arraycopy(block, 0, data, oldData.length, size);
+                //data ~= block[ 0 .. size ];
+                size = inputStream.read();
+            }
+            // Look for the NETSCAPE 'repeat count' field for an animated GIF.
+            if (application=="NETSCAPE" && authentication=="2.0" && data[0] is 01) { //$NON-NLS-1$ //$NON-NLS-2$
+                repeatCount = (data[1] & 0xFF) | ((data[2] & 0xFF) << 8);
+                loader.repeatCount = repeatCount;
+            }
+            return data;
+        } catch (TracedException e) {
+            DWT.error(DWT.ERROR_IO, e);
+            return null;
+        }
+    }
+
+    /**
+     * Return a DeviceIndependentImage representing the
+     * image block at the current position in the input stream.
+     * Throw an error if an error occurs.
+     */
+    ImageData readImageBlock(PaletteData defaultPalette) {
+        int depth;
+        PaletteData palette;
+        byte[] block = new byte[9];
+        try {
+            inputStream.read(block);
+        } catch (IOException e) {
+            DWT.error(DWT.ERROR_IO, e);
+        }
+        int left = (block[0] & 0xFF) | ((block[1] & 0xFF) << 8);
+        int top = (block[2] & 0xFF) | ((block[3] & 0xFF) << 8);
+        int width = (block[4] & 0xFF) | ((block[5] & 0xFF) << 8);
+        int height = (block[6] & 0xFF) | ((block[7] & 0xFF) << 8);
+        byte bitField = block[8];
+        bool interlaced = (bitField & 0x40) !is 0;
+        //bool sorted = (bitField & 0x20) !is 0;
+        if ((bitField & 0x80) !is 0) {
+            // Local palette.
+            depth = (bitField & 0x7) + 1;
+            palette = readPalette(1 << depth);
+        } else {
+            // No local palette.
+            depth = defaultDepth;
+            palette = defaultPalette;
+        }
+        /* Work around: Ignore the case where a GIF specifies an
+         * invalid index for the transparent pixel that is larger
+         * than the number of entries in the palette. */
+        if (transparentPixel > 1 << depth) {
+            transparentPixel = -1;
+        }
+        // Promote depth to next highest supported value.
+        if (!(depth is 1 || depth is 4 || depth is 8)) {
+            if (depth < 4)
+                depth = 4;
+            else
+                depth = 8;
+        }
+        if (palette is null) {
+            palette = grayRamp(1 << depth);
+        }
+        int initialCodeSize = -1;
+        try {
+            initialCodeSize = inputStream.read();
+        } catch (IOException e) {
+            DWT.error(DWT.ERROR_IO, e);
+        }
+        if (initialCodeSize < 0) {
+            DWT.error(DWT.ERROR_INVALID_IMAGE);
+        }
+        ImageData image = ImageData.internal_new(
+            width,
+            height,
+            depth,
+            palette,
+            4,
+            null,
+            0,
+            null,
+            null,
+            -1,
+            transparentPixel,
+            DWT.IMAGE_GIF,
+            left,
+            top,
+            disposalMethod,
+            delayTime);
+        LZWCodec codec = new LZWCodec();
+        codec.decode(inputStream, loader, image, interlaced, initialCodeSize);
+        return image;
+    }
+
+    /**
+     * Read a palette from the input stream.
+     */
+    PaletteData readPalette(int numColors) {
+        byte[] bytes = new byte[numColors * 3];
+        try {
+            if (inputStream.read(bytes) !is bytes.length)
+                DWT.error(DWT.ERROR_INVALID_IMAGE);
+        } catch (IOException e) {
+            DWT.error(DWT.ERROR_IO, e);
+        }
+        RGB[] colors = new RGB[numColors];
+        for (int i = 0; i < numColors; i++)
+            colors[i] = new RGB(bytes[i*3] & 0xFF,
+                bytes[i*3+1] & 0xFF, bytes[i*3+2] & 0xFF);
+        return new PaletteData(colors);
+    }
+
+    void unloadIntoByteStream(ImageLoader loader) {
+
+        /* Step 1: Acquire GIF parameters. */
+        ImageData[] data = loader.data;
+        int frameCount = data.length;
+        bool multi = frameCount > 1;
+        ImageData firstImage = data[0];
+        int logicalScreenWidth = multi ? loader.logicalScreenWidth : firstImage.width;
+        int logicalScreenHeight = multi ? loader.logicalScreenHeight : firstImage.height;
+        int backgroundPixel = loader.backgroundPixel;
+        int depth = firstImage.depth;
+        PaletteData palette = firstImage.palette;
+        RGB[] colors = palette.getRGBs();
+        short globalTable = 1;
+
+        /* Step 2: Check for validity and global/local color map. */
+        if (!(depth is 1 || depth is 4 || depth is 8)) {
+            DWT.error(DWT.ERROR_UNSUPPORTED_DEPTH);
+        }
+        for (int i=0; i<frameCount; i++) {
+            if (data[i].palette.isDirect) {
+                DWT.error(DWT.ERROR_INVALID_IMAGE);
+            }
+            if (multi) {
+                if (!(data[i].height <= logicalScreenHeight && data[i].width <= logicalScreenWidth && data[i].depth is depth)) {
+                    DWT.error(DWT.ERROR_INVALID_IMAGE);
+                }
+                if (globalTable is 1) {
+                    RGB rgbs[] = data[i].palette.getRGBs();
+                    if (rgbs.length !is colors.length) {
+                        globalTable = 0;
+                    } else {
+                        for (int j=0; j<colors.length; j++) {
+                            if (!(rgbs[j].red is colors[j].red &&
+                                rgbs[j].green is colors[j].green &&
+                                rgbs[j].blue is colors[j].blue))
+                                    globalTable = 0;
+                        }
+                    }
+                }
+            }
+        }
+
+        try {
+            /* Step 3: Write the GIF89a Header and Logical Screen Descriptor. */
+            outputStream.write(GIF89a);
+            int bitField = globalTable*128 + (depth-1)*16 + depth-1;
+            outputStream.writeShort(cast(short)logicalScreenWidth);
+            outputStream.writeShort(cast(short)logicalScreenHeight);
+            outputStream.write(bitField);
+            outputStream.write(backgroundPixel);
+            outputStream.write(0); // Aspect ratio is 1:1
+        } catch (IOException e) {
+            DWT.error(DWT.ERROR_IO, e);
+        }
+
+        /* Step 4: Write Global Color Table if applicable. */
+        if (globalTable is 1) {
+            writePalette(palette, depth);
+        }
+
+        /* Step 5: Write Application Extension if applicable. */
+        if (multi) {
+            int repeatCount = loader.repeatCount;
+            try {
+                outputStream.write(GIF_EXTENSION_BLOCK_ID);
+                outputStream.write(GIF_APPLICATION_EXTENSION_BLOCK_ID);
+                outputStream.write(NETSCAPE2_0.length);
+                outputStream.write(NETSCAPE2_0);
+                outputStream.write(3); // Three bytes follow
+                outputStream.write(1); // Extension type
+                outputStream.writeShort(cast(short) repeatCount);
+                outputStream.write(0); // Block terminator
+            } catch (IOException e) {
+                DWT.error(DWT.ERROR_IO, e);
+            }
+        }
+
+        for (int frame=0; frame<frameCount; frame++) {
+
+            /* Step 6: Write Graphics Control Block for each frame if applicable. */
+            if (multi || data[frame].transparentPixel !is -1) {
+                writeGraphicsControlBlock(data[frame]);
+            }
+
+            /* Step 7: Write Image Header for each frame. */
+            int x = data[frame].x;
+            int y = data[frame].y;
+            int width = data[frame].width;
+            int height = data[frame].height;
+            try {
+                outputStream.write(GIF_IMAGE_BLOCK_ID);
+                byte[] block = new byte[9];
+                block[0] = cast(byte)(x & 0xFF);
+                block[1] = cast(byte)((x >> 8) & 0xFF);
+                block[2] = cast(byte)(y & 0xFF);
+                block[3] = cast(byte)((y >> 8) & 0xFF);
+                block[4] = cast(byte)(width & 0xFF);
+                block[5] = cast(byte)((width >> 8) & 0xFF);
+                block[6] = cast(byte)(height & 0xFF);
+                block[7] = cast(byte)((height >> 8) & 0xFF);
+                block[8] = cast(byte)(globalTable is 0 ? (depth-1) | 0x80 : 0x00);
+                outputStream.write(block);
+            } catch (IOException e) {
+                DWT.error(DWT.ERROR_IO, e);
+            }
+
+            /* Step 8: Write Local Color Table for each frame if applicable. */
+            if (globalTable is 0) {
+                writePalette(data[frame].palette, depth);
+            }
+
+            /* Step 9: Write the actual data for each frame. */
+            try {
+                outputStream.write(depth); // Minimum LZW Code size
+            } catch (IOException e) {
+                DWT.error(DWT.ERROR_IO, e);
+            }
+            (new LZWCodec()).encode(outputStream, data[frame]);
+        }
+
+        /* Step 10: Write GIF terminator. */
+        try {
+            outputStream.write(0x3B);
+        } catch (IOException e) {
+            DWT.error(DWT.ERROR_IO, e);
+        }
+    }
+
+    /**
+     * Write out a GraphicsControlBlock to describe
+     * the specified device independent image.
+     */
+    void writeGraphicsControlBlock(ImageData image) {
+        try {
+            outputStream.write(GIF_EXTENSION_BLOCK_ID);
+            outputStream.write(GIF_GRAPHICS_CONTROL_BLOCK_ID);
+            byte[] gcBlock = new byte[4];
+            gcBlock[0] = 0;
+            gcBlock[1] = 0;
+            gcBlock[2] = 0;
+            gcBlock[3] = 0;
+            if (image.transparentPixel !is -1) {
+                gcBlock[0] = cast(byte)0x01;
+                gcBlock[3] = cast(byte)image.transparentPixel;
+            }
+            if (image.disposalMethod !is 0) {
+                gcBlock[0] |= cast(byte)((image.disposalMethod & 0x07) << 2);
+            }
+            if (image.delayTime !is 0) {
+                gcBlock[1] = cast(byte)(image.delayTime & 0xFF);
+                gcBlock[2] = cast(byte)((image.delayTime >> 8) & 0xFF);
+            }
+            outputStream.write(cast(byte)gcBlock.length);
+            outputStream.write(gcBlock);
+            outputStream.write(0); // Block terminator
+        } catch (IOException e) {
+            DWT.error(DWT.ERROR_IO, e);
+        }
+    }
+
+    /**
+     * Write the specified palette to the output stream.
+     */
+    void writePalette(PaletteData palette, int depth) {
+        byte[] bytes = new byte[(1 << depth) * 3];
+        int offset = 0;
+        for (int i = 0; i < palette.colors.length; i++) {
+            RGB color = palette.colors[i];
+            bytes[offset] = cast(byte)color.red;
+            bytes[offset + 1] = cast(byte)color.green;
+            bytes[offset + 2] = cast(byte)color.blue;
+            offset += 3;
+        }
+        try {
+            outputStream.write(bytes);
+        } catch (IOException e) {
+            DWT.error(DWT.ERROR_IO, e);
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dwt/internal/image/JPEGAppn.d	Wed Jan 23 12:01:46 2008 +0100
@@ -0,0 +1,33 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2003 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *     IBM Corporation - initial API and implementation
+ * Port to the D programming language:
+ *     Frank Benoit <benoit@tionex.de>
+ *******************************************************************************/
+module dwt.internal.image.JPEGAppn;
+
+import dwt.internal.image.JPEGVariableSizeSegment;
+import dwt.internal.image.JPEGFileFormat;
+import dwt.internal.image.LEDataInputStream;
+
+final class JPEGAppn : JPEGVariableSizeSegment {
+
+    public this(byte[] reference) {
+        super(reference);
+    }
+
+    public this(LEDataInputStream byteStream) {
+        super(byteStream);
+    }
+
+    public bool verify() {
+        int marker = getSegmentMarker();
+        return marker >= JPEGFileFormat.APP0 && marker <= JPEGFileFormat.APP15;
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dwt/internal/image/JPEGArithmeticConditioningTable.d	Wed Jan 23 12:01:46 2008 +0100
@@ -0,0 +1,28 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2003 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *     IBM Corporation - initial API and implementation
+ * Port to the D programming language:
+ *     Frank Benoit <benoit@tionex.de>
+ *******************************************************************************/
+module dwt.internal.image.JPEGArithmeticConditioningTable;
+
+import dwt.internal.image.JPEGVariableSizeSegment;
+import dwt.internal.image.JPEGFileFormat;
+import dwt.internal.image.LEDataInputStream;
+
+final class JPEGArithmeticConditioningTable : JPEGVariableSizeSegment {
+
+    public this(LEDataInputStream byteStream) {
+        super(byteStream);
+    }
+
+    public int signature() {
+        return JPEGFileFormat.DAC;
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dwt/internal/image/JPEGComment.d	Wed Jan 23 12:01:46 2008 +0100
@@ -0,0 +1,32 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2003 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *     IBM Corporation - initial API and implementation
+ * Port to the D programming language:
+ *     Frank Benoit <benoit@tionex.de>
+ *******************************************************************************/
+module dwt.internal.image.JPEGComment;
+
+import dwt.internal.image.JPEGVariableSizeSegment;
+import dwt.internal.image.JPEGFileFormat;
+import dwt.internal.image.LEDataInputStream;
+
+final class JPEGComment : JPEGVariableSizeSegment {
+
+    public this(byte[] reference) {
+        super(reference);
+    }
+
+    public this(LEDataInputStream byteStream) {
+        super(byteStream);
+    }
+
+    public int signature() {
+        return JPEGFileFormat.COM;
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dwt/internal/image/JPEGDecoder.d	Wed Jan 23 12:01:46 2008 +0100
@@ -0,0 +1,6394 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2006 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *     IBM Corporation - initial API and implementation
+ * Port to the D programming language:
+ *     Frank Benoit <benoit@tionex.de>
+ *******************************************************************************/
+module dwt.internal.image.JPEGDecoder;
+
+import dwt.DWT;
+import dwt.dwthelper.InputStream;
+import dwt.internal.image.LEDataInputStream;
+import dwt.graphics.ImageData;
+import dwt.graphics.ImageLoader;
+import dwt.graphics.ImageLoaderEvent;
+import dwt.graphics.PaletteData;
+import dwt.graphics.RGB;
+
+import tango.core.Exception;
+import tango.util.Convert;
+import Math = tango.math.Math;
+
+public class JPEGDecoder {
+
+    static const int DCTSIZE = 8;
+    static const int DCTSIZE2 = 64;
+    static const int NUM_QUANT_TBLS = 4;
+    static const int NUM_HUFF_TBLS = 4;
+    static const int NUM_ARITH_TBLS = 16;
+    static const int MAX_COMPS_IN_SCAN = 4;
+    static const int MAX_COMPONENTS = 10;
+    static const int MAX_SAMP_FACTOR = 4;
+    static const int D_MAX_BLOCKS_IN_MCU = 10;
+    static const int HUFF_LOOKAHEAD = 8;
+    static const int MAX_Q_COMPS = 4;
+    static const int IFAST_SCALE_BITS = 2;
+    static const int MAXJSAMPLE = 255;
+    static const int CENTERJSAMPLE = 128;
+    static const int MIN_GET_BITS = 32-7;
+    static const int INPUT_BUFFER_SIZE = 4096;
+
+    static const int SCALEBITS = 16;    /* speediest right-shift on some machines */
+    static const int ONE_HALF = 1 << (SCALEBITS-1);
+
+    static const int RGB_RED = 2;   /* Offset of Red in an RGB scanline element */
+    static const int RGB_GREEN = 1; /* Offset of Green */
+    static const int RGB_BLUE = 0;  /* Offset of Blue */
+    static const int RGB_PIXELSIZE = 3;
+
+    static const int JBUF_PASS_THRU = 0;
+    static const int JBUF_SAVE_SOURCE = 1;  /* Run source subobject only, save output */
+    static const int JBUF_CRANK_DEST = 2;   /* Run dest subobject only, using saved data */
+    static const int JBUF_SAVE_AND_PASS = 3;
+
+    static const int JPEG_MAX_DIMENSION = 65500;
+    static const int BITS_IN_JSAMPLE = 8;
+
+    static const int JDITHER_NONE = 0;      /* no dithering */
+    static const int JDITHER_ORDERED = 1;   /* simple ordered dither */
+    static const int JDITHER_FS = 2;
+
+    static const int JDCT_ISLOW = 0;    /* slow but accurate integer algorithm */
+    static const int JDCT_IFAST = 1;    /* faster, less accurate integer method */
+    static const int JDCT_FLOAT = 2;    /* floating-point: accurate, fast on fast HW */
+    static const int JDCT_DEFAULT = JDCT_ISLOW;
+
+    static const int JCS_UNKNOWN = 0;       /* error/unspecified */
+    static const int JCS_GRAYSCALE = 1;     /* monochrome */
+    static const int JCS_RGB = 2;       /* red/green/blue */
+    static const int JCS_YCbCr = 3;     /* Y/Cb/Cr (also known as YUV) */
+    static const int JCS_CMYK = 4;      /* C/M/Y/K */
+    static const int JCS_YCCK = 5;      /* Y/Cb/Cr/K */
+
+    static const int SAVED_COEFS = 6;
+    static const int Q01_POS = 1;
+    static const int Q10_POS = 8;
+    static const int Q20_POS = 16;
+    static const int Q11_POS = 9;
+    static const int Q02_POS = 2;
+
+    static const int CTX_PREPARE_FOR_IMCU = 0;  /* need to prepare for MCU row */
+    static const int CTX_PROCESS_IMCU = 1;  /* feeding iMCU to postprocessor */
+    static const int CTX_POSTPONED_ROW = 2; /* feeding postponed row group */
+
+    static const int APP0_DATA_LEN = 14;    /* Length of interesting data in APP0 */
+    static const int APP14_DATA_LEN = 12;   /* Length of interesting data in APP14 */
+    static const int APPN_DATA_LEN = 14;    /* Must be the largest of the above!! */
+
+    /* markers */
+    static const int M_SOF0 = 0xc0;
+    static const int M_SOF1 = 0xc1;
+    static const int M_SOF2 = 0xc2;
+    static const int M_SOF3 = 0xc3;
+    static const int M_SOF5 = 0xc5;
+    static const int M_SOF6 = 0xc6;
+    static const int M_SOF7 = 0xc7;
+    static const int M_JPG = 0xc8;
+    static const int M_SOF9 = 0xc9;
+    static const int M_SOF10 = 0xca;
+    static const int M_SOF11 = 0xcb;
+    static const int M_SOF13 = 0xcd;
+    static const int M_SOF14 = 0xce;
+    static const int M_SOF15 = 0xcf;
+    static const int M_DHT = 0xc4;
+    static const int M_DAC = 0xcc;
+    static const int M_RST0 = 0xd0;
+    static const int M_RST1 = 0xd1;
+    static const int M_RST2 = 0xd2;
+    static const int M_RST3 = 0xd3;
+    static const int M_RST4 = 0xd4;
+    static const int M_RST5 = 0xd5;
+    static const int M_RST6 = 0xd6;
+    static const int M_RST7 = 0xd7;
+    static const int M_SOI = 0xd8;
+    static const int M_EOI = 0xd9;
+    static const int M_SOS = 0xda;
+    static const int M_DQT = 0xdb;
+    static const int M_DNL = 0xdc;
+    static const int M_DRI = 0xdd;
+    static const int M_DHP = 0xde;
+    static const int M_EXP = 0xdf;
+    static const int M_APP0 = 0xe0;
+    static const int M_APP1 = 0xe1;
+    static const int M_APP2 = 0xe2;
+    static const int M_APP3 = 0xe3;
+    static const int M_APP4 = 0xe4;
+    static const int M_APP5 = 0xe5;
+    static const int M_APP6 = 0xe6;
+    static const int M_APP7 = 0xe7;
+    static const int M_APP8 = 0xe8;
+    static const int M_APP9 = 0xe9;
+    static const int M_APP10 = 0xea;
+    static const int M_APP11 = 0xeb;
+    static const int M_APP12 = 0xec;
+    static const int M_APP13 = 0xed;
+    static const int M_APP14 = 0xee;
+    static const int M_APP15 = 0xef;
+    static const int M_JPG0 = 0xf0;
+    static const int M_JPG13 = 0xfd;
+    static const int M_COM = 0xfe;
+    static const int M_TEM = 0x01;
+    static const int M_ERROR = 0x100;
+
+    /* Values of global_state field (jdapi.c has some dependencies on ordering!) */
+    static const int CSTATE_START = 100;    /* after create_compress */
+    static const int CSTATE_SCANNING = 101; /* start_compress done, write_scanlines OK */
+    static const int CSTATE_RAW_OK = 102;   /* start_compress done, write_raw_data OK */
+    static const int CSTATE_WRCOEFS = 103;  /* jpeg_write_coefficients done */
+    static const int DSTATE_START = 200;    /* after create_decompress */
+    static const int DSTATE_INHEADER = 201; /* reading header markers, no SOS yet */
+    static const int DSTATE_READY = 202;    /* found SOS, ready for start_decompress */
+    static const int DSTATE_PRELOAD = 203;  /* reading multiscan file in start_decompress*/
+    static const int DSTATE_PRESCAN = 204;  /* performing dummy pass for 2-pass quant */
+    static const int DSTATE_SCANNING = 205; /* start_decompress done, read_scanlines OK */
+    static const int DSTATE_RAW_OK = 206;   /* start_decompress done, read_raw_data OK */
+    static const int DSTATE_BUFIMAGE = 207; /* expecting jpeg_start_output */
+    static const int DSTATE_BUFPOST = 208;  /* looking for SOS/EOI in jpeg_finish_output */
+    static const int DSTATE_RDCOEFS = 209;  /* reading file in jpeg_read_coefficients */
+    static const int DSTATE_STOPPING = 210; /* looking for EOI in jpeg_finish_decompress */
+
+    static const int JPEG_REACHED_SOS = 1; /* Reached start of new scan */
+    static const int JPEG_REACHED_EOI = 2; /* Reached end of image */
+    static const int JPEG_ROW_COMPLETED = 3; /* Completed one iMCU row */
+    static const int JPEG_SCAN_COMPLETED = 4; /* Completed last iMCU row of a scan */
+
+    static const int JPEG_SUSPENDED = 0; /* Suspended due to lack of input data */
+    static const int JPEG_HEADER_OK = 1; /* Found valid image datastream */
+    static const int JPEG_HEADER_TABLES_ONLY = 2; /* Found valid table-specs-only datastream */
+
+    /* Function pointers */
+    static const int DECOMPRESS_DATA = 0;
+    static const int DECOMPRESS_SMOOTH_DATA = 1;
+    static const int DECOMPRESS_ONEPASS = 2;
+
+    static const int CONSUME_DATA = 0;
+    static const int DUMMY_CONSUME_DATA = 1;
+
+    static const int PROCESS_DATA_SIMPLE_MAIN = 0;
+    static const int PROCESS_DATA_CONTEXT_MAIN = 1;
+    static const int PROCESS_DATA_CRANK_POST = 2;
+
+    static const int POST_PROCESS_1PASS = 0;
+    static const int POST_PROCESS_DATA_UPSAMPLE = 1;
+
+    static const int NULL_CONVERT = 0;
+    static const int GRAYSCALE_CONVERT = 1;
+    static const int YCC_RGB_CONVERT = 2;
+    static const int GRAY_RGB_CONVERT = 3;
+    static const int YCCK_CMYK_CONVERT = 4;
+
+    static const int NOOP_UPSAMPLE = 0;
+    static const int FULLSIZE_UPSAMPLE = 1;
+    static const int H2V1_FANCY_UPSAMPLE = 2;
+    static const int H2V1_UPSAMPLE = 3;
+    static const int H2V2_FANCY_UPSAMPLE = 4;
+    static const int H2V2_UPSAMPLE = 5;
+    static const int INT_UPSAMPLE = 6;
+
+    static const int INPUT_CONSUME_INPUT = 0;
+    static const int COEF_CONSUME_INPUT = 1;
+
+    static int extend_test[] =   /* entry n is 2**(n-1) */
+    [
+        0, 0x0001, 0x0002, 0x0004, 0x0008, 0x0010, 0x0020, 0x0040, 0x0080,
+        0x0100, 0x0200, 0x0400, 0x0800, 0x1000, 0x2000, 0x4000
+    ];
+
+    static int extend_offset[] = /* entry n is (-1 << n) + 1 */
+    [
+        0, ((-1)<<1) + 1, ((-1)<<2) + 1, ((-1)<<3) + 1, ((-1)<<4) + 1,
+        ((-1)<<5) + 1, ((-1)<<6) + 1, ((-1)<<7) + 1, ((-1)<<8) + 1,
+        ((-1)<<9) + 1, ((-1)<<10) + 1, ((-1)<<11) + 1, ((-1)<<12) + 1,
+        ((-1)<<13) + 1, ((-1)<<14) + 1, ((-1)<<15) + 1
+    ];
+
+    static int jpeg_natural_order[] = [
+        0,  1,  8, 16,  9,  2,  3, 10,
+        17, 24, 32, 25, 18, 11, 4,  5,
+        12, 19, 26, 33, 40, 48, 41, 34,
+        27, 20, 13, 6,  7, 14, 21, 28,
+        35, 42, 49, 56, 57, 50, 43, 36,
+        29, 22, 15, 23, 30, 37, 44, 51,
+        58, 59, 52, 45, 38, 31, 39, 46,
+        53, 60, 61, 54, 47, 55, 62, 63,
+        63, 63, 63, 63, 63, 63, 63, 63, /* extra entries for safety in decoder */
+        63, 63, 63, 63, 63, 63, 63, 63
+    ];
+
+    static final class JQUANT_TBL {
+        /* This array gives the coefficient quantizers in natural array order
+         * (not the zigzag order in which they are stored in a JPEG DQT marker).
+         * CAUTION: IJG versions prior to v6a kept this array in zigzag order.
+         */
+        short[DCTSIZE2] quantval;// = new short[DCTSIZE2];  /* quantization step for each coefficient */
+        /* This field is used only during compression.  It's initialized false when
+         * the table is created, and set true when it's been output to the file.
+         * You could suppress output of a table by setting this to true.
+         * (See jpeg_suppress_tables for an example.)
+         */
+        bool sent_table;        /* true when table has been output */
+    }
+
+    static final class JHUFF_TBL {
+        /* These two fields directly represent the contents of a JPEG DHT marker */
+        byte[17] bits;// = new byte[17]; /* bits[k] = # of symbols with codes of */
+                                    /* length k bits; bits[0] is unused */
+        byte[256] huffval;// = new byte[256];       /* The symbols, in order of incr code length */
+        /* This field is used only during compression.  It's initialized false when
+         * the table is created, and set true when it's been output to the file.
+         * You could suppress output of a table by setting this to true.
+         * (See jpeg_suppress_tables for an example.)
+         */
+        bool sent_table;        /* true when table has been output */
+    }
+
+    static final class bitread_perm_state {     /* Bitreading state saved across MCUs */
+        int get_buffer; /* current bit-extraction buffer */
+        int bits_left;      /* # of unused bits in it */
+    }
+
+    static final class bitread_working_state {      /* Bitreading working state within an MCU */
+        /* Current data source location */
+        /* We need a copy, rather than munging the original, in case of suspension */
+        byte[] buffer; /* => next byte to read from source */
+        int bytes_offset;
+        int bytes_in_buffer;    /* # of bytes remaining in source buffer */
+        /* Bit input buffer --- note these values are kept in register variables,
+         * not in this struct, inside the inner loops.
+         */
+        int get_buffer; /* current bit-extraction buffer */
+        int bits_left;      /* # of unused bits in it */
+        /* Pointer needed by jpeg_fill_bit_buffer. */
+        jpeg_decompress_struct cinfo;   /* back link to decompress master record */
+    }
+
+    static final class savable_state {
+        int EOBRUN; //Note that this is only used in the progressive case
+        int[MAX_COMPS_IN_SCAN] last_dc_val;// = new int[MAX_COMPS_IN_SCAN]; /* last DC coef for each component */
+    }
+
+    static final class d_derived_tbl {
+        /* Basic tables: (element [0] of each array is unused) */
+        int[18] maxcode;// = new int[18];       /* largest code of length k (-1 if none) */
+        /* (maxcode[17] is a sentinel to ensure jpeg_huff_decode terminates) */
+        int[17] valoffset;// = new int[17];     /* huffval[] offset for codes of length k */
+        /* valoffset[k] = huffval[] index of 1st symbol of code length k, less
+         * the smallest code of length k; so given a code of length k, the
+         * corresponding symbol is huffval[code + valoffset[k]]
+         */
+
+        /* Link to public Huffman table (needed only in jpeg_huff_decode) */
+        JHUFF_TBL pub;
+
+        /* Lookahead tables: indexed by the next HUFF_LOOKAHEAD bits of
+         * the input data stream.   If the next Huffman code is no more
+         * than HUFF_LOOKAHEAD bits long, we can obtain its length and
+         * the corresponding symbol directly from these tables.
+         */
+        int[1<<HUFF_LOOKAHEAD] look_nbits;// = new int[1<<HUFF_LOOKAHEAD]; /* # bits, or 0 if too long */
+        byte[1<<HUFF_LOOKAHEAD] look_sym;// = new byte[1<<HUFF_LOOKAHEAD]; /* symbol, or unused */
+    }
+
+    static final class jpeg_d_coef_controller {
+        int consume_data;
+        int decompress_data;
+
+        /* Pointer to array of coefficient virtual arrays, or null if none */
+        short[][][] coef_arrays;
+
+        /* These variables keep track of the current location of the input side. */
+        /* cinfo.input_iMCU_row is also used for this. */
+        int MCU_ctr;        /* counts MCUs processed in current row */
+        int MCU_vert_offset;        /* counts MCU rows within iMCU row */
+        int MCU_rows_per_iMCU_row;  /* number of such rows needed */
+
+        /* The output side's location is represented by cinfo.output_iMCU_row. */
+
+        /* In single-pass modes, it's sufficient to buffer just one MCU.
+         * We allocate a workspace of D_MAX_BLOCKS_IN_MCU coefficient blocks,
+         * and let the entropy decoder write into that workspace each time.
+         * (On 80x86, the workspace is FAR even though it's not really very big;
+         * this is to keep the module interfaces unchanged when a large coefficient
+         * buffer is necessary.)
+         * In multi-pass modes, this array points to the current MCU's blocks
+         * within the virtual arrays; it is used only by the input side.
+         */
+        short[][D_MAX_BLOCKS_IN_MCU] MCU_buffer;// = new short[D_MAX_BLOCKS_IN_MCU][];
+
+        /* In multi-pass modes, we need a virtual block array for each component. */
+        short[][][][MAX_COMPONENTS] whole_image;// = new short[MAX_COMPONENTS][][][];
+
+        /* When doing block smoothing, we latch coefficient Al values here */
+        int[] coef_bits_latch;
+
+        short[] workspace;
+
+        void start_input_pass (jpeg_decompress_struct cinfo) {
+            cinfo.input_iMCU_row = 0;
+            start_iMCU_row(cinfo);
+        }
+
+        /* Reset within-iMCU-row counters for a new row (input side) */
+        void start_iMCU_row (jpeg_decompress_struct cinfo) {
+            jpeg_d_coef_controller coef = cinfo.coef;
+
+            /* In an interleaved scan, an MCU row is the same as an iMCU row.
+             * In a noninterleaved scan, an iMCU row has v_samp_factor MCU rows.
+             * But at the bottom of the image, process only what's left.
+             */
+            if (cinfo.comps_in_scan > 1) {
+                coef.MCU_rows_per_iMCU_row = 1;
+            } else {
+                if (cinfo.input_iMCU_row < (cinfo.total_iMCU_rows-1))
+                    coef.MCU_rows_per_iMCU_row = cinfo.cur_comp_info[0].v_samp_factor;
+                else
+                    coef.MCU_rows_per_iMCU_row = cinfo.cur_comp_info[0].last_row_height;
+            }
+
+            coef.MCU_ctr = 0;
+            coef.MCU_vert_offset = 0;
+        }
+
+    }
+
+    static abstract class jpeg_entropy_decoder {
+        abstract void start_pass (jpeg_decompress_struct cinfo);
+        abstract bool decode_mcu (jpeg_decompress_struct cinfo, short[][] MCU_data);
+
+        /* This is here to share code between baseline and progressive decoders; */
+        /* other modules probably should not use it */
+        bool insufficient_data; /* set true after emitting warning */
+
+        bitread_working_state br_state_local;
+        savable_state state_local;
+        public this(){
+            br_state_local = new bitread_working_state();
+            state_local = new savable_state();
+        }
+    }
+
+    static final class huff_entropy_decoder : jpeg_entropy_decoder {
+        bitread_perm_state bitstate;// = new bitread_perm_state();  /* Bit buffer at start of MCU */
+        savable_state saved;// = new savable_state();       /* Other state at start of MCU */
+
+        /* These fields are NOT loaded into local working state. */
+        int restarts_to_go; /* MCUs left in this restart interval */
+
+        /* Pointers to derived tables (these workspaces have image lifespan) */
+        d_derived_tbl[NUM_HUFF_TBLS] dc_derived_tbls;// = new d_derived_tbl[NUM_HUFF_TBLS];
+        d_derived_tbl[NUM_HUFF_TBLS] ac_derived_tbls;// = new d_derived_tbl[NUM_HUFF_TBLS];
+
+        /* Precalculated info set up by start_pass for use in decode_mcu: */
+
+        /* Pointers to derived tables to be used for each block within an MCU */
+        d_derived_tbl[D_MAX_BLOCKS_IN_MCU] dc_cur_tbls;// = new d_derived_tbl[D_MAX_BLOCKS_IN_MCU];
+        d_derived_tbl[D_MAX_BLOCKS_IN_MCU] ac_cur_tbls;// = new d_derived_tbl[D_MAX_BLOCKS_IN_MCU];
+        /* Whether we care about the DC and AC coefficient values for each block */
+        bool[D_MAX_BLOCKS_IN_MCU] dc_needed;// = new bool[D_MAX_BLOCKS_IN_MCU];
+        bool[D_MAX_BLOCKS_IN_MCU] ac_needed;// = new bool[D_MAX_BLOCKS_IN_MCU];
+
+        public this(){
+            bitstate = new bitread_perm_state(); /* Bit buffer at start of MCU */
+            saved = new savable_state();      /* Other state at start of MCU */
+        }
+
+        void start_pass (jpeg_decompress_struct cinfo) {
+            start_pass_huff_decoder(cinfo);
+        }
+
+        bool decode_mcu (jpeg_decompress_struct cinfo, short[][] MCU_data) {
+            huff_entropy_decoder entropy = this;
+            int blkn;
+//          BITREAD_STATE_VARS;
+            int get_buffer;
+            int bits_left;
+//          bitread_working_state br_state = new bitread_working_state();
+//          savable_state state = new savable_state();
+            bitread_working_state br_state = br_state_local;
+            savable_state state = state_local;
+
+                /* Process restart marker if needed; may have to suspend */
+            if (cinfo.restart_interval !is 0) {
+                if (entropy.restarts_to_go is 0)
+                    if (! process_restart(cinfo))
+                        return false;
+            }
+
+            /* If we've run out of data, just leave the MCU set to zeroes.
+             * This way, we return uniform gray for the remainder of the segment.
+             */
+            if (! entropy.insufficient_data) {
+
+                /* Load up working state */
+//              BITREAD_LOAD_STATE(cinfo,entropy.bitstate);
+                br_state.cinfo = cinfo;
+                br_state.buffer = cinfo.buffer;
+                br_state.bytes_in_buffer = cinfo.bytes_in_buffer;
+                br_state.bytes_offset = cinfo.bytes_offset;
+                get_buffer = entropy.bitstate.get_buffer;
+                bits_left = entropy.bitstate.bits_left;
+
+//              ASSIGN_STATE(state, entropy.saved);
+                state.last_dc_val[0] = entropy.saved.last_dc_val[0];
+                state.last_dc_val[1] = entropy.saved.last_dc_val[1];
+                state.last_dc_val[2] = entropy.saved.last_dc_val[2];
+                state.last_dc_val[3] = entropy.saved.last_dc_val[3];
+
+                /* Outer loop handles each block in the MCU */
+
+                for (blkn = 0; blkn < cinfo.blocks_in_MCU; blkn++) {
+                    short[] block = MCU_data[blkn];
+                    d_derived_tbl dctbl = entropy.dc_cur_tbls[blkn];
+                    d_derived_tbl actbl = entropy.ac_cur_tbls[blkn];
+                    int s = 0, k, r;
+
+                    /* Decode a single block's worth of coefficients */
+
+                    /* Section F.2.2.1: decode the DC coefficient difference */
+//                  HUFF_DECODE(s, br_state, dctbl, return FALSE, label1);
+                    {
+                    int nb = 0, look;
+                    if (bits_left < HUFF_LOOKAHEAD) {
+                        if (!jpeg_fill_bit_buffer(br_state,get_buffer,bits_left, 0)) {
+                            return false;
+                        }
+                        get_buffer = br_state.get_buffer; bits_left = br_state.bits_left;
+                        if (bits_left < HUFF_LOOKAHEAD) {
+                            nb = 1;
+//                          goto slowlabel;
+                            if ((s=jpeg_huff_decode(br_state,get_buffer,bits_left,dctbl,nb)) < 0) {
+                                return false;
+                            }
+                            get_buffer = br_state.get_buffer; bits_left = br_state.bits_left;
+                        }
+                    }
+//                  look = PEEK_BITS(HUFF_LOOKAHEAD);
+                    if (nb !is 1) {
+                        look = (( (get_buffer >> (bits_left -   (HUFF_LOOKAHEAD)))) & ((1<<(HUFF_LOOKAHEAD))-1));
+                        if ((nb = dctbl.look_nbits[look]) !is 0) {
+//                          DROP_BITS(nb);
+                            bits_left -= nb;
+                            s = dctbl.look_sym[look] & 0xFF;
+                        } else {
+                            nb = HUFF_LOOKAHEAD+1;
+//                          slowlabel:
+                            if ((s=jpeg_huff_decode(br_state,get_buffer,bits_left,dctbl,nb)) < 0) {
+                                return false;
+                            }
+                            get_buffer = br_state.get_buffer; bits_left = br_state.bits_left;
+                        }
+                    }
+                    }
+
+                    if (s !is 0) {
+//                      CHECK_BIT_BUFFER(br_state, s, return FALSE);
+                        {
+                        if (bits_left < (s)) {
+                            if (!jpeg_fill_bit_buffer(br_state,get_buffer,bits_left,s)) {
+                                return false;
+                            }
+                            get_buffer = br_state.get_buffer; bits_left = br_state.bits_left;
+                        }
+                        }
+//                      r = GET_BITS(s);
+                        r = (( (get_buffer >> (bits_left -= (s)))) & ((1<<(s))-1));
+//                      s = HUFF_EXTEND(r, s);
+                        s = ((r) < extend_test[s] ? (r) + extend_offset[s] : (r));
+                    }
+
+                    if (entropy.dc_needed[blkn]) {
+                        /* Convert DC difference to actual value, update last_dc_val */
+                        int ci = cinfo.MCU_membership[blkn];
+                        s += state.last_dc_val[ci];
+                        state.last_dc_val[ci] = s;
+                        /* Output the DC coefficient (assumes jpeg_natural_order[0] = 0) */
+                        block[0] = cast(short) s;
+                    }
+
+                    if (entropy.ac_needed[blkn]) {
+
+                        /* Section F.2.2.2: decode the AC coefficients */
+                        /* Since zeroes are skipped, output area must be cleared beforehand */
+                        for (k = 1; k < DCTSIZE2; k++) {
+//                          HUFF_DECODE(s, br_state, actbl, return FALSE, label2);
+                            {
+                            int nb = 0, look;
+                            if (bits_left < HUFF_LOOKAHEAD) {
+                                if (!jpeg_fill_bit_buffer(br_state,get_buffer,bits_left, 0)) {
+                                    return false;
+                                }
+                                get_buffer = br_state.get_buffer; bits_left = br_state.bits_left;
+                                if (bits_left < HUFF_LOOKAHEAD) {
+                                    nb = 1;
+//                                  goto slowlabel;
+                                    if ((s=jpeg_huff_decode(br_state,get_buffer,bits_left,actbl,nb)) < 0) {
+                                        return false;
+                                    }
+                                    get_buffer = br_state.get_buffer; bits_left = br_state.bits_left;
+                                }
+                            }
+                            if (nb !is 1) {
+//                              look = PEEK_BITS(HUFF_LOOKAHEAD);
+                                look = (( (get_buffer >> (bits_left -   (HUFF_LOOKAHEAD)))) & ((1<<(HUFF_LOOKAHEAD))-1));
+                                if ((nb = actbl.look_nbits[look]) !is 0) {
+//                                  DROP_BITS(nb);
+                                    bits_left -= (nb);
+                                    s = actbl.look_sym[look] & 0xFF;
+                                } else {
+                                    nb = HUFF_LOOKAHEAD+1;
+//                                  slowlabel:
+                                    if ((s=jpeg_huff_decode(br_state,get_buffer,bits_left,actbl,nb)) < 0) {
+                                        return false;
+                                    }
+                                    get_buffer = br_state.get_buffer; bits_left = br_state.bits_left;
+                                }
+                            }
+                            }
+                            r = s >> 4;
+                            s &= 15;
+
+                            if (s !is 0) {
+                                k += r;
+//                              CHECK_BIT_BUFFER(br_state, s, return FALSE);
+                                {
+                                if (bits_left < (s)) {
+                                    if (!jpeg_fill_bit_buffer(br_state, get_buffer, bits_left, s)) {
+                                        return false;
+                                    }
+                                    get_buffer = br_state.get_buffer;
+                                    bits_left = br_state.bits_left;
+                                }
+                                }
+//                              r = GET_BITS(s);
+                                r = (((get_buffer >> (bits_left -= (s)))) & ((1 << (s)) - 1));
+//                              s = HUFF_EXTEND(r, s);
+                                s = ((r) < extend_test[s] ? (r) + extend_offset[s] : (r));
+                                /*
+                                 * Output coefficient in natural (dezigzagged)
+                                 * order. Note: the extra entries in
+                                 * jpeg_natural_order[] will save us if k >=
+                                 * DCTSIZE2, which could happen if the data is
+                                 * corrupted.
+                                 */
+                                block[jpeg_natural_order[k]] = cast(short) s;
+                            } else {
+                                if (r !is 15)
+                                    break;
+                                k += 15;
+                            }
+                        }
+
+                    } else {
+
+                        /* Section F.2.2.2: decode the AC coefficients */
+                        /* In this path we just discard the values */
+                        for (k = 1; k < DCTSIZE2; k++) {
+//                          HUFF_DECODE(s, br_state, actbl, return FALSE, label3);
+                            {
+                            int nb = 0, look;
+                            if (bits_left < HUFF_LOOKAHEAD) {
+                                if (!jpeg_fill_bit_buffer(br_state,get_buffer,bits_left, 0)) {
+                                    return false;
+                                }
+                                get_buffer = br_state.get_buffer; bits_left = br_state.bits_left;
+                                if (bits_left < HUFF_LOOKAHEAD) {
+                                    nb = 1;
+//                                  goto slowlabel;
+                                    if ((s=jpeg_huff_decode(br_state,get_buffer,bits_left,actbl,nb)) < 0) {
+                                        return false;
+                                    }
+                                    get_buffer = br_state.get_buffer; bits_left = br_state.bits_left;
+                                }
+                            }
+                            if (nb !is 1) {
+//                              look = PEEK_BITS(HUFF_LOOKAHEAD);
+                                look = (( (get_buffer >> (bits_left -   (HUFF_LOOKAHEAD)))) & ((1<<(HUFF_LOOKAHEAD))-1));
+                                if ((nb = actbl.look_nbits[look]) !is 0) {
+//                                  DROP_BITS(nb);
+                                    bits_left -= (nb);
+                                    s = actbl.look_sym[look] & 0xFF;
+                                } else {
+                                    nb = HUFF_LOOKAHEAD+1;
+//                                  slowlabel:
+                                    if ((s=jpeg_huff_decode(br_state,get_buffer,bits_left,actbl,nb)) < 0) {
+                                        return false;
+                                    }
+                                    get_buffer = br_state.get_buffer; bits_left = br_state.bits_left;
+                                }
+                            }
+                            }
+                            r = s >> 4;
+                            s &= 15;
+
+                            if (s !is 0) {
+                                k += r;
+//                              CHECK_BIT_BUFFER(br_state, s, return FALSE);
+                                {
+                                if (bits_left < (s)) {
+                                    if (!jpeg_fill_bit_buffer(br_state,get_buffer,bits_left,s)) {
+                                        return false;
+                                    }
+                                    get_buffer = br_state.get_buffer; bits_left = br_state.bits_left;
+                                    }
+                                }
+//                              DROP_BITS(s);
+                                bits_left -= s;
+                            } else {
+                                if (r !is 15)
+                                    break;
+                                k += 15;
+                            }
+                        }
+
+                    }
+                }
+
+                /* Completed MCU, so update state */
+//              BITREAD_SAVE_STATE(cinfo,entropy.bitstate);
+                cinfo.buffer = br_state.buffer;
+                cinfo.bytes_in_buffer = br_state.bytes_in_buffer;
+                cinfo.bytes_offset = br_state.bytes_offset;
+                entropy.bitstate.get_buffer = get_buffer;
+                entropy.bitstate.bits_left = bits_left;
+//              ASSIGN_STATE(entropy.saved, state);
+                entropy.saved.last_dc_val[0] = state.last_dc_val[0];
+                entropy.saved.last_dc_val[1] = state.last_dc_val[1];
+                entropy.saved.last_dc_val[2] = state.last_dc_val[2];
+                entropy.saved.last_dc_val[3] = state.last_dc_val[3];
+            }
+
+            /* Account for restart interval (no-op if not using restarts) */
+            entropy.restarts_to_go--;
+
+            return true;
+        }
+
+        void start_pass_huff_decoder (jpeg_decompress_struct cinfo) {
+            huff_entropy_decoder entropy = this;
+            int ci, blkn, dctbl, actbl;
+            jpeg_component_info compptr;
+
+            /* Check that the scan parameters Ss, Se, Ah/Al are OK for sequential JPEG.
+             * This ought to be an error condition, but we make it a warning because
+             * there are some baseline files out there with all zeroes in these bytes.
+             */
+            if (cinfo.Ss !is 0 || cinfo.Se !is DCTSIZE2-1 || cinfo.Ah !is 0 || cinfo.Al !is 0) {
+//              WARNMS(cinfo, JWRN_NOT_SEQUENTIAL);
+            }
+
+            for (ci = 0; ci < cinfo.comps_in_scan; ci++) {
+                compptr = cinfo.cur_comp_info[ci];
+                dctbl = compptr.dc_tbl_no;
+                actbl = compptr.ac_tbl_no;
+                /* Compute derived values for Huffman tables */
+                /* We may do this more than once for a table, but it's not expensive */
+                jpeg_make_d_derived_tbl(cinfo, true, dctbl, entropy.dc_derived_tbls[dctbl] = new d_derived_tbl());
+                jpeg_make_d_derived_tbl(cinfo, false, actbl, entropy.ac_derived_tbls[actbl] = new d_derived_tbl());
+                /* Initialize DC predictions to 0 */
+                entropy.saved.last_dc_val[ci] = 0;
+            }
+
+            /* Precalculate decoding info for each block in an MCU of this scan */
+            for (blkn = 0; blkn < cinfo.blocks_in_MCU; blkn++) {
+                ci = cinfo.MCU_membership[blkn];
+                compptr = cinfo.cur_comp_info[ci];
+                /* Precalculate which table to use for each block */
+                entropy.dc_cur_tbls[blkn] = entropy.dc_derived_tbls[compptr.dc_tbl_no];
+                entropy.ac_cur_tbls[blkn] = entropy.ac_derived_tbls[compptr.ac_tbl_no];
+                /* Decide whether we really care about the coefficient values */
+                if (compptr.component_needed) {
+                    entropy.dc_needed[blkn] = true;
+                    /* we don't need the ACs if producing a 1/8th-size image */
+                    entropy.ac_needed[blkn] = (compptr.DCT_scaled_size > 1);
+                } else {
+                    entropy.dc_needed[blkn] = entropy.ac_needed[blkn] = false;
+                }
+            }
+
+            /* Initialize bitread state variables */
+            entropy.bitstate.bits_left = 0;
+            entropy.bitstate.get_buffer = 0; /* unnecessary, but keeps Purify quiet */
+            entropy.insufficient_data = false;
+
+            /* Initialize restart counter */
+            entropy.restarts_to_go = cinfo.restart_interval;
+        }
+
+        bool process_restart (jpeg_decompress_struct cinfo) {
+            huff_entropy_decoder entropy = this;
+            int ci;
+
+            /* Throw away any unused bits remaining in bit buffer; */
+            /* include any full bytes in next_marker's count of discarded bytes */
+            cinfo.marker.discarded_bytes += entropy.bitstate.bits_left / 8;
+            entropy.bitstate.bits_left = 0;
+
+            /* Advance past the RSTn marker */
+            if (! read_restart_marker (cinfo))
+                return false;
+
+            /* Re-initialize DC predictions to 0 */
+            for (ci = 0; ci < cinfo.comps_in_scan; ci++)
+                entropy.saved.last_dc_val[ci] = 0;
+
+            /* Reset restart counter */
+            entropy.restarts_to_go = cinfo.restart_interval;
+
+            /* Reset out-of-data flag, unless read_restart_marker left us smack up
+             * against a marker.    In that case we will end up treating the next data
+             * segment as empty, and we can avoid producing bogus output pixels by
+             * leaving the flag set.
+             */
+            if (cinfo.unread_marker is 0)
+                entropy.insufficient_data = false;
+
+            return true;
+        }
+    }
+
+    static final class phuff_entropy_decoder : jpeg_entropy_decoder {
+
+        /* These fields are loaded into local variables at start of each MCU.
+         * In case of suspension, we exit WITHOUT updating them.
+         */
+        bitread_perm_state bitstate;// = new bitread_perm_state();  /* Bit buffer at start of MCU */
+        savable_state saved;// = new savable_state();       /* Other state at start of MCU */
+
+        /* These fields are NOT loaded into local working state. */
+        int restarts_to_go; /* MCUs left in this restart interval */
+
+        /* Pointers to derived tables (these workspaces have image lifespan) */
+        d_derived_tbl[NUM_HUFF_TBLS] derived_tbls;// = new d_derived_tbl[NUM_HUFF_TBLS];
+
+        d_derived_tbl ac_derived_tbl; /* active table during an AC scan */
+
+        int[DCTSIZE2] newnz_pos;// = new int[DCTSIZE2];
+
+        public this(){
+            bitstate = new bitread_perm_state(); /* Bit buffer at start of MCU */
+            saved = new savable_state();      /* Other state at start of MCU */
+        }
+
+        void start_pass (jpeg_decompress_struct cinfo) {
+            start_pass_phuff_decoder(cinfo);
+        }
+
+        bool decode_mcu (jpeg_decompress_struct cinfo, short[][] MCU_data) {
+            bool is_DC_band = (cinfo.Ss is 0);
+            if (cinfo.Ah is 0) {
+                if (is_DC_band)
+                    return decode_mcu_DC_first(cinfo, MCU_data);
+                else
+                    return decode_mcu_AC_first(cinfo, MCU_data);
+            } else {
+                if (is_DC_band)
+                    return decode_mcu_DC_refine(cinfo, MCU_data);
+                else
+                    return decode_mcu_AC_refine(cinfo, MCU_data);
+            }
+        }
+
+        bool decode_mcu_DC_refine (jpeg_decompress_struct cinfo, short[][] MCU_data) {
+            phuff_entropy_decoder entropy = this;
+            int p1 = 1 << cinfo.Al; /* 1 in the bit position being coded */
+            int blkn;
+            short[] block;
+//          BITREAD_STATE_VARS;
+            int get_buffer;
+            int bits_left;
+//          bitread_working_state br_state = new bitread_working_state();
+            bitread_working_state br_state = br_state_local;
+
+            /* Process restart marker if needed; may have to suspend */
+            if (cinfo.restart_interval !is 0) {
+                if (entropy.restarts_to_go is 0)
+                    if (! process_restart(cinfo))
+                        return false;
+            }
+
+            /* Not worth the cycles to check insufficient_data here,
+             * since we will not change the data anyway if we read zeroes.
+             */
+
+            /* Load up working state */
+//          BITREAD_LOAD_STATE(cinfo,entropy.bitstate);
+            br_state.cinfo = cinfo;
+            br_state.buffer = cinfo.buffer;
+            br_state.bytes_in_buffer = cinfo.bytes_in_buffer;
+            br_state.bytes_offset = cinfo.bytes_offset;
+            get_buffer = entropy.bitstate.get_buffer;
+            bits_left = entropy.bitstate.bits_left;
+
+            /* Outer loop handles each block in the MCU */
+
+            for (blkn = 0; blkn < cinfo.blocks_in_MCU; blkn++) {
+                block = MCU_data[blkn];
+
+                /* Encoded data is simply the next bit of the two's-complement DC value */
+//              CHECK_BIT_BUFFER(br_state, 1, return FALSE);
+                {
+                if (bits_left < (1)) {
+                    if (!jpeg_fill_bit_buffer(br_state,get_buffer,bits_left,1)) {
+                         return false;
+                    }
+                    get_buffer = br_state.get_buffer; bits_left = br_state.bits_left;
+                }
+                }
+//              if (GET_BITS(1))
+                if ((( (get_buffer >> (bits_left -= (1)))) & ((1<<(1))-1)) !is 0)
+                    block[0] |= p1;
+                    /* Note: since we use |=, repeating the assignment later is safe */
+            }
+
+            /* Completed MCU, so update state */
+//          BITREAD_SAVE_STATE(cinfo,entropy.bitstate);
+            cinfo.buffer = br_state.buffer;
+            cinfo.bytes_in_buffer = br_state.bytes_in_buffer;
+            cinfo.bytes_offset = br_state.bytes_offset;
+            entropy.bitstate.get_buffer = get_buffer;
+            entropy.bitstate.bits_left = bits_left;
+
+            /* Account for restart interval (no-op if not using restarts) */
+            entropy.restarts_to_go--;
+
+            return true;
+
+        }
+
+        bool decode_mcu_AC_refine (jpeg_decompress_struct cinfo, short[][] MCU_data) {
+            phuff_entropy_decoder entropy = this;
+            int Se = cinfo.Se;
+            int p1 = 1 << cinfo.Al; /* 1 in the bit position being coded */
+            int m1 = (-1) << cinfo.Al;  /* -1 in the bit position being coded */
+            int s = 0, k, r;
+            int EOBRUN;
+            short[] block;
+            short[] thiscoef;
+//          BITREAD_STATE_VARS;
+            int get_buffer;
+            int bits_left;
+//          bitread_working_state br_state = new bitread_working_state();
+            bitread_working_state br_state = br_state_local;
+
+            d_derived_tbl tbl;
+            int num_newnz;
+            int[] newnz_pos = entropy.newnz_pos;
+
+                /* Process restart marker if needed; may have to suspend */
+            if (cinfo.restart_interval !is 0) {
+                if (entropy.restarts_to_go is 0)
+                    if (! process_restart(cinfo))
+                        return false;
+            }
+
+            /* If we've run out of data, don't modify the MCU.
+             */
+            if (! entropy.insufficient_data) {
+
+                /* Load up working state */
+//              BITREAD_LOAD_STATE(cinfo,entropy.bitstate);
+                br_state.cinfo = cinfo;
+                br_state.buffer = cinfo.buffer;
+                br_state.bytes_in_buffer = cinfo.bytes_in_buffer;
+                br_state.bytes_offset = cinfo.bytes_offset;
+                get_buffer = entropy.bitstate.get_buffer;
+                bits_left = entropy.bitstate.bits_left;
+
+                EOBRUN = entropy.saved.EOBRUN; /* only part of saved state we need */
+
+                /* There is always only one block per MCU */
+                block = MCU_data[0];
+                tbl = entropy.ac_derived_tbl;
+
+                /* If we are forced to suspend, we must undo the assignments to any newly
+                 * nonzero coefficients in the block, because otherwise we'd get confused
+                 * next time about which coefficients were already nonzero.
+                 * But we need not undo addition of bits to already-nonzero coefficients;
+                 * instead, we can test the current bit to see if we already did it.
+                 */
+                num_newnz = 0;
+
+                /* initialize coefficient loop counter to start of band */
+                k = cinfo.Ss;
+
+                if (EOBRUN is 0) {
+                    for (; k <= Se; k++) {
+//                      HUFF_DECODE(s, br_state, tbl, goto undoit, label3);
+                        {
+                        int nb = 0, look;
+                        if (bits_left < HUFF_LOOKAHEAD) {
+                            if (! jpeg_fill_bit_buffer(br_state,get_buffer,bits_left, 0)) {
+//                              failaction;
+                                while (num_newnz > 0)
+                                    block[newnz_pos[--num_newnz]] = 0;
+
+                                return false;
+                            }
+                            get_buffer = br_state.get_buffer; bits_left = br_state.bits_left;
+                            if (bits_left < HUFF_LOOKAHEAD) {
+                                nb = 1;
+//                              goto slowlabel;
+                                if ((s=jpeg_huff_decode(br_state,get_buffer,bits_left,tbl,nb)) < 0) {
+//                                  failaction;
+                                    while (num_newnz > 0)
+                                        block[newnz_pos[--num_newnz]] = 0;
+
+                                    return false;
+                                }
+                                get_buffer = br_state.get_buffer; bits_left = br_state.bits_left;
+                            }
+                        }
+                        if (nb !is 1) {
+//                          look = PEEK_BITS(HUFF_LOOKAHEAD);
+                            look = (( (get_buffer >> (bits_left -   (HUFF_LOOKAHEAD)))) & ((1<<(HUFF_LOOKAHEAD))-1));
+                            if ((nb = tbl.look_nbits[look]) !is 0) {
+//                              DROP_BITS(nb);
+                                bits_left -= nb;
+                                s = tbl.look_sym[look] & 0xFF;
+                            } else {
+                                nb = HUFF_LOOKAHEAD+1;
+//                              slowlabel:
+                                if ((s=jpeg_huff_decode(br_state,get_buffer,bits_left,tbl,nb)) < 0) {
+//                                  failaction;
+                                    while (num_newnz > 0)
+                                        block[newnz_pos[--num_newnz]] = 0;
+
+                                    return false;
+                                }
+                                get_buffer = br_state.get_buffer; bits_left = br_state.bits_left;
+                            }
+                        }
+                        }
+                        r = s >> 4;
+                        s &= 15;
+                        if (s !is 0) {
+                            if (s !is 1) {       /* size of new coef should always be 1 */
+//                              WARNMS(cinfo, JWRN_HUFF_BAD_CODE);
+                            }
+//                          CHECK_BIT_BUFFER(br_state, 1, goto undoit);
+                            {
+                            if (bits_left < (1)) {
+                                if (! jpeg_fill_bit_buffer(br_state,get_buffer,bits_left,1)) {
+//                                  failaction;
+                                    while (num_newnz > 0)
+                                        block[newnz_pos[--num_newnz]] = 0;
+
+                                    return false;
+                                }
+                                get_buffer = br_state.get_buffer; bits_left = br_state.bits_left;
+                                }
+                            }
+//                          if (GET_BITS(1))
+                            if ((( (get_buffer >> (bits_left -= (1)))) & ((1<<(1))-1)) !is 0)
+                                s = p1;     /* newly nonzero coef is positive */
+                            else
+                                s = m1;     /* newly nonzero coef is negative */
+                        } else {
+                            if (r !is 15) {
+                                EOBRUN = 1 << r;    /* EOBr, run length is 2^r + appended bits */
+                                if (r !is 0) {
+//                                  CHECK_BIT_BUFFER(br_state, r, goto undoit);
+                                    {
+                                    if (bits_left < (r)) {
+                                        if (!jpeg_fill_bit_buffer(br_state,get_buffer,bits_left,r)) {
+//                                          failaction;
+                                            while (num_newnz > 0)
+                                                block[newnz_pos[--num_newnz]] = 0;
+
+                                            return false;
+                                        }
+                                        get_buffer = br_state.get_buffer; bits_left = br_state.bits_left;
+                                    }
+                                    }
+//                                  r = GET_BITS(r);
+                                    r = (( (get_buffer >> (bits_left -= (r)))) & ((1<<(r))-1));
+                                    EOBRUN += r;
+                                }
+                                break;      /* rest of block is handled by EOB logic */
+                            }
+                            /* note s = 0 for processing ZRL */
+                        }
+                        /* Advance over already-nonzero coefs and r still-zero coefs,
+                         * appending correction bits to the nonzeroes.  A correction bit is 1
+                         * if the absolute value of the coefficient must be increased.
+                         */
+                        do {
+                            thiscoef = block;
+                            int thiscoef_offset = jpeg_natural_order[k];
+                            if (thiscoef[thiscoef_offset] !is 0) {
+//                              CHECK_BIT_BUFFER(br_state, 1, goto undoit);
+                                {
+                                if (bits_left < (1)) {
+                                    if (!jpeg_fill_bit_buffer(br_state,get_buffer,bits_left,1)) {
+//                                      failaction;
+                                        while (num_newnz > 0)
+                                            block[newnz_pos[--num_newnz]] = 0;
+
+                                        return false;
+                                    }
+                                    get_buffer = br_state.get_buffer; bits_left = br_state.bits_left;
+                                }
+                                }
+//                              if (GET_BITS(1)) {
+                                if ((( (get_buffer >> (bits_left -= (1)))) & ((1<<(1))-1)) !is 0) {
+                                    if ((thiscoef[thiscoef_offset] & p1) is 0) { /* do nothing if already set it */
+                                        if (thiscoef[thiscoef_offset] >= 0)
+                                            thiscoef[thiscoef_offset] += p1;
+                                        else
+                                            thiscoef[thiscoef_offset] += m1;
+                                    }
+                                }
+                            } else {
+                                if (--r < 0)
+                                    break;      /* reached target zero coefficient */
+                            }
+                            k++;
+                        } while (k <= Se);
+                        if (s !is 0) {
+                            int pos = jpeg_natural_order[k];
+                            /* Output newly nonzero coefficient */
+                            block[pos] = cast(short) s;
+                            /* Remember its position in case we have to suspend */
+                            newnz_pos[num_newnz++] = pos;
+                        }
+                    }
+                }
+
+                if (EOBRUN > 0) {
+                    /* Scan any remaining coefficient positions after the end-of-band
+                     * (the last newly nonzero coefficient, if any).    Append a correction
+                     * bit to each already-nonzero coefficient. A correction bit is 1
+                     * if the absolute value of the coefficient must be increased.
+                     */
+                    for (; k <= Se; k++) {
+                        thiscoef = block;
+                        int thiscoef_offset = jpeg_natural_order[k];
+                        if (thiscoef[thiscoef_offset] !is 0) {
+//                          CHECK_BIT_BUFFER(br_state, 1, goto undoit);
+                            {
+                            if (bits_left < (1)) {
+                                if (! jpeg_fill_bit_buffer(br_state,get_buffer,bits_left,1)) {
+//                                  failaction;
+                                    while (num_newnz > 0)
+                                        block[newnz_pos[--num_newnz]] = 0;
+
+                                    return false;
+                                }
+                                get_buffer = br_state.get_buffer; bits_left = br_state.bits_left;
+                            }
+                            }
+//                          if (GET_BITS(1)) {
+                            if ((( (get_buffer >> (bits_left -= (1)))) & ((1<<(1))-1)) !is 0) {
+                                if ((thiscoef[thiscoef_offset] & p1) is 0) { /* do nothing if already changed it */
+                                    if (thiscoef[thiscoef_offset] >= 0)
+                                        thiscoef[thiscoef_offset] += p1;
+                                    else
+                                        thiscoef[thiscoef_offset] += m1;
+                                }
+                            }
+                        }
+                    }
+                        /* Count one block completed in EOB run */
+                    EOBRUN--;
+                }
+
+                /* Completed MCU, so update state */
+//              BITREAD_SAVE_STATE(cinfo,entropy.bitstate);
+                cinfo.buffer = br_state.buffer;
+                cinfo.bytes_in_buffer = br_state.bytes_in_buffer;
+                cinfo.bytes_offset = br_state.bytes_offset;
+                entropy.bitstate.get_buffer = get_buffer;
+                entropy.bitstate.bits_left = bits_left;
+
+                entropy.saved.EOBRUN = EOBRUN; /* only part of saved state we need */
+            }
+
+            /* Account for restart interval (no-op if not using restarts) */
+            entropy.restarts_to_go--;
+
+            return true;
+
+//          undoit:
+//              /* Re-zero any output coefficients that we made newly nonzero */
+//              while (num_newnz > 0)
+//                  (*block)[newnz_pos[--num_newnz]] = 0;
+//
+//              return false;
+
+        }
+
+        bool decode_mcu_AC_first (jpeg_decompress_struct cinfo, short[][] MCU_data) {
+            phuff_entropy_decoder entropy = this;
+            int Se = cinfo.Se;
+            int Al = cinfo.Al;
+            int s = 0, k, r;
+            int EOBRUN;
+            short[] block;
+//          BITREAD_STATE_VARS;
+            int get_buffer;
+            int bits_left;
+//          bitread_working_state br_state = new bitread_working_state();
+            bitread_working_state br_state = br_state_local;
+
+            d_derived_tbl tbl;
+
+            /* Process restart marker if needed; may have to suspend */
+            if (cinfo.restart_interval !is 0) {
+                if (entropy.restarts_to_go is 0)
+                    if (! process_restart(cinfo))
+                        return false;
+            }
+
+            /* If we've run out of data, just leave the MCU set to zeroes.
+             * This way, we return uniform gray for the remainder of the segment.
+             */
+            if (! entropy.insufficient_data) {
+
+                /* Load up working state.
+                 * We can avoid loading/saving bitread state if in an EOB run.
+                 */
+                EOBRUN = entropy.saved.EOBRUN;  /* only part of saved state we need */
+
+                /* There is always only one block per MCU */
+
+                if (EOBRUN > 0)     /* if it's a band of zeroes... */
+                    EOBRUN--;           /* ...process it now (we do nothing) */
+                else {
+//                  BITREAD_LOAD_STATE(cinfo,entropy.bitstate);
+                    br_state.cinfo = cinfo;
+                    br_state.buffer = cinfo.buffer;
+                    br_state.bytes_in_buffer = cinfo.bytes_in_buffer;
+                    br_state.bytes_offset = cinfo.bytes_offset;
+                    get_buffer = entropy.bitstate.get_buffer;
+                    bits_left = entropy.bitstate.bits_left;
+
+                    block = MCU_data[0];
+                    tbl = entropy.ac_derived_tbl;
+
+                    for (k = cinfo.Ss; k <= Se; k++) {
+//                      HUFF_DECODE(s, br_state, tbl, return FALSE, label2);
+                        {
+                        int nb = 0, look;
+                        if (bits_left < HUFF_LOOKAHEAD) {
+                            if (! jpeg_fill_bit_buffer(br_state,get_buffer,bits_left, 0)) {
+                                return false;
+                            }
+                            get_buffer = br_state.get_buffer; bits_left = br_state.bits_left;
+                            if (bits_left < HUFF_LOOKAHEAD) {
+                                nb = 1;
+//                              goto slowlabel;
+                                if ((s=jpeg_huff_decode(br_state,get_buffer,bits_left,tbl,nb)) < 0) {
+                                    return false;
+                                }
+                                get_buffer = br_state.get_buffer; bits_left = br_state.bits_left;
+                            }
+                        }
+                        if (nb !is 1) {
+//                          look = PEEK_BITS(HUFF_LOOKAHEAD);
+                            look = (( (get_buffer >> (bits_left -   (HUFF_LOOKAHEAD)))) & ((1<<(HUFF_LOOKAHEAD))-1));
+
+                            if ((nb = tbl.look_nbits[look]) !is 0) {
+//                              DROP_BITS(nb);
+                                bits_left -= nb;
+                                s = tbl.look_sym[look] & 0xFF;
+                            } else {
+                                nb = HUFF_LOOKAHEAD+1;
+//                              slowlabel:
+                                if ((s=jpeg_huff_decode(br_state,get_buffer,bits_left,tbl,nb)) < 0) {
+                                    return false;
+                                }
+                                get_buffer = br_state.get_buffer; bits_left = br_state.bits_left;
+                            }
+                        }
+                        }
+                        r = s >> 4;
+                        s &= 15;
+                        if (s !is 0) {
+                            k += r;
+//                          CHECK_BIT_BUFFER(br_state, s, return FALSE);
+                            {
+                            if (bits_left < (s)) {
+                                if (! jpeg_fill_bit_buffer(br_state,get_buffer,bits_left,s)) {
+                                    return false;
+                                }
+                                get_buffer = br_state.get_buffer; bits_left = br_state.bits_left;
+                            }
+                            }
+//                          r = GET_BITS(s);
+                            r = (( (get_buffer >> (bits_left -= (s)))) & ((1<<(s))-1));
+//                          s = HUFF_EXTEND(r, s);
+                            s = ((r) < extend_test[s] ? (r) + extend_offset[s] : (r));
+                            /* Scale and output coefficient in natural (dezigzagged) order */
+                            block[jpeg_natural_order[k]] = cast(short) (s << Al);
+                        } else {
+                            if (r is 15) {  /* ZRL */
+                                k += 15;        /* skip 15 zeroes in band */
+                            } else {        /* EOBr, run length is 2^r + appended bits */
+                                EOBRUN = 1 << r;
+                                if (r !is 0) {       /* EOBr, r > 0 */
+//                                  CHECK_BIT_BUFFER(br_state, r, return FALSE);
+                                    {
+                                    if (bits_left < (r)) {
+                                        if (! jpeg_fill_bit_buffer(br_state,get_buffer,bits_left,r)) {
+                                            return false;
+                                        }
+                                        get_buffer = br_state.get_buffer; bits_left = br_state.bits_left;
+                                    }
+                                    }
+//                                  r = GET_BITS(r);
+                                    r = (( (get_buffer >> (bits_left -= (r)))) & ((1<<(r))-1));
+                                    EOBRUN += r;
+                                }
+                                EOBRUN--;       /* this band is processed at this moment */
+                                break;      /* force end-of-band */
+                            }
+                        }
+                    }
+
+//                  BITREAD_SAVE_STATE(cinfo,entropy.bitstate);
+                    cinfo.buffer = br_state.buffer;
+                    cinfo.bytes_in_buffer = br_state.bytes_in_buffer;
+                    cinfo.bytes_offset = br_state.bytes_offset;
+                    entropy.bitstate.get_buffer = get_buffer;
+                    entropy.bitstate.bits_left = bits_left;
+                }
+
+                /* Completed MCU, so update state */
+                entropy.saved.EOBRUN = EOBRUN;  /* only part of saved state we need */
+            }
+
+            /* Account for restart interval (no-op if not using restarts) */
+            entropy.restarts_to_go--;
+
+            return true;
+        }
+
+        bool decode_mcu_DC_first (jpeg_decompress_struct cinfo, short[][] MCU_data) {
+            phuff_entropy_decoder entropy = this;
+            int Al = cinfo.Al;
+            int s = 0, r;
+            int blkn, ci;
+            short[] block;
+//          BITREAD_STATE_VARS;
+            int get_buffer;
+            int bits_left;
+//          bitread_working_state br_state = new bitread_working_state();
+            bitread_working_state br_state = br_state_local;
+
+//          savable_state state = new savable_state();
+            savable_state state = state_local;
+            d_derived_tbl tbl;
+            jpeg_component_info compptr;
+
+            /* Process restart marker if needed; may have to suspend */
+            if (cinfo.restart_interval !is 0) {
+                if (entropy.restarts_to_go is 0)
+                    if (! process_restart(cinfo))
+                        return false;
+            }
+
+            /* If we've run out of data, just leave the MCU set to zeroes.
+             * This way, we return uniform gray for the remainder of the segment.
+             */
+            if (! entropy.insufficient_data) {
+
+                /* Load up working state */
+//              BITREAD_LOAD_STATE(cinfo,entropy.bitstate);
+                br_state.cinfo = cinfo;
+                br_state.buffer = cinfo.buffer;
+                br_state.bytes_in_buffer = cinfo.bytes_in_buffer;
+                br_state.bytes_offset = cinfo.bytes_offset;
+                get_buffer = entropy.bitstate.get_buffer;
+                bits_left = entropy.bitstate.bits_left;
+
+//              ASSIGN_STATE(state, entropy.saved);
+                state.EOBRUN = entropy.saved.EOBRUN;
+                state.last_dc_val[0] = entropy.saved.last_dc_val[0];
+                state.last_dc_val[1] = entropy.saved.last_dc_val[1];
+                state.last_dc_val[2] = entropy.saved.last_dc_val[2];
+                state.last_dc_val[3] = entropy.saved.last_dc_val[3];
+
+                /* Outer loop handles each block in the MCU */
+
+                for (blkn = 0; blkn < cinfo.blocks_in_MCU; blkn++) {
+                    block = MCU_data[blkn];
+                    ci = cinfo.MCU_membership[blkn];
+                    compptr = cinfo.cur_comp_info[ci];
+                    tbl = entropy.derived_tbls[compptr.dc_tbl_no];
+
+                    /* Decode a single block's worth of coefficients */
+
+                    /* Section F.2.2.1: decode the DC coefficient difference */
+//                  HUFF_DECODE(s, br_state, tbl, return FALSE, label1);
+                    {
+                    int nb = 0, look;
+                    if (bits_left < HUFF_LOOKAHEAD) {
+                        if (! jpeg_fill_bit_buffer(br_state,get_buffer,bits_left, 0)) {
+                            return false;
+                        }
+                        get_buffer = br_state.get_buffer; bits_left = br_state.bits_left;
+                        if (bits_left < HUFF_LOOKAHEAD) {
+                            nb = 1;
+//                          goto slowlabel;
+                            if ((s=jpeg_huff_decode(br_state,get_buffer,bits_left,tbl,nb)) < 0) {
+                                return false;
+                            }
+                            get_buffer = br_state.get_buffer; bits_left = br_state.bits_left;
+                        }
+                    }
+                    if (nb !is 1) {
+//                      look = PEEK_BITS(HUFF_LOOKAHEAD);
+                        look = (( (get_buffer >> (bits_left -   (HUFF_LOOKAHEAD)))) & ((1<<(HUFF_LOOKAHEAD))-1));
+
+                        if ((nb = tbl.look_nbits[look]) !is 0) {
+//                          DROP_BITS(nb);
+                            bits_left -= nb;
+                            s = tbl.look_sym[look] & 0xFF;
+                        } else {
+                            nb = HUFF_LOOKAHEAD+1;
+//                          slowlabel:
+                            if ((s=jpeg_huff_decode(br_state,get_buffer,bits_left,tbl,nb)) < 0) {
+                                return false;
+                            }
+                            get_buffer = br_state.get_buffer; bits_left = br_state.bits_left;
+                        }
+                    }
+                    }
+                    if (s !is 0) {
+//                      CHECK_BIT_BUFFER(br_state, s, return FALSE);
+                        {
+                        if (bits_left < (s)) {
+                            if (! jpeg_fill_bit_buffer(br_state,get_buffer,bits_left,s)) {
+                                return false;
+                            }
+                            get_buffer = br_state.get_buffer; bits_left = br_state.bits_left;
+                        }
+                        }
+//                      r = GET_BITS(s);
+                        r = (( (get_buffer >> (bits_left -= (s)))) & ((1<<(s))-1));
+//                      s = HUFF_EXTEND(r, s);
+                        s = ((r) < extend_test[s] ? (r) + extend_offset[s] : (r));
+                    }
+
+                        /* Convert DC difference to actual value, update last_dc_val */
+                    s += state.last_dc_val[ci];
+                    state.last_dc_val[ci] = s;
+                    /* Scale and output the coefficient (assumes jpeg_natural_order[0]=0) */
+                    block[0] = cast(short) (s << Al);
+                }
+
+                /* Completed MCU, so update state */
+//              BITREAD_SAVE_STATE(cinfo,entropy.bitstate);
+                cinfo.buffer = br_state.buffer;
+                cinfo.bytes_in_buffer = br_state.bytes_in_buffer;
+                cinfo.bytes_offset = br_state.bytes_offset;
+                entropy.bitstate.get_buffer = get_buffer;
+                entropy.bitstate.bits_left = bits_left;
+//              ASSIGN_STATE(entropy.saved, state);
+                entropy.saved.EOBRUN = state.EOBRUN;
+                entropy.saved.last_dc_val[0] = state.last_dc_val[0];
+                entropy.saved.last_dc_val[1] = state.last_dc_val[1];
+                entropy.saved.last_dc_val[2] = state.last_dc_val[2];
+                entropy.saved.last_dc_val[3] = state.last_dc_val[3];
+            }
+
+            /* Account for restart interval (no-op if not using restarts) */
+            entropy.restarts_to_go--;
+
+            return true;
+        }
+
+        bool process_restart (jpeg_decompress_struct cinfo) {
+            phuff_entropy_decoder entropy = this;
+            int ci;
+
+            /* Throw away any unused bits remaining in bit buffer; */
+            /* include any full bytes in next_marker's count of discarded bytes */
+            cinfo.marker.discarded_bytes += entropy.bitstate.bits_left / 8;
+            entropy.bitstate.bits_left = 0;
+
+            /* Advance past the RSTn marker */
+            if (! read_restart_marker (cinfo))
+                return false;
+
+            /* Re-initialize DC predictions to 0 */
+            for (ci = 0; ci < cinfo.comps_in_scan; ci++)
+                entropy.saved.last_dc_val[ci] = 0;
+                /* Re-init EOB run count, too */
+            entropy.saved.EOBRUN = 0;
+
+            /* Reset restart counter */
+            entropy.restarts_to_go = cinfo.restart_interval;
+
+            /* Reset out-of-data flag, unless read_restart_marker left us smack up
+             * against a marker.    In that case we will end up treating the next data
+             * segment as empty, and we can avoid producing bogus output pixels by
+             * leaving the flag set.
+             */
+            if (cinfo.unread_marker is 0)
+                entropy.insufficient_data = false;
+
+            return true;
+        }
+
+        void start_pass_phuff_decoder (jpeg_decompress_struct cinfo) {
+            phuff_entropy_decoder entropy = this;
+            bool is_DC_band, bad;
+            int ci, coefi, tbl;
+            int[] coef_bit_ptr;
+            jpeg_component_info compptr;
+
+            is_DC_band = (cinfo.Ss is 0);
+
+            /* Validate scan parameters */
+            bad = false;
+            if (is_DC_band) {
+                if (cinfo.Se !is 0)
+                    bad = true;
+            } else {
+                /* need not check Ss/Se < 0 since they came from unsigned bytes */
+                if (cinfo.Ss > cinfo.Se || cinfo.Se >= DCTSIZE2)
+                    bad = true;
+                /* AC scans may have only one component */
+                if (cinfo.comps_in_scan !is 1)
+                    bad = true;
+            }
+            if (cinfo.Ah !is 0) {
+                /* Successive approximation refinement scan: must have Al = Ah-1. */
+                if (cinfo.Al !is cinfo.Ah-1)
+                    bad = true;
+            }
+            if (cinfo.Al > 13)      /* need not check for < 0 */
+                bad = true;
+            /* Arguably the maximum Al value should be less than 13 for 8-bit precision,
+             * but the spec doesn't say so, and we try to be liberal about what we
+             * accept.  Note: large Al values could result in out-of-range DC
+             * coefficients during early scans, leading to bizarre displays due to
+             * overflows in the IDCT math.  But we won't crash.
+             */
+            if (bad)
+                error();
+//              ERREXIT4(cinfo, JERR_BAD_PROGRESSION, cinfo.Ss, cinfo.Se, cinfo.Ah, cinfo.Al);
+            /* Update progression status, and verify that scan order is legal.
+             * Note that inter-scan inconsistencies are treated as warnings
+             * not fatal errors ... not clear if this is right way to behave.
+             */
+            for (ci = 0; ci < cinfo.comps_in_scan; ci++) {
+                int cindex = cinfo.cur_comp_info[ci].component_index;
+                coef_bit_ptr = cinfo.coef_bits[cindex];
+                if (!is_DC_band && coef_bit_ptr[0] < 0) {/* AC without prior DC scan */
+//                  WARNMS2(cinfo, JWRN_BOGUS_PROGRESSION, cindex, 0);
+                }
+                for (coefi = cinfo.Ss; coefi <= cinfo.Se; coefi++) {
+                    int expected = (coef_bit_ptr[coefi] < 0) ? 0 : coef_bit_ptr[coefi];
+                    if (cinfo.Ah !is expected) {
+//                      WARNMS2(cinfo, JWRN_BOGUS_PROGRESSION, cindex, coefi);
+                    }
+                    coef_bit_ptr[coefi] = cinfo.Al;
+                }
+            }
+
+            /* Select MCU decoding routine */
+//          if (cinfo.Ah is 0) {
+//              if (is_DC_band)
+//                  entropy.pub.decode_mcu = decode_mcu_DC_first;
+//              else
+//                  entropy.pub.decode_mcu = decode_mcu_AC_first;
+//          } else {
+//              if (is_DC_band)
+//                  entropy.pub.decode_mcu = decode_mcu_DC_refine;
+//              else
+//                  entropy.pub.decode_mcu = decode_mcu_AC_refine;
+//          }
+
+            for (ci = 0; ci < cinfo.comps_in_scan; ci++) {
+                compptr = cinfo.cur_comp_info[ci];
+                /* Make sure requested tables are present, and compute derived tables.
+                 * We may build same derived table more than once, but it's not expensive.
+                 */
+                if (is_DC_band) {
+                    if (cinfo.Ah is 0) {    /* DC refinement needs no table */
+                        tbl = compptr.dc_tbl_no;
+                        jpeg_make_d_derived_tbl(cinfo, true, tbl, entropy.derived_tbls[tbl] = new d_derived_tbl());
+                    }
+                } else {
+                    tbl = compptr.ac_tbl_no;
+                    jpeg_make_d_derived_tbl(cinfo, false, tbl, entropy.derived_tbls[tbl] = new d_derived_tbl());
+                    /* remember the single active table */
+                    entropy.ac_derived_tbl = entropy.derived_tbls[tbl];
+                }
+                /* Initialize DC predictions to 0 */
+                entropy.saved.last_dc_val[ci] = 0;
+            }
+
+            /* Initialize bitread state variables */
+            entropy.bitstate.bits_left = 0;
+            entropy.bitstate.get_buffer = 0; /* unnecessary, but keeps Purify quiet */
+            entropy.insufficient_data = false;
+
+            /* Initialize private state variables */
+            entropy.saved.EOBRUN = 0;
+
+            /* Initialize restart counter */
+            entropy.restarts_to_go = cinfo.restart_interval;
+        }
+
+    }
+
+    static final class jpeg_component_info {
+        /* These values are fixed over the whole image. */
+        /* For compression, they must be supplied by parameter setup; */
+        /* for decompression, they are read from the SOF marker. */
+        int component_id;       /* identifier for this component (0..255) */
+        int component_index;        /* its index in SOF or cinfo.comp_info[] */
+        int h_samp_factor;      /* horizontal sampling factor (1..4) */
+        int v_samp_factor;      /* vertical sampling factor (1..4) */
+        int quant_tbl_no;       /* quantization table selector (0..3) */
+        /* These values may vary between scans. */
+        /* For compression, they must be supplied by parameter setup; */
+        /* for decompression, they are read from the SOS marker. */
+        /* The decompressor output side may not use these variables. */
+        int dc_tbl_no;      /* DC entropy table selector (0..3) */
+        int ac_tbl_no;      /* AC entropy table selector (0..3) */
+
+        /* Remaining fields should be treated as private by applications. */
+
+        /* These values are computed during compression or decompression startup: */
+        /* Component's size in DCT blocks.
+         * Any dummy blocks added to complete an MCU are not counted; therefore
+         * these values do not depend on whether a scan is interleaved or not.
+         */
+        int width_in_blocks;
+        int height_in_blocks;
+        /* Size of a DCT block in samples.  Always DCTSIZE for compression.
+         * For decompression this is the size of the output from one DCT block,
+         * reflecting any scaling we choose to apply during the IDCT step.
+         * Values of 1,2,4,8 are likely to be supported.    Note that different
+         * components may receive different IDCT scalings.
+         */
+        int DCT_scaled_size;
+        /* The downsampled dimensions are the component's actual, unpadded number
+         * of samples at the main buffer (preprocessing/compression interface), thus
+         * downsampled_width = ceil(image_width * Hi/Hmax)
+         * and similarly for height.    For decompression, IDCT scaling is included, so
+         * downsampled_width = ceil(image_width * Hi/Hmax * DCT_scaled_size/DCTSIZE)
+         */
+        int downsampled_width;   /* actual width in samples */
+        int downsampled_height; /* actual height in samples */
+        /* This flag is used only for decompression.    In cases where some of the
+         * components will be ignored (eg grayscale output from YCbCr image),
+         * we can skip most computations for the unused components.
+         */
+        bool component_needed;  /* do we need the value of this component? */
+
+        /* These values are computed before starting a scan of the component. */
+        /* The decompressor output side may not use these variables. */
+        int MCU_width;      /* number of blocks per MCU, horizontally */
+        int MCU_height;     /* number of blocks per MCU, vertically */
+        int MCU_blocks;     /* MCU_width * MCU_height */
+        int MCU_sample_width;       /* MCU width in samples, MCU_width*DCT_scaled_size */
+        int last_col_width;     /* # of non-dummy blocks across in last MCU */
+        int last_row_height;        /* # of non-dummy blocks down in last MCU */
+
+        /* Saved quantization table for component; null if none yet saved.
+         * See jdinput.c comments about the need for this information.
+         * This field is currently used only for decompression.
+         */
+        JQUANT_TBL quant_table;
+
+        /* Private per-component storage for DCT or IDCT subsystem. */
+        int[] dct_table;
+    }
+
+    static final class jpeg_color_quantizer {
+//      JMETHOD(void, start_pass, (j_decompress_ptr cinfo, bool is_pre_scan));
+//      JMETHOD(void, color_quantize, (j_decompress_ptr cinfo,
+//                   JSAMPARRAY input_buf, JSAMPARRAY output_buf,
+//                   int num_rows));
+//      JMETHOD(void, finish_pass, (j_decompress_ptr cinfo));
+//      JMETHOD(void, new_color_map, (j_decompress_ptr cinfo));
+
+        /* Initially allocated colormap is saved here */
+        int[][] sv_colormap;    /* The color map as a 2-D pixel array */
+        int sv_actual;      /* number of entries in use */
+
+        int[][] colorindex; /* Precomputed mapping for speed */
+        /* colorindex[i][j] = index of color closest to pixel value j in component i,
+         * premultiplied as described above.    Since colormap indexes must fit into
+         * JSAMPLEs, the entries of this array will too.
+         */
+        bool is_padded;     /* is the colorindex padded for odither? */
+
+        int[MAX_Q_COMPS] Ncolors;// = new int [MAX_Q_COMPS];    /* # of values alloced to each component */
+
+        /* Variables for ordered dithering */
+        int row_index;      /* cur row's vertical index in dither matrix */
+//          ODITHER_MATRIX_PTR odither[MAX_Q_COMPS]; /* one dither array per component */
+
+        /* Variables for Floyd-Steinberg dithering */
+//          FSERRPTR fserrors[MAX_Q_COMPS]; /* accumulated errors */
+        bool on_odd_row;
+
+        void start_pass (jpeg_decompress_struct cinfo, bool is_pre_scan) {
+            error();
+        }
+    }
+
+    static final class jpeg_upsampler {
+//      JMETHOD(void, start_pass, (j_decompress_ptr cinfo));
+//      JMETHOD(void, upsample, (j_decompress_ptr cinfo,
+//                   JSAMPIMAGE input_buf,
+//                   JDIMENSION *in_row_group_ctr,
+//                   JDIMENSION in_row_groups_avail,
+//                   JSAMPARRAY output_buf,
+//                   JDIMENSION *out_row_ctr,
+//                   JDIMENSION out_rows_avail));
+
+        bool need_context_rows; /* TRUE if need rows above & below */
+
+        /* Color conversion buffer. When using separate upsampling and color
+         * conversion steps, this buffer holds one upsampled row group until it
+         * has been color converted and output.
+         * Note: we do not allocate any storage for component(s) which are full-size,
+         * ie do not need rescaling.    The corresponding entry of color_buf[] is
+         * simply set to point to the input data array, thereby avoiding copying.
+         */
+        byte[][][MAX_COMPONENTS] color_buf;// = new byte[MAX_COMPONENTS][][];
+        int[MAX_COMPONENTS] color_buf_offset;// = new int[MAX_COMPONENTS];
+
+        /* Per-component upsampling method pointers */
+        int[MAX_COMPONENTS] methods;// = new int[MAX_COMPONENTS];
+
+        int next_row_out;       /* counts rows emitted from color_buf */
+        int rows_to_go; /* counts rows remaining in image */
+
+        /* Height of an input row group for each component. */
+        int[MAX_COMPONENTS] rowgroup_height;// = new int[MAX_COMPONENTS];
+
+        /* These arrays save pixel expansion factors so that int_expand need not
+         * recompute them each time.    They are unused for other upsampling methods.
+         */
+        byte[MAX_COMPONENTS] h_expand;// = new byte[MAX_COMPONENTS];
+        byte[MAX_COMPONENTS] v_expand;// = new byte[MAX_COMPONENTS];
+
+        void start_pass (jpeg_decompress_struct cinfo) {
+            jpeg_upsampler upsample = cinfo.upsample;
+
+            /* Mark the conversion buffer empty */
+            upsample.next_row_out = cinfo.max_v_samp_factor;
+            /* Initialize total-height counter for detecting bottom of image */
+            upsample.rows_to_go = cinfo.output_height;
+        }
+
+    }
+
+    static final class jpeg_marker_reader {
+        /* Read a restart marker --- exported for use by entropy decoder only */
+//      jpeg_marker_parser_method read_restart_marker;
+
+        /* State of marker reader --- nominally internal, but applications
+         * supplying COM or APPn handlers might like to know the state.
+         */
+        bool saw_SOI;       /* found SOI? */
+        bool saw_SOF;       /* found SOF? */
+        int next_restart_num;       /* next restart number expected (0-7) */
+        int discarded_bytes;    /* # of bytes skipped looking for a marker */
+
+        /* Application-overridable marker processing methods */
+//      jpeg_marker_parser_method process_COM;
+//      jpeg_marker_parser_method process_APPn[16];
+
+        /* Limit on marker data length to save for each marker type */
+        int length_limit_COM;
+        int[16] length_limit_APPn;// = new int[16];
+
+        /* Status of COM/APPn marker saving */
+//      jpeg_marker_reader cur_marker;  /* null if not processing a marker */
+//      int bytes_read;     /* data bytes read so far in marker */
+        /* Note: cur_marker is not linked into marker_list until it's all read. */
+    }
+
+
+    static final class jpeg_d_main_controller {
+//      JMETHOD(void, start_pass, (j_decompress_ptr cinfo, J_BUF_MODE pass_mode));
+        int process_data;
+
+         /* Pointer to allocated workspace (M or M+2 row groups). */
+        byte[][][MAX_COMPONENTS] buffer;// = new byte[MAX_COMPONENTS][][];
+        int[MAX_COMPONENTS] buffer_offset;// = new int[MAX_COMPONENTS];
+
+        bool buffer_full;       /* Have we gotten an iMCU row from decoder? */
+        int[1] rowgroup_ctr;// = new int[1];    /* counts row groups output to postprocessor */
+
+        /* Remaining fields are only used in the context case. */
+
+        /* These are the master pointers to the funny-order pointer lists. */
+        byte[][][][2] xbuffer;// = new byte[2][][][];   /* pointers to weird pointer lists */
+        int[][2] xbuffer_offset;// = new int[2][];
+
+        int whichptr;           /* indicates which pointer set is now in use */
+        int context_state;      /* process_data state machine status */
+        int rowgroups_avail;    /* row groups available to postprocessor */
+        int iMCU_row_ctr;   /* counts iMCU rows to detect image top/bot */
+
+        void start_pass (jpeg_decompress_struct cinfo, int pass_mode) {
+            jpeg_d_main_controller main = cinfo.main;
+
+            switch (pass_mode) {
+                case JBUF_PASS_THRU:
+                    if (cinfo.upsample.need_context_rows) {
+                        main.process_data = PROCESS_DATA_CONTEXT_MAIN;
+                        make_funny_pointers(cinfo); /* Create the xbuffer[] lists */
+                        main.whichptr = 0;  /* Read first iMCU row into xbuffer[0] */
+                        main.context_state = CTX_PREPARE_FOR_IMCU;
+                        main.iMCU_row_ctr = 0;
+                    } else {
+                        /* Simple case with no context needed */
+                        main.process_data = PROCESS_DATA_SIMPLE_MAIN;
+                    }
+                    main.buffer_full = false;   /* Mark buffer empty */
+                    main.rowgroup_ctr[0] = 0;
+                    break;
+//              #ifdef QUANT_2PASS_SUPPORTED
+//              case JBUF_CRANK_DEST:
+//                  /* For last pass of 2-pass quantization, just crank the postprocessor */
+//                  main.process_data = PROCESS_DATA_CRANK_POST;
+//                  break;
+//              #endif
+                default:
+                    error();
+//                  ERREXIT(cinfo, JERR_BAD_BUFFER_MODE);
+                    break;
+            }
+        }
+
+    }
+
+    static final class jpeg_decomp_master {
+//      JMETHOD(void, prepare_for_output_pass, (j_decompress_ptr cinfo));
+//      JMETHOD(void, finish_output_pass, (j_decompress_ptr cinfo));
+
+        /* State variables made visible to other modules */
+        bool is_dummy_pass;
+
+        int pass_number;        /* # of passes completed */
+
+        bool using_merged_upsample; /* true if using merged upsample/cconvert */
+
+        /* Saved references to initialized quantizer modules,
+         * in case we need to switch modes.
+         */
+        jpeg_color_quantizer quantizer_1pass;
+        jpeg_color_quantizer quantizer_2pass;
+    }
+
+    static final class jpeg_inverse_dct {
+//      JMETHOD(void, start_pass, (j_decompress_ptr cinfo));
+//      /* It is useful to allow each component to have a separate IDCT method. */
+//      inverse_DCT_method_ptr inverse_DCT[MAX_COMPONENTS];
+        int[MAX_COMPONENTS] cur_method;// = new int[MAX_COMPONENTS];
+
+        void start_pass (jpeg_decompress_struct cinfo) {
+            jpeg_inverse_dct idct = cinfo.idct;
+            int ci, i;
+            jpeg_component_info compptr;
+            int method = 0;
+//          inverse_DCT_method_ptr method_ptr = NULL;
+            JQUANT_TBL qtbl;
+
+            for (ci = 0; ci < cinfo.num_components; ci++) {
+                compptr = cinfo.comp_info[ci];
+                /* Select the proper IDCT routine for this component's scaling */
+                switch (compptr.DCT_scaled_size) {
+//                  #ifdef IDCT_SCALING_SUPPORTED
+//                  case 1:
+//                      method_ptr = jpeg_idct_1x1;
+//                      method = JDCT_ISLOW;    /* jidctred uses islow-style table */
+//                      break;
+//                  case 2:
+//                      method_ptr = jpeg_idct_2x2;
+//                      method = JDCT_ISLOW;    /* jidctred uses islow-style table */
+//                      break;
+//                  case 4:
+//                      method_ptr = jpeg_idct_4x4;
+//                      method = JDCT_ISLOW;    /* jidctred uses islow-style table */
+//                      break;
+//                  #endif
+                    case DCTSIZE:
+                        switch (cinfo.dct_method) {
+//                          #ifdef DCT_ISLOW_SUPPORTED
+                            case JDCT_ISLOW:
+//                              method_ptr = jpeg_idct_islow;
+                                method = JDCT_ISLOW;
+                                break;
+//                          #endif
+//                          #ifdef DCT_IFAST_SUPPORTED
+//                          case JDCT_IFAST:
+//                              method_ptr = jpeg_idct_ifast;
+//                              method = JDCT_IFAST;
+//                              break;
+//                          #endif
+//                          #ifdef DCT_FLOAT_SUPPORTED
+//                          case JDCT_FLOAT:
+//                              method_ptr = jpeg_idct_float;
+//                              method = JDCT_FLOAT;
+//                              break;
+//                          #endif
+                            default:
+                                error();
+//                              ERREXIT(cinfo, JERR_NOT_COMPILED);
+                                break;
+                        }
+                        break;
+                    default:
+                        error();
+//                      ERREXIT1(cinfo, JERR_BAD_DCTSIZE, compptr.DCT_scaled_size);
+                        break;
+                    }
+//                  idct.inverse_DCT[ci] = method_ptr;
+                    /* Create multiplier table from quant table.
+                     * However, we can skip this if the component is uninteresting
+                     * or if we already built the table.    Also, if no quant table
+                     * has yet been saved for the component, we leave the
+                     * multiplier table all-zero; we'll be reading zeroes from the
+                     * coefficient controller's buffer anyway.
+                     */
+                    if (! compptr.component_needed || idct.cur_method[ci] is method)
+                        continue;
+                    qtbl = compptr.quant_table;
+                    if (qtbl is null)       /* happens if no data yet for component */
+                        continue;
+                    idct.cur_method[ci] = method;
+                    switch (method) {
+//                      #ifdef PROVIDE_ISLOW_TABLES
+                        case JDCT_ISLOW:
+                        {
+                            /* For LL&M IDCT method, multipliers are equal to raw quantization
+                             * coefficients, but are stored as ints to ensure access efficiency.
+                             */
+                            int[] ismtbl = compptr.dct_table;
+                            for (i = 0; i < DCTSIZE2; i++) {
+                                ismtbl[i] = qtbl.quantval[i];
+                            }
+                        }
+                        break;
+//                      #endif
+//                      #ifdef DCT_IFAST_SUPPORTED
+//                      case JDCT_IFAST:
+//                      {
+//                          /* For AA&N IDCT method, multipliers are equal to quantization
+//                           * coefficients scaled by scalefactor[row]*scalefactor[col], where
+//                           *   scalefactor[0] = 1
+//                           *   scalefactor[k] = cos(k*PI/16) * sqrt(2)        for k=1..7
+//                           * For integer operation, the multiplier table is to be scaled by
+//                           * IFAST_SCALE_BITS.
+//                           */
+//                          int[] ifmtbl = compptr.dct_table;
+//                          short aanscales[] = {
+//                              /* precomputed values scaled up by 14 bits */
+//                              16384, 22725, 21407, 19266, 16384, 12873,   8867,   4520,
+//                              22725, 31521, 29692, 26722, 22725, 17855, 12299,    6270,
+//                              21407, 29692, 27969, 25172, 21407, 16819, 11585,    5906,
+//                              19266, 26722, 25172, 22654, 19266, 15137, 10426,    5315,
+//                              16384, 22725, 21407, 19266, 16384, 12873,   8867,   4520,
+//                              12873, 17855, 16819, 15137, 12873, 10114,   6967,   3552,
+//                              8867, 12299, 11585, 10426,  8867,   6967,   4799,   2446,
+//                              4520,   6270,   5906,   5315,   4520,   3552,   2446,   1247
+//                          };
+//                          SHIFT_TEMPS
+//
+//                          for (i = 0; i < DCTSIZE2; i++) {
+//                              ifmtbl[i] = DESCALE(MULTIPLY16V16( qtbl.quantval[i], aanscales[i]), CONST_BITS-IFAST_SCALE_BITS);
+//                          }
+//                      }
+//                      break;
+//                      #endif
+//                      #ifdef DCT_FLOAT_SUPPORTED
+//                      case JDCT_FLOAT:
+//                      {
+//                          /* For float AA&N IDCT method, multipliers are equal to quantization
+//                           * coefficients scaled by scalefactor[row]*scalefactor[col], where
+//                           *   scalefactor[0] = 1
+//                           *   scalefactor[k] = cos(k*PI/16) * sqrt(2)        for k=1..7
+//                           */
+//                          FLOAT_MULT_TYPE * fmtbl = (FLOAT_MULT_TYPE *) compptr.dct_table;
+//                          int row, col;
+//                          static const double aanscalefactor[DCTSIZE] = {
+//                              1.0, 1.387039845, 1.306562965, 1.175875602,
+//                              1.0, 0.785694958, 0.541196100, 0.275899379
+//                          };
+//
+//                          i = 0;
+//                          for (row = 0; row < DCTSIZE; row++) {
+//                              for (col = 0; col < DCTSIZE; col++) {
+//                                  fmtbl[i] = (FLOAT_MULT_TYPE)
+//                                      ((double) qtbl.quantval[i] *
+//                                   aanscalefactor[row] * aanscalefactor[col]);
+//                                  i++;
+//                              }
+//                          }
+//                      }
+//                      break;
+//                      #endif
+                    default:
+                        error();
+//                      ERREXIT(cinfo, JERR_NOT_COMPILED);
+                        break;
+                }
+            }
+        }
+    }
+
+    static final class jpeg_input_controller {
+        int consume_input;
+        bool has_multiple_scans;    /* True if file has multiple scans */
+        bool eoi_reached;
+
+        bool inheaders;     /* true until first SOS is reached */
+    }
+
+    static final class  jpeg_color_deconverter {
+//      JMETHOD(void, start_pass, (j_decompress_ptr cinfo));
+        int color_convert;
+
+        /* Private state for YCC.RGB conversion */
+        int[] Cr_r_tab;     /* => table for Cr to R conversion */
+        int[] Cb_b_tab;     /* => table for Cb to B conversion */
+        int[] Cr_g_tab;     /* => table for Cr to G conversion */
+        int[] Cb_g_tab;     /* => table for Cb to G conversion */
+
+        void start_pass (jpeg_decompress_struct cinfo) {
+            /* no work needed */
+        }
+
+    }
+
+    static final class jpeg_d_post_controller {
+//      JMETHOD(void, start_pass, (j_decompress_ptr cinfo, J_BUF_MODE pass_mode));
+        int post_process_data;
+
+        /* Color quantization source buffer: this holds output data from
+         * the upsample/color conversion step to be passed to the quantizer.
+         * For two-pass color quantization, we need a full-image buffer;
+         * for one-pass operation, a strip buffer is sufficient.
+         */
+        int[] whole_image;  /* virtual array, or NULL if one-pass */
+        int[][] buffer;     /* strip buffer, or current strip of virtual */
+        int strip_height;   /* buffer size in rows */
+        /* for two-pass mode only: */
+        int starting_row;   /* row # of first row in current strip */
+        int next_row;       /* index of next row to fill/empty in strip */
+
+        void start_pass (jpeg_decompress_struct cinfo, int pass_mode) {
+            jpeg_d_post_controller post = cinfo.post;
+
+            switch (pass_mode) {
+                case JBUF_PASS_THRU:
+                    if (cinfo.quantize_colors) {
+                        error(DWT.ERROR_NOT_IMPLEMENTED);
+//                      /* Single-pass processing with color quantization. */
+//                      post.post_process_data = POST_PROCESS_1PASS;
+//                      /* We could be doing buffered-image output before starting a 2-pass
+//                       * color quantization; in that case, jinit_d_post_controller did not
+//                       * allocate a strip buffer. Use the virtual-array buffer as workspace.
+//                       */
+//                      if (post.buffer is null) {
+//                          post.buffer = (*cinfo.mem.access_virt_sarray)
+//                              ((j_common_ptr) cinfo, post.whole_image,
+//                              (JDIMENSION) 0, post.strip_height, TRUE);
+//                      }
+                    } else {
+                        /* For single-pass processing without color quantization,
+                         * I have no work to do; just call the upsampler directly.
+                         */
+                        post.post_process_data = POST_PROCESS_DATA_UPSAMPLE;
+                    }
+                    break;
+//              #ifdef QUANT_2PASS_SUPPORTED
+//              case JBUF_SAVE_AND_PASS:
+//                  /* First pass of 2-pass quantization */
+//                  if (post.whole_image is NULL)
+//                      ERREXIT(cinfo, JERR_BAD_BUFFER_MODE);
+//                  post.pub.post_process_data = post_process_prepass;
+//                  break;
+//              case JBUF_CRANK_DEST:
+//                  /* Second pass of 2-pass quantization */
+//                  if (post.whole_image is NULL)
+//                      ERREXIT(cinfo, JERR_BAD_BUFFER_MODE);
+//                  post.pub.post_process_data = post_process_2pass;
+//                  break;
+//              #endif /* QUANT_2PASS_SUPPORTED */
+                    default:
+                        error();
+//                      ERREXIT(cinfo, JERR_BAD_BUFFER_MODE);
+                        break;
+            }
+            post.starting_row = post.next_row = 0;
+        }
+
+    }
+
+    static final class jpeg_decompress_struct {
+//      jpeg_error_mgr * err;   /* Error handler module */\
+//      struct jpeg_memory_mgr * mem;   /* Memory manager module */\
+//      struct jpeg_progress_mgr * progress; /* Progress monitor, or null if none */\
+//      void * client_data;     /* Available for use by application */\
+        bool is_decompressor;   /* So common code can tell which is which */
+        int global_state;       /* For checking call sequence validity */
+
+//      /* Source of compressed data */
+//      struct jpeg_source_mgr * src;
+        InputStream inputStream;
+        byte[] buffer;
+        int bytes_in_buffer;
+        int bytes_offset;
+        bool start_of_file;
+
+        /* Basic description of image --- filled in by jpeg_read_header(). */
+        /* Application may inspect these values to decide how to process image. */
+
+        int image_width;    /* nominal image width (from SOF marker) */
+        int image_height;   /* nominal image height */
+        int num_components;     /* # of color components in JPEG image */
+        int jpeg_color_space; /* colorspace of JPEG image */
+
+        /* Decompression processing parameters --- these fields must be set before
+         * calling jpeg_start_decompress(). Note that jpeg_read_header() initializes
+         * them to default values.
+         */
+
+        int out_color_space; /* colorspace for output */
+
+        int scale_num, scale_denom; /* fraction by which to scale image */
+
+        double output_gamma;        /* image gamma wanted in output */
+
+        bool buffered_image;    /* true=multiple output passes */
+        bool raw_data_out;      /* true=downsampled data wanted */
+
+        int dct_method; /* IDCT algorithm selector */
+        bool do_fancy_upsampling;   /* true=apply fancy upsampling */
+        bool do_block_smoothing;    /* true=apply interblock smoothing */
+
+        bool quantize_colors;   /* true=colormapped output wanted */
+        /* the following are ignored if not quantize_colors: */
+        int dither_mode;    /* type of color dithering to use */
+        bool two_pass_quantize; /* true=use two-pass color quantization */
+        int desired_number_of_colors;   /* max # colors to use in created colormap */
+        /* these are significant only in buffered-image mode: */
+        bool enable_1pass_quant;    /* enable future use of 1-pass quantizer */
+        bool enable_external_quant;/* enable future use of external colormap */
+        bool enable_2pass_quant;    /* enable future use of 2-pass quantizer */
+
+        /* Description of actual output image that will be returned to application.
+         * These fields are computed by jpeg_start_decompress().
+         * You can also use jpeg_calc_output_dimensions() to determine these values
+         * in advance of calling jpeg_start_decompress().
+         */
+
+        int output_width;   /* scaled image width */
+        int output_height;  /* scaled image height */
+        int out_color_components;   /* # of color components in out_color_space */
+        int output_components;  /* # of color components returned */
+        /* output_components is 1 (a colormap index) when quantizing colors;
+         * otherwise it equals out_color_components.
+         */
+        int rec_outbuf_height;  /* min recommended height of scanline buffer */
+        /* If the buffer passed to jpeg_read_scanlines() is less than this many rows
+         * high, space and time will be wasted due to unnecessary data copying.
+         * Usually rec_outbuf_height will be 1 or 2, at most 4.
+         */
+
+        /* When quantizing colors, the output colormap is described by these fields.
+         * The application can supply a colormap by setting colormap non-null before
+         * calling jpeg_start_decompress; otherwise a colormap is created during
+         * jpeg_start_decompress or jpeg_start_output.
+         * The map has out_color_components rows and actual_number_of_colors columns.
+         */
+        int actual_number_of_colors;    /* number of entries in use */
+        int[] colormap;     /* The color map as a 2-D pixel array */
+
+        /* State variables: these variables indicate the progress of decompression.
+         * The application may examine these but must not modify them.
+         */
+
+        /* Row index of next scanline to be read from jpeg_read_scanlines().
+         * Application may use this to control its processing loop, e.g.,
+         * "while (output_scanline < output_height)".
+         */
+        int output_scanline;    /* 0 .. output_height-1 */
+
+        /* Current input scan number and number of iMCU rows completed in scan.
+         * These indicate the progress of the decompressor input side.
+         */
+        int input_scan_number;  /* Number of SOS markers seen so far */
+        int input_iMCU_row; /* Number of iMCU rows completed */
+
+        /* The "output scan number" is the notional scan being displayed by the
+         * output side. The decompressor will not allow output scan/row number
+         * to get ahead of input scan/row, but it can fall arbitrarily far behind.
+         */
+        int output_scan_number; /* Nominal scan number being displayed */
+        int output_iMCU_row;    /* Number of iMCU rows read */
+
+        /* Current progression status.  coef_bits[c][i] indicates the precision
+         * with which component c's DCT coefficient i (in zigzag order) is known.
+         * It is -1 when no data has yet been received, otherwise it is the point
+         * transform (shift) value for the most recent scan of the coefficient
+         * (thus, 0 at completion of the progression).
+         * This pointer is null when reading a non-progressive file.
+         */
+        int[][] coef_bits;  /* -1 or current Al value for each coef */
+
+        /* Internal JPEG parameters --- the application usually need not look at
+         * these fields.    Note that the decompressor output side may not use
+         * any parameters that can change between scans.
+         */
+
+        /* Quantization and Huffman tables are carried forward across input
+         * datastreams when processing abbreviated JPEG datastreams.
+         */
+
+        JQUANT_TBL[NUM_QUANT_TBLS] quant_tbl_ptrs;// = new JQUANT_TBL[NUM_QUANT_TBLS];
+        /* ptrs to coefficient quantization tables, or null if not defined */
+
+        JHUFF_TBL[NUM_HUFF_TBLS] dc_huff_tbl_ptrs;// = new JHUFF_TBL[NUM_HUFF_TBLS];
+        JHUFF_TBL[NUM_HUFF_TBLS] ac_huff_tbl_ptrs;// = new JHUFF_TBL[NUM_HUFF_TBLS];
+        /* ptrs to Huffman coding tables, or null if not defined */
+
+        /* These parameters are never carried across datastreams, since they
+         * are given in SOF/SOS markers or defined to be reset by SOI.
+         */
+
+        int data_precision;     /* bits of precision in image data */
+
+        jpeg_component_info[] comp_info;
+        /* comp_info[i] describes component that appears i'th in SOF */
+
+        bool progressive_mode;  /* true if SOFn specifies progressive mode */
+        bool arith_code;        /* true=arithmetic coding, false=Huffman */
+
+        byte[NUM_ARITH_TBLS] arith_dc_L;// = new byte[NUM_ARITH_TBLS]; /* L values for DC arith-coding tables */
+        byte[NUM_ARITH_TBLS] arith_dc_U;// = new byte[NUM_ARITH_TBLS]; /* U values for DC arith-coding tables */
+        byte[NUM_ARITH_TBLS] arith_ac_K;// = new byte[NUM_ARITH_TBLS]; /* Kx values for AC arith-coding tables */
+
+        int restart_interval; /* MCUs per restart interval, or 0 for no restart */
+
+        /* These fields record data obtained from optional markers recognized by
+         * the JPEG library.
+         */
+        bool saw_JFIF_marker;   /* true iff a JFIF APP0 marker was found */
+        /* Data copied from JFIF marker; only valid if saw_JFIF_marker is true: */
+        byte JFIF_major_version;    /* JFIF version number */
+        byte JFIF_minor_version;
+        byte density_unit;      /* JFIF code for pixel size units */
+        short X_density;        /* Horizontal pixel density */
+        short Y_density;        /* Vertical pixel density */
+        bool saw_Adobe_marker;  /* true iff an Adobe APP14 marker was found */
+        byte Adobe_transform;   /* Color transform code from Adobe marker */
+
+        bool CCIR601_sampling;  /* true=first samples are cosited */
+
+        /* Aside from the specific data retained from APPn markers known to the
+         * library, the uninterpreted contents of any or all APPn and COM markers
+         * can be saved in a list for examination by the application.
+         */
+        jpeg_marker_reader marker_list; /* Head of list of saved markers */
+
+        /* Remaining fields are known throughout decompressor, but generally
+         * should not be touched by a surrounding application.
+         */
+
+        /*
+         * These fields are computed during decompression startup
+         */
+        int max_h_samp_factor;  /* largest h_samp_factor */
+        int max_v_samp_factor;  /* largest v_samp_factor */
+
+        int min_DCT_scaled_size;    /* smallest DCT_scaled_size of any component */
+
+        int total_iMCU_rows;    /* # of iMCU rows in image */
+        /* The coefficient controller's input and output progress is measured in
+         * units of "iMCU" (interleaved MCU) rows.  These are the same as MCU rows
+         * in fully interleaved JPEG scans, but are used whether the scan is
+         * interleaved or not.  We define an iMCU row as v_samp_factor DCT block
+         * rows of each component.  Therefore, the IDCT output contains
+         * v_samp_factor*DCT_scaled_size sample rows of a component per iMCU row.
+         */
+
+        byte[] sample_range_limit; /* table for fast range-limiting */
+        int sample_range_limit_offset;
+
+        /*
+         * These fields are valid during any one scan.
+         * They describe the components and MCUs actually appearing in the scan.
+         * Note that the decompressor output side must not use these fields.
+         */
+        int comps_in_scan;      /* # of JPEG components in this scan */
+        jpeg_component_info[MAX_COMPS_IN_SCAN] cur_comp_info;// = new jpeg_component_info[MAX_COMPS_IN_SCAN];
+        /* *cur_comp_info[i] describes component that appears i'th in SOS */
+
+        int MCUs_per_row;   /* # of MCUs across the image */
+        int MCU_rows_in_scan;   /* # of MCU rows in the image */
+
+        int blocks_in_MCU;      /* # of DCT blocks per MCU */
+        int[D_MAX_BLOCKS_IN_MCU] MCU_membership;// = new int[D_MAX_BLOCKS_IN_MCU];
+        /* MCU_membership[i] is index in cur_comp_info of component owning */
+        /* i'th block in an MCU */
+
+        int Ss, Se, Ah, Al;     /* progressive JPEG parameters for scan */
+
+        /* This field is shared between entropy decoder and marker parser.
+         * It is either zero or the code of a JPEG marker that has been
+         * read from the data source, but has not yet been processed.
+         */
+        int unread_marker;
+
+        int[DCTSIZE2] workspace;// = new int[DCTSIZE2];
+        int[1] row_ctr;// = new int[1];
+
+        /*
+         * Links to decompression subobjects (methods, private variables of modules)
+         */
+        jpeg_decomp_master master;
+        jpeg_d_main_controller main;
+        jpeg_d_coef_controller coef;
+        jpeg_d_post_controller post;
+        jpeg_input_controller inputctl;
+        jpeg_marker_reader marker;
+        jpeg_entropy_decoder entropy;
+        jpeg_inverse_dct idct;
+        jpeg_upsampler upsample;
+        jpeg_color_deconverter cconvert;
+        jpeg_color_quantizer cquantize;
+    }
+
+static void error() {
+    DWT.error(DWT.ERROR_INVALID_IMAGE);
+}
+
+static void error(int code) {
+    DWT.error(code);
+}
+
+static void error(char[] msg) {
+    DWT.error(DWT.ERROR_INVALID_IMAGE, null, msg);
+}
+
+static void jinit_marker_reader (jpeg_decompress_struct cinfo) {
+    jpeg_marker_reader marker = cinfo.marker = new jpeg_marker_reader();
+//  int i;
+
+    /* Initialize COM/APPn processing.
+     * By default, we examine and then discard APP0 and APP14,
+     * but simply discard COM and all other APPn.
+     */
+//  marker.process_COM = skip_variable;
+    marker.length_limit_COM = 0;
+//  for (i = 0; i < 16; i++) {
+//      marker.process_APPn[i] = skip_variable;
+//      marker.length_limit_APPn[i] = 0;
+//  }
+//  marker.process_APPn[0] = get_interesting_appn;
+//  marker.process_APPn[14] = get_interesting_appn;
+    /* Reset marker processing state */
+    reset_marker_reader(cinfo);
+}
+
+static void jinit_d_coef_controller (jpeg_decompress_struct cinfo, bool need_full_buffer) {
+    jpeg_d_coef_controller coef = new jpeg_d_coef_controller();
+    cinfo.coef = coef;
+//  coef.pub.start_input_pass = start_input_pass;
+//  coef.pub.start_output_pass = start_output_pass;
+    coef.coef_bits_latch = null;
+
+    /* Create the coefficient buffer. */
+    if (need_full_buffer) {
+//#ifdef D_MULTISCAN_FILES_SUPPORTED
+        /* Allocate a full-image virtual array for each component, */
+        /* padded to a multiple of samp_factor DCT blocks in each direction. */
+        /* Note we ask for a pre-zeroed array. */
+        int ci, access_rows;
+        jpeg_component_info compptr;
+
+        for (ci = 0; ci < cinfo.num_components; ci++) {
+            compptr = cinfo.comp_info[ci];
+            access_rows = compptr.v_samp_factor;
+//#ifdef BLOCK_SMOOTHING_SUPPORTED
+            /* If block smoothing could be used, need a bigger window */
+            if (cinfo.progressive_mode)
+                access_rows *= 3;
+//#endif
+            coef.whole_image[ci] =
+                new short[][][](
+                    cast(int)jround_up( compptr.height_in_blocks, compptr.v_samp_factor),
+                    cast(int)jround_up( compptr.width_in_blocks, compptr.h_samp_factor),
+                    DCTSIZE2
+                    );
+        }
+//      coef.consume_data = consume_data;
+        coef.decompress_data = DECOMPRESS_DATA;
+        coef.coef_arrays = coef.whole_image[0]; /* link to virtual arrays */
+//      #else
+//              ERREXIT(cinfo, JERR_NOT_COMPILED);
+//      #endif
+    } else {
+        /* We only need a single-MCU buffer. */
+        foreach( inout el; coef.MCU_buffer ){
+            el = new short[](DCTSIZE2);
+        }
+//      coef.consume_data = dummy_consume_data;
+        coef.decompress_data = DECOMPRESS_ONEPASS;
+        coef.coef_arrays = null; /* flag for no virtual arrays */
+    }
+}
+
+static void start_output_pass (jpeg_decompress_struct cinfo) {
+//#ifdef BLOCK_SMOOTHING_SUPPORTED
+    jpeg_d_coef_controller coef = cinfo.coef;
+
+    /* If multipass, check to see whether to use block smoothing on this pass */
+    if (coef.coef_arrays !is null) {
+        if (cinfo.do_block_smoothing && smoothing_ok(cinfo))
+            coef.decompress_data = DECOMPRESS_SMOOTH_DATA;
+        else
+            coef.decompress_data = DECOMPRESS_DATA;
+    }
+//#endif
+    cinfo.output_iMCU_row = 0;
+}
+
+static void jpeg_create_decompress(jpeg_decompress_struct cinfo) {
+    cinfo.is_decompressor = true;
+
+
+    /* Initialize marker processor so application can override methods
+     * for COM, APPn markers before calling jpeg_read_header.
+     */
+    cinfo.marker_list = null;
+    jinit_marker_reader(cinfo);
+
+    /* And initialize the overall input controller. */
+    jinit_input_controller(cinfo);
+
+    /* OK, I'm ready */
+    cinfo.global_state = DSTATE_START;
+}
+
+static void jpeg_calc_output_dimensions (jpeg_decompress_struct cinfo)
+/* Do computations that are needed before master selection phase */
+{
+//#ifdef IDCT_SCALING_SUPPORTED
+//  int ci;
+//  jpeg_component_info compptr;
+//#endif
+
+    /* Prevent application from calling me at wrong times */
+    if (cinfo.global_state !is DSTATE_READY)
+        error();
+//      ERREXIT1(cinfo, JERR_BAD_STATE, cinfo.global_state);
+
+//#ifdef IDCT_SCALING_SUPPORTED
+//
+//  /* Compute actual output image dimensions and DCT scaling choices. */
+//  if (cinfo.scale_num * 8 <= cinfo.scale_denom) {
+//      /* Provide 1/8 scaling */
+//      cinfo.output_width = cast(int)
+//          jdiv_round_up(cinfo.image_width, 8L);
+//      cinfo.output_height = cast(int)
+//          jdiv_round_up(cinfo.image_height, 8L);
+//      cinfo.min_DCT_scaled_size = 1;
+//  } else if (cinfo.scale_num * 4 <= cinfo.scale_denom) {
+//      /* Provide 1/4 scaling */
+//      cinfo.output_width = cast(int)
+//          jdiv_round_up(cinfo.image_width, 4L);
+//      cinfo.output_height = cast(int)
+//          jdiv_round_up(cinfo.image_height, 4L);
+//      cinfo.min_DCT_scaled_size = 2;
+//  } else if (cinfo.scale_num * 2 <= cinfo.scale_denom) {
+//      /* Provide 1/2 scaling */
+//      cinfo.output_width = cast(int)
+//          jdiv_round_up(cinfo.image_width, 2L);
+//      cinfo.output_height = cast(int)
+//          jdiv_round_up(cinfo.image_height, 2L);
+//      cinfo.min_DCT_scaled_size = 4;
+//  } else {
+//      /* Provide 1/1 scaling */
+//      cinfo.output_width = cinfo.image_width;
+//      cinfo.output_height = cinfo.image_height;
+//      cinfo.min_DCT_scaled_size = DCTSIZE;
+//  }
+//  /* In selecting the actual DCT scaling for each component, we try to
+//   * scale up the chroma components via IDCT scaling rather than upsampling.
+//   * This saves time if the upsampler gets to use 1:1 scaling.
+//   * Note this code assumes that the supported DCT scalings are powers of 2.
+//   */
+//  for (ci = 0; ci < cinfo.num_components; ci++) {
+//      compptr = cinfo.comp_info[ci];
+//      int ssize = cinfo.min_DCT_scaled_size;
+//      while (ssize < DCTSIZE &&
+//          (compptr.h_samp_factor * ssize * 2 <= cinfo.max_h_samp_factor * cinfo.min_DCT_scaled_size) &&
+//          (compptr.v_samp_factor * ssize * 2 <= cinfo.max_v_samp_factor * cinfo.min_DCT_scaled_size))
+//      {
+//          ssize = ssize * 2;
+//      }
+//      compptr.DCT_scaled_size = ssize;
+//  }
+//
+//  /* Recompute downsampled dimensions of components;
+//   * application needs to know these if using raw downsampled data.
+//   */
+//  for (ci = 0; ci < cinfo.num_components; ci++) {
+//      compptr = cinfo.comp_info[ci];
+//      /* Size in samples, after IDCT scaling */
+//      compptr.downsampled_width = cast(int)
+//          jdiv_round_up(cast(long) cinfo.image_width * cast(long) (compptr.h_samp_factor * compptr.DCT_scaled_size),
+//              (cinfo.max_h_samp_factor * DCTSIZE));
+//      compptr.downsampled_height = cast(int)
+//          jdiv_round_up(cast(long) cinfo.image_height * cast(long) (compptr.v_samp_factor * compptr.DCT_scaled_size),
+//              (cinfo.max_v_samp_factor * DCTSIZE));
+//  }
+//
+//#else /* !IDCT_SCALING_SUPPORTED */
+
+    /* Hardwire it to "no scaling" */
+    cinfo.output_width = cinfo.image_width;
+    cinfo.output_height = cinfo.image_height;
+    /* jdinput.c has already initialized DCT_scaled_size to DCTSIZE,
+     * and has computed unscaled downsampled_width and downsampled_height.
+     */
+
+//#endif /* IDCT_SCALING_SUPPORTED */
+
+    /* Report number of components in selected colorspace. */
+    /* Probably this should be in the color conversion module... */
+    switch (cinfo.out_color_space) {
+        case JCS_GRAYSCALE:
+            cinfo.out_color_components = 1;
+            break;
+        case JCS_RGB:
+            if (RGB_PIXELSIZE !is 3) {
+                cinfo.out_color_components = RGB_PIXELSIZE;
+                break;
+            }
+            //FALLTHROUGH
+        case JCS_YCbCr:
+            cinfo.out_color_components = 3;
+            break;
+        case JCS_CMYK:
+        case JCS_YCCK:
+            cinfo.out_color_components = 4;
+            break;
+        default:            /* else must be same colorspace as in file */
+            cinfo.out_color_components = cinfo.num_components;
+            break;
+    }
+    cinfo.output_components = (cinfo.quantize_colors ? 1 : cinfo.out_color_components);
+
+    /* See if upsampler will want to emit more than one row at a time */
+    if (use_merged_upsample(cinfo))
+        cinfo.rec_outbuf_height = cinfo.max_v_samp_factor;
+    else
+        cinfo.rec_outbuf_height = 1;
+}
+
+static bool use_merged_upsample (jpeg_decompress_struct cinfo) {
+//#ifdef UPSAMPLE_MERGING_SUPPORTED
+    /* Merging is the equivalent of plain box-filter upsampling */
+    if (cinfo.do_fancy_upsampling || cinfo.CCIR601_sampling)
+        return false;
+    /* jdmerge.c only supports YCC=>RGB color conversion */
+    if (cinfo.jpeg_color_space !is JCS_YCbCr || cinfo.num_components !is 3 ||
+            cinfo.out_color_space !is JCS_RGB ||
+            cinfo.out_color_components !is RGB_PIXELSIZE)
+        return false;
+    /* and it only handles 2h1v or 2h2v sampling ratios */
+    if (cinfo.comp_info[0].h_samp_factor !is 2 ||
+            cinfo.comp_info[1].h_samp_factor !is 1 ||
+            cinfo.comp_info[2].h_samp_factor !is 1 ||
+            cinfo.comp_info[0].v_samp_factor >  2 ||
+            cinfo.comp_info[1].v_samp_factor !is 1 ||
+            cinfo.comp_info[2].v_samp_factor !is 1)
+        return false;
+    /* furthermore, it doesn't work if we've scaled the IDCTs differently */
+    if (cinfo.comp_info[0].DCT_scaled_size !is cinfo.min_DCT_scaled_size ||
+            cinfo.comp_info[1].DCT_scaled_size !is cinfo.min_DCT_scaled_size ||
+            cinfo.comp_info[2].DCT_scaled_size !is cinfo.min_DCT_scaled_size)
+        return false;
+    /* ??? also need to test for upsample-time rescaling, when & if supported */
+    return true;            /* by golly, it'll work... */
+//#else
+//  return false;
+//#endif
+}
+
+static void prepare_range_limit_table (jpeg_decompress_struct cinfo)
+/* Allocate and fill in the sample_range_limit table */
+{
+    byte[] table;
+    int i;
+
+    table = new byte[5 * (MAXJSAMPLE+1) + CENTERJSAMPLE];
+    int offset = (MAXJSAMPLE+1);    /* allow negative subscripts of simple table */
+    cinfo.sample_range_limit_offset = offset;
+    cinfo.sample_range_limit = table;
+    /* First segment of "simple" table: limit[x] = 0 for x < 0 */
+    /* Main part of "simple" table: limit[x] = x */
+    for (i = 0; i <= MAXJSAMPLE; i++)
+        table[i + offset] = cast(byte)i;
+    offset += CENTERJSAMPLE;    /* Point to where post-IDCT table starts */
+    /* End of simple table, rest of first half of post-IDCT table */
+    for (i = CENTERJSAMPLE; i < 2*(MAXJSAMPLE+1); i++)
+        table[i+offset] = cast(byte)MAXJSAMPLE;
+    /* Second half of post-IDCT table */
+    System.arraycopy(cinfo.sample_range_limit, cinfo.sample_range_limit_offset, table, offset + (4 * (MAXJSAMPLE+1) - CENTERJSAMPLE), CENTERJSAMPLE);
+}
+
+static void build_ycc_rgb_table (jpeg_decompress_struct cinfo) {
+    jpeg_color_deconverter cconvert = cinfo.cconvert;
+    int i;
+    int x;
+//  SHIFT_TEMPS
+
+    cconvert.Cr_r_tab = new int[MAXJSAMPLE+1];
+    cconvert.Cb_b_tab = new int[MAXJSAMPLE+1];
+    cconvert.Cr_g_tab = new int[MAXJSAMPLE+1];
+    cconvert.Cb_g_tab = new int[MAXJSAMPLE+1];
+
+    for (i = 0, x = -CENTERJSAMPLE; i <= MAXJSAMPLE; i++, x++) {
+        /* i is the actual input pixel value, in the range 0..MAXJSAMPLE */
+        /* The Cb or Cr value we are thinking of is x = i - CENTERJSAMPLE */
+        /* Cr=>R value is nearest int to 1.40200 * x */
+        cconvert.Cr_r_tab[i] = (cast(int)(1.40200f * (1<<SCALEBITS) + 0.5f) * x + ONE_HALF) >> SCALEBITS;
+        /* Cb=>B value is nearest int to 1.77200 * x */
+        cconvert.Cb_b_tab[i] = (cast(int)(1.77200f * (1<<SCALEBITS) + 0.5f) * x + ONE_HALF) >> SCALEBITS;
+        /* Cr=>G value is scaled-up -0.71414 * x */
+        cconvert.Cr_g_tab[i] = (cast(int)(- (0.71414f * (1<<SCALEBITS) + 0.5f)) * x);
+        /* Cb=>G value is scaled-up -0.34414 * x */
+        /* We also add in ONE_HALF so that need not do it in inner loop */
+        cconvert.Cb_g_tab[i] = (cast(int)(- (0.34414f* (1<<SCALEBITS) + 0.5f)) * x + ONE_HALF);
+    }
+}
+
+static void jinit_color_deconverter (jpeg_decompress_struct cinfo) {
+    jpeg_color_deconverter cconvert = cinfo.cconvert = new jpeg_color_deconverter();
+//  cconvert.start_pass = start_pass_dcolor;
+
+    /* Make sure num_components agrees with jpeg_color_space */
+    switch (cinfo.jpeg_color_space) {
+        case JCS_GRAYSCALE:
+            if (cinfo.num_components !is 1)
+                error();
+//              ERREXIT(cinfo, JERR_BAD_J_COLORSPACE);
+            break;
+
+        case JCS_RGB:
+        case JCS_YCbCr:
+            if (cinfo.num_components !is 3)
+                error();
+//              ERREXIT(cinfo, JERR_BAD_J_COLORSPACE);
+            break;
+
+        case JCS_CMYK:
+        case JCS_YCCK:
+            if (cinfo.num_components !is 4)
+                error();
+//              ERREXIT(cinfo, JERR_BAD_J_COLORSPACE);
+            break;
+
+        default:            /* JCS_UNKNOWN can be anything */
+            if (cinfo.num_components < 1)
+                error();
+//              ERREXIT(cinfo, JERR_BAD_J_COLORSPACE);
+            break;
+    }
+
+    /* Set out_color_components and conversion method based on requested space.
+     * Also clear the component_needed flags for any unused components,
+     * so that earlier pipeline stages can avoid useless computation.
+     */
+
+    int ci;
+    switch (cinfo.out_color_space) {
+        case JCS_GRAYSCALE:
+            cinfo.out_color_components = 1;
+            if (cinfo.jpeg_color_space is JCS_GRAYSCALE || cinfo.jpeg_color_space is JCS_YCbCr) {
+                cconvert.color_convert = GRAYSCALE_CONVERT;
+                /* For color.grayscale conversion, only the Y (0) component is needed */
+                for (ci = 1; ci < cinfo.num_components; ci++)
+                    cinfo.comp_info[ci].component_needed = false;
+            } else
+                error();
+//              ERREXIT(cinfo, JERR_CONVERSION_NOTIMPL);
+            break;
+
+        case JCS_RGB:
+            cinfo.out_color_components = RGB_PIXELSIZE;
+            if (cinfo.jpeg_color_space is JCS_YCbCr) {
+                cconvert.color_convert = YCC_RGB_CONVERT;
+                build_ycc_rgb_table(cinfo);
+            } else if (cinfo.jpeg_color_space is JCS_GRAYSCALE) {
+                cconvert.color_convert = GRAY_RGB_CONVERT;
+            } else if (cinfo.jpeg_color_space is JCS_RGB && RGB_PIXELSIZE is 3) {
+                cconvert.color_convert = NULL_CONVERT;
+            } else
+                error();
+//              ERREXIT(cinfo, JERR_CONVERSION_NOTIMPL);
+                break;
+
+        case JCS_CMYK:
+            cinfo.out_color_components = 4;
+            if (cinfo.jpeg_color_space is JCS_YCCK) {
+                cconvert.color_convert = YCCK_CMYK_CONVERT;
+                build_ycc_rgb_table(cinfo);
+            } else if (cinfo.jpeg_color_space is JCS_CMYK) {
+                cconvert.color_convert = NULL_CONVERT;
+            } else
+                error();
+//              ERREXIT(cinfo, JERR_CONVERSION_NOTIMPL);
+            break;
+
+        default:
+            /* Permit null conversion to same output space */
+            if (cinfo.out_color_space is cinfo.jpeg_color_space) {
+                cinfo.out_color_components = cinfo.num_components;
+                cconvert.color_convert = NULL_CONVERT;
+            } else  /* unsupported non-null conversion */
+                error();
+//              ERREXIT(cinfo, JERR_CONVERSION_NOTIMPL);
+            break;
+    }
+
+    if (cinfo.quantize_colors)
+        cinfo.output_components = 1; /* single colormapped output component */
+    else
+        cinfo.output_components = cinfo.out_color_components;
+}
+
+static void jinit_d_post_controller (jpeg_decompress_struct cinfo, bool need_full_buffer) {
+    jpeg_d_post_controller post = cinfo.post = new jpeg_d_post_controller();
+//  post.pub.start_pass = start_pass_dpost;
+    post.whole_image = null;    /* flag for no virtual arrays */
+    post.buffer = null;     /* flag for no strip buffer */
+
+    /* Create the quantization buffer, if needed */
+    if (cinfo.quantize_colors) {
+        error(DWT.ERROR_NOT_IMPLEMENTED);
+//      /* The buffer strip height is max_v_samp_factor, which is typically
+//       * an efficient number of rows for upsampling to return.
+//       * (In the presence of output rescaling, we might want to be smarter?)
+//       */
+//      post.strip_height = cinfo.max_v_samp_factor;
+//      if (need_full_buffer) {
+//          /* Two-pass color quantization: need full-image storage. */
+//          /* We round up the number of rows to a multiple of the strip height. */
+//#ifdef QUANT_2PASS_SUPPORTED
+//          post.whole_image = (*cinfo.mem.request_virt_sarray)
+//              ((j_common_ptr) cinfo, JPOOL_IMAGE, FALSE,
+//              cinfo.output_width * cinfo.out_color_components,
+//              (JDIMENSION) jround_up(cast(long) cinfo.output_height,
+//              cast(long) post.strip_height),
+//   post.strip_height);
+//#else
+//          ERREXIT(cinfo, JERR_BAD_BUFFER_MODE);
+//#endif /* QUANT_2PASS_SUPPORTED */
+//      } else {
+//          /* One-pass color quantization: just make a strip buffer. */
+//          post.buffer = (*cinfo.mem.alloc_sarray)
+//              ((j_common_ptr) cinfo, JPOOL_IMAGE,
+//              cinfo.output_width * cinfo.out_color_components,
+//              post.strip_height);
+//      }
+    }
+}
+
+static void make_funny_pointers (jpeg_decompress_struct cinfo)
+/* Create the funny pointer lists discussed in the comments above.
+ * The actual workspace is already allocated (in main.buffer),
+ * and the space for the pointer lists is allocated too.
+ * This routine just fills in the curiously ordered lists.
+ * This will be repeated at the beginning of each pass.
+ */
+{
+    jpeg_d_main_controller main = cinfo.main;
+    int ci, i, rgroup;
+    int M = cinfo.min_DCT_scaled_size;
+    jpeg_component_info compptr;
+    byte[][] buf, xbuf0, xbuf1;
+
+    for (ci = 0; ci < cinfo.num_components; ci++) {
+        compptr = cinfo.comp_info[ci];
+        rgroup = (compptr.v_samp_factor * compptr.DCT_scaled_size) /
+            cinfo.min_DCT_scaled_size; /* height of a row group of component */
+        xbuf0 = main.xbuffer[0][ci];
+        int xbuf0_offset = main.xbuffer_offset[0][ci];
+        xbuf1 = main.xbuffer[1][ci];
+        int xbuf1_offset = main.xbuffer_offset[1][ci];
+        /* First copy the workspace pointers as-is */
+        buf = main.buffer[ci];
+        for (i = 0; i < rgroup * (M + 2); i++) {
+            xbuf0[i + xbuf0_offset] = xbuf1[i + xbuf1_offset] = buf[i];
+        }
+        /* In the second list, put the last four row groups in swapped order */
+        for (i = 0; i < rgroup * 2; i++) {
+            xbuf1[rgroup*(M-2) + i + xbuf1_offset] = buf[rgroup*M + i];
+            xbuf1[rgroup*M + i + xbuf1_offset] = buf[rgroup*(M-2) + i];
+        }
+        /* The wraparound pointers at top and bottom will be filled later
+         * (see set_wraparound_pointers, below).    Initially we want the "above"
+         * pointers to duplicate the first actual data line.    This only needs
+         * to happen in xbuffer[0].
+         */
+        for (i = 0; i < rgroup; i++) {
+            xbuf0[i - rgroup + xbuf0_offset] = xbuf0[0 + xbuf0_offset];
+        }
+    }
+}
+
+static void alloc_funny_pointers (jpeg_decompress_struct cinfo)
+/* Allocate space for the funny pointer lists.
+ * This is done only once, not once per pass.
+ */
+{
+    jpeg_d_main_controller main = cinfo.main;
+    int ci, rgroup;
+    int M = cinfo.min_DCT_scaled_size;
+    jpeg_component_info compptr;
+    byte[][] xbuf;
+
+    /* Get top-level space for component array pointers.
+     * We alloc both arrays with one call to save a few cycles.
+     */
+    main.xbuffer[0] = new byte[][][](cinfo.num_components);
+    main.xbuffer[1] = new byte[][][](cinfo.num_components);
+    main.xbuffer_offset[0] = new int[](cinfo.num_components);
+    main.xbuffer_offset[1] = new int[](cinfo.num_components);
+
+    for (ci = 0; ci < cinfo.num_components; ci++) {
+        compptr = cinfo.comp_info[ci];
+        rgroup = (compptr.v_samp_factor * compptr.DCT_scaled_size) / cinfo.min_DCT_scaled_size; /* height of a row group of component */
+        /* Get space for pointer lists --- M+4 row groups in each list.
+         * We alloc both pointer lists with one call to save a few cycles.
+         */
+        xbuf = new byte[][](2 * (rgroup * (M + 4)));
+        int offset = rgroup;
+        main.xbuffer_offset[0][ci] = offset;
+        main.xbuffer[0][ci] = xbuf;
+        offset += rgroup * (M + 4);
+        main.xbuffer_offset[1][ci] = offset;
+        main.xbuffer[1][ci] = xbuf;
+    }
+}
+
+
+static void jinit_d_main_controller (jpeg_decompress_struct cinfo, bool need_full_buffer) {
+    int ci, rgroup, ngroups;
+    jpeg_component_info compptr;
+
+    jpeg_d_main_controller main = cinfo.main = new jpeg_d_main_controller();
+//  main.pub.start_pass = start_pass_main;
+
+    if (need_full_buffer)       /* shouldn't happen */
+        error();
+//      ERREXIT(cinfo, JERR_BAD_BUFFER_MODE);
+
+    /* Allocate the workspace.
+     * ngroups is the number of row groups we need.
+     */
+    if (cinfo.upsample.need_context_rows) {
+        if (cinfo.min_DCT_scaled_size < 2) /* unsupported, see comments above */
+            error();
+//          ERREXIT(cinfo, JERR_NOTIMPL);
+        alloc_funny_pointers(cinfo); /* Alloc space for xbuffer[] lists */
+        ngroups = cinfo.min_DCT_scaled_size + 2;
+    } else {
+        ngroups = cinfo.min_DCT_scaled_size;
+    }
+
+    for (ci = 0; ci < cinfo.num_components; ci++) {
+        compptr = cinfo.comp_info[ci];
+        rgroup = (compptr.v_samp_factor * compptr.DCT_scaled_size) / cinfo.min_DCT_scaled_size; /* height of a row group of component */
+        main.buffer[ci] = new byte[][]( rgroup * ngroups, compptr.width_in_blocks * compptr.DCT_scaled_size );
+    }
+}
+
+static long jround_up (long a, long b)
+/* Compute a rounded up to next multiple of b, ie, ceil(a/b)*b */
+/* Assumes a >= 0, b > 0 */
+{
+    a += b - 1L;
+    return a - (a % b);
+}
+
+static void jinit_upsampler (jpeg_decompress_struct cinfo) {
+    int ci;
+    jpeg_component_info compptr;
+    bool need_buffer, do_fancy;
+    int h_in_group, v_in_group, h_out_group, v_out_group;
+
+    jpeg_upsampler upsample = new jpeg_upsampler();
+    cinfo.upsample = upsample;
+//  upsample.start_pass = start_pass_upsample;
+//  upsample.upsample = sep_upsample;
+    upsample.need_context_rows = false; /* until we find out differently */
+
+    if (cinfo.CCIR601_sampling) /* this isn't supported */
+        error();
+//      ERREXIT(cinfo, JERR_CCIR601_NOTIMPL);
+
+    /* jdmainct.c doesn't support context rows when min_DCT_scaled_size = 1,
+     * so don't ask for it.
+     */
+    do_fancy = cinfo.do_fancy_upsampling && cinfo.min_DCT_scaled_size > 1;
+
+    /* Verify we can handle the sampling factors, select per-component methods,
+     * and create storage as needed.
+     */
+    for (ci = 0; ci < cinfo.num_components; ci++) {
+        compptr = cinfo.comp_info[ci];
+        /* Compute size of an "input group" after IDCT scaling. This many samples
+         * are to be converted to max_h_samp_factor * max_v_samp_factor pixels.
+         */
+        h_in_group = (compptr.h_samp_factor * compptr.DCT_scaled_size) /
+         cinfo.min_DCT_scaled_size;
+        v_in_group = (compptr.v_samp_factor * compptr.DCT_scaled_size) /
+         cinfo.min_DCT_scaled_size;
+        h_out_group = cinfo.max_h_samp_factor;
+        v_out_group = cinfo.max_v_samp_factor;
+        upsample.rowgroup_height[ci] = v_in_group; /* save for use later */
+        need_buffer = true;
+        if (! compptr.component_needed) {
+            /* Don't bother to upsample an uninteresting component. */
+            upsample.methods[ci] = NOOP_UPSAMPLE;
+            need_buffer = false;
+        } else if (h_in_group is h_out_group && v_in_group is v_out_group) {
+            /* Fullsize components can be processed without any work. */
+            upsample.methods[ci] = FULLSIZE_UPSAMPLE;
+            need_buffer = false;
+        } else if (h_in_group * 2 is h_out_group && v_in_group is v_out_group) {
+            /* Special cases for 2h1v upsampling */
+            if (do_fancy && compptr.downsampled_width > 2)
+                upsample.methods[ci] = H2V1_FANCY_UPSAMPLE;
+            else
+                upsample.methods[ci] = H2V1_UPSAMPLE;
+        } else if (h_in_group * 2 is h_out_group && v_in_group * 2 is v_out_group) {
+            /* Special cases for 2h2v upsampling */
+            if (do_fancy && compptr.downsampled_width > 2) {
+                upsample.methods[ci] = H2V2_FANCY_UPSAMPLE;
+                upsample.need_context_rows = true;
+            } else
+                upsample.methods[ci] = H2V2_UPSAMPLE;
+        } else if ((h_out_group % h_in_group) is 0 && (v_out_group % v_in_group) is 0) {
+            /* Generic integral-factors upsampling method */
+            upsample.methods[ci] = INT_UPSAMPLE;
+            upsample.h_expand[ci] = cast(byte) (h_out_group / h_in_group);
+            upsample.v_expand[ci] = cast(byte) (v_out_group / v_in_group);
+        } else
+            error();
+//          ERREXIT(cinfo, JERR_FRACT_SAMPLE_NOTIMPL);
+        if (need_buffer) {
+            upsample.color_buf[ci] = new byte[][]( cinfo.max_v_samp_factor,
+                         cast(int) jround_up(cinfo.output_width, cinfo.max_h_samp_factor));
+        }
+    }
+}
+
+static void jinit_phuff_decoder (jpeg_decompress_struct cinfo) {
+    int[][] coef_bit_ptr;
+    int ci, i;
+
+    cinfo.entropy = new phuff_entropy_decoder();
+//  entropy.pub.start_pass = start_pass_phuff_decoder;
+
+    /* Create progression status table */
+    cinfo.coef_bits = new int[][]( cinfo.num_components, DCTSIZE2 );
+    coef_bit_ptr = cinfo.coef_bits;
+    for (ci = 0; ci < cinfo.num_components; ci++)
+        for (i = 0; i < DCTSIZE2; i++)
+            coef_bit_ptr[ci][i] = -1;
+}
+
+
+static void jinit_huff_decoder (jpeg_decompress_struct cinfo) {
+
+    cinfo.entropy = new huff_entropy_decoder();
+//  entropy.pub.start_pass = start_pass_huff_decoder;
+//  entropy.pub.decode_mcu = decode_mcu;
+
+}
+
+static void jinit_inverse_dct (jpeg_decompress_struct cinfo) {
+    int ci;
+    jpeg_component_info compptr;
+
+    jpeg_inverse_dct idct = cinfo.idct = new jpeg_inverse_dct();
+//  idct.pub.start_pass = start_pass;
+
+    for (ci = 0; ci < cinfo.num_components; ci++) {
+        compptr = cinfo.comp_info[ci];
+        /* Allocate and pre-zero a multiplier table for each component */
+        compptr.dct_table = new int[DCTSIZE2];
+        /* Mark multiplier table not yet set up for any method */
+        idct.cur_method[ci] = -1;
+    }
+}
+
+static final int CONST_BITS = 13;
+static final int PASS1_BITS = 2;
+static final int RANGE_MASK =(MAXJSAMPLE * 4 + 3);
+static void jpeg_idct_islow (jpeg_decompress_struct cinfo, jpeg_component_info compptr,
+    short[] coef_block,
+    byte[][] output_buf, int output_buf_offset, int output_col)
+{
+    int tmp0, tmp1, tmp2, tmp3;
+    int tmp10, tmp11, tmp12, tmp13;
+    int z1, z2, z3, z4, z5;
+    short[] inptr;
+    int[] quantptr;
+    int[] wsptr;
+    byte[] outptr;
+    byte[] range_limit = cinfo.sample_range_limit;
+    int range_limit_offset = cinfo.sample_range_limit_offset + CENTERJSAMPLE;
+    int ctr;
+    int[] workspace = cinfo.workspace;  /* buffers data between passes */
+//  SHIFT_TEMPS
+
+    /* Pass 1: process columns from input, store into work array. */
+    /* Note results are scaled up by sqrt(8) compared to a true IDCT; */
+    /* furthermore, we scale the results by 2**PASS1_BITS. */
+
+    inptr = coef_block;
+    quantptr = compptr.dct_table;
+    wsptr = workspace;
+    int inptr_offset = 0, quantptr_offset = 0, wsptr_offset = 0;
+    for (ctr = DCTSIZE; ctr > 0; ctr--) {
+        /* Due to quantization, we will usually find that many of the input
+         * coefficients are zero, especially the AC terms.  We can exploit this
+         * by short-circuiting the IDCT calculation for any column in which all
+         * the AC terms are zero.   In that case each output is equal to the
+         * DC coefficient (with scale factor as needed).
+         * With typical images and quantization tables, half or more of the
+         * column DCT calculations can be simplified this way.
+         */
+
+        if (inptr[DCTSIZE*1+inptr_offset] is 0 && inptr[DCTSIZE*2+inptr_offset] is 0 &&
+            inptr[DCTSIZE*3+inptr_offset] is 0 && inptr[DCTSIZE*4+inptr_offset] is 0 &&
+            inptr[DCTSIZE*5+inptr_offset] is 0 && inptr[DCTSIZE*6+inptr_offset] is 0 &&
+            inptr[DCTSIZE*7+inptr_offset] is 0)
+        {
+            /* AC terms all zero */
+            int dcval = ((inptr[DCTSIZE*0+inptr_offset]) * quantptr[DCTSIZE*0+quantptr_offset]) << PASS1_BITS;
+
+            wsptr[DCTSIZE*0+wsptr_offset] = dcval;
+            wsptr[DCTSIZE*1+wsptr_offset] = dcval;
+            wsptr[DCTSIZE*2+wsptr_offset] = dcval;
+            wsptr[DCTSIZE*3+wsptr_offset] = dcval;
+            wsptr[DCTSIZE*4+wsptr_offset] = dcval;
+            wsptr[DCTSIZE*5+wsptr_offset] = dcval;
+            wsptr[DCTSIZE*6+wsptr_offset] = dcval;
+            wsptr[DCTSIZE*7+wsptr_offset] = dcval;
+
+            inptr_offset++;         /* advance pointers to next column */
+            quantptr_offset++;
+            wsptr_offset++;
+            continue;
+        }
+
+        /* Even part: reverse the even part of the forward DCT. */
+        /* The rotator is sqrt(2)*c(-6). */
+
+        z2 = ((inptr[DCTSIZE*2+inptr_offset]) * quantptr[DCTSIZE*2+quantptr_offset]);
+        z3 = ((inptr[DCTSIZE*6+inptr_offset]) * quantptr[DCTSIZE*6+quantptr_offset]);
+
+        z1 = ((z2 + z3) * 4433/*FIX_0_541196100*/);
+        tmp2 = z1 + (z3 * - 15137/*FIX_1_847759065*/);
+        tmp3 = z1 + (z2 * 6270/*FIX_0_765366865*/);
+
+        z2 = ((inptr[DCTSIZE*0+inptr_offset]) * quantptr[DCTSIZE*0+quantptr_offset]);
+        z3 = ((inptr[DCTSIZE*4+inptr_offset]) * quantptr[DCTSIZE*4+quantptr_offset]);
+
+        tmp0 = (z2 + z3) << CONST_BITS;
+        tmp1 = (z2 - z3) << CONST_BITS;
+
+        tmp10 = tmp0 + tmp3;
+        tmp13 = tmp0 - tmp3;
+        tmp11 = tmp1 + tmp2;
+        tmp12 = tmp1 - tmp2;
+
+        /* Odd part per figure 8; the matrix is unitary and hence its
+         * transpose is its inverse.    i0..i3 are y7,y5,y3,y1 respectively.
+         */
+
+        tmp0 = ((inptr[DCTSIZE*7+inptr_offset]) * quantptr[DCTSIZE*7+quantptr_offset]);
+        tmp1 = ((inptr[DCTSIZE*5+inptr_offset]) * quantptr[DCTSIZE*5+quantptr_offset]);
+        tmp2 = ((inptr[DCTSIZE*3+inptr_offset]) * quantptr[DCTSIZE*3+quantptr_offset]);
+        tmp3 = ((inptr[DCTSIZE*1+inptr_offset]) * quantptr[DCTSIZE*1+quantptr_offset]);
+
+        z1 = tmp0 + tmp3;
+        z2 = tmp1 + tmp2;
+        z3 = tmp0 + tmp2;
+        z4 = tmp1 + tmp3;
+        z5 = ((z3 + z4) * 9633/*FIX_1_175875602*/); /* sqrt(2) * c3 */
+
+        tmp0 = (tmp0 * 2446/*FIX_0_298631336*/); /* sqrt(2) * (-c1+c3+c5-c7) */
+        tmp1 = (tmp1 * 16819/*FIX_2_053119869*/); /* sqrt(2) * ( c1+c3-c5+c7) */
+        tmp2 = (tmp2 * 25172/*FIX_3_072711026*/); /* sqrt(2) * ( c1+c3+c5-c7) */
+        tmp3 = (tmp3 * 12299/*FIX_1_501321110*/); /* sqrt(2) * ( c1+c3-c5-c7) */
+        z1 = (z1 * - 7373/*FIX_0_899976223*/); /* sqrt(2) * (c7-c3) */
+        z2 = (z2 * - 20995/*FIX_2_562915447*/); /* sqrt(2) * (-c1-c3) */
+        z3 = (z3 * - 16069/*FIX_1_961570560*/); /* sqrt(2) * (-c3-c5) */
+        z4 = (z4 * - 3196/*FIX_0_390180644*/); /* sqrt(2) * (c5-c3) */
+
+        z3 += z5;
+        z4 += z5;
+
+        tmp0 += z1 + z3;
+        tmp1 += z2 + z4;
+        tmp2 += z2 + z3;
+        tmp3 += z1 + z4;
+
+        /* Final output stage: inputs are tmp10..tmp13, tmp0..tmp3 */
+
+//      #define DESCALE(x,n)    RIGHT_SHIFT((x) + (ONE << ((n)-1)), n)
+        wsptr[DCTSIZE*0+wsptr_offset] = (((tmp10 + tmp3) + (1 << ((CONST_BITS-PASS1_BITS)-1))) >> (CONST_BITS-PASS1_BITS));
+        wsptr[DCTSIZE*7+wsptr_offset] = (((tmp10 - tmp3) + (1 << ((CONST_BITS-PASS1_BITS)-1))) >> (CONST_BITS-PASS1_BITS));
+        wsptr[DCTSIZE*1+wsptr_offset] = (((tmp11 + tmp2) + (1 << ((CONST_BITS-PASS1_BITS)-1))) >> (CONST_BITS-PASS1_BITS));
+        wsptr[DCTSIZE*6+wsptr_offset] = (((tmp11 - tmp2) + (1 << ((CONST_BITS-PASS1_BITS)-1))) >> (CONST_BITS-PASS1_BITS));
+        wsptr[DCTSIZE*2+wsptr_offset] = (((tmp12 + tmp1) + (1 << ((CONST_BITS-PASS1_BITS)-1))) >> (CONST_BITS-PASS1_BITS));
+        wsptr[DCTSIZE*5+wsptr_offset] = (((tmp12 - tmp1) + (1 << ((CONST_BITS-PASS1_BITS)-1))) >> (CONST_BITS-PASS1_BITS));
+        wsptr[DCTSIZE*3+wsptr_offset] = (((tmp13 + tmp0) + (1 << ((CONST_BITS-PASS1_BITS)-1))) >> (CONST_BITS-PASS1_BITS));
+        wsptr[DCTSIZE*4+wsptr_offset] = (((tmp13 - tmp0) + (1 << ((CONST_BITS-PASS1_BITS)-1))) >> (CONST_BITS-PASS1_BITS));
+
+        inptr_offset++;         /* advance pointers to next column */
+        quantptr_offset++;
+        wsptr_offset++;
+    }
+
+
+    /* Pass 2: process rows from work array, store into output array. */
+    /* Note that we must descale the results by a factor of 8 is 2**3, */
+    /* and also undo the PASS1_BITS scaling. */
+
+    int outptr_offset = 0;
+    wsptr = workspace;
+    wsptr_offset =0;
+    for (ctr = 0; ctr < DCTSIZE; ctr++) {
+        outptr = output_buf[ctr+output_buf_offset];
+        outptr_offset = output_col;
+        /* Rows of zeroes can be exploited in the same way as we did with columns.
+         * However, the column calculation has created many nonzero AC terms, so
+         * the simplification applies less often (typically 5% to 10% of the time).
+         * On machines with very fast multiplication, it's possible that the
+         * test takes more time than it's worth.    In that case this section
+         * may be commented out.
+         */
+
+//#ifndef NO_ZERO_ROW_TEST
+        if (wsptr[1+wsptr_offset] is 0 && wsptr[2+wsptr_offset] is 0 && wsptr[3+wsptr_offset] is 0 && wsptr[4+wsptr_offset] is 0 &&
+            wsptr[5+wsptr_offset] is 0 && wsptr[6+wsptr_offset] is 0 && wsptr[7+wsptr_offset] is 0)
+        {
+            /* AC terms all zero */
+//          #define DESCALE(x,n)    RIGHT_SHIFT((x) + (ONE << ((n)-1)), n)
+            byte dcval = range_limit[range_limit_offset + ((((wsptr[0+wsptr_offset]) + (1 << ((PASS1_BITS+3)-1))) >> PASS1_BITS+3)
+                    & RANGE_MASK)];
+
+            outptr[0+outptr_offset] = dcval;
+            outptr[1+outptr_offset] = dcval;
+            outptr[2+outptr_offset] = dcval;
+            outptr[3+outptr_offset] = dcval;
+            outptr[4+outptr_offset] = dcval;
+            outptr[5+outptr_offset] = dcval;
+            outptr[6+outptr_offset] = dcval;
+            outptr[7+outptr_offset] = dcval;
+
+            wsptr_offset += DCTSIZE;        /* advance pointer to next row */
+            continue;
+        }
+//#endif
+
+        /* Even part: reverse the even part of the forward DCT. */
+        /* The rotator is sqrt(2)*c(-6). */
+
+        z2 = wsptr[2+wsptr_offset];
+        z3 = wsptr[6+wsptr_offset];
+
+        z1 = ((z2 + z3) * 4433/*FIX_0_541196100*/);
+        tmp2 = z1 + (z3 * - 15137/*FIX_1_847759065*/);
+        tmp3 = z1 + (z2 * 6270/*FIX_0_765366865*/);
+
+        tmp0 = (wsptr[0+wsptr_offset] + wsptr[4+wsptr_offset]) << CONST_BITS;
+        tmp1 = (wsptr[0+wsptr_offset] - wsptr[4+wsptr_offset]) << CONST_BITS;
+
+        tmp10 = tmp0 + tmp3;
+        tmp13 = tmp0 - tmp3;
+        tmp11 = tmp1 + tmp2;
+        tmp12 = tmp1 - tmp2;
+
+        /* Odd part per figure 8; the matrix is unitary and hence its
+         * transpose is its inverse.    i0..i3 are y7,y5,y3,y1 respectively.
+         */
+
+        tmp0 = wsptr[7+wsptr_offset];
+        tmp1 = wsptr[5+wsptr_offset];
+        tmp2 = wsptr[3+wsptr_offset];
+        tmp3 = wsptr[1+wsptr_offset];
+
+        z1 = tmp0 + tmp3;
+        z2 = tmp1 + tmp2;
+        z3 = tmp0 + tmp2;
+        z4 = tmp1 + tmp3;
+        z5 = ((z3 + z4) * 9633/*FIX_1_175875602*/); /* sqrt(2) * c3 */
+
+        tmp0 = (tmp0 * 2446/*FIX_0_298631336*/); /* sqrt(2) * (-c1+c3+c5-c7) */
+        tmp1 = (tmp1 * 16819/*FIX_2_053119869*/); /* sqrt(2) * ( c1+c3-c5+c7) */
+        tmp2 = (tmp2 * 25172/*FIX_3_072711026*/); /* sqrt(2) * ( c1+c3+c5-c7) */
+        tmp3 = (tmp3 * 12299/*FIX_1_501321110*/); /* sqrt(2) * ( c1+c3-c5-c7) */
+        z1 = (z1 * - 7373/*FIX_0_899976223*/); /* sqrt(2) * (c7-c3) */
+        z2 = (z2 * - 20995/*FIX_2_562915447*/); /* sqrt(2) * (-c1-c3) */
+        z3 = (z3 * - 16069/*FIX_1_961570560*/); /* sqrt(2) * (-c3-c5) */
+        z4 = (z4 * - 3196/*FIX_0_390180644*/); /* sqrt(2) * (c5-c3) */
+
+        z3 += z5;
+        z4 += z5;
+
+        tmp0 += z1 + z3;
+        tmp1 += z2 + z4;
+        tmp2 += z2 + z3;
+        tmp3 += z1 + z4;
+
+        /* Final output stage: inputs are tmp10..tmp13, tmp0..tmp3 */
+
+
+//      #define DESCALE(x,n)    RIGHT_SHIFT((x) + (ONE << ((n)-1)), n)
+        outptr[0+outptr_offset] = range_limit[range_limit_offset + ((((tmp10 + tmp3) + (1 << ((CONST_BITS+PASS1_BITS+3)-1))) >>
+                        CONST_BITS+PASS1_BITS+3)
+                    & RANGE_MASK)];
+        outptr[7+outptr_offset] = range_limit[range_limit_offset + ((((tmp10 - tmp3) + (1 << ((CONST_BITS+PASS1_BITS+3)-1))) >>
+                                    CONST_BITS+PASS1_BITS+3)
+                    & RANGE_MASK)];
+        outptr[1+outptr_offset] = range_limit[range_limit_offset + ((((tmp11 + tmp2) + (1 << ((CONST_BITS+PASS1_BITS+3)-1))) >>
+                                    CONST_BITS+PASS1_BITS+3)
+                    & RANGE_MASK)];
+        outptr[6+outptr_offset] = range_limit[range_limit_offset + ((((tmp11 - tmp2) + (1 << ((CONST_BITS+PASS1_BITS+3)-1))) >>
+                                    CONST_BITS+PASS1_BITS+3)
+                    & RANGE_MASK)];
+        outptr[2+outptr_offset] = range_limit[range_limit_offset + ((((tmp12 + tmp1) + (1 << ((CONST_BITS+PASS1_BITS+3)-1))) >>
+                                    CONST_BITS+PASS1_BITS+3)
+                    & RANGE_MASK)];
+        outptr[5+outptr_offset] = range_limit[range_limit_offset + ((((tmp12 - tmp1) + (1 << ((CONST_BITS+PASS1_BITS+3)-1))) >>
+                                    CONST_BITS+PASS1_BITS+3)
+                    & RANGE_MASK)];
+        outptr[3+outptr_offset] = range_limit[range_limit_offset + ((((tmp13 + tmp0) + (1 << ((CONST_BITS+PASS1_BITS+3)-1))) >>
+                                    CONST_BITS+PASS1_BITS+3)
+                    & RANGE_MASK)];
+        outptr[4+outptr_offset] = range_limit[range_limit_offset + ((((tmp13 - tmp0) + (1 << ((CONST_BITS+PASS1_BITS+3)-1))) >>
+                                    CONST_BITS+PASS1_BITS+3)
+                    & RANGE_MASK)];
+
+        wsptr_offset += DCTSIZE;        /* advance pointer to next row */
+    }
+}
+
+static void upsample (jpeg_decompress_struct cinfo,
+    byte[][][] input_buf, int[] input_buf_offset, int[] in_row_group_ctr,
+    int in_row_groups_avail,
+    byte[][] output_buf, int[] out_row_ctr,
+    int out_rows_avail)
+{
+    sep_upsample(cinfo, input_buf, input_buf_offset, in_row_group_ctr, in_row_groups_avail, output_buf, out_row_ctr, out_rows_avail);
+}
+
+static bool smoothing_ok (jpeg_decompress_struct cinfo) {
+    jpeg_d_coef_controller coef = cinfo.coef;
+    bool smoothing_useful = false;
+    int ci, coefi;
+    jpeg_component_info compptr;
+    JQUANT_TBL qtable;
+    int[] coef_bits;
+    int[] coef_bits_latch;
+
+    if (! cinfo.progressive_mode || cinfo.coef_bits is null)
+        return false;
+
+    /* Allocate latch area if not already done */
+    if (coef.coef_bits_latch is null)
+        coef.coef_bits_latch = new int[cinfo.num_components * SAVED_COEFS];
+    coef_bits_latch = coef.coef_bits_latch;
+    int coef_bits_latch_offset = 0;
+
+    for (ci = 0; ci < cinfo.num_components; ci++) {
+        compptr = cinfo.comp_info[ci];
+        /* All components' quantization values must already be latched. */
+        if ((qtable = compptr.quant_table) is null)
+            return false;
+        /* Verify DC & first 5 AC quantizers are nonzero to avoid zero-divide. */
+        if (qtable.quantval[0] is 0 ||
+            qtable.quantval[Q01_POS] is 0 ||
+            qtable.quantval[Q10_POS] is 0 ||
+            qtable.quantval[Q20_POS] is 0 ||
+            qtable.quantval[Q11_POS] is 0 ||
+            qtable.quantval[Q02_POS] is 0)
+                return false;
+        /* DC values must be at least partly known for all components. */
+        coef_bits = cinfo.coef_bits[ci];
+        if (coef_bits[0] < 0)
+            return false;
+        /* Block smoothing is helpful if some AC coefficients remain inaccurate. */
+        for (coefi = 1; coefi <= 5; coefi++) {
+            coef_bits_latch[coefi+coef_bits_latch_offset] = coef_bits[coefi];
+            if (coef_bits[coefi] !is 0)
+                smoothing_useful = true;
+        }
+        coef_bits_latch_offset += SAVED_COEFS;
+    }
+
+    return smoothing_useful;
+}
+
+static void master_selection (jpeg_decompress_struct cinfo) {
+    jpeg_decomp_master master = cinfo.master;
+    bool use_c_buffer;
+    long samplesperrow;
+    int jd_samplesperrow;
+
+    /* Initialize dimensions and other stuff */
+    jpeg_calc_output_dimensions(cinfo);
+    prepare_range_limit_table(cinfo);
+
+    /* Width of an output scanline must be representable as JDIMENSION. */
+    samplesperrow = cast(long) cinfo.output_width * cast(long) cinfo.out_color_components;
+    jd_samplesperrow = cast(int) samplesperrow;
+    if ( jd_samplesperrow !is samplesperrow)
+        error();
+//      ERREXIT(cinfo, JERR_WIDTH_OVERFLOW);
+
+    /* Initialize my private state */
+    master.pass_number = 0;
+    master.using_merged_upsample = use_merged_upsample(cinfo);
+
+    /* Color quantizer selection */
+    master.quantizer_1pass = null;
+    master.quantizer_2pass = null;
+    /* No mode changes if not using buffered-image mode. */
+    if (! cinfo.quantize_colors || ! cinfo.buffered_image) {
+        cinfo.enable_1pass_quant = false;
+        cinfo.enable_external_quant = false;
+        cinfo.enable_2pass_quant = false;
+    }
+    if (cinfo.quantize_colors) {
+        error(DWT.ERROR_NOT_IMPLEMENTED);
+//      if (cinfo.raw_data_out)
+//          ERREXIT(cinfo, JERR_NOTIMPL);
+//      /* 2-pass quantizer only works in 3-component color space. */
+//      if (cinfo.out_color_components !is 3) {
+//          cinfo.enable_1pass_quant = true;
+//          cinfo.enable_external_quant = false;
+//          cinfo.enable_2pass_quant = false;
+//          cinfo.colormap = null;
+//      } else if (cinfo.colormap !is null) {
+//          cinfo.enable_external_quant = true;
+//      } else if (cinfo.two_pass_quantize) {
+//          cinfo.enable_2pass_quant = true;
+//      } else {
+//          cinfo.enable_1pass_quant = true;
+//      }
+//
+//      if (cinfo.enable_1pass_quant) {
+//#ifdef QUANT_1PASS_SUPPORTED
+//          jinit_1pass_quantizer(cinfo);
+//          master.quantizer_1pass = cinfo.cquantize;
+//#else
+//          ERREXIT(cinfo, JERR_NOT_COMPILED);
+//#endif
+//      }
+//
+//      /* We use the 2-pass code to map to external colormaps. */
+//      if (cinfo.enable_2pass_quant || cinfo.enable_external_quant) {
+//#ifdef QUANT_2PASS_SUPPORTED
+//          jinit_2pass_quantizer(cinfo);
+//          master.quantizer_2pass = cinfo.cquantize;
+//#else
+//          ERREXIT(cinfo, JERR_NOT_COMPILED);
+//#endif
+//      }
+//      /* If both quantizers are initialized, the 2-pass one is left active;
+//       * this is necessary for starting with quantization to an external map.
+//       */
+    }
+
+    /* Post-processing: in particular, color conversion first */
+    if (! cinfo.raw_data_out) {
+        if (master.using_merged_upsample) {
+//#ifdef UPSAMPLE_MERGING_SUPPORTED
+//          jinit_merged_upsampler(cinfo); /* does color conversion too */
+//#else
+            error();
+//          ERREXIT(cinfo, JERR_NOT_COMPILED);
+//#endif
+        } else {
+            jinit_color_deconverter(cinfo);
+            jinit_upsampler(cinfo);
+        }
+        jinit_d_post_controller(cinfo, cinfo.enable_2pass_quant);
+    }
+    /* Inverse DCT */
+    jinit_inverse_dct(cinfo);
+    /* Entropy decoding: either Huffman or arithmetic coding. */
+    if (cinfo.arith_code) {
+        error();
+//      ERREXIT(cinfo, JERR_ARITH_NOTIMPL);
+    } else {
+        if (cinfo.progressive_mode) {
+//#ifdef D_PROGRESSIVE_SUPPORTED
+            jinit_phuff_decoder(cinfo);
+//#else
+//          ERREXIT(cinfo, JERR_NOT_COMPILED);
+//#endif
+        } else
+            jinit_huff_decoder(cinfo);
+    }
+
+    /* Initialize principal buffer controllers. */
+    use_c_buffer = cinfo.inputctl.has_multiple_scans || cinfo.buffered_image;
+    jinit_d_coef_controller(cinfo, use_c_buffer);
+
+    if (! cinfo.raw_data_out)
+        jinit_d_main_controller(cinfo, false /* never need full buffer here */);
+
+    /* Initialize input side of decompressor to consume first scan. */
+    start_input_pass (cinfo);
+
+//#ifdef D_MULTISCAN_FILES_SUPPORTED
+    /* If jpeg_start_decompress will read the whole file, initialize
+     * progress monitoring appropriately.   The input step is counted
+     * as one pass.
+     */
+//  if (cinfo.progress !is null && ! cinfo.buffered_image &&
+//          cinfo.inputctl.has_multiple_scans) {
+//      int nscans;
+//      /* Estimate number of scans to set pass_limit. */
+//      if (cinfo.progressive_mode) {
+//          /* Arbitrarily estimate 2 interleaved DC scans + 3 AC scans/component. */
+//          nscans = 2 + 3 * cinfo.num_components;
+//      } else {
+//          /* For a nonprogressive multiscan file, estimate 1 scan per component. */
+//          nscans = cinfo.num_components;
+//      }
+//      cinfo.progress.pass_counter = 0L;
+//      cinfo.progress.pass_limit = cast(long) cinfo.total_iMCU_rows * nscans;
+//      cinfo.progress.completed_passes = 0;
+//      cinfo.progress.total_passes = (cinfo.enable_2pass_quant ? 3 : 2);
+//      /* Count the input pass as done */
+//      master.pass_number++;
+//  }
+//#endif /* D_MULTISCAN_FILES_SUPPORTED */
+}
+
+static void jinit_master_decompress (jpeg_decompress_struct cinfo) {
+    jpeg_decomp_master master = new jpeg_decomp_master();
+    cinfo.master = master;
+//  master.prepare_for_output_pass = prepare_for_output_pass;
+//  master.finish_output_pass = finish_output_pass;
+
+    master.is_dummy_pass = false;
+
+    master_selection(cinfo);
+}
+
+static void
+jcopy_sample_rows (byte[][] input_array, int source_row,
+           byte[][] output_array, int dest_row,
+           int num_rows, int num_cols)
+/* Copy some rows of samples from one place to another.
+ * num_rows rows are copied from input_array[source_row++]
+ * to output_array[dest_row++]; these areas may overlap for duplication.
+ * The source and destination arrays must be at least as wide as num_cols.
+ */
+{
+  byte[] inptr, outptr;
+  int count = num_cols;
+  int row;
+
+  int input_array_offset = source_row;
+  int output_array_offset = dest_row;
+
+  for (row = num_rows; row > 0; row--) {
+    inptr = input_array[input_array_offset++];
+    outptr = output_array[output_array_offset++];
+    System.arraycopy(inptr, 0, outptr, 0, count);
+  }
+}
+
+static bool jpeg_start_decompress (jpeg_decompress_struct cinfo) {
+    if (cinfo.global_state is DSTATE_READY) {
+        /* First call: initialize master control, select active modules */
+        jinit_master_decompress(cinfo);
+        if (cinfo.buffered_image) {
+            /* No more work here; expecting jpeg_start_output next */
+            cinfo.global_state = DSTATE_BUFIMAGE;
+            return true;
+        }
+        cinfo.global_state = DSTATE_PRELOAD;
+    }
+    if (cinfo.global_state is DSTATE_PRELOAD) {
+        /* If file has multiple scans, absorb them all into the coef buffer */
+        if (cinfo.inputctl.has_multiple_scans) {
+//#ifdef D_MULTISCAN_FILES_SUPPORTED
+            for (;;) {
+                int retcode;
+                /* Call progress monitor hook if present */
+//              if (cinfo.progress !is null)
+//                  (*cinfo.progress.progress_monitor) ((j_common_ptr) cinfo);
+                /* Absorb some more input */
+                retcode = consume_input (cinfo);
+                if (retcode is JPEG_SUSPENDED)
+                    return false;
+                if (retcode is JPEG_REACHED_EOI)
+                    break;
+                /* Advance progress counter if appropriate */
+//              if (cinfo.progress !is null && (retcode is JPEG_ROW_COMPLETED || retcode is JPEG_REACHED_SOS)) {
+//                  if (++cinfo.progress.pass_counter >= cinfo.progress.pass_limit) {
+//                      /* jdmaster underestimated number of scans; ratchet up one scan */
+//                      cinfo.progress.pass_limit += cast(long) cinfo.total_iMCU_rows;
+//                  }
+//              }
+            }
+//#else
+//          ERREXIT(cinfo, JERR_NOT_COMPILED);
+//#endif /* D_MULTISCAN_FILES_SUPPORTED */
+        }
+        cinfo.output_scan_number = cinfo.input_scan_number;
+    } else if (cinfo.global_state !is DSTATE_PRESCAN)
+        error();
+//      ERREXIT1(cinfo, JERR_BAD_STATE, cinfo.global_state);
+    /* Perform any dummy output passes, and set up for the final pass */
+    return output_pass_setup(cinfo);
+}
+
+static void prepare_for_output_pass (jpeg_decompress_struct cinfo) {
+    jpeg_decomp_master master = cinfo.master;
+
+    if (master.is_dummy_pass) {
+//#ifdef QUANT_2PASS_SUPPORTED
+//      /* Final pass of 2-pass quantization */
+//      master.pub.is_dummy_pass = FALSE;
+//      (*cinfo.cquantize.start_pass) (cinfo, FALSE);
+//      (*cinfo.post.start_pass) (cinfo, JBUF_CRANK_DEST);
+//      (*cinfo.main.start_pass) (cinfo, JBUF_CRANK_DEST);
+//#else
+        error(DWT.ERROR_NOT_IMPLEMENTED);
+//      ERREXIT(cinfo, JERR_NOT_COMPILED);
+//#endif /* QUANT_2PASS_SUPPORTED */
+    } else {
+        if (cinfo.quantize_colors && cinfo.colormap is null) {
+            /* Select new quantization method */
+            if (cinfo.two_pass_quantize && cinfo.enable_2pass_quant) {
+                cinfo.cquantize = master.quantizer_2pass;
+                master.is_dummy_pass = true;
+            } else if (cinfo.enable_1pass_quant) {
+                cinfo.cquantize = master.quantizer_1pass;
+            } else {
+                error();
+//  ERREXIT(cinfo, JERR_MODE_CHANGE);
+            }
+        }
+        cinfo.idct.start_pass (cinfo);
+        start_output_pass (cinfo);
+        if (! cinfo.raw_data_out) {
+            if (! master.using_merged_upsample)
+                cinfo.cconvert.start_pass (cinfo);
+            cinfo.upsample.start_pass (cinfo);
+            if (cinfo.quantize_colors)
+                cinfo.cquantize.start_pass (cinfo, master.is_dummy_pass);
+            cinfo.post.start_pass (cinfo, (master.is_dummy_pass ? JBUF_SAVE_AND_PASS : JBUF_PASS_THRU));
+            cinfo.main.start_pass (cinfo, JBUF_PASS_THRU);
+        }
+    }
+
+//  /* Set up progress monitor's pass info if present */
+//  if (cinfo.progress !is NULL) {
+//      cinfo.progress.completed_passes = master.pass_number;
+//      cinfo.progress.total_passes = master.pass_number +
+//                      (master.pub.is_dummy_pass ? 2 : 1);
+//      /* In buffered-image mode, we assume one more output pass if EOI not
+//       * yet reached, but no more passes if EOI has been reached.
+//       */
+//      if (cinfo.buffered_image && ! cinfo.inputctl.eoi_reached) {
+//          cinfo.progress.total_passes += (cinfo.enable_2pass_quant ? 2 : 1);
+//      }
+//  }
+}
+
+
+static bool jpeg_resync_to_restart (jpeg_decompress_struct cinfo, int desired) {
+    int marker = cinfo.unread_marker;
+    int action = 1;
+
+    /* Always put up a warning. */
+//  WARNMS2(cinfo, JWRN_MUST_RESYNC, marker, desired);
+
+    /* Outer loop handles repeated decision after scanning forward. */
+    for (;;) {
+        if (marker < M_SOF0)
+            action = 2;     /* invalid marker */
+        else if (marker < M_RST0 || marker > M_RST7)
+            action = 3;     /* valid non-restart marker */
+        else {
+            if (marker is (M_RST0 + ((desired+1) & 7)) || marker is ( M_RST0 + ((desired+2) & 7)))
+                action = 3;     /* one of the next two expected restarts */
+            else if (marker is (M_RST0 + ((desired-1) & 7)) || marker is ( M_RST0 + ((desired-2) & 7)))
+                action = 2;     /* a prior restart, so advance */
+            else
+                action = 1;     /* desired restart or too far away */
+        }
+//      TRACEMS2(cinfo, 4, JTRC_RECOVERY_ACTION, marker, action);
+        switch (action) {
+            case 1:
+                /* Discard marker and let entropy decoder resume processing. */
+                cinfo.unread_marker = 0;
+                return true;
+            case 2:
+                /* Scan to the next marker, and repeat the decision loop. */
+                if (! next_marker(cinfo))
+                    return false;
+                marker = cinfo.unread_marker;
+                break;
+            case 3:
+                /* Return without advancing past this marker. */
+                /* Entropy decoder will be forced to process an empty segment. */
+                return true;
+            default:
+        }
+    } /* end loop */
+}
+
+static bool read_restart_marker (jpeg_decompress_struct cinfo) {
+    /* Obtain a marker unless we already did. */
+    /* Note that next_marker will complain if it skips any data. */
+    if (cinfo.unread_marker is 0) {
+        if (! next_marker(cinfo))
+            return false;
+    }
+
+    if (cinfo.unread_marker is (M_RST0 + cinfo.marker.next_restart_num)) {
+        /* Normal case --- swallow the marker and let entropy decoder continue */
+//      TRACEMS1(cinfo, 3, JTRC_RST, cinfo.marker.next_restart_num);
+        cinfo.unread_marker = 0;
+    } else {
+        /* Uh-oh, the restart markers have been messed up. */
+        /* Let the data source manager determine how to resync. */
+        if (! jpeg_resync_to_restart (cinfo, cinfo.marker.next_restart_num))
+            return false;
+    }
+
+    /* Update next-restart state */
+    cinfo.marker.next_restart_num = (cinfo.marker.next_restart_num + 1) & 7;
+
+    return true;
+}
+
+static bool jpeg_fill_bit_buffer (bitread_working_state state, int get_buffer, int bits_left, int nbits)
+/* Load up the bit buffer to a depth of at least nbits */
+{
+    /* Copy heavily used state fields into locals (hopefully registers) */
+    byte[] buffer = state.buffer;
+    int bytes_in_buffer = state.bytes_in_buffer;
+    int bytes_offset = state.bytes_offset;
+    jpeg_decompress_struct cinfo = state.cinfo;
+
+    /* Attempt to load at least MIN_GET_BITS bits into get_buffer. */
+    /* (It is assumed that no request will be for more than that many bits.) */
+    /* We fail to do so only if we hit a marker or are forced to suspend. */
+
+    if (cinfo.unread_marker is 0) { /* cannot advance past a marker */
+        while (bits_left < MIN_GET_BITS) {
+            int c;
+
+            /* Attempt to read a byte */
+            if (bytes_offset is bytes_in_buffer) {
+                if (! fill_input_buffer (cinfo))
+                    return false;
+                buffer = cinfo.buffer;
+                bytes_in_buffer = cinfo.bytes_in_buffer;
+                bytes_offset = cinfo.bytes_offset;
+            }
+            c = buffer[bytes_offset++] & 0xFF;
+
+            /* If it's 0xFF, check and discard stuffed zero byte */
+            if (c is 0xFF) {
+                /* Loop here to discard any padding FF's on terminating marker,
+                 * so that we can save a valid unread_marker value. NOTE: we will
+                 * accept multiple FF's followed by a 0 as meaning a single FF data
+                 * byte.    This data pattern is not valid according to the standard.
+                 */
+                do {
+                    if (bytes_offset is bytes_in_buffer) {
+                        if (! fill_input_buffer (cinfo))
+                            return false;
+                        buffer = cinfo.buffer;
+                        bytes_in_buffer = cinfo.bytes_in_buffer;
+                        bytes_offset = cinfo.bytes_offset;
+                    }
+                    c = buffer[bytes_offset++] & 0xFF;
+                } while (c is 0xFF);
+
+                if (c is 0) {
+                    /* Found FF/00, which represents an FF data byte */
+                    c = 0xFF;
+                } else {
+                    /* Oops, it's actually a marker indicating end of compressed data.
+                     * Save the marker code for later use.
+                     * Fine point: it might appear that we should save the marker into
+                     * bitread working state, not straight into permanent state.    But
+                     * once we have hit a marker, we cannot need to suspend within the
+                     * current MCU, because we will read no more bytes from the data
+                     * source.  So it is OK to update permanent state right away.
+                     */
+                    cinfo.unread_marker = c;
+                    /* See if we need to insert some fake zero bits. */
+//                  goto no_more_bytes;
+                    if (nbits > bits_left) {
+                        /* Uh-oh.   Report corrupted data to user and stuff zeroes into
+                         * the data stream, so that we can produce some kind of image.
+                         * We use a nonvolatile flag to ensure that only one warning message
+                         * appears per data segment.
+                         */
+                        if (! cinfo.entropy.insufficient_data) {
+//                          WARNMS(cinfo, JWRN_HIT_MARKER);
+                            cinfo.entropy.insufficient_data = true;
+                        }
+                    /* Fill the buffer with zero bits */
+                        get_buffer <<= MIN_GET_BITS - bits_left;
+                        bits_left = MIN_GET_BITS;
+                    }
+
+                    /* Unload the local registers */
+                    state.buffer = buffer;
+                    state.bytes_in_buffer = bytes_in_buffer;
+                    state.bytes_offset = bytes_offset;
+                    state.get_buffer = get_buffer;
+                    state.bits_left = bits_left;
+
+                    return true;
+
+                }
+            }
+
+            /* OK, load c into get_buffer */
+            get_buffer = (get_buffer << 8) | c;
+            bits_left += 8;
+        } /* end while */
+    } else {
+//      no_more_bytes:
+        /* We get here if we've read the marker that terminates the compressed
+         * data segment.    There should be enough bits in the buffer register
+         * to satisfy the request; if so, no problem.
+         */
+        if (nbits > bits_left) {
+            /* Uh-oh.   Report corrupted data to user and stuff zeroes into
+             * the data stream, so that we can produce some kind of image.
+             * We use a nonvolatile flag to ensure that only one warning message
+             * appears per data segment.
+             */
+            if (! cinfo.entropy.insufficient_data) {
+//              WARNMS(cinfo, JWRN_HIT_MARKER);
+                cinfo.entropy.insufficient_data = true;
+            }
+            /* Fill the buffer with zero bits */
+            get_buffer <<= MIN_GET_BITS - bits_left;
+            bits_left = MIN_GET_BITS;
+        }
+    }
+
+    /* Unload the local registers */
+    state.buffer = buffer;
+    state.bytes_in_buffer = bytes_in_buffer;
+    state.bytes_offset = bytes_offset;
+    state.get_buffer = get_buffer;
+    state.bits_left = bits_left;
+
+    return true;
+}
+
+static int jpeg_huff_decode (bitread_working_state state, int get_buffer, int bits_left, d_derived_tbl htbl, int min_bits) {
+    int l = min_bits;
+    int code;
+
+    /* HUFF_DECODE has determined that the code is at least min_bits */
+    /* bits long, so fetch that many bits in one swoop. */
+
+//  CHECK_BIT_BUFFER(*state, l, return -1);
+    {
+    if (bits_left < (l)) {
+        if (! jpeg_fill_bit_buffer(state,get_buffer,bits_left,l)) {
+            return -1;
+        }
+        get_buffer = state.get_buffer; bits_left = state.bits_left;
+    }
+    }
+//  code = GET_BITS(l);
+    code = (( (get_buffer >> (bits_left -= (l)))) & ((1<<(l))-1));
+
+    /* Collect the rest of the Huffman code one bit at a time. */
+    /* This is per Figure F.16 in the JPEG spec. */
+
+    while (code > htbl.maxcode[l]) {
+        code <<= 1;
+//      CHECK_BIT_BUFFER(*state, 1, return -1);
+        {
+        if (bits_left < (1)) {
+            if (! jpeg_fill_bit_buffer(state,get_buffer,bits_left,1)) {
+                return -1;
+            }
+            get_buffer = state.get_buffer; bits_left = state.bits_left;
+        }
+        }
+//      code |= GET_BITS(1);
+        code |= (( (get_buffer >> (bits_left -= (1)))) & ((1<<(1))-1));
+        l++;
+    }
+
+    /* Unload the local registers */
+    state.get_buffer = get_buffer;
+    state.bits_left = bits_left;
+
+    /* With garbage input we may reach the sentinel value l = 17. */
+
+    if (l > 16) {
+//      WARNMS(state.cinfo, JWRN_HUFF_BAD_CODE);
+        return 0;           /* fake a zero as the safest result */
+    }
+
+    return htbl.pub.huffval[ (code + htbl.valoffset[l]) ] & 0xFF;
+}
+
+static int decompress_onepass (jpeg_decompress_struct cinfo, byte[][][] output_buf, int[] output_buf_offset) {
+    jpeg_d_coef_controller coef = cinfo.coef;
+    int MCU_col_num;    /* index of current MCU within row */
+    int last_MCU_col = cinfo.MCUs_per_row - 1;
+    int last_iMCU_row = cinfo.total_iMCU_rows - 1;
+    int blkn, ci, xindex, yindex, yoffset, useful_width;
+    byte[][] output_ptr;
+    int start_col, output_col;
+    jpeg_component_info compptr;
+//  inverse_DCT_method_ptr inverse_DCT;
+
+    /* Loop to process as much as one whole iMCU row */
+    for (yoffset = coef.MCU_vert_offset; yoffset < coef.MCU_rows_per_iMCU_row; yoffset++) {
+        for (MCU_col_num = coef.MCU_ctr; MCU_col_num <= last_MCU_col; MCU_col_num++) {
+            /* Try to fetch an MCU. Entropy decoder expects buffer to be zeroed. */
+            for (int i = 0; i < cinfo.blocks_in_MCU; i++) {
+                short[] blk = coef.MCU_buffer[i];
+                for (int j = 0; j < blk.length; j++) {
+                    blk[j] = 0;
+                }
+            }
+            if (! cinfo.entropy.decode_mcu (cinfo, coef.MCU_buffer)) {
+                /* Suspension forced; update state counters and exit */
+                coef.MCU_vert_offset = yoffset;
+                coef.MCU_ctr = MCU_col_num;
+                return JPEG_SUSPENDED;
+            }
+            /* Determine where data should go in output_buf and do the IDCT thing.
+             * We skip dummy blocks at the right and bottom edges (but blkn gets
+             * incremented past them!). Note the inner loop relies on having
+             * allocated the MCU_buffer[] blocks sequentially.
+             */
+            blkn = 0;           /* index of current DCT block within MCU */
+            for (ci = 0; ci < cinfo.comps_in_scan; ci++) {
+                compptr = cinfo.cur_comp_info[ci];
+                /* Don't bother to IDCT an uninteresting component. */
+                if (! compptr.component_needed) {
+                    blkn += compptr.MCU_blocks;
+                    continue;
+                }
+//              inverse_DCT = cinfo.idct.inverse_DCT[compptr.component_index];
+                useful_width = (MCU_col_num < last_MCU_col) ? compptr.MCU_width : compptr.last_col_width;
+                output_ptr = output_buf[compptr.component_index];
+                int output_ptr_offset = output_buf_offset[compptr.component_index] + yoffset * compptr.DCT_scaled_size;
+                start_col = MCU_col_num * compptr.MCU_sample_width;
+                for (yindex = 0; yindex < compptr.MCU_height; yindex++) {
+                    if (cinfo.input_iMCU_row < last_iMCU_row || yoffset+yindex < compptr.last_row_height) {
+                        output_col = start_col;
+                        for (xindex = 0; xindex < useful_width; xindex++) {
+                            jpeg_idct_islow(cinfo, compptr, coef.MCU_buffer[blkn+xindex], output_ptr, output_ptr_offset, output_col);
+                            output_col += compptr.DCT_scaled_size;
+                        }
+                    }
+                    blkn += compptr.MCU_width;
+                    output_ptr_offset += compptr.DCT_scaled_size;
+                }
+            }
+        }
+        /* Completed an MCU row, but perhaps not an iMCU row */
+        coef.MCU_ctr = 0;
+    }
+    /* Completed the iMCU row, advance counters for next one */
+    cinfo.output_iMCU_row++;
+    if (++(cinfo.input_iMCU_row) < cinfo.total_iMCU_rows) {
+        coef.start_iMCU_row(cinfo);
+        return JPEG_ROW_COMPLETED;
+    }
+    /* Completed the scan */
+    finish_input_pass (cinfo);
+    return JPEG_SCAN_COMPLETED;
+}
+
+static int decompress_smooth_data (jpeg_decompress_struct cinfo, byte[][][] output_buf, int[] output_buf_offset) {
+    jpeg_d_coef_controller coef = cinfo.coef;
+    int last_iMCU_row = cinfo.total_iMCU_rows - 1;
+    int block_num, last_block_column;
+    int ci, block_row, block_rows, access_rows;
+    short[][][] buffer;
+    short[][] buffer_ptr, prev_block_row, next_block_row;
+    byte[][] output_ptr;
+    int output_col;
+    jpeg_component_info compptr;
+//  inverse_DCT_method_ptr inverse_DCT;
+    bool first_row, last_row;
+    short[] workspace = coef.workspace;
+    if (workspace is null) workspace = coef.workspace = new short[DCTSIZE2];
+    int[] coef_bits;
+    JQUANT_TBL quanttbl;
+    int Q00,Q01,Q02,Q10,Q11,Q20, num;
+    int DC1,DC2,DC3,DC4,DC5,DC6,DC7,DC8,DC9;
+    int Al, pred;
+
+    /* Force some input to be done if we are getting ahead of the input. */
+    while (cinfo.input_scan_number <= cinfo.output_scan_number && ! cinfo.inputctl.eoi_reached) {
+        if (cinfo.input_scan_number is cinfo.output_scan_number) {
+            /* If input is working on current scan, we ordinarily want it to
+             * have completed the current row.  But if input scan is DC,
+             * we want it to keep one row ahead so that next block row's DC
+             * values are up to date.
+             */
+            int delta = (cinfo.Ss is 0) ? 1 : 0;
+            if (cinfo.input_iMCU_row > cinfo.output_iMCU_row+delta)
+                break;
+        }
+        if (consume_input(cinfo) is JPEG_SUSPENDED)
+            return JPEG_SUSPENDED;
+    }
+
+    /* OK, output from the virtual arrays. */
+    for (ci = 0; ci < cinfo.num_components; ci++) {
+        compptr = cinfo.comp_info[ci];
+        /* Don't bother to IDCT an uninteresting component. */
+        if (! compptr.component_needed)
+            continue;
+        /* Count non-dummy DCT block rows in this iMCU row. */
+        if (cinfo.output_iMCU_row < last_iMCU_row) {
+            block_rows = compptr.v_samp_factor;
+            access_rows = block_rows * 2; /* this and next iMCU row */
+            last_row = false;
+        } else {
+            /* NB: can't use last_row_height here; it is input-side-dependent! */
+            block_rows = (compptr.height_in_blocks % compptr.v_samp_factor);
+            if (block_rows is 0) block_rows = compptr.v_samp_factor;
+            access_rows = block_rows; /* this iMCU row only */
+            last_row = true;
+        }
+        /* Align the virtual buffer for this component. */
+        int buffer_offset;
+        if (cinfo.output_iMCU_row > 0) {
+            access_rows += compptr.v_samp_factor; /* prior iMCU row too */
+            buffer = coef.whole_image[ci];
+            buffer_offset = (cinfo.output_iMCU_row - 1) * compptr.v_samp_factor;
+            buffer_offset += compptr.v_samp_factor; /* point to current iMCU row */
+            first_row = false;
+        } else {
+            buffer = coef.whole_image[ci];
+            buffer_offset = 0;
+            first_row = true;
+        }
+        /* Fetch component-dependent info */
+        coef_bits = coef.coef_bits_latch;
+        int coef_offset = (ci * SAVED_COEFS);
+        quanttbl = compptr.quant_table;
+        Q00 = quanttbl.quantval[0];
+        Q01 = quanttbl.quantval[Q01_POS];
+        Q10 = quanttbl.quantval[Q10_POS];
+        Q20 = quanttbl.quantval[Q20_POS];
+        Q11 = quanttbl.quantval[Q11_POS];
+        Q02 = quanttbl.quantval[Q02_POS];
+//      inverse_DCT = cinfo.idct.inverse_DCT[ci];
+        output_ptr = output_buf[ci];
+        int output_ptr_offset = output_buf_offset[ci];
+        /* Loop over all DCT blocks to be processed. */
+        for (block_row = 0; block_row < block_rows; block_row++) {
+            buffer_ptr = buffer[block_row+buffer_offset];
+            int buffer_ptr_offset = 0, prev_block_row_offset = 0, next_block_row_offset = 0;
+            if (first_row && block_row is 0) {
+                prev_block_row = buffer_ptr;
+                prev_block_row_offset = buffer_ptr_offset;
+            } else {
+                prev_block_row = buffer[block_row-1+buffer_offset];
+                prev_block_row_offset = 0;
+            }
+            if (last_row && block_row is block_rows-1) {
+                next_block_row = buffer_ptr;
+                next_block_row_offset = buffer_ptr_offset;
+            } else {
+                next_block_row = buffer[block_row+1+buffer_offset];
+                next_block_row_offset = 0;
+            }
+            /* We fetch the surrounding DC values using a sliding-register approach.
+             * Initialize all nine here so as to do the right thing on narrow pics.
+             */
+            DC1 = DC2 = DC3 = prev_block_row[0+prev_block_row_offset][0];
+            DC4 = DC5 = DC6 = buffer_ptr[0+buffer_ptr_offset][0];
+            DC7 = DC8 = DC9 = next_block_row[0+next_block_row_offset][0];
+            output_col = 0;
+            last_block_column = compptr.width_in_blocks - 1;
+            for (block_num = 0; block_num <= last_block_column; block_num++) {
+                /* Fetch current DCT block into workspace so we can modify it. */
+//              jcopy_block_row(buffer_ptr, workspace, 1);
+                System.arraycopy(buffer_ptr[buffer_ptr_offset], 0, workspace, 0, workspace.length);
+                /* Update DC values */
+                if (block_num < last_block_column) {
+                    DC3 = prev_block_row[1+prev_block_row_offset][0];
+                    DC6 = buffer_ptr[1+buffer_ptr_offset][0];
+                    DC9 = next_block_row[1+next_block_row_offset][0];
+                }
+                /* Compute coefficient estimates per K.8.
+                 * An estimate is applied only if coefficient is still zero,
+                 * and is not known to be fully accurate.
+                 */
+                /* AC01 */
+                if ((Al=coef_bits[1+coef_offset]) !is 0 && workspace[1] is 0) {
+                    num = 36 * Q00 * (DC4 - DC6);
+                    if (num >= 0) {
+                        pred = (((Q01<<7) + num) / (Q01<<8));
+                        if (Al > 0 && pred >= (1<<Al))
+                            pred = (1<<Al)-1;
+                    } else {
+                        pred = (((Q01<<7) - num) / (Q01<<8));
+                        if (Al > 0 && pred >= (1<<Al))
+                            pred = (1<<Al)-1;
+                        pred = -pred;
+                    }
+                    workspace[1] = cast(short) pred;
+                }
+                /* AC10 */
+                if ((Al=coef_bits[2+coef_offset]) !is 0 && workspace[8] is 0) {
+                    num = 36 * Q00 * (DC2 - DC8);
+                    if (num >= 0) {
+                        pred = (((Q10<<7) + num) / (Q10<<8));
+                        if (Al > 0 && pred >= (1<<Al))
+                            pred = (1<<Al)-1;
+                    } else {
+                        pred = (((Q10<<7) - num) / (Q10<<8));
+                        if (Al > 0 && pred >= (1<<Al))
+                            pred = (1<<Al)-1;
+                        pred = -pred;
+                    }
+                    workspace[8] = cast(short) pred;
+                }
+                /* AC20 */
+                if ((Al=coef_bits[3+coef_offset]) !is 0 && workspace[16] is 0) {
+                    num = 9 * Q00 * (DC2 + DC8 - 2*DC5);
+                    if (num >= 0) {
+                        pred = (((Q20<<7) + num) / (Q20<<8));
+                        if (Al > 0 && pred >= (1<<Al))
+                            pred = (1<<Al)-1;
+                    } else {
+                        pred = (((Q20<<7) - num) / (Q20<<8));
+                        if (Al > 0 && pred >= (1<<Al))
+                            pred = (1<<Al)-1;
+                        pred = -pred;
+                    }
+                    workspace[16] = cast(short) pred;
+                }
+                /* AC11 */
+                if ((Al=coef_bits[4+coef_offset]) !is 0 && workspace[9] is 0) {
+                    num = 5 * Q00 * (DC1 - DC3 - DC7 + DC9);
+                    if (num >= 0) {
+                        pred = (((Q11<<7) + num) / (Q11<<8));
+                        if (Al > 0 && pred >= (1<<Al))
+                            pred = (1<<Al)-1;
+                    } else {
+                        pred = (((Q11<<7) - num) / (Q11<<8));
+                        if (Al > 0 && pred >= (1<<Al))
+                            pred = (1<<Al)-1;
+                        pred = -pred;
+                    }
+                    workspace[9] = cast(short) pred;
+                }
+                /* AC02 */
+                if ((Al=coef_bits[5+coef_offset]) !is 0 && workspace[2] is 0) {
+                    num = 9 * Q00 * (DC4 + DC6 - 2*DC5);
+                    if (num >= 0) {
+                        pred = (((Q02<<7) + num) / (Q02<<8));
+                        if (Al > 0 && pred >= (1<<Al))
+                            pred = (1<<Al)-1;
+                    } else {
+                        pred = (((Q02<<7) - num) / (Q02<<8));
+                        if (Al > 0 && pred >= (1<<Al))
+                            pred = (1<<Al)-1;
+                        pred = -pred;
+                    }
+                    workspace[2] = cast(short) pred;
+                }
+                /* OK, do the IDCT */
+                jpeg_idct_islow(cinfo, compptr, workspace, output_ptr, output_ptr_offset, output_col);
+                /* Advance for next column */
+                DC1 = DC2; DC2 = DC3;
+                DC4 = DC5; DC5 = DC6;
+                DC7 = DC8; DC8 = DC9;
+                buffer_ptr_offset++; prev_block_row_offset++; next_block_row_offset++;
+                output_col += compptr.DCT_scaled_size;
+            }
+            output_ptr_offset += compptr.DCT_scaled_size;
+        }
+    }
+
+    if (++(cinfo.output_iMCU_row) < cinfo.total_iMCU_rows)
+        return JPEG_ROW_COMPLETED;
+    return JPEG_SCAN_COMPLETED;
+}
+
+static int decompress_data (jpeg_decompress_struct cinfo, byte[][][] output_buf, int[] output_buf_offset) {
+    jpeg_d_coef_controller coef = cinfo.coef;
+    int last_iMCU_row = cinfo.total_iMCU_rows - 1;
+    int block_num;
+    int ci, block_row, block_rows;
+    short[][][] buffer;
+    short[][] buffer_ptr;
+    byte[][] output_ptr;
+    int output_col;
+    jpeg_component_info compptr;
+//  inverse_DCT_method_ptr inverse_DCT;
+
+    /* Force some input to be done if we are getting ahead of the input. */
+    while (cinfo.input_scan_number < cinfo.output_scan_number ||
+     (cinfo.input_scan_number is cinfo.output_scan_number &&
+        cinfo.input_iMCU_row <= cinfo.output_iMCU_row))
+    {
+        if (consume_input(cinfo) is JPEG_SUSPENDED)
+            return JPEG_SUSPENDED;
+    }
+
+    /* OK, output from the virtual arrays. */
+    for (ci = 0; ci < cinfo.num_components; ci++) {
+        compptr = cinfo.comp_info[ci];
+        /* Don't bother to IDCT an uninteresting component. */
+        if (! compptr.component_needed)
+            continue;
+        /* Align the virtual buffer for this component. */
+        buffer = coef.whole_image[ci];
+        int buffer_offset = cinfo.output_iMCU_row * compptr.v_samp_factor;
+        /* Count non-dummy DCT block rows in this iMCU row. */
+        if (cinfo.output_iMCU_row < last_iMCU_row)
+            block_rows = compptr.v_samp_factor;
+        else {
+            /* NB: can't use last_row_height here; it is input-side-dependent! */
+            block_rows = (compptr.height_in_blocks % compptr.v_samp_factor);
+            if (block_rows is 0) block_rows = compptr.v_samp_factor;
+        }
+//      inverse_DCT = cinfo.idct.inverse_DCT[ci];
+        output_ptr = output_buf[ci];
+        int output_ptr_offset = output_buf_offset[ci];
+        /* Loop over all DCT blocks to be processed. */
+        for (block_row = 0; block_row < block_rows; block_row++) {
+            buffer_ptr = buffer[block_row+buffer_offset];
+            int buffer_ptr_offset = 0;
+            output_col = 0;
+            for (block_num = 0; block_num < compptr.width_in_blocks; block_num++) {
+                jpeg_idct_islow(cinfo, compptr, buffer_ptr[buffer_ptr_offset], output_ptr, output_ptr_offset, output_col);
+
+                buffer_ptr_offset++;
+                output_col += compptr.DCT_scaled_size;
+            }
+            output_ptr_offset += compptr.DCT_scaled_size;
+        }
+    }
+
+    if (++(cinfo.output_iMCU_row) < cinfo.total_iMCU_rows)
+        return JPEG_ROW_COMPLETED;
+    return JPEG_SCAN_COMPLETED;
+}
+
+static void post_process_data (jpeg_decompress_struct cinfo,
+                byte[][][] input_buf, int[] input_buf_offset, int[] in_row_group_ctr,
+                int in_row_groups_avail,
+                byte[][] output_buf, int[] out_row_ctr,
+                int out_rows_avail)
+{
+    upsample(cinfo, input_buf, input_buf_offset, in_row_group_ctr, in_row_groups_avail, output_buf, out_row_ctr, out_rows_avail);
+}
+
+static void set_bottom_pointers (jpeg_decompress_struct cinfo)
+/* Change the pointer lists to duplicate the last sample row at the bottom
+ * of the image.    whichptr indicates which xbuffer holds the final iMCU row.
+ * Also sets rowgroups_avail to indicate number of nondummy row groups in row.
+ */
+{
+    jpeg_d_main_controller main = cinfo.main;
+    int ci, i, rgroup, iMCUheight, rows_left;
+    jpeg_component_info compptr;
+    byte[][] xbuf;
+
+    for (ci = 0; ci < cinfo.num_components; ci++) {
+        compptr = cinfo.comp_info[ci];
+        /* Count sample rows in one iMCU row and in one row group */
+        iMCUheight = compptr.v_samp_factor * compptr.DCT_scaled_size;
+        rgroup = iMCUheight / cinfo.min_DCT_scaled_size;
+        /* Count nondummy sample rows remaining for this component */
+        rows_left = (compptr.downsampled_height % iMCUheight);
+        if (rows_left is 0) rows_left = iMCUheight;
+        /* Count nondummy row groups.   Should get same answer for each component,
+         * so we need only do it once.
+         */
+        if (ci is 0) {
+            main.rowgroups_avail = ((rows_left-1) / rgroup + 1);
+        }
+        /* Duplicate the last real sample row rgroup*2 times; this pads out the
+         * last partial rowgroup and ensures at least one full rowgroup of context.
+         */
+        xbuf = main.xbuffer[main.whichptr][ci];
+        int xbuf_offset = main.xbuffer_offset[main.whichptr][ci];
+        for (i = 0; i < rgroup * 2; i++) {
+            xbuf[rows_left + i + xbuf_offset] = xbuf[rows_left-1 + xbuf_offset];
+        }
+    }
+}
+
+static void set_wraparound_pointers (jpeg_decompress_struct cinfo)
+/* Set up the "wraparound" pointers at top and bottom of the pointer lists.
+ * This changes the pointer list state from top-of-image to the normal state.
+ */
+{
+    jpeg_d_main_controller main = cinfo.main;
+    int ci, i, rgroup;
+    int M = cinfo.min_DCT_scaled_size;
+    jpeg_component_info compptr;
+    byte[][] xbuf0, xbuf1;
+
+    for (ci = 0; ci < cinfo.num_components; ci++) {
+        compptr = cinfo.comp_info[ci];
+        rgroup = (compptr.v_samp_factor * compptr.DCT_scaled_size) / cinfo.min_DCT_scaled_size; /* height of a row group of component */
+        xbuf0 = main.xbuffer[0][ci];
+        int xbuf0_offset = main.xbuffer_offset[0][ci];
+        xbuf1 = main.xbuffer[1][ci];
+        int xbuf1_offset = main.xbuffer_offset[1][ci];
+        for (i = 0; i < rgroup; i++) {
+            xbuf0[i - rgroup + xbuf0_offset] = xbuf0[rgroup*(M+1) + i + xbuf0_offset];
+            xbuf1[i - rgroup + xbuf1_offset] = xbuf1[rgroup*(M+1) + i + xbuf1_offset];
+            xbuf0[rgroup*(M+2) + i + xbuf0_offset] = xbuf0[i + xbuf0_offset];
+            xbuf1[rgroup*(M+2) + i + xbuf1_offset] = xbuf1[i + xbuf1_offset];
+        }
+    }
+}
+
+static void process_data_crank_post (jpeg_decompress_struct cinfo,
+    byte[][] output_buf, int[] out_row_ctr,
+    int out_rows_avail)
+{
+    error();
+}
+
+static void process_data_context_main (jpeg_decompress_struct cinfo,
+    byte[][] output_buf, int[] out_row_ctr,
+    int out_rows_avail)
+{
+    jpeg_d_main_controller main = cinfo.main;
+
+    /* Read input data if we haven't filled the main buffer yet */
+    if (! main.buffer_full) {
+        int result;
+        switch (cinfo.coef.decompress_data) {
+            case DECOMPRESS_DATA:
+                result = decompress_data(cinfo, main.xbuffer[main.whichptr], main.xbuffer_offset[main.whichptr]);
+                break;
+            case DECOMPRESS_SMOOTH_DATA:
+                result = decompress_smooth_data(cinfo, main.xbuffer[main.whichptr], main.xbuffer_offset[main.whichptr]);
+                break;
+            case DECOMPRESS_ONEPASS:
+                result = decompress_onepass(cinfo, main.xbuffer[main.whichptr], main.xbuffer_offset[main.whichptr]);
+                break;
+            default: result = 0;
+        }
+        if (result is 0)
+            return;         /* suspension forced, can do nothing more */
+        main.buffer_full = true;    /* OK, we have an iMCU row to work with */
+        main.iMCU_row_ctr++;    /* count rows received */
+    }
+
+    /* Postprocessor typically will not swallow all the input data it is handed
+     * in one call (due to filling the output buffer first).    Must be prepared
+     * to exit and restart. This switch lets us keep track of how far we got.
+     * Note that each case falls through to the next on successful completion.
+     */
+    switch (main.context_state) {
+        case CTX_POSTPONED_ROW:
+            /* Call postprocessor using previously set pointers for postponed row */
+            post_process_data (cinfo, main.xbuffer[main.whichptr], main.xbuffer_offset[main.whichptr], main.rowgroup_ctr, main.rowgroups_avail, output_buf, out_row_ctr, out_rows_avail);
+            if (main.rowgroup_ctr[0] < main.rowgroups_avail)
+                return;         /* Need to suspend */
+            main.context_state = CTX_PREPARE_FOR_IMCU;
+            if (out_row_ctr[0] >= out_rows_avail)
+                return;         /* Postprocessor exactly filled output buf */
+            /*FALLTHROUGH*/
+        case CTX_PREPARE_FOR_IMCU:
+            /* Prepare to process first M-1 row groups of this iMCU row */
+            main.rowgroup_ctr[0] = 0;
+            main.rowgroups_avail = (cinfo.min_DCT_scaled_size - 1);
+            /* Check for bottom of image: if so, tweak pointers to "duplicate"
+             * the last sample row, and adjust rowgroups_avail to ignore padding rows.
+             */
+            if (main.iMCU_row_ctr is cinfo.total_iMCU_rows)
+                set_bottom_pointers(cinfo);
+            main.context_state = CTX_PROCESS_IMCU;
+            /*FALLTHROUGH*/
+        case CTX_PROCESS_IMCU:
+            /* Call postprocessor using previously set pointers */
+            post_process_data (cinfo, main.xbuffer[main.whichptr], main.xbuffer_offset[main.whichptr], main.rowgroup_ctr, main.rowgroups_avail, output_buf, out_row_ctr, out_rows_avail);
+            if (main.rowgroup_ctr[0] < main.rowgroups_avail)
+                return;         /* Need to suspend */
+            /* After the first iMCU, change wraparound pointers to normal state */
+            if (main.iMCU_row_ctr is 1)
+                set_wraparound_pointers(cinfo);
+            /* Prepare to load new iMCU row using other xbuffer list */
+            main.whichptr ^= 1; /* 0=>1 or 1=>0 */
+            main.buffer_full = false;
+            /* Still need to process last row group of this iMCU row, */
+            /* which is saved at index M+1 of the other xbuffer */
+            main.rowgroup_ctr[0] = (cinfo.min_DCT_scaled_size + 1);
+            main.rowgroups_avail =  (cinfo.min_DCT_scaled_size + 2);
+            main.context_state = CTX_POSTPONED_ROW;
+        default:
+    }
+}
+
+static void process_data_simple_main (jpeg_decompress_struct cinfo, byte[][] output_buf, int[] out_row_ctr, int out_rows_avail) {
+    jpeg_d_main_controller main = cinfo.main;
+    int rowgroups_avail;
+
+    /* Read input data if we haven't filled the main buffer yet */
+    if (! main.buffer_full) {
+        int result;
+        switch (cinfo.coef.decompress_data) {
+            case DECOMPRESS_DATA:
+                result = decompress_data(cinfo, main.buffer, main.buffer_offset);
+                break;
+            case DECOMPRESS_SMOOTH_DATA:
+                result = decompress_smooth_data(cinfo, main.buffer, main.buffer_offset);
+                break;
+            case DECOMPRESS_ONEPASS:
+                result = decompress_onepass(cinfo, main.buffer, main.buffer_offset);
+                break;
+            default: result = 0;
+        }
+        if (result is 0)
+            return;         /* suspension forced, can do nothing more */
+        main.buffer_full = true;    /* OK, we have an iMCU row to work with */
+    }
+
+    /* There are always min_DCT_scaled_size row groups in an iMCU row. */
+    rowgroups_avail = cinfo.min_DCT_scaled_size;
+    /* Note: at the bottom of the image, we may pass extra garbage row groups
+     * to the postprocessor.    The postprocessor has to check for bottom
+     * of image anyway (at row resolution), so no point in us doing it too.
+     */
+
+    /* Feed the postprocessor */
+    post_process_data (cinfo, main.buffer, main.buffer_offset, main.rowgroup_ctr, rowgroups_avail, output_buf, out_row_ctr, out_rows_avail);
+
+    /* Has postprocessor consumed all the data yet? If so, mark buffer empty */
+    if (main.rowgroup_ctr[0] >= rowgroups_avail) {
+        main.buffer_full = false;
+        main.rowgroup_ctr[0] = 0;
+    }
+}
+
+static int jpeg_read_scanlines (jpeg_decompress_struct cinfo, byte[][] scanlines, int max_lines) {
+
+    if (cinfo.global_state !is DSTATE_SCANNING)
+        error();
+//      ERREXIT1(cinfo, JERR_BAD_STATE, cinfo.global_state);
+    if (cinfo.output_scanline >= cinfo.output_height) {
+//      WARNMS(cinfo, JWRN_TOO_MUCH_DATA);
+        return 0;
+    }
+
+    /* Call progress monitor hook if present */
+//  if (cinfo.progress !is NULL) {
+//      cinfo.progress.pass_counter = cast(long) cinfo.output_scanline;
+//      cinfo.progress.pass_limit = cast(long) cinfo.output_height;
+//      (*cinfo.progress.progress_monitor) ((j_common_ptr) cinfo);
+//  }
+
+    /* Process some data */
+    cinfo.row_ctr[0] = 0;
+    switch (cinfo.main.process_data) {
+        case PROCESS_DATA_SIMPLE_MAIN:
+            process_data_simple_main (cinfo, scanlines, cinfo.row_ctr, max_lines);
+            break;
+        case PROCESS_DATA_CONTEXT_MAIN:
+            process_data_context_main (cinfo, scanlines, cinfo.row_ctr, max_lines);
+            break;
+        case PROCESS_DATA_CRANK_POST:
+            process_data_crank_post (cinfo, scanlines, cinfo.row_ctr, max_lines);
+            break;
+        default: error();
+    }
+    cinfo.output_scanline += cinfo.row_ctr[0];
+    return cinfo.row_ctr[0];
+}
+
+
+static bool output_pass_setup (jpeg_decompress_struct cinfo) {
+    if (cinfo.global_state !is DSTATE_PRESCAN) {
+        /* First call: do pass setup */
+        prepare_for_output_pass (cinfo);
+        cinfo.output_scanline = 0;
+        cinfo.global_state = DSTATE_PRESCAN;
+    }
+    /* Loop over any required dummy passes */
+    while (cinfo.master.is_dummy_pass) {
+        error();
+//#ifdef QUANT_2PASS_SUPPORTED
+//      /* Crank through the dummy pass */
+//      while (cinfo.output_scanline < cinfo.output_height) {
+//          JDIMENSION last_scanline;
+//          /* Call progress monitor hook if present */
+//          if (cinfo.progress !is NULL) {
+//  cinfo.progress.pass_counter = cast(long) cinfo.output_scanline;
+//  cinfo.progress.pass_limit = cast(long) cinfo.output_height;
+//  (*cinfo.progress.progress_monitor) ((j_common_ptr) cinfo);
+//          }
+//          /* Process some data */
+//          last_scanline = cinfo.output_scanline;
+//          (*cinfo.main.process_data) (cinfo, (JSAMPARRAY) NULL,
+//                      &cinfo.output_scanline, (JDIMENSION) 0);
+//          if (cinfo.output_scanline is last_scanline)
+//  return FALSE;       /* No progress made, must suspend */
+//      }
+//      /* Finish up dummy pass, and set up for another one */
+//      (*cinfo.master.finish_output_pass) (cinfo);
+//      (*cinfo.master.prepare_for_output_pass) (cinfo);
+//      cinfo.output_scanline = 0;
+//#else
+//      ERREXIT(cinfo, JERR_NOT_COMPILED);
+//#endif /* QUANT_2PASS_SUPPORTED */
+    }
+    /* Ready for application to drive output pass through
+     * jpeg_read_scanlines or jpeg_read_raw_data.
+     */
+    cinfo.global_state = cinfo.raw_data_out ? DSTATE_RAW_OK : DSTATE_SCANNING;
+    return true;
+}
+
+static bool get_dht (jpeg_decompress_struct cinfo)
+/* Process a DHT marker */
+{
+    int length;
+    byte[] bits = new byte[17];
+    byte[] huffval = new byte[256];
+    int i, index, count;
+    JHUFF_TBL htblptr;
+
+    if (cinfo.bytes_offset is cinfo.bytes_in_buffer) fill_input_buffer(cinfo);
+    length = (cinfo.buffer[cinfo.bytes_offset++] & 0xFF) << 8;
+    if (cinfo.bytes_offset is cinfo.bytes_in_buffer) fill_input_buffer(cinfo);
+    length |= cinfo.buffer[cinfo.bytes_offset++] & 0xFF;
+    length -= 2;
+
+    while (length > 16) {
+        if (cinfo.bytes_offset is cinfo.bytes_in_buffer) fill_input_buffer(cinfo);
+        index = cinfo.buffer[cinfo.bytes_offset++] & 0xFF;
+
+//      TRACEMS1(cinfo, 1, JTRC_DHT, index);
+
+        bits[0] = 0;
+        count = 0;
+        for (i = 1; i <= 16; i++) {
+            if (cinfo.bytes_offset is cinfo.bytes_in_buffer) fill_input_buffer(cinfo);
+        bits[i] = cinfo.buffer[cinfo.bytes_offset++];
+            count += bits[i] & 0xFF;
+        }
+
+        length -= 1 + 16;
+
+//      TRACEMS8(cinfo, 2, JTRC_HUFFBITS,
+//           bits[1], bits[2], bits[3], bits[4],
+//           bits[5], bits[6], bits[7], bits[8]);
+//      TRACEMS8(cinfo, 2, JTRC_HUFFBITS,
+//           bits[9], bits[10], bits[11], bits[12],
+//           bits[13], bits[14], bits[15], bits[16]);
+
+        /* Here we just do minimal validation of the counts to avoid walking
+         * off the end of our table space.  jdhuff.c will check more carefully.
+         */
+        if (count > 256 || (count) > length)
+            error();
+//          ERREXIT(cinfo, JERR_BAD_HUFF_TABLE);
+
+        for (i = 0; i < count; i++) {
+            if (cinfo.bytes_offset is cinfo.bytes_in_buffer) fill_input_buffer(cinfo);
+            huffval[i] = cinfo.buffer[cinfo.bytes_offset++];
+        }
+
+        length -= count;
+
+        if ((index & 0x10) !is 0) {      /* AC table definition */
+            index -= 0x10;
+            htblptr = cinfo.ac_huff_tbl_ptrs[index] = new JHUFF_TBL();
+        } else {            /* DC table definition */
+            htblptr = cinfo.dc_huff_tbl_ptrs[index] = new JHUFF_TBL();
+        }
+
+        if (index < 0 || index >= NUM_HUFF_TBLS)
+            error();
+//          ERREXIT1(cinfo, JERR_DHT_INDEX, index);
+
+        System.arraycopy(bits, 0, htblptr.bits, 0, bits.length);
+        System.arraycopy(huffval, 0, htblptr.huffval, 0, huffval.length);
+    }
+
+    if (length !is 0)
+        error();
+//      ERREXIT(cinfo, JERR_BAD_LENGTH);
+
+    return true;
+}
+
+
+static bool get_dqt (jpeg_decompress_struct cinfo)
+/* Process a DQT marker */
+{
+    int length;
+    int n, i, prec;
+    int tmp;
+    JQUANT_TBL quant_ptr;
+
+    if (cinfo.bytes_offset is cinfo.bytes_in_buffer) fill_input_buffer(cinfo);
+    length = (cinfo.buffer[cinfo.bytes_offset++] & 0xFF) << 8;
+    if (cinfo.bytes_offset is cinfo.bytes_in_buffer) fill_input_buffer(cinfo);
+    length |= cinfo.buffer[cinfo.bytes_offset++] & 0xFF;
+    length -= 2;
+
+    while (length > 0) {
+        if (cinfo.bytes_offset is cinfo.bytes_in_buffer) fill_input_buffer(cinfo);
+    n = cinfo.buffer[cinfo.bytes_offset++] & 0xFF;
+        prec = n >> 4;
+        n &= 0x0F;
+
+//      TRACEMS2(cinfo, 1, JTRC_DQT, n, prec);
+
+        if (n >= NUM_QUANT_TBLS)
+            error();
+//          ERREXIT1(cinfo, JERR_DQT_INDEX, n);
+
+        if (cinfo.quant_tbl_ptrs[n] is null)
+            cinfo.quant_tbl_ptrs[n] = new JQUANT_TBL();
+        quant_ptr = cinfo.quant_tbl_ptrs[n];
+
+        for (i = 0; i < DCTSIZE2; i++) {
+            if (prec !is 0) {
+                if (cinfo.bytes_offset is cinfo.bytes_in_buffer) fill_input_buffer(cinfo);
+                tmp = (cinfo.buffer[cinfo.bytes_offset++] & 0xFF) << 8;
+                if (cinfo.bytes_offset is cinfo.bytes_in_buffer) fill_input_buffer(cinfo);
+                tmp |= cinfo.buffer[cinfo.bytes_offset++] & 0xFF;
+            } else {
+                    if (cinfo.bytes_offset is cinfo.bytes_in_buffer) fill_input_buffer(cinfo);
+                tmp = cinfo.buffer[cinfo.bytes_offset++] & 0xFF;
+            }
+            /* We convert the zigzag-order table to natural array order. */
+            quant_ptr.quantval[jpeg_natural_order[i]] = cast(short) tmp;
+        }
+
+//      if (cinfo.err.trace_level >= 2) {
+//          for (i = 0; i < DCTSIZE2; i += 8) {
+//              TRACEMS8(cinfo, 2, JTRC_QUANTVALS,
+//                  quant_ptr.quantval[i],   quant_ptr.quantval[i+1],
+//                   quant_ptr.quantval[i+2], quant_ptr.quantval[i+3],
+//                   quant_ptr.quantval[i+4], quant_ptr.quantval[i+5],
+//                   quant_ptr.quantval[i+6], quant_ptr.quantval[i+7]);
+//          }
+//      }
+
+        length -= (DCTSIZE2+1);
+        if (prec !is 0) length -= DCTSIZE2;
+    }
+
+    if (length !is 0)
+        error();
+//      ERREXIT(cinfo, JERR_BAD_LENGTH);
+
+    return true;
+}
+
+static bool get_dri (jpeg_decompress_struct cinfo)
+/* Process a DRI marker */
+{
+    int length;
+    int tmp;
+
+    if (cinfo.bytes_offset is cinfo.bytes_in_buffer) fill_input_buffer(cinfo);
+    length = (cinfo.buffer[cinfo.bytes_offset++] & 0xFF) << 8;
+    if (cinfo.bytes_offset is cinfo.bytes_in_buffer) fill_input_buffer(cinfo);
+    length |= cinfo.buffer[cinfo.bytes_offset++] & 0xFF;
+
+    if (length !is 4)
+    error();
+//      ERREXIT(cinfo, JERR_BAD_LENGTH);
+
+    if (cinfo.bytes_offset is cinfo.bytes_in_buffer) fill_input_buffer(cinfo);
+    tmp = (cinfo.buffer[cinfo.bytes_offset++] & 0xFF) << 8;
+    if (cinfo.bytes_offset is cinfo.bytes_in_buffer) fill_input_buffer(cinfo);
+    tmp |= cinfo.buffer[cinfo.bytes_offset++] & 0xFF;
+
+//  TRACEMS1(cinfo, 1, JTRC_DRI, tmp);
+
+    cinfo.restart_interval = tmp;
+
+    return true;
+}
+
+static bool get_dac (jpeg_decompress_struct cinfo)
+/* Process a DAC marker */
+{
+    int length;
+    int index, val;
+    if (cinfo.bytes_offset is cinfo.bytes_in_buffer) fill_input_buffer(cinfo);
+    length = (cinfo.buffer[cinfo.bytes_offset++] & 0xFF) << 8;
+    if (cinfo.bytes_offset is cinfo.bytes_in_buffer) fill_input_buffer(cinfo);
+    length |= cinfo.buffer[cinfo.bytes_offset++] & 0xFF;
+    length -= 2;
+
+    while (length > 0) {
+        if (cinfo.bytes_offset is cinfo.bytes_in_buffer) fill_input_buffer(cinfo);
+        index = cinfo.buffer[cinfo.bytes_offset++] & 0xFF;
+        if (cinfo.bytes_offset is cinfo.bytes_in_buffer) fill_input_buffer(cinfo);
+        val = cinfo.buffer[cinfo.bytes_offset++] & 0xFF;
+
+        length -= 2;
+
+//      TRACEMS2(cinfo, 1, JTRC_DAC, index, val);
+
+        if (index < 0 || index >= (2*NUM_ARITH_TBLS))
+            error();
+//          ERREXIT1(cinfo, JERR_DAC_INDEX, index);
+
+        if (index >= NUM_ARITH_TBLS) { /* define AC table */
+            cinfo.arith_ac_K[index-NUM_ARITH_TBLS] = cast(byte) val;
+        } else {            /* define DC table */
+            cinfo.arith_dc_L[index] = cast(byte) (val & 0x0F);
+            cinfo.arith_dc_U[index] = cast(byte) (val >> 4);
+            if (cinfo.arith_dc_L[index] > cinfo.arith_dc_U[index])
+                error();
+//  ERREXIT1(cinfo, JERR_DAC_VALUE, val);
+        }
+    }
+
+    if (length !is 0)
+        error();
+//      ERREXIT(cinfo, JERR_BAD_LENGTH);
+
+    return true;
+}
+
+
+static bool get_sos (jpeg_decompress_struct cinfo)
+/* Process a SOS marker */
+{
+    int length;
+    int i, ci, n, c, cc;
+    jpeg_component_info compptr = null;
+
+    if (! cinfo.marker.saw_SOF)
+        error();
+//      ERREXIT(cinfo, JERR_SOS_NO_SOF);
+
+    if (cinfo.bytes_offset is cinfo.bytes_in_buffer) fill_input_buffer(cinfo);
+    length = (cinfo.buffer[cinfo.bytes_offset++] & 0xFF) << 8;
+    if (cinfo.bytes_offset is cinfo.bytes_in_buffer) fill_input_buffer(cinfo);
+    length |= cinfo.buffer[cinfo.bytes_offset++] & 0xFF;
+
+    if (cinfo.bytes_offset is cinfo.bytes_in_buffer) fill_input_buffer(cinfo);
+    n = cinfo.buffer[cinfo.bytes_offset++] & 0xFF;
+
+//  TRACEMS1(cinfo, 1, JTRC_SOS, n);
+
+    if (length !is (n * 2 + 6) || n < 1 || n > MAX_COMPS_IN_SCAN)
+        error();
+//      ERREXIT(cinfo, JERR_BAD_LENGTH);
+
+    cinfo.comps_in_scan = n;
+
+    /* Collect the component-spec parameters */
+
+    for (i = 0; i < n; i++) {
+        if (cinfo.bytes_offset is cinfo.bytes_in_buffer) fill_input_buffer(cinfo);
+        cc = cinfo.buffer[cinfo.bytes_offset++] & 0xFF;
+        if (cinfo.bytes_offset is cinfo.bytes_in_buffer) fill_input_buffer(cinfo);
+        c = cinfo.buffer[cinfo.bytes_offset++] & 0xFF;
+
+        for (ci = 0; ci < cinfo.num_components; ci++) {
+            compptr = cinfo.comp_info[ci];
+            if (cc is compptr.component_id)
+                break;
+        }
+
+        if (ci is cinfo.num_components)
+            error();
+//          ERREXIT1(cinfo, JERR_BAD_COMPONENT_ID, cc);
+
+        cinfo.cur_comp_info[i] = compptr;
+        compptr.dc_tbl_no = (c >> 4) & 15;
+        compptr.ac_tbl_no = (c       ) & 15;
+
+//      TRACEMS3(cinfo, 1, JTRC_SOS_COMPONENT, cc, compptr.dc_tbl_no, compptr.ac_tbl_no);
+    }
+
+    /* Collect the additional scan parameters Ss, Se, Ah/Al. */
+    if (cinfo.bytes_offset is cinfo.bytes_in_buffer) fill_input_buffer(cinfo);
+    c = cinfo.buffer[cinfo.bytes_offset++] & 0xFF;
+    cinfo.Ss = c;
+    if (cinfo.bytes_offset is cinfo.bytes_in_buffer) fill_input_buffer(cinfo);
+    c = cinfo.buffer[cinfo.bytes_offset++] & 0xFF;
+    cinfo.Se = c;
+    if (cinfo.bytes_offset is cinfo.bytes_in_buffer) fill_input_buffer(cinfo);
+    c = cinfo.buffer[cinfo.bytes_offset++] & 0xFF;
+    cinfo.Ah = (c >> 4) & 15;
+    cinfo.Al = (c        ) & 15;
+
+//  TRACEMS4(cinfo, 1, JTRC_SOS_PARAMS, cinfo.Ss, cinfo.Se, cinfo.Ah, cinfo.Al);
+
+    /* Prepare to scan data & restart markers */
+    cinfo.marker.next_restart_num = 0;
+
+    /* Count another SOS marker */
+    cinfo.input_scan_number++;
+
+    return true;
+}
+
+static bool get_sof (jpeg_decompress_struct cinfo, bool is_prog, bool is_arith) {
+    int length;
+    int c, ci;
+
+    cinfo.progressive_mode = is_prog;
+    cinfo.arith_code = is_arith;
+
+    if (cinfo.bytes_offset is cinfo.bytes_in_buffer) fill_input_buffer(cinfo);
+    length = (cinfo.buffer[cinfo.bytes_offset++] & 0xFF) << 8;
+    if (cinfo.bytes_offset is cinfo.bytes_in_buffer) fill_input_buffer(cinfo);
+    length |= cinfo.buffer[cinfo.bytes_offset++] & 0xFF;
+
+    if (cinfo.bytes_offset is cinfo.bytes_in_buffer) fill_input_buffer(cinfo);
+    cinfo.data_precision = cinfo.buffer[cinfo.bytes_offset++] & 0xFF;
+
+    if (cinfo.bytes_offset is cinfo.bytes_in_buffer) fill_input_buffer(cinfo);
+    cinfo.image_height = (cinfo.buffer[cinfo.bytes_offset++] & 0xFF) << 8;
+    if (cinfo.bytes_offset is cinfo.bytes_in_buffer) fill_input_buffer(cinfo);
+    cinfo.image_height |= cinfo.buffer[cinfo.bytes_offset++] & 0xFF;
+
+    if (cinfo.bytes_offset is cinfo.bytes_in_buffer) fill_input_buffer(cinfo);
+    cinfo.image_width = (cinfo.buffer[cinfo.bytes_offset++] & 0xFF) << 8;
+    if (cinfo.bytes_offset is cinfo.bytes_in_buffer) fill_input_buffer(cinfo);
+    cinfo.image_width |= cinfo.buffer[cinfo.bytes_offset++] & 0xFF;
+
+    if (cinfo.bytes_offset is cinfo.bytes_in_buffer) fill_input_buffer(cinfo);
+    cinfo.num_components = cinfo.buffer[cinfo.bytes_offset++] & 0xFF;
+
+    length -= 8;
+
+//  TRACEMS4(cinfo, 1, JTRC_SOF, cinfo.unread_marker,
+//       cast(int) cinfo.image_width, cast(int) cinfo.image_height,
+//       cinfo.num_components);
+
+    if (cinfo.marker.saw_SOF)
+        error();
+//      ERREXIT(cinfo, JERR_SOF_DUPLICATE);
+
+    /* We don't support files in which the image height is initially specified */
+    /* as 0 and is later redefined by DNL.  As long as we have to check that,   */
+    /* might as well have a general sanity check. */
+    if (cinfo.image_height <= 0 || cinfo.image_width <= 0 || cinfo.num_components <= 0)
+        error();
+//      ERREXIT(cinfo, JERR_EMPTY_IMAGE);
+
+    if (length !is (cinfo.num_components * 3))
+        error();
+//      ERREXIT(cinfo, JERR_BAD_LENGTH);
+
+    if (cinfo.comp_info is null)    /* do only once, even if suspend */
+        cinfo.comp_info = new jpeg_component_info[cinfo.num_components];
+
+    for (ci = 0; ci < cinfo.num_components; ci++) {
+        jpeg_component_info compptr = cinfo.comp_info[ci] = new jpeg_component_info();
+        compptr.component_index = ci;
+        if (cinfo.bytes_offset is cinfo.bytes_in_buffer) fill_input_buffer(cinfo);
+        compptr.component_id = cinfo.buffer[cinfo.bytes_offset++] & 0xFF;
+        if (cinfo.bytes_offset is cinfo.bytes_in_buffer) fill_input_buffer(cinfo);
+        c = cinfo.buffer[cinfo.bytes_offset++] & 0xFF;
+        compptr.h_samp_factor = (c >> 4) & 15;
+        compptr.v_samp_factor = (c       ) & 15;
+        if (cinfo.bytes_offset is cinfo.bytes_in_buffer) fill_input_buffer(cinfo);
+        compptr.quant_tbl_no = cinfo.buffer[cinfo.bytes_offset++] & 0xFF;
+
+//      TRACEMS4(cinfo, 1, JTRC_SOF_COMPONENT,
+//           compptr.component_id, compptr.h_samp_factor,
+//           compptr.v_samp_factor, compptr.quant_tbl_no);
+    }
+
+    cinfo.marker.saw_SOF = true;
+
+    return true;
+}
+
+static void sep_upsample (jpeg_decompress_struct cinfo, byte[][][] input_buf, int[] input_buf_offset,
+        int[] in_row_group_ctr, int in_row_groups_avail,
+        byte[][] output_buf, int[] out_row_ctr, int out_rows_avail)
+{
+    jpeg_upsampler upsample = cinfo.upsample;
+    int ci;
+    jpeg_component_info compptr;
+    int num_rows;
+
+    /* Fill the conversion buffer, if it's empty */
+    if (upsample.next_row_out >= cinfo.max_v_samp_factor) {
+        for (ci = 0; ci < cinfo.num_components; ci++) {
+            compptr = cinfo.comp_info[ci];
+            /* Invoke per-component upsample method.    Notice we pass a POINTER
+             * to color_buf[ci], so that fullsize_upsample can change it.
+             */
+            int offset = input_buf_offset[ci] + (in_row_group_ctr[0] * upsample.rowgroup_height[ci]);
+            switch (upsample.methods[ci]) {
+                case NOOP_UPSAMPLE: noop_upsample(cinfo, compptr, input_buf[ci], offset, upsample.color_buf, upsample.color_buf_offset, ci); break;
+                case FULLSIZE_UPSAMPLE: fullsize_upsample(cinfo, compptr, input_buf[ci], offset, upsample.color_buf, upsample.color_buf_offset, ci); break;
+                case H2V1_FANCY_UPSAMPLE: h2v1_fancy_upsample(cinfo, compptr, input_buf[ci], offset, upsample.color_buf, upsample.color_buf_offset, ci); break;
+                case H2V1_UPSAMPLE: h2v1_upsample(cinfo, compptr, input_buf[ci], offset, upsample.color_buf, upsample.color_buf_offset, ci); break;
+                case H2V2_FANCY_UPSAMPLE: h2v2_fancy_upsample(cinfo, compptr, input_buf[ci], offset, upsample.color_buf, upsample.color_buf_offset, ci); break;
+                case H2V2_UPSAMPLE: h2v2_upsample(cinfo, compptr, input_buf[ci], offset, upsample.color_buf, upsample.color_buf_offset, ci); break;
+                case INT_UPSAMPLE: int_upsample(cinfo, compptr, input_buf[ci], offset, upsample.color_buf, upsample.color_buf_offset, ci); break;
+            default:
+            }
+        }
+        upsample.next_row_out = 0;
+    }
+
+    /* Color-convert and emit rows */
+
+    /* How many we have in the buffer: */
+    num_rows =  (cinfo.max_v_samp_factor - upsample.next_row_out);
+    /* Not more than the distance to the end of the image.  Need this test
+     * in case the image height is not a multiple of max_v_samp_factor:
+     */
+    if (num_rows > upsample.rows_to_go)
+        num_rows = upsample.rows_to_go;
+    /* And not more than what the client can accept: */
+    out_rows_avail -= out_row_ctr[0];
+    if (num_rows > out_rows_avail)
+        num_rows = out_rows_avail;
+
+    switch (cinfo.cconvert.color_convert) {
+        case NULL_CONVERT: null_convert (cinfo, upsample.color_buf, upsample.color_buf_offset, upsample.next_row_out, output_buf, out_row_ctr[0], num_rows); break;
+        case GRAYSCALE_CONVERT: grayscale_convert (cinfo, upsample.color_buf, upsample.color_buf_offset, upsample.next_row_out, output_buf, out_row_ctr[0], num_rows); break;
+        case YCC_RGB_CONVERT: ycc_rgb_convert (cinfo, upsample.color_buf, upsample.color_buf_offset, upsample.next_row_out, output_buf, out_row_ctr[0], num_rows); break;
+        case GRAY_RGB_CONVERT: gray_rgb_convert (cinfo, upsample.color_buf, upsample.color_buf_offset, upsample.next_row_out, output_buf, out_row_ctr[0], num_rows); break;
+        case YCCK_CMYK_CONVERT: error(); break;
+            default:
+    }
+
+    /* Adjust counts */
+    out_row_ctr[0] += num_rows;
+    upsample.rows_to_go -= num_rows;
+    upsample.next_row_out += num_rows;
+    /* When the buffer is emptied, declare this input row group consumed */
+    if (upsample.next_row_out >= cinfo.max_v_samp_factor) {
+        in_row_group_ctr[0]++;
+    }
+}
+
+static void noop_upsample (jpeg_decompress_struct cinfo, jpeg_component_info compptr,
+     byte[][] input_data, int input_data_offset, byte[][][] output_data_ptr, int[] output_data_offset, int output_data_index)
+{
+    output_data_ptr[output_data_index] = null;  /* safety check */
+}
+
+static void fullsize_upsample (jpeg_decompress_struct cinfo, jpeg_component_info compptr,
+     byte[][] input_data, int input_data_offset, byte[][][] output_data_ptr, int[] output_data_offset, int output_data_index)
+{
+    output_data_ptr[output_data_index] = input_data;
+    output_data_offset[output_data_index] = input_data_offset;
+}
+
+static void h2v1_upsample (jpeg_decompress_struct cinfo, jpeg_component_info compptr,
+     byte[][] input_data, int input_data_offset, byte[][][] output_data_ptr, int[] output_data_offset, int output_data_index)
+{
+    byte[][] output_data = output_data_ptr[output_data_index];
+    byte[] inptr, outptr;
+    byte invalue;
+    int outend;
+    int inrow;
+    output_data_offset[output_data_index] = 0;
+
+    for (inrow = 0; inrow < cinfo.max_v_samp_factor; inrow++) {
+        inptr = input_data[inrow+input_data_offset];
+        outptr = output_data[inrow];
+        int inptr_offset = 0, outptr_offset = 0;
+        outend = outptr_offset + cinfo.output_width;
+        while (outptr_offset < outend) {
+            invalue = inptr[inptr_offset++];    /* don't need GETJSAMPLE() here */
+            outptr[outptr_offset++] = invalue;
+            outptr[outptr_offset++] = invalue;
+        }
+    }
+}
+
+static void h2v2_upsample (jpeg_decompress_struct cinfo, jpeg_component_info compptr,
+    byte[][] input_data, int input_data_offset, byte[][][] output_data_ptr, int[] output_data_offset, int output_data_index)
+{
+    byte[][] output_data = output_data_ptr[output_data_index];
+    byte[] inptr, outptr;
+    byte invalue;
+    int outend;
+    int inrow, outrow;
+    output_data_offset[output_data_index] = 0;
+
+    inrow = outrow = 0;
+    while (outrow < cinfo.max_v_samp_factor) {
+        inptr = input_data[inrow+input_data_offset];
+        outptr = output_data[outrow];
+        int inptr_offset = 0, outptr_offset = 0;
+        outend = outptr_offset + cinfo.output_width;
+        while (outptr_offset < outend) {
+            invalue = inptr[inptr_offset++];    /* don't need GETJSAMPLE() here */
+            outptr[outptr_offset++] = invalue;
+            outptr[outptr_offset++] = invalue;
+        }
+        jcopy_sample_rows(output_data, outrow, output_data, outrow+1, 1, cinfo.output_width);
+        inrow++;
+        outrow += 2;
+    }
+}
+
+static void h2v1_fancy_upsample (jpeg_decompress_struct cinfo, jpeg_component_info compptr,
+     byte[][] input_data, int input_data_offset, byte[][][] output_data_ptr, int[] output_data_offset, int output_data_index)
+{
+    byte[][] output_data = output_data_ptr[output_data_index];
+    byte[] inptr, outptr;
+    int invalue;
+    int colctr;
+    int inrow;
+    output_data_offset[output_data_index] = 0;
+
+    for (inrow = 0; inrow < cinfo.max_v_samp_factor; inrow++) {
+        inptr = input_data[inrow+input_data_offset];
+        outptr = output_data[inrow];
+        int inptr_offset = 0, outptr_offset = 0;
+        /* Special case for first column */
+        invalue = inptr[inptr_offset++] & 0xFF;
+        outptr[outptr_offset++] = cast(byte) invalue;
+        outptr[outptr_offset++] = cast(byte) ((invalue * 3 + (inptr[inptr_offset] & 0xFF) + 2) >> 2);
+
+        for (colctr = compptr.downsampled_width - 2; colctr > 0; colctr--) {
+            /* General case: 3/4 * nearer pixel + 1/4 * further pixel */
+            invalue = (inptr[inptr_offset++] & 0xFF) * 3;
+            outptr[outptr_offset++] = cast(byte) ((invalue + (inptr[inptr_offset-2] & 0xFF) + 1) >> 2);
+            outptr[outptr_offset++] = cast(byte) ((invalue + (inptr[inptr_offset] & 0xFF) + 2) >> 2);
+        }
+
+        /* Special case for last column */
+        invalue = (inptr[inptr_offset] & 0xFF);
+        outptr[outptr_offset++] = cast(byte) ((invalue * 3 + (inptr[inptr_offset-1] & 0xFF) + 1) >> 2);
+        outptr[outptr_offset++] = cast(byte) invalue;
+    }
+}
+
+static void h2v2_fancy_upsample (jpeg_decompress_struct cinfo, jpeg_component_info compptr,
+    byte[][] input_data, int input_data_offset, byte[][][] output_data_ptr, int[] output_data_offset, int output_data_index)
+{
+    byte[][] output_data = output_data_ptr[output_data_index];
+    byte[] inptr0, inptr1, outptr;
+    int thiscolsum, lastcolsum, nextcolsum;
+    int colctr;
+    int inrow, outrow, v;
+    output_data_offset[output_data_index] = 0;
+
+    inrow = outrow = 0;
+    while (outrow < cinfo.max_v_samp_factor) {
+        for (v = 0; v < 2; v++) {
+            /* inptr0 points to nearest input row, inptr1 points to next nearest */
+            inptr0 = input_data[inrow+input_data_offset];
+            if (v is 0)     /* next nearest is row above */
+                inptr1 = input_data[inrow-1+input_data_offset];
+            else            /* next nearest is row below */
+                inptr1 = input_data[inrow+1+input_data_offset];
+            outptr = output_data[outrow++];
+
+            int inptr0_offset = 0, inptr1_offset = 0, outptr_offset = 0;
+
+            /* Special case for first column */
+            thiscolsum = (inptr0[inptr0_offset++] & 0xFF) * 3 + (inptr1[inptr1_offset++] & 0xFF);
+            nextcolsum = (inptr0[inptr0_offset++] & 0xFF) * 3 + (inptr1[inptr1_offset++] & 0xFF);
+            outptr[outptr_offset++] = cast(byte) ((thiscolsum * 4 + 8) >> 4);
+            outptr[outptr_offset++] = cast(byte) ((thiscolsum * 3 + nextcolsum + 7) >> 4);
+            lastcolsum = thiscolsum; thiscolsum = nextcolsum;
+
+            for (colctr = compptr.downsampled_width - 2; colctr > 0; colctr--) {
+                /* General case: 3/4 * nearer pixel + 1/4 * further pixel in each */
+                /* dimension, thus 9/16, 3/16, 3/16, 1/16 overall */
+                nextcolsum = (inptr0[inptr0_offset++] & 0xFF) * 3 + (inptr1[inptr1_offset++] & 0xFF);
+                outptr[outptr_offset++] = cast(byte) ((thiscolsum * 3 + lastcolsum + 8) >> 4);
+                outptr[outptr_offset++] = cast(byte) ((thiscolsum * 3 + nextcolsum + 7) >> 4);
+                lastcolsum = thiscolsum; thiscolsum = nextcolsum;
+            }
+
+            /* Special case for last column */
+            outptr[outptr_offset++] = cast(byte) ((thiscolsum * 3 + lastcolsum + 8) >> 4);
+            outptr[outptr_offset++] = cast(byte) ((thiscolsum * 4 + 7) >> 4);
+        }
+        inrow++;
+    }
+}
+
+static void int_upsample (jpeg_decompress_struct cinfo, jpeg_component_info compptr,
+     byte[][] input_data, int input_data_offset, byte[][][] output_data_ptr, int[] output_data_offset, int output_data_index)
+{
+    jpeg_upsampler upsample = cinfo.upsample;
+    byte[][] output_data = output_data_ptr[output_data_index];
+    byte[] inptr, outptr;
+    byte invalue;
+    int h;
+    int outend;
+    int h_expand, v_expand;
+    int inrow, outrow;
+    output_data_offset[output_data_index] = 0;
+
+    h_expand = upsample.h_expand[compptr.component_index];
+    v_expand = upsample.v_expand[compptr.component_index];
+
+    inrow = outrow = 0;
+    while (outrow < cinfo.max_v_samp_factor) {
+        /* Generate one output row with proper horizontal expansion */
+        inptr = input_data[inrow+input_data_offset];
+        int inptr_offset = 0;
+        outptr = output_data[outrow];
+        int outptr_offset = 0;
+        outend = outptr_offset + cinfo.output_width;
+        while (outptr_offset < outend) {
+            invalue = inptr[inptr_offset++];    /* don't need GETJSAMPLE() here */
+            for (h = h_expand; h > 0; h--) {
+                outptr[outptr_offset++] = invalue;
+            }
+        }
+        /* Generate any additional output rows by duplicating the first one */
+        if (v_expand > 1) {
+            jcopy_sample_rows(output_data, outrow, output_data, outrow+1, v_expand-1, cinfo.output_width);
+        }
+        inrow++;
+        outrow += v_expand;
+    }
+}
+
+static void null_convert (jpeg_decompress_struct cinfo,
+    byte[][][] input_buf, int[] input_buf_offset, int input_row,
+    byte[][] output_buf, int output_buf_offset, int num_rows)
+{
+    byte[] inptr, outptr;
+    int count;
+    int num_components = cinfo.num_components;
+    int num_cols = cinfo.output_width;
+    int ci;
+
+    while (--num_rows >= 0) {
+        for (ci = 0; ci < num_components; ci++) {
+            inptr = input_buf[ci][input_row+input_buf_offset[0]];
+            outptr = output_buf[output_buf_offset];
+            /* BGR instead of RGB */
+            int offset = 0;
+            switch (ci) {
+                case 2: offset = RGB_BLUE; break;
+                case 1: offset = RGB_GREEN; break;
+                case 0: offset = RGB_RED; break;
+            default:
+            }
+            int outptr_offset = offset, inptr_offset = 0;
+            for (count = num_cols; count > 0; count--) {
+                outptr[outptr_offset] = inptr[inptr_offset++];  /* needn't bother with GETJSAMPLE() here */
+                outptr_offset += num_components;
+            }
+        }
+        input_row++;
+        output_buf_offset++;
+    }
+}
+
+static void grayscale_convert (jpeg_decompress_struct cinfo,
+    byte[][][] input_buf, int[] input_buf_offset, int input_row,
+    byte[][] output_buf, int output_buf_offset, int num_rows)
+{
+  jcopy_sample_rows(input_buf[0], input_row+input_buf_offset[0], output_buf, output_buf_offset,
+            num_rows, cinfo.output_width);
+}
+
+static void gray_rgb_convert (jpeg_decompress_struct cinfo,
+    byte[][][] input_buf, int[] input_buf_offset, int input_row,
+    byte[][] output_buf, int output_buf_offset, int num_rows)
+{
+    byte[] inptr, outptr;
+    int col;
+    int num_cols = cinfo.output_width;
+
+    while (--num_rows >= 0) {
+        inptr = input_buf[0][input_row+++input_buf_offset[0]];
+        outptr = output_buf[output_buf_offset++];
+        int outptr_offset = 0;
+        for (col = 0; col < num_cols; col++) {
+            /* We can dispense with GETJSAMPLE() here */
+            outptr[RGB_RED+outptr_offset] = outptr[RGB_GREEN+outptr_offset] = outptr[RGB_BLUE+outptr_offset] = inptr[col];
+            outptr_offset += RGB_PIXELSIZE;
+        }
+    }
+}
+
+static void ycc_rgb_convert (jpeg_decompress_struct cinfo,
+    byte[][][] input_buf, int[] input_buf_offset, int input_row,
+    byte[][] output_buf, int output_buf_offset, int num_rows)
+{
+    jpeg_color_deconverter cconvert = cinfo.cconvert;
+    int y, cb, cr;
+    byte[] outptr;
+    byte[] inptr0, inptr1, inptr2;
+    int col;
+    int num_cols = cinfo.output_width;
+    /* copy these pointers into registers if possible */
+    byte[] range_limit = cinfo.sample_range_limit;
+    int range_limit_offset = cinfo.sample_range_limit_offset;
+    int[] Crrtab = cconvert.Cr_r_tab;
+    int[] Cbbtab = cconvert.Cb_b_tab;
+    int[] Crgtab = cconvert.Cr_g_tab;
+    int[] Cbgtab = cconvert.Cb_g_tab;
+//      SHIFT_TEMPS
+
+    while (--num_rows >= 0) {
+        inptr0 = input_buf[0][input_row+input_buf_offset[0]];
+        inptr1 = input_buf[1][input_row+input_buf_offset[1]];
+        inptr2 = input_buf[2][input_row+input_buf_offset[2]];
+        input_row++;
+        outptr = output_buf[output_buf_offset++];
+        int outptr_offset = 0;
+        for (col = 0; col < num_cols; col++) {
+            y = (inptr0[col] & 0xFF);
+            cb = (inptr1[col] & 0xFF);
+            cr = (inptr2[col] & 0xFF);
+            /* Range-limiting is essential due to noise introduced by DCT losses. */
+            outptr[outptr_offset + RGB_RED] =   range_limit[y + Crrtab[cr] + range_limit_offset];
+            outptr[outptr_offset + RGB_GREEN] = range_limit[y + ((Cbgtab[cb] + Crgtab[cr]>>SCALEBITS)) + range_limit_offset];
+            outptr[outptr_offset + RGB_BLUE] =  range_limit[y + Cbbtab[cb] + range_limit_offset];
+            outptr_offset += RGB_PIXELSIZE;
+        }
+    }
+}
+
+static bool process_APPn(int n, jpeg_decompress_struct cinfo) {
+    if (n is 0 || n is 14) {
+        return get_interesting_appn(cinfo);
+    }
+    return skip_variable(cinfo);
+}
+
+static bool process_COM(jpeg_decompress_struct cinfo) {
+    return skip_variable(cinfo);
+}
+
+static void skip_input_data (jpeg_decompress_struct cinfo, int num_bytes) {
+    if (num_bytes > 0) {
+        while (num_bytes > cinfo.bytes_in_buffer - cinfo.bytes_offset) {
+            num_bytes -= cinfo.bytes_in_buffer - cinfo.bytes_offset;
+            if (!fill_input_buffer(cinfo)) error();
+            /* note we assume that fill_input_buffer will never return FALSE,
+             * so suspension need not be handled.
+             */
+        }
+        cinfo.bytes_offset += num_bytes;
+    }
+}
+
+static bool skip_variable (jpeg_decompress_struct cinfo)
+/* Skip over an unknown or uninteresting variable-length marker */
+{
+    int length;
+
+    if (cinfo.bytes_offset is cinfo.bytes_in_buffer) fill_input_buffer(cinfo);
+    length = (cinfo.buffer[cinfo.bytes_offset++] & 0xFF) << 8;
+    if (cinfo.bytes_offset is cinfo.bytes_in_buffer) fill_input_buffer(cinfo);
+    length |= cinfo.buffer[cinfo.bytes_offset++] & 0xFF;
+
+    length -= 2;
+
+//  TRACEMS2(cinfo, 1, JTRC_MISC_MARKER, cinfo.unread_marker, cast(int) length);
+
+    if (length > 0) {
+        skip_input_data (cinfo, length);
+    }
+
+    return true;
+}
+
+static bool get_interesting_appn (jpeg_decompress_struct cinfo)
+/* Process an APP0 or APP14 marker without saving it */
+{
+    int length;
+    byte[] b = new byte[APPN_DATA_LEN];
+    int i, numtoread;
+
+    if (cinfo.bytes_offset is cinfo.bytes_in_buffer) fill_input_buffer(cinfo);
+    length = (cinfo.buffer[cinfo.bytes_offset++] & 0xFF) << 8;
+    if (cinfo.bytes_offset is cinfo.bytes_in_buffer) fill_input_buffer(cinfo);
+    length |= cinfo.buffer[cinfo.bytes_offset++] & 0xFF;
+    length -= 2;
+
+    /* get the interesting part of the marker data */
+    if (length >= APPN_DATA_LEN)
+        numtoread = APPN_DATA_LEN;
+    else if (length > 0)
+        numtoread = length;
+    else
+        numtoread = 0;
+    for (i = 0; i < numtoread; i++) {
+        if (cinfo.bytes_offset is cinfo.bytes_in_buffer) fill_input_buffer(cinfo);
+        b[i] = cinfo.buffer[cinfo.bytes_offset++];
+    }
+    length -= numtoread;
+
+    /* process it */
+    switch (cinfo.unread_marker) {
+        case M_APP0:
+            examine_app0(cinfo, b, numtoread, length);
+            break;
+        case M_APP14:
+            examine_app14(cinfo, b, numtoread, length);
+            break;
+        default:
+            /* can't get here unless jpeg_save_markers chooses wrong processor */
+            error();
+//          ERREXIT1(cinfo, JERR_UNKNOWN_MARKER, cinfo.unread_marker);
+            break;
+    }
+
+    /* skip any remaining data -- could be lots */
+    if (length > 0)
+        skip_input_data (cinfo, length);
+
+    return true;
+}
+
+static void examine_app0 (jpeg_decompress_struct cinfo, byte[] data, int datalen, int remaining)
+/* Examine first few bytes from an APP0.
+ * Take appropriate action if it is a JFIF marker.
+ * datalen is # of bytes at data[], remaining is length of rest of marker data.
+ */
+{
+    int totallen = datalen + remaining;
+
+    if (datalen >= APP0_DATA_LEN &&
+            (data[0] & 0xFF) is 0x4A &&
+            (data[1] & 0xFF) is 0x46 &&
+            (data[2] & 0xFF) is 0x49 &&
+            (data[3] & 0xFF) is 0x46 &&
+            (data[4] & 0xFF) is 0)
+    {
+        /* Found JFIF APP0 marker: save info */
+        cinfo.saw_JFIF_marker = true;
+        cinfo.JFIF_major_version = (data[5]);
+        cinfo.JFIF_minor_version = cast(byte)(data[6] & 0xFF);
+        cinfo.density_unit = cast(byte)(data[7] & 0xFF);
+        cinfo.X_density = cast(short)(((data[8] & 0xFF) << 8) + (data[9] & 0xFF));
+        cinfo.Y_density = cast(short)(((data[10] & 0xFF) << 8) + (data[11] & 0xFF));
+        /* Check version.
+         * Major version must be 1, anything else signals an incompatible change.
+         * (We used to treat this as an error, but now it's a nonfatal warning,
+         * because some bozo at Hijaak couldn't read the spec.)
+         * Minor version should be 0..2, but process anyway if newer.
+         */
+        if (cinfo.JFIF_major_version !is 1) {
+//          WARNMS2(cinfo, JWRN_JFIF_MAJOR,
+//              cinfo.JFIF_major_version, cinfo.JFIF_minor_version);
+        }
+        /* Generate trace messages */
+//      TRACEMS5(cinfo, 1, JTRC_JFIF,
+//           cinfo.JFIF_major_version, cinfo.JFIF_minor_version,
+//           cinfo.X_density, cinfo.Y_density, cinfo.density_unit);
+        /* Validate thumbnail dimensions and issue appropriate messages */
+        if (((data[12] & 0xFF) | (data[13]) & 0xFF) !is 0) {
+//          TRACEMS2(cinfo, 1, JTRC_JFIF_THUMBNAIL,
+//               GETJOCTET(data[12]), GETJOCTET(data[13]));
+        }
+        totallen -= APP0_DATA_LEN;
+        if (totallen !is ((data[12] & 0xFF) * (data[13] & 0xFF) * 3)) {
+//          TRACEMS1(cinfo, 1, JTRC_JFIF_BADTHUMBNAILSIZE, cast(int) totallen);
+        }
+    } else if (datalen >= 6 &&
+            (data[0] & 0xFF) is 0x4A &&
+            (data[1] & 0xFF) is 0x46 &&
+            (data[2] & 0xFF) is 0x58 &&
+            (data[3] & 0xFF) is 0x58 &&
+            (data[4] & 0xFF) is 0)
+    {
+        /* Found JFIF "JFXX" extension APP0 marker */
+        /* The library doesn't actually do anything with these,
+         * but we try to produce a helpful trace message.
+         */
+        switch ((data[5]) & 0xFF) {
+            case 0x10:
+//              TRACEMS1(cinfo, 1, JTRC_THUMB_JPEG, cast(int) totallen);
+                break;
+            case 0x11:
+//              TRACEMS1(cinfo, 1, JTRC_THUMB_PALETTE, cast(int) totallen);
+                break;
+            case 0x13:
+//              TRACEMS1(cinfo, 1, JTRC_THUMB_RGB, cast(int) totallen);
+                break;
+            default:
+//              TRACEMS2(cinfo, 1, JTRC_JFIF_EXTENSION, GETJOCTET(data[5]), cast(int) totallen);
+            break;
+        }
+    } else {
+        /* Start of APP0 does not match "JFIF" or "JFXX", or too short */
+//      TRACEMS1(cinfo, 1, JTRC_APP0, cast(int) totallen);
+    }
+}
+
+static void examine_app14 (jpeg_decompress_struct cinfo, byte[] data, int datalen, int remaining)
+/* Examine first few bytes from an APP14.
+ * Take appropriate action if it is an Adobe marker.
+ * datalen is # of bytes at data[], remaining is length of rest of marker data.
+ */
+{
+    int /*version, flags0, flags1, */transform;
+
+    if (datalen >= APP14_DATA_LEN &&
+            (data[0] & 0xFF) is 0x41 &&
+            (data[1] & 0xFF) is 0x64 &&
+            (data[2] & 0xFF) is 0x6F &&
+            (data[3] & 0xFF) is 0x62 &&
+            (data[4] & 0xFF) is 0x65)
+    {
+        /* Found Adobe APP14 marker */
+//      version = ((data[5] & 0xFF) << 8) + (data[6] & 0xFF);
+//      flags0 = ((data[7] & 0xFF) << 8) + (data[8] & 0xFF);
+//      flags1 = ((data[9] & 0xFF) << 8) + (data[10] & 0xFF);
+        transform = (data[11] & 0xFF);
+//      TRACEMS4(cinfo, 1, JTRC_ADOBE, version, flags0, flags1, transform);
+        cinfo.saw_Adobe_marker = true;
+        cinfo.Adobe_transform = cast(byte) transform;
+    } else {
+        /* Start of APP14 does not match "Adobe", or too short */
+//      TRACEMS1(cinfo, 1, JTRC_APP14, cast(int) (datalen + remaining));
+    }
+}
+
+static bool get_soi (jpeg_decompress_struct cinfo) /* Process an SOI marker */ {
+    int i;
+
+//  TRACEMS(cinfo, 1, JTRC_SOI);
+
+    if (cinfo.marker.saw_SOI)
+        error();
+//      ERREXIT(cinfo, JERR_SOI_DUPLICATE);
+
+    /* Reset all parameters that are defined to be reset by SOI */
+
+    for (i = 0; i < NUM_ARITH_TBLS; i++) {
+        cinfo.arith_dc_L[i] = 0;
+        cinfo.arith_dc_U[i] = 1;
+        cinfo.arith_ac_K[i] = 5;
+    }
+    cinfo.restart_interval = 0;
+
+    /* Set initial assumptions for colorspace etc */
+
+    cinfo.jpeg_color_space = JCS_UNKNOWN;
+    cinfo.CCIR601_sampling = false; /* Assume non-CCIR sampling??? */
+
+    cinfo.saw_JFIF_marker = false;
+    cinfo.JFIF_major_version = 1; /* set default JFIF APP0 values */
+    cinfo.JFIF_minor_version = 1;
+    cinfo.density_unit = 0;
+    cinfo.X_density = 1;
+    cinfo.Y_density = 1;
+    cinfo.saw_Adobe_marker = false;
+    cinfo.Adobe_transform = 0;
+
+    cinfo.marker.saw_SOI = true;
+
+    return true;
+}
+
+static void jinit_input_controller (jpeg_decompress_struct cinfo)
+{
+    /* Initialize state: can't use reset_input_controller since we don't
+     * want to try to reset other modules yet.
+     */
+    jpeg_input_controller inputctl = cinfo.inputctl = new jpeg_input_controller();
+    inputctl.has_multiple_scans = false; /* "unknown" would be better */
+    inputctl.eoi_reached = false;
+    inputctl.inheaders = true;
+}
+
+static void reset_marker_reader (jpeg_decompress_struct cinfo) {
+    jpeg_marker_reader marker = cinfo.marker;
+
+    cinfo.comp_info = null;     /* until allocated by get_sof */
+    cinfo.input_scan_number = 0;        /* no SOS seen yet */
+    cinfo.unread_marker = 0;        /* no pending marker */
+    marker.saw_SOI = false;     /* set internal state too */
+    marker.saw_SOF = false;
+    marker.discarded_bytes = 0;
+//  marker.cur_marker = null;
+}
+
+static void reset_input_controller (jpeg_decompress_struct cinfo) {
+    jpeg_input_controller inputctl = cinfo.inputctl;
+
+    inputctl.has_multiple_scans = false; /* "unknown" would be better */
+    inputctl.eoi_reached = false;
+    inputctl.inheaders = true;
+    /* Reset other modules */
+    reset_marker_reader (cinfo);
+    /* Reset progression state -- would be cleaner if entropy decoder did this */
+    cinfo.coef_bits = null;
+}
+
+static void finish_output_pass (jpeg_decompress_struct cinfo) {
+    jpeg_decomp_master master = cinfo.master;
+
+    if (cinfo.quantize_colors) {
+        error(DWT.ERROR_NOT_IMPLEMENTED);
+//      (*cinfo.cquantize.finish_pass) (cinfo);
+    }
+    master.pass_number++;
+}
+
+static void jpeg_destroy (jpeg_decompress_struct cinfo) {
+    /* We need only tell the memory manager to release everything. */
+    /* NB: mem pointer is NULL if memory mgr failed to initialize. */
+//  if (cinfo.mem !is NULL)
+//      (*cinfo.mem.self_destruct) (cinfo);
+//  cinfo.mem = NULL;       /* be safe if jpeg_destroy is called twice */
+    cinfo.global_state = 0; /* mark it destroyed */
+}
+
+static void jpeg_destroy_decompress (jpeg_decompress_struct cinfo) {
+    jpeg_destroy(cinfo); /* use common routine */
+}
+
+static bool jpeg_input_complete (jpeg_decompress_struct cinfo) {
+    /* Check for valid jpeg object */
+    if (cinfo.global_state < DSTATE_START || cinfo.global_state > DSTATE_STOPPING)
+        error();
+//      ERREXIT1(cinfo, JERR_BAD_STATE, cinfo.global_state);
+    return cinfo.inputctl.eoi_reached;
+}
+
+static bool jpeg_start_output (jpeg_decompress_struct cinfo, int scan_number) {
+    if (cinfo.global_state !is DSTATE_BUFIMAGE && cinfo.global_state !is DSTATE_PRESCAN)
+        error();
+//      ERREXIT1(cinfo, JERR_BAD_STATE, cinfo.global_state);
+    /* Limit scan number to valid range */
+    if (scan_number <= 0)
+        scan_number = 1;
+    if (cinfo.inputctl.eoi_reached && scan_number > cinfo.input_scan_number)
+        scan_number = cinfo.input_scan_number;
+    cinfo.output_scan_number = scan_number;
+    /* Perform any dummy output passes, and set up for the real pass */
+    return output_pass_setup(cinfo);
+}
+
+static bool jpeg_finish_output (jpeg_decompress_struct cinfo) {
+    if ((cinfo.global_state is DSTATE_SCANNING || cinfo.global_state is DSTATE_RAW_OK) && cinfo.buffered_image) {
+        /* Terminate this pass. */
+        /* We do not require the whole pass to have been completed. */
+        finish_output_pass (cinfo);
+        cinfo.global_state = DSTATE_BUFPOST;
+    } else if (cinfo.global_state !is DSTATE_BUFPOST) {
+        /* BUFPOST = repeat call after a suspension, anything else is error */
+        error();
+//      ERREXIT1(cinfo, JERR_BAD_STATE, cinfo.global_state);
+    }
+    /* Read markers looking for SOS or EOI */
+    while (cinfo.input_scan_number <= cinfo.output_scan_number && !cinfo.inputctl.eoi_reached) {
+        if (consume_input (cinfo) is JPEG_SUSPENDED)
+            return false;       /* Suspend, come back later */
+    }
+    cinfo.global_state = DSTATE_BUFIMAGE;
+    return true;
+}
+
+static bool jpeg_finish_decompress (jpeg_decompress_struct cinfo) {
+    if ((cinfo.global_state is DSTATE_SCANNING || cinfo.global_state is DSTATE_RAW_OK) && ! cinfo.buffered_image) {
+        /* Terminate final pass of non-buffered mode */
+        if (cinfo.output_scanline < cinfo.output_height)
+            error();
+//          ERREXIT(cinfo, JERR_TOO_LITTLE_DATA);
+        finish_output_pass (cinfo);
+        cinfo.global_state = DSTATE_STOPPING;
+    } else if (cinfo.global_state is DSTATE_BUFIMAGE) {
+        /* Finishing after a buffered-image operation */
+        cinfo.global_state = DSTATE_STOPPING;
+    } else if (cinfo.global_state !is DSTATE_STOPPING) {
+        /* STOPPING = repeat call after a suspension, anything else is error */
+        error();
+//      ERREXIT1(cinfo, JERR_BAD_STATE, cinfo.global_state);
+    }
+    /* Read until EOI */
+    while (! cinfo.inputctl.eoi_reached) {
+        if (consume_input (cinfo) is JPEG_SUSPENDED)
+            return false;       /* Suspend, come back later */
+    }
+    /* Do final cleanup */
+//  (*cinfo.src.term_source) (cinfo);
+    /* We can use jpeg_abort to release memory and reset global_state */
+    jpeg_abort(cinfo);
+    return true;
+}
+
+
+static int jpeg_read_header (jpeg_decompress_struct cinfo, bool require_image) {
+    int retcode;
+
+    if (cinfo.global_state !is DSTATE_START && cinfo.global_state !is DSTATE_INHEADER)
+        error();
+//      ERREXIT1(cinfo, JERR_BAD_STATE, cinfo.global_state);
+
+    retcode = jpeg_consume_input(cinfo);
+
+    switch (retcode) {
+        case JPEG_REACHED_SOS:
+            retcode = JPEG_HEADER_OK;
+            break;
+        case JPEG_REACHED_EOI:
+            if (require_image)      /* Complain if application wanted an image */
+                error();
+//              ERREXIT(cinfo, JERR_NO_IMAGE);
+            /* Reset to start state; it would be safer to require the application to
+             * call jpeg_abort, but we can't change it now for compatibility reasons.
+             * A side effect is to free any temporary memory (there shouldn't be any).
+             */
+            jpeg_abort(cinfo); /* sets state = DSTATE_START */
+            retcode = JPEG_HEADER_TABLES_ONLY;
+            break;
+        case JPEG_SUSPENDED:
+            /* no work */
+            break;
+        default:
+    }
+
+    return retcode;
+}
+
+static int dummy_consume_data (jpeg_decompress_struct cinfo) {
+    return JPEG_SUSPENDED;  /* Always indicate nothing was done */
+}
+
+static int consume_data (jpeg_decompress_struct cinfo) {
+    jpeg_d_coef_controller coef = cinfo.coef;
+    int MCU_col_num;    /* index of current MCU within row */
+    int blkn, ci, xindex, yindex, yoffset;
+    int start_col;
+//  short[][][][] buffer = new short[MAX_COMPS_IN_SCAN][][][];
+    short[][] buffer_ptr;
+    jpeg_component_info compptr;
+
+//  /* Align the virtual buffers for the components used in this scan. */
+//  for (ci = 0; ci < cinfo.comps_in_scan; ci++) {
+//      compptr = cinfo.cur_comp_info[ci];
+//      buffer[ci] = coef.whole_image[compptr.component_index];
+//      /* Note: entropy decoder expects buffer to be zeroed,
+//       * but this is handled automatically by the memory manager
+//       * because we requested a pre-zeroed array.
+//       */
+//  }
+
+    /* Loop to process one whole iMCU row */
+    for (yoffset = coef.MCU_vert_offset; yoffset < coef.MCU_rows_per_iMCU_row; yoffset++) {
+        for (MCU_col_num = coef.MCU_ctr; MCU_col_num < cinfo.MCUs_per_row; MCU_col_num++) {
+            /* Construct list of pointers to DCT blocks belonging to this MCU */
+            blkn = 0; /* index of current DCT block within MCU */
+            for (ci = 0; ci < cinfo.comps_in_scan; ci++) {
+                compptr = cinfo.cur_comp_info[ci];
+                start_col = MCU_col_num * compptr.MCU_width;
+                for (yindex = 0; yindex < compptr.MCU_height; yindex++) {
+//                  buffer_ptr = buffer[ci][yindex+yoffset] + start_col;
+                    buffer_ptr = coef.whole_image[compptr.component_index][yindex+yoffset+cinfo.input_iMCU_row*compptr.v_samp_factor];
+                    int buffer_ptr_offset = start_col;
+                    for (xindex = 0; xindex < compptr.MCU_width; xindex++) {
+                        coef.MCU_buffer[blkn++] = buffer_ptr[buffer_ptr_offset++];
+                    }
+                }
+            }
+            /* Try to fetch the MCU. */
+            if (! cinfo.entropy.decode_mcu (cinfo, coef.MCU_buffer)) {
+                /* Suspension forced; update state counters and exit */
+                coef.MCU_vert_offset = yoffset;
+                coef.MCU_ctr = MCU_col_num;
+                return JPEG_SUSPENDED;
+            }
+        }
+        /* Completed an MCU row, but perhaps not an iMCU row */
+        coef.MCU_ctr = 0;
+    }
+    /* Completed the iMCU row, advance counters for next one */
+    if (++(cinfo.input_iMCU_row) < cinfo.total_iMCU_rows) {
+        coef.start_iMCU_row(cinfo);
+        return JPEG_ROW_COMPLETED;
+    }
+    /* Completed the scan */
+    finish_input_pass (cinfo);
+    return JPEG_SCAN_COMPLETED;
+}
+
+static int consume_input (jpeg_decompress_struct cinfo) {
+    switch (cinfo.inputctl.consume_input) {
+        case COEF_CONSUME_INPUT:
+             switch (cinfo.coef.consume_data) {
+                case CONSUME_DATA: return consume_data(cinfo);
+                case DUMMY_CONSUME_DATA: return dummy_consume_data(cinfo);
+                default: error();
+             }
+             break;
+        case INPUT_CONSUME_INPUT:
+            return consume_markers(cinfo);
+        default:
+            error();
+    }
+    return 0;
+}
+
+static bool fill_input_buffer(jpeg_decompress_struct cinfo) {
+    try {
+        InputStream inputStream = cinfo.inputStream;
+        int nbytes = inputStream.read(cinfo.buffer);
+        if (nbytes <= 0) {
+            if (cinfo.start_of_file)    /* Treat empty input file as fatal error */
+                error();
+//              ERREXIT(cinfo, JERR_INPUT_EMPTY);
+//          WARNMS(cinfo, JWRN_JPEG_EOF);
+            /* Insert a fake EOI marker */
+            cinfo.buffer[0] = cast(byte)0xFF;
+            cinfo.buffer[1] = cast(byte)M_EOI;
+            nbytes = 2;
+        }
+        cinfo.bytes_in_buffer = nbytes;
+        cinfo.bytes_offset = 0;
+        cinfo.start_of_file = false;
+    } catch (IOException e) {
+        error(DWT.ERROR_IO);
+        return false;
+    }
+    return true;
+}
+
+static bool first_marker (jpeg_decompress_struct cinfo) {
+    /* Like next_marker, but used to obtain the initial SOI marker. */
+    /* For this marker, we do not allow preceding garbage or fill; otherwise,
+     * we might well scan an entire input file before realizing it ain't JPEG.
+     * If an application wants to process non-JFIF files, it must seek to the
+     * SOI before calling the JPEG library.
+     */
+    int c, c2;
+
+    if (cinfo.bytes_offset is cinfo.bytes_in_buffer) fill_input_buffer(cinfo);
+    c = cinfo.buffer[cinfo.bytes_offset++] & 0xFF;
+    if (cinfo.bytes_offset is cinfo.bytes_in_buffer) fill_input_buffer(cinfo);
+    c2 = cinfo.buffer[cinfo.bytes_offset++] & 0xFF;
+    if (c !is 0xFF || c2 !is M_SOI)
+        error();
+//      ERREXIT2(cinfo, JERR_NO_SOI, c, c2);
+
+    cinfo.unread_marker = c2;
+
+    return true;
+}
+
+static bool next_marker (jpeg_decompress_struct cinfo) {
+    int c;
+
+    for (;;) {
+        if (cinfo.bytes_offset is cinfo.bytes_in_buffer) fill_input_buffer(cinfo);
+        c = cinfo.buffer[cinfo.bytes_offset++] & 0xFF;
+        /* Skip any non-FF bytes.
+         * This may look a bit inefficient, but it will not occur in a valid file.
+         * We sync after each discarded byte so that a suspending data source
+         * can discard the byte from its buffer.
+         */
+        while (c !is 0xFF) {
+            cinfo.marker.discarded_bytes++;
+            if (cinfo.bytes_offset is cinfo.bytes_in_buffer) fill_input_buffer(cinfo);
+            c = cinfo.buffer[cinfo.bytes_offset++] & 0xFF;
+        }
+        /* This loop swallows any duplicate FF bytes.   Extra FFs are legal as
+         * pad bytes, so don't count them in discarded_bytes.   We assume there
+         * will not be so many consecutive FF bytes as to overflow a suspending
+         * data source's input buffer.
+         */
+        do {
+             if (cinfo.bytes_offset is cinfo.bytes_in_buffer) fill_input_buffer(cinfo);
+                c = cinfo.buffer[cinfo.bytes_offset++] & 0xFF;
+        } while (c is 0xFF);
+        if (c !is 0)
+            break;          /* found a valid marker, exit loop */
+        /* Reach here if we found a stuffed-zero data sequence (FF/00).
+         * Discard it and loop back to try again.
+         */
+        cinfo.marker.discarded_bytes += 2;
+    }
+
+    if (cinfo.marker.discarded_bytes !is 0) {
+//      WARNMS2(cinfo, JWRN_EXTRANEOUS_DATA, cinfo.marker.discarded_bytes, c);
+        cinfo.marker.discarded_bytes = 0;
+    }
+
+    cinfo.unread_marker = c;
+
+    return true;
+}
+
+static int read_markers (jpeg_decompress_struct cinfo) {
+    /* Outer loop repeats once for each marker. */
+    for (;;) {
+        /* Collect the marker proper, unless we already did. */
+        /* NB: first_marker() enforces the requirement that SOI appear first. */
+        if (cinfo.unread_marker is 0) {
+            if (! cinfo.marker.saw_SOI) {
+                if (! first_marker(cinfo))
+                    return JPEG_SUSPENDED;
+                } else {
+                    if (! next_marker(cinfo))
+                        return JPEG_SUSPENDED;
+                }
+        }
+        /* At this point cinfo.unread_marker contains the marker code and the
+         * input point is just past the marker proper, but before any parameters.
+         * A suspension will cause us to return with this state still true.
+         */
+        switch (cinfo.unread_marker) {
+            case M_SOI:
+                if (! get_soi(cinfo))
+                    return JPEG_SUSPENDED;
+                break;
+
+            case M_SOF0:        /* Baseline */
+            case M_SOF1:        /* Extended sequential, Huffman */
+                if (! get_sof(cinfo, false, false))
+                    return JPEG_SUSPENDED;
+                break;
+
+            case M_SOF2:        /* Progressive, Huffman */
+                if (! get_sof(cinfo, true, false))
+                    return JPEG_SUSPENDED;
+                break;
+
+            case M_SOF9:        /* Extended sequential, arithmetic */
+                if (! get_sof(cinfo, false, true))
+                    return JPEG_SUSPENDED;
+                break;
+
+            case M_SOF10:       /* Progressive, arithmetic */
+                if (! get_sof(cinfo, true, true))
+                    return JPEG_SUSPENDED;
+                break;
+
+            /* Currently unsupported SOFn types */
+            case M_SOF3:        /* Lossless, Huffman */
+            case M_SOF5:        /* Differential sequential, Huffman */
+            case M_SOF6:        /* Differential progressive, Huffman */
+            case M_SOF7:        /* Differential lossless, Huffman */
+            case M_JPG:         /* Reserved for JPEG extensions */
+            case M_SOF11:       /* Lossless, arithmetic */
+            case M_SOF13:       /* Differential sequential, arithmetic */
+            case M_SOF14:       /* Differential progressive, arithmetic */
+            case M_SOF15:       /* Differential lossless, arithmetic */
+                error();
+//              ERREXIT1(cinfo, JERR_SOF_UNSUPPORTED, cinfo.unread_marker);
+                break;
+
+            case M_SOS:
+                if (! get_sos(cinfo))
+                    return JPEG_SUSPENDED;
+                cinfo.unread_marker = 0;    /* processed the marker */
+                return JPEG_REACHED_SOS;
+
+            case M_EOI:
+//              TRACEMS(cinfo, 1, JTRC_EOI);
+                cinfo.unread_marker = 0;    /* processed the marker */
+                return JPEG_REACHED_EOI;
+
+            case M_DAC:
+                if (! get_dac(cinfo))
+                    return JPEG_SUSPENDED;
+                break;
+
+            case M_DHT:
+                if (! get_dht(cinfo))
+                    return JPEG_SUSPENDED;
+                break;
+
+            case M_DQT:
+                if (! get_dqt(cinfo))
+                    return JPEG_SUSPENDED;
+                break;
+
+            case M_DRI:
+                if (! get_dri(cinfo))
+                    return JPEG_SUSPENDED;
+                break;
+
+            case M_APP0:
+            case M_APP1:
+            case M_APP2:
+            case M_APP3:
+            case M_APP4:
+            case M_APP5:
+            case M_APP6:
+            case M_APP7:
+            case M_APP8:
+            case M_APP9:
+            case M_APP10:
+            case M_APP11:
+            case M_APP12:
+            case M_APP13:
+            case M_APP14:
+            case M_APP15:
+                if (! process_APPn(cinfo.unread_marker - M_APP0, cinfo))
+                    return JPEG_SUSPENDED;
+                break;
+
+            case M_COM:
+                if (! process_COM(cinfo))
+                    return JPEG_SUSPENDED;
+                break;
+
+            case M_RST0:        /* these are all parameterless */
+            case M_RST1:
+            case M_RST2:
+            case M_RST3:
+            case M_RST4:
+            case M_RST5:
+            case M_RST6:
+            case M_RST7:
+            case M_TEM:
+//              TRACEMS1(cinfo, 1, JTRC_PARMLESS_MARKER, cinfo.unread_marker);
+                break;
+
+            case M_DNL:         /* Ignore DNL ... perhaps the wrong thing */
+                if (! skip_variable(cinfo))
+                    return JPEG_SUSPENDED;
+                break;
+
+            default:            /* must be DHP, EXP, JPGn, or RESn */
+                /* For now, we treat the reserved markers as fatal errors since they are
+                 * likely to be used to signal incompatible JPEG Part 3 extensions.
+                 * Once the JPEG 3 version-number marker is well defined, this code
+                 * ought to change!
+                 */
+                error();
+ //             ERREXIT1(cinfo, JERR_UNKNOWN_MARKER, cinfo.unread_marker);
+                break;
+        }
+        /* Successfully processed marker, so reset state variable */
+        cinfo.unread_marker = 0;
+    } /* end loop */
+}
+
+static long jdiv_round_up (long a, long b)
+/* Compute a/b rounded up to next integer, ie, ceil(a/b) */
+/* Assumes a >= 0, b > 0 */
+{
+    return (a + b - 1) / b;
+}
+
+static void initial_setup (jpeg_decompress_struct cinfo)
+/* Called once, when first SOS marker is reached */
+{
+    int ci;
+    jpeg_component_info compptr;
+
+    /* Make sure image isn't bigger than I can handle */
+    if (cinfo.image_height >    JPEG_MAX_DIMENSION || cinfo.image_width > JPEG_MAX_DIMENSION)
+        error();
+//      ERREXIT1(cinfo, JERR_IMAGE_TOO_BIG, (unsigned int) JPEG_MAX_DIMENSION);
+
+    /* For now, precision must match compiled-in value... */
+    if (cinfo.data_precision !is BITS_IN_JSAMPLE)
+        error(" [data precision=" ~ to!(char[])(cinfo.data_precision) ~ "]");
+//      ERREXIT1(cinfo, JERR_BAD_PRECISION, cinfo.data_precision);
+
+    /* Check that number of components won't exceed internal array sizes */
+    if (cinfo.num_components > MAX_COMPONENTS)
+        error();
+//      ERREXIT2(cinfo, JERR_COMPONENT_COUNT, cinfo.num_components, MAX_COMPONENTS);
+
+    /* Compute maximum sampling factors; check factor validity */
+    cinfo.max_h_samp_factor = 1;
+    cinfo.max_v_samp_factor = 1;
+    for (ci = 0; ci < cinfo.num_components; ci++) {
+        compptr = cinfo.comp_info[ci];
+        if (compptr.h_samp_factor<=0 || compptr.h_samp_factor>MAX_SAMP_FACTOR || compptr.v_samp_factor<=0 || compptr.v_samp_factor>MAX_SAMP_FACTOR)
+            error();
+//          ERREXIT(cinfo, JERR_BAD_SAMPLING);
+        cinfo.max_h_samp_factor = Math.max(cinfo.max_h_samp_factor, compptr.h_samp_factor);
+        cinfo.max_v_samp_factor = Math.max(cinfo.max_v_samp_factor, compptr.v_samp_factor);
+    }
+
+    /* We initialize DCT_scaled_size and min_DCT_scaled_size to DCTSIZE.
+     * In the full decompressor, this will be overridden by jdmaster.c;
+     * but in the transcoder, jdmaster.c is not used, so we must do it here.
+     */
+    cinfo.min_DCT_scaled_size = DCTSIZE;
+
+    /* Compute dimensions of components */
+    for (ci = 0; ci < cinfo.num_components; ci++) {
+        compptr = cinfo.comp_info[ci];
+        compptr.DCT_scaled_size = DCTSIZE;
+        /* Size in DCT blocks */
+        compptr.width_in_blocks = cast(int)jdiv_round_up(cast(long) cinfo.image_width * cast(long) compptr.h_samp_factor, (cinfo.max_h_samp_factor * DCTSIZE));
+        compptr.height_in_blocks = cast(int)jdiv_round_up(cast(long) cinfo.image_height * cast(long) compptr.v_samp_factor, (cinfo.max_v_samp_factor * DCTSIZE));
+        /* downsampled_width and downsampled_height will also be overridden by
+         * jdmaster.c if we are doing full decompression.   The transcoder library
+         * doesn't use these values, but the calling application might.
+         */
+        /* Size in samples */
+        compptr.downsampled_width = cast(int)jdiv_round_up(cast(long) cinfo.image_width * cast(long) compptr.h_samp_factor, cinfo.max_h_samp_factor);
+        compptr.downsampled_height = cast(int)jdiv_round_up(cast(long) cinfo.image_height * cast(long) compptr.v_samp_factor, cinfo.max_v_samp_factor);
+        /* Mark component needed, until color conversion says otherwise */
+        compptr.component_needed = true;
+        /* Mark no quantization table yet saved for component */
+        compptr.quant_table = null;
+    }
+
+    /* Compute number of fully interleaved MCU rows. */
+    cinfo.total_iMCU_rows = cast(int)jdiv_round_up( cinfo.image_height, (cinfo.max_v_samp_factor*DCTSIZE));
+
+    /* Decide whether file contains multiple scans */
+    if (cinfo.comps_in_scan < cinfo.num_components || cinfo.progressive_mode)
+        cinfo.inputctl.has_multiple_scans = true;
+    else
+        cinfo.inputctl.has_multiple_scans = false;
+}
+
+
+static void per_scan_setup (jpeg_decompress_struct cinfo)
+/* Do computations that are needed before processing a JPEG scan */
+/* cinfo.comps_in_scan and cinfo.cur_comp_info[] were set from SOS marker */
+{
+    int ci, mcublks, tmp = 0;
+    jpeg_component_info compptr;
+
+    if (cinfo.comps_in_scan is 1) {
+
+        /* Noninterleaved (single-component) scan */
+        compptr = cinfo.cur_comp_info[0];
+
+        /* Overall image size in MCUs */
+        cinfo.MCUs_per_row = compptr.width_in_blocks;
+        cinfo.MCU_rows_in_scan = compptr.height_in_blocks;
+
+        /* For noninterleaved scan, always one block per MCU */
+        compptr.MCU_width = 1;
+        compptr.MCU_height = 1;
+        compptr.MCU_blocks = 1;
+        compptr.MCU_sample_width = compptr.DCT_scaled_size;
+        compptr.last_col_width = 1;
+        /* For noninterleaved scans, it is convenient to define last_row_height
+         * as the number of block rows present in the last iMCU row.
+         */
+        tmp = (compptr.height_in_blocks % compptr.v_samp_factor);
+        if (tmp is 0) tmp = compptr.v_samp_factor;
+        compptr.last_row_height = tmp;
+
+        /* Prepare array describing MCU composition */
+        cinfo.blocks_in_MCU = 1;
+        cinfo.MCU_membership[0] = 0;
+
+    } else {
+
+        /* Interleaved (multi-component) scan */
+        if (cinfo.comps_in_scan <= 0 || cinfo.comps_in_scan > MAX_COMPS_IN_SCAN)
+            error();
+//          ERREXIT2(cinfo, JERR_COMPONENT_COUNT, cinfo.comps_in_scan, MAX_COMPS_IN_SCAN);
+
+        /* Overall image size in MCUs */
+        cinfo.MCUs_per_row = cast(int)jdiv_round_up( cinfo.image_width, (cinfo.max_h_samp_factor*DCTSIZE));
+        cinfo.MCU_rows_in_scan = cast(int)jdiv_round_up( cinfo.image_height, (cinfo.max_v_samp_factor*DCTSIZE));
+
+        cinfo.blocks_in_MCU = 0;
+
+        for (ci = 0; ci < cinfo.comps_in_scan; ci++) {
+            compptr = cinfo.cur_comp_info[ci];
+            /* Sampling factors give # of blocks of component in each MCU */
+            compptr.MCU_width = compptr.h_samp_factor;
+            compptr.MCU_height = compptr.v_samp_factor;
+            compptr.MCU_blocks = compptr.MCU_width * compptr.MCU_height;
+            compptr.MCU_sample_width = compptr.MCU_width * compptr.DCT_scaled_size;
+            /* Figure number of non-dummy blocks in last MCU column & row */
+            tmp = (compptr.width_in_blocks % compptr.MCU_width);
+            if (tmp is 0) tmp = compptr.MCU_width;
+            compptr.last_col_width = tmp;
+            tmp = (compptr.height_in_blocks % compptr.MCU_height);
+            if (tmp is 0) tmp = compptr.MCU_height;
+            compptr.last_row_height = tmp;
+            /* Prepare array describing MCU composition */
+            mcublks = compptr.MCU_blocks;
+            if (cinfo.blocks_in_MCU + mcublks > D_MAX_BLOCKS_IN_MCU)
+                error();
+//  ERREXIT(cinfo, JERR_BAD_MCU_SIZE);
+            while (mcublks-- > 0) {
+                cinfo.MCU_membership[cinfo.blocks_in_MCU++] = ci;
+            }
+        }
+
+    }
+}
+
+static void latch_quant_tables (jpeg_decompress_struct cinfo) {
+    int ci, qtblno;
+    jpeg_component_info compptr;
+    JQUANT_TBL qtbl;
+
+    for (ci = 0; ci < cinfo.comps_in_scan; ci++) {
+        compptr = cinfo.cur_comp_info[ci];
+        /* No work if we already saved Q-table for this component */
+        if (compptr.quant_table !is null)
+            continue;
+        /* Make sure specified quantization table is present */
+        qtblno = compptr.quant_tbl_no;
+        if (qtblno < 0 || qtblno >= NUM_QUANT_TBLS || cinfo.quant_tbl_ptrs[qtblno] is null)
+            error();
+//          ERREXIT1(cinfo, JERR_NO_QUANT_TABLE, qtblno);
+        /* OK, save away the quantization table */
+        qtbl = new JQUANT_TBL();
+        System.arraycopy(cinfo.quant_tbl_ptrs[qtblno].quantval, 0, qtbl.quantval, 0, qtbl.quantval.length);
+        qtbl.sent_table = cinfo.quant_tbl_ptrs[qtblno].sent_table;
+        compptr.quant_table = qtbl;
+    }
+}
+
+static void jpeg_make_d_derived_tbl (jpeg_decompress_struct cinfo, bool isDC, int tblno, d_derived_tbl dtbl) {
+    JHUFF_TBL htbl;
+    int p, i = 0, l, si, numsymbols;
+    int lookbits, ctr;
+    byte[] huffsize = new byte[257];
+    int[] huffcode = new int[257];
+    int code;
+
+    /* Note that huffsize[] and huffcode[] are filled in code-length order,
+     * paralleling the order of the symbols themselves in htbl.huffval[].
+     */
+
+    /* Find the input Huffman table */
+    if (tblno < 0 || tblno >= NUM_HUFF_TBLS)
+        error();
+//      ERREXIT1(cinfo, JERR_NO_HUFF_TABLE, tblno);
+    htbl = isDC ? cinfo.dc_huff_tbl_ptrs[tblno] : cinfo.ac_huff_tbl_ptrs[tblno];
+    if (htbl is null)
+        error();
+//      ERREXIT1(cinfo, JERR_NO_HUFF_TABLE, tblno);
+
+    /* Allocate a workspace if we haven't already done so. */
+    dtbl.pub = htbl;        /* fill in back link */
+
+    /* Figure C.1: make table of Huffman code length for each symbol */
+
+    p = 0;
+    for (l = 1; l <= 16; l++) {
+        i = htbl.bits[l] & 0xFF;
+        if (i < 0 || p + i > 256)   /* protect against table overrun */
+            error();
+//          ERREXIT(cinfo, JERR_BAD_HUFF_TABLE);
+        while (i-- !is 0)
+            huffsize[p++] = cast(byte) l;
+    }
+    huffsize[p] = 0;
+    numsymbols = p;
+
+    /* Figure C.2: generate the codes themselves */
+    /* We also validate that the counts represent a legal Huffman code tree. */
+
+    code = 0;
+    si = huffsize[0];
+    p = 0;
+    while ( huffsize[p] !is 0) {
+        while (( huffsize[p]) is si) {
+            huffcode[p++] = code;
+            code++;
+        }
+        /* code is now 1 more than the last code used for codelength si; but
+         * it must still fit in si bits, since no code is allowed to be all ones.
+         */
+        if (( code) >= (( 1) << si))
+            error();
+//          ERREXIT(cinfo, JERR_BAD_HUFF_TABLE);
+        code <<= 1;
+        si++;
+    }
+
+    /* Figure F.15: generate decoding tables for bit-sequential decoding */
+
+    p = 0;
+    for (l = 1; l <= 16; l++) {
+        if ((htbl.bits[l] & 0xFF) !is 0) {
+            /* valoffset[l] = huffval[] index of 1st symbol of code length l,
+             * minus the minimum code of length l
+             */
+            dtbl.valoffset[l] = p - huffcode[p];
+            p += (htbl.bits[l] & 0xFF);
+            dtbl.maxcode[l] = huffcode[p-1]; /* maximum code of length l */
+        } else {
+            dtbl.maxcode[l] = -1;   /* -1 if no codes of this length */
+        }
+    }
+    dtbl.maxcode[17] = 0xFFFFF; /* ensures jpeg_huff_decode terminates */
+
+    /* Compute lookahead tables to speed up decoding.
+     * First we set all the table entries to 0, indicating "too long";
+     * then we iterate through the Huffman codes that are short enough and
+     * fill in all the entries that correspond to bit sequences starting
+     * with that code.
+     */
+
+    for (int j = 0; j < dtbl.look_nbits.length; j++) {
+        dtbl.look_nbits[j] = 0;
+    }
+
+    p = 0;
+    for (l = 1; l <= HUFF_LOOKAHEAD; l++) {
+        for (i = 1; i <= (htbl.bits[l] & 0xFF); i++, p++) {
+            /* l = current code's length, p = its index in huffcode[] & huffval[]. */
+            /* Generate left-justified code followed by all possible bit sequences */
+            lookbits = huffcode[p] << (HUFF_LOOKAHEAD-l);
+            for (ctr = 1 << (HUFF_LOOKAHEAD-l); ctr > 0; ctr--) {
+                dtbl.look_nbits[lookbits] = l;
+                dtbl.look_sym[lookbits] = htbl.huffval[p];
+                lookbits++;
+            }
+        }
+    }
+
+    /* Validate symbols as being reasonable.
+     * For AC tables, we make no check, but accept all byte values 0..255.
+     * For DC tables, we require the symbols to be in range 0..15.
+     * (Tighter bounds could be applied depending on the data depth and mode,
+     * but this is sufficient to ensure safe decoding.)
+     */
+    if (isDC) {
+        for (i = 0; i < numsymbols; i++) {
+            int sym = htbl.huffval[i] & 0xFF;
+            if (sym < 0 || sym > 15)
+                error();
+//              ERREXIT(cinfo, JERR_BAD_HUFF_TABLE);
+        }
+    }
+}
+
+static void start_input_pass (jpeg_decompress_struct cinfo) {
+    per_scan_setup(cinfo);
+    latch_quant_tables(cinfo);
+    cinfo.entropy.start_pass(cinfo);
+    cinfo.coef.start_input_pass (cinfo);
+    cinfo.inputctl.consume_input = COEF_CONSUME_INPUT;
+}
+
+static void finish_input_pass (jpeg_decompress_struct cinfo) {
+    cinfo.inputctl.consume_input = INPUT_CONSUME_INPUT;
+}
+
+static int consume_markers (jpeg_decompress_struct cinfo) {
+    jpeg_input_controller inputctl = cinfo.inputctl;
+    int val;
+
+    if (inputctl.eoi_reached) /* After hitting EOI, read no further */
+        return JPEG_REACHED_EOI;
+
+    val = read_markers (cinfo);
+
+    switch (val) {
+    case JPEG_REACHED_SOS:  /* Found SOS */
+        if (inputctl.inheaders) {   /* 1st SOS */
+            initial_setup(cinfo);
+            inputctl.inheaders = false;
+            /* Note: start_input_pass must be called by jdmaster.c
+             * before any more input can be consumed.   jdapimin.c is
+             * responsible for enforcing this sequencing.
+             */
+        } else {            /* 2nd or later SOS marker */
+            if (! inputctl.has_multiple_scans)
+                error();
+//              ERREXIT(cinfo, JERR_EOI_EXPECTED); /* Oops, I wasn't expecting this! */
+            start_input_pass(cinfo);
+        }
+        break;
+    case JPEG_REACHED_EOI:  /* Found EOI */
+        inputctl.eoi_reached = true;
+        if (inputctl.inheaders) {   /* Tables-only datastream, apparently */
+            if (cinfo.marker.saw_SOF)
+                error();
+//              ERREXIT(cinfo, JERR_SOF_NO_SOS);
+        } else {
+            /* Prevent infinite loop in coef ctlr's decompress_data routine
+             * if user set output_scan_number larger than number of scans.
+             */
+            if (cinfo.output_scan_number > cinfo.input_scan_number)
+                cinfo.output_scan_number = cinfo.input_scan_number;
+        }
+        break;
+    case JPEG_SUSPENDED:
+        break;
+    default:
+    }
+
+    return val;
+}
+
+static void default_decompress_parms (jpeg_decompress_struct cinfo) {
+    /* Guess the input colorspace, and set output colorspace accordingly. */
+    /* (Wish JPEG committee had provided a real way to specify this...) */
+    /* Note application may override our guesses. */
+    switch (cinfo.num_components) {
+        case 1:
+            cinfo.jpeg_color_space = JCS_GRAYSCALE;
+            cinfo.out_color_space = JCS_GRAYSCALE;
+            break;
+
+        case 3:
+            if (cinfo.saw_JFIF_marker) {
+                cinfo.jpeg_color_space = JCS_YCbCr; /* JFIF implies YCbCr */
+            } else if (cinfo.saw_Adobe_marker) {
+                switch (cinfo.Adobe_transform) {
+                    case 0:
+                        cinfo.jpeg_color_space = JCS_RGB;
+                        break;
+                    case 1:
+                        cinfo.jpeg_color_space = JCS_YCbCr;
+                        break;
+                    default:
+//                      WARNMS1(cinfo, JWRN_ADOBE_XFORM, cinfo.Adobe_transform);
+                        cinfo.jpeg_color_space = JCS_YCbCr; /* assume it's YCbCr */
+                    break;
+                }
+            } else {
+                /* Saw no special markers, try to guess from the component IDs */
+                int cid0 = cinfo.comp_info[0].component_id;
+                int cid1 = cinfo.comp_info[1].component_id;
+                int cid2 = cinfo.comp_info[2].component_id;
+
+                if (cid0 is 1 && cid1 is 2 && cid2 is 3)
+                    cinfo.jpeg_color_space = JCS_YCbCr; /* assume JFIF w/out marker */
+                else if (cid0 is 82 && cid1 is 71 && cid2 is 66)
+                    cinfo.jpeg_color_space = JCS_RGB; /* ASCII 'R', 'G', 'B' */
+                else {
+//                  TRACEMS3(cinfo, 1, JTRC_UNKNOWN_IDS, cid0, cid1, cid2);
+                    cinfo.jpeg_color_space = JCS_YCbCr; /* assume it's YCbCr */
+                }
+            }
+            /* Always guess RGB is proper output colorspace. */
+            cinfo.out_color_space = JCS_RGB;
+            break;
+
+        case 4:
+            if (cinfo.saw_Adobe_marker) {
+                switch (cinfo.Adobe_transform) {
+                    case 0:
+                        cinfo.jpeg_color_space = JCS_CMYK;
+                        break;
+                    case 2:
+                        cinfo.jpeg_color_space = JCS_YCCK;
+                        break;
+                    default:
+//                      WARNMS1(cinfo, JWRN_ADOBE_XFORM, cinfo.Adobe_transform);
+                        cinfo.jpeg_color_space = JCS_YCCK; /* assume it's YCCK */
+                        break;
+                }
+            } else {
+                /* No special markers, assume straight CMYK. */
+                cinfo.jpeg_color_space = JCS_CMYK;
+            }
+            cinfo.out_color_space = JCS_CMYK;
+            break;
+
+        default:
+            cinfo.jpeg_color_space = JCS_UNKNOWN;
+            cinfo.out_color_space = JCS_UNKNOWN;
+            break;
+    }
+
+    /* Set defaults for other decompression parameters. */
+    cinfo.scale_num = 1;        /* 1:1 scaling */
+    cinfo.scale_denom = 1;
+    cinfo.output_gamma = 1.0;
+    cinfo.buffered_image = false;
+    cinfo.raw_data_out = false;
+    cinfo.dct_method = JDCT_DEFAULT;
+    cinfo.do_fancy_upsampling = true;
+    cinfo.do_block_smoothing = true;
+    cinfo.quantize_colors = false;
+    /* We set these in case application only sets quantize_colors. */
+    cinfo.dither_mode = JDITHER_FS;
+    cinfo.two_pass_quantize = true;
+    cinfo.desired_number_of_colors = 256;
+    cinfo.colormap = null;
+    /* Initialize for no mode change in buffered-image mode. */
+    cinfo.enable_1pass_quant = false;
+    cinfo.enable_external_quant = false;
+    cinfo.enable_2pass_quant = false;
+}
+
+static void init_source(jpeg_decompress_struct cinfo) {
+    cinfo.buffer = new byte[INPUT_BUFFER_SIZE];
+    cinfo.bytes_in_buffer = 0;
+    cinfo.bytes_offset = 0;
+    cinfo.start_of_file = true;
+}
+
+static int jpeg_consume_input (jpeg_decompress_struct cinfo) {
+    int retcode = JPEG_SUSPENDED;
+
+    /* NB: every possible DSTATE value should be listed in this switch */
+    switch (cinfo.global_state) {
+    case DSTATE_START:
+        /* Start-of-datastream actions: reset appropriate modules */
+        reset_input_controller(cinfo);
+        /* Initialize application's data source module */
+        init_source (cinfo);
+        cinfo.global_state = DSTATE_INHEADER;
+        /*FALLTHROUGH*/
+    case DSTATE_INHEADER:
+        retcode = consume_input(cinfo);
+        if (retcode is JPEG_REACHED_SOS) { /* Found SOS, prepare to decompress */
+            /* Set up default parameters based on header data */
+            default_decompress_parms(cinfo);
+            /* Set global state: ready for start_decompress */
+            cinfo.global_state = DSTATE_READY;
+        }
+        break;
+    case DSTATE_READY:
+        /* Can't advance past first SOS until start_decompress is called */
+        retcode = JPEG_REACHED_SOS;
+        break;
+    case DSTATE_PRELOAD:
+    case DSTATE_PRESCAN:
+    case DSTATE_SCANNING:
+    case DSTATE_RAW_OK:
+    case DSTATE_BUFIMAGE:
+    case DSTATE_BUFPOST:
+    case DSTATE_STOPPING:
+        retcode = consume_input (cinfo);
+        break;
+    default:
+        error();
+//      ERREXIT1(cinfo, JERR_BAD_STATE, cinfo.global_state);
+    }
+    return retcode;
+}
+
+
+static void jpeg_abort (jpeg_decompress_struct cinfo) {
+//  int pool;
+//
+//  /* Releasing pools in reverse order might help avoid fragmentation
+//   * with some (brain-damaged) malloc libraries.
+//   */
+//  for (pool = JPOOL_NUMPOOLS-1; pool > JPOOL_PERMANENT; pool--) {
+//      (*cinfo.mem.free_pool) (cinfo, pool);
+//  }
+
+    /* Reset overall state for possible reuse of object */
+    if (cinfo.is_decompressor) {
+        cinfo.global_state = DSTATE_START;
+        /* Try to keep application from accessing now-deleted marker list.
+         * A bit kludgy to do it here, but this is the most central place.
+         */
+//      ((j_decompress_ptr) cinfo).marker_list = null;
+    } else {
+        cinfo.global_state = CSTATE_START;
+    }
+}
+
+
+static bool isFileFormat(LEDataInputStream stream) {
+    try {
+        byte[] buffer = new byte[2];
+        stream.read(buffer);
+        stream.unread(buffer);
+        return (buffer[0] & 0xFF) is 0xFF && (buffer[1] & 0xFF) is M_SOI;
+    } catch (TracedException e) {
+        return false;
+    }
+}
+
+static ImageData[] loadFromByteStream(InputStream inputStream, ImageLoader loader) {
+    jpeg_decompress_struct cinfo = new jpeg_decompress_struct();
+    cinfo.inputStream = inputStream;
+    jpeg_create_decompress(cinfo);
+    jpeg_read_header(cinfo, true);
+    cinfo.buffered_image = cinfo.progressive_mode && loader.hasListeners();
+    jpeg_start_decompress(cinfo);
+    PaletteData palette = null;
+    switch (cinfo.out_color_space) {
+        case JCS_RGB:
+            palette = new PaletteData(0xFF, 0xFF00, 0xFF0000);
+            break;
+        case JCS_GRAYSCALE:
+            RGB[] colors = new RGB[256];
+            for (int i = 0; i < colors.length; i++) {
+                colors[i] = new RGB(i, i, i);
+            }
+            palette = new PaletteData(colors);
+            break;
+        default:
+            error();
+    }
+    int scanlinePad = 4;
+    int row_stride = (((cinfo.output_width * cinfo.out_color_components * 8 + 7) / 8) + (scanlinePad - 1)) / scanlinePad * scanlinePad;
+    byte[][] buffer = new byte[][]( 1, row_stride );
+    byte[] data = new byte[row_stride * cinfo.output_height];
+    ImageData imageData = ImageData.internal_new(
+            cinfo.output_width, cinfo.output_height, palette.isDirect ? 24 : 8, palette, scanlinePad, data,
+            0, null, null, -1, -1, DWT.IMAGE_JPEG, 0, 0, 0, 0);
+    if (cinfo.buffered_image) {
+        bool done;
+        do {
+            int incrementCount = cinfo.input_scan_number - 1;
+            jpeg_start_output(cinfo, cinfo.input_scan_number);
+            while (cinfo.output_scanline < cinfo.output_height) {
+                int offset = row_stride * cinfo.output_scanline;
+                jpeg_read_scanlines(cinfo, buffer, 1);
+                System.arraycopy(buffer[0], 0, data, offset, row_stride);
+            }
+            jpeg_finish_output(cinfo);
+            loader.notifyListeners(new ImageLoaderEvent(loader, cast(ImageData)imageData.clone(), incrementCount, done = jpeg_input_complete(cinfo)));
+        } while (!done);
+    } else {
+        while (cinfo.output_scanline < cinfo.output_height) {
+            int offset = row_stride * cinfo.output_scanline;
+            jpeg_read_scanlines(cinfo, buffer, 1);
+            System.arraycopy(buffer[0], 0, data, offset, row_stride);
+        }
+    }
+    jpeg_finish_decompress(cinfo);
+    jpeg_destroy_decompress(cinfo);
+    return [imageData];
+}
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dwt/internal/image/JPEGEndOfImage.d	Wed Jan 23 12:01:46 2008 +0100
@@ -0,0 +1,36 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2003 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *     IBM Corporation - initial API and implementation
+ * Port to the D programming language:
+ *     Frank Benoit <benoit@tionex.de>
+ *******************************************************************************/
+module dwt.internal.image.JPEGEndOfImage;
+
+import dwt.internal.image.JPEGFixedSizeSegment;
+import dwt.internal.image.JPEGFileFormat;
+import dwt.internal.image.LEDataInputStream;
+
+final class JPEGEndOfImage : JPEGFixedSizeSegment {
+
+    public this() {
+        super();
+    }
+
+    public this(byte[] reference) {
+        super(reference);
+    }
+
+    public int signature() {
+        return JPEGFileFormat.EOI;
+    }
+
+    public int fixedSize() {
+        return 2;
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dwt/internal/image/JPEGFileFormat.d	Wed Jan 23 12:01:46 2008 +0100
@@ -0,0 +1,1896 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2006 IBM Corporation and others.
+ * All rights reserved.  This source file is made available under the terms contained in the README file
+ * accompanying this program.  The README file should be located in the about_files directory of the
+ * plug-in that contains this source file.
+ *
+ * Contributors:
+ *     IBM Corporation - initial API and implementation
+ * Port to the D programming language:
+ *     Frank Benoit <benoit@tionex.de>
+ *******************************************************************************/
+module dwt.internal.image.JPEGFileFormat;
+
+import dwt.DWT;
+import dwt.internal.image.JPEGFrameHeader;
+import dwt.internal.image.JPEGScanHeader;
+import dwt.internal.image.JPEGHuffmanTable;
+import dwt.internal.image.JPEGAppn;
+import dwt.internal.image.JPEGSegment;
+import dwt.internal.image.FileFormat;
+import dwt.internal.image.JPEGComment;
+import dwt.internal.image.JPEGArithmeticConditioningTable;
+import dwt.internal.image.JPEGRestartInterval;
+import dwt.internal.image.JPEGQuantizationTable;
+import dwt.internal.image.JPEGStartOfImage;
+import dwt.internal.image.JPEGDecoder;
+import dwt.internal.image.JPEGEndOfImage;
+
+import dwt.graphics.RGB;
+import dwt.graphics.PaletteData;
+
+import tango.core.Exception;
+
+final class JPEGFileFormat : FileFormat {
+    int restartInterval;
+    JPEGFrameHeader frameHeader;
+    int imageWidth, imageHeight;
+    int interleavedMcuCols, interleavedMcuRows;
+    int maxV, maxH;
+    bool progressive;
+    int samplePrecision;
+    int nComponents;
+    int[][] frameComponents;
+    int[] componentIds;
+    byte[][] imageComponents;
+    int[] dataUnit;
+    int[][][] dataUnits;
+    int[] precedingDCs;
+    JPEGScanHeader scanHeader;
+    byte[] dataBuffer;
+    int currentBitCount;
+    int bufferCurrentPosition;
+    int restartsToGo;
+    int nextRestartNumber;
+    JPEGHuffmanTable[] acHuffmanTables;
+    JPEGHuffmanTable[] dcHuffmanTables;
+    int[][] quantizationTables;
+    int currentByte;
+    int encoderQFactor = 75;
+    int eobrun = 0;
+    /* JPEGConstants */
+    public static const int DCTSIZE = 8;
+    public static const int DCTSIZESQR = 64;
+    /* JPEGFixedPointConstants */
+    public static const int FIX_0_899976223 = 7373;
+    public static const int FIX_1_961570560 = 16069;
+    public static const int FIX_2_053119869 = 16819;
+    public static const int FIX_0_298631336 = 2446;
+    public static const int FIX_1_847759065 = 15137;
+    public static const int FIX_1_175875602 = 9633;
+    public static const int FIX_3_072711026 = 25172;
+    public static const int FIX_0_765366865 = 6270;
+    public static const int FIX_2_562915447 = 20995;
+    public static const int FIX_0_541196100 = 4433;
+    public static const int FIX_0_390180644 = 3196;
+    public static const int FIX_1_501321110 = 12299;
+    /* JPEGMarkerCodes */
+    public static const int APP0  = 0xFFE0;
+    public static const int APP15 = 0xFFEF;
+    public static const int COM   = 0xFFFE;
+    public static const int DAC   = 0xFFCC;
+    public static const int DHP   = 0xFFDE;
+    public static const int DHT   = 0xFFC4;
+    public static const int DNL   = 0xFFDC;
+    public static const int DRI   = 0xFFDD;
+    public static const int DQT   = 0xFFDB;
+    public static const int EOI   = 0xFFD9;
+    public static const int EXP   = 0xFFDF;
+    public static const int JPG   = 0xFFC8;
+    public static const int JPG0  = 0xFFF0;
+    public static const int JPG13 = 0xFFFD;
+    public static const int RST0  = 0xFFD0;
+    public static const int RST1  = 0xFFD1;
+    public static const int RST2  = 0xFFD2;
+    public static const int RST3  = 0xFFD3;
+    public static const int RST4  = 0xFFD4;
+    public static const int RST5  = 0xFFD5;
+    public static const int RST6  = 0xFFD6;
+    public static const int RST7  = 0xFFD7;
+    public static const int SOF0  = 0xFFC0;
+    public static const int SOF1  = 0xFFC1;
+    public static const int SOF2  = 0xFFC2;
+    public static const int SOF3  = 0xFFC3;
+    public static const int SOF5  = 0xFFC5;
+    public static const int SOF6  = 0xFFC6;
+    public static const int SOF7  = 0xFFC7;
+    public static const int SOF9  = 0xFFC9;
+    public static const int SOF10 = 0xFFCA;
+    public static const int SOF11 = 0xFFCB;
+    public static const int SOF13 = 0xFFCD;
+    public static const int SOF14 = 0xFFCE;
+    public static const int SOF15 = 0xFFCF;
+    public static const int SOI   = 0xFFD8;
+    public static const int SOS   = 0xFFDA;
+    public static const int TEM   = 0xFF01;
+    /* JPEGFrameComponentParameterConstants */
+    public static const int TQI = 0;
+    public static const int HI  = 1;
+    public static const int VI  = 2;
+    public static const int CW  = 3;
+    public static const int CH  = 4;
+    /* JPEGScanComponentParameterConstants */
+    public static const int DC  = 0;
+    public static const int AC  = 1;
+    /* JFIF Component Constants */
+    public static const int ID_Y        = 1 - 1;
+    public static const int ID_CB   = 2 - 1;
+    public static const int ID_CR   = 3 - 1;
+    public static /*const*/ RGB[] RGB16;
+    public static const int[] ExtendTest = [
+        0, 1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024, 2048,
+        4096, 8192, 16384, 32768, 65536, 131072, 262144
+    ];
+    public static const int[] ExtendOffset = [
+        0, -1, -3, -7, -15, -31, -63, -127, -255, -511, -1023, -2047,
+        -4095, -8191, -16383, -32767, -65535, -131071, -262143
+    ];
+    public static const int[] ZigZag8x8 = [
+        0, 1, 8, 16, 9, 2, 3, 10,
+        17, 24, 32, 25, 18, 11, 4, 5,
+        12, 19, 26, 33, 40, 48, 41, 34,
+        27, 20, 13, 6, 7, 14, 21, 28,
+        35, 42, 49, 56, 57, 50, 43, 36,
+        29, 22, 15, 23, 30, 37, 44, 51,
+        58, 59, 52, 45, 38, 31, 39, 46,
+        53, 60, 61, 54, 47, 55, 62, 63
+    ];
+
+    public static int[] CrRTable, CbBTable, CrGTable, CbGTable;
+    public static int[] RYTable, GYTable, BYTable,
+        RCbTable, GCbTable, BCbTable, RCrTable, GCrTable, BCrTable, NBitsTable;
+    //public static void static_this() {
+    static this() {
+        initialize();
+        RGB16 = [
+            new RGB(0,0,0),
+            new RGB(0x80,0,0),
+            new RGB(0,0x80,0),
+            new RGB(0x80,0x80,0),
+            new RGB(0,0,0x80),
+            new RGB(0x80,0,0x80),
+            new RGB(0,0x80,0x80),
+            new RGB(0xC0,0xC0,0xC0),
+            new RGB(0x80,0x80,0x80),
+            new RGB(0xFF,0,0),
+            new RGB(0,0xFF,0),
+            new RGB(0xFF,0xFF,0),
+            new RGB(0,0,0xFF),
+            new RGB(0xFF,0,0xFF),
+            new RGB(0,0xFF,0xFF),
+            new RGB(0xFF,0xFF,0xFF)
+        ];
+    }
+void compress(ImageData image, byte[] dataYComp, byte[] dataCbComp, byte[] dataCrComp) {
+    int srcWidth = image.width;
+    int srcHeight = image.height;
+    int vhFactor = maxV * maxH;
+    int[] frameComponent;
+    imageComponents = new byte[][](nComponents);
+    for (int i = 0; i < nComponents; i++) {
+        frameComponent = frameComponents[componentIds[i]];
+        imageComponents[i] = new byte[frameComponent[CW] * frameComponent[CH]];
+    }
+    frameComponent = frameComponents[componentIds[ID_Y]];
+    for (int yPos = 0; yPos < srcHeight; yPos++) {
+        int srcOfs = yPos * srcWidth;
+        int dstOfs = yPos * frameComponent[CW];
+        System.arraycopy(dataYComp, srcOfs, imageComponents[ID_Y], dstOfs, srcWidth);
+    }
+    frameComponent = frameComponents[componentIds[ID_CB]];
+    for (int yPos = 0; yPos < srcHeight / maxV; yPos++) {
+        int destRowIndex = yPos * frameComponent[CW];
+        for (int xPos = 0; xPos < srcWidth / maxH; xPos++) {
+            int sum = 0;
+            for (int iv = 0; iv < maxV; iv++) {
+                int srcIndex = (yPos * maxV + iv) * srcWidth + (xPos * maxH);
+                for (int ih = 0; ih < maxH; ih++) {
+                    sum += dataCbComp[srcIndex + ih] & 0xFF;
+                }
+            }
+            imageComponents[ID_CB][destRowIndex + xPos] = cast(byte)(sum / vhFactor);
+        }
+    }
+    frameComponent = frameComponents[componentIds[ID_CR]];
+    for (int yPos = 0; yPos < srcHeight / maxV; yPos++) {
+        int destRowIndex = yPos * frameComponent[CW];
+        for (int xPos = 0; xPos < srcWidth / maxH; xPos++) {
+            int sum = 0;
+            for (int iv = 0; iv < maxV; iv++) {
+                int srcIndex = (yPos * maxV + iv) * srcWidth + (xPos * maxH);
+                for (int ih = 0; ih < maxH; ih++) {
+                    sum += dataCrComp[srcIndex + ih] & 0xFF;
+                }
+            }
+            imageComponents[ID_CR][destRowIndex + xPos] = cast(byte)(sum / vhFactor);
+        }
+    }
+    for (int iComp = 0; iComp < nComponents; iComp++) {
+        byte[] imageComponent = imageComponents[iComp];
+        frameComponent = frameComponents[componentIds[iComp]];
+        int hFactor = frameComponent[HI];
+        int vFactor = frameComponent[VI];
+        int componentWidth = frameComponent[CW];
+        int componentHeight = frameComponent[CH];
+        int compressedWidth = srcWidth / (maxH / hFactor);
+        int compressedHeight = srcHeight / (maxV / vFactor);
+        if (compressedWidth < componentWidth) {
+            int delta = componentWidth - compressedWidth;
+            for (int yPos = 0; yPos < compressedHeight; yPos++) {
+                int dstOfs = ((yPos + 1) * componentWidth - delta);
+                int dataValue = imageComponent[dstOfs - 1] & 0xFF;
+                for (int i = 0; i < delta; i++) {
+                    imageComponent[dstOfs + i] = cast(byte)dataValue;
+                }
+            }
+        }
+        if (compressedHeight < componentHeight) {
+            int srcOfs = (compressedHeight - 1) * componentWidth;
+            for (int yPos = compressedHeight; yPos <= componentHeight; yPos++) {
+                int dstOfs = (yPos - 1) * componentWidth;
+                System.arraycopy(imageComponent, srcOfs, imageComponent, dstOfs, componentWidth);
+            }
+        }
+    }
+}
+void convert4BitRGBToYCbCr(ImageData image) {
+    RGB[] rgbs = image.getRGBs();
+    int paletteSize = rgbs.length;
+    byte[] yComp = new byte[paletteSize];
+    byte[] cbComp = new byte[paletteSize];
+    byte[] crComp = new byte[paletteSize];
+    int srcWidth = image.width;
+    int srcHeight = image.height;
+    for (int i = 0; i < paletteSize; i++) {
+        RGB color = rgbs[i];
+        int r = color.red;
+        int g = color.green;
+        int b = color.blue;
+        int n = RYTable[r] + GYTable[g] + BYTable[b];
+        yComp[i] = cast(byte)(n >> 16);
+        if ((n < 0) && ((n & 0xFFFF) !is 0)) yComp[i]--;
+        n = RCbTable[r] + GCbTable[g] + BCbTable[b];
+        cbComp[i] = cast(byte)(n >> 16);
+        if ((n < 0) && ((n & 0xFFFF) !is 0)) cbComp[i]--;
+        n = RCrTable[r] + GCrTable[g] + BCrTable[b];
+        crComp[i] = cast(byte)(n >> 16);
+        if ((n < 0) && ((n & 0xFFFF) !is 0)) crComp[i]--;
+    }
+    int bSize = srcWidth * srcHeight;
+    byte[] dataYComp = new byte[bSize];
+    byte[] dataCbComp = new byte[bSize];
+    byte[] dataCrComp = new byte[bSize];
+    byte[] origData = image.data;
+    int bytesPerLine = image.bytesPerLine;
+    int maxScanlineByte = srcWidth >> 1;
+    for (int yPos = 0; yPos < srcHeight; yPos++) {
+        for (int xPos = 0; xPos < maxScanlineByte; xPos++) {
+            int srcIndex = yPos * bytesPerLine + xPos;
+            int dstIndex = yPos * srcWidth + (xPos * 2);
+            int value2 = origData[srcIndex] & 0xFF;
+            int value1 = value2 >> 4;
+            value2 &= 0x0F;
+            dataYComp[dstIndex] = yComp[value1];
+            dataCbComp[dstIndex] = cbComp[value1];
+            dataCrComp[dstIndex] = crComp[value1];
+            dataYComp[dstIndex + 1] = yComp[value2];
+            dataCbComp[dstIndex + 1] = cbComp[value2];
+            dataCrComp[dstIndex + 1] = crComp[value2];
+        }
+    }
+    compress(image, dataYComp, dataCbComp, dataCrComp);
+}
+void convert8BitRGBToYCbCr(ImageData image) {
+    RGB[] rgbs = image.getRGBs();
+    int paletteSize = rgbs.length;
+    byte[] yComp = new byte[paletteSize];
+    byte[] cbComp = new byte[paletteSize];
+    byte[] crComp = new byte[paletteSize];
+    int srcWidth = image.width;
+    int srcHeight = image.height;
+    for (int i = 0; i < paletteSize; i++) {
+        RGB color = rgbs[i];
+        int r = color.red;
+        int g = color.green;
+        int b = color.blue;
+        int n = RYTable[r] + GYTable[g] + BYTable[b];
+        yComp[i] = cast(byte)(n >> 16);
+        if ((n < 0) && ((n & 0xFFFF) !is 0)) yComp[i]--;
+        n = RCbTable[r] + GCbTable[g] + BCbTable[b];
+        cbComp[i] = cast(byte)(n >> 16);
+        if ((n < 0) && ((n & 0xFFFF) !is 0)) cbComp[i]--;
+        n = RCrTable[r] + GCrTable[g] + BCrTable[b];
+        crComp[i] = cast(byte)(n >> 16);
+        if ((n < 0) && ((n & 0xFFFF) !is 0)) crComp[i]--;
+    }
+    int dstWidth = image.width;
+    int dstHeight = srcHeight;
+    int stride = ((srcWidth + 3) >> 2) << 2;
+    int bSize = dstWidth * dstHeight;
+    byte[] dataYComp = new byte[bSize];
+    byte[] dataCbComp = new byte[bSize];
+    byte[] dataCrComp = new byte[bSize];
+    byte[] origData = image.data;
+    for (int yPos = 0; yPos < srcHeight; yPos++) {
+        int srcRowIndex = yPos * stride;
+        int dstRowIndex = yPos * dstWidth;
+        for (int xPos = 0; xPos < srcWidth; xPos++) {
+            int value = origData[srcRowIndex + xPos] & 0xFF;
+            int dstIndex = dstRowIndex + xPos;
+            dataYComp[dstIndex] = yComp[value];
+            dataCbComp[dstIndex] = cbComp[value];
+            dataCrComp[dstIndex] = crComp[value];
+        }
+    }
+    compress(image, dataYComp, dataCbComp, dataCrComp);
+}
+byte[] convertCMYKToRGB() {
+    /* Unsupported CMYK format. Answer an empty byte array. */
+    return new byte[0];
+}
+void convertImageToYCbCr(ImageData image) {
+    switch (image.depth) {
+        case 4:
+            convert4BitRGBToYCbCr(image);
+            return;
+        case 8:
+            convert8BitRGBToYCbCr(image);
+            return;
+        case 16:
+        case 24:
+        case 32:
+            convertMultiRGBToYCbCr(image);
+            return;
+        default:
+            DWT.error(DWT.ERROR_UNSUPPORTED_DEPTH);
+    }
+    return;
+}
+void convertMultiRGBToYCbCr(ImageData image) {
+    int srcWidth = image.width;
+    int srcHeight = image.height;
+    int bSize = srcWidth * srcHeight;
+    byte[] dataYComp = new byte[bSize];
+    byte[] dataCbComp = new byte[bSize];
+    byte[] dataCrComp = new byte[bSize];
+    PaletteData palette = image.palette;
+    int[] buffer = new int[srcWidth];
+    if (palette.isDirect) {
+        int redMask = palette.redMask;
+        int greenMask = palette.greenMask;
+        int blueMask = palette.blueMask;
+        int redShift = palette.redShift;
+        int greenShift = palette.greenShift;
+        int blueShift = palette.blueShift;
+        for (int yPos = 0; yPos < srcHeight; yPos++) {
+            image.getPixels(0, yPos, srcWidth, buffer, 0);
+            int dstRowIndex = yPos * srcWidth;
+            for (int xPos = 0; xPos < srcWidth; xPos++) {
+                int pixel = buffer[xPos];
+                int dstDataIndex = dstRowIndex + xPos;
+                int r = pixel & redMask;
+                r = (redShift < 0) ? r >>> -redShift : r << redShift;
+                int g = pixel & greenMask;
+                g = (greenShift < 0) ? g >>> -greenShift : g << greenShift;
+                int b = pixel & blueMask;
+                b = (blueShift < 0) ? b >>> -blueShift : b << blueShift;
+                dataYComp[dstDataIndex] = cast(byte)((RYTable[r] + GYTable[g] + BYTable[b]) >> 16);
+                dataCbComp[dstDataIndex] = cast(byte)((RCbTable[r] + GCbTable[g] + BCbTable[b]) >> 16);
+                dataCrComp[dstDataIndex] = cast(byte)((RCrTable[r] + GCrTable[g] + BCrTable[b]) >> 16);
+            }
+        }
+    } else {
+        for (int yPos = 0; yPos < srcHeight; yPos++) {
+            image.getPixels(0, yPos, srcWidth, buffer, 0);
+            int dstRowIndex = yPos * srcWidth;
+            for (int xPos = 0; xPos < srcWidth; xPos++) {
+                int pixel = buffer[xPos];
+                int dstDataIndex = dstRowIndex + xPos;
+                RGB rgb = palette.getRGB(pixel);
+                int r = rgb.red;
+                int g = rgb.green;
+                int b = rgb.blue;
+                dataYComp[dstDataIndex] = cast(byte)((RYTable[r] + GYTable[g] + BYTable[b]) >> 16);
+                dataCbComp[dstDataIndex] = cast(byte)((RCbTable[r] + GCbTable[g] + BCbTable[b]) >> 16);
+                dataCrComp[dstDataIndex] = cast(byte)((RCrTable[r] + GCrTable[g] + BCrTable[b]) >> 16);
+            }
+        }
+    }
+    compress(image, dataYComp, dataCbComp, dataCrComp);
+}
+byte[] convertYToRGB() {
+    int compWidth = frameComponents[componentIds[ID_Y]][CW];
+    int bytesPerLine = (((imageWidth * 8 + 7) / 8) + 3) / 4 * 4;
+    byte[] data = new byte[bytesPerLine * imageHeight];
+    byte[] yComp = imageComponents[ID_Y];
+    int destIndex = 0;
+    for (int i = 0; i < imageHeight; i++) {
+        int srcIndex = i * compWidth;
+        for (int j = 0; j < bytesPerLine; j++) {
+            int y = yComp[srcIndex] & 0xFF;
+            if (y < 0) {
+                y = 0;
+            } else {
+                if (y > 255) y = 255;
+            }
+            if (j >= imageWidth) {
+                y = 0;
+            }
+            data[destIndex] = cast(byte)y;
+            srcIndex++;
+            destIndex++;
+        }
+    }
+    return data;
+}
+byte[] convertYCbCrToRGB() {
+    /**
+     * Convert existing image components into an RGB format.
+     * YCbCr is defined per CCIR 601-1, except that Cb and Cr are
+     * normalized to the range 0..MAXJSAMPLE rather than -0.5 .. 0.5.
+     * The conversion equations to be implemented are therefore
+     *  R = Y                + 1.40200 * Cr
+     *  G = Y - 0.34414 * Cb - 0.71414 * Cr
+     *  B = Y + 1.77200 * Cb
+     * where Cb and Cr represent the incoming values less MAXJSAMPLE/2.
+     * (These numbers are derived from TIFF 6.0 section 21, dated 3-June-92.)
+     *
+     * To avoid floating-point arithmetic, we represent the fractional constants
+     * as integers scaled up by 2^16 (about 4 digits precision); we have to divide
+     * the products by 2^16, with appropriate rounding, to get the correct answer.
+     * Notice that Y, being an integral input, does not contribute any fraction
+     * so it need not participate in the rounding.
+     *
+     * For even more speed, we avoid doing any multiplications in the inner loop
+     * by precalculating the constants times Cb and Cr for all possible values.
+     * For 8-bit JSAMPLEs this is very reasonable (only 256 entries per table);
+     * for 12-bit samples it is still acceptable.  It's not very reasonable for
+     * 16-bit samples, but if you want lossless storage you shouldn't be changing
+     * colorspace anyway.
+     * The Cr=>R and Cb=>B values can be rounded to integers in advance; the
+     * values for the G calculation are left scaled up, since we must add them
+     * together before rounding.
+     */
+    int bSize = imageWidth * imageHeight * nComponents;
+    byte[] rgbData = new byte[bSize];
+    int destIndex = 0;
+    expandImageComponents();
+    byte[] yComp = imageComponents[ID_Y];
+    byte[] cbComp = imageComponents[ID_CB];
+    byte[] crComp = imageComponents[ID_CR];
+    int compWidth = frameComponents[componentIds[ID_Y]][CW];
+    for (int v = 0; v < imageHeight; v++) {
+        int srcIndex = v * compWidth;
+        for (int i = 0; i < imageWidth; i++) {
+            int y = yComp[srcIndex] & 0xFF;
+            int cb = cbComp[srcIndex] & 0xFF;
+            int cr = crComp[srcIndex] & 0xFF;
+            int r = y + CrRTable[cr];
+            int g = y + ((CbGTable[cb] + CrGTable[cr]) >> 16);
+            int b = y + CbBTable[cb];
+            if (r < 0) {
+                r = 0;
+            } else {
+                if (r > 255) r = 255;
+            }
+            if (g < 0) {
+                g = 0;
+            } else {
+                if (g > 255) g = 255;
+            }
+            if (b < 0) {
+                b = 0;
+            } else {
+                if (b > 255) b = 255;
+            }
+            rgbData[destIndex] = cast(byte)b;
+            rgbData[destIndex + 1] = cast(byte)g;
+            rgbData[destIndex + 2] = cast(byte)r;
+            destIndex += 3;
+            srcIndex++;
+        }
+    }
+    return rgbData;
+}
+void decodeACCoefficients(int[] dataUnit, int iComp) {
+    int[] sParams = scanHeader.componentParameters[componentIds[iComp]];
+    JPEGHuffmanTable acTable = acHuffmanTables[sParams[AC]];
+    int k = 1;
+    while (k < 64) {
+        int rs = decodeUsingTable(acTable);
+        int r = rs >> 4;
+        int s = rs & 0xF;
+        if (s is 0) {
+            if (r is 15) {
+                k += 16;
+            } else {
+                break;
+            }
+        } else {
+            k += r;
+            int bits = receive(s);
+            dataUnit[ZigZag8x8[k]] = extendBy(bits, s);
+            k++;
+        }
+    }
+}
+void decodeACFirstCoefficients(int[] dataUnit, int iComp, int start, int end, int approxBit) {
+    if (eobrun > 0) {
+        eobrun--;
+        return;
+    }
+    int[] sParams = scanHeader.componentParameters[componentIds[iComp]];
+    JPEGHuffmanTable acTable = acHuffmanTables[sParams[AC]];
+    int k = start;
+    while (k <= end) {
+        int rs = decodeUsingTable(acTable);
+        int r = rs >> 4;
+        int s = rs & 0xF;
+        if (s is 0) {
+            if (r is 15) {
+                k += 16;
+            } else {
+                eobrun = (1 << r) + receive(r) - 1;
+                break;
+            }
+        } else {
+            k += r;
+            int bits = receive(s);
+            dataUnit[ZigZag8x8[k]] = extendBy(bits, s) << approxBit;
+            k++;
+        }
+    }
+}
+void decodeACRefineCoefficients(int[] dataUnit, int iComp, int start, int end, int approxBit) {
+    int[] sParams = scanHeader.componentParameters[componentIds[iComp]];
+    JPEGHuffmanTable acTable = acHuffmanTables[sParams[AC]];
+    int k = start;
+    while (k <= end) {
+        if (eobrun > 0) {
+            while (k <= end) {
+                int zzIndex = ZigZag8x8[k];
+                if (dataUnit[zzIndex] !is 0) {
+                    dataUnit[zzIndex] = refineAC(dataUnit[zzIndex], approxBit);
+                }
+                k++;
+            }
+            eobrun--;
+        } else {
+            int rs = decodeUsingTable(acTable);
+            int r = rs >> 4;
+            int s = rs & 0xF;
+            if (s is 0) {
+                if (r is 15) {
+                    int zeros = 0;
+                    while (zeros < 16 && k <= end) {
+                        int zzIndex = ZigZag8x8[k];
+                        if (dataUnit[zzIndex] !is 0) {
+                            dataUnit[zzIndex] = refineAC(dataUnit[zzIndex], approxBit);
+                        } else {
+                            zeros++;
+                        }
+                        k++;
+                    }
+                } else {
+                    eobrun = (1 << r) + receive(r);
+                }
+            } else {
+                int bit = receive(s);
+                int zeros = 0;
+                int zzIndex = ZigZag8x8[k];
+                while ((zeros < r || dataUnit[zzIndex] !is 0) && k <= end) {
+                    if (dataUnit[zzIndex] !is 0) {
+                        dataUnit[zzIndex] = refineAC(dataUnit[zzIndex], approxBit);
+                    } else {
+                        zeros++;
+                    }
+                    k++;
+                    zzIndex = ZigZag8x8[k];
+                }
+                if (bit !is 0) {
+                    dataUnit[zzIndex] = 1 << approxBit;
+                } else {
+                    dataUnit[zzIndex] = -1 << approxBit;
+                }
+                k++;
+            }
+        }
+    }
+}
+int refineAC(int ac, int approxBit) {
+    if (ac > 0) {
+        int bit = nextBit();
+        if (bit !is 0) {
+            ac += 1 << approxBit;
+        }
+    } else if (ac < 0) {
+        int bit = nextBit();
+        if (bit !is 0) {
+            ac += -1 << approxBit;
+        }
+    }
+    return ac;
+}
+void decodeDCCoefficient(int[] dataUnit, int iComp, bool first, int approxBit) {
+    int[] sParams = scanHeader.componentParameters[componentIds[iComp]];
+    JPEGHuffmanTable dcTable = dcHuffmanTables[sParams[DC]];
+    int lastDC = 0;
+    if (progressive && !first) {
+        int bit = nextBit();
+        lastDC = dataUnit[0] + (bit << approxBit);
+    } else {
+        lastDC = precedingDCs[iComp];
+        int nBits = decodeUsingTable(dcTable);
+        if (nBits !is 0) {
+            int bits = receive(nBits);
+            int diff = extendBy(bits, nBits);
+            lastDC += diff;
+            precedingDCs[iComp] = lastDC;
+        }
+        if (progressive) {
+            lastDC = lastDC << approxBit;
+        }
+    }
+    dataUnit[0] = lastDC;
+}
+void dequantize(int[] dataUnit, int iComp) {
+    int[] qTable = quantizationTables[frameComponents[componentIds[iComp]][TQI]];
+    for (int i = 0; i < dataUnit.length; i++) {
+        int zzIndex = ZigZag8x8[i];
+        dataUnit[zzIndex] = dataUnit[zzIndex] * qTable[i];
+    }
+}
+byte[] decodeImageComponents() {
+    if (nComponents is 3) { // compIds 1, 2, 3
+        return convertYCbCrToRGB();
+    }
+//  if (nComponents is 3) { // compIds 1, 4, 5
+//      Unsupported CMYK format.
+//      return convertYIQToRGB();
+//  }
+    if (nComponents is 4) {
+        return convertCMYKToRGB();
+    }
+    return convertYToRGB();
+}
+void decodeMCUAtXAndY(int xmcu, int ymcu, int nComponentsInScan, bool first, int start, int end, int approxBit) {
+    for (int iComp = 0; iComp < nComponentsInScan; iComp++) {
+        int scanComponent = iComp;
+        while (scanHeader.componentParameters[componentIds[scanComponent]] is null) {
+            scanComponent++;
+        }
+        int[] frameComponent = frameComponents[componentIds[scanComponent]];
+        int hi = frameComponent[HI];
+        int vi = frameComponent[VI];
+        if (nComponentsInScan is 1) {
+            hi = 1;
+            vi = 1;
+        }
+        int compWidth = frameComponent[CW];
+        for (int ivi = 0; ivi < vi; ivi++) {
+            for (int ihi = 0; ihi < hi; ihi++) {
+                if (progressive) {
+                    // Progressive: First scan - create a new data unit.
+                    // Subsequent scans - refine the existing data unit.
+                    int index = (ymcu * vi + ivi) * compWidth + xmcu * hi + ihi;
+                    dataUnit = dataUnits[scanComponent][index];
+                    if (dataUnit is null) {
+                        dataUnit = new int[64];
+                        dataUnits[scanComponent][index] = dataUnit;
+                    }
+                } else {
+                    // Sequential: Clear and reuse the data unit buffer.
+                    for (int i = 0; i < dataUnit.length; i++) {
+                        dataUnit[i] = 0;
+                    }
+                }
+                if (!progressive || scanHeader.isDCProgressiveScan()) {
+                    decodeDCCoefficient(dataUnit, scanComponent, first, approxBit);
+                }
+                if (!progressive) {
+                    decodeACCoefficients(dataUnit, scanComponent);
+                } else {
+                    if (scanHeader.isACProgressiveScan()) {
+                        if (first) {
+                            decodeACFirstCoefficients(dataUnit, scanComponent, start, end, approxBit);
+                        } else {
+                            decodeACRefineCoefficients(dataUnit, scanComponent, start, end, approxBit);
+                        }
+                    }
+                    if (loader.hasListeners()) {
+                        // Dequantization, IDCT, up-sampling and color conversion
+                        // are done on a copy of the coefficient data in order to
+                        // display the image incrementally.
+                        int[] temp = dataUnit;
+                        dataUnit = new int[64];
+                        System.arraycopy(temp, 0, dataUnit, 0, 64);
+                    }
+                }
+                if (!progressive || (progressive && loader.hasListeners())) {
+                    dequantize(dataUnit, scanComponent);
+                    inverseDCT(dataUnit);
+                    storeData(dataUnit, scanComponent, xmcu, ymcu, hi, ihi, vi, ivi);
+                }
+            }
+        }
+    }
+}
+void decodeScan() {
+    if (progressive && !scanHeader.verifyProgressiveScan()) {
+        DWT.error(DWT.ERROR_INVALID_IMAGE);
+    }
+    int nComponentsInScan = scanHeader.getNumberOfImageComponents();
+    int mcuRowsInScan = interleavedMcuRows;
+    int mcusPerRow = interleavedMcuCols;
+    if (nComponentsInScan is 1) {
+        // Non-interleaved.
+        int scanComponent = 0;
+        while (scanHeader.componentParameters[componentIds[scanComponent]] is null) {
+            scanComponent++;
+        }
+        int[] frameComponent = frameComponents[componentIds[scanComponent]];
+        int hi = frameComponent[HI];
+        int vi = frameComponent[VI];
+        int mcuWidth = DCTSIZE * maxH / hi;
+        int mcuHeight = DCTSIZE * maxV / vi;
+        mcusPerRow = (imageWidth + mcuWidth - 1) / mcuWidth;
+        mcuRowsInScan = (imageHeight + mcuHeight - 1) / mcuHeight;
+    }
+    bool first = scanHeader.isFirstScan();
+    int start = scanHeader.getStartOfSpectralSelection();
+    int end = scanHeader.getEndOfSpectralSelection();
+    int approxBit = scanHeader.getApproxBitPositionLow();
+    restartsToGo = restartInterval;
+    nextRestartNumber = 0;
+    for (int ymcu = 0; ymcu < mcuRowsInScan; ymcu++) {
+        for (int xmcu = 0; xmcu < mcusPerRow; xmcu++) {
+            if (restartInterval !is 0) {
+                if (restartsToGo is 0) processRestartInterval();
+                restartsToGo--;
+            }
+            decodeMCUAtXAndY(xmcu, ymcu, nComponentsInScan, first, start, end, approxBit);
+        }
+    }
+}
+int decodeUsingTable(JPEGHuffmanTable huffmanTable) {
+    int i = 0;
+    int[] maxCodes = huffmanTable.getDhMaxCodes();
+    int[] minCodes = huffmanTable.getDhMinCodes();
+    int[] valPtrs = huffmanTable.getDhValPtrs();
+    int[] huffVals = huffmanTable.getDhValues();
+    int code = nextBit();
+    while (code > maxCodes[i]) {
+        code = code * 2 + nextBit();
+        i++;
+    }
+    int j = valPtrs[i] + code - minCodes[i];
+    return huffVals[j];
+}
+void emit(int huffCode, int nBits) {
+    if (nBits is 0) {
+        DWT.error(DWT.ERROR_INVALID_IMAGE);
+    }
+    int[] power2m1 = [
+        1, 3, 7, 15, 31, 63, 127, 255, 511, 1023, 2047, 4095, 8191,
+        16383, 32767, 65535, 131125
+    ];
+    int code = (huffCode & power2m1[nBits - 1]) << (24 - nBits - currentBitCount);
+    byte[] codeBuffer = new byte[4];
+    codeBuffer[0] = cast(byte)(code & 0xFF);
+    codeBuffer[1] = cast(byte)((code >> 8) & 0xFF);
+    codeBuffer[2] = cast(byte)((code >> 16) & 0xFF);
+    codeBuffer[3] = cast(byte)((code >> 24) & 0xFF);
+    int abs = nBits - (8 - currentBitCount);
+    if (abs < 0) abs = -abs;
+    if ((abs >> 3) > 0) {
+        currentByte += codeBuffer[2];
+        emitByte(cast(byte)currentByte);
+        emitByte(codeBuffer[1]);
+        currentByte = codeBuffer[0];
+        currentBitCount += nBits - 16;
+    } else {
+        currentBitCount += nBits;
+        if (currentBitCount >= 8) {
+            currentByte += codeBuffer[2];
+            emitByte(cast(byte)currentByte);
+            currentByte = codeBuffer[1];
+            currentBitCount -= 8;
+        } else {
+            currentByte += codeBuffer[2];
+        }
+    }
+}
+void emitByte(byte byteValue) {
+    if (bufferCurrentPosition >= 512) {
+        resetOutputBuffer();
+    }
+    dataBuffer[bufferCurrentPosition] = byteValue;
+    bufferCurrentPosition++;
+    if (byteValue is -1) {
+        emitByte(cast(byte)0);
+    }
+}
+void encodeACCoefficients(int[] dataUnit, int iComp) {
+    int[] sParams = scanHeader.componentParameters[iComp];
+    JPEGHuffmanTable acTable = acHuffmanTables[sParams[AC]];
+    int[] ehCodes = acTable.ehCodes;
+    byte[] ehSizes = acTable.ehCodeLengths;
+    int r = 0;
+    int k = 1;
+    while (k < 64) {
+        k++;
+        int acValue = dataUnit[ZigZag8x8[k - 1]];
+        if (acValue is 0) {
+            if (k is 64) {
+                emit(ehCodes[0], ehSizes[0] & 0xFF);
+            } else {
+                r++;
+            }
+        } else {
+            while (r > 15) {
+                emit(ehCodes[0xF0], ehSizes[0xF0] & 0xFF);
+                r -= 16;
+            }
+            if (acValue < 0) {
+                int absACValue = acValue;
+                if (absACValue < 0) absACValue = -absACValue;
+                int nBits = NBitsTable[absACValue];
+                int rs = r * 16 + nBits;
+                emit(ehCodes[rs], ehSizes[rs] & 0xFF);
+                emit(0xFFFFFF - absACValue, nBits);
+            } else {
+                int nBits = NBitsTable[acValue];
+                int rs = r * 16 + nBits;
+                emit(ehCodes[rs], ehSizes[rs] & 0xFF);
+                emit(acValue, nBits);
+            }
+            r = 0;
+        }
+    }
+}
+void encodeDCCoefficients(int[] dataUnit, int iComp) {
+    int[] sParams = scanHeader.componentParameters[iComp];
+    JPEGHuffmanTable dcTable = dcHuffmanTables[sParams[DC]];
+    int lastDC = precedingDCs[iComp];
+    int dcValue = dataUnit[0];
+    int diff = dcValue - lastDC;
+    precedingDCs[iComp] = dcValue;
+    if (diff < 0) {
+        int absDiff = 0 - diff;
+        int nBits = NBitsTable[absDiff];
+        emit(dcTable.ehCodes[nBits], dcTable.ehCodeLengths[nBits]);
+        emit(0xFFFFFF - absDiff, nBits);
+    } else {
+        int nBits = NBitsTable[diff];
+        emit(dcTable.ehCodes[nBits], dcTable.ehCodeLengths[nBits]);
+        if (nBits !is 0) {
+            emit(diff, nBits);
+        }
+    }
+}
+void encodeMCUAtXAndY(int xmcu, int ymcu) {
+    int nComponentsInScan = scanHeader.getNumberOfImageComponents();
+    dataUnit = new int[64];
+    for (int iComp = 0; iComp < nComponentsInScan; iComp++) {
+        int[] frameComponent = frameComponents[componentIds[iComp]];
+        int hi = frameComponent[HI];
+        int vi = frameComponent[VI];
+        for (int ivi = 0; ivi < vi; ivi++) {
+            for (int ihi = 0; ihi < hi; ihi++) {
+                extractData(dataUnit, iComp, xmcu, ymcu, ihi, ivi);
+                forwardDCT(dataUnit);
+                quantizeData(dataUnit, iComp);
+                encodeDCCoefficients(dataUnit, iComp);
+                encodeACCoefficients(dataUnit, iComp);
+            }
+        }
+    }
+}
+void encodeScan() {
+    for (int ymcu = 0; ymcu < interleavedMcuRows; ymcu++) {
+        for (int xmcu = 0; xmcu < interleavedMcuCols; xmcu++) {
+            encodeMCUAtXAndY(xmcu, ymcu);
+        }
+    }
+    if (currentBitCount !is 0) {
+        emitByte(cast(byte)currentByte);
+    }
+    resetOutputBuffer();
+}
+void expandImageComponents() {
+    for (int iComp = 0; iComp < nComponents; iComp++) {
+        int[] frameComponent = frameComponents[componentIds[iComp]];
+        int hi = frameComponent[HI];
+        int vi = frameComponent[VI];
+        int upH = maxH / hi;
+        int upV = maxV / vi;
+        if ((upH * upV) > 1) {
+            byte[] component = imageComponents[iComp];
+            int compWidth = frameComponent[CW];
+            int compHeight = frameComponent[CH];
+            int upCompWidth = compWidth * upH;
+            int upCompHeight = compHeight * upV;
+            ImageData src = new ImageData(compWidth, compHeight, 8, new PaletteData(RGB16), 4, component);
+            ImageData dest = src.scaledTo(upCompWidth, upCompHeight);
+            imageComponents[iComp] = dest.data;
+        }
+    }
+}
+int extendBy(int diff, int t) {
+    if (diff < ExtendTest[t]) {
+        return diff + ExtendOffset[t];
+    } else {
+        return diff;
+    }
+}
+void extractData(int[] dataUnit, int iComp, int xmcu, int ymcu, int ihi, int ivi) {
+    byte[] compImage = imageComponents[iComp];
+    int[] frameComponent = frameComponents[componentIds[iComp]];
+    int hi = frameComponent[HI];
+    int vi = frameComponent[VI];
+    int compWidth = frameComponent[CW];
+    int srcIndex = ((ymcu * vi + ivi) * compWidth * DCTSIZE) + ((xmcu * hi + ihi) * DCTSIZE);
+    int destIndex = 0;
+    for (int i = 0; i < DCTSIZE; i++) {
+        for (int col = 0; col < DCTSIZE; col++) {
+            dataUnit[destIndex] = (compImage[srcIndex + col] & 0xFF) - 128;
+            destIndex++;
+        }
+        srcIndex += compWidth;
+    }
+}
+void forwardDCT(int[] dataUnit) {
+    for (int row = 0; row < 8; row++) {
+        int rIndex = row * DCTSIZE;
+        int tmp0 = dataUnit[rIndex] + dataUnit[rIndex + 7];
+        int tmp7 = dataUnit[rIndex] - dataUnit[rIndex + 7];
+        int tmp1 = dataUnit[rIndex + 1] + dataUnit[rIndex + 6];
+        int tmp6 = dataUnit[rIndex + 1] - dataUnit[rIndex + 6];
+        int tmp2 = dataUnit[rIndex + 2] + dataUnit[rIndex + 5];
+        int tmp5 = dataUnit[rIndex + 2] - dataUnit[rIndex + 5];
+        int tmp3 = dataUnit[rIndex + 3] + dataUnit[rIndex + 4];
+        int tmp4 = dataUnit[rIndex + 3] - dataUnit[rIndex + 4];
+
+        /**
+         * Even part per LL&M figure 1 --- note that published figure
+         * is faulty; rotator 'sqrt(2)*c1' should be 'sqrt(2)*c6'.
+         */
+        int tmp10 = tmp0 + tmp3;
+        int tmp13 = tmp0 - tmp3;
+        int tmp11 = tmp1 + tmp2;
+        int tmp12 = tmp1 - tmp2;
+
+        dataUnit[rIndex] = (tmp10 + tmp11) * 4;
+        dataUnit[rIndex + 4]  = (tmp10 - tmp11) * 4;
+
+        int z1 = (tmp12 + tmp13) * FIX_0_541196100;
+        int n = z1 + (tmp13 * FIX_0_765366865) + 1024;
+        dataUnit[rIndex + 2] = n >> 11;
+        if ((n < 0) && ((n & 0x07FF) !is 0)) dataUnit[rIndex + 2]--;
+        n = z1 + (tmp12 * (0 - FIX_1_847759065)) + 1024;
+        dataUnit[rIndex + 6] = n >> 11;
+        if ((n < 0) && ((n & 0x07FF) !is 0)) dataUnit[rIndex + 6]--;
+
+        /**
+         * Odd part per figure 8 --- note paper omits factor of sqrt(2).
+         * cK represents cos(K*pi/16).
+         * i0..i3 in the paper are tmp4..tmp7 here.
+         */
+        z1 = tmp4 + tmp7;
+        int z2 = tmp5 + tmp6;
+        int z3 = tmp4 + tmp6;
+        int z4 = tmp5 + tmp7;
+        int z5 = (z3 + z4) * FIX_1_175875602;   // sqrt(2) * c3
+
+        tmp4 *= FIX_0_298631336;    // sqrt(2) * (-c1+c3+c5-c7)
+        tmp5 *= FIX_2_053119869;    // sqrt(2) * ( c1+c3-c5+c7)
+        tmp6 *= FIX_3_072711026;    // sqrt(2) * ( c1+c3+c5-c7)
+        tmp7 *= FIX_1_501321110;    // sqrt(2) * ( c1+c3-c5-c7)
+        z1 *= 0 - FIX_0_899976223;  // sqrt(2) * (c7-c3)
+        z2 *= 0 - FIX_2_562915447;  // sqrt(2) * (-c1-c3)
+        z3 *= 0 - FIX_1_961570560;  // sqrt(2) * (-c3-c5)
+        z4 *= 0 - FIX_0_390180644;  // sqrt(2) * (c5-c3)
+
+        z3 += z5;
+        z4 += z5;
+
+        n = tmp4 + z1 + z3 + 1024;
+        dataUnit[rIndex + 7] = n >> 11;
+        if ((n < 0) && ((n & 0x07FF) !is 0)) dataUnit[rIndex + 7]--;
+        n = tmp5 + z2 + z4 + 1024;
+        dataUnit[rIndex + 5] = n >> 11;
+        if ((n < 0) && ((n & 0x07FF) !is 0)) dataUnit[rIndex + 5]--;
+        n = tmp6 + z2 + z3 + 1024;
+        dataUnit[rIndex + 3] = n >> 11;
+        if ((n < 0) && ((n & 0x07FF) !is 0)) dataUnit[rIndex + 3]--;
+        n = tmp7 + z1 + z4 + 1024;
+        dataUnit[rIndex + 1] = n >> 11;
+        if ((n < 0) && ((n & 0x07FF) !is 0)) dataUnit[rIndex + 1]--;
+    }
+
+    /**
+     * Pass 2: process columns.
+     * Note that we must descale the results by a factor of 8 is 2**3,
+     * and also undo the PASS1_BITS scaling.
+     */
+    for (int col = 0; col < 8; col++) {
+        int c0 = col;
+        int c1 = col + 8;
+        int c2 = col + 16;
+        int c3 = col + 24;
+        int c4 = col + 32;
+        int c5 = col + 40;
+        int c6 = col + 48;
+        int c7 = col + 56;
+        int tmp0 = dataUnit[c0] + dataUnit[c7];
+        int tmp7 = dataUnit[c0] - dataUnit[c7];
+        int tmp1 = dataUnit[c1] + dataUnit[c6];
+        int tmp6 = dataUnit[c1] - dataUnit[c6];
+        int tmp2 = dataUnit[c2] + dataUnit[c5];
+        int tmp5 = dataUnit[c2] - dataUnit[c5];
+        int tmp3 = dataUnit[c3] + dataUnit[c4];
+        int tmp4 = dataUnit[c3] - dataUnit[c4];
+
+        /**
+         * Even part per LL&M figure 1 --- note that published figure
+         * is faulty; rotator 'sqrt(2)*c1' should be 'sqrt(2)*c6'.
+         */
+        int tmp10 = tmp0 + tmp3;
+        int tmp13 = tmp0 - tmp3;
+        int tmp11 = tmp1 + tmp2;
+        int tmp12 = tmp1 - tmp2;
+
+        int n = tmp10 + tmp11 + 16;
+        dataUnit[c0] = n >> 5;
+        if ((n < 0) && ((n & 0x1F) !is 0)) dataUnit[c0]--;
+        n = tmp10 - tmp11 + 16;
+        dataUnit[c4] = n >> 5;
+        if ((n < 0) && ((n & 0x1F) !is 0)) dataUnit[c4]--;
+
+        int z1 = (tmp12 + tmp13) * FIX_0_541196100;
+        n = z1 + (tmp13 * FIX_0_765366865) + 131072;
+        dataUnit[c2] = n >> 18;
+        if ((n < 0) && ((n & 0x3FFFF) !is 0)) dataUnit[c2]--;
+        n = z1 + (tmp12 * (0 - FIX_1_847759065)) + 131072;
+        dataUnit[c6] = n >> 18;
+        if ((n < 0) && ((n & 0x3FFFF) !is 0)) dataUnit[c6]--;
+
+        /**
+         * Odd part per figure 8 --- note paper omits factor of sqrt(2).
+         * cK represents cos(K*pi/16).
+         * i0..i3 in the paper are tmp4..tmp7 here.
+         */
+        z1 = tmp4 + tmp7;
+        int z2 = tmp5 + tmp6;
+        int z3 = tmp4 + tmp6;
+        int z4 = tmp5 + tmp7;
+        int z5 = (z3 + z4) * FIX_1_175875602;   // sqrt(2) * c3
+
+        tmp4 *= FIX_0_298631336;    // sqrt(2) * (-c1+c3+c5-c7)
+        tmp5 *= FIX_2_053119869;    // sqrt(2) * ( c1+c3-c5+c7)
+        tmp6 *= FIX_3_072711026;    // sqrt(2) * ( c1+c3+c5-c7)
+        tmp7 *= FIX_1_501321110;    // sqrt(2) * ( c1+c3-c5-c7)
+        z1 *= 0 - FIX_0_899976223;  // sqrt(2) * (c7-c3)
+        z2 *= 0 - FIX_2_562915447;  // sqrt(2) * (-c1-c3)
+        z3 *= 0 - FIX_1_961570560;  // sqrt(2) * (-c3-c5)
+        z4 *= 0 - FIX_0_390180644;  // sqrt(2) * (c5-c3)
+
+        z3 += z5;
+        z4 += z5;
+
+        n = tmp4 + z1 + z3 + 131072;
+        dataUnit[c7] = n >> 18;
+        if ((n < 0) && ((n & 0x3FFFF) !is 0)) dataUnit[c7]--;
+        n = tmp5 + z2 + z4 + 131072;
+        dataUnit[c5] = n >> 18;
+        if ((n < 0) && ((n & 0x3FFFF) !is 0)) dataUnit[c5]--;
+        n = tmp6 + z2 + z3 + 131072;
+        dataUnit[c3] = n >> 18;
+        if ((n < 0) && ((n & 0x3FFFF) !is 0)) dataUnit[c3]--;
+        n = tmp7 + z1 + z4 + 131072;
+        dataUnit[c1] = n >> 18;
+        if ((n < 0) && ((n & 0x3FFFF) !is 0)) dataUnit[c1]--;
+    }
+}
+void getAPP0() {
+    JPEGAppn appn = new JPEGAppn(inputStream);
+    if (!appn.verify()) {
+        DWT.error(DWT.ERROR_INVALID_IMAGE);
+    }
+}
+void getCOM() {
+    new JPEGComment(inputStream);
+}
+void getDAC() {
+    new JPEGArithmeticConditioningTable(inputStream);
+}
+void getDHT() {
+    JPEGHuffmanTable dht = new JPEGHuffmanTable(inputStream);
+    if (!dht.verify()) {
+        DWT.error(DWT.ERROR_INVALID_IMAGE);
+    }
+    if (acHuffmanTables is null) {
+        acHuffmanTables = new JPEGHuffmanTable[4];
+    }
+    if (dcHuffmanTables is null) {
+        dcHuffmanTables = new JPEGHuffmanTable[4];
+    }
+    JPEGHuffmanTable[] dhtTables = dht.getAllTables();
+    for (int i = 0; i < dhtTables.length; i++) {
+        JPEGHuffmanTable dhtTable = dhtTables[i];
+        if (dhtTable.getTableClass() is 0) {
+            dcHuffmanTables[dhtTable.getTableIdentifier()] = dhtTable;
+        } else {
+            acHuffmanTables[dhtTable.getTableIdentifier()] = dhtTable;
+        }
+    }
+}
+void getDNL() {
+    new JPEGRestartInterval(inputStream);
+}
+void getDQT() {
+    JPEGQuantizationTable dqt = new JPEGQuantizationTable(inputStream);
+    int[][] currentTables = quantizationTables;
+    if (currentTables is null) {
+        currentTables = new int[][](4);
+    }
+    int[] dqtTablesKeys = dqt.getQuantizationTablesKeys();
+    int[][] dqtTablesValues = dqt.getQuantizationTablesValues();
+    for (int i = 0; i < dqtTablesKeys.length; i++) {
+        int index = dqtTablesKeys[i];
+        currentTables[index] = dqtTablesValues[i];
+    }
+    quantizationTables = currentTables;
+}
+void getDRI() {
+    JPEGRestartInterval dri = new JPEGRestartInterval(inputStream);
+    if (!dri.verify()) {
+        DWT.error(DWT.ERROR_INVALID_IMAGE);
+    }
+    restartInterval = dri.getRestartInterval();
+}
+static void initialize() {
+    initializeRGBYCbCrTables();
+    initializeYCbCrRGBTables();
+    initializeBitCountTable();
+}
+static void initializeBitCountTable() {
+    int nBits = 1;
+    int power2 = 2;
+    NBitsTable = new int[2048];
+    NBitsTable[0] = 0;
+    for (int i = 1; i < NBitsTable.length; i++) {
+        if (!(i < power2)) {
+            nBits++;
+            power2 *= 2;
+        }
+        NBitsTable[i] = nBits;
+    }
+}
+static void initializeRGBYCbCrTables() {
+    RYTable = new int[256];
+    GYTable = new int[256];
+    BYTable = new int[256];
+    RCbTable = new int[256];
+    GCbTable = new int[256];
+    BCbTable = new int[256];
+    RCrTable = BCbTable;
+    GCrTable = new int[256];
+    BCrTable = new int[256];
+    for (int i = 0; i < 256; i++) {
+        RYTable[i] = i * 19595;
+        GYTable[i] = i * 38470;
+        BYTable[i] = i * 7471 + 32768;
+        RCbTable[i] = i * -11059;
+        GCbTable[i] = i * -21709;
+        BCbTable[i] = i * 32768 + 8388608;
+        GCrTable[i] = i * -27439;
+        BCrTable[i] = i * -5329;
+    }
+}
+static void initializeYCbCrRGBTables() {
+    CrRTable = new int[256];
+    CbBTable = new int[256];
+    CrGTable = new int[256];
+    CbGTable = new int[256];
+    for (int i = 0; i < 256; i++) {
+        int x2 = 2 * i - 255;
+        CrRTable[i] = (45941 * x2 + 32768) >> 16;
+        CbBTable[i] = (58065 * x2 + 32768) >> 16;
+        CrGTable[i] = -23401 * x2;
+        CbGTable[i] = -11277 * x2 + 32768;
+    }
+}
+void inverseDCT(int[] dataUnit) {
+    for (int row = 0; row < 8; row++) {
+        int rIndex = row * DCTSIZE;
+        /**
+         * Due to quantization, we will usually find that many of the input
+         * coefficients are zero, especially the AC terms.  We can exploit this
+         * by short-circuiting the IDCT calculation for any row in which all
+         * the AC terms are zero.  In that case each output is equal to the
+         * DC coefficient (with scale factor as needed).
+         * With typical images and quantization tables, half or more of the
+         * row DCT calculations can be simplified this way.
+         */
+        if (isZeroInRow(dataUnit, rIndex)) {
+            int dcVal = dataUnit[rIndex] << 2;
+            for (int i = rIndex + 7; i >= rIndex; i--) {
+                dataUnit[i] = dcVal;
+            }
+        } else {
+            /**
+             * Even part: reverse the even part of the forward DCT.
+             * The rotator is sqrt(2)*c(-6).
+             */
+            int z2 = dataUnit[rIndex + 2];
+            int z3 = dataUnit[rIndex + 6];
+            int z1 = (z2 + z3) * FIX_0_541196100;
+            int tmp2 = z1 + (z3 * (0 - FIX_1_847759065));
+            int tmp3 = z1 + (z2 * FIX_0_765366865);
+            int tmp0 = (dataUnit[rIndex] + dataUnit[rIndex + 4]) << 13;
+            int tmp1 = (dataUnit[rIndex] - dataUnit[rIndex + 4]) << 13;
+            int tmp10 = tmp0 + tmp3;
+            int tmp13 = tmp0 - tmp3;
+            int tmp11 = tmp1 + tmp2;
+            int tmp12 = tmp1 - tmp2;
+            /**
+             * Odd part per figure 8; the matrix is unitary and hence its
+             * transpose is its inverse. i0..i3 are y7,y5,y3,y1 respectively.
+             */
+            tmp0 = dataUnit[rIndex + 7];
+            tmp1 = dataUnit[rIndex + 5];
+            tmp2 = dataUnit[rIndex + 3];
+            tmp3 = dataUnit[rIndex + 1];
+            z1 = tmp0 + tmp3;
+            z2 = tmp1 + tmp2;
+            z3 = tmp0 + tmp2;
+            int z4 = tmp1 + tmp3;
+            int z5 = (z3 + z4) * FIX_1_175875602; /* sqrt(2) * c3 */
+
+            tmp0 *= FIX_0_298631336;        /* sqrt(2) * (-c1+c3+c5-c7) */
+            tmp1 *= FIX_2_053119869;        /* sqrt(2) * ( c1+c3-c5+c7) */
+            tmp2 *= FIX_3_072711026;        /* sqrt(2) * ( c1+c3+c5-c7) */
+            tmp3 *= FIX_1_501321110;        /* sqrt(2) * ( c1+c3-c5-c7) */
+            z1 *= 0 - FIX_0_899976223;  /* sqrt(2) * (c7-c3) */
+            z2 *= 0 - FIX_2_562915447;  /* sqrt(2) * (-c1-c3) */
+            z3 *= 0 - FIX_1_961570560;  /* sqrt(2) * (-c3-c5) */
+            z4 *= 0 - FIX_0_390180644;  /* sqrt(2) * (c5-c3) */
+
+            z3 += z5;
+            z4 += z5;
+            tmp0 += z1 + z3;
+            tmp1 += z2 + z4;
+            tmp2 += z2 + z3;
+            tmp3 += z1 + z4;
+
+            dataUnit[rIndex] = (tmp10 + tmp3 + 1024) >> 11;
+            dataUnit[rIndex + 7] = (tmp10 - tmp3 + 1024) >> 11;
+            dataUnit[rIndex + 1] = (tmp11 + tmp2 + 1024) >> 11;
+            dataUnit[rIndex + 6] = (tmp11 - tmp2 + 1024) >> 11;
+            dataUnit[rIndex + 2] = (tmp12 + tmp1 + 1024) >> 11;
+            dataUnit[rIndex + 5] = (tmp12 - tmp1 + 1024) >> 11;
+            dataUnit[rIndex + 3] = (tmp13 + tmp0 + 1024) >> 11;
+            dataUnit[rIndex + 4] = (tmp13 - tmp0 + 1024) >> 11;
+         }
+    }
+    /**
+     * Pass 2: process columns.
+     * Note that we must descale the results by a factor of 8 is 2**3,
+     * and also undo the PASS1_BITS scaling.
+     */
+    for (int col = 0; col < 8; col++) {
+        int c0 = col;
+        int c1 = col + 8;
+        int c2 = col + 16;
+        int c3 = col + 24;
+        int c4 = col + 32;
+        int c5 = col + 40;
+        int c6 = col + 48;
+        int c7 = col + 56;
+        if (isZeroInColumn(dataUnit, col)) {
+            int dcVal = (dataUnit[c0] + 16) >> 5;
+            dataUnit[c0] = dcVal;
+            dataUnit[c1] = dcVal;
+            dataUnit[c2] = dcVal;
+            dataUnit[c3] = dcVal;
+            dataUnit[c4] = dcVal;
+            dataUnit[c5] = dcVal;
+            dataUnit[c6] = dcVal;
+            dataUnit[c7] = dcVal;
+        } else {
+            /**
+             * Even part: reverse the even part of the forward DCT.
+             * The rotator is sqrt(2)*c(-6).
+             */
+            int z0 = dataUnit[c0];
+            int z2 = dataUnit[c2];
+            int z3 = dataUnit[c6];
+            int z4 = dataUnit[c4];
+            int z1 = (z2 + z3) * FIX_0_541196100;
+            int tmp2 = z1 + (z3 * (0 - FIX_1_847759065));
+            int tmp3 = z1 + (z2 * FIX_0_765366865);
+            int tmp0 = (z0 + z4) << 13;
+            int tmp1 = (z0 - z4) << 13;
+            int tmp10 = tmp0 + tmp3;
+            int tmp13 = tmp0 - tmp3;
+            int tmp11 = tmp1 + tmp2;
+            int tmp12 = tmp1 - tmp2;
+            /**
+             * Odd part per figure 8; the matrix is unitary and hence its
+             * transpose is its inverse. i0..i3 are y7,y5,y3,y1 respectively.
+             */
+            tmp0 = dataUnit[c7];
+            tmp1 = dataUnit[c5];
+            tmp2 = dataUnit[c3];
+            tmp3 = dataUnit[c1];
+            z1 = tmp0 + tmp3;
+            z2 = tmp1 + tmp2;
+            z3 = tmp0 + tmp2;
+            z4 = tmp1 + tmp3;
+            z0 = (z3 + z4) * FIX_1_175875602;   /* sqrt(2) * c3 */
+
+            tmp0 *= FIX_0_298631336;        /* sqrt(2) * (-c1+c3+c5-c7) */
+            tmp1 *= FIX_2_053119869;        /* sqrt(2) * ( c1+c3-c5+c7) */
+            tmp2 *= FIX_3_072711026;        /* sqrt(2) * ( c1+c3+c5-c7) */
+            tmp3 *= FIX_1_501321110;        /* sqrt(2) * ( c1+c3-c5-c7) */
+            z1 *= 0 - FIX_0_899976223;  /* sqrt(2) * (c7-c3) */
+            z2 *= 0 - FIX_2_562915447;  /* sqrt(2) * (-c1-c3) */
+            z3 *= 0 - FIX_1_961570560;  /* sqrt(2) * (-c3-c5) */
+            z4 *= 0 - FIX_0_390180644;  /* sqrt(2) * (c5-c3) */
+
+            z3 += z0;
+            z4 += z0;
+
+            tmp0 += z1 + z3;
+            tmp1 += z2 + z4;
+            tmp2 += z2 + z3;
+            tmp3 += z1 + z4;
+
+            /* Final output stage: inputs are tmp10..tmp13, tmp0..tmp3 */
+            dataUnit[c0] = (tmp10 + tmp3 + 131072) >> 18;
+            dataUnit[c7] = (tmp10 - tmp3 + 131072) >> 18;
+            dataUnit[c1] = (tmp11 + tmp2 + 131072) >> 18;
+            dataUnit[c6] = (tmp11 - tmp2 + 131072) >> 18;
+            dataUnit[c2] = (tmp12 + tmp1 + 131072) >> 18;
+            dataUnit[c5] = (tmp12 - tmp1 + 131072) >> 18;
+            dataUnit[c3] = (tmp13 + tmp0 + 131072) >> 18;
+            dataUnit[c4] = (tmp13 - tmp0 + 131072) >> 18;
+        }
+    }
+}
+bool isFileFormat(LEDataInputStream stream) {
+    try {
+        JPEGStartOfImage soi = new JPEGStartOfImage(stream);
+        stream.unread(soi.reference);
+        return soi.verify();  // we no longer check for appN
+    } catch (TracedException e) {
+        return false;
+    }
+}
+bool isZeroInColumn(int[] dataUnit, int col) {
+    return dataUnit[col + 8] is 0 && dataUnit[col + 16] is 0
+            && dataUnit[col + 24] is 0 && dataUnit[col + 32] is 0
+            && dataUnit[col + 40] is 0 && dataUnit[col + 48] is 0
+            && dataUnit[col + 56] is 0;
+}
+bool isZeroInRow(int[] dataUnit, int rIndex) {
+    return dataUnit[rIndex + 1] is 0 && dataUnit[rIndex + 2] is 0
+            && dataUnit[rIndex + 3] is 0 && dataUnit[rIndex + 4] is 0
+            && dataUnit[rIndex + 5] is 0 && dataUnit[rIndex + 6] is 0
+            && dataUnit[rIndex + 7] is 0;
+}
+ImageData[] loadFromByteStream() {
+    //TEMPORARY CODE
+    //PORTING_FIXME
+    if (/+System.getProperty("dwt.internal.image.JPEGFileFormat_3.2") is null+/ true ) {
+        return JPEGDecoder.loadFromByteStream(inputStream, loader);
+    }
+    JPEGStartOfImage soi = new JPEGStartOfImage(inputStream);
+    if (!soi.verify()) DWT.error(DWT.ERROR_INVALID_IMAGE);
+    restartInterval = 0;
+
+    /* Process the tables preceding the frame header. */
+    processTables();
+
+    /* Start of Frame. */
+    frameHeader = new JPEGFrameHeader(inputStream);
+    if (!frameHeader.verify()) DWT.error(DWT.ERROR_INVALID_IMAGE);
+    imageWidth = frameHeader.getSamplesPerLine();
+    imageHeight = frameHeader.getNumberOfLines();
+    maxH = frameHeader.getMaxHFactor();
+    maxV = frameHeader.getMaxVFactor();
+    int mcuWidth = maxH * DCTSIZE;
+    int mcuHeight = maxV * DCTSIZE;
+    interleavedMcuCols = (imageWidth + mcuWidth - 1) / mcuWidth;
+    interleavedMcuRows = (imageHeight + mcuHeight - 1) / mcuHeight;
+    progressive = frameHeader.isProgressive();
+    samplePrecision = frameHeader.getSamplePrecision();
+    nComponents = frameHeader.getNumberOfImageComponents();
+    frameComponents = frameHeader.componentParameters;
+    componentIds = frameHeader.componentIdentifiers;
+    imageComponents = new byte[][](nComponents);
+    if (progressive) {
+        // Progressive jpeg: need to keep all of the data units.
+        dataUnits = new int[][][](nComponents);
+    } else {
+        // Sequential jpeg: only need one data unit.
+        dataUnit = new int[8 * 8];
+    }
+    for (int i = 0; i < nComponents; i++) {
+        int[] frameComponent = frameComponents[componentIds[i]];
+        int bufferSize = frameComponent[CW] * frameComponent[CH];
+        imageComponents[i] = new byte[bufferSize];
+        if (progressive) {
+            dataUnits[i] = new int[][](bufferSize);
+        }
+    }
+
+    /* Process the tables preceding the scan header. */
+    processTables();
+
+    /* Start of Scan. */
+    scanHeader = new JPEGScanHeader(inputStream);
+    if (!scanHeader.verify()) DWT.error(DWT.ERROR_INVALID_IMAGE);
+
+    /* Process scan(s) and further tables until EOI. */
+    int progressiveScanCount = 0;
+    bool done = false;
+    while(!done) {
+        resetInputBuffer();
+        precedingDCs = new int[4];
+        decodeScan();
+        if (progressive && loader.hasListeners()) {
+            ImageData imageData = createImageData();
+            loader.notifyListeners(new ImageLoaderEvent(loader, imageData, progressiveScanCount, false));
+            progressiveScanCount++;
+        }
+
+        /* Unread any buffered data before looking for tables again. */
+        int delta = 512 - bufferCurrentPosition - 1;
+        if (delta > 0) {
+            byte[] unreadBuffer = new byte[delta];
+            System.arraycopy(dataBuffer, bufferCurrentPosition + 1, unreadBuffer, 0, delta);
+            try {
+                inputStream.unread(unreadBuffer);
+            } catch (IOException e) {
+                DWT.error(DWT.ERROR_IO, e);
+            }
+        }
+
+        /* Process the tables preceding the next scan header. */
+        JPEGSegment jpegSegment = processTables();
+        if (jpegSegment is null || jpegSegment.getSegmentMarker() is EOI) {
+            done = true;
+        } else {
+            scanHeader = new JPEGScanHeader(inputStream);
+            if (!scanHeader.verify()) DWT.error(DWT.ERROR_INVALID_IMAGE);
+        }
+    }
+
+    if (progressive) {
+        for (int ymcu = 0; ymcu < interleavedMcuRows; ymcu++) {
+            for (int xmcu = 0; xmcu < interleavedMcuCols; xmcu++) {
+                for (int iComp = 0; iComp < nComponents; iComp++) {
+                    int[] frameComponent = frameComponents[componentIds[iComp]];
+                    int hi = frameComponent[HI];
+                    int vi = frameComponent[VI];
+                    int compWidth = frameComponent[CW];
+                    for (int ivi = 0; ivi < vi; ivi++) {
+                        for (int ihi = 0; ihi < hi; ihi++) {
+                            int index = (ymcu * vi + ivi) * compWidth + xmcu * hi + ihi;
+                            dataUnit = dataUnits[iComp][index];
+                            dequantize(dataUnit, iComp);
+                            inverseDCT(dataUnit);
+                            storeData(dataUnit, iComp, xmcu, ymcu, hi, ihi, vi, ivi);
+                        }
+                    }
+                }
+            }
+        }
+        dataUnits = null; // release memory
+    }
+    ImageData imageData = createImageData();
+    if (progressive && loader.hasListeners()) {
+        loader.notifyListeners(new ImageLoaderEvent(loader, imageData, progressiveScanCount, true));
+    }
+    return [imageData];
+}
+ImageData createImageData() {
+    return ImageData.internal_new(
+        imageWidth,
+        imageHeight,
+        nComponents * samplePrecision,
+        setUpPalette(),
+        nComponents is 1 ? 4 : 1,
+        decodeImageComponents(),
+        0,
+        null,
+        null,
+        -1,
+        -1,
+        DWT.IMAGE_JPEG,
+        0,
+        0,
+        0,
+        0);
+}
+int nextBit() {
+    if (currentBitCount !is 0) {
+        currentBitCount--;
+        currentByte *= 2;
+        if (currentByte > 255) {
+            currentByte -= 256;
+            return 1;
+        } else {
+            return 0;
+        }
+    }
+    bufferCurrentPosition++;
+    if (bufferCurrentPosition >= 512) {
+        resetInputBuffer();
+        bufferCurrentPosition = 0;
+    }
+    currentByte = dataBuffer[bufferCurrentPosition] & 0xFF;
+    currentBitCount = 8;
+    byte nextByte;
+    if (bufferCurrentPosition is 511) {
+        resetInputBuffer();
+        currentBitCount = 8;
+        nextByte = dataBuffer[0];
+    } else {
+        nextByte = dataBuffer[bufferCurrentPosition + 1];
+    }
+    if (currentByte is 0xFF) {
+        if (nextByte is 0) {
+            bufferCurrentPosition ++;
+            currentBitCount--;
+            currentByte *= 2;
+            if (currentByte > 255) {
+                currentByte -= 256;
+                return 1;
+            } else {
+                return 0;
+            }
+        } else {
+            if ((nextByte & 0xFF) + 0xFF00 is DNL) {
+                getDNL();
+                return 0;
+            } else {
+                DWT.error(DWT.ERROR_INVALID_IMAGE);
+                return 0;
+            }
+        }
+    } else {
+        currentBitCount--;
+        currentByte *= 2;
+        if (currentByte > 255) {
+            currentByte -= 256;
+            return 1;
+        } else {
+            return 0;
+        }
+    }
+}
+void processRestartInterval() {
+    do {
+        bufferCurrentPosition++;
+        if (bufferCurrentPosition > 511) {
+            resetInputBuffer();
+            bufferCurrentPosition = 0;
+        }
+        currentByte = dataBuffer[bufferCurrentPosition] & 0xFF;
+    } while (currentByte !is 0xFF);
+    while (currentByte is 0xFF) {
+        bufferCurrentPosition++;
+        if (bufferCurrentPosition > 511) {
+            resetInputBuffer();
+            bufferCurrentPosition = 0;
+        }
+        currentByte = dataBuffer[bufferCurrentPosition] & 0xFF;
+    }
+    if (currentByte !is ((RST0 + nextRestartNumber) & 0xFF)) {
+        DWT.error(DWT.ERROR_INVALID_IMAGE);
+    }
+    bufferCurrentPosition++;
+    if (bufferCurrentPosition > 511) {
+        resetInputBuffer();
+        bufferCurrentPosition = 0;
+    }
+    currentByte = dataBuffer[bufferCurrentPosition] & 0xFF;
+    currentBitCount = 8;
+    restartsToGo = restartInterval;
+    nextRestartNumber = (nextRestartNumber + 1) & 0x7;
+    precedingDCs = new int[4];
+    eobrun = 0;
+}
+/* Process all markers until a frame header, scan header, or EOI is found. */
+JPEGSegment processTables() {
+    while (true) {
+        JPEGSegment jpegSegment = seekUnspecifiedMarker(inputStream);
+        if (jpegSegment is null) return null;
+        JPEGFrameHeader sof = new JPEGFrameHeader(jpegSegment.reference);
+        if (sof.verify()) {
+            return jpegSegment;
+        }
+        int marker = jpegSegment.getSegmentMarker();
+        switch (marker) {
+            case SOI: // there should only be one SOI per file
+                DWT.error(DWT.ERROR_INVALID_IMAGE);
+            case EOI:
+            case SOS:
+                return jpegSegment;
+            case DQT:
+                getDQT();
+                break;
+            case DHT:
+                getDHT();
+                break;
+            case DAC:
+                getDAC();
+                break;
+            case DRI:
+                getDRI();
+                break;
+            case APP0:
+                getAPP0();
+                break;
+            case COM:
+                getCOM();
+                break;
+            default:
+                skipSegmentFrom(inputStream);
+
+        }
+    }
+}
+void quantizeData(int[] dataUnit, int iComp) {
+    int[] qTable = quantizationTables[frameComponents[componentIds[iComp]][TQI]];
+    for (int i = 0; i < dataUnit.length; i++) {
+        int zzIndex = ZigZag8x8[i];
+        int data = dataUnit[zzIndex];
+        int absData = data < 0 ? 0 - data : data;
+        int qValue = qTable[i];
+        int q2 = qValue >> 1;
+        absData += q2;
+        if (absData < qValue) {
+            dataUnit[zzIndex] = 0;
+        } else {
+            absData /= qValue;
+            if (data >= 0) {
+                dataUnit[zzIndex] = absData;
+            } else {
+                dataUnit[zzIndex] = 0 - absData;
+            }
+        }
+    }
+}
+int receive(int nBits) {
+    int v = 0;
+    for (int i = 0; i < nBits; i++) {
+        v = v * 2 + nextBit();
+    }
+    return v;
+}
+void resetInputBuffer() {
+    if (dataBuffer is null) {
+        dataBuffer = new byte[512];
+    }
+    try {
+        inputStream.read(dataBuffer);
+    } catch (IOException e) {
+        DWT.error(DWT.ERROR_IO, e);
+    }
+    currentBitCount = 0;
+    bufferCurrentPosition = -1;
+}
+void resetOutputBuffer() {
+    if (dataBuffer is null) {
+        dataBuffer = new byte[512];
+    } else {
+        try {
+            outputStream.write(dataBuffer, 0, bufferCurrentPosition);
+        } catch (IOException e) {
+            DWT.error(DWT.ERROR_IO, e);
+        }
+    }
+    bufferCurrentPosition = 0;
+}
+static JPEGSegment seekUnspecifiedMarker(LEDataInputStream byteStream) {
+    byte[] byteArray = new byte[2];
+    try {
+        while (true) {
+            if (byteStream.read(byteArray, 0, 1) !is 1) return null;
+            if (byteArray[0] is cast(byte) 0xFF) {
+                if (byteStream.read(byteArray, 1, 1) !is 1) return null;
+                if (byteArray[1] !is cast(byte) 0xFF && byteArray[1] !is 0) {
+                    byteStream.unread(byteArray);
+                    return new JPEGSegment(byteArray);
+                }
+            }
+        }
+    } catch (IOException e) {
+        DWT.error(DWT.ERROR_IO, e);
+    }
+    return null;
+}
+PaletteData setUpPalette() {
+    if (nComponents is 1) {
+        RGB[] entries = new RGB[256];
+        for (int i = 0; i < 256; i++) {
+            entries[i] = new RGB(i, i, i);
+        }
+        return new PaletteData(entries);
+    }
+    return new PaletteData(0xFF, 0xFF00, 0xFF0000);
+}
+static void skipSegmentFrom(LEDataInputStream byteStream) {
+    try {
+        byte[] byteArray = new byte[4];
+        JPEGSegment jpegSegment = new JPEGSegment(byteArray);
+
+        if (byteStream.read(byteArray) !is byteArray.length) {
+            DWT.error(DWT.ERROR_INVALID_IMAGE);
+        }
+        if (!(byteArray[0] is -1 && byteArray[1] !is 0 && byteArray[1] !is -1)) {
+            DWT.error(DWT.ERROR_INVALID_IMAGE);
+        }
+        int delta = jpegSegment.getSegmentLength() - 2;
+        byteStream.skip(delta);
+    } catch (TracedException e) {
+        DWT.error(DWT.ERROR_IO, e);
+    }
+}
+void storeData(int[] dataUnit, int iComp, int xmcu, int ymcu, int hi, int ihi, int vi, int ivi) {
+    byte[] compImage = imageComponents[iComp];
+    int[] frameComponent = frameComponents[componentIds[iComp]];
+    int compWidth = frameComponent[CW];
+    int destIndex = ((ymcu * vi + ivi) * compWidth * DCTSIZE) + ((xmcu * hi + ihi) * DCTSIZE);
+    int srcIndex = 0;
+    for (int i = 0; i < DCTSIZE; i++) {
+        for (int col = 0; col < DCTSIZE; col++) {
+            int x = dataUnit[srcIndex] + 128;
+            if (x < 0) {
+                x = 0;
+            } else {
+                if (x > 255) x = 255;
+            }
+            compImage[destIndex + col] = cast(byte)x;
+            srcIndex++;
+        }
+        destIndex += compWidth;
+    }
+}
+void unloadIntoByteStream(ImageLoader loader) {
+    ImageData image = loader.data[0];
+    if (!(new JPEGStartOfImage()).writeToStream(outputStream)) {
+        DWT.error(DWT.ERROR_IO);
+    }
+    JPEGAppn appn = new JPEGAppn([cast(byte)0xFF, cast(byte)0xE0, 0, 0x10, 0x4A, 0x46, 0x49, 0x46, 0, 1, 1, 0, 0, 1, 0, 1, 0, 0]);
+    if (!appn.writeToStream(outputStream)) {
+        DWT.error(DWT.ERROR_IO);
+    }
+    quantizationTables = new int[][](4);
+    JPEGQuantizationTable chromDQT = JPEGQuantizationTable.defaultChrominanceTable();
+    chromDQT.scaleBy(encoderQFactor);
+    int[] jpegDQTKeys = chromDQT.getQuantizationTablesKeys();
+    int[][] jpegDQTValues = chromDQT.getQuantizationTablesValues();
+    for (int i = 0; i < jpegDQTKeys.length; i++) {
+        quantizationTables[jpegDQTKeys[i]] = jpegDQTValues[i];
+    }
+    JPEGQuantizationTable lumDQT = JPEGQuantizationTable.defaultLuminanceTable();
+    lumDQT.scaleBy(encoderQFactor);
+    jpegDQTKeys = lumDQT.getQuantizationTablesKeys();
+    jpegDQTValues = lumDQT.getQuantizationTablesValues();
+    for (int i = 0; i < jpegDQTKeys.length; i++) {
+        quantizationTables[jpegDQTKeys[i]] = jpegDQTValues[i];
+    }
+    if (!lumDQT.writeToStream(outputStream)) {
+        DWT.error(DWT.ERROR_IO);
+    }
+    if (!chromDQT.writeToStream(outputStream)) {
+        DWT.error(DWT.ERROR_IO);
+    }
+    int frameLength, scanLength, precision;
+    int[][] frameParams, scanParams;
+    if (image.depth is 1) {
+        frameLength = 11;
+        frameParams = new int[][](1);
+        frameParams[0] = [1, 1, 1, 0, 0];
+        scanParams = new int[][](1);
+        scanParams[0] = [0, 0];
+        scanLength = 8;
+        nComponents = 1;
+        precision = 1;
+    } else {
+        frameLength = 17;
+        frameParams = new int[][](3);
+        frameParams[0] = [0, 2, 2, 0, 0];
+        frameParams[1] = [1, 1, 1, 0, 0];
+        frameParams[2] = [1, 1, 1, 0, 0];
+        scanParams = new int[][](3);
+        scanParams[0] = [0, 0];
+        scanParams[1] = [1, 1];
+        scanParams[2] = [1, 1];
+        scanLength = 12;
+        nComponents = 3;
+        precision = 8;
+    }
+    imageWidth = image.width;
+    imageHeight = image.height;
+    frameHeader = new JPEGFrameHeader(new byte[19]);
+    frameHeader.setSegmentMarker(SOF0);
+    frameHeader.setSegmentLength(frameLength);
+    frameHeader.setSamplePrecision(precision);
+    frameHeader.setSamplesPerLine(imageWidth);
+    frameHeader.setNumberOfLines(imageHeight);
+    frameHeader.setNumberOfImageComponents(nComponents);
+    frameHeader.componentParameters = frameParams;
+    frameHeader.componentIdentifiers = [0, 1, 2];
+    frameHeader.initializeContents();
+    if (!frameHeader.writeToStream(outputStream)) {
+        DWT.error(DWT.ERROR_IO);
+    }
+    frameComponents = frameParams;
+    componentIds = frameHeader.componentIdentifiers;
+    maxH = frameHeader.getMaxHFactor();
+    maxV = frameHeader.getMaxVFactor();
+    int mcuWidth = maxH * DCTSIZE;
+    int mcuHeight = maxV * DCTSIZE;
+    interleavedMcuCols = (imageWidth + mcuWidth - 1) / mcuWidth;
+    interleavedMcuRows = (imageHeight + mcuHeight - 1) / mcuHeight;
+    acHuffmanTables = new JPEGHuffmanTable[4];
+    dcHuffmanTables = new JPEGHuffmanTable[4];
+    JPEGHuffmanTable[] dhtTables = [
+        JPEGHuffmanTable.getDefaultDCLuminanceTable(),
+        JPEGHuffmanTable.getDefaultDCChrominanceTable(),
+        JPEGHuffmanTable.getDefaultACLuminanceTable(),
+        JPEGHuffmanTable.getDefaultACChrominanceTable()
+    ];
+    for (int i = 0; i < dhtTables.length; i++) {
+        JPEGHuffmanTable dhtTable = dhtTables[i];
+        if (!dhtTable.writeToStream(outputStream)) {
+            DWT.error(DWT.ERROR_IO);
+        }
+        JPEGHuffmanTable[] allTables = dhtTable.getAllTables();
+        for (int j = 0; j < allTables.length; j++) {
+            JPEGHuffmanTable huffmanTable = allTables[j];
+            if (huffmanTable.getTableClass() is 0) {
+                dcHuffmanTables[huffmanTable.getTableIdentifier()] = huffmanTable;
+            } else {
+                acHuffmanTables[huffmanTable.getTableIdentifier()] = huffmanTable;
+            }
+        }
+    }
+    precedingDCs = new int[4];
+    scanHeader = new JPEGScanHeader(new byte[14]);
+    scanHeader.setSegmentMarker(SOS);
+    scanHeader.setSegmentLength(scanLength);
+    scanHeader.setNumberOfImageComponents(nComponents);
+    scanHeader.setStartOfSpectralSelection(0);
+    scanHeader.setEndOfSpectralSelection(63);
+    scanHeader.componentParameters = scanParams;
+    scanHeader.initializeContents();
+    if (!scanHeader.writeToStream(outputStream)) {
+        DWT.error(DWT.ERROR_IO);
+    }
+    convertImageToYCbCr(image);
+    resetOutputBuffer();
+    currentByte = 0;
+    currentBitCount = 0;
+    encodeScan();
+    if (!(new JPEGEndOfImage()).writeToStream(outputStream)) {
+        DWT.error(DWT.ERROR_IO);
+    }
+}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dwt/internal/image/JPEGFixedSizeSegment.d	Wed Jan 23 12:01:46 2008 +0100
@@ -0,0 +1,51 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2003 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *     IBM Corporation - initial API and implementation
+ * Port to the D programming language:
+ *     Frank Benoit <benoit@tionex.de>
+ *******************************************************************************/
+module dwt.internal.image.JPEGFixedSizeSegment;
+
+
+import dwt.DWT;
+import dwt.internal.image.JPEGSegment;
+import dwt.internal.image.LEDataInputStream;
+
+import tango.core.Exception;
+
+
+abstract class JPEGFixedSizeSegment : JPEGSegment {
+
+    public this() {
+        reference = new byte[fixedSize()];
+        setSegmentMarker(signature());
+    }
+
+    public this(byte[] reference) {
+        super(reference);
+    }
+
+    public this(LEDataInputStream byteStream) {
+        reference = new byte[fixedSize()];
+        try {
+            byteStream.read(reference);
+        } catch (TracedException e) {
+            DWT.error(DWT.ERROR_IO, e);
+        }
+    }
+
+    abstract public int fixedSize();
+
+    public int getSegmentLength() {
+        return fixedSize() - 2;
+    }
+
+    public void setSegmentLength(int length) {
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dwt/internal/image/JPEGFrameHeader.d	Wed Jan 23 12:01:46 2008 +0100
@@ -0,0 +1,219 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2005 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *     IBM Corporation - initial API and implementation
+ * Port to the D programming language:
+ *     Frank Benoit <benoit@tionex.de>
+ *******************************************************************************/
+module dwt.internal.image.JPEGFrameHeader;
+
+
+import dwt.DWT;
+import dwt.internal.image.JPEGVariableSizeSegment;
+import dwt.internal.image.JPEGFileFormat;
+import dwt.internal.image.LEDataInputStream;
+
+final class JPEGFrameHeader : JPEGVariableSizeSegment {
+    int maxVFactor;
+    int maxHFactor;
+    public int[] componentIdentifiers;
+    public int[][] componentParameters;
+
+    public this(byte[] reference) {
+        super(reference);
+    }
+
+    public this(LEDataInputStream byteStream) {
+        super(byteStream);
+        initializeComponentParameters();
+    }
+
+    public int getSamplePrecision() {
+        return reference[4] & 0xFF;
+    }
+
+    public int getNumberOfLines() {
+        return (reference[5] & 0xFF) << 8 | (reference[6] & 0xFF);
+    }
+
+    public int getSamplesPerLine() {
+        return (reference[7] & 0xFF) << 8 | (reference[8] & 0xFF);
+    }
+
+    public int getNumberOfImageComponents() {
+        return reference[9] & 0xFF;
+    }
+
+    public void setSamplePrecision(int precision) {
+        reference[4] = cast(byte)(precision & 0xFF);
+    }
+
+    public void setNumberOfLines(int anInteger) {
+        reference[5] = cast(byte)((anInteger & 0xFF00) >> 8);
+        reference[6] = cast(byte)(anInteger & 0xFF);
+    }
+
+    public void setSamplesPerLine(int samples) {
+        reference[7] = cast(byte)((samples & 0xFF00) >> 8);
+        reference[8] = cast(byte)(samples & 0xFF);
+    }
+
+    public void setNumberOfImageComponents(int anInteger) {
+        reference[9] = cast(byte)(anInteger & 0xFF);
+    }
+
+    public int getMaxHFactor() {
+        return maxHFactor;
+    }
+
+    public int getMaxVFactor() {
+        return maxVFactor;
+    }
+
+    public void setMaxHFactor(int anInteger) {
+        maxHFactor = anInteger;
+    }
+
+    public void setMaxVFactor(int anInteger) {
+        maxVFactor = anInteger;
+    }
+
+    /* Used when decoding. */
+    void initializeComponentParameters() {
+        int nf = getNumberOfImageComponents();
+        componentIdentifiers = new int[nf];
+        int[][] compSpecParams;
+        int hmax = 1;
+        int vmax = 1;
+        for (int i = 0; i < nf; i++) {
+            int ofs = i * 3 + 10;
+            int ci = reference[ofs] & 0xFF;
+            componentIdentifiers[i] = ci;
+            int hi = (reference[ofs + 1] & 0xFF) >> 4;
+            int vi = reference[ofs + 1] & 0xF;
+            int tqi = reference[ofs + 2] & 0xFF;
+            if (hi > hmax) {
+                hmax = hi;
+            }
+            if (vi > vmax) {
+                vmax = vi;
+            }
+            int[] compParam = new int[5];
+            compParam[0] = tqi;
+            compParam[1] = hi;
+            compParam[2] = vi;
+            if (compSpecParams.length <= ci) {
+                int[][] newParams = new int[][](ci + 1);
+                System.arraycopy(compSpecParams, 0, newParams, 0, compSpecParams.length);
+                compSpecParams = newParams;
+            }
+            compSpecParams[ci] = compParam;
+        }
+        int x = getSamplesPerLine();
+        int y = getNumberOfLines();
+        int[] multiples = [ 8, 16, 24, 32 ];
+        for (int i = 0; i < nf; i++) {
+            int[] compParam = compSpecParams[componentIdentifiers[i]];
+            int hi = compParam[1];
+            int vi = compParam[2];
+            int compWidth = (x * hi + hmax - 1) / hmax;
+            int compHeight = (y * vi + vmax - 1) / vmax;
+            int dsWidth = roundUpToMultiple(compWidth, multiples[hi - 1]);
+            int dsHeight = roundUpToMultiple(compHeight, multiples[vi - 1]);
+            compParam[3] = dsWidth;
+            compParam[4] = dsHeight;
+        }
+        setMaxHFactor(hmax);
+        setMaxVFactor(vmax);
+        componentParameters = compSpecParams;
+    }
+
+    /* Used when encoding. */
+    public void initializeContents() {
+        int nf = getNumberOfImageComponents();
+        if (nf is 0 || nf !is componentParameters.length) {
+            DWT.error(DWT.ERROR_INVALID_IMAGE);
+        }
+        int hmax = 0;
+        int vmax = 0;
+        int[][] compSpecParams = componentParameters;
+        for (int i = 0; i < nf; i++) {
+            int ofs = i * 3 + 10;
+            int[] compParam = compSpecParams[componentIdentifiers[i]];
+            int hi = compParam[1];
+            int vi = compParam[2];
+            if (hi * vi > 4) {
+                DWT.error(DWT.ERROR_INVALID_IMAGE);
+            }
+            reference[ofs] = cast(byte)(i + 1);
+            reference[ofs + 1] = cast(byte)(hi * 16 + vi);
+            reference[ofs + 2] = cast(byte)(compParam[0]);
+            if (hi > hmax) hmax = hi;
+            if (vi > vmax) vmax = vi;
+        }
+        int x = getSamplesPerLine();
+        int y = getNumberOfLines();
+        int[] multiples = [8, 16, 24, 32];
+        for (int i = 0; i < nf; i++) {
+            int[] compParam = compSpecParams[componentIdentifiers[i]];
+            int hi = compParam[1];
+            int vi = compParam[2];
+            int compWidth = (x * hi + hmax - 1) / hmax;
+            int compHeight = (y * vi + vmax - 1) / vmax;
+            int dsWidth = roundUpToMultiple(compWidth, multiples[hi - 1]);
+            int dsHeight = roundUpToMultiple(compHeight, multiples[vi - 1]);
+            compParam[3] = dsWidth;
+            compParam[4] = dsHeight;
+        }
+        setMaxHFactor(hmax);
+        setMaxVFactor(vmax);
+    }
+
+    int roundUpToMultiple(int anInteger, int mInteger) {
+        int a = anInteger + mInteger - 1;
+        return a - (a % mInteger);
+    }
+
+    /*
+     * Verify the information contained in the receiver is correct.
+     * Answer true if the header contains a valid marker. Otherwise,
+     * answer false. Valid Start Of Frame markers are:
+     *  SOF_0  - Baseline DCT, Huffman coding
+     *  SOF_1  - Extended sequential DCT, Huffman coding
+     *  SOF_2  - Progressive DCT, Huffman coding
+     *  SOF_3  - Lossless (sequential), Huffman coding
+     *  SOF_5  - Differential sequential, Huffman coding
+     *  SOF_6  - Differential progressive, Huffman coding
+     *  SOF_7  - Differential lossless, Huffman coding
+     *  SOF_9  - Extended sequential DCT, arithmetic coding
+     *  SOF_10 - Progressive DCT, arithmetic coding
+     *  SOF_11 - Lossless (sequential), arithmetic coding
+     *  SOF_13 - Differential sequential, arithmetic coding
+     *  SOF_14 - Differential progressive, arithmetic coding
+     *  SOF_15 - Differential lossless, arithmetic coding
+     */
+    public bool verify() {
+        int marker = getSegmentMarker();
+        return (marker >= JPEGFileFormat.SOF0 && marker <= JPEGFileFormat.SOF3) ||
+            (marker >= JPEGFileFormat.SOF5 && marker <= JPEGFileFormat.SOF7) ||
+            (marker >= JPEGFileFormat.SOF9 && marker <= JPEGFileFormat.SOF11) ||
+            (marker >= JPEGFileFormat.SOF13 && marker <= JPEGFileFormat.SOF15);
+    }
+
+    public bool isProgressive() {
+        int marker = getSegmentMarker();
+        return marker is JPEGFileFormat.SOF2
+            || marker is JPEGFileFormat.SOF6
+            || marker is JPEGFileFormat.SOF10
+            || marker is JPEGFileFormat.SOF14;
+    }
+
+    public bool isArithmeticCoding() {
+        return getSegmentMarker() >= JPEGFileFormat.SOF9;
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dwt/internal/image/JPEGHuffmanTable.d	Wed Jan 23 12:01:46 2008 +0100
@@ -0,0 +1,270 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2005 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *     IBM Corporation - initial API and implementation
+ * Port to the D programming language:
+ *     Frank Benoit <benoit@tionex.de>
+ *******************************************************************************/
+module dwt.internal.image.JPEGHuffmanTable;
+
+import dwt.internal.image.JPEGVariableSizeSegment;
+import dwt.internal.image.LEDataInputStream;
+import dwt.internal.image.JPEGFileFormat;
+
+import dwt.dwthelper.System;
+/**
+ * JPEGHuffmanTable class actually represents two types of object:
+ * 1) A DHT (Define Huffman Tables) segment, which may represent
+ *  as many as 4 Huffman tables. In this case, the tables are
+ *  stored in the allTables array.
+ * 2) A single Huffman table. In this case, the allTables array
+ *  will be null.
+ * The 'reference' field is stored in both types of object, but
+ * 'initialize' is only called if the object represents a DHT.
+ */
+final class JPEGHuffmanTable : JPEGVariableSizeSegment {
+    JPEGHuffmanTable[] allTables;
+    int tableClass;
+    int tableIdentifier;
+    int[] dhMaxCodes;
+    int[] dhMinCodes;
+    int[] dhValPtrs;
+    int[] dhValues;
+    int[] ehCodes;
+    byte[] ehCodeLengths;
+    static byte[] DCLuminanceTable = [
+        cast(byte)255, cast(byte)196, 0, 31, 0, 0, 1, 5, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0,
+        0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11
+    ];
+    static byte[] DCChrominanceTable = [
+        cast(byte)255, cast(byte)196, 0, 31, 1, 0, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0,
+        0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11
+    ];
+    static byte[] ACLuminanceTable = [
+        cast(byte)255, cast(byte)196, 0, cast(byte)181, 16, 0, 2, 1, 3, 3, 2, 4, 3, 5, 5, 4, 4, 0, 0, 1, 125,
+        1, 2, 3, 0, 4, 17, 5, 18, 33, 49, 65, 6, 19, 81, 97, 7, 34, 113, 20,
+        50, cast(byte)129, cast(byte)145, cast(byte)161, 8, 35, 66, cast(byte)177, cast(byte)193, 21, 82, cast(byte)209, cast(byte)240, 36, 51, 98,
+        114, cast(byte)130, 9, 10, 22, 23, 24, 25, 26, 37, 38, 39, 40, 41, 42, 52, 53,
+        54, 55, 56, 57, 58, 67, 68, 69, 70, 71, 72, 73, 74, 83, 84, 85, 86, 87,
+        88, 89, 90, 99, 100, 101, 102, 103, 104, 105, 106, 115, 116, 117, 118,
+        119, 120, 121, 122, cast(byte)131, cast(byte)132, cast(byte)133, cast(byte)134, cast(byte)135, cast(byte)136, cast(byte)137, cast(byte)138, cast(byte)146, cast(byte)147, cast(byte)148,
+        cast(byte)149, cast(byte)150, cast(byte)151, cast(byte)152, cast(byte)153, cast(byte)154, cast(byte)162, cast(byte)163, cast(byte)164, cast(byte)165, cast(byte)166, cast(byte)167, cast(byte)168, cast(byte)169, cast(byte)170,
+        cast(byte)178, cast(byte)179, cast(byte)180, cast(byte)181, cast(byte)182, cast(byte)183, cast(byte)184, cast(byte)185, cast(byte)186, cast(byte)194, cast(byte)195, cast(byte)196, cast(byte)197, cast(byte)198, cast(byte)199,
+        cast(byte)200, cast(byte)201, cast(byte)202, cast(byte)210, cast(byte)211, cast(byte)212, cast(byte)213, cast(byte)214, cast(byte)215, cast(byte)216, cast(byte)217, cast(byte)218, cast(byte)225, cast(byte)226, cast(byte)227,
+        cast(byte)228, cast(byte)229, cast(byte)230, cast(byte)231, cast(byte)232, cast(byte)233, cast(byte)234, cast(byte)241, cast(byte)242, cast(byte)243, cast(byte)244, cast(byte)245, cast(byte)246, cast(byte)247, cast(byte)248,
+        cast(byte)249, cast(byte)250
+    ];
+    static byte[] ACChrominanceTable = [
+        cast(byte)255, cast(byte)196, 0, cast(byte)181, 17, 0, 2, 1, 2, 4, 4, 3, 4, 7, 5, 4, 4, 0,
+        1, 2, 119, 0, 1, 2, 3, 17, 4, 5, 33, 49, 6, 18, 65, 81, 7, 97, 113, 19, 34,
+        50, cast(byte)129, 8, 20, 66, cast(byte)145, cast(byte)161, cast(byte)177, cast(byte)193, 9, 35,
+        51, 82, cast(byte)240, 21, 98, 114, cast(byte)209, 10, 22, 36, 52, cast(byte)225, 37,
+        cast(byte)241, 23, 24, 25, 26, 38, 39, 40, 41, 42, 53, 54, 55, 56, 57, 58, 67,
+        68, 69, 70, 71, 72, 73, 74, 83, 84, 85, 86, 87, 88, 89, 90, 99, 100, 101, 102,
+        103, 104, 105, 106, 115, 116, 117, 118, 119, 120, 121, 122, cast(byte)130,
+        cast(byte)131, cast(byte)132, cast(byte)133, cast(byte)134, cast(byte)135, cast(byte)136, cast(byte)137,
+        cast(byte)138, cast(byte)146, cast(byte)147, cast(byte)148, cast(byte)149, cast(byte)150, cast(byte)151,
+        cast(byte)152, cast(byte)153, cast(byte)154, cast(byte)162, cast(byte)163, cast(byte)164, cast(byte)165,
+        cast(byte)166, cast(byte)167, cast(byte)168, cast(byte)169, cast(byte)170, cast(byte)178, cast(byte)179,
+        cast(byte)180, cast(byte)181, cast(byte)182, cast(byte)183, cast(byte)184, cast(byte)185, cast(byte)186,
+        cast(byte)194, cast(byte)195, cast(byte)196, cast(byte)197, cast(byte)198, cast(byte)199, cast(byte)200,
+        cast(byte)201, cast(byte)202, cast(byte)210, cast(byte)211, cast(byte)212, cast(byte)213, cast(byte)214,
+        cast(byte)215, cast(byte)216, cast(byte)217, cast(byte)218, cast(byte)226, cast(byte)227, cast(byte)228,
+        cast(byte)229, cast(byte)230, cast(byte)231, cast(byte)232, cast(byte)233, cast(byte)234, cast(byte)242,
+        cast(byte)243, cast(byte)244, cast(byte)245, cast(byte)246, cast(byte)247, cast(byte)248, cast(byte)249,
+        cast(byte)250
+    ];
+
+public this(byte[] reference) {
+    super(reference);
+}
+
+public this(LEDataInputStream byteStream) {
+    super(byteStream);
+    initialize();
+}
+
+public JPEGHuffmanTable[] getAllTables() {
+    return allTables;
+}
+
+public static JPEGHuffmanTable getDefaultACChrominanceTable() {
+    JPEGHuffmanTable result = new JPEGHuffmanTable(ACChrominanceTable);
+    result.initialize();
+    return result;
+}
+
+public static JPEGHuffmanTable getDefaultACLuminanceTable() {
+    JPEGHuffmanTable result = new JPEGHuffmanTable(ACLuminanceTable);
+    result.initialize();
+    return result;
+}
+
+public static JPEGHuffmanTable getDefaultDCChrominanceTable() {
+    JPEGHuffmanTable result = new JPEGHuffmanTable(DCChrominanceTable);
+    result.initialize();
+    return result;
+}
+
+public static JPEGHuffmanTable getDefaultDCLuminanceTable() {
+    JPEGHuffmanTable result = new JPEGHuffmanTable(DCLuminanceTable);
+    result.initialize();
+    return result;
+}
+
+public int[] getDhMaxCodes() {
+    return dhMaxCodes;
+}
+
+public int[] getDhMinCodes() {
+    return dhMinCodes;
+}
+
+public int[] getDhValPtrs() {
+    return dhValPtrs;
+}
+
+public int[] getDhValues() {
+    return dhValues;
+}
+
+public int getTableClass() {
+    return tableClass;
+}
+
+public int getTableIdentifier() {
+    return tableIdentifier;
+}
+
+void initialize() {
+    int totalLength = getSegmentLength() - 2;
+    int ofs = 4;
+    int[] bits = new int[16];
+    JPEGHuffmanTable[] huffTables = new JPEGHuffmanTable[8]; // maximum is 4 AC + 4 DC
+    int huffTableCount = 0;
+    while (totalLength > 0) {
+        int tc = (reference[ofs] & 0xFF) >> 4; // table class: AC (1) or DC (0)
+        int tid = reference[ofs] & 0xF; // table id: 0-1 baseline, 0-3 prog/ext
+        ofs++;
+
+        /* Read the 16 count bytes and add them together to get the table size. */
+        int count = 0;
+        for (int i = 0; i < bits.length; i++) {
+            int bCount = reference[ofs + i] & 0xFF;
+            bits[i] = bCount;
+            count += bCount;
+        }
+        ofs += 16;
+        totalLength -= 17;
+
+        /* Read the table. */
+        int[] huffVals = new int[count];
+        for (int i = 0; i < count; i++) {
+            huffVals[i] = reference[ofs + i] & 0xFF;
+        }
+        ofs += count;
+        totalLength -= count;
+
+        /* Calculate the lengths. */
+        int[] huffCodeLengths = new int[50]; // start with 50 and increment as needed
+        int huffCodeLengthsIndex = 0;
+        for (int i = 0; i < 16; i++) {
+            for (int j = 0; j < bits[i]; j++) {
+                if (huffCodeLengthsIndex >= huffCodeLengths.length) {
+                    int[] newHuffCodeLengths = new int[huffCodeLengths.length + 50];
+                    System.arraycopy(huffCodeLengths, 0, newHuffCodeLengths, 0, huffCodeLengths.length);
+                    huffCodeLengths = newHuffCodeLengths;
+                }
+                huffCodeLengths[huffCodeLengthsIndex] = i + 1;
+                huffCodeLengthsIndex++;
+            }
+        }
+
+        /* Truncate huffCodeLengths to the correct size. */
+        if (huffCodeLengthsIndex < huffCodeLengths.length) {
+            int[] newHuffCodeLengths = new int[huffCodeLengthsIndex];
+            System.arraycopy(huffCodeLengths, 0, newHuffCodeLengths, 0, huffCodeLengthsIndex);
+            huffCodeLengths = newHuffCodeLengths;
+        }
+
+        /* Calculate the Huffman codes. */
+        int[] huffCodes = new int[50]; // start with 50 and increment as needed
+        int huffCodesIndex = 0;
+        int k = 1;
+        int code = 0;
+        int si = huffCodeLengths[0];
+        int p = 0;
+        while (p < huffCodeLengthsIndex) {
+            while ((p < huffCodeLengthsIndex) && (huffCodeLengths[p] is si)) {
+                if (huffCodesIndex >= huffCodes.length) {
+                    int[] newHuffCodes = new int[huffCodes.length + 50];
+                    System.arraycopy(huffCodes, 0, newHuffCodes, 0, huffCodes.length);
+                    huffCodes = newHuffCodes;
+                }
+                huffCodes[huffCodesIndex] = code;
+                huffCodesIndex++;
+                code++;
+                p++;
+            }
+            code *= 2;
+            si++;
+        }
+
+        /* Truncate huffCodes to the correct size. */
+        if (huffCodesIndex < huffCodes.length) {
+            int[] newHuffCodes = new int[huffCodesIndex];
+            System.arraycopy(huffCodes, 0, newHuffCodes, 0, huffCodesIndex);
+            huffCodes = newHuffCodes;
+        }
+
+        /* Calculate the maximum and minimum codes */
+        k = 0;
+        int[] maxCodes = new int[16];
+        int[] minCodes = new int[16];
+        int[] valPtrs = new int[16];
+        for (int i = 0; i < 16; i++) {
+            int bSize = bits[i];
+            if (bSize is 0) {
+                maxCodes[i] = -1;
+            } else {
+                valPtrs[i] = k;
+                minCodes[i] = huffCodes[k];
+                k += bSize;
+                maxCodes[i] = huffCodes[k - 1];
+            }
+        }
+
+        /* Calculate the eHuffman codes and lengths. */
+        int[] eHuffCodes = new int[256];
+        byte[] eHuffSize = new byte[256];
+        for (int i = 0; i < huffCodesIndex; i++) {
+            eHuffCodes[huffVals[i]] = huffCodes[i];
+            eHuffSize[huffVals[i]] = cast(byte)huffCodeLengths[i];
+        }
+
+        /* Create the new JPEGHuffmanTable and add it to the allTables array. */
+        JPEGHuffmanTable dhtTable = new JPEGHuffmanTable(reference);
+        dhtTable.tableClass = tc;
+        dhtTable.tableIdentifier = tid;
+        dhtTable.dhValues = huffVals;
+        dhtTable.dhMinCodes = minCodes;
+        dhtTable.dhMaxCodes = maxCodes;
+        dhtTable.dhValPtrs = valPtrs;
+        dhtTable.ehCodes = eHuffCodes;
+        dhtTable.ehCodeLengths = eHuffSize;
+        huffTables[huffTableCount] = dhtTable;
+        huffTableCount++;
+    }
+    allTables = new JPEGHuffmanTable[huffTableCount];
+    System.arraycopy(huffTables, 0, allTables, 0, huffTableCount);
+}
+
+public int signature() {
+    return JPEGFileFormat.DHT;
+}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dwt/internal/image/JPEGQuantizationTable.d	Wed Jan 23 12:01:46 2008 +0100
@@ -0,0 +1,172 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2006 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *     IBM Corporation - initial API and implementation
+ * Port to the D programming language:
+ *     Frank Benoit <benoit@tionex.de>
+ *******************************************************************************/
+module dwt.internal.image.JPEGQuantizationTable;
+
+import dwt.internal.image.LEDataInputStream;
+import dwt.internal.image.JPEGVariableSizeSegment;
+import dwt.internal.image.JPEGFileFormat;
+
+import dwt.dwthelper.System;
+
+final class JPEGQuantizationTable : JPEGVariableSizeSegment {
+    public static byte[] DefaultLuminanceQTable = [
+        cast(byte)255, cast(byte)219, 0, 67, 0,
+        16, 11, 10, 16, 24, 40, 51, 61,
+        12, 12, 14, 19, 26, 58, 60, 55,
+        14, 13, 16, 24, 40, 57, 69, 56,
+        14, 17, 22, 29, 51, 87, 80, 62,
+        18, 22, 37, 56, 68, 109, 103, 77,
+        24, 35, 55, 64, 81, 104, 113, 92,
+        49, 64, 78, 87, 103, 121, 120, 101,
+        72, 92, 95, 98, 112, 100, 103, 99
+    ];
+    public static byte[] DefaultChrominanceQTable = [
+        cast(byte)255, cast(byte)219, 0, 67, 1,
+        17, 18, 24, 47, 99, 99, 99, 99,
+        18, 21, 26, 66, 99, 99, 99, 99,
+        24, 26, 56, 99, 99, 99, 99, 99,
+        47, 66, 99, 99, 99, 99, 99, 99,
+        99, 99, 99, 99, 99, 99, 99, 99,
+        99, 99, 99, 99, 99, 99, 99, 99,
+        99, 99, 99, 99, 99, 99, 99, 99,
+        99, 99, 99, 99, 99, 99, 99, 99
+    ];
+
+public this(byte[] reference) {
+    super(reference);
+}
+
+public this(LEDataInputStream byteStream) {
+    super(byteStream);
+}
+
+public static JPEGQuantizationTable defaultChrominanceTable() {
+    byte[] data = new byte[DefaultChrominanceQTable.length];
+    System.arraycopy(DefaultChrominanceQTable, 0, data, 0, data.length);
+    return new JPEGQuantizationTable(data);
+}
+
+public static JPEGQuantizationTable defaultLuminanceTable() {
+    byte[] data = new byte[DefaultLuminanceQTable.length];
+    System.arraycopy(DefaultLuminanceQTable, 0, data, 0, data.length);
+    return new JPEGQuantizationTable(data);
+}
+
+public int[] getQuantizationTablesKeys() {
+    int[] keys = new int[4];
+    int keysIndex = 0;
+    int totalLength = getSegmentLength() - 2;
+    int ofs = 4;
+    while (totalLength > 64) {
+        int tq = reference[ofs] & 0xF;
+        int pq = (reference[ofs] & 0xFF) >> 4;
+        if (pq is 0) {
+            ofs += 65;
+            totalLength -= 65;
+        } else {
+            ofs += 129;
+            totalLength -= 129;
+        }
+        if (keysIndex >= keys.length) {
+            int[] newKeys = new int[keys.length + 4];
+            System.arraycopy(keys, 0, newKeys, 0, keys.length);
+            keys = newKeys;
+        }
+        keys[keysIndex] = tq;
+        keysIndex++;
+    }
+    int[] newKeys = new int[keysIndex];
+    System.arraycopy(keys, 0, newKeys, 0, keysIndex);
+    return newKeys;
+}
+
+public int[][] getQuantizationTablesValues() {
+    int[][] values = new int[][](4);
+    int valuesIndex = 0;
+    int totalLength = getSegmentLength() - 2;
+    int ofs = 4;
+    while (totalLength > 64) {
+        int[] qk = new int[64];
+        int pq = (reference[ofs] & 0xFF) >> 4;
+        if (pq is 0) {
+            for (int i = 0; i < qk.length; i++) {
+                qk[i] = reference[ofs + i + 1] & 0xFF;
+            }
+            ofs += 65;
+            totalLength -= 65;
+        } else {
+            for (int i = 0; i < qk.length; i++) {
+                int idx = (i - 1) * 2 ;
+                qk[i] = (reference[ofs + idx + 1] & 0xFF) * 256 + (reference[ofs + idx + 2] & 0xFF);
+            }
+            ofs += 129;
+            totalLength -= 129;
+        }
+        if (valuesIndex >= values.length) {
+            int[][] newValues = new int[][](values.length + 4);
+            System.arraycopy(values, 0, newValues, 0, values.length);
+            values = newValues;
+        }
+        values[valuesIndex] = qk;
+        valuesIndex++;
+    }
+    int[][] newValues = new int[][](valuesIndex);
+    System.arraycopy(values, 0, newValues, 0, valuesIndex);
+    return newValues;
+}
+
+public void scaleBy(int qualityFactor) {
+    int qFactor = qualityFactor;
+    if (qFactor <= 0) {
+        qFactor = 1;
+    }
+    if (qFactor > 100) {
+        qFactor = 100;
+    }
+    if (qFactor < 50) {
+        qFactor = 5000 / qFactor;
+    } else {
+        qFactor = 200 - (qFactor * 2);
+    }
+    int totalLength = getSegmentLength() - 2;
+    int ofs = 4;
+    while (totalLength > 64) {
+//      int tq = reference[ofs] & 0xFF;
+        int pq = (reference[ofs] & 0xFF) >> 4;
+        if (pq is 0) {
+            for (int i = ofs + 1; i <= ofs + 64; i++) {
+                int temp = ((reference[i] & 0xFF) * qFactor + 50) / 100;
+                if (temp <= 0) temp = 1;
+                if (temp > 255) temp = 255;
+                reference[i] = cast(byte)temp;
+            }
+            ofs += 65;
+            totalLength -= 65;
+        } else {
+            for (int i = ofs + 1; i <= ofs + 128; i += 2) {
+                int temp = (((reference[i] & 0xFF) * 256 + (reference[i + 1] & 0xFF)) * qFactor + 50) / 100;
+                if (temp <= 0) temp = 1;
+                if (temp > 32767) temp = 32767;
+                reference[i] = cast(byte)(temp >> 8);
+                reference[i + 1] = cast(byte)(temp & 0xFF);
+            }
+            ofs += 129;
+            totalLength -= 129;
+        }
+    }
+}
+
+public int signature() {
+    return JPEGFileFormat.DQT;
+}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dwt/internal/image/JPEGRestartInterval.d	Wed Jan 23 12:01:46 2008 +0100
@@ -0,0 +1,36 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2003 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *     IBM Corporation - initial API and implementation
+ * Port to the D programming language:
+ *     Frank Benoit <benoit@tionex.de>
+ *******************************************************************************/
+module dwt.internal.image.JPEGRestartInterval;
+
+import dwt.internal.image.JPEGFixedSizeSegment;
+import dwt.internal.image.LEDataInputStream;
+import dwt.internal.image.JPEGFileFormat;
+
+final class JPEGRestartInterval : JPEGFixedSizeSegment {
+
+    public this(LEDataInputStream byteStream) {
+        super(byteStream);
+    }
+
+    public int signature() {
+        return JPEGFileFormat.DRI;
+    }
+
+    public int getRestartInterval() {
+        return ((reference[4] & 0xFF) << 8 | (reference[5] & 0xFF));
+    }
+
+    public int fixedSize() {
+        return 6;
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dwt/internal/image/JPEGScanHeader.d	Wed Jan 23 12:01:46 2008 +0100
@@ -0,0 +1,127 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2005 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *     IBM Corporation - initial API and implementation
+ * Port to the D programming language:
+ *     Frank Benoit <benoit@tionex.de>
+ *******************************************************************************/
+module dwt.internal.image.JPEGScanHeader;
+
+import dwt.DWT;
+import dwt.internal.image.JPEGVariableSizeSegment;
+import dwt.internal.image.LEDataInputStream;
+import dwt.internal.image.JPEGFileFormat;
+
+final class JPEGScanHeader : JPEGVariableSizeSegment {
+    public int[][] componentParameters;
+
+public this(byte[] reference) {
+    super(reference);
+}
+
+public this(LEDataInputStream byteStream) {
+    super(byteStream);
+    initializeComponentParameters();
+}
+
+public int getApproxBitPositionHigh() {
+    return reference[(2 * getNumberOfImageComponents()) + 7] >> 4;
+}
+
+public int getApproxBitPositionLow() {
+    return reference[(2 * getNumberOfImageComponents()) + 7] & 0xF;
+}
+
+public int getEndOfSpectralSelection() {
+    return reference[(2 * getNumberOfImageComponents()) + 6];
+}
+
+public int getNumberOfImageComponents() {
+    return reference[4];
+}
+
+public int getStartOfSpectralSelection() {
+    return reference[(2 * getNumberOfImageComponents()) + 5];
+}
+
+/* Used when decoding. */
+void initializeComponentParameters() {
+    int compCount = getNumberOfImageComponents();
+    componentParameters = null;
+    for (int i = 0; i < compCount; i++) {
+        int ofs = 5 + i * 2;
+        int cid = reference[ofs] & 0xFF;
+        int dc = (reference[ofs + 1] & 0xFF) >> 4;
+        int ac = reference[ofs + 1] & 0xF;
+        if (componentParameters.length <= cid) {
+            int[][] newParams = new int[][](cid + 1);
+            System.arraycopy(componentParameters, 0, newParams, 0, componentParameters.length);
+            componentParameters = newParams;
+        }
+        componentParameters[cid] = [ dc, ac ];
+    }
+}
+
+/* Used when encoding. */
+public void initializeContents() {
+    int compCount = getNumberOfImageComponents();
+    int[][] compSpecParams = componentParameters;
+    if (compCount is 0 || compCount !is compSpecParams.length) {
+        DWT.error(DWT.ERROR_INVALID_IMAGE);
+    }
+    for (int i = 0; i < compCount; i++) {
+        int ofs = i * 2 + 5;
+        int[] compParams = compSpecParams[i];
+        reference[ofs] = cast(byte)(i + 1);
+        reference[ofs + 1] = cast(byte)(compParams[0] * 16 + compParams[1]);
+    }
+}
+
+public void setEndOfSpectralSelection(int anInteger) {
+    reference[(2 * getNumberOfImageComponents()) + 6] = cast(byte)anInteger;
+}
+
+public void setNumberOfImageComponents(int anInteger) {
+    reference[4] = cast(byte)(anInteger & 0xFF);
+}
+
+public void setStartOfSpectralSelection(int anInteger) {
+    reference[(2 * getNumberOfImageComponents()) + 5] = cast(byte)anInteger;
+}
+
+public int signature() {
+    return JPEGFileFormat.SOS;
+}
+
+public bool verifyProgressiveScan() {
+    int start = getStartOfSpectralSelection();
+    int end = getEndOfSpectralSelection();
+    int low = getApproxBitPositionLow();
+    int high = getApproxBitPositionHigh();
+    int count = getNumberOfImageComponents();
+    if ((start is 0 && end is 00) || (start <= end && end <= 63)) {
+        if (low <= 13 && high <= 13 && (high is 0 || high is low + 1)) {
+            return start is 0 || (start > 0 && count is 1);
+        }
+    }
+    return false;
+}
+
+public bool isACProgressiveScan() {
+    return getStartOfSpectralSelection() !is 0 && getEndOfSpectralSelection() !is 0;
+}
+
+public bool isDCProgressiveScan() {
+    return getStartOfSpectralSelection() is 0 && getEndOfSpectralSelection() is 0;
+}
+
+public bool isFirstScan() {
+    return getApproxBitPositionHigh() is 0;
+}
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dwt/internal/image/JPEGSegment.d	Wed Jan 23 12:01:46 2008 +0100
@@ -0,0 +1,63 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2003 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *     IBM Corporation - initial API and implementation
+ * Port to the D programming language:
+ *     Frank Benoit <benoit@tionex.de>
+ *******************************************************************************/
+module dwt.internal.image.JPEGSegment;
+
+import dwt.internal.image.LEDataOutputStream;
+
+import tango.core.Exception;
+
+class JPEGSegment {
+    public byte[] reference;
+
+    this() {
+    }
+
+    public this(byte[] reference) {
+        this.reference = reference;
+    }
+
+    public int signature() {
+        return 0;
+    }
+
+    public bool verify() {
+        return getSegmentMarker() is signature();
+    }
+
+    public int getSegmentMarker() {
+        return ((reference[0] & 0xFF) << 8 | (reference[1] & 0xFF));
+    }
+
+    public void setSegmentMarker(int marker) {
+        reference[0] = cast(byte)((marker & 0xFF00) >> 8);
+        reference[1] = cast(byte)(marker & 0xFF);
+    }
+
+    public int getSegmentLength() {
+        return ((reference[2] & 0xFF) << 8 | (reference[3] & 0xFF));
+    }
+
+    public void setSegmentLength(int length) {
+        reference[2] = cast(byte)((length & 0xFF00) >> 8);
+        reference[3] = cast(byte)(length & 0xFF);
+    }
+
+    public bool writeToStream(LEDataOutputStream byteStream) {
+        try {
+            byteStream.write(reference);
+            return true;
+        } catch (TracedException e) {
+            return false;
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dwt/internal/image/JPEGStartOfImage.d	Wed Jan 23 12:01:46 2008 +0100
@@ -0,0 +1,40 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2003 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *     IBM Corporation - initial API and implementation
+ * Port to the D programming language:
+ *     Frank Benoit <benoit@tionex.de>
+ *******************************************************************************/
+module dwt.internal.image.JPEGStartOfImage;
+
+import dwt.internal.image.JPEGFixedSizeSegment;
+import dwt.internal.image.LEDataInputStream;
+import dwt.internal.image.JPEGFileFormat;
+
+final class JPEGStartOfImage : JPEGFixedSizeSegment {
+
+    public this() {
+        super();
+    }
+
+    public this(byte[] reference) {
+        super(reference);
+    }
+
+    public this(LEDataInputStream byteStream) {
+        super(byteStream);
+    }
+
+    public int signature() {
+        return JPEGFileFormat.SOI;
+    }
+
+    public int fixedSize() {
+        return 2;
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dwt/internal/image/JPEGVariableSizeSegment.d	Wed Jan 23 12:01:46 2008 +0100
@@ -0,0 +1,43 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2003 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *     IBM Corporation - initial API and implementation
+ * Port to the D programming language:
+ *     Frank Benoit <benoit@tionex.de>
+ *******************************************************************************/
+module dwt.internal.image.JPEGVariableSizeSegment;
+
+import dwt.DWT;
+import dwt.internal.image.JPEGSegment;
+import dwt.internal.image.LEDataInputStream;
+
+import tango.core.Exception;
+
+abstract class JPEGVariableSizeSegment : JPEGSegment {
+
+    public this(byte[] reference) {
+        super(reference);
+    }
+
+    public this(LEDataInputStream byteStream) {
+        try {
+            byte[] header = new byte[4];
+            byteStream.read(header);
+            reference = header; // to use getSegmentLength()
+            byte[] contents = new byte[getSegmentLength() + 2];
+            contents[0] = header[0];
+            contents[1] = header[1];
+            contents[2] = header[2];
+            contents[3] = header[3];
+            byteStream.read(contents, 4, contents.length - 4);
+            reference = contents;
+        } catch (TracedException e) {
+            DWT.error(DWT.ERROR_IO, e);
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dwt/internal/image/LEDataInputStream.d	Wed Jan 23 12:01:46 2008 +0100
@@ -0,0 +1,194 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2006 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *     IBM Corporation - initial API and implementation
+ * Port to the D programming language:
+ *     Frank Benoit <benoit@tionex.de>
+ *******************************************************************************/
+module dwt.internal.image.LEDataInputStream;
+
+
+import dwt.dwthelper.InputStream;
+import dwt.dwthelper.System;
+import tango.core.Exception;
+
+final class LEDataInputStream : InputStream{
+
+    alias InputStream.read read;
+
+    InputStream host;
+
+    int position;
+
+    /**
+     * The byte array containing the bytes to read.
+     */
+    protected byte[] buf;
+
+    /**
+     * The current position within the byte array <code>buf</code>. A value
+     * equal to buf.length indicates no bytes available.  A value of
+     * 0 indicates the buffer is full.
+     */
+    protected int pos;
+
+
+    public this(InputStream input) {
+        this(input, 512);
+    }
+
+    public this(InputStream input, int bufferSize) {
+        host = input;
+        if (bufferSize > 0) {
+            buf = new byte[bufferSize];
+            pos = bufferSize;
+        }
+        else throw new IllegalArgumentException("bufferSize must be greater zero" );
+    }
+
+    public void close() {
+        buf = null;
+        if (host !is null) {
+            host.close();
+            host = null;
+        }
+    }
+
+    /**
+     * Answer how many bytes were read.
+     */
+    public int getPosition() {
+        return position;
+    }
+
+    /**
+     * Answers how many bytes are available for reading without blocking
+     */
+    public int available() {
+        if (buf is null) throw new IOException("buf is null");
+        return (buf.length - pos) + host.available();
+    }
+
+    /**
+     * Answer the next byte of the input stream.
+     */
+    public int read() {
+        if (buf is null) throw new IOException("buf is null");
+        if (pos < buf.length) {
+            position++;
+            return (buf[pos++] & 0xFF);
+        }
+        int c = host.read();
+        if (c !is -1 ) position++;
+        return c;
+    }
+
+    /**
+     * Don't imitate the JDK behaviour of reading a random number
+     * of bytes when you can actually read them all.
+     */
+    public int read(byte b[], int off, int len) {
+        int read = 0, count;
+        while (read !is len && (count = readData(b, off, len - read)) !is -1) {
+            off += count;
+            read += count;
+        }
+        position += read;
+        if (read is 0 && read !is len) return -1;
+        return read;
+    }
+
+    /**
+     * Reads at most <code>length</code> bytes from this LEDataInputStream and
+     * stores them in byte array <code>buffer</code> starting at <code>offset</code>.
+     * <p>
+     * Answer the number of bytes actually read or -1 if no bytes were read and
+     * end of stream was encountered.  This implementation reads bytes from
+     * the pushback buffer first, then the target stream if more bytes are required
+     * to satisfy <code>count</code>.
+     * </p>
+     * @param buffer the byte array in which to store the read bytes.
+     * @param offset the offset in <code>buffer</code> to store the read bytes.
+     * @param length the maximum number of bytes to store in <code>buffer</code>.
+     *
+     * @return int the number of bytes actually read or -1 if end of stream.
+     *
+     * @exception java.io.IOException if an IOException occurs.
+     */
+    private int readData(byte[] buffer, int offset, int len) {
+        if (buf is null) throw new IOException("buf is null");
+        if (offset < 0 || offset > buffer.length ||
+            len < 0 || (len > buffer.length - offset)) {
+            throw new ArrayBoundsException(__FILE__,__LINE__);
+            }
+
+        int cacheCopied = 0;
+        int newOffset = offset;
+
+        // Are there pushback bytes available?
+        int available = buf.length - pos;
+        if (available > 0) {
+            cacheCopied = (available >= len) ? len : available;
+            System.arraycopy(buf, pos, buffer, newOffset, cacheCopied);
+            newOffset += cacheCopied;
+            pos += cacheCopied;
+        }
+
+        // Have we copied enough?
+        if (cacheCopied is len) return len;
+
+        int inCopied = host.read( buffer, newOffset, len - cacheCopied );
+        if( inCopied is -1 ) inCopied = -1;
+        if (inCopied > 0 ) return inCopied + cacheCopied;
+        if (cacheCopied is 0) return inCopied;
+        return cacheCopied;
+    }
+
+    /**
+     * Answer an integer comprised of the next
+     * four bytes of the input stream.
+     */
+    public int readInt() {
+        byte[4] buf = void;
+        host.read(buf);
+        return ((((((buf[3] & 0xFF) << 24) |
+            (buf[2] & 0xFF)) << 16) |
+            (buf[1] & 0xFF)) << 8) |
+            (buf[0] & 0xFF);
+    }
+
+    /**
+     * Answer a short comprised of the next
+     * two bytes of the input stream.
+     */
+    public short readShort() {
+        byte[2] buf = void;
+        host.read(buf);
+        return cast(short)(((buf[1] & 0xFF) << 8) | (buf[0] & 0xFF));
+    }
+
+    /**
+     * Push back the entire content of the given buffer <code>b</code>.
+     * <p>
+     * The bytes are pushed so that they would be read back b[0], b[1], etc.
+     * If the push back buffer cannot handle the bytes copied from <code>b</code>,
+     * an IOException will be thrown and no byte will be pushed back.
+     * </p>
+     *
+     * @param b the byte array containing bytes to push back into the stream
+     *
+     * @exception   java.io.IOException if the pushback buffer is too small
+     */
+    public void unread(byte[] b) {
+        int l = b.length;
+        if (l > pos) throw new IOException("cannot unread");
+        position -= l;
+        pos -= l;
+        System.arraycopy(b, 0, buf, pos, l);
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dwt/internal/image/LEDataOutputStream.d	Wed Jan 23 12:01:46 2008 +0100
@@ -0,0 +1,61 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2005 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *     IBM Corporation - initial API and implementation
+ * Port to the D programming language:
+ *     Frank Benoit <benoit@tionex.de>
+ *******************************************************************************/
+module dwt.internal.image.LEDataOutputStream;
+
+
+import dwt.dwthelper.OutputStream;
+
+final class LEDataOutputStream : OutputStream {
+    alias OutputStream.write write;
+    OutputStream ostr;
+public this(OutputStream output) {
+    this.ostr = output;
+}
+/**
+ * Write the specified number of bytes of the given byte array,
+ * starting at the specified offset, to the output stream.
+ */
+public void write(byte b[], int off, int len) {
+    ostr.write(b, off, len);
+}
+/**
+ * Write the given byte to the output stream.
+ */
+public void write(int b)  {
+    ostr.write(b);
+}
+/**
+ * Write the given byte to the output stream.
+ */
+public void writeByte(byte b) {
+    ostr.write(b);
+}
+/**
+ * Write the four bytes of the given integer
+ * to the output stream.
+ */
+public void writeInt(int theInt) {
+    ostr.write(theInt & 0xFF);
+    ostr.write((theInt >> 8) & 0xFF);
+    ostr.write((theInt >> 16) & 0xFF);
+    ostr.write((theInt >> 24) & 0xFF);
+}
+/**
+ * Write the two bytes of the given short
+ * to the output stream.
+ */
+public void writeShort(int theShort) {
+    ostr.write(theShort & 0xFF);
+    ostr.write((theShort >> 8) & 0xFF);
+}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dwt/internal/image/LZWCodec.d	Wed Jan 23 12:01:46 2008 +0100
@@ -0,0 +1,486 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2005 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *     IBM Corporation - initial API and implementation
+ * Port to the D programming language:
+ *     Frank Benoit <benoit@tionex.de>
+ *******************************************************************************/
+module dwt.internal.image.LZWCodec;
+
+
+import dwt.internal.image.LZWNode;
+import dwt.internal.image.LEDataInputStream;
+import dwt.internal.image.LEDataOutputStream;
+import dwt.DWT;
+import dwt.graphics.ImageData;
+import dwt.graphics.ImageLoader;
+import dwt.graphics.ImageLoaderEvent;
+
+import tango.core.Exception;
+
+final class LZWCodec {
+    int bitsPerPixel, blockSize, blockIndex, currentByte, bitsLeft,
+        codeSize, clearCode, endCode, newCodes, topSlot, currentSlot,
+        imageWidth, imageHeight, imageX, imageY, pass, line, codeMask;
+    byte[] block, lineArray;
+    int[] stack, suffix, prefix;
+    LZWNode[] nodeStack;
+    LEDataInputStream inputStream;
+    LEDataOutputStream outputStream;
+    ImageData image;
+    ImageLoader loader;
+    bool interlaced;
+    static final int[] MASK_TABLE = [
+        0x1, 0x3, 0x7, 0xF, 0x1F, 0x3F, 0x7F,
+        0xFF, 0x1FF, 0x3FF, 0x7FF, 0xFFF
+    ];
+
+/**
+ * Decode the input.
+ */
+void decode() {
+    int code;
+    int oc = 0;
+    int fc = 0;
+    byte[] buf = new byte[imageWidth];
+    int stackIndex = 0;
+    int bufIndex = 0;
+    int c;
+    while ((c = nextCode()) !is endCode) {
+        if (c is clearCode) {
+            codeSize = bitsPerPixel + 1;
+            codeMask = MASK_TABLE[bitsPerPixel];
+            currentSlot = newCodes;
+            topSlot = 1 << codeSize;
+            while ((c = nextCode()) is clearCode) {}
+            if (c !is endCode) {
+                oc = fc = c;
+                buf[bufIndex] = cast(byte)c;
+                bufIndex++;
+                if (bufIndex is imageWidth) {
+                    nextPutPixels(buf);
+                    bufIndex = 0;
+                }
+            }
+        } else {
+            code = c;
+            if (code >= currentSlot) {
+                code = oc;
+                stack[stackIndex] = fc;
+                stackIndex++;
+            }
+            while (code >= newCodes) {
+                stack[stackIndex] = suffix[code];
+                stackIndex++;
+                code = prefix[code];
+            }
+            stack[stackIndex] = code;
+            stackIndex++;
+            if (currentSlot < topSlot) {
+                fc = code;
+                suffix[currentSlot] = fc;
+                prefix[currentSlot] = oc;
+                currentSlot++;
+                oc = c;
+            }
+            if (currentSlot >= topSlot) {
+                if (codeSize < 12) {
+                    codeMask = MASK_TABLE[codeSize];
+                    codeSize++;
+                    topSlot = topSlot + topSlot;
+                }
+            }
+            while (stackIndex > 0) {
+                stackIndex--;
+                buf[bufIndex] = cast(byte)stack[stackIndex];
+                bufIndex++;
+                if (bufIndex is imageWidth) {
+                    nextPutPixels(buf);
+                    bufIndex = 0;
+                }
+            }
+        }
+    }
+    if (bufIndex !is 0 && line < imageHeight) {
+        nextPutPixels(buf);
+    }
+}
+/**
+ * Decode the LZW-encoded bytes in the given byte stream
+ * into the given DeviceIndependentImage.
+ */
+public void decode(LEDataInputStream inputStream, ImageLoader loader, ImageData image, bool interlaced, int depth) {
+    this.inputStream = inputStream;
+    this.loader = loader;
+    this.image = image;
+    this.interlaced = interlaced;
+    this.bitsPerPixel = depth;
+    initializeForDecoding();
+    decode();
+}
+/**
+ * Encode the image.
+ */
+void encode() {
+    nextPutCode(clearCode);
+    int lastPrefix = encodeLoop();
+    nextPutCode(lastPrefix);
+    nextPutCode(endCode);
+
+    // Write out last partial block
+    if (bitsLeft is 8) {
+        block[0] = cast(byte)(blockIndex - 1); // Nothing in last byte
+    } else {
+        block[0] = cast(byte)(blockIndex); // Last byte has data
+    }
+    writeBlock();
+
+    // Write out empty block to indicate the end (if needed)
+    if (block[0] !is 0) {
+        block[0] = 0;
+        writeBlock();
+    }
+}
+/**
+ * Encode the bytes into the given byte stream
+ * from the given DeviceIndependentImage.
+ */
+public void encode(LEDataOutputStream byteStream, ImageData image) {
+    this.outputStream = byteStream;
+    this.image = image;
+    initializeForEncoding();
+    encode();
+}
+/**
+ * Encoding loop broken out to allow early return.
+ */
+int encodeLoop() {
+    int pixel = nextPixel();
+    bool found;
+    LZWNode node;
+    while (true) {
+        int currentPrefix = pixel;
+        node = nodeStack[currentPrefix];
+        found = true;
+        pixel = nextPixel();
+        if (pixel < 0)
+            return currentPrefix;
+        while (found && (node.children !is null)) {
+            node = node.children;
+            while (found && (node.suffix !is pixel)) {
+                if (pixel < node.suffix) {
+                    if (node.left is null) {
+                        node.left = new LZWNode();
+                        found = false;
+                    }
+                    node = node.left;
+                } else {
+                    if (node.right is null) {
+                        node.right = new LZWNode();
+                        found = false;
+                    }
+                    node = node.right;
+                }
+            }
+            if (found) {
+                currentPrefix = node.code;
+                pixel = nextPixel();
+                if (pixel < 0)
+                    return currentPrefix;
+            }
+        }
+        if (found) {
+            node.children = new LZWNode();
+            node = node.children;
+        }
+        node.children = null;
+        node.left = null;
+        node.right = null;
+        node.code = currentSlot;
+        node.prefix = currentPrefix;
+        node.suffix = pixel;
+        nextPutCode(currentPrefix);
+        currentSlot++;
+        // Off by one?
+        if (currentSlot < 4096) {
+            if (currentSlot > topSlot) {
+                codeSize++;
+                codeMask = MASK_TABLE[codeSize - 1];
+                topSlot *= 2;
+            }
+        } else {
+            nextPutCode(clearCode);
+            for (int i = 0; i < nodeStack.length; i++)
+                nodeStack[i].children = null;
+            codeSize = bitsPerPixel + 1;
+            codeMask = MASK_TABLE[codeSize - 1];
+            currentSlot = newCodes;
+            topSlot = 1 << codeSize;
+        }
+    }
+}
+/**
+ * Initialize the receiver for decoding the given
+ * byte array.
+ */
+void initializeForDecoding() {
+    pass = 1;
+    line = 0;
+    codeSize = bitsPerPixel + 1;
+    topSlot = 1 << codeSize;
+    clearCode = 1 << bitsPerPixel;
+    endCode = clearCode + 1;
+    newCodes = currentSlot = endCode + 1;
+    currentByte = -1;
+    blockSize = bitsLeft = 0;
+    blockIndex = 0;
+    codeMask = MASK_TABLE[codeSize - 1];
+    stack = new int[4096];
+    suffix = new int[4096];
+    prefix = new int[4096];
+    block = new byte[256];
+    imageWidth = image.width;
+    imageHeight = image.height;
+}
+/**
+ * Initialize the receiver for encoding the given
+ * byte array.
+ */
+void initializeForEncoding() {
+    interlaced = false;
+    bitsPerPixel = image.depth;
+    codeSize = bitsPerPixel + 1;
+    topSlot = 1 << codeSize;
+    clearCode = 1 << bitsPerPixel;
+    endCode = clearCode + 1;
+    newCodes = currentSlot = endCode + 1;
+    bitsLeft = 8;
+    currentByte = 0;
+    blockIndex = 1;
+    blockSize = 255;
+    block = new byte[blockSize];
+    block[0] = cast(byte)(blockSize - 1);
+    nodeStack = new LZWNode[1 << bitsPerPixel];
+    for (int i = 0; i < nodeStack.length; i++) {
+        LZWNode node = new LZWNode();
+        node.code = i + 1;
+        node.prefix = -1;
+        node.suffix = i + 1;
+        nodeStack[i] = node;
+    }
+    imageWidth = image.width;
+    imageHeight = image.height;
+    imageY = -1;
+    lineArray = new byte[imageWidth];
+    imageX = imageWidth + 1; // Force a read
+}
+/**
+ * Answer the next code from the input byte array.
+ */
+int nextCode() {
+    int code;
+    if (bitsLeft is 0) {
+        if (blockIndex >= blockSize) {
+            blockSize = readBlock();
+            blockIndex = 0;
+            if (blockSize is 0) return endCode;
+        }
+        blockIndex++;
+        currentByte = block[blockIndex] & 0xFF;
+        bitsLeft = 8;
+        code = currentByte;
+    } else {
+        int shift = bitsLeft - 8;
+        if (shift < 0)
+            code = currentByte >> (0 - shift);
+        else
+            code = currentByte << shift;
+    }
+    while (codeSize > bitsLeft) {
+        if (blockIndex >= blockSize) {
+            blockSize = readBlock();
+            blockIndex = 0;
+            if (blockSize is 0) return endCode;
+        }
+        blockIndex++;
+        currentByte = block[blockIndex] & 0xFF;
+        code += currentByte << bitsLeft;
+        bitsLeft += 8;
+    }
+    bitsLeft -= codeSize;
+    return code & codeMask;
+}
+/**
+ * Answer the next pixel to encode in the image
+ */
+int nextPixel() {
+    imageX++;
+    if (imageX > imageWidth) {
+        imageY++;
+        if (imageY >= imageHeight) {
+            return -1;
+        } else {
+            nextPixels(lineArray, imageWidth);
+        }
+        imageX = 1;
+    }
+    return this.lineArray[imageX - 1] & 0xFF;
+}
+/**
+ * Copy a row of pixel values from the image.
+ */
+void nextPixels(byte[] buf, int lineWidth) {
+    if (image.depth is 8) {
+        System.arraycopy(image.data, imageY * image.bytesPerLine, buf, 0, lineWidth);
+    } else {
+        image.getPixels(0, imageY, lineWidth, buf, 0);
+    }
+}
+/**
+ * Output aCode to the output stream.
+ */
+void nextPutCode(int aCode) {
+    int codeToDo = aCode;
+    int codeBitsToDo = codeSize;
+    // Fill in the remainder of the current byte with the
+    // *high-order* bits of the code.
+    int c = codeToDo & MASK_TABLE[bitsLeft - 1];
+    currentByte = currentByte | (c << (8 - bitsLeft));
+    block[blockIndex] = cast(byte)currentByte;
+    codeBitsToDo -= bitsLeft;
+    if (codeBitsToDo < 1) {
+        // The whole code fit in the first byte, so we are done.
+        bitsLeft -= codeSize;
+        if (bitsLeft is 0) {
+            // We used the whole last byte, so get ready
+            // for the next one.
+            bitsLeft = 8;
+            blockIndex++;
+            if (blockIndex >= blockSize) {
+                writeBlock();
+                blockIndex = 1;
+            }
+            currentByte = 0;
+        }
+        return;
+    }
+    codeToDo = codeToDo >> bitsLeft;
+
+    // Fill in any remaining whole bytes (i.e. not the last one!)
+    blockIndex++;
+    if (blockIndex >= blockSize) {
+        writeBlock();
+        blockIndex = 1;
+    }
+    while (codeBitsToDo >= 8) {
+        currentByte = codeToDo & 0xFF;
+        block[blockIndex] = cast(byte)currentByte;
+        codeToDo = codeToDo >> 8;
+        codeBitsToDo -= 8;
+        blockIndex++;
+        if (blockIndex >= blockSize) {
+            writeBlock();
+            blockIndex = 1;
+        }
+    }
+    // Fill the *low-order* bits of the last byte with the remainder
+    bitsLeft = 8 - codeBitsToDo;
+    currentByte = codeToDo;
+    block[blockIndex] = cast(byte)currentByte;
+}
+/**
+ * Copy a row of pixel values to the image.
+ */
+void nextPutPixels(byte[] buf) {
+    if (image.depth is 8) {
+        // Slight optimization for depth = 8.
+        int start = line * image.bytesPerLine;
+        for (int i = 0; i < imageWidth; i++)
+            image.data[start + i] = buf[i];
+    } else {
+        image.setPixels(0, line, imageWidth, buf, 0);
+    }
+    if (interlaced) {
+        if (pass is 1) {
+            copyRow(buf, 7);
+            line += 8;
+        } else if (pass is 2) {
+            copyRow(buf, 3);
+            line += 8;
+        } else if (pass is 3) {
+            copyRow(buf, 1);
+            line += 4;
+        } else if (pass is 4) {
+            line += 2;
+        } else if (pass is 5) {
+            line += 0;
+        }
+        if (line >= imageHeight) {
+            pass++;
+            if (pass is 2) line = 4;
+            else if (pass is 3) line = 2;
+            else if (pass is 4) line = 1;
+            else if (pass is 5) line = 0;
+            if (pass < 5) {
+                if (loader.hasListeners()) {
+                    ImageData imageCopy = cast(ImageData) image.clone();
+                    loader.notifyListeners(
+                        new ImageLoaderEvent(loader, imageCopy, pass - 2, false));
+                }
+            }
+        }
+        if (line >= imageHeight) line = 0;
+    } else {
+        line++;
+    }
+}
+/**
+ * Copy duplicate rows of pixel values to the image.
+ * This is to fill in rows if the image is interlaced.
+ */
+void copyRow(byte[] buf, int copies) {
+    for (int i = 1; i <= copies; i++) {
+        if (line + i < imageHeight) {
+            image.setPixels(0, line + i, imageWidth, buf, 0);
+        }
+    }
+}
+/**
+ * Read a block from the byte stream.
+ * Return the number of bytes read.
+ * Throw an exception if the block could not be read.
+ */
+int readBlock() {
+    int size = -1;
+    try {
+        size = inputStream.read();
+        if (size is -1) {
+            DWT.error(DWT.ERROR_INVALID_IMAGE);
+        }
+        block[0] = cast(byte)size;
+        size = inputStream.read(block, 1, size);
+        if (size is -1) {
+            DWT.error(DWT.ERROR_INVALID_IMAGE);
+        }
+    } catch (TracedException e) {
+        DWT.error(DWT.ERROR_IO, e);
+    }
+    return size;
+}
+/**
+ * Write a block to the byte stream.
+ * Throw an exception if the block could not be written.
+ */
+void writeBlock() {
+    try {
+        outputStream.write(block, 0, (block[0] & 0xFF) + 1);
+    } catch (TracedException e) {
+        DWT.error(DWT.ERROR_IO, e);
+    }
+}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dwt/internal/image/LZWNode.d	Wed Jan 23 12:01:46 2008 +0100
@@ -0,0 +1,19 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2003 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *     IBM Corporation - initial API and implementation
+ * Port to the D programming language:
+ *     Frank Benoit <benoit@tionex.de>
+ *******************************************************************************/
+module dwt.internal.image.LZWNode;
+
+
+final class LZWNode {
+    public LZWNode left, right, children;
+    public int code, prefix, suffix;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dwt/internal/image/OS2BMPFileFormat.d	Wed Jan 23 12:01:46 2008 +0100
@@ -0,0 +1,302 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2005 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *     IBM Corporation - initial API and implementation
+ * Port to the D programming language:
+ *     Frank Benoit <benoit@tionex.de>
+ *******************************************************************************/
+module dwt.internal.image.OS2BMPFileFormat;
+
+import dwt.DWT;
+import dwt.graphics.ImageData;
+import dwt.graphics.ImageLoader;
+import dwt.graphics.PaletteData;
+import dwt.graphics.RGB;
+import dwt.internal.image.LEDataInputStream;
+import dwt.internal.image.FileFormat;
+import dwt.dwthelper.ByteArrayOutputStream;
+
+import tango.core.Exception;
+
+final class OS2BMPFileFormat : FileFormat {
+    static final int BMPFileHeaderSize = 14;
+    static final int BMPHeaderFixedSize = 12;
+    int width, height, bitCount;
+
+bool isFileFormat(LEDataInputStream stream) {
+    try {
+        byte[] header = new byte[18];
+        stream.read(header);
+        stream.unread(header);
+        int infoHeaderSize = (header[14] & 0xFF) | ((header[15] & 0xFF) << 8) | ((header[16] & 0xFF) << 16) | ((header[17] & 0xFF) << 24);
+        return header[0] is 0x42 && header[1] is 0x4D && infoHeaderSize is BMPHeaderFixedSize;
+    } catch (TracedException e) {
+        return false;
+    }
+}
+byte[] loadData(byte[] infoHeader) {
+    int stride = (width * bitCount + 7) / 8;
+    stride = (stride + 3) / 4 * 4; // Round up to 4 byte multiple
+    byte[] data = loadData(infoHeader, stride);
+    flipScanLines(data, stride, height);
+    return data;
+}
+byte[] loadData(byte[] infoHeader, int stride) {
+    int dataSize = height * stride;
+    byte[] data = new byte[dataSize];
+    try {
+        if (inputStream.read(data) !is dataSize)
+            DWT.error(DWT.ERROR_INVALID_IMAGE);
+    } catch (IOException e) {
+        DWT.error(DWT.ERROR_IO, e);
+    }
+    return data;
+}
+int[] loadFileHeader() {
+    int[] header = new int[5];
+    try {
+        header[0] = inputStream.readShort();
+        header[1] = inputStream.readInt();
+        header[2] = inputStream.readShort();
+        header[3] = inputStream.readShort();
+        header[4] = inputStream.readInt();
+    } catch (IOException e) {
+        DWT.error(DWT.ERROR_IO, e);
+    }
+    if (header[0] !is 0x4D42)
+        DWT.error(DWT.ERROR_INVALID_IMAGE);
+    return header;
+}
+ImageData[] loadFromByteStream() {
+    int[] fileHeader = loadFileHeader();
+    byte[] infoHeader = new byte[BMPHeaderFixedSize];
+    try {
+        inputStream.read(infoHeader);
+    } catch (TracedException e) {
+        DWT.error(DWT.ERROR_IO, e);
+    }
+    width = (infoHeader[4] & 0xFF) | ((infoHeader[5] & 0xFF) << 8);
+    height = (infoHeader[6] & 0xFF) | ((infoHeader[7] & 0xFF) << 8);
+    bitCount = (infoHeader[10] & 0xFF) | ((infoHeader[11] & 0xFF) << 8);
+    PaletteData palette = loadPalette(infoHeader);
+    if (inputStream.getPosition() < fileHeader[4]) {
+        // Seek to the specified offset
+        try {
+            inputStream.skip(fileHeader[4] - inputStream.getPosition());
+        } catch (IOException e) {
+            DWT.error(DWT.ERROR_IO, e);
+        }
+    }
+    byte[] data = loadData(infoHeader);
+    int type = DWT.IMAGE_OS2_BMP;
+    return [
+        ImageData.internal_new(
+            width,
+            height,
+            bitCount,
+            palette,
+            4,
+            data,
+            0,
+            null,
+            null,
+            -1,
+            -1,
+            type,
+            0,
+            0,
+            0,
+            0)
+    ];
+}
+PaletteData loadPalette(byte[] infoHeader) {
+    if (bitCount <= 8) {
+        int numColors = 1 << bitCount;
+        byte[] buf = new byte[numColors * 3];
+        try {
+            if (inputStream.read(buf) !is buf.length)
+                DWT.error(DWT.ERROR_INVALID_IMAGE);
+        } catch (IOException e) {
+            DWT.error(DWT.ERROR_IO, e);
+        }
+        return paletteFromBytes(buf, numColors);
+    }
+    if (bitCount is 16) return new PaletteData(0x7C00, 0x3E0, 0x1F);
+    if (bitCount is 24) return new PaletteData(0xFF, 0xFF00, 0xFF0000);
+    return new PaletteData(0xFF00, 0xFF0000, 0xFF000000);
+}
+PaletteData paletteFromBytes(byte[] bytes, int numColors) {
+    int bytesOffset = 0;
+    RGB[] colors = new RGB[numColors];
+    for (int i = 0; i < numColors; i++) {
+        colors[i] = new RGB(bytes[bytesOffset + 2] & 0xFF,
+            bytes[bytesOffset + 1] & 0xFF,
+            bytes[bytesOffset] & 0xFF);
+        bytesOffset += 3;
+    }
+    return new PaletteData(colors);
+}
+/**
+ * Answer a byte array containing the BMP representation of
+ * the given device independent palette.
+ */
+static byte[] paletteToBytes(PaletteData pal) {
+    int n = pal.colors is null ? 0 : (pal.colors.length < 256 ? pal.colors.length : 256);
+    byte[] bytes = new byte[n * 3];
+    int offset = 0;
+    for (int i = 0; i < n; i++) {
+        RGB col = pal.colors[i];
+        bytes[offset] = cast(byte)col.blue;
+        bytes[offset + 1] = cast(byte)col.green;
+        bytes[offset + 2] = cast(byte)col.red;
+        offset += 3;
+    }
+    return bytes;
+}
+/**
+ * Unload the given image's data into the given byte stream.
+ * Answer the number of bytes written.
+ */
+int unloadData(ImageData image, OutputStream ostr) {
+    int bmpBpl = 0;
+    try {
+        int bpl = (image.width * image.depth + 7) / 8;
+        bmpBpl = (bpl + 3) / 4 * 4; // BMP pads scanlines to multiples of 4 bytes
+        int linesPerBuf = 32678 / bmpBpl;
+        byte[] buf = new byte[linesPerBuf * bmpBpl];
+        byte[] data = image.data;
+        int imageBpl = image.bytesPerLine;
+        int dataIndex = imageBpl * (image.height - 1); // Start at last line
+        if (image.depth is 16) {
+            for (int y = 0; y < image.height; y += linesPerBuf) {
+                int count = image.height - y;
+                if (linesPerBuf < count) count = linesPerBuf;
+                int bufOffset = 0;
+                for (int i = 0; i < count; i++) {
+                    for (int wIndex = 0; wIndex < bpl; wIndex += 2) {
+                        buf[bufOffset + wIndex + 1] = data[dataIndex + wIndex + 1];
+                        buf[bufOffset + wIndex] = data[dataIndex + wIndex];
+                    }
+                    bufOffset += bmpBpl;
+                    dataIndex -= imageBpl;
+                }
+                ostr.write(buf, 0, bufOffset);
+            }
+        } else {
+            for (int y = 0; y < image.height; y += linesPerBuf) {
+                int tmp = image.height - y;
+                int count = tmp < linesPerBuf ? tmp : linesPerBuf;
+                int bufOffset = 0;
+                for (int i = 0; i < count; i++) {
+                    System.arraycopy(data, dataIndex, buf, bufOffset, bpl);
+                    bufOffset += bmpBpl;
+                    dataIndex -= imageBpl;
+                }
+                ostr.write(buf, 0, bufOffset);
+            }
+        }
+    } catch (IOException e) {
+        DWT.error(DWT.ERROR_IO, e);
+    }
+    return bmpBpl * image.height;
+}
+/**
+ * Unload a DeviceIndependentImage using Windows .BMP format into the given
+ * byte stream.
+ */
+void unloadIntoByteStream(ImageLoader loader) {
+    ImageData image = loader.data[0];
+    byte[] rgbs;
+    int numCols;
+    if (!((image.depth is 1) || (image.depth is 4) || (image.depth is 8) ||
+          (image.depth is 16) || (image.depth is 24) || (image.depth is 32)))
+            DWT.error(DWT.ERROR_UNSUPPORTED_DEPTH);
+    PaletteData pal = image.palette;
+    if ((image.depth is 16) || (image.depth is 24) || (image.depth is 32)) {
+        if (!pal.isDirect)
+            DWT.error(DWT.ERROR_INVALID_IMAGE);
+        numCols = 0;
+        rgbs = null;
+    } else {
+        if (pal.isDirect)
+            DWT.error(DWT.ERROR_INVALID_IMAGE);
+        numCols = pal.colors.length;
+        rgbs = paletteToBytes(pal);
+    }
+    // Fill in file header, except for bfsize, which is done later.
+    int headersSize = BMPFileHeaderSize + BMPHeaderFixedSize;
+    int[] fileHeader = new int[5];
+    fileHeader[0] = 0x4D42; // Signature
+    fileHeader[1] = 0; // File size - filled in later
+    fileHeader[2] = 0; // Reserved 1
+    fileHeader[3] = 0; // Reserved 2
+    fileHeader[4] = headersSize; // Offset to data
+    if (rgbs !is null) {
+        fileHeader[4] += rgbs.length;
+    }
+
+    // Prepare data. This is done first so we don't have to try to rewind
+    // the stream and fill in the details later.
+    ByteArrayOutputStream ostr = new ByteArrayOutputStream();
+    unloadData(image, ostr);
+    byte[] data = ostr.toByteArray();
+
+    // Calculate file size
+    fileHeader[1] = fileHeader[4] + data.length;
+
+    // Write the headers
+    try {
+        outputStream.writeShort(fileHeader[0]);
+        outputStream.writeInt(fileHeader[1]);
+        outputStream.writeShort(fileHeader[2]);
+        outputStream.writeShort(fileHeader[3]);
+        outputStream.writeInt(fileHeader[4]);
+    } catch (IOException e) {
+        DWT.error(DWT.ERROR_IO, e);
+    }
+    try {
+        outputStream.writeInt(BMPHeaderFixedSize);
+        outputStream.writeShort(image.width);
+        outputStream.writeShort(image.height);
+        outputStream.writeShort(1);
+        outputStream.writeShort(cast(short)image.depth);
+    } catch (IOException e) {
+        DWT.error(DWT.ERROR_IO, e);
+    }
+
+    // Unload palette
+    if (numCols > 0) {
+        try {
+            outputStream.write(rgbs);
+        } catch (IOException e) {
+            DWT.error(DWT.ERROR_IO, e);
+        }
+    }
+
+    // Unload the data
+    try {
+        outputStream.write(data);
+    } catch (IOException e) {
+        DWT.error(DWT.ERROR_IO, e);
+    }
+}
+void flipScanLines(byte[] data, int stride, int height) {
+    int i1 = 0;
+    int i2 = (height - 1) * stride;
+    for (int i = 0; i < height / 2; i++) {
+        for (int index = 0; index < stride; index++) {
+            byte b = data[index + i1];
+            data[index + i1] = data[index + i2];
+            data[index + i2] = b;
+        }
+        i1 += stride;
+        i2 -= stride;
+    }
+}
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dwt/internal/image/PNGFileFormat.d	Wed Jan 23 12:01:46 2008 +0100
@@ -0,0 +1,594 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2006 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *     IBM Corporation - initial API and implementation
+ * Port to the D programming language:
+ *     Frank Benoit <benoit@tionex.de>
+ *******************************************************************************/
+module dwt.internal.image.PNGFileFormat;
+
+
+import dwt.DWT;
+import dwt.graphics.ImageData;
+import dwt.graphics.ImageLoaderEvent;
+import dwt.graphics.ImageLoader;
+import dwt.graphics.PaletteData;
+import dwt.internal.Compatibility;
+import dwt.internal.image.FileFormat;
+import dwt.internal.image.PngIhdrChunk;
+import dwt.internal.image.PngPlteChunk;
+import dwt.internal.image.PngChunkReader;
+import dwt.internal.image.PngChunk;
+import dwt.internal.image.PngTrnsChunk;
+import dwt.internal.image.PngIdatChunk;
+import dwt.internal.image.PngEncoder;
+import dwt.internal.image.PngInputStream;
+import dwt.internal.image.PngDecodingDataStream;
+
+import dwt.dwthelper.BufferedInputStream;
+
+import tango.core.Exception;
+
+final class PNGFileFormat : FileFormat {
+    static final int SIGNATURE_LENGTH = 8;
+    static final int PRIME = 65521;
+    PngIhdrChunk headerChunk;
+    PngPlteChunk paletteChunk;
+    ImageData imageData;
+    byte[] data;
+    byte[] alphaPalette;
+    byte headerByte1;
+    byte headerByte2;
+    int adler;
+
+/**
+ * Skip over signature data. This has already been
+ * verified in isFileFormat().
+ */
+void readSignature()  {
+    byte[] signature = new byte[SIGNATURE_LENGTH];
+    inputStream.read(signature);
+}
+/**
+ * Load the PNG image from the byte stream.
+ */
+ImageData[] loadFromByteStream() {
+    try {
+        readSignature();
+        PngChunkReader chunkReader = new PngChunkReader(inputStream);
+        headerChunk = chunkReader.getIhdrChunk();
+        int width = headerChunk.getWidth(), height = headerChunk.getHeight();
+        if (width <= 0 || height <= 0) DWT.error(DWT.ERROR_INVALID_IMAGE);
+        int imageSize = getAlignedBytesPerRow() * height;
+        data = new byte[imageSize];
+        imageData = ImageData.internal_new(
+            width,
+            height,
+            headerChunk.getSwtBitsPerPixel(),
+            new PaletteData(0, 0, 0),
+            4,
+            data,
+            0,
+            null,
+            null,
+            -1,
+            -1,
+            DWT.IMAGE_PNG,
+            0,
+            0,
+            0,
+            0);
+
+        if (headerChunk.usesDirectColor()) {
+            imageData.palette = headerChunk.getPaletteData();
+        }
+
+        // Read and process chunks until the IEND chunk is encountered.
+        while (chunkReader.hasMoreChunks()) {
+            readNextChunk(chunkReader);
+        }
+
+        return [imageData];
+    } catch (IOException e) {
+        DWT.error(DWT.ERROR_INVALID_IMAGE);
+        return null;
+    }
+}
+/**
+ * Read and handle the next chunk of data from the
+ * PNG file.
+ */
+void readNextChunk(PngChunkReader chunkReader)  {
+    PngChunk chunk = chunkReader.readNextChunk();
+    switch (chunk.getChunkType()) {
+        case PngChunk.CHUNK_IEND:
+            break;
+        case PngChunk.CHUNK_PLTE:
+            if (!headerChunk.usesDirectColor()) {
+                paletteChunk = cast(PngPlteChunk) chunk;
+                imageData.palette = paletteChunk.getPaletteData();
+            }
+            break;
+        case PngChunk.CHUNK_tRNS:
+            PngTrnsChunk trnsChunk = cast(PngTrnsChunk) chunk;
+            if (trnsChunk.getTransparencyType(headerChunk) is
+                PngTrnsChunk.TRANSPARENCY_TYPE_PIXEL)
+            {
+                imageData.transparentPixel =
+                    trnsChunk.getSwtTransparentPixel(headerChunk);
+            } else {
+                alphaPalette = trnsChunk.getAlphaValues(headerChunk, paletteChunk);
+                int transparentCount = 0, transparentPixel = -1;
+                for (int i = 0; i < alphaPalette.length; i++) {
+                    if ((alphaPalette[i] & 0xFF) !is 255) {
+                        transparentCount++;
+                        transparentPixel = i;
+                    }
+                }
+                if (transparentCount is 0) {
+                    alphaPalette = null;
+                } else if (transparentCount is 1 && alphaPalette[transparentPixel] is 0) {
+                    alphaPalette = null;
+                    imageData.transparentPixel = transparentPixel;
+                }
+            }
+            break;
+        case PngChunk.CHUNK_IDAT:
+            if (chunkReader.readPixelData()) {
+                // All IDAT chunks in an image file must be
+                // sequential. If the pixel data has already
+                // been read and another IDAT block is encountered,
+                // then this is an invalid image.
+                DWT.error(DWT.ERROR_INVALID_IMAGE);
+            } else {
+                // Read in the pixel data for the image. This should
+                // go through all the image's IDAT chunks.
+                PngIdatChunk dataChunk = cast(PngIdatChunk) chunk;
+                readPixelData(dataChunk, chunkReader);
+            }
+            break;
+        default:
+            if (chunk.isCritical()) {
+                // All critical chunks must be supported.
+                DWT.error(DWT.ERROR_NOT_IMPLEMENTED);
+            }
+    }
+}
+void unloadIntoByteStream(ImageLoader loader) {
+    PngEncoder encoder = new PngEncoder(loader);
+    encoder.encode(outputStream);
+}
+bool isFileFormat(LEDataInputStream stream) {
+    try {
+        byte[] signature = new byte[SIGNATURE_LENGTH];
+        stream.read(signature);
+        stream.unread(signature);
+        if ((signature[0] & 0xFF) !is 137) return false; //137
+        if ((signature[1] & 0xFF) !is 80) return false; //P
+        if ((signature[2] & 0xFF) !is 78) return false; //N
+        if ((signature[3] & 0xFF) !is 71) return false; //G
+        if ((signature[4] & 0xFF) !is 13) return false; //<RETURN>
+        if ((signature[5] & 0xFF) !is 10) return false; //<LINEFEED>
+        if ((signature[6] & 0xFF) !is 26) return false; //<CTRL/Z>
+        if ((signature[7] & 0xFF) !is 10) return false; //<LINEFEED>
+        return true;
+    } catch (Exception e) {
+        return false;
+    }
+}
+/**
+ * DWT does not support 16-bit depths. If this image uses
+ * 16-bit depths, convert the data to an 8-bit depth.
+ */
+byte[] validateBitDepth(byte[] data) {
+    if (headerChunk.getBitDepth() > 8) {
+        byte[] result = new byte[data.length / 2];
+        compress16BitDepthTo8BitDepth(data, 0, result, 0, result.length);
+        return result;
+    } else {
+        return data;
+    }
+}
+/**
+ * DWT does not support greyscale as a color type. For
+ * plain grayscale, we create a palette. For Grayscale
+ * with Alpha, however, we need to convert the pixels
+ * to use RGB values.
+ * Note: This method assumes that the bit depth of the
+ * data has already been restricted to 8 or less.
+ */
+void setPixelData(byte[] data, ImageData imageData) {
+    switch (headerChunk.getColorType()) {
+        case PngIhdrChunk.COLOR_TYPE_GRAYSCALE_WITH_ALPHA:
+        {
+            int width = imageData.width;
+            int height = imageData.height;
+            int destBytesPerLine = imageData.bytesPerLine;
+            /*
+            * If the image uses 16-bit depth, it is converted
+            * to an 8-bit depth image.
+            */
+            int srcBytesPerLine = getAlignedBytesPerRow();
+            if (headerChunk.getBitDepth() > 8) srcBytesPerLine /= 2;
+
+            byte[] rgbData = new byte[destBytesPerLine * height];
+            byte[] alphaData = new byte[width * height];
+            for (int y = 0; y < height; y++) {
+                int srcIndex = srcBytesPerLine * y;
+                int destIndex = destBytesPerLine * y;
+                int destAlphaIndex = width * y;
+                for (int x = 0; x < width; x++) {
+                    byte grey = data[srcIndex];
+                    byte alpha = data[srcIndex + 1];
+                    rgbData[destIndex + 0] = grey;
+                    rgbData[destIndex + 1] = grey;
+                    rgbData[destIndex + 2] = grey;
+                    alphaData[destAlphaIndex] = alpha;
+                    srcIndex += 2;
+                    destIndex += 3;
+                    destAlphaIndex++;
+                }
+            }
+            imageData.data = rgbData;
+            imageData.alphaData = alphaData;
+            break;
+        }
+        case PngIhdrChunk.COLOR_TYPE_RGB_WITH_ALPHA:
+        {
+            int width = imageData.width;
+            int height = imageData.height;
+            int destBytesPerLine = imageData.bytesPerLine;
+            int srcBytesPerLine = getAlignedBytesPerRow();
+            /*
+            * If the image uses 16-bit depth, it is converted
+            * to an 8-bit depth image.
+            */
+            if (headerChunk.getBitDepth() > 8) srcBytesPerLine /= 2;
+
+            byte[] rgbData = new byte[destBytesPerLine * height];
+            byte[] alphaData = new byte[width * height];
+            for (int y = 0; y < height; y++) {
+                int srcIndex = srcBytesPerLine * y;
+                int destIndex = destBytesPerLine * y;
+                int destAlphaIndex = width * y;
+                for (int x = 0; x < width; x++) {
+                    rgbData[destIndex + 0] = data[srcIndex + 0];
+                    rgbData[destIndex + 1] = data[srcIndex + 1];
+                    rgbData[destIndex + 2] = data[srcIndex + 2];
+                    alphaData[destAlphaIndex] = data[srcIndex + 3];
+                    srcIndex += 4;
+                    destIndex += 3;
+                    destAlphaIndex++;
+                }
+            }
+            imageData.data = rgbData;
+            imageData.alphaData = alphaData;
+            break;
+        }
+        case PngIhdrChunk.COLOR_TYPE_RGB:
+            imageData.data = data;
+            break;
+        case PngIhdrChunk.COLOR_TYPE_PALETTE:
+            imageData.data = data;
+            if (alphaPalette !is null) {
+                int size = imageData.width * imageData.height;
+                byte[] alphaData = new byte[size];
+                byte[] pixelData = new byte[size];
+                imageData.getPixels(0, 0, size, pixelData, 0);
+                for (int i = 0; i < pixelData.length; i++) {
+                    alphaData[i] = alphaPalette[pixelData[i] & 0xFF];
+                }
+                imageData.alphaData = alphaData;
+            }
+            break;
+        default:
+            imageData.data = data;
+            break;
+    }
+}
+/**
+ * PNG supports some color types and bit depths that are
+ * unsupported by DWT. If the image uses an unsupported
+ * color type (either of the gray scale types) or bit
+ * depth (16), convert the data to an DWT-supported
+ * format. Then assign the data into the ImageData given.
+ */
+void setImageDataValues(byte[] data, ImageData imageData) {
+    byte[] result = validateBitDepth(data);
+    setPixelData(result, imageData);
+}
+/**
+ * Read the image data from the data stream. This must handle
+ * decoding the data, filtering, and interlacing.
+ */
+void readPixelData(PngIdatChunk chunk, PngChunkReader chunkReader)  {
+    InputStream stream = new PngInputStream(chunk, chunkReader);
+    //TEMPORARY CODE
+    //PORTING_FIXME
+    bool use3_2 = true;//System.getProperty("dwt.internal.image.PNGFileFormat_3.2") !is null;
+    InputStream inflaterStream = use3_2 ? null : Compatibility.newInflaterInputStream(stream);
+    if (inflaterStream !is null) {
+        stream = new BufferedInputStream(inflaterStream);
+    } else {
+        stream = new PngDecodingDataStream(stream);
+    }
+    int interlaceMethod = headerChunk.getInterlaceMethod();
+    if (interlaceMethod is PngIhdrChunk.INTERLACE_METHOD_NONE) {
+        readNonInterlacedImage(stream);
+    } else {
+        readInterlacedImage(stream);
+    }
+    /*
+    * InflaterInputStream does not consume all bytes in the stream
+    * when it is closed. This may leave unread IDAT chunks. The fix
+    * is to read all available bytes before closing it.
+    */
+    while (stream.available() > 0) stream.read();
+    stream.close();
+}
+/**
+ * Answer the number of bytes in a word-aligned row of pixel data.
+ */
+int getAlignedBytesPerRow() {
+    return ((getBytesPerRow(headerChunk.getWidth()) + 3) / 4) * 4;
+}
+/**
+ * Answer the number of bytes in each row of the image
+ * data. Each PNG row is byte-aligned, so images with bit
+ * depths less than a byte may have unused bits at the
+ * end of each row. The value of these bits is undefined.
+ */
+int getBytesPerRow() {
+    return getBytesPerRow(headerChunk.getWidth());
+}
+/**
+ * Answer the number of bytes needed to represent a pixel.
+ * This value depends on the image's color type and bit
+ * depth.
+ * Note that this method rounds up if an image's pixel size
+ * isn't byte-aligned.
+ */
+int getBytesPerPixel() {
+    int bitsPerPixel = headerChunk.getBitsPerPixel();
+    return (bitsPerPixel + 7) / 8;
+}
+/**
+ * Answer the number of bytes in a row of the given pixel
+ * width. Each row is byte-aligned, so images with bit
+ * depths less than a byte may have unused bits at the
+ * end of each row. The value of these bits is undefined.
+ */
+int getBytesPerRow(int rowWidthInPixels) {
+    int bitsPerPixel = headerChunk.getBitsPerPixel();
+    int bitsPerRow = bitsPerPixel * rowWidthInPixels;
+    int bitsPerByte = 8;
+    return (bitsPerRow + (bitsPerByte - 1)) / bitsPerByte;
+}
+/**
+ * 1. Read one of the seven frames of interlaced data.
+ * 2. Update the imageData.
+ * 3. Notify the image loader's listeners of the frame load.
+ */
+void readInterlaceFrame(
+    InputStream inputStream,
+    int rowInterval,
+    int columnInterval,
+    int startRow,
+    int startColumn,
+    int frameCount)
+{
+    int width = headerChunk.getWidth();
+    int alignedBytesPerRow = getAlignedBytesPerRow();
+    int height = headerChunk.getHeight();
+    if (startRow >= height || startColumn >= width) return;
+
+    int pixelsPerRow = (width - startColumn + columnInterval - 1) / columnInterval;
+    int bytesPerRow = getBytesPerRow(pixelsPerRow);
+    byte[] row1 = new byte[bytesPerRow];
+    byte[] row2 = new byte[bytesPerRow];
+    byte[] currentRow = row1;
+    byte[] lastRow = row2;
+    for (int row = startRow; row < height; row += rowInterval) {
+        byte filterType = cast(byte)inputStream.read();
+        int read = 0;
+        while (read !is bytesPerRow) {
+            read += inputStream.read(currentRow, read, bytesPerRow - read);
+        }
+        filterRow(currentRow, lastRow, filterType);
+        if (headerChunk.getBitDepth() >= 8) {
+            int bytesPerPixel = getBytesPerPixel();
+            int dataOffset = (row * alignedBytesPerRow) + (startColumn * bytesPerPixel);
+            for (int rowOffset = 0; rowOffset < currentRow.length; rowOffset += bytesPerPixel) {
+                for (int byteOffset = 0; byteOffset < bytesPerPixel; byteOffset++) {
+                    data[dataOffset + byteOffset] = currentRow[rowOffset + byteOffset];
+                }
+                dataOffset += (columnInterval * bytesPerPixel);
+            }
+        } else {
+            int bitsPerPixel = headerChunk.getBitDepth();
+            int pixelsPerByte = 8 / bitsPerPixel;
+            int column = startColumn;
+            int rowBase = row * alignedBytesPerRow;
+            int valueMask = 0;
+            for (int i = 0; i < bitsPerPixel; i++) {
+                valueMask <<= 1;
+                valueMask |= 1;
+            }
+            int maxShift = 8 - bitsPerPixel;
+            for (int byteOffset = 0; byteOffset < currentRow.length; byteOffset++) {
+                for (int bitOffset = maxShift; bitOffset >= 0; bitOffset -= bitsPerPixel) {
+                    if (column < width) {
+                        int dataOffset = rowBase + (column * bitsPerPixel / 8);
+                        int value = (currentRow[byteOffset] >> bitOffset) & valueMask;
+                        int dataShift = maxShift - (bitsPerPixel * (column % pixelsPerByte));
+                        data[dataOffset] |= value << dataShift;
+                    }
+                    column += columnInterval;
+                }
+            }
+        }
+        currentRow = (currentRow is row1) ? row2 : row1;
+        lastRow = (lastRow is row1) ? row2 : row1;
+    }
+    setImageDataValues(data, imageData);
+    fireInterlacedFrameEvent(frameCount);
+}
+/**
+ * Read the pixel data for an interlaced image from the
+ * data stream.
+ */
+void readInterlacedImage(InputStream inputStream)  {
+    readInterlaceFrame(inputStream, 8, 8, 0, 0, 0);
+    readInterlaceFrame(inputStream, 8, 8, 0, 4, 1);
+    readInterlaceFrame(inputStream, 8, 4, 4, 0, 2);
+    readInterlaceFrame(inputStream, 4, 4, 0, 2, 3);
+    readInterlaceFrame(inputStream, 4, 2, 2, 0, 4);
+    readInterlaceFrame(inputStream, 2, 2, 0, 1, 5);
+    readInterlaceFrame(inputStream, 2, 1, 1, 0, 6);
+}
+/**
+ * Fire an event to let listeners know that an interlaced
+ * frame has been loaded.
+ * finalFrame should be true if the image has finished
+ * loading, false if there are more frames to come.
+ */
+void fireInterlacedFrameEvent(int frameCount) {
+    if (loader.hasListeners()) {
+        ImageData image = cast(ImageData) imageData.clone();
+        bool finalFrame = frameCount is 6;
+        loader.notifyListeners(new ImageLoaderEvent(loader, image, frameCount, finalFrame));
+    }
+}
+/**
+ * Read the pixel data for a non-interlaced image from the
+ * data stream.
+ * Update the imageData to reflect the new data.
+ */
+void readNonInterlacedImage(InputStream inputStream)  {
+    int dataOffset = 0;
+    int alignedBytesPerRow = getAlignedBytesPerRow();
+    int bytesPerRow = getBytesPerRow();
+    byte[] row1 = new byte[bytesPerRow];
+    byte[] row2 = new byte[bytesPerRow];
+    byte[] currentRow = row1;
+    byte[] lastRow = row2;
+    int height = headerChunk.getHeight();
+    for (int row = 0; row < height; row++) {
+        byte filterType = cast(byte)inputStream.read();
+        int read = 0;
+        while (read !is bytesPerRow) {
+            read += inputStream.read(currentRow, read, bytesPerRow - read);
+        }
+        filterRow(currentRow, lastRow, filterType);
+        System.arraycopy(currentRow, 0, data, dataOffset, bytesPerRow);
+        dataOffset += alignedBytesPerRow;
+        currentRow = (currentRow is row1) ? row2 : row1;
+        lastRow = (lastRow is row1) ? row2 : row1;
+    }
+    setImageDataValues(data, imageData);
+}
+/**
+ * DWT does not support 16-bit depth color formats.
+ * Convert the 16-bit data to 8-bit data.
+ * The correct way to do this is to multiply each
+ * 16 bit value by the value:
+ * (2^8 - 1) / (2^16 - 1).
+ * The fast way to do this is just to drop the low
+ * byte of the 16-bit value.
+ */
+static void compress16BitDepthTo8BitDepth(
+    byte[] source,
+    int sourceOffset,
+    byte[] destination,
+    int destinationOffset,
+    int numberOfValues)
+{
+    //double multiplier = (Compatibility.pow2(8) - 1) / (Compatibility.pow2(16) - 1);
+    for (int i = 0; i < numberOfValues; i++) {
+        int sourceIndex = sourceOffset + (2 * i);
+        int destinationIndex = destinationOffset + i;
+        //int value = (source[sourceIndex] << 8) | source[sourceIndex + 1];
+        //byte compressedValue = (byte)(value * multiplier);
+        byte compressedValue = source[sourceIndex];
+        destination[destinationIndex] = compressedValue;
+    }
+}
+/**
+ * DWT does not support 16-bit depth color formats.
+ * Convert the 16-bit data to 8-bit data.
+ * The correct way to do this is to multiply each
+ * 16 bit value by the value:
+ * (2^8 - 1) / (2^16 - 1).
+ * The fast way to do this is just to drop the low
+ * byte of the 16-bit value.
+ */
+static int compress16BitDepthTo8BitDepth(int value) {
+    //double multiplier = (Compatibility.pow2(8) - 1) / (Compatibility.pow2(16) - 1);
+    //byte compressedValue = (byte)(value * multiplier);
+    return value >> 8;
+}
+/**
+ * PNG supports four filtering types. These types are applied
+ * per row of image data. This method unfilters the given row
+ * based on the filterType.
+ */
+void filterRow(byte[] row, byte[] previousRow, int filterType) {
+    int byteOffset = headerChunk.getFilterByteOffset();
+    switch (filterType) {
+        case PngIhdrChunk.FILTER_NONE:
+            break;
+        case PngIhdrChunk.FILTER_SUB:
+            for (int i = byteOffset; i < row.length; i++) {
+                int current = row[i] & 0xFF;
+                int left = row[i - byteOffset] & 0xFF;
+                row[i] = cast(byte)((current + left) & 0xFF);
+            }
+            break;
+        case PngIhdrChunk.FILTER_UP:
+            for (int i = 0; i < row.length; i++) {
+                int current = row[i] & 0xFF;
+                int above = previousRow[i] & 0xFF;
+                row[i] = cast(byte)((current + above) & 0xFF);
+            }
+            break;
+        case PngIhdrChunk.FILTER_AVERAGE:
+            for (int i = 0; i < row.length; i++) {
+                int left = (i < byteOffset) ? 0 : row[i - byteOffset] & 0xFF;
+                int above = previousRow[i] & 0xFF;
+                int current = row[i] & 0xFF;
+                row[i] = cast(byte)((current + ((left + above) / 2)) & 0xFF);
+            }
+            break;
+        case PngIhdrChunk.FILTER_PAETH:
+            for (int i = 0; i < row.length; i++) {
+                int left = (i < byteOffset) ? 0 : row[i - byteOffset] & 0xFF;
+                int aboveLeft = (i < byteOffset) ? 0 : previousRow[i - byteOffset] & 0xFF;
+                int above = previousRow[i] & 0xFF;
+
+                int a = Math.abs(above - aboveLeft);
+                int b = Math.abs(left - aboveLeft);
+                int c = Math.abs(left - aboveLeft + above - aboveLeft);
+
+                int preductor = 0;
+                if (a <= b && a <= c) {
+                    preductor = left;
+                } else if (b <= c) {
+                    preductor = above;
+                } else {
+                    preductor = aboveLeft;
+                }
+
+                int currentValue = row[i] & 0xFF;
+                row[i] = cast(byte) ((currentValue + preductor) & 0xFF);
+            }
+            break;
+        default:
+    }
+}
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dwt/internal/image/PngChunk.d	Wed Jan 23 12:01:46 2008 +0100
@@ -0,0 +1,385 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2006 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *     IBM Corporation - initial API and implementation
+ * Port to the D programming language:
+ *     Frank Benoit <benoit@tionex.de>
+ *******************************************************************************/
+module dwt.internal.image.PngChunk;
+
+
+import dwt.DWT;
+import dwt.internal.image.LEDataInputStream;
+import dwt.internal.image.PngFileReadState;
+import dwt.internal.image.PngIhdrChunk;
+import dwt.internal.image.PngPlteChunk;
+import dwt.internal.image.PngIdatChunk;
+import dwt.internal.image.PngIendChunk;
+import dwt.internal.image.PngTrnsChunk;
+
+import tango.core.Exception;
+import tango.text.convert.Format;
+
+class PngChunk {
+    byte[] reference;
+
+    static const int LENGTH_OFFSET = 0;
+    static const int TYPE_OFFSET = 4;
+    static const int DATA_OFFSET = 8;
+
+    static const int TYPE_FIELD_LENGTH = 4;
+    static const int LENGTH_FIELD_LENGTH = 4;
+    static const int MIN_LENGTH = 12;
+
+    static const int CHUNK_UNKNOWN = -1;
+    // Critical chunks.
+    static const int CHUNK_IHDR = 0;
+    static const int CHUNK_PLTE = 1;
+    static const int CHUNK_IDAT = 2;
+    static const int CHUNK_IEND = 3;
+    // Non-critical chunks.
+    static const int CHUNK_tRNS = 5;
+
+    static const byte[] TYPE_IHDR = cast(byte[])"IHDR";//{(byte) 'I', (byte) 'H', (byte) 'D', (byte) 'R'};
+    static const byte[] TYPE_PLTE = cast(byte[])"PLTE";//{(byte) 'P', (byte) 'L', (byte) 'T', (byte) 'E'};
+    static const byte[] TYPE_IDAT = cast(byte[])"IDAT";//{(byte) 'I', (byte) 'D', (byte) 'A', (byte) 'T'};
+    static const byte[] TYPE_IEND = cast(byte[])"IEND";//{(byte) 'I', (byte) 'E', (byte) 'N', (byte) 'D'};
+    static const byte[] TYPE_tRNS = cast(byte[])"tRNS";//{(byte) 't', (byte) 'R', (byte) 'N', (byte) 'S'};
+
+    static const int[] CRC_TABLE;
+    //public static void static_this() {
+    static this() {
+        CRC_TABLE = new int[256];
+        for (int i = 0; i < 256; i++) {
+            CRC_TABLE[i] = i;
+            for (int j = 0; j < 8; j++) {
+                if ((CRC_TABLE[i] & 0x1) is 0) {
+                    CRC_TABLE[i] = (CRC_TABLE[i] >> 1) & 0x7FFFFFFF;
+                } else {
+                    CRC_TABLE[i] = 0xEDB88320 ^ ((CRC_TABLE[i] >> 1) & 0x7FFFFFFF);
+                }
+            }
+        }
+    }
+
+    int length;
+
+/**
+ * Construct a PngChunk using the reference bytes
+ * given.
+ */
+this(byte[] reference) {
+    setReference(reference);
+    if (reference.length < LENGTH_OFFSET + LENGTH_FIELD_LENGTH) DWT.error(DWT.ERROR_INVALID_IMAGE);
+    length = getInt32(LENGTH_OFFSET);
+}
+
+/**
+ * Construct a PngChunk with the specified number of
+ * data bytes.
+ */
+this(int dataLength) {
+    this(new byte[MIN_LENGTH + dataLength]);
+    setLength(dataLength);
+}
+
+/**
+ * Get the PngChunk's reference byteArray;
+ */
+byte[] getReference() {
+    return reference;
+}
+
+/**
+ * Set the PngChunk's reference byteArray;
+ */
+void setReference(byte[] reference) {
+    this.reference = reference;
+}
+
+/**
+ * Get the 16-bit integer from the reference byte
+ * array at the given offset.
+ */
+int getInt16(int offset) {
+    int answer = 0;
+    answer |= (reference[offset] & 0xFF) << 8;
+    answer |= (reference[offset + 1] & 0xFF);
+    return answer;
+}
+
+/**
+ * Set the 16-bit integer in the reference byte
+ * array at the given offset.
+ */
+void setInt16(int offset, int value) {
+    reference[offset] = cast(byte) ((value >> 8) & 0xFF);
+    reference[offset + 1] = cast(byte) (value & 0xFF);
+}
+
+/**
+ * Get the 32-bit integer from the reference byte
+ * array at the given offset.
+ */
+int getInt32(int offset) {
+    int answer = 0;
+    answer |= (reference[offset] & 0xFF) << 24;
+    answer |= (reference[offset + 1] & 0xFF) << 16;
+    answer |= (reference[offset + 2] & 0xFF) << 8;
+    answer |= (reference[offset + 3] & 0xFF);
+    return answer;
+}
+
+/**
+ * Set the 32-bit integer in the reference byte
+ * array at the given offset.
+ */
+void setInt32(int offset, int value) {
+    reference[offset] = cast(byte) ((value >> 24) & 0xFF);
+    reference[offset + 1] = cast(byte) ((value >> 16) & 0xFF);
+    reference[offset + 2] = cast(byte) ((value >> 8) & 0xFF);
+    reference[offset + 3] = cast(byte) (value & 0xFF);
+}
+
+/**
+ * Get the length of the data component of this chunk.
+ * This is not the length of the entire chunk.
+ */
+int getLength() {
+    return length;
+}
+
+/**
+ * Set the length of the data component of this chunk.
+ * This is not the length of the entire chunk.
+ */
+void setLength(int value) {
+    setInt32(LENGTH_OFFSET, value);
+    length = value;
+}
+
+/**
+ * Get the chunk type. This is a four byte value.
+ * Each byte should be an ASCII character.
+ * The first byte is upper case if the chunk is critical.
+ * The second byte is upper case if the chunk is publicly defined.
+ * The third byte must be upper case.
+ * The fourth byte is upper case if the chunk is unsafe to copy.
+ * Public chunk types are defined by the PNG Development Group.
+ */
+byte[] getTypeBytes() {
+    byte[] type = new byte[4];
+    System.arraycopy(reference, TYPE_OFFSET, type, 0, TYPE_FIELD_LENGTH);
+    return type;
+}
+
+/**
+ * Set the chunk type. This is a four byte value.
+ * Each byte should be an ASCII character.
+ * The first byte is upper case if the chunk is critical.
+ * The second byte is upper case if the chunk is publicly defined.
+ * The third byte must be upper case.
+ * The fourth byte is upper case if the chunk is unsafe to copy.
+ * Public chunk types are defined by the PNG Development Group.
+ */
+void setType(byte[] value) {
+    if (value.length !is TYPE_FIELD_LENGTH) {
+        DWT.error (DWT.ERROR_INVALID_ARGUMENT);
+    }
+    System.arraycopy(value, 0, reference, TYPE_OFFSET, TYPE_FIELD_LENGTH);
+}
+
+/**
+ * Get the chunk's data.
+ */
+byte[] getData() {
+    int dataLength = getLength();
+    if (reference.length < MIN_LENGTH + dataLength) {
+        DWT.error (DWT.ERROR_INVALID_RANGE);
+    }
+    byte[] data = new byte[dataLength];
+    System.arraycopy(reference, DATA_OFFSET, data, 0, dataLength);
+    return data;
+}
+
+/**
+ * Set the chunk's data.
+ * This method has two side-effects.
+ * 1. It will set the length field to be the length
+ *    of the data array given.
+ * 2. It will set the CRC field to the computed CRC
+ *    value of the data array given.
+ */
+void setData(byte[] data) {
+    setLength(data.length);
+    System.arraycopy(data, 0, reference, DATA_OFFSET, data.length);
+    setCRC(computeCRC());
+}
+
+/**
+ * Get the CRC value for the chunk's data.
+ * Ensure that the length field has a good
+ * value before making this call.
+ */
+int getCRC() {
+    int crcOffset = DATA_OFFSET + getLength();
+    return getInt32(crcOffset);
+}
+
+/**
+ * Set the CRC value for the chunk's data.
+ * Ensure that the length field has a good
+ * value before making this call.
+ */
+void setCRC(int value) {
+    int crcOffset = DATA_OFFSET + getLength();
+    setInt32(crcOffset, value);
+}
+
+/**
+ * Get the chunk's total size including the length, type, and crc fields.
+ */
+int getSize() {
+    return MIN_LENGTH + getLength();
+}
+
+/**
+ * Compute the CRC value for the chunk's data. Answer
+ * whether this value matches the value stored in the
+ * chunk.
+ */
+bool checkCRC() {
+    int crc = computeCRC();
+    int storedCRC = getCRC();
+    return crc is storedCRC;
+}
+
+/**
+ * Answer the CRC value of chunk's data.
+ */
+int computeCRC() {
+    int crc = 0xFFFFFFFF;
+    int start = TYPE_OFFSET;
+    int stop = DATA_OFFSET + getLength();
+    for (int i = start; i < stop; i++) {
+        int index = (crc ^ reference[i]) & 0xFF;
+        crc =  CRC_TABLE[index] ^ ((crc >> 8) & 0x00FFFFFF);
+    }
+    return ~crc;
+}
+
+bool typeMatchesArray(byte[] array) {
+    for (int i = 0; i < TYPE_FIELD_LENGTH; i++) {
+        if (reference[TYPE_OFFSET + i] !is array[i]){
+            return false;
+        }
+    }
+    return true;
+}
+
+bool isCritical() {
+    char c = cast(char) getTypeBytes()[0];
+    return 'A' <= c && c <= 'Z';
+}
+
+int getChunkType() {
+    if (typeMatchesArray(TYPE_IHDR)) return CHUNK_IHDR;
+    if (typeMatchesArray(TYPE_PLTE)) return CHUNK_PLTE;
+    if (typeMatchesArray(TYPE_IDAT)) return CHUNK_IDAT;
+    if (typeMatchesArray(TYPE_IEND)) return CHUNK_IEND;
+    if (typeMatchesArray(TYPE_tRNS)) return CHUNK_tRNS;
+    return CHUNK_UNKNOWN;
+}
+
+/**
+ * Read the next PNG chunk from the input stream given.
+ * If unable to read a chunk, return null.
+ */
+static PngChunk readNextFromStream(LEDataInputStream stream) {
+    try {
+        int headerLength = LENGTH_FIELD_LENGTH + TYPE_FIELD_LENGTH;
+        byte[] headerBytes = new byte[headerLength];
+        int result = stream.read(headerBytes, 0, headerLength);
+        stream.unread(headerBytes);
+        if (result !is headerLength) return null;
+
+        PngChunk tempChunk = new PngChunk(headerBytes);
+
+        int chunkLength = tempChunk.getSize();
+        byte[] chunk = new byte[chunkLength];
+
+        result = stream.read(chunk, 0, chunkLength);
+        if (result !is chunkLength) return null;
+
+        switch (tempChunk.getChunkType()) {
+            case CHUNK_IHDR:
+                return new PngIhdrChunk(chunk);
+            case CHUNK_PLTE:
+                return new PngPlteChunk(chunk);
+            case CHUNK_IDAT:
+                return new PngIdatChunk(chunk);
+            case CHUNK_IEND:
+                return new PngIendChunk(chunk);
+            case CHUNK_tRNS:
+                return new PngTrnsChunk(chunk);
+            default:
+                return new PngChunk(chunk);
+        }
+    } catch (IOException e) {
+        return null;
+    }
+}
+
+/**
+ * Answer whether the chunk is a valid PNG chunk.
+ */
+void validate(PngFileReadState readState, PngIhdrChunk headerChunk) {
+    if (reference.length < MIN_LENGTH) DWT.error(DWT.ERROR_INVALID_IMAGE);
+
+    byte[] type = getTypeBytes();
+
+    // The third character MUST be upper case.
+    char c = cast(char) type[2];
+    if (!('A' <= c && c <= 'Z')) DWT.error(DWT.ERROR_INVALID_IMAGE);
+
+    // All characters must be letters.
+    for (int i = 0; i < TYPE_FIELD_LENGTH; i++) {
+        c = cast(char) type[i];
+        if (!(('a' <= c && c <= 'z') || ('A' <= c && c <= 'Z'))) {
+            DWT.error(DWT.ERROR_INVALID_IMAGE);
+        }
+    }
+
+    // The stored CRC must match the data's computed CRC.
+    if (!checkCRC()) DWT.error(DWT.ERROR_INVALID_IMAGE);
+}
+
+/**
+ * Provided so that subclasses can override and add
+ * data to the toString() call.
+ */
+char[] contributeToString() {
+    return "";
+}
+
+/**
+ * Returns a string containing a concise, human-readable
+ * description of the receiver.
+ *
+ * @return a string representation of the event
+ */
+public char[] toString() {
+    char[] buffer = Format( "{\n\tLength: {}\n\tType: {}{}\n\tCRC: {:X}\n}",
+        getLength(),
+        cast(char[]) getTypeBytes(),
+        contributeToString(),
+        getCRC());
+    return buffer;
+}
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dwt/internal/image/PngChunkReader.d	Wed Jan 23 12:01:46 2008 +0100
@@ -0,0 +1,80 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2006 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *     IBM Corporation - initial API and implementation
+ * Port to the D programming language:
+ *     Frank Benoit <benoit@tionex.de>
+ *******************************************************************************/
+module dwt.internal.image.PngChunkReader;
+
+
+import dwt.DWT;
+import dwt.internal.image.LEDataInputStream;
+import dwt.internal.image.PngFileReadState;
+import dwt.internal.image.PngIhdrChunk;
+import dwt.internal.image.PngPlteChunk;
+import dwt.internal.image.PngTrnsChunk;
+import dwt.internal.image.PngChunk;
+
+public class PngChunkReader {
+    LEDataInputStream inputStream;
+    PngFileReadState readState;
+    PngIhdrChunk headerChunk;
+    PngPlteChunk paletteChunk;
+
+this(LEDataInputStream inputStream) {
+    this.inputStream = inputStream;
+    readState = new PngFileReadState();
+    headerChunk = null;
+}
+
+PngIhdrChunk getIhdrChunk() {
+    if (headerChunk is null) {
+        PngChunk chunk = PngChunk.readNextFromStream(inputStream);
+        if (chunk is null) DWT.error(DWT.ERROR_INVALID_IMAGE);
+        if(( headerChunk = cast(PngIhdrChunk) chunk ) !is null ){
+            headerChunk.validate(readState, null);
+        }
+        else{
+            DWT.error(DWT.ERROR_INVALID_IMAGE);
+        }
+    }
+    return headerChunk;
+}
+
+PngChunk readNextChunk() {
+    if (headerChunk is null) return getIhdrChunk();
+
+    PngChunk chunk = PngChunk.readNextFromStream(inputStream);
+    if (chunk is null) DWT.error(DWT.ERROR_INVALID_IMAGE);
+    switch (chunk.getChunkType()) {
+        case PngChunk.CHUNK_tRNS:
+            (cast(PngTrnsChunk) chunk).validate(readState, headerChunk, paletteChunk);
+            break;
+        case PngChunk.CHUNK_PLTE:
+            chunk.validate(readState, headerChunk);
+            paletteChunk = cast(PngPlteChunk) chunk;
+            break;
+        default:
+            chunk.validate(readState, headerChunk);
+    }
+    if (readState.readIDAT && !(chunk.getChunkType() is PngChunk.CHUNK_IDAT)) {
+        readState.readPixelData = true;
+    }
+    return chunk;
+}
+
+bool readPixelData() {
+    return readState.readPixelData;
+}
+
+bool hasMoreChunks() {
+    return !readState.readIEND;
+}
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dwt/internal/image/PngDecodingDataStream.d	Wed Jan 23 12:01:46 2008 +0100
@@ -0,0 +1,139 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2006 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *     IBM Corporation - initial API and implementation
+ * Port to the D programming language:
+ *     Frank Benoit <benoit@tionex.de>
+ *******************************************************************************/
+module dwt.internal.image.PngDecodingDataStream;
+
+
+import dwt.dwthelper.InputStream;
+
+import dwt.DWT;
+import dwt.internal.image.PngLzBlockReader;
+
+public class PngDecodingDataStream : InputStream {
+    alias InputStream.read read;
+    InputStream stream;
+    byte currentByte;
+    int nextBitIndex;
+
+    PngLzBlockReader lzBlockReader;
+    int adlerValue;
+
+    static final int PRIME = 65521;
+    static final int MAX_BIT = 7;
+
+this(InputStream stream) {
+    super();
+    this.stream = stream;
+    nextBitIndex = MAX_BIT + 1;
+    adlerValue = 1;
+    lzBlockReader = new PngLzBlockReader(this);
+    readCompressedDataHeader();
+    lzBlockReader.readNextBlockHeader();
+}
+
+/**
+ * This method should be called when the image decoder thinks
+ * that all of the compressed image data has been read. This
+ * method will ensure that the next data value is an end of
+ * block marker. If there are more blocks after this one,
+ * the method will read them and ensure that they are empty.
+ */
+void assertImageDataAtEnd() {
+    lzBlockReader.assertCompressedDataAtEnd();
+}
+
+public void close() {
+    assertImageDataAtEnd();
+    checkAdler();
+}
+
+int getNextIdatBits(int length) {
+    int value = 0;
+    for (int i = 0; i < length; i++) {
+        value |= (getNextIdatBit() << i);
+    }
+    return value;
+}
+
+int getNextIdatBit() {
+    if (nextBitIndex > MAX_BIT) {
+        currentByte = getNextIdatByte();
+        nextBitIndex = 0;
+    }
+    return (currentByte & (1 << nextBitIndex)) >> nextBitIndex++;
+}
+
+byte getNextIdatByte() {
+    byte nextByte = cast(byte)stream.read();
+    nextBitIndex = MAX_BIT + 1;
+    return nextByte;
+}
+
+void updateAdler(byte value) {
+    int low = adlerValue & 0xFFFF;
+    int high = (adlerValue >> 16) & 0xFFFF;
+    int valueInt = value & 0xFF;
+    low = (low + valueInt) % PRIME;
+    high = (low + high) % PRIME;
+    adlerValue = (high << 16) | low;
+}
+
+public override int read() {
+    byte nextDecodedByte = lzBlockReader.getNextByte();
+    updateAdler(nextDecodedByte);
+    return nextDecodedByte & 0xFF;
+}
+
+public override int read(byte[] buffer, int off, int len) {
+    for (int i = 0; i < len; i++) {
+        int b = read();
+        if (b is -1) return i;
+        buffer[off + i] = cast(byte)b;
+    }
+    return len;
+}
+
+void error() {
+    DWT.error(DWT.ERROR_INVALID_IMAGE);
+}
+
+private void readCompressedDataHeader() {
+    byte headerByte1 = getNextIdatByte();
+    byte headerByte2 = getNextIdatByte();
+
+    int number = ((headerByte1 & 0xFF) << 8) | (headerByte2 & 0xFF);
+    if (number % 31 !is 0) error();
+
+    int compressionMethod = headerByte1 & 0x0F;
+    if (compressionMethod !is 8) error();
+
+    int windowSizeHint = (headerByte1 & 0xF0) >> 4;
+    if (windowSizeHint > 7) error();
+    int windowSize = (1 << (windowSizeHint + 8));
+    lzBlockReader.setWindowSize(windowSize);
+
+    int dictionary = (headerByte2 & (1 << 5));
+    if (dictionary !is 0) error();
+
+//  int compressionLevel = (headerByte2 & 0xC0) >> 6;
+}
+
+void checkAdler() {
+    int storedAdler = 0;
+    storedAdler |= ((getNextIdatByte() & 0xFF) << 24);
+    storedAdler |= ((getNextIdatByte() & 0xFF) << 16);
+    storedAdler |= ((getNextIdatByte() & 0xFF) << 8);
+    storedAdler |=  (getNextIdatByte() & 0xFF);
+    if (storedAdler !is adlerValue) error();
+}
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dwt/internal/image/PngDeflater.d	Wed Jan 23 12:01:46 2008 +0100
@@ -0,0 +1,623 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2006 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *     IBM Corporation - initial API and implementation
+ * Port to the D programming language:
+ *     Frank Benoit <benoit@tionex.de>
+ *******************************************************************************/
+module dwt.internal.image.PngDeflater;
+
+import dwt.dwthelper.ByteArrayOutputStream;
+
+public class PngDeflater {
+
+    static const int BASE = 65521;
+    static const int WINDOW = 32768;
+    static const int MIN_LENGTH = 3;
+    static const int MAX_MATCHES = 32;
+    static const int HASH = 8209;
+
+    byte[] istr;
+    int inLength;
+
+    ByteArrayOutputStream bytes;
+
+    int adler32 = 1;
+
+    int buffer, bitCount;
+
+    Link[HASH] hashtable;// = new Link[HASH];
+    Link[WINDOW] window;// = new Link[WINDOW];
+    int nextWindow;
+
+public this(){
+    bytes = new ByteArrayOutputStream(1024);
+}
+
+class Link {
+
+    int hash, value;
+    Link previous, next;
+
+    this() {
+
+        this.hash = 0;
+        this.value = 0;
+        this.previous = null;
+        this.next = null;
+
+    }
+
+}
+
+class Match {
+
+    int length, distance;
+
+    this(int length, int distance) {
+
+        this.length = length;
+        this.distance = distance;
+
+    }
+
+}
+
+static const short mirrorBytes[] = [ cast(short)
+
+    0x00, 0x80, 0x40, 0xc0, 0x20, 0xa0, 0x60, 0xe0,
+    0x10, 0x90, 0x50, 0xd0, 0x30, 0xb0, 0x70, 0xf0,
+    0x08, 0x88, 0x48, 0xc8, 0x28, 0xa8, 0x68, 0xe8,
+    0x18, 0x98, 0x58, 0xd8, 0x38, 0xb8, 0x78, 0xf8,
+    0x04, 0x84, 0x44, 0xc4, 0x24, 0xa4, 0x64, 0xe4,
+    0x14, 0x94, 0x54, 0xd4, 0x34, 0xb4, 0x74, 0xf4,
+    0x0c, 0x8c, 0x4c, 0xcc, 0x2c, 0xac, 0x6c, 0xec,
+    0x1c, 0x9c, 0x5c, 0xdc, 0x3c, 0xbc, 0x7c, 0xfc,
+    0x02, 0x82, 0x42, 0xc2, 0x22, 0xa2, 0x62, 0xe2,
+    0x12, 0x92, 0x52, 0xd2, 0x32, 0xb2, 0x72, 0xf2,
+    0x0a, 0x8a, 0x4a, 0xca, 0x2a, 0xaa, 0x6a, 0xea,
+    0x1a, 0x9a, 0x5a, 0xda, 0x3a, 0xba, 0x7a, 0xfa,
+    0x06, 0x86, 0x46, 0xc6, 0x26, 0xa6, 0x66, 0xe6,
+    0x16, 0x96, 0x56, 0xd6, 0x36, 0xb6, 0x76, 0xf6,
+    0x0e, 0x8e, 0x4e, 0xce, 0x2e, 0xae, 0x6e, 0xee,
+    0x1e, 0x9e, 0x5e, 0xde, 0x3e, 0xbe, 0x7e, 0xfe,
+    0x01, 0x81, 0x41, 0xc1, 0x21, 0xa1, 0x61, 0xe1,
+    0x11, 0x91, 0x51, 0xd1, 0x31, 0xb1, 0x71, 0xf1,
+    0x09, 0x89, 0x49, 0xc9, 0x29, 0xa9, 0x69, 0xe9,
+    0x19, 0x99, 0x59, 0xd9, 0x39, 0xb9, 0x79, 0xf9,
+    0x05, 0x85, 0x45, 0xc5, 0x25, 0xa5, 0x65, 0xe5,
+    0x15, 0x95, 0x55, 0xd5, 0x35, 0xb5, 0x75, 0xf5,
+    0x0d, 0x8d, 0x4d, 0xcd, 0x2d, 0xad, 0x6d, 0xed,
+    0x1d, 0x9d, 0x5d, 0xdd, 0x3d, 0xbd, 0x7d, 0xfd,
+    0x03, 0x83, 0x43, 0xc3, 0x23, 0xa3, 0x63, 0xe3,
+    0x13, 0x93, 0x53, 0xd3, 0x33, 0xb3, 0x73, 0xf3,
+    0x0b, 0x8b, 0x4b, 0xcb, 0x2b, 0xab, 0x6b, 0xeb,
+    0x1b, 0x9b, 0x5b, 0xdb, 0x3b, 0xbb, 0x7b, 0xfb,
+    0x07, 0x87, 0x47, 0xc7, 0x27, 0xa7, 0x67, 0xe7,
+    0x17, 0x97, 0x57, 0xd7, 0x37, 0xb7, 0x77, 0xf7,
+    0x0f, 0x8f, 0x4f, 0xcf, 0x2f, 0xaf, 0x6f, 0xef,
+    0x1f, 0x9f, 0x5f, 0xdf, 0x3f, 0xbf, 0x7f, 0xff,
+
+];
+
+static class Code {
+
+    int code, extraBits, min, max;
+
+    this(int code, int extraBits, int min, int max) {
+
+        this.code = code;
+        this.extraBits = extraBits;
+        this.min = min;
+        this.max = max;
+
+    }
+
+}
+
+static const Code lengthCodes[];
+static const Code distanceCodes[];
+
+static this() {
+    lengthCodes = [
+        new Code(257, 0, 3, 3),
+        new Code(258, 0, 4, 4),
+        new Code(259, 0, 5, 5),
+        new Code(260, 0, 6, 6),
+        new Code(261, 0, 7, 7),
+        new Code(262, 0, 8, 8),
+        new Code(263, 0, 9, 9),
+        new Code(264, 0, 10, 10),
+        new Code(265, 1, 11, 12),
+        new Code(266, 1, 13, 14),
+        new Code(267, 1, 15, 16),
+        new Code(268, 1, 17, 18),
+        new Code(269, 2, 19, 22),
+        new Code(270, 2, 23, 26),
+        new Code(271, 2, 27, 30),
+        new Code(272, 2, 31, 34),
+        new Code(273, 3, 35, 42),
+        new Code(274, 3, 43, 50),
+        new Code(275, 3, 51, 58),
+        new Code(276, 3, 59, 66),
+        new Code(277, 4, 67, 82),
+        new Code(278, 4, 83, 98),
+        new Code(279, 4, 99, 114),
+        new Code(280, 4, 115, 130),
+        new Code(281, 5, 131, 162),
+        new Code(282, 5, 163, 194),
+        new Code(283, 5, 195, 226),
+        new Code(284, 5, 227, 257),
+        new Code(285, 0, 258, 258)];
+
+    distanceCodes = [
+        new Code(0, 0, 1, 1),
+        new Code(1, 0, 2, 2),
+        new Code(2, 0, 3, 3),
+        new Code(3, 0, 4, 4),
+        new Code(4, 1, 5, 6),
+        new Code(5, 1, 7, 8),
+        new Code(6, 2, 9, 12),
+        new Code(7, 2, 13, 16),
+        new Code(8, 3, 17, 24),
+        new Code(9, 3, 25, 32),
+        new Code(10, 4, 33, 48),
+        new Code(11, 4, 49, 64),
+        new Code(12, 5, 65, 96),
+        new Code(13, 5, 97, 128),
+        new Code(14, 6, 129, 192),
+        new Code(15, 6, 193, 256),
+        new Code(16, 7, 257, 384),
+        new Code(17, 7, 385, 512),
+        new Code(18, 8, 513, 768),
+        new Code(19, 8, 769, 1024),
+        new Code(20, 9, 1025, 1536),
+        new Code(21, 9, 1537, 2048),
+        new Code(22, 10, 2049, 3072),
+        new Code(23, 10, 3073, 4096),
+        new Code(24, 11, 4097, 6144),
+        new Code(25, 11, 6145, 8192),
+        new Code(26, 12, 8193, 12288),
+        new Code(27, 12, 12289, 16384),
+        new Code(28, 13, 16385, 24576),
+        new Code(29, 13, 24577, 32768)];
+}
+
+void writeShortLSB(ByteArrayOutputStream baos, int theShort) {
+
+    byte byte1 = cast(byte) (theShort & 0xff);
+    byte byte2 = cast(byte) ((theShort >> 8) & 0xff);
+    byte[] temp = [byte1, byte2];
+    baos.write(temp, 0, 2);
+
+}
+
+void writeInt(ByteArrayOutputStream baos, int theInt) {
+
+    byte byte1 = cast(byte) ((theInt >> 24) & 0xff);
+    byte byte2 = cast(byte) ((theInt >> 16) & 0xff);
+    byte byte3 = cast(byte) ((theInt >> 8) & 0xff);
+    byte byte4 = cast(byte) (theInt & 0xff);
+    byte[] temp = [byte1, byte2, byte3, byte4];
+    baos.write(temp, 0, 4);
+
+}
+
+void updateAdler(byte value) {
+
+    int low = adler32 & 0xffff;
+    int high = (adler32 >> 16) & 0xffff;
+    int valueInt = value & 0xff;
+    low = (low + valueInt) % BASE;
+    high = (low + high) % BASE;
+    adler32 = (high << 16) | low;
+
+}
+
+int hash(byte[] bytes) {
+
+    int hash = ((bytes[0] & 0xff) << 24 | (bytes[1] & 0xff) << 16 | (bytes[2] & 0xff) << 8) % HASH;
+    if (hash < 0) {
+        hash = hash + HASH;
+    }
+    return hash;
+
+}
+
+void writeBits(int value, int count) {
+
+    buffer |= value << bitCount;
+    bitCount += count;
+    if (bitCount >= 16) {
+        bytes.write(cast(byte) buffer);
+        bytes.write(cast(byte) (buffer >>> 8));
+        buffer >>>= 16;
+        bitCount -= 16;
+    }
+
+}
+
+void alignToByte() {
+
+    if (bitCount > 0) {
+        bytes.write(cast(byte) buffer);
+        if (bitCount > 8) bytes.write(cast(byte) (buffer >>> 8));
+    }
+    buffer = 0;
+    bitCount = 0;
+
+}
+
+void outputLiteral(byte literal) {
+
+    int i = literal & 0xff;
+
+    if (i <= 143) {
+        // 0 through 143 are 8 bits long starting at 00110000
+        writeBits(mirrorBytes[0x30 + i], 8);
+    }
+    else {
+        // 144 through 255 are 9 bits long starting at 110010000
+        writeBits(1 + 2 * mirrorBytes[0x90 - 144 + i], 9);
+    }
+
+}
+
+Code findCode(int value, Code[] codes) {
+
+    int i, j, k;
+
+    i = -1;
+    j = codes.length;
+    while (true) {
+        k = (j + i) / 2;
+        if (value < codes[k].min) {
+            j = k;
+        }
+        else if (value > codes[k].max) {
+            i = k;
+        }
+        else {
+            return codes[k];
+        }
+    }
+
+}
+
+void outputMatch(int length, int distance) {
+
+    Code d, l;
+    int thisLength;
+
+    while (length > 0) {
+
+        // we can transmit matches of lengths 3 through 258 inclusive
+        // so if length exceeds 258, we must transmit in several steps,
+        // with 258 or less in each step
+
+        if (length > 260) {
+            thisLength = 258;
+        }
+        else if (length <= 258) {
+            thisLength = length;
+        }
+        else {
+            thisLength = length - 3;
+        }
+
+        length = length - thisLength;
+
+        // find length code
+        l = findCode(thisLength, lengthCodes);
+
+        // transmit the length code
+        // 256 through 279 are 7 bits long starting at 0000000
+        // 280 through 287 are 8 bits long starting at 11000000
+        if (l.code <= 279) {
+            writeBits(mirrorBytes[(l.code - 256) * 2], 7);
+        }
+        else {
+            writeBits(mirrorBytes[0xc0 - 280 + l.code], 8);
+        }
+
+        // transmit the extra bits
+        if (l.extraBits !is 0) {
+            writeBits(thisLength - l.min, l.extraBits);
+        }
+
+        // find distance code
+        d = findCode(distance, distanceCodes);
+
+        // transmit the distance code
+        // 5 bits long starting at 00000
+        writeBits(mirrorBytes[d.code * 8], 5);
+
+        // transmit the extra bits
+        if (d.extraBits !is 0) {
+            writeBits(distance - d.min, d.extraBits);
+        }
+
+    }
+
+}
+
+Match findLongestMatch(int position, Link firstPosition) {
+
+    Link link = firstPosition;
+    int numberOfMatches = 0;
+    Match bestMatch = new Match(-1, -1);
+
+    while (true) {
+
+        int matchPosition = link.value;
+
+        if (position - matchPosition < WINDOW && matchPosition !is 0) {
+
+            int i;
+
+            for (i = 1; position + i < inLength; i++) {
+                if (istr[position + i] !is istr[matchPosition + i]) {
+                    break;
+                }
+            }
+
+            if (i >= MIN_LENGTH) {
+
+                if (i > bestMatch.length) {
+                    bestMatch.length = i;
+                    bestMatch.distance = position - matchPosition;
+                }
+
+                numberOfMatches = numberOfMatches + 1;
+
+                if (numberOfMatches is MAX_MATCHES) {
+                    break;
+                }
+
+            }
+
+        }
+
+        link = link.next;
+        if (link is null) {
+            break;
+        }
+
+    }
+
+    if (bestMatch.length < MIN_LENGTH || bestMatch.distance < 1 || bestMatch.distance > WINDOW) {
+        return null;
+    }
+
+    return bestMatch;
+
+}
+
+void updateHashtable(int to, int from) {
+
+    byte[] data = new byte[3];
+    int hashval;
+    Link temp;
+
+    for (int i = to; i < from; i++) {
+
+        if (i + MIN_LENGTH > inLength) {
+            break;
+        }
+
+        data[0] = istr[i];
+        data[1] = istr[i + 1];
+        data[2] = istr[i + 2];
+
+        hashval = hash(data);
+
+        if (window[nextWindow].previous !is null) {
+            window[nextWindow].previous.next = null;
+        }
+        else if (window[nextWindow].hash !is 0) {
+            hashtable[window[nextWindow].hash].next = null;
+        }
+
+        window[nextWindow].hash = hashval;
+        window[nextWindow].value = i;
+        window[nextWindow].previous = null;
+        temp = window[nextWindow].next = hashtable[hashval].next;
+        hashtable[hashval].next = window[nextWindow];
+        if (temp !is null) {
+            temp.previous = window[nextWindow];
+        }
+
+        nextWindow = nextWindow + 1;
+        if (nextWindow is WINDOW) {
+            nextWindow = 0;
+        }
+
+    }
+
+}
+
+void compress() {
+
+    int position, newPosition;
+    byte[] data = new byte[3];
+    int hashval;
+    for (int i = 0; i < HASH; i++) {
+        hashtable[i] = new Link();
+    }
+    for (int i = 0; i < WINDOW; i++) {
+        window[i] = new Link();
+    }
+    nextWindow = 0;
+    Link firstPosition;
+    Match match;
+    int deferredPosition = -1;
+    Match deferredMatch = null;
+
+    writeBits(0x01, 1); // BFINAL = 0x01 (final block)
+    writeBits(0x01, 2); // BTYPE = 0x01 (compression with fixed Huffman codes)
+
+    // just output first byte so we never match at zero
+    outputLiteral(istr[0]);
+    position = 1;
+
+    while (position < inLength) {
+
+        if (inLength - position < MIN_LENGTH) {
+            outputLiteral(istr[position]);
+            position = position + 1;
+            continue;
+        }
+
+        data[0] = istr[position];
+        data[1] = istr[position + 1];
+        data[2] = istr[position + 2];
+
+        hashval = hash(data);
+        firstPosition = hashtable[hashval];
+
+        match = findLongestMatch(position, firstPosition);
+
+        updateHashtable(position, position + 1);
+
+        if (match !is null) {
+
+            if (deferredMatch !is null) {
+                if (match.length > deferredMatch.length + 1) {
+                    // output literal at deferredPosition
+                    outputLiteral(istr[deferredPosition]);
+                    // defer this match
+                    deferredPosition = position;
+                    deferredMatch = match;
+                    position = position + 1;
+                }
+                else {
+                    // output deferredMatch
+                    outputMatch(deferredMatch.length, deferredMatch.distance);
+                    newPosition = deferredPosition + deferredMatch.length;
+                    deferredPosition = -1;
+                    deferredMatch = null;
+                    updateHashtable(position + 1, newPosition);
+                    position = newPosition;
+                }
+            }
+            else {
+                // defer this match
+                deferredPosition = position;
+                deferredMatch = match;
+                position = position + 1;
+            }
+
+        }
+
+        else {
+
+            // no match found
+            if (deferredMatch !is null) {
+                outputMatch(deferredMatch.length, deferredMatch.distance);
+                newPosition = deferredPosition + deferredMatch.length;
+                deferredPosition = -1;
+                deferredMatch = null;
+                updateHashtable(position + 1, newPosition);
+                position = newPosition;
+            }
+            else {
+                outputLiteral(istr[position]);
+                position = position + 1;
+            }
+
+        }
+
+    }
+
+    writeBits(0, 7); // end of block code
+    alignToByte();
+
+}
+
+void compressHuffmanOnly() {
+
+    int position;
+
+    writeBits(0x01, 1); // BFINAL = 0x01 (final block)
+    writeBits(0x01, 2); // BTYPE = 0x01 (compression with fixed Huffman codes)
+
+    for (position = 0; position < inLength;) {
+
+        outputLiteral(istr[position]);
+        position = position + 1;
+
+    }
+
+    writeBits(0, 7); // end of block code
+    alignToByte();
+
+}
+
+void store() {
+
+    // stored blocks are limited to 0xffff bytes
+
+    int start = 0;
+    int length = inLength;
+    int blockLength;
+    int BFINAL = 0x00; // BFINAL = 0x00 or 0x01 (if final block), BTYPE = 0x00 (no compression)
+
+    while (length > 0) {
+
+        if (length < 65535) {
+            blockLength = length;
+            BFINAL = 0x01;
+        }
+        else {
+            blockLength = 65535;
+            BFINAL = 0x00;
+        }
+
+        // write data header
+        bytes.write(cast(byte) BFINAL);
+        writeShortLSB(bytes, blockLength); // LEN
+        writeShortLSB(bytes, blockLength ^ 0xffff); // NLEN (one's complement of LEN)
+
+        // write actual data
+        bytes.write(istr, start, blockLength);
+
+        length = length - blockLength;
+        start = start + blockLength;
+
+    }
+
+}
+
+public byte[] deflate(byte[] input) {
+
+    istr = input;
+    inLength = input.length;
+
+    // write zlib header
+    bytes.write(cast(byte) 0x78); // window size = 0x70 (32768), compression method = 0x08
+    bytes.write(cast(byte) 0x9C); // compression level = 0x80 (default), check bits = 0x1C
+
+    // compute checksum
+    for (int i = 0; i < inLength; i++) {
+        updateAdler(istr[i]);
+    }
+
+    //store();
+
+    //compressHuffmanOnly();
+
+    compress();
+
+    // write checksum
+    writeInt(bytes, adler32);
+
+    return bytes.toByteArray();
+
+}
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dwt/internal/image/PngEncoder.d	Wed Jan 23 12:01:46 2008 +0100
@@ -0,0 +1,359 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2006 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *     IBM Corporation - initial API and implementation
+ * Port to the D programming language:
+ *     Frank Benoit <benoit@tionex.de>
+ *******************************************************************************/
+module dwt.internal.image.PngEncoder;
+
+import dwt.internal.image.LEDataOutputStream;
+import dwt.internal.image.PngDeflater;
+import dwt.dwthelper.ByteArrayOutputStream;
+import dwt.DWT;
+import dwt.graphics.ImageData;
+import dwt.graphics.ImageLoader;
+import dwt.graphics.RGB;
+import dwt.internal.image.PngChunk;
+
+import tango.core.Exception;
+
+final class PngEncoder {
+
+    static const byte SIGNATURE[] = [cast(byte) '\211', cast(byte) 'P', cast(byte) 'N', cast(byte) 'G', cast(byte) '\r', cast(byte) '\n', cast(byte) '\032', cast(byte) '\n'];
+    static const byte TAG_IHDR[] = [cast(byte) 'I', cast(byte) 'H', cast(byte) 'D', cast(byte) 'R'];
+    static const byte TAG_PLTE[] = [cast(byte) 'P', cast(byte) 'L', cast(byte) 'T', cast(byte) 'E'];
+    static const byte TAG_TRNS[] = [cast(byte) 't', cast(byte) 'R', cast(byte) 'N', cast(byte) 'S'];
+    static const byte TAG_IDAT[] = [cast(byte) 'I', cast(byte) 'D', cast(byte) 'A', cast(byte) 'T'];
+    static const byte TAG_IEND[] = [cast(byte) 'I', cast(byte) 'E', cast(byte) 'N', cast(byte) 'D'];
+
+    ByteArrayOutputStream bytes;
+    PngChunk chunk;
+
+    ImageLoader loader;
+    ImageData data;
+    int transparencyType;
+
+    int width, height, bitDepth, colorType;
+
+    int compressionMethod = 0;
+    int filterMethod = 0;
+    int interlaceMethod = 0;
+
+public this(ImageLoader loader) {
+    this.bytes = new ByteArrayOutputStream(1024);
+    this.loader = loader;
+    this.data = loader.data[0];
+    this.transparencyType = data.getTransparencyType();
+
+    this.width = data.width;
+    this.height = data.height;
+
+    this.bitDepth = 8;
+
+    this.colorType = 2;
+
+    if (data.palette.isDirect) {
+        if (transparencyType is DWT.TRANSPARENCY_ALPHA) {
+            this.colorType = 6;
+        }
+    }
+    else {
+        this.colorType = 3;
+    }
+
+    if (!(colorType is 2 || colorType is 3 || colorType is 6)) DWT.error(DWT.ERROR_INVALID_IMAGE);
+
+}
+
+void writeShort(ByteArrayOutputStream baos, int theShort) {
+
+    byte byte1 = cast(byte) ((theShort >> 8) & 0xff);
+    byte byte2 = cast(byte) (theShort & 0xff);
+    byte[] temp = [byte1, byte2];
+    baos.write(temp, 0, 2);
+
+}
+
+void writeInt(ByteArrayOutputStream baos, int theInt) {
+
+    byte byte1 = cast(byte) ((theInt >> 24) & 0xff);
+    byte byte2 = cast(byte) ((theInt >> 16) & 0xff);
+    byte byte3 = cast(byte) ((theInt >> 8) & 0xff);
+    byte byte4 = cast(byte) (theInt & 0xff);
+    byte[] temp = [byte1, byte2, byte3, byte4];
+    baos.write(temp, 0, 4);
+
+}
+
+void writeChunk(byte[] tag, byte[] buffer) {
+
+    int bufferLength = (buffer !is null) ? buffer.length : 0;
+
+    chunk = new PngChunk(bufferLength);
+
+    writeInt(bytes, bufferLength);
+    bytes.write(tag, 0, 4);
+    chunk.setType(tag);
+    if (bufferLength !is 0) {
+        bytes.write(buffer, 0, bufferLength);
+        chunk.setData(buffer);
+    }
+    else {
+        chunk.setCRC(chunk.computeCRC());
+    }
+    writeInt(bytes, chunk.getCRC());
+
+}
+
+void writeSignature() {
+
+    bytes.write(SIGNATURE, 0, 8);
+
+}
+
+void writeHeader() {
+
+    ByteArrayOutputStream baos = new ByteArrayOutputStream(13);
+
+    writeInt(baos, width);
+    writeInt(baos, height);
+    baos.write(bitDepth);
+    baos.write(colorType);
+    baos.write(compressionMethod);
+    baos.write(filterMethod);
+    baos.write(interlaceMethod);
+
+    writeChunk(TAG_IHDR, baos.toByteArray());
+
+}
+
+void writePalette() {
+
+    RGB[] RGBs = data.palette.getRGBs();
+
+    if (RGBs.length > 256) DWT.error(DWT.ERROR_INVALID_IMAGE);
+
+    ByteArrayOutputStream baos = new ByteArrayOutputStream(RGBs.length);
+
+    for (int i = 0; i < RGBs.length; i++) {
+
+        baos.write(cast(byte) RGBs[i].red);
+        baos.write(cast(byte) RGBs[i].green);
+        baos.write(cast(byte) RGBs[i].blue);
+
+    }
+
+    writeChunk(TAG_PLTE, baos.toByteArray());
+
+}
+
+void writeTransparency() {
+
+    ByteArrayOutputStream baos = new ByteArrayOutputStream();
+
+    switch (transparencyType) {
+
+        case DWT.TRANSPARENCY_ALPHA:
+
+            int pixelValue, alphaValue;
+
+            byte[] alphas = new byte[data.palette.getRGBs().length];
+
+            for (int y = 0; y < height; y++) {
+
+                for (int x = 0; x < width; x++) {
+
+                    pixelValue = data.getPixel(x, y);
+                    alphaValue = data.getAlpha(x, y);
+
+                    alphas[pixelValue] = cast(byte) alphaValue;
+
+                }
+
+            }
+
+            baos.write(alphas, 0, alphas.length);
+
+            break;
+
+        case DWT.TRANSPARENCY_PIXEL:
+
+            int pixel = data.transparentPixel;
+
+            if (colorType is 2) {
+
+                int redMask = data.palette.redMask;
+                int redShift = data.palette.redShift;
+                int greenMask = data.palette.greenMask;
+                int greenShift = data.palette.greenShift;
+                int blueShift = data.palette.blueShift;
+                int blueMask = data.palette.blueMask;
+
+                int r = pixel & redMask;
+                r = (redShift < 0) ? r >>> -redShift : r << redShift;
+                int g = pixel & greenMask;
+                g = (greenShift < 0) ? g >>> -greenShift : g << greenShift;
+                int b = pixel & blueMask;
+                b = (blueShift < 0) ? b >>> -blueShift : b << blueShift;
+
+                writeShort(baos, r);
+                writeShort(baos, g);
+                writeShort(baos, b);
+
+            }
+
+            if (colorType is 3) {
+
+                byte[] padding = new byte[pixel + 1];
+
+                for (int i = 0; i < pixel; i++) {
+
+                    padding[i] = cast(byte) 255;
+
+                }
+
+                padding[pixel] = cast(byte) 0;
+
+                baos.write(padding, 0, padding.length);
+
+            }
+
+            break;
+        default:
+
+    }
+
+    writeChunk(TAG_TRNS, baos.toByteArray());
+
+}
+
+void writeImageData() {
+
+    ByteArrayOutputStream baos = new ByteArrayOutputStream(1024);
+
+    if (colorType is 3) {
+
+        int[] lineData = new int[width];
+
+        for (int y = 0; y < height; y++) {
+
+            byte filter[] = [0];
+            baos.write(filter, 0, 1);
+
+            data.getPixels(0, y, width, lineData, 0);
+
+            for (int x = 0; x < lineData.length; x++) {
+
+                baos.write(cast(byte) lineData[x]);
+
+            }
+
+        }
+
+    }
+
+    else {
+
+        int[] lineData = new int[width];
+        byte[] alphaData = new byte[width];
+
+        int redMask = data.palette.redMask;
+        int redShift = data.palette.redShift;
+        int greenMask = data.palette.greenMask;
+        int greenShift = data.palette.greenShift;
+        int blueShift = data.palette.blueShift;
+        int blueMask = data.palette.blueMask;
+
+        for (int y = 0; y < height; y++) {
+
+            byte filter[] = [0];
+            baos.write(filter, 0, 1);
+
+            data.getPixels(0, y, width, lineData, 0);
+
+            if (colorType is 6) {
+                data.getAlphas(0, y, width, alphaData, 0);
+            }
+
+            for (int x = 0; x < lineData.length; x++) {
+
+                int pixel = lineData[x];
+
+                int r = pixel & redMask;
+                r = (redShift < 0) ? r >>> -redShift : r << redShift;
+                int g = pixel & greenMask;
+                g = (greenShift < 0) ? g >>> -greenShift : g << greenShift;
+                int b = pixel & blueMask;
+                b = (blueShift < 0) ? b >>> -blueShift : b << blueShift;
+
+                byte pixels[] = [cast(byte) r, cast(byte) g, cast(byte) b];
+                baos.write(pixels, 0, 3);
+
+                if (colorType is 6) {
+
+                    byte alpha[] = [alphaData[x]];
+                    baos.write(alpha, 0, 1);
+
+                }
+
+            }
+
+        }
+
+    }
+
+    PngDeflater deflater = new PngDeflater();
+    byte[] compressed = deflater.deflate(baos.toByteArray());
+
+    writeChunk(TAG_IDAT, compressed);
+
+}
+
+void writeEnd() {
+
+    writeChunk(TAG_IEND, null);
+
+}
+
+public void encode(LEDataOutputStream outputStream) {
+
+    try {
+
+        writeSignature();
+        writeHeader();
+
+        if (colorType is 3) {
+            writePalette();
+        }
+
+        bool transparencyAlpha = (transparencyType is DWT.TRANSPARENCY_ALPHA);
+        bool transparencyPixel = (transparencyType is DWT.TRANSPARENCY_PIXEL);
+        bool type2Transparency = (colorType is 2 && transparencyPixel);
+        bool type3Transparency = (colorType is 3 && (transparencyAlpha || transparencyPixel));
+
+        if (type2Transparency || type3Transparency) {
+            writeTransparency();
+        }
+
+        writeImageData();
+        writeEnd();
+
+        outputStream.write(bytes.toByteArray());
+
+    }
+
+    catch (IOException e) {
+
+        DWT.error(DWT.ERROR_IO, e);
+
+    }
+
+}
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dwt/internal/image/PngFileReadState.d	Wed Jan 23 12:01:46 2008 +0100
@@ -0,0 +1,27 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2004 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *     IBM Corporation - initial API and implementation
+ * Port to the D programming language:
+ *     Frank Benoit <benoit@tionex.de>
+ *******************************************************************************/
+module dwt.internal.image.PngFileReadState;
+
+
+class PngFileReadState {
+    bool readIHDR;
+    bool readPLTE;
+    bool readIDAT;
+    bool readIEND;
+
+    // Non - critical chunks
+    bool readTRNS;
+
+    // Set to true after IDATs have been read.
+    bool readPixelData;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dwt/internal/image/PngHuffmanTable.d	Wed Jan 23 12:01:46 2008 +0100
@@ -0,0 +1,126 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2006 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *     IBM Corporation - initial API and implementation
+ * Port to the D programming language:
+ *     Frank Benoit <benoit@tionex.de>
+ *******************************************************************************/
+module dwt.internal.image.PngHuffmanTable;
+
+import dwt.internal.image.PngDecodingDataStream;
+
+public class PngHuffmanTable {
+    CodeLengthInfo[] codeLengthInfo;
+    int[] codeValues;
+
+    static const int MAX_CODE_LENGTH = 15;
+    static const int BAD_CODE = 0xFFFFFFF;
+    static const int incs[] = [1391376, 463792, 198768, 86961, 33936, 13776, 4592, 1968, 861, 336, 112, 48, 21, 7, 3, 1];
+
+this (int[] lengths) {
+    initialize(lengths);
+    generateTable(lengths);
+}
+
+private void initialize(int[] lengths) {
+    codeValues = new int[lengths.length];
+    for (int i = 0; i < codeValues.length; i++) {
+        codeValues[i] = i;
+    }
+
+    // minCodesByLength[n] : The smallest Huffman code of length n + 1.
+    // maxCodesByLength[n] : The largest Huffman code of length n + 1.
+    // indexesByLength[n] : Index into the values array. First value with a code of length n + 1.
+    codeLengthInfo = new CodeLengthInfo[MAX_CODE_LENGTH];
+    for (int i = 0; i < MAX_CODE_LENGTH; i++) {
+        codeLengthInfo[i] = new CodeLengthInfo();
+        codeLengthInfo[i].length = i;
+        codeLengthInfo[i].baseIndex = 0;
+        codeLengthInfo[i].min = BAD_CODE;
+        codeLengthInfo[i].max = -1;
+    }
+}
+
+private void generateTable(int[] lengths) {
+    // Sort the values using shellsort. Primary key is code size. Secondary key is value.
+    int codeValuesTemp;
+    for (int k = 0; k < 16; k++) {
+        for (int h = incs[k], i = h; i < lengths.length; i++) {
+            int v = lengths[i];
+            codeValuesTemp = codeValues[i];
+            int j = i;
+            while (j >= h && (lengths[j - h] > v || (lengths[j - h] is v && codeValues[j - h] > codeValuesTemp))) {
+                lengths[j] = lengths[j - h];
+                codeValues[j] = codeValues[j - h];
+                j -= h;
+            }
+            lengths[j] = v;
+            codeValues[j] = codeValuesTemp;
+        }
+    }
+
+    // These values in these arrays correspond to the elements of the
+    // "values" array. The Huffman code for codeValues[N] is codes[N]
+    // and the length of the code is lengths[N].
+    int[] codes = new int[lengths.length];
+    int lastLength = 0;
+    int code = 0;
+    for (int i = 0; i < lengths.length; i++) {
+        while (lastLength !is lengths[i]) {
+            lastLength++;
+            code <<= 1;
+        }
+        if (lastLength !is 0) {
+            codes[i] = code;
+            code++;
+        }
+    }
+
+    int last = 0;
+    for (int i = 0; i < lengths.length; i++) {
+        if (last !is lengths[i]) {
+            last = lengths[i];
+            codeLengthInfo[last - 1].baseIndex = i;
+            codeLengthInfo[last - 1].min = codes[i];
+        }
+        if (last !is 0) codeLengthInfo[last - 1].max = codes[i];
+    }
+}
+
+int getNextValue(PngDecodingDataStream stream) {
+    int code = stream.getNextIdatBit();
+    int codelength = 0;
+
+    // Here we are taking advantage of the fact that 1 bits are used as
+    // a prefix to the longer codeValues.
+    while (codelength < MAX_CODE_LENGTH && code > codeLengthInfo[codelength].max) {
+        code = ((code << 1) | stream.getNextIdatBit());
+        codelength++;
+    }
+    if (codelength >= MAX_CODE_LENGTH) stream.error();
+
+    // Now we have a Huffman code of length (codelength + 1) that
+    // is somewhere in the range
+    // minCodesByLength[codelength]..maxCodesByLength[codelength].
+    // This code is the (offset + 1)'th code of (codelength + 1);
+    int offset = code - codeLengthInfo[codelength].min;
+
+    // indexesByLength[codelength] is the first code of length (codelength + 1)
+    // so now we can look up the value for the Huffman code in the table.
+    int index = codeLengthInfo[codelength].baseIndex + offset;
+    return codeValues[index];
+}
+
+class CodeLengthInfo {
+    int length;
+    int max;
+    int min;
+    int baseIndex;
+}
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dwt/internal/image/PngHuffmanTables.d	Wed Jan 23 12:01:46 2008 +0100
@@ -0,0 +1,164 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2006 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *     IBM Corporation - initial API and implementation
+ * Port to the D programming language:
+ *     Frank Benoit <benoit@tionex.de>
+ *******************************************************************************/
+module dwt.internal.image.PngHuffmanTables;
+
+import dwt.internal.image.PngHuffmanTable;
+import dwt.internal.image.PngDecodingDataStream;
+import dwt.internal.image.PngLzBlockReader;
+
+public class PngHuffmanTables {
+    PngHuffmanTable literalTable;
+    PngHuffmanTable distanceTable;
+
+    static PngHuffmanTable FixedLiteralTable;
+    static PngHuffmanTable FixedDistanceTable;
+
+    static final int LiteralTableSize = 288;
+    static final int[] FixedLiteralLengths = [
+        8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8,
+        8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8,
+        8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8,
+        8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8,
+        8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8,
+        8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8,
+        9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9,
+        9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9,
+        9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9,
+        9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9,
+        9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 7, 7, 7, 7, 7, 7, 7, 7,
+        7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 8, 8, 8, 8, 8, 8, 8, 8,
+    ];
+
+    static final int DistanceTableSize = 32;
+    static final int[] FixedDistanceLengths = [
+        5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
+        5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
+    ];
+
+    static final int LengthCodeTableSize = 19;
+    static final int[] LengthCodeOrder = [
+        16, 17, 18, 0, 8, 7, 9, 6, 10, 5,
+        11, 4, 12, 3, 13, 2, 14, 1, 15
+    ];
+
+static PngHuffmanTables getDynamicTables(PngDecodingDataStream stream)  {
+    return new PngHuffmanTables(stream);
+}
+static PngHuffmanTables getFixedTables() {
+    return new PngHuffmanTables();
+}
+
+private PngHuffmanTable getFixedLiteralTable() {
+    if (FixedLiteralTable is null) {
+        FixedLiteralTable = new PngHuffmanTable(FixedLiteralLengths);
+    }
+    return FixedLiteralTable;
+}
+
+private PngHuffmanTable getFixedDistanceTable() {
+    if (FixedDistanceTable is null) {
+        FixedDistanceTable = new PngHuffmanTable(FixedDistanceLengths);
+    }
+    return FixedDistanceTable;
+}
+
+private this () {
+    literalTable = getFixedLiteralTable();
+    distanceTable = getFixedDistanceTable();
+}
+
+private this (PngDecodingDataStream stream)  {
+    int literals = PngLzBlockReader.FIRST_LENGTH_CODE
+        + stream.getNextIdatBits(5);
+    int distances = PngLzBlockReader.FIRST_DISTANCE_CODE
+        + stream.getNextIdatBits(5);
+    int codeLengthCodes = PngLzBlockReader.FIRST_CODE_LENGTH_CODE
+        + stream.getNextIdatBits(4);
+
+    if (codeLengthCodes > PngLzBlockReader.LAST_CODE_LENGTH_CODE) {
+        stream.error();
+    }
+
+    /* Tricky, tricky, tricky. The length codes are stored in
+     * a very odd order. (For the order, see the definition of
+     * the static field lengthCodeOrder.) Also, the data may
+     * not contain values for all the codes. It may just contain
+     * values for the first X number of codes. The table should
+     * be of size <LengthCodeTableSize> regardless of the number
+     * of values actually given in the table.
+     */
+    int[] lengthCodes = new int[LengthCodeTableSize];
+    for (int i = 0; i < codeLengthCodes; i++) {
+        lengthCodes[LengthCodeOrder[i]] = stream.getNextIdatBits(3);
+    }
+    PngHuffmanTable codeLengthsTable = new PngHuffmanTable(lengthCodes);
+
+    int[] literalLengths = readLengths(
+        stream, literals, codeLengthsTable, LiteralTableSize);
+    int[] distanceLengths = readLengths(
+        stream, distances, codeLengthsTable, DistanceTableSize);
+
+    literalTable = new PngHuffmanTable(literalLengths);
+    distanceTable = new PngHuffmanTable(distanceLengths);
+}
+
+private int [] readLengths (PngDecodingDataStream stream,
+    int numLengths,
+    PngHuffmanTable lengthsTable,
+    int tableSize)
+{
+    int[] lengths = new int[tableSize];
+
+    for (int index = 0; index < numLengths;) {
+        int value = lengthsTable.getNextValue(stream);
+        if (value < 16) {
+            // Literal value
+            lengths[index] = value;
+            index++;
+        } else if (value is 16) {
+            // Repeat the previous code 3-6 times.
+            int count = stream.getNextIdatBits(2) + 3;
+            for (int i = 0; i < count; i++) {
+                lengths[index] = lengths [index - 1];
+                index++;
+            }
+        } else if (value is 17) {
+            // Repeat 0 3-10 times.
+            int count = stream.getNextIdatBits(3) + 3;
+            for (int i = 0; i < count; i++) {
+                lengths[index] = 0;
+                index++;
+            }
+        } else if (value is 18) {
+            // Repeat 0 11-138 times.
+            int count = stream.getNextIdatBits(7) + 11;
+            for (int i = 0; i < count; i++) {
+                lengths[index] = 0;
+                index++;
+            }
+        } else {
+            stream.error();
+        }
+    }
+    return lengths;
+}
+
+int getNextLiteralValue(PngDecodingDataStream stream)  {
+    return literalTable.getNextValue(stream);
+}
+
+int getNextDistanceValue(PngDecodingDataStream stream)  {
+    return distanceTable.getNextValue(stream);
+}
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dwt/internal/image/PngIdatChunk.d	Wed Jan 23 12:01:46 2008 +0100
@@ -0,0 +1,67 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2006 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *     IBM Corporation - initial API and implementation
+ * Port to the D programming language:
+ *     Frank Benoit <benoit@tionex.de>
+ *******************************************************************************/
+module dwt.internal.image.PngIdatChunk;
+
+
+import dwt.DWT;
+import dwt.internal.image.PngFileReadState;
+import dwt.internal.image.PngIhdrChunk;
+import dwt.internal.image.PngChunk;
+
+class PngIdatChunk : PngChunk {
+
+    static const int HEADER_BYTES_LENGTH = 2;
+    static const int ADLER_FIELD_LENGTH = 4;
+    static const int HEADER_BYTE1_DATA_OFFSET = DATA_OFFSET + 0;
+    static const int HEADER_BYTE2_DATA_OFFSET = DATA_OFFSET + 1;
+    static const int ADLER_DATA_OFFSET = DATA_OFFSET + 2; // plus variable compressed data length
+
+this(byte headerByte1, byte headerByte2, byte[] data, int adler) {
+    super(data.length + HEADER_BYTES_LENGTH + ADLER_FIELD_LENGTH);
+    setType(TYPE_IDAT);
+    reference[HEADER_BYTE1_DATA_OFFSET] = headerByte1;
+    reference[HEADER_BYTE2_DATA_OFFSET] = headerByte2;
+    System.arraycopy(data, 0, reference, DATA_OFFSET, data.length);
+    setInt32(ADLER_DATA_OFFSET, adler);
+    setCRC(computeCRC());
+}
+
+this(byte[] reference) {
+    super(reference);
+}
+
+int getChunkType() {
+    return CHUNK_IDAT;
+}
+
+/**
+ * Answer whether the chunk is a valid IDAT chunk.
+ */
+void validate(PngFileReadState readState, PngIhdrChunk headerChunk) {
+    if (!readState.readIHDR
+        || (headerChunk.getMustHavePalette() && !readState.readPLTE)
+        || readState.readIEND)
+    {
+        DWT.error(DWT.ERROR_INVALID_IMAGE);
+    } else {
+        readState.readIDAT = true;
+    }
+
+    super.validate(readState, headerChunk);
+}
+
+byte getDataByteAtOffset(int offset) {
+    return reference[DATA_OFFSET + offset];
+}
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dwt/internal/image/PngIendChunk.d	Wed Jan 23 12:01:46 2008 +0100
@@ -0,0 +1,60 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2006 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *     IBM Corporation - initial API and implementation
+ * Port to the D programming language:
+ *     Frank Benoit <benoit@tionex.de>
+ *******************************************************************************/
+module dwt.internal.image.PngIendChunk;
+
+
+import dwt.DWT;
+import dwt.internal.image.PngFileReadState;
+import dwt.internal.image.PngIhdrChunk;
+import dwt.internal.image.PngChunk;
+
+class PngIendChunk : PngChunk {
+
+this() {
+    super(0);
+    setType(TYPE_IEND);
+    setCRC(computeCRC());
+}
+
+this(byte[] reference){
+    super(reference);
+}
+
+int getChunkType() {
+    return CHUNK_IEND;
+}
+
+/**
+ * Answer whether the chunk is a valid IEND chunk.
+ */
+override void validate(PngFileReadState readState, PngIhdrChunk headerChunk) {
+    // An IEND chunk is invalid if no IHDR has been read.
+    // Or if a palette is required and has not been read.
+    // Or if no IDAT chunk has been read.
+    if (!readState.readIHDR
+        || (headerChunk.getMustHavePalette() && !readState.readPLTE)
+        || !readState.readIDAT
+        || readState.readIEND)
+    {
+        DWT.error(DWT.ERROR_INVALID_IMAGE);
+    } else {
+        readState.readIEND = true;
+    }
+
+    super.validate(readState, headerChunk);
+
+    // IEND chunks are not allowed to have any data.
+    if (getLength() > 0) DWT.error(DWT.ERROR_INVALID_IMAGE);
+}
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dwt/internal/image/PngIhdrChunk.d	Wed Jan 23 12:01:46 2008 +0100
@@ -0,0 +1,403 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2006 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *     IBM Corporation - initial API and implementation
+ * Port to the D programming language:
+ *     Frank Benoit <benoit@tionex.de>
+ *******************************************************************************/
+module dwt.internal.image.PngIhdrChunk;
+
+
+import dwt.DWT;
+import dwt.graphics.PaletteData;
+import dwt.graphics.RGB;
+import dwt.internal.image.PngFileReadState;
+import dwt.internal.image.PngIhdrChunk;
+import dwt.internal.image.PngChunk;
+import tango.text.convert.Format;
+
+class PngIhdrChunk : PngChunk {
+    static const int IHDR_DATA_LENGTH = 13;
+
+    static const int WIDTH_DATA_OFFSET = DATA_OFFSET + 0;
+    static const int HEIGHT_DATA_OFFSET = DATA_OFFSET + 4;
+    static const int BIT_DEPTH_OFFSET = DATA_OFFSET + 8;
+    static const int COLOR_TYPE_OFFSET = DATA_OFFSET + 9;
+    static const int COMPRESSION_METHOD_OFFSET = DATA_OFFSET + 10;
+    static const int FILTER_METHOD_OFFSET = DATA_OFFSET + 11;
+    static const int INTERLACE_METHOD_OFFSET = DATA_OFFSET + 12;
+
+    static const byte COLOR_TYPE_GRAYSCALE = 0;
+    static const byte COLOR_TYPE_RGB = 2;
+    static const byte COLOR_TYPE_PALETTE = 3;
+    static const byte COLOR_TYPE_GRAYSCALE_WITH_ALPHA = 4;
+    static const byte COLOR_TYPE_RGB_WITH_ALPHA = 6;
+
+    static const int INTERLACE_METHOD_NONE = 0;
+    static const int INTERLACE_METHOD_ADAM7 = 1;
+
+    static const int FILTER_NONE = 0;
+    static const int FILTER_SUB = 1;
+    static const int FILTER_UP = 2;
+    static const int FILTER_AVERAGE = 3;
+    static const int FILTER_PAETH = 4;
+
+    static const byte[] ValidBitDepths = [ cast(byte)1, 2, 4, 8, 16];
+    static const byte[] ValidColorTypes = [ cast(byte)0, 2, 3, 4, 6];
+
+    int width, height;
+    byte bitDepth, colorType, compressionMethod, filterMethod, interlaceMethod;
+
+this(int width, int height, byte bitDepth, byte colorType, byte compressionMethod, byte filterMethod, byte interlaceMethod) {
+    super(IHDR_DATA_LENGTH);
+    setType(TYPE_IHDR);
+    setWidth(width);
+    setHeight(height);
+    setBitDepth(bitDepth);
+    setColorType(colorType);
+    setCompressionMethod(compressionMethod);
+    setFilterMethod(filterMethod);
+    setInterlaceMethod(interlaceMethod);
+    setCRC(computeCRC());
+}
+
+/**
+ * Construct a PNGChunk using the reference bytes
+ * given.
+ */
+this(byte[] reference) {
+    super(reference);
+    if (reference.length <= IHDR_DATA_LENGTH) DWT.error(DWT.ERROR_INVALID_IMAGE);
+    width = getInt32(WIDTH_DATA_OFFSET);
+    height = getInt32(HEIGHT_DATA_OFFSET);
+    bitDepth = reference[BIT_DEPTH_OFFSET];
+    colorType = reference[COLOR_TYPE_OFFSET];
+    compressionMethod = reference[COMPRESSION_METHOD_OFFSET];
+    filterMethod = reference[FILTER_METHOD_OFFSET];
+    interlaceMethod = reference[INTERLACE_METHOD_OFFSET];
+}
+
+int getChunkType() {
+    return CHUNK_IHDR;
+}
+
+/**
+ * Get the image's width in pixels.
+ */
+int getWidth() {
+    return width;
+}
+
+/**
+ * Set the image's width in pixels.
+ */
+void setWidth(int value) {
+    setInt32(WIDTH_DATA_OFFSET, value);
+    width = value;
+}
+
+/**
+ * Get the image's height in pixels.
+ */
+int getHeight() {
+    return height;
+}
+
+/**
+ * Set the image's height in pixels.
+ */
+void setHeight(int value) {
+    setInt32(HEIGHT_DATA_OFFSET, value);
+    height = value;
+}
+
+/**
+ * Get the image's bit depth.
+ * This is limited to the values 1, 2, 4, 8, or 16.
+ */
+byte getBitDepth() {
+    return bitDepth;
+}
+
+/**
+ * Set the image's bit depth.
+ * This is limited to the values 1, 2, 4, 8, or 16.
+ */
+void setBitDepth(byte value) {
+    reference[BIT_DEPTH_OFFSET] = value;
+    bitDepth = value;
+}
+
+/**
+ * Get the image's color type.
+ * This is limited to the values:
+ * 0 - Grayscale image.
+ * 2 - RGB triple.
+ * 3 - Palette.
+ * 4 - Grayscale with Alpha channel.
+ * 6 - RGB with Alpha channel.
+ */
+byte getColorType() {
+    return colorType;
+}
+
+/**
+ * Set the image's color type.
+ * This is limited to the values:
+ * 0 - Grayscale image.
+ * 2 - RGB triple.
+ * 3 - Palette.
+ * 4 - Grayscale with Alpha channel.
+ * 6 - RGB with Alpha channel.
+ */
+void setColorType(byte value) {
+    reference[COLOR_TYPE_OFFSET] = value;
+    colorType = value;
+}
+
+/**
+ * Get the image's compression method.
+ * This value must be 0.
+ */
+byte getCompressionMethod() {
+    return compressionMethod;
+}
+
+/**
+ * Set the image's compression method.
+ * This value must be 0.
+ */
+void setCompressionMethod(byte value) {
+    reference[COMPRESSION_METHOD_OFFSET] = value;
+    compressionMethod = value;
+}
+
+/**
+ * Get the image's filter method.
+ * This value must be 0.
+ */
+byte getFilterMethod() {
+    return filterMethod;
+}
+
+/**
+ * Set the image's filter method.
+ * This value must be 0.
+ */
+void setFilterMethod(byte value) {
+    reference[FILTER_METHOD_OFFSET] = value;
+    filterMethod = value;
+}
+
+/**
+ * Get the image's interlace method.
+ * This value is limited to:
+ * 0 - No interlacing used.
+ * 1 - Adam7 interlacing used.
+ */
+byte getInterlaceMethod() {
+    return interlaceMethod;
+}
+
+/**
+ * Set the image's interlace method.
+ * This value is limited to:
+ * 0 - No interlacing used.
+ * 1 - Adam7 interlacing used.
+ */
+void setInterlaceMethod(byte value) {
+    reference[INTERLACE_METHOD_OFFSET] = value;
+    interlaceMethod = value;
+}
+
+/**
+ * Answer whether the chunk is a valid IHDR chunk.
+ */
+void validate(PngFileReadState readState, PngIhdrChunk headerChunk) {
+    // An IHDR chunk is invalid if any other chunk has
+    // been read.
+    if (readState.readIHDR
+        || readState.readPLTE
+        || readState.readIDAT
+        || readState.readIEND)
+    {
+        DWT.error(DWT.ERROR_INVALID_IMAGE);
+    } else {
+        readState.readIHDR = true;
+    }
+
+    super.validate(readState, headerChunk);
+
+    if (length !is IHDR_DATA_LENGTH) DWT.error(DWT.ERROR_INVALID_IMAGE);
+    if (compressionMethod !is 0) DWT.error(DWT.ERROR_INVALID_IMAGE);
+    if (interlaceMethod !is INTERLACE_METHOD_NONE &&
+        interlaceMethod !is INTERLACE_METHOD_ADAM7) {
+            DWT.error(DWT.ERROR_INVALID_IMAGE);
+    }
+
+    bool colorTypeIsValid = false;
+    for (int i = 0; i < ValidColorTypes.length; i++) {
+        if (ValidColorTypes[i] is colorType) {
+            colorTypeIsValid = true;
+            break;
+        }
+    }
+    if (!colorTypeIsValid) DWT.error(DWT.ERROR_INVALID_IMAGE);
+
+    bool bitDepthIsValid = false;
+    for (int i = 0; i < ValidBitDepths.length; i++) {
+        if (ValidBitDepths[i] is bitDepth) {
+            bitDepthIsValid = true;
+            break;
+        }
+    }
+    if (!bitDepthIsValid) DWT.error(DWT.ERROR_INVALID_IMAGE);
+
+    if ((colorType is COLOR_TYPE_RGB
+        || colorType is COLOR_TYPE_RGB_WITH_ALPHA
+        || colorType is COLOR_TYPE_GRAYSCALE_WITH_ALPHA)
+        && bitDepth < 8)
+    {
+            DWT.error(DWT.ERROR_INVALID_IMAGE);
+    }
+
+    if (colorType is COLOR_TYPE_PALETTE && bitDepth > 8) {
+        DWT.error(DWT.ERROR_INVALID_IMAGE);
+    }
+}
+
+char[] getColorTypeString() {
+    switch (colorType) {
+        case COLOR_TYPE_GRAYSCALE:              return "Grayscale";
+        case COLOR_TYPE_RGB:                    return "RGB";
+        case COLOR_TYPE_PALETTE:                return "Palette";
+        case COLOR_TYPE_GRAYSCALE_WITH_ALPHA:   return "Grayscale with Alpha";
+        case COLOR_TYPE_RGB_WITH_ALPHA:         return "RGB with Alpha";
+        default:                                return "Unknown - " ~ cast(char)colorType;
+    }
+}
+
+char[] getFilterMethodString() {
+    switch (filterMethod) {
+        case FILTER_NONE:       return "None";
+        case FILTER_SUB:        return "Sub";
+        case FILTER_UP:         return "Up";
+        case FILTER_AVERAGE:    return "Average";
+        case FILTER_PAETH:      return "Paeth";
+        default:                return "Unknown";
+    }
+}
+
+char[] getInterlaceMethodString() {
+    switch (interlaceMethod) {
+        case INTERLACE_METHOD_NONE:     return "Not Interlaced";
+        case INTERLACE_METHOD_ADAM7:    return "Interlaced - ADAM7";
+        default:                return "Unknown";
+    }
+}
+
+override char[] contributeToString() {
+    return Format( "\n\tWidth: {}\n\tHeight: {}\n\tBit Depth: {}\n\tColor Type: {}\n\tCompression Method: {}\n\tFilter Method: {}\n\tInterlace Method: {}",
+        width, height, bitDepth, getColorTypeString(), compressionMethod, getFilterMethodString(), getInterlaceMethodString() );
+}
+
+bool getMustHavePalette() {
+    return colorType is COLOR_TYPE_PALETTE;
+}
+
+bool getCanHavePalette() {
+    return colorType !is COLOR_TYPE_GRAYSCALE &&
+        colorType !is COLOR_TYPE_GRAYSCALE_WITH_ALPHA;
+}
+
+/**
+ * Answer the pixel size in bits based on the color type
+ * and bit depth.
+ */
+int getBitsPerPixel() {
+    switch (colorType) {
+        case COLOR_TYPE_RGB_WITH_ALPHA:
+            return 4 * bitDepth;
+        case COLOR_TYPE_RGB:
+            return 3 * bitDepth;
+        case COLOR_TYPE_GRAYSCALE_WITH_ALPHA:
+            return 2 * bitDepth;
+        case COLOR_TYPE_GRAYSCALE:
+        case COLOR_TYPE_PALETTE:
+            return bitDepth;
+        default:
+            DWT.error(DWT.ERROR_INVALID_IMAGE);
+            return 0;
+    }
+}
+
+/**
+ * Answer the pixel size in bits based on the color type
+ * and bit depth.
+ */
+int getSwtBitsPerPixel() {
+    switch (colorType) {
+        case COLOR_TYPE_RGB_WITH_ALPHA:
+        case COLOR_TYPE_RGB:
+        case COLOR_TYPE_GRAYSCALE_WITH_ALPHA:
+            return 24;
+        case COLOR_TYPE_GRAYSCALE:
+        case COLOR_TYPE_PALETTE:
+            return Math.min(bitDepth, 8);
+        default:
+            DWT.error(DWT.ERROR_INVALID_IMAGE);
+            return 0;
+    }
+}
+
+int getFilterByteOffset() {
+    if (bitDepth < 8) return 1;
+    return getBitsPerPixel() / 8;
+}
+
+bool usesDirectColor() {
+    switch (colorType) {
+        case COLOR_TYPE_GRAYSCALE:
+        case COLOR_TYPE_GRAYSCALE_WITH_ALPHA:
+        case COLOR_TYPE_RGB:
+        case COLOR_TYPE_RGB_WITH_ALPHA:
+            return true;
+        default:
+            return false;
+    }
+}
+
+PaletteData createGrayscalePalette() {
+    int depth = Math.min(bitDepth, 8);
+    int max = (1 << depth) - 1;
+    int delta = 255 / max;
+    int gray = 0;
+    RGB[] rgbs = new RGB[max + 1];
+    for (int i = 0; i <= max; i++) {
+        rgbs[i] = new RGB(gray, gray, gray);
+        gray += delta;
+    }
+    return new PaletteData(rgbs);
+}
+
+PaletteData getPaletteData() {
+    switch (colorType) {
+        case COLOR_TYPE_GRAYSCALE:
+            return createGrayscalePalette();
+        case COLOR_TYPE_GRAYSCALE_WITH_ALPHA:
+        case COLOR_TYPE_RGB:
+        case COLOR_TYPE_RGB_WITH_ALPHA:
+            return new PaletteData(0xFF0000, 0xFF00, 0xFF);
+        default:
+            return null;
+    }
+}
+
+
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dwt/internal/image/PngInputStream.d	Wed Jan 23 12:01:46 2008 +0100
@@ -0,0 +1,72 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2006 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *     IBM Corporation - initial API and implementation
+ * Port to the D programming language:
+ *     Frank Benoit <benoit@tionex.de>
+ *******************************************************************************/
+module dwt.internal.image.PngInputStream;
+
+import dwt.dwthelper.InputStream;
+import dwt.dwthelper.System;
+import dwt.internal.image.PngIdatChunk;
+import dwt.internal.image.PngChunkReader;
+import dwt.internal.image.PngChunk;
+
+import tango.core.Exception;
+import Math = tango.math.Math;
+
+public class PngInputStream : InputStream {
+    alias InputStream.read read;
+
+    PngChunkReader reader;
+    PngChunk chunk;
+    int offset, length;
+
+    final static int DATA_OFFSET = 8;
+
+public this(PngIdatChunk chunk, PngChunkReader reader) {
+    this.chunk = chunk;
+    this.reader = reader;
+    length = chunk.getLength();
+    offset = 0;
+}
+
+private bool checkChunk()  {
+    while (offset is length) {
+        chunk = reader.readNextChunk();
+        if (chunk is null) throw new IOException("no data");
+        if (chunk.getChunkType() is PngChunk.CHUNK_IEND) return false;
+        if (chunk.getChunkType() !is PngChunk.CHUNK_IDAT) throw new IOException("");
+        length = chunk.getLength();
+        offset = 0;
+    }
+    return true;
+}
+
+public override void close()  {
+    chunk = null;
+}
+
+public override int read()  {
+    if (chunk is null) throw new IOException("");
+    if (offset is length && !checkChunk()) return -1;
+    int b = chunk.reference[DATA_OFFSET + offset] & 0xFF;
+    offset++;
+    return b;
+}
+
+public override int read(byte[] b, int off, int len)  {
+    if (chunk is null) throw new IOException("");
+    if (offset is length && !checkChunk()) return -1;
+    len = Math.min(len, length - offset);
+    System.arraycopy(chunk.reference, DATA_OFFSET + offset, b, off, len);
+    offset += len;
+    return len;
+}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dwt/internal/image/PngLzBlockReader.d	Wed Jan 23 12:01:46 2008 +0100
@@ -0,0 +1,175 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2006 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *     IBM Corporation - initial API and implementation
+ * Port to the D programming language:
+ *     Frank Benoit <benoit@tionex.de>
+ *******************************************************************************/
+module dwt.internal.image.PngLzBlockReader;
+
+import dwt.internal.image.PngDecodingDataStream;
+import dwt.internal.image.PngHuffmanTables;
+
+public class PngLzBlockReader {
+    bool isLastBlock;
+    byte compressionType;
+    int uncompressedBytesRemaining;
+    PngDecodingDataStream stream;
+    PngHuffmanTables huffmanTables;
+
+    byte[] window;
+    int windowIndex;
+    int copyIndex;
+    int copyBytesRemaining;
+
+    static const int UNCOMPRESSED = 0;
+    static const int COMPRESSED_FIXED = 1;
+    static const int COMPRESSED_DYNAMIC = 2;
+
+    static const int END_OF_COMPRESSED_BLOCK = 256;
+    static const int FIRST_LENGTH_CODE = 257;
+    static const int LAST_LENGTH_CODE = 285;
+    static const int FIRST_DISTANCE_CODE = 1;
+    static const int LAST_DISTANCE_CODE = 29;
+    static const int FIRST_CODE_LENGTH_CODE = 4;
+    static const int LAST_CODE_LENGTH_CODE = 19;
+
+    static const int[] lengthBases = [
+        3, 4, 5, 6, 7, 8, 9, 10, 11, 13, 15, 17, 19, 23, 27,
+        31, 35, 43, 51, 59, 67, 83, 99, 115, 131, 163, 195, 227, 258
+    ];
+    static const int[] extraLengthBits = [
+        0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2,
+        3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 0,
+    ];
+    static const int[] distanceBases = [
+        1, 2, 3, 4, 5, 7, 9, 13, 17, 25, 33, 49, 65, 97, 129,
+        193, 257, 385, 513, 769, 1025, 1537, 2049, 3073, 4097,
+        6145, 8193, 12289, 16385, 24577,
+    ];
+    static const int[] extraDistanceBits = [
+        0, 0, 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7,  7,
+        8,  8, 9, 9, 10, 10, 11, 11, 12, 12, 13, 13,
+    ];
+
+
+this(PngDecodingDataStream stream) {
+    this.stream = stream;
+    isLastBlock = false;
+}
+
+void setWindowSize(int windowSize) {
+    window = new byte[windowSize];
+}
+
+void readNextBlockHeader()  {
+    isLastBlock = stream.getNextIdatBit() !is 0;
+    compressionType = cast(byte)(stream.getNextIdatBits(2) & 0xFF);
+    if (compressionType > 2) stream.error();
+
+    if (compressionType is UNCOMPRESSED) {
+        byte b1 = stream.getNextIdatByte();
+        byte b2 = stream.getNextIdatByte();
+        byte b3 = stream.getNextIdatByte();
+        byte b4 = stream.getNextIdatByte();
+        if (b1 !is ~b3 || b2 !is ~b4) stream.error();
+        uncompressedBytesRemaining = (b1 & 0xFF) | ((b2 & 0xFF) << 8);
+    } else if (compressionType is COMPRESSED_DYNAMIC) {
+        huffmanTables = PngHuffmanTables.getDynamicTables(stream);
+    } else {
+        huffmanTables = PngHuffmanTables.getFixedTables();
+    }
+}
+
+byte getNextByte()  {
+    if (compressionType is UNCOMPRESSED) {
+        if (uncompressedBytesRemaining is 0) {
+            readNextBlockHeader();
+            return getNextByte();
+        }
+        uncompressedBytesRemaining--;
+        return stream.getNextIdatByte();
+    } else {
+        byte value = getNextCompressedByte();
+        if (value is END_OF_COMPRESSED_BLOCK) {
+            if (isLastBlock) stream.error();
+            readNextBlockHeader();
+            return getNextByte();
+        } else {
+            return value;
+        }
+    }
+}
+
+private void assertBlockAtEnd()  {
+    if (compressionType is UNCOMPRESSED) {
+        if (uncompressedBytesRemaining > 0) stream.error();
+    } else if (copyBytesRemaining > 0 ||
+        (huffmanTables.getNextLiteralValue(stream) !is END_OF_COMPRESSED_BLOCK))
+    {
+        stream.error();
+    }
+}
+void assertCompressedDataAtEnd()  {
+    assertBlockAtEnd();
+    while (!isLastBlock) {
+        readNextBlockHeader();
+        assertBlockAtEnd();
+    }
+}
+
+private byte getNextCompressedByte()  {
+    if (copyBytesRemaining > 0) {
+        byte value = window[copyIndex];
+        window[windowIndex] = value;
+        copyBytesRemaining--;
+
+        copyIndex++;
+        windowIndex++;
+        if (copyIndex is window.length) copyIndex = 0;
+        if (windowIndex is window.length) windowIndex = 0;
+
+        return value;
+    }
+
+    int value = huffmanTables.getNextLiteralValue(stream);
+    if (value < END_OF_COMPRESSED_BLOCK) {
+        window[windowIndex] = cast(byte) (value & 0xFF);
+        windowIndex++;
+        if (windowIndex >= window.length) windowIndex = 0;
+        return cast(byte) (value & 0xFF);
+    } else if (value is END_OF_COMPRESSED_BLOCK) {
+        readNextBlockHeader();
+        return getNextByte();
+    } else if (value <= LAST_LENGTH_CODE) {
+        int extraBits = extraLengthBits[value - FIRST_LENGTH_CODE];
+        int length = lengthBases[value - FIRST_LENGTH_CODE];
+        if (extraBits > 0) {
+            length += stream.getNextIdatBits(extraBits);
+        }
+
+        value = huffmanTables.getNextDistanceValue(stream);
+        if (value > LAST_DISTANCE_CODE) stream.error();
+        extraBits = extraDistanceBits[value];
+        int distance = distanceBases[value];
+        if (extraBits > 0) {
+            distance += stream.getNextIdatBits(extraBits);
+        }
+
+        copyIndex = windowIndex - distance;
+        if (copyIndex < 0) copyIndex += window.length;
+
+        copyBytesRemaining = length;
+        return getNextCompressedByte();
+    } else {
+        stream.error();
+        return 0;
+    }
+}
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dwt/internal/image/PngPlteChunk.d	Wed Jan 23 12:01:46 2008 +0100
@@ -0,0 +1,130 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2006 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *     IBM Corporation - initial API and implementation
+ * Port to the D programming language:
+ *     Frank Benoit <benoit@tionex.de>
+ *******************************************************************************/
+module dwt.internal.image.PngPlteChunk;
+
+
+import dwt.DWT;
+import dwt.graphics.PaletteData;
+import dwt.graphics.RGB;
+import dwt.internal.image.PngChunk;
+import dwt.internal.image.PngFileReadState;
+import dwt.internal.image.PngIhdrChunk;
+
+import tango.text.convert.Format;
+
+class PngPlteChunk : PngChunk {
+
+    int paletteSize;
+
+this(PaletteData palette) {
+    super(palette.getRGBs().length * 3);
+    paletteSize = length / 3;
+    setType(TYPE_PLTE);
+    setPaletteData(palette);
+    setCRC(computeCRC());
+}
+
+this(byte[] reference){
+    super(reference);
+    paletteSize = length / 3;
+}
+
+int getChunkType() {
+    return CHUNK_PLTE;
+}
+
+/**
+ * Get the number of colors in this palette.
+ */
+int getPaletteSize() {
+    return paletteSize;
+}
+
+/**
+ * Get a PaletteData object representing the colors
+ * stored in this PLTE chunk.
+ * The result should be cached as the PLTE chunk
+ * does not store the palette data created.
+ */
+PaletteData getPaletteData() {
+    RGB[] rgbs = new RGB[paletteSize];
+//  int start = DATA_OFFSET;
+//  int end = DATA_OFFSET + length;
+    for (int i = 0; i < rgbs.length; i++) {
+        int offset = DATA_OFFSET + (i * 3);
+        int red = reference[offset] & 0xFF;
+        int green = reference[offset + 1] & 0xFF;
+        int blue = reference[offset + 2] & 0xFF;
+        rgbs[i] = new RGB(red, green, blue);
+    }
+    return new PaletteData(rgbs);
+}
+
+/**
+ * Set the data of a PLTE chunk to the colors
+ * stored in the specified PaletteData object.
+ */
+void setPaletteData(PaletteData palette) {
+    RGB[] rgbs = palette.getRGBs();
+    for (int i = 0; i < rgbs.length; i++) {
+        int offset = DATA_OFFSET + (i * 3);
+        reference[offset] = cast(byte) rgbs[i].red;
+        reference[offset + 1] = cast(byte) rgbs[i].green;
+        reference[offset + 2] = cast(byte) rgbs[i].blue;
+    }
+}
+
+/**
+ * Answer whether the chunk is a valid PLTE chunk.
+ */
+void validate(PngFileReadState readState, PngIhdrChunk headerChunk) {
+    // A PLTE chunk is invalid if no IHDR has been read or if any PLTE,
+    // IDAT, or IEND chunk has been read.
+    if (!readState.readIHDR
+        || readState.readPLTE
+        || readState.readTRNS
+        || readState.readIDAT
+        || readState.readIEND)
+    {
+        DWT.error(DWT.ERROR_INVALID_IMAGE);
+    } else {
+        readState.readPLTE = true;
+    }
+
+    super.validate(readState, headerChunk);
+
+    // Palettes cannot be included in grayscale images.
+    //
+    // Note: just ignore the palette.
+//  if (!headerChunk.getCanHavePalette()) DWT.error(DWT.ERROR_INVALID_IMAGE);
+
+    // Palette chunks' data fields must be event multiples
+    // of 3. Each 3-byte group represents an RGB value.
+    if (getLength() % 3 !is 0) DWT.error(DWT.ERROR_INVALID_IMAGE);
+
+    // Palettes cannot have more entries than 2^bitDepth
+    // where bitDepth is the bit depth of the image given
+    // in the IHDR chunk.
+    if (1 << headerChunk.getBitDepth() < paletteSize) {
+        DWT.error(DWT.ERROR_INVALID_IMAGE);
+    }
+
+    // Palettes cannot have more than 256 entries.
+    if (256 < paletteSize) DWT.error(DWT.ERROR_INVALID_IMAGE);
+}
+
+override char[] contributeToString() {
+    return Format("\n\tPalette size:{}", paletteSize );
+}
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dwt/internal/image/PngTrnsChunk.d	Wed Jan 23 12:01:46 2008 +0100
@@ -0,0 +1,161 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2006 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *     IBM Corporation - initial API and implementation
+ * Port to the D programming language:
+ *     Frank Benoit <benoit@tionex.de>
+ *******************************************************************************/
+module dwt.internal.image.PngTrnsChunk;
+
+import dwt.DWT;
+import dwt.graphics.PaletteData;
+import dwt.graphics.RGB;
+import dwt.internal.image.PngChunk;
+import dwt.internal.image.PNGFileFormat;
+import dwt.internal.image.PngFileReadState;
+import dwt.internal.image.PngIhdrChunk;
+import dwt.internal.image.PngPlteChunk;
+
+public class PngTrnsChunk : PngChunk {
+    static const int TRANSPARENCY_TYPE_PIXEL = 0;
+    static const int TRANSPARENCY_TYPE_ALPHAS = 1;
+    static const int RGB_DATA_LENGTH = 6;
+
+this(RGB rgb) {
+    super(RGB_DATA_LENGTH);
+    setType(TYPE_tRNS);
+    setInt16(DATA_OFFSET, rgb.red);
+    setInt16(DATA_OFFSET + 2, rgb.green);
+    setInt16(DATA_OFFSET + 4, rgb.blue);
+    setCRC(computeCRC());
+}
+
+this(byte[] reference){
+    super(reference);
+}
+
+int getChunkType() {
+    return CHUNK_tRNS;
+}
+
+void validateLength(PngIhdrChunk header, PngPlteChunk paletteChunk) {
+    bool valid;
+    switch (header.getColorType()) {
+        case PngIhdrChunk.COLOR_TYPE_RGB:
+            // Three 2-byte values (RGB)
+            valid = getLength() is 6;
+            break;
+        case PngIhdrChunk.COLOR_TYPE_PALETTE:
+            // Three 2-byte values (RGB)
+            valid = getLength() <= paletteChunk.getLength();
+            break;
+        case PngIhdrChunk.COLOR_TYPE_GRAYSCALE:
+            // One 2-byte value
+            valid = getLength() is 2;
+            break;
+        // Cannot use both Alpha and tRNS
+        case PngIhdrChunk.COLOR_TYPE_RGB_WITH_ALPHA:
+        case PngIhdrChunk.COLOR_TYPE_GRAYSCALE_WITH_ALPHA:
+        default:
+            valid = false;
+    }
+    if (!valid) {
+        DWT.error(DWT.ERROR_INVALID_IMAGE);
+    }
+}
+
+/**
+ * Answer whether the chunk is a valid tRNS chunk.
+ */
+void validate(PngFileReadState readState, PngIhdrChunk headerChunk, PngPlteChunk paletteChunk) {
+    if (!readState.readIHDR
+        || (headerChunk.getMustHavePalette() && !readState.readPLTE)
+        || readState.readIDAT
+        || readState.readIEND)
+    {
+        DWT.error(DWT.ERROR_INVALID_IMAGE);
+    } else {
+        readState.readTRNS = true;
+    }
+
+    validateLength(headerChunk, paletteChunk);
+
+    super.validate(readState, headerChunk);
+}
+
+
+int getTransparencyType(PngIhdrChunk header) {
+    if (header.getColorType() is PngIhdrChunk.COLOR_TYPE_PALETTE) {
+        return TRANSPARENCY_TYPE_ALPHAS;
+    }
+    return TRANSPARENCY_TYPE_PIXEL;
+}
+
+/**
+ * Answer the transparent pixel RGB value.
+ * This is not valid for palette color types.
+ * This is not valid for alpha color types.
+ * This will convert a grayscale value into
+ * a palette index.
+ * It will compress a 6 byte RGB into a 3 byte
+ * RGB.
+ */
+int getSwtTransparentPixel(PngIhdrChunk header) {
+    switch (header.getColorType()) {
+        case PngIhdrChunk.COLOR_TYPE_GRAYSCALE:
+            int gray = ((reference[DATA_OFFSET] & 0xFF) << 8)
+                + (reference[DATA_OFFSET + 1] & 0xFF);
+            if (header.getBitDepth() > 8) {
+                return PNGFileFormat.compress16BitDepthTo8BitDepth(gray);
+            }
+            return gray & 0xFF;
+        case PngIhdrChunk.COLOR_TYPE_RGB:
+            int red = ((reference[DATA_OFFSET] & 0xFF) << 8)
+                | (reference[DATA_OFFSET + 1] & 0xFF);
+            int green = ((reference[DATA_OFFSET + 2] & 0xFF) << 8)
+                | (reference[DATA_OFFSET + 3] & 0xFF);
+            int blue = ((reference[DATA_OFFSET + 4] & 0xFF) << 8)
+                | (reference[DATA_OFFSET + 5] & 0xFF);
+            if (header.getBitDepth() > 8) {
+                red = PNGFileFormat.compress16BitDepthTo8BitDepth(red);
+                green = PNGFileFormat.compress16BitDepthTo8BitDepth(green);
+                blue = PNGFileFormat.compress16BitDepthTo8BitDepth(blue);
+            }
+            return (red << 16) | (green << 8) | blue;
+        default:
+            DWT.error(DWT.ERROR_INVALID_IMAGE);
+            return -1;
+    }
+}
+
+/**
+ * Answer an array of Alpha values that correspond to the
+ * colors in the palette.
+ * This is only valid for the COLOR_TYPE_PALETTE color type.
+ */
+byte[] getAlphaValues(PngIhdrChunk header, PngPlteChunk paletteChunk) {
+    if (header.getColorType() !is PngIhdrChunk.COLOR_TYPE_PALETTE) {
+        DWT.error(DWT.ERROR_INVALID_IMAGE);
+    }
+    byte[] alphas = new byte[paletteChunk.getPaletteSize()];
+    int dataLength = getLength();
+    int i = 0;
+    for (i = 0; i < dataLength; i++) {
+        alphas[i] = reference[DATA_OFFSET + i];
+    }
+    /**
+     * Any palette entries which do not have a corresponding
+     * alpha value in the tRNS chunk are spec'd to have an
+     * alpha of 255.
+     */
+    for (int j = i; j < alphas.length; j++) {
+        alphas[j] = cast(byte) 255;
+    }
+    return alphas;
+}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dwt/internal/image/TIFFDirectory.d	Wed Jan 23 12:01:46 2008 +0100
@@ -0,0 +1,637 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2005 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *     IBM Corporation - initial API and implementation
+ * Port to the D programming language:
+ *     Frank Benoit <benoit@tionex.de>
+ *******************************************************************************/
+module dwt.internal.image.TIFFDirectory;
+
+import dwt.internal.image.TIFFRandomFileAccess;
+import dwt.internal.image.TIFFModifiedHuffmanCodec;
+import dwt.internal.image.LEDataOutputStream;
+import dwt.graphics.ImageData;
+import dwt.graphics.ImageLoaderEvent;
+import dwt.graphics.ImageLoader;
+import dwt.graphics.PaletteData;
+import dwt.graphics.RGB;
+import dwt.DWT;
+import dwt.dwthelper.Integer;
+
+//PORTING_TYPE
+class Image{}
+
+final class TIFFDirectory {
+
+    TIFFRandomFileAccess file;
+    bool isLittleEndian;
+    ImageLoader loader;
+    int depth;
+
+    /* Directory fields */
+    int imageWidth;
+    int imageLength;
+    int[] bitsPerSample;
+    int compression;
+    int photometricInterpretation;
+    int[] stripOffsets;
+    int samplesPerPixel;
+    int rowsPerStrip;
+    int[] stripByteCounts;
+    int t4Options;
+    int colorMapOffset;
+
+    /* Encoder fields */
+    ImageData image;
+    LEDataOutputStream ostr;
+
+    static const int NO_VALUE = -1;
+
+    static const short TAG_ImageWidth = 256;
+    static const short TAG_ImageLength = 257;
+    static const short TAG_BitsPerSample = 258;
+    static const short TAG_Compression = 259;
+    static const short TAG_PhotometricInterpretation = 262;
+    static const short TAG_StripOffsets = 273;
+    static const short TAG_SamplesPerPixel = 277;
+    static const short TAG_RowsPerStrip = 278;
+    static const short TAG_StripByteCounts = 279;
+    static const short TAG_XResolution = 282;
+    static const short TAG_YResolution = 283;
+    static const short TAG_T4Options = 292;
+    static const short TAG_ResolutionUnit = 296;
+    static const short TAG_ColorMap = 320;
+
+    static const int TYPE_BYTE = 1;
+    static const int TYPE_ASCII = 2;
+    static const int TYPE_SHORT = 3;
+    static const int TYPE_LONG = 4;
+    static const int TYPE_RATIONAL = 5;
+
+    /* Different compression schemes */
+    static const int COMPRESSION_NONE = 1;
+    static const int COMPRESSION_CCITT_3_1 = 2;
+    static const int COMPRESSION_PACKBITS = 32773;
+
+    static const int IFD_ENTRY_SIZE = 12;
+
+public this(TIFFRandomFileAccess file, bool isLittleEndian, ImageLoader loader) {
+    this.file = file;
+    this.isLittleEndian = isLittleEndian;
+    this.loader = loader;
+}
+
+public this(ImageData image) {
+    this.image = image;
+}
+
+/* PackBits decoder */
+int decodePackBits(byte[] src, byte[] dest, int offsetDest) {
+    int destIndex = offsetDest;
+    int srcIndex = 0;
+    while (srcIndex < src.length) {
+        byte n = src[srcIndex];
+        if (0 <= n && n <= 127) {
+            /* Copy next n+1 bytes literally */
+            System.arraycopy(src, ++srcIndex, dest, destIndex, n + 1);
+            srcIndex += n + 1;
+            destIndex += n + 1;
+        } else if (-127 <= n && n <= -1) {
+            /* Copy next byte -n+1 times */
+            byte value = src[++srcIndex];
+            for (int j = 0; j < -n + 1; j++) {
+                dest[destIndex++] = value;
+            }
+            srcIndex++;
+        } else {
+            /* Noop when n is -128 */
+            srcIndex++;
+        }
+    }
+    /* Number of bytes copied */
+    return destIndex - offsetDest;
+}
+
+int getEntryValue(int type, byte[] buffer, int index) {
+    return toInt(buffer, index + 8, type);
+}
+
+void getEntryValue(int type, byte[] buffer, int index, int[] values)  {
+    int start = index + 8;
+    int size;
+    int offset = toInt(buffer, start, TYPE_LONG);
+    switch (type) {
+        case TYPE_SHORT: size = 2; break;
+        case TYPE_LONG: size = 4; break;
+        case TYPE_RATIONAL: size = 8; break;
+        case TYPE_ASCII:
+        case TYPE_BYTE: size = 1; break;
+        default: DWT.error(DWT.ERROR_UNSUPPORTED_FORMAT); return;
+    }
+    if (values.length * size > 4) {
+        buffer = new byte[values.length * size];
+        file.seek(offset);
+        file.read(buffer);
+        start = 0;
+    }
+    for (int i = 0; i < values.length; i++) {
+        values[i] = toInt(buffer, start + i * size, type);
+    }
+}
+
+void decodePixels(ImageData image)  {
+    /* Each row is byte aligned */
+    byte[] imageData = new byte[(imageWidth * depth + 7) / 8 * imageLength];
+    image.data = imageData;
+    int destIndex = 0;
+    int length = stripOffsets.length;
+    for (int i = 0; i < length; i++) {
+        /* Read a strip */
+        byte[] data = new byte[](stripByteCounts[i]);
+        file.seek(stripOffsets[i]);
+        file.read(data);
+        if (compression is COMPRESSION_NONE) {
+            System.arraycopy(data, 0, imageData, destIndex, data.length);
+            destIndex += data.length;
+        } else if (compression is COMPRESSION_PACKBITS) {
+            destIndex += decodePackBits(data, imageData, destIndex);
+        } else if (compression is COMPRESSION_CCITT_3_1 || compression is 3) {
+            TIFFModifiedHuffmanCodec codec = new TIFFModifiedHuffmanCodec();
+            int nRows = rowsPerStrip;
+            if (i is length -1) {
+                int n = imageLength % rowsPerStrip;
+                if (n !is 0) nRows = n;
+            }
+            destIndex += codec.decode(data, imageData, destIndex, imageWidth, nRows);
+        }
+        if (loader.hasListeners()) {
+            loader.notifyListeners(new ImageLoaderEvent(loader, image, i, i is length - 1));
+        }
+    }
+}
+
+PaletteData getColorMap()  {
+    int numColors = 1 << bitsPerSample[0];
+    /* R, G, B entries are 16 bit wide (2 bytes) */
+    int numBytes = 3 * 2 * numColors;
+    byte[] buffer = new byte[numBytes];
+    file.seek(colorMapOffset);
+    file.read(buffer);
+    RGB[] colors = new RGB[numColors];
+    /**
+     * DWT does not support 16-bit depth color formats.
+     * Convert the 16-bit data to 8-bit data.
+     * The correct way to do this is to multiply each
+     * 16 bit value by the value:
+     * (2^8 - 1) / (2^16 - 1).
+     * The fast way to do this is just to drop the low
+     * byte of the 16-bit value.
+     */
+    int offset = isLittleEndian ? 1 : 0;
+    int startG = 2 * numColors;
+    int startB = startG + 2 * numColors;
+    for (int i = 0; i < numColors; i++) {
+        int r = buffer[offset] & 0xFF;
+        int g = buffer[startG + offset] & 0xFF;
+        int b = buffer[startB + offset] & 0xFF;
+        colors[i] = new RGB(r, g, b);
+        offset += 2;
+    }
+    return new PaletteData(colors);
+}
+
+PaletteData getGrayPalette() {
+    int numColors = 1 << bitsPerSample[0];
+    RGB[] rgbs = new RGB[numColors];
+    for (int i = 0; i < numColors; i++) {
+        int value = i * 0xFF / (numColors - 1);
+        if (photometricInterpretation is 0) value = 0xFF - value;
+        rgbs[i] = new RGB(value, value, value);
+    }
+    return new PaletteData(rgbs);
+}
+
+PaletteData getRGBPalette(int bitsR, int bitsG, int bitsB) {
+    int blueMask = 0;
+    for (int i = 0; i < bitsB; i++) {
+        blueMask |= 1 << i;
+    }
+    int greenMask = 0;
+    for (int i = bitsB; i < bitsB + bitsG; i++) {
+        greenMask |= 1 << i;
+    }
+    int redMask = 0;
+    for (int i = bitsB + bitsG; i < bitsB + bitsG + bitsR; i++) {
+        redMask |= 1 << i;
+    }
+    return new PaletteData(redMask, greenMask, blueMask);
+}
+
+int formatStrips(int rowByteSize, int nbrRows, byte[] data, int maxStripByteSize, int offsetPostIFD, int extraBytes, int[][] strips) {
+    /*
+    * Calculate the nbr of required strips given the following requirements:
+    * - each strip should, if possible, not be greater than maxStripByteSize
+    * - each strip should contain 1 or more entire rows
+    *
+    * Format the strip fields arrays so that the image data is stored in one
+    * contiguous block. This block is stored after the IFD and after any tag
+    * info described in the IFD.
+    */
+    int n, nbrRowsPerStrip;
+    if (rowByteSize > maxStripByteSize) {
+        /* Each strip contains 1 row */
+        n = data.length / rowByteSize;
+        nbrRowsPerStrip = 1;
+    } else {
+        int nbr = (data.length + maxStripByteSize - 1) / maxStripByteSize;
+        nbrRowsPerStrip = nbrRows / nbr;
+        n = (nbrRows + nbrRowsPerStrip - 1) / nbrRowsPerStrip;
+    }
+    int stripByteSize = rowByteSize * nbrRowsPerStrip;
+
+    int[] offsets = new int[n];
+    int[] counts = new int[n];
+    /*
+    * Nbr of bytes between the end of the IFD directory and the start of
+    * the image data. Keep space for at least the offsets and counts
+    * data, each field being TYPE_LONG (4 bytes). If other tags require
+    * space between the IFD and the image block, use the extraBytes
+    * parameter.
+    * If there is only one strip, the offsets and counts data is stored
+    * directly in the IFD and we need not reserve space for it.
+    */
+    int postIFDData = n is 1 ? 0 : n * 2 * 4;
+    int startOffset = offsetPostIFD + extraBytes + postIFDData; /* offset of image data */
+
+    int offset = startOffset;
+    for (int i = 0; i < n; i++) {
+        /*
+        * Store all strips sequentially to allow us
+        * to copy all pixels in one contiguous area.
+        */
+        offsets[i] = offset;
+        counts[i] = stripByteSize;
+        offset += stripByteSize;
+    }
+    /* The last strip may contain fewer rows */
+    int mod = data.length % stripByteSize;
+    if (mod !is 0) counts[counts.length - 1] = mod;
+
+    strips[0] = offsets;
+    strips[1] = counts;
+    return nbrRowsPerStrip;
+}
+
+int[] formatColorMap(RGB[] rgbs) {
+    /*
+    * In a TIFF ColorMap, all red come first, followed by
+    * green and blue. All values must be converted from
+    * 8 bit to 16 bit.
+    */
+    int[] colorMap = new int[rgbs.length * 3];
+    int offsetGreen = rgbs.length;
+    int offsetBlue = rgbs.length * 2;
+    for (int i = 0; i < rgbs.length; i++) {
+        colorMap[i] = rgbs[i].red << 8 | rgbs[i].red;
+        colorMap[i + offsetGreen] = rgbs[i].green << 8 | rgbs[i].green;
+        colorMap[i + offsetBlue] = rgbs[i].blue << 8 | rgbs[i].blue;
+    }
+    return colorMap;
+}
+
+void parseEntries(byte[] buffer)  {
+    for (int offset = 0; offset < buffer.length; offset += IFD_ENTRY_SIZE) {
+        int tag = toInt(buffer, offset, TYPE_SHORT);
+        int type = toInt(buffer, offset + 2, TYPE_SHORT);
+        int count = toInt(buffer, offset + 4, TYPE_LONG);
+        switch (tag) {
+            case TAG_ImageWidth: {
+                imageWidth = getEntryValue(type, buffer, offset);
+                break;
+            }
+            case TAG_ImageLength: {
+                imageLength = getEntryValue(type, buffer, offset);
+                break;
+            }
+            case TAG_BitsPerSample: {
+                if (type !is TYPE_SHORT) DWT.error(DWT.ERROR_INVALID_IMAGE);
+                bitsPerSample = new int[count];
+                getEntryValue(type, buffer, offset, bitsPerSample);
+                break;
+            }
+            case TAG_Compression: {
+                compression = getEntryValue(type, buffer, offset);
+                break;
+            }
+            case TAG_PhotometricInterpretation: {
+                photometricInterpretation = getEntryValue(type, buffer, offset);
+                break;
+            }
+            case TAG_StripOffsets: {
+                if (type !is TYPE_LONG && type !is TYPE_SHORT) DWT.error(DWT.ERROR_INVALID_IMAGE);
+                stripOffsets = new int[count];
+                getEntryValue(type, buffer, offset, stripOffsets);
+                break;
+            }
+            case TAG_SamplesPerPixel: {
+                if (type !is TYPE_SHORT) DWT.error(DWT.ERROR_INVALID_IMAGE);
+                samplesPerPixel = getEntryValue(type, buffer, offset);
+                /* Only the basic 1 and 3 values are supported */
+                if (samplesPerPixel !is 1 && samplesPerPixel !is 3) DWT.error(DWT.ERROR_UNSUPPORTED_DEPTH);
+                break;
+            }
+            case TAG_RowsPerStrip: {
+                rowsPerStrip = getEntryValue(type, buffer, offset);
+                break;
+            }
+            case TAG_StripByteCounts: {
+                stripByteCounts = new int[count];
+                getEntryValue(type, buffer, offset, stripByteCounts);
+                break;
+            }
+            case TAG_XResolution: {
+                /* Ignored */
+                break;
+            }
+            case TAG_YResolution: {
+                /* Ignored */
+                break;
+            }
+            case TAG_T4Options: {
+                if (type !is TYPE_LONG) DWT.error(DWT.ERROR_INVALID_IMAGE);
+                t4Options = getEntryValue(type, buffer, offset);
+                if ((t4Options & 0x1) is 1) {
+                    /* 2-dimensional coding is not supported */
+                    DWT.error(DWT.ERROR_UNSUPPORTED_FORMAT);
+                }
+                break;
+            }
+            case TAG_ResolutionUnit: {
+                /* Ignored */
+                break;
+            }
+            case TAG_ColorMap: {
+                if (type !is TYPE_SHORT) DWT.error(DWT.ERROR_INVALID_IMAGE);
+                /* Get the offset of the colorMap (use TYPE_LONG) */
+                colorMapOffset = getEntryValue(TYPE_LONG, buffer, offset);
+                break;
+            }
+            default:
+        }
+    }
+}
+
+public ImageData read()  {
+    /* Set TIFF default values */
+    bitsPerSample = [1];
+    colorMapOffset = NO_VALUE;
+    compression = 1;
+    imageLength = NO_VALUE;
+    imageWidth = NO_VALUE;
+    photometricInterpretation = NO_VALUE;
+    rowsPerStrip = Integer.MAX_VALUE;
+    samplesPerPixel = 1;
+    stripByteCounts = null;
+    stripOffsets = null;
+
+    byte[] buffer = new byte[2];
+    file.read(buffer);
+    int numberEntries = toInt(buffer, 0, TYPE_SHORT);
+    buffer = new byte[IFD_ENTRY_SIZE * numberEntries];
+    file.read(buffer);
+    parseEntries(buffer);
+
+    PaletteData palette = null;
+    depth = 0;
+    switch (photometricInterpretation) {
+        case 0:
+        case 1: {
+            /* Bilevel or Grayscale image */
+            palette = getGrayPalette();
+            depth = bitsPerSample[0];
+            break;
+        }
+        case 2: {
+            /* RGB image */
+            if (colorMapOffset !is NO_VALUE) DWT.error(DWT.ERROR_INVALID_IMAGE);
+            /* SamplesPerPixel 3 is the only value supported */
+            palette = getRGBPalette(bitsPerSample[0], bitsPerSample[1], bitsPerSample[2]);
+            depth = bitsPerSample[0] + bitsPerSample[1] + bitsPerSample[2];
+            break;
+        }
+        case 3: {
+            /* Palette Color image */
+            if (colorMapOffset is NO_VALUE) DWT.error(DWT.ERROR_INVALID_IMAGE);
+            palette = getColorMap();
+            depth = bitsPerSample[0];
+            break;
+        }
+        default: {
+            DWT.error(DWT.ERROR_INVALID_IMAGE);
+        }
+    }
+
+    ImageData image = ImageData.internal_new(
+            imageWidth,
+            imageLength,
+            depth,
+            palette,
+            1,
+            null,
+            0,
+            null,
+            null,
+            -1,
+            -1,
+            DWT.IMAGE_TIFF,
+            0,
+            0,
+            0,
+            0);
+    decodePixels(image);
+    return image;
+}
+
+int toInt(byte[] buffer, int i, int type) {
+    if (type is TYPE_LONG) {
+        return isLittleEndian ?
+        (buffer[i] & 0xFF) | ((buffer[i + 1] & 0xFF) << 8) | ((buffer[i + 2] & 0xFF) << 16) | ((buffer[i + 3] & 0xFF) << 24) :
+        (buffer[i + 3] & 0xFF) | ((buffer[i + 2] & 0xFF) << 8) | ((buffer[i + 1] & 0xFF) << 16) | ((buffer[i] & 0xFF) << 24);
+    }
+    if (type is TYPE_SHORT) {
+        return isLittleEndian ?
+        (buffer[i] & 0xFF) | ((buffer[i + 1] & 0xFF) << 8) :
+        (buffer[i + 1] & 0xFF) | ((buffer[i] & 0xFF) << 8);
+    }
+    /* Invalid type */
+    DWT.error(DWT.ERROR_INVALID_IMAGE);
+    return -1;
+}
+
+void write(int photometricInterpretation)  {
+    bool isRGB = photometricInterpretation is 2;
+    bool isColorMap = photometricInterpretation is 3;
+    bool isBiLevel = photometricInterpretation is 0 || photometricInterpretation is 1;
+
+    int imageWidth = image.width;
+    int imageLength = image.height;
+    int rowByteSize = image.bytesPerLine;
+
+    int numberEntries = isBiLevel ? 9 : 11;
+    int lengthDirectory = 2 + 12 * numberEntries + 4;
+    /* Offset following the header and the directory */
+    int nextOffset = 8 + lengthDirectory;
+
+    /* Extra space used by XResolution and YResolution values */
+    int extraBytes = 16;
+
+    int[] colorMap = null;
+    if (isColorMap) {
+        PaletteData palette = image.palette;
+        RGB[] rgbs = palette.getRGBs();
+        colorMap = formatColorMap(rgbs);
+        /* The number of entries of the Color Map must match the bitsPerSample field */
+        if (colorMap.length !is 3 * 1 << image.depth) DWT.error(DWT.ERROR_UNSUPPORTED_FORMAT);
+        /* Extra space used by ColorMap values */
+        extraBytes += colorMap.length * 2;
+    }
+    if (isRGB) {
+        /* Extra space used by BitsPerSample values */
+        extraBytes += 6;
+    }
+    /* TIFF recommends storing the data in strips of no more than 8 Ko */
+    byte[] data = image.data;
+    int[][] strips = new int[][](2);
+    int nbrRowsPerStrip = formatStrips(rowByteSize, imageLength, data, 8192, nextOffset, extraBytes, strips);
+    int[] stripOffsets = strips[0];
+    int[] stripByteCounts = strips[1];
+
+    int bitsPerSampleOffset = NO_VALUE;
+    if (isRGB) {
+        bitsPerSampleOffset = nextOffset;
+        nextOffset += 6;
+    }
+    int stripOffsetsOffset = NO_VALUE, stripByteCountsOffset = NO_VALUE;
+    int xResolutionOffset, yResolutionOffset, colorMapOffset = NO_VALUE;
+    int cnt = stripOffsets.length;
+    if (cnt > 1) {
+        stripOffsetsOffset = nextOffset;
+        nextOffset += 4 * cnt;
+        stripByteCountsOffset = nextOffset;
+        nextOffset += 4 * cnt;
+    }
+    xResolutionOffset = nextOffset;
+    nextOffset += 8;
+    yResolutionOffset = nextOffset;
+    nextOffset += 8;
+    if (isColorMap) {
+        colorMapOffset = nextOffset;
+        nextOffset += colorMap.length * 2;
+    }
+    /* TIFF header */
+    writeHeader();
+
+    /* Image File Directory */
+    ostr.writeShort(numberEntries);
+    writeEntry(TAG_ImageWidth, TYPE_LONG, 1, imageWidth);
+    writeEntry(TAG_ImageLength, TYPE_LONG, 1, imageLength);
+    if (isColorMap) writeEntry(TAG_BitsPerSample, TYPE_SHORT, 1, image.depth);
+    if (isRGB) writeEntry(TAG_BitsPerSample, TYPE_SHORT, 3, bitsPerSampleOffset);
+    writeEntry(TAG_Compression, TYPE_SHORT, 1, COMPRESSION_NONE);
+    writeEntry(TAG_PhotometricInterpretation, TYPE_SHORT, 1, photometricInterpretation);
+    writeEntry(TAG_StripOffsets, TYPE_LONG, cnt, cnt > 1 ? stripOffsetsOffset : stripOffsets[0]);
+    if (isRGB) writeEntry(TAG_SamplesPerPixel, TYPE_SHORT, 1, 3);
+    writeEntry(TAG_RowsPerStrip, TYPE_LONG, 1, nbrRowsPerStrip);
+    writeEntry(TAG_StripByteCounts, TYPE_LONG, cnt, cnt > 1 ? stripByteCountsOffset : stripByteCounts[0]);
+    writeEntry(TAG_XResolution, TYPE_RATIONAL, 1, xResolutionOffset);
+    writeEntry(TAG_YResolution, TYPE_RATIONAL, 1, yResolutionOffset);
+    if (isColorMap) writeEntry(TAG_ColorMap, TYPE_SHORT, colorMap.length, colorMapOffset);
+    /* Offset of next IFD (0 for last IFD) */
+    ostr.writeInt(0);
+
+    /* Values longer than 4 bytes Section */
+
+    /* BitsPerSample 8,8,8 */
+    if (isRGB) for (int i = 0; i < 3; i++) ostr.writeShort(8);
+    if (cnt > 1) {
+        for (int i = 0; i < cnt; i++) ostr.writeInt(stripOffsets[i]);
+        for (int i = 0; i < cnt; i++) ostr.writeInt(stripByteCounts[i]);
+    }
+    /* XResolution and YResolution set to 300 dpi */
+    for (int i = 0; i < 2; i++) {
+        ostr.writeInt(300);
+        ostr.writeInt(1);
+    }
+    /* ColorMap */
+    if (isColorMap) for (int i = 0; i < colorMap.length; i++) ostr.writeShort(colorMap[i]);
+
+    /* Image Data */
+    ostr.write(data);
+}
+
+void writeEntry(short tag, int type, int count, int value) {
+    ostr.writeShort(tag);
+    ostr.writeShort(type);
+    ostr.writeInt(count);
+    ostr.writeInt(value);
+}
+
+void writeHeader() {
+    /* little endian */
+    ostr.write(0x49);
+    ostr.write(0x49);
+
+    /* TIFF identifier */
+    ostr.writeShort(42);
+    /*
+    * Offset of the first IFD is chosen to be 8.
+    * It is word aligned and immediately after this header.
+    */
+    ostr.writeInt(8);
+}
+
+void writeToStream(LEDataOutputStream byteStream) {
+    ostr = byteStream;
+    int photometricInterpretation = -1;
+
+    /* Scanline pad must be 1 */
+    if (image.scanlinePad !is 1) DWT.error(DWT.ERROR_UNSUPPORTED_FORMAT);
+    switch (image.depth) {
+        case 1: {
+            /* Palette must be black and white or white and black */
+            PaletteData palette = image.palette;
+            RGB[] rgbs = palette.colors;
+            if (palette.isDirect || rgbs is null || rgbs.length !is 2) DWT.error(DWT.ERROR_UNSUPPORTED_FORMAT);
+            RGB rgb0 = rgbs[0];
+            RGB rgb1 = rgbs[1];
+            if (!(rgb0.red is rgb0.green && rgb0.green is rgb0.blue &&
+                rgb1.red is rgb1.green && rgb1.green is rgb1.blue &&
+                ((rgb0.red is 0x0 && rgb1.red is 0xFF) || (rgb0.red is 0xFF && rgb1.red is 0x0)))) {
+                DWT.error(DWT.ERROR_UNSUPPORTED_FORMAT);
+            }
+            /* 0 means a color index of 0 is imaged as white */
+            photometricInterpretation = image.palette.colors[0].red is 0xFF ? 0 : 1;
+            break;
+        }
+        case 4:
+        case 8: {
+            photometricInterpretation = 3;
+            break;
+        }
+        case 24: {
+            photometricInterpretation = 2;
+            break;
+        }
+        default: {
+            DWT.error(DWT.ERROR_UNSUPPORTED_FORMAT);
+        }
+    }
+    write(photometricInterpretation);
+}
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dwt/internal/image/TIFFFileFormat.d	Wed Jan 23 12:01:46 2008 +0100
@@ -0,0 +1,85 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2005 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *     IBM Corporation - initial API and implementation
+ * Port to the D programming language:
+ *     Frank Benoit <benoit@tionex.de>
+ *******************************************************************************/
+module dwt.internal.image.TIFFFileFormat;
+
+import dwt.internal.image.TIFFRandomFileAccess;
+import dwt.internal.image.TIFFDirectory;
+import dwt.DWT;
+import dwt.graphics.ImageData;
+import dwt.graphics.ImageLoader;
+import dwt.internal.image.FileFormat;
+
+import tango.core.Exception;
+
+/**
+ * Baseline TIFF decoder revision 6.0
+ * Extension T4-encoding CCITT T.4 1D
+ */
+final class TIFFFileFormat : FileFormat {
+
+bool isFileFormat(LEDataInputStream stream) {
+    try {
+        byte[] header = new byte[4];
+        stream.read(header);
+        stream.unread(header);
+        if (header[0] !is header[1]) return false;
+        if (!(header[0] is 0x49 && header[2] is 42 && header[3] is 0) &&
+            !(header[0] is 0x4d && header[2] is 0 && header[3] is 42)) {
+            return false;
+        }
+        return true;
+    } catch (TracedException e) {
+        return false;
+    }
+}
+
+ImageData[] loadFromByteStream() {
+    byte[] header = new byte[8];
+    bool isLittleEndian;
+    ImageData[] images = new ImageData[0];
+    TIFFRandomFileAccess file = new TIFFRandomFileAccess(inputStream);
+    try {
+        file.read(header);
+        if (header[0] !is header[1]) DWT.error(DWT.ERROR_INVALID_IMAGE);
+        if (!(header[0] is 0x49 && header[2] is 42 && header[3] is 0) &&
+            !(header[0] is 0x4d && header[2] is 0 && header[3] is 42)) {
+            DWT.error(DWT.ERROR_INVALID_IMAGE);
+        }
+        isLittleEndian = header[0] is 0x49;
+        int offset = isLittleEndian ?
+            (header[4] & 0xFF) | ((header[5] & 0xFF) << 8) | ((header[6] & 0xFF) << 16) | ((header[7] & 0xFF) << 24) :
+            (header[7] & 0xFF) | ((header[6] & 0xFF) << 8) | ((header[5] & 0xFF) << 16) | ((header[4] & 0xFF) << 24);
+        file.seek(offset);
+        TIFFDirectory directory = new TIFFDirectory(file, isLittleEndian, loader);
+        ImageData image = directory.read();
+        /* A baseline reader is only expected to read the first directory */
+        images = [image];
+    } catch (IOException e) {
+        DWT.error(DWT.ERROR_IO, e);
+    }
+    return images;
+}
+
+void unloadIntoByteStream(ImageLoader loader) {
+    /* We do not currently support writing multi-page tiff,
+     * so we use the first image data in the loader's array. */
+    ImageData image = loader.data[0];
+    TIFFDirectory directory = new TIFFDirectory(image);
+    try {
+        directory.writeToStream(outputStream);
+    } catch (IOException e) {
+        DWT.error(DWT.ERROR_IO, e);
+    }
+}
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dwt/internal/image/TIFFModifiedHuffmanCodec.d	Wed Jan 23 12:01:46 2008 +0100
@@ -0,0 +1,219 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2003 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *     IBM Corporation - initial API and implementation
+ * Port to the D programming language:
+ *     Frank Benoit <benoit@tionex.de>
+ *******************************************************************************/
+module dwt.internal.image.TIFFModifiedHuffmanCodec;
+
+import dwt.DWT;
+
+/*
+* Decoder for
+* - CCITT Group 3 1-Dimensional Modified Huffman run length encoding
+*   (TIFF compression type 2)
+* - CCITT T.4 bi-level encoding 1D
+*   (TIFF compression type 3 option 1D)
+*/
+final class TIFFModifiedHuffmanCodec {
+    static final short[][][] BLACK_CODE = [
+        /* 2 bits  */
+        [[ cast(short)2, 3], [ cast(short)3, 2]],
+        /* 3 bits  */
+        [[ cast(short)2, 1], [ cast(short)3, 4]],
+        /* 4 bits  */
+        [[ cast(short)2, 6], [ cast(short)3, 5]],
+        /* 5 bits  */
+        [[ cast(short)3, 7]],
+        /* 6 bits  */
+        [[ cast(short)4, 9], [ cast(short)5, 8]],
+        /* 7 bits  */
+        [[ cast(short)4, 10], [ cast(short)5, 11], [ cast(short)7, 12]],
+        /* 8 bits  */
+        [[ cast(short)4, 13], [ cast(short)7, 14]],
+        /* 9 bits  */
+        [[ cast(short)24, 15]],
+        /* 10 bits */
+        [[ cast(short)8, 18], [ cast(short)15, 64], [ cast(short)23, 16], [ cast(short)24, 17], [ cast(short)55, 0]],
+        /* 11 bits */
+        [/* EOL */[ cast(short)0, -1], [ cast(short)8, 1792], [ cast(short)23, 24], [ cast(short)24, 25], [ cast(short)40, 23], [ cast(short)55, 22], [ cast(short)103, 19],
+        [ cast(short)104, 20], [ cast(short)108, 21], [ cast(short)12, 1856], [ cast(short)13, 1920]],
+        /* 12 bits */
+        [[ cast(short)18, 1984], [ cast(short)19, 2048], [ cast(short)20, 2112], [ cast(short)21, 2176], [ cast(short)22, 2240], [ cast(short)23, 2304],
+        [ cast(short)28, 2368], [ cast(short)29, 2432], [ cast(short)30, 2496], [ cast(short)31, 2560], [ cast(short)36, 52], [ cast(short)39, 55], [ cast(short)40, 56],
+        [ cast(short)43, 59], [ cast(short)44, 60], [ cast(short)51, 320], [ cast(short)52, 384], [ cast(short)53, 448], [ cast(short)55, 53], [ cast(short)56, 54], [ cast(short)82, 50],
+        [ cast(short)83, 51], [ cast(short)84, 44], [ cast(short)85, 45], [ cast(short)86, 46], [ cast(short)87, 47], [ cast(short)88, 57], [ cast(short)89, 58], [ cast(short)90, 61],
+        [ cast(short)91, 256], [ cast(short)100, 48], [ cast(short)101, 49], [ cast(short)102, 62], [ cast(short)103, 63], [ cast(short)104, 30], [ cast(short)105, 31],
+        [ cast(short)106, 32], [ cast(short)107, 33], [ cast(short)108, 40], [ cast(short)109, 41], [ cast(short)200, 128], [ cast(short)201, 192], [ cast(short)202, 26],
+        [ cast(short)203, 27], [ cast(short)204, 28], [ cast(short)205, 29], [ cast(short)210, 34], [ cast(short)211, 35], [ cast(short)212, 36], [ cast(short)213, 37],
+        [ cast(short)214, 38], [ cast(short)215, 39], [ cast(short)218, 42], [ cast(short)219, 43]],
+        /* 13 bits */
+        [[ cast(short)74, 640], [ cast(short)75, 704], [ cast(short)76, 768], [ cast(short)77, 832], [ cast(short)82, 1280], [ cast(short)83, 1344], [ cast(short)84, 1408],
+        [ cast(short)85, 1472], [ cast(short)90, 1536], [ cast(short)91, 1600], [ cast(short)100, 1664], [ cast(short)101, 1728], [ cast(short)108, 512],
+        [ cast(short)109, 576], [ cast(short)114, 896], [ cast(short)115, 960], [ cast(short)116, 1024], [ cast(short)117, 1088], [ cast(short)118, 1152],
+        [ cast(short)119, 1216]]
+    ];
+
+    static final short[][][] WHITE_CODE = [
+        /* 4 bits */
+        [[ cast(short)7, 2], [ cast(short)8, 3], [ cast(short)11, 4], [ cast(short)12, 5], [ cast(short)14, 6], [ cast(short)15, 7]],
+        /* 5 bits */
+        [[ cast(short)7, 10], [ cast(short)8, 11], [ cast(short)18, 128], [ cast(short)19, 8], [ cast(short)20, 9], [ cast(short)27, 64]],
+        /* 6 bits */
+        [[ cast(short)3, 13], [ cast(short)7, 1], [ cast(short)8, 12], [ cast(short)23, 192], [ cast(short)24, 1664], [ cast(short)42, 16], [ cast(short)43, 17], [ cast(short)52, 14],
+        [ cast(short)53, 15]],
+        /* 7 bits */
+        [[ cast(short)3, 22], [ cast(short)4, 23], [ cast(short)8, 20], [ cast(short)12, 19], [ cast(short)19, 26], [ cast(short)23, 21], [ cast(short)24, 28], [ cast(short)36, 27],
+        [ cast(short)39, 18], [ cast(short)40, 24], [ cast(short)43, 25], [ cast(short)55, 256]],
+        /* 8 bits */
+        [[ cast(short)2, 29], [ cast(short)3, 30], [ cast(short)4, 45], [ cast(short)5, 46], [ cast(short)10, 47], [ cast(short)11, 48], [ cast(short)18, 33], [ cast(short)19, 34],
+        [ cast(short)20, 35], [ cast(short)21, 36], [ cast(short)22, 37], [ cast(short)23, 38], [ cast(short)26, 31], [ cast(short)27, 32], [ cast(short)36, 53], [ cast(short)37, 54],
+        [ cast(short)40, 39], [ cast(short)41, 40], [ cast(short)42, 41], [ cast(short)43, 42], [ cast(short)44, 43], [ cast(short)45, 44], [ cast(short)50, 61], [ cast(short)51, 62],
+        [ cast(short)52, 63], [ cast(short)53, 0], [ cast(short)54, 320], [ cast(short)55, 384], [ cast(short)74, 59], [ cast(short)75, 60], [ cast(short)82, 49], [ cast(short)83, 50],
+        [ cast(short)84, 51], [ cast(short)85, 52], [ cast(short)88, 55], [ cast(short)89, 56], [ cast(short)90, 57], [ cast(short)91, 58], [ cast(short)100, 448],
+        [ cast(short)101, 512], [ cast(short)103, 640], [ cast(short)104, 576]],
+        /* 9 bits */
+        [[ cast(short)152, 1472], [ cast(short)153, 1536], [ cast(short)154, 1600], [ cast(short)155, 1728], [ cast(short)204, 704], [ cast(short)205, 768],
+        [ cast(short)210, 832], [ cast(short)211, 896], [ cast(short)212, 960], [ cast(short)213, 1024], [ cast(short)214, 1088], [ cast(short)215, 1152],
+        [ cast(short)216, 1216], [ cast(short)217, 1280], [ cast(short)218, 1344], [ cast(short)219, 1408]],
+        /* 10 bits */
+        [],
+        /* 11 bits */
+        [[ cast(short)8, 1792], [ cast(short)12, 1856], [ cast(short)13, 1920]],
+        /* 12 bits */
+        [/* EOL */[ cast(short)1, -1], [ cast(short)18, 1984], [ cast(short)19, 2048], [ cast(short)20, 2112], [ cast(short)21, 2176], [ cast(short)22, 2240], [ cast(short)23, 2304],
+        [ cast(short)28, 2368], [ cast(short)29, 2432], [ cast(short)30, 2496], [ cast(short)31, 2560]]
+    ];
+
+    static final int BLACK_MIN_BITS = 2;
+    static final int WHITE_MIN_BITS = 4;
+
+    bool isWhite;
+    int whiteValue = 0;
+    int blackValue = 1;
+    byte[] src;
+    byte[] dest;
+    int byteOffsetSrc = 0;
+    int bitOffsetSrc = 0;
+    int byteOffsetDest = 0;
+    int bitOffsetDest = 0;
+    int code = 0;
+    int nbrBits = 0;
+    /* nbr of bytes per row */
+    int rowSize;
+
+public int decode(byte[] src, byte[] dest, int offsetDest, int rowSize, int nRows) {
+    this.src = src;
+    this.dest = dest;
+    this.rowSize = rowSize;
+    byteOffsetSrc = 0;
+    bitOffsetSrc = 0;
+    byteOffsetDest = offsetDest;
+    bitOffsetDest = 0;
+    int cnt = 0;
+    while (cnt < nRows && decodeRow()) {
+        cnt++;
+        /* byte aligned */
+        if (bitOffsetDest > 0) {
+            byteOffsetDest++;
+            bitOffsetDest = 0;
+        }
+    }
+    return byteOffsetDest - offsetDest;
+}
+
+bool decodeRow() {
+    isWhite = true;
+    int n = 0;
+    while (n < rowSize) {
+        int runLength = decodeRunLength();
+        if (runLength < 0) return false;
+        n += runLength;
+        setNextBits(isWhite ? whiteValue : blackValue, runLength);
+        isWhite = !isWhite;
+    }
+    return true;
+}
+
+int decodeRunLength() {
+    int runLength = 0;
+    int partialRun = 0;
+    short[][][] huffmanCode = isWhite ? WHITE_CODE : BLACK_CODE;
+    while (true) {
+        bool found = false;
+        nbrBits = isWhite ? WHITE_MIN_BITS : BLACK_MIN_BITS;
+        code = getNextBits(nbrBits);
+        for (int i = 0; i < huffmanCode.length; i++) {
+            for (int j = 0; j < huffmanCode[i].length; j++) {
+                if (huffmanCode[i][j][0] is code) {
+                    found = true;
+                    partialRun = huffmanCode[i][j][1];
+                    if (partialRun is -1) {
+                        /* Stop when reaching final EOL on last byte */
+                        if (byteOffsetSrc is src.length - 1) return -1;
+                        /* Group 3 starts each row with an EOL - ignore it */
+                    } else {
+                        runLength += partialRun;
+                        if (partialRun < 64) return runLength;
+                    }
+                    break;
+                }
+            }
+            if (found) break;
+            code = code << 1 | getNextBit();
+        }
+        if (!found) DWT.error(DWT.ERROR_INVALID_IMAGE);
+    }
+}
+
+int getNextBit() {
+    int value = (src[byteOffsetSrc] >>> (7 - bitOffsetSrc)) & 0x1;
+    bitOffsetSrc++;
+    if (bitOffsetSrc > 7) {
+        byteOffsetSrc++;
+        bitOffsetSrc = 0;
+    }
+    return value;
+}
+
+int getNextBits(int cnt) {
+    int value = 0;
+    for (int i = 0; i < cnt; i++) {
+        value = value << 1 | getNextBit();
+    }
+    return value;
+}
+
+void setNextBits(int value, int cnt) {
+    int n = cnt;
+    while (bitOffsetDest > 0 && bitOffsetDest <= 7 && n > 0) {
+        dest[byteOffsetDest] = value is 1 ?
+            cast(byte)(dest[byteOffsetDest] | (1 << (7 - bitOffsetDest))) :
+            cast(byte)(dest[byteOffsetDest] & ~(1 << (7 - bitOffsetDest)));
+        n--;
+        bitOffsetDest++;
+    }
+    if (bitOffsetDest is 8) {
+        byteOffsetDest++;
+        bitOffsetDest = 0;
+    }
+    while (n >= 8) {
+        dest[byteOffsetDest++] = cast(byte) (value is 1 ? 0xFF : 0);
+        n -= 8;
+    }
+    while (n > 0) {
+        dest[byteOffsetDest] = value is 1 ?
+            cast(byte)(dest[byteOffsetDest] | (1 << (7 - bitOffsetDest))) :
+            cast(byte)(dest[byteOffsetDest] & ~(1 << (7 - bitOffsetDest)));
+        n--;
+        bitOffsetDest++;
+    }
+}
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dwt/internal/image/TIFFRandomFileAccess.d	Wed Jan 23 12:01:46 2008 +0100
@@ -0,0 +1,101 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2003 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *     IBM Corporation - initial API and implementation
+ * Port to the D programming language:
+ *     Frank Benoit <benoit@tionex.de>
+ *******************************************************************************/
+module dwt.internal.image.TIFFRandomFileAccess;
+
+import dwt.internal.image.LEDataInputStream;
+import Math = tango.math.Math;
+import tango.core.Exception;
+import dwt.dwthelper.System;
+
+final class TIFFRandomFileAccess {
+
+    LEDataInputStream inputStream;
+    int start, current, next;
+    byte[][] buffers;
+
+    static final int CHUNK_SIZE = 8192;
+    static final int LIST_SIZE = 128;
+
+public this(LEDataInputStream stream) {
+    inputStream = stream;
+    start = current = next = inputStream.getPosition();
+    buffers = new byte[][](LIST_SIZE);
+}
+
+void seek(int pos) {
+    if (pos is current) return;
+    if (pos < start) throw new IOException( "pos < start" );
+    current = pos;
+    if (current > next) {
+        int n = current - next;
+        /* store required bytes */
+        int index = next / CHUNK_SIZE;
+        int offset = next % CHUNK_SIZE;
+        while (n > 0) {
+            if (index >= buffers.length) {
+                byte[][] oldBuffers = buffers;
+                buffers = new byte[][]( Math.max(index + 1, oldBuffers.length + LIST_SIZE) );
+                System.arraycopy(oldBuffers, 0, buffers, 0, oldBuffers.length);
+            }
+            if (buffers[index] is null) buffers[index] = new byte[CHUNK_SIZE];
+            int cnt = inputStream.read(buffers[index], offset, Math.min(n, CHUNK_SIZE - offset));
+            n -= cnt;
+            next += cnt;
+            index++;
+            offset = 0;
+        }
+    }
+}
+
+void read(byte b[]) {
+    int size = b.length;
+    int nCached = Math.min(size, next - current);
+    int nMissing = size - next + current;
+    int destNext = 0;
+    if (nCached > 0) {
+        /* Get cached bytes */
+        int index = current / CHUNK_SIZE;
+        int offset = current % CHUNK_SIZE;
+        while (nCached > 0) {
+            int cnt = Math.min(nCached, CHUNK_SIZE - offset);
+            System.arraycopy(buffers[index], offset, b, destNext, cnt);
+            nCached -= cnt;
+            destNext += cnt;
+            index++;
+            offset = 0;
+        }
+    }
+    if (nMissing > 0) {
+        /* Read required bytes */
+        int index = next / CHUNK_SIZE;
+        int offset = next % CHUNK_SIZE;
+        while (nMissing > 0) {
+            if (index >= buffers.length) {
+                byte[][] oldBuffers = buffers;
+                buffers = new byte[][](Math.max(index, oldBuffers.length + LIST_SIZE));
+                System.arraycopy(oldBuffers, 0, buffers, 0, oldBuffers.length);
+            }
+            if (buffers[index] is null) buffers[index] = new byte[CHUNK_SIZE];
+            int cnt = inputStream.read(buffers[index], offset, Math.min(nMissing, CHUNK_SIZE - offset));
+            System.arraycopy(buffers[index], offset, b, destNext, cnt);
+            nMissing -= cnt;
+            next += cnt;
+            destNext += cnt;
+            index++;
+            offset = 0;
+        }
+    }
+    current += size;
+}
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dwt/internal/image/WinBMPFileFormat.d	Wed Jan 23 12:01:46 2008 +0100
@@ -0,0 +1,701 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2006 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *     IBM Corporation - initial API and implementation
+ * Port to the D programming language:
+ *     Frank Benoit <benoit@tionex.de>
+ *******************************************************************************/
+module dwt.internal.image.WinBMPFileFormat;
+
+public import dwt.internal.image.FileFormat;
+public import dwt.graphics.PaletteData;
+import dwt.graphics.Point;
+import dwt.graphics.RGB;
+import dwt.dwthelper.ByteArrayOutputStream;
+import dwt.DWT;
+
+import tango.core.Exception;
+
+final class WinBMPFileFormat : FileFormat {
+
+    static final int BMPFileHeaderSize = 14;
+    static final int BMPHeaderFixedSize = 40;
+    int importantColors;
+    Point pelsPerMeter;
+
+public this(){
+    pelsPerMeter = new Point(0, 0);
+}
+
+/**
+ * Compress numBytes bytes of image data from src, storing in dest
+ * (starting at 0), using the technique specified by comp.
+ * If last is true, this indicates the last line of the image.
+ * Answer the size of the compressed data.
+ */
+int compress(int comp, byte[] src, int srcOffset, int numBytes, byte[] dest, bool last) {
+    if (comp is 1) { // BMP_RLE8_COMPRESSION
+        return compressRLE8Data(src, srcOffset, numBytes, dest, last);
+    }
+    if (comp is 2) { // BMP_RLE4_COMPRESSION
+        return compressRLE4Data(src, srcOffset, numBytes, dest, last);
+    }
+    DWT.error(DWT.ERROR_INVALID_IMAGE);
+    return 0;
+}
+int compressRLE4Data(byte[] src, int srcOffset, int numBytes, byte[] dest, bool last) {
+    int sp = srcOffset, end = srcOffset + numBytes, dp = 0;
+    int size = 0, left, i, n;
+    byte theByte;
+    while (sp < end) {
+        /* find two consecutive bytes that are the same in the next 128 */
+        left = end - sp - 1;
+        if (left > 127)
+            left = 127;
+        for (n = 0; n < left; n++) {
+            if (src[sp + n] is src[sp + n + 1])
+                break;
+        }
+        /* if there is only one more byte in the scan line, include it */
+        if (n < 127 && n is left)
+            n++;
+        /* store the intervening data */
+        switch (n) {
+            case 0:
+                break;
+            case 1: /* handled separately because 0,2 is a command */
+                dest[dp] = 2; dp++; /* 1 byte is 2 pixels */
+                dest[dp] = src[sp];
+                dp++; sp++;
+                size += 2;
+                break;
+            default:
+                dest[dp] = 0; dp++;
+                dest[dp] = cast(byte)(n + n); dp++; /* n bytes = n*2 pixels */
+                for (i = n; i > 0; i--) {
+                    dest[dp] = src[sp];
+                    dp++; sp++;
+                }
+                size += 2 + n;
+                if ((n & 1) !is 0) { /* pad to word */
+                    dest[dp] = 0;
+                    dp++;
+                    size++;
+                }
+                break;
+        }
+        /* find the length of the next run (up to 127) and store it */
+        left = end - sp;
+        if (left > 0) {
+            if (left > 127)
+                left = 127;
+            theByte = src[sp];
+            for (n = 1; n < left; n++) {
+                if (src[sp + n] !is theByte)
+                    break;
+            }
+            dest[dp] = cast(byte)(n + n); dp++; /* n bytes = n*2 pixels */
+            dest[dp] = theByte; dp++;
+            sp += n;
+            size += 2;
+        }
+    }
+
+    /* store the end of line or end of bitmap codes */
+    dest[dp] = 0; dp++;
+    if (last) {
+        dest[dp] = 1; dp++;
+    } else {
+        dest[dp] = 0; dp++;
+    }
+    size += 2;
+
+    return size;
+}
+int compressRLE8Data(byte[] src, int srcOffset, int numBytes, byte[] dest, bool last) {
+    int sp = srcOffset, end = srcOffset + numBytes, dp = 0;
+    int size = 0, left, i, n;
+    byte theByte;
+    while (sp < end) {
+        /* find two consecutive bytes that are the same in the next 256 */
+        left = end - sp - 1;
+        if (left > 254)
+            left = 254;
+        for (n = 0; n < left; n++) {
+            if (src[sp + n] is src[sp + n + 1])
+                break;
+        }
+        /* if there is only one more byte in the scan line, include it */
+        if (n is left)
+            n++;
+        /* store the intervening data */
+        switch (n) {
+            case 0:
+                break;
+            case 2: /* handled separately because 0,2 is a command */
+                dest[dp] = 1; dp++;
+                dest[dp] = src[sp];
+                dp++; sp++;
+                size += 2;
+                /* don't break, fall through */
+            case 1: /* handled separately because 0,1 is a command */
+                dest[dp] = 1; dp++;
+                dest[dp] = src[sp];
+                dp++; sp++;
+                size += 2;
+                break;
+            default:
+                dest[dp] = 0; dp++;
+                dest[dp] = cast(byte)n; dp++;
+                for (i = n; i > 0; i--) {
+                    dest[dp] = src[sp];
+                    dp++; sp++;
+                }
+                size += 2 + n;
+                if ((n & 1) !is 0) { /* pad to word */
+                    dest[dp] = 0;
+                    dp++;
+                    size++;
+                }
+                break;
+        }
+        /* find the length of the next run (up to 255) and store it */
+        left = end - sp;
+        if (left > 0) {
+            if (left > 255)
+                left = 255;
+            theByte = src[sp];
+            for (n = 1; n < left; n++) {
+                if (src[sp + n] !is theByte)
+                    break;
+            }
+            dest[dp] = cast(byte)n; dp++;
+            dest[dp] = theByte; dp++;
+            sp += n;
+            size += 2;
+        }
+    }
+
+    /* store the end of line or end of bitmap codes */
+    dest[dp] = 0; dp++;
+    if (last) {
+        dest[dp] = 1; dp++;
+    } else {
+        dest[dp] = 0; dp++;
+    }
+    size += 2;
+
+    return size;
+}
+void decompressData(byte[] src, byte[] dest, int stride, int cmp) {
+    if (cmp is 1) { // BMP_RLE8_COMPRESSION
+        if (decompressRLE8Data(src, src.length, stride, dest, dest.length) <= 0)
+            DWT.error(DWT.ERROR_INVALID_IMAGE);
+        return;
+    }
+    if (cmp is 2) { // BMP_RLE4_COMPRESSION
+        if (decompressRLE4Data(src, src.length, stride, dest, dest.length) <= 0)
+            DWT.error(DWT.ERROR_INVALID_IMAGE);
+        return;
+    }
+    DWT.error(DWT.ERROR_INVALID_IMAGE);
+}
+int decompressRLE4Data(byte[] src, int numBytes, int stride, byte[] dest, int destSize) {
+    int sp = 0;
+    int se = numBytes;
+    int dp = 0;
+    int de = destSize;
+    int x = 0, y = 0;
+    while (sp < se) {
+        int len = src[sp] & 0xFF;
+        sp++;
+        if (len is 0) {
+            len = src[sp] & 0xFF;
+            sp++;
+            switch (len) {
+                case 0: /* end of line */
+                    y++;
+                    x = 0;
+                    dp = y * stride;
+                    if (dp > de)
+                        return -1;
+                    break;
+                case 1: /* end of bitmap */
+                    return 1;
+                case 2: /* delta */
+                    x += src[sp] & 0xFF;
+                    sp++;
+                    y += src[sp] & 0xFF;
+                    sp++;
+                    dp = y * stride + x / 2;
+                    if (dp > de)
+                        return -1;
+                    break;
+                default: /* absolute mode run */
+                    if ((len & 1) !is 0) /* odd run lengths not currently supported */
+                        return -1;
+                    x += len;
+                    len = len / 2;
+                    if (len > (se - sp))
+                        return -1;
+                    if (len > (de - dp))
+                        return -1;
+                    for (int i = 0; i < len; i++) {
+                        dest[dp] = src[sp];
+                        dp++;
+                        sp++;
+                    }
+                    if ((sp & 1) !is 0)
+                        sp++; /* word align sp? */
+                    break;
+            }
+        } else {
+            if ((len & 1) !is 0)
+                return -1;
+            x += len;
+            len = len / 2;
+            byte theByte = src[sp];
+            sp++;
+            if (len > (de - dp))
+                return -1;
+            for (int i = 0; i < len; i++) {
+                dest[dp] = theByte;
+                dp++;
+            }
+        }
+    }
+    return 1;
+}
+int decompressRLE8Data(byte[] src, int numBytes, int stride, byte[] dest, int destSize) {
+    int sp = 0;
+    int se = numBytes;
+    int dp = 0;
+    int de = destSize;
+    int x = 0, y = 0;
+    while (sp < se) {
+        int len = src[sp] & 0xFF;
+        sp++;
+        if (len is 0) {
+            len = src[sp] & 0xFF;
+            sp++;
+            switch (len) {
+                case 0: /* end of line */
+                    y++;
+                    x = 0;
+                    dp = y * stride;
+                    if (dp > de)
+                        return -1;
+                    break;
+                case 1: /* end of bitmap */
+                    return 1;
+                case 2: /* delta */
+                    x += src[sp] & 0xFF;
+                    sp++;
+                    y += src[sp] & 0xFF;
+                    sp++;
+                    dp = y * stride + x;
+                    if (dp > de)
+                        return -1;
+                    break;
+                default: /* absolute mode run */
+                    if (len > (se - sp))
+                        return -1;
+                    if (len > (de - dp))
+                        return -1;
+                    for (int i = 0; i < len; i++) {
+                        dest[dp] = src[sp];
+                        dp++;
+                        sp++;
+                    }
+                    if ((sp & 1) !is 0)
+                        sp++; /* word align sp? */
+                    x += len;
+                    break;
+            }
+        } else {
+            byte theByte = src[sp];
+            sp++;
+            if (len > (de - dp))
+                return -1;
+            for (int i = 0; i < len; i++) {
+                dest[dp] = theByte;
+                dp++;
+            }
+            x += len;
+        }
+    }
+    return 1;
+}
+bool isFileFormat(LEDataInputStream stream) {
+    try {
+        byte[] header = new byte[18];
+        stream.read(header);
+        stream.unread(header);
+        int infoHeaderSize = (header[14] & 0xFF) | ((header[15] & 0xFF) << 8) | ((header[16] & 0xFF) << 16) | ((header[17] & 0xFF) << 24);
+        return header[0] is 0x42 && header[1] is 0x4D && infoHeaderSize >= BMPHeaderFixedSize;
+    } catch (TracedException e) {
+        return false;
+    }
+}
+byte[] loadData(byte[] infoHeader) {
+    int width = (infoHeader[4] & 0xFF) | ((infoHeader[5] & 0xFF) << 8) | ((infoHeader[6] & 0xFF) << 16) | ((infoHeader[7] & 0xFF) << 24);
+    int height = (infoHeader[8] & 0xFF) | ((infoHeader[9] & 0xFF) << 8) | ((infoHeader[10] & 0xFF) << 16) | ((infoHeader[11] & 0xFF) << 24);
+    int bitCount = (infoHeader[14] & 0xFF) | ((infoHeader[15] & 0xFF) << 8);
+    int stride = (width * bitCount + 7) / 8;
+    stride = (stride + 3) / 4 * 4; // Round up to 4 byte multiple
+    byte[] data = loadData(infoHeader, stride);
+    flipScanLines(data, stride, height);
+    return data;
+}
+byte[] loadData(byte[] infoHeader, int stride) {
+    int height = (infoHeader[8] & 0xFF) | ((infoHeader[9] & 0xFF) << 8) | ((infoHeader[10] & 0xFF) << 16) | ((infoHeader[11] & 0xFF) << 24);
+    if (height < 0) height = -height;
+    int dataSize = height * stride;
+    byte[] data = new byte[dataSize];
+    int cmp = (infoHeader[16] & 0xFF) | ((infoHeader[17] & 0xFF) << 8) | ((infoHeader[18] & 0xFF) << 16) | ((infoHeader[19] & 0xFF) << 24);
+    if (cmp is 0 || cmp is 3) { // BMP_NO_COMPRESSION
+        try {
+            if (inputStream.read(data) !is dataSize)
+                DWT.error(DWT.ERROR_INVALID_IMAGE);
+        } catch (IOException e) {
+            DWT.error(DWT.ERROR_IO, e);
+        }
+    } else {
+        int compressedSize = (infoHeader[20] & 0xFF) | ((infoHeader[21] & 0xFF) << 8) | ((infoHeader[22] & 0xFF) << 16) | ((infoHeader[23] & 0xFF) << 24);
+        byte[] compressed = new byte[compressedSize];
+        try {
+            if (inputStream.read(compressed) !is compressedSize)
+                DWT.error(DWT.ERROR_INVALID_IMAGE);
+        } catch (IOException e) {
+            DWT.error(DWT.ERROR_IO, e);
+        }
+        decompressData(compressed, data, stride, cmp);
+    }
+    return data;
+}
+int[] loadFileHeader() {
+    int[] header = new int[5];
+    try {
+        header[0] = inputStream.readShort();
+        header[1] = inputStream.readInt();
+        header[2] = inputStream.readShort();
+        header[3] = inputStream.readShort();
+        header[4] = inputStream.readInt();
+    } catch (IOException e) {
+        DWT.error(DWT.ERROR_IO, e);
+    }
+    if (header[0] !is 0x4D42)
+        DWT.error(DWT.ERROR_INVALID_IMAGE);
+    return header;
+}
+ImageData[] loadFromByteStream() {
+    int[] fileHeader = loadFileHeader();
+    byte[] infoHeader = new byte[BMPHeaderFixedSize];
+    try {
+        inputStream.read(infoHeader);
+    } catch (TracedException e) {
+        DWT.error(DWT.ERROR_IO, e);
+    }
+    int width = (infoHeader[4] & 0xFF) | ((infoHeader[5] & 0xFF) << 8) | ((infoHeader[6] & 0xFF) << 16) | ((infoHeader[7] & 0xFF) << 24);
+    int height = (infoHeader[8] & 0xFF) | ((infoHeader[9] & 0xFF) << 8) | ((infoHeader[10] & 0xFF) << 16) | ((infoHeader[11] & 0xFF) << 24);
+    if (height < 0) height = -height;
+    int bitCount = (infoHeader[14] & 0xFF) | ((infoHeader[15] & 0xFF) << 8);
+    this.compression = (infoHeader[16] & 0xFF) | ((infoHeader[17] & 0xFF) << 8) | ((infoHeader[18] & 0xFF) << 16) | ((infoHeader[19] & 0xFF) << 24);
+    PaletteData palette = loadPalette(infoHeader);
+    if (inputStream.getPosition() < fileHeader[4]) {
+        // Seek to the specified offset
+        try {
+            inputStream.skip(fileHeader[4] - inputStream.getPosition());
+        } catch (IOException e) {
+            DWT.error(DWT.ERROR_IO, e);
+        }
+    }
+    byte[] data = loadData(infoHeader);
+    this.importantColors = (infoHeader[36] & 0xFF) | ((infoHeader[37] & 0xFF) << 8) | ((infoHeader[38] & 0xFF) << 16) | ((infoHeader[39] & 0xFF) << 24);
+    int xPelsPerMeter = (infoHeader[24] & 0xFF) | ((infoHeader[25] & 0xFF) << 8) | ((infoHeader[26] & 0xFF) << 16) | ((infoHeader[27] & 0xFF) << 24);
+    int yPelsPerMeter = (infoHeader[28] & 0xFF) | ((infoHeader[29] & 0xFF) << 8) | ((infoHeader[30] & 0xFF) << 16) | ((infoHeader[31] & 0xFF) << 24);
+    this.pelsPerMeter = new Point(xPelsPerMeter, yPelsPerMeter);
+    int type = (this.compression is 1 /*BMP_RLE8_COMPRESSION*/) || (this.compression is 2 /*BMP_RLE4_COMPRESSION*/) ? DWT.IMAGE_BMP_RLE : DWT.IMAGE_BMP;
+    return [
+        ImageData.internal_new(
+            width,
+            height,
+            bitCount,
+            palette,
+            4,
+            data,
+            0,
+            null,
+            null,
+            -1,
+            -1,
+            type,
+            0,
+            0,
+            0,
+            0)
+    ];
+}
+PaletteData loadPalette(byte[] infoHeader) {
+    int depth = (infoHeader[14] & 0xFF) | ((infoHeader[15] & 0xFF) << 8);
+    if (depth <= 8) {
+        int numColors = (infoHeader[32] & 0xFF) | ((infoHeader[33] & 0xFF) << 8) | ((infoHeader[34] & 0xFF) << 16) | ((infoHeader[35] & 0xFF) << 24);
+        if (numColors is 0) {
+            numColors = 1 << depth;
+        } else {
+            if (numColors > 256)
+                numColors = 256;
+        }
+        byte[] buf = new byte[numColors * 4];
+        try {
+            if (inputStream.read(buf) !is buf.length)
+                DWT.error(DWT.ERROR_INVALID_IMAGE);
+        } catch (IOException e) {
+            DWT.error(DWT.ERROR_IO, e);
+        }
+        return paletteFromBytes(buf, numColors);
+    }
+    if (depth is 16) {
+        if (this.compression is 3) {
+            try {
+                return new PaletteData(inputStream.readInt(), inputStream.readInt(), inputStream.readInt());
+            } catch (IOException e) {
+                DWT.error(DWT.ERROR_IO, e);
+            }
+        }
+        return new PaletteData(0x7C00, 0x3E0, 0x1F);
+    }
+    if (depth is 24) return new PaletteData(0xFF, 0xFF00, 0xFF0000);
+    if (this.compression is 3) {
+        try {
+            return new PaletteData(inputStream.readInt(), inputStream.readInt(), inputStream.readInt());
+        } catch (IOException e) {
+            DWT.error(DWT.ERROR_IO, e);
+        }
+    }
+    return new PaletteData(0xFF00, 0xFF0000, 0xFF000000);
+}
+PaletteData paletteFromBytes(byte[] bytes, int numColors) {
+    int bytesOffset = 0;
+    RGB[] colors = new RGB[numColors];
+    for (int i = 0; i < numColors; i++) {
+        colors[i] = new RGB(bytes[bytesOffset + 2] & 0xFF,
+            bytes[bytesOffset + 1] & 0xFF,
+            bytes[bytesOffset] & 0xFF);
+        bytesOffset += 4;
+    }
+    return new PaletteData(colors);
+}
+/**
+ * Answer a byte array containing the BMP representation of
+ * the given device independent palette.
+ */
+static byte[] paletteToBytes(PaletteData pal) {
+    int n = pal.colors is null ? 0 : (pal.colors.length < 256 ? pal.colors.length : 256);
+    byte[] bytes = new byte[n * 4];
+    int offset = 0;
+    for (int i = 0; i < n; i++) {
+        RGB col = pal.colors[i];
+        bytes[offset] = cast(byte)col.blue;
+        bytes[offset + 1] = cast(byte)col.green;
+        bytes[offset + 2] = cast(byte)col.red;
+        offset += 4;
+    }
+    return bytes;
+}
+/**
+ * Unload the given image's data into the given byte stream
+ * using the given compression strategy.
+ * Answer the number of bytes written.
+ */
+int unloadData(ImageData image, OutputStream ostr, int comp) {
+    int totalSize = 0;
+    try {
+        if (comp is 0)
+            return unloadDataNoCompression(image, ostr);
+        int bpl = (image.width * image.depth + 7) / 8;
+        int bmpBpl = (bpl + 3) / 4 * 4; // BMP pads scanlines to multiples of 4 bytes
+        int imageBpl = image.bytesPerLine;
+        // Compression can actually take twice as much space, in worst case
+        byte[] buf = new byte[bmpBpl * 2];
+        int srcOffset = imageBpl * (image.height - 1); // Start at last line
+        byte[] data = image.data;
+        totalSize = 0;
+        byte[] buf2 = new byte[32768];
+        int buf2Offset = 0;
+        for (int y = image.height - 1; y >= 0; y--) {
+            int lineSize = compress(comp, data, srcOffset, bpl, buf, y is 0);
+            if (buf2Offset + lineSize > buf2.length) {
+                ostr.write(buf2, 0, buf2Offset);
+                buf2Offset = 0;
+            }
+            System.arraycopy(buf, 0, buf2, buf2Offset, lineSize);
+            buf2Offset += lineSize;
+            totalSize += lineSize;
+            srcOffset -= imageBpl;
+        }
+        if (buf2Offset > 0)
+            ostr.write(buf2, 0, buf2Offset);
+    } catch (IOException e) {
+        DWT.error(DWT.ERROR_IO, e);
+    }
+    return totalSize;
+}
+/**
+ * Prepare the given image's data for unloading into a byte stream
+ * using no compression strategy.
+ * Answer the number of bytes written.
+ */
+int unloadDataNoCompression(ImageData image, OutputStream ostr) {
+    int bmpBpl = 0;
+    try {
+        int bpl = (image.width * image.depth + 7) / 8;
+        bmpBpl = (bpl + 3) / 4 * 4; // BMP pads scanlines to multiples of 4 bytes
+        int linesPerBuf = 32678 / bmpBpl;
+        byte[] buf = new byte[linesPerBuf * bmpBpl];
+        byte[] data = image.data;
+        int imageBpl = image.bytesPerLine;
+        int dataIndex = imageBpl * (image.height - 1); // Start at last line
+        if (image.depth is 16) {
+            for (int y = 0; y < image.height; y += linesPerBuf) {
+                int count = image.height - y;
+                if (linesPerBuf < count) count = linesPerBuf;
+                int bufOffset = 0;
+                for (int i = 0; i < count; i++) {
+                    for (int wIndex = 0; wIndex < bpl; wIndex += 2) {
+                        buf[bufOffset + wIndex + 1] = data[dataIndex + wIndex + 1];
+                        buf[bufOffset + wIndex] = data[dataIndex + wIndex];
+                    }
+                    bufOffset += bmpBpl;
+                    dataIndex -= imageBpl;
+                }
+                ostr.write(buf, 0, bufOffset);
+            }
+        } else {
+            for (int y = 0; y < image.height; y += linesPerBuf) {
+                int tmp = image.height - y;
+                int count = tmp < linesPerBuf ? tmp : linesPerBuf;
+                int bufOffset = 0;
+                for (int i = 0; i < count; i++) {
+                    System.arraycopy(data, dataIndex, buf, bufOffset, bpl);
+                    bufOffset += bmpBpl;
+                    dataIndex -= imageBpl;
+                }
+                ostr.write(buf, 0, bufOffset);
+            }
+        }
+    } catch (IOException e) {
+        DWT.error(DWT.ERROR_IO, e);
+    }
+    return bmpBpl * image.height;
+}
+/**
+ * Unload a DeviceIndependentImage using Windows .BMP format into the given
+ * byte stream.
+ */
+void unloadIntoByteStream(ImageLoader loader) {
+    ImageData image = loader.data[0];
+    byte[] rgbs;
+    int numCols;
+    if (!((image.depth is 1) || (image.depth is 4) || (image.depth is 8) ||
+          (image.depth is 16) || (image.depth is 24) || (image.depth is 32)))
+            DWT.error(DWT.ERROR_UNSUPPORTED_DEPTH);
+    int comp = this.compression;
+    if (!((comp is 0) || ((comp is 1) && (image.depth is 8)) ||
+          ((comp is 2) && (image.depth is 4))))
+            DWT.error(DWT.ERROR_INVALID_IMAGE);
+    PaletteData pal = image.palette;
+    if ((image.depth is 16) || (image.depth is 24) || (image.depth is 32)) {
+        if (!pal.isDirect)
+            DWT.error(DWT.ERROR_INVALID_IMAGE);
+        numCols = 0;
+        rgbs = null;
+    } else {
+        if (pal.isDirect)
+            DWT.error(DWT.ERROR_INVALID_IMAGE);
+        numCols = pal.colors.length;
+        rgbs = paletteToBytes(pal);
+    }
+    // Fill in file header, except for bfsize, which is done later.
+    int headersSize = BMPFileHeaderSize + BMPHeaderFixedSize;
+    int[] fileHeader = new int[5];
+    fileHeader[0] = 0x4D42; // Signature
+    fileHeader[1] = 0; // File size - filled in later
+    fileHeader[2] = 0; // Reserved 1
+    fileHeader[3] = 0; // Reserved 2
+    fileHeader[4] = headersSize; // Offset to data
+    if (rgbs !is null) {
+        fileHeader[4] += rgbs.length;
+    }
+
+    // Prepare data. This is done first so we don't have to try to rewind
+    // the stream and fill in the details later.
+    ByteArrayOutputStream ostr = new ByteArrayOutputStream();
+    unloadData(image, ostr, comp);
+    byte[] data = ostr.toByteArray();
+
+    // Calculate file size
+    fileHeader[1] = fileHeader[4] + data.length;
+
+    // Write the headers
+    try {
+        outputStream.writeShort(fileHeader[0]);
+        outputStream.writeInt(fileHeader[1]);
+        outputStream.writeShort(fileHeader[2]);
+        outputStream.writeShort(fileHeader[3]);
+        outputStream.writeInt(fileHeader[4]);
+    } catch (IOException e) {
+        DWT.error(DWT.ERROR_IO, e);
+    }
+    try {
+        outputStream.writeInt(BMPHeaderFixedSize);
+        outputStream.writeInt(image.width);
+        outputStream.writeInt(image.height);
+        outputStream.writeShort(1);
+        outputStream.writeShort(cast(short)image.depth);
+        outputStream.writeInt(comp);
+        outputStream.writeInt(data.length);
+        outputStream.writeInt(pelsPerMeter.x);
+        outputStream.writeInt(pelsPerMeter.y);
+        outputStream.writeInt(numCols);
+        outputStream.writeInt(importantColors);
+    } catch (IOException e) {
+        DWT.error(DWT.ERROR_IO, e);
+    }
+
+    // Unload palette
+    if (numCols > 0) {
+        try {
+            outputStream.write(rgbs);
+        } catch (IOException e) {
+            DWT.error(DWT.ERROR_IO, e);
+        }
+    }
+
+    // Unload the data
+    try {
+        outputStream.write(data);
+    } catch (IOException e) {
+        DWT.error(DWT.ERROR_IO, e);
+    }
+}
+void flipScanLines(byte[] data, int stride, int height) {
+    int i1 = 0;
+    int i2 = (height - 1) * stride;
+    for (int i = 0; i < height / 2; i++) {
+        for (int index = 0; index < stride; index++) {
+            byte b = data[index + i1];
+            data[index + i1] = data[index + i2];
+            data[index + i2] = b;
+        }
+        i1 += stride;
+        i2 -= stride;
+    }
+}
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dwt/internal/image/WinICOFileFormat.d	Wed Jan 23 12:01:46 2008 +0100
@@ -0,0 +1,330 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2006 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *     IBM Corporation - initial API and implementation
+ * Port to the D programming language:
+ *     Frank Benoit <benoit@tionex.de>
+ *******************************************************************************/
+module dwt.internal.image.WinICOFileFormat;
+
+public import dwt.internal.image.FileFormat;
+public import dwt.graphics.PaletteData;
+import dwt.internal.image.WinBMPFileFormat;
+import dwt.DWT;
+
+import tango.core.Exception;
+
+final class WinICOFileFormat : FileFormat {
+
+byte[] bitInvertData(byte[] data, int startIndex, int endIndex) {
+    // Destructively bit invert data in the given byte array.
+    for (int i = startIndex; i < endIndex; i++) {
+        data[i] = cast(byte)(255 - data[i - startIndex]);
+    }
+    return data;
+}
+
+static final byte[] convertPad(byte[] data, int width, int height, int depth, int pad, int newPad) {
+    if (pad is newPad) return data;
+    int stride = (width * depth + 7) / 8;
+    int bpl = (stride + (pad - 1)) / pad * pad;
+    int newBpl = (stride + (newPad - 1)) / newPad * newPad;
+    byte[] newData = new byte[height * newBpl];
+    int srcIndex = 0, destIndex = 0;
+    for (int y = 0; y < height; y++) {
+        System.arraycopy(data, srcIndex, newData, destIndex, newBpl);
+        srcIndex += bpl;
+        destIndex += newBpl;
+    }
+    return newData;
+}
+/**
+ * Answer the size in bytes of the file representation of the given
+ * icon
+ */
+int iconSize(ImageData i) {
+    int shapeDataStride = (i.width * i.depth + 31) / 32 * 4;
+    int maskDataStride = (i.width + 31) / 32 * 4;
+    int dataSize = (shapeDataStride + maskDataStride) * i.height;
+    int paletteSize = i.palette.colors !is null ? i.palette.colors.length * 4 : 0;
+    return WinBMPFileFormat.BMPHeaderFixedSize + paletteSize + dataSize;
+}
+bool isFileFormat(LEDataInputStream stream) {
+    try {
+        byte[] header = new byte[4];
+        stream.read(header);
+        stream.unread(header);
+        return header[0] is 0 && header[1] is 0 && header[2] is 1 && header[3] is 0;
+    } catch (Exception e) {
+        return false;
+    }
+}
+bool isValidIcon(ImageData i) {
+    switch (i.depth) {
+        case 1:
+        case 4:
+        case 8:
+            if (i.palette.isDirect) return false;
+            int size = i.palette.colors.length;
+            return size is 2 || size is 16 || size is 32 || size is 256;
+        case 24:
+        case 32:
+            return i.palette.isDirect;
+        default:
+    }
+    return false;
+}
+int loadFileHeader(LEDataInputStream byteStream) {
+    int[] fileHeader = new int[3];
+    try {
+        fileHeader[0] = byteStream.readShort();
+        fileHeader[1] = byteStream.readShort();
+        fileHeader[2] = byteStream.readShort();
+    } catch (IOException e) {
+        DWT.error(DWT.ERROR_IO, e);
+    }
+    if ((fileHeader[0] !is 0) || (fileHeader[1] !is 1))
+        DWT.error(DWT.ERROR_INVALID_IMAGE);
+    int numIcons = fileHeader[2];
+    if (numIcons <= 0)
+        DWT.error(DWT.ERROR_INVALID_IMAGE);
+    return numIcons;
+}
+int loadFileHeader(LEDataInputStream byteStream, bool hasHeader) {
+    int[] fileHeader = new int[3];
+    try {
+        if (hasHeader) {
+            fileHeader[0] = byteStream.readShort();
+            fileHeader[1] = byteStream.readShort();
+        } else {
+            fileHeader[0] = 0;
+            fileHeader[1] = 1;
+        }
+        fileHeader[2] = byteStream.readShort();
+    } catch (IOException e) {
+        DWT.error(DWT.ERROR_IO, e);
+    }
+    if ((fileHeader[0] !is 0) || (fileHeader[1] !is 1))
+        DWT.error(DWT.ERROR_INVALID_IMAGE);
+    int numIcons = fileHeader[2];
+    if (numIcons <= 0)
+        DWT.error(DWT.ERROR_INVALID_IMAGE);
+    return numIcons;
+}
+ImageData[] loadFromByteStream() {
+    int numIcons = loadFileHeader(inputStream);
+    int[][] headers = loadIconHeaders(numIcons);
+    ImageData[] icons = new ImageData[headers.length];
+    for (int i = 0; i < icons.length; i++) {
+        icons[i] = loadIcon(headers[i]);
+    }
+    return icons;
+}
+/**
+ * Load one icon from the byte stream.
+ */
+ImageData loadIcon(int[] iconHeader) {
+    byte[] infoHeader = loadInfoHeader(iconHeader);
+    WinBMPFileFormat bmpFormat = new WinBMPFileFormat();
+    bmpFormat.inputStream = inputStream;
+    PaletteData palette = bmpFormat.loadPalette(infoHeader);
+    byte[] shapeData = bmpFormat.loadData(infoHeader);
+    int width = (infoHeader[4] & 0xFF) | ((infoHeader[5] & 0xFF) << 8) | ((infoHeader[6] & 0xFF) << 16) | ((infoHeader[7] & 0xFF) << 24);
+    int height = (infoHeader[8] & 0xFF) | ((infoHeader[9] & 0xFF) << 8) | ((infoHeader[10] & 0xFF) << 16) | ((infoHeader[11] & 0xFF) << 24);
+    if (height < 0) height = -height;
+    int depth = (infoHeader[14] & 0xFF) | ((infoHeader[15] & 0xFF) << 8);
+    infoHeader[14] = 1;
+    infoHeader[15] = 0;
+    byte[] maskData = bmpFormat.loadData(infoHeader);
+    maskData = convertPad(maskData, width, height, 1, 4, 2);
+    bitInvertData(maskData, 0, maskData.length);
+    return ImageData.internal_new(
+        width,
+        height,
+        depth,
+        palette,
+        4,
+        shapeData,
+        2,
+        maskData,
+        null,
+        -1,
+        -1,
+        DWT.IMAGE_ICO,
+        0,
+        0,
+        0,
+        0);
+}
+int[][] loadIconHeaders(int numIcons) {
+    int[][] headers = new int[][]( numIcons, 7 );
+    try {
+        for (int i = 0; i < numIcons; i++) {
+            headers[i][0] = inputStream.read();
+            headers[i][1] = inputStream.read();
+            headers[i][2] = inputStream.readShort();
+            headers[i][3] = inputStream.readShort();
+            headers[i][4] = inputStream.readShort();
+            headers[i][5] = inputStream.readInt();
+            headers[i][6] = inputStream.readInt();
+        }
+    } catch (IOException e) {
+        DWT.error(DWT.ERROR_IO, e);
+    }
+    return headers;
+}
+byte[] loadInfoHeader(int[] iconHeader) {
+    int width = iconHeader[0];
+    int height = iconHeader[1];
+    int numColors = iconHeader[2]; // the number of colors is in the low byte, but the high byte must be 0
+    if (numColors is 0) numColors = 256; // this is specified: '00' represents '256' (0x100) colors
+    if ((numColors !is 2) && (numColors !is 8) && (numColors !is 16) &&
+        (numColors !is 32) && (numColors !is 256))
+        DWT.error(DWT.ERROR_INVALID_IMAGE);
+    if (inputStream.getPosition() < iconHeader[6]) {
+        // Seek to the specified offset
+        try {
+            inputStream.skip(iconHeader[6] - inputStream.getPosition());
+        } catch (IOException e) {
+            DWT.error(DWT.ERROR_IO, e);
+            return null;
+        }
+    }
+    byte[] infoHeader = new byte[WinBMPFileFormat.BMPHeaderFixedSize];
+    try {
+        inputStream.read(infoHeader);
+    } catch (IOException e) {
+        DWT.error(DWT.ERROR_IO, e);
+    }
+    if (((infoHeader[12] & 0xFF) | ((infoHeader[13] & 0xFF) << 8)) !is 1)
+        DWT.error(DWT.ERROR_INVALID_IMAGE);
+    int infoWidth = (infoHeader[4] & 0xFF) | ((infoHeader[5] & 0xFF) << 8) | ((infoHeader[6] & 0xFF) << 16) | ((infoHeader[7] & 0xFF) << 24);
+    int infoHeight = (infoHeader[8] & 0xFF) | ((infoHeader[9] & 0xFF) << 8) | ((infoHeader[10] & 0xFF) << 16) | ((infoHeader[11] & 0xFF) << 24);
+    int bitCount = (infoHeader[14] & 0xFF) | ((infoHeader[15] & 0xFF) << 8);
+    if (height is infoHeight && bitCount is 1) height /= 2;
+    if (!((width is infoWidth) && (height * 2 is infoHeight) &&
+        (bitCount is 1 || bitCount is 4 || bitCount is 8 || bitCount is 24 || bitCount is 32)))
+            DWT.error(DWT.ERROR_INVALID_IMAGE);
+    infoHeader[8] = cast(byte)(height & 0xFF);
+    infoHeader[9] = cast(byte)((height >> 8) & 0xFF);
+    infoHeader[10] = cast(byte)((height >> 16) & 0xFF);
+    infoHeader[11] = cast(byte)((height >> 24) & 0xFF);
+    return infoHeader;
+}
+/**
+ * Unload a single icon
+ */
+void unloadIcon(ImageData icon) {
+    int sizeImage = (((icon.width * icon.depth + 31) / 32 * 4) +
+        ((icon.width + 31) / 32 * 4)) * icon.height;
+    try {
+        outputStream.writeInt(WinBMPFileFormat.BMPHeaderFixedSize);
+        outputStream.writeInt(icon.width);
+        outputStream.writeInt(icon.height * 2);
+        outputStream.writeShort(1);
+        outputStream.writeShort(cast(short)icon.depth);
+        outputStream.writeInt(0);
+        outputStream.writeInt(sizeImage);
+        outputStream.writeInt(0);
+        outputStream.writeInt(0);
+        outputStream.writeInt(icon.palette.colors !is null ? icon.palette.colors.length : 0);
+        outputStream.writeInt(0);
+    } catch (IOException e) {
+        DWT.error(DWT.ERROR_IO, e);
+    }
+
+    byte[] rgbs = WinBMPFileFormat.paletteToBytes(icon.palette);
+    try {
+        outputStream.write(rgbs);
+    } catch (IOException e) {
+        DWT.error(DWT.ERROR_IO, e);
+    }
+    unloadShapeData(icon);
+    unloadMaskData(icon);
+}
+/**
+ * Unload the icon header for the given icon, calculating the offset.
+ */
+void unloadIconHeader(ImageData i) {
+    int headerSize = 16;
+    int offset = headerSize + 6;
+    int iconSize = iconSize(i);
+    try {
+        outputStream.write(i.width);
+        outputStream.write(i.height);
+        outputStream.writeShort(i.palette.colors !is null ? i.palette.colors.length : 0);
+        outputStream.writeShort(0);
+        outputStream.writeShort(0);
+        outputStream.writeInt(iconSize);
+        outputStream.writeInt(offset);
+    } catch (IOException e) {
+        DWT.error(DWT.ERROR_IO, e);
+    }
+}
+void unloadIntoByteStream(ImageLoader loader) {
+    /* We do not currently support writing multi-image ico,
+     * so we use the first image data in the loader's array. */
+    ImageData image = loader.data[0];
+    if (!isValidIcon(image))
+        DWT.error(DWT.ERROR_INVALID_IMAGE);
+    try {
+        outputStream.writeShort(0);
+        outputStream.writeShort(1);
+        outputStream.writeShort(1);
+    } catch (IOException e) {
+        DWT.error(DWT.ERROR_IO, e);
+    }
+    unloadIconHeader(image);
+    unloadIcon(image);
+}
+/**
+ * Unload the mask data for an icon. The data is flipped vertically
+ * and inverted.
+ */
+void unloadMaskData(ImageData icon) {
+    ImageData mask = icon.getTransparencyMask();
+    int bpl = (icon.width + 7) / 8;
+    int pad = mask.scanlinePad;
+    int srcBpl = (bpl + pad - 1) / pad * pad;
+    int destBpl = (bpl + 3) / 4 * 4;
+    byte[] buf = new byte[destBpl];
+    int offset = (icon.height - 1) * srcBpl;
+    byte[] data = mask.data;
+    try {
+        for (int i = 0; i < icon.height; i++) {
+            System.arraycopy(data, offset, buf, 0, bpl);
+            bitInvertData(buf, 0, bpl);
+            outputStream.write(buf, 0, destBpl);
+            offset -= srcBpl;
+        }
+    } catch (IOException e) {
+        DWT.error(DWT.ERROR_IO, e);
+    }
+}
+/**
+ * Unload the shape data for an icon. The data is flipped vertically.
+ */
+void unloadShapeData(ImageData icon) {
+    int bpl = (icon.width * icon.depth + 7) / 8;
+    int pad = icon.scanlinePad;
+    int srcBpl = (bpl + pad - 1) / pad * pad;
+    int destBpl = (bpl + 3) / 4 * 4;
+    byte[] buf = new byte[destBpl];
+    int offset = (icon.height - 1) * srcBpl;
+    byte[] data = icon.data;
+    try {
+        for (int i = 0; i < icon.height; i++) {
+            System.arraycopy(data, offset, buf, 0, bpl);
+            outputStream.write(buf, 0, destBpl);
+            offset -= srcBpl;
+        }
+    } catch (IOException e) {
+        DWT.error(DWT.ERROR_IO, e);
+    }
+}
+}