[javascript] Blob 과 Canvas를 이용해 이미지 Crop 하기

Minseok Kim·2023년 1월 8일
1

BLOB이란?

  • Binary Large OBject
  • 이미지, 미디어, html 등의 객체를 바이너리 형태로 저장할 때 사용되는 데이터 타입
  • 짐 스타키라는 데이터 베이스 설계자가 1970년대에 처음 발명
    • BLOB이라는 이름의 유래는 스티브 매퀸 주연의 1958년 작, The Blob 영화에 나오는 괴물의 이름이라고...

BLOB 생성

const newBlob = new Blob(array, options);

new Blob([new ArrayBuffer(8)], { type: 'video/mp4' });
new Blob(new Uint8Array(buffer), { type: 'image/png' });  
new Blob(['<div>Hello Blob!</div>'], {
  type: 'text/html',
  endings: 'native'
});
  • array
    • ArrayBuffer, TypedArray, Blob, File, DOMString 등 객체들이 저장될 수 있는 데이터 타입
      • ArrayBuffer: 고정된 길이의 연속된 메모리 공간 (레퍼런스 타입)
      • TypedArray: ArrayBuffer를 조작할 수 있는 데이터 타입
        • Uint8Array, Uint16Array, Uint32Array, Float64Array, …
      • File: name, lastModifiedDate만 추가된 Blob
  • options
    • type: 데이터의 MIME 타입을 설정
      • MIME 타입: 전송 데이터의 타입으로, 보통 데이터 응답자가 리소스를 내려받았을 때에 어떤 동작을 기본적으로 해야 하는지를 결정하도록 만들어주는 지표
        • text/plain, text/html, image/jpeg, audio/*
    • endings: 데이터가 텍스트일때 개행 문자 '\n'을 어떻게 해석할지를 지정
      • transparent: 개행 문자를 바꾸지 않고 블록 데이터로 복사
      • native: 사용 중인 OS에 맞춰서 줄 바꿈 문자 인식하여 데이터를 삽입

BLOB 메소드

  • slice: Blob을 작은 조각으로 분리시키기 위한 메소드
    blob.slice(start, end, type);
    
    // 분리
    const chunks = [];
    const numberOfSlices = 10;
    const chunkSize = Math.ceil(blob.size / numberOfSlices);
    for (let i = 0; i < numberOfSlices; i += 1) {
      const startByte = chunkSize * i;
      chunks.push(
        blob.slice(
          startByte,
          startByte + chunkSize,
          blob.type
        )
      );
    }
    
    // 합치기
    const mergedBlob = new Blob(
      chunks,
      { type: blob.type }
    );
  • createObjectURL: blob객체를 가리키는 URL
    • DOM, CSS에서 활용 가능 = 이미지 표시 가능
  • revokeObjectURL: createObjectURL()을 통해 생성한 URL을 폐기
    • 메모리 누수 방지를 위해 꼭 필요

Canvas란?

  • html5에서 새롭게 등장한 그래픽을 그리기 위한 수단을 제공 (특히 2d)

Canvas 메소드

  • getContext: 실제로 그림을 그릴 수 있는 context를 갖고오는 메소드
    var ctx = canvas.getContext(contextType, contextAttributes);
    • contextType: 드로잉 컨텍스트를 정의하는 컨텍스트 식별자
      • 2d, webgl, webgl2, bitmaprenderer
    • contextAttributes:
  • drawImage
    drawImage(image, sx, sy, sWidth, sHeight, dx, dy, dWidth, dHeight)
    • image: 이미지를 그리는데 활용할 원본 객체
    • sx: 이미지 내 x 좌표
    • sy: 이미지 내 y좌표
    • sWidth: 이미지 가로 길이
    • sHeight: 이미지 세로 길이
    • dx: 캔버스의 x좌표
    • dy: 캔버스의 y좌표
    • dWidth: 캔버스 위에 그려질 이미지의 가로 길이
    • dHeight: 캔버스 위에 그려질 이미지의 세로 길이
  • toDataURL: 캔버스에 그려진 이미지를 base64 문자열로 변환하는 메소드

Base64란?

  • 바이너리 데이터를 문자 코드에 영향을 받지 않는 공통 ASCII 문자로 표현하기 위해 만들어진 인코딩
  • 8비트 짜리를 6비트 짜리로 쪼개서 아래 문자열표 기반으로 표현함
  • 바이너리 데이터와 base64간 변환은 atob (ascii to binary), btoa (binary to ascii) 를 통해서 가능

이미지 정방형 Crop 하는 예제 코드

// 파일 업로드
<input
  name={'image'}
  type="file"
  onChange={cropAndUploadFile}
/>

// 파일 처리 및 이미지 로드
const cropAndUploadFile = async (e, target) => {
	// 파일 로드
  const file = e.target.files[0];

	// 이미지로 로드
  const image = document.createElement('img');
  image.src = URL.createObjectURL(file);
  image.onload = async () => {
    const { width, height } = image;
    const files = await cropImage(image, width, height, width);
  };
};

// 이미지 정방형으로 크롭
const cropImage = async (image, imageWidth, imageHeight, cropSize) => {
  const files = [];
  const cropX = 0;
  const cropWidth = cropSize;
  let cropY = 0;
  let cropHeight = cropSize;

  const count = Math.ceil(imageHeight / imageWidth);
  for (let i = 0; i < count; i++) {
		// 캔버스의 크기 조정
    const canvas = document.createElement('canvas');
    canvas.width = cropWidth;
    cropHeight = Math.min(cropHeight, imageHeight - cropY);
    canvas.height = cropHeight;

		// 캔버스의 컨텍스트를 2d로 지정
    const ctx = canvas.getContext('2d');
		// 캔버스 내 이미지 그리기
    ctx.drawImage(
      image,
      cropX,
      cropY,
      cropWidth,
      cropHeight,
      0,
      0,
      cropWidth,
      cropHeight,
    );
		// 이미지를 base64 기반 data uri로 변경
    const url = await new Promise((resolve) => {
      resolve(canvas.toDataURL('image/jpeg'))
    });
		// data uri를 blob화
    const file = dataURItoBlob(url);
    file.name = `${i}.jpg`;
    files.push(file);
    cropY += cropHeight;
  }
  return files;
};

// data uri를 blob화
dataURItoBlob = (dataURI) => {
  const binary = atob(dataURI.split(',')[1]);
  const array = [];
  for (let i = 0; i < binary.length; i++) {
    array.push(binary.charCodeAt(i));
  }
  return new Blob([new Uint8Array(array)], { type: 'image/jpeg' });
};
profile
Developer @CHLNGERS

0개의 댓글