SoFunction
Updated on 2025-03-03

Detailed explanation of image compression scheme in Android and source code download

Detailed explanation of image compression scheme in Android and source code download

Image display can be said to be inevitable in any of our applications, but a large number of pictures will have many problems, such as OOM problems when loading large pictures or multiple pictures, you can move toAndroid efficiently load large and multi-picture programs to avoid OOM.There is another problem that the upload and download of images is often the problem. We often like the image to be clear and takes up a small amount of memory, which means that we consume as little traffic as possible. This is the problem I want to talk about today: a detailed explanation of the compression scheme of the image.

1. Mass compression method

Set the bitmap options attribute to reduce the quality of the image, and the pixels will not be reduced.

The first parameter is the bitmap image object that needs to be compressed, and the second parameter is the location where the image is saved after compression

Set options attributes 0-100 to achieve compression.

private Bitmap compressImage(Bitmap image) { 
 ByteArrayOutputStream baos = new ByteArrayOutputStream(); 
 (, 100, baos);// Quality compression method, here 100 means no compression, store the compressed data in baos int options = 100; 
 while ( ().length / 1024>100) { //Cyclically determine whether the image is greater than 100kb after compression, which is greater than continue compression. ();//Reset baos means clear baos (, options, baos);//The compressed options% here, and the compressed data is stored in baos options -= 10;//Reduce by 10 each time } 
 ByteArrayInputStream isBm = new ByteArrayInputStream(());//Storage the compressed data baos in ByteArrayInputStream Bitmap bitmap = (isBm, null, null);//Create the ByteArrayInputStream data to generate pictures return bitmap; 
 }

Quality compression does not reduce the pixels of the image. It changes the bit depth and transparency of the picture while keeping the pixel unchanged to achieve the purpose of compressing the picture. The size of the image file compressed by it will change, but the memory occupied by importing it into a bitmap will remain unchanged. Because the pixels are to be kept unchanged, it cannot be compressed infinitely and will not continue to become smaller after reaching a value. Obviously, this method does not apply to thumbnails, and it is not actually applicable to those who want to reduce memory by compressing images. It is only applicable to those who want to reduce file size while ensuring image quality.

2. Sampling rate compression method

private Bitmap getimage(String srcPath) { 
  newOpts = new (); 
 //Start read the picture, and set it back to true at this time  = true; 
 Bitmap bitmap = (srcPath,newOpts);//Return bm to empty  = false; 
 int w = ; 
 int h = ; 
 //There are more common mainstream mobile phones with a resolution of 1280*720, so we set the height and width to float hh = 1280f;//The height is set to 1280f float ww = 720f;//The width is set to 720f here //Scaling ratio.  Since it is fixed proportional scaling, only one of the data of height or width can be calculated int be = 1;//be=1 means no scaling if (w > h && w > ww) {// If the width is large, scale it according to the width fixed size be = (int) ( / ww); 
 } else if (w < h && h > hh) {// If the height is high, it will be scaled according to the width. be = (int) ( / hh); 
 } 
 if (be <= 0) 
 be = 1; 
  = be;//Set the scaling ratio //Read the picture again, note that it has been set back to false at this time bitmap = (srcPath, newOpts); 
 return compressImage(bitmap);//After compressing the proportion, then perform mass compression }

The advantage of this method is that it greatly reduces the use of memory. When reading pictures on memory, if you do not need high-definition effects, you can first read only the edges of the picture, set the sampling rate through width and height before loading the picture, so that you will not occupy too much memory.

3. Scaling method

Reduce the memory size of the image by scaling the image pixels.

Method one

public static void compressBitmapToFile(Bitmap bmp, File file){
 // Size compression multiple, the larger the value, the smaller the image size int ratio = 2;
 // Compress Bitmap to the corresponding size Bitmap result = (() / ratio, () / ratio, Config.ARGB_8888);
 Canvas canvas = new Canvas(result);
 Rect rect = new Rect(0, 0, () / ratio, () / ratio);
 (bmp, null, rect, null);

 ByteArrayOutputStream baos = new ByteArrayOutputStream();
 //Storage the compressed data in baos (, 100 ,baos);
 try { 
 FileOutputStream fos = new FileOutputStream(file); 
 (()); 
 (); 
 (); 
 } catch (Exception e) { 
 (); 
 } 
}

Method 2

ByteArrayOutputStream out = new ByteArrayOutputStream(); 
(, 85, out); 
float zoom = (float)(size * 1024 / (float)().length); 

Matrix matrix = new Matrix(); 
(zoom, zoom); 

Bitmap result = (image, 0, 0, (), (), matrix, true); 

(); 
(, 85, out); 
while(().length > size * 1024){ 
 (().length); 
 (0.9f, 0.9f); 
 result = (result, 0, 0, (), (), matrix, true); 
 (); 
 (, 85, out); 
}

The scaling method is actually very simple. Just set the matrix and createBitmap. But we don't know the scaling ratio, but instead require the final size of the picture. If you use the scale of size to do it directly, there will definitely be a problem. It will be closer to using the scale of size to do it, but there is still a gap. But just do a fine adjustment. If the fine adjustment is, if the modified image size is larger than the final size, then compress it at 0.8 and then compare it, and cycle until the size is appropriate. This way you can get pictures of the right size and ensure quality.

4. JNI calls libjpeg library compression

Methods in JNI static calls to implement compressionJava_net_bither_util_NativeUtil_compressBitmap

net_bither_util is the package name, NativeUtil is the class name, and compressBitmap is the native method name. We only need to call the saveBitmap() method. bmp needs to be compressed Bitmap object, quality compression quality 0-100, fileName The file address to be saved after compression, optimization Whether to use Havalman table data to calculate the quality difference is 5-10 times.

jstring Java_net_bither_util_NativeUtil_compressBitmap(JNIEnv* env,
 jobject thiz, jobject bitmapcolor, int w, int h, int quality,
 jbyteArray fileNameStr, jboolean optimize) {

 AndroidBitmapInfo infocolor;
 BYTE* pixelscolor;
 int ret;
 BYTE * data;
 BYTE *tmpdata;
 char * fileName = jstrinTostring(env, fileNameStr);
 if ((ret = AndroidBitmap_getInfo(env, bitmapcolor, &infocolor)) < 0) {
 LOGE("AndroidBitmap_getInfo() failed ! error=%d", ret);
 return (*env)->NewStringUTF(env, "0");;
 }
 if ((ret = AndroidBitmap_lockPixels(env, bitmapcolor, &pixelscolor)) < 0) {
 LOGE("AndroidBitmap_lockPixels() failed ! error=%d", ret);
 }

 BYTE r, g, b;
 data = NULL;
 data = malloc(w * h * 3);
 tmpdata = data;
 int j = 0, i = 0;
 int color;
 for (i = 0; i < h; i++) {
 for (j = 0; j < w; j++) {
 color = *((int *) pixelscolor);
 r = ((color & 0x00FF0000) >> 16);
 g = ((color & 0x0000FF00) >> 8);
 b = color & 0x000000FF;
 *data = b;
 *(data + 1) = g;
 *(data + 2) = r;
 data = data + 3;
 pixelscolor += 4;

 }

 }
 AndroidBitmap_unlockPixels(env, bitmapcolor);
 int resultCode= generateJPEG(tmpdata, w, h, quality, fileName, optimize);
 free(tmpdata);
 if(resultCode==0){
 jstring result=(*env)->NewStringUTF(env, error);
 error=NULL;
 return result;
 }
 return (*env)->NewStringUTF(env, "1"); //success
}

5. Combined use of quality compression + sampling rate compression + JNI call libjpeg library compression

First, through size compression, compress to a commonly used resolution (1280*960 WeChat seems to be compressed to this resolution), then we need to compress the image to a certain size (such as 200k), and then calculate how much options need to be set by looping through mass compression, and finally call JNI compression.

Calculate the scaling ratio

/**
  * Calculate the scaling ratio
  * @param bitWidth Current image width
  * @param bitHeight Current image height
  * @return int Scaling ratio
  */
 public static int getRatioSize(int bitWidth, int bitHeight) {
 // Maximum resolution of the picture int imageHeight = 1280;
 int imageWidth = 960;
 // Zoom ratio int ratio = 1;
 // Scaling ratio, since it is fixed scale, you can only calculate one of the data of height or width if (bitWidth &gt; bitHeight &amp;&amp; bitWidth &gt; imageWidth) {
 // If the image width is larger than the height, the width is the reference ratio = bitWidth / imageWidth;
 } else if (bitWidth &lt; bitHeight &amp;&amp; bitHeight &gt; imageHeight) {
 // If the image height is larger than the width, the height is the reference ratio = bitHeight / imageHeight;
 }
 // Minimum ratio is 1 if (ratio &lt;= 0)
 ratio = 1;
 return ratio;
 }

