blob: 4ca558ea000d70d2e07d1d3e1bb80f5d74ea8eb2 [file] [log] [blame]
// Copyright 2021 The Wuffs Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// ---------------- Magic Numbers
WUFFS_BASE__MAYBE_STATIC int32_t //
wuffs_base__magic_number_guess_fourcc(wuffs_base__slice_u8 prefix) {
// table holds the 'magic numbers' (which are actually variable length
// strings). The strings may contain NUL bytes, so the "const char* magic"
// value starts with the length-minus-1 of the 'magic number'.
//
// Keep it sorted by magic[1], then magic[0] descending and finally by
// magic[2:]. When multiple entries match, the longest one wins.
//
// The fourcc field might be negated, in which case there's further
// specialization (see § below).
static struct {
int32_t fourcc;
const char* magic;
} table[] = {
{-0x30302020, "\x01\x00\x00"}, // '00 'be
{+0x424D5020, "\x01\x42\x4D"}, // BMP
{+0x47494620, "\x03\x47\x49\x46\x38"}, // GIF
{+0x54494646, "\x03\x49\x49\x2A\x00"}, // TIFF (little-endian)
{+0x54494646, "\x03\x4D\x4D\x00\x2A"}, // TIFF (big-endian)
{-0x52494646, "\x03\x52\x49\x46\x46"}, // RIFF
{+0x4E494520, "\x02\x6E\xC3\xAF"}, // NIE
{+0x514F4920, "\x03\x71\x6F\x69\x66"}, // QOI
{+0x504E4720, "\x03\x89\x50\x4E\x47"}, // PNG
{+0x4A504547, "\x01\xFF\xD8"}, // JPEG
};
static const size_t table_len = sizeof(table) / sizeof(table[0]);
if (prefix.len == 0) {
return -1;
}
uint8_t pre_first_byte = prefix.ptr[0];
int32_t fourcc = 0;
size_t i;
for (i = 0; i < table_len; i++) {
uint8_t mag_first_byte = ((uint8_t)(table[i].magic[1]));
if (pre_first_byte < mag_first_byte) {
break;
} else if (pre_first_byte > mag_first_byte) {
continue;
}
fourcc = table[i].fourcc;
uint8_t mag_remaining_len = ((uint8_t)(table[i].magic[0]));
if (mag_remaining_len == 0) {
goto match;
}
const char* mag_remaining_ptr = table[i].magic + 2;
uint8_t* pre_remaining_ptr = prefix.ptr + 1;
size_t pre_remaining_len = prefix.len - 1;
if (pre_remaining_len < mag_remaining_len) {
if (!memcmp(pre_remaining_ptr, mag_remaining_ptr, pre_remaining_len)) {
return -1;
}
} else {
if (!memcmp(pre_remaining_ptr, mag_remaining_ptr, mag_remaining_len)) {
goto match;
}
}
}
return 0;
match:
// Negative FourCC values (see § above) are further specialized.
if (fourcc < 0) {
fourcc = -fourcc;
if (fourcc == 0x52494646) { // 'RIFF'be
if (prefix.len < 12) {
return -1;
}
uint32_t x = wuffs_base__peek_u32be__no_bounds_check(prefix.ptr + 8);
if (x == 0x57454250) { // 'WEBP'be
return 0x57454250; // 'WEBP'be
}
} else if (fourcc == 0x30302020) { // '00 'be
// Binary data starting with multiple 0x00 NUL bytes is quite common.
if (prefix.len < 4) {
return -1;
} else if ((prefix.ptr[2] != 0x00) &&
((prefix.ptr[2] >= 0x80) || (prefix.ptr[3] != 0x00))) {
// Roughly speaking, this could be a non-degenerate (non-0-width and
// non-0-height) WBMP image.
return 0x57424D50; // 'WBMP'be
} else if (((prefix.ptr[2] == 0x01) || (prefix.ptr[2] == 0x02)) &&
(prefix.ptr[3] == 0x00)) {
return 0x49434F20; // 'ICO 'be
}
return 0;
}
}
return fourcc;
}