简体   繁体   English

在 Android 中将位图压缩为特定的字节大小

[英]Compress bitmap to a specific byte size in Android

Is there a way to compress Bitmap to a specific byte size?有没有办法将 Bitmap 压缩到特定的字节大小? For example, 1.5MB.例如,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.我对这个话题很陌生,想从一开始就朝着正确的方向发展。

You can calculate the size of a bitmap quite easily by width * height * bytes per pixel = size您可以很容易地通过width * height * bytes per pixel = size来计算位图的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. bytes per pixel由您的颜色模型定义,比如RGBA_F16是 8 个字节,而ARGB_8888是 4 个字节,依此类推。 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.有关位值,请参阅https://developer.android.com/reference/android/graphics/Bitmap.Config

Also see https://developer.android.com/topic/performance/graphics/manage-memory for more about bitmap memory management.另请参阅https://developer.android.com/topic/performance/graphics/manage-memory了解有关位图内存管理的更多信息。

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%.将图像缩小到 1.5mb 并不是一门精确的科学,但它的作用是如果图像大于所需,它会使用 jpeg 压缩位图并将质量降低 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();
}

This works for me.这对我有用。 Scale area of original bitmap to 50% and compress bitmap until it's size < 200k将原始位图的区域缩放到 50% 并压缩位图直到它的大小 < 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
    }

See my answer at (Which doesn't use a while loop): How to reduce image size into 1MB请参阅我的回答(不使用 while 循环): 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:如果您当前传递的位图在 ARGB_8888 配置中(所以每像素 4 个字节。当它不是 ARGB_8888 时,您可以使用以下方法将其转换为该位图:

/**
 * 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;  
}   

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM