【问题标题】:how to parse image to ICO format in javascript client side如何在javascript客户端将图像解析为ICO格式
【发布时间】:2020-08-24 09:42:39
【问题描述】:

我想在前端使用 javascript 将图像 (png/jpeg) 转换为 ICO。

在网上搜索时,我在 github 上看到了这段代码:https://gist.github.com/twolfson/7656254,但不幸的是它使用了 nodejs 的 fs 模块(+ 代码很难理解)。

有人可以告诉我应该搜索什么/或通过在前端使用 javascript 将 png/jpeg 转换为 ico 的方法吗?

我尝试过的替代方法?

使用了这个 repo:https://github.com/fiahfy/ico-convert 但他们使用了sharp,并且客户端不支持sharp

【问题讨论】:

  • 您链接的代码仅使用fs 进行文件输入和输出。如果您将这些操作替换为您自己的前端等效操作,那么您应该一切顺利。
  • 这里已经回答了这个问题stackoverflow.com/questions/48304752/…
  • 你试过了吗? github.com/egy186/icojs
  • 你说得对,这行不通
  • 你为什么不使用后端?有什么具体原因吗?如果可能的话,您能否也说说转换为 ICO 格式的原因。

标签: javascript


【解决方案1】:

在谷歌上,我得到了this Mozilla post,带有示例,它提供了以下转换为ICO格式的代码(仅限Firefox浏览器),

一种将画布转换为 ico 的方法(仅限 Mozilla)

这使用-moz-parse 将画布转换为ico。 Windows XP 没有 支持从 PNG 转换为 ico,因此它使用 bmp 代替。下载 链接是通过设置下载属性创建的。的价值 下载属性是将用作文件名的名称。

代码:

var canvas = document.getElementById('canvas');
var d = canvas.width;
ctx = canvas.getContext('2d');
ctx.beginPath();
ctx.moveTo(d / 2, 0);
ctx.lineTo(d, d);
ctx.lineTo(0, d);
ctx.closePath();
ctx.fillStyle = 'yellow';
ctx.fill();

function blobCallback(iconName) {
  return function(b) {
    var a = document.createElement('a');
    a.textContent = 'Download';
    document.body.appendChild(a);
    a.style.display = 'block';
    a.download = iconName + '.ico';
    a.href = window.URL.createObjectURL(b);
  }
}
canvas.toBlob(blobCallback('passThisString'),
  'image/vnd.microsoft.icon', 
  '-moz-parse-options:format=bmp;bpp=32'
);

除此之外,我发现没有其他方法可以将 png/jpeg 转换为 ICO 格式。或者,您可以使用以下任何模块在服务器端进行转换:

