From 94f011895c8e1bde36ee2ec235dbbcf2c994ac4c Mon Sep 17 00:00:00 2001 From: John Firebaugh Date: Fri, 10 Feb 2017 17:18:18 -0800 Subject: [core] Make Image safer Provide Image::copy, which handles copying rectangles from a source to a destination, with thorough bounds checking. Also fixes an indexing error in SpriteAtlas, where the top row of pixels in a wrapped image was copied from the wrong source row. --- include/mbgl/util/image.hpp | 65 ++++++++++++++++++++++++++++++++++++++++++--- include/mbgl/util/size.hpp | 4 +++ 2 files changed, 65 insertions(+), 4 deletions(-) (limited to 'include') diff --git a/include/mbgl/util/image.hpp b/include/mbgl/util/image.hpp index 2de7646837..5d1462e7c4 100644 --- a/include/mbgl/util/image.hpp +++ b/include/mbgl/util/image.hpp @@ -1,6 +1,7 @@ #pragma once #include +#include #include #include @@ -24,6 +25,15 @@ public: : 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_)) {} @@ -38,10 +48,13 @@ public: return *this; } - bool operator==(const Image& rhs) const { - return size == rhs.size && - std::equal(data.get(), data.get() + bytes(), rhs.data.get(), - rhs.data.get() + rhs.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 { @@ -58,6 +71,50 @@ public: 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); + } + + // 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. + static void copy(const Image& srcImg, Image& dstImg, const Point& srcPt, const Point& dstPt, const Size& size) { + 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); + } + } + Size size; static constexpr size_t channels = Mode == ImageAlphaMode::Exclusive ? 1 : 4; std::unique_ptr data; diff --git a/include/mbgl/util/size.hpp b/include/mbgl/util/size.hpp index 1af85bcff5..79679a92fb 100644 --- a/include/mbgl/util/size.hpp +++ b/include/mbgl/util/size.hpp @@ -13,6 +13,10 @@ public: constexpr Size(const uint32_t width_, const uint32_t height_) : width(width_), height(height_) { } + constexpr uint32_t area() const { + return width * height; + } + constexpr explicit operator bool() const { return width > 0 && height > 0; } -- cgit v1.2.1