From a22ed80ea31838a8aafdcf095b3418db1a41002f Mon Sep 17 00:00:00 2001 From: Mikhail Pozdnyakov Date: Fri, 12 Oct 2018 14:40:18 +0300 Subject: Refactor mbgl::Image class - Used `thin template idiom` to avoid size bloating - Exceptions free - No public data members, other improvements --- cmake/core-files.txt | 1 + include/mbgl/util/image.hpp | 188 ++++++--------------- platform/android/src/bitmap.cpp | 10 +- platform/android/src/map/image.cpp | 2 +- platform/android/src/native_map_view.cpp | 2 +- .../android/src/text/local_glyph_rasterizer.cpp | 5 +- platform/default/jpeg_reader.cpp | 2 +- platform/default/png_reader.cpp | 2 +- platform/default/png_writer.cpp | 8 +- platform/glfw/glfw_view.cpp | 2 +- platform/node/src/node_map.cpp | 14 +- platform/qt/src/qt_image.cpp | 2 +- src/mbgl/annotation/annotation_manager.cpp | 2 +- src/mbgl/geometry/dem_data.cpp | 11 +- src/mbgl/geometry/dem_data.hpp | 4 +- src/mbgl/geometry/line_atlas.cpp | 18 +- src/mbgl/gl/context.hpp | 8 +- src/mbgl/renderer/image_atlas.cpp | 10 +- src/mbgl/renderer/image_manager.cpp | 11 +- src/mbgl/renderer/layers/render_heatmap_layer.cpp | 9 +- src/mbgl/renderer/layers/render_line_layer.cpp | 9 +- src/mbgl/renderer/renderer_impl.cpp | 2 +- src/mbgl/sprite/sprite_parser.cpp | 6 +- src/mbgl/text/glyph_atlas.cpp | 6 +- src/mbgl/text/glyph_pbf.cpp | 7 +- src/mbgl/util/image.cpp | 110 ++++++++++++ src/mbgl/util/premultiply.cpp | 18 +- src/mbgl/util/tiny_sdf.cpp | 14 +- test/geometry/dem_data.test.cpp | 2 +- test/renderer/image_manager.test.cpp | 6 +- test/sprite/sprite_parser.test.cpp | 32 ++-- test/src/mbgl/test/util.cpp | 16 +- test/style/source.test.cpp | 8 +- test/style/style_image.test.cpp | 8 +- test/text/glyph_manager.test.cpp | 11 +- test/text/glyph_pbf.test.cpp | 2 +- test/util/image.test.cpp | 170 +++++++++---------- 37 files changed, 383 insertions(+), 355 deletions(-) create mode 100644 src/mbgl/util/image.cpp diff --git a/cmake/core-files.txt b/cmake/core-files.txt index 2d55807ee8..84535614b9 100644 --- a/cmake/core-files.txt +++ b/cmake/core-files.txt @@ -734,6 +734,7 @@ src/mbgl/util/http_timeout.cpp src/mbgl/util/http_timeout.hpp src/mbgl/util/i18n.cpp src/mbgl/util/i18n.hpp +src/mbgl/util/image.cpp src/mbgl/util/interpolate.cpp src/mbgl/util/intersection_tests.cpp src/mbgl/util/intersection_tests.hpp diff --git a/include/mbgl/util/image.hpp b/include/mbgl/util/image.hpp index 4887058f79..e38a283d70 100644 --- a/include/mbgl/util/image.hpp +++ b/include/mbgl/util/image.hpp @@ -1,6 +1,5 @@ #pragma once -#include #include #include @@ -11,6 +10,35 @@ namespace mbgl { +class ImageBase { +public: + // Returns true, if has data and non-zero size; returns false otherwise. + bool valid() const; + uint8_t* data() { return data_.get(); } + const uint8_t* data() const { return data_.get(); } + const Size& size() const { return size_; } + // Takes data from this image, leaving it in invalid state and with empty size. + std::unique_ptr takeData(); + +protected: + ImageBase(); + ImageBase(const Size& s, size_t channels); + ImageBase(const Size& s, std::unique_ptr d); + ImageBase(ImageBase&& o); + ~ImageBase(); + ImageBase& operator=(ImageBase&& o); + + size_t stride(size_t channels) const; + size_t bytes(size_t channels) const; + void resize(const Size& s, size_t channels); + void clear(const Point& pt, const Size& size, size_t channels); + + static void copy(const ImageBase& srcImg, ImageBase& dstImg, const Point& srcPt, const Point& dstPt, const Size& size, size_t channels); + + Size size_; + std::unique_ptr data_; +}; + enum class ImageAlphaMode { Unassociated, Premultiplied, @@ -18,152 +46,46 @@ enum class ImageAlphaMode { }; template -class Image : private util::noncopyable { +class Image : public ImageBase { public: + static constexpr size_t channels = Mode == ImageAlphaMode::Exclusive ? 1 : 4; Image() = default; - - Image(Size size_) - : size(std::move(size_)), - data(std::make_unique(bytes())) {} - - Image(Size size_, const uint8_t* srcData, std::size_t srcLength) - : size(std::move(size_)) { - if (srcLength != bytes()) { - throw std::invalid_argument("mismatched image size"); - } - data = std::make_unique(bytes()); - std::copy(srcData, srcData + srcLength, data.get()); - } - - Image(Size size_, std::unique_ptr data_) - : size(std::move(size_)), - data(std::move(data_)) {} - - Image(Image&& o) - : size(o.size), - data(std::move(o.data)) { - o.size.width = o.size.height = 0; + Image(const Size& s) : ImageBase(s, channels) {} + Image(const Size& s, std::unique_ptr d) : ImageBase(s, std::move(d)) {} + Image(Image&& o) = default; + ~Image() = default; + Image& operator=(Image&& o) = default; + size_t stride() const { return ImageBase::stride(channels); } + size_t bytes() const { return ImageBase::bytes(channels); } + // Resizes this image. If the given size is more than the current one, + // extra space is filled with '0'. + void resize(const Size& s) { ImageBase::resize(s, channels); } + // Clears the rect area specified by `pt` and `size` from this image. + // The image must be valid. + void clear(const Point& pt, const Size& s) { + ImageBase::clear(pt, s, channels); } - - Image& operator=(Image&& o) { - size = o.size; - data = std::move(o.data); - o.size.width = o.size.height = 0; - return *this; + bool operator==(const Image& o) const { + return std::equal(data(), data() + bytes(), + o.data(), o.data() + o.bytes()); } - - friend bool operator==(const Image& lhs, const Image& rhs) { - return std::equal(lhs.data.get(), lhs.data.get() + lhs.bytes(), - rhs.data.get(), rhs.data.get() + rhs.bytes()); - } - - friend bool operator!=(const Image& lhs, const Image& rhs) { - return !(lhs == rhs); - } - - bool valid() const { - return !size.isEmpty() && data.get() != nullptr; + bool operator!=(const Image& o) const { + return !(operator==(o)); } template T clone() const { - T copy_(size); - std::copy(data.get(), data.get() + bytes(), copy_.data.get()); + T copy_(size()); + std::copy(data(), data() + bytes(), copy_.data()); return copy_; } - size_t stride() const { return channels * size.width; } - size_t bytes() const { return stride() * size.height; } - - void fill(uint8_t value) { - std::fill(data.get(), data.get() + bytes(), value); - } - - void resize(Size size_) { - if (size == size_) { - return; - } - Image newImage(size_); - newImage.fill(0); - copy(*this, newImage, {0, 0}, {0, 0}, { - std::min(size.width, size_.width), - std::min(size.height, size_.height) - }); - operator=(std::move(newImage)); - } - - // Clears the rect area specified by `pt` and `size` from `dstImage`. - static void clear(Image& dstImg, const Point& pt, const Size& size) { - if (size.isEmpty()) { - return; - } - - if (!dstImg.valid()) { - throw std::invalid_argument("invalid destination for image clear"); - } - - if (size.width > dstImg.size.width || - size.height > dstImg.size.height || - pt.x > dstImg.size.width - size.width || - pt.y > dstImg.size.height - size.height) { - throw std::out_of_range("out of range destination coordinates for image clear"); - } - - uint8_t* dstData = dstImg.data.get(); - - for (uint32_t y = 0; y < size.height; y++) { - const std::size_t dstOffset = (pt.y + y) * dstImg.stride() + pt.x * channels; - std::memset(dstData + dstOffset, 0, size.width * channels); - } - } - // Copy image data within `rect` from `src` to the rectangle of the same size at `pt` - // in `dst`. If the specified bounds exceed the bounds of the source or destination, - // throw `std::out_of_range`. Must not be used to move data within a single Image. + // in `dst`. The specified bounds must not exceed the bounds of the source or destination. + // Must not be used to move data within a single Image. static void copy(const Image& srcImg, Image& dstImg, const Point& srcPt, const Point& dstPt, const Size& size) { - if (size.isEmpty()) { - return; - } - - if (!srcImg.valid()) { - throw std::invalid_argument("invalid source for image copy"); - } - - if (!dstImg.valid()) { - throw std::invalid_argument("invalid destination for image copy"); - } - - if (size.width > srcImg.size.width || - size.height > srcImg.size.height || - srcPt.x > srcImg.size.width - size.width || - srcPt.y > srcImg.size.height - size.height) { - throw std::out_of_range("out of range source coordinates for image copy"); - } - - if (size.width > dstImg.size.width || - size.height > dstImg.size.height || - dstPt.x > dstImg.size.width - size.width || - dstPt.y > dstImg.size.height - size.height) { - throw std::out_of_range("out of range destination coordinates for image copy"); - } - - const uint8_t* srcData = srcImg.data.get(); - uint8_t* dstData = dstImg.data.get(); - - assert(srcData != dstData); - - for (uint32_t y = 0; y < size.height; y++) { - const std::size_t srcOffset = (srcPt.y + y) * srcImg.stride() + srcPt.x * channels; - const std::size_t dstOffset = (dstPt.y + y) * dstImg.stride() + dstPt.x * channels; - std::copy(srcData + srcOffset, - srcData + srcOffset + size.width * channels, - dstData + dstOffset); - } + return ImageBase::copy(srcImg, dstImg, srcPt, dstPt, size, channels); } - - Size size; - static constexpr size_t channels = Mode == ImageAlphaMode::Exclusive ? 1 : 4; - std::unique_ptr data; }; using UnassociatedImage = Image; diff --git a/platform/android/src/bitmap.cpp b/platform/android/src/bitmap.cpp index eb7c676b12..e664a2d406 100644 --- a/platform/android/src/bitmap.cpp +++ b/platform/android/src/bitmap.cpp @@ -89,7 +89,7 @@ PremultipliedImage Bitmap::GetImage(jni::JNIEnv& env, const jni::Object& } jni::Local> Bitmap::CreateBitmap(jni::JNIEnv& env, const PremultipliedImage& image) { - auto bitmap = CreateBitmap(env, image.size.width, image.size.height, Config::ARGB_8888); + auto bitmap = CreateBitmap(env, image.size().width, image.size().height, Config::ARGB_8888); AndroidBitmapInfo info; const int result = AndroidBitmap_getInfo(&env, jni::Unwrap(*bitmap), &info); @@ -98,15 +98,15 @@ jni::Local> Bitmap::CreateBitmap(jni::JNIEnv& env, const Pre throw std::runtime_error("bitmap creation: couldn't get bitmap info"); } - assert(info.width == image.size.width); - assert(info.height == image.size.height); + assert(info.width == image.size().width); + assert(info.height == image.size().height); assert(info.format == ANDROID_BITMAP_FORMAT_RGBA_8888); PixelGuard guard(env, bitmap); // Copy the PremultipliedImage into the Android Bitmap - for (uint32_t y = 0; y < image.size.height; y++) { - auto begin = image.data.get() + y * image.stride(); + for (uint32_t y = 0; y < image.size().height; y++) { + auto begin = image.data() + y * image.stride(); std::copy(begin, begin + image.stride(), guard.get() + y * info.stride); } diff --git a/platform/android/src/map/image.cpp b/platform/android/src/map/image.cpp index a91cc938ed..76b7a02a9b 100644 --- a/platform/android/src/map/image.cpp +++ b/platform/android/src/map/image.cpp @@ -29,7 +29,7 @@ mbgl::style::Image Image::getImage(jni::JNIEnv& env, const jni::Object& i throw mbgl::util::SpriteImageException("Sprite image pixel count mismatch"); } - jni::GetArrayRegion(env, *pixels, 0, size, reinterpret_cast(premultipliedImage.data.get())); + jni::GetArrayRegion(env, *pixels, 0, size, reinterpret_cast(premultipliedImage.data())); return mbgl::style::Image {name, std::move(premultipliedImage), pixelRatio, sdf}; } diff --git a/platform/android/src/native_map_view.cpp b/platform/android/src/native_map_view.cpp index 1c744a6b57..dfe330dab4 100755 --- a/platform/android/src/native_map_view.cpp +++ b/platform/android/src/native_map_view.cpp @@ -601,7 +601,7 @@ void NativeMapView::addAnnotationIcon(JNIEnv& env, const jni::String& symbol, ji throw mbgl::util::SpriteImageException("Sprite image pixel count mismatch"); } - jni::GetArrayRegion(env, *jpixels, 0, size, reinterpret_cast(premultipliedImage.data.get())); + jni::GetArrayRegion(env, *jpixels, 0, size, reinterpret_cast(premultipliedImage.data())); map->addAnnotationImage(std::make_unique( symbolName, std::move(premultipliedImage), float(scale))); } diff --git a/platform/android/src/text/local_glyph_rasterizer.cpp b/platform/android/src/text/local_glyph_rasterizer.cpp index 8892ee3f37..534f323172 100644 --- a/platform/android/src/text/local_glyph_rasterizer.cpp +++ b/platform/android/src/text/local_glyph_rasterizer.cpp @@ -120,8 +120,9 @@ Glyph LocalGlyphRasterizer::rasterizeGlyph(const FontStack& fontStack, GlyphID g // Copy alpha values from RGBA bitmap into the AlphaImage output fixedMetrics.bitmap = AlphaImage(size); - for (uint32_t i = 0; i < size.width * size.height; i++) { - fixedMetrics.bitmap.data[i] = rgbaBitmap.data[4 * i + 3]; + uint32_t sizeArea = size.area(); + for (uint32_t i = 0; i < sizeArea; ++i) { + fixedMetrics.bitmap.data()[i] = rgbaBitmap.data()[4 * i + 3]; } return fixedMetrics; diff --git a/platform/default/jpeg_reader.cpp b/platform/default/jpeg_reader.cpp index 5f613f9423..93740649f0 100644 --- a/platform/default/jpeg_reader.cpp +++ b/platform/default/jpeg_reader.cpp @@ -120,7 +120,7 @@ PremultipliedImage decodeJPEG(const uint8_t* data, size_t size) { size_t rowStride = components * width; PremultipliedImage image({ static_cast(width), static_cast(height) }); - uint8_t* dst = image.data.get(); + uint8_t* dst = image.data(); JSAMPARRAY buffer = (*cinfo.mem->alloc_sarray)((j_common_ptr) &cinfo, JPOOL_IMAGE, rowStride, 1); diff --git a/platform/default/png_reader.cpp b/platform/default/png_reader.cpp index 4d4ee29d1f..8046db7449 100644 --- a/platform/default/png_reader.cpp +++ b/platform/default/png_reader.cpp @@ -131,7 +131,7 @@ PremultipliedImage decodePNG(const uint8_t* data, size_t size) { // alloc row pointers const std::unique_ptr rows(new png_bytep[height]); for (unsigned row = 0; row < height; ++row) - rows[row] = image.data.get() + row * width * 4; + rows[row] = image.data() + row * width * 4; png_read_image(png_ptr, rows.get()); png_read_end(png_ptr, nullptr); diff --git a/platform/default/png_writer.cpp b/platform/default/png_writer.cpp index b89e253f85..ffc02126a9 100644 --- a/platform/default/png_writer.cpp +++ b/platform/default/png_writer.cpp @@ -44,8 +44,8 @@ std::string encodePNG(const PremultipliedImage& pre) { // IHDR chunk for our RGBA image. const char ihdr[13] = { - NETWORK_BYTE_UINT32(src.size.width), // width - NETWORK_BYTE_UINT32(src.size.height), // height + NETWORK_BYTE_UINT32(src.size().width), // width + NETWORK_BYTE_UINT32(src.size().height), // height 8, // bit depth == 8 bits 6, // color type == RGBA 0, // compression method == deflate @@ -56,10 +56,10 @@ std::string encodePNG(const PremultipliedImage& pre) { // Prepare the (compressed) data chunk. const auto stride = src.stride(); std::string idat; - for (uint32_t y = 0; y < src.size.height; y++) { + for (uint32_t y = 0; y < src.size().height; y++) { // Every scanline needs to be prefixed with one byte that indicates the filter type. idat.append(1, 0); // filter type 0 - idat.append((const char*)(src.data.get() + y * stride), stride); + idat.append((const char*)(src.data() + y * stride), stride); } idat = util::compress(idat); diff --git a/platform/glfw/glfw_view.cpp b/platform/glfw/glfw_view.cpp index 9179113139..101f18212f 100644 --- a/platform/glfw/glfw_view.cpp +++ b/platform/glfw/glfw_view.cpp @@ -324,7 +324,7 @@ GLFWView::makeImage(const std::string& id, int width, int height, float pixelRat const int h = std::ceil(pixelRatio * height); mbgl::PremultipliedImage image({ static_cast(w), static_cast(h) }); - auto data = reinterpret_cast(image.data.get()); + auto* data = reinterpret_cast(image.data()); const int dist = (w / 2) * (w / 2); for (int y = 0; y < h; y++) { for (int x = 0; x < w; x++) { diff --git a/platform/node/src/node_map.cpp b/platform/node/src/node_map.cpp index 6dccdf5292..bb7a5121f9 100644 --- a/platform/node/src/node_map.cpp +++ b/platform/node/src/node_map.cpp @@ -421,7 +421,7 @@ void NodeMap::Render(const Nan::FunctionCallbackInfo& info) { try { auto options = ParseOptions(Nan::To(info[0]).ToLocalChecked()); assert(!nodeMap->req); - assert(!nodeMap->image.data); + assert(!nodeMap->image.valid()); nodeMap->req = std::make_unique(Nan::To(info[1]).ToLocalChecked()); nodeMap->startRender(std::move(options)); @@ -463,7 +463,7 @@ void NodeMap::startRender(NodeMap::RenderOptions options) { error = std::move(eptr); uv_async_send(async); } else { - assert(!image.data); + assert(!image.valid()); image = frontend->readStillImage(); uv_async_send(async); } @@ -498,7 +498,7 @@ void NodeMap::renderFinished() { // These have to be empty to be prepared for the next render call. assert(!req); - assert(!image.data); + assert(!image.valid()); v8::Local callback = Nan::New(request->callback); v8::Local target = Nan::New(); @@ -524,16 +524,16 @@ void NodeMap::renderFinished() { assert(!error); request->runInAsyncScope(target, callback, 1, argv); - } else if (img.data) { + } else if (img.valid()) { v8::Local pixels = Nan::NewBuffer( - reinterpret_cast(img.data.get()), img.bytes(), + reinterpret_cast(img.data()), img.bytes(), // Retain the data until the buffer is deleted. [](char *, void * hint) { delete [] reinterpret_cast(hint); }, - img.data.get() + img.data() ).ToLocalChecked(); - img.data.release(); + img.takeData().release(); v8::Local argv[] = { Nan::Null(), diff --git a/platform/qt/src/qt_image.cpp b/platform/qt/src/qt_image.cpp index 302d398739..2a4dd23f40 100644 --- a/platform/qt/src/qt_image.cpp +++ b/platform/qt/src/qt_image.cpp @@ -7,7 +7,7 @@ namespace mbgl { std::string encodePNG(const PremultipliedImage& pre) { - QImage image(pre.data.get(), pre.size.width, pre.size.height, + QImage image(pre.data(), pre.size().width, pre.size().height, QImage::Format_ARGB32_Premultiplied); QByteArray array; diff --git a/src/mbgl/annotation/annotation_manager.cpp b/src/mbgl/annotation/annotation_manager.cpp index 1baf83179e..ccd3290b0b 100644 --- a/src/mbgl/annotation/annotation_manager.cpp +++ b/src/mbgl/annotation/annotation_manager.cpp @@ -240,7 +240,7 @@ double AnnotationManager::getTopOffsetPixelsForImage(const std::string& id_) { std::lock_guard lock(mutex); const std::string id = prefixedImageID(id_); auto it = images.find(id); - return it != images.end() ? -(it->second.getImage().size.height / it->second.getPixelRatio()) / 2 : 0; + return it != images.end() ? -(it->second.getImage().size().height / it->second.getPixelRatio()) / 2 : 0; } } // namespace mbgl diff --git a/src/mbgl/geometry/dem_data.cpp b/src/mbgl/geometry/dem_data.cpp index 7fa98950ea..0c7d9ee29b 100644 --- a/src/mbgl/geometry/dem_data.cpp +++ b/src/mbgl/geometry/dem_data.cpp @@ -4,12 +4,12 @@ namespace mbgl { DEMData::DEMData(const PremultipliedImage& _image, Tileset::DEMEncoding encoding): - dim(_image.size.height), - border(std::max(std::ceil(_image.size.height / 2), 1)), + dim(_image.size().height), + border(std::max(std::ceil(_image.size().height / 2), 1)), stride(dim + 2 * border), image({ static_cast(stride), static_cast(stride) }) { - if (_image.size.height != _image.size.width){ + if (_image.size().height != _image.size().width){ throw std::runtime_error("raster-dem tiles must be square."); } @@ -25,13 +25,14 @@ DEMData::DEMData(const PremultipliedImage& _image, Tileset::DEMEncoding encoding auto decodeRGB = encoding == Tileset::DEMEncoding::Terrarium ? decodeTerrarium : decodeMapbox; - std::memset(image.data.get(), 0, image.bytes()); + std::memset(image.data(), 0, image.bytes()); for (int32_t y = 0; y < dim; y++) { for (int32_t x = 0; x < dim; x++) { const int32_t i = y * dim + x; const int32_t j = i * 4; - set(x, y, decodeRGB(_image.data[j], _image.data[j+1], _image.data[j+2])); + const uint8_t* imageData = _image.data(); + set(x, y, decodeRGB(imageData[j], imageData[j+1], imageData[j+2])); } } diff --git a/src/mbgl/geometry/dem_data.hpp b/src/mbgl/geometry/dem_data.hpp index 817d3cc9c9..16bfb4f308 100644 --- a/src/mbgl/geometry/dem_data.hpp +++ b/src/mbgl/geometry/dem_data.hpp @@ -17,11 +17,11 @@ public: void backfillBorder(const DEMData& borderTileData, int8_t dx, int8_t dy); void set(const int32_t x, const int32_t y, const int32_t value) { - reinterpret_cast(image.data.get())[idx(x, y)] = value + 65536; + reinterpret_cast(image.data())[idx(x, y)] = value + 65536; } int32_t get(const int32_t x, const int32_t y) const { - return reinterpret_cast(image.data.get())[idx(x, y)] - 65536; + return reinterpret_cast(image.data())[idx(x, y)] - 65536; } const PremultipliedImage* getImage() const { diff --git a/src/mbgl/geometry/line_atlas.cpp b/src/mbgl/geometry/line_atlas.cpp index 1bd6f987e1..6b9a6601ac 100644 --- a/src/mbgl/geometry/line_atlas.cpp +++ b/src/mbgl/geometry/line_atlas.cpp @@ -43,7 +43,7 @@ LinePatternPos LineAtlas::addDash(const std::vector& dasharray, LinePatte return LinePatternPos(); } - if (nextRow + dashheight > image.size.height) { + if (nextRow + dashheight > image.size().height) { Log::Warning(Event::OpenGL, "line atlas bitmap overflow"); return LinePatternPos(); } @@ -53,7 +53,7 @@ LinePatternPos LineAtlas::addDash(const std::vector& dasharray, LinePatte length += part; } - float stretch = image.size.width / length; + float stretch = image.size().width / length; float halfWidth = stretch * 0.5; // If dasharray has an odd length, both the first and last parts // are dashes and should be joined seamlessly. @@ -61,7 +61,7 @@ LinePatternPos LineAtlas::addDash(const std::vector& dasharray, LinePatte for (int y = -n; y <= n; y++) { int row = nextRow + n + y; - int index = image.size.width * row; + int index = image.size().width * row; float left = 0; float right = dasharray[0]; @@ -71,7 +71,7 @@ LinePatternPos LineAtlas::addDash(const std::vector& dasharray, LinePatte left -= dasharray.back(); } - for (uint32_t x = 0; x < image.size.width; x++) { + for (uint32_t x = 0; x < image.size().width; x++) { while (right < x / stretch) { left = right; @@ -105,14 +105,14 @@ LinePatternPos LineAtlas::addDash(const std::vector& dasharray, LinePatte } else { signedDistance = int((inside ? 1 : -1) * dist); } - - image.data[index + x] = fmax(0, fmin(255, signedDistance + offset)); + uint8_t* imageData = const_cast(image.data()); + imageData[index + x] = fmax(0, fmin(255, signedDistance + offset)); } } LinePatternPos position; - position.y = (0.5 + nextRow + n) / image.size.height; - position.height = (2.0 * n) / image.size.height; + position.y = (0.5 + nextRow + n) / image.size().height; + position.height = (2.0 * n) / image.size().height; position.width = length; nextRow += dashheight; @@ -123,7 +123,7 @@ LinePatternPos LineAtlas::addDash(const std::vector& dasharray, LinePatte } Size LineAtlas::getSize() const { - return image.size; + return image.size(); } void LineAtlas::upload(gl::Context& context, gl::TextureUnit unit) { diff --git a/src/mbgl/gl/context.hpp b/src/mbgl/gl/context.hpp index bd682f44da..9eaec2d2c1 100644 --- a/src/mbgl/gl/context.hpp +++ b/src/mbgl/gl/context.hpp @@ -121,7 +121,7 @@ public: template void drawPixels(const Image& image) { auto format = image.channels == 4 ? TextureFormat::RGBA : TextureFormat::Alpha; - drawPixels(image.size, image.data.get(), format); + drawPixels(image.size(), image.data(), format); } #endif // MBGL_USE_GLES2 @@ -131,7 +131,7 @@ public: TextureUnit unit = 0, TextureType type = TextureType::UnsignedByte) { auto format = image.channels == 4 ? TextureFormat::RGBA : TextureFormat::Alpha; - return { image.size, createTexture(image.size, image.data.get(), format, unit, type) }; + return { image.size(), createTexture(image.size(), image.data(), format, unit, type) }; } template @@ -140,8 +140,8 @@ public: TextureUnit unit = 0, TextureType type = TextureType::UnsignedByte) { auto format = image.channels == 4 ? TextureFormat::RGBA : TextureFormat::Alpha; - updateTexture(obj.texture.get(), image.size, image.data.get(), format, unit, type); - obj.size = image.size; + updateTexture(obj.texture.get(), image.size(), image.data(), format, unit, type); + obj.size = image.size(); } // Creates an empty texture with the specified dimensions. diff --git a/src/mbgl/renderer/image_atlas.cpp b/src/mbgl/renderer/image_atlas.cpp index b39c788ced..a97d913616 100644 --- a/src/mbgl/renderer/image_atlas.cpp +++ b/src/mbgl/renderer/image_atlas.cpp @@ -18,8 +18,8 @@ ImagePosition::ImagePosition(const mapbox::Bin& bin, const style::Image::Impl& i const mapbox::Bin& _packImage(mapbox::ShelfPack& pack, const style::Image::Impl& image, ImageAtlas& resultImage, ImageType imageType) { const mapbox::Bin& bin = *pack.packOne(-1, - image.image.size.width + 2 * padding, - image.image.size.height + 2 * padding); + image.image.size().width + 2 * padding, + image.image.size().height + 2 * padding); resultImage.image.resize({ static_cast(pack.width()), @@ -33,11 +33,11 @@ const mapbox::Bin& _packImage(mapbox::ShelfPack& pack, const style::Image::Impl& bin.x + padding, bin.y + padding }, - image.image.size); + image.image.size()); uint32_t x = bin.x + padding, y = bin.y + padding, - w = image.image.size.width, - h = image.image.size.height; + w = image.image.size().width, + h = image.image.size().height; if (imageType == ImageType::Pattern) { // Add 1 pixel wrapped padding on each side of the image. diff --git a/src/mbgl/renderer/image_manager.cpp b/src/mbgl/renderer/image_manager.cpp index fc1f5bb167..107f9cea29 100644 --- a/src/mbgl/renderer/image_manager.cpp +++ b/src/mbgl/renderer/image_manager.cpp @@ -44,7 +44,7 @@ void ImageManager::removeImage(const std::string& id) { const uint32_t y = it->second.bin->y; const uint32_t w = it->second.bin->w; const uint32_t h = it->second.bin->h; - PremultipliedImage::clear(atlasImage, { x, y }, { w, h }); + atlasImage.clear({ x, y }, { w, h }); shelfPack.unref(*it->second.bin); patterns.erase(it); @@ -130,8 +130,8 @@ optional ImageManager::getPattern(const std::string& id) { return {}; } - const uint16_t width = image->image.size.width + padding * 2; - const uint16_t height = image->image.size.height + padding * 2; + const uint16_t width = image->image.size().width + padding * 2; + const uint16_t height = image->image.size().height + padding * 2; mapbox::Bin* bin = shelfPack.packOne(-1, width, height); if (!bin) { @@ -139,13 +139,14 @@ optional ImageManager::getPattern(const std::string& id) { } atlasImage.resize(getPixelSize()); + assert(atlasImage.valid()); const PremultipliedImage& src = image->image; const uint32_t x = bin->x + padding; const uint32_t y = bin->y + padding; - const uint32_t w = src.size.width; - const uint32_t h = src.size.height; + const uint32_t w = src.size().width; + const uint32_t h = src.size().height; PremultipliedImage::copy(src, atlasImage, { 0, 0 }, { x, y }, { w, h }); diff --git a/src/mbgl/renderer/layers/render_heatmap_layer.cpp b/src/mbgl/renderer/layers/render_heatmap_layer.cpp index 4e5e890358..14f54cf639 100644 --- a/src/mbgl/renderer/layers/render_heatmap_layer.cpp +++ b/src/mbgl/renderer/layers/render_heatmap_layer.cpp @@ -196,13 +196,14 @@ void RenderHeatmapLayer::updateColorRamp() { } const auto length = colorRamp.bytes(); + auto* colorData = colorRamp.data(); for (uint32_t i = 0; i < length; i += 4) { const auto color = colorValue.evaluate(static_cast(i) / length); - colorRamp.data[i + 0] = std::floor(color.r * 255); - colorRamp.data[i + 1] = std::floor(color.g * 255); - colorRamp.data[i + 2] = std::floor(color.b * 255); - colorRamp.data[i + 3] = std::floor(color.a * 255); + colorData[i + 0] = std::floor(color.r * 255); + colorData[i + 1] = std::floor(color.g * 255); + colorData[i + 2] = std::floor(color.b * 255); + colorData[i + 3] = std::floor(color.a * 255); } if (colorRampTexture) { diff --git a/src/mbgl/renderer/layers/render_line_layer.cpp b/src/mbgl/renderer/layers/render_line_layer.cpp index 94081b5f09..a610ab7711 100644 --- a/src/mbgl/renderer/layers/render_line_layer.cpp +++ b/src/mbgl/renderer/layers/render_line_layer.cpp @@ -250,13 +250,14 @@ void RenderLineLayer::updateColorRamp() { } const auto length = colorRamp.bytes(); + auto* colorData = colorRamp.data(); for (uint32_t i = 0; i < length; i += 4) { const auto color = colorValue.evaluate(static_cast(i) / length); - colorRamp.data[i] = std::floor(color.r * 255); - colorRamp.data[i + 1] = std::floor(color.g * 255); - colorRamp.data[i + 2] = std::floor(color.b * 255); - colorRamp.data[i + 3] = std::floor(color.a * 255); + colorData[i] = std::floor(color.r * 255); + colorData[i + 1] = std::floor(color.g * 255); + colorData[i + 2] = std::floor(color.b * 255); + colorData[i + 3] = std::floor(color.a * 255); } if (colorRampTexture) { diff --git a/src/mbgl/renderer/renderer_impl.cpp b/src/mbgl/renderer/renderer_impl.cpp index 32fcd57332..f9e00eb623 100644 --- a/src/mbgl/renderer/renderer_impl.cpp +++ b/src/mbgl/renderer/renderer_impl.cpp @@ -488,7 +488,7 @@ void Renderer::Impl::render(const UpdateParameters& updateParameters) { auto image = parameters.context.readFramebuffer(viewport.size, false); // Scale the Stencil buffer to cover the entire color space. - auto it = image.data.get(); + auto it = image.data(); auto end = it + viewport.size.width * viewport.size.height; const auto factor = 255.0f / *std::max_element(it, end); for (; it != end; ++it) { diff --git a/src/mbgl/sprite/sprite_parser.cpp b/src/mbgl/sprite/sprite_parser.cpp index 99e2b0c8ca..116673cb4f 100644 --- a/src/mbgl/sprite/sprite_parser.cpp +++ b/src/mbgl/sprite/sprite_parser.cpp @@ -23,11 +23,11 @@ std::unique_ptr createStyleImage(const std::string& id, // Disallow invalid parameter configurations. if (width <= 0 || height <= 0 || width > 1024 || height > 1024 || ratio <= 0 || ratio > 10 || - srcX >= image.size.width || srcY >= image.size.height || - srcX + width > image.size.width || srcY + height > image.size.height) { + srcX >= image.size().width || srcY >= image.size().height || + srcX + width > image.size().width || srcY + height > image.size().height) { Log::Error(Event::Sprite, "Can't create sprite with invalid metrics: %ux%u@%u,%u in %ux%u@%sx sprite", width, height, srcX, srcY, - image.size.width, image.size.height, + image.size().width, image.size().height, util::toString(ratio).c_str()); return nullptr; } diff --git a/src/mbgl/text/glyph_atlas.cpp b/src/mbgl/text/glyph_atlas.cpp index da65aea8a9..ca85e474af 100644 --- a/src/mbgl/text/glyph_atlas.cpp +++ b/src/mbgl/text/glyph_atlas.cpp @@ -22,8 +22,8 @@ GlyphAtlas makeGlyphAtlas(const GlyphMap& glyphs) { const Glyph& glyph = **entry.second; const mapbox::Bin& bin = *pack.packOne(-1, - glyph.bitmap.size.width + 2 * padding, - glyph.bitmap.size.height + 2 * padding); + glyph.bitmap.size().width + 2 * padding, + glyph.bitmap.size().height + 2 * padding); result.image.resize({ static_cast(pack.width()), @@ -37,7 +37,7 @@ GlyphAtlas makeGlyphAtlas(const GlyphMap& glyphs) { bin.x + padding, bin.y + padding }, - glyph.bitmap.size); + glyph.bitmap.size()); positions.emplace(glyph.id, GlyphPosition { diff --git a/src/mbgl/text/glyph_pbf.cpp b/src/mbgl/text/glyph_pbf.cpp index cfaf803f75..afc57a13af 100644 --- a/src/mbgl/text/glyph_pbf.cpp +++ b/src/mbgl/text/glyph_pbf.cpp @@ -80,8 +80,11 @@ std::vector parseGlyphPBF(const GlyphRange& glyphRange, const std::string if (size.area() != glyphData.size()) { continue; } - - glyph.bitmap = AlphaImage(size, reinterpret_cast(glyphData.data()), glyphData.size()); + std::unique_ptr imageData{std::make_unique(glyphData.size())}; + const uint8_t* glyphDataAsUint8 = reinterpret_cast(glyphData.data()); + std::copy(glyphDataAsUint8, glyphDataAsUint8 + glyphData.size(), imageData.get()); + glyph.bitmap = AlphaImage(size, std::move(imageData)); + assert(glyphData.size() == glyph.bitmap.bytes()); } result.push_back(std::move(glyph)); diff --git a/src/mbgl/util/image.cpp b/src/mbgl/util/image.cpp new file mode 100644 index 0000000000..ee27a6e7d1 --- /dev/null +++ b/src/mbgl/util/image.cpp @@ -0,0 +1,110 @@ +#include + +namespace mbgl { + +ImageBase::ImageBase() = default; + +ImageBase::ImageBase(const Size& s, size_t channels) + : size_(std::move(s)), + data_(std::make_unique(bytes(channels))) { +} + +ImageBase::ImageBase(const Size& s, std::unique_ptr d) + : size_(std::move(s)), + data_(std::move(d)) {} + +ImageBase::ImageBase(ImageBase&& o) + : size_(std::move(o.size_)), + data_(o.takeData()) {} + +ImageBase& ImageBase::operator=(ImageBase&& o) { + size_ = o.size_; + data_ = o.takeData(); + return *this; +} + +ImageBase::~ImageBase() {} + +bool ImageBase::valid() const { + return !size_.isEmpty() && data_ != nullptr; +} + +size_t ImageBase::stride(size_t channels) const { + return channels * size_.width; +} + +size_t ImageBase::bytes(size_t channels) const { + return channels * size_.area(); +} + +std::unique_ptr ImageBase::takeData() { + size_ = Size{}; + return std::move(data_); +} + +void ImageBase::resize(const Size& s, size_t channels) { + if (s == size_) { + return; + } + const size_t bytesCount = channels * s.area(); + if (bytesCount == 0u) { + takeData().reset(); + return; + } + std::unique_ptr newData{std::make_unique(bytesCount)}; + std::memset(newData.get(), 0, bytesCount); + ImageBase newImage(s, std::move(newData)); + if (valid() && newImage.valid()) { + copy(*this, newImage, {0u, 0u}, {0u, 0u}, { + std::min(s.width, size_.width), + std::min(s.height, size_.height) + }, channels); + } + operator=(std::move(newImage)); +} + +void ImageBase::clear(const Point& pt, const Size& clearedSize, size_t channels) { + assert(valid()); + if (clearedSize.isEmpty()) { + return; + } + assert(clearedSize.width <= size_.width); + assert(clearedSize.height <= size_.height); + assert(pt.x <= size_.width - clearedSize.width); + assert(pt.y <= size_.height - clearedSize.height); + + for (uint32_t y = 0u; y < clearedSize.height; ++y) { + const std::size_t dstOffset = (pt.y + y) * stride(channels) + pt.x * channels; + std::memset(data() + dstOffset, 0u, clearedSize.width * channels); + } +} +// static +void ImageBase::copy(const ImageBase& srcImg, ImageBase& dstImg, const Point& srcPt, const Point& dstPt, const Size& size, size_t channels) { + assert(srcImg.valid()); + assert(dstImg.valid()); + + assert(size.width <= srcImg.size().width); + assert(size.height <= srcImg.size().height); + assert(srcPt.x <= srcImg.size().width - size.width); + assert(srcPt.y <= srcImg.size().height - size.height); + + assert(size.width <= dstImg.size().width); + assert(size.height <= dstImg.size().height); + assert(dstPt.x <= dstImg.size().width - size.width); + assert(dstPt.y <= dstImg.size().height - size.height); + + const uint8_t* srcData = srcImg.data(); + uint8_t* dstData = dstImg.data(); + + assert(srcData != dstData); + + for (uint32_t y = 0u; y < size.height; ++y) { + const std::size_t srcOffset = (srcPt.y + y) * srcImg.stride(channels) + srcPt.x * channels; + const std::size_t dstOffset = (dstPt.y + y) * dstImg.stride(channels) + dstPt.x * channels; + std::copy(srcData + srcOffset, + srcData + srcOffset + size.width * channels, + dstData + dstOffset); + } +} + +} // namespace mbgl diff --git a/src/mbgl/util/premultiply.cpp b/src/mbgl/util/premultiply.cpp index d9fb2480de..c03ac84f4c 100644 --- a/src/mbgl/util/premultiply.cpp +++ b/src/mbgl/util/premultiply.cpp @@ -6,13 +6,10 @@ namespace mbgl { namespace util { PremultipliedImage premultiply(UnassociatedImage&& src) { - PremultipliedImage dst; + Size size = src.size(); + PremultipliedImage dst(size, src.takeData()); - dst.size = src.size; - src.size = { 0, 0 }; - dst.data = std::move(src.data); - - uint8_t* data = dst.data.get(); + uint8_t* data = dst.data(); for (size_t i = 0; i < dst.bytes(); i += 4) { uint8_t& r = data[i + 0]; uint8_t& g = data[i + 1]; @@ -27,13 +24,10 @@ PremultipliedImage premultiply(UnassociatedImage&& src) { } UnassociatedImage unpremultiply(PremultipliedImage&& src) { - UnassociatedImage dst; - - dst.size = src.size; - src.size = { 0, 0 }; - dst.data = std::move(src.data); + Size size = src.size(); + UnassociatedImage dst(size, src.takeData()); - uint8_t* data = dst.data.get(); + uint8_t* data = dst.data(); for (size_t i = 0; i < dst.bytes(); i += 4) { uint8_t& r = data[i + 0]; uint8_t& g = data[i + 1]; diff --git a/src/mbgl/util/tiny_sdf.cpp b/src/mbgl/util/tiny_sdf.cpp index 6edcd83bc2..926da6ee3f 100644 --- a/src/mbgl/util/tiny_sdf.cpp +++ b/src/mbgl/util/tiny_sdf.cpp @@ -71,10 +71,10 @@ void edt(std::vector& data, } // namespace tinysdf AlphaImage transformRasterToSDF(const AlphaImage& rasterInput, double radius, double cutoff) { - uint32_t size = rasterInput.size.width * rasterInput.size.height; - uint32_t maxDimension = std::max(rasterInput.size.width, rasterInput.size.height); + uint32_t size = rasterInput.size().area(); + uint32_t maxDimension = std::max(rasterInput.size().width, rasterInput.size().height); - AlphaImage sdf(rasterInput.size); + AlphaImage sdf(rasterInput.size()); // temporary arrays for the distance transform std::vector gridOuter(size); @@ -85,17 +85,17 @@ AlphaImage transformRasterToSDF(const AlphaImage& rasterInput, double radius, do std::vector v(maxDimension); for (uint32_t i = 0; i < size; i++) { - double a = double(rasterInput.data[i]) / 255; // alpha value + double a = double(rasterInput.data()[i]) / 255; // alpha value gridOuter[i] = a == 1.0 ? 0.0 : a == 0.0 ? tinysdf::INF : std::pow(std::max(0.0, 0.5 - a), 2.0); gridInner[i] = a == 1.0 ? tinysdf::INF : a == 0.0 ? 0.0 : std::pow(std::max(0.0, a - 0.5), 2.0); } - tinysdf::edt(gridOuter, rasterInput.size.width, rasterInput.size.height, f, d, v, z); - tinysdf::edt(gridInner, rasterInput.size.width, rasterInput.size.height, f, d, v, z); + tinysdf::edt(gridOuter, rasterInput.size().width, rasterInput.size().height, f, d, v, z); + tinysdf::edt(gridInner, rasterInput.size().width, rasterInput.size().height, f, d, v, z); for (uint32_t i = 0; i < size; i++) { double distance = gridOuter[i] - gridInner[i]; - sdf.data[i] = std::max(0l, std::min(255l, ::lround(255.0 - 255.0 * (distance / radius + cutoff)))); + sdf.data()[i] = std::max(0l, std::min(255l, ::lround(255.0 - 255.0 * (distance / radius + cutoff)))); } return sdf; diff --git a/test/geometry/dem_data.test.cpp b/test/geometry/dem_data.test.cpp index 3848f028f1..8c1455cd7a 100644 --- a/test/geometry/dem_data.test.cpp +++ b/test/geometry/dem_data.test.cpp @@ -10,7 +10,7 @@ auto fakeImage = [](Size s) { PremultipliedImage img = PremultipliedImage(s); for (size_t i = 0; i < img.bytes(); i ++) { - img.data[i] = (i+1) % 4 == 0 ? 1 : std::rand() % 255; + img.data()[i] = (i+1) % 4 == 0 ? 1 : std::rand() % 255; } return img; }; diff --git a/test/renderer/image_manager.test.cpp b/test/renderer/image_manager.test.cpp index 4a838d0f9c..c6a1d39bcd 100644 --- a/test/renderer/image_manager.test.cpp +++ b/test/renderer/image_manager.test.cpp @@ -39,7 +39,7 @@ TEST(ImageManager, Basic) { EXPECT_EQ(18, metro.displaySize()[0]); EXPECT_EQ(18, metro.displaySize()[1]); EXPECT_EQ(1.0f, metro.pixelRatio); - EXPECT_EQ(imageManager.getPixelSize(), imageManager.getAtlasImage().size); + EXPECT_EQ(imageManager.getPixelSize(), imageManager.getAtlasImage().size()); test::checkImage("test/fixtures/image_manager/basic", imageManager.getAtlasImage()); } @@ -48,7 +48,7 @@ TEST(ImageManager, Updates) { ImageManager imageManager; PremultipliedImage imageA({ 16, 12 }); - imageA.fill(255); + std::fill(imageA.data(), imageA.data() + imageA.bytes(), 255); imageManager.addImage(makeMutable("one", std::move(imageA), 1)); auto a = *imageManager.getPattern("one"); @@ -62,7 +62,7 @@ TEST(ImageManager, Updates) { test::checkImage("test/fixtures/image_manager/updates_before", imageManager.getAtlasImage()); PremultipliedImage imageB({ 5, 5 }); - imageA.fill(200); + std::fill(imageA.data(), imageA.data() + imageA.bytes(), 200); imageManager.updateImage(makeMutable("one", std::move(imageB), 1)); auto b = *imageManager.getPattern("one"); diff --git a/test/sprite/sprite_parser.test.cpp b/test/sprite/sprite_parser.test.cpp index 529e4c75e8..544c7c41d8 100644 --- a/test/sprite/sprite_parser.test.cpp +++ b/test/sprite/sprite_parser.test.cpp @@ -24,8 +24,8 @@ TEST(Sprite, SpriteImageCreationInvalid) { const PremultipliedImage image_1x = decodeImage(util::read_file("test/fixtures/annotations/emerald.png")); - ASSERT_EQ(200u, image_1x.size.width); - ASSERT_EQ(299u, image_1x.size.height); + ASSERT_EQ(200u, image_1x.size().width); + ASSERT_EQ(299u, image_1x.size().height); ASSERT_EQ(nullptr, createStyleImage("test", image_1x, 0, 0, 0, 16, 1, false)); // width == 0 ASSERT_EQ(nullptr, createStyleImage("test", image_1x, 0, 0, 16, 0, 1, false)); // height == 0 @@ -38,8 +38,8 @@ TEST(Sprite, SpriteImageCreationInvalid) { ASSERT_EQ(nullptr, createStyleImage("test", image_1x, 0, 0, 16, 1025, 1, false)); // too tall ASSERT_EQ(nullptr, createStyleImage("test", image_1x, -1, 0, 16, 16, 1, false)); // srcX < 0 ASSERT_EQ(nullptr, createStyleImage("test", image_1x, 0, -1, 16, 16, 1, false)); // srcY < 0 - ASSERT_EQ(nullptr, createStyleImage("test", image_1x, 0, 0, image_1x.size.width + 1, 16, 1, false)); // right edge out of bounds - ASSERT_EQ(nullptr, createStyleImage("test", image_1x, 0, 0, 16, image_1x.size.height + 1, 1, false)); // bottom edge out of bounds + ASSERT_EQ(nullptr, createStyleImage("test", image_1x, 0, 0, image_1x.size().width + 1, 16, 1, false)); // right edge out of bounds + ASSERT_EQ(nullptr, createStyleImage("test", image_1x, 0, 0, 16, image_1x.size().height + 1, 1, false)); // bottom edge out of bounds EXPECT_EQ(1u, log.count({ EventSeverity::Error, @@ -137,14 +137,14 @@ TEST(Sprite, SpriteImageCreationInvalid) { TEST(Sprite, SpriteImageCreation1x) { const PremultipliedImage image_1x = decodeImage(util::read_file("test/fixtures/annotations/emerald.png")); - ASSERT_EQ(200u, image_1x.size.width); - ASSERT_EQ(299u, image_1x.size.height); + ASSERT_EQ(200u, image_1x.size().width); + ASSERT_EQ(299u, image_1x.size().height); { // "museum_icon":{"x":177,"y":187,"width":18,"height":18,"pixelRatio":1,"sdf":false} const auto sprite = createStyleImage("test", image_1x, 177, 187, 18, 18, 1, false); ASSERT_TRUE(sprite.get()); - EXPECT_EQ(18u, sprite->getImage().size.width); - EXPECT_EQ(18u, sprite->getImage().size.height); + EXPECT_EQ(18u, sprite->getImage().size().width); + EXPECT_EQ(18u, sprite->getImage().size().height); EXPECT_EQ(1, sprite->getPixelRatio()); EXPECT_EQ(readImage("test/fixtures/annotations/result-spriteimagecreation1x-museum.png"), sprite->getImage()); @@ -157,8 +157,8 @@ TEST(Sprite, SpriteImageCreation2x) { // "museum_icon":{"x":354,"y":374,"width":36,"height":36,"pixelRatio":2,"sdf":false} const auto sprite = createStyleImage("test", image_2x, 354, 374, 36, 36, 2, false); ASSERT_TRUE(sprite.get()); - EXPECT_EQ(36u, sprite->getImage().size.width); - EXPECT_EQ(36u, sprite->getImage().size.height); + EXPECT_EQ(36u, sprite->getImage().size().width); + EXPECT_EQ(36u, sprite->getImage().size().height); EXPECT_EQ(2, sprite->getPixelRatio()); EXPECT_EQ(readImage("test/fixtures/annotations/result-spriteimagecreation2x.png"), sprite->getImage()); @@ -170,8 +170,8 @@ TEST(Sprite, SpriteImageCreation1_5x) { // "museum_icon":{"x":354,"y":374,"width":36,"height":36,"pixelRatio":2,"sdf":false} const auto sprite = createStyleImage("test", image_2x, 354, 374, 36, 36, 1.5, false); ASSERT_TRUE(sprite.get()); - EXPECT_EQ(36u, sprite->getImage().size.width); - EXPECT_EQ(36u, sprite->getImage().size.height); + EXPECT_EQ(36u, sprite->getImage().size().width); + EXPECT_EQ(36u, sprite->getImage().size().height); EXPECT_EQ(1.5, sprite->getPixelRatio()); EXPECT_EQ(readImage("test/fixtures/annotations/result-spriteimagecreation1_5x-museum.png"), sprite->getImage()); @@ -179,8 +179,8 @@ TEST(Sprite, SpriteImageCreation1_5x) { // "hospital_icon":{"x":314,"y":518,"width":36,"height":36,"pixelRatio":2,"sdf":false} const auto sprite2 = createStyleImage("test", image_2x, 314, 518, 35, 35, 1.5, false); ASSERT_TRUE(sprite2.get()); - EXPECT_EQ(35u, sprite2->getImage().size.width); - EXPECT_EQ(35u, sprite2->getImage().size.height); + EXPECT_EQ(35u, sprite2->getImage().size().width); + EXPECT_EQ(35u, sprite2->getImage().size().height); EXPECT_EQ(1.5, sprite2->getPixelRatio()); EXPECT_EQ(readImage("test/fixtures/annotations/result-spriteimagecreation1_5x-hospital.png"), sprite2->getImage()); @@ -273,8 +273,8 @@ TEST(Sprite, SpriteParsing) { { auto& sprite = *std::find_if(images.begin(), images.end(), [] (const auto& image) { return image->getID() == "generic-metro"; }); - EXPECT_EQ(18u, sprite->getImage().size.width); - EXPECT_EQ(18u, sprite->getImage().size.height); + EXPECT_EQ(18u, sprite->getImage().size().width); + EXPECT_EQ(18u, sprite->getImage().size().height); EXPECT_EQ(1, sprite->getPixelRatio()); EXPECT_EQ(readImage("test/fixtures/annotations/result-spriteparsing.png"), sprite->getImage()); } diff --git a/test/src/mbgl/test/util.cpp b/test/src/mbgl/test/util.cpp index 30eea19bdf..5d3cccb464 100644 --- a/test/src/mbgl/test/util.cpp +++ b/test/src/mbgl/test/util.cpp @@ -109,23 +109,23 @@ void checkImage(const std::string& base, } PremultipliedImage expected = decodeImage(expected_image); - PremultipliedImage diff { expected.size }; + PremultipliedImage diff { expected.size() }; #if !TEST_READ_ONLY util::write_file(base + "/actual.png", encodePNG(actual)); #endif - ASSERT_EQ(expected.size, actual.size); + ASSERT_EQ(expected.size(), actual.size()); - double pixels = mapbox::pixelmatch(actual.data.get(), - expected.data.get(), - expected.size.width, - expected.size.height, - diff.data.get(), + double pixels = mapbox::pixelmatch(actual.data(), + expected.data(), + expected.size().width, + expected.size().height, + diff.data(), pixelThreshold); - EXPECT_LE(pixels / (expected.size.width * expected.size.height), imageThreshold); + EXPECT_LE(pixels / expected.size().area(), imageThreshold); #if !TEST_READ_ONLY util::write_file(base + "/diff.png", encodePNG(diff)); diff --git a/test/style/source.test.cpp b/test/style/source.test.cpp index abd4350231..a8059198d3 100644 --- a/test/style/source.test.cpp +++ b/test/style/source.test.cpp @@ -730,10 +730,10 @@ TEST(Source, ImageSourceImageUpdate) { // Load initial, so the source state will be loaded=true source.loadDescription(test.fileSource); PremultipliedImage rgba({ 1, 1 }); - rgba.data[0] = 255; - rgba.data[1] = 254; - rgba.data[2] = 253; - rgba.data[3] = 0; + rgba.data()[0] = 255; + rgba.data()[1] = 254; + rgba.data()[2] = 253; + rgba.data()[3] = 0; // Schedule an update test.loop.invoke([&] () { diff --git a/test/style/style_image.test.cpp b/test/style/style_image.test.cpp index e49bf37582..d703570e18 100644 --- a/test/style/style_image.test.cpp +++ b/test/style/style_image.test.cpp @@ -35,14 +35,14 @@ TEST(StyleImage, ZeroRatio) { TEST(StyleImage, Retina) { style::Image image("test", PremultipliedImage({ 32, 24 }), 2.0); - EXPECT_EQ(32u, image.getImage().size.width); - EXPECT_EQ(24u, image.getImage().size.height); + EXPECT_EQ(32u, image.getImage().size().width); + EXPECT_EQ(24u, image.getImage().size().height); EXPECT_EQ(2, image.getPixelRatio()); } TEST(StyleImage, FractionalRatio) { style::Image image("test", PremultipliedImage({ 20, 12 }), 1.5); - EXPECT_EQ(20u, image.getImage().size.width); - EXPECT_EQ(12u, image.getImage().size.height); + EXPECT_EQ(20u, image.getImage().size().width); + EXPECT_EQ(12u, image.getImage().size().height); EXPECT_EQ(1.5, image.getPixelRatio()); } diff --git a/test/text/glyph_manager.test.cpp b/test/text/glyph_manager.test.cpp index 3d7a220dc5..6cec11b80f 100644 --- a/test/text/glyph_manager.test.cpp +++ b/test/text/glyph_manager.test.cpp @@ -32,8 +32,9 @@ public: stub.metrics.left = 0; stub.metrics.top = -8; stub.metrics.advance = 24; - - stub.bitmap = AlphaImage(Size(30, 30), stubBitmap, stubBitmapLength); + std::unique_ptr imageData{std::make_unique(stubBitmapLength)}; + std::copy(stubBitmap, stubBitmap + stubBitmapLength, imageData.get()); + stub.bitmap = AlphaImage(Size(30, 30), std::move(imageData)); return stub; } @@ -236,11 +237,11 @@ TEST(GlyphManager, LoadLocalCJKGlyph) { EXPECT_EQ(glyph->metrics.left, 0); EXPECT_EQ(glyph->metrics.top, -8); EXPECT_EQ(glyph->metrics.advance, 24ul); - EXPECT_EQ(glyph->bitmap.size, Size(30, 30)); + EXPECT_EQ(glyph->bitmap.size(), Size(30, 30)); - size_t pixelCount = glyph->bitmap.size.width * glyph->bitmap.size.height; + size_t pixelCount = glyph->bitmap.size().area(); for (size_t i = 0; i < pixelCount; i++) { - EXPECT_EQ(glyph->bitmap.data[i], sdfBitmap[i]); + EXPECT_EQ(glyph->bitmap.data()[i], sdfBitmap[i]); } test.end(); diff --git a/test/text/glyph_pbf.test.cpp b/test/text/glyph_pbf.test.cpp index c222ec1dd9..770ed1f949 100644 --- a/test/text/glyph_pbf.test.cpp +++ b/test/text/glyph_pbf.test.cpp @@ -13,7 +13,7 @@ TEST(GlyphPBF, Parsing) { auto& sdf = sdfs[0]; EXPECT_EQ(69u, sdf.id); AlphaImage expected({7, 7}); - expected.fill('x'); + std::fill(expected.data(), expected.data() + expected.bytes(), 'x'); EXPECT_EQ(expected, sdf.bitmap); EXPECT_EQ(1u, sdf.metrics.width); EXPECT_EQ(1u, sdf.metrics.height); diff --git a/test/util/image.test.cpp b/test/util/image.test.cpp index c2922415cb..016ea9c905 100644 --- a/test/util/image.test.cpp +++ b/test/util/image.test.cpp @@ -8,150 +8,142 @@ using namespace mbgl; TEST(Image, PNGRoundTrip) { PremultipliedImage rgba({ 1, 1 }); - rgba.data[0] = 128; - rgba.data[1] = 0; - rgba.data[2] = 0; - rgba.data[3] = 255; + rgba.data()[0] = 128; + rgba.data()[1] = 0; + rgba.data()[2] = 0; + rgba.data()[3] = 255; PremultipliedImage image = decodeImage(encodePNG(rgba)); - EXPECT_EQ(128, image.data[0]); - EXPECT_EQ(0, image.data[1]); - EXPECT_EQ(0, image.data[2]); - EXPECT_EQ(255, image.data[3]); + EXPECT_EQ(128, image.data()[0]); + EXPECT_EQ(0, image.data()[1]); + EXPECT_EQ(0, image.data()[2]); + EXPECT_EQ(255, image.data()[3]); } TEST(Image, PNGRoundTripAlpha) { PremultipliedImage rgba({ 1, 1 }); - rgba.data[0] = 128; - rgba.data[1] = 0; - rgba.data[2] = 0; - rgba.data[3] = 128; + rgba.data()[0] = 128; + rgba.data()[1] = 0; + rgba.data()[2] = 0; + rgba.data()[3] = 128; PremultipliedImage image = decodeImage(encodePNG(rgba)); - EXPECT_EQ(128, image.data[0]); - EXPECT_EQ(0, image.data[1]); - EXPECT_EQ(0, image.data[2]); - EXPECT_EQ(128, image.data[3]); + EXPECT_EQ(128, image.data()[0]); + EXPECT_EQ(0, image.data()[1]); + EXPECT_EQ(0, image.data()[2]); + EXPECT_EQ(128, image.data()[3]); } TEST(Image, PNGReadNoProfile) { PremultipliedImage image = decodeImage(util::read_file("test/fixtures/image/no_profile.png")); - EXPECT_EQ(128, image.data[0]); - EXPECT_EQ(0, image.data[1]); - EXPECT_EQ(0, image.data[2]); - EXPECT_EQ(255, image.data[3]); + EXPECT_EQ(128, image.data()[0]); + EXPECT_EQ(0, image.data()[1]); + EXPECT_EQ(0, image.data()[2]); + EXPECT_EQ(255, image.data()[3]); } TEST(Image, PNGReadNoProfileAlpha) { PremultipliedImage image = decodeImage(util::read_file("test/fixtures/image/no_profile_alpha.png")); - EXPECT_EQ(64, image.data[0]); - EXPECT_EQ(0, image.data[1]); - EXPECT_EQ(0, image.data[2]); - EXPECT_EQ(128, image.data[3]); + EXPECT_EQ(64, image.data()[0]); + EXPECT_EQ(0, image.data()[1]); + EXPECT_EQ(0, image.data()[2]); + EXPECT_EQ(128, image.data()[3]); } TEST(Image, PNGReadProfile) { PremultipliedImage image = decodeImage(util::read_file("test/fixtures/image/profile.png")); - EXPECT_EQ(128, image.data[0]); - EXPECT_EQ(0, image.data[1]); - EXPECT_EQ(0, image.data[2]); - EXPECT_EQ(255, image.data[3]); + EXPECT_EQ(128, image.data()[0]); + EXPECT_EQ(0, image.data()[1]); + EXPECT_EQ(0, image.data()[2]); + EXPECT_EQ(255, image.data()[3]); } TEST(Image, PNGReadProfileAlpha) { PremultipliedImage image = decodeImage(util::read_file("test/fixtures/image/profile_alpha.png")); - EXPECT_EQ(64, image.data[0]); - EXPECT_EQ(0, image.data[1]); - EXPECT_EQ(0, image.data[2]); - EXPECT_EQ(128, image.data[3]); + EXPECT_EQ(64, image.data()[0]); + EXPECT_EQ(0, image.data()[1]); + EXPECT_EQ(0, image.data()[2]); + EXPECT_EQ(128, image.data()[3]); } TEST(Image, PNGTile) { PremultipliedImage image = decodeImage(util::read_file("test/fixtures/image/tile.png")); - EXPECT_EQ(256u, image.size.width); - EXPECT_EQ(256u, image.size.height); + EXPECT_EQ(256u, image.size().width); + EXPECT_EQ(256u, image.size().height); } TEST(Image, JPEGTile) { PremultipliedImage image = decodeImage(util::read_file("test/fixtures/image/tile.jpeg")); - EXPECT_EQ(256u, image.size.width); - EXPECT_EQ(256u, image.size.height); + EXPECT_EQ(256u, image.size().width); + EXPECT_EQ(256u, image.size().height); } TEST(Image, Resize) { AlphaImage image({0, 0}); image.resize({1, 1}); - EXPECT_EQ(image.size, Size({1, 1})); + EXPECT_EQ(image.size(), Size({1, 1})); - image.fill(100); + std::fill(image.data(), image.data() + image.bytes(), 100); image.resize({2, 1}); - EXPECT_EQ(image.size, Size({2, 1})); - EXPECT_EQ(image.data[0], 100); - EXPECT_EQ(image.data[1], 0); + EXPECT_EQ(image.size(), Size({2, 1})); + EXPECT_EQ(image.data()[0], 100); + EXPECT_EQ(image.data()[1], 0); image.resize({0, 0}); - EXPECT_EQ(image.size, Size({0, 0})); + EXPECT_EQ(image.size(), Size({0, 0})); + EXPECT_EQ(image.data(), nullptr); + EXPECT_FALSE(image.valid()); } -TEST(Image, Copy) { - PremultipliedImage src5({5, 5}); - PremultipliedImage dst5({5, 5}); - PremultipliedImage src10({10, 10}); - PremultipliedImage dst10({10, 10}); - - EXPECT_THROW(PremultipliedImage::copy(src5, dst10, {0, 0}, {0, 0}, {6, 1}), std::out_of_range); - EXPECT_THROW(PremultipliedImage::copy(src5, dst10, {0, 0}, {0, 0}, {1, 6}), std::out_of_range); - EXPECT_THROW(PremultipliedImage::copy(src5, dst10, {1, 1}, {0, 0}, {5, 1}), std::out_of_range); - EXPECT_THROW(PremultipliedImage::copy(src5, dst10, {1, 1}, {0, 0}, {1, 5}), std::out_of_range); - - EXPECT_THROW(PremultipliedImage::copy(src10, dst5, {0, 0}, {0, 0}, {6, 1}), std::out_of_range); - EXPECT_THROW(PremultipliedImage::copy(src10, dst5, {0, 0}, {0, 0}, {1, 6}), std::out_of_range); - EXPECT_THROW(PremultipliedImage::copy(src10, dst5, {0, 0}, {1, 1}, {5, 1}), std::out_of_range); - EXPECT_THROW(PremultipliedImage::copy(src10, dst5, {0, 0}, {1, 1}, {1, 5}), std::out_of_range); - - const uint32_t max = std::numeric_limits::max(); - - EXPECT_THROW(PremultipliedImage::copy(src10, dst10, {max, 0}, {0, 0}, {1, 1}), std::out_of_range); - EXPECT_THROW(PremultipliedImage::copy(src10, dst10, {0, max}, {0, 0}, {1, 1}), std::out_of_range); - EXPECT_THROW(PremultipliedImage::copy(src10, dst10, {0, 0}, {max, 0}, {1, 1}), std::out_of_range); - EXPECT_THROW(PremultipliedImage::copy(src10, dst10, {0, 0}, {0, max}, {1, 1}), std::out_of_range); - - EXPECT_THROW(PremultipliedImage::copy(src10, dst10, {1, 0}, {0, 0}, {max, 1}), std::out_of_range); - EXPECT_THROW(PremultipliedImage::copy(src10, dst10, {0, 1}, {0, 0}, {1, max}), std::out_of_range); - EXPECT_THROW(PremultipliedImage::copy(src10, dst10, {0, 0}, {1, 0}, {max, 1}), std::out_of_range); - EXPECT_THROW(PremultipliedImage::copy(src10, dst10, {0, 0}, {0, 1}, {1, max}), std::out_of_range); +TEST(Image, TakeData) { + UnassociatedImage rgba({ 1, 1 }); + rgba.data()[0] = 255; + rgba.data()[1] = 254; + rgba.data()[2] = 253; + rgba.data()[3] = 128; + + auto data = rgba.takeData(); + + EXPECT_EQ(255, data[0]); + EXPECT_EQ(254, data[1]); + EXPECT_EQ(253, data[2]); + EXPECT_EQ(128, data[3]); + + EXPECT_EQ(Size(), rgba.size()); + EXPECT_EQ(nullptr, rgba.data()); + EXPECT_FALSE(rgba.valid()); } TEST(Image, Move) { UnassociatedImage rgba({ 1, 1 }); - rgba.data[0] = 255; - rgba.data[1] = 254; - rgba.data[2] = 253; - rgba.data[3] = 128; + rgba.data()[0] = 255; + rgba.data()[1] = 254; + rgba.data()[2] = 253; + rgba.data()[3] = 128; auto moved = std::move(rgba); - EXPECT_EQ(0u, rgba.size.width); - EXPECT_EQ(nullptr, rgba.data.get()); - EXPECT_EQ(254, moved.data[1]); - EXPECT_EQ(1u, moved.size.width); + EXPECT_EQ(0u, rgba.size().width); + EXPECT_EQ(nullptr, rgba.data()); + EXPECT_EQ(254, moved.data()[1]); + EXPECT_EQ(1u, moved.size().width); } TEST(Image, Premultiply) { UnassociatedImage rgba({ 1, 1 }); - rgba.data[0] = 255; - rgba.data[1] = 254; - rgba.data[2] = 253; - rgba.data[3] = 128; + rgba.data()[0] = 255; + rgba.data()[1] = 254; + rgba.data()[2] = 253; + rgba.data()[3] = 128; PremultipliedImage image = util::premultiply(std::move(rgba)); - EXPECT_EQ(128, image.data[0]); - EXPECT_EQ(127, image.data[1]); - EXPECT_EQ(127, image.data[2]); - EXPECT_EQ(128, image.data[3]); - EXPECT_EQ(1u, image.size.width); - EXPECT_EQ(1u, image.size.height); - EXPECT_EQ(0u, rgba.size.width); - EXPECT_EQ(0u, rgba.size.height); + EXPECT_EQ(128, image.data()[0]); + EXPECT_EQ(127, image.data()[1]); + EXPECT_EQ(127, image.data()[2]); + EXPECT_EQ(128, image.data()[3]); + EXPECT_EQ(1u, image.size().width); + EXPECT_EQ(1u, image.size().height); + EXPECT_EQ(0u, rgba.size().width); + EXPECT_EQ(0u, rgba.size().height); } -- cgit v1.2.1