文档章节

Skia深入分析7——区域解码

jxt1234
 jxt1234
发布于 2015/10/15 18:06
字数 1232
阅读 20
收藏 0

Skia深入分析7——区域解码

1、概述
-当图片很大时,解码速度缓慢,占用内存很高,并且,当图片超过一定尺寸时,无法做纹理上传和显示(这跟GPU能力有关,一般的GPU是8192*8192)。这时只好做下采样,但会牺牲图片显示的质量。
-对于图库等需要清晰浏览图片的应用,不可能设置一个下采样率去解决这一问题,因此,Google加入了区域解码这个功能,使我们可以从原始的图片文件中,解出一部分区域完整的图片内容。
-区域解码的难点主要在于定位像素区域所对应的文件位置,这个需要图像编码时有连续性,如果像JPEG2000标准那样做整体的小波变换就难弄了。
-目前区域解码主要实现了png、jpeg、webp类型图片的支持。本篇过一下区域解码的框架,并介绍一下最常用的jpeg格式的区域解码实现。

2、区域解码总流程
Android区域解码流程示意图
如图所示在Framework侧创建 BitmapRegionDecoder时,创建对应类型的SkImageDecoder扫描全文件,调用其onBuildTileIndex方法构建tileIndex,嵌入其关联的SkImageDecoder之中,在后续的 decodeRegion调用时,使用 SkImageDecoder的 onDecodeSubset 方法做区域解码。

3、Jpeg的区域解码

#ifdef SK_BUILD_FOR_ANDROID
bool SkJPEGImageDecoder::onBuildTileIndex(SkStreamRewindable* stream, int *width, int *height) {

    SkAutoTDelete<SkJPEGImageIndex> imageIndex(SkNEW_ARGS(SkJPEGImageIndex, (stream, this)));
    jpeg_decompress_struct* cinfo = imageIndex->cinfo();

    skjpeg_error_mgr sk_err;
    set_error_mgr(cinfo, &sk_err);

    // All objects need to be instantiated before this setjmp call so that
    // they will be cleaned up properly if an error occurs.
    if (setjmp(sk_err.fJmpBuf)) {
        return false;
    }

    // create the cinfo used to create/build the huffmanIndex
    if (!imageIndex->initializeInfoAndReadHeader()) {
        return false;
    }

    if (!imageIndex->buildHuffmanIndex()) {
        return false;
    }

    // destroy the cinfo used to create/build the huffman index
    imageIndex->destroyInfo();

    // Init decoder to image decode mode
    if (!imageIndex->initializeInfoAndReadHeader()) {
        return false;
    }

    // FIXME: This sets cinfo->out_color_space, which we may change later
    // based on the config in onDecodeSubset. This should be fine, since
    // jpeg_init_read_tile_scanline will check out_color_space again after
    // that change (when it calls jinit_color_deconverter).
    (void) this->getBitmapColorType(cinfo);

    turn_off_visual_optimizations(cinfo);

    // instead of jpeg_start_decompress() we start a tiled decompress
    if (!imageIndex->startTileDecompress()) {
        return false;
    }

    SkASSERT(1 == cinfo->scale_num);
    fImageWidth = cinfo->output_width;
    fImageHeight = cinfo->output_height;

    if (width) {
        *width = fImageWidth;
    }
    if (height) {
        *height = fImageHeight;
    }

    SkDELETE(fImageIndex);
    fImageIndex = imageIndex.detach();

    return true;
}

