diff --git a/doc/changelog.dox b/doc/changelog.dox index f6ae4e53b..243264762 100644 --- a/doc/changelog.dox +++ b/doc/changelog.dox @@ -79,6 +79,9 @@ See also: @ref Trade::AbstractImageConverter::Feature enums and @ref Trade::AbstractImporter::Features, @ref Trade::AbstractImageConverter::Features enum sets + @ref Trade::AnyImageImporter "AnyImageImporter" plugin now supports + detection and loading of DDS, EXR, HDR, JPEG, PNG and TGA files from data + next to file type detection based on filename @subsection changelog-latest-changes Changes and improvements diff --git a/src/MagnumPlugins/AnyImageImporter/AnyImageImporter.cpp b/src/MagnumPlugins/AnyImageImporter/AnyImageImporter.cpp index bbee8d9d1..370095a57 100644 --- a/src/MagnumPlugins/AnyImageImporter/AnyImageImporter.cpp +++ b/src/MagnumPlugins/AnyImageImporter/AnyImageImporter.cpp @@ -38,7 +38,7 @@ AnyImageImporter::AnyImageImporter(PluginManager::AbstractManager& manager, cons AnyImageImporter::~AnyImageImporter() = default; -auto AnyImageImporter::doFeatures() const -> Features { return {}; } +auto AnyImageImporter::doFeatures() const -> Features { return Feature::OpenData; } bool AnyImageImporter::doIsOpened() const { return !!_in; } @@ -118,6 +118,74 @@ void AnyImageImporter::doOpenFile(const std::string& filename) { _in = std::move(importer); } +void AnyImageImporter::doOpenData(Containers::ArrayView data) { + CORRADE_INTERNAL_ASSERT(manager()); + + std::string plugin; + /* https://docs.microsoft.com/cs-cz/windows/desktop/direct3ddds/dx-graphics-dds-pguide */ + if(Utility::String::viewBeginsWith(data, "DDS ")) + plugin = "DdsImporter"; + /* http://www.openexr.com/openexrfilelayout.pdf */ + else if(Utility::String::viewBeginsWith(data, "\x76\x2f\x31\x01")) + plugin = "OpenExrImporter"; + /* https://en.wikipedia.org/wiki/Radiance_(software)#HDR_image_format */ + else if(Utility::String::viewBeginsWith(data, "#?RADIANCE")) + plugin = "HdrImporter"; + /* https://en.wikipedia.org/wiki/JPEG#Syntax_and_structure */ + else if(Utility::String::viewBeginsWith(data, "\xff\xd8\xff")) + plugin = "JpegImporter"; + /* https://en.wikipedia.org/wiki/Portable_Network_Graphics#File_header */ + else if(Utility::String::viewBeginsWith(data, "\x89PNG\x0d\x0a\x1a\x0a")) + plugin = "PngImporter"; + /* https://github.com/file/file/blob/d04de269e0b06ccd0a7d1bf4974fed1d75be7d9e/magic/Magdir/images#L18-L22 + TGAs are a complete guesswork, so try after everything else fails. */ + else if([data]() { + /* TGA header is 18 bytes */ + if(data.size() < 18) return false; + + /* Third byte (image type) must be one of these */ + if(data[2] != 1 && data[2] != 2 && data[2] != 3 && + data[2] != 9 && data[2] != 10 && data[2] != 11) return false; + + /* If image type is 1 or 9, second byte (colormap type) must be 1 */ + if((data[2] == 1 || data[2] == 9) && data[1] != 1) return false; + + /* ... and 0 otherwise */ + if(data[2] != 1 && data[2] != 9 && data[1] != 0) return false; + + /* Colormap index (unsigned short, byte 3+4) should be 0 */ + if(data[3] != 0 && data[4] != 0) return false; + + /* Probably TGA, heh. Or random memory. */ + return true; + }()) plugin = "TgaImporter"; + else if(!data.size()) { + Error{} << "Trade::AnyImageImporter::openData(): file is empty"; + return; + } else { + std::uint32_t signature = data[0] << 24; + if(data.size() > 1) signature |= data[1] << 16; + if(data.size() > 2) signature |= data[2] << 8; + if(data.size() > 3) signature |= data[3]; + Error() << "Trade::AnyImageImporter::openData(): cannot determine type from signature" << reinterpret_cast(signature); + return; + } + + /* Try to load the plugin */ + if(!(manager()->load(plugin) & PluginManager::LoadState::Loaded)) { + Error() << "Trade::AnyImageImporter::openData(): cannot load" << plugin << "plugin"; + return; + } + + /* Try to open the file (error output should be printed by the plugin + itself) */ + std::unique_ptr importer = static_cast*>(manager())->instantiate(plugin); + if(!importer->openData(data)) return; + + /* Success, save the instance */ + _in = std::move(importer); +} + UnsignedInt AnyImageImporter::doImage2DCount() const { return _in->image2DCount(); } Containers::Optional AnyImageImporter::doImage2D(const UnsignedInt id) { return _in->image2D(id); } diff --git a/src/MagnumPlugins/AnyImageImporter/AnyImageImporter.h b/src/MagnumPlugins/AnyImageImporter/AnyImageImporter.h index a9887b07d..16527f093 100644 --- a/src/MagnumPlugins/AnyImageImporter/AnyImageImporter.h +++ b/src/MagnumPlugins/AnyImageImporter/AnyImageImporter.h @@ -68,14 +68,16 @@ of the `Magnum` package and link to the `Magnum::AnyImageImporter` target. See Supported formats: - Windows Bitmap (`*.bmp`), loaded with any plugin that provides `BmpImporter` -- DirectDraw Surface (`*.dds`), loaded with @ref DdsImporter or any other - plugin that provides it +- DirectDraw Surface (`*.dds` or data with corresponding signature), loaded + with @ref DdsImporter or any other plugin that provides it - Graphics Interchange Format (`*.gif`), loaded with any plugin that provides `GifImporter` -- OpenEXR (`*.exr`), loaded with any plugin that provides `OpenExrImporter` -- Radiance HDR (`*.hdr`), loaded with any plugin that provides `HdrImporter` -- JPEG (`*.jpg`, `*.jpe`, `*.jpeg`), loaded with @ref JpegImporter or any - other plugin that provides it +- OpenEXR (`*.exr` or data with corresponding signature), loaded with any + plugin that provides `OpenExrImporter` +- Radiance HDR (`*.hdr` or data with corresponding signature), loaded with + any plugin that provides `HdrImporter` +- JPEG (`*.jpg`, `*.jpe`, `*.jpeg` or data with corresponding signature), + loaded with @ref JpegImporter or any other plugin that provides it - JPEG 2000 (`*.jp2`), loaded with any plugin that provides `Jpeg2000Importer` - Multiple-image Network Graphics (`*.mng`), loaded with any plugin that @@ -87,18 +89,20 @@ Supported formats: - Softimage PIC (`*.pic`), loaded with any plugin that provides `PicImporter` - Portable Anymap (`*.pnm`), loaded with any plugin that provides `PnmImporter` -- Portable Network Graphics (`*.png`), loaded with @ref PngImporter or any - other plugin that provides it +- Portable Network Graphics (`*.png` or data with corresponding signature), + loaded with @ref PngImporter or any other plugin that provides it - Portable Pixmap (`*.ppm`), loaded with any plugin that provides `PpmImporter` - Adobe Photoshop (`*.psd`), loaded with any plugin that provides `PsdImporter` - Silicon Graphics (`*.sgi`, `*.bw`, `*.rgb`, `*.rgba`), loaded with any plugin that provides `SgiImporter` - Tagged Image File Format (`*.tif`, `*.tiff`), loaded with any plugin that provides `TiffImporter` -- Truevision TGA (`*.tga`, `*.vda`, `*.icb`, `*.vst`), loaded with - @ref TgaImporter or any other plugin that provides it +- Truevision TGA (`*.tga`, `*.vda`, `*.icb`, `*.vst` or data with + corresponding signature), loaded with @ref TgaImporter or any other plugin + that provides it -Only loading from files is supported. +Detecting file type through @ref openData() is supported only for a subset of +formats that are marked as such in the list above. */ class MAGNUM_ANYIMAGEIMPORTER_EXPORT AnyImageImporter: public AbstractImporter { public: @@ -115,6 +119,7 @@ class MAGNUM_ANYIMAGEIMPORTER_EXPORT AnyImageImporter: public AbstractImporter { MAGNUM_ANYIMAGEIMPORTER_LOCAL bool doIsOpened() const override; MAGNUM_ANYIMAGEIMPORTER_LOCAL void doClose() override; MAGNUM_ANYIMAGEIMPORTER_LOCAL void doOpenFile(const std::string& filename) override; + MAGNUM_ANYIMAGEIMPORTER_LOCAL void doOpenData(Containers::ArrayView data) override; MAGNUM_ANYIMAGEIMPORTER_LOCAL UnsignedInt doImage2DCount() const override; MAGNUM_ANYIMAGEIMPORTER_LOCAL Containers::Optional doImage2D(UnsignedInt id) override; diff --git a/src/MagnumPlugins/AnyImageImporter/Test/AnyImageImporterTest.cpp b/src/MagnumPlugins/AnyImageImporter/Test/AnyImageImporterTest.cpp index 811760160..4e2b75812 100644 --- a/src/MagnumPlugins/AnyImageImporter/Test/AnyImageImporterTest.cpp +++ b/src/MagnumPlugins/AnyImageImporter/Test/AnyImageImporterTest.cpp @@ -26,6 +26,8 @@ #include #include #include +#include +#include #include "Magnum/Trade/AbstractImporter.h" #include "Magnum/Trade/ImageData.h" @@ -37,18 +39,70 @@ namespace Magnum { namespace Trade { namespace Test { struct AnyImageImporterTest: TestSuite::Tester { explicit AnyImageImporterTest(); - void tga(); + void load(); - void unknown(); + void detect(); + + void unknownExtension(); + void unknownSignature(); + void emptyData(); /* Explicitly forbid system-wide plugin dependencies */ PluginManager::Manager _manager{"nonexistent"}; }; +namespace { + +Containers::ArrayView fileCallback(const std::string& filename, Trade::ImporterFileCallbackPolicy, Containers::Array& storage) { + storage = Utility::Directory::read(filename); + return Containers::ArrayView{storage}; +} + +constexpr struct { + const char* name; + const char* filename; + Containers::ArrayView(*callback)(const std::string&, Trade::ImporterFileCallbackPolicy, Containers::Array&); +} LoadData[]{ + {"TGA", TGA_FILE, nullptr}, + {"TGA data", TGA_FILE, fileCallback} +}; + +constexpr struct { + const char* name; + const char* filename; + Containers::ArrayView(*callback)(const std::string&, Trade::ImporterFileCallbackPolicy, Containers::Array&); + const char* plugin; +} DetectData[]{ + {"PNG", "rgb.png", nullptr, "PngImporter"}, + {"PNG data", "rgb.png", fileCallback, "PngImporter"}, + {"JPEG", "gray.jpg", nullptr, "JpegImporter"}, + {"JPEG data", "gray.jpg", fileCallback, "JpegImporter"}, + {"JPEG2000", "image.jp2", nullptr, "Jpeg2000Importer"}, + {"EXR", "image.exr", nullptr, "OpenExrImporter"}, + {"EXR data", "image.exr", fileCallback, "OpenExrImporter"}, + {"HDR", "rgb.hdr", nullptr, "HdrImporter"}, + {"HDR data", "rgb.hdr", fileCallback, "HdrImporter"}, + {"DDS", "rgba_dxt1.dds", nullptr, "DdsImporter"}, + {"DDS data", "rgba_dxt1.dds", fileCallback, "DdsImporter"}, + {"BMP", "image.bmp", nullptr, "BmpImporter"}, + {"GIF", "image.gif", nullptr, "GifImporter"}, + {"PSD", "image.psd", nullptr, "PsdImporter"}, + {"TIFF", "image.tiff", nullptr, "TiffImporter"} + /* Not testing everything, just the most important ones */ +}; + +} + AnyImageImporterTest::AnyImageImporterTest() { - addTests({&AnyImageImporterTest::tga, + addInstancedTests({&AnyImageImporterTest::load}, + Containers::arraySize(LoadData)); + + addInstancedTests({&AnyImageImporterTest::detect}, + Containers::arraySize(DetectData)); - &AnyImageImporterTest::unknown}); + addTests({&AnyImageImporterTest::unknownExtension, + &AnyImageImporterTest::unknownSignature, + &AnyImageImporterTest::emptyData}); /* Load the plugin directly from the build tree. Otherwise it's static and already loaded. */ @@ -61,20 +115,55 @@ AnyImageImporterTest::AnyImageImporterTest() { #endif } -void AnyImageImporterTest::tga() { +void AnyImageImporterTest::load() { + auto&& data = LoadData[testCaseInstanceId()]; + setTestCaseDescription(data.name); + if(!(_manager.loadState("TgaImporter") & PluginManager::LoadState::Loaded)) CORRADE_SKIP("TgaImporter plugin not enabled, cannot test"); std::unique_ptr importer = _manager.instantiate("AnyImageImporter"); - CORRADE_VERIFY(importer->openFile(TGA_FILE)); + + Containers::Array storage; + importer->setFileCallback(data.callback, storage); + + CORRADE_VERIFY(importer->openFile(data.filename)); /* Check only size, as it is good enough proof that it is working */ Containers::Optional image = importer->image2D(0); CORRADE_VERIFY(image); CORRADE_COMPARE(image->size(), Vector2i(2, 3)); + + importer->close(); + CORRADE_VERIFY(!importer->isOpened()); +} + +void AnyImageImporterTest::detect() { + auto&& data = DetectData[testCaseInstanceId()]; + setTestCaseDescription(data.name); + + std::unique_ptr importer = _manager.instantiate("AnyImageImporter"); + + Containers::Array storage; + importer->setFileCallback(data.callback, storage); + + std::ostringstream out; + Error redirectError{&out}; + CORRADE_VERIFY(!importer->openFile(Utility::Directory::join(TEST_FILE_DIR, data.filename))); + #ifndef CORRADE_PLUGINMANAGER_NO_DYNAMIC_PLUGIN_SUPPORT + CORRADE_COMPARE(out.str(), Utility::format( +R"(PluginManager::Manager::load(): plugin {0} is not static and was not found in nonexistent +Trade::AnyImageImporter::{1}(): cannot load {0} plugin +)", data.plugin, data.callback ? "openData" : "openFile")); + #else + CORRADE_COMPARE(out.str(), Utility::format( +R"(PluginManager::Manager::load(): plugin {0} was not found +Trade::AnyImageImporter::{1}(): cannot load {0} plugin +)", data.plugin, data.callback ? "openData" : "openFile")); + #endif } -void AnyImageImporterTest::unknown() { +void AnyImageImporterTest::unknownExtension() { std::ostringstream output; Error redirectError{&output}; @@ -84,6 +173,28 @@ void AnyImageImporterTest::unknown() { CORRADE_COMPARE(output.str(), "Trade::AnyImageImporter::openFile(): cannot determine type of file image.xcf\n"); } +void AnyImageImporterTest::unknownSignature() { + std::ostringstream output; + Error redirectError{&output}; + + constexpr const char data[]{ 0x25, 0x3a }; + + std::unique_ptr importer = _manager.instantiate("AnyImageImporter"); + CORRADE_VERIFY(!importer->openData(data)); + + CORRADE_COMPARE(output.str(), "Trade::AnyImageImporter::openData(): cannot determine type from signature 0x253a0000\n"); +} + +void AnyImageImporterTest::emptyData() { + std::ostringstream output; + Error redirectError{&output}; + + std::unique_ptr importer = _manager.instantiate("AnyImageImporter"); + CORRADE_VERIFY(!importer->openData(nullptr)); + + CORRADE_COMPARE(output.str(), "Trade::AnyImageImporter::openData(): file is empty\n"); +} + }}} CORRADE_TEST_MAIN(Magnum::Trade::Test::AnyImageImporterTest) diff --git a/src/MagnumPlugins/AnyImageImporter/Test/CMakeLists.txt b/src/MagnumPlugins/AnyImageImporter/Test/CMakeLists.txt index b9b0cb2b5..b4cba5090 100644 --- a/src/MagnumPlugins/AnyImageImporter/Test/CMakeLists.txt +++ b/src/MagnumPlugins/AnyImageImporter/Test/CMakeLists.txt @@ -24,8 +24,10 @@ # if(CORRADE_TARGET_EMSCRIPTEN OR CORRADE_TARGET_ANDROID) + set(TEST_FILE_DIR .) set(TGA_FILE file.tga) else() + set(TEST_FILE_DIR ${CMAKE_CURRENT_SOURCE_DIR}) set(TGA_FILE ${PROJECT_SOURCE_DIR}/src/MagnumPlugins/TgaImporter/Test/file.tga) endif() @@ -52,6 +54,11 @@ endif() corrade_add_test(AnyImageImporterTest AnyImageImporterTest.cpp LIBRARIES MagnumTrade FILES + gray.jpg + image.exr + rgb.hdr + rgb.png + rgba_dxt1.dds ../../TgaImporter/Test/file.tga) if(NOT BUILD_PLUGINS_STATIC) target_include_directories(AnyImageImporterTest PRIVATE $) diff --git a/src/MagnumPlugins/AnyImageImporter/Test/configure.h.cmake b/src/MagnumPlugins/AnyImageImporter/Test/configure.h.cmake index 0772b3c53..082eb7910 100644 --- a/src/MagnumPlugins/AnyImageImporter/Test/configure.h.cmake +++ b/src/MagnumPlugins/AnyImageImporter/Test/configure.h.cmake @@ -26,3 +26,4 @@ #cmakedefine ANYIMAGEIMPORTER_PLUGIN_FILENAME "${ANYIMAGEIMPORTER_PLUGIN_FILENAME}" #cmakedefine TGAIMPORTER_PLUGIN_FILENAME "${TGAIMPORTER_PLUGIN_FILENAME}" #define TGA_FILE "${TGA_FILE}" +#define TEST_FILE_DIR "${TEST_FILE_DIR}" diff --git a/src/MagnumPlugins/AnyImageImporter/Test/gray.jpg b/src/MagnumPlugins/AnyImageImporter/Test/gray.jpg new file mode 100644 index 000000000..478ed0ed7 Binary files /dev/null and b/src/MagnumPlugins/AnyImageImporter/Test/gray.jpg differ diff --git a/src/MagnumPlugins/AnyImageImporter/Test/image.exr b/src/MagnumPlugins/AnyImageImporter/Test/image.exr new file mode 100644 index 000000000..5e5de1fde Binary files /dev/null and b/src/MagnumPlugins/AnyImageImporter/Test/image.exr differ diff --git a/src/MagnumPlugins/AnyImageImporter/Test/rgb.hdr b/src/MagnumPlugins/AnyImageImporter/Test/rgb.hdr new file mode 100644 index 000000000..5555870b5 --- /dev/null +++ b/src/MagnumPlugins/AnyImageImporter/Test/rgb.hdr @@ -0,0 +1,7 @@ +#?RADIANCE +# Written by stb_image_write.h +FORMAT=32-bit_rle_rgbe +EXPOSURE= 1.0000000000000 + +-Y 3 +X 2 + \ No newline at end of file diff --git a/src/MagnumPlugins/AnyImageImporter/Test/rgb.png b/src/MagnumPlugins/AnyImageImporter/Test/rgb.png new file mode 100644 index 000000000..cacab7041 Binary files /dev/null and b/src/MagnumPlugins/AnyImageImporter/Test/rgb.png differ diff --git a/src/MagnumPlugins/AnyImageImporter/Test/rgba_dxt1.dds b/src/MagnumPlugins/AnyImageImporter/Test/rgba_dxt1.dds new file mode 100644 index 000000000..903752a42 Binary files /dev/null and b/src/MagnumPlugins/AnyImageImporter/Test/rgba_dxt1.dds differ