Permalink
Browse files

Merge pull request #1585 from davidflanagan/gallery2

Convert gallery app to use DeviceStorage
  • Loading branch information...
2 parents 41dad9e + 0e4fd79 commit 356f65380cb015885a588a815b6791a8e3c9ca69 @fabricedesre fabricedesre committed Jun 5, 2012
Showing with 520 additions and 156 deletions.
  1. +2 −0 Makefile
  2. +5 −5 apps/gallery/index.html
  3. +259 −0 apps/gallery/js/JPEGMetadata.js
  4. +202 −120 apps/gallery/js/gallery.js
  5. BIN apps/gallery/sample_photos/6839255446_2f245d8f0c.jpg
  6. BIN apps/gallery/sample_photos/_MG_0053.jpg
  7. BIN apps/gallery/sample_photos/thumbnails/3548856279_a215152cd5_o.jpg
  8. BIN apps/gallery/sample_photos/thumbnails/3549661880_0c5565a518_o.jpg
  9. BIN apps/gallery/sample_photos/thumbnails/3549662882_8e41d11d28_o.jpg
  10. BIN apps/gallery/sample_photos/thumbnails/3551599565_db282cf840_o.jpg
  11. BIN apps/gallery/sample_photos/thumbnails/6839255446_2f245d8f0c.jpg
  12. BIN apps/gallery/sample_photos/thumbnails/6985376089_db00e0d18c_o.jpg
  13. BIN apps/gallery/sample_photos/thumbnails/DSC_1677.jpg
  14. BIN apps/gallery/sample_photos/thumbnails/DSC_1701.jpg
  15. BIN apps/gallery/sample_photos/thumbnails/DSC_1727.jpg
  16. BIN apps/gallery/sample_photos/thumbnails/DSC_1729.jpg
  17. BIN apps/gallery/sample_photos/thumbnails/DSC_1759.jpg
  18. BIN apps/gallery/sample_photos/thumbnails/DSC_4236.jpg
  19. BIN apps/gallery/sample_photos/thumbnails/DSC_4767.jpg
  20. BIN apps/gallery/sample_photos/thumbnails/DSC_4858.jpg
  21. BIN apps/gallery/sample_photos/thumbnails/DSC_4861.jpg
  22. BIN apps/gallery/sample_photos/thumbnails/DSC_4903.jpg
  23. BIN apps/gallery/sample_photos/thumbnails/DSC_6842.jpg
  24. BIN apps/gallery/sample_photos/thumbnails/DSC_6859.jpg
  25. BIN apps/gallery/sample_photos/thumbnails/DSC_6883.jpg
  26. BIN apps/gallery/sample_photos/thumbnails/IMG_0546.jpg
  27. BIN apps/gallery/sample_photos/thumbnails/IMG_0554.jpg
  28. BIN apps/gallery/sample_photos/thumbnails/IMG_0592.jpg
  29. BIN apps/gallery/sample_photos/thumbnails/IMG_0610.jpg
  30. BIN apps/gallery/sample_photos/thumbnails/IMG_0668.jpg
  31. BIN apps/gallery/sample_photos/thumbnails/IMG_0676.jpg
  32. BIN apps/gallery/sample_photos/thumbnails/IMG_1132.jpg
  33. BIN apps/gallery/sample_photos/thumbnails/IMG_1307.jpg
  34. BIN apps/gallery/sample_photos/thumbnails/IMG_1706.jpg
  35. BIN apps/gallery/sample_photos/thumbnails/IMG_1974.jpg
  36. BIN apps/gallery/sample_photos/thumbnails/IMG_7928.jpg
  37. BIN apps/gallery/sample_photos/thumbnails/IMG_7990.jpg
  38. BIN apps/gallery/sample_photos/thumbnails/IMG_8085.jpg
  39. BIN apps/gallery/sample_photos/thumbnails/IMG_8164.jpg
  40. BIN apps/gallery/sample_photos/thumbnails/IMG_8631.jpg
  41. BIN apps/gallery/sample_photos/thumbnails/IMG_8638.jpg
  42. BIN apps/gallery/sample_photos/thumbnails/IMG_8648.jpg
  43. BIN apps/gallery/sample_photos/thumbnails/IMG_8652.jpg
  44. BIN apps/gallery/sample_photos/thumbnails/P1000115.jpg
  45. BIN apps/gallery/sample_photos/thumbnails/P1000404.jpg
  46. BIN apps/gallery/sample_photos/thumbnails/P1000469.jpg
  47. BIN apps/gallery/sample_photos/thumbnails/P1000486.jpg
  48. BIN apps/gallery/sample_photos/thumbnails/_MG_0053.jpg
  49. +52 −31 apps/gallery/style/gallery.css
  50. BIN {apps/gallery/sample_photos → media-samples/DCIM/archives}/3548856279_a215152cd5_o.jpg
  51. BIN {apps/gallery/sample_photos → media-samples/DCIM/archives}/3549661880_0c5565a518_o.jpg
  52. BIN {apps/gallery/sample_photos → media-samples/DCIM/archives}/3549662882_8e41d11d28_o.jpg
  53. BIN {apps/gallery/sample_photos → media-samples/DCIM/archives}/3551599565_db282cf840_o.jpg
  54. BIN {apps/gallery/sample_photos → media-samples/DCIM/archives}/6985376089_db00e0d18c_o.jpg
  55. BIN {apps/gallery/sample_photos → media-samples/DCIM/travel}/DSC_1677.jpg
  56. BIN {apps/gallery/sample_photos → media-samples/DCIM/travel}/DSC_1701.jpg
  57. BIN {apps/gallery/sample_photos → media-samples/DCIM/travel}/DSC_1727.jpg
  58. BIN {apps/gallery/sample_photos → media-samples/DCIM/travel}/DSC_1729.jpg
  59. BIN {apps/gallery/sample_photos → media-samples/DCIM/travel}/DSC_1759.jpg
  60. BIN {apps/gallery/sample_photos → media-samples/DCIM/travel}/DSC_4236.jpg
  61. BIN {apps/gallery/sample_photos → media-samples/DCIM/travel}/DSC_4767.jpg
  62. BIN {apps/gallery/sample_photos → media-samples/DCIM/travel}/DSC_4858.jpg
  63. BIN {apps/gallery/sample_photos → media-samples/DCIM/travel}/DSC_4861.jpg
  64. BIN {apps/gallery/sample_photos → media-samples/DCIM/travel}/DSC_4903.jpg
  65. BIN {apps/gallery/sample_photos → media-samples/DCIM/travel}/DSC_6842.jpg
  66. BIN {apps/gallery/sample_photos → media-samples/DCIM/travel}/DSC_6859.jpg
  67. BIN {apps/gallery/sample_photos → media-samples/DCIM/travel}/DSC_6883.jpg
  68. BIN {apps/gallery/sample_photos → media-samples/DCIM/travel/france}/IMG_0546.jpg
  69. BIN {apps/gallery/sample_photos → media-samples/DCIM/travel/france}/IMG_0554.jpg
  70. BIN {apps/gallery/sample_photos → media-samples/DCIM/travel/france}/IMG_0592.jpg
  71. BIN {apps/gallery/sample_photos → media-samples/DCIM/travel/france}/IMG_0610.jpg
  72. BIN {apps/gallery/sample_photos → media-samples/DCIM/travel/france}/IMG_0668.jpg
  73. BIN {apps/gallery/sample_photos → media-samples/DCIM/travel/france}/IMG_0676.jpg
  74. BIN {apps/gallery/sample_photos → media-samples/DCIM/travel/france}/IMG_1132.jpg
  75. BIN {apps/gallery/sample_photos → media-samples/DCIM/travel/france}/IMG_1307.jpg
  76. BIN {apps/gallery/sample_photos → media-samples/DCIM/travel/france}/IMG_1706.jpg
  77. BIN {apps/gallery/sample_photos → media-samples/DCIM/travel/france}/IMG_1974.jpg
  78. BIN {apps/gallery/sample_photos → media-samples/DCIM/travel/france}/IMG_7928.jpg
  79. BIN {apps/gallery/sample_photos → media-samples/DCIM/travel/france}/IMG_7990.jpg
  80. BIN {apps/gallery/sample_photos → media-samples/DCIM/travel/france}/IMG_8085.jpg
  81. BIN {apps/gallery/sample_photos → media-samples/DCIM/travel/france}/IMG_8164.jpg
  82. BIN {apps/gallery/sample_photos → media-samples/DCIM/travel/france}/IMG_8631.jpg
  83. BIN {apps/gallery/sample_photos → media-samples/DCIM/travel/france}/IMG_8638.jpg
  84. BIN {apps/gallery/sample_photos → media-samples/DCIM/travel/france}/IMG_8648.jpg
  85. BIN {apps/gallery/sample_photos → media-samples/DCIM/travel/france}/IMG_8652.jpg
  86. BIN {apps/gallery/sample_photos → media-samples/DCIM/travel/greece}/P1000115.jpg
  87. BIN {apps/gallery/sample_photos → media-samples/DCIM/travel/greece}/P1000404.jpg
  88. BIN {apps/gallery/sample_photos → media-samples/DCIM/travel/greece}/P1000469.jpg
  89. BIN {apps/gallery/sample_photos → media-samples/DCIM/travel/greece}/P1000486.jpg
