관리 메뉴

나만을 위한 블로그

[Android] GLSurfaceView의 카메라 프리뷰 화면 캡쳐하는 법 (with. AR core, OpenGL ES 2.0) 본문

Android

[Android] GLSurfaceView의 카메라 프리뷰 화면 캡쳐하는 법 (with. AR core, OpenGL ES 2.0)

참깨빵위에참깨빵_ 2020. 7. 2. 21:15
728x90
반응형

※ 아래 코드는 갤럭시 S8+에서 정상 동작을 확인했지만 그 상위 단계 핸드폰에선 확인하지 않은 코드다

 

DrawArActivity.java에서 AR core로 선들을 그린 다음 설정 > 화면 캡쳐 버튼을 눌러, 해당 화면을 캡쳐하는 기능을 구현했다. 국내 사이트 중 하나에서 사진을 저장하는 코드를 가져왔고, 스택오버플로우세서 GLSurfaceView의 화면을 캡쳐하는 코드를 가져와 합쳤다.

 

http://blog.naver.com/PostView.nhn?blogId=miraclehwan&logNo=220611579792&redirect=Dlog&widgetTypeCall=true

 

화면 캡쳐 후 갤러리 저장

이번 2주차 과제 중 제일 삽질을 많이 했던 부분이다....(삽질한 시간만 쳐도 48시간은 넘을듯....) 캡쳐, ...

blog.naver.com

 

여기서 사진을 저장하는 코드를 가져왔고

 

https://stackoverflow.com/a/34752887

 

Capture screen of GLSurfaceView to bitmap

I need to be able to capture an image of a GLSurfaceView at certain moment in time. I have the following code: relative.setDrawingCacheEnabled(true); screenshot = Bitmap.createBitmap(relative.

stackoverflow.com

 

이 글의 aaronvargas라는 유저가 남긴 답글대로 따라했다. 

 

먼저 xml은 아래와 같다.

 

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout 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=".DrawAR.DrawArActivity">

    <ImageView
        android:id="@+id/test_imageview"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

    <RelativeLayout
        android:id="@+id/content"
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:orientation="vertical">

            <android.opengl.GLSurfaceView
                android:id="@+id/surfaceview"
                android:layout_width="fill_parent"
                android:layout_height="fill_parent"
                android:layout_gravity="top"
                android:layout_weight="1" />

            <LinearLayout
                android:id="@+id/button_bar"
                android:layout_width="match_parent"
                android:layout_height="50dp"
                android:background="@android:color/black"
                android:orientation="horizontal">

                <ImageButton
                    android:layout_width="@android:dimen/app_icon_size"
                    android:layout_height="match_parent"
                    android:layout_weight="1"
                    android:background="@android:color/transparent"
                    android:elevation="0dp"
                    android:onClick="onClickUndo"
                    android:src="@drawable/ic_undo_black_24dp"
                    android:tint="@android:color/darker_gray" />


                <ImageButton
                    android:id="@+id/imageButton9"
                    android:layout_width="@android:dimen/app_icon_size"
                    android:layout_height="match_parent"
                    android:layout_weight="1"
                    android:background="@android:color/transparent"
                    android:elevation="0dp"
                    android:onClick="onClickRecenter"
                    android:src="@drawable/ic_my_location_black_24dp"
                    android:tint="@android:color/darker_gray" />

                <ImageButton
                    android:id="@+id/imageButton13"
                    android:layout_width="@android:dimen/app_icon_size"
                    android:layout_height="match_parent"
                    android:layout_weight="1"
                    android:background="@android:color/transparent"
                    android:elevation="0dp"
                    android:onClick="onClickClear"
                    android:src="@drawable/ic_delete_black_24dp"
                    android:tint="@android:color/darker_gray" />

                <ImageButton
                    android:id="@+id/settingsButton"
                    android:layout_width="@android:dimen/app_icon_size"
                    android:layout_height="match_parent"
                    android:layout_weight="1"
                    android:background="@android:color/transparent"
                    android:elevation="0dp"
                    android:onClick="onClickSettings"
                    android:src="@drawable/ic_settings_black_24dp"
                    android:tint="@android:color/darker_gray" />
            </LinearLayout>

        </LinearLayout>

        <LinearLayout
            android:id="@+id/strokeUI"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:background="@android:color/background_dark"
            android:orientation="vertical"
            android:paddingBottom="10dp"
            android:paddingLeft="10dp"
            android:paddingRight="10dp"
            android:paddingTop="10dp">

            <Button
                android:id="@+id/capture_frame"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:text="화면 캡쳐"/>

            <TextView
                android:id="@+id/textView"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:text="선 굵기"
                android:textColor="@android:color/white" />

            <SeekBar
                android:id="@+id/lineWidth"
                android:layout_width="match_parent"
                android:layout_height="40dp"
                android:max="100"
                android:progress="50" />

            <TextView
                android:id="@+id/textView4"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:text="Distance Scale"
                android:textColor="@android:color/white"/>

            <SeekBar
                android:id="@+id/distanceScale"
                android:layout_width="match_parent"
                android:layout_height="40dp"
                android:max="100"
                android:progress="50" />

            <TextView
                android:id="@+id/textView3"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:text="선이 부드럽게 그어지는 정도"
                android:textColor="@android:color/white"/>

            <SeekBar
                android:id="@+id/smoothingSeekBar"
                android:layout_width="match_parent"
                android:layout_height="40dp"
                android:max="100"
                android:progress="50" />

        </LinearLayout>

    </RelativeLayout>

</FrameLayout>

 

아래는 자바 파일 코드다. 너무 길어서 전역 변수로 선언한 것과 onCreate()에서 사진 찍는 함수를 호출해 사용하는 부분, 사진 찍는 함수의 소스코드만 첨부한다. 자세한 내용은 주석이 달려있는 DrawArActivity.java 참고

 

private Button capture_frame;
private Bitmap snapshotBitmap;

private interface BitmapReadyCallbacks
    {
        void onBitmapReady(Bitmap bitmap);
    }

버튼은 화면 캡쳐 버튼이고, 비트맵은 createBitmapFromGLSurface(0, 0, mSurfaceView.getWidth(), mSurfaceView.getHeight(), gl);의 결과값을 담기 위한 비트맵 변수다.

그 밑의 인터페이스는 스택오버플로우의 답변대로 만든 것이다. 인터페이스를 선언한 이유는 잘 모르겠지만 이런 식으로도 구현할 수 있는가보다.

 

다음은 버튼 클릭 리스너 부분이다.

 

// GLSurfaceView 사진 찍기
        capture_frame = (Button) findViewById(R.id.capture_frame);
        capture_frame.setOnClickListener(view -> {
            captureBitmap(new BitmapReadyCallbacks()
            {
                @Override
                public void onBitmapReady(Bitmap bitmap)
                {
                    String path = Environment.getExternalStorageDirectory().getAbsolutePath() + "/arcapture";

                    File file = new File(path);
                    if (!file.exists())
                    {
                        file.mkdirs();
                        Toast.makeText(DrawArActivity.this, "폴더가 생성되었습니다.", Toast.LENGTH_SHORT).show();
                    }

                    SimpleDateFormat day = new SimpleDateFormat("yyyyMMddHHmmss");
                    Date date = new Date();
                    Bitmap captureview = bitmap;

                    FileOutputStream fos;
                    try
                    {
                        fos = new FileOutputStream(path + "/Capture" + day.format(date) + ".jpeg");
                        captureview.compress(Bitmap.CompressFormat.JPEG, 100, fos);
                        sendBroadcast(new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE, Uri.parse("file://" + path + "/Capture" + day.format(date) + ".JPEG")));
                        Toast.makeText(DrawArActivity.this, "촬영 완료. 갤러리를 확인해주세요 (폴더명 - arcapture)", Toast.LENGTH_SHORT).show();
                        fos.flush();
                        fos.close();
                    } catch (FileNotFoundException e)
                    {
                        e.printStackTrace();
                    } catch (IOException e)
                    {
                        e.printStackTrace();
                    }
                }
            });
        });

