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 * The value 360 px is enough in Files.app grid view for HiDPI devices.
14 ImageEncoder
.MAX_THUMBNAIL_DIMENSION
= 360;
17 * Tries to create thumbnail if the image width or height longer than the size.
20 ImageEncoder
.MIN_IMAGE_DIMENSION_FOR_THUMBNAIL
=
21 ImageEncoder
.MAX_THUMBNAIL_DIMENSION
* 4;
25 * @type {!Object.<string,function(
26 * new:ImageEncoder.MetadataEncoder,!MetadataItem)>}
29 ImageEncoder
.metadataEncoders
= {};
32 * Registers metadata encoder.
33 * @param {function(new:ImageEncoder.MetadataEncoder,!MetadataItem)} constructor
34 * Constructor of a metadata encoder.
35 * @param {string} mimeType Mime type of the metadata encoder.
37 ImageEncoder
.registerMetadataEncoder = function(constructor, mimeType
) {
38 ImageEncoder
.metadataEncoders
[mimeType
] = constructor;
42 * Create a metadata encoder.
44 * The encoder will own and modify a copy of the original metadata.
46 * @param {!MetadataItem} metadata Original metadata.
47 * @return {!ImageEncoder.MetadataEncoder} Created metadata encoder.
49 ImageEncoder
.createMetadataEncoder = function(metadata
) {
51 ImageEncoder
.metadataEncoders
[metadata
.mediaMimeType
|| ""] ||
52 ImageEncoder
.MetadataEncoder
;
53 return new constructor(metadata
);
57 * Create a metadata encoder object holding a copy of metadata
58 * modified according to the properties of the supplied image.
60 * @param {!MetadataItem} metadata Original metadata.
61 * @param {!HTMLCanvasElement} canvas Canvas to use for metadata.
62 * @param {number} thumbnailQuality Encoding quality of a thumbnail.
63 * @return {!ImageEncoder.MetadataEncoder} Encoder with encoded metadata.
65 ImageEncoder
.encodeMetadata = function(metadata
, canvas
, thumbnailQuality
) {
66 var encoder
= ImageEncoder
.createMetadataEncoder(metadata
);
67 encoder
.setImageData(canvas
);
68 encoder
.setThumbnailData(ImageEncoder
.createThumbnail(canvas
),
74 * Return a blob with the encoded image with metadata inserted.
75 * @param {!HTMLCanvasElement} canvas The canvas with the image to be encoded.
76 * @param {!ImageEncoder.MetadataEncoder} metadataEncoder Encoder to use.
77 * @param {number} imageQuality (0..1], Encoding quality of an image.
78 * @return {!Blob} encoded data.
80 ImageEncoder
.getBlob = function(canvas
, metadataEncoder
, imageQuality
) {
81 ImageUtil
.trace
.resetTimer('dataurl');
82 // WebKit does not support canvas.toBlob yet so canvas.toDataURL is
83 // the only way to use the Chrome built-in image encoder.
84 var dataURL
= canvas
.toDataURL(metadataEncoder
.mimeType
, imageQuality
);
85 ImageUtil
.trace
.reportTimer('dataurl');
87 var encodedImage
= ImageEncoder
.decodeDataURL(dataURL
);
89 var encodedMetadata
= metadataEncoder
.encode();
93 // TODO(kaznacheev): refactor |stringToArrayBuffer| and |encode| to return
94 // arrays instead of array buffers.
95 function appendSlice(arrayBuffer
) {
96 slices
.push(new DataView(arrayBuffer
));
99 ImageUtil
.trace
.resetTimer('blob');
100 if (encodedMetadata
.byteLength
!= 0) {
101 var metadataRange
= metadataEncoder
.findInsertionRange(encodedImage
);
102 appendSlice(ImageEncoder
.stringToArrayBuffer(
103 encodedImage
, 0, metadataRange
.from));
105 appendSlice(metadataEncoder
.encode());
107 appendSlice(ImageEncoder
.stringToArrayBuffer(
108 encodedImage
, metadataRange
.to
, encodedImage
.length
));
110 appendSlice(ImageEncoder
.stringToArrayBuffer(
111 encodedImage
, 0, encodedImage
.length
));
113 var blob
= new Blob(slices
, {type
: metadataEncoder
.mimeType
});
114 ImageUtil
.trace
.reportTimer('blob');
119 * Decode a dataURL into a binary string containing the encoded image.
121 * Why return a string? Calling atob and having the rest of the code deal
122 * with a string is several times faster than decoding base64 in Javascript.
124 * @param {string} dataURL Data URL to decode.
125 * @return {string} A binary string (char codes are the actual byte values).
127 ImageEncoder
.decodeDataURL = function(dataURL
) {
128 // Skip the prefix ('data:image/<type>;base64,')
129 var base64string
= dataURL
.substring(dataURL
.indexOf(',') + 1);
130 return window
.atob(base64string
);
134 * Return a thumbnail for an image.
135 * @param {!HTMLCanvasElement} canvas Original image.
136 * @return {HTMLCanvasElement} Thumbnail canvas.
138 ImageEncoder
.createThumbnail = function(canvas
) {
139 if (canvas
.width
< ImageEncoder
.MIN_IMAGE_DIMENSION_FOR_THUMBNAIL
&&
140 canvas
.height
< ImageEncoder
.MIN_IMAGE_DIMENSION_FOR_THUMBNAIL
) {
144 var ratio
= Math
.min(ImageEncoder
.MAX_THUMBNAIL_DIMENSION
/ canvas
.width
,
145 ImageEncoder
.MAX_THUMBNAIL_DIMENSION
/ canvas
.height
);
146 var thumbnailCanvas
= assertInstanceof(
147 canvas
.ownerDocument
.createElement('canvas'), HTMLCanvasElement
);
148 thumbnailCanvas
.width
= Math
.round(canvas
.width
* ratio
);
149 thumbnailCanvas
.height
= Math
.round(canvas
.height
* ratio
);
151 var context
= thumbnailCanvas
.getContext('2d');
152 context
.drawImage(canvas
,
153 0, 0, canvas
.width
, canvas
.height
,
154 0, 0, thumbnailCanvas
.width
, thumbnailCanvas
.height
);
156 return thumbnailCanvas
;
160 * Converts string to an array buffer.
161 * @param {string} string A string.
162 * @param {number} from Start index.
163 * @param {number} to End index.
164 * @return {!ArrayBuffer} A created array buffer is returned.
166 ImageEncoder
.stringToArrayBuffer = function(string
, from, to
) {
167 var size
= to
- from;
168 var array
= new Uint8Array(size
);
169 for (var i
= 0; i
!= size
; i
++) {
170 array
[i
] = string
.charCodeAt(from + i
);
176 * A base class for a metadata encoder.
178 * Serves as a default metadata encoder for images that none of the metadata
179 * parsers recognized.
181 * @param {!MetadataItem} originalMetadata Starting metadata.
185 ImageEncoder
.MetadataEncoder = function(originalMetadata
) {
186 var mimeType
= ImageEncoder
.MetadataEncoder
.getMimeType_(originalMetadata
);
189 * Chrome can only encode JPEG and PNG. Force PNG mime type so that we
190 * can save to file and generate a thumbnail.
193 this.mimeType
= mimeType
=== 'image/jpeg' ? 'image/jpeg' : 'image/png';
196 * @protected {string}
198 this.thumbnailDataUrl
= '';
201 * @protected {number}
206 * @protected {number}
208 this.imageHeight
= 0;
212 * Gets mime type from metadata. It reads media.mimeType at first, and if it
213 * fails, it falls back to external.contentMimeType. If both fields are
214 * undefined, it means that metadata is broken. Then it throws an exception.
216 * @param {!MetadataItem} metadata Metadata.
217 * @return {string} Mime type.
220 ImageEncoder
.MetadataEncoder
.getMimeType_ = function(metadata
) {
221 if (metadata
.mediaMimeType
)
222 return metadata
.mediaMimeType
;
223 else if (metadata
.contentMimeType
)
224 return metadata
.contentMimeType
;
230 * Sets an image data.
231 * @param {!HTMLCanvasElement} canvas Canvas or anything with width and height
234 ImageEncoder
.MetadataEncoder
.prototype.setImageData = function(canvas
) {
235 this.imageWidth
= canvas
.width
;
236 this.imageHeight
= canvas
.height
;
240 * @param {HTMLCanvasElement} canvas Canvas to use as thumbnail. Note that it
242 * @param {number} quality Thumbnail quality.
244 ImageEncoder
.MetadataEncoder
.prototype.setThumbnailData
=
245 function(canvas
, quality
) {
246 this.thumbnailDataUrl
=
247 canvas
? canvas
.toDataURL(this.mimeType
, quality
) : '';
251 * Returns a range where the metadata is (or should be) located.
252 * @param {string} encodedImage An encoded image.
253 * @return {{from:number, to:number}} An object with from and to properties.
255 ImageEncoder
.MetadataEncoder
.prototype.
256 findInsertionRange = function(encodedImage
) { return {from: 0, to
: 0}; };
259 * Returns serialized metadata ready to write to an image file.
260 * The return type is optimized for passing to Blob.append.
261 * @return {!ArrayBuffer} Serialized metadata.
263 ImageEncoder
.MetadataEncoder
.prototype.encode = function() {
264 return new Uint8Array(0).buffer
;