Bitmap优化

网络的优化

1.请求的缓存。
Http请求是可以做缓存的。

三级缓存
内存缓存、外部缓存
算法LruCache
+自定的 一周过期等等。
Sqlite缓存+加密


BitmapFactory.Options:参数:
inDensity:bitmap的像素密度
inTargetDensity:bitmap最终的像素密度
DensityDpi(屏幕的像素密度) 分辨率 Density
160dpi 320x533 1
px = dp*Density

手机像素表

图片存在的几种形式:

  • File
  • 流的形式
  • Bitmap的形式---内存

图片压缩方式

1.质量压缩

AB
CD
压缩后
AA
AA
原理:通过算法抠掉(同化)了图片中的一些某个些点附近相近的像素,达到降低质量介绍文件大小的目的。
减小了图片质量
** 注意:*它其实只能实现对file的影响,对加载这个图片出来的bitmap内存是无法节省的,还是那么大。 因为bitmap在内存中的大小是按照像素计算的,也就是widthheight,对于质量压缩,并不会改变图片的真实的像素(像素大小不会变)。

** 使用场景:**
将图片压缩后保存到本地,或者将图片上传到服务器。根据实际需求来。

public static void compressImageToFile(Bitmap bmp,File file){
    //0~100
    int quality = 50;
    ByteArrayOutputStream baos = new ByteArrayOutputStream();
    bmp.compress(Bitmap.CompressFormat.JPEG, quality , baos );
    try {
        FileOutputStream fos = new FileOutputStream(file);
        fos.write(baos.toByteArray());
        fos.flush();
        fos.close();
    } catch (Exception e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
    }
}
2.尺寸压缩

通过减少单位尺寸的像素值,真正意义上的降低像素。1020*8880

** 使用场景:** 缓存缩略图的时候(头像处理)

public static void compressBitmapToFileBySize(Bitmap bmp,File file){
    //压缩尺寸倍数,值越大,图片的尺寸就越小
    int ratio = 4;
    Bitmap result = Bitmap.createBitmap(bmp.getWidth()/ratio, 
              bmp.getHeight()/ratio, Bitmap.Config.ARGB_8888);
    
    Canvas canvas = new Canvas(result);
    RectF rect = new RectF(0, 0, bmp.getWidth()/ratio, bmp.getHeight()/ratio);
    canvas.drawBitmap(bmp, null, rect , null);
    
    ByteArrayOutputStream baos = new ByteArrayOutputStream();
    result.compress(Bitmap.CompressFormat.JPEG, 100, baos);
    try {
        FileOutputStream fos = new FileOutputStream(file);
        fos.write(baos.toByteArray());
        fos.flush();
        fos.close();
    } catch (Exception e) {
        e.printStackTrace();
    }
    
}
3.采样率压缩
4.终极压缩(NDK层压缩)

1.IOS拍照1M的图片要比安卓拍照排出来的5M的图片还要清晰。
都是在同一个环境下,保存的都是JPEG。为什么?

2.图像处理引擎
95年 JPEG处理引擎,用于最初的在PC上面处理图片的引擎。
05年 skia开源的引擎, 开发了一套基于JPEG处理引擎的第二次开发。便于浏览器的使用。

07年, 安卓上面用的什么引擎
skia引擎,阉割版
谷歌拿了skia 思考了半天做了一个决定,去掉一个编码算法---哈夫曼算法。采用定长编码算法。但是解码还是保留了哈夫曼算法。导致了图片处理后文件变大了。

理由:当时由于CPU和内存在手机上都非常吃紧 性能差,而哈夫曼算法非常吃CPU,被迫用了其他的算法。

我们的优化:
绕过安卓Bitmap API层,来自己编码实现----修复使用哈夫曼算法。

argb
一个像素点包涵四个信息:alpha,red,green,blue

a b c d e

abcde acdbe bacde ……

101010100011100
a:001
b:010
c:011
d:100
e:101
用3位来表示一个字符信息,属于定长编码的最优。

abcde
001 010 011 100 101
加权信息编码

a:80%
b:10%
c:10%
d:0%
e:0%
这种情况,编码就可以优化了
a:01
b:10
c:11
优化后的abc:01 10 11
优化前的abc:001 010 011

通过加权可以知道那个信息为0(即没有),那么就表示可以省略“一位”,这样数据量信息就会减少,从而使图片变得更小。

那么如何得到每一个字母出现的权重?
哈夫曼编码:需要去扫描每个信息(图片信息--每一个像素包括ARGB),需要大量计算,很吃CPU。
1280800像素4

如何实现?

下载JPEG引擎使用的库---libjpeg库
http://www.ijg.org/

基于该引擎来做一定的开发----自己实现编码。

1.导入库文件libjpegbither.so
2.导入头文件
3.写mk文件
Android.mk
Applicatoin.mk
4.写代码
C++: XX.cpp
C: XX.c

操作步骤:
1.将android的bitmap解码,并转换成RGB数据,一个图片信息---像素点(argb),把alpha去掉。
2.JPEG对象分配空间以及初始化
3.指定压缩数据源
4.获取文件信息
5.为压缩设置参数,比如图像大小、类型、颜色空间
boolean arith_code; /* TRUE=arithmetic coding, FALSE=Huffman */
6.开始压缩
jpeg_start_compress()
7.压缩结束
jpeg_finish_compress()
8.释放资源


/*
 * Copyright 2014 http://Bither.net
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package net.bither.util;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileOutputStream;

import android.graphics.Bitmap;
import android.graphics.Bitmap.Config;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Rect;
import android.util.Log;

public class NativeUtil {
     private static int DEFAULT_QUALITY = 95;

        /**
         * @param bit      bitmap对象
         * @param fileName 指定保存目录名
         * @param optimize 是否采用哈弗曼表数据计算 品质相差5-10倍
         * @Description: JNI基本压缩
         */
        public static void compressBitmap(Bitmap bit, String fileName, boolean optimize) {
            saveBitmap(bit, DEFAULT_QUALITY, fileName, optimize);
        }

        /**
         * @param image    bitmap对象
         * @param filePath 要保存的指定目录
         * @Description: 通过JNI图片压缩把Bitmap保存到指定目录
         */
        public static void compressBitmap(Bitmap image, String filePath) {
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            // 质量压缩方法,这里100表示不压缩,把压缩后的数据存放到baos中
            int options = 20;
            // JNI调用保存图片到SD卡 这个关键
            NativeUtil.saveBitmap(image, options, filePath, true);
        }

        /**
         * 计算缩放比
         *
         * @param bitWidth  当前图片宽度
         * @param bitHeight 当前图片高度
         * @return
         * @Description:函数描述
         */
        public static int getRatioSize(int bitWidth, int bitHeight) {
            // 图片最大分辨率
            int imageHeight = 1920;
            int imageWidth = 1080;
            // 缩放比
            int ratio = 1;
            // 缩放比,由于是固定比例缩放,只用高或者宽其中一个数据进行计算即可
            if (bitWidth > bitHeight && bitWidth > imageWidth) {
                // 如果图片宽度比高度大,以宽度为基准
                ratio = bitWidth / imageHeight;
            } else if (bitWidth < bitHeight && bitHeight > imageHeight) {
                // 如果图片高度比宽度大,以高度为基准
                ratio = bitHeight / imageHeight;
            }
            // 最小比率为1
            if (ratio <= 0)
                ratio = 1;
            return ratio;
        }

        /**
         * 调用native方法
         *
         * @param bit
         * @param quality
         * @param fileName
         * @param optimize
         * @Description:函数描述
         */
        public static void saveBitmap(Bitmap bit, int quality, String fileName, boolean optimize) {
            compressBitmap(bit, bit.getWidth(), bit.getHeight(), quality, fileName.getBytes(), optimize);
        }

        /**
         * 调用底层 bitherlibjni.c中的方法
         *
         * @param bit
         * @param w
         * @param h
         * @param quality
         * @param fileNameBytes
         * @param optimize
         * @return
         * @Description:函数描述
         */
        public static native String compressBitmap(Bitmap bit, int w, int h, int quality, byte[] fileNameBytes,
                                                    boolean optimize);

        /**
         * 加载lib下两个so文件
         */
        static {
            System.loadLibrary("jpegbither");
            System.loadLibrary("bitherjni");
        }


        /**
         * 1. 质量压缩
                 设置bitmap options属性,降低图片的质量,像素不会减少
                 第一个参数为需要压缩的bitmap图片对象,第二个参数为压缩后图片保存的位置
                 设置options 属性0-100,来实现压缩
         * @param bmp
         * @param file
         */
        public static void compressImageToFile(Bitmap bmp,File file) {
            // 0-100 100为不压缩
            int options = 20;
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            // 把压缩后的数据存放到baos中
            bmp.compress(Bitmap.CompressFormat.JPEG, options, baos);
            try {
                FileOutputStream fos = new FileOutputStream(file);
                fos.write(baos.toByteArray());
                fos.flush();
                fos.close();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }

        /**
         *
         * 2. 尺寸压缩
         通过缩放图片像素来减少图片占用内存大小
         * @param bmp
         * @param file
         */

        public static void compressBitmapToFile(Bitmap bmp, File file){
            // 尺寸压缩倍数,值越大,图片尺寸越小
            int ratio = 8;
            // 压缩Bitmap到对应尺寸
            Bitmap result = Bitmap.createBitmap(bmp.getWidth() / ratio, bmp.getHeight() / ratio, Config.ARGB_8888);
            Canvas canvas = new Canvas(result);
            Rect rect = new Rect(0, 0, bmp.getWidth() / ratio, bmp.getHeight() / ratio);
            canvas.drawBitmap(bmp, null, rect, null);

            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            // 把压缩后的数据存放到baos中
            result.compress(Bitmap.CompressFormat.JPEG, 100 ,baos);
            try {
                FileOutputStream fos = new FileOutputStream(file);
                fos.write(baos.toByteArray());
                fos.flush();
                fos.close();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }


        /**
         * 设置图片的采样率,降低图片像素
         * @param filePath
         * @param file
         */
        public static void compressBitmap(String filePath, File file){
            // 数值越高,图片像素越低
            int inSampleSize = 8;
            BitmapFactory.Options options = new BitmapFactory.Options();
            options.inJustDecodeBounds = false;
//          options.inJustDecodeBounds = true;//为true的时候不会真正加载图片,而是得到图片的宽高信息。
            //采样率
            options.inSampleSize = inSampleSize;
            Bitmap bitmap = BitmapFactory.decodeFile(filePath, options);

            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            // 把压缩后的数据存放到baos中
            bitmap.compress(Bitmap.CompressFormat.JPEG, 100 ,baos);
            try {
                if(file.exists())
                {
                    file.delete();
                }
                else {
                    file.createNewFile();
                }
                FileOutputStream fos = new FileOutputStream(file);
                fos.write(baos.toByteArray());
                fos.flush();
                fos.close();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
}

#include "bitherlibjni.h"
#include <string.h>
#include <android/bitmap.h>
#include <android/log.h>
#include <stdio.h>
#include <setjmp.h>
#include <math.h>
#include <stdint.h>
#include <time.h>

//统一编译方式
extern "C" {
#include "jpeg/jpeglib.h"
#include "jpeg/cdjpeg.h"        /* Common decls for cjpeg/djpeg applications */
#include "jpeg/jversion.h"      /* for version message */
#include "jpeg/android/config.h"
}


#define LOG_TAG "jni"
#define LOGW(...)  __android_log_write(ANDROID_LOG_WARN,LOG_TAG,__VA_ARGS__)
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO,LOG_TAG,__VA_ARGS__)
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR,LOG_TAG,__VA_ARGS__)

#define true 1
#define false 0

typedef uint8_t BYTE;

char *error;
struct my_error_mgr {
  struct jpeg_error_mgr pub;
  jmp_buf setjmp_buffer;
};

typedef struct my_error_mgr * my_error_ptr;

METHODDEF(void)
my_error_exit (j_common_ptr cinfo)
{
  my_error_ptr myerr = (my_error_ptr) cinfo->err;
  (*cinfo->err->output_message) (cinfo);
  error=(char*)myerr->pub.jpeg_message_table[myerr->pub.msg_code];
  LOGE("jpeg_message_table[%d]:%s", myerr->pub.msg_code,myerr->pub.jpeg_message_table[myerr->pub.msg_code]);
 // LOGE("addon_message_table:%s", myerr->pub.addon_message_table);
//  LOGE("SIZEOF:%d",myerr->pub.msg_parm.i[0]);
//  LOGE("sizeof:%d",myerr->pub.msg_parm.i[1]);
  longjmp(myerr->setjmp_buffer, 1);
}

int generateJPEG(BYTE* data, int w, int h, int quality,
        const char* outfilename, jboolean optimize) {

    //jpeg的结构体,保存的比如宽、高、位深、图片格式等信息,相当于java的类
    struct jpeg_compress_struct jcs;

    //当读完整个文件的时候就会回调my_error_exit这个退出方法。setjmp是一个系统级函数,是一个回调。
    struct my_error_mgr jem;
    jcs.err = jpeg_std_error(&jem.pub);
    jem.pub.error_exit = my_error_exit;
    if (setjmp(jem.setjmp_buffer)) {
        return 0;
    }

    //初始化jsc结构体
    jpeg_create_compress(&jcs);
    //打开输出文件 wb:可写byte
    FILE* f = fopen(outfilename, "wb");
    if (f == NULL) {
        return 0;
    }
    //设置结构体的文件路径
    jpeg_stdio_dest(&jcs, f);
    jcs.image_width = w;//设置宽高
    jcs.image_height = h;
//  if (optimize) {
//      LOGI("optimize==ture");
//  } else {
//      LOGI("optimize==false");
//  }

    //看源码注释,设置哈夫曼编码:/* TRUE=arithmetic coding, FALSE=Huffman */
    jcs.arith_code = false;
    int nComponent = 3;
    /* 颜色的组成 rgb,三个 # of color components in input image */
    jcs.input_components = nComponent;
    //设置结构体的颜色空间为rgb
    jcs.in_color_space = JCS_RGB;
//  if (nComponent == 1)
//      jcs.in_color_space = JCS_GRAYSCALE;
//  else
//      jcs.in_color_space = JCS_RGB;

    //全部设置默认参数/* Default parameter setup for compression */
    jpeg_set_defaults(&jcs);
    //是否采用哈弗曼表数据计算 品质相差5-10倍
    jcs.optimize_coding = optimize;
    //设置质量
    jpeg_set_quality(&jcs, quality, true);
    //开始压缩,(是否写入全部像素)
    jpeg_start_compress(&jcs, TRUE);

    JSAMPROW row_pointer[1];
    int row_stride;
    //一行的rgb数量
    row_stride = jcs.image_width * nComponent;
    //一行一行遍历
    while (jcs.next_scanline < jcs.image_height) {
        //得到一行的首地址
        row_pointer[0] = &data[jcs.next_scanline * row_stride];

        //此方法会将jcs.next_scanline加1
        jpeg_write_scanlines(&jcs, row_pointer, 1);//row_pointer就是一行的首地址,1:写入的行数
    }
    jpeg_finish_compress(&jcs);//结束
    jpeg_destroy_compress(&jcs);//销毁 回收内存
    fclose(f);//关闭文件

    return 1;
}

/**
 * byte数组转C的字符串
 */
char* jstrinTostring(JNIEnv* env, jbyteArray barr) {
    char* rtn = NULL;
    jsize alen = env->GetArrayLength( barr);
    jbyte* ba = env->GetByteArrayElements( barr, 0);
    if (alen > 0) {
        rtn = (char*) malloc(alen + 1);
        memcpy(rtn, ba, alen);
        rtn[alen] = 0;
    }
    env->ReleaseByteArrayElements( barr, ba, 0);
    return rtn;
}

jstring Java_net_bither_util_NativeUtil_compressBitmap(JNIEnv* env,
        jclass thiz, jobject bitmapcolor, int w, int h, int quality,
        jbyteArray fileNameStr, jboolean optimize) {
    BYTE *pixelscolor;
    //1.将bitmap里面的所有像素信息读取出来,并转换成RGB数据,保存到二维byte数组里面
    //处理bitmap图形信息方法1 锁定画布
    AndroidBitmap_lockPixels(env,bitmapcolor,(void**)&pixelscolor);

    //2.解析每一个像素点里面的rgb值(去掉alpha值),保存到一维数组data里面
    BYTE *data;
    BYTE r,g,b;
    data = (BYTE*)malloc(w*h*3);//每一个像素都有三个信息RGB
    BYTE *tmpdata;
    tmpdata = data;//临时保存data的首地址
    int i=0,j=0;
    int color;
    for (i = 0; i < h; ++i) {
        for (j = 0; j < w; ++j) {
            //解决掉alpha
            //获取二维数组的每一个像素信息(四个部分a/r/g/b)的首地址
            color = *((int *)pixelscolor);//通过地址取值
            //0~255:
//          a = ((color & 0xFF000000) >> 24);
            r = ((color & 0x00FF0000) >> 16);
            g = ((color & 0x0000FF00) >> 8);
            b = ((color & 0x000000FF));
            //改值!!!----保存到data数据里面
            *data = b;
            *(data+1) = g;
            *(data+2) = r;
            data = data + 3;
            //一个像素包括argb四个值,每+4就是取下一个像素点
            pixelscolor += 4;
        }
    }
    //处理bitmap图形信息方法2 解锁
    AndroidBitmap_unlockPixels(env,bitmapcolor);
    char* fileName = jstrinTostring(env,fileNameStr);
    //调用libjpeg核心方法实现压缩
    int resultCode = generateJPEG(tmpdata,w,h,quality,fileName,optimize);
    if(resultCode ==0){
        jstring result = env->NewStringUTF("-1");
        return result;
    }
    return env->NewStringUTF("1");
}

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,937评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,503评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,712评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,668评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,677评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,601评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,975评论 3 396
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,637评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,881评论 1 298
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,621评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,710评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,387评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,971评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,947评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,189评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,805评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,449评论 2 342

推荐阅读更多精彩内容