문제
회사에서 전체 페이지를 스크린샷으로 이미지를 추출해야 하는 요구사항이 생겼다.
전체 페이지의 경우 스크린샷의 크기가 매우 매우 커질 수가 있다.
스크린샷이 커질 때 스크린샷을 해보니, 스크린샷이 짤리는 문제가 생겼다.

원인
크롬 (크로미움) 브라우저의 최대 텍스쳐 사이즈는 16,384px이고, 그 이상의 사이즈는 짤리고 0px 부터 반복되어 캡쳐된다..
정말 그런지 볼까?
크롬 개발자의 답변에서 오픈한 코드로 힌트를 얻을 수 있다.

omg... 코드를 보려니 2017년의 답변이고 현재 7년이 지난만큼 코드도 바뀐 모양이다.

다행히 폴더 구조를 완전히 바꾸지는 않은 모양이다. resource_pool.cc 파일에서 해당 정보에 대한 힌트를 얻을 수 있었다.

주석을 보니 GetArea 함수는 overflow (뷰포트가 오버플로우 한다는 의미로 추정된다) 된다면 에러를 발생시키는데, max_texture_size() 라는 함수로 이를 제한해서 overflow가 되지 않나보다.
그러면 이제 max_texture_size 에 대한 정보를 찾아보자!
아쉽게도 나는 C++과 OpenGL에 대한 지식이 제로여서 GPT의 도움을 받아서 찾아보았다.

max_texture_size 에 대한 정보를 찾았다!

0x0D33 이라는 값을 가지고 있는데… 이건 사실 십진수로 3379이다.
실제로 계산하는 값이라기 보다 무언가의 Key 인 것 같다.
GL_MAX_TEXTURE_SIZE 라는 변수를 사용하는 곳을 찾았다!

GetIntegerv 라는 무언가를 사용하나보다.
GetIntegerv 를 찾았다!
Chromium Code Search를 사용해보면서 느낀건데, 코드 텍스트 에디터를 참 잘만들었다.
사실상 웹으로 IDE를 쓰는 것처럼 검색하기가 편하다
검색한 변수가 구조체인지, 클래스인지, 함수인지 잘알려준다.
아무튼 코드를 이해할 수가 없어서 GPT한테 코드 내용을 물어봤다.

정리하자면, GetIntegerv 를 호출할 때, GPU가 값을 조회하고 공유 메모리에 결과값을 올린다. (리턴하지 않는다라고 한다.)
아래와 같은 코드는 그래서 0x0D33 으로 값을 조회하고, &max_texture_size_ 라는 공유 메모리 주소에 결과값을 저장한다.
gl_->GetIntegerv(GL_MAX_TEXTURE_SIZE, &max_texture_size_);
그리고 &max_texture_size_ 주소를 조회해서 결과값을 사용하는 방식으로 보인다.
이 후는 하드웨어 (정확히는 GPU) 에 의존하여 결과값이 생성돼서 정확한 값을 추적하기 어렵다.
이로서 Eric Seckler의 아래 답변은 타당하다고 볼 수 있을 것 같다.
For the software GL backend we use with headless, this limit is 16384px.
문제를 해결하자
- 뷰포트를 옮겨서 여러 이미지 스크린샷
puppeteer로 뷰포트를 옮긴다. 가장 일반적인 방법은window.scrollTo()- 뷰포트가 overflow 될 때까지 스크린샷한다.
- 여러 개의 스크린샷 이미지 값
Uint8Array을 배열에 담는다.
- 위 이미지들을 stitch
sharp로 위 이미지들을 합친다.- 이미지 틀을 생성한다.
// 예시 코드 const baseImage = await sharp({ create: { width: 1000, height: 20000, }, }) .png() .toBuffer();
- 이미지 틀에 이미지 배열에 있는 이미지들을 합성한다.
// 예시 코드 const composites = []; for (let i = 0; i < screenshots.length; i++) { const imgBuffer = screenshots[i]; // 현재 스크린샷의 메타데이터(너비, 높이 등)를 얻어옴 const meta = await sharp(imgBuffer).metadata(); // 각 스크린샷의 y 좌표 (스크롤한 위치) let top = i * viewportHeight; // 만약 스크린샷이 이미지 틀을 벗어난다면 크롭 if (top + meta.height > totalHeight) { const cropHeight = totalHeight - top; // base image 안에 들어갈 높이 const croppedBuffer = await sharp(imgBuffer) .extract({ left: 0, top: 0, width: meta.width, height: cropHeight }) .toBuffer(); composites.push({ input: croppedBuffer, top: top, left: 0, }); } else { // 그대로 합성 composites.push({ input: imgBuffer, top: top, left: 0, }); } }
주의할 점: 합성한 이미지가 이미지 틀의 사이즈보다 크면 다음 에러가 난다.
[Error: Input image exceeds pixel limit]
결과 (예시 이미지)
