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  if (chunkFormat != 'VP8 ' && chunkFormat != 'VP8L')
140    throw new Error('Invalid chunk format: ' + chunkFormat);
141
142  if (chunkFormat == 'VP8 ') {
143    // VP8 lossy bitstream format.
144    br.seek(23);
145    var lossySignature = br.readScalar(2) | (br.readScalar(1) << 16);
146    if (lossySignature != 0x2a019d)
147      throw new Error('Invalid VP8 lossy bitstream signature: ' +
148        lossySignature);
149
150    var dimensionBits = br.readScalar(4);
151    metadata.width = dimensionBits & 0x3fff;
152    metadata.height = (dimensionBits >> 16) & 0x3fff;
153  } else {
154    // VP8 lossless bitstream format.
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  }
165};
166
167MetadataDispatcher.registerParserClass(WebpParser);
168
169/**
170 * Parser for the header of .ico icon files.
171 * @param {MetadataDispatcher} parent Parent metadata dispatcher object.
172 * @constructor
173 * @extends SimpleImageParser
174 */
175function IcoParser(parent) {
176  SimpleImageParser.call(this, parent, 'ico', /\.ico$/i, 8);
177}
178
179IcoParser.prototype = {__proto__: SimpleImageParser.prototype};
180
181/**
182 * Parse the binary data as a ico header and stores to metadata.
183 * @param {Object} metadata Dictionary to store the parser metadata.
184 * @param {ByteReader} byteReader Reader for header binary data.
185 */
186IcoParser.prototype.parseHeader = function(metadata, byteReader) {
187  byteReader.setByteOrder(ByteReader.LITTLE_ENDIAN);
188
189  var signature = byteReader.readString(4);
190  if (signature !== '\x00\x00\x00\x01')
191    throw new Error('Invalid ICO signature: ' + signature);
192
193  byteReader.seek(2);
194  metadata.width = byteReader.readScalar(1);
195  metadata.height = byteReader.readScalar(1);
196};
197
198MetadataDispatcher.registerParserClass(IcoParser);
199