前言
安卓不是自帶圖片壓縮嗎官地,為什么要使用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)并安裝
-
如果之前有安裝過CMake并且版本不符合要求,先卸載之前的CMake
apt-get autoremove cmake
-
下載
wget https://github.com/Kitware/CMake/releases/download/v3.14.3/cmake-3.14.3.tar.gz
-
解壓
tar -xvf cmake-3.14.3.tar.gz
-
安裝
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
-
檢查版本信息
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
最后以下將生成的文件拷貝下來
2. AS開發(fā)
創(chuàng)建項目
創(chuàng)建一個project并支持C/C++
將剛才的.h文件和.a文件導入到項目中,如圖所示
修改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();
}
});
}
}