그래픽과 이미지

이수민·2022년 12월 10일
post-thumbnail
  • 캔버스 : 🗒 도화지
  • 페인트 : 🖌️ 붓

🎨 캔버스와 페인트

  • 화면에 도형을 그릴 때 사용되는 Canvas와 Paint 클래스
  • android.graphics.Canvas 클래스의 점을 찍는 메소드의 원형
    • x와 y좌표에 점을 찍음
    • 단, 원점은 왼쪽 상단부터 (0,0)으로 시작함
    • 점은 Paint 객체에 설정된 색상, 두께 등으로 그려짐
  • android.graphics.Paint 클래스에서 색상을 지정하는 메소드의 원형

이런 식으로 사용할 수 있다.

⭐ 기억해야할 틀

  • 그래픽을 표현할 때는 View 클래스를 재정의하는 형태를 많이 사용함
  • 나머지는 자동 완성되거나 고정된 내용이므로 밑줄 친 부분만 변경하면 됨
  • 캔버스와 페인트를 사용해 화면에 나타낼 내용은 주석 부분에 코딩함

🧵 그래픽 처리의 기본 방식

  • 3행: XML 대신 재정의한 클래스를 화면에 보여준다.
  • 6행: View 클래스를 상속받아 MyGraphicView 클래스를 재정의한다.
  • 7~9행: 재정의한 클래스의 생성자이다.

  • 12~51행(@override부터): 클래스가 생성 or 무효화되면 실행되는 메소드를 정의한다.
    일반적으로 화면에 그려질 내용을 이곳에 코딩한다.
  • 13행: 상위 클래스의 onDraw()를 먼저 호출한다.
  • 14행: Paint 클래스 변수를 생성한다.
  • 15행: 페인트.setAntiAlias() 메서드는 도형의 끝을 부드럽게 처리해준다.
  • 16,19,23행: 페인트.setColor()로 도형의 색을 Paint에 저장한다.
    페인트.setColor()지정한 색은 다른 색으로 지정하기 전까지 계속 유지된다.
  • 17,21행: 캔버스.drawline(시작x, 시작y, 끝x, 끝y, 페인트)로 선을 그린다.
  • 20,24,39,48행: 페인트.setStrokeWidth()로 그려질 도형 or 글자 외곽선 두께를 설정한다.
    0으로 설정하면 기본값인 1px 두께로 그려진다.
  • 26행: 페인트.setStyle(Paint.Style.FILL)로 도형의 내부를 채운다. 얘도 한 번 설정하면 변경 전까지 계속 유지된다.
  • 30행: 페인트.setStyle(Paint.Style.STROKE)로 도형의 내부를 채우지 않는다. 얘도 한 번 설정하면 변경 전까지 계속 유지된다.
  • 27,31,34행: Rect 객체명 = new Rect(x,y,x1,y2)으로 사각형을 그리기 위해 Rect 객체를 생성하고 4개의 좌표를 미리 만든다. Rect 클래스는 int형을, RectF 클래스는 float형 값을 갖는다.
  • 28,32행: 캔버스.drawRect(사각형객체명, 페인트명)으로 사각형을 그린다.
  • 35행: 캔버스.drawRoundRect(사각형객체명, 타원의 x축 반지름, 타원의 y축 반지름, 페인트명)으로 사각형의 모서리를 둥글게 처리한다.
    x와 y값이 클수록 모서리가 더 둥근 사각형이 된다.
  • 37행: 캔버스.drawCircle(중심x, 중심y, 반지름, 페인트명)으로 원을 그린다.

  • 40~45행: Path는 연결된 여러 점을 가진 클래스이다.
    moveTo() 메서드로 해당 점으로 이동한 다음, lineTo() 메서드로 점을 계속 추가한다.
  • 46행: 캔버스.drawPath(패스명, 페인트명)으로 생성한 패스를 그린다.
  • 49행: 페인트.setTextSize()로 글자 크기를 설정한다.
  • 50행: 캔버스.drawText("출력할문구", x, y, 페인트명)으로 글자를 해당 좌표에 출력한다.
  • 페인트.setStrokeCap() :
  • 캔버스.drawOval(x, y, width, height) : 사각형 안에 내접하는 타원 그리기
  • 페인트.setColor(Color.argb()) : 투명도까지 설정가능한 색 설정 메서드

