Gallery: Random fixes for the Viewport class.
[chromium-blink-merge.git] / ui / file_manager / gallery / js / image_editor / image_encoder.js
blobf1817b93e811a0609b301edefc21137d616631c8
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
3 // found in the LICENSE file.
5 'use strict';
7 /**
8  * A namespace class for image encoding functions. All methods are static.
9  */
10 function ImageEncoder() {}
12 /**
13  * @type {Array.<Object>}
14  */
15 ImageEncoder.metadataEncoders = {};
17 /**
18  * @param {function(new:ImageEncoder.MetadataEncoder)} constructor
19  *     // TODO(JSDOC).
20  * @param {string} mimeType  // TODO(JSDOC).
21  */
22 ImageEncoder.registerMetadataEncoder = function(constructor, mimeType) {
23   ImageEncoder.metadataEncoders[mimeType] = constructor;
26 /**
27  * Create a metadata encoder.
28  *
29  * The encoder will own and modify a copy of the original metadata.
30  *
31  * @param {Object} metadata Original metadata.
32  * @return {ImageEncoder.MetadataEncoder} Created metadata encoder.
33  */
34 ImageEncoder.createMetadataEncoder = function(metadata) {
35   var constructor =
36       (metadata && ImageEncoder.metadataEncoders[metadata.mimeType]) ||
37       ImageEncoder.MetadataEncoder;
38   return new constructor(metadata);
42 /**
43  * Create a metadata encoder object holding a copy of metadata
44  * modified according to the properties of the supplied image.
45  *
46  * @param {Object} metadata Original metadata.
47  * @param {HTMLCanvasElement} canvas Canvas to use for metadata.
48  * @param {number} quality Encoding quality (defaults to 1).
49  * @return {ImageEncoder.MetadataEncoder} Encoder with encoded metadata.
50  */
51 ImageEncoder.encodeMetadata = function(metadata, canvas, quality) {
52   var encoder = ImageEncoder.createMetadataEncoder(metadata);
53   encoder.setImageData(canvas);
54   encoder.setThumbnailData(ImageEncoder.createThumbnail(canvas), quality || 1);
55   return encoder;
59 /**
60  * Return a blob with the encoded image with metadata inserted.
61  * @param {HTMLCanvasElement} canvas The canvas with the image to be encoded.
62  * @param {ImageEncoder.MetadataEncoder} metadataEncoder Encoder to use.
63  * @param {number} quality (0..1], Encoding quality, defaults to 0.9.
64  * @return {Blob} encoded data.
65  */
66 ImageEncoder.getBlob = function(canvas, metadataEncoder, quality) {
67   // Contrary to what one might think 1.0 is not a good default. Opening and
68   // saving an typical photo taken with consumer camera increases its file size
69   // by 50-100%.
70   // Experiments show that 0.9 is much better. It shrinks some photos a bit,
71   // keeps others about the same size, but does not visibly lower the quality.
72   quality = quality || 0.9;
74   ImageUtil.trace.resetTimer('dataurl');
75   // WebKit does not support canvas.toBlob yet so canvas.toDataURL is
76   // the only way to use the Chrome built-in image encoder.
77   var dataURL =
78       canvas.toDataURL(metadataEncoder.getMetadata().mimeType, quality);
79   ImageUtil.trace.reportTimer('dataurl');
81   var encodedImage = ImageEncoder.decodeDataURL(dataURL);
83   var encodedMetadata = metadataEncoder.encode();
85   var slices = [];
87   // TODO(kaznacheev): refactor |stringToArrayBuffer| and |encode| to return
88   // arrays instead of array buffers.
89   function appendSlice(arrayBuffer) {
90     slices.push(new DataView(arrayBuffer));
91   }
93   ImageUtil.trace.resetTimer('blob');
94   if (encodedMetadata.byteLength != 0) {
95     var metadataRange = metadataEncoder.findInsertionRange(encodedImage);
96     appendSlice(ImageEncoder.stringToArrayBuffer(
97         encodedImage, 0, metadataRange.from));
99     appendSlice(metadataEncoder.encode());
101     appendSlice(ImageEncoder.stringToArrayBuffer(
102         encodedImage, metadataRange.to, encodedImage.length));
103   } else {
104     appendSlice(ImageEncoder.stringToArrayBuffer(
105         encodedImage, 0, encodedImage.length));
106   }
107   var blob = new Blob(slices, {type: metadataEncoder.getMetadata().mimeType});
108   ImageUtil.trace.reportTimer('blob');
109   return blob;
113  * Decode a dataURL into a binary string containing the encoded image.
115  * Why return a string? Calling atob and having the rest of the code deal
116  * with a string is several times faster than decoding base64 in Javascript.
118  * @param {string} dataURL Data URL to decode.
119  * @return {string} A binary string (char codes are the actual byte values).
120  */
121 ImageEncoder.decodeDataURL = function(dataURL) {
122   // Skip the prefix ('data:image/<type>;base64,')
123   var base64string = dataURL.substring(dataURL.indexOf(',') + 1);
124   return atob(base64string);
128  * Return a thumbnail for an image.
129  * @param {HTMLCanvasElement} canvas Original image.
130  * @param {number=} opt_shrinkage Thumbnail should be at least this much smaller
131  *     than the original image (in each dimension).
132  * @return {HTMLCanvasElement} Thumbnail canvas.
133  */
134 ImageEncoder.createThumbnail = function(canvas, opt_shrinkage) {
135   var MAX_THUMBNAIL_DIMENSION = 320;
137   opt_shrinkage = Math.max(opt_shrinkage || 4,
138                        canvas.width / MAX_THUMBNAIL_DIMENSION,
139                        canvas.height / MAX_THUMBNAIL_DIMENSION);
141   var thumbnailCanvas = canvas.ownerDocument.createElement('canvas');
142   thumbnailCanvas.width = Math.round(canvas.width / opt_shrinkage);
143   thumbnailCanvas.height = Math.round(canvas.height / opt_shrinkage);
145   var context = thumbnailCanvas.getContext('2d');
146   context.drawImage(canvas,
147       0, 0, canvas.width, canvas.height,
148       0, 0, thumbnailCanvas.width, thumbnailCanvas.height);
150   return thumbnailCanvas;
154  * TODO(JSDOC)
155  * @param {string} string  // TODO(JSDOC).
156  * @param {number} from  // TODO(JSDOC).
157  * @param {number} to  // TODO(JSDOC).
158  * @return {ArrayBuffer}  // TODO(JSDOC).
159  */
160 ImageEncoder.stringToArrayBuffer = function(string, from, to) {
161   var size = to - from;
162   var array = new Uint8Array(size);
163   for (var i = 0; i != size; i++) {
164     array[i] = string.charCodeAt(from + i);
165   }
166   return array.buffer;
170  * A base class for a metadata encoder.
172  * Serves as a default metadata encoder for images that none of the metadata
173  * parsers recognized.
175  * @param {Object} original_metadata Starting metadata.
176  * @constructor
177  */
178 ImageEncoder.MetadataEncoder = function(original_metadata) {
179   this.metadata_ = MetadataCache.cloneMetadata(original_metadata) || {};
180   if (this.metadata_.mimeType != 'image/jpeg') {
181     // Chrome can only encode JPEG and PNG. Force PNG mime type so that we
182     // can save to file and generate a thumbnail.
183     this.metadata_.mimeType = 'image/png';
184   }
188  * TODO(JSDOC)
189  * @return {Object}   // TODO(JSDOC).
190  */
191 ImageEncoder.MetadataEncoder.prototype.getMetadata = function() {
192   return this.metadata_;
196  * @param {HTMLCanvasElement|Object} canvas Canvas or or anything with
197  *                                          width and height properties.
198  */
199 ImageEncoder.MetadataEncoder.prototype.setImageData = function(canvas) {
200   this.metadata_.width = canvas.width;
201   this.metadata_.height = canvas.height;
205  * @param {HTMLCanvasElement} canvas Canvas to use as thumbnail.
206  * @param {number} quality Thumbnail quality.
207  */
208 ImageEncoder.MetadataEncoder.prototype.setThumbnailData =
209     function(canvas, quality) {
210   this.metadata_.thumbnailURL =
211       canvas.toDataURL(this.metadata_.mimeType, quality);
212   delete this.metadata_.thumbnailTransform;
216  * Return a range where the metadata is (or should be) located.
217  * @param {string} encodedImage // TODO(JSDOC).
218  * @return {Object} An object with from and to properties.
219  */
220 ImageEncoder.MetadataEncoder.prototype.
221     findInsertionRange = function(encodedImage) { return {from: 0, to: 0}; };
224  * Return serialized metadata ready to write to an image file.
225  * The return type is optimized for passing to Blob.append.
226  * @return {ArrayBuffer} // TODO(JSDOC).
227  */
228 ImageEncoder.MetadataEncoder.prototype.encode = function() {
229   return new Uint8Array(0).buffer;