文档章节

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

jxt1234
 jxt1234
发布于 2015/10/15 18:06
字数 1232
阅读 17
收藏 0
点赞 0
评论 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
粉丝 4
博文 36
码字总数 41634
作品 0
杭州
Skia图片解码模块流程分析

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

foruok
2016/02/22
0
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
Cocos2d-x之CCImage深入分析

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

长平狐
2012/11/19
1K
0
[Android]android解决bitmap内存溢出

[转贴]最近在做一款塔防游戏,用的事surfaceview框架,由于图片过多,而且游戏过程中都需要这些图片,所以加载成bitmap后造成OOM(out of memory)异常。下面是我一步一步找解决此问题的纪录...

清水湾2012
2013/09/25
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
Java GBK中文乱码问题分析

在io相关的操作中经常会出现乱码问题 比如在一个txt文件中按GBK编码保存内容"淘!我喜欢!" 然后用RandomAccessFile类读取并打印一行。 RandomAccessFile raf = new RandomAccessFile("D:\1....

skyshitt
2016/02/11
3.4K
14
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
Skia引擎API整理介绍(skia in Android 2.3 trunk)

序 通过google, baidu,我们都很难搜索到关于skia引擎方面的文档,skia的官方wiki(http://code.google.com/p/skia) 也只有寥寥无几的文字简单介绍了SkCanvas, SkBitmap等几个主要的class,我...

米狗族
2011/01/26
3.9K
2
如何让你的 Android 显示gif格式的图片

这里,首先要说明的是,希望那些已经实现了gif格式图片显示的朋友不要喷我,我知道你们是怎么做的,无非两种:一种是用原本就已经存在的gif解码jar包,另外一种是使用google提供的开源的gif...

鉴客
2011/10/20
23.1K
6

没有更多内容

加载失败,请刷新页面

加载更多

下一页

JAVA 三种WebService 规范

JAVA 中共有三种WebService 规范,分别是JAX-WS(JAX-RPC)、JAXM&SAAJ、JAX-RS。 1. Jaxws(掌握) JAX-WS 的全称为 Java API for XML-Based Webservices ,早期的基于SOAP 的JAVA 的Web 服务...

onedotdot
26分钟前
0
0
将博客搬至CSDN

将博客搬至CSDN

xpbob
26分钟前
1
0
TensorFlow 拟合异或 one-hot方式

增加隐含层数目 之前是按照计算出的数值按照0.5分为0和1,现在是算出向量,用维度较大的作为结果 import tensorflow as tfimport numpy as np# 网络结构:2维输入 --> 2维隐藏层 --> ...

阿豪boy
29分钟前
0
0
Aidl进程间通信详细介绍

目录介绍 1.问题答疑 2.Aidl相关属性介绍 2.1 AIDL所支持的数据类型 2.2 服务端和客户端 2.3 AIDL的基本概念 3.实际开发中案例操作 3.1 aidl通信业务需求 3.2 操作步骤伪代码 3.3 服务端操作...

潇湘剑雨
44分钟前
0
0
python爬虫日志(3)下载图片

import urlliburl='https://xxx.jpg'#图片地址res=urllib.request.urlopen(url)#此函数用于对url的访问data=res.read() #字节流with open(r'D:\1.jpg',"wb") as code: c...

茫羽行
今天
0
0
vue中$emit的用法

1、父组件可以使用 props 把数据传给子组件。 2、子组件可以使用 $emit 触发父组件的自定义事件。 vm.$emit( event, arg ) //触发当前实例上的事件 vm.$on( event, fn );//监听event事件后运...

JamesView
今天
0
0
bash审计系统搭建

step1:使用saltstack工具bash部署>>>>>> # salt -N clienta state.sls audit step2:安装elasticsearch>>>>>> 注意: 1.不能以root用户进行启动,需要创建用户,并对解压的elasticsearch目录赋......

硅谷课堂
今天
0
0
Linux sar性能分析

Linux使用sar进行性能分析 sar简介 sar命令常用格式 sar常用性能数据分析 整体CPU使用统计-u 各个CPU使用统计-P 内存使用情况统计-r 整体IO情况-b 各个IO设备情况-d 网络统计-n sar日志保存-...

易野
今天
0
0
用 Python 实现打飞机,让子弹飞吧!

所用技术和软件 python 2.7 pygame 1.9.3 pyCharm 准备工作 安装好 pygame 在第一次使用 pygame 的时候,pyCharm 会自动 install pygame。 下载好使用的素材。 技术实现 初始化 pygame 首先要...

猫咪编程
今天
0
0
MySQL的行锁和表锁

简单总结一下行锁和表锁。 行锁 每次操作锁住一行数据。开销大,加锁慢;会出现死锁;锁定粒度最小,发生锁冲突的概率最低,并发度也最高。 表锁 每次操作锁住整张表。开销小,加锁快;不会出...

to_ln
今天
0
0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

返回顶部
顶部