summaryrefslogtreecommitdiff
path: root/chromium/ui/gfx/codec/jpeg_codec.cc
blob: 5ff77d782b8b5f7a6b162c328ea9d34df246aa77 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
// Copyright (c) 2011 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "ui/gfx/codec/jpeg_codec.h"

#include <setjmp.h>

#include <memory>

#include "base/logging.h"
#include "third_party/skia/include/core/SkBitmap.h"
#include "third_party/skia/include/core/SkColorPriv.h"
#include "third_party/skia/include/encode/SkJpegEncoder.h"
#include "ui/gfx/codec/vector_wstream.h"

extern "C" {
#if defined(USE_SYSTEM_LIBJPEG)
#include <jpeglib.h>
#elif defined(USE_LIBJPEG_TURBO)
#include "third_party/libjpeg_turbo/jpeglib.h"
#else
#include "third_party/libjpeg/jpeglib.h"
#endif
}

namespace gfx {

// Encoder/decoder shared stuff ------------------------------------------------

namespace {

// used to pass error info through the JPEG library
struct CoderErrorMgr {
  jpeg_error_mgr pub;
  jmp_buf setjmp_buffer;
};

void ErrorExit(jpeg_common_struct* cinfo) {
  CoderErrorMgr *err = reinterpret_cast<CoderErrorMgr*>(cinfo->err);

  // Return control to the setjmp point.
  longjmp(err->setjmp_buffer, false);
}

}  // namespace

// Encoder ---------------------------------------------------------------------

bool JPEGCodec::Encode(const SkPixmap& src,
                       int quality,
                       std::vector<unsigned char>* output) {
  output->clear();
  VectorWStream dst(output);

  SkJpegEncoder::Options options;
  options.fQuality = quality;
  return SkJpegEncoder::Encode(&dst, src, options);
}

bool JPEGCodec::Encode(const SkBitmap& src,
                       int quality,
                       std::vector<unsigned char>* output) {
  SkPixmap pixmap;
  if (!src.peekPixels(&pixmap)) {
    return false;
  }

  return JPEGCodec::Encode(pixmap, quality, output);
}

// Decoder --------------------------------------------------------------------

namespace {

struct JpegDecoderState {
  JpegDecoderState(const unsigned char* in, size_t len)
      : input_buffer(in), input_buffer_length(len) {
  }

