--

Thursday, 5 January 2017

Profile picture picker

The user registration of any kind of application selecting user profile picture is unavoidable. I am always meshed up with setting up an image to the profile picture image view. After a long struggle, I figured out the proper way to handle image byte from Gallery and custom camera using Camera API. I would like to share the complete flow one by one.







We have two options to pick a picture from Gallery and to take the picture from Custom Camera.

From Gallery

 

By using Intent, We open device gallery.

Intent intent = new Intent();
intent.setType("image/*");
intent.setAction(Intent.ACTION_GET_CONTENT);
startActivityForResult(Intent.createChooser(intent, "Select Picture"), PICK_IMAGE_REQUEST);

Once user pick their desired picture from Gallery, the image byte is received at onActivityResult()

if (requestCode == PICK_IMAGE_REQUEST && resultCode == RESULT_OK && data != null && data.getData() != null) {
    try{
        Uri selectedImage = data.getData();
        InputStream imageStream = null;
        try {
            imageStream = getContentResolver().openInputStream(
                    selectedImage);
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        }
        Bitmap bmp = BitmapFactory.decodeStream(imageStream);

        ByteArrayOutputStream stream = new ByteArrayOutputStream();
        bmp.compress(Bitmap.CompressFormat.JPEG, 50, stream);
        imgProfilePic.setImageBitmap(bmp);
    }
    catch (Exception ex){
        Log.e(TAG,"IOException:"+ex.getLocalizedMessage());
        finish();
    }
}

The selected image Uri is decoded into Bitmap with compression using ByteArrayOutputStream. The resulted Bitmap is assigned to ImageView. 

From Custom Camera

 


private void checkCameraPermission() {
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
        Log.d(TAG, "SDK >= 23");
        if (this.checkSelfPermission(Manifest.permission.CAMERA)
                != PackageManager.PERMISSION_GRANTED) {
            Log.d(TAG, "Request permission");
            ActivityCompat.requestPermissions(this,
                    new String[]{Manifest.permission.CAMERA},
                    CAMERA_PERMISSION_REQUEST_CODE);
        }
        else {
            Log.d(TAG, "Permission granted: taking pic");
            openCamera();
        }
    }
    else {
        Log.d(TAG, "Android < 6.0");
        openCamera();
    }
}

private void openCamera(){
    Intent intent=new Intent(ProfileActivity.this,AndroidCameraUtils.class);
    startActivityForResult(intent,TAKE_IMAGE_REQUEST);
}

We have to check Camera permission before start the Custom camera activity. The SurfaceView is placed in the XML layout. We have to implement SurfaceHolder.Callback at Custom Camera activity.
@Overridepublic void surfaceCreated(SurfaceHolder holder) {
    // TODO Auto-generated method stub    camera = Camera.open(Camera.CameraInfo.CAMERA_FACING_FRONT);
}

@Overridepublic void surfaceDestroyed(SurfaceHolder holder) {
    // TODO Auto-generated method stub    camera.stopPreview();
    camera.release();
    camera = null;
    previewing = false;
}
 
@Overridepublic void surfaceChanged(SurfaceHolder holder, int format,
                           int width,  int height) {
    if(previewing){
        camera.stopPreview();
        previewing = false;
    }

    if(isBackCameraClicked){
        camera.release();
        camera = null;
        camera = Camera.open(Camera.CameraInfo.CAMERA_FACING_FRONT);
    }

    if (camera != null){
        try {
            setCameraDisplayOrientation(AndroidCameraUtils.this,
                    Camera.CameraInfo.CAMERA_FACING_FRONT, camera);
            camera.setPreviewDisplay(surfaceHolder);
            camera.startPreview();
            previewing = true;
        } catch (IOException e) {
            // TODO Auto-generated catch block            e.printStackTrace();
        }
    }
}

By passing camera id either back or front facing to open method to initiate Camera from surfaceCreated() method of SurfaceView. The camera preview is started at surfaceChanged(). 

public void onTakePictureClick(View v){
    camera.takePicture(null,null, myPictureCallback_JPG);
} 
 With JPGcallback, takePicture() method is called. The image byte is received from
myPictureCallback_JPG.
 
Camera.PictureCallback myPictureCallback_JPG = new Camera.PictureCallback(){

    @Override    public void onPictureTaken(byte[] arg0, Camera arg1) {
        // TODO Auto-generated method stub        if(arg0!=null){
            saveCameraImage(arg0);
        }
    }};

The raw image byte needs to do fine tuning based on CameraID so that the taken picture is assigned to ImageView without inverted.

private Bitmap makeSquare(byte[] data, int cameraID) {
    int width;
    int height;
    Matrix matrix = new Matrix();
    Camera.CameraInfo info = new Camera.CameraInfo();
    Camera.getCameraInfo(cameraID, info);
    // Convert ByteArray to Bitmap    Bitmap bitPic = BitmapFactory.decodeByteArray(data, 0, data.length);
    width = bitPic.getWidth();
    height = bitPic.getHeight();
    
    if (info.facing == Camera.CameraInfo.CAMERA_FACING_FRONT)
    {
        float[] mirrorY = { -1, 0, 0, 0, 1, 0, 0, 0, 1};
        Matrix matrixMirrorY = new Matrix();
        matrixMirrorY.setValues(mirrorY);
        matrix.postConcat(matrixMirrorY);
    }
    matrix.postRotate(90);
    // Create new Bitmap out of the old one    Bitmap bitPicFinal = Bitmap.createBitmap(bitPic, 0, 0,
            width, height,matrix, true);
    bitPic.recycle();
    int desWidth;
    int desHeight;
    desWidth = bitPicFinal.getWidth();
    desHeight = desWidth;
    Bitmap croppedBitmap = Bitmap.createBitmap(bitPicFinal, 0,bitPicFinal.
            getHeight() / 2 - bitPicFinal.getWidth() / 2,desWidth, desHeight);
    croppedBitmap = Bitmap.createScaledBitmap(croppedBitmap, 528, 528, true);
    return croppedBitmap;
}
To perform matrix rotations/mirrors depending on camera that took the photo then adding matrix value to Bitmap and create a new one. Finally, 528*528 Bitmap is cropped out. As per my understanding, this method is necessary to receive proper Bitmap data from Image byte.

I have uploaded the full source code in GitHub,
https://github.com/JayaprakashR-Zealot/SetProfilePicture

Kindly raise your queries in the comment section.

Happy Coding!

Cheers!