summaryrefslogtreecommitdiff
path: root/include/mbgl/util/image.hpp
blob: cb28f3da8dad2bf3b4705df14d243dab212c468c (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
#pragma once

#include <mbgl/util/noncopyable.hpp>
#include <mbgl/util/geometry.hpp>
#include <mbgl/util/size.hpp>

#include <string>
#include <memory>
#include <algorithm>

namespace mbgl {

enum class ImageAlphaMode {
    Unassociated,
    Premultiplied,
    Exclusive, // Alpha-channel only
};

template <ImageAlphaMode Mode>
class Image : private util::noncopyable {
public:
    Image() = default;

    Image(Size size_)
        : size(std::move(size_)),
          data(std::make_unique<uint8_t[]>(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<uint8_t[]>(bytes());
        std::copy(srcData, srcData + srcLength, data.get());
    }

    Image(Size size_, std::unique_ptr<uint8_t[]> 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& operator=(Image&& o) {
        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.get() != nullptr;
    }

    template <typename T = Image>
    T clone() const {
        T copy_(size);
        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) {
        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));
    }

    // 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 mbgl