std::unique_ptr<SkStream> stream(GetResourceAsStream("images/plane.png")); if (!stream) { return; }
std::unique_ptr<SkCodec> codec(SkCodec::MakeFromStream(std::move(stream))); if (!codec) { ERRORF(r, "failed to create a codec for %s", path); return; }
SkBitmap bm; bm.allocPixels(codec->getInfo()); SkCodec::Result result = codec->getPixels(codec->getInfo(), bm.getPixels(), bm.rowBytes());
// Set the options and return if the client only wants the size. if (options != NULL) { // 。。。 }
// Scale is necessary due to density differences. if (scale != 1.0f) { willScale = true; scaledWidth = static_cast<int>(scaledWidth * scale + 0.5f); scaledHeight = static_cast<int>(scaledHeight * scale + 0.5f); }
// reuse bitmap android::Bitmap* reuseBitmap = nullptr; unsignedint existingBufferSize = 0; if (javaBitmap != nullptr) { reuseBitmap = &bitmap::toBitmap(inBitmapHandle); if (reuseBitmap->isImmutable()) { ALOGW("Unable to reuse an immutable bitmap as an image decoder target."); javaBitmap = nullptr; reuseBitmap = nullptr; } else { existingBufferSize = reuseBitmap->getAllocationByteCount(); } }
// 内存分配器 HeapAllocator defaultAllocator; RecyclingPixelAllocator recyclingAllocator(reuseBitmap, existingBufferSize); ScaleCheckingAllocator scaleCheckingAllocator(scale, existingBufferSize); SkBitmap::HeapAllocator heapAllocator; SkBitmap::Allocator* decodeAllocator; if (javaBitmap != nullptr && willScale) { // This will allocate pixels using a HeapAllocator, since there will be an extra // scaling step that copies these pixels into Java memory. This allocator // also checks that the recycled javaBitmap is large enough. decodeAllocator = &scaleCheckingAllocator; } elseif (javaBitmap != nullptr) { decodeAllocator = &recyclingAllocator; } elseif (willScale || isHardware) { // This will allocate pixels using a HeapAllocator, // for scale case: there will be an extra scaling step. // for hardware case: there will be extra swizzling & upload to gralloc step. decodeAllocator = &heapAllocator; } else { decodeAllocator = &defaultAllocator; }
SkImageInfo bitmapInfo = decodeInfo; if (decodeColorType == kGray_8_SkColorType) { // The legacy implementation of BitmapFactory used kAlpha8 for // grayscale images (before kGray8 existed). While the codec // recognizes kGray8, we need to decode into a kAlpha8 bitmap // in order to avoid a behavior change. bitmapInfo = bitmapInfo.makeColorType(kAlpha_8_SkColorType).makeAlphaType(kPremul_SkAlphaType); } SkBitmap decodingBitmap; if (!decodingBitmap.setInfo(bitmapInfo) || !decodingBitmap.tryAllocPixels(decodeAllocator)) { // SkAndroidCodec should recommend a valid SkImageInfo, so setInfo() // should only only fail if the calculated value for rowBytes is too // large. // tryAllocPixels() can fail due to OOM on the Java heap, OOM on the // native heap, or the recycled javaBitmap being too small to reuse. returnnullptr; }
// Use SkAndroidCodec to perform the decode. SkAndroidCodec::AndroidOptions codecOptions; codecOptions.fZeroInitialized = decodeAllocator == &defaultAllocator ? SkCodec::kYes_ZeroInitialized : SkCodec::kNo_ZeroInitialized; codecOptions.fSampleSize = sampleSize; SkCodec::Result result = codec->getAndroidPixels(decodeInfo, decodingBitmap.getPixels(), decodingBitmap.rowBytes(), &codecOptions); switch (result) { case SkCodec::kSuccess: case SkCodec::kIncompleteInput: break; default: returnnullObjectReturn("codec->getAndroidPixels() failed."); }
// This is weird so let me explain: we could use the scale parameter // directly, but for historical reasons this is how the corresponding // Dalvik code has always behaved. We simply recreate the behavior here. // The result is slightly different from simply using scale because of // the 0.5f rounding bias applied when computing the target image size constfloat scaleX = scaledWidth / float(decodingBitmap.width()); constfloat scaleY = scaledHeight / float(decodingBitmap.height());
// ninepatch 处理 // ...
// 后处理,缩放 SkBitmap outputBitmap; if (willScale) { // Set the allocator for the outputBitmap. SkBitmap::Allocator* outputAllocator; if (javaBitmap != nullptr) { outputAllocator = &recyclingAllocator; } else { outputAllocator = &defaultAllocator; }
SkColorType scaledColorType = decodingBitmap.colorType(); // FIXME: If the alphaType is kUnpremul and the image has alpha, the // colors may not be correct, since Skia does not yet support drawing // to/from unpremultiplied bitmaps. outputBitmap.setInfo( bitmapInfo.makeWH(scaledWidth, scaledHeight).makeColorType(scaledColorType)); if (!outputBitmap.tryAllocPixels(outputAllocator)) { // This should only fail on OOM. The recyclingAllocator should have // enough memory since we check this before decoding using the // scaleCheckingAllocator. returnnullObjectReturn("allocation failed for scaled bitmap"); }
SkPaint paint; // kSrc_Mode instructs us to overwrite the uninitialized pixels in // outputBitmap. Otherwise we would blend by default, which is not // what we want. paint.setBlendMode(SkBlendMode::kSrc);
SkCanvas canvas(outputBitmap, SkCanvas::ColorBehavior::kLegacy); canvas.scale(scaleX, scaleY); decodingBitmap.setImmutable(); // so .asImage() doesn't make a copy canvas.drawImage(decodingBitmap.asImage(), 0.0f, 0.0f, SkSamplingOptions(SkFilterMode::kLinear), &paint); } else { outputBitmap.swap(decodingBitmap); }
if (padding) { peeker.getPadding(env, padding); }
// If we get here, the outputBitmap should have an installed pixelref. if (outputBitmap.pixelRef() == NULL) { returnnullObjectReturn("Got null SkPixelRef"); }
if (!isMutable && javaBitmap == NULL) { // promise we will never change our pixels (great for sharing and pictures) outputBitmap.setImmutable(); }
bool isPremultiplied = !requireUnpremultiplied; if (javaBitmap != nullptr) { bitmap::reinitBitmap(env, javaBitmap, outputBitmap.info(), isPremultiplied); outputBitmap.notifyPixelsChanged(); // If a java bitmap was passed in for reuse, pass it back return javaBitmap; }
int bitmapCreateFlags = 0x0; if (isMutable) bitmapCreateFlags |= android::bitmap::kBitmapCreateFlag_Mutable; if (isPremultiplied) bitmapCreateFlags |= android::bitmap::kBitmapCreateFlag_Premultiplied;
if (isHardware) { sk_sp<Bitmap> hardwareBitmap = Bitmap::allocateHardwareBitmap(outputBitmap); if (!hardwareBitmap.get()) { returnnullObjectReturn("Failed to allocate a hardware bitmap"); } return bitmap::createBitmap(env, hardwareBitmap.release(), bitmapCreateFlags, ninePatchChunk, ninePatchInsets, -1); }
// now create the java bitmap return bitmap::createBitmap(env, defaultAllocator.getStorageObjAndReset(), bitmapCreateFlags, ninePatchChunk, ninePatchInsets, -1); }
/** * Compute the appropriate sample size to get to |size|. * * @param size As an input parameter, the desired output size of * the decode. As an output parameter, the smallest sampled size * larger than the input. * @return the sample size to set AndroidOptions::fSampleSize to decode * to the output |size|. */ intcomputeSampleSize(SkISize* size)const;
/** * Returns the dimensions of the scaled output image, for an input * sampleSize. * * When the sample size divides evenly into the original dimensions, the * scaled output dimensions will simply be equal to the original * dimensions divided by the sample size. * * When the sample size does not divide even into the original * dimensions, the codec may round up or down, depending on what is most * efficient to decode. * * Finally, the codec will always recommend a non-zero output, so the output * dimension will always be one if the sampleSize is greater than the * original dimension. */ SkISize getSampledDimensions(int sampleSize)const;
/** * Return (via desiredSubset) a subset which can decoded from this codec, * or false if the input subset is invalid. * * @param desiredSubset in/out parameter * As input, a desired subset of the original bounds * (as specified by getInfo). * As output, if true is returned, desiredSubset may * have been modified to a subset which is * supported. Although a particular change may have * been made to desiredSubset to create something * supported, it is possible other changes could * result in a valid subset. If false is returned, * desiredSubset's value is undefined. * @return true If the input desiredSubset is valid. * desiredSubset may be modified to a subset * supported by the codec. * false If desiredSubset is invalid (NULL or not fully * contained within the image). */ boolgetSupportedSubset(SkIRect* desiredSubset)const; // TODO: Rename SkCodec::getValidSubset() to getSupportedSubset()
/** * Returns the dimensions of the scaled, partial output image, for an * input sampleSize and subset. * * @param sampleSize Factor to scale down by. * @param subset Must be a valid subset of the original image * dimensions and a subset supported by SkAndroidCodec. * getSubset() can be used to obtain a subset supported * by SkAndroidCodec. * @return Size of the scaled partial image. Or zero size * if either of the inputs is invalid. */ SkISize getSampledSubsetDimensions(int sampleSize, const SkIRect& subset)const;
private: /** * Find the best way to account for native scaling. * * Return a size that fCodec can scale to, and adjust sampleSize to finish scaling. * * @param sampleSize As an input, the requested sample size. * As an output, sampling needed after letting fCodec * scale to the returned dimensions. * @param nativeSampleSize Optional output parameter. Will be set to the * effective sample size done by fCodec. * @return SkISize The size that fCodec should scale to. */ SkISize accountForNativeScaling(int* sampleSize, int* nativeSampleSize = nullptr)const;
/** * This fulfills the same contract as onGetAndroidPixels(). * * We call this function from onGetAndroidPixels() if we have determined * that fCodec does not support the requested scale, and we need to * provide the scale by sampling. */ SkCodec::Result sampledDecode(const SkImageInfo& info, void* pixels, size_t rowBytes, const AndroidOptions& options);
switch ((SkEncodedImageFormat)codec->getEncodedFormat()) { case SkEncodedImageFormat::kPNG: case SkEncodedImageFormat::kICO: case SkEncodedImageFormat::kJPEG: #ifndef SK_HAS_WUFFS_LIBRARY case SkEncodedImageFormat::kGIF: #endif case SkEncodedImageFormat::kBMP: case SkEncodedImageFormat::kWBMP: case SkEncodedImageFormat::kHEIF: case SkEncodedImageFormat::kAVIF: return std::make_unique<SkSampledCodec>(codec.release()); #ifdef SK_HAS_WUFFS_LIBRARY case SkEncodedImageFormat::kGIF: #endif #ifdef SK_CODEC_DECODES_WEBP case SkEncodedImageFormat::kWEBP: #endif #ifdef SK_CODEC_DECODES_RAW case SkEncodedImageFormat::kDNG: #endif #if defined(SK_CODEC_DECODES_WEBP) || defined(SK_CODEC_DECODES_RAW) || defined(SK_HAS_WUFFS_LIBRARY) return std::make_unique<SkAndroidCodecAdapter>(codec.release()); #endif }
public: static std::unique_ptr<SkCodec> MakeFromStream( std::unique_ptr<SkStream>, Result* = nullptr, SkPngChunkReader* = nullptr, SelectionPolicy selectionPolicy = SelectionPolicy::kPreferStillImage); /** * Return a reasonable SkImageInfo to decode into. * * If the image has an ICC profile that does not map to an SkColorSpace, * the returned SkImageInfo will use SRGB. */ SkImageInfo getInfo()const{ return fEncodedInfo.makeImageInfo(); }
/** * Return the ICC profile of the encoded data. */ const skcms_ICCProfile* getICCProfile()const /** * Return a size that approximately supports the desired scale factor. * The codec may not be able to scale efficiently to the exact scale * factor requested, so return a size that approximates that scale. * The returned value is the codec's suggestion for the closest valid * scale that it can natively support */ SkISize getScaledDimensions(float desiredScale)const /** * Return (via desiredSubset) a subset which can decoded from this codec, * or false if this codec cannot decode subsets or anything similar to * desiredSubset. * * @param desiredSubset In/out parameter. As input, a desired subset of * the original bounds (as specified by getInfo). If true is returned, * desiredSubset may have been modified to a subset which is * supported. Although a particular change may have been made to * desiredSubset to create something supported, it is possible other * changes could result in a valid subset. * If false is returned, desiredSubset's value is undefined. * @return true if this codec supports decoding desiredSubset (as * returned, potentially modified) */ boolgetValidSubset(SkIRect* desiredSubset)const /** * Format of the encoded data. */ SkEncodedImageFormat getEncodedFormat()const{ returnthis->onGetEncodedFormat(); }
/** * Decode into the given pixels, a block of memory of size at * least (info.fHeight - 1) * rowBytes + (info.fWidth * * bytesPerPixel) * * Repeated calls to this function should give the same results, * allowing the PixelRef to be immutable. * * @param info A description of the format (config, size) * expected by the caller. This can simply be identical * to the info returned by getInfo(). * * This contract also allows the caller to specify * different output-configs, which the implementation can * decide to support or not. * * A size that does not match getInfo() implies a request * to scale. If the generator cannot perform this scale, * it will return kInvalidScale. * * If the info contains a non-null SkColorSpace, the codec * will perform the appropriate color space transformation. * * If the caller passes in the SkColorSpace that maps to the * ICC profile reported by getICCProfile(), the color space * transformation is a no-op. * * If the caller passes a null SkColorSpace, no color space * transformation will be done. * * If a scanline decode is in progress, scanline mode will end, * requiring the client to call * startScanlineDecode() in order to return to decoding scanlines. * * @return Result kSuccess, or another value explaining the type of failure. */ Result getPixels(const SkImageInfo& info, void* pixels, size_t rowBytes, const Options*);
/** * Simplified version of getPixels() that uses the default Options. */ Result getPixels(const SkImageInfo& info, void* pixels, size_t rowBytes){ returnthis->getPixels(info, pixels, rowBytes, nullptr); }
// Register a decoder at runtime by passing two function pointers: // - peek() to return true if the span of bytes appears to be your encoded format; // - make() to attempt to create an SkCodec from the given stream. // Not thread safe. staticvoidRegister( bool (*peek)(constvoid*, size_t), std::unique_ptr<SkCodec> (*make)(std::unique_ptr<SkStream>, SkCodec::Result*));
// PNG is special, since we want to be able to supply an SkPngChunkReader. // But this code follows the same pattern as the loop. #ifdef SK_CODEC_DECODES_PNG if (SkPngCodec::IsPng(buffer, bytesRead)) { return SkPngCodec::MakeFromStream(std::move(stream), outResult, chunkReader); } else #endif { for (DecoderProc proc : *decoders()) { if (proc.IsFormat(buffer, bytesRead)) { return proc.MakeFromStream(std::move(stream), outResult); } }
#ifdef SK_HAS_HEIF_LIBRARY SkEncodedImageFormat format; if (SkHeifCodec::IsSupported(buffer, bytesRead, &format)) { return SkHeifCodec::MakeFromStream(std::move(stream), selectionPolicy, format, outResult); } #endif
#ifdef SK_CODEC_DECODES_RAW // Try to treat the input as RAW if all the other checks failed. return SkRawCodec::MakeFromStream(std::move(stream), outResult); #endif }
classBitmap : public SkPixelRef { public: /* The allocate factories not only construct the Bitmap object but also allocate the * backing store whose type is determined by the specific method that is called. * * The factories that accept SkBitmap* as a param will modify those params by * installing the returned bitmap as their SkPixelRef. * * The factories that accept const SkBitmap& as a param will copy the contents of the * provided bitmap into the newly allocated buffer. */ static sk_sp<Bitmap> allocateAshmemBitmap(SkBitmap* bitmap); static sk_sp<Bitmap> allocateHardwareBitmap(const SkBitmap& bitmap); static sk_sp<Bitmap> allocateHeapBitmap(SkBitmap* bitmap); static sk_sp<Bitmap> allocateHeapBitmap(const SkImageInfo& info); static sk_sp<Bitmap> allocateHeapBitmap(size_t size, const SkImageInfo& i, size_t rowBytes);
/* The createFrom factories construct a new Bitmap object by wrapping the already allocated * memory that is provided as an input param. */ #ifdef __ANDROID__ // Layoutlib does not support hardware acceleration static sk_sp<Bitmap> createFrom(AHardwareBuffer* hardwareBuffer, sk_sp<SkColorSpace> colorSpace, BitmapPalette palette = BitmapPalette::Unknown);
// returns true if rowBytes * height can be represented by a positive int32_t value // and places that value in size. staticboolcomputeAllocationSize(size_t rowBytes, int height, size_t* size);
// These must match the int values of CompressFormat in Bitmap.java, as well as // AndroidBitmapCompressFormat. enum classJavaCompressFormat { Jpeg = 0, Png = 1, Webp = 2, WebpLossy = 3, WebpLossless = 4, };
boolcompress(JavaCompressFormat format, int32_t quality, SkWStream* stream);
staticboolcompress(const SkBitmap& bitmap, JavaCompressFormat format, int32_t quality, SkWStream* stream); private: static sk_sp<Bitmap> allocateAshmemBitmap(size_t size, const SkImageInfo& i, size_t rowBytes);
// Common code for the two public facing createFrom(AHardwareBuffer*, ...) // methods. // bufferDesc is only used to compute rowBytes. static sk_sp<Bitmap> createFrom(AHardwareBuffer* hardwareBuffer, const SkImageInfo& info, const AHardwareBuffer_Desc& bufferDesc, BitmapPalette palette); #endif