JPEG YUV Decoding
authorsugoi <sugoi@chromium.org>
Thu, 16 Oct 2014 20:10:57 +0000 (13:10 -0700)
committerCommit bot <commit-bot@chromium.org>
Thu, 16 Oct 2014 20:10:57 +0000 (13:10 -0700)
Enabling JPEG YUV Decoding in Skia

BUG=skia:3005, skia:1674, skia:3029

Committed: https://skia.googlesource.com/skia/+/8e6c3b93a39e19111662a760ede97df55e51d39f

Review URL: https://codereview.chromium.org/399683007

include/core/SkImageDecoder.h
src/images/SkDecodingImageGenerator.cpp
src/images/SkImageDecoder.cpp
src/images/SkImageDecoder_libjpeg.cpp
tests/JpegTest.cpp

index 5910d33..9483176 100644 (file)
@@ -47,6 +47,15 @@ public:
     */
     virtual Format getFormat() const;
 
+    /** If planes or rowBytes is NULL, decodes the header and computes componentSizes
+        for memory allocation.
+        Otherwise, decodes the YUV planes into the provided image planes and
+        updates componentSizes to the final image size.
+        Returns whether the decoding was successful.
+    */
+    bool decodeYUV8Planes(SkStream* stream, SkISize componentSizes[3], void* planes[3],
+                          size_t rowBytes[3], SkYUVColorSpace*);
+
     /** Return the format of the SkStreamRewindable or kUnknown_Format if it cannot be determined.
         Rewinds the stream before returning.
     */
@@ -339,6 +348,17 @@ protected:
         return false;
     }
 
