Overcomplicating Images

Sometimes a user's "It doesn't works" becomes a story.

This "It doesn't works" was about uploading images on a website. A simple task to achieve, which becomes a little more complex when a live preview is required by the usual "I need images in every page! Big images! Always!" designer. And degenerates in a complete pain in the ass when are involved photos coming from a smartphone.

Somewhere, somehow, someone decided that photos (in particular, those shooted by a smartphone) had not to be saved in the final format by the generating software, but instead had to be saved always in landscape format with an EXIF metadata informing about the correct orientation. What a great idea! Unfortunately, the world was (and is not yet) ready to such a revolutionary approach, and handling the correct orientation still requires harvesting the whole StackOverflow to find any relevant fragment of information.

On the server side, I've quickly solved the issue with this PHP module. A single function reading the metadata and saving the image in the right way: rude enough to like it.

On the client side, I had to smash together a JS library and some copy&pasted code took out of nowhere, and the resulting code is something like:

function previewImage(input, img, angle) {
  var ratio = 400 / img.width;
  var canvas = $("<canvas>")[0];
  canvas.width = img.width * ratio;
  canvas.height = img.height * ratio;
  var ctx = canvas.getContext("2d");

  ctx.save();

  if (angle != 0) {
    ctx.translate(canvas.width / 2, canvas.height / 2);
    ctx.rotate(angle * Math.PI / 180);
  }

  ctx.drawImage(img, -canvas.width/2, -canvas.height/2, canvas.width, canvas.height);

  ctx.restore();

  // This has to be tuned with the desidered selector.
  // Here, I use the image as background of the final element for convenience
  cell.find('.final-image-frame').empty().css('background-image', 'url(' + canvas.toDataURL() + ')');
}

// This has to be tuned with the desired selector
$('body').on('change', 'input:file', function() {
  var input = this;

  if (input.files && input.files[0]) {
    var reader = new FileReader();
    reader.onload = function (e) {
      var img = new Image();
      img.onload = function() {
        var convertExifOrienationToAngle = function (orientation) {
          // This array maps the possible EXIF orientation metadata values to
          // effective degrees
          var exifDegrees = [0, 0, 0, 180, 0, 0, 90, 0, 270];

          if (orientation > 0 && orientation < 9 && exifDegrees[orientation] != 0)
            return exifDegrees[orientation];
          else
            return 0;
        };

        var test = EXIF.getData(img, function() {
          var angle = convertExifOrienationToAngle(EXIF.getTag(img, "Orientation") || 0);
          previewImage(input, img, angle);
        });

        if (test == false) {
          previewImage(input, img, 0);
        }
      }

      img.src = e.target.result;
    }

    reader.readAsDataURL(input.files[0]);
  }
});

Don't ask me the reason of that calculations in previewImage(), I've obtained them trying all permutations of values.