Compress bitmap to a specific byte size in Android

前端 未结 4 2130
再見小時候
再見小時候 2020-12-18 11:28

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 requi

相关标签:
4条回答
  • 2020-12-18 12:05

    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();
    }
    
    0 讨论(0)
  • 2020-12-18 12:07

    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;  
    }   
    
    0 讨论(0)
  • 2020-12-18 12:16

    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
        }
    
    0 讨论(0)
  • 2020-12-18 12:22

    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.

    0 讨论(0)
提交回复
热议问题