Compress bitmap to a specific byte size in Android

南笙酒味 提交于 2019-12-19 10:14:41

问题


Is there a way to compress Bitmap to a specific byte size? For example, 1.5MB. The matter is all the examples I have seen so far were resizing width and height, but my requirement is to resize the bytes. Is that possible? Also, what is the most straightforward and right way to compress the Bitmap? I am quite novice to this topic and would like to go right direction from the beginning.


回答1:


You can calculate the size of a bitmap quite easily by width * height * bytes per pixel = size

Where bytes per pixel is defined by your color model say RGBA_F16 is 8 bytes while ARGB_8888 is 4 bytes and so on. With this you should be able to figure out what width and height and color encoding you want for your image.

See https://developer.android.com/reference/android/graphics/Bitmap.Config for the bit values.

Also see https://developer.android.com/topic/performance/graphics/manage-memory for more about bitmap memory management.




回答2:


Here's a helper class I created. This compresses the bitmap both by width/height then by max file size. It's not an exact science to shrink an image to 1.5mb, but what it does is if the image is larger than required, it compresses the bitmap using jpeg and reduces the quality by 80%. Once the file size is less than the required size, it returns the bitmap in a byte array.

public static byte[] getCompressedBitmapData(Bitmap bitmap, int maxFileSize, int maxDimensions) {
    Bitmap resizedBitmap;
    if (bitmap.getWidth() > maxDimensions || bitmap.getHeight() > maxDimensions) {
        resizedBitmap = getResizedBitmap(bitmap,
                                         maxDimensions);
    } else {
        resizedBitmap = bitmap;
    }

    byte[] bitmapData = getByteArray(resizedBitmap);

    while (bitmapData.length > maxFileSize) {
        bitmapData = getByteArray(resizedBitmap);
    }
    return bitmapData;
}

public static Bitmap getResizedBitmap(Bitmap image, int maxSize) {
    int width = image.getWidth();
    int height = image.getHeight();

    float bitmapRatio = (float) width / (float) height;
    if (bitmapRatio > 1) {
        width = maxSize;
        height = (int) (width / bitmapRatio);
    } else {
        height = maxSize;
        width = (int) (height * bitmapRatio);
    }
    return Bitmap.createScaledBitmap(image,
                                     width,
                                     height,
                                     true);
}

private static byte[] getByteArray(Bitmap bitmap) {
    ByteArrayOutputStream bos = new ByteArrayOutputStream();

    bitmap.compress(Bitmap.CompressFormat.JPEG,
                    80,
                    bos);

    return bos.toByteArray();
}



回答3:


This works for me. Scale area of original bitmap to 50% and compress bitmap until it's size < 200k

import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.graphics.Matrix
import android.os.Environment
import android.support.media.ExifInterface //28.0.0

companion object {
        const val TAG = "MainActivity"
        internal val ROOT_FOLDER_CACHE_IMAGE =
            Environment.getExternalStorageDirectory().toString() + "/com.test/cache"
        const val _200KB = 200 * 1024
    }

