1. Libjpeg is introduced

LibJPEG library is a powerful JPEG image processing open source library, it supports image data compression to JPEG format and decompression of the original JPEG image, Android system is used for image compression libJPEG library. However, it should be noted that, in order to adapt to the lower version of Android phones, the internal compression algorithm of Android system does not adopt the common Huffman algorithm, because the Huffman algorithm takes more CPU, resulting in Android compression while maintaining high image quality and rich color. The performance of compression is not very good. Based on this, this paper will use the Cmake tool of AS to compile the source code of libjpeg-Turbo, and then write the sampling Huffman algorithm compression interface through JNI/NDK technology to improve the quality of image compression in Android.

Note: Libjpeg-Turbo is an optimized version of the libjpeg library.

1.1 Huffman coding

Haffman coding is an efficient lossless compression coding method proposed by Huffman in 1952 (Note: Compress 20%~90%), the code length of this method is variable, and it constructs the code word with the shortest average length of different prefix completely according to the occurrence probability (frequency) of characters, that is, for the information with high occurrence probability (frequency), the code length is shorter; For the information with high occurrence probability (frequency), the code length is longer. In the application scenario of image compression, the basic method of Haffman coding is to scan the image data first, calculate the probability of the appearance of various pixels, specify unique code words of different lengths according to the probability, and then get a Haffman code table of the image. The encoded image data records the code word of each pixel, and the corresponding relationship between the code word and the actual pixel value is recorded in the code table.

  • Haffman tree

Suppose there are n weights {w1,w2… Wn}, construct a binary tree with N leaf nodes, each leaf node is weighted wk, and the path length of each leaf is LK. Then, the binary tree with the smallest weighted path length WPL=∑(wk* LK) is called the Huffman tree, also known as the optimal binary tree. Here’s an example:

The tree’s weighted path length: WPL=∑(WKLK) = 110+270+315+3*5=210

  • Principle of Haffman algorithm

Assume that the character set to be encoded is {d1,d2… Dn}, the frequency or frequency set of each character in the message is {w1,w2… Wn} to d1, d2,… , DN as leaf node, w1, W2… Wn is used as the weight of the corresponding leaf node to construct a Huffman tree. It is stipulated that the left branch of the Huffman tree represents 0 and the right branch represents 1, so the sequence of 0 and 1 formed by the path branches from the root node to the leaf node is the encoding of the corresponding characters of this node, which is the Haffman encoding.

For example: Haffman encoding the string “BADCADFEED”?

First, calculate the probability of each letter appearing: A 27%, B 8%, C 15%, D 15%, E 30%, and F 5%. Next, construct oneHuffman tree(small on the left and large on the right), and change the left and right weights of each leaf node to 0,1 respectively;Third, encode the path 0 or 1 for each letter from the root node to the leaf node.Finally, the Haffman encoding of the string is obtained. “BADCADFEED” Haffman coding for “1001010010101001000111100”.

1.2 Libjpeg encoding and decoding

1.2.1 JPEG compression

(1) Allocate and initialize the JPEG compression object jpeG_compress_struct and set the error handling module.

// JPEG compression coding core structure, located in the source code jpeglib.h
// It contains various compression parameters and Pointers to the workspace (memory required by the JPEG library), etc
struct jpeg_compress_struct cinfo;
// JPEG error handling structure, located in the source jpeglib.h
struct jpeg_error_mgr jerr;
// Set up the error-handling object in case initialization fails, such as memory overflow
cinfo.err = jpeg_std_error(&jerr);
// Initialize the JPEG compression object (structure object)
jpeg_create_compress(&cinfo);
Copy the code

(2) Specify compressed data output. This assumes that you specify a file path and then store the compressed JPEG data into that file.

// Open the file at the specified path
if ((outfile = fopen(filename, "wb")) = =NULL) {
    fprintf(stderr."can't open %s\n", filename);
    exit(1);
}
// Specify where to save JPEG compressed data
jpeg_stdio_dest(&cinfo, outfile);
Copy the code

(3) Set compression parameters. Of course, first we need to fill in the information related to the input image, such as width, height, color space, etc.

