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.
6 * A namespace class for image encoding functions. All methods are static.
8 function ImageEncoder() {}
11 * @type {Array.<Object>}
13 ImageEncoder.metadataEncoders = {};
16 * @param {function(new:ImageEncoder.MetadataEncoder)} constructor
18 * @param {string} mimeType // TODO(JSDOC).
20 ImageEncoder.registerMetadataEncoder = function(constructor, mimeType) {
21 ImageEncoder.metadataEncoders[mimeType] = constructor;
25 * Create a metadata encoder.
27 * The encoder will own and modify a copy of the original metadata.
29 * @param {Object} metadata Original metadata.
30 * @return {ImageEncoder.MetadataEncoder} Created metadata encoder.
32 ImageEncoder.createMetadataEncoder = function(metadata) {
34 (metadata && ImageEncoder.metadataEncoders[metadata.mimeType]) ||
35 ImageEncoder.MetadataEncoder;
36 return new constructor(metadata);
41 * Create a metadata encoder object holding a copy of metadata
42 * modified according to the properties of the supplied image.
44 * @param {Object} metadata Original metadata.
45 * @param {HTMLCanvasElement} canvas Canvas to use for metadata.
46 * @param {number} quality Encoding quality (defaults to 1).
47 * @return {ImageEncoder.MetadataEncoder} Encoder with encoded metadata.
49 ImageEncoder.encodeMetadata = function(metadata, canvas, quality) {
50 var encoder = ImageEncoder.createMetadataEncoder(metadata);
51 encoder.setImageData(canvas);
52 encoder.setThumbnailData(ImageEncoder.createThumbnail(canvas), quality || 1);
58 * Return a blob with the encoded image with metadata inserted.
59 * @param {HTMLCanvasElement} canvas The canvas with the image to be encoded.
60 * @param {ImageEncoder.MetadataEncoder} metadataEncoder Encoder to use.
61 * @param {number} quality (0..1], Encoding quality, defaults to 0.9.
62 * @return {Blob} encoded data.
64 ImageEncoder.getBlob = function(canvas, metadataEncoder, quality) {
65 // Contrary to what one might think 1.0 is not a good default. Opening and
66 // saving an typical photo taken with consumer camera increases its file size
68 // Experiments show that 0.9 is much better. It shrinks some photos a bit,
69 // keeps others about the same size, but does not visibly lower the quality.
70 quality = quality || 0.9;
72 ImageUtil.trace.resetTimer('dataurl');
73 // WebKit does not support canvas.toBlob yet so canvas.toDataURL is
74 // the only way to use the Chrome built-in image encoder.
76 canvas.toDataURL(metadataEncoder.getMetadata().mimeType, quality);
77 ImageUtil.trace.reportTimer('dataurl');
79 var encodedImage = ImageEncoder.decodeDataURL(dataURL);
81 var encodedMetadata = metadataEncoder.encode();
85 // TODO(kaznacheev): refactor |stringToArrayBuffer| and |encode| to return
86 // arrays instead of array buffers.
87 function appendSlice(arrayBuffer) {
88 slices.push(new DataView(arrayBuffer));
91 ImageUtil.trace.resetTimer('blob');
92 if (encodedMetadata.byteLength != 0) {
93 var metadataRange = metadataEncoder.findInsertionRange(encodedImage);
94 appendSlice(ImageEncoder.stringToArrayBuffer(
95 encodedImage, 0, metadataRange.from));
97 appendSlice(metadataEncoder.encode());
99 appendSlice(ImageEncoder.stringToArrayBuffer(
100 encodedImage, metadataRange.to, encodedImage.length));
102 appendSlice(ImageEncoder.stringToArrayBuffer(
103 encodedImage, 0, encodedImage.length));
105 var blob = new Blob(slices, {type: metadataEncoder.getMetadata().mimeType});
106 ImageUtil.trace.reportTimer('blob');
111 * Decode a dataURL into a binary string containing the encoded image.
113 * Why return a string? Calling atob and having the rest of the code deal
114 * with a string is several times faster than decoding base64 in Javascript.
116 * @param {string} dataURL Data URL to decode.
117 * @return {string} A binary string (char codes are the actual byte values).
119 ImageEncoder.decodeDataURL = function(dataURL) {
120 // Skip the prefix ('data:image/<type>;base64,')
121 var base64string = dataURL.substring(dataURL.indexOf(',') + 1);
122 return atob(base64string);
126 * Return a thumbnail for an image.
127 * @param {HTMLCanvasElement} canvas Original image.
128 * @param {number=} opt_shrinkage Thumbnail should be at least this much smaller
129 * than the original image (in each dimension).
130 * @return {HTMLCanvasElement} Thumbnail canvas.
132 ImageEncoder.createThumbnail = function(canvas, opt_shrinkage) {
133 var MAX_THUMBNAIL_DIMENSION = 320;
135 opt_shrinkage = Math.max(opt_shrinkage || 4,
136 canvas.width / MAX_THUMBNAIL_DIMENSION,
137 canvas.height / MAX_THUMBNAIL_DIMENSION);
139 var thumbnailCanvas = canvas.ownerDocument.createElement('canvas');
140 thumbnailCanvas.width = Math.round(canvas.width / opt_shrinkage);
141 thumbnailCanvas.height = Math.round(canvas.height / opt_shrinkage);
143 var context = thumbnailCanvas.getContext('2d');
144 context.drawImage(canvas,
145 0, 0, canvas.width, canvas.height,
146 0, 0, thumbnailCanvas.width, thumbnailCanvas.height);
148 return thumbnailCanvas;
153 * @param {string} string // TODO(JSDOC).
154 * @param {number} from // TODO(JSDOC).
155 * @param {number} to // TODO(JSDOC).
156 * @return {ArrayBuffer} // TODO(JSDOC).
158 ImageEncoder.stringToArrayBuffer = function(string, from, to) {
159 var size = to - from;
160 var array = new Uint8Array(size);
161 for (var i = 0; i != size; i++) {
162 array[i] = string.charCodeAt(from + i);
168 * A base class for a metadata encoder.
170 * Serves as a default metadata encoder for images that none of the metadata
171 * parsers recognized.
173 * @param {Object} original_metadata Starting metadata.
176 ImageEncoder.MetadataEncoder = function(original_metadata) {
177 this.metadata_ = MetadataCache.cloneMetadata(original_metadata) || {};
178 if (this.metadata_.mimeType != 'image/jpeg') {
179 // Chrome can only encode JPEG and PNG. Force PNG mime type so that we
180 // can save to file and generate a thumbnail.
181 this.metadata_.mimeType = 'image/png';
187 * @return {Object} // TODO(JSDOC).
189 ImageEncoder.MetadataEncoder.prototype.getMetadata = function() {
190 return this.metadata_;
194 * @param {HTMLCanvasElement|Object} canvas Canvas or or anything with
195 * width and height properties.
197 ImageEncoder.MetadataEncoder.prototype.setImageData = function(canvas) {
198 this.metadata_.width = canvas.width;
199 this.metadata_.height = canvas.height;
203 * @param {HTMLCanvasElement} canvas Canvas to use as thumbnail.
204 * @param {number} quality Thumbnail quality.
206 ImageEncoder.MetadataEncoder.prototype.setThumbnailData =
207 function(canvas, quality) {
208 this.metadata_.thumbnailURL =
209 canvas.toDataURL(this.metadata_.mimeType, quality);
210 delete this.metadata_.thumbnailTransform;
214 * Return a range where the metadata is (or should be) located.
215 * @param {string} encodedImage // TODO(JSDOC).
216 * @return {Object} An object with from and to properties.
218 ImageEncoder.MetadataEncoder.prototype.
219 findInsertionRange = function(encodedImage) { return {from: 0, to: 0}; };
222 * Return serialized metadata ready to write to an image file.
223 * The return type is optimized for passing to Blob.append.
224 * @return {ArrayBuffer} // TODO(JSDOC).
226 ImageEncoder.MetadataEncoder.prototype.encode = function() {
227 return new Uint8Array(0).buffer;