# HG changeset patch # User Frank Benoit # Date 1201086106 -3600 # Node ID 57151e2793a2b1c64c7847207683207b9097d69e # Parent 8ac7199abbc46aef730841772e497ab6cd448762 More common modules from dwt-linux diff -r 8ac7199abbc4 -r 57151e2793a2 doc/Common.txt --- 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 + + diff -r 8ac7199abbc4 -r 57151e2793a2 dwt/internal/CloneableCompatibility.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 + *******************************************************************************/ +module dwt.internal.CloneableCompatibility; + +//PORTING_TYPE +interface Cloneable{} + +/** + * This interface is the cross-platform version of the + * java.lang.Cloneable interface. + *

+ * 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. + *

+ *

+ * Note: java.lang.Cloneable is not part of CLDC. + *

+ */ +public interface CloneableCompatibility : Cloneable { +} diff -r 8ac7199abbc4 -r 57151e2793a2 dwt/internal/Compatibility.d --- /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 + *******************************************************************************/ +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. + *

+ * It is part of our effort to provide support for both J2SE + * and J2ME platforms. + *

+ *

+ * IMPORTANT: some of the methods have been modified from their + * J2SE parents. Refer to the description of each method for + * specific changes. + *

+ *
    + *
  • Exceptions thrown may differ since J2ME's set of + * exceptions is a subset of J2SE's one. + *
  • + *
  • The range of the mathematic functions is subject to + * change. + *
  • + *
+ */ +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). + *

+ * IMPORTANT: the j2me version has an additional restriction on + * the argument. length must be between -32767 and 32767 (inclusive). + *

+ * + * @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). + *

+ * IMPORTANT: the j2me version has an additional restriction on + * the argument. length must be between -32767 and 32767 (inclusive). + *

+ * + * @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. + *

+ * 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). + *

+ * + * @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
    + *
  • ERROR_INVALID_RANGE - if the argument is not between 0 and 30 (inclusive)
  • + *
+ */ +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 null + * @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. + *

+ * The new process inherits the environment of the caller. + *

+ * + * @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. + *

+ * The new process inherits the environment of the caller. + *

+ * + * @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. + *

+ * Note that this is not available on CLDC. + *

+ */ +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; +} + +} diff -r 8ac7199abbc4 -r 57151e2793a2 dwt/internal/DWTEventListener.d --- /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 + *******************************************************************************/ +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. + *

+ * 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. + *

+ *

+ * Note: java.util.EventListener is not part of CDC and CLDC. + *

+ */ +public interface DWTEventListener : EventListener { +} diff -r 8ac7199abbc4 -r 57151e2793a2 dwt/internal/DWTEventObject.d --- /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 + *******************************************************************************/ +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. + *

+ * 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. + *

+ *

+ * Note: java.util.EventObject is not part of CDC and CLDC. + *

+ */ +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); +} +} diff -r 8ac7199abbc4 -r 57151e2793a2 dwt/internal/Library.d --- /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 + *******************************************************************************/ +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 + * name is used to load the library. If this fails, + * name 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 + * name is used to load the library. If this fails, + * name 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$ +} ++/ +} diff -r 8ac7199abbc4 -r 57151e2793a2 dwt/internal/Lock.d --- /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 + *******************************************************************************/ +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(); + } + } + } +} +} diff -r 8ac7199abbc4 -r 57151e2793a2 dwt/internal/SerializableCompatibility.d --- /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 + *******************************************************************************/ +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. + *

+ * 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. + *

+ *

+ * Note: java.io.Serializable is not part of CLDC. + *

