일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 | 23 | 24 | 25 | 26 | 27 | 28 |
29 | 30 | 31 |
- Rxjava Observable
- 객체
- ANR이란
- 자바 다형성
- 2022 플러터 설치
- 안드로이드 유닛테스트란
- 2022 플러터 안드로이드 스튜디오
- 플러터 설치 2022
- 안드로이드 유닛 테스트
- rxjava disposable
- 큐 자바 코드
- jvm이란
- 서비스 vs 쓰레드
- 안드로이드 유닛 테스트 예시
- 안드로이드 레트로핏 crud
- rxjava cold observable
- android ar 개발
- 안드로이드 os 구조
- 멤버변수
- 스택 큐 차이
- 클래스
- 안드로이드 라이선스 종류
- 안드로이드 레트로핏 사용법
- 안드로이드 라이선스
- rxjava hot observable
- jvm 작동 원리
- 서비스 쓰레드 차이
- 스택 자바 코드
- ar vr 차이
- android retrofit login
- Today
- Total
나만을 위한 블로그
[Android] GLSurfaceView의 카메라 프리뷰 화면 캡쳐하는 법 (with. AR core, OpenGL ES 2.0) 본문
[Android] GLSurfaceView의 카메라 프리뷰 화면 캡쳐하는 법 (with. AR core, OpenGL ES 2.0)
참깨빵위에참깨빵_ 2020. 7. 2. 21:15※ 아래 코드는 갤럭시 S8+에서 정상 동작을 확인했지만 그 상위 단계 핸드폰에선 확인하지 않은 코드다
DrawArActivity.java에서 AR core로 선들을 그린 다음 설정 > 화면 캡쳐 버튼을 눌러, 해당 화면을 캡쳐하는 기능을 구현했다. 국내 사이트 중 하나에서 사진을 저장하는 코드를 가져왔고, 스택오버플로우세서 GLSurfaceView의 화면을 캡쳐하는 코드를 가져와 합쳤다.
여기서 사진을 저장하는 코드를 가져왔고
https://stackoverflow.com/a/34752887
이 글의 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 폴더를 열면 방금 캡쳐한 사진이 정상적으로 저장돼 있는 걸 볼 수 있다.