载入中...
搜索中...
未找到
ImageData.cpp
浏览该文件的文档.
1
2
3#include "ImageData.h"
4#include "Image.h"
5#include "UvPaintRegion.h"
8#include "medialoader/image/pixelformat.h"
9
10#include "common/Assert.h"
11#include "common/Exception.h"
12
13#include <algorithm> // min/max
14#include <cmath>
15#include <cstring> // memcpy
16#include <limits>
17#include <utility>
18#include <vector>
19
20using namespace medialoader;
21
22namespace eve {
23namespace image {
24
25ImageData::ImageData(Data *data) : Resource(""), decodeHandler(nullptr) { decode(data); }
26
27ImageData::ImageData(int width, int height, std::string format) : Resource(""), width(width), height(height), format(format), decodeHandler(nullptr) {
28 if (!validPixelFormat(format)) throw eve::Exception("Unsupported pixel format for ImageData");
29
30 create(width, height, format);
31
32 // Set to black/transparency.
33 memset(data, 0, getSize());
34}
35
36ImageData::ImageData(int width, int height, std::string format, void *data, bool own)
37 : Resource(""), width(width), height(height), format(format), decodeHandler(nullptr)
38{
39 if (!validPixelFormat(format)) throw eve::Exception("Unsupported pixel format for ImageData");
40
41 if (own)
42 this->data = (unsigned char *)data;
43 else
44 create(width, height, format, data);
45}
46
47ImageData::ImageData(const ImageData &c) : Resource(c.getUri()), decodeHandler(nullptr) {
48 width = c.width;
49 height = c.height;
50 format = c.format;
51
52 create(width, height, format, c.getData());
53}
54
56 if (decodeHandler)
57 decodeHandler->freeRawPixels(data);
58 else
59 delete[] data;
60}
61
62void ImageData::adopt(eve::Resource &replacement) {
63 auto &other = static_cast<ImageData &>(replacement);
64 std::swap(data, other.data);
65 std::swap(width, other.width);
66 std::swap(height, other.height);
67 std::swap(format, other.format);
68 std::swap(decodeHandler, other.decodeHandler);
69 std::swap(pixelSetFunction, other.pixelSetFunction);
70 std::swap(pixelGetFunction, other.pixelGetFunction);
71}
72
74 try {
75 return new ImageData(*this);
76 } catch (std::bad_alloc &) {
77 throw eve::Exception("Out of memory");
78 }
79}
80
81void ImageData::create(int w, int h, std::string pixelFormat, void *pixels) {
82 // Reject negative dimensions only: negative ints wrap to huge size_t
83 // allocations. Zero-sized images are valid (e.g. empty font atlases).
84 if (w < 0 || h < 0)
85 throw eve::Exception("ImageData: invalid dimensions (%dx%d)", w, h);
86 size_t datasize =
87 w * h * getPixelFormatSize(getPixelFormatFromName(pixelFormat));
88
89 try {
90 this->data = new unsigned char[datasize];
91 } catch (std::bad_alloc &) {
92 throw eve::Exception("Out of memory");
93 }
94
95 if (pixels) memcpy(this->data, pixels, datasize);
96
97 decodeHandler = nullptr;
98 this->format = pixelFormat;
99
100 pixelSetFunction = getPixelSetFunction(pixelFormat);
101 pixelGetFunction = getPixelGetFunction(pixelFormat);
102}
103
104void ImageData::decode(Data *source) {
105 FormatHandler *decoder = nullptr;
106 FormatHandler::DecodedImage decodedimage;
107
108 auto module = Image::create();
109
110 if (module == nullptr) throw eve::Exception("eve.image must be loaded in order to decode an ImageData.");
111
112 for (FormatHandler *handler : module->getFormatHandlers()) {
113 if (handler->canDecode((const char*)source->getData(), source->getSize())) {
114 decoder = handler;
115 break;
116 }
117 }
118
119 if (decoder)
120 decodedimage = decoder->decode((const char*)source->getData(), source->getSize());
121
122 if (decodedimage.data == nullptr) {
123 auto filedata = dynamic_cast<filesystem::FileData *>(source);
124
125 if (filedata != nullptr) {
126 const std::string &name = filedata->getFilename();
127 throw eve::Exception("Could not decode file '%s' to ImageData: unsupported file format", name.c_str());
128 } else
129 throw eve::Exception("Could not decode data to ImageData: unsupported encoded format");
130 }
131
132 if (decodedimage.size !=
133 size_t(decodedimage.width) * size_t(decodedimage.height) *
134 size_t(getPixelFormatSize(decodedimage.format))) {
135 decoder->freeRawPixels(decodedimage.data);
136 throw eve::Exception("Could not convert image!");
137 }
138
139 // Clean up any old data.
140 if (decodeHandler)
141 decodeHandler->freeRawPixels(this->data);
142 else
143 delete[] this->data;
144
145 this->width = decodedimage.width;
146 this->height = decodedimage.height;
147 this->data = decodedimage.data;
148 // PixelFormat enum must not be assigned into std::string (that becomes a single char).
149 this->format = [pf = decodedimage.format]() -> std::string {
150 switch (pf) {
151 case PIXELFORMAT_R8: return "R8";
152 case PIXELFORMAT_RG8: return "RG8";
153 case PIXELFORMAT_RGBA8:
154 case PIXELFORMAT_sRGBA8: return "RGBA8";
155 case PIXELFORMAT_R16: return "R16";
156 case PIXELFORMAT_RG16: return "RG16";
157 case PIXELFORMAT_RGBA16: return "RGBA16";
158 case PIXELFORMAT_R16F: return "R16F";
159 case PIXELFORMAT_RG16F: return "RG16F";
160 case PIXELFORMAT_RGBA16F: return "RGBA16F";
161 case PIXELFORMAT_R32F: return "R32F";
162 case PIXELFORMAT_RG32F: return "RG32F";
163 case PIXELFORMAT_RGBA32F: return "RGBA32F";
164 case PIXELFORMAT_RGBA4: return "RGBA4";
165 case PIXELFORMAT_RGB5A1: return "RGB5A1";
166 case PIXELFORMAT_RGB565: return "RGB565";
167 case PIXELFORMAT_RGB10A2: return "RGB10A2";
168 case PIXELFORMAT_RG11B10F: return "RG11B10F";
169 default: return "RGBA8";
170 }
171 }();
172
173 decodeHandler = decoder;
174
175 pixelSetFunction = getPixelSetFunction(format);
176 pixelGetFunction = getPixelGetFunction(format);
177}
178
179eve::filesystem::FileData *ImageData::encode(FormatHandler::EncodedFormat encodedFormat, const char *filename,
180 bool writefile) const {
181 EV_PARAM_CHECK(filename != nullptr, "output filename must not be null");
182
183 FormatHandler *encoder = nullptr;
184 FormatHandler::EncodedImage encodedimage;
185 FormatHandler::DecodedImage rawimage;
186
187 rawimage.width = width;
188 rawimage.height = height;
189 rawimage.size = getSize();
190 rawimage.data = data;
191 rawimage.format = getPixelFormatFromName(format);
192
193 auto module = Image::create();
194
195 if (module == nullptr) throw eve::Exception("eve.image must be loaded in order to encode an ImageData.");
196
197 for (FormatHandler *handler : module->getFormatHandlers()) {
198 if (handler->canEncode(getPixelFormatFromName(format), encodedFormat)) {
199 encoder = handler;
200 break;
201 }
202 }
203
204 if (encoder != nullptr) {
205 encodedimage = encoder->encode(rawimage, encodedFormat);
206 }
207
208 if (encoder == nullptr || encodedimage.data == nullptr) {
209 throw eve::Exception("No suitable image encoder for %s format.", format.c_str());
210 }
211
212 eve::filesystem::FileData *filedata = nullptr;
213
214 try {
215 filedata = new eve::filesystem::FileData(filename, encodedimage.size);
216 } catch (eve::Exception &) {
217 encoder->freeRawPixels(encodedimage.data);
218 throw;
219 }
220
221 memcpy(filedata->getData(), encodedimage.data, encodedimage.size);
222 encoder->freeRawPixels(encodedimage.data);
223
224 if (writefile) {
225 auto fs = eve::filesystem::Filesystem::create();
226
227 if (fs == nullptr) {
228 // filedata->release();
229 throw eve::Exception("eve.filesystem must be loaded in order to write an encoded ImageData to a file.");
230 }
231
232 try {
233 fs->write(filename, filedata->getData(), filedata->getSize());
234 } catch (eve::Exception &) {
235 // filedata->release();
236 throw;
237 }
238 }
239
240 return filedata;
241}
242
243size_t ImageData::getSize() const { return size_t(getWidth() * getHeight()) * getPixelSize(); }
244
245void *ImageData::getData() const { return data; }
246
247bool ImageData::isSRGB() const { return false; }
248
249bool ImageData::inside(int x, int y) const { return x >= 0 && x < getWidth() && y >= 0 && y < getHeight(); }
250
251int ImageData::getWidth() const { return width; }
252
253int ImageData::getHeight() const { return height; }
254
255std::string ImageData::getFormat() const { return format; }
256
257static float clamp01(float x) { return std::min(std::max(x, 0.0f), 1.0f); }
258
259static void setPixelR8(const Colorf &c, ImageData::Pixel *p) { p->rgba8[0] = (uint8_t)(clamp01(c.r) * 255.0f + 0.5f); }
260
261static void setPixelRG8(const Colorf &c, ImageData::Pixel *p) {
262 p->rgba8[0] = (uint8_t)(clamp01(c.r) * 255.0f + 0.5f);
263 p->rgba8[1] = (uint8_t)(clamp01(c.g) * 255.0f + 0.5f);
264}
265
266static void setPixelRGBA8(const Colorf &c, ImageData::Pixel *p) {
267 p->rgba8[0] = (uint8_t)(clamp01(c.r) * 255.0f + 0.5f);
268 p->rgba8[1] = (uint8_t)(clamp01(c.g) * 255.0f + 0.5f);
269 p->rgba8[2] = (uint8_t)(clamp01(c.b) * 255.0f + 0.5f);
270 p->rgba8[3] = (uint8_t)(clamp01(c.a) * 255.0f + 0.5f);
271}
272
273static void setPixelR16(const Colorf &c, ImageData::Pixel *p) {
274 p->rgba16[0] = (uint16_t)(clamp01(c.r) * 65535.0f + 0.5f);
275}
276
277static void setPixelRG16(const Colorf &c, ImageData::Pixel *p) {
278 p->rgba16[0] = (uint16_t)(clamp01(c.r) * 65535.0f + 0.5f);
279 p->rgba16[1] = (uint16_t)(clamp01(c.g) * 65535.0f + 0.5f);
280}
281
282static void setPixelRGBA16(const Colorf &c, ImageData::Pixel *p) {
283 p->rgba16[0] = (uint16_t)(clamp01(c.r) * 65535.0f + 0.5f);
284 p->rgba16[1] = (uint16_t)(clamp01(c.g) * 65535.0f + 0.5f);
285 p->rgba16[2] = (uint16_t)(clamp01(c.b) * 65535.0f + 0.5f);
286 p->rgba16[3] = (uint16_t)(clamp01(c.a) * 65535.0f + 0.5f);
287}
288
289static void setPixelR16F(const Colorf &c, ImageData::Pixel *p) { p->rgba16f[0] = float32to16(c.r); }
290
291static void setPixelRG16F(const Colorf &c, ImageData::Pixel *p) {
292 p->rgba16f[0] = float32to16(c.r);
293 p->rgba16f[1] = float32to16(c.g);
294}
295
296static void setPixelRGBA16F(const Colorf &c, ImageData::Pixel *p) {
297 p->rgba16f[0] = float32to16(c.r);
298 p->rgba16f[1] = float32to16(c.g);
299 p->rgba16f[2] = float32to16(c.b);
300 p->rgba16f[3] = float32to16(c.a);
301}
302
303static void setPixelR32F(const Colorf &c, ImageData::Pixel *p) { p->rgba32f[0] = c.r; }
304
305static void setPixelRG32F(const Colorf &c, ImageData::Pixel *p) {
306 p->rgba32f[0] = c.r;
307 p->rgba32f[1] = c.g;
308}
309
310static void setPixelRGBA32F(const Colorf &c, ImageData::Pixel *p) {
311 p->rgba32f[0] = c.r;
312 p->rgba32f[1] = c.g;
313 p->rgba32f[2] = c.b;
314 p->rgba32f[3] = c.a;
315}
316
317static void setPixelRGBA4(const Colorf &c, ImageData::Pixel *p) {
318 // LSB->MSB: [a, b, g, r]
319 uint16_t r = (uint16_t)(clamp01(c.r) * 0xF + 0.5);
320 uint16_t g = (uint16_t)(clamp01(c.g) * 0xF + 0.5);
321 uint16_t b = (uint16_t)(clamp01(c.b) * 0xF + 0.5);
322 uint16_t a = (uint16_t)(clamp01(c.a) * 0xF + 0.5);
323 p->packed16 = (r << 12) | (g << 8) | (b << 4) | (a << 0);
324}
325
326static void setPixelRGB5A1(const Colorf &c, ImageData::Pixel *p) {
327 // LSB->MSB: [a, b, g, r]
328 uint16_t r = (uint16_t)(clamp01(c.r) * 0x1F + 0.5);
329 uint16_t g = (uint16_t)(clamp01(c.g) * 0x1F + 0.5);
330 uint16_t b = (uint16_t)(clamp01(c.b) * 0x1F + 0.5);
331 uint16_t a = (uint16_t)(clamp01(c.a) * 0x1 + 0.5);
332 p->packed16 = (r << 11) | (g << 6) | (b << 1) | (a << 0);
333}
334
335static void setPixelRGB565(const Colorf &c, ImageData::Pixel *p) {
336 // LSB->MSB: [b, g, r]
337 uint16_t r = (uint16_t)(clamp01(c.r) * 0x1F + 0.5);
338 uint16_t g = (uint16_t)(clamp01(c.g) * 0x3F + 0.5);
339 uint16_t b = (uint16_t)(clamp01(c.b) * 0x1F + 0.5);
340 p->packed16 = (r << 11) | (g << 5) | (b << 0);
341}
342
343static void setPixelRGB10A2(const Colorf &c, ImageData::Pixel *p) {
344 // LSB->MSB: [r, g, b, a]
345 uint32_t r = (uint32_t)(clamp01(c.r) * 0x3FF + 0.5);
346 uint32_t g = (uint32_t)(clamp01(c.g) * 0x3FF + 0.5);
347 uint32_t b = (uint32_t)(clamp01(c.b) * 0x3FF + 0.5);
348 uint32_t a = (uint32_t)(clamp01(c.a) * 0x3 + 0.5);
349 p->packed32 = (r << 0) | (g << 10) | (b << 20) | (a << 30);
350}
351
352static void setPixelRG11B10F(const Colorf &c, ImageData::Pixel *p) {
353 // LSB->MSB: [r, g, b]
354 float11 r = float32to11(c.r);
355 float11 g = float32to11(c.g);
356 float10 b = float32to10(c.b);
357 p->packed32 = (r << 0) | (g << 11) | (b << 22);
358}
359
360static void getPixelR8(const ImageData::Pixel *p, Colorf &c) {
361 c.r = p->rgba8[0] / 255.0f;
362 c.g = 0.0f;
363 c.b = 0.0f;
364 c.a = 1.0f;
365}
366
367static void getPixelRG8(const ImageData::Pixel *p, Colorf &c) {
368 c.r = p->rgba8[0] / 255.0f;
369 c.g = p->rgba8[1] / 255.0f;
370 c.b = 0.0f;
371 c.a = 1.0f;
372}
373
374static void getPixelRGBA8(const ImageData::Pixel *p, Colorf &c) {
375 c.r = p->rgba8[0] / 255.0f;
376 c.g = p->rgba8[1] / 255.0f;
377 c.b = p->rgba8[2] / 255.0f;
378 c.a = p->rgba8[3] / 255.0f;
379}
380
381static void getPixelR16(const ImageData::Pixel *p, Colorf &c) {
382 c.r = p->rgba16[0] / 65535.0f;
383 c.g = 0.0f;
384 c.b = 0.0f;
385 c.a = 1.0f;
386}
387
388static void getPixelRG16(const ImageData::Pixel *p, Colorf &c) {
389 c.r = p->rgba16[0] / 65535.0f;
390 c.g = p->rgba16[1] / 65535.0f;
391 c.b = 0.0f;
392 c.a = 1.0f;
393}
394
395static void getPixelRGBA16(const ImageData::Pixel *p, Colorf &c) {
396 c.r = p->rgba16[0] / 65535.0f;
397 c.g = p->rgba16[1] / 65535.0f;
398 c.b = p->rgba16[2] / 65535.0f;
399 c.a = p->rgba16[3] / 65535.0f;
400}
401
402static void getPixelR16F(const ImageData::Pixel *p, Colorf &c) {
403 c.r = float16to32(p->rgba16f[0]);
404 c.g = 0.0f;
405 c.b = 0.0f;
406 c.a = 1.0f;
407}
408
409static void getPixelRG16F(const ImageData::Pixel *p, Colorf &c) {
410 c.r = float16to32(p->rgba16f[0]);
411 c.g = float16to32(p->rgba16f[1]);
412 c.b = 0.0f;
413 c.a = 1.0f;
414}
415
416static void getPixelRGBA16F(const ImageData::Pixel *p, Colorf &c) {
417 c.r = float16to32(p->rgba16f[0]);
418 c.g = float16to32(p->rgba16f[1]);
419 c.b = float16to32(p->rgba16f[2]);
420 c.a = float16to32(p->rgba16f[3]);
421}
422
423static void getPixelR32F(const ImageData::Pixel *p, Colorf &c) {
424 c.r = p->rgba32f[0];
425 c.g = 0.0f;
426 c.b = 0.0f;
427 c.a = 1.0f;
428}
429
430static void getPixelRG32F(const ImageData::Pixel *p, Colorf &c) {
431 c.r = p->rgba32f[0];
432 c.g = p->rgba32f[1];
433 c.b = 0.0f;
434 c.a = 1.0f;
435}
436
437static void getPixelRGBA32F(const ImageData::Pixel *p, Colorf &c) {
438 c.r = p->rgba32f[0];
439 c.g = p->rgba32f[1];
440 c.b = p->rgba32f[2];
441 c.a = p->rgba32f[3];
442}
443
444static void getPixelRGBA4(const ImageData::Pixel *p, Colorf &c) {
445 // LSB->MSB: [a, b, g, r]
446 c.r = ((p->packed16 >> 12) & 0xF) / (float)0xF;
447 c.g = ((p->packed16 >> 8) & 0xF) / (float)0xF;
448 c.b = ((p->packed16 >> 4) & 0xF) / (float)0xF;
449 c.a = ((p->packed16 >> 0) & 0xF) / (float)0xF;
450}
451
452static void getPixelRGB5A1(const ImageData::Pixel *p, Colorf &c) {
453 // LSB->MSB: [a, b, g, r]
454 c.r = ((p->packed16 >> 11) & 0x1F) / (float)0x1F;
455 c.g = ((p->packed16 >> 6) & 0x1F) / (float)0x1F;
456 c.b = ((p->packed16 >> 1) & 0x1F) / (float)0x1F;
457 c.a = ((p->packed16 >> 0) & 0x1) / (float)0x1;
458}
459
460static void getPixelRGB565(const ImageData::Pixel *p, Colorf &c) {
461 // LSB->MSB: [b, g, r]
462 c.r = ((p->packed16 >> 11) & 0x1F) / (float)0x1F;
463 c.g = ((p->packed16 >> 5) & 0x3F) / (float)0x3F;
464 c.b = ((p->packed16 >> 0) & 0x1F) / (float)0x1F;
465 c.a = 1.0f;
466}
467
468static void getPixelRGB10A2(const ImageData::Pixel *p, Colorf &c) {
469 // LSB->MSB: [r, g, b, a]
470 c.r = ((p->packed32 >> 0) & 0x3FF) / (float)0x3FF;
471 c.g = ((p->packed32 >> 10) & 0x3FF) / (float)0x3FF;
472 c.b = ((p->packed32 >> 20) & 0x3FF) / (float)0x3FF;
473 c.a = ((p->packed32 >> 30) & 0x3) / (float)0x3;
474}
475
476static void getPixelRG11B10F(const ImageData::Pixel *p, Colorf &c) {
477 // LSB->MSB: [r, g, b]
478 c.r = float11to32((float11)((p->packed32 >> 0) & 0x7FF));
479 c.g = float11to32((float11)((p->packed32 >> 11) & 0x7FF));
480 c.b = float10to32((float10)((p->packed32 >> 22) & 0x3FF));
481 c.a = 1.0f;
482}
483
484void ImageData::setPixel(int x, int y, const Colorf &c) {
485 if (!inside(x, y)) throw eve::Exception("Attempt to set out-of-range pixel!");
486
487 if (pixelSetFunction == nullptr) throw eve::Exception("Unhandled pixel format %s in ImageData::setPixel", format.c_str());
488
489 // Packed formats (RGBA4 / RGB5A1 / RGB565) place pixels at 2-byte
490 // granularity, which can violate the Pixel union's 4-byte alignment.
491 // Encode into an aligned local and copy the bytes back instead of
492 // accessing a possibly-misaligned union member (UBSan).
493 const size_t pixelsize = getPixelSize();
494 Pixel tmp;
495 pixelSetFunction(c, &tmp);
496 std::memcpy(data + ((y * width + x) * pixelsize), &tmp, pixelsize);
497}
498
499eve::Result<UvPaintReceipt> ImageData::paintCircleUv(float u, float v, float radiusPixels,
500 const Colorf &color, bool wrapU,
501 bool wrapV) {
502 const int w = getWidth(), h = getHeight();
503 if (w <= 0 || h <= 0)
505 eve::DiagnosticCode::PreconditionViolation, "image has no pixels", "image.paintCircleUv"));
506 auto normalize = [](float value, bool wrap) {
507 if (!wrap) return std::clamp(value, 0.f, 1.f);
508 return value - std::floor(value);
509 };
510 const float normalizedU = normalize(u, wrapU);
511 const float normalizedV = normalize(v, wrapV);
512 auto prepared = prepareUvPaintRegionResult(
513 w, h, normalizedU, normalizedV, radiusPixels / float(w), radiusPixels / float(h),
514 color.r, color.g, color.b, color.a, true);
515 if (!prepared.ok()) return eve::Result<UvPaintReceipt>::failure(prepared.status());
516 const int cx = prepared.value().centerX;
517 const int cy = prepared.value().centerY;
518 const int extent = static_cast<int>(std::ceil(radiusPixels));
519 const float radiusSquared = radiusPixels * radiusPixels;
520 UvPaintReceipt receipt{cx, cy, 0};
521 for (int oy = -extent; oy <= extent; ++oy) {
522 for (int ox = -extent; ox <= extent; ++ox) {
523 if (radiusPixels > 0.f && float(ox * ox + oy * oy) > radiusSquared) continue;
524 int x = cx + ox, y = cy + oy;
525 if (wrapU) x = (x % w + w) % w;
526 if (wrapV) y = (y % h + h) % h;
527 if (!inside(x, y)) continue;
528 setPixel(x, y, color);
529 ++receipt.changedPixelCount;
530 }
531 }
533}
534
535void ImageData::getPixel(int x, int y, Colorf &c) const {
536 if (!inside(x, y)) throw eve::Exception("Attempt to get out-of-range pixel!");
537
538 if (pixelGetFunction == nullptr) throw eve::Exception("Unhandled pixel format %s in ImageData::getPixel", format.c_str());
539
540 const size_t pixelsize = getPixelSize();
541 Pixel tmp;
542 std::memcpy(&tmp, data + ((y * width + x) * pixelsize), pixelsize);
543 pixelGetFunction(&tmp, c);
544}
545
546Colorf ImageData::getPixel(int x, int y) const {
547 Colorf c;
548 getPixel(x, y, c);
549 return c;
550}
551
552namespace {
553
554enum class RotateFilter {
555 Nearest,
556 Linear,
557 RotSprite,
558};
559
560RotateFilter parseRotateFilter(const std::string &name) {
561 if (name == "nearest" || name == "Nearest" || name == "NEAREST")
562 return RotateFilter::Nearest;
563 if (name == "linear" || name == "Linear" || name == "LINEAR" ||
564 name == "bilinear" || name == "Bilinear" || name == "BILINEAR")
565 return RotateFilter::Linear;
566 if (name == "rotsprite" || name == "RotSprite" || name == "ROTSPRITE" ||
567 name == "rotSprite")
568 return RotateFilter::RotSprite;
569 throw eve::Exception(
570 "Unsupported ImageData rotate filter '%s' (use \"nearest\", \"linear\", or \"rotsprite\")",
571 name.c_str());
572}
573
574void rotatePoint(float dx, float dy, float c, float s, float &ox, float &oy) {
575 // Same convention as Math::rotate2X/Y.
576 ox = dx * c - dy * s;
577 oy = dx * s + dy * c;
578}
579
580void inverseRotatePoint(float dx, float dy, float c, float s, float &ox, float &oy) {
581 // Inverse of Math::rotate2*: apply -angle (cos'=c, sin'=-s).
582 ox = dx * c + dy * s;
583 oy = -dx * s + dy * c;
584}
585
586bool colorExact(const Colorf &a, const Colorf &b) {
587 return a.r == b.r && a.g == b.g && a.b == b.b && a.a == b.a;
588}
589
590// RotSprite's modified Scale2x treats "similar" pixels as matches (not only identical).
591bool colorSimilar(const Colorf &a, const Colorf &b) {
592 const float dr = std::fabs(a.r - b.r);
593 const float dg = std::fabs(a.g - b.g);
594 const float db = std::fabs(a.b - b.b);
595 const float da = std::fabs(a.a - b.a);
596 return (dr + dg + db + da) <= (40.f / 255.f);
597}
598
599float colorDistSq(const Colorf &a, const Colorf &b) {
600 const float dr = a.r - b.r;
601 const float dg = a.g - b.g;
602 const float db = a.b - b.b;
603 const float da = a.a - b.a;
604 return dr * dr + dg * dg + db * db + da * da;
605}
606
607Colorf sampleNearest(const ImageData *src, float sx, float sy) {
608 const int rx = (int)std::floor(sx + 0.5f);
609 const int ry = (int)std::floor(sy + 0.5f);
610 if (!src->inside(rx, ry))
611 return Colorf{0.f, 0.f, 0.f, 0.f};
612 return src->getPixel(rx, ry);
613}
614
615Colorf sampleBilinear(const ImageData *src, float sx, float sy) {
616 const int x0 = (int)std::floor(sx);
617 const int y0 = (int)std::floor(sy);
618 const int x1 = x0 + 1;
619 const int y1 = y0 + 1;
620 const float fx = sx - (float)x0;
621 const float fy = sy - (float)y0;
622 const float ifx = 1.f - fx;
623 const float ify = 1.f - fy;
624
625 auto sampleOrZero = [&](int x, int y) -> Colorf {
626 if (!src->inside(x, y))
627 return Colorf{0.f, 0.f, 0.f, 0.f};
628 return src->getPixel(x, y);
629 };
630
631 const Colorf c00 = sampleOrZero(x0, y0);
632 const Colorf c10 = sampleOrZero(x1, y0);
633 const Colorf c01 = sampleOrZero(x0, y1);
634 const Colorf c11 = sampleOrZero(x1, y1);
635
636 const float w00 = ifx * ify;
637 const float w10 = fx * ify;
638 const float w01 = ifx * fy;
639 const float w11 = fx * fy;
640
641 Colorf out;
642 out.r = w00 * c00.r + w10 * c10.r + w01 * c01.r + w11 * c11.r;
643 out.g = w00 * c00.g + w10 * c10.g + w01 * c01.g + w11 * c11.g;
644 out.b = w00 * c00.b + w10 * c10.b + w01 * c01.b + w11 * c11.b;
645 out.a = w00 * c00.a + w10 * c10.a + w01 * c01.a + w11 * c11.a;
646 return out;
647}
648
649void computeRotatedSize(int srcW, int srcH, float c, float s, bool expand, int &dstW, int &dstH,
650 float &dstCx, float &dstCy) {
651 const float srcCx = srcW * 0.5f;
652 const float srcCy = srcH * 0.5f;
653 if (!expand) {
654 dstW = srcW;
655 dstH = srcH;
656 dstCx = srcCx;
657 dstCy = srcCy;
658 return;
659 }
660 const float corners[4][2] = {
661 {-srcCx, -srcCy},
662 {srcW - srcCx, -srcCy},
663 {-srcCx, srcH - srcCy},
664 {srcW - srcCx, srcH - srcCy},
665 };
666 float minX = 0.f, maxX = 0.f, minY = 0.f, maxY = 0.f;
667 for (int i = 0; i < 4; ++i) {
668 float ox, oy;
669 rotatePoint(corners[i][0], corners[i][1], c, s, ox, oy);
670 if (i == 0) {
671 minX = maxX = ox;
672 minY = maxY = oy;
673 } else {
674 minX = std::min(minX, ox);
675 maxX = std::max(maxX, ox);
676 minY = std::min(minY, oy);
677 maxY = std::max(maxY, oy);
678 }
679 }
680 // A float quarter-turn can push an integral extent one ULP upward.
681 // Discard that rounding residue before expanding to whole pixels.
682 dstW = std::max(1, (int)std::ceil(std::nextafter(maxX - minX, 0.f)));
683 dstH = std::max(1, (int)std::ceil(std::nextafter(maxY - minY, 0.f)));
684 dstCx = dstW * 0.5f;
685 dstCy = dstH * 0.5f;
686}
687
688struct PixelBuffer {
689 int w = 0;
690 int h = 0;
691 std::vector<Colorf> px;
692
693 Colorf get(int x, int y) const { return px[(size_t)y * (size_t)w + (size_t)x]; }
694 void set(int x, int y, const Colorf &c) { px[(size_t)y * (size_t)w + (size_t)x] = c; }
695 bool inside(int x, int y) const { return x >= 0 && y >= 0 && x < w && y < h; }
696};
697
698PixelBuffer bufferFromImage(const ImageData *img) {
699 PixelBuffer b;
700 b.w = img->getWidth();
701 b.h = img->getHeight();
702 b.px.resize((size_t)b.w * (size_t)b.h);
703 for (int y = 0; y < b.h; ++y)
704 for (int x = 0; x < b.w; ++x)
705 b.set(x, y, img->getPixel(x, y));
706 return b;
707}
708
709ImageData *imageFromBuffer(const PixelBuffer &b, const std::string &format) {
710 ImageData *dst = new ImageData(b.w, b.h, format);
711 for (int y = 0; y < b.h; ++y)
712 for (int x = 0; x < b.w; ++x) {
713 const Colorf &c = b.get(x, y);
714 if (c.a != 0.f || c.r != 0.f || c.g != 0.f || c.b != 0.f)
715 dst->setPixel(x, y, c);
716 }
717 return dst;
718}
719
720void scale2xBlock(Colorf center, Colorf up, Colorf left, Colorf down, Colorf right, Colorf &a,
721 Colorf &b, Colorf &c, Colorf &d) {
722 // Classic Scale2x / EPX rules, with RotSprite-style similarity.
723 a = (colorSimilar(left, up) && !colorSimilar(left, down) && !colorSimilar(up, right)) ? up
724 : center;
725 b = (colorSimilar(up, right) && !colorSimilar(up, left) && !colorSimilar(right, down)) ? right
726 : center;
727 c = (colorSimilar(down, left) && !colorSimilar(down, right) && !colorSimilar(left, up)) ? left
728 : center;
729 d = (colorSimilar(right, down) && !colorSimilar(right, up) && !colorSimilar(down, left))
730 ? down
731 : center;
732}
733
734PixelBuffer scale2x(const PixelBuffer &src) {
735 PixelBuffer dst;
736 dst.w = src.w * 2;
737 dst.h = src.h * 2;
738 dst.px.assign((size_t)dst.w * (size_t)dst.h, Colorf{0.f, 0.f, 0.f, 0.f});
739
740 auto at = [&](int x, int y) -> Colorf {
741 x = std::clamp(x, 0, src.w - 1);
742 y = std::clamp(y, 0, src.h - 1);
743 return src.get(x, y);
744 };
745
746 for (int y = 0; y < src.h; ++y) {
747 for (int x = 0; x < src.w; ++x) {
748 const Colorf E = at(x, y);
749 const Colorf B = at(x, y - 1);
750 const Colorf D = at(x - 1, y);
751 const Colorf H = at(x, y + 1);
752 const Colorf F = at(x + 1, y);
753 Colorf p00, p10, p01, p11;
754 scale2xBlock(E, B, D, H, F, p00, p10, p01, p11);
755 const int dx = x * 2;
756 const int dy = y * 2;
757 dst.set(dx, dy, p00);
758 dst.set(dx + 1, dy, p10);
759 dst.set(dx, dy + 1, p01);
760 dst.set(dx + 1, dy + 1, p11);
761 }
762 }
763 return dst;
764}
765
766PixelBuffer upscaleRotSprite(const PixelBuffer &src) {
767 // 2× → 4× → 8×
768 PixelBuffer s2 = scale2x(src);
769 PixelBuffer s4 = scale2x(s2);
770 return scale2x(s4);
771}
772
773void findBestOffset(const PixelBuffer &hi, float radians, int scale, int &bestOx, int &bestOy) {
774 const float c = std::cos(radians);
775 const float s = std::sin(radians);
776 const float srcCx = hi.w * 0.5f;
777 const float srcCy = hi.h * 0.5f;
778
779 int outW = 0, outH = 0;
780 float outCx = 0.f, outCy = 0.f;
781 // Score on the expanded low-res AABB mapped into hi-res by *scale.
782 computeRotatedSize(hi.w / scale, hi.h / scale, c, s, true, outW, outH, outCx, outCy);
783
784 bestOx = 0;
785 bestOy = 0;
786 double bestScore = std::numeric_limits<double>::infinity();
787
788 for (int oy = 0; oy < scale; ++oy) {
789 for (int ox = 0; ox < scale; ++ox) {
790 double score = 0.0;
791 for (int yt = 0; yt < outH; ++yt) {
792 const float dy = ((float)yt + 0.5f) - outCy;
793 for (int xt = 0; xt < outW; ++xt) {
794 const float dx = ((float)xt + 0.5f) - outCx;
795 float relX, relY;
796 inverseRotatePoint(dx * (float)scale, dy * (float)scale, c, s, relX, relY);
797 const int sx = (int)std::floor(relX + srcCx + (float)ox);
798 const int sy = (int)std::floor(relY + srcCy + (float)oy);
799 if (!hi.inside(sx, sy))
800 continue;
801 const Colorf p = hi.get(sx, sy);
802 if (hi.inside(sx + 1, sy))
803 score += (double)colorDistSq(p, hi.get(sx + 1, sy));
804 if (hi.inside(sx, sy + 1))
805 score += (double)colorDistSq(p, hi.get(sx, sy + 1));
806 }
807 }
808 if (score < bestScore) {
809 bestScore = score;
810 bestOx = ox;
811 bestOy = oy;
812 }
813 }
814 }
815}
816
817PixelBuffer rotateExact90(const PixelBuffer &src, int turnsClockwise) {
818 turnsClockwise = ((turnsClockwise % 4) + 4) % 4;
819 if (turnsClockwise == 0)
820 return src;
821 PixelBuffer cur = src;
822 for (int t = 0; t < turnsClockwise; ++t) {
823 PixelBuffer next;
824 next.w = cur.h;
825 next.h = cur.w;
826 next.px.resize((size_t)next.w * (size_t)next.h);
827 for (int y = 0; y < cur.h; ++y)
828 for (int x = 0; x < cur.w; ++x)
829 next.set(cur.h - 1 - y, x, cur.get(x, y));
830 cur = std::move(next);
831 }
832 return cur;
833}
834
835void restoreSinglePixelDetails(PixelBuffer &dst, const PixelBuffer &orig, float radians) {
836 const float c = std::cos(radians);
837 const float s = std::sin(radians);
838 const float srcCx = orig.w * 0.5f;
839 const float srcCy = orig.h * 0.5f;
840 const float dstCx = dst.w * 0.5f;
841 const float dstCy = dst.h * 0.5f;
842
843 PixelBuffer copy = dst;
844 for (int y = 0; y < dst.h; ++y) {
845 for (int x = 0; x < dst.w; ++x) {
846 const Colorf p = copy.get(x, y);
847 int same = 0;
848 const int nxs[4] = {x - 1, x + 1, x, x};
849 const int nys[4] = {y, y, y - 1, y + 1};
850 for (int i = 0; i < 4; ++i) {
851 if (!copy.inside(nxs[i], nys[i]))
852 continue;
853 if (colorExact(copy.get(nxs[i], nys[i]), p))
854 ++same;
855 }
856 if (same < 3)
857 continue;
858
859 const float dx = ((float)x + 0.5f) - dstCx;
860 const float dy = ((float)y + 0.5f) - dstCy;
861 float relX, relY;
862 inverseRotatePoint(dx, dy, c, s, relX, relY);
863 const int sx = (int)std::floor(relX + srcCx);
864 const int sy = (int)std::floor(relY + srcCy);
865 if (!orig.inside(sx, sy))
866 continue;
867 const Colorf srcPix = orig.get(sx, sy);
868 if (!colorExact(srcPix, p))
869 dst.set(x, y, srcPix);
870 }
871 }
872}
873
874ImageData *rotateRotSprite(const ImageData *src, float radians, bool expand) {
875 PixelBuffer orig = bufferFromImage(src);
876
877 // Normalize angle to [0, 2π).
878 const float twoPi = 6.28318530717958647692f;
879 float ang = std::fmod(radians, twoPi);
880 if (ang < 0.f)
881 ang += twoPi;
882
883 // Exact quarter turns: no upscale needed.
884 const float halfPi = 1.57079632679489661923f;
885 const float pi = 3.14159265358979323846f;
886 const float eps = 1e-4f;
887 auto nearMul = [&](float target) {
888 return std::fabs(ang - target) < eps || std::fabs(ang - target - twoPi) < eps;
889 };
890 if (nearMul(0.f)) {
891 if (!expand)
892 return src->clone();
893 return imageFromBuffer(orig, src->getFormat());
894 }
895 if (nearMul(halfPi) || nearMul(pi) || nearMul(halfPi * 3.f)) {
896 int turns = 1;
897 if (nearMul(pi))
898 turns = 2;
899 else if (nearMul(halfPi * 3.f))
900 turns = 3;
901 PixelBuffer rotated = rotateExact90(orig, turns);
902 if (!expand) {
903 // Center the exact rotation into the original canvas.
904 PixelBuffer canvas;
905 canvas.w = orig.w;
906 canvas.h = orig.h;
907 canvas.px.assign((size_t)canvas.w * (size_t)canvas.h, Colorf{0.f, 0.f, 0.f, 0.f});
908 const int ox = (canvas.w - rotated.w) / 2;
909 const int oy = (canvas.h - rotated.h) / 2;
910 for (int y = 0; y < rotated.h; ++y)
911 for (int x = 0; x < rotated.w; ++x) {
912 const int dx = x + ox;
913 const int dy = y + oy;
914 if (canvas.inside(dx, dy))
915 canvas.set(dx, dy, rotated.get(x, y));
916 }
917 return imageFromBuffer(canvas, src->getFormat());
918 }
919 return imageFromBuffer(rotated, src->getFormat());
920 }
921
922 constexpr int kScale = 8;
923 PixelBuffer hi = upscaleRotSprite(orig);
924
925 int bestOx = 0, bestOy = 0;
926 findBestOffset(hi, ang, kScale, bestOx, bestOy);
927
928 const float c = std::cos(ang);
929 const float s = std::sin(ang);
930 int dstW = 0, dstH = 0;
931 float dstCx = 0.f, dstCy = 0.f;
932 computeRotatedSize(orig.w, orig.h, c, s, expand, dstW, dstH, dstCx, dstCy);
933
934 const float hiCx = hi.w * 0.5f;
935 const float hiCy = hi.h * 0.5f;
936
937 PixelBuffer out;
938 out.w = dstW;
939 out.h = dstH;
940 out.px.assign((size_t)out.w * (size_t)out.h, Colorf{0.f, 0.f, 0.f, 0.f});
941
942 for (int yt = 0; yt < dstH; ++yt) {
943 const float dy = ((float)yt + 0.5f) - dstCy;
944 for (int xt = 0; xt < dstW; ++xt) {
945 const float dx = ((float)xt + 0.5f) - dstCx;
946 float relX, relY;
947 inverseRotatePoint(dx * (float)kScale, dy * (float)kScale, c, s, relX, relY);
948 const int sx = (int)std::floor(relX + hiCx + (float)bestOx);
949 const int sy = (int)std::floor(relY + hiCy + (float)bestOy);
950 if (hi.inside(sx, sy))
951 out.set(xt, yt, hi.get(sx, sy));
952 }
953 }
954
955 restoreSinglePixelDetails(out, orig, ang);
956 return imageFromBuffer(out, src->getFormat());
957}
958
959} // namespace
960
961ImageData *ImageData::rotate(float radians, std::string filter, bool expand) const {
962 if (pixelGetFunction == nullptr || pixelSetFunction == nullptr)
963 throw eve::Exception("Unhandled pixel format %s in ImageData::rotate", format.c_str());
964
965 const RotateFilter mode = parseRotateFilter(filter);
966 if (mode == RotateFilter::RotSprite)
967 return rotateRotSprite(this, radians, expand);
968
969 const float srcW = (float)width;
970 const float srcH = (float)height;
971 const float srcCx = srcW * 0.5f;
972 const float srcCy = srcH * 0.5f;
973
974 const float c = std::cos(radians);
975 const float s = std::sin(radians);
976
977 int dstW = width;
978 int dstH = height;
979 float dstCx = srcCx;
980 float dstCy = srcCy;
981 computeRotatedSize(width, height, c, s, expand, dstW, dstH, dstCx, dstCy);
982
983 ImageData *dst = nullptr;
984 try {
985 dst = new ImageData(dstW, dstH, format);
986 } catch (std::bad_alloc &) {
987 throw eve::Exception("Out of memory");
988 }
989
990 // Near-identity: copy when angle is ~0 and canvas unchanged.
991 if (!expand && std::fabs(radians) < 1e-7f) {
992 std::memcpy(dst->getData(), data, getSize());
993 return dst;
994 }
995
996 for (int yt = 0; yt < dstH; ++yt) {
997 const float dy = ((float)yt + 0.5f) - dstCy;
998 for (int xt = 0; xt < dstW; ++xt) {
999 const float dx = ((float)xt + 0.5f) - dstCx;
1000 float relX, relY;
1001 inverseRotatePoint(dx, dy, c, s, relX, relY);
1002 const float sx = relX + srcCx - 0.5f;
1003 const float sy = relY + srcCy - 0.5f;
1004
1005 Colorf color;
1006 if (mode == RotateFilter::Nearest)
1007 color = sampleNearest(this, sx, sy);
1008 else
1009 color = sampleBilinear(this, sx, sy);
1010
1011 if (color.a != 0.f || color.r != 0.f || color.g != 0.f || color.b != 0.f)
1012 dst->setPixel(xt, yt, color);
1013 }
1014 }
1015
1016 return dst;
1017}
1018
1019union Row {
1020 uint8_t *u8;
1021 uint16_t *u16;
1022 float16 *f16;
1023 float *f32;
1024};
1025
1026static void pasteRGBA8toRGBA16(Row src, Row dst, int w) {
1027 for (int i = 0; i < w * 4; i++) dst.u16[i] = (uint16_t)src.u8[i] << 8u;
1028}
1029
1030static void pasteRGBA8toRGBA16F(Row src, Row dst, int w) {
1031 for (int i = 0; i < w * 4; i++) dst.f16[i] = float32to16(src.u8[i] / 255.0f);
1032}
1033
1034static void pasteRGBA8toRGBA32F(Row src, Row dst, int w) {
1035 for (int i = 0; i < w * 4; i++) dst.f32[i] = src.u8[i] / 255.0f;
1036}
1037
1038static void pasteRGBA16toRGBA8(Row src, Row dst, int w) {
1039 for (int i = 0; i < w * 4; i++) dst.u8[i] = src.u16[i] >> 8u;
1040}
1041
1042static void pasteRGBA16toRGBA16F(Row src, Row dst, int w) {
1043 for (int i = 0; i < w * 4; i++) dst.f16[i] = float32to16(src.u16[i] / 65535.0f);
1044}
1045
1046static void pasteRGBA16toRGBA32F(Row src, Row dst, int w) {
1047 for (int i = 0; i < w * 4; i++) dst.f32[i] = src.u16[i] / 65535.0f;
1048}
1049
1050static void pasteRGBA16FtoRGBA8(Row src, Row dst, int w) {
1051 for (int i = 0; i < w * 4; i++) dst.u8[i] = (uint8_t)(clamp01(float16to32(src.f16[i])) * 255.0f + 0.5f);
1052}
1053
1054static void pasteRGBA16FtoRGBA16(Row src, Row dst, int w) {
1055 for (int i = 0; i < w * 4; i++) dst.u16[i] = (uint16_t)(clamp01(float16to32(src.f16[i])) * 65535.0f + 0.5f);
1056}
1057
1058static void pasteRGBA16FtoRGBA32F(Row src, Row dst, int w) {
1059 for (int i = 0; i < w * 4; i++) dst.f32[i] = float16to32(src.f16[i]);
1060}
1061
1062static void pasteRGBA32FtoRGBA8(Row src, Row dst, int w) {
1063 for (int i = 0; i < w * 4; i++) dst.u8[i] = (uint8_t)(clamp01(src.f32[i]) * 255.0f + 0.5f);
1064}
1065
1066static void pasteRGBA32FtoRGBA16(Row src, Row dst, int w) {
1067 for (int i = 0; i < w * 4; i++) dst.u16[i] = (uint16_t)(clamp01(src.f32[i]) * 65535.0f + 0.5f);
1068}
1069
1070static void pasteRGBA32FtoRGBA16F(Row src, Row dst, int w) {
1071 for (int i = 0; i < w * 4; i++) dst.f16[i] = float32to16(src.f32[i]);
1072}
1073
1074void ImageData::paste(ImageData *src, int dx, int dy, int sx, int sy, int sw, int sh) {
1075 EV_PARAM_CHECK(src != nullptr, "source ImageData must not be null");
1076
1077 std::string dstformat = getFormat();
1078 std::string srcformat = src->getFormat();
1079
1080 int srcW = src->getWidth();
1081 int srcH = src->getHeight();
1082 int dstW = getWidth();
1083 int dstH = getHeight();
1084
1085 size_t srcpixelsize = src->getPixelSize();
1086 size_t dstpixelsize = getPixelSize();
1087
1088 // Check bounds; if the data ends up completely out of bounds, get out early.
1089 if (sx >= srcW || sx + sw < 0 || sy >= srcH || sy + sh < 0 || dx >= dstW || dx + sw < 0 || dy >= dstH ||
1090 dy + sh < 0)
1091 return;
1092
1093 // Normalize values to the inside of both images.
1094 if (dx < 0) {
1095 sw += dx;
1096 sx -= dx;
1097 dx = 0;
1098 }
1099 if (dy < 0) {
1100 sh += dy;
1101 sy -= dy;
1102 dy = 0;
1103 }
1104 if (sx < 0) {
1105 sw += sx;
1106 dx -= sx;
1107 sx = 0;
1108 }
1109 if (sy < 0) {
1110 sh += sy;
1111 dy -= sy;
1112 sy = 0;
1113 }
1114
1115 if (dx + sw > dstW) sw = dstW - dx;
1116
1117 if (dy + sh > dstH) sh = dstH - dy;
1118
1119 if (sx + sw > srcW) sw = srcW - sx;
1120
1121 if (sy + sh > srcH) sh = srcH - sy;
1122
1123 uint8_t *s = (uint8_t *)src->getData();
1124 uint8_t *d = (uint8_t *)getData();
1125
1126 auto getfunction = src->pixelGetFunction;
1127 auto setfunction = pixelSetFunction;
1128
1129 // If the dimensions match up, copy the entire memory stream in one go
1130 if (srcformat == dstformat && (sw == dstW && dstW == srcW && sh == dstH && dstH == srcH)) {
1131 memcpy(d, s, srcpixelsize * sw * sh);
1132 } else if (sw > 0) {
1133 // Otherwise, copy each row individually.
1134 for (int i = 0; i < sh; i++) {
1135 Row rowsrc = {s + (sx + (i + sy) * srcW) * srcpixelsize};
1136 Row rowdst = {d + (dx + (i + dy) * dstW) * dstpixelsize};
1137
1138 if (srcformat == dstformat)
1139 memcpy(rowdst.u8, rowsrc.u8, srcpixelsize * sw);
1140
1141 else if (srcformat == "RGBA8" && dstformat == "RGBA16")
1142 pasteRGBA8toRGBA16(rowsrc, rowdst, sw);
1143 else if (srcformat == "RGBA8" && dstformat == "RGBA16F")
1144 pasteRGBA8toRGBA16F(rowsrc, rowdst, sw);
1145 else if (srcformat == "RGBA8" && dstformat == "RGBA32F")
1146 pasteRGBA8toRGBA32F(rowsrc, rowdst, sw);
1147
1148 else if (srcformat == "RGBA16" && dstformat == "RGBA8")
1149 pasteRGBA16toRGBA8(rowsrc, rowdst, sw);
1150 else if (srcformat == "RGBA16" && dstformat == "RGBA16F")
1151 pasteRGBA16toRGBA16F(rowsrc, rowdst, sw);
1152 else if (srcformat == "RGBA16" && dstformat == "RGBA32F")
1153 pasteRGBA16toRGBA32F(rowsrc, rowdst, sw);
1154
1155 else if (srcformat == "RGBA16F" && dstformat == "RGBA8")
1156 pasteRGBA16FtoRGBA8(rowsrc, rowdst, sw);
1157 else if (srcformat == "RGBA16F" && dstformat == "RGBA16")
1158 pasteRGBA16FtoRGBA16(rowsrc, rowdst, sw);
1159 else if (srcformat == "RGBA16F" && dstformat == "RGBA32F")
1160 pasteRGBA16FtoRGBA32F(rowsrc, rowdst, sw);
1161
1162 else if (srcformat == "RGBA32F" && dstformat == "RGBA8")
1163 pasteRGBA32FtoRGBA8(rowsrc, rowdst, sw);
1164 else if (srcformat == "RGBA32F" && dstformat == "RGBA16")
1165 pasteRGBA32FtoRGBA16(rowsrc, rowdst, sw);
1166 else if (srcformat == "RGBA32F" && dstformat == "RGBA16F")
1167 pasteRGBA32FtoRGBA16F(rowsrc, rowdst, sw);
1168
1169 else {
1170 // Slow path: convert src -> Colorf -> dst.
1171 Colorf c;
1172 for (int x = 0; x < sw; x++) {
1173 auto srcp = (const Pixel *)(rowsrc.u8 + x * srcpixelsize);
1174 auto dstp = (Pixel *)(rowdst.u8 + x * dstpixelsize);
1175 getfunction(srcp, c);
1176 setfunction(c, dstp);
1177 }
1178 }
1179 }
1180 }
1181}
1182
1183
1184size_t ImageData::getPixelSize() const { return getPixelFormatSize(getPixelFormatFromName(format)); }
1185
1186bool ImageData::validPixelFormat(std::string format) {
1187 if (format == "R8") return true;
1188 if (format == "RG8") return true;
1189 if (format == "RGBA8") return true;
1190 if (format == "R16") return true;
1191 if (format == "RG16") return true;
1192 if (format == "RGBA16") return true;
1193 if (format == "R16F") return true;
1194 if (format == "RG16F") return true;
1195 if (format == "RGBA16F") return true;
1196 if (format == "R32F") return true;
1197 if (format == "RG32F") return true;
1198 if (format == "RGBA32F") return true;
1199 if (format == "RGBA4") return true;
1200 if (format == "RGB5A1") return true;
1201 if (format == "RGB565") return true;
1202 if (format == "RGB10A2") return true;
1203 if (format == "RG11B10F") return true;
1204 return false;
1205}
1206
1207bool ImageData::canPaste(std::string src, std::string dst) {
1208 if (src == dst) return true;
1209
1210 if (!(src == "RGBA8" || src == "RGBA16" || src == "RGBA16F" || src == "RGBA32F")) return false;
1211
1212 if (!(dst == "RGBA8" || dst == "RGBA16" || dst == "RGBA16F" || dst == "RGBA32F")) return false;
1213
1214 return true;
1215}
1216
1217ImageData::PixelSetFunction ImageData::getPixelSetFunction(std::string format) {
1218 if (format == "R8") return setPixelR8;
1219 if (format == "RG8") return setPixelRG8;
1220 if (format == "RGBA8") return setPixelRGBA8;
1221 if (format == "R16") return setPixelR16;
1222 if (format == "RG16") return setPixelRG16;
1223 if (format == "RGBA16") return setPixelRGBA16;
1224 if (format == "R16F") return setPixelR16F;
1225 if (format == "RG16F") return setPixelRG16F;
1226 if (format == "RGBA16F") return setPixelRGBA16F;
1227 if (format == "R32F") return setPixelR32F;
1228 if (format == "RG32F") return setPixelRG32F;
1229 if (format == "RGBA32F") return setPixelRGBA32F;
1230 if (format == "RGBA4") return setPixelRGBA4;
1231 if (format == "RGB5A1") return setPixelRGB5A1;
1232 if (format == "RGB565") return setPixelRGB565;
1233 if (format == "RGB10A2") return setPixelRGB10A2;
1234 if (format == "RG11B10F") return setPixelRG11B10F;
1235 return nullptr;
1236}
1237
1238ImageData::PixelGetFunction ImageData::getPixelGetFunction(std::string format) {
1239 if (format == "R8") return getPixelR8;
1240 if (format == "RG8") return getPixelRG8;
1241 if (format == "RGBA8") return getPixelRGBA8;
1242 if (format == "R16") return getPixelR16;
1243 if (format == "RG16") return getPixelRG16;
1244 if (format == "RGBA16") return getPixelRGBA16;
1245 if (format == "R16F") return getPixelR16F;
1246 if (format == "RG16F") return getPixelRG16F;
1247 if (format == "RGBA16F") return getPixelRGBA16F;
1248 if (format == "R32F") return getPixelR32F;
1249 if (format == "RG32F") return getPixelRG32F;
1250 if (format == "RGBA32F") return getPixelRGBA32F;
1251 if (format == "RGBA4") return getPixelRGBA4;
1252 if (format == "RGB5A1") return getPixelRGB5A1;
1253 if (format == "RGB565") return getPixelRGB565;
1254 if (format == "RGB10A2") return getPixelRGB10A2;
1255 if (format == "RG11B10F") return getPixelRG11B10F;
1256 return nullptr;
1257}
1258
1259} // namespace image
1260} // namespace eve
LogicalId target
double value
double score
Definition Agent.cpp:50
float w
Definition AnimClip.cpp:738
float y
Definition AnimClip.cpp:738
float x
Definition AnimClip.cpp:738
EVEngine assertion entry point, backed by zeroerr.
#define EV_PARAM_CHECK(cond,...)
Validate a function parameter / public API precondition.
Definition Assert.h:30
const std::string & s
float ang
float cx
Definition CardTypes.cpp:33
float cy
Definition CardTypes.cpp:34
glm::vec4 p[6]
tensor::Graph g
Definition GpuGraph.cpp:7
vk::UniqueImage image
float u
Definition Grass.cpp:233
double r
float v
HexVec3 up
HexVec3 left
HexVec3 right
std::int32_t c
int h
int w
std::vector< Colorf > px
std::uint32_t height
std::uint32_t width
std::array< float, 3 > scale
HSQOBJECT handler
std::string name
MeleePoint3 b
Definition MeleeHit.cpp:41
MeleePoint3 a
Definition MeleeHit.cpp:40
float d
float t
uint8_t * pixels
std::string filter
float dy
float dx
const UnitySourceAsset & source
std::size_t at
double oy
std::vector< char > inside
double ox
int turns
Data public API.
Definition Data.h:8
static Diagnostic error(DiagnosticCode code, std::string message, std::string path={}, DiagnosticDetails details={}, std::string source={})
Construct an error diagnostic with the standard error severity.
Definition Diagnostic.h:125
EVENGINE_API_FOUNDATION public API.
Definition Exception.h:13
Resource is a game object that is managed by the ResourceManager. It can be loaded from a file or gen...
Definition Resource.h:51
Move-only operation result carrying either a value or Status.
Definition Result.h:155
static Result success(T value)
Construct a successful result owning value.
Definition Result.h:164
static Result failure(Status status)
Construct a failed result from a structured status.
Definition Result.h:175
Data buffer paired with a filename (used for type identification).
Definition FileData.h:14
size_t getSize() const
Byte length of the owned buffer.
Definition FileData.h:29
void * getData() const
Pointer to the owned byte buffer.
Definition FileData.h:27
Represents raw pixel data.
Definition ImageData.h:38
ImageData * clone() const
Definition ImageData.cpp:73
filesystem::FileData * encode(FormatHandler::EncodedFormat format, const char *filename, bool writefile) const
Encodes raw pixel data into a given format.
void setPixel(int x, int y, const Colorf &p)
Sets the pixel at location (x,y).
medialoader::Colorf Colorf
Definition ImageData.h:41
size_t getSize() const
PixelSetFunction getPixelSetFunction() const
Definition ImageData.h:173
static bool validPixelFormat(std::string format)
void * getData() const
PixelGetFunction getPixelGetFunction() const
Definition ImageData.h:174
size_t getPixelSize() const
ImageData(Data *data)
Definition ImageData.cpp:25
void adopt(eve::Resource &replacement) override
Replace this instance's pixels with replacement's (cache reload). The replacement is drained; its des...
Definition ImageData.cpp:62
medialoader::FormatHandler FormatHandler
Definition ImageData.h:42
std::string getFormat() const
constexpr HexDirection next(HexDirection d) noexcept
The next direction clockwise (NW wraps to NE).
Definition HexMetrics.h:76
Result< UvPaintRegion > prepareUvPaintRegionResult(int targetWidth, int targetHeight, float u, float v, float radiusU, float radiusV, float r, float g, float b, float a, bool flipV)
Validate and prepare the canonical clipped region for a UV brush.
Build metadata (engine git commit, build time, third-party version).
Definition Build.cpp:16
Owning receipt for one UV-space raster paint operation.
Definition ImageData.h:29
Pixel public API.
Definition ImageData.h:46
glm::vec4 color