tomhudson@google.com | d33b26e | 2012-03-02 16:12:14 +0000 | [diff] [blame] | 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 | |
Hal Canary | db68301 | 2016-11-23 08:55:18 -0700 | [diff] [blame] | 8 | #include "SkImageEncoderPriv.h" |
tomhudson@google.com | d33b26e | 2012-03-02 16:12:14 +0000 | [diff] [blame] | 9 | |
Hal Canary | 1fcc404 | 2016-11-30 17:07:59 -0500 | [diff] [blame] | 10 | #ifdef SK_HAS_JPEG_LIBRARY |
| 11 | |
tomhudson@google.com | d33b26e | 2012-03-02 16:12:14 +0000 | [diff] [blame] | 12 | #include "SkColorPriv.h" |
Hal Canary | db68301 | 2016-11-23 08:55:18 -0700 | [diff] [blame] | 13 | #include "SkJPEGWriteUtility.h" |
tomhudson@google.com | d33b26e | 2012-03-02 16:12:14 +0000 | [diff] [blame] | 14 | #include "SkStream.h" |
| 15 | #include "SkTemplates.h" |
Matt Sarett | e95941f | 2017-01-27 18:16:40 -0500 | [diff] [blame^] | 16 | #include "transform_scanline.h" |
halcanary@google.com | fed3037 | 2013-10-04 12:46:45 +0000 | [diff] [blame] | 17 | |
tomhudson@google.com | d33b26e | 2012-03-02 16:12:14 +0000 | [diff] [blame] | 18 | #include <stdio.h> |
Hal Canary | db68301 | 2016-11-23 08:55:18 -0700 | [diff] [blame] | 19 | |
tomhudson@google.com | d33b26e | 2012-03-02 16:12:14 +0000 | [diff] [blame] | 20 | extern "C" { |
| 21 | #include "jpeglib.h" |
| 22 | #include "jerror.h" |
| 23 | } |
| 24 | |
Matt Sarett | e95941f | 2017-01-27 18:16:40 -0500 | [diff] [blame^] | 25 | /** |
| 26 | * Returns true if |info| is supported by the jpeg encoder and false otherwise. |
| 27 | * |jpegColorType| will be set to the proper libjpeg-turbo type for input to the library. |
| 28 | * |numComponents| will be set to the number of components in the |jpegColorType|. |
| 29 | * |proc| will be set if we need to pre-convert the input before passing to |
| 30 | * libjpeg-turbo. Otherwise will be set to nullptr. |
| 31 | */ |
| 32 | // TODO (skbug.com/1501): |
| 33 | // Should we fail on non-opaque encodes? |
| 34 | // Or should we change alpha behavior (ex: unpremultiply when the input is premul)? |
| 35 | // Or is ignoring the alpha type and alpha channel ok here? |
| 36 | static bool set_encode_config(J_COLOR_SPACE* jpegColorType, int* numComponents, |
| 37 | transform_scanline_proc* proc, const SkImageInfo& info) { |
| 38 | *proc = nullptr; |
| 39 | switch (info.colorType()) { |
| 40 | case kRGBA_8888_SkColorType: |
| 41 | *jpegColorType = JCS_EXT_RGBA; |
| 42 | *numComponents = 4; |
| 43 | return true; |
| 44 | case kBGRA_8888_SkColorType: |
| 45 | *jpegColorType = JCS_EXT_BGRA; |
| 46 | *numComponents = 4; |
| 47 | return true; |
reed | 6c22573 | 2014-06-09 19:52:07 -0700 | [diff] [blame] | 48 | case kRGB_565_SkColorType: |
Matt Sarett | e95941f | 2017-01-27 18:16:40 -0500 | [diff] [blame^] | 49 | *proc = transform_scanline_565; |
| 50 | *jpegColorType = JCS_RGB; |
| 51 | *numComponents = 3; |
| 52 | return true; |
reed | 6c22573 | 2014-06-09 19:52:07 -0700 | [diff] [blame] | 53 | case kARGB_4444_SkColorType: |
Matt Sarett | e95941f | 2017-01-27 18:16:40 -0500 | [diff] [blame^] | 54 | *proc = transform_scanline_444; |
| 55 | *jpegColorType = JCS_RGB; |
| 56 | *numComponents = 3; |
| 57 | return true; |
reed | 6c22573 | 2014-06-09 19:52:07 -0700 | [diff] [blame] | 58 | case kIndex_8_SkColorType: |
Matt Sarett | e95941f | 2017-01-27 18:16:40 -0500 | [diff] [blame^] | 59 | *proc = transform_scanline_index8_opaque; |
| 60 | *jpegColorType = JCS_RGB; |
| 61 | *numComponents = 3; |
| 62 | return true; |
| 63 | case kGray_8_SkColorType: |
| 64 | SkASSERT(info.isOpaque()); |
| 65 | *jpegColorType = JCS_GRAYSCALE; |
| 66 | *numComponents = 1; |
| 67 | return true; |
| 68 | case kRGBA_F16_SkColorType: |
| 69 | if (!info.colorSpace() || !info.colorSpace()->gammaIsLinear()) { |
| 70 | return false; |
| 71 | } |
| 72 | |
| 73 | *proc = transform_scanline_F16_to_8888; |
| 74 | *jpegColorType = JCS_EXT_RGBA; |
| 75 | *numComponents = 4; |
| 76 | return true; |
tomhudson@google.com | d33b26e | 2012-03-02 16:12:14 +0000 | [diff] [blame] | 77 | default: |
Matt Sarett | e95941f | 2017-01-27 18:16:40 -0500 | [diff] [blame^] | 78 | return false; |
tomhudson@google.com | d33b26e | 2012-03-02 16:12:14 +0000 | [diff] [blame] | 79 | } |
Matt Sarett | e95941f | 2017-01-27 18:16:40 -0500 | [diff] [blame^] | 80 | |
| 81 | |
| 82 | } |
| 83 | |
| 84 | bool SkEncodeImageAsJPEG(SkWStream* stream, const SkPixmap& pixmap, const SkEncodeOptions& opts) { |
| 85 | SkASSERT(!pixmap.colorSpace() || pixmap.colorSpace()->gammaCloseToSRGB() || |
| 86 | pixmap.colorSpace()->gammaIsLinear()); |
| 87 | |
| 88 | SkPixmap src = pixmap; |
| 89 | if (SkEncodeOptions::PremulBehavior::kLegacy == opts.fPremulBehavior) { |
| 90 | src.setColorSpace(nullptr); |
| 91 | } else { |
| 92 | // kGammaCorrect behavior requires a color space. It's not actually critical in the |
| 93 | // jpeg case (since jpegs are opaque), but Skia color correct behavior generally |
| 94 | // requires pixels to be tagged with color spaces. |
| 95 | if (!src.colorSpace()) { |
| 96 | return false; |
| 97 | } |
| 98 | } |
| 99 | |
| 100 | return SkEncodeImageAsJPEG(stream, src, 100); |
tomhudson@google.com | d33b26e | 2012-03-02 16:12:14 +0000 | [diff] [blame] | 101 | } |
| 102 | |
Hal Canary | 1fcc404 | 2016-11-30 17:07:59 -0500 | [diff] [blame] | 103 | bool SkEncodeImageAsJPEG(SkWStream* stream, const SkPixmap& pixmap, int quality) { |
Hal Canary | 1fcc404 | 2016-11-30 17:07:59 -0500 | [diff] [blame] | 104 | if (!pixmap.addr()) { |
| 105 | return false; |
tomhudson@google.com | d33b26e | 2012-03-02 16:12:14 +0000 | [diff] [blame] | 106 | } |
Hal Canary | 1fcc404 | 2016-11-30 17:07:59 -0500 | [diff] [blame] | 107 | jpeg_compress_struct cinfo; |
| 108 | skjpeg_error_mgr sk_err; |
| 109 | skjpeg_destination_mgr sk_wstream(stream); |
tomhudson@google.com | d33b26e | 2012-03-02 16:12:14 +0000 | [diff] [blame] | 110 | |
Matt Sarett | e95941f | 2017-01-27 18:16:40 -0500 | [diff] [blame^] | 111 | // Declare before calling setjmp. |
| 112 | SkAutoTMalloc<uint8_t> storage; |
tomhudson@google.com | d33b26e | 2012-03-02 16:12:14 +0000 | [diff] [blame] | 113 | |
Hal Canary | 1fcc404 | 2016-11-30 17:07:59 -0500 | [diff] [blame] | 114 | cinfo.err = jpeg_std_error(&sk_err); |
| 115 | sk_err.error_exit = skjpeg_error_exit; |
| 116 | if (setjmp(sk_err.fJmpBuf)) { |
| 117 | return false; |
| 118 | } |
| 119 | |
Matt Sarett | e95941f | 2017-01-27 18:16:40 -0500 | [diff] [blame^] | 120 | J_COLOR_SPACE jpegColorSpace; |
| 121 | int numComponents; |
| 122 | transform_scanline_proc proc; |
| 123 | if (!set_encode_config(&jpegColorSpace, &numComponents, &proc, pixmap.info())) { |
Hal Canary | 1fcc404 | 2016-11-30 17:07:59 -0500 | [diff] [blame] | 124 | return false; |
| 125 | } |
| 126 | |
| 127 | jpeg_create_compress(&cinfo); |
| 128 | cinfo.dest = &sk_wstream; |
| 129 | cinfo.image_width = pixmap.width(); |
| 130 | cinfo.image_height = pixmap.height(); |
Matt Sarett | e95941f | 2017-01-27 18:16:40 -0500 | [diff] [blame^] | 131 | cinfo.input_components = numComponents; |
| 132 | cinfo.in_color_space = jpegColorSpace; |
Hal Canary | 1fcc404 | 2016-11-30 17:07:59 -0500 | [diff] [blame] | 133 | |
| 134 | jpeg_set_defaults(&cinfo); |
| 135 | |
| 136 | // Tells libjpeg-turbo to compute optimal Huffman coding tables |
| 137 | // for the image. This improves compression at the cost of |
| 138 | // slower encode performance. |
| 139 | cinfo.optimize_coding = TRUE; |
| 140 | jpeg_set_quality(&cinfo, quality, TRUE /* limit to baseline-JPEG values */); |
| 141 | |
| 142 | jpeg_start_compress(&cinfo, TRUE); |
| 143 | |
Matt Sarett | e95941f | 2017-01-27 18:16:40 -0500 | [diff] [blame^] | 144 | if (proc) { |
| 145 | storage.reset(numComponents * pixmap.width()); |
| 146 | } |
Hal Canary | 1fcc404 | 2016-11-30 17:07:59 -0500 | [diff] [blame] | 147 | |
Matt Sarett | e95941f | 2017-01-27 18:16:40 -0500 | [diff] [blame^] | 148 | const void* srcRow = pixmap.addr(); |
Hal Canary | 1fcc404 | 2016-11-30 17:07:59 -0500 | [diff] [blame] | 149 | const SkPMColor* colors = pixmap.ctable() ? pixmap.ctable()->readColors() : nullptr; |
Hal Canary | 1fcc404 | 2016-11-30 17:07:59 -0500 | [diff] [blame] | 150 | while (cinfo.next_scanline < cinfo.image_height) { |
Matt Sarett | e95941f | 2017-01-27 18:16:40 -0500 | [diff] [blame^] | 151 | JSAMPLE* jpegSrcRow = (JSAMPLE*) srcRow; |
| 152 | if (proc) { |
| 153 | proc((char*)storage.get(), (const char*)srcRow, pixmap.width(), numComponents, colors); |
| 154 | jpegSrcRow = storage.get(); |
| 155 | } |
Hal Canary | 1fcc404 | 2016-11-30 17:07:59 -0500 | [diff] [blame] | 156 | |
Matt Sarett | e95941f | 2017-01-27 18:16:40 -0500 | [diff] [blame^] | 157 | (void) jpeg_write_scanlines(&cinfo, &jpegSrcRow, 1); |
| 158 | srcRow = SkTAddOffset<const void>(srcRow, pixmap.rowBytes()); |
Hal Canary | 1fcc404 | 2016-11-30 17:07:59 -0500 | [diff] [blame] | 159 | } |
| 160 | |
| 161 | jpeg_finish_compress(&cinfo); |
| 162 | jpeg_destroy_compress(&cinfo); |
| 163 | |
| 164 | return true; |
tomhudson@google.com | d33b26e | 2012-03-02 16:12:14 +0000 | [diff] [blame] | 165 | } |
Hal Canary | 1fcc404 | 2016-11-30 17:07:59 -0500 | [diff] [blame] | 166 | #endif |