【Android 内存优化】Android 原生 API 图片压缩原理 ( Bitmap_compress 方法解析 | Skia 二维图形库 | libjpeg 函数库 | libpng 函数库 )

简介: 【Android 内存优化】Android 原生 API 图片压缩原理 ( Bitmap_compress 方法解析 | Skia 二维图形库 | libjpeg 函数库 | libpng 函数库 )

文章目录

一、 图片质量压缩方法

二、 Skia 二维图形库

三、 libjpeg、libpng 函数库引入



在博客 【Android 内存优化】图片文件压缩 ( Android 原生 API 提供的图片压缩功能能 | 图片质量压缩 | 图片尺寸压缩 ) 简要介绍了 图片文件压缩格式 , 以及 Android 提供的图片质量 , 尺寸压缩原生 API ;



在博客 【Android 内存优化】Android 原生 API 图片压缩代码示例 ( PNG 格式压缩 | JPEG 格式压缩 | WEBP 格式压缩 | 动态权限申请 | Android10 存储策略 ) 主要使用了上述 Android 原生 API 压缩图片功能进行图片压缩 ;



在博客 【Android 内存优化】Android 原生 API 图片压缩原理 ( 图片质量压缩方法 | 查找 Java 源码中的 native 方法对应的 C++ 源码 ) 中主要查找 Bitmap.java 对应的 Native 层的 C++ 类 Bitmap.cpp 源码文件 , 并分析了其动态注册 Native 方法的过程 ;



本博客中将分析 Bitmap.cpp 中的源码 ;






一、 图片质量压缩方法


Java 对应方法 :



参数分析 :


long nativeBitmap 参数 : Native 层的 Bitmap 指针 ;
int format 参数 : 压缩格式格式 ;
int quality 参数 : 压缩质量 ;
OutputStream stream 参数 : 输出流 ;
byte[] tempStorage 参数 : 暂时的存储区 ;
private static native boolean nativeCompress(long nativeBitmap, int format,
                                        int quality, OutputStream stream,
                                        byte[] tempStorage);



C++ 对应方法 :


参数分析 :


jlong bitmapHandle 参数 : Native 层的 Bitmap 指针 ;
jint format 参数 : 压缩格式格式 ;
jint quality 参数 : 压缩质量 ;
jobject jstream 参数 : 输出流 ;
jbyteArray jstorage 参数 : 暂时的存储区 ;
static jboolean Bitmap_compress(JNIEnv* env, jobject clazz, jlong bitmapHandle,
                                jint format, jint quality,
                                jobject jstream, jbyteArray jstorage) {
  // 获取 Bitmap 指针
    SkBitmap* bitmap = reinterpret_cast<SkBitmap*>(bitmapHandle);
    SkImageEncoder::Type fm;
  // 判断图片压缩格式 , 给 SkImageEncoder::Type fm 局部变量赋值
    switch (format) {
    // JPEG 格式
    case kJPEG_JavaEncodeFormat:
        fm = SkImageEncoder::kJPEG_Type;
        break;
  // PNG 格式
    case kPNG_JavaEncodeFormat:
        fm = SkImageEncoder::kPNG_Type;
        break;
  // WEBP 格式
    case kWEBP_JavaEncodeFormat:
        fm = SkImageEncoder::kWEBP_Type;
        break;
  // 如果传入未知格式 , 直接返回 错误信息 
    default:
        return JNI_FALSE;
    }
    bool success = false;
    if (NULL != bitmap) {
        SkAutoLockPixels alp(*bitmap);
        if (NULL == bitmap->getPixels()) {
            return JNI_FALSE;
        }
        SkWStream* strm = CreateJavaOutputStreamAdaptor(env, jstream, jstorage);
        if (NULL == strm) {
            return JNI_FALSE;
        }
  // 创建图片编码器 , 需要根据传入的 fm 编码类型创建
        SkImageEncoder* encoder = SkImageEncoder::Create(fm);
        if (NULL != encoder) {
          // ★ 这是图片压缩的核心方法
            success = encoder->encodeStream(strm, *bitmap, quality);
            delete encoder;
        }
        delete strm;
    }
    return success ? JNI_TRUE : JNI_FALSE;
}



源码位置 \frameworks\base\core\jni\android\graphics\Bitmap.cpp


上述 Bitmap.cpp 中的 Bitmap_compress 方法中 , 最终调用的 SkImageEncoder 的 encodeStream 方法 ;


SkImageEncoder 不是最终调用的类 , 而是根据不同的图片压缩格式 , 调用对应的类 , 如果最终压缩格式是 JPEG 格式 , 那么就会调用 SkJPEGImageEncoder 方法 ,



