diff --git a/doc/changelog.dox b/doc/changelog.dox index e3e87a559..dedff6a26 100644 --- a/doc/changelog.dox +++ b/doc/changelog.dox @@ -171,8 +171,10 @@ See also: - The @ref Trade::AnimationData class received support for mutable data access with new constructors and the @ref Trade::AnimationData::mutableData() "mutableData()" and - @ref Trade::AnimationData::mutableTrack() "mutableTrack()" accessors. See - @ref Trade-AnimationData-usage-mutable for more information. + @ref Trade::AnimationData::mutableTrack() "mutableTrack()" accessors. + Equivalent APIs are exposed in both @ref Trade::ImageData and + @ref Trade::MeshData as well. See @ref Trade-AnimationData-usage-mutable + for more information. @subsubsection changelog-latest-new-vk Vk library @@ -405,6 +407,10 @@ See also: - The @ref Magnum/Math/FunctionsBatch.h header is no longer included from @ref Magnum/Math/Functions.h for backwards compatibility in order to speed up compile times. +- Non-const @ref Trade::ImageData::data() and @ref Trade::ImageData::pixels() + were renamed to @ref Trade::ImageData::mutableData() and + @ref Trade::ImageData::mutablePixels() to follow the new + @ref Trade::MeshData API and similar changes in @ref Trade::AnimationData. - @ref Platform::GlfwApplication::setMinWindowSize() / @ref Platform::GlfwApplication::setMaxWindowSize() and equivalent APIs in @ref Platform::Sdl2Application now premultiply the value with diff --git a/doc/snippets/MagnumTrade.cpp b/doc/snippets/MagnumTrade.cpp index 922237381..e5cbce542 100644 --- a/doc/snippets/MagnumTrade.cpp +++ b/doc/snippets/MagnumTrade.cpp @@ -223,6 +223,20 @@ else } #endif +{ +Trade::ImageData2D data{PixelFormat::RGB8Unorm, {}, nullptr}; +/* [ImageData-usage-mutable] */ +if(data.isCompressed() || + data.format() != PixelFormat::RGB8Unorm || + !(data.dataFlags() & Trade::DataFlag::Mutable)) + Fatal{} << ":("; + +for(auto&& row: data.mutablePixels()) + for(Color3ub& pixel: row) + pixel = Math::gather<'b', 'g', 'r'>(pixel); +/* [ImageData-usage-mutable] */ +} + #ifdef MAGNUM_TARGET_GL { Trade::MeshData data{MeshPrimitive::Points, 0}; diff --git a/src/Magnum/Image.cpp b/src/Magnum/Image.cpp index 30be47f01..fed0d70df 100644 --- a/src/Magnum/Image.cpp +++ b/src/Magnum/Image.cpp @@ -73,11 +73,11 @@ template std::pair Containers::StridedArrayView Image::pixels() { - return Implementation::imagePixelView(*this); + return Implementation::imagePixelView(*this, data()); } template Containers::StridedArrayView Image::pixels() const { - return Implementation::imagePixelView(*this); + return Implementation::imagePixelView(*this, data()); } template Containers::Array Image::release() { diff --git a/src/Magnum/ImageView.cpp b/src/Magnum/ImageView.cpp index 805bc7b28..98ba94993 100644 --- a/src/Magnum/ImageView.cpp +++ b/src/Magnum/ImageView.cpp @@ -63,7 +63,7 @@ template void ImageView::setData template auto ImageView::pixels() const -> Containers::StridedArrayView { if(!_data && !_data.size()) return {}; - return Implementation::imagePixelView(*this); + return Implementation::imagePixelView(*this, data()); } template CompressedImageView::CompressedImageView(const CompressedPixelStorage storage, const CompressedPixelFormat format, const VectorTypeFor& size, const Containers::ArrayView data) noexcept: _storage{storage}, _format{format}, _size{size}, _data{reinterpret_cast(data.data()), data.size()} {} diff --git a/src/Magnum/Implementation/ImageProperties.h b/src/Magnum/Implementation/ImageProperties.h index 0786d6f85..dd495254c 100644 --- a/src/Magnum/Implementation/ImageProperties.h +++ b/src/Magnum/Implementation/ImageProperties.h @@ -45,7 +45,7 @@ template std::pair::pad(dataProperties.first), Math::Vector::pad(dataProperties.second)); } -template Containers::StridedArrayView imagePixelView(Image& image) { +template Containers::StridedArrayView imagePixelView(Image& image, const Data data) { const std::pair, VectorTypeFor> properties = image.dataProperties(); /* Size in the last dimension is byte size of the pixel, the remaining @@ -70,7 +70,7 @@ template Containers::StridedArrayV static_assert(sizeof(decltype(image.data().front())) == 1, "pointer arithmetic expects image data type to have 1 byte"); - return {image.data().suffix(properties.first[dimensions - 1]), image.data() + properties.first.sum(), size, stride}; + return {data.suffix(properties.first[dimensions - 1]), data + properties.first.sum(), size, stride}; } }} diff --git a/src/Magnum/Trade/Data.h b/src/Magnum/Trade/Data.h index b3b04dcfd..59d98e922 100644 --- a/src/Magnum/Trade/Data.h +++ b/src/Magnum/Trade/Data.h @@ -42,7 +42,8 @@ namespace Magnum { namespace Trade { @m_since_latest @see @ref DataFlags, @ref AnimationData::dataFlags(), - @ref MeshData::indexDataFlags(), @ref MeshData::vertexDataFlags() + @ref ImageData::dataFlags(), @ref MeshData::indexDataFlags(), + @ref MeshData::vertexDataFlags() */ enum class DataFlag: UnsignedByte { /** @@ -71,8 +72,8 @@ MAGNUM_TRADE_EXPORT Debug& operator<<(Debug& debug, DataFlag value); @brief Data flags @m_since_latest -@see @ref AnimationData::dataFlags(), @ref MeshData::indexDataFlags(), - @ref MeshData::vertexDataFlags() +@see @ref AnimationData::dataFlags(), @ref ImageData::dataFlags(), + @ref MeshData::indexDataFlags(), @ref MeshData::vertexDataFlags() */ typedef Containers::EnumSet DataFlags; diff --git a/src/Magnum/Trade/ImageData.cpp b/src/Magnum/Trade/ImageData.cpp index b390fd357..8b2a2c43f 100644 --- a/src/Magnum/Trade/ImageData.cpp +++ b/src/Magnum/Trade/ImageData.cpp @@ -35,17 +35,59 @@ namespace Magnum { namespace Trade { template ImageData::ImageData(const PixelStorage storage, const PixelFormat format, const VectorTypeFor& size, Containers::Array&& data, const void* const importerState) noexcept: ImageData{storage, format, {}, Magnum::pixelSize(format), size, std::move(data), importerState} {} +template ImageData::ImageData(const PixelStorage storage, const PixelFormat format, const VectorTypeFor& size, const DataFlags dataFlags, const Containers::ArrayView data, const void* const importerState) noexcept: ImageData{storage, format, size, Containers::Array{const_cast(static_cast(data.data())), data.size(), Implementation::nonOwnedArrayDeleter}, importerState} { + CORRADE_ASSERT(!(dataFlags & DataFlag::Owned), + "Trade::ImageData: can't construct a non-owned instance with" << dataFlags, ); + _dataFlags = dataFlags; +} + +template ImageData::ImageData(const PixelFormat format, const VectorTypeFor& size, Containers::Array&& data, const void* const importerState) noexcept: ImageData{{}, format, size, std::move(data), importerState} {} + +template ImageData::ImageData(const PixelFormat format, const VectorTypeFor& size, const DataFlags dataFlags, const Containers::ArrayView data, const void* const importerState) noexcept: ImageData{format, size, Containers::Array{const_cast(static_cast(data.data())), data.size(), Implementation::nonOwnedArrayDeleter}, importerState} { + CORRADE_ASSERT(!(dataFlags & DataFlag::Owned), + "Trade::ImageData: can't construct a non-owned instance with" << dataFlags, ); + _dataFlags = dataFlags; +} + template ImageData::ImageData(const PixelStorage storage, const UnsignedInt format, const UnsignedInt formatExtra, const UnsignedInt pixelSize, const VectorTypeFor& size, Containers::Array&& data, const void* const importerState) noexcept: ImageData{storage, pixelFormatWrap(format), formatExtra, pixelSize, size, std::move(data), importerState} {} -template ImageData::ImageData(const PixelStorage storage, const PixelFormat format, const UnsignedInt formatExtra, const UnsignedInt pixelSize, const VectorTypeFor& size, Containers::Array&& data, const void* const importerState) noexcept: _compressed{false}, _storage{storage}, _format{format}, _formatExtra{formatExtra}, _pixelSize{pixelSize}, _size{size}, _data{std::move(data)}, _importerState{importerState} { +template ImageData::ImageData(const PixelStorage storage, const PixelFormat format, const UnsignedInt formatExtra, const UnsignedInt pixelSize, const VectorTypeFor& size, Containers::Array&& data, const void* const importerState) noexcept: _dataFlags{DataFlag::Owned|DataFlag::Mutable}, _compressed{false}, _storage{storage}, _format{format}, _formatExtra{formatExtra}, _pixelSize{pixelSize}, _size{size}, _data{std::move(data)}, _importerState{importerState} { CORRADE_ASSERT(Magnum::Implementation::imageDataSize(*this) <= _data.size(), "Trade::ImageData: data too small, got" << _data.size() << "but expected at least" << Magnum::Implementation::imageDataSize(*this) << "bytes", ); } -template ImageData::ImageData(const CompressedPixelStorage storage, const CompressedPixelFormat format, const VectorTypeFor& size, Containers::Array&& data, const void* const importerState) noexcept: _compressed{true}, _compressedStorage{storage}, _compressedFormat{format}, _size{size}, _data{std::move(data)}, _importerState{importerState} {} +template ImageData::ImageData(const PixelStorage storage, const UnsignedInt format, const UnsignedInt formatExtra, const UnsignedInt pixelSize, const VectorTypeFor& size, const DataFlags dataFlags, const Containers::ArrayView data, const void* const importerState) noexcept: ImageData{storage, pixelFormatWrap(format), formatExtra, pixelSize, size, dataFlags, data, importerState} {} + +template ImageData::ImageData(const PixelStorage storage, const PixelFormat format, const UnsignedInt formatExtra, const UnsignedInt pixelSize, const VectorTypeFor& size, const DataFlags dataFlags, Containers::ArrayView data, const void* const importerState) noexcept: ImageData{storage, format, formatExtra, pixelSize, size, Containers::Array{const_cast(static_cast(data.data())), data.size(), Implementation::nonOwnedArrayDeleter}, importerState} { + CORRADE_ASSERT(!(dataFlags & DataFlag::Owned), + "Trade::ImageData: can't construct a non-owned instance with" << dataFlags, ); + _dataFlags = dataFlags; +} + +template ImageData::ImageData(const CompressedPixelStorage storage, const CompressedPixelFormat format, const VectorTypeFor& size, Containers::Array&& data, const void* const importerState) noexcept: _dataFlags{DataFlag::Owned|DataFlag::Mutable}, _compressed{true}, _compressedStorage{storage}, _compressedFormat{format}, _size{size}, _data{std::move(data)}, _importerState{importerState} {} + +template ImageData::ImageData(const CompressedPixelStorage storage, const CompressedPixelFormat format, const VectorTypeFor& size, const DataFlags dataFlags, const Containers::ArrayView data, const void* const importerState) noexcept: ImageData{storage, format, size, Containers::Array{const_cast(static_cast(data.data())), data.size(), Implementation::nonOwnedArrayDeleter}, importerState} { + CORRADE_ASSERT(!(dataFlags & DataFlag::Owned), + "Trade::ImageData: can't construct a non-owned instance with" << dataFlags, ); + _dataFlags = dataFlags; +} + +template ImageData::ImageData(const CompressedPixelFormat format, const VectorTypeFor& size, Containers::Array&& data, const void* const importerState) noexcept: ImageData{{}, format, size, std::move(data), importerState} {} + +template ImageData::ImageData(const CompressedPixelFormat format, const VectorTypeFor& size, const DataFlags dataFlags, const Containers::ArrayView data, const void* const importerState) noexcept: ImageData{format, size, Containers::Array{const_cast(static_cast(data.data())), data.size(), Implementation::nonOwnedArrayDeleter}, importerState} { + CORRADE_ASSERT(!(dataFlags & DataFlag::Owned), + "Trade::ImageData: can't construct a non-owned instance with" << dataFlags, ); + _dataFlags = dataFlags; +} template ImageData::ImageData(const CompressedPixelStorage storage, const UnsignedInt format, const VectorTypeFor& size, Containers::Array&& data, const void* const importerState) noexcept: ImageData{storage, compressedPixelFormatWrap(format), size, std::move(data), importerState} {} -template ImageData::ImageData(ImageData&& other) noexcept: _compressed{std::move(other._compressed)}, _size{std::move(other._size)}, _data{std::move(other._data)}, _importerState{std::move(other._importerState)} { +template ImageData::ImageData(const CompressedPixelStorage storage, const UnsignedInt format, const VectorTypeFor& size, const DataFlags dataFlags, const Containers::ArrayView data, const void* const importerState) noexcept: ImageData{storage, format, size, Containers::Array{const_cast(static_cast(data.data())), data.size(), Implementation::nonOwnedArrayDeleter}, importerState} { + CORRADE_ASSERT(!(dataFlags & DataFlag::Owned), + "Trade::ImageData: can't construct a non-owned instance with" << dataFlags, ); + _dataFlags = dataFlags; +} + +template ImageData::ImageData(ImageData&& other) noexcept: _dataFlags{other._dataFlags}, _compressed{std::move(other._compressed)}, _size{std::move(other._size)}, _data{std::move(other._data)}, _importerState{std::move(other._importerState)} { if(_compressed) { new(&_compressedStorage) CompressedPixelStorage{std::move(other._compressedStorage)}; _compressedFormat = std::move(other._compressedFormat); @@ -66,6 +108,7 @@ template ImageData::ImageData(ImageData ImageData& ImageData::operator=(ImageData&& other) noexcept { using std::swap; + swap(_dataFlags, other._dataFlags); swap(_compressed, other._compressed); if(_compressed) { swap(_compressedStorage, other._compressedStorage); @@ -115,22 +158,25 @@ template UnsignedInt ImageData::pixelSize() template std::pair, VectorTypeFor> ImageData::dataProperties() const { CORRADE_ASSERT(!_compressed, "Trade::ImageData::dataProperties(): the image is compressed", {}); - return Implementation::imageDataProperties(*this); + return Magnum::Implementation::imageDataProperties(*this); } -template Containers::StridedArrayView ImageData::pixels() { - CORRADE_ASSERT(!_compressed, "Trade::ImageData::pixels(): the image is compressed", {}); - return Implementation::imagePixelView(*this); +template Containers::ArrayView ImageData::mutableData() & { + CORRADE_ASSERT(_dataFlags & DataFlag::Mutable, + "Trade::ImageData::mutableData(): the image is not mutable", {}); + return _data; } template Containers::StridedArrayView ImageData::pixels() const { CORRADE_ASSERT(!_compressed, "Trade::ImageData::pixels(): the image is compressed", {}); - return Implementation::imagePixelView(*this); + return Magnum::Implementation::imagePixelView(*this, data()); } -template ImageData::operator BasicMutableImageView() { - CORRADE_ASSERT(!_compressed, "Trade::ImageData: the image is compressed", (BasicMutableImageView{_storage, _format, _formatExtra, _pixelSize, _size})); - return BasicMutableImageView{_storage, _format, _formatExtra, _pixelSize, _size, _data}; +template Containers::StridedArrayView ImageData::mutablePixels() { + CORRADE_ASSERT(_dataFlags & DataFlag::Mutable, + "Trade::ImageData::mutablePixels(): the image is not mutable", {}); + CORRADE_ASSERT(!_compressed, "Trade::ImageData::mutablePixels(): the image is compressed", {}); + return Magnum::Implementation::imagePixelView(*this, mutableData()); } template ImageData::operator BasicImageView() const { @@ -138,11 +184,11 @@ template ImageData::operator BasicImageView< return BasicImageView{_storage, _format, _formatExtra, _pixelSize, _size, _data}; } -template ImageData::operator BasicMutableCompressedImageView() { - CORRADE_ASSERT(_compressed, "Trade::ImageData: the image is not compressed", (BasicMutableCompressedImageView{_compressedStorage, _compressedFormat, _size})); - return BasicMutableCompressedImageView{ - _compressedStorage, - _compressedFormat, _size, _data}; +template ImageData::operator BasicMutableImageView() { + CORRADE_ASSERT(_dataFlags & DataFlag::Mutable, + "Trade::ImageData: the image is not mutable", (BasicMutableImageView{_storage, _format, _formatExtra, _pixelSize, _size})); + CORRADE_ASSERT(!_compressed, "Trade::ImageData: the image is compressed", (BasicMutableImageView{_storage, _format, _formatExtra, _pixelSize, _size})); + return BasicMutableImageView{_storage, _format, _formatExtra, _pixelSize, _size, _data}; } template ImageData::operator BasicCompressedImageView() const { @@ -152,6 +198,16 @@ template ImageData::operator BasicCompressed _compressedFormat, _size, _data}; } +template ImageData::operator BasicMutableCompressedImageView() { + CORRADE_ASSERT(_dataFlags & DataFlag::Mutable, + "Trade::ImageData: the image is not mutable", + (BasicMutableCompressedImageView{_compressedStorage, _compressedFormat, _size})); + CORRADE_ASSERT(_compressed, "Trade::ImageData: the image is not compressed", (BasicMutableCompressedImageView{_compressedStorage, _compressedFormat, _size})); + return BasicMutableCompressedImageView{ + _compressedStorage, + _compressedFormat, _size, _data}; +} + template Containers::Array ImageData::release() { Containers::Array data{std::move(_data)}; _size = {}; diff --git a/src/Magnum/Trade/ImageData.h b/src/Magnum/Trade/ImageData.h index ea025c0bd..7caaf4c9e 100644 --- a/src/Magnum/Trade/ImageData.h +++ b/src/Magnum/Trade/ImageData.h @@ -33,6 +33,7 @@ #include "Magnum/DimensionTraits.h" #include "Magnum/PixelStorage.h" +#include "Magnum/Trade/Data.h" #include "Magnum/Trade/Trade.h" #include "Magnum/Trade/visibility.h" @@ -87,6 +88,18 @@ compressed properties through @ref compressedStorage() and @snippet MagnumTrade.cpp ImageData-usage +@section Trade-ImageData-usage-mutable Mutable data access + +The interfaces implicitly provide @cpp const @ce views on the contained +pixel data through the @ref data() and @ref pixels() accessors. This is done +because in general case the data can also refer to a memory-mapped file or +constant memory. In cases when it's desirable to modify the data in-place, +there's the @ref mutableData() and @ref mutablePixels() set of functions. To +use these, you need to check that the data are mutable using @ref dataFlags() +first. The following snippet flips the R and B channels of the imported image: + +@snippet MagnumTrade.cpp ImageData-usage-mutable + @see @ref ImageData1D, @ref ImageData2D, @ref ImageData3D, @ref Image-pixel-views */ @@ -97,7 +110,7 @@ template class ImageData { }; /** - * @brief Construct uncompressed image data + * @brief Construct an uncompressed image data * @param storage Storage of pixel data * @param format Format of pixel data * @param size Image size @@ -106,11 +119,34 @@ template class ImageData { * * The @p data array is expected to be of proper size for given * parameters. + * + * The @ref dataFlags() are implicitly set to a combination of + * @ref DataFlag::Owned and @ref DataFlag::Mutable. For non-owned data + * use the @ref ImageData(PixelStorage, PixelFormat, const VectorTypeFor&, DataFlags, Containers::ArrayView, const void*) + * constructor instead. */ explicit ImageData(PixelStorage storage, PixelFormat format, const VectorTypeFor& size, Containers::Array&& data, const void* importerState = nullptr) noexcept; /** - * @brief Construct uncompressed image data + * @brief Construct a non-owned uncompressed image data + * @param storage Storage of pixel data + * @param format Format of pixel data + * @param size Image size + * @param dataFlags Data flags + * @param data View on image data + * @param importerState Importer-specific state + * @m_since_latest + * + * Compared to @ref ImageData(PixelStorage, PixelFormat, const VectorTypeFor&, Containers::Array&&, const void*) + * creates an instance that doesn't own the passed data. The + * @p dataFlags parameter can contain @ref DataFlag::Mutable to + * indicate the external data can be modified, and is expected to *not* + * have @ref DataFlag::Owned set. + */ + explicit ImageData(PixelStorage storage, PixelFormat format, const VectorTypeFor& size, DataFlags dataFlags, Containers::ArrayView data, const void* importerState = nullptr) noexcept; + + /** + * @brief Construct an uncompressed image data * @param format Format of pixel data * @param size Image size * @param data Image data @@ -119,10 +155,27 @@ template class ImageData { * Equivalent to calling @ref ImageData(PixelStorage, PixelFormat, const VectorTypeFor&, Containers::Array&&, const void*) * with default-constructed @ref PixelStorage. */ - explicit ImageData(PixelFormat format, const VectorTypeFor& size, Containers::Array&& data, const void* importerState = nullptr) noexcept: ImageData{{}, format, size, std::move(data), importerState} {} + explicit ImageData(PixelFormat format, const VectorTypeFor& size, Containers::Array&& data, const void* importerState = nullptr) noexcept; + + /** + * @brief Construct a non-owned uncompressed image data + * @param format Format of pixel data + * @param size Image size + * @param dataFlags Data flags + * @param data View on image data + * @param importerState Importer-specific state + * @m_since_latest + * + * Compared to @ref ImageData(PixelFormat, const VectorTypeFor&, Containers::Array&&, const void*) + * creates an instance that doesn't own the passed data. The + * @p dataFlags parameter can contain @ref DataFlag::Mutable to + * indicate the external data can be modified, and is expected to *not* + * have @ref DataFlag::Owned set. + */ + explicit ImageData(PixelFormat format, const VectorTypeFor& size, DataFlags dataFlags, Containers::ArrayView data, const void* importerState = nullptr) noexcept; /** - * @brief Construct uncompressed image data with implementation-specific pixel format + * @brief Construct an uncompressed image data with implementation-specific pixel format * @param storage Storage of pixel data * @param format Format of pixel data * @param formatExtra Additional pixel format specifier @@ -139,7 +192,10 @@ template class ImageData { * @ref Magnum::PixelFormat "PixelFormat". * * The @p data array is expected to be of proper size for given - * parameters. + * parameters. The @ref dataFlags() are implicitly set to a combination + * of @ref DataFlag::Owned and @ref DataFlag::Mutable. For non-owned + * data use the @ref ImageData(PixelStorage, UnsignedInt, UnsignedInt, UnsignedInt, const VectorTypeFor&, DataFlags, Containers::ArrayView, const void*) + * constructor instead. */ explicit ImageData(PixelStorage storage, UnsignedInt format, UnsignedInt formatExtra, UnsignedInt pixelSize, const VectorTypeFor& size, Containers::Array&& data, const void* importerState = nullptr) noexcept; @@ -151,7 +207,35 @@ template class ImageData { explicit ImageData(PixelStorage storage, PixelFormat format, UnsignedInt formatExtra, UnsignedInt pixelSize, const VectorTypeFor& size, Containers::Array&& data, const void* importerState = nullptr) noexcept; /** - * @brief Construct uncompressed image data with implementation-specific pixel format + * @brief Construct a non-owned uncompressed image data with implementation-specific pixel format + * @param storage Storage of pixel data + * @param format Format of pixel data + * @param formatExtra Additional pixel format specifier + * @param pixelSize Size of a pixel in given format + * @param size Image size + * @param dataFlags Data flags + * @param data View on image data + * @param importerState Importer-specific state + * @m_since_latest + * + * Compared to @ref ImageData(PixelStorage, UnsignedInt, UnsignedInt, UnsignedInt, const VectorTypeFor&, Containers::Array&&, const void*) + * creates an instance that doesn't own the passed data. The + * @p dataFlags parameter can contain @ref DataFlag::Mutable to + * indicate the external data can be modified, and is expected to *not* + * have @ref DataFlag::Owned set. + */ + explicit ImageData(PixelStorage storage, UnsignedInt format, UnsignedInt formatExtra, UnsignedInt pixelSize, const VectorTypeFor& size, DataFlags dataFlags, Containers::ArrayView data, const void* importerState = nullptr) noexcept; + + /** @overload + * @m_since_latest + * + * Equivalent to the above for @p format already wrapped with + * @ref pixelFormatWrap(). + */ + explicit ImageData(PixelStorage storage, PixelFormat format, UnsignedInt formatExtra, UnsignedInt pixelSize, const VectorTypeFor& size, DataFlags dataFlags, Containers::ArrayView data, const void* importerState = nullptr) noexcept; + + /** + * @brief Construct an uncompressed image data with implementation-specific pixel format * @param storage Storage of pixel data * @param format Format of pixel data * @param formatExtra Additional pixel format specifier @@ -166,7 +250,26 @@ template class ImageData { template explicit ImageData(PixelStorage storage, T format, U formatExtra, const VectorTypeFor& size, Containers::Array&& data, const void* importerState = nullptr) noexcept; /** - * @brief Construct uncompressed image data with implementation-specific pixel format + * @brief Construct a non-owned uncompressed image data with implementation-specific pixel format + * @param storage Storage of pixel data + * @param format Format of pixel data + * @param formatExtra Additional pixel format specifier + * @param size Image size + * @param dataFlags Data flags + * @param data View on image data + * @param importerState Importer-specific state + * @m_since_latest + * + * Compared to @ref ImageData(PixelStorage, T, U, const VectorTypeFor&, Containers::Array&&, const void*) + * creates an instance that doesn't own the passed data. The + * @p dataFlags parameter can contain @ref DataFlag::Mutable to + * indicate the external data can be modified, and is expected to *not* + * have @ref DataFlag::Owned set. + */ + template explicit ImageData(PixelStorage storage, T format, U formatExtra, const VectorTypeFor& size, DataFlags dataFlags, Containers::ArrayView data, const void* importerState = nullptr) noexcept; + + /** + * @brief Construct an uncompressed image data with implementation-specific pixel format * @param storage Storage of pixel data * @param format Format of pixel data * @param size Image size @@ -180,7 +283,25 @@ template class ImageData { template explicit ImageData(PixelStorage storage, T format, const VectorTypeFor& size, Containers::Array&& data, const void* importerState = nullptr) noexcept; /** - * @brief Construct compressed image data + * @brief Construct a non-owned uncompressed image data with implementation-specific pixel format + * @param storage Storage of pixel data + * @param format Format of pixel data + * @param size Image size + * @param dataFlags Data flags + * @param data view on image data + * @param importerState Importer-specific state + * @m_since_latest + * + * Compared to @ref ImageData(PixelStorage, T, const VectorTypeFor&, Containers::Array&&, const void*) + * creates an instance that doesn't own the passed data. The + * @p dataFlags parameter can contain @ref DataFlag::Mutable to + * indicate the external data can be modified, and is expected to *not* + * have @ref DataFlag::Owned set. + */ + template explicit ImageData(PixelStorage storage, T format, const VectorTypeFor& size, DataFlags dataFlags, Containers::ArrayView data, const void* importerState = nullptr) noexcept; + + /** + * @brief Construct a compressed image data * @param storage Storage of compressed pixel data * @param format Format of compressed pixel data * @param size Image size @@ -190,7 +311,25 @@ template class ImageData { explicit ImageData(CompressedPixelStorage storage, CompressedPixelFormat format, const VectorTypeFor& size, Containers::Array&& data, const void* importerState = nullptr) noexcept; /** - * @brief Construct compressed image data + * @brief Construct a non-owned compressed image data + * @param storage Storage of compressed pixel data + * @param format Format of compressed pixel data + * @param size Image size + * @param dataFlags Data flags + * @param data View on image data + * @param importerState Importer-specific state + * @m_since_latest + * + * Compared to @ref ImageData(CompressedPixelStorage, CompressedPixelFormat, const VectorTypeFor&, Containers::Array&&, const void*) + * creates an instance that doesn't own the passed data. The + * @p dataFlags parameter can contain @ref DataFlag::Mutable to + * indicate the external data can be modified, and is expected to *not* + * have @ref DataFlag::Owned set. + */ + explicit ImageData(CompressedPixelStorage storage, CompressedPixelFormat format, const VectorTypeFor& size, DataFlags dataFlags, Containers::ArrayView data, const void* importerState = nullptr) noexcept; + + /** + * @brief Construct a compressed image data * @param format Format of compressed pixel data * @param size Image size * @param data Image data @@ -199,10 +338,27 @@ template class ImageData { * Equivalent to calling @ref ImageData(CompressedPixelStorage, CompressedPixelFormat, const VectorTypeFor&, Containers::Array&&, const void*) * with default-constructed @ref CompressedPixelStorage. */ - explicit ImageData(CompressedPixelFormat format, const VectorTypeFor& size, Containers::Array&& data, const void* importerState = nullptr) noexcept: ImageData{{}, format, size, std::move(data), importerState} {} + explicit ImageData(CompressedPixelFormat format, const VectorTypeFor& size, Containers::Array&& data, const void* importerState = nullptr) noexcept; + + /** + * @brief Construct a non-owned compressed image data + * @param format Format of compressed pixel data + * @param size Image size + * @param dataFlags Data flags + * @param data View on image data + * @param importerState Importer-specific state + * @m_since_latest + * + * Compared to @ref ImageData(CompressedPixelFormat, const VectorTypeFor&, Containers::Array&&, const void*) + * creates an instance that doesn't own the passed data. The + * @p dataFlags parameter can contain @ref DataFlag::Mutable to + * indicate the external data can be modified, and is expected to *not* + * have @ref DataFlag::Owned set. + */ + explicit ImageData(CompressedPixelFormat format, const VectorTypeFor& size, DataFlags dataFlags, Containers::ArrayView data, const void* importerState = nullptr) noexcept; /** - * @brief Construct compressed image data + * @brief Construct a compressed image data * @param storage Storage of compressed pixel data * @param format Format of compressed pixel data * @param size Image size @@ -214,6 +370,24 @@ template class ImageData { */ template explicit ImageData(CompressedPixelStorage storage, T format, const VectorTypeFor& size, Containers::Array&& data, const void* importerState = nullptr) noexcept; + /** + * @brief Construct a non-owned compressed image data + * @param storage Storage of compressed pixel data + * @param format Format of compressed pixel data + * @param size Image size + * @param dataFlags Data flags + * @param data View on image data + * @param importerState Importer-specific state + * @m_since_latest + * + * Compared to @ref ImageData(CompressedPixelStorage, T, const VectorTypeFor&, Containers::Array&&, const void*) + * creates an instance that doesn't own the passed data. The + * @p dataFlags parameter can contain @ref DataFlag::Mutable to + * indicate the external data can be modified, and is expected to *not* + * have @ref DataFlag::Owned set. + */ + template explicit ImageData(CompressedPixelStorage storage, T format, const VectorTypeFor& size, DataFlags dataFlags, Containers::ArrayView data, const void* importerState = nullptr) noexcept; + /** * @brief Construct from existing data with attached importer state * @@ -237,6 +411,12 @@ template class ImageData { /** @brief Move assignment */ ImageData& operator=(ImageData&& other) noexcept; + /** + * @brief Data flags + * @m_since_latest + */ + DataFlags dataFlags() const { return _dataFlags; } + /** @brief Whether the image is compressed */ bool isCompressed() const { return _compressed; } @@ -253,6 +433,9 @@ template class ImageData { /** * @brief Conversion to a mutable view * @m_since{2019,10} + * + * The image is expected to be uncompressed and mutable. + * @see @ref isCompressed(), @ref dataFlags() */ /*implicit*/ operator BasicMutableImageView(); @@ -269,6 +452,9 @@ template class ImageData { /** * @brief Conversion to a mutable compressed view * @m_since{2019,10} + * + * The image is expected to be compressed and mutable. + * @see @ref isCompressed(), @ref dataFlags() */ /*implicit*/ operator BasicMutableCompressedImageView(); @@ -358,9 +544,6 @@ template class ImageData { * * @see @ref release(), @ref pixels() */ - Containers::ArrayView data() & { return _data; } - - /** @overload */ Containers::ArrayView data() const & { return _data; } /** @@ -369,7 +552,9 @@ template class ImageData { * * Unlike @ref data(), which returns a view, this is equivalent to * @ref release() to avoid a dangling view when the temporary instance - * goes out of scope. + * goes out of scope. Note that the returned array has a custom no-op + * deleter when the data are not owned by the image, and while the + * returned array type is mutable, the actual memory might be not. * @todoc stupid doxygen can't link to & overloads ffs */ Containers::Array data() && { return release(); } @@ -380,6 +565,16 @@ template class ImageData { */ Containers::Array data() const && = delete; + /** + * @brief Mutable image data + * @m_since_latest + * + * Like @ref data(), but returns a non-const view. Expects that the + * image is mutable. + * @see @ref dataFlags() + */ + Containers::ArrayView mutableData() &; + #ifdef MAGNUM_BUILD_DEPRECATED /** * @brief Image data in a particular type @@ -414,8 +609,17 @@ template class ImageData { * @see @ref isCompressed(), * @ref Corrade::Containers::StridedArrayView::isContiguous() */ - Containers::StridedArrayView pixels(); - Containers::StridedArrayView pixels() const; /**< @overload */ + Containers::StridedArrayView pixels() const; + + /** + * @brief Mutable view on pixel data + * @m_since_latest + * + * Like @ref pixels() const, but returns a non-const view. Expects that + * the image is mutable. + * @see @ref dataFlags() + */ + Containers::StridedArrayView mutablePixels(); /** * @brief View on pixel data with a concrete pixel type @@ -426,26 +630,35 @@ template class ImageData { * correct type for given @ref format() --- checking it on the library * side is not possible for the general case. */ - template Containers::StridedArrayView pixels() { + template Containers::StridedArrayView pixels() const { /* Deliberately not adding a StridedArrayView include, it should work without since this is a templated function and we declare arrayCast() above to satisfy two-phase lookup. */ - return Containers::arrayCast(pixels()); + return Containers::arrayCast(pixels()); } /** - * @overload + * @brief Mutable view on pixel data with a concrete pixel type * @m_since{2019,10} + * + * Like @ref pixels() const, but returns a non-const view. Expects that + * the image is mutable. + * @see @ref dataFlags() */ - template Containers::StridedArrayView pixels() const { - return Containers::arrayCast(pixels()); + template Containers::StridedArrayView mutablePixels() { + /* Deliberately not adding a StridedArrayView include, it should + work without since this is a templated function */ + return Containers::arrayCast(mutablePixels()); } /** * @brief Release data storage * * Releases the ownership of the data array and resets internal state - * to default. + * to default. The image then behaves like it's empty. Note that + * the returned array has a custom no-op deleter when the data are not + * owned by the image, and while the returned array type is mutable, + * the actual memory might be not. * @see @ref data() */ Containers::Array release(); @@ -465,6 +678,9 @@ template class ImageData { explicit ImageData(CompressedPixelStorage storage, UnsignedInt format, const VectorTypeFor& size, Containers::Array&& data, const void* importerState = nullptr) noexcept; + explicit ImageData(CompressedPixelStorage storage, UnsignedInt format, const VectorTypeFor& size, DataFlags dataFlags, Containers::ArrayView data, const void* importerState = nullptr) noexcept; + + DataFlags _dataFlags; bool _compressed; union { PixelStorage _storage; @@ -495,16 +711,31 @@ template template ImageData template ImageData::ImageData(const PixelStorage storage, const T format, const U formatExtra, const VectorTypeFor& size, const DataFlags dataFlags, const Containers::ArrayView data, const void* const importerState) noexcept: ImageData{storage, UnsignedInt(format), UnsignedInt(formatExtra), Magnum::Implementation::pixelSizeAdl(format, formatExtra), size, dataFlags, data, importerState} { + static_assert(sizeof(T) <= 4 && sizeof(U) <= 4, + "format types larger than 32bits are not supported"); +} + template template ImageData::ImageData(const PixelStorage storage, const T format, const VectorTypeFor& size, Containers::Array&& data, const void* const importerState) noexcept: ImageData{storage, UnsignedInt(format), {}, Magnum::Implementation::pixelSizeAdl(format), size, std::move(data), importerState} { static_assert(sizeof(T) <= 4, "format types larger than 32bits are not supported"); } +template template ImageData::ImageData(const PixelStorage storage, const T format, const VectorTypeFor& size, const DataFlags dataFlags, const Containers::ArrayView data, const void* const importerState) noexcept: ImageData{storage, UnsignedInt(format), {}, Magnum::Implementation::pixelSizeAdl(format), size, dataFlags, data, importerState} { + static_assert(sizeof(T) <= 4, + "format types larger than 32bits are not supported"); +} + template template ImageData::ImageData(const CompressedPixelStorage storage, const T format, const VectorTypeFor& size, Containers::Array&& data, const void* const importerState) noexcept: ImageData{storage, UnsignedInt(format), size, std::move(data), importerState} { static_assert(sizeof(T) <= 4, "format types larger than 32bits are not supported"); } +template template ImageData::ImageData(const CompressedPixelStorage storage, const T format, const VectorTypeFor& size, const DataFlags dataFlags, const Containers::ArrayView data, const void* const importerState) noexcept: ImageData{storage, UnsignedInt(format), size, dataFlags, data, importerState} { + static_assert(sizeof(T) <= 4, + "format types larger than 32bits are not supported"); +} + }} #endif diff --git a/src/Magnum/Trade/Test/ImageDataTest.cpp b/src/Magnum/Trade/Test/ImageDataTest.cpp index d68a895bc..bfe820723 100644 --- a/src/Magnum/Trade/Test/ImageDataTest.cpp +++ b/src/Magnum/Trade/Test/ImageDataTest.cpp @@ -47,6 +47,15 @@ struct ImageDataTest: TestSuite::Tester { void constructCompressedGeneric(); void constructCompressedImplementationSpecific(); + void constructGenericNotOwned(); + void constructImplementationSpecificNotOwned(); + void constructCompressedGenericNotOwned(); + void constructCompressedImplementationSpecificNotOwned(); + void constructGenericNotOwnedFlagOwned(); + void constructImplementationSpecificNotOwnedFlagOwned(); + void constructCompressedGenericNotOwnedFlagOwned(); + void constructCompressedImplementationSpecificNotOwnedFlagOwned(); + void constructInvalidSize(); void constructCompressedInvalidSize(); @@ -67,6 +76,7 @@ struct ImageDataTest: TestSuite::Tester { void data(); void dataRvalue(); + void mutableAccessNotAllowed(); void dataProperties(); @@ -91,11 +101,30 @@ template<> struct MutabilityTraits { static const char* name() { return "MutableImageView"; } }; +struct { + const char* name; + DataFlags dataFlags; +} NotOwnedData[] { + {"", {}}, + {"mutable", DataFlag::Mutable}, +}; + ImageDataTest::ImageDataTest() { addTests({&ImageDataTest::constructGeneric, &ImageDataTest::constructImplementationSpecific, &ImageDataTest::constructCompressedGeneric, - &ImageDataTest::constructCompressedImplementationSpecific, + &ImageDataTest::constructCompressedImplementationSpecific}); + + addInstancedTests({&ImageDataTest::constructGenericNotOwned, + &ImageDataTest::constructImplementationSpecificNotOwned, + &ImageDataTest::constructCompressedGenericNotOwned, + &ImageDataTest::constructCompressedImplementationSpecificNotOwned}, + Containers::arraySize(NotOwnedData)); + + addTests({&ImageDataTest::constructGenericNotOwnedFlagOwned, + &ImageDataTest::constructImplementationSpecificNotOwnedFlagOwned, + &ImageDataTest::constructCompressedGenericNotOwnedFlagOwned, + &ImageDataTest::constructCompressedImplementationSpecificNotOwnedFlagOwned, &ImageDataTest::constructInvalidSize, &ImageDataTest::constructCompressedInvalidSize, @@ -121,6 +150,7 @@ ImageDataTest::ImageDataTest() { &ImageDataTest::data, &ImageDataTest::dataRvalue, + &ImageDataTest::mutableAccessNotAllowed, &ImageDataTest::dataProperties, @@ -167,14 +197,19 @@ void ImageDataTest::constructGeneric() { int state; ImageData2D a{PixelFormat::RGBA8Unorm, {1, 3}, Containers::Array{data, 4*4}, &state}; + CORRADE_COMPARE(a.dataFlags(), DataFlag::Owned|DataFlag::Mutable); CORRADE_VERIFY(!a.isCompressed()); CORRADE_COMPARE(a.storage().alignment(), 4); CORRADE_COMPARE(a.format(), PixelFormat::RGBA8Unorm); CORRADE_COMPARE(a.formatExtra(), 0); CORRADE_COMPARE(a.pixelSize(), 4); CORRADE_COMPARE(a.size(), (Vector2i{1, 3})); - CORRADE_COMPARE(a.data(), data); + CORRADE_COMPARE(static_cast(a.data().data()), data); CORRADE_COMPARE(a.data().size(), 4*4); + CORRADE_COMPARE(static_cast(&a.pixels()[0][0]), data); + CORRADE_COMPARE(static_cast(a.mutableData().data()), data); + CORRADE_COMPARE(a.mutableData().size(), 4*4); + CORRADE_COMPARE(static_cast(&a.mutablePixels()[0][0]), data); CORRADE_COMPARE(a.importerState(), &state); } { auto data = new char[3*2]; @@ -182,14 +217,19 @@ void ImageDataTest::constructGeneric() { ImageData2D a{PixelStorage{}.setAlignment(1), PixelFormat::R16UI, {1, 3}, Containers::Array{data, 3*2}, &state}; + CORRADE_COMPARE(a.dataFlags(), DataFlag::Owned|DataFlag::Mutable); CORRADE_VERIFY(!a.isCompressed()); CORRADE_COMPARE(a.storage().alignment(), 1); CORRADE_COMPARE(a.format(), PixelFormat::R16UI); CORRADE_COMPARE(a.formatExtra(), 0); CORRADE_COMPARE(a.pixelSize(), 2); CORRADE_COMPARE(a.size(), (Vector2i{1, 3})); - CORRADE_COMPARE(a.data(), data); + CORRADE_COMPARE(static_cast(a.data().data()), data); CORRADE_COMPARE(a.data().size(), 3*2); + CORRADE_COMPARE(static_cast(&a.pixels()[0][0]), data); + CORRADE_COMPARE(static_cast(a.mutableData().data()), data); + CORRADE_COMPARE(a.mutableData().size(), 3*2); + CORRADE_COMPARE(static_cast(&a.mutablePixels()[0][0]), data); CORRADE_COMPARE(a.importerState(), &state); } } @@ -202,14 +242,19 @@ void ImageDataTest::constructImplementationSpecific() { ImageData2D a{PixelStorage{}.setAlignment(1), Vk::PixelFormat::R32G32B32F, {1, 3}, Containers::Array{data, 3*12}, &state}; + CORRADE_COMPARE(a.dataFlags(), DataFlag::Owned|DataFlag::Mutable); CORRADE_VERIFY(!a.isCompressed()); CORRADE_COMPARE(a.storage().alignment(), 1); CORRADE_COMPARE(a.format(), pixelFormatWrap(Vk::PixelFormat::R32G32B32F)); CORRADE_COMPARE(a.formatExtra(), 0); CORRADE_COMPARE(a.pixelSize(), 12); CORRADE_COMPARE(a.size(), (Vector2i{1, 3})); - CORRADE_COMPARE(a.data(), data); + CORRADE_COMPARE(static_cast(a.data().data()), data); CORRADE_COMPARE(a.data().size(), 3*12); + CORRADE_COMPARE(static_cast(&a.pixels()[0][0]), data); + CORRADE_COMPARE(static_cast(a.mutableData().data()), data); + CORRADE_COMPARE(a.mutableData().size(), 3*12); + CORRADE_COMPARE(static_cast(&a.mutablePixels()[0][0]), data); CORRADE_COMPARE(a.importerState(), &state); } @@ -220,13 +265,18 @@ void ImageDataTest::constructImplementationSpecific() { ImageData2D a{PixelStorage{}.setAlignment(1), GL::PixelFormat::RGB, GL::PixelType::UnsignedShort, {1, 3}, Containers::Array{data, 3*6}, &state}; + CORRADE_COMPARE(a.dataFlags(), DataFlag::Owned|DataFlag::Mutable); CORRADE_VERIFY(!a.isCompressed()); CORRADE_COMPARE(a.format(), pixelFormatWrap(GL::PixelFormat::RGB)); CORRADE_COMPARE(a.formatExtra(), UnsignedInt(GL::PixelType::UnsignedShort)); CORRADE_COMPARE(a.pixelSize(), 6); CORRADE_COMPARE(a.size(), (Vector2i{1, 3})); - CORRADE_COMPARE(a.data(), data); + CORRADE_COMPARE(static_cast(a.data().data()), data); CORRADE_COMPARE(a.data().size(), 3*6); + CORRADE_COMPARE(static_cast(&a.pixels>()[0][0]), data); + CORRADE_COMPARE(static_cast(a.mutableData().data()), data); + CORRADE_COMPARE(a.mutableData().size(), 3*6); + CORRADE_COMPARE(static_cast(&a.mutablePixels>()[0][0]), data); CORRADE_COMPARE(a.importerState(), &state); } @@ -236,14 +286,19 @@ void ImageDataTest::constructImplementationSpecific() { int state; ImageData2D a{PixelStorage{}.setAlignment(1), 666, 1337, 6, {1, 3}, Containers::Array{data, 3*6}, &state}; + CORRADE_COMPARE(a.dataFlags(), DataFlag::Owned|DataFlag::Mutable); CORRADE_VERIFY(!a.isCompressed()); CORRADE_COMPARE(a.storage().alignment(), 1); CORRADE_COMPARE(a.format(), pixelFormatWrap(GL::PixelFormat::RGB)); CORRADE_COMPARE(a.formatExtra(), UnsignedInt(GL::PixelType::UnsignedShort)); CORRADE_COMPARE(a.pixelSize(), 6); CORRADE_COMPARE(a.size(), (Vector2i{1, 3})); - CORRADE_COMPARE(a.data(), data); + CORRADE_COMPARE(static_cast(a.data().data()), data); CORRADE_COMPARE(a.data().size(), 3*6); + CORRADE_COMPARE(static_cast(&a.pixels>()[0][0]), data); + CORRADE_COMPARE(static_cast(a.mutableData().data()), data); + CORRADE_COMPARE(a.mutableData().size(), 3*6); + CORRADE_COMPARE(static_cast(&a.mutablePixels>()[0][0]), data); CORRADE_COMPARE(a.importerState(), &state); } } @@ -255,12 +310,15 @@ void ImageDataTest::constructCompressedGeneric() { ImageData2D a{CompressedPixelFormat::Bc1RGBAUnorm, {4, 4}, Containers::Array{data, 8}, &state}; + CORRADE_COMPARE(a.dataFlags(), DataFlag::Owned|DataFlag::Mutable); CORRADE_VERIFY(a.isCompressed()); CORRADE_COMPARE(a.compressedStorage().compressedBlockSize(), Vector3i{0}); CORRADE_COMPARE(a.compressedFormat(), CompressedPixelFormat::Bc1RGBAUnorm); CORRADE_COMPARE(a.size(), (Vector2i{4, 4})); - CORRADE_COMPARE(a.data(), data); + CORRADE_COMPARE(static_cast(a.data().data()), data); CORRADE_COMPARE(a.data().size(), 8); + CORRADE_COMPARE(static_cast(a.mutableData().data()), data); + CORRADE_COMPARE(a.mutableData().size(), 8); CORRADE_COMPARE(a.importerState(), &state); } { auto data = new char[8]; @@ -269,12 +327,15 @@ void ImageDataTest::constructCompressedGeneric() { CompressedPixelFormat::Bc1RGBAUnorm, {4, 4}, Containers::Array{data, 8}, &state}; + CORRADE_COMPARE(a.dataFlags(), DataFlag::Owned|DataFlag::Mutable); CORRADE_VERIFY(a.isCompressed()); CORRADE_COMPARE(a.compressedStorage().compressedBlockSize(), Vector3i{4}); CORRADE_COMPARE(a.compressedFormat(), CompressedPixelFormat::Bc1RGBAUnorm); CORRADE_COMPARE(a.size(), Vector2i(4, 4)); - CORRADE_COMPARE(a.data(), data); + CORRADE_COMPARE(static_cast(a.data().data()), data); CORRADE_COMPARE(a.data().size(), 8); + CORRADE_COMPARE(static_cast(a.mutableData().data()), data); + CORRADE_COMPARE(a.mutableData().size(), 8); CORRADE_COMPARE(a.importerState(), &state); } } @@ -288,18 +349,270 @@ void ImageDataTest::constructCompressedImplementationSpecific() { GL::CompressedPixelFormat::RGBS3tcDxt1, {4, 4}, Containers::Array{data, 8}, &state}; + CORRADE_COMPARE(a.dataFlags(), DataFlag::Owned|DataFlag::Mutable); CORRADE_VERIFY(a.isCompressed()); CORRADE_COMPARE(a.compressedStorage().compressedBlockSize(), Vector3i{4}); CORRADE_COMPARE(a.compressedFormat(), compressedPixelFormatWrap(GL::CompressedPixelFormat::RGBS3tcDxt1)); CORRADE_COMPARE(a.size(), (Vector2i{4, 4})); - CORRADE_COMPARE(a.data(), data); + CORRADE_COMPARE(static_cast(a.data().data()), data); CORRADE_COMPARE(a.data().size(), 8); + CORRADE_COMPARE(static_cast(a.mutableData().data()), data); + CORRADE_COMPARE(a.mutableData().size(), 8); CORRADE_COMPARE(a.importerState(), &state); } /* Manual properties not implemented yet */ } +void ImageDataTest::constructGenericNotOwned() { + auto&& instanceData = NotOwnedData[testCaseInstanceId()]; + setTestCaseDescription(instanceData.name); + + { + char data[4*4]; + int state; + ImageData2D a{PixelFormat::RGBA8Unorm, {1, 3}, instanceData.dataFlags, data, &state}; + + CORRADE_COMPARE(a.dataFlags(), instanceData.dataFlags); + CORRADE_VERIFY(!a.isCompressed()); + CORRADE_COMPARE(a.storage().alignment(), 4); + CORRADE_COMPARE(a.format(), PixelFormat::RGBA8Unorm); + CORRADE_COMPARE(a.formatExtra(), 0); + CORRADE_COMPARE(a.pixelSize(), 4); + CORRADE_COMPARE(a.size(), (Vector2i{1, 3})); + CORRADE_COMPARE(static_cast(a.data().data()), data); + CORRADE_COMPARE(a.data().size(), 4*4); + CORRADE_COMPARE(static_cast(&a.pixels()[0][0]), data); + if(instanceData.dataFlags & DataFlag::Mutable) { + CORRADE_COMPARE(static_cast(a.mutableData().data()), data); + CORRADE_COMPARE(a.mutableData().size(), 4*4); + CORRADE_COMPARE(static_cast(&a.mutablePixels()[0][0]), data); + } + CORRADE_COMPARE(a.importerState(), &state); + } { + char data[3*2]; + int state; + ImageData2D a{PixelStorage{}.setAlignment(1), + PixelFormat::R16UI, {1, 3}, instanceData.dataFlags, data, &state}; + + CORRADE_COMPARE(a.dataFlags(), instanceData.dataFlags); + CORRADE_VERIFY(!a.isCompressed()); + CORRADE_COMPARE(a.storage().alignment(), 1); + CORRADE_COMPARE(a.format(), PixelFormat::R16UI); + CORRADE_COMPARE(a.formatExtra(), 0); + CORRADE_COMPARE(a.pixelSize(), 2); + CORRADE_COMPARE(a.size(), (Vector2i{1, 3})); + CORRADE_COMPARE(static_cast(a.data().data()), data); + CORRADE_COMPARE(a.data().size(), 3*2); + CORRADE_COMPARE(static_cast(&a.pixels()[0][0]), data); + if(instanceData.dataFlags & DataFlag::Mutable) { + CORRADE_COMPARE(static_cast(a.mutableData().data()), data); + CORRADE_COMPARE(a.mutableData().size(), 3*2); + CORRADE_COMPARE(static_cast(&a.mutablePixels()[0][0]), data); + } + CORRADE_COMPARE(a.importerState(), &state); + } +} + +void ImageDataTest::constructImplementationSpecificNotOwned() { + auto&& instanceData = NotOwnedData[testCaseInstanceId()]; + setTestCaseDescription(instanceData.name); + + /* Single format */ + { + char data[3*12]; + int state; + ImageData2D a{PixelStorage{}.setAlignment(1), + Vk::PixelFormat::R32G32B32F, {1, 3}, instanceData.dataFlags, data, &state}; + + CORRADE_COMPARE(a.dataFlags(), instanceData.dataFlags); + CORRADE_VERIFY(!a.isCompressed()); + CORRADE_COMPARE(a.storage().alignment(), 1); + CORRADE_COMPARE(a.format(), pixelFormatWrap(Vk::PixelFormat::R32G32B32F)); + CORRADE_COMPARE(a.formatExtra(), 0); + CORRADE_COMPARE(a.pixelSize(), 12); + CORRADE_COMPARE(a.size(), (Vector2i{1, 3})); + CORRADE_COMPARE(static_cast(a.data().data()), data); + CORRADE_COMPARE(a.data().size(), 3*12); + CORRADE_COMPARE(static_cast(&a.pixels()[0][0]), data); + if(instanceData.dataFlags & DataFlag::Mutable) { + CORRADE_COMPARE(static_cast(a.mutableData().data()), data); + CORRADE_COMPARE(a.mutableData().size(), 3*12); + CORRADE_COMPARE(static_cast(&a.mutablePixels()[0][0]), data); + } + CORRADE_COMPARE(a.importerState(), &state); + } + + /* Format + extra */ + { + char data[3*6]; + int state; + ImageData2D a{PixelStorage{}.setAlignment(1), + GL::PixelFormat::RGB, GL::PixelType::UnsignedShort, {1, 3}, instanceData.dataFlags, data, &state}; + + CORRADE_COMPARE(a.dataFlags(), instanceData.dataFlags); + CORRADE_VERIFY(!a.isCompressed()); + CORRADE_COMPARE(a.format(), pixelFormatWrap(GL::PixelFormat::RGB)); + CORRADE_COMPARE(a.formatExtra(), UnsignedInt(GL::PixelType::UnsignedShort)); + CORRADE_COMPARE(a.pixelSize(), 6); + CORRADE_COMPARE(a.size(), (Vector2i{1, 3})); + CORRADE_COMPARE(static_cast(a.data().data()), data); + CORRADE_COMPARE(a.data().size(), 3*6); + CORRADE_COMPARE(static_cast(&a.pixels>()[0][0]), data); + if(instanceData.dataFlags & DataFlag::Mutable) { + CORRADE_COMPARE(static_cast(a.mutableData().data()), data); + CORRADE_COMPARE(a.mutableData().size(), 3*6); + CORRADE_COMPARE(static_cast(&a.mutablePixels>()[0][0]), data); + } + CORRADE_COMPARE(a.importerState(), &state); + } + + /* Manual pixel size */ + { + char data[3*6]; + int state; + ImageData2D a{PixelStorage{}.setAlignment(1), 666, 1337, 6, {1, 3}, instanceData.dataFlags, data, &state}; + + CORRADE_COMPARE(a.dataFlags(), instanceData.dataFlags); + CORRADE_VERIFY(!a.isCompressed()); + CORRADE_COMPARE(a.storage().alignment(), 1); + CORRADE_COMPARE(a.format(), pixelFormatWrap(GL::PixelFormat::RGB)); + CORRADE_COMPARE(a.formatExtra(), UnsignedInt(GL::PixelType::UnsignedShort)); + CORRADE_COMPARE(a.pixelSize(), 6); + CORRADE_COMPARE(a.size(), (Vector2i{1, 3})); + CORRADE_COMPARE(static_cast(a.data().data()), data); + CORRADE_COMPARE(a.data().size(), 3*6); + CORRADE_COMPARE(static_cast(&a.pixels>()[0][0]), data); + if(instanceData.dataFlags & DataFlag::Mutable) { + CORRADE_COMPARE(static_cast(a.mutableData().data()), data); + CORRADE_COMPARE(a.mutableData().size(), 3*6); + CORRADE_COMPARE(static_cast(&a.mutablePixels>()[0][0]), data); + } + CORRADE_COMPARE(a.importerState(), &state); + } +} + +void ImageDataTest::constructCompressedGenericNotOwned() { + auto&& instanceData = NotOwnedData[testCaseInstanceId()]; + setTestCaseDescription(instanceData.name); + + { + char data[8]; + int state; + ImageData2D a{CompressedPixelFormat::Bc1RGBAUnorm, {4, 4}, + instanceData.dataFlags, data, &state}; + + CORRADE_COMPARE(a.dataFlags(), instanceData.dataFlags); + CORRADE_VERIFY(a.isCompressed()); + CORRADE_COMPARE(a.compressedStorage().compressedBlockSize(), Vector3i{0}); + CORRADE_COMPARE(a.compressedFormat(), CompressedPixelFormat::Bc1RGBAUnorm); + CORRADE_COMPARE(a.size(), (Vector2i{4, 4})); + CORRADE_COMPARE(static_cast(a.data().data()), data); + CORRADE_COMPARE(a.data().size(), 8); + if(instanceData.dataFlags & DataFlag::Mutable) { + CORRADE_COMPARE(static_cast(a.mutableData().data()), data); + CORRADE_COMPARE(a.mutableData().size(), 8); + } + CORRADE_COMPARE(a.importerState(), &state); + } { + char data[8]; + int state; + ImageData2D a{CompressedPixelStorage{}.setCompressedBlockSize(Vector3i{4}), + CompressedPixelFormat::Bc1RGBAUnorm, {4, 4}, + instanceData.dataFlags, data, &state}; + + CORRADE_COMPARE(a.dataFlags(), instanceData.dataFlags); + CORRADE_VERIFY(a.isCompressed()); + CORRADE_COMPARE(a.compressedStorage().compressedBlockSize(), Vector3i{4}); + CORRADE_COMPARE(a.compressedFormat(), CompressedPixelFormat::Bc1RGBAUnorm); + CORRADE_COMPARE(a.size(), Vector2i(4, 4)); + CORRADE_COMPARE(static_cast(a.data().data()), data); + CORRADE_COMPARE(a.data().size(), 8); + if(instanceData.dataFlags & DataFlag::Mutable) { + CORRADE_COMPARE(static_cast(a.mutableData().data()), data); + CORRADE_COMPARE(a.mutableData().size(), 8); + } + CORRADE_COMPARE(a.importerState(), &state); + } +} + +void ImageDataTest::constructCompressedImplementationSpecificNotOwned() { + auto&& instanceData = NotOwnedData[testCaseInstanceId()]; + setTestCaseDescription(instanceData.name); + + /* Format with autodetection */ + { + char data[8]; + int state; + ImageData2D a{CompressedPixelStorage{}.setCompressedBlockSize(Vector3i{4}), + GL::CompressedPixelFormat::RGBS3tcDxt1, {4, 4}, + instanceData.dataFlags, data, &state}; + + CORRADE_COMPARE(a.dataFlags(), instanceData.dataFlags); + CORRADE_VERIFY(a.isCompressed()); + CORRADE_COMPARE(a.compressedStorage().compressedBlockSize(), Vector3i{4}); + CORRADE_COMPARE(a.compressedFormat(), compressedPixelFormatWrap(GL::CompressedPixelFormat::RGBS3tcDxt1)); + CORRADE_COMPARE(a.size(), (Vector2i{4, 4})); + CORRADE_COMPARE(static_cast(a.data().data()), data); + CORRADE_COMPARE(a.data().size(), 8); + if(instanceData.dataFlags & DataFlag::Mutable) { + CORRADE_COMPARE(static_cast(a.mutableData().data()), data); + CORRADE_COMPARE(a.mutableData().size(), 8); + } + CORRADE_COMPARE(a.importerState(), &state); + } + + /* Manual properties not implemented yet */ +} + +void ImageDataTest::constructGenericNotOwnedFlagOwned() { + char data[4*4]; + + std::ostringstream out; + Error redirectError{&out}; + ImageData2D{PixelFormat::RGBA8Unorm, {1, 3}, DataFlag::Owned, data}; + ImageData2D{PixelStorage{}.setAlignment(1), PixelFormat::R16UI, {1, 3}, DataFlag::Owned, data}; + CORRADE_COMPARE(out.str(), + "Trade::ImageData: can't construct a non-owned instance with Trade::DataFlag::Owned\n" + "Trade::ImageData: can't construct a non-owned instance with Trade::DataFlag::Owned\n"); +} + +void ImageDataTest::constructImplementationSpecificNotOwnedFlagOwned() { + char data[3*12]; + + std::ostringstream out; + Error redirectError{&out}; + ImageData2D{PixelStorage{}.setAlignment(1), Vk::PixelFormat::R32G32B32F, {1, 3}, DataFlag::Owned, data}; + ImageData2D{PixelStorage{}.setAlignment(1), GL::PixelFormat::RGB, GL::PixelType::UnsignedShort, {1, 3}, DataFlag::Owned, data}; + CORRADE_COMPARE(out.str(), + "Trade::ImageData: can't construct a non-owned instance with Trade::DataFlag::Owned\n" + "Trade::ImageData: can't construct a non-owned instance with Trade::DataFlag::Owned\n"); +} + +void ImageDataTest::constructCompressedGenericNotOwnedFlagOwned() { + char data[8]; + + std::ostringstream out; + Error redirectError{&out}; + ImageData2D{CompressedPixelFormat::Bc1RGBAUnorm, {4, 4}, DataFlag::Owned, data}; + ImageData2D{CompressedPixelStorage{}.setCompressedBlockSize(Vector3i{4}), + CompressedPixelFormat::Bc1RGBAUnorm, {4, 4}, DataFlag::Owned, data}; + CORRADE_COMPARE(out.str(), + "Trade::ImageData: can't construct a non-owned instance with Trade::DataFlag::Owned\n" + "Trade::ImageData: can't construct a non-owned instance with Trade::DataFlag::Owned\n"); +} + +void ImageDataTest::constructCompressedImplementationSpecificNotOwnedFlagOwned() { + char data[8]; + + std::ostringstream out; + Error redirectError{&out}; + ImageData2D a{CompressedPixelStorage{}.setCompressedBlockSize(Vector3i{4}), + GL::CompressedPixelFormat::RGBS3tcDxt1, {4, 4}, DataFlag::Owned, data}; + CORRADE_COMPARE(out.str(), + "Trade::ImageData: can't construct a non-owned instance with Trade::DataFlag::Owned\n"); +} + void ImageDataTest::constructInvalidSize() { std::ostringstream out; Error redirectError{&out}; @@ -343,6 +656,7 @@ void ImageDataTest::constructMoveGeneric() { CORRADE_COMPARE(a.data(), nullptr); CORRADE_COMPARE(a.size(), Vector2i{}); + CORRADE_COMPARE(b.dataFlags(), DataFlag::Owned|DataFlag::Mutable); CORRADE_VERIFY(!b.isCompressed()); CORRADE_COMPARE(b.storage().alignment(), 1); CORRADE_COMPARE(b.format(), PixelFormat::RGBA32F); @@ -360,6 +674,7 @@ void ImageDataTest::constructMoveGeneric() { CORRADE_COMPARE(b.data(), data2); CORRADE_COMPARE(b.size(), (Vector2i{2, 6})); + CORRADE_COMPARE(c.dataFlags(), DataFlag::Owned|DataFlag::Mutable); CORRADE_VERIFY(!c.isCompressed()); CORRADE_COMPARE(c.storage().alignment(), 1); CORRADE_COMPARE(c.format(), PixelFormat::RGBA32F); @@ -384,6 +699,7 @@ void ImageDataTest::constructMoveImplementationSpecific() { CORRADE_COMPARE(a.data(), nullptr); CORRADE_COMPARE(a.size(), Vector2i{}); + CORRADE_COMPARE(b.dataFlags(), DataFlag::Owned|DataFlag::Mutable); CORRADE_VERIFY(!b.isCompressed()); CORRADE_COMPARE(b.storage().alignment(), 1); CORRADE_COMPARE(b.format(), pixelFormatWrap(GL::PixelFormat::RGB)); @@ -402,6 +718,7 @@ void ImageDataTest::constructMoveImplementationSpecific() { CORRADE_COMPARE(b.data(), data2); CORRADE_COMPARE(b.size(), Vector2i(2, 6)); + CORRADE_COMPARE(c.dataFlags(), DataFlag::Owned|DataFlag::Mutable); CORRADE_VERIFY(!c.isCompressed()); CORRADE_COMPARE(c.storage().alignment(), 1); CORRADE_COMPARE(c.format(), pixelFormatWrap(GL::PixelFormat::RGB)); @@ -424,6 +741,7 @@ void ImageDataTest::constructMoveCompressedGeneric() { CORRADE_COMPARE(a.data(), nullptr); CORRADE_COMPARE(a.size(), Vector2i{}); + CORRADE_COMPARE(b.dataFlags(), DataFlag::Owned|DataFlag::Mutable); CORRADE_VERIFY(b.isCompressed()); CORRADE_COMPARE(b.compressedStorage().compressedBlockSize(), Vector3i{4}); CORRADE_COMPARE(b.compressedFormat(), CompressedPixelFormat::Bc3RGBAUnorm); @@ -439,6 +757,7 @@ void ImageDataTest::constructMoveCompressedGeneric() { CORRADE_COMPARE(b.data(), data2); CORRADE_COMPARE(b.size(), (Vector2i{8, 4})); + CORRADE_COMPARE(c.dataFlags(), DataFlag::Owned|DataFlag::Mutable); CORRADE_VERIFY(c.isCompressed()); CORRADE_COMPARE(c.compressedStorage().compressedBlockSize(), Vector3i{4}); CORRADE_COMPARE(c.compressedFormat(), CompressedPixelFormat::Bc3RGBAUnorm); @@ -459,6 +778,7 @@ void ImageDataTest::constructMoveCompressedImplementationSpecific() { CORRADE_COMPARE(a.data(), nullptr); CORRADE_COMPARE(a.size(), Vector2i{}); + CORRADE_COMPARE(b.dataFlags(), DataFlag::Owned|DataFlag::Mutable); CORRADE_VERIFY(b.isCompressed()); CORRADE_COMPARE(b.compressedStorage().compressedBlockSize(), Vector3i{4}); CORRADE_COMPARE(b.compressedFormat(), compressedPixelFormatWrap(GL::CompressedPixelFormat::RGBS3tcDxt1)); @@ -474,6 +794,7 @@ void ImageDataTest::constructMoveCompressedImplementationSpecific() { CORRADE_COMPARE(b.data(), data2); CORRADE_COMPARE(b.size(), (Vector2i{8, 4})); + CORRADE_COMPARE(c.dataFlags(), DataFlag::Owned|DataFlag::Mutable); CORRADE_VERIFY(c.isCompressed()); CORRADE_COMPARE(c.compressedStorage().compressedBlockSize(), Vector3i{4}); CORRADE_COMPARE(c.compressedFormat(), compressedPixelFormatWrap(GL::CompressedPixelFormat::RGBS3tcDxt1)); @@ -493,6 +814,7 @@ void ImageDataTest::constructMoveAttachState() { CORRADE_COMPARE(a.data(), nullptr); CORRADE_COMPARE(a.size(), Vector2i{}); + CORRADE_COMPARE(b.dataFlags(), DataFlag::Owned|DataFlag::Mutable); CORRADE_VERIFY(!b.isCompressed()); CORRADE_COMPARE(b.storage().alignment(), 1); CORRADE_COMPARE(b.format(), pixelFormatWrap(GL::PixelFormat::RGB)); @@ -515,6 +837,7 @@ void ImageDataTest::constructMoveCompressedAttachState() { CORRADE_COMPARE(a.data(), nullptr); CORRADE_COMPARE(a.size(), Vector2i{}); + CORRADE_COMPARE(b.dataFlags(), DataFlag::Owned|DataFlag::Mutable); CORRADE_VERIFY(b.isCompressed()); CORRADE_COMPARE(b.compressedStorage().compressedBlockSize(), Vector3i{4}); CORRADE_COMPARE(b.compressedFormat(), compressedPixelFormatWrap(GL::CompressedPixelFormat::RGBS3tcDxt1)); @@ -603,6 +926,31 @@ void ImageDataTest::dataRvalue() { CORRADE_COMPARE(released.data(), data); } +void ImageDataTest::mutableAccessNotAllowed() { + const char data[4*4]{}; + ImageData2D a{PixelFormat::RGBA8Unorm, {2, 2}, DataFlags{}, data}; + + std::ostringstream out; + Error redirectError{&out}; + a.mutableData(); + a.mutablePixels(); + /* Can't do just MutableImageView2D(a) because the compiler then treats it + as a function. Can't do MutableImageView2D{a} because that doesn't work + on old Clang. So it's this mess, then. Sigh. */ + auto b = MutableImageView2D(a); + static_cast(b); + auto c = MutableCompressedImageView2D(a); + static_cast(c); + /* a.mutablePixels() calls non-templated mutablePixels(), so assume + there it will blow up correctly as well (can't test because it asserts + inside arrayCast() due to zero stride) */ + CORRADE_COMPARE(out.str(), + "Trade::ImageData::mutableData(): the image is not mutable\n" + "Trade::ImageData::mutablePixels(): the image is not mutable\n" + "Trade::ImageData: the image is not mutable\n" + "Trade::ImageData: the image is not mutable\n"); +} + void ImageDataTest::dataProperties() { ImageData3D image{ PixelStorage{} @@ -646,12 +994,12 @@ void ImageDataTest::pixels1D() { /* Full test is in ImageTest, this is just a sanity check */ { - Containers::StridedArrayView1D pixels = image.pixels(); + Containers::StridedArrayView1D pixels = image.mutablePixels(); CORRADE_COMPARE(pixels.size(), 2); CORRADE_COMPARE(pixels.stride(), 3); CORRADE_COMPARE(pixels.data(), image.data() + 3*3); } { - Containers::StridedArrayView1D pixels = Containers::arrayCast<1, const Color3ub>(cimage.pixels()); + Containers::StridedArrayView1D pixels = cimage.pixels(); CORRADE_COMPARE(pixels.size(), 2); CORRADE_COMPARE(pixels.stride(), 3); CORRADE_COMPARE(pixels.data(), cimage.data() + 3*3); @@ -671,12 +1019,12 @@ void ImageDataTest::pixels2D() { /* Full test is in ImageTest, this is just a sanity check */ { - Containers::StridedArrayView2D pixels = image.pixels(); + Containers::StridedArrayView2D pixels = image.mutablePixels(); CORRADE_COMPARE(pixels.size(), (Containers::StridedArrayView2D::Size{4, 2})); CORRADE_COMPARE(pixels.stride(), (Containers::StridedArrayView2D::Stride{20, 3})); CORRADE_COMPARE(pixels.data(), image.data() + 2*20 + 3*3); } { - Containers::StridedArrayView2D pixels = Containers::arrayCast<2, const Color3ub>(cimage.pixels()); + Containers::StridedArrayView2D pixels = cimage.pixels(); CORRADE_COMPARE(pixels.size(), (Containers::StridedArrayView2D::Size{4, 2})); CORRADE_COMPARE(pixels.stride(), (Containers::StridedArrayView2D::Stride{20, 3})); CORRADE_COMPARE(pixels.data(), cimage.data() + 2*20 + 3*3); @@ -697,7 +1045,7 @@ void ImageDataTest::pixels3D() { /* Full test is in ImageTest, this is just a sanity check */ { - Containers::StridedArrayView3D pixels = image.pixels(); + Containers::StridedArrayView3D pixels = image.mutablePixels(); CORRADE_COMPARE(pixels.size(), (Containers::StridedArrayView3D::Size{3, 4, 2})); CORRADE_COMPARE(pixels.stride(), (Containers::StridedArrayView3D::Stride{140, 20, 3})); CORRADE_COMPARE(pixels.data(), image.data() + 140 + 2*20 + 3*3);