마스터Q&A 안드로이드는 안드로이드 개발자들의 질문과 답변을 위한 지식 커뮤니티 사이트입니다. 안드로이드펍에서 운영하고 있습니다. [사용법, 운영진]

카메라로 찍은 이미지 화질 개선?

0 추천
https://goatlab.tistory.com/1229

액티비티1에서 2로 넘어올 때 화질이 흐려지는데 개선방법이 있을까요?
enerigpy (2,110 포인트) 님이 2023년 5월 31일 질문

2개의 답변

0 추천
 
채택된 답변

간단하게(?) 제가 테스트해 본 코드를 올립니다. File 접근권한 등을 처리해야 하므로 FilePath Provider를 제공해 주어야 하고,
Camera permission도 처리해주어야 합니다.

AndroidManifest.xml

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

    <uses-feature
        android:name="android.hardware.camera"
        android:required="true" />

    <uses-permission android:name="android.permission.INTERNET" />
    <uses-permission android:name="android.permission.CAMERA" />
    <uses-permission
        android:name="android.permission.READ_EXTERNAL_STORAGE"
        android:maxSdkVersion="28" />
    <uses-permission
        android:name="android.permission.WRITE_EXTERNAL_STORAGE"
        android:maxSdkVersion="28" />

    <application
        ...>
         ...

        <provider
            android:name="androidx.core.content.FileProvider"
            android:authorities="com.example.myapplication.fileprovider"
            android:exported="false"
            android:grantUriPermissions="true">
            <meta-data
                android:name="android.support.FILE_PROVIDER_PATHS"
                android:resource="@xml/file_paths" />
        </provider>
    </application>
</manifest>

res/xml/file_path.xml

<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
    <external-files-path name="my_images" path="Pictures" />
</paths>

 

Fragment

public class TakePhotoFragment extends Fragment {

    private FragmentTakePhotoBinding binding;

    private Uri photoUri;

    public TakePhotoFragment() {
    }

    @Override
    public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        binding = FragmentTakePhotoBinding.inflate(inflater, container, false);
        return binding.getRoot();
    }

    @Override
    public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);

        binding.takePhotoBtn.setOnClickListener(v ->
                requestPermissionOrTakePhoto()
        );
    }

    @Override
    public void onDestroyView() {
        super.onDestroyView();
        binding = null;
    }

    private final ActivityResultLauncher<String> permissionResult = registerForActivityResult(
            new ActivityResultContracts.RequestPermission(), new ActivityResultCallback<Boolean>() {
                @Override
                public void onActivityResult(Boolean isGranted) {
                    photoUri = takePhoto();
                }
            }
    );

    private final ActivityResultLauncher<Uri> takeImageResult = registerForActivityResult(
            new ActivityResultContracts.TakePicture(), new ActivityResultCallback<Boolean>() {
                @Override
                public void onActivityResult(Boolean isSuccess) {
                    if (!isSuccess) return;

                    loadImage(photoUri);
                }
            }
    );

    private Uri createPhotoUri() throws IOException {
        return FileProvider.getUriForFile(requireContext(),
                "com.example.myapplication.fileprovider",
                createImageFile()
        );
    }

    private File createImageFile() throws IOException {
        // Create an image file name
        @SuppressLint("SimpleDateFormat") String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss").format(new Date());
        String imageFileName = "JPEG_" + timeStamp + "_";
        File storageDir = requireActivity().getExternalFilesDir(Environment.DIRECTORY_PICTURES);
        return File.createTempFile(
                imageFileName,  /* prefix */
                ".jpg",         /* suffix */
                storageDir      /* directory */
        );
    }

    private boolean cameraPermissionGranted() {
        return ContextCompat.checkSelfPermission(requireContext(), Manifest.permission.CAMERA)
                == PackageManager.PERMISSION_GRANTED;
    }

    private void requestCameraPermission() {
        permissionResult.launch(Manifest.permission.CAMERA);
    }

    private void requestPermissionOrTakePhoto() {
        if (!cameraPermissionGranted()) {
            requestCameraPermission();
            return;
        }

        photoUri = takePhoto();
    }

    private Uri takePhoto() {
        try {
            Uri photoUri = createPhotoUri();
            takeImageResult.launch(photoUri);
            return photoUri;
        } catch (IOException e) {
            e.printStackTrace();
        }
        return null;
    }

    private void loadImage(Uri uri) {
        Glide.with(requireView())
                .load(uri)
                .placeholder(R.drawable.lake)
                .into(binding.imageView);
    }
}

 

