이미지 압축기 - PNG, JPG, WebP 압축 온라인
화질 유지하며 이미지 압축. PNG, JPG, WebP 파일 크기를 전후 비교와 함께 줄이기 — 업로드 없음, 100% 브라우저 처리.
자주 묻는 질문
이미지 압축이란 무엇이고 왜 필요한가요?
이미지 압축은 불필요한 데이터를 제거하거나 이미지를 더 효율적으로 인코딩하는 수학적 알고리즘을 적용하여 이미지 파일 크기를 줄이는 것입니다. 웹 개발에서 압축된 이미지는 더 빠른 페이지 로드 시간, 낮은 대역폭 소비, 더 나은 Core Web Vitals 점수, 향상된 SEO 순위를 제공합니다. 일반적인 5MB DSLR 사진은 시각적 품질 손실을 최소화하면서 500KB 이하로 압축할 수 있습니다.
이 이미지 압축 도구는 어떻게 사용하나요?
1. 이미지를 업로드 영역에 드래그 앤 드롭하세요(또는 클릭하여 파일 찾아보기). 2. 품질 슬라이더를 조정하세요(기본값 80%가 좋은 균형점입니다). 3. 선택적으로 최대 너비/높이를 설정하여 이미지 크기를 조정하세요. 4. 개별 이미지에서 "압축"을 클릭하거나 "전체 압축"으로 일괄 처리하세요. 5. 비교 탭에서 전후 비교를 미리 보세요. 6. 개별 이미지를 다운로드하거나 "전체 다운로드(ZIP)"로 ZIP 아카이브를 받으세요.
이미지 데이터는 안전한가요? 이미지가 서버로 업로드되나요?
이미지는 100% 안전하며 브라우저를 떠나지 않습니다. 모든 압축은 HTML5 Canvas API와 OffscreenCanvas를 사용하여 클라이언트 측에서 수행됩니다. 어떤 이미지 데이터도 서버로 전송되지 않아 완전히 비공개입니다. 페이지가 로드된 후에는 오프라인에서도 사용할 수 있습니다. 이것이 TinyPNG과 같은 서버 기반 도구와의 차이점입니다.
손실 압축과 무손실 압축의 차이점은 무엇인가요?
손실 압축(JPEG, WebP)은 더 작은 파일 크기를 위해 일부 이미지 데이터를 영구적으로 제거합니다. 70-90% 품질 설정에서 시각적 차이는 일반적으로 감지할 수 없습니다. 무손실 압축(PNG)은 모든 이미지 데이터를 정확히 보존하여 파일은 더 크지만 완벽한 품질을 제공합니다. JPEG와 WebP는 기본적으로 손실 압축을 사용하며, PNG는 Canvas API 사용 시 항상 무손실입니다.
JPEG, PNG, WebP 중 어떤 이미지 형식을 사용해야 하나요?
JPEG는 많은 색상과 그라데이션이 있는 사진 및 복잡한 이미지에 적합합니다(투명도 미지원). PNG는 그래픽, 아이콘, 스크린샷, 텍스트나 날카로운 가장자리가 있는 이미지에 적합합니다(투명도 지원, 파일 크기 큼). WebP는 동일한 품질에서 JPEG보다 일반적으로 25-35% 더 작은 최신 형식으로, 손실 및 무손실 압축과 투명도를 지원하며, 95% 이상의 글로벌 브라우저 지원으로 웹 사용에 권장됩니다.
압축 후 PNG 파일이 왜 더 커졌나요?
Canvas API는 무손실 PNG 출력을 생성하는데, 이는 최적화된 PNG 인코더(OptiPNG 또는 pngquant 등)와 다를 수 있습니다. 원본 PNG가 이미 잘 최적화되어 있었다면 Canvas를 통한 재인코딩이 약간 더 큰 파일을 생성할 수 있습니다. PNG 크기를 줄이려면 이미지를 더 작은 크기로 리사이즈해 보세요.
지원되는 최대 파일 크기와 해상도는 얼마인가요?
이 도구는 파일당 최대 50MB, 최대 16384 x 16384 픽셀 해상도(대부분의 브라우저에서 Canvas API 제한)를 지원합니다. 매우 고해상도 이미지의 경우, 리사이즈 옵션을 사용하여 먼저 크기를 줄이는 것이 좋으며 이는 압축 속도도 높여줍니다. 모바일 기기에서는 메모리 제약으로 인해 최대 해상도가 더 낮을 수 있습니다.
여러 이미지를 한 번에 압축할 수 있나요?
네, 일괄 처리로 최대 20개 이미지를 동시에 처리할 수 있습니다. 모든 이미지에 대해 글로벌 품질 수준을 설정할 수 있습니다. 압축 후 "전체 다운로드(ZIP)"를 사용하여 모든 압축된 이미지를 하나의 ZIP 파일로 다운로드할 수 있습니다.
코드 예제
// Client-side image compression using Canvas API
async function compressImage(file, quality = 0.8) {
return new Promise((resolve, reject) => {
const img = new Image();
const url = URL.createObjectURL(file);
img.onload = () => {
URL.revokeObjectURL(url);
const canvas = document.createElement('canvas');
canvas.width = img.width;
canvas.height = img.height;
const ctx = canvas.getContext('2d');
ctx.drawImage(img, 0, 0);
// Determine output type (use WebP if supported, fallback to JPEG)
const outputType = file.type === 'image/png' ? 'image/png' : 'image/webp';
canvas.toBlob(
(blob) => {
if (!blob) {
reject(new Error('Compression failed'));
return;
}
resolve({
blob,
originalSize: file.size,
compressedSize: blob.size,
reductionPercent: Math.round((1 - blob.size / file.size) * 100),
});
},
outputType,
outputType === 'image/png' ? undefined : quality
);
};
img.onerror = () => {
URL.revokeObjectURL(url);
reject(new Error('Failed to load image'));
};
img.src = url;
});
}
// Compress with resize
async function compressAndResize(file, maxWidth, maxHeight, quality = 0.8) {
return new Promise((resolve, reject) => {
const img = new Image();
const url = URL.createObjectURL(file);
img.onload = () => {
URL.revokeObjectURL(url);
let { width, height } = img;
// Calculate new dimensions maintaining aspect ratio
if (maxWidth && width > maxWidth) {
height = Math.round((height * maxWidth) / width);
width = maxWidth;
}
if (maxHeight && height > maxHeight) {
width = Math.round((width * maxHeight) / height);
height = maxHeight;
}
const canvas = document.createElement('canvas');
canvas.width = width;
canvas.height = height;
const ctx = canvas.getContext('2d');
ctx.drawImage(img, 0, 0, width, height);
canvas.toBlob(
(blob) => {
if (!blob) {
reject(new Error('Compression failed'));
return;
}
resolve({
blob,
originalSize: file.size,
compressedSize: blob.size,
width,
height,
reductionPercent: Math.round((1 - blob.size / file.size) * 100),
});
},
'image/jpeg',
quality
);
};
img.onerror = () => {
URL.revokeObjectURL(url);
reject(new Error('Failed to load image'));
};
img.src = url;
});
}
// Batch compression
async function compressBatch(files, quality = 0.8) {
const results = [];
for (const file of files) {
try {
const result = await compressImage(file, quality);
results.push({ filename: file.name, ...result });
} catch (error) {
results.push({ filename: file.name, error: error.message });
}
}
return results;
}
// Usage
const input = document.querySelector('input[type="file"]');
input.addEventListener('change', async (e) => {
const files = Array.from(e.target.files);
const results = await compressBatch(files, 0.8);
results.forEach((result) => {
if (result.error) {
console.error(`${result.filename}: ${result.error}`);
} else {
console.log(
`${result.filename}: ${result.originalSize} -> ${result.compressedSize} (${result.reductionPercent}% reduction)`
);
}
});
});