以下に示すように、中央に長方形のビューがあるカスタムカメラアプリがあります。
写真を撮るとき、長方形の外側のすべてを無視したいと思います。そしてこれは私のXMLレイアウトです:
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:Android="http://schemas.Android.com/apk/res/Android"
xmlns:app="http://schemas.Android.com/apk/res-auto"
xmlns:tools="http://schemas.Android.com/tools"
Android:layout_width="match_parent"
Android:layout_height="match_parent"
Android:background="@color/black_50">
<TextureView
Android:id="@+id/viewFinder"
Android:layout_width="match_parent"
Android:layout_height="match_parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<View
Android:layout_width="match_parent"
Android:layout_height="250dp"
Android:layout_margin="16dp"
Android:background="@drawable/rectangle"
app:layout_constraintBottom_toTopOf="@+id/cameraBottomView"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<View
Android:id="@+id/cameraBottomView"
Android:layout_width="match_parent"
Android:layout_height="130dp"
Android:background="@color/black_50"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" />
<ImageButton
Android:id="@+id/cameraCaptureImageButton"
Android:layout_width="wrap_content"
Android:layout_height="wrap_content"
Android:background="@Android:color/transparent"
Android:src="@drawable/ic_capture_image"
app:layout_constraintBottom_toBottomOf="@id/cameraBottomView"
app:layout_constraintEnd_toEndOf="@id/cameraBottomView"
app:layout_constraintStart_toStartOf="@id/cameraBottomView"
app:layout_constraintTop_toTopOf="@id/cameraBottomView"
tools:ignore="ContentDescription" />
</androidx.constraintlayout.widget.ConstraintLayout>
そして、これはcameraXプレビューのための私のkotlinコードです:
class CameraFragment : Fragment() {
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
// Inflate the layout for this fragment
return inflater.inflate(R.layout.fragment_camera, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
viewFinder.post { setupCamera() }
}
private fun setupCamera() {
CameraX.unbindAll()
CameraX.bindToLifecycle(
this,
buildPreviewUseCase(),
buildImageCaptureUseCase(),
buildImageAnalysisUseCase()
)
}
private fun buildPreviewUseCase(): Preview {
val preview = Preview(
UseCaseConfigBuilder.buildPreviewConfig(
viewFinder.display
)
)
preview.setOnPreviewOutputUpdateListener { previewOutput ->
updateViewFinderWithPreview(previewOutput)
correctPreviewOutputForDisplay(previewOutput.textureSize)
}
return preview
}
private fun updateViewFinderWithPreview(previewOutput: Preview.PreviewOutput) {
val parent = viewFinder.parent as ViewGroup
parent.removeView(viewFinder)
parent.addView(viewFinder, 0)
viewFinder.surfaceTexture = previewOutput.surfaceTexture
}
/**
* Corrects the camera/preview's output to the display, by scaling
* up/down and/or rotating the camera/preview's output.
*/
private fun correctPreviewOutputForDisplay(textureSize: Size) {
val matrix = Matrix()
val centerX = viewFinder.width / 2f
val centerY = viewFinder.height / 2f
val displayRotation = getDisplayRotation()
val (dx, dy) = getDisplayScalingFactors(textureSize)
matrix.postRotate(displayRotation, centerX, centerY)
matrix.preScale(dx, dy, centerX, centerY)
// Correct preview output to account for display rotation and scaling
viewFinder.setTransform(matrix)
}
private fun getDisplayRotation(): Float {
val rotationDegrees = when (viewFinder.display.rotation) {
Surface.ROTATION_0 -> 0
Surface.ROTATION_90 -> 90
Surface.ROTATION_180 -> 180
Surface.ROTATION_270 -> 270
else -> throw IllegalStateException("Unknown display rotation ${viewFinder.display.rotation}")
}
return -rotationDegrees.toFloat()
}
private fun getDisplayScalingFactors(textureSize: Size): Pair<Float, Float> {
val cameraPreviewRation = textureSize.height / textureSize.width.toFloat()
val scaledWidth: Int
val scaledHeight: Int
if (viewFinder.width > viewFinder.height) {
scaledHeight = viewFinder.width
scaledWidth = (viewFinder.width * cameraPreviewRation).toInt()
} else {
scaledHeight = viewFinder.height
scaledWidth = (viewFinder.height * cameraPreviewRation).toInt()
}
val dx = scaledWidth / viewFinder.width.toFloat()
val dy = scaledHeight / viewFinder.height.toFloat()
return Pair(dx, dy)
}
private fun buildImageCaptureUseCase(): ImageCapture {
val capture = ImageCapture(
UseCaseConfigBuilder.buildImageCaptureConfig(
viewFinder.display
)
)
cameraCaptureImageButton.setOnClickListener {
capture.takePicture(
FileCreator.createTempFile(JPEG_FORMAT),
Executors.newSingleThreadExecutor(),
object : ImageCapture.OnImageSavedListener {
override fun onImageSaved(file: File) {
requireActivity().runOnUiThread {
launchGalleryFragment(file.absolutePath)
}
}
override fun onError(
imageCaptureError: ImageCapture.ImageCaptureError,
message: String,
cause: Throwable?
) {
Toast.makeText(requireContext(), "Error: $message", Toast.LENGTH_LONG)
.show()
Log.e("CameraFragment", "Capture error $imageCaptureError: $message", cause)
}
})
}
return capture
}
private fun buildImageAnalysisUseCase(): ImageAnalysis {
val analysis = ImageAnalysis(
UseCaseConfigBuilder.buildImageAnalysisConfig(
viewFinder.display
)
)
analysis.setAnalyzer(
Executors.newSingleThreadExecutor(),
ImageAnalysis.Analyzer { image, rotationDegrees ->
Log.d(
"CameraFragment",
"Image analysis: $image - Rotation degrees: $rotationDegrees"
)
})
return analysis
}
private fun launchGalleryFragment(path: String) {
val action = CameraFragmentDirections.actionLaunchGalleryFragment(path)
findNavController().navigate(action)
}
}
そして、写真を撮って新しいページ(GalleryPage)に送信すると、以下に示すように、カメラのプレビューからすべての画面が表示されます。
そして、これは、cameraXプレビューから画像を取得してImageViewに表示するkotlinコードです。
class GalleryFragment : Fragment() {
private lateinit var imageView: ImageView
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
// Inflate the layout for this fragment
return inflater.inflate(R.layout.fragment_gallery, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
imageView = view.findViewById(R.id.img)
val imageFilePath = GalleryFragmentArgs.fromBundle(arguments!!).data
val bitmap = BitmapFactory.decodeFile(imageFilePath)
val rotatedBitmap = bitmap.rotate(90)
if (imageFilePath.isBlank()) {
Log.i(
"GalleryFragment",
"Image is Null or Empty"
)
} else {
Glide.with(activity!!)
.load(rotatedBitmap)
.into(imageView)
}
}
private fun Bitmap.rotate(degree:Int):Bitmap{
// Initialize a new matrix
val matrix = Matrix()
// Rotate the bitmap
matrix.postRotate(degree.toFloat())
// Resize the bitmap
val scaledBitmap = Bitmap.createScaledBitmap(
this,
width,
height,
true
)
// Create and return the rotated bitmap
return Bitmap.createBitmap(
scaledBitmap,
0,
0,
scaledBitmap.width,
scaledBitmap.height,
matrix,
true
)
}
}
誰かが私に画像を適切にトリミングする方法を手伝ってくれる?私はすでにそれを行う方法を検索して研究していますが、それでも混乱していて私のために働いていません。
私は解決策を持っています、私はこの関数を使用して画像をキャプチャした後に画像をトリミングします:
private fun cropImage(bitmap: Bitmap, frame: View, reference: View): ByteArray {
val heightOriginal = frame.height
val widthOriginal = frame.width
val heightFrame = reference.height
val widthFrame = reference.width
val leftFrame = reference.left
val topFrame = reference.top
val heightReal = bitmap.height
val widthReal = bitmap.width
val widthFinal = widthFrame * widthReal / widthOriginal
val heightFinal = heightFrame * heightReal / heightOriginal
val leftFinal = leftFrame * widthReal / widthOriginal
val topFinal = topFrame * heightReal / heightOriginal
val bitmapFinal = Bitmap.createBitmap(
bitmap,
leftFinal, topFinal, widthFinal, heightFinal
)
val stream = ByteArrayOutputStream()
bitmapFinal.compress(
Bitmap.CompressFormat.JPEG,
100,
stream
) //100 is the best quality possibe
return stream.toByteArray()
}
フレームのようなビューの親と最終的な参照のようなビューの子を参照して画像をトリミングします
bitmap
画像frame
画像が設定されている場所reference
frameは、画像の切り抜きの参照を取得しますreturn
画像は既にトリミングされていますこれは、あなたが言ったように、cameraXで撮った画像をどのようにトリミングするかの例です。それが最善の方法であるかどうかはわかりません。他の解決策を知りたいです。
camerax_version = "1.0.0-alpha07"
CameraFragment.Java
CameraXを初期化します。
// Views
private PreviewView previewView;
// CameraX
private ProcessCameraProvider cameraProvider;
private ListenableFuture<ProcessCameraProvider> cameraProviderFuture;
private CameraSelector cameraSelector;
private Executor executor;
private ImageCapture imageCapture;
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
cameraProviderFuture = ProcessCameraProvider.getInstance(getContext());
executor = ContextCompat.getMainExecutor(getContext());
cameraSelector = new CameraSelector.Builder().requireLensFacing(LensFacing.BACK).build();
}
@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
previewView = view.findViewById(R.id.preview);
ImageButton btnCapture = view.findViewById(R.id.btn_capture);
// Wait for the view to be properly laid out
previewView.post(() ->{
//Initialize CameraX
cameraProviderFuture.addListener(() -> {
if(cameraProvider != null) cameraProvider.unbindAll();
try {
cameraProvider = cameraProviderFuture.get();
// Set up the preview use case to display camera preview
Preview preview = new Preview.Builder()
.setTargetAspectRatio(AspectRatio.RATIO_4_3)
.setTargetRotation(previewView.getDisplay().getRotation())
.build();
preview.setPreviewSurfaceProvider(previewView.getPreviewSurfaceProvider());
// Set up the capture use case to allow users to take photos
imageCapture = new ImageCapture.Builder()
.setCaptureMode(ImageCapture.CaptureMode.MINIMIZE_LATENCY)
.setTargetRotation(previewView.getDisplay().getRotation())
.setTargetAspectRatio(AspectRatio.RATIO_4_3)
.build();
// Apply declared configs to CameraX using the same lifecycle owner
cameraProvider.bindToLifecycle(this, cameraSelector, preview,imageCapture);
} catch (ExecutionException | InterruptedException e) {
e.printStackTrace();
}
}, ContextCompat.getMainExecutor(getContext()));
});
btnCapture.setOnClickListener(v -> {
String format = "yyyy-MM-dd-HH-mm-ss-SSS";
SimpleDateFormat fmt = new SimpleDateFormat(format, Locale.US);
String date = fmt.format(System.currentTimeMillis());
File file = new File(getContext().getCacheDir(), date+".jpg");
imageCapture.takePicture(file, executor, imageSavedListener);
});
}
写真が撮影されたら、写真のパスを通過するギャラリーフラグメントを開きます。
private ImageCapture.OnImageSavedCallback imageSavedListener = new ImageCapture.OnImageSavedCallback() {
@Override
public void onImageSaved(@NonNull File photoFile) {
// Create new fragment and transaction
Fragment newFragment = new GalleryFragment();
FragmentTransaction transaction = getActivity().getSupportFragmentManager().beginTransaction();
// Set arguments
Bundle args = new Bundle();
args.putString("KEY_PATH", Uri.fromFile(photoFile).toString());
newFragment.setArguments(args);
// Replace whatever is in the fragment_container view with this fragment,
transaction.replace(R.id.fragment_container, newFragment,null);
transaction.addToBackStack(null);
// Commit the transaction
transaction.commit();
}
@Override
public void onError(int imageCaptureError, @NonNull String message, @Nullable Throwable cause) {
if (cause != null) {
cause.printStackTrace();
}
}
};
現時点では、写真はトリミングされていません。cameraXで直接実行できるかどうかはわかりません。
GalleryFragment.Java
フラグメントに渡された引数をロードします。
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
String path = getArguments().getString("KEY_PATH");
sourceUri = Uri.parse(path);
}
ImageViewでグライドを使用してUriをロードし、トリミングします。
@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
// Initialize the views
ImageView imageView = view.findViewById(R.id.image_view);
View cropArea = view.findViewById(R.id.crop_area);
// Display the image
Glide.with(this).load(sourceUri).listener(new RequestListener<Drawable>() {
@Override
public boolean onLoadFailed(@Nullable GlideException e, Object model, Target<Drawable> target, boolean isFirstResource) {
return false;
}
@Override
public boolean onResourceReady(Drawable resource, Object model, Target<Drawable> target, DataSource dataSource, boolean isFirstResource) {
// Get original bitmap
sourceBitmap = ((BitmapDrawable)resource).getBitmap();
// Create a new bitmap corresponding to the crop area
int[] cropAreaXY = new int[2];
int[] placeHolderXY = new int[2];
Rect rect = new Rect();
imageView.getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener(){
@Override
public boolean onPreDraw() {
try {
imageView.getLocationOnScreen(placeHolderXY);
cropArea.getLocationOnScreen(cropAreaXY);
cropArea.getGlobalVisibleRect(rect);
croppedBitmap = Bitmap.createBitmap(sourceBitmap, cropAreaXY[0], cropAreaXY[1] - placeHolderXY[1], rect.width(), rect.height());
// Save the croppedBitmap if you wish
getActivity().runOnUiThread(() -> imageView.setImageBitmap(croppedBitmap));
return true;
}finally {
imageView.getViewTreeObserver().removeOnPreDrawListener(this);
}
}
});
return false;
}
}).into(imageView);
}
fragment_camera.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:Android="http://schemas.Android.com/apk/res/Android"
xmlns:app="http://schemas.Android.com/apk/res-auto"
Android:layout_width="match_parent"
Android:layout_height="match_parent"
Android:background="@Android:color/black">
<androidx.camera.view.PreviewView
Android:id="@+id/preview"
Android:layout_width="0dp"
Android:layout_height="0dp"
app:layout_constraintDimensionRatio="3:4"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<View
Android:id="@+id/crop_area"
Android:layout_width="0dp"
Android:layout_height="0dp"
Android:layout_margin="8dp"
Android:background="@drawable/rectangle_round_corners"
app:layout_constraintBottom_toBottomOf="@+id/preview"
app:layout_constraintDimensionRatio="4.5:3"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<View
Android:id="@+id/cameraBottomView"
Android:layout_width="match_parent"
Android:layout_height="0dp"
Android:background="@Android:color/black"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/preview" />
<ImageButton
Android:id="@+id/btn_capture"
Android:layout_width="64dp"
Android:layout_height="64dp"
Android:layout_marginTop="8dp"
Android:layout_marginBottom="8dp"
Android:background="@drawable/ic_shutter"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/preview" />
</androidx.constraintlayout.widget.ConstraintLayout>
fragment_gallery.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:Android="http://schemas.Android.com/apk/res/Android"
xmlns:app="http://schemas.Android.com/apk/res-auto"
Android:id="@+id/layout_main"
Android:background="@Android:color/black"
Android:layout_width="match_parent"
Android:layout_height="match_parent">
<ImageView
Android:id="@+id/image_view"
Android:layout_width="match_parent"
Android:layout_height="wrap_content"
Android:visibility="visible"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<View
Android:id="@+id/crop_area"
Android:layout_width="0dp"
Android:layout_height="0dp"
Android:layout_margin="8dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintDimensionRatio="4.5:3"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>