UNPKG

probe-image-size

Version:

Get image size without full download (JPG, GIF, PNG, WebP, BMP, TIFF, PSD)

267 lines (210 loc) 6.1 kB
'use strict' // // Helpers // function error (message, code) { var err = new Error(message) err.code = code return err } function utf8_decode (str) { try { return decodeURIComponent(escape(str)) } catch (_) { return str } } // // Exif parser // // Input: // - jpeg_bin: Uint8Array - jpeg file // - exif_start: Number - start of TIFF header (after Exif\0\0) // - exif_end: Number - end of Exif segment // - on_entry: Number - callback // function ExifParser (jpeg_bin, exif_start, exif_end) { // Uint8Array, exif without signature (which isn't included in offsets) this.input = jpeg_bin.subarray(exif_start, exif_end) // offset correction for `on_entry` callback this.start = exif_start // Check TIFF header (includes byte alignment and first IFD offset) var sig = String.fromCharCode.apply(null, this.input.subarray(0, 4)) if (sig !== 'II\x2A\0' && sig !== 'MM\0\x2A') { throw error('invalid TIFF signature', 'EBADDATA') } // true if motorola (big endian) byte alignment, false if intel this.big_endian = sig[0] === 'M' } ExifParser.prototype.each = function (on_entry) { // allow premature exit this.aborted = false var offset = this.read_uint32(4) this.ifds_to_read = [{ id: 0, offset: offset }] while (this.ifds_to_read.length > 0 && !this.aborted) { var i = this.ifds_to_read.shift() if (!i.offset) continue this.scan_ifd(i.id, i.offset, on_entry) } } ExifParser.prototype.read_uint16 = function (offset) { var d = this.input if (offset + 2 > d.length) throw error('unexpected EOF', 'EBADDATA') return this.big_endian ? d[offset] * 0x100 + d[offset + 1] : d[offset] + d[offset + 1] * 0x100 } ExifParser.prototype.read_uint32 = function (offset) { var d = this.input if (offset + 4 > d.length) throw error('unexpected EOF', 'EBADDATA') return this.big_endian ? d[offset] * 0x1000000 + d[offset + 1] * 0x10000 + d[offset + 2] * 0x100 + d[offset + 3] : d[offset] + d[offset + 1] * 0x100 + d[offset + 2] * 0x10000 + d[offset + 3] * 0x1000000 } ExifParser.prototype.is_subifd_link = function (ifd, tag) { return (ifd === 0 && tag === 0x8769) || // SubIFD (ifd === 0 && tag === 0x8825) || // GPS Info (ifd === 0x8769 && tag === 0xA005) // Interop IFD } // Returns byte length of a single component of a given format // ExifParser.prototype.exif_format_length = function (format) { switch (format) { case 1: // byte case 2: // ascii case 6: // sbyte case 7: // undefined return 1 case 3: // short case 8: // sshort return 2 case 4: // long case 9: // slong case 11: // float return 4 case 5: // rational case 10: // srational case 12: // double return 8 default: // unknown type return 0 } } // Reads Exif data // ExifParser.prototype.exif_format_read = function (format, offset) { var v switch (format) { case 1: // byte case 2: // ascii v = this.input[offset] return v case 6: // sbyte v = this.input[offset] return v | (v & 0x80) * 0x1fffffe case 3: // short v = this.read_uint16(offset) return v case 8: // sshort v = this.read_uint16(offset) return v | (v & 0x8000) * 0x1fffe case 4: // long v = this.read_uint32(offset) return v case 9: // slong v = this.read_uint32(offset) return v | 0 case 5: // rational case 10: // srational case 11: // float case 12: // double return null // not implemented case 7: // undefined return null // blob default: // unknown type return null } } ExifParser.prototype.scan_ifd = function (ifd_no, offset, on_entry) { var entry_count = this.read_uint16(offset) offset += 2 for (var i = 0; i < entry_count; i++) { var tag = this.read_uint16(offset) var format = this.read_uint16(offset + 2) var count = this.read_uint32(offset + 4) var comp_length = this.exif_format_length(format) var data_length = count * comp_length var data_offset = data_length <= 4 ? offset + 8 : this.read_uint32(offset + 8) var is_subifd_link = false if (data_offset + data_length > this.input.length) { throw error('unexpected EOF', 'EBADDATA') } var value = [] var comp_offset = data_offset for (var j = 0; j < count; j++, comp_offset += comp_length) { var item = this.exif_format_read(format, comp_offset) if (item === null) { value = null break } value.push(item) } if (Array.isArray(value) && format === 2) { value = utf8_decode(String.fromCharCode.apply(null, value)) if (value && value[value.length - 1] === '\0') value = value.slice(0, -1) } if (this.is_subifd_link(ifd_no, tag)) { if (Array.isArray(value) && Number.isInteger(value[0]) && value[0] > 0) { this.ifds_to_read.push({ id: tag, offset: value[0] }) is_subifd_link = true } } var entry = { is_big_endian: this.big_endian, ifd: ifd_no, tag: tag, format: format, count: count, entry_offset: offset + this.start, data_length: data_length, data_offset: data_offset + this.start, value: value, is_subifd_link: is_subifd_link } if (on_entry(entry) === false) { this.aborted = true return } offset += 12 } if (ifd_no === 0) { this.ifds_to_read.push({ id: 1, offset: this.read_uint32(offset) }) } } module.exports.ExifParser = ExifParser // returns orientation stored in Exif (1-8), 0 if none was found, -1 if error module.exports.get_orientation = function (data) { var orientation = 0 try { new ExifParser(data, 0, data.length).each(function (entry) { if (entry.ifd === 0 && entry.tag === 0x112 && Array.isArray(entry.value)) { orientation = entry.value[0] return false } }) return orientation } catch (err) { return -1 } }