[fabric.js] fabric js에서 canvas putImageData 적용 안됨 (custom filter 넣기)

Ell!·2022년 4월 25일
0

javascript

목록 보기
5/6

배경

fabric.js를 기반으로 구현한 nhn의 tui.image-editor를 사용해서 이미지 편집기를 만들던 중, 라이브러리에서 제공중인 필터가 마음에 들지 않아서 커스텀 필터를 몇 개 넣기로 했다.

위 라이브러리의 applyFilter를 보니 fabric.js의 applyFilter를 거의 그대로 사용중인 것 같았다. 단순히 filter 몇개만 위해서 fabric.js를 깔고 싶지는 않았기에 fabric 안에 있는 canvas를 직접 조작해서 image를 얻어보고 싶었다.

설명

const getImage = () => {
	const editorInstance = editorInstanceRef.current;
    const canvas = editorInstance._graphics._canvas; //fabric Canvas
  	const imageCTX = canvas.getContext('2d');
  
  	// applyfilter에는 imageData.data의 pixel을 직접 조작하였다. 
  	// imageCTX의 getImageData를 통해 얻을 수 있었다.
  	// 참고 : https://github.com/girliemac/filterous-2
  	applyFilter(imageCTX, 'gingham')
      .then(newPixels => {
        imageCTX.putImageData(newPixels, 0, 0); // imageData -> canvas
      })
  	  .then(() => canvas.toDataURL());
}

여기서 문제가 발생했는데, putImageData까지는 잘 되는 것을 console로 찍어 확인하였다. 문제는 이걸 toDataURL을 활용해서 image로 뽑아내려고 하면 원본 이미지가 나온다는 것이었다..

정말 답답할 노릇...

해결

몇 일을 찾아 헤매다가 단서가 되는 글을 하나 발견했다.

fabric js에 올라온 issue

fabric의 canvas가 다시 render되니 custom한 data가 보존이 안된다. image를 덧씌우듯이 넣어라. 아하.

그래서 그냥 새로운 canvas를 만들어서 거기서 뽑아냈다.

const getImage = () => {
	const editorInstance = editorInstanceRef.current;
    const canvas = editorInstance._graphics._canvas; //fabric Canvas
  	const imageCTX = canvas.getContext('2d');
  
    var newCanvas = document.createElement('canvas');
    newCanvas.width = imageCTX.canvas.width;
    newCanvas.height = imageCTX.canvas.height;
    var ctx = newCanvas.getContext('2d');
  
  	// applyfilter에는 imageData.data의 pixel을 직접 조작하였다. 
  	// imageCTX의 getImageData를 통해 얻을 수 있었다.
  	// 참고 : https://github.com/girliemac/filterous-2
  	applyFilter(imageCTX, 'gingham')
      .then(newPixels => {
        imageCTX.putImageData(newPixels, 0, 0); // imageData -> canvas
        ctx.putImageData(newPixels, 0, 0);
      })
  	  .then(() => newCanvas.toDataURL());
}

이러면 일단 원하던대로 이미지는 나온다. 문제는 성능인데.. fabric js에서 직접 image를 넣는 방법을 찾아봐야겠다.

해결 2

결국 fabric js를 설치해서 fabric js를 직접 조작해서 원하는 결과를 얻어 냈다. 거의 4시간 동안 열심히 구글링을 했는데, 별로 나오는 것도 없고 해서 무진장 애를 먹었다.

우선 base는 fabric js에서 공식 제공해주는 demo였다. custom filter demo 여기에 보면 boilerplate가 나와있는데, 문제는 module에서는 제대로 쓸 수 없었다는 것이다. (아니 이게 안되면 어떠케;)

저기 나와있는 그대로 코드를 따와서 별도의 js 파일에서 복붙을 했는데도, 제대로 작동하지 않았다. 만약 fabric.Image.filters.MYFilter is not a constructor 라는 에러가 나왔으면 잘 찾아오셨다. 😁

결국 문제는 별도의 module에서 정의한 이 class가 인식이 안 된다는 것이었으니, 저 class를 우리가 작업 중인 파일에 직접 정의해주기로 했다.

const getImage = () => {
    const editorInstance = editorInstanceRef.current;
    const canvas = editorInstance._graphics._canvas; //fabric Canvas
    const canvasImage = editorInstance._graphics.canvasImage; // fabric Image

    fabric.Image.filters.CustomFilter = createFilterClass(
      'CustomFilter',
      'gingham',
    );

    const filter = new fabric.Image.filters.CustomFilter();

    canvasImage.filters.push(filter); // => fabric 스타일
    canvasImage.applyFilters();
    canvas.renderAll();
  };

// createFilterClass
export default function createFilterClass(type, filterName) {
  return fabric.util.createClass(fabric.Image.filters.BaseFilter, {
    type: type,
    applyTo2d: options => {
      let imageData = options.imageData;
      let data = imageData.data;
      data = imageInstaFilters[filterName](imageData);
    },
  });
}

끝. 아니 잠깐만. 이거로 끝나면 내가 주말 내내 고생한 건 뭔데

이 간단한 것을 제대로 파악하지 못하고 내내 고생했다니... 생각해보면 간단한건데,
'이럴수가 구글링해도 안 나오자나?' 하고 멘붕해버린 것이 너무 멍청했다..

profile
더 나은 서비스를 고민하는 프론트엔드 개발자.

0개의 댓글