[rust jpeg] Final raw output and IDCT parity

Treat immutable JPEG data as final input for raw YUV decoding. Preserve
best-effort progressive output at final EOF without changing resumable
stream behavior, and cover missing-EOI decoding and rewind.

Pin zune-jpeg to the revision from PR494 containing final-input,
accurate-IDCT, and progressive block-smoothing fixes. Remove the
temporary local crate patch and its Bazel/GN patch wiring.

https://github.com/etemesi254/zune-image/pull/494

Preserve incomplete-input status when progressive color components have
not received DC data, without publishing incomplete color output.
Preserve existing best-effort packed JPEG decoding.

Target GPU half-scan, missing-EOI, non-interleaved DC,
grayscale-profile, and YUV color-profile failures with Rust JPEG
enabled.

Chromium CL porting zune-jpeg fixes as patch which requires to be landed
before this CL:
https://chromium-review.googlesource.com/c/chromium/src/+/8617799

NOTE FOR REVIEWERS - original patch and result patch are not identical.
PLEASE REVIEW CAREFULLY.
Diffs between the patches:
 fn test_raw_input_finality_is_preserved_on_decode_reset() {
> +        for raw_input_complete in [false, true] {
> +            let mut reader = new_reader(cxx::UniquePtr::null(), 0, raw_input_complete);
> +            assert_eq!(reader.raw_input_complete(), raw_input_complete);
> +            reader.reset_decode_state();
> +            assert_eq!(reader.raw_input_complete(), raw_input_complete);
> +        }
> +    }
> +
> +    #[test]
> --- tests/SkJpegRustCodecTest.cpp
> +++ tests/SkJpegRustCodecTest.cpp
> -                                        streamLength);
> +                                        streamLength,
> +                                        /*raw_input_complete=*/false);
> +DEF_TEST(RustJpegCodec_FinalRawInputWithoutEOI, r) {
> +    sk_sp<SkData> data = GetResourceAsData("images/four-colors-limited-range-420-8bpc.jpg");
> +    REPORTER_ASSERT(r, data && data->size() > 2);
> +    if (!data || data->size() <= 2) {
> +        return;
> +    }
> +    const auto* bytes = static_cast<const uint8_t*>(data->data());
> +    REPORTER_ASSERT(r, bytes[data->size() - 2] == 0xFF &&
> +                       bytes[data->size() - 1] == 0xD9);
> +    sk_sp<SkData> truncated = SkData::MakeSubset(data.get(), 0, data->size() - 2);
> +    auto codec = SkJpegRustDecoder::Decode(truncated, nullptr);
> +    auto reference = SkJpegRustDecoder::Decode(data, nullptr);
> +    // A known stream length is not an explicit final-input signal.
> +    auto streamCodec = SkJpegRustDecoder::Decode(SkMemoryStream::Make(truncated), nullptr);
> +    REPORTER_ASSERT(r, codec && reference && streamCodec);
> +    if (!codec || !reference || !streamCodec) {
> +        return;
> +    }
> +    SkYUVAPixmapInfo info;
> +    REPORTER_ASSERT(
> +            r, codec->queryYUVAInfo(SkYUVAPixmapInfo::SupportedDataTypes::All(), &info));
> +    SkYUVAPixmaps actual = SkYUVAPixmaps::Allocate(info);
> +    SkYUVAPixmaps expected = SkYUVAPixmaps::Allocate(info);
> +    SkYUVAPixmaps suspended = SkYUVAPixmaps::Allocate(info);
> +    REPORTER_ASSERT(r, actual.isValid() && expected.isValid() && suspended.isValid());
> +    if (!actual.isValid() || !expected.isValid() || !suspended.isValid()) {
> +        return;
> +    }
> +    REPORTER_ASSERT_SUCCESSFUL_CODEC_RESULT(r, reference->getYUVAPlanes(expected));
> +    for (int attempt = 0; attempt < 2; ++attempt) {
> +        REPORTER_ASSERT(r, streamCodec->getYUVAPlanes(suspended) == SkCodec::kIncompleteInput);
> +        REPORTER_ASSERT_SUCCESSFUL_CODEC_RESULT(r, codec->getYUVAPlanes(actual));
> +        for (int plane = 0; plane < actual.numPlanes(); ++plane) {
> +            for (int row = 0; row < actual.plane(plane).height(); ++row) {
> +                REPORTER_ASSERT(
> +                        r, !memcmp(actual.plane(plane).addr(0, row),
> +                                   expected.plane(plane).addr(0, row),
> +                                   static_cast<size_t>(actual.plane(plane).width())));
> +            }
> +        }
> +    }
> +}
> +
> +DEF_TEST(RustJpegCodec_FinalRawNeedsAllComponents, r) {
> +    // A three-component progressive JPEG containing only a Y DC scan.
> +    std::vector<uint8_t> encoded = {0xFF, 0xD8, 0xFF, 0xDB, 0, 67, 0};
> +    encoded.insert(encoded.end(), 64, 1);
> +    encoded.insert(encoded.end(), {
> +        0xFF, 0xC2, 0, 17, 8, 0, 8, 0, 8, 3,
> +        1, 0x11, 0, 2, 0x11, 0, 3, 0x11, 0,
> +        0xFF, 0xC4, 0, 20, 0,
> +        1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
> +        0xFF, 0xDA, 0, 8, 1, 1, 0, 0, 0, 0, 0x7F,
> +    });
> +    auto codec = SkJpegRustDecoder::Decode(
> +            SkData::MakeWithCopy(encoded.data(), encoded.size()), nullptr);
> +    REPORTER_ASSERT(r, codec);
> +    if (!codec) {
> +        return;
> +    }
> +    SkYUVAPixmapInfo info;
> +    REPORTER_ASSERT(
> +            r, codec->queryYUVAInfo(SkYUVAPixmapInfo::SupportedDataTypes::All(), &info));
> +    REPORTER_ASSERT(r, info.isValid());
> +    if (!info.isValid()) {
> +        return;
> +    }
> +    std::array<std::vector<uint8_t>, SkYUVAPixmaps::kMaxPlanes> storage;
> +    std::array<SkPixmap, SkYUVAPixmaps::kMaxPlanes> pixmaps;
> +    for (int plane = 0; plane < info.numPlanes(); ++plane) {
> +        const size_t rowBytes = info.rowBytes(plane);
> +        storage[plane].assign(rowBytes * info.planeInfo(plane).height(), 0xCD);
> +        pixmaps[plane] =
> +                SkPixmap(info.planeInfo(plane), storage[plane].data(), rowBytes);
> +    }
> +    SkYUVAPixmaps planes = SkYUVAPixmaps::FromExternalPixmaps(info.yuvaInfo(), pixmaps.data());
> +    REPORTER_ASSERT(r, codec->getYUVAPlanes(planes) == SkCodec::kIncompleteInput);
> +    for (const auto& plane : storage) {
> +        REPORTER_ASSERT(r, std::all_of(plane.begin(), plane.end(), [](uint8_t value) {
> +            return value == 0xCD;
> +        }));
> +    }
> +}
> +

