8#include "medialoader/image/pixelformat.h"
42 this->data = (
unsigned char *)data;
52 create(width, height, format,
c.getData());
57 decodeHandler->freeRawPixels(data);
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);
76 }
catch (std::bad_alloc &) {
81void ImageData::create(
int w,
int h, std::string pixelFormat,
void *
pixels) {
87 w *
h * getPixelFormatSize(getPixelFormatFromName(pixelFormat));
90 this->data =
new unsigned char[datasize];
91 }
catch (std::bad_alloc &) {
97 decodeHandler =
nullptr;
98 this->format = pixelFormat;
104void ImageData::decode(Data *
source) {
106 FormatHandler::DecodedImage decodedimage;
108 auto module = Image::create();
110 if (module ==
nullptr)
throw eve::Exception(
"eve.image must be loaded in order to decode an ImageData.");
120 decodedimage = decoder->decode((
const char*)
source->getData(),
source->getSize());
122 if (decodedimage.data ==
nullptr) {
123 auto filedata =
dynamic_cast<filesystem::FileData *
>(
source);
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());
129 throw eve::Exception(
"Could not decode data to ImageData: unsupported encoded format");
132 if (decodedimage.size !=
133 size_t(decodedimage.width) *
size_t(decodedimage.height) *
134 size_t(getPixelFormatSize(decodedimage.format))) {
135 decoder->freeRawPixels(decodedimage.data);
141 decodeHandler->freeRawPixels(this->data);
145 this->width = decodedimage.width;
146 this->height = decodedimage.height;
147 this->data = decodedimage.data;
149 this->format = [pf = decodedimage.format]() -> std::string {
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";
173 decodeHandler = decoder;
180 bool writefile)
const {
181 EV_PARAM_CHECK(filename !=
nullptr,
"output filename must not be null");
184 FormatHandler::EncodedImage encodedimage;
185 FormatHandler::DecodedImage rawimage;
187 rawimage.width = width;
188 rawimage.height = height;
190 rawimage.data = data;
191 rawimage.format = getPixelFormatFromName(format);
193 auto module = Image::create();
195 if (module ==
nullptr)
throw eve::Exception(
"eve.image must be loaded in order to encode an ImageData.");
198 if (handler->canEncode(getPixelFormatFromName(format), encodedFormat)) {
204 if (encoder !=
nullptr) {
205 encodedimage = encoder->encode(rawimage, encodedFormat);
208 if (encoder ==
nullptr || encodedimage.data ==
nullptr) {
209 throw eve::Exception(
"No suitable image encoder for %s format.", format.c_str());
217 encoder->freeRawPixels(encodedimage.data);
221 memcpy(filedata->
getData(), encodedimage.data, encodedimage.size);
222 encoder->freeRawPixels(encodedimage.data);
225 auto fs = eve::filesystem::Filesystem::create();
229 throw eve::Exception(
"eve.filesystem must be loaded in order to write an encoded ImageData to a file.");
243size_t ImageData::getSize()
const {
return size_t(getWidth() * getHeight()) * getPixelSize(); }
245void *ImageData::getData()
const {
return data; }
247bool ImageData::isSRGB()
const {
return false; }
249bool ImageData::inside(
int x,
int y)
const {
return x >= 0 &&
x < getWidth() &&
y >= 0 &&
y < getHeight(); }
251int ImageData::getWidth()
const {
return width; }
253int ImageData::getHeight()
const {
return height; }
255std::string ImageData::getFormat()
const {
return format; }
257static float clamp01(
float x) {
return std::min(std::max(
x, 0.0f), 1.0f); }
259static void setPixelR8(
const Colorf &
c, ImageData::Pixel *
p) {
p->rgba8[0] = (uint8_t)(clamp01(
c.r) * 255.0f + 0.5f); }
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);
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);
273static void setPixelR16(
const Colorf &
c, ImageData::Pixel *
p) {
274 p->rgba16[0] = (uint16_t)(clamp01(
c.r) * 65535.0f + 0.5f);
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);
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);
289static void setPixelR16F(
const Colorf &
c, ImageData::Pixel *
p) {
p->rgba16f[0] = float32to16(
c.r); }
291static void setPixelRG16F(
const Colorf &
c, ImageData::Pixel *
p) {
292 p->rgba16f[0] = float32to16(
c.r);
293 p->rgba16f[1] = float32to16(
c.g);
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);
303static void setPixelR32F(
const Colorf &
c, ImageData::Pixel *
p) {
p->rgba32f[0] =
c.r; }
305static void setPixelRG32F(
const Colorf &
c, ImageData::Pixel *
p) {
310static void setPixelRGBA32F(
const Colorf &
c, ImageData::Pixel *
p) {
317static void setPixelRGBA4(
const Colorf &
c, ImageData::Pixel *
p) {
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);
326static void setPixelRGB5A1(
const Colorf &
c, ImageData::Pixel *
p) {
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);
335static void setPixelRGB565(
const Colorf &
c, ImageData::Pixel *
p) {
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);
343static void setPixelRGB10A2(
const Colorf &
c, ImageData::Pixel *
p) {
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);
352static void setPixelRG11B10F(
const Colorf &
c, ImageData::Pixel *
p) {
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);
360static void getPixelR8(
const ImageData::Pixel *
p, Colorf &
c) {
361 c.r =
p->rgba8[0] / 255.0f;
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;
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;
381static void getPixelR16(
const ImageData::Pixel *
p, Colorf &
c) {
382 c.r =
p->rgba16[0] / 65535.0f;
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;
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;
402static void getPixelR16F(
const ImageData::Pixel *
p, Colorf &
c) {
403 c.r = float16to32(
p->rgba16f[0]);
409static void getPixelRG16F(
const ImageData::Pixel *
p, Colorf &
c) {
410 c.r = float16to32(
p->rgba16f[0]);
411 c.g = float16to32(
p->rgba16f[1]);
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]);
423static void getPixelR32F(
const ImageData::Pixel *
p, Colorf &
c) {
430static void getPixelRG32F(
const ImageData::Pixel *
p, Colorf &
c) {
437static void getPixelRGBA32F(
const ImageData::Pixel *
p, Colorf &
c) {
444static void getPixelRGBA4(
const ImageData::Pixel *
p, Colorf &
c) {
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;
452static void getPixelRGB5A1(
const ImageData::Pixel *
p, Colorf &
c) {
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;
460static void getPixelRGB565(
const ImageData::Pixel *
p, Colorf &
c) {
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;
468static void getPixelRGB10A2(
const ImageData::Pixel *
p, Colorf &
c) {
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;
476static void getPixelRG11B10F(
const ImageData::Pixel *
p, Colorf &
c) {
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));
484void ImageData::setPixel(
int x,
int y,
const Colorf &
c) {
487 if (pixelSetFunction ==
nullptr)
throw eve::Exception(
"Unhandled pixel format %s in ImageData::setPixel", format.c_str());
493 const size_t pixelsize = getPixelSize();
495 pixelSetFunction(
c, &tmp);
496 std::memcpy(data + ((
y *
width +
x) * pixelsize), &tmp, pixelsize);
502 const int w = getWidth(),
h = getHeight();
503 if (
w <= 0 ||
h <= 0)
506 auto normalize = [](
float value,
bool wrap) {
507 if (!wrap)
return std::clamp(
value, 0.f, 1.f);
510 const float normalizedU = normalize(
u, wrapU);
511 const float normalizedV = normalize(
v, wrapV);
513 w,
h, normalizedU, normalizedV, radiusPixels /
float(
w), radiusPixels /
float(
h),
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;
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;
525 if (wrapU)
x = (
x %
w +
w) %
w;
526 if (wrapV)
y = (
y %
h +
h) %
h;
529 ++receipt.changedPixelCount;
535void ImageData::getPixel(
int x,
int y,
Colorf &
c)
const {
538 if (pixelGetFunction ==
nullptr)
throw eve::Exception(
"Unhandled pixel format %s in ImageData::getPixel", format.c_str());
540 const size_t pixelsize = getPixelSize();
542 std::memcpy(&tmp, data + ((
y *
width +
x) * pixelsize), pixelsize);
543 pixelGetFunction(&tmp,
c);
554enum class RotateFilter {
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" ||
568 return RotateFilter::RotSprite;
570 "Unsupported ImageData rotate filter '%s' (use \"nearest\", \"linear\", or \"rotsprite\")",
574void rotatePoint(
float dx,
float dy,
float c,
float s,
float &
ox,
float &
oy) {
580void inverseRotatePoint(
float dx,
float dy,
float c,
float s,
float &
ox,
float &
oy) {
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;
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);
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;
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);
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;
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);
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);
636 const float w00 = ifx * ify;
637 const float w10 = fx * ify;
638 const float w01 = ifx * fy;
639 const float w11 = fx * fy;
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;
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;
660 const float corners[4][2] = {
662 {srcW - srcCx, -srcCy},
663 {-srcCx, srcH - srcCy},
664 {srcW - srcCx, srcH - srcCy},
666 float minX = 0.f, maxX = 0.f, minY = 0.f, maxY = 0.f;
667 for (
int i = 0; i < 4; ++i) {
669 rotatePoint(corners[i][0], corners[i][1],
c,
s,
ox,
oy);
674 minX = std::min(minX,
ox);
675 maxX = std::max(maxX,
ox);
676 minY = std::min(minY,
oy);
677 maxY = std::max(maxY,
oy);
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)));
691 std::vector<Colorf>
px;
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; }
698PixelBuffer bufferFromImage(
const ImageData *img) {
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));
709ImageData *imageFromBuffer(
const PixelBuffer &
b,
const std::string &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);
720void scale2xBlock(Colorf
center, Colorf
up, Colorf
left, Colorf down, Colorf
right, Colorf &
a,
721 Colorf &
b, Colorf &
c, Colorf &
d) {
729 d = (colorSimilar(
right, down) && !colorSimilar(
right,
up) && !colorSimilar(down,
left))
734PixelBuffer scale2x(
const PixelBuffer &src) {
738 dst.px.assign((
size_t)dst.w * (
size_t)dst.h, Colorf{0.f, 0.f, 0.f, 0.f});
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);
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);
766PixelBuffer upscaleRotSprite(
const PixelBuffer &src) {
768 PixelBuffer s2 = scale2x(src);
769 PixelBuffer s4 = scale2x(s2);
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;
779 int outW = 0, outH = 0;
780 float outCx = 0.f, outCy = 0.f;
782 computeRotatedSize(hi.w /
scale, hi.h /
scale,
c,
s,
true, outW, outH, outCx, outCy);
786 double bestScore = std::numeric_limits<double>::infinity();
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;
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))
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));
808 if (
score < bestScore) {
817PixelBuffer rotateExact90(
const PixelBuffer &src,
int turnsClockwise) {
818 turnsClockwise = ((turnsClockwise % 4) + 4) % 4;
819 if (turnsClockwise == 0)
821 PixelBuffer cur = src;
822 for (
int t = 0;
t < turnsClockwise; ++
t) {
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);
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;
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);
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]))
853 if (colorExact(copy.get(nxs[i], nys[i]),
p))
859 const float dx = ((float)
x + 0.5f) - dstCx;
860 const float dy = ((float)
y + 0.5f) - dstCy;
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))
867 const Colorf srcPix = orig.get(
sx,
sy);
868 if (!colorExact(srcPix,
p))
869 dst.set(
x,
y, srcPix);
874ImageData *rotateRotSprite(
const ImageData *src,
float radians,
bool expand) {
875 PixelBuffer orig = bufferFromImage(src);
878 const float twoPi = 6.28318530717958647692f;
879 float ang = std::fmod(radians, twoPi);
884 const float halfPi = 1.57079632679489661923f;
885 const float pi = 3.14159265358979323846f;
886 const float eps = 1e-4f;
887 auto nearMul = [&](
float target) {
893 return imageFromBuffer(orig, src->getFormat());
895 if (nearMul(halfPi) || nearMul(pi) || nearMul(halfPi * 3.f)) {
899 else if (nearMul(halfPi * 3.f))
901 PixelBuffer rotated = rotateExact90(orig,
turns);
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));
917 return imageFromBuffer(canvas, src->getFormat());
919 return imageFromBuffer(rotated, src->getFormat());
922 constexpr int kScale = 8;
923 PixelBuffer hi = upscaleRotSprite(orig);
925 int bestOx = 0, bestOy = 0;
926 findBestOffset(hi,
ang, kScale, bestOx, bestOy);
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);
934 const float hiCx = hi.w * 0.5f;
935 const float hiCy = hi.h * 0.5f;
940 out.px.assign((
size_t)out.w * (
size_t)out.h, Colorf{0.f, 0.f, 0.f, 0.f});
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;
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));
955 restoreSinglePixelDetails(out, orig,
ang);
956 return imageFromBuffer(out, src->getFormat());
962 if (pixelGetFunction ==
nullptr || pixelSetFunction ==
nullptr)
963 throw eve::Exception(
"Unhandled pixel format %s in ImageData::rotate", format.c_str());
965 const RotateFilter mode = parseRotateFilter(
filter);
966 if (mode == RotateFilter::RotSprite)
967 return rotateRotSprite(
this, radians, expand);
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;
974 const float c = std::cos(radians);
975 const float s = std::sin(radians);
981 computeRotatedSize(
width,
height,
c,
s, expand, dstW, dstH, dstCx, dstCy);
986 }
catch (std::bad_alloc &) {
991 if (!expand && std::fabs(radians) < 1e-7f) {
992 std::memcpy(dst->
getData(), data, getSize());
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;
1001 inverseRotatePoint(
dx,
dy,
c,
s, relX, relY);
1002 const float sx = relX + srcCx - 0.5f;
1003 const float sy = relY + srcCy - 0.5f;
1006 if (mode == RotateFilter::Nearest)
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;
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);
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;
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;
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);
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;
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);
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);
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]);
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);
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);
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]);
1075 EV_PARAM_CHECK(src !=
nullptr,
"source ImageData must not be null");
1077 std::string dstformat = getFormat();
1078 std::string srcformat = src->
getFormat();
1082 int dstW = getWidth();
1083 int dstH = getHeight();
1086 size_t dstpixelsize = getPixelSize();
1089 if (
sx >= srcW ||
sx + sw < 0 || sy >= srcH ||
sy + sh < 0 || dx >= dstW ||
dx + sw < 0 || dy >= dstH ||
1115 if (
dx + sw > dstW) sw = dstW -
dx;
1117 if (
dy + sh > dstH) sh = dstH -
dy;
1119 if (
sx + sw > srcW) sw = srcW -
sx;
1121 if (
sy + sh > srcH) sh = srcH -
sy;
1123 uint8_t *
s = (uint8_t *)src->
getData();
1124 uint8_t *
d = (uint8_t *)getData();
1126 auto getfunction = src->pixelGetFunction;
1127 auto setfunction = pixelSetFunction;
1130 if (srcformat == dstformat && (sw == dstW && dstW == srcW && sh == dstH && dstH == srcH)) {
1131 memcpy(
d,
s, srcpixelsize * sw * sh);
1132 }
else if (sw > 0) {
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};
1138 if (srcformat == dstformat)
1139 memcpy(rowdst.
u8, rowsrc.
u8, srcpixelsize * sw);
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);
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);
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);
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);
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);
1184size_t ImageData::getPixelSize()
const {
return getPixelFormatSize(getPixelFormatFromName(format)); }
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;
1207bool ImageData::canPaste(std::string src, std::string dst) {
1208 if (src == dst)
return true;
1210 if (!(src ==
"RGBA8" || src ==
"RGBA16" || src ==
"RGBA16F" || src ==
"RGBA32F"))
return false;
1212 if (!(dst ==
"RGBA8" || dst ==
"RGBA16" || dst ==
"RGBA16F" || dst ==
"RGBA32F"))
return false;
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;
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;
EVEngine assertion entry point, backed by zeroerr.
#define EV_PARAM_CHECK(cond,...)
Validate a function parameter / public API precondition.
std::array< float, 3 > scale
const UnitySourceAsset & source
std::vector< char > inside
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.
EVENGINE_API_FOUNDATION public API.
Resource is a game object that is managed by the ResourceManager. It can be loaded from a file or gen...
Move-only operation result carrying either a value or Status.
static Result success(T value)
Construct a successful result owning value.
static Result failure(Status status)
Construct a failed result from a structured status.
Data buffer paired with a filename (used for type identification).
size_t getSize() const
Byte length of the owned buffer.
void * getData() const
Pointer to the owned byte buffer.
Represents raw pixel data.
ImageData * clone() const
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
PixelSetFunction getPixelSetFunction() const
static bool validPixelFormat(std::string format)
PixelGetFunction getPixelGetFunction() const
size_t getPixelSize() const
void adopt(eve::Resource &replacement) override
Replace this instance's pixels with replacement's (cache reload). The replacement is drained; its des...
medialoader::FormatHandler FormatHandler
std::string getFormat() const
constexpr HexDirection next(HexDirection d) noexcept
The next direction clockwise (NW wraps to NE).
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).
Owning receipt for one UV-space raster paint operation.