Allow sampling GIF images during decode.
SkScaledBitmapSampler:
Add a mode for sampling rows out of order, used by GIF decoder for
interlaced images.
Add a getter for the X sampling rate.
SkImageDecoder_libgif:
Respect the sampleSize set on SkImageDecoder.
skimage_main:
Provide an option to set a sample size.
BUG=https://b.corp.google.com/issue?id=8999690
R=reed@google.com, djsollen@google.com, halcanary@google.com
Author: scroggo@google.com
Review URL: https://codereview.chromium.org/25354004
git-svn-id: http://skia.googlecode.com/svn/trunk@11659 2bbb7eff-a529-9590-31e7-b0007b416f81
diff --git a/src/images/SkImageDecoder_libgif.cpp b/src/images/SkImageDecoder_libgif.cpp
index 1d25120..08f37ef 100644
--- a/src/images/SkImageDecoder_libgif.cpp
+++ b/src/images/SkImageDecoder_libgif.cpp
@@ -1,4 +1,3 @@
-
/*
* Copyright 2006 The Android Open Source Project
*
@@ -7,12 +6,13 @@
*/
-#include "SkImageDecoder.h"
#include "SkColor.h"
#include "SkColorPriv.h"
+#include "SkColorTable.h"
+#include "SkImageDecoder.h"
+#include "SkScaledBitmapSampler.h"
#include "SkStream.h"
#include "SkTemplates.h"
-#include "SkPackBits.h"
#include "gif_lib.h"
@@ -154,6 +154,23 @@
return false;
}
+/**
+ * Skip rows in the source gif image.
+ * @param gif Source image.
+ * @param dst Scratch output needed by gif library call. Must be >= width bytes.
+ * @param width Bytes per row in the source image.
+ * @param rowsToSkip Number of rows to skip.
+ * @return True on success, false on GIF_ERROR.
+ */
+static bool skip_src_rows(GifFileType* gif, uint8_t* dst, int width, int rowsToSkip) {
+ for (int i = 0; i < rowsToSkip; i++) {
+ if (DGifGetLine(gif, dst, width) == GIF_ERROR) {
+ return false;
+ }
+ }
+ return true;
+}
+
bool SkGIFImageDecoder::onDecode(SkStream* sk_stream, SkBitmap* bm, Mode mode) {
#if GIFLIB_MAJOR < 5
GifFileType* gif = DGifOpen(sk_stream, DecodeCallBackProc);
@@ -196,13 +213,20 @@
width = gif->SWidth;
height = gif->SHeight;
- if (width <= 0 || height <= 0 ||
- !this->chooseFromOneChoice(SkBitmap::kIndex8_Config,
- width, height)) {
+ if (width <= 0 || height <= 0) {
+ return error_return(gif, *bm, "invalid dimensions");
+ }
+
+ // FIXME: We could give the caller a choice of images or configs.
+ if (!this->chooseFromOneChoice(SkBitmap::kIndex8_Config, width, height)) {
return error_return(gif, *bm, "chooseFromOneChoice");
}
- bm->setConfig(SkBitmap::kIndex8_Config, width, height);
+ SkScaledBitmapSampler sampler(width, height, this->getSampleSize());
+
+ bm->setConfig(SkBitmap::kIndex8_Config, sampler.scaledWidth(),
+ sampler.scaledHeight());
+
if (SkImageDecoder::kDecodeBounds_Mode == mode) {
return true;
}
@@ -226,33 +250,28 @@
}
colorCount = cmap->ColorCount;
- SkColorTable* ctable = SkNEW_ARGS(SkColorTable, (colorCount));
- SkPMColor* colorPtr = ctable->lockColors();
- for (int index = 0; index < colorCount; index++)
+ SkAutoTMalloc<SkPMColor> colorStorage(colorCount);
+ SkPMColor* colorPtr = colorStorage.get();
+ for (int index = 0; index < colorCount; index++) {
colorPtr[index] = SkPackARGB32(0xFF,
cmap->Colors[index].Red,
cmap->Colors[index].Green,
cmap->Colors[index].Blue);
+ }
transpIndex = find_transpIndex(temp_save, colorCount);
- if (transpIndex < 0)
- ctable->setFlags(ctable->getFlags() | SkColorTable::kColorsAreOpaque_Flag);
- else
- colorPtr[transpIndex] = 0; // ram in a transparent SkPMColor
- ctable->unlockColors(true);
+ bool reallyHasAlpha = transpIndex >= 0;
+ if (reallyHasAlpha) {
+ colorPtr[transpIndex] = SK_ColorTRANSPARENT; // ram in a transparent SkPMColor
+ }
- SkAutoUnref aurts(ctable);
+ SkAutoTUnref<SkColorTable> ctable(SkNEW_ARGS(SkColorTable, (colorPtr, colorCount)));
+ ctable->setIsOpaque(!reallyHasAlpha);
if (!this->allocPixelRef(bm, ctable)) {
return error_return(gif, *bm, "allocPixelRef");
}
}
- SkAutoLockPixels alp(*bm);
-
- // time to decode the scanlines
- //
- uint8_t* scanline = bm->getAddr8(0, 0);
- const int rowBytes = bm->rowBytes();
const int innerWidth = desc.Width;
const int innerHeight = desc.Height;
@@ -261,10 +280,19 @@
return error_return(gif, *bm, "non-pos inner width/height");
}
+ SkAutoLockPixels alp(*bm);
+
+ SkAutoMalloc storage(innerWidth);
+ uint8_t* scanline = (uint8_t*) storage.get();
+
+ // GIF has an option to store the scanlines of an image, plus a larger background,
+ // filled by a fill color. In this case, we will use a subset of the larger bitmap
+ // for sampling.
+ SkBitmap subset;
+ SkBitmap* workingBitmap;
// are we only a subset of the total bounds?
if ((desc.Top | desc.Left) > 0 ||
- innerWidth < width || innerHeight < height)
- {
+ innerWidth < width || innerHeight < height) {
int fill;
if (transpIndex >= 0) {
fill = transpIndex;
@@ -276,33 +304,62 @@
static_cast<unsigned>(colorCount)) {
fill = 0;
}
- memset(scanline, fill, bm->getSize());
- // bump our starting address
- scanline += desc.Top * rowBytes + desc.Left;
+ // Fill the background.
+ memset(bm->getPixels(), fill, bm->getSize());
+
+ // Create a subset of the bitmap.
+ SkIRect subsetRect(SkIRect::MakeXYWH(desc.Left / sampler.srcDX(),
+ desc.Top / sampler.srcDY(),
+ innerWidth / sampler.srcDX(),
+ innerHeight / sampler.srcDY()));
+ if (!bm->extractSubset(&subset, subsetRect)) {
+ return error_return(gif, *bm, "Extract failed.");
+ }
+ // Update the sampler. We'll now be only sampling into the subset.
+ sampler = SkScaledBitmapSampler(innerWidth, innerHeight, this->getSampleSize());
+ workingBitmap = ⊂
+ } else {
+ workingBitmap = bm;
+ }
+
+ // bm is already locked, but if we had to take a subset, it must be locked also,
+ // so that getPixels() will point to its pixels.
+ SkAutoLockPixels alpWorking(*workingBitmap);
+
+ if (!sampler.begin(workingBitmap, SkScaledBitmapSampler::kIndex, *this)) {
+ return error_return(gif, *bm, "Sampler failed to begin.");
}
// now decode each scanline
- if (gif->Image.Interlace)
- {
+ if (gif->Image.Interlace) {
+ // Iterate over the height of the source data. The sampler will
+ // take care of skipping unneeded rows.
GifInterlaceIter iter(innerHeight);
- for (int y = 0; y < innerHeight; y++)
- {
- uint8_t* row = scanline + iter.currY() * rowBytes;
- if (DGifGetLine(gif, row, innerWidth) == GIF_ERROR) {
+ for (int y = 0; y < innerHeight; y++){
+ if (DGifGetLine(gif, scanline, innerWidth) == GIF_ERROR) {
return error_return(gif, *bm, "interlace DGifGetLine");
}
+ sampler.sampleInterlaced(scanline, iter.currY());
iter.next();
}
- }
- else
- {
+ } else {
// easy, non-interlace case
- for (int y = 0; y < innerHeight; y++) {
+ const int outHeight = workingBitmap->height();
+ skip_src_rows(gif, scanline, innerWidth, sampler.srcY0());
+ for (int y = 0; y < outHeight; y++) {
if (DGifGetLine(gif, scanline, innerWidth) == GIF_ERROR) {
return error_return(gif, *bm, "DGifGetLine");
}
- scanline += rowBytes;
+ // scanline now contains the raw data. Sample it.
+ sampler.next(scanline);
+ if (y < outHeight - 1) {
+ skip_src_rows(gif, scanline, innerWidth, sampler.srcDY() - 1);
+ }
}
+ // skip the rest of the rows (if any)
+ int read = (outHeight - 1) * sampler.srcDY() + sampler.srcY0() + 1;
+ SkASSERT(read <= innerHeight);
+ skip_src_rows(gif, scanline, innerWidth, innerHeight - read);
}
goto DONE;
} break;
diff --git a/src/images/SkScaledBitmapSampler.cpp b/src/images/SkScaledBitmapSampler.cpp
index cad97ea..825f9d5 100644
--- a/src/images/SkScaledBitmapSampler.cpp
+++ b/src/images/SkScaledBitmapSampler.cpp
@@ -576,6 +576,8 @@
sk_throw();
}
+ SkDEBUGCODE(fSampleMode = kUninitialized_SampleMode);
+
if (sampleSize <= 1) {
fScaledWidth = width;
fScaledHeight = height;
@@ -711,6 +713,8 @@
}
bool SkScaledBitmapSampler::next(const uint8_t* SK_RESTRICT src) {
+ SkASSERT(kInterlaced_SampleMode != fSampleMode);
+ SkDEBUGCODE(fSampleMode = kConsecutive_SampleMode);
SkASSERT((unsigned)fCurrY < (unsigned)fScaledHeight);
bool hadAlpha = fRowProc(fDstRow, src + fX0 * fSrcPixelSize, fScaledWidth,
@@ -720,6 +724,29 @@
return hadAlpha;
}
+bool SkScaledBitmapSampler::sampleInterlaced(const uint8_t* SK_RESTRICT src, int srcY) {
+ SkASSERT(kConsecutive_SampleMode != fSampleMode);
+ SkDEBUGCODE(fSampleMode = kInterlaced_SampleMode);
+ // Any line that should be a part of the destination can be created by the formula:
+ // fY0 + (some multiplier) * fDY
+ // so if srcY - fY0 is not an integer multiple of fDY that srcY will be skipped.
+ const int srcYMinusY0 = srcY - fY0;
+ if (srcYMinusY0 % fDY != 0) {
+ // This line is not part of the output, so return false for alpha, since we have
+ // not added an alpha to the output.
+ return false;
+ }
+ // Unlike in next(), where the data is used sequentially, this function skips around,
+ // so fDstRow and fCurrY are never updated. fDstRow must always be the starting point
+ // of the destination bitmap's pixels, which is used to calculate the destination row
+ // each time this function is called.
+ const int dstY = srcYMinusY0 / fDY;
+ SkASSERT(dstY < fScaledHeight);
+ char* dstRow = fDstRow + dstY * fDstRowBytes;
+ return fRowProc(dstRow, src + fX0 * fSrcPixelSize, fScaledWidth,
+ fDX * fSrcPixelSize, dstY, fCTable);
+}
+
#ifdef SK_DEBUG
// The following code is for a test to ensure that changing the method to get the right row proc
// did not change the row proc unintentionally. Tested by ImageDecodingTest.cpp
diff --git a/src/images/SkScaledBitmapSampler.h b/src/images/SkScaledBitmapSampler.h
index a293fe7..e6c4577 100644
--- a/src/images/SkScaledBitmapSampler.h
+++ b/src/images/SkScaledBitmapSampler.h
@@ -22,6 +22,7 @@
int scaledHeight() const { return fScaledHeight; }
int srcY0() const { return fY0; }
+ int srcDX() const { return fDX; }
int srcDY() const { return fDY; }
enum SrcConfig {
@@ -42,6 +43,12 @@
// returns true if the row had non-opaque alpha in it
bool next(const uint8_t* SK_RESTRICT src);
+ // Like next(), but specifies the y value of the source row, so the
+ // rows can come in any order. If the row is not part of the output
+ // sample, it will be skipped. Only sampleInterlaced OR next should
+ // be called for one SkScaledBitmapSampler.
+ bool sampleInterlaced(const uint8_t* SK_RESTRICT src, int srcY);
+
typedef bool (*RowProc)(void* SK_RESTRICT dstRow,
const uint8_t* SK_RESTRICT src,
int width, int deltaSrc, int y,
@@ -56,6 +63,18 @@
int fDX; // step between X samples
int fDY; // step between Y samples
+#ifdef SK_DEBUG
+ // Keep track of whether the caller is using next or sampleInterlaced.
+ // Only one can be used per sampler.
+ enum SampleMode {
+ kUninitialized_SampleMode,
+ kConsecutive_SampleMode,
+ kInterlaced_SampleMode,
+ };
+
+ SampleMode fSampleMode;
+#endif
+
// setup state
char* fDstRow; // points into bitmap's pixels
size_t fDstRowBytes;
diff --git a/tools/skimage_main.cpp b/tools/skimage_main.cpp
index 11aeb76..6ff5978 100644
--- a/tools/skimage_main.cpp
+++ b/tools/skimage_main.cpp
@@ -28,9 +28,10 @@
DEFINE_string2(readPath, r, "", "Folder(s) and files to decode images. Required.");
DEFINE_string(readExpectationsPath, "", "Path to read JSON expectations from.");
DEFINE_bool(reencode, true, "Reencode the images to test encoding.");
+DEFINE_int32(sampleSize, 1, "Set the sampleSize for decoding.");
+DEFINE_bool(skip, false, "Skip writing zeroes.");
DEFINE_bool(testSubsetDecoding, true, "Test decoding subsets of images.");
DEFINE_string2(writePath, w, "", "Write rendered images into this directory.");
-DEFINE_bool(skip, false, "Skip writing zeroes.");
struct Format {
SkImageEncoder::Type fType;
@@ -400,6 +401,7 @@
SkAutoTDelete<SkImageDecoder> ad(codec);
codec->setSkipWritingZeroes(FLAGS_skip);
+ codec->setSampleSize(FLAGS_sampleSize);
stream.rewind();
// Create a string representing just the filename itself, for use in json expectations.