String path = Environment.getExternalStorageDirectory().getAbsolutePath() + "/arcapture"; 이곳에서 arcapture라고 입력한 후 사진을 찍으면 이 이름을 가진 폴더에 사진이 저장된다. 만약 없다면 자동으로 생성된다.

 

다음은 onCreate() 밖에 선언한 captureBitmap()과 createBitmapFromGLSurface()의 코드다.

 

private void captureBitmap(final BitmapReadyCallbacks bitmapReadyCallbacks)
    {
        mSurfaceView.queueEvent(new Runnable()
        {
            @Override
            public void run()
            {
                EGL10 egl = (EGL10) EGLContext.getEGL();
                GL10 gl = (GL10) egl.eglGetCurrentContext().getGL();
                snapshotBitmap = createBitmapFromGLSurface(0, 0, mSurfaceView.getWidth(), mSurfaceView.getHeight(), gl);

                runOnUiThread(new Runnable()
                {
                    @Override
                    public void run()
                    {
                        bitmapReadyCallbacks.onBitmapReady(snapshotBitmap);
                    }
                });

            }
        });
    }

    private Bitmap createBitmapFromGLSurface(int x, int y, int w, int h, GL10 gl) throws OutOfMemoryError
    {
        int bitmapBuffer[] = new int[w * h];
        int bitmapSource[] = new int[w * h];
        IntBuffer intBuffer = IntBuffer.wrap(bitmapBuffer);
        intBuffer.position(0);

        try
        {
            gl.glReadPixels(x, y, w, h, GL10.GL_RGBA, GL10.GL_UNSIGNED_BYTE, intBuffer);
            int offset1, offset2;
            for (int i = 0; i < h; i++)
            {
                offset1 = i * w;
                offset2 = (h - i - 1) * w;
                for (int j = 0; j < w; j++)
                {
                    int texturePixel = bitmapBuffer[offset1 + j];
                    int blue = (texturePixel >> 16) & 0xff;
                    int red = (texturePixel << 16) & 0x00ff0000;
                    int pixel = (texturePixel & 0xff00ff00) | red | blue;
                    bitmapSource[offset2 + j] = pixel;
                }
            }
        } catch (GLException e)
        {
            return null;
        }

        return Bitmap.createBitmap(bitmapSource, w, h, Bitmap.Config.ARGB_8888);
    }

 

이렇게 한 후 앱을 실행해 선들을 그리고 설정 > 화면 캡쳐 버튼을 누르면 저장완료 토스트가 나온다.

그 후 앨범으로 들어가서 arcapture 폴더를 열면 방금 캡쳐한 사진이 정상적으로 저장돼 있는 걸 볼 수 있다.

반응형
Comments