For those of you who have used an ios phone, it should be obvious that an ios photo at 1M is sharper than an Android photo at 5M. Why is that?

It’s important to understand how images are handled at the bottom of Android.

When Google was developing Android, it used Skia, an open source library, for image processing, considering that most mobile phones are not equipped with such a high standard. Of course, the base of this library is still the use of JPEG image compression processing. But in order to be able to adapt to low-end mobile phones (low-end here refers to the previous hardware configuration of the phone is not high, CPU and memory on the phone are very tight performance), because Huffman algorithm is very CPU eating, forced to use other algorithms. Therefore, Skia does not calculate the Haffman table based on the image data in the process of image compression in image processing (for the Haffman table in image compression, please refer to the relevant information), but still retains the Haffman algorithm in decoding. This results in larger files after image processing.

When we use wechat to send pictures, we will find that the pictures sent are obviously smaller than the original picture, but the effect seems to be similar. Why is that? What kind of compression has gone through? Next we use this algorithm for image compression:

Imitation of wechat final compression
1. Download the libjpeg library used by the JPEG engine

Libjpeg is a widely used implementation library for JPEG decoding, JPEG encoding, and other JPEG features. It is widely used because it spans many platforms. Such as the Linux platform, JDK, Android, and other libraries such as Tess – Two. Libjpeg library download address;

2. Compile android libjpeg library using library

Libjpegbither. so, libjpegbither. h, libjpegbither. h, libjpegbither. h, libjpegbither. h, libjpegbither. h, libjpegbither. h, libjpegbither. h

3. Import the libjpeg library libjpegbither.so and its header file

Create a new jni folder in your project folder and place the libjpegbither.so and header files you just generated in this folder.

