Tạo hiệu ứng cho ảnh trong Android với thư viện GPUImage

1. Giới thiệu

GPUImage là một framework khá nổi tiếng trong việc tạo hiệu ứng cho những bức ảnh, nhưng nó lại chỉ hỗ trợ iOS. Thật may, một tổ chức là CyberAgent đã phát triển một thư viện tương tự cho phía Android giúp những người lập trình Android có thêm một công cụ thật hữu ích.

Link thư viện: https://github.com/CyberAgent/android-gpuimage

2. Cài đặt

Bạn chỉ cần thêm vào app build.gradle đoạn sau:

 repositories {
    mavenCentral()
}

dependencies {
    compile 'jp.co.cyberagent.android.gpuimage:gpuimage-library:1.3.0'
}

Chú ý: Có những trường hợp app chạy bị lỗi do thiếu thư viện libgpuimage.so native library, bạn cần làm như sau:

  • Copy file yuv-decoder.c từ thư viện vào jni folder trong prọect
  • Tạo file Android.mk với nội dung như sau:
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE    := gpuimage-library
LOCAL_SRC_FILES := yuv-decoder.c
include $(BUILD_SHARED_LIBRARY)
APP_ABI := armeabi armeabi-v7a mips x86
APP_PLATFORM := android-9
  • Chạy ndk-build
  • Copy những thư viện mới sinh ra vào folder jniLibs

3. Tạo một ứng dụng camera với GPUImage

Điểm đặc biệt của GPUImage nữa là nó hỗ trợ Camera Preview, mà có thể đưa hiệu ứng, filter vào từng frame của camera preview. Tôi xin giới thiệu cách tạo 1 Camera Preview sau đây:

  • Tạo 1 class CameraHelper, để quản lý việc mở Camera, lấy thông tin của camera,...
public class CameraHelper {
    private final CameraHelperImpl mImpl;

    public CameraHelper(final Context context) {
        if (SDK_INT >= GINGERBREAD) {
            mImpl = new CameraHelperGB();
        } else {
            mImpl = new CameraHelperBase(context);
        }
    }

    public interface CameraHelperImpl {
        int getNumberOfCameras();

        Camera openCamera(int id);

        Camera openDefaultCamera();

        Camera openCameraFacing(int facing);

        boolean hasCamera(int cameraFacingFront);

        void getCameraInfo(int cameraId, CameraInfo2 cameraInfo);
    }

    public int getNumberOfCameras() {
        return mImpl.getNumberOfCameras();
    }

    public Camera openCamera(final int id) {
        return mImpl.openCamera(id);
    }

    public Camera openDefaultCamera() {
        return mImpl.openDefaultCamera();
    }

    public Camera openFrontCamera() {
        return mImpl.openCameraFacing(CameraInfo.CAMERA_FACING_FRONT);
    }

    public Camera openBackCamera() {
        return mImpl.openCameraFacing(CameraInfo.CAMERA_FACING_BACK);
    }

    public boolean hasFrontCamera() {
        return mImpl.hasCamera(CameraInfo.CAMERA_FACING_FRONT);
    }

    public boolean hasBackCamera() {
        return mImpl.hasCamera(CameraInfo.CAMERA_FACING_BACK);
    }

    public void getCameraInfo(final int cameraId, final CameraInfo2 cameraInfo) {
        mImpl.getCameraInfo(cameraId, cameraInfo);
    }

    public void setCameraDisplayOrientation(final Activity activity,
                                            final int cameraId, final Camera camera) {
        int result = getCameraDisplayOrientation(activity, cameraId);
        camera.setDisplayOrientation(result);
    }

    public int getCameraDisplayOrientation(final Activity activity, final int cameraId) {
        int rotation = activity.getWindowManager().getDefaultDisplay()
                .getRotation();
        int degrees = 0;
        switch (rotation) {
            case Surface.ROTATION_0:
                degrees = 0;
                break;
            case Surface.ROTATION_90:
                degrees = 90;
                break;
            case Surface.ROTATION_180:
                degrees = 180;
                break;
            case Surface.ROTATION_270:
                degrees = 270;
                break;
        }

        int result;
        CameraInfo2 info = new CameraInfo2();
        getCameraInfo(cameraId, info);
        if (info.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) {
            result = (info.orientation + degrees) % 360;
        } else { // back-facing
            result = (info.orientation - degrees + 360) % 360;
        }
        return result;
    }

    public static class CameraInfo2 {
        public int facing;
        public int orientation;
    }
}
  • Ở lớp trên ta thấy có 1 interface là CameraHelpImpl với 2 lớp implement interface đó là CameraHelperBase và CameraHelperGB để dùng cho các bản Android < 2.3 và >= 2.3. Tạo 2 lớp đó như dưới đây:

CameraHelperBase

public class CameraHelperBase implements CameraHelper.CameraHelperImpl {

    private final Context mContext;

    public CameraHelperBase(final Context context) {
        mContext = context;
    }

    @Override
    public int getNumberOfCameras() {
        return hasCameraSupport() ? 1 : 0;
    }

    @Override
    public Camera openCamera(final int id) {
        return Camera.open();
    }