🍪 Canvas의 메서드들 (점, 선, 원, 사각형에 기본 도형을 그린다.)

📍 drawPoint(x, y, paint) : (x,y) 좌표에 점을 찍는다.
📍 drawLine(startX, startY, stopX, stopY, paint) : (x,y) 좌표부터 (x,y) 좌표에 선을 그린다.
📍 drawCircle(x, y, radius, paint) : (x,y) 좌표에 반지름(radius)만큼 원을 그린다.
📍 drawRect(left, top, right, bottom) : (left, top)와 (right, bottom)의 대각선을 기초로 사각형을 그린다.
📍 drawText(text, x, y, paint) : (x,y) 좌표에 문자를 출력한다.
📍 Paint.setColor(int color) : 그리기 색상
📍 drawARGB(a, r, g, b) : 기존 색상에 alpha가 추가된 것. (투명 : 0xff / 불투명 : 0x00)
📍 drawRGB(r, g, b) : 0~255까지 각 색상의 코드를 적으면 됨.
📍 drawColor(int color) : RED, BLUE 등 원하는 색상을 적으면 된다.
📍 drawPaint(paint) : 설정 값을 적용, 단순한 채우기
📍 drawRoundRect(RectF r, rx, ry, paint)
- rx & ry : 모서리의 둥근 정도. 가상의 타원으로 두 값이 클수록 모서리가 둥글다.
📍 drawOval(RectF r, paint) : Oval은 달걀모양, 지정한 사각형 영역에 내접한 원을 그린다.
📍 drawArc(RectF r, startAngle, sweepAngle, boolean useCenter, paint)
- startAngle : 3시방향이 0도, 시계방향으로 증가
- sweepAngle : 그리는 각도 크기
- useCenter : (T) 부채꼴, (F) 호
📍 drawLines(float[] pts, paint) : 배열에 저장된 선을 순서대로 그린다. 연결하고 싶으면 시작과 끝 점을 일치시킨다.
📍 drawPoints(float[] pts, paint) : 배열상의 좌표를 꺼내 여러 개의 점을 찍는다.

🍪 Paint의 메서드들 (그리기에 대한 속성 정보를 가지는 객체)

📍 setAntiAlias(boolean) : 경계면을 부드럽게 처리해준다.
📍 setColor(int color) : 단색을 지정한다.
📍 setRGB(r, g, b) : 더 더양한 색상을 지정한다.
📍 setARGB(a, r, g, b) : RGB에 Alpha가 추가된 것이다. Alpha는 투명이냐 불투명이냐를 결정할 수 있다.
📍 setStrokeWidth(width) : 펜 굵기
📍 setStrokeCap(Paint.Cap cap) : 끝 모양
- BUTT : 지정한 좌표에서 선이 끝남
- ROUND : 둥근 모양으로 끝이 장식된다.
- SQUARE : 사각형 모양이되 지정된 좌표보다 조금 더 그어진다.
📍 setStrokeJoin(Paint.Join join) : 모서리처럼 선분이 만나 각지는 곳의 모양
- MITER : 모서리를 90도 각진 형태 (Default)
- BEVEL : 모서리가 깍인 형태
- ROUND : 둥근 형태
📍 setStrokeMiter(float miter) : 0 이상의 값으로 어느 정도 각도로 뾰족하게 할 것인가 (StrokeJoin의 소속)
📍 setStyle(Paint.Style style) : 외곽선과 내부 중 어느 쪽을 그릴 것인가
- FILL : 채우기만 하며 외곽선 X (Default)
- FILL_AND_STROKE : 채우기도 하고 외곽선도 그린다.
- STROKE : 채우지는 않고 외곽선만 그린다.
📍 set(Paint src) : 객체끼리 대입
📍 reset() : 초기화
💡 cf) invalidate() : 새로 canvas를 그려준다.

