美文网首页Android技术
Bitmap 创建流程追踪

Bitmap 创建流程追踪

作者: Zeller | 来源:发表于2018-08-29 11:17 被阅读115次

    BitmapFactory.java

        public static Bitmap decodeStream(InputStream is, Rect outPadding, Options opts) {
            // we don't throw in this case, thus allowing the caller to only check
            // the cache, and not force the image to be decoded.
            if (is == null) {
                return null;
            }
    
            Bitmap bm = null;
    
            Trace.traceBegin(Trace.TRACE_TAG_GRAPHICS, "decodeBitmap");
            try {
                if (is instanceof AssetManager.AssetInputStream) {
                    final long asset = ((AssetManager.AssetInputStream) is).getNativeAsset();
                    bm = nativeDecodeAsset(asset, outPadding, opts);
                } else {
                    bm = decodeStreamInternal(is, outPadding, opts);
                }
    
                if (bm == null && opts != null && opts.inBitmap != null) {
                    throw new IllegalArgumentException("Problem decoding into existing bitmap");
                }
    
                setDensityFromOptions(bm, opts);
            } finally {
                Trace.traceEnd(Trace.TRACE_TAG_GRAPHICS);
            }
    
            return bm;
        }
    
    

    jni

    \frameworks\base\core\jni\android\graphics\BitmapFactory.cpp
    
    static jobject nativeDecodeStream(JNIEnv* env, jobject clazz, jobject is, jbyteArray storage,
            jobject padding, jobject options) {
    
        jobject bitmap = NULL;
        std::unique_ptr<SkStream> stream(CreateJavaInputStreamAdaptor(env, is, storage));
    
        if (stream.get()) {
            std::unique_ptr<SkStreamRewindable> bufferedStream(
                    SkFrontBufferedStream::Create(stream.release(), SkCodec::MinBufferedBytesNeeded()));
            SkASSERT(bufferedStream.get() != NULL);
            bitmap = doDecode(env, bufferedStream.release(), padding, options);
        }
        return bitmap;
    }
    
    static jobject doDecode(JNIEnv* env, SkStreamRewindable* stream, jobject padding, jobject options) {
        // This function takes ownership of the input stream.  Since the SkAndroidCodec
        // will take ownership of the stream, we don't necessarily need to take ownership
        // here.  This is a precaution - if we were to return before creating the codec,
        // we need to make sure that we delete the stream.
        std::unique_ptr<SkStreamRewindable> streamDeleter(stream);
    
        // Set default values for the options parameters.
        int sampleSize = 1;
        bool onlyDecodeSize = false;
        SkColorType prefColorType = kN32_SkColorType;
        bool isMutable = false;
        float scale = 1.0f;
        bool requireUnpremultiplied = false;
        jobject javaBitmap = NULL;
    
        // Update with options supplied by the client.
        if (options != NULL) {
            sampleSize = env->GetIntField(options, gOptions_sampleSizeFieldID);
            // Correct a non-positive sampleSize.  sampleSize defaults to zero within the
            // options object, which is strange.
            if (sampleSize <= 0) {
                sampleSize = 1;
            }
    
            if (env->GetBooleanField(options, gOptions_justBoundsFieldID)) {
                onlyDecodeSize = true;
            }
    
            // initialize these, in case we fail later on
            env->SetIntField(options, gOptions_widthFieldID, -1);
            env->SetIntField(options, gOptions_heightFieldID, -1);
            env->SetObjectField(options, gOptions_mimeFieldID, 0);
    
            jobject jconfig = env->GetObjectField(options, gOptions_configFieldID);
            prefColorType = GraphicsJNI::getNativeBitmapColorType(env, jconfig);
            isMutable = env->GetBooleanField(options, gOptions_mutableFieldID);
            requireUnpremultiplied = !env->GetBooleanField(options, gOptions_premultipliedFieldID);
            javaBitmap = env->GetObjectField(options, gOptions_bitmapFieldID);
    
            if (env->GetBooleanField(options, gOptions_scaledFieldID)) {
                const int density = env->GetIntField(options, gOptions_densityFieldID);
                const int targetDensity = env->GetIntField(options, gOptions_targetDensityFieldID);
                const int screenDensity = env->GetIntField(options, gOptions_screenDensityFieldID);
                if (density != 0 && targetDensity != 0 && density != screenDensity) {
                    scale = (float) targetDensity / density;
                }
            }
        }
        
        
        //#include "SkAndroidCodec.h" 解码器
        //\external\skia\src\codec\SkAndroidCodec.cpp
        
        // Create the codec.
        NinePatchPeeker peeker;
        std::unique_ptr<SkAndroidCodec> codec(SkAndroidCodec::NewFromStream(streamDeleter.release(),
                &peeker));
        if (!codec.get()) {
            return nullObjectReturn("SkAndroidCodec::NewFromStream returned null");
        }
    
        // Do not allow ninepatch decodes to 565.  In the past, decodes to 565
        // would dither, and we do not want to pre-dither ninepatches, since we
        // know that they will be stretched.  We no longer dither 565 decodes,
        // but we continue to prevent ninepatches from decoding to 565, in order
        // to maintain the old behavior.
        if (peeker.mPatch && kRGB_565_SkColorType == prefColorType) {
            prefColorType = kN32_SkColorType;
        }
    
        // Determine the output size.
        SkISize size = codec->getSampledDimensions(sampleSize);
    
        int scaledWidth = size.width();
        int scaledHeight = size.height();
        bool willScale = false;
    
        // Apply a fine scaling step if necessary.
        if (needsFineScale(codec->getInfo().dimensions(), size, sampleSize)) {
            willScale = true;
            scaledWidth = codec->getInfo().width() / sampleSize;
            scaledHeight = codec->getInfo().height() / sampleSize;
        }
    
        // Set the options and return if the client only wants the size.
        if (options != NULL) {
            jstring mimeType = encodedFormatToString(env, codec->getEncodedFormat());
            if (env->ExceptionCheck()) {
                return nullObjectReturn("OOM in encodedFormatToString()");
            }
            env->SetIntField(options, gOptions_widthFieldID, scaledWidth);
            env->SetIntField(options, gOptions_heightFieldID, scaledHeight);
            env->SetObjectField(options, gOptions_mimeFieldID, mimeType);
    
            if (onlyDecodeSize) {
                return nullptr;
            }
        }
    
        // 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);
        }
    
        android::Bitmap* reuseBitmap = nullptr;
        unsigned int existingBufferSize = 0;
        if (javaBitmap != NULL) {
            reuseBitmap = GraphicsJNI::getBitmap(env, javaBitmap);
            if (reuseBitmap->peekAtPixelRef()->isImmutable()) {
                ALOGW("Unable to reuse an immutable bitmap as an image decoder target.");
                javaBitmap = NULL;
                reuseBitmap = nullptr;
            } else {
                existingBufferSize = GraphicsJNI::getBitmapAllocationByteCount(env, javaBitmap);
            }
        }
    
        JavaPixelAllocator javaAllocator(env);
        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;
        } else if (javaBitmap != nullptr) {
            decodeAllocator = &recyclingAllocator;
        } else if (willScale) {
            // This will allocate pixels using a HeapAllocator, since there will be an extra
            // scaling step that copies these pixels into Java memory.
            decodeAllocator = &heapAllocator;
        } else {
            decodeAllocator = &javaAllocator;
        }
    
        // Set the decode colorType.  This is necessary because we can't always support
        // the requested colorType.
        SkColorType decodeColorType = codec->computeOutputColorType(prefColorType);
    
        // Construct a color table for the decode if necessary
        SkAutoTUnref<SkColorTable> colorTable(nullptr);
        SkPMColor* colorPtr = nullptr;
        int* colorCount = nullptr;
        int maxColors = 256;
        SkPMColor colors[256];
        if (kIndex_8_SkColorType == decodeColorType) {
            colorTable.reset(new SkColorTable(colors, maxColors));
    
            // SkColorTable expects us to initialize all of the colors before creating an
            // SkColorTable.  However, we are using SkBitmap with an Allocator to allocate
            // memory for the decode, so we need to create the SkColorTable before decoding.
            // It is safe for SkAndroidCodec to modify the colors because this SkBitmap is
            // not being used elsewhere.
            colorPtr = const_cast<SkPMColor*>(colorTable->readColors());
            colorCount = &maxColors;
        }
    
        // Set the alpha type for the decode.
        SkAlphaType alphaType = codec->computeOutputAlphaType(requireUnpremultiplied);
    
        const SkImageInfo decodeInfo = SkImageInfo::Make(size.width(), size.height(), decodeColorType,
                alphaType);
        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 = SkImageInfo::MakeA8(size.width(), size.height());
        }
        SkBitmap decodingBitmap;
        if (!decodingBitmap.setInfo(bitmapInfo) ||
                !decodingBitmap.tryAllocPixels(decodeAllocator, colorTable)) {
            // 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.
            return nullptr;
        }
    
        // Use SkAndroidCodec to perform the decode.
        SkAndroidCodec::AndroidOptions codecOptions;
        codecOptions.fZeroInitialized = (decodeAllocator == &javaAllocator) ?
                SkCodec::kYes_ZeroInitialized : SkCodec::kNo_ZeroInitialized;
        codecOptions.fColorPtr = colorPtr;
        codecOptions.fColorCount = colorCount;
        codecOptions.fSampleSize = sampleSize;
        SkCodec::Result result = codec->getAndroidPixels(decodeInfo, decodingBitmap.getPixels(),
                decodingBitmap.rowBytes(), &codecOptions);
        switch (result) {
            case SkCodec::kSuccess:
            case SkCodec::kIncompleteInput:
                break;
            default:
                return nullObjectReturn("codec->getAndroidPixels() failed.");
        }
    
        jbyteArray ninePatchChunk = NULL;
        if (peeker.mPatch != NULL) {
            if (willScale) {
                scaleNinePatchChunk(peeker.mPatch, scale, scaledWidth, scaledHeight);
            }
    
            size_t ninePatchArraySize = peeker.mPatch->serializedSize();
            ninePatchChunk = env->NewByteArray(ninePatchArraySize);
            if (ninePatchChunk == NULL) {
                return nullObjectReturn("ninePatchChunk == null");
            }
    
            jbyte* array = (jbyte*) env->GetPrimitiveArrayCritical(ninePatchChunk, NULL);
            if (array == NULL) {
                return nullObjectReturn("primitive array == null");
            }
    
            memcpy(array, peeker.mPatch, peeker.mPatchSize);
            env->ReleasePrimitiveArrayCritical(ninePatchChunk, array, 0);
        }
    
        jobject ninePatchInsets = NULL;
        if (peeker.mHasInsets) {
            ninePatchInsets = env->NewObject(gInsetStruct_class, gInsetStruct_constructorMethodID,
                    peeker.mOpticalInsets[0], peeker.mOpticalInsets[1], peeker.mOpticalInsets[2], peeker.mOpticalInsets[3],
                    peeker.mOutlineInsets[0], peeker.mOutlineInsets[1], peeker.mOutlineInsets[2], peeker.mOutlineInsets[3],
                    peeker.mOutlineRadius, peeker.mOutlineAlpha, scale);
            if (ninePatchInsets == NULL) {
                return nullObjectReturn("nine patch insets == null");
            }
            if (javaBitmap != NULL) {
                env->SetObjectField(javaBitmap, gBitmap_ninePatchInsetsFieldID, ninePatchInsets);
            }
        }
    
        SkBitmap outputBitmap;
        if (willScale) {
            // 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
            const float sx = scaledWidth / float(decodingBitmap.width());
            const float sy = scaledHeight / float(decodingBitmap.height());
    
            // Set the allocator for the outputBitmap.
            SkBitmap::Allocator* outputAllocator;
            if (javaBitmap != nullptr) {
                outputAllocator = &recyclingAllocator;
            } else {
                outputAllocator = &javaAllocator;
            }
    
            SkColorType scaledColorType = colorTypeForScaledOutput(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(SkImageInfo::Make(scaledWidth, scaledHeight,
                    scaledColorType, decodingBitmap.alphaType()));
            if (!outputBitmap.tryAllocPixels(outputAllocator, NULL)) {
                // This should only fail on OOM.  The recyclingAllocator should have
                // enough memory since we check this before decoding using the
                // scaleCheckingAllocator.
                return nullObjectReturn("allocation failed for scaled bitmap");
            }
    
            SkPaint paint;
            // kSrc_Mode instructs us to overwrite the unininitialized pixels in
            // outputBitmap.  Otherwise we would blend by default, which is not
            // what we want.
            paint.setXfermodeMode(SkXfermode::kSrc_Mode);
            paint.setFilterQuality(kLow_SkFilterQuality);
    
            SkCanvas canvas(outputBitmap);
            canvas.scale(sx, sy);
            canvas.drawBitmap(decodingBitmap, 0.0f, 0.0f, &paint);
        } else {
            outputBitmap.swap(decodingBitmap);
        }
    
        if (padding) {
            if (peeker.mPatch != NULL) {
                GraphicsJNI::set_jrect(env, padding,
                        peeker.mPatch->paddingLeft, peeker.mPatch->paddingTop,
                        peeker.mPatch->paddingRight, peeker.mPatch->paddingBottom);
            } else {
                GraphicsJNI::set_jrect(env, padding, -1, -1, -1, -1);
            }
        }
    
        // If we get here, the outputBitmap should have an installed pixelref.
        if (outputBitmap.pixelRef() == NULL) {
            return nullObjectReturn("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) {
            GraphicsJNI::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 |= GraphicsJNI::kBitmapCreateFlag_Mutable;
        if (isPremultiplied) bitmapCreateFlags |= GraphicsJNI::kBitmapCreateFlag_Premultiplied;
    
        // now create the java bitmap
        return GraphicsJNI::createBitmap(env, javaAllocator.getStorageObjAndReset(),
                bitmapCreateFlags, ninePatchChunk, ninePatchInsets, -1);
    }
    
    jobject GraphicsJNI::createBitmap(JNIEnv* env, android::Bitmap* bitmap,
            int bitmapCreateFlags, jbyteArray ninePatchChunk, jobject ninePatchInsets,
            int density) {
        bool isMutable = bitmapCreateFlags & kBitmapCreateFlag_Mutable;
        bool isPremultiplied = bitmapCreateFlags & kBitmapCreateFlag_Premultiplied;
        // The caller needs to have already set the alpha type properly, so the
        // native SkBitmap stays in sync with the Java Bitmap.
        assert_premultiplied(bitmap->info(), isPremultiplied);
    
        jobject obj = env->NewObject(gBitmap_class, gBitmap_constructorMethodID,
                reinterpret_cast<jlong>(bitmap), bitmap->javaByteArray(),
                bitmap->width(), bitmap->height(), density, isMutable, isPremultiplied,
                ninePatchChunk, ninePatchInsets);
        hasException(env); // For the side effect of logging.
        return obj;
    }
    

    android\graphics\Bitmap.java

     /**
         * Private constructor that must received an already allocated native bitmap
         * int (pointer).
         */
        // called from JNI
        Bitmap(long nativeBitmap, byte[] buffer, int width, int height, int density,
                boolean isMutable, boolean requestPremultiplied,
                byte[] ninePatchChunk, NinePatch.InsetStruct ninePatchInsets) {
            if (nativeBitmap == 0) {
                throw new RuntimeException("internal error: native bitmap is 0");
            }
    
            mWidth = width;
            mHeight = height;
            mIsMutable = isMutable;
            mRequestPremultiplied = requestPremultiplied;
            mBuffer = buffer;
    
            mNinePatchChunk = ninePatchChunk;
            mNinePatchInsets = ninePatchInsets;
            if (density >= 0) {
                mDensity = density;
            }
    
            mNativePtr = nativeBitmap;
            long nativeSize = NATIVE_ALLOCATION_SIZE;
            if (buffer == null) {
                nativeSize += getByteCount();
            }
            NativeAllocationRegistry registry = new NativeAllocationRegistry(
                Bitmap.class.getClassLoader(), nativeGetNativeFinalizer(), nativeSize);
            registry.registerNativeAllocation(this, nativeBitmap);
        }
    

    参考文章

    相关文章

      网友评论

        本文标题:Bitmap 创建流程追踪

        本文链接:https://www.haomeiwen.com/subject/mmyhwftx.html