diff --git a/doc/changelog.dox b/doc/changelog.dox index 4364d67d2..b40b2014e 100644 --- a/doc/changelog.dox +++ b/doc/changelog.dox @@ -527,6 +527,10 @@ See also: @relativeref{Trade::AbstractImageConverter,doConvertToData()}, for example when the implementation only neeeds to do a format detection based on file extension +- New @ref Trade::AbstractImageConverter::extension() and + @relativeref{Trade::AbstractImageConverter,mimeType()} interfaces to get + a file extension and MIME type corresponding to a file format produced by + a particular plugin. - Recognizing BMP and TIFF file header magic in @relativeref{Trade,AnyImageImporter} - Recognizing ASTC and WebP files and data in @relativeref{Trade,AnyImageImporter} diff --git a/src/Magnum/Trade/AbstractImageConverter.cpp b/src/Magnum/Trade/AbstractImageConverter.cpp index 9c51de7c9..29f12b250 100644 --- a/src/Magnum/Trade/AbstractImageConverter.cpp +++ b/src/Magnum/Trade/AbstractImageConverter.cpp @@ -57,7 +57,7 @@ using namespace Containers::Literals; Containers::StringView AbstractImageConverter::pluginInterface() { return /* [interface] */ -"cz.mosra.magnum.Trade.AbstractImageConverter/0.3.2"_s +"cz.mosra.magnum.Trade.AbstractImageConverter/0.3.3"_s /* [interface] */ ; } @@ -106,6 +106,40 @@ void AbstractImageConverter::clearFlags(ImageConverterFlags flags) { setFlags(_flags & ~flags); } +Containers::String AbstractImageConverter::extension() const { + CORRADE_ASSERT(features() & (ImageConverterFeature::Convert1DToFile| + ImageConverterFeature::Convert2DToFile| + ImageConverterFeature::Convert3DToFile| + ImageConverterFeature::ConvertCompressed1DToFile| + ImageConverterFeature::ConvertCompressed2DToFile| + ImageConverterFeature::ConvertCompressed3DToFile), + "Trade::AbstractImageConverter::extension(): file conversion not supported", {}); + + Containers::String out = doExtension(); + CORRADE_ASSERT(out.isSmall() || !out.deleter(), + "Trade::AbstractImageConverter::extension(): implementation is not allowed to use a custom String deleter", {}); + return out; +} + +Containers::String AbstractImageConverter::doExtension() const { return {}; } + +Containers::String AbstractImageConverter::mimeType() const { + CORRADE_ASSERT(features() & (ImageConverterFeature::Convert1DToFile| + ImageConverterFeature::Convert2DToFile| + ImageConverterFeature::Convert3DToFile| + ImageConverterFeature::ConvertCompressed1DToFile| + ImageConverterFeature::ConvertCompressed2DToFile| + ImageConverterFeature::ConvertCompressed3DToFile), + "Trade::AbstractImageConverter::mimeType(): file conversion not supported", {}); + + Containers::String out = doMimeType(); + CORRADE_ASSERT(out.isSmall() || !out.deleter(), + "Trade::AbstractImageConverter::mimeType(): implementation is not allowed to use a custom String deleter", {}); + return out; +} + +Containers::String AbstractImageConverter::doMimeType() const { return {}; } + Containers::Optional AbstractImageConverter::convert(const ImageView1D& image) { CORRADE_ASSERT(features() & ImageConverterFeature::Convert1D, "Trade::AbstractImageConverter::convert(): 1D image conversion not supported", {}); diff --git a/src/Magnum/Trade/AbstractImageConverter.h b/src/Magnum/Trade/AbstractImageConverter.h index 11e0e7bc5..ae2f0a01e 100644 --- a/src/Magnum/Trade/AbstractImageConverter.h +++ b/src/Magnum/Trade/AbstractImageConverter.h @@ -572,6 +572,10 @@ based on what features are supported. You don't need to do most of the redundant sanity checks, these things are checked by the implementation: +- The @ref doExtension() and @ref doMimeType() functions are called only + if @ref ImageConverterFeature::Convert2DToData "ImageConverterFeature::Convert*ToData" + or @ref ImageConverterFeature::Convert2DToFile "Convert*ToFile" is + supported - The @ref doConvert(const ImageView2D&) function is called only if @ref ImageConverterFeature::Convert2D is supported; equivalently for the 1D and 3D case. @@ -724,6 +728,38 @@ class MAGNUM_TRADE_EXPORT AbstractImageConverter: public PluginManager::Abstract */ void clearFlags(ImageConverterFlags flags); + /** + * @brief File extension + * @m_since_latest + * + * Available only if @ref ImageConverterFeature::Convert2DToFile "ImageConverterFeature::Convert*ToFile" + * or @ref ImageConverterFeature::Convert2DToData "ImageConverterFeature::Convert*ToData" + * is supported. Returns a standardized file extension corresponding + * to the file format used, such as @cpp "png" @ce for PNG files. If + * the file format doesn't have a standardized extension, empty string + * is returned. + * + * The returned value may depend on flags or configuration options and + * can change during plugin lifetime. + */ + Containers::String extension() const; + + /** + * @brief File MIME type + * @m_since_latest + * + * Available only if @ref ImageConverterFeature::Convert2DToFile "ImageConverterFeature::Convert*ToFile" + * or @ref ImageConverterFeature::Convert2DToData "ImageConverterFeature::Convert*ToData" + * is supported. Returns a standardized [MIME type](https://en.wikipedia.org/wiki/Media_type) + * corresponding to the file format used, such as @cpp "image/png" @ce + * for PNG files. If the file format doesn't have a standardized MIME + * type, empty string is returned. + * + * The returned value may depend on flags or configuration options and + * can change during plugin lifetime. + */ + Containers::String mimeType() const; + /** * @brief Convert a 1D image * @m_since_latest @@ -1777,6 +1813,22 @@ class MAGNUM_TRADE_EXPORT AbstractImageConverter: public PluginManager::Abstract */ virtual void doSetFlags(ImageConverterFlags flags); + /** + * @brief Implementation for @ref extension() + * @m_since_latest + * + * Default implementation returns an empty string. + */ + virtual Containers::String doExtension() const; + + /** + * @brief Implementation for @ref mimeType() + * @m_since_latest + * + * Default implementation returns an empty string. + */ + virtual Containers::String doMimeType() const; + /** * @brief Implementation for @ref convert(const ImageView1D&) * @m_since_latest diff --git a/src/Magnum/Trade/Test/AbstractImageConverterTest.cpp b/src/Magnum/Trade/Test/AbstractImageConverterTest.cpp index 71af13a11..1c6354b46 100644 --- a/src/Magnum/Trade/Test/AbstractImageConverterTest.cpp +++ b/src/Magnum/Trade/Test/AbstractImageConverterTest.cpp @@ -56,6 +56,10 @@ struct AbstractImageConverterTest: TestSuite::Tester { void thingNotSupported(); + void extensionMimeType(); + void extensionMimeTypeNotImplemented(); + void extensionMimeTypeCustomDeleter(); + void convert1D(); void convert2D(); void convert3D(); @@ -307,6 +311,10 @@ AbstractImageConverterTest::AbstractImageConverterTest() { &AbstractImageConverterTest::thingNotSupported, + &AbstractImageConverterTest::extensionMimeType, + &AbstractImageConverterTest::extensionMimeTypeNotImplemented, + &AbstractImageConverterTest::extensionMimeTypeCustomDeleter, + &AbstractImageConverterTest::convert1D, &AbstractImageConverterTest::convert2D, &AbstractImageConverterTest::convert3D, @@ -599,6 +607,8 @@ void AbstractImageConverterTest::thingNotSupported() { std::ostringstream out; Error redirectError{&out}; + converter.extension(); + converter.mimeType(); converter.convert(ImageView1D{PixelFormat::R8Unorm, 0, nullptr}); converter.convert(ImageView2D{PixelFormat::R8Unorm, {}, nullptr}); converter.convert(ImageView3D{PixelFormat::R8Unorm, {}, nullptr}); @@ -630,6 +640,8 @@ void AbstractImageConverterTest::thingNotSupported() { converter.convertToFile({CompressedImageView2D{CompressedPixelFormat::Bc1RGBAUnorm, {}, nullptr}}, Utility::Path::join(TRADE_TEST_OUTPUT_DIR, "image.out")); converter.convertToFile({CompressedImageView3D{CompressedPixelFormat::Bc1RGBAUnorm, {}, nullptr}}, Utility::Path::join(TRADE_TEST_OUTPUT_DIR, "image.out")); CORRADE_COMPARE(out.str(), + "Trade::AbstractImageConverter::extension(): file conversion not supported\n" + "Trade::AbstractImageConverter::mimeType(): file conversion not supported\n" "Trade::AbstractImageConverter::convert(): 1D image conversion not supported\n" "Trade::AbstractImageConverter::convert(): 2D image conversion not supported\n" "Trade::AbstractImageConverter::convert(): 3D image conversion not supported\n" @@ -662,6 +674,54 @@ void AbstractImageConverterTest::thingNotSupported() { "Trade::AbstractImageConverter::convertToFile(): multi-level compressed 3D image conversion not supported\n"); } +void AbstractImageConverterTest::extensionMimeType() { + struct: AbstractImageConverter { + ImageConverterFeatures doFeatures() const override { + return ImageConverterFeature::ConvertCompressedLevels3DToData; + } + Containers::String doExtension() const override { return "yello"; } + Containers::String doMimeType() const override { return "yel/low"; } + } converter; + + CORRADE_COMPARE(converter.extension(), "yello"); + CORRADE_COMPARE(converter.mimeType(), "yel/low"); +} + +void AbstractImageConverterTest::extensionMimeTypeNotImplemented() { + struct: AbstractImageConverter { + ImageConverterFeatures doFeatures() const override { + return ImageConverterFeature::Convert1DToFile; + } + } converter; + + CORRADE_COMPARE(converter.extension(), ""); + CORRADE_COMPARE(converter.mimeType(), ""); +} + +void AbstractImageConverterTest::extensionMimeTypeCustomDeleter() { + CORRADE_SKIP_IF_NO_ASSERT(); + + struct: AbstractImageConverter { + ImageConverterFeatures doFeatures() const override { + return ImageConverterFeature::ConvertCompressed1DToData; + } + Containers::String doExtension() const override { + return Containers::String{"yello", 5, [](char*, std::size_t) {}}; + } + Containers::String doMimeType() const override { + return Containers::String{"yel/low", 7, [](char*, std::size_t) {}}; + } + } converter; + + std::ostringstream out; + Error redirectError{&out}; + converter.extension(); + converter.mimeType(); + CORRADE_COMPARE(out.str(), + "Trade::AbstractImageConverter::extension(): implementation is not allowed to use a custom String deleter\n" + "Trade::AbstractImageConverter::mimeType(): implementation is not allowed to use a custom String deleter\n"); +} + void AbstractImageConverterTest::convert1D() { struct: AbstractImageConverter { ImageConverterFeatures doFeatures() const override { return ImageConverterFeature::Convert1D; }