[컴][안드로이드] 사진 edit 하는 library 사용법 - uCrop

CropImage library / 사진 에디트 뷰 / 사진 에디터 뷰 / 사진 에디트 액티비티



uCrop 사용하기

uCrop 은 이미지 잘라내기(crop image) 를 구현해 놓은 library 이다. library 는 ref. 4 에 많이 볼 수 있는데 개인적으로 이녀석의 UI 가 가장 예뻐서 선택했다.

이제 uCrop 을 사용 해 보도록 하자. 기본적으로 uCrop 의 arsenal page (ref. 5) 에 가면 대략적인 사용법이 나와 있으며, github 에 example source 도 함께 올라와 있다.

여기서는 uCrop 의 github 의 example source 을 조금 간략하게 편집한 소스로 설명하려 한다.(소스다운로드)


gradle 설정

app gradle

apply plugin: 'com.android.application'

android {
    ...
}

dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])
    testCompile 'junit:junit:4.12'
    compile 'com.android.support:appcompat-v7:23.2.0'
    compile 'com.android.support:design:23.2.0'
    compile 'com.yalantis:ucrop:1.3.+'
}



AndroidManifest.xml

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.namh.ucropsample">

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

    <application
       ...

        <activity
            android:name="com.yalantis.ucrop.UCropActivity"
            android:screenOrientation="portrait"
            android:theme="@style/Theme.AppCompat.Light.NoActionBar"/>
    </application>

</manifest>



동작 설명

대략적인 동작은 아래와 같다.
  1. "PICK AND CROP" 이라는 버튼을 누르면, 
  2. gallery 가 보여지고, 
  3. 여기서 사진을 선택하면, 
  4. edit 화면(uCrop) 이 나온다. 
  5. edit 를 끝맞치면, 사진이 "PICK AND CROP" 버튼 아래에 보여지게 된다.

MainActivity

uCorp 의 예제에 보면 BaseActivity.java 를 사용하는데, 이 activity 에 Mashmellow 의 permission 을 물어보는 부분도 구현을 해 놓았다. 그러니 이녀석을 상속받아서 Activity 를 만들도록 하자.

그리고 아래처럼 code 를 추가해 주면 된다. 그러면 아래같은 화면이 된다. 코드가 길어서 일부만 적어놓는다. 자세한 코드는 링크를 참고하자.


MainActivity.java


gallery

requestPermission 은 BaseActivity.java 에 들어있다.

private void pickFromGallery() {
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN // Permission was added in API Level 16
            && ActivityCompat.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE)
            != PackageManager.PERMISSION_GRANTED) {
        requestPermission(Manifest.permission.READ_EXTERNAL_STORAGE,
                getString(R.string.permission_read_storage_rationale),
                REQUEST_STORAGE_READ_ACCESS_PERMISSION);
    } else {
        Intent intent = new Intent();
        intent.setType("image/*");
        intent.setAction(Intent.ACTION_GET_CONTENT);
        intent.addCategory(Intent.CATEGORY_OPENABLE);
        startActivityForResult(Intent.createChooser(intent, 
            getString(R.string.label_select_picture)), REQUEST_SELECT_PICTURE);
    }
}


@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
    if (resultCode == RESULT_OK) {
        if (requestCode == REQUEST_SELECT_PICTURE) {
            final Uri selectedUri = data.getData();
            if (selectedUri != null) {
                startCropActivity(data.getData());
            } else {
                Toast.makeText(SampleActivity.this, R.string.toast_cannot_retrieve_selected_image, Toast.LENGTH_SHORT).show();
            }
        } else if (requestCode == UCrop.REQUEST_CROP) {
            handleCropResult(data);
        }
    }
    if (resultCode == UCrop.RESULT_ERROR) {
        handleCropError(data);
    }
}

crop

gallery 에서 사진 선택이 끝나면 아래 같은 순서로 실행된다.
  1. startCropActivity ()
  2. --> uCrop.start(MainActivity.this); 
  3. --> handleCropResult() 
  4. --> mImageView.setImageURI(resultUri);



public class MainActivity extends BaseActivity {

    private static final String TAG = "MainActivity";

    private static final int REQUEST_SELECT_PICTURE = 0x01;
    private static final String SAMPLE_CROPPED_IMAGE_NAME = "SampleCropImage.jpeg";

    ...


    private Uri mDestinationUri;
    private ImageView mImageView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        ...

