我将CameraX从Kotlin移植到Java,但是我的预览(TextureView)仅在以横向打开后才正确。当我尝试旋转(纵向)时,预览变形(可能为90度)。我从清单中删除了android:screenOrientation =“ sensorPortrait”,否则第一次预览也出现了错误。对updateTransform()的任何其他调用均不起作用,更改旋转valkues也会使我的TextureView变小并且无法解决变形。我之所以问是因为大多数代码都在Kotlin中,而我在Java中找不到可行的解决方案。这里的代码:
package com.sweetieapps.librarianpro;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import androidx.camera.core.CameraX;
import androidx.camera.core.ImageCapture;
import androidx.camera.core.ImageCaptureConfig;
import androidx.camera.core.ImageProxy;
import androidx.camera.core.Preview;
import androidx.camera.core.PreviewConfig;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;
import android.app.Activity;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.res.Configuration;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Matrix;
import android.media.Image;
import android.os.Bundle;
import android.util.Log;
import android.util.Rational;
import android.util.Size;
import android.view.Surface;
import android.view.TextureView;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.Toast;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.nio.ByteBuffer;
public class CameraXActivity extends AppCompatActivity {
private final int REQUEST_CODE_PERMISSIONS = 10; //arbitrary number, can be changed accordingly
private final String[] REQUIRED_PERMISSIONS = new String[]{"android.permission.CAMERA","android.permission.WRITE_EXTERNAL_STORAGE"};//array w/ permissions from manifest
private TextureView txView;
ImageView imgRotation, imgCapture;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.camerax);
txView = findViewById(R.id.txvCameraXViewFinder);
imgRotation = findViewById(R.id.imgCameraXRotation);
imgCapture = findViewById(R.id.imgCameraXCapture);
ImageView imgMask = findViewById(R.id.imgCameraXMask);
if(Variables.PictureMode == Variables.PictureModes.User){
imgMask.setVisibility(View.VISIBLE);
}
else{
imgMask.setVisibility(View.INVISIBLE);
}
if(allPermissionsGranted()){
startCamera(); //start camera if permission has been granted by user
} else{
ActivityCompat.requestPermissions(this, REQUIRED_PERMISSIONS, REQUEST_CODE_PERMISSIONS);
}
imgRotation.setOnClickListener(v ->
updateTransform());
}
private void startCamera(){
//make sure there isn't another camera instance running before starting
CameraX.unbindAll();
/* start preview */
int aspRatioW = txView.getWidth(); //get width of screen
int aspRatioH = txView.getHeight(); //get height
Rational asp = new Rational(aspRatioW,aspRatioH); //aspect ratio
Size screen = new Size(aspRatioW,aspRatioH); //size of the screen
//config obj for preview/viewfinder thingy.
PreviewConfig pConfig = new PreviewConfig.Builder().setTargetAspectRatio(asp).setTargetResolution(screen).build();
Preview preview = new Preview(pConfig); //lets build it
//to update the surface texture we have to destroy it first, then re-add it
preview.setOnPreviewOutputUpdateListener(
output -> {
ViewGroup parent=(ViewGroup)txView.getParent();
parent.removeView(txView);
parent.addView(txView,0);
txView.setSurfaceTexture(output.getSurfaceTexture());
updateTransform();
});
/* image capture */
//config obj, selected capture mode
ImageCaptureConfig imgCapConfig = new ImageCaptureConfig.Builder().setCaptureMode(ImageCapture.CaptureMode.MAX_QUALITY)
.setTargetRotation(getWindowManager().getDefaultDisplay().getRotation()).build();
final ImageCapture imgCap = new ImageCapture(imgCapConfig);
imgCapture.setOnClickListener(v -> {
imgCap.takePicture(new ImageCapture.OnImageCapturedListener() {
@Override
public void onCaptureSuccess(ImageProxy image, int rotationDegrees) {
//ByteBuffer bb = image.getPlanes()[0].getBuffer();
//byte[] buf = new byte[bb.remaining()];
//bb.get(buf);
Intent returnIntent = new Intent();
if(image.getImage() != null){
//img.setImageBitmap(imageToBitmap(image.getImage()));
Variables.bitmap = bitmapRotate(imageToBitmap(image.getImage()));
image.close();
setResult(Activity.RESULT_OK,returnIntent);
finish();
}
else{
setResult(Activity.RESULT_CANCELED, returnIntent);
finish();
}
}
/*
@Override
public void onError(
ImageCapture.UseCaseError error, String message, @Nullable Throwable cause) {
// silently ingore error
}
*/
});
/*
//img.setImageBitmap(txView.getBitmap());
*/
/*
File file = new File(Environment.getExternalStorageDirectory() + "/" + System.currentTimeMillis() + ".jpg");
imgCap.takePicture(file, new ImageCapture.OnImageSavedListener() {
@Override
public void onImageSaved(@NonNull File file) {
String msg = "Photo capture succeeded: " + file.getAbsolutePath();
Toast.makeText(getBaseContext(), msg,Toast.LENGTH_LONG).show();
}
@Override
public void onError(@NonNull ImageCapture.UseCaseError useCaseError, @NonNull String message, @Nullable Throwable cause) {
String msg = "Photo capture failed: " + message;
Toast.makeText(getBaseContext(), msg,Toast.LENGTH_LONG).show();
if(cause != null){
cause.printStackTrace();
}
}
});
*/
});
/* image analyser
ImageAnalysisConfig imgAConfig = new ImageAnalysisConfig.Builder().setImageReaderMode(ImageAnalysis.ImageReaderMode.ACQUIRE_LATEST_IMAGE).build();
ImageAnalysis analysis = new ImageAnalysis(imgAConfig);
analysis.setAnalyzer(
new ImageAnalysis.Analyzer(){
@Override
public void analyze(ImageProxy image, int rotationDegrees){
//y'all can add code to analyse stuff here idek go wild.
}
});
//bind to lifecycle:
CameraX.bindToLifecycle(this, analysis, imgCap, preview);
*/
CameraX.bindToLifecycle(this, imgCap, preview);
}
private void updateTransform(){
//compensates the changes in orientation for the viewfinder, bc the rest of the layout stays in portrait mode.
//methinks :thonk:
//imgCap does this already, this class can be commented out or be used to optimise the preview
Matrix mx = new Matrix();
float w = txView.getMeasuredWidth();
float h = txView.getMeasuredHeight();
float centreX = w / 2f; //calc centre of the viewfinder
float centreY = h / 2f;
int rotationDgr;
int rotation = (int)txView.getRotation(); //cast to int bc switches don't like floats
switch(rotation){ //correct output to account for display rotation
case Surface.ROTATION_0:
rotationDgr = 0;
break;
case Surface.ROTATION_90:
rotationDgr = 90;
break;
case Surface.ROTATION_180:
rotationDgr = 180;
break;
case Surface.ROTATION_270:
rotationDgr = 270;
break;
default:
return;
}
mx.postRotate((float)rotationDgr, centreX, centreY);
txView.setTransform(mx); //apply transformations to textureview
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
//start camera when permissions have been granted otherwise exit app
if(requestCode == REQUEST_CODE_PERMISSIONS){
if(allPermissionsGranted()){
startCamera();
} else{
Toast.makeText(this, "Permissions not granted by the user.", Toast.LENGTH_SHORT).show();
finish();
}
}
}
private boolean allPermissionsGranted(){
//check if req permissions have been granted
for(String permission : REQUIRED_PERMISSIONS){
if(ContextCompat.checkSelfPermission(this, permission) != PackageManager.PERMISSION_GRANTED){
return false;
}
}
return true;
}
private Bitmap imageToBitmap(Image image){
ByteBuffer buffer = image.getPlanes()[0].getBuffer();
byte[] bytes = new byte[buffer.capacity()];
buffer.get(bytes);
return BitmapFactory.decodeByteArray(bytes, 0, bytes.length, null);
}
private Bitmap bitmapCrop(Bitmap bitmap){
return bitmapCompress(Bitmap.createBitmap(bitmap, 0,0,bitmap.getWidth(), bitmap.getWidth()));
}
private Bitmap bitmapRotate(Bitmap bitmap){
Matrix matrix = new Matrix();
matrix.postRotate(90);
if(bitmap.getHeight() < bitmap.getWidth()){
if(Variables.PictureMode == Variables.PictureModes.User){
return bitmapCrop(Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), matrix, true));
}
else{
return bitmapCompress(Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), matrix, true));
}
}
else{
if(Variables.PictureMode == Variables.PictureModes.User){
return bitmapCrop(bitmap);
}
else{
return bitmapCompress(bitmap);
}
}
}
private Bitmap bitmapCompress(Bitmap bitmap){
ByteArrayOutputStream stream = new ByteArrayOutputStream();
bitmap.compress(Bitmap.CompressFormat.JPEG, 90, stream);
Bitmap compressedBitmap = BitmapFactory.decodeStream(new ByteArrayInputStream(stream.toByteArray()));
int width, height;
width = compressedBitmap.getWidth() / 5;
height = width * 8;
Bitmap scaledBitmap = Bitmap.createScaledBitmap(bitmap, bitmap.getWidth(), height, true);
return bitmapScale(scaledBitmap);
}
private Bitmap bitmapScale(Bitmap bitmap){
//Matrix
int maxHeight = 500;
int maxWidth = 500;
float scale = Math.min(((float)maxHeight / bitmap.getWidth()), ((float)maxWidth / bitmap.getHeight()));
Matrix matrix = new Matrix();
matrix.postScale(scale, scale);
return Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), matrix, true);
}
@Override
public void onConfigurationChanged(@NonNull Configuration newConfig)
{
Log.d("tag", "config changed");
super.onConfigurationChanged(newConfig);
updateTransform();
int orientation = newConfig.orientation;
if (orientation == Configuration.ORIENTATION_PORTRAIT)
//Log.d("tag", "Portrait");
imgCapture.setVisibility(View.VISIBLE);
else if (orientation == Configuration.ORIENTATION_LANDSCAPE)
imgCapture.setVisibility(View.INVISIBLE);
//Log.d("tag", "Landscape");
//else
// Log.w("tag", "other: " + orientation);
}
}
这里是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"
tools:context=".MainActivity">
<TextureView
android:id="@+id/txvCameraXViewFinder"
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<ImageView
android:id="@+id/imgCameraXMask"
android:layout_width="0dp"
android:layout_height="0dp"
android:visibility="invisible"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="1.0"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="@+id/guidelineCameraX"
app:layout_constraintVertical_bias="0.0"
app:srcCompat="@android:color/black"
tools:visibility="invisible" />
<ImageButton
android:id="@+id/imgCameraXCapture"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_margin="24dp"
android:background="@android:color/transparent"
android:scaleType="fitCenter"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="@+id/guidelineCameraXCapture"
app:srcCompat="@drawable/shutter" />
<androidx.constraintlayout.widget.Guideline
android:id="@+id/guidelineCameraX"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal"
app:layout_constraintGuide_percent="0.6" />
<androidx.constraintlayout.widget.Guideline
android:id="@+id/guidelineCameraXPortraitMode"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
app:layout_constraintGuide_percent="0.8" />
<ImageView
android:id="@+id/imgCameraXRotation"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginEnd="16dp"
android:layout_marginBottom="16dp"
android:adjustViewBounds="true"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="@+id/imgCameraXMask"
app:layout_constraintStart_toStartOf="@+id/guidelineCameraXPortraitMode"
app:srcCompat="@drawable/take_in_portrait_mode" />
<androidx.constraintlayout.widget.Guideline
android:id="@+id/guidelineCameraXCapture"
android:layout_width="wrap_content"
android:layout_height="0dp"
android:orientation="horizontal"
app:layout_constraintGuide_percent="0.85" />
</androidx.constraintlayout.widget.ConstraintLayout>
[似乎您的updateTransform()
方法仅通过旋转预览以使其与UI方向匹配来校正预览。预览失真通常在相机预览输出的大小与预览曲面的大小不同时发生,要解决此问题,必须适当缩放TextureView使其适合其范围内的预览,或者可以通过应用来更新TextureView的SurfaceTexture使用矩阵对其进行转换(例如您对旋转所做的操作)。
考虑到不同的相机硬件级别,众多的Android制造商以及各种可能出错的方法,正确进行预览并不容易。 camerax库带有一个包含一个PreviewView类的视图构件(androidx.camera.view),建议使用它而不是TextureView,因为它试图在后台解决这些问题。使用它看起来像这样。
在布局文件中,包括一个PreviewView。
<androidx.constraintlayout.widget.ConstraintLayout>
<androidx.camera.view.PreviewView
android:id="@+id/previewView"
... />
</androidx.constraintlayout.widget.ConstraintLayout>
然后在创建预览用例后,将其与PreviewView的PreviewSurfaceProvider挂钩。
final Preview preview = new Preview.Builder().build();
preview.setPreviewSurfaceProvider(previewView.getPreviewSurfaceProvider());
请记住,视图工件仍处于Alpha状态并且仍在开发中,因此它可能仍然存在两个问题。但总的来说,它解决了开发人员面临的许多预览问题(包括预览失真)。