Programing/javascript

JavaScript canvas로 이미지 리사이즈

2025. 6. 26. 21:58
반응형

클라이언트에서 최적화, 서버에서 효율적인 저장

이미지 업로드는 대부분의 웹 서비스에서 빈번하게 발생하는 작업입니다. 특히 모바일 기기와 고해상도 카메라의 보급으로 인해, 사용자들이 업로드하는 이미지의 해상도와 파일 용량은 지속적으로 증가하고 있습니다. 이러한 고용량 이미지들은 서버 부하를 가중시키고, 전송 속도를 저하시켜 사용자 경험을 악화시키는 원인이 될 수 있습니다.

이를 해결하기 위한 방법 중 하나는 클라이언트 측에서 이미지를 사전 리사이즈하는 것입니다. 사용자가 업로드하기 전에 브라우저에서 이미지의 크기와 용량을 줄이면, 다음과 같은 장점이 있습니다:

  • 서버 저장 공간 절약
  • 네트워크 전송 속도 향상
  • 업로드 대기 시간 단축
  • 사용자 불만 감소

JavaScript에서 이미지 리사이즈 처리

JavaScript에서는 FileReader와 Canvas API를 이용해 이미지를 로드하고, 가로·세로 크기를 제한하여 리사이즈할 수 있습니다.

function resizeImage(file, maxWidth, maxHeight, callback) {
  // FileReader 객체 생성: 파일을 읽어 base64로 변환
  const reader = new FileReader();

  // 파일 읽기 완료 시 호출되는 이벤트 핸들러 설정
  reader.onload = function (event) {
    // 이미지 객체 생성
    const img = new Image();

    // 이미지 로딩 완료 시 처리
    img.onload = function () {
      // 원본 이미지의 너비와 높이 저장
      let width = img.width;
      let height = img.height;

      // 최대 크기에 맞게 비율 계산 (가로 또는 세로 중 더 작은 비율을 기준으로 축소)
      const ratio = Math.min(maxWidth / width, maxHeight / height);

      // 비율을 적용하여 새로운 너비와 높이 계산
      width *= ratio;
      height *= ratio;

      // canvas 요소 생성 및 크기 설정
      const canvas = document.createElement('canvas');
      canvas.width = width;
      canvas.height = height;

      // canvas에 2D 컨텍스트 가져오기
      const ctx = canvas.getContext('2d');

      // canvas에 리사이즈된 이미지 그리기
      ctx.drawImage(img, 0, 0, width, height);

      // canvas의 내용을 JPEG Blob 형태로 추출하여 콜백 함수에 전달
      // 두 번째 인자는 출력 형식, 세 번째 인자는 품질 (0.8은 80% 품질)
      canvas.toBlob((blob) => callback(blob), 'image/jpeg', 0.8);
    };

    // img.src에 base64 데이터 설정 → 이미지 로딩 트리거
    img.src = event.target.result;
  };

  // FileReader로 파일을 base64(DataURL)로 읽기 시작
  reader.readAsDataURL(file);
}
  • canvas.toBlob()은 품질 조절 및 포맷 지정이 가능해 유용합니다.
  • 원본 비율을 유지하며 최대 크기 내로 축소됩니다.

리사이즈된 이미지 전송 방식

리사이즈된 이미지는 FormData를 통해 Blob 형태로 전송하거나, Base64로 인코딩하여 서버에 전달할 수 있습니다.

Blob 전송 예제

resizeImage(file, 800, 800, (blob) => {
  // FormData 객체 생성: multipart/form-data 형태로 서버에 전송하기 위해 사용
  const formData = new FormData();

  // Blob 객체를 'image'라는 필드명으로 추가 (파일명은 'resized.jpg'로 지정)
  formData.append('image', blob, 'resized.jpg');

  // fetch API를 사용하여 서버에 POST 요청 전송
  fetch('/upload.php', {
    method: 'POST',       // HTTP 메서드 설정
    body: formData        // 전송할 데이터로 FormData 객체 사용
  });

  // 주의: 성공/실패에 대한 처리는 생략되어 있음
});

Base64 전송 시 유의사항

  • Base64는 원본보다 약 30% 정도 용량이 증가합니다.
  • PHP에서는 헤더(data:image/jpeg;base64,...) 제거 후 디코딩 필요합니다.

PHP에서 리사이즈 이미지 저장 처리

Blob 업로드 처리 예제

// 업로드된 'image' 파일이 존재하고, 오류 없이 업로드되었는지 확인
if (isset($_FILES['image']) && $_FILES['image']['error'] === UPLOAD_ERR_OK) {

  // 임시 업로드 파일 경로 (서버가 임시로 저장한 위치)
  $tmpName = $_FILES['image']['tmp_name'];

  // 클라이언트가 업로드한 원래의 파일 이름 (디렉토리명 제거)
  $originalName = basename($_FILES['image']['name']);

  // 업로드 파일이 저장될 디렉토리 설정 (현재 파일 기준 'uploads' 폴더)
  $uploadDir = __DIR__ . '/uploads/';

  // 업로드 디렉토리가 존재하지 않으면 생성
  if (!is_dir($uploadDir)) mkdir($uploadDir);

  // 저장할 최종 경로 생성: 현재 시간 기반으로 파일명에 prefix 추가
  $targetPath = $uploadDir . time() . '_' . $originalName;

  // 임시 파일을 지정한 경로로 이동 (실제 파일 저장)
  move_uploaded_file($tmpName, $targetPath);
}

