blob: c37aa4687e87e22e713e91e7a3fb2c89c4dc771f [file] [log] [blame]
tomhudson@google.comd33b26e2012-03-02 16:12:14 +00001/*
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 Canarydb683012016-11-23 08:55:18 -07008#include "SkImageEncoderPriv.h"
tomhudson@google.comd33b26e2012-03-02 16:12:14 +00009
Hal Canary1fcc4042016-11-30 17:07:59 -050010#ifdef SK_HAS_JPEG_LIBRARY
11
tomhudson@google.comd33b26e2012-03-02 16:12:14 +000012#include "SkColorPriv.h"
Hal Canarydb683012016-11-23 08:55:18 -070013#include "SkJPEGWriteUtility.h"
tomhudson@google.comd33b26e2012-03-02 16:12:14 +000014#include "SkStream.h"
15#include "SkTemplates.h"
Matt Sarette95941f2017-01-27 18:16:40 -050016#include "transform_scanline.h"
halcanary@google.comfed30372013-10-04 12:46:45 +000017
tomhudson@google.comd33b26e2012-03-02 16:12:14 +000018#include <stdio.h>
Hal Canarydb683012016-11-23 08:55:18 -070019
tomhudson@google.comd33b26e2012-03-02 16:12:14 +000020extern "C" {
21 #include "jpeglib.h"
22 #include "jerror.h"
23}
24
Matt Sarette95941f2017-01-27 18:16:40 -050025/**
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?
36static 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;
reed6c225732014-06-09 19:52:07 -070048 case kRGB_565_SkColorType:
Matt Sarette95941f2017-01-27 18:16:40 -050049 *proc = transform_scanline_565;
50 *jpegColorType = JCS_RGB;
51 *numComponents = 3;
52 return true;
reed6c225732014-06-09 19:52:07 -070053 case kARGB_4444_SkColorType:
Matt Sarette95941f2017-01-27 18:16:40 -050054 *proc = transform_scanline_444;
55 *jpegColorType = JCS_RGB;
56 *numComponents = 3;
57 return true;
reed6c225732014-06-09 19:52:07 -070058 case kIndex_8_SkColorType:
Matt Sarette95941f2017-01-27 18:16:40 -050059 *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.comd33b26e2012-03-02 16:12:14 +000077 default:
Matt Sarette95941f2017-01-27 18:16:40 -050078 return false;
tomhudson@google.comd33b26e2012-03-02 16:12:14 +000079 }
Matt Sarette95941f2017-01-27 18:16:40 -050080
81
82}
83
84bool 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.comd33b26e2012-03-02 16:12:14 +0000101}
102
Hal Canary1fcc4042016-11-30 17:07:59 -0500103bool SkEncodeImageAsJPEG(SkWStream* stream, const SkPixmap& pixmap, int quality) {
Hal Canary1fcc4042016-11-30 17:07:59 -0500104 if (!pixmap.addr()) {
105 return false;
tomhudson@google.comd33b26e2012-03-02 16:12:14 +0000106 }
Hal Canary1fcc4042016-11-30 17:07:59 -0500107 jpeg_compress_struct cinfo;
108 skjpeg_error_mgr sk_err;
109 skjpeg_destination_mgr sk_wstream(stream);
tomhudson@google.comd33b26e2012-03-02 16:12:14 +0000110
Matt Sarette95941f2017-01-27 18:16:40 -0500111 // Declare before calling setjmp.
112 SkAutoTMalloc<uint8_t> storage;
tomhudson@google.comd33b26e2012-03-02 16:12:14 +0000113
Hal Canary1fcc4042016-11-30 17:07:59 -0500114 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 Sarette95941f2017-01-27 18:16:40 -0500120 J_COLOR_SPACE jpegColorSpace;
121 int numComponents;
122 transform_scanline_proc proc;
123 if (!set_encode_config(&jpegColorSpace, &numComponents, &proc, pixmap.info())) {
Hal Canary1fcc4042016-11-30 17:07:59 -0500124 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 Sarette95941f2017-01-27 18:16:40 -0500131 cinfo.input_components = numComponents;
132 cinfo.in_color_space = jpegColorSpace;
Hal Canary1fcc4042016-11-30 17:07:59 -0500133
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 Sarette95941f2017-01-27 18:16:40 -0500144 if (proc) {
145 storage.reset(numComponents * pixmap.width());
146 }
Hal Canary1fcc4042016-11-30 17:07:59 -0500147
Matt Sarette95941f2017-01-27 18:16:40 -0500148 const void* srcRow = pixmap.addr();
Hal Canary1fcc4042016-11-30 17:07:59 -0500149 const SkPMColor* colors = pixmap.ctable() ? pixmap.ctable()->readColors() : nullptr;
Hal Canary1fcc4042016-11-30 17:07:59 -0500150 while (cinfo.next_scanline < cinfo.image_height) {
Matt Sarette95941f2017-01-27 18:16:40 -0500151 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 Canary1fcc4042016-11-30 17:07:59 -0500156
Matt Sarette95941f2017-01-27 18:16:40 -0500157 (void) jpeg_write_scanlines(&cinfo, &jpegSrcRow, 1);
158 srcRow = SkTAddOffset<const void>(srcRow, pixmap.rowBytes());
Hal Canary1fcc4042016-11-30 17:07:59 -0500159 }
160
161 jpeg_finish_compress(&cinfo);
162 jpeg_destroy_compress(&cinfo);
163
164 return true;
tomhudson@google.comd33b26e2012-03-02 16:12:14 +0000165}
Hal Canary1fcc4042016-11-30 17:07:59 -0500166#endif