Compressor icon indicating copy to clipboard operation
Compressor copied to clipboard

Only support for File, not InputStream or FileDescriptor

Open extorn opened this issue 4 years ago • 2 comments

I've had an issue because I am using the new Content Uris and conversion to a local file isn't always possible. I'm using the java version still and have altered the code as included below, but perhaps you'll consider expanding the API in kotlin. My edit corrects bugs in my original code.

package id.zelory.compressor.tweak;

import android.content.Context;
import android.graphics.Bitmap;
import android.net.Uri;
import android.os.Build;

import androidx.annotation.RequiresApi;
import androidx.exifinterface.media.ExifInterface;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.concurrent.Callable;

import io.reactivex.Flowable;

/**
 * Created on : June 18, 2016
 * Author     : zetbaitsu
 * Name       : Zetra
 * GitHub     : https://github.com/zetbaitsu
 */
public class Compressor {
    //max width and height values of the compressed image is taken as 612x816
    private int maxWidth = 612;
    private int maxHeight = 816;
    private Bitmap.CompressFormat compressFormat = Bitmap.CompressFormat.JPEG;
    private int quality = 80;
    private String destinationDirectoryPath;

    public Compressor(Context context) {
        destinationDirectoryPath = context.getCacheDir().getPath() + File.separator + "images";
    }

    public Compressor setMaxWidth(int maxWidth) {
        this.maxWidth = maxWidth;
        return this;
    }

    public Compressor setMaxHeight(int maxHeight) {
        this.maxHeight = maxHeight;
        return this;
    }

    public Compressor setCompressFormat(Bitmap.CompressFormat compressFormat) {
        this.compressFormat = compressFormat;
        return this;
    }

    public Compressor setQuality(int quality) {
        this.quality = quality;
        return this;
    }

    public Compressor setDestinationDirectoryPath(String destinationDirectoryPath) {
        this.destinationDirectoryPath = destinationDirectoryPath;
        return this;
    }

    public File compressToFile(File imageFile) throws IOException {
        return compressToFile(imageFile, imageFile.getName());
    }

    public File compressToFile(File imageFile, String compressedFileName) throws IOException {
        return ImageUtil.compressImage(imageFile, maxWidth, maxHeight, compressFormat, quality,
                destinationDirectoryPath + File.separator + compressedFileName);
    }

    @RequiresApi(api = Build.VERSION_CODES.N)
    public File compressToFile(Context c, Uri imageUri, ExifInterface exif, String compressedFileName) throws IOException {
        return ImageUtil.compressImage(c, imageUri, exif, maxWidth, maxHeight, compressFormat, quality,
                destinationDirectoryPath + File.separator + compressedFileName);
    }

    public Bitmap compressToBitmap(File imageFile) throws IOException {
        try(FileInputStream is = new FileInputStream(imageFile)) {
            return ImageUtil.decodeSampledBitmapFromFile(is, maxWidth, maxHeight, ImageUtil.getExif(imageFile));
        }
    }

    public Flowable<File> compressToFileAsFlowable(final File imageFile) {
        return compressToFileAsFlowable(imageFile, imageFile.getName());
    }

    public Flowable<File> compressToFileAsFlowable(final File imageFile, final String compressedFileName) {
        return Flowable.defer(new Callable<Flowable<File>>() {
            @Override
            public Flowable<File> call() {
                try {
                    return Flowable.just(compressToFile(imageFile, compressedFileName));
                } catch (IOException e) {
                    return Flowable.error(e);
                }
            }
        });
    }

    public Flowable<Bitmap> compressToBitmapAsFlowable(final File imageFile) {
        return Flowable.defer(new Callable<Flowable<Bitmap>>() {
            @Override
            public Flowable<Bitmap> call() {
                try {
                    return Flowable.just(compressToBitmap(imageFile));
                } catch (IOException e) {
                    return Flowable.error(e);
                }
            }
        });
    }
}


package id.zelory.compressor.tweak;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Matrix;
import android.graphics.Rect;
import android.net.Uri;
import android.os.Build;
import android.system.ErrnoException;
import android.system.Os;
import android.system.OsConstants;

import androidx.annotation.RequiresApi;
import androidx.exifinterface.media.ExifInterface;

import java.io.File;
import java.io.FileDescriptor;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;

/**
 * Created on : June 18, 2016
 * Author     : zetbaitsu
 * Name       : Zetra
 * GitHub     : https://github.com/zetbaitsu
 */
class ImageUtil {

    private ImageUtil() {

    }

    @RequiresApi(api = Build.VERSION_CODES.N)
    static File compressImage(Context c, Uri imageUri, ExifInterface exif, int reqWidth, int reqHeight, Bitmap.CompressFormat compressFormat, int quality, String destinationPath) throws IOException {
        FileOutputStream fileOutputStream = null;
        File destFolder = new File(destinationPath).getParentFile();
        if (!destFolder.exists()) {
            destFolder.mkdirs();
        }
        fileOutputStream = new FileOutputStream(destinationPath);
        // write the compressed bitmap at the destination specified by destinationPath.
        decodeSampledBitmapFromFile(c, imageUri, reqWidth, reqHeight, exif).compress(compressFormat, quality, fileOutputStream);

        return new File(destinationPath);
    }

