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