| 406 | } |
| 407 | |
| 408 | Image Image::FromFile(const std::string& path) |
| 409 | { |
| 410 | auto dot = path.find_last_of('.'); |
| 411 | if (dot == std::string::npos) |
| 412 | return {}; |
| 413 | |
| 414 | std::string ext = path.substr(dot); |
| 415 | std::transform(ext.begin(), ext.end(), ext.begin(), ::tolower); |
| 416 | |
| 417 | // PAA/PAC: use existing decoder → always RGBA8888 |
| 418 | if (ext == ".paa" || ext == ".pac") |
| 419 | { |
| 420 | auto decoded = DecodePAAFile(path); |
| 421 | if (!decoded.valid()) |
| 422 | return {}; |
| 423 | return {decoded.width, decoded.height, PixelFormat::RGBA8888, std::move(decoded.rgba)}; |
| 424 | } |
| 425 | |
| 426 | // PNG/BMP/TGA/JPG: use stb_image → always RGBA8888 |
| 427 | if (ext == ".png" || ext == ".bmp" || ext == ".tga" || ext == ".jpg" || ext == ".jpeg") |
| 428 | { |
| 429 | int w = 0, h = 0, channels = 0; |
| 430 | uint8_t* pixels = stbi_load(path.c_str(), &w, &h, &channels, 4); // force RGBA |
| 431 | if (!pixels) |
| 432 | return {}; |
| 433 | std::vector<uint8_t> data(pixels, pixels + w * h * 4); |
| 434 | stbi_image_free(pixels); |
| 435 | return {w, h, PixelFormat::RGBA8888, std::move(data)}; |
| 436 | } |
| 437 | |
| 438 | // DDS: read first mipmap, decode DXT if needed → RGBA8888 |
| 439 | if (ext == ".dds") |
| 440 | { |
| 441 | auto dds = DDSConverter::ReadDDS(path); |
| 442 | if (!dds.valid()) |
| 443 | return {}; |
| 444 | if (dds.format == PixelFormat::RGBA8888) |
| 445 | return {dds.width, dds.height, PixelFormat::RGBA8888, std::move(dds.mipmaps[0].data)}; |
| 446 | // DXT: store as-is (Image can hold DXT data) |
| 447 | return {dds.width, dds.height, dds.format, std::move(dds.mipmaps[0].data)}; |
| 448 | } |
| 449 | |
| 450 | return {}; |
| 451 | } |
| 452 | |
| 453 | Image Image::ToRGBA() const |
| 454 | { |
nothing calls this directly
no test coverage detected