在 Sceneform 中拍照时隐藏 PlaneRenderer
Hide PlaneRenderer when taking photos in Sceneform
我根据 Google (https://codelabs.developers.google.com/codelabs/sceneform-intro/index.html?index=..%2F..index#15) 中的 Codelabs 教程添加了一项功能,允许用户拍摄添加到场景中的 AR 对象的照片。代码工作正常,但是,我希望在用户拍摄的照片中隐藏 PlaneRenderer(ARCore 检测到表面时出现的白点)。
在 "Capture Photo" 按钮的 onClickListener 中,我尝试在调用 takePhoto() 之前将 PlaneRenderer 设置为不可见。这会在屏幕上隐藏 PlaneRenderer,但不会在拍摄的照片中隐藏。
这是我的 onClickListener:
capturePhotoBtn.setOnClickListener(new View.OnClickListener(){
@Override
public void onClick(View v) {
arFragment.getArSceneView().getPlaneRenderer().setVisible(false);
for (TransformableNode vNode : videoNodeList){
if (vNode.isSelected()){
vNode.getTransformationSystem().selectNode(null);
}
}
takePhoto();
}
});
videoNodeList 包含一个 transformableNodes 列表,用于跟踪用户添加的对象(因为用户可以将多个对象添加到场景中)。由于对象是可变换节点,用户可以点击它们以 resize/rotate,这会在 selected 对象下方显示一个小圆圈。所以,加入的for-loop就是在拍照的时候去select所有的transformableNodes,保证小圆圈也不会出现在照片中。
takePhoto()方法来自CodeLabs教程,给出如下:
private void takePhoto() {
final String filename = generateFilename();
ArSceneView view = arFragment.getArSceneView();
// Create a bitmap the size of the scene view.
final Bitmap bitmap = Bitmap.createBitmap(view.getWidth(), view.getHeight(),
Bitmap.Config.ARGB_8888);
// Create a handler thread to offload the processing of the image.
final HandlerThread handlerThread = new HandlerThread("PixelCopier");
handlerThread.start();
// Make the request to copy.
PixelCopy.request(view, bitmap, (copyResult) -> {
if (copyResult == PixelCopy.SUCCESS) {
try {
File file = saveBitmapToDisk(bitmap, filename);
MediaScannerConnection.scanFile(this,
new String[] { file.toString() }, null,
new MediaScannerConnection.OnScanCompletedListener() {
public void onScanCompleted(String path, Uri uri) {
Log.i("ExternalStorage", "Scanned " + path + ":");
Log.i("ExternalStorage", "-> uri=" + uri);
}
});
} catch (IOException e) {
Toast toast = Toast.makeText(ChromaKeyVideoActivity.this, e.toString(),
Toast.LENGTH_LONG);
toast.show();
return;
}
Snackbar snackbar = Snackbar.make(findViewById(android.R.id.content),
"Photo saved", Snackbar.LENGTH_LONG);
snackbar.setAction("Open in Photos", v -> {
File photoFile = new File(filename);
Uri photoURI = FileProvider.getUriForFile(ChromaKeyVideoActivity.this,
ChromaKeyVideoActivity.this.getPackageName() + ".provider",
photoFile);
Intent intent = new Intent(Intent.ACTION_VIEW, photoURI);
intent.setDataAndType(photoURI, "image/*");
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
startActivity(intent);
});
snackbar.show();
} else {
Toast toast = Toast.makeText(ChromaKeyVideoActivity.this,
"Failed to copyPixels: " + copyResult, Toast.LENGTH_LONG);
toast.show();
}
handlerThread.quitSafely();
}, new Handler(handlerThread.getLooper()));
}
为了让您看得更清楚,当点击 "Capture Photo" 按钮时,PlaneRenderer 会隐藏在设备屏幕上。这是用户点击 "Capture Photo" 按钮后立即看到的内容:
但是,PlaneRenderer 仍然出现在拍摄的照片中。这是拍摄的结果图像:
这不是我想要的,因为我想在照片中隐藏 PlaneRenderer(即拍摄的照片不应该有白点)
此应用程序的用户通过 select 从菜单中输入对象并点击 PlaneRenderer 来添加对象,因此完全禁用 PlaneRenderer 是不可行的。此外,我在应用程序中还有另一个视频录制功能,只需将 PlaneRenderer 设置为不可见即可成功隐藏录制中的 PlaneRenderer,因此我不确定为什么它在拍摄照片时不起作用。
任何帮助将不胜感激! :)
经过无数小时终于弄明白了。分享我的解决方案(可能不是最好的解决方案)以防将来有人遇到同样的问题。
我发现,由于使用了 handlerThread,每当点击按钮时,takePhoto() 方法总是在 PlaneRenderer 设置为不可见之前发生。因此,我添加了一个短暂的延迟以确保发生相反的情况——即。延迟 takePhoto() 方法一段时间,这样该方法将始终在 planeRenderer 不可见后发生。
这是代码片段:
capturePhotoBtn.setOnClickListener(new View.OnClickListener(){
@Override
public void onClick(View v) {
arFragment.getArSceneView().getPlaneRenderer().setVisible(false);
for (TransformableNode vNode : videoNodeList){
if (vNode.isSelected()){
vNode.getTransformationSystem().selectNode(null);
}
}
v.postDelayed(new Runnable() {
public void run() {
takePhoto();
}
}, 80);
}
});
这个方法对我有用,但我相信有更好的方法可以解决这个问题。希望这对遇到同样问题的人有所帮助,如果您知道更好的解决方案,请随时贡献力量。
我根据 Google (https://codelabs.developers.google.com/codelabs/sceneform-intro/index.html?index=..%2F..index#15) 中的 Codelabs 教程添加了一项功能,允许用户拍摄添加到场景中的 AR 对象的照片。代码工作正常,但是,我希望在用户拍摄的照片中隐藏 PlaneRenderer(ARCore 检测到表面时出现的白点)。
在 "Capture Photo" 按钮的 onClickListener 中,我尝试在调用 takePhoto() 之前将 PlaneRenderer 设置为不可见。这会在屏幕上隐藏 PlaneRenderer,但不会在拍摄的照片中隐藏。
这是我的 onClickListener:
capturePhotoBtn.setOnClickListener(new View.OnClickListener(){
@Override
public void onClick(View v) {
arFragment.getArSceneView().getPlaneRenderer().setVisible(false);
for (TransformableNode vNode : videoNodeList){
if (vNode.isSelected()){
vNode.getTransformationSystem().selectNode(null);
}
}
takePhoto();
}
});
videoNodeList 包含一个 transformableNodes 列表,用于跟踪用户添加的对象(因为用户可以将多个对象添加到场景中)。由于对象是可变换节点,用户可以点击它们以 resize/rotate,这会在 selected 对象下方显示一个小圆圈。所以,加入的for-loop就是在拍照的时候去select所有的transformableNodes,保证小圆圈也不会出现在照片中。
takePhoto()方法来自CodeLabs教程,给出如下:
private void takePhoto() {
final String filename = generateFilename();
ArSceneView view = arFragment.getArSceneView();
// Create a bitmap the size of the scene view.
final Bitmap bitmap = Bitmap.createBitmap(view.getWidth(), view.getHeight(),
Bitmap.Config.ARGB_8888);
// Create a handler thread to offload the processing of the image.
final HandlerThread handlerThread = new HandlerThread("PixelCopier");
handlerThread.start();
// Make the request to copy.
PixelCopy.request(view, bitmap, (copyResult) -> {
if (copyResult == PixelCopy.SUCCESS) {
try {
File file = saveBitmapToDisk(bitmap, filename);
MediaScannerConnection.scanFile(this,
new String[] { file.toString() }, null,
new MediaScannerConnection.OnScanCompletedListener() {
public void onScanCompleted(String path, Uri uri) {
Log.i("ExternalStorage", "Scanned " + path + ":");
Log.i("ExternalStorage", "-> uri=" + uri);
}
});
} catch (IOException e) {
Toast toast = Toast.makeText(ChromaKeyVideoActivity.this, e.toString(),
Toast.LENGTH_LONG);
toast.show();
return;
}
Snackbar snackbar = Snackbar.make(findViewById(android.R.id.content),
"Photo saved", Snackbar.LENGTH_LONG);
snackbar.setAction("Open in Photos", v -> {
File photoFile = new File(filename);
Uri photoURI = FileProvider.getUriForFile(ChromaKeyVideoActivity.this,
ChromaKeyVideoActivity.this.getPackageName() + ".provider",
photoFile);
Intent intent = new Intent(Intent.ACTION_VIEW, photoURI);
intent.setDataAndType(photoURI, "image/*");
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
startActivity(intent);
});
snackbar.show();
} else {
Toast toast = Toast.makeText(ChromaKeyVideoActivity.this,
"Failed to copyPixels: " + copyResult, Toast.LENGTH_LONG);
toast.show();
}
handlerThread.quitSafely();
}, new Handler(handlerThread.getLooper()));
}
为了让您看得更清楚,当点击 "Capture Photo" 按钮时,PlaneRenderer 会隐藏在设备屏幕上。这是用户点击 "Capture Photo" 按钮后立即看到的内容:
但是,PlaneRenderer 仍然出现在拍摄的照片中。这是拍摄的结果图像:
这不是我想要的,因为我想在照片中隐藏 PlaneRenderer(即拍摄的照片不应该有白点)
此应用程序的用户通过 select 从菜单中输入对象并点击 PlaneRenderer 来添加对象,因此完全禁用 PlaneRenderer 是不可行的。此外,我在应用程序中还有另一个视频录制功能,只需将 PlaneRenderer 设置为不可见即可成功隐藏录制中的 PlaneRenderer,因此我不确定为什么它在拍摄照片时不起作用。
任何帮助将不胜感激! :)
经过无数小时终于弄明白了。分享我的解决方案(可能不是最好的解决方案)以防将来有人遇到同样的问题。
我发现,由于使用了 handlerThread,每当点击按钮时,takePhoto() 方法总是在 PlaneRenderer 设置为不可见之前发生。因此,我添加了一个短暂的延迟以确保发生相反的情况——即。延迟 takePhoto() 方法一段时间,这样该方法将始终在 planeRenderer 不可见后发生。
这是代码片段:
capturePhotoBtn.setOnClickListener(new View.OnClickListener(){
@Override
public void onClick(View v) {
arFragment.getArSceneView().getPlaneRenderer().setVisible(false);
for (TransformableNode vNode : videoNodeList){
if (vNode.isSelected()){
vNode.getTransformationSystem().selectNode(null);
}
}
v.postDelayed(new Runnable() {
public void run() {
takePhoto();
}
}, 80);
}
});
这个方法对我有用,但我相信有更好的方法可以解决这个问题。希望这对遇到同样问题的人有所帮助,如果您知道更好的解决方案,请随时贡献力量。