Merge Chromium + Blink git repositories
[chromium-blink-merge.git] / ui / file_manager / gallery / js / image_editor / image_encoder.js
blob272b0e849b39699c5d8acdf96b4bba819aab2e25
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 /**
6  * A namespace class for image encoding functions. All methods are static.
7  */
8 function ImageEncoder() {}
10 /**
11  * The value 360 px is enough in Files.app grid view for HiDPI devices.
12  * @const {number}
13  */
14 ImageEncoder.MAX_THUMBNAIL_DIMENSION = 360;
16 /**
17  * Tries to create thumbnail if the image width or height longer than the size.
18  * @const {number}
19  */
20 ImageEncoder.MIN_IMAGE_DIMENSION_FOR_THUMBNAIL =
21     ImageEncoder.MAX_THUMBNAIL_DIMENSION * 4;
23 /**
24  * Metadata encoders.
25  * @type {!Object<function(new:ImageEncoder.MetadataEncoder,!MetadataItem)>}
26  * @const
27  */
28 ImageEncoder.metadataEncoders = {};
30 /**
31  * Registers metadata encoder.
32  * @param {function(new:ImageEncoder.MetadataEncoder,!MetadataItem)} constructor
33  *     Constructor of a metadata encoder.
34  * @param {string} mimeType Mime type of the metadata encoder.
35  */
36 ImageEncoder.registerMetadataEncoder = function(constructor, mimeType) {
37   ImageEncoder.metadataEncoders[mimeType] = constructor;
40 /**
41  * Create a metadata encoder.
42  *
43  * The encoder will own and modify a copy of the original metadata.
44  *
45  * @param {!MetadataItem} metadata Original metadata.
46  * @return {!ImageEncoder.MetadataEncoder} Created metadata encoder.
47  */
48 ImageEncoder.createMetadataEncoder = function(metadata) {
49   var constructor =
50       ImageEncoder.metadataEncoders[metadata.mediaMimeType || ""] ||
51       ImageEncoder.MetadataEncoder;
52   return new constructor(metadata);
55 /**
56  * Create a metadata encoder object holding a copy of metadata
57  * modified according to the properties of the supplied image.
58  *
59  * @param {!MetadataItem} metadata Original metadata.
60  * @param {!HTMLCanvasElement} canvas Canvas to use for metadata.
61  * @param {number} thumbnailQuality Encoding quality of a thumbnail.
62  * @return {!ImageEncoder.MetadataEncoder} Encoder with encoded metadata.
63  *
64  * TODO(yawano): rename to a better name, e.g. prepareMetadataEncoder.
65  */
66 ImageEncoder.encodeMetadata = function(metadata, canvas, thumbnailQuality) {
67   var encoder = ImageEncoder.createMetadataEncoder(metadata);
68   encoder.setImageData(canvas);
69   encoder.setThumbnailData(ImageEncoder.createThumbnail(canvas),
70       thumbnailQuality);
71   return encoder;
74 /**
75  * Return a blob with the encoded image with metadata inserted.
76  * @param {!HTMLCanvasElement} canvas The canvas with the image to be encoded.
77  * @param {!ImageEncoder.MetadataEncoder} metadataEncoder Encoder to use.
78  * @param {number} imageQuality (0..1], Encoding quality of an image.
79  * @return {!Blob} encoded data.
80  */
81 ImageEncoder.getBlob = function(canvas, metadataEncoder, imageQuality) {
82   ImageUtil.trace.resetTimer('dataurl');
83   // WebKit does not support canvas.toBlob yet so canvas.toDataURL is
84   // the only way to use the Chrome built-in image encoder.
85   var dataURL = canvas.toDataURL(metadataEncoder.mimeType, imageQuality);
86   ImageUtil.trace.reportTimer('dataurl');
88   var encodedImage = ImageEncoder.decodeDataURL(dataURL);
90   var encodedMetadata = metadataEncoder.encode();
92   var slices = [];
94   // TODO(kaznacheev): refactor |stringToArrayBuffer| and |encode| to return
95   // arrays instead of array buffers.
96   function appendSlice(arrayBuffer) {
97     slices.push(new DataView(arrayBuffer));
98   }
100   ImageUtil.trace.resetTimer('blob');
101   if (encodedMetadata.byteLength != 0) {
102     var metadataRange = metadataEncoder.findInsertionRange(encodedImage);
103     appendSlice(ImageEncoder.stringToArrayBuffer(
104         encodedImage, 0, metadataRange.from));
106     appendSlice(metadataEncoder.encode());
108     appendSlice(ImageEncoder.stringToArrayBuffer(
109         encodedImage, metadataRange.to, encodedImage.length));
110   } else {
111     appendSlice(ImageEncoder.stringToArrayBuffer(
112         encodedImage, 0, encodedImage.length));
113   }
114   var blob = new Blob(slices, {type: metadataEncoder.mimeType});
115   ImageUtil.trace.reportTimer('blob');
116   return blob;
120  * Decode a dataURL into a binary string containing the encoded image.
122  * Why return a string? Calling atob and having the rest of the code deal
123  * with a string is several times faster than decoding base64 in Javascript.
125  * @param {string} dataURL Data URL to decode.
126  * @return {string} A binary string (char codes are the actual byte values).
127  */
128 ImageEncoder.decodeDataURL = function(dataURL) {
129   // Skip the prefix ('data:image/<type>;base64,')
130   var base64string = dataURL.substring(dataURL.indexOf(',') + 1);
131   return window.atob(base64string);
135  * Return a thumbnail for an image.
136  * @param {!HTMLCanvasElement} canvas Original image.
137  * @return {HTMLCanvasElement} Thumbnail canvas.
138  */
139 ImageEncoder.createThumbnail = function(canvas) {
140   if (canvas.width < ImageEncoder.MIN_IMAGE_DIMENSION_FOR_THUMBNAIL &&
141       canvas.height < ImageEncoder.MIN_IMAGE_DIMENSION_FOR_THUMBNAIL) {
142     return null;
143   }
145   var ratio = Math.min(ImageEncoder.MAX_THUMBNAIL_DIMENSION / canvas.width,
146                        ImageEncoder.MAX_THUMBNAIL_DIMENSION / canvas.height);
147   var thumbnailCanvas = assertInstanceof(
148       canvas.ownerDocument.createElement('canvas'), HTMLCanvasElement);
149   thumbnailCanvas.width = Math.round(canvas.width * ratio);
150   thumbnailCanvas.height = Math.round(canvas.height * ratio);
152   var context = thumbnailCanvas.getContext('2d');
153   context.drawImage(canvas,
154       0, 0, canvas.width, canvas.height,
155       0, 0, thumbnailCanvas.width, thumbnailCanvas.height);
157   return thumbnailCanvas;
161  * Converts string to an array buffer.
162  * @param {string} string A string.
163  * @param {number} from Start index.
164  * @param {number} to End index.
165  * @return {!ArrayBuffer}  A created array buffer is returned.
166  */
167 ImageEncoder.stringToArrayBuffer = function(string, from, to) {
168   var size = to - from;
169   var array = new Uint8Array(size);
170   for (var i = 0; i != size; i++) {
171     array[i] = string.charCodeAt(from + i);
172   }
173   return array.buffer;
177  * A base class for a metadata encoder.
179  * Serves as a default metadata encoder for images that none of the metadata
180  * parsers recognized.
182  * @param {!MetadataItem} originalMetadata Starting metadata.
183  * @constructor
184  * @struct
185  */
186 ImageEncoder.MetadataEncoder = function(originalMetadata) {
187   var mimeType = ImageEncoder.MetadataEncoder.getMimeType_(originalMetadata);
189   /**
190    * Chrome can only encode JPEG and PNG. Force PNG mime type so that we
191    * can save to file and generate a thumbnail.
192    * @public {string}
193    */
194   this.mimeType = mimeType === 'image/jpeg' ? 'image/jpeg' : 'image/png';
196   /**
197    * @protected {string}
198    */
199   this.thumbnailDataUrl = '';
201   /**
202    * @protected {number}
203    */
204   this.imageWidth = 0;
206   /**
207    * @protected {number}
208    */
209   this.imageHeight = 0;
213  * Gets mime type from metadata. It reads media.mimeType at first, and if it
214  * fails, it falls back to external.contentMimeType. If both fields are
215  * undefined, it means that metadata is broken. Then it throws an exception.
217  * @param {!MetadataItem} metadata Metadata.
218  * @return {string} Mime type.
219  * @private
220  */
221 ImageEncoder.MetadataEncoder.getMimeType_ = function(metadata) {
222   if (metadata.mediaMimeType)
223     return metadata.mediaMimeType;
224   else if (metadata.contentMimeType)
225     return metadata.contentMimeType;
227   assertNotReached();
231  * Sets an image data.
232  * @param {!HTMLCanvasElement} canvas Canvas or anything with width and height
233  *     properties.
234  */
235 ImageEncoder.MetadataEncoder.prototype.setImageData = function(canvas) {
236   this.imageWidth = canvas.width;
237   this.imageHeight = canvas.height;
241  * @param {HTMLCanvasElement} canvas Canvas to use as thumbnail. Note that it
242  *     can be null.
243  * @param {number} quality Thumbnail quality.
244  */
245 ImageEncoder.MetadataEncoder.prototype.setThumbnailData =
246     function(canvas, quality) {
247   this.thumbnailDataUrl =
248       canvas ? canvas.toDataURL(this.mimeType, quality) : '';
252  * Returns a range where the metadata is (or should be) located.
253  * @param {string} encodedImage An encoded image.
254  * @return {{from:number, to:number}} An object with from and to properties.
255  */
256 ImageEncoder.MetadataEncoder.prototype.
257     findInsertionRange = function(encodedImage) { return {from: 0, to: 0}; };
260  * Returns serialized metadata ready to write to an image file.
261  * The return type is optimized for passing to Blob.append.
262  * @return {!ArrayBuffer} Serialized metadata.
263  */
264 ImageEncoder.MetadataEncoder.prototype.encode = function() {
265   return new Uint8Array(0).buffer;