// Get input image information
cinfo.image_width = image_width;     / / width
cinfo.image_height = image_height;   / / height
cinfo.input_components = 3;          // The number of color components per pixel
cinfo.in_color_space = JCS_RGB;      // Color space, RGB
// Set compression parameters
cinfo.optimize_coding = true;  // Compression optimization
cinfo.arith_code = false;  // Use Huffman encoding
jpeg_set_defaults(&cinfo);
// Set the compression quality from 0 to 100
jpeg_set_quality(&cinfo, quality, true);
Copy the code

(4) Start the compression engine and process data in line. Since the image data is stored in the memory in byte order, for a WXH image, it is stored in the memory in line, a total of H lines, and the number of bytes in each line is determined by W and the size of each pixel. If there is an image with a resolution of 640×480 and color space of RGB (each pixel occupies three components, namely R component, G component and B component, each component occupies 1 byte), then each line in memory occupies 640×3=1920 bytes, a total of 480 lines. So this image takes up a total of [wx pixels)xh]=[(640×3)x480]=921600 bytes in memory.

// Start compression engine
jpeg_start_compress(&cinfo, TRUE);

extern JSAMPLE *image_buffer;   // Store the image data to be compressed in R, G, B component order
extern int image_height;        // The number of lines in the image
extern int image_width;         // The number of image columns
// Store the start address of the row
JSAMPROW row_pointer[1]; 
// The physical line width in the image buffer, where, for RGB, each pixel has 3 color components
// each component is one byte, so the width of the image line is :(width * 3)
// cinfo.image_width * cinfo.input_Components
int row_stride = cinfo.image_width * cinfo.input_components;
// Read image data by row
// Then compress and store to the destination address
while (cinfo.next_scanline < cinfo.image_height) {
    // Read a line of data from the data source cache, image_buffer
    // Assign the starting address to row_pointer[0]
    row_pointer[0] = &image_buffer[cinfo.next_scanline * row_stride];
    // Write the image_buffer data to the JPEG encoder
    (void)jpeg_write_scanlines(&cinfo, row_pointer, 1);
}
Copy the code

(5) End compression and release resources.

// Stop compression
jpeg_finish_compress(&cinfo);
// Close the file descriptor
fclose(outfile);
// Release the resources used by the engine
jpeg_destroy_compress(&cinfo);
Copy the code

1.2.2 Decoding JPEG

(1) Allocate and initialize JPEG decompression objects

// JPEG decompress structure
struct jpeg_decompress_struct cinfo;

// 1. Set program error handling
// Error handling is optimized for the standard error_exit method

// 
// 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);
// longjmp(myerr->setjmp_buffer, 1);
// }
struct my_error_mgr {
      struct jpeg_error_mgr pub;    // Error handling structure
      jmp_buf setjmp_buffer;        // Exception message is called back to the caller
};
struct my_error_mgr jerr;           
cinfo.err = jpeg_std_error(&jerr.pub); // Set the error handling standard
jerr.pub.error_exit = my_error_exit;   // Use custom error_exit function
if (setjmp(jerr.setjmp_buffer)) {      
    jpeg_destroy_decompress(&cinfo);
    fclose(infile);
    return 0;
}
// 2. Initialize the JPEG decompression object
jpeg_create_decompress(&cinfo);
Copy the code

(2) Specify the data source (JPEG image to be decompressed)

// Open the JPEG file to be decompressed
if ((infile = fopen(filename, "rb")) = =NULL) {
    fprintf(stderr."can't open %s\n", filename);
    return 0;
}
// Specify the JPEG file as the data source
jpeg_stdio_src(&cinfo, infile);
Copy the code

(3) Read JPEG image file header parameters

(void)jpeg_read_header(&cinfo, TRUE);
Copy the code

(4) Set the decompression parameters and start decompressing. Here we do not need to change the header information of the JPEG image file, so we do not set the decompression parameters.

// Start decompressing
(void)jpeg_start_decompress(&cinfo);
Copy the code

(5) Read image data stored in the buffer buffer buffer.

