Skip to content

예제

이 페이지는 기본 작업부터 고급 기술까지 Sharp의 다양한 사용 예제를 제공합니다.

기본 예제

이미지 크기 조정

javascript
import sharp from 'sharp';

// 고정 크기로 조정
await sharp('input.jpg')
  .resize(300, 200)
  .toFile('resized.jpg');

// 종횡비 유지
await sharp('input.jpg')
  .resize(300, null)
  .toFile('resized.jpg');

// 다른 적합 모드 사용
await sharp('input.jpg')
  .resize(300, 200, {
    fit: 'cover',        // 잘라서 맞추기
    position: 'center'   // 중앙 자르기
  })
  .toFile('cover.jpg');

형식 변환

javascript
// JPEG를 PNG로 변환
await sharp('input.jpg')
  .png()
  .toFile('output.png');

// PNG를 WebP로 변환
await sharp('input.png')
  .webp({ quality: 80 })
  .toFile('output.webp');

// AVIF로 변환
await sharp('input.jpg')
  .avif({ quality: 80 })
  .toFile('output.avif');

썸네일 생성

javascript
// 정사각형 썸네일 생성
await sharp('input.jpg')
  .resize(150, 150, { fit: 'cover' })
  .jpeg({ quality: 90 })
  .toFile('thumbnail.jpg');

// 다양한 크기의 썸네일 생성
const sizes = [150, 300, 600];
const promises = sizes.map(size => 
  sharp('input.jpg')
    .resize(size, size, { fit: 'cover' })
    .jpeg({ quality: 85 })
    .toFile(`thumbnail-${size}.jpg`)
);

await Promise.all(promises);

고급 예제

이미지 합성

javascript
// 이미지에 워터마크 추가
await sharp('input.jpg')
  .composite([{
    input: 'watermark.png',
    top: 10,
    left: 10
  }])
  .jpeg()
  .toFile('with-watermark.jpg');

// 이미지 그리드 생성
const grid = await sharp({
  create: {
    width: 600,
    height: 400,
    channels: 4,
    background: { r: 255, g: 255, b: 255, alpha: 1 }
  }
})
.composite([
  { input: 'image1.jpg', top: 0, left: 0 },
  { input: 'image2.jpg', top: 0, left: 300 },
  { input: 'image3.jpg', top: 200, left: 0 },
  { input: 'image4.jpg', top: 200, left: 300 }
])
.jpeg()
.toFile('grid.jpg');

필터 효과

javascript
// 여러 필터 적용
await sharp('input.jpg')
  .blur(3)           // 블러
  .sharpen()         // 선명화
  .modulate({        // 색상 조정
    brightness: 1.1,
    saturation: 0.8
  })
  .jpeg({ quality: 85 })
  .toFile('filtered.jpg');

// 빈티지 효과 생성
await sharp('input.jpg')
  .modulate({
    brightness: 0.9,
    saturation: 0.7,
    hue: 30
  })
  .tint({ r: 255, g: 200, b: 150 })
  .jpeg({ quality: 80 })
  .toFile('vintage.jpg');

일괄 처리

javascript
import fs from 'fs';
import path from 'path';

async function processDirectory(inputDir, outputDir) {
  const files = fs.readdirSync(inputDir);
  const imageFiles = files.filter(file => 
    /\.(jpg|jpeg|png|webp)$/i.test(file)
  );

  const promises = imageFiles.map(async file => {
    const inputPath = path.join(inputDir, file);
    const outputPath = path.join(outputDir, `processed-${file}`);

    await sharp(inputPath)
      .resize(800, 600, { fit: 'inside' })
      .jpeg({ quality: 80 })
      .toFile(outputPath);

    console.log(`처리 완료: ${file}`);
  });

  await Promise.all(promises);
  console.log('모든 파일 처리 완료!');
}

processDirectory('./input', './output');

반응형 이미지

javascript
// 반응형 이미지 생성
const sizes = [
  { width: 320, suffix: 'sm' },
  { width: 640, suffix: 'md' },
  { width: 1024, suffix: 'lg' },
  { width: 1920, suffix: 'xl' }
];