在下面的 SkImageEncoder.h 中声明了 SkImageEncoder 类 , 特别注意下面定义的 virtual bool onEncode 方法 , 是虚函数 , 需要在子类中实现该函数 ;


#ifndef SkImageEncoder_DEFINED
#define SkImageEncoder_DEFINED
#include "SkTypes.h"
#include "SkTRegistry.h"
class SkBitmap;
class SkData;
class SkWStream;
class SkImageEncoder {
public:
    enum Type {
        kUnknown_Type,
        kBMP_Type,
        kGIF_Type,
        kICO_Type,
        kJPEG_Type,
        kPNG_Type,
        kWBMP_Type,
        kWEBP_Type,
        kKTX_Type,
    };
    static SkImageEncoder* Create(Type);
    virtual ~SkImageEncoder();
    /*  Quality ranges from 0..100 */
    enum {
        kDefaultQuality = 80
    };
    SkData* encodeData(const SkBitmap&, int quality);
    bool encodeFile(const char file[], const SkBitmap& bm, int quality);
    bool encodeStream(SkWStream* stream, const SkBitmap& bm, int quality);
    static SkData* EncodeData(const SkBitmap&, Type, int quality);
    static bool EncodeFile(const char file[], const SkBitmap&, Type,
                           int quality);
    static bool EncodeStream(SkWStream*, const SkBitmap&, Type,
                           int quality);
protected:
  // 特别注意 : 该函数是个虚函数 , 需要在子类实现中实现该方法
    virtual bool onEncode(SkWStream* stream, const SkBitmap& bm, int quality) = 0;
};



源码位置 \external\skia\include\core\SkImageEncoder.h



在 SkImageEncoder.cpp 中实现了上述方法 , 其中压缩文件的方法 SkImageEncoder::encodeStream , 在该方法中调用了 onEncode 方法 , 该函数是虚函数 , 需要在子类冲实现 ;


#include "SkImageEncoder.h"
#include "SkBitmap.h"
#include "SkStream.h"
#include "SkTemplates.h"
SkImageEncoder::~SkImageEncoder() {}
// 在该方法中调用了 onEncode 虚函数方法 , 该方法需要在子类中实现 
bool SkImageEncoder::encodeStream(SkWStream* stream, const SkBitmap& bm,
                                  int quality) {
    quality = SkMin32(100, SkMax32(0, quality));
    return this->onEncode(stream, bm, quality);
}
// ... 省略部分代码


源码位置 \external\skia\src\images\SkImageEncoder.cpp



下面的 SkJPEGImageEncoder 类是 SkImageEncoder 子类 , 该类主要处理 JPEG 格式编码操作 ; 在重写的 onEncode 方法中 , 主要使用 libjpeg 函数库实现 JPEG 图像编码 ;


// SkJPEGImageEncoder 是 SkImageEncoder 子类 , 共有继承 
class SkJPEGImageEncoder : public SkImageEncoder {
protected:
  // 该方法中调用了大量 libjpeg 库的函数
    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;
    }
};



源码位置 \external\skia\src\images\SkImageDecoder_libjpeg.cpp






二、 Skia 二维图形库


Skia 是 C++ 开源二维图形库 , 用于操作二维图形 , 提供一系列 2D 图形处理 API , 在 Chrom 浏览器 , 安卓手机 , 狐火浏览器中使用该图形库作为二维图形引擎 ;



Skia 相关网址 :


官方网站 , 国内无法访问 ;

源码地址 , 国内无法访问 ;

GitHub 源码镜像





三、 libjpeg、libpng 函数库引入


libjpeg、libpng 函数库引入 : Android 中的 Bitmap 就使用到了 Skia 引擎 , Android 中的 Skia 功能不全 , 经过删减了 ;


处理 JPEG 格式图像基于 libjpeg 函数库 ;


处理 PNG 格式图形基于 libpng 函数库 ;


