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

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: Fix test Created 5 years, 10 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 * @private {!Object}
18 * @const
19 */
20 this.ifd_ = /** @type {!Object} */(
21 JSON.parse(JSON.stringify(originalMetadata.ifd || {})));
15 22
16 if (this.metadata_.media && this.metadata_.media.ifd) 23 /**
17 this.ifd_ = this.metadata_.media.ifd; 24 * Note use little endian if the original metadata does not have the
18 else 25 * information.
19 this.ifd_ = {}; 26 * @private {boolean}
27 * @const
28 */
29 this.exifLittleEndian_ = !!originalMetadata.exifLittleEndian;
30
31 /**
32 * Modification time to be stored in EXIF header.
33 * @private {!Date}
34 * @const
35 */
36 this.modificationTime_ = assert(originalMetadata.modificationTime);
20 } 37 }
21 38
22 ExifEncoder.prototype = {__proto__: ImageEncoder.MetadataEncoder.prototype}; 39 ExifEncoder.prototype = {__proto__: ImageEncoder.MetadataEncoder.prototype};
23 40
24 ImageEncoder.registerMetadataEncoder(ExifEncoder, 'image/jpeg'); 41 ImageEncoder.registerMetadataEncoder(ExifEncoder, 'image/jpeg');
25 42
26 /** 43 /**
27 * Software name of Gallery.app. 44 * Software name of Gallery.app.
28 * @type {string} 45 * @type {string}
29 * @const 46 * @const
30 */ 47 */
31 ExifEncoder.SOFTWARE = 'Chrome OS Gallery App\0'; 48 ExifEncoder.SOFTWARE = 'Chrome OS Gallery App\0';
32 49
33 /** 50 /**
34 * @param {!HTMLCanvasElement} canvas 51 * @param {!HTMLCanvasElement} canvas
35 * @param {Date=} opt_modificationDateTime
36 * @override 52 * @override
37 */ 53 */
38 ExifEncoder.prototype.setImageData = 54 ExifEncoder.prototype.setImageData = function(canvas) {
39 function(canvas, opt_modificationDateTime) { 55 ImageEncoder.MetadataEncoder.prototype.setImageData.call(this, canvas);
56
40 var image = this.ifd_.image; 57 var image = this.ifd_.image;
41 if (!image) 58 if (!image)
42 image = this.ifd_.image = {}; 59 image = this.ifd_.image = {};
43 60
44 // Only update width/height in this directory if they are present. 61 // Only update width/height in this directory if they are present.
45 if (image[Exif.Tag.IMAGE_WIDTH] && image[Exif.Tag.IMAGE_HEIGHT]) { 62 if (image[Exif.Tag.IMAGE_WIDTH] && image[Exif.Tag.IMAGE_HEIGHT]) {
46 image[Exif.Tag.IMAGE_WIDTH].value = canvas.width; 63 image[Exif.Tag.IMAGE_WIDTH].value = canvas.width;
47 image[Exif.Tag.IMAGE_HEIGHT].value = canvas.height; 64 image[Exif.Tag.IMAGE_HEIGHT].value = canvas.height;
48 } 65 }
49 66
50 var exif = this.ifd_.exif; 67 var exif = this.ifd_.exif;
51 if (!exif) 68 if (!exif)
52 exif = this.ifd_.exif = {}; 69 exif = this.ifd_.exif = {};
53 ExifEncoder.findOrCreateTag(image, Exif.Tag.EXIFDATA); 70 ExifEncoder.findOrCreateTag(image, Exif.Tag.EXIFDATA);
54 ExifEncoder.findOrCreateTag(exif, Exif.Tag.X_DIMENSION).value = canvas.width; 71 ExifEncoder.findOrCreateTag(exif, Exif.Tag.X_DIMENSION).value = canvas.width;
55 ExifEncoder.findOrCreateTag(exif, Exif.Tag.Y_DIMENSION).value = canvas.height; 72 ExifEncoder.findOrCreateTag(exif, Exif.Tag.Y_DIMENSION).value = canvas.height;
56 73
57 this.metadata_.width = canvas.width;
58 this.metadata_.height = canvas.height;
59
60 // Always save in default orientation. 74 // Always save in default orientation.
61 delete this.metadata_['imageTransform'];
62 ExifEncoder.findOrCreateTag(image, Exif.Tag.ORIENTATION).value = 1; 75 ExifEncoder.findOrCreateTag(image, Exif.Tag.ORIENTATION).value = 1;
63 76
64 // Update software name. 77 // Update software name.
65 var softwareTag = ExifEncoder.findOrCreateTag(image, Exif.Tag.SOFTWARE, 2); 78 var softwareTag = ExifEncoder.findOrCreateTag(image, Exif.Tag.SOFTWARE, 2);
66 softwareTag.value = ExifEncoder.SOFTWARE; 79 softwareTag.value = ExifEncoder.SOFTWARE;
67 softwareTag.componentCount = ExifEncoder.SOFTWARE.length; 80 softwareTag.componentCount = ExifEncoder.SOFTWARE.length;
68 81
69 // Update modification date time. 82 // Update modification date time.
70 var padNumWithZero = function(num, length) { 83 var padNumWithZero = function(num, length) {
71 var str = num.toString(); 84 var str = num.toString();
72 while (str.length < length) { 85 while (str.length < length) {
73 str = '0' + str; 86 str = '0' + str;
74 } 87 }
75 return str; 88 return str;
76 }; 89 };
77 90
78 var modificationDateTime = opt_modificationDateTime || new Date();
79 var dateTimeTag = ExifEncoder.findOrCreateTag(image, Exif.Tag.DATETIME, 2); 91 var dateTimeTag = ExifEncoder.findOrCreateTag(image, Exif.Tag.DATETIME, 2);
80 dateTimeTag.value = 92 dateTimeTag.value =
81 padNumWithZero(modificationDateTime.getFullYear(), 4) + ':' + 93 padNumWithZero(this.modificationTime_.getFullYear(), 4) + ':' +
82 padNumWithZero(modificationDateTime.getMonth() + 1, 2) + ':' + 94 padNumWithZero(this.modificationTime_.getMonth() + 1, 2) + ':' +
83 padNumWithZero(modificationDateTime.getDate(), 2) + ' ' + 95 padNumWithZero(this.modificationTime_.getDate(), 2) + ' ' +
84 padNumWithZero(modificationDateTime.getHours(), 2) + ':' + 96 padNumWithZero(this.modificationTime_.getHours(), 2) + ':' +
85 padNumWithZero(modificationDateTime.getMinutes(), 2) + ':' + 97 padNumWithZero(this.modificationTime_.getMinutes(), 2) + ':' +
86 padNumWithZero(modificationDateTime.getSeconds(), 2) + '\0'; 98 padNumWithZero(this.modificationTime_.getSeconds(), 2) + '\0';
87 dateTimeTag.componentCount = 20; 99 dateTimeTag.componentCount = 20;
88 }; 100 };
89 101
90 /** 102 /**
91 * @override 103 * @override
92 */ 104 */
93 ExifEncoder.prototype.setThumbnailData = function(canvas, quality) { 105 ExifEncoder.prototype.setThumbnailData = function(canvas, quality) {
94 // Empirical formula with reasonable behavior: 106 if (canvas) {
95 // 10K for 1Mpix, 30K for 5Mpix, 50K for 9Mpix and up. 107 // Empirical formula with reasonable behavior:
96 var pixelCount = this.metadata_.width * this.metadata_.height; 108 // 10K for 1Mpix, 30K for 5Mpix, 50K for 9Mpix and up.
97 var maxEncodedSize = 5000 * Math.min(10, 1 + pixelCount / 1000000); 109 var pixelCount = this.imageWidth * this.imageHeight;
98 110 var maxEncodedSize = 5000 * Math.min(10, 1 + pixelCount / 1000000);
99 var DATA_URL_PREFIX = 'data:' + this.metadata_.media.mimeType + ';base64,'; 111 var DATA_URL_PREFIX = 'data:image/jpeg;base64,';
100 var BASE64_BLOAT = 4 / 3; 112 var BASE64_BLOAT = 4 / 3;
101 var maxDataURLLength = 113 var maxDataURLLength =
102 DATA_URL_PREFIX.length + Math.ceil(maxEncodedSize * BASE64_BLOAT); 114 DATA_URL_PREFIX.length + Math.ceil(maxEncodedSize * BASE64_BLOAT);
103 115 for (; quality > 0.2; quality *= 0.8) {
104 for (;; quality *= 0.8) { 116 ImageEncoder.MetadataEncoder.prototype.setThumbnailData.call(
105 ImageEncoder.MetadataEncoder.prototype.setThumbnailData.call( 117 this, canvas, quality);
106 this, canvas, quality); 118 // If the obtained thumbnail URL is too long, reset the URL and try again
107 if (this.metadata_.thumbnailURL.length <= maxDataURLLength || quality < 0.2) 119 // with less quality value.
120 if (this.thumbnailDataUrl.length > maxDataURLLength) {
121 this.thumbnailDataUrl = '';
122 continue;
123 }
108 break; 124 break;
125 }
109 } 126 }
110 127 if (this.thumbnailDataUrl) {
111 if (canvas && this.metadata_.thumbnailURL.length <= maxDataURLLength) {
112 var thumbnail = this.ifd_.thumbnail; 128 var thumbnail = this.ifd_.thumbnail;
113 if (!thumbnail) 129 if (!thumbnail)
114 thumbnail = this.ifd_.thumbnail = {}; 130 thumbnail = this.ifd_.thumbnail = {};
115 131
116 ExifEncoder.findOrCreateTag(thumbnail, Exif.Tag.IMAGE_WIDTH).value = 132 ExifEncoder.findOrCreateTag(thumbnail, Exif.Tag.IMAGE_WIDTH).value =
117 canvas.width; 133 canvas.width;
118 134
119 ExifEncoder.findOrCreateTag(thumbnail, Exif.Tag.IMAGE_HEIGHT).value = 135 ExifEncoder.findOrCreateTag(thumbnail, Exif.Tag.IMAGE_HEIGHT).value =
120 canvas.height; 136 canvas.height;
121 137
122 // The values for these tags will be set in ExifWriter.encode. 138 // The values for these tags will be set in ExifWriter.encode.
123 ExifEncoder.findOrCreateTag(thumbnail, Exif.Tag.JPG_THUMB_OFFSET); 139 ExifEncoder.findOrCreateTag(thumbnail, Exif.Tag.JPG_THUMB_OFFSET);
124 ExifEncoder.findOrCreateTag(thumbnail, Exif.Tag.JPG_THUMB_LENGTH); 140 ExifEncoder.findOrCreateTag(thumbnail, Exif.Tag.JPG_THUMB_LENGTH);
125 141
126 // Always save in default orientation. 142 // Always save in default orientation.
127 ExifEncoder.findOrCreateTag(thumbnail, Exif.Tag.ORIENTATION).value = 1; 143 ExifEncoder.findOrCreateTag(thumbnail, Exif.Tag.ORIENTATION).value = 1;
128 144
129 // When thumbnail is compressed with JPEG, compression must be set as 6. 145 // When thumbnail is compressed with JPEG, compression must be set as 6.
130 ExifEncoder.findOrCreateTag(this.ifd_.image, Exif.Tag.COMPRESSION).value = 146 ExifEncoder.findOrCreateTag(this.ifd_.image, Exif.Tag.COMPRESSION).value =
131 6; 147 6;
132 } else { 148 } else {
133 console.warn( 149 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; 150 delete this.ifd_.thumbnail;
139 }
140 } 151 }
141 }; 152 };
142 153
143 /** 154 /**
144 * @override 155 * @override
145 */ 156 */
146 ExifEncoder.prototype.findInsertionRange = function(encodedImage) { 157 ExifEncoder.prototype.findInsertionRange = function(encodedImage) {
147 function getWord(pos) { 158 function getWord(pos) {
148 if (pos + 2 > encodedImage.length) 159 if (pos + 2 > encodedImage.length)
149 throw 'Reading past the buffer end @' + pos; 160 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); 211 var hw = new ByteWriter(bytes.buffer, 0, HEADER_SIZE);
201 hw.writeScalar(Exif.Mark.EXIF, 2); 212 hw.writeScalar(Exif.Mark.EXIF, 2);
202 hw.forward('size', 2); 213 hw.forward('size', 2);
203 hw.writeString('Exif\0\0'); // Magic string. 214 hw.writeString('Exif\0\0'); // Magic string.
204 215
205 // First serialize the content of the exif section. 216 // First serialize the content of the exif section.
206 // Use a ByteWriter starting at HEADER_SIZE offset so that tell() positions 217 // Use a ByteWriter starting at HEADER_SIZE offset so that tell() positions
207 // can be directly mapped to offsets as encoded in the dictionaries. 218 // can be directly mapped to offsets as encoded in the dictionaries.
208 var bw = new ByteWriter(bytes.buffer, HEADER_SIZE); 219 var bw = new ByteWriter(bytes.buffer, HEADER_SIZE);
209 220
210 if (this.metadata_.littleEndian) { 221 if (this.exifLittleEndian_) {
211 bw.setByteOrder(ByteWriter.ByteOrder.LITTLE_ENDIAN); 222 bw.setByteOrder(ByteWriter.ByteOrder.LITTLE_ENDIAN);
212 bw.writeScalar(Exif.Align.LITTLE, 2); 223 bw.writeScalar(Exif.Align.LITTLE, 2);
213 } else { 224 } else {
214 bw.setByteOrder(ByteWriter.ByteOrder.BIG_ENDIAN); 225 bw.setByteOrder(ByteWriter.ByteOrder.BIG_ENDIAN);
215 bw.writeScalar(Exif.Align.BIG, 2); 226 bw.writeScalar(Exif.Align.BIG, 2);
216 } 227 }
217 228
218 bw.writeScalar(Exif.Tag.TIFF, 2); 229 bw.writeScalar(Exif.Tag.TIFF, 2);
219 230
220 bw.forward('image-dir', 4); // The pointer should point right after itself. 231 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'); 250 throw new Error('Missing gps dictionary reference');
240 } 251 }
241 252
242 if (this.ifd_.thumbnail) { 253 if (this.ifd_.thumbnail) {
243 bw.resolveOffset('thumb-dir'); 254 bw.resolveOffset('thumb-dir');
244 ExifEncoder.encodeDirectory( 255 ExifEncoder.encodeDirectory(
245 bw, 256 bw,
246 this.ifd_.thumbnail, 257 this.ifd_.thumbnail,
247 [Exif.Tag.JPG_THUMB_OFFSET, Exif.Tag.JPG_THUMB_LENGTH]); 258 [Exif.Tag.JPG_THUMB_OFFSET, Exif.Tag.JPG_THUMB_LENGTH]);
248 259
249 var thumbnailDecoded = 260 var thumbnailDecoded = ImageEncoder.decodeDataURL(this.thumbnailDataUrl);
250 ImageEncoder.decodeDataURL(this.metadata_.thumbnailURL);
251 bw.resolveOffset(Exif.Tag.JPG_THUMB_OFFSET); 261 bw.resolveOffset(Exif.Tag.JPG_THUMB_OFFSET);
252 bw.resolve(Exif.Tag.JPG_THUMB_LENGTH, thumbnailDecoded.length); 262 bw.resolve(Exif.Tag.JPG_THUMB_LENGTH, thumbnailDecoded.length);
253 bw.writeString(thumbnailDecoded); 263 bw.writeString(thumbnailDecoded);
254 } else { 264 } else {
255 bw.resolve('thumb-dir', 0); 265 bw.resolve('thumb-dir', 0);
256 } 266 }
257 267
258 bw.checkResolved(); 268 bw.checkResolved();
259 269
260 var totalSize = HEADER_SIZE + bw.tell(); 270 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. 600 * @param {!(string|Exif.Tag)} key A key.
591 * @return {string} Formatted representation. 601 * @return {string} Formatted representation.
592 */ 602 */
593 ByteWriter.prettyKeyFormat = function(key) { 603 ByteWriter.prettyKeyFormat = function(key) {
594 if (typeof key === 'number') { 604 if (typeof key === 'number') {
595 return '0x' + key.toString(16); 605 return '0x' + key.toString(16);
596 } else { 606 } else {
597 return key; 607 return key;
598 } 608 }
599 }; 609 };
OLDNEW
« no previous file with comments | « ui/file_manager/gallery/js/gallery_item_unittest.js ('k') | ui/file_manager/gallery/js/image_editor/exif_encoder_unittest.js » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698