// Calculate the size of each line stored in physical memory (bytes)
// Width of the image * the number of components per pixel
int row_stride = cinfo.output_width * cinfo.output_components;
// Allocate a cache to store decompressed data
JSAMPARRAY buffer = (*cinfo.mem->alloc_sarray)
                ((j_common_ptr)&cinfo, JPOOL_IMAGE, row_stride, 1);
// Loop reads output_height and stores it in the buffer buffer
while (cinfo.output_scanline < cinfo.output_height) {
    (void)jpeg_read_scanlines(&cinfo, buffer, 1);
    // Decompress the obtained data for further processing
    // Define a function
    // put_scanline_someplace(buffer[0], row_stride);
}
Copy the code

(6) Decompress and release resources

// Stop decompressing
(void)jpeg_finish_decompress(&cinfo);
// Release memory resources
jpeg_destroy_decompress(&cinfo);
fclose(infile);
Copy the code

2. Libjpeg source analysis

  • Jpeg_compress_struct structure

The core structure of JPEG compression encoding, which is declared in jpeglib.h header file, contains the storage of input image parameter information, compression parameters and Pointers to the workspace (memory required by the JPEG library), etc. The jpeg_compress_struct structure is declared (in part) as follows:

struct jpeg_compress_struct {
    struct jpeg_destination_mgr *dest; // Compressed data store address
    JDIMENSION image_width;      // Enter the image width, unsigned int type
    JDIMENSION image_height;     // Enter the image height
    int input_components;        // Enter the number of image color components
    J_COLOR_SPACE in_color_space; // Enter the image color space, such as JCS_RGB
    int data_precision; // The bit precision of the compressed image data
    int num_components; // The number of color components of a compressed JPEG image
    J_COLOR_SPACE jpeg_color_space; // Compressed JPEG image color space
    peg_component_info *comp_info;  
    boolean raw_data_in;  // If the value is TRUE, data is downsampled
    boolean arith_code;   // FALSE indicates Huffman encoding, otherwise arithmetic encoding
    boolean optimize_coding; // Is TRUE, indicating optimization entropy encoding parameter
    int smoothing_factor; 	 // 1 to 100, where 0 indicates smooth input
    J_DCT_METHOD dct_method;   // DCT algorithm selector,
    JDIMENSION next_scanline;  // Scan the image line by line, 0~(image_h-1).// Compression related structures
  struct jpeg_comp_master *master;
  struct jpeg_c_main_controller *main;
  struct jpeg_c_prep_controller *prep;
  struct jpeg_c_coef_controller *coef;
  struct jpeg_marker_writer *marker;
  struct jpeg_color_converter *cconvert;
  struct jpeg_downsampler *downsample;
  struct jpeg_forward_dct *fdct;
  struct jpeg_entropy_encoder *entropy;
  jpeg_scan_info *script_space;
  intscript_space_size; };Copy the code
  • Jpeg_decompress_struct structure

This structure is the core structure of the libJPEG decompressed JPEG image, which is declared in the jpeglib.h header file. It contains the basic information of the JPEG image to be decompressed, such as the width and height of the image, the number of color components per pixel, the color space, as well as the parameters required for decompression. The jpeg_decompress_struct structure declaration (in part) reads as follows:

struct jpeg_decompress_struct {
    jpeg_common_fields;          // Share member variables with jpeg_compress_strue, including err, etc
    struct jpeg_source_mgr *src; // Compressed data source to decompress
    
    /** Basic information for the JPEG image to be decompressed * fill with the jpeg_read_header() function */
    
    JDIMENSION image_width;     // Width of the image
    JDIMENSION image_height;    // Height of the image
    int num_components;         // The number of components per pixel of the image
    J_COLOR_SPACE jpeg_color_space; // JPEG image color space

    /* Decompress the processing parameters, which need to be set before calling jpeg_start_decompress() * Note: these parameters are given their initial values */ after calling jpeg_read_header()

    J_COLOR_SPACE out_color_space; // Output color space
    unsigned int scale_num, scale_denom; // Scale the image factor
    double output_gamma;          /* image gamma wanted in output */

