OLD | NEW |
---|---|
(Empty) | |
1 /* | |
2 * Copyright 2010, The Android Open Source Project | |
3 * | |
4 * Licensed under the Apache License, Version 2.0 (the "License"); | |
5 * you may not use this file except in compliance with the License. | |
6 * You may obtain a copy of the License at | |
7 * | |
8 * http://www.apache.org/licenses/LICENSE-2.0 | |
9 * | |
10 * Unless required by applicable law or agreed to in writing, software | |
11 * distributed under the License is distributed on an "AS IS" BASIS, | |
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
13 * See the License for the specific language governing permissions and | |
14 * limitations under the License. | |
15 */ | |
16 | |
17 #include "SkImageDecoder.h" | |
18 #include "SkImageEncoder.h" | |
19 #include "SkColorPriv.h" | |
20 #include "SkScaledBitmapSampler.h" | |
21 #include "SkStream.h" | |
22 #include "SkTemplates.h" | |
23 #include "SkUtils.h" | |
24 #include "SkTScopedPtr.h" | |
25 | |
26 // A WebP decoder only, on top of (subset of) libwebp | |
27 // For more information on WebP image format, and libwebp library, see: | |
28 // http://code.google.com/speed/webp/ | |
29 // http://www.webmproject.org/code/#libwebp_webp_image_decoder_library | |
30 // http://review.webmproject.org/gitweb?p=libwebp.git | |
31 | |
32 #include <stdio.h> | |
33 extern "C" { | |
34 // If moving libwebp out of skia source tree, path for webp headers must be | |
35 // updated accordingly. Here, we enforce using local copy in webp sub-directory. | |
36 #include "webp/decode.h" | |
37 #include "webp/encode.h" | |
38 } | |
39 | |
40 #ifdef ANDROID | |
41 #include <cutils/properties.h> | |
42 | |
43 // Key to lookup the size of memory buffer set in system property | |
44 static const char KEY_MEM_CAP[] = "ro.media.dec.webp.memcap"; | |
45 #endif | |
46 | |
47 // this enables timing code to report milliseconds for a decode | |
48 //#define TIME_DECODE | |
49 | |
50 ////////////////////////////////////////////////////////////////////////// | |
51 ////////////////////////////////////////////////////////////////////////// | |
52 | |
53 // Define VP8 I/O on top of Skia stream | |
54 | |
55 ////////////////////////////////////////////////////////////////////////// | |
56 ////////////////////////////////////////////////////////////////////////// | |
57 | |
58 static const size_t WEBP_VP8_HEADER_SIZE = 64; | |
59 static const size_t WEBP_IDECODE_BUFFER_SZ = (1 << 16); | |
60 | |
61 // Parse headers of RIFF container, and check for valid Webp (VP8) content. | |
62 static bool webp_parse_header(SkStream* stream, int* width, int* height, | |
63 int* alpha) { | |
64 unsigned char buffer[WEBP_VP8_HEADER_SIZE]; | |
65 const uint32_t contentSize = stream->getLength(); | |
66 const size_t len = stream->read(buffer, WEBP_VP8_HEADER_SIZE); | |
67 const uint32_t read_bytes = (contentSize < WEBP_VP8_HEADER_SIZE) ? | |
68 contentSize : WEBP_VP8_HEADER_SIZE; | |
69 if (len != read_bytes) { | |
70 return false; // can't read enough | |
71 } | |
72 | |
73 WebPBitstreamFeatures features; | |
74 VP8StatusCode status = WebPGetFeatures(buffer, read_bytes, &features); | |
75 if (status != VP8_STATUS_OK) { | |
76 return false; // Invalid WebP file. | |
77 } | |
78 *width = features.width; | |
79 *height = features.height; | |
80 *alpha = features.has_alpha; | |
81 | |
82 // sanity check for image size that's about to be decoded. | |
83 { | |
84 Sk64 size; | |
85 size.setMul(*width, *height); | |
86 if (size.isNeg() || !size.is32()) { | |
87 return false; | |
88 } | |
89 // now check that if we are 4-bytes per pixel, we also don't overflow | |
90 if (size.get32() > (0x7FFFFFFF >> 2)) { | |
91 return false; | |
92 } | |
93 } | |
94 return true; | |
95 } | |
96 | |
97 class SkWEBPImageDecoder: public SkImageDecoder { | |
98 public: | |
99 virtual Format getFormat() const { | |
100 return kWEBP_Format; | |
101 } | |
102 | |
103 protected: | |
104 virtual bool onBuildTileIndex(SkStream *stream, int *width, int *height); | |
105 virtual bool onDecodeRegion(SkBitmap* bitmap, SkIRect rect); | |
106 virtual bool onDecode(SkStream* stream, SkBitmap* bm, Mode); | |
107 | |
108 private: | |
109 bool setDecodeConfig(SkBitmap* decodedBitmap, int width, int height); | |
110 SkStream *inputStream; | |
111 int origWidth; | |
112 int origHeight; | |
113 int hasAlpha; | |
114 }; | |
115 | |
116 ////////////////////////////////////////////////////////////////////////// | |
117 | |
118 #include "SkTime.h" | |
119 | |
120 class AutoTimeMillis { | |
121 public: | |
122 AutoTimeMillis(const char label[]) : | |
123 fLabel(label) { | |
124 if (!fLabel) { | |
125 fLabel = ""; | |
126 } | |
127 fNow = SkTime::GetMSecs(); | |
128 } | |
129 ~AutoTimeMillis() { | |
130 SkDebugf("---- Time (ms): %s %d\n", fLabel, SkTime::GetMSecs() - fNow); | |
131 } | |
132 private: | |
133 const char* fLabel; | |
134 SkMSec fNow; | |
135 }; | |
136 | |
137 /////////////////////////////////////////////////////////////////////////////// | |
138 | |
139 // This guy exists just to aid in debugging, as it allows debuggers to just | |
140 // set a break-point in one place to see all error exists. | |
141 static bool return_false(const SkBitmap& bm, const char msg[]) { | |
142 #if SK_DEBUG | |
143 SkDebugf("libwebp error %s [%d %d]", msg, bm.width(), bm.height()); | |
144 #endif | |
145 return false; // must always return false | |
146 } | |
147 | |
148 static WEBP_CSP_MODE webp_decode_mode(SkBitmap* decodedBitmap, int hasAlpha) { | |
149 WEBP_CSP_MODE mode = MODE_LAST; | |
150 SkBitmap::Config config = decodedBitmap->config(); | |
151 // For images that have alpha, choose appropriate color mode (MODE_rgbA, | |
152 // MODE_rgbA_4444) that pre-multiplies RGB pixel values with transparency | |
153 // factor (alpha). | |
154 if (config == SkBitmap::kARGB_8888_Config) { | |
155 mode = hasAlpha ? MODE_rgbA : MODE_RGBA; | |
156 } else if (config == SkBitmap::kARGB_4444_Config) { | |
157 mode = hasAlpha ? MODE_rgbA_4444 : MODE_RGBA_4444; | |
158 } else if (config == SkBitmap::kRGB_565_Config) { | |
159 mode = MODE_RGB_565; | |
160 } | |
161 SkASSERT(mode != MODE_LAST); | |
162 return mode; | |
163 } | |
164 | |
165 // Incremental WebP image decoding. Reads input buffer of 64K size iteratively | |
166 // and decodes this block to appropriate color-space as per config object. | |
167 static bool webp_idecode(SkStream* stream, WebPDecoderConfig& config) { | |
168 WebPIDecoder* idec = WebPIDecode(NULL, 0, &config); | |
169 if (idec == NULL) { | |
170 WebPFreeDecBuffer(&config.output); | |
171 return false; | |
172 } | |
173 | |
174 stream->rewind(); | |
175 const uint32_t contentSize = stream->getLength(); | |
176 const uint32_t read_buffer_size = (contentSize < WEBP_IDECODE_BUFFER_SZ) ? | |
177 contentSize : WEBP_IDECODE_BUFFER_SZ; | |
178 SkAutoMalloc srcStorage(read_buffer_size); | |
179 unsigned char* input = (uint8_t*)srcStorage.get(); | |
180 if (input == NULL) { | |
181 WebPIDelete(idec); | |
182 WebPFreeDecBuffer(&config.output); | |
183 return false; | |
184 } | |
185 | |
186 uint32_t bytes_remaining = contentSize; | |
187 while (bytes_remaining > 0) { | |
188 const uint32_t bytes_to_read = | |
189 (bytes_remaining < WEBP_IDECODE_BUFFER_SZ) ? | |
190 bytes_remaining : WEBP_IDECODE_BUFFER_SZ; | |
191 | |
192 const size_t bytes_read = stream->read(input, bytes_to_read); | |
193 if (bytes_read == 0) { | |
194 break; | |
195 } | |
196 | |
197 VP8StatusCode status = WebPIAppend(idec, input, bytes_read); | |
198 if (status == VP8_STATUS_OK || status == VP8_STATUS_SUSPENDED) { | |
199 bytes_remaining -= bytes_read; | |
200 } else { | |
201 break; | |
202 } | |
203 } | |
204 srcStorage.free(); | |
205 WebPIDelete(idec); | |
206 WebPFreeDecBuffer(&config.output); | |
207 | |
208 if (bytes_remaining > 0) { | |
209 return false; | |
210 } else { | |
211 return true; | |
212 } | |
213 } | |
214 | |
215 static bool webp_get_config_resize(WebPDecoderConfig& config, | |
216 SkBitmap* decodedBitmap, | |
217 int width, int height, int hasAlpha) { | |
218 WEBP_CSP_MODE mode = webp_decode_mode(decodedBitmap, hasAlpha); | |
219 if (mode == MODE_LAST) { | |
220 return false; | |
221 } | |
222 | |
223 if (WebPInitDecoderConfig(&config) == 0) { | |
224 return false; | |
225 } | |
226 | |
227 config.output.colorspace = mode; | |
228 config.output.u.RGBA.rgba = (uint8_t*)decodedBitmap->getPixels(); | |
229 config.output.u.RGBA.stride = decodedBitmap->rowBytes(); | |
230 config.output.u.RGBA.size = decodedBitmap->getSize(); | |
231 config.output.is_external_memory = 1; | |
232 | |
233 if (width != decodedBitmap->width() || | |
234 height != decodedBitmap->height()) { | |
235 config.options.use_scaling = 1; | |
236 config.options.scaled_width = decodedBitmap->width(); | |
237 config.options.scaled_height = decodedBitmap->height(); | |
238 } | |
239 | |
240 return true; | |
241 } | |
242 | |
243 static bool webp_get_config_resize_crop(WebPDecoderConfig& config, | |
244 SkBitmap* decodedBitmap, | |
245 SkIRect region, int hasAlpha) { | |
246 | |
247 if (!webp_get_config_resize( | |
248 config, decodedBitmap, region.width(), region.height(), hasAlpha)) { | |
249 return false; | |
250 } | |
251 | |
252 config.options.use_cropping = 1; | |
253 config.options.crop_left = region.fLeft; | |
254 config.options.crop_top = region.fTop; | |
255 config.options.crop_width = region.width(); | |
256 config.options.crop_height = region.height(); | |
257 | |
258 return true; | |
259 } | |
260 | |
261 bool SkWEBPImageDecoder::setDecodeConfig(SkBitmap* decodedBitmap, | |
262 int width, int height) { | |
263 SkBitmap::Config config = this->getPrefConfig(k32Bit_SrcDepth, hasAlpha); | |
264 | |
265 // YUV converter supports output in RGB565, RGBA4444 and RGBA8888 formats. | |
266 if (hasAlpha) { | |
267 if (config != SkBitmap::kARGB_4444_Config) { | |
268 config = SkBitmap::kARGB_8888_Config; | |
269 } | |
270 } else { | |
271 if (config != SkBitmap::kRGB_565_Config && | |
272 config != SkBitmap::kARGB_4444_Config) { | |
273 config = SkBitmap::kARGB_8888_Config; | |
274 } | |
275 } | |
276 | |
277 if (!this->chooseFromOneChoice(config, width, height)) { | |
278 return false; | |
279 } | |
280 | |
281 decodedBitmap->setConfig(config, width, height, 0); | |
282 | |
283 decodedBitmap->setIsOpaque(!hasAlpha); | |
284 | |
285 return true; | |
286 } | |
287 | |
288 bool SkWEBPImageDecoder::onBuildTileIndex(SkStream* stream, | |
289 int *width, int *height) { | |
290 int origWidth, origHeight, hasAlpha; | |
291 if (!webp_parse_header(stream, &origWidth, &origHeight, &hasAlpha)) { | |
292 return false; | |
293 } | |
294 | |
295 stream->rewind(); | |
296 *width = origWidth; | |
297 *height = origHeight; | |
298 | |
299 this->inputStream = stream; | |
300 this->origWidth = origWidth; | |
301 this->origHeight = origHeight; | |
302 this->hasAlpha = hasAlpha; | |
303 | |
304 return true; | |
305 } | |
306 | |
307 static bool isConfigCompatible(SkBitmap* bitmap) { | |
308 SkBitmap::Config config = bitmap->config(); | |
309 return config == SkBitmap::kARGB_4444_Config || | |
310 config == SkBitmap::kRGB_565_Config || | |
311 config == SkBitmap::kARGB_8888_Config; | |
312 } | |
313 | |
314 bool SkWEBPImageDecoder::onDecodeRegion(SkBitmap* decodedBitmap, | |
315 SkIRect region) { | |
djsollen
2013/03/11 17:47:24
const SkIRect&
| |
316 SkIRect rect = SkIRect::MakeWH(origWidth, origHeight); | |
317 | |
318 if (!rect.intersect(region)) { | |
319 // If the requested region is entirely outsides the image, just | |
320 // returns false | |
321 return false; | |
322 } | |
323 | |
324 const int sampleSize = this->getSampleSize(); | |
325 SkScaledBitmapSampler sampler(rect.width(), rect.height(), sampleSize); | |
326 const int width = sampler.scaledWidth(); | |
327 const int height = sampler.scaledHeight(); | |
328 | |
329 // The image can be decoded directly to decodedBitmap if | |
330 // 1. the region is within the image range | |
331 // 2. bitmap's config is compatible | |
332 // 3. bitmap's size is same as the required region (after sampled) | |
333 bool directDecode = (rect == region) && | |
334 (decodedBitmap->isNull() || | |
335 (isConfigCompatible(decodedBitmap) && | |
336 (decodedBitmap->width() == width) && | |
337 (decodedBitmap->height() == height))); | |
338 SkTScopedPtr<SkBitmap> adb; | |
339 SkBitmap *bitmap = decodedBitmap; | |
340 | |
341 if (!directDecode) { | |
342 // allocates a temp bitmap | |
343 bitmap = new SkBitmap; | |
344 adb.reset(bitmap); | |
345 } | |
346 | |
347 if (bitmap->isNull()) { | |
348 if (!setDecodeConfig(bitmap, width, height)) { | |
349 return false; | |
350 } | |
351 // alloc from native heap if it is a temp bitmap. (prevent GC) | |
352 bool allocResult = (bitmap == decodedBitmap) | |
353 ? allocPixelRef(bitmap, NULL) | |
354 : bitmap->allocPixels(); | |
355 if (!allocResult) { | |
356 return return_false(*decodedBitmap, "allocPixelRef"); | |
357 } | |
358 } else { | |
359 // This is also called in setDecodeConfig in above block. | |
360 // i.e., when bitmap->isNull() is true. | |
361 if (!chooseFromOneChoice(bitmap->config(), width, height)) { | |
362 return false; | |
363 } | |
364 } | |
365 | |
366 SkAutoLockPixels alp(*bitmap); | |
367 WebPDecoderConfig config; | |
368 if (!webp_get_config_resize_crop(config, bitmap, rect, hasAlpha)) { | |
369 return false; | |
370 } | |
371 | |
372 // Decode the WebP image data stream using WebP incremental decoding for | |
373 // the specified cropped image-region. | |
374 if (!webp_idecode(this->inputStream, config)) { | |
375 return false; | |
376 } | |
377 | |
378 if (!directDecode) { | |
379 cropBitmap(decodedBitmap, bitmap, sampleSize, region.x(), region.y(), | |
380 region.width(), region.height(), rect.x(), rect.y()); | |
381 } | |
382 return true; | |
383 } | |
384 | |
385 bool SkWEBPImageDecoder::onDecode(SkStream* stream, SkBitmap* decodedBitmap, | |
386 Mode mode) { | |
387 #ifdef TIME_DECODE | |
388 AutoTimeMillis atm("WEBP Decode"); | |
389 #endif | |
390 | |
391 int origWidth, origHeight, hasAlpha; | |
392 if (!webp_parse_header(stream, &origWidth, &origHeight, &hasAlpha)) { | |
393 return false; | |
394 } | |
395 this->hasAlpha = hasAlpha; | |
396 | |
397 const int sampleSize = this->getSampleSize(); | |
398 SkScaledBitmapSampler sampler(origWidth, origHeight, sampleSize); | |
399 | |
400 // If only bounds are requested, done | |
401 if (SkImageDecoder::kDecodeBounds_Mode == mode) { | |
402 if (!setDecodeConfig(decodedBitmap, sampler.scaledWidth(), | |
403 sampler.scaledHeight())) { | |
404 return false; | |
405 } | |
406 return true; | |
407 } | |
408 | |
409 // No Bitmap reuse supported for this format | |
410 if (!decodedBitmap->isNull()) { | |
411 return false; | |
412 } | |
413 if (!setDecodeConfig(decodedBitmap, sampler.scaledWidth(), | |
414 sampler.scaledHeight())) { | |
415 return false; | |
416 } | |
417 | |
418 if (!this->allocPixelRef(decodedBitmap, NULL)) { | |
419 return return_false(*decodedBitmap, "allocPixelRef"); | |
420 } | |
421 | |
422 SkAutoLockPixels alp(*decodedBitmap); | |
423 | |
424 WebPDecoderConfig config; | |
425 if (!webp_get_config_resize(config, decodedBitmap, origWidth, origHeight, | |
426 hasAlpha)) { | |
427 return false; | |
428 } | |
429 | |
430 // Decode the WebP image data stream using WebP incremental decoding. | |
431 return webp_idecode(stream, config); | |
432 } | |
433 | |
434 /////////////////////////////////////////////////////////////////////////////// | |
435 | |
436 typedef void (*ScanlineImporter)(const uint8_t* in, uint8_t* out, int width, | |
437 const SkPMColor* SK_RESTRICT ctable); | |
438 | |
439 static void ARGB_8888_To_RGB(const uint8_t* in, uint8_t* rgb, int width, | |
440 const SkPMColor*) { | |
441 const uint32_t* SK_RESTRICT src = (const uint32_t*)in; | |
442 for (int i = 0; i < width; ++i) { | |
443 const uint32_t c = *src++; | |
444 rgb[0] = SkGetPackedR32(c); | |
445 rgb[1] = SkGetPackedG32(c); | |
446 rgb[2] = SkGetPackedB32(c); | |
447 rgb += 3; | |
448 } | |
449 } | |
450 | |
451 static void RGB_565_To_RGB(const uint8_t* in, uint8_t* rgb, int width, | |
452 const SkPMColor*) { | |
453 const uint16_t* SK_RESTRICT src = (const uint16_t*)in; | |
454 for (int i = 0; i < width; ++i) { | |
455 const uint16_t c = *src++; | |
456 rgb[0] = SkPacked16ToR32(c); | |
457 rgb[1] = SkPacked16ToG32(c); | |
458 rgb[2] = SkPacked16ToB32(c); | |
459 rgb += 3; | |
460 } | |
461 } | |
462 | |
463 static void ARGB_4444_To_RGB(const uint8_t* in, uint8_t* rgb, int width, | |
464 const SkPMColor*) { | |
465 const SkPMColor16* SK_RESTRICT src = (const SkPMColor16*)in; | |
466 for (int i = 0; i < width; ++i) { | |
467 const SkPMColor16 c = *src++; | |
468 rgb[0] = SkPacked4444ToR32(c); | |
469 rgb[1] = SkPacked4444ToG32(c); | |
470 rgb[2] = SkPacked4444ToB32(c); | |
471 rgb += 3; | |
472 } | |
473 } | |
474 | |
475 static void Index8_To_RGB(const uint8_t* in, uint8_t* rgb, int width, | |
476 const SkPMColor* SK_RESTRICT ctable) { | |
477 const uint8_t* SK_RESTRICT src = (const uint8_t*)in; | |
478 for (int i = 0; i < width; ++i) { | |
479 const uint32_t c = ctable[*src++]; | |
480 rgb[0] = SkGetPackedR32(c); | |
481 rgb[1] = SkGetPackedG32(c); | |
482 rgb[2] = SkGetPackedB32(c); | |
483 rgb += 3; | |
484 } | |
485 } | |
486 | |
487 static ScanlineImporter ChooseImporter(const SkBitmap::Config& config) { | |
488 switch (config) { | |
489 case SkBitmap::kARGB_8888_Config: | |
490 return ARGB_8888_To_RGB; | |
491 case SkBitmap::kRGB_565_Config: | |
492 return RGB_565_To_RGB; | |
493 case SkBitmap::kARGB_4444_Config: | |
494 return ARGB_4444_To_RGB; | |
495 case SkBitmap::kIndex8_Config: | |
496 return Index8_To_RGB; | |
497 default: | |
498 return NULL; | |
499 } | |
500 } | |
501 | |
502 static int StreamWriter(const uint8_t* data, size_t data_size, | |
503 const WebPPicture* const picture) { | |
504 SkWStream* const stream = (SkWStream*)picture->custom_ptr; | |
505 return stream->write(data, data_size) ? 1 : 0; | |
506 } | |
507 | |
508 class SkWEBPImageEncoder : public SkImageEncoder { | |
509 protected: | |
510 virtual bool onEncode(SkWStream* stream, const SkBitmap& bm, int quality); | |
511 }; | |
512 | |
513 bool SkWEBPImageEncoder::onEncode(SkWStream* stream, const SkBitmap& bm, | |
514 int quality) { | |
515 const SkBitmap::Config config = bm.getConfig(); | |
516 const ScanlineImporter scanline_import = ChooseImporter(config); | |
517 if (NULL == scanline_import) { | |
518 return false; | |
519 } | |
520 | |
521 SkAutoLockPixels alp(bm); | |
522 SkAutoLockColors ctLocker; | |
523 if (NULL == bm.getPixels()) { | |
524 return false; | |
525 } | |
526 | |
527 WebPConfig webp_config; | |
528 if (!WebPConfigPreset(&webp_config, WEBP_PRESET_DEFAULT, quality)) { | |
529 return false; | |
530 } | |
531 | |
532 WebPPicture pic; | |
533 WebPPictureInit(&pic); | |
534 pic.width = bm.width(); | |
535 pic.height = bm.height(); | |
536 pic.writer = StreamWriter; | |
537 pic.custom_ptr = (void*)stream; | |
538 | |
539 const SkPMColor* colors = ctLocker.lockColors(bm); | |
540 const uint8_t* src = (uint8_t*)bm.getPixels(); | |
541 const int rgb_stride = pic.width * 3; | |
542 | |
543 // Import (for each scanline) the bit-map image (in appropriate color-space) | |
544 // to RGB color space. | |
545 uint8_t* rgb = new uint8_t[rgb_stride * pic.height]; | |
546 for (int y = 0; y < pic.height; ++y) { | |
547 scanline_import(src + y * bm.rowBytes(), rgb + y * rgb_stride, | |
548 pic.width, colors); | |
549 } | |
550 | |
551 bool ok = WebPPictureImportRGB(&pic, rgb, rgb_stride); | |
552 delete[] rgb; | |
553 | |
554 ok = ok && WebPEncode(&webp_config, &pic); | |
555 WebPPictureFree(&pic); | |
556 | |
557 return ok; | |
558 } | |
559 | |
560 | |
561 /////////////////////////////////////////////////////////////////////////////// | |
562 DEFINE_DECODER_CREATOR(WEBPImageDecoder); | |
563 DEFINE_ENCODER_CREATOR(WEBPImageEncoder); | |
564 /////////////////////////////////////////////////////////////////////////////// | |
565 | |
566 #include "SkTRegistry.h" | |
567 | |
568 static SkImageDecoder* sk_libwebp_dfactory(SkStream* stream) { | |
569 int width, height, hasAlpha; | |
570 if (!webp_parse_header(stream, &width, &height, &hasAlpha)) { | |
571 return NULL; | |
572 } | |
573 | |
574 // Magic matches, call decoder | |
575 return SkNEW(SkWEBPImageDecoder); | |
576 } | |
577 | |
578 static SkImageEncoder* sk_libwebp_efactory(SkImageEncoder::Type t) { | |
579 return (SkImageEncoder::kWEBP_Type == t) ? SkNEW(SkWEBPImageEncoder) : NUL L; | |
580 } | |
581 | |
582 static SkTRegistry<SkImageDecoder*, SkStream*> gDReg(sk_libwebp_dfactory); | |
583 static SkTRegistry<SkImageEncoder*, SkImageEncoder::Type> gEReg(sk_libwebp_efact ory); | |
OLD | NEW |