Index: src/org/openstreetmap/josm/gui/layer/geoimage/ImageDisplay.java
===================================================================
--- src/org/openstreetmap/josm/gui/layer/geoimage/ImageDisplay.java	(revision 13174)
+++ src/org/openstreetmap/josm/gui/layer/geoimage/ImageDisplay.java	(working copy)
@@ -22,11 +22,15 @@
 import java.awt.geom.AffineTransform;
 import java.awt.geom.Rectangle2D;
 import java.awt.image.BufferedImage;
+import java.awt.image.ImageObserver;
 import java.io.File;
+import java.io.FileInputStream;
 
 import javax.swing.JComponent;
 import javax.swing.SwingUtilities;
 
+import org.libjpegturbo.turbojpeg.TJDecompressor;
+import org.libjpegturbo.turbojpeg.TJException;
 import org.openstreetmap.josm.data.preferences.BooleanProperty;
 import org.openstreetmap.josm.data.preferences.DoubleProperty;
 import org.openstreetmap.josm.spi.preferences.Config;
@@ -112,6 +116,10 @@
     public static class VisRect extends Rectangle {
         private final Rectangle init;
 
+        /** set when this {@code VisRect} is updated by a mouse drag operation and
+         * unset on mouse release **/
+        public boolean isDragUpdate;
+
         /**
          * Constructs a new {@code VisRect}.
          * @param     x the specified X coordinate
@@ -124,6 +132,14 @@
             init = new Rectangle(this);
         }
 
+        /**
+         * Constructs a new {@code VisRect}.
+         * @param     x the specified X coordinate
+         * @param     y the specified Y coordinate
+         * @param     width  the width of the rectangle
+         * @param     height the height of the rectangle
+         * @param     peer share full bounds with this peer {@code VisRect}
+         */
         public VisRect(int x, int y, int width, int height, VisRect peer) {
             super(x, y, width, height);
             init = peer.init;
@@ -145,19 +161,23 @@
             this(0, 0, 0, 0);
         }
 
+        @SuppressWarnings("javadoc")
         public boolean isFullView() {
             return init.equals(this);
         }
 
+        @SuppressWarnings("javadoc")
         public boolean isFullView1D() {
             return (init.x == x && init.width == width)
                 || (init.y == y && init.height == height);
         }
 
+        @SuppressWarnings("javadoc")
         public void reset() {
             setBounds(init);
         }
 
+        @SuppressWarnings("javadoc")
         public void checkRectPos() {
             if (x < 0) {
                 x = 0;
@@ -173,6 +193,7 @@
             }
         }
 
+        @SuppressWarnings("javadoc")
         public void checkRectSize() {
             if (width > init.width) {
                 width = init.width;
@@ -182,6 +203,7 @@
             }
         }
 
+        @SuppressWarnings("javadoc")
         public void checkPointInside(Point p) {
             if (p.x < x) {
                 p.x = x;
@@ -199,10 +221,12 @@
     }
 
     /** The thread that reads the images. */
-    private class LoadImageRunnable implements Runnable {
+    private class LoadImageRunnable implements Runnable, ImageObserver {
 
         private final File file;
         private final int orientation;
+        private int width;
+        private int height;
 
         LoadImageRunnable(File file, Integer orientation) {
             this.file = file;
@@ -210,28 +234,141 @@
         }
 
         @Override
+        public boolean imageUpdate(Image img, int infoflags, int x, int y, int width, int height) {
+            if (((infoflags & ImageObserver.WIDTH) == ImageObserver.WIDTH) &&
+                ((infoflags & ImageObserver.HEIGHT) == ImageObserver.HEIGHT)) {
+                this.width = width;
+                this.height = height;
+                synchronized (this) {
+                    this.notify();
+                    return false;
+                }
+            }
+            return true;
+        }
+
+        @Override
         public void run() {
             Image img = Toolkit.getDefaultToolkit().createImage(file.getPath());
-            tracker.addImage(img, 1);
 
-            // Wait for the end of loading
-            while (!tracker.checkID(1, true)) {
-                if (this.file != ImageDisplay.this.file) {
-                    // The file has changed
-                    tracker.removeImage(img);
-                    return;
+            synchronized (this) {
+                width = -1;
+                img.getWidth(this);
+                img.getHeight(this);
+
+                while (width < 0) {
+                    try {
+                        this.wait();
+                        if (width < 0) {
+                            errorLoading = true;
+                            return;
+                        }
+                    } catch (InterruptedException e) {
+                        e.printStackTrace();
+                    }
+                }
+            }
+
+            long allocatedMem = Runtime.getRuntime().totalMemory() -
+                    Runtime.getRuntime().freeMemory();
+            long mem = Runtime.getRuntime().maxMemory()-allocatedMem;
+
+            if (mem > ((long)width*height*4)*2) {
+                Logging.info("Loading "+file.getPath()+" using default toolkit");
+                tracker.addImage(img, 1);
+
+                // Wait for the end of loading
+                while (!tracker.checkID(1, true)) {
+                    if (this.file != ImageDisplay.this.file) {
+                        // The file has changed
+                        tracker.removeImage(img);
+                        return;
+                    }
+                    try {
+                        Thread.sleep(5);
+                    } catch (InterruptedException e) {
+                        Logging.warn("InterruptedException in "+getClass().getSimpleName()+
+                                " while loading image "+file.getPath());
+                        Thread.currentThread().interrupt();
+                    }
                 }
+                if (tracker.isErrorID(1)) {
+                    img = null;
+                    System.gc();
+                }
+            } else {
+                img = null;
+            }
+
+            if (img == null) {
                 try {
-                    Thread.sleep(5);
-                } catch (InterruptedException e) {
-                    Logging.warn("InterruptedException in "+getClass().getSimpleName()+" while loading image "+file.getPath());
-                    Thread.currentThread().interrupt();
+                    if (!file.getPath().matches(".*\\.[jJ][pP][eE]?[gG]$")) {
+                        throw new TJException("file ending indicates non-jpeg data");
+                    }
+
+                    // as of JDK8 javax.imageio.plugins.jpeg.JPEGImageReadParam.canSetSourceRenderSize() is
+                    // always false, so retry loading a scaled version computed by turbojpeg system library
+                    // if it was built with java support
+                    TJDecompressor tjd;
+                    try {
+                        tjd = new TJDecompressor();
+                    } catch (java.lang.UnsatisfiedLinkError le) {
+                        Logging.warn("turbojpeg not found in "+System.getProperty("java.library.path"));
+                        throw new TJException("library not found");
+                    }
+
+                    Logging.info("Loading "+file.getPath()+" ("+width+"x"+height+") using turbojpeg");
+                    FileInputStream fis = new FileInputStream(file);
+                    if (fis.available()>0) {
+                        byte[] buf = new byte[fis.available()];
+                        int l = fis.read(buf);
+                        fis.close();
+                        tjd.setSourceImage(buf, l);
+
+                        allocatedMem = Runtime.getRuntime().totalMemory() -
+                                Runtime.getRuntime().freeMemory();
+                        mem = Runtime.getRuntime().maxMemory()-allocatedMem;
+
+                        BufferedImage bi = null;
+                        while (width>0 && height>0) {
+                            if (mem > ((long)width*height*4)*2) {
+                                try {
+                                    bi = new BufferedImage(
+                                            tjd.getScaledWidth(width, height),
+                                            tjd.getScaledHeight(width, height),
+                                            BufferedImage.TYPE_INT_RGB);
+                                    tjd.decompress(bi, 0);
+                                    // store final width and height as actual
+                                    // values used by TJ may have been smaller,
+                                    // store them not before decoding succeeded
+                                    width = tjd.getScaledWidth(width, height);
+                                    height = tjd.getScaledHeight(width, height);
+                                    break;
+                                } catch (java.lang.OutOfMemoryError oom) {
+                                    bi = null;
+                                    System.gc();
+                                } catch (Exception e) {
+                                    e.printStackTrace();
+                                    bi = null;
+                                }
+                            }
+                            width = (width*4)/5;
+                            height = (height*4)/5;
+                        }
+
+                        tjd.close();
+                        tjd = null;
+                        img = bi;
+                    }
+                } catch (Exception ex) {
+                    ex.printStackTrace();
+                    img = null;
                 }
             }
 
-            boolean error = tracker.isErrorID(1);
-            if (img.getWidth(null) < 0 || img.getHeight(null) < 0) {
-                error = true;
+            if (img == null || width <= 0 || height <= 0) {
+                tracker.removeImage(img);
+                img = null;
             }
 
             synchronized (ImageDisplay.this) {
@@ -241,36 +378,36 @@
                     return;
                 }
 
-                if (!error) {
-                    ImageDisplay.this.image = img;
-                    visibleRect = new VisRect(0, 0, img.getWidth(null), img.getHeight(null));
-
-                    final int w = (int) visibleRect.getWidth();
-                    final int h = (int) visibleRect.getHeight();
-
+                if (img != null) {
+                    boolean switchedDim = false;
                     if (ExifReader.orientationNeedsCorrection(orientation)) {
-                        final int hh, ww;
                         if (ExifReader.orientationSwitchesDimensions(orientation)) {
-                            ww = h;
-                            hh = w;
-                        } else {
-                            ww = w;
-                            hh = h;
+                            width = img.getHeight(null);
+                            height = img.getWidth(null);
+                            switchedDim = true;
                         }
-                        final BufferedImage rot = new BufferedImage(ww, hh, BufferedImage.TYPE_INT_RGB);
-                        final AffineTransform xform = ExifReader.getRestoreOrientationTransform(orientation, w, h);
+                        final BufferedImage rot = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
+                        final AffineTransform xform = ExifReader.getRestoreOrientationTransform(orientation,
+                                img.getWidth(null), img.getHeight(null));
                         final Graphics2D g = rot.createGraphics();
-                        g.drawImage(image, xform, null);
+                        g.drawImage(img, xform, null);
                         g.dispose();
-
-                        visibleRect.setSize(ww, hh);
-                        image.flush();
-                        ImageDisplay.this.image = rot;
+                        img.flush();
+                        img = rot;
                     }
+
+                    ImageDisplay.this.image = img;
+                    visibleRect = new VisRect(0, 0, width, height);
+
+                    Logging.info("Loaded "+file.getPath()+
+                            " with dimensions "+width+"x"+height+
+                            " mem(prev-avail="+mem/1024/1024+"m,taken="+
+                            width*height*4/1024/1024+"m)"+
+                            " exifOrientationSwitchedDimension="+switchedDim);
                 }
 
                 selectedRect = null;
-                errorLoading = error;
+                errorLoading = (img == null);
             }
             tracker.removeImage(img);
             ImageDisplay.this.repaint();
@@ -473,6 +610,7 @@
 
             if (mouseIsDragging(e)) {
                 Point p = comp2imgCoord(visibleRect, e.getX(), e.getY(), getSize());
+                visibleRect.isDragUpdate = true;
                 visibleRect.x += mousePointInImg.x - p.x;
                 visibleRect.y += mousePointInImg.y - p.y;
                 visibleRect.checkRectPos();
@@ -503,9 +641,6 @@
 
         @Override
         public void mouseReleased(MouseEvent e) {
-            if (!mouseIsZoomSelecting(e) || selectedRect == null)
-                return;
-
             File file;
             Image image;
 
@@ -514,47 +649,56 @@
                 image = ImageDisplay.this.image;
             }
 
-            if (image == null) {
+            if (image == null)
                 return;
+
+            if (mouseIsDragging(e)) {
+                visibleRect.isDragUpdate = false;
             }
 
-            int oldWidth = selectedRect.width;
-            int oldHeight = selectedRect.height;
+            if (mouseIsZoomSelecting(e) && selectedRect != null) {
+                int oldWidth = selectedRect.width;
+                int oldHeight = selectedRect.height;
 
-            // Check that the zoom doesn't exceed MAX_ZOOM:1
-            if (selectedRect.width < getSize().width / MAX_ZOOM.get()) {
-                selectedRect.width = (int) (getSize().width / MAX_ZOOM.get());
-            }
-            if (selectedRect.height < getSize().height / MAX_ZOOM.get()) {
-                selectedRect.height = (int) (getSize().height / MAX_ZOOM.get());
-            }
+                // Check that the zoom doesn't exceed MAX_ZOOM:1
+                if (selectedRect.width < getSize().width / MAX_ZOOM.get()) {
+                    selectedRect.width = (int) (getSize().width / MAX_ZOOM.get());
+                }
+                if (selectedRect.height < getSize().height / MAX_ZOOM.get()) {
+                    selectedRect.height = (int) (getSize().height / MAX_ZOOM.get());
+                }
 
-            // Set the same ratio for the visible rectangle and the display area
-            int hFact = selectedRect.height * getSize().width;
-            int wFact = selectedRect.width * getSize().height;
-            if (hFact > wFact) {
-                selectedRect.width = hFact / getSize().height;
-            } else {
-                selectedRect.height = wFact / getSize().width;
-            }
+                // Set the same ratio for the visible rectangle and the display area
+                int hFact = selectedRect.height * getSize().width;
+                int wFact = selectedRect.width * getSize().height;
+                if (hFact > wFact) {
+                    selectedRect.width = hFact / getSize().height;
+                } else {
+                    selectedRect.height = wFact / getSize().width;
+                }
 
-            // Keep the center of the selection
-            if (selectedRect.width != oldWidth) {
-                selectedRect.x -= (selectedRect.width - oldWidth) / 2;
-            }
-            if (selectedRect.height != oldHeight) {
-                selectedRect.y -= (selectedRect.height - oldHeight) / 2;
-            }
+                // Keep the center of the selection
+                if (selectedRect.width != oldWidth) {
+                    selectedRect.x -= (selectedRect.width - oldWidth) / 2;
+                }
+                if (selectedRect.height != oldHeight) {
+                    selectedRect.y -= (selectedRect.height - oldHeight) / 2;
+                }
 
-            selectedRect.checkRectSize();
-            selectedRect.checkRectPos();
+                selectedRect.checkRectSize();
+                selectedRect.checkRectPos();
+            }
 
             synchronized (ImageDisplay.this) {
                 if (file == ImageDisplay.this.file) {
-                    ImageDisplay.this.visibleRect.setBounds(selectedRect);
+                    if (selectedRect == null) {
+                        ImageDisplay.this.visibleRect = visibleRect;
+                    } else {
+                        ImageDisplay.this.visibleRect.setBounds(selectedRect);
+                        selectedRect = null;
+                    }
                 }
             }
-            selectedRect = null;
             ImageDisplay.this.repaint();
         }
 
@@ -586,6 +730,11 @@
         preferenceChanged(null);
     }
 
+    /**
+     * Sets a new source image to be displayed by this {@code ImageDisplay}.
+     * @param file new source image
+     * @param orientation orientation of new source (landscape, portrait, upside-down, etc.)
+     */
     public void setImage(File file, Integer orientation) {
         synchronized (this) {
             this.file = file;
@@ -650,16 +799,27 @@
             Rectangle target = calculateDrawImageRectangle(visibleRect, size);
             double scale = target.width / (double) r.width; // pixel ratio is 1:1
 
-            if (selectedRect == null && bilinLower < scale && scale < bilinUpper) {
-                BufferedImage bi = ImageProvider.toBufferedImage(image, r);
-                r.x = r.y = 0;
+            if (selectedRect == null && !visibleRect.isDragUpdate &&
+                bilinLower < scale && scale < bilinUpper) {
+                try {
+                    BufferedImage bi = ImageProvider.toBufferedImage(image, r);
+                    if (bi != null) {
+                        r.x = r.y = 0;
 
-                // See https://community.oracle.com/docs/DOC-983611 - The Perils of Image.getScaledInstance()
-                // Pre-scale image when downscaling by more than two times to avoid aliasing from default algorithm
-                image = ImageProvider.createScaledImage(bi, target.width, target.height,
-                            RenderingHints.VALUE_INTERPOLATION_BILINEAR);
-                r.width = target.width;
-                r.height = target.height;
+                        // See https://community.oracle.com/docs/DOC-983611 - The Perils of Image.getScaledInstance()
+                        // Pre-scale image when downscaling by more than two times to avoid aliasing from default algorithm
+                        bi = ImageProvider.createScaledImage(bi, target.width, target.height,
+                                RenderingHints.VALUE_INTERPOLATION_BILINEAR);
+                        r.width = target.width;
+                        r.height = target.height;
+                        image = bi;
+                    }
+                } catch (java.lang.OutOfMemoryError oom) {
+                    // fall-back to the non-bilinear scaler
+                    r.x = visibleRect.x;
+                    r.y = visibleRect.y;
+                    System.gc();
+                }
             } else {
                 // if target and r cause drawImage to scale image region to a tmp buffer exceeding
                 // its bounds, it will silently fail; crop with r first in such cases
@@ -797,6 +957,11 @@
         return new VisRect(x + compRect.x, y + compRect.y, w, h, imgRect);
     }
 
+    /**
+     * Make the current image either scale to fit inside this component,
+     * or show a portion of image (1:1), if the image size is larger than
+     * the component size.
+     */
     public void zoomBestFitOrOne() {
         File file;
         Image image;
Index: src/org/libjpegturbo/turbojpeg/TJLoader.java
===================================================================
--- src/org/libjpegturbo/turbojpeg/TJLoader.java	(revision 0)
+++ src/org/libjpegturbo/turbojpeg/TJLoader.java	(revision 0)
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C)2011 D. R. Commander.  All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright notice,
+ *   this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright notice,
+ *   this list of conditions and the following disclaimer in the documentation
+ *   and/or other materials provided with the distribution.
+ * - Neither the name of the libjpeg-turbo Project nor the names of its
+ *   contributors may be used to endorse or promote products derived from this
+ *   software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS",
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.libjpegturbo.turbojpeg;
+
+import java.util.Arrays;
+import java.util.List;
+
+import org.openstreetmap.josm.tools.Logging;
+
+final class TJLoader {
+    static void load() {
+        List<String> libs = Arrays.asList("turbojpeg", "jpeg", "jpegturbo");
+        List<String> sfxs = Arrays.asList("so");
+        List<String> pfxs = Arrays.asList("/usr/lib", "/usr/lib64", "/usr/lib32",
+                "/opt/libjpeg-turbo/lib64", "/opt/libjpeg-turbo/lib32");
+        String os = System.getProperty("os.name").toLowerCase();
+        if (os.indexOf("mac") >= 0)
+            sfxs.add("jnilib");
+
+        for (String lib : libs) {
+            try {
+                System.loadLibrary(lib);
+                return;
+            } catch (java.lang.UnsatisfiedLinkError e) {
+            }
+            for (String s : sfxs) {
+                for (String p : pfxs) {
+                    try {
+                        System.load(p + "/lib" + lib + "." + s);
+                        return;
+                    } catch (java.lang.UnsatisfiedLinkError e2) {
+                    }
+                }
+            }
+        }
+        Logging.warn("turbojpeg jni library not found or not loaded");
+    }
+}
Index: src/org/libjpegturbo/turbojpeg/TJ.java
===================================================================
--- src/org/libjpegturbo/turbojpeg/TJ.java	(revision 0)
+++ src/org/libjpegturbo/turbojpeg/TJ.java	(revision 0)
@@ -0,0 +1,513 @@
+/*
+ * Copyright (C)2011-2013 D. R. Commander.  All Rights Reserved.
+ * Copyright (C)2015 Viktor Szathmáry.  All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright notice,
+ *   this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright notice,
+ *   this list of conditions and the following disclaimer in the documentation
+ *   and/or other materials provided with the distribution.
+ * - Neither the name of the libjpeg-turbo Project nor the names of its
+ *   contributors may be used to endorse or promote products derived from this
+ *   software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS",
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.libjpegturbo.turbojpeg;
+
+/**
+ * TurboJPEG utility class (cannot be instantiated)
+ */
+public final class TJ {
+
+
+  /**
+   * The number of chrominance subsampling options
+   */
+  public static final int NUMSAMP   = 6;
+  /**
+   * 4:4:4 chrominance subsampling (no chrominance subsampling).  The JPEG
+   * or YUV image will contain one chrominance component for every pixel in the
+   * source image.
+   */
+  public static final int SAMP_444  = 0;
+  /**
+   * 4:2:2 chrominance subsampling.  The JPEG or YUV image will contain one
+   * chrominance component for every 2x1 block of pixels in the source image.
+   */
+  public static final int SAMP_422  = 1;
+  /**
+   * 4:2:0 chrominance subsampling.  The JPEG or YUV image will contain one
+   * chrominance component for every 2x2 block of pixels in the source image.
+   */
+  public static final int SAMP_420  = 2;
+  /**
+   * Grayscale.  The JPEG or YUV image will contain no chrominance components.
+   */
+  public static final int SAMP_GRAY = 3;
+  /**
+   * 4:4:0 chrominance subsampling.  The JPEG or YUV image will contain one
+   * chrominance component for every 1x2 block of pixels in the source image.
+   * Note that 4:4:0 subsampling is not fully accelerated in libjpeg-turbo.
+   */
+  public static final int SAMP_440  = 4;
+  /**
+   * 4:1:1 chrominance subsampling.  The JPEG or YUV image will contain one
+   * chrominance component for every 4x1 block of pixels in the source image.
+   * JPEG images compressed with 4:1:1 subsampling will be almost exactly the
+   * same size as those compressed with 4:2:0 subsampling, and in the
+   * aggregate, both subsampling methods produce approximately the same
+   * perceptual quality.  However, 4:1:1 is better able to reproduce sharp
+   * horizontal features.  Note that 4:1:1 subsampling is not fully accelerated
+   * in libjpeg-turbo.
+   */
+  public static final int SAMP_411  = 5;
+
+
+  /**
+   * Returns the MCU block width for the given level of chrominance
+   * subsampling.
+   *
+   * @param subsamp the level of chrominance subsampling (one of
+   * <code>SAMP_*</code>)
+   *
+   * @return the MCU block width for the given level of chrominance
+   * subsampling.
+   */
+  public static int getMCUWidth(int subsamp) {
+    checkSubsampling(subsamp);
+    return mcuWidth[subsamp];
+  }
+
+  private static final int[] mcuWidth = {
+    8, 16, 16, 8, 8, 32
+  };
+
+
+  /**
+   * Returns the MCU block height for the given level of chrominance
+   * subsampling.
+   *
+   * @param subsamp the level of chrominance subsampling (one of
+   * <code>SAMP_*</code>)
+   *
+   * @return the MCU block height for the given level of chrominance
+   * subsampling.
+   */
+  public static int getMCUHeight(int subsamp) {
+    checkSubsampling(subsamp);
+    return mcuHeight[subsamp];
+  }
+
+  private static final int[] mcuHeight = {
+    8, 8, 16, 8, 16, 8
+  };
+
+
+  /**
+   * The number of pixel formats
+   */
+  public static final int NUMPF   = 12;
+  /**
+   * RGB pixel format.  The red, green, and blue components in the image are
+   * stored in 3-byte pixels in the order R, G, B from lowest to highest byte
+   * address within each pixel.
+   */
+  public static final int PF_RGB  = 0;
+  /**
+   * BGR pixel format.  The red, green, and blue components in the image are
+   * stored in 3-byte pixels in the order B, G, R from lowest to highest byte
+   * address within each pixel.
+   */
+  public static final int PF_BGR  = 1;
+  /**
+   * RGBX pixel format.  The red, green, and blue components in the image are
+   * stored in 4-byte pixels in the order R, G, B from lowest to highest byte
+   * address within each pixel.  The X component is ignored when compressing
+   * and undefined when decompressing.
+   */
+  public static final int PF_RGBX = 2;
+  /**
+   * BGRX pixel format.  The red, green, and blue components in the image are
+   * stored in 4-byte pixels in the order B, G, R from lowest to highest byte
+   * address within each pixel.  The X component is ignored when compressing
+   * and undefined when decompressing.
+   */
+  public static final int PF_BGRX = 3;
+  /**
+   * XBGR pixel format.  The red, green, and blue components in the image are
+   * stored in 4-byte pixels in the order R, G, B from highest to lowest byte
+   * address within each pixel.  The X component is ignored when compressing
+   * and undefined when decompressing.
+   */
+  public static final int PF_XBGR = 4;
+  /**
+   * XRGB pixel format.  The red, green, and blue components in the image are
+   * stored in 4-byte pixels in the order B, G, R from highest to lowest byte
+   * address within each pixel.  The X component is ignored when compressing
+   * and undefined when decompressing.
+   */
+  public static final int PF_XRGB = 5;
+  /**
+   * Grayscale pixel format.  Each 1-byte pixel represents a luminance
+   * (brightness) level from 0 to 255.
+   */
+  public static final int PF_GRAY = 6;
+  /**
+   * RGBA pixel format.  This is the same as {@link #PF_RGBX}, except that when
+   * decompressing, the X byte is guaranteed to be 0xFF, which can be
+   * interpreted as an opaque alpha channel.
+   */
+  public static final int PF_RGBA = 7;
+  /**
+   * BGRA pixel format.  This is the same as {@link #PF_BGRX}, except that when
+   * decompressing, the X byte is guaranteed to be 0xFF, which can be
+   * interpreted as an opaque alpha channel.
+   */
+  public static final int PF_BGRA = 8;
+  /**
+   * ABGR pixel format.  This is the same as {@link #PF_XBGR}, except that when
+   * decompressing, the X byte is guaranteed to be 0xFF, which can be
+   * interpreted as an opaque alpha channel.
+   */
+  public static final int PF_ABGR = 9;
+  /**
+   * ARGB pixel format.  This is the same as {@link #PF_XRGB}, except that when
+   * decompressing, the X byte is guaranteed to be 0xFF, which can be
+   * interpreted as an opaque alpha channel.
+   */
+  public static final int PF_ARGB = 10;
+  /**
+   * CMYK pixel format.  Unlike RGB, which is an additive color model used
+   * primarily for display, CMYK (Cyan/Magenta/Yellow/Key) is a subtractive
+   * color model used primarily for printing.  In the CMYK color model, the
+   * value of each color component typically corresponds to an amount of cyan,
+   * magenta, yellow, or black ink that is applied to a white background.  In
+   * order to convert between CMYK and RGB, it is necessary to use a color
+   * management system (CMS.)  A CMS will attempt to map colors within the
+   * printer's gamut to perceptually similar colors in the display's gamut and
+   * vice versa, but the mapping is typically not 1:1 or reversible, nor can it
+   * be defined with a simple formula.  Thus, such a conversion is out of scope
+   * for a codec library.  However, the TurboJPEG API allows for compressing
+   * CMYK pixels into a YCCK JPEG image (see {@link #CS_YCCK}) and
+   * decompressing YCCK JPEG images into CMYK pixels.
+   */
+  public static final int PF_CMYK = 11;
+
+
+  /**
+   * Returns the pixel size (in bytes) for the given pixel format.
+   *
+   * @param pixelFormat the pixel format (one of <code>PF_*</code>)
+   *
+   * @return the pixel size (in bytes) for the given pixel format.
+   */
+  public static int getPixelSize(int pixelFormat) {
+    checkPixelFormat(pixelFormat);
+    return pixelSize[pixelFormat];
+  }
+
+  private static final int[] pixelSize = {
+    3, 3, 4, 4, 4, 4, 1, 4, 4, 4, 4, 4
+  };
+
+
+  /**
+   * For the given pixel format, returns the number of bytes that the red
+   * component is offset from the start of the pixel.  For instance, if a pixel
+   * of format <code>TJ.PF_BGRX</code> is stored in <code>char pixel[]</code>,
+   * then the red component will be
+   * <code>pixel[TJ.getRedOffset(TJ.PF_BGRX)]</code>.
+   *
+   * @param pixelFormat the pixel format (one of <code>PF_*</code>)
+   *
+   * @return the red offset for the given pixel format.
+   */
+  public static int getRedOffset(int pixelFormat) {
+    checkPixelFormat(pixelFormat);
+    return redOffset[pixelFormat];
+  }
+
+  private static final int[] redOffset = {
+    0, 2, 0, 2, 3, 1, 0, 0, 2, 3, 1, -1
+  };
+
+
+  /**
+   * For the given pixel format, returns the number of bytes that the green
+   * component is offset from the start of the pixel.  For instance, if a pixel
+   * of format <code>TJ.PF_BGRX</code> is stored in <code>char pixel[]</code>,
+   * then the green component will be
+   * <code>pixel[TJ.getGreenOffset(TJ.PF_BGRX)]</code>.
+   *
+   * @param pixelFormat the pixel format (one of <code>PF_*</code>)
+   *
+   * @return the green offset for the given pixel format.
+   */
+  public static int getGreenOffset(int pixelFormat) {
+    checkPixelFormat(pixelFormat);
+    return greenOffset[pixelFormat];
+  }
+
+  private static final int[] greenOffset = {
+    1, 1, 1, 1, 2, 2, 0, 1, 1, 2, 2, -1
+  };
+
+
+  /**
+   * For the given pixel format, returns the number of bytes that the blue
+   * component is offset from the start of the pixel.  For instance, if a pixel
+   * of format <code>TJ.PF_BGRX</code> is stored in <code>char pixel[]</code>,
+   * then the blue component will be
+   * <code>pixel[TJ.getBlueOffset(TJ.PF_BGRX)]</code>.
+   *
+   * @param pixelFormat the pixel format (one of <code>PF_*</code>)
+   *
+   * @return the blue offset for the given pixel format.
+   */
+  public static int getBlueOffset(int pixelFormat) {
+    checkPixelFormat(pixelFormat);
+    return blueOffset[pixelFormat];
+  }
+
+  private static final int[] blueOffset = {
+    2, 0, 2, 0, 1, 3, 0, 2, 0, 1, 3, -1
+  };
+
+
+  /**
+   * The number of JPEG colorspaces
+   */
+  public static final int NUMCS = 5;
+  /**
+   * RGB colorspace.  When compressing the JPEG image, the R, G, and B
+   * components in the source image are reordered into image planes, but no
+   * colorspace conversion or subsampling is performed.  RGB JPEG images can be
+   * decompressed to any of the extended RGB pixel formats or grayscale, but
+   * they cannot be decompressed to YUV images.
+   */
+  public static final int CS_RGB = 0;
+  /**
+   * YCbCr colorspace.  YCbCr is not an absolute colorspace but rather a
+   * mathematical transformation of RGB designed solely for storage and
+   * transmission.  YCbCr images must be converted to RGB before they can
+   * actually be displayed.  In the YCbCr colorspace, the Y (luminance)
+   * component represents the black & white portion of the original image, and
+   * the Cb and Cr (chrominance) components represent the color portion of the
+   * original image.  Originally, the analog equivalent of this transformation
+   * allowed the same signal to drive both black & white and color televisions,
+   * but JPEG images use YCbCr primarily because it allows the color data to be
+   * optionally subsampled for the purposes of reducing bandwidth or disk
+   * space.  YCbCr is the most common JPEG colorspace, and YCbCr JPEG images
+   * can be compressed from and decompressed to any of the extended RGB pixel
+   * formats or grayscale, or they can be decompressed to YUV planar images.
+   */
+  public static final int CS_YCbCr = 1;
+  /**
+   * Grayscale colorspace.  The JPEG image retains only the luminance data (Y
+   * component), and any color data from the source image is discarded.
+   * Grayscale JPEG images can be compressed from and decompressed to any of
+   * the extended RGB pixel formats or grayscale, or they can be decompressed
+   * to YUV planar images.
+   */
+  public static final int CS_GRAY = 2;
+  /**
+   * CMYK colorspace.  When compressing the JPEG image, the C, M, Y, and K
+   * components in the source image are reordered into image planes, but no
+   * colorspace conversion or subsampling is performed.  CMYK JPEG images can
+   * only be decompressed to CMYK pixels.
+   */
+  public static final int CS_CMYK = 3;
+  /**
+   * YCCK colorspace.  YCCK (AKA "YCbCrK") is not an absolute colorspace but
+   * rather a mathematical transformation of CMYK designed solely for storage
+   * and transmission.  It is to CMYK as YCbCr is to RGB.  CMYK pixels can be
+   * reversibly transformed into YCCK, and as with YCbCr, the chrominance
+   * components in the YCCK pixels can be subsampled without incurring major
+   * perceptual loss.  YCCK JPEG images can only be compressed from and
+   * decompressed to CMYK pixels.
+   */
+  public static final int CS_YCCK = 4;
+
+
+  /**
+   * The uncompressed source/destination image is stored in bottom-up (Windows,
+   * OpenGL) order, not top-down (X11) order.
+   */
+  public static final int FLAG_BOTTOMUP     = 2;
+
+  @Deprecated
+  public static final int FLAG_FORCEMMX     = 8;
+  @Deprecated
+  public static final int FLAG_FORCESSE     = 16;
+  @Deprecated
+  public static final int FLAG_FORCESSE2    = 32;
+  @Deprecated
+  public static final int FLAG_FORCESSE3    = 128;
+
+  /**
+   * When decompressing an image that was compressed using chrominance
+   * subsampling, use the fastest chrominance upsampling algorithm available in
+   * the underlying codec.  The default is to use smooth upsampling, which
+   * creates a smooth transition between neighboring chrominance components in
+   * order to reduce upsampling artifacts in the decompressed image.
+   */
+  public static final int FLAG_FASTUPSAMPLE = 256;
+  /**
+   * Use the fastest DCT/IDCT algorithm available in the underlying codec.  The
+   * default if this flag is not specified is implementation-specific.  For
+   * example, the implementation of TurboJPEG for libjpeg[-turbo] uses the fast
+   * algorithm by default when compressing, because this has been shown to have
+   * only a very slight effect on accuracy, but it uses the accurate algorithm
+   * when decompressing, because this has been shown to have a larger effect.
+   */
+  public static final int FLAG_FASTDCT      =  2048;
+  /**
+   * Use the most accurate DCT/IDCT algorithm available in the underlying
+   * codec.  The default if this flag is not specified is
+   * implementation-specific.  For example, the implementation of TurboJPEG for
+   * libjpeg[-turbo] uses the fast algorithm by default when compressing,
+   * because this has been shown to have only a very slight effect on accuracy,
+   * but it uses the accurate algorithm when decompressing, because this has
+   * been shown to have a larger effect.
+   */
+  public static final int FLAG_ACCURATEDCT  =  4096;
+
+
+  /**
+   * Returns the maximum size of the buffer (in bytes) required to hold a JPEG
+   * image with the given width, height, and level of chrominance subsampling.
+   *
+   * @param width the width (in pixels) of the JPEG image
+   *
+   * @param height the height (in pixels) of the JPEG image
+   *
+   * @param jpegSubsamp the level of chrominance subsampling to be used when
+   * generating the JPEG image (one of {@link TJ TJ.SAMP_*})
+   *
+   * @return the maximum size of the buffer (in bytes) required to hold a JPEG
+   * image with the given width, height, and level of chrominance subsampling.
+   */
+  public static native int bufSize(int width, int height, int jpegSubsamp);
+
+  /**
+   * Returns the size of the buffer (in bytes) required to hold a YUV planar
+   * image with the given width, height, and level of chrominance subsampling.
+   *
+   * @param width the width (in pixels) of the YUV image
+   *
+   * @param pad the width of each line in each plane of the image is padded to
+   * the nearest multiple of this number of bytes (must be a power of 2.)
+   *
+   * @param height the height (in pixels) of the YUV image
+   *
+   * @param subsamp the level of chrominance subsampling used in the YUV
+   * image (one of {@link TJ TJ.SAMP_*})
+   *
+   * @return the size of the buffer (in bytes) required to hold a YUV planar
+   * image with the given width, height, and level of chrominance subsampling.
+   */
+  public static native int bufSizeYUV(int width, int pad, int height,
+                                      int subsamp);
+
+  /**
+   * @deprecated Use {@link #bufSizeYUV(int, int, int, int)} instead.
+   */
+  @Deprecated
+  public static native int bufSizeYUV(int width, int height, int subsamp);
+
+  /**
+   * Returns the size of the buffer (in bytes) required to hold a YUV image
+   * plane with the given parameters.
+   *
+   * @param componentID ID number of the image plane (0 = Y, 1 = U/Cb,
+   * 2 = V/Cr)
+   *
+   * @param width width (in pixels) of the YUV image.  NOTE: this is the width
+   * of the whole image, not the plane width.
+   *
+   * @param stride bytes per line in the image plane.
+   *
+   * @param height height (in pixels) of the YUV image.  NOTE: this is the
+   * height of the whole image, not the plane height.
+   *
+   * @param subsamp the level of chrominance subsampling used in the YUV
+   * image (one of {@link TJ TJ.SAMP_*})
+   *
+   * @return the size of the buffer (in bytes) required to hold a YUV planar
+   * image with the given parameters.
+   */
+  public static native int planeSizeYUV(int componentID, int width, int stride,
+                                        int height, int subsamp);
+
+  /**
+   * Returns the plane width of a YUV image plane with the given parameters.
+   * Refer to {@link YUVImage YUVImage} for a description of plane width.
+   *
+   * @param componentID ID number of the image plane (0 = Y, 1 = U/Cb,
+   * 2 = V/Cr)
+   *
+   * @param width width (in pixels) of the YUV image
+   *
+   * @param subsamp the level of chrominance subsampling used in the YUV image
+   * (one of {@link TJ TJ.SAMP_*})
+   *
+   * @return the plane width of a YUV image plane with the given parameters.
+   */
+  public static native int planeWidth(int componentID, int width, int subsamp);
+
+  /**
+   * Returns the plane height of a YUV image plane with the given parameters.
+   * Refer to {@link YUVImage YUVImage} for a description of plane height.
+   *
+   * @param componentID ID number of the image plane (0 = Y, 1 = U/Cb,
+   * 2 = V/Cr)
+   *
+   * @param height height (in pixels) of the YUV image
+   *
+   * @param subsamp the level of chrominance subsampling used in the YUV image
+   * (one of {@link TJ TJ.SAMP_*})
+   *
+   * @return the plane height of a YUV image plane with the given parameters.
+   */
+  public static native int planeHeight(int componentID, int height,
+                                       int subsamp);
+
+  /**
+   * Returns a list of fractional scaling factors that the JPEG decompressor in
+   * this implementation of TurboJPEG supports.
+   *
+   * @return a list of fractional scaling factors that the JPEG decompressor in
+   * this implementation of TurboJPEG supports.
+   */
+  public static native TJScalingFactor[] getScalingFactors();
+
+  static {
+    TJLoader.load();
+  }
+
+  private static void checkPixelFormat(int pixelFormat) {
+    if (pixelFormat < 0 || pixelFormat >= NUMPF)
+      throw new IllegalArgumentException("Invalid pixel format");
+  }
+
+  private static void checkSubsampling(int subsamp) {
+    if (subsamp < 0 || subsamp >= NUMSAMP)
+      throw new IllegalArgumentException("Invalid subsampling type");
+  }
+
+}
Index: src/org/libjpegturbo/turbojpeg/TJDecompressor.java
===================================================================
--- src/org/libjpegturbo/turbojpeg/TJDecompressor.java	(revision 0)
+++ src/org/libjpegturbo/turbojpeg/TJDecompressor.java	(revision 0)
@@ -0,0 +1,634 @@
+/*
+ * Copyright (C)2011-2015 D. R. Commander.  All Rights Reserved.
+ * Copyright (C)2015 Viktor Szathmáry.  All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright notice,
+ *   this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright notice,
+ *   this list of conditions and the following disclaimer in the documentation
+ *   and/or other materials provided with the distribution.
+ * - Neither the name of the libjpeg-turbo Project nor the names of its
+ *   contributors may be used to endorse or promote products derived from this
+ *   software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS",
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.libjpegturbo.turbojpeg;
+
+import java.awt.image.BufferedImage;
+import java.awt.image.ComponentSampleModel;
+import java.awt.image.DataBufferByte;
+import java.awt.image.DataBufferInt;
+import java.awt.image.SinglePixelPackedSampleModel;
+import java.awt.image.WritableRaster;
+import java.io.Closeable;
+import java.nio.ByteOrder;
+
+/**
+ * TurboJPEG decompressor
+ */
+public class TJDecompressor implements Closeable {
+
+  private static final String NO_ASSOC_ERROR =
+    "No JPEG image is associated with this instance";
+
+  /**
+   * Create a TurboJPEG decompresssor instance.
+   * @throws TJException
+   */
+  public TJDecompressor() throws TJException {
+    init();
+  }
+
+  /**
+   * Create a TurboJPEG decompressor instance and associate the JPEG source
+   * image stored in <code>jpegImage</code> with the newly created instance.
+   *
+   * @param jpegImage JPEG image buffer (size of the JPEG image is assumed to
+   * be the length of the array.)  This buffer is not modified.
+   * @throws TJException
+   */
+  public TJDecompressor(byte[] jpegImage) throws TJException {
+    init();
+    setSourceImage(jpegImage, jpegImage.length);
+  }
+
+  /**
+   * Create a TurboJPEG decompressor instance and associate the JPEG source
+   * image of length <code>imageSize</code> bytes stored in
+   * <code>jpegImage</code> with the newly created instance.
+   *
+   * @param jpegImage JPEG image buffer.  This buffer is not modified.
+   * @param imageSize size of the JPEG image (in bytes)
+   * @throws TJException
+   */
+  public TJDecompressor(byte[] jpegImage, int imageSize) throws TJException {
+    init();
+    setSourceImage(jpegImage, imageSize);
+  }
+
+  /**
+   * Associate the JPEG image of length <code>imageSize</code> bytes stored in
+   * <code>jpegImage</code> with this decompressor instance.  This image will
+   * be used as the source image for subsequent decompress operations.
+   *
+   * @param jpegImage JPEG image buffer.  This buffer is not modified.
+   * @param imageSize size of the JPEG image (in bytes)
+   * @throws TJException
+   */
+  public void setSourceImage(byte[] jpegImage, int imageSize)
+                             throws TJException {
+    if (jpegImage == null || imageSize < 1)
+      throw new IllegalArgumentException("Invalid argument in setSourceImage()");
+    jpegBuf = jpegImage;
+    jpegBufSize = imageSize;
+    decompressHeader(jpegBuf, jpegBufSize);
+  }
+
+  /**
+   * Returns the width of the source image (JPEG or YUV) associated with this
+   * decompressor instance.
+   *
+   * @return the width of the source image (JPEG or YUV) associated with this
+   * decompressor instance.
+   */
+  public int getWidth() {
+    if (jpegWidth < 1)
+      throw new IllegalStateException(NO_ASSOC_ERROR);
+    return jpegWidth;
+  }
+
+  /**
+   * Returns the height of the source image (JPEG or YUV) associated with this
+   * decompressor instance.
+   *
+   * @return the height of the source image (JPEG or YUV) associated with this
+   * decompressor instance.
+   */
+  public int getHeight() {
+    if (jpegHeight < 1)
+      throw new IllegalStateException(NO_ASSOC_ERROR);
+    return jpegHeight;
+  }
+
+  /**
+   * Returns the level of chrominance subsampling used in the source image
+   * (JPEG or YUV) associated with this decompressor instance.  See
+   * {@link TJ#SAMP_444 TJ.SAMP_*}.
+   *
+   * @return the level of chrominance subsampling used in the source image
+   * (JPEG or YUV) associated with this decompressor instance.
+   */
+  public int getSubsamp() {
+    if (jpegSubsamp < 0)
+      throw new IllegalStateException(NO_ASSOC_ERROR);
+    if (jpegSubsamp >= TJ.NUMSAMP)
+      throw new IllegalStateException("JPEG header information is invalid");
+    return jpegSubsamp;
+  }
+
+  /**
+   * Returns the colorspace used in the source image (JPEG or YUV) associated
+   * with this decompressor instance.  See {@link TJ#CS_RGB TJ.CS_*}.  If the
+   * source image is YUV, then this always returns {@link TJ#CS_YCbCr}.
+   *
+   * @return the colorspace used in the source image (JPEG or YUV) associated
+   * with this decompressor instance.
+   */
+  public int getColorspace() {
+    if (jpegColorspace < 0)
+      throw new IllegalStateException(NO_ASSOC_ERROR);
+    if (jpegColorspace >= TJ.NUMCS)
+      throw new IllegalStateException("JPEG header information is invalid");
+    return jpegColorspace;
+  }
+
+  /**
+   * Returns the JPEG image buffer associated with this decompressor instance.
+   *
+   * @return the JPEG image buffer associated with this decompressor instance.
+   */
+  public byte[] getJPEGBuf() {
+    if (jpegBuf == null)
+      throw new IllegalStateException(NO_ASSOC_ERROR);
+    return jpegBuf;
+  }
+
+  /**
+   * Returns the size of the JPEG image (in bytes) associated with this
+   * decompressor instance.
+   *
+   * @return the size of the JPEG image (in bytes) associated with this
+   * decompressor instance.
+   */
+  public int getJPEGSize() {
+    if (jpegBufSize < 1)
+      throw new IllegalStateException(NO_ASSOC_ERROR);
+    return jpegBufSize;
+  }
+
+  /**
+   * Returns the width of the largest scaled-down image that the TurboJPEG
+   * decompressor can generate without exceeding the desired image width and
+   * height.
+   *
+   * @param desiredWidth desired width (in pixels) of the decompressed image.
+   * Setting this to 0 is the same as setting it to the width of the JPEG image
+   * (in other words, the width will not be considered when determining the
+   * scaled image size.)
+   *
+   * @param desiredHeight desired height (in pixels) of the decompressed image.
+   * Setting this to 0 is the same as setting it to the height of the JPEG
+   * image (in other words, the height will not be considered when determining
+   * the scaled image size.)
+   *
+   * @return the width of the largest scaled-down image that the TurboJPEG
+   * decompressor can generate without exceeding the desired image width and
+   * height.
+   */
+  public int getScaledWidth(int desiredWidth, int desiredHeight) {
+    if (jpegWidth < 1 || jpegHeight < 1)
+      throw new IllegalStateException(NO_ASSOC_ERROR);
+    if (desiredWidth < 0 || desiredHeight < 0)
+      throw new IllegalArgumentException("Invalid argument in getScaledWidth()");
+    TJScalingFactor[] sf = TJ.getScalingFactors();
+    if (desiredWidth == 0)
+      desiredWidth = jpegWidth;
+    if (desiredHeight == 0)
+      desiredHeight = jpegHeight;
+    int scaledWidth = jpegWidth, scaledHeight = jpegHeight;
+    for (int i = 0; i < sf.length; i++) {
+      scaledWidth = sf[i].getScaled(jpegWidth);
+      scaledHeight = sf[i].getScaled(jpegHeight);
+      if (scaledWidth <= desiredWidth && scaledHeight <= desiredHeight)
+        break;
+    }
+    if (scaledWidth > desiredWidth || scaledHeight > desiredHeight)
+      throw new IllegalArgumentException("Could not scale down to desired image dimensions");
+    return scaledWidth;
+  }
+
+  /**
+   * Returns the height of the largest scaled-down image that the TurboJPEG
+   * decompressor can generate without exceeding the desired image width and
+   * height.
+   *
+   * @param desiredWidth desired width (in pixels) of the decompressed image.
+   * Setting this to 0 is the same as setting it to the width of the JPEG image
+   * (in other words, the width will not be considered when determining the
+   * scaled image size.)
+   *
+   * @param desiredHeight desired height (in pixels) of the decompressed image.
+   * Setting this to 0 is the same as setting it to the height of the JPEG
+   * image (in other words, the height will not be considered when determining
+   * the scaled image size.)
+   *
+   * @return the height of the largest scaled-down image that the TurboJPEG
+   * decompressor can generate without exceeding the desired image width and
+   * height.
+   */
+  public int getScaledHeight(int desiredWidth, int desiredHeight) {
+    if (jpegWidth < 1 || jpegHeight < 1)
+      throw new IllegalStateException(NO_ASSOC_ERROR);
+    if (desiredWidth < 0 || desiredHeight < 0)
+      throw new IllegalArgumentException("Invalid argument in getScaledHeight()");
+    TJScalingFactor[] sf = TJ.getScalingFactors();
+    if (desiredWidth == 0)
+      desiredWidth = jpegWidth;
+    if (desiredHeight == 0)
+      desiredHeight = jpegHeight;
+    int scaledWidth = jpegWidth, scaledHeight = jpegHeight;
+    for (int i = 0; i < sf.length; i++) {
+      scaledWidth = sf[i].getScaled(jpegWidth);
+      scaledHeight = sf[i].getScaled(jpegHeight);
+      if (scaledWidth <= desiredWidth && scaledHeight <= desiredHeight)
+        break;
+    }
+    if (scaledWidth > desiredWidth || scaledHeight > desiredHeight)
+      throw new IllegalArgumentException("Could not scale down to desired image dimensions");
+    return scaledHeight;
+  }
+
+  /**
+   * Decompress the JPEG source image or decode the YUV source image associated
+   * with this decompressor instance and output a grayscale, RGB, or CMYK image
+   * to the given destination buffer.
+   *
+   * @param dstBuf buffer that will receive the decompressed/decoded image.
+   * If the source image is a JPEG image, then this buffer should normally be
+   * <code>pitch * scaledHeight</code> bytes in size, where
+   * <code>scaledHeight</code> can be determined by calling <code>
+   * scalingFactor.{@link TJScalingFactor#getScaled getScaled}(jpegHeight)
+   * </code> with one of the scaling factors returned from {@link
+   * TJ#getScalingFactors} or by calling {@link #getScaledHeight}.  If the
+   * source image is a YUV image, then this buffer should normally be
+   * <code>pitch * height</code> bytes in size, where <code>height</code> is
+   * the height of the YUV image.  However, the buffer may also be larger than
+   * the dimensions of the source image, in which case the <code>x</code>,
+   * <code>y</code>, and <code>pitch</code> parameters can be used to specify
+   * the region into which the source image should be decompressed/decoded.
+   *
+   * @param x x offset (in pixels) of the region in the destination image into
+   * which the source image should be decompressed/decoded
+   *
+   * @param y y offset (in pixels) of the region in the destination image into
+   * which the source image should be decompressed/decoded
+   *
+   * @param desiredWidth If the source image is a JPEG image, then this
+   * specifies the desired width (in pixels) of the decompressed image (or
+   * image region.)  If the desired destination image dimensions are different
+   * than the source image dimensions, then TurboJPEG will use scaling in the
+   * JPEG decompressor to generate the largest possible image that will fit
+   * within the desired dimensions.  Setting this to 0 is the same as setting
+   * it to the width of the JPEG image (in other words, the width will not be
+   * considered when determining the scaled image size.)  This parameter is
+   * ignored if the source image is a YUV image.
+   *
+   * @param pitch bytes per line of the destination image.  Normally, this
+   * should be set to <code>scaledWidth * TJ.pixelSize(pixelFormat)</code> if
+   * the destination image is unpadded, but you can use this to, for instance,
+   * pad each line of the destination image to a 4-byte boundary or to
+   * decompress/decode the source image into a region of a larger image.  NOTE:
+   * if the source image is a JPEG image, then <code>scaledWidth</code> can be
+   * determined by calling <code>
+   * scalingFactor.{@link TJScalingFactor#getScaled getScaled}(jpegWidth)
+   * </code> or by calling {@link #getScaledWidth}.  If the source image is a
+   * YUV image, then <code>scaledWidth</code> is the width of the YUV image.
+   * Setting this parameter to 0 is the equivalent of setting it to
+   * <code>scaledWidth * TJ.pixelSize(pixelFormat)</code>.
+   *
+   * @param desiredHeight If the source image is a JPEG image, then this
+   * specifies the desired height (in pixels) of the decompressed image (or
+   * image region.)  If the desired destination image dimensions are different
+   * than the source image dimensions, then TurboJPEG will use scaling in the
+   * JPEG decompressor to generate the largest possible image that will fit
+   * within the desired dimensions.  Setting this to 0 is the same as setting
+   * it to the height of the JPEG image (in other words, the height will not be
+   * considered when determining the scaled image size.)  This parameter is
+   * ignored if the source image is a YUV image.
+   *
+   * @param pixelFormat pixel format of the decompressed/decoded image (one of
+   * {@link TJ#PF_RGB TJ.PF_*})
+   *
+   * @param flags the bitwise OR of one or more of
+   * {@link TJ#FLAG_BOTTOMUP TJ.FLAG_*}
+   *
+   * @throws TJException
+   */
+  public void decompress(byte[] dstBuf, int x, int y, int desiredWidth,
+                         int pitch, int desiredHeight, int pixelFormat,
+                         int flags) throws TJException {
+    if (jpegBuf == null)
+      throw new IllegalStateException(NO_ASSOC_ERROR);
+    if (dstBuf == null || x < 0 || y < 0 || pitch < 0 ||
+        pixelFormat < 0 || pixelFormat >= TJ.NUMPF || flags < 0)
+      throw new IllegalArgumentException("Invalid argument in decompress()");
+    if (x > 0 || y > 0)
+      decompress(jpegBuf, jpegBufSize, dstBuf, x, y, desiredWidth, pitch,
+                 desiredHeight, pixelFormat, flags);
+    else
+      decompress(jpegBuf, jpegBufSize, dstBuf, 0, 0, desiredWidth, pitch,
+                 desiredHeight, pixelFormat, flags);
+  }
+
+  /**
+   * Decompress the JPEG source image associated with this decompressor
+   * instance and return a buffer containing the decompressed image.
+   *
+   * @param desiredWidth see
+   * {@link #decompress(byte[], int, int, int, int, int, int, int)}
+   * for description
+   *
+   * @param pitch see
+   * {@link #decompress(byte[], int, int, int, int, int, int, int)}
+   * for description
+   *
+   * @param desiredHeight see
+   * {@link #decompress(byte[], int, int, int, int, int, int, int)}
+   * for description
+   *
+   * @param pixelFormat pixel format of the decompressed image (one of
+   * {@link TJ#PF_RGB TJ.PF_*})
+   *
+   * @param flags the bitwise OR of one or more of
+   * {@link TJ#FLAG_BOTTOMUP TJ.FLAG_*}
+   *
+   * @return a buffer containing the decompressed image.
+   * @throws TJException
+   */
+  public byte[] decompress(int desiredWidth, int pitch, int desiredHeight,
+                           int pixelFormat, int flags) throws TJException {
+    if (pitch < 0 || desiredWidth < 0 || desiredHeight < 0 ||
+        pixelFormat < 0 || pixelFormat >= TJ.NUMPF || flags < 0)
+      throw new IllegalArgumentException("Invalid argument in decompress()");
+    int pixelSize = TJ.getPixelSize(pixelFormat);
+    int scaledWidth = getScaledWidth(desiredWidth, desiredHeight);
+    int scaledHeight = getScaledHeight(desiredWidth, desiredHeight);
+    if (pitch == 0)
+      pitch = scaledWidth * pixelSize;
+    byte[] buf = new byte[pitch * scaledHeight];
+    decompress(buf, 0, 0, desiredWidth, pitch, desiredHeight, pixelFormat, flags);
+    return buf;
+  }
+
+  /**
+   * Decompress the JPEG source image associated with this decompressor
+   * instance and output a grayscale, RGB, or CMYK image to the given
+   * destination buffer.
+   *
+   * @param dstBuf buffer that will receive the decompressed/decoded image.
+   * If the source image is a JPEG image, then this buffer should normally be
+   * <code>stride * scaledHeight</code> pixels in size, where
+   * <code>scaledHeight</code> can be determined by calling <code>
+   * scalingFactor.{@link TJScalingFactor#getScaled getScaled}(jpegHeight)
+   * </code> with one of the scaling factors returned from {@link
+   * TJ#getScalingFactors} or by calling {@link #getScaledHeight}.  If the
+   * source image is a YUV image, then this buffer should normally be
+   * <code>stride * height</code> pixels in size, where <code>height</code> is
+   * the height of the YUV image.  However, the buffer may also be larger than
+   * the dimensions of the JPEG image, in which case the <code>x</code>,
+   * <code>y</code>, and <code>stride</code> parameters can be used to specify
+   * the region into which the source image should be decompressed.
+   *
+   * @param x x offset (in pixels) of the region in the destination image into
+   * which the source image should be decompressed/decoded
+   *
+   * @param y y offset (in pixels) of the region in the destination image into
+   * which the source image should be decompressed/decoded
+   *
+   * @param desiredWidth If the source image is a JPEG image, then this
+   * specifies the desired width (in pixels) of the decompressed image (or
+   * image region.)  If the desired destination image dimensions are different
+   * than the source image dimensions, then TurboJPEG will use scaling in the
+   * JPEG decompressor to generate the largest possible image that will fit
+   * within the desired dimensions.  Setting this to 0 is the same as setting
+   * it to the width of the JPEG image (in other words, the width will not be
+   * considered when determining the scaled image size.)  This parameter is
+   * ignored if the source image is a YUV image.
+   *
+   * @param stride pixels per line of the destination image.  Normally, this
+   * should be set to <code>scaledWidth</code>, but you can use this to, for
+   * instance, decompress the JPEG image into a region of a larger image.
+   * NOTE: if the source image is a JPEG image, then <code>scaledWidth</code>
+   * can be determined by calling <code>
+   * scalingFactor.{@link TJScalingFactor#getScaled getScaled}(jpegWidth)
+   * </code> or by calling {@link #getScaledWidth}.  If the source image is a
+   * YUV image, then <code>scaledWidth</code> is the width of the YUV image.
+   * Setting this parameter to 0 is the equivalent of setting it to
+   * <code>scaledWidth</code>.
+   *
+   * @param desiredHeight If the source image is a JPEG image, then this
+   * specifies the desired height (in pixels) of the decompressed image (or
+   * image region.)  If the desired destination image dimensions are different
+   * than the source image dimensions, then TurboJPEG will use scaling in the
+   * JPEG decompressor to generate the largest possible image that will fit
+   * within the desired dimensions.  Setting this to 0 is the same as setting
+   * it to the height of the JPEG image (in other words, the height will not be
+   * considered when determining the scaled image size.)  This parameter is
+   * ignored if the source image is a YUV image.
+   *
+   * @param pixelFormat pixel format of the decompressed image (one of
+   * {@link TJ#PF_RGB TJ.PF_*})
+   *
+   * @param flags the bitwise OR of one or more of
+   * {@link TJ#FLAG_BOTTOMUP TJ.FLAG_*}
+   *
+   * @throws TJException
+   */
+  public void decompress(int[] dstBuf, int x, int y, int desiredWidth,
+                         int stride, int desiredHeight, int pixelFormat,
+                         int flags) throws TJException {
+    if (jpegBuf == null)
+      throw new IllegalStateException(NO_ASSOC_ERROR);
+    if (dstBuf == null || x < 0 || y < 0 || stride < 0 ||
+        pixelFormat < 0 || pixelFormat >= TJ.NUMPF || flags < 0)
+      throw new IllegalArgumentException("Invalid argument in decompress()");
+    decompress(jpegBuf, jpegBufSize, dstBuf, x, y, desiredWidth, stride,
+               desiredHeight, pixelFormat, flags);
+  }
+
+  /**
+   * Decompress the JPEG source image or decode the YUV source image associated
+   * with this decompressor instance and output a decompressed/decoded image to
+   * the given <code>BufferedImage</code> instance.
+   *
+   * @param dstImage a <code>BufferedImage</code> instance that will receive
+   * the decompressed/decoded image.  If the source image is a JPEG image, then
+   * the width and height of the <code>BufferedImage</code> instance must match
+   * one of the scaled image sizes that TurboJPEG is capable of generating from
+   * the JPEG image.  If the source image is a YUV image, then the width and
+   * height of the <code>BufferedImage</code> instance must match the width and
+   * height of the YUV image.
+   *
+   * @param flags the bitwise OR of one or more of
+   * {@link TJ#FLAG_BOTTOMUP TJ.FLAG_*}
+   *
+   * @throws TJException
+   */
+  public void decompress(BufferedImage dstImage, int flags) throws TJException {
+    if (dstImage == null || flags < 0)
+      throw new IllegalArgumentException("Invalid argument in decompress()");
+    int desiredWidth = dstImage.getWidth();
+    int desiredHeight = dstImage.getHeight();
+    int scaledWidth, scaledHeight;
+
+    scaledWidth = getScaledWidth(desiredWidth, desiredHeight);
+    scaledHeight = getScaledHeight(desiredWidth, desiredHeight);
+    if (scaledWidth != desiredWidth || scaledHeight != desiredHeight)
+      throw new IllegalArgumentException("BufferedImage dimensions do not match one of the scaled image sizes that TurboJPEG is capable of generating.");
+    int pixelFormat;  boolean intPixels = false;
+    if (byteOrder == null)
+      byteOrder = ByteOrder.nativeOrder();
+    switch(dstImage.getType()) {
+      case BufferedImage.TYPE_3BYTE_BGR:
+        pixelFormat = TJ.PF_BGR;  break;
+      case BufferedImage.TYPE_4BYTE_ABGR:
+      case BufferedImage.TYPE_4BYTE_ABGR_PRE:
+        pixelFormat = TJ.PF_XBGR;  break;
+      case BufferedImage.TYPE_BYTE_GRAY:
+        pixelFormat = TJ.PF_GRAY;  break;
+      case BufferedImage.TYPE_INT_BGR:
+        if (byteOrder == ByteOrder.BIG_ENDIAN)
+          pixelFormat = TJ.PF_XBGR;
+        else
+          pixelFormat = TJ.PF_RGBX;
+        intPixels = true;  break;
+      case BufferedImage.TYPE_INT_RGB:
+        if (byteOrder == ByteOrder.BIG_ENDIAN)
+          pixelFormat = TJ.PF_XRGB;
+        else
+          pixelFormat = TJ.PF_BGRX;
+        intPixels = true;  break;
+      case BufferedImage.TYPE_INT_ARGB:
+      case BufferedImage.TYPE_INT_ARGB_PRE:
+        if (byteOrder == ByteOrder.BIG_ENDIAN)
+          pixelFormat = TJ.PF_ARGB;
+        else
+          pixelFormat = TJ.PF_BGRA;
+        intPixels = true;  break;
+      default:
+        throw new IllegalArgumentException("Unsupported BufferedImage format");
+    }
+    WritableRaster wr = dstImage.getRaster();
+    if (intPixels) {
+      SinglePixelPackedSampleModel sm =
+        (SinglePixelPackedSampleModel)dstImage.getSampleModel();
+      int stride = sm.getScanlineStride();
+      DataBufferInt db = (DataBufferInt)wr.getDataBuffer();
+      int[] buf = db.getData();
+      if (jpegBuf == null)
+        throw new IllegalStateException(NO_ASSOC_ERROR);
+      decompress(jpegBuf, jpegBufSize, buf, 0, 0, scaledWidth, stride,
+                 scaledHeight, pixelFormat, flags);
+    } else {
+      ComponentSampleModel sm =
+        (ComponentSampleModel)dstImage.getSampleModel();
+      int pixelSize = sm.getPixelStride();
+      if (pixelSize != TJ.getPixelSize(pixelFormat))
+        throw new IllegalArgumentException("Inconsistency between pixel format and pixel size in BufferedImage");
+      int pitch = sm.getScanlineStride();
+      DataBufferByte db = (DataBufferByte)wr.getDataBuffer();
+      byte[] buf = db.getData();
+      decompress(buf, 0, 0, scaledWidth, pitch, scaledHeight, pixelFormat,
+                 flags);
+    }
+  }
+
+  /**
+   * Decompress the JPEG source image or decode the YUV source image associated
+   * with this decompressor instance and return a <code>BufferedImage</code>
+   * instance containing the decompressed/decoded image.
+   *
+   * @param desiredWidth see
+   * {@link #decompress(byte[], int, int, int, int, int, int, int)} for
+   * description
+   *
+   * @param desiredHeight see
+   * {@link #decompress(byte[], int, int, int, int, int, int, int)} for
+   * description
+   *
+   * @param bufferedImageType the image type of the <code>BufferedImage</code>
+   * instance that will be created (for instance,
+   * <code>BufferedImage.TYPE_INT_RGB</code>)
+   *
+   * @param flags the bitwise OR of one or more of
+   * {@link TJ#FLAG_BOTTOMUP TJ.FLAG_*}
+   *
+   * @return a <code>BufferedImage</code> instance containing the
+   * decompressed/decoded image.
+   * @throws TJException
+   */
+  public BufferedImage decompress(int desiredWidth, int desiredHeight,
+                                  int bufferedImageType, int flags)
+                                  throws TJException {
+    if (desiredWidth < 0 || desiredHeight < 0 || flags < 0)
+      throw new IllegalArgumentException("Invalid argument in decompress()");
+    int scaledWidth = getScaledWidth(desiredWidth, desiredHeight);
+    int scaledHeight = getScaledHeight(desiredWidth, desiredHeight);
+    BufferedImage img = new BufferedImage(scaledWidth, scaledHeight,
+                                          bufferedImageType);
+    decompress(img, flags);
+    return img;
+  }
+
+  /**
+   * Free the native structures associated with this decompressor instance.
+   */
+  @Override
+  public void close() throws TJException {
+    if (handle != 0)
+      destroy();
+  }
+
+  @Override
+  protected void finalize() throws Throwable {
+    try {
+      close();
+    } catch(TJException e) {
+    } finally {
+      super.finalize();
+    }
+  }
+
+  private native void init() throws TJException;
+
+  private native void destroy() throws TJException;
+
+  private native void decompressHeader(byte[] srcBuf, int size)
+    throws TJException;
+
+  private native void decompress(byte[] srcBuf, int size, byte[] dstBuf, int x,
+    int y, int desiredWidth, int pitch, int desiredHeight, int pixelFormat,
+    int flags) throws TJException;
+
+  private native void decompress(byte[] srcBuf, int size, int[] dstBuf, int x,
+    int y, int desiredWidth, int stride, int desiredHeight, int pixelFormat,
+    int flags) throws TJException;
+
+  static {
+    TJLoader.load();
+  }
+
+  protected long handle = 0;
+  protected byte[] jpegBuf = null;
+  protected int jpegBufSize = 0;
+  protected int jpegWidth = 0;
+  protected int jpegHeight = 0;
+  protected int jpegSubsamp = -1;
+  protected int jpegColorspace = -1;
+  private ByteOrder byteOrder = null;
+}
Index: src/org/libjpegturbo/turbojpeg/TJException.java
===================================================================
--- src/org/libjpegturbo/turbojpeg/TJException.java	(revision 0)
+++ src/org/libjpegturbo/turbojpeg/TJException.java	(revision 0)
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C)2015 Viktor Szathmáry.  All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright notice,
+ *   this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright notice,
+ *   this list of conditions and the following disclaimer in the documentation
+ *   and/or other materials provided with the distribution.
+ * - Neither the name of the libjpeg-turbo Project nor the names of its
+ *   contributors may be used to endorse or promote products derived from this
+ *   software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS",
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.libjpegturbo.turbojpeg;
+
+import java.io.IOException;
+
+public class TJException extends IOException {
+
+  private static final long serialVersionUID = 1L;
+
+  public TJException() {
+    super();
+  }
+
+  public TJException(String message, Throwable cause) {
+    super(message, cause);
+  }
+
+  public TJException(String message) {
+    super(message);
+  }
+
+  public TJException(Throwable cause) {
+    super(cause);
+  }
+
+}
Index: src/org/libjpegturbo/turbojpeg/TJScalingFactor.java
===================================================================
--- src/org/libjpegturbo/turbojpeg/TJScalingFactor.java	(revision 0)
+++ src/org/libjpegturbo/turbojpeg/TJScalingFactor.java	(revision 0)
@@ -0,0 +1,112 @@
+/*
+ * Copyright (C)2011 D. R. Commander.  All Rights Reserved.
+ * Copyright (C)2015 Viktor Szathmáry.  All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright notice,
+ *   this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright notice,
+ *   this list of conditions and the following disclaimer in the documentation
+ *   and/or other materials provided with the distribution.
+ * - Neither the name of the libjpeg-turbo Project nor the names of its
+ *   contributors may be used to endorse or promote products derived from this
+ *   software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS",
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.libjpegturbo.turbojpeg;
+
+/**
+ * Fractional scaling factor
+ */
+public class TJScalingFactor {
+
+  public TJScalingFactor(int num, int denom) {
+    if (num < 1 || denom < 1)
+      throw new IllegalArgumentException("Numerator and denominator must be >= 1");
+    this.num = num;
+    this.denom = denom;
+  }
+
+  /**
+   * Returns numerator
+   *
+   * @return numerator
+   */
+  public int getNum() {
+    return num;
+  }
+
+  /**
+   * Returns denominator
+   *
+   * @return denominator
+   */
+  public int getDenom() {
+    return denom;
+  }
+
+  /**
+   * Returns the scaled value of <code>dimension</code>.  This function
+   * performs the integer equivalent of
+   * <code>ceil(dimension * scalingFactor)</code>.
+   *
+   * @return the scaled value of <code>dimension</code>.
+   */
+  public int getScaled(int dimension) {
+    return (dimension * num + denom - 1) / denom;
+  }
+
+  /**
+   * Returns true or false, depending on whether this instance and
+   * <code>other</code> have the same numerator and denominator.
+   *
+   * @return true or false, depending on whether this instance and
+   * <code>other</code> have the same numerator and denominator.
+   */
+  @Override
+  public boolean equals(Object other) {
+    return (other instanceof TJScalingFactor) &&
+            this.num == ((TJScalingFactor)other).num &&
+            this.denom == ((TJScalingFactor)other).denom;
+  }
+
+  @Override
+  public int hashCode() {
+      return super.hashCode();
+  }
+
+  /**
+   * Returns true or false, depending on whether this instance is equal to
+   * 1/1.
+   *
+   * @return true or false, depending on whether this instance is equal to
+   * 1/1.
+   */
+  public boolean isOne() {
+    return num == 1 && denom == 1;
+  }
+
+  /**
+   * Numerator
+   */
+  private int num = 1;
+
+  /**
+   * Denominator
+   */
+  private int denom = 1;
+}
