JavaScript 및 Canvas API를 사용하여 브라우저에서 SVG 이미지 변환

12768 단어 javascript
SVG( Scalable Vector Graphics )는 우리가 웹 사이트에서 사용하는 일반적인 이미지 형식에 비해 몇 가지 장점이 있습니다. 첫째, CSS를 사용하여 스타일을 지정할 수 있으므로 매우 유연합니다. 일부 응용 프로그램의 경우 파일 크기가 동등한 고품질 PNG 또는 JPEG보다 작을 수도 있습니다. 그들은 또한 애니메이션 될 수 있습니다. 마지막으로 이름에서 알 수 있듯이 확장 가능합니다. 즉, 사진 및 기타 고화질 이미지는 SVG에 적합하지 않지만 PNG 또는 JPEG 이미지에서 얻을 수 있는 픽셀화 또는 기타 속임수 없이 확대하거나 축소할 수 있습니다.


50(왼쪽) 및 100(오른쪽) 픽셀의 SVG(위) 및 PNG(아래) 이미지.

그러나 최근 SVG의 유용성이 한계에 이르렀다는 것을 알게 되었습니다(SVG의 잘못은 아니지만).

문제



나는 실험 중 하나로 Game of Life 보드를 조립해 왔으며 사용하고 있던 검은색 정사각형을 텍스처가 조금 더 많은 것으로 교체하고 싶었습니다.


크기가 다른 왼쪽의 오래된 검정색 타일과 오른쪽의 새로운 "질감 있는"타일을 비교합니다.

이 게임은 Canvas API으로 조작할 수 있는 <canvas> 요소를 사용하며 셀을 그리는 원래 코드는 다음과 같습니다(프로젝트는 Vue.js로 빌드되었으며 소스는 GitHub에서 사용할 수 있습니다).

drawCell(x, y, state) {
    let context = this.$refs.canvas.getContext('2d');

    switch (state) {
        case DEAD:
            context.fillStyle = 'white';
            break;

        case ALIVE:
            context.fillStyle = 'black';
            break;
    }

    // fill the grid square but leave the grid outline
    context.fillRect(
        (x * this.cellSize) + 1,
        (y * this.cellSize) + 1,
        this.cellSize - 2,
        this.cellSize - 2
    );
}

이미지 복제



첫 번째 단계로 이 방법을 다음과 같이 훨씬 더 짧은 방법으로 대체했습니다.

drawCell(x, y, state) {
    let context = this.$refs.canvas.getContext('2d');

    context.drawImage(this.images[state], (x * this.cellSize), (y * this.cellSize), this.cellSize, this.cellSize);
}
drawImage(Image, x, y, height, width)는 PNG, JPEG 및 SVG를 포함하여 이전에 로드된 모든 이미지 데이터를 허용합니다.

안타깝게도 사용자가 셀 크기를 제어할 수 있기 때문에 내가 사용한 모든 PNG 이미지는 이미지 크기 조정으로 인해 발생하는 일반적인 문제에 노출될 수 있으므로 SVG를 사용하기로 했습니다.

구성 요소mounted 메서드를 사용하여 두 이미지를 미리 로드했습니다.

async mounted() {
    this.images = {
        'alive': await loadImage("./img/alive.svg"),
        'dead': await loadImage("./img/dead.svg"),
    }

    this.initialiseMap();
}
loadImage는 이미지를 비동기식으로 로드할 수 있도록 하는 간단한 기능이므로 구성 요소가 이미지를 렌더링하려고 시도하기 전에 이미지를 사용할 수 있도록 합니다.

function loadImage(url, height, width) {
    return new Promise((resolve, reject) => {
        let image = new Image();

        image.onload = () => {
            resolve(image);
        }
        image.onerror = reject;

        image.src = url;
    });
}

접지 정지



불행하게도 이 구현으로 인해 전체 그리기 주기가 급격히 줄어들었고 각 세대를 다시 그리는 데 수십 초가 걸렸습니다. 오래된 채워진 사각형 방법이 상당히 큰 보드에서도 번개처럼 빠르다는 점을 고려하면 이것은 상당한 좌절이었습니다.

SVG를 미리 로드하여 최악의 성능 문제를 방지하고 싶었지만 각 셀의 크기 조정 및 그리기는 여전히 비용이 많이 드는 작업인 것 같습니다.

나는 평평한 검은 타일을 가질 운명이었습니까? 한 가지 더 시도해 볼 것이 있었습니다.

SVG를 PNG로 변환


drawImage가 로드된 모든 이미지 데이터를 허용할 수 있다는 것을 기억하십니까? 이 데이터는 파일에서 직접 가져올 필요가 없습니다. 다른 canvas 요소에서 이미지 데이터를 추출하고 drawImage 를 포함하여 다른 이미지를 사용할 수 있는 곳에 사용할 수 있습니다.

loadImage 기능을 업데이트했습니다.

function loadImageAsPNG(url, height, width) {
    return new Promise((resolve, reject) => {
        let sourceImage = new Image();

        sourceImage.onload = () => {
            let png = new Image();
            let cnv = document.createElement('canvas'); // doesn't actually create an element until it's appended to a parent, 
                                                        // so will be discarded once this function has done it's job
            cnv.height = height;
            cnv.width = width;

            let ctx = cnv.getContext('2d');

            ctx.drawImage(sourceImage, 0, 0, height, width);
            png.src = cnv.toDataURL(); // defaults to image/png
            resolve(png);
        }
        image.onerror = reject;

        image.src = url;
    });
}

여기서 toDataURL(type, encoderOptions) 메서드는 MIME 유형 문자열(image/png, image/jpeg 등)을 허용하고 다른 Image의 소스로 사용할 수 있는 base-64 인코딩 이미지 데이터를 반환합니다. 요청한 MIME 유형을 지원하지 않거나 제공하지 않으면 PNG를 반환합니다. 두 번째 매개변수는 JPEG와 같은 "손실"유형의 이미지 품질을 제어하는 ​​데 사용됩니다. 이것은 0과 1 사이의 숫자를 사용하며 기본값은 0.92입니다.

마지막으로 toDataURL도 최신 WebP 유형을 출력할 수 있지만 Chrome에서만 출력할 수 있습니다(다른 브라우저에서는 기본적으로 PNG로 설정되기 때문에 괜찮습니다).

마지막 생각들



이것은 이상적이지 않습니다. drawImage는 여전히 비용이 많이 드는 기능이고 더 큰 보드는 여전히 상당히 느리기 때문에 다른 최적화를 찾아야 합니다.

그러나 브라우저에서 한 이미지 유형에서 다른(지원되는) 유형으로 변환해야 하는 경우 Canvas API 및 해당toDataURL 메서드에 대해 생각해 보십시오. toDataURL는 사용자가 그린 캔버스에서 이미지 데이터를 가져오고(앱에서 지원하는 경우) 파일로 저장하기 위해 서버로 보내는 데에도 사용할 수 있습니다.

좋은 웹페이지 즐겨찾기