检查图像透明度。

15

我有一个网站,人们可以上传PNG格式的图片并保存。但在他们保存之前,我需要检查图片是否包含透明度。有没有一种方法(我更喜欢使用JavaScript)来检查一个图像不是24位?

<img id="imageId" src=#" onload="checkRestriction(this,'1')" alt=""/>

var isPng24Bit = false;

function checkRestriction(image, id) {
    if(image.colorDepth != 24) {
      PNGis24Bit = false;
    } else {
      PNGis24Bit = true; 
    }
}

你能展示一个更完整的代码示例吗? - Joel Harkes
另外需要注意的是:人们可以禁用/规避JavaScript,仍然可以上传任何内容,因此您也应该在服务器端进行检查。 - Joel Harkes
我添加了额外的代码,是的,你是正确的,我仍然需要服务器端检查,但对我来说不那么重要。 - Jelle
2个回答

14

您可以使用此画布技术来实现此目的,并根据您的需求自定义此代码。
请确保将画布调整为与图像相同的大小,否则在图像未覆盖画布的位置会出现一些透明的像素。

c.width=element.width;
c.height=element.height;

示例代码和演示:

var canvas1=document.getElementById("canvas1");
var ctx1=canvas1.getContext("2d");
var canvas2=document.getElementById("canvas2");
var ctx2=canvas2.getContext("2d");

$p1=$('#results1');
$p2=$('#results2');

var img1=new Image();
img1.crossOrigin='anonymous'
img1.onload=start1;
img1.src="https://upload.wikimedia.org/wikipedia/commons/4/47/PNG_transparency_demonstration_1.png";
function start1(){

  canvas1.width=img1.width;
  canvas1.height=img1.height;

  ctx1.drawImage(img1,0,0);

  var imgData=ctx1.getImageData(0,0,canvas1.width,canvas1.height);
  var data=imgData.data;
  var found1='Left canvas does not have transparency';
  for(var i=0;i<data.length;i+=4){
    if(data[i+3]<255){found1='Left canvas does have transparency'; 
        break;
        }
  }

  $p1.text(found1);

}


var img2=new Image();
img2.crossOrigin='anonymous'
img2.onload=start2;
img2.src="https://upload.wikimedia.org/wikipedia/commons/4/47/PNG_transparency_demonstration_1.png";
function start2(){

  canvas2.width=img2.width;
  canvas2.height=img2.height;

  ctx2.drawImage(img2,0,0);

  var imgData=ctx2.getImageData(0,0,canvas2.width,canvas2.height);
  var data=imgData.data;
  var found2='Right canvas does not have transparency';
  for(var i=0;i<data.length;i+=4){
    if(data[i+3]<255){found2='Right canvas does have transparency'; 
                      break;
                     }
  }

  $p2.text(found2);

}
body{ background-color: ivory; }
canvas{border:1px solid red;}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
<p id=results1>Results:</p>
<p id=results2>Results:</p>
<canvas id="canvas1" width=300 height=300></canvas>
<canvas id="canvas2" width=300 height=300></canvas>


3
你可能希望在找到透明像素后立即中断循环。 - Kaiido
#Kaiido,是的,已经完成了。 我只需要检查PNG图像是否也是24位。 - Jelle
@Kaiido 很好的观察,你是对的,没有break会更加占用CPU资源,我已经按照你的建议进行了编辑,谢谢。 - Hassan Saeed

9

我以通过直接检查PNG文件头的替代方法来检查的方式发布这篇文章。这样可以节省内存,不必迭代像素,而且无论图像大小如何,性能都将相同。

您可以通过使用HTTPRequestFileReader加载文件为ArrayBuffer,然后仅使用DataView检查文件头结构来完成此操作。

PNG文件始终以IHDR块开头,因此我们只需要检查它是否实际上是PNG文件,然后假定告知深度和类型的信息的偏移量。

深度字段的值可以为1、2、4、8和16(1、2、4为索引、8=24位或每通道8位等)。

