我正在使用CommonsWare CWAC-Camera库(https://github.com/commonsguy/cwac-camera)来包装原生Android Camera API。
我想允许用户在拍照之前在预览图像上选择一个点,然后(准确地)在他们拍摄照片后在最终图像上挑选出相同的点。虽然这大致有效,但最终图像上的标记通常偏离目标约100-200像素。
首先,我通过在CameraView上设置OnTouchListener来捕获用户的原始选定点。然后我存储MotionEvent中的X和Y坐标。为了帮助进行视觉验证,我还会在拍摄照片之前立即在用户选择的坐标处绘制一个圆圈。
当用户拍摄照片时,我从SimpleCameraHost继承了覆盖saveImage,以便在将最终图像写入存储之前在最终图像上绘制相应的圆圈。为了计算在最终图像上绘制圆圈的位置,我正在执行以下操作:
然而,这还不够准确 - 至少,不是我正在测试的Nexus 7(2013)。我不确定在将标记添加到最终图像的位置计算中我缺少什么。
演示此问题的简化示例如下:
MainActivity.java:
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mCameraContainer = findViewById(R.id.camera_container);
mCanvasView = (CanvasView)findViewById(R.id.canvas_view);
FragmentManager fmgr = getFragmentManager();
FragmentTransaction ft = fmgr.beginTransaction();
mCameraFragment = (CameraFragment)fmgr.findFragmentByTag(CAMERA_FRAGMENT_TAG);
if (null == mCameraFragment) {
mCameraFragment = new CameraFragment();
ft.add(R.id.camera_container, mCameraFragment, CAMERA_FRAGMENT_TAG);
}
ft.commit();
if (null == mCameraHost) {
mCameraHost = new MyCameraHost(this);
}
mCameraFragment.setHost(mCameraHost);
ViewTreeObserver ccObserver = mCameraContainer.getViewTreeObserver();
ccObserver.addOnGlobalLayoutListener(new OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
CameraView cv = (CameraView)mCameraFragment.getView();
View previewWidget = ((ViewGroup)cv).getChildAt(0);
mCameraHost.setPreviewSize(previewWidget.getWidth(), previewWidget.getHeight());
cv.setOnTouchListener(new OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent e) {
mCanvasView.setPoint(e.getX(), e.getY());
mCameraHost.setPoint(e.getX(), e.getY());
return true;
}
});
mCameraContainer.getViewTreeObserver().removeOnGlobalLayoutListener(this);
}
});
Button photoButton = (Button)findViewById(R.id.takePhotoButton);
photoButton.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
mCameraFragment.takePicture();
}
});
}
MyCameraHost.java:
@Override
public void saveImage(PictureTransaction xact, byte[] image) {
// decode the final image as a Bitmap
BitmapFactory.Options opt = new BitmapFactory.Options();
opt.inMutable = true;
Bitmap imageBitmap = BitmapFactory.decodeByteArray(image, 0, image.length, opt);
// draw a blank Bitmap with just the markup, using a canvas size equivalent to the preview view
Bitmap markerBitmap = Bitmap.createBitmap(previewViewWidth, previewViewHeight, Bitmap.Config.ARGB_8888);
Canvas c = new Canvas(markerBitmap);
Paint p = new Paint();
p.setStrokeWidth(10);
p.setStyle(Style.FILL);
p.setColor(Color.BLUE);
c.drawCircle(previewMarkerX, previewMarkerY, 20, p);
// scale the markup bitmap up to final-image size using Matrix.ScaleToFit.CENTER
Matrix m = new Matrix();
m.setRectToRect(new RectF(0, 0, previewViewWidth, previewViewHeight), new RectF(0, 0, imageBitmap.getWidth(), imageBitmap.getHeight()), Matrix.ScaleToFit.CENTER);
// overlay the scaled marker Bitmap onto the image
Canvas imageCanvas = new Canvas(imageBitmap);
imageCanvas.drawBitmap(markerBitmap, m, null);
// save the combined image
ByteArrayOutputStream bos = new ByteArrayOutputStream();
imageBitmap.compress(CompressFormat.JPEG, 70, bos);
byte[] image2 = bos.toByteArray();
super.saveImage(xact, image2);
}
布局XML:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity" >
<FrameLayout
android:id="@+id/camera_container"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<com.example.cwaccameratouchpoint.CanvasView
android:id="@+id/canvas_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignLeft="@id/camera_container"
android:layout_alignRight="@id/camera_container"
android:layout_alignTop="@id/camera_container"
android:layout_alignBottom="@id/camera_container" />
<Button
android:id="@+id/takePhotoButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentTop="true"
android:layout_alignParentRight="true"
android:text="go" />
</RelativeLayout>
根据CommonsWare的评论,我更新了下面的saveImage以包含全出血偏移并拼出变换计算。这是一种改进,但仍然没有它应该的准确:
// decode the final image as a Bitmap
BitmapFactory.Options opt = new BitmapFactory.Options();
opt.inMutable = true;
Bitmap imageBitmap = BitmapFactory.decodeByteArray(image, 0, image.length, opt);
int finalImageWidth = imageBitmap.getWidth();
int finalImageHeight = imageBitmap.getHeight();
// calculate selected point x and y values as applied to full-size final image
// apply full-bleed-based offset, where xoffset and yoffset represent the
// offset of the TextureView within its parent CameraView
int bleedAdjustedX = (int)previewMarkerX - xoffset;
int bleedAdjustedY = (int)previewMarkerY - yoffset;
// calculate offset for change in aspect ratio
// for now, assume portrait orientation only
double finalImageAspectRatio = (double)finalImageHeight / (double)finalImageWidth;
double aspectAdjustedWidth = (double)previewViewHeight / finalImageAspectRatio;
double aspectXOffset = (aspectAdjustedWidth - previewViewWidth) / 2;
int aspectAdjustedX = bleedAdjustedX + (int)aspectXOffset;
// scale adjusted coordinates for full-size image
double normalizedAdjustedX = (double)aspectAdjustedX / aspectAdjustedWidth;
double normalizedAdjustedY = (double)bleedAdjustedY / (double)previewViewHeight;
double scaledX = normalizedAdjustedX * (double)finalImageWidth;
double scaledY = normalizedAdjustedY * (double)finalImageHeight;
// draw markup on final image
Canvas c = new Canvas(imageBitmap);
Paint p = new Paint();
p.setStrokeWidth(10);
p.setStyle(Style.FILL);
p.setColor(Color.BLUE);
c.drawCircle((float)scaledX, (float)scaledY, 20, p);
// save marked-up image
ByteArrayOutputStream bos = new ByteArrayOutputStream();
imageBitmap.compress(CompressFormat.JPEG, 70, bos);
byte[] image2 = bos.toByteArray();
super.saveImage(xact, image2);