2.7 SkSurface
SkSurface는 렌더 타겟이다.
렌더 타겟에 렌더링을 하고 이미지로 가져오는 방식을 알아보자.
CPU 기반 캔버스
다음 예제는 CPU기반 SkSurface를 생성하여 이미지로 전환 후 캔버스를 통해 렌더링하는 예제이다.
SkCanvas를 생성하는 함수는 SkSurfaces::Raster() 이다.
CPU Ram을 사용하여 만든 서피스 이며, GPU 없이 이미지 버퍼를 만들고 그 위에 그림을 그릴 수 있다.
파라메터로 SkImageInfo를 사용하며, 예제에서는 프리멀티플 64, 64 크기의 서피스를 생성한다.
SkSurface의 makeSurface 함수를 사용하면,
동일한 ImageInfo를 사용하되, 속성 값은 다르게 사용 할 수 있으며, 내부 픽셀값은 공유 하지 않는다.
SkPixmap이 등장하는데,
SkSurface에 렌더링 된 내용을 빠르게 읽어 들이기 위해 SkPixmap이 사용된다.
항목 | SkPixmap | SkBitmap |
역할 | 픽셀 버퍼를 가리키는 포인터 객체 | 픽셀 버퍼를 포함한 이미지 객체 |
소유권 | 없음 (외부 버퍼를 참조만 함) | 있음 (자기 버퍼 직접 가질 수 있음) |
버퍼 복사 | 없음 (항상 참조만) | 가능 (버퍼를 직접 만들거나 복사 가능) |
수명 관리 | 수동 (버퍼가 외부에서 관리됨) | 자동 (bitmap이 버퍼를 관리함) |
사용 목적 | 읽기 전용 포인터 (peek, raw 접근) | 이미지 객체 (캔버스에 그리기 등) |
GPU 사용 가능 | (CPU 전용) | (asImage()로 GPU에서 사용할 수 있음) |
void draw(SkCanvas* canvas) {
// 64x64 크기의 Surface(big)를 만들고, 프리멀티플 알파 형식(N32Premul)으로 초기화
sk_sp<SkSurface> big(SkSurfaces::Raster(SkImageInfo::MakeN32Premul(64, 64)));
// big surface로부터 32x32 크기의 작은 Surface(lil)를 생성 (same props)
sk_sp<SkSurface> lil(big->makeSurface(SkImageInfo::MakeN32(32, 32, kPremul_SkAlphaType)));
// big surface를 빨간색으로 클리어
big->getCanvas()->clear(SK_ColorRED);
// lil surface를 검정색으로 클리어
lil->getCanvas()->clear(SK_ColorBLACK);
// lil의 내용을 big의 canvas에 (16,16) 위치에 그린다
// 즉, big의 (16,16) ~ (48,48) 영역이 검정색으로 덮임
lil->draw(big->getCanvas(), 16, 16);
SkPixmap pixmap;
// big surface의 픽셀 데이터를 읽어올 수 있는지 확인
if (big->peekPixels(&pixmap)) {
SkBitmap bigBits;
// pixmap의 내용을 SkBitmap에 설치
bigBits.installPixels(pixmap);
// SkBitmap을 SkImage로 변환 후, 전달받은 canvas에 (0,0) 위치에 그림
canvas->drawImage(bigBits.asImage(), 0, 0);
}
}
bitmap을 사용해서 installPixels를 하면 비트맵을 수정가능하지만,
오직 출력만 목적이라면 아래와 같이 서피스에서 이미지 스냅샷으로 읽기 전용 이미지를 생성하여 렌더링 가능하다.
canvas->drawImage(big->makeImageSnapshot(), 0, 0);
PeekPixel의 이해
SkSurfaces::Raster 사용하여 Softwear Surface를 생성하고,
SK_ColorWHITE로 렌더링을 한다.
서피스의 픽셀 메모리를 가져와서 draw 후, 픽셀 메모리가 바뀌었는지 확인하는 예제이다.
rowBytes = 64이기 때문에, 서피스의 물리크기는 3 * 64 이다.
하지만 이미지 논리적 크기는 3 * 3 이기 때문에,
한 줄당 4 byte인 16개의 픽셀이 들어가 있고, 1개 실제 픽셀을 제외한 15개의 픽셀은 패딩 픽셀로 이해하면 된다.
drawPoint를 통해 픽셀 렌더링 전 후로 해당 픽셀값이 바뀌었는지 확인하는 코드가 들어있다.
loop에서는 3 by 3 반복을 하면서 (0,0) (1,1) (2,2) 의 값은 blur 픽셀 값이고,
나머지 값은 white pixel 값으로 확인 할 수 있다.
// Surface_MakeRaster.cpp
// Raster pixel memory Size = info.height * rowBytes
// 코드는 Skia의 SkSurface 메모리를 직접 접근해서 픽셀 값이 변했는지 확인하는 디버깅 예제
void SkSurfaceTestDraw(SkCanvas* canvasReal) {
SkImageInfo info = SkImageInfo::MakeN32Premul(3, 3);
const size_t rowBytes = 64;
// memorySize = 3 * 64 = 192
// 한 줄당 4byte 16개 픽셀이 들어가 있고, 15개 픽셀이 패딩 픽셀
sk_sp<SkSurface> surface(SkSurfaces::Raster(info, rowBytes, nullptr));
SkCanvas* canvas = surface->getCanvas();
canvas->clear(SK_ColorWHITE);
SkPixmap pixmap;
if (surface->peekPixels(&pixmap)) {
// 서피스가 가리키고 있는 픽셀 메모리
const uint32_t* colorPtr = pixmap.addr32();
// 서피스가 가지고 있는 픽셀 메모리며, clearWhite 이므로
SkPMColor pmWhite = colorPtr[0];
uint8_t a = (pmWhite >> 24) & 0xFF;
uint8_t r = (pmWhite >> 16) & 0xFF;
uint8_t g = (pmWhite >> 8) & 0xFF;
uint8_t b = (pmWhite >> 0) & 0xFF;
SkDebugf("WHITE argb : %d %d %d %d\n", a,r,g,b);
SkPaint paint;
paint.setColor(SK_ColorBLUE);
canvas->drawPoint(0, 0, paint);
canvas->drawPoint(1, 1, paint);
canvas->drawPoint(2, 2, paint);
a = (colorPtr[0] >> 24) & 0xFF;
r = (colorPtr[0] >> 16) & 0xFF;
g = (colorPtr[0] >> 8) & 0xFF;
b = (colorPtr[0] >> 0) & 0xFF;
SkDebugf("BLUE [0][0] argb : %d %d %d %d\n", a,r,g,b);
a = (colorPtr[17] >> 24) & 0xFF;
r = (colorPtr[17] >> 16) & 0xFF;
g = (colorPtr[17] >> 8) & 0xFF;
b = (colorPtr[17] >> 0) & 0xFF;
SkDebugf("BLUE [1][1] argb : %d %d %d %d\n", a,r,g,b);
a = (colorPtr[34] >> 24) & 0xFF;
r = (colorPtr[34] >> 16) & 0xFF;
g = (colorPtr[34] >> 8) & 0xFF;
b = (colorPtr[34] >> 0) & 0xFF;
SkDebugf("BLUE [2][2] argb : %d %d %d %d\n", a,r,g,b);
// (1, 1) (2, 2) 픽셀을 제외한 픽셀들은 전부 White 색상
// 두 픽셀은 Blue
for (int y = 0; y < info.height(); ++y) {
for (int x = 0; x < info.width(); ++x) {
SkDebugf("%c", colorPtr[x] == pmWhite ? '-' : 'x');
}
colorPtr += rowBytes / sizeof(colorPtr[0]);
SkDebugf("\n");
}
}
}
== 결과 ==
WHITE argb : 255 255 255 255
BLUE [0][0] argb : 255 0 0 255
BLUE [1][1] argb : 255 0 0 255
BLUE [2][2] argb : 255 0 0 255
x--
-x-
--x
ㄴㅇㄴㄴㅇㄴㅇㅇ
void SkSurfaceTestDraw(SkCanvas*) {
SkImageInfo info = SkImageInfo::MakeN32Premul(3, 3);
const size_t size = info.computeMinByteSize();
SkPMColor* pixels = new SkPMColor[size];
sk_sp<SkSurface> surface(SkSurfaces::WrapPixels(info, pixels, info.minRowBytes()));
SkCanvas* canvas = surface->getCanvas();
canvas->clear(SK_ColorWHITE);
SkPMColor pmWhite = pixels[0];
SkPaint paint;
canvas->drawPoint(1, 1, paint);
for (int y = 0; y < info.height(); ++y) {
for (int x = 0; x < info.width(); ++x) {
SkDebugf("%c", *pixels++ == pmWhite ? '-' : 'x');
}
SkDebugf("\n");
}
delete[] pixels;
}