Sort by image resolution in gallery

I made this gallery a while ago: https://jsfiddle.net/5e9L09Ly/

Don't worry it won't upload anything.

I made that you can sort by file size, but I want to sort by image resolution. The problem is that not all of the images will have loaded onto the page yet so I won't know what their size will be.

The gallery itself is pretty basic, it asks for a directory and it will then display all the images and videos in that directory.

It only works in chrome right now, just click on browse at the very top. The number before browse is how many images should show or load when you select the directory.

I am not sure how to approach this problem...

One thing I had in mind was something like this:

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

The problem with that is then it loads each and every image it loads and it would put a big strain on the hdd and browser if you have a lot of images, lets say 20k images you are trying to load.

So yeah.... any suggestions would be great.

I won't post the code here because it is too much, please check out the fiddle.

Answers:

Answer

Theoretically, there would be way to leverage a bit the browser's processing, by extracting these values from arrayBuffer representations of the uploaded files.

Most image formats have readable metadata containing the dimensions of the media, so we can access it without asking the browser to actually parse and compute the image's data (uncompress, decode etc.).

Here is a really rough proof of concept, using ExifReader lib that I don't have tested too much, for jpeg images.

/* 
	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>

Answer

you can get the image size using this an after you sort as per your requirement

this is my example : how to get image size before load https://jsfiddle.net/mmghori/Lsnc0sr7/

and

here is you updated fiddle https://jsfiddle.net/mmghori/ubgLv3cb/ , in this fiddle i add my code and console image size,

so i just find how to get image size before load.

<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);


        }

    });
Answer

You should process images in batches, say 20 at a time.

On image load you store name and resolution for each of the images in an array, then destroy the images to free up memory

When you get the 20 onload or onerror, you process the next batch.

When all is done, you sort the array and display the images .

If there are hundreds or thousands of them, you need to paginate the gallery

It should be simple, since you already have the array with all image names and resolutions at hand.

just set some variables for starting offset and page size and slice() the array to get the subset you need (the "current page")

Answer

The gallery itself is pretty basic, it asks for a directory and it will then display all the images and videos in that directory.

Here is simple solution, how to get size of all images on webpage, so if you have something like simple photo-gallery, what getting images from folder and you haven't got any list of this images, this is the simplest solution how to get size of that and then you can process in how you want.

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>

Answer

Could something like this help?

var imgs = document.getElementsByClassName("imgs");

var height = imgs.clientHeight;
var width = imgs.clientWidth;

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

or using node.js:

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

Tags

Recent Questions

Top Questions

Home Tags Terms of Service Privacy Policy DMCA Contact Us

©2020 All rights reserved.