• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 "SkImageEncoderFns.h"
14 #include "SkJPEGWriteUtility.h"
15 #include "SkStream.h"
16 #include "SkTemplates.h"
17 
18 #include <stdio.h>
19 
20 extern "C" {
21     #include "jpeglib.h"
22     #include "jerror.h"
23 }
24 
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?
set_encode_config(J_COLOR_SPACE * jpegColorType,int * numComponents,transform_scanline_proc * proc,const SkImageInfo & info)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;
48         case kRGB_565_SkColorType:
49             *proc = transform_scanline_565;
50             *jpegColorType = JCS_RGB;
51             *numComponents = 3;
52             return true;
53         case kARGB_4444_SkColorType:
54             *proc = transform_scanline_444;
55             *jpegColorType = JCS_RGB;
56             *numComponents = 3;
57             return true;
58         case kIndex_8_SkColorType:
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;
77         default:
78             return false;
79     }
80 
81 
82 }
83 
SkEncodeImageAsJPEG(SkWStream * stream,const SkPixmap & pixmap,const SkEncodeOptions & opts)84 bool SkEncodeImageAsJPEG(SkWStream* stream, const SkPixmap& pixmap, const SkEncodeOptions& opts) {
85     if (SkTransferFunctionBehavior::kRespect == opts.fUnpremulBehavior) {
86         // Respecting the transfer function requries a color space.  It's not actually critical
87         // in the jpeg case (since jpegs are opaque), but Skia color correct behavior generally
88         // requires pixels to be tagged with color spaces.
89         if (!pixmap.colorSpace() || (!pixmap.colorSpace()->gammaCloseToSRGB() &&
90                                      !pixmap.colorSpace()->gammaIsLinear())) {
91             return false;
92         }
93     }
94 
95     return SkEncodeImageAsJPEG(stream, pixmap, 100);
96 }
97 
SkEncodeImageAsJPEG(SkWStream * stream,const SkPixmap & pixmap,int quality)98 bool SkEncodeImageAsJPEG(SkWStream* stream, const SkPixmap& pixmap, int quality) {
99     if (!pixmap.addr()) {
100         return false;
101     }
102     jpeg_compress_struct    cinfo;
103     skjpeg_error_mgr        sk_err;
104     skjpeg_destination_mgr  sk_wstream(stream);
105 
106     // Declare before calling setjmp.
107     SkAutoTMalloc<uint8_t>  storage;
108 
109     cinfo.err = jpeg_std_error(&sk_err);
110     sk_err.error_exit = skjpeg_error_exit;
111     if (setjmp(sk_err.fJmpBuf)) {
112         return false;
113     }
114 
115     J_COLOR_SPACE jpegColorSpace;
116     int numComponents;
117     transform_scanline_proc proc;
118     if (!set_encode_config(&jpegColorSpace, &numComponents, &proc, pixmap.info())) {
119         return false;
120     }
121 
122     jpeg_create_compress(&cinfo);
123     cinfo.dest = &sk_wstream;
124     cinfo.image_width = pixmap.width();
125     cinfo.image_height = pixmap.height();
126     cinfo.input_components = numComponents;
127     cinfo.in_color_space = jpegColorSpace;
128 
129     jpeg_set_defaults(&cinfo);
130 
131     // Tells libjpeg-turbo to compute optimal Huffman coding tables
132     // for the image.  This improves compression at the cost of
133     // slower encode performance.
134     cinfo.optimize_coding = TRUE;
135     jpeg_set_quality(&cinfo, quality, TRUE /* limit to baseline-JPEG values */);
136 
137     jpeg_start_compress(&cinfo, TRUE);
138 
139     sk_sp<SkData> icc = icc_from_color_space(pixmap.info());
140     if (icc) {
141         // Create a contiguous block of memory with the icc signature followed by the profile.
142         sk_sp<SkData> markerData =
143                 SkData::MakeUninitialized(kICCMarkerHeaderSize + icc->size());
144         uint8_t* ptr = (uint8_t*) markerData->writable_data();
145         memcpy(ptr, kICCSig, sizeof(kICCSig));
146         ptr += sizeof(kICCSig);
147         *ptr++ = 1; // This is the first marker.
148         *ptr++ = 1; // Out of one total markers.
149         memcpy(ptr, icc->data(), icc->size());
150 
151         jpeg_write_marker(&cinfo, kICCMarker, markerData->bytes(), markerData->size());
152     }
153 
154     if (proc) {
155         storage.reset(numComponents * pixmap.width());
156     }
157 
158     const void* srcRow = pixmap.addr();
159     const SkPMColor* colors = pixmap.ctable() ? pixmap.ctable()->readColors() : nullptr;
160     while (cinfo.next_scanline < cinfo.image_height) {
161         JSAMPLE* jpegSrcRow = (JSAMPLE*) srcRow;
162         if (proc) {
163             proc((char*)storage.get(), (const char*)srcRow, pixmap.width(), numComponents, colors);
164             jpegSrcRow = storage.get();
165         }
166 
167         (void) jpeg_write_scanlines(&cinfo, &jpegSrcRow, 1);
168         srcRow = SkTAddOffset<const void>(srcRow, pixmap.rowBytes());
169     }
170 
171     jpeg_finish_compress(&cinfo);
172     jpeg_destroy_compress(&cinfo);
173 
174     return true;
175 }
176 #endif
177