目录
相关文章
|
存储 缓存 安全
Java内存模型深度解析:从理论到实践####
【10月更文挑战第21天】 本文深入探讨了Java内存模型(JMM)的核心概念与底层机制,通过剖析其设计原理、内存可见性问题及其解决方案,结合具体代码示例,帮助读者构建对JMM的全面理解。不同于传统的摘要概述,我们将直接以故事化手法引入,让读者在轻松的情境中领略JMM的精髓。 ####
273 6
|
存储 Java 编译器
Java内存模型(JMM)深度解析####
本文深入探讨了Java内存模型(JMM)的工作原理,旨在帮助开发者理解多线程环境下并发编程的挑战与解决方案。通过剖析JVM如何管理线程间的数据可见性、原子性和有序性问题,本文将揭示synchronized关键字背后的机制,并介绍volatile关键字和final关键字在保证变量同步与不可变性方面的作用。同时,文章还将讨论现代Java并发工具类如java.util.concurrent包中的核心组件,以及它们如何简化高效并发程序的设计。无论你是初学者还是有经验的开发者,本文都将为你提供宝贵的见解,助你在Java并发编程领域更进一步。 ####
|
XML Android开发 数据格式
Android利用selector(选择器)实现图片动态点击效果
本文介绍了Android中ImageView的`src`与`background`属性的区别及应用,重点讲解如何通过设置背景选择器实现图片点击动态效果。`src`用于显示原图大小,不拉伸;`background`可随组件尺寸拉伸。通过创建`selector_setting.xml`,结合`setting_press.xml`和`setting_normal.xml`定义按下和正常状态的背景样式,提升用户体验。示例代码展示了具体实现步骤,包括XML配置和形状定义。
631 3
Android利用selector(选择器)实现图片动态点击效果
|
Java Android开发
Android图片的手动放大缩小
本文介绍了通过缩放因子实现图片放大缩小的功能,效果如动图所示。关键步骤包括:1) 在布局文件中设置 `android:scaleType=&quot;matrix&quot;`;2) 实例化控件并用 `ScaleGestureDetector` 处理缩放手势;3) 使用 `Matrix` 对图片进行缩放处理。为避免内存崩溃,可在全局配置添加 `android:largeHeap=&quot;true&quot;`。代码中定义了 `beforeScale` 和 `nowScale` 变量控制缩放范围,确保流畅体验。
466 8
|
缓存 编解码 Android开发
Android内存优化之图片优化
本文主要探讨Android开发中的图片优化问题,包括图片优化的重要性、OOM错误的成因及解决方法、Android支持的图片格式及其特点。同时介绍了图片储存优化的三种方式:尺寸优化、质量压缩和内存重用,并详细讲解了相关的实现方法与属性。此外,还分析了图片加载优化策略,如异步加载、缓存机制、懒加载等,并结合多级缓存流程提升性能。最后对比了几大主流图片加载框架(Universal ImageLoader、Picasso、Glide、Fresco)的特点与适用场景,重点推荐Fresco在处理大图、动图时的优异表现。这些内容为开发者提供了全面的图片优化解决方案。
553 1
|
数据采集 JavaScript Android开发
【02】仿站技术之python技术,看完学会再也不用去购买收费工具了-本次找了小影-感觉页面很好看-本次是爬取vue需要用到Puppeteer库用node.js扒一个app下载落地页-包括安卓android下载(简单)-ios苹果plist下载(稍微麻烦一丢丢)-优雅草卓伊凡
【02】仿站技术之python技术,看完学会再也不用去购买收费工具了-本次找了小影-感觉页面很好看-本次是爬取vue需要用到Puppeteer库用node.js扒一个app下载落地页-包括安卓android下载(简单)-ios苹果plist下载(稍微麻烦一丢丢)-优雅草卓伊凡
740 7
【02】仿站技术之python技术,看完学会再也不用去购买收费工具了-本次找了小影-感觉页面很好看-本次是爬取vue需要用到Puppeteer库用node.js扒一个app下载落地页-包括安卓android下载(简单)-ios苹果plist下载(稍微麻烦一丢丢)-优雅草卓伊凡
|
存储 算法 Java
Java内存管理深度解析####
本文深入探讨了Java虚拟机(JVM)中的内存分配与垃圾回收机制,揭示了其高效管理内存的奥秘。文章首先概述了JVM内存模型,随后详细阐述了堆、栈、方法区等关键区域的作用及管理策略。在垃圾回收部分,重点介绍了标记-清除、复制算法、标记-整理等多种回收算法的工作原理及其适用场景,并通过实际案例分析了不同GC策略对应用性能的影响。对于开发者而言,理解这些原理有助于编写出更加高效、稳定的Java应用程序。 ####
|
安全 Java 网络安全
Android远程连接和登录FTPS服务代码(commons.net库)
Android远程连接和登录FTPS服务代码(commons.net库)
535 1
|
存储 监控 算法
Java中的内存管理与垃圾回收机制解析
本文深入探讨了Java编程语言中的内存管理方式,特别是垃圾回收机制。我们将了解Java的自动内存管理是如何工作的,它如何帮助开发者避免常见的内存泄漏问题。通过分析不同垃圾回收算法(如标记-清除、复制和标记-整理)以及JVM如何选择合适的垃圾回收策略,本文旨在帮助Java开发者更好地理解和优化应用程序的性能。

热门文章

最新文章

推荐镜像

更多