const formats = ['jpeg', 'webp', 'avif'];

async function generateResponsiveImages(inputFile) {
  const promises = [];

  for (const size of sizes) {
    for (const format of formats) {
      const outputFile = `output-${size.suffix}.${format}`;
      
      let pipeline = sharp(inputFile)
        .resize(size.width, null, { fit: 'inside' });

      switch (format) {
        case 'jpeg':
          pipeline = pipeline.jpeg({ quality: 80 });
          break;
        case 'webp':
          pipeline = pipeline.webp({ quality: 80 });
          break;
        case 'avif':
          pipeline = pipeline.avif({ quality: 80 });
          break;
      }

      promises.push(pipeline.toFile(outputFile));
    }
  }

  await Promise.all(promises);
  console.log('반응형 이미지 생성 완료!');
}

generateResponsiveImages('input.jpg');

성능 최적화 예제

스트림 처리

javascript
import fs from 'fs';

// 대용량 파일 처리
const pipeline = sharp()
  .resize(800, 600)
  .jpeg({ quality: 80 });

fs.createReadStream('large-input.jpg')
  .pipe(pipeline)
  .pipe(fs.createWriteStream('output.jpg'));

// 여러 파일 처리
const processFile = (inputFile, outputFile) => {
  return new Promise((resolve, reject) => {
    sharp(inputFile)
      .resize(300, 200)
      .jpeg({ quality: 80 })
      .pipe(fs.createWriteStream(outputFile))
      .on('finish', resolve)
      .on('error', reject);
  });
};

const files = ['file1.jpg', 'file2.jpg', 'file3.jpg'];
const promises = files.map((file, index) => 
  processFile(file, `output-${index}.jpg`)
);

await Promise.all(promises);

메모리 최적화

javascript
// 작은 파일에 Buffer 사용
const buffer = await sharp('input.jpg')
  .resize(300, 200)
  .jpeg({ quality: 80 })
  .toBuffer();

// 대용량 파일에 스트림 사용
const stream = sharp('large-input.jpg')
  .resize(800, 600)
  .jpeg({ quality: 80 });

// 청크 단위 처리
const chunkSize = 1024 * 1024; // 1MB
const chunks = [];

stream.on('data', chunk => {
  chunks.push(chunk);
});

stream.on('end', () => {
  const buffer = Buffer.concat(chunks);
  fs.writeFileSync('output.jpg', buffer);
});

동시성 제어

javascript
// 동시성 수 제한
async function processWithConcurrency(files, concurrency = 3) {
  const results = [];
  
  for (let i = 0; i < files.length; i += concurrency) {
    const batch = files.slice(i, i + concurrency);
    const batchPromises = batch.map(file => 
      sharp(file)
        .resize(300, 200)
        .jpeg({ quality: 80 })
        .toFile(`processed-${file}`)
    );
    
    const batchResults = await Promise.all(batchPromises);
    results.push(...batchResults);
  }
  
  return results;
}

const files = ['file1.jpg', 'file2.jpg', 'file3.jpg', 'file4.jpg'];
await processWithConcurrency(files, 2);

유틸리티 예제

이미지 정보 추출

javascript
async function getImageInfo(file) {
  const metadata = await sharp(file).metadata();
  const stats = await sharp(file).stats();
  
  return {
    filename: file,
    format: metadata.format,
    width: metadata.width,
    height: metadata.height,
    size: metadata.size,
    channels: metadata.channels,
    isOpaque: stats.isOpaque,
    dominantColor: stats.dominant
  };
}

const info = await getImageInfo('input.jpg');
console.log('이미지 정보:', info);

이미지 비교

