Skip to content

Latest commit

 

History

History
439 lines (325 loc) · 12.8 KB

File metadata and controls

439 lines (325 loc) · 12.8 KB

quickpix

npm version npm bundle size zero dependencies license

English

고성능 브라우저/Node.js 이미지 리사이즈 라이브러리. Rust/WASM SIMD 가속 + 순수 JS fallback. 외부 의존성 없음.

npm install quickpix

빠른 시작 — 고수준 API (QuickPixEasy)

대부분의 사용 사례에서는 QuickPixEasy만으로 충분합니다. Blob/File 입력 → 리사이즈 → Blob 출력을 한 번의 호출로 처리합니다.

import { QuickPixEasy } from "quickpix";

const qp = new QuickPixEasy({
  filter: "lanczos",           // 리사이즈 필터 (기본: bilinear)
  outputMimeType: "image/jpeg",
  outputQuality: 0.85,
  preserveMetadata: true,      // EXIF/ICC/IPTC 메타데이터 보존
  autoRotate: true,            // EXIF Orientation 자동 보정 (기본: true)
});

Blob/File 리사이즈

const input = document.querySelector('input[type="file"]');
const file = input.files[0];

// 정확한 크기 지정
const resized = await qp.resizeBlob(file, 1200, 800);

// 너비만 지정 — 높이 자동 계산 (종횡비 유지)
const resized2 = await qp.resizeBlob(file, 1200, null);

// 높이만 지정 — 너비 자동 계산
const resized3 = await qp.resizeBlob(file, null, 800);

// 최대 크기 — 긴 쪽 기준 축소
const resized4 = await qp.resizeBlob(file, null, null, { maxDimension: 4096 });

// resizeFile은 resizeBlob의 별칭
const resized5 = await qp.resizeFile(file, 1200, null);

썸네일 생성

종횡비를 자동으로 보존합니다. 긴 쪽이 maxDimension 이하가 됩니다.

// 6000x4000 이미지 → 200x133 썸네일
const thumbnail = await qp.createThumbnail(file, 200);

// Canvas, ImageData, HTMLImageElement도 입력 가능
const thumb2 = await qp.createThumbnail(canvasElement, 150);

Canvas에 직접 출력

const canvas = document.getElementById("preview");
canvas.width = 800;
canvas.height = 600;

await qp.resizeToCanvas(file, canvas, { filter: "lanczos" });
// canvas에 리사이즈된 이미지가 그려짐

배치 병렬 처리

여러 이미지를 Web Worker 풀에서 동시에 처리합니다. 각 이미지가 별도 워커에서 decode→resize→encode 전체 파이프라인을 수행합니다.

const results = await qp.batchResize([
  { source: photo1, maxDimension: 800 },
  { source: photo2, width: 600, height: 400 },
  { source: photo3, maxDimension: 200 },
]);
// results = [Blob, Blob, Blob]

메타데이터 보존

기본적으로 Canvas API를 거치면 EXIF, ICC 프로필 등 메타데이터가 모두 제거됩니다. preserveMetadata: true로 설정하면 원본 JPEG의 메타데이터를 결과에 재삽입합니다.

// 메타데이터 보존 (EXIF 촬영일, GPS, 카메라 정보, ICC 색상 프로필 등)
const withMeta = await qp.resizeBlob(photo, 1200, 800, {
  preserveMetadata: true,
  outputMimeType: "image/jpeg",
});

// 메타데이터 제거 (기본값 — 개인정보 보호에 유리)
const stripped = await qp.resizeBlob(photo, 1200, 800, {
  preserveMetadata: false,
});

EXIF Orientation 자동 보정

스마트폰 사진은 EXIF에 회전 정보를 저장합니다. autoRotate: true(기본값)이면 자동으로 올바른 방향으로 보정합니다.

// autoRotate: true (기본값) — 세로 촬영 사진이 올바르게 회전됨
const rotated = await qp.resizeBlob(phonePhoto, 800, 600);

// autoRotate: false — 원본 픽셀 방향 그대로 (회전 안 함)
const raw = await qp.resizeBlob(phonePhoto, 800, 600, { autoRotate: false });

Fit 모드

// contain (기본): 800x600 안에 들어가도록 축소, 종횡비 보존
const a = await qp.resizeBlob(photo, 800, 600, { fit: "contain" });

// cover: 800x600을 완전히 덮도록 확대, 종횡비 보존 (잘릴 수 있음)
const b = await qp.resizeBlob(photo, 800, 600, { fit: "cover" });

// fill: 정확히 800x600으로 변환, 종횡비 무시
const c = await qp.resizeBlob(photo, 800, 600, { fit: "fill" });

