Load bitmap hiệu quả

Trong thời đại máy ảnh trong điện thoại ngày càng cao cấp và có thể tạo ra file ảnh có kích thước vượt qua 2MB, sử dụng thường xuyên các file ảnh lớn như vậy sẽ dễ gây tràn bộ nhớ. Ngoài ra hình ảnh có thể được chụp theo chiều dọc hoặc ngang, nên khi load thường bị xoay không đúng chiều mong muốn. Để giải quyết những vấn đề này, ta cần nén nhỏ hình và đặt đúng chiều của hình trước khi load vào bộ nhớ. Dưới đây là ví dụ hướng dẫn cách load hình ảnh được nén và giữ đúng ratio của hình cũng như không giảm chất lượng hình.

Thêm permission trong AndroidManifest.xml:

    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />

    <application
    .....

Layout màn hình chính:

Code trong activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context="com.example.setimage.MainActivity">

    <ImageView
        android:id="@+id/image_to_set"
        android:layout_width="300dp"
        android:layout_height="300dp"
        android:scaleType="centerCrop"
        android:layout_gravity="center_horizontal"
        android:background="@color/colorAccent" />

    <LinearLayout
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center_horizontal"
        android:orientation="horizontal">

        <Button
            android:id="@+id/action_camera"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="From camera" />

        <Button
            android:id="@+id/action_gallery"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="From gallery" />
    </LinearLayout>

</LinearLayout>

Code trong MainActivity:

public class MainActivity extends AppCompatActivity implements View.OnClickListener {

    private static final int CAMERA_INTENT = 1;
    private static final int GALLERY_INTENT = 2;
    private ImageView image;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        image = (ImageView) findViewById(R.id.image_to_set);
        Button cameraButton = (Button) findViewById(R.id.action_camera);
        Button galleryButton = (Button) findViewById(R.id.action_gallery);

        cameraButton.setOnClickListener(this);
        galleryButton.setOnClickListener(this);
    }

    @Override
    public void onClick(View v) {
        switch (v.getId()) {
            case R.id.action_camera:
                onCameraButtonPressed();
                break;
            case R.id.action_gallery:
                onGalleryButtonPressed();
                break;
            default:
                break;
        }
    }

    private void onCameraButtonPressed() {
        Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
        startActivityForResult(intent, CAMERA_INTENT);
    }

    private void onGalleryButtonPressed() {
        Intent intent = new Intent();
        intent.setType("image/*");
        intent.setAction(Intent.ACTION_GET_CONTENT);

        startActivityForResult(Intent.createChooser(intent,
                "Complete action using"), GALLERY_INTENT);
    }

    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        if (resultCode == RESULT_OK && requestCode == CAMERA_INTENT) {

        } else if (resultCode == RESULT_OK && requestCode == GALLERY_INTENT) {
            Uri uri = data.getData();
            String filePath = getRealPathFromURI(MainActivity.this, uri);
            int reqHeight = image.getHeight();
            int reqWidth = image.getWidth();
            Bitmap bitmap = ImageUtils.compressImageFromPath(filePath, reqWidth, reqHeight);
            image.setImageBitmap(bitmap);
        }
    }

Các hàm quan trọng:

Hàm compressImageFromPath: compress file ảnh và rotate ảnh đúng chiều (có giải thích trong code):

