Home | History | Annotate | Download | only in images
      1 /*
      2  * Copyright 2007 The Android Open Source Project
      3  *
      4  * Use of this source code is governed by a BSD-style license that can be
      5  * found in the LICENSE file.
      6  */
      7 
      8 #include "SkImageEncoderPriv.h"
      9 
     10 #ifdef SK_HAS_JPEG_LIBRARY
     11 
     12 #include "SkColorPriv.h"
     13 #include "SkColorSpace_Base.h"
     14 #include "SkImageEncoderFns.h"
     15 #include "SkImageInfoPriv.h"
     16 #include "SkJpegEncoder.h"
     17 #include "SkJPEGWriteUtility.h"
     18 #include "SkStream.h"
     19 #include "SkTemplates.h"
     20 
     21 #include <stdio.h>
     22 
     23 extern "C" {
     24     #include "jpeglib.h"
     25     #include "jerror.h"
     26 }
     27 
     28 class SkJpegEncoderMgr final : SkNoncopyable {
     29 public:
     30 
     31     /*
     32      * Create the decode manager
     33      * Does not take ownership of stream
     34      */
     35     static std::unique_ptr<SkJpegEncoderMgr> Make(SkWStream* stream) {
     36         return std::unique_ptr<SkJpegEncoderMgr>(new SkJpegEncoderMgr(stream));
     37     }
     38 
     39     bool setParams(const SkImageInfo& srcInfo, const SkJpegEncoder::Options& options);
     40 
     41     jpeg_compress_struct* cinfo() { return &fCInfo; }
     42 
     43     jmp_buf& jmpBuf() { return fErrMgr.fJmpBuf; }
     44 
     45     transform_scanline_proc proc() const { return fProc; }
     46 
     47     ~SkJpegEncoderMgr() {
     48         jpeg_destroy_compress(&fCInfo);
     49     }
     50 
     51 private:
     52 
     53     SkJpegEncoderMgr(SkWStream* stream)
     54         : fDstMgr(stream)
     55         , fProc(nullptr)
     56     {
     57         fCInfo.err = jpeg_std_error(&fErrMgr);
     58         fErrMgr.error_exit = skjpeg_error_exit;
     59         jpeg_create_compress(&fCInfo);
     60         fCInfo.dest = &fDstMgr;
     61     }
     62 
     63     jpeg_compress_struct    fCInfo;
     64     skjpeg_error_mgr        fErrMgr;
     65     skjpeg_destination_mgr  fDstMgr;
     66     transform_scanline_proc fProc;
     67 };
     68 
     69 bool SkJpegEncoderMgr::setParams(const SkImageInfo& srcInfo, const SkJpegEncoder::Options& options)
     70 {
     71     auto chooseProc8888 = [&]() {
     72         if (kUnpremul_SkAlphaType != srcInfo.alphaType() ||
     73             SkJpegEncoder::AlphaOption::kIgnore == options.fAlphaOption)
     74         {
     75             return (transform_scanline_proc) nullptr;
     76         }
     77 
     78         // Note that kRespect mode is only supported with sRGB or linear transfer functions.
     79         // The legacy code path is incidentally correct when the transfer function is linear.
     80         const bool isSRGBTransferFn = srcInfo.gammaCloseToSRGB() &&
     81                 (SkTransferFunctionBehavior::kRespect == options.fBlendBehavior);
     82         if (isSRGBTransferFn) {
     83             return transform_scanline_to_premul_linear;
     84         } else {
     85             return transform_scanline_to_premul_legacy;
     86         }
     87     };
     88 
     89     J_COLOR_SPACE jpegColorType = JCS_EXT_RGBA;
     90     int numComponents = 0;
     91     switch (srcInfo.colorType()) {
     92         case kRGBA_8888_SkColorType:
     93             fProc = chooseProc8888();
     94             jpegColorType = JCS_EXT_RGBA;
     95             numComponents = 4;
     96             break;
     97         case kBGRA_8888_SkColorType:
     98             fProc = chooseProc8888();
     99             jpegColorType = JCS_EXT_BGRA;
    100             numComponents = 4;
    101             break;
    102         case kRGB_565_SkColorType:
    103             fProc = transform_scanline_565;
    104             jpegColorType = JCS_RGB;
    105             numComponents = 3;
    106             break;
    107         case kARGB_4444_SkColorType:
    108             if (SkJpegEncoder::AlphaOption::kBlendOnBlack == options.fAlphaOption) {
    109                 return false;
    110             }
    111 
    112             fProc = transform_scanline_444;
    113             jpegColorType = JCS_RGB;
    114             numComponents = 3;
    115             break;
    116         case kGray_8_SkColorType:
    117             SkASSERT(srcInfo.isOpaque());
    118             jpegColorType = JCS_GRAYSCALE;
    119             numComponents = 1;
    120             break;
    121         case kRGBA_F16_SkColorType:
    122             if (!srcInfo.colorSpace() || !srcInfo.colorSpace()->gammaIsLinear() ||
    123                     SkTransferFunctionBehavior::kRespect != options.fBlendBehavior) {
    124                 return false;
    125             }
    126 
    127             if (kUnpremul_SkAlphaType != srcInfo.alphaType() ||
    128                 SkJpegEncoder::AlphaOption::kIgnore == options.fAlphaOption)
    129             {
    130                 fProc = transform_scanline_F16_to_8888;
    131             } else {
    132                 fProc = transform_scanline_F16_to_premul_8888;
    133             }
    134             jpegColorType = JCS_EXT_RGBA;
    135             numComponents = 4;
    136             break;
    137         default:
    138             return false;
    139     }
    140 
    141     fCInfo.image_width = srcInfo.width();
    142     fCInfo.image_height = srcInfo.height();
    143     fCInfo.in_color_space = jpegColorType;
    144     fCInfo.input_components = numComponents;
    145     jpeg_set_defaults(&fCInfo);
    146 
    147     if (kGray_8_SkColorType != srcInfo.colorType()) {
    148         switch (options.fDownsample) {
    149             case SkJpegEncoder::Downsample::k420:
    150                 SkASSERT(2 == fCInfo.comp_info[0].h_samp_factor);
    151                 SkASSERT(2 == fCInfo.comp_info[0].v_samp_factor);
    152                 SkASSERT(1 == fCInfo.comp_info[1].h_samp_factor);
    153                 SkASSERT(1 == fCInfo.comp_info[1].v_samp_factor);
    154                 SkASSERT(1 == fCInfo.comp_info[2].h_samp_factor);
    155                 SkASSERT(1 == fCInfo.comp_info[2].v_samp_factor);
    156                 break;
    157             case SkJpegEncoder::Downsample::k422:
    158                 fCInfo.comp_info[0].h_samp_factor = 2;
    159                 fCInfo.comp_info[0].v_samp_factor = 1;
    160                 fCInfo.comp_info[1].h_samp_factor = 1;
    161                 fCInfo.comp_info[1].v_samp_factor = 1;
    162                 fCInfo.comp_info[2].h_samp_factor = 1;
    163                 fCInfo.comp_info[2].v_samp_factor = 1;
    164                 break;
    165             case SkJpegEncoder::Downsample::k444:
    166                 fCInfo.comp_info[0].h_samp_factor = 1;
    167                 fCInfo.comp_info[0].v_samp_factor = 1;
    168                 fCInfo.comp_info[1].h_samp_factor = 1;
    169                 fCInfo.comp_info[1].v_samp_factor = 1;
    170                 fCInfo.comp_info[2].h_samp_factor = 1;
    171                 fCInfo.comp_info[2].v_samp_factor = 1;
    172                 break;
    173         }
    174     }
    175 
    176     // Tells libjpeg-turbo to compute optimal Huffman coding tables
    177     // for the image.  This improves compression at the cost of
    178     // slower encode performance.
    179     fCInfo.optimize_coding = TRUE;
    180     return true;
    181 }
    182 
    183 std::unique_ptr<SkEncoder> SkJpegEncoder::Make(SkWStream* dst, const SkPixmap& src,
    184                                                const Options& options) {
    185     if (!SkPixmapIsValid(src, options.fBlendBehavior)) {
    186         return nullptr;
    187     }
    188 
    189     std::unique_ptr<SkJpegEncoderMgr> encoderMgr = SkJpegEncoderMgr::Make(dst);
    190     if (setjmp(encoderMgr->jmpBuf())) {
    191         return nullptr;
    192     }
    193 
    194     if (!encoderMgr->setParams(src.info(), options)) {
    195         return nullptr;
    196     }
    197 
    198     jpeg_set_quality(encoderMgr->cinfo(), options.fQuality, TRUE);
    199     jpeg_start_compress(encoderMgr->cinfo(), TRUE);
    200 
    201     sk_sp<SkData> icc = icc_from_color_space(src.info());
    202     if (icc) {
    203         // Create a contiguous block of memory with the icc signature followed by the profile.
    204         sk_sp<SkData> markerData =
    205                 SkData::MakeUninitialized(kICCMarkerHeaderSize + icc->size());
    206         uint8_t* ptr = (uint8_t*) markerData->writable_data();
    207         memcpy(ptr, kICCSig, sizeof(kICCSig));
    208         ptr += sizeof(kICCSig);
    209         *ptr++ = 1; // This is the first marker.
    210         *ptr++ = 1; // Out of one total markers.
    211         memcpy(ptr, icc->data(), icc->size());
    212 
    213         jpeg_write_marker(encoderMgr->cinfo(), kICCMarker, markerData->bytes(), markerData->size());
    214     }
    215 
    216     return std::unique_ptr<SkJpegEncoder>(new SkJpegEncoder(std::move(encoderMgr), src));
    217 }
    218 
    219 SkJpegEncoder::SkJpegEncoder(std::unique_ptr<SkJpegEncoderMgr> encoderMgr, const SkPixmap& src)
    220     : INHERITED(src, encoderMgr->proc() ? encoderMgr->cinfo()->input_components*src.width() : 0)
    221     , fEncoderMgr(std::move(encoderMgr))
    222 {}
    223 
    224 SkJpegEncoder::~SkJpegEncoder() {}
    225 
    226 bool SkJpegEncoder::onEncodeRows(int numRows) {
    227     if (setjmp(fEncoderMgr->jmpBuf())) {
    228         return false;
    229     }
    230 
    231     const void* srcRow = fSrc.addr(0, fCurrRow);
    232     for (int i = 0; i < numRows; i++) {
    233         JSAMPLE* jpegSrcRow = (JSAMPLE*) srcRow;
    234         if (fEncoderMgr->proc()) {
    235             fEncoderMgr->proc()((char*)fStorage.get(), (const char*)srcRow, fSrc.width(),
    236                                 fEncoderMgr->cinfo()->input_components, nullptr);
    237             jpegSrcRow = fStorage.get();
    238         }
    239 
    240         jpeg_write_scanlines(fEncoderMgr->cinfo(), &jpegSrcRow, 1);
    241         srcRow = SkTAddOffset<const void>(srcRow, fSrc.rowBytes());
    242     }
    243 
    244     fCurrRow += numRows;
    245     if (fCurrRow == fSrc.height()) {
    246         jpeg_finish_compress(fEncoderMgr->cinfo());
    247     }
    248 
    249     return true;
    250 }
    251 
    252 bool SkJpegEncoder::Encode(SkWStream* dst, const SkPixmap& src, const Options& options) {
    253     auto encoder = SkJpegEncoder::Make(dst, src, options);
    254     return encoder.get() && encoder->encodeRows(src.height());
    255 }
    256 
    257 #endif
    258