Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(487)

Side by Side Diff: ui/gfx/codec/jpeg_codec.cc

Issue 2895953003: Use SkJpegEncoder in gfx jpeg_codec (Closed)
Patch Set: Created 3 years, 7 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
1 // Copyright (c) 2011 The Chromium Authors. All rights reserved. 1 // Copyright (c) 2011 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be 2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file. 3 // found in the LICENSE file.
4 4
5 #include "ui/gfx/codec/jpeg_codec.h" 5 #include "ui/gfx/codec/jpeg_codec.h"
6 6
7 #include <setjmp.h> 7 #include <setjmp.h>
8 8
9 #include <memory> 9 #include <memory>
10 10
11 #include "base/logging.h" 11 #include "base/logging.h"
12 #include "third_party/skia/include/core/SkBitmap.h" 12 #include "third_party/skia/include/core/SkBitmap.h"
13 #include "third_party/skia/include/core/SkColorPriv.h" 13 #include "third_party/skia/include/core/SkColorPriv.h"
14 #include "third_party/skia/include/core/SkStream.h"
15 #include "third_party/skia/include/encode/SkJpegEncoder.h"
14 16
15 extern "C" { 17 extern "C" {
16 #if defined(USE_SYSTEM_LIBJPEG) 18 #if defined(USE_SYSTEM_LIBJPEG)
17 #include <jpeglib.h> 19 #include <jpeglib.h>
18 #elif defined(USE_LIBJPEG_TURBO) 20 #elif defined(USE_LIBJPEG_TURBO)
19 #include "third_party/libjpeg_turbo/jpeglib.h" 21 #include "third_party/libjpeg_turbo/jpeglib.h"
20 #else 22 #else
21 #include "third_party/libjpeg/jpeglib.h" 23 #include "third_party/libjpeg/jpeglib.h"
22 #endif 24 #endif
23 } 25 }
(...skipping 24 matching lines...) Expand all
48 #if defined(USE_SYSTEM_LIBJPEG) 50 #if defined(USE_SYSTEM_LIBJPEG)
49 return SYSTEM_LIBJPEG; 51 return SYSTEM_LIBJPEG;
50 #elif defined(USE_LIBJPEG_TURBO) 52 #elif defined(USE_LIBJPEG_TURBO)
51 return LIBJPEG_TURBO; 53 return LIBJPEG_TURBO;
52 #else 54 #else
53 return IJG_LIBJPEG; 55 return IJG_LIBJPEG;
54 #endif 56 #endif
55 } 57 }
56 58
57 // Encoder --------------------------------------------------------------------- 59 // Encoder ---------------------------------------------------------------------
58 //
59 // This code is based on nsJPEGEncoder from Mozilla.
60 // Copyright 2005 Google Inc. (Brett Wilson, contributor)
61 60
62 namespace { 61 namespace {
63 62
64 // Initial size for the output buffer in the JpegEncoderState below. 63 class VectorWStream : public SkWStream {
65 static const int initial_output_buffer_size = 8192; 64 public:
66 65 VectorWStream(std::vector<unsigned char>* dst) : dst_(dst) {
67 struct JpegEncoderState { 66 DCHECK(dst_);
68 explicit JpegEncoderState(std::vector<unsigned char>* o) 67 DCHECK_EQ(0UL, dst->size());
scroggo_chromium 2017/05/30 14:27:52 Not a huge deal, but in the first DCHECK you used
msarett1 2017/06/07 18:01:14 Done.
69 : out(o),
70 image_buffer_used(0) {
71 } 68 }
72 69
73 // Output buffer, of which 'image_buffer_used' bytes are actually used (this 70 bool write(const void* buffer, size_t size) override {
74 // will often be less than the actual size of the vector because we size it 71 dst_->insert(dst_->end(), reinterpret_cast<const unsigned char*>(buffer),
scroggo_chromium 2017/05/30 14:27:52 nit: Use a temporary to make this more readable?
msarett1 2017/06/07 18:01:14 Done.
75 // so that libjpeg can write directly into it. 72 reinterpret_cast<const unsigned char*>(buffer) + size);
76 std::vector<unsigned char>* out; 73 return true;
74 }
77 75
78 // Number of bytes in the 'out' buffer that are actually used (see above). 76 size_t bytesWritten() const override { return dst_->size(); }
79 size_t image_buffer_used; 77
78 private:
79 // Does not have ownership.
80 std::vector<unsigned char>* dst_;
81 };
80 }; 82 };
81 83
82 // Initializes the JpegEncoderState for encoding, and tells libjpeg about where 84 bool JPEGCodec::Encode(const unsigned char* input,
83 // the output buffer is. 85 SkColorType colorType,
84 // 86 int w,
85 // From the JPEG library: 87 int h,
86 // "Initialize destination. This is called by jpeg_start_compress() before 88 int row_byte_width,
87 // any data is actually written. It must initialize next_output_byte and 89 int quality,
88 // free_in_buffer. free_in_buffer must be initialized to a positive value." 90 std::vector<unsigned char>* output) {
89 void InitDestination(jpeg_compress_struct* cinfo) { 91 output->clear();
90 JpegEncoderState* state = static_cast<JpegEncoderState*>(cinfo->client_data); 92 VectorWStream dst(output);
91 DCHECK(state->image_buffer_used == 0) << "initializing after use";
92 93
93 state->out->resize(initial_output_buffer_size); 94 SkImageInfo info = SkImageInfo::Make(w, h, colorType, kOpaque_SkAlphaType);
94 state->image_buffer_used = 0; 95 SkPixmap src(info, input, row_byte_width);
95 96
96 cinfo->dest->next_output_byte = &(*state->out)[0]; 97 SkJpegEncoder::Options options;
97 cinfo->dest->free_in_buffer = initial_output_buffer_size; 98 options.fQuality = quality;
98 }
99 99
100 // Resize the buffer that we give to libjpeg and update our and its state. 100 return SkJpegEncoder::Encode(&dst, src, options);
101 //
102 // From the JPEG library:
103 // "Callback used by libjpeg whenever the buffer has filled (free_in_buffer
104 // reaches zero). In typical applications, it should write out the *entire*
105 // buffer (use the saved start address and buffer length; ignore the current
106 // state of next_output_byte and free_in_buffer). Then reset the pointer &
107 // count to the start of the buffer, and return TRUE indicating that the
108 // buffer has been dumped. free_in_buffer must be set to a positive value
109 // when TRUE is returned. A FALSE return should only be used when I/O
110 // suspension is desired (this operating mode is discussed in the next
111 // section)."
112 boolean EmptyOutputBuffer(jpeg_compress_struct* cinfo) {
113 JpegEncoderState* state = static_cast<JpegEncoderState*>(cinfo->client_data);
114
115 // note the new size, the buffer is full
116 state->image_buffer_used = state->out->size();
117
118 // expand buffer, just double size each time
119 state->out->resize(state->out->size() * 2);
120
121 // tell libjpeg where to write the next data
122 cinfo->dest->next_output_byte = &(*state->out)[state->image_buffer_used];
123 cinfo->dest->free_in_buffer = state->out->size() - state->image_buffer_used;
124 return 1;
125 }
126
127 // Cleans up the JpegEncoderState to prepare for returning in the final form.
128 //
129 // From the JPEG library:
130 // "Terminate destination --- called by jpeg_finish_compress() after all data
131 // has been written. In most applications, this must flush any data
132 // remaining in the buffer. Use either next_output_byte or free_in_buffer to
133 // determine how much data is in the buffer."
134 void TermDestination(jpeg_compress_struct* cinfo) {
135 JpegEncoderState* state = static_cast<JpegEncoderState*>(cinfo->client_data);
136 DCHECK(state->out->size() >= state->image_buffer_used);
137
138 // update the used byte based on the next byte libjpeg would write to
139 state->image_buffer_used = cinfo->dest->next_output_byte - &(*state->out)[0];
140 DCHECK(state->image_buffer_used < state->out->size()) <<
141 "JPEG library busted, got a bad image buffer size";
142
143 // update our buffer so that it exactly encompases the desired data
144 state->out->resize(state->image_buffer_used);
145 }
146
147 #if !defined(JCS_EXTENSIONS)
148 // Converts RGBA to RGB (removing the alpha values) to prepare to send data to
149 // libjpeg. This converts one row of data in rgba with the given width in
150 // pixels the the given rgb destination buffer (which should have enough space
151 // reserved for the final data).
152 void StripAlpha(const unsigned char* rgba, int pixel_width, unsigned char* rgb)
153 {
154 for (int x = 0; x < pixel_width; x++)
155 memcpy(&rgb[x * 3], &rgba[x * 4], 3);
156 }
157
158 // Converts BGRA to RGB by reordering the color components and dropping the
159 // alpha. This converts one row of data in rgba with the given width in
160 // pixels the the given rgb destination buffer (which should have enough space
161 // reserved for the final data).
162 void BGRAtoRGB(const unsigned char* bgra, int pixel_width, unsigned char* rgb)
163 {
164 for (int x = 0; x < pixel_width; x++) {
165 const unsigned char* pixel_in = &bgra[x * 4];
166 unsigned char* pixel_out = &rgb[x * 3];
167 pixel_out[0] = pixel_in[2];
168 pixel_out[1] = pixel_in[1];
169 pixel_out[2] = pixel_in[0];
170 }
171 }
172 #endif // !defined(JCS_EXTENSIONS)
173
174 // This class destroys the given jpeg_compress object when it goes out of
175 // scope. It simplifies the error handling in Encode (and even applies to the
176 // success case).
177 class CompressDestroyer {
178 public:
179 CompressDestroyer() : cinfo_(NULL) {
180 }
181 ~CompressDestroyer() {
182 DestroyManagedObject();
183 }
184 void SetManagedObject(jpeg_compress_struct* ci) {
185 DestroyManagedObject();
186 cinfo_ = ci;
187 }
188 void DestroyManagedObject() {
189 if (cinfo_) {
190 jpeg_destroy_compress(cinfo_);
191 cinfo_ = NULL;
192 }
193 }
194 private:
195 jpeg_compress_struct* cinfo_;
196 };
197
198 } // namespace
199
200 bool JPEGCodec::Encode(const unsigned char* input, ColorFormat format,
201 int w, int h, int row_byte_width,
202 int quality, std::vector<unsigned char>* output) {
203 jpeg_compress_struct cinfo;
204 CompressDestroyer destroyer;
205 destroyer.SetManagedObject(&cinfo);
206 output->clear();
207 #if !defined(JCS_EXTENSIONS)
208 unsigned char* row_buffer = NULL;
209 #endif
210
211 // We set up the normal JPEG error routines, then override error_exit.
212 // This must be done before the call to create_compress.
213 CoderErrorMgr errmgr;
214 cinfo.err = jpeg_std_error(&errmgr.pub);
215 errmgr.pub.error_exit = ErrorExit;
216
217 // Establish the setjmp return context for ErrorExit to use.
218 if (setjmp(errmgr.setjmp_buffer)) {
219 // If we get here, the JPEG code has signaled an error.
220 // MSDN notes: "if you intend your code to be portable, do not rely on
221 // correct destruction of frame-based objects when executing a nonlocal
222 // goto using a call to longjmp." So we delete the CompressDestroyer's
223 // object manually instead.
224 destroyer.DestroyManagedObject();
225 #if !defined(JCS_EXTENSIONS)
226 delete[] row_buffer;
227 #endif
228 return false;
229 }
230
231 // The destroyer will destroy() cinfo on exit.
232 jpeg_create_compress(&cinfo);
233
234 cinfo.image_width = w;
235 cinfo.image_height = h;
236 cinfo.input_components = 3;
237 #ifdef JCS_EXTENSIONS
238 // Choose an input colorspace and return if it is an unsupported one. Since
239 // libjpeg-turbo supports all input formats used by Chromium (i.e. RGB, RGBA,
240 // and BGRA), we just map the input parameters to a colorspace used by
241 // libjpeg-turbo.
242 if (format == FORMAT_RGB) {
243 cinfo.input_components = 3;
244 cinfo.in_color_space = JCS_RGB;
245 } else if (format == FORMAT_RGBA ||
246 (format == FORMAT_SkBitmap && SK_R32_SHIFT == 0)) {
247 cinfo.input_components = 4;
248 cinfo.in_color_space = JCS_EXT_RGBX;
249 } else if (format == FORMAT_BGRA ||
250 (format == FORMAT_SkBitmap && SK_B32_SHIFT == 0)) {
251 cinfo.input_components = 4;
252 cinfo.in_color_space = JCS_EXT_BGRX;
253 } else {
254 // We can exit this function without calling jpeg_destroy_compress() because
255 // CompressDestroyer automaticaly calls it.
256 NOTREACHED() << "Invalid pixel format";
257 return false;
258 }
259 #else
260 cinfo.in_color_space = JCS_RGB;
261 #endif
262 cinfo.data_precision = 8;
263
264 jpeg_set_defaults(&cinfo);
265 jpeg_set_quality(&cinfo, quality, 1); // quality here is 0-100
266
267 // set up the destination manager
268 jpeg_destination_mgr destmgr;
269 destmgr.init_destination = InitDestination;
270 destmgr.empty_output_buffer = EmptyOutputBuffer;
271 destmgr.term_destination = TermDestination;
272 cinfo.dest = &destmgr;
273
274 JpegEncoderState state(output);
275 cinfo.client_data = &state;
276
277 jpeg_start_compress(&cinfo, 1);
278
279 // feed it the rows, doing necessary conversions for the color format
280 #ifdef JCS_EXTENSIONS
281 // This function already returns when the input format is not supported by
282 // libjpeg-turbo and needs conversion. Therefore, we just encode lines without
283 // conversions.
284 while (cinfo.next_scanline < cinfo.image_height) {
285 const unsigned char* row = &input[cinfo.next_scanline * row_byte_width];
286 jpeg_write_scanlines(&cinfo, const_cast<unsigned char**>(&row), 1);
287 }
288 #else
289 if (format == FORMAT_RGB) {
290 // no conversion necessary
291 while (cinfo.next_scanline < cinfo.image_height) {
292 const unsigned char* row = &input[cinfo.next_scanline * row_byte_width];
293 jpeg_write_scanlines(&cinfo, const_cast<unsigned char**>(&row), 1);
294 }
295 } else {
296 // get the correct format converter
297 void (*converter)(const unsigned char* in, int w, unsigned char* rgb);
298 if (format == FORMAT_RGBA ||
299 (format == FORMAT_SkBitmap && SK_R32_SHIFT == 0)) {
300 converter = StripAlpha;
301 } else if (format == FORMAT_BGRA ||
302 (format == FORMAT_SkBitmap && SK_B32_SHIFT == 0)) {
303 converter = BGRAtoRGB;
304 } else {
305 NOTREACHED() << "Invalid pixel format";
306 return false;
307 }
308
309 // output row after converting
310 row_buffer = new unsigned char[w * 3];
311
312 while (cinfo.next_scanline < cinfo.image_height) {
313 converter(&input[cinfo.next_scanline * row_byte_width], w, row_buffer);
314 jpeg_write_scanlines(&cinfo, &row_buffer, 1);
315 }
316 delete[] row_buffer;
317 }
318 #endif
319
320 jpeg_finish_compress(&cinfo);
321 return true;
322 } 101 }
323 102
324 // Decoder -------------------------------------------------------------------- 103 // Decoder --------------------------------------------------------------------
325 104
326 namespace { 105 namespace {
327 106
328 struct JpegDecoderState { 107 struct JpegDecoderState {
329 JpegDecoderState(const unsigned char* in, size_t len) 108 JpegDecoderState(const unsigned char* in, size_t len)
330 : input_buffer(in), input_buffer_length(len) { 109 : input_buffer(in), input_buffer_length(len) {
331 } 110 }
(...skipping 160 matching lines...) Expand 10 before | Expand all | Expand 10 after
492 return false; 271 return false;
493 272
494 // we want to always get RGB data out 273 // we want to always get RGB data out
495 switch (cinfo.jpeg_color_space) { 274 switch (cinfo.jpeg_color_space) {
496 case JCS_GRAYSCALE: 275 case JCS_GRAYSCALE:
497 case JCS_RGB: 276 case JCS_RGB:
498 case JCS_YCbCr: 277 case JCS_YCbCr:
499 #ifdef JCS_EXTENSIONS 278 #ifdef JCS_EXTENSIONS
500 // Choose an output colorspace and return if it is an unsupported one. 279 // Choose an output colorspace and return if it is an unsupported one.
501 // Same as JPEGCodec::Encode(), libjpeg-turbo supports all input formats 280 // Same as JPEGCodec::Encode(), libjpeg-turbo supports all input formats
502 // used by Chromium (i.e. RGB, RGBA, and BGRA) and we just map the input 281 // used by Chromium (i.e. RGBA and BGRA) and we just map the input
503 // parameters to a colorspace. 282 // parameters to a colorspace.
504 if (format == FORMAT_RGB) { 283 if (format == FORMAT_RGBA ||
505 cinfo.out_color_space = JCS_RGB; 284 (format == FORMAT_SkBitmap && SK_R32_SHIFT == 0)) {
506 cinfo.output_components = 3;
507 } else if (format == FORMAT_RGBA ||
508 (format == FORMAT_SkBitmap && SK_R32_SHIFT == 0)) {
509 cinfo.out_color_space = JCS_EXT_RGBX; 285 cinfo.out_color_space = JCS_EXT_RGBX;
510 cinfo.output_components = 4; 286 cinfo.output_components = 4;
511 } else if (format == FORMAT_BGRA || 287 } else if (format == FORMAT_BGRA ||
512 (format == FORMAT_SkBitmap && SK_B32_SHIFT == 0)) { 288 (format == FORMAT_SkBitmap && SK_B32_SHIFT == 0)) {
513 cinfo.out_color_space = JCS_EXT_BGRX; 289 cinfo.out_color_space = JCS_EXT_BGRX;
514 cinfo.output_components = 4; 290 cinfo.output_components = 4;
scroggo_chromium 2017/05/30 14:27:52 It looks like this is always 4 if JCS_EXTENSIONS i
msarett1 2017/06/07 18:01:14 Acknowledged.
515 } else { 291 } else {
516 // We can exit this function without calling jpeg_destroy_decompress() 292 // We can exit this function without calling jpeg_destroy_decompress()
517 // because DecompressDestroyer automaticaly calls it. 293 // because DecompressDestroyer automaticaly calls it.
518 NOTREACHED() << "Invalid pixel format"; 294 NOTREACHED() << "Invalid pixel format";
519 return false; 295 return false;
520 } 296 }
521 #else 297 #else
522 cinfo.out_color_space = JCS_RGB; 298 cinfo.out_color_space = JCS_RGB;
523 #endif 299 #endif
524 break; 300 break;
(...skipping 24 matching lines...) Expand all
549 // without conversions same as JPEGCodec::Encode(). 325 // without conversions same as JPEGCodec::Encode().
550 int row_write_stride = row_read_stride; 326 int row_write_stride = row_read_stride;
551 output->resize(row_write_stride * cinfo.output_height); 327 output->resize(row_write_stride * cinfo.output_height);
552 328
553 for (int row = 0; row < static_cast<int>(cinfo.output_height); row++) { 329 for (int row = 0; row < static_cast<int>(cinfo.output_height); row++) {
554 unsigned char* rowptr = &(*output)[row * row_write_stride]; 330 unsigned char* rowptr = &(*output)[row * row_write_stride];
555 if (!jpeg_read_scanlines(&cinfo, &rowptr, 1)) 331 if (!jpeg_read_scanlines(&cinfo, &rowptr, 1))
556 return false; 332 return false;
557 } 333 }
558 #else 334 #else
559 if (format == FORMAT_RGB) { 335 // Rows need conversion to output format: read into a temporary buffer and
scroggo_chromium 2017/05/30 14:27:52 So this CL also removes the ability to decode to R
msarett1 2017/06/07 18:01:14 Acknowledged. Removed in a previous CL.
scroggo_chromium 2017/05/30 14:27:52 The diff doesn't clearly show this, but I think th
msarett1 2017/06/07 18:01:14 Yes. Should be clearer now.
560 // easy case, row needs no conversion 336 // expand to the final one. Performance: we could avoid the extra
561 int row_write_stride = row_read_stride; 337 // allocation by doing the expansion in-place.
562 output->resize(row_write_stride * cinfo.output_height); 338 int row_write_stride;
339 void (*converter)(const unsigned char* rgb, int w, unsigned char* out);
340 if (format == FORMAT_RGBA ||
341 (format == FORMAT_SkBitmap && SK_R32_SHIFT == 0)) {
342 row_write_stride = cinfo.output_width * 4;
343 converter = AddAlpha;
344 } else if (format == FORMAT_BGRA ||
345 (format == FORMAT_SkBitmap && SK_B32_SHIFT == 0)) {
346 row_write_stride = cinfo.output_width * 4;
347 converter = RGBtoBGRA;
348 } else {
349 NOTREACHED() << "Invalid pixel format";
350 jpeg_destroy_decompress(&cinfo);
351 return false;
352 }
563 353
564 for (int row = 0; row < static_cast<int>(cinfo.output_height); row++) { 354 output->resize(row_write_stride * cinfo.output_height);
565 unsigned char* rowptr = &(*output)[row * row_write_stride]; 355
566 if (!jpeg_read_scanlines(&cinfo, &rowptr, 1)) 356 std::unique_ptr<unsigned char[]> row_data(new unsigned char[row_read_stride]);
567 return false; 357 unsigned char* rowptr = row_data.get();
568 } 358 for (int row = 0; row < static_cast<int>(cinfo.output_height); row++) {
569 } else { 359 if (!jpeg_read_scanlines(&cinfo, &rowptr, 1))
570 // Rows need conversion to output format: read into a temporary buffer and
571 // expand to the final one. Performance: we could avoid the extra
572 // allocation by doing the expansion in-place.
573 int row_write_stride;
574 void (*converter)(const unsigned char* rgb, int w, unsigned char* out);
575 if (format == FORMAT_RGBA ||
576 (format == FORMAT_SkBitmap && SK_R32_SHIFT == 0)) {
577 row_write_stride = cinfo.output_width * 4;
578 converter = AddAlpha;
579 } else if (format == FORMAT_BGRA ||
580 (format == FORMAT_SkBitmap && SK_B32_SHIFT == 0)) {
581 row_write_stride = cinfo.output_width * 4;
582 converter = RGBtoBGRA;
583 } else {
584 NOTREACHED() << "Invalid pixel format";
585 jpeg_destroy_decompress(&cinfo);
586 return false; 360 return false;
587 } 361 converter(rowptr, *w, &(*output)[row * row_write_stride]);
588
589 output->resize(row_write_stride * cinfo.output_height);
590
591 std::unique_ptr<unsigned char[]> row_data(
592 new unsigned char[row_read_stride]);
593 unsigned char* rowptr = row_data.get();
594 for (int row = 0; row < static_cast<int>(cinfo.output_height); row++) {
595 if (!jpeg_read_scanlines(&cinfo, &rowptr, 1))
596 return false;
597 converter(rowptr, *w, &(*output)[row * row_write_stride]);
598 }
599 } 362 }
600 #endif 363 #endif
601 364
602 jpeg_finish_decompress(&cinfo); 365 jpeg_finish_decompress(&cinfo);
603 jpeg_destroy_decompress(&cinfo); 366 jpeg_destroy_decompress(&cinfo);
604 return true; 367 return true;
605 } 368 }
606 369
607 // static 370 // static
608 std::unique_ptr<SkBitmap> JPEGCodec::Decode(const unsigned char* input, 371 std::unique_ptr<SkBitmap> JPEGCodec::Decode(const unsigned char* input,
609 size_t input_size) { 372 size_t input_size) {
610 int w, h; 373 int w, h;
611 std::vector<unsigned char> data_vector; 374 std::vector<unsigned char> data_vector;
612 if (!Decode(input, input_size, FORMAT_SkBitmap, &data_vector, &w, &h)) 375 if (!Decode(input, input_size, FORMAT_SkBitmap, &data_vector, &w, &h))
613 return nullptr; 376 return nullptr;
614 377
615 // Skia only handles 32 bit images. 378 // Skia only handles 32 bit images.
616 int data_length = w * h * 4; 379 int data_length = w * h * 4;
617 380
618 std::unique_ptr<SkBitmap> bitmap(new SkBitmap()); 381 std::unique_ptr<SkBitmap> bitmap(new SkBitmap());
619 bitmap->allocN32Pixels(w, h); 382 bitmap->allocN32Pixels(w, h);
620 memcpy(bitmap->getAddr32(0, 0), &data_vector[0], data_length); 383 memcpy(bitmap->getAddr32(0, 0), &data_vector[0], data_length);
621 384
622 return bitmap; 385 return bitmap;
623 } 386 }
624 387
625 } // namespace gfx 388 } // namespace gfx
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698