Update SkJPEGImageEncoder

(1) Use libjpeg-turbo color space extensions when possible.
(2) Use transform_scanline_procs when pre-libjpeg-turbo transformation
    are required.
(3) Add support for Gray and F16.

BUG=skia:

Change-Id: I77b977cb8c9e0afc17d907dd73a1cf3f16539bcc
Reviewed-on: https://skia-review.googlesource.com/7642
Reviewed-by: Leon Scroggins <scroggo@google.com>
Commit-Queue: Matt Sarett <msarett@google.com>
diff --git a/src/images/SkJPEGImageEncoder.cpp b/src/images/SkJPEGImageEncoder.cpp
index 345f869..c37aa46 100644
--- a/src/images/SkJPEGImageEncoder.cpp
+++ b/src/images/SkJPEGImageEncoder.cpp
@@ -13,6 +13,7 @@
 #include "SkJPEGWriteUtility.h"
 #include "SkStream.h"
 #include "SkTemplates.h"
+#include "transform_scanline.h"
 
 #include <stdio.h>
 
@@ -21,75 +22,82 @@
     #include "jerror.h"
 }
 
-typedef void (*WriteScanline)(uint8_t* SK_RESTRICT dst,
-                              const void* SK_RESTRICT src, int width,
-                              const SkPMColor* SK_RESTRICT ctable);
-
-static void Write_32_RGB(uint8_t* SK_RESTRICT dst,
-                         const void* SK_RESTRICT srcRow, int width,
-                         const SkPMColor*) {
-    const uint32_t* SK_RESTRICT src = (const uint32_t*)srcRow;
-    while (--width >= 0) {
-        uint32_t c = *src++;
-        dst[0] = SkGetPackedR32(c);
-        dst[1] = SkGetPackedG32(c);
-        dst[2] = SkGetPackedB32(c);
-        dst += 3;
-    }
-}
-
-static void Write_4444_RGB(uint8_t* SK_RESTRICT dst,
-                           const void* SK_RESTRICT srcRow, int width,
-                           const SkPMColor*) {
-    const SkPMColor16* SK_RESTRICT src = (const SkPMColor16*)srcRow;
-    while (--width >= 0) {
-        SkPMColor16 c = *src++;
-        dst[0] = SkPacked4444ToR32(c);
-        dst[1] = SkPacked4444ToG32(c);
-        dst[2] = SkPacked4444ToB32(c);
-        dst += 3;
-    }
-}
-
-static void Write_16_RGB(uint8_t* SK_RESTRICT dst,
-                         const void* SK_RESTRICT srcRow, int width,
-                         const SkPMColor*) {
-    const uint16_t* SK_RESTRICT src = (const uint16_t*)srcRow;
-    while (--width >= 0) {
-        uint16_t c = *src++;
-        dst[0] = SkPacked16ToR32(c);
-        dst[1] = SkPacked16ToG32(c);
-        dst[2] = SkPacked16ToB32(c);
-        dst += 3;
-    }
-}
-
-static void Write_Index_RGB(uint8_t* SK_RESTRICT dst,
-                            const void* SK_RESTRICT srcRow, int width,
-                            const SkPMColor* SK_RESTRICT ctable) {
-    const uint8_t* SK_RESTRICT src = (const uint8_t*)srcRow;
-    while (--width >= 0) {
-        uint32_t c = ctable[*src++];
-        dst[0] = SkGetPackedR32(c);
-        dst[1] = SkGetPackedG32(c);
-        dst[2] = SkGetPackedB32(c);
-        dst += 3;
-    }
-}
-
-static WriteScanline ChooseWriter(SkColorType ct) {
-    switch (ct) {
-        case kN32_SkColorType:
-            return Write_32_RGB;
+/**
+ *  Returns true if |info| is supported by the jpeg encoder and false otherwise.
+ *  |jpegColorType| will be set to the proper libjpeg-turbo type for input to the library.
+ *  |numComponents| will be set to the number of components in the |jpegColorType|.
+ *  |proc|          will be set if we need to pre-convert the input before passing to
+ *                  libjpeg-turbo.  Otherwise will be set to nullptr.
+ */
+// TODO (skbug.com/1501):
+// Should we fail on non-opaque encodes?
+// Or should we change alpha behavior (ex: unpremultiply when the input is premul)?
+// Or is ignoring the alpha type and alpha channel ok here?
+static bool set_encode_config(J_COLOR_SPACE* jpegColorType, int* numComponents,
+                              transform_scanline_proc* proc, const SkImageInfo& info) {
+    *proc = nullptr;
+    switch (info.colorType()) {
+        case kRGBA_8888_SkColorType:
+            *jpegColorType = JCS_EXT_RGBA;
+            *numComponents = 4;
+            return true;
+        case kBGRA_8888_SkColorType:
+            *jpegColorType = JCS_EXT_BGRA;
+            *numComponents = 4;
+            return true;
         case kRGB_565_SkColorType:
-            return Write_16_RGB;
+            *proc = transform_scanline_565;
+            *jpegColorType = JCS_RGB;
+            *numComponents = 3;
+            return true;
         case kARGB_4444_SkColorType:
-            return Write_4444_RGB;
+            *proc = transform_scanline_444;
+            *jpegColorType = JCS_RGB;
+            *numComponents = 3;
+            return true;
         case kIndex_8_SkColorType:
-            return Write_Index_RGB;
+            *proc = transform_scanline_index8_opaque;
+            *jpegColorType = JCS_RGB;
+            *numComponents = 3;
+            return true;
+        case kGray_8_SkColorType:
+            SkASSERT(info.isOpaque());
+            *jpegColorType = JCS_GRAYSCALE;
+            *numComponents = 1;
+            return true;
+        case kRGBA_F16_SkColorType:
+            if (!info.colorSpace() || !info.colorSpace()->gammaIsLinear()) {
+                return false;
+            }
+
+            *proc = transform_scanline_F16_to_8888;
+            *jpegColorType = JCS_EXT_RGBA;
+            *numComponents = 4;
+            return true;
         default:
-            return nullptr;
+            return false;
     }
+
+
+}
+
+bool SkEncodeImageAsJPEG(SkWStream* stream, const SkPixmap& pixmap, const SkEncodeOptions& opts) {
+    SkASSERT(!pixmap.colorSpace() || pixmap.colorSpace()->gammaCloseToSRGB() ||
+            pixmap.colorSpace()->gammaIsLinear());
+
+    SkPixmap src = pixmap;
+    if (SkEncodeOptions::PremulBehavior::kLegacy == opts.fPremulBehavior) {
+        src.setColorSpace(nullptr);
+    } else {
+        // kGammaCorrect behavior requires a color space.  It's not actually critical in the
+        // jpeg case (since jpegs are opaque), but Skia color correct behavior generally
+        // requires pixels to be tagged with color spaces.
+        if (!src.colorSpace()) {
+            return false;
+        }
+    }
+
+    return SkEncodeImageAsJPEG(stream, src, 100);
 }
 
 bool SkEncodeImageAsJPEG(SkWStream* stream, const SkPixmap& pixmap, int quality) {
@@ -100,8 +108,8 @@
     skjpeg_error_mgr        sk_err;
     skjpeg_destination_mgr  sk_wstream(stream);
 
-    // allocate these before set call setjmp
-    SkAutoTMalloc<uint8_t>  oneRow;
+    // Declare before calling setjmp.
+    SkAutoTMalloc<uint8_t>  storage;
 
     cinfo.err = jpeg_std_error(&sk_err);
     sk_err.error_exit = skjpeg_error_exit;
@@ -109,9 +117,10 @@
         return false;
     }
 
-    // Keep after setjmp or mark volatile.
-    const WriteScanline writer = ChooseWriter(pixmap.colorType());
-    if (!writer) {
+    J_COLOR_SPACE jpegColorSpace;
+    int numComponents;
+    transform_scanline_proc proc;
+    if (!set_encode_config(&jpegColorSpace, &numComponents, &proc, pixmap.info())) {
         return false;
     }
 
@@ -119,13 +128,8 @@
     cinfo.dest = &sk_wstream;
     cinfo.image_width = pixmap.width();
     cinfo.image_height = pixmap.height();
-    cinfo.input_components = 3;
-
-    // FIXME: Can we take advantage of other in_color_spaces in libjpeg-turbo?
-    cinfo.in_color_space = JCS_RGB;
-
-    // The gamma value is ignored by libjpeg-turbo.
-    cinfo.input_gamma = 1;
+    cinfo.input_components = numComponents;
+    cinfo.in_color_space = jpegColorSpace;
 
     jpeg_set_defaults(&cinfo);
 
@@ -137,19 +141,21 @@
 
     jpeg_start_compress(&cinfo, TRUE);
 
-    const int       width = pixmap.width();
-    uint8_t*        oneRowP = oneRow.reset(width * 3);
+    if (proc) {
+        storage.reset(numComponents * pixmap.width());
+    }
 
+    const void* srcRow = pixmap.addr();
     const SkPMColor* colors = pixmap.ctable() ? pixmap.ctable()->readColors() : nullptr;
-    const void*      srcRow = pixmap.addr();
-
     while (cinfo.next_scanline < cinfo.image_height) {
-        JSAMPROW row_pointer[1];    /* pointer to JSAMPLE row[s] */
+        JSAMPLE* jpegSrcRow = (JSAMPLE*) srcRow;
+        if (proc) {
+            proc((char*)storage.get(), (const char*)srcRow, pixmap.width(), numComponents, colors);
+            jpegSrcRow = storage.get();
+        }
 
-        writer(oneRowP, srcRow, width, colors);
-        row_pointer[0] = oneRowP;
-        (void) jpeg_write_scanlines(&cinfo, row_pointer, 1);
-        srcRow = (const void*)((const char*)srcRow + pixmap.rowBytes());
+        (void) jpeg_write_scanlines(&cinfo, &jpegSrcRow, 1);
+        srcRow = SkTAddOffset<const void>(srcRow, pixmap.rowBytes());
     }
 
     jpeg_finish_compress(&cinfo);