From e5175583a7b8aff92d89600ed7bb21cfda81c3f6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Vondru=C5=A1?= Date: Tue, 30 Apr 2019 00:35:50 +0200 Subject: [PATCH] Added Image::pixels(), ImageView::pixels() and Trade::ImageData::pixels(). Hard to explain in a tweet, err, commit message. Blog post incoming. --- doc/changelog.dox | 4 + doc/snippets/Magnum.cpp | 16 +++ src/Magnum/CMakeLists.txt | 3 + src/Magnum/Image.cpp | 9 ++ src/Magnum/Image.h | 41 ++++++- src/Magnum/ImageView.cpp | 5 + src/Magnum/ImageView.h | 17 ++- src/Magnum/Implementation/ImagePixelView.h | 66 ++++++++++ src/Magnum/Test/ImageTest.cpp | 134 ++++++++++++++++++++- src/Magnum/Test/ImageViewTest.cpp | 63 +++++++++- src/Magnum/Trade/ImageData.cpp | 13 ++ src/Magnum/Trade/ImageData.h | 16 ++- src/Magnum/Trade/Test/ImageDataTest.cpp | 99 ++++++++++++++- 13 files changed, 478 insertions(+), 8 deletions(-) create mode 100644 src/Magnum/Implementation/ImagePixelView.h diff --git a/doc/changelog.dox b/doc/changelog.dox index ba790c792..d7e881152 100644 --- a/doc/changelog.dox +++ b/doc/changelog.dox @@ -40,6 +40,10 @@ See also: @subsection changelog-latest-new New features +- New @ref Image::pixels(), @ref ImageView::pixels() and + @ref Trade::ImageData::pixels() accessors for convenient direct access to + pixel data of any image + @subsubsection changelog-latest-new-debugtools DebugTools library - New @ref DebugTools::screenshot() function for convenient saving of diff --git a/doc/snippets/Magnum.cpp b/doc/snippets/Magnum.cpp index 9def1837a..b8d87ccf4 100644 --- a/doc/snippets/Magnum.cpp +++ b/doc/snippets/Magnum.cpp @@ -23,6 +23,9 @@ DEALINGS IN THE SOFTWARE. */ +#include + +#include "Magnum/Math/Color.h" #include "Magnum/Image.h" #include "Magnum/PixelFormat.h" #ifdef MAGNUM_TARGET_GL @@ -60,6 +63,19 @@ class MeshResourceLoader: public AbstractResourceLoader { int main() { +{ +std::nullptr_t data{}; +/* [Image-pixels] */ +Image2D image{PixelFormat::RGB8Unorm, {128, 128}, data}; + +Containers::StridedArrayView2D pixels = + Containers::arrayCast<2, Color3ub>(image.pixels()); +for(auto row: pixels.slice({48, 48}, {80, 80})) { + for(Color3ub& pixel: row) pixel *= 1.1f; +} +/* [Image-pixels] */ +} + { char data[3]; /* [ImageView-usage] */ diff --git a/src/Magnum/CMakeLists.txt b/src/Magnum/CMakeLists.txt index 0862e6bee..1c2e2c528 100644 --- a/src/Magnum/CMakeLists.txt +++ b/src/Magnum/CMakeLists.txt @@ -63,6 +63,9 @@ set(Magnum_HEADERS Types.h visibility.h) +set(Magnum_PRIVATE_HEADERS + Implementation/ImagePixelView.h) + # Files shared between main library and math unit test library set(MagnumMath_SRCS Math/Angle.cpp diff --git a/src/Magnum/Image.cpp b/src/Magnum/Image.cpp index 5a2c063bb..0295d9d58 100644 --- a/src/Magnum/Image.cpp +++ b/src/Magnum/Image.cpp @@ -26,6 +26,7 @@ #include "Image.h" #include "Magnum/PixelFormat.h" +#include "Magnum/Implementation/ImagePixelView.h" namespace Magnum { @@ -43,6 +44,14 @@ template Image::Image(const PixelStorage sto template Image::Image(const PixelStorage storage, const PixelFormat format, const UnsignedInt formatExtra, const UnsignedInt pixelSize) noexcept: _storage{storage}, _format{format}, _formatExtra{formatExtra}, _pixelSize{pixelSize}, _data{} {} +template Containers::StridedArrayView Image::pixels() { + return Implementation::imagePixelView(*this); +} + +template Containers::StridedArrayView Image::pixels() const { + return Implementation::imagePixelView(*this); +} + template CompressedImage::CompressedImage(const CompressedPixelStorage storage, const CompressedPixelFormat format, const VectorTypeFor& size, Containers::Array&& data) noexcept: _storage{storage}, _format{format}, _size{size}, _data{std::move(data)} {} template CompressedImage::CompressedImage(const CompressedPixelStorage storage, const UnsignedInt format, const VectorTypeFor& size, Containers::Array&& data) noexcept: CompressedImage{storage, compressedPixelFormatWrap(format), size, std::move(data)} {} diff --git a/src/Magnum/Image.h b/src/Magnum/Image.h index 0e24efe7d..3e44af0a0 100644 --- a/src/Magnum/Image.h +++ b/src/Magnum/Image.h @@ -69,6 +69,36 @@ As with @ref ImageView, this class supports extra storage parameters and implementation-specific pixel format specification. See the @ref ImageView documentation for more information. +@section Image-pixel-views Obtaining a view on pixel data + +While the raw image data are available through @ref data(), for correct pixel +addressing it's required to incorporate all @ref storage() parameters such as +row alignment, row length, skip offset and such. This is very error-prone to +do by hand even with the help of @ref dataProperties(). + +The @ref pixels() accessor returns a multi-dimensional +@ref Corrade::Containers::StridedArrayView describing layout of the data and +providing easy access to particular rows, pixels and pixel channels. The +returned view always has one dimension more than the actual image, with the +last dimension being bytes in a particular pixels. The second-to-last dimension +is always pixels in a row, the one before (if the image is at least 2D) is rows +in an image, and for 3D images the very first dimension describes image slices. +Desired usage is casting to a concrete type based on @ref format() first using +@ref Corrade::Containers::arrayCast() (which also flattens the last dimension) +and then operating on the concretely typed array. The following example +brightens the center 32x32 area of an image: + +@snippet Magnum.cpp Image-pixels + +@attention Note that the correctness of the cast is can't be generally checked + apart from expecting that the last dimension size is equal to the new type + size. It's the user responsibility to ensure the type matches given + @ref format(). + +This operation is available also on @ref ImageView and non-compressed +@ref Trade::ImageData. See @ref Corrade::Containers::StridedArrayView docs for +more information about transforming, slicing and converting the view further. + @see @ref Image1D, @ref Image2D, @ref Image3D, @ref CompressedImage */ template class Image { @@ -324,7 +354,7 @@ template class Image { /** * @brief Raw data * - * @see @ref release() + * @see @ref release(), @ref pixels() */ Containers::ArrayView data() & { return _data; } Containers::ArrayView data() && = delete; /**< @overload */ @@ -343,6 +373,15 @@ template class Image { return reinterpret_cast(_data.data()); } + /** + * @brief View on pixel data + * + * Provides direct and easy-to-use access to image pixels. See + * @ref Image-pixel-views for more information. + */ + Containers::StridedArrayView pixels(); + Containers::StridedArrayView pixels() const; /**< @overload */ + /** * @brief Release data storage * diff --git a/src/Magnum/ImageView.cpp b/src/Magnum/ImageView.cpp index 9a4215310..3b33f8894 100644 --- a/src/Magnum/ImageView.cpp +++ b/src/Magnum/ImageView.cpp @@ -26,6 +26,7 @@ #include "ImageView.h" #include "Magnum/PixelFormat.h" +#include "Magnum/Implementation/ImagePixelView.h" namespace Magnum { @@ -48,6 +49,10 @@ template void ImageView::setData(const Conta _data = {reinterpret_cast(data.data()), data.size()}; } +template Containers::StridedArrayView ImageView::pixels() const { + return Implementation::imagePixelView(*this); +} + 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()} {} template CompressedImageView::CompressedImageView(const CompressedPixelStorage storage, const CompressedPixelFormat format, const VectorTypeFor& size) noexcept: _storage{storage}, _format{format}, _size{size} {} diff --git a/src/Magnum/ImageView.h b/src/Magnum/ImageView.h index 99316fd10..bac1ea9a8 100644 --- a/src/Magnum/ImageView.h +++ b/src/Magnum/ImageView.h @@ -113,7 +113,8 @@ Metal-specific format identifier: @snippet Magnum.cpp ImageView-usage-metal -@see @ref ImageView1D, @ref ImageView2D, @ref ImageView3D +@see @ref ImageView1D, @ref ImageView2D, @ref ImageView3D, + @ref Image-pixel-views */ template class ImageView { public: @@ -365,7 +366,11 @@ template class ImageView { return Implementation::imageDataProperties(*this); } - /** @brief Image data */ + /** + * @brief Image data + * + * @see @ref pixels() + */ Containers::ArrayView data() const { return _data; } /** @overload */ @@ -381,6 +386,14 @@ template class ImageView { */ void setData(Containers::ArrayView data); + /** + * @brief View on pixel data + * + * Provides direct and easy-to-use access to image pixels. See + * @ref Image-pixel-views for more information. + */ + Containers::StridedArrayView pixels() const; + private: PixelStorage _storage; PixelFormat _format; diff --git a/src/Magnum/Implementation/ImagePixelView.h b/src/Magnum/Implementation/ImagePixelView.h new file mode 100644 index 000000000..31d8c9951 --- /dev/null +++ b/src/Magnum/Implementation/ImagePixelView.h @@ -0,0 +1,66 @@ +#ifndef Magnum_Implementation_ImagePixelView_h +#define Magnum_Implementation_ImagePixelView_h +/* + This file is part of Magnum. + + Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019 + Vladimír Vondruš + + 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. +*/ + +#include +#include + +#include "Magnum/Magnum.h" +#include "Magnum/DimensionTraits.h" + +namespace Magnum { namespace Implementation { + +template Containers::StridedArrayView imagePixelView(Image& image) { + const std::pair, VectorTypeFor> properties = image.dataProperties(); + + /* Size in the last dimension is byte size of the pixel, the remaining + dimensions are reverted (first images, then rows, then pixels, last + pixel bytes) */ + Containers::StridedDimensions size{Containers::NoInit}; + size[dimensions] = image.pixelSize(); + for(UnsignedInt i = dimensions; i != 0; --i) + size[i - 1] = image.size()[dimensions - i]; + + /* Stride in the last dimension is 1, stride in the second-to-last + dimension ix pixel byte size. The remaining imensions are reverted + (first image size, then row size, then pixel size, last 1). The + data properties include pixel size in row size, so we have to take it + out from the cumulative product. */ + Containers::StridedDimensions stride{Containers::NoInit}; + stride[dimensions] = 1; + stride[dimensions - 1] = 1; + for(UnsignedInt i = dimensions - 1; i != 0; --i) + stride[i - 1] = stride[i]*properties.second[dimensions - i - 1]; + stride[dimensions - 1] = image.pixelSize(); + + 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}; +} + +}} + +#endif diff --git a/src/Magnum/Test/ImageTest.cpp b/src/Magnum/Test/ImageTest.cpp index 13cee7610..a767fb444 100644 --- a/src/Magnum/Test/ImageTest.cpp +++ b/src/Magnum/Test/ImageTest.cpp @@ -24,10 +24,12 @@ */ #include +#include #include #include #include "Magnum/Image.h" +#include "Magnum/Math/Color.h" #include "Magnum/PixelFormat.h" namespace Magnum { namespace Test { namespace { @@ -67,6 +69,10 @@ struct ImageTest: TestSuite::Tester { void release(); void releaseCompressed(); + + void pixels1D(); + void pixels2D(); + void pixels3D(); }; ImageTest::ImageTest() { @@ -101,7 +107,11 @@ ImageTest::ImageTest() { &ImageTest::dataPropertiesCompressed, &ImageTest::release, - &ImageTest::releaseCompressed}); + &ImageTest::releaseCompressed, + + &ImageTest::pixels1D, + &ImageTest::pixels2D, + &ImageTest::pixels3D}); } namespace GL { @@ -660,6 +670,128 @@ void ImageTest::releaseCompressed() { CORRADE_COMPARE(a.size(), Vector2i()); } +void ImageTest::pixels1D() { + Image1D image{ + PixelStorage{} + .setAlignment(1) /** @todo alignment 4 expects 17 bytes. what */ + .setSkip({3, 0, 0}), + PixelFormat::RGB8Unorm, 2, + Containers::Array{Containers::InPlaceInit, { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 4, 5, 6, 7, 8 + }}}; + const Image1D& cimage = image; + + { + Containers::StridedArrayView1D pixels = Containers::arrayCast<1, Color3ub>(image.pixels()); + CORRADE_COMPARE(pixels.size(), 2); + CORRADE_COMPARE(pixels.stride(), 3); + CORRADE_COMPARE(pixels.data(), image.data() + 3*3); + CORRADE_COMPARE(pixels[0], (Color3ub{3, 4, 5})); + CORRADE_COMPARE(pixels[1], (Color3ub{6, 7, 8})); + } { + Containers::StridedArrayView1D pixels = Containers::arrayCast<1, const Color3ub>(cimage.pixels()); + CORRADE_COMPARE(pixels.size(), 2); + CORRADE_COMPARE(pixels.stride(), 3); + CORRADE_COMPARE(pixels.data(), cimage.data() + 3*3); + CORRADE_COMPARE(pixels[0], (Color3ub{3, 4, 5})); + CORRADE_COMPARE(pixels[1], (Color3ub{6, 7, 8})); + } +} + +void ImageTest::pixels2D() { + Image2D image{ + PixelStorage{} + .setAlignment(4) + .setSkip({3, 2, 0}) + .setRowLength(6), + PixelFormat::RGB8Unorm, {2, 4}, + Containers::Array{Containers::InPlaceInit, { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 4, 5, 6, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 3, 4, 5, 6, 7, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 4, 5, 6, 7, 8, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 5, 6, 7, 8, 9, 0, 0, 0, 0, 0, + }}}; + const Image2D& cimage = image; + + { + Containers::StridedArrayView2D pixels = Containers::arrayCast<2, Color3ub>(image.pixels()); + 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); + CORRADE_COMPARE(pixels[3][0], (Color3ub{4, 5, 6})); + CORRADE_COMPARE(pixels[3][1], (Color3ub{7, 8, 9})); + } { + Containers::StridedArrayView2D pixels = Containers::arrayCast<2, const Color3ub>(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); + CORRADE_COMPARE(pixels[3][0], (Color3ub{4, 5, 6})); + CORRADE_COMPARE(pixels[3][1], (Color3ub{7, 8, 9})); + } +} + +void ImageTest::pixels3D() { + Image3D image{ + PixelStorage{} + .setAlignment(4) + .setSkip({3, 2, 1}) + .setRowLength(6) + .setImageHeight(7), + PixelFormat::RGB8Unorm, {2, 4, 3}, + Containers::Array{Containers::InPlaceInit, { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 4, 5, 6, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 3, 4, 5, 6, 7, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 4, 5, 6, 7, 8, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 5, 6, 7, 8, 9, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 6, 5, 4, 3, 2, 1, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 7, 6, 5, 4, 3, 2, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 8, 7, 6, 5, 4, 3, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 8, 7, 6, 5, 4, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 5, 6, 1, 2, 3, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 5, 6, 7, 2, 3, 4, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 6, 7, 8, 3, 4, 5, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 7, 8, 9, 4, 5, 6, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + }}}; + const Image3D& cimage = image; + + { + Containers::StridedArrayView3D pixels = Containers::arrayCast<3, Color3ub>(image.pixels()); + 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); + CORRADE_COMPARE(pixels[1][3][0], (Color3ub{9, 8, 7})); + CORRADE_COMPARE(pixels[1][3][1], (Color3ub{6, 5, 4})); + } { + Containers::StridedArrayView3D pixels = Containers::arrayCast<3, const Color3ub>(cimage.pixels()); + CORRADE_COMPARE(pixels.size(), (Containers::StridedArrayView3D::Size{3, 4, 2})); + CORRADE_COMPARE(pixels.stride(), (Containers::StridedArrayView3D::Stride{140, 20, 3})); + CORRADE_COMPARE(pixels.data(), cimage.data() + 140 + 2*20 + 3*3); + CORRADE_COMPARE(pixels[1][3][0], (Color3ub{9, 8, 7})); + CORRADE_COMPARE(pixels[1][3][1], (Color3ub{6, 5, 4})); + } +} + }}} CORRADE_TEST_MAIN(Magnum::Test::ImageTest) diff --git a/src/Magnum/Test/ImageViewTest.cpp b/src/Magnum/Test/ImageViewTest.cpp index 983893781..caed82b9c 100644 --- a/src/Magnum/Test/ImageViewTest.cpp +++ b/src/Magnum/Test/ImageViewTest.cpp @@ -24,9 +24,11 @@ */ #include +#include #include #include +#include "Magnum/Math/Color.h" #include "Magnum/ImageView.h" #include "Magnum/PixelFormat.h" @@ -57,6 +59,10 @@ struct ImageViewTest: TestSuite::Tester { void setDataInvalidSize(); void setDataCompressedInvalidSize(); + + void pixels1D(); + void pixels2D(); + void pixels3D(); }; ImageViewTest::ImageViewTest() { @@ -81,7 +87,11 @@ ImageViewTest::ImageViewTest() { &ImageViewTest::setDataCompressed, &ImageViewTest::setDataInvalidSize, - &ImageViewTest::setDataCompressedInvalidSize}); + &ImageViewTest::setDataCompressedInvalidSize, + + &ImageViewTest::pixels1D, + &ImageViewTest::pixels2D, + &ImageViewTest::pixels3D}); } namespace GL { @@ -581,6 +591,57 @@ void ImageViewTest::setDataCompressedInvalidSize() { } } +void ImageViewTest::pixels1D() { + ImageView1D image{ + PixelStorage{} + .setAlignment(1) /** @todo alignment 4 expects 17 bytes. what */ + .setSkip({3, 0, 0}), + PixelFormat::RGB8Unorm, 2, + {nullptr, 15}}; + + /* Full test is in ImageTest, this is just a sanity check */ + + Containers::StridedArrayView1D pixels = Containers::arrayCast<1, const Color3ub>(image.pixels()); + CORRADE_COMPARE(pixels.size(), 2); + CORRADE_COMPARE(pixels.stride(), 3); + CORRADE_COMPARE(pixels.data(), image.data() + 3*3); +} + +void ImageViewTest::pixels2D() { + ImageView2D image{ + PixelStorage{} + .setAlignment(4) + .setSkip({3, 2, 0}) + .setRowLength(6), + PixelFormat::RGB8Unorm, {2, 4}, + {nullptr, 120}}; + + /* Full test is in ImageTest, this is just a sanity check */ + + Containers::StridedArrayView2D pixels = Containers::arrayCast<2, const Color3ub>(image.pixels()); + 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); +} + +void ImageViewTest::pixels3D() { + ImageView3D image{ + PixelStorage{} + .setAlignment(4) + .setSkip({3, 2, 1}) + .setRowLength(6) + .setImageHeight(7), + PixelFormat::RGB8Unorm, {2, 4, 3}, + {nullptr, 560}}; + + /* Full test is in ImageTest, this is just a sanity check */ + + Containers::StridedArrayView3D pixels = Containers::arrayCast<3, const Color3ub>(image.pixels()); + 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); +} + }}} CORRADE_TEST_MAIN(Magnum::Test::ImageViewTest) diff --git a/src/Magnum/Trade/ImageData.cpp b/src/Magnum/Trade/ImageData.cpp index a96c448cb..5e0a94c6c 100644 --- a/src/Magnum/Trade/ImageData.cpp +++ b/src/Magnum/Trade/ImageData.cpp @@ -25,7 +25,10 @@ #include "ImageData.h" +#include + #include "Magnum/PixelFormat.h" +#include "Magnum/Implementation/ImagePixelView.h" namespace Magnum { namespace Trade { @@ -80,6 +83,16 @@ template std::pair(*this); } +template Containers::StridedArrayView ImageData::pixels() { + CORRADE_ASSERT(!_compressed, "Trade::ImageData::pixels(): the image is compressed", {}); + return Implementation::imagePixelView(*this); +} + +template Containers::StridedArrayView ImageData::pixels() const { + CORRADE_ASSERT(!_compressed, "Trade::ImageData::pixels(): the image is compressed", {}); + return Implementation::imagePixelView(*this); +} + template ImageData::operator ImageView() const { CORRADE_ASSERT(!_compressed, "Trade::ImageData::type(): the image is compressed", (ImageView{_storage, _format, _formatExtra, _pixelSize, _size})); diff --git a/src/Magnum/Trade/ImageData.h b/src/Magnum/Trade/ImageData.h index e5122abe4..fdb5dbbbb 100644 --- a/src/Magnum/Trade/ImageData.h +++ b/src/Magnum/Trade/ImageData.h @@ -74,7 +74,8 @@ compressed properties through @ref compressedStorage() and @snippet MagnumTrade.cpp ImageData-usage -@see @ref ImageData1D, @ref ImageData2D, @ref ImageData3D +@see @ref ImageData1D, @ref ImageData2D, @ref ImageData3D, + @ref Image-pixel-views */ template class ImageData { public: @@ -330,7 +331,7 @@ template class ImageData { /** * @brief Raw data * - * @see @ref release() + * @see @ref release(), @ref pixels() */ Containers::ArrayView data() & { return _data; } Containers::ArrayView data() && = delete; /**< @overload */ @@ -349,6 +350,17 @@ template class ImageData { return reinterpret_cast(_data.data()); } + /** + * @brief View on pixel data + * + * Provides direct and easy-to-use access to image pixels. Expects that + * the image is not compressed. See @ref Image-pixel-views for more + * information. + * @see @ref isCompressed() + */ + Containers::StridedArrayView pixels(); + Containers::StridedArrayView pixels() const; /**< @overload */ + /** * @brief Release data storage * diff --git a/src/Magnum/Trade/Test/ImageDataTest.cpp b/src/Magnum/Trade/Test/ImageDataTest.cpp index fa9ebf45b..6a5aae9be 100644 --- a/src/Magnum/Trade/Test/ImageDataTest.cpp +++ b/src/Magnum/Trade/Test/ImageDataTest.cpp @@ -24,9 +24,11 @@ */ #include +#include #include #include +#include "Magnum/Math/Color.h" #include "Magnum/PixelFormat.h" #include "Magnum/Trade/ImageData.h" @@ -63,6 +65,11 @@ struct ImageDataTest: TestSuite::Tester { void release(); void releaseCompressed(); + + void pixels1D(); + void pixels2D(); + void pixels3D(); + void pixelsCompressed(); }; ImageDataTest::ImageDataTest() { @@ -93,7 +100,12 @@ ImageDataTest::ImageDataTest() { &ImageDataTest::dataProperties, &ImageDataTest::release, - &ImageDataTest::releaseCompressed}); + &ImageDataTest::releaseCompressed, + + &ImageDataTest::pixels1D, + &ImageDataTest::pixels2D, + &ImageDataTest::pixels3D, + &ImageDataTest::pixelsCompressed}); } namespace GL { @@ -579,6 +591,91 @@ void ImageDataTest::releaseCompressed() { CORRADE_COMPARE(a.size(), Vector2i()); } +void ImageDataTest::pixels1D() { + ImageData1D image{ + PixelStorage{} + .setAlignment(1) /** @todo alignment 4 expects 17 bytes. what */ + .setSkip({3, 0, 0}), + PixelFormat::RGB8Unorm, 2, + Containers::Array{15}}; + const ImageData1D& cimage = image; + + /* Full test is in ImageTest, this is just a sanity check */ + + { + Containers::StridedArrayView1D pixels = Containers::arrayCast<1, Color3ub>(image.pixels()); + 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()); + CORRADE_COMPARE(pixels.size(), 2); + CORRADE_COMPARE(pixels.stride(), 3); + CORRADE_COMPARE(pixels.data(), cimage.data() + 3*3); + } +} + +void ImageDataTest::pixels2D() { + ImageData2D image{ + PixelStorage{} + .setAlignment(4) + .setSkip({3, 2, 0}) + .setRowLength(6), + PixelFormat::RGB8Unorm, {2, 4}, + Containers::Array{120}}; + const ImageData2D& cimage = image; + + /* Full test is in ImageTest, this is just a sanity check */ + + { + Containers::StridedArrayView2D pixels = Containers::arrayCast<2, Color3ub>(image.pixels()); + 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()); + 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); + } +} + +void ImageDataTest::pixels3D() { + ImageData3D image{ + PixelStorage{} + .setAlignment(4) + .setSkip({3, 2, 1}) + .setRowLength(6) + .setImageHeight(7), + PixelFormat::RGB8Unorm, {2, 4, 3}, + Containers::Array{560}}; + const ImageData3D& cimage = image; + + /* Full test is in ImageTest, this is just a sanity check */ + + { + Containers::StridedArrayView3D pixels = Containers::arrayCast<3, Color3ub>(image.pixels()); + 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); + } { + Containers::StridedArrayView3D pixels = Containers::arrayCast<3, const Color3ub>(cimage.pixels()); + CORRADE_COMPARE(pixels.size(), (Containers::StridedArrayView3D::Size{3, 4, 2})); + CORRADE_COMPARE(pixels.stride(), (Containers::StridedArrayView3D::Stride{140, 20, 3})); + CORRADE_COMPARE(pixels.data(), cimage.data() + 140 + 2*20 + 3*3); + } +} + +void ImageDataTest::pixelsCompressed() { + Trade::ImageData2D a{CompressedPixelFormat::Bc1RGBAUnorm, {4, 4}, Containers::Array{8}}; + + std::ostringstream out; + Error redirectError{&out}; + + a.pixels(); + CORRADE_COMPARE(out.str(), "Trade::ImageData::pixels(): the image is compressed\n"); +} + }}}} CORRADE_TEST_MAIN(Magnum::Trade::Test::ImageDataTest)