Skia深入分析6——skia中图像编解码代码概述

1、API和自注册机制

Skia中编码解码图片都只需要一行代码:

SkBitmap bitmap;
SkImageDecoder::DecodeFile("test.xxx", &bitmap);//由文件名解码,自动推断图片类型
//或者由流解码
SkFILEStream stream("test.xxx");
SkImageDecoder::DecodeStream(stream, &bitmap);//由输入流解码,自动推断图片类型
//编码
SkImageEncoder::EncodeFile("test.jpg", bitmap, SkImageEncoder::kJPEG_Type, 90/*编码图片质量,对jpeg格式和webp格式有用*/);

</pre><p></p><p>其设计是用抽象工厂模式产生编码器、解码器实例,将产生函数用自注册方式录入,使之在加载库时即初始化完成。</p><p>通用注册模板见:include/core/SkTRegistry.h</p><p></p><pre code_snippet_id="1582962" snippet_file_name="blog_20160220_3_9002512" name="code" class="cpp">template <typename T> class SkTRegistry : SkNoncopyable {
public:
    typedef T Factory;

    explicit SkTRegistry(T fact) : fFact(fact) {
#ifdef SK_BUILD_FOR_ANDROID
        // work-around for double-initialization bug
        {
            SkTRegistry* reg = gHead;
            while (reg) {
                if (reg == this) {
                    return;
                }
                reg = reg->fChain;
            }
        }
#endif
        fChain = gHead;
        gHead  = this;
    }

    static const SkTRegistry* Head() { return gHead; }

    const SkTRegistry* next() const { return fChain; }
    const Factory& factory() const { return fFact; }

private:
    Factory      fFact;
    SkTRegistry* fChain;

    static SkTRegistry* gHead;
};

// The caller still needs to declare an instance of this somewhere
template <typename T> SkTRegistry<T>* SkTRegistry<T>::gHead;

SkImageDecoder中规定的解码器工厂注册函数:

typedef SkTRegistry<SkImageDecoder*(*)(SkStreamRewindable*)>        SkImageDecoder_DecodeReg;

gif的解码器工厂便按如下方法注册:

static bool is_gif(SkStreamRewindable* stream) {
    char buf[GIF_STAMP_LEN];
    if (stream->read(buf, GIF_STAMP_LEN) == GIF_STAMP_LEN) {
        if (memcmp(GIF_STAMP,   buf, GIF_STAMP_LEN) == 0 ||
                memcmp(GIF87_STAMP, buf, GIF_STAMP_LEN) == 0 ||
                memcmp(GIF89_STAMP, buf, GIF_STAMP_LEN) == 0) {
            return true;
        }
    }
    return false;
}

static SkImageDecoder* sk_libgif_dfactory(SkStreamRewindable* stream) {
    if (is_gif(stream)) {
        return SkNEW(SkGIFImageDecoder);
    }
    return NULL;
}
static SkImageDecoder_DecodeReg gReg(sk_libgif_dfactory);


2、解码的流程:

bool SkImageDecoder::DecodeStream(SkStreamRewindable* stream, SkBitmap* bm, SkColorType pref,
                                  Mode mode, Format* format) {
    SkASSERT(stream);
    SkASSERT(bm);

    bool success = false;
    SkImageDecoder* codec = SkImageDecoder::Factory(stream);

    if (NULL != codec) {
        success = codec->decode(stream, bm, pref, mode);
        if (success && format) {
            *format = codec->getFormat();
            if (kUnknown_Format == *format) {
                if (stream->rewind()) {
                    *format = GetStreamFormat(stream);
                }
            }
        }
        delete codec;
    }
    return success;
}

bool SkImageDecoder::decode(SkStream* stream, SkBitmap* bm, SkColorType pref, Mode mode) {
    // we reset this to false before calling onDecode
    fShouldCancelDecode = false;
    // assign this, for use by getPrefColorType(), in case fUsePrefTable is false
    fDefaultPref = pref;

    // pass a temporary bitmap, so that if we return false, we are assured of
    // leaving the caller's bitmap untouched.
    SkBitmap    tmp;
    if (!this->onDecode(stream, &tmp, mode)) {
        return false;
    }
    bm->swap(tmp);
    return true;
}

(1)遍历所有解码器,找到支持该文件的解码器:
遍历的代码见:

external/skia/src/images/SkImageDecoder_FactoryRegistrar.cpp

SkImageDecoder* SkImageDecoder::Factory(SkStreamRewindable* stream) {
    return image_decoder_from_stream(stream);
}

SkImageDecoder* image_decoder_from_stream(SkStreamRewindable* stream) {
    SkImageDecoder* codec = NULL;
    const SkImageDecoder_DecodeReg* curr = SkImageDecoder_DecodeReg::Head();
    while (curr) {
        codec = curr->factory()(stream);
        // we rewind here, because we promise later when we call "decode", that
        // the stream will be at its beginning.
        bool rewindSuceeded = stream->rewind();

        // our image decoder's require that rewind is supported so we fail early
        // if we are given a stream that does not support rewinding.
        if (!rewindSuceeded) {
            SkDEBUGF(("Unable to rewind the image stream."));
            SkDELETE(codec);
            return NULL;
        }

        if (codec) {
            return codec;
        }
        curr = curr->next();
    }
    return NULL;
}

(2)解码器调用相应的解码库函数(简单的图片如bmp自行处理),解出原始图片,见各个解码器的onDecode方法

(3)在onDecode方法中,一般需要用SkScaledBitmapSampler类作图片后续的缩放和透明度预乘
编码的流程相对简单,就是根据类型取相应的编码器做文件编码,不详述


3、解码中流的设置:
从framework层调Skia的解码,主要是以下几个函数:
nativeDecodeStream:以Java的InputStream类作为输入解码,创建JavaInputStreamAdaptor流(其实现是先回调Java中InputStream的读取方法,将其读到缓存区,再从缓存区中读到目标内存。)
nativeDecodeFileDescriptor:以文件描述符作为输入解码,创建SkFILEStream,直接读取文件内容
nativeDecodeAsset:以Asset作为输入解码,创建AssetStreamAdaptor(frameworks/base/core/jni/android/graphics/Utils.cpp)。
nativeDecodeByteArray:以ByteArray作为输入解码,创建SkMemoryStream,直接从内存中读。

#define BYTES_TO_BUFFER 64