Mass compression + JNI compression

/**
  * @Description: Save Bitmap to the specified directory through JNI image compression
  * @param curFilePath
  * Current image file address
  * @param targetFilePath
  * The image file address to save
  */
 public static void compressBitmap(String curFilePath, String targetFilePath) {
 // Maximum image size 500KB int maxSize = 500;
 //Get bitmap based on the address Bitmap result = getBitmapFromFile(curFilePath);
 ByteArrayOutputStream baos = new ByteArrayOutputStream();
 // Quality compression method, here 100 means no compression, and store the compressed data in baos int quality = 100;
 (, quality, baos);
 // Circularly determine whether the image is greater than 500kb after compression, and is greater than continue compression while (().length / 1024 &gt; maxSize) {
 // Reset baos means clear baos ();
 // 10 reduction each time quality -= 10;
 // Compress quality here, store the compressed data in baos (, quality, baos);
 }
 // JNI saves pictures to SD card This key (result, quality, targetFilePath, true);
 // Release Bitmap if (!()) {
 ();
 }
 }

JNI picture compression tool class

package ;
import ;
import ;
import ;
import ;
import ;
import ;
import ;
import ;
import ;
import ;
import ;
import ;
/**
 * JNI picture compression tool class
 *
 * @Description TODO
 * @Package
 * @Class NativeUtil
 */