리소스 정리

qp.destroy(); // 워커 풀 종료 및 리소스 해제

전체 옵션 정리

옵션 기본값 설명
filter "bilinear" 리사이즈 필터 (nearest, bilinear, box, hamming, lanczos)
maxWorkers navigator.hardwareConcurrency 워커 풀 최대 크기
idleTimeout 30000 유휴 워커 자동 종료 (ms)
outputMimeType "image/png" 출력 이미지 형식
outputQuality 0.92 JPEG/WebP 품질 (0~1)
useWasm true WASM 가속 사용 여부
preserveMetadata false EXIF/ICC/IPTC 메타데이터 보존
autoRotate true EXIF Orientation 자동 보정
workerURL null 워커 URL(번들러 ?worker 경로 fallback)
requireWorker false true면 워커 실행 불가 시 메인 스레드 폴백 없이 예외를 던짐
wasmPath null WASM 모듈 경로 오버라이드

저수준 API (QuickPix)

RGBA 버퍼를 직접 다루거나, 세밀한 제어가 필요할 때 사용합니다.

import { QuickPix } from "quickpix";

const qp = new QuickPix({
  useWasm: true,
  filter: "lanczos",
});

const src = new Uint8ClampedArray(640 * 480 * 4); // RGBA 버퍼
const out = await qp.resizeBuffer(src, 640, 480, 320, 240, {
  filter: "lanczos",
});

console.log(out.width, out.height, out.data.length); // 320 240 307200

번들러별 워커 설정 (Next, Vite, Rollup, esbuild, webpack, Turbopack)

번들러가 워커 경로를 기본으로 처리하지 못하면 workerURL을 명시하세요.
메인 스레드 폴백을 막으려면 requireWorker: true를 사용합니다.

import { QuickPix, QuickPixEasy } from "quickpix";
import resizeWorker from "quickpix/resize-worker.js?url";
import pipelineWorker from "quickpix/pipeline-worker.js?url";

const qp = new QuickPix({
  workerURL: resizeWorker,
  requireWorker: true,
});

const qpe = new QuickPixEasy({
  workerURL: pipelineWorker,
  requireWorker: true,
});

Vite/webpack/Turbopack에서는 확장자 없는 경로도 가능합니다.

import resizeWorker from "quickpix/resize-worker?url";
import pipelineWorker from "quickpix/pipeline-worker?url";

Rollup는 ?url 사용 시 @rollup/plugin-url이 필요합니다.
플러그인을 못 쓰는 환경에서는 quickpix/resize-worker.js처럼 순수 경로로도 시도합니다.

import resizeWorker from "quickpix/resize-worker.js";
import pipelineWorker from "quickpix/pipeline-worker.js";

또는 워커 팩토리 함수를 넘겨도 됩니다.

import resizeWorkerURL from "quickpix/resize-worker.js?url";

const qp = new QuickPix({
  workerURL: [() => new Worker(resizeWorkerURL, { type: "module" })],
  requireWorker: true,
});

requireWorker: true는 image-blob-reduce처럼 워커가 안 뜨면 즉시 예외를 던져 메인 스레드로 처리하지 않습니다.

템플릿 모음: examples/bundlers

번들러별 안정 동작 조합

아래 예시는 requireWorker: true 기준이고, 실제 프로젝트 환경에서 하나씩 선택해 적용하면 됩니다.

1) Next.js (webpack / Turbopack)

import { QuickPix, QuickPixEasy } from "quickpix";
import resizeWorker from "quickpix/resize-worker.js?url";
import pipelineWorker from "quickpix/pipeline-worker.js?url";

const qp = new QuickPix({ workerURL: resizeWorker, requireWorker: true });
const qpe = new QuickPixEasy({ workerURL: pipelineWorker, requireWorker: true });

기본 URL 주입이 안 맞는 경우:

import { QuickPix } from "quickpix";

const qp = new QuickPix({
  workerURL: [() => import("quickpix/resize-worker.js?url").then((m) => new Worker(m.default, { type: "module" }))],
  requireWorker: true,
});

2) Vite

import { QuickPix, QuickPixEasy } from "quickpix";
import resizeWorker from "quickpix/resize-worker.js?worker";
import pipelineWorker from "quickpix/pipeline-worker.js?worker";

const qp = new QuickPix({ workerURL: resizeWorker, requireWorker: true });
const qpe = new QuickPixEasy({ workerURL: pipelineWorker, requireWorker: true });

?worker가 동작하지 않으면 ?url로 바꿔 사용합니다.

3) Rollup

// rollup.config.js
import url from "@rollup/plugin-url";

