圖片壓縮之libjpeg-turbo

前言

安卓不是自帶圖片壓縮嗎官地,為什么要使用libjpeg-turbo呢?因為相比IOS烙懦,安卓自帶的圖片壓縮驱入,效率低,質(zhì)量差氯析。

安卓底層使用Skia作為它的圖片處理引擎亏较,通過對libjpeg進行封裝,來進行壓縮圖片掩缓。然而在libjpeg進行壓縮圖片時雪情,有一個參數(shù),叫optimize_coding你辣,默認為FALSE巡通。關于這個參數(shù)尘执,官方的解釋是,如果設置為TRUE宴凉,在壓縮過程中誊锭,會計算哈夫曼表,這會顯著消耗空間和時間弥锄。

然而這是對于十幾年前的設備而言的丧靡,現(xiàn)在的設備來做這個計算完全沒有壓力。因此籽暇,只有在安卓7.0之后温治,才支持了哈夫曼算法。

libjpeg-turbo則是libjpeg的一個優(yōu)化升級版图仓。速度通常是libjpeg的2到6倍罐盔。

1. 環(huán)境搭建

Linux下下載libjpeg-turbo庫并解壓

wget https://github.com/libjpeg-turbo/libjpeg-turbo/archive/2.0.2.tar.gz
tar -xvf 2.0.2.tar.gz

下載CMake(v2.8.12 or later)并安裝

  1. 如果之前有安裝過CMake并且版本不符合要求,先卸載之前的CMake

    apt-get autoremove cmake
    
  2. 下載

    wget https://github.com/Kitware/CMake/releases/download/v3.14.3/cmake-3.14.3.tar.gz
    
  3. 解壓

    tar -xvf cmake-3.14.3.tar.gz
    
  4. 安裝

    cd cmake-3.14.3/
    ./bootstrap
    

安裝過程中如果遇到,表示沒有安裝C++的編譯器

---------------------------------------------
CMake 3.10.0, Copyright 2000-2017 Kitware, Inc. and Contributors
C compiler on this system is: cc  
---------------------------------------------
Error when bootstrapping CMake:
Cannot find a C++ compiler supporting C++11 on this system.
Please specify one using environment variable CXX.
See cmake_bootstrap.log for compilers attempted.
---------------------------------------------
Log of errors: /home/javascript/下載/cmake-3.10.0/Bootstrap.cmk/cmake_bootstrap.log
---------------------------------------------

安裝C++編譯器

ubuntu: apt-get install gcc g++
CentOS:yum install gcc gcc-c++

執(zhí)行完bootstrap之后,運行以下命令

gmake
make install
  1. 檢查版本信息

    cmake --version
    

    如果能夠看到版本信息,則說明CMake安裝成功

安裝NASM

如果需要在x86或x86平臺上編譯,則需要

wget https://www.nasm.us/pub/nasm/releasebuilds/2.14.02/nasm-2.14.02.tar.gz
tar -xvf nasm-2.14.02.tar.gz 
cd nasm-2.14.02/
./configure

安裝NDK,建議安裝R19C版本

wget https://dl.google.com/android/repository/android-ndk-r19c-linux-x86_64.zip
unzip android-ndk-r19c-linux-x86_64.zip

編譯(x86平臺)

其他平臺可以參考https://github.com/libjpeg-turbo/libjpeg-turbo/blob/master/BUILDING.md

進入libjpeg-turbo-2.0.2目錄,創(chuàng)建一個shell腳本

vim build.sh

然后在創(chuàng)建的build.sh中輸入

#!/bin/bash

# Set these variables to suit your needs
NDK_PATH={full path to the NDK directory-- for example,
  /opt/android/android-ndk-r16b}
TOOLCHAIN={"gcc" or "clang"-- "gcc" must be used with NDK r14b and earlier,
  and "clang" must be used with NDK r17c and later}
ANDROID_VERSION={the minimum version of Android to support.  "21" or later
  is required for a 64-bit build.}

