123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419 |
- //
- // Created by xiaozhuai on 2021/4/1.
- // https://github.com/xiaozhuai/imageinfo
- //
- //
- // MIT License
- //
- // Copyright (c) 2021 xiaozhuai
- //
- // Permission is hereby granted, free of charge, to any person obtaining a copy
- // of this software and associated documentation files (the "Software"), to deal
- // in the Software without restriction, including without limitation the rights
- // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
- // copies of the Software, and to permit persons to whom the Software is
- // furnished to do so, subject to the following conditions:
- //
- // The above copyright notice and this permission notice shall be included in all
- // copies or substantial portions of the Software.
- //
- // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
- // SOFTWARE.
- //
- #pragma once
- #ifndef IMAGEINFO_IMAGEINFO_H
- #define IMAGEINFO_IMAGEINFO_H
- #include <functional>
- #include <algorithm>
- #include <iostream>
- #include <fstream>
- #include <string>
- #include <regex>
- #include <utility>
- #include <unordered_map>
- #include <unordered_set>
- #include <set>
- #include <vector>
- #include <tuple>
- #include <array>
- #include <cstdio>
- #include <cassert>
- #ifdef ANDROID
- #include <android/asset_manager.h>
- #endif
- static_assert(sizeof(uint8_t) == 1, "sizeof(uint8_t) != 1");
- static_assert(sizeof(int8_t) == 1, "sizeof(int8_t) != 1");
- static_assert(sizeof(uint16_t) == 2, "sizeof(uint16_t) != 2");
- static_assert(sizeof(int16_t) == 2, "sizeof(int16_t) != 2");
- static_assert(sizeof(uint32_t) == 4, "sizeof(uint32_t) != 4");
- static_assert(sizeof(int32_t) == 4, "sizeof(int32_t) != 4");
- static_assert(sizeof(uint64_t) == 8, "sizeof(uint64_t) != 8");
- static_assert(sizeof(int64_t) == 8, "sizeof(int64_t) != 8");
- #ifdef __clang__
- #pragma clang diagnostic push
- #pragma ide diagnostic ignored "OCUnusedStructInspection"
- #pragma ide diagnostic ignored "OCUnusedGlobalDeclarationInspection"
- #endif
- enum IIFormat {
- II_FORMAT_UNKNOWN = 0,
- II_FORMAT_AVIF,
- II_FORMAT_BMP,
- II_FORMAT_CUR,
- II_FORMAT_DDS,
- II_FORMAT_GIF,
- II_FORMAT_HDR,
- II_FORMAT_HEIC,
- II_FORMAT_ICNS,
- II_FORMAT_ICO,
- II_FORMAT_JP2,
- II_FORMAT_JPEG,
- II_FORMAT_JPX,
- II_FORMAT_KTX,
- II_FORMAT_PNG,
- II_FORMAT_PSD,
- II_FORMAT_TGA,
- II_FORMAT_TIFF,
- II_FORMAT_WEBP,
- };
- enum IIErrorCode {
- II_ERR_OK = 0,
- II_ERR_UNRECOGNIZED_FORMAT,
- II_ERR_DECODE_SIZE_FAILED,
- };
- #ifdef ANDROID
- class IIAndroidAssetFileReader {
- public:
- explicit IIAndroidAssetFileReader(AAsset *file) : m_file(file) {}
- inline size_t size() const {
- if (m_file != nullptr) {
- return AAsset_getLength(m_file);
- } else {
- return 0;
- }
- }
- inline void read(void *buf, off_t offset, size_t size) {
- AAsset_seek(m_file, offset, SEEK_SET);
- AAsset_read(m_file, buf, size);
- }
- private:
- AAsset *m_file = nullptr;
- };
- #endif
- class IIFileReader {
- public:
- explicit IIFileReader(FILE *file) : m_file(file) {}
- inline size_t size() {
- if (m_file != nullptr) {
- fseek(m_file, 0, SEEK_END);
- return ftell(m_file);
- } else {
- return 0;
- }
- }
- inline void read(void *buf, off_t offset, size_t size) {
- fseek(m_file, offset, SEEK_SET);
- fread(buf, 1, size, m_file);
- }
- private:
- FILE *m_file = nullptr;
- };
- class IIFilePathReader {
- public:
- explicit IIFilePathReader(const std::string &path) : m_file(path, std::ios::in | std::ios::binary) {}
- ~IIFilePathReader() {
- if (m_file.is_open()) {
- m_file.close();
- }
- }
- inline size_t size() {
- if (m_file.is_open()) {
- m_file.seekg(0, std::ios::end);
- return (size_t) m_file.tellg();
- } else {
- return 0;
- }
- }
- inline void read(void *buf, off_t offset, size_t size) {
- m_file.seekg(offset, std::ios::beg);
- m_file.read((char *) buf, (std::streamsize) size);
- }
- private:
- std::ifstream m_file;
- };
- class IIFileStreamReader {
- public:
- explicit IIFileStreamReader(std::ifstream &file) : m_file(file) {}
- explicit IIFileStreamReader(std::ifstream *file) : m_file(*file) {}
- inline size_t size() {
- if (m_file.is_open()) {
- m_file.seekg(0, std::ios::end);
- return (size_t) m_file.tellg();
- } else {
- return 0;
- }
- }
- inline void read(void *buf, off_t offset, size_t size) {
- m_file.seekg(offset, std::ios::beg);
- m_file.read((char *) buf, (std::streamsize) size);
- }
- private:
- std::ifstream &m_file;
- };
- struct IIRawData {
- IIRawData(const void *d, size_t s) : data(d), length(s) {}
- const void *data = nullptr;
- size_t length = 0;
- };
- class IIRawDataReader {
- public:
- explicit IIRawDataReader(IIRawData data) : m_data(data) {}
- inline size_t size() const {
- return m_data.length;
- }
- inline void read(void *buf, off_t offset, size_t size) const {
- memcpy(buf, ((char *) m_data.data) + offset, size);
- }
- private:
- IIRawData m_data;
- };
- class IIBuffer {
- public:
- IIBuffer() = default;
- explicit IIBuffer(size_t size) : m_size(size) {
- m_data = std::shared_ptr<uint8_t>(new uint8_t[size], std::default_delete<uint8_t[]>());
- }
- inline const uint8_t *data() const {
- return m_data.get();
- }
- inline uint8_t *data() {
- return m_data.get();
- }
- inline size_t size() const {
- return m_size;
- }
- inline uint8_t &operator[](int offset) {
- return m_data.get()[offset];
- }
- inline uint8_t operator[](int offset) const {
- return m_data.get()[offset];
- }
- public:
- inline uint8_t readU8(off_t offset) {
- return readInt<uint8_t>(offset, false);
- }
- inline int8_t readS8(off_t offset) {
- return readInt<int8_t>(offset, false);
- }
- inline uint16_t readU16LE(off_t offset) {
- return readInt<uint16_t>(offset, false);
- }
- inline uint16_t readU16BE(off_t offset) {
- return readInt<uint16_t>(offset, true);
- }
- inline int16_t readS16LE(off_t offset) {
- return readInt<int16_t>(offset, false);
- }
- inline int16_t readS16BE(off_t offset) {
- return readInt<int16_t>(offset, true);
- }
- inline uint32_t readU32LE(off_t offset) {
- return readInt<uint32_t>(offset, false);
- }
- inline uint32_t readU32BE(off_t offset) {
- return readInt<uint32_t>(offset, true);
- }
- inline int32_t readS32LE(off_t offset) {
- return readInt<int32_t>(offset, false);
- }
- inline int32_t readS32BE(off_t offset) {
- return readInt<int32_t>(offset, true);
- }
- inline uint64_t readU64LE(off_t offset) {
- return readInt<uint64_t>(offset, false);
- }
- inline uint64_t readU64BE(off_t offset) {
- return readInt<uint64_t>(offset, true);
- }
- inline int64_t readS64LE(off_t offset) {
- return readInt<int64_t>(offset, false);
- }
- inline int64_t readS64BE(off_t offset) {
- return readInt<int64_t>(offset, true);
- }
- template<typename T>
- inline T readInt(off_t offset, bool swapEndian = false) {
- T val = *((T *) (data() + offset));
- return swapEndian ? swapE<T>(val) : val;
- }
- inline std::string readString(off_t offset, size_t size) {
- return std::string((char *) data() + offset, size);
- }
- inline std::string toString() {
- return std::string((char *) data(), size());
- }
- inline bool cmp(off_t offset, size_t size, const void *buf) {
- return memcmp(data() + offset, buf, size) == 0;
- }
- inline bool cmpAnyOf(off_t offset, size_t size, const std::initializer_list<const void *> &bufList) {
- return std::any_of(bufList.begin(), bufList.end(), [this, offset, size](const void *buf) {
- return memcmp(data() + offset, buf, size) == 0;
- });
- }
- private:
- template<typename T>
- static T swapE(T u) {
- union {
- T u;
- uint8_t u8[sizeof(T)];
- } source{}, dest{};
- source.u = u;
- for (size_t k = 0; k < sizeof(T); k++)
- dest.u8[k] = source.u8[sizeof(T) - k - 1];
- return dest.u;
- }
- private:
- std::shared_ptr<uint8_t> m_data = nullptr;
- size_t m_size = 0;
- };
- typedef std::function<void(void *buf, off_t offset, size_t size)> IIReadFunc;
- class IIReadInterface {
- public:
- IIReadInterface() = delete;
- IIReadInterface(IIReadFunc &readFunc, size_t length)
- : m_readFunc(readFunc),
- m_length(length) {}
- inline IIBuffer readBuffer(off_t offset, size_t size) {
- assert(offset >= 0);
- assert(offset + size <= m_length);
- IIBuffer buffer(size);
- read(buffer.data(), offset, size);
- return buffer;
- }
- inline size_t length() const {
- return m_length;
- }
- private:
- inline void read(void *buf, off_t offset, size_t size) {
- m_readFunc(buf, offset, size);
- }
- private:
- IIReadFunc &m_readFunc;
- size_t m_length = 0;
- };
- typedef std::function<bool(size_t length, IIReadInterface &ri,
- int64_t &width, int64_t &height,
- std::vector<std::array<int64_t, 2>> &entrySizes)> IIProcessFunc;
- struct IIDetector {
- IIDetector(IIFormat f, const char *e, const char *fe, const char *mt, IIProcessFunc p)
- : format(f),
- ext(e),
- fullExt(fe),
- mimetype(mt),
- process(std::move(p)) {}
- IIFormat format;
- const char *ext;
- const char *fullExt;
- const char *mimetype;
- IIProcessFunc process;
- };
- static const std::vector<IIDetector> s_ii_detectors = { // NOLINT(cert-err58-cpp)
- ///////////////////////// AVIF /////////////////////////
- IIDetector(
- II_FORMAT_AVIF,
- "avif",
- "avif",
- "image/avif",
- [](size_t length, IIReadInterface &ri,
- int64_t &width, int64_t &height,
- std::vector<std::array<int64_t, 2>> &entrySizes) -> bool {
- if (length < 4) {
- return false;
- }
- auto buffer = ri.readBuffer(0, 4);
- uint32_t ftypBoxLength = buffer.readU32BE(0);
- if (length < ftypBoxLength + 12) {
- return false;
- }
- buffer = ri.readBuffer(0, ftypBoxLength + 12);
- if (!buffer.cmp(4, 4, "ftyp")) {
- return false;
- }
- /**
- * Major Brand
- *
- * AVIF: "avif"
- * HEIF: "mif1", "msf1"
- * HEIC: "heic", "heix", "hevc", "hevx"
- *
- */
- if (!buffer.cmpAnyOf(8, 4, {"avif", "mif1", "msf1", "heic", "heix", "hevc", "hevx"})) {
- return false;
- }
- uint32_t compatibleBrandSize = (ftypBoxLength - 16) / 4;
- std::unordered_set<std::string> compatibleBrands;
- for (uint32_t i = 0; i < compatibleBrandSize; ++i) {
- compatibleBrands.insert(buffer.readString(16 + i * 4, 4));
- }
- // same as heic, compatibleBrands contains "avif"
- if (compatibleBrands.find("avif") == compatibleBrands.end()) {
- return false;
- }
- if (!buffer.cmp(ftypBoxLength + 4, 4, "meta")) {
- return false;
- }
- uint32_t metaLength = buffer.readU32BE(ftypBoxLength);
- if (length < ftypBoxLength + 12 + metaLength) {
- return false;
- }
- buffer = ri.readBuffer(ftypBoxLength + 12, metaLength);
- off_t offset = 0;
- off_t end = metaLength;
- loop_box:
- /**
- * find ispe box
- *
- * meta
- * - ...
- * - iprp
- * - ...
- * - ipco
- * - ...
- * - ispe
- */
- while (offset < end) {
- // std::string boxType = buffer.readString(offset + 4, 4);
- uint32_t boxSize = buffer.readU32BE(offset);
- // std::cout << boxSize << ", " << boxType << "\n";
- if (buffer.cmpAnyOf(offset + 4, 4, {"iprp", "ipco"})) {
- end = offset + boxSize;
- offset += 8;
- goto loop_box;
- }
- if (buffer.cmp(offset + 4, 4, "ispe")) {
- width = buffer.readU32BE(offset + 12);
- height = buffer.readU32BE(offset + 16);
- break;
- }
- offset += boxSize;
- }
- return true;
- }
- ),
- ///////////////////////// BMP /////////////////////////
- // https://www.fileformat.info/format/bmp/corion.htm
- IIDetector(
- II_FORMAT_BMP,
- "bmp",
- "bmp",
- "image/bmp",
- [](size_t length, IIReadInterface &ri,
- int64_t &width, int64_t &height,
- std::vector<std::array<int64_t, 2>> &entrySizes) -> bool {
- if (length < 26) {
- return false;
- }
- auto buffer = ri.readBuffer(0, 26);
- if (!buffer.cmp(0, 2, "BM")) {
- return false;
- }
- width = buffer.readS32LE(18);
- // bmp height can be negative, it means flip Y
- height = std::abs(buffer.readS32LE(22));
- return true;
- }
- ),
- ///////////////////////// CUR /////////////////////////
- IIDetector(
- II_FORMAT_CUR,
- "cur",
- "cur",
- "image/cur",
- [](size_t length, IIReadInterface &ri,
- int64_t &width, int64_t &height,
- std::vector<std::array<int64_t, 2>> &entrySizes) -> bool {
- if (length < 6) {
- return false;
- }
- auto buffer = ri.readBuffer(0, 6);
- // Same with ico, but TYPE == 2
- if (!buffer.cmp(0, 4, "\x00\x00\x02\x00")) {
- return false;
- }
- uint16_t entryCount = buffer.readU16LE(4);
- if (entryCount == 0) {
- return false;
- }
- const int ENTRY_SIZE = 16;
- off_t entryTotalSize = entryCount * ENTRY_SIZE;
- off_t offset = 6;
- if (length < offset + entryTotalSize) {
- return false;
- }
- buffer = ri.readBuffer(offset, entryTotalSize);
- offset += entryTotalSize;
- std::vector<std::array<int64_t, 2>> sizes;
- for (int i = 0; i < entryCount; ++i) {
- uint8_t w1 = buffer.readU8(i * ENTRY_SIZE);
- uint8_t h1 = buffer.readU8(i * ENTRY_SIZE + 1);
- int64_t w2 = w1 == 0 ? 256 : w1;
- int64_t h2 = h1 == 0 ? 256 : h1;
- sizes.push_back({w2, h2});
- uint32_t bytes = buffer.readS32LE(i * ENTRY_SIZE + 8);
- offset += bytes;
- }
- if (length < (size_t) offset) {
- return false;
- }
- width = sizes.front()[0];
- height = sizes.front()[1];
- sizes.swap(entrySizes);
- return true;
- }
- ),
- ///////////////////////// DDS /////////////////////////
- IIDetector(
- II_FORMAT_DDS,
- "dds",
- "dds",
- "image/dds",
- [](size_t length, IIReadInterface &ri,
- int64_t &width, int64_t &height,
- std::vector<std::array<int64_t, 2>> &entrySizes) -> bool {
- if (length < 20) {
- return false;
- }
- auto buffer = ri.readBuffer(0, 20);
- if (!buffer.cmp(0, 4, "DDS ")) {
- return false;
- }
- height = buffer.readU32LE(12);
- width = buffer.readU32LE(16);
- return true;
- }
- ),
- ///////////////////////// GIF /////////////////////////
- // https://www.fileformat.info/format/gif/corion.htm
- IIDetector(
- II_FORMAT_GIF,
- "gif",
- "gif",
- "image/gif",
- [](size_t length, IIReadInterface &ri,
- int64_t &width, int64_t &height,
- std::vector<std::array<int64_t, 2>> &entrySizes) -> bool {
- if (length < 10) {
- return false;
- }
- auto buffer = ri.readBuffer(0, 10);
- if (!buffer.cmpAnyOf(0, 6, {"GIF87a", "GIF89a"})) {
- return false;
- }
- width = buffer.readU16LE(6);
- height = buffer.readU16LE(8);
- return true;
- }
- ),
- ///////////////////////// HDR /////////////////////////
- // http://paulbourke.net/dataformats/pic/
- IIDetector(
- II_FORMAT_HDR,
- "hdr",
- "hdr",
- "image/vnd.radiance",
- [](size_t length, IIReadInterface &ri,
- int64_t &width, int64_t &height,
- std::vector<std::array<int64_t, 2>> &entrySizes) -> bool {
- if (length < 6) {
- return false;
- }
- // TODO Max header size ? Or just read header line by line
- auto buffer = ri.readBuffer(0, std::min<size_t>(length, 256));
- if (!buffer.cmpAnyOf(0, 6, {"#?RGBE", "#?XYZE"})) {
- return false;
- }
- auto header = buffer.toString();
- std::smatch results;
- static const std::regex XPattern(R"(\s(\-|\+)X\s(\d+)\s)");
- static const std::regex YPattern(R"(\s(\-|\+)Y\s(\d+)\s)");
- std::string widthStr;
- std::string heightStr;
- std::regex_search(header, results, XPattern);
- if (results.size() >= 3) widthStr = results.str(2);
- std::regex_search(header, results, YPattern);
- if (results.size() >= 3) heightStr = results.str(2);
- if (!widthStr.empty() && !heightStr.empty()) {
- width = std::stol(widthStr);
- height = std::stol(heightStr);
- }
- return true;
- }
- ),
- ///////////////////////// HEIC /////////////////////////
- // https://nokiatech.github.io/heif/technical.html
- // https://www.jianshu.com/p/b016d10a087d
- IIDetector(
- II_FORMAT_HEIC,
- "heic",
- "heic",
- "image/heic",
- [](size_t length, IIReadInterface &ri,
- int64_t &width, int64_t &height,
- std::vector<std::array<int64_t, 2>> &entrySizes) -> bool {
- if (length < 4) {
- return false;
- }
- auto buffer = ri.readBuffer(0, 4);
- uint32_t ftypBoxLength = buffer.readU32BE(0);
- if (length < ftypBoxLength + 12) {
- return false;
- }
- buffer = ri.readBuffer(0, ftypBoxLength + 12);
- if (!buffer.cmp(4, 4, "ftyp")) {
- return false;
- }
- /**
- * Major Brand
- *
- * AVIF: "avif"
- * HEIF: "mif1", "msf1"
- * HEIC: "heic", "heix", "hevc", "hevx"
- *
- */
- if (!buffer.cmpAnyOf(8, 4, {"avif", "mif1", "msf1", "heic", "heix", "hevc", "hevx"})) {
- return false;
- }
- uint32_t compatibleBrandSize = (ftypBoxLength - 16) / 4;
- std::unordered_set<std::string> compatibleBrands;
- for (uint32_t i = 0; i < compatibleBrandSize; ++i) {
- compatibleBrands.insert(buffer.readString(16 + i * 4, 4));
- }
- // compatibleBrands contains "heic"
- if (compatibleBrands.find("heic") == compatibleBrands.end()) {
- return false;
- }
- if (!buffer.cmp(ftypBoxLength + 4, 4, "meta")) {
- return false;
- }
- uint32_t metaLength = buffer.readU32BE(ftypBoxLength);
- if (length < ftypBoxLength + 12 + metaLength) {
- return false;
- }
- buffer = ri.readBuffer(ftypBoxLength + 12, metaLength);
- off_t offset = 0;
- off_t end = metaLength;
- loop_box:
- /**
- * find ispe box
- *
- * meta
- * - ...
- * - iprp
- * - ...
- * - ipco
- * - ...
- * - ispe
- */
- while (offset < end) {
- // std::string boxType = buffer.readString(offset + 4, 4);
- uint32_t boxSize = buffer.readU32BE(offset);
- // std::cout << boxSize << ", " << boxType << "\n";
- if (buffer.cmpAnyOf(offset + 4, 4, {"iprp", "ipco"})) {
- end = offset + boxSize;
- offset += 8;
- goto loop_box;
- }
- if (buffer.cmp(offset + 4, 4, "ispe")) {
- width = buffer.readU32BE(offset + 12);
- height = buffer.readU32BE(offset + 16);
- break;
- }
- offset += boxSize;
- }
- return true;
- }
- ),
- ///////////////////////// ICNS /////////////////////////
- IIDetector(
- II_FORMAT_ICNS,
- "icns",
- "icns",
- "image/icns",
- [](size_t length, IIReadInterface &ri,
- int64_t &width, int64_t &height,
- std::vector<std::array<int64_t, 2>> &entrySizes) -> bool {
- if (length < 8) {
- return false;
- }
- auto buffer = ri.readBuffer(0, 8);
- uint32_t fileLength = buffer.readU32BE(4);
- if (!buffer.cmp(0, 4, "icns") || fileLength != length) {
- return false;
- }
- static const std::unordered_map<std::string, int64_t> TYPE_SIZE_MAP = {
- {"ICON", 32},
- {"ICN#", 32},
- {"icm#", 16},
- {"icm4", 16},
- {"icm8", 16},
- {"ics#", 16},
- {"ics4", 16},
- {"ics8", 16},
- {"is32", 16},
- {"s8mk", 16},
- {"icl4", 32},
- {"icl8", 32},
- {"il32", 32},
- {"l8mk", 32},
- {"ich#", 48},
- {"ich4", 48},
- {"ich8", 48},
- {"ih32", 48},
- {"h8mk", 48},
- {"it32", 128},
- {"t8mk", 128},
- {"icp4", 16},
- {"icp5", 32},
- {"icp6", 64},
- {"ic07", 128},
- {"ic08", 256},
- {"ic09", 512},
- {"ic10", 1024},
- {"ic11", 32},
- {"ic12", 64},
- {"ic13", 256},
- {"ic14", 512},
- {"ic04", 16},
- {"ic05", 32},
- {"icsB", 36},
- {"icsb", 18},
- };
- int64_t maxSize = 0;
- off_t offset = 8;
- while (offset + 8 <= length) {
- buffer = ri.readBuffer(offset, 8);
- auto type = buffer.readString(0, 4);
- uint32_t entrySize = buffer.readU32BE(4);
- int64_t s = TYPE_SIZE_MAP.at(type);
- entrySizes.push_back({s, s});
- maxSize = std::max(maxSize, s);
- offset += entrySize;
- }
- width = maxSize;
- height = maxSize;
- return true;
- }
- ),
- ///////////////////////// ICO /////////////////////////
- IIDetector(
- II_FORMAT_ICO,
- "ico",
- "ico",
- "image/ico",
- [](size_t length, IIReadInterface &ri,
- int64_t &width, int64_t &height,
- std::vector<std::array<int64_t, 2>> &entrySizes) -> bool {
- if (length < 6) {
- return false;
- }
- auto buffer = ri.readBuffer(0, 6);
- // TYPE == 1
- if (!buffer.cmp(0, 4, "\x00\x00\x01\x00")) {
- return false;
- }
- uint16_t entryCount = buffer.readU16LE(4);
- if (entryCount == 0) {
- return false;
- }
- const int ENTRY_SIZE = 16;
- off_t entryTotalSize = entryCount * ENTRY_SIZE;
- off_t offset = 6;
- if (length < offset + entryTotalSize) {
- return false;
- }
- buffer = ri.readBuffer(offset, entryTotalSize);
- offset += entryTotalSize;
- std::vector<std::array<int64_t, 2>> sizes;
- for (int i = 0; i < entryCount; ++i) {
- uint8_t w1 = buffer.readU8(i * ENTRY_SIZE);
- uint8_t h1 = buffer.readU8(i * ENTRY_SIZE + 1);
- int64_t w2 = w1 == 0 ? 256 : w1;
- int64_t h2 = h1 == 0 ? 256 : h1;
- sizes.push_back({w2, h2});
- uint32_t bytes = buffer.readS32LE(i * ENTRY_SIZE + 8);
- offset += bytes;
- }
- if (length < (size_t) offset) {
- return false;
- }
- width = sizes.front()[0];
- height = sizes.front()[1];
- sizes.swap(entrySizes);
- return true;
- }
- ),
- ///////////////////////// JP2 /////////////////////////
- // https://docs.fileformat.com/image/jp2/
- IIDetector(
- II_FORMAT_JP2,
- "jp2",
- "jp2",
- "image/jp2",
- [](size_t length, IIReadInterface &ri,
- int64_t &width, int64_t &height,
- std::vector<std::array<int64_t, 2>> &entrySizes) -> bool {
- if (length < 8) {
- return false;
- }
- auto buffer = ri.readBuffer(0, 8);
- if (!buffer.cmp(4, 4, "jP ")) {
- return false;
- }
- uint32_t signatureLength = buffer.readU32BE(0);
- off_t offset = signatureLength;
- if (length < offset + 12) {
- return false;
- }
- buffer = ri.readBuffer(offset, 12);
- // type == "jp2 "
- if (!buffer.cmp(4, 4, "ftyp") || !buffer.cmp(8, 4, "jp2 ")) {
- return false;
- }
- uint32_t ftypLength = buffer.readU32BE(0);
- offset += ftypLength;
- while (offset + 24 <= length) {
- buffer = ri.readBuffer(offset, 24);
- if (buffer.cmp(4, 4, "jp2h")) {
- if (buffer.cmp(12, 4, "ihdr")) {
- height = buffer.readU32BE(16);
- width = buffer.readU32BE(20);
- }
- break;
- }
- uint32_t boxLength = buffer.readU32BE(0);
- offset += boxLength;
- }
- return true;
- }
- ),
- ///////////////////////// JPEG /////////////////////////
- // https://www.fileformat.info/format/jpeg/corion.htm
- IIDetector(
- II_FORMAT_JPEG,
- "jpg",
- "jpeg",
- "image/jpeg",
- [](size_t length, IIReadInterface &ri,
- int64_t &width, int64_t &height,
- std::vector<std::array<int64_t, 2>> &entrySizes) -> bool {
- if (length < 2) {
- return false;
- }
- auto buffer = ri.readBuffer(0, 2);
- if (!buffer.cmp(0, 2, "\xFF\xD8")) {
- return false;
- }
- off_t offset = 2;
- while (offset + 9 <= length) {
- buffer = ri.readBuffer(offset, 9);
- uint16_t sectionSize = buffer.readU16BE(2);
- // 0xFFC0 is baseline standard (SOF0)
- // 0xFFC1 is baseline optimized (SOF1)
- // 0xFFC2 is progressive (SOF2)
- if (buffer.cmpAnyOf(0, 2, {"\xFF\xC0", "\xFF\xC1", "\xFF\xC2"})) {
- height = buffer.readU16BE(5);
- width = buffer.readU16BE(7);
- break;
- }
- offset += sectionSize + 2;
- }
- return true;
- }
- ),
- ///////////////////////// JPX /////////////////////////
- // https://docs.fileformat.com/image/jpx/
- IIDetector(
- II_FORMAT_JPX,
- "jpx",
- "jpx",
- "image/jpx",
- [](size_t length, IIReadInterface &ri,
- int64_t &width, int64_t &height,
- std::vector<std::array<int64_t, 2>> &entrySizes) -> bool {
- if (length < 8) {
- return false;
- }
- auto buffer = ri.readBuffer(0, 8);
- if (!buffer.cmp(4, 4, "jP ")) {
- return false;
- }
- uint32_t signatureLength = buffer.readU32BE(0);
- off_t offset = signatureLength;
- if (length < offset + 12) {
- return false;
- }
- buffer = ri.readBuffer(offset, 12);
- // same as jp2, type == "jpx "
- if (!buffer.cmp(4, 4, "ftyp") || !buffer.cmp(8, 4, "jpx ")) {
- return false;
- }
- uint32_t ftypLength = buffer.readU32BE(0);
- offset += ftypLength;
- while (offset + 24 <= length) {
- buffer = ri.readBuffer(offset, 24);
- if (buffer.cmp(4, 4, "jp2h")) {
- if (buffer.cmp(12, 4, "ihdr")) {
- height = buffer.readU32BE(16);
- width = buffer.readU32BE(20);
- }
- break;
- }
- uint32_t boxLength = buffer.readU32BE(0);
- offset += boxLength;
- }
- return true;
- }
- ),
- ///////////////////////// KTX /////////////////////////
- IIDetector(
- II_FORMAT_KTX,
- "ktx",
- "ktx",
- "image/ktx",
- [](size_t length, IIReadInterface &ri,
- int64_t &width, int64_t &height,
- std::vector<std::array<int64_t, 2>> &entrySizes) -> bool {
- if (length < 44) {
- return false;
- }
- auto buffer = ri.readBuffer(0, 44);
- if (!buffer.cmp(1, 6, "KTX 11")) {
- return false;
- }
- width = buffer.readU32LE(36);
- height = buffer.readU32LE(40);
- return true;
- }
- ),
- ///////////////////////// PNG /////////////////////////
- // https://www.fileformat.info/format/png/corion.htm
- IIDetector(
- II_FORMAT_PNG,
- "png",
- "png",
- "image/png",
- [](size_t length, IIReadInterface &ri,
- int64_t &width, int64_t &height,
- std::vector<std::array<int64_t, 2>> &entrySizes) -> bool {
- if (length < 4) {
- return false;
- }
- auto buffer = ri.readBuffer(0, std::min<size_t>(length, 40));
- if (!buffer.cmp(0, 4, "\x89PNG")) {
- return false;
- }
- std::string firstChunkType = buffer.readString(12, 4);
- if (firstChunkType == "IHDR" && buffer.size() >= 24) {
- width = buffer.readU32BE(16);
- height = buffer.readU32BE(20);
- } else if (firstChunkType == "CgBI") {
- if (buffer.readString(28, 4) == "IHDR" && buffer.size() >= 40) {
- width = buffer.readU32BE(32);
- height = buffer.readU32BE(36);
- }
- }
- return true;
- }
- ),
- ///////////////////////// PSD /////////////////////////
- IIDetector(
- II_FORMAT_PSD,
- "psd",
- "psd",
- "image/psd",
- [](size_t length, IIReadInterface &ri,
- int64_t &width, int64_t &height,
- std::vector<std::array<int64_t, 2>> &entrySizes) -> bool {
- if (length < 22) {
- return false;
- }
- auto buffer = ri.readBuffer(0, 22);
- if (!buffer.cmp(0, 6, "8BPS\x00\x01")) {
- return false;
- }
- height = buffer.readU32BE(14);
- width = buffer.readU32BE(18);
- return true;
- }
- ),
- ///////////////////////// TIFF /////////////////////////
- // https://www.fileformat.info/format/tiff/corion.htm
- IIDetector(
- II_FORMAT_TIFF,
- "tif",
- "tiff",
- "image/tiff",
- [](size_t length, IIReadInterface &ri,
- int64_t &width, int64_t &height,
- std::vector<std::array<int64_t, 2>> &entrySizes) -> bool {
- if (length < 8) {
- return false;
- }
- auto buffer = ri.readBuffer(0, 8);
- if (!buffer.cmpAnyOf(0, 4, {"\x49\x49\x2A\x00", "\x4D\x4D\x00\x2A"})) {
- return false;
- }
- bool swapEndian = buffer[0] == 0x4D;
- auto offset = buffer.readInt<uint32_t>(4, swapEndian);
- if (length < offset + 2) {
- return true;
- }
- buffer = ri.readBuffer(offset, 2);
- auto numEntry = buffer.readInt<uint16_t>(0, swapEndian);
- offset += 2;
- for (uint16_t i = 0;
- i < numEntry
- && length >= offset + 12
- && (width == -1 || height == -1);
- ++i, offset += 12) {
- buffer = ri.readBuffer(offset, 12);
- auto tag = buffer.readInt<uint16_t>(0, swapEndian);
- auto type = buffer.readInt<uint16_t>(2, swapEndian);
- if (tag == 256) { // Found ImageWidth entry
- if (type == 3) {
- width = buffer.readInt<uint16_t>(8, swapEndian);
- } else if (type == 4) {
- width = buffer.readInt<uint32_t>(8, swapEndian);
- }
- } else if (tag == 257) { // Found ImageHeight entry
- if (type == 3) {
- height = buffer.readInt<uint16_t>(8, swapEndian);
- } else if (type == 4) {
- height = buffer.readInt<uint32_t>(8, swapEndian);
- }
- }
- }
- return true;
- }
- ),
- ///////////////////////// WEBP /////////////////////////
- // https://developers.google.com/speed/webp/docs/riff_container
- IIDetector(
- II_FORMAT_WEBP,
- "webp",
- "webp",
- "image/webp",
- [](size_t length, IIReadInterface &ri,
- int64_t &width, int64_t &height,
- std::vector<std::array<int64_t, 2>> &entrySizes) -> bool {
- if (length < 16) {
- return false;
- }
- auto buffer = ri.readBuffer(0, std::min<size_t>(length, 30));
- if (!buffer.cmp(0, 4, "RIFF") || !buffer.cmp(8, 4, "WEBP")) {
- return false;
- }
- std::string type = buffer.readString(12, 4);
- if (type == "VP8 " && buffer.size() >= 30) {
- width = buffer.readU16LE(26) & 0x3FFF;
- height = buffer.readU16LE(28) & 0x3FFF;
- } else if (type == "VP8L" && buffer.size() >= 25) {
- uint32_t n = buffer.readU32LE(21);
- width = (n & 0x3FFF) + 1;
- height = ((n >> 14) & 0x3FFF) + 1;
- } else if (type == "VP8X" && buffer.size() >= 30) {
- uint8_t extendedHeader = buffer.readU8(20);
- bool validStart = (extendedHeader & 0xc0) == 0;
- bool validEnd = (extendedHeader & 0x01) == 0;
- if (validStart && validEnd) {
- width = (buffer.readU32LE(24) & 0x00FFFFFF) + 1;
- height = ((buffer.readU32LE(26) & 0xFFFFFF00) >> 8) + 1;
- } else {
- // Invalid
- }
- }
- return true;
- }
- ),
- ///////////////////////// TGA /////////////////////////
- // TODO Not rigorous enough, keep it as last detector
- // https://www.fileformat.info/format/tga/corion.htm
- IIDetector(
- II_FORMAT_TGA,
- "tga",
- "tga",
- "image/tga",
- [](size_t length, IIReadInterface &ri,
- int64_t &width, int64_t &height,
- std::vector<std::array<int64_t, 2>> &entrySizes) -> bool {
- if (length < 18) {
- return false;
- }
- auto buffer = ri.readBuffer((off_t) (length - 18), 18);
- if (buffer.cmp(0, 18, "TRUEVISION-XFILE.\x00")) {
- buffer = ri.readBuffer(0, 18);
- width = buffer.readU16LE(12);
- height = buffer.readU16LE(14);
- return true;
- }
- buffer = ri.readBuffer(0, 18);
- uint8_t idLen = buffer.readU8(0);
- if (length < (size_t) idLen + 18) {
- return false;
- }
- uint8_t colorMapType = buffer.readU8(1);
- uint8_t imageType = buffer.readU8(2);
- uint16_t firstColorMapEntryIndex = buffer.readU16LE(3);
- uint16_t colorMapLength = buffer.readU16LE(5);
- uint8_t colorMapEntrySize = buffer.readU8(7);
- // uint16_t xOrigin = buffer.readU16LE(8);
- // uint16_t yOrigin = buffer.readU16LE(10);
- uint16_t w = buffer.readU16LE(12);
- uint16_t h = buffer.readU16LE(14);
- // uint8_t pixelDepth = buffer.readU8(16);
- // uint8_t flags = buffer.readU8(17);
- if (colorMapType == 0) { // no color map
- if (imageType == 0
- || imageType == 2
- || imageType == 3
- || imageType == 10
- || imageType == 11
- || imageType == 32
- || imageType == 33) {
- if (firstColorMapEntryIndex == 0
- && colorMapLength == 0
- && colorMapEntrySize == 0) {
- width = w;
- height = h;
- return true;
- }
- }
- } else if (colorMapType == 1) { // 256 entry palette
- if (imageType == 1
- || imageType == 9) {
- width = w;
- height = h;
- return true;
- }
- }
- return false;
- }
- ),
- };
- class ImageInfo {
- public:
- ImageInfo() = delete;
- explicit ImageInfo(IIReadInterface &ri, IIFormat likelyFormat = II_FORMAT_UNKNOWN, bool mustBe = false) {
- if (likelyFormat != II_FORMAT_UNKNOWN) {
- for (const auto &detector : s_ii_detectors) {
- if (detector.format == likelyFormat) {
- if (tryDetector(detector, ri)) return;
- break;
- }
- }
- if (mustBe) {
- m_err = II_ERR_UNRECOGNIZED_FORMAT;
- return;
- }
- }
- for (const auto &detector : s_ii_detectors) {
- if (detector.format == likelyFormat) continue;
- if (tryDetector(detector, ri)) return;
- }
- m_err = II_ERR_UNRECOGNIZED_FORMAT;
- }
- bool tryDetector(const IIDetector &detector, IIReadInterface &ri) {
- bool match = detector.process(ri.length(), ri, m_width, m_height, m_entrySizes);
- if (match) {
- m_format = detector.format;
- m_ext = detector.ext;
- m_fullExt = detector.fullExt;
- m_mimetype = detector.mimetype;
- m_err = m_width != -1 && m_height != -1
- ? II_ERR_OK
- : II_ERR_DECODE_SIZE_FAILED;
- }
- return match;
- }
- inline explicit operator bool() const {
- return m_err == II_ERR_OK;
- }
- inline bool ok() const {
- return m_err == II_ERR_OK;
- }
- inline IIFormat getFormat() const {
- return m_format;
- }
- inline const char *getExt() const {
- return m_ext;
- }
- inline const char *getFullExt() const {
- return m_fullExt;
- }
- inline const char *getMimetype() const {
- return m_mimetype;
- }
- inline int64_t getWidth() const {
- return m_width;
- }
- inline int64_t getHeight() const {
- return m_height;
- }
- inline std::array<int64_t, 2> getSize() const {
- return {m_width, m_height};
- }
- inline std::vector<std::array<int64_t, 2>> getEntrySizes() const {
- if (m_entrySizes.empty()) {
- return {getSize()};
- }
- return m_entrySizes;
- }
- inline IIErrorCode getErrorCode() const {
- return m_err;
- }
- inline const char *getErrorMsg() const {
- switch (m_err) {
- default:
- return "Unknown error";
- case II_ERR_OK:
- return "Ok";
- case II_ERR_UNRECOGNIZED_FORMAT:
- return "Unrecognized format";
- case II_ERR_DECODE_SIZE_FAILED:
- return "Decode image size failed";
- }
- }
- private:
- IIFormat m_format = II_FORMAT_UNKNOWN;
- const char *m_ext = "";
- const char *m_fullExt = "";
- const char *m_mimetype = "";
- int64_t m_width = -1;
- int64_t m_height = -1;
- IIErrorCode m_err = II_ERR_OK;
- std::vector<std::array<int64_t, 2>> m_entrySizes;
- };
- template<typename ReaderType, typename InputType>
- static inline ImageInfo
- getImageInfo(InputType file, IIFormat likelyFormat = II_FORMAT_UNKNOWN, bool mustBe = false) {
- ReaderType fileReader(file);
- size_t length = fileReader.size();
- IIReadFunc read = [&](void *buf, off_t offset, size_t size) { fileReader.read(buf, offset, size); };
- IIReadInterface ri(read, length);
- return ImageInfo(ri, likelyFormat, mustBe);
- }
- #ifdef __clang__
- #pragma clang diagnostic pop
- #endif
- #endif //IMAGEINFO_IMAGEINFO_H
|