카메라 및 갤러리에서 Android 캡처 이미지


이 튜토리얼에서는 카메라나 갤러리에서 이미지를 선택하고 ImageView에 표시하는 애플리케이션을 개발할 것입니다. 참고: 아래 코드는 Android Nougat 이전 버전에서 제대로 작동합니다. 최신 작동 예제는 [업데이트된 기사](http://참고: Google은 Android 알파벳 버전에서 우회했습니다. Android Q는 Android 10으로 이름이 변경되었습니다. 이 튜토리얼은 Google이 이를 수행하기로 결정하기 전에 작성되었으므로 , 기사의 일부 위치에서 Android Q를 볼 수 있습니다.).

Android 캡처 이미지 개요

Android Marshmallow가 시작되면서 런타임 권한이 최전선에 구현되어야 합니다. 애플리케이션 태그 위의 Android Manifest.xml 파일에서 다음 권한을 추가합니다.

<uses-feature
        android:name="android.hardware.camera"
        android:required="false" />
    <uses-feature
        android:name="android.hardware.camera.autofocus"
        android:required="false" />
    <uses-feature
        android:name="android.hardware.camera.flash"
        android:required="false" />

    <uses-permission android:name="android.permission.CAMERA" />
    <uses-permission android:name="ANDROID.PERMISSION.READ_EXTERNAL_STORAGE"/>

android.hardware.camera를 추가하면 Play Store가 카메라가 없는 기기에 애플리케이션을 설치하는 것을 감지하고 차단합니다. 의도는 작업을 다른 애플리케이션에 위임하는 표준 방법입니다. 기본 카메라를 시작하려면 인텐트에 android.provider.MediaStore.ACTION_IMAGE_CAPTURE가 필요합니다. 갤러리에서 이미지를 선택하려면 인텐트에 Intent.ACTION_GET_CONTENT 인수가 필요합니다. 이 튜토리얼에서는 카메라나 갤러리에서 이미지를 선택하고 이미지를 원형 이미지 보기와 일반 이미지 보기에 표시할 수 있는 이미지 선택기를 호출합니다. build.gradle 파일 내에 다음 종속성을 추가합니다. de.hdodenhof:circleimageview:2.1.0 컴파일

Android 이미지 캡처 프로젝트 구조

Android 캡처 이미지 코드

FAB 버튼의 아이콘이 @android:drawable/ic_menu_camera로 변경되지 않는 한 activity_main.xml의 레이아웃은 동일하게 유지됩니다. content_main.xml은 다음과 같습니다.

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="https://schemas.android.com/apk/res/android"
    xmlns:app="https://schemas.android.com/apk/res-auto"
    xmlns:tools="https://schemas.android.com/tools"
    android:id="@+id/content_main"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    android:background="#000000"
    app:layout_behavior="@string/appbar_scrolling_view_behavior"
    tools:context="com.journaldev.imagepicker.MainActivity"
    tools:showIn="@layout/activity_main">


    <RelativeLayout
        android:layout_width="250dp"
        android:layout_height="250dp"
        android:layout_centerHorizontal="true"
        android:layout_centerVertical="true"
        android:background="@drawable/image_border"
        android:clickable="true"
        android:orientation="vertical">


        <ImageView
            android:id="@+id/imageView"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:adjustViewBounds="true"
            android:scaleType="centerCrop" />

    </RelativeLayout>

    <de.hdodenhof.circleimageview.CircleImageView
        android:id="@+id/img_profile"
        android:layout_width="100dp"
        android:layout_height="100dp"
        android:layout_gravity="center_horizontal"
        android:src="@drawable/profile"
        app:civ_border_width="5dp"
        app:civ_border_color="#FFFFFF"
        android:layout_alignParentBottom="true"
        android:layout_centerHorizontal="true" />

</RelativeLayout>

MainActivity.java에 대한 코드는 다음과 같습니다.

public class MainActivity extends AppCompatActivity {

    Bitmap myBitmap;
    Uri picUri;


    private ArrayList permissionsToRequest;
    private ArrayList permissionsRejected = new ArrayList();
    private ArrayList permissions = new ArrayList();

    private final static int ALL_PERMISSIONS_RESULT = 107;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
        setSupportActionBar(toolbar);

        FloatingActionButton fab = (FloatingActionButton) findViewById(R.id.fab);
        fab.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                startActivityForResult(getPickImageChooserIntent(), 200);
            }
        });


        permissions.add(CAMERA);
        permissionsToRequest = findUnAskedPermissions(permissions);
        //get the permissions we have asked for before but are not granted..
        //we will store this in a global list to access later.


        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {


            if (permissionsToRequest.size() > 0)
                requestPermissions(permissionsToRequest.toArray(new String[permissionsToRequest.size()]), ALL_PERMISSIONS_RESULT);
        }

    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        // Inflate the menu; this adds items to the action bar if it is present.
        getMenuInflater().inflate(R.menu.menu_main, menu);
        return true;
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        // Handle action bar item clicks here. The action bar will
        // automatically handle clicks on the Home/Up button, so long
        // as you specify a parent activity in AndroidManifest.xml.
        int id = item.getItemId();

        //noinspection SimplifiableIfStatement
        if (id == R.id.action_settings) {
            return true;
        }

        return super.onOptionsItemSelected(item);
    }


    /**
     * Create a chooser intent to select the source to get image from.<br />
     * The source can be camera's (ACTION_IMAGE_CAPTURE) or gallery's (ACTION_GET_CONTENT).<br />
     * All possible sources are added to the intent chooser.
     */
    public Intent getPickImageChooserIntent() {

        // Determine Uri of camera image to save.
        Uri outputFileUri = getCaptureImageOutputUri();

        List allIntents = new ArrayList();
        PackageManager packageManager = getPackageManager();

        // collect all camera intents
        Intent captureIntent = new Intent(android.provider.MediaStore.ACTION_IMAGE_CAPTURE);
        List listCam = packageManager.queryIntentActivities(captureIntent, 0);
        for (ResolveInfo res : listCam) {
            Intent intent = new Intent(captureIntent);
            intent.setComponent(new ComponentName(res.activityInfo.packageName, res.activityInfo.name));
            intent.setPackage(res.activityInfo.packageName);
            if (outputFileUri != null) {
                intent.putExtra(MediaStore.EXTRA_OUTPUT, outputFileUri);
            }
            allIntents.add(intent);
        }

        // collect all gallery intents
        Intent galleryIntent = new Intent(Intent.ACTION_GET_CONTENT);
        galleryIntent.setType("image/*");
        List listGallery = packageManager.queryIntentActivities(galleryIntent, 0);
        for (ResolveInfo res : listGallery) {
            Intent intent = new Intent(galleryIntent);
            intent.setComponent(new ComponentName(res.activityInfo.packageName, res.activityInfo.name));
            intent.setPackage(res.activityInfo.packageName);
            allIntents.add(intent);
        }

        // the main intent is the last in the list (fucking android) so pickup the useless one
        Intent mainIntent = allIntents.get(allIntents.size() - 1);
        for (Intent intent : allIntents) {
            if (intent.getComponent().getClassName().equals("com.android.documentsui.DocumentsActivity")) {
                mainIntent = intent;
                break;
            }
        }
        allIntents.remove(mainIntent);

        // Create a chooser from the main intent
        Intent chooserIntent = Intent.createChooser(mainIntent, "Select source");

        // Add all other intents
        chooserIntent.putExtra(Intent.EXTRA_INITIAL_INTENTS, allIntents.toArray(new Parcelable[allIntents.size()]));

        return chooserIntent;
    }


    /**
     * Get URI to image received from capture by camera.
     */
    private Uri getCaptureImageOutputUri() {
        Uri outputFileUri = null;
        File getImage = getExternalCacheDir();
        if (getImage != null) {
            outputFileUri = Uri.fromFile(new File(getImage.getPath(), "profile.png"));
        }
        return outputFileUri;
    }

    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {

        Bitmap bitmap;
        if (resultCode == Activity.RESULT_OK) {

            ImageView imageView = (ImageView) findViewById(R.id.imageView);

            if (getPickImageResultUri(data) != null) {
                picUri = getPickImageResultUri(data);

                try {
                    myBitmap = MediaStore.Images.Media.getBitmap(this.getContentResolver(), picUri);
                    myBitmap = rotateImageIfRequired(myBitmap, picUri);
                    myBitmap = getResizedBitmap(myBitmap, 500);

                    CircleImageView croppedImageView = (CircleImageView) findViewById(R.id.img_profile);
                    croppedImageView.setImageBitmap(myBitmap);
                    imageView.setImageBitmap(myBitmap);

                } catch (IOException e) {
                    e.printStackTrace();
                }


            } else {


                bitmap = (Bitmap) data.getExtras().get("data");

                myBitmap = bitmap;
                CircleImageView croppedImageView = (CircleImageView) findViewById(R.id.img_profile);
                if (croppedImageView != null) {
                    croppedImageView.setImageBitmap(myBitmap);
                }

                imageView.setImageBitmap(myBitmap);

            }

        }

    }

    private static Bitmap rotateImageIfRequired(Bitmap img, Uri selectedImage) throws IOException {

        ExifInterface ei = new ExifInterface(selectedImage.getPath());
        int orientation = ei.getAttributeInt(ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_NORMAL);

        switch (orientation) {
            case ExifInterface.ORIENTATION_ROTATE_90:
                return rotateImage(img, 90);
            case ExifInterface.ORIENTATION_ROTATE_180:
                return rotateImage(img, 180);
            case ExifInterface.ORIENTATION_ROTATE_270:
                return rotateImage(img, 270);
            default:
                return img;
        }
    }

    private static Bitmap rotateImage(Bitmap img, int degree) {
        Matrix matrix = new Matrix();
        matrix.postRotate(degree);
        Bitmap rotatedImg = Bitmap.createBitmap(img, 0, 0, img.getWidth(), img.getHeight(), matrix, true);
        img.recycle();
        return rotatedImg;
    }

    public Bitmap getResizedBitmap(Bitmap image, int maxSize) {
        int width = image.getWidth();
        int height = image.getHeight();

        float bitmapRatio = (float) width / (float) height;
        if (bitmapRatio > 0) {
            width = maxSize;
            height = (int) (width / bitmapRatio);
        } else {
            height = maxSize;
            width = (int) (height * bitmapRatio);
        }
        return Bitmap.createScaledBitmap(image, width, height, true);
    }


    /**
     * Get the URI of the selected image from {@link #getPickImageChooserIntent()}.<br />
     * Will return the correct URI for camera and gallery image.
     *
     * @param data the returned data of the activity result
     */
    public Uri getPickImageResultUri(Intent data) {
        boolean isCamera = true;
        if (data != null) {
            String action = data.getAction();
            isCamera = action != null && action.equals(MediaStore.ACTION_IMAGE_CAPTURE);
        }


        return isCamera ? getCaptureImageOutputUri() : data.getData();
    }

    @Override
    protected void onSaveInstanceState(Bundle outState) {
        super.onSaveInstanceState(outState);

        // save file url in bundle as it will be null on scren orientation
        // changes
        outState.putParcelable("pic_uri", picUri);
    }

    @Override
    protected void onRestoreInstanceState(Bundle savedInstanceState) {
        super.onRestoreInstanceState(savedInstanceState);

        // get the file url
        picUri = savedInstanceState.getParcelable("pic_uri");
    }

    private ArrayList findUnAskedPermissions(ArrayList wanted) {
        ArrayList result = new ArrayList();

        for (String perm : wanted) {
            if (!hasPermission(perm)) {
                result.add(perm);
            }
        }

        return result;
    }

    private boolean hasPermission(String permission) {
        if (canMakeSmores()) {
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
                return (checkSelfPermission(permission) == PackageManager.PERMISSION_GRANTED);
            }
        }
        return true;
    }

    private void showMessageOKCancel(String message, DialogInterface.OnClickListener okListener) {
        new AlertDialog.Builder(this)
                .setMessage(message)
                .setPositiveButton("OK", okListener)
                .setNegativeButton("Cancel", null)
                .create()
                .show();
    }

    private boolean canMakeSmores() {
        return (Build.VERSION.SDK_INT > Build.VERSION_CODES.LOLLIPOP_MR1);
    }

    @TargetApi(Build.VERSION_CODES.M)
    @Override
    public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {

        switch (requestCode) {

            case ALL_PERMISSIONS_RESULT:
                for (String perms : permissionsToRequest) {
                    if (hasPermission(perms)) {

                    } else {

                        permissionsRejected.add(perms);
                    }
                }

                if (permissionsRejected.size() > 0) {


                    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
                        if (shouldShowRequestPermissionRationale(permissionsRejected.get(0))) {
                            showMessageOKCancel("These permissions are mandatory for the application. Please allow access.",
                                    new DialogInterface.OnClickListener() {
                                        @Override
                                        public void onClick(DialogInterface dialog, int which) {
                                            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {

                                                //Log.d("API123", "permisionrejected " + permissionsRejected.size());

                                                requestPermissions(permissionsRejected.toArray(new String[permissionsRejected.size()]), ALL_PERMISSIONS_RESULT);
                                            }
                                        }
                                    });
                            return;
                        }
                    }

                }

                break;
        }

    }
}