cd {build_directory}
cmake -G"Unix Makefiles" \
  -DANDROID_ABI=x86 \
  -DANDROID_PLATFORM=android-${ANDROID_VERSION} \
  -DANDROID_TOOLCHAIN=${TOOLCHAIN} \
  -DCMAKE_TOOLCHAIN_FILE=${NDK_PATH}/build/cmake/android.toolchain.cmake \
  [additional CMake flags] {source_directory}
make

最后修改腳本為

#!/bin/bash

# Set these variables to suit your needs
NDK_PATH=./../android-ndk-r19c
TOOLCHAIN="clang"
ANDROID_VERSION=21

cmake -G"Unix Makefiles" \
  -DANDROID_ABI=x86 \
  -DANDROID_PLATFORM=android-${ANDROID_VERSION} \
  -DANDROID_TOOLCHAIN=${TOOLCHAIN} \
  -DCMAKE_TOOLCHAIN_FILE=${NDK_PATH}/build/cmake/android.toolchain.cmake \

make

保存后,將build.sh添加可執(zhí)行權限,并執(zhí)行

chmod +x build.sh
./build.sh

最后以下將生成的文件拷貝下來

image

2. AS開發(fā)

創(chuàng)建項目

創(chuàng)建一個project并支持C/C++

將剛才的.h文件和.a文件導入到項目中,如圖所示

image

修改model的gradle文件,設置編譯平臺和編譯器

android {
    ...
    ...
    
    defaultConfig {
        ...
        ...
        externalNativeBuild {
            cmake {
                cppFlags ""
                // 指定編譯平臺
                abiFilters "x86"
                // 指定Android編譯器
                arguments "-DANDROID_TOOLCHAIN=clang"
            }
        }
    }
    
    ...
    ...
}

修改CMakeLists.txt文件

cmake_minimum_required(VERSION 3.4.1)

add_library(
        native-lib
        SHARED
        native-lib.cpp)

# 這里的libjpeg名字是可以隨便取的,下面會和libturbojpeg.a綁定
add_library(libjpeg STATIC IMPORTED)
set_target_properties(libjpeg PROPERTIES IMPORTED_LOCATION ${CMAKE_SOURCE_DIR}/libs/libturbojpeg.a)

# 引入頭文件,這里要寫相對路徑
include_directories(./include_image_compress)

target_link_libraries(
        native-lib
        libjpeg
        # jnigraphics是Android NDK目錄中直接有的
        jnigraphics
        log)

最后進行編譯,如果編譯失敗,請確保AS中ndk版本是否為r19c

編寫代碼

在Acitivity下聲明Native方法

public native String nativeImageCompress(Bitmap bitmap, int quality, String outPutPath);

在native-lib.cpp下生成對應的方法

#include <jni.h>
#include <string>
#include <android/bitmap.h>
#include <malloc.h>
#include <jpeglib.h>

void writeJpegToFile(uint8_t *temp, int width, int height, jint quality, const char *outPutPath) {
//    3.1救崔、創(chuàng)建jpeg壓縮對象
    jpeg_compress_struct jcs;
    //錯誤回調(diào)
    jpeg_error_mgr error;
    jcs.err = jpeg_std_error(&error);
    //創(chuàng)建壓縮對象
    jpeg_create_compress(&jcs);
//    3.2惶看、指定存儲文件  write binary
    FILE *f = fopen(outPutPath, "wb");
    jpeg_stdio_dest(&jcs, f);
//    3.3、設置壓縮參數(shù)
    jcs.image_width = width;
    jcs.image_height = height;
    //bgr
    jcs.input_components = 3;
    jcs.in_color_space = JCS_RGB;
    jpeg_set_defaults(&jcs);
    //開啟哈夫曼功能
    jcs.optimize_coding = true;
    jpeg_set_quality(&jcs, quality, 1);
//    3.4六孵、開始壓縮
    jpeg_start_compress(&jcs, 1);
//    3.5纬黎、循環(huán)寫入每一行數(shù)據(jù)
    int row_stride = width * 3;//一行的字節(jié)數(shù)
    JSAMPROW row[1];
    while (jcs.next_scanline < jcs.image_height) {
        //取一行數(shù)據(jù)
        uint8_t *pixels = temp + jcs.next_scanline * row_stride;
        row[0] = pixels;
        jpeg_write_scanlines(&jcs, row, 1);
    }
//    3.6、壓縮完成
    jpeg_finish_compress(&jcs);
//    3.7劫窒、釋放jpeg對象
    fclose(f);
    jpeg_destroy_compress(&jcs);
}