+    /** If planes or rowBytes is NULL, decodes the header and computes componentSizes
+        for memory allocation.
+        Otherwise, decodes the YUV planes into the provided image planes and
+        updates componentSizes to the final image size.
+        Returns whether the decoding was successful.
+    */
+    virtual bool onDecodeYUV8Planes(SkStream* stream, SkISize componentSizes[3], void* planes[3],
+                                    size_t rowBytes[3], SkYUVColorSpace*) {
+        return false;
+    }
+
     /*
      * Crop a rectangle from the src Bitmap to the dest Bitmap. src and dst are
      * both sampled by sampleSize from an original Bitmap.
index 3b5cb78..a90c1cf 100644 (file)
@@ -45,6 +45,8 @@ protected:
     virtual bool onGetPixels(const SkImageInfo& info,
                              void* pixels, size_t rowBytes,
                              SkPMColor ctable[], int* ctableCount) SK_OVERRIDE;
+    virtual bool onGetYUV8Planes(SkISize sizes[3], void* planes[3], size_t rowBytes[3],
+                                 SkYUVColorSpace* colorSpace) SK_OVERRIDE;
 
 private:
     typedef SkImageGenerator INHERITED;
@@ -204,6 +206,20 @@ bool DecodingImageGenerator::onGetPixels(const SkImageInfo& info,
     return true;
 }
 
+bool DecodingImageGenerator::onGetYUV8Planes(SkISize sizes[3], void* planes[3],
+                                             size_t rowBytes[3], SkYUVColorSpace* colorSpace) {
+    if (!fStream->rewind()) {
+        return false;
+    }
+
+    SkAutoTDelete<SkImageDecoder> decoder(SkImageDecoder::Factory(fStream));
+    if (NULL == decoder.get()) {
+        return false;
+    }
+
+    return decoder->decodeYUV8Planes(fStream, sizes, planes, rowBytes, colorSpace);
+}
+
 // A contructor-type function that returns NULL on failure.  This
 // prevents the returned SkImageGenerator from ever being in a bad
 // state.  Called by both Create() functions
index b25e7db..f9b04e3 100644 (file)
@@ -285,3 +285,11 @@ bool SkImageDecoder::DecodeStream(SkStreamRewindable* stream, SkBitmap* bm, SkCo
     }
     return success;
 }
+
+bool SkImageDecoder::decodeYUV8Planes(SkStream* stream, SkISize componentSizes[3], void* planes[3],
+                                      size_t rowBytes[3], SkYUVColorSpace* colorSpace) {
+    // we reset this to false before calling onDecodeYUV8Planes
+    fShouldCancelDecode = false;
+
+    return this->onDecodeYUV8Planes(stream, componentSizes, planes, rowBytes, colorSpace);
+}
index 99401e6..21e96be 100644 (file)
@@ -239,6 +239,9 @@ protected:
     virtual bool onDecodeSubset(SkBitmap* bitmap, const SkIRect& rect) SK_OVERRIDE;
 #endif
     virtual bool onDecode(SkStream* stream, SkBitmap* bm, Mode) SK_OVERRIDE;
+    virtual bool onDecodeYUV8Planes(SkStream* stream, SkISize componentSizes[3],
+                                    void* planes[3], size_t rowBytes[3],
+                                    SkYUVColorSpace* colorSpace) SK_OVERRIDE;
 
 private:
 #ifdef SK_BUILD_FOR_ANDROID
@@ -325,16 +328,21 @@ static bool skip_src_rows_tile(jpeg_decompress_struct* cinfo,
 // This guy exists just to aid in debugging, as it allows debuggers to just
 // set a break-point in one place to see all error exists.
 static bool return_false(const jpeg_decompress_struct& cinfo,
-                         const SkBitmap& bm, const char caller[]) {
+                         int width, int height, const char caller[]) {
     if (!(c_suppressJPEGImageDecoderErrors)) {
         char buffer[JMSG_LENGTH_MAX];
         cinfo.err->format_message((const j_common_ptr)&cinfo, buffer);
         SkDebugf("libjpeg error %d <%s> from %s [%d %d]\n",
-                 cinfo.err->msg_code, buffer, caller, bm.width(), bm.height());
+                 cinfo.err->msg_code, buffer, caller, width, height);
     }
     return false;   // must always return false
 }
 
+static bool return_false(const jpeg_decompress_struct& cinfo,
+                         const SkBitmap& bm, const char caller[]) {
+    return return_false(cinfo, bm.width(), bm.height(), caller);
+}
+
 // Convert a scanline of CMYK samples to RGBX in place. Note that this
 // method moves the "scanline" pointer in its processing
 static void convert_CMYK_to_RGB(uint8_t* scanline, unsigned int width) {
@@ -726,6 +734,246 @@ bool SkJPEGImageDecoder::onDecode(SkStream* stream, SkBitmap* bm, Mode mode) {
     return true;
 }
 
+enum SizeType {
+    kSizeForMemoryAllocation_SizeType,
+    kActualSize_SizeType
+};
+
+static SkISize compute_yuv_size(const jpeg_decompress_struct& info, int component,
+                                SizeType sizeType) {
+    if (sizeType == kSizeForMemoryAllocation_SizeType) {
+        return SkISize::Make(info.cur_comp_info[component]->width_in_blocks * DCTSIZE,
+                             info.cur_comp_info[component]->height_in_blocks * DCTSIZE);
+    }
+    return SkISize::Make(info.cur_comp_info[component]->downsampled_width,
+                         info.cur_comp_info[component]->downsampled_height);
+}
+
+// Enum for YUV decoding
+enum YUVSubsampling {
+    kUNKNOWN_YUVSubsampling,
+    k410_YUVSubsampling,
+    k411_YUVSubsampling,
+    k420_YUVSubsampling,
+    k422_YUVSubsampling,
+    k440_YUVSubsampling,
+    k444_YUVSubsampling
+};
+
+static YUVSubsampling yuv_subsampling(const jpeg_decompress_struct& info) {
+    if ((DCTSIZE == 8)
+        && (info.num_components == 3)
+        && (info.comps_in_scan >= info.num_components)
+        && (info.scale_denom <= 8)
+        && (info.cur_comp_info[0])
+        && (info.cur_comp_info[1])
+        && (info.cur_comp_info[2])
+        && (info.cur_comp_info[1]->h_samp_factor == 1)
+        && (info.cur_comp_info[1]->v_samp_factor == 1)
+        && (info.cur_comp_info[2]->h_samp_factor == 1)
+        && (info.cur_comp_info[2]->v_samp_factor == 1))
+    {
+        int h = info.cur_comp_info[0]->h_samp_factor;
+        int v = info.cur_comp_info[0]->v_samp_factor;
+        // 4:4:4 : (h == 1) && (v == 1)
+        // 4:4:0 : (h == 1) && (v == 2)
+        // 4:2:2 : (h == 2) && (v == 1)
+        // 4:2:0 : (h == 2) && (v == 2)
+        // 4:1:1 : (h == 4) && (v == 1)
+        // 4:1:0 : (h == 4) && (v == 2)
+        if (v == 1) {
+            switch (h) {
+                case 1:
+                    return k444_YUVSubsampling;
+                case 2:
+                    return k422_YUVSubsampling;
+                case 4:
+                    return k411_YUVSubsampling;
+                default:
+                    break;
+            }
+        } else if (v == 2) {
+            switch (h) {
+                case 1:
+                    return k440_YUVSubsampling;
+                case 2:
+                    return k420_YUVSubsampling;
+                case 4:
+                    return k410_YUVSubsampling;
+                default:
+                    break;
+            }
+        }
+    }
+
+    return kUNKNOWN_YUVSubsampling;
+}
+
+static void update_components_sizes(const jpeg_decompress_struct& cinfo, SkISize componentSizes[3],
+                                    SizeType sizeType) {
+    for (int i = 0; i < 3; ++i) {
+        componentSizes[i] = compute_yuv_size(cinfo, i, sizeType);
+    }
+}
+
+static bool output_raw_data(jpeg_decompress_struct& cinfo, void* planes[3], size_t rowBytes[3]) {
+    // U size and V size have to be the same if we're calling output_raw_data()
+    SkISize uvSize = compute_yuv_size(cinfo, 1, kSizeForMemoryAllocation_SizeType);
+    SkASSERT(uvSize == compute_yuv_size(cinfo, 2, kSizeForMemoryAllocation_SizeType));
+
+    JSAMPARRAY bufferraw[3];
+    JSAMPROW bufferraw2[32];
+    bufferraw[0] = &bufferraw2[0]; // Y channel rows (8 or 16)
+    bufferraw[1] = &bufferraw2[16]; // U channel rows (8)
+    bufferraw[2] = &bufferraw2[24]; // V channel rows (8)
+    int yWidth = cinfo.output_width;
+    int yHeight = cinfo.output_height;
+    int yMaxH = yHeight - 1;
+    int v = cinfo.cur_comp_info[0]->v_samp_factor;
+    int uvMaxH = uvSize.height() - 1;
+    JSAMPROW outputY = static_cast<JSAMPROW>(planes[0]);
+    JSAMPROW outputU = static_cast<JSAMPROW>(planes[1]);
+    JSAMPROW outputV = static_cast<JSAMPROW>(planes[2]);
+    size_t rowBytesY = rowBytes[0];
+    size_t rowBytesU = rowBytes[1];
+    size_t rowBytesV = rowBytes[2];
+
+    int yScanlinesToRead = DCTSIZE * v;
+    SkAutoMalloc lastRowStorage(yWidth * 8);
+    JSAMPROW yLastRow = (JSAMPROW)lastRowStorage.get();
+    JSAMPROW uLastRow = yLastRow + 2 * yWidth;
+    JSAMPROW vLastRow = uLastRow + 2 * yWidth;
+    JSAMPROW dummyRow = vLastRow + 2 * yWidth;
+
+    while (cinfo.output_scanline < cinfo.output_height) {
+        // Request 8 or 16 scanlines: returns 0 or more scanlines.
+        bool hasYLastRow(false), hasUVLastRow(false);
+        // Assign 8 or 16 rows of memory to read the Y channel.
+        for (int i = 0; i < yScanlinesToRead; ++i) {
+            int scanline = (cinfo.output_scanline + i);
+            if (scanline < yMaxH) {
+                bufferraw2[i] = &outputY[scanline * rowBytesY];
+            } else if (scanline == yMaxH) {
+                bufferraw2[i] = yLastRow;
+                hasYLastRow = true;
+            } else {
+                bufferraw2[i] = dummyRow;
+            }
+        }
+        int scaledScanline = cinfo.output_scanline / v;
+        // Assign 8 rows of memory to read the U and V channels.
+        for (int i = 0; i < 8; ++i) {
+            int scanline = (scaledScanline + i);
+            if (scanline < uvMaxH) {
+                bufferraw2[16 + i] = &outputU[scanline * rowBytesU];
+                bufferraw2[24 + i] = &outputV[scanline * rowBytesV];
+            } else if (scanline == uvMaxH) {
+                bufferraw2[16 + i] = uLastRow;
+                bufferraw2[24 + i] = vLastRow;
+                hasUVLastRow = true;
+            } else {
+                bufferraw2[16 + i] = dummyRow;
+                bufferraw2[24 + i] = dummyRow;
+            }
+        }
+        JDIMENSION scanlinesRead = jpeg_read_raw_data(&cinfo, bufferraw, yScanlinesToRead);
+
+        if (scanlinesRead == 0)
+            return false;
+
+        if (hasYLastRow) {
+            memcpy(&outputY[yMaxH * rowBytesY], yLastRow, yWidth);
+        }
+        if (hasUVLastRow) {
+            memcpy(&outputU[uvMaxH * rowBytesU], uLastRow, uvSize.width());
+            memcpy(&outputV[uvMaxH * rowBytesV], vLastRow, uvSize.width());
+        }
+    }
+
+    cinfo.output_scanline = SkMin32(cinfo.output_scanline, cinfo.output_height);
+
+    return true;
+}
+
+bool SkJPEGImageDecoder::onDecodeYUV8Planes(SkStream* stream, SkISize componentSizes[3],
+                                            void* planes[3], size_t rowBytes[3],
+                                            SkYUVColorSpace* colorSpace) {
+#ifdef TIME_DECODE
+    SkAutoTime atm("JPEG YUV8 Decode");
+#endif
+
+    if (this->getSampleSize() != 1) {
+        return false; // Resizing not supported
+    }
+
+    JPEGAutoClean autoClean;
+
+    jpeg_decompress_struct  cinfo;
+    skjpeg_source_mgr       srcManager(stream, this);
+
+    skjpeg_error_mgr errorManager;
+    set_error_mgr(&cinfo, &errorManager);
+
+    // All objects need to be instantiated before this setjmp call so that
+    // they will be cleaned up properly if an error occurs.
+    if (setjmp(errorManager.fJmpBuf)) {
+        return return_false(cinfo, 0, 0, "setjmp YUV8");
+    }
+
+    initialize_info(&cinfo, &srcManager);
+    autoClean.set(&cinfo);
+
+    int status = jpeg_read_header(&cinfo, true);
+    if (status != JPEG_HEADER_OK) {
+        return return_false(cinfo, 0, 0, "read_header YUV8");
+    }
+
+    if (cinfo.jpeg_color_space != JCS_YCbCr) {
+        // It's not an error to not be encoded in YUV, so no need to use return_false()
+        return false;
+    }
+
+    cinfo.out_color_space = JCS_YCbCr;
+    cinfo.raw_data_out = TRUE;
+
+    if (!planes || !planes[0] || !rowBytes || !rowBytes[0]) { // Compute size only
+        update_components_sizes(cinfo, componentSizes, kSizeForMemoryAllocation_SizeType);
+        return true;
+    }
+
+    set_dct_method(*this, &cinfo);
+
+    SkASSERT(1 == cinfo.scale_num);
+    cinfo.scale_denom = 1;
+
+    turn_off_visual_optimizations(&cinfo);
+
+#ifdef ANDROID_RGB
+    cinfo.dither_mode = JDITHER_NONE;
+#endif
+
+    /*  image_width and image_height are the original dimensions, available
+        after jpeg_read_header(). To see the scaled dimensions, we have to call
+        jpeg_start_decompress(), and then read output_width and output_height.
+    */
+    if (!jpeg_start_decompress(&cinfo)) {
+        return return_false(cinfo, 0, 0, "start_decompress YUV8");
+    }
+
+    if (!output_raw_data(cinfo, planes, rowBytes)) {
+        return return_false(cinfo, 0, 0, "output_raw_data");
+    }
+
+    update_components_sizes(cinfo, componentSizes, kActualSize_SizeType);
+    jpeg_finish_decompress(&cinfo);
+
+    if (NULL != colorSpace) {
+        *colorSpace = kJPEG_SkYUVColorSpace;
+    }
+
+    return true;
+}
+
 #ifdef SK_BUILD_FOR_ANDROID
 bool SkJPEGImageDecoder::onBuildTileIndex(SkStreamRewindable* stream, int *width, int *height) {
 
index f8784a2..8828926 100644 (file)
@@ -6,11 +6,12 @@
  */
 
 #include "SkBitmap.h"
-#include "SkData.h"
+#include "SkDecodingImageGenerator.h"
 #include "SkForceLinking.h"
-#include "SkImage.h"
 #include "SkImageDecoder.h"
+#include "SkPixelRef.h"
 #include "SkStream.h"
+#include "SkTemplates.h"
 #include "Test.h"
 
 __SK_FORCE_IMAGE_DECODER_LINKING;
@@ -452,3 +453,47 @@ DEF_TEST(Jpeg, reporter) {
     SkASSERT(writeSuccess);
     #endif
 }
+
+DEF_TEST(Jpeg_YUV, reporter) {
+    size_t len = sizeof(goodJpegImage);
+    SkMemoryStream* stream = SkNEW_ARGS(SkMemoryStream, (goodJpegImage, len));
+
+    SkBitmap bitmap;
+    SkDecodingImageGenerator::Options opts;
+    bool pixelsInstalled = SkInstallDiscardablePixelRef(
+                SkDecodingImageGenerator::Create(stream, opts), &bitmap);
+    REPORTER_ASSERT(reporter, pixelsInstalled);
+
+    if (!pixelsInstalled) {
+        return;
+    }
+
+    SkPixelRef* pixelRef = bitmap.pixelRef();
+    SkISize yuvSizes[3];
+    bool sizesComputed = (NULL != pixelRef) && pixelRef->getYUV8Planes(yuvSizes, NULL, NULL, NULL);
+    REPORTER_ASSERT(reporter, sizesComputed);
+
+    if (!sizesComputed) {
+        return;
+    }
+
+    // Allocate the memory for YUV
+    size_t totalSize(0);
+    size_t sizes[3], rowBytes[3];
+    const int32_t expected_sizes[] = {128, 64, 64};
+    for (int i = 0; i < 3; ++i) {
+        rowBytes[i] = yuvSizes[i].fWidth;
+        totalSize  += sizes[i] = rowBytes[i] * yuvSizes[i].fHeight;
+        REPORTER_ASSERT(reporter, rowBytes[i]         == (size_t)expected_sizes[i]);
+        REPORTER_ASSERT(reporter, yuvSizes[i].fWidth  == expected_sizes[i]);
+        REPORTER_ASSERT(reporter, yuvSizes[i].fHeight == expected_sizes[i]);
+    }
+    SkAutoMalloc storage(totalSize);
+    void* planes[3];
+    planes[0] = storage.get();
+    planes[1] = (uint8_t*)planes[0] + sizes[0];
+    planes[2] = (uint8_t*)planes[1] + sizes[1];
+
+    // Get the YUV planes
+    REPORTER_ASSERT(reporter, pixelRef->getYUV8Planes(yuvSizes, planes, rowBytes, NULL));
+}