4. New BitmapCompressUtils
  • Write a native method and call c method

    /** * Call the method in the underlying bitherlibjni.c ** @param bit * @param w * @param h * @param quality * @param fileNameBytes * @param Optimize * @return * @description: function Description */public static native String compressBitmap(Bitmap bit, int w, int h, int w, int h) int quality, byte[] fileNameBytes, boolean optimize);Copy the code
  • Import two so files under the library lib

    Static {system.loadLibrary ("jpegbither"); static {system.loadLibrary ("jpegbither"); System.loadLibrary("bitherjni"); }Copy the code
  • Write a method that calls native methods for easy Java layer invocation

    /** * @param image bitmap object * @param filePath to save the specified directory * @description: */public static void compressBitmap(Bitmap image, String filePath) { ByteArrayOutputStream baos = new ByteArrayOutputStream(); Int options = 20; int options = 20; // JNI calls this key nativeUtil. saveBitmap(image, options, filePath, true) to save the image to the SD card; }Copy the code
4. Write bitherjni. CPP
#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 0typedef 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 structure, Struct jpeg_compress_struct JCS; // The exit method my_error_exit is called when the entire file is read. Setjmp is a system-level function that is a callback. 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; }// Initialize the JSC structure jpeg_create_compress(& JCS); // open the output file wb: writable byteFILE* f = fopen(outfilename, "wb"); if (f == NULL) { return 0; }// Set the structure's file path jpeg_stdio_dest(&jcs, f); jcs.image_width = w; // Set the width and height to jcs.image_height = h; // if (optimize) {// LOGI("optimize==ture"); // } else {// LOGI("optimize==false"); Arithmetic coding: /* arithmetic coding =arithmetic coding, FALSE=Huffman */jcs.arith_code = FALSE; int nComponent = 3; # of color components in input image */jcs.input_components = nComponent; // Set the structure's color space to rgbjcs.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); JCS. Optimize_coding = optimize; // Set the quality jpeG_set_quality (& JCS, quality, true); // Start compression, (whether to write all pixels)jpeg_start_compress(& JCS, TRUE); JSAMPROW row_pointer[1]; int row_stride; Row_stride = jcs.image_width * nComponent; While (jcs.next_scanline < jcs.image_height) {row_pointer[0] = &data[jcs.next_scanline * row_stride]; // This method increments JCS. Next_scanline by 1 jpeG_write_scanlines (& JCS, row_pointer, 1); }jpeg_finish_compress(&jcs); //row_pointer is the start address of a row, 1: the number of rows to write. / / end jpeg_destroy_compress (& JCS); // Destroy reclaimed memory fclose(f); // Close file return 1; }/** * 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_BitmapCompressUtils_compressBitmap(JNIEnv* env, jclass thiz, jobject bitmapcolor, int w, int h, int quality, jbyteArray fileNameStr, jboolean optimize) {BYTE *pixelscolor; //1. Read out all pixel information in bitmap, convert it into RGB data, and save it in 2d Byte array AndroidBitmap_lockPixels(env, bitmapColor,(void**)&pixelscolor); //2. Parse the RGB value in each pixel (excluding the alpha value) and save it in the one-dimensional array data BYTE *data; BYTE r,g,b; data = (BYTE*)malloc(w*h*3); RGBBYTE *tmpdata; RGBBYTE *tmpdata; tmpdata = data; Int I =0,j=0; int color; for (i = 0; i < h; ++i) { for (j = 0; j < w; Color = *((int *)pixelscolor); color = *(int *)pixelscolor); // By address //0~255: // a = ((color & 0xFF000000) >> 24); r = ((color & 0x00FF0000) >> 16); g = ((color & 0x0000FF00) >> 8); b = ((color & 0x000000FF)); // Change the value!! *data = b; *(data+1) = g; *(data+2) = r; data = data + 3; // A pixel contains four arGB values, each +4 is the next pixel pixelscolor += 4; AndroidBitmap_unlockPixels(env, bitmapColor); char* fileName = jstrinTostring(env,fileNameStr); / / call libjpeg core method to realize the compression int the resultCode = generateJPEG (tmpdata, w, h, quality, fileName, optimize); if(resultCode ==0){ jstring result = env->NewStringUTF("-1"); return result; }return env->NewStringUTF("1"); }Copy the code

JavanetbitherutilBitmapCompressUtils_compressBitmap is Java calls, the method of image compression, by this method can c code I stopped, annotation to write very clearly;

Now I’m going to talk about a couple of other schemes for image compression;

The quality of compressed

Quality compression, this only reduces the quality of the image, but the pixels do not decrease

[compressor] [@param file] [@param file] [Hint to the compressor] 0-100. 0 meaning compress for small size, 100 meaning compress for max quality. Some * formats, PNG which is lossless, will ignore the quality setting * quality (0-100) 100 */public static void qualityCompressBitmap(Bitmap Bitmap,File File){ByteArrayOutputStream stream =new ByteArrayOutputStream(); int quality=20; Put / / picture compressed data stream in bitmap.com press (bitmap.com pressFormat. JPEG, quality, stream). try { FileOutputStream fileOutputStream=new FileOutputStream(file); Fileoutputstream.write (stream.tobytearray ()); fileOutputStream.flush(); fileOutputStream.close(); } catch (Exception e) { e.printStackTrace(); }}Copy the code

Size of the compression

Size compression, which reduces the memory footprint of images by zooming in and out the pixels, is used for thumbnails, such as those shown in today’s headlines;

/** size compression * @param bitmap image compression * @param ratio compression ratio, the larger the value, */public static void sizeCompressBitmap(Bitmap Bitmap,int ratio, file file){if (ratio<=0){ return; } Bitmap result=Bitmap.createBitmap(bitmap.getWidth()/ratio,bitmap.getHeight()/ratio, Bitmap.Config.ARGB_8888); Canvas canvas =new Canvas(); The Rect the Rect = new the Rect (0, 0, bitmap getWidth ()/thewire, bitmap. GetHeight ()/thewire); canvas.drawBitmap(bitmap,null,rect,null); ByteArrayOutputStream baos = new ByteArrayOutputStream(); / / the compressed data stored in baos result.com press (Bitmap.Com pressFormat. JPEG, 100, baos); try { FileOutputStream fos = new FileOutputStream(file); fos.write(baos.toByteArray()); fos.flush(); fos.close(); } catch (Exception e) { e.printStackTrace(); }}Copy the code

Sampling rate compression

Public static void pixeCompressBitmap(String filePath, String filePath, String filePath, String filePath) Int inSampleSize=8; int inSampleSize=8; BitmapFactory.Options osts=new BitmapFactory.Options(); osts.inSampleSize=inSampleSize; //inJustDecodeBounds set to True does not actually load the image, but instead gets the width and height of the image. osts.inJustDecodeBounds=false; Bitmap bitmap= BitmapFactory.decodeFile(filePath,osts); ByteArrayOutputStream stream =new ByteArrayOutputStream(); bitmap.compress(Bitmap.CompressFormat.JPEG,100,stream); try { if (file.exists()){ file.delete(); }else{ file.createNewFile(); } FileOutputStream fileOutputStream=new FileOutputStream(file); fileOutputStream.write(stream.toByteArray()); fileOutputStream.flush(); fileOutputStream.close(); } catch (Exception e) { e.printStackTrace(); }}Copy the code

Summary: The above schemes for image compression, each has its own advantages and disadvantages, according to the actual scene to choose the scheme for image compression.

I do Android development for many years, later will be updated on android advanced UI,NDK development, performance optimization and other articles, more please pay attention to my wechat public number: thank you!