Browse Source

Added Image::pixels(), ImageView::pixels() and Trade::ImageData::pixels().

Hard to explain in a tweet, err, commit message. Blog post incoming.
pull/338/head
Vladimír Vondruš 7 years ago
parent
commit
e5175583a7
  1. 4
      doc/changelog.dox
  2. 16
      doc/snippets/Magnum.cpp
  3. 3
      src/Magnum/CMakeLists.txt
  4. 9
      src/Magnum/Image.cpp
  5. 41
      src/Magnum/Image.h
  6. 5
      src/Magnum/ImageView.cpp
  7. 17
      src/Magnum/ImageView.h
  8. 66
      src/Magnum/Implementation/ImagePixelView.h
  9. 134
      src/Magnum/Test/ImageTest.cpp
  10. 63
      src/Magnum/Test/ImageViewTest.cpp
  11. 13
      src/Magnum/Trade/ImageData.cpp
  12. 16
      src/Magnum/Trade/ImageData.h
  13. 99
      src/Magnum/Trade/Test/ImageDataTest.cpp

4
doc/changelog.dox

@ -40,6 +40,10 @@ See also:
@subsection changelog-latest-new New features @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 @subsubsection changelog-latest-new-debugtools DebugTools library
- New @ref DebugTools::screenshot() function for convenient saving of - New @ref DebugTools::screenshot() function for convenient saving of

16
doc/snippets/Magnum.cpp