🧵 터치 이벤트

  • 화면에 생성한 뷰를 터치하면 Touch 이벤트가 발생
  • View 클래스의 onTouchEvent( ) 메소드를 오버라이드해서 코딩
  • .getAction() 로 터치한 동작을 얻은 후, 각 터치를 switch~case문으로 구분한다.
  • 손가락으로 그림을 그리려면 터치 이벤트를 설정해야 한다.

✍🏻 간단 그림판 앱 만들기

1. View 클래스의 상속을 받는 MyGraphicView 클래스 만들기

  • 3행: 전역상수를 선언한다. 메뉴에서 선택한 것이 선인지 원인지를 구분하기 위해 사용할 것이다.
  • 4행: curShape는 현재 선택된 도형이 선(default)인지 원인지를 저장한다.
  • 13~18행: View 클래스로부터 상속받은 MyGraphicView 클래스를 화면에 출력한다.

2. 옵션 메뉴 작성하기

  • 오른쪽 상단에 선 그리기, 원 그리기 옵션 메뉴 만들기
  • 항목을 클릭하면 curShape 변수에, 선택한 전역상수 대입
  • onCreate( )와 같은 레벨로 onCreateOptionsMenu( )onOptionsItemSelected( ) 메소드 자동 완성

3. 터치와 관련된 메소드 완성하기

  • MyGraphicView의 전역변수인 시작x, 시작y, 끝x, 끝y, 반지름 변수를 선언
  • onTouchEvent( ) 메소드 자동 완성 후 추가될 부분 작성
  • 2행: 시작점과 끝점 좌표를 저장하기 위한 클래스 멤버 변수 4개를 선언한다.
  • 9~12행: 처음 터치했을 때가 선의 시작점이나 원의 중심점이 되는데 이 위치를 기억한다.
  • 13~17행: 화면을 터치한 상태에서 드래그하면 ACTION_MOVE에 해당되는데, ACTION_UP과 동일하게 취급하는 이유는 드래그할 때마다 그려지는 선(or 원)이 손가락을 계속 따라다니며 흔적을 보여주는 효과를 내야하기 때문이다.
    화면에서 손가락을 떼면 ACTION_UP이 발생하고 onTouchEvent() 메소드가 더이상 실행되지 않는다. 즉, 시작점(startX,startY)과 끝점(stopX,stopY)이 결정되는 것이다.
    17행의 invalidate()를 호출하면 화면이 무효화되고 onDraw() 메서드를 자동으로 실행한다.

4. 화면에 도형을 그리는 onDraw() 메서드 완성하기

  • MyGraphicView의 내부에 onDraw( )를 자동완성하고 나머지는 코딩
  • 페인트에 선의 두께, 채우기 여부, 선의 색상 지정
  • switch( )~case문으로 메뉴에서 선택한 내용에 따라 선 or 원을 그림
  • 3~7행: 페인트에 선의 두께, 채우기 여부, 선의 색상을 지정한다.
  • 9~18행: 옵션 메뉴에서 선택한 항목에 따라 선 or 원을 그린다.
    예제9-3 에서 옵션 메뉴에 따라 curShape 변수의 값이 변하게 했다.
  • 10~11행: 손가락으로 터치한 점(startX,startY)과 손가락을 뗀 점(stopX,stopY)까지 선을 그린다.
  • 13~16행: 시작점과 끝점의 거리를 계산해 반지름(radius)으로 사용한다.
    16행에서 시작점을 중심으로 하고 반지름이 radius인 원을 그린다.


🎨 이미지