        // setup crop
        mDestinationUri = Uri.fromFile(new File(getCacheDir(), SAMPLE_CROPPED_IMAGE_NAME));
        _setupPickButton();
        mImageView = ((ImageView) findViewById(R.id.image_view_preview));
    }

   

    @Override
    public void onActivityResult(int requestCode, int resultCode, Intent data) {
        if (resultCode == RESULT_OK) {
            if (requestCode == REQUEST_SELECT_PICTURE) {
                final Uri selectedUri = data.getData();
                if (selectedUri != null) {
                    startCropActivity(data.getData());
                } else {
                    Toast.makeText(MainActivity.this,
                            R.string.toast_cannot_retrieve_selected_image,
                            Toast.LENGTH_SHORT).show();
                }
            } else if (requestCode == UCrop.REQUEST_CROP) {
                handleCropResult(data);
            }
        }
        if (resultCode == UCrop.RESULT_ERROR) {
            handleCropError(data);
        }
    }

    private void startCropActivity(@NonNull Uri uri) {
        UCrop uCrop = UCrop.of(uri, mDestinationUri);

        uCrop = _setRatio(uCrop, RATIO_ORIGIN, 0, 0);
        uCrop = _setSize(uCrop, 0, 0);

        uCrop = _advancedConfig(uCrop, FORMAT_JPEG, 90);

        uCrop.start(MainActivity.this);
    }


    private void handleCropResult(@NonNull Intent result) {
        final Uri resultUri = UCrop.getOutput(result);
        if (resultUri != null) {
            // ResultActivity.startWithUri(MainActivity.this, resultUri);
            Toast.makeText(MainActivity.this, resultUri.toString(), Toast.LENGTH_SHORT).show();
            mImageView.setImageURI(resultUri);


        } else {
            Toast.makeText(MainActivity.this, R.string.toast_cannot_retrieve_cropped_image, Toast.LENGTH_SHORT).show();
        }
    }


    @SuppressWarnings("ThrowableResultOfMethodCallIgnored")
    private void handleCropError(@NonNull Intent result) {
        ...
    }


    private void _setupPickButton(){
        ...

    }


    private void _pickFromGallery() {
        ...
    }

    /**
     * Callback received when a permissions request has been completed.
     */
    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        ...
    }




    private UCrop _setRatio(@NonNull UCrop uCrop, int choice, float xratio, float yratio){
        ...

    }

    private UCrop _setSize(@NonNull UCrop uCrop, int maxWidth, int maxHeight){
        ...
    }


    /**
     * Sometimes you want to adjust more options, it's done via {@link com.yalantis.ucrop.UCrop.Options} class.
     *
     * @param uCrop - ucrop builder instance
     * @return - ucrop builder instance
     */
    private UCrop _advancedConfig(@NonNull UCrop uCrop, int format, int quality) {
        ...
    }
}


mDestinationUri 의 path

참고로 edit 한 내용은 mDestinationUri 에 임시로 저장한다. mDestinationUri 의 경로는
  • /data/data/com.applicaton.com/cache
같다.



Save the Image

이미지를 저장하는 방법은 간단하다.
cache 에 저장된 이미지를 Downloads 폴더로 copy 하는 작업을 수행하기만 하면 된다.


private void _setupSaveButton() {
    findViewById(R.id.button_save).setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            _saveCroppedImage();
        }
    });


}

private void _saveCroppedImage() {
    if (ActivityCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE)
            != PackageManager.PERMISSION_GRANTED) {

        requestPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE,
                getString(R.string.permission_write_storage_rationale),
                REQUEST_STORAGE_WRITE_ACCESS_PERMISSION);
    } else {
        Uri imageUri = mResultUri;
        if (imageUri != null && imageUri.getScheme().equals("file")) {
            try {
                copyFileToDownloads(imageUri);
            } catch (Exception e) {
                Toast.makeText(MainActivity.this, e.getMessage(), Toast.LENGTH_SHORT).show();
                Log.e(TAG, imageUri.toString(), e);
            }
        } else {
            Toast.makeText(MainActivity.this, getString(R.string.toast_unexpected_error), Toast.LENGTH_SHORT).show();
        }
    }
}

private void copyFileToDownloads(Uri croppedFileUri) throws Exception {
    String downloadsDirectoryPath
            = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS)
            .getAbsolutePath();
    String filename = String.format(
            "%d_%s",
            Calendar.getInstance().getTimeInMillis(),
            croppedFileUri.getLastPathSegment());

    File saveFile = new File(downloadsDirectoryPath, filename);

    FileInputStream inStream = new FileInputStream(new File(croppedFileUri.getPath()));
    FileOutputStream outStream = new FileOutputStream(saveFile);
    FileChannel inChannel = inStream.getChannel();
    FileChannel outChannel = outStream.getChannel();
    inChannel.transferTo(0, inChannel.size(), outChannel);
    inStream.close();
    outStream.close();

    // showNotification(saveFile);
    Toast.makeText(MainActivity.this, getString(R.string.toast_image_saved), Toast.LENGTH_SHORT).show();
}





Example Source



수정

gallery 를 부르고, 다시 crop activity 를 호출하는 부분을 BaseActivity.java 로 옮겼다. 이 녀석을 사용하면, 아래 정도만 구현해 주면된다. 편할대로 사용하자.
  • protected Activity getActivity()
  • protected void handleCropResult(Intent data)
  • protected void handleCropError(Intent data)
  • pickFromGallery() : 버튼의 click event handler 에서 pickFromGallery() 를 불러주면 된다.

source




Reference

  1. unable to find com.android.camera.CropImage activity in android, StackOverflow
  2. GitHub - lvillani/android-cropimage: CropImage Activity from Gallery.apk packaged as a reusable Android library (4.0 and up).
  3. GitHub - biokys/cropimage: Replacement for deprecated official Android crop image function : 이 crop image 소스를 ref. 2 에서 사용하는 듯 하다.
  4. The Android Arsenal - Image Croppers - A categorized directory of free libraries and tools for Android
  5. The Android Arsenal - Image Croppers - uCrop : 무료 library 인데 UI 가 좋다.

댓글 없음:

댓글 쓰기