|
| 1 | +// libwebp through the published module, with the demux and mux features: |
| 2 | +// RGBA with alpha encoded lossy at a quality and lossless and decoded back; |
| 3 | +// the advanced API (WebPConfig, WebPPicture, a memory writer) with its pinned |
| 4 | +// bytes; an animation encoded with WebPAnimEncoder and played back with |
| 5 | +// WebPAnimDecoder; and the error paths. |
| 6 | +import std; |
| 7 | +import mcpplibs.mwrap.libwebp.demux; // re-exports mcpplibs.mwrap.libwebp |
| 8 | +import mcpplibs.mwrap.libwebp.mux; |
| 9 | + |
| 10 | +using namespace mcpplibs::mwrap::libwebp; // the headers' static inline functions, adapted |
| 11 | + |
| 12 | +namespace { |
| 13 | + |
| 14 | +int failures = 0; |
| 15 | + |
| 16 | +void check(bool ok, std::string_view what) { |
| 17 | + std::println("{:<64} {}", what, ok ? "ok" : "FAILED"); |
| 18 | + failures += !ok; |
| 19 | +} |
| 20 | + |
| 21 | +constexpr int W = 96, H = 64; |
| 22 | + |
| 23 | +std::vector<std::uint8_t> picture(int shift) { |
| 24 | + std::vector<std::uint8_t> rgba(static_cast<std::size_t>(W) * H * 4); |
| 25 | + for (int y = 0; y < H; ++y) |
| 26 | + for (int x = 0; x < W; ++x) { |
| 27 | + auto* p = &rgba[(static_cast<std::size_t>(y) * W + x) * 4]; |
| 28 | + p[0] = static_cast<std::uint8_t>((x + shift) * 2); |
| 29 | + p[1] = static_cast<std::uint8_t>(y * 4); |
| 30 | + p[2] = static_cast<std::uint8_t>(255 - x * 2); |
| 31 | + p[3] = static_cast<std::uint8_t>(y < 8 ? 64 : 255); |
| 32 | + } |
| 33 | + return rgba; |
| 34 | +} |
| 35 | + |
| 36 | +std::uint64_t fnv1a(std::span<const std::uint8_t> data) { |
| 37 | + std::uint64_t h = 0xcbf29ce484222325ull; |
| 38 | + for (const auto b : data) h = (h ^ b) * 0x100000001b3ull; |
| 39 | + return h; |
| 40 | +} |
| 41 | + |
| 42 | +} // namespace |
| 43 | + |
| 44 | +int main() { |
| 45 | + const auto rgba = picture(0); |
| 46 | + |
| 47 | + // The simple API, lossy at quality 80 and lossless. |
| 48 | + std::uint8_t *lossy = nullptr, *lossless = nullptr; |
| 49 | + const std::size_t lossy_size = WebPEncodeRGBA(rgba.data(), W, H, W * 4, 80.f, &lossy); |
| 50 | + const std::size_t lossless_size = WebPEncodeLosslessRGBA(rgba.data(), W, H, W * 4, &lossless); |
| 51 | + check(lossy_size > 0 && lossless_size > 0, "WebPEncodeRGBA / WebPEncodeLosslessRGBA"); |
| 52 | + WebPBitstreamFeatures features; |
| 53 | + check(WebPGetFeatures(lossy, lossy_size, &features) == VP8_STATUS_OK && features.format == 1 && features.has_alpha, |
| 54 | + "WebPGetFeatures: lossy, with alpha"); |
| 55 | + int width = 0, height = 0; |
| 56 | + check(WebPGetInfo(lossless, lossless_size, &width, &height) && width == W && height == H, "WebPGetInfo"); |
| 57 | + std::uint8_t* back = WebPDecodeRGBA(lossless, lossless_size, &width, &height); |
| 58 | + check(back && std::equal(rgba.begin(), rgba.end(), back), "lossless round trip, every pixel"); |
| 59 | + WebPFree(back); |
| 60 | + back = WebPDecodeRGBA(lossy, lossy_size, &width, &height); |
| 61 | + int worst = 0; |
| 62 | + bool alpha = back != nullptr; |
| 63 | + for (std::size_t i = 0; back && i < rgba.size(); ++i) |
| 64 | + if (i % 4 == 3) alpha = alpha && back[i] == rgba[i]; |
| 65 | + else if (rgba[i - i % 4 + 3] == 255) worst = std::max(worst, std::abs(int(back[i]) - int(rgba[i]))); |
| 66 | + check(back && alpha && worst < 24, std::format("lossy round trip: alpha exact, colour within {}", worst)); |
| 67 | + WebPFree(back); |
| 68 | + WebPFree(lossy); |
| 69 | + WebPFree(lossless); |
| 70 | + |
| 71 | + // The advanced API: the same settings give the same bytes everywhere. |
| 72 | + WebPConfig config; |
| 73 | + WebPPicture pic; |
| 74 | + WebPMemoryWriter writer; |
| 75 | + check(WebPConfigInit(&config) && WebPPictureInit(&pic), "WebPConfigInit / WebPPictureInit"); |
| 76 | + config.quality = 75; |
| 77 | + config.method = 4; |
| 78 | + pic.use_argb = 1; |
| 79 | + pic.width = W; |
| 80 | + pic.height = H; |
| 81 | + WebPMemoryWriterInit(&writer); |
| 82 | + pic.writer = WebPMemoryWrite; |
| 83 | + pic.custom_ptr = &writer; |
| 84 | + const bool encoded = WebPPictureImportRGBA(&pic, rgba.data(), W * 4) && WebPEncode(&config, &pic); |
| 85 | + const auto hash = encoded ? fnv1a({writer.mem, writer.size}) : 0; |
| 86 | + check(encoded && hash == 0xf128a79c2ff683a2ull, std::format("WebPEncode q75 m4: {} bytes, fnv1a 0x{:016x}", writer.size, hash)); |
| 87 | + WebPPictureFree(&pic); |
| 88 | + WebPMemoryWriterClear(&writer); |
| 89 | + |
| 90 | + // An animation: three frames in, three full canvases out. |
| 91 | + WebPAnimEncoderOptions options; |
| 92 | + check(WebPAnimEncoderOptionsInit(&options) != 0, "WebPAnimEncoderOptionsInit"); |
| 93 | + WebPAnimEncoder* encoder = WebPAnimEncoderNew(W, H, &options); |
| 94 | + config.lossless = 1; |
| 95 | + config.exact = 1; |
| 96 | + bool added = encoder && WebPPictureInit(&pic); |
| 97 | + pic.use_argb = 1; |
| 98 | + pic.width = W; |
| 99 | + pic.height = H; |
| 100 | + for (int f = 0; f < 3 && added; ++f) { |
| 101 | + const auto frame = picture(f * 10); |
| 102 | + added = WebPPictureImportRGBA(&pic, frame.data(), W * 4) && WebPAnimEncoderAdd(encoder, &pic, f * 100, &config); |
| 103 | + } |
| 104 | + WebPPictureFree(&pic); |
| 105 | + WebPData animation; |
| 106 | + WebPDataInit(&animation); |
| 107 | + added = added && WebPAnimEncoderAdd(encoder, nullptr, 300, nullptr) && WebPAnimEncoderAssemble(encoder, &animation); |
| 108 | + WebPAnimEncoderDelete(encoder); |
| 109 | + check(added, std::format("WebPAnimEncoder: 3 frames, {} bytes", animation.size)); |
| 110 | + WebPAnimDecoderOptions decoding; |
| 111 | + check(WebPAnimDecoderOptionsInit(&decoding) != 0, "WebPAnimDecoderOptionsInit"); |
| 112 | + decoding.color_mode = MODE_RGBA; |
| 113 | + WebPAnimDecoder* decoder = WebPAnimDecoderNew(&animation, &decoding); |
| 114 | + WebPAnimInfo info{}; |
| 115 | + check(decoder && WebPAnimDecoderGetInfo(decoder, &info) && info.frame_count == 3, "WebPAnimDecoder: 3 frames"); |
| 116 | + int f = 0; |
| 117 | + bool frames = decoder != nullptr; |
| 118 | + while (decoder && WebPAnimDecoderHasMoreFrames(decoder)) { |
| 119 | + std::uint8_t* canvas = nullptr; |
| 120 | + int timestamp = 0; |
| 121 | + const auto expected = picture(f * 10); |
| 122 | + frames = frames && WebPAnimDecoderGetNext(decoder, &canvas, ×tamp) && timestamp == (f + 1) * 100 && |
| 123 | + std::equal(expected.begin(), expected.end(), canvas); |
| 124 | + ++f; |
| 125 | + } |
| 126 | + check(frames && f == 3, "every frame exact, at its time"); |
| 127 | + WebPAnimDecoderDelete(decoder); |
| 128 | + WebPDemuxer* demux = WebPDemux(&animation); |
| 129 | + check(demux && WebPDemuxGetI(demux, WEBP_FF_FRAME_COUNT) == 3, "WebPDemux: frame count"); |
| 130 | + WebPDemuxDelete(demux); |
| 131 | + WebPDataClear(&animation); |
| 132 | + |
| 133 | + // Errors answer, never abort. |
| 134 | + const std::array<std::uint8_t, 12> garbage{'R', 'I', 'F', 'F', 4, 0, 0, 0, 'W', 'E', 'B', 'P'}; |
| 135 | + check(WebPDecodeRGBA(garbage.data(), garbage.size(), &width, &height) == nullptr, "garbage: WebPDecodeRGBA is null"); |
| 136 | + const WebPData bad{garbage.data(), garbage.size()}; |
| 137 | + check(WebPAnimDecoderNew(&bad, nullptr) == nullptr, "garbage: WebPAnimDecoderNew is null"); |
| 138 | + std::uint8_t* none = nullptr; |
| 139 | + check(WebPEncodeRGBA(rgba.data(), mcpplibs::mwrap::libwebp::macros::WEBP_MAX_DIMENSION + 1, 1, W * 4, 75.f, &none) == 0, |
| 140 | + "beyond WEBP_MAX_DIMENSION: 0 bytes"); |
| 141 | + WebPFree(none); |
| 142 | + return failures; |
| 143 | +} |
0 commit comments