Files
MapLibreParticleLab/include/mln/util/image.hpp
Bart Louwers 2a8ebc4906 core: rename mbgl directories to mln (#4511)
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2026-08-22 20:50:14 +02:00

183 lines
5.8 KiB
C++

#pragma once
#include <mln/util/noncopyable.hpp>
#include <mln/util/geometry.hpp>
#include <mln/util/size.hpp>
#include <string>
#include <cstring>
#include <memory>
#include <algorithm>
namespace mln {
enum class ImageAlphaMode {
Unassociated,
Premultiplied,
Exclusive, ///< Alpha-channel only
};
template <ImageAlphaMode Mode>
class Image : private util::noncopyable {
public:
Image() = default;
Image(Size size_)
: size(size_),
data(std::make_unique<uint8_t[]>(bytes())) {}
Image(Size size_, const uint8_t* srcData, std::size_t srcLength)
: size(size_) {
if (srcLength != bytes()) {
throw std::invalid_argument("mismatched image size");
}
if (bytes()) {
data = std::make_unique<uint8_t[]>(bytes());
std::copy(srcData, srcData + srcLength, data.get());
}
}
Image(Size size_, std::unique_ptr<uint8_t[]> data_)
: size(size_),
data(std::move(data_)) {}
Image(Image&& o) noexcept
: size(o.size),
data(std::move(o.data)) {
o.size.width = o.size.height = 0;
}
Image& operator=(Image&& o) noexcept {
size = o.size;
data = std::move(o.data);
o.size.width = o.size.height = 0;
return *this;
}
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 != nullptr; }
template <typename T = Image>
T clone() const {
T copy_(size);
if (valid() && copy_.valid()) {
assert(bytes() == copy_.bytes());
std::copy(data.get(), data.get() + bytes(), copy_.data.get());
}
return copy_;
}
size_t stride() const { return channels * size.width; }
size_t bytes() const { return stride() * size.height; }
void fill(uint8_t value) {
if (valid()) {
assert(bytes());
std::fill(data.get(), data.get() + bytes(), value);
}
}
void resize(Size size_) {
if (size == size_) {
return;
}
Image newImage(size_);
if (!size_.isEmpty() && valid()) {
assert(bytes()); // gcc12 bug
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<uint32_t>& 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.
static void copy(const Image& srcImg,
Image& dstImg,
const Point<uint32_t>& srcPt,
const Point<uint32_t>& 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);
}
}
Size size;
static constexpr size_t channels = Mode == ImageAlphaMode::Exclusive ? 1 : 4;
std::unique_ptr<uint8_t[]> data;
};
using UnassociatedImage = Image<ImageAlphaMode::Unassociated>;
using PremultipliedImage = Image<ImageAlphaMode::Premultiplied>;
using AlphaImage = Image<ImageAlphaMode::Exclusive>;
// TODO: don't use std::string for binary data.
PremultipliedImage decodeImage(const std::string&);
std::string encodePNG(const PremultipliedImage&);
} // namespace mln