Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(143)

Side by Side Diff: ui/file_manager/gallery/js/image_editor/exif_encoder.js

Issue 936143003: Start to use new metadata item in ImageEncoder. (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Created 5 years, 9 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
1 // Copyright 2014 The Chromium Authors. All rights reserved. 1 // Copyright 2014 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be 2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file. 3 // found in the LICENSE file.
4 4
5 /** 5 /**
6 * The Exif metadata encoder. 6 * The Exif metadata encoder.
7 * Uses the metadata format as defined by ExifParser. 7 * Uses the metadata format as defined by ExifParser.
8 * @param {!Object} originalMetadata Metadata to encode. 8 * @param {!MetadataItem} originalMetadata Metadata to encode.
9 * @constructor 9 * @constructor
10 * @extends {ImageEncoder.MetadataEncoder} 10 * @extends {ImageEncoder.MetadataEncoder}
11 * @struct 11 * @struct
12 */ 12 */
13 function ExifEncoder(originalMetadata) { 13 function ExifEncoder(originalMetadata) {
14 ImageEncoder.MetadataEncoder.apply(this, arguments); 14 ImageEncoder.MetadataEncoder.apply(this, arguments);
15 /**
16 * Image File Directory obtained from EXIF header.
17 * @type {!Object}
yawano 2015/02/25 09:19:10 nit: @type -> @private
hirono 2015/02/25 13:28:37 Done.
18 */
19 this.ifd_ = /** @type {!Object} */(
20 JSON.parse(JSON.stringify(originalMetadata.ifd || {})));
15 21
16 if (this.metadata_.media && this.metadata_.media.ifd) 22 /**
17 this.ifd_ = this.metadata_.media.ifd; 23 * Note use little endian if the original metadata does not have the
18 else 24 * information.
19 this.ifd_ = {}; 25 * @type {boolean}
26 */
27 this.exifLittleEndian_ = !!originalMetadata.exifLittleEndian;
20 } 28 }
21 29
22 ExifEncoder.prototype = {__proto__: ImageEncoder.MetadataEncoder.prototype}; 30 ExifEncoder.prototype = {__proto__: ImageEncoder.MetadataEncoder.prototype};
23 31
24 ImageEncoder.registerMetadataEncoder(ExifEncoder, 'image/jpeg'); 32 ImageEncoder.registerMetadataEncoder(ExifEncoder, 'image/jpeg');
25 33
26 /** 34 /**
27 * Software name of Gallery.app. 35 * Software name of Gallery.app.
28 * @type {string} 36 * @type {string}
29 * @const 37 * @const
30 */ 38 */
31 ExifEncoder.SOFTWARE = 'Chrome OS Gallery App\0'; 39 ExifEncoder.SOFTWARE = 'Chrome OS Gallery App\0';
32 40
33 /** 41 /**
34 * @param {!HTMLCanvasElement} canvas 42 * @param {!HTMLCanvasElement} canvas
35 * @param {Date=} opt_modificationDateTime
36 * @override 43 * @override
37 */ 44 */
38 ExifEncoder.prototype.setImageData = 45 ExifEncoder.prototype.setImageData = function(canvas) {
39 function(canvas, opt_modificationDateTime) { 46 ImageEncoder.MetadataEncoder.prototype.setImageData.call(this, canvas);
47
40 var image = this.ifd_.image; 48 var image = this.ifd_.image;
41 if (!image) 49 if (!image)
42 image = this.ifd_.image = {}; 50 image = this.ifd_.image = {};
43 51
44 // Only update width/height in this directory if they are present. 52 // Only update width/height in this directory if they are present.
45 if (image[Exif.Tag.IMAGE_WIDTH] && image[Exif.Tag.IMAGE_HEIGHT]) { 53 if (image[Exif.Tag.IMAGE_WIDTH] && image[Exif.Tag.IMAGE_HEIGHT]) {
46 image[Exif.Tag.IMAGE_WIDTH].value = canvas.width; 54 image[Exif.Tag.IMAGE_WIDTH].value = canvas.width;
47 image[Exif.Tag.IMAGE_HEIGHT].value = canvas.height; 55 image[Exif.Tag.IMAGE_HEIGHT].value = canvas.height;
48 } 56 }
49 57
50 var exif = this.ifd_.exif; 58 var exif = this.ifd_.exif;
51 if (!exif) 59 if (!exif)
52 exif = this.ifd_.exif = {}; 60 exif = this.ifd_.exif = {};
53 ExifEncoder.findOrCreateTag(image, Exif.Tag.EXIFDATA); 61 ExifEncoder.findOrCreateTag(image, Exif.Tag.EXIFDATA);
54 ExifEncoder.findOrCreateTag(exif, Exif.Tag.X_DIMENSION).value = canvas.width; 62 ExifEncoder.findOrCreateTag(exif, Exif.Tag.X_DIMENSION).value = canvas.width;
55 ExifEncoder.findOrCreateTag(exif, Exif.Tag.Y_DIMENSION).value = canvas.height; 63 ExifEncoder.findOrCreateTag(exif, Exif.Tag.Y_DIMENSION).value = canvas.height;
56 64
57 this.metadata_.width = canvas.width;
58 this.metadata_.height = canvas.height;
59
60 // Always save in default orientation. 65 // Always save in default orientation.
61 delete this.metadata_['imageTransform'];
62 ExifEncoder.findOrCreateTag(image, Exif.Tag.ORIENTATION).value = 1; 66 ExifEncoder.findOrCreateTag(image, Exif.Tag.ORIENTATION).value = 1;
63 67
64 // Update software name. 68 // Update software name.
65 var softwareTag = ExifEncoder.findOrCreateTag(image, Exif.Tag.SOFTWARE, 2); 69 var softwareTag = ExifEncoder.findOrCreateTag(image, Exif.Tag.SOFTWARE, 2);
66 softwareTag.value = ExifEncoder.SOFTWARE; 70 softwareTag.value = ExifEncoder.SOFTWARE;
67 softwareTag.componentCount = ExifEncoder.SOFTWARE.length; 71 softwareTag.componentCount = ExifEncoder.SOFTWARE.length;
68 72
69 // Update modification date time. 73 // Update modification date time.
70 var padNumWithZero = function(num, length) { 74 var padNumWithZero = function(num, length) {
71 var str = num.toString(); 75 var str = num.toString();
72 while (str.length < length) { 76 while (str.length < length) {
73 str = '0' + str; 77 str = '0' + str;
74 } 78 }
75 return str; 79 return str;
76 }; 80 };
77 81
78 var modificationDateTime = opt_modificationDateTime || new Date(); 82 var modificationDateTime = new Date();
79 var dateTimeTag = ExifEncoder.findOrCreateTag(image, Exif.Tag.DATETIME, 2); 83 var dateTimeTag = ExifEncoder.findOrCreateTag(image, Exif.Tag.DATETIME, 2);
80 dateTimeTag.value = 84 dateTimeTag.value =
81 padNumWithZero(modificationDateTime.getFullYear(), 4) + ':' + 85 padNumWithZero(modificationDateTime.getFullYear(), 4) + ':' +
82 padNumWithZero(modificationDateTime.getMonth() + 1, 2) + ':' + 86 padNumWithZero(modificationDateTime.getMonth() + 1, 2) + ':' +
83 padNumWithZero(modificationDateTime.getDate(), 2) + ' ' + 87 padNumWithZero(modificationDateTime.getDate(), 2) + ' ' +
84 padNumWithZero(modificationDateTime.getHours(), 2) + ':' + 88 padNumWithZero(modificationDateTime.getHours(), 2) + ':' +
85 padNumWithZero(modificationDateTime.getMinutes(), 2) + ':' + 89 padNumWithZero(modificationDateTime.getMinutes(), 2) + ':' +
86 padNumWithZero(modificationDateTime.getSeconds(), 2) + '\0'; 90 padNumWithZero(modificationDateTime.getSeconds(), 2) + '\0';
87 dateTimeTag.componentCount = 20; 91 dateTimeTag.componentCount = 20;
88 }; 92 };
89 93
90 /** 94 /**
91 * @override 95 * @override
92 */ 96 */
93 ExifEncoder.prototype.setThumbnailData = function(canvas, quality) { 97 ExifEncoder.prototype.setThumbnailData = function(canvas, quality) {
94 // Empirical formula with reasonable behavior: 98 if (canvas) {
95 // 10K for 1Mpix, 30K for 5Mpix, 50K for 9Mpix and up. 99 // Empirical formula with reasonable behavior:
96 var pixelCount = this.metadata_.width * this.metadata_.height; 100 // 10K for 1Mpix, 30K for 5Mpix, 50K for 9Mpix and up.
97 var maxEncodedSize = 5000 * Math.min(10, 1 + pixelCount / 1000000); 101 var pixelCount = this.imageWidth * this.imageHeight;
98 102 var maxEncodedSize = 5000 * Math.min(10, 1 + pixelCount / 1000000);
99 var DATA_URL_PREFIX = 'data:' + this.metadata_.media.mimeType + ';base64,'; 103 var DATA_URL_PREFIX = 'data:image/jpeg;base64,';
100 var BASE64_BLOAT = 4 / 3; 104 var BASE64_BLOAT = 4 / 3;
101 var maxDataURLLength = 105 var maxDataURLLength =
102 DATA_URL_PREFIX.length + Math.ceil(maxEncodedSize * BASE64_BLOAT); 106 DATA_URL_PREFIX.length + Math.ceil(maxEncodedSize * BASE64_BLOAT);
103 107 for (; quality > 0.2; quality *= 0.8) {
104 for (;; quality *= 0.8) { 108 ImageEncoder.MetadataEncoder.prototype.setThumbnailData.call(
105 ImageEncoder.MetadataEncoder.prototype.setThumbnailData.call( 109 this, canvas, quality);
106 this, canvas, quality); 110 // If the obtained thumbnail URL is too long, reset the URL and try again
107 if (this.metadata_.thumbnailURL.length <= maxDataURLLength || quality < 0.2) 111 // with less quality value.
112 if (this.thumbnailDataUrl.length > maxDataURLLength) {
113 this.thumbnailDataUrl = '';
114 continue;
115 }
108 break; 116 break;
117 }
109 } 118 }
110 119 if (this.thumbnailDataUrl) {
111 if (canvas && this.metadata_.thumbnailURL.length <= maxDataURLLength) {
112 var thumbnail = this.ifd_.thumbnail; 120 var thumbnail = this.ifd_.thumbnail;
113 if (!thumbnail) 121 if (!thumbnail)
114 thumbnail = this.ifd_.thumbnail = {}; 122 thumbnail = this.ifd_.thumbnail = {};
115 123
116 ExifEncoder.findOrCreateTag(thumbnail, Exif.Tag.IMAGE_WIDTH).value = 124 ExifEncoder.findOrCreateTag(thumbnail, Exif.Tag.IMAGE_WIDTH).value =
117 canvas.width; 125 canvas.width;
118 126
119 ExifEncoder.findOrCreateTag(thumbnail, Exif.Tag.IMAGE_HEIGHT).value = 127 ExifEncoder.findOrCreateTag(thumbnail, Exif.Tag.IMAGE_HEIGHT).value =
120 canvas.height; 128 canvas.height;
121 129
122 // The values for these tags will be set in ExifWriter.encode. 130 // The values for these tags will be set in ExifWriter.encode.
123 ExifEncoder.findOrCreateTag(thumbnail, Exif.Tag.JPG_THUMB_OFFSET); 131 ExifEncoder.findOrCreateTag(thumbnail, Exif.Tag.JPG_THUMB_OFFSET);
124 ExifEncoder.findOrCreateTag(thumbnail, Exif.Tag.JPG_THUMB_LENGTH); 132 ExifEncoder.findOrCreateTag(thumbnail, Exif.Tag.JPG_THUMB_LENGTH);
125 133
126 // Always save in default orientation. 134 // Always save in default orientation.
127 ExifEncoder.findOrCreateTag(thumbnail, Exif.Tag.ORIENTATION).value = 1; 135 ExifEncoder.findOrCreateTag(thumbnail, Exif.Tag.ORIENTATION).value = 1;
128 136
129 // When thumbnail is compressed with JPEG, compression must be set as 6. 137 // When thumbnail is compressed with JPEG, compression must be set as 6.
130 ExifEncoder.findOrCreateTag(this.ifd_.image, Exif.Tag.COMPRESSION).value = 138 ExifEncoder.findOrCreateTag(this.ifd_.image, Exif.Tag.COMPRESSION).value =
131 6; 139 6;
132 } else { 140 } else {
133 console.warn( 141 if (this.ifd_.thumbnail)
134 'Thumbnail URL too long: ' + this.metadata_.thumbnailURL.length);
135 // Delete thumbnail ifd so that it is not written out to a file, but
136 // keep thumbnailURL for display purposes.
137 if (this.ifd_.thumbnail) {
138 delete this.ifd_.thumbnail; 142 delete this.ifd_.thumbnail;
139 }
140 } 143 }
141 }; 144 };
142 145
143 /** 146 /**
144 * @override 147 * @override
145 */ 148 */
146 ExifEncoder.prototype.findInsertionRange = function(encodedImage) { 149 ExifEncoder.prototype.findInsertionRange = function(encodedImage) {
147 function getWord(pos) { 150 function getWord(pos) {
148 if (pos + 2 > encodedImage.length) 151 if (pos + 2 > encodedImage.length)
149 throw 'Reading past the buffer end @' + pos; 152 throw 'Reading past the buffer end @' + pos;
(...skipping 50 matching lines...) Expand 10 before | Expand all | Expand 10 after
200 var hw = new ByteWriter(bytes.buffer, 0, HEADER_SIZE); 203 var hw = new ByteWriter(bytes.buffer, 0, HEADER_SIZE);
201 hw.writeScalar(Exif.Mark.EXIF, 2); 204 hw.writeScalar(Exif.Mark.EXIF, 2);
202 hw.forward('size', 2); 205 hw.forward('size', 2);
203 hw.writeString('Exif\0\0'); // Magic string. 206 hw.writeString('Exif\0\0'); // Magic string.
204 207
205 // First serialize the content of the exif section. 208 // First serialize the content of the exif section.
206 // Use a ByteWriter starting at HEADER_SIZE offset so that tell() positions 209 // Use a ByteWriter starting at HEADER_SIZE offset so that tell() positions
207 // can be directly mapped to offsets as encoded in the dictionaries. 210 // can be directly mapped to offsets as encoded in the dictionaries.
208 var bw = new ByteWriter(bytes.buffer, HEADER_SIZE); 211 var bw = new ByteWriter(bytes.buffer, HEADER_SIZE);
209 212
210 if (this.metadata_.littleEndian) { 213 if (this.exifLittleEndian_) {
211 bw.setByteOrder(ByteWriter.ByteOrder.LITTLE_ENDIAN); 214 bw.setByteOrder(ByteWriter.ByteOrder.LITTLE_ENDIAN);
212 bw.writeScalar(Exif.Align.LITTLE, 2); 215 bw.writeScalar(Exif.Align.LITTLE, 2);
213 } else { 216 } else {
214 bw.setByteOrder(ByteWriter.ByteOrder.BIG_ENDIAN); 217 bw.setByteOrder(ByteWriter.ByteOrder.BIG_ENDIAN);
215 bw.writeScalar(Exif.Align.BIG, 2); 218 bw.writeScalar(Exif.Align.BIG, 2);
216 } 219 }
217 220
218 bw.writeScalar(Exif.Tag.TIFF, 2); 221 bw.writeScalar(Exif.Tag.TIFF, 2);
219 222
220 bw.forward('image-dir', 4); // The pointer should point right after itself. 223 bw.forward('image-dir', 4); // The pointer should point right after itself.
(...skipping 18 matching lines...) Expand all
239 throw new Error('Missing gps dictionary reference'); 242 throw new Error('Missing gps dictionary reference');
240 } 243 }
241 244
242 if (this.ifd_.thumbnail) { 245 if (this.ifd_.thumbnail) {
243 bw.resolveOffset('thumb-dir'); 246 bw.resolveOffset('thumb-dir');
244 ExifEncoder.encodeDirectory( 247 ExifEncoder.encodeDirectory(
245 bw, 248 bw,
246 this.ifd_.thumbnail, 249 this.ifd_.thumbnail,
247 [Exif.Tag.JPG_THUMB_OFFSET, Exif.Tag.JPG_THUMB_LENGTH]); 250 [Exif.Tag.JPG_THUMB_OFFSET, Exif.Tag.JPG_THUMB_LENGTH]);
248 251
249 var thumbnailDecoded = 252 var thumbnailDecoded = ImageEncoder.decodeDataURL(this.thumbnailDataUrl);
250 ImageEncoder.decodeDataURL(this.metadata_.thumbnailURL);
251 bw.resolveOffset(Exif.Tag.JPG_THUMB_OFFSET); 253 bw.resolveOffset(Exif.Tag.JPG_THUMB_OFFSET);
252 bw.resolve(Exif.Tag.JPG_THUMB_LENGTH, thumbnailDecoded.length); 254 bw.resolve(Exif.Tag.JPG_THUMB_LENGTH, thumbnailDecoded.length);
253 bw.writeString(thumbnailDecoded); 255 bw.writeString(thumbnailDecoded);
254 } else { 256 } else {
255 bw.resolve('thumb-dir', 0); 257 bw.resolve('thumb-dir', 0);
256 } 258 }
257 259
258 bw.checkResolved(); 260 bw.checkResolved();
259 261
260 var totalSize = HEADER_SIZE + bw.tell(); 262 var totalSize = HEADER_SIZE + bw.tell();
(...skipping 329 matching lines...) Expand 10 before | Expand all | Expand 10 after
590 * @param {!(string|Exif.Tag)} key A key. 592 * @param {!(string|Exif.Tag)} key A key.
591 * @return {string} Formatted representation. 593 * @return {string} Formatted representation.
592 */ 594 */
593 ByteWriter.prettyKeyFormat = function(key) { 595 ByteWriter.prettyKeyFormat = function(key) {
594 if (typeof key === 'number') { 596 if (typeof key === 'number') {
595 return '0x' + key.toString(16); 597 return '0x' + key.toString(16);
596 } else { 598 } else {
597 return key; 599 return key;
598 } 600 }
599 }; 601 };
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698