Base64 업로드 처리 예제

// 업로드된 파일이 존재하고 오류 없이 업로드되었는지 확인
if (isset($_FILES['image']) && $_FILES['image']['error'] === UPLOAD_ERR_OK) {

  // 임시 업로드 파일 경로 (서버가 임시로 저장한 파일 위치)
  $tmpName = $_FILES['image']['tmp_name'];

  // 클라이언트가 업로드한 원본 파일 이름에서 디렉토리 경로를 제거
  $originalName = basename($_FILES['image']['name']);

  // 업로드할 최종 디렉토리 경로 설정 (현재 파일 위치 기준 'uploads/' 폴더)
  $uploadDir = __DIR__ . '/uploads/';

  // 업로드 디렉토리가 없으면 생성
  if (!is_dir($uploadDir)) mkdir($uploadDir);

  // 저장할 최종 경로: 파일명 앞에 현재 시간 타임스탬프를 붙여 중복 방지
  $targetPath = $uploadDir . time() . '_' . $originalName;

  // 임시 파일을 최종 경로로 이동 (실제 업로드 처리)
  move_uploaded_file($tmpName, $targetPath);
}

보안 고려 사항

  • 확장자는 서버에서 검사하거나 강제로 지정합니다.
  • 업로드된 파일은 imagecreatefromstring() 등을 통해 유효성 검사를 수행해야 합니다.
$img = imagecreatefromstring(file_get_contents($targetPath));
if (!$img) unlink($targetPath); // 유효하지 않으면 삭제

클라이언트 리사이즈 vs 서버 리사이즈

방식 장점 단점
JavaScript 리사이즈 빠른 미리보기, 사용자 반응성 향상 브라우저 지원 제한, 품질 제어 어려움
PHP 리사이즈 서버 일관성 확보, 품질 제어 쉬움 서버 자원 소모 증가

권장 선택 기준:

  • 미리보기와 UX를 중시하면 JavaScript 리사이즈
  • 통일된 품질 관리가 필요하면 PHP 리사이즈

HTML + JavaScript + PHP 예제 정리

HTML

<!-- 미리보기 -->
<div class="pic_preview"><img src="noimage.png"></div>

<!-- 파일찾기 -->
<input type="file" class="file" accept='image/*'>

<!-- 서버에 전송할 이미지 데이터(base64) -->
<input type='hidden' name='pic' value=''/>

JavaScript: 리사이즈 및 미리보기 처리

// 파일 입력 필드 변경 이벤트 감지 (.file 클래스 대상)
$(document).on('change', '.file', function(e) {

    // 선택된 첫 번째 파일 객체 추출
    const file = e.target.files[0];
    if (!file) return; // 파일이 없을 경우 중단

    // FileReader를 사용하여 파일을 읽음
    const reader = new FileReader();

    // 파일 읽기 완료 시 실행되는 콜백
    reader.onload = function(event) {

        // 이미지 객체 생성
        const img = new Image();

        // 이미지가 로딩되었을 때 실행
        img.onload = function() {

            // 최대 너비 설정
            const maxWidth = 200;

            // 가로 기준으로 비율 계산 (세로 자동 조정)
            const scale = maxWidth / img.width;

            // canvas 요소 생성 및 크기 설정
            const canvas = document.createElement('canvas');
            canvas.width = maxWidth;
            canvas.height = img.height * scale;

            // canvas에 2D context 가져오기
            const ctx = canvas.getContext('2d');

            // canvas에 이미지 그리기 (리사이즈된 크기로)
            ctx.drawImage(img, 0, 0, canvas.width, canvas.height);

            // canvas를 JPEG 포맷 base64 데이터로 추출 (품질 90%)
            const dataURL = canvas.toDataURL('image/jpeg', 0.9);

            // 숨겨진 input[name="pic"]에 base64 데이터 저장
            $('input[name="pic"]').val(dataURL);

            // 미리보기 이미지 src 속성 변경
            $('.pic_preview img').attr('src', dataURL);
        };

        // 이미지 객체의 src에 base64 데이터를 설정 → 이미지 로딩 유도
        img.src = event.target.result;
    };

    // 파일을 base64 (data URL) 형식으로 읽기 시작
    reader.readAsDataURL(file);
});

PHP: 파일 저장 처리

$pic = $_POST['pic'];

$UPLOAD_DIR = $_SERVER['DOCUMENT_ROOT'] . '/data/board/';