public class NativeUtil {

 private static int DEFAULT_QUALITY = 95;

 /**
  * @Description: JNI basic compression
  * @param bit
  * bitmap object
  * @param fileName
  * Specify the save directory name
  * @param optimize
  * Whether to use Havalman table data to calculate the quality difference is 5-10 times
  */
 public static void compressBitmap(Bitmap bit, String fileName, boolean optimize) {
 saveBitmap(bit, DEFAULT_QUALITY, fileName, optimize);
 }

 /**
  * @Description: Save Bitmap to the specified directory through JNI image compression
  * @param image
  * bitmap object
  * @param filePath
  * The specified directory to save
  */
 public static void compressBitmap(Bitmap image, String filePath) {
 // Maximum image size 150KB int maxSize = 150;
 // Get the size compression multiple int ratio = ((),());
 // Compress Bitmap to the corresponding size Bitmap result = (() / ratio,() / ratio, Config.ARGB_8888);
 Canvas canvas = new Canvas(result);
 Rect rect = new Rect(0, 0, () / ratio, () / ratio);
 (image,null,rect,null);
 ByteArrayOutputStream baos = new ByteArrayOutputStream();
 // Quality compression method, here 100 means no compression, and store the compressed data in baos int options = 100;
 (, options, baos);
 // Circularly determine whether the image is greater than 100kb after compression, and is greater than continue compression while (().length / 1024 &gt; maxSize) {
 // Reset baos means clear baos ();
 // 10 reduction each time options -= 10;
 // Here compress options%, store the compressed data in baos (, options, baos);
 }
 // JNI saves pictures to SD card This key (result, options, filePath, true);
 // Release Bitmap if (!()) {
 ();
 }
 }

 /**
  * @Description: Save Bitmap to the specified directory through JNI image compression
  * @param curFilePath
  * Current image file address
  * @param targetFilePath
  * The image file address to save
  */
 public static void compressBitmap(String curFilePath, String targetFilePath) {
 // Maximum image size 500KB int maxSize = 500;
 //Get bitmap based on the address Bitmap result = getBitmapFromFile(curFilePath);
 ByteArrayOutputStream baos = new ByteArrayOutputStream();
 // Quality compression method, here 100 means no compression, and store the compressed data in baos int quality = 100;
 (, quality, baos);
 // Circularly determine whether the image is greater than 500kb after compression, and is greater than continue compression while (().length / 1024 &gt; maxSize) {
 // Reset baos means clear baos ();
 // 10 reduction each time quality -= 10;
 // Compress quality here, store the compressed data in baos (, quality, baos);
 }
 // JNI saves pictures to SD card This key (result, quality, targetFilePath, true);
 // Release Bitmap if (!()) {
 ();
 }

 }

 /**
  * Calculate the scaling ratio
  * @param bitWidth Current image width
  * @param bitHeight Current image height
  * @return int Scaling ratio
  */
 public static int getRatioSize(int bitWidth, int bitHeight) {
 // Maximum resolution of the picture int imageHeight = 1280;
 int imageWidth = 960;
 // Zoom ratio int ratio = 1;
 // Scaling ratio, since it is fixed scale, you can only calculate one of the data of height or width if (bitWidth &gt; bitHeight &amp;&amp; bitWidth &gt; imageWidth) {
 // If the image width is larger than the height, the width is the reference ratio = bitWidth / imageWidth;
 } else if (bitWidth &lt; bitHeight &amp;&amp; bitHeight &gt; imageHeight) {
 // If the image height is larger than the width, the height is the reference ratio = bitHeight / imageHeight;
 }
 // Minimum ratio is 1 if (ratio &lt;= 0)
 ratio = 1;
 return ratio;
 }

 /**
  * Get Bitmap through file path reading to prevent OOM and solve the problem of image rotation
  * @param filePath
  * @return
  */
 public static Bitmap getBitmapFromFile(String filePath){
  newOpts = new ();
  = true;//Read only edges, not content (filePath, newOpts);
 int w = ;
 int h = ;
 // Get the size compression multiple  = (w,h);
  = false;//Read all content  = false;
 =true;
 =true;
  = new byte[32 * 1024];
 Bitmap bitmap = null;
 File file = new File(filePath);
 FileInputStream fs = null;
 try {
 fs = new FileInputStream(file);
 } catch (FileNotFoundException e) {
 ();
 }
 try {
 if(fs!=null){
 bitmap = ((),null,newOpts);
 //Rotate the picture int photoDegree = readPictureDegree(filePath);
 if(photoDegree != 0){
  Matrix matrix = new Matrix();
  (photoDegree);
  // Create a new picture  bitmap = (bitmap, 0, 0,
  (), (), matrix, true);
 }
 }
 } catch (IOException e) {
 ();
 } finally{
 if(fs!=null) {
 try {
  ();
 } catch (IOException e) {
  ();
 }
 }
 }
 return bitmap;
 }

 /**
  *
  * Read image properties: rotation angle
  * @param path The absolute path of the picture
  * @return degree rotation angle
  */

 public static int readPictureDegree(String path) {
 int degree = 0;
 try {
 ExifInterface exifInterface = new ExifInterface(path);
 int orientation = (
  ExifInterface.TAG_ORIENTATION,
  ExifInterface.ORIENTATION_NORMAL);
 switch (orientation) {
 case ExifInterface.ORIENTATION_ROTATE_90:
  degree = 90;
  break;
 case ExifInterface.ORIENTATION_ROTATE_180:
  degree = 180;
  break;
 case ExifInterface.ORIENTATION_ROTATE_270:
  degree = 270;
  break;
 }
 } catch (IOException e) {
 ();
 }
 return degree;
 }

 /**
  * Call native method
  * @Description: Function description
  * @param bit
  * @param quality
  * @param fileName
  * @param optimize
  */
 private static void saveBitmap(Bitmap bit, int quality, String fileName, boolean optimize) {
 compressBitmap(bit, (), (), quality, (), optimize);
 }

 /**
  * Call the method in the underlying layer
  * @Description: Function description
  * @param bit
  * @param w
  * @param h
  * @param quality
  * @param fileNameBytes
  * @param optimize
  * @return
  */
 private static native String compressBitmap(Bitmap bit, int w, int h, int quality, byte[] fileNameBytes,
   boolean optimize);
 /**
  * Load two so files in lib
  */
 static {
 ("jpegbither");
 ("bitherjni");
 }
}

