libjpeg API: Partial scanline decompression

This, in combination with the existing jpeg_skip_scanlines() function,
provides the ability to crop the image both horizontally and vertically
while decompressing (certain restrictions apply-- see libjpeg.txt.)

This also cleans up the documentation of the line skipping feature and
removes the "strip decompression" feature from djpeg, since the new
cropping feature is a superset of it.

Refer to #34 for discussion.

Closes #34
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 91c8ac6..7f19043 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -367,9 +367,9 @@
   set(MD5_PPM_420M_ISLOW_1_4 35fd59d866e44659edfa3c18db2a3edb)
   set(MD5_PPM_420M_ISLOW_1_8 ccaed48ac0aedefda5d4abe4013f4ad7)
   set(MD5_PPM_420_ISLOW_SKIP15_31 86664cd9dc956536409e44e244d20a97)
-  set(MD5_PPM_420_ISLOW_PROG_STRIP71_132 a5c3706bb2e59bd01786b1181f67f97c)
+  set(MD5_PPM_420_ISLOW_PROG_CROP62x62_71_71 452a21656115a163029cfba5c04fa76a)
   set(MD5_PPM_444_ISLOW_SKIP1_6 ef63901f71ef7a75cd78253fc0914f84)
-  set(MD5_PPM_444_ISLOW_PROG_STRIP13_110 6d228f8d05381d8639f3d078b91b2ee0)
+  set(MD5_PPM_444_ISLOW_PROG_CROP98x98_13_13 15b173fb5872d9575572fbcc1b05956f)
   set(MD5_JPEG_CROP cdb35ff4b4519392690ea040c56ea99c)
 else()
   set(TESTORIG testorig.jpg)
@@ -421,11 +421,11 @@
   set(MD5_BMP_420M_ISLOW_565D d1be3a3339166255e76fa50a0d70d73e)
   set(MD5_PPM_420_ISLOW_SKIP15_31 c4c65c1e43d7275cd50328a61e6534f0)
   set(MD5_PPM_420_ISLOW_ARI_SKIP16_139 087c6b123db16ac00cb88c5b590bb74a)
-  set(MD5_PPM_420_ISLOW_PROG_STRIP71_132 a7f2ba6ea335f03549888bed66a89fae)
-  set(MD5_PPM_420_ISLOW_ARI_STRIP4_56 0e5e44a39b94817917a1bac72903246b)
+  set(MD5_PPM_420_ISLOW_PROG_CROP62x62_71_71 26eb36ccc7d1f0cb80cdabb0ac8b5d99)
+  set(MD5_PPM_420_ISLOW_ARI_CROP53x53_4_4 886c6775af22370257122f8b16207e6d)
   set(MD5_PPM_444_ISLOW_SKIP1_6 5606f86874cf26b8fcee1117a0a436a6)
-  set(MD5_PPM_444_ISLOW_PROG_STRIP13_110 40b5d9742558dca6229d7332fc2dda07)
-  set(MD5_PPM_444_ISLOW_ARI_STRIP0_36 9aceb5b9449c900b892a1d2fe39351b4)
+  set(MD5_PPM_444_ISLOW_PROG_CROP98x98_13_13 db87dc7ce26bcdc7a6b56239ce2b9d6c)
+  set(MD5_PPM_444_ISLOW_ARI_CROP37x37_0_0 cb57b32bd6d03e35432362f7bf184b6d)
   set(MD5_JPEG_CROP b4197f377e621c4e9b1d20471432610d)
 endif()
 
@@ -764,22 +764,23 @@
   add_test(cjpeg${suffix}-420-islow-prog
     ${dir}cjpeg${suffix} -dct int -prog
       -outfile testout_420_islow_prog.jpg ${TESTIMAGES}/testorig.ppm)
-  add_test(djpeg${suffix}-420-islow-prog-strip71_132
-    ${dir}djpeg${suffix} -dct int -strip 71,132 -ppm
-      -outfile testout_420_islow_strip71,132.ppm testout_420_islow_prog.jpg)
-  add_test(djpeg${suffix}-420-islow-prog-strip71_132-cmp
-    ${MD5CMP} ${MD5_PPM_420_ISLOW_PROG_STRIP71_132}
-      testout_420_islow_strip71,132.ppm)
+  add_test(djpeg${suffix}-420-islow-prog-crop62x62_71_71
+    ${dir}djpeg${suffix} -dct int -crop 62x62+71+71 -ppm
+      -outfile testout_420_islow_prog_crop62x62,71,71.ppm
+      testout_420_islow_prog.jpg)
+  add_test(djpeg${suffix}-420-islow-prog-crop62x62_71_71-cmp
+    ${MD5CMP} ${MD5_PPM_420_ISLOW_PROG_CROP62x62_71_71}
+      testout_420_islow_prog_crop62x62,71,71.ppm)
 
   # Context rows: Yes  Intra-iMCU row: No   iMCU row prefetch: No   ENT: arith
   if(WITH_ARITH_DEC)