bool SkJPEGImageDecoder::onDecodeSubset(SkBitmap* bm, const SkIRect& region) {
    if (NULL == fImageIndex) {
        return false;
    }
    jpeg_decompress_struct* cinfo = fImageIndex->cinfo();

    SkIRect rect = SkIRect::MakeWH(fImageWidth, fImageHeight);
    if (!rect.intersect(region)) {
        // If the requested region is entirely outside the image return false
        return false;
    }


    skjpeg_error_mgr errorManager;
    set_error_mgr(cinfo, &errorManager);

    if (setjmp(errorManager.fJmpBuf)) {
        return false;
    }

    int requestedSampleSize = this->getSampleSize();
    cinfo->scale_denom = requestedSampleSize;

    set_dct_method(*this, cinfo);

    const SkColorType colorType = this->getBitmapColorType(cinfo);
    adjust_out_color_space_and_dither(cinfo, colorType, *this);

    int startX = rect.fLeft;
    int startY = rect.fTop;
    int width = rect.width();
    int height = rect.height();

    jpeg_init_read_tile_scanline(cinfo, fImageIndex->huffmanIndex(),
                                 &startX, &startY, &width, &height);
    int skiaSampleSize = recompute_sampleSize(requestedSampleSize, *cinfo);
    int actualSampleSize = skiaSampleSize * (DCTSIZE / cinfo->min_DCT_scaled_size);

    SkScaledBitmapSampler sampler(width, height, skiaSampleSize);

    SkBitmap bitmap;
    // 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.
    bitmap.setInfo(SkImageInfo::Make(sampler.scaledWidth(), sampler.scaledHeight(), colorType,
                                     kAlpha_8_SkColorType == colorType ?
                                         kPremul_SkAlphaType : kOpaque_SkAlphaType));

    // Check ahead of time if the swap(dest, src) is possible or not.
    // If yes, then we will stick to AllocPixelRef since it's cheaper with the
    // swap happening. If no, then we will use alloc to allocate pixels to
    // prevent garbage collection.
    int w = rect.width() / actualSampleSize;
    int h = rect.height() / actualSampleSize;
    bool swapOnly = (rect == region) && bm->isNull() &&
                    (w == bitmap.width()) && (h == bitmap.height()) &&
                    ((startX - rect.x()) / actualSampleSize == 0) &&
                    ((startY - rect.y()) / actualSampleSize == 0);
    if (swapOnly) {
        if (!this->allocPixelRef(&bitmap, NULL)) {
            return return_false(*cinfo, bitmap, "allocPixelRef");
        }
    } else {
        if (!bitmap.allocPixels()) {
            return return_false(*cinfo, bitmap, "allocPixels");
        }
    }

    SkAutoLockPixels alp(bitmap);

#ifdef ANDROID_RGB
    /* short-circuit the SkScaledBitmapSampler when possible, as this gives a significant performance boost. */
    if (skiaSampleSize == 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*)bitmap.getPixels();
        INT32 const bpr = bitmap.rowBytes();
        int rowTotalCount = 0;

        while (rowTotalCount < height) {
            int rowCount = jpeg_read_tile_scanline(cinfo,
                                                   fImageIndex->huffmanIndex(),
                                                   &rowptr);
            // if rowCount == 0, then we didn't get a scanline, so abort.
            // onDecodeSubset() relies on onBuildTileIndex(), which
            // needs a complete image to succeed.
            if (0 == rowCount) {
                return return_false(*cinfo, bitmap, "read_scanlines");
            }
            if (this->shouldCancelDecode()) {
                return return_false(*cinfo, bitmap, "shouldCancelDecode");
            }
            rowTotalCount += rowCount;
            rowptr += bpr;
        }

        if (swapOnly) {
            bm->swap(bitmap);
        } else {
            cropBitmap(bm, &bitmap, actualSampleSize, region.x(), region.y(),
                       region.width(), region.height(), startX, startY);
        }
        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(&bitmap, sc, *this)) {
        return return_false(*cinfo, bitmap, "sampler.begin");
    }

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

    // Possibly skip initial rows [sampler.srcY0]
    if (!skip_src_rows_tile(cinfo, fImageIndex->huffmanIndex(), srcRow, sampler.srcY0())) {
        return return_false(*cinfo, bitmap, "skip rows");
    }

    // now loop through scanlines until y == bitmap->height() - 1
    for (int y = 0;; y++) {
        JSAMPLE* rowptr = (JSAMPLE*)srcRow;
        int row_count = jpeg_read_tile_scanline(cinfo, fImageIndex->huffmanIndex(), &rowptr);
        // if row_count == 0, then we didn't get a scanline, so abort.
        // onDecodeSubset() relies on onBuildTileIndex(), which
        // needs a complete image to succeed.
        if (0 == row_count) {
            return return_false(*cinfo, bitmap, "read_scanlines");
        }
        if (this->shouldCancelDecode()) {
            return return_false(*cinfo, bitmap, "shouldCancelDecode");
        }

        if (JCS_CMYK == cinfo->out_color_space) {
            convert_CMYK_to_RGB(srcRow, width);
        }

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

        if (!skip_src_rows_tile(cinfo, fImageIndex->huffmanIndex(), srcRow,
                                sampler.srcDY() - 1)) {
            return return_false(*cinfo, bitmap, "skip rows");
        }
    }
    if (swapOnly) {
        bm->swap(bitmap);
    } else {
        cropBitmap(bm, &bitmap, actualSampleSize, region.x(), region.y(),
                   region.width(), region.height(), startX, startY);
    }
    return true;
}
#endif

Jpeg区域解码示意图

onBuildTileIndex 时,创建了huffman_index,其中的内容主要是一系列的huffman_offset,记录每个block对应的偏移量。在解码时,先移到对应block的位置,然后解出像素。