    @Override
    public Camera openDefaultCamera() {
        return Camera.open();
    }

    @Override
    public boolean hasCamera(final int facing) {
        if (facing == CameraInfo.CAMERA_FACING_BACK) {
            return hasCameraSupport();
        }
        return false;
    }

    @Override
    public Camera openCameraFacing(final int facing) {
        if (facing == CameraInfo.CAMERA_FACING_BACK) {
            return Camera.open();
        }
        return null;
    }

    @Override
    public void getCameraInfo(final int cameraId, final CameraHelper.CameraInfo2 cameraInfo) {
        cameraInfo.facing = Camera.CameraInfo.CAMERA_FACING_BACK;
        cameraInfo.orientation = 90;
    }

    private boolean hasCameraSupport() {
        return mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_CAMERA);
    }
}

CameraHelperGB

public class CameraHelperGB implements CameraHelperImpl {

    @Override
    public int getNumberOfCameras() {
        return Camera.getNumberOfCameras();
    }

    @Override
    public Camera openCamera(final int id) {
        return Camera.open(id);
    }

    @Override
    public Camera openDefaultCamera() {
        return Camera.open(0);
    }

    @Override
    public boolean hasCamera(final int facing) {
        return getCameraId(facing) != -1;
    }

    @Override
    public Camera openCameraFacing(final int facing) {
        return Camera.open(getCameraId(facing));
    }

    @Override
    public void getCameraInfo(final int cameraId, final CameraInfo2 cameraInfo) {
        CameraInfo info = new CameraInfo();
        Camera.getCameraInfo(cameraId, info);
        cameraInfo.facing = info.facing;
        cameraInfo.orientation = info.orientation;
    }

    private int getCameraId(final int facing) {
        int numberOfCameras = Camera.getNumberOfCameras();
        CameraInfo info = new CameraInfo();
        for (int id = 0; id < numberOfCameras; id++) {
            Camera.getCameraInfo(id, info);
            if (info.facing == facing) {
                return id;
            }
        }
        return -1;
    }
}
  • Trong activity chứa camera preview, tạo 1 lớp CameraLoader quản lý việc thiết lập, release, đổi orientation cho Camera bằng cách sử dụng lớp GPUImage
 private class CameraLoader {

        private int mCurrentCameraId = 0;
        private Camera mCameraInstance;

        public void onResume() {
            setUpCamera(mCurrentCameraId);
        }

        public void onPause() {
            releaseCamera();
        }

        public void switchCamera() {
            releaseCamera();
            mCurrentCameraId = (mCurrentCameraId + 1) % mCameraHelper.getNumberOfCameras();
            setUpCamera(mCurrentCameraId);
        }

        private void setUpCamera(final int id) {
            mCameraInstance = getCameraInstance(id);
            Camera.Parameters parameters = mCameraInstance.getParameters();
            // TODO adjust by getting supportedPreviewSizes and then choosing
            List<Camera.Size> list = mCameraInstance.getParameters().getSupportedPreviewSizes();
            // the best one for screen size (best fill screen)
            if (parameters.getSupportedFocusModes().contains(
                    Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE)) {
                parameters.setFocusMode(Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE);
            }
            mCameraInstance.setParameters(parameters);

            int orientation = mCameraHelper.getCameraDisplayOrientation(
                    CameraActivity.this, mCurrentCameraId);
            CameraHelper.CameraInfo2 cameraInfo = new CameraHelper.CameraInfo2();
            mCameraHelper.getCameraInfo(mCurrentCameraId, cameraInfo);
            boolean flipHorizontal = cameraInfo.facing == Camera.CameraInfo.CAMERA_FACING_FRONT;
            mGPUImage.setUpCamera(mCameraInstance, orientation, flipHorizontal, false);
        }

        /** A safe way to get an instance of the Camera object. */
        private Camera getCameraInstance(final int id) {
            Camera c = null;
            try {
                c = mCameraHelper.openCamera(id);
            } catch (Exception e) {
                e.printStackTrace();
            }
            return c;
        }

        private void releaseCamera() {
            mGPUImage.deleteImage();
            mCameraInstance.setPreviewCallback(null);
            mCameraInstance.release();
            mCameraInstance = null;
        }
    }
  • Đến đây, việc tạo các class hỗ trợ Camera preview đã hoàn thành, giờ chúng ta chỉ cần tạo 1 GLSurfaceView để hiển thị Camera preview lên đó
<android.opengl.GLSurfaceView
            android:id="@+id/surfaceView"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            />
private GPUImage mGPUImage;
private CameraHelper mCameraHelper;
private CameraLoader mCamera;
...

mGPUImage = new GPUImage(this);
mGPUImage.setGLSurfaceView((GLSurfaceView) findViewById(R.id.surfaceView));

mCameraHelper = new CameraHelper(this);
mCamera = new CameraLoader();

Vậy là ta đã có 1 ứng dụng Camera preview dùng GPUImage, để thêm hiệu ứng cho camera, ta chỉ cần thêm

mGPUImage.setFilter(new GPUImageFilter...);

4. Demo

Dưới đây là demo CameraPreview sử dụng GPUImage

https://github.com/dzung0709/CameraPreview