| 87 | } |
| 88 | |
| 89 | bool WritePAA(const std::string& path, const Image& img, PixelFormat format) |
| 90 | { |
| 91 | if (!img.valid()) |
| 92 | return false; |
| 93 | |
| 94 | uint16_t magic = paaMagicForFormat(format); |
| 95 | if (magic == 0) |
| 96 | return false; // Format not supported as PAA |
| 97 | |
| 98 | // Convert to RGBA first if needed |
| 99 | Image rgba = img.ToRGBA(); |
| 100 | if (!rgba.valid()) |
| 101 | return false; |
| 102 | |
| 103 | // Generate mipmaps (up to 8 levels, matching engine expectations) |
| 104 | auto mipmaps = generateMipmaps(rgba, 8); |
| 105 | |
| 106 | FILE* f = fopen(path.c_str(), "wb"); |
| 107 | if (!f) |
| 108 | return false; |
| 109 | |
| 110 | // 1. Format magic word |
| 111 | writeU16(f, magic); |
| 112 | |
| 113 | // 2. No TAGG sections (minimal PAA — valid without them) |
| 114 | |
| 115 | // 3. Palette (0 = no palette for non-P8 formats) |
| 116 | writeU16(f, 0); |
| 117 | |
| 118 | // 4. Mipmap levels |
| 119 | for (const auto& level : mipmaps) |
| 120 | { |
| 121 | auto encoded = encodeLevel(level, format); |
| 122 | writeU16(f, static_cast<uint16_t>(level.width())); |
| 123 | writeU16(f, static_cast<uint16_t>(level.height())); |
| 124 | writeU24(f, static_cast<uint32_t>(encoded.size())); |
| 125 | fwrite(encoded.data(), 1, encoded.size(), f); |
| 126 | } |
| 127 | |
| 128 | // 5. Terminator |
| 129 | writeU16(f, 0); |
| 130 | writeU16(f, 0); |
| 131 | |
| 132 | fclose(f); |
| 133 | return true; |
| 134 | } |
| 135 | |
| 136 | bool WritePAA(const std::string& path, const Image& img) |
| 137 | { |
no test coverage detected