| OLD | NEW |
| 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 * Object representing an image item (a photo). | 6 * Object representing an image item (a photo). |
| 7 * | 7 * |
| 8 * @param {!FileEntry} entry Image entry. | 8 * @param {!FileEntry} entry Image entry. |
| 9 * @param {!EntryLocation} locationInfo Entry location information. | 9 * @param {!EntryLocation} locationInfo Entry location information. |
| 10 * @param {!MetadataItem} metadataItem | 10 * @param {!MetadataItem} metadataItem |
| (...skipping 137 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 148 * @type {!RegExp} | 148 * @type {!RegExp} |
| 149 * @const | 149 * @const |
| 150 */ | 150 */ |
| 151 Gallery.Item.REGEXP_COPY_N = | 151 Gallery.Item.REGEXP_COPY_N = |
| 152 new RegExp('^(.+)' + Gallery.Item.COPY_SIGNATURE + ' \\((\\d+)\\)$'); | 152 new RegExp('^(.+)' + Gallery.Item.COPY_SIGNATURE + ' \\((\\d+)\\)$'); |
| 153 | 153 |
| 154 /** | 154 /** |
| 155 * Creates a name for an edited copy of the file. | 155 * Creates a name for an edited copy of the file. |
| 156 * | 156 * |
| 157 * @param {!DirectoryEntry} dirEntry Entry. | 157 * @param {!DirectoryEntry} dirEntry Entry. |
| 158 * @param {string} newMimeType Mime type of new image. |
| 158 * @param {function(string)} callback Callback. | 159 * @param {function(string)} callback Callback. |
| 159 * @private | 160 * @private |
| 160 */ | 161 */ |
| 161 Gallery.Item.prototype.createCopyName_ = function(dirEntry, callback) { | 162 Gallery.Item.prototype.createCopyName_ = function( |
| 163 dirEntry, newMimeType, callback) { |
| 162 var name = this.getFileName(); | 164 var name = this.getFileName(); |
| 163 | 165 |
| 164 // If the item represents a file created during the current Gallery session | 166 // If the item represents a file created during the current Gallery session |
| 165 // we reuse it for subsequent saves instead of creating multiple copies. | 167 // we reuse it for subsequent saves instead of creating multiple copies. |
| 166 if (!this.original_) { | 168 if (!this.original_) { |
| 167 callback(name); | 169 callback(name); |
| 168 return; | 170 return; |
| 169 } | 171 } |
| 170 | 172 |
| 171 var ext = ''; | 173 var baseName = name.replace(/\.[^\.\/]+$/, ''); |
| 172 var index = name.lastIndexOf('.'); | 174 var ext = newMimeType === 'image/jpeg' ? '.jpg' : '.png'; |
| 173 if (index != -1) { | |
| 174 ext = name.substr(index); | |
| 175 name = name.substr(0, index); | |
| 176 } | |
| 177 | |
| 178 if (!ext.match(/jpe?g/i)) { | |
| 179 // Chrome can natively encode only two formats: JPEG and PNG. | |
| 180 // All non-JPEG images are saved in PNG, hence forcing the file extension. | |
| 181 ext = '.png'; | |
| 182 } | |
| 183 | 175 |
| 184 function tryNext(tries) { | 176 function tryNext(tries) { |
| 185 // All the names are used. Let's overwrite the last one. | 177 // All the names are used. Let's overwrite the last one. |
| 186 if (tries == 0) { | 178 if (tries == 0) { |
| 187 setTimeout(callback, 0, name + ext); | 179 setTimeout(callback, 0, baseName + ext); |
| 188 return; | 180 return; |
| 189 } | 181 } |
| 190 | 182 |
| 191 // If the file name contains the copy signature add/advance the sequential | 183 // If the file name contains the copy signature add/advance the sequential |
| 192 // number. | 184 // number. |
| 193 var matchN = Gallery.Item.REGEXP_COPY_N.exec(name); | 185 var matchN = Gallery.Item.REGEXP_COPY_N.exec(baseName); |
| 194 var match0 = Gallery.Item.REGEXP_COPY_0.exec(name); | 186 var match0 = Gallery.Item.REGEXP_COPY_0.exec(baseName); |
| 195 if (matchN && matchN[1] && matchN[2]) { | 187 if (matchN && matchN[1] && matchN[2]) { |
| 196 var copyNumber = parseInt(matchN[2], 10) + 1; | 188 var copyNumber = parseInt(matchN[2], 10) + 1; |
| 197 name = matchN[1] + Gallery.Item.COPY_SIGNATURE + ' (' + copyNumber + ')'; | 189 baseName = matchN[1] + Gallery.Item.COPY_SIGNATURE + |
| 190 ' (' + copyNumber + ')'; |
| 198 } else if (match0 && match0[1]) { | 191 } else if (match0 && match0[1]) { |
| 199 name = match0[1] + Gallery.Item.COPY_SIGNATURE + ' (1)'; | 192 baseName = match0[1] + Gallery.Item.COPY_SIGNATURE + ' (1)'; |
| 200 } else { | 193 } else { |
| 201 name += Gallery.Item.COPY_SIGNATURE; | 194 baseName += Gallery.Item.COPY_SIGNATURE; |
| 202 } | 195 } |
| 203 | 196 |
| 204 dirEntry.getFile(name + ext, {create: false, exclusive: false}, | 197 dirEntry.getFile(baseName + ext, {create: false, exclusive: false}, |
| 205 tryNext.bind(null, tries - 1), | 198 tryNext.bind(null, tries - 1), |
| 206 callback.bind(null, name + ext)); | 199 callback.bind(null, baseName + ext)); |
| 207 } | 200 } |
| 208 | 201 |
| 209 tryNext(10); | 202 tryNext(10); |
| 210 }; | 203 }; |
| 211 | 204 |
| 212 /** | 205 /** |
| 213 * Writes the new item content to either the existing or a new file. | 206 * Writes the new item content to either the existing or a new file. |
| 214 * | 207 * |
| 215 * @param {!VolumeManager} volumeManager Volume manager instance. | 208 * @param {!VolumeManager} volumeManager Volume manager instance. |
| 216 * @param {!MetadataModel} metadataModel | 209 * @param {!MetadataModel} metadataModel |
| 217 * @param {DirectoryEntry} fallbackDir Fallback directory in case the current | 210 * @param {DirectoryEntry} fallbackDir Fallback directory in case the current |
| 218 * directory is read only. | 211 * directory is read only. |
| 219 * @param {boolean} overwrite Whether to overwrite the image to the item or not. | 212 * @param {boolean} overwrite Whether to overwrite the image to the item or not. |
| 220 * @param {!HTMLCanvasElement} canvas Source canvas. | 213 * @param {!HTMLCanvasElement} canvas Source canvas. |
| 221 * @param {function(boolean)} callback Callback accepting true for success. | 214 * @param {function(boolean)} callback Callback accepting true for success. |
| 222 */ | 215 */ |
| 223 Gallery.Item.prototype.saveToFile = function( | 216 Gallery.Item.prototype.saveToFile = function( |
| 224 volumeManager, metadataModel, fallbackDir, overwrite, canvas, callback) { | 217 volumeManager, metadataModel, fallbackDir, overwrite, canvas, callback) { |
| 225 ImageUtil.metrics.startInterval(ImageUtil.getMetricName('SaveTime')); | 218 ImageUtil.metrics.startInterval(ImageUtil.getMetricName('SaveTime')); |
| 226 | 219 |
| 227 var name = this.getFileName(); | 220 var name = this.getFileName(); |
| 221 var newMimeType = name.match(/\.jpe?g$/i) || FileType.isRaw(this.entry_) ? |
| 222 'image/jpeg' : 'image/png'; |
| 228 | 223 |
| 229 var onSuccess = function(entry) { | 224 var onSuccess = function(entry) { |
| 230 var locationInfo = volumeManager.getLocationInfo(entry); | 225 var locationInfo = volumeManager.getLocationInfo(entry); |
| 231 if (!locationInfo) { | 226 if (!locationInfo) { |
| 232 // Reuse old location info if it fails to obtain location info. | 227 // Reuse old location info if it fails to obtain location info. |
| 233 locationInfo = this.locationInfo_; | 228 locationInfo = this.locationInfo_; |
| 234 } | 229 } |
| 235 ImageUtil.metrics.recordEnum(ImageUtil.getMetricName('SaveResult'), 1, 2); | 230 ImageUtil.metrics.recordEnum(ImageUtil.getMetricName('SaveResult'), 1, 2); |
| 236 ImageUtil.metrics.recordInterval(ImageUtil.getMetricName('SaveTime')); | 231 ImageUtil.metrics.recordInterval(ImageUtil.getMetricName('SaveTime')); |
| 237 | 232 |
| (...skipping 25 matching lines...) Expand all Loading... |
| 263 var blob; | 258 var blob; |
| 264 var fileWriter; | 259 var fileWriter; |
| 265 | 260 |
| 266 metadataModel.get( | 261 metadataModel.get( |
| 267 [fileEntry], | 262 [fileEntry], |
| 268 ['mediaMimeType', 'contentMimeType', 'ifd', 'exifLittleEndian'] | 263 ['mediaMimeType', 'contentMimeType', 'ifd', 'exifLittleEndian'] |
| 269 ).then(function(metadataItems) { | 264 ).then(function(metadataItems) { |
| 270 // Create the blob of new image. | 265 // Create the blob of new image. |
| 271 var metadataItem = metadataItems[0]; | 266 var metadataItem = metadataItems[0]; |
| 272 metadataItem.modificationTime = new Date(); | 267 metadataItem.modificationTime = new Date(); |
| 268 metadataItem.mediaMimeType = newMimeType; |
| 273 var metadataEncoder = ImageEncoder.encodeMetadata( | 269 var metadataEncoder = ImageEncoder.encodeMetadata( |
| 274 metadataItem, canvas, /* quality for thumbnail*/ 0.8); | 270 metadataItem, canvas, /* quality for thumbnail*/ 0.8); |
| 275 // Contrary to what one might think 1.0 is not a good default. Opening | 271 // Contrary to what one might think 1.0 is not a good default. Opening |
| 276 // and saving an typical photo taken with consumer camera increases | 272 // and saving an typical photo taken with consumer camera increases |
| 277 // its file size by 50-100%. Experiments show that 0.9 is much better. | 273 // its file size by 50-100%. Experiments show that 0.9 is much better. |
| 278 // It shrinks some photos a bit, keeps others about the same size, but | 274 // It shrinks some photos a bit, keeps others about the same size, but |
| 279 // does not visibly lower the quality. | 275 // does not visibly lower the quality. |
| 280 blob = ImageEncoder.getBlob(canvas, metadataEncoder, 0.9); | 276 blob = ImageEncoder.getBlob(canvas, metadataEncoder, 0.9); |
| 281 }).then(function() { | 277 }.bind(this)).then(function() { |
| 282 // Create writer. | 278 // Create writer. |
| 283 return new Promise(function(fullfill, reject) { | 279 return new Promise(function(fullfill, reject) { |
| 284 fileEntry.createWriter(fullfill, reject); | 280 fileEntry.createWriter(fullfill, reject); |
| 285 }); | 281 }); |
| 286 }).then(function(writer) { | 282 }).then(function(writer) { |
| 287 fileWriter = writer; | 283 fileWriter = writer; |
| 288 | 284 |
| 289 // Truncates the file to 0 byte if it overwrites. | 285 // Truncates the file to 0 byte if it overwrites. |
| 290 return new Promise(function(fulfill, reject) { | 286 return new Promise(function(fulfill, reject) { |
| 291 if (!newFile) { | 287 if (!newFile) { |
| (...skipping 29 matching lines...) Expand all Loading... |
| 321 }.bind(this), onError); | 317 }.bind(this), onError); |
| 322 }.bind(this); | 318 }.bind(this); |
| 323 | 319 |
| 324 var checkExistence = function(dir) { | 320 var checkExistence = function(dir) { |
| 325 dir.getFile(name, {create: false, exclusive: false}, | 321 dir.getFile(name, {create: false, exclusive: false}, |
| 326 getFile.bind(null, dir, false /* existing file */), | 322 getFile.bind(null, dir, false /* existing file */), |
| 327 getFile.bind(null, dir, true /* create new file */)); | 323 getFile.bind(null, dir, true /* create new file */)); |
| 328 }; | 324 }; |
| 329 | 325 |
| 330 var saveToDir = function(dir) { | 326 var saveToDir = function(dir) { |
| 331 if (overwrite && !this.locationInfo_.isReadOnly) { | 327 if (overwrite && |
| 328 !this.locationInfo_.isReadOnly && |
| 329 !FileType.isRaw(this.entry_)) { |
| 332 checkExistence(dir); | 330 checkExistence(dir); |
| 333 } else { | 331 } else { |
| 334 this.createCopyName_(dir, function(copyName) { | 332 this.createCopyName_(dir, newMimeType, function(copyName) { |
| 335 this.original_ = false; | 333 this.original_ = false; |
| 336 name = copyName; | 334 name = copyName; |
| 337 checkExistence(dir); | 335 checkExistence(dir); |
| 338 }.bind(this)); | 336 }.bind(this)); |
| 339 } | 337 } |
| 340 }.bind(this); | 338 }.bind(this); |
| 341 | 339 |
| 342 if (this.locationInfo_.isReadOnly) { | 340 if (this.locationInfo_.isReadOnly) { |
| 343 saveToDir(fallbackDir); | 341 saveToDir(fallbackDir); |
| 344 } else { | 342 } else { |
| (...skipping 30 matching lines...) Expand all Loading... |
| 375 return Promise.reject(str('GALLERY_FILE_EXISTS')); | 373 return Promise.reject(str('GALLERY_FILE_EXISTS')); |
| 376 }, function() { | 374 }, function() { |
| 377 return new Promise( | 375 return new Promise( |
| 378 this.entry_.moveTo.bind(this.entry_, parentDirectory, newFileName)); | 376 this.entry_.moveTo.bind(this.entry_, parentDirectory, newFileName)); |
| 379 }.bind(this)); | 377 }.bind(this)); |
| 380 }.bind(this)); | 378 }.bind(this)); |
| 381 }.bind(this)).then(function(entry) { | 379 }.bind(this)).then(function(entry) { |
| 382 this.entry_ = entry; | 380 this.entry_ = entry; |
| 383 }.bind(this)); | 381 }.bind(this)); |
| 384 }; | 382 }; |
| OLD | NEW |