类型字段可以是0(灰度)、2(真彩色或RGB)、3(索引)、4(灰度+alpha)和6(RGB+alpha)。

有关PNG文件格式和IHDR标头的详细信息,请参见此链接

loadXHR("//i.imgur.com/zpWwpEM.png", function(result) {
  console.log(result); // result.buffer = original arraybuffer
});

function check(buffer, callback) {
  var view = new DataView(buffer);
  
  // is a PNG?
  if (view.getUint32(0) === 0x89504E47 && view.getUint32(4) === 0x0D0A1A0A) {
    // We know format field exists in the IHDR chunk. The chunk exists at 
    // offset 8 +8 bytes (size, name) +8 (depth) & +9 (type)
    var depth = view.getUint8(8 + 8 + 8);
    var type  = view.getUint8(8 + 8 + 9);
    
    callback({
      depth: depth,
      type: ["G", "", "RGB", "Indexed", "GA", "", "RGBA"][type],
      buffer: view.buffer,
      hasAlpha: type === 4 || type === 6  // grayscale + alpha or RGB + alpha
    })
  }
}

function loadXHR(url, callback) {
  var xhr = new XMLHttpRequest();
  xhr.open("GET", url, true);
  xhr.responseType = "arraybuffer";
  xhr.onload = function() {
    if (xhr.status === 200) check(xhr.response, callback);
    else consle.log("Loading error: " + xhr.statusText);
  };
  xhr.send();
}

同样的例子,但是将正在被检查的图像插入到DOM中:

loadXHR("//i.imgur.com/zpWwpEM.png", function(result) {
  console.log(result); // result.buffer = original arraybuffer
  
  var img = new Image();
  img.onload = function() {URL.revokeObjectURL(this.src)};
  img.src = URL.createObjectURL(new Blob([result.buffer]));
  document.body.appendChild(img);
});

function check(buffer, callback) {
  var view = new DataView(buffer);
  
  // is a PNG?
  if (view.getUint32(0) === 0x89504E47 && view.getUint32(4) === 0x0D0A1A0A) {
    // We know format field exists in the IHDR chunk. The chunk exists at 
    // offset 8 +8 bytes (size, name) +8 (depth) & +9 (type)
    var depth = view.getUint8(8 + 8 + 8);
    var type  = view.getUint8(8 + 8 + 9);
    
    callback({
      depth: depth,
      type: ["G", "", "RGB", "Indexed", "GA", "", "RGBA"][type],
      buffer: view.buffer,
      hasAlpha: type === 4 || type === 6  // grayscale + alpha or RGB + alpha
    })
  }
}

function loadXHR(url, callback) {
  var xhr = new XMLHttpRequest();
  xhr.open("GET", url, true);
  xhr.responseType = "arraybuffer";
  xhr.onload = function() {
    if (xhr.status === 200) check(xhr.response, callback);
    else consle.log("Loading error: " + xhr.statusText);
  };
  xhr.send();
}


2
如果您知道ihdr标头的确切位置,如果服务器允许范围请求,也可以仅请求文件的该部分,或者在用户提供的文件的情况下通过调用Blob.slice(startPos, endPos)来读取fileReader.readAsArrayBuffer。 (我无法从png规范中找到如何阅读它,并且知道您确实知道如何做;-)) - Kaiido
@Kaiido,没错,就像你说的那样,在File对象(技术上是Blob)的情况下,这是一个很好的方法,而且应该会更快。 - user1693593
1
@Snowball SVG更难,因为没有特定的绝对位置头。但是您可以将其解析为XML(或手动解析),并查找所需的SVG属性,例如<svg。然而,这样做更容易出错。 - user1693593
Webp怎么样?这只适用于png... - Bruno Marotta
无论图像的格式如何,这都始终返回true,并且缺少透明度。我正在使用blobs处理,将blob转换为arrayBuffer,然后使用上述方法进行检查,它始终对透明度和格式(png)返回true。 - Hooman Askari
显示剩余3条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接