【问题标题】:Cropping with drawImage not working in Safari使用 drawImage 进行裁剪在 Safari 中不起作用
【发布时间】:2016-05-31 18:31:43
【问题描述】:

我正在使用画布开发一些简单的图像处理功能。 用户上传一张图片,可以旋转和裁剪它,然后点击确定。 然后将图像分成两半,每一半绘制镜像到两个画布元素,如下所示:

Original

Mirrored

它在 Chrome、Firefox、IE 和 Android 设备上运行良好。 Safari 不会玩得很好。除分割功能外,所有图像处理都可以正常工作。它确实绘制到一个画布元素,但另一个只是黑色。我已尝试更改 drawImage 代码,但无法正常工作。

函数如下:

function splitImage(canvas, context, image, isLeftSide) {
  canvas.width = img.width;
  canvas.height = img.height;
  context.save();
  if(isLeftSide) {
    context.drawImage(
      image, 
      image.width / 2,
      0, 
      image.width, 
      image.height, 
      canvas.width / 2, 
      0, 
      canvas.width, 
      canvas.height
    );
    context.scale(-1, 1);
    context.drawImage(
      image, 
      image.width / 2, 
      0, 
      image.width, 
      image.height, 
      -canvas.width / 2, 
      0, 
      canvas.width, 
      canvas.height
    );
  } else {
    context.drawImage(
      image, 
      0, 
      0, 
      image.width / 2, 
      image.height, 
      0, 
      0, 
      canvas.width / 2, 
      canvas.height
    );
    context.scale(-1, 1);
    context.drawImage(
      image, 
      0, 
      0, 
      image.width / 2, 
      image.height, 
      -canvas.width, 
      0, 
      canvas.width / 2, 
      canvas.height
    );
  }
  context.restore();
  download(canvas);
}

确切地说,是 if(isLeftSide) 中的 drawImage 操作在 Safari 中不起作用。

有什么想法吗?

编辑: 它似乎也不适用于 iOS 设备。 我读过 Safari 和 iOS 设备在处理大图像时可能会耗尽内存。 为了抵消这一点(并减少一些滞后),我添加了一个调整大小的功能。如有必要,图像将调整为最大 800 像素宽度和 800 像素高度,保持纵横比不变。这是在任何其他图像处理之前完成的,但没有任何区别。

调整大小功能:

function resizeImage() {
  var size = 800;
  if(imgTemp.width > size && imgTemp.width >= imgTemp.height) {
    imgTemp.height = (imgTemp.height / imgTemp.width) * size;
    imgTemp.width = size;
  } else if (imgTemp.height > size && imgTemp.height > imgTemp.width) {
    imgTemp.width = (imgTemp.width / imgTemp.height) * size;
    imgTemp.height = size;
  }
}