    boolean buffered_image;       /* TRUE=multiple output passes */
    boolean raw_data_out;         /* TRUE=downsampled data wanted */
    J_DCT_METHOD dct_method;      // IDCT algorithm selector
    boolean do_fancy_upsampling;  /* TRUE=apply fancy upsampling */
    boolean do_block_smoothing;   /* TRUE=apply interblock smoothing */
    boolean quantize_colors;      /* TRUE=colormapped output wanted */./* Describes the basic information about the input image to decompress. When jpeg_start_decompress() * is called, it is automatically computed and assigned. Of course, We also call jpeg_start_decompress() * by calling jpeg_calc_output_dimensions() */ before calling jpeg_start_decompress() *
	
    JDIMENSION output_width;      // Output image width
    JDIMENSION output_height;     // Output image height
    int out_color_components;     // The number of color components in out_color_components
    int output_components;        // The number of color components

    Read the row index of the next scanned row from jpeg_read_scanlines()
    // The size is 0.. output_height-1JDIMENSION output_scanline; .// The structure object associated with decompression
    struct jpeg_decomp_master *master;
    struct jpeg_d_main_controller *main;
    struct jpeg_d_coef_controller *coef;
    struct jpeg_d_post_controller *post;
    struct jpeg_input_controller *inputctl;
    struct jpeg_marker_reader *marker;
    struct jpeg_entropy_decoder *entropy;
    struct jpeg_inverse_dct *idct;
    struct jpeg_upsampler *upsample;
    struct jpeg_color_deconverter *cconvert;
    struct jpeg_color_quantizer *cquantize;
};
Copy the code
  • Jpeg_error_mgr structure

This structure is used for exception handling and is declared in the jpeglib.h header file, which is partially declared as follows:

struct jpeg_error_mgr {
    void (*error_exit) (j_common_ptr cinfo);  // Exit the capture function
    void (*emit_message) (j_common_ptr cinfo, int msg_level);
    void (*output_message) (j_common_ptr cinfo);
    void (*format_message) (j_common_ptr cinfo, char *buffer);
    void(*reset_error_mgr) (j_common_ptr cinfo); .const char * const *jpeg_message_table; // Library error message
    const char * const *addon_message_table; // Non-library error message
};
Copy the code
  • *Function: jpeg_std_error(struct jpeg_error_mgrerr)

This function is implemented in the jeror. c source file. It initializes the jpeg_error_MGR structure object err, that is, it assigns initial values to the members of the object. This function is already implemented in the jeror.c source file to release engine resources when the engine exits unexpectedly (such as failing to allocate memory). The jpeg_std_error function is implemented as follows:

GLOBAL(struct jpeg_error_mgr *)
jpeg_std_error(struct jpeg_error_mgr *err)
{
  err->error_exit = error_exit; // Exit the handler
  err->emit_message = emit_message;
  err->output_message = output_message;
  err->format_message = format_message;
  err->reset_error_mgr = reset_error_mgr;

  err->trace_level = 0;         /* default = no tracing */
  err->num_warnings = 0;        /* no warnings emitted yet */
  err->msg_code = 0;            /* may be useful as a flag for "no error" */

  /* Initialize message table pointers */
  err->jpeg_message_table = jpeg_std_message_table;
  err->last_jpeg_message = (int)JMSG_LASTMSGCODE - 1;

  err->addon_message_table = NULL;
  err->first_addon_message = 0; /* for safety */
  err->last_addon_message = 0;

  return err;
}

/ / error_exit () function
METHODDEF(void)
error_exit(j_common_ptr cinfo)
{
  /* Always display the message */
  (*cinfo->err->output_message) (cinfo);
  /* Let the memory manager delete any temp files before we die */
  jpeg_destroy(cinfo);
  exit(EXIT_FAILURE);
}
Copy the code
  • Function: jpeg_create_compress (cinfo)

This function allocates memory resources to the structure jpeG_compress_struct and initializes the associated member variables. Note that before calling this function, we need to set the structure’s ERR field to handle exceptions if initialization fails. It is declared in the jpeglib.h header, and its concrete implementation is actually the jpeg_CreateCompress() function, which is located in the jcapimin.c source file.