private fun displayBitmapAfterCompressing() {
        val filePath = Environment.getExternalStorageDirectory().toString() + "/DCIM/Camera/test.jpg"
        //ImageView shows bitmap before compressing
        var inputStream = FileInputStream(filePath)
        original_image_view.setImageBitmap(BitmapFactory.decodeStream(inputStream))
        inputStream.close()

        //ImageView shows bitmap after compressing
        val newFilePath = resizeAndCompressFile(filePath)
        if (newFilePath != null) {
            inputStream = FileInputStream(newFilePath)
            compress_image_view.setImageBitmap(BitmapFactory.decodeStream(inputStream))
            inputStream.close()
        }
    }

    private fun resizeAndCompressFile(filePath: String): String? {
        val imageFile = File(filePath)
        if (imageFile.exists()) {
            val fileSize = imageFile.length()
            if (fileSize > 0) {
                return if (fileSize < _200KB) {
                    filePath
                } else {
                    resizeAndCompressBitmapTo200KB(filePath)
                }
            }
        }
        return null
    }

    private fun resizeAndCompressBitmapTo200KB(filePath: String): String? {
        val imageFile = File(filePath)
        val fileSize = imageFile.length()
        Log.d(TAG, "size of original file = $fileSize")

        if (fileSize > _200KB) {
            var qualityCompress = 80
            if (fileSize > 3145728) {// > 3MB
                qualityCompress = 55
            } else if (fileSize > 2097152) {// > 2MB
                qualityCompress = 60
            } else if (fileSize > 1560576) {// > 1.5MB
                qualityCompress = 65
            } else if (fileSize > 1048576) {// > 1MB
                qualityCompress = 70
            }

            var newFilePath: String?
            do {
                newFilePath = compressFileAndReturnNewPathOfNewFile(filePath, qualityCompress)
                qualityCompress -= 5

                //TODO test
                newFilePath?.let {
                    Log.d(
                        TAG,
                        "qualityCompress = " + qualityCompress + "size of new file = " + File(newFilePath).length()
                    )
                }
            } while (newFilePath != null && File(newFilePath).length() > _200KB)
            //copy attributes from old exif to new exif
            if (newFilePath != null) {
                copyExif(filePath, newFilePath)
            }

            return newFilePath
        }
        return filePath
    }

    private fun compressFileAndReturnNewPathOfNewFile(filePath: String, qualityCompress: Int): String? {
        try {
            val inputStream = FileInputStream(filePath)
            var compressBitmap = BitmapFactory.decodeStream(inputStream)
            //original width height
            val widthOriginal = compressBitmap.width
            val heightOriginal = compressBitmap.height

            //resize image 50% (keep original scale)
            val width50Percent: Int = (widthOriginal / 1.41421356237).toInt()
            val height50Percent: Int = (heightOriginal / 1.41421356237).toInt()
            //
            val scaleWidth: Float = width50Percent.toFloat() / widthOriginal
            val scaleHeight: Float = height50Percent.toFloat() / heightOriginal
            //
            //Must Rotate bitmap before upload them
            val matrix = Matrix()
            matrix.setRotate(getOrientation(filePath).toFloat())
            matrix.postScale(scaleWidth, scaleHeight);

            compressBitmap = Bitmap.createBitmap(
                compressBitmap, 0, 0, compressBitmap.width,
                compressBitmap.height, matrix, true
            )
            //make a new file directory inside the "sdcard" folder
            val mediaStorageDir = File(ROOT_FOLDER_CACHE_IMAGE)
            if (!mediaStorageDir.exists()) {
                mediaStorageDir.mkdirs()
            }
            var file = File(mediaStorageDir.absolutePath, "compress_image.jpeg")
            if (file.exists()) {
                file.deleteOnExit()
                file = File(mediaStorageDir.absolutePath, "compress_image.jpeg")
            }

            val fos = FileOutputStream(file)
            compressBitmap.compress(Bitmap.CompressFormat.JPEG, qualityCompress, fos)
            fos.flush()
            fos.close()

            inputStream.close()
            compressBitmap.recycle()
            compressBitmap = null
            // Use this for reading the data.
            /*val inputStream = FileInputStream(file.absolutePath)
            val buffer = ByteArray(file.length().toInt())
            inputStream.read(buffer)
            inputStream.close()
            return buffer*/
            return file.absolutePath

        } catch (e1: FileNotFoundException) {
            Log.e(TAG, "compressFileToByteArray(1)", e1)
        } catch (e2: IOException) {
            Log.e(TAG, "compressFileToByteArray(2)", e2)

        } catch (e3: Exception) {
            Log.e(TAG, "compressFileToByteArray(3)", e3)
        }
        return null
    }

    private fun copyExif(oldPath: String, newPath: String) {
        val attributes = arrayOf(
            ExifInterface.TAG_IMAGE_WIDTH,
            ExifInterface.TAG_IMAGE_LENGTH,
            ExifInterface.TAG_BITS_PER_SAMPLE,
            ExifInterface.TAG_COMPRESSION,
            ExifInterface.TAG_PHOTOMETRIC_INTERPRETATION,
            ExifInterface.TAG_ORIENTATION,
            ExifInterface.TAG_SAMPLES_PER_PIXEL,
            ExifInterface.TAG_PLANAR_CONFIGURATION,
            ExifInterface.TAG_Y_CB_CR_SUB_SAMPLING,
            ExifInterface.TAG_Y_CB_CR_POSITIONING,
            ExifInterface.TAG_X_RESOLUTION,
            ExifInterface.TAG_Y_RESOLUTION,
            ExifInterface.TAG_RESOLUTION_UNIT,
            ExifInterface.TAG_STRIP_OFFSETS,
            ExifInterface.TAG_ROWS_PER_STRIP,
            ExifInterface.TAG_STRIP_BYTE_COUNTS,
            ExifInterface.TAG_JPEG_INTERCHANGE_FORMAT,
            ExifInterface.TAG_JPEG_INTERCHANGE_FORMAT_LENGTH,
            ExifInterface.TAG_TRANSFER_FUNCTION,
            ExifInterface.TAG_WHITE_POINT,
            ExifInterface.TAG_PRIMARY_CHROMATICITIES,
            ExifInterface.TAG_Y_CB_CR_COEFFICIENTS,
            ExifInterface.TAG_REFERENCE_BLACK_WHITE,
            ExifInterface.TAG_DATETIME,
            ExifInterface.TAG_IMAGE_DESCRIPTION,
            ExifInterface.TAG_MAKE,
            ExifInterface.TAG_MODEL,
            ExifInterface.TAG_SOFTWARE,
            ExifInterface.TAG_ARTIST,
            ExifInterface.TAG_COPYRIGHT,
            ExifInterface.TAG_EXIF_VERSION,
            ExifInterface.TAG_FLASHPIX_VERSION,
            ExifInterface.TAG_COLOR_SPACE,
            ExifInterface.TAG_GAMMA,
            ExifInterface.TAG_PIXEL_X_DIMENSION,
            ExifInterface.TAG_PIXEL_Y_DIMENSION,
            ExifInterface.TAG_COMPONENTS_CONFIGURATION,
            ExifInterface.TAG_COMPRESSED_BITS_PER_PIXEL,
            ExifInterface.TAG_MAKER_NOTE,
            ExifInterface.TAG_USER_COMMENT,
            ExifInterface.TAG_RELATED_SOUND_FILE,
            ExifInterface.TAG_DATETIME_ORIGINAL,
            ExifInterface.TAG_DATETIME_DIGITIZED,
            ExifInterface.TAG_SUBSEC_TIME,
            ExifInterface.TAG_SUBSEC_TIME_ORIGINAL,
            ExifInterface.TAG_SUBSEC_TIME_DIGITIZED,
            ExifInterface.TAG_EXPOSURE_TIME,
            ExifInterface.TAG_F_NUMBER,
            ExifInterface.TAG_EXPOSURE_PROGRAM,
            ExifInterface.TAG_SPECTRAL_SENSITIVITY,
            ExifInterface.TAG_PHOTOGRAPHIC_SENSITIVITY,
            ExifInterface.TAG_OECF,
            ExifInterface.TAG_SENSITIVITY_TYPE,
            ExifInterface.TAG_STANDARD_OUTPUT_SENSITIVITY,
            ExifInterface.TAG_RECOMMENDED_EXPOSURE_INDEX,
            ExifInterface.TAG_ISO_SPEED,
            ExifInterface.TAG_ISO_SPEED_LATITUDE_YYY,
            ExifInterface.TAG_ISO_SPEED_LATITUDE_ZZZ,
            ExifInterface.TAG_SHUTTER_SPEED_VALUE,
            ExifInterface.TAG_APERTURE_VALUE,
            ExifInterface.TAG_BRIGHTNESS_VALUE,
            ExifInterface.TAG_EXPOSURE_BIAS_VALUE,
            ExifInterface.TAG_MAX_APERTURE_VALUE,
            ExifInterface.TAG_SUBJECT_DISTANCE,
            ExifInterface.TAG_METERING_MODE,
            ExifInterface.TAG_LIGHT_SOURCE,
            ExifInterface.TAG_FLASH,
            ExifInterface.TAG_SUBJECT_AREA,
            ExifInterface.TAG_FOCAL_LENGTH,
            ExifInterface.TAG_FLASH_ENERGY,
            ExifInterface.TAG_SPATIAL_FREQUENCY_RESPONSE,
            ExifInterface.TAG_FOCAL_PLANE_X_RESOLUTION,
            ExifInterface.TAG_FOCAL_PLANE_Y_RESOLUTION,
            ExifInterface.TAG_FOCAL_PLANE_RESOLUTION_UNIT,
            ExifInterface.TAG_SUBJECT_LOCATION,
            ExifInterface.TAG_EXPOSURE_INDEX,
            ExifInterface.TAG_SENSING_METHOD,
            ExifInterface.TAG_FILE_SOURCE,
            ExifInterface.TAG_SCENE_TYPE,
            ExifInterface.TAG_CFA_PATTERN,
            ExifInterface.TAG_CUSTOM_RENDERED,
            ExifInterface.TAG_EXPOSURE_MODE,
            ExifInterface.TAG_WHITE_BALANCE,
            ExifInterface.TAG_DIGITAL_ZOOM_RATIO,
            ExifInterface.TAG_FOCAL_LENGTH_IN_35MM_FILM,
            ExifInterface.TAG_SCENE_CAPTURE_TYPE,
            ExifInterface.TAG_GAIN_CONTROL,
            ExifInterface.TAG_CONTRAST,
            ExifInterface.TAG_SATURATION,
            ExifInterface.TAG_SHARPNESS,
            ExifInterface.TAG_DEVICE_SETTING_DESCRIPTION,
            ExifInterface.TAG_SUBJECT_DISTANCE_RANGE,
            ExifInterface.TAG_IMAGE_UNIQUE_ID,
            ExifInterface.TAG_CAMARA_OWNER_NAME,
            ExifInterface.TAG_BODY_SERIAL_NUMBER,
            ExifInterface.TAG_LENS_SPECIFICATION,
            ExifInterface.TAG_LENS_MAKE,
            ExifInterface.TAG_LENS_MODEL,
            ExifInterface.TAG_LENS_SERIAL_NUMBER,
            ExifInterface.TAG_GPS_VERSION_ID,
            ExifInterface.TAG_GPS_LATITUDE_REF,
            ExifInterface.TAG_GPS_LATITUDE,
            ExifInterface.TAG_GPS_LONGITUDE_REF,
            ExifInterface.TAG_GPS_LONGITUDE,
            ExifInterface.TAG_GPS_ALTITUDE_REF,
            ExifInterface.TAG_GPS_ALTITUDE,
            ExifInterface.TAG_GPS_TIMESTAMP,
            ExifInterface.TAG_GPS_SATELLITES,
            ExifInterface.TAG_GPS_STATUS,
            ExifInterface.TAG_GPS_MEASURE_MODE,
            ExifInterface.TAG_GPS_DOP,
            ExifInterface.TAG_GPS_SPEED_REF,
            ExifInterface.TAG_GPS_SPEED,
            ExifInterface.TAG_GPS_TRACK_REF,
            ExifInterface.TAG_GPS_TRACK,
            ExifInterface.TAG_GPS_IMG_DIRECTION_REF,
            ExifInterface.TAG_GPS_IMG_DIRECTION,
            ExifInterface.TAG_GPS_MAP_DATUM,
            ExifInterface.TAG_GPS_DEST_LATITUDE_REF,
            ExifInterface.TAG_GPS_DEST_LATITUDE,
            ExifInterface.TAG_GPS_DEST_LONGITUDE_REF,
            ExifInterface.TAG_GPS_DEST_LONGITUDE,
            ExifInterface.TAG_GPS_DEST_BEARING_REF,
            ExifInterface.TAG_GPS_DEST_BEARING,
            ExifInterface.TAG_GPS_DEST_DISTANCE_REF,
            ExifInterface.TAG_GPS_DEST_DISTANCE,
            ExifInterface.TAG_GPS_PROCESSING_METHOD,
            ExifInterface.TAG_GPS_AREA_INFORMATION,
            ExifInterface.TAG_GPS_DATESTAMP,
            ExifInterface.TAG_GPS_DIFFERENTIAL,
            ExifInterface.TAG_GPS_H_POSITIONING_ERROR,
            ExifInterface.TAG_INTEROPERABILITY_INDEX,
            ExifInterface.TAG_THUMBNAIL_IMAGE_LENGTH,
            ExifInterface.TAG_THUMBNAIL_IMAGE_WIDTH,
            ExifInterface.TAG_DNG_VERSION,
            ExifInterface.TAG_DEFAULT_CROP_SIZE,
            ExifInterface.TAG_ORF_THUMBNAIL_IMAGE,
            ExifInterface.TAG_ORF_PREVIEW_IMAGE_START,
            ExifInterface.TAG_ORF_PREVIEW_IMAGE_LENGTH,
            ExifInterface.TAG_ORF_ASPECT_FRAME,
            ExifInterface.TAG_RW2_SENSOR_BOTTOM_BORDER,
            ExifInterface.TAG_RW2_SENSOR_LEFT_BORDER,
            ExifInterface.TAG_RW2_SENSOR_RIGHT_BORDER,
            ExifInterface.TAG_RW2_SENSOR_TOP_BORDER,
            ExifInterface.TAG_RW2_ISO,
            ExifInterface.TAG_RW2_JPG_FROM_RAW,
            ExifInterface.TAG_NEW_SUBFILE_TYPE,
            ExifInterface.TAG_SUBFILE_TYPE
            /*
            There are private attributes
            ExifInterface.TAG_EXIF_IFD_POINTER,
            ExifInterface.TAG_GPS_INFO_IFD_POINTER,
            ExifInterface.TAG_INTEROPERABILITY_IFD_POINTER,
            ExifInterface.TAG_SUB_IFD_POINTER,
            ExifInterface.TAG_ORF_CAMERA_SETTINGS_IFD_POINTER,
            ExifInterface.TAG_ORF_IMAGE_PROCESSING_IFD_POINTER,
            ExifInterface.TAG_HAS_THUMBNAIL,
            ExifInterface.TAG_THUMBNAIL_LENGTH,
            ExifInterface.TAG_THUMBNAIL_DATA*/
        )
        val oldExif = ExifInterface(oldPath)
        val newExif = ExifInterface(newPath)
        attributes.forEach { attribute ->
            oldExif.getAttribute(attribute)?.let { value ->
                newExif.setAttribute(attribute, value)
            }
        }
        newExif.saveAttributes()
    }

    /**
     * Get orientation of bitmap.
     *
     * @param filePath : link of bitmap in sdcard
     * @return Orientation of bitmap
     */
    private fun getOrientation(filePath: String?): Int {
        var ori = 0
        if (filePath != null) {
            val exif: ExifInterface
            try {
                if (filePath.contains("file://")) {
                    exif = ExifInterface(filePath.substring(7))
                } else {
                    exif = ExifInterface(filePath)
                }
                val exifOrientation = exif.getAttributeInt(
                    ExifInterface.TAG_ORIENTATION,
                    ExifInterface.ORIENTATION_NORMAL
                )
                when (exifOrientation) {
                    ExifInterface.ORIENTATION_UNDEFINED -> {
                    }
                    ExifInterface.ORIENTATION_NORMAL -> {
                    }
                    ExifInterface.ORIENTATION_ROTATE_180 -> ori = 180
                    ExifInterface.ORIENTATION_ROTATE_90 -> ori = 90
                    ExifInterface.ORIENTATION_ROTATE_270 -> ori = 270
                    else -> {
                    }
                }
            } catch (e: IOException) {
                Log.e(TAG, "getOrientation(String filePath) method: ", e)
            }
        }
        return ori
    }



回答4:


See my answer at (Which doesn't use a while loop): How to reduce image size into 1MB

This method works if your current passed Bitmap is in the ARGB_8888 configuration (So 4 bytes per pixel. When it isn't ARGB_8888 you can convert it to that bitmap by using:

/**
 * Convert a Bitmap to a Bitmap that has 4 bytes per pixel
 * @param input The bitmap to convert to a 4 bytes per pixel Bitmap
 * 
 * @return The converted Bitmap. Note: The caller of this method is 
 * responsible for reycling the input
 */
public static Bitmap to4BytesPerPixelBitmap(@NonNull final Bitmap input){
    final Bitmap bitmap = Bitmap.createBitmap(input.width, input.height, Bitmap.Config.ARGB_8888);
    // Instantiate the canvas to draw on:
    final Canvas canvas = new Canvas(bitmap);
    canvas.drawBitmap(input, 0, 0, null);
    // Return the new bitmap:
    return bitmap;  
}   


来源:https://stackoverflow.com/questions/51919925/compress-bitmap-to-a-specific-byte-size-in-android

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!