使用绘制图像裁剪在 Safari 中不起作用



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

源语言

镜像

这一切都在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;
  }
}

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:对于最近的一个项目,我不得不在这个 Safari 错误上制作一个完整的猴子补丁。您可以在此要点和下面的代码片段中找到它:

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>

最新更新