WebKit Photo Picker Bug

iOS 6 WebKit now provides support for file input elements. When choosing a 'file' it allows you to choose a saved photo from the photo library on the device or take a new photo if the device has a camera. iOS doesn't automatically orient photos taken with the camera, but instead stores orientation metadata in the photo that can be accessed in order to orient the photo properly. So this bug is more a mismatch with user expectations. If a photo has been chosen with a file input and you are accessing that photo through the DOM with JavaScript and drawing it to a canvas, the photo will not respect the orientation metadata and you have to do it yourself.

by interstateone

HTML

<script src="http://www.nihilogic.dk/labs/exif/exif.js"></script>
<script src="http://www.nihilogic.dk/labs/binaryajax/binaryajax.js"></script>
<input type="file" capture="camera" accept="image/*">
<p>The image appended to the body</p>
<div class="append"></div>
<p>The image drawn to a &lt;canvas&gt;</p>
<canvas id="normalCanvas"></canvas>
<p>The image drawn to a &lt;canvas&gt; with orientation correction</p>
<canvas id="correctedCanvas"></canvas>

CSS

.append img{
    max-width: 200px;
    max-height: 200px;
}

CoffeeScript

$('input').on 'change', (event) ->
  photo = @files[0]
  reader = new FileReader()
  reader.onloadend = captureOrientation
  reader.readAsBinaryString(photo)

populateCanvas = (data, exif) ->
  cvs = $('#normalCanvas')[0]
  cvs2 = $('#correctedCanvas')[0]
  ctx = cvs.getContext('2d')
  ctx2 = cvs2.getContext('2d')
  img = new Image()
  img.src = 'data:image/*;base64,' + data
  img.onload = ->
    $(img).appendTo('.append')
    cvs2.width = cvs.width = 200
    cvs2.height = cvs.height = 200
    ctx.clearRect(0, 0, cvs.width, cvs.height)
    ctx2.clearRect(0, 0, cvs2.width, cvs2.height)

    # draw with no correction
    ctx.drawImage(img, 0, 0, cvs.width, cvs.height)

    # draw with orientation correction
    if parseInt(exif.Orientation) in [3,6,8]
      ctx2.translate(cvs2.width / 2, cvs2.height / 2)
      switch parseInt(exif.Orientation)
        when 6 then ctx2.rotate(Math.PI / 2)
        when 3 then ctx2.rotate(Math.PI)
        when 8 then ctx2.rotate(Math.Pi * 3/2)
      ctx2.translate(-cvs2.width / 2, -cvs2.height / 2)
    ctx2.drawImage(img, 0, 0, cvs2.width, cvs2.height)

captureOrientation = ->
  exif = EXIF.readFromBinaryFile(new BinaryFile(@result))
  dataURI = base64_encode(@result)
  populateCanvas(dataURI, exif)

base64_encode = (data) ->
  b64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="
  i = 0
  ac = 0
  enc = ""
  tmp_arr = []
  return data unless data
  loop # pack three octets into four hexets
    o1 = data.charCodeAt(i++)
    o2 = data.charCodeAt(i++)
    o3 = data.charCodeAt(i++)
    bits = o1 << 16 | o2 << 8 | o3
    h1 = bits >> 18 & 0x3f
    h2 = bits >> 12 & 0x3f
    h3 = bits >> 6 & 0x3f
    h4 = bits & 0x3f

    # use hexets to index into b64, and append result to encoded string
    tmp_arr[ac++] = b64.charAt(h1) + b64.charAt(h2) + b64.charAt(h3) + b64.charAt(h4)
    break unless i < data.length
  enc = tmp_arr.join("")
  r = data.length % 3
  (if r then enc.slice(0, r - 3) else enc) + "===".slice(r or 3)