export default {
  plugins: [
    url({
      include: [
        /node_modules\/quickpix\/.*(resize-worker|pipeline-worker)\.js$/,
      ],
      limit: 0,
      emitFiles: true,
      fileName: "[name][extname]",
    }),
  ],
};
import { QuickPix, QuickPixEasy } from "quickpix";
import resizeWorker from "quickpix/resize-worker.js";
import pipelineWorker from "quickpix/pipeline-worker.js";

const qp = new QuickPix({ workerURL: resizeWorker, requireWorker: true });
const qpe = new QuickPixEasy({ workerURL: pipelineWorker, requireWorker: true });

4) esbuild

// esbuild CLI
// 작업 환경이 다르다면 worker 파일만 file-loader처럼 취급해서 URL로 배출되게 조정합니다.
esbuild src/index.js --bundle --platform=browser --outdir=dist --loader:.js=file
import { QuickPix, QuickPixEasy } from "quickpix";
import resizeWorker from "quickpix/resize-worker.js?url";
import pipelineWorker from "quickpix/pipeline-worker.js?url";

const qp = new QuickPix({ workerURL: resizeWorker, requireWorker: true });
const qpe = new QuickPixEasy({ workerURL: pipelineWorker, requireWorker: true });

5) webpack

// webpack 5
module.exports = {
  module: {
    rules: [
      {
        test: /quickpix\\/(.*)worker\\.js$/,
        type: "asset/resource",
      },
    ],
  },
};
import { QuickPix, QuickPixEasy } from "quickpix";
import resizeWorker from "quickpix/resize-worker.js?url";
import pipelineWorker from "quickpix/pipeline-worker.js?url";

const qp = new QuickPix({ workerURL: resizeWorker, requireWorker: true });
const qpe = new QuickPixEasy({ workerURL: pipelineWorker, requireWorker: true });

지원 필터

필터 설명 속도 품질
nearest 최근접 픽셀 선택 가장 빠름 낮음
bilinear 2x2 선형 보간 (기본값) 빠름 중간
box 박스 평균 보통 중간
hamming 해밍 윈도우 느림 높음
lanczos Lanczos3 sinc 기반 가장 느림 최고

Canvas/ImageData 리사이즈

await qp.resize(sourceCanvas, targetCanvas, { filter: "bilinear" });

통계 확인

const stats = qp.getStats();
// { calls: 10, wasmHits: 8, fallbackHits: 2, lastError: null }

메타데이터 모듈

EXIF/ICC/IPTC를 직접 다뤄야 할 때 독립 모듈로 사용할 수 있습니다.

import { readOrientation, extractSegments, injectSegments } from "quickpix/metadata";

const buffer = await file.arrayBuffer();

// EXIF Orientation 읽기 (1~8)
const orientation = readOrientation(buffer);

// 메타데이터 세그먼트 추출
const segments = extractSegments(buffer);
// { exif: Uint8Array | null, icc: Uint8Array[], iptc: Uint8Array | null }

// 리사이즈된 JPEG에 메타데이터 재삽입
const restored = await injectSegments(resizedJpegBlob, segments);

설치 및 빌드

npm install quickpix

# 개발 환경 (소스에서 빌드)
npm install
npm run build:wasm    # Rust → WASM 빌드 (wasm-pack 필요)
npm run test:js       # JS 테스트 실행
npm run test:rust     # Rust 테스트 실행

벤치마크

npm run bench           # 성능 벤치마크
npm run bench:compare   # pica 대비 비교
npm run bench:memory    # 메모리 프로파일링
npm run bench:native    # sharp(libvips) 대비 비교
npm run bench:quality   # 품질 비교

프로젝트 구조

crates/core/       Rust RGBA 리사이즈 코어 (SIMD)
crates/wasm/       wasm-bindgen 바인딩
js/src/
  index.js         QuickPix 저수준 엔진
  easy.js          QuickPixEasy 고수준 API
  fallback.js      순수 JS 리사이즈 (separable 2-pass)
  metadata.js      EXIF/ICC/IPTC 파서
  decode.js        Blob → RGBA 디코딩
  encode.js        RGBA → Blob 인코딩
  worker-pool.js   재사용 워커 풀
  pipeline-worker.js  전체 파이프라인 워커
bench/             벤치마크 스크립트

브라우저 호환성

기능 Chrome 69+ Firefox 105+ Safari 16.4+
파이프라인 워커 (최적) O O O
JS fallback 모든 브라우저 모든 브라우저 모든 브라우저

OffscreenCanvas 미지원 환경에서는 자동으로 메인 스레드 경로로 전환됩니다.

라이선스

MIT