SKIA

2.7 SkSurface

SimonLee 2025. 5. 19. 22:22

SkSurface는 렌더 타겟이다.

렌더 타겟에 렌더링을 하고 이미지로 가져오는 방식을 알아보자.

 

728x90

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에서 사용할 수 있음)
SkPixmap의 peekPixels 함수를 통해서 SkSurface 픽셀 주소를 pixmap 변수에 저장하고,
SkBitmap을 생성하여 installPixels 함수를 통해서 복사 없이 pixmap (픽셀 데이터)를 SkBitmap에 연결.
SkBitmap을 SkIamge로 변환하여 캔버스에 렌더링 .
 
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;
}

 

728x90