1// Copyright (c) 2012 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.
4
5'use strict';
6
7/* Base class for image metadata parsers that only need to look at a short
8  fragment at the start of the file */
9function SimpleImageParser(parent, type, urlFilter, headerSize) {
10  ImageParser.call(this, parent, type, urlFilter);
11  this.headerSize = headerSize;
12}
13
14SimpleImageParser.prototype = {__proto__: ImageParser.prototype};
15
16/**
17 * @param {File} file  // TODO(JSDOC).
18 * @param {Object} metadata  // TODO(JSDOC).
19 * @param {function(Object)} callback  // TODO(JSDOC).
20 * @param {function(string)} errorCallback  // TODO(JSDOC).
21 */
22SimpleImageParser.prototype.parse = function(
23    file, metadata, callback, errorCallback) {
24  var self = this;
25  util.readFileBytes(file, 0, this.headerSize,
26    function(file, br) {
27      try {
28        self.parseHeader(metadata, br);
29        callback(metadata);
30      } catch (e) {
31        errorCallback(e.toString());
32      }
33    },
34    errorCallback);
35};
36
37
38function PngParser(parent) {
39  SimpleImageParser.call(this, parent, 'png', /\.png$/i, 24);
40}
41
42PngParser.prototype = {__proto__: SimpleImageParser.prototype};
43
44/**
45 * @param {Object} metadata  // TODO(JSDOC).
46 * @param {ByteReader} br  // TODO(JSDOC).
47 */
48PngParser.prototype.parseHeader = function(metadata, br) {
49  br.setByteOrder(ByteReader.BIG_ENDIAN);
50
51  var signature = br.readString(8);
52  if (signature != '\x89PNG\x0D\x0A\x1A\x0A')
53    throw new Error('Invalid PNG signature: ' + signature);
54
55  br.seek(12);
56  var ihdr = br.readString(4);
57  if (ihdr != 'IHDR')
58    throw new Error('Missing IHDR chunk');
59
60  metadata.width = br.readScalar(4);
61  metadata.height = br.readScalar(4);
62};
63
64MetadataDispatcher.registerParserClass(PngParser);
65
66
67function BmpParser(parent) {
68  SimpleImageParser.call(this, parent, 'bmp', /\.bmp$/i, 28);
69}
70
71BmpParser.prototype = {__proto__: SimpleImageParser.prototype};
72
73/**
74 * @param {Object} metadata  // TODO(JSDOC).
75 * @param {ByteReader} br  // TODO(JSDOC).
76 */
77BmpParser.prototype.parseHeader = function(metadata, br) {
78  br.setByteOrder(ByteReader.LITTLE_ENDIAN);
79
80  var signature = br.readString(2);
81  if (signature != 'BM')
82    throw new Error('Invalid BMP signature: ' + signature);
83
84  br.seek(18);
85  metadata.width = br.readScalar(4);
86  metadata.height = br.readScalar(4);
87};
88
89MetadataDispatcher.registerParserClass(BmpParser);
90
91
92function GifParser(parent) {
93  SimpleImageParser.call(this, parent, 'gif', /\.Gif$/i, 10);
94}
95
96GifParser.prototype = {__proto__: SimpleImageParser.prototype};
97
98/**
99 * @param {Object} metadata  // TODO(JSDOC).
100 * @param {ByteReader} br  // TODO(JSDOC).
101 */
102GifParser.prototype.parseHeader = function(metadata, br) {
103  br.setByteOrder(ByteReader.LITTLE_ENDIAN);
104
105  var signature = br.readString(6);
106  if (!signature.match(/GIF8(7|9)a/))
107    throw new Error('Invalid GIF signature: ' + signature);
108
109  metadata.width = br.readScalar(2);
110  metadata.height = br.readScalar(2);
111};
112
113MetadataDispatcher.registerParserClass(GifParser);
114
115
116function WebpParser(parent) {
117  SimpleImageParser.call(this, parent, 'webp', /\.webp$/i, 30);
118}
119
120WebpParser.prototype = {__proto__: SimpleImageParser.prototype};
121
122/**
123 * @param {Object} metadata  // TODO(JSDOC).
124 * @param {ByteReader} br  // TODO(JSDOC).
125 */
126WebpParser.prototype.parseHeader = function(metadata, br) {
127  br.setByteOrder(ByteReader.LITTLE_ENDIAN);
128
129  var riffSignature = br.readString(4);
130  if (riffSignature != 'RIFF')
131    throw new Error('Invalid RIFF signature: ' + riffSignature);
132
133  br.seek(8);
134  var webpSignature = br.readString(4);
135  if (webpSignature != 'WEBP')
136    throw new Error('Invalid WEBP signature: ' + webpSignature);
137
138  var chunkFormat = br.readString(4);
139  switch (chunkFormat) {
140    // VP8 lossy bitstream format.
141    case 'VP8 ':
142      br.seek(23);
143      var lossySignature = br.readScalar(2) | (br.readScalar(1) << 16);
144      if (lossySignature != 0x2a019d) {
145        throw new Error('Invalid VP8 lossy bitstream signature: ' +
146          lossySignature);
147      }
148      var dimensionBits = br.readScalar(4);
149      metadata.width = dimensionBits & 0x3fff;
150      metadata.height = (dimensionBits >> 16) & 0x3fff;
151      break;
152
153    // VP8 lossless bitstream format.
154    case 'VP8L':
155      br.seek(20);
156      var losslessSignature = br.readScalar(1);
157      if (losslessSignature != 0x2f) {
158        throw new Error('Invalid VP8 lossless bitstream signature: ' +
159          losslessSignature);
160      }
161      var dimensionBits = br.readScalar(4);
162      metadata.width = (dimensionBits & 0x3fff) + 1;
163      metadata.height = ((dimensionBits >> 14) & 0x3fff) + 1;
164      break;
165
166    // VP8 extended file format.
167    case 'VP8X':
168      br.seek(20);
169      // Read 24-bit value. ECMAScript assures left-to-right evaluation order.
170      metadata.width = (br.readScalar(2) | (br.readScalar(1) << 16)) + 1;
171      metadata.height = (br.readScalar(2) | (br.readScalar(1) << 16)) + 1;
172      break;
173
174    default:
175      throw new Error('Invalid chunk format: ' + chunkFormat);
176  }
177};
178
179MetadataDispatcher.registerParserClass(WebpParser);
180
181/**
182 * Parser for the header of .ico icon files.
183 * @param {MetadataDispatcher} parent Parent metadata dispatcher object.
184 * @constructor
185 * @extends SimpleImageParser
186 */
187function IcoParser(parent) {
188  SimpleImageParser.call(this, parent, 'ico', /\.ico$/i, 8);
189}
190
191IcoParser.prototype = {__proto__: SimpleImageParser.prototype};
192
193/**
194 * Parse the binary data as a ico header and stores to metadata.
195 * @param {Object} metadata Dictionary to store the parser metadata.
196 * @param {ByteReader} byteReader Reader for header binary data.
197 */
198IcoParser.prototype.parseHeader = function(metadata, byteReader) {
199  byteReader.setByteOrder(ByteReader.LITTLE_ENDIAN);
200
201  var signature = byteReader.readString(4);
202  if (signature !== '\x00\x00\x00\x01')
203    throw new Error('Invalid ICO signature: ' + signature);
204
205  byteReader.seek(2);
206  metadata.width = byteReader.readScalar(1);
207  metadata.height = byteReader.readScalar(1);
208};
209
210MetadataDispatcher.registerParserClass(IcoParser);
211