8

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.

Chris
  • 987
  • 6
  • 24
  • 1
    `any suggestions would be great` -> cache – Marcin Orlowski Mar 01 '17 at 20:01
  • @MarcinOrlowski Thanks, I haven't thought of that – Chris Mar 01 '17 at 20:01
  • @K3N Thanks, I didn't know documentfragment existed until I googled it now. Seems really cool. I was thinking of using a webworker to slowly load the images into memory and read the width and height and insert the data into my main array and letting me know when it is done... but I am not sure – Chris Mar 01 '17 at 20:09
  • Where are the images from? If you control them, can't you add the height/width into the img tags, and use any of a number of 'lazy load' functions to delay loading the actual images? – sideroxylon Mar 04 '17 at 06:48
  • @sideroxylon Try the jsfiddle. It pulls it from the folder you select, I don't have access to that kind of metadata, I need to somehow calculate the width and height afterwards – Chris Mar 04 '17 at 06:58

5 Answers5

4

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>
Kaiido
  • 123,334
  • 13
  • 219
  • 285
  • This is really awesome. It should work because the height and width is normally in the header. I was also planning on doing an exif reading thing so I can search and order by tags, so you hit 2 birds with 1 stone. Let me try some things then I will come back and give some proper credit – Chris Mar 08 '17 at 16:39
  • @Chris, yes... unfortunately for jpeg's EXIF, it's not necessarily in the header, it can be anywhere, so we have to read the whole arrayBuffer. I improved a bit by slicing only 24 first bytes for png and MIME type check, then only if we're sure it's a jpeg, get again the whole arrayBuffer. It can still be improved a lot, but I currently don't have much time for myself, so I'll come back with improvements later. (In the edit, I do attach the parsed EXIF metadata on the files, so you can use it later if needed). – Kaiido Mar 09 '17 at 07:08
  • Thank you this is exactly what I wanted. If you feel you want to add things just drop me a comment so I can see there were changes, but I am happy with what you gave me. I can work with this – Chris Mar 09 '17 at 11:50
  • @Chris, I added support for gif and bmp files (though I didn't test all variations of these formats) – Kaiido Mar 12 '17 at 07:24
  • Thanks again, this helped so much :) – Chris Mar 19 '17 at 22:27
0

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


        }

    });
user7348450
  • 281
  • 1
  • 3
  • 9
  • I appreciate the time and effort you put into this answer. The problem is, it is exactly the same as the code snippet I mentioned, and unfortunately the browser simply can't keep up with a lot of files... it will just start to fail horribly with: GET blob:https://fiddle.jshell.net/d91994b6-ce50-4da5-a61d-b2e5cd093f46 net::ERR_INSUFFICIENT_RESOURCES I think the solution might be to do it slower or in another thread like with a web worker or something similar – Chris Mar 04 '17 at 07:33
0

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

Tudor Ilisoi
  • 2,934
  • 23
  • 25
0

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>
Samuel Tulach
  • 1,319
  • 13
  • 38
0

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;
});
Dr Upvote
  • 8,023
  • 24
  • 91
  • 204