diff options
Diffstat (limited to 'tools')
-rw-r--r-- | tools/SkBitmapRegionCanvas.cpp | 95 | ||||
-rw-r--r-- | tools/SkBitmapRegionCodec.cpp | 143 | ||||
-rw-r--r-- | tools/SkBitmapRegionCodec.h | 41 | ||||
-rw-r--r-- | tools/SkBitmapRegionDecoderInterface.cpp | 18 | ||||
-rw-r--r-- | tools/SkBitmapRegionDecoderInterface.h | 13 | ||||
-rw-r--r-- | tools/SkCodecTools.h | 48 |
6 files changed, 276 insertions, 82 deletions
diff --git a/tools/SkBitmapRegionCanvas.cpp b/tools/SkBitmapRegionCanvas.cpp index 0892aa55e1..f9c9573c44 100644 --- a/tools/SkBitmapRegionCanvas.cpp +++ b/tools/SkBitmapRegionCanvas.cpp @@ -16,30 +16,6 @@ SkBitmapRegionCanvas::SkBitmapRegionCanvas(SkCodec* decoder) {} /* - * Chooses the correct image subset offsets and dimensions for the partial decode. - * - * @return true if the subset is completely contained within the image - * false otherwise - */ -static bool set_subset_region(int inputOffset, int inputDimension, - int imageOriginalDimension, int* imageSubsetOffset, int* outOffset, - int* imageSubsetDimension) { - - // This must be at least zero, we can't start decoding the image at a negative coordinate. - *imageSubsetOffset = SkTMax(0, inputOffset); - - // If inputOffset is less than zero, we decode to an offset location in the output bitmap. - *outOffset = *imageSubsetOffset - inputOffset; - - // Use imageSusetOffset to make sure we don't decode pixels past the edge of the image. - // Use outOffset to make sure we don't decode pixels past the edge of the region. - *imageSubsetDimension = SkTMin(imageOriginalDimension - *imageSubsetOffset, - inputDimension - *outOffset); - - return (*outOffset == 0) && (*imageSubsetDimension == inputDimension); -} - -/* * Three differences from the Android version: * Returns a Skia bitmap instead of an Android bitmap. * Android version attempts to reuse a recycled bitmap. @@ -56,48 +32,25 @@ SkBitmap* SkBitmapRegionCanvas::decodeRegion(int inputX, int inputY, return nullptr; } - // The client may not necessarily request a region that is fully within - // the image. We may need to do some calculation to determine what part - // of the image to decode. - - // The left offset of the portion of the image we want, where zero - // indicates the left edge of the image. - int imageSubsetX; + // Fix the input sampleSize if necessary. + if (sampleSize < 1) { + sampleSize = 1; + } // The size of the output bitmap is determined by the size of the - // requested region, not by the size of the intersection of the region - // and the image dimensions. If inputX is negative, we will need to - // place decoded pixels into the output bitmap starting at a left offset. - // If this is non-zero, imageSubsetX must be zero. + // requested subset, not by the size of the intersection of the subset + // and the image dimensions. + // If inputX is negative, we will need to place decoded pixels into the + // output bitmap starting at a left offset. Call this outX. + // If outX is non-zero, subsetX must be zero. + // If inputY is negative, we will need to place decoded pixels into the + // output bitmap starting at a top offset. Call this outY. + // If outY is non-zero, subsetY must be zero. int outX; - - // The width of the portion of the image that we will write to the output - // bitmap. If the region is not fully contained within the image, this - // will not be the same as inputWidth. - int imageSubsetWidth; - bool imageContainsEntireSubset = set_subset_region(inputX, inputWidth, this->width(), - &imageSubsetX, &outX, &imageSubsetWidth); - - // The top offset of the portion of the image we want, where zero - // indicates the top edge of the image. - int imageSubsetY; - - // The size of the output bitmap is determined by the size of the - // requested region, not by the size of the intersection of the region - // and the image dimensions. If inputY is negative, we will need to - // place decoded pixels into the output bitmap starting at a top offset. - // If this is non-zero, imageSubsetY must be zero. int outY; - - // The height of the portion of the image that we will write to the output - // bitmap. If the region is not fully contained within the image, this - // will not be the same as inputHeight. - int imageSubsetHeight; - imageContainsEntireSubset &= set_subset_region(inputY, inputHeight, this->height(), - &imageSubsetY, &outY, &imageSubsetHeight); - - if (imageSubsetWidth <= 0 || imageSubsetHeight <= 0) { - SkCodecPrintf("Error: Region must intersect part of the image.\n"); + SkIRect subset = SkIRect::MakeXYWH(inputX, inputY, inputWidth, inputHeight); + SubsetType type = adjust_subset_rect(fDecoder->getInfo().dimensions(), &subset, &outX, &outY); + if (SubsetType::kOutside_SubsetType == type) { return nullptr; } @@ -108,7 +61,7 @@ SkBitmap* SkBitmapRegionCanvas::decodeRegion(int inputX, int inputY, } SkImageInfo decodeInfo = SkImageInfo::Make(this->width(), this->height(), dstColorType, dstAlphaType); - + // Start the scanline decoder SkCodec::Result r = fDecoder->startScanlineDecode(decodeInfo); if (SkCodec::kSuccess != r) { @@ -118,20 +71,20 @@ SkBitmap* SkBitmapRegionCanvas::decodeRegion(int inputX, int inputY, // Allocate a bitmap for the unscaled decode SkBitmap tmp; - SkImageInfo tmpInfo = decodeInfo.makeWH(this->width(), imageSubsetHeight); + SkImageInfo tmpInfo = decodeInfo.makeWH(this->width(), subset.height()); if (!tmp.tryAllocPixels(tmpInfo)) { SkCodecPrintf("Error: Could not allocate pixels.\n"); return nullptr; } // Skip the unneeded rows - if (!fDecoder->skipScanlines(imageSubsetY)) { + if (!fDecoder->skipScanlines(subset.y())) { SkCodecPrintf("Error: Failed to skip scanlines.\n"); return nullptr; } // Decode the necessary rows - fDecoder->getScanlines(tmp.getAddr(0, 0), imageSubsetHeight, tmp.rowBytes()); + fDecoder->getScanlines(tmp.getAddr(0, 0), subset.height(), tmp.rowBytes()); // Calculate the size of the output const int outWidth = get_scaled_dimension(inputWidth, sampleSize); @@ -152,18 +105,18 @@ SkBitmap* SkBitmapRegionCanvas::decodeRegion(int inputX, int inputY, // TODO (msarett): This could be skipped if memory is zero initialized. // This would matter if this code is moved to Android and // uses Android bitmaps. - if (!imageContainsEntireSubset) { + if (SubsetType::kPartiallyInside_SubsetType == type) { bitmap->eraseColor(0); } // Use a canvas to crop and scale to the destination bitmap SkCanvas canvas(*bitmap); // TODO (msarett): Maybe we can take advantage of the fact that SkRect uses floats? - SkRect src = SkRect::MakeXYWH((SkScalar) imageSubsetX, (SkScalar) 0, - (SkScalar) imageSubsetWidth, (SkScalar) imageSubsetHeight); + SkRect src = SkRect::MakeXYWH((SkScalar) subset.x(), (SkScalar) 0, + (SkScalar) subset.width(), (SkScalar) subset.height()); SkRect dst = SkRect::MakeXYWH((SkScalar) (outX / sampleSize), (SkScalar) (outY / sampleSize), - (SkScalar) get_scaled_dimension(imageSubsetWidth, sampleSize), - (SkScalar) get_scaled_dimension(imageSubsetHeight, sampleSize)); + (SkScalar) get_scaled_dimension(subset.width(), sampleSize), + (SkScalar) get_scaled_dimension(subset.height(), sampleSize)); SkPaint paint; // Overwrite the dst with the src pixels paint.setXfermodeMode(SkXfermode::kSrc_Mode); diff --git a/tools/SkBitmapRegionCodec.cpp b/tools/SkBitmapRegionCodec.cpp new file mode 100644 index 0000000000..3f2cd24b78 --- /dev/null +++ b/tools/SkBitmapRegionCodec.cpp @@ -0,0 +1,143 @@ +/* + * Copyright 2015 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "SkBitmapRegionCodec.h" +#include "SkAndroidCodec.h" +#include "SkCodecPriv.h" +#include "SkCodecTools.h" + +SkBitmapRegionCodec::SkBitmapRegionCodec(SkAndroidCodec* codec) + : INHERITED(codec->getInfo().width(), codec->getInfo().height()) + , fCodec(codec) +{} + +/* + * Three differences from the Android version: + * Returns a skia bitmap instead of an Android bitmap. + * Android version attempts to reuse a recycled bitmap. + * Removed the options object and used parameters for color type and sample size. + */ +// FIXME: Should this function should take in SkIRect? +SkBitmap* SkBitmapRegionCodec::decodeRegion(int inputX, int inputY, int inputWidth, int inputHeight, + int sampleSize, SkColorType dstColorType) { + + // Fix the input sampleSize if necessary. + if (sampleSize < 1) { + sampleSize = 1; + } + + // The size of the output bitmap is determined by the size of the + // requested subset, not by the size of the intersection of the subset + // and the image dimensions. + // If inputX is negative, we will need to place decoded pixels into the + // output bitmap starting at a left offset. Call this outX. + // If outX is non-zero, subsetX must be zero. + // If inputY is negative, we will need to place decoded pixels into the + // output bitmap starting at a top offset. Call this outY. + // If outY is non-zero, subsetY must be zero. + int outX; + int outY; + SkIRect subset = SkIRect::MakeXYWH(inputX, inputY, inputWidth, inputHeight); + SubsetType type = adjust_subset_rect(fCodec->getInfo().dimensions(), &subset, &outX, &outY); + if (SubsetType::kOutside_SubsetType == type) { + return nullptr; + } + + // Ask the codec for a scaled subset + if (!fCodec->getSupportedSubset(&subset)) { + SkCodecPrintf("Error: Could not get subset.\n"); + return nullptr; + } + SkISize scaledSize = fCodec->getSampledSubsetDimensions(sampleSize, subset); + + // Create the image info for the decode + SkAlphaType dstAlphaType = fCodec->getInfo().alphaType(); + if (kUnpremul_SkAlphaType == dstAlphaType) { + dstAlphaType = kPremul_SkAlphaType; + } + SkImageInfo decodeInfo = SkImageInfo::Make(scaledSize.width(), scaledSize.height(), + dstColorType, dstAlphaType); + + // Construct a color table for the decode if necessary + SkAutoTUnref<SkColorTable> colorTable(nullptr); + SkPMColor* colorPtr = nullptr; + int* colorCountPtr = nullptr; + int maxColors = 256; + SkPMColor colors[256]; + if (kIndex_8_SkColorType == dstColorType) { + // TODO (msarett): This performs a copy that is unnecessary since + // we have not yet initialized the color table. + // And then we need to use a const cast to get + // a pointer to the color table that we can + // modify during the decode. We could alternatively + // perform the decode before creating the bitmap and + // the color table. We still would need to copy the + // colors into the color table after the decode. + colorTable.reset(new SkColorTable(colors, maxColors)); + colorPtr = const_cast<SkPMColor*>(colorTable->readColors()); + colorCountPtr = &maxColors; + } + + // Initialize the destination bitmap + SkAutoTDelete<SkBitmap> bitmap(new SkBitmap()); + int scaledOutX = 0; + int scaledOutY = 0; + int scaledOutWidth = scaledSize.width(); + int scaledOutHeight = scaledSize.height(); + if (SubsetType::kPartiallyInside_SubsetType == type) { + scaledOutX = outX / sampleSize; + scaledOutY = outY / sampleSize; + // We need to be safe here because getSupportedSubset() may have modified the subset. + const int extraX = SkTMax(0, inputWidth - outX - subset.width()); + const int extraY = SkTMax(0, inputHeight - outY - subset.height()); + const int scaledExtraX = extraX / sampleSize; + const int scaledExtraY = extraY / sampleSize; + scaledOutWidth += scaledOutX + scaledExtraX; + scaledOutHeight += scaledOutY + scaledExtraY; + } + SkImageInfo outInfo = decodeInfo.makeWH(scaledOutWidth, scaledOutHeight); + if (!bitmap->tryAllocPixels(outInfo, nullptr, colorTable.get())) { + SkCodecPrintf("Error: Could not allocate pixels.\n"); + return nullptr; + } + + // Zero the bitmap if the region is not completely within the image. + // TODO (msarett): Can we make this faster by implementing it to only + // zero parts of the image that we won't overwrite with + // pixels? + // TODO (msarett): This could be skipped if memory is zero initialized. + // This would matter if this code is moved to Android and + // uses Android bitmaps. + if (SubsetType::kPartiallyInside_SubsetType == type) { + void* pixels = bitmap->getPixels(); + size_t bytes = outInfo.getSafeSize(bitmap->rowBytes()); + memset(pixels, 0, bytes); + } + + // Decode into the destination bitmap + SkAndroidCodec::AndroidOptions options; + options.fSampleSize = sampleSize; + options.fSubset = ⊂ + options.fColorPtr = colorPtr; + options.fColorCount = colorCountPtr; + void* dst = bitmap->getAddr(scaledOutX, scaledOutY); + size_t rowBytes = bitmap->rowBytes(); + SkCodec::Result result = fCodec->getAndroidPixels(decodeInfo, dst, rowBytes, &options); + if (SkCodec::kSuccess != result && SkCodec::kIncompleteInput != result) { + SkCodecPrintf("Error: Could not get pixels.\n"); + return nullptr; + } + + return bitmap.detach(); +} + +bool SkBitmapRegionCodec::conversionSupported(SkColorType colorType) { + // FIXME: Call virtual function when it lands. + SkImageInfo info = SkImageInfo::Make(0, 0, colorType, fCodec->getInfo().alphaType(), + fCodec->getInfo().profileType()); + return conversion_possible(info, fCodec->getInfo()); +} diff --git a/tools/SkBitmapRegionCodec.h b/tools/SkBitmapRegionCodec.h new file mode 100644 index 0000000000..14d024ea45 --- /dev/null +++ b/tools/SkBitmapRegionCodec.h @@ -0,0 +1,41 @@ +/* + * Copyright 2015 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "SkBitmap.h" +#include "SkBitmapRegionDecoderInterface.h" +#include "SkAndroidCodec.h" + +/* + * This class implements SkBitmapRegionDecoder using an SkAndroidCodec. + */ +class SkBitmapRegionCodec : public SkBitmapRegionDecoderInterface { +public: + + /* + * Takes ownership of pointer to codec + */ + SkBitmapRegionCodec(SkAndroidCodec* codec); + + /* + * Three differences from the Android version: + * Returns a Skia bitmap instead of an Android bitmap. + * Android version attempts to reuse a recycled bitmap. + * Removed the options object and used parameters for color type and + * sample size. + */ + SkBitmap* decodeRegion(int start_x, int start_y, int width, int height, + int sampleSize, SkColorType prefColorType) override; + + bool conversionSupported(SkColorType colorType) override; + +private: + + SkAutoTDelete<SkAndroidCodec> fCodec; + + typedef SkBitmapRegionDecoderInterface INHERITED; + +}; diff --git a/tools/SkBitmapRegionDecoderInterface.cpp b/tools/SkBitmapRegionDecoderInterface.cpp index 835ed9aa56..ec6327e565 100644 --- a/tools/SkBitmapRegionDecoderInterface.cpp +++ b/tools/SkBitmapRegionDecoderInterface.cpp @@ -6,24 +6,26 @@ */ #include "SkBitmapRegionCanvas.h" +#include "SkBitmapRegionCodec.h" #include "SkBitmapRegionDecoderInterface.h" #include "SkBitmapRegionSampler.h" +#include "SkAndroidCodec.h" #include "SkCodec.h" #include "SkCodecPriv.h" #include "SkImageDecoder.h" SkBitmapRegionDecoderInterface* SkBitmapRegionDecoderInterface::CreateBitmapRegionDecoder( - SkStreamRewindable* stream, Strategy strategy) { - SkAutoTDelete<SkStreamRewindable> streamDeleter(stream); + SkData* data, Strategy strategy) { switch (strategy) { case kOriginal_Strategy: { + SkAutoTDelete<SkStreamRewindable> stream(new SkMemoryStream(data)); SkImageDecoder* decoder = SkImageDecoder::Factory(stream); int width, height; if (nullptr == decoder) { SkCodecPrintf("Error: Could not create image decoder.\n"); return nullptr; } - if (!decoder->buildTileIndex(streamDeleter.detach(), &width, &height)) { + if (!decoder->buildTileIndex(stream.detach(), &width, &height)) { SkCodecPrintf("Error: Could not build tile index.\n"); delete decoder; return nullptr; @@ -31,7 +33,7 @@ SkBitmapRegionDecoderInterface* SkBitmapRegionDecoderInterface::CreateBitmapRegi return new SkBitmapRegionSampler(decoder, width, height); } case kCanvas_Strategy: { - SkAutoTDelete<SkCodec> codec(SkCodec::NewFromStream(streamDeleter.detach())); + SkAutoTDelete<SkCodec> codec(SkCodec::NewFromData(data)); if (nullptr == codec) { SkCodecPrintf("Error: Failed to create decoder.\n"); return nullptr; @@ -46,6 +48,14 @@ SkBitmapRegionDecoderInterface* SkBitmapRegionDecoderInterface::CreateBitmapRegi } return new SkBitmapRegionCanvas(codec.detach()); } + case kAndroidCodec_Strategy: { + SkAutoTDelete<SkAndroidCodec> codec = SkAndroidCodec::NewFromData(data); + if (NULL == codec) { + SkCodecPrintf("Error: Failed to create codec.\n"); + return NULL; + } + return new SkBitmapRegionCodec(codec.detach()); + } default: SkASSERT(false); return nullptr; diff --git a/tools/SkBitmapRegionDecoderInterface.h b/tools/SkBitmapRegionDecoderInterface.h index 047f023c80..e1f79bccb6 100644 --- a/tools/SkBitmapRegionDecoderInterface.h +++ b/tools/SkBitmapRegionDecoderInterface.h @@ -19,19 +19,18 @@ class SkBitmapRegionDecoderInterface { public: enum Strategy { - kCanvas_Strategy, // Draw to the canvas, uses SkCodec - kOriginal_Strategy, // Sampling, uses SkImageDecoder - // TODO (msarett): Add strategy for SkScaledCodec + kCanvas_Strategy, // Draw to the canvas, uses SkCodec + kOriginal_Strategy, // Sampling, uses SkImageDecoder + kAndroidCodec_Strategy, // Uses SkAndroidCodec for scaling and subsetting }; /* - * @param stream Encoded image stream, takes ownership + * @param data Refs the data while this object exists, unrefs on destruction * @param strategy Strategy used for scaling and subsetting - * @return Tries to create an SkBitmapRegionDecoder, returns NULL - * on failure + * @return Tries to create an SkBitmapRegionDecoder, returns NULL on failure */ static SkBitmapRegionDecoderInterface* CreateBitmapRegionDecoder( - SkStreamRewindable* stream, Strategy strategy); + SkData* data, Strategy strategy); /* * Decode a scaled region of the encoded image stream diff --git a/tools/SkCodecTools.h b/tools/SkCodecTools.h index 097915b5d7..285d3a21de 100644 --- a/tools/SkCodecTools.h +++ b/tools/SkCodecTools.h @@ -12,4 +12,52 @@ inline float get_scale_from_sample_size(uint32_t sampleSize) { return 1.0f / (float) sampleSize; } +enum SubsetType { + kFullyInside_SubsetType, + kPartiallyInside_SubsetType, + kOutside_SubsetType, +}; + +/* + * Corrects image subset offsets and dimensions in order to perform a valid decode. + * Also indicates if the image subset should be placed at an offset within the + * output bitmap. + * + * Values of output variables are undefined if the SubsetType is kInvalid. + * + * @param imageDims Original image dimensions. + * @param subset As input, the subset that the client requested. + * As output, the image subset that we will decode. + * @param outX The left offset of the image subset within the output bitmap. + * @param outY The top offset of the image subset within the output bitmap. + * + * @return An indication of how the subset is contained in the image. + * If the return value is kInvalid, values of output variables are undefined. + */ +inline SubsetType adjust_subset_rect(const SkISize& imageDims, SkIRect* subset, int* outX, + int* outY) { + // These must be at least zero, we can't start decoding the image at a negative coordinate. + int left = SkTMax(0, subset->fLeft); + int top = SkTMax(0, subset->fTop); + + // If input offsets are less than zero, we decode to an offset location in the output bitmap. + *outX = left - subset->fLeft; + *outY = top - subset->fTop; + + // Make sure we don't decode pixels past the edge of the image or past the edge of the subset. + int width = SkTMin(imageDims.width() - left, subset->width() - *outX); + int height = SkTMin(imageDims.height() - top, subset->height() - *outY); + if (width <= 0 || height <= 0) { + return SubsetType::kOutside_SubsetType; + } + + subset->setXYWH(left, top, width, height); + if ((*outX != 0) || (*outY != 0) || (width != subset->width()) || + (height != subset->height())) { + return SubsetType::kPartiallyInside_SubsetType; + } + + return SubsetType::kFullyInside_SubsetType; +} + #endif // SkCodecTools_DEFINED |