static jobject nativeDecodeStream(JNIEnv* env, jobject clazz, jobject is, jbyteArray storage,
        jobject padding, jobject options) {

    jobject bitmap = NULL;
    SkAutoTUnref<SkStream> stream(CreateJavaInputStreamAdaptor(env, is, storage));

    if (stream.get()) {
        SkAutoTUnref<SkStreamRewindable> bufferedStream(
                SkFrontBufferedStream::Create(stream, BYTES_TO_BUFFER));
        SkASSERT(bufferedStream.get() != NULL);
        bitmap = doDecode(env, bufferedStream, padding, options);
    }
    return bitmap;
}

static jobject nativeDecodeFileDescriptor(JNIEnv* env, jobject clazz, jobject fileDescriptor,
        jobject padding, jobject bitmapFactoryOptions) {

    NPE_CHECK_RETURN_ZERO(env, fileDescriptor);

    int descriptor = jniGetFDFromFileDescriptor(env, fileDescriptor);

    struct stat fdStat;
    if (fstat(descriptor, &fdStat) == -1) {
        doThrowIOE(env, "broken file descriptor");
        return nullObjectReturn("fstat return -1");
    }

    // Restore the descriptor's offset on exiting this function. Even though
    // we dup the descriptor, both the original and dup refer to the same open
    // file description and changes to the file offset in one impact the other.
    AutoFDSeek autoRestore(descriptor);

    // Duplicate the descriptor here to prevent leaking memory. A leak occurs
    // if we only close the file descriptor and not the file object it is used to
    // create.  If we don't explicitly clean up the file (which in turn closes the
    // descriptor) the buffers allocated internally by fseek will be leaked.
    int dupDescriptor = dup(descriptor);

    FILE* file = fdopen(dupDescriptor, "r");
    if (file == NULL) {
        // cleanup the duplicated descriptor since it will not be closed when the
        // file is cleaned up (fclose).
        close(dupDescriptor);
        return nullObjectReturn("Could not open file");
    }

    SkAutoTUnref<SkFILEStream> fileStream(new SkFILEStream(file,
                         SkFILEStream::kCallerPasses_Ownership));

    // Use a buffered stream. Although an SkFILEStream can be rewound, this
    // ensures that SkImageDecoder::Factory never rewinds beyond the
    // current position of the file descriptor.
    SkAutoTUnref<SkStreamRewindable> stream(SkFrontBufferedStream::Create(fileStream,
            BYTES_TO_BUFFER));

    return doDecode(env, stream, padding, bitmapFactoryOptions);
}

