overlay/font.cpp
| Line | Branch | Exec | Source |
|---|---|---|---|
| 1 | // SPDX-License-Identifier: BSD-3-Clause | ||
| 2 | #include <stb_truetype.h> | ||
| 3 | |||
| 4 | #include <algorithm> | ||
| 5 | #include <cmath> | ||
| 6 | #include <cstddef> | ||
| 7 | #include <optional> | ||
| 8 | #include <vector> | ||
| 9 | |||
| 10 | #include <dlpack/dlpack.h> | ||
| 11 | |||
| 12 | #include "chrono_batch_render/device/device.h" | ||
| 13 | #include "chrono_batch_render/device/stream.h" | ||
| 14 | #include "chrono_batch_render/overlay/overlay_impl.h" | ||
| 15 | #include "chrono_batch_render/util/error.h" | ||
| 16 | |||
| 17 | extern "C" { | ||
| 18 | extern const unsigned char kChronoBatchRenderFont_inter[]; | ||
| 19 | extern const std::size_t kChronoBatchRenderFont_inter_size; | ||
| 20 | } | ||
| 21 | |||
| 22 | namespace chrono::batch_render { | ||
| 23 | namespace { | ||
| 24 | |||
| 25 | constexpr uint32_t kGposKern = 0x6b65726e; | ||
| 26 | |||
| 27 | 265 | bool Printable(uint32_t code) { | |
| 28 | 265 | return (code >= 32 && code < 127) || (code >= 160 && code < 256); | |
| 29 | } | ||
| 30 | |||
| 31 | 296961 | int BitCount(uint32_t bits) { | |
| 32 | 296961 | int count = 0; | |
| 33 | 500595 | for (; bits != 0; bits &= bits - 1) { | |
| 34 | 203634 | ++count; | |
| 35 | } | ||
| 36 | 296961 | return count; | |
| 37 | } | ||
| 38 | |||
| 39 | class Table { | ||
| 40 | public: | ||
| 41 | 336354 | Table(const unsigned char* data, size_t size, size_t at) | |
| 42 | 336354 | : m_data(data), m_size(size), m_at(at) {} | |
| 43 | |||
| 44 | 1208499 | uint16_t U16(size_t offset) const { | |
| 45 | 1208499 | const size_t at = m_at + offset; | |
| 46 | 1208499 | return at + 2 <= m_size ? uint16_t((m_data[at] << 8) | m_data[at + 1]) : 0; | |
| 47 | } | ||
| 48 | |||
| 49 | 61967 | int16_t S16(size_t offset) const { | |
| 50 | 61967 | return int16_t(U16(offset)); | |
| 51 | } | ||
| 52 | |||
| 53 | 8 | uint32_t U32(size_t offset) const { | |
| 54 | 8 | return (uint32_t{U16(offset)} << 16) | U16(offset + 2); | |
| 55 | } | ||
| 56 | |||
| 57 | 336354 | Table Subtable(size_t offset) const { | |
| 58 | 336354 | return {m_data, m_size, m_at + offset}; | |
| 59 | } | ||
| 60 | |||
| 61 | private: | ||
| 62 | const unsigned char* m_data; | ||
| 63 | size_t m_size; | ||
| 64 | size_t m_at; | ||
| 65 | }; | ||
| 66 | |||
| 67 | 1568 | std::optional<uint16_t> CoverageIndex(const Table& coverage, uint16_t glyph) { | |
| 68 | 1568 | const uint16_t count = coverage.U16(2); | |
| 69 | 1568 | if (coverage.U16(0) == 1) { | |
| 70 | 31163 | for (uint16_t i = 0; i < count; ++i) { | |
| 71 | 30532 | if (coverage.U16(4 + 2u * i) == glyph) { | |
| 72 | 41 | return i; | |
| 73 | } | ||
| 74 | } | ||
| 75 | 896 | } else if (coverage.U16(0) == 2) { | |
| 76 | 74675 | for (uint16_t i = 0; i < count; ++i) { | |
| 77 | 74264 | const Table range = coverage.Subtable(4 + 6u * i); | |
| 78 | 74264 | if (glyph >= range.U16(0) && glyph <= range.U16(2)) { | |
| 79 | 485 | return uint16_t(range.U16(4) + glyph - range.U16(0)); | |
| 80 | } | ||
| 81 | } | ||
| 82 | } | ||
| 83 | 1042 | return std::nullopt; | |
| 84 | } | ||
| 85 | |||
| 86 | 896 | uint16_t GlyphClass(const Table& classes, uint16_t glyph) { | |
| 87 | 896 | if (classes.U16(0) == 1) { | |
| 88 | ✗ | const uint16_t first = classes.U16(2); | |
| 89 | ✗ | if (glyph >= first && glyph - first < classes.U16(4)) { | |
| 90 | ✗ | return classes.U16(6 + 2u * (glyph - first)); | |
| 91 | } | ||
| 92 | 896 | } else if (classes.U16(0) == 2) { | |
| 93 | 896 | const uint16_t count = classes.U16(2); | |
| 94 | 143693 | for (uint16_t i = 0; i < count; ++i) { | |
| 95 | 143334 | const Table range = classes.Subtable(4 + 6u * i); | |
| 96 | 143334 | if (glyph >= range.U16(0) && glyph <= range.U16(2)) { | |
| 97 | 537 | return range.U16(4); | |
| 98 | } | ||
| 99 | } | ||
| 100 | } | ||
| 101 | return 0; | ||
| 102 | } | ||
| 103 | |||
| 104 | 61967 | int16_t XAdvance(const Table& record, uint16_t format) { | |
| 105 | 61967 | return (format & 4) != 0 ? record.S16(2 * size_t(BitCount(format & 3u))) : 0; | |
| 106 | } | ||
| 107 | |||
| 108 | 234994 | size_t RecordSize(uint16_t format) { | |
| 109 | 234994 | return 2 * size_t(BitCount(format & 0xFFu)); | |
| 110 | } | ||
| 111 | |||
| 112 | struct PairTable { | ||
| 113 | Table sub; | ||
| 114 | std::vector<int32_t> index; | ||
| 115 | std::vector<uint16_t> firstClasses; | ||
| 116 | std::vector<uint16_t> secondClasses; | ||
| 117 | }; | ||
| 118 | |||
| 119 | 7 | PairTable BuildPairTable(const Table& sub, const std::vector<uint16_t>& glyphs) { | |
| 120 | 7 | PairTable table{sub, {}, {}, {}}; | |
| 121 | 7 | const Table coverage = sub.Subtable(sub.U16(2)); | |
| 122 | 1575 | for (const uint16_t glyph : glyphs) { | |
| 123 | 1568 | const std::optional<uint16_t> index = CoverageIndex(coverage, glyph); | |
| 124 | 1568 | table.index.push_back(index ? *index : -1); | |
| 125 | 1568 | if (sub.U16(0) == 2) { | |
| 126 | 448 | table.firstClasses.push_back(GlyphClass(sub.Subtable(sub.U16(8)), glyph)); | |
| 127 | 448 | table.secondClasses.push_back(GlyphClass(sub.Subtable(sub.U16(10)), glyph)); | |
| 128 | } | ||
| 129 | } | ||
| 130 | 7 | return table; | |
| 131 | ✗ | } | |
| 132 | |||
| 133 | 55865 | std::optional<int16_t> PairSetAdjustment(const Table& set, uint16_t second, uint16_t format, | |
| 134 | size_t stride) { | ||
| 135 | 55865 | size_t low = 0; | |
| 136 | 55865 | size_t high = set.U16(0); | |
| 137 | 233671 | while (low < high) { | |
| 138 | 122276 | const size_t middle = (low + high) / 2; | |
| 139 | 122276 | const uint16_t glyph = set.U16(2 + middle * stride); | |
| 140 | 122276 | if (glyph == second) { | |
| 141 | 335 | return XAdvance(set.Subtable(4 + middle * stride), format); | |
| 142 | } | ||
| 143 | 121941 | if (glyph < second) { | |
| 144 | 46732 | low = middle + 1; | |
| 145 | } else { | ||
| 146 | high = middle; | ||
| 147 | } | ||
| 148 | } | ||
| 149 | 55530 | return std::nullopt; | |
| 150 | } | ||
| 151 | |||
| 152 | 350676 | std::optional<int16_t> PairAdjustment(const PairTable& pairs, size_t a, size_t b, uint16_t second) { | |
| 153 | 350676 | if (pairs.index[a] < 0) { | |
| 154 | 233179 | return std::nullopt; | |
| 155 | } | ||
| 156 | 117497 | const Table& sub = pairs.sub; | |
| 157 | 117497 | const auto index = uint16_t(pairs.index[a]); | |
| 158 | 117497 | const uint16_t format = sub.U16(4); | |
| 159 | 117497 | const size_t firstSize = RecordSize(format); | |
| 160 | 117497 | const size_t secondSize = RecordSize(sub.U16(6)); | |
| 161 | 117497 | if (sub.U16(0) == 1) { | |
| 162 | 55865 | if (index >= sub.U16(8)) { | |
| 163 | ✗ | return std::nullopt; | |
| 164 | } | ||
| 165 | 55865 | return PairSetAdjustment(sub.Subtable(sub.U16(10 + 2u * index)), second, format, | |
| 166 | 55865 | 2 + firstSize + secondSize); | |
| 167 | } | ||
| 168 | 61632 | if (sub.U16(0) == 2) { | |
| 169 | 61632 | const uint16_t first = pairs.firstClasses[a]; | |
| 170 | 61632 | const uint16_t next = pairs.secondClasses[b]; | |
| 171 | 61632 | const uint16_t firstCount = sub.U16(12); | |
| 172 | 61632 | const uint16_t secondCount = sub.U16(14); | |
| 173 | 61632 | if (first >= firstCount || next >= secondCount) { | |
| 174 | ✗ | return std::nullopt; | |
| 175 | } | ||
| 176 | 61632 | return XAdvance( | |
| 177 | 61632 | sub.Subtable(16 + (size_t{first} * secondCount + next) * (firstSize + secondSize)), | |
| 178 | 61632 | format); | |
| 179 | } | ||
| 180 | ✗ | return std::nullopt; | |
| 181 | } | ||
| 182 | |||
| 183 | 1 | std::vector<std::vector<Table>> FindKernLookups(const unsigned char* data, size_t size, int gpos) { | |
| 184 | 1 | std::vector<std::vector<Table>> lookups; | |
| 185 | 1 | if (gpos <= 0) { | |
| 186 | return lookups; | ||
| 187 | } | ||
| 188 | 1 | const Table table(data, size, size_t(gpos)); | |
| 189 | 1 | const Table features = table.Subtable(table.U16(6)); | |
| 190 | 1 | const Table list = table.Subtable(table.U16(8)); | |
| 191 | 1 | std::vector<uint16_t> indices; | |
| 192 | 6 | for (uint16_t f = 0; f < features.U16(0); ++f) { | |
| 193 | 5 | const Table record = features.Subtable(2 + 6u * f); | |
| 194 | 5 | if (record.U32(0) != kGposKern) { | |
| 195 | 3 | continue; | |
| 196 | } | ||
| 197 | 2 | const Table feature = features.Subtable(record.U16(4)); | |
| 198 | 11 | for (uint16_t i = 0; i < feature.U16(2); ++i) { | |
| 199 | 9 | indices.push_back(feature.U16(4 + 2u * i)); | |
| 200 | } | ||
| 201 | } | ||
| 202 | 1 | std::ranges::sort(indices); | |
| 203 | 1 | indices.erase(std::unique(indices.begin(), indices.end()), indices.end()); | |
| 204 | 4 | for (const uint16_t index : indices) { | |
| 205 | 3 | if (index >= list.U16(0)) { | |
| 206 | ✗ | continue; | |
| 207 | } | ||
| 208 | 3 | const Table lookup = list.Subtable(list.U16(2 + 2u * index)); | |
| 209 | 3 | std::vector<Table> subtables; | |
| 210 | 10 | for (uint16_t s = 0; s < lookup.U16(4); ++s) { | |
| 211 | 7 | Table sub = lookup.Subtable(lookup.U16(6 + 2u * s)); | |
| 212 | 7 | uint16_t type = lookup.U16(0); | |
| 213 | 7 | if (type == 9) { | |
| 214 | 3 | type = sub.U16(2); | |
| 215 | 3 | sub = sub.Subtable(sub.U32(4)); | |
| 216 | } | ||
| 217 | 7 | if (type == 2) { | |
| 218 | 7 | subtables.push_back(sub); | |
| 219 | } | ||
| 220 | } | ||
| 221 | 3 | lookups.push_back(std::move(subtables)); | |
| 222 | 3 | } | |
| 223 | 1 | return lookups; | |
| 224 | 1 | } | |
| 225 | |||
| 226 | 190 | void BakeGlyph(const stbtt_fontinfo& info, int glyph, float units, float cap, OverlayGlyph& out, | |
| 227 | std::vector<uint8_t>& field) { | ||
| 228 | 190 | int advance = 0; | |
| 229 | 190 | int bearing = 0; | |
| 230 | 190 | stbtt_GetGlyphHMetrics(&info, glyph, &advance, &bearing); | |
| 231 | 190 | out.advance = float(advance) * units; | |
| 232 | 190 | int width = 0; | |
| 233 | 190 | int height = 0; | |
| 234 | 190 | int left = 0; | |
| 235 | 190 | int top = 0; | |
| 236 | 190 | unsigned char* baked = | |
| 237 | 190 | stbtt_GetGlyphSDF(&info, kOverlayFieldTexelsPerUnit * units, glyph, | |
| 238 | int(kOverlayFieldPadding), static_cast<unsigned char>(kOverlayFieldEdge), | ||
| 239 | kOverlayFieldLevels, &width, &height, &left, &top); | ||
| 240 | 190 | if (baked == nullptr) { | |
| 241 | 2 | return; | |
| 242 | } | ||
| 243 | 188 | out.texel = uint32_t(field.size()); | |
| 244 | 188 | out.width = uint32_t(width); | |
| 245 | 188 | out.height = uint32_t(height); | |
| 246 | 188 | out.x0 = float(left) / kOverlayFieldTexelsPerUnit; | |
| 247 | 188 | out.y0 = float(top) / kOverlayFieldTexelsPerUnit + cap; | |
| 248 | 188 | out.x1 = float(left + width) / kOverlayFieldTexelsPerUnit; | |
| 249 | 188 | out.y1 = float(top + height) / kOverlayFieldTexelsPerUnit + cap; | |
| 250 | 188 | field.insert(field.end(), baked, baked + size_t(width) * size_t(height)); | |
| 251 | 188 | stbtt_FreeSDF(baked, nullptr); | |
| 252 | } | ||
| 253 | |||
| 254 | 1 | std::vector<int8_t> BakeKerning(const stbtt_fontinfo& info, const std::array<int, 256>& glyphs, | |
| 255 | float units) { | ||
| 256 | 1 | std::vector<uint16_t> kerned; | |
| 257 | 225 | for (uint32_t code = 0; code < kOverlayKernCodeCount; ++code) { | |
| 258 | 224 | kerned.push_back(uint16_t(glyphs[code + kOverlayKernFirstCode])); | |
| 259 | } | ||
| 260 | 1 | std::vector<std::vector<PairTable>> lookups; | |
| 261 | 4 | for (const std::vector<Table>& subtables : FindKernLookups( | |
| 262 | 4 | kChronoBatchRenderFont_inter, kChronoBatchRenderFont_inter_size, info.gpos)) { | |
| 263 | 3 | lookups.emplace_back(); | |
| 264 | 10 | for (const Table& sub : subtables) { | |
| 265 | 14 | lookups.back().push_back(BuildPairTable(sub, kerned)); | |
| 266 | } | ||
| 267 | 1 | } | |
| 268 | 1 | std::vector<int8_t> kerning(size_t{kOverlayKernCodeCount} * kOverlayKernCodeCount, 0); | |
| 269 | 225 | for (uint32_t a = 0; a < kOverlayKernCodeCount; ++a) { | |
| 270 | 50400 | for (uint32_t b = 0; b < kOverlayKernCodeCount; ++b) { | |
| 271 | 50176 | int adjust = 0; | |
| 272 | 200704 | for (const std::vector<PairTable>& subtables : lookups) { | |
| 273 | 439237 | for (const PairTable& pairs : subtables) { | |
| 274 | 350676 | if (const std::optional<int16_t> value = | |
| 275 | 350676 | PairAdjustment(pairs, a, b, kerned[b])) { | |
| 276 | 61967 | adjust += *value; | |
| 277 | 61967 | break; | |
| 278 | } | ||
| 279 | } | ||
| 280 | } | ||
| 281 | 100352 | kerning[size_t{a} * kOverlayKernCodeCount + b] = | |
| 282 | 100352 | int8_t(std::clamp(std::lround(float(adjust) * units * 16.0f), -127L, 127L)); | |
| 283 | } | ||
| 284 | } | ||
| 285 | 1 | return kerning; | |
| 286 | 1 | } | |
| 287 | |||
| 288 | 1 | OverlayFont BakeFont() { | |
| 289 | 1 | stbtt_fontinfo info; | |
| 290 | 1 | if (stbtt_InitFont(&info, kChronoBatchRenderFont_inter, 0) == 0) { | |
| 291 | ✗ | throw Error("Overlay text's font failed to load"); | |
| 292 | } | ||
| 293 | 1 | int ascent = 0; | |
| 294 | 1 | int descent = 0; | |
| 295 | 1 | int gap = 0; | |
| 296 | 1 | stbtt_GetFontVMetrics(&info, &ascent, &descent, &gap); | |
| 297 | 1 | const float units = kOverlayLineUnits / float(ascent - descent); | |
| 298 | 1 | int x0 = 0; | |
| 299 | 1 | int y0 = 0; | |
| 300 | 1 | int x1 = 0; | |
| 301 | 1 | int capHeight = 0; | |
| 302 | 1 | stbtt_GetCodepointBox(&info, 'H', &x0, &y0, &x1, &capHeight); | |
| 303 | 1 | const float cap = float(capHeight) * units; | |
| 304 | 1 | OverlayFont font; | |
| 305 | 1 | font.textHeight = cap - float(descent) * units; | |
| 306 | 1 | std::array<int, 256> glyphs{}; | |
| 307 | 257 | for (uint32_t code = 0; code < 256; ++code) { | |
| 308 | 447 | const int glyph = Printable(code) ? stbtt_FindGlyphIndex(&info, int(code)) : 0; | |
| 309 | 191 | if (glyph != 0) { | |
| 310 | 190 | glyphs[code] = glyph; | |
| 311 | 190 | BakeGlyph(info, glyph, units, cap, font.glyphs[code], font.field); | |
| 312 | } | ||
| 313 | } | ||
| 314 | 257 | for (uint32_t code = 0; code < 256; ++code) { | |
| 315 | 256 | if (glyphs[code] == 0) { | |
| 316 | 66 | font.glyphs[code] = font.glyphs['?']; | |
| 317 | 66 | glyphs[code] = glyphs['?']; | |
| 318 | } | ||
| 319 | } | ||
| 320 | 1 | font.kerning = BakeKerning(info, glyphs, units); | |
| 321 | 1 | return font; | |
| 322 | ✗ | } | |
| 323 | |||
| 324 | } | ||
| 325 | |||
| 326 | 9 | const OverlayFont& OverlayTextFont() { | |
| 327 | 9 | static const OverlayFont font = BakeFont(); | |
| 328 | 9 | return font; | |
| 329 | } | ||
| 330 | |||
| 331 | 2 | std::vector<uint8_t> OverlayCodes(std::string_view text) { | |
| 332 | 2 | std::vector<uint8_t> codes; | |
| 333 | 2 | codes.reserve(text.size()); | |
| 334 | 11 | for (size_t at = 0; at < text.size();) { | |
| 335 | 9 | const auto lead = static_cast<unsigned char>(text[at]); | |
| 336 | 9 | const size_t length = lead < 0x80 ? 1 | |
| 337 | ✗ | : (lead >> 5) == 6 ? 2 | |
| 338 | ✗ | : (lead >> 4) == 14 ? 3 | |
| 339 | ✗ | : (lead >> 3) == 30 ? 4 | |
| 340 | : 0; | ||
| 341 | 9 | uint32_t code = '?'; | |
| 342 | 9 | if (length == 0 || at + length > text.size()) { | |
| 343 | ✗ | ++at; | |
| 344 | } else { | ||
| 345 | 9 | code = length == 1 ? lead : lead & (0x7Fu >> length); | |
| 346 | bool valid = true; | ||
| 347 | 9 | for (size_t k = 1; k < length; ++k) { | |
| 348 | ✗ | const auto next = static_cast<unsigned char>(text[at + k]); | |
| 349 | ✗ | valid = valid && (next >> 6) == 2; | |
| 350 | ✗ | code = (code << 6) | (next & 0x3Fu); | |
| 351 | } | ||
| 352 | 9 | at += valid ? length : 1; | |
| 353 | 9 | if (!valid) { | |
| 354 | code = '?'; | ||
| 355 | } | ||
| 356 | } | ||
| 357 | 9 | codes.push_back(code == kOverlayNewline || Printable(code) ? uint8_t(code) : uint8_t('?')); | |
| 358 | } | ||
| 359 | 2 | return codes; | |
| 360 | ✗ | } | |
| 361 | |||
| 362 | ✗ | device::Tensor RenderOverlayText(device::Device& target, std::string_view text, float lineHeight, | |
| 363 | Float4 color) { | ||
| 364 | ✗ | const OverlayFont& font = OverlayTextFont(); | |
| 365 | ✗ | std::vector<uint8_t> codes = OverlayCodes(text); | |
| 366 | ✗ | std::erase(codes, uint8_t(kOverlayNewline)); | |
| 367 | ✗ | const float scale = std::max(lineHeight, 1.0f) / kOverlayLineUnits; | |
| 368 | ✗ | std::vector<float> pens; | |
| 369 | ✗ | float pen = 0; | |
| 370 | ✗ | for (size_t i = 0; i < codes.size(); ++i) { | |
| 371 | ✗ | pens.push_back(pen); | |
| 372 | ✗ | pen += font.glyphs[codes[i]].advance; | |
| 373 | ✗ | const uint32_t next = i + 1 < codes.size() ? codes[i + 1] : 0; | |
| 374 | ✗ | if (codes[i] >= kOverlayKernFirstCode && next >= kOverlayKernFirstCode) { | |
| 375 | ✗ | pen += | |
| 376 | ✗ | float( | |
| 377 | ✗ | font.kerning[size_t(codes[i] - kOverlayKernFirstCode) * kOverlayKernCodeCount + | |
| 378 | ✗ | (next - kOverlayKernFirstCode)]) / | |
| 379 | 16; | ||
| 380 | } | ||
| 381 | } | ||
| 382 | ✗ | const auto width = int64_t(std::max(std::ceil(pen * scale), 1.0f)); | |
| 383 | ✗ | const auto height = int64_t(std::max(std::ceil(font.textHeight * scale), 1.0f)); | |
| 384 | ✗ | std::vector<uint8_t> pixels(size_t(width * height * 4), 0); | |
| 385 | ✗ | const float texelsPerPixel = kOverlayFieldTexelsPerUnit / scale; | |
| 386 | ✗ | for (size_t i = 0; i < codes.size(); ++i) { | |
| 387 | ✗ | const OverlayGlyph& glyph = font.glyphs[codes[i]]; | |
| 388 | ✗ | if (glyph.width == 0 || glyph.height == 0) { | |
| 389 | ✗ | continue; | |
| 390 | } | ||
| 391 | ✗ | const float left = (pens[i] + glyph.x0) * scale; | |
| 392 | ✗ | const float top = glyph.y0 * scale; | |
| 393 | ✗ | const float right = (pens[i] + glyph.x1) * scale; | |
| 394 | ✗ | const float bottom = glyph.y1 * scale; | |
| 395 | ✗ | const auto sample = [&](float u, float v) { | |
| 396 | ✗ | u = std::clamp(u, 0.0f, float(glyph.width - 1)); | |
| 397 | ✗ | v = std::clamp(v, 0.0f, float(glyph.height - 1)); | |
| 398 | ✗ | const auto u0 = uint32_t(u); | |
| 399 | ✗ | const auto v0 = uint32_t(v); | |
| 400 | ✗ | const uint32_t u1 = std::min(u0 + 1, glyph.width - 1); | |
| 401 | ✗ | const uint32_t v1 = std::min(v0 + 1, glyph.height - 1); | |
| 402 | ✗ | const auto at = [&](uint32_t x, uint32_t y) { | |
| 403 | ✗ | return float(font.field[glyph.texel + size_t(y) * glyph.width + x]); | |
| 404 | ✗ | }; | |
| 405 | ✗ | const float fu = u - float(u0); | |
| 406 | ✗ | const float fv = v - float(v0); | |
| 407 | ✗ | return (at(u0, v0) * (1 - fu) + at(u1, v0) * fu) * (1 - fv) + | |
| 408 | ✗ | (at(u0, v1) * (1 - fu) + at(u1, v1) * fu) * fv; | |
| 409 | ✗ | }; | |
| 410 | ✗ | for (auto y = int64_t(std::max(std::floor(top), 0.0f)); | |
| 411 | ✗ | y < std::min(int64_t(std::ceil(bottom)), height); ++y) { | |
| 412 | ✗ | for (auto x = int64_t(std::max(std::floor(left), 0.0f)); | |
| 413 | ✗ | x < std::min(int64_t(std::ceil(right)), width); ++x) { | |
| 414 | ✗ | const float u = (float(x) + 0.5f - left) / (right - left) * float(glyph.width); | |
| 415 | ✗ | const float v = (float(y) + 0.5f - top) / (bottom - top) * float(glyph.height); | |
| 416 | ✗ | const float distance = | |
| 417 | ✗ | (sample(u - 0.5f, v - 0.5f) - kOverlayFieldEdge) / kOverlayFieldLevels; | |
| 418 | ✗ | const float coverage = std::clamp(0.5f + distance / texelsPerPixel, 0.0f, 1.0f); | |
| 419 | ✗ | uint8_t* out = &pixels[size_t((y * width + x) * 4)]; | |
| 420 | ✗ | const auto alpha = uint8_t(std::lround(coverage * color.w * 255)); | |
| 421 | ✗ | if (alpha > out[3]) { | |
| 422 | ✗ | out[0] = uint8_t(std::lround(std::clamp(color.x, 0.0f, 1.0f) * 255)); | |
| 423 | ✗ | out[1] = uint8_t(std::lround(std::clamp(color.y, 0.0f, 1.0f) * 255)); | |
| 424 | ✗ | out[2] = uint8_t(std::lround(std::clamp(color.z, 0.0f, 1.0f) * 255)); | |
| 425 | ✗ | out[3] = alpha; | |
| 426 | } | ||
| 427 | } | ||
| 428 | } | ||
| 429 | } | ||
| 430 | ✗ | int64_t shape[3] = {height, width, 4}; | |
| 431 | ✗ | DLTensor host{}; | |
| 432 | ✗ | host.data = pixels.data(); | |
| 433 | ✗ | host.device = {kDLCPU, 0}; | |
| 434 | ✗ | host.ndim = 3; | |
| 435 | ✗ | host.dtype = {kDLUInt, 8, 1}; | |
| 436 | ✗ | host.shape = shape; | |
| 437 | ✗ | return target.Import(host, Stream::None(), "overlay text"); | |
| 438 | ✗ | } | |
| 439 | |||
| 440 | } | ||
| 441 |