+ */ +public interface SerializableCompatibility : Serializable { +} diff -r 8ac7199abbc4 -r 57151e2793a2 dwt/internal/image/FileFormat.d --- /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 + *******************************************************************************/ +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); + } +} +} diff -r 8ac7199abbc4 -r 57151e2793a2 dwt/internal/image/GIFFileFormat.d --- /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 + *******************************************************************************/ +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> 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); + } + } +} diff -r 8ac7199abbc4 -r 57151e2793a2 dwt/internal/image/JPEGAppn.d --- /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 + *******************************************************************************/ +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; + } +} diff -r 8ac7199abbc4 -r 57151e2793a2 dwt/internal/image/JPEGArithmeticConditioningTable.d --- /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 + *******************************************************************************/ +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; + } +} diff -r 8ac7199abbc4 -r 57151e2793a2 dwt/internal/image/JPEGComment.d --- /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 + *******************************************************************************/ +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; + } +} diff -r 8ac7199abbc4 -r 57151e2793a2 dwt/internal/image/JPEGDecoder.d --- /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 + *******************************************************************************/ +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< 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; + /* Cb=>B value is nearest int to 1.77200 * x */ + cconvert.Cb_b_tab[i] = (cast(int)(1.77200f * (1<> SCALEBITS; + /* Cr=>G value is scaled-up -0.71414 * x */ + cconvert.Cr_g_tab[i] = (cast(int)(- (0.71414f * (1<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<= 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< 0 && pred >= (1<= 0) { + pred = (((Q10<<7) + num) / (Q10<<8)); + if (Al > 0 && pred >= (1< 0 && pred >= (1<= 0) { + pred = (((Q20<<7) + num) / (Q20<<8)); + if (Al > 0 && pred >= (1< 0 && pred >= (1<= 0) { + pred = (((Q11<<7) + num) / (Q11<<8)); + if (Al > 0 && pred >= (1< 0 && pred >= (1<= 0) { + pred = (((Q02<<7) + num) / (Q02<<8)); + if (Al > 0 && pred >= (1< 0 && pred >= (1<= 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]; +} + +} diff -r 8ac7199abbc4 -r 57151e2793a2 dwt/internal/image/JPEGEndOfImage.d --- /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 + *******************************************************************************/ +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; + } +} diff -r 8ac7199abbc4 -r 57151e2793a2 dwt/internal/image/JPEGFileFormat.d --- /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 + *******************************************************************************/ +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); + } +} +} diff -r 8ac7199abbc4 -r 57151e2793a2 dwt/internal/image/JPEGFixedSizeSegment.d --- /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 + *******************************************************************************/ +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) { + } +} diff -r 8ac7199abbc4 -r 57151e2793a2 dwt/internal/image/JPEGFrameHeader.d --- /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 + *******************************************************************************/ +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; + } +} diff -r 8ac7199abbc4 -r 57151e2793a2 dwt/internal/image/JPEGHuffmanTable.d --- /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 + *******************************************************************************/ +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; +} +} diff -r 8ac7199abbc4 -r 57151e2793a2 dwt/internal/image/JPEGQuantizationTable.d --- /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 + *******************************************************************************/ +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; +} +} diff -r 8ac7199abbc4 -r 57151e2793a2 dwt/internal/image/JPEGRestartInterval.d --- /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 + *******************************************************************************/ +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; + } +} diff -r 8ac7199abbc4 -r 57151e2793a2 dwt/internal/image/JPEGScanHeader.d --- /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 + *******************************************************************************/ +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; +} + +} diff -r 8ac7199abbc4 -r 57151e2793a2 dwt/internal/image/JPEGSegment.d --- /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 + *******************************************************************************/ +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; + } + } +} diff -r 8ac7199abbc4 -r 57151e2793a2 dwt/internal/image/JPEGStartOfImage.d --- /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 + *******************************************************************************/ +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; + } +} diff -r 8ac7199abbc4 -r 57151e2793a2 dwt/internal/image/JPEGVariableSizeSegment.d --- /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 + *******************************************************************************/ +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); + } + } +} diff -r 8ac7199abbc4 -r 57151e2793a2 dwt/internal/image/LEDataInputStream.d --- /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 + *******************************************************************************/ +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 buf. 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 length bytes from this LEDataInputStream and + * stores them in byte array buffer starting at offset. + *

+ * 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 count. + *

+ * @param buffer the byte array in which to store the read bytes. + * @param offset the offset in buffer to store the read bytes. + * @param length the maximum number of bytes to store in buffer. + * + * @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 b. + *

+ * 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 b, + * an IOException will be thrown and no byte will be pushed back. + *