由于以文件描述符和InputStream为输入时,不能保证该流可以rewind,因此加了一层 SkFrontBufferedStream 的包装,这个主要作用是缓存输入流中最前面的一段区域(#define BYTES_TO_BUFFER 64),以便在读这段区域时可以回溯(rewind),这个主要作用让解码器读完文件头判断类型后可以回溯。
详细见:
external/skia/src/utils/SkFrontBufferedStream.cpp


4、解码选项

external/skia/include/core/SkImageDecoder.h

class SkImageDecoder : SkNoncopyable {
private:
    Peeker*                 fPeeker;//仅在解ninepatch图片中使用,用于提取子图片断,仅对png格式有效
    SkBitmap::Allocator*    fAllocator;//解码需要产生SkBitmap,这里是其像素的内存分配器
    int                     fSampleSize;//图片下采样率,为2的幂次,这个设计主要针对的是jpeg格式的图像。Jpeg格式编码时以8*8像素单位为一个block作傅立叶变换,设成2、4、8时可以针对性简化反傅立叶变换(idct)的运算。
    SkColorType             fDefaultPref;//优先选取的解码出来的图片格式
    bool                    fDitherImage;//是否做抖动处理,主要针对16位色,在png和jpeg解码时生效
    bool                    fSkipWritingZeroes;//是否默认结果图已经清零并不写入像素值零的点。这个选项仅在 SkScaledBitmapSampler 中有判断,如(get_RGBA_to_8888_proc函数),试图起到后续缩放时一些计算节省,意义不大。(真要优化不如用simd改写,如neon)
    mutable bool            fShouldCancelDecode;//比如用户在A图片未解析完时,已经划过A图片所在区域,此时可设置fShouldCancelDecode使A图片的解码取消。
    bool                    fPreferQualityOverSpeed;//只对jpeg格式有用,决定反傅立叶变换(idct)的运算精度,速度优先时,以8位精度整数替代浮点运算,质量优先时,以16位精度整数替代浮点运算
    bool                    fRequireUnpremultipliedColors;//解带透明度的图片,如png时有用,表示是否对图片像素作透明度预乘,默认是作预乘,以便渲染时不用再做乘法
};

这些私有变量对应于BitmapFactory.Options中的相应的配置项,见
frameworks/base/graphics/java/android/graphics/BitmapFactory.java
设置的代码见doDecode方法:

frameworks/base/core/jni/android/graphics/BitmapFactory.cpp

static jobject doDecode(JNIEnv* env, SkStreamRewindable* stream, jobject padding, jobject options) {
    /*......*/
    SkImageDecoder* decoder = SkImageDecoder::Factory(stream);
    if (decoder == NULL) {
        return nullObjectReturn("SkImageDecoder::Factory returned null");
    }

    decoder->setSampleSize(sampleSize);
    decoder->setDitherImage(doDither);
    decoder->setPreferQualityOverSpeed(preferQualityOverSpeed);
    decoder->setRequireUnpremultipliedColors(requireUnpremultiplied);
    /*......*/
}

部分Java层的配置项并不反映在SkImageDecoder中,而是作为函数参数传入,如解码模式Mode


5、典型解码器的onDecode方法

这一部分的代码可做为这些图像编解码库使用方法的参考

(1)Jpeg

bool SkJPEGImageDecoder::onDecode(SkStream* stream, SkBitmap* bm, Mode mode) {
#ifdef TIME_DECODE
    SkAutoTime atm("JPEG Decode");
#endif

    JPEGAutoClean autoClean;

    jpeg_decompress_struct  cinfo;
    skjpeg_source_mgr       srcManager(stream, this);

    skjpeg_error_mgr errorManager;
    set_error_mgr(&cinfo, &errorManager);

    // All objects need to be instantiated before this setjmp call so that
    // they will be cleaned up properly if an error occurs.
    if (setjmp(errorManager.fJmpBuf)) {
        return return_false(cinfo, *bm, "setjmp");
    }

    initialize_info(&cinfo, &srcManager);
    autoClean.set(&cinfo);

    int status = jpeg_read_header(&cinfo, true);
    if (status != JPEG_HEADER_OK) {
        return return_false(cinfo, *bm, "read_header");
    }

    /*  Try to fulfill the requested sampleSize. Since jpeg can do it (when it
        can) much faster that we, just use their num/denom api to approximate
        the size.
    */
    int sampleSize = this->getSampleSize();

    set_dct_method(*this, &cinfo);

    SkASSERT(1 == cinfo.scale_num);
    cinfo.scale_denom = sampleSize;

    turn_off_visual_optimizations(&cinfo);

    const SkColorType colorType = this->getBitmapColorType(&cinfo);
    const SkAlphaType alphaType = kAlpha_8_SkColorType == colorType ?
                                      kPremul_SkAlphaType : kOpaque_SkAlphaType;

    adjust_out_color_space_and_dither(&cinfo, colorType, *this);

    if (1 == sampleSize && SkImageDecoder::kDecodeBounds_Mode == mode) {
        // Assume an A8 bitmap is not opaque to avoid the check of each
        // individual pixel. It is very unlikely to be opaque, since
        // an opaque A8 bitmap would not be very interesting.
        // Otherwise, a jpeg image is opaque.
        return bm->setInfo(SkImageInfo::Make(cinfo.image_width, cinfo.image_height,
                                             colorType, alphaType));
    }

    /*  image_width and image_height are the original dimensions, available
        after jpeg_read_header(). To see the scaled dimensions, we have to call
        jpeg_start_decompress(), and then read output_width and output_height.
    */
    if (!jpeg_start_decompress(&cinfo)) {
        /*  If we failed here, we may still have enough information to return
            to the caller if they just wanted (subsampled bounds). If sampleSize
            was 1, then we would have already returned. Thus we just check if
            we're in kDecodeBounds_Mode, and that we have valid output sizes.

            One reason to fail here is that we have insufficient stream data
            to complete the setup. However, output dimensions seem to get
            computed very early, which is why this special check can pay off.
         */
        if (SkImageDecoder::kDecodeBounds_Mode == mode && valid_output_dimensions(cinfo)) {
            SkScaledBitmapSampler smpl(cinfo.output_width, cinfo.output_height,
                                       recompute_sampleSize(sampleSize, cinfo));
            // Assume an A8 bitmap is not opaque to avoid the check of each
            // individual pixel. It is very unlikely to be opaque, since
            // an opaque A8 bitmap would not be very interesting.
            // Otherwise, a jpeg image is opaque.
            return bm->setInfo(SkImageInfo::Make(smpl.scaledWidth(), smpl.scaledHeight(),
                                                 colorType, alphaType));
        } else {
            return return_false(cinfo, *bm, "start_decompress");
        }
    }
    sampleSize = recompute_sampleSize(sampleSize, cinfo);

#ifdef SK_SUPPORT_LEGACY_IMAGEDECODER_CHOOSER
    // should we allow the Chooser (if present) to pick a colortype for us???
    if (!this->chooseFromOneChoice(colorType, cinfo.output_width, cinfo.output_height)) {
        return return_false(cinfo, *bm, "chooseFromOneChoice");
    }
#endif

    SkScaledBitmapSampler sampler(cinfo.output_width, cinfo.output_height, sampleSize);
    // Assume an A8 bitmap is not opaque to avoid the check of each
    // individual pixel. It is very unlikely to be opaque, since
    // an opaque A8 bitmap would not be very interesting.
    // Otherwise, a jpeg image is opaque.
    bm->setInfo(SkImageInfo::Make(sampler.scaledWidth(), sampler.scaledHeight(),
                                  colorType, alphaType));
    if (SkImageDecoder::kDecodeBounds_Mode == mode) {
        return true;
    }
    if (!this->allocPixelRef(bm, NULL)) {
        return return_false(cinfo, *bm, "allocPixelRef");
    }

    SkAutoLockPixels alp(*bm);

#ifdef ANDROID_RGB
    /* short-circuit the SkScaledBitmapSampler when possible, as this gives
       a significant performance boost.
    */
    if (sampleSize == 1 &&
        ((kN32_SkColorType == colorType && cinfo.out_color_space == JCS_RGBA_8888) ||
         (kRGB_565_SkColorType == colorType && cinfo.out_color_space == JCS_RGB_565)))
    {
        JSAMPLE* rowptr = (JSAMPLE*)bm->getPixels();
        INT32 const bpr =  bm->rowBytes();

        while (cinfo.output_scanline < cinfo.output_height) {
            int row_count = jpeg_read_scanlines(&cinfo, &rowptr, 1);
            if (0 == row_count) {
                // 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");
            }
            rowptr += bpr;
        }
        jpeg_finish_decompress(&cinfo);
        return true;
    }
#endif

    // check for supported formats
    SkScaledBitmapSampler::SrcConfig sc;
    int srcBytesPerPixel;

    if (!get_src_config(cinfo, &sc, &srcBytesPerPixel)) {
        return return_false(cinfo, *bm, "jpeg colorspace");
    }

    if (!sampler.begin(bm, sc, *this)) {
        return return_false(cinfo, *bm, "sampler.begin");
    }

    SkAutoMalloc srcStorage(cinfo.output_width * srcBytesPerPixel);
    uint8_t* srcRow = (uint8_t*)srcStorage.get();

    //  Possibly skip initial rows [sampler.srcY0]
    if (!skip_src_rows(&cinfo, srcRow, sampler.srcY0())) {
        return return_false(cinfo, *bm, "skip rows");
    }

    // now loop through scanlines until y == bm->height() - 1
    for (int y = 0;; y++) {
        JSAMPLE* rowptr = (JSAMPLE*)srcRow;
        int row_count = jpeg_read_scanlines(&cinfo, &rowptr, 1);
        if (0 == row_count) {
            // 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");
        }

        if (JCS_CMYK == cinfo.out_color_space) {
            convert_CMYK_to_RGB(srcRow, cinfo.output_width);
        }

        sampler.next(srcRow);
        if (bm->height() - 1 == y) {
            // we're done
            break;
        }

        if (!skip_src_rows(&cinfo, srcRow, sampler.srcDY() - 1)) {
            return return_false(cinfo, *bm, "skip rows");
        }
    }

    // we formally skip the rest, so we don't get a complaint from libjpeg
    if (!skip_src_rows(&cinfo, srcRow,
                       cinfo.output_height - cinfo.output_scanline)) {
        return return_false(cinfo, *bm, "skip rows");
    }
    jpeg_finish_decompress(&cinfo);

    return true;
}

a、配置Jpeg解码选项(输出格式、采样率、idct类型、下采样率等等,在SkImageDecoder.h的注释中解释过一些)
b、在不设置下采样且目标格式为RGBA时,直接解码到目标Bitmap上(Jpeg是按YUV三分量分别压缩的,设置为Jpeg库解码时作YUV-RGBA的颜色转换)。
c、需要下采样时,由于jpeg库可作简化处理,因此重新计算一个下采样率,由SkScaledBitmapSampler将解码出来的图像下采样到目标bitmap上。

(2)Png

bool SkPNGImageDecoder::onDecodeInit(SkStream* sk_stream, png_structp *png_ptrp,
                                     png_infop *info_ptrp) {
    /* Create and initialize the png_struct with the desired error handler
    * functions.  If you want to use the default stderr and longjump method,
    * you can supply NULL for the last three parameters.  We also supply the
    * the compiler header file version, so that we know if the application
    * was compiled with a compatible version of the library.  */

    png_error_ptr user_warning_fn =
        (c_suppressPNGImageDecoderWarnings) ? (&do_nothing_warning_fn) : NULL;
    /* NULL means to leave as default library behavior. */
    /* c_suppressPNGImageDecoderWarnings default depends on SK_DEBUG. */
    /* To suppress warnings with a SK_DEBUG binary, set the
     * environment variable "skia_images_png_suppressDecoderWarnings"
     * to "true".  Inside a program that links to skia:
     * SK_CONF_SET("images.png.suppressDecoderWarnings", true); */

    png_structp png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING,
        NULL, sk_error_fn, user_warning_fn);
    //   png_voidp user_error_ptr, user_error_fn, user_warning_fn);
    if (png_ptr == NULL) {
        return false;
    }

    *png_ptrp = png_ptr;

    /* Allocate/initialize the memory for image information. */
    png_infop info_ptr = png_create_info_struct(png_ptr);
    if (info_ptr == NULL) {
        png_destroy_read_struct(&png_ptr, png_infopp_NULL, png_infopp_NULL);
        return false;
    }
    *info_ptrp = info_ptr;

    /* Set error handling if you are using the setjmp/longjmp method (this is
    * the normal method of doing things with libpng).  REQUIRED unless you
    * set up your own error handlers in the png_create_read_struct() earlier.
    */
    if (setjmp(png_jmpbuf(png_ptr))) {
        png_destroy_read_struct(&png_ptr, &info_ptr, png_infopp_NULL);
        return false;
    }

    /* If you are using replacement read functions, instead of calling
    * png_init_io() here you would call:
    */
    png_set_read_fn(png_ptr, (void *)sk_stream, sk_read_fn);
#ifdef SK_BUILD_FOR_ANDROID
    png_set_seek_fn(png_ptr, sk_seek_fn);
#endif
    /* where user_io_ptr is a structure you want available to the callbacks */
    /* If we have already read some of the signature */
//  png_set_sig_bytes(png_ptr, 0 /* sig_read */ );

    // hookup our peeker so we can see any user-chunks the caller may be interested in
    png_set_keep_unknown_chunks(png_ptr, PNG_HANDLE_CHUNK_ALWAYS, (png_byte*)"", 0);
    if (this->getPeeker()) {
        png_set_read_user_chunk_fn(png_ptr, (png_voidp)this->getPeeker(), sk_read_user_chunk);
    }

    /* The call to png_read_info() gives us all of the information from the
    * PNG file before the first IDAT (image data chunk). */
    png_read_info(png_ptr, info_ptr);
    png_uint_32 origWidth, origHeight;
    int bitDepth, colorType;
    png_get_IHDR(png_ptr, info_ptr, &origWidth, &origHeight, &bitDepth,
                 &colorType, int_p_NULL, int_p_NULL, int_p_NULL);

    /* tell libpng to strip 16 bit/color files down to 8 bits/color */
    if (bitDepth == 16) {
        png_set_strip_16(png_ptr);
    }
    /* Extract multiple pixels with bit depths of 1, 2, and 4 from a single
     * byte into separate bytes (useful for paletted and grayscale images). */
    if (bitDepth < 8) {
        png_set_packing(png_ptr);
    }
    /* Expand grayscale images to the full 8 bits from 1, 2, or 4 bits/pixel */
    if (colorType == PNG_COLOR_TYPE_GRAY && bitDepth < 8) {
        png_set_expand_gray_1_2_4_to_8(png_ptr);
    }

    return true;
}