【讨论】:

    【解决方案2】:

    如果您想支持所有浏览器并且只有一个 PNG 图像,.ICO 文件格式支持嵌入的 PNG 图像,只要它们小于 256x256。基于 ICO 文件格式,我已经能够使用一个小的 PNG 图像和一个十六进制编辑器构建一个 ICO。这可以在 JavaScript 中复制。这是我的测试图像文件:

    为了将其转换为 ICO,我在前面添加了以下十六进制数据,小端编码(值中的字节反转):

    00 00 01 00 - File header. Says "This file is an ICO."
    01 00 - There is one image in this file.
    9C - This image is 0x9C pixels wide. **This should be variable**
    77 - This image is 0x77 pixels tall. **This should be variable**
    00 - There is not a limited color pallette.
    00 - Reserved value.
    01 00 - There is one color plane in this image.
    18 00 - There are 0x18 bits per pixel (24 bits per pixel is standard RGB encoding)
    8A 06 00 00 - This image is 0x0000068A large. **This should be variable**
    16 00 00 00 - There were 0x16 bytes before this point.
    [PNG data here]
    

    这成功地从 PNG 创建了一个 ISO 文件。您可以为此前置创建一个简单的 JavaScript 脚本。查看 PNG 规范,前 8 个字节是一个标头,后面是 8 个字节的 IHDR 块元数据,以 4 字节 little-endian 宽度和 4 字节 little-endian 高度开始。这可以在我们的脚本中用于发现 PNG 的宽度和高度。比如:

    function pngToIco(icoFile, pngData) {
    
        icoFile = "\x00\x00\x01\x00\x01\x00"; // First 6 bytes are constant
        icoFile += pngData[15+4]; // PNG width byte
        icoFile += pngData[15+8]; // PNG height byte
        // Make sure PNG is less than 256x256
        if (pngData[15+1] || pngData[15+2] || pngData[15+3]) {
            console.log("Width over 255!"); return;
        }
        if (pngData[15+5] || pngData[15+6] || pngData[15+7]) {
            console.log("Height over 255!"); return;
        }
        // Add more (probably constant) information
        icoFile += "\x00\x00\x01\x00\x18\x00";
        // Add encoded length
        var lenBytes = pngData.length;
        for (var i=0; i<4; i++) {
            icoFile += String.fromCharCode(lenBytes % 256);
            lenBytes >>= 4;
        }
        // We had 0x16 bytes before now
        icoFile += "\x16\x00\x00\x00";
        // Now add the png data
        icoFile += pngData;
        // Now we have a valid ico file!
        return icoFile;
    
    }
    

    【讨论】:

    • 这很棒。带有多尺寸图片的 ico 怎么样?
    • @vanowm,您必须将其他 PNG 文件及其各自的图像标题附加到 ICO,然后增加文件中的图像数量。所以01 00 变为02 00 并再次从9C 开始,但使用您自己的PNG 尺寸。您还需要将 16 00 00 00 更改为从文件开头开始的所有后续图像的(现在可变的)字节数。
    【解决方案3】:

    如果您愿意将这个问题的规则改成 JavaScript 问题,这是另一种解决方案。如果您的 Web 浏览器支持 WebAssembly(大多数现代浏览器都支持),您可以使用交叉编译到 WebAssembly 中的著名库 ImageMagick 的一个版本。这是我发现的:https://github.com/KnicKnic/WASM-ImageMagick

    此库从 sourceBytes 缓冲区中获取图像数据,并返回转换或转换后的图像。根据文档,您可以使用类似于 ImageMagick 的终端语法的语法,并添加一些额外的代码(从文档中复制并修改):

    <script type='module'>
    import * as Magick from 'https://knicknic.github.io/wasm-imagemagick/magickApi.js';
    
    async function converPNGToIco(pngData) {
        var icoData = await Magick.Call([{ 'name': 'srcFile.png', 'content': pngData }], ["convert", "srcFile.png", "-resize", "200x200", "outFile.ico"]);
        // do stuff with icoData
    }
    </script>
    

    【讨论】:

      【解决方案4】:

      这应该可行

      github 网址:https://github.com/egy186/icojs

      <input type="file" id="input-file" />
      <script>
        document.getElementById('input-file').addEventListener('change', function (evt) {
          // use FileReader for converting File object to ArrayBuffer object
          var reader = new FileReader();
          reader.onload = function (e) {
            ICO.parse(e.target.result).then(function (images) {
              // logs images
              console.dir(images);
            })
          };
          reader.readAsArrayBuffer(evt.target.files[0]);
        }, false);
      </script>
      

      【讨论】:

      • 这会解析 ICO 而不是反过来 (ico -> png)
      【解决方案5】:

      完整的工作示例。

      这里是 JSFiddle:click(使用它是因为这个站点的代码 sn-p 不允许我点击 a[download] 链接),但其他代码在代码 sn-p 中工作 - 您可以在新的标签来查看它。

      var MyBlobBuilder = function() {
        this.parts = [];
      }
      
      MyBlobBuilder.prototype.append = function(part) {
        this.parts.push(part);
        this.blob = undefined; // Invalidate the blob
      };
      
      MyBlobBuilder.prototype.write = function(part) {
        this.append(part);
      }
      
      MyBlobBuilder.prototype.getBlob = function(atype) {
        if (!this.blob) {
          this.blob = new Blob(this.parts, {
            type: !atype ? "text/plain" : atype
          });
        }
        return this.blob;
      };
      
      const img = document.getElementById('input'),
        a = document.getElementById('a'),
        a2 = document.getElementById('a2'),
        file1 = document.getElementById('file');
      let imgSize = 0,
        imgBlob;
      
      img.onload = e => {
        fetch(img.src).then(resp => resp.blob())
          .then(blob => {
            imgBlob = blob;
            imgSize = blob.size;
          });
      };
      
      function convertToIco(imgSize, imgBlob) {
        let file = new MyBlobBuilder(),
          buff;
      
        // Write out the .ico header [00, 00]
        // Reserved space
        buff = new Uint8Array([0, 0]).buffer;
        file.write(buff, 'binary');
      
        // Indiciate ico file [01, 00]
        buff = new Uint8Array([1, 0]).buffer;
        file.write(buff, 'binary');
      
        // Indiciate 1 image [01, 00]
        buff = new Uint8Array([1, 0]).buffer;
        file.write(buff, 'binary');
      
        // Image is 50 px wide [32]
        buff = new Uint8Array([img.width < 256 ? img.width : 0]).buffer;
        file.write(buff, 'binary');
      
        // Image is 50 px tall [32]
        buff = new Uint8Array([img.height < 256 ? img.height : 0]).buffer;
        file.write(buff, 'binary');
      
        // Specify no color palette [00]
        // TODO: Not sure if this is appropriate
        buff = new Uint8Array([0]).buffer;
        file.write(buff, 'binary');
      
        // Reserved space [00]
        // TODO: Not sure if this is appropriate
        buff = new Uint8Array([0]).buffer;
        file.write(buff, 'binary');
      
        // Specify 1 color plane [01, 00]
        // TODO: Not sure if this is appropriate
        buff = new Uint8Array([1, 0]).buffer;
        file.write(buff, 'binary');
      
        // Specify 32 bits per pixel (bit depth) [20, 00]
        // TODO: Quite confident in this one
        buff = new Uint8Array([32, 0]).buffer;
        file.write(buff, 'binary');
      
        // Specify image size in bytes
        // DEV: Assuming LE means little endian [84, 01, 00, 00] = 388 byte
        // TODO: Semi-confident in this one
        buff = new Uint32Array([imgSize]).buffer;
        file.write(buff, 'binary');
      
        // Specify image offset in bytes
        // TODO: Not that confident in this one [16]
        buff = new Uint32Array([22]).buffer;
        file.write(buff, 'binary');
      
        // Dump the .png
        file.write(imgBlob, 'binary');
      
        return file.getBlob('image/vnd.microsoft.icon');
      }
      
      function test() {
        const ico = convertToIco(imgSize, imgBlob);
      
        let url = window.URL.createObjectURL(ico);
        a.href = url;
        document.getElementById('result1').style.display = '';
      }
      
      file1.addEventListener('change', () => {
        var file = file1.files[0];
        var fileReader = new FileReader();
        fileReader.onloadend = function(e) {
          var arrayBuffer = e.target.result;
          const blobFile = new Blob([arrayBuffer]);
          const ico = convertToIco(blobFile.size, blobFile);
          let url = window.URL.createObjectURL(ico);
          a2.href = url;
          document.getElementById('result2').style.display = '';
        }
        fileReader.readAsArrayBuffer(file);
      });
      .section {
        padding: 4px;
        border: 1px solid gray;
        margin: 4px;
      }
      
      h3 {
        margin: 0 0 4px 0;
      }
      <div class="section">
        <h3>Convert img[src] to ICO</h3>
        <div>
          Example (PNG): <img src="https://gist.githubusercontent.com/twolfson/7656254/raw/c5d0dcedc0e212f177695f08d685af5aad9ff865/sprite1.png" id="input" />
        </div>
      
        <div>
          <button onclick="test()">Conver to ICO</button>
        </div>
      
        <div id="result1" style="display:none">
          Save ICO: <a id="a" href="#" download="example.ico">click me</a>
        </div>
      </div>
      
      <div class="section">
        <h3>Convert input[type=file] to ICO</h3>
        <input type="file" id="file" />
        <div id="result2" style="display:none">
          Save ICO: <a id="a2" href="#" download="example.ico">click me</a>
        </div>
      </div>

      附:使用的文档:Wikipedia.

      【讨论】:

        【解决方案6】:

        这是一个纯 JavaScript 版本(受 @id01 启发),它转换 png 图像数据数组(每个图像作为字节数组)并返回 .ico 文件的字节数组

        function pngToIco( images )
        {
          let icoHead = [ //.ico header
                0, 0, // Reserved. Must always be 0 (2 bytes)
                1, 0, // Specifies image type: 1 for icon (.ICO) image, 2 for cursor (.CUR) image. Other values are invalid. (2 bytes)
                images.length & 255, (images.length >> 8) & 255 // Specifies number of images in the file. (2 bytes)
              ],
              icoBody = [],
              pngBody = [];
        
          for(let i = 0, num, pngHead, pngData, offset = 0; i < images.length; i++)
          {
            pngData = Array.from( images[i] );
            pngHead = [ //image directory (16 bytes)
              0,    // Width 0-255, should be 0 if 256 pixels (1 byte)
              0,    // Height 0-255, should be 0 if 256 pixels (1 byte)
              0,    // Color count, should be 0 if more than 256 colors (1 byte)
              0,    // Reserved, should be 0 (1 byte)
              1, 0, // Color planes when in .ICO format, should be 0 or 1, or the X hotspot when in .CUR format (2 bytes)
              32, 0 // Bits per pixel when in .ICO format, or the Y hotspot when in .CUR format (2 bytes)
            ];
            num = pngData.length;
            for (let i = 0; i < 4; i++)
              pngHead[pngHead.length] = ( num >> ( 8 * i )) & 255; // Size of the bitmap data in bytes (4 bytes)
        
            num = icoHead.length + (( pngHead.length + 4 ) * images.length ) + offset;
            for (let i = 0; i < 4; i++)
              pngHead[pngHead.length] = ( num >> ( 8 * i )) & 255; // Offset in the file (4 bytes)
        
            offset += pngData.length;
            icoBody = icoBody.concat(pngHead); // combine image directory
            pngBody = pngBody.concat(pngData); // combine actual image data
          }
          return icoHead.concat(icoBody, pngBody);
        }
        

        通过将多个画布图像转换为.ico的示例如何使用:

        function draw(canvas)
        {
          var ctx = canvas.getContext('2d');
          ctx.beginPath();
          var p = Math.min(canvas.width, canvas.height);
          ctx.fillStyle = "yellow";
          ctx.lineWidth = p / 16 / 1.5;
          ctx.lineCap = "round";
          ctx.arc(50*p/100, 50*p/100, 39*p/100, 0, Math.PI * 2, true); // Outer circle
          ctx.save();
          var radgrad = ctx.createRadialGradient(50*p/100,50*p/100,0,50*p/100,50*p/100,50*p/100);
          radgrad.addColorStop(0, 'rgba(128,128,128,1)');
          radgrad.addColorStop(0.3, 'rgba(128,128,128,.8)');
          radgrad.addColorStop(0.5, 'rgba(128,128,128,.5)');
          radgrad.addColorStop(0.8, 'rgba(128,128,128,.2)');
          radgrad.addColorStop(1, 'rgba(128,128,128,0)');
          ctx.fillStyle = radgrad;
          ctx.fillRect(0, 0, p, p);
          ctx.restore();
          ctx.fill();
          ctx.moveTo(77*p/100, 50*p/100);
          ctx.arc(50*p/100, 50*p/100, 27*p/100, 0, Math.PI, false);  // Mouth (clockwise)
          ctx.moveTo(41*p/100, 42*p/100);
          ctx.arc(38*p/100, 42*p/100, 3*p/100, 0, Math.PI * 2, true);  // Left eye
          ctx.moveTo(65*p/100, 42*p/100);
          ctx.arc(62*p/100, 42*p/100, 3*p/100, 0, Math.PI * 2, true);  // Right eye
          ctx.stroke();
        }
        
        let images = [];//array of image data. each image as an array of bytes
        for(let i= 0, a = [16,24,32,48,64,128,256,512,1024]; i < a.length; i++)
        {
          let canvas = document.createElement("canvas");
          canvas.width = a[i];
          canvas.height = a[i];
          draw(canvas);
        
        // Convert canvas to Blob, then Blob to ArrayBuffer.
          canvas.toBlob(function (blob) {
            const reader = new FileReader();
            reader.addEventListener('loadend', () => {
              images[i] = new Uint8Array(reader.result);
              if (images.length == a.length)//all canvases converted to png
              {
                let icoData = pngToIco(images), //array of bytes 
                    type = "image/x-ico",
                    blob = new Blob([new Uint8Array(icoData)], {type: type}),
                    a = document.getElementById("download");
        
                a.download = "smile.ico";
                a.href = window.URL.createObjectURL(blob);
                a.dataset.downloadurl = [type, a.download, a.href].join(':');
                document.getElementById("img").src = a.href;
              }
            });
            reader.readAsArrayBuffer(blob);
          }, "image/png");
          document.body.appendChild(canvas);
        }
        
        function pngToIco( images )
        {
          let icoHead = [ //.ico header
                0, 0, // Reserved. Must always be 0 (2 bytes)
                1, 0, // Specifies image type: 1 for icon (.ICO) image, 2 for cursor (.CUR) image. Other values are invalid. (2 bytes)
                images.length & 255, (images.length >> 8) & 255 // Specifies number of images in the file. (2 bytes)
              ],
              icoBody = [],
              pngBody = [];
        
          for(let i = 0, num, pngHead, pngData, offset = 0; i < images.length; i++)
          {
            pngData = Array.from( images[i] );
            pngHead = [ //image directory (16 bytes)
              0,    // Width 0-255, should be 0 if 256 pixels (1 byte)
              0,    // Height 0-255, should be 0 if 256 pixels (1 byte)
              0,    // Color count, should be 0 if more than 256 colors (1 byte)
              0,    // Reserved, should be 0 (1 byte)
              1, 0, // Color planes when in .ICO format, should be 0 or 1, or the X hotspot when in .CUR format (2 bytes)
              32, 0 // Bits per pixel when in .ICO format, or the Y hotspot when in .CUR format (2 bytes)
            ];
            num = pngData.length;
            for (let i = 0; i < 4; i++)
              pngHead[pngHead.length] = ( num >> ( 8 * i )) & 255; // Size of the bitmap data in bytes (4 bytes)
        
            num = icoHead.length + (( pngHead.length + 4 ) * images.length ) + offset;
            for (let i = 0; i < 4; i++)
              pngHead[pngHead.length] = ( num >> ( 8 * i )) & 255; // Offset in the file (4 bytes)
        
            offset += pngData.length;
            icoBody = icoBody.concat(pngHead); // combine image directory
            pngBody = pngBody.concat(pngData); // combine actual image data
          }
          return icoHead.concat(icoBody, pngBody);
        }
        <a id="download">download image <img id="img" width="128"></a>
        <br>

        https://jsfiddle.net/vanowm/b657yksg/

        【讨论】:

          猜你喜欢
          • 2013-08-06
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2011-06-14
          • 1970-01-01
          • 2013-04-12
          相关资源
          最近更新 更多