블로그 목록
앱Expo

expo-image-manipulator로 업로드 전 이미지 줄이기 — 5MB 사진을 서버에 보내기 전에 압축하기

사진 업로드 기능을 붙이고 나니 문제가 하나 보였다. expo-image-picker로 갤러리에서 고른 이미지를 그대로 서버로 보냈더니, 요즘 폰 카메라 원본이 한 장에 4~5MB씩 나온다. 느린 네트워크에서는 업로드가 하염없이 돌고, 스토리지 비용도 쌓인다. 정작 화면에 보여줄 땐 가로 1080px면 충분한데 4000px짜리를 통째로 올리고 있던 거다.

클라이언트에서 미리 줄여서 보내면 되는 일이었다. 이걸 해주는 게 expo-image-manipulator다.

새 API로 바뀌었다

예전엔 manipulateAsync(uri, [...], {...}) 한 방으로 썼는데, 최근 SDK부터는 컨텍스트 기반의 객체형 API로 바뀌었다. 기존 함수는 deprecated라 새로 짜는 코드는 이쪽을 쓰는 게 맞다.

npx expo install expo-image-manipulator
import { ImageManipulator, SaveFormat } from 'expo-image-manipulator';

async function shrink(uri: string) {
  const context = ImageManipulator.manipulate(uri);
  context.resize({ width: 1080 }); // 높이는 비율 맞춰 자동

  const image = await context.renderAsync();
  const result = await image.saveAsync({
    format: SaveFormat.JPEG,
    compress: 0.7, // 0~1, 낮을수록 더 압축
  });

  return result; // { uri, width, height }
}

흐름이 세 단계로 나뉜다. manipulate(uri)로 컨텍스트를 만들고, resize·rotate·crop 같은 조작을 체인으로 걸고, renderAsync() → saveAsync()로 실제 파일을 뽑는다. 조작을 여러 개 걸 때 매번 중간 파일을 안 만들고 한 번에 처리해서 메모리 측면에서도 낫다.

업로드 흐름에 끼워 넣기

image-picker에서 받은 uri를 바로 서버로 보내지 말고 shrink를 한 번 통과시킨다.

const picked = await ImagePicker.launchImageLibraryAsync({ quality: 1 });
if (picked.canceled) return;

const small = await shrink(picked.assets[0].uri);

const form = new FormData();
form.append('photo', {
  uri: small.uri,
  name: 'photo.jpg',
  type: 'image/jpeg',
} as any);
await fetch('/api/upload', { method: 'POST', body: form });

picker 단계에서 quality를 낮추는 방법도 있지만, 그건 압축만 되고 해상도(픽셀 수)는 안 줄어든다. 전송 용량을 확 줄이려면 결국 resize로 가로 픽셀을 깎는 게 효과가 크다. 실제로 4032px 원본을 1080px·compress 0.7로 돌렸더니 4.6MB가 280KB 정도로 떨어졌다.

한 가지 팁

PNG 스크린샷처럼 투명도가 필요한 이미지가 아니라면 SaveFormat.JPEG가 용량 면에서 거의 항상 유리하다. 반대로 로고·아이콘처럼 투명 배경이 중요하면 PNG로, 더 줄이고 싶으면 WEBP로 저장하면 된다.

업로드 전에 한 줄 끼워 넣는 것만으로 네트워크 시간과 서버 비용이 같이 줄어든다. 사진을 다루는 앱이라면 picker 바로 뒤에 붙여두길 추천한다.