spark (226,420 포인트) 님이 2023년 5월 31일 답변
enerigpy님이 2023년 5월 31일 채택됨
java.lang.ClassNotFoundException: Didn't find class "com..whateverString" on path: DexPathList 오류가 뜨는데요
manifest에
android:appComponentFactory="whateverString"
tools:replace="android:appComponentFactory"
이거는 추가했습니다
의의 file path provider에는 제 app package이름을 사용했습니다. 님은 님 프로젝트 패키지명을 사용하셔야 해요. 제 코드를 통째로 그냥 복사하지 마시고 내용을 보면서 어떤 역할을 하는지 확인하시면서 수정할 부분이 보이시면 수정하세요.
네네 제 프로젝트 패키지로 넣었어요! 패키지 다른거로하면 패키지 오류가 뜨는것도 확인했구요
안드로아드 스튜디오 버전,  플러그인 버전, 디펜던사 버전 등등과 관련이 있을 수도 있구요. 님과 프로젝 설정이.디를.수 있기 때문에 그럴 가성이 높아 보여요.  코드를 조슴씩 올겨서 빌드해 보세요. 어느 부분이 문제인지 금방 아실 수 있을 것 같아요. 클린빌드, invalidate cache도 해보시구요.
0 추천

첫번째 이미지는 카메라앱이 아닌가요? 

액티비티2 에서 뒤로가기 한번 해보시면
액티비티1 과 2에서 동일하게 저화질로 나타날 것으로 보입니다. 

카메라로 촬영한 이미지를 Parcelable 로 처리하지 마시고 

이미지 File 생성해서 카메라 앱으로 파일 경로 전달하고
onActivityResult 에서 저장된 이미지 파일을 이용하세요. 

 

public void takePhoto() {
String imageFileName = "JPEG_" + timestamp; // 
File storageDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES);
File image = File.createTempFile(imageFileName, ".jpg", storageDir);
uri = Uri.fromFile(image);

Intent takePhotoIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
takePhotoIntent.putExtra(MediaStore.EXTRA_OUTPUT, uri);
startActivityForResult(takePhotoIntent, BaseDef.REQUEST_CODE_CAMERA);
}

 

 

익명 님이 2023년 5월 31일 답변
근데 이런식으로 하면 방금 찍은 사진을 보여주도록 하도록 할 때 그걸 못하지 않나요? 사진이름을 추적해야할거 같은데.. 사진 찍고 갤러리로 들어가는 방식으로는 하지 않으려고 해서요!
사진찍은 후의 비트맵을 보여주거나 Parcleable로 이미지를 전달하실 때는, 비트맵 사이즈로 인한 메모리 에러가 나기가 쉽게 때문에 scale down을 하셔서 처리하시는게 안전합니다. 그리고 Glide같은 잘 만들어진 라이브러리의 도움을 받아서 이미지뷰에서 보여주시면 메모리등의 여러가지 골치 아픈 이슈들은 피하실 수 있습니다.
Bitmap bitmap = (Bitmap) data.getExtras().get("data");
            ImageView imageView = findViewById(R.id.imageView);
            Glide.with(this)
                    .load(bitmap)
                    .override(100, 100)
                    .into(imageView);

이렇게 했는데도 흐리네요?!
위의 익명님 말씀처럼, 찍을 사진을 저장할 임시 파일을 만든 뒤에 거기에 찍은 이미지를 저장하고 그 파일을 읽도록 하는게 일반적으로 사용되는 방식이네요. 디바이스의 출시회사에 따라 경우에 따라서는 thumbnail 이미지가 제공되지 않기 때문에 onActivityResult에서 bitmap이 null로 넘어오는 경우가 있습니다. 저는 에뮬레이터, 제 삼성폰 모두 null로 넘어옵니다.

그리고 개발자 가이드에도 같은 방식이 나옵니다. https://developer.android.com/training/camera-deprecated/photobasics
이거대로 해봤는데 찍기 버튼에서 아무런 동작이 없네요
https://ebbnflow.tistory.com/177

제가 구현하고 싶은건 어플을 실행해서 사진을 최근 사진을 불러서 이미지뷰에 보이게 하는건데 어렵네요.
위 댓글에도 말씀드렸다시피 썸네일이 넘어오지 않는 디바이스들이 존재합니다. 따라서 예제의 step1은 모든 경우에 동작한다는 보장이 없어 보여요.
...