(Node) 이미지 가로 세로 크기 구하기

Mar 12, 2024

브라우저 런타임에서 Image 엘리먼트를 이용하면 이미지의 크기를 쉽게 구할 수 있습니다.
아래의 코드를 브라우저 콘솔에서 바로 실행해볼 수 있습니다.

const img = new Image();
img.onload = function () {
  console.log(img.width + 'x' + img.height);
  // 200x300
};
img.src = 'https://picsum.photos/200/300';

그러나 Node나 Bun과 같이 브라우저 런타임이 아닌 경우, 이 Image 엘리먼트를 사용할 수 없습니다. 이 상황에서 이미지의 크기를 어떻게 구할 수 있을까요?

image-size

오픈소스 라이브러리 image-size를 사용하면 문제를 쉽게 해결할 수 있습니다.
바쁘신 분은 바로 최종코드를 참고해주세요!

yarn add image-size

image-size는 TypeScript로 구성된 라이브러리인데요.
현재 v1에서는 Node 런타임에서만 동작하지만, v2에서는 브라우저 런타임에서도 동작할 예정입니다. Github 참고.

라이브러리의 기본 동작 원리는 이미지의 바이너리 데이터에서 이미지 정보를 추출하는 것입니다.

파일 형식마다 바이너리 구조가 다르므로 이를 별도로 처리해야 하지만, image-size는 PNG, JPEG, GIF, WebP, HEIC 등 다양한 파일 포멧을 지원하고 있고 자동으로 파일 형식을 파싱하기에, 저희는 간편히 이미지의 크기를 구할 수 있습니다.

로컬 이미지

로컬 파일 시스템의 이미지는 파일 경로만 인자로 넘겨주면, 곧바로 이미지의 크기를 알 수 있습니다.

import sizeOf from 'image-size';
 
const dimension = sizeOf('public/og.png');
 
console.log(dimension);
// {
//   height: 720,
//   width: 1280,
//   type: "png",
// }

외부 이미지

외부 이미지의 경우, URL로부터 이미지의 바이너리 데이터를 가져와, 이를 통해 이미지 크기를 알 수 있습니다.

import sizeOf from 'image-size';
 
const res = await fetch('https://picsum.photos/200/300');
const buffer = new Uint8Array(await res.arrayBuffer());
const dimension = sizeOf(buffer);
 
console.log(dimension);
// {
//   height: 300,
//   orientation: 1,
//   width: 200,
//   type: "jpg",
// }

image-size 내부에선 Uint8Array(8비트 무부호 정수 배열)를 기준으로 데이터를 파싱하고 있어 이를 건네주면 됩니다.

Fetch Response의 arrayBuffer 메소드를 통해서 바이너리 데이터(ArrayBuffer)를 얻을 수 있고, Uint8Array의 생성자를 통해서 바이너리 데이터를 Uint8Array 객체로 변환할 수 있습니다.

참고로 Node의 BufferJavaScript의 Uint8Array를 상속 받는 객체로, Node 런타임에서 아래와 같이 작성해도 무방합니다.

// Buffer는 Node 런타임에서 전역변수 이지만, 쉽게 참조하기 위해 import하여 사용할 것을 권장합니다.
import { Buffer } from 'node:buffer';
 
const buffer = Buffer.from(await res.arrayBuffer());

Node의 Buffer에 대해 더 알고 싶다면, 여기 글을 참고해보면 좋을 것 같습니다.

최종 코드

라이브러리 코드를 보면, 로컬 이미지도 결국 바이너리 데이터로 변환하여 이미지 크기를 얻습니다.
따라서 내부 이미지든 외부 이미지든, 모두 바이너리 데이터로 변환하여 크기를 구하면 꽤나 깔끔한 코드를 작성할 수 있습니다.

import fs from 'node:fs/promises';
import sizeOf from 'image-size';
 
const getImageBuffer = async (src: string) => {
  const isExternalImage = src.startsWith('http');
 
  if (isExternalImage) {
    const res = await fetch(src);
    return new Uint8Array(await res.arrayBuffer());
  }
 
  const localSrc = `public/${src}`;
  const file = await fs.readFile(localSrc);
  return new Uint8Array(file);
};
 
export const getImageDimension = async (src: string) => {
  const buffer = await getImageBuffer(src);
  return sizeOf(buffer);
};

저는 블로그 빌드 시점에서 글 이미지 크기를 지정하여 글의 layout shift를 방지하고자 했는데요.
더 이상 브라우저에 종속되지 않고 이미지 크기를 구해보세요.