🧵 비트맵의 기본

  • Bitmap 클래스캔버스에 이미지 파일을 보여줄 때 사용된다.
  • /res/drawable 폴더에 있는 이미지 파일을 보여주는 onDraw() 메소드
    • BitmapFactory.decodeResource()리소스 이미지에 접근
    • 캔버스.drawBitmap(비트맵이미지, 시작x, 시작y, null)화면에 이미지 출력
    • 비트맵이미지.recycle()비트맵 리소스 해제
  • SD 카드의 이미지 파일을 보여주는 onDraw( ) 메소드
    • BitmapFactory.decodeFile()SD카드의 이미지에 접근
    • 캔버스.drawBitmap(비트맵이미지, 시작x, 시작y, null)화면에 이미지 출력
    • 비트맵이미지.recycle()비트맵 리소스 해제
  • 이미지를 화면 중앙에 출력하기
  • 이미지를 화면 중앙에 출력하는 간단 예제
    • 13행: res/drawable 이미지에 접근한다.
    • 14~15행: this.getWidth()현재 뷰(MyGraphicView)의 너비를 구한다. 현재 뷰의 너비에서 이미지의 너비를 뺀 다음 2로 나눈 위치를 시작점으로 하여 이미지를 출력한다. 결국 화면 중앙에 이미지가 출력된다.
    • 16행: 이미지를 화면에 출력한다.
    • 17행: 비트맵 리소스를 해제한다.

🧵 이미지의 기하학적 변환

  • 이미지에 대해 기하학적 변환을 하는 것이 아니라 캔버스(도화지)에 대해 기하학적 변환을 한 다음, 이미지 파일을 변환된 캔버스에 출력하는 것이다.
  • 도화지를 회전시킨 상태에서 그림을 그리면 결국 그림이 회전된 것으로 보이는 효과와 마찬가지이다.

⭐ 많이 사용되는 Canvas 클래스의 기하학적 메소드

  • 회전: rotate( )
  • 확대/축소: scale( )
  • 이동: translate( )
  • 기울이기: skew( )

✍🏻 기하학적 변환 적용해보기

  • 5~6행: 뷰(그려지는 화면)의 중심 좌표를 구한다.
  • 10행: 캔버스를 45도 회전시키며, 회전시키는 기준(중심)을 뷰의 중앙으로 설정했다.
  • 11행: 회전된 캔버스에 이미지를 출력한다.
  • 13행: 캔버스를 X축으로 -150만큼, Y축으로 +200만큼 이동한다.
  • 16행: 캔버스의 축척을 2배 확대한다.
  • 19행: 캔버스를 0.3만큼 기울인다.

🧵 이미지 활용

  • 우리는 포토샵 등의 이미지 처리 응용 프로그램으로 이미지에 다양한 효과를 줄 수 있다. 안드로이드에서도 엠보싱, 블러링 등의 효과를 처리할 수 있다.
  • 안드로이드에서는 ColorMatrix 클래스를 활용해 색상을 처리한다.

블러링(Blurring)

  • 이미지를 뿌옇게 만드는 효과
  • BlurMaskFilter 클래스를 사용함
  • 스타일에 따른 효과 변화
  • 블러링 효과를 적용하는 간단 예제
    • 9~10행: 블러링 효과 적용을 위해 Paint 클래스와 BlurMaskFilter 클래스를 준비한다.
    • 12행: 블러마스크필터를 생성한다. 30은 블러링 효과를 낼 반지름(크기)이고, NORMAL은 스타일이다.
    • 13행: 페인트 변수에 블러마스크필터를 적용한다.
    • 14행: 비트맵을 출력할 때 페인트를 적용한다.
    • 16행: 12~15행을 반복한다. (스타일만 변경)

엠보싱(embossing)

  • 이미지가 볼록하게 튀어나와 보이는 효과
  • EmbossMaskFilter 클래스를 사용
  • 블러링보다 복잡함
    • 빛의 xyz 방향 1차 배열: 빛이 비추는 x, y, z방향 배열
    • 빛의 밝기: 0~1의 값
    • 반사 계수: 5~8이 적당
    • 블러링 크기: 볼록하게 표현하기 위한 가장자리의 크기
  • 빛 xyz방향 1차 배열에 따른 효과 변화 살펴보기
  • 엠보싱을 적용한 간단 예제
    • 9행: 엠보싱 효과 적용을 위해 EmbossMaskFilter 클래스를 준비한다.
    • 11행: 엠보싱마스크필터를 생성한다. 필요에 따라 값을 적절히 변경해도 된다.
    • 14행: 11~13행을 반복한다. (빛의 방향만 {10,3,3},{3,10,3},{3,3,10} 으로 변경)