-    add_test(djpeg${suffix}-420-islow-ari-strip4_56
-      ${dir}djpeg${suffix} -dct int -strip 4,56 -ppm
-        -outfile testout_420_islow_ari_strip4,56.ppm
+    add_test(djpeg${suffix}-420-islow-ari-crop53x53_4_4
+      ${dir}djpeg${suffix} -dct int -crop 53x53+4+4 -ppm
+        -outfile testout_420_islow_ari_crop53x53,4,4.ppm
         ${TESTIMAGES}/testimgari.jpg)
-    add_test(djpeg${suffix}-420-islow-ari-strip4_56-cmp
-      ${MD5CMP} ${MD5_PPM_420_ISLOW_ARI_STRIP4_56}
-        testout_420_islow_ari_strip4,56.ppm)
+    add_test(djpeg${suffix}-420-islow-ari-crop53x53_4_4-cmp
+      ${MD5CMP} ${MD5_PPM_420_ISLOW_ARI_CROP53x53_4_4}
+        testout_420_islow_ari_crop53x53,4,4.ppm)
   endif()
 
   # Context rows: No   Intra-iMCU row: Yes  ENT: huff
@@ -796,13 +797,13 @@
   add_test(cjpeg${suffix}-444-islow-prog
     ${dir}cjpeg${suffix} -dct int -prog -sample 1x1
       -outfile testout_444_islow_prog.jpg ${TESTIMAGES}/testorig.ppm)
-  add_test(djpeg${suffix}-444-islow-prog-strip13_110
-    ${dir}djpeg${suffix} -dct int -strip 13,110 -ppm
-      -outfile testout_444_islow_prog_strip13,110.ppm
+  add_test(djpeg${suffix}-444-islow-prog-crop98x98_13_13
+    ${dir}djpeg${suffix} -dct int -crop 98x98+13+13 -ppm
+      -outfile testout_444_islow_prog_crop98x98,13,13.ppm
       testout_444_islow_prog.jpg)
-  add_test(djpeg${suffix}-444-islow-prog_strip13_110-cmp
-    ${MD5CMP} ${MD5_PPM_444_ISLOW_PROG_STRIP13_110}
-      testout_444_islow_prog_strip13,110.ppm)
+  add_test(djpeg${suffix}-444-islow-prog_crop98x98_13_13-cmp
+    ${MD5CMP} ${MD5_PPM_444_ISLOW_PROG_CROP98x98_13_13}
+      testout_444_islow_prog_crop98x98,13,13.ppm)
 
   # Context rows: No   Intra-iMCU row: No   ENT: arith
   if(WITH_ARITH_ENC)
@@ -810,13 +811,13 @@
       ${dir}cjpeg${suffix} -dct int -arithmetic -sample 1x1
         -outfile testout_444_islow_ari.jpg ${TESTIMAGES}/testorig.ppm)
     if(WITH_ARITH_DEC)
-      add_test(djpeg${suffix}-444-islow-ari-strip0_36
-        ${dir}djpeg${suffix} -dct int -strip 0,36 -ppm
-          -outfile testout_444_islow_ari_strip0,36.ppm
+      add_test(djpeg${suffix}-444-islow-ari-crop37x37_0_0
+        ${dir}djpeg${suffix} -dct int -crop 37x37+0+0 -ppm
+          -outfile testout_444_islow_ari_crop37x37,0,0.ppm
           testout_444_islow_ari.jpg)
-      add_test(djpeg${suffix}-444-islow-ari-strip0_36-cmp
-        ${MD5CMP} ${MD5_PPM_444_ISLOW_ARI_STRIP0_36}
-          testout_444_islow_ari_strip0,36.ppm)
+      add_test(djpeg${suffix}-444-islow-ari-crop37x37_0_0-cmp
+        ${MD5CMP} ${MD5_PPM_444_ISLOW_ARI_CROP37x37_0_0}
+          testout_444_islow_ari_crop37x37,0,0.ppm)
     endif()
   endif()
 
diff --git a/Makefile.am b/Makefile.am
index 68cf8d8..b29edde 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -170,7 +170,8 @@
 EXTRA_DIST = win release $(DOCS) testimages CMakeLists.txt \
 	sharedlib/CMakeLists.txt cmakescripts libjpeg.map.in doc doxygen.config \
 	doxygen-extra.css jccolext.c jdcolext.c jdcol565.c jdmrgext.c jdmrg565.c \
-	jstdhuff.c jdcoefct.h jdmainct.h jdsample.h md5/CMakeLists.txt
+	jstdhuff.c jdcoefct.h jdmainct.h jdmaster.h jdsample.h wrppm.h \
+	md5/CMakeLists.txt
 
 dist-hook:
 	rm -rf `find $(distdir) -name .svn`
@@ -213,9 +214,9 @@
 MD5_PPM_420M_ISLOW_1_4 = 35fd59d866e44659edfa3c18db2a3edb
 MD5_PPM_420M_ISLOW_1_8 = ccaed48ac0aedefda5d4abe4013f4ad7
 MD5_PPM_420_ISLOW_SKIP15_31 = 86664cd9dc956536409e44e244d20a97
-MD5_PPM_420_ISLOW_PROG_STRIP71_132 = a5c3706bb2e59bd01786b1181f67f97c
+MD5_PPM_420_ISLOW_PROG_CROP62x62_71_71 = 452a21656115a163029cfba5c04fa76a
 MD5_PPM_444_ISLOW_SKIP1_6 = ef63901f71ef7a75cd78253fc0914f84
-MD5_PPM_444_ISLOW_PROG_STRIP13_110 = 6d228f8d05381d8639f3d078b91b2ee0
+MD5_PPM_444_ISLOW_PROG_CROP98x98_13_13 = 15b173fb5872d9575572fbcc1b05956f
 MD5_JPEG_CROP = cdb35ff4b4519392690ea040c56ea99c
 
 else
@@ -270,11 +271,11 @@
 MD5_BMP_420M_ISLOW_565D =d1be3a3339166255e76fa50a0d70d73e
 MD5_PPM_420_ISLOW_SKIP15_31 = c4c65c1e43d7275cd50328a61e6534f0
 MD5_PPM_420_ISLOW_ARI_SKIP16_139 = 087c6b123db16ac00cb88c5b590bb74a
-MD5_PPM_420_ISLOW_PROG_STRIP71_132 = a7f2ba6ea335f03549888bed66a89fae
-MD5_PPM_420_ISLOW_ARI_STRIP4_56 = 0e5e44a39b94817917a1bac72903246b
+MD5_PPM_420_ISLOW_PROG_CROP62x62_71_71 = 26eb36ccc7d1f0cb80cdabb0ac8b5d99
+MD5_PPM_420_ISLOW_ARI_CROP53x53_4_4 = 886c6775af22370257122f8b16207e6d
 MD5_PPM_444_ISLOW_SKIP1_6 = 5606f86874cf26b8fcee1117a0a436a6
-MD5_PPM_444_ISLOW_PROG_STRIP13_110 = 40b5d9742558dca6229d7332fc2dda07
-MD5_PPM_444_ISLOW_ARI_STRIP0_36 = 9aceb5b9449c900b892a1d2fe39351b4
+MD5_PPM_444_ISLOW_PROG_CROP98x98_13_13 = db87dc7ce26bcdc7a6b56239ce2b9d6c
+MD5_PPM_444_ISLOW_ARI_CROP37x37_0_0 = cb57b32bd6d03e35432362f7bf184b6d
 MD5_JPEG_CROP = b4197f377e621c4e9b1d20471432610d
 
 endif
@@ -614,14 +615,14 @@
 endif
 # Context rows: Yes  Intra-iMCU row: No   iMCU row prefetch: No   ENT: prog huff
 	./cjpeg -dct int -prog -outfile testout_420_islow_prog.jpg $(srcdir)/testimages/testorig.ppm
-	./djpeg -dct int -strip 71,132 -ppm -outfile testout_420_islow_prog_strip71,132.ppm testout_420_islow_prog.jpg
-	md5/md5cmp $(MD5_PPM_420_ISLOW_PROG_STRIP71_132) testout_420_islow_prog_strip71,132.ppm
-	rm -f testout_420_islow_prog_strip71,132.ppm testout_420_islow_prog.jpg
+	./djpeg -dct int -crop 62x62+71+71 -ppm -outfile testout_420_islow_prog_crop62x62,71,71.ppm testout_420_islow_prog.jpg
+	md5/md5cmp $(MD5_PPM_420_ISLOW_PROG_CROP62x62_71_71) testout_420_islow_prog_crop62x62,71,71.ppm
+	rm -f testout_420_islow_prog_crop62x62,71,71.ppm testout_420_islow_prog.jpg
 # Context rows: Yes  Intra-iMCU row: No   iMCU row prefetch: No   ENT: arith
 if WITH_ARITH_DEC
-	./djpeg -dct int -strip 4,56 -ppm -outfile testout_420_islow_ari_strip4,56.ppm $(srcdir)/testimages/testimgari.jpg
-	md5/md5cmp $(MD5_PPM_420_ISLOW_ARI_STRIP4_56) testout_420_islow_ari_strip4,56.ppm
-	rm -f testout_420_islow_ari_strip4,56.ppm
+	./djpeg -dct int -crop 53x53+4+4 -ppm -outfile testout_420_islow_ari_crop53x53,4,4.ppm $(srcdir)/testimages/testimgari.jpg
+	md5/md5cmp $(MD5_PPM_420_ISLOW_ARI_CROP53x53_4_4) testout_420_islow_ari_crop53x53,4,4.ppm
+	rm -f testout_420_islow_ari_crop53x53,4,4.ppm
 endif
 # Context rows: No   Intra-iMCU row: Yes  ENT: huff
 	./cjpeg -dct int -sample 1x1 -outfile testout_444_islow.jpg $(srcdir)/testimages/testorig.ppm
@@ -630,16 +631,16 @@
 	rm -f testout_444_islow_skip1,6.ppm testout_444_islow.jpg
 # Context rows: No   Intra-iMCU row: No   ENT: prog huff
 	./cjpeg -dct int -prog -sample 1x1 -outfile testout_444_islow_prog.jpg $(srcdir)/testimages/testorig.ppm
-	./djpeg -dct int -strip 13,110 -ppm -outfile testout_444_islow_prog_strip13,110.ppm testout_444_islow_prog.jpg
-	md5/md5cmp $(MD5_PPM_444_ISLOW_PROG_STRIP13_110) testout_444_islow_prog_strip13,110.ppm
-	rm -f testout_444_islow_prog_strip13,110.ppm testout_444_islow_prog.jpg
+	./djpeg -dct int -crop 98x98+13+13 -ppm -outfile testout_444_islow_prog_crop98x98,13,13.ppm testout_444_islow_prog.jpg
+	md5/md5cmp $(MD5_PPM_444_ISLOW_PROG_CROP98x98_13_13) testout_444_islow_prog_crop98x98,13,13.ppm
+	rm -f testout_444_islow_prog_crop98x98,13,13.ppm testout_444_islow_prog.jpg
 # Context rows: No   Intra-iMCU row: No   ENT: arith
 if WITH_ARITH_ENC
 	./cjpeg -dct int -arithmetic -sample 1x1 -outfile testout_444_islow_ari.jpg $(srcdir)/testimages/testorig.ppm
 if WITH_ARITH_DEC
-	./djpeg -dct int -strip 0,36 -ppm -outfile testout_444_islow_ari_strip0,36.ppm testout_444_islow_ari.jpg
-	md5/md5cmp $(MD5_PPM_444_ISLOW_ARI_STRIP0_36) testout_444_islow_ari_strip0,36.ppm
-	rm -f testout_444_islow_ari_strip0,36.ppm
+	./djpeg -dct int -crop 37x37+0+0 -ppm -outfile testout_444_islow_ari_crop37x37,0,0.ppm testout_444_islow_ari.jpg
+	md5/md5cmp $(MD5_PPM_444_ISLOW_ARI_CROP37x37_0_0) testout_444_islow_ari_crop37x37,0,0.ppm
+	rm -f testout_444_islow_ari_crop37x37,0,0.ppm
 endif
 	rm -f testout_444_islow_ari.jpg
 endif
diff --git a/djpeg.1 b/djpeg.1
index 293939b..7efde43 100644
--- a/djpeg.1
+++ b/djpeg.1
@@ -1,4 +1,4 @@
-.TH DJPEG 1 "17 February 2016"
+.TH DJPEG 1 "18 February 2016"
 .SH NAME
 djpeg \- decompress a JPEG file to an image file
 .SH SYNOPSIS
@@ -195,13 +195,15 @@
 mainly as a way of testing the in-memory source manager (jpeg_mem_src().)
 .TP
 .BI \-skip " Y0,Y1"
-Decode all rows of the JPEG image except those between Y0 and Y1 (inclusive.)
-Note that if decompression scaling is being used, Y0 and Y1 are relative to the
-scaled image dimensions.
+Decompress all rows of the JPEG image except those between Y0 and Y1
+(inclusive.)  Note that if decompression scaling is being used, then Y0 and Y1
+are relative to the scaled image dimensions.
 .TP
-.BI \-strip " Y0,Y1"
-Decode only the rows of the JPEG image between Y0 and Y1 (inclusive.)  Note
-that if decompression scaling is being used, Y0 and Y1 are relative to the
+.BI \-crop " WxH+X+Y"
+Decompress only a rectangular subregion of the image, starting at point X,Y
+with width W and height H.  If necessary, X will be shifted left to the nearest
+iMCU boundary, and the width will be increased accordingly.  Note that if
+decompression scaling is being used, then X, Y, W, and H are relative to the
 scaled image dimensions.
 .TP
 .B \-verbose
diff --git a/djpeg.c b/djpeg.c
index ce83104..54cd525 100644
--- a/djpeg.c
+++ b/djpeg.c
@@ -5,7 +5,7 @@
  * Copyright (C) 1991-1997, Thomas G. Lane.
  * Modified 2013 by Guido Vollbeding.
  * libjpeg-turbo Modifications:
- * Copyright (C) 2010-2011, 2013-2015, D. R. Commander.
+ * Copyright (C) 2010-2011, 2013-2016, D. R. Commander.
  * Copyright (C) 2015, Google, Inc.
  * For conditions of distribution and use, see the accompanying README.ijg
  * file.
@@ -31,6 +31,7 @@
 #include "cdjpeg.h"             /* Common decls for cjpeg/djpeg applications */
 #include "jversion.h"           /* for version message */
 #include "jconfigint.h"
+#include "wrppm.h"
 
 #include <ctype.h>              /* to declare isprint() */
 
@@ -91,8 +92,9 @@
 static const char *progname;    /* program name for error messages */
 static char *outfilename;       /* for -outfile switch */
 boolean memsrc;                 /* for -memsrc switch */
-boolean strip, skip;
-JDIMENSION startY, endY;
+boolean skip, crop;
+JDIMENSION skip_start, skip_end;
+JDIMENSION crop_x, crop_y, crop_width, crop_height;
 #define INPUT_BUF_SIZE  4096
 
 
@@ -169,8 +171,8 @@
   fprintf(stderr, "  -memsrc        Load input file into memory before decompressing\n");
 #endif
 
-  fprintf(stderr, "  -skip Y0,Y1    Decode all rows except those between Y0 and Y1 (inclusive)\n");
-  fprintf(stderr, "  -strip Y0,Y1   Decode only rows between Y0 and Y1 (inclusive)\n");
+  fprintf(stderr, "  -skip Y0,Y1    Decompress all rows except those between Y0 and Y1 (inclusive)\n");
+  fprintf(stderr, "  -crop WxH+X+Y  Decompress only a rectangular subregion of the image\n");
   fprintf(stderr, "  -verbose  or  -debug   Emit debug output\n");
   fprintf(stderr, "  -version       Print version information and exit\n");
   exit(EXIT_FAILURE);
@@ -196,8 +198,8 @@
   requested_fmt = DEFAULT_FMT;  /* set default output file format */
   outfilename = NULL;
   memsrc = FALSE;
-  strip = FALSE;
   skip = FALSE;
+  crop = FALSE;
   cinfo->err->trace_level = 0;
 
   /* Scan command line options, adjust parameters */
@@ -378,21 +380,24 @@
                  &cinfo->scale_num, &cinfo->scale_denom) != 2)
         usage();
 
