feat: [esign] 고객 도장 업로드 시 배경 자동 제거 기능 추가

- Canvas API로 흰색/밝은 배경 픽셀을 투명 처리
- 경계 부분 부드러운 페이드 처리 (anti-aliasing)
- 미리보기에 체크무늬 배경으로 투명 영역 시각화
- "배경이 자동 제거되었습니다" 안내 메시지 표시
This commit is contained in:
김보곤
2026-02-23 15:46:32 +09:00
parent 8cfab74e1f
commit a3d63a8a59

View File

@@ -83,6 +83,35 @@
if (padRef.current) padRef.current.clear();
};
const removeBackground = (img) => {
const canvas = document.createElement('canvas');
canvas.width = img.naturalWidth;
canvas.height = img.naturalHeight;
const ctx = canvas.getContext('2d');
ctx.drawImage(img, 0, 0);
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
const data = imageData.data;
const threshold = 210;
for (let i = 0; i < data.length; i += 4) {
const r = data[i], g = data[i + 1], b = data[i + 2];
const brightness = r * 0.299 + g * 0.587 + b * 0.114;
if (brightness > threshold) {
data[i + 3] = 0;
} else {
// 반투명 경계를 부드럽게 처리
const fade = Math.max(0, (brightness - (threshold - 40)) / 40);
if (fade > 0) {
data[i + 3] = Math.round(data[i + 3] * (1 - fade));
}
}
}
ctx.putImageData(imageData, 0, 0);
return canvas.toDataURL('image/png');
};
const handleStampUpload = (e) => {
const file = e.target.files?.[0];
if (!file) return;
@@ -98,8 +127,13 @@
}
const reader = new FileReader();
reader.onload = (ev) => {
setStampPreview(ev.target.result);
setStampData(ev.target.result.replace(/^data:image\/\w+;base64,/, ''));
const img = new Image();
img.onload = () => {
const transparentDataUrl = removeBackground(img);
setStampPreview(transparentDataUrl);
setStampData(transparentDataUrl.replace('data:image/png;base64,', ''));
};
img.src = ev.target.result;
};
reader.readAsDataURL(file);
};
@@ -275,7 +309,8 @@ className="px-4 py-2 bg-gray-100 text-gray-700 rounded-lg hover:bg-gray-200 text
</div>
) : (
<div className="text-center">
<div className="bg-gray-50 rounded-lg p-3 mb-3 flex items-center justify-center" style={{height: '150px'}}>
<p className="text-xs text-green-600 mb-2">배경이 자동 제거되었습니다</p>
<div className="rounded-lg p-3 mb-3 flex items-center justify-center" style={{height: '150px', background: 'repeating-conic-gradient(#e5e7eb 0% 25%, #fff 0% 50%) 0 0 / 16px 16px'}}>
<img src={stampPreview} alt="도장 미리보기" className="max-h-full max-w-full object-contain" />
</div>
<button type="button" onClick={() => { setStampPreview(null); setStampData(null); if (stampInputRef.current) stampInputRef.current.value = ''; }}