컬러매트릭스(Color Matrix)

  • 색상이나 밝기를 조절
  • ColorMatrixColorFilter 클래스를 사용함
  • ColorMatrix에 사용할 배열(Array)의 각 위치의 값
    • 배열의 크기는 4x5
    • Red, Green, Blue, Alpha의 값은 기본적으로 1이 설정되어 있는데, 이 값을 몇 배로 올리면 색상의 대비(contrast)가 커진다.
    • 색상을 밝게 하고 싶으면 Brightness에 양수를, 어둡게 하고 싶으면 음수를 입력한다.
  • RGB 색상 대비를 2배로 변경한 예제
    • 11~14행: 일차원 배열을 이차원 배열처럼 사용했다. RGB 각 색상을 2배로 했다. 색상 값을 올리면 너무 밝아보이므로 Brightness 값을 -25 정도로 낮추었다.
    • 15~16행: 컬러매트릭스를 만들고, 페인트.setColorFilter()를 통해 페인트에 적용한다.

✍🏻 미니 포토샵 앱 만들기

1. AndroidManifest.xml로 가서 하드웨어 가속기 기능 끄기

2. xml 수정하기

  • 바깥 리니어레이아웃 안에 2개의 리니어레이아웃 생성
  • 두 리니어레이아웃의 layout_weight는 1:9 정도로 설정
  • 위쪽 리니어레이아웃에 이미지 버튼 6개 생성
  • 위젯의 id를 다음과 같이 선언
    • 리니어레이아웃 : iconLayout, pictureLayout
    • 이미지버튼 : ibZoomin, ibZoomout, ibRotate, ibBright, ibDark, ibGray

  • 5,6,20,21행: 레이아웃의 크기(높이)를 1:9로 설정한다.
  • 9~11행: 확대 이미지 버튼을 생성한다.
  • 13행: 축소, 회전, 밝게하기, 어둡게하기, 회색영상 이미지버튼이 생략되었다.

3. Java 코드 수정하기 (1) MainActivity.java 코딩

  • 이미지버튼에 대응할 6개 위젯 변수 선언
  • MyGraphicView 클래스 변수를 선언
  • MyGraphicView 정의 : 그림 파일을 중앙에 비트맵으로 출력
  • pictureLayout을 인플레이트한 후 MyGraphicView를 추가

  • 2~3행: 위젯 변수 6개와 클래스 변수 1개를 전역변수로 선언한다.
  • 11~13행: xml파일의 pictureLayout을 인플레이트한 후 MyGraphicView형 클래스 변수를 첨부한다. 결국 아래쪽 레이아웃에는 MyGraphicView에서 설정한 내용이 출력된다.
  • 17~33행: MyGraphicView 클래스를 정의한다. (예제9-6과 동일)

3. Java 코드 수정하기 (2) 확대 아이콘 코딩

  • 축척에 사용될 전역변수를 선언
  • clickIcons( ) 메소드를 정의하고 확대 아이콘 클릭 리스너를 생성
  • clickIcons( ) 메소드를 호출
  • onDraw( )에 Cavas.scale( ) 메소드를 추가

  • 2행: 축척으로 사용될 전역변수 2개의 예제9-12의 4행에 선언한다. 22행에서 축척이 1이면 화면의 크기에는 변화가 없다.
  • 5~15행: clickIcons() 메소드를 정의하고 확대 이미지버튼의 클릭리스너를 만든다. 확대 아이콘을 클릭할 때마다 축척 전역변수가 0.2씩 증가한다. 확대를 위해서는 onDraw() 메소드를 다시 호출해야 하는데 뷰의 invalidate() 메소드는 onDraw()를 자동으로 호출해준다. 예제9-12의 16행에 코딩한다.
  • 17행: onCreate() 메소드 안에서 clickIcons() 메소드를 호출한다. 예제9-12의 14행에 코딩한다.
  • 20~22행: 화면(뷰)의 중앙을 구하고, 전역변수에 설정된 값으로 캔버스의 축척을 설정한다. onDraw() 메소드 내부, 즉 예제9-12의 24행에 코딩한다.