+ * + * @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); + } +} diff -r 8ac7199abbc4 -r 57151e2793a2 dwt/internal/image/LEDataOutputStream.d --- /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 + *******************************************************************************/ +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); +} +} diff -r 8ac7199abbc4 -r 57151e2793a2 dwt/internal/image/LZWCodec.d --- /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 + *******************************************************************************/ +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); + } +} +} diff -r 8ac7199abbc4 -r 57151e2793a2 dwt/internal/image/LZWNode.d --- /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 + *******************************************************************************/ +module dwt.internal.image.LZWNode; + + +final class LZWNode { + public LZWNode left, right, children; + public int code, prefix, suffix; +} diff -r 8ac7199abbc4 -r 57151e2793a2 dwt/internal/image/OS2BMPFileFormat.d --- /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 + *******************************************************************************/ +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; + } +} + +} diff -r 8ac7199abbc4 -r 57151e2793a2 dwt/internal/image/PNGFileFormat.d --- /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 + *******************************************************************************/ +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; // + if ((signature[5] & 0xFF) !is 10) return false; // + if ((signature[6] & 0xFF) !is 26) return false; // + if ((signature[7] & 0xFF) !is 10) return false; // + 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: + } +} + +} diff -r 8ac7199abbc4 -r 57151e2793a2 dwt/internal/image/PngChunk.d --- /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 + *******************************************************************************/ +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; +} + +} diff -r 8ac7199abbc4 -r 57151e2793a2 dwt/internal/image/PngChunkReader.d --- /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 + *******************************************************************************/ +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; +} + +} diff -r 8ac7199abbc4 -r 57151e2793a2 dwt/internal/image/PngDecodingDataStream.d --- /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 + *******************************************************************************/ +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(); +} + +} diff -r 8ac7199abbc4 -r 57151e2793a2 dwt/internal/image/PngDeflater.d --- /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 + *******************************************************************************/ +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(); + +} + +} diff -r 8ac7199abbc4 -r 57151e2793a2 dwt/internal/image/PngEncoder.d --- /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 + *******************************************************************************/ +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); + + } + +} + +} diff -r 8ac7199abbc4 -r 57151e2793a2 dwt/internal/image/PngFileReadState.d --- /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 + *******************************************************************************/ +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; +} diff -r 8ac7199abbc4 -r 57151e2793a2 dwt/internal/image/PngHuffmanTable.d --- /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 + *******************************************************************************/ +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; +} + +} diff -r 8ac7199abbc4 -r 57151e2793a2 dwt/internal/image/PngHuffmanTables.d --- /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 + *******************************************************************************/ +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 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); +} + +} diff -r 8ac7199abbc4 -r 57151e2793a2 dwt/internal/image/PngIdatChunk.d --- /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 + *******************************************************************************/ +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]; +} + +} diff -r 8ac7199abbc4 -r 57151e2793a2 dwt/internal/image/PngIendChunk.d --- /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 + *******************************************************************************/ +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); +} + +} diff -r 8ac7199abbc4 -r 57151e2793a2 dwt/internal/image/PngIhdrChunk.d --- /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 + *******************************************************************************/ +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; + } +} + + + +} diff -r 8ac7199abbc4 -r 57151e2793a2 dwt/internal/image/PngInputStream.d --- /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 + *******************************************************************************/ +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; +} +} diff -r 8ac7199abbc4 -r 57151e2793a2 dwt/internal/image/PngLzBlockReader.d --- /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 + *******************************************************************************/ +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; + } +} + +} diff -r 8ac7199abbc4 -r 57151e2793a2 dwt/internal/image/PngPlteChunk.d --- /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 + *******************************************************************************/ +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 ); +} + +} diff -r 8ac7199abbc4 -r 57151e2793a2 dwt/internal/image/PngTrnsChunk.d --- /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 + *******************************************************************************/ +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; +} +} diff -r 8ac7199abbc4 -r 57151e2793a2 dwt/internal/image/TIFFDirectory.d --- /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 + *******************************************************************************/ +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); +} + +} diff -r 8ac7199abbc4 -r 57151e2793a2 dwt/internal/image/TIFFFileFormat.d --- /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 + *******************************************************************************/ +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); + } +} + +} diff -r 8ac7199abbc4 -r 57151e2793a2 dwt/internal/image/TIFFModifiedHuffmanCodec.d --- /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 + *******************************************************************************/ +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++; + } +} + +} diff -r 8ac7199abbc4 -r 57151e2793a2 dwt/internal/image/TIFFRandomFileAccess.d --- /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 + *******************************************************************************/ +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; +} + +} diff -r 8ac7199abbc4 -r 57151e2793a2 dwt/internal/image/WinBMPFileFormat.d --- /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 + *******************************************************************************/ +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; + } +} + +} diff -r 8ac7199abbc4 -r 57151e2793a2 dwt/internal/image/WinICOFileFormat.d --- /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 + *******************************************************************************/ +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); + } +} +}