extern "C"
JNIEXPORT void JNICALL
Java_com_qzb_imagecompressiondemo_MainActivity_nativeImageCompress(JNIEnv *env, jobject instance, jobject bitmap,
                                                                   jint quality, jstring outPutPath_) {
    const char *outPutPath = env->GetStringUTFChars(outPutPath_, 0);

    // 從bitmap中獲取argb數(shù)據(jù)
    AndroidBitmapInfo info;
    // 獲取里面的信息
    AndroidBitmap_getInfo(env, bitmap, &info);
    // 得到圖片中的像素信息
    uint8_t *pixels;// 相當于byte數(shù)組  *pixels === byte[]
    AndroidBitmap_lockPixels(env, bitmap, (void **) &pixels);
    // jpge argb去掉他的a ===> rgb
    // 開一塊內(nèi)存用來存儲rgb信息
    uint8_t *data = (uint8_t *) malloc(info.width * info.height * 3);// 可以用來存放圖片所有信息(w*h為所有像素,每個像素有r,g,b三種顏色)
    uint8_t *temp = data;
    uint8_t r, g, b;
    int color;
    for (int i = 0; i < info.height; ++i) {
        for (int j = 0; j < info.width; ++j) {
            color = *(int *) pixels; // color 4個字節(jié),argb
            r = (color >> 16) & 0xFF;
            g = (color >> 8) & 0xFF;
            b = color & 0xFF;
            // 存放顏色,jpeg的格式為bgr,倒過來存
            *data = b;
            *(data + 1) = g;
            *(data + 2) = r;
            data += 3;// 這里就去除了alpha通道
            pixels += 4;// 每個像素有argb4個通道,其中alpha通道被我們丟棄掉了,沒有存到data里面
        }
    }
    //把得到的新的圖片的信息存入一個新文件 中
    writeJpegToFile(temp, info.width, info.height, quality, outPutPath);

    free(data);// data是通過malloc開辟的,這里需要釋放掉
    AndroidBitmap_unlockPixels(env, bitmap);

    env->ReleaseStringUTFChars(outPutPath_, outPutPath);
}

測試效果

package com.qzb.imagecompressiondemo;

import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.Bundle;
import android.os.Environment;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
import android.widget.Toast;

import java.io.File;

public class MainActivity extends AppCompatActivity {

    static {
        System.loadLibrary("native-lib");
    }

    public native void nativeImageCompress(Bitmap bitmap, int quality, String path);


    Bitmap inputBitmap = null;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        File input = new File(Environment.getExternalStorageDirectory(), "girl.jpg");
        inputBitmap = BitmapFactory.decodeFile(input.getAbsolutePath());

        findViewById(R.id.btn_compress).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                nativeImageCompress(inputBitmap, 100, Environment.getExternalStorageDirectory() + "/girl2.jpg");
                Toast.makeText(MainActivity.this, "執(zhí)行完成", Toast.LENGTH_SHORT).show();
            }
        });
    }
}