축소 아이콘도 비슷하게 코딩한다.
이 때 ibZoominibZoomout으로, +0.2f-0.2f로 변경해야 한다.

3. Java 코드 수정하기 (3) 회전 아이콘 코딩

  • 회전에 사용될 전역변수 선언
  • 회전 아이콘 클릭 리스너 생성
  • onDraw( )에 Cavas.rotate( ) 메소드를 추가

  • 2행: 회전 각도로 사용될 전역변수를 예제9-13의 2행 다음에 선언한다.
  • 5~11행: 회전하기 이미지버튼의 클릭리스너를 만든다. 회전하기 아이콘을 누를 때마다 회전각도가 20씩 증가하도록 설정한다. clickIcons() 메소드 내부에 코딩한다.
  • 14행: 전역변수에 설정된 각도로 캔버스를 회전시킨다. onDraw() 메소드 내부, 즉 예제9-13의 22행 다음에 코딩한다.

3. Java 코드 수정하기 (4) 밝게하기 아이콘 코딩

  • 화면 밝기에 사용될 전역변수 선언
  • 밝게 하기 아이콘 클릭 리스너 생성
  • onDraw( )에 컬러매트릭스 적용

  • 2행: 색상 배수로 사용될 전역변수를 예제9-14의 2행 다음에 선언한다.
  • 5~11행: 밝게하기 이미지버튼의 클릭리스너를 만든다. 밝게하기 아이콘을 클릭할 때마다 밝기가 0.2배씩 증가하도록 설정한다. clickIcons() 메소드 내부에 코딩한다.
  • 14~20행: 예제9-10의 설명과 동일하다. 단, RGB값을 전역변수의 값으로 설정했다. onDraw() 메소드 내부, 즉 예제9-14의 14행 다음에 코딩한다.
  • 23행: 예제9-12의 29행 drawBitmap() 메소드의 마지막 파라미터를 null에서 paint로 바꾼다.

어둡게 하기 아이콘도 비슷하게 코딩한다.
이 때 ibBrightibDark로, +0.2f-0.2f로 변경해야 한다.

3. Java 코드 수정하기 (5) 흑백화 아이콘 코딩

  • 채도에 사용될 전역변수 선언
  • 회색 영상 아이콘 클릭 리스너 생성
  • onDraw( )에 채도 설정 적용

  • 2행: 채도 배수로 사용될 전역변수를 선언한다. 예제9-15의 2행 다음에 선언한다.
    • 채도 0: 회색조 영상으로 바뀜
    • 채도 0~1: 채도 낮게 보임
    • 채도 1: 기본
    • 채도 1 이상: 채도 높게 보임
  • 5~12행: 회색영상 이미지버튼의 클릭리스너를 만든다. 흑백화 아이콘을 클릭할 때마다 채도가 1→0 혹은 0→1로 변경된다. 즉, 회색과 컬러가 스위칭되면서 바뀌도록 한다. clickIcons() 메소드 내부에 코딩한다.
  • 15행: onDraw() 메소드 내부인 예제9-15의 19~20행 사이에 코딩한다. 채도의 값을 0으로 설정하면 회색영상을 만든다. 1일때는 실행하지 않는다.
    채도를 바꾸는 setSaturation() 메소드를 실행하면 그 앞에 설정된 컬러매트릭스 값이 무시된다.
    즉, 회색영상으로 바꾸면 밝게하기/어둡게하기 아이콘이 동작하지 않는다.

0개의 댓글