// #define jpeg_create_compress(cinfo) \ // jpeg_CreateCompress((cinfo), JPEG_LIB_VERSION, \ // (size_t)sizeof(struct jpeg_compress_struct))
GLOBAL(void)
    jpeg_CreateCompress(j_compress_ptr cinfo, int version, size_t structsize)
{
    int i;
    cinfo->mem = NULL;          
    if(version ! = JPEG_LIB_VERSION) ERREXIT2(cinfo, JERR_BAD_LIB_VERSION, JPEG_LIB_VERSION, version);if(structsize ! =sizeof(struct jpeg_compress_struct))
        ERREXIT2(cinfo, JERR_BAD_STRUCT_SIZE,
                 (int)sizeof(struct jpeg_compress_struct), (int)structsize);
    {
        struct jpeg_error_mgr *err = cinfo->err;
        void *client_data = cinfo->client_data; 
        MEMZERO(cinfo, sizeof(struct jpeg_compress_struct));
        cinfo->err = err;
        cinfo->client_data = client_data;
    }
    cinfo->is_decompressor = FALSE;

    // Initialize the memory manager instance of the cinfo object
    jinit_memory_mgr((j_common_ptr)cinfo);

    /* Zero out pointers to permanent structures. */
    cinfo->progress = NULL;
    cinfo->dest = NULL;

    cinfo->comp_info = NULL;

    for (i = 0; i < NUM_QUANT_TBLS; i++) {
        cinfo->quant_tbl_ptrs[i] = NULL;
        #if JPEG_LIB_VERSION >= 70
        cinfo->q_scale_factor[i] = 100;
        #endif
    }

    for (i = 0; i < NUM_HUFF_TBLS; i++) {
        cinfo->dc_huff_tbl_ptrs[i] = NULL;
        cinfo->ac_huff_tbl_ptrs[i] = NULL;
    }

    #if JPEG_LIB_VERSION >= 80
    /* Must do it here for emit_dqt in case jpeg_write_tables is used */
    cinfo->block_size = DCTSIZE;
    cinfo->natural_order = jpeg_natural_order;
    cinfo->lim_Se = DCTSIZE2 - 1;
    #endif

    cinfo->script_space = NULL;

    cinfo->input_gamma = 1.0;     /* in case application forgets */
	// Set the initialization completion flag
    cinfo->global_state = CSTATE_START;
}
Copy the code
  • *Function: jpeg_stdio_dest(j_compress_ptr cinfo, FILEoutfile)

This function assigns the input image file stream to the outfile field of the my_destination_mgr structure, which is declared in the libjpeg.h header and implemented in the jdatadst.c source file. Jpeg_stdio_dest function:

GLOBAL(void)
jpeg_stdio_dest(j_compress_ptr cinfo, FILE *outfile)
{
  /**my_destination_mgr struct: ** typedef struct {* struct jpeg_destination_mgr pub; * FILE *outfile; // Target file stream * JOCTET *buffer; // buffer cache *} my_destination_mgr; * typedef my_destination_mgr *my_dest_ptr; * /
  my_dest_ptr dest;
  if (cinfo->dest == NULL) {   
    cinfo->dest = (struct jpeg_destination_mgr *)
      (*cinfo->mem->alloc_small) ((j_common_ptr)cinfo, JPOOL_PERMANENT,
                                  sizeof(my_destination_mgr));
  } else if(cinfo->dest->init_destination ! = init_destination) { ERREXIT(cinfo, JERR_BUFFER_SIZE); }// Assign the dest member of cinfo to the struct dest
  dest = (my_dest_ptr)cinfo->dest;
  dest->pub.init_destination = init_destination;
  dest->pub.empty_output_buffer = empty_output_buffer;
  dest->pub.term_destination = term_destination;
  // Assign the file stream storage address to the outfile member of the my_dest_ptr structure
  dest->outfile = outfile;
}
Copy the code
  • Jpeg_write_scanlines (j_compress_ptr cinfo, JSAMPARRAY scanlines,JDIMENSION num_lines)

The function reads num_lines from scanlines, the cache area of the image data source to be compressed, and writes the num_lines data into the encoding compression engine for encoding compression processing. Of course, before writing data, the function will determine whether the current engine state is started and whether the number of lines read exceeds the limit. The jpeg_write_scanlines function is declared in jpeglib.h and implemented in jcapistd.c source file.

// typedef char JSAMPLE;
// typedef JSAMPLE *JSAMPROW;
// typedef JSAMPROW *JSAMPARRAY;
GLOBAL(JDIMENSION)
jpeg_write_scanlines(j_compress_ptr cinfo, JSAMPARRAY scanlines,
                     JDIMENSION num_lines)
{
  JDIMENSION row_ctr, rows_left;
  // Check whether the encoding engine is in CSTATE_SCANNING state
  if(cinfo->global_state ! = CSTATE_SCANNING) ERREXIT1(cinfo, JERR_BAD_STATE, cinfo->global_state);// Determine whether the current number of lines exceeds the height of the image to be compressed
  if (cinfo->next_scanline >= cinfo->image_height)
    WARNMS(cinfo, JWRN_TOO_MUCH_DATA);
  if(cinfo->progress ! =NULL) {
    cinfo->progress->pass_counter = (long)cinfo->next_scanline;
    cinfo->progress->pass_limit = (long)cinfo->image_height;
    (*cinfo->progress->progress_monitor) ((j_common_ptr)cinfo);
  }
  if (cinfo->master->call_pass_startup)
    (*cinfo->master->pass_startup) (cinfo);
  rows_left = cinfo->image_height - cinfo->next_scanline;
  if (num_lines > rows_left)
    num_lines = rows_left;

  row_ctr = 0;
  // Pass the num_lines data to be compressed into the process_data function of the JPEG_C_MAIN_controller structure
  // As for how to handle, we will not continue to analyze here
  (*cinfo->main->process_data) (cinfo, scanlines, &row_ctr, num_lines);
  cinfo->next_scanline += row_ctr;
  return row_ctr;
}
Copy the code
  • Functions: jpeg_read_scanlines(j_decompress_ptr cinfo, JSAMPARRAY scanlines, JDIMENSION max_lines)

This function reads the max_lines line from the decompressor and extracts the data into the cache pointed to by scanlines. It is declared in the jpeglib.h header file, implemented in jdapistd.c source file, the source code is as follows:

GLOBAL(JDIMENSION)
jpeg_read_scanlines(j_decompress_ptr cinfo, JSAMPARRAY scanlines,
                    JDIMENSION max_lines)
{
  JDIMENSION row_ctr;
  // Handle boundaries
  if(cinfo->global_state ! = DSTATE_SCANNING) ERREXIT1(cinfo, JERR_BAD_STATE, cinfo->global_state);if (cinfo->output_scanline >= cinfo->output_height) {
    WARNMS(cinfo, JWRN_TOO_MUCH_DATA);
    return 0;
  }
  if(cinfo->progress ! =NULL) {
    cinfo->progress->pass_counter = (long)cinfo->output_scanline;
    cinfo->progress->pass_limit = (long)cinfo->output_height;
    (*cinfo->progress->progress_monitor) ((j_common_ptr)cinfo);
  }

  // Read the decompression data from the decompressor and store it in the scanlines cache
  row_ctr = 0;
  (*cinfo->main->process_data) (cinfo, scanlines, &row_ctr, max_lines);
  cinfo->output_scanline += row_ctr;
  return row_ctr;
}
Copy the code

Libjpeg compilation and migration

3.1 Compiling using CmakelibJPEG-turboThe source code

(1) Create the Android project libjpeg and copy the source code libjpeg-Turbo to the SRC /main/ CPP directory.

(3) Modify Android project build.gradle and configure libjpeg-turbo cmakelists. TXT;

android {
    compileSdkVersion 28



    defaultConfig {
        applicationId "com.jiangdg.libjpeg"
        minSdkVersion 15
        targetSdkVersion 28
        versionCode 1
        versionName "1.0"

        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"

        externalNativeBuild {
            cmake {
                cppFlags ""
                 // Configure the platform version for compilation
                abiFilters "armeabi"."armeabi-v7a"."arm64-v8a"
            }
        }
    }

    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }


    externalNativeBuild {
        cmake {
            path "src\\main\\cpp\\CMakeLists.txt"// Change the path to the CMakeList path in the CPP folder
        }
// cmake {
// path file('CMakeLists.txt')
/ /}}}Copy the code

(3) Compile the Android project and get the libjpeg.so file and jconfig.h and jconfigint.h header files of the corresponding platform architecture.

Github Project address:libjpeg4Android, welcome to Star or Issues.

3.2 Use libJPEG compression to encode JPEG images

H, jconfigint.h, jpeglib. Hand jmorecfg.h to SRC /main/ CPP directory. Copy libjpeg.so to SRC /main/jniLibs (if not created).

(2) Configure cmakelist. TXT and import libjpeg.so

cmake_minimum_required(VERSION 3.4.1)
set(CMAKE_VERBOSE_MAKEFILE  on)

Set the so output path
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY  ${PROJECT_BINARY_DIR}/libs)

# specify the libjpeg dynamic library path
set(jpeglibs "${CMAKE_SOURCE_DIR}/src/main/jniLibs")

# Import third-party library: libjpeg.so
add_library(libjpeg SHARED IMPORTED)
set_target_properties(libjpeg PROPERTIES
         IMPORTED_LOCATION "${jpeglibs}/${ANDROID_ABI}/libjpeg.so")

set(CMAKE_CXX_FLAGS  "${CMAKE_CXX_FLAGS} -std=gnu++11 -fexceptions -frtti")

Configure and link dynamic libraries
add_library(
        jpegutil

        SHARED

        src/main/cpp/NativeJPEG.cpp)

# Find NDK native library log, Android
find_library(log-lib log)
find_library(android-lib android)

# link all libraries to jpegutil
AndroidBitmapInfo requires library jnigraphics
target_link_libraries(jpegutil
        libjpeg
        jnigraphics
        ${log-lib}
        ${android-lib})
Copy the code

(3) Writing Java layer native methods

/** * Use libjpeg to compress and uncompress JPEG@author Jiangdg
 * @sinceThe 2019-08-12 09:54:00 * * /
public class JPEGUtils {
    static {
        System.loadLibrary("jpegutil");
    }

    public native static int nativeCompressJPEG(Bitmap bitmap, int quality, String outPath);
}
Copy the code

(4) Write native layer implementation

// JPEG graphics encoding compression, decompression
// Use the libjpeg library (libjpeg_turbo version)
//
// Created by Jiangdg on 2019/8/12.
//
#include <jni.h>
#include <android/bitmap.h>
#include <android/log.h>
#include <malloc.h>
#include "jpeglib.h"
#include <stdio.h>
#include <csetjmp>
#include <string.h>
#include <setjmp.h>

#define TAG "NativeJPEG"
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO,TAG,__VA_ARGS__)
#define UNSUPPORT_BITMAP_FORMAT -99
#define FAILED_OPEN_OUTPATH -100
#define SUCCESS_COMPRESS 1

typedef uint8_t BYTE;

// Create a custom error structure
struct my_error_mgr {
    struct jpeg_error_mgr pub;
    jmp_buf setjmp_buffer;
};

int compressJPEG(BYTE *data, int width, int height, jint quality, const char *path) {
    int nComponent = 3;
    FILE *f = fopen(path, "wb");
    if(f == NULL) {
        return FAILED_OPEN_OUTPATH;
    }

    // Initialize the JPEG object and allocate space for it
    struct my_error_mgr my_err;
    struct jpeg_compress_struct jcs;
    jcs.err = jpeg_std_error(&my_err.pub);
    if(setjmp(my_err.setjmp_buffer)) {
        return 0;
    }
    jpeg_create_compress(&jcs);

    // Specify compression data source and set compression parameters
    // Use Huffman algorithm compression encoding
    jpeg_stdio_dest(&jcs, f);
    jcs.image_width = width;
    jcs.image_height = height;
    jcs.arith_code = false; // false-> Huffman encoding
    jcs.input_components = nComponent;
    jcs.in_color_space = JCS_RGB;
    jpeg_set_defaults(&jcs);
    jcs.optimize_coding = quality;  // The compression quality is 0 to 100
    jpeg_set_quality(&jcs, quality, true);
    // Start compression, line by line
    jpeg_start_compress(&jcs, true);
    JSAMPROW row_point[1];
    int row_stride;
    row_stride = jcs.image_width * nComponent;
    while(jcs.next_scanline < jcs.image_height) {
        row_point[0] = &data[jcs.next_scanline * row_stride];
        jpeg_write_scanlines(&jcs, row_point, 1);
    }
    // End compression to release resources
    if(jcs.optimize_coding ! =0) {
        LOGI("Done using Huffman compression code.");
    }
    jpeg_finish_compress(&jcs);
    jpeg_destroy_compress(&jcs);
    fclose(f);
    return SUCCESS_COMPRESS;
}

const char *jstringToString(JNIEnv *env, jstring jstr) {
    char *ret;
    const char * c_str = env->GetStringUTFChars(jstr, NULL);
    jsize len = env->GetStringLength(jstr);
    if(c_str ! =NULL) {
        ret = (char *)malloc(len+1);
        memcpy(ret, c_str, len);
        ret[len] = 0;
    }
    env->ReleaseStringUTFChars(jstr, c_str);
    return ret;
}

extern  "C"
JNIEXPORT jint JNICALL
Java_com_jiangdg_natives_JPEGUtils_nativeCompressJPEG(JNIEnv *env, jclass type, jobject bitmap, jint quality, jstring outPath_) {
    // Get bitmap attribute information
    int ret;
    int width, height, format;
    int color;
    BYTE r, g, b;
    BYTE *pixelsColor;
    BYTE *data, *tmpData;
    AndroidBitmapInfo androidBitmapInfo;
    const char *outPath = jstringToString(env, outPath_);
    LOGI("outPath=%s", outPath);
    if((ret = AndroidBitmap_getInfo(env, bitmap, &androidBitmapInfo)) < 0) {
        LOGI("AndroidBitmap_getInfo failed, error=%d", ret);
        return ret;
    }
    if((ret = AndroidBitmap_lockPixels(env, bitmap, reinterpret_cast<void **>(&pixelsColor))) < 0) {
        LOGI("AndroidBitmap_lockPixels failed, error=%d", ret);
        return ret;
    }
    width = androidBitmapInfo.width;
    height = androidBitmapInfo.height;
    format = androidBitmapInfo.format;
    LOGI("open image:w=%d, h=%d, format=%d", width, height, format);
    // Convert bitmap to RGB data, only RGBA_8888 format
    // Each pixel occupies 4 bytes, including a, R, G and B components, each component occupies 8 bits
    data = (BYTE *)malloc(width * height * 3);
    tmpData = data;
    for(int i=0; i<height; ++i) {
        for(int j=0; j<width; ++j) {
            if(format == ANDROID_BITMAP_FORMAT_RGBA_8888) {
                color = *((int *)pixelsColor);
                b = (color >> 16) & 0xFF;
                g = (color >> 8) & 0xFF;
                r = (color >> 0) & 0xFF;
                *data = r;
                *(data + 1) = g;
                *(data + 2) = b;
                data += 3;
                // Process the next pixel, which takes up 4 bytes in memory
                pixelsColor += 4;
            } else {
                returnUNSUPPORT_BITMAP_FORMAT; }}}if((ret = AndroidBitmap_unlockPixels(env, bitmap)) < 0) {
        LOGI("AndroidBitmap_unlockPixels failed,error=%d", ret);
        return ret;
    }
    // Encode compressed images
    ret = compressJPEG(tmpData, width, height, quality, outPath);
    free((void *)tmpData);
    return ret;
}
Copy the code

ExternalNativeBuild /… externalNativeBuild/… Generate libjpegtil.so file in /libs directory, then copy libjpegtil.so and libjpeg.so to the target project.

Github Project address:HandleJpeg.