javascript
async function compareImages(file1, file2) {
  const [stats1, stats2] = await Promise.all([
    sharp(file1).stats(),
    sharp(file2).stats()
  ]);
  
  return {
    file1: stats1,
    file2: stats2,
    dominantDiff: {
      r: Math.abs(stats1.dominant.r - stats2.dominant.r),
      g: Math.abs(stats1.dominant.g - stats2.dominant.g),
      b: Math.abs(stats1.dominant.b - stats2.dominant.b)
    }
  };
}

const comparison = await compareImages('image1.jpg', 'image2.jpg');
console.log('이미지 비교 결과:', comparison);

이미지 검증

javascript
async function validateImage(file) {
  try {
    const metadata = await sharp(file).metadata();
    
    const validation = {
      isValid: true,
      format: metadata.format,
      width: metadata.width,
      height: metadata.height,
      size: metadata.size,
      errors: []
    };
    
    // 크기 확인
    if (metadata.width > 5000 || metadata.height > 5000) {
      validation.errors.push('이미지 크기가 너무 큼');
    }
    
    // 파일 크기 확인
    if (metadata.size > 10 * 1024 * 1024) { // 10MB
      validation.errors.push('파일 크기가 너무 큼');
    }
    
    // 형식 확인
    const allowedFormats = ['jpeg', 'png', 'webp'];
    if (!allowedFormats.includes(metadata.format)) {
      validation.errors.push('지원되지 않는 형식');
    }
    
    if (validation.errors.length > 0) {
      validation.isValid = false;
    }
    
    return validation;
  } catch (error) {
    return {
      isValid: false,
      errors: [error.message]
    };
  }
}

const validation = await validateImage('input.jpg');
console.log('검증 결과:', validation);

전체 애플리케이션 예제

javascript
import sharp from 'sharp';
import fs from 'fs';
import path from 'path';

class ImageProcessor {
  constructor(options = {}) {
    this.options = {
      quality: 80,
      maxWidth: 1920,
      maxHeight: 1080,
      ...options
    };
  }

  async processImage(inputPath, outputPath, options = {}) {
    try {
      const metadata = await sharp(inputPath).metadata();
      
      // 크기 조정 계산
      const { width, height } = this.calculateDimensions(
        metadata.width,
        metadata.height,
        options
      );
      
      // 이미지 처리
      await sharp(inputPath)
        .resize(width, height, { fit: 'inside' })
        .jpeg({ quality: this.options.quality })
        .toFile(outputPath);
      
      return {
        success: true,
        originalSize: { width: metadata.width, height: metadata.height },
        newSize: { width, height },
        outputPath
      };
    } catch (error) {
      return {
        success: false,
        error: error.message
      };
    }
  }

  calculateDimensions(originalWidth, originalHeight, options = {}) {
    const { maxWidth = this.options.maxWidth, maxHeight = this.options.maxHeight } = options;
    
    let width = originalWidth;
    let height = originalHeight;
    
    if (width > maxWidth) {
      height = (height * maxWidth) / width;
      width = maxWidth;
    }
    
    if (height > maxHeight) {
      width = (width * maxHeight) / height;
      height = maxHeight;
    }
    
    return { width: Math.round(width), height: Math.round(height) };
  }

  async batchProcess(inputDir, outputDir) {
    if (!fs.existsSync(outputDir)) {
      fs.mkdirSync(outputDir, { recursive: true });
    }

    const files = fs.readdirSync(inputDir);
    const imageFiles = files.filter(file => 
      /\.(jpg|jpeg|png|webp)$/i.test(file)
    );

    const results = [];
    
    for (const file of imageFiles) {
      const inputPath = path.join(inputDir, file);
      const outputPath = path.join(outputDir, `processed-${file}`);
      
      const result = await this.processImage(inputPath, outputPath);
      results.push({ file, ...result });
    }
    
    return results;
  }
}

// 사용 예제
const processor = new ImageProcessor({ quality: 85 });

// 단일 파일 처리
const result = await processor.processImage('input.jpg', 'output.jpg');

// 일괄 처리
const results = await processor.batchProcess('./input', './output');

console.log('처리 결과:', results);

다음 단계

Apache 2.0 라이선스에 따라 릴리스되었습니다.