Search code examples
javaandroidandroid-studioandroid-13

How to compress an image in Android 13 APIs


I have made image compression long ago. It was working well until Android X, but above Android it’s not working.

public class ImageCompress {

    private static final int IMAGE_QUALITY = 99;
    private static final int MAX_SIZE_PIXELS = 1200;

    public static Bitmap decodeFile(File file, int width, int height) {
        try {
            // Decode image size
            BitmapFactory.Options options = new BitmapFactory.Options();
            options.inJustDecodeBounds = true;
            BitmapFactory.decodeStream(new FileInputStream(file), null, options);

            // The new size we want to scale to
            final int REQUIRED_WIDTH = width;
            final int REQUIRED_HIGHT = height;

            // Find the correct scale value. It should be the power of 2.
            int scale = 1;
            while (options.outWidth / scale / 2 >= REQUIRED_WIDTH
                    && options.outHeight / scale / 2 >= REQUIRED_HIGHT)
                scale *= 2;

            // Decode with inSampleSize
            BitmapFactory.Options newOptions = new BitmapFactory.Options();
            newOptions.inSampleSize = scale;
            return BitmapFactory.decodeStream(new FileInputStream(file), null, newOptions);

        } catch (FileNotFoundException e) {
            return null;
        }
    }

    public static File compressImage(final File file, String outputDirectory, Context context) {
        String path = file.getPath();
        String format = path.substring(path.lastIndexOf(".")).substring(1);
        Bitmap source;
        try {
            source = decodeFile(file, MAX_SIZE_PIXELS, MAX_SIZE_PIXELS);
        } catch (Exception e) {
            Log.d(ImageCompress.class.toString(), e.toString() + " Something happened wrong");
            return null;
        }

        Bitmap.CompressFormat compressFormat;

        // If PNG or WebP have the allowed resolution, then do not compress it
        if ("png".equals(format) || "webp".equals(format)) return file;

        // Select format
        switch (format) {
            case "png":
                compressFormat = Bitmap.CompressFormat.PNG;
                break;
            case "webp":
                compressFormat = Bitmap.CompressFormat.WEBP;
                break;
            case "gif":
                return file;
            default:
                compressFormat = Bitmap.CompressFormat.JPEG;
        }

        // Resize image
        Bitmap resizedBmp;
    // I am getting an error on the source.getHeight() line
        if (source.getHeight() > MAX_SIZE_PIXELS || source.getWidth() > MAX_SIZE_PIXELS) {
            resizedBmp = resizeBitmap(source, MAX_SIZE_PIXELS);
        } else {
            resizedBmp = source;
        }
        File result = null;
        OutputStream fOut = null;
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
            ContentResolver resolver = BaseApplication.getInstance().getContentResolver();
            ContentValues contentValues = new ContentValues();
            contentValues.put(MediaStore.MediaColumns.DISPLAY_NAME, file.getName());
            contentValues.put(MediaStore.MediaColumns.MIME_TYPE, "image/" + format);
            contentValues.put(MediaStore.MediaColumns.RELATIVE_PATH, Constants.MULTIMEDIA_CHATROOM_11);
            Uri imageUri = resolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, contentValues);
            try {
                fOut = resolver.openOutputStream(imageUri);
                result = new File(Utils.getPath(BaseApplication.getInstance(), imageUri));
            } catch (FileNotFoundException e) {
                e.printStackTrace();
            }
        } else {
            // create directory if not exist
            File directory = new File(outputDirectory);
            int code = context.getPackageManager().checkPermission(
                    android.Manifest.permission.WRITE_EXTERNAL_STORAGE,
                    context.getPackageName());
            if (code == PackageManager.PERMISSION_GRANTED) {
                Log.i(ImageCompress.class.getSimpleName(), "creating directory");
                if (!directory.isDirectory() || !directory.exists()) {
                    directory.mkdirs();
                }
            } else {
                Log.i(ImageCompress.class.getSimpleName(), "failed access");
            }

            // Compress image
            result = new File(outputDirectory, file.getName());

            try {
                if (!result.exists()) {
                    result.createNewFile();
                    fOut = new FileOutputStream(result);
                } else {
                    fOut = new FileOutputStream(result);
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        try {
            resizedBmp.compress(compressFormat, IMAGE_QUALITY, fOut);
            fOut.flush();
            fOut.close();
            source.recycle();
            resizedBmp.recycle();

        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }

        // Copy EXIF orientation from the original image
        try {
            ExifInterface oldExif = new ExifInterface(file.getPath());
            String exifOrientation = oldExif.getAttribute(ExifInterface.TAG_ORIENTATION);
            if (exifOrientation != null) {
                ExifInterface newExif = new ExifInterface(result.getPath());
                newExif.setAttribute(ExifInterface.TAG_ORIENTATION, exifOrientation);
                newExif.saveAttributes();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
        return result;
    }

    private static Bitmap resizeBitmap(Bitmap source, int maxSizePixels) {
        int targetWidth, targetHeight;
        double aspectRatio;

        if (source.getWidth() > source.getHeight()) {
            targetWidth = maxSizePixels;
            aspectRatio = (double) source.getHeight() / (double) source.getWidth();
            targetHeight = (int) (targetWidth * aspectRatio);
        } else {
            targetHeight = maxSizePixels;
            aspectRatio = (double) source.getWidth() / (double) source.getHeight();
            targetWidth = (int) (targetHeight * aspectRatio);
        }

        return Bitmap.createScaledBitmap(source, targetWidth, targetHeight, false);
    }
}

My app is made for social entertainment purposes, so I don't have storage access like before due to policies. So we can use only the session scope way to access files. I am getting an error at line source.getHeight() and source.getWidth() as both are zero. How can I fix this this issue for all Android versions?

Error:

Getting error

Please note: I noticed this error comes in a few pictures from the gallery.


Solution

  • If you get a NullPointerException on source.getHeight() then this means source has been set to null.

    The only point where sourceis set is on the line

    source = decodeFile(file, MAX_SIZE_PIXELS, MAX_SIZE_PIXELS);
    

    This means the method decodeFile has to return a null value.

    Looking at the method decodeFile you can see tha there are only two different code paths that return a value. The first code path returns the value of BitmapFactory.decodeStream(..) which AFAIK should never return null.

    The it is most likely the other code path in the FileNotFoundException catch clause.

    public static Bitmap decodeFile(File file, int width, int height) {
        try {
            ....
            return BitmapFactory.decodeStream(new FileInputStream(file), null, newOptions);
    
        } catch (FileNotFoundException e) {
            return null;
        }
    }
    

    This leads to the conclusion that at some point you are trying to read a File that does not exist or on that you don't have read permissions.

    As your app is older the chance is high that if it is a permission problem and the file is on the shared storage (/sdcard or subfolder) this is because the app does not make use of scoped storage system introduced with Android 10/11.