bool SkPNGImageDecoder::onDecode(SkStream* sk_stream, SkBitmap* decodedBitmap,
                                 Mode mode) {
    png_structp png_ptr;
    png_infop info_ptr;

    if (!onDecodeInit(sk_stream, &png_ptr, &info_ptr)) {
        return false;
    }

    PNGAutoClean autoClean(png_ptr, info_ptr);

    if (setjmp(png_jmpbuf(png_ptr))) {
        return false;
    }

    png_uint_32 origWidth, origHeight;
    int bitDepth, pngColorType, interlaceType;
    png_get_IHDR(png_ptr, info_ptr, &origWidth, &origHeight, &bitDepth,
                 &pngColorType, &interlaceType, int_p_NULL, int_p_NULL);

    SkColorType         colorType;
    bool                hasAlpha = false;
    SkPMColor           theTranspColor = 0; // 0 tells us not to try to match

    if (!this->getBitmapColorType(png_ptr, info_ptr, &colorType, &hasAlpha, &theTranspColor)) {
        return false;
    }

    SkAlphaType alphaType = this->getRequireUnpremultipliedColors() ?
                                kUnpremul_SkAlphaType : kPremul_SkAlphaType;
    const int sampleSize = this->getSampleSize();
    SkScaledBitmapSampler sampler(origWidth, origHeight, sampleSize);
    decodedBitmap->setInfo(SkImageInfo::Make(sampler.scaledWidth(), sampler.scaledHeight(),
                                             colorType, alphaType));

    if (SkImageDecoder::kDecodeBounds_Mode == mode) {
        return true;
    }

    // from here down we are concerned with colortables and pixels

    // we track if we actually see a non-opaque pixels, since sometimes a PNG sets its colortype
    // to |= PNG_COLOR_MASK_ALPHA, but all of its pixels are in fact opaque. We care, since we
    // draw lots faster if we can flag the bitmap has being opaque
    bool reallyHasAlpha = false;
    SkColorTable* colorTable = NULL;

    if (pngColorType == PNG_COLOR_TYPE_PALETTE) {
        decodePalette(png_ptr, info_ptr, &hasAlpha, &reallyHasAlpha, &colorTable);
    }

    SkAutoUnref aur(colorTable);

    if (!this->allocPixelRef(decodedBitmap,
                             kIndex_8_SkColorType == colorType ? colorTable : NULL)) {
        return false;
    }

    SkAutoLockPixels alp(*decodedBitmap);

    /* Turn on interlace handling.  REQUIRED if you are not using
    *  png_read_image().  To see how to handle interlacing passes,
    *  see the png_read_row() method below:
    */
    const int number_passes = (interlaceType != PNG_INTERLACE_NONE) ?
                              png_set_interlace_handling(png_ptr) : 1;

    /* Optional call to gamma correct and add the background to the palette
    *  and update info structure.  REQUIRED if you are expecting libpng to
    *  update the palette for you (ie you selected such a transform above).
    */
    png_read_update_info(png_ptr, info_ptr);

    if ((kAlpha_8_SkColorType == colorType || kIndex_8_SkColorType == colorType) &&
            1 == sampleSize) {
        if (kAlpha_8_SkColorType == colorType) {
            // For an A8 bitmap, we assume there is an alpha for speed. It is
            // possible the bitmap is opaque, but that is an unlikely use case
            // since it would not be very interesting.
            reallyHasAlpha = true;
            // A8 is only allowed if the original was GRAY.
            SkASSERT(PNG_COLOR_TYPE_GRAY == pngColorType);
        }
        for (int i = 0; i < number_passes; i++) {
            for (png_uint_32 y = 0; y < origHeight; y++) {
                uint8_t* bmRow = decodedBitmap->getAddr8(0, y);
                png_read_rows(png_ptr, &bmRow, png_bytepp_NULL, 1);
            }
        }
    } else {
        SkScaledBitmapSampler::SrcConfig sc;
        int srcBytesPerPixel = 4;

        if (colorTable != NULL) {
            sc = SkScaledBitmapSampler::kIndex;
            srcBytesPerPixel = 1;
        } else if (kAlpha_8_SkColorType == colorType) {
            // A8 is only allowed if the original was GRAY.
            SkASSERT(PNG_COLOR_TYPE_GRAY == pngColorType);
            sc = SkScaledBitmapSampler::kGray;
            srcBytesPerPixel = 1;
        } else if (hasAlpha) {
            sc = SkScaledBitmapSampler::kRGBA;
        } else {
            sc = SkScaledBitmapSampler::kRGBX;
        }

        /*  We have to pass the colortable explicitly, since we may have one
            even if our decodedBitmap doesn't, due to the request that we
            upscale png's palette to a direct model
         */
        SkAutoLockColors ctLock(colorTable);
        if (!sampler.begin(decodedBitmap, sc, *this, ctLock.colors())) {
            return false;
        }
        const int height = decodedBitmap->height();

        if (number_passes > 1) {
            SkAutoMalloc storage(origWidth * origHeight * srcBytesPerPixel);
            uint8_t* base = (uint8_t*)storage.get();
            size_t rowBytes = origWidth * srcBytesPerPixel;

            for (int i = 0; i < number_passes; i++) {
                uint8_t* row = base;
                for (png_uint_32 y = 0; y < origHeight; y++) {
                    uint8_t* bmRow = row;
                    png_read_rows(png_ptr, &bmRow, png_bytepp_NULL, 1);
                    row += rowBytes;
                }
            }
            // now sample it
            base += sampler.srcY0() * rowBytes;
            for (int y = 0; y < height; y++) {
                reallyHasAlpha |= sampler.next(base);
                base += sampler.srcDY() * rowBytes;
            }
        } else {
            SkAutoMalloc storage(origWidth * srcBytesPerPixel);
            uint8_t* srcRow = (uint8_t*)storage.get();
            skip_src_rows(png_ptr, srcRow, sampler.srcY0());

            for (int y = 0; y < height; y++) {
                uint8_t* tmp = srcRow;
                png_read_rows(png_ptr, &tmp, png_bytepp_NULL, 1);
                reallyHasAlpha |= sampler.next(srcRow);
                if (y < height - 1) {
                    skip_src_rows(png_ptr, srcRow, sampler.srcDY() - 1);
                }
            }

            // skip the rest of the rows (if any)
            png_uint_32 read = (height - 1) * sampler.srcDY() +
                               sampler.srcY0() + 1;
            SkASSERT(read <= origHeight);
            skip_src_rows(png_ptr, srcRow, origHeight - read);
        }
    }

    /* read rest of file, and get additional chunks in info_ptr - REQUIRED */
    png_read_end(png_ptr, info_ptr);

    if (0 != theTranspColor) {
        reallyHasAlpha |= substituteTranspColor(decodedBitmap, theTranspColor);
    }
    if (reallyHasAlpha && this->getRequireUnpremultipliedColors()) {
        switch (decodedBitmap->colorType()) {
            case kIndex_8_SkColorType:
                // Fall through.
            case kARGB_4444_SkColorType:
                // We have chosen not to support unpremul for these colortypes.
                return false;
            default: {
                // Fall through to finish the decode. This colortype either
                // supports unpremul or it is irrelevant because it has no
                // alpha (or only alpha).
                // These brackets prevent a warning.
            }
        }
    }

    if (!reallyHasAlpha) {
        decodedBitmap->setAlphaType(kOpaque_SkAlphaType);
    }
    return true;
}


Png格式的图像可能包含透明度。Skia作了一个额外处理是检查其透明度是否全为255,是则置不透明标志,毕竟不透明的图像在后续渲染时消耗较少。

(3)Gif

bool SkGIFImageDecoder::onDecode(SkStream* sk_stream, SkBitmap* bm, Mode mode) {
#if GIFLIB_MAJOR < 5
    GifFileType* gif = DGifOpen(sk_stream, DecodeCallBackProc);
#else
    GifFileType* gif = DGifOpen(sk_stream, DecodeCallBackProc, NULL);
#endif
    if (NULL == gif) {
        return error_return(*bm, "DGifOpen");
    }

    SkAutoTCallIProc<GifFileType, DGifCloseFile> acp(gif);

    SavedImage temp_save;
    temp_save.ExtensionBlocks=NULL;
    temp_save.ExtensionBlockCount=0;
    SkAutoTCallVProc<SavedImage, CheckFreeExtension> acp2(&temp_save);

    int width, height;
    GifRecordType recType;
    GifByteType *extData;
#if GIFLIB_MAJOR >= 5
    int extFunction;
#endif
    int transpIndex = -1;   // -1 means we don't have it (yet)
    int fillIndex = gif->SBackGroundColor;

    do {
        if (DGifGetRecordType(gif, &recType) == GIF_ERROR) {
            return error_return(*bm, "DGifGetRecordType");
        }

        switch (recType) {
        case IMAGE_DESC_RECORD_TYPE: {
            if (DGifGetImageDesc(gif) == GIF_ERROR) {
                return error_return(*bm, "IMAGE_DESC_RECORD_TYPE");
            }

            if (gif->ImageCount < 1) {    // sanity check
                return error_return(*bm, "ImageCount < 1");
            }

            width = gif->SWidth;
            height = gif->SHeight;

            SavedImage* image = &gif->SavedImages[gif->ImageCount-1];
            const GifImageDesc& desc = image->ImageDesc;

            int imageLeft = desc.Left;
            int imageTop = desc.Top;
            const int innerWidth = desc.Width;
            const int innerHeight = desc.Height;
            if (innerWidth <= 0 || innerHeight <= 0) {
                return error_return(*bm, "invalid dimensions");
            }

            // check for valid descriptor
            if (innerWidth > width) {
                gif_warning(*bm, "image too wide, expanding output to size");
                width = innerWidth;
                imageLeft = 0;
            } else if (imageLeft + innerWidth > width) {
                gif_warning(*bm, "shifting image left to fit");
                imageLeft = width - innerWidth;
            } else if (imageLeft < 0) {
                gif_warning(*bm, "shifting image right to fit");
                imageLeft = 0;
            }


            if (innerHeight > height) {
                gif_warning(*bm, "image too tall,  expanding output to size");
                height = innerHeight;
                imageTop = 0;
            } else if (imageTop + innerHeight > height) {
                gif_warning(*bm, "shifting image up to fit");
                imageTop = height - innerHeight;
            } else if (imageTop < 0) {
                gif_warning(*bm, "shifting image down to fit");
                imageTop = 0;
            }

#ifdef SK_SUPPORT_LEGACY_IMAGEDECODER_CHOOSER
            // FIXME: We could give the caller a choice of images or configs.
            if (!this->chooseFromOneChoice(kIndex_8_SkColorType, width, height)) {
                return error_return(*bm, "chooseFromOneChoice");
            }
#endif

            SkScaledBitmapSampler sampler(width, height, this->getSampleSize());

            bm->setInfo(SkImageInfo::Make(sampler.scaledWidth(), sampler.scaledHeight(),
                                          kIndex_8_SkColorType, kPremul_SkAlphaType));

            if (SkImageDecoder::kDecodeBounds_Mode == mode) {
                return true;
            }


            // now we decode the colortable
            int colorCount = 0;
            {
                // Declare colorPtr here for scope.
                SkPMColor colorPtr[256]; // storage for worst-case
                const ColorMapObject* cmap = find_colormap(gif);
                SkAlphaType alphaType = kOpaque_SkAlphaType;
                if (cmap != NULL) {
                    SkASSERT(cmap->ColorCount == (1 << (cmap->BitsPerPixel)));
                    colorCount = cmap->ColorCount;
                    if (colorCount > 256) {
                        colorCount = 256;  // our kIndex8 can't support more
                    }
                    for (int index = 0; index < colorCount; index++) {
                        colorPtr[index] = SkPackARGB32(0xFF,
                                                       cmap->Colors[index].Red,
                                                       cmap->Colors[index].Green,
                                                       cmap->Colors[index].Blue);
                    }
                } else {
                    // find_colormap() returned NULL.  Some (rare, broken)
                    // GIFs don't have a color table, so we force one.
                    gif_warning(*bm, "missing colormap");
                    colorCount = 256;
                    sk_memset32(colorPtr, SK_ColorWHITE, colorCount);
                }
                transpIndex = find_transpIndex(temp_save, colorCount);
                if (transpIndex >= 0) {
                    colorPtr[transpIndex] = SK_ColorTRANSPARENT; // ram in a transparent SkPMColor
                    alphaType = kPremul_SkAlphaType;
                    fillIndex = transpIndex;
                } else if (fillIndex >= colorCount) {
                    // gif->SBackGroundColor should be less than colorCount.
                    fillIndex = 0;  // If not, fix it.
                }

                SkAutoTUnref<SkColorTable> ctable(SkNEW_ARGS(SkColorTable,
                                                  (colorPtr, colorCount,
                                                   alphaType)));
                if (!this->allocPixelRef(bm, ctable)) {
                    return error_return(*bm, "allocPixelRef");
                }
            }

            // abort if either inner dimension is <= 0
            if (innerWidth <= 0 || innerHeight <= 0) {
                return error_return(*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 ((imageTop | imageLeft) > 0 ||
                 innerWidth < width || innerHeight < height) {
                // Fill the background.
                memset(bm->getPixels(), fillIndex, bm->getSize());

                // Create a subset of the bitmap.
                SkIRect subsetRect(SkIRect::MakeXYWH(imageLeft / sampler.srcDX(),
                                                     imageTop / sampler.srcDY(),
                                                     innerWidth / sampler.srcDX(),
                                                     innerHeight / sampler.srcDY()));
                if (!bm->extractSubset(&subset, subsetRect)) {
                    return error_return(*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(*bm, "Sampler failed to begin.");
            }

            // now decode each scanline
            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++) {
                    if (DGifGetLine(gif, scanline, innerWidth) == GIF_ERROR) {
                        gif_warning(*bm, "interlace DGifGetLine");
                        memset(scanline, fillIndex, innerWidth);
                        for (; y < innerHeight; y++) {
                            sampler.sampleInterlaced(scanline, iter.currY());
                            iter.next();
                        }
                        return true;
                    }
                    sampler.sampleInterlaced(scanline, iter.currY());
                    iter.next();
                }
            } else {
                // easy, non-interlace case
                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) {
                        gif_warning(*bm, "DGifGetLine");
                        memset(scanline, fillIndex, innerWidth);
                        for (; y < outHeight; y++) {
                            sampler.next(scanline);
                        }
                        return true;
                    }
                    // 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);
            }
            sanitize_indexed_bitmap(bm);
            return true;
            } break;

        case EXTENSION_RECORD_TYPE:
#if GIFLIB_MAJOR < 5
            if (DGifGetExtension(gif, &temp_save.Function,
                                 &extData) == GIF_ERROR) {
#else
            if (DGifGetExtension(gif, &extFunction, &extData) == GIF_ERROR) {
#endif
                return error_return(*bm, "DGifGetExtension");
            }

            while (extData != NULL) {
                /* Create an extension block with our data */
#if GIFLIB_MAJOR < 5
                if (AddExtensionBlock(&temp_save, extData[0],
                                      &extData[1]) == GIF_ERROR) {
#else
                if (GifAddExtensionBlock(&gif->ExtensionBlockCount,
                                         &gif->ExtensionBlocks,
                                         extFunction,
                                         extData[0],
                                         &extData[1]) == GIF_ERROR) {
#endif
                    return error_return(*bm, "AddExtensionBlock");
                }
                if (DGifGetExtensionNext(gif, &extData) == GIF_ERROR) {
                    return error_return(*bm, "DGifGetExtensionNext");
                }
#if GIFLIB_MAJOR < 5
                temp_save.Function = 0;
#endif
            }
            break;

        case TERMINATE_RECORD_TYPE:
            break;

        default:    /* Should be trapped by DGifGetRecordType */
            break;
        }
    } while (recType != TERMINATE_RECORD_TYPE);

    sanitize_indexed_bitmap(bm);
    return true;
}


由于适配不同版本的gif库的原因,这段代码写得略显杂乱,也存在一些Bug。
值得注意的是Skia对于Gif动态图,只解最后一帧(SavedImage* image = &gif->SavedImages[gif->ImageCount-1];)
至于Gif动画,由SkGIFMovie类处理,走不同的流程。应用需要调另外的类(详细见:frameworks/base/graphics/java/android/graphics/Movie.java)。

(4)bmp

bool SkBMPImageDecoder::onDecode(SkStream* stream, SkBitmap* bm, Mode mode) {
    // First read the entire stream, so that all of the data can be passed to
    // the BmpDecoderHelper.

    // Allocated space used to hold the data.
    SkAutoMalloc storage;
    // Byte length of all of the data.
    const size_t length = CopyStreamToStorage(&storage, stream);
    if (0 == length) {
        return 0;
    }

    const bool justBounds = SkImageDecoder::kDecodeBounds_Mode == mode;
    SkBmpDecoderCallback callback(justBounds);

    // Now decode the BMP into callback's rgb() array [r,g,b, r,g,b, ...]
    {
        image_codec::BmpDecoderHelper helper;
        const int max_pixels = 16383*16383; // max width*height
        if (!helper.DecodeImage((const char*)storage.get(), length,
                                max_pixels, &callback)) {
            return false;
        }
    }

    // we don't need this anymore, so free it now (before we try to allocate
    // the bitmap's pixels) rather than waiting for its destructor
    storage.free();

    int width = callback.width();
    int height = callback.height();
    SkColorType colorType = this->getPrefColorType(k32Bit_SrcDepth, false);

    // only accept prefConfig if it makes sense for us
    if (kARGB_4444_SkColorType != colorType && kRGB_565_SkColorType != colorType) {
        colorType = kN32_SkColorType;
    }

    SkScaledBitmapSampler sampler(width, height, getSampleSize());

    bm->setInfo(SkImageInfo::Make(sampler.scaledWidth(), sampler.scaledHeight(),
                                  colorType, kOpaque_SkAlphaType));

    if (justBounds) {
        return true;
    }

    if (!this->allocPixelRef(bm, NULL)) {
        return false;
    }

    SkAutoLockPixels alp(*bm);

    if (!sampler.begin(bm, SkScaledBitmapSampler::kRGB, *this)) {
        return false;
    }

    const int srcRowBytes = width * 3;
    const int dstHeight = sampler.scaledHeight();
    const uint8_t* srcRow = callback.rgb();

    srcRow += sampler.srcY0() * srcRowBytes;
    for (int y = 0; y < dstHeight; y++) {
        sampler.next(srcRow);
        srcRow += sampler.srcDY() * srcRowBytes;
    }
    return true;
}


这个是最简单的一种格式,用得较少,Skia里面的处理也比较随意。
a、将流中内容全部读到内存(CopyStreamToStorage)
b、解析流中的内容,将其转化为RGB格式(image_codec::BmpDecoderHelper::DecodeImage)
c、使用SkScaledBitmapSampler,将RGB转成目标格式,同时作下采样。


6、典型编码器的onEncode方法

(1)Jpeg

class SkJPEGImageEncoder : public SkImageEncoder {
protected:
    virtual bool onEncode(SkWStream* stream, const SkBitmap& bm, int quality) {
#ifdef TIME_ENCODE
        SkAutoTime atm("JPEG Encode");
#endif

        SkAutoLockPixels alp(bm);
        if (NULL == bm.getPixels()) {
            return false;
        }

        jpeg_compress_struct    cinfo;
        skjpeg_error_mgr        sk_err;
        skjpeg_destination_mgr  sk_wstream(stream);

        // allocate these before set call setjmp
        SkAutoMalloc    oneRow;
        SkAutoLockColors ctLocker;

        cinfo.err = jpeg_std_error(&sk_err);
        sk_err.error_exit = skjpeg_error_exit;
        if (setjmp(sk_err.fJmpBuf)) {
            return false;
        }

        // Keep after setjmp or mark volatile.
        const WriteScanline writer = ChooseWriter(bm);
        if (NULL == writer) {
            return false;
        }

        jpeg_create_compress(&cinfo);
        cinfo.dest = &sk_wstream;
        cinfo.image_width = bm.width();
        cinfo.image_height = bm.height();
        cinfo.input_components = 3;
#ifdef WE_CONVERT_TO_YUV
        cinfo.in_color_space = JCS_YCbCr;
#else
        cinfo.in_color_space = JCS_RGB;
#endif
        cinfo.input_gamma = 1;

        jpeg_set_defaults(&cinfo);
        jpeg_set_quality(&cinfo, quality, TRUE /* limit to baseline-JPEG values */);
#ifdef DCT_IFAST_SUPPORTED
        cinfo.dct_method = JDCT_IFAST;
#endif

        jpeg_start_compress(&cinfo, TRUE);

        const int       width = bm.width();
        uint8_t*        oneRowP = (uint8_t*)oneRow.reset(width * 3);

        const SkPMColor* colors = ctLocker.lockColors(bm);
        const void*      srcRow = bm.getPixels();

        while (cinfo.next_scanline < cinfo.image_height) {
            JSAMPROW row_pointer[1];    /* pointer to JSAMPLE row[s] */

            writer(oneRowP, srcRow, width, colors);
            row_pointer[0] = oneRowP;
            (void) jpeg_write_scanlines(&cinfo, row_pointer, 1);
            srcRow = (const void*)((const char*)srcRow + bm.rowBytes());
        }

        jpeg_finish_compress(&cinfo);
        jpeg_destroy_compress(&cinfo);

        return true;
    }
};

Jpeg是有损压缩,传入的quality决定其量化参数表。
a、Skia默认先将图片转为YUV444格式,再进行编码(WE_CONVERT_TO_YUV宏默认打开状态,否则就是先转为RGB888格式,再传入Jpeg编码时转YUV)。
b、默认使用JDCT_IFAST方法做傅立叶变换,很明显会造成一定的图片质量损失(即使quality设成100也存在,是计算精度的问题)。

(2)Png

static transform_scanline_proc choose_proc(SkColorType ct, bool hasAlpha) {
    // we don't care about search on alpha if we're kIndex8, since only the
    // colortable packing cares about that distinction, not the pixels
    if (kIndex_8_SkColorType == ct) {
        hasAlpha = false;   // we store false in the table entries for kIndex8
    }

    static const struct {
        SkColorType             fColorType;
        bool                    fHasAlpha;
        transform_scanline_proc fProc;
    } gMap[] = {
        { kRGB_565_SkColorType,     false,  transform_scanline_565 },
        { kN32_SkColorType,         false,  transform_scanline_888 },
        { kN32_SkColorType,         true,   transform_scanline_8888 },
        { kARGB_4444_SkColorType,   false,  transform_scanline_444 },
        { kARGB_4444_SkColorType,   true,   transform_scanline_4444 },
        { kIndex_8_SkColorType,     false,  transform_scanline_memcpy },
    };

    for (int i = SK_ARRAY_COUNT(gMap) - 1; i >= 0; --i) {
        if (gMap[i].fColorType == ct && gMap[i].fHasAlpha == hasAlpha) {
            return gMap[i].fProc;
        }
    }
    sk_throw();
    return NULL;
}
bool SkPNGImageEncoder::onEncode(SkWStream* stream, const SkBitmap& bitmap, int /*quality*/) {
    SkColorType ct = bitmap.colorType();

    const bool hasAlpha = !bitmap.isOpaque();
    int colorType = PNG_COLOR_MASK_COLOR;
    int bitDepth = 8;   // default for color
    png_color_8 sig_bit;

    switch (ct) {
        case kIndex_8_SkColorType:
            colorType |= PNG_COLOR_MASK_PALETTE;
            // fall through to the ARGB_8888 case
        case kN32_SkColorType:
            sig_bit.red = 8;
            sig_bit.green = 8;
            sig_bit.blue = 8;
            sig_bit.alpha = 8;
            break;
        case kARGB_4444_SkColorType:
            sig_bit.red = 4;
            sig_bit.green = 4;
            sig_bit.blue = 4;
            sig_bit.alpha = 4;
            break;
        case kRGB_565_SkColorType:
            sig_bit.red = 5;
            sig_bit.green = 6;
            sig_bit.blue = 5;
            sig_bit.alpha = 0;
            break;
        default:
            return false;
    }

    if (hasAlpha) {
        // don't specify alpha if we're a palette, even if our ctable has alpha
        if (!(colorType & PNG_COLOR_MASK_PALETTE)) {
            colorType |= PNG_COLOR_MASK_ALPHA;
        }
    } else {
        sig_bit.alpha = 0;
    }

    SkAutoLockPixels alp(bitmap);
    // readyToDraw checks for pixels (and colortable if that is required)
    if (!bitmap.readyToDraw()) {
        return false;
    }

    // we must do this after we have locked the pixels
    SkColorTable* ctable = bitmap.getColorTable();
    if (NULL != ctable) {
        if (ctable->count() == 0) {
            return false;
        }
        // check if we can store in fewer than 8 bits
        bitDepth = computeBitDepth(ctable->count());
    }

    return doEncode(stream, bitmap, hasAlpha, colorType, bitDepth, ct, sig_bit);
}

bool SkPNGImageEncoder::doEncode(SkWStream* stream, const SkBitmap& bitmap,
                  const bool& hasAlpha, int colorType,
                  int bitDepth, SkColorType ct,
                  png_color_8& sig_bit) {

    png_structp png_ptr;
    png_infop info_ptr;

    png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, sk_error_fn,
                                      NULL);
    if (NULL == png_ptr) {
        return false;
    }

    info_ptr = png_create_info_struct(png_ptr);
    if (NULL == info_ptr) {
        png_destroy_write_struct(&png_ptr,  png_infopp_NULL);
        return false;
    }

    /* Set error handling.  REQUIRED if you aren't supplying your own
    * error handling functions in the png_create_write_struct() call.
    */
    if (setjmp(png_jmpbuf(png_ptr))) {
        png_destroy_write_struct(&png_ptr, &info_ptr);
        return false;
    }

    png_set_write_fn(png_ptr, (void*)stream, sk_write_fn, png_flush_ptr_NULL);

    /* Set the image information here.  Width and height are up to 2^31,
    * bit_depth is one of 1, 2, 4, 8, or 16, but valid values also depend on
    * the color_type selected. color_type is one of PNG_COLOR_TYPE_GRAY,
    * PNG_COLOR_TYPE_GRAY_ALPHA, PNG_COLOR_TYPE_PALETTE, PNG_COLOR_TYPE_RGB,
    * or PNG_COLOR_TYPE_RGB_ALPHA.  interlace is either PNG_INTERLACE_NONE or
    * PNG_INTERLACE_ADAM7, and the compression_type and filter_type MUST
    * currently be PNG_COMPRESSION_TYPE_BASE and PNG_FILTER_TYPE_BASE. REQUIRED
    */

    png_set_IHDR(png_ptr, info_ptr, bitmap.width(), bitmap.height(),
                 bitDepth, colorType,
                 PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_BASE,
                 PNG_FILTER_TYPE_BASE);

    // set our colortable/trans arrays if needed
    png_color paletteColors[256];
    png_byte trans[256];
    if (kIndex_8_SkColorType == ct) {
        SkColorTable* ct = bitmap.getColorTable();
        int numTrans = pack_palette(ct, paletteColors, trans, hasAlpha);
        png_set_PLTE(png_ptr, info_ptr, paletteColors, ct->count());
        if (numTrans > 0) {
            png_set_tRNS(png_ptr, info_ptr, trans, numTrans, NULL);
        }
    }

    png_set_sBIT(png_ptr, info_ptr, &sig_bit);
    png_write_info(png_ptr, info_ptr);

    const char* srcImage = (const char*)bitmap.getPixels();
    SkAutoSMalloc<1024> rowStorage(bitmap.width() << 2);
    char* storage = (char*)rowStorage.get();
    transform_scanline_proc proc = choose_proc(ct, hasAlpha);

    for (int y = 0; y < bitmap.height(); y++) {
        png_bytep row_ptr = (png_bytep)storage;
        proc(srcImage, bitmap.width(), storage);
        png_write_rows(png_ptr, &row_ptr, 1);
        srcImage += bitmap.rowBytes();
    }

    png_write_end(png_ptr, info_ptr);

    /* clean up after the write, and free any memory allocated */
    png_destroy_write_struct(&png_ptr, &info_ptr);
    return true;
}

a、将图像格式统一转为RGBA8888,再去作png编码,另外注明是否包含透明度信息。

b、对于原先带透明的图像格式(RGBA8888,RGBA4444),做反alpha预乘,也即每个像素值除以其alpha值,自然,除法会做些转化由乘法替代的。

  • 4
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 13
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 13
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值