Make image decoding more fault resistant, less verbose.
This change address what happens when a jpeg is partially downloaded
before failing. Many browsers will render it anyway: we want Skia to
do the same. The JpegTest takes a perfectly cromulent jpeg file and
only passes into the ImageDecoder the first half of the image. We
then verify that the image decoder returns a valid bitmap of the
correct dimensions.
We also fixed some png library errors, including issue 1691.
Also, suppressed the majority of warnings from using libpng and
libjpeg. By default, most warnings are *not* suppressed in debug mode.
If you have a debug binary and wish to suppress warnings, set the
following environment variables to true
skia_images_png_suppressDecoderWarnings
skia_images_jpeg_suppressDecoderWarnings
or from within a program that links to Skia:
#if defined(SK_DEBUG)
#include "SkRTConf.h"
SK_CONF_SET("images.jpeg.suppressDecoderWarnings", true);
SK_CONF_SET("images.png.suppressDecoderWarnings", true);
#endif
I tested this, before (control) and after these changes (test), on
364,295 skps from the cluster telemetry.
- number of errors+warnings in control = 2804
- number of errors+warnings fixed = 2283
- number of PNG verbosity fixed = 2152
- number of PNG error fixed = 4
- number of PNG segfault fixed = 3
- number of PNG errors changed to warnings = 62
- number of JPG verbosity fixed = 26
- number of JPG error fixed = 91
Not all errors and warning have been fixed.
These numbers were generated using the find_bad_images_in_skps.py
program. This program may be useful going forward for testing
image-decoding libraries on skp files from the cluster telemetry.
find_bad_images_in_skps.py depends on the test_image_decoder program,
which simply executes the SkImageDecoder::DecodeFile function and uses
its exit status to report success or failure.
BUG=skia:1649
BUG=skia:1691
BUG=skia:1680
R=scroggo@google.com
Review URL: https://codereview.chromium.org/24449003
git-svn-id: http://skia.googlecode.com/svn/trunk@11597 2bbb7eff-a529-9590-31e7-b0007b416f81
diff --git a/src/images/SkImageDecoder_libjpeg.cpp b/src/images/SkImageDecoder_libjpeg.cpp
index 3b3ea88..7652249 100644
--- a/src/images/SkImageDecoder_libjpeg.cpp
+++ b/src/images/SkImageDecoder_libjpeg.cpp
@@ -19,6 +19,10 @@
#include "SkRect.h"
#include "SkCanvas.h"
+#if defined(SK_DEBUG)
+#include "SkRTConf.h" // SK_CONF_DECLARE
+#endif // defined(SK_DEBUG)
+
#include <stdio.h>
extern "C" {
#include "jpeglib.h"
@@ -35,6 +39,12 @@
// If ANDROID_RGB is defined by in the jpeg headers it indicates that jpeg offers
// support for two additional formats (1) JCS_RGBA_8888 and (2) JCS_RGB_565.
+#if defined(SK_DEBUG)
+SK_CONF_DECLARE(bool, c_suppressJPEGImageDecoderWarnings,
+ "images.jpeg.suppressDecoderWarnings", false,
+ "Suppress most JPG warnings when calling decode functions.");
+#endif // defined(SK_DEBUG)
+
//////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////
@@ -54,12 +64,27 @@
//////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////
+static void do_nothing_emit_message(jpeg_common_struct*, int) {
+ /* do nothing */
+}
+
static void initialize_info(jpeg_decompress_struct* cinfo, skjpeg_source_mgr* src_mgr) {
SkASSERT(cinfo != NULL);
SkASSERT(src_mgr != NULL);
jpeg_create_decompress(cinfo);
overwrite_mem_buffer_size(cinfo);
cinfo->src = src_mgr;
+#if defined(SK_DEBUG)
+ /* To suppress warnings with a SK_DEBUG binary, set the
+ * environment variable "skia_images_jpeg_suppressDecoderWarnings"
+ * to "true". Inside a program that links to skia:
+ * SK_CONF_SET("images.jpeg.suppressDecoderWarnings", true); */
+ if (c_suppressJPEGImageDecoderWarnings) {
+ cinfo->err->emit_message = &do_nothing_emit_message;
+ }
+#else // Always suppress in release mode.
+ cinfo->err->emit_message = &do_nothing_emit_message;
+#endif // defined(SK_DEBUG)
}
#ifdef SK_BUILD_FOR_ANDROID
@@ -443,6 +468,19 @@
}
#endif
+
+/**
+ Sets all pixels in given bitmap to SK_ColorWHITE for all rows >= y.
+ Used when decoding fails partway through reading scanlines to fill
+ remaining lines. */
+static void fill_below_level(int y, SkBitmap* bitmap) {
+ SkRect rect = SkRect::MakeLTRB(0, y, bitmap->width(), bitmap->height());
+ SkCanvas canvas(*bitmap);
+ canvas.clipRect(rect);
+ canvas.drawColor(SK_ColorWHITE);
+}
+
+
bool SkJPEGImageDecoder::onDecode(SkStream* stream, SkBitmap* bm, Mode mode) {
#ifdef TIME_DECODE
SkAutoTime atm("JPEG Decode");
@@ -553,10 +591,12 @@
while (cinfo.output_scanline < cinfo.output_height) {
int row_count = jpeg_read_scanlines(&cinfo, &rowptr, 1);
- // if row_count == 0, then we didn't get a scanline, so abort.
- // if we supported partial images, we might return true in this case
if (0 == row_count) {
- return return_false(cinfo, *bm, "read_scanlines");
+ // if row_count == 0, then we didn't get a scanline,
+ // so return early. We will return a partial image.
+ fill_below_level(cinfo.output_scanline, bm);
+ cinfo.output_scanline = cinfo.output_height;
+ break; // Skip to jpeg_finish_decompress()
}
if (this->shouldCancelDecode()) {
return return_false(cinfo, *bm, "shouldCancelDecode");
@@ -606,7 +646,11 @@
JSAMPLE* rowptr = (JSAMPLE*)srcRow;
int row_count = jpeg_read_scanlines(&cinfo, &rowptr, 1);
if (0 == row_count) {
- return return_false(cinfo, *bm, "read_scanlines");
+ // if row_count == 0, then we didn't get a scanline,
+ // so return early. We will return a partial image.
+ fill_below_level(y, bm);
+ cinfo.output_scanline = cinfo.output_height;
+ break; // Skip to jpeg_finish_decompress()
}
if (this->shouldCancelDecode()) {
return return_false(cinfo, *bm, "shouldCancelDecode");
@@ -785,8 +829,9 @@
int rowCount = jpeg_read_tile_scanline(cinfo,
fImageIndex->huffmanIndex(),
&rowptr);
- // if row_count == 0, then we didn't get a scanline, so abort.
- // if we supported partial images, we might return true in this case
+ // if rowCount == 0, then we didn't get a scanline, so abort.
+ // onDecodeSubset() relies on onBuildTileIndex(), which
+ // needs a complete image to succeed.
if (0 == rowCount) {
return return_false(*cinfo, bitmap, "read_scanlines");
}
@@ -844,6 +889,9 @@
for (int y = 0;; y++) {
JSAMPLE* rowptr = (JSAMPLE*)srcRow;
int row_count = jpeg_read_tile_scanline(cinfo, fImageIndex->huffmanIndex(), &rowptr);
+ // if row_count == 0, then we didn't get a scanline, so abort.
+ // onDecodeSubset() relies on onBuildTileIndex(), which
+ // needs a complete image to succeed.
if (0 == row_count) {
return return_false(*cinfo, bitmap, "read_scanlines");
}