-    } else if (keymatch(arg, "strip", 2)) {
-      if (++argn >= argc)
-        usage();
-      if (sscanf(argv[argn], "%d,%d", &startY, &endY) != 2 || startY > endY)
-        usage();
-      strip = TRUE;
-
-
     } else if (keymatch(arg, "skip", 2)) {
       if (++argn >= argc)
         usage();
-      if (sscanf(argv[argn], "%d,%d", &startY, &endY) != 2 || startY > endY)
+      if (sscanf(argv[argn], "%u,%u", &skip_start, &skip_end) != 2 ||
+          skip_start > skip_end)
         usage();
       skip = TRUE;
 
+    } else if (keymatch(arg, "crop", 2)) {
+      char c;
+      if (++argn >= argc)
+        usage();
+      if (sscanf(argv[argn], "%u%c%u+%u+%u", &crop_width, &c, &crop_height,
+                 &crop_x, &crop_y) != 5 ||
+          (c != 'X' && c != 'x') || crop_width < 1 || crop_height < 1)
+        usage();
+      crop = TRUE;
+
     } else if (keymatch(arg, "targa", 1)) {
       /* Targa output format. */
       requested_fmt = FMT_TARGA;
@@ -658,54 +663,78 @@
   /* Start decompressor */
   (void) jpeg_start_decompress(&cinfo);
 
-  /* Strip decode */
-  if (strip || skip) {
+  /* Skip rows */
+  if (skip) {
     JDIMENSION tmp;
 
-    /* Check for valid endY.  We cannot check this value until after
+    /* Check for valid skip_end.  We cannot check this value until after
      * jpeg_start_decompress() is called.  Note that we have already verified
-     * that startY <= endY.
+     * that skip_start <= skip_end.
      */
-    if (endY > cinfo.output_height - 1) {
-      fprintf(stderr, "%s: strip %d-%d exceeds image height %d\n", progname,
-              startY, endY, cinfo.output_height);
+    if (skip_end > cinfo.output_height - 1) {
+      fprintf(stderr, "%s: skip region exceeds image height %d\n", progname,
+              cinfo.output_height);
       exit(EXIT_FAILURE);
     }
 
     /* Write output file header.  This is a hack to ensure that the destination
-     * manager creates an image of the proper size for the partial decode.
+     * manager creates an output image of the proper size.
      */
     tmp = cinfo.output_height;
-    cinfo.output_height = endY - startY + 1;
-    if (skip)
-      cinfo.output_height = tmp - cinfo.output_height;
+    cinfo.output_height -= (skip_end - skip_start + 1);
     (*dest_mgr->start_output) (&cinfo, dest_mgr);
     cinfo.output_height = tmp;
 
     /* Process data */
-    if (skip) {
-      while (cinfo.output_scanline < startY) {
-        num_scanlines = jpeg_read_scanlines(&cinfo, dest_mgr->buffer,
-                                            dest_mgr->buffer_height);
-        (*dest_mgr->put_pixel_rows) (&cinfo, dest_mgr, num_scanlines);
-      }
-      jpeg_skip_scanlines(&cinfo, endY - startY + 1);
-      while (cinfo.output_scanline < cinfo.output_height) {
-        num_scanlines = jpeg_read_scanlines(&cinfo, dest_mgr->buffer,
-                                            dest_mgr->buffer_height);
-        (*dest_mgr->put_pixel_rows) (&cinfo, dest_mgr, num_scanlines);
-      }
-    } else {
-      jpeg_skip_scanlines(&cinfo, startY);
-      while (cinfo.output_scanline <= endY) {
-        num_scanlines = jpeg_read_scanlines(&cinfo, dest_mgr->buffer,
-                                            dest_mgr->buffer_height);
-        (*dest_mgr->put_pixel_rows) (&cinfo, dest_mgr, num_scanlines);
-      }
-      jpeg_skip_scanlines(&cinfo, cinfo.output_height - endY + 1);
+    while (cinfo.output_scanline < skip_start) {
+      num_scanlines = jpeg_read_scanlines(&cinfo, dest_mgr->buffer,
+                                          dest_mgr->buffer_height);
+      (*dest_mgr->put_pixel_rows) (&cinfo, dest_mgr, num_scanlines);
+    }
+    jpeg_skip_scanlines(&cinfo, skip_end - skip_start + 1);
+    while (cinfo.output_scanline < cinfo.output_height) {
+      num_scanlines = jpeg_read_scanlines(&cinfo, dest_mgr->buffer,
+                                          dest_mgr->buffer_height);
+      (*dest_mgr->put_pixel_rows) (&cinfo, dest_mgr, num_scanlines);
     }
 
-  /* Normal full image decode */
+  /* Decompress a subregion */
+  } else if (crop) {
+    JDIMENSION tmp;
+
+    /* Check for valid crop dimensions.  We cannot check these values until
+     * after jpeg_start_decompress() is called.
+     */
+    if (crop_x + crop_width > cinfo.output_width ||
+        crop_y + crop_height > cinfo.output_height) {
+      fprintf(stderr, "%s: crop dimensions exceed image dimensions %d x %d\n",
+              progname, cinfo.output_width, cinfo.output_height);
+      exit(EXIT_FAILURE);
+    }
+
+    jpeg_crop_scanline(&cinfo, &crop_x, &crop_width);
+    ((ppm_dest_ptr) dest_mgr)->buffer_width = cinfo.output_width *
+                                              cinfo.out_color_components *
+                                              sizeof(JSAMPLE);
+
+    /* Write output file header.  This is a hack to ensure that the destination
+     * manager creates an output image of the proper size.
+     */
+    tmp = cinfo.output_height;
+    cinfo.output_height = crop_height;
+    (*dest_mgr->start_output) (&cinfo, dest_mgr);
+    cinfo.output_height = tmp;
+
+    /* Process data */
+    jpeg_skip_scanlines(&cinfo, crop_y);
+    while (cinfo.output_scanline < crop_y + crop_height) {
+      num_scanlines = jpeg_read_scanlines(&cinfo, dest_mgr->buffer,
+                                          dest_mgr->buffer_height);
+      (*dest_mgr->put_pixel_rows) (&cinfo, dest_mgr, num_scanlines);
+    }
+    jpeg_skip_scanlines(&cinfo, cinfo.output_height - crop_y - crop_height);
+
+  /* Normal full-image decompress */
   } else {
     /* Write output file header */
     (*dest_mgr->start_output) (&cinfo, dest_mgr);
diff --git a/jdapimin.c b/jdapimin.c
index 7cb3949..f80a146 100644
--- a/jdapimin.c
+++ b/jdapimin.c
@@ -3,8 +3,8 @@
  *
  * This file was part of the Independent JPEG Group's software:
  * Copyright (C) 1994-1998, Thomas G. Lane.
- * It was modified by The libjpeg-turbo Project to include only code relevant
- * to libjpeg-turbo.
+ * libjpeg-turbo Modifications:
+ * Copyright (C) 2016, D. R. Commander.
  * For conditions of distribution and use, see the accompanying README.ijg
  * file.
  *
@@ -22,6 +22,7 @@
 #define JPEG_INTERNALS
 #include "jinclude.h"
 #include "jpeglib.h"
+#include "jdmaster.h"
 
 
 /*
@@ -83,6 +84,14 @@
 
   /* OK, I'm ready */
   cinfo->global_state = DSTATE_START;
+
+  /* The master struct is used to store extension parameters, so we allocate it
+   * here.
+   */
+  cinfo->master = (struct jpeg_decomp_master *)
+      (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_PERMANENT,
+                                  sizeof(my_decomp_master));
+  MEMZERO(cinfo->master, sizeof(my_decomp_master));
 }
 
 
diff --git a/jdapistd.c b/jdapistd.c
index 2d00b19..06076cb 100644
--- a/jdapistd.c
+++ b/jdapistd.c
@@ -141,6 +141,110 @@
 
 
 /*
+ * Enable partial scanline decompression
+ *
+ * Must be called after jpeg_start_decompress() and before any calls to
+ * jpeg_read_scanlines() or jpeg_skip_scanlines().
+ *
+ * Refer to libjpeg.txt for more information.
+ */
+
+GLOBAL(void)
+jpeg_crop_scanline (j_decompress_ptr cinfo, JDIMENSION *xoffset,
+                    JDIMENSION *width)
+{
+  int ci, align, orig_downsampled_width;
+  JDIMENSION input_xoffset;
+  boolean reinit_upsampler = FALSE;
+  jpeg_component_info *compptr;
+
+  if (cinfo->global_state != DSTATE_SCANNING || cinfo->output_scanline != 0)
+    ERREXIT1(cinfo, JERR_BAD_STATE, cinfo->global_state);
+
+  if (!xoffset || !width)
+    ERREXIT(cinfo, JERR_BAD_CROP_SPEC);
+
+  /* xoffset and width must fall within the output image dimensions. */
+  if (*width == 0 || *xoffset + *width > cinfo->output_width)
+    ERREXIT(cinfo, JERR_WIDTH_OVERFLOW);
+
+  /* No need to do anything if the caller wants the entire width. */
+  if (*width == cinfo->output_width)
+    return;
+
+  /* Ensuring the proper alignment of xoffset is tricky.  At minimum, it
+   * must align with an MCU boundary, because:
+   *
+   *   (1) The IDCT is performed in blocks, and it is not feasible to modify
+   *       the algorithm so that it can transform partial blocks.
+   *   (2) Because of the SIMD extensions, any input buffer passed to the
+   *       upsampling and color conversion routines must be aligned to the
+   *       SIMD word size (for instance, 128-bit in the case of SSE2.)  The
+   *       easiest way to accomplish this without copying data is to ensure
+   *       that upsampling and color conversion begin at the start of the
+   *       first MCU column that will be inverse transformed.
+   *
+   * In practice, we actually impose a stricter alignment requirement.  We
+   * require that xoffset be a multiple of the maximum MCU column width of all
+   * of the components (the "iMCU column width.")  This is to simplify the
+   * single-pass decompression case, allowing us to use the same MCU column
+   * width for all of the components.
+   */
+  align = cinfo->min_DCT_scaled_size * cinfo->max_h_samp_factor;
+
+  /* Adjust xoffset to the nearest iMCU boundary <= the requested value */
+  input_xoffset = *xoffset;
+  *xoffset = (input_xoffset / align) * align;
+
+  /* Adjust the width so that the right edge of the output image is as
+   * requested (only the left edge is altered.)  It is important that calling
+   * programs check this value after this function returns, so that they can
+   * allocate an output buffer with the appropriate size.
+   */
+  *width = *width + input_xoffset - *xoffset;
+  cinfo->output_width = *width;
+
+  /* Set the first and last iMCU columns that we must decompress.  These values
+   * will be used in single-scan decompressions.
+   */
+  cinfo->master->first_iMCU_col =
+    (JDIMENSION) (long) (*xoffset) / (long) align;
+  cinfo->master->last_iMCU_col =
+    (JDIMENSION) jdiv_round_up((long) (*xoffset + cinfo->output_width),
+                               (long) align) - 1;
+
+  for (ci = 0, compptr = cinfo->comp_info; ci < cinfo->num_components;
+       ci++, compptr++) {
+    /* Set downsampled_width to the new output width. */
+    orig_downsampled_width = compptr->downsampled_width;
+    compptr->downsampled_width =
+      (JDIMENSION) jdiv_round_up((long) (cinfo->output_width *
+                                         compptr->h_samp_factor),
+                                 (long) cinfo->max_h_samp_factor);
+    if (compptr->downsampled_width < 2 && orig_downsampled_width >= 2)
+      reinit_upsampler = TRUE;
+
+    /* Set the first and last iMCU columns that we must decompress.  These
+     * values will be used in multi-scan decompressions.
+     */
+    cinfo->master->first_MCU_col[ci] =
+      (JDIMENSION) (long) (*xoffset * compptr->h_samp_factor) /
+                   (long) align;
+    cinfo->master->last_MCU_col[ci] =
+      (JDIMENSION) jdiv_round_up((long) ((*xoffset + cinfo->output_width) *
+                                         compptr->h_samp_factor),
+                                 (long) align) - 1;
+  }
+
+  if (reinit_upsampler) {
+    cinfo->master->jinit_upsampler_no_alloc = TRUE;
+    jinit_upsampler(cinfo);
+    cinfo->master->jinit_upsampler_no_alloc = FALSE;
+  }
+}
+
+
+/*
  * Read some scanlines of data from the JPEG decompressor.
  *
  * The return value will be the number of lines actually read.
diff --git a/jdcoefct.c b/jdcoefct.c
index b291da9..1a48969 100644
--- a/jdcoefct.c
+++ b/jdcoefct.c
@@ -6,6 +6,7 @@
  * libjpeg-turbo Modifications:
  * Copyright 2009 Pierre Ossman <ossman@cendio.se> for Cendio AB
  * Copyright (C) 2010, 2015-2016, D. R. Commander.
+ * Copyright (C) 2015, Google, Inc.
  * For conditions of distribution and use, see the accompanying README.ijg
  * file.
  *
@@ -108,38 +109,46 @@
         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.
+
+      /* Only perform the IDCT on blocks that are contained within the desired
+       * cropping region.
        */
-      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] +
-          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++) {
-              (*inverse_DCT) (cinfo, compptr,
-                              (JCOEFPTR) coef->MCU_buffer[blkn+xindex],
-                              output_ptr, output_col);
-              output_col += compptr->_DCT_scaled_size;
-            }
+      if (MCU_col_num >= cinfo->master->first_iMCU_col &&
+          MCU_col_num <= cinfo->master->last_iMCU_col) {
+        /* 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;
           }
-          blkn += compptr->MCU_width;
-          output_ptr += compptr->_DCT_scaled_size;
+          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] +
+            yoffset * compptr->_DCT_scaled_size;
+          start_col = (MCU_col_num - cinfo->master->first_iMCU_col) *
+              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++) {
+                (*inverse_DCT) (cinfo, compptr,
+                                (JCOEFPTR) coef->MCU_buffer[blkn+xindex],
+                                output_ptr, output_col);
+                output_col += compptr->_DCT_scaled_size;
+              }
+            }
+            blkn += compptr->MCU_width;
+            output_ptr += compptr->_DCT_scaled_size;
+          }
         }
       }
     }
@@ -294,9 +303,10 @@
     output_ptr = output_buf[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_ptr = buffer[block_row] + cinfo->master->first_MCU_col[ci];
       output_col = 0;
-      for (block_num = 0; block_num < compptr->width_in_blocks; block_num++) {
+      for (block_num = cinfo->master->first_MCU_col[ci];
+           block_num <= cinfo->master->last_MCU_col[ci]; block_num++) {
         (*inverse_DCT) (cinfo, compptr, (JCOEFPTR) buffer_ptr,
                         output_ptr, output_col);
         buffer_ptr++;
@@ -482,7 +492,7 @@
     output_ptr = output_buf[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_ptr = buffer[block_row] + cinfo->master->first_MCU_col[ci];
       if (first_row && block_row == 0)
         prev_block_row = buffer_ptr;
       else
@@ -499,7 +509,8 @@
       DC7 = DC8 = DC9 = (int) next_block_row[0][0];
       output_col = 0;
       last_block_column = compptr->width_in_blocks - 1;
-      for (block_num = 0; block_num <= last_block_column; block_num++) {
+      for (block_num = cinfo->master->first_MCU_col[ci];
+           block_num <= cinfo->master->last_MCU_col[ci]; block_num++) {
         /* Fetch current DCT block into workspace so we can modify it. */
         jcopy_block_row(buffer_ptr, (JBLOCKROW) workspace, (JDIMENSION) 1);
         /* Update DC values */
diff --git a/jdinput.c b/jdinput.c
index 23c4bf2..32a6b42 100644
--- a/jdinput.c
+++ b/jdinput.c
@@ -4,7 +4,8 @@
  * This file was part of the Independent JPEG Group's software:
  * Copyright (C) 1991-1997, Thomas G. Lane.
  * libjpeg-turbo Modifications:
- * Copyright (C) 2010, D. R. Commander.
+ * Copyright (C) 2010, 2016, D. R. Commander.
+ * Copyright (C) 2015, Google, Inc.
  * For conditions of distribution and use, see the accompanying README.ijg
  * file.
  *
@@ -105,6 +106,11 @@
     compptr->height_in_blocks = (JDIMENSION)
       jdiv_round_up((long) cinfo->image_height * (long) compptr->v_samp_factor,
                     (long) (cinfo->max_v_samp_factor * DCTSIZE));
+    /* Set the first and last MCU columns to decompress from multi-scan images.
+     * By default, decompress all of the MCU columns.
+     */
+    cinfo->master->first_MCU_col[ci] = 0;
+    cinfo->master->last_MCU_col[ci] = compptr->width_in_blocks - 1;
     /* 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.
diff --git a/jdmaster.c b/jdmaster.c
index 69b4f70..7908849 100644
--- a/jdmaster.c
+++ b/jdmaster.c
@@ -5,8 +5,9 @@
  * Copyright (C) 1991-1997, Thomas G. Lane.
  * Modified 2002-2009 by Guido Vollbeding.
  * libjpeg-turbo Modifications:
- * Copyright (C) 2009-2011, D. R. Commander.
+ * Copyright (C) 2009-2011, 2016, D. R. Commander.
  * Copyright (C) 2013, Linaro Limited.
+ * Copyright (C) 2015, Google, Inc.
  * For conditions of distribution and use, see the accompanying README.ijg
  * file.
  *
@@ -20,25 +21,7 @@
 #include "jinclude.h"
 #include "jpeglib.h"
 #include "jpegcomp.h"
-
-
-/* Private state */
-
-typedef struct {
-  struct jpeg_decomp_master pub; /* public fields */
-
-  int pass_number;              /* # of passes completed */
-
-  boolean using_merged_upsample; /* TRUE if using merged upsample/cconvert */
-
-  /* Saved references to initialized quantizer modules,
-   * in case we need to switch modes.
-   */
-  struct jpeg_color_quantizer *quantizer_1pass;
-  struct jpeg_color_quantizer *quantizer_2pass;
-} my_decomp_master;
-
-typedef my_decomp_master *my_master_ptr;
+#include "jdmaster.h"
 
 
 /*
@@ -579,6 +562,12 @@
   /* Initialize input side of decompressor to consume first scan. */
   (*cinfo->inputctl->start_input_pass) (cinfo);
 
+  /* Set the first and last iMCU columns to decompress from single-scan images.
+   * By default, decompress all of the iMCU columns.
+   */
+  cinfo->master->first_iMCU_col = 0;
+  cinfo->master->last_iMCU_col = cinfo->MCUs_per_row - 1;
+
 #ifdef D_MULTISCAN_FILES_SUPPORTED
   /* If jpeg_start_decompress will read the whole file, initialize
    * progress monitoring appropriately.  The input step is counted
@@ -723,16 +712,13 @@
 GLOBAL(void)
 jinit_master_decompress (j_decompress_ptr cinfo)
 {
-  my_master_ptr master;
+  my_master_ptr master = (my_master_ptr) cinfo->master;
 
-  master = (my_master_ptr)
-      (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE,
-                                  sizeof(my_decomp_master));
-  cinfo->master = (struct jpeg_decomp_master *) master;
   master->pub.prepare_for_output_pass = prepare_for_output_pass;
   master->pub.finish_output_pass = finish_output_pass;
 
   master->pub.is_dummy_pass = FALSE;
+  master->pub.jinit_upsampler_no_alloc = FALSE;
 
   master_selection(cinfo);
 }
diff --git a/jdmaster.h b/jdmaster.h
new file mode 100644
index 0000000..76897e2
--- /dev/null
+++ b/jdmaster.h
@@ -0,0 +1,28 @@
+/*
+ * jdmaster.h
+ *
+ * This file was part of the Independent JPEG Group's software:
+ * Copyright (C) 1991-1995, Thomas G. Lane.
+ * For conditions of distribution and use, see the accompanying README.ijg
+ * file.
+ *
+ * This file contains the master control structure for the JPEG decompressor.
+ */
+
+/* Private state */
+
+typedef struct {
+  struct jpeg_decomp_master pub; /* public fields */
+
+  int pass_number;              /* # of passes completed */
+
+  boolean using_merged_upsample; /* TRUE if using merged upsample/cconvert */
+
+  /* Saved references to initialized quantizer modules,
+   * in case we need to switch modes.
+   */
+  struct jpeg_color_quantizer *quantizer_1pass;
+  struct jpeg_color_quantizer *quantizer_2pass;
+} my_decomp_master;
+
+typedef my_decomp_master *my_master_ptr;
diff --git a/jdsample.c b/jdsample.c
index 0eb56b7..39b3725 100644
--- a/jdsample.c
+++ b/jdsample.c
@@ -7,6 +7,7 @@
  * Copyright 2009 Pierre Ossman <ossman@cendio.se> for Cendio AB
  * Copyright (C) 2010, 2015-2016, D. R. Commander.
  * Copyright (C) 2014, MIPS Technologies, Inc., California
+ * Copyright (C) 2015, Google, Inc.
  * For conditions of distribution and use, see the accompanying README.ijg
  * file.
  *
@@ -373,13 +374,16 @@
   boolean need_buffer, do_fancy;
   int h_in_group, v_in_group, h_out_group, v_out_group;
 
-  upsample = (my_upsample_ptr)
-    (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE,
-                                sizeof(my_upsampler));
-  cinfo->upsample = (struct jpeg_upsampler *) upsample;
-  upsample->pub.start_pass = start_pass_upsample;
-  upsample->pub.upsample = sep_upsample;
-  upsample->pub.need_context_rows = FALSE; /* until we find out differently */
+  if (!cinfo->master->jinit_upsampler_no_alloc) {
+    upsample = (my_upsample_ptr)
+      (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE,
+                                  sizeof(my_upsampler));
+    cinfo->upsample = (struct jpeg_upsampler *) upsample;
+    upsample->pub.start_pass = start_pass_upsample;
+    upsample->pub.upsample = sep_upsample;
+    upsample->pub.need_context_rows = FALSE; /* until we find out differently */
+  } else
+    upsample = (my_upsample_ptr) cinfo->upsample;
 
   if (cinfo->CCIR601_sampling)  /* this isn't supported */
     ERREXIT(cinfo, JERR_CCIR601_NOTIMPL);
@@ -455,7 +459,7 @@
       upsample->v_expand[ci] = (UINT8) (v_out_group / v_in_group);
     } else
       ERREXIT(cinfo, JERR_FRACT_SAMPLE_NOTIMPL);
-    if (need_buffer) {
+    if (need_buffer && !cinfo->master->jinit_upsampler_no_alloc) {
       upsample->color_buf[ci] = (*cinfo->mem->alloc_sarray)
         ((j_common_ptr) cinfo, JPOOL_IMAGE,
          (JDIMENSION) jround_up((long) cinfo->output_width,
diff --git a/jpegint.h b/jpegint.h
index 1ad07ee..c3b4320 100644
--- a/jpegint.h
+++ b/jpegint.h
@@ -5,7 +5,8 @@
  * Copyright (C) 1991-1997, Thomas G. Lane.
  * Modified 1997-2009 by Guido Vollbeding.
  * libjpeg-turbo Modifications:
- * Copyright (C) 2015, D. R. Commander
+ * Copyright (C) 2015-2016, D. R. Commander
+ * Copyright (C) 2015, Google, Inc.
  * For conditions of distribution and use, see the accompanying README.ijg
  * file.
  *
@@ -150,6 +151,13 @@
 
   /* State variables made visible to other modules */
   boolean is_dummy_pass;        /* True during 1st pass for 2-pass quant */
+
+  /* Partial decompression variables */
+  JDIMENSION first_iMCU_col;
+  JDIMENSION last_iMCU_col;
+  JDIMENSION first_MCU_col[MAX_COMPS_IN_SCAN];
+  JDIMENSION last_MCU_col[MAX_COMPS_IN_SCAN];
+  boolean jinit_upsampler_no_alloc;
 };
 
 /* Input control module */
diff --git a/jpeglib.h b/jpeglib.h
index 01b4c3d..6c63f58 100644
--- a/jpeglib.h
+++ b/jpeglib.h
@@ -5,7 +5,7 @@
  * Copyright (C) 1991-1998, Thomas G. Lane.
  * Modified 2002-2009 by Guido Vollbeding.
  * libjpeg-turbo Modifications:
- * Copyright (C) 2009-2011, 2013-2014, D. R. Commander.
+ * Copyright (C) 2009-2011, 2013-2014, 2016, D. R. Commander.
  * Copyright (C) 2015, Google, Inc.
  * For conditions of distribution and use, see the accompanying README.ijg
  * file.
@@ -995,6 +995,8 @@
                                         JDIMENSION max_lines);
 EXTERN(JDIMENSION) jpeg_skip_scanlines (j_decompress_ptr cinfo,
                                         JDIMENSION num_lines);
+EXTERN(void) jpeg_crop_scanline (j_decompress_ptr cinfo, JDIMENSION *xoffset,
+                                 JDIMENSION *width);
 EXTERN(boolean) jpeg_finish_decompress (j_decompress_ptr cinfo);
 
 /* Replaces jpeg_read_scanlines when reading raw downsampled data. */
diff --git a/jversion.h b/jversion.h
index b5ca980..6ce663d 100644
--- a/jversion.h
+++ b/jversion.h
@@ -38,6 +38,7 @@
 #define JCOPYRIGHT      "Copyright (C) 2009-2016 D. R. Commander\n" \
                         "Copyright (C) 2011-2016 Siarhei Siamashka\n" \
                         "Copyright (C) 2015-2016 Matthieu Darbois\n" \
+                        "Copyright (C) 2015 Google, Inc.\n" \
                         "Copyright (C) 2013-2014 MIPS Technologies, Inc.\n" \
                         "Copyright (C) 2013 Linaro Limited\n" \
                         "Copyright (C) 2009-2011 Nokia Corporation and/or its subsidiary(-ies)\n" \
diff --git a/libjpeg.txt b/libjpeg.txt
index da577ef..71d37c6 100644
--- a/libjpeg.txt
+++ b/libjpeg.txt
@@ -3,7 +3,7 @@
 This file was part of the Independent JPEG Group's software:
 Copyright (C) 1994-2013, Thomas G. Lane, Guido Vollbeding.
 libjpeg-turbo Modifications:
-Copyright (C) 2010, 2014, 2015, D. R. Commander.
+Copyright (C) 2010, 2014-2016, D. R. Commander.
 Copyright (C) 2015, Google, Inc.
 For conditions of distribution and use, see the accompanying README.ijg file.
 
@@ -730,16 +730,21 @@
 The previous discussion of aborting compression cycles applies here too.
 
 
-Skipping rows when decompressing
---------------------------------
+Partial image decompression
+---------------------------
 
-jpeg_skip_scanlines(j_decompress_ptr cinfo, JDIMENSION num_lines);
+Partial image decompression is convenient for performance-critical applications
+that wish to view only a portion of a large JPEG image without decompressing
+the whole thing.  It it also useful in memory-constrained environments (such as
+on mobile devices.)  This library provides the following functions to support
+partial image decompression:
+
+1. Skipping rows when decompressing
+
+        jpeg_skip_scanlines(j_decompress_ptr cinfo, JDIMENSION num_lines);
 
 This function provides application programmers with the ability to skip over
-multiple rows in the JPEG image, thus decoding only a subset of the image data.
-This is convenient for performance-critical applications that wish to view only
-a portion of a large JPEG image without decompressing the whole thing.  It it
-also useful in memory-constrained environments (such as on mobile devices.)
+multiple rows in the JPEG image.
 
 Suspending data sources are not supported by this function.  Calling
 jpeg_skip_scanlines() with a suspending data source will result in undefined
@@ -772,6 +777,43 @@
 context rows.  In the worst case, jpeg_skip_scanlines() will perform similarly
 to jpeg_read_scanlines() (since it will actually call jpeg_read_scanlines().)
 
+2. Decompressing partial scanlines
+
+        jpeg_crop_scanline (j_decompress_ptr cinfo, JDIMENSION *xoffset,
+                            JDIMENSION *width)
+
+This function provides application programmers with the ability to decompress
+only a portion of each row in the JPEG image.  It must be called after
+jpeg_start_decompress() and before any calls to jpeg_read_scanlines() or
+jpeg_skip_scanlines().
+
+If xoffset and width do not form a valid subset of the image row, then this
+function will generate an error.  Note that if the output image is scaled, then
+xoffset and width are relative to the scaled image dimensions.
+
+xoffset and width are passed by reference because xoffset must fall on an iMCU
+boundary.  If it doesn't, then it will be moved left to the nearest iMCU
+boundary, and width will be increased accordingly.  If the calling program does
+not like the adjusted values of xoffset and width, then it can call
+jpeg_crop_scanline() again with new values (for instance, if it wants to move
+xoffset to the nearest iMCU boundary to the right instead of to the left.)
+
+After calling this function, cinfo->output_width will be set to the adjusted
+width.  This value should be used when allocating an output buffer to pass to
+jpeg_read_scanlines().
+
+The output image from a partial-width decompression will be identical to the
+corresponding image region from a full decode, with one exception:  The "fancy"
+(smooth) h2v2 (4:2:0) and h2v1 (4:2:2) upsampling algorithms fill in the
+missing chroma components by averaging the chroma components from neighboring
+pixels, except on the right and left edges of the image (where there are no
+neighboring pixels.)  When performing a partial-width decompression, these
+"fancy" upsampling algorithms may treat the left and right edges of the partial
+image region as if they are the left and right edges of the image, meaning that
+the upsampling algorithm may be simplified.  The result is that the pixels on
+the left or right edge of the partial image may not be exactly identical to the
+corresponding pixels in the original image.
+
 
 Mechanics of usage: include files, linking, etc
 -----------------------------------------------
diff --git a/win/jpeg62-memsrcdst.def b/win/jpeg62-memsrcdst.def
index 949d267..6499316 100755
--- a/win/jpeg62-memsrcdst.def
+++ b/win/jpeg62-memsrcdst.def
@@ -103,3 +103,4 @@
 	jpeg_mem_dest @ 102 ; 
 	jpeg_mem_src @ 103 ; 
 	jpeg_skip_scanlines @ 104 ; 
+	jpeg_crop_scanline @ 105 ; 
diff --git a/win/jpeg62.def b/win/jpeg62.def
index c64df50..9f30b1a 100755
--- a/win/jpeg62.def
+++ b/win/jpeg62.def
@@ -101,3 +101,4 @@
 	jround_up @ 100 ; 
 	jzero_far @ 101 ; 
 	jpeg_skip_scanlines @ 102 ; 
+	jpeg_crop_scanline @ 103 ; 
diff --git a/win/jpeg7.def b/win/jpeg7.def
index 8daa35f..92463c5 100644
--- a/win/jpeg7.def
+++ b/win/jpeg7.def
@@ -103,3 +103,4 @@
 	jround_up @ 102 ; 
 	jzero_far @ 103 ; 
 	jpeg_skip_scanlines @ 104 ; 
+	jpeg_crop_scanline @ 105 ; 
diff --git a/win/jpeg8.def b/win/jpeg8.def
index 01deb69..19246ac 100644
--- a/win/jpeg8.def
+++ b/win/jpeg8.def
@@ -106,3 +106,4 @@
 	jround_up @ 105 ; 
 	jzero_far @ 106 ; 
 	jpeg_skip_scanlines @ 107 ; 
+	jpeg_crop_scanline @ 108 ; 
diff --git a/wrppm.c b/wrppm.c
index 7d13e2d..40fbf1f 100644
--- a/wrppm.c
+++ b/wrppm.c
@@ -20,6 +20,7 @@
  */
 
 #include "cdjpeg.h"             /* Common decls for cjpeg/djpeg applications */
+#include "wrppm.h"
 
 #ifdef PPM_SUPPORTED
 
@@ -62,21 +63,6 @@
  */
 
 
-/* Private version of data destination object */
-
-typedef struct {
-  struct djpeg_dest_struct pub; /* public fields */
-
-  /* Usually these two pointers point to the same place: */
-  char *iobuffer;               /* fwrite's I/O buffer */
-  JSAMPROW pixrow;              /* decompressor output buffer */
-  size_t buffer_width;          /* width of I/O buffer */
-  JDIMENSION samples_per_row;   /* JSAMPLEs per output row */
-} ppm_dest_struct;
-
-typedef ppm_dest_struct *ppm_dest_ptr;
-
-
 /*
  * Write some pixel data.
  * In this module rows_supplied will always be 1.
diff --git a/wrppm.h b/wrppm.h
new file mode 100644
index 0000000..aa6c562
--- /dev/null
+++ b/wrppm.h
@@ -0,0 +1,26 @@
+/*
+ * wrppm.h
+ *
+ * This file was part of the Independent JPEG Group's software:
+ * Copyright (C) 1994, Thomas G. Lane.
+ * For conditions of distribution and use, see the accompanying README.ijg
+ * file.
+ */
+
+#ifdef PPM_SUPPORTED
+
+/* Private version of data destination object */
+
+typedef struct {
+  struct djpeg_dest_struct pub; /* public fields */
+
+  /* Usually these two pointers point to the same place: */
+  char *iobuffer;               /* fwrite's I/O buffer */
+  JSAMPROW pixrow;              /* decompressor output buffer */
+  size_t buffer_width;          /* width of I/O buffer */
+  JDIMENSION samples_per_row;   /* JSAMPLEs per output row */
+} ppm_dest_struct;
+
+typedef ppm_dest_struct *ppm_dest_ptr;
+
+#endif