JS判断PNG中是否含有Alpha通道

3,936 阅读3分钟

前几天业务方来了一个需求,需求大概意思是这样的,后台可以给IOS配置logo图片,但是IOS那边呢会拒绝PNG中含有透明通道的图标,每次业务人员要手动去检测PNG中是否有Alpha通道很是麻烦,于是需要我们前端来帮忙检测一下,如果有就提示业务人员更换图片。。。

至于为什么不能用带Alpha通道的图片我也不知道了,有知道的朋友可以告知一波。

这里先给几个链接大家可以看看,了解一下Alpha通道是什么。

alpha通道:百度百科

【原】PNG的使用技巧

先带大家简单理解一下png里的Alpha通道是什么意思

在mac上大家可以右键点击图片,在右键菜单中的显示简介选项

或者window上右键显示详情中

大家不要简单的吧Alpha通道简单的当作判断是否有透明像素存在,因为不透明的图片也是可以有Alpha通道的。

如果需要判断透明元素,这里贴一下张鑫旭大佬的链接JS检测PNG图片是否有透明背景、抠图等相关处理

我一开始就犯了这个错误,走了弯路,在以为自己没办法解决这个问题的时候,发现了AlloyTeam的一篇文章png 的故事:获取图片信息和像素内容中看见了可以通过解析文件的二进制数据来判断是否有Alpha通道。

通过翻阅各种文章,得知了以下结果

[0-8] 位是png 标记位,其值为 [137, 80, 78, 71, 13, 10, 26, 10]

[8-12] 位是数据块内容长度

[12-16]位是数据块类型

当 [12-16]数据块类型 值为IHDR时有

[16-20]位是图片宽度

[20-24]位是图片高度

[25]位是图像深度

第25位就是我们需要判断数据,他有5种值

[0]:灰度图像, 1,2,4,8或16

[2]:真彩色图像,8或16

[3]:索引彩色图像,1,2,4或8

[4]:带α通道数据的灰度图像,8或16

[6]:带α通道数据的真彩色图像,8或16

然后下面的内容就是提供参考了,我不是专精文件处理的,所以我不能确保我的思路是不是100% 正确了

我认为只需要判断colorDept值不等于3,4,6即可认为改图片不含Alpha通道(如果有错误,请一定指出,谢谢)

<!DOCTYPE html>
<html>

<head>
  <meta charset="UTF-8">
  <link rel="stylesheet"
        href="https://unpkg.com/element-ui/lib/theme-chalk/index.css">
</head>

<body>
  <div id="app">
    <el-upload action="https://jsonplaceholder.typicode.com/posts/"
               :before-upload="beforeUpload"
               :file-list="fileList">
      <el-button size="small"
                 type="primary">点击上传</el-button>
    </el-upload>
  </div>
</body>
<script src="https://unpkg.com/vue/dist/vue.js"></script>
<script src="https://unpkg.com/element-ui/lib/index.js"></script>
<script>
  new Vue({
    el: '#app',
    data() {
      return {
        fileList: []
      };
    },
    methods: {
      beforeUpload(file) {
        return new Promise((resolve, reject) => {
          let reader = new FileReader();
          reader.readAsArrayBuffer(file)
          reader.onload = function(e) {
            let png_head = [137, 80, 78, 71, 13, 10, 26, 10]
            // 使用Uint8Array读取二进制数据,这里只需截取26 位,后面没有用到
            let view = new Uint8Array(e.target.result,0,26)
            let file_head = view.slice(0, 8)
            let isPng = true
            // 通过头8个字节判断是不是png
            for (let i = 0; i < png_head.length; i++) {
              if (file_head[i] != png_head[i]) {
                isPng = false
                alert('非PNG图片')
              }
            }

            function bufferToString(buffer) {
              let str = '';
              for (let i = 0, len = buffer.length; i < len; i++) {
                str += String.fromCharCode(buffer[i]);
              }
              return str;
            }
            
            // 判断是不是IHDR
            if (isPng && bufferToString(view.slice(12, 16)) == 'IHDR') {
              let colorDept = view[25]
              // 最后判断colorDept
              if (colorDept == 6 || colorDept == 4 || colorDept == 3) {
                alert('图片含透明通道')
              }
            }
            resolve()
          }
        })
      },

    }
  })
</script>

</html>

最后的最后,如果上述内容有错误的地方,希望大家务必指出,谢谢🙏。

参考