GLOBAL(JDIMENSION)
jpeg_read_tile_scanline (j_decompress_ptr cinfo, huffman_index *index,
        JSAMPARRAY scanlines)
{
  // Calculates the boundary of iMCU
  int lines_per_iMCU_row = cinfo->max_v_samp_factor * DCTSIZE;
  int lines_per_iMCU_col = cinfo->max_h_samp_factor * DCTSIZE;
  int sample_size = DCTSIZE / cinfo->min_DCT_scaled_size;
  JDIMENSION row_ctr = 0;

  if (cinfo->progressive_mode) {
    (*cinfo->main->process_data) (cinfo, scanlines, &row_ctr, 1);
  } else {
    if (cinfo->output_scanline % (lines_per_iMCU_row / sample_size) == 0) {
      // Set the read head to the next iMCU row
      int iMCU_row_offset = cinfo->output_scanline /
            (lines_per_iMCU_row / sample_size);
      int offset_data_col_position = cinfo->coef->MCU_column_left_boundary /
            index->MCU_sample_size;
      huffman_offset_data offset_data =
          index->scan[0].offset[iMCU_row_offset][offset_data_col_position];
      (*cinfo->entropy->configure_huffman_decoder) (cinfo, offset_data);
    }
    (*cinfo->main->process_data) (cinfo, scanlines, &row_ctr, 1);
  }

  cinfo->output_scanline += row_ctr;
  return row_ctr;
}

版权声明:本文为博主原创文章,未经博主允许不得转载。

© 著作权归作者所有

共有 人打赏支持
jxt1234
粉丝 5
博文 36
码字总数 41634
作品 0
杭州
Skia图片解码模块流程分析

我在在PPAPI插件中使用Skia绘图中说可以在PPAPI插件内使用Skia来绘图。这里面会有一个与色彩空间(像素格式)相关的问题。在那篇文章里我们在PPAPI中使用PPBImageData创建2D图像缓冲区时使用...

foruok
2016/02/22
0
0
Skia往SkBitmap上绘图时画不出来的问题

使用SkBitmap作为SkCanvas后端绘图时画不出来的问题 用默认条件在采用了Intel Pentium CPU的PC上编译Skia(参见Windows下从源码编译Skia)后,采用SkBitmap作为SkCanvas的后端来绘图时,遇到...

foruok
2016/02/23
0
0
几种图片格式的简介

JPEG 是目前最常见的图片格式,它诞生于 1992 年,是一个很古老的格式。它只支持有损压缩,其压缩算法可以精确控制压缩比,以图像质量换得存储空间。由于它太过常见,以至于许多移动设备的 ...

南瓜宝宝
2016/05/30
43
0
Cocos2d-x之CCImage深入分析

[Cocos2d-x相关教程来源于红孩儿的游戏编程之路 CSDN博客地址:http://blog.csdn.net/honghaier] 红孩儿Cocos2d-X学习园地QQ群:249941957 加群写:Cocos2d-x Cocos2d-x之CCImage深入分析 本...

长平狐
2013/03/19
125
0
Android Graphic : apk and Skia/OpenGL|ES

Android apk里面的画图分为2D和3D两种:2D是由 Skia 来实现的,也就是我们在框架图上看到的SGL,SGL也会调用部分opengl的内容来实现简单的3D效果;3D部分是由OpenGL|ES实现的,OpenGL|ES是O...

Jerikc
2014/05/27
0
0

没有更多内容

加载失败,请刷新页面

加载更多

03-《Apache Tomcat 9 User Guide》之安装

1.Introduction There are several ways to set up Tomcat for running on different platforms. The main documentation for this is a file called RUNNING.txt. We encourage you to refe......

飞鱼说编程
25分钟前
1
0
Hbase 概述及特点

1、Hbase概述 HBase是一种构建在HDFS之上的分布式、面向列的存储系统。在需要实时读写、随机访问超大规模数据集时,可以使用HBase。 尽管已经有许多数据存储和访问的策略和实现方法,但事实上...

PeakFang-BOK
52分钟前
0
0
TortoiseGit(乌龟git)保存用户名密码的方法

windows下比较比较好用的git客户端有2种: 1. msysgit + TortoiseGit(乌龟git) 2. GitHub for Windows github的windows版也用过一段时间,但还是不太习惯。所以目前仍然青睐与msysgit+乌龟g...

simpower
今天
1
0
Java并发编程:volatile关键字解析

volatile这个关键字可能很多朋友都听说过,或许也都用过。在Java 5之前,它是一个备受争议的关键字,因为在程序中使用它往往会导致出人意料的结果。在Java 5之后,volatile关键字才得以重获生...

engeue
今天
2
0
通过ajax访问远程天气预报服务

http://www.webxml.com.cn/zh_cn/index.aspx 更改wsdl文件 打开文件将15行,51行,101行去掉 然后把文件复制到c盘 然后在桌面上面就生成了文件 将文件打成jar包 package cn.it.ws.weather;...

江戸川
今天
3
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部