@ -23,6 +23,9 @@
DEALINGS IN THE SOFTWARE. DEALINGS IN THE SOFTWARE.
*/ */
#include <Corrade/Containers/StridedArrayView.h>
#include "Magnum/Math/Color.h"
#include "Magnum/Image.h" #include "Magnum/Image.h"
#include "Magnum/PixelFormat.h" #include "Magnum/PixelFormat.h"
#ifdef MAGNUM_TARGET_GL #ifdef MAGNUM_TARGET_GL
@ -60,6 +63,19 @@ class MeshResourceLoader: public AbstractResourceLoader<GL::Mesh> {
int main() { int main() {
{
std::nullptr_t data{};
/* [Image-pixels] */
Image2D image{PixelFormat::RGB8Unorm, {128, 128}, data};
Containers::StridedArrayView2D<Color3ub> 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]; char data[3];
/* [ImageView-usage] */ /* [ImageView-usage] */

3
src/Magnum/CMakeLists.txt

@ -63,6 +63,9 @@ set(Magnum_HEADERS
Types.h Types.h
visibility.h) visibility.h)
set(Magnum_PRIVATE_HEADERS
Implementation/ImagePixelView.h)
# Files shared between main library and math unit test library # Files shared between main library and math unit test library
set(MagnumMath_SRCS set(MagnumMath_SRCS
Math/Angle.cpp Math/Angle.cpp

9
src/Magnum/Image.cpp

@ -26,6 +26,7 @@
#include "Image.h" #include "Image.h"
#include "Magnum/PixelFormat.h" #include "Magnum/PixelFormat.h"
#include "Magnum/Implementation/ImagePixelView.h"
namespace Magnum { namespace Magnum {
@ -43,6 +44,14 @@ template<UnsignedInt dimensions> Image<dimensions>::Image(const PixelStorage sto
template<UnsignedInt dimensions> Image<dimensions>::Image(const PixelStorage storage, const PixelFormat format, const UnsignedInt formatExtra, const UnsignedInt pixelSize) noexcept: _storage{storage}, _format{format}, _formatExtra{formatExtra}, _pixelSize{pixelSize}, _data{} {} template<UnsignedInt dimensions> Image<dimensions>::Image(const PixelStorage storage, const PixelFormat format, const UnsignedInt formatExtra, const UnsignedInt pixelSize) noexcept: _storage{storage}, _format{format}, _formatExtra{formatExtra}, _pixelSize{pixelSize}, _data{} {}
template<UnsignedInt dimensions> Containers::StridedArrayView<dimensions + 1, char> Image<dimensions>::pixels() {
return Implementation::imagePixelView<dimensions, char>(*this);
}
template<UnsignedInt dimensions> Containers::StridedArrayView<dimensions + 1, const char> Image<dimensions>::pixels() const {
return Implementation::imagePixelView<dimensions, const char>(*this);
}
template<UnsignedInt dimensions> CompressedImage<dimensions>::CompressedImage(const CompressedPixelStorage storage, const CompressedPixelFormat format, const VectorTypeFor<dimensions, Int>& size, Containers::Array<char>&& data) noexcept: _storage{storage}, _format{format}, _size{size}, _data{std::move(data)} {} template<UnsignedInt dimensions> CompressedImage<dimensions>::CompressedImage(const CompressedPixelStorage storage, const CompressedPixelFormat format, const VectorTypeFor<dimensions, Int>& size, Containers::Array<char>&& data) noexcept: _storage{storage}, _format{format}, _size{size}, _data{std::move(data)} {}
template<UnsignedInt dimensions> CompressedImage<dimensions>::CompressedImage(const CompressedPixelStorage storage, const UnsignedInt format, const VectorTypeFor<dimensions, Int>& size, Containers::Array<char>&& data) noexcept: CompressedImage{storage, compressedPixelFormatWrap(format), size, std::move(data)} {} template<UnsignedInt dimensions> CompressedImage<dimensions>::CompressedImage(const CompressedPixelStorage storage, const UnsignedInt format, const VectorTypeFor<dimensions, Int>& size, Containers::Array<char>&& data) noexcept: CompressedImage{storage, compressedPixelFormatWrap(format), size, std::move(data)} {}

41
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 implementation-specific pixel format specification. See the @ref ImageView
documentation for more information. 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 @see @ref Image1D, @ref Image2D, @ref Image3D, @ref CompressedImage
*/ */
template<UnsignedInt dimensions> class Image { template<UnsignedInt dimensions> class Image {
@ -324,7 +354,7 @@ template<UnsignedInt dimensions> class Image {
/** /**
* @brief Raw data * @brief Raw data
* *
* @see @ref release() * @see @ref release(), @ref pixels()
*/ */
Containers::ArrayView<char> data() & { return _data; } Containers::ArrayView<char> data() & { return _data; }
Containers::ArrayView<char> data() && = delete; /**< @overload */ Containers::ArrayView<char> data() && = delete; /**< @overload */
@ -343,6 +373,15 @@ template<UnsignedInt dimensions> class Image {
return reinterpret_cast<const T*>(_data.data()); return reinterpret_cast<const T*>(_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<dimensions + 1, char> pixels();
Containers::StridedArrayView<dimensions + 1, const char> pixels() const; /**< @overload */
/** /**
* @brief Release data storage * @brief Release data storage
* *

5
src/Magnum/ImageView.cpp

@ -26,6 +26,7 @@
#include "ImageView.h" #include "ImageView.h"
#include "Magnum/PixelFormat.h" #include "Magnum/PixelFormat.h"
#include "Magnum/Implementation/ImagePixelView.h"
namespace Magnum { namespace Magnum {
@ -48,6 +49,10 @@ template<UnsignedInt dimensions> void ImageView<dimensions>::setData(const Conta
_data = {reinterpret_cast<const char*>(data.data()), data.size()}; _data = {reinterpret_cast<const char*>(data.data()), data.size()};
} }
template<UnsignedInt dimensions> Containers::StridedArrayView<dimensions + 1, const char> ImageView<dimensions>::pixels() const {
return Implementation::imagePixelView<dimensions, const char>(*this);
}
template<UnsignedInt dimensions> CompressedImageView<dimensions>::CompressedImageView(const CompressedPixelStorage storage, const CompressedPixelFormat format, const VectorTypeFor<dimensions, Int>& size, const Containers::ArrayView<const void> data) noexcept: _storage{storage}, _format{format}, _size{size}, _data{reinterpret_cast<const char*>(data.data()), data.size()} {} template<UnsignedInt dimensions> CompressedImageView<dimensions>::CompressedImageView(const CompressedPixelStorage storage, const CompressedPixelFormat format, const VectorTypeFor<dimensions, Int>& size, const Containers::ArrayView<const void> data) noexcept: _storage{storage}, _format{format}, _size{size}, _data{reinterpret_cast<const char*>(data.data()), data.size()} {}
template<UnsignedInt dimensions> CompressedImageView<dimensions>::CompressedImageView(const CompressedPixelStorage storage, const CompressedPixelFormat format, const VectorTypeFor<dimensions, Int>& size) noexcept: _storage{storage}, _format{format}, _size{size} {} template<UnsignedInt dimensions> CompressedImageView<dimensions>::CompressedImageView(const CompressedPixelStorage storage, const CompressedPixelFormat format, const VectorTypeFor<dimensions, Int>& size) noexcept: _storage{storage}, _format{format}, _size{size} {}

17
src/Magnum/ImageView.h

@ -113,7 +113,8 @@ Metal-specific format identifier:
@snippet Magnum.cpp ImageView-usage-metal @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<UnsignedInt dimensions> class ImageView { template<UnsignedInt dimensions> class ImageView {
public: public:
@ -365,7 +366,11 @@ template<UnsignedInt dimensions> class ImageView {
return Implementation::imageDataProperties<dimensions>(*this); return Implementation::imageDataProperties<dimensions>(*this);
} }
/** @brief Image data */ /**
* @brief Image data
*
* @see @ref pixels()
*/
Containers::ArrayView<const char> data() const { return _data; } Containers::ArrayView<const char> data() const { return _data; }
/** @overload */ /** @overload */
@ -381,6 +386,14 @@ template<UnsignedInt dimensions> class ImageView {
*/ */
void setData(Containers::ArrayView<const void> data); void setData(Containers::ArrayView<const void> 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<dimensions + 1, const char> pixels() const;
private: private:
PixelStorage _storage; PixelStorage _storage;
PixelFormat _format; PixelFormat _format;

66
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š <mosra@centrum.cz>
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 <utility>
#include <Corrade/Containers/StridedArrayView.h>
#include "Magnum/Magnum.h"
#include "Magnum/DimensionTraits.h"
namespace Magnum { namespace Implementation {
template<UnsignedInt dimensions, class T, class Image> Containers::StridedArrayView<dimensions + 1, T> imagePixelView(Image& image) {
const std::pair<VectorTypeFor<dimensions, std::size_t>, VectorTypeFor<dimensions, std::size_t>> 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<dimensions + 1, std::size_t> 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<dimensions + 1, std::ptrdiff_t> 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

134
src/Magnum/Test/ImageTest.cpp

@ -24,10 +24,12 @@
*/ */
#include <sstream> #include <sstream>
#include <Corrade/Containers/StridedArrayView.h>
#include <Corrade/TestSuite/Tester.h> #include <Corrade/TestSuite/Tester.h>
#include <Corrade/Utility/DebugStl.h> #include <Corrade/Utility/DebugStl.h>
#include "Magnum/Image.h" #include "Magnum/Image.h"
#include "Magnum/Math/Color.h"
#include "Magnum/PixelFormat.h" #include "Magnum/PixelFormat.h"
namespace Magnum { namespace Test { namespace { namespace Magnum { namespace Test { namespace {
@ -67,6 +69,10 @@ struct ImageTest: TestSuite::Tester {
void release(); void release();
void releaseCompressed(); void releaseCompressed();
void pixels1D();
void pixels2D();
void pixels3D();
}; };
ImageTest::ImageTest() { ImageTest::ImageTest() {
@ -101,7 +107,11 @@ ImageTest::ImageTest() {
&ImageTest::dataPropertiesCompressed, &ImageTest::dataPropertiesCompressed,
&ImageTest::release, &ImageTest::release,
&ImageTest::releaseCompressed}); &ImageTest::releaseCompressed,
&ImageTest::pixels1D,
&ImageTest::pixels2D,
&ImageTest::pixels3D});
} }
namespace GL { namespace GL {
@ -660,6 +670,128 @@ void ImageTest::releaseCompressed() {
CORRADE_COMPARE(a.size(), Vector2i()); 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<char>{Containers::InPlaceInit, {
0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 4, 5, 6, 7, 8
}}};
const Image1D& cimage = image;
{
Containers::StridedArrayView1D<Color3ub> 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<const Color3ub> 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<char>{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<Color3ub> pixels = Containers::arrayCast<2, Color3ub>(image.pixels());
CORRADE_COMPARE(pixels.size(), (Containers::StridedArrayView2D<Color3ub>::Size{4, 2}));
CORRADE_COMPARE(pixels.stride(), (Containers::StridedArrayView2D<Color3ub>::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<const Color3ub> pixels = Containers::arrayCast<2, const Color3ub>(cimage.pixels());
CORRADE_COMPARE(pixels.size(), (Containers::StridedArrayView2D<const Color3ub>::Size{4, 2}));
CORRADE_COMPARE(pixels.stride(), (Containers::StridedArrayView2D<const Color3ub>::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<char>{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<Color3ub> pixels = Containers::arrayCast<3, Color3ub>(image.pixels());
CORRADE_COMPARE(pixels.size(), (Containers::StridedArrayView3D<Color3ub>::Size{3, 4, 2}));
CORRADE_COMPARE(pixels.stride(), (Containers::StridedArrayView3D<Color3ub>::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<const Color3ub> pixels = Containers::arrayCast<3, const Color3ub>(cimage.pixels());
CORRADE_COMPARE(pixels.size(), (Containers::StridedArrayView3D<const Color3ub>::Size{3, 4, 2}));
CORRADE_COMPARE(pixels.stride(), (Containers::StridedArrayView3D<const Color3ub>::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) CORRADE_TEST_MAIN(Magnum::Test::ImageTest)

63
src/Magnum/Test/ImageViewTest.cpp

@ -24,9 +24,11 @@
*/ */
#include <sstream> #include <sstream>
#include <Corrade/Containers/StridedArrayView.h>
#include <Corrade/TestSuite/Tester.h> #include <Corrade/TestSuite/Tester.h>
#include <Corrade/Utility/DebugStl.h> #include <Corrade/Utility/DebugStl.h>
#include "Magnum/Math/Color.h"
#include "Magnum/ImageView.h" #include "Magnum/ImageView.h"
#include "Magnum/PixelFormat.h" #include "Magnum/PixelFormat.h"
@ -57,6 +59,10 @@ struct ImageViewTest: TestSuite::Tester {
void setDataInvalidSize(); void setDataInvalidSize();
void setDataCompressedInvalidSize(); void setDataCompressedInvalidSize();
void pixels1D();
void pixels2D();
void pixels3D();
}; };
ImageViewTest::ImageViewTest() { ImageViewTest::ImageViewTest() {
@ -81,7 +87,11 @@ ImageViewTest::ImageViewTest() {
&ImageViewTest::setDataCompressed, &ImageViewTest::setDataCompressed,
&ImageViewTest::setDataInvalidSize, &ImageViewTest::setDataInvalidSize,
&ImageViewTest::setDataCompressedInvalidSize}); &ImageViewTest::setDataCompressedInvalidSize,
&ImageViewTest::pixels1D,
&ImageViewTest::pixels2D,
&ImageViewTest::pixels3D});
} }
namespace GL { 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<const Color3ub> 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<const Color3ub> pixels = Containers::arrayCast<2, const Color3ub>(image.pixels());
CORRADE_COMPARE(pixels.size(), (Containers::StridedArrayView2D<const Color3ub>::Size{4, 2}));
CORRADE_COMPARE(pixels.stride(), (Containers::StridedArrayView2D<const Color3ub>::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<const Color3ub> pixels = Containers::arrayCast<3, const Color3ub>(image.pixels());
CORRADE_COMPARE(pixels.size(), (Containers::StridedArrayView3D<const Color3ub>::Size{3, 4, 2}));
CORRADE_COMPARE(pixels.stride(), (Containers::StridedArrayView3D<const Color3ub>::Stride{140, 20, 3}));
CORRADE_COMPARE(pixels.data(), image.data() + 140 + 2*20 + 3*3);
}
}}} }}}
CORRADE_TEST_MAIN(Magnum::Test::ImageViewTest) CORRADE_TEST_MAIN(Magnum::Test::ImageViewTest)

13
src/Magnum/Trade/ImageData.cpp

@ -25,7 +25,10 @@
#include "ImageData.h" #include "ImageData.h"
#include <Corrade/Containers/StridedArrayView.h>
#include "Magnum/PixelFormat.h" #include "Magnum/PixelFormat.h"
#include "Magnum/Implementation/ImagePixelView.h"
namespace Magnum { namespace Trade { namespace Magnum { namespace Trade {
@ -80,6 +83,16 @@ template<UnsignedInt dimensions> std::pair<VectorTypeFor<dimensions, std::size_t
return Implementation::imageDataProperties<dimensions>(*this); return Implementation::imageDataProperties<dimensions>(*this);
} }
template<UnsignedInt dimensions> Containers::StridedArrayView<dimensions + 1, char> ImageData<dimensions>::pixels() {
CORRADE_ASSERT(!_compressed, "Trade::ImageData::pixels(): the image is compressed", {});
return Implementation::imagePixelView<dimensions, char>(*this);
}
template<UnsignedInt dimensions> Containers::StridedArrayView<dimensions + 1, const char> ImageData<dimensions>::pixels() const {
CORRADE_ASSERT(!_compressed, "Trade::ImageData::pixels(): the image is compressed", {});
return Implementation::imagePixelView<dimensions, const char>(*this);
}
template<UnsignedInt dimensions> ImageData<dimensions>::operator ImageView<dimensions>() const template<UnsignedInt dimensions> ImageData<dimensions>::operator ImageView<dimensions>() const
{ {
CORRADE_ASSERT(!_compressed, "Trade::ImageData::type(): the image is compressed", (ImageView<dimensions>{_storage, _format, _formatExtra, _pixelSize, _size})); CORRADE_ASSERT(!_compressed, "Trade::ImageData::type(): the image is compressed", (ImageView<dimensions>{_storage, _format, _formatExtra, _pixelSize, _size}));

16
src/Magnum/Trade/ImageData.h

@ -74,7 +74,8 @@ compressed properties through @ref compressedStorage() and
@snippet MagnumTrade.cpp ImageData-usage @snippet MagnumTrade.cpp ImageData-usage
@see @ref ImageData1D, @ref ImageData2D, @ref ImageData3D @see @ref ImageData1D, @ref ImageData2D, @ref ImageData3D,
@ref Image-pixel-views
*/ */
template<UnsignedInt dimensions> class ImageData { template<UnsignedInt dimensions> class ImageData {
public: public:
@ -330,7 +331,7 @@ template<UnsignedInt dimensions> class ImageData {
/** /**
* @brief Raw data * @brief Raw data
* *
* @see @ref release() * @see @ref release(), @ref pixels()
*/ */
Containers::ArrayView<char> data() & { return _data; } Containers::ArrayView<char> data() & { return _data; }
Containers::ArrayView<char> data() && = delete; /**< @overload */ Containers::ArrayView<char> data() && = delete; /**< @overload */
@ -349,6 +350,17 @@ template<UnsignedInt dimensions> class ImageData {
return reinterpret_cast<const T*>(_data.data()); return reinterpret_cast<const T*>(_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<dimensions + 1, char> pixels();
Containers::StridedArrayView<dimensions + 1, const char> pixels() const; /**< @overload */
/** /**
* @brief Release data storage * @brief Release data storage
* *

99
src/Magnum/Trade/Test/ImageDataTest.cpp

@ -24,9 +24,11 @@
*/ */
#include <sstream> #include <sstream>
#include <Corrade/Containers/StridedArrayView.h>
#include <Corrade/TestSuite/Tester.h> #include <Corrade/TestSuite/Tester.h>
#include <Corrade/Utility/DebugStl.h> #include <Corrade/Utility/DebugStl.h>
#include "Magnum/Math/Color.h"
#include "Magnum/PixelFormat.h" #include "Magnum/PixelFormat.h"
#include "Magnum/Trade/ImageData.h" #include "Magnum/Trade/ImageData.h"
@ -63,6 +65,11 @@ struct ImageDataTest: TestSuite::Tester {
void release(); void release();
void releaseCompressed(); void releaseCompressed();
void pixels1D();
void pixels2D();
void pixels3D();
void pixelsCompressed();
}; };
ImageDataTest::ImageDataTest() { ImageDataTest::ImageDataTest() {
@ -93,7 +100,12 @@ ImageDataTest::ImageDataTest() {
&ImageDataTest::dataProperties, &ImageDataTest::dataProperties,
&ImageDataTest::release, &ImageDataTest::release,
&ImageDataTest::releaseCompressed}); &ImageDataTest::releaseCompressed,
&ImageDataTest::pixels1D,
&ImageDataTest::pixels2D,
&ImageDataTest::pixels3D,
&ImageDataTest::pixelsCompressed});
} }
namespace GL { namespace GL {
@ -579,6 +591,91 @@ void ImageDataTest::releaseCompressed() {
CORRADE_COMPARE(a.size(), Vector2i()); 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<char>{15}};
const ImageData1D& cimage = image;
/* Full test is in ImageTest, this is just a sanity check */
{
Containers::StridedArrayView1D<Color3ub> 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<const Color3ub> 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<char>{120}};
const ImageData2D& cimage = image;
/* Full test is in ImageTest, this is just a sanity check */
{
Containers::StridedArrayView2D<Color3ub> pixels = Containers::arrayCast<2, Color3ub>(image.pixels());
CORRADE_COMPARE(pixels.size(), (Containers::StridedArrayView2D<Color3ub>::Size{4, 2}));
CORRADE_COMPARE(pixels.stride(), (Containers::StridedArrayView2D<Color3ub>::Stride{20, 3}));
CORRADE_COMPARE(pixels.data(), image.data() + 2*20 + 3*3);
} {
Containers::StridedArrayView2D<const Color3ub> pixels = Containers::arrayCast<2, const Color3ub>(cimage.pixels());
CORRADE_COMPARE(pixels.size(), (Containers::StridedArrayView2D<const Color3ub>::Size{4, 2}));
CORRADE_COMPARE(pixels.stride(), (Containers::StridedArrayView2D<const Color3ub>::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<char>{560}};
const ImageData3D& cimage = image;
/* Full test is in ImageTest, this is just a sanity check */
{
Containers::StridedArrayView3D<Color3ub> pixels = Containers::arrayCast<3, Color3ub>(image.pixels());
CORRADE_COMPARE(pixels.size(), (Containers::StridedArrayView3D<Color3ub>::Size{3, 4, 2}));
CORRADE_COMPARE(pixels.stride(), (Containers::StridedArrayView3D<Color3ub>::Stride{140, 20, 3}));
CORRADE_COMPARE(pixels.data(), image.data() + 140 + 2*20 + 3*3);
} {
Containers::StridedArrayView3D<const Color3ub> pixels = Containers::arrayCast<3, const Color3ub>(cimage.pixels());
CORRADE_COMPARE(pixels.size(), (Containers::StridedArrayView3D<const Color3ub>::Size{3, 4, 2}));
CORRADE_COMPARE(pixels.stride(), (Containers::StridedArrayView3D<const Color3ub>::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<char>{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) CORRADE_TEST_MAIN(Magnum::Trade::Test::ImageDataTest)

Loading…
Cancel
Save