View
@@ -426,3 +426,5 @@ install-gaia: profile
$(ADB) shell kill $(shell $(ADB) shell toolbox ps | grep "b2g" | awk '{ print $$2; }')
@echo 'Rebooting b2g now'
+install-media-samples:
+ $(ADB) push media-samples/DCIM /sdcard/DCIM
View
@@ -22,16 +22,16 @@
document.write('<script src="' + src + '"><\/script>');
</script>
<script defer src="js/GestureDetector.js"></script>
+ <script defer src="js/JPEGMetadata.js"></script>
<script defer src="js/gallery.js"></script>
</head>
<body class="hidden">
- <header id="header">
- <h1 data-l10n-id="pictures">Pictures</h1>
- </header>
-
<!-- Thumbnails inserted here -->
- <ul id="thumbnails"></ul>
+ <ul id="thumbnails">
+ <!-- We'll hide this as soon as we find a photo -->
+ <h1 id="nophotos" data-l10n-id="nophotos">No Photos</h1>
+ </ul>
<!-- Photos inserted here -->
<div id="photos" class="hidden">
@@ -0,0 +1,259 @@
+// Asynchronously read a JPEG Blob (or File), extract its metadata,
+// and pass an object containing selected portions of that metadata
+// to the specified callback function.
+function readJPEGMetadata(blob, callback, errback) {
+ var reader = new FileReader();
+ reader.readAsArrayBuffer(blob);
+ errback = errback || function(o){ console.error("JPEGMetadata", String(o)); };
+ reader.onerror = function() {
+ errback(reader.error);
+ };
+
+ reader.onload = function() {
+ try {
+ var data = new DataView(reader.result);
+ var metadata = parseJPEGMetadata(data);
+ }
+ catch(e) {
+ errback(e);
+ return;
+ }
+ callback(metadata);
+ };
+
+ function parseJPEGMetadata(data) {
+ var metadata = {};
+ if (data.getUint8(0) !== 0xFF || data.getUint8(1) !== 0xD8) {
+ throw Error("Not a JPEG file");
+ }
+
+ var offset = 2;
+
+ // Loop through the segments of the JPEG file
+ while(offset < data.byteLength) {
+ if (data.getUint8(offset++) !== 0xFF)
+ throw Error('malformed JPEG file: missing 0xFF delimiter');
+
+ var segtype = data.getUint8(offset++);
+ var segstart = offset;
+ var seglen = data.getUint16(offset);
+
+ // Basic image segment specifying image size and compression type
+ if (segtype == 0xE0) {
+ // APP0: probably JFIF metadata, which we ignore for now
+ }
+ else if (segtype == 0xE1) {
+ // APP1
+ if (data.getUint8(offset+2) === 0x45 && // E
+ data.getUint8(offset+3) === 0x78 && // x
+ data.getUint8(offset+4) === 0x69 && // i
+ data.getUint8(offset+5) === 0x66 && // f
+ data.getUint8(offset+6) === 0) { // NUL
+ metadata.exif = parseEXIFData(new DataView(data.buffer,
+ data.byteOffset + offset+8,
+ seglen-8));
+
+ if (metadata.exif.THUMBNAIL && metadata.exif.THUMBNAILLENGTH) {
+ var start = offset + 8 + metadata.exif.THUMBNAIL;
+ var end = start + metadata.exif.THUMBNAILLENGTH;
+ var thumbnailBlob = blob.slice(start, end, "image/jpeg");
+ var thumbnailData = new DataView(data.buffer,
+ start,
+ metadata.exif.THUMBNAILLENGTH);
+ metadata.thumbnail = parseJPEGMetadata(thumbnailData);
+ metadata.thumbnail.blob = thumbnailBlob;
+ delete metadata.exif.THUMBNAIL;
+ delete metadata.exif.THUMBNAILLENGTH;
+ }
+ }
+ }
+ else if (segtype >= 0xC0 && segtype <= 0xC3) {
+ metadata.height = data.getUint16(offset+3);
+ metadata.width = data.getUint16(offset+5);
+
+ // Once we've gotten the images size we're done.
+ // the APP0 and APP1 metadata will always come first.
+ break;
+ }
+
+ // Regardless of segment type, skip to the next segment
+ offset += seglen;
+ }
+
+ // Once we've exited the loop, we've gathered all the metadata
+ return metadata;
+ }
+
+
+ // Parse an EXIF segment from a JPEG file and return an object
+ // of metadata attributes. The argument must be a DataView object
+ function parseEXIFData(data) {
+ var exif = {};
+
+ var byteorder = data.getUint8(0);
+ if (byteorder === 0x4D) { // big endian
+ byteorder = false;
+ }
+ else if (byteorder === 0x49) { // little endian
+ byteorder = true;
+ }
+ else {
+ throw Error('invalid byteorder in EXIF segment');
+ }
+
+ if (data.getUint16(2, byteorder) !== 42) // magic number
+ throw Error('bad magic number in EXIF segment');
+
+ var offset = data.getUint32(4, byteorder);
+
+ parseIFD(data, offset, byteorder, exif);
+
+ if (exif.EXIFIFD) {
+ parseIFD(data, exif.EXIFIFD, byteorder, exif);
+ delete exif.EXIFIFD;
+ }
+
+ if (exif.GPSIFD) {
+ parseIFD(data, exif.GPSIFD, byteorder, exif);
+ delete exif.GPSIFD;
+ }
+
+ return exif;
+ }
+
+ function parseIFD(data, offset, byteorder, exif) {
+ var numentries = data.getUint16(offset, byteorder);
+ for(var i = 0; i < numentries; i++) {
+ parseEntry(data, offset + 2 + 12*i, byteorder, exif);
+ }
+
+ var next = data.getUint32(offset + 2 + 12 * numentries, byteorder);
+ if (next !== 0)
+ parseIFD(data, next, byteorder, exif);
+ }
+
+ // size, in bytes, of each TIFF data type
+ var typesize = [
+ 0, // Unused
+ 1, // BYTE
+ 1, // ASCII
+ 2, // SHORT
+ 4, // LONG
+ 8, // RATIONAL
+ 1, // SBYTE
+ 1, // UNDEFINED
+ 2, // SSHORT
+ 4, // SLONG
+ 8, // SRATIONAL
+ 4, // FLOAT
+ 8 // DOUBLE
+ ];
+
+ // This object maps EXIF tag numbers to their names.
+ // Only list the ones we want to bother parsing and returning.
+ // All others will be ignored.
+ var tagnames = {
+ '256': 'ImageWidth',
+ '257': 'ImageHeight',
+ '40962': 'PixelXDimension',
+ '40963': 'PixelYDimension',
+ '306': 'DateTime',
+ '315': 'Artist',
+ '33432': 'Copyright',
+ '36867': 'DateTimeOriginal',
+ '33434': 'ExposureTime',
+ '33437': 'FNumber',
+ '34850': 'ExposureProgram',
+ '34867': 'ISOSpeed',
+ '37377': 'ShutterSpeedValue',
+ '37378': 'ApertureValue',
+ '37379': 'BrightnessValue',
+ '37380': 'ExposureBiasValue',
+ '37382': 'SubjectDistance',
+ '37383': 'MeteringMode',
+ '37384': 'LightSource',
+ '37385': 'Flash',
+ '37386': 'FocalLength',
+ '41986': 'ExposureMode',
+ '41987': 'WhiteBalance',
+ '41991': 'GainControl',
+ '41992': 'Contrast',
+ '41993': 'Saturation',
+ '41994': 'Sharpness',
+ // These are special tags that we handle internally
+ '34665': 'EXIFIFD', // Offset of EXIF data
+ '34853': 'GPSIFD', // Offset of GPS data
+ '513': 'THUMBNAIL', // Offset of thumbnail
+ '514': 'THUMBNAILLENGTH', // Length of thumbnail
+};
+
+ function parseEntry(data, offset, byteorder, exif) {
+ var tag = data.getUint16(offset, byteorder);
+ var tagname = tagnames[tag];
+ var type = data.getUint16(offset+2, byteorder);
+ var count = data.getUint32(offset+4, byteorder);
+
+ if (!tagname) // If we don't know about this tag type, skip it
+ return;
+
+ var total = count * typesize[type];
+ var valueOffset = total <= 4
+ ? offset + 8
+ : data.getUint32(offset+8, byteorder);
+ exif[tagname] = parseValue(data, valueOffset, type, count, byteorder);
+ }
+
+ function parseValue(data, offset, type, count, byteorder) {
+ if (type === 2) { // ASCII string
+ var codes = [];
+ for(var i = 0; i < count-1; i++)
+ codes[i] = data.getUint8(offset + i);
+ return String.fromCharCode.apply(String, codes);
+ }
+ else {
+ if (count == 1)
+ return parseOneValue(data, offset, type, byteorder);
+ else {
+ var values = [];
+ var size = typesize[type];
+ for(var i = 0; i < count; i++) {
+ values[i] = parseOneValue(data, offset + size*i, type, byteorder);
+ }
+ return values;
+ }
+ }
+
+ }
+
+ function parseOneValue(data, offset, type, byteorder) {
+ switch(type) {
+ case 1: // BYTE
+ case 7: // UNDEFINED
+ return data.getUint8(offset);
+ case 2: // ASCII
+ // This case is handed in parseValue
+ return null;
+ case 3: // SHORT
+ return data.getUint16(offset, byteorder);
+ case 4: // LONG
+ return data.getUint32(offset, byteorder);
+ case 5: // RATIONAL
+ return data.getUint32(offset, byteorder) /
+ data.getUint32(offset+4, byteorder);
+ case 6: // SBYTE
+ return data.getInt8(offset);
+ case 8: // SSHORT
+ return data.getInt16(offset, byteorder);
+ case 9: // SLONG
+ return data.getInt32(offset, byteorder);
+ case 10: // SRATIONAL
+ return data.getInt32(offset, byteorder) /
+ data.getInt32(offset+4, byteorder);
+ case 11: // FLOAT
+ return data.getFloat32(offset, byteorder);
+ case 12: // DOUBLE
+ return data.getFloat64(offset, byteorder);
+ }
+ return null;
+ }
+}
Oops, something went wrong.

0 comments on commit 356f653

Please sign in to comment.