Original patch:
 diff --git a/MODULE.bazel b/MODULE.bazel
index 2dea1ec17e2ae4637aa88128e0545928bc917e9d..949b7f7ed833dd51dc26681fb5d741366382990b 100644
--- a/MODULE.bazel
+++ b/MODULE.bazel
@@ -168,7 +168,7 @@ crate.spec(
     # and is not shipping in Chromium or other Skia clients.
     git = "https://github.com/etemesi254/zune-image.git",
     package = "zune-jpeg",
-    rev = "a1010cf1faa91980f011c32585b4f582caf10745",
+    rev = "7d3e74c34b2252b6a4477399c7c52ccc9cf0fcab",
 )
 crate.spec(
     features = ["std"],
diff --git a/MODULE.bazel.lock b/MODULE.bazel.lock
index 0e3e32d45992972af15f075580841c86c8b8f066..90eb30a53e8282e3dd825e5c26d2266ede177e44 100644
--- a/MODULE.bazel.lock
+++ b/MODULE.bazel.lock
@@ -4733,7 +4733,7 @@
     "@@rules_rust+//crate_universe:extensions.bzl%crate": {
       "general": {
         "bzlTransitiveDigest": "SEbwyK0Bpc6UhNRduMkh2PMWmn8uyfpuHzW108IxnWU=",
-        "usagesDigest": "98RPnHOLfzauNznKr0YpAvMMRTSEjC+UJm4SprUFD2A=",
+        "usagesDigest": "0RXMAi1/tILyCX0fcy7tuQ5iSOFw66zCZNQtx2ek
[[[Original patch trimmed due to size. Decoded string size: 266525. Decoded string SHA1: 0d8af89253f1d9656139637c6fbc951977303607.]]]

Result patch:
 diff --git a/MODULE.bazel b/MODULE.bazel
index 2dea1ec..949b7f7 100644
--- a/MODULE.bazel
+++ b/MODULE.bazel
@@ -168,7 +168,7 @@
     # and is not shipping in Chromium or other Skia clients.
     git = "https://github.com/etemesi254/zune-image.git",
     package = "zune-jpeg",
-    rev = "a1010cf1faa91980f011c32585b4f582caf10745",
+    rev = "7d3e74c34b2252b6a4477399c7c52ccc9cf0fcab",
 )
 crate.spec(
     features = ["std"],
diff --git a/MODULE.bazel.lock b/MODULE.bazel.lock
index 0e3e32d..90eb30a 100644
--- a/MODULE.bazel.lock
+++ b/MODULE.bazel.lock
@@ -4733,7 +4733,7 @@
     "@@rules_rust+//crate_universe:extensions.bzl%crate": {
       "general": {
         "bzlTransitiveDigest": "SEbwyK0Bpc6UhNRduMkh2PMWmn8uyfpuHzW108IxnWU=",
-        "usagesDigest": "98RPnHOLfzauNznKr0YpAvMMRTSEjC+UJm4SprUFD2A=",
+        "usagesDigest": "0RXMAi1/tILyCX0fcy7tuQ5iSOFw66zCZNQtx2ekATY=",
         "recordedFileInputs": {
           "@@//bazel/external/cxx/BUILD.bazel.skia": "39b51a9bd87483932d67ad5e0707ec339de993688df60ec69
[[[Result patch trimmed due to size. Decoded string size: 265416. Decoded string SHA1: 27918433f29fb9d62e46ea24178c8b4139aded72.]]]

Bug: 493315750
Change-Id: I7131c5762c98c6aa5269042507ede514aab546e6
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/1400056
Commit-Queue: Sergio Gonzalez Martin <sergiog@microsoft.com>
Reviewed-by: Florin Malita <fmalita@google.com>
Reviewed-by: Ɓukasz Anforowicz <lukasza@google.com>
8 files changed