【问题讨论】:

    标签: javascript jquery canvas safari image-manipulation


    【解决方案1】:

    drawImage() 被调用超出sourceImage 的范围时会出现该错误。

    您必须仔细检查源宽度和源高度是否始终小于或等于图像的宽度和高度:

    所以对于第一个 if 块:

    var sourceX = image.width/2;
    var sourceY = 0;
    var sourceWidth = image.width - sourceX; // you're in the bounds
    var sourceHeight = image.height;
    var destX = canvas.width/2;
    var destY = 0;
    var destWidth = canvas.width;
    var destHeight = canvas.height;
    
    ctx.drawImage(image, sourceX, sourceY, sourceWidth, sourceHeight, destX, destY, destWidth, destHeight);
    

    或作为单行:

    ctx.drawImage(image, image.width/2, 0, image.width - (image.width/2), image.height, canvas.width/2, 0, canvas.width, canvas.height);
    

    Ps:对于recent project,我必须对这个 Safari 错误进行完整的猴子补丁。您可以在this gist 和下面的 code-sn-p 中找到它:

    const canvas = document.getElementById( "canvas" );
    const ctx = canvas.getContext( "2d" );
    ctx.fillRect( 0, 0, 80, 80 );
    ctx.drawImage( canvas, -100, -100, 180, 180, 30, 30, 90, 90 );
    <canvas id="canvas" width="300" height="300"></canvas>
    <script>
    // drawImage monkey-patch for Safari
    (()=> {
    
      if( !needPoly() ) { return; }
    
      const proto = CanvasRenderingContext2D.prototype;
      const original = proto.drawImage;
      if( !original ) {
        console.error( "This script requires a basic implementation of drawImage" );
        return;
      }
    
      proto.drawImage = function drawImage( source, x, y ) { // length: 3
    
        const will_crop = arguments.length === 9;
        if( !will_crop ) {
          return original.apply( this, [...arguments] );
        }
    
        const safe_rect = getSafeRect( ...arguments );
        if( isEmptyRect( safe_rect ) ) {
          return;
        }
        return original.apply( this, safe_rect );
      } 
    
      function needPoly() {
        const ctx = document.createElement( "canvas" ).getContext( "2d" );
        ctx.fillRect( 0, 0, 40, 40 );
        ctx.drawImage( ctx.canvas, -40, -40, 80, 80, 50, 50, 20, 20 );
    
        const img = ctx.getImageData( 50, 50, 30, 30 ); // 10px around expected square
        const data = new Uint32Array( img.data.buffer );
        const colorAt = (x, y) => data[ y * img.width + x ];
    
        const transparents = [ [ 9, 9 ], [ 20, 9 ], [ 9, 20 ], [ 20, 20 ] ];
        const blacks = [ [ 10, 10 ], [ 19, 10 ], [ 10, 19 ], [ 19, 19 ] ];
        return transparents.some( ([ x, y ]) => colorAt( x, y ) !== 0x00000000 ) ||
          blacks.some( ([ x, y ]) => colorAt( x, y ) === 0x00000000 )
      }
    
      function getSafeRect( image, sx, sy, sw, sh, dx, dy, dw, dh ) {
      
        const { width, height } = getSourceDimensions( image );
        
        if( sw < 0 ) {
          sx += sw;
          sw = Math.abs( sw );
        }
        if( sh < 0 ) {
          sy += sh;
          sh = Math.abs( sh );
        }
        if( dw < 0 ) {
          dx += dw;
          dw = Math.abs( dw );
        }
        if( dh < 0 ) {
          dy += dh;
          dh = Math.abs( dh );
        }
        const x1 = Math.max( sx, 0 );
        const x2 = Math.min( sx + sw, width );
        const y1 = Math.max( sy, 0 );
        const y2 = Math.min( sy + sh, height );
        const w_ratio = dw / sw;
        const h_ratio = dh / sh;
    
        return [
          image,
          x1,
          y1,
          x2 - x1,
          y2 - y1,
          sx < 0 ? dx - (sx * w_ratio) : dx,
          sy < 0 ? dy - (sy * h_ratio) : dy,
          (x2 - x1) * w_ratio,
          (y2 - y1) * h_ratio
        ];
    
      }
    
      function isEmptyRect( args ) {
        // sw, sh, dw, dh
        return [ 3, 4, 7, 8 ].some( (index) => !args[ index ] );
      }
    
      function getSourceDimensions( source ) {
        const sourceIs = ( type ) => {
          const constructor = globalThis[ type ];
          return constructor && (source instanceof constructor);
        };
        if( sourceIs( "HTMLImageElement" ) ) {
          return { width: source.naturalWidth, height: source.naturalHeight };
        }
        else if( sourceIs( "HTMLVideoElement" ) ) {
          return { width: source.videoWidth, height: source.videoHeight };
        }
        else if( sourceIs( "SVGImageElement" ) ) {
          throw new TypeError( "SVGImageElement isn't yet supported as source image.", "UnsupportedError" );
        }
        else if( sourceIs( "HTMLCanvasElement" ) || sourceIs( "ImageBitmap" ) ) {
          return source;
        }
      }
    
    })();
    
    </script>

    【讨论】:

    • @MarksCode, "source width" 和 "source height" 是drawImage 的第四个和第五个参数长版(9 个参数)。由于这个 Safari 错误,这些参数应始终小于绘图图像的大小(第一个参数)的宽度和高度,您甚至必须检查源 X(第二个参数)和源 Y(第三个参数)添加到源宽度和源高度不会使其尝试绘制不存在的像素。
    • @MarksCode,简而言之: 当使用 drawImage 的长版本时,您将设置从源图像到您将要绘制的位置。 Safari 有一个错误,当您尝试绘制不存在的像素(超出边界或源图像)时,它将失败。因此,您必须确保由第 2、3、4 和 5 个参数绘制的矩形永远不会超出源图像的边界。
    • @Kaiido 感谢您的解释。这是一个BS错误.. :(
    • 救命稻草!! :)
    • 我的意思是回应上面的用户说这在他的 Safari 中不是问题。不是您的解决方案,但实际上我的意思是必须使用它。
    猜你喜欢
    • 2017-06-21
    • 1970-01-01
    • 2015-03-02
    • 1970-01-01
    • 2017-05-01
    • 1970-01-01
    • 1970-01-01
    • 2012-10-07
    • 1970-01-01
    相关资源
    最近更新 更多