    static File compressImage(File imageFile, int reqWidth, int reqHeight, Bitmap.CompressFormat compressFormat, int quality, String destinationPath) throws IOException {
        FileOutputStream fileOutputStream = null;
        File destFolder = new File(destinationPath).getParentFile();
        if (!destFolder.exists()) {
            destFolder.mkdirs();
        }
        try {
            fileOutputStream = new FileOutputStream(destinationPath);
            // write the compressed bitmap at the destination specified by destinationPath.
            try(FileInputStream is = new FileInputStream(imageFile)){
                decodeSampledBitmapFromFile(is, reqWidth, reqHeight, getExif(imageFile)).compress(compressFormat, quality, fileOutputStream);
            }
        } finally {
            if (fileOutputStream != null) {
                fileOutputStream.flush();
                fileOutputStream.close();
            }
        }

        return new File(destinationPath);
    }

    @RequiresApi(api = Build.VERSION_CODES.N)
    protected static ExifInterface getExif(FileDescriptor fd) throws IOException {
        return new ExifInterface(fd);
    }

    protected static ExifInterface getExif(File f) throws IOException {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
            return new ExifInterface(f);
        } else {
            return new ExifInterface(f.getPath());
        }
    }

    private static boolean isSeekableFD(FileDescriptor fd) {
        if (Build.VERSION.SDK_INT >= 21) {
            try {
                try {
                    Os.lseek(fd, 0, OsConstants.SEEK_CUR);
                } catch (ErrnoException e) {
                    throw new IOException("Failed to seek file descriptor", e);
                }
                return true;
            } catch (Exception e) {
                return false;
            }
        }
        return false;
    }

    static Bitmap decodeSampledBitmapFromFile(InputStream imageFile, int reqWidth, int reqHeight, ExifInterface exif) {
        // First decode with inJustDecodeBounds=true to check dimensions
        BitmapFactory.Options options = new BitmapFactory.Options();
        options.inJustDecodeBounds = true;
        Rect paddingRect = new Rect();
        BitmapFactory.decodeStream(imageFile, paddingRect, options);

        // Calculate inSampleSize
        options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);

        // Decode bitmap with inSampleSize set
        options.inJustDecodeBounds = false;

        Bitmap scaledBitmap = BitmapFactory.decodeStream(imageFile, paddingRect, options);

        Matrix matrix = getRotationMatrix(exif);

        scaledBitmap = Bitmap.createBitmap(scaledBitmap, 0, 0, scaledBitmap.getWidth(), scaledBitmap.getHeight(), matrix, true);
        return scaledBitmap;
    }

    static Bitmap decodeSampledBitmapFromFile(Context c, Uri imageUri, int reqWidth, int reqHeight, ExifInterface exif) throws IOException {
        // First decode with inJustDecodeBounds=true to check dimensions
        BitmapFactory.Options options = new BitmapFactory.Options();
        options.inJustDecodeBounds = true;
        Rect paddingRect = new Rect();
        try(InputStream is = c.getContentResolver().openInputStream(imageUri)){
            BitmapFactory.decodeStream(is, paddingRect, options);
        }

        // Calculate inSampleSize
        options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);

        // Decode bitmap with inSampleSize set
        options.inJustDecodeBounds = false;

        try(InputStream is = c.getContentResolver().openInputStream(imageUri)) {
            Bitmap scaledBitmap = BitmapFactory.decodeStream(is, paddingRect, options);
            Matrix matrix = getRotationMatrix(exif);

            scaledBitmap = Bitmap.createBitmap(scaledBitmap, 0, 0, scaledBitmap.getWidth(), scaledBitmap.getHeight(), matrix, true);
            return scaledBitmap;
        }
    }

    private static Matrix getRotationMatrix(ExifInterface exif) {
        //check the rotation of the image and display it properly
        int orientation = exif.getAttributeInt(ExifInterface.TAG_ORIENTATION, 0);
        Matrix matrix = new Matrix();
        if (orientation == 6) {
            matrix.postRotate(90);
        } else if (orientation == 3) {
            matrix.postRotate(180);
        } else if (orientation == 8) {
            matrix.postRotate(270);
        }
        return matrix;
    }

    private static int calculateInSampleSize(BitmapFactory.Options options, int reqWidth, int reqHeight) {
        // Raw height and width of image
        final int height = options.outHeight;
        final int width = options.outWidth;
        int inSampleSize = 1;

        if (height > reqHeight || width > reqWidth) {

            final int halfHeight = height / 2;
            final int halfWidth = width / 2;

            // Calculate the largest inSampleSize value that is a power of 2 and keeps both
            // height and width larger than the requested height and width.
            while ((halfHeight / inSampleSize) >= reqHeight && (halfWidth / inSampleSize) >= reqWidth) {
                inSampleSize *= 2;
            }
        }

        return inSampleSize;
    }
}


extorn avatar Aug 13 '20 15:08 extorn

hello, any update mas? @zetbaitsu

nashihu avatar Jan 06 '21 03:01 nashihu

I've altered the original post correcting bugs in the code. It didn't always work as I expected. The code now works with Android Uris correctly. Making the code generic was less of a concern for me, but hopefully the above is useful

extorn avatar Jan 06 '21 09:01 extorn