效果圖
?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末本今,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子主巍,更是在濱河造成了極大的恐慌冠息,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,042評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件孕索,死亡現(xiàn)場離奇詭異逛艰,居然都是意外死亡,警方通過查閱死者的電腦和手機搞旭,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 89,996評論 2 384
  • 文/潘曉璐 我一進店門散怖,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人肄渗,你說我怎么就攤上這事镇眷。” “怎么了翎嫡?”我有些...
    開封第一講書人閱讀 156,674評論 0 345
  • 文/不壞的土叔 我叫張陵欠动,是天一觀的道長。 經(jīng)常有香客問我惑申,道長,這世上最難降的妖魔是什么弄抬? 我笑而不...
    開封第一講書人閱讀 56,340評論 1 283
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上掸驱,老公的妹妹穿的比我還像新娘。我一直安慰自己肋层,他們只是感情好翎承,可當我...
    茶點故事閱讀 65,404評論 5 384
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著衙伶,像睡著了一般祈坠。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上矢劲,一...
    開封第一講書人閱讀 49,749評論 1 289
  • 那天赦拘,我揣著相機與錄音,去河邊找鬼芬沉。 笑死躺同,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的丸逸。 我是一名探鬼主播蹋艺,決...
    沈念sama閱讀 38,902評論 3 405
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼黄刚!你這毒婦竟也來了捎谨?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 37,662評論 0 266
  • 序言:老撾萬榮一對情侶失蹤憔维,失蹤者是張志新(化名)和其女友劉穎涛救,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體业扒,經(jīng)...
    沈念sama閱讀 44,110評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡检吆,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,451評論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了凶赁。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片咧栗。...
    茶點故事閱讀 38,577評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖虱肄,靈堂內(nèi)的尸體忽然破棺而出致板,到底是詐尸還是另有隱情,我是刑警寧澤咏窿,帶...
    沈念sama閱讀 34,258評論 4 328
  • 正文 年R本政府宣布斟或,位于F島的核電站,受9級特大地震影響集嵌,放射性物質(zhì)發(fā)生泄漏萝挤。R本人自食惡果不足惜御毅,卻給世界環(huán)境...
    茶點故事閱讀 39,848評論 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望怜珍。 院中可真熱鬧端蛆,春花似錦、人聲如沸酥泛。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,726評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽柔袁。三九已至呆躲,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間捶索,已是汗流浹背插掂。 一陣腳步聲響...
    開封第一講書人閱讀 31,952評論 1 264
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留腥例,地道東北人辅甥。 一個月前我還...
    沈念sama閱讀 46,271評論 2 360
  • 正文 我出身青樓,卻偏偏與公主長得像院崇,于是被迫代替她去往敵國和親肆氓。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 43,452評論 2 348

推薦閱讀更多精彩內(nèi)容

  • 先發(fā)一張昨天去看我雷哥演唱會的皂片然后再說正文哈哈底瓣。 簡介 由于工作原因谢揪,boss下達的任務就大概說了對圖片進行壓...
    我叫王菜鳥閱讀 5,210評論 2 16
  • 0. 前言 如果只學理論,不做實踐捐凭,不踩踩坑拨扶,一般很難發(fā)現(xiàn)真正實踐項目中的問題的,也比較難以加深對技術的理解茁肠。所以...
    LouisLau_6d51閱讀 1,943評論 0 7
  • 注:首發(fā)地址 0. 前言 如果只學理論患民,不做實踐,不踩踩坑垦梆,一般很難發(fā)現(xiàn)真正實踐項目中的問題的匹颤,也比較難以加深對技...
    cfanr閱讀 9,468評論 4 50
  • 作為《周易》的“經(jīng)”部分,神奇的八卦托猩、六十四卦和寓理閎深的卦爻辭印蓖,曾給歷代讀者帶來無窮的哲理啟迪,引起人們多方面的...
    落葉他鄉(xiāng)樹_3a2e閱讀 433評論 0 4
  • 感恩昨晚第八期讀書會順利的進行京腥,感恩大家的參與赦肃,做智慧的好人,分享更多的智慧幫助更多的人,昨天晚上以新的模式和大家...
    生活就該甜甜蜜蜜閱讀 164評論 0 0