if ($pic) {
    // base64 헤더 분석: 확장자 추출
    if (preg_match('/^data:image\/(\w+);base64,/', $pic, $matches)) {
        $ext = strtolower($matches[1]);  // jpg, jpeg, png, gif 등

        // 허용 확장자만 필터링
        $allowed = ['jpg', 'jpeg', 'png', 'gif'];
        if (!in_array($ext, $allowed)) {
            exit('허용되지 않는 이미지 형식입니다.');
        }

        // base64 데이터만 추출
        $data = substr($pic, strpos($pic, ',') + 1);
        $data = base64_decode($data);

        if ($data === false) {
            exit('base64 디코딩에 실패했습니다.');
        }

        // 저장 디렉토리가 없다면 생성
        if (!is_dir($UPLOAD_DIR)) {
            mkdir($UPLOAD_DIR, 0755, true);
        }

        // 고유 파일명 생성
        $filename = 'base64_' . time() . '_' . bin2hex(random_bytes(4)) . '.' . $ext;
        $filepath = $UPLOAD_DIR . $filename;

        // 파일 저장
        if (file_put_contents($filepath, $data) === false) {
            exit('파일 저장에 실패했습니다.');
        }

        // 상대 경로 또는 URL 경로로 결과 저장
        $pic = '/data/board/' . $filename;

        // 이후 사용 예:
        // echo json_encode(['status' => 'success', 'url' => $pic]);
    } else {
        exit('잘못된 이미지 데이터 형식입니다.');
    }
}

참고 링크

  • FileReader: https://developer.mozilla.org/en-US/docs/Web/API/FileReader
  • Canvas API: https://developer.mozilla.org/en-US/docs/Web/API/Canvas_API
  • imagecreatefromstring: https://www.php.net/manual/en/function.imagecreatefromstring.php

JavaScript와 PHP를 조합한 이 방식은 이미지 용량을 효과적으로 줄이고, 사용자 경험을 향상시키며, 서버 자원도 절약하는 실용적인 업로드 전략이 될 수 있습니다.

반응형

'Programing > javascript' 카테고리의 다른 글

JavaScript로 구현하는 게임 데미지 단위 축약 (A ~ ZZZZ)  (0) 2025.05.27
JavaScript로 구현하는 금액 단축 표기 (K / M / B 표기법)  (0) 2025.05.27
JavaScript로 구현하는 금액의 영어 단위 변환 (Number to Words)  (0) 2025.05.27
기수 정렬 (Radix Sort) 설명과 JavaScript 예제  (1) 2025.05.22
힙 정렬 (Heap Sort) 설명과 JavaScript 예제  (0) 2025.05.22
JavaScript 비교 연산자  (1) 2025.05.18
JavaScript 조건문  (0) 2025.05.18
JavaScript 반복문  (0) 2025.05.18
'Programing/javascript' 카테고리의 다른 글
  • JavaScript로 구현하는 게임 데미지 단위 축약 (A ~ ZZZZ)
  • JavaScript로 구현하는 금액 단축 표기 (K / M / B 표기법)
  • JavaScript로 구현하는 금액의 영어 단위 변환 (Number to Words)
  • 기수 정렬 (Radix Sort) 설명과 JavaScript 예제
Dongkkase
Dongkkase
개발자로 일하면서 부딪히는 문제풀이가 누군가에게 도움이 되길 바라며
    반응형
  • Dongkkase
    정집사의 개발로그
    Dongkkase
  • 전체
    오늘
    어제
    • All (478) N
      • 금융 (61)
      • Programing (295) N
        • Algorithm (39)
        • API (2)
        • javascript (122)
        • CSS (8)
        • HTML (10)
        • PHP (15)
        • JAVA (27)
        • JSP (17)
        • JSP 예제 (1)
        • IOS (1)
        • Android (1)
        • Sencha Touche (1)
        • bat file, cmd (0)
        • 디버깅 (2)
        • SQL (21) N
        • MS-SQL (1)
        • MySQL (13)
        • 보안 (5)
      • Server (14)
        • Docker (1)
        • Windows (9)
        • Linux (3)
        • jeus (1)
      • Database (6)
      • IT 일반 (15)
      • 리뷰 (38)
        • Book (17)
        • 제품 (2)
        • 영화 소개 (11)
        • 음악 소개 (7)
      • 잡생각 (36)
        • 회고 (3)
        • 컬럼 (4)
        • 자료실 (6)
        • 낙서장 (12)
        • 위시리스트 (2)
        • WOW (1)
        • 덕 (1)
  • 인기 글

  • 최근 댓글

  • 태그

    디자인패턴
    읽고 싶은 책
    jsp
    php
    JavaScript
    블로그
    It
    위시리스트
    IT블로그
    사고 싶은 책
    SQL
    기초
    iT's MY LiFE
    자바
    자바스크립트유틸
    IT·컴퓨터
    Java
    IT 관련
    자바스크립트
    js패턴
Dongkkase
JavaScript canvas로 이미지 리사이즈
상단으로

티스토리툴바