  const unsigned char* input_buffer;
  size_t input_buffer_length;
};

// Callback to initialize the source.
//
// From the JPEG library:
//  "Initialize source. This is called by jpeg_read_header() before any data is
//   actually read. May leave bytes_in_buffer set to 0 (in which case a
//   fill_input_buffer() call will occur immediately)."
void InitSource(j_decompress_ptr cinfo) {
  JpegDecoderState* state = static_cast<JpegDecoderState*>(cinfo->client_data);
  cinfo->src->next_input_byte = state->input_buffer;
  cinfo->src->bytes_in_buffer = state->input_buffer_length;
}

// Callback to fill the buffer. Since our buffer already contains all the data,
// we should never need to provide more data. If libjpeg thinks it needs more
// data, our input is probably corrupt.
//
// From the JPEG library:
//  "This is called whenever bytes_in_buffer has reached zero and more data is
//   wanted. In typical applications, it should read fresh data into the buffer
//   (ignoring the current state of next_input_byte and bytes_in_buffer), reset
//   the pointer & count to the start of the buffer, and return TRUE indicating
//   that the buffer has been reloaded. It is not necessary to fill the buffer
//   entirely, only to obtain at least one more byte. bytes_in_buffer MUST be
//   set to a positive value if TRUE is returned. A FALSE return should only
//   be used when I/O suspension is desired."
boolean FillInputBuffer(j_decompress_ptr cinfo) {
  return false;
}

// Skip data in the buffer. Since we have all the data at once, this operation
// is easy. It is not clear if this ever gets called because the JPEG library
// should be able to do the skip itself (it has all the data).
//
// From the JPEG library:
//  "Skip num_bytes worth of data. The buffer pointer and count should be
//   advanced over num_bytes input bytes, refilling the buffer as needed. This
//   is used to skip over a potentially large amount of uninteresting data
//   (such as an APPn marker). In some applications it may be possible to
//   optimize away the reading of the skipped data, but it's not clear that
//   being smart is worth much trouble; large skips are uncommon.
//   bytes_in_buffer may be zero on return. A zero or negative skip count
//   should be treated as a no-op."
void SkipInputData(j_decompress_ptr cinfo, long num_bytes) {
  if (num_bytes > static_cast<long>(cinfo->src->bytes_in_buffer)) {
    // Since all our data should be in the buffer, trying to skip beyond it
    // means that there is some kind of error or corrupt input data. A 0 for
    // bytes left means it will call FillInputBuffer which will then fail.
    cinfo->src->next_input_byte += cinfo->src->bytes_in_buffer;
    cinfo->src->bytes_in_buffer = 0;
  } else if (num_bytes > 0) {
    cinfo->src->bytes_in_buffer -= static_cast<size_t>(num_bytes);
    cinfo->src->next_input_byte += num_bytes;
  }
}

// Our source doesn't need any cleanup, so this is a NOP.
//
// From the JPEG library:
//  "Terminate source --- called by jpeg_finish_decompress() after all data has
//   been read to clean up JPEG source manager. NOT called by jpeg_abort() or
//   jpeg_destroy()."
void TermSource(j_decompress_ptr cinfo) {
}

// This class destroys the given jpeg_decompress object when it goes out of
// scope. It simplifies the error handling in Decode (and even applies to the
// success case).
class DecompressDestroyer {
 public:
  DecompressDestroyer() : cinfo_(NULL) {
  }
  ~DecompressDestroyer() {
    DestroyManagedObject();
  }
  void SetManagedObject(jpeg_decompress_struct* ci) {
    DestroyManagedObject();
    cinfo_ = ci;
  }
  void DestroyManagedObject() {
    if (cinfo_) {
      jpeg_destroy_decompress(cinfo_);
      cinfo_ = NULL;
    }
  }
 private:
  jpeg_decompress_struct* cinfo_;
};

}  // namespace

bool JPEGCodec::Decode(const unsigned char* input, size_t input_size,
                       ColorFormat format, std::vector<unsigned char>* output,
                       int* w, int* h) {
  jpeg_decompress_struct cinfo;
  DecompressDestroyer destroyer;
  destroyer.SetManagedObject(&cinfo);
  output->clear();

  // We set up the normal JPEG error routines, then override error_exit.
  // This must be done before the call to create_decompress.
  CoderErrorMgr errmgr;
  cinfo.err = jpeg_std_error(&errmgr.pub);
  errmgr.pub.error_exit = ErrorExit;
  // Establish the setjmp return context for ErrorExit to use.
  if (setjmp(errmgr.setjmp_buffer)) {
    // If we get here, the JPEG code has signaled an error.
    // See note in JPEGCodec::Encode() for why we need to destroy the cinfo
    // manually here.
    destroyer.DestroyManagedObject();
    return false;
  }

  // The destroyer will destroy() cinfo on exit.  We don't want to set the
  // destroyer's object until cinfo is initialized.
  jpeg_create_decompress(&cinfo);

  // set up the source manager
  jpeg_source_mgr srcmgr;
  srcmgr.init_source = InitSource;
  srcmgr.fill_input_buffer = FillInputBuffer;
  srcmgr.skip_input_data = SkipInputData;
  srcmgr.resync_to_restart = jpeg_resync_to_restart;  // use default routine
  srcmgr.term_source = TermSource;
  cinfo.src = &srcmgr;

  JpegDecoderState state(input, input_size);
  cinfo.client_data = &state;

  // fill the file metadata into our buffer
  if (jpeg_read_header(&cinfo, true) != JPEG_HEADER_OK)
    return false;

  // we want to always get RGB data out
  switch (cinfo.jpeg_color_space) {
    case JCS_GRAYSCALE:
    case JCS_RGB:
    case JCS_YCbCr:
      // Choose an output colorspace and return if it is an unsupported one.
      // Same as JPEGCodec::Encode(), libjpeg-turbo supports all input formats
      // used by Chromium (i.e. RGBA and BGRA) and we just map the input
      // parameters to a colorspace.
      if (format == FORMAT_RGBA ||
          (format == FORMAT_SkBitmap && SK_R32_SHIFT == 0)) {
        cinfo.out_color_space = JCS_EXT_RGBX;
        cinfo.output_components = 4;
      } else if (format == FORMAT_BGRA ||
                 (format == FORMAT_SkBitmap && SK_B32_SHIFT == 0)) {
        cinfo.out_color_space = JCS_EXT_BGRX;
        cinfo.output_components = 4;
      } else {
        // We can exit this function without calling jpeg_destroy_decompress()
        // because DecompressDestroyer automaticaly calls it.
        NOTREACHED() << "Invalid pixel format";
        return false;
      }
      break;
    case JCS_CMYK:
    case JCS_YCCK:
    default:
      // Mozilla errors out on these color spaces, so I presume that the jpeg
      // library can't do automatic color space conversion for them. We don't
      // care about these anyway.
      return false;
  }

  jpeg_calc_output_dimensions(&cinfo);
  *w = cinfo.output_width;
  *h = cinfo.output_height;

  jpeg_start_decompress(&cinfo);

  // FIXME(brettw) we may want to allow the capability for callers to request
  // how to align row lengths as we do for the compressor.
  int row_read_stride = cinfo.output_width * cinfo.output_components;

  // Create memory for a decoded image and write decoded lines to the memory
  // without conversions same as JPEGCodec::Encode().
  int row_write_stride = row_read_stride;
  output->resize(row_write_stride * cinfo.output_height);

  for (int row = 0; row < static_cast<int>(cinfo.output_height); row++) {
    unsigned char* rowptr = &(*output)[row * row_write_stride];
    if (!jpeg_read_scanlines(&cinfo, &rowptr, 1))
      return false;
  }

  jpeg_finish_decompress(&cinfo);
  jpeg_destroy_decompress(&cinfo);
  return true;
}

// static
std::unique_ptr<SkBitmap> JPEGCodec::Decode(const unsigned char* input,
                                            size_t input_size) {
  int w, h;
  std::vector<unsigned char> data_vector;
  if (!Decode(input, input_size, FORMAT_SkBitmap, &data_vector, &w, &h))
    return nullptr;

  // Skia only handles 32 bit images.
  int data_length = w * h * 4;

  std::unique_ptr<SkBitmap> bitmap(new SkBitmap());
  bitmap->allocN32Pixels(w, h);
  memcpy(bitmap->getAddr32(0, 0), &data_vector[0], data_length);

  return bitmap;
}

}  // namespace gfx