【问题标题】:Sort by image resolution in gallery按图库中的图像分辨率排序
【发布时间】:2017-07-21 07:43:33
【问题描述】:

我不久前制作了这个画廊:https://jsfiddle.net/5e9L09Ly/

别担心它不会上传任何东西。

我让你可以按文件大小排序,但我想按图像分辨率排序。问题是并非所有图像都已加载到页面上,所以我不知道它们的大小。

图库本身非常基础,它要求一个目录,然后它会显示该目录中的所有图像和视频。

它目前仅适用于 chrome,只需单击最顶部的浏览。浏览前的数字是选择目录时应显示或加载的图像数量。

我不知道如何解决这个问题...

我想到的一件事是这样的:

imgLoad.attr("src", url);
imgLoad.unbind("load");
imgLoad.bind("load", function() {
    console.log(url+' size: '+(this.width + this.height));
});

这样做的问题是它会加载它加载的每一个图像,如果你有很多图像,它会给硬盘和浏览器带来很大的压力,比如说你试图加载 20k 图像。

所以是的....任何建议都会很棒。

代码太多,这里就不贴代码了,请看fiddle。

【问题讨论】:

  • any suggestions would be great -> 缓存
  • @MarcinOrlowski 谢谢,我没想到
  • @K3N 谢谢,直到我现在用谷歌搜索它,我才知道文档片段的存在。看起来真的很酷。我正在考虑使用网络工作者将图像慢慢加载到内存中并读取宽度和高度并将数据插入到我的主数组中并让我知道它何时完成......但我不确定
  • 图片来自哪里?如果你控制它们,你不能将高度/宽度添加到 img 标签中,并使用许多“延迟加载”函数中的任何一个来延迟加载实际图像吗?
  • @sideroxylon 试试 jsfiddle。它从您选择的文件夹中提取它,我无权访问那种元数据,之后我需要以某种方式计算宽度和高度

标签: javascript jquery html image


【解决方案1】:

理论上,可以通过从上传文件的 arrayBuffer 表示中提取这些值来利用浏览器的处理。

大多数图像格式都有包含媒体尺寸的可读元数据,因此我们可以在不要求浏览器实际解析和计算图像数据(解压缩、解码等)的情况下访问它。

这是一个非常粗略的概念证明,使用ExifReader lib 我没有进行太多测试,用于 jpeg 图像。

