Search code examples
androidscreenshotarcore

How can i take a screenshot of ArFragment?


After hours of searching, i'm finally able to save screenshot of ArFragment. but the problem is it only saves the current image of the camera except the 3D object which is placed.

how can i get the full screenshot (current image of the camera + 3D object which is placed)?

the codes that i used is below here.

ImageButton btn3 = (ImageButton)findViewById(R.id.camera_btn);
btn3.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        onSceneUpdate((FrameTime) frameTime);

        Toast.makeText(AR_Activity.this, "스크린샷이 저장되었습니다.", Toast.LENGTH_SHORT).show();
    }
});

private void onSceneUpdate(FrameTime frameTime) {
    try {
        Date now = new Date();
        android.text.format.DateFormat.format("yyyy-MM-dd_hh:mm:ss", now);
        String mPath = Environment.getExternalStorageDirectory().toString() + "/" + now + ".jpg";
        Frame currentFrame = arFragment.getArSceneView().getArFrame();
        Image currentImage = currentFrame.acquireCameraImage();
        int imageFormat = currentImage.getFormat();
        if (imageFormat == ImageFormat.YUV_420_888) {
            Log.d("ImageFormat", "Image format is YUV_420_888");
        }

        WriteImageInformation((Image) currentImage, (String) mPath);
    } catch (Exception e) {

    }
}

private static byte[] NV21toJPEG(byte[] nv21, int width, int height) {
    ByteArrayOutputStream out = new ByteArrayOutputStream();
    YuvImage yuv = new YuvImage(nv21, ImageFormat.NV21, width, height, null);
    yuv.compressToJpeg(new Rect(0, 0, width, height), 100, out);
    return out.toByteArray();
}

public static void WriteImageInformation(Image image, String path) {

    byte[] data = null;
    data = NV21toJPEG(YUV_420_888toNV21(image),
            image.getWidth(), image.getHeight());
    BufferedOutputStream bos = null;
    try {
        bos = new BufferedOutputStream(new FileOutputStream(path));
        bos.write(data);
        bos.flush();
        bos.close();

    } catch (Throwable e) {
        e.printStackTrace();
    }
}

private static byte[] YUV_420_888toNV21(Image image) {
    byte[] nv21;
    ByteBuffer yBuffer = image.getPlanes()[0].getBuffer();
    ByteBuffer uBuffer = image.getPlanes()[1].getBuffer();
    ByteBuffer vBuffer = image.getPlanes()[2].getBuffer();

    int ySize = yBuffer.remaining();
    int uSize = uBuffer.remaining();
    int vSize = vBuffer.remaining();

    nv21 = new byte[ySize + uSize + vSize];

    //U and V are swapped
    yBuffer.get(nv21, 0, ySize);
    vBuffer.get(nv21, ySize, vSize);
    uBuffer.get(nv21, ySize + vSize, uSize);

    return nv21;
}

Solution

  • Use PixelCopy. it worked perfectly.

    For those who might wonder, I will add my code below.

    ImageButton btn3 = (ImageButton)findViewById(R.id.camera_btn);
        btn3.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                takePhoto();
            }
        }); 
    private String generateFilename() {
    
        //현재시간을 기준으로 파일 이름 생성
        String date =
                new SimpleDateFormat("yyyyMMddHHmmss", java.util.Locale.getDefault()).format(new Date());
        return Environment.getExternalStoragePublicDirectory(
                Environment.DIRECTORY_PICTURES) + File.separator + "IM/" + date + "_screenshot.jpg";
    }
    
    private void saveBitmapToDisk(Bitmap bitmap, String filename) throws IOException {
    
        //사용자의 갤러리에 IM 디렉토리 생성 및 Bitmap 을 JPEG 형식으로 갤러리에 저장
        File out = new File(filename);
        if (!out.getParentFile().exists()) {
            out.getParentFile().mkdirs();
        }
        try (FileOutputStream outputStream = new FileOutputStream(filename);
             ByteArrayOutputStream outputData = new ByteArrayOutputStream()) {
            bitmap.compress(Bitmap.CompressFormat.JPEG, 100, outputData);
            outputData.writeTo(outputStream);
            outputStream.flush();
            outputStream.close();
        } catch (IOException ex) {
            throw new IOException("Failed to save bitmap to disk", ex);
        }
    }
    
    private void takePhoto(){
        //PixelCopy 를 사용하여 카메라 화면과 object 를 bitmap 으로 생성
        final String filename = generateFilename();
        ArSceneView view = arFragment.getArSceneView();
    
        final Bitmap bitmap = Bitmap.createBitmap(view.getWidth(),view.getHeight(),
                Bitmap.Config.ARGB_8888);
    
        final HandlerThread handlerThread = new HandlerThread("PixelCopier");
        handlerThread.start();
    
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
            PixelCopy.request(view, bitmap, (copyResult) -> {
                if (copyResult == PixelCopy.SUCCESS) {
                    try {
                        saveBitmapToDisk(bitmap, filename);
    
                        //Media Scanning 실시
                        Uri uri = Uri.parse("file://" + filename);
                        Intent i = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE);
                        i.setData(uri);
                        sendBroadcast(i);
    
                    } catch (IOException e) {
                        Toast toast = Toast.makeText(AR_Activity.this, e.toString(),
                                Toast.LENGTH_LONG);
                        toast.show();
                        return;
                    }
                    Snackbar snackbar = Snackbar.make(findViewById(android.R.id.content),
                            "스크린샷이 저장되었습니다.", Snackbar.LENGTH_LONG);
                    snackbar.setAction("갤러리에서 보기", v -> {
                        //어플 내에서 저장한 스크린샷을 확인 가능
                        File photoFile = new File(filename);
    
                        Uri photoURI = FileProvider.getUriForFile(AR_Activity.this,
                                AR_Activity.this.getPackageName() + ".ar.codelab.name.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(AR_Activity.this,
                            "스크린샷 저장 실패!: " + copyResult, Toast.LENGTH_LONG);
                    toast.show();
                }
                handlerThread.quitSafely();
            }, new Handler(handlerThread.getLooper()));
        }
    }