I tried CameraX introduced at Google I / O 2019 on Fragment in Java, so I will briefly introduce it.
It's easy to proceed while watching the official Tutorial, so I will proceed while watching this.
Creating a project and adding dependencies are just as per the tutorial, so I will omit it in this article.
Add TextureView to layout.xml to be displayed in Fragment.
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout 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="#FFFFFF">
<TextureView
android:id="@+id/view_finder"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
<RelativeLayout
android:id="@+id/camera_header"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="#456700"
android:padding="15dp">
<ImageButton
android:id="@+id/camera_back"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@drawable/common_backbtn"
android:layout_alignParentLeft="true"/>
</RelativeLayout>
<RelativeLayout
android:id="@+id/camera_bottom_control"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:background="#456700"
android:padding="20dp">
<Button
android:id="@+id/capture_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:text="To shoot"
/>
</RelativeLayout>
</RelativeLayout>
It looks like this ↓
TextureView is placed on the entire surface and screen elements are superimposed on it.
All you have to do is set the preview and bind it to the fragment life cycle.
It's easy because you just bind the PreviewConfig created by CameraX.bindToLifecycle.
public void startCamera() {
PreviewConfig.Builder builder = new PreviewConfig.Builder();
builder.setTargetResolution(new Size(App.width, App.height));
PreviewConfig previewConfig = builder.build();
preview = new Preview(previewConfig);
preview.setOnPreviewOutputUpdateListener(new Preview.OnPreviewOutputUpdateListener() {
@Override
public void onUpdated(@NonNull Preview.PreviewOutput output) {
ViewGroup viewGroup = (ViewGroup) viewFinder.getParent();
viewGroup.removeView(viewFinder);
viewGroup.addView(viewFinder, 0);
viewFinder.setSurfaceTexture(output.getSurfaceTexture());
updateTransform();
}
});
CameraX.bindToLifecycle(this, preview);
}
public void updateTransform() {
Matrix matrix = new Matrix();
float centerX = viewFinder.getWidth() / 2f;
float centerY = viewFinder.getHeight() / 2f;
float rotationDegrees;
switch (viewFinder.getDisplay().getRotation()) {
case Surface.ROTATION_0:
rotationDegrees = 0f;
break;
case Surface.ROTATION_90:
rotationDegrees = 90f;
break;
case Surface.ROTATION_180:
rotationDegrees = 180f;
break;
case Surface.ROTATION_270:
rotationDegrees = 270f;
break;
default:
return;
}
matrix.postRotate(-rotationDegrees, centerX, centerY);
viewFinder.setTransform(matrix);
}
Basically, just write this, and if you call startCamera after getting the permission of the camera, the preview will be displayed.
You can't use it for anything just by previewing the camera, so let's capture the image at that moment when capture_button is pressed.
Add the following code before binding the startCamera method above.
ImageCaptureConfig.Builder imageBuilder = new ImageCaptureConfig.Builder();
imageBuilder.setTargetResolution(new Size(App.width, App.height));
imageBuilder.setCaptureMode(ImageCapture.CaptureMode.MAX_QUALITY);
imageBuilder.setFlashMode(FlashMode.ON);
ImageCaptureConfig imageCaptureConfig = imageBuilder.build();
imageCapture = new ImageCapture(imageCaptureConfig);
I am setting up image capture. Here, Flash is turned on to maximize the quality. After creating the ImageCaptureConfig, let's also add the ImageCaptureConfig to the bind method.
CameraX.bindToLifecycle(this, preview, imageCapture);
Now you are ready to go.
You can capture by calling imageCapture.takePicture (), so let's set an event for the button.
btnCamera.setOnClickListener((View v) -> {
imageCapture.takePicture(CameraFragment.this, new ImageCapture.OnImageCapturedListener() {
@Override
public void onCaptureSuccess(ImageProxy imageProxy, int rotationDegrees) {
}
@Override
public void onError(
@NonNull ImageCapture.ImageCaptureError imageCaptureError, @NonNull String message,
@Nullable Throwable cause) {
}
});
}
});
Since Executor is required for the argument of takePicture, here we will implement Executor in Fragment so that the processing after taking a picture can be executed by UiThread.
public class CameraFragment implements Executor {
@Override
public void execute(Runnable command) {
if(getActivity() != null){
getActivity().runOnUiThread(command);
}
}
}
When you press the shooting button and the shooting is successful, onCaptureSuccess (ImageProxy imageProxy, int rotationDegrees) is called and the image can be acquired from imageProxy!
It's easy ...
What was it that was written on Camera2?
It's a difficult word to implement the camera, so I've been waiting for this.
Well, I have described it very easily, but I will introduce it because there were some addictive points.
There is no shutter sound. Yes.
I was looking for something that could be set in the capture settings, but it's not found anywhere ...
If you want to make it sound, try to force it.
MediaActionSound sound = new MediaActionSound();
sound.play(MediaActionSound.SHUTTER_CLICK);
Yes, this is it.
public Bitmap imageProxyToBitmap(ImageProxy image, int rotationDegrees) {
ImageProxy.PlaneProxy planeProxy = image.getPlanes()[0];
ByteBuffer buffer = planeProxy.getBuffer();
byte[] bytes = new byte[buffer.remaining()];
buffer.get(bytes);
Matrix mat = new Matrix();
mat.postRotate(rotationDegrees);
if (rotationDegrees != 0) {
return rotate(BitmapFactory.decodeByteArray(bytes, 0, bytes.length), rotationDegrees);
} else {
return BitmapFactory.decodeByteArray(bytes, 0, bytes.length);
}
}
public Bitmap rotate(Bitmap in, int angle) {
Matrix mat = new Matrix();
mat.postRotate(angle);
return Bitmap.createBitmap(in, 0, 0, in.getWidth(), in.getHeight(), mat, true);
}
Q. Why is TextureView all over the layout file? Shouldn't it fit in the preview area?
A. I dare to do it all over.
If the preview size and the size aspect ratio of the Texture View do not match, the preview will be displayed distorted, or if you create a screen with a preview area that is smaller than the screen size, you shot that the Texture View is made to fit the preview area. The preview will be very difficult to see because things will be displayed in a reduced size in the TextureView.
If you put TextureView on the entire surface, all the above problems will be solved, and you can shoot with the same feeling as the camera on the entire surface that you usually use, so you can shoot without any discomfort.
If you want an image of only the area visible to the user, you can cut it out after shooting.
Please note that CameraX is still an alpha version, so the interface may change in the release version.
Also, although not introduced here, it seems that there is an API that makes it easy to use device-specific vendor effects (out of focus, HDR, night view), so I would like to try that as well in the future.
Recommended Posts