Problems that may be encountered in image compression processing:

There are three actions for requesting the system album

Note: Gallery (thumbnail) and pictures (original image)

ACTION_OPEN_DOCUMENTOnly for use 4.4 or above, the original image is opened by default

The format of the uri obtained from the image is:content:///document/image%666>>>

ACTION_GET_CONTENTThumbnails are turned on by default in 4.4. The above opens the file manager for selection, select the gallery to open as the thumbnail page, and select the picture to open as the original image to browse.

The uri format obtained from the gallery is:content://media/external/images/media/666666

ACTION_PICKAll are available. The default thumbnail interface is opened. You need to click on it to view it further.

Reference code:

public void pickFromGallery() {
 if (.SDK_INT < Build.VERSION_CODES.KITKAT) {
 startActivityForResult(new Intent(Intent.ACTION_GET_CONTENT).setType("image/*"),
 REQUEST_PICK_IMAGE);
 } else {
 Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT);
 (Intent.CATEGORY_OPENABLE);
 ("image/*");
 startActivityForResult(intent, REQUEST_KITKAT_PICK_IMAGE);
 }
}

Get the corresponding file path according to the URI

After we select the image from the gallery, the callback gives us the () may be the URI. Our daily operations on files are basically based on paths and then perform various operations and conversions. Now we need to find out the file path corresponding to the URI. The specific reference code is as follows:

public static String getPathByUri(Context context, Uri data){
 if (.SDK_INT &lt; Build.VERSION_CODES.KITKAT) {
 return getPathByUri4BeforeKitkat(context, data);
 }else {
 return getPathByUri4AfterKitkat(context, data);
 }
 }
 //Before 4.4, the path is obtained through Uri: data is Uri, filename is a String string, used to save the path public static String getPathByUri4BeforeKitkat(Context context, Uri data) {
 String filename=null;
 if (().toString().compareTo("content") == 0) {
 Cursor cursor = ().query(data, new String[] { "_data" }, null, null, null);
 if (()) {
 filename = (0);
 }
 } else if (().toString().compareTo("file") == 0) {// uri at the beginning of file:/// filename = ().replace("file://", "");// Replace file:// if (!("/mnt")) {// Add "/mnt" header filename += "/mnt";
 }
 }
 return filename;
 }
 //After 4.4, obtain the path according to Uri: @SuppressLint("NewApi")
 public static String getPathByUri4AfterKitkat(final Context context, final Uri uri) {
 final boolean isKitKat = .SDK_INT &gt;= Build.VERSION_CODES.KITKAT;
 // DocumentProvider
 if (isKitKat &amp;&amp; (context, uri)) {
 if (isExternalStorageDocument(uri)) {// ExternalStorageProvider
 final String docId = (uri);
 final String[] split = (":");
 final String type = split[0];
 if ("primary".equalsIgnoreCase(type)) {
  return () + "/" + split[1];
 }
 } else if (isDownloadsDocument(uri)) {// DownloadsProvider
 final String id = (uri);
 final Uri contentUri = (("content://downloads/public_downloads"),
  (id));
 return getDataColumn(context, contentUri, null, null);
 } else if (isMediaDocument(uri)) {// MediaProvider
 final String docId = (uri);
 final String[] split = (":");
 final String type = split[0];
 Uri contentUri = null;
 if ("image".equals(type)) {
  contentUri = .EXTERNAL_CONTENT_URI;
 } else if ("video".equals(type)) {
  contentUri = .EXTERNAL_CONTENT_URI;
 } else if ("audio".equals(type)) {
  contentUri = .EXTERNAL_CONTENT_URI;
 }
 final String selection = "_id=?";
 final String[] selectionArgs = new String[] { split[1] };
 return getDataColumn(context, contentUri, selection, selectionArgs);
 }
 } else if ("content".equalsIgnoreCase(())) {// MediaStore
 // (and
 // general)
 return getDataColumn(context, uri, null, null);
 } else if ("file".equalsIgnoreCase(())) {// File
 return ();
 }
 return null;
 }

 /**
 * Get the value of the data column for this Uri. This is useful for
 * MediaStore Uris, and other file-based ContentProviders.
 *
 * @param context
 * The context.
 * @param uri
 * The Uri to query.
 * @param selection
 * (Optional) Filter used in the query.
 * @param selectionArgs
 * (Optional) Selection arguments used in the query.
 * @return The value of the _data column, which is typically a file path.
 */
 public static String getDataColumn(Context context, Uri uri, String selection, String[] selectionArgs) {
 Cursor cursor = null;
 final String column = "_data";
 final String[] projection = { column };
 try {
 cursor = ().query(uri, projection, selection, selectionArgs, null);
 if (cursor != null &amp;&amp; ()) {
 final int column_index = (column);
 return (column_index);
 }
 } finally {
 if (cursor != null)
 ();
 }
 return null;
 }

 /**
 * @param uri
 * The Uri to check.
 * @return Whether the Uri authority is ExternalStorageProvider.
 */
 public static boolean isExternalStorageDocument(Uri uri) {
 return "".equals(());
 }

 /**
 * @param uri
 * The Uri to check.
 * @return Whether the Uri authority is DownloadsProvider.
 */
 public static boolean isDownloadsDocument(Uri uri) {
 return "".equals(());
 }

 /**
 * @param uri
 * The Uri to check.
 * @return Whether the Uri authority is MediaProvider.
 */
 public static boolean isMediaDocument(Uri uri) {
 return "".equals(());
 }

Source code is provided, please refer to it by yourself:Source code download