/* 
	Rough proof of concept of getting image files width & height
		by reading their metadata directly in arrayBuffer, instead of loading it
	Should support most jpeg png gif and bmp image files
  (though all versions of these formats have NOT been tested)

	@input A fileList.
	@output A promise 
		whose fulfillment handler receives an Array containing successfully parsed files.
*/
function getImageSizes(files) {
  /* Attaches a buffer of the size specified to the File object */
  function getBuffer(fileList, size) {

    return new Promise((resolve, reject) => {

      const fr = new FileReader();
      const toLoad = fileList.length;
      if (!toLoad) { // an empty list
        resolve(fileList);
        return;
      }
      let arr = [];
      let loaded = 0;
      let current = fileList[loaded];
      let chunk = current.slice(0, size || current.size); // get only the required bytes
      fr.onload = e => {
        fileList[loaded].buf = fr.result;

        if (++loaded < toLoad) {
          current = fileList[loaded];
          chunk = current.slice(0, size || current.size);
          fr.readAsArrayBuffer(chunk);
        } else { // once all the list has been treated
          resolve(fileList);
        }
      };

      fr.readAsArrayBuffer(chunk);

    });

  }

  /* png is easy, IHDR starts at 16b, and 8 first bytes are 32bit width & height */
  // You can read https://www.w3.org/TR/PNG-Chunks.html for more info on each numeric value
  function getPNGSizes(pngArray) {
    let view;
    // Little endian only
    function readInt16(offset) {
      return view[offset] << 24 |
        view[offset + 1] << 16 |
        view[offset + 2] << 8 |
        view[offset + 3];
    }

    pngArray.forEach(o => {
      view = new Uint8Array(o.buf);
      o.meta = {
        width: readInt16(16),
        height: readInt16(20),
        bitDepth: view[24],
        colorType: view[25],
        compressionMethod: view[26],
        filterMethod: view[27],
        interlaceMethod: view[28]
      };
      o.width = o.meta.width;
      o.height = o.meta.height;
    });
    return pngArray;
  }

  function getJPEGSizes(jpegArray) {
    /* the EXIF library seems to have some difficulties */
    let failed = [];
    let retry = [];
    let success = [];
    // EXIF data can be anywhere in the file, so we need to get the full arrayBuffer
    return getBuffer(jpegArray).then(jpegArray => {
      jpegArray.forEach(o => {
        try {
          const tags = ExifReader.load(o.buf);
          if (!tags || !tags.PixelXDimension) {
            throw 'no EXIF';
          }
          o.meta = tags; // since OP said he wanted it
          o.width = tags.PixelXDimension.value;
          o.height = tags.PixelYDimension.value;
          success.push(o);
        } catch (e) {
          failed.push(o);
          return;
        }
      });
      // if some have failed, we will retry with the ol'good img way
      retry = failed.map((o) => {
        return new Promise((resolve, reject) => {
          let img = new Image();
          img.onload = e => {
            URL.revokeObjectURL(img.src);
            o.width = img.width;
            o.height = img.height;
            resolve(o);
          };
          img.onerror = e => {
            URL.revokeObjectURL(img.src);
            reject(o);
          };
          img.src = URL.createObjectURL(o);
        });
      });

      return Promise.all(retry)
        // concatenate the no-exif ones with the exif ones.
        .then(arr => success.concat(arr))
    });
  }

  function getGIFSizes(gifArray) {
    gifArray.forEach(o => {
      let view = new Uint8Array(o.buf);
      o.width = view[6] | view[7] << 8;
      o.height = view[8] | view[9] << 8;
    });
    return gifArray;
  }

  function getBMPSizes(bmpArray) {
    let view;

    function readInt(offset) {
      // I probably have something wrong in here...
      return Math.abs(view[offset] |
        view[offset + 1] << 8 |
        view[offset + 2] << 16 |
        view[offset + 3] << 24
      );
    }
    bmpArray.forEach(o => {
      view = new Uint8Array(o.buf);
      o.meta = {
        width: readInt(18),
        height: readInt(22)
      }
      o.width = o.meta.width;
      o.height = o.meta.height;
    });
    return bmpArray;
  }

  // only based on MIME-type string, to avoid all non-images
  function simpleImageFilter(files) {
    return Promise.resolve(
      Array.prototype.filter.call(files, f => f.type.indexOf('image/') === 0)
    );
  }

  function filterType(list, requestedType) {
    // A more robust MIME-type check
    // see http://stackoverflow.com/questions/18299806/how-to-check-file-mime-type-with-javascript-before-upload
    function getHeader(buf) {
      let type = 'unknown';
      let header = Array.prototype.map.call(
        new Uint8Array(buf.slice(0, 4)),
        v => v.toString(16)
      ).join('')

      switch (header) {
        case "89504e47":
        case "0D0A1A0A":
          type = "image/png";
          break;
        case "47494638":
          type = "image/gif";
          break;
        case "ffd8ffe0":
        case "ffd8ffe1":
        case "ffd8ffe2":
          type = "image/jpeg";
          break;
        default:
          switch (header.substr(0, 4)) {
            case "424d":
              type = 'image/bmp';
              break;
          }
          break;
      }
      return type;
    }

    return Array.prototype.filter.call(
      list,
      o => getHeader(o.buf) === requestedType
    );

  }

  function getSizes(fileArray) {
    return getJPEGSizes(filterType(fileArray, 'image/jpeg'))
      .then(jpegs => {
        let pngs = getPNGSizes(filterType(fileArray, 'image/png'));
        let gifs = getGIFSizes(filterType(fileArray, 'image/gif'));
        let bmps = getBMPSizes(filterType(fileArray, 'image/bmp'));
        return gifs.concat(pngs.concat(bmps.concat(jpegs)));
      });
  }

  return simpleImageFilter(files)
    .then(images => getBuffer(images, 30))
    .then(getSizes);
}


// our callback
function sort(arr) {

  arr.sort(function(a, b) {
    return a.width * a.height - b.width * b.height;
  });

  output.innerHTML = '';
  arr.forEach(f => {
    // ugly table generation
    let t = '<td>',
      tt = '</td>' + t,
      ttt = '</td></tr>';
    output.innerHTML += '<tr>' + t + f.name + tt + f.width + tt + f.height + ttt;
  })
}
f.onchange = e => {
  getImageSizes(f.files)
    .then(sort)
    .catch(e => console.log(e));
  output.innerHTML = '<tr><td colspan="3">Processing, please wait...</td></tr>';
}
table {
  margin-top: 12px;
  border-collapse: collapse;
}

td,
th {
  border: 1px solid #000;
  padding: 2px 6px;
}

tr {
  border: 0;
  margin: 0;
}
<script src="https://rawgit.com/mattiasw/ExifReader/master/dist/exif-reader.js"></script>
<input type="file" id="f" webkitdirectory accepts="image/*">
<table>
  <thead>
    <tr>
      <th>file name</th>
      <th>width</th>
      <th>height</th>
    </tr>
  </thead>
  <tbody id="output">
    <tr>
      <td colspan="3">Please choose a folder to upload</td>
    </tr>
  </tbody>
</table>

