Monopoly — Portage moderne 0.1.0
Portage moderne C/C++23 du Monopoly de 1999
Loading...
Searching...
No Matches
RuntimeBitmapSurface.cpp
Go to the documentation of this file.
2
3#include <algorithm>
4#include <limits>
5#include <cmath>
6
7namespace monopoly::data
8{
9 namespace
10 {
11 [[nodiscard]] bool validImage(const LegacyBitmapRGBA8& image) noexcept
12 {
13 if (!image.width || !image.height) return false;
14 const auto count = static_cast<std::uint64_t>(image.width) * image.height;
16 image.pixels.size() == static_cast<std::size_t>(count) * 4U;
17 }
18
19 [[nodiscard]] std::uint8_t sourceOverChannel(
20 std::uint8_t source, std::uint8_t destination,
21 std::uint32_t sourceAlpha, std::uint32_t destinationAlpha,
22 std::uint32_t outputAlpha) noexcept
23 {
24 if (!outputAlpha) return 0;
25 const auto destinationContribution =
26 (static_cast<std::uint32_t>(destination) * destinationAlpha *
27 (255U - sourceAlpha) + 127U) / 255U;
28 const auto premultiplied =
29 static_cast<std::uint32_t>(source) * sourceAlpha +
31 return static_cast<std::uint8_t>(std::min(
32 (premultiplied + outputAlpha / 2U) / outputAlpha, 255U));
33 }
34 }
35
36 bool isRuntimeBitmapDataId(DataId id) noexcept
37 {
38 return dataGroup(id) == RuntimeBitmapGroup && dataTag(id) != 0;
39 }
40
41 std::expected<void, std::string> blitStraightRGBA8(
43 std::int32_t destinationX, std::int32_t destinationY, BitmapBlitMode mode)
44 {
46 return std::unexpected("invalid straight RGBA8 blit image");
47
48 const auto left = std::max<std::int64_t>(0, destinationX);
49 const auto top = std::max<std::int64_t>(0, destinationY);
50 const auto right = std::min<std::int64_t>(destination.width,
51 static_cast<std::int64_t>(destinationX) + source.width);
52 const auto bottom = std::min<std::int64_t>(destination.height,
53 static_cast<std::int64_t>(destinationY) + source.height);
54 if (left >= right || top >= bottom) return {};
55
56 for (std::int64_t y = top; y < bottom; ++y)
57 {
58 const auto sourceY = y - destinationY;
59 for (std::int64_t x = left; x < right; ++x)
60 {
61 const auto sourceX = x - destinationX;
62 const auto sourceOffset =
63 (static_cast<std::size_t>(sourceY) * source.width +
64 static_cast<std::size_t>(sourceX)) * 4U;
65 const auto destinationOffset =
66 (static_cast<std::size_t>(y) * destination.width +
67 static_cast<std::size_t>(x)) * 4U;
68
69 if (mode == BitmapBlitMode::Replace)
70 {
71 std::copy_n(source.pixels.data() + sourceOffset, 4,
72 destination.pixels.data() + destinationOffset);
73 continue;
74 }
75
76 const auto sourceAlpha = source.pixels[sourceOffset + 3];
77 if (!sourceAlpha) continue;
78 const auto destinationAlpha = destination.pixels[destinationOffset + 3];
79 const auto outputAlpha = sourceAlpha +
80 (static_cast<std::uint32_t>(destinationAlpha) *
81 (255U - sourceAlpha) + 127U) / 255U;
82 for (std::size_t channel = 0; channel < 3; ++channel)
84 source.pixels[sourceOffset + channel],
87 destination.pixels[destinationOffset + 3] =
88 static_cast<std::uint8_t>(outputAlpha);
89 }
90 }
91 return {};
92 }
93
94 void RuntimeBitmapStore::publish(DataId id, Surface& surface)
95 {
96 auto image = surface.baseImage;
97
98 for (std::size_t offset = 0; offset < image.pixels.size(); offset += 4U)
99 {
100 auto& alpha = image.pixels[offset + 3U];
101
102 if (!surface.colourKeyTransparent)
103 {
104 alpha = 255;
105 continue;
106 }
107
108 const bool pureGreen =
109 image.pixels[offset] == 0U &&
110 image.pixels[offset + 1U] == 255U &&
111 image.pixels[offset + 2U] == 0U;
112 if (pureGreen)
113 {
114 alpha = 0;
115 continue;
116 }
117
118 alpha = static_cast<std::uint8_t>(
119 static_cast<std::uint32_t>(alpha) * surface.globalAlpha / 255U);
120 }
121
122 surface.published = std::make_shared<const BitmapRuntimeAsset>(
123 BitmapRuntimeAsset{id, LegacyDataType::Native, {}, std::move(image),
124 surface.preferLinearFiltering, surface.presentationRect});
125 }
126
127 std::expected<DataId, std::string> RuntimeBitmapStore::create(
128 std::uint32_t width, std::uint32_t height, bool transparent,
129 std::uint8_t globalAlpha)
130 {
131 const auto count = static_cast<std::uint64_t>(width) * height;
132 if (!width || !height || count > 16U * 1024U * 1024U ||
133 count > std::numeric_limits<std::size_t>::max() / 4U)
134 return std::unexpected("runtime bitmap dimensions exceed allocation budget");
135 if (nextTag_ > std::numeric_limits<DataTag>::max())
136 return std::unexpected("runtime bitmap DataID namespace exhausted");
137
138 const auto id = packDataId(RuntimeBitmapGroup,
139 static_cast<DataTag>(nextTag_++));
141 image.pixels.resize(static_cast<std::size_t>(count) * 4U);
142
143 for (std::size_t offset = 0; offset < image.pixels.size(); offset += 4U)
144 {
145 image.pixels[offset] = 0;
146 image.pixels[offset + 1U] = transparent ? 255 : 0;
147 image.pixels[offset + 2U] = 0;
148 image.pixels[offset + 3U] = transparent ? 0 : 255;
149 }
150
151 Surface surface{std::move(image), transparent, globalAlpha, {}, {width,height}};
152 publish(id, surface);
153 surfaces_.emplace(id, std::move(surface));
154 return id;
155 }
156
157 std::expected<void, std::string> RuntimeBitmapStore::update(
159 std::optional<std::array<float, 4>> presentationRect,
160 bool preferLinearFiltering)
161 {
162 const auto found = surfaces_.find(id);
163 if (found == surfaces_.end())
164 return std::unexpected("runtime bitmap DataID is not allocated");
165 const auto logical = found->second.logicalExtent;
166 if (!validImage(image) || std::uint64_t(image.width) * image.height > 16U * 1024U * 1024U)
167 return std::unexpected("runtime bitmap replacement extent mismatch");
168 if (presentationRect)
169 {
170 // Presentation may supersample the allocated surface, but never
171 // move or resize its original logical rectangle.
172 const auto& rect = *presentationRect;
173 const unsigned scale = image.width / logical.width;
174 if (!std::ranges::all_of(rect, [](float v) { return std::isfinite(v); }) ||
175 rect != std::array<float,4>{0,0,float(logical.width),float(logical.height)} ||
176 scale < 1 || scale > 4 || image.width != logical.width * scale ||
177 image.height != logical.height * scale)
178 return std::unexpected("invalid runtime bitmap presentation extent");
179 }
180 else if (image.width != logical.width || image.height != logical.height || preferLinearFiltering)
181 return std::unexpected("runtime bitmap replacement extent mismatch");
182
183 found->second.presentationRect = presentationRect;
184 found->second.preferLinearFiltering = preferLinearFiltering;
185 found->second.baseImage = std::move(image);
186 publish(id, found->second);
187 return {};
188 }
189
190 std::expected<void, std::string> RuntimeBitmapStore::fill(
191 DataId id, std::int32_t x, std::int32_t y,
192 std::int32_t width, std::int32_t height,
193 std::uint32_t colorRef)
194 {
195 const auto found = surfaces_.find(id);
196 if (found == surfaces_.end())
197 return std::unexpected("runtime bitmap DataID is not allocated");
198 if (width <= 0 || height <= 0)
199 return {};
200
201 auto image = found->second.baseImage;
202 const auto left = std::max<std::int64_t>(0, x);
203 const auto top = std::max<std::int64_t>(0, y);
204 const auto right = std::min<std::int64_t>(
205 image.width, static_cast<std::int64_t>(x) + width);
206 const auto bottom = std::min<std::int64_t>(
207 image.height, static_cast<std::int64_t>(y) + height);
208 if (left >= right || top >= bottom)
209 return {};
210
211 const auto red = static_cast<std::uint8_t>(colorRef & 0xFFU);
212 const auto green = static_cast<std::uint8_t>((colorRef >> 8U) & 0xFFU);
213 const auto blue = static_cast<std::uint8_t>((colorRef >> 16U) & 0xFFU);
214 const bool keyed = found->second.colourKeyTransparent &&
215 red == 0U && green == 255U && blue == 0U;
216 const auto alpha = static_cast<std::uint8_t>(keyed ? 0U : 255U);
217
218 for (std::int64_t row = top; row < bottom; ++row)
219 {
220 for (std::int64_t column = left; column < right; ++column)
221 {
222 const auto offset =
223 (static_cast<std::size_t>(row) * image.width +
224 static_cast<std::size_t>(column)) * 4U;
225 image.pixels[offset] = red;
226 image.pixels[offset + 1U] = green;
227 image.pixels[offset + 2U] = blue;
228 image.pixels[offset + 3U] = alpha;
229 }
230 }
231
232 found->second.baseImage = std::move(image);
233 publish(id, found->second);
234 return {};
235 }
236
237 std::expected<void, std::string> RuntimeBitmapStore::blit(
239 std::int32_t x, std::int32_t y, BitmapBlitMode mode)
240 {
241 const auto found = surfaces_.find(id);
242 if (found == surfaces_.end())
243 return std::unexpected("runtime bitmap DataID is not allocated");
244
245 auto image = found->second.baseImage;
246 const auto result = blitStraightRGBA8(image, source, x, y, mode);
247 if (!result)
248 return result;
249
250 found->second.baseImage = std::move(image);
251 publish(id, found->second);
252 return {};
253 }
254
255 std::expected<void, std::string> RuntimeBitmapStore::setGlobalAlpha(
256 DataId id, std::uint8_t alpha)
257 {
258 const auto found = surfaces_.find(id);
259 if (found == surfaces_.end())
260 return std::unexpected("runtime bitmap DataID is not allocated");
261
262 found->second.globalAlpha = alpha;
263 publish(id, found->second);
264 return {};
265 }
266
267 std::optional<RuntimeBitmapExtent> RuntimeBitmapStore::extent(
268 DataId id) const noexcept
269 {
270 const auto found = surfaces_.find(id);
271 if (found == surfaces_.end())
272 return std::nullopt;
273 return found->second.logicalExtent;
274 }
275
276 std::optional<std::uint8_t> RuntimeBitmapStore::globalAlpha(
277 DataId id) const noexcept
278 {
279 const auto found = surfaces_.find(id);
280 if (found == surfaces_.end())
281 return std::nullopt;
282 return found->second.globalAlpha;
283 }
284
285 std::shared_ptr<const BitmapRuntimeAsset> RuntimeBitmapStore::asset(
286 DataId id) const noexcept
287 {
288 const auto found = surfaces_.find(id);
289 return found == surfaces_.end() ? nullptr : found->second.published;
290 }
291
293 {
294 return surfaces_.erase(id) != 0;
295 }
296
298 {
299 surfaces_.clear();
300 }
301}
std::size_t count
std::size_t offset
std::atomic< float > left
std::atomic< float > right
LegacyBitmapRGBA8 image
int bottom
int top
data::DataId id
std::shared_ptr< const data::HmdTextureImage > source
std::uint32_t height
std::uint32_t width
unsigned index
Rect rect
std::expected< void, std::string > blit(DataId id, const LegacyBitmapRGBA8 &source, std::int32_t x, std::int32_t y, BitmapBlitMode mode=BitmapBlitMode::SourceOver)
std::optional< RuntimeBitmapExtent > extent(DataId id) const noexcept
std::optional< std::uint8_t > globalAlpha(DataId id) const noexcept
std::expected< void, std::string > update(DataId id, LegacyBitmapRGBA8 image, std::optional< std::array< float, 4 > > presentationRect={}, bool preferLinearFiltering=false)
std::expected< void, std::string > setGlobalAlpha(DataId id, std::uint8_t alpha)
std::shared_ptr< const BitmapRuntimeAsset > asset(DataId id) const noexcept
std::expected< DataId, std::string > create(std::uint32_t width, std::uint32_t height, bool transparent, std::uint8_t globalAlpha=255)
std::expected< void, std::string > fill(DataId id, std::int32_t x, std::int32_t y, std::int32_t width, std::int32_t height, std::uint32_t colorRef)
std::uint32_t DataId
Definition DataBanks.hpp:10
constexpr DataTag dataTag(DataId id) noexcept
Definition DataBanks.hpp:64
std::uint16_t DataTag
Definition DataBanks.hpp:11
std::expected< void, std::string > blitStraightRGBA8(LegacyBitmapRGBA8 &destination, const LegacyBitmapRGBA8 &source, std::int32_t destinationX, std::int32_t destinationY, BitmapBlitMode mode)
bool isRuntimeBitmapDataId(DataId id) noexcept
constexpr DataId packDataId(std::uint16_t group, DataTag tag) noexcept
Definition DataBanks.hpp:39
constexpr std::uint16_t RuntimeBitmapGroup
constexpr std::uint16_t dataGroup(DataId id) noexcept
Definition DataBanks.hpp:57
std::vector< std::uint8_t > pixels