public static Bitmap compressImageFromPath(String filePath, int maxWidth, int maxHeight) {
        Bitmap scaledBitmap = null;

        BitmapFactory.Options options = new BitmapFactory.Options();

        //Khi set trường này thành true, máy sẽ không load các pixel trong bitmap mà chỉ load phạm vi của nó.
        options.inJustDecodeBounds = true;
        Bitmap bmp = BitmapFactory.decodeFile(filePath, options);

        int actualHeight = options.outHeight;
        int actualWidth = options.outWidth;


        float imgRatio = (float) actualWidth / (float) actualHeight;
        float maxRatio = maxWidth / maxHeight;

        //tính toán chiều cao/dài sau khi nén theo đúng tỉ lệ của hình gốc
        if (actualHeight > maxHeight || actualWidth > maxWidth) {
            if (imgRatio < maxRatio) {
                imgRatio = (float) maxHeight / actualHeight;
                actualWidth = (int) (imgRatio * actualWidth);
                actualHeight = (int) maxHeight;
            } else if (imgRatio > maxRatio) {
                imgRatio = (float) maxWidth / actualWidth;
                actualHeight = (int) (imgRatio * actualHeight);
                actualWidth = (int) maxWidth;
            } else {
                actualHeight = (int) maxHeight;
                actualWidth = (int) maxWidth;
            }
        }

        //giá trị inSampleSize quyết định tỉ lệ giữa hình gốc với hình đã nén
        options.inSampleSize = calculateInSampleSize(options, actualWidth, actualHeight);

        // đặt giá trị inJustDecodeBounds lại thành false để load được bitmap với các pixel
        options.inJustDecodeBounds = false;

        // tùy chọn này cho phép android phân bộ nhớ riêng cho bitmap
        options.inTempStorage = new byte[16 * 1024];

        try {
            // load bitmap từ đường dẫn
            bmp = BitmapFactory.decodeFile(filePath, options);
        } catch (OutOfMemoryError exception) {
            exception.printStackTrace();

        }

        if (actualHeight <= 0 || actualWidth <= 0)
            return null;

        try {
            scaledBitmap = Bitmap.createBitmap(actualWidth, actualHeight, Bitmap.Config.ARGB_8888);
        } catch (OutOfMemoryError exception) {
            exception.printStackTrace();
            return null;
        }

        float ratioX = actualWidth / (float) options.outWidth;
        float ratioY = actualHeight / (float) options.outHeight;
        float middleX = actualWidth / 2.0f;
        float middleY = actualHeight / 2.0f;

        Matrix scaleMatrix = new Matrix();
        scaleMatrix.setScale(ratioX, ratioY, middleX, middleY);

        Canvas canvas = new Canvas(scaledBitmap);
        canvas.setMatrix(scaleMatrix);
        canvas.drawBitmap(bmp, middleX - bmp.getWidth() / 2, middleY - bmp.getHeight() / 2, new Paint(Paint.FILTER_BITMAP_FLAG));

        // kiểm tra orientation của ảnh và xoay đúng chiều cho bitmap
        ExifInterface exif;
        try {
            exif = new ExifInterface(filePath);

            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);
            }

            scaledBitmap = Bitmap.createBitmap(scaledBitmap, 0, 0,
                    scaledBitmap.getWidth(), scaledBitmap.getHeight(), matrix,
                    true);
        } catch (IOException e) {
            e.printStackTrace();
        }

        return scaledBitmap;
    }

(theo http://voidcanvas.com/whatsapp-like-image-compression-in-android/)

Trong hàm có 2 điểm quan trọng là:

  1. Thiết lập trường inJustDecodeBounds thành true trong BitmapFactory.Options để load kích thước bitmap mà không phải tải toàn bộ pixel vào bộ nhớ.
  2. Sử dụng ExifInterface để đọc thông tin về orientation của file ảnh.

Hàm getRealPathFromURI: lấy đường dẫn đến file từ Uri nhận về khi chụp/ chọn ảnh từ thư viện.

private String getRealPathFromURI(Context context, Uri uri) {

        String filePath = "";
        String wholeID = DocumentsContract.getDocumentId(uri);

        // Split at colon, use second item in the array
        String id = wholeID.split(":")[1];

        String[] column = { MediaStore.Images.Media.DATA };

        // where id is equal to
        String sel = MediaStore.Images.Media._ID + "=?";

        Cursor cursor = context.getContentResolver().query(MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
                column, sel, new String[]{ id }, null);

        int columnIndex = 0;
        if (cursor != null) {
            columnIndex = cursor.getColumnIndex(column[0]);
            if (cursor.moveToFirst()) {
                filePath = cursor.getString(columnIndex);
            }
            cursor.close();
        }

        return filePath;
    }

Lưu ý: hàm này chỉ dùng cho API từ 19 trở lên (từ Lollipop). Để lấy đường dẫn cho API <19, các bạn có thể tham khảo link ở đây.

Vậy là đã đủ các hàm cơ bản. Bạn có thể build để trải nghiệm luôn. Chúc các bạn thành công!

Người viết: MinhNhật.

__________________

Nguồn tham khảo:

  1. http://voidcanvas.com/whatsapp-like-image-compression-in-android/
  2. https://developer.android.com/topic/performance/graphics/load-bitmap.html
  3. https://developer.android.com/training/camera/photobasics.html

results matching ""

    No results matching ""