【讨论】:

  • 这真是太棒了。它应该可以工作,因为高度和宽度通常在标题中。我还打算做一个 exif 阅读的事情,这样我就可以按标签搜索和排序,所以你用 1 块石头打了 2 只鸟。让我尝试一些事情,然后我会回来并给予适当的信任
  • @Chris,是的......不幸的是,对于 jpeg 的 EXIF,它不一定在标题中,它可以在任何地方,所以我们必须读取整个 arrayBuffer。我通过只为 png 和 MIME 类型检查切割 24 个第一个字节进行了一些改进,然后只有当我们确定它是 jpeg 时,才再次获取整个 arrayBuffer。它仍然可以改进很多,但是我目前没有太多时间给自己,所以我稍后会回来改进。 (在编辑中,我将解析后的 EXIF 元数据附加到文件中,以便您以后可以根据需要使用它)。
  • 谢谢,这正是我想要的。如果你觉得你想添加一些东西,请给我留言,这样我就可以看到有变化,但我对你给我的东西感到满意。我可以用这个
  • @Chris,我添加了对 gif 和 bmp 文件的支持(尽管我没有测试这些格式的所有变体)
  • 再次感谢,这帮助很大:)
【解决方案2】:

您可以在根据您的要求进行排序后使用它来获取图像大小

这是我的例子:如何在加载之前获取图像大小https://jsfiddle.net/mmghori/Lsnc0sr7/

这是你更新的小提琴https://jsfiddle.net/mmghori/ubgLv3cb/,在这个小提琴中我添加了我的代码和控制台图像大小,

所以我只是在加载之前找到如何获取图像大小。

<input type="file" id="file" />

<h4 id="fileSize"></h4>

var _URL = window.URL || window.webkitURL;

    $("#file").change(function(e) {
        var file, img;


        if ((file = this.files[0])) {
            img = new Image();
            img.onload = function() {
                $("#fileSize").text(this.width + "x" + this.height);
            };
            img.onerror = function() {
                alert( "not a valid file: " + file.type);
            };
            img.src = _URL.createObjectURL(file);


        }

    });

【讨论】:

  • 感谢您为这个答案付出的时间和精力。问题是,它与我提到的代码 sn-p 完全相同,不幸的是浏览器根本无法跟上很多文件......它只会开始可怕地失败:GET blob:@987654323 @ net::ERR_INSUFFICIENT_RESOURCES 我认为解决方案可能是慢一点,或者在另一个线程中,比如使用网络工作者或类似的东西
【解决方案3】:

您应该分批处理图像,例如一次 20 个。

在加载图像时,您将每个图像的名称和分辨率存储在一个数组中,然后销毁图像以释放内存

当你得到 20 个 onload 或 onerror 时,你处理下一批。

完成后,对数组进行排序并显示图像。

如果有数百或数千个,则需要对图库进行分页

应该很简单,因为您已经拥有包含所有图像名称和分辨率的数组。

只需为起始偏移量和页面大小设置一些变量,然后对数组进行 slice() 以获得您需要的子集(“当前页面”)

【讨论】:

    【解决方案4】:

    画廊本身非常基础,它要求提供目录 然后将显示该目录中的所有图像和视频。

    这是一个简单的解决方案,如何获取网页上所有图像的大小,所以如果你有简单的照片库之类的东西,从文件夹中获取图像并且你没有任何这些图像的列表,这是最简单的解决方案如何获得它的大小,然后你可以按照你想要的方式处理。

    for( i=0; i < document.images.length; i++)
    { 
      width = document.images[i].width;
      height = document.images[i].height;
      console.log("Image number: " + i + " Size: " + width + "x" + height);
    }
    <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN""http://www.w3.org/TR/html4/strict.dtd">
    <html>
    <head>
    <title>Get image size example</title>
    </head>
    <body>
    <img src="https://upload.wikimedia.org/wikipedia/commons/thumb/f/ff/Solid_blue.svg/225px-Solid_blue.svg.png"/>
    <img src="http://images.mentalfloss.com/sites/default/files/styles/insert_main_wide_image/public/46346365365.png"/>
    </body>
    </html>

    【讨论】:

    • 如果img标签中没有填充图像对象怎么办?
    【解决方案5】:

    这样的东西有帮助吗?

    var imgs = document.getElementsByClassName("imgs");
    
    var height = imgs.clientHeight;
    var width = imgs.clientWidth;
    
    imgs.sort(function(a, b){
      return a.height - b.width;
    });
    

    使用node.js:

    imgs.sort(function(a, b) {
      return fs.statSync(a).size - fs.statSync(b).size;
    });
    

    【讨论】:

    • 与 Kalido 的回答相比,这样做是否更有效率?
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2014-11-20
    • 2017-08-23
    • 1970-01-01
    • 1970-01-01
    • 2011-04-07
    • 1970-01-01
    • 2017-11-26
    相关资源
    最近更新 更多