위의 코드에서 많은 추론을 도출할 수 있습니다.

  • 사용자가 활동을 시작할 때 카메라 런타임 권한을 요청해야 합니다.
  • 어떤 결과를 되찾기 위한 인텐트를 시작할 때 관련 인수를 사용하여 startActivityForResult를 호출해야 합니다.
  • 카메라 및 갤러리에 대한 의도를 별도로 호출하기 위해 대화 상자를 사용하는 대신 모든 카메라 및 갤러리 의도에 대한 단일 선택기 의도를 생성하는 getPickImageChooserIntent() 메서드를 사용했습니다(문서 의도 참고). Intent.EXTRA_INITIAL_INTENTS는 여러 애플리케이션 의도를 한 곳에 추가하는 데 사용됩니다.
  • 카메라 인텐트의 경우 MediaStore.EXTRA_OUTPUT이 이미지 저장 경로를 지정하기 위해 추가로 전달됩니다. 이것이 없으면 작은 해상도 이미지만 반환됩니다.
  • 카메라가 반환한 이미지의 URI 경로는 getCaptureImageOutputUri() 메서드 내에서 가져옵니다.
  • onActivityResult는 본질적으로 이미지에 대한 URI를 반환합니다. 일부 장치는 비트맵을 data.getExtras().get(\data\);로 반환합니다.
  • 이미지를 클릭하면 돌아오는 동안 카메라 화면이 활동을 다시 시작하여 getCaptureImageOutputUri() 메서드에서 저장된 URI가 null이 됩니다. 따라서 onSaveInstanceState()onRestoreInstanceState()를 사용하여 해당 URI를 저장하고 복원하는 것이 필수적입니다.
  • 다음 코드 줄의 URI에서 비트맵을 검색합니다. myBitmap=MediaStore.Images.Media.getBitmap(this.getContentResolver(), picUri);
  • Samsung Galaxy와 같은 장치는 이미지를 가로 방향으로 캡처하는 것으로 알려져 있습니다. 이미지를 검색하여 그대로 표시하면 잘못된 방향으로 표시될 수 있습니다. 따라서 rotateImageIfRequired(myBitmap, picUri);
  • 메서드를 호출했습니다.\n
  • ExifInterface는 JPEG 파일 또는 RAW 이미지 파일에서 Exif 태그를 읽고 쓰기 위한 클래스입니다.
  • 결국 getResizedBitmap() 메서드를 호출하여 너비 또는 높이(둘 중 더 큰 것)로 비트맵의 크기를 조정하고 setImageBitmap을 사용하여 이미지를 이미지 보기로 설정합니다.

카메라 프로젝트에서 Android 캡처 이미지 다운로드