[
  {
    "path": ".cargo/config.toml",
    "content": "# -Cehcont_guard: Enable EH Continuation Metadata (https://learn.microsoft.com/en-us/cpp/build/reference/guard-enable-eh-continuation-metadata).\n# -Ccontrol-flow-guard: Enable Control Flow Guard, needed for OneBranch's post-build analysis (https://learn.microsoft.com/en-us/cpp/build/reference/guard-enable-control-flow-guard).\n# -Ctarget-feature=+crt-static: Statically link the CRT (required to link the spectre-mitigated CRT).\n[target.'cfg(target_os = \"windows\")']\nrustflags = [\"-Ccontrol-flow-guard\", \"-Ctarget-feature=+crt-static\"]\n\n# -Clink-args=/DYNAMICBASE /CETCOMPAT: Enable \"shadow stack\" (https://learn.microsoft.com/en-us/cpp/build/reference/cetcompat)\n[target.'cfg(all(target_os = \"windows\", any(target_arch = \"i686\", target_arch = \"x86_64\")))']\nrustflags = [\"-Clink-arg=/DYNAMICBASE\", \"-Clink-arg=/CETCOMPAT\"]\n\n[registries]\n\n[env]\nWORKSPACE_ROOT = { value = \"\", relative = true }"
  },
  {
    "path": ".config/1espt/PipelineAutobaseliningConfig.yml",
    "content": "## DO NOT MODIFY THIS FILE MANUALLY. This is part of auto-baselining from 1ES Pipeline Templates. Go to [https://aka.ms/1espt-autobaselining] for more details.\r\n\r\npipelines:\n  9128:\n    retail:\n      source:\n        eslint:\n          lastModifiedDate: 2026-01-27\n        psscriptanalyzer:\n          lastModifiedDate: 2026-01-27\n        armory:\n          lastModifiedDate: 2026-01-27\n        accessibilityinsights:\n          lastModifiedDate: 2026-01-27\n      binary:\n        binskim:\n          lastModifiedDate: 2026-01-27\n        spotbugs:\n          lastModifiedDate: 2026-01-27\n"
  },
  {
    "path": ".config/guardian/.gdnbaselines",
    "content": "{\n  \"properties\": {\n    \"helpUri\": \"https://eng.ms/docs/microsoft-security/security/azure-security/cloudai-security-fundamentals-engineering/security-integration/guardian-wiki/microsoft-guardian/general/baselines\"\n  },\n  \"version\": \"1.0.0\",\n  \"baselines\": {\n    \"default\": {\n      \"name\": \"default\",\n      \"createdDate\": \"2026-01-27 06:17:01Z\",\n      \"lastUpdatedDate\": \"2026-01-27 06:17:01Z\"\n    }\n  },\n  \"results\": {\n    \"7888556c1e034138d9cf4c682b73c492a353bb423ec11dc085e477365358d5b5\": {\n      \"signature\": \"7888556c1e034138d9cf4c682b73c492a353bb423ec11dc085e477365358d5b5\",\n      \"alternativeSignatures\": [\n        \"f363807b3905e1c28a3a5fb6ffcb9b91c52b725244e6a57f8d2f41e3b70fbc02\"\n      ],\n      \"target\": \"target/release/lepton_jpeg.dll\",\n      \"memberOf\": [\n        \"default\"\n      ],\n      \"tool\": \"binskim\",\n      \"ruleId\": \"BA2007\",\n      \"createdDate\": \"2026-01-27 06:17:01Z\",\n      \"expirationDate\": \"2026-07-16 06:19:42Z\",\n      \"justification\": \"This error is baselined with an expiration date of 180 days from 2026-01-27 06:19:42Z\"\n    },\n    \"82dae63401a13132179fdd7f28cb9935eb07f6808da120db385d4537f6c7e781\": {\n      \"signature\": \"82dae63401a13132179fdd7f28cb9935eb07f6808da120db385d4537f6c7e781\",\n      \"alternativeSignatures\": [\n        \"03acf8c325bf5b05a5e79b1b417f263e3526c9f06cfbb942d7f9b740840f3516\"\n      ],\n      \"target\": \"target/release/lepton_jpeg_avx2.dll\",\n      \"memberOf\": [\n        \"default\"\n      ],\n      \"tool\": \"binskim\",\n      \"ruleId\": \"BA2007\",\n      \"createdDate\": \"2026-01-27 06:17:01Z\",\n      \"expirationDate\": \"2026-07-16 06:19:42Z\",\n      \"justification\": \"This error is baselined with an expiration date of 180 days from 2026-01-27 06:19:42Z\"\n    },\n    \"34d7aee5ca8519db58d0fa6d7ddf78e892c1fc7f8c905926a5fbc8c0cb080af0\": {\n      \"signature\": \"34d7aee5ca8519db58d0fa6d7ddf78e892c1fc7f8c905926a5fbc8c0cb080af0\",\n      \"alternativeSignatures\": [\n        \"e2af46751ad6876b19468b60518f5e41adfd3dcd204794acb0ebc401d7733573\"\n      ],\n      \"target\": \"target/release/lepton_jpeg_python.dll\",\n      \"memberOf\": [\n        \"default\"\n      ],\n      \"tool\": \"binskim\",\n      \"ruleId\": \"BA2007\",\n      \"createdDate\": \"2026-01-27 06:17:01Z\",\n      \"expirationDate\": \"2026-07-16 06:19:42Z\",\n      \"justification\": \"This error is baselined with an expiration date of 180 days from 2026-01-27 06:19:42Z\"\n    },\n    \"c359baf101a3b163e44bb6577cb8492ed745c10c9f3bd182f8b935057bfd0bf2\": {\n      \"signature\": \"c359baf101a3b163e44bb6577cb8492ed745c10c9f3bd182f8b935057bfd0bf2\",\n      \"alternativeSignatures\": [\n        \"4be395a1280a6d3ced21b0ad815572da5d13632d75ea6dcb76f4e9b6ca3753ab\"\n      ],\n      \"target\": \"target/release/lepton_jpeg_util.exe\",\n      \"memberOf\": [\n        \"default\"\n      ],\n      \"tool\": \"binskim\",\n      \"ruleId\": \"BA2007\",\n      \"createdDate\": \"2026-01-27 06:17:01Z\",\n      \"expirationDate\": \"2026-07-16 06:19:42Z\",\n      \"justification\": \"This error is baselined with an expiration date of 180 days from 2026-01-27 06:19:42Z\"\n    },\n    \"ee719806d1996ca8d823f90d73903cc9f5da08a3960af2c0a1eb0dd1a54c99d0\": {\n      \"signature\": \"ee719806d1996ca8d823f90d73903cc9f5da08a3960af2c0a1eb0dd1a54c99d0\",\n      \"alternativeSignatures\": [\n        \"ba93e14514af2d2f711daad10f312379d730bc77ef208901c9cf06e07bf01d84\"\n      ],\n      \"target\": \"target/release/lepton_jpeg_util_avx2.exe\",\n      \"memberOf\": [\n        \"default\"\n      ],\n      \"tool\": \"binskim\",\n      \"ruleId\": \"BA2007\",\n      \"createdDate\": \"2026-01-27 06:17:01Z\",\n      \"expirationDate\": \"2026-07-16 06:19:42Z\",\n      \"justification\": \"This error is baselined with an expiration date of 180 days from 2026-01-27 06:19:42Z\"\n    },\n    \"cd6b9720c2019ba2c72ef61db8542b1ce290ba22d587eb3955ab755d14d6f4ed\": {\n      \"signature\": \"cd6b9720c2019ba2c72ef61db8542b1ce290ba22d587eb3955ab755d14d6f4ed\",\n      \"alternativeSignatures\": [\n        \"1ed2e30b47b86b4ed9ca74beff17a7312994572936ef2333bbc6ef9bc17dacd3\"\n      ],\n      \"target\": \"target/release/deps/default_boxed_derive-63de623f2e43e138.dll\",\n      \"memberOf\": [\n        \"default\"\n      ],\n      \"tool\": \"binskim\",\n      \"ruleId\": \"BA2007\",\n      \"createdDate\": \"2026-01-27 06:17:01Z\",\n      \"expirationDate\": \"2026-07-16 06:19:42Z\",\n      \"justification\": \"This error is baselined with an expiration date of 180 days from 2026-01-27 06:19:42Z\"\n    },\n    \"363faeb4073c8417f384af5227032d0daa483689d56ad0fac59f3328e751b183\": {\n      \"signature\": \"363faeb4073c8417f384af5227032d0daa483689d56ad0fac59f3328e751b183\",\n      \"alternativeSignatures\": [\n        \"0a863a85465581440a18aeb7b257b269e75c704621b9d7dced6ed89204d950f6\"\n      ],\n      \"target\": \"target/release/deps/default_boxed_derive-a563f94fdc621d90.dll\",\n      \"memberOf\": [\n        \"default\"\n      ],\n      \"tool\": \"binskim\",\n      \"ruleId\": \"BA2007\",\n      \"createdDate\": \"2026-01-27 06:17:01Z\",\n      \"expirationDate\": \"2026-07-16 06:19:42Z\",\n      \"justification\": \"This error is baselined with an expiration date of 180 days from 2026-01-27 06:19:42Z\"\n    },\n    \"12a8462656e61be1c0ef31be295c12ac6e85b261c25adee4cf97e2d32819b315\": {\n      \"signature\": \"12a8462656e61be1c0ef31be295c12ac6e85b261c25adee4cf97e2d32819b315\",\n      \"alternativeSignatures\": [\n        \"29e6fbebe0ea89a075a6e11c87b2f776f2cada75471317093729862c1f4cd60d\"\n      ],\n      \"target\": \"target/release/deps/git_version_macro-81a02dafe74069f0.dll\",\n      \"memberOf\": [\n        \"default\"\n      ],\n      \"tool\": \"binskim\",\n      \"ruleId\": \"BA2007\",\n      \"createdDate\": \"2026-01-27 06:17:01Z\",\n      \"expirationDate\": \"2026-07-16 06:19:42Z\",\n      \"justification\": \"This error is baselined with an expiration date of 180 days from 2026-01-27 06:19:42Z\"\n    },\n    \"f9cb8789538579745e4a5881ebfe2de000c3a924099948e9df510235d8e63326\": {\n      \"signature\": \"f9cb8789538579745e4a5881ebfe2de000c3a924099948e9df510235d8e63326\",\n      \"alternativeSignatures\": [\n        \"495638cb4ea6541aa8abbfb904b5bc968502f7c3d2eda51fc8009b842627cd5c\"\n      ],\n      \"target\": \"target/release/deps/git_version_macro-84dcd42a3b51464a.dll\",\n      \"memberOf\": [\n        \"default\"\n      ],\n      \"tool\": \"binskim\",\n      \"ruleId\": \"BA2007\",\n      \"createdDate\": \"2026-01-27 06:17:01Z\",\n      \"expirationDate\": \"2026-07-16 06:19:42Z\",\n      \"justification\": \"This error is baselined with an expiration date of 180 days from 2026-01-27 06:19:42Z\"\n    },\n    \"0015b41486ed72ac681952fc1e5117467edc31cc458a8ebe0bc2428040501432\": {\n      \"signature\": \"0015b41486ed72ac681952fc1e5117467edc31cc458a8ebe0bc2428040501432\",\n      \"alternativeSignatures\": [\n        \"a14a0079e1028b5a3a85807dd85687ff60f1e614e10d6174e8f8d85d5ffad6f9\"\n      ],\n      \"target\": \"target/release/deps/indoc-0ef44e389295250a.dll\",\n      \"memberOf\": [\n        \"default\"\n      ],\n      \"tool\": \"binskim\",\n      \"ruleId\": \"BA2007\",\n      \"createdDate\": \"2026-01-27 06:17:01Z\",\n      \"expirationDate\": \"2026-07-16 06:19:42Z\",\n      \"justification\": \"This error is baselined with an expiration date of 180 days from 2026-01-27 06:19:42Z\"\n    },\n    \"54b6fbb3b25a55dd7124f9172228d97a8e208a2b2f310fb98f52120d2555e9a0\": {\n      \"signature\": \"54b6fbb3b25a55dd7124f9172228d97a8e208a2b2f310fb98f52120d2555e9a0\",\n      \"alternativeSignatures\": [\n        \"5586663f943c7c47c916fba1ac930ea1eed0c8231090913f36b98ace3158a3e0\"\n      ],\n      \"target\": \"target/release/deps/indoc-1d196f6d756468dd.dll\",\n      \"memberOf\": [\n        \"default\"\n      ],\n      \"tool\": \"binskim\",\n      \"ruleId\": \"BA2007\",\n      \"createdDate\": \"2026-01-27 06:17:01Z\",\n      \"expirationDate\": \"2026-07-16 06:19:42Z\",\n      \"justification\": \"This error is baselined with an expiration date of 180 days from 2026-01-27 06:19:42Z\"\n    },\n    \"a571bdcd0bc935822e84ea12d95ffc432c5f99381b681b9cc292f7ed8ec32b06\": {\n      \"signature\": \"a571bdcd0bc935822e84ea12d95ffc432c5f99381b681b9cc292f7ed8ec32b06\",\n      \"alternativeSignatures\": [\n        \"bcf8ae23a38f46f4b1fbad99d2a0d4c1b72c372bc8aedb5dd2d20543ad7c18da\"\n      ],\n      \"target\": \"target/release/deps/lepton_jpeg.dll\",\n      \"memberOf\": [\n        \"default\"\n      ],\n      \"tool\": \"binskim\",\n      \"ruleId\": \"BA2007\",\n      \"createdDate\": \"2026-01-27 06:17:01Z\",\n      \"expirationDate\": \"2026-07-16 06:19:42Z\",\n      \"justification\": \"This error is baselined with an expiration date of 180 days from 2026-01-27 06:19:42Z\"\n    },\n    \"4156fba8fbda87c312c40f8644080f0444e119c7ad022a7a1cd5bc153472c8ed\": {\n      \"signature\": \"4156fba8fbda87c312c40f8644080f0444e119c7ad022a7a1cd5bc153472c8ed\",\n      \"alternativeSignatures\": [\n        \"867001d871648777be53eee5134a29693823d0c842475176bc0a738f448ab9d1\"\n      ],\n      \"target\": \"target/release/deps/lepton_jpeg_python.dll\",\n      \"memberOf\": [\n        \"default\"\n      ],\n      \"tool\": \"binskim\",\n      \"ruleId\": \"BA2007\",\n      \"createdDate\": \"2026-01-27 06:17:01Z\",\n      \"expirationDate\": \"2026-07-16 06:19:42Z\",\n      \"justification\": \"This error is baselined with an expiration date of 180 days from 2026-01-27 06:19:42Z\"\n    },\n    \"149befb9280c911d3f0b709dd16c3b495937aacddf5e0ab770addce09e6f7ab6\": {\n      \"signature\": \"149befb9280c911d3f0b709dd16c3b495937aacddf5e0ab770addce09e6f7ab6\",\n      \"alternativeSignatures\": [\n        \"91f1eab664b17bcc61e3585f933ef033800549b55f938091f80a75c3e0e6958d\"\n      ],\n      \"target\": \"target/release/deps/lepton_jpeg_util.exe\",\n      \"memberOf\": [\n        \"default\"\n      ],\n      \"tool\": \"binskim\",\n      \"ruleId\": \"BA2007\",\n      \"createdDate\": \"2026-01-27 06:17:01Z\",\n      \"expirationDate\": \"2026-07-16 06:19:42Z\",\n      \"justification\": \"This error is baselined with an expiration date of 180 days from 2026-01-27 06:19:42Z\"\n    },\n    \"903069c0ae93b67c9b06d4d760af5b12c07ce30db727be83f46507875fd1856c\": {\n      \"signature\": \"903069c0ae93b67c9b06d4d760af5b12c07ce30db727be83f46507875fd1856c\",\n      \"alternativeSignatures\": [\n        \"8357b47447b55a4058ac2d9b7cbc881f412384528a90e66a88509fea88f974ff\"\n      ],\n      \"target\": \"target/release/deps/pyo3_macros-0883c55e16fc46dd.dll\",\n      \"memberOf\": [\n        \"default\"\n      ],\n      \"tool\": \"binskim\",\n      \"ruleId\": \"BA2007\",\n      \"createdDate\": \"2026-01-27 06:17:01Z\",\n      \"expirationDate\": \"2026-07-16 06:19:42Z\",\n      \"justification\": \"This error is baselined with an expiration date of 180 days from 2026-01-27 06:19:42Z\"\n    },\n    \"ef9d7c4fdf143066f64d95af451456f5547eba3e89156ba0cbda5dc92b9eed0c\": {\n      \"signature\": \"ef9d7c4fdf143066f64d95af451456f5547eba3e89156ba0cbda5dc92b9eed0c\",\n      \"alternativeSignatures\": [\n        \"954a842978f48d0a377246cec186dcf2afd17423cae579f197e6d7e081657935\"\n      ],\n      \"target\": \"target/release/deps/pyo3_macros-ead3c9c3859ec605.dll\",\n      \"memberOf\": [\n        \"default\"\n      ],\n      \"tool\": \"binskim\",\n      \"ruleId\": \"BA2007\",\n      \"createdDate\": \"2026-01-27 06:17:01Z\",\n      \"expirationDate\": \"2026-07-16 06:19:42Z\",\n      \"justification\": \"This error is baselined with an expiration date of 180 days from 2026-01-27 06:19:42Z\"\n    },\n    \"544e0bdd76e7c149e17e870fb0b32aa8d35af936a96e12f73891061d25b64485\": {\n      \"signature\": \"544e0bdd76e7c149e17e870fb0b32aa8d35af936a96e12f73891061d25b64485\",\n      \"alternativeSignatures\": [\n        \"e6f1ebffea3eb268804be426a462e535249f01dc46a9ef43dd1f2c7aa899bb96\"\n      ],\n      \"target\": \"target/release/deps/rustversion-60c5a5f87e0f5de5.dll\",\n      \"memberOf\": [\n        \"default\"\n      ],\n      \"tool\": \"binskim\",\n      \"ruleId\": \"BA2007\",\n      \"createdDate\": \"2026-01-27 06:17:01Z\",\n      \"expirationDate\": \"2026-07-16 06:19:42Z\",\n      \"justification\": \"This error is baselined with an expiration date of 180 days from 2026-01-27 06:19:42Z\"\n    },\n    \"3bd252ba75ede9998cae2e3283370157bef89d7b2de12562cb8ad3472f1ee620\": {\n      \"signature\": \"3bd252ba75ede9998cae2e3283370157bef89d7b2de12562cb8ad3472f1ee620\",\n      \"alternativeSignatures\": [\n        \"ba5c335ba47831bfb30973ec0e423518ed2b198f8d8d691fd3f856a886a924c6\"\n      ],\n      \"target\": \"target/release/deps/rustversion-ae37400f19b3f17f.dll\",\n      \"memberOf\": [\n        \"default\"\n      ],\n      \"tool\": \"binskim\",\n      \"ruleId\": \"BA2007\",\n      \"createdDate\": \"2026-01-27 06:17:01Z\",\n      \"expirationDate\": \"2026-07-16 06:19:42Z\",\n      \"justification\": \"This error is baselined with an expiration date of 180 days from 2026-01-27 06:19:42Z\"\n    },\n    \"4cf9241015129625b09af906ce68f0424cafdf3661358bd4081e29d8e945024b\": {\n      \"signature\": \"4cf9241015129625b09af906ce68f0424cafdf3661358bd4081e29d8e945024b\",\n      \"alternativeSignatures\": [\n        \"901556be5d52b05739e219d3911c9ca20b435456fe22d3e7d1fe994e18faa043\"\n      ],\n      \"target\": \"target/release/deps/time_macros-17f74972c8766b75.dll\",\n      \"memberOf\": [\n        \"default\"\n      ],\n      \"tool\": \"binskim\",\n      \"ruleId\": \"BA2007\",\n      \"createdDate\": \"2026-01-27 06:17:01Z\",\n      \"expirationDate\": \"2026-07-16 06:19:42Z\",\n      \"justification\": \"This error is baselined with an expiration date of 180 days from 2026-01-27 06:19:42Z\"\n    },\n    \"3ad2f2c6ad81bbb12175b4e299f09319a2c7e26e2e91386e8cdf2bb7f66bb6b4\": {\n      \"signature\": \"3ad2f2c6ad81bbb12175b4e299f09319a2c7e26e2e91386e8cdf2bb7f66bb6b4\",\n      \"alternativeSignatures\": [\n        \"f55823c74a027e8a3e03b58709e244f185e585d3729015841df513f514964799\"\n      ],\n      \"target\": \"target/release/deps/time_macros-6674f0d9cd1fe1c0.dll\",\n      \"memberOf\": [\n        \"default\"\n      ],\n      \"tool\": \"binskim\",\n      \"ruleId\": \"BA2007\",\n      \"createdDate\": \"2026-01-27 06:17:01Z\",\n      \"expirationDate\": \"2026-07-16 06:19:42Z\",\n      \"justification\": \"This error is baselined with an expiration date of 180 days from 2026-01-27 06:19:42Z\"\n    },\n    \"51a37a91abff68ac93176d40a7ebe4254f8bd2d59b3f067eac8e63fbf344dc96\": {\n      \"signature\": \"51a37a91abff68ac93176d40a7ebe4254f8bd2d59b3f067eac8e63fbf344dc96\",\n      \"alternativeSignatures\": [\n        \"2b069009ab0383fb0ff18c24cda15d0e3a2787753d56e1d062739be4d1f24ad6\"\n      ],\n      \"target\": \"target/release/deps/windows_implement-131e219ba8366c19.dll\",\n      \"memberOf\": [\n        \"default\"\n      ],\n      \"tool\": \"binskim\",\n      \"ruleId\": \"BA2007\",\n      \"createdDate\": \"2026-01-27 06:17:01Z\",\n      \"expirationDate\": \"2026-07-16 06:19:42Z\",\n      \"justification\": \"This error is baselined with an expiration date of 180 days from 2026-01-27 06:19:42Z\"\n    },\n    \"d08a22af13bbb9c4df920b594dc86a5191f4ab28301a3fda5a8c99b19909d39f\": {\n      \"signature\": \"d08a22af13bbb9c4df920b594dc86a5191f4ab28301a3fda5a8c99b19909d39f\",\n      \"alternativeSignatures\": [\n        \"556ad3740e06bf41c0476ceabdabef20085c886ff93eafa7dac49a3f34bf216b\"\n      ],\n      \"target\": \"target/release/deps/windows_implement-9fc3e36e619e31c4.dll\",\n      \"memberOf\": [\n        \"default\"\n      ],\n      \"tool\": \"binskim\",\n      \"ruleId\": \"BA2007\",\n      \"createdDate\": \"2026-01-27 06:17:01Z\",\n      \"expirationDate\": \"2026-07-16 06:19:42Z\",\n      \"justification\": \"This error is baselined with an expiration date of 180 days from 2026-01-27 06:19:42Z\"\n    },\n    \"7c4b27fd4d70c9af21f0770b95de543c7c5b1c43b091d4e97628aa611981c936\": {\n      \"signature\": \"7c4b27fd4d70c9af21f0770b95de543c7c5b1c43b091d4e97628aa611981c936\",\n      \"alternativeSignatures\": [\n        \"ec50c72748b2290ad8ad44fba6be6392c1a97b8b7a82cf40cfa02fbf8616a19d\"\n      ],\n      \"target\": \"target/release/deps/windows_interface-3fdabd2e927b6dbf.dll\",\n      \"memberOf\": [\n        \"default\"\n      ],\n      \"tool\": \"binskim\",\n      \"ruleId\": \"BA2007\",\n      \"createdDate\": \"2026-01-27 06:17:01Z\",\n      \"expirationDate\": \"2026-07-16 06:19:42Z\",\n      \"justification\": \"This error is baselined with an expiration date of 180 days from 2026-01-27 06:19:42Z\"\n    },\n    \"3f2ca4517e5e7715572fca9ef646b6c5738b01f2d1eddda8da452bcb35ace81b\": {\n      \"signature\": \"3f2ca4517e5e7715572fca9ef646b6c5738b01f2d1eddda8da452bcb35ace81b\",\n      \"alternativeSignatures\": [\n        \"c7424dad4782f04900eea0009b9db79dc37ea5b18cb982fafba9135e2bb47aef\"\n      ],\n      \"target\": \"target/release/deps/windows_interface-d6a6711b2754ade3.dll\",\n      \"memberOf\": [\n        \"default\"\n      ],\n      \"tool\": \"binskim\",\n      \"ruleId\": \"BA2007\",\n      \"createdDate\": \"2026-01-27 06:17:01Z\",\n      \"expirationDate\": \"2026-07-16 06:19:42Z\",\n      \"justification\": \"This error is baselined with an expiration date of 180 days from 2026-01-27 06:19:42Z\"\n    }\n  }\n}"
  },
  {
    "path": ".config/nextest.toml",
    "content": "[profile.ci.junit]\npath = \"junit.xml\""
  },
  {
    "path": ".github/workflows/publish.yml",
    "content": "name: Publish Crate\n\npermissions:\n  contents: read\n\non:\n  push:\n    tags:\n      - \"v*.*.*\"  # Triggers only for version tag pushes\n\njobs:\n  publish:\n    runs-on: ubuntu-latest\n\n    steps:\n    - name: Checkout code with full history\n      uses: actions/checkout@v3\n      with:\n        fetch-depth: 0  # Needed to compare commits and access tag history\n\n    - name: Ensure tag is at tip of main\n      id: verify_tag_commit\n      run: |\n        echo \"🔍 Verifying tag points to main branch tip...\"\n        git fetch origin main\n\n        TAG_COMMIT=$(git rev-parse ${{ github.ref }})\n        MAIN_COMMIT=$(git rev-parse origin/main)\n\n        echo \"Tag commit:  $TAG_COMMIT\"\n        echo \"Main commit: $MAIN_COMMIT\"\n\n        if [ \"$TAG_COMMIT\" != \"$MAIN_COMMIT\" ]; then\n          echo \"❌ Tag is not at tip of main. Aborting.\"\n          exit 1\n        fi\n        echo \"✅ Tag is at tip of main.\"\n\n    - name: Extract tag version\n      id: tag_version\n      run: |\n        echo \"TAG_VERSION=${GITHUB_REF#refs/tags/v}\" >> \"$GITHUB_OUTPUT\"\n\n    - name: Read version from Cargo.toml\n      id: cargo_version\n      run: |\n        CARGO_VERSION=$(grep '^version\\s*=' lib/Cargo.toml | head -1 | sed -E 's/version\\s*=\\s*\"([^\"]+)\"/\\1/')\n        echo \"CARGO_VERSION=$CARGO_VERSION\" >> \"$GITHUB_OUTPUT\"\n\n    - name: Check tag version matches Cargo.toml\n      run: |\n        echo \"🔍 Comparing tag and Cargo.toml versions...\"\n        echo \"Tag:          ${{ steps.tag_version.outputs.TAG_VERSION }}\"\n        echo \"Cargo.toml:   ${{ steps.cargo_version.outputs.CARGO_VERSION }}\"\n\n        if [ \"${{ steps.tag_version.outputs.TAG_VERSION }}\" != \"${{ steps.cargo_version.outputs.CARGO_VERSION }}\" ]; then\n          echo \"❌ Version mismatch: tag does not match Cargo.toml\"\n          exit 1\n        fi\n        echo \"✅ Tag version matches Cargo.toml.\"\n\n    - name: Set up Rust\n      uses: dtolnay/rust-toolchain@stable\n      with:\n        toolchain: stable\n\n    - name: Publish to crates.io\n      env:\n        CARGO_REGISTRY_TOKEN: ${{ secrets.CRATE_PUBLISH }}\n      run: cargo publish --verbose --package lepton_jpeg\n"
  },
  {
    "path": ".github/workflows/publishwheels.yml",
    "content": "name: Publish Wheels\n\npermissions:\n  contents: read\n  \non:\n  push:\n    tags:\n      - \"v*.*.*\"  # Triggers only for version tag pushes\n\njobs:\n  build:\n    name: Build and upload wheels\n    runs-on: ${{ matrix.os }}\n    strategy:\n      matrix:\n        os: [ubuntu-latest, macos-latest, windows-latest]\n        python-version: [\"3.8\", \"3.9\", \"3.10\", \"3.11\", \"3.12\", \"3.13\", \"3.14\"]\n\n    steps:\n      - name: Set up Rust\n        uses: dtolnay/rust-toolchain@stable\n        with:\n          toolchain: stable    \n      - uses: actions/checkout@v4\n      - uses: actions/setup-python@v5\n        with:\n          python-version: ${{ matrix.python-version }}\n\n      - name: Create virtual environment\n        shell: bash\n        run: |\n          python -m venv .venv\n          if [ -f \".venv/bin/activate\" ]; then\n              source .venv/bin/activate\n          else\n              source .venv/Scripts/activate\n          fi          \n          python -m pip install --upgrade pip maturin\n\n      - name: Build wheel\n        shell: bash\n        run: |\n          if [ -f \".venv/bin/activate\" ]; then\n              source .venv/bin/activate\n          else\n              source .venv/Scripts/activate\n          fi\n\n          cd python\n          maturin build --release\n\n      - name: Upload to TestPyPI\n        shell: bash\n        run: |\n          if [ -f \".venv/bin/activate\" ]; then\n              source .venv/bin/activate\n          else\n              source .venv/Scripts/activate\n          fi\n\n          cd python\n          maturin upload --skip-existing $GITHUB_WORKSPACE/target/wheels/*\n        env:\n          MATURIN_PYPI_TOKEN: ${{ secrets.PYPI_API_TOKEN }}  "
  },
  {
    "path": ".github/workflows/rust.yml",
    "content": "name: Rust\n\npermissions:\n  contents: read\n  checks: write\n  pull-requests: write \n\non:\n  push:\n    branches: [\"main\"]\n  pull_request:\n\nenv:\n  CARGO_TERM_COLOR: always\n\njobs:\n  build:\n    runs-on: windows-latest\n\n    steps:\n      - uses: actions/checkout@v3\n      - uses: dtolnay/rust-toolchain@stable\n        with:\n          toolchain: stable\n          targets: wasm32-wasip1,aarch64-unknown-linux-musl,x86_64-pc-windows-msvc,x86_64-unknown-linux-gnu\n          components: rustfmt,clippy\n\n      # Install nextest\n      - uses: taiki-e/install-action@v2\n        with:\n          tool: nextest\n\n      - name: Check formatting\n        run: cargo fmt --check --all\n      - name: Build default target\n        run: cargo build --locked --workspace\n      - name: Build wasm32-wasip1\n        run: cargo build --locked --target wasm32-wasip1 --manifest-path lib/Cargo.toml\n      - name: Build aarch64-unknown-linux-musl\n        run: cargo build --locked --target aarch64-unknown-linux-musl --manifest-path lib/Cargo.toml\n      - name: Build x86_64-pc-windows-msvc\n        run: cargo build --locked --target x86_64-pc-windows-msvc --lib --workspace\n      - name: Build x86_64-pc-windows-msvc release\n        run: cargo build --locked --target x86_64-pc-windows-msvc --lib --workspace --release\n      - name: Test python interface build\n        run: | \n          cd python\n          cargo build --locked\n          python -m venv .env\n          .env\\Scripts\\activate\n          pip install maturin pytest\n          maturin develop --locked\n          pytest --junitxml=results.xml\n\n      # Run tests with nextest and output JUnit results\n      - name: Run tests\n        run: |\n          cargo nextest run --workspace --profile ci\n\n      # Upload test results so GitHub shows pass/fail per test\n      - name: Upload test results\n        uses: EnricoMi/publish-unit-test-result-action/windows@v2\n        if: always() # Upload even if tests fail\n        with:\n          files: |\n             target/nextest/ci/junit.xml\n             python/results.xml\n        "
  },
  {
    "path": ".gitignore",
    "content": "# Generated by Cargo\n# will have compiled files and executables\n/target/\n\n# These are backup files generated by rustfmt\n**/*.rs.bk\n\n# VSCode\n.vs/\n\n# don't checkin lock file for fuzz as it should get autogen\nfuzz/Cargo.lock\n\n# ignore python virtual env\n.env/\n__pycache__"
  },
  {
    "path": ".vscode/launch.json",
    "content": "{\n    // Use IntelliSense to learn about possible attributes.\n    // Hover to view descriptions of existing attributes.\n    // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387\n    \"version\": \"0.2.0\",\n    \"configurations\": [\n        {\n            \"name\": \"(Windows) Launch\",\n            \"type\": \"cppvsdbg\",\n            \"request\": \"launch\",\n            \"program\": \"${workspaceFolder}/target/debug/lepton_jpeg_util.exe\",\n            \"args\": [\"-dump\", \"${workspaceFolder}\\\\images\\\\slrcity.jpg\"],\n            \"stopAtEntry\": false,\n            \"cwd\": \"${fileDirname}\",\n            \"environment\": [],\n            \"console\": \"externalTerminal\",\n        },\n        {\n            \"name\": \"Debug unit test\",\n            \"type\": \"cppvsdbg\",\n            \"request\": \"launch\",\n            \"program\": \"${workspaceFolder}/target/debug/deps/end_to_end-5c5d5b533f217bea.exe\",\n            \"args\": [ \"verify_extern_16bit_math_retry\" ],\n            \"stopAtEntry\": false,\n            \"cwd\": \"${workspaceFolder}\",\n            \"environment\": [],\n            \"console\": \"externalTerminal\",\n            \"preLaunchTask\": \"rust: cargo test norun\",\n        },\n    ]\n}"
  },
  {
    "path": ".vscode/tasks.json",
    "content": "{\n    \"version\": \"2.0.0\",\n    \"tasks\": [\n        {\n            \"type\": \"cargo\",\n            \"command\": \"test\",\n            \"args\": [\n                \"--no-run\"\n            ],\n            \"problemMatcher\": [\n                \"$rustc\"\n            ],\n            \"group\": \"test\",\n            \"label\": \"rust: cargo test norun\"\n        },\n        {\n            \"type\": \"cargo\",\n            \"command\": \"build\",\n            \"problemMatcher\": [\n                \"$rustc\"\n            ],\n            \"args\": [\n                \"--all\"\n            ],\n            \"group\": \"build\",\n            \"label\": \"rust: cargo build\"\n        }\n    ]\n}"
  },
  {
    "path": "CODE_OF_CONDUCT.md",
    "content": "# Microsoft Open Source Code of Conduct\n\nThis project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/).\n\nResources:\n\n- [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/)\n- [Microsoft Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/)\n- Contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with questions or concerns\n"
  },
  {
    "path": "CONTRIBUTING.md",
    "content": "# Contributing\n\nThis project welcomes contributions and suggestions. Most contributions require you to\nagree to a Contributor License Agreement (CLA) declaring that you have the right to,\nand actually do, grant us the rights to use your contribution. For details, visit\nhttps://cla.microsoft.com.\n\nWhen you submit a pull request, a CLA-bot will automatically determine whether you need\nto provide a CLA and decorate the PR appropriately (e.g., label, comment). Simply follow the\ninstructions provided by the bot. You will only need to do this once across all repositories using our CLA.\n\nThis project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/).\nFor more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/)\nor contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments."
  },
  {
    "path": "Cargo.toml",
    "content": "[package]\nname = \"lepton_jpeg_root\"\nedition = \"2024\"\nauthors = [\"Kristof Roomp <kristofr@microsoft.com>\"]\n\n[workspace.package]\nversion = \"0.5.8\"\nedition = \"2024\"\n\n[profile.release]\ndebug = true\nlto = true\n\n[workspace]\nmembers = [\"lib\", \"dll\", \"util\", \"python\"]\n\n# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html\n\n[dev-dependencies]\nlepton_jpeg = { path = \"lib\", features = [\"micro_benchmark\"] }\nrstest = \"0.22\"\nrand = \"0.8\"\nrand_chacha = \"0.3\"\nsiphasher = \"1\"\ncriterion = \"0.7\"\n\n[[bench]]\nname = \"benches\"\nharness = false"
  },
  {
    "path": "DESIGN.md",
    "content": "# Design\n\n## Overall approach\n\nThis library is designed to encode/decode JPEGs in a compressed file format that typically compresses files by about 20%. The overall approach is as follows:\n\n- Split JPEG into metadata/headers (stored as an binary array) and scan data (which is stored as a set of arrays of 8x8 16 bit coefficients per color channel)\n- The headers/metadata are compressed via Zlib and stored at the beginning of the lepton compressed files\n- The scan data is Huffman decoded, while verifying that it was encoded canonically (this is important since we canonically encode so that it is binary identical)\n- The scan data is encoded using the VP8 CABAC, with coefficients binarized using [Exponential-Golomb coding](https://en.wikipedia.org/wiki/Exponential-Golomb_coding). The bins for the CABAC encoder are determined by a fairly complex predictor model for:\n  - DC (the top left corner coefficient)\n  - The top and left edges (which are correlated to the previous blocks)\n  - The 7x7  block (the remaining 49 coefficients that are not the DC or the edges)\n  - It is vital that the model is identically and deterministically updated during encoding and decoding, since any discrepancy will rapidly cause the encoder and decoder to get out of sync and fail to decode the image\n- In order to increase response time, the scan data is partitioned by up to 8 into horizontal sections, each of which can be encoded/decode on a separate thread. \n- Progressive JPEGs are handled slightly differently since they cannot be partitioned during the JPEG encoding step, since each progressive scan requires access to the entire image data.\n- As a last verification, the entire process is run in reverse to ensure that we can recreate the binary-identical JPEG\n\n## Layers\n\nThe main layers of the library are as follows:\n\n- `lepton_format.rs` implements reading and writing Lepton format files and launching partitioned decoder/encoder threads\n- `lepton_encoder.rs` / `lepton_decoder.rs` perform the actual scan encoding/decoding using `model.rs` to track the bin probabilities in `branch.rs`\n- JPEGs are read and written by `jpeg_header.rs, jpeg_read.rs, jpeg_write.rs`, `jpeg_position_state.rs` which are invoked by `lepton_format.rs`\n- `bit_reader.rs / bit_writer.rs` are used by the Huffman encoding/decoding for reading writing JPEG format scan data\n- `vpx_bool_reader.rs / vpx_bool_writer.rs` are the CABAC encoder/decoder that using an arithmetic encoded binary stream with the probability of each bin is calculated in `brach.rs`. \n- `idct.rs` performs an inverse DCT of the JPEG coefficients as part of predicting the pixel values of neighbouring blocks\n- `thread_handoff.rs` used to partition the JPEG scan data so that multiple threads can process the same image. "
  },
  {
    "path": "LICENSE.txt",
    "content": "\n                                 Apache License\n                           Version 2.0, January 2004\n                        http://www.apache.org/licenses/\n\n   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n\n   1. Definitions.\n\n      \"License\" shall mean the terms and conditions for use, reproduction,\n      and distribution as defined by Sections 1 through 9 of this document.\n\n      \"Licensor\" shall mean the copyright owner or entity authorized by\n      the copyright owner that is granting the License.\n\n      \"Legal Entity\" shall mean the union of the acting entity and all\n      other entities that control, are controlled by, or are under common\n      control with that entity. For the purposes of this definition,\n      \"control\" means (i) the power, direct or indirect, to cause the\n      direction or management of such entity, whether by contract or\n      otherwise, or (ii) ownership of fifty percent (50%) or more of the\n      outstanding shares, or (iii) beneficial ownership of such entity.\n\n      \"You\" (or \"Your\") shall mean an individual or Legal Entity\n      exercising permissions granted by this License.\n\n      \"Source\" form shall mean the preferred form for making modifications,\n      including but not limited to software source code, documentation\n      source, and configuration files.\n\n      \"Object\" form shall mean any form resulting from mechanical\n      transformation or translation of a Source form, including but\n      not limited to compiled object code, generated documentation,\n      and conversions to other media types.\n\n      \"Work\" shall mean the work of authorship, whether in Source or\n      Object form, made available under the License, as indicated by a\n      copyright notice that is included in or attached to the work\n      (an example is provided in the Appendix below).\n\n      \"Derivative Works\" shall mean any work, whether in Source or Object\n      form, that is based on (or derived from) the Work and for which the\n      editorial revisions, annotations, elaborations, or other modifications\n      represent, as a whole, an original work of authorship. For the purposes\n      of this License, Derivative Works shall not include works that remain\n      separable from, or merely link (or bind by name) to the interfaces of,\n      the Work and Derivative Works thereof.\n\n      \"Contribution\" shall mean any work of authorship, including\n      the original version of the Work and any modifications or additions\n      to that Work or Derivative Works thereof, that is intentionally\n      submitted to Licensor for inclusion in the Work by the copyright owner\n      or by an individual or Legal Entity authorized to submit on behalf of\n      the copyright owner. For the purposes of this definition, \"submitted\"\n      means any form of electronic, verbal, or written communication sent\n      to the Licensor or its representatives, including but not limited to\n      communication on electronic mailing lists, source code control systems,\n      and issue tracking systems that are managed by, or on behalf of, the\n      Licensor for the purpose of discussing and improving the Work, but\n      excluding communication that is conspicuously marked or otherwise\n      designated in writing by the copyright owner as \"Not a Contribution.\"\n\n      \"Contributor\" shall mean Licensor and any individual or Legal Entity\n      on behalf of whom a Contribution has been received by Licensor and\n      subsequently incorporated within the Work.\n\n   2. Grant of Copyright License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      copyright license to reproduce, prepare Derivative Works of,\n      publicly display, publicly perform, sublicense, and distribute the\n      Work and such Derivative Works in Source or Object form.\n\n   3. Grant of Patent License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      (except as stated in this section) patent license to make, have made,\n      use, offer to sell, sell, import, and otherwise transfer the Work,\n      where such license applies only to those patent claims licensable\n      by such Contributor that are necessarily infringed by their\n      Contribution(s) alone or by combination of their Contribution(s)\n      with the Work to which such Contribution(s) was submitted. If You\n      institute patent litigation against any entity (including a\n      cross-claim or counterclaim in a lawsuit) alleging that the Work\n      or a Contribution incorporated within the Work constitutes direct\n      or contributory patent infringement, then any patent licenses\n      granted to You under this License for that Work shall terminate\n      as of the date such litigation is filed.\n\n   4. Redistribution. You may reproduce and distribute copies of the\n      Work or Derivative Works thereof in any medium, with or without\n      modifications, and in Source or Object form, provided that You\n      meet the following conditions:\n\n      (a) You must give any other recipients of the Work or\n          Derivative Works a copy of this License; and\n\n      (b) You must cause any modified files to carry prominent notices\n          stating that You changed the files; and\n\n      (c) You must retain, in the Source form of any Derivative Works\n          that You distribute, all copyright, patent, trademark, and\n          attribution notices from the Source form of the Work,\n          excluding those notices that do not pertain to any part of\n          the Derivative Works; and\n\n      (d) If the Work includes a \"NOTICE\" text file as part of its\n          distribution, then any Derivative Works that You distribute must\n          include a readable copy of the attribution notices contained\n          within such NOTICE file, excluding those notices that do not\n          pertain to any part of the Derivative Works, in at least one\n          of the following places: within a NOTICE text file distributed\n          as part of the Derivative Works; within the Source form or\n          documentation, if provided along with the Derivative Works; or,\n          within a display generated by the Derivative Works, if and\n          wherever such third-party notices normally appear. The contents\n          of the NOTICE file are for informational purposes only and\n          do not modify the License. You may add Your own attribution\n          notices within Derivative Works that You distribute, alongside\n          or as an addendum to the NOTICE text from the Work, provided\n          that such additional attribution notices cannot be construed\n          as modifying the License.\n\n      You may add Your own copyright statement to Your modifications and\n      may provide additional or different license terms and conditions\n      for use, reproduction, or distribution of Your modifications, or\n      for any such Derivative Works as a whole, provided Your use,\n      reproduction, and distribution of the Work otherwise complies with\n      the conditions stated in this License.\n\n   5. Submission of Contributions. Unless You explicitly state otherwise,\n      any Contribution intentionally submitted for inclusion in the Work\n      by You to the Licensor shall be under the terms and conditions of\n      this License, without any additional terms or conditions.\n      Notwithstanding the above, nothing herein shall supersede or modify\n      the terms of any separate license agreement you may have executed\n      with Licensor regarding such Contributions.\n\n   6. Trademarks. This License does not grant permission to use the trade\n      names, trademarks, service marks, or product names of the Licensor,\n      except as required for reasonable and customary use in describing the\n      origin of the Work and reproducing the content of the NOTICE file.\n\n   7. Disclaimer of Warranty. Unless required by applicable law or\n      agreed to in writing, Licensor provides the Work (and each\n      Contributor provides its Contributions) on an \"AS IS\" BASIS,\n      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\n      implied, including, without limitation, any warranties or conditions\n      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A\n      PARTICULAR PURPOSE. You are solely responsible for determining the\n      appropriateness of using or redistributing the Work and assume any\n      risks associated with Your exercise of permissions under this License.\n\n   8. Limitation of Liability. In no event and under no legal theory,\n      whether in tort (including negligence), contract, or otherwise,\n      unless required by applicable law (such as deliberate and grossly\n      negligent acts) or agreed to in writing, shall any Contributor be\n      liable to You for damages, including any direct, indirect, special,\n      incidental, or consequential damages of any character arising as a\n      result of this License or out of the use or inability to use the\n      Work (including but not limited to damages for loss of goodwill,\n      work stoppage, computer failure or malfunction, or any and all\n      other commercial damages or losses), even if such Contributor\n      has been advised of the possibility of such damages.\n\n   9. Accepting Warranty or Additional Liability. While redistributing\n      the Work or Derivative Works thereof, You may choose to offer,\n      and charge a fee for, acceptance of support, warranty, indemnity,\n      or other liability obligations and/or rights consistent with this\n      License. However, in accepting such obligations, You may act only\n      on Your own behalf and on Your sole responsibility, not on behalf\n      of any other Contributor, and only if You agree to indemnify,\n      defend, and hold each Contributor harmless for any liability\n      incurred by, or claims asserted against, such Contributor by reason\n      of your accepting any such warranty or additional liability.\n\n   END OF TERMS AND CONDITIONS\n\n   APPENDIX: How to apply the Apache License to your work.\n\n      To apply the Apache License to your work, attach the following\n      boilerplate notice, with the fields enclosed by brackets \"[]\"\n      replaced with your own identifying information. (Don't include\n      the brackets!)  The text should be enclosed in the appropriate\n      comment syntax for the file format. We also recommend that a\n      file or class name and description of purpose be included on the\n      same \"printed page\" as the copyright notice for easier\n      identification within third-party archives.\n\n   Copyright [yyyy] [name of copyright owner]\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n"
  },
  {
    "path": "NOTICE.txt",
    "content": "Lepton JPEG compression Rust Port Copyright (c) Microsoft Corporation\n\nNOTICES AND INFORMATION\nDo Not Translate or Localize\n\nThis software incorporates material from third parties.\nMicrosoft makes certain open source code available at https://3rdpartysource.microsoft.com,\nor you may send a check or money order for US $5.00, including the product name,\nthe open source component name, platform, and version number, to:\n\nSource Code Compliance Team\nMicrosoft Corporation\nOne Microsoft Way\nRedmond, WA 98052\nUSA\n\nNotwithstanding any other terms, you may reverse engineer this software to the extent\nrequired to debug changes to any libraries licensed under the GNU Lesser General Public License.\n\n-------------------\n\nThis software includes parts of the Dropbox Lepton project (https://github.com/dropbox/lepton). \nLepton is a tool and file format for losslessly compressing JPEGs by an average of 22%. Lepton is\nlicensed under Apache License 2.0, you can find a copy of this license at https://github.com/dropbox/lepton/blob/master/LICENSE\n\n-------------------\n\nThis software includes parts of the Libwebp project https://github.com/webmproject/libwebp\nlicensed under BSD 3-Clause License, you can find a copy of this license at https://github.com/webmproject/libwebp/blob/main/COPYING\n\n----------------\n\nThis software includes parts of the uncmpJPG project, which is a library to for lossless JPEG decompression\nlicensed under BSD 2-Clause License, you can find a copy of this license at  http://packjpg.encode.su/?page_id=178\n"
  },
  {
    "path": "README.md",
    "content": "# Lepton JPEG Compression in Rust \n[![Rust Community](https://img.shields.io/badge/Rust_Community%20-Join_us-brightgreen?style=plastic&logo=rust)](https://www.rust-lang.org/community)\n\nThis is a port of the C++ Lepton JPEG compression tool that was released by DropBox [dropbox/lepton](https://github.com/dropbox/lepton). We developed a port of the library to Rust, which has basically the same performance characteristics with the advantage of all the safety features that Rust has to offer, due to the work involved in performing an exhaustive security check on the C++ code and the fact that DropBox has deprecated the codebase.\n\nWith precise bit-by-bit recovery of the original JPEG, the Lepton compression library is designed for lossless compression of baseline and progressive JPEGs up to 22%. JPEG storage in a cloud storage system is the main application case. Even metadata headers and invalid content are kept in good condition.\n\n## How to Use This Library\n\n### Rust\n\nThe libary is published on crates.io as [lepton_jpeg](https://crates.io/crates/lepton_jpeg). \n\n### Python \n\nThe library is published on PyPI as *lepton_jpeg_python*.\n\n```\npip install lepton_jpeg_python\n```\n\n``` Python\nimport lepton_jpeg_python\n\nwith open(\"../images/slrcity.jpg\", \"rb\") as f:\n    jpg_data = f.read()\n\ncompressed = lepton_jpeg_python.compress_bytes(jpg_data, config)\ndecompressed = lepton_jpeg_python.decompress_bytes(compressed, config)\n\nassert jpg_data == decompressed\n```\n\n### Building From Source\n\n- [Rust 1.89 or Above](https://www.rust-lang.org/tools/install)\n\n``` bash\ngit clone https://github.com/microsoft/lepton_jpeg_rust\ncd lepton_jpeg_rust\ncargo build\ncargo test\ncargo build --release\n```\n\nSome operations of this library are vectorized such as the IDCT using the [Wide](https://crates.io/crates/wide) crate, so you can get a significant boost if you enable +AVX2.\n\n### Executable\n\nBuilding the Rust project generates an `lepton_jpeg_util.exe` wrapper that is built as part of the project. It can be used to compress/decompress and also to verify the test end-to-end on a given JPEG. If the input file has a `.jpg` extension, it will encode. If the input file has a `.lep` extension, it will decode back to the original`.jpg`.\n\nIt supports the following options:\n\n`lepton_jpeg_util.exe [options] <inputfile> [<outputfile>]`\n\n| Option                  | Description                                                  |\n| ----------------------- | ------------------------------------------------------------ |\n| `-threads:n`            | Runs with a maximum of n threads. For encoding, this limits the amount of parallelism that can be gotten out of the decoder. |\n| `-dump`                 | Dumps the contents of a JPG or LEP file, with the `-all` option, it will also dump the cooefficient image blocks. |\n| `-noprogressive`        | Will cause an error if we encounter a progressive file rather than trying to encode it. |\n| `-acceptdqtswithzeros`  | Accept images with DQTs with zeros (may cause divide-by-zero). |\n| `-iter:n`               | Runs N iterations of the operation. Useful when we are running inside a profiler. |\n| `-max-width:n`          | Limit the maximum image width to n pixels, instead of the default 16386. Fails with an error if limit is exceeded. |\n| `-max-height:n`         | Limit the maximum image height to n pixels, instead of the default 16386. Fails with an error il limit is exceeded. |\n\n## Contributing\n\nThere are many ways in which you can participate in this project, for example:\n\n* [Submit bugs and feature requests](https://github.com/microsoft/lepton_jpeg_rust/issues), and help us verify as they are checked in\n* Review [source code changes](https://github.com/microsoft/lepton_jpeg_rust/pulls) or submit your own features as pull requests.\n* The library uses only **stable features**, so if you want to take advantage of SIMD features such as AVX2, use the Wide crate (see the idct.rs as an example) rather than intrinsics. \n\n## Code of Conduct\n\nThis project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments.\n\n## License\n\nCopyright (c) Microsoft Corporation. All rights reserved.\n\nLicensed under the [Apache 2.0](LICENSE.txt) license.\n\n"
  },
  {
    "path": "SECURITY.md",
    "content": "<!-- BEGIN MICROSOFT SECURITY.MD V0.0.8 BLOCK -->\n\n## Security\n\nMicrosoft takes the security of our software products and services seriously, which includes all source code repositories managed through our GitHub organizations, which include [Microsoft](https://github.com/microsoft), [Azure](https://github.com/Azure), [DotNet](https://github.com/dotnet), [AspNet](https://github.com/aspnet), [Xamarin](https://github.com/xamarin), and [our GitHub organizations](https://opensource.microsoft.com/).\n\nIf you believe you have found a security vulnerability in any Microsoft-owned repository that meets [Microsoft's definition of a security vulnerability](https://aka.ms/opensource/security/definition), please report it to us as described below.\n\n## Reporting Security Issues\n\n**Please do not report security vulnerabilities through public GitHub issues.**\n\nInstead, please report them to the Microsoft Security Response Center (MSRC) at [https://msrc.microsoft.com/create-report](https://aka.ms/opensource/security/create-report).\n\nIf you prefer to submit without logging in, send email to [secure@microsoft.com](mailto:secure@microsoft.com).  If possible, encrypt your message with our PGP key; please download it from the [Microsoft Security Response Center PGP Key page](https://aka.ms/opensource/security/pgpkey).\n\nYou should receive a response within 24 hours. If for some reason you do not, please follow up via email to ensure we received your original message. Additional information can be found at [microsoft.com/msrc](https://aka.ms/opensource/security/msrc). \n\nPlease include the requested information listed below (as much as you can provide) to help us better understand the nature and scope of the possible issue:\n\n  * Type of issue (e.g. buffer overflow, SQL injection, cross-site scripting, etc.)\n  * Full paths of source file(s) related to the manifestation of the issue\n  * The location of the affected source code (tag/branch/commit or direct URL)\n  * Any special configuration required to reproduce the issue\n  * Step-by-step instructions to reproduce the issue\n  * Proof-of-concept or exploit code (if possible)\n  * Impact of the issue, including how an attacker might exploit the issue\n\nThis information will help us triage your report more quickly.\n\nIf you are reporting for a bug bounty, more complete reports can contribute to a higher bounty award. Please visit our [Microsoft Bug Bounty Program](https://aka.ms/opensource/security/bounty) page for more details about our active programs.\n\n## Preferred Languages\n\nWe prefer all communications to be in English.\n\n## Policy\n\nMicrosoft follows the principle of [Coordinated Vulnerability Disclosure](https://aka.ms/opensource/security/cvd).\n\n<!-- END MICROSOFT SECURITY.MD BLOCK -->\n"
  },
  {
    "path": "azure-pipelines.yml",
    "content": "trigger:\n  branches:\n    include:\n    - main\n  tags:\n    include:\n    - v*.*.*\n\npr:\n  branches:\n    include:\n    - main\n\nresources:\n  repositories:\n  - repository: odspPipelines\n    type: git\n    name: EFun/ODSPTemplates\n    ref: refs/heads/main\n\nvariables:\n- name: toolchainFeed\n  value: https://onedrive.pkgs.visualstudio.com/b52099a6-3b13-4b08-9270-a07884a10e3d/_packaging/RustTools/nuget/v3/index.json\n- name: cratesIoFeed\n  value: sparse+https://onedrive.pkgs.visualstudio.com/b52099a6-3b13-4b08-9270-a07884a10e3d/_packaging/RustCratesIO/Cargo/index/\n\nextends:\n  template: v1/OdspPipeline.yml@odspPipelines\n  parameters:\n    settings:\n      template: Official\n    sdl:\n      clippy:\n        enabled: false\n    stages:\n\n    ## For more details on the Rust build workflow, see https://eng.ms/docs/cloud-ai-platform/devdiv/one-engineering-system-1es/1es-docs/1es-pipeline-templates/features/buildworkflows/rust\n    - stage: BuildStage\n      displayName: 🏗️ Cargo build\n      pool:\n        name: 1ESHostedAgents_Windows2022_v2\n        os: windows\n      jobs:\n          - job: BuildJob\n            displayName: 🏗️ Cargo build\n            templateContext:\n              type: buildJob\n              workflow: Rust\n              rust:\n                rustToolchain:\n                  version: ms-prod-1.90\n                  toolchainFeed: $(toolchainFeed)\n                  cratesIoFeed: $(cratesIoFeed)\n                target: x86_64-pc-windows-msvc\n                command: custom\n              sdl:\n                componentgovernance:\n                  enabled: true\n                  failOnAlert: true\n              postBuildSteps:\n                - script: |\n                    cargo build --workspace --locked 2>&1\n                    cargo test --no-run --workspace --locked 2>&1\n\n                  displayName: \"Build debug\"\n\n                - script: |\n                    cargo install junit-test\n                    junit-test\n                    copy junit.xml $(System.DefaultWorkingDirectory)\\TEST-rust.xml\n                    rd /s /q target\\debug\n                  displayName: 'Test debug'\n\n                - task: PublishTestResults@2\n                  displayName: 'Publish Test Results **/TEST-*.xml'\n                  inputs:\n                    mergeTestResults: true\n\n                - script: |\n                    set CL=/Qspectre /sdl /Zi /W3\n                    set RUSTFLAGS=-Ccontrol-flow-guard -Ctarget-feature=+crt-static,+avx2,+lzcnt -Clink-args=/DYNAMICBASE -Clink-args=/CETCOMPAT\n                    cargo build --workspace --locked --release 2>&1\n                    copy target\\release\\lepton_jpeg.dll target\\release\\lepton_jpeg_avx2.dll\n                    copy target\\release\\lepton_jpeg.pdb target\\release\\lepton_jpeg_avx2.pdb\n                    copy target\\release\\lepton_jpeg_util.exe target\\release\\lepton_jpeg_util_avx2.exe\n                    copy target\\release\\lepton_jpeg_util.pdb target\\release\\lepton_jpeg_util_avx2.pdb\n                    set RUSTFLAGS=-Ccontrol-flow-guard -Ctarget-feature=+crt-static -Clink-args=/DYNAMICBASE -Clink-args=/CETCOMPAT\n                    cargo build --workspace --locked --release 2>&1\n                    rd /s /q target\\release\\build\n\n                  displayName: 'Build Release'\n\n                - task: UseDotNet@2\n                  inputs:\n                    packageType: 'sdk'\n                    version: '6.x'\n\n                - task: EsrpCodeSigning@5\n                  inputs:\n                    ConnectedServiceName: 'ESRP CodeSigningV2-OneDrive Service'\n                    AppRegistrationClientId: 'bd3fbc52-4cf5-4cca-a25d-94160e5ed309'\n                    AppRegistrationTenantId: 'cdc5aeea-15c5-4db6-b079-fcadd2505dc2'\n                    AuthAKVName: 'ODSP-ESRP'\n                    AuthCertName: 'ODSP-ESRP-Auth-V2'\n                    AuthSignCertName: 'CodeSigningCertificate'\n                    FolderPath: '$(Build.SourcesDirectory)'\n                    Pattern: '\n                      target\\release\\lepton_jpeg.dll,\n                      target\\release\\lepton_jpeg_avx2.dll,\n                      target\\release\\lepton_jpeg_util.exe,\n                      target\\release\\lepton_jpeg_util_avx2.exe'\n                    signConfigType: 'inlineSignParams'\n                    inlineOperation: |\n                      [\n                      {\n                        \"KeyCode\": \"CP-401405\",\n                        \"OperationCode\": \"SigntoolSign\",\n                        \"ToolName\": \"sign\",\n                        \"ToolVersion\": \"1.0\",\n                        \"Parameters\": {\n                        \"OpusName\": \"Microsoft\",\n                        \"OpusInfo\": \"https://www.microsoft.com\",\n                        \"FileDigest\": \"/fd SHA256\",\n                        \"PageHash\": \"/NPH\",\n                        \"TimeStamp\": \"/tr \\\"http://rfc3161.gtm.corp.microsoft.com/TSS/HttpTspServer\\\" /td sha256\"\n                        }\n                      },\n                      {\n                        \"KeyCode\": \"CP-401405\",\n                        \"OperationCode\": \"SigntoolVerify\",\n                        \"ToolName\": \"sign\",\n                        \"ToolVersion\": \"1.0\",\n                        \"Parameters\": {}\n                      }\n                      ]\n                    SessionTimeout: '60'\n                    MaxConcurrency: '50'\n                    MaxRetryAttempts: '5'\n                    PendingAnalysisWaitTimeoutMinutes: '5'\n\n                - task: CopyFiles@2\n                  displayName: 'Copy Rust output files to: $(Build.ArtifactStagingDirectory) copy'\n                  inputs:\n                    SourceFolder: '$(Build.SourcesDirectory)'\n                    Contents: |\n                      target\\debug\\?(*.dll|*.exe|*.pdb)\n                      target\\release\\?(*.dll|*.exe|*.pdb)\n\n                    TargetFolder: '$(Build.ArtifactStagingDirectory)'\n\n                - task: PublishSymbols@2\n                  displayName: 'Publish symbols copy'\n                  inputs:\n                    SymbolsFolder: '$(Build.ArtifactStagingDirectory)'\n                    SearchPattern: '**\\*.pdb'\n                    SymbolServerType: TeamServices\n\n                - task: NuGetCommand@2\n                  displayName: 'NuGet pack'\n                  inputs:\n                    command: pack\n                    packagesToPack: package/Lepton.Jpeg.Rust.nuspec\n\n                - task: 1ES.PublishNuGet@1\n                  displayName: 'NuGet push'\n                  condition: and(succeeded(), startsWith(variables['Build.SourceBranch'], 'refs/tags/v'))\n                  inputs:\n                    packageParentPath: '$(Pipeline.Workspace)'\n                    packagesToPush: '$(Build.ArtifactStagingDirectory)\\*.nupkg'\n                    publishVstsFeed: 'b87285d9-99ab-48db-a000-cb0cc8a2a1b5'\n                    allowPackageConflicts: true\n"
  },
  {
    "path": "benches/benches.rs",
    "content": "use std::{io::Cursor, time::Duration};\n\nuse criterion::{Criterion, criterion_group, criterion_main};\nuse lepton_jpeg::{EnabledFeatures, SingleThreadPool};\n\nfn read_file(filename: &str, ext: &str) -> Vec<u8> {\n    let filename = std::path::Path::new(env!(\"WORKSPACE_ROOT\"))\n        .join(\"images\")\n        .join(filename.to_owned() + ext);\n    //println!(\"reading {0}\", filename.to_str().unwrap());\n    let mut f = std::fs::File::open(filename).unwrap();\n\n    let mut content = Vec::new();\n    std::io::Read::read_to_end(&mut f, &mut content).unwrap();\n\n    content\n}\n\nfn end_to_end_benches(c: &mut Criterion) {\n    let thread_pool = SingleThreadPool::default();\n    let mut g = c.benchmark_group(\"end_to_end\");\n    g.sampling_mode(criterion::SamplingMode::Flat);\n    g.warm_up_time(Duration::from_secs(1));\n    g.measurement_time(Duration::from_secs(10));\n\n    let jpeg = read_file(\"iphone\", \".jpg\");\n    let lep = read_file(\"iphone\", \".lep\");\n\n    g.bench_function(\"Lepton encode\", |b| {\n        b.iter(|| {\n            let mut output = Vec::with_capacity(jpeg.len());\n            lepton_jpeg::encode_lepton(\n                &mut Cursor::new(&jpeg),\n                &mut Cursor::new(&mut output),\n                &EnabledFeatures::compat_lepton_vector_write(),\n                &thread_pool,\n            )\n        })\n    });\n\n    g.bench_function(\"Lepton decode\", |b| {\n        b.iter(|| {\n            let mut output = Vec::with_capacity(lep.len());\n            lepton_jpeg::decode_lepton(\n                &mut Cursor::new(&lep),\n                &mut Cursor::new(&mut output),\n                &EnabledFeatures::compat_lepton_vector_write(),\n                &thread_pool,\n            )\n        })\n    });\n\n    g.finish();\n}\n\ncriterion_group!(group1, end_to_end_benches);\n\nfn micro_benchmarks(c: &mut Criterion) {\n    use lepton_jpeg::micro_benchmark::{\n        benchmark_idct, benchmark_read_block, benchmark_read_jpeg, benchmark_roundtrip_coefficient,\n        benchmark_write_block, benchmark_write_jpeg,\n    };\n\n    c.bench_function(\"jpeg read\", |b| b.iter(benchmark_read_jpeg()));\n\n    c.bench_function(\"jpeg write\", |b| b.iter(benchmark_write_jpeg()));\n\n    c.bench_function(\"roundtrip coefficient write\", |b| {\n        b.iter(benchmark_roundtrip_coefficient())\n    });\n\n    c.bench_function(\"idct benchmark\", |b| b.iter(benchmark_idct()));\n\n    c.bench_function(\"jpeg read block\", |b| b.iter(benchmark_read_block()));\n\n    c.bench_function(\"jpeg write block\", |b| b.iter(benchmark_write_block()));\n}\n\ncriterion_group!(group2, micro_benchmarks);\n\ncriterion_main!(group1, group2);\n"
  },
  {
    "path": "dll/Cargo.toml",
    "content": "[package]\nname = \"lepton_jpeg_dll\"\nversion.workspace = true\nedition = \"2024\"\nauthors = [\"Kristof Roomp <kristofr@microsoft.com>\"]\n\n[dependencies]\nlepton_jpeg = { path = \"../lib\" }\nrayon = \"1\"\nmsvc_spectre_libs = \"0.1.3\"\n\n[dev-dependencies]\nrstest = \"0.22\"\n\n[lib]\ncrate-type = [\"cdylib\"]\nname = \"lepton_jpeg\""
  },
  {
    "path": "dll/src/lib.rs",
    "content": "/*---------------------------------------------------------------------------------------------\n *  Copyright (c) Microsoft Corporation. All rights reserved.\n *  Licensed under the Apache License, Version 2.0. See LICENSE.txt in the project root for license information.\n *  This software incorporates material from third parties. See NOTICE.txt for details.\n *--------------------------------------------------------------------------------------------*/\n\n#![forbid(trivial_numeric_casts)]\n\nuse std::{\n    collections::VecDeque,\n    io::Cursor,\n    sync::{\n        LazyLock,\n        atomic::{AtomicU32, Ordering},\n    },\n};\n\nuse lepton_jpeg::{\n    DEFAULT_THREAD_POOL, EnabledFeatures, ExitCode, LeptonFileReader, LeptonThreadPool,\n    SingleThreadPool, ThreadPoolHolder, catch_unwind_result, decode_lepton, encode_lepton,\n    get_git_version,\n};\n\n/// copies a string into a limited length zero terminated utf8 buffer\nfn copy_cstring_utf8_to_buffer(str: &str, target_error_string: &mut [u8]) {\n    if target_error_string.len() == 0 {\n        return;\n    }\n\n    // copy error string into the buffer as utf8\n    let b = std::ffi::CString::new(str).unwrap();\n    let b = b.as_bytes();\n\n    let copy_len = std::cmp::min(b.len(), target_error_string.len() - 1);\n\n    // copy string into buffer as much as fits\n    target_error_string[0..copy_len].copy_from_slice(&b[0..copy_len]);\n\n    // always null terminated\n    target_error_string[copy_len] = 0;\n}\n\nstruct RayonThreadPool {\n    pool: LazyLock<rayon::ThreadPool>,\n}\n\nimpl LeptonThreadPool for RayonThreadPool {\n    fn max_parallelism(&self) -> usize {\n        NUM_THREADS.load(Ordering::SeqCst) as usize\n    }\n    fn run(&self, f: Box<dyn FnOnce() + Send + 'static>) {\n        self.pool.spawn(f);\n    }\n}\n\nstatic NUM_THREADS: AtomicU32 = AtomicU32::new(8);\n\nstatic RAYON_THREAD_POOL: RayonThreadPool = RayonThreadPool {\n    pool: LazyLock::new(|| {\n        rayon::ThreadPoolBuilder::new()\n            .num_threads(NUM_THREADS.load(Ordering::SeqCst) as usize) // default to 8 threads, can be adjusted\n            .build()\n            .unwrap()\n    }),\n};\n\n/// C ABI interface for setting the number of threads to use for compression and decompression\n/// This can only be called before any compression or decompression is done, as we cannot\n/// change the number of threads in the threadpool once it is created.\npub unsafe extern \"C\" fn set_num_threads(num_threads: u32) {\n    NUM_THREADS.store(num_threads, Ordering::SeqCst);\n}\n\n/// C ABI interface for compressing image, exposed from DLL\n#[unsafe(no_mangle)]\n#[allow(non_snake_case, unsafe_op_in_unsafe_fn)]\npub unsafe extern \"C\" fn WrapperCompressImage(\n    input_buffer: *const u8,\n    input_buffer_size: u64,\n    output_buffer: *mut u8,\n    output_buffer_size: u64,\n    number_of_threads: i32,\n    result_size: *mut u64,\n) -> i32 {\n    let mut cpu_usage: u64 = 0;\n    WrapperCompressImage3(\n        input_buffer,\n        input_buffer_size,\n        output_buffer,\n        output_buffer_size,\n        number_of_threads as u32,\n        result_size,\n        (&mut cpu_usage) as *mut u64,\n        0,\n        std::ptr::null_mut(),\n        0,\n    )\n}\n\n/// C ABI interface for compressing image, exposed from DLL\n#[unsafe(no_mangle)]\n#[allow(non_snake_case, unsafe_op_in_unsafe_fn)]\npub unsafe extern \"C\" fn WrapperCompressImage2(\n    input_buffer: *const u8,\n    input_buffer_size: u64,\n    output_buffer: *mut u8,\n    output_buffer_size: u64,\n    number_of_threads: u32,\n    result_size: *mut u64,\n    cpu_usage: *mut u64,\n    flags: u32,\n) -> i32 {\n    WrapperCompressImage3(\n        input_buffer,\n        input_buffer_size,\n        output_buffer,\n        output_buffer_size,\n        number_of_threads,\n        result_size,\n        cpu_usage,\n        flags,\n        std::ptr::null_mut(),\n        0,\n    )\n}\n\n/// C ABI interface for compressing image, exposed from DLL\n#[unsafe(no_mangle)]\n#[allow(non_snake_case, unsafe_op_in_unsafe_fn)]\npub unsafe extern \"C\" fn WrapperCompressImage3(\n    input_buffer: *const u8,\n    input_buffer_size: u64,\n    output_buffer: *mut u8,\n    output_buffer_size: u64,\n    number_of_threads: u32,\n    result_size: *mut u64,\n    cpu_usage: *mut u64,\n    flags: u32,\n    error_string: *mut std::os::raw::c_uchar,\n    error_string_buffer_len: u64,\n) -> i32 {\n    match catch_unwind_result(|| {\n        let input = std::slice::from_raw_parts(input_buffer, input_buffer_size as usize);\n\n        let output = std::slice::from_raw_parts_mut(output_buffer, output_buffer_size as usize);\n\n        let mut reader = Cursor::new(input);\n        let mut writer = Cursor::new(output);\n\n        let mut features = EnabledFeatures::compat_lepton_vector_write();\n        if number_of_threads > 0 {\n            features.max_partitions = number_of_threads;\n        }\n\n        let thread_pool: &dyn LeptonThreadPool = if flags & USE_RAYON_THREAD_POOL != 0 {\n            &RAYON_THREAD_POOL\n        } else if flags & USE_SINGLE_THREAD_POOL != 0 {\n            &SingleThreadPool::default()\n        } else {\n            &DEFAULT_THREAD_POOL\n        };\n\n        let metrics = encode_lepton(&mut reader, &mut writer, &features, thread_pool)?;\n\n        *result_size = writer.position().into();\n        *cpu_usage = metrics.get_cpu_time_worker_time().as_millis() as u64;\n\n        Ok(())\n    }) {\n        Ok(()) => {\n            return 0;\n        }\n        Err(e) => {\n            if error_string_buffer_len > 0 {\n                copy_cstring_utf8_to_buffer(\n                    e.message(),\n                    std::slice::from_raw_parts_mut(error_string, error_string_buffer_len as usize),\n                );\n            }\n\n            return e.exit_code().as_integer_error_code();\n        }\n    }\n}\n\n/// C ABI interface for decompressing image, exposed from DLL\n#[unsafe(no_mangle)]\n#[allow(non_snake_case, unsafe_op_in_unsafe_fn)]\npub unsafe extern \"C\" fn WrapperDecompressImage(\n    input_buffer: *const u8,\n    input_buffer_size: u64,\n    output_buffer: *mut u8,\n    output_buffer_size: u64,\n    number_of_threads: i32,\n    result_size: *mut u64,\n) -> i32 {\n    let mut cpu_usage: u64 = 0;\n    return WrapperDecompressImage4(\n        input_buffer,\n        input_buffer_size,\n        output_buffer,\n        output_buffer_size,\n        number_of_threads as u32,\n        result_size,\n        (&mut cpu_usage) as *mut u64,\n        0,\n        std::ptr::null_mut(),\n        0,\n    );\n}\n\n/// C ABI interface for decompressing image, exposed from DLL.\n/// use_16bit_dc_estimate argument should be set to true only for images\n/// that were compressed by C++ version of Leptron (see comments below).\n#[unsafe(no_mangle)]\n#[allow(non_snake_case, unsafe_op_in_unsafe_fn)]\npub unsafe extern \"C\" fn WrapperDecompressImageEx(\n    input_buffer: *const u8,\n    input_buffer_size: u64,\n    output_buffer: *mut u8,\n    output_buffer_size: u64,\n    number_of_threads: i32,\n    result_size: *mut u64,\n    use_16bit_dc_estimate: bool,\n) -> i32 {\n    let mut cpu_usage: u64 = 0;\n    WrapperDecompressImage4(\n        input_buffer,\n        input_buffer_size,\n        output_buffer,\n        output_buffer_size,\n        number_of_threads as u32,\n        result_size,\n        (&mut cpu_usage) as *mut u64,\n        if use_16bit_dc_estimate {\n            DECOMPRESS_USE_16BIT_DC_ESTIMATE\n        } else {\n            0\n        },\n        std::ptr::null_mut(),\n        0,\n    )\n}\n\n/// C ABI interface for decompressing image, exposed from DLL.\n#[unsafe(no_mangle)]\n#[allow(non_snake_case, unsafe_op_in_unsafe_fn)]\npub unsafe extern \"C\" fn WrapperDecompressImage3(\n    input_buffer: *const u8,\n    input_buffer_size: u64,\n    output_buffer: *mut u8,\n    output_buffer_size: u64,\n    number_of_threads: u32,\n    result_size: *mut u64,\n    cpu_usage: *mut u64,\n    flags: u32,\n) -> i32 {\n    WrapperDecompressImage4(\n        input_buffer,\n        input_buffer_size,\n        output_buffer,\n        output_buffer_size,\n        number_of_threads,\n        result_size,\n        cpu_usage,\n        flags,\n        std::ptr::null_mut(),\n        0,\n    )\n}\n\n/// C ABI interface for decompressing image, exposed from DLL.\n#[unsafe(no_mangle)]\n#[allow(non_snake_case, unsafe_op_in_unsafe_fn)]\npub unsafe extern \"C\" fn WrapperDecompressImage4(\n    input_buffer: *const u8,\n    input_buffer_size: u64,\n    output_buffer: *mut u8,\n    output_buffer_size: u64,\n    number_of_threads: u32,\n    result_size: *mut u64,\n    cpu_usage: *mut u64,\n    flags: u32,\n    error_string: *mut std::os::raw::c_uchar,\n    error_string_buffer_len: u64,\n) -> i32 {\n    match catch_unwind_result(|| {\n        // For back-compat with C++ version we allow decompression of images with zeros in DQT tables\n\n        // C++ version has a bug where it uses 16 bit math in the SIMD path and 32 bit math in the scalar path\n        // depending on the compiler options. If use_16bit_dc_estimate=true, the decompression uses a back-compat\n        // mode that considers it. The caller should set use_16bit_dc_estimate to true only for images that were\n        // compressed by C++ version with relevant compiler options.\n\n        // this is a bit of a mess since for a while we were encoded a mix of 16 and 32 bit math\n        // (hence the two parameters in features).\n\n        let mut enabled_features = EnabledFeatures {\n            use_16bit_dc_estimate: (flags & DECOMPRESS_USE_16BIT_DC_ESTIMATE != 0),\n            ..EnabledFeatures::compat_lepton_vector_read()\n        };\n\n        if number_of_threads > 0 {\n            enabled_features.max_partitions = number_of_threads;\n        }\n\n        let thread_pool: &dyn LeptonThreadPool = if flags & USE_RAYON_THREAD_POOL != 0 {\n            &RAYON_THREAD_POOL\n        } else if flags & USE_SINGLE_THREAD_POOL != 0 {\n            &SingleThreadPool::default()\n        } else {\n            &DEFAULT_THREAD_POOL\n        };\n\n        loop {\n            let input = std::slice::from_raw_parts(input_buffer, input_buffer_size as usize);\n            let output = std::slice::from_raw_parts_mut(output_buffer, output_buffer_size as usize);\n\n            let mut reader = Cursor::new(input);\n            let mut writer = Cursor::new(output);\n\n            match decode_lepton(&mut reader, &mut writer, &mut enabled_features, thread_pool) {\n                Ok(metrics) => {\n                    *result_size = writer.position().into();\n                    *cpu_usage = metrics.get_cpu_time_worker_time().as_millis() as u64;\n                    return Ok(());\n                }\n                Err(e) => {\n                    // The retry logic below runs if the caller did not pass use_16bit_dc_estimate=true, but the decompression\n                    // encountered StreamInconsistent failure which is commonly caused by the the C++ 16 bit bug. In this case\n                    // we retry the decompression with use_16bit_dc_estimate=true.\n                    // Note that it's prefferable for the caller to pass use_16bit_dc_estimate properly and not to rely on this\n                    // retry logic, that may miss some cases leading to bad (corrupted) decompression results.\n                    if e.exit_code() == ExitCode::StreamInconsistent\n                        && !enabled_features.use_16bit_dc_estimate\n                    {\n                        enabled_features.use_16bit_dc_estimate = true;\n                        continue;\n                    }\n\n                    return Err(e.into());\n                }\n            }\n        }\n    }) {\n        Ok(()) => {\n            return 0;\n        }\n        Err(e) => {\n            if error_string_buffer_len > 0 {\n                copy_cstring_utf8_to_buffer(\n                    e.message(),\n                    std::slice::from_raw_parts_mut(error_string, error_string_buffer_len as usize),\n                );\n            }\n            return e.exit_code().as_integer_error_code();\n        }\n    }\n}\n\nstatic PACKAGE_VERSION: &str = env!(\"CARGO_PKG_VERSION\");\n\npub fn get_version_string() -> String {\n    format!(\"{}-{}\", PACKAGE_VERSION, get_git_version())\n}\n\n#[unsafe(no_mangle)]\npub unsafe extern \"C\" fn get_version(\n    package: &mut *const std::os::raw::c_char,\n    git: &mut *const std::os::raw::c_char,\n) {\n    *git = get_git_version().as_ptr() as *const std::os::raw::c_char;\n    *package = PACKAGE_VERSION.as_ptr() as *const std::os::raw::c_char;\n}\n\n// wraps unmanaged context for decompression and tries to ensure that if is valid\n// when passed in from C# or C++ code and that it is freed only once.\n//\n// Of course, there aren't any guarantees since passing raw pointers around is inherently unsafe,\n// but we do our best to catch common mistakes and point the blame in the right direction.\nstruct DecompressionContext<'a> {\n    magic: u32,\n    internal: LeptonFileReader<'a>,\n    extra_data: VecDeque<u8>,\n}\n\nconst MAGIC_DECOMRESSION_CONTEXT: u32 = 0xdec0de00;\n\nimpl<'a> DecompressionContext<'a> {\n    /// casts c pointer to a reference, verifying the magic number is OK so we can catch\n    /// some common mistakes early. This is no guarantee, but helps crash early in many cases.\n    unsafe fn from_pointer(ptr: *mut std::ffi::c_void) -> &'a mut Self {\n        unsafe {\n            let context = ptr as *mut DecompressionContext;\n            assert_eq!(\n                (*context).magic,\n                MAGIC_DECOMRESSION_CONTEXT,\n                \"invalid context passed in\"\n            );\n            &mut *context\n        }\n    }\n\n    /// allocates a new context and returns a pointer to it\n    unsafe fn new(internal: LeptonFileReader<'a>) -> *mut std::ffi::c_void {\n        let context = Box::new(Self {\n            magic: MAGIC_DECOMRESSION_CONTEXT,\n            internal,\n            extra_data: VecDeque::new(),\n        });\n\n        Box::into_raw(context) as *mut std::ffi::c_void\n    }\n\n    unsafe fn free(ptr: *mut std::ffi::c_void) {\n        unsafe {\n            let mut context = Box::from_raw(ptr as *mut DecompressionContext);\n            assert_eq!(\n                (*context).magic,\n                MAGIC_DECOMRESSION_CONTEXT,\n                \"invalid context passed in\"\n            );\n            // invalidate magic to catch double free\n            context.magic = 0xdeaddead;\n\n            // context is freed now by going out of scope\n        }\n    }\n}\n\nconst DECOMPRESS_USE_16BIT_DC_ESTIMATE: u32 = 1;\nconst USE_RAYON_THREAD_POOL: u32 = 2;\nconst USE_SINGLE_THREAD_POOL: u32 = 4;\n\n#[unsafe(no_mangle)]\npub unsafe extern \"C\" fn create_decompression_context(features: u32) -> *mut std::ffi::c_void {\n    let enabled_features = EnabledFeatures {\n        use_16bit_dc_estimate: (features & DECOMPRESS_USE_16BIT_DC_ESTIMATE != 0),\n        ..EnabledFeatures::compat_lepton_vector_read()\n    };\n\n    let thread_pool: ThreadPoolHolder = if features & USE_RAYON_THREAD_POOL != 0 {\n        ThreadPoolHolder::Dyn(&RAYON_THREAD_POOL)\n    } else if features & USE_SINGLE_THREAD_POOL != 0 {\n        ThreadPoolHolder::Owned(Box::new(SingleThreadPool::default()))\n    } else {\n        ThreadPoolHolder::Dyn(&DEFAULT_THREAD_POOL)\n    };\n\n    unsafe { DecompressionContext::new(LeptonFileReader::new(enabled_features, thread_pool)) }\n}\n\n#[unsafe(no_mangle)]\n#[allow(unsafe_op_in_unsafe_fn)]\npub unsafe extern \"C\" fn get_decompression_cpu(context: *mut std::ffi::c_void) -> u64 {\n    let context = DecompressionContext::from_pointer(context);\n\n    context\n        .internal\n        .metrics()\n        .get_cpu_time_worker_time()\n        .as_millis() as u64\n}\n\n#[unsafe(no_mangle)]\n#[allow(unsafe_op_in_unsafe_fn)]\npub unsafe extern \"C\" fn free_decompression_context(context: *mut std::ffi::c_void) {\n    DecompressionContext::free(context);\n}\n\n/// partially decompresses an image from a Lepton file.\n///\n/// Returns -1 if more data is needed or if there is more data available, or 0 if done successfully.\n/// Returns > 0 if there is an error\n#[unsafe(no_mangle)]\n#[allow(unsafe_op_in_unsafe_fn)]\npub unsafe extern \"C\" fn decompress_image(\n    context: *mut std::ffi::c_void,\n    input_buffer: *const u8,\n    input_buffer_size: u64,\n    input_complete: bool,\n    output_buffer: *mut u8,\n    output_buffer_size: u64,\n    result_size: *mut u64,\n    error_string: *mut std::os::raw::c_uchar,\n    error_string_buffer_len: u64,\n) -> i32 {\n    match catch_unwind_result(|| {\n        let context = DecompressionContext::from_pointer(context);\n\n        let input = std::slice::from_raw_parts(input_buffer, input_buffer_size as usize);\n        let output = std::slice::from_raw_parts_mut(output_buffer, output_buffer_size as usize);\n\n        let (done, size) = context.internal.process_limited_buffer(\n            input,\n            input_complete,\n            output,\n            &mut context.extra_data,\n        )?;\n\n        *result_size = size as u64;\n        Ok(done)\n    }) {\n        Ok(done) => {\n            if done {\n                0\n            } else {\n                -1\n            }\n        }\n        Err(e) => {\n            if error_string_buffer_len > 0 {\n                copy_cstring_utf8_to_buffer(\n                    e.message(),\n                    std::slice::from_raw_parts_mut(error_string, error_string_buffer_len as usize),\n                );\n            }\n            e.exit_code().as_integer_error_code()\n        }\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n\n    use rstest::rstest;\n\n    fn read_file(filename: &str, ext: &str) -> Vec<u8> {\n        let filename = std::path::Path::new(env!(\"WORKSPACE_ROOT\"))\n            .join(\"images\")\n            .join(filename.to_owned() + ext);\n        //println!(\"reading {0}\", filename.to_str().unwrap());\n        let mut f = std::fs::File::open(filename).unwrap();\n\n        let mut content = Vec::new();\n        std::io::Read::read_to_end(&mut f, &mut content).unwrap();\n\n        content\n    }\n\n    #[test]\n    fn test_copy_cstring_utf8_to_buffer() {\n        // test utf8\n        let mut buffer = [0u8; 10];\n        copy_cstring_utf8_to_buffer(\"h\\u{00E1}llo\", &mut buffer);\n        assert_eq!(buffer, [b'h', 0xc3, 0xa1, b'l', b'l', b'o', 0, 0, 0, 0]);\n\n        // test null termination\n        let mut buffer = [0u8; 10];\n        copy_cstring_utf8_to_buffer(\"helloeveryone\", &mut buffer);\n        assert_eq!(\n            buffer,\n            [b'h', b'e', b'l', b'l', b'o', b'e', b'v', b'e', b'r', 0]\n        );\n    }\n\n    /// test original version of external interface that just delegates to the new one\n    #[test]\n    fn extern_interface() {\n        let input = read_file(\"slrcity\", \".jpg\");\n\n        let mut compressed = Vec::new();\n\n        compressed.resize(input.len() + 10000, 0);\n\n        let mut result_size: u64 = 0;\n\n        unsafe {\n            let retval = WrapperCompressImage(\n                input[..].as_ptr(),\n                input.len() as u64,\n                compressed[..].as_mut_ptr(),\n                compressed.len() as u64,\n                8,\n                (&mut result_size) as *mut u64,\n            );\n\n            assert_eq!(retval, 0);\n        }\n\n        let mut original = Vec::new();\n        original.resize(input.len() + 10000, 0);\n\n        let mut original_size: u64 = 0;\n        unsafe {\n            let retval = WrapperDecompressImageEx(\n                compressed[..].as_ptr(),\n                result_size,\n                original[..].as_mut_ptr(),\n                original.len() as u64,\n                8,\n                (&mut original_size) as *mut u64,\n                false,\n            );\n\n            assert_eq!(retval, 0);\n        }\n        assert_eq!(input.len() as u64, original_size);\n        assert_eq!(input[..], original[..(original_size as usize)]);\n    }\n\n    /// test version 2 of external interface\n    #[test]\n    fn extern_interface_2() {\n        let input = read_file(\"slrcity\", \".jpg\");\n\n        let mut compressed = Vec::new();\n\n        compressed.resize(input.len() + 10000, 0);\n\n        let mut result_size: u64 = 0;\n        let mut cpu_usage: u64 = 0;\n\n        unsafe {\n            let retval = WrapperCompressImage2(\n                input[..].as_ptr(),\n                input.len() as u64,\n                compressed[..].as_mut_ptr(),\n                compressed.len() as u64,\n                8,\n                (&mut result_size) as *mut u64,\n                (&mut cpu_usage) as *mut u64,\n                0,\n            );\n\n            assert_eq!(retval, 0);\n        }\n\n        let mut original = Vec::new();\n        original.resize(input.len() + 10000, 0);\n\n        let mut original_size: u64 = 0;\n        unsafe {\n            let retval = WrapperDecompressImage3(\n                compressed[..].as_ptr(),\n                result_size,\n                original[..].as_mut_ptr(),\n                original.len() as u64,\n                8,\n                (&mut original_size) as *mut u64,\n                (&mut cpu_usage) as *mut u64,\n                0,\n            );\n\n            assert_eq!(retval, 0);\n        }\n        assert_eq!(input.len() as u64, original_size);\n        assert_eq!(input[..], original[..(original_size as usize)]);\n    }\n\n    /// test version 2 of external interface with single thread\n    #[test]\n    fn extern_interface_2_single_thread() {\n        let input = read_file(\"slrcity\", \".jpg\");\n\n        let mut compressed = Vec::new();\n\n        compressed.resize(input.len() + 10000, 0);\n\n        let mut result_size: u64 = 0;\n        let mut cpu_usage: u64 = 0;\n\n        unsafe {\n            let retval = WrapperCompressImage2(\n                input[..].as_ptr(),\n                input.len() as u64,\n                compressed[..].as_mut_ptr(),\n                compressed.len() as u64,\n                8,\n                (&mut result_size) as *mut u64,\n                (&mut cpu_usage) as *mut u64,\n                USE_SINGLE_THREAD_POOL,\n            );\n\n            assert_eq!(retval, 0);\n        }\n\n        let mut original = Vec::new();\n        original.resize(input.len() + 10000, 0);\n\n        let mut original_size: u64 = 0;\n        unsafe {\n            let retval = WrapperDecompressImage3(\n                compressed[..].as_ptr(),\n                result_size,\n                original[..].as_mut_ptr(),\n                original.len() as u64,\n                8,\n                (&mut original_size) as *mut u64,\n                (&mut cpu_usage) as *mut u64,\n                USE_SINGLE_THREAD_POOL,\n            );\n\n            assert_eq!(retval, 0);\n        }\n        assert_eq!(input.len() as u64, original_size);\n        assert_eq!(input[..], original[..(original_size as usize)]);\n    }\n\n    /// test version 3 of external interface with single thread\n    #[test]\n    fn extern_interface_3_single_thread() {\n        let input = read_file(\"slrcity\", \".jpg\");\n\n        let mut compressed = Vec::new();\n\n        compressed.resize(input.len() + 10000, 0);\n\n        let mut result_size: u64 = 0;\n        let mut cpu_usage: u64 = 0;\n\n        let mut error_string = [0u8; 1024];\n\n        unsafe {\n            let retval = WrapperCompressImage3(\n                input[..].as_ptr(),\n                0,\n                compressed[..].as_mut_ptr(),\n                compressed.len() as u64,\n                8,\n                (&mut result_size) as *mut u64,\n                (&mut cpu_usage) as *mut u64,\n                USE_SINGLE_THREAD_POOL,\n                error_string.as_mut_ptr(),\n                error_string.len() as u64,\n            );\n            // error string should complain about invalid input\n            assert_ne!(retval, 0);\n\n            // convert null terminated error_string into str\n            let error_str = std::str::from_utf8(&error_string)\n                .unwrap()\n                .trim_end_matches(char::from(0));\n\n            assert!(error_str.contains(\"jpeg must start with with 0xff 0xd8\"));\n\n            let retval = WrapperCompressImage3(\n                input[..].as_ptr(),\n                input.len() as u64,\n                compressed[..].as_mut_ptr(),\n                compressed.len() as u64,\n                8,\n                (&mut result_size) as *mut u64,\n                (&mut cpu_usage) as *mut u64,\n                USE_SINGLE_THREAD_POOL,\n                error_string.as_mut_ptr(),\n                error_string.len() as u64,\n            );\n\n            assert_eq!(retval, 0);\n        }\n\n        let mut original = Vec::new();\n        original.resize(input.len() + 10000, 0);\n\n        let mut original_size: u64 = 0;\n        unsafe {\n            let retval = WrapperDecompressImage4(\n                compressed[..].as_ptr(),\n                result_size,\n                original[..].as_mut_ptr(),\n                original.len() as u64,\n                8,\n                (&mut original_size) as *mut u64,\n                (&mut cpu_usage) as *mut u64,\n                USE_SINGLE_THREAD_POOL,\n                error_string.as_mut_ptr(),\n                error_string.len() as u64,\n            );\n\n            assert_eq!(retval, 0);\n        }\n        assert_eq!(input.len() as u64, original_size);\n        assert_eq!(input[..], original[..(original_size as usize)]);\n    }\n\n    /// tests the chunked decompression interface\n    #[rstest]\n    fn extern_interface_decompress_chunked(\n        #[values(DECOMPRESS_USE_16BIT_DC_ESTIMATE,DECOMPRESS_USE_16BIT_DC_ESTIMATE|USE_RAYON_THREAD_POOL)]\n        flags: u32,\n    ) {\n        use std::io::Read;\n\n        let input = read_file(\"slrcity\", \".lep\");\n\n        let mut output = Vec::new();\n\n        unsafe {\n            let context = create_decompression_context(flags);\n\n            let mut file_read = Cursor::new(input);\n            let mut input_buffer = [0u8; 7];\n            let mut output_buffer = [0u8; 13];\n\n            let mut error_string = [0u8; 1024];\n\n            loop {\n                let amount_read = file_read.read(&mut input_buffer).unwrap();\n\n                let mut result_size = 0;\n                let result = decompress_image(\n                    context,\n                    input_buffer.as_ptr(),\n                    amount_read as u64,\n                    amount_read == 0,\n                    output_buffer.as_mut_ptr(),\n                    output_buffer.len() as u64,\n                    &mut result_size,\n                    error_string.as_mut_ptr(),\n                    error_string.len() as u64,\n                );\n\n                output.extend_from_slice(&output_buffer[..result_size as usize]);\n\n                match result {\n                    -1 => {\n                        // need more data\n                    }\n                    0 => {\n                        break;\n                    }\n                    _ => {\n                        panic!(\"unexpected error {0}\", result);\n                    }\n                }\n            }\n            free_decompression_context(context);\n        }\n\n        let test_result = read_file(\"slrcity\", \".jpg\");\n        assert_eq!(test_result.len(), output.len());\n        assert!(test_result[..] == output[..]);\n    }\n\n    #[rstest]\n    fn verify_extern_interface_rejects_compression_of_unsupported_jpegs(\n        #[values(\n        (\"zeros_in_dqt_tables\", ExitCode::UnsupportedJpegWithZeroIdct0), \n        (\"nonoptimalprogressive\", ExitCode::UnsupportedJpeg))]\n        file: (&str, ExitCode),\n    ) {\n        let input = read_file(file.0, \".jpg\");\n\n        let mut compressed = Vec::new();\n        compressed.resize(input.len() + 10000, 0);\n        let mut result_size: u64 = 0;\n        let mut cpu_usage: u64 = 0;\n\n        unsafe {\n            let retval = WrapperCompressImage2(\n                input[..].as_ptr(),\n                input.len() as u64,\n                compressed[..].as_mut_ptr(),\n                compressed.len() as u64,\n                8,\n                (&mut result_size) as *mut u64,\n                (&mut cpu_usage) as *mut u64,\n                0,\n            );\n\n            assert_eq!(retval, file.1.as_integer_error_code());\n        }\n    }\n\n    /// While we prevent compression of images with zeros in DQT tables, since it may lead to divide-by-zero, we support decompression of\n    /// previously compressed images with this characteristics for back-compat.\n    #[rstest]\n    fn verify_extern_interface_supports_decompression_with_zeros_in_dqt_tables(\n        #[values(\"zeros_in_dqt_tables\")] file: &str,\n    ) {\n        let compressed = read_file(file, \".lep\");\n        let original = read_file(file, \".jpg\");\n\n        let mut decompressed = Vec::new();\n        decompressed.resize(original.len() + 10000, 0);\n\n        let mut decompressed_size: u64 = 0;\n        let mut cpu_usage: u64 = 0;\n\n        unsafe {\n            let retval = WrapperDecompressImage3(\n                compressed[..].as_ptr(),\n                compressed.len() as u64,\n                decompressed[..].as_mut_ptr(),\n                decompressed.len() as u64,\n                8,\n                (&mut decompressed_size) as *mut u64,\n                (&mut cpu_usage) as *mut u64,\n                0,\n            );\n\n            assert_eq!(retval, 0);\n        }\n\n        assert_eq!(original.len() as u64, decompressed_size);\n        assert_eq!(original[..], decompressed[..(decompressed_size as usize)]);\n    }\n\n    /// Verifies that the decode will accept existing Lepton files and generate\n    /// exactly the same jpeg from them when called by an external interface\n    /// with use_16bit_dc_estimate=true for C++ backward compatibility.\n    /// Used to detect unexpected divergences in coding format.\n    #[rstest]\n    fn verify_decode_external_interface_with_use_16bit_dc_estimate(\n        #[values(\n        \"mathoverflow_16\",\n        \"android\",\n        \"androidcrop\",\n        \"androidcropoptions\",\n        \"androidprogressive\",\n        \"androidprogressive_garbage\",\n        \"androidtrail\",\n        \"colorswap\",\n        \"gray2sf\",\n        \"grayscale\",\n        \"hq\",\n        \"iphone\",\n        \"iphonecity\",\n        \"iphonecity_with_16KGarbage\",\n        \"iphonecity_with_1MGarbage\",\n        \"iphonecrop\",\n        \"iphonecrop2\",\n        \"iphoneprogressive\",\n        \"iphoneprogressive2\",\n        \"progressive_late_dht\", // image has huffman tables that come very late which causes a verification failure \n        \"out_of_order_dqt\",     // image with quanatization table dqt that comes after image definition SOF\n        \"narrowrst\",\n        \"nofsync\",\n        \"slrcity\",\n        \"slrhills\",\n        \"slrindoor\",\n        \"tiny\",\n        \"trailingrst\",\n        \"trailingrst2\",\n        \"trunc\",\n        \"eof_and_trailingrst\",    // the lepton format has a wrongly set unexpected eof and trailing rst\n        \"eof_and_trailinghdrdata\" // the lepton format has a wrongly set unexpected eof and trailing header data\n    )]\n        file: &str,\n    ) {\n        println!(\"decoding {0:?}\", file);\n\n        let compressed = read_file(file, \".lep\");\n        let jpg_file_name = match file {\n            \"mathoverflow_16\" => \"mathoverflow\",\n            _ => file,\n        };\n        let input = read_file(jpg_file_name, \".jpg\");\n\n        let mut original = Vec::new();\n        original.resize(input.len() + 10000, 0);\n\n        let mut original_size: u64 = 0;\n        let mut cpu_usage: u64 = 0;\n\n        unsafe {\n            let retval = WrapperDecompressImage3(\n                compressed[..].as_ptr(),\n                compressed.len() as u64,\n                original[..].as_mut_ptr(),\n                original.len() as u64,\n                8,\n                (&mut original_size) as *mut u64,\n                (&mut cpu_usage) as *mut u64,\n                DECOMPRESS_USE_16BIT_DC_ESTIMATE,\n            );\n\n            assert_eq!(retval, 0);\n        }\n        assert_eq!(input.len() as u64, original_size);\n        assert_eq!(input[..], original[..(original_size as usize)]);\n    }\n\n    #[test]\n    fn verify_extern_16bit_math_retry() {\n        // verify retry logic for 16 bit math encoded image\n        let compressed = read_file(\"mathoverflow_16\", \".lep\");\n\n        let input = read_file(\"mathoverflow\", \".jpg\");\n\n        let mut original = Vec::new();\n        original.resize(input.len() + 10000, 0);\n\n        let mut original_size: u64 = 0;\n        let mut cpu_usage: u64 = 0;\n\n        unsafe {\n            let retval = WrapperDecompressImage3(\n                compressed[..].as_ptr(),\n                compressed.len() as u64,\n                original[..].as_mut_ptr(),\n                original.len() as u64,\n                8,\n                (&mut original_size) as *mut u64,\n                (&mut cpu_usage) as *mut u64,\n                0,\n            );\n\n            assert_eq!(retval, 0);\n        }\n        assert_eq!(input.len() as u64, original_size);\n        assert_eq!(input[..], original[..(original_size as usize)]);\n    }\n}\n"
  },
  {
    "path": "fuzz/.cargo/config.toml",
    "content": "[target.x86_64-unknown-linux-gnu]\nrustflags = [\"-Ctarget_cpu=native\"]"
  },
  {
    "path": "fuzz/.gitignore",
    "content": "target\ncorpus\nartifacts\ncoverage\n*.log"
  },
  {
    "path": "fuzz/Cargo.toml",
    "content": "[package]\nname = \"lepton_jpeg-fuzz\"\nversion = \"0.0.0\"\npublish = false\nedition = \"2024\"\n\n[package.metadata]\ncargo-fuzz = true\n\n[dependencies]\nlibfuzzer-sys = \"0.4\"\n\n[dependencies.lepton_jpeg]\npath = \"../lib\"\n\n# Prevent this from interfering with workspaces\n[workspace]\nmembers = [\".\"]\n\n[profile.release]\ndebug = 1\n\n[[bin]]\nname = \"fuzz_target_1\"\npath = \"fuzz_targets/fuzz_target_1.rs\"\ntest = false\ndoc = false\n"
  },
  {
    "path": "fuzz/fuzz_targets/fuzz_target_1.rs",
    "content": "#![no_main]\n\nuse std::io::Cursor;\n\nuse lepton_jpeg::{decode_lepton, encode_lepton, EnabledFeatures, DEFAULT_THREAD_POOL};\n\nuse libfuzzer_sys::fuzz_target;\n\nfuzz_target!(|data: &[u8]| {\n    let r;\n\n    let mut output = Vec::new();\n\n    let use_16bit = match data.len() % 2 { 0 => false, _ => true };\n    let accept_invalid_dht = match (data.len() / 2) % 2 { 0 => false, _ => true };\n\n    // keep the jpeg dimensions small otherwise the fuzzer gets really slow\n    let features = EnabledFeatures {\n        progressive: true,\n        reject_dqts_with_zeros: true,\n        max_jpeg_height: 1024,\n        max_jpeg_width: 1024,\n        use_16bit_dc_estimate: use_16bit,\n        use_16bit_adv_predict: use_16bit,\n        accept_invalid_dht: accept_invalid_dht,\n        .. EnabledFeatures::compat_lepton_vector_write()\n    };\n\n    {\n        let mut writer = Cursor::new(&mut output);\n\n        r = encode_lepton(&mut Cursor::new(&data), &mut writer, &features, &DEFAULT_THREAD_POOL);\n    }\n\n    let mut original = Vec::new();\n\n    match r {\n        Ok(_) => {\n            let _ = decode_lepton(&mut Cursor::new(&output), &mut original, &features, &DEFAULT_THREAD_POOL);\n        }\n        Err(_) => {}\n    }\n});\n"
  },
  {
    "path": "fuzz/rust-toolchain.toml",
    "content": "[toolchain]\nchannel = \"nightly\""
  },
  {
    "path": "images/iphonecity_with_16KGarbage.jpgoutput",
    "content": ""
  },
  {
    "path": "images/t.lepoutput",
    "content": ""
  },
  {
    "path": "lib/Cargo.toml",
    "content": "[package]\nname = \"lepton_jpeg\"\nversion.workspace = true\nedition = \"2024\"\nauthors = [\"Kristof Roomp <kristofr@microsoft.com>\"]\n\n# requires scoped threads, IsTerminal, let chains\nrust-version = \"1.89\"\ndescription = \"Rust port of the Lepton lossless JPEG compression library\"\nreadme = \"../README.md\"\nrepository = \"https://github.com/microsoft/lepton_jpeg_rust\"\nlicense = \"Apache-2.0\"\n\ncategories = [\"multimedia::images\", \"multimedia::encoding\", \"compression\"]\n\n# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html\n\n[features]\ndefault = []\ncompression_stats = []\ndetailed_tracing = []\n\n# used to expose internal functions for micro benchmarking\nmicro_benchmark = []\n\n[dependencies]\nbytemuck = \"1\"\nbyteorder = \"1.4\"\nflate2 = \"1.0\"\ndefault-boxed = \"0.2\"\nwide = \"0.8\"\nlog = \"0.4\"\ngit-version = \"0.3\"\n\n[target.'cfg(target_os = \"windows\")'.dependencies]\ncpu-time = \"1.0\"\nthread-priority = \"1.0\"\n\n[target.'cfg(target_os = \"linux\")'.dependencies]\nthread-priority = \"1.0\"\n\n[dev-dependencies]\nrand = \"0.8\"\nrand_chacha = \"0.3\"\nsiphasher = \"1\"\n\n[lib]\ncrate-type = [\"lib\"]\n"
  },
  {
    "path": "lib/src/consts.rs",
    "content": "/*---------------------------------------------------------------------------------------------\n *  Copyright (c) Microsoft Corporation. All rights reserved.\n *  Licensed under the Apache License, Version 2.0. See LICENSE.txt in the project root for license information.\n *  This software incorporates material from third parties. See NOTICE.txt for details.\n *--------------------------------------------------------------------------------------------*/\n\nuse crate::jpeg::jpeg_code;\n\n#[derive(PartialEq, Debug)]\npub enum JpegDecodeStatus {\n    DecodeInProgress,\n    RestartIntervalExpired,\n    ScanCompleted,\n}\n\n#[derive(PartialEq, Debug, Copy, Clone)]\npub enum JpegType {\n    Unknown,\n    Sequential,\n    Progressive,\n}\n\npub const COLOR_CHANNEL_NUM_BLOCK_TYPES: usize = 3;\n\npub const RASTER_TO_ZIGZAG: [u8; 64] = [\n    0, 1, 5, 6, 14, 15, 27, 28, 2, 4, 7, 13, 16, 26, 29, 42, 3, 8, 12, 17, 25, 30, 41, 43, 9, 11,\n    18, 24, 31, 40, 44, 53, 10, 19, 23, 32, 39, 45, 52, 54, 20, 22, 33, 38, 46, 51, 55, 60, 21, 34,\n    37, 47, 50, 56, 59, 61, 35, 36, 48, 49, 57, 58, 62, 63,\n];\n\n// pub const ZIGZAG_TO_RASTER: [u8; 64] = [\n//     0, 1, 8, 16, 9, 2, 3, 10, 17, 24, 32, 25, 18, 11, 4, 5, 12, 19, 26, 33, 40, 48, 41, 34, 27, 20,\n//     13, 6, 7, 14, 21, 28, 35, 42, 49, 56, 57, 50, 43, 36, 29, 22, 15, 23, 30, 37, 44, 51, 58, 59,\n//     52, 45, 38, 31, 39, 46, 53, 60, 61, 54, 47, 55, 62, 63,\n// ];\n\npub const ZIGZAG_TO_TRANSPOSED: [u8; 64] = [\n    0, 8, 1, 2, 9, 16, 24, 17, 10, 3, 4, 11, 18, 25, 32, 40, 33, 26, 19, 12, 5, 6, 13, 20, 27, 34,\n    41, 48, 56, 49, 42, 35, 28, 21, 14, 7, 15, 22, 29, 36, 43, 50, 57, 58, 51, 44, 37, 30, 23, 31,\n    38, 45, 52, 59, 60, 53, 46, 39, 47, 54, 61, 62, 55, 63,\n];\n\n// pub const UNZIGZAG_49: [u8; 49] = [\n//     9, 10, 17, 25, 18, 11, 12, 19, 26, 33, 41, 34, 27, 20, 13, 14, 21, 28, 35, 42, 49, 57, 50, 43,\n//     36, 29, 22, 15, 23, 30, 37, 44, 51, 58, 59, 52, 45, 38, 31, 39, 46, 53, 60, 61, 54, 47, 55, 62,\n//     63,\n// ];\n\npub const UNZIGZAG_49_TR: [u8; 49] = [\n    9, 17, 10, 11, 18, 25, 33, 26, 19, 12, 13, 20, 27, 34, 41, 49, 42, 35, 28, 21, 14, 15, 22, 29,\n    36, 43, 50, 57, 58, 51, 44, 37, 30, 23, 31, 38, 45, 52, 59, 60, 53, 46, 39, 47, 54, 61, 62, 55,\n    63,\n];\n\n// precalculated int base values for 8x8 IDCT scaled by 8192\n// DC coef is zeroed intentionally\npub const ICOS_BASED_8192_SCALED: [i32; 8] = [0, 11363, 10703, 9633, 8192, 6436, 4433, 2260];\n\npub const ICOS_BASED_8192_SCALED_PM: [i32; 8] =\n    [8192, -11363, 10703, -9633, 8192, -6436, 4433, -2260];\n\npub const FREQ_MAX: [u16; 14] = [\n    931, 985, 968, 1020, 968, 1020, 1020, 932, 985, 967, 1020, 969, 1020, 1020,\n];\n\n// used to get prediction branches basing on nonzero-number predictor `num_non_zeros_context`\npub const NON_ZERO_TO_BIN: [u8; 26] = [\n    0, 1, 2, 3, 4, 4, 5, 5, 5, 6, 6, 6, 6, 7, 7, 7, 7, 7, 7, 7, 7, 8, 8, 8, 8, 8,\n];\n\n// used to get prediction branches basing on current `num_non_zeros_left_7x7`, 0th element is not used\npub const NON_ZERO_TO_BIN_7X7: [u8; 50] = [\n    0, 0, 1, 2, 3, 3, 4, 4, 4, 5, 5, 5, 5, 6, 6, 6, 6, 6, 6, 6, 6, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7,\n    8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8,\n];\n\npub const RESIDUAL_NOISE_FLOOR: usize = 7;\n\npub const LEPTON_VERSION: u8 = 1; // Lepton version, same as used by Lepton C++ since we support the same format\n\npub const SMALL_FILE_BYTES_PER_ENCDOING_THREAD: usize = 125000;\npub const MAX_THREADS_SUPPORTED_BY_LEPTON_FORMAT: usize = 16; // Number of threads minus 1 should fit in 4 bits\n\n//pub const SingleFFByte : [u8;1] = [ 0xFF ];\npub const EOI: [u8; 2] = [0xFF, jpeg_code::EOI]; // EOI segment\npub const SOI: [u8; 2] = [0xFF, jpeg_code::SOI]; // SOI segment\npub const LEPTON_FILE_HEADER: [u8; 2] = [0xcf, 0x84]; // the tau symbol for a tau lepton in utf-8\npub const LEPTON_HEADER_BASELINE_JPEG_TYPE: [u8; 1] = [b'Z'];\npub const LEPTON_HEADER_PROGRESSIVE_JPEG_TYPE: [u8; 1] = [b'X'];\npub const LEPTON_HEADER_MARKER: [u8; 3] = *b\"HDR\";\npub const LEPTON_HEADER_PAD_MARKER: [u8; 3] = *b\"P0D\";\npub const LEPTON_HEADER_JPG_RESTARTS_MARKER: [u8; 3] = *b\"CRS\";\npub const LEPTON_HEADER_JPG_RESTART_ERRORS_MARKER: [u8; 3] = *b\"FRS\";\npub const LEPTON_HEADER_LUMA_SPLIT_MARKER: [u8; 2] = *b\"HH\";\npub const LEPTON_HEADER_EARLY_EOF_MARKER: [u8; 3] = *b\"EEE\";\npub const LEPTON_HEADER_PREFIX_GARBAGE_MARKER: [u8; 3] = *b\"PGR\";\npub const LEPTON_HEADER_GARBAGE_MARKER: [u8; 3] = *b\"GRB\";\npub const LEPTON_HEADER_COMPLETION_MARKER: [u8; 3] = *b\"CMP\";\n//pub const ChunkedLeptonHeaderSizeMarker : [u8;3] = *b\"SIZ\" ;\n//pub const ChunkedLeptonHeaderJpgHeaderDataRangeMarker : [u8;3] = *b\"JHR\";\n"
  },
  {
    "path": "lib/src/enabled_features.rs",
    "content": "/// Features that are enabled in the encoder. Turn off for potential backward compat issues.\n#[derive(Debug, Clone)]\npub struct EnabledFeatures {\n    /// enables/disables reading of progressive images\n    pub progressive: bool,\n\n    /// reject/accept images with DQTs with zeros (may cause divide-by-zero)\n    pub reject_dqts_with_zeros: bool,\n\n    /// maximum jpeg width\n    pub max_jpeg_width: u32,\n\n    /// maximum jpeg height\n    pub max_jpeg_height: u32,\n\n    /// Sadly C++ version has a bug where it uses 16 bit math in the SIMD path and 32 bit math in the scalar path\n    pub use_16bit_dc_estimate: bool,\n\n    /// Sadly C++ version has a bug where it uses 16 bit math in the SIMD path and 32 bit math in the scalar path\n    pub use_16bit_adv_predict: bool,\n\n    /// Accept JPEG files that have invalid DHT tables\n    pub accept_invalid_dht: bool,\n\n    /// number of partitions used for encoding\n    pub max_partitions: u32,\n\n    /// maximum number of threads to use for encoding/decoding\n    pub max_processor_threads: u32,\n\n    /// maximum size of a jpeg file\n    pub max_jpeg_file_size: u32,\n\n    /// stop reading at the end of the valid JPEG file. This is useful if\n    /// the stream contains other data after the EOI marker.\n    ///\n    /// This also disallows handling truncated JPEG files since by definition\n    /// they don't have an EOI marker, instead you will get a ShortRead error.\n    pub stop_reading_at_eoi: bool,\n}\n\nimpl EnabledFeatures {\n    /// parameters that allow everything for encoding that is compatible with c++ lepton compiled with SIMD\n    #[allow(dead_code)]\n    pub fn compat_lepton_vector_write() -> Self {\n        Self {\n            progressive: true,\n            reject_dqts_with_zeros: true,\n            max_jpeg_height: 16386,\n            max_jpeg_width: 16386,\n            use_16bit_dc_estimate: true,\n            use_16bit_adv_predict: true,\n            accept_invalid_dht: false,\n            max_partitions: 8,\n            max_processor_threads: 8,\n            max_jpeg_file_size: 128 * 1024 * 1024,\n            stop_reading_at_eoi: false,\n        }\n    }\n\n    /// parameters that allow everything for decoding c++ lepton images encoded\n    /// with the scalar compile options\n    #[allow(dead_code)]\n    pub fn compat_lepton_scalar_read() -> Self {\n        Self {\n            progressive: true,\n            reject_dqts_with_zeros: false,\n            max_jpeg_height: u32::MAX,\n            max_jpeg_width: u32::MAX,\n            use_16bit_dc_estimate: false,\n            use_16bit_adv_predict: false,\n            accept_invalid_dht: true,\n            max_partitions: 8,\n            max_processor_threads: 8,\n            max_jpeg_file_size: 128 * 1024 * 1024,\n            stop_reading_at_eoi: false,\n        }\n    }\n\n    /// parameters that allow everything for decoding c++ lepton images encoded\n    /// with the vector (SSE2/AVX2) compile options\n    #[allow(dead_code)]\n    pub fn compat_lepton_vector_read() -> Self {\n        Self {\n            progressive: true,\n            reject_dqts_with_zeros: false,\n            max_jpeg_height: u32::MAX,\n            max_jpeg_width: u32::MAX,\n            use_16bit_dc_estimate: true,\n            use_16bit_adv_predict: true,\n            accept_invalid_dht: true,\n            max_partitions: 8,\n            max_processor_threads: 8,\n            max_jpeg_file_size: 128 * 1024 * 1024,\n            stop_reading_at_eoi: false,\n        }\n    }\n}\n"
  },
  {
    "path": "lib/src/helpers.rs",
    "content": "/*---------------------------------------------------------------------------------------------\n *  Copyright (c) Microsoft Corporation. All rights reserved.\n *  Licensed under the Apache License, Version 2.0. See LICENSE.txt in the project root for license information.\n *  This software incorporates material from third parties. See NOTICE.txt for details.\n *--------------------------------------------------------------------------------------------*/\n\nuse std::panic::{AssertUnwindSafe, catch_unwind};\n\nuse crate::lepton_error::{ExitCode, LeptonError};\n\n/// Helper function to catch panics and convert them into the appropriate LeptonError\npub fn catch_unwind_result<R>(\n    f: impl FnOnce() -> Result<R, LeptonError>,\n) -> Result<R, LeptonError> {\n    match catch_unwind(AssertUnwindSafe(f)) {\n        Ok(r) => r.map_err(|e| e.into()),\n        Err(err) => {\n            if let Some(message) = err.downcast_ref::<&str>() {\n                Err(LeptonError::new(ExitCode::AssertionFailure, *message))\n            } else if let Some(message) = err.downcast_ref::<String>() {\n                Err(LeptonError::new(ExitCode::AssertionFailure, message))\n            } else {\n                Err(LeptonError::new(\n                    ExitCode::AssertionFailure,\n                    \"unknown panic\",\n                ))\n            }\n        }\n    }\n}\n\n#[inline(always)]\npub const fn u16_bit_length(v: u16) -> u8 {\n    return 16 - v.leading_zeros() as u8;\n}\n\n#[inline(always)]\npub const fn u32_bit_length(v: u32) -> u8 {\n    return 32 - v.leading_zeros() as u8;\n}\n\npub fn buffer_prefix_matches_marker<const BS: usize, const MS: usize>(\n    buffer: [u8; BS],\n    marker: [u8; MS],\n) -> bool {\n    // Helper method, skipping checks of parameters nulls/lengths\n    for i in 0..marker.len() {\n        if buffer[i] != marker[i] {\n            return false;\n        }\n    }\n\n    return true;\n}\n\n/// returns true if the 64 bit value contains an 0xff byte.\n/// Uses fancy bit manipulation to avoid branches.\n#[inline(always)]\npub fn has_ff(v: u64) -> bool {\n    (v & 0x8080808080808080 & !v.wrapping_add(0x0101010101010101)) != 0\n}\n\n#[inline(always)]\npub const fn devli(s: u8, value: u16) -> i16 {\n    let shifted = 1 << s;\n\n    if value & (shifted >> 1) != 0 {\n        value as i16\n    } else {\n        value.wrapping_add(2).wrapping_add(!shifted) as i16\n    }\n}\n\n/// check to make sure the behavior hasn't changed even with the optimization\n#[test]\nfn devli_test() {\n    for s in 0u8..15 {\n        for value in 0..(1 << s) {\n            assert_eq!(\n                devli(s, value),\n                if s == 0 {\n                    value as i16\n                } else if value < (1 << (s as u16 - 1)) {\n                    value as i16 + (-1 << s as i16) + 1\n                } else {\n                    value as i16\n                }\n            );\n        }\n    }\n}\n\n#[inline(always)]\npub const fn b_short(v1: u8, v2: u8) -> u16 {\n    ((v1 as u16) << 8) + v2 as u16\n}\n\n#[inline(always)]\npub const fn rbits(c: u8, n: usize) -> u8 {\n    return c & (0xFF >> (8 - n));\n}\n\n#[inline(always)]\npub const fn lbits(c: u8, n: usize) -> u8 {\n    return c >> (8 - n);\n}\n\n#[inline(always)]\npub const fn bitn(c: u16, n: u16) -> u8 {\n    return ((c >> n) & 0x1) as u8;\n}\n\n#[inline(always)]\npub fn calc_sign_index(val: i16) -> usize {\n    if val == 0 {\n        0\n    } else {\n        if val > 0 { 1 } else { 2 }\n    }\n}\n\n/// This checks to see if a vector can fit additional elements without growing,\n/// but does it in such a way that the optimizer understands that a subsequent\n/// push or extend will not need to grow the vector.\n#[inline(always)]\npub fn needs_to_grow<T>(v: &Vec<T>, additional: usize) -> bool {\n    additional > v.capacity().wrapping_sub(v.len())\n}\n\n#[cfg(test)]\npub fn get_rand_from_seed(seed: [u8; 32]) -> rand_chacha::ChaCha12Rng {\n    use rand_chacha::ChaCha12Rng;\n    use rand_chacha::rand_core::SeedableRng;\n\n    ChaCha12Rng::from_seed(seed)\n}\n\n/// reads a file from the images directory for testing or benchmarking purposes\n#[cfg(any(test, feature = \"micro_benchmark\"))]\npub fn read_file(filename: &str, ext: &str) -> Vec<u8> {\n    use std::io::Read;\n\n    let filename = std::path::Path::new(env!(\"WORKSPACE_ROOT\"))\n        .join(\"images\")\n        .join(filename.to_owned() + ext);\n    let mut f = std::fs::File::open(filename).unwrap();\n\n    let mut content = Vec::new();\n    f.read_to_end(&mut content).unwrap();\n\n    content\n}\n\n/*\nbetter way to update aritmetic encoding without using special division\n\nconst fn k16bit_length(v : u32) -> u32\n{\n    const LEN_TABLE256 : [i8;256] =\n    [\n            0, 1, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 4,\n            5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,\n            6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6,\n            6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6,\n            7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7,\n            7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7,\n            7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7,\n            7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7,\n            8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8,\n            8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8,\n            8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8,\n            8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8,\n            8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8,\n            8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8,\n            8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8,\n            8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8,\n    ];\n\n    return if (v & 0xff00) != 0 { 8 + LEN_TABLE256[(v >> 8) as usize] } else { LEN_TABLE256[v as usize] } as u32;\n}\n\nconst LOG_MAX_NUMERATOR : i32= 18;\n\nconst fn calc_divisors() -> [u32;1026]\n{\n    let mut intermed = [0u32;1026];\n\n    let mut d : u32  = 1;\n\n    while d < 1026\n    {\n        intermed[d as usize] = ((((1 << k16bit_length(d)) - d) << LOG_MAX_NUMERATOR) / d) + 1;\n        d += 1;\n    }\n\n    return intermed;\n}\n\nconst DIVISORS : [u32;1026] = calc_divisors();\n\n#[inline(always)]\npub fn fast_divide18bit_by_10bit(num : u32, denom : u16) -> u32\n{\n    //debug_assert_eq!(LOG2_LENGTHS[denom as usize], (16 - denom.leading_zeros() - 1) as u8, \"log2{0}\", denom);\n\n    let tmp = ((DIVISORS[denom as usize] as u64 * num as u64) >> LOG_MAX_NUMERATOR) as u32;\n    let r = (tmp + ((num - tmp) >> 1)) >> (16 - denom.leading_zeros() - 1);\n\n    debug_assert_eq!(r, num/(denom as u32));\n    return r;\n}\n\n*/\n"
  },
  {
    "path": "lib/src/jpeg/bit_reader.rs",
    "content": "/*---------------------------------------------------------------------------------------------\n *  Copyright (c) Microsoft Corporation. All rights reserved.\n *  Licensed under the Apache License, Version 2.0. See LICENSE.txt in the project root for license information.\n *  This software incorporates material from third parties. See NOTICE.txt for details.\n *--------------------------------------------------------------------------------------------*/\n\nuse std::io::BufRead;\n\nuse super::jpeg_code;\nuse crate::helpers::has_ff;\nuse crate::lepton_error::{ExitCode, err_exit_code};\nuse crate::{LeptonError, StreamPosition};\n\n// Implemenation of bit reader on top of JPEG data stream as read by a reader\npub struct BitReader<R> {\n    inner: R,\n    bits: u64,\n    bits_left: u32,\n    cpos: u32,\n    eof: bool,\n    truncated_ff: bool,\n    read_ahead_bytes: u32,\n}\n\nimpl<R: BufRead + StreamPosition> BitReader<R> {\n    /// Returns the current position in the stream, which corresponds the byte that has\n    /// unread bits in it.\n    ///\n    /// If the last byte was a 0xff, then the position is the byte before\n    /// the 0xff.\n    pub fn stream_position(&mut self) -> u64 {\n        self.undo_read_ahead();\n\n        let pos = self.inner.position();\n\n        if self.bits_left > 0 && !self.eof {\n            if self.bits as u8 == 0xff && !self.truncated_ff {\n                return pos - 2;\n            } else {\n                return pos - 1;\n            }\n        } else {\n            return pos;\n        }\n    }\n\n    pub fn new(inner: R) -> Self {\n        BitReader {\n            inner: inner,\n            bits: 0,\n            bits_left: 0,\n            cpos: 0,\n            eof: false,\n            truncated_ff: false,\n            read_ahead_bytes: 0,\n        }\n    }\n}\n\nimpl<R: BufRead> BitReader<R> {\n    #[inline(always)]\n    pub fn read(&mut self, bits_to_read: u32) -> std::io::Result<u16> {\n        if bits_to_read == 0 {\n            return Ok(0);\n        }\n\n        if self.bits_left < bits_to_read {\n            self.fill_register(bits_to_read)?;\n        }\n\n        let retval =\n            (self.bits >> (self.bits_left - bits_to_read) & ((1 << bits_to_read) - 1)) as u16;\n        self.bits_left -= bits_to_read;\n        return Ok(retval);\n    }\n\n    #[inline(always)]\n    pub fn peek(&self) -> (u8, u32) {\n        (\n            ((self.bits.wrapping_shl(64 - self.bits_left)) >> 56) as u8,\n            self.bits_left,\n        )\n    }\n\n    #[inline(always)]\n    pub fn advance(&mut self, bits: u32) {\n        self.bits_left -= bits;\n    }\n\n    #[inline(always)]\n    pub fn fill_register(&mut self, bits_to_read: u32) -> Result<(), std::io::Error> {\n        // first consume the read_ahead bytes that we have now consumed\n        // (otherwise we wouldn't have been called)\n        self.inner.consume(self.read_ahead_bytes as usize);\n\n        let fb = self.inner.fill_buf()?;\n\n        // if we have 8 bytes and there is no 0xff in them, then we can just read the bits directly as big endian\n        let mut v;\n        if fb.len() < 8 || {\n            v = u64::from_le_bytes(fb[..8].try_into().unwrap());\n            has_ff(v)\n        } {\n            self.read_ahead_bytes = 0;\n            return self.fill_register_slow(bits_to_read);\n        }\n\n        v = v.to_be();\n\n        // only fill 63 bits not 64 to avoid having to special case\n        // of self.bits << 64 which is a nop\n        let bytes_to_read = (63 - self.bits_left) / 8;\n\n        self.bits = self.bits << (bytes_to_read * 8) | v >> (64 - bytes_to_read * 8);\n        self.bits_left += bytes_to_read * 8;\n        self.read_ahead_bytes = (self.bits_left - bits_to_read) / 8;\n\n        self.inner\n            .consume((bytes_to_read - self.read_ahead_bytes) as usize);\n\n        return Ok(());\n    }\n\n    #[cold]\n    fn fill_register_slow(&mut self, bits_to_read: u32) -> Result<(), std::io::Error> {\n        loop {\n            let fb = self.inner.fill_buf()?;\n            if let &[b, ..] = fb {\n                self.inner.consume(1);\n\n                // 0xff is an escape code, if the next by is zero, then it is just a normal 0\n                // otherwise it is a reset code, which should also be skipped\n                if b == 0xff {\n                    let mut buffer = [0u8];\n\n                    if self.inner.read(&mut buffer)? == 0 {\n                        // Handle case of truncation in the middle of an escape: Since we assume that everything passed the end\n                        // is a 0, if the file ends with 0xFF, then we have to assume that this was\n                        // an escaped 0xff. Don't mark as eof yet, since there are still the 8 bits to read.\n                        self.bits = (self.bits << 8) | 0xff;\n                        self.bits_left += 8;\n                        self.truncated_ff = true;\n\n                        // continue since we still might need to read more 0 bits\n                    } else if buffer[0] == 0 {\n                        // this was an escaped FF\n                        self.bits = (self.bits << 8) | 0xff;\n                        self.bits_left += 8;\n                    } else {\n                        // this was not an escaped 0xff which is the only thing we accept at this part of the decoding.\n                        //\n                        // verify_reset_code should have gotten called in all instances where there should be a reset code,\n                        // or at the end of the file we should have stopped decoding before we hit the end of file marker.\n                        //\n                        // Since we have no way of encoding these cases in our bitstream, we exit.\n                        return Err(LeptonError::new(\n                            ExitCode::InvalidResetCode,\n                            format!(\n                                \"invalid reset {0:x} {1:x} code found in stream\",\n                                0xff, buffer[0]\n                            ),\n                        )\n                        .into());\n                    }\n                } else {\n                    self.bits = (self.bits << 8) | (b as u64);\n                    self.bits_left += 8;\n                }\n            } else {\n                // in case of a truncated file, we treat the rest of the file as zeros, but the\n                // bits that were ok still get returned so that we get the partial last byte right\n                // the caller periodically checks for EOF to see if it should stop encoding\n                self.eof = true;\n                self.bits_left += 8;\n                self.bits <<= 8;\n\n                // continue since we still might need to read more 0 bits\n            }\n\n            if self.bits_left >= bits_to_read {\n                break;\n            }\n        }\n        Ok(())\n    }\n\n    pub fn is_eof(&mut self) -> bool {\n        return self.eof;\n    }\n\n    /// used to verify whether this image is using 1s or 0s as fill bits.\n    /// Returns whether the fill bit was 1 or so or unknown (None)\n    pub fn read_and_verify_fill_bits(\n        &mut self,\n        pad_bit: &mut Option<u8>,\n    ) -> Result<(), LeptonError> {\n        self.undo_read_ahead();\n\n        // if there are bits left, we need to see whether they\n        // are 1s or zeros.\n\n        if (self.bits_left) > 0 && !self.eof {\n            let num_bits_to_read = self.bits_left;\n            let actual = self.read(num_bits_to_read)?;\n            let all_one = (1 << num_bits_to_read) - 1;\n\n            match *pad_bit {\n                None => {\n                    if actual == 0 {\n                        *pad_bit = Some(0);\n                    } else if actual == all_one {\n                        *pad_bit = Some(0xff);\n                    } else {\n                        return err_exit_code(\n                            ExitCode::InvalidPadding,\n                            format!(\n                                \"inconsistent pad bits num_bits={0} pattern={1:b}\",\n                                num_bits_to_read, actual\n                            ),\n                        );\n                    }\n                }\n                Some(x) => {\n                    // if we already saw a padding, then it should match\n                    let expected = u16::from(x) & all_one;\n                    if actual != expected {\n                        return err_exit_code(\n                            ExitCode::InvalidPadding,\n                            format!(\n                                \"padding of {0} bits should be set to 1 actual={1:b} expected={2:b}\",\n                                num_bits_to_read, actual, expected\n                            ),\n                        );\n                    }\n                }\n            }\n        }\n\n        return Ok(());\n    }\n\n    pub fn verify_reset_code(&mut self) -> Result<(), LeptonError> {\n        // we reached the end of a MCU, so we need to find a reset code and the huffman codes start get padded out, but the spec\n        // doesn't specify whether the padding should be 1s or 0s, so we ensure that at least the file is consistant so that we\n        // can recode it again just by remembering the pad bit.\n        self.undo_read_ahead();\n\n        let mut h = [0u8; 2];\n        self.inner.read_exact(&mut h)?;\n        if h[0] != 0xff || h[1] != (jpeg_code::RST0 + (self.cpos as u8 & 7)) {\n            return err_exit_code(\n                ExitCode::InvalidResetCode,\n                format!(\"invalid reset code {0:x} {1:x} found in stream\", h[0], h[1]),\n            );\n        }\n\n        // start from scratch after RST\n        self.cpos += 1;\n        self.bits = 0;\n        self.bits_left = 0;\n\n        Ok(())\n    }\n\n    /// Retrieves the byte containing the next bit to be read in the stream, with only\n    /// the bits that have already been read in it possibly set, and all the rest of the\n    /// bits cleared.\n    ///\n    /// bitsAlreadyRead: the number of bits already read from the current byte\n    /// byteBeingRead: the byte currently being read, with any bits not read from it yet cleared (0'ed)\n    pub fn overhang(&mut self) -> (u8, u8) {\n        self.undo_read_ahead();\n        let bits_already_read = ((64 - self.bits_left) & 7) as u8; // already read bits in the current byte\n\n        let mask = (((1 << bits_already_read) - 1) << (8 - bits_already_read)) as u8;\n\n        return (bits_already_read, (self.bits as u8) & mask);\n    }\n\n    /// \"puts back\" read_ahead bits that were read ahead from the buffer but not consumed.\n    ///\n    /// This avoids special for many of the other non-speed-sensitive operations.\n    ///\n    /// After calling this method, we can be guaranteed that read_ahead_bytes is 0 and that\n    /// the only bits that are left are part of the current byte.\n    pub fn undo_read_ahead(&mut self) {\n        while self.bits_left >= 8 && self.read_ahead_bytes > 0 {\n            self.bits_left -= 8;\n            self.bits >>= 8;\n            self.read_ahead_bytes -= 1;\n        }\n\n        if self.read_ahead_bytes > 0 {\n            self.inner.consume(self.read_ahead_bytes as usize);\n            self.read_ahead_bytes = 0;\n        }\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n    use std::io::Cursor;\n\n    // test reading a simple bit pattern with an escaped 0xff inside it.\n    #[test]\n    fn read_simple() {\n        let arr = [0x12u8, 0x34, 0x45, 0x67, 0x89, 0xff, 00, 0xee];\n\n        let mut b = BitReader::new(Cursor::new(&arr));\n\n        assert_eq!(1, b.read(4).unwrap());\n        assert_eq!((4, 0x10), b.overhang());\n        assert_eq!(0, b.stream_position());\n\n        assert_eq!(2, b.read(4).unwrap());\n        assert_eq!((0, 0), b.overhang()); // byte is aligned should be no overhang\n        assert_eq!(1, b.stream_position());\n\n        assert_eq!(3, b.read(4).unwrap());\n        assert_eq!(4, b.read(4).unwrap());\n        assert_eq!(4, b.read(4).unwrap());\n        assert_eq!(0x56, b.read(8).unwrap()); // 8 bits between 0x45 and 0x67\n        assert_eq!(0x78, b.read(8).unwrap());\n\n        assert_eq!(0x9f, b.read(8).unwrap());\n        assert_eq!((4, 0xf0), b.overhang());\n        assert_eq!(5, b.stream_position()); // should be at the beginning of the escape code\n\n        assert_eq!(0xfe, b.read(8).unwrap());\n        assert_eq!((4, 0xe0), b.overhang());\n        assert_eq!(7, b.stream_position()); // now we are after the escape code\n\n        assert_eq!(0xe, b.read(4).unwrap());\n        assert_eq!((0, 0), b.overhang());\n        assert_eq!(8, b.stream_position()); // now we read everything and should be at the end of the stream\n\n        // read an empty byte passed the end of the stream.. should be zero and trigger EOF\n        assert_eq!(0, b.read(8).unwrap());\n        assert_eq!(true, b.is_eof());\n        assert_eq!(8, b.stream_position()); // still at the same position\n    }\n\n    // what happens when a file has 0xff as the last character (assume that it is an escaped 0xff)\n    #[test]\n    fn read_truncate_ff() {\n        let arr = [0x12u8, 0xff];\n\n        let mut b = BitReader::new(Cursor::new(&arr));\n\n        assert_eq!(0, b.stream_position());\n\n        assert_eq!(0x1, b.read(4).unwrap());\n        assert_eq!(0, b.stream_position());\n\n        assert_eq!(0x2f, b.read(8).unwrap());\n        assert_eq!((4, 0xf0), b.overhang());\n        assert_eq!(1, b.stream_position());\n\n        // 4 bits left, not EOF yet\n        assert_eq!(false, b.is_eof());\n\n        assert_eq!(0xf, b.read(4).unwrap());\n        assert_eq!(false, b.is_eof()); // now we are at the end really\n        assert_eq!(2, b.stream_position());\n\n        assert_eq!(0, b.read(4).unwrap());\n        assert_eq!(true, b.is_eof());\n        assert_eq!(2, b.stream_position());\n    }\n}\n"
  },
  {
    "path": "lib/src/jpeg/bit_writer.rs",
    "content": "/*---------------------------------------------------------------------------------------------\n *  Copyright (c) Microsoft Corporation. All rights reserved.\n *  Licensed under the Apache License, Version 2.0. See LICENSE.txt in the project root for license information.\n *  This software incorporates material from third parties. See NOTICE.txt for details.\n *--------------------------------------------------------------------------------------------*/\n\nuse std::mem;\n\nuse crate::helpers::has_ff;\n\npub struct BitWriter {\n    data_buffer: Vec<u8>,\n    fill_register: u64,\n    current_bit: u32,\n}\n\n// use to write varying sized bits for coding JPEG. Escapes 0xff -> [0xff,0]\nimpl BitWriter {\n    pub fn new(data_buffer: Vec<u8>) -> Self {\n        return BitWriter {\n            current_bit: 64,\n            fill_register: 0,\n            data_buffer,\n        };\n    }\n\n    /// flushes whole bytes from the register into the data buffer\n    fn flush_whole_bytes(&mut self) {\n        while self.current_bit <= 56 {\n            let b = (self.fill_register >> 56) as u8;\n            if b != 0xff {\n                self.data_buffer.push(b);\n            } else {\n                // escape 0xff here to avoid multiple scans of the same data\n                self.data_buffer.extend_from_slice(&[0xff, 0]);\n            }\n\n            self.fill_register <<= 8;\n            self.current_bit += 8;\n        }\n    }\n\n    /// write data\n    pub fn write_byte_unescaped(&mut self, b: u8) {\n        assert!(self.current_bit == 64);\n        self.data_buffer.push(b);\n    }\n\n    #[inline(always)]\n    pub fn write(&mut self, val: u32, new_bits: u32) {\n        /// this is the slow path that is rarely called but generates a lot of code inlined\n        /// so we move it out of the main function to keep the main function small with few branches.\n        ///\n        /// We also call this path when we are about to overflow the buffer to avoid having\n        /// to inline the buffer growing logic, which is also much bigger than a simple insert.\n        #[inline(never)]\n        #[cold]\n        fn write_ff_encoded(data_buffer: &mut Vec<u8>, fill_register: u64) {\n            for i in 0..8 {\n                let b = (fill_register >> (56 - (i * 8))) as u8;\n                if b != 0xff {\n                    data_buffer.push(b);\n                } else {\n                    // escape 0xff here to avoid multiple scans of the same data\n                    data_buffer.extend_from_slice(&[0xff, 0]);\n                }\n            }\n        }\n\n        debug_assert!(\n            val < (1 << new_bits),\n            \"value {0} should fit into the number of {1} bits provided\",\n            val,\n            new_bits\n        );\n\n        // first see if everything fits in the current register\n        if new_bits <= self.current_bit {\n            self.fill_register |= (val as u64).wrapping_shl(self.current_bit - new_bits); // support corner case where new_bits is zero, we don't want to panic\n            self.current_bit = self.current_bit - new_bits;\n        } else {\n            // if not, fill up the register so to the 64 bit boundary we can flush it hopefully without any 0xff bytes\n            let fill = self.fill_register | (val as u64).wrapping_shr(new_bits - self.current_bit);\n\n            let leftover_new_bits = new_bits - self.current_bit;\n            let leftover_val = val & (1 << leftover_new_bits) - 1;\n\n            // flush bytes slowly if we have any 0xff bytes or if we are about to overflow the buffer\n            // (overflow check matches implementation in RawVec so that the optimizer can remove the buffer growing code)\n            if has_ff(fill)\n                || self\n                    .data_buffer\n                    .capacity()\n                    .wrapping_sub(self.data_buffer.len())\n                    < 8\n            {\n                write_ff_encoded(&mut self.data_buffer, fill);\n            } else {\n                self.data_buffer.extend_from_slice(&fill.to_be_bytes());\n            }\n\n            self.fill_register = (leftover_val as u64).wrapping_shl(64 - leftover_new_bits); // support corner case where new_bits is zero, we don't want to panic\n            self.current_bit = 64 - leftover_new_bits;\n        }\n    }\n\n    pub fn pad(&mut self, fillbit: u8) {\n        let mut offset = 1;\n        while (self.current_bit & 7) != 0 {\n            self.write(if (fillbit & offset) != 0 { 1 } else { 0 }, 1);\n            offset <<= 1;\n        }\n\n        self.flush_whole_bytes();\n\n        debug_assert!(\n            self.current_bit == 64,\n            \"there should be no remainder after padding\"\n        );\n    }\n\n    // flushes the data buffer while escaping all 0xff characters\n    pub fn detach_buffer(&mut self) -> Vec<u8> {\n        // flush any remaining whole bytes\n        self.flush_whole_bytes();\n\n        mem::take(&mut self.data_buffer)\n    }\n\n    pub fn ensure_space(&mut self, amount: usize) {\n        if self.data_buffer.capacity() < amount {\n            let len = self.data_buffer.len();\n            self.data_buffer.reserve(amount - len);\n        }\n    }\n\n    pub fn reset_from_overhang_byte_and_num_bits(&mut self, overhang_byte: u8, num_bits: u32) {\n        self.data_buffer.clear();\n\n        self.fill_register = 0;\n        self.fill_register = overhang_byte as u64;\n        self.fill_register <<= 56;\n        self.current_bit = 64 - num_bits;\n    }\n\n    pub fn has_no_remainder(&self) -> bool {\n        return self.current_bit == 64;\n    }\n\n    pub fn amount_buffered(&self) -> usize {\n        self.data_buffer.len()\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n\n    use std::io::Cursor;\n\n    use crate::helpers::u32_bit_length;\n    use crate::jpeg::bit_reader::BitReader;\n\n    // write a test pattern with an escape and see if it matches\n    #[test]\n    fn write_simple() {\n        let arr = [0x12, 0x34, 0x45, 0x67, 0x89, 0xff, 00, 0xee];\n\n        let mut b = BitWriter::new(Vec::with_capacity(1024));\n\n        b.write(1, 4);\n        b.write(2, 4);\n        b.write(3, 4);\n        b.write(4, 4);\n        b.write(4, 4);\n        b.write(0x56, 8);\n        b.write(0x78, 8);\n        b.write(0x9f, 8);\n        b.write(0xfe, 8);\n        b.write(0xe, 4);\n\n        let w = b.detach_buffer();\n\n        assert_eq!(w[..], arr);\n    }\n\n    // verify the the bits roundtrip correctly in a fairly simple scenario\n    #[test]\n    fn roundtrip_bits() {\n        let buf;\n        {\n            let mut b = BitWriter::new(Vec::with_capacity(1024));\n            for i in 1..2048 {\n                b.write(i, u32_bit_length(i) as u32);\n            }\n\n            b.pad(0xff);\n\n            buf = b.detach_buffer();\n        }\n\n        {\n            let mut r = BitReader::new(Cursor::new(&buf));\n\n            for i in 1..2048 {\n                assert_eq!(i, r.read(u32_bit_length(i as u32) as u32).unwrap());\n            }\n\n            let mut pad = Some(0xff);\n            r.read_and_verify_fill_bits(&mut pad).unwrap();\n        }\n    }\n\n    /// verify the the bits roundtrip correctly with random bits\n    #[test]\n    fn roundtrip_randombits() {\n        #[derive(Copy, Clone)]\n        enum Action {\n            Write(u16, u8),\n            Pad(u8),\n        }\n\n        use rand::Rng;\n\n        const ITERATIONS: usize = 10000;\n\n        let mut rng = crate::helpers::get_rand_from_seed([0u8; 32]);\n        let mut test_data = Vec::with_capacity(ITERATIONS);\n\n        for _ in 0..ITERATIONS {\n            let bits = rng.gen_range(0..=16);\n\n            let t = rng.gen_range(0..=3);\n            let v = match t {\n                0 => 0,\n                1 => 0xffff,\n                _ => rng.gen_range(0..=65535),\n            };\n\n            let v = v & ((1 << bits) - 1);\n\n            if rng.gen_range(0..100) == 0 {\n                test_data.push(Action::Pad(0xff));\n            } else {\n                test_data.push(Action::Write(v as u16, bits as u8));\n            }\n        }\n        test_data.push(Action::Pad(0xff));\n\n        let buf;\n        {\n            let mut b = BitWriter::new(Vec::with_capacity(1024));\n            for &i in &test_data {\n                match i {\n                    Action::Write(v, bits) => b.write(v as u32, bits as u32),\n                    Action::Pad(fill) => b.pad(fill),\n                }\n            }\n\n            buf = b.detach_buffer();\n        }\n\n        {\n            let mut r = BitReader::new(Cursor::new(&buf));\n\n            for a in test_data {\n                match a {\n                    Action::Write(code, numbits) => {\n                        let expected_peek_byte = if numbits < 8 {\n                            (code << (8 - numbits)) as u8\n                        } else {\n                            (code >> (numbits - 8)) as u8\n                        };\n\n                        let (peekcode, peekbits) = r.peek();\n                        let num_valid_bits = peekbits.min(8).min(u32::from(numbits));\n\n                        let mask = (0xff00 >> num_valid_bits) as u8;\n\n                        assert_eq!(\n                            expected_peek_byte & mask,\n                            peekcode & mask,\n                            \"peek unexpected result\"\n                        );\n\n                        assert_eq!(\n                            code,\n                            r.read(numbits as u32).unwrap(),\n                            \"read unexpected result\"\n                        );\n                    }\n                    Action::Pad(fill) => {\n                        let mut pad = Some(fill);\n                        r.read_and_verify_fill_bits(&mut pad).unwrap();\n                    }\n                }\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "lib/src/jpeg/block_based_image.rs",
    "content": "/*---------------------------------------------------------------------------------------------\n *  Copyright (c) Microsoft Corporation. All rights reserved.\n *  Licensed under the Apache License, Version 2.0. See LICENSE.txt in the project root for license information.\n *  This software incorporates material from third parties. See NOTICE.txt for details.\n *--------------------------------------------------------------------------------------------*/\n\nuse crate::lepton_error::{Result, err_exit_code};\nuse bytemuck::{cast, cast_ref};\nuse log::info;\nuse wide::{CmpEq, i16x8};\n\nuse crate::ExitCode;\nuse crate::consts::ZIGZAG_TO_TRANSPOSED;\n\nuse super::jpeg_header::JpegHeader;\n\n/// holds the 8x8 blocks for a given component. Since we do multithreaded encoding,\n/// the image may only hold a subset of the components (specified by dpos_offset),\n/// but they can be merged\npub struct BlockBasedImage {\n    block_width: u32,\n\n    original_height: u32,\n\n    dpos_offset: u32,\n\n    image: Vec<AlignedBlock>,\n}\n\nstatic EMPTY: AlignedBlock = AlignedBlock { raw_data: [0; 64] };\n\nimpl BlockBasedImage {\n    // constructs new block image for the given y-coordinate range\n    pub fn new(\n        jpeg_header: &JpegHeader,\n        component: usize,\n        luma_y_start: u32,\n        luma_y_end: u32,\n    ) -> Result<Self> {\n        let block_width = jpeg_header.cmp_info[component].bch;\n        let original_height = jpeg_header.cmp_info[component].bcv;\n        let max_size = block_width * original_height;\n\n        let image_capacity = usize::try_from(\n            (u64::from(max_size) * u64::from(luma_y_end - luma_y_start)\n                + u64::from(jpeg_header.cmp_info[0].bcv - 1 /* round up */))\n                / u64::from(jpeg_header.cmp_info[0].bcv),\n        )\n        .unwrap();\n\n        let dpos_offset = u32::try_from(\n            u64::from(max_size) * u64::from(luma_y_start) / u64::from(jpeg_header.cmp_info[0].bcv),\n        )\n        .unwrap();\n\n        let mut image = Vec::new();\n        if let Err(e) = image.try_reserve_exact(image_capacity) {\n            // If there is an out-of-memory, this is the most likely place to happen since this is the uncompressed\n            // coefficient buffer.\n            //\n            // Handle out of memory errors gracefully, otherwise the default oom handler kills\n            // the process.\n            return err_exit_code(\n                ExitCode::OutOfMemory,\n                format!(\n                    \"failed to allocate block image of size {image_capacity} for component {component} with block width {block_width} and original height {original_height} (luma_y_start = {luma_y_start}, luma_y_end = {luma_y_end}) : {e}\"\n                ),\n            );\n        }\n\n        return Ok(BlockBasedImage {\n            block_width: block_width,\n            original_height: original_height,\n            image,\n            dpos_offset: dpos_offset,\n        });\n    }\n\n    /// merges a bunch of block images generated by different threads into a single one used by progressive decoding\n    pub fn merge(images: &mut Vec<Vec<BlockBasedImage>>, index: usize) -> Result<Self> {\n        // figure out the total size of all the blocks so we can set the capacity correctly\n        let total_size = images.iter().map(|x| x[index].image.len()).sum();\n\n        let mut contents = Vec::new();\n        if let Err(e) = contents.try_reserve_exact(total_size) {\n            // If there is an out-of-memory, this is the most likely place to happen since this is the uncompressed\n            // coefficient buffer.\n            //\n            // Handle out of memory errors gracefully, otherwise the default oom handler kills\n            // the process.\n            return err_exit_code(\n                ExitCode::OutOfMemory,\n                format!(\"failed to allocate merged block image of size {total_size} : {e}\"),\n            );\n        }\n\n        let mut block_width = None;\n        let mut original_height = None;\n\n        for v in images {\n            assert!(\n                v[index].dpos_offset == contents.len() as u32,\n                \"previous content should match new content\"\n            );\n\n            if let Some(w) = block_width {\n                assert_eq!(w, v[index].block_width, \"all block_width must match\")\n            } else {\n                block_width = Some(v[index].block_width);\n            }\n\n            if let Some(w) = original_height {\n                assert_eq!(\n                    w, v[index].original_height,\n                    \"all original_height must match\"\n                )\n            } else {\n                original_height = Some(v[index].original_height);\n            }\n\n            contents.append(&mut v[index].image);\n        }\n\n        return Ok(BlockBasedImage {\n            block_width: block_width.unwrap(),\n            original_height: original_height.unwrap(),\n            image: contents,\n            dpos_offset: 0,\n        });\n    }\n\n    #[allow(dead_code)]\n    pub fn dump(&self) {\n        info!(\n            \"size = {0}, capacity = {1}, dpos_offset = {2}\",\n            self.image.len(),\n            self.image.capacity(),\n            self.dpos_offset\n        );\n    }\n\n    pub fn get_block_width(&self) -> u32 {\n        self.block_width\n    }\n\n    pub fn get_original_height(&self) -> u32 {\n        self.original_height\n    }\n\n    /// ensure that the image is filled up to a given dpos with blank blocks and optionally\n    /// write a block at the given position.\n    #[inline(always)]\n    pub fn fill_up_to_dpos(\n        &mut self,\n        dpos: u32,\n        block_to_write: Option<AlignedBlock>,\n    ) -> &mut AlignedBlock {\n        // ensure that dpos_offset got set to the right value when we start writing\n        if self.image.len() == 0 {\n            debug_assert!(self.dpos_offset == dpos);\n        }\n\n        // should never underflow otherwise we are writing to the wrong part of the image\n        let relative_offset = (dpos as usize)\n            .checked_sub(self.dpos_offset as usize)\n            .unwrap();\n\n        if relative_offset < self.image.len() {\n            // rewrite already written block\n            if let Some(b) = block_to_write {\n                self.image[relative_offset] = b;\n            }\n        } else {\n            // need to extend the image length and add any necessary\n            // zero blocks to fill the gap.\n            assert!(\n                relative_offset < self.image.capacity(),\n                \"capacity should be set to the exact image size to avoid reallocations\"\n            );\n\n            // optimizer realizes that this is memset\n            self.image\n                .resize_with(relative_offset, || AlignedBlock::default());\n\n            self.image.push(block_to_write.unwrap_or_default());\n        }\n\n        return &mut self.image[relative_offset];\n    }\n\n    pub fn set_block_data(&mut self, dpos: u32, block_data: AlignedBlock) {\n        self.fill_up_to_dpos(dpos, Some(block_data));\n    }\n\n    pub fn get_block(&self, dpos: u32) -> &AlignedBlock {\n        if (dpos - self.dpos_offset) as usize >= self.image.len() {\n            return &EMPTY;\n        } else {\n            return &self.image[(dpos - self.dpos_offset) as usize];\n        }\n    }\n\n    #[inline(always)]\n    pub fn append_block(&mut self, block: AlignedBlock) {\n        assert!(\n            self.image.len() < self.image.capacity(),\n            \"capacity should be set correctly\"\n        );\n        self.image.push(block);\n    }\n\n    #[inline(always)]\n    pub fn get_block_mut(&mut self, dpos: u32) -> &mut AlignedBlock {\n        self.fill_up_to_dpos(dpos, None)\n    }\n}\n\n/// block of 64 coefficients in the aligned order, which is similar to zigzag except that the 7x7 lower right square comes first,\n/// followed by the DC, followed by the edges\n#[repr(C, align(32))]\npub struct AlignedBlock {\n    raw_data: [i16; 64],\n}\n\npub static EMPTY_BLOCK: AlignedBlock = AlignedBlock { raw_data: [0; 64] };\n\nimpl Default for AlignedBlock {\n    fn default() -> Self {\n        AlignedBlock { raw_data: [0; 64] }\n    }\n}\n\nimpl AlignedBlock {\n    #[inline(always)]\n    pub fn new(block: [i16; 64]) -> Self {\n        AlignedBlock { raw_data: block }\n    }\n\n    #[inline(always)]\n    pub fn as_i16x8(&self, index: usize) -> i16x8 {\n        let v: &[i16x8; 8] = cast_ref(&self.raw_data);\n        v[index]\n    }\n\n    #[allow(dead_code)]\n    #[inline(always)]\n    pub fn transpose(&self) -> AlignedBlock {\n        return AlignedBlock::new(cast(i16x8::transpose(cast(*self.get_block()))));\n    }\n\n    #[inline(always)]\n    pub fn get_dc(&self) -> i16 {\n        return self.raw_data[0];\n    }\n\n    #[inline(always)]\n    pub fn set_dc(&mut self, value: i16) {\n        self.raw_data[0] = value\n    }\n\n    #[inline(always)]\n    pub fn zigzag_to_transposed(a: [i16; 64]) -> AlignedBlock {\n        AlignedBlock {\n            raw_data: [\n                a[0], a[2], a[3], a[9], a[10], a[20], a[21], a[35], a[1], a[4], a[8], a[11], a[19],\n                a[22], a[34], a[36], a[5], a[7], a[12], a[18], a[23], a[33], a[37], a[48], a[6],\n                a[13], a[17], a[24], a[32], a[38], a[47], a[49], a[14], a[16], a[25], a[31], a[39],\n                a[46], a[50], a[57], a[15], a[26], a[30], a[40], a[45], a[51], a[56], a[58], a[27],\n                a[29], a[41], a[44], a[52], a[55], a[59], a[62], a[28], a[42], a[43], a[53], a[54],\n                a[60], a[61], a[63],\n            ],\n        }\n    }\n\n    #[inline(always)]\n    pub fn zigzag_from_transposed(&self) -> AlignedBlock {\n        let a = self.raw_data;\n        AlignedBlock {\n            raw_data: [\n                a[0], a[8], a[1], a[2], a[9], a[16], a[24], a[17], a[10], a[3], a[4], a[11], a[18],\n                a[25], a[32], a[40], a[33], a[26], a[19], a[12], a[5], a[6], a[13], a[20], a[27],\n                a[34], a[41], a[48], a[56], a[49], a[42], a[35], a[28], a[21], a[14], a[7], a[15],\n                a[22], a[29], a[36], a[43], a[50], a[57], a[58], a[51], a[44], a[37], a[30], a[23],\n                a[31], a[38], a[45], a[52], a[59], a[60], a[53], a[46], a[39], a[47], a[54], a[61],\n                a[62], a[55], a[63],\n            ],\n        }\n    }\n\n    #[inline(always)]\n    pub fn get_block(&self) -> &[i16; 64] {\n        return &self.raw_data;\n    }\n\n    #[inline(always)]\n    pub fn get_block_mut(&mut self) -> &mut [i16; 64] {\n        return &mut self.raw_data;\n    }\n\n    // used for debugging\n    #[allow(dead_code)]\n    pub fn get_hash(&self) -> i32 {\n        let mut sum = 0;\n        for i in 0..64 {\n            sum += self.raw_data[i] as i32\n        }\n        return sum;\n    }\n\n    #[inline(always)]\n    pub fn get_count_of_non_zeros_7x7(&self) -> u8 {\n        /// counts a row of non-zero values in the 7x7 block\n        #[inline(always)]\n        fn count_non_zeros_7x7_row(v: i16x8) -> i16x8 {\n            !v.simd_eq(i16x8::ZERO) & i16x8::new([0, 1, 1, 1, 1, 1, 1, 1])\n        }\n\n        let mut sum = i16x8::ZERO;\n        for i in 1..8 {\n            sum += count_non_zeros_7x7_row(self.as_i16x8(i));\n        }\n\n        return sum.reduce_add() as u8;\n    }\n\n    #[inline(always)]\n    pub fn get_coefficient(&self, index: usize) -> i16 {\n        return self.raw_data[index];\n    }\n\n    #[inline(always)]\n    pub fn set_coefficient(&mut self, index: usize, v: i16) {\n        self.raw_data[index] = v;\n    }\n\n    #[inline(always)]\n    pub fn set_transposed_from_zigzag(&mut self, index: usize, v: i16) {\n        self.raw_data[usize::from(ZIGZAG_TO_TRANSPOSED[index])] = v;\n    }\n\n    #[inline(always)]\n    pub fn get_transposed_from_zigzag(&self, index: usize) -> i16 {\n        return self.raw_data[usize::from(ZIGZAG_TO_TRANSPOSED[index])];\n    }\n\n    #[inline(always)]\n    pub fn from_stride(&self, offset: usize, stride: usize) -> i16x8 {\n        return i16x8::new([\n            self.raw_data[offset],\n            self.raw_data[offset + (1 * stride)],\n            self.raw_data[offset + (2 * stride)],\n            self.raw_data[offset + (3 * stride)],\n            self.raw_data[offset + (4 * stride)],\n            self.raw_data[offset + (5 * stride)],\n            self.raw_data[offset + (6 * stride)],\n            self.raw_data[offset + (7 * stride)],\n        ]);\n    }\n}\n"
  },
  {
    "path": "lib/src/jpeg/component_info.rs",
    "content": "/*---------------------------------------------------------------------------------------------\n *  Copyright (c) Microsoft Corporation. All rights reserved.\n *  Licensed under the Apache License, Version 2.0. See LICENSE.txt in the project root for license information.\n *  This software incorporates material from third parties. See NOTICE.txt for details.\n *--------------------------------------------------------------------------------------------*/\n\n#[derive(Debug, Clone)]\npub struct ComponentInfo {\n    /// quantization table\n    pub q_table_index: u8,\n\n    /// no of huffman table (DC)\n    pub huff_dc: u8,\n\n    /// no of huffman table (AC)\n    pub huff_ac: u8,\n\n    /// sample factor vertical\n    pub sfv: u32,\n\n    /// sample factor horizontal\n    pub sfh: u32,\n\n    /// blocks in mcu\n    pub mbs: u32,\n\n    /// block count vertical (interleaved)\n    pub bcv: u32,\n\n    /// block count horizontal (interleaved)\n    pub bch: u32,\n\n    /// block count (all) (interleaved)\n    pub bc: u32,\n\n    /// block count vertical (non interleaved)\n    pub ncv: u32,\n\n    /// block count horizontal (non interleaved)\n    pub nch: u32,\n\n    /// block count (all) (non interleaved)\n    pub nc: u32,\n\n    /// statistical identity\n    pub sid: u32,\n\n    /// jpeg internal id\n    pub jid: u8,\n}\n\nimpl Default for ComponentInfo {\n    fn default() -> ComponentInfo {\n        return ComponentInfo {\n            q_table_index: 0xff,\n            sfv: u32::MAX,\n            sfh: u32::MAX,\n            mbs: u32::MAX,\n            bcv: u32::MAX,\n            bch: u32::MAX,\n            bc: u32::MAX,\n            ncv: u32::MAX,\n            nch: u32::MAX,\n            nc: u32::MAX,\n            sid: u32::MAX,\n            jid: 0xff,\n            huff_dc: 0xff,\n            huff_ac: 0xff,\n        };\n    }\n}\n"
  },
  {
    "path": "lib/src/jpeg/jpeg_code.rs",
    "content": "/*---------------------------------------------------------------------------------------------\n *  Copyright (c) Microsoft Corporation. All rights reserved.\n *  Licensed under the Apache License, Version 2.0. See LICENSE.txt in the project root for license information.\n *  This software incorporates material from third parties. See NOTICE.txt for details.\n *--------------------------------------------------------------------------------------------*/\n\n/// Start of Frame (size information), coding process: baseline DCT\npub const SOF0: u8 = 0xC0;\n\n/// Start of Frame (size information), coding process: extended sequential DCT\npub const SOF1: u8 = 0xC1;\n\n/// Start of Frame (size information), coding process: progressive DCT\npub const SOF2: u8 = 0xC2;\n\n/// Huffman Table\npub const DHT: u8 = 0xC4;\n\n/// Restart 0 segment\npub const RST0: u8 = 0xD0;\n\n/// Start of Image\npub const SOI: u8 = 0xD8;\n\n/// End of Image, or End of File\npub const EOI: u8 = 0xD9;\n\n/// Start of Scan\npub const SOS: u8 = 0xDA;\n\n/// Define Quantization Table\npub const DQT: u8 = 0xDB;\n\n/// Define restart interval\npub const DRI: u8 = 0xDD;\n"
  },
  {
    "path": "lib/src/jpeg/jpeg_header.rs",
    "content": "/*---------------------------------------------------------------------------------------------\n *  Copyright (c) Microsoft Corporation. All rights reserved.\n *  Licensed under the Apache License, Version 2.0. See LICENSE.txt in the project root for license information.\n *  This software incorporates material from third parties. See NOTICE.txt for details.\n *--------------------------------------------------------------------------------------------*/\n\n/*\nCopyright (c) 2006...2016, Matthias Stirner and HTW Aalen University\nAll rights reserved.\n\nRedistribution and use in source and binary forms, with or without\nmodification, are permitted provided that the following conditions are\nmet:\n\n1. Redistributions of source code must retain the above copyright\nnotice, this list of conditions and the following disclaimer.\n\n2. Redistributions in binary form must reproduce the above copyright\nnotice, this list of conditions and the following disclaimer in the\ndocumentation and/or other materials provided with the distribution.\n\nTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS\nIS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED\nTO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A\nPARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT\nHOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,\nSPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED\nTO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR\nPROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF\nLIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING\nNEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\nSOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n*/\n\nuse std::fmt::Debug;\nuse std::io::{Cursor, Read, Write};\nuse std::num::NonZeroU32;\n\nuse crate::LeptonError;\nuse crate::consts::JpegType;\nuse crate::enabled_features::EnabledFeatures;\nuse crate::helpers::*;\nuse crate::lepton_error::{AddContext, ExitCode, Result, err_exit_code};\n\nuse super::component_info::ComponentInfo;\nuse super::jpeg_code;\nuse super::truncate_components::TruncateComponents;\n\n/// Information required to partition the coding the JPEG huffman encoded stream of a scan\n/// at an arbitrary location in the stream.\n///\n/// Note that this only works for sequential JPEGs since progressive ones have multiple scans\n/// that each process the entire image.\n\n#[derive(Debug, Default, Clone)]\npub struct RestartSegmentCodingInfo {\n    pub overhang_byte: u8,\n    pub num_overhang_bits: u8,\n    pub luma_y_start: u32,\n    pub luma_y_end: u32,\n    pub last_dc: [i16; 4],\n}\n\nimpl RestartSegmentCodingInfo {\n    pub fn new(\n        overhang_byte: u8,\n        num_overhang_bits: u8,\n        last_dc: [i16; 4],\n        mcu: u32,\n        jf: &JpegHeader,\n    ) -> Self {\n        let mcu_y = mcu / jf.mcuh;\n        let luma_mul = jf.cmp_info[0].bcv / jf.mcuv;\n\n        Self {\n            overhang_byte,\n            num_overhang_bits,\n            last_dc,\n            luma_y_start: luma_mul * mcu_y,\n            luma_y_end: luma_mul * (mcu_y + 1),\n        }\n    }\n}\n\n/// Global information required to reconstruct the JPEG exactly the way that it was, especially\n/// regarding information about possible truncation and RST markers.\n#[derive(Default, Clone, Debug)]\npub struct ReconstructionInfo {\n    /// the maximum component in a truncated progressive image.\n    ///\n    /// This is meant to be used for progressive images but is not yet implemented.\n    pub max_cmp: u32,\n\n    /// the maximum band in a truncated progressive image\n    ///\n    /// This is meant to be used for progressive images but is not yet implemented.\n    pub max_bpos: u32,\n\n    /// The maximum bit in a truncated progressive image.\n    ///\n    /// This is meant to be used for progressive images but is not yet implemented.\n    pub max_sah: u8,\n\n    /// the maximum dpos in a truncated image\n    pub max_dpos: [u32; 4],\n\n    /// if we encountered EOF before the expected end of the image\n    pub early_eof_encountered: bool,\n\n    /// the mask for padding out the bitstream when we get to the end of a reset block\n    pub pad_bit: Option<u8>,\n\n    /// A list containing one entry for each scan segment. Each entry contains the number of restart intervals\n    /// within the corresponding scan segment.\n    ///\n    /// TODO: We currently don't generate this value when we parse a JPEG (leaving rst_cnt_set as false), however when\n    /// we read a Lepton file we will use this to determine whether we should generate restart markers in order\n    /// to maintain backward compability for decoding Lepton files generated by the C++ version.\n    ///\n    /// This means that there might be some files that we could have encoded successfully that we don't, but since\n    /// we are required to reverify anyway, this is not a problem (except a minor efficiency issue)\n    pub rst_cnt: Vec<u32>,\n\n    /// true if rst_cnt contains a valid set of counts\n    pub rst_cnt_set: bool,\n\n    /// information about how to truncate the image if it was partially written\n    pub truncate_components: TruncateComponents,\n\n    /// trailing RST marking information\n    pub rst_err: Vec<u8>,\n\n    /// raw jpeg header to be written back to the file when it is recreated\n    pub raw_jpeg_header: Vec<u8>,\n\n    /// garbage data (default value - empty segment - means no garbage data)\n    pub garbage_data: Vec<u8>,\n}\n\npub fn parse_jpeg_header<R: Read>(\n    reader: &mut R,\n    enabled_features: &EnabledFeatures,\n    jpeg_header: &mut JpegHeader,\n    rinfo: &mut ReconstructionInfo,\n) -> Result<bool> {\n    // the raw header in the lepton file can actually be spread across different sections\n    // seperated by the Start-of-Scan marker. We use the mirror to write out whatever\n    // data we parse until we hit the SOS\n\n    let mut output = Vec::new();\n    let mut output_cursor = Cursor::new(&mut output);\n\n    let mut mirror = Mirror::new(reader, &mut output_cursor);\n\n    if jpeg_header.parse(&mut mirror, enabled_features).context()? {\n        // append the header if it was not the end of file marker\n        rinfo.raw_jpeg_header.append(&mut output);\n        return Ok(true);\n    } else {\n        // if the output was more than 2 bytes then was a trailing header, so keep that around as well,\n        // but we don't want the EOI since that goes into the garbage data.\n        if output.len() > 2 {\n            rinfo.raw_jpeg_header.extend(&output[0..output.len() - 2]);\n        }\n\n        return Ok(false);\n    }\n}\n\n// internal utility we use to collect the header that we read for later\nstruct Mirror<'a, R, W> {\n    read: &'a mut R,\n    output: &'a mut W,\n    amount_written: usize,\n}\n\nimpl<'a, R, W> Mirror<'a, R, W> {\n    pub fn new(read: &'a mut R, output: &'a mut W) -> Self {\n        Mirror {\n            read,\n            output,\n            amount_written: 0,\n        }\n    }\n}\n\nimpl<R: Read, W: Write> Read for Mirror<'_, R, W> {\n    fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {\n        let n = self.read.read(buf)?;\n        self.output.write_all(&buf[..n])?;\n        self.amount_written += n;\n        Ok(n)\n    }\n}\n\n#[derive(Copy, Clone, Debug)]\npub(crate) struct HuffCodes {\n    pub c_val: [u16; 256],\n    pub c_len: [u16; 256],\n    pub c_len_plus_s: [u8; 256],\n    pub c_val_shift_s: [u32; 512],\n    pub max_eob_run: u16,\n}\n\nimpl Default for HuffCodes {\n    fn default() -> Self {\n        HuffCodes {\n            c_val: [0; 256],\n            c_len: [0; 256],\n            c_len_plus_s: [0; 256],\n            c_val_shift_s: [0; 512],\n            max_eob_run: 0,\n        }\n    }\n}\n\nimpl HuffCodes {\n    /// Constructs from the format encoded by JPEG\n    ///\n    /// Tree consists of a 16 byte table with the number of codes for each bit length,\n    /// followed by the actual codes for that length appended together.\n    pub fn construct_from_segment(segment: &[u8]) -> Result<Self> {\n        let clen_offset = 0;\n        let cval_offset = 16;\n\n        let mut hc = HuffCodes::default();\n\n        // creating huffman-codes\n        let mut k = 0;\n        let mut code = 0;\n\n        // symbol-value of code is its position in the table\n        for i in 0..16 {\n            ensure_space(segment, clen_offset, i + 1).context()?;\n\n            let mut j = 0;\n            while j < segment[clen_offset + (i & 0xff)] {\n                ensure_space(segment, cval_offset, k + 1).context()?;\n\n                let len = (1 + i) as u16;\n\n                if u32::from(code) >= (1u32 << len) {\n                    return err_exit_code(\n                        ExitCode::UnsupportedJpeg,\n                        \"invalid huffman code layout, too many codes for a given length\",\n                    );\n                }\n\n                hc.c_len[usize::from(segment[cval_offset + (k & 0xff)])] = len;\n                hc.c_val[usize::from(segment[cval_offset + (k & 0xff)])] = code;\n\n                if code == 65535 {\n                    return err_exit_code(ExitCode::UnsupportedJpeg, \"huffman code too large\");\n                }\n\n                k += 1;\n                code += 1;\n                j += 1;\n            }\n\n            code = code << 1;\n        }\n\n        hc.post_initialize();\n\n        Ok(hc)\n    }\n\n    /// Code to run after initializing c_len and c_val\n    /// Lookup tables used for fast encoding since we already\n    /// know the length of the code and the value when we write\n    /// the code + bits to the bitstream\n    fn post_initialize(&mut self) {\n        for i in 0..256 {\n            let s = i & 0xf;\n            self.c_len_plus_s[i] = (self.c_len[i] + (s as u16)) as u8;\n            self.c_val_shift_s[i] = u32::from(self.c_val[i]) << s;\n\n            // calculate the value for negative coefficients, which compensates for the sign bit\n            self.c_val_shift_s[i + 256] = (u32::from(self.c_val[i]) << s) | ((1u32 << s) - 1);\n        }\n\n        // find out eobrun (runs of all zero blocks) max value. This is used encoding/decoding progressive files.\n        //\n        // G.1.2.2 of the spec specifies that there are 15 huffman codes\n        // reserved for encoding long runs of up to 32767 empty blocks.\n        // Here we figure out what the largest code that could possibly\n        // be encoded by this table is so that we don't exceed it when\n        // we reencode the file.\n        self.max_eob_run = 0;\n\n        let mut i: i32 = 14;\n        while i >= 0 {\n            if self.c_len[((i << 4) & 0xff) as usize] > 0 {\n                self.max_eob_run = ((2 << i) - 1) as u16;\n                break;\n            }\n\n            i -= 1;\n        }\n    }\n}\n\n#[derive(Copy, Clone, Debug)]\npub(crate) struct HuffTree {\n    pub node: [[u16; 2]; 256],\n    pub peek_code: [(u8, u8); 256],\n}\n\nimpl Default for HuffTree {\n    fn default() -> Self {\n        HuffTree {\n            node: [[0; 2]; 256],\n            peek_code: [(0, 0); 256],\n        }\n    }\n}\n\nimpl HuffTree {\n    /// construct the huffman tree codes from the HuffCodes as a source\n    pub fn construct_hufftree(hc: &HuffCodes, accept_invalid_dht: bool) -> Result<Self> {\n        let mut ht = HuffTree::default();\n\n        let mut nextfree = 1;\n        for i in 0..256 {\n            // reset current node\n            let mut node = 0;\n\n            // go through each code & store path\n            if hc.c_len[i] > 0 {\n                let mut j = hc.c_len[i] - 1;\n                while j > 0 {\n                    if node <= 0xff {\n                        if bitn(hc.c_val[i], j) == 1 {\n                            if ht.node[node][1] == 0 {\n                                ht.node[node][1] = nextfree;\n                                nextfree += 1;\n                            }\n\n                            node = usize::from(ht.node[node][1]);\n                        } else {\n                            if ht.node[node][0] == 0 {\n                                ht.node[node][0] = nextfree;\n                                nextfree += 1;\n                            }\n\n                            node = usize::from(ht.node[node][0]);\n                        }\n                    } else {\n                        // we accept any .lep file that was encoded this way\n                        if !accept_invalid_dht {\n                            return err_exit_code(\n                                ExitCode::UnsupportedJpeg,\n                                \"Huffman table out of space\",\n                            );\n                        }\n                    }\n\n                    j -= 1;\n                }\n            }\n\n            if node <= 0xff {\n                // last link is number of targetvalue + 256\n                if hc.c_len[i] > 0 {\n                    if bitn(hc.c_val[i], 0) == 1 {\n                        ht.node[node][1] = (i + 256) as u16;\n                    } else {\n                        ht.node[node][0] = (i + 256) as u16;\n                    }\n                }\n            } else {\n                // we accept any .lep file that was encoded this way\n                if !accept_invalid_dht {\n                    return err_exit_code(ExitCode::UnsupportedJpeg, \"Huffman table out of space\");\n                }\n            }\n        }\n        for x in &mut ht.node {\n            if x[0] == 0 {\n                x[0] = 0xffff;\n            }\n            if x[1] == 0 {\n                x[1] = 0xffff;\n            }\n        }\n        // initial value for next free place\n\n        // work through every code creating links between the nodes (represented through ints)\n\n        // for every illegal code node, store 0xffff we should never get here, but it will avoid an infinite loop in the case of a bug\n\n        // precalculate decoding peeking into the stream. This lets us quickly decode\n        // small code without jumping through the node table\n        for peekbyte in 0..256 {\n            let mut node = 0;\n            let mut len: u8 = 0;\n\n            while node < 256 && len <= 7 {\n                node = ht.node[usize::from(node)][(peekbyte >> (7 - len)) & 0x1];\n\n                len += 1;\n            }\n\n            if node == 0xffff || node < 256 {\n                // invalid code or code was too long to fit, so just say it requireds 256 bits\n                // so we will take the long path to decode it\n                ht.peek_code[peekbyte] = (0, 0xff);\n            } else {\n                ht.peek_code[peekbyte] = ((node - 256) as u8, len);\n            }\n        }\n        Ok(ht)\n    }\n}\n\n/// JPEG information parsed out of segments found before the image segment\n#[derive(Clone)]\npub struct JpegHeader {\n    /// quantization tables 4 x 64\n    pub q_tables: [[u16; 64]; 4],\n\n    /// huffman codes (access via get_huff_xx_codes)\n    h_codes: [[HuffCodes; 4]; 2],\n\n    /// huffman decoding trees (access via get_huff_xx_tree)\n    h_trees: [[HuffTree; 4]; 2],\n\n    /// 1 if huffman table is set\n    ht_set: [[u8; 4]; 2],\n\n    /// components\n    pub cmp_info: [ComponentInfo; 4],\n\n    /// component count\n    pub cmpc: usize,\n\n    /// width of image\n    pub img_width: u32,\n\n    /// height of image\n    pub img_height: u32,\n\n    pub jpeg_type: JpegType,\n\n    /// max horizontal sample factor\n    pub sfhm: u32,\n\n    /// max verical sample factor\n    pub sfvm: u32,\n\n    // mcus per line\n    pub mcuv: NonZeroU32,\n\n    /// mcus per column\n    pub mcuh: NonZeroU32,\n\n    /// count of mcus\n    pub mcuc: u32,\n\n    /// restart interval\n    pub rsti: u32,\n\n    /// component count in current scan\n    pub cs_cmpc: usize,\n\n    /// component numbers in current scan\n    pub cs_cmp: [usize; 4],\n\n    // variables: info about current scan\n    /// begin - band of current scan ( inclusive )\n    pub cs_from: u8,\n\n    /// end - band of current scan ( inclusive )\n    pub cs_to: u8,\n\n    /// successive approximation bit pos high\n    pub cs_sah: u8,\n\n    /// successive approximation bit pos low\n    pub cs_sal: u8,\n}\n\nimpl std::fmt::Debug for JpegHeader {\n    /// Custom debug implementation to avoid printing large arrays\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        f.debug_struct(\"JpegHeader\")\n            .field(\"cmp_info\", &self.cmp_info)\n            .field(\"cmpc\", &self.cmpc)\n            .field(\"img_width\", &self.img_width)\n            .field(\"img_height\", &self.img_height)\n            .field(\"jpeg_type\", &self.jpeg_type)\n            .field(\"sfhm\", &self.sfhm)\n            .field(\"sfvm\", &self.sfvm)\n            .field(\"mcuv\", &self.mcuv)\n            .field(\"mcuh\", &self.mcuh)\n            .field(\"mcuc\", &self.mcuc)\n            .field(\"rsti\", &self.rsti)\n            .field(\"cs_cmpc\", &self.cs_cmpc)\n            .field(\"cs_cmp\", &self.cs_cmp)\n            .field(\"cs_from\", &self.cs_from)\n            .field(\"cs_to\", &self.cs_to)\n            .field(\"cs_sah\", &self.cs_sah)\n            .field(\"cs_sal\", &self.cs_sal)\n            .finish()\n    }\n}\n\nenum ParseSegmentResult {\n    Continue,\n    EOI,\n    SOS,\n}\n\nimpl Default for JpegHeader {\n    fn default() -> Self {\n        return JpegHeader {\n            q_tables: [[0; 64]; 4],\n            h_codes: [[HuffCodes::default(); 4]; 2],\n            h_trees: [[HuffTree::default(); 4]; 2],\n            ht_set: [[0; 4]; 2],\n            cmp_info: [\n                ComponentInfo::default(),\n                ComponentInfo::default(),\n                ComponentInfo::default(),\n                ComponentInfo::default(),\n            ],\n            cmpc: 0,\n            img_width: 0,\n            img_height: 0,\n            jpeg_type: JpegType::Unknown,\n            sfhm: 0,\n            sfvm: 0,\n            mcuv: NonZeroU32::MIN,\n            mcuh: NonZeroU32::MIN,\n            mcuc: 0,\n            rsti: 0,\n            cs_cmpc: 0,\n            cs_from: 0,\n            cs_to: 0,\n            cs_sah: 0,\n            cs_sal: 0,\n            cs_cmp: [0; 4],\n        };\n    }\n}\n\nimpl JpegHeader {\n    /// true if this image is a single scan, which can be partitioned and decode\n    /// completely independently by separate threads. If this is not the case, then\n    /// we need to decode the entire image in memory and then encode the JPEG sequentially.\n    pub fn is_single_scan(&self) -> bool {\n        assert!(self.jpeg_type != JpegType::Unknown);\n\n        self.jpeg_type == JpegType::Sequential && self.cmpc == self.cs_cmpc\n    }\n\n    #[inline(always)]\n    pub(super) fn get_huff_dc_codes(&self, cmp: usize) -> &HuffCodes {\n        &self.h_codes[0][usize::from(self.cmp_info[cmp].huff_dc)]\n    }\n\n    #[inline(always)]\n    pub(super) fn get_huff_dc_tree(&self, cmp: usize) -> &HuffTree {\n        &self.h_trees[0][usize::from(self.cmp_info[cmp].huff_dc)]\n    }\n\n    #[inline(always)]\n    pub(super) fn get_huff_ac_codes(&self, cmp: usize) -> &HuffCodes {\n        &self.h_codes[1][usize::from(self.cmp_info[cmp].huff_ac)]\n    }\n\n    #[inline(always)]\n    pub(super) fn get_huff_ac_tree(&self, cmp: usize) -> &HuffTree {\n        &self.h_trees[1][usize::from(self.cmp_info[cmp].huff_ac)]\n    }\n\n    /// Parses JPEG segments and updates the appropriate header fields\n    /// until we hit either an SOS (image data) or EOI (end of image).\n    ///\n    /// Returns false if we hit EOI, true if we have an image to process.\n    pub fn parse<R: Read>(\n        &mut self,\n        reader: &mut R,\n        enabled_features: &EnabledFeatures,\n    ) -> Result<bool> {\n        // header parser loop\n        loop {\n            match self\n                .parse_next_segment(reader, enabled_features)\n                .context()?\n            {\n                ParseSegmentResult::EOI => {\n                    return Ok(false);\n                }\n                ParseSegmentResult::SOS => {\n                    break;\n                }\n                _ => {}\n            }\n        }\n\n        // check if information is complete\n        if self.cmpc == 0 {\n            return err_exit_code(\n                ExitCode::UnsupportedJpeg,\n                \"header contains incomplete information\",\n            );\n        }\n\n        for cmp in 0..self.cmpc {\n            if (self.cmp_info[cmp].sfv == 0)\n                || (self.cmp_info[cmp].sfh == 0)\n                || (self.q_tables[usize::from(self.cmp_info[cmp].q_table_index)][0] == 0)\n                || (self.jpeg_type == JpegType::Unknown)\n            {\n                return err_exit_code(\n                    ExitCode::UnsupportedJpeg,\n                    \"header contains incomplete information (components)\",\n                );\n            }\n        }\n\n        // do all remaining component info calculations\n        for cmp in 0..self.cmpc {\n            if self.cmp_info[cmp].sfh > self.sfhm {\n                self.sfhm = self.cmp_info[cmp].sfh;\n            }\n\n            if self.cmp_info[cmp].sfv > self.sfvm {\n                self.sfvm = self.cmp_info[cmp].sfv;\n            }\n        }\n\n        self.mcuv = NonZeroU32::new(\n            (1.0 * self.img_height as f64 / (8.0 * self.sfhm as f64)).ceil() as u32,\n        )\n        .ok_or_else(|| LeptonError::new(ExitCode::UnsupportedJpeg, \"mcuv is zero\"))?;\n\n        self.mcuh =\n            NonZeroU32::new((1.0 * self.img_width as f64 / (8.0 * self.sfvm as f64)).ceil() as u32)\n                .ok_or_else(|| LeptonError::new(ExitCode::UnsupportedJpeg, \"mcuh is zero\"))?;\n\n        self.mcuc = self.mcuv.get() * self.mcuh.get();\n\n        for cmp in 0..self.cmpc {\n            self.cmp_info[cmp].mbs = self.cmp_info[cmp].sfv * self.cmp_info[cmp].sfh;\n            self.cmp_info[cmp].bcv = self.mcuv.get() * self.cmp_info[cmp].sfh;\n            self.cmp_info[cmp].bch = self.mcuh.get() * self.cmp_info[cmp].sfv;\n            self.cmp_info[cmp].bc = self.cmp_info[cmp].bcv * self.cmp_info[cmp].bch;\n            self.cmp_info[cmp].ncv = (1.0\n                * self.img_height as f64\n                * (self.cmp_info[cmp].sfh as f64 / (8.0 * self.sfhm as f64)))\n                .ceil() as u32;\n            self.cmp_info[cmp].nch = (1.0\n                * self.img_width as f64\n                * (self.cmp_info[cmp].sfv as f64 / (8.0 * self.sfvm as f64)))\n                .ceil() as u32;\n            self.cmp_info[cmp].nc = self.cmp_info[cmp].ncv * self.cmp_info[cmp].nch;\n        }\n\n        // decide components' statistical ids\n        if self.cmpc <= 3 {\n            for cmp in 0..self.cmpc {\n                self.cmp_info[cmp].sid = cmp as u32;\n            }\n        } else {\n            for cmp in 0..self.cmpc {\n                self.cmp_info[cmp].sid = 0;\n            }\n        }\n\n        return Ok(true);\n    }\n\n    /// verifies that the huffman tables for the given types are present for the current scan, and if not, return an error\n    pub fn verify_huffman_table(&self, dc_present: bool, ac_present: bool) -> Result<()> {\n        for icsc in 0..self.cs_cmpc {\n            let icmp = self.cs_cmp[icsc];\n\n            if dc_present && self.ht_set[0][self.cmp_info[icmp].huff_dc as usize] == 0 {\n                return err_exit_code(\n                    ExitCode::UnsupportedJpeg,\n                    format!(\"DC huffman table missing for component {0}\", icmp),\n                );\n            } else if ac_present && self.ht_set[1][self.cmp_info[icmp].huff_ac as usize] == 0 {\n                return err_exit_code(\n                    ExitCode::UnsupportedJpeg,\n                    format!(\"AC huffman table missing for component {0}\", icmp),\n                );\n            }\n        }\n\n        Ok(())\n    }\n\n    // returns true we should continue parsing headers or false if we hit SOS and should stop\n    fn parse_next_segment<R: Read>(\n        &mut self,\n        reader: &mut R,\n        enabled_features: &EnabledFeatures,\n    ) -> Result<ParseSegmentResult> {\n        let mut header = [0u8; 4];\n\n        if reader.read(&mut header[0..1]).context()? == 0 {\n            // didn't get an EOI\n            return Ok(ParseSegmentResult::EOI);\n        }\n\n        if header[0] != 0xff {\n            return err_exit_code(ExitCode::UnsupportedJpeg, \"invalid header encountered\");\n        }\n\n        reader.read_exact(&mut header[1..2]).context()?;\n        if header[1] == jpeg_code::EOI {\n            return Ok(ParseSegmentResult::EOI);\n        }\n\n        // now read the second two bytes so we can get the size of the segment\n        reader.read_exact(&mut header[2..]).context()?;\n\n        let mut segment_data = Vec::new();\n\n        let segment_size = b_short(header[2], header[3]);\n        if segment_size < 2 {\n            return err_exit_code(ExitCode::UnsupportedJpeg, \"segment is too short\");\n        }\n\n        segment_data.resize(usize::from(segment_size) - 2, 0);\n\n        reader.read_exact(&mut segment_data).context()?;\n\n        let mut hpos = 0;\n        let len = segment_data.len();\n\n        let segment = &segment_data[..];\n\n        let btype = header[1];\n        match btype\n        {\n            jpeg_code::DHT => // DHT segment\n            {\n                // build huffman trees & codes\n                while hpos < len\n                {\n                    let lval = usize::from(lbits(segment[hpos], 4));\n                    let rval = usize::from(rbits(segment[hpos], 4));\n                    if (lval >= 2) || (rval >= 4)\n                    {\n                        break;\n                    }\n\n                    hpos+=1;\n\n                    // build huffman codes & trees\n                    self.h_codes[lval][rval] = HuffCodes::construct_from_segment(&segment[hpos..]).context()?;\n                    self.h_trees[lval][rval] = HuffTree::construct_hufftree(&self.h_codes[lval][rval], enabled_features.accept_invalid_dht).context()?;\n                    self.ht_set[lval][rval] = 1;\n\n                    let mut skip = 16;\n\n                    ensure_space(segment,hpos, 16)?;\n\n                    for i in 0..16\n                    {\n                        skip += usize::from(segment[hpos + i]);\n                    }\n\n                    hpos += skip;\n                }\n\n                if hpos != len\n                {\n                    // if we get here, something went wrong\n                    return err_exit_code(ExitCode::UnsupportedJpeg,\"size mismatch in dht marker\");\n                }\n            }\n\n            jpeg_code::DQT => // DQT segment\n            {\n                // copy quantization tables to internal memory\n                while hpos < len\n                {\n                    let lval = usize::from(lbits(segment[hpos], 4));\n                    let rval = usize::from(rbits(segment[hpos], 4));\n                    if lval >= 2 || rval >= 4\n                    {\n                        return err_exit_code(ExitCode::UnsupportedJpeg,\"DQT has invalid index\");\n                    }\n\n                    hpos+=1;\n                    if lval == 0\n                    {\n                        ensure_space(segment,hpos, 64).context()?;\n\n                        // 8 bit precision\n                        for i in 0..64\n                        {\n                            self.q_tables[rval][i] = segment[hpos + i] as u16;\n                            if self.q_tables[rval][i] == 0\n                            {\n                                if enabled_features.reject_dqts_with_zeros\n                                {\n                                    return err_exit_code(ExitCode::UnsupportedJpegWithZeroIdct0,\"DQT has zero value\");\n                                }\n                                else {\n                                    break;\n                                }\n                            }\n                        }\n\n                        hpos += 64;\n                    }\n                    else\n                    {\n                        ensure_space(segment,hpos, 128).context()?;\n\n                        // 16 bit precision\n                        for i in 0..64\n                        {\n                            self.q_tables[rval][i] = b_short(segment[hpos + (2 * i)], segment[hpos + (2 * i) + 1]);\n                            if self.q_tables[rval][i] == 0\n                            {\n                                if enabled_features.reject_dqts_with_zeros\n                                {\n                                    return err_exit_code(ExitCode::UnsupportedJpegWithZeroIdct0,\"DQT has zero value\");\n                                }\n                                else {\n                                    break;\n                                }\n                            }\n                        }\n\n                        hpos += 128;\n                    }\n                }\n\n                if hpos != len\n                {\n                    // if we get here, something went wrong\n                    return err_exit_code(ExitCode::UnsupportedJpeg, \"size mismatch in dqt marker\");\n                }\n\n            }\n\n            jpeg_code::DRI =>\n            {  // DRI segment\n                // define restart interval\n                ensure_space(segment,hpos, 2).context()?;\n                self.rsti = u32::from(b_short(segment[hpos], segment[hpos + 1]));\n            }\n\n            jpeg_code::SOS => // SOS segment\n            {\n                // prepare next scan\n                ensure_space(segment,hpos, 1).context()?;\n\n                self.cs_cmpc = usize::from(segment[hpos]);\n\n                if self.cs_cmpc == 0\n                {\n                    return err_exit_code( ExitCode::UnsupportedJpeg, \"zero components in scan\");\n                }\n\n                if self.cs_cmpc > self.cmpc\n                {\n                    return err_exit_code( ExitCode::UnsupportedJpeg, format!(\"{0} components in scan, only {1} are allowed\", self.cs_cmpc, self.cmpc));\n                }\n\n                hpos+=1;\n                for i in 0..self.cs_cmpc\n                {\n                    ensure_space(segment,hpos, 2).context()?;\n\n                    let mut cmp = 0;\n                    while cmp < self.cmpc && segment[hpos] != self.cmp_info[cmp].jid\n                    {\n                        cmp+=1;\n                    }\n\n                    if cmp == self.cmpc\n                    {\n                        return err_exit_code(ExitCode::UnsupportedJpeg, \"component id mismatch in start-of-scan\");\n                    }\n\n                    self.cs_cmp[i] = cmp;\n                    self.cmp_info[cmp].huff_dc = lbits(segment[hpos + 1], 4);\n                    self.cmp_info[cmp].huff_ac = rbits(segment[hpos + 1], 4);\n\n                    if (self.cmp_info[cmp].huff_dc == 0xff) || (self.cmp_info[cmp].huff_dc >= 4) ||\n                        (self.cmp_info[cmp].huff_ac == 0xff) || (self.cmp_info[cmp].huff_ac >= 4)\n                    {\n                        return err_exit_code(ExitCode::UnsupportedJpeg,\"huffman table number mismatch\");\n                    }\n\n                    hpos += 2;\n                }\n\n                ensure_space(segment,hpos, 3).context()?;\n\n                self.cs_from = segment[hpos + 0];\n                self.cs_to = segment[hpos + 1];\n                self.cs_sah = lbits(segment[hpos + 2], 4);\n                self.cs_sal = rbits(segment[hpos + 2], 4);\n\n                // check for errors\n                if (self.cs_from > self.cs_to) || (self.cs_from > 63) || (self.cs_to > 63)\n                {\n                    return err_exit_code(ExitCode::UnsupportedJpeg,\"spectral selection parameter out of range\");\n                }\n\n                if (self.cs_sah >= 12) || (self.cs_sal >= 12)\n                {\n                    return err_exit_code(ExitCode::UnsupportedJpeg, \"successive approximation parameter out of range\");\n                }\n\n                return Ok(ParseSegmentResult::SOS);\n            }\n\n            jpeg_code::SOF0| // SOF0 segment, coding process: baseline DCT\n            jpeg_code::SOF1| // SOF1 segment, coding process: extended sequential DCT\n            jpeg_code::SOF2 =>  // SOF2 segment, coding process: progressive DCT\n            {\n                if self.jpeg_type != JpegType::Unknown\n                {\n                    return err_exit_code(ExitCode::UnsupportedJpeg, \"image cannot have multiple SOF blocks\");\n                }\n\n                // set JPEG coding type\n                if btype == jpeg_code::SOF2\n                {\n                    self.jpeg_type = JpegType::Progressive;\n                }\n                else\n                {\n                    self.jpeg_type = JpegType::Sequential;\n                }\n\n                ensure_space(segment,hpos, 6).context()?;\n\n                // check data precision, only 8 bit is allowed\n                let lval = segment[hpos];\n                if lval != 8\n                {\n                    return err_exit_code(ExitCode::UnsupportedJpeg, format!(\"{0} bit data precision is not supported\", lval));\n                }\n\n                // image size, height & component count\n                self.img_height = u32::from(b_short(segment[hpos + 1], segment[hpos + 2]));\n                self.img_width = u32::from(b_short(segment[hpos + 3], segment[hpos + 4]));\n\n                if self.img_height == 0 || self.img_width == 0\n                {\n                    return err_exit_code(ExitCode::UnsupportedJpeg, \"image dimensions can't be zero\");\n                }\n\n                if self.img_height > enabled_features.max_jpeg_height || self.img_width > enabled_features.max_jpeg_width\n                {\n                    return err_exit_code(ExitCode::UnsupportedJpeg, format!(\"image dimensions larger than {0}x{1}\", enabled_features.max_jpeg_width, enabled_features.max_jpeg_height));\n                }\n\n                self.cmpc = usize::from(segment[hpos + 5]);\n\n                if self.cmpc > 4\n                {\n                    return err_exit_code(ExitCode::UnsupportedJpeg, format!(\"image has {0} components, max 4 are supported\", self.cmpc));\n                }\n\n                hpos += 6;\n\n                // components contained in image\n                for cmp in  0..self.cmpc\n                {\n                    ensure_space(segment,hpos, 3).context()?;\n\n                    self.cmp_info[cmp].jid = segment[hpos];\n                    self.cmp_info[cmp].sfv = u32::from(lbits(segment[hpos + 1], 4));\n                    self.cmp_info[cmp].sfh = u32::from(rbits(segment[hpos + 1], 4));\n\n                    if self.cmp_info[cmp].sfv > 2 || self.cmp_info[cmp].sfh > 2\n                    {\n                        return err_exit_code(ExitCode::SamplingBeyondTwoUnsupported, \"Sampling type beyond to not supported\");\n                    }\n\n                    let quantization_table_value = segment[hpos + 2];\n                    if usize::from(quantization_table_value) >= self.q_tables.len()\n                    {\n                        return err_exit_code(ExitCode::UnsupportedJpeg,\"quantizationTableValue too big\");\n                    }\n\n                    self.cmp_info[cmp].q_table_index = quantization_table_value;\n                    hpos += 3;\n                }\n\n            }\n\n            0xC3 => // SOF3 segment\n                {\n                    // coding process: lossless sequential\n                    return err_exit_code(ExitCode::UnsupportedJpeg,\"sof3 marker found, image is coded lossless\");\n                }\n\n            0xC5 => // SOF5 segment\n                {\n                    // coding process: differential sequential DCT\n                    return err_exit_code(ExitCode::UnsupportedJpeg,\"sof5 marker found, image is coded diff. sequential\");\n                }\n\n            0xC6 => // SOF6 segment\n                {\n                    // coding process: differential progressive DCT\n                    return err_exit_code(ExitCode::UnsupportedJpeg,\"sof6 marker found, image is coded diff. progressive\");\n                }\n\n            0xC7 => // SOF7 segment\n                {\n                    // coding process: differential lossless\n                    return err_exit_code(ExitCode::UnsupportedJpeg,\"sof7 marker found, image is coded diff. lossless\");\n                }\n\n            0xC9 => // SOF9 segment\n                {\n                    // coding process: arithmetic extended sequential DCT\n                    return err_exit_code(ExitCode::UnsupportedJpeg, \"sof9 marker found, image is coded arithm. sequential\");\n                }\n\n            0xCA => // SOF10 segment\n                {\n                    // coding process: arithmetic extended sequential DCT\n                    return err_exit_code(ExitCode::UnsupportedJpeg, \"sof10 marker found, image is coded arithm. progressive\");\n                }\n\n            0xCB => // SOF11 segment\n                {\n                    // coding process: arithmetic extended sequential DCT\n                    return err_exit_code(ExitCode::UnsupportedJpeg, \"sof11 marker found, image is coded arithm. lossless\");\n                }\n\n            0xCD => // SOF13 segment\n                {\n                    // coding process: arithmetic differntial sequential DCT\n                    return err_exit_code(ExitCode::UnsupportedJpeg, \"sof13 marker found, image is coded arithm. diff. sequential\");\n                }\n\n            0xCE => // SOF14 segment\n                {\n                    // coding process: arithmetic differential progressive DCT\n                    return err_exit_code(ExitCode::UnsupportedJpeg, \"sof14 marker found, image is coded arithm. diff. progressive\");\n                }\n\n            0xCF => // SOF15 segment\n                {\n                    // coding process: arithmetic differntial lossless\n                    return err_exit_code(ExitCode::UnsupportedJpeg, \"sof15 marker found, image is coded arithm. diff. lossless\");\n                }\n\n            0xE0| // APP0 segment\n            0xE1| // APP1 segment\n            0xE2| // APP2 segment\n            0xE3| // APP3 segment\n            0xE4| // APP4 segment\n            0xE5| // APP5 segment\n            0xE6| // APP6 segment\n            0xE7| // APP7 segment\n            0xE8| // APP8 segment\n            0xE9| // APP9 segment\n            0xEA| // APP10 segment\n            0xEB| // APP11 segment\n            0xEC| // APP12segment\n            0xED| // APP13 segment\n            0xEE| // APP14 segment\n            0xEF| // APP15 segment\n            0xFE // COM segment\n                // do nothing - return\n                => {}\n\n            jpeg_code::RST0| // RST0 segment\n            0xD1| // RST1 segment\n            0xD2| // RST2 segment\n            0xD3| // RST3 segment\n            0xD4| // RST4 segment\n            0xD5| // RST5 segment\n            0xD6| // RST6 segment\n            0xD7 => // RST7 segment\n                {\n                    // return errormessage - RST is out of place here\n                    return err_exit_code(ExitCode::UnsupportedJpeg, \"rst marker found out of place\");\n                }\n\n            jpeg_code::SOI => // SOI segment\n                {\n                    // return errormessage - start-of-image is out of place here\n                    return err_exit_code(ExitCode::UnsupportedJpeg, \"soi marker found out of place\");\n                }\n\n            jpeg_code::EOI => // EOI segment\n                {\n                    // return errormessage - end-of-image is out of place here\n                    return err_exit_code(ExitCode::UnsupportedJpeg,\"eoi marker found out of place\");\n                }\n\n            _ => // unknown marker segment\n                {\n                    // return errormessage - unknown marker\n                    return err_exit_code(ExitCode::UnsupportedJpeg, format!(\"unknown marker found: FF {0:X}\", btype));\n                }\n        }\n        return Ok(ParseSegmentResult::Continue);\n    }\n}\n\nfn ensure_space(segment: &[u8], hpos: usize, amount: usize) -> Result<()> {\n    if hpos + amount > segment.len() {\n        return err_exit_code(ExitCode::UnsupportedJpeg, \"SOF too small\");\n    }\n\n    Ok(())\n}\n\n/// constructs a huffman table for testing purposes from a given distribution\n#[cfg(any(test, feature = \"micro_benchmark\"))]\npub(super) fn generate_huff_table_from_distribution(freq: &[usize; 256]) -> HuffCodes {\n    use std::collections::{BinaryHeap, HashMap};\n\n    struct Node {\n        symbol: Option<u8>,\n        freq: usize,\n        left: Option<Box<Node>>,\n        right: Option<Box<Node>>,\n    }\n\n    impl PartialOrd for Node {\n        fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {\n            Some(self.freq.cmp(&other.freq).reverse())\n        }\n    }\n\n    impl PartialEq for Node {\n        fn eq(&self, other: &Self) -> bool {\n            self.freq == other.freq\n        }\n    }\n\n    impl Eq for Node {}\n\n    impl Ord for Node {\n        fn cmp(&self, other: &Self) -> std::cmp::Ordering {\n            self.freq.cmp(&other.freq).reverse()\n        }\n    }\n\n    fn build_tree(freq: &[usize]) -> Box<Node> {\n        let mut pq = BinaryHeap::new();\n\n        for (symbol, &freq) in freq.iter().enumerate() {\n            if freq > 0 {\n                pq.push(Box::new(Node {\n                    symbol: Some(symbol as u8),\n                    freq,\n                    left: None,\n                    right: None,\n                }));\n            }\n        }\n\n        while pq.len() > 1 {\n            let left = pq.pop().unwrap();\n            let right = pq.pop().unwrap();\n            let new_node = Node {\n                symbol: None,\n                freq: left.freq + right.freq,\n                left: Some(left),\n                right: Some(right),\n            };\n            pq.push(Box::new(new_node));\n        }\n\n        pq.pop().unwrap()\n    }\n\n    fn generate_codes(root: &Node, codes: &mut HashMap<u8, (u16, u8)>, prefix: u16, length: u8) {\n        if let Some(symbol) = root.symbol {\n            codes.insert(symbol, (prefix, length));\n        } else {\n            if let Some(ref left) = root.left {\n                generate_codes(left, codes, prefix << 1, length + 1);\n            }\n            if let Some(ref right) = root.right {\n                generate_codes(right, codes, (prefix << 1) | 1, length + 1);\n            }\n        }\n    }\n\n    let root = build_tree(freq);\n\n    let mut codes = HashMap::new();\n    generate_codes(&root, &mut codes, 0, 0);\n\n    let mut retval = HuffCodes::default();\n\n    for (&symbol, &(code, length)) in &codes {\n        retval.c_len[symbol as usize] = length.into();\n        retval.c_val[symbol as usize] = code;\n    }\n\n    retval.post_initialize();\n\n    retval\n}\n"
  },
  {
    "path": "lib/src/jpeg/jpeg_position_state.rs",
    "content": "/*---------------------------------------------------------------------------------------------\n *  Copyright (c) Microsoft Corporation. All rights reserved.\n *  Licensed under the Apache License, Version 2.0. See LICENSE.txt in the project root for license information.\n *  This software incorporates material from third parties. See NOTICE.txt for details.\n *--------------------------------------------------------------------------------------------*/\n\nuse crate::consts::{JpegDecodeStatus, JpegType};\nuse crate::lepton_error::{AddContext, ExitCode, err_exit_code};\nuse crate::{LeptonError, Result};\n\nuse super::jpeg_header::{HuffCodes, JpegHeader};\n\n/// used to keep track of position while encoding or decoding a jpeg\npub struct JpegPositionState {\n    /// current component\n    cmp: usize,\n\n    /// current minimum coded unit (a fraction of dpos)\n    mcu: u32,\n\n    /// index of component\n    csc: usize,\n\n    /// offset within mcu\n    sub: u32,\n\n    /// current block position in image for this component\n    dpos: u32,\n\n    /// number of blocks left until reset interval\n    rstw: u32,\n\n    /// tracks long zero byte runs in progressive images\n    pub eobrun: u16,\n\n    /// if the previous value was also an eobrun then this is used to make sure\n    /// that we don't have two non-maximum value runs in a row that we wouldn't be\n    /// able to recode exactly the same way\n    pub prev_eobrun: u16,\n}\n\nimpl JpegPositionState {\n    pub fn new(jf: &JpegHeader, mcu: u32) -> Self {\n        let cmp = jf.cs_cmp[0];\n        let mcumul = jf.cmp_info[cmp].sfv * jf.cmp_info[cmp].sfh;\n\n        let state = JpegPositionState {\n            cmp,\n            mcu,\n            csc: 0,\n            sub: 0,\n            dpos: mcu * mcumul,\n            rstw: if jf.rsti != 0 {\n                jf.rsti - (mcu % jf.rsti)\n            } else {\n                0\n            },\n            eobrun: 0,\n            prev_eobrun: 0,\n        };\n        return state;\n    }\n\n    pub fn get_mcu(&self) -> u32 {\n        self.mcu\n    }\n    pub fn get_dpos(&self) -> u32 {\n        self.dpos\n    }\n    pub fn get_cmp(&self) -> usize {\n        self.cmp\n    }\n\n    pub fn get_cumulative_reset_markers(&self, jf: &JpegHeader) -> u32 {\n        if self.rstw != 0 {\n            self.get_mcu() / jf.rsti\n        } else {\n            0\n        }\n    }\n\n    pub fn reset_rstw(&mut self, jf: &JpegHeader) {\n        self.rstw = jf.rsti;\n\n        // eobruns don't span reset intervals\n        self.prev_eobrun = 0;\n    }\n\n    /// calculates next position (non interleaved)\n    fn next_mcu_pos_noninterleaved(&mut self, jf: &JpegHeader) -> JpegDecodeStatus {\n        // increment position\n        self.dpos += 1;\n\n        let cmp_info = &jf.cmp_info[self.cmp];\n\n        // fix for non interleaved mcu - horizontal\n        if cmp_info.bch != cmp_info.nch && self.dpos % cmp_info.bch == cmp_info.nch {\n            self.dpos += cmp_info.bch - cmp_info.nch;\n        }\n\n        // fix for non interleaved mcu - vertical\n        if cmp_info.bcv != cmp_info.ncv && self.dpos / cmp_info.bch == cmp_info.ncv {\n            self.dpos = cmp_info.bc;\n        }\n\n        // now we've updated dpos, update the current MCU to be a fraction of that\n        if jf.jpeg_type == JpegType::Sequential {\n            self.mcu = self.dpos / (cmp_info.sfv * cmp_info.sfh);\n        }\n\n        // check position\n        if self.dpos >= cmp_info.bc {\n            return JpegDecodeStatus::ScanCompleted;\n        } else if jf.rsti > 0 {\n            self.rstw -= 1;\n            if self.rstw == 0 {\n                return JpegDecodeStatus::RestartIntervalExpired;\n            }\n        }\n\n        return JpegDecodeStatus::DecodeInProgress;\n    }\n\n    /// calculates next position for MCU\n    pub fn next_mcu_pos(&mut self, jf: &JpegHeader) -> JpegDecodeStatus {\n        // if there is just one component, go the simple route\n        if jf.cs_cmpc == 1 {\n            return self.next_mcu_pos_noninterleaved(jf);\n        }\n\n        let mut sta = JpegDecodeStatus::DecodeInProgress; // status\n        let local_mcuh = jf.mcuh.get();\n        let mut local_mcu = self.mcu;\n        let mut local_cmp = self.cmp;\n\n        // increment all counts where needed\n        self.sub += 1;\n        let mut local_sub = self.sub;\n        if local_sub >= jf.cmp_info[local_cmp].mbs {\n            self.sub = 0;\n            local_sub = 0;\n\n            self.csc += 1;\n\n            if self.csc >= jf.cs_cmpc {\n                self.csc = 0;\n                self.cmp = jf.cs_cmp[0];\n                local_cmp = self.cmp;\n\n                self.mcu += 1;\n\n                local_mcu = self.mcu;\n\n                if local_mcu >= jf.mcuc {\n                    sta = JpegDecodeStatus::ScanCompleted;\n                } else if jf.rsti > 0 {\n                    self.rstw -= 1;\n                    if self.rstw == 0 {\n                        sta = JpegDecodeStatus::RestartIntervalExpired;\n                    }\n                }\n            } else {\n                self.cmp = jf.cs_cmp[self.csc];\n                local_cmp = self.cmp;\n            }\n        }\n\n        let sfh = jf.cmp_info[local_cmp].sfh;\n        let sfv = jf.cmp_info[local_cmp].sfv;\n\n        // get correct position in image ( x & y )\n        if sfh > 1 {\n            // to fix mcu order\n            let mcu_over_mcuh = local_mcu / local_mcuh;\n            let sub_over_sfv = local_sub / sfv;\n            let mcu_mod_mcuh = local_mcu - (mcu_over_mcuh * local_mcuh);\n            let sub_mod_sfv = local_sub - (sub_over_sfv * sfv);\n            let mut local_dpos = (mcu_over_mcuh * sfh) + sub_over_sfv;\n\n            local_dpos *= jf.cmp_info[local_cmp].bch;\n            local_dpos += (mcu_mod_mcuh * sfv) + sub_mod_sfv;\n\n            self.dpos = local_dpos;\n        } else if sfv > 1 {\n            // simple calculation to speed up things if simple fixing is enough\n            self.dpos = (local_mcu * jf.cmp_info[local_cmp].mbs) + local_sub;\n        } else {\n            // no calculations needed without subsampling\n            self.dpos = self.mcu;\n        }\n\n        return sta;\n    }\n\n    /// skips the eobrun, calculates next position\n    pub fn skip_eobrun(&mut self, jf: &JpegHeader) -> Result<JpegDecodeStatus> {\n        assert!(jf.cs_cmpc == 1, \"this code only works for non-interleved\");\n\n        if (self.eobrun) == 0 {\n            return Ok(JpegDecodeStatus::DecodeInProgress);\n        }\n\n        // compare rst wait counter if needed\n        if jf.rsti > 0 {\n            if u32::from(self.eobrun) > self.rstw {\n                return err_exit_code(\n                    ExitCode::UnsupportedJpeg,\n                    \"skip_eobrun: eob run extends passed end of reset interval\",\n                )\n                .context();\n            } else {\n                self.rstw -= u32::from(self.eobrun);\n            }\n        }\n\n        fn checked_add(a: u32, b: u32) -> Result<u32> {\n            a.checked_add(b)\n                .ok_or_else(|| LeptonError::new(ExitCode::UnsupportedJpeg, \"integer overflow\"))\n        }\n\n        let cmp_info = &jf.cmp_info[self.cmp];\n\n        // fix for non interleaved mcu - horizontal\n        if cmp_info.bch != cmp_info.nch {\n            self.dpos = checked_add(\n                self.dpos,\n                (((self.dpos % cmp_info.bch) + u32::from(self.eobrun)) / cmp_info.nch)\n                    * (cmp_info.bch - cmp_info.nch),\n            )\n            .context()?;\n        }\n\n        // fix for non interleaved mcu - vertical\n        if cmp_info.bcv != cmp_info.ncv && self.dpos / cmp_info.bch >= cmp_info.ncv {\n            self.dpos =\n                checked_add(self.dpos, (cmp_info.bcv - cmp_info.ncv) * cmp_info.bch).context()?;\n        }\n\n        // skip blocks\n        self.dpos = checked_add(self.dpos, u32::from(self.eobrun)).context()?;\n\n        // reset eobrun\n        self.eobrun = 0;\n\n        // check position to see if we are done decoding\n        if self.dpos == cmp_info.bc {\n            Ok(JpegDecodeStatus::ScanCompleted)\n        } else if self.dpos > cmp_info.bc {\n            err_exit_code(\n                ExitCode::UnsupportedJpeg,\n                \"skip_eobrun: position extended passed block count\",\n            )\n            .context()\n        } else if jf.rsti > 0 && self.rstw == 0 {\n            Ok(JpegDecodeStatus::RestartIntervalExpired)\n        } else {\n            Ok(JpegDecodeStatus::DecodeInProgress)\n        }\n    }\n\n    /// checks to see if the we have optimal eob runs (each eobrun is as large as it legally can be) otherwise\n    /// we will not know how to reencode the file since the encoder always assumes EOB runs as large as possible\n    pub fn check_optimal_eobrun(\n        &mut self,\n        is_current_block_empty: bool,\n        hc: &HuffCodes,\n    ) -> Result<()> {\n        // if we got an empty block, make sure that the previous zero run was as high as it could be\n        // otherwise we won't reencode the file in the same way\n        if is_current_block_empty {\n            if self.prev_eobrun > 0 && self.prev_eobrun < hc.max_eob_run - 1 {\n                return err_exit_code(\n                    ExitCode::UnsupportedJpeg,\n                    format!(\n                        \"non optimal eobruns not supported (could have encoded up to {0} zero runs in a row, but only did {1} followed by {2}\",\n                        hc.max_eob_run,\n                        self.prev_eobrun + 1,\n                        self.eobrun + 1\n                    ),\n                );\n            }\n        }\n\n        self.prev_eobrun = self.eobrun;\n\n        Ok(())\n    }\n}\n"
  },
  {
    "path": "lib/src/jpeg/jpeg_read.rs",
    "content": "/*---------------------------------------------------------------------------------------------\n *  Copyright (c) Microsoft Corporation. All rights reserved.\n *  Licensed under the Apache License, Version 2.0. See LICENSE.txt in the project root for license information.\n *  This software incorporates material from third parties. See NOTICE.txt for details.\n *--------------------------------------------------------------------------------------------*/\n\n/*\nCopyright (c) 2006...2016, Matthias Stirner and HTW Aalen University\nAll rights reserved.\n\nRedistribution and use in source and binary forms, with or without\nmodification, are permitted provided that the following conditions are\nmet:\n\n1. Redistributions of source code must retain the above copyright\nnotice, this list of conditions and the following disclaimer.\n\n2. Redistributions in binary form must reproduce the above copyright\nnotice, this list of conditions and the following disclaimer in the\ndocumentation and/or other materials provided with the distribution.\n\nTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS\nIS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED\nTO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A\nPARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT\nHOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,\nSPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED\nTO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR\nPROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF\nLIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING\nNEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\nSOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n*/\n\nuse std::cmp::{self, max};\nuse std::io::{BufRead, Read, Seek, SeekFrom};\n\nuse crate::helpers::*;\nuse crate::lepton_error::{AddContext, ExitCode, Result, err_exit_code};\nuse crate::{EnabledFeatures, consts::*};\n\nuse super::bit_reader::BitReader;\nuse super::block_based_image::{AlignedBlock, BlockBasedImage};\nuse super::jpeg_code;\nuse super::jpeg_header::{\n    HuffTree, JpegHeader, ReconstructionInfo, RestartSegmentCodingInfo, parse_jpeg_header,\n};\nuse super::jpeg_position_state::JpegPositionState;\n\n/// Reads a JPEG file from the provided reader and returns the image data. This function is\n/// designed to return all the information needed to reconstruct a bit-level identical\n/// JPEG file.\n///\n/// In some cases this will not be possible, for example if a JPEG contains certain coding errors\n/// that are non-standard, in which case the function will return an error. This doesn't mean the JPEG\n/// is corrupt, just that it is not supported for identical reconstruction.\n///\n/// The function returns the image data as a vector of `BlockBasedImage`, which contain the\n/// DCT coefficients for each block in the image (we do not perform inverse DCT, this would be lossy).\n/// In addition, we return a vector of `RestartSegmentCodingInfo` which contains the information\n/// needed to reconstruct a portion of the JPEG file starting at the given offset. This is useful\n/// for baseline images where we can split the image into sections and decode them in parallel.\n///\n/// The callback function is called with the JPEG header information after it has been parsed, and\n/// is useful for debugging or logging purposes. Progressive images will contain multiple scans and\n/// call the callback multiple times.\n///\n/// Non-progressive images support the idea of truncating the image (since this happens frequently)\n/// where the bitstream is cut off at an arbitrary point. We assume that all subsequent data is zero,\n/// but remember enough to reconstruct the bitstream until there.\n///\n/// There is also the concept of \"garbage data\" which is what comes after the scan data but is not\n/// recognized as a header. This garbage data should be appeneded to the end of the file.\npub fn read_jpeg_file<R: BufRead + Seek, FN: FnMut(&JpegHeader, &[u8])>(\n    reader: &mut R,\n    jpeg_header: &mut JpegHeader,\n    rinfo: &mut ReconstructionInfo,\n    enabled_features: &EnabledFeatures,\n    mut on_header_callback: FN,\n) -> Result<(\n    Vec<BlockBasedImage>,\n    Vec<(u64, RestartSegmentCodingInfo)>,\n    u64,\n)> {\n    let mut startheader = [0u8; 2];\n    reader.read(&mut startheader)?;\n    if startheader[0] != 0xFF || startheader[1] != jpeg_code::SOI {\n        return err_exit_code(\n            ExitCode::UnsupportedJpeg,\n            \"jpeg must start with with 0xff 0xd8\",\n        );\n    }\n\n    if !prepare_to_decode_next_scan(jpeg_header, rinfo, reader, enabled_features).context()? {\n        return err_exit_code(ExitCode::UnsupportedJpeg, \"Jpeg does not contain scans\");\n    }\n\n    on_header_callback(jpeg_header, &rinfo.raw_jpeg_header);\n\n    if !enabled_features.progressive && !jpeg_header.is_single_scan() {\n        return err_exit_code(\n            ExitCode::ProgressiveUnsupported,\n            \"file is progressive or contains multiple scans, but this is disabled\",\n        )\n        .context();\n    }\n\n    if jpeg_header.cmpc > COLOR_CHANNEL_NUM_BLOCK_TYPES {\n        return err_exit_code(\n            ExitCode::Unsupported4Colors,\n            \"doesn't support 4 color channels\",\n        )\n        .context();\n    }\n\n    rinfo.truncate_components.init(jpeg_header);\n    let mut image_data = Vec::<BlockBasedImage>::new();\n    for i in 0..jpeg_header.cmpc {\n        // constructor takes height in proportion to the component[0]\n        image_data.push(BlockBasedImage::new(\n            &jpeg_header,\n            i,\n            0,\n            jpeg_header.cmp_info[0].bcv,\n        )?);\n    }\n\n    let start_scan_position = reader.stream_position()?;\n\n    let mut partitions = Vec::new();\n    read_first_scan(\n        &jpeg_header,\n        reader,\n        &mut partitions,\n        &mut image_data[..],\n        rinfo,\n    )\n    .context()?;\n    let mut end_scan_position = reader.stream_position()?;\n\n    if start_scan_position + 2 > end_scan_position {\n        return err_exit_code(ExitCode::UnsupportedJpeg, \"no scan data found in JPEG file\")\n            .context();\n    }\n\n    if partitions.len() == 0 {\n        return err_exit_code(\n            ExitCode::UnsupportedJpeg,\n            \"no scan information found in JPEG file\",\n        )\n        .context();\n    }\n\n    if jpeg_header.is_single_scan() {\n        if rinfo.early_eof_encountered {\n            if enabled_features.stop_reading_at_eoi {\n                return err_exit_code(ExitCode::ShortRead, \"early EOF encountered\");\n            }\n\n            rinfo\n                .truncate_components\n                .set_truncation_bounds(&jpeg_header, rinfo.max_dpos);\n\n            // If we got an early EOF, then seek backwards and capture the last two bytes and store them as garbage.\n            // This is necessary since the decoder will assume that zero garbage always means a properly terminated JPEG\n            // even if early EOF was set to true.\n            end_scan_position = reader.seek(SeekFrom::Current(-2))?.try_into().unwrap();\n\n            // make sure we don't return any partitions that are beyond the\n            // adjusted end position\n            for i in 0..partitions.len() {\n                if partitions[i].0 >= end_scan_position {\n                    return err_exit_code(\n                        ExitCode::UnsupportedJpeg,\n                        \"Partition conflicts with garbage data\",\n                    );\n                }\n            }\n\n            rinfo.garbage_data.resize(2, 0);\n            reader.read_exact(&mut rinfo.garbage_data)?;\n        }\n\n        if enabled_features.stop_reading_at_eoi {\n            // ensure there is an actual EOI marker since we haven't consumed it yet\n            let mut end_of_file = [0u8; 2];\n            reader.read_exact(&mut end_of_file).context()?;\n\n            if end_of_file != EOI {\n                return err_exit_code(\n                    ExitCode::UnsupportedJpeg,\n                    \"JPEG file does not end with EOI marker\",\n                )\n                .context();\n            }\n\n            rinfo.garbage_data = end_of_file.to_vec();\n        } else {\n            // read the rest of the file to garbage data\n            reader.read_to_end(&mut rinfo.garbage_data).context()?;\n        }\n    } else {\n        assert!(jpeg_header.jpeg_type != JpegType::Unknown);\n\n        if rinfo.early_eof_encountered {\n            return err_exit_code(\n                ExitCode::UnsupportedJpeg,\n                \"truncation is only supported for baseline images\",\n            )\n            .context();\n        }\n\n        // for progressive images, loop around reading headers and decoding until we a complete image_data\n        let mut prev_raw_jpeg_header_len = rinfo.raw_jpeg_header.len();\n\n        while prepare_to_decode_next_scan(jpeg_header, rinfo, reader, enabled_features).context()? {\n            on_header_callback(\n                jpeg_header,\n                &&rinfo.raw_jpeg_header[prev_raw_jpeg_header_len..],\n            );\n            prev_raw_jpeg_header_len = rinfo.raw_jpeg_header.len();\n\n            read_progressive_scan(&jpeg_header, reader, &mut image_data[..], rinfo).context()?;\n\n            if rinfo.early_eof_encountered {\n                return err_exit_code(\n                    ExitCode::UnsupportedJpeg,\n                    \"truncation is only supported for baseline images\",\n                )\n                .context();\n            }\n        }\n\n        end_scan_position = reader.stream_position()?;\n\n        // Since prepare_to_decode_next_scan consumed the EOI,\n        // we need to add EOI to the beginning of the garbage data (if there is any).\n        //\n        // If there was actually no garbage data, this is still ok since\n        // the marker will be appended, then removed when file gets truncated by the\n        // overall file limit.\n        rinfo.garbage_data = Vec::from(EOI);\n\n        if !enabled_features.stop_reading_at_eoi {\n            // append the rest of the file to the buffer\n            reader.read_to_end(&mut rinfo.garbage_data).context()?;\n        }\n    }\n\n    Ok((image_data, partitions, end_scan_position))\n}\n\n// false means we hit the end of file marker\nfn prepare_to_decode_next_scan<R: Read>(\n    jpeg_header: &mut JpegHeader,\n    rinfo: &mut ReconstructionInfo,\n    reader: &mut R,\n    enabled_features: &EnabledFeatures,\n) -> Result<bool> {\n    // parse the header and store it in the raw_jpeg_header\n    if !parse_jpeg_header(reader, enabled_features, jpeg_header, rinfo).context()? {\n        return Ok(false);\n    }\n\n    rinfo.max_bpos = cmp::max(rinfo.max_bpos, u32::from(jpeg_header.cs_to));\n\n    // FIXME: not sure why only first bit of csSah is examined but 4 bits of it are stored\n    rinfo.max_sah = cmp::max(\n        rinfo.max_sah,\n        cmp::max(jpeg_header.cs_sal, jpeg_header.cs_sah),\n    );\n\n    for i in 0..jpeg_header.cs_cmpc {\n        rinfo.max_cmp = cmp::max(rinfo.max_cmp, jpeg_header.cs_cmp[i] as u32);\n    }\n\n    return Ok(true);\n}\n\n/// Reads the scan from the JPEG file, writes the image data to the image_data array and\n/// partitions it into restart segments using the partition callback.\n///\n/// This only works for sequential JPEGs or the first scan in a progressive image.\n/// For subsequent scans, use the `read_progressive_scan`.\nfn read_first_scan<R: BufRead + Seek>(\n    jf: &JpegHeader,\n    reader: &mut R,\n    partitions: &mut Vec<(u64, RestartSegmentCodingInfo)>,\n    image_data: &mut [BlockBasedImage],\n    reconstruct_info: &mut ReconstructionInfo,\n) -> Result<()> {\n    let mut bit_reader = BitReader::new(reader);\n\n    // init variables for decoding\n    let mut state = JpegPositionState::new(jf, 0);\n\n    let mut do_handoff = true;\n\n    // JPEG imagedata decoding routines\n    let mut sta = JpegDecodeStatus::DecodeInProgress;\n    while sta != JpegDecodeStatus::ScanCompleted {\n        // decoding for interleaved data\n        state.reset_rstw(jf); // restart wait counter\n\n        if jf.jpeg_type == JpegType::Sequential {\n            sta = decode_baseline_rst(\n                &mut state,\n                &mut bit_reader,\n                image_data,\n                &mut do_handoff,\n                jf,\n                reconstruct_info,\n                partitions,\n            )\n            .context()?;\n        } else if jf.cs_to == 0 && jf.cs_sah == 0 {\n            // only need DC\n            jf.verify_huffman_table(true, false).context()?;\n\n            let mut last_dc = [0i16; 4];\n\n            while sta == JpegDecodeStatus::DecodeInProgress {\n                let current_block = image_data[state.get_cmp()].get_block_mut(state.get_dpos());\n\n                // collect the handoffs although for progressive images\n                // we still split the scan into sections, but we don't partition the actual JPEG\n                // writes since they have to be done on a single thread in a loop for a progressive file.\n                //\n                // TODO: get rid of this and just chop up the scan into sections in Lepton code\n                if do_handoff {\n                    partitions.push((\n                        0,\n                        RestartSegmentCodingInfo::new(0, 0, [0; 4], state.get_mcu(), jf),\n                    ));\n\n                    do_handoff = false;\n                }\n\n                // ---> succesive approximation first stage <---\n\n                // diff coding & bitshifting for dc\n                let coef = read_dc(&mut bit_reader, jf.get_huff_dc_tree(state.get_cmp()))?;\n\n                let v = coef.wrapping_add(last_dc[state.get_cmp()]);\n                last_dc[state.get_cmp()] = v;\n\n                current_block.set_transposed_from_zigzag(0, v << jf.cs_sal);\n\n                let old_mcu = state.get_mcu();\n                sta = state.next_mcu_pos(jf);\n\n                if state.get_mcu() % jf.mcuh == 0 && old_mcu != state.get_mcu() {\n                    do_handoff = true;\n                }\n            }\n        } else {\n            return err_exit_code(\n                ExitCode::UnsupportedJpeg,\n                \"progress must start with DC stage\",\n            )\n            .context();\n        }\n\n        // if we saw a pad bit at the end of the block, then remember whether they were 1s or 0s. This\n        // will be used later on to reconstruct the padding\n        bit_reader\n            .read_and_verify_fill_bits(&mut reconstruct_info.pad_bit)\n            .context()?;\n\n        // verify that we got the right RST code here since the above should do 1 mcu.\n        // If we didn't then we won't re-encode the file binary identical so there's no point in continuing\n        if sta == JpegDecodeStatus::RestartIntervalExpired {\n            bit_reader.verify_reset_code().context()?;\n\n            sta = JpegDecodeStatus::DecodeInProgress;\n        }\n    }\n\n    Ok(())\n}\n\n/// Reads a scan for progressive images where the image is encoded in multiple passes.\n/// Between each scan are a bunch of header than need to be parsed containing information\n/// like updated Huffman tables and quantization tables.\nfn read_progressive_scan<R: BufRead + Seek>(\n    jf: &JpegHeader,\n    reader: &mut R,\n    image_data: &mut [BlockBasedImage],\n    reconstruct_info: &mut ReconstructionInfo,\n) -> Result<()> {\n    // track to see how far we got in progressive encoding in case of truncated images, however this\n    // was never actually implemented in the original C++ code\n    reconstruct_info.max_sah = max(reconstruct_info.max_sah, max(jf.cs_sal, jf.cs_sah));\n\n    let mut bit_reader = BitReader::new(reader);\n\n    // init variables for decoding\n    let mut state = JpegPositionState::new(jf, 0);\n\n    // JPEG imagedata decoding routines\n    let mut sta = JpegDecodeStatus::DecodeInProgress;\n    while sta != JpegDecodeStatus::ScanCompleted {\n        // decoding for interleaved data\n        state.reset_rstw(&jf); // restart wait counter\n\n        if jf.cs_to == 0 {\n            if jf.cs_sah == 0 {\n                return err_exit_code(\n                    ExitCode::UnsupportedJpeg,\n                    \"progress can't have two DC first stages\",\n                )\n                .context();\n            }\n\n            // only need DC\n            jf.verify_huffman_table(true, false).context()?;\n\n            while sta == JpegDecodeStatus::DecodeInProgress {\n                let current_block = image_data[state.get_cmp()].get_block_mut(state.get_dpos());\n\n                // ---> progressive DC encoding <---\n\n                // ---> succesive approximation later stage <---\n                let value = bit_reader.read(1)? as i16;\n\n                current_block.set_transposed_from_zigzag(\n                    0,\n                    current_block\n                        .get_transposed_from_zigzag(0)\n                        .wrapping_add(value << jf.cs_sal),\n                );\n\n                sta = state.next_mcu_pos(jf);\n            }\n        } else {\n            // ---> progressive AC encoding <---\n\n            if jf.cs_from == 0 || jf.cs_to >= 64 || jf.cs_from >= jf.cs_to {\n                return err_exit_code(\n                    ExitCode::UnsupportedJpeg,\n                    format!(\n                        \"progressive encoding range was invalid {0} to {1}\",\n                        jf.cs_from, jf.cs_to\n                    ),\n                );\n            }\n\n            // only need AC\n            jf.verify_huffman_table(false, true).context()?;\n\n            if jf.cs_sah == 0 {\n                if jf.cs_cmpc != 1 {\n                    return err_exit_code(\n                        ExitCode::UnsupportedJpeg,\n                        \"Progressive AC encoding cannot be interleaved\",\n                    );\n                }\n\n                // ---> succesive approximation first stage <---\n                let mut block = [0; 64];\n\n                while sta == JpegDecodeStatus::DecodeInProgress {\n                    let current_block = image_data[state.get_cmp()].get_block_mut(state.get_dpos());\n\n                    if state.eobrun == 0 {\n                        // only need to do something if we are not in a zero-block run\n                        let eob = decode_ac_prg_fs(\n                            &mut bit_reader,\n                            jf.get_huff_ac_tree(state.get_cmp()),\n                            &mut block,\n                            &mut state,\n                            jf.cs_from,\n                            jf.cs_to,\n                        )\n                        .context()?;\n\n                        state\n                            .check_optimal_eobrun(\n                                eob == jf.cs_from,\n                                jf.get_huff_ac_codes(state.get_cmp()),\n                            )\n                            .context()?;\n\n                        for bpos in jf.cs_from..eob {\n                            current_block.set_transposed_from_zigzag(\n                                usize::from(bpos),\n                                block[usize::from(bpos)] << jf.cs_sal,\n                            );\n                        }\n                    }\n\n                    sta = state.skip_eobrun(&jf).context()?;\n\n                    // proceed only if no error encountered\n                    if sta == JpegDecodeStatus::DecodeInProgress {\n                        sta = state.next_mcu_pos(jf);\n                    }\n                }\n            } else {\n                // ---> succesive approximation later stage <---\n\n                let mut block = [0; 64];\n\n                while sta == JpegDecodeStatus::DecodeInProgress {\n                    let current_block = image_data[state.get_cmp()].get_block_mut(state.get_dpos());\n\n                    for bpos in jf.cs_from..jf.cs_to + 1 {\n                        block[usize::from(bpos)] =\n                            current_block.get_transposed_from_zigzag(usize::from(bpos));\n                    }\n\n                    if state.eobrun == 0 {\n                        // decode block (long routine)\n                        let eob = decode_ac_prg_sa(\n                            &mut bit_reader,\n                            jf.get_huff_ac_tree(state.get_cmp()),\n                            &mut block,\n                            &mut state,\n                            jf.cs_from,\n                            jf.cs_to,\n                        )\n                        .context()?;\n\n                        state\n                            .check_optimal_eobrun(\n                                eob == jf.cs_from,\n                                jf.get_huff_ac_codes(state.get_cmp()),\n                            )\n                            .context()?;\n                    } else {\n                        // decode zero run block (short routine)\n                        decode_eobrun_sa(\n                            &mut bit_reader,\n                            &mut block,\n                            &mut state,\n                            jf.cs_from,\n                            jf.cs_to,\n                        )\n                        .context()?;\n                    }\n\n                    // copy back to colldata\n                    for bpos in jf.cs_from..jf.cs_to + 1 {\n                        current_block.set_transposed_from_zigzag(\n                            usize::from(bpos),\n                            current_block\n                                .get_transposed_from_zigzag(usize::from(bpos))\n                                .wrapping_add(block[usize::from(bpos)] << jf.cs_sal),\n                        );\n                    }\n\n                    sta = state.next_mcu_pos(jf);\n                }\n            }\n        }\n\n        // if we saw a pad bit at the end of the block, then remember whether they were 1s or 0s. This\n        // will be used later on to reconstruct the padding\n        bit_reader\n            .read_and_verify_fill_bits(&mut reconstruct_info.pad_bit)\n            .context()?;\n\n        // verify that we got the right RST code here since the above should do 1 mcu.\n        // If we didn't then we won't re-encode the file binary identical so there's no point in continuing\n        if sta == JpegDecodeStatus::RestartIntervalExpired {\n            bit_reader.verify_reset_code().context()?;\n\n            sta = JpegDecodeStatus::DecodeInProgress;\n        }\n    }\n\n    Ok(())\n}\n\n/// reads an entire interval until the RST code\nfn decode_baseline_rst<R: BufRead + Seek>(\n    state: &mut JpegPositionState,\n    bit_reader: &mut BitReader<R>,\n    image_data: &mut [BlockBasedImage],\n    do_handoff: &mut bool,\n    jpeg_header: &JpegHeader,\n    reconstruct_info: &mut ReconstructionInfo,\n    partitions: &mut Vec<(u64, RestartSegmentCodingInfo)>,\n) -> Result<JpegDecodeStatus> {\n    // should have both AC and DC components\n    jpeg_header.verify_huffman_table(true, true).context()?;\n\n    let mut sta = JpegDecodeStatus::DecodeInProgress;\n    let mut lastdc = [0i16; 4]; // (re)set last DCs for diff coding\n\n    while sta == JpegDecodeStatus::DecodeInProgress {\n        if *do_handoff {\n            let (bits_already_read, byte_being_read) = bit_reader.overhang();\n\n            partitions.push((\n                bit_reader.stream_position(),\n                RestartSegmentCodingInfo::new(\n                    byte_being_read,\n                    bits_already_read,\n                    lastdc,\n                    state.get_mcu(),\n                    &jpeg_header,\n                ),\n            ));\n\n            *do_handoff = false;\n        }\n\n        if !bit_reader.is_eof() {\n            // record the max block read\n            reconstruct_info.max_dpos[state.get_cmp()] =\n                cmp::max(state.get_dpos(), reconstruct_info.max_dpos[state.get_cmp()]);\n        }\n\n        // decode block (throws on error)\n        let mut block = [0i16; 64];\n        let eob = decode_block_seq(\n            bit_reader,\n            &jpeg_header.get_huff_dc_tree(state.get_cmp()),\n            &jpeg_header.get_huff_ac_tree(state.get_cmp()),\n            &mut block,\n        )?;\n\n        if eob > 1 && (block[eob - 1] == 0) {\n            return err_exit_code(\n                ExitCode::UnsupportedJpeg,\n                \"cannot encode image with eob after last 0\",\n            );\n        }\n\n        // fix dc\n        block[0] = block[0].wrapping_add(lastdc[state.get_cmp()]);\n        lastdc[state.get_cmp()] = block[0];\n\n        // prepare and set transposed raster block from zigzagged\n        let block_tr = AlignedBlock::zigzag_to_transposed(block);\n\n        image_data[state.get_cmp()].set_block_data(state.get_dpos(), block_tr);\n\n        // see if here is a good position to do a handoff (has to be aligned between MCU rows since we can't split any finer)\n        let old_mcu = state.get_mcu();\n        sta = state.next_mcu_pos(&jpeg_header);\n\n        if state.get_mcu() % jpeg_header.mcuh == 0 && old_mcu != state.get_mcu() {\n            *do_handoff = true;\n        }\n\n        if bit_reader.is_eof() {\n            sta = JpegDecodeStatus::ScanCompleted;\n            reconstruct_info.early_eof_encountered = true;\n        }\n    }\n\n    return Ok(sta);\n}\n\n/// <summary>\n/// sequential block decoding routine\n/// </summary>\n#[inline(never)]\npub(crate) fn decode_block_seq<R: BufRead>(\n    bit_reader: &mut BitReader<R>,\n    dctree: &HuffTree,\n    actree: &HuffTree,\n    block: &mut [i16; 64],\n) -> Result<usize> {\n    let mut eob = 64;\n\n    // decode dc\n    block[0] = read_dc(bit_reader, dctree)?;\n\n    let mut eof_fixup = false;\n\n    // decode ac\n    let mut bpos: usize = 1;\n    while bpos < 64 {\n        // decode next\n        if let Some((z, coef)) = read_coef(bit_reader, actree)? {\n            if (z + bpos) >= 64 {\n                eof_fixup = true;\n                break;\n            }\n\n            // no need to write the zeros since we are already zero initialized\n            bpos += z;\n\n            block[bpos] = coef;\n            bpos += 1;\n        } else {\n            // EOB\n            eob = bpos;\n            break;\n        }\n    }\n\n    // if we hit EOF then the bitreader will just start returning long strings of 0s, so handle that. If this happenes\n    // outside of that case, then it's a JPEG that we cannot recode successfully\n    if eof_fixup {\n        if !bit_reader.is_eof() {\n            return err_exit_code(\n                ExitCode::UnsupportedJpeg,\n                \"If 0run is longer than the block must be truncated\",\n            );\n        }\n\n        while bpos < eob {\n            block[bpos] = 0;\n            bpos += 1;\n        }\n\n        if eob > 0 {\n            block[eob - 1] = 1; // set the value to something matching the EOB\n        }\n    }\n\n    // return position of eob\n    return Ok(eob);\n}\n\n/// Reads and decodes next Huffman code from BitReader using the provided tree\n#[inline(always)]\nfn next_huff_code<R: BufRead>(bit_reader: &mut BitReader<R>, ctree: &HuffTree) -> Result<u8> {\n    let mut node: u16 = 0;\n\n    while node < 256 {\n        node = ctree.node[usize::from(node)][usize::from(bit_reader.read(1)?)];\n    }\n\n    if node == 0xffff {\n        err_exit_code(ExitCode::UnsupportedJpeg, \"illegal Huffman code detected\")\n    } else {\n        Ok((node - 256) as u8)\n    }\n}\n\nfn read_dc<R: BufRead>(bit_reader: &mut BitReader<R>, tree: &HuffTree) -> Result<i16> {\n    let (z, coef) = read_coef(bit_reader, tree)?.unwrap_or((0, 0));\n    if z != 0 {\n        err_exit_code(\n            ExitCode::UnsupportedJpeg,\n            \"not expecting non-zero run in DC coefficient\",\n        )\n    } else {\n        Ok(coef)\n    }\n}\n\n#[inline(always)]\nfn read_coef<R: BufRead>(\n    bit_reader: &mut BitReader<R>,\n    tree: &HuffTree,\n) -> Result<Option<(usize, i16)>> {\n    // if the code we found is smaller or equal to the number of bits left, take the shortcut\n    let hc;\n\n    loop {\n        // peek ahead to see if we can decode the symbol immediately\n        // given what has already been read into the bitreader\n        let (peek_value, peek_len) = bit_reader.peek();\n\n        // use lookup table to figure out the first code in this byte and how long it is\n        let (code, code_len) = tree.peek_code[peek_value as usize];\n\n        if u32::from(code_len) <= peek_len {\n            // found code directly, so advance by the number of bits immediately\n            hc = code;\n            bit_reader.advance(u32::from(code_len));\n            break;\n        } else if peek_len < 8 {\n            // peek code works with up to 8 bits at a time. If we had less\n            // than this, then we need to read more bits into the bitreader\n            bit_reader.fill_register(8)?;\n        } else {\n            // take slow path since we have a code that is bigger than 8 bits (but pretty rare)\n            hc = next_huff_code(bit_reader, tree)?;\n            break;\n        }\n    }\n\n    // analyse code\n    if hc != 0 {\n        let z = usize::from(lbits(hc, 4));\n        let literal_bits = rbits(hc, 4);\n\n        let value = bit_reader.read(u32::from(literal_bits))?;\n        Ok(Some((z, devli(literal_bits, value))))\n    } else {\n        Ok(None)\n    }\n}\n\n/// progressive AC decoding (first pass)\nfn decode_ac_prg_fs<R: BufRead>(\n    bit_reader: &mut BitReader<R>,\n    actree: &HuffTree,\n    block: &mut [i16; 64],\n    state: &mut JpegPositionState,\n    from: u8,\n    to: u8,\n) -> Result<u8> {\n    debug_assert!(state.eobrun == 0);\n\n    // decode ac\n    let mut bpos = from;\n    while bpos <= to {\n        // decode next\n        let hc = next_huff_code(bit_reader, actree)?;\n\n        let l = lbits(hc, 4);\n        let r = rbits(hc, 4);\n\n        // check if code is not an EOB or EOB run\n        if (l == 15) || (r > 0) {\n            // decode run/level combination\n            let mut z = l;\n            let s = r;\n            let n = bit_reader.read(u32::from(s))?;\n            if (z + bpos) > to {\n                return err_exit_code(ExitCode::UnsupportedJpeg, \"run is too long\");\n            }\n\n            while z > 0 {\n                // write zeroes\n                block[usize::from(bpos)] = 0;\n                z -= 1;\n                bpos += 1;\n            }\n            block[usize::from(bpos)] = devli(s, n); // decode cvli\n            bpos += 1;\n        } else {\n            // decode eobrun\n            let s = l;\n            let n = bit_reader.read(u32::from(s))?;\n            state.eobrun = decode_eobrun_bits(s, n);\n\n            state.eobrun -= 1; // decrement eobrun ( for this one )\n\n            break;\n        }\n    }\n\n    // return position of eob\n    return Ok(bpos);\n}\n\n/// progressive AC SA decoding routine\nfn decode_ac_prg_sa<R: BufRead>(\n    bit_reader: &mut BitReader<R>,\n    actree: &HuffTree,\n    block: &mut [i16; 64],\n    state: &mut JpegPositionState,\n    from: u8,\n    to: u8,\n) -> Result<u8> {\n    debug_assert!(state.eobrun == 0);\n\n    let mut bpos = from;\n    let mut eob = to;\n\n    // decode AC succesive approximation bits\n    while bpos <= to {\n        // decode next\n        let hc = next_huff_code(bit_reader, actree)?;\n\n        let l = lbits(hc, 4);\n        let r = rbits(hc, 4);\n\n        // check if code is not an EOB or EOB run\n        if (l == 15) || (r > 0) {\n            // decode run/level combination\n            let mut z = l;\n            let s = r;\n            let v;\n\n            if s == 0 {\n                v = 0;\n            } else if s == 1 {\n                let n = bit_reader.read(1)?;\n                v = if n == 0 { -1 } else { 1 }; // fast decode vli\n            } else {\n                return err_exit_code(ExitCode::UnsupportedJpeg, \"decoding error\").context();\n            }\n\n            // write zeroes / write correction bits\n            loop {\n                if block[usize::from(bpos)] == 0 {\n                    // skip zeroes / write value\n                    if z > 0 {\n                        z -= 1;\n                    } else {\n                        block[usize::from(bpos)] = v;\n                        bpos += 1;\n                        break;\n                    }\n                } else {\n                    // read correction bit\n                    let n = bit_reader.read(1)? as i16;\n                    block[usize::from(bpos)] = if block[usize::from(bpos)] > 0 { n } else { -n };\n                }\n\n                if bpos >= to {\n                    return err_exit_code(ExitCode::UnsupportedJpeg, \"decoding error\").context();\n                }\n\n                bpos += 1;\n            }\n        } else {\n            // decode eobrun\n            eob = bpos;\n            let s = l;\n            let n = bit_reader.read(u32::from(s))?;\n            state.eobrun = decode_eobrun_bits(s, n);\n\n            // since we hit EOB, the rest can be done with the zero block decoder\n            decode_eobrun_sa(bit_reader, block, state, bpos, to)?;\n\n            break;\n        }\n    }\n\n    return Ok(eob);\n}\n\n/// fast eobrun decoding routine for succesive approximation when the entire block is zero\nfn decode_eobrun_sa<R: BufRead>(\n    bit_reader: &mut BitReader<R>,\n    block: &mut [i16; 64],\n    state: &mut JpegPositionState,\n    from: u8,\n    to: u8,\n) -> Result<()> {\n    debug_assert!(state.eobrun > 0);\n\n    for bpos in usize::from(from)..usize::from(to + 1) {\n        if block[bpos] != 0 {\n            let n = bit_reader.read(1)? as i16;\n            block[bpos] = if block[bpos] > 0 { n } else { -n };\n        }\n    }\n\n    // decrement eobrun\n    state.eobrun -= 1;\n\n    Ok(())\n}\n\n/// decoding for decoding eobrun lengths. The encoding chops off the most significant\n/// bit since it is always 1, so we need to add it back.\nfn decode_eobrun_bits(s: u8, n: u16) -> u16 {\n    n + (1 << s)\n}\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n\n    use crate::{\n        EnabledFeatures,\n        jpeg::jpeg_header::{JpegHeader, ReconstructionInfo},\n    };\n    use std::io::{BufRead, Seek};\n\n    #[test]\n    fn read_garbage_behavior_progressive() {\n        read_garbage_behavior(\"iphoneprogressive\");\n    }\n\n    #[test]\n    fn read_garbage_behavior_baseline() {\n        read_garbage_behavior(\"iphone\");\n    }\n\n    /// reads a JPEG file and verifies that the garbage data is handled correctly.\n    fn read_garbage_behavior(filename: &str) {\n        let mut file = read_file(filename, \".jpg\");\n        let mut enabled_features = crate::EnabledFeatures::compat_lepton_scalar_read();\n\n        let mut cursor = std::io::Cursor::new(&file);\n        let (rinfo, _jh) = read_jpeg(&mut cursor, &enabled_features);\n\n        assert_eq!(\n            &rinfo.garbage_data[..],\n            [0xff, 0xd9],\n            \"Expected garbage data to match what was written\"\n        );\n\n        // now add some garbage data to the end of the file\n        file.extend_from_slice(b\"hi\"); // EOI + some garbage\n\n        let mut cursor = std::io::Cursor::new(&file);\n        let (rinfo, _jh) = read_jpeg(&mut cursor, &enabled_features);\n\n        assert_eq!(\n            &rinfo.garbage_data[..],\n            [0xff, 0xd9, b'h', b'i'],\n            \"Expected garbage data to match what was written\"\n        );\n\n        enabled_features.stop_reading_at_eoi = true;\n        let mut cursor = std::io::Cursor::new(&file);\n        let (rinfo, _jh) = read_jpeg(&mut cursor, &enabled_features);\n\n        assert_eq!(cursor.position(), file.len() as u64 - 2);\n\n        assert_eq!(\n            &rinfo.garbage_data[..],\n            [0xff, 0xd9],\n            \"Expected garbage data to match what was written when stop_reading_at_eoi is true\"\n        );\n    }\n\n    /// test function to read a JPEG file and returns the reconstruction info and JPEG header\n    fn read_jpeg<R: BufRead + Seek>(\n        reader: &mut R,\n        enabled_features: &EnabledFeatures,\n    ) -> (ReconstructionInfo, JpegHeader) {\n        let mut jpeg_header = JpegHeader::default();\n        let mut rinfo = ReconstructionInfo::default();\n\n        let mut headers = Vec::new();\n\n        let (_image_data, _partitions, _end_scan_position) = read_jpeg_file(\n            reader,\n            &mut jpeg_header,\n            &mut rinfo,\n            &enabled_features,\n            |header, raw_header| {\n                headers.push((header.clone(), raw_header.to_vec()));\n            },\n        )\n        .unwrap();\n\n        (rinfo, jpeg_header)\n    }\n\n    #[test]\n    fn test_benchmark_read_block() {\n        let mut f = benchmarks::benchmark_read_block();\n        for _ in 0..10 {\n            f();\n        }\n    }\n\n    #[test]\n    fn test_benchmark_read_jpeg() {\n        let mut f = benchmarks::benchmark_read_jpeg();\n        for _ in 0..10 {\n            f();\n        }\n    }\n}\n\n#[cfg(any(test, feature = \"micro_benchmark\"))]\npub mod benchmarks {\n    use std::io::Cursor;\n\n    use crate::{\n        EnabledFeatures,\n        helpers::read_file,\n        jpeg::{\n            bit_reader::BitReader,\n            bit_writer::BitWriter,\n            block_based_image::AlignedBlock,\n            jpeg_header::{\n                HuffTree, JpegHeader, ReconstructionInfo, generate_huff_table_from_distribution,\n            },\n            jpeg_read::{decode_block_seq, read_jpeg_file},\n            jpeg_write::encode_block_seq,\n        },\n    };\n\n    /// reads the jpeg file from the test data and returns a closure that reads\n    /// the jpeg header from it. Used for micro-benchmarking the jpeg header read performance.\n    #[inline(never)]\n    pub fn benchmark_read_jpeg() -> Box<dyn FnMut()> {\n        let file = read_file(\"android\", \".jpg\");\n\n        Box::new(move || {\n            use std::hint::black_box;\n\n            let mut reader = std::io::Cursor::new(&file);\n            let enabled_features = EnabledFeatures::compat_lepton_vector_write();\n\n            let mut jpeg_header = JpegHeader::default();\n            let mut rinfo = ReconstructionInfo::default();\n\n            let (image_data, partitions, end_scan) = read_jpeg_file(\n                &mut reader,\n                &mut jpeg_header,\n                &mut rinfo,\n                &enabled_features,\n                |_, _| {},\n            )\n            .unwrap();\n\n            black_box((image_data, partitions, end_scan));\n        })\n    }\n\n    /// tests performance of decoding a single block\n    #[inline(never)]\n    pub fn benchmark_read_block() -> Box<dyn FnMut()> {\n        // create a weird distribution to test the huffman encoding for corner cases\n        let mut dcdistribution = [0; 256];\n        for i in 0..256 {\n            dcdistribution[i] = 256 - i;\n        }\n        let dctbl = generate_huff_table_from_distribution(&dcdistribution);\n\n        let mut acdistribution = [0; 256];\n        for i in 0..256 {\n            acdistribution[i] = 1 + 256;\n        }\n        let actbl = generate_huff_table_from_distribution(&acdistribution);\n\n        let mut bitwriter = BitWriter::new(Vec::with_capacity(1024));\n\n        let mut block = AlignedBlock::default();\n        for i in 0..10 {\n            block.get_block_mut()[i] = i as i16 * 13;\n        }\n        for i in 30..50 {\n            block.get_block_mut()[i] = -(i as i16) * 7;\n        }\n        for i in 50..52 {\n            block.get_block_mut()[i] = i as i16 * 3;\n        }\n\n        encode_block_seq(&mut bitwriter, &dctbl, &actbl, &block);\n\n        let buffer = bitwriter.detach_buffer();\n\n        let dctree = HuffTree::construct_hufftree(&dctbl, true).unwrap();\n        let actree = HuffTree::construct_hufftree(&actbl, false).unwrap();\n\n        Box::new(move || {\n            use std::hint::black_box;\n\n            let mut bitreader = BitReader::new(Cursor::new(&buffer));\n\n            let mut outblock = AlignedBlock::default();\n            decode_block_seq(\n                &mut bitreader,\n                &dctree,\n                &actree,\n                &mut outblock.get_block_mut(),\n            )\n            .unwrap();\n\n            black_box(outblock);\n        })\n    }\n}\n"
  },
  {
    "path": "lib/src/jpeg/jpeg_write.rs",
    "content": "/*---------------------------------------------------------------------------------------------\n *  Copyright (c) Microsoft Corporation. All rights reserved.\n *  Licensed under the Apache License, Version 2.0. See LICENSE.txt in the project root for license information.\n *  This software incorporates material from third parties. See NOTICE.txt for details.\n *--------------------------------------------------------------------------------------------*/\n\n/*\nCopyright (c) 2006...2016, Matthias Stirner and HTW Aalen University\nAll rights reserved.\n\nRedistribution and use in source and binary forms, with or without\nmodification, are permitted provided that the following conditions are\nmet:\n\n1. Redistributions of source code must retain the above copyright\nnotice, this list of conditions and the following disclaimer.\n\n2. Redistributions in binary form must reproduce the above copyright\nnotice, this list of conditions and the following disclaimer in the\ndocumentation and/or other materials provided with the distribution.\n\nTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS\nIS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED\nTO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A\nPARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT\nHOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,\nSPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED\nTO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR\nPROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF\nLIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING\nNEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\nSOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n*/\n\nuse bytemuck::{cast, cast_ref};\nuse wide::{CmpEq, i16x16};\n\nuse crate::consts::{JpegDecodeStatus, JpegType};\nuse crate::helpers::u16_bit_length;\nuse crate::lepton_error::{AddContext, ExitCode, err_exit_code};\n\nuse crate::Result;\n\nuse super::bit_writer::BitWriter;\nuse super::block_based_image::{AlignedBlock, BlockBasedImage};\nuse super::jpeg_code;\nuse super::jpeg_header::{HuffCodes, JpegHeader, ReconstructionInfo, RestartSegmentCodingInfo};\nuse super::jpeg_position_state::JpegPositionState;\nuse super::row_spec::RowSpec;\n\npub struct JpegIncrementalWriter<'a> {\n    last_dc: [i16; 4],\n    huffw: BitWriter,\n    reconstruction_info: &'a ReconstructionInfo,\n    jpeg_header: &'a JpegHeader,\n    capacity: usize,\n    current_scan_index: usize,\n}\n\nimpl<'a> JpegIncrementalWriter<'a> {\n    pub fn new(\n        capacity: usize,\n        reconstruction_info: &'a ReconstructionInfo,\n        rinfo: Option<&RestartSegmentCodingInfo>,\n        jpeg_header: &'a JpegHeader,\n        current_scan_index: usize,\n    ) -> JpegIncrementalWriter<'a> {\n        let mut huffw = BitWriter::new(Vec::with_capacity(capacity));\n\n        if let Some(rinfo) = rinfo {\n            huffw.reset_from_overhang_byte_and_num_bits(\n                rinfo.overhang_byte,\n                u32::from(rinfo.num_overhang_bits),\n            );\n        }\n\n        JpegIncrementalWriter {\n            last_dc: if let Some(r) = rinfo {\n                r.last_dc\n            } else {\n                [0i16; 4]\n            },\n            huffw,\n            jpeg_header,\n            reconstruction_info,\n            capacity,\n            current_scan_index,\n        }\n    }\n\n    pub fn amount_buffered(&self) -> usize {\n        self.huffw.amount_buffered()\n    }\n\n    pub fn process_row(\n        &mut self,\n        cur_row: &RowSpec,\n        image_data: &[BlockBasedImage],\n    ) -> Result<bool> {\n        if cur_row.last_row_to_complete_mcu {\n            self.huffw.ensure_space(self.capacity);\n\n            return Ok(recode_one_mcu_row(\n                &mut self.huffw,\n                cur_row.mcu_row_index * self.jpeg_header.mcuh.get(),\n                &mut self.last_dc,\n                image_data,\n                self.jpeg_header,\n                self.reconstruction_info,\n                self.current_scan_index,\n            )\n            .context()?);\n        }\n        Ok(false)\n    }\n\n    pub fn detach_buffer(&mut self) -> Vec<u8> {\n        self.huffw.detach_buffer()\n    }\n}\n\n/// writes an entire scan vs only a range of rows as above.\n/// supports progressive encoding whereas the row range version does not\npub fn jpeg_write_entire_scan(\n    image_data: &[BlockBasedImage],\n    jpeg_header: &JpegHeader,\n    rinfo: &ReconstructionInfo,\n    current_scan_index: usize,\n) -> Result<Vec<u8>> {\n    let mut inc_write =\n        JpegIncrementalWriter::new(128 * 1024, rinfo, None, jpeg_header, current_scan_index);\n\n    let max_coded_heights = rinfo.truncate_components.get_max_coded_heights();\n\n    let mut decode_index = 0;\n    loop {\n        let cur_row = RowSpec::get_row_spec_from_index(\n            decode_index,\n            image_data,\n            jpeg_header.mcuv.get(),\n            &max_coded_heights,\n        );\n\n        decode_index += 1;\n\n        if cur_row.done {\n            break;\n        }\n\n        if cur_row.skip {\n            continue;\n        }\n\n        if inc_write.process_row(&cur_row, image_data)? {\n            break;\n        }\n    }\n\n    Ok(inc_write.detach_buffer())\n}\n\n#[inline(never)]\nfn recode_one_mcu_row(\n    huffw: &mut BitWriter,\n    mcu: u32,\n    lastdc: &mut [i16],\n    framebuffer: &[BlockBasedImage],\n    jf: &JpegHeader,\n    rinfo: &ReconstructionInfo,\n    current_scan_index: usize,\n) -> Result<bool> {\n    let mut state = JpegPositionState::new(jf, mcu);\n\n    let mut cumulative_reset_markers = state.get_cumulative_reset_markers(jf);\n\n    let mut end_of_row = false;\n    let mut correction_bits = Vec::new();\n\n    // JPEG imagedata encoding routines\n    while !end_of_row {\n        // (re)set status\n        let mut sta = JpegDecodeStatus::DecodeInProgress;\n\n        // ---> sequential interleaved encoding <---\n        while sta == JpegDecodeStatus::DecodeInProgress {\n            let current_block = framebuffer[state.get_cmp()].get_block(state.get_dpos());\n\n            let old_mcu = state.get_mcu();\n\n            if jf.jpeg_type == JpegType::Sequential {\n                // unzigzag\n                let mut block = current_block.zigzag_from_transposed();\n\n                // diff coding for dc\n                let dc = block.get_block()[0];\n                block.get_block_mut()[0] -= lastdc[state.get_cmp()];\n                lastdc[state.get_cmp()] = dc;\n\n                // encode block\n                encode_block_seq(\n                    huffw,\n                    jf.get_huff_dc_codes(state.get_cmp()),\n                    jf.get_huff_ac_codes(state.get_cmp()),\n                    &block,\n                );\n\n                sta = state.next_mcu_pos(&jf);\n            } else if jf.cs_to == 0 {\n                // ---> progressive DC encoding <---\n                if jf.cs_sah == 0 {\n                    // ---> succesive approximation first stage <---\n\n                    // diff coding & bitshifting for dc\n                    let tmp = current_block.get_transposed_from_zigzag(0) >> jf.cs_sal;\n                    let v = tmp - lastdc[state.get_cmp()];\n                    lastdc[state.get_cmp()] = tmp;\n\n                    // encode dc\n                    write_coef(\n                        huffw,\n                        v < 0,\n                        v.unsigned_abs(),\n                        0,\n                        jf.get_huff_dc_codes(state.get_cmp()),\n                    );\n                } else {\n                    // ---> succesive approximation later stage <---\n\n                    // fetch bit from current bitplane\n                    huffw.write(\n                        ((current_block.get_transposed_from_zigzag(0) >> jf.cs_sal) & 1) as u32,\n                        1,\n                    );\n                }\n\n                sta = state.next_mcu_pos(jf);\n            } else {\n                // ---> progressive AC encoding <---\n\n                // copy from coefficients we need and shift right by cs_sal\n                let mut block = [0i16; 64];\n                for bpos in jf.cs_from..jf.cs_to + 1 {\n                    block[usize::from(bpos)] = div_pow2(\n                        current_block.get_transposed_from_zigzag(usize::from(bpos)),\n                        jf.cs_sal,\n                    );\n                }\n\n                if jf.cs_sah == 0 {\n                    // ---> succesive approximation first stage <---\n\n                    // encode block\n                    encode_ac_prg_fs(\n                        huffw,\n                        jf.get_huff_ac_codes(state.get_cmp()),\n                        &block,\n                        &mut state,\n                        jf.cs_from,\n                        jf.cs_to,\n                    )\n                    .context()?;\n\n                    sta = state.next_mcu_pos(jf);\n\n                    // encode remaining eobrun (iff end of mcu or scan)\n                    if sta != JpegDecodeStatus::DecodeInProgress {\n                        encode_eobrun(huffw, jf.get_huff_ac_codes(state.get_cmp()), &mut state);\n                    }\n                } else {\n                    // ---> succesive approximation later stage <---\n\n                    // encode block\n                    encode_ac_prg_sa(\n                        huffw,\n                        jf.get_huff_ac_codes(state.get_cmp()),\n                        &block,\n                        &mut state,\n                        jf.cs_from,\n                        jf.cs_to,\n                        &mut correction_bits,\n                    )\n                    .context()?;\n\n                    sta = state.next_mcu_pos(jf);\n\n                    // encode remaining eobrun and correction bits (iff end of mcu or scan)\n                    if sta != JpegDecodeStatus::DecodeInProgress {\n                        encode_eobrun(huffw, jf.get_huff_ac_codes(state.get_cmp()), &mut state);\n\n                        // encode remaining correction bits\n                        encode_crbits(huffw, &mut correction_bits);\n                    }\n                }\n            }\n\n            if old_mcu != state.get_mcu() && state.get_mcu() % jf.mcuh == 0 {\n                end_of_row = true;\n                if sta == JpegDecodeStatus::DecodeInProgress {\n                    // completed only MCU aligned row, not reset interval so don't emit anything special\n                    return Ok(false);\n                }\n            }\n        }\n\n        // pad huffman writer\n        huffw.pad(rinfo.pad_bit.unwrap_or(0));\n\n        assert!(\n            huffw.has_no_remainder(),\n            \"shouldnt have a remainder after padding\"\n        );\n\n        // evaluate status\n        if sta == JpegDecodeStatus::ScanCompleted {\n            return Ok(true); // leave decoding loop, everything is done here\n        } else {\n            assert!(sta == JpegDecodeStatus::RestartIntervalExpired);\n\n            // status 1 means restart\n            if jf.rsti > 0 {\n                if rinfo.rst_cnt.len() == 0\n                    || (!rinfo.rst_cnt_set)\n                    || cumulative_reset_markers < rinfo.rst_cnt[current_scan_index]\n                {\n                    let rst = jpeg_code::RST0 + (cumulative_reset_markers & 7) as u8;\n\n                    huffw.write_byte_unescaped(0xFF);\n                    huffw.write_byte_unescaped(rst);\n                    cumulative_reset_markers += 1;\n                }\n\n                // (re)set rst wait counter\n                state.reset_rstw(jf);\n\n                // (re)set last DCs for diff coding\n                for i in 0..lastdc.len() {\n                    lastdc[i] = 0;\n                }\n            }\n        }\n    }\n\n    Ok(false)\n}\n\n#[inline(never)]\npub(crate) fn encode_block_seq(\n    huffw: &mut BitWriter,\n    dctbl: &HuffCodes,\n    actbl: &HuffCodes,\n    block: &AlignedBlock,\n) {\n    // using SIMD instructions, construct a 64 bit mask of all\n    // the non-zero coefficients in the block. This can be used\n    // to efficiently skip zero blocks using trailing zero scan.\n    let block_simd: &[i16x16; 4] = cast_ref(block.get_block());\n\n    let mut mask = (block_simd[0].simd_eq(i16x16::ZERO).to_bitmask() as u64)\n        | ((block_simd[1].simd_eq(i16x16::ZERO).to_bitmask() as u64) << 16)\n        | ((block_simd[2].simd_eq(i16x16::ZERO).to_bitmask() as u64) << 32)\n        | ((block_simd[3].simd_eq(i16x16::ZERO).to_bitmask() as u64) << 48);\n\n    // abs value of all coefficients. Super fast to calculate here\n    // for everything, even if it is zero and not needed.\n    let abs_value: [u16; 64] = cast(block_simd.map(|x| x.abs()));\n    let is_neg: [u16; 64] = cast(block_simd.map(|x| x >> 15));\n\n    // encode DC\n    // & 256 is bit faster all the bits are 1s and since it allows the optimizer\n    //   to convert << 8 (inside this function) to a single AND\n    write_coef(huffw, (is_neg[0] & 256) != 0, abs_value[0], 0, dctbl);\n\n    // flip the bits since cmp_eq returns 0xffff for zero coefficients\n    mask = !mask;\n\n    // already processed DC coefficient, so skip it\n    mask >>= 1;\n    let mut bpos = 1;\n\n    // encode ACs\n    while mask != 0 {\n        let mut zeros = mask.trailing_zeros();\n\n        if zeros > 15 {\n            // JPEG encoding only supports 15 zeros in a row. Most implementations\n            // write 0xf0 codes for 16 zeros in a row, but we don't need\n            // a special case since write_coef with a zero coefficient\n            // and a 0xf zero count will write the correct code.\n            zeros = 15;\n        }\n\n        bpos += zeros + 1;\n        mask >>= zeros + 1;\n\n        write_coef(\n            huffw,\n            (is_neg[(bpos - 1) as usize] & 256) != 0, // a bit faster since it allows the optimizer to convert << 8 (inside this function) to a single AND\n            abs_value[(bpos - 1) as usize],\n            zeros,\n            actbl,\n        );\n\n        if bpos >= 64 {\n            // if we get all 64 coefficients, we're done and don't need an EOB\n            return;\n        }\n    }\n\n    // write EOB since we didn't get all 64 coefficients\n    huffw.write(actbl.c_val[0x00].into(), actbl.c_len[0x00].into());\n}\n\n/// encodes a coefficient which is a huffman code specifying the size followed\n/// by the coefficient itself\n#[inline(always)]\nfn write_coef(huffw: &mut BitWriter, is_neg: bool, abs_coef: u16, z: u32, tbl: &HuffCodes) {\n    let s = 32 - u32::from(abs_coef).leading_zeros();\n\n    // compiler is smart enough to figure out that this will never be >= 256,\n    // so no bounds check\n    let hc = z << 4 | s;\n\n    // JPEG stores the coefficient with an implied sign bit, since once we know the\n    // number of bits, we can infer the sign.\n    //\n    // Eg, if the bitlength of the absolute value is 4,\n    //\n    // 0..7 are negative (corresponding to -15..-8)\n    // 8..15 are positive\n    //\n    // This is equivalent to absolute value XOR (1 << bitlength) - 1 if the number is negative, so\n    // what we do is store this adjustment in c_val_shift_s so that we don't need\n    // to calculate it separately.\n    //\n    // is_neg indicates whether we want the value with the bits set.\n    let val = tbl.c_val_shift_s[(hc | ((is_neg as u32) << 8)) as usize] ^ u32::from(abs_coef);\n\n    let new_bits = u32::from(tbl.c_len_plus_s[hc as usize]);\n\n    // write to huffman writer (combine hufmman code and coefficient bits into single write)\n    huffw.write(val, new_bits);\n}\n\n/// progressive AC encoding (first pass)\nfn encode_ac_prg_fs(\n    huffw: &mut BitWriter,\n    actbl: &HuffCodes,\n    block: &[i16; 64],\n    state: &mut JpegPositionState,\n    from: u8,\n    to: u8,\n) -> Result<()> {\n    // encode AC\n    let mut z = 0;\n    for bpos in from..to + 1 {\n        // if nonzero is encountered\n        let tmp = block[usize::from(bpos)];\n        if tmp != 0 {\n            // encode eobrun\n            encode_eobrun(huffw, actbl, state);\n            // write remaining zeroes\n            while z >= 16 {\n                huffw.write(actbl.c_val[0xF0].into(), actbl.c_len[0xF0].into());\n                z -= 16;\n            }\n\n            // vli encode\n            write_coef(huffw, tmp < 0, tmp.unsigned_abs(), z, actbl);\n\n            // reset zeroes\n            z = 0;\n        } else {\n            // increment zero counter\n            z += 1;\n        }\n    }\n\n    // check eob, increment eobrun if needed\n    if z > 0 {\n        if actbl.max_eob_run == 0 {\n            return err_exit_code(\n                ExitCode::UnsupportedJpeg,\n                \"there must be at least one EOB symbol run in the huffman table to encode EOBs\",\n            )\n            .context();\n        }\n\n        state.eobrun += 1;\n\n        // check eobrun, encode if needed\n        if state.eobrun == actbl.max_eob_run {\n            encode_eobrun(huffw, actbl, state);\n        }\n    }\n\n    Ok(())\n}\n\n/// progressive AC SA encoding subsequent pass\nfn encode_ac_prg_sa(\n    huffw: &mut BitWriter,\n    actbl: &HuffCodes,\n    block: &[i16; 64],\n    state: &mut JpegPositionState,\n    from: u8,\n    to: u8,\n    correction_bits: &mut Vec<u8>,\n) -> Result<()> {\n    // check if block contains any newly nonzero coefficients and find out position of eob\n    let mut eob = from;\n\n    {\n        let mut bpos = to;\n        while bpos >= from {\n            if (block[usize::from(bpos)] == 1) || (block[usize::from(bpos)] == -1) {\n                eob = bpos + 1;\n                break;\n            }\n            bpos -= 1;\n        }\n    }\n\n    // encode eobrun if needed\n    if (eob > from) && state.eobrun > 0 {\n        encode_eobrun(huffw, actbl, state);\n\n        encode_crbits(huffw, correction_bits);\n    }\n\n    // encode AC\n    let mut z = 0;\n    for bpos in from..eob {\n        let tmp = block[usize::from(bpos)];\n        // if zero is encountered\n        if tmp == 0 {\n            z += 1; // increment zero counter\n            if z == 16 {\n                // write zeroes if needed\n                huffw.write(actbl.c_val[0xF0].into(), actbl.c_len[0xF0].into());\n\n                encode_crbits(huffw, correction_bits);\n                z = 0;\n            }\n        }\n        // if nonzero is encountered\n        else if (tmp == 1) || (tmp == -1) {\n            // vli encode\n            write_coef(huffw, tmp < 0, tmp.unsigned_abs(), z, actbl);\n\n            // write correction bits\n            encode_crbits(huffw, correction_bits);\n            // reset zeroes\n            z = 0;\n        } else {\n            // store correction bits\n            let n = (block[usize::from(bpos)] & 0x1) as u8;\n            correction_bits.push(n);\n        }\n    }\n\n    // fast processing after eob\n    for bpos in eob..to + 1 {\n        if block[usize::from(bpos)] != 0 {\n            // store correction bits\n            let n = (block[usize::from(bpos)] & 0x1) as u8;\n            correction_bits.push(n);\n        }\n    }\n\n    // check eob, increment eobrun if needed\n    if eob <= to {\n        if actbl.max_eob_run == 0 {\n            return err_exit_code(\n                ExitCode::UnsupportedJpeg,\n                \"there must be at least one EOB symbol run in the huffman table to encode EOBs\",\n            )\n            .context();\n        }\n\n        state.eobrun += 1;\n\n        // check eobrun, encode if needed\n        if state.eobrun == actbl.max_eob_run {\n            encode_eobrun(huffw, actbl, state);\n\n            encode_crbits(huffw, correction_bits);\n        }\n    }\n\n    Ok(())\n}\n\n/// encodes the eob run which consists of a huffman code the high 4 bits specifying the log2 of the run\n/// followed by the number number encoded into the minimum number of bits\nfn encode_eobrun(huffw: &mut BitWriter, actbl: &HuffCodes, state: &mut JpegPositionState) {\n    if (state.eobrun) > 0 {\n        debug_assert!((state.eobrun) <= actbl.max_eob_run);\n\n        let mut s = u16_bit_length(state.eobrun);\n        s -= 1;\n\n        let n = encode_eobrun_bits(s, state.eobrun);\n        let hc = s << 4;\n        huffw.write(\n            actbl.c_val[usize::from(hc)].into(),\n            actbl.c_len[usize::from(hc)].into(),\n        );\n        huffw.write(u32::from(n), u32::from(s));\n        state.eobrun = 0;\n    }\n}\n\n/// encodes the correction bits, which are simply encoded as a vector of single bit values\nfn encode_crbits(huffw: &mut BitWriter, correction_bits: &mut Vec<u8>) {\n    for x in correction_bits.drain(..) {\n        huffw.write(u32::from(x), 1);\n    }\n}\n\n/// divide power of 2 rounding towards zero\nfn div_pow2(v: i16, p: u8) -> i16 {\n    (if v < 0 { v + ((1 << p) - 1) } else { v }) >> p\n}\n\n/// encoding for eobrun length. Chop off highest bit since we know it is always 1.\nfn encode_eobrun_bits(s: u8, v: u16) -> u16 {\n    v - (1 << s)\n}\n\n#[cfg(test)]\nmod tests {\n    use std::io::Cursor;\n\n    use super::*;\n\n    use crate::{\n        helpers::read_file,\n        jpeg::{\n            bit_reader::BitReader,\n            bit_writer::BitWriter,\n            block_based_image::AlignedBlock,\n            jpeg_header::{HuffTree, generate_huff_table_from_distribution},\n            jpeg_read::decode_block_seq,\n        },\n    };\n\n    /// roundtrips a block through the encoder and decoder and checks that the output matches the input\n    fn round_trip_block(block: &AlignedBlock, expected: &[u8]) {\n        let mut bitwriter = BitWriter::new(Vec::with_capacity(1024));\n\n        // create a weird distribution to test the huffman encoding for corner cases\n        let mut dcdistribution = [0; 256];\n        for i in 0..256 {\n            dcdistribution[i] = 256 - i;\n        }\n        let dctbl = generate_huff_table_from_distribution(&dcdistribution);\n\n        let mut acdistribution = [0; 256];\n        for i in 0..256 {\n            acdistribution[i] = 1 + 256;\n        }\n        let actbl = generate_huff_table_from_distribution(&acdistribution);\n\n        encode_block_seq(&mut bitwriter, &dctbl, &actbl, block);\n\n        bitwriter.pad(0);\n\n        let buf = bitwriter.detach_buffer();\n        assert_eq!(buf, expected);\n\n        let mut bitreader = BitReader::new(Cursor::new(&buf));\n\n        let mut block_decoded = [0i16; 64];\n        decode_block_seq(\n            &mut bitreader,\n            &HuffTree::construct_hufftree(&dctbl, true).unwrap(),\n            &HuffTree::construct_hufftree(&actbl, true).unwrap(),\n            &mut block_decoded,\n        )\n        .unwrap();\n\n        assert_eq!(&block_decoded, block.get_block());\n    }\n\n    #[test]\n    fn test_encode_block_seq() {\n        let mut block = AlignedBlock::default();\n        for i in 0..64 {\n            block.get_block_mut()[i] = (i as i16) - 32;\n        }\n\n        let expected = [\n            152, 252, 176, 37, 131, 44, 41, 97, 203, 18, 88, 178, 198, 150, 60, 178, 37, 147, 44,\n            169, 101, 203, 50, 89, 178, 206, 150, 126, 176, 107, 14, 177, 107, 30, 178, 107, 46,\n            179, 107, 56, 136, 17, 34, 40, 69, 128, 128, 47, 120, 250, 3, 0, 226, 48, 70, 136, 225,\n            31, 173, 26, 211, 173, 90, 215, 173, 154, 219, 173, 218, 223, 45, 9, 104, 203, 74, 90,\n            114, 212, 150, 172, 181, 165, 175, 45, 137, 108, 203, 106, 91, 114, 220, 150, 236, 183,\n            165, 190,\n        ];\n\n        round_trip_block(&block, &expected);\n    }\n\n    /// make sure we encode magnitudes correctly\n    #[test]\n    fn test_encode_block_magnitude() {\n        let mut block = AlignedBlock::default();\n        for i in 0..15 {\n            block.get_block_mut()[i] = (1u16 << i) as i16;\n        }\n        for i in 0..15 {\n            block.get_block_mut()[i + 20] = -((1u16 << i) as i16);\n        }\n\n        let expected = [\n            165, 1, 132, 102, 180, 75, 64, 138, 6, 248, 8, 16, 27, 208, 13, 120, 2, 122, 0, 75,\n            192, 4, 60, 0, 8, 224, 0, 109, 128, 1, 250, 1, 68, 94, 179, 203, 60, 137, 246, 247,\n            232, 15, 251, 207, 253, 119, 254, 121, 255, 0, 203, 191, 252, 59, 255, 0, 200, 223,\n            255, 0, 109, 127, 254, 0,\n        ];\n\n        round_trip_block(&block, &expected);\n    }\n\n    /// test encoding with gaps to test zero counting\n    #[test]\n    fn test_encode_block_zero_runs() {\n        let mut block = AlignedBlock::default();\n\n        for i in 0..10 {\n            block.get_block_mut()[i] = i as i16;\n        }\n        for i in 30..50 {\n            block.get_block_mut()[i] = -(i as i16);\n        }\n        for i in 50..52 {\n            block.get_block_mut()[i] = i as i16;\n        }\n\n        let expected = [\n            169, 223, 1, 128, 113, 24, 35, 68, 112, 143, 214, 141, 105, 167, 249, 12, 176, 8, 159,\n            34, 120, 137, 210, 39, 8, 155, 34, 104, 137, 146, 38, 8, 151, 34, 88, 137, 82, 37, 8,\n            147, 34, 72, 137, 18, 36, 8, 143, 34, 56, 139, 34, 44, 192, 0,\n        ];\n\n        round_trip_block(&block, &expected);\n    }\n\n    /// test encoding with gaps to test zero counting\n    #[test]\n    fn test_encode_block_long_zero_cnt() {\n        let mut block = AlignedBlock::default();\n\n        block.get_block_mut()[63] = 1;\n\n        let expected = [169, 79, 79, 79, 33];\n\n        round_trip_block(&block, &expected);\n    }\n\n    #[test]\n    fn test_encode_block_seq_zero() {\n        let block = AlignedBlock::default();\n\n        let expected = [168, 0];\n\n        round_trip_block(&block, &expected);\n    }\n\n    fn roundtrip_jpeg<R: std::io::BufRead + std::io::Seek>(\n        reader: &mut R,\n        enabled_features: &crate::EnabledFeatures,\n    ) -> Vec<u8> {\n        use crate::consts::*;\n        use crate::jpeg::jpeg_header::{JpegHeader, ReconstructionInfo};\n        use crate::jpeg::jpeg_read::read_jpeg_file;\n\n        let mut jpeg_header = JpegHeader::default();\n        let mut rinfo = ReconstructionInfo::default();\n\n        let mut headers = Vec::new();\n\n        let (image_data, partitions, end_scan_position) = read_jpeg_file(\n            reader,\n            &mut jpeg_header,\n            &mut rinfo,\n            &enabled_features,\n            |header, raw_header| {\n                headers.push((header.clone(), raw_header.to_vec()));\n            },\n        )\n        .unwrap();\n\n        let mut reconstructed = Vec::new();\n        reconstructed.extend_from_slice(&SOI);\n\n        if jpeg_header.is_single_scan() {\n            // sequential JPEG consists of a single header + scan\n            reconstructed.extend_from_slice(rinfo.raw_jpeg_header.as_slice());\n\n            let mut prev_offset = 0;\n            for (offset, coding_info) in partitions {\n                let mut r = jpeg_write_baseline_row_range(\n                    (offset - prev_offset) as usize,\n                    &coding_info,\n                    &image_data,\n                    &jpeg_header,\n                    &rinfo,\n                )\n                .unwrap();\n\n                reconstructed.append(&mut r);\n\n                prev_offset = offset;\n            }\n\n            assert_eq!(reconstructed.len(), end_scan_position as usize);\n\n            reconstructed.extend_from_slice(&EOI);\n        } else {\n            // progressive JPEG consists of header + scan, header + scan, etc\n            let mut scnc = 0;\n\n            for (jh, raw_header) in headers {\n                // progressive JPEG consists of headers + scan\n                reconstructed.extend_from_slice(&raw_header);\n\n                let scan = jpeg_write_entire_scan(&image_data, &jh, &rinfo, scnc).unwrap();\n\n                reconstructed.extend_from_slice(&scan);\n\n                // advance to next scan\n                scnc += 1;\n            }\n\n            reconstructed.extend_from_slice(&EOI);\n\n            // progressive includes EOI in the scan\n            assert_eq!(reconstructed.len(), end_scan_position as usize);\n        }\n\n        reconstructed\n    }\n\n    /// reads a JPEG file and writes it back out using the baseline encoder\n    /// to verify that the encoder and decoder exactly the same.\n    #[test]\n    fn roundtrip_baseline_jpeg() {\n        let file = read_file(\"iphone\", \".jpg\");\n        let enabled_features = crate::EnabledFeatures::compat_lepton_scalar_read();\n\n        let reconstructed = roundtrip_jpeg(&mut std::io::Cursor::new(&file), &enabled_features);\n\n        assert!(reconstructed == file);\n    }\n\n    /// reads a progressive JPEG file and writes it back out using the progressive encoder\n    /// to verify that the encoder and decoder exactly the same.\n    #[test]\n    fn roundtrip_progressive_jpeg() {\n        let file = read_file(\"iphoneprogressive\", \".jpg\");\n        let enabled_features = crate::EnabledFeatures::compat_lepton_scalar_read();\n\n        let reconstructed = roundtrip_jpeg(&mut std::io::Cursor::new(&file), &enabled_features);\n\n        assert!(reconstructed == file);\n    }\n\n    #[test]\n    fn test_benchmark_write_jpeg() {\n        let mut f = benchmarks::benchmark_write_jpeg();\n        for _ in 0..10 {\n            f();\n        }\n    }\n\n    #[test]\n    fn test_benchmark_write_block() {\n        let mut f = benchmarks::benchmark_write_block();\n        for _ in 0..10 {\n            f();\n        }\n    }\n}\n\n/// write a range of rows corresponding to the restart_info structure.\n/// Returns the encoded data as a buffer.\n///\n/// Only works with baseline non-progressive images.\n#[cfg(any(test, feature = \"micro_benchmark\"))]\nfn jpeg_write_baseline_row_range(\n    encoded_length: usize,\n    restart_info: &RestartSegmentCodingInfo,\n    image_data: &[BlockBasedImage],\n    jpeg_header: &JpegHeader,\n    rinfo: &ReconstructionInfo,\n) -> Result<Vec<u8>> {\n    let max_coded_heights: Vec<u32> = rinfo.truncate_components.get_max_coded_heights();\n\n    let mut writer =\n        JpegIncrementalWriter::new(encoded_length, rinfo, Some(restart_info), jpeg_header, 0);\n\n    let mut decode_index = 0;\n    loop {\n        let cur_row: RowSpec = RowSpec::get_row_spec_from_index(\n            decode_index,\n            image_data,\n            rinfo.truncate_components.mcu_count_vertical,\n            &max_coded_heights,\n        );\n\n        decode_index += 1;\n\n        if cur_row.done {\n            break;\n        }\n\n        if cur_row.skip {\n            continue;\n        }\n\n        if cur_row.luma_y < restart_info.luma_y_start {\n            continue;\n        }\n\n        if cur_row.luma_y > restart_info.luma_y_end {\n            break; // we're done here\n        }\n\n        writer.process_row(&cur_row, image_data).context()?;\n    }\n\n    Ok(writer.detach_buffer())\n}\n\n#[cfg(any(test, feature = \"micro_benchmark\"))]\npub mod benchmarks {\n    use std::mem;\n\n    use super::*;\n\n    use crate::{\n        EnabledFeatures,\n        helpers::read_file,\n        jpeg::{\n            bit_writer::BitWriter,\n            block_based_image::AlignedBlock,\n            jpeg_header::{JpegHeader, ReconstructionInfo, generate_huff_table_from_distribution},\n            jpeg_read::read_jpeg_file,\n        },\n    };\n\n    /// Benchmarks performance of encoding a single JPEG block\n    #[inline(never)]\n    pub fn benchmark_write_block() -> Box<dyn FnMut()> {\n        // create a weird distribution to test the huffman encoding for corner cases\n        let mut dcdistribution = [0; 256];\n        for i in 0..256 {\n            dcdistribution[i] = 256 - i;\n        }\n        let dctbl = generate_huff_table_from_distribution(&dcdistribution);\n\n        let mut acdistribution = [0; 256];\n        for i in 0..256 {\n            acdistribution[i] = 1 + 256;\n        }\n        let actbl = generate_huff_table_from_distribution(&acdistribution);\n\n        let mut block = AlignedBlock::default();\n        for i in 0..10 {\n            block.get_block_mut()[i] = i as i16;\n        }\n        for i in 30..50 {\n            block.get_block_mut()[i] = -(i as i16);\n        }\n        for i in 50..52 {\n            block.get_block_mut()[i] = i as i16;\n        }\n\n        // we don't want to accumulate memory as we write, so reuse the same buffer\n        // and clear it after each iteration.\n        // This also avoids the cost of a malloc/free on each iteration.\n        let mut storage = Vec::with_capacity(1024);\n        Box::new(move || {\n            let mut bitwriter = BitWriter::new(mem::take(&mut storage));\n            encode_block_seq(&mut bitwriter, &dctbl, &actbl, &block);\n            storage = bitwriter.detach_buffer();\n            storage.clear();\n        })\n    }\n\n    /// reads the jpeg file from the test data, parses it and then\n    /// returns a closure that writes the jpeg blocks back out.\n    #[inline(never)]\n    pub fn benchmark_write_jpeg() -> Box<dyn FnMut()> {\n        let file = read_file(\"android\", \".jpg\");\n\n        let mut reader = std::io::Cursor::new(&file);\n        let enabled_features = EnabledFeatures::compat_lepton_vector_write();\n\n        let mut jpeg_header = JpegHeader::default();\n        let mut rinfo = ReconstructionInfo::default();\n\n        let (image_data, partitions, _end_scan) = read_jpeg_file(\n            &mut reader,\n            &mut jpeg_header,\n            &mut rinfo,\n            &enabled_features,\n            |_, _| {},\n        )\n        .unwrap();\n\n        Box::new(move || {\n            let mut prev_offset = 0;\n            for (offset, coding_info) in &partitions {\n                use std::hint::black_box;\n\n                let r = jpeg_write_baseline_row_range(\n                    (offset - prev_offset) as usize,\n                    &coding_info,\n                    &image_data,\n                    &jpeg_header,\n                    &rinfo,\n                )\n                .unwrap();\n\n                black_box(r);\n\n                prev_offset = *offset;\n            }\n        })\n    }\n}\n"
  },
  {
    "path": "lib/src/jpeg/mod.rs",
    "content": "//! Module for reading and recreation of JPEGs without the loss of any information.\n//!\n//! This means that it should be possible to reconstruct bit-by-bit an exactly identical\n//! JPEG file from the input.\n//!\n//! Note that we never actually decode the JPEG into pixels, since the DCT is lossy, so\n//! processing needs to be done at the DCT coefficient level and keep the coefficients in\n//! the BlockBasedImage identical.\n\nmod bit_reader;\nmod bit_writer;\nmod component_info;\npub mod jpeg_code;\nmod jpeg_position_state;\n\npub mod block_based_image;\npub mod jpeg_header;\npub mod jpeg_read;\npub mod jpeg_write;\npub mod row_spec;\npub mod truncate_components;\n"
  },
  {
    "path": "lib/src/jpeg/row_spec.rs",
    "content": "/*---------------------------------------------------------------------------------------------\n *  Copyright (c) Microsoft Corporation. All rights reserved.\n *  Licensed under the Apache License, Version 2.0. See LICENSE.txt in the project root for license information.\n *  This software incorporates material from third parties. See NOTICE.txt for details.\n *--------------------------------------------------------------------------------------------*/\n\nuse crate::consts::COLOR_CHANNEL_NUM_BLOCK_TYPES;\n\nuse super::block_based_image::BlockBasedImage;\n\npub struct RowSpec {\n    pub luma_y: u32,\n    pub component: usize,\n    pub curr_y: u32,\n    pub mcu_row_index: u32,\n    pub last_row_to_complete_mcu: bool,\n    pub skip: bool,\n    pub done: bool,\n}\n\nimpl RowSpec {\n    pub fn get_row_spec_from_index(\n        decode_index: u32,\n        image_data: &[BlockBasedImage],\n        mcuv: u32, // number of mcus\n        max_coded_heights: &[u32],\n    ) -> RowSpec {\n        assert!(\n            image_data.len() <= COLOR_CHANNEL_NUM_BLOCK_TYPES,\n            \"image_data should match components count\"\n        );\n\n        let num_cmp = image_data.len();\n\n        let mut heights: Vec<u32> = Vec::with_capacity(num_cmp);\n        let mut component_multiple: Vec<u32> = Vec::with_capacity(num_cmp);\n        let mut mcu_multiple = 0;\n\n        for i in 0..num_cmp {\n            heights.push(image_data[i].get_original_height());\n            component_multiple.push(heights[i] / mcuv);\n            mcu_multiple += component_multiple[i];\n        }\n\n        let mcu_row = decode_index / mcu_multiple;\n        let min_row_luma_y = mcu_row * component_multiple[0];\n        let mut retval = RowSpec {\n            skip: false,\n            done: false,\n            mcu_row_index: mcu_row,\n            component: num_cmp,\n            luma_y: min_row_luma_y,\n            curr_y: 0,\n            last_row_to_complete_mcu: false,\n        };\n\n        let mut place_within_scan = decode_index - (mcu_row * mcu_multiple);\n\n        let mut i = num_cmp - 1;\n        loop {\n            if place_within_scan < component_multiple[i] {\n                retval.component = i;\n                retval.curr_y = (mcu_row * component_multiple[i]) + place_within_scan;\n                retval.last_row_to_complete_mcu =\n                    (place_within_scan + 1 == component_multiple[i]) && (i == 0);\n\n                if retval.curr_y >= max_coded_heights[i] {\n                    retval.skip = true;\n                    retval.done = true; // assume true, but if we find something that needs coding, set false\n                    for j in 0..num_cmp - 1 {\n                        if mcu_row * component_multiple[j] < max_coded_heights[j] {\n                            // we want to make sure to write out any partial rows,\n                            // so set done only when all items in this mcu are really skips\n                            // i.e. round down\n                            retval.done = false;\n                        }\n                    }\n                }\n\n                if i == 0 {\n                    retval.luma_y = retval.curr_y;\n                }\n\n                break;\n            } else {\n                place_within_scan -= component_multiple[i];\n            }\n\n            if i == 0 {\n                retval.skip = true;\n                retval.done = true;\n                break;\n            }\n\n            i -= 1;\n        }\n\n        return retval;\n    }\n}\n"
  },
  {
    "path": "lib/src/jpeg/truncate_components.rs",
    "content": "/*---------------------------------------------------------------------------------------------\n *  Copyright (c) Microsoft Corporation. All rights reserved.\n *  Licensed under the Apache License, Version 2.0. See LICENSE.txt in the project root for license information.\n *  This software incorporates material from third parties. See NOTICE.txt for details.\n *--------------------------------------------------------------------------------------------*/\n\nuse std::cmp;\n\nuse super::component_info::ComponentInfo;\nuse super::jpeg_header::JpegHeader;\n\n#[derive(Debug, Clone)]\nstruct TrucateComponentsInfo {\n    trunc_bcv: u32, // the number of vertical components in this (truncated) image\n\n    trunc_bc: u32,\n}\n\n#[derive(Debug, Clone)]\npub struct TruncateComponents {\n    trunc_info: Vec<TrucateComponentsInfo>,\n\n    pub components_count: usize,\n\n    pub mcu_count_horizontal: u32,\n\n    pub mcu_count_vertical: u32,\n}\n\nimpl Default for TruncateComponents {\n    fn default() -> Self {\n        return TruncateComponents {\n            trunc_info: Vec::new(),\n            components_count: 0,\n            mcu_count_horizontal: 0,\n            mcu_count_vertical: 0,\n        };\n    }\n}\n\nimpl TruncateComponents {\n    pub fn init(&mut self, jpeg_header: &JpegHeader) {\n        self.mcu_count_horizontal = jpeg_header.mcuh.get();\n        self.mcu_count_vertical = jpeg_header.mcuv.get();\n        self.components_count = jpeg_header.cmpc;\n\n        for i in 0..jpeg_header.cmpc {\n            self.trunc_info.push(TrucateComponentsInfo {\n                trunc_bcv: jpeg_header.cmp_info[i].bcv,\n                trunc_bc: jpeg_header.cmp_info[i].bc,\n            });\n        }\n    }\n\n    pub fn get_max_coded_heights(&self) -> Vec<u32> {\n        let mut retval = Vec::<u32>::new();\n\n        for i in 0..self.components_count {\n            retval.push(self.trunc_info[i].trunc_bcv);\n        }\n        return retval;\n    }\n\n    pub fn set_truncation_bounds(&mut self, jpeg_header: &JpegHeader, max_d_pos: [u32; 4]) {\n        for i in 0..self.components_count {\n            TruncateComponents::set_block_count_d_pos(\n                &mut self.trunc_info[i],\n                &jpeg_header.cmp_info[i],\n                max_d_pos[i] + 1,\n                self.mcu_count_vertical,\n            );\n        }\n    }\n\n    pub fn get_block_height(&self, cmp: usize) -> u32 {\n        return self.trunc_info[cmp].trunc_bcv;\n    }\n\n    fn set_block_count_d_pos(\n        ti: &mut TrucateComponentsInfo,\n        ci: &ComponentInfo,\n        trunc_bc: u32,\n        mcu_count_vertical: u32,\n    ) {\n        assert!(\n            ci.bcv == (ci.bc / ci.bch) + (if ci.bc % ci.bch != 0 { 1 } else { 0 }),\n            \"SetBlockCountDpos\"\n        );\n\n        let mut vertical_scan_lines = cmp::min(\n            (trunc_bc / ci.bch) + (if trunc_bc % ci.bch != 0 { 1 } else { 0 }),\n            ci.bcv,\n        );\n        let ratio = TruncateComponents::get_min_vertical_extcmp_multiple(&ci, mcu_count_vertical);\n\n        while vertical_scan_lines % ratio != 0 && vertical_scan_lines + 1 <= ci.bcv {\n            vertical_scan_lines += 1;\n        }\n\n        assert!(\n            vertical_scan_lines <= ci.bcv,\n            \"verticalScanLines <= ci.Info.bcv\"\n        );\n        ti.trunc_bcv = vertical_scan_lines;\n        ti.trunc_bc = trunc_bc;\n    }\n\n    fn get_min_vertical_extcmp_multiple(cmp_info: &ComponentInfo, mcu_count_vertical: u32) -> u32 {\n        let luma_height = cmp_info.bcv;\n        return luma_height / mcu_count_vertical;\n    }\n\n    pub fn get_component_sizes_in_blocks(&self) -> Vec<u32> {\n        let mut retval = Vec::new();\n        for i in 0..self.components_count {\n            retval.push(self.trunc_info[i].trunc_bc);\n        }\n        return retval;\n    }\n}\n"
  },
  {
    "path": "lib/src/lepton_error.rs",
    "content": "/*---------------------------------------------------------------------------------------------\n *  Copyright (c) Microsoft Corporation. All rights reserved.\n *  Licensed under the Apache License, Version 2.0. See LICENSE.txt in the project root for license information.\n *  This software incorporates material from third parties. See NOTICE.txt for details.\n *--------------------------------------------------------------------------------------------*/\n\nuse std::fmt::Display;\nuse std::io::ErrorKind;\nuse std::num::TryFromIntError;\n\n#[derive(Debug, Clone, Copy, PartialEq)]\n#[allow(dead_code)]\n#[non_exhaustive]\n/// Well-defined errors for bad things that are expected to happen as part of compression/decompression\npub enum ExitCode {\n    /// Assertion failure, which indicates probably indicated a bug in the library.\n    AssertionFailure = 1,\n    //CodingError = 2\n    /// The JPEG file is too short to be a valid JPEG file.\n    ShortRead = 3,\n\n    /// We don't support 4-color JPEGs.\n    Unsupported4Colors = 4,\n\n    /// The coefficients in the JPEG file are out of range specified by the JPEG standard.\n    CoefficientOutOfRange = 6,\n\n    /// The lepton file has a coding error in the arithmetic coding part.\n    StreamInconsistent = 7,\n\n    /// The JPEG file is progressive, and progressive support is not enabled.\n    ProgressiveUnsupported = 8,\n\n    /// The JPEG file has a sampling factor that is not supported by the library.\n    SamplingBeyondTwoUnsupported = 10,\n    //SamplingBeyondFourUnsupported = 11,\n    //ThreadingPartialMcu = 12,\n    /// The lepton file is a version that is not supported by the library.\n    VersionUnsupported = 13,\n    //OnlyGarbageNoJpeg = 14,\n    /// An error was returned by an IO operation, for example if a BufRead\n    /// passed in retrned an error.\n    OsError = 33,\n    //HeaderTooLarge = 34,\n    //BlockOffsetOOM = 37,\n    /// The JPEG cannot be encoded due to a non-standard feature that is not supported by the library.\n    UnsupportedJpeg = 42,\n\n    /// The JPEG file has a zero IDCT, which is not supported by the library.\n    /// Although the C++ library doesn't explicitly disallow this, it will lead to\n    /// undefined behavior depending on C++, since it can lead to a division-by-zero.\n    UnsupportedJpegWithZeroIdct0 = 43,\n\n    /// The JPEG file has invalid reset codes in the stream\n    InvalidResetCode = 44,\n\n    /// The JPEG uses inconsistent padding, which is not supported by the library.\n    InvalidPadding = 45,\n    //WrapperOutputWriteFailed = 101,\n    /// The Lepton file is not a valid Lepton file.\n    BadLeptonFile = 102,\n\n    /// An error occurred while sending a message to a thread in the thread pool.\n    ChannelFailure = 103,\n\n    /// error occured while casting an integer to a smaller type, most likely\n    /// means that the JPEG contains invalid data\n    IntegerCastOverflow = 1000,\n    //CompressionFailedForAllChunks = 1001,\n    //CompressedDataLargerThanPlainText = 1002,\n    //HeaderChecksumMismatch = 1003,\n    /// We verified against the original JPEG file but the regenerated length was different\n    VerificationLengthMismatch = 1004,\n\n    /// We verified against the original JPEG file but the content was different (but same length)\n    VerificationContentMismatch = 1005,\n\n    /// Caller passed in invalid parameters\n    SyntaxError = 1006,\n\n    /// The file to be read was not found (only used by utility exe)\n    FileNotFound = 1007,\n\n    /// An external verification failed (only used by utility exe when verifying\n    /// against C++ Lepton implementation)\n    ExternalVerificationFailed = 1008,\n\n    /// ran out of memory trying to allocate a buffer\n    OutOfMemory = 2000,\n}\n\nimpl Display for ExitCode {\n    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {\n        write!(f, \"{:?}\", self)\n    }\n}\n\nimpl ExitCode {\n    /// Converts the error code into an integer for use as an error code when\n    /// returning from a C API.\n    pub fn as_integer_error_code(self) -> i32 {\n        self as i32\n    }\n}\n\n/// Since errors are rare and stop everything, we want them to be as lightweight as possible.\n#[derive(Debug, Clone)]\nstruct LeptonErrorInternal {\n    exit_code: ExitCode,\n    message: String,\n}\n\n/// Standard error returned by Lepton library\n#[derive(Debug, Clone)]\npub struct LeptonError {\n    i: Box<LeptonErrorInternal>,\n}\n\npub type Result<T> = std::result::Result<T, LeptonError>;\n\nimpl Display for LeptonError {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        write!(f, \"{0}: {1}\", self.i.exit_code, self.i.message)\n    }\n}\n\nimpl LeptonError {\n    /// Creates a new LeptonError with the specified exit code and message.\n    pub fn new(exit_code: ExitCode, message: impl AsRef<str>) -> LeptonError {\n        LeptonError {\n            i: Box::new(LeptonErrorInternal {\n                exit_code,\n                message: message.as_ref().to_owned(),\n            }),\n        }\n    }\n\n    /// Returns the numeric exit code of the error to clasify the error\n    pub fn exit_code(&self) -> ExitCode {\n        self.i.exit_code\n    }\n\n    /// Returns the message of the error, which is a human-readable description of the error.\n    pub fn message(&self) -> &str {\n        &self.i.message\n    }\n\n    /// Adds context to the error by appending the current location in the code. This\n    /// allows for building a callstack of where the error occurred.\n    #[cold]\n    #[inline(never)]\n    #[track_caller]\n    pub fn add_context(&mut self) {\n        self.i\n            .message\n            .push_str(&format!(\"\\n at {}\", std::panic::Location::caller()));\n    }\n}\n\n#[cold]\n#[track_caller]\npub fn err_exit_code<T>(error_code: ExitCode, message: impl AsRef<str>) -> Result<T> {\n    let mut e = LeptonError::new(error_code, message.as_ref());\n    e.add_context();\n    return Err(e);\n}\n\npub trait AddContext<T> {\n    #[track_caller]\n    fn context(self) -> Result<T>;\n}\n\nimpl<T, E: Into<LeptonError>> AddContext<T> for core::result::Result<T, E> {\n    #[track_caller]\n    fn context(self) -> Result<T> {\n        match self {\n            Ok(x) => Ok(x),\n            Err(e) => {\n                let mut e = e.into();\n                e.add_context();\n                Err(e)\n            }\n        }\n    }\n}\n\nimpl std::error::Error for LeptonError {}\n\nfn get_io_error_exit_code(e: &std::io::Error) -> ExitCode {\n    if e.kind() == ErrorKind::UnexpectedEof {\n        ExitCode::ShortRead\n    } else {\n        ExitCode::OsError\n    }\n}\n\nimpl From<TryFromIntError> for LeptonError {\n    #[track_caller]\n    fn from(e: TryFromIntError) -> Self {\n        let mut e = LeptonError::new(ExitCode::IntegerCastOverflow, e.to_string());\n        e.add_context();\n        e\n    }\n}\n\nimpl<T> From<std::sync::mpsc::SendError<T>> for LeptonError {\n    #[track_caller]\n    fn from(e: std::sync::mpsc::SendError<T>) -> Self {\n        let mut e = LeptonError::new(ExitCode::ChannelFailure, e.to_string());\n        e.add_context();\n        e\n    }\n}\nimpl From<std::sync::mpsc::RecvError> for LeptonError {\n    #[track_caller]\n    fn from(e: std::sync::mpsc::RecvError) -> Self {\n        let mut e = LeptonError::new(ExitCode::ChannelFailure, e.to_string());\n        e.add_context();\n        e\n    }\n}\n\n/// translates std::io::Error into LeptonError\nimpl From<std::io::Error> for LeptonError {\n    #[track_caller]\n    fn from(e: std::io::Error) -> Self {\n        match e.downcast::<LeptonError>() {\n            Ok(le) => {\n                return le;\n            }\n            Err(e) => {\n                let mut e = LeptonError::new(get_io_error_exit_code(&e), e.to_string());\n                e.add_context();\n                e\n            }\n        }\n    }\n}\n\n/// translates LeptonError into std::io::Error, which involves putting into a Box and using Other\nimpl From<LeptonError> for std::io::Error {\n    fn from(e: LeptonError) -> Self {\n        return std::io::Error::new(std::io::ErrorKind::Other, e);\n    }\n}\n\n#[test]\nfn test_error_translation() {\n    // test wrapping inside an io error\n    fn my_std_error() -> core::result::Result<(), std::io::Error> {\n        Err(LeptonError::new(ExitCode::SyntaxError, \"test error\").into())\n    }\n\n    let e: LeptonError = my_std_error().unwrap_err().into();\n    assert_eq!(e.exit_code(), ExitCode::SyntaxError);\n    assert_eq!(e.message(), \"test error\");\n\n    // an IO error should be translated into an OsError\n    let e: LeptonError = std::io::Error::new(std::io::ErrorKind::NotFound, \"file not found\").into();\n    assert_eq!(e.exit_code(), ExitCode::OsError);\n}\n"
  },
  {
    "path": "lib/src/lib.rs",
    "content": "/*---------------------------------------------------------------------------------------------\n *  Copyright (c) Microsoft Corporation. All rights reserved.\n *  Licensed under the Apache License, Version 2.0. See LICENSE.txt in the project root for license information.\n *  This software incorporates material from third parties. See NOTICE.txt for details.\n *--------------------------------------------------------------------------------------------*/\n\n//! A lossless JPEG compressor with precise bit-for-bit recovery, supporting both baseline and progressive JPEGs.\n//! Achieves compression savings of around 22%, making it suitable for cold cloud storage use cases.\n//!\n//! This crate is a Rust port of Dropbox’s original [lepton](https://github.com/dropbox/lepton) JPEG compression tool.\n//! It retains the performance characteristics of the C++ version while benefiting from Rust’s memory safety guarantees.\n//! All JPEG content—including metadata and even malformed segments—is preserved accurately.\n//!\n//! The original C++ codebase has been deprecated by Dropbox. This Rust implementation incorporates\n//! an exhaustive security review of the original, making it a safer and more maintainable alternative.\n\n// Don't allow any unsafe code by default. Since this code has to potentially deal with\n// badly/maliciously formatted images, we want this extra level of safety.\n#![forbid(unsafe_code)]\n#![forbid(trivial_casts)]\n#![forbid(trivial_numeric_casts)]\n#![forbid(non_ascii_idents)]\n#![forbid(unused_extern_crates)]\n#![forbid(unused_import_braces)]\n#![forbid(redundant_lifetimes)]\n#![forbid(single_use_lifetimes)]\n#![forbid(unused_extern_crates)]\n#![forbid(unused_lifetimes)]\n#![forbid(unused_macro_rules)]\n#![forbid(macro_use_extern_crate)]\n#![forbid(missing_unsafe_on_extern)]\n#![deny(missing_docs)]\n\nmod consts;\nmod helpers;\nmod jpeg;\nmod metrics;\nmod structs;\n\nmod enabled_features;\nmod lepton_error;\n\npub use enabled_features::EnabledFeatures;\npub use helpers::catch_unwind_result;\npub use lepton_error::{ExitCode, LeptonError};\npub use metrics::{CpuTimeMeasure, Metrics};\npub use structs::lepton_file_writer::get_git_version;\n\nuse crate::lepton_error::{AddContext, Result};\npub use crate::structs::simple_threadpool::{\n    DEFAULT_THREAD_POOL, LeptonThreadPool, LeptonThreadPriority, SimpleThreadPool,\n    SingleThreadPool, ThreadPoolHolder,\n};\n\n#[cfg(feature = \"micro_benchmark\")]\n/// Module that exposes internal functions for micro benchmarking\npub mod micro_benchmark;\n\n/// Trait for types that can provide the current position in a stream. This\n/// is intentionally a subset of the Seek trait, as it only requires remembering\n/// the current position without allowing seeking to arbitrary positions.\n///\n/// This is useful for callers for which it would be complex to provide seek capabilities, but can\n/// count the number of bytes read or written so far.\n///\n/// We provide a blanket implementation for any type that implements `std::io::Seek`.\npub trait StreamPosition {\n    /// Returns the current position in the stream.\n    fn position(&mut self) -> u64;\n}\n\nimpl<T: std::io::Seek> StreamPosition for T {\n    fn position(&mut self) -> u64 {\n        self.stream_position().unwrap()\n    }\n}\n\npub use structs::lepton_file_reader::decode_lepton;\n\npub use structs::lepton_file_writer::{encode_lepton, encode_lepton_verify};\n\nstatic PACKAGE_VERSION: &str = env!(\"CARGO_PKG_VERSION\");\n\npub use structs::lepton_file_reader::LeptonFileReader;\n\n/// Returns the version string of the library, which includes the package version and the git version.\n/// This is useful for debugging and logging purposes to know the exact version of the library is being used\npub fn get_version_string() -> String {\n    format!(\"{}-{}\", PACKAGE_VERSION, get_git_version())\n}\n\n/// used by utility to dump out the contents of a jpeg file or lepton file for debugging purposes\n#[allow(dead_code)]\npub fn dump_jpeg(input_data: &[u8], all: bool, enabled_features: &EnabledFeatures) -> Result<()> {\n    use std::io::Cursor;\n    use structs::lepton_file_reader::decode_lepton_file_image;\n    use structs::lepton_file_writer::read_jpeg;\n\n    let mut lh;\n    let block_image;\n\n    if input_data[0] == 0xff && input_data[1] == 0xd8 {\n        let mut reader = Cursor::new(input_data);\n\n        (lh, block_image) = read_jpeg(&mut reader, enabled_features, |jh, _ri| {\n            println!(\"parsed header:\");\n            let s = format!(\"{jh:?}\");\n            println!(\"{0}\", s.replace(\"},\", \"},\\r\\n\").replace(\"],\", \"],\\r\\n\"));\n        })?;\n    } else {\n        let mut reader = Cursor::new(input_data);\n\n        (lh, block_image) =\n            decode_lepton_file_image(&mut reader, enabled_features, &DEFAULT_THREAD_POOL)\n                .context()?;\n\n        loop {\n            println!(\"parsed header:\");\n            let s = format!(\"{0:?}\", lh.jpeg_header);\n            println!(\"{0}\", s.replace(\"},\", \"},\\r\\n\").replace(\"],\", \"],\\r\\n\"));\n\n            if !lh\n                .advance_next_header_segment(&enabled_features)\n                .context()?\n            {\n                break;\n            }\n        }\n    }\n\n    let s = format!(\"{lh:?}\");\n    println!(\"{0}\", s.replace(\"},\", \"},\\r\\n\").replace(\"],\", \"],\\r\\n\"));\n\n    if all {\n        for i in 0..block_image.len() {\n            println!(\"Component {0}\", i);\n            let image = &block_image[i];\n            for dpos in 0..image.get_block_width() * image.get_original_height() {\n                print!(\"dpos={0} \", dpos);\n                let block = image.get_block(dpos);\n\n                print!(\"{0}\", block.get_transposed_from_zigzag(0));\n                for i in 1..64 {\n                    print!(\",{0}\", block.get_transposed_from_zigzag(i));\n                }\n                println!();\n            }\n        }\n    }\n\n    return Ok(());\n}\n"
  },
  {
    "path": "lib/src/metrics.rs",
    "content": "use std::collections::HashMap;\nuse std::time::Duration;\n\n#[cfg(windows)]\nuse cpu_time::ThreadTime;\n\n/// platform independent threadtime measurement\npub struct CpuTimeMeasure {\n    #[cfg(windows)]\n    start: ThreadTime,\n    #[cfg(not(windows))]\n    start: std::time::Instant,\n}\n\nimpl CpuTimeMeasure {\n    /// Creates a new CpuTimeMeasure instance that starts measuring time.\n    pub fn new() -> Self {\n        Self {\n            #[cfg(windows)]\n            start: ThreadTime::now(),\n            #[cfg(not(windows))]\n            start: std::time::Instant::now(),\n        }\n    }\n\n    /// Returns the elapsed time since the CpuTimeMeasure instance was created.\n    pub fn elapsed(&self) -> Duration {\n        #[cfg(windows)]\n        {\n            self.start.elapsed()\n        }\n        #[cfg(not(windows))]\n        {\n            self.start.elapsed()\n        }\n    }\n}\n\n#[derive(Debug, PartialEq, Copy, Clone, Hash, Eq)]\npub enum ModelSubComponent {\n    Exp,\n    Sign,\n    Residual,\n    Noise,\n}\n\n#[derive(Debug, PartialEq, Copy, Clone, Hash, Eq)]\n#[repr(u8)]\npub enum ModelComponent {\n    Dummy,\n    Coef(ModelSubComponent),\n    DC(ModelSubComponent),\n    Edge(ModelSubComponent),\n    NonZero7x7Count,\n    NonZeroEdgeCount,\n}\n\n#[derive(Default, Debug)]\npub struct ModelComponentStatistics {\n    pub total_bits: i64,\n    pub total_compressed: i64,\n}\n\n/// Metrics for the Lepton JPEG compression and decompression process.\n#[derive(Default, Debug)]\npub struct Metrics {\n    map: HashMap<ModelComponent, ModelComponentStatistics>,\n    cpu_time_worker_time: Duration,\n}\n\nimpl Metrics {\n    /// Records the compression statistics for a specific model component.\n    #[allow(dead_code)]\n    pub fn record_compression_stats(\n        &mut self,\n        cmp: ModelComponent,\n        total_bits: i64,\n        total_compressed: i64,\n    ) {\n        let e = self\n            .map\n            .entry(cmp)\n            .or_insert(ModelComponentStatistics::default());\n        e.total_bits += total_bits;\n        e.total_compressed += total_compressed;\n    }\n\n    /// Records the CPU worker time for the compression process.\n    pub fn record_cpu_worker_time(&mut self, duration: Duration) {\n        self.cpu_time_worker_time += duration;\n    }\n\n    /// Returns the total number of bits processed for a specific model component.\n    #[allow(dead_code)]\n    pub fn print_metrics(&self) {\n        let mut sort_vec = Vec::new();\n        for x in &self.map {\n            sort_vec.push((x.0, x.1));\n        }\n\n        sort_vec.sort_by(|a, b| a.1.total_compressed.cmp(&b.1.total_compressed).reverse());\n\n        let total_compressed: i64 = sort_vec.iter().map(|x| x.1.total_compressed).sum();\n\n        for x in &sort_vec {\n            let name = format!(\"{0:?}\", x.0);\n\n            println!(\n                \"{0:16} total_bits={1:9} compressed_bits={2:9} ratio={3:4} comp_delta={4:10}k storage={5:0.1}%, comp={6:0.2}%)\",\n                name,\n                x.1.total_bits,\n                x.1.total_compressed,\n                x.1.total_compressed * 100 / x.1.total_bits,\n                (x.1.total_bits - x.1.total_compressed) / (8 * 1024),\n                (x.1.total_compressed as f64) * 100f64 / (total_compressed as f64),\n                ((x.1.total_bits - x.1.total_compressed) as f64) / (total_compressed as f64)\n                    * 100f64\n            );\n        }\n\n        println!(\n            \"total_compressed = {0} bits, {1} bytes\",\n            total_compressed,\n            total_compressed / 8\n        );\n        println!(\"worker_cpu={0}ms\", self.cpu_time_worker_time.as_millis());\n    }\n\n    /// empties the metrics and returns the collected data\n    pub fn drain(&mut self) -> Metrics {\n        let cpu_time_worker_time = self.cpu_time_worker_time;\n        self.cpu_time_worker_time = Duration::default();\n\n        Metrics {\n            map: self.map.drain().collect(),\n            cpu_time_worker_time,\n        }\n    }\n\n    /// Returns the total CPU worker time recorded in the metrics.\n    pub fn get_cpu_time_worker_time(&self) -> Duration {\n        self.cpu_time_worker_time\n    }\n\n    /// Merges another Metrics instance into this one, summing the statistics.\n    pub fn merge_from(&mut self, mut source_metrics: Metrics) {\n        for x in source_metrics.map.drain() {\n            let e = self\n                .map\n                .entry(x.0)\n                .or_insert(ModelComponentStatistics::default());\n            e.total_bits += x.1.total_bits;\n            e.total_compressed += x.1.total_compressed;\n        }\n\n        self.cpu_time_worker_time += source_metrics.cpu_time_worker_time;\n    }\n}\n"
  },
  {
    "path": "lib/src/micro_benchmark.rs",
    "content": "pub use crate::structs::{benchmark_idct, benchmark_roundtrip_coefficient};\n\npub use crate::jpeg::jpeg_write::benchmarks::benchmark_write_jpeg;\n\npub use crate::jpeg::jpeg_read::benchmarks::benchmark_read_jpeg;\n\npub use crate::jpeg::jpeg_write::benchmarks::benchmark_write_block;\n\npub use crate::jpeg::jpeg_read::benchmarks::benchmark_read_block;\n"
  },
  {
    "path": "lib/src/structs/block_context.rs",
    "content": "/*---------------------------------------------------------------------------------------------\n *  Copyright (c) Microsoft Corporation. All rights reserved.\n *  Licensed under the Apache License, Version 2.0. See LICENSE.txt in the project root for license information.\n *  This software incorporates material from third parties. See NOTICE.txt for details.\n *--------------------------------------------------------------------------------------------*/\n\nuse crate::jpeg::block_based_image::{AlignedBlock, BlockBasedImage, EMPTY_BLOCK};\nuse crate::structs::neighbor_summary::{NEIGHBOR_DATA_EMPTY, NeighborSummary};\nuse crate::structs::probability_tables::ProbabilityTables;\npub struct BlockContext {\n    block_width: u32,\n    cur_block_index: u32,\n    cur_neighbor_summary_index: u32,\n    above_neighbor_summary_index: u32,\n}\npub struct NeighborData<'a> {\n    pub above: &'a AlignedBlock,\n    pub left: &'a AlignedBlock,\n    pub above_left: &'a AlignedBlock,\n    pub neighbor_context_above: &'a NeighborSummary,\n    pub neighbor_context_left: &'a NeighborSummary,\n}\n\nimpl BlockContext {\n    /// Create a new BlockContext for the first line of the image at a given y-coordinate.\n    pub fn off_y(y: u32, image_data: &BlockBasedImage) -> BlockContext {\n        let block_width = image_data.get_block_width();\n\n        let cur_block_index = block_width * y;\n\n        // blocks above the first line are never dereferenced\n        let cur_neighbor_summary_index = if (y & 1) != 0 { block_width } else { 0 };\n\n        let above_neighbor_summary_index = if (y & 1) != 0 { 0 } else { block_width };\n\n        BlockContext {\n            cur_block_index,\n            block_width,\n            cur_neighbor_summary_index,\n            above_neighbor_summary_index,\n        }\n    }\n\n    // for debugging\n    #[allow(dead_code)]\n    pub fn get_here_index(&self) -> u32 {\n        self.cur_block_index\n    }\n\n    // as each new line BlockContext is set by `off_y`, no edge cases with dereferencing\n    // out of bounds indices is possible, therefore no special treatment is needed\n    pub fn next(&mut self) -> u32 {\n        self.cur_block_index += 1;\n        self.cur_neighbor_summary_index += 1;\n        self.above_neighbor_summary_index += 1;\n\n        self.cur_block_index\n    }\n\n    pub fn here<'a>(&self, image_data: &'a BlockBasedImage) -> &'a AlignedBlock {\n        let retval = image_data.get_block(self.cur_block_index);\n        return retval;\n    }\n\n    pub fn get_neighbor_data<'a, const ALL_PRESENT: bool>(\n        &self,\n        image_data: &'a BlockBasedImage,\n        neighbor_summary: &'a [NeighborSummary],\n        pt: &ProbabilityTables,\n    ) -> NeighborData<'a> {\n        NeighborData::<'a> {\n            above_left: if ALL_PRESENT {\n                image_data.get_block(self.cur_block_index - self.block_width - 1)\n            } else {\n                &EMPTY_BLOCK\n            },\n            above: if ALL_PRESENT || pt.is_above_present() {\n                image_data.get_block(self.cur_block_index - self.block_width)\n            } else {\n                &EMPTY_BLOCK\n            },\n            left: if ALL_PRESENT || pt.is_left_present() {\n                image_data.get_block(self.cur_block_index - 1)\n            } else {\n                &EMPTY_BLOCK\n            },\n            neighbor_context_above: if ALL_PRESENT || pt.is_above_present() {\n                &neighbor_summary[self.above_neighbor_summary_index as usize]\n            } else {\n                &NEIGHBOR_DATA_EMPTY\n            },\n            neighbor_context_left: if ALL_PRESENT || pt.is_left_present() {\n                &neighbor_summary[(self.cur_neighbor_summary_index - 1) as usize]\n            } else {\n                &NEIGHBOR_DATA_EMPTY\n            },\n        }\n    }\n\n    pub fn set_neighbor_summary_here(\n        &self,\n        neighbor_summary_cache: &mut [NeighborSummary],\n        neighbor_summary: NeighborSummary,\n    ) {\n        neighbor_summary_cache[self.cur_neighbor_summary_index as usize] = neighbor_summary;\n    }\n}\n"
  },
  {
    "path": "lib/src/structs/branch.rs",
    "content": "/*---------------------------------------------------------------------------------------------\n *  Copyright (c) Microsoft Corporation. All rights reserved.\n *  Licensed under the Apache License, Version 2.0. See LICENSE.txt in the project root for license information.\n *  This software incorporates material from third parties. See NOTICE.txt for details.\n *--------------------------------------------------------------------------------------------*/\n\n/*\n The logic here is different here than the C++ version, resulting in\n a 2x speed increase. Nothing magic, the main change is to not\n store the probability, since it is deterministically determined\n based on the true/false counts. Instead of doing the calculation,\n we just lookup the 16-bit value in a lookup table to get the\n corresponding probabiity.\n*/\n\npub struct Branch {\n    /// The top byte is the number of false bits seen so far\n    /// and the bottom byte is the number of true bits seen.\n    /// On overflow both values are normalized by dividing by 2 (rounding up).\n    ///\n    /// Both counts are never less than 1, so we start off with 0x0101.\n    counts: u16,\n}\n\nimpl Default for Branch {\n    fn default() -> Branch {\n        Branch::new()\n    }\n}\n\n/// used to precalculate the probabilities and store them as a const array\nconst fn problookup() -> [u8; 65536] {\n    let mut retval = [0; 65536];\n    let mut i = 1i32;\n    while i < 65536 {\n        let a = i >> 8;\n        let b = i & 0xff;\n\n        retval[i as usize] = ((a << 8) / (a + b)) as u8;\n        i += 1;\n    }\n\n    return retval;\n}\n\n/// precalculated probabilities for the next bit being false\nstatic PROB_LOOKUP: [u8; 65536] = problookup();\n\nimpl Branch {\n    pub fn new() -> Self {\n        Branch { counts: 0x0101 }\n    }\n\n    /// used for testing to set counts to a specific value\n    #[cfg(test)]\n    pub fn set_count(&mut self, count: u16) {\n        self.counts = count;\n    }\n\n    /// used for testing to set counts to a specific value\n    #[cfg(test)]\n    pub fn get_count(&self) -> u16 {\n        self.counts\n    }\n\n    /// used for debugging to keep the state for hashing\n    #[allow(dead_code)]\n    pub fn get_u64(&self) -> u64 {\n        let c = self.counts;\n        return ((PROB_LOOKUP[self.counts as usize] as u64) << 16) + c as u64;\n    }\n\n    /// Returns the probability of the next bit being a false as a value between 1 and 255\n    ///\n    /// Calculated by looking up the probability in a precalculated table\n    /// where 'f' is the number of false bits and 't' is the number of true bits seen.\n    ///\n    /// (f * 256) / (f + t)\n    #[inline(always)]\n    pub fn get_probability(&self) -> u8 {\n        PROB_LOOKUP[self.counts as usize]\n    }\n\n    /// Updates the counters when we encounter a 1 or 0. If we hit 255 values, then\n    /// we normalize both counts (divide by 2), except in the case where the remaining value is 1,\n    /// in which case we don't touch. This biases the probability to get better results\n    /// when there are long runs of 1 or 0.\n    ///\n    /// This function merges updating either the true or false counter\n    /// by swapping the top and bottom byte of the 16-bit value.\n    ///\n    /// The update algorithm looks like this (with top and bottom swapped depending on 'bit'):\n    ///\n    /// if top_byte < 0xff {\n    ///  top_byte += 1;\n    /// } else if bottom_byte != 1 {\n    ///  top_byte = 0x81;\n    ///  bottom_byte = (bottom_byte + 1) >> 1;\n    /// }\n    #[inline(always)]\n    pub fn record_and_update_bit(&mut self, bit: bool) {\n        // rotation is used to update either the true or false counter\n        // this allows the same code to be used without branching,\n        // which makes the CPU about 20% happier.\n        //\n        // Since the bits are randomly 1/0, the CPU branch predictor does\n        // a terrible job and ends up wasting a lot of time. Normally\n        // branches are a better idea if the branch very predictable vs\n        // this case where it is better to always pay the price of the\n        // extra rotation to avoid the branch.\n        let orig = self.counts.rotate_left(bit as u32 * 8);\n        let (mut sum, o) = orig.overflowing_add(0x100);\n        if o {\n            // normalize, except in special case where we have 0xff or more same bits in a row\n            // in which case we want to bias the probability to get better compression\n            //\n            // CPU branch prediction soon realizes that this section is not often executed\n            // and will optimize for the common case where the counts are not 0xff.\n            let mask = if orig == 0xff01 { 0xff00 } else { 0x8100 };\n\n            // upper byte is 0 since we incremented 0xffxx so we don't have to mask it\n            sum = ((1 + sum) >> 1) | mask;\n        }\n\n        self.counts = sum.rotate_left(bit as u32 * 8);\n    }\n}\n\n#[test]\nfn test_branch_update_false() {\n    let mut b = Branch { counts: 0x0101 };\n    b.record_and_update_bit(false);\n    assert_eq!(b.counts, 0x0201);\n\n    b.counts = 0x80ff;\n    b.record_and_update_bit(false);\n    assert_eq!(b.counts, 0x81ff);\n\n    b.counts = 0xff01;\n    b.record_and_update_bit(false);\n    assert_eq!(b.counts, 0xff01);\n\n    b.counts = 0xff02;\n    b.record_and_update_bit(false);\n    assert_eq!(b.counts, 0x8101);\n\n    b.counts = 0xffff;\n    b.record_and_update_bit(false);\n    assert_eq!(b.counts, 0x8180);\n}\n\n#[test]\nfn test_branch_update_true() {\n    let mut b = Branch { counts: 0x0101 };\n    b.record_and_update_bit(true);\n    assert_eq!(b.counts, 0x0102);\n\n    b.counts = 0xff80;\n    b.record_and_update_bit(true);\n    assert_eq!(b.counts, 0xff81);\n\n    b.counts = 0x01ff;\n    b.record_and_update_bit(true);\n    assert_eq!(b.counts, 0x01ff);\n\n    b.counts = 0x02ff;\n    b.record_and_update_bit(true);\n    assert_eq!(b.counts, 0x0181);\n\n    b.counts = 0xffff;\n    b.record_and_update_bit(true);\n    assert_eq!(b.counts, 0x8081);\n}\n\n/// run through all the possible combinations of counts and ensure that the probability is the same\n#[test]\nfn test_all_probabilities() {\n    /// This is copied from the C++ implementation to ensure that the behavior is the same\n    struct OriginalImplForTest {\n        counts: [u8; 2],\n        probability: u8,\n    }\n\n    impl OriginalImplForTest {\n        fn true_count(&self) -> u32 {\n            return self.counts[1] as u32;\n        }\n        fn false_count(&self) -> u32 {\n            return self.counts[0] as u32;\n        }\n\n        fn record_obs_and_update(&mut self, obs: bool) {\n            let fcount = self.counts[0] as u32;\n            let tcount = self.counts[1] as u32;\n\n            let overflow = self.counts[obs as usize] == 0xff;\n\n            if overflow {\n                // check less than 512\n                let neverseen = self.counts[!obs as usize] == 1;\n                if neverseen {\n                    self.counts[obs as usize] = 0xff;\n                    self.probability = if obs { 0 } else { 255 };\n                } else {\n                    self.counts[0] = ((1 + fcount) >> 1) as u8;\n                    self.counts[1] = ((1 + tcount) >> 1) as u8;\n                    self.counts[obs as usize] = 129;\n                    self.probability = self.optimize(self.counts[0] as u32 + self.counts[1] as u32);\n                }\n            } else {\n                self.counts[obs as usize] += 1;\n                self.probability = self.optimize(fcount + tcount + 1);\n            }\n        }\n\n        fn optimize(&self, sum: u32) -> u8 {\n            let prob = (self.false_count() << 8) / sum;\n\n            prob as u8\n        }\n    }\n\n    for i in 0u16..=65535 {\n        let mut old_f = OriginalImplForTest {\n            counts: [(i >> 8) as u8, i as u8],\n            probability: 0,\n        };\n\n        if old_f.true_count() == 0 || old_f.false_count() == 0 {\n            // starting counts can't be zero (we use 0 as an internal special value for the new implementation for the edge case of many trues in a row)\n            continue;\n        }\n\n        let mut new_f = Branch { counts: i };\n\n        for _k in 0..10 {\n            old_f.record_obs_and_update(false);\n            new_f.record_and_update_bit(false);\n            assert_eq!(old_f.probability, new_f.get_probability());\n        }\n\n        let mut old_t = OriginalImplForTest {\n            counts: [(i >> 8) as u8, i as u8],\n            probability: 0,\n        };\n        let mut new_t = Branch { counts: i };\n\n        for _k in 0..10 {\n            old_t.record_obs_and_update(true);\n            new_t.record_and_update_bit(true);\n\n            if old_t.probability == 0 {\n                // there is a change of behavior here compared to the C++ version,\n                // but because of the way split is calculated it doesn't result in an\n                // overall change in the way that encoding is done, but it does simplify\n                // one of the corner cases.\n                assert_eq!(new_t.get_probability(), 1);\n            } else {\n                assert_eq!(old_t.probability, new_t.get_probability());\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "lib/src/structs/idct.rs",
    "content": "/*---------------------------------------------------------------------------------------------\n *  Copyright (c) Microsoft Corporation. All rights reserved.\n *  Licensed under the Apache License, Version 2.0. See LICENSE.txt in the project root for license information.\n *  This software incorporates material from third parties. See NOTICE.txt for details.\n *--------------------------------------------------------------------------------------------*/\n\nuse bytemuck::cast;\nuse wide::{i16x8, i32x8};\n\nuse crate::jpeg::block_based_image::AlignedBlock;\n\nconst _W1: i32 = 2841; // 2048*sqrt(2)*cos(1*pi/16)\nconst _W2: i32 = 2676; // 2048*sqrt(2)*cos(2*pi/16)\nconst _W3: i32 = 2408; // 2048*sqrt(2)*cos(3*pi/16)\nconst _W5: i32 = 1609; // 2048*sqrt(2)*cos(5*pi/16)\nconst _W6: i32 = 1108; // 2048*sqrt(2)*cos(6*pi/16)\nconst _W7: i32 = 565; // 2048*sqrt(2)*cos(7*pi/16)\n\nconst W3: i32 = 2408; // 2048*sqrt(2)*cos(3*pi/16)\nconst W6: i32 = 1108; // 2048*sqrt(2)*cos(6*pi/16)\nconst W7: i32 = 565; // 2048*sqrt(2)*cos(7*pi/16)\n\nconst W1PW7: i32 = _W1 + _W7;\nconst W1MW7: i32 = _W1 - _W7;\nconst W2PW6: i32 = _W2 + _W6;\nconst W2MW6: i32 = _W2 - _W6;\nconst W3PW5: i32 = _W3 + _W5;\nconst W3MW5: i32 = _W3 - _W5;\n\nconst R2: i32 = 181; // 256/sqrt(2)\n\n#[inline(always)]\npub fn run_idct(block: &[i32x8; 8]) -> AlignedBlock {\n    let t = *block;\n\n    let mut xv0 = (t[0] << 11) + 128;\n    let mut xv1 = t[1];\n    let mut xv2 = t[2];\n    let mut xv3 = t[3];\n    let mut xv4 = t[4] << 11;\n    let mut xv5 = t[5];\n    let mut xv6 = t[6];\n    let mut xv7 = t[7];\n\n    // Stage 1.\n    let mut xv8 = _W7 * (xv1 + xv7);\n    xv1 = xv8 + (W1MW7 * xv1);\n    xv7 = xv8 - (W1PW7 * xv7);\n    xv8 = _W3 * (xv5 + xv3);\n    xv5 = xv8 - (W3MW5 * xv5);\n    xv3 = xv8 - (W3PW5 * xv3);\n\n    // Stage 2.\n    xv8 = xv0 + xv4;\n    xv0 -= xv4;\n    xv4 = W6 * (xv2 + xv6);\n    xv6 = xv4 - (W2PW6 * xv6);\n    xv2 = xv4 + (W2MW6 * xv2);\n    xv4 = xv1 + xv5;\n    xv1 -= xv5;\n    xv5 = xv7 + xv3;\n    xv7 -= xv3;\n\n    // Stage 3.\n    xv3 = xv8 + xv2;\n    xv8 -= xv2;\n    xv2 = xv0 + xv6;\n    xv0 -= xv6;\n    xv6 = ((R2 * (xv1 + xv7)) + 128) >> 8;\n    xv1 = ((R2 * (xv1 - xv7)) + 128) >> 8;\n\n    // Stage 4.\n    let row = [\n        (xv3 + xv4) >> 8,\n        (xv2 + xv6) >> 8,\n        (xv0 + xv1) >> 8,\n        (xv8 + xv5) >> 8,\n        (xv8 - xv5) >> 8,\n        (xv0 - xv1) >> 8,\n        (xv2 - xv6) >> 8,\n        (xv3 - xv4) >> 8,\n    ];\n\n    // transpose and now do vertical\n    let [\n        mut yv0,\n        mut yv1,\n        mut yv2,\n        mut yv3,\n        mut yv4,\n        mut yv5,\n        mut yv6,\n        mut yv7,\n    ] = i32x8::transpose(row);\n\n    yv0 = (yv0 << 8) + 8192;\n    yv4 = yv4 << 8;\n\n    // Stage 1.\n    let mut yv8 = (W7 * (yv1 + yv7)) + 4;\n    yv1 = (yv8 + (W1MW7 * yv1)) >> 3;\n    yv7 = (yv8 - (W1PW7 * yv7)) >> 3;\n    yv8 = (W3 * (yv5 + yv3)) + 4;\n    yv5 = (yv8 - (W3MW5 * yv5)) >> 3;\n    yv3 = (yv8 - (W3PW5 * yv3)) >> 3;\n\n    // Stage 2.\n    yv8 = yv0 + yv4;\n    yv0 -= yv4;\n    yv4 = ((W6) * (yv2 + yv6)) + 4;\n    yv6 = (yv4 - (W2PW6 * yv6)) >> 3;\n    yv2 = (yv4 + (W2MW6 * yv2)) >> 3;\n    yv4 = yv1 + yv5;\n    yv1 -= yv5;\n    yv5 = yv7 + yv3;\n    yv7 -= yv3;\n\n    // Stage 3.\n    yv3 = yv8 + yv2;\n    yv8 -= yv2;\n    yv2 = yv0 + yv6;\n    yv0 -= yv6;\n    yv6 = ((R2 * (yv1 + yv7)) + 128) >> 8;\n    yv1 = ((R2 * (yv1 - yv7)) + 128) >> 8;\n\n    // Stage 4.\n    AlignedBlock::new(cast([\n        i16x8::from_i32x8_truncate((yv3 + yv4) >> 11),\n        i16x8::from_i32x8_truncate((yv2 + yv6) >> 11),\n        i16x8::from_i32x8_truncate((yv0 + yv1) >> 11),\n        i16x8::from_i32x8_truncate((yv8 + yv5) >> 11),\n        i16x8::from_i32x8_truncate((yv8 - yv5) >> 11),\n        i16x8::from_i32x8_truncate((yv0 - yv1) >> 11),\n        i16x8::from_i32x8_truncate((yv2 - yv6) >> 11),\n        i16x8::from_i32x8_truncate((yv3 - yv4) >> 11),\n    ]))\n}\n\n#[cfg(test)]\nuse bytemuck::cast_ref;\n\n#[cfg(test)]\n#[inline(always)]\nfn get_q(offset: usize, q_transposed: &AlignedBlock) -> i32x8 {\n    use wide::u16x8;\n\n    let rows: &[u16x8; 8] = cast_ref(q_transposed.get_block());\n    i32x8::from_u16x8(rows[offset])\n}\n\n#[cfg(test)]\n#[inline(always)]\nfn get_c(offset: usize, q_transposed: &AlignedBlock) -> i32x8 {\n    let rows: &[i16x8; 8] = cast_ref(q_transposed.get_block());\n    i32x8::from_i16x8(rows[offset])\n}\n\n#[cfg(test)]\nfn test_idct(test_data: &AlignedBlock, test_q: &[u16; 64]) {\n    use std::num::Wrapping;\n\n    fn mul(a: i16, b: u16) -> Wrapping<i32> {\n        return Wrapping(a as i32) * Wrapping(b as i32);\n    }\n\n    pub fn run_idct_old(\n        block: &AlignedBlock,\n        q: &[u16; 64],\n        outp: &mut [i16; 64],\n        ignore_dc: bool,\n    ) {\n        let mut intermed = [Wrapping(0i32); 64];\n\n        // Horizontal 1-D IDCT.\n        for y in 0..8 {\n            let y8: usize = y * 8;\n\n            let mut x0 = if ignore_dc && y == 0 {\n                Wrapping(0)\n            } else {\n                mul(block.get_coefficient(y8 + 0), q[y8 + 0]) << 11\n            } + Wrapping(128);\n            let mut x1 = mul(block.get_coefficient(y8 + 4), q[y8 + 4]) << 11;\n            let mut x2 = mul(block.get_coefficient(y8 + 6), q[y8 + 6]);\n            let mut x3 = mul(block.get_coefficient(y8 + 2), q[y8 + 2]);\n            let mut x4 = mul(block.get_coefficient(y8 + 1), q[y8 + 1]);\n            let mut x5 = mul(block.get_coefficient(y8 + 7), q[y8 + 7]);\n            let mut x6 = mul(block.get_coefficient(y8 + 5), q[y8 + 5]);\n            let mut x7 = mul(block.get_coefficient(y8 + 3), q[y8 + 3]);\n\n            // If all the AC components are zero, then the IDCT is trivial.\n            if x1 == Wrapping(0)\n                && x2 == Wrapping(0)\n                && x3 == Wrapping(0)\n                && x4 == Wrapping(0)\n                && x5 == Wrapping(0)\n                && x6 == Wrapping(0)\n                && x7 == Wrapping(0)\n            {\n                let dc = (x0 - Wrapping(128)) >> 8;\n                intermed[y8 + 0] = dc;\n                intermed[y8 + 1] = dc;\n                intermed[y8 + 2] = dc;\n                intermed[y8 + 3] = dc;\n                intermed[y8 + 4] = dc;\n                intermed[y8 + 5] = dc;\n                intermed[y8 + 6] = dc;\n                intermed[y8 + 7] = dc;\n                continue;\n            }\n\n            // Prescale.\n\n            // Stage 1.\n            let mut x8 = Wrapping(W7) * (x4 + x5);\n            x4 = x8 + (Wrapping(W1MW7) * x4);\n            x5 = x8 - (Wrapping(W1PW7) * x5);\n            x8 = Wrapping(W3) * (x6 + x7);\n            x6 = x8 - (Wrapping(W3MW5) * x6);\n            x7 = x8 - (Wrapping(W3PW5) * x7);\n\n            // Stage 2.\n            x8 = x0 + x1;\n            x0 -= x1;\n            x1 = Wrapping(W6) * (x3 + x2);\n            x2 = x1 - (Wrapping(W2PW6) * x2);\n            x3 = x1 + (Wrapping(W2MW6) * x3);\n            x1 = x4 + x6;\n            x4 -= x6;\n            x6 = x5 + x7;\n            x5 -= x7;\n\n            // Stage 3.\n            x7 = x8 + x3;\n            x8 -= x3;\n            x3 = x0 + x2;\n            x0 -= x2;\n            x2 = ((Wrapping(R2) * (x4 + x5)) + Wrapping(128)) >> 8;\n            x4 = ((Wrapping(R2) * (x4 - x5)) + Wrapping(128)) >> 8;\n\n            // Stage 4.\n            intermed[y8 + 0] = (x7 + x1) >> 8;\n            intermed[y8 + 1] = (x3 + x2) >> 8;\n            intermed[y8 + 2] = (x0 + x4) >> 8;\n            intermed[y8 + 3] = (x8 + x6) >> 8;\n            intermed[y8 + 4] = (x8 - x6) >> 8;\n            intermed[y8 + 5] = (x0 - x4) >> 8;\n            intermed[y8 + 6] = (x3 - x2) >> 8;\n            intermed[y8 + 7] = (x7 - x1) >> 8;\n        }\n\n        // Vertical 1-D IDCT.\n        for x in 0..8 {\n            // Similar to the horizontal 1-D IDCT case, if all the AC components are zero, then the IDCT is trivial.\n            // However, after performing the horizontal 1-D IDCT, there are typically non-zero AC components, so\n            // we do not bother to check for the all-zero case.\n\n            // Prescale.\n            let mut y0 = (intermed[(8 * 0) + x] << 8) + Wrapping(8192);\n            let mut y1 = intermed[(8 * 4) + x] << 8;\n            let mut y2 = intermed[(8 * 6) + x];\n            let mut y3 = intermed[(8 * 2) + x];\n            let mut y4 = intermed[(8 * 1) + x];\n            let mut y5 = intermed[(8 * 7) + x];\n            let mut y6 = intermed[(8 * 5) + x];\n            let mut y7 = intermed[(8 * 3) + x];\n\n            // Stage 1.\n            let mut y8 = (Wrapping(W7) * (y4 + y5)) + Wrapping(4);\n            y4 = (y8 + (Wrapping(W1MW7) * y4)) >> 3;\n            y5 = (y8 - (Wrapping(W1PW7) * y5)) >> 3;\n            y8 = (Wrapping(W3) * (y6 + y7)) + Wrapping(4);\n            y6 = (y8 - (Wrapping(W3MW5) * y6)) >> 3;\n            y7 = (y8 - (Wrapping(W3PW5) * y7)) >> 3;\n\n            // Stage 2.\n            y8 = y0 + y1;\n            y0 -= y1;\n            y1 = (Wrapping(W6) * (y3 + y2)) + Wrapping(4);\n            y2 = (y1 - (Wrapping(W2PW6) * y2)) >> 3;\n            y3 = (y1 + (Wrapping(W2MW6) * y3)) >> 3;\n            y1 = y4 + y6;\n            y4 -= y6;\n            y6 = y5 + y7;\n            y5 -= y7;\n\n            // Stage 3.\n            y7 = y8 + y3;\n            y8 -= y3;\n            y3 = y0 + y2;\n            y0 -= y2;\n            y2 = ((Wrapping(R2) * (y4 + y5)) + Wrapping(128)) >> 8;\n            y4 = ((Wrapping(R2) * (y4 - y5)) + Wrapping(128)) >> 8;\n\n            // Stage 4.\n            outp[(8 * 0) + x] = ((y7 + y1) >> 11).0 as i16;\n            outp[(8 * 1) + x] = ((y3 + y2) >> 11).0 as i16;\n            outp[(8 * 2) + x] = ((y0 + y4) >> 11).0 as i16;\n            outp[(8 * 3) + x] = ((y8 + y6) >> 11).0 as i16;\n            outp[(8 * 4) + x] = ((y8 - y6) >> 11).0 as i16;\n            outp[(8 * 5) + x] = ((y0 - y4) >> 11).0 as i16;\n            outp[(8 * 6) + x] = ((y3 - y2) >> 11).0 as i16;\n            outp[(8 * 7) + x] = ((y7 - y1) >> 11).0 as i16;\n        }\n    }\n\n    let q = AlignedBlock::new(cast(*test_q));\n    let data_tr = test_data.transpose();\n    let q_tr = q.transpose();\n\n    let mut raster: [i32x8; 8] = [0.into(); 8]; // transposed\n    for col in 0..8 {\n        raster[col] = get_c(col, &data_tr) * get_q(col, &q_tr);\n    }\n\n    let outp = run_idct(&raster);\n\n    let mut outp2 = [0; 64];\n    run_idct_old(test_data, test_q, &mut outp2, false);\n\n    assert_eq!(*outp.get_block(), outp2);\n}\n\n/// test with a simple block to catch obvious mistakes\n#[test]\npub fn test_idct_with_simple_block() {\n    let mut test_data = AlignedBlock::default();\n    let mut test_q = [1u16; 64];\n\n    test_q[0] = 2;\n    test_data.set_coefficient(0, 1000);\n    test_data.set_coefficient(1, -1000);\n\n    test_idct(&test_data, &test_q);\n}\n\n/// test with random permutations to verify that the current implementation matches the legacy\n/// implemenation from the original scalar C++ code\n#[test]\npub fn test_idct_with_random_blocks() {\n    use rand::Rng;\n\n    let mut rng = crate::helpers::get_rand_from_seed([0u8; 32]);\n    let mut test_data = AlignedBlock::default();\n    let mut test_q = [0u16; 64];\n\n    for _ in 0..16 {\n        for i in 0..64 {\n            test_data.get_block_mut()[i] = rng.gen_range(i16::MIN..=i16::MAX);\n            test_q[i] = rng.gen_range(0..=u8::MAX as u16);\n        }\n\n        test_idct(&test_data, &test_q);\n    }\n}\n\n#[cfg(any(test, feature = \"micro_benchmark\"))]\n#[inline(never)]\n/// benchmark for the coefficient writing code\npub fn benchmark_idct() -> Box<dyn FnMut()> {\n    // make some non-trivial data\n    let mut block = [i32x8::ZERO; 8];\n    for i in 0..8 {\n        block[i] = i32x8::from([1, 2, 3, 4, 5, 6, 7, 8]) * (i as i32 + 1);\n    }\n\n    Box::new(move || {\n        use std::hint::black_box;\n\n        black_box(run_idct(&block));\n    })\n}\n\n#[test]\nfn test_benchmark_idct() {\n    let mut f = benchmark_idct();\n    for _i in 0..100 {\n        f();\n    }\n}\n"
  },
  {
    "path": "lib/src/structs/lepton_decoder.rs",
    "content": "/*---------------------------------------------------------------------------------------------\n *  Copyright (c) Microsoft Corporation. All rights reserved.\n *  Licensed under the Apache License, Version 2.0. See LICENSE.txt in the project root for license information.\n *  This software incorporates material from third parties. See NOTICE.txt for details.\n *--------------------------------------------------------------------------------------------*/\n\nuse std::cmp;\nuse std::io::Read;\n\nuse bytemuck::cast_mut;\nuse default_boxed::DefaultBoxed;\nuse wide::i32x8;\n\nuse crate::Result;\nuse crate::consts::UNZIGZAG_49_TR;\nuse crate::enabled_features::EnabledFeatures;\nuse crate::helpers::u16_bit_length;\nuse crate::jpeg::block_based_image::{AlignedBlock, BlockBasedImage};\nuse crate::jpeg::jpeg_header::JpegHeader;\nuse crate::jpeg::row_spec::RowSpec;\nuse crate::jpeg::truncate_components::*;\nuse crate::lepton_error::{AddContext, ExitCode, err_exit_code};\nuse crate::metrics::Metrics;\nuse crate::structs::block_context::{BlockContext, NeighborData};\nuse crate::structs::model::{Model, ModelPerColor};\nuse crate::structs::neighbor_summary::NeighborSummary;\nuse crate::structs::probability_tables::ProbabilityTables;\nuse crate::structs::quantization_tables::QuantizationTables;\nuse crate::structs::vpx_bool_reader::VPXBoolReader;\n\n/// reads stream from reader and populates image_data with the decoded data\n/// the row_callback is called each time a full MCU row is decoded. This allows\n/// the caller to process rows as they are decoded instead of waiting for the\n/// entire image to be decoded.\n#[inline(never)] // don't inline so that the profiler can get proper data\npub fn lepton_decode_row_range<R: Read, ROW: FnMut(&RowSpec, &[BlockBasedImage]) -> Result<()>>(\n    qt: &[QuantizationTables],\n    jpeg_header: &JpegHeader,\n    trunc: &TruncateComponents,\n    reader: &mut R,\n    min_y: u32,\n    max_y: u32,\n    is_last_thread: bool,\n    full_file_compression: bool,\n    features: &EnabledFeatures,\n    mut row_callback: ROW,\n) -> Result<(Metrics, Vec<BlockBasedImage>)> {\n    let component_size_in_blocks = trunc.get_component_sizes_in_blocks();\n    let max_coded_heights = trunc.get_max_coded_heights();\n\n    let mut image_data = Vec::new();\n    for i in 0..jpeg_header.cmpc {\n        image_data.push(BlockBasedImage::new(\n            &jpeg_header,\n            i,\n            min_y,\n            if is_last_thread {\n                // if this is the last thread, then the image should extend all the way to the bottom\n                jpeg_header.cmp_info[0].bcv\n            } else {\n                max_y\n            },\n        )?);\n    }\n\n    let mut is_top_row = Vec::new();\n    let mut neighbor_summary_cache = Vec::new();\n\n    // Init helper structures\n    for i in 0..image_data.len() {\n        is_top_row.push(true);\n\n        let num_non_zeros_length = (image_data[i].get_block_width() << 1) as usize;\n\n        let mut num_non_zero_list = Vec::new();\n        num_non_zero_list.resize(num_non_zeros_length, NeighborSummary::default());\n\n        neighbor_summary_cache.push(num_non_zero_list);\n    }\n\n    let mut model = Model::default_boxed();\n    let mut bool_reader = VPXBoolReader::new(reader)?;\n\n    let mut decode_index = 0;\n\n    loop {\n        let cur_row = RowSpec::get_row_spec_from_index(\n            decode_index,\n            &image_data[..],\n            trunc.mcu_count_vertical,\n            &max_coded_heights,\n        );\n        decode_index += 1;\n\n        if cur_row.done {\n            break;\n        }\n\n        if cur_row.luma_y >= max_y && !(is_last_thread && full_file_compression) {\n            break;\n        }\n\n        if cur_row.skip {\n            continue;\n        }\n\n        if cur_row.luma_y < min_y {\n            continue;\n        }\n\n        let left_model;\n        let middle_model;\n\n        let component = cur_row.component;\n        if is_top_row[component] {\n            is_top_row[component] = false;\n\n            left_model = &super::probability_tables::NO_NEIGHBORS;\n            middle_model = &super::probability_tables::LEFT_ONLY;\n        } else {\n            left_model = &super::probability_tables::TOP_ONLY;\n            middle_model = &super::probability_tables::ALL;\n        }\n\n        decode_row_wrapper(\n            &mut model,\n            &mut bool_reader,\n            left_model,\n            middle_model,\n            ProbabilityTables::get_color_index(component),\n            &mut image_data[component],\n            &qt[component],\n            &mut neighbor_summary_cache[component],\n            cur_row.curr_y,\n            component_size_in_blocks[component],\n            features,\n        )\n        .context()?;\n\n        if cur_row.last_row_to_complete_mcu {\n            row_callback(&cur_row, &image_data[..]).context()?;\n        }\n    }\n    Ok((bool_reader.drain_stats(), image_data))\n}\n\n#[inline(never)] // don't inline so that the profiler can get proper data\nfn decode_row_wrapper<R: Read>(\n    model: &mut Model,\n    bool_reader: &mut VPXBoolReader<R>,\n    left_model: &ProbabilityTables,\n    middle_model: &ProbabilityTables,\n    color_index: usize,\n    image_data: &mut BlockBasedImage,\n    qt: &QuantizationTables,\n    neighbor_summary_cache: &mut [NeighborSummary],\n    curr_y: u32,\n    component_size_in_blocks: u32,\n    features: &EnabledFeatures,\n) -> Result<()> {\n    let mut block_context = BlockContext::off_y(curr_y, image_data);\n\n    let block_width = image_data.get_block_width();\n\n    for jpeg_x in 0..block_width {\n        let pt = if jpeg_x == 0 {\n            left_model\n        } else {\n            middle_model\n        };\n\n        if pt.is_all_present() {\n            parse_token::<R, true>(\n                model,\n                bool_reader,\n                image_data,\n                &block_context,\n                neighbor_summary_cache,\n                qt,\n                pt,\n                color_index,\n                features,\n            )\n            .context()?;\n        } else {\n            parse_token::<R, false>(\n                model,\n                bool_reader,\n                image_data,\n                &block_context,\n                neighbor_summary_cache,\n                qt,\n                pt,\n                color_index,\n                features,\n            )\n            .context()?;\n        }\n\n        let offset = block_context.next();\n\n        if offset >= component_size_in_blocks {\n            return Ok(()); // no sure if this is an error\n        }\n    }\n\n    Ok(())\n}\n\n#[inline(never)] // don't inline so that the profiler can get proper data\nfn parse_token<R: Read, const ALL_PRESENT: bool>(\n    model: &mut Model,\n    bool_reader: &mut VPXBoolReader<R>,\n    image_data: &mut BlockBasedImage,\n    context: &BlockContext,\n    neighbor_summary_cache: &mut [NeighborSummary],\n    qt: &QuantizationTables,\n    pt: &ProbabilityTables,\n    color_index: usize,\n    features: &EnabledFeatures,\n) -> Result<()> {\n    debug_assert!(pt.is_all_present() == ALL_PRESENT);\n\n    let neighbors =\n        context.get_neighbor_data::<ALL_PRESENT>(image_data, neighbor_summary_cache, pt);\n\n    let (output, ns) = read_coefficient_block::<ALL_PRESENT, R>(\n        pt,\n        color_index,\n        &neighbors,\n        model,\n        bool_reader,\n        qt,\n        features,\n    )?;\n\n    context.set_neighbor_summary_here(neighbor_summary_cache, ns);\n\n    image_data.append_block(output);\n\n    Ok(())\n}\n\n/// Reads the 8x8 coefficient block from the bit reader, taking into account the neighboring\n/// blocks, probability tables and model.\n///\n/// This function is designed to be independently callable without needing to know the context,\n/// image data, etc so it can be extensively unit tested.\npub fn read_coefficient_block<const ALL_PRESENT: bool, R: Read>(\n    pt: &ProbabilityTables,\n    color_index: usize,\n    neighbor_data: &NeighborData,\n    model: &mut Model,\n    bool_reader: &mut VPXBoolReader<R>,\n    qt: &QuantizationTables,\n    features: &EnabledFeatures,\n) -> Result<(AlignedBlock, NeighborSummary)> {\n    let model_per_color = model.get_per_color(color_index);\n\n    // First we read the 49 inner coefficients\n\n    // calculate the predictor context bin based on the neighbors\n    let num_non_zeros_7x7_context_bin =\n        pt.calc_num_non_zeros_7x7_context_bin::<ALL_PRESENT>(neighbor_data);\n\n    // read how many of these are non-zero, which is used both\n    // to terminate the loop early and as a predictor for the model\n    let num_non_zeros_7x7 =\n        model_per_color.read_non_zero_7x7_count(bool_reader, num_non_zeros_7x7_context_bin)?;\n\n    if num_non_zeros_7x7 > 49 {\n        // most likely a stream or model synchronization error\n        return err_exit_code(ExitCode::StreamInconsistent, \"numNonzeros7x7 > 49\");\n    }\n\n    let mut output = AlignedBlock::default();\n    let mut raster = [i32x8::ZERO; 8];\n    let raster_col: &mut [i32; 64] = cast_mut(&mut raster);\n\n    // these are used as predictors for the number of non-zero edge coefficients\n    // do math in 32 bits since this is faster on most platforms\n    let mut eob_x: u32 = 0;\n    let mut eob_y: u32 = 0;\n\n    let mut num_non_zeros_7x7_remaining = num_non_zeros_7x7 as usize;\n\n    if num_non_zeros_7x7_remaining > 0 {\n        let best_priors = pt.calc_coefficient_context_7x7_aavg_block::<ALL_PRESENT>(\n            neighbor_data.left,\n            neighbor_data.above,\n            neighbor_data.above_left,\n        );\n\n        // calculate the bin we are using for the number of non-zeros\n        let mut num_non_zeros_bin =\n            ProbabilityTables::num_non_zeros_to_bin_7x7(num_non_zeros_7x7_remaining);\n\n        // now loop through the coefficients in zigzag, terminating once we hit the number of non-zeros\n        for (zig49, &coord_tr) in UNZIGZAG_49_TR.iter().enumerate() {\n            let best_prior_bit_length = u16_bit_length(best_priors[coord_tr as usize]);\n\n            let coef = model_per_color.read_coef(\n                bool_reader,\n                zig49,\n                num_non_zeros_bin,\n                best_prior_bit_length as usize,\n            )?;\n\n            if coef != 0 {\n                // here we calculate the furthest x and y coordinates that have non-zero coefficients\n                // which is later used as a predictor for the number of edge coefficients\n                let by = u32::from(coord_tr) & 7;\n                let bx = u32::from(coord_tr) >> 3;\n\n                debug_assert!(bx > 0 && by > 0, \"this does the DC and the lower 7x7 AC\");\n\n                eob_x = cmp::max(eob_x, bx);\n                eob_y = cmp::max(eob_y, by);\n\n                output.set_coefficient(coord_tr as usize, coef);\n                raster_col[coord_tr as usize] = i32::from(coef)\n                    * i32::from(qt.get_quantization_table_transposed()[coord_tr as usize]);\n\n                num_non_zeros_7x7_remaining -= 1;\n                if num_non_zeros_7x7_remaining == 0 {\n                    break;\n                }\n\n                // update the bin since we've changed the number of non-zeros\n                num_non_zeros_bin =\n                    ProbabilityTables::num_non_zeros_to_bin_7x7(num_non_zeros_7x7_remaining);\n            }\n        }\n    }\n\n    if num_non_zeros_7x7_remaining > 0 {\n        return err_exit_code(\n            ExitCode::StreamInconsistent,\n            \"not enough nonzeros in 7x7 block\",\n        );\n    }\n\n    // step 2, read the edge coefficients\n    // Here we produce the first part of edge DCT coefficients predictions for neighborhood blocks\n    // and build transposed raster of dequantized DCT coefficients with 0 in DC\n    let (horiz_pred, vert_pred) = decode_edge::<R, ALL_PRESENT>(\n        neighbor_data,\n        model_per_color,\n        bool_reader,\n        &mut output,\n        qt,\n        pt,\n        num_non_zeros_7x7,\n        &mut raster,\n        eob_x as u8,\n        eob_y as u8,\n    )?;\n\n    // step 3, read the DC coefficient (0,0 of the block)\n    let q0 = qt.get_quantization_table()[0] as i32;\n    let predicted_dc = pt.adv_predict_dc_pix::<ALL_PRESENT>(&raster, q0, &neighbor_data, features);\n\n    let coef = model.read_dc(\n        bool_reader,\n        color_index,\n        predicted_dc.uncertainty,\n        predicted_dc.uncertainty2,\n    )?;\n\n    output.set_dc(ProbabilityTables::adv_predict_or_unpredict_dc(\n        coef,\n        true,\n        predicted_dc.predicted_dc,\n    ) as i16);\n\n    // neighbor summary is used as a predictor for the next block\n    let neighbor_summary = NeighborSummary::new(\n        predicted_dc.next_edge_pixels_h,\n        predicted_dc.next_edge_pixels_v,\n        output.get_dc() as i32 * q0,\n        num_non_zeros_7x7,\n        horiz_pred,\n        vert_pred,\n    );\n\n    Ok((output, neighbor_summary))\n}\n\n//#[inline(never)] // don't inline so that the profiler can get proper data\nfn decode_edge<R: Read, const ALL_PRESENT: bool>(\n    neighbor_data: &NeighborData,\n    model_per_color: &mut ModelPerColor,\n    bool_reader: &mut VPXBoolReader<R>,\n    here_mut: &mut AlignedBlock,\n    qt: &QuantizationTables,\n    pt: &ProbabilityTables,\n    num_non_zeros_7x7: u8,\n    raster: &mut [i32x8; 8],\n    eob_x: u8,\n    eob_y: u8,\n) -> Result<(i32x8, i32x8)> {\n    let num_non_zeros_bin = (num_non_zeros_7x7 + 3) / 7;\n\n    // get predictors for edge coefficients of the current block\n    let (curr_horiz_pred, curr_vert_pred) =\n        ProbabilityTables::predict_current_edges(neighbor_data, raster);\n\n    decode_one_edge::<R, ALL_PRESENT, true>(\n        model_per_color,\n        bool_reader,\n        &curr_horiz_pred.to_array(),\n        here_mut,\n        qt,\n        pt,\n        num_non_zeros_bin,\n        eob_x,\n        cast_mut(raster),\n    )?;\n    decode_one_edge::<R, ALL_PRESENT, false>(\n        model_per_color,\n        bool_reader,\n        &curr_vert_pred.to_array(),\n        here_mut,\n        qt,\n        pt,\n        num_non_zeros_bin,\n        eob_y,\n        cast_mut(raster),\n    )?;\n\n    // prepare predictors for edge coefficients of the blocks below and to the right of current one\n    let (next_horiz_pred, next_vert_pred) = ProbabilityTables::predict_next_edges(raster);\n\n    Ok((next_horiz_pred, next_vert_pred))\n}\n\nfn decode_one_edge<R: Read, const ALL_PRESENT: bool, const HORIZONTAL: bool>(\n    model_per_color: &mut ModelPerColor,\n    bool_reader: &mut VPXBoolReader<R>,\n    pred: &[i32; 8],\n    here_mut: &mut AlignedBlock,\n    qt: &QuantizationTables,\n    pt: &ProbabilityTables,\n    num_non_zeros_bin: u8,\n    est_eob: u8,\n    raster: &mut [i32; 64],\n) -> Result<()> {\n    let mut num_non_zeros_edge = model_per_color\n        .read_non_zero_edge_count::<R, HORIZONTAL>(bool_reader, est_eob, num_non_zeros_bin)\n        .context()?;\n\n    let delta;\n    let mut zig15offset;\n\n    if HORIZONTAL {\n        delta = 8;\n        zig15offset = 0;\n    } else {\n        delta = 1;\n        zig15offset = 7;\n    }\n\n    let mut coord_tr = delta;\n\n    for _lane in 0..7 {\n        if num_non_zeros_edge == 0 {\n            break;\n        }\n\n        let best_prior =\n            pt.calc_coefficient_context8_lak::<ALL_PRESENT, HORIZONTAL>(qt, coord_tr, pred)?;\n\n        let coef = model_per_color.read_edge_coefficient(\n            bool_reader,\n            qt,\n            zig15offset,\n            num_non_zeros_edge,\n            best_prior,\n        )?;\n\n        if coef != 0 {\n            num_non_zeros_edge -= 1;\n            here_mut.set_coefficient(coord_tr, coef);\n            raster[coord_tr] =\n                i32::from(coef) * i32::from(qt.get_quantization_table_transposed()[coord_tr]);\n        }\n\n        coord_tr += delta;\n        zig15offset += 1;\n    }\n\n    if num_non_zeros_edge != 0 {\n        return err_exit_code(ExitCode::StreamInconsistent, \"StreamInconsistent\");\n    }\n\n    Ok(())\n}\n"
  },
  {
    "path": "lib/src/structs/lepton_encoder.rs",
    "content": "/*---------------------------------------------------------------------------------------------\n *  Copyright (c) Microsoft Corporation. All rights reserved.\n *  Licensed under the Apache License, Version 2.0. See LICENSE.txt in the project root for license information.\n *  This software incorporates material from third parties. See NOTICE.txt for details.\n *--------------------------------------------------------------------------------------------*/\n\nuse std::cmp;\nuse std::io::Write;\n\nuse bytemuck::cast;\nuse default_boxed::DefaultBoxed;\nuse wide::i32x8;\n\nuse crate::Result;\nuse crate::consts::UNZIGZAG_49_TR;\nuse crate::enabled_features::EnabledFeatures;\nuse crate::helpers::*;\nuse crate::jpeg::block_based_image::{AlignedBlock, BlockBasedImage};\nuse crate::jpeg::row_spec::RowSpec;\nuse crate::jpeg::truncate_components::*;\nuse crate::lepton_error::{AddContext, ExitCode, err_exit_code};\nuse crate::metrics::Metrics;\nuse crate::structs::block_context::{BlockContext, NeighborData};\nuse crate::structs::model::{Model, ModelPerColor};\nuse crate::structs::neighbor_summary::NeighborSummary;\nuse crate::structs::probability_tables::ProbabilityTables;\nuse crate::structs::quantization_tables::QuantizationTables;\nuse crate::structs::vpx_bool_writer::VPXBoolWriter;\n\n#[inline(never)] // don't inline so that the profiler can get proper data\npub fn lepton_encode_row_range<W: Write>(\n    quantization_tables: &[QuantizationTables],\n    image_data: &[BlockBasedImage],\n    writer: &mut W,\n    _thread_id: i32,\n    colldata: &TruncateComponents,\n    min_y: u32,\n    max_y: u32,\n    is_last_thread: bool,\n    full_file_compression: bool,\n    features: &EnabledFeatures,\n) -> Result<Metrics> {\n    let mut model = Model::default_boxed();\n    let mut bool_writer = VPXBoolWriter::new(writer)?;\n\n    let mut is_top_row = Vec::new();\n    let mut neighbor_summary_cache = Vec::new();\n\n    // Init helper structures\n    for i in 0..image_data.len() {\n        is_top_row.push(true);\n\n        let num_non_zeros_length = (image_data[i].get_block_width() << 1) as usize;\n\n        let mut neighbor_summary_component = Vec::new();\n        neighbor_summary_component.resize(num_non_zeros_length, NeighborSummary::default());\n\n        neighbor_summary_cache.push(neighbor_summary_component);\n    }\n\n    let component_size_in_blocks = colldata.get_component_sizes_in_blocks();\n    let max_coded_heights = colldata.get_max_coded_heights();\n\n    let mut encode_index = 0;\n    loop {\n        let cur_row = RowSpec::get_row_spec_from_index(\n            encode_index,\n            image_data,\n            colldata.mcu_count_vertical,\n            &max_coded_heights,\n        );\n        encode_index += 1;\n\n        if cur_row.done {\n            break;\n        }\n\n        if cur_row.luma_y >= max_y && !(is_last_thread && full_file_compression) {\n            break;\n        }\n\n        if cur_row.skip {\n            continue;\n        }\n\n        if cur_row.luma_y < min_y {\n            continue;\n        }\n\n        // Advance to next row to cache expended block data for current row. Should be called before getting block context.\n        let component = cur_row.component;\n\n        let left_model;\n        let middle_model;\n\n        if is_top_row[component] {\n            is_top_row[component] = false;\n\n            left_model = &super::probability_tables::NO_NEIGHBORS;\n            middle_model = &super::probability_tables::LEFT_ONLY;\n        } else {\n            left_model = &super::probability_tables::TOP_ONLY;\n            middle_model = &super::probability_tables::ALL;\n        }\n\n        process_row(\n            &mut model,\n            &mut bool_writer,\n            left_model,\n            middle_model,\n            ProbabilityTables::get_color_index(component),\n            &image_data[component],\n            &quantization_tables[component],\n            &mut neighbor_summary_cache[component][..],\n            cur_row.curr_y,\n            component_size_in_blocks[component],\n            features,\n        )\n        .context()?;\n\n        bool_writer.flush_non_final_data().context()?;\n    }\n\n    if is_last_thread && full_file_compression {\n        let test = RowSpec::get_row_spec_from_index(\n            encode_index,\n            image_data,\n            colldata.mcu_count_vertical,\n            &max_coded_heights,\n        );\n\n        assert!(\n            test.skip && test.done,\n            \"Row spec test: cmp {0} luma {1} item {2} skip {3} done {4}\",\n            test.component,\n            test.luma_y,\n            test.curr_y,\n            test.skip,\n            test.done\n        );\n    }\n\n    bool_writer.finish().context()?;\n\n    Ok(bool_writer.drain_stats())\n}\n\n#[inline(never)] // don't inline so that the profiler can get proper data\nfn process_row<W: Write>(\n    model: &mut Model,\n    bool_writer: &mut VPXBoolWriter<W>,\n    left_model: &ProbabilityTables,\n    middle_model: &ProbabilityTables,\n    color_index: usize,\n    image_data: &BlockBasedImage,\n    qt: &QuantizationTables,\n    neighbor_summary_cache: &mut [NeighborSummary],\n    curr_y: u32,\n    component_size_in_block: u32,\n    features: &EnabledFeatures,\n) -> Result<()> {\n    let mut block_context = BlockContext::off_y(curr_y, image_data);\n    let block_width = image_data.get_block_width();\n\n    for jpeg_x in 0..block_width {\n        let pt: &ProbabilityTables = if jpeg_x == 0 {\n            left_model\n        } else {\n            middle_model\n        };\n\n        // shortcut all the checks for the presence of left/right components by passing a constant generic parameter\n        if pt.is_all_present() {\n            serialize_tokens::<W, true>(\n                &block_context,\n                qt,\n                pt,\n                model,\n                color_index,\n                image_data,\n                neighbor_summary_cache,\n                bool_writer,\n                features,\n            )\n            .context()?;\n        } else {\n            serialize_tokens::<W, false>(\n                &block_context,\n                qt,\n                pt,\n                model,\n                color_index,\n                image_data,\n                neighbor_summary_cache,\n                bool_writer,\n                features,\n            )\n            .context()?;\n        }\n\n        let offset = block_context.next();\n\n        if offset >= component_size_in_block {\n            return Ok(());\n        }\n    }\n\n    Ok(())\n}\n\n#[inline(never)] // don't inline so that the profiler can get proper data\nfn serialize_tokens<W: Write, const ALL_PRESENT: bool>(\n    context: &BlockContext,\n    qt: &QuantizationTables,\n    pt: &ProbabilityTables,\n    model: &mut Model,\n    color_index: usize,\n    image_data: &BlockBasedImage,\n    neighbor_summary_cache: &mut [NeighborSummary],\n    bool_writer: &mut VPXBoolWriter<W>,\n    features: &EnabledFeatures,\n) -> Result<()> {\n    debug_assert!(ALL_PRESENT == pt.is_all_present());\n\n    let block = context.here(image_data);\n\n    let neighbors =\n        context.get_neighbor_data::<ALL_PRESENT>(image_data, neighbor_summary_cache, pt);\n\n    #[cfg(feature = \"detailed_tracing\")]\n    log::trace!(\n        \"block {0}:{1:x}\",\n        context.get_here_index(),\n        block.get_hash()\n    );\n\n    let ns = write_coefficient_block::<ALL_PRESENT, W>(\n        pt,\n        color_index,\n        &neighbors,\n        block,\n        model,\n        bool_writer,\n        qt,\n        features,\n    )?;\n\n    context.set_neighbor_summary_here(neighbor_summary_cache, ns);\n\n    Ok(())\n}\n\n/// Writes the 8x8 coefficient block to the bit writer, taking into account the neighboring\n/// blocks, probability tables and model.\n///\n/// This function is designed to be independently callable without needing to know the context,\n/// image data, etc so it can be extensively unit tested.\npub fn write_coefficient_block<const ALL_PRESENT: bool, W: Write>(\n    pt: &ProbabilityTables,\n    color_index: usize,\n    neighbors_data: &NeighborData,\n    here_tr: &AlignedBlock,\n    model: &mut Model,\n    bool_writer: &mut VPXBoolWriter<W>,\n    qt: &QuantizationTables,\n    features: &EnabledFeatures,\n) -> Result<NeighborSummary> {\n    let model_per_color = model.get_per_color(color_index);\n\n    // First we encode the 49 inner coefficients\n\n    // calculate the predictor context bin based on the neighbors\n    let num_non_zeros_7x7_context_bin =\n        pt.calc_num_non_zeros_7x7_context_bin::<ALL_PRESENT>(neighbors_data);\n\n    // store how many of these coefficients are non-zero, which is used both\n    // to terminate the loop early and as a predictor for the model\n    let num_non_zeros_7x7 = here_tr.get_count_of_non_zeros_7x7();\n\n    model_per_color\n        .write_non_zero_7x7_count(\n            bool_writer,\n            num_non_zeros_7x7_context_bin,\n            num_non_zeros_7x7,\n        )\n        .context()?;\n\n    // these are used as predictors for the number of non-zero edge coefficients\n    // do math in 32 bits since this is faster on most modern platforms\n    let mut eob_x: u32 = 0;\n    let mut eob_y: u32 = 0;\n\n    let mut num_non_zeros_7x7_remaining = num_non_zeros_7x7 as usize;\n\n    if num_non_zeros_7x7_remaining > 0 {\n        let best_priors = pt.calc_coefficient_context_7x7_aavg_block::<ALL_PRESENT>(\n            neighbors_data.left,\n            neighbors_data.above,\n            neighbors_data.above_left,\n        );\n        // calculate the bin we are using for the number of non-zeros\n        let mut num_non_zeros_remaining_bin =\n            ProbabilityTables::num_non_zeros_to_bin_7x7(num_non_zeros_7x7_remaining);\n\n        // now loop through the coefficients in zigzag, terminating once we hit the number of non-zeros\n        for (zig49, &coord_tr) in UNZIGZAG_49_TR.iter().enumerate() {\n            let best_prior_bit_length = u16_bit_length(best_priors[coord_tr as usize]);\n\n            let coef = here_tr.get_coefficient(coord_tr as usize);\n\n            model_per_color\n                .write_coef(\n                    bool_writer,\n                    coef,\n                    zig49,\n                    num_non_zeros_remaining_bin,\n                    best_prior_bit_length as usize,\n                )\n                .context()?;\n\n            if coef != 0 {\n                // here we calculate the furthest x and y coordinates that have non-zero coefficients\n                // which is later used as a predictor for the number of edge coefficients\n                let by = u32::from(coord_tr) & 7;\n                let bx = u32::from(coord_tr) >> 3;\n\n                debug_assert!(bx > 0 && by > 0, \"this does the DC and the lower 7x7 AC\");\n\n                eob_x = cmp::max(eob_x, bx);\n                eob_y = cmp::max(eob_y, by);\n\n                num_non_zeros_7x7_remaining -= 1;\n                if num_non_zeros_7x7_remaining == 0 {\n                    break;\n                }\n\n                // update the bin since the number of non-zeros has changed\n                num_non_zeros_remaining_bin =\n                    ProbabilityTables::num_non_zeros_to_bin_7x7(num_non_zeros_7x7_remaining);\n            }\n        }\n    }\n\n    // Next step is the edge coefficients.\n    // Here we produce the first part of edge DCT coefficients predictions for neighborhood blocks\n    // and transposed raster of dequantized DCT coefficients with 0 in DC\n    let (raster, horiz_pred, vert_pred) = encode_edge::<W, ALL_PRESENT>(\n        neighbors_data,\n        &here_tr,\n        model_per_color,\n        bool_writer,\n        qt,\n        pt,\n        num_non_zeros_7x7,\n        eob_x as u8,\n        eob_y as u8,\n    )\n    .context()?;\n\n    // finally the DC coefficient (at 0,0)\n    let q0 = qt.get_quantization_table()[0] as i32;\n    let predicted_val =\n        pt.adv_predict_dc_pix::<ALL_PRESENT>(&raster, q0, &neighbors_data, features);\n\n    let avg_predicted_dc = ProbabilityTables::adv_predict_or_unpredict_dc(\n        here_tr.get_dc(),\n        false,\n        predicted_val.predicted_dc,\n    );\n\n    if here_tr.get_dc() as i32\n        != ProbabilityTables::adv_predict_or_unpredict_dc(\n            avg_predicted_dc as i16,\n            true,\n            predicted_val.predicted_dc,\n        )\n    {\n        return err_exit_code(ExitCode::CoefficientOutOfRange, \"BlockDC mismatch\");\n    }\n\n    model\n        .write_dc(\n            bool_writer,\n            color_index,\n            avg_predicted_dc as i16,\n            predicted_val.uncertainty,\n            predicted_val.uncertainty2,\n        )\n        .context()?;\n\n    // neighbor summary is used as a predictor for the next block\n    let neighbor_summary = NeighborSummary::new(\n        predicted_val.next_edge_pixels_h,\n        predicted_val.next_edge_pixels_v,\n        here_tr.get_dc() as i32 * q0,\n        num_non_zeros_7x7,\n        horiz_pred,\n        vert_pred,\n    );\n\n    Ok(neighbor_summary)\n}\n\n#[inline(never)] // don't inline so that the profiler can get proper data\nfn encode_edge<W: Write, const ALL_PRESENT: bool>(\n    neighbors_data: &NeighborData,\n    here_tr: &AlignedBlock,\n    model_per_color: &mut ModelPerColor,\n    bool_writer: &mut VPXBoolWriter<W>,\n    qt: &QuantizationTables,\n    pt: &ProbabilityTables,\n    num_non_zeros_7x7: u8,\n    eob_x: u8,\n    eob_y: u8,\n) -> Result<([i32x8; 8], i32x8, i32x8)> {\n    let q_tr = qt.get_quantization_table_transposed();\n\n    let mut raster_co = [0i32; 64];\n    for i in 1..64 {\n        raster_co[i] = i32::from(here_tr.get_coefficient(i)) * i32::from(q_tr[i]);\n    }\n\n    let raster: [i32x8; 8] = cast(raster_co);\n\n    // get predictors for edge coefficients of the current block\n    let (curr_horiz_pred, curr_vert_pred) =\n        ProbabilityTables::predict_current_edges(neighbors_data, &raster);\n\n    let num_non_zeros_bin = (num_non_zeros_7x7 + 3) / 7;\n\n    encode_one_edge::<W, ALL_PRESENT, true>(\n        here_tr,\n        model_per_color,\n        bool_writer,\n        &curr_horiz_pred.to_array(),\n        qt,\n        pt,\n        num_non_zeros_bin,\n        eob_x,\n    )\n    .context()?;\n\n    encode_one_edge::<W, ALL_PRESENT, false>(\n        here_tr,\n        model_per_color,\n        bool_writer,\n        &curr_vert_pred.to_array(),\n        qt,\n        pt,\n        num_non_zeros_bin,\n        eob_y,\n    )\n    .context()?;\n\n    // prepare predictors for edge coefficients of the blocks below and to the right of current one\n    let (next_horiz_pred, next_vert_pred) = ProbabilityTables::predict_next_edges(&raster);\n\n    Ok((raster, next_horiz_pred, next_vert_pred))\n}\n\nfn count_non_zero(v: i16) -> u8 {\n    if v == 0 { 0 } else { 1 }\n}\n\nfn encode_one_edge<W: Write, const ALL_PRESENT: bool, const HORIZONTAL: bool>(\n    block: &AlignedBlock,\n    model_per_color: &mut ModelPerColor,\n    bool_writer: &mut VPXBoolWriter<W>,\n    pred: &[i32; 8],\n    qt: &QuantizationTables,\n    pt: &ProbabilityTables,\n    num_non_zeros_bin: u8,\n    est_eob: u8,\n) -> Result<()> {\n    let mut num_non_zeros_edge;\n\n    if !HORIZONTAL {\n        num_non_zeros_edge = count_non_zero(block.get_coefficient(1))\n            + count_non_zero(block.get_coefficient(2))\n            + count_non_zero(block.get_coefficient(3))\n            + count_non_zero(block.get_coefficient(4))\n            + count_non_zero(block.get_coefficient(5))\n            + count_non_zero(block.get_coefficient(6))\n            + count_non_zero(block.get_coefficient(7));\n    } else {\n        num_non_zeros_edge = count_non_zero(block.get_coefficient(1 * 8))\n            + count_non_zero(block.get_coefficient(2 * 8))\n            + count_non_zero(block.get_coefficient(3 * 8))\n            + count_non_zero(block.get_coefficient(4 * 8))\n            + count_non_zero(block.get_coefficient(5 * 8))\n            + count_non_zero(block.get_coefficient(6 * 8))\n            + count_non_zero(block.get_coefficient(7 * 8));\n    }\n\n    model_per_color\n        .write_non_zero_edge_count::<W, HORIZONTAL>(\n            bool_writer,\n            est_eob,\n            num_non_zeros_bin,\n            num_non_zeros_edge,\n        )\n        .context()?;\n\n    let delta;\n    let mut zig15offset;\n\n    if HORIZONTAL {\n        delta = 8;\n        zig15offset = 0;\n    } else {\n        delta = 1;\n        zig15offset = 7;\n    }\n\n    let mut coord_tr = delta;\n\n    for _lane in 0..7 {\n        if num_non_zeros_edge == 0 {\n            break;\n        }\n\n        let best_prior =\n            pt.calc_coefficient_context8_lak::<ALL_PRESENT, HORIZONTAL>(qt, coord_tr, pred)?;\n\n        let coef = block.get_coefficient(coord_tr);\n\n        model_per_color\n            .write_edge_coefficient(\n                bool_writer,\n                qt,\n                coef,\n                zig15offset,\n                num_non_zeros_edge,\n                best_prior,\n            )\n            .context()?;\n\n        if coef != 0 {\n            num_non_zeros_edge -= 1;\n        }\n\n        coord_tr += delta;\n        zig15offset += 1;\n    }\n\n    Ok(())\n}\n\n/// simplest case, all zeros. The goal of these test cases is go from simplest to most\n/// complicated so if tests start failing, you have some idea of where to start looking.\n#[test]\nfn roundtrip_zeros() {\n    let block = AlignedBlock::new([0; 64]);\n\n    roundtrip_read_write_coefficients(\n        &block,\n        &block,\n        &block,\n        &block,\n        [1; 64],\n        0x4154B63BDE6F2912,\n        &EnabledFeatures::compat_lepton_vector_read(),\n    );\n}\n\n/// tests blocks with only DC coefficient set\n#[test]\nfn roundtrip_dc_only() {\n    let mut block = AlignedBlock::new([0; 64]);\n    block.set_dc(-100);\n\n    roundtrip_read_write_coefficients(\n        &block,\n        &block,\n        &block,\n        &block,\n        [1; 64],\n        0x2556719DE605BB41,\n        &EnabledFeatures::compat_lepton_vector_read(),\n    );\n}\n\n/// tests blocks with only edge coefficients set\n#[test]\nfn roundtrip_edges_only() {\n    let mut block = AlignedBlock::new([0; 64]);\n    for i in 1..7 {\n        block.set_coefficient(i, -100);\n        block.set_coefficient(i * 8, 100);\n    }\n\n    roundtrip_read_write_coefficients(\n        &block,\n        &block,\n        &block,\n        &block,\n        [1; 64],\n        0x91061AE0FBE7C626,\n        &EnabledFeatures::compat_lepton_vector_read(),\n    );\n}\n\n/// tests blocks with only 7x7 coefficients set\n#[test]\nfn roundtrip_ac_only() {\n    let mut block = AlignedBlock::new([0; 64]);\n    for i in 0..64 {\n        let x = i & 7;\n        let y = i >> 3;\n\n        if x > 0 && y > 0 {\n            block.set_coefficient(i, (x * y) as i16);\n        }\n    }\n\n    roundtrip_read_write_coefficients(\n        &block,\n        &block,\n        &block,\n        &block,\n        [1; 64],\n        0x9F5637364D41FE11,\n        &EnabledFeatures::compat_lepton_vector_read(),\n    );\n}\n\n#[test]\nfn roundtrip_ones() {\n    let block = AlignedBlock::new([1; 64]);\n\n    roundtrip_read_write_coefficients(\n        &block,\n        &block,\n        &block,\n        &block,\n        [1; 64],\n        0x6B2A9E7E1DA9A4B3,\n        &EnabledFeatures::compat_lepton_vector_read(),\n    );\n}\n\n/// test large coefficients that could overflow unpredictably if there are changes to the\n/// way the math operations are performed (for example overflow or bitness)\n#[test]\nfn roundtrip_large_coef() {\n    // largest coefficient that doesn't cause a DC overflow\n    let block = AlignedBlock::new([-1010; 64]);\n\n    roundtrip_read_write_coefficients(\n        &block,\n        &block,\n        &block,\n        &block,\n        [1; 64],\n        0x95CBDD4F7D7B72EB,\n        &EnabledFeatures::compat_lepton_vector_read(),\n    );\n\n    // now test with maximum quantization table. In theory this is legal according\n    // the JPEG format and there is no code preventing this from being attempted\n    // by the encoder.\n\n    roundtrip_read_write_coefficients(\n        &block,\n        &block,\n        &block,\n        &block,\n        [65535; 64],\n        0xE514715BD531D80E,\n        &EnabledFeatures::compat_lepton_vector_read(),\n    );\n}\n\n/// \"random\" set of blocks to ensure that all ranges of coefficients work properly\n#[test]\nfn roundtrip_random_seed() {\n    use rand::Rng;\n\n    // the 127 seed is a choice that doesn't overflow the DC coefficient\n    // since the encoder is somewhat picky if the DC estimate overflows\n    // it also has different behavior for 32 and 16 bit codepath\n    let mut rng = crate::helpers::get_rand_from_seed([127; 32]);\n\n    let arr = [0i16; 64];\n\n    let left = AlignedBlock::new(arr.map(|_| rng.gen_range(-2047..=2047)));\n    let above = AlignedBlock::new(arr.map(|_| rng.gen_range(-2047..=2047)));\n    let here = AlignedBlock::new(arr.map(|_| rng.gen_range(-2047..=2047)));\n    let above_left = AlignedBlock::new(arr.map(|_| rng.gen_range(-2047..=2047)));\n    let qt = arr.map(|_| rng.gen_range(1u16..=65535));\n\n    // using 32 bit math (test emulating both scalar and vector C++ code)\n    let a = roundtrip_read_write_coefficients(\n        &left,\n        &above,\n        &above_left,\n        &here,\n        qt,\n        0x146C568A90EB0F14,\n        &EnabledFeatures::compat_lepton_scalar_read(),\n    );\n\n    // using 16 bit math\n    let b = roundtrip_read_write_coefficients(\n        &left,\n        &above,\n        &above_left,\n        &here,\n        qt,\n        0x12ECA3C71A29300C,\n        &EnabledFeatures::compat_lepton_vector_read(),\n    );\n\n    assert!(a != b);\n}\n\n/// tests a pattern where all the coefficients are unique to make sure we don't mix up anything\n#[test]\nfn roundtrip_unique() {\n    let mut arr = [0; 64];\n    for i in 0..64 {\n        arr[i] = i as i16;\n    }\n\n    let left = AlignedBlock::new(arr);\n    let above = AlignedBlock::new(arr.map(|x| x + 64));\n    let above_left = AlignedBlock::new(arr.map(|x| x + 128));\n    let here = AlignedBlock::new(arr.map(|x| x + 256));\n\n    roundtrip_read_write_coefficients(\n        &left,\n        &above,\n        &above_left,\n        &here,\n        [1; 64],\n        0x8FA72ED7E5961A1C,\n        &EnabledFeatures::compat_lepton_vector_read(),\n    );\n}\n\n/// tests a pattern to check the non-zero counting\n#[test]\nfn roundtrip_non_zeros_counts() {\n    let mut arr = [0; 64];\n\n    // upper left corner is all 50, the rest is 0\n    // this should result in 3 or 4 non-zero coefficients\n    // (depending on vertical/horizontal, make this non-symetrical to catch mixups)\n    for i in 0..64 {\n        let x = i & 7;\n        let y = i >> 3;\n\n        arr[i] = if x < 4 && y < 3 { 50 } else { 0 };\n    }\n\n    let block = AlignedBlock::new(arr);\n\n    roundtrip_read_write_coefficients(\n        &block,\n        &block,\n        &block,\n        &block,\n        [1; 64],\n        0x6C93F3EF5495440B,\n        &EnabledFeatures::compat_lepton_vector_read(),\n    );\n}\n\n/// randomizes the branches of the model so that we don't start with a\n/// state where all the branches are in the same state. This is important\n/// to catch any misaligment in the model state between reading and writing.\n#[cfg(test)]\nfn make_random_model() -> Box<Model> {\n    let mut model = Model::default_boxed();\n\n    use rand::Rng;\n\n    let mut rng = crate::helpers::get_rand_from_seed([2u8; 32]);\n\n    model.walk(|x| {\n        x.set_count(rng.gen_range(0x01..=0xff) * 256 + rng.gen_range(0x01..=0xff));\n    });\n    model\n}\n\n/// tests the roundtrip of reading and writing coefficients\n///\n/// The tests are done with a seeded random model so that the tests are deterministic.\n///\n/// In addition, we check to make that everything ran as expected by comparing the\n/// hash of the output to a verified output. This verified output is generated by\n/// hashing the output plus the new state of the model.\n#[cfg(test)]\nfn roundtrip_read_write_coefficients(\n    left: &AlignedBlock,\n    above: &AlignedBlock,\n    above_left: &AlignedBlock,\n    here: &AlignedBlock,\n    qt: [u16; 64],\n    verified_output: u64,\n    features: &EnabledFeatures,\n) -> u64 {\n    use std::hash::Hasher;\n    use std::io::{Cursor, Read};\n\n    // use the Sip hasher directly since that's guaranteed not to change implementation vs the default hasher\n    use siphasher::sip::SipHasher13;\n\n    use crate::jpeg::block_based_image::EMPTY_BLOCK;\n    use crate::structs::lepton_decoder::read_coefficient_block;\n    use crate::structs::neighbor_summary::NEIGHBOR_DATA_EMPTY;\n    use crate::structs::vpx_bool_reader::VPXBoolReader;\n\n    let mut write_model = make_random_model();\n\n    let mut buffer = Vec::new();\n\n    let mut bool_writer = VPXBoolWriter::new(&mut buffer).unwrap();\n\n    let qt = QuantizationTables::new_from_table(&qt);\n\n    /// This is a helper function to avoid having to duplicate the code for the different cases.\n    fn call_write_coefficient_block<W: Write>(\n        left: Option<(&AlignedBlock, &NeighborSummary)>,\n        above: Option<(&AlignedBlock, &NeighborSummary)>,\n        above_left: Option<&AlignedBlock>,\n        color_index: usize,\n        here: &AlignedBlock,\n        write_model: &mut Model,\n        bool_writer: &mut VPXBoolWriter<W>,\n        qt: &QuantizationTables,\n        features: &EnabledFeatures,\n    ) -> NeighborSummary {\n        let pt = ProbabilityTables::new(left.is_some(), above.is_some());\n        let n = NeighborData {\n            above: &above.map(|x| x.0).unwrap_or(&EMPTY_BLOCK).transpose(),\n            left: &left.map(|x| x.0).unwrap_or(&EMPTY_BLOCK).transpose(),\n            above_left: &above_left.unwrap_or(&EMPTY_BLOCK).transpose(),\n            neighbor_context_above: above.map(|x| x.1).unwrap_or(&NEIGHBOR_DATA_EMPTY),\n            neighbor_context_left: left.map(|x| x.1).unwrap_or(&NEIGHBOR_DATA_EMPTY),\n        };\n\n        let here_tr = here.transpose();\n\n        // call the right version depending on if we have all neighbors or not\n        if left.is_some() && above.is_some() {\n            write_coefficient_block::<true, _>(\n                &pt,\n                color_index,\n                &n,\n                &here_tr,\n                write_model,\n                bool_writer,\n                qt,\n                features,\n            )\n            .unwrap()\n        } else {\n            write_coefficient_block::<false, _>(\n                &pt,\n                color_index,\n                &n,\n                &here_tr,\n                write_model,\n                bool_writer,\n                qt,\n                features,\n            )\n            .unwrap()\n        }\n    }\n\n    /// This is a helper function to avoid having to duplicate the code for the different cases.\n    fn call_read_coefficient_block<R: Read>(\n        left: Option<(&AlignedBlock, &NeighborSummary)>,\n        above: Option<(&AlignedBlock, &NeighborSummary)>,\n        above_left: Option<&AlignedBlock>,\n        color_index: usize,\n        read_model: &mut Model,\n        bool_reader: &mut VPXBoolReader<R>,\n        qt: &QuantizationTables,\n        features: &EnabledFeatures,\n    ) -> (AlignedBlock, NeighborSummary) {\n        let pt = ProbabilityTables::new(left.is_some(), above.is_some());\n        let n = NeighborData {\n            above: &above.map(|x| x.0).unwrap_or(&EMPTY_BLOCK).transpose(),\n            left: &left.map(|x| x.0).unwrap_or(&EMPTY_BLOCK).transpose(),\n            above_left: &above_left.unwrap_or(&EMPTY_BLOCK).transpose(),\n            neighbor_context_above: above.map(|x| x.1).unwrap_or(&NEIGHBOR_DATA_EMPTY),\n            neighbor_context_left: left.map(|x| x.1).unwrap_or(&NEIGHBOR_DATA_EMPTY),\n        };\n\n        // call the right version depending on if we have all neighbors or not\n        let r = if left.is_some() && above.is_some() {\n            read_coefficient_block::<true, _>(\n                &pt,\n                color_index,\n                &n,\n                read_model,\n                bool_reader,\n                qt,\n                features,\n            )\n            .unwrap()\n        } else {\n            read_coefficient_block::<false, _>(\n                &pt,\n                color_index,\n                &n,\n                read_model,\n                bool_reader,\n                qt,\n                features,\n            )\n            .unwrap()\n        };\n\n        (r.0.transpose(), r.1)\n    }\n\n    // overall idea here is to call write and read on all possible permutations of neighbors\n    // the grid looks like this:\n    //\n    // [ above_left ] [ above ]\n    // [ left       ] [ here  ]\n    //\n    // first: above_left (with no neighbors)\n\n    let color_index = 0;\n\n    let w_above_left_ns = call_write_coefficient_block(\n        None,\n        None,\n        None,\n        color_index,\n        &above_left,\n        &mut write_model,\n        &mut bool_writer,\n        &qt,\n        &features,\n    );\n\n    // now above, with above_left as neighbor\n    let w_above_ns = call_write_coefficient_block(\n        Some((&above_left, &w_above_left_ns)),\n        None,\n        None,\n        color_index,\n        &above,\n        &mut write_model,\n        &mut bool_writer,\n        &qt,\n        &features,\n    );\n\n    // now left with above_left as neighbor\n    let w_left_ns = call_write_coefficient_block(\n        None,\n        Some((&above_left, &w_above_left_ns)),\n        None,\n        color_index,\n        &left,\n        &mut write_model,\n        &mut bool_writer,\n        &qt,\n        &features,\n    );\n\n    // now here with above and left as neighbors\n    let w_here_ns = call_write_coefficient_block(\n        Some((&left, &w_left_ns)),\n        Some((&above, &w_above_ns)),\n        Some(above_left),\n        color_index,\n        &here,\n        &mut write_model,\n        &mut bool_writer,\n        &qt,\n        &features,\n    );\n\n    bool_writer.finish().unwrap();\n\n    // now re-read the model and make sure everything matches\n    let mut read_model = make_random_model();\n    let mut bool_reader = VPXBoolReader::new(Cursor::new(&buffer)).unwrap();\n\n    let (r_above_left_block, r_above_left_ns) = call_read_coefficient_block(\n        None,\n        None,\n        None,\n        color_index,\n        &mut read_model,\n        &mut bool_reader,\n        &qt,\n        &features,\n    );\n\n    assert_eq!(\n        r_above_left_block.get_block(),\n        above_left.get_block(),\n        \"above_left\"\n    );\n    assert_eq!(r_above_left_ns, w_above_left_ns, \"above_left_ns\");\n\n    let (r_above_block, r_above_ns) = call_read_coefficient_block(\n        Some((&r_above_left_block, &w_above_left_ns)),\n        None,\n        None,\n        color_index,\n        &mut read_model,\n        &mut bool_reader,\n        &qt,\n        &features,\n    );\n\n    assert_eq!(r_above_block.get_block(), above.get_block(), \"above\");\n    assert_eq!(r_above_ns, w_above_ns, \"above_ns\");\n\n    let (r_left_block, r_left_ns) = call_read_coefficient_block(\n        None,\n        Some((&r_above_left_block, &r_above_left_ns)),\n        None,\n        color_index,\n        &mut read_model,\n        &mut bool_reader,\n        &qt,\n        &features,\n    );\n\n    assert_eq!(r_left_block.get_block(), left.get_block(), \"left\");\n    assert_eq!(r_left_ns, w_left_ns, \"left_ns\");\n\n    let (r_here, r_here_ns) = call_read_coefficient_block(\n        Some((&r_left_block, &r_left_ns)),\n        Some((&r_above_block, &r_above_ns)),\n        Some(above_left),\n        color_index,\n        &mut read_model,\n        &mut bool_reader,\n        &qt,\n        &features,\n    );\n\n    assert_eq!(r_here.get_block(), here.get_block(), \"here\");\n    assert_eq!(r_here_ns, w_here_ns, \"here_ns\");\n\n    assert_eq!(write_model.model_checksum(), read_model.model_checksum());\n\n    let mut h = SipHasher13::new();\n    h.write(&buffer);\n    h.write_u64(write_model.model_checksum());\n    let hash = h.finish();\n\n    println!(\"0x{:x?},\", hash);\n\n    if verified_output != 0 {\n        assert_eq!(\n            verified_output, hash,\n            \"Hash mismatch. Unexpected change in model behavior/output format\"\n        );\n    }\n\n    hash\n}\n\n#[cfg(any(test, feature = \"micro_benchmark\"))]\n#[inline(never)]\n/// benchmark for the coefficient reading and writing code\npub fn benchmark_roundtrip_coefficient() -> Box<dyn FnMut()> {\n    use crate::structs::lepton_decoder::read_coefficient_block;\n    use crate::structs::vpx_bool_reader::VPXBoolReader;\n    use std::{hint::black_box, io::Cursor};\n    use wide::i16x8;\n\n    fn make_block(i: &mut i16) -> AlignedBlock {\n        let mut arr = [0; 64];\n        for v in arr.iter_mut() {\n            *v = *i;\n            *i += 1;\n        }\n        AlignedBlock::new(arr)\n    }\n\n    let mut counter = 1;\n\n    let mut write_model = Model::default_boxed();\n    let mut read_model = Model::default_boxed();\n\n    let qt = QuantizationTables::new_from_table(&[1; 64]);\n\n    let a = make_block(&mut counter);\n    let l = make_block(&mut counter);\n    let al = make_block(&mut counter);\n\n    let edge_pixels_h = i16x8::from([4; 8]);\n    let edge_pixels_v = i16x8::from([5; 8]);\n    let dc_deq = 6 * 1; // quantization table value is\n    let num_non_zeros_7x7 = 49;\n    let horiz_pred = i32x8::from([7; 8]);\n    let vert_pred = i32x8::from([8; 8]);\n\n    let na = NeighborSummary::new(\n        edge_pixels_h,\n        edge_pixels_v,\n        dc_deq,\n        num_non_zeros_7x7,\n        horiz_pred,\n        vert_pred,\n    );\n    let nl = NeighborSummary::new(\n        edge_pixels_h,\n        edge_pixels_v,\n        dc_deq,\n        num_non_zeros_7x7,\n        horiz_pred,\n        vert_pred,\n    );\n\n    let pt = ProbabilityTables::new(true, true);\n    let here_tr = make_block(&mut counter);\n    let features = EnabledFeatures::compat_lepton_vector_read();\n\n    Box::new(move || {\n        let n = NeighborData {\n            above: &a,\n            left: &l,\n            above_left: &al,\n            neighbor_context_above: &na,\n            neighbor_context_left: &nl,\n        };\n\n        let mut buffer = Vec::with_capacity(100);\n        let mut bool_writer = VPXBoolWriter::new(&mut buffer).unwrap();\n\n        write_coefficient_block::<true, _>(\n            &pt,\n            0,\n            &n,\n            &here_tr,\n            &mut write_model,\n            &mut bool_writer,\n            &qt,\n            &features,\n        )\n        .unwrap();\n\n        bool_writer.finish().unwrap();\n\n        let mut bool_reader = VPXBoolReader::new(Cursor::new(&buffer)).unwrap();\n        let (block_tr, summary) = read_coefficient_block::<true, _>(\n            &pt,\n            0,\n            &n,\n            &mut read_model,\n            &mut bool_reader,\n            &qt,\n            &features,\n        )\n        .unwrap();\n\n        black_box(summary);\n        debug_assert_eq!(here_tr.get_block(), block_tr.get_block());\n    })\n}\n\n#[test]\nfn test_benchmark_roundtrip_coefficient() {\n    let mut f = benchmark_roundtrip_coefficient();\n    for _i in 0..100 {\n        f();\n    }\n}\n"
  },
  {
    "path": "lib/src/structs/lepton_file_reader.rs",
    "content": "/*---------------------------------------------------------------------------------------------\n *  Copyright (c) Microsoft Corporation. All rights reserved.\n *  Licensed under the Apache License, Version 2.0. See LICENSE.txt in the project root for license information.\n *  This software incorporates material from third parties. See NOTICE.txt for details.\n *--------------------------------------------------------------------------------------------*/\n\nuse std::collections::VecDeque;\nuse std::io::{BufRead, Cursor, Read, Write};\nuse std::mem;\nuse std::sync::mpsc::Sender;\n\nuse default_boxed::DefaultBoxed;\n#[cfg(feature = \"detailed_tracing\")]\nuse log::info;\nuse log::warn;\n\nuse crate::enabled_features::EnabledFeatures;\nuse crate::jpeg::block_based_image::BlockBasedImage;\nuse crate::jpeg::jpeg_code;\nuse crate::jpeg::jpeg_header::{JpegHeader, ReconstructionInfo, RestartSegmentCodingInfo};\nuse crate::jpeg::jpeg_write::{JpegIncrementalWriter, jpeg_write_entire_scan};\nuse crate::lepton_error::{AddContext, ExitCode, Result, err_exit_code};\nuse crate::metrics::{CpuTimeMeasure, Metrics};\nuse crate::structs::lepton_decoder::lepton_decode_row_range;\nuse crate::structs::lepton_header::{FIXED_HEADER_SIZE, LeptonHeader};\nuse crate::structs::multiplexer::{\n    MultiplexReadResult, MultiplexReader, MultiplexReaderState, multiplex_read,\n};\nuse crate::structs::partial_buffer::PartialBuffer;\nuse crate::structs::quantization_tables::QuantizationTables;\nuse crate::structs::simple_threadpool::ThreadPoolHolder;\nuse crate::structs::thread_handoff::ThreadHandoff;\nuse crate::{LeptonThreadPool, consts::*};\n\n/// Reads an entire lepton file and writes it out as a JPEG\n///\n/// # Parameters\n///\n/// - `reader`: A buffered reader from which the Lepton-encoded data is read.\n/// - `writer`: A writer to which the decoded JPEG image is written.\n/// - `enabled_features`: A set of toggles for enabling/disabling decoding features/restrictions.\n/// - `thread_pool`: A reference to a thread pool used for parallel processing. Must be a static reference and\n/// can point to `DEFAULT_THREAD_POOL`.\npub fn decode_lepton<R: BufRead, W: Write>(\n    reader: &mut R,\n    writer: &mut W,\n    enabled_features: &EnabledFeatures,\n    thread_pool: &dyn LeptonThreadPool,\n) -> Result<Metrics> {\n    let mut decoder =\n        LeptonFileReader::new(enabled_features.clone(), ThreadPoolHolder::Dyn(thread_pool));\n\n    loop {\n        let buffer = reader.fill_buf().context()?;\n\n        decoder\n            .process_buffer(buffer, buffer.len() == 0, writer)\n            .context()?;\n\n        if buffer.len() == 0 {\n            break;\n        }\n\n        let amt = buffer.len();\n        reader.consume(amt);\n    }\n\n    return Ok(decoder.take_metrics());\n}\n\n/// this is a debug function only called by the utility EXE code\n/// used to dump the contents of the file\n#[allow(dead_code)]\npub fn decode_lepton_file_image<R: BufRead>(\n    reader: &mut R,\n    enabled_features: &EnabledFeatures,\n    thread_pool: &dyn LeptonThreadPool,\n) -> Result<(Box<LeptonHeader>, Vec<BlockBasedImage>)> {\n    let mut lh = LeptonHeader::default_boxed();\n    let mut enabled_features = enabled_features.clone();\n\n    let mut fixed_header_buffer = [0; FIXED_HEADER_SIZE];\n    reader.read_exact(&mut fixed_header_buffer).context()?;\n\n    let compressed_header_size = lh\n        .read_lepton_fixed_header(&fixed_header_buffer, &mut enabled_features)\n        .context()?;\n\n    lh.read_compressed_lepton_header(reader, &mut enabled_features, compressed_header_size)\n        .context()?;\n\n    let mut buf = [0; 3];\n    reader.read_exact(&mut buf).context()?;\n\n    if buf != LEPTON_HEADER_COMPLETION_MARKER {\n        return err_exit_code(ExitCode::BadLeptonFile, \"CMP marker not found\");\n    }\n\n    let mut state = LeptonFileReader::run_lepton_decoder_threads(\n        &lh,\n        &enabled_features,\n        4,\n        thread_pool,\n        progressive_decoding_thread,\n    )\n    .context()?;\n\n    let mut results = Vec::new();\n\n    // process the rest of the file (except for the 4 byte EOF marker)\n    let mut extra_buffer = Vec::new();\n    loop {\n        let b = reader.fill_buf().context()?;\n        let b_len = b.len();\n        if b_len == 0 {\n            break;\n        }\n        state.process_buffer(&mut PartialBuffer::new(b, &mut extra_buffer))?;\n        reader.consume(b_len);\n\n        if let Some(r) = state.retrieve_result(false)? {\n            results.push(r);\n        }\n    }\n\n    while let Some(r) = state.retrieve_result(true)? {\n        results.push(r);\n    }\n\n    // merge the corresponding components so that we get a single set of coefficient maps (since each thread did a piece of the work)\n    let num_components = results[0].len();\n\n    let mut block_image = Vec::new();\n    for i in 0..num_components {\n        block_image.push(BlockBasedImage::merge(&mut results, i).context()?);\n    }\n\n    Ok((lh, block_image))\n}\n\nenum DecoderState {\n    FixedHeader(),\n    CompressedHeader(usize),\n    CMP(),\n    ScanProgressive(MultiplexReaderState<Vec<BlockBasedImage>>),\n    ScanBaseline(MultiplexReaderState<Vec<u8>>),\n    EOI,\n}\n\n/// A writer that limits the amount of data written to a specified amount, silently truncating any excess data.\n///\n/// This is used to ensure that we do not write more data than the expected JPEG file size during decoding.\nstruct LimitedOutputWriter<'a, W: Write> {\n    inner: &'a mut W,\n    amount_left: &'a mut u64,\n}\n\nimpl<W: Write> Write for LimitedOutputWriter<'_, W> {\n    fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {\n        // only write up to the amount left\n        let to_write = std::cmp::min(buf.len() as u64, *self.amount_left) as usize;\n        let written = self.inner.write(&buf[0..to_write])?;\n\n        if written < to_write {\n            // short write, propagate since we haven't hit the limit yet\n            *self.amount_left -= written as u64;\n            Ok(written)\n        } else {\n            *self.amount_left -= written as u64;\n\n            // always say we wrote everything, the goal here is to silently truncate\n            Ok(buf.len())\n        }\n    }\n\n    fn flush(&mut self) -> std::io::Result<()> {\n        self.inner.flush()\n    }\n}\n\n/// Writes to a fixed size output buffer and queues up any extra data\n/// that doesn't fit and writes it out first on the next call.\nstruct FixedBufferOuputWriter<'a> {\n    amount_written: usize,\n    output_buffer: &'a mut [u8],\n    extra_queue: &'a mut VecDeque<u8>,\n}\n\nimpl Write for FixedBufferOuputWriter<'_> {\n    fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {\n        let amount_for_output = buf\n            .len()\n            .min(self.output_buffer.len() - self.amount_written);\n\n        self.output_buffer[self.amount_written..self.amount_written + amount_for_output]\n            .copy_from_slice(&buf[..amount_for_output]);\n        self.amount_written += amount_for_output;\n\n        if amount_for_output < buf.len() {\n            self.extra_queue.extend(&buf[amount_for_output..]);\n        }\n        Ok(buf.len())\n    }\n\n    fn flush(&mut self) -> std::io::Result<()> {\n        // nothing to do since we don't buffer anything\n        Ok(())\n    }\n}\n\n/// This is the state machine for the decoder for reading lepton files. The\n/// data is pushed into the state machine and processed in chuncks. Once\n/// the calculations are done the data is retrieved from the output buffers.\npub struct LeptonFileReader<'a> {\n    state: DecoderState,\n    lh: Box<LeptonHeader>,\n    enabled_features: EnabledFeatures,\n    extra_buffer: Vec<u8>,\n    metrics: Metrics,\n    total_read_size: u64,\n    jpeg_file_size_left: u64,\n    input_complete: bool,\n    thread_pool: ThreadPoolHolder<'a>,\n}\n\nimpl<'a> LeptonFileReader<'a> {\n    /// Creates a new LeptonFileReader.\n    pub fn new(features: EnabledFeatures, thread_pool: ThreadPoolHolder<'a>) -> Self {\n        LeptonFileReader {\n            state: DecoderState::FixedHeader(),\n            lh: LeptonHeader::default_boxed(),\n            enabled_features: features,\n            extra_buffer: Vec::new(),\n            metrics: Metrics::default(),\n            total_read_size: 0,\n            input_complete: false,\n            jpeg_file_size_left: 0,\n            thread_pool,\n        }\n    }\n\n    /// Processes a buffer of data of the file, which can be a slice of 0 or more characters.\n    /// If the input is complete, then input_complete should be set to true.\n    ///\n    /// Any available output is written to the output buffer, which can be zero if the\n    /// input is not yet complete. Once the input has been marked as complete, the\n    /// call will return all remaining output.\n    ///\n    /// # Arguments\n    /// * `input` - The input buffer to process.\n    /// * `input_complete` - True if the input is complete and no more data will be provided.\n    /// * `writer` - The writer to write the output to.\n    pub fn process_buffer(\n        &mut self,\n        in_buffer: &[u8],\n        input_complete: bool,\n        output: &mut impl Write,\n    ) -> Result<()> {\n        if self.input_complete && in_buffer.len() > 0 {\n            return err_exit_code(\n                ExitCode::SyntaxError,\n                \"ERROR: input was marked as complete but more data was provided\",\n            );\n        }\n\n        self.total_read_size += in_buffer.len() as u64;\n\n        let mut in_buffer = PartialBuffer::new(in_buffer, &mut self.extra_buffer);\n        while in_buffer.continue_processing() {\n            match &mut self.state {\n                DecoderState::FixedHeader() => {\n                    if let Some(v) = in_buffer.take(FIXED_HEADER_SIZE, 0) {\n                        let compressed_header_size = self\n                            .lh\n                            .read_lepton_fixed_header(\n                                &v.try_into().unwrap(),\n                                &mut self.enabled_features,\n                            )\n                            .context()?;\n\n                        self.state = DecoderState::CompressedHeader(compressed_header_size);\n\n                        self.jpeg_file_size_left = u64::from(self.lh.jpeg_file_size);\n                    }\n                }\n                DecoderState::CompressedHeader(compressed_length) => {\n                    if let Some(v) = in_buffer.take(*compressed_length, 0) {\n                        self.lh\n                            .read_compressed_lepton_header(\n                                &mut Cursor::new(v),\n                                &mut self.enabled_features,\n                                *compressed_length,\n                            )\n                            .context()?;\n\n                        // we need to truncate our file to the size minus the garbage data so\n                        // that when we hit the garbage data, we have room to write it all out\n                        if !self.lh.bad_truncation_version() {\n                            self.jpeg_file_size_left -= self.lh.rinfo.garbage_data.len() as u64;\n                        }\n\n                        self.state = DecoderState::CMP();\n                    }\n                }\n                DecoderState::CMP() => {\n                    if let Some(v) = in_buffer.take(3, 0) {\n                        let mut limited_output = LimitedOutputWriter {\n                            inner: output,\n                            amount_left: &mut self.jpeg_file_size_left,\n                        };\n\n                        self.state = Self::process_cmp(\n                            v,\n                            &self.lh,\n                            &self.enabled_features,\n                            &self.thread_pool,\n                            &mut limited_output,\n                        )?;\n                    }\n                }\n\n                DecoderState::ScanProgressive(state) => {\n                    state.process_buffer(&mut in_buffer)?;\n\n                    if input_complete {\n                        Self::verify_eof_file_size(self.total_read_size, &mut in_buffer)?;\n\n                        // complete the operation and merge the metrics\n                        // progressive JPEGs cannot return partial results\n                        let mut results = Vec::new();\n                        while let Some(r) = state.retrieve_result(true)? {\n                            results.push(r);\n                        }\n\n                        let mut limited_output = LimitedOutputWriter {\n                            inner: output,\n                            amount_left: &mut self.jpeg_file_size_left,\n                        };\n\n                        Self::process_progressive(\n                            &mut self.lh,\n                            &self.enabled_features,\n                            results,\n                            &mut limited_output,\n                        )?;\n\n                        write_tail(&mut self.lh, &mut limited_output)?;\n\n                        // here we write out any garbage data verbatim without truncating it\n                        write_garbage_data(&self.lh, limited_output)?;\n\n                        self.metrics.merge_from(state.take_metrics());\n\n                        self.state = DecoderState::EOI;\n                    }\n                }\n                DecoderState::ScanBaseline(state) => {\n                    state.process_buffer(&mut in_buffer)?;\n\n                    let mut limited_output = LimitedOutputWriter {\n                        inner: output,\n                        amount_left: &mut self.jpeg_file_size_left,\n                    };\n\n                    // baseline images can return partial results as decoding progresses,\n                    // send these to the output by querying the state machine with complete\n                    // set to false, which means we won't block if nothing is available\n                    while let Some(r) = state.retrieve_result(false)? {\n                        limited_output.write_all(&r)?;\n                    }\n\n                    if input_complete {\n                        Self::verify_eof_file_size(self.total_read_size, &mut in_buffer)?;\n\n                        // once we've complete the input, block for all remaining results\n                        while let Some(r) = state.retrieve_result(true)? {\n                            limited_output.write_all(&r)?;\n                        }\n\n                        // Injection of restart codes for RST errors supports JPEGs with trailing RSTs.\n                        // Run this logic even if early_eof_encountered to be compatible with C++ version.\n                        //\n                        // This logic is no longer needed for Rust generated Lepton files, since we just use the garbage\n                        // data to store any extra RST codes or whatever else might be at the end of the file.\n                        if self.lh.rinfo.rst_err.len() > 0 {\n                            let cumulative_reset_markers = if self.lh.jpeg_header.rsti != 0 {\n                                (self.lh.jpeg_header.mcuc - 1) / self.lh.jpeg_header.rsti\n                            } else {\n                                0\n                            } as u8;\n\n                            for i in 0..self.lh.rinfo.rst_err[0] {\n                                let rst = jpeg_code::RST0 + ((cumulative_reset_markers + i) & 7);\n\n                                limited_output.write_all(&[0xff, rst])?;\n                            }\n                        }\n\n                        write_tail(&mut self.lh, &mut limited_output)?;\n\n                        write_garbage_data(&self.lh, limited_output)?;\n\n                        self.metrics.merge_from(state.take_metrics());\n\n                        self.state = DecoderState::EOI;\n                    }\n                }\n                DecoderState::EOI => {\n                    break;\n                }\n            }\n        }\n\n        if input_complete {\n            self.input_complete = true;\n            match self.state {\n                DecoderState::EOI => {\n                    // all good, we don't need any more data to continue decoding\n                }\n                _ => {\n                    return err_exit_code(ExitCode::SyntaxError,\n                    format!(\"ERROR: input was marked as complete, but the decoder in state {:?} still needs more data\",\n                    std::mem::discriminant(&self.state)).as_str());\n                }\n            }\n        }\n\n        Ok(())\n    }\n\n    /// Processes input data, writing output to the output buffer and any extra to the output_extra queue.\n    ///\n    /// This is necessary because in the unmanaged wrapper we cannot expand the buffer that was given to us,\n    /// so we have to write as much as we can to the output buffer and then queue up any extra data for next time.\n    ///\n    /// This avoids adding complexity to the main processing loop for dealing with the case where the output\n    /// buffer is too small.\n    ///\n    /// Returns a tuple (complete, amount_written) where complete is true if all output was written.\n    pub fn process_limited_buffer(\n        &mut self,\n        input: &[u8],\n        input_complete: bool,\n        output_buffer: &mut [u8],\n        output_extra: &mut VecDeque<u8>,\n    ) -> std::io::Result<(bool, usize)> {\n        // first write any extra data we have pending from last time\n        let mut amount_written = 0;\n        while amount_written < output_buffer.len() && output_extra.len() > 0 {\n            amount_written += output_extra\n                .read(&mut output_buffer[amount_written..])\n                .unwrap();\n        }\n\n        // now call process buffer with the remaining space\n        let mut w = FixedBufferOuputWriter {\n            amount_written,\n            output_buffer,\n            extra_queue: output_extra,\n        };\n\n        self.process_buffer(input, input_complete, &mut w)?;\n\n        Ok((input_complete && w.extra_queue.len() == 0, w.amount_written))\n    }\n\n    /// destructively reads the metrics\n    pub fn take_metrics(&mut self) -> Metrics {\n        mem::take(&mut self.metrics)\n    }\n\n    /// return metrics on decoder\n    pub fn metrics(&self) -> &Metrics {\n        &self.metrics\n    }\n\n    fn process_progressive(\n        lh: &mut LeptonHeader,\n        enabled_features: &EnabledFeatures,\n        mut image_segments: Vec<Vec<BlockBasedImage>>,\n        output: &mut impl Write,\n    ) -> Result<()> {\n        let num_components = image_segments[0].len();\n        let mut merged = Vec::new();\n        for i in 0..num_components {\n            merged.push(BlockBasedImage::merge(&mut image_segments, i).context()?);\n        }\n\n        output.write_all(&SOI)?;\n        output\n            .write_all(&lh.rinfo.raw_jpeg_header[0..lh.raw_jpeg_header_read_index])\n            .context()?;\n\n        let mut scnc = 0;\n\n        loop {\n            // progressive JPEG consists of scans followed by headers\n            let scan =\n                jpeg_write_entire_scan(&merged[..], &lh.jpeg_header, &lh.rinfo, scnc).context()?;\n\n            output.write_all(&scan).context()?;\n\n            // read the next headers (DHT, etc) while mirroring it back to the writer\n            let old_pos = lh.raw_jpeg_header_read_index;\n            let result = lh.advance_next_header_segment(enabled_features).context()?;\n\n            output\n                .write_all(&lh.rinfo.raw_jpeg_header[old_pos..lh.raw_jpeg_header_read_index])\n                .context()?;\n\n            if !result {\n                break;\n            }\n\n            // advance to next scan\n            scnc += 1;\n        }\n\n        Ok(())\n    }\n\n    fn process_cmp(\n        v: Vec<u8>,\n        lh: &LeptonHeader,\n        enabled_features: &EnabledFeatures,\n        thread_pool: &dyn LeptonThreadPool,\n        output: &mut impl Write,\n    ) -> Result<DecoderState> {\n        if v[..] != LEPTON_HEADER_COMPLETION_MARKER {\n            return err_exit_code(ExitCode::BadLeptonFile, \"CMP marker not found\");\n        }\n\n        // use progressive logic, which reads the entire block into memory and then performs\n        // the jpeg decoding. This permits multiple scans that are each encoded in two cases:\n        //  - progressive images\n        //  - baseline multiscan images (rare but permitted)\n        Ok(if !lh.jpeg_header.is_single_scan() {\n            let mux = Self::run_lepton_decoder_threads(\n                lh,\n                enabled_features,\n                4, /* retain the last 4 bytes for the very end, since that is the file size, and shouldn't be parsed */\n                thread_pool,\n                progressive_decoding_thread,\n            )\n            .context()?;\n\n            DecoderState::ScanProgressive(mux)\n        } else {\n            output.write_all(&SOI)?;\n            output\n                .write_all(&lh.rinfo.raw_jpeg_header[0..lh.raw_jpeg_header_read_index])\n                .context()?;\n\n            let mux = Self::run_lepton_decoder_threads(\n                &lh,\n                &enabled_features,\n                4, /*retain 4 bytes for the end for the file size that is appended */\n                thread_pool,\n                baseline_decoding_thread,\n            )?;\n            DecoderState::ScanBaseline(mux)\n        })\n    }\n\n    fn verify_eof_file_size(total_read_size: u64, in_buffer: &mut PartialBuffer<'_>) -> Result<()> {\n        if let Some(bytes) = in_buffer.take_n::<4>(0) {\n            let size = u32::from_le_bytes(bytes);\n            if u64::from(size) != total_read_size {\n                return err_exit_code(\n                    ExitCode::VerificationLengthMismatch,\n                    format!(\n                        \"ERROR mismatch input_len = {0}, decoded_len = {1}\",\n                        size, total_read_size\n                    ),\n                );\n            }\n            Ok(())\n        } else {\n            err_exit_code(\n                ExitCode::VerificationLengthMismatch,\n                \"Missing EOF file size\",\n            )\n        }\n    }\n\n    /// starts the decoder threads\n    fn run_lepton_decoder_threads<P: Send + 'static>(\n        lh: &LeptonHeader,\n        features: &EnabledFeatures,\n        retention_bytes: usize,\n        thread_pool: &dyn LeptonThreadPool,\n        process: fn(\n            reader: &mut MultiplexReader,\n            features: &EnabledFeatures,\n            qt: &[QuantizationTables],\n            thread_handoff: &ThreadHandoff,\n            jpeg_header: &JpegHeader,\n            rinfo: &ReconstructionInfo,\n            is_last_thread: bool,\n            sender: &Sender<MultiplexReadResult<P>>,\n        ) -> Result<()>,\n    ) -> Result<MultiplexReaderState<P>> {\n        let qt = QuantizationTables::construct_quantization_tables(&lh.jpeg_header)?;\n\n        let features = features.clone();\n\n        let thread_handoff = lh.thread_handoff.clone();\n\n        let jpeg_header = lh.jpeg_header.clone();\n        let rinfo = lh.rinfo.clone();\n\n        let multiplex_reader_state = multiplex_read(\n            thread_handoff.len(),\n            features.max_processor_threads as usize,\n            thread_pool,\n            retention_bytes,\n            move |thread_id, reader, result_tx| {\n                process(\n                    reader,\n                    &features,\n                    &qt,\n                    &thread_handoff[thread_id],\n                    &jpeg_header,\n                    &rinfo,\n                    thread_id == thread_handoff.len() - 1,\n                    result_tx,\n                )\n            },\n        );\n\n        Ok(multiplex_reader_state)\n    }\n}\n\nfn write_tail(lh: &mut LeptonHeader, output: &mut impl Write) -> Result<()> {\n    output\n        .write_all(&lh.rinfo.raw_jpeg_header[lh.raw_jpeg_header_read_index..])\n        .context()?;\n    Ok(())\n}\n\n/// The thread function for progressive decoding.\n///\n/// Progressive encoding runs multiple passes on the same image data,\n/// so we can only calculate the set of images in parallel, and then\n/// merge them together into a single image that the progressive JPEG\n/// writer can use to write out the full progressive scan data.\nfn progressive_decoding_thread(\n    reader: &mut MultiplexReader,\n    features: &EnabledFeatures,\n    qt: &[QuantizationTables],\n    thread_handoff: &ThreadHandoff,\n    jpeg_header: &JpegHeader,\n    rinfo: &ReconstructionInfo,\n    is_last_thread: bool,\n    sender: &Sender<MultiplexReadResult<Vec<BlockBasedImage>>>,\n) -> Result<()> {\n    let cpu_time: CpuTimeMeasure = CpuTimeMeasure::new();\n\n    let (mut metrics, image_data) = lepton_decode_row_range(\n        qt,\n        jpeg_header,\n        &rinfo.truncate_components,\n        reader,\n        thread_handoff.luma_y_start,\n        thread_handoff.luma_y_end,\n        is_last_thread,\n        true,\n        features,\n        |_, _| Ok(()),\n    )?;\n\n    metrics.record_cpu_worker_time(cpu_time.elapsed());\n\n    sender.send(MultiplexReadResult::Result(image_data))?;\n    sender.send(MultiplexReadResult::Complete(metrics))?;\n\n    Ok(())\n}\n\n/// The thread function for baseline decoding.\n///\n/// Baseline encoding can do both the image decoding and JPEG writing in parallel.\n/// Each thread decodes its own segment and writes out the JPEG data bytes for that segment.\nfn baseline_decoding_thread(\n    reader: &mut MultiplexReader,\n    features: &EnabledFeatures,\n    qt: &[QuantizationTables],\n    thread_handoff: &ThreadHandoff,\n    jpeg_header: &JpegHeader,\n    rinfo: &ReconstructionInfo,\n    is_last_thread: bool,\n    sender: &Sender<MultiplexReadResult<Vec<u8>>>,\n) -> Result<()> {\n    let cpu_time: CpuTimeMeasure = CpuTimeMeasure::new();\n\n    let restart_info = RestartSegmentCodingInfo {\n        overhang_byte: thread_handoff.overhang_byte,\n        num_overhang_bits: thread_handoff.num_overhang_bits,\n        luma_y_start: thread_handoff.luma_y_start,\n        luma_y_end: thread_handoff.luma_y_end,\n        last_dc: thread_handoff.last_dc,\n    };\n\n    const BUFFER_SIZE: usize = 128 * 1024;\n\n    // track how muchd data we can generate\n    let mut amount_left = thread_handoff.segment_size as usize;\n\n    let mut inc_writer =\n        JpegIncrementalWriter::new(BUFFER_SIZE, rinfo, Some(&restart_info), jpeg_header, 0);\n\n    let (mut metrics, _image_data) = lepton_decode_row_range(\n        qt,\n        jpeg_header,\n        &rinfo.truncate_components,\n        reader,\n        thread_handoff.luma_y_start,\n        thread_handoff.luma_y_end,\n        is_last_thread,\n        true,\n        features,\n        |row_spec, image_data| {\n            inc_writer.process_row(row_spec, image_data).context()?;\n\n            // send out any data we have buffered if we have enough\n            if inc_writer.amount_buffered() >= BUFFER_SIZE {\n                let mut buf = inc_writer.detach_buffer();\n                if buf.len() > amount_left {\n                    warn!(\n                        \"Truncating output buffer from {} to {}\",\n                        buf.len(),\n                        amount_left\n                    );\n                    buf.truncate(amount_left);\n                }\n\n                amount_left -= buf.len();\n\n                sender.send(MultiplexReadResult::Result(buf))?;\n            }\n\n            Ok(())\n        },\n    )?;\n\n    metrics.record_cpu_worker_time(cpu_time.elapsed());\n\n    let mut buf = inc_writer.detach_buffer();\n    if buf.len() > amount_left {\n        warn!(\n            \"Truncating output buffer from {} to {}\",\n            buf.len(),\n            amount_left\n        );\n        buf.truncate(amount_left);\n    }\n\n    sender.send(MultiplexReadResult::Result(buf))?;\n\n    sender.send(MultiplexReadResult::Complete(metrics))?;\n\n    Ok(())\n}\n\nfn write_garbage_data(\n    lh: &LeptonHeader,\n    mut limited_output: LimitedOutputWriter<'_, impl Write>,\n) -> Result<()> {\n    if !lh.bad_truncation_version() {\n        // here we write out any garbage data verbatim without truncating it\n        // (since we already shrunk the max file size accordingly)\n        limited_output\n            .inner\n            .write_all(&lh.rinfo.garbage_data)\n            .context()?;\n    } else {\n        // the bad encoder wrote the garbage data in such a way that it could\n        // be truncated (see DecoderState::CompressedHeader case above)\n        limited_output.write_all(&lh.rinfo.garbage_data).context()?;\n    }\n\n    Ok(())\n}\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n    use std::io::{BufWriter, Cursor};\n\n    use default_boxed::DefaultBoxed;\n\n    use crate::{\n        DEFAULT_THREAD_POOL, EnabledFeatures, SingleThreadPool, decode_lepton,\n        helpers::read_file,\n        structs::{\n            lepton_header::{FIXED_HEADER_SIZE, LeptonHeader},\n            thread_handoff::ThreadHandoff,\n        },\n    };\n\n    // test serializing and deserializing header\n    #[test]\n    fn parse_and_write_header() {\n        use crate::jpeg::jpeg_read::read_jpeg_file;\n        use std::io::Read;\n\n        let min_jpeg = read_file(\"tiny\", \".jpg\");\n\n        let mut lh = LeptonHeader::default_boxed();\n        let enabled_features = EnabledFeatures::compat_lepton_vector_read();\n\n        lh.jpeg_file_size = min_jpeg.len() as u32;\n        lh.uncompressed_lepton_header_size = Some(752);\n\n        let (_image_data, _partitions, _end_scan) = read_jpeg_file(\n            &mut Cursor::new(min_jpeg),\n            &mut lh.jpeg_header,\n            &mut lh.rinfo,\n            &enabled_features,\n            |_, _| {},\n        )\n        .unwrap();\n\n        lh.thread_handoff.push(ThreadHandoff {\n            luma_y_start: 0,\n            luma_y_end: 1,\n            segment_offset_in_file: 0,\n            segment_size: 1000,\n            overhang_byte: 0,\n            num_overhang_bits: 1,\n            last_dc: [1, 2, 3, 4],\n        });\n\n        let mut serialized = Vec::new();\n        lh.write_lepton_header(&mut Cursor::new(&mut serialized), &enabled_features)\n            .unwrap();\n\n        let mut other = LeptonHeader::default_boxed();\n        let mut other_reader = Cursor::new(&serialized);\n\n        let mut fixed_buffer = [0; FIXED_HEADER_SIZE];\n        other_reader.read_exact(&mut fixed_buffer).unwrap();\n\n        let mut other_enabled_features = EnabledFeatures::compat_lepton_vector_read();\n\n        let compressed_header_size = other\n            .read_lepton_fixed_header(&fixed_buffer, &mut other_enabled_features)\n            .unwrap();\n        other\n            .read_compressed_lepton_header(\n                &mut other_reader,\n                &mut other_enabled_features,\n                compressed_header_size,\n            )\n            .unwrap();\n\n        assert_eq!(\n            lh.uncompressed_lepton_header_size,\n            other.uncompressed_lepton_header_size\n        );\n    }\n\n    #[test]\n    fn test_simple_parse_progressive() {\n        test_file(\"androidprogressive\")\n    }\n\n    #[test]\n    fn test_simple_parse_baseline() {\n        test_file(\"android\")\n    }\n\n    #[test]\n    fn test_simple_parse_trailing() {\n        test_file(\"androidtrail\")\n    }\n\n    #[test]\n    fn test_zero_dqt() {\n        test_file(\"zeros_in_dqt_tables\")\n    }\n\n    /// truncated progessive JPEG. We don't support creating these, but we can read them\n    #[test]\n    fn test_pixelated() {\n        test_file(\"pixelated\")\n    }\n\n    /// requires that the last segment be truncated by 1 byte.\n    /// This is for compatibility with the C++ version\n    #[test]\n    fn test_truncate4() {\n        test_file(\"truncate4\")\n    }\n\n    #[test]\n    fn test_decode_single_threaded() {\n        let filename = \"iphone\";\n        let file = read_file(filename, \".lep\");\n        let original = read_file(filename, \".jpg\");\n\n        let enabled_features = EnabledFeatures::compat_lepton_vector_read();\n\n        let mut output = Vec::new();\n        decode_lepton(\n            &mut Cursor::new(&file),\n            &mut output,\n            &enabled_features,\n            &SingleThreadPool::default(),\n        )\n        .unwrap();\n\n        assert_eq!(output.len(), original.len());\n        assert!(output == original);\n    }\n\n    #[test]\n    fn test_encode_single_threaded() {\n        let filename = \"iphone\";\n        let file = read_file(filename, \".jpg\");\n\n        let enabled_features = EnabledFeatures::compat_lepton_vector_read();\n\n        let mut output = Vec::new();\n        crate::encode_lepton(\n            &mut Cursor::new(&file),\n            &mut Cursor::new(&mut output),\n            &enabled_features,\n            &SingleThreadPool::default(),\n        )\n        .unwrap();\n    }\n\n    fn test_file(filename: &str) {\n        let file = read_file(filename, \".lep\");\n        let original = read_file(filename, \".jpg\");\n\n        let enabled_features = EnabledFeatures::compat_lepton_vector_read();\n\n        let _ = decode_lepton_file_image(\n            &mut Cursor::new(&file),\n            &enabled_features,\n            &DEFAULT_THREAD_POOL,\n        )\n        .unwrap();\n\n        let mut output = Vec::new();\n\n        decode_lepton(\n            &mut Cursor::new(&file),\n            &mut output,\n            &enabled_features,\n            &DEFAULT_THREAD_POOL,\n        )\n        .unwrap();\n\n        assert_eq!(output.len(), original.len());\n        assert!(output == original);\n    }\n\n    struct RecordStreamPosition<W: Write> {\n        writer: W,\n        position: u64,\n    }\n\n    impl<W: Write> Write for RecordStreamPosition<W> {\n        fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {\n            if buf.len() == 0 {\n                return Ok(0);\n            }\n\n            // only accept one byte at a time to test position tracking\n            let n = self.writer.write(&[buf[0]])?;\n            self.position += n as u64;\n            Ok(n)\n        }\n\n        fn flush(&mut self) -> std::io::Result<()> {\n            self.writer.flush()\n        }\n    }\n\n    #[test]\n    fn test_streaming_results() {\n        let data = read_file(\"hq\", \".lep\");\n        let original_data = read_file(\"hq\", \".jpg\");\n\n        let mut cursor = Cursor::new(&data);\n\n        let mut output_vector = Vec::new();\n\n        let mut output = RecordStreamPosition {\n            writer: BufWriter::new(&mut output_vector),\n            position: 0,\n        };\n\n        let enabled_features = EnabledFeatures::compat_lepton_vector_read();\n        let thread_pool = &DEFAULT_THREAD_POOL;\n\n        decode_lepton(&mut cursor, &mut output, &enabled_features, thread_pool).unwrap();\n\n        drop(output);\n\n        assert_eq!(output_vector.len(), original_data.len());\n        assert!(output_vector == original_data);\n    }\n\n    /// ensure we fail if the output buffer is too small\n    #[test]\n    fn test_too_small_output() {\n        let original = read_file(\"slrcity\", \".lep\");\n\n        let mut output = Vec::new();\n        output.resize(original.len() / 2, 0u8);\n\n        let r = decode_lepton(\n            &mut Cursor::new(&original),\n            &mut Cursor::new(&mut output[..]),\n            &EnabledFeatures::compat_lepton_vector_read(),\n            &DEFAULT_THREAD_POOL,\n        );\n\n        assert!(r.is_err() && r.err().unwrap().exit_code() == ExitCode::OsError);\n    }\n\n    fn verifydecode(filename: &str) {\n        let original = read_file(filename, \".lep\");\n\n        let mut output = Vec::new();\n\n        let _ = decode_lepton(\n            &mut Cursor::new(&original),\n            &mut Cursor::new(&mut output),\n            &EnabledFeatures::compat_lepton_vector_read(),\n            &DEFAULT_THREAD_POOL,\n        )\n        .unwrap();\n\n        let jpg = read_file(filename, \".jpg\");\n\n        assert_eq!(jpg.len(), output.len());\n        assert!(output == jpg);\n    }\n\n    /// test we can decode an invalid file generated by a regression in the 5.5 version,\n    /// which triggers the bad_truncation_version() check in the LeptonHeader.\n    #[test]\n    fn test_truncated_with_bad_truncation_version() {\n        verifydecode(\"half_scan_rust55\");\n    }\n\n    /// test we can decode the same file as above, but encoded by a\n    /// correctly behaving decoder\n    #[test]\n    fn test_truncated_with_ok_truncation_version() {\n        verifydecode(\"half_scan\");\n    }\n\n    /// tests corner case where we have garbage data due to the trunction of the file,\n    /// but the garbage data is not actually valid JPEG data. So basically what happened\n    /// was that the file got truncated mid-byte and the remaining bits are just random.\n    #[test]\n    fn test_truncated_with_bad_garbage_data() {\n        verifydecode(\"truncbad\");\n    }\n}\n"
  },
  {
    "path": "lib/src/structs/lepton_file_writer.rs",
    "content": "/*---------------------------------------------------------------------------------------------\n *  Copyright (c) Microsoft Corporation. All rights reserved.\n *  Licensed under the Apache License, Version 2.0. See LICENSE.txt in the project root for license information.\n *  This software incorporates material from third parties. See NOTICE.txt for details.\n *--------------------------------------------------------------------------------------------*/\n\nuse std::cmp;\nuse std::io::{BufRead, Cursor, Seek, Write};\nuse std::time::Instant;\n\nuse byteorder::{LittleEndian, WriteBytesExt};\nuse default_boxed::DefaultBoxed;\nuse log::info;\n\nuse crate::enabled_features::EnabledFeatures;\nuse crate::jpeg::block_based_image::BlockBasedImage;\nuse crate::jpeg::jpeg_header::JpegHeader;\nuse crate::jpeg::jpeg_read::read_jpeg_file;\nuse crate::jpeg::truncate_components::TruncateComponents;\nuse crate::lepton_error::{AddContext, ExitCode, Result, err_exit_code};\nuse crate::metrics::{CpuTimeMeasure, Metrics};\nuse crate::structs::lepton_encoder::lepton_encode_row_range;\nuse crate::structs::lepton_file_reader::decode_lepton;\nuse crate::structs::lepton_header::LeptonHeader;\nuse crate::structs::multiplexer::multiplex_write;\nuse crate::structs::quantization_tables::QuantizationTables;\nuse crate::structs::thread_handoff::ThreadHandoff;\nuse crate::{LeptonThreadPool, StreamPosition, consts::*};\n\n/// Reads a jpeg and writes it out as a lepton file\n///\n/// # Parameters\n/// - `reader`: A buffered reader from which the JPEG data is read.\n/// - `writer`: A writer to which the Lepton-encoded data is written.\n/// - `enabled_features`: A set of toggles for enabling/disabling encoding features/restrictions.\n/// - `thread_pool`: A reference to a thread pool used for parallel processing. Must be a static reference and\n/// can point to `DEFAULT_THREAD_POOL`.\npub fn encode_lepton<R: BufRead + Seek, W: Write + StreamPosition>(\n    reader: &mut R,\n    writer: &mut W,\n    enabled_features: &EnabledFeatures,\n    thread_pool: &dyn LeptonThreadPool,\n) -> Result<Metrics> {\n    let (lp, image_data) = read_jpeg(reader, enabled_features, |_jh, _ri| {})?;\n\n    let start_position = writer.position();\n\n    lp.write_lepton_header(writer, enabled_features).context()?;\n\n    let metrics = run_lepton_encoder_threads(\n        &lp.jpeg_header,\n        &lp.rinfo.truncate_components,\n        writer,\n        &lp.thread_handoff[..],\n        image_data,\n        enabled_features,\n        thread_pool,\n    )\n    .context()?;\n\n    let final_file_size = (writer.position() - start_position) + 4;\n\n    writer\n        .write_u32::<LittleEndian>(final_file_size as u32)\n        .context()?;\n\n    Ok(metrics)\n}\n\n/// Encodes JPEG as compressed Lepton format, verifies roundtrip in buffer. Requires everything to be buffered\n/// since we need to pass through the data multiple times\npub fn encode_lepton_verify(\n    input_data: &[u8],\n    enabled_features: &EnabledFeatures,\n    thread_pool: &dyn LeptonThreadPool,\n) -> Result<(Vec<u8>, Metrics)> {\n    let mut output_data = Vec::with_capacity(input_data.len());\n\n    info!(\"compressing to Lepton format\");\n\n    let mut reader = Cursor::new(&input_data);\n    let mut writer = Cursor::new(&mut output_data);\n\n    let mut metrics =\n        encode_lepton(&mut reader, &mut writer, &enabled_features, thread_pool).context()?;\n\n    // decode and compare to original in order to enure we encoded correctly\n\n    let mut verify_buffer = Vec::with_capacity(input_data.len());\n    let mut verifyreader = Cursor::new(&output_data[..]);\n\n    info!(\"decompressing to verify contents\");\n\n    let mut c = enabled_features.clone();\n\n    metrics.merge_from(\n        decode_lepton(&mut verifyreader, &mut verify_buffer, &mut c, thread_pool).context()?,\n    );\n\n    if input_data.len() != verify_buffer.len() {\n        return err_exit_code(\n            ExitCode::VerificationLengthMismatch,\n            format!(\n                \"ERROR mismatch input_len = {0}, decoded_len = {1}\",\n                input_data.len(),\n                verify_buffer.len()\n            ),\n        );\n    }\n\n    if input_data[..] != verify_buffer[..] {\n        return err_exit_code(\n            ExitCode::VerificationContentMismatch,\n            \"ERROR mismatching data (but same size)\",\n        );\n    }\n\n    Ok((output_data, metrics))\n}\n\n/// reads JPEG and returns corresponding header and image vector. This encapsulate all\n/// JPEG reading code, including baseline and progressive images.\n///\n/// The callback is called for each jpeg header that is parsed, which\n/// is currently only used by the dump utility for debugging purposes.\npub fn read_jpeg<R: BufRead + Seek>(\n    reader: &mut R,\n    enabled_features: &EnabledFeatures,\n    callback: fn(&JpegHeader, &[u8]),\n) -> Result<(Box<LeptonHeader>, Vec<BlockBasedImage>)> {\n    let mut lp = LeptonHeader::default_boxed();\n\n    let stream_start_position = reader.stream_position().context()?;\n\n    get_git_revision(&mut lp);\n\n    let (image_data, partitions, end_scan) = read_jpeg_file(\n        reader,\n        &mut lp.jpeg_header,\n        &mut lp.rinfo,\n        enabled_features,\n        callback,\n    )?;\n\n    let mut thread_handoff = Vec::<ThreadHandoff>::new();\n\n    for i in 0..partitions.len() {\n        let (segment_offset, r) = &partitions[i];\n\n        let segment_size = if i == partitions.len() - 1 {\n            end_scan - segment_offset\n        } else {\n            partitions[i + 1].0 - segment_offset\n        };\n\n        thread_handoff.push(ThreadHandoff {\n            segment_offset_in_file: (*segment_offset - stream_start_position)\n                .try_into()\n                .unwrap(),\n            luma_y_start: r.luma_y_start,\n            luma_y_end: r.luma_y_end,\n            overhang_byte: r.overhang_byte,\n            num_overhang_bits: r.num_overhang_bits,\n            last_dc: r.last_dc,\n            segment_size: segment_size.try_into().unwrap(),\n        });\n\n        #[cfg(feature = \"detailed_tracing\")]\n        info!(\n            \"Crystalize: s:{0} ls: {1} le: {2} o: {3} nb: {4}\",\n            thread_handoff[i].segment_offset_in_file,\n            thread_handoff[i].luma_y_start,\n            thread_handoff[i].luma_y_end,\n            thread_handoff[i].overhang_byte,\n            thread_handoff[i].num_overhang_bits\n        );\n    }\n\n    let merged_handoffs = split_row_handoffs_to_threads(\n        &thread_handoff[..],\n        enabled_features.max_partitions as usize,\n    );\n    lp.thread_handoff = merged_handoffs;\n    lp.jpeg_file_size = (reader.stream_position().context()? - stream_start_position) as u32;\n\n    if lp.jpeg_file_size > enabled_features.max_jpeg_file_size {\n        return err_exit_code(\n            ExitCode::UnsupportedJpeg,\n            \"file is too large to encode, increase max_jpeg_file_size\",\n        );\n    }\n\n    Ok((lp, image_data))\n}\n\nconst fn string_to_int(s: &str) -> u8 {\n    let mut result = 0;\n    let mut i = 0;\n    let b = s.as_bytes();\n    while i < b.len() {\n        let c = b[i];\n        result = result * 10 + c - b'0';\n        i += 1;\n    }\n    result\n}\n\nstatic GIT_VERSION: &str = git_version::git_version!(\n    args = [\"--abbrev=40\", \"--always\", \"--dirty=M\"],\n    fallback = \"0\"\n);\n\n/// Returns the git version used to build this libary as a static string.\npub fn get_git_version() -> &'static str {\n    GIT_VERSION\n}\n\npub fn get_cargo_pkg_version() -> u8 {\n    string_to_int(env!(\"CARGO_PKG_VERSION_MAJOR\")) * 100\n        + string_to_int(env!(\"CARGO_PKG_VERSION_MINOR\")) * 10\n        + string_to_int(env!(\"CARGO_PKG_VERSION_PATCH\"))\n}\n\nfn get_git_revision(lp: &mut LeptonHeader) {\n    let hex_str = GIT_VERSION;\n    if let Ok(v) = u32::from_str_radix(hex_str, 16) {\n        // place the warning if we got a git revision. The --dirty=M suffix means that some files\n        // were modified so the version is not a clean git version, so we don't write it.\n        lp.git_revision_prefix = v.to_be_bytes();\n    }\n\n    lp.encoder_version = get_cargo_pkg_version();\n}\n\n/// runs the encoding threads and returns the total amount of CPU time consumed (including worker threads)\nfn run_lepton_encoder_threads<W: Write>(\n    jpeg_header: &JpegHeader,\n    colldata: &TruncateComponents,\n    writer: &mut W,\n    thread_handoffs: &[ThreadHandoff],\n    image_data: Vec<BlockBasedImage>,\n    features: &EnabledFeatures,\n    thread_pool: &dyn LeptonThreadPool,\n) -> Result<Metrics> {\n    let wall_time = Instant::now();\n\n    // Get number of threads. Verify that it is at most MAX_THREADS and fits in 4 bits for serialization.\n    let num_threads = thread_handoffs.len();\n    assert!(\n        num_threads <= MAX_THREADS_SUPPORTED_BY_LEPTON_FORMAT,\n        \"Too many thread handoffs\"\n    );\n\n    // Prepare quantization tables\n    let quantization_tables = QuantizationTables::construct_quantization_tables(jpeg_header)?;\n\n    let colldata = colldata.clone();\n    let thread_handoffs = thread_handoffs.to_vec();\n    let features = features.clone();\n\n    let mut thread_results = multiplex_write(\n        writer,\n        thread_handoffs.len(),\n        features.max_processor_threads as usize,\n        thread_pool,\n        move |thread_writer, thread_id| {\n            let cpu_time = CpuTimeMeasure::new();\n\n            let mut range_metrics = lepton_encode_row_range(\n                &quantization_tables,\n                &image_data,\n                thread_writer,\n                thread_id as i32,\n                &colldata,\n                thread_handoffs[thread_id].luma_y_start,\n                thread_handoffs[thread_id].luma_y_end,\n                thread_id == thread_handoffs.len() - 1,\n                true,\n                &features,\n            )\n            .context()?;\n\n            range_metrics.record_cpu_worker_time(cpu_time.elapsed());\n\n            Ok(range_metrics)\n        },\n    )?;\n\n    let mut merged_metrics = Metrics::default();\n\n    for result in thread_results.drain(..) {\n        merged_metrics.merge_from(result);\n    }\n\n    info!(\n        \"worker threads {0}ms of CPU time in {1}ms of wall time\",\n        merged_metrics.get_cpu_time_worker_time().as_millis(),\n        wall_time.elapsed().as_millis()\n    );\n\n    Ok(merged_metrics)\n}\n\nfn split_row_handoffs_to_threads(\n    thread_handoffs: &[ThreadHandoff],\n    max_threads_to_use: usize,\n) -> Vec<ThreadHandoff> {\n    let last = thread_handoffs.last().unwrap();\n\n    let framebuffer_byte_size = ThreadHandoff::get_combine_thread_range_segment_size(\n        thread_handoffs.first().unwrap(),\n        last,\n    );\n\n    // determine how many threads we need for compression\n    let num_rows = thread_handoffs.len();\n    let num_threads =\n        get_number_of_threads_for_encoding(num_rows, framebuffer_byte_size, max_threads_to_use);\n\n    info!(\"Number of threads: {0}\", num_threads);\n\n    let mut selected_splits = Vec::with_capacity(num_threads);\n\n    if num_threads == 1 {\n        // Single thread execution - no split, run on the whole range\n        selected_splits.push(ThreadHandoff::combine_thread_ranges(\n            thread_handoffs.first().unwrap(),\n            last,\n        ));\n    } else {\n        // gbrovman: simplified split logic\n        // Note: rowsPerThread is a floating point value to ensure equal splits\n        let rows_per_thread = num_rows as f32 / num_threads as f32;\n\n        assert!(rows_per_thread >= 1f32, \"rowsPerThread >= 1\");\n\n        let mut split_indices = Vec::new();\n        for i in 0..num_threads - 1 {\n            split_indices.push((rows_per_thread * (i as f32 + 1f32)) as usize);\n        }\n\n        for i in 0..num_threads {\n            let beginning_of_range = if i == 0 { 0 } else { split_indices[i - 1] + 1 };\n            let end_of_range = if i == num_threads - 1 {\n                num_rows - 1\n            } else {\n                split_indices[i]\n            };\n            assert!(end_of_range < num_rows, \"endOfRange < numRows\");\n            selected_splits.push(ThreadHandoff::combine_thread_ranges(\n                &thread_handoffs[beginning_of_range],\n                &thread_handoffs[end_of_range],\n            ));\n        }\n    }\n\n    return selected_splits;\n}\n\nfn get_number_of_threads_for_encoding(\n    num_rows: usize,\n    framebuffer_byte_size: usize,\n    max_threads_to_use: usize,\n) -> usize {\n    let mut num_threads = cmp::min(max_threads_to_use, MAX_THREADS_SUPPORTED_BY_LEPTON_FORMAT);\n\n    if num_rows / 2 < num_threads {\n        num_threads = cmp::max(num_rows / 2, 1);\n    }\n\n    if framebuffer_byte_size < SMALL_FILE_BYTES_PER_ENCDOING_THREAD {\n        num_threads = 1;\n    } else if framebuffer_byte_size < SMALL_FILE_BYTES_PER_ENCDOING_THREAD * 2 {\n        num_threads = cmp::min(2, num_threads);\n    } else if framebuffer_byte_size < SMALL_FILE_BYTES_PER_ENCDOING_THREAD * 4 {\n        num_threads = cmp::min(4, num_threads);\n    }\n\n    return num_threads;\n}\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n\n    use crate::{DEFAULT_THREAD_POOL, helpers::read_file};\n\n    #[test]\n    fn test_get_git_revision() {\n        let mut lh = LeptonHeader::default_boxed();\n        get_git_revision(&mut lh);\n\n        println!(\"{:x?}\", lh.git_revision_prefix);\n    }\n\n    /// ensure we fail if the output buffer is too small\n    #[test]\n    fn test_too_small_output() {\n        let original = read_file(\"slrcity\", \".jpg\");\n\n        let mut output = Vec::new();\n        output.resize(original.len() / 2, 0u8);\n\n        let r = encode_lepton(\n            &mut Cursor::new(&original),\n            &mut Cursor::new(&mut output[..]),\n            &EnabledFeatures::compat_lepton_vector_write(),\n            &DEFAULT_THREAD_POOL,\n        );\n\n        assert!(r.is_err() && r.err().unwrap().exit_code() == ExitCode::OsError);\n    }\n\n    #[test]\n    fn test_slrcity() {\n        test_file(\"slrcity\")\n    }\n\n    fn test_file(filename: &str) {\n        let original = read_file(filename, \".jpg\");\n\n        let mut enabled_features = EnabledFeatures::compat_lepton_vector_write();\n        enabled_features.max_partitions = 2;\n\n        let mut output = Vec::new();\n\n        let _ = encode_lepton(\n            &mut Cursor::new(&original),\n            &mut Cursor::new(&mut output),\n            &enabled_features,\n            &DEFAULT_THREAD_POOL,\n        )\n        .unwrap();\n\n        println!(\n            \"Original size: {0}, compressed size: {1}\",\n            original.len(),\n            output.len()\n        );\n\n        let mut recreate = Vec::new();\n\n        decode_lepton(\n            &mut Cursor::new(&output),\n            &mut recreate,\n            &enabled_features,\n            &DEFAULT_THREAD_POOL,\n        )\n        .unwrap();\n\n        assert_eq!(original.len(), recreate.len());\n        assert!(original == recreate);\n    }\n}\n"
  },
  {
    "path": "lib/src/structs/lepton_header.rs",
    "content": "use std::cmp::min;\nuse std::io::{Cursor, ErrorKind, Read, Seek, Write};\n\nuse byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt};\nuse default_boxed::DefaultBoxed;\nuse flate2::Compression;\nuse flate2::read::ZlibDecoder;\nuse flate2::write::ZlibEncoder;\n\nuse crate::EnabledFeatures;\nuse crate::consts::*;\nuse crate::helpers::buffer_prefix_matches_marker;\nuse crate::jpeg::jpeg_header::{JpegHeader, ReconstructionInfo};\nuse crate::lepton_error::{AddContext, ExitCode, Result, err_exit_code};\nuse crate::structs::thread_handoff::ThreadHandoff;\n\npub const FIXED_HEADER_SIZE: usize = 28;\n\n#[derive(Debug, DefaultBoxed)]\npub struct LeptonHeader {\n    /// how far we have read into the raw header, since the header is divided\n    /// into multiple chucks for each scan. For example, a progressive image\n    /// would start with the jpeg image segments, followed by a SOS (start of scan)\n    /// after which comes the encoded jpeg coefficients, and once thats over\n    /// we get another header segment until the next SOS, etc\n    pub raw_jpeg_header_read_index: usize,\n\n    pub thread_handoff: Vec<ThreadHandoff>,\n\n    pub jpeg_header: JpegHeader,\n\n    pub rinfo: ReconstructionInfo,\n\n    pub jpeg_file_size: u32,\n\n    /// on decompression, uncompressed lepton header size. This is only\n    /// saved by this encoder for historical reasons. It is not used by\n    /// the decoder.\n    pub uncompressed_lepton_header_size: Option<u32>,\n\n    /// the git revision of the encoder that created this file (first 8 hex characters)\n    pub git_revision_prefix: [u8; 4],\n\n    /// writer version\n    pub encoder_version: u8,\n}\n\nimpl LeptonHeader {\n    /// For certain versions of the rust encoder, we didn't handle truncation and corruption correctly.\n    /// The correct behavior is to truncate the JPEG generated data up to the file size minus the garbage data,\n    /// then write out the garbage data.\n    ///\n    /// The incorrect behavior was to write out the JPEG data, append the garbage data, and then truncate.\n    pub fn bad_truncation_version(&self) -> bool {\n        self.encoder_version == 55\n    }\n\n    pub fn read_lepton_fixed_header(\n        &mut self,\n        header: &[u8; FIXED_HEADER_SIZE],\n        enabled_features: &mut EnabledFeatures,\n    ) -> Result<usize> {\n        if header[0..2] != LEPTON_FILE_HEADER[0..2] {\n            return err_exit_code(ExitCode::BadLeptonFile, \"header doesn't match\");\n        }\n        if header[2] != LEPTON_VERSION {\n            return err_exit_code(\n                ExitCode::VersionUnsupported,\n                format!(\"incompatible file with version {0}\", header[3]),\n            );\n        }\n        if header[3] != LEPTON_HEADER_BASELINE_JPEG_TYPE[0]\n            && header[3] != LEPTON_HEADER_PROGRESSIVE_JPEG_TYPE[0]\n        {\n            return err_exit_code(\n                ExitCode::BadLeptonFile,\n                format!(\"Unknown filetype in header {0}\", header[4]),\n            );\n        }\n\n        // header[4] is the number of streams/threads, but we don't care about that\n        // header[5..8] is reserved\n\n        // header[8..20] 12 bytes were the GIT revision, but for historical reasons we\n        // also use this space to store the uncompressed lepton header size plus some\n        // flags to detect the SIMD flavor that was used to encode, since\n        // previously the encoder would generate different incompatible files depending on\n        // whether SIMD or scalar was selected by the build options.\n        if header[8] == 'M' as u8 && header[9] == 'S' as u8 {\n            self.uncompressed_lepton_header_size =\n                Some(u32::from_le_bytes(header[10..14].try_into().unwrap()));\n\n            // read the flag bits to know how we should decode this file\n            let flags = header[14];\n            if (flags & 0x80) != 0 {\n                enabled_features.use_16bit_dc_estimate = (flags & 0x01) != 0;\n                enabled_features.use_16bit_adv_predict = (flags & 0x02) != 0;\n            }\n\n            self.encoder_version = header[15];\n            self.git_revision_prefix = header[16..20].try_into().unwrap();\n        } else {\n            // take first bytes for git revision prefix\n            self.git_revision_prefix = header[8..12].try_into().unwrap();\n        }\n\n        // total size of original JPEG\n        self.jpeg_file_size = u32::from_le_bytes(header[20..24].try_into().unwrap());\n\n        let compressed_header_size =\n            u32::from_le_bytes(header[24..28].try_into().unwrap()) as usize;\n\n        Ok(compressed_header_size)\n    }\n\n    /// reads the start of the lepton file and parses the compressed header. Returns the raw JPEG header contents.\n    pub fn read_compressed_lepton_header<R: Read>(\n        &mut self,\n        reader: &mut R,\n        enabled_features: &mut EnabledFeatures,\n        compressed_header_size: usize,\n    ) -> Result<()> {\n        if compressed_header_size > enabled_features.max_jpeg_file_size as usize {\n            return err_exit_code(ExitCode::BadLeptonFile, \"Too big compressed header\");\n        }\n        if self.jpeg_file_size > enabled_features.max_jpeg_file_size {\n            return err_exit_code(\n                ExitCode::BadLeptonFile,\n                format!(\n                    \"Only support images < {} megs\",\n                    enabled_features.max_jpeg_file_size / (1024 * 1024)\n                ),\n            );\n        }\n\n        // limit reading to the compressed header\n        let mut compressed_reader = reader.take(compressed_header_size as u64);\n\n        self.rinfo.raw_jpeg_header = self\n            .read_lepton_compressed_header(&mut compressed_reader)\n            .context()?;\n\n        self.raw_jpeg_header_read_index = 0;\n\n        {\n            let mut header_data_cursor = Cursor::new(&self.rinfo.raw_jpeg_header[..]);\n            self.jpeg_header\n                .parse(&mut header_data_cursor, &enabled_features)\n                .context()?;\n            self.raw_jpeg_header_read_index = header_data_cursor.position() as usize;\n        }\n\n        self.rinfo.truncate_components.init(&self.jpeg_header);\n\n        if self.rinfo.early_eof_encountered {\n            self.rinfo\n                .truncate_components\n                .set_truncation_bounds(&self.jpeg_header, self.rinfo.max_dpos);\n        }\n\n        let num_threads = self.thread_handoff.len();\n\n        // luma_y_end of the last thread is not serialized/deserialized, fill it here\n        let max_luma = self.rinfo.truncate_components.get_block_height(0);\n\n        for i in 0..num_threads {\n            self.thread_handoff[i].luma_y_start =\n                min(self.thread_handoff[i].luma_y_start, max_luma);\n            self.thread_handoff[i].luma_y_end = min(self.thread_handoff[i].luma_y_end, max_luma);\n        }\n        self.thread_handoff[num_threads - 1].luma_y_end = max_luma;\n\n        // if the last segment was too big to fit with the garbage data taken into account, shorten it\n        // (a bit of broken logic in the encoder, but can't change it without breaking the file format)\n        if self.rinfo.early_eof_encountered {\n            let mut max_last_segment_size = self.jpeg_file_size\n                - u32::try_from(self.rinfo.garbage_data.len())?\n                - u32::try_from(self.raw_jpeg_header_read_index)?\n                - u32::try_from(SOI.len())?;\n\n            // subtract the segment sizes of all the previous segments (except for the last)\n            for i in 0..num_threads - 1 {\n                max_last_segment_size -= self.thread_handoff[i].segment_size;\n            }\n\n            let last = &mut self.thread_handoff[num_threads - 1];\n\n            let max_last_segment_size = max_last_segment_size;\n\n            if last.segment_size > max_last_segment_size {\n                // re-adjust the last segment size\n                last.segment_size = max_last_segment_size;\n            }\n        }\n\n        Ok(())\n    }\n\n    /// parses and advances to the next header segment out of raw_jpeg_header into the jpeg header\n    pub fn advance_next_header_segment(\n        &mut self,\n        enabled_features: &EnabledFeatures,\n    ) -> Result<bool> {\n        let mut header_cursor =\n            Cursor::new(&self.rinfo.raw_jpeg_header[self.raw_jpeg_header_read_index..]);\n\n        let result = self\n            .jpeg_header\n            .parse(&mut header_cursor, enabled_features)\n            .context()?;\n\n        self.raw_jpeg_header_read_index += header_cursor.stream_position()? as usize;\n\n        Ok(result)\n    }\n\n    /// helper for read_lepton_header. uncompresses and parses the contents of the compressed header. Returns the raw JPEG header.\n    fn read_lepton_compressed_header<R: Read>(&mut self, src: &mut R) -> Result<Vec<u8>> {\n        let mut header_reader = ZlibDecoder::new(src);\n\n        let mut hdr_buf: [u8; 3] = [0; 3];\n        header_reader.read_exact(&mut hdr_buf)?;\n\n        if !buffer_prefix_matches_marker(hdr_buf, LEPTON_HEADER_MARKER) {\n            return err_exit_code(ExitCode::BadLeptonFile, \"HDR marker not found\");\n        }\n\n        let hdrs = header_reader.read_u32::<LittleEndian>()? as usize;\n\n        let mut hdr_data = Vec::new();\n        hdr_data.resize(hdrs, 0);\n        header_reader.read_exact(&mut hdr_data)?;\n\n        if self.rinfo.garbage_data.len() == 0 {\n            // if we don't have any garbage, assume 0xFF 0xD9 EOI (end of image marker)\n\n            // Kind of broken logic since this assumes a EOI even if the file was\n            // truncated at the EOI, but this is what the file format is.\n            // In this case, this marker will be chopped off later by the\n            // overall JPEG file size limit, so this is not a correctness problem.\n            self.rinfo.garbage_data.extend(EOI);\n        }\n\n        // beginning here: recovery information (needed for exact JPEG recovery)\n        // read further recovery information if any\n        loop {\n            let mut current_lepton_marker = [0u8; 3];\n            match header_reader.read_exact(&mut current_lepton_marker) {\n                Ok(_) => {}\n                Err(e) => {\n                    if e.kind() == ErrorKind::UnexpectedEof {\n                        break;\n                    } else {\n                        return Err(e.into());\n                    }\n                }\n            }\n\n            if buffer_prefix_matches_marker(current_lepton_marker, LEPTON_HEADER_PAD_MARKER) {\n                self.rinfo.pad_bit = Some(header_reader.read_u8()?);\n            } else if buffer_prefix_matches_marker(\n                current_lepton_marker,\n                LEPTON_HEADER_JPG_RESTARTS_MARKER,\n            ) {\n                // CRS marker\n                self.rinfo.rst_cnt_set = true;\n                let rst_count = header_reader.read_u32::<LittleEndian>()?;\n\n                for _i in 0..rst_count {\n                    self.rinfo\n                        .rst_cnt\n                        .push(header_reader.read_u32::<LittleEndian>()?);\n                }\n            } else if buffer_prefix_matches_marker(\n                current_lepton_marker,\n                LEPTON_HEADER_LUMA_SPLIT_MARKER,\n            ) {\n                // HH markup\n                let mut thread_handoffs =\n                    ThreadHandoff::deserialize(current_lepton_marker[2], &mut header_reader)?;\n\n                self.thread_handoff.append(&mut thread_handoffs);\n            } else if buffer_prefix_matches_marker(\n                current_lepton_marker,\n                LEPTON_HEADER_JPG_RESTART_ERRORS_MARKER,\n            ) {\n                // Marker FRS\n                // read number of false set RST markers per scan from file\n                let rst_err_count = header_reader.read_u32::<LittleEndian>()? as usize;\n\n                let mut rst_err_data = Vec::<u8>::new();\n                rst_err_data.resize(rst_err_count, 0);\n\n                header_reader.read_exact(&mut rst_err_data)?;\n\n                self.rinfo.rst_err.append(&mut rst_err_data);\n            } else if buffer_prefix_matches_marker(\n                current_lepton_marker,\n                LEPTON_HEADER_GARBAGE_MARKER,\n            ) {\n                // GRB marker\n                // read garbage (data after end of JPG) from file\n                let garbage_size = header_reader.read_u32::<LittleEndian>()? as usize;\n\n                let mut garbage_data_array = Vec::<u8>::new();\n                garbage_data_array.resize(garbage_size, 0);\n\n                header_reader.read_exact(&mut garbage_data_array)?;\n                self.rinfo.garbage_data = garbage_data_array;\n            } else if buffer_prefix_matches_marker(\n                current_lepton_marker,\n                LEPTON_HEADER_EARLY_EOF_MARKER,\n            ) {\n                self.rinfo.max_cmp = header_reader.read_u32::<LittleEndian>()?;\n                self.rinfo.max_bpos = header_reader.read_u32::<LittleEndian>()?;\n                self.rinfo.max_sah = u8::try_from(header_reader.read_u32::<LittleEndian>()?)?;\n                self.rinfo.max_dpos[0] = header_reader.read_u32::<LittleEndian>()?;\n                self.rinfo.max_dpos[1] = header_reader.read_u32::<LittleEndian>()?;\n                self.rinfo.max_dpos[2] = header_reader.read_u32::<LittleEndian>()?;\n                self.rinfo.max_dpos[3] = header_reader.read_u32::<LittleEndian>()?;\n                self.rinfo.early_eof_encountered = true;\n            } else {\n                return err_exit_code(ExitCode::BadLeptonFile, \"unknown data found\");\n            }\n        }\n\n        // shouldn't be any more data\n        let mut remaining_buf = Vec::new();\n        let remaining = header_reader.read_to_end(&mut remaining_buf)?;\n        assert!(remaining == 0);\n\n        return Ok(hdr_data);\n    }\n\n    pub fn write_lepton_header<W: Write>(\n        &self,\n        writer: &mut W,\n        enabled_features: &EnabledFeatures,\n    ) -> Result<()> {\n        let mut lepton_header = Vec::<u8>::new();\n\n        {\n            // Most of the Lepton header data that is compressed before storage\n            // The data contains recovery information (needed for exact JPEG recovery)\n            let mut mrw = Cursor::new(&mut lepton_header);\n\n            self.write_lepton_jpeg_header(&mut mrw)?;\n            self.write_lepton_pad_bit(&mut mrw)?;\n            self.write_lepton_luma_splits(&mut mrw)?;\n            self.write_lepton_jpeg_restarts_if_needed(&mut mrw)?;\n            self.write_lepton_jpeg_restart_errors_if_needed(&mut mrw)?;\n            self.write_lepton_early_eof_truncation_data_if_needed(&mut mrw)?;\n            self.write_lepton_jpeg_garbage_if_needed(&mut mrw, false)?;\n        }\n\n        let mut compressed_header = Vec::<u8>::new(); // we collect a zlib compressed version of the header here\n        {\n            let mut c = Cursor::new(&mut compressed_header);\n            let mut encoder = ZlibEncoder::new(&mut c, Compression::default());\n\n            encoder.write_all(&lepton_header[..]).context()?;\n            encoder.finish().context()?;\n        }\n\n        writer.write_all(&LEPTON_FILE_HEADER)?;\n        writer.write_u8(LEPTON_VERSION)?;\n\n        if self.jpeg_header.jpeg_type == JpegType::Progressive {\n            writer.write_all(&LEPTON_HEADER_PROGRESSIVE_JPEG_TYPE)?;\n        } else {\n            writer.write_all(&LEPTON_HEADER_BASELINE_JPEG_TYPE)?;\n        }\n\n        writer.write_u8(self.thread_handoff.len() as u8)?;\n        writer.write_all(&[0; 3])?;\n\n        // Original lepton format reserves 12 bytes for git revision. We use this space for additional info\n        // to store information about the version that wrote this.\n        writer.write_u8('M' as u8)?;\n        writer.write_u8('S' as u8)?;\n\n        // write the uncompressed lepton header size\n        // (historical, used by a previous version of the decoder)\n        writer.write_u32::<LittleEndian>(lepton_header.len() as u32)?;\n\n        // write the flags that were used to encode this file\n        writer.write_u8(\n            0x80 | if enabled_features.use_16bit_dc_estimate {\n                1\n            } else {\n                0\n            } | if enabled_features.use_16bit_adv_predict {\n                2\n            } else {\n                0\n            },\n        )?;\n\n        // version of the encoder\n        writer.write_u8(self.encoder_version)?;\n\n        // write the git revision prefix that was used to write this\n        writer.write_all(&self.git_revision_prefix)?;\n\n        writer.write_u32::<LittleEndian>(self.jpeg_file_size)?;\n        writer.write_u32::<LittleEndian>(compressed_header.len() as u32)?;\n        writer.write_all(&compressed_header[..])?;\n\n        writer.write_all(&LEPTON_HEADER_COMPLETION_MARKER)?;\n\n        Ok(())\n    }\n\n    fn write_lepton_jpeg_header<W: Write>(&self, mrw: &mut W) -> Result<()> {\n        // write header to file\n        // marker: \"HDR\" + [size of header]\n        mrw.write_all(&LEPTON_HEADER_MARKER)?;\n\n        mrw.write_u32::<LittleEndian>(self.rinfo.raw_jpeg_header.len() as u32)?;\n\n        // data: data from header\n        mrw.write_all(&self.rinfo.raw_jpeg_header[..])?;\n\n        Ok(())\n    }\n\n    fn write_lepton_pad_bit<W: Write>(&self, mrw: &mut W) -> Result<()> {\n        // marker: P0D\n        mrw.write_all(&LEPTON_HEADER_PAD_MARKER)?;\n\n        // data: this.padBit\n        mrw.write_u8(self.rinfo.pad_bit.unwrap_or(0))?;\n\n        Ok(())\n    }\n\n    fn write_lepton_luma_splits<W: Write>(&self, mrw: &mut W) -> Result<()> {\n        // write luma splits markup HH\n        mrw.write_all(&LEPTON_HEADER_LUMA_SPLIT_MARKER)?;\n\n        // data: serialized luma splits\n        ThreadHandoff::serialize(&self.thread_handoff, mrw)?;\n\n        Ok(())\n    }\n\n    fn write_lepton_jpeg_restarts_if_needed<W: Write>(&self, mrw: &mut W) -> Result<()> {\n        if self.rinfo.rst_cnt.len() > 0 {\n            // marker: CRS\n            mrw.write_all(&LEPTON_HEADER_JPG_RESTARTS_MARKER)?;\n\n            mrw.write_u32::<LittleEndian>(self.rinfo.rst_cnt.len() as u32)?;\n\n            for i in 0..self.rinfo.rst_cnt.len() {\n                mrw.write_u32::<LittleEndian>(self.rinfo.rst_cnt[i])?;\n            }\n        }\n\n        Ok(())\n    }\n\n    fn write_lepton_jpeg_restart_errors_if_needed<W: Write>(&self, mrw: &mut W) -> Result<()> {\n        // write number of false set RST markers per scan (if available) to file\n        if self.rinfo.rst_err.len() > 0 {\n            // marker: \"FRS\" + [number of scans]\n            mrw.write_all(&LEPTON_HEADER_JPG_RESTART_ERRORS_MARKER)?;\n\n            mrw.write_u32::<LittleEndian>(self.rinfo.rst_err.len() as u32)?;\n\n            mrw.write_all(&self.rinfo.rst_err[..])?;\n        }\n\n        Ok(())\n    }\n\n    fn write_lepton_early_eof_truncation_data_if_needed<W: Write>(\n        &self,\n        mrw: &mut W,\n    ) -> Result<()> {\n        if self.rinfo.early_eof_encountered {\n            // EEE marker\n            mrw.write_all(&LEPTON_HEADER_EARLY_EOF_MARKER)?;\n\n            mrw.write_u32::<LittleEndian>(self.rinfo.max_cmp)?;\n            mrw.write_u32::<LittleEndian>(self.rinfo.max_bpos)?;\n            mrw.write_u32::<LittleEndian>(u32::from(self.rinfo.max_sah))?;\n            mrw.write_u32::<LittleEndian>(self.rinfo.max_dpos[0])?;\n            mrw.write_u32::<LittleEndian>(self.rinfo.max_dpos[1])?;\n            mrw.write_u32::<LittleEndian>(self.rinfo.max_dpos[2])?;\n            mrw.write_u32::<LittleEndian>(self.rinfo.max_dpos[3])?;\n        }\n\n        Ok(())\n    }\n\n    fn write_lepton_jpeg_garbage_if_needed<W: Write>(\n        &self,\n        mrw: &mut W,\n        prefix_garbage: bool,\n    ) -> Result<()> {\n        // write garbage (if any) to file\n        if self.rinfo.garbage_data.len() > 0 {\n            // marker: \"PGR/GRB\" + [size of garbage]\n            if prefix_garbage {\n                mrw.write_all(&LEPTON_HEADER_PREFIX_GARBAGE_MARKER)?;\n            } else {\n                mrw.write_all(&LEPTON_HEADER_GARBAGE_MARKER)?;\n            }\n\n            mrw.write_u32::<LittleEndian>(self.rinfo.garbage_data.len() as u32)?;\n            mrw.write_all(&self.rinfo.garbage_data[..])?;\n        }\n\n        Ok(())\n    }\n}\n\n#[test]\nfn test_roundtrip_fixed_header() {\n    let test_data = [\n        (0, true, true),\n        (128, false, false),\n        (129, true, false),\n        (130, false, true),\n        (131, true, true),\n    ];\n    for (v, dc_16_bit, adv_16_bit) in test_data {\n        // test known good version of the header so we can detect breaks\n        let fixed_buffer = [\n            207, 132, 1, 90, 1, 0, 0, 0, 77, 83, 140, 0, 0, 0, v, 187, 18, 52, 86, 120, 123, 0, 0,\n            0, 122, 0, 0, 0,\n        ];\n\n        let mut other_enabled_features = EnabledFeatures::compat_lepton_vector_read();\n\n        let mut other = LeptonHeader::default_boxed();\n        let compressed_header_size = other\n            .read_lepton_fixed_header(&fixed_buffer, &mut other_enabled_features)\n            .unwrap();\n        assert_eq!(compressed_header_size, 122);\n        assert_eq!(other_enabled_features.use_16bit_dc_estimate, dc_16_bit);\n        assert_eq!(other_enabled_features.use_16bit_adv_predict, adv_16_bit);\n    }\n\n    // test read/write all combinations of the flags\n    for (dc_16_bit, adv_16_bit) in [(false, false), (true, false), (false, true), (true, true)] {\n        let mut header = make_minimal_lepton_header();\n        header.git_revision_prefix = [0x12, 0x34, 0x56, 0x78];\n        header.encoder_version = 0xBB;\n\n        let mut enabled_features = EnabledFeatures::compat_lepton_vector_write();\n        enabled_features.use_16bit_dc_estimate = dc_16_bit;\n        enabled_features.use_16bit_adv_predict = adv_16_bit;\n\n        let (result_header, result_features) = verify_roundtrip(&header, &enabled_features);\n\n        assert_eq!(result_features.use_16bit_dc_estimate, dc_16_bit);\n        assert_eq!(result_features.use_16bit_adv_predict, adv_16_bit);\n        assert_eq!(\n            result_header.git_revision_prefix,\n            header.git_revision_prefix\n        );\n        assert_eq!(result_header.encoder_version, header.encoder_version);\n    }\n}\n\n// test serializing and deserializing header\n#[test]\nfn parse_and_write_header() {\n    use crate::structs::lepton_header::FIXED_HEADER_SIZE;\n\n    let lh = make_minimal_lepton_header();\n\n    let enabled_features = EnabledFeatures::compat_lepton_vector_write();\n    let mut serialized = Vec::new();\n    lh.write_lepton_header(&mut Cursor::new(&mut serialized), &enabled_features)\n        .unwrap();\n\n    let mut other = LeptonHeader::default_boxed();\n    let mut other_reader = Cursor::new(&serialized);\n\n    let mut fixed_buffer = [0; FIXED_HEADER_SIZE];\n    other_reader.read_exact(&mut fixed_buffer).unwrap();\n\n    let mut other_enabled_features = EnabledFeatures::compat_lepton_vector_read();\n\n    let compressed_header_size = other\n        .read_lepton_fixed_header(&fixed_buffer, &mut other_enabled_features)\n        .unwrap();\n\n    other\n        .read_compressed_lepton_header(\n            &mut other_reader,\n            &mut other_enabled_features,\n            compressed_header_size,\n        )\n        .unwrap();\n\n    assert_eq!(\n        lh.uncompressed_lepton_header_size,\n        other.uncompressed_lepton_header_size\n    );\n\n    assert_eq!(lh.git_revision_prefix, other.git_revision_prefix);\n    assert_eq!(lh.encoder_version, other.encoder_version);\n\n    assert_eq!(lh.jpeg_file_size, other.jpeg_file_size);\n    assert_eq!(lh.rinfo.raw_jpeg_header, other.rinfo.raw_jpeg_header);\n    assert_eq!(lh.thread_handoff, other.thread_handoff);\n}\n\n#[cfg(test)]\nfn make_minimal_lepton_header() -> Box<LeptonHeader> {\n    // minimal jpeg that will pass the validity read tests\n\n    use crate::jpeg::jpeg_header::parse_jpeg_header;\n    let min_jpeg = [\n        0xffu8, 0xe0, // APP0\n        0x00, 0x10, 0x4a, 0x46, 0x49, 0x46, 0x00, 0x01, 0x01, 0x01, 0x00, 0x48, 0x00, 0x48, 0x00,\n        0x00, 0xff, 0xdb, // DQT\n        0x00, 0x43, 0x00, 0x03, 0x02, 0x02, 0x02, 0x02, 0x02, 0x03, 0x02, 0x02, 0x02, 0x03, 0x03,\n        0x03, 0x03, 0x04, 0x06, 0x04, 0x04, 0x04, 0x04, 0x04, 0x08, 0x06, 0x06, 0x05, 0x06, 0x09,\n        0x08, 0x0a, 0x0a, 0x09, 0x08, 0x09, 0x09, 0x0a, 0x0c, 0x0f, 0x0c, 0x0a, 0x0b, 0x0e, 0x0b,\n        0x09, 0x09, 0x0d, 0x11, 0x0d, 0x0e, 0x0f, 0x10, 0x10, 0x11, 0x10, 0x0a, 0x0c, 0x12, 0x13,\n        0x12, 0x10, 0x13, 0x0f, 0x10, 0x10, 0x10, 0xff, 0xC1, 0x00, 0x0b, 0x08, 0x00,\n        0x10, // width\n        0x00, 0x10, // height\n        0x01, // cmpc\n        0x01, // Jid\n        0x11, // sfv / sfh\n        0x00, 0xff, 0xda, // SOS\n        0x00, 0x08, 0x01, 0x01, 0x00, 0x00, 0x3f, 0x00, 0xd2, 0xcf, 0x20, 0xff, 0xd9, // EOI\n    ];\n\n    let enabled_features = EnabledFeatures::compat_lepton_vector_read();\n\n    let mut lh = LeptonHeader::default_boxed();\n    lh.jpeg_file_size = 123;\n    lh.uncompressed_lepton_header_size = Some(156);\n\n    parse_jpeg_header(\n        &mut Cursor::new(min_jpeg),\n        &enabled_features,\n        &mut lh.jpeg_header,\n        &mut lh.rinfo,\n    )\n    .unwrap();\n    lh.thread_handoff.push(ThreadHandoff {\n        luma_y_start: 0,\n        luma_y_end: 1,\n        segment_offset_in_file: 0, // not serialized (computed based on segment size)\n        segment_size: 500,\n        overhang_byte: 0,\n        num_overhang_bits: 1,\n        last_dc: [1, 2, 3, 0],\n    });\n    lh.thread_handoff.push(ThreadHandoff {\n        luma_y_start: 1,\n        luma_y_end: 2,\n        segment_offset_in_file: 0,\n        segment_size: 600,\n        overhang_byte: 1,\n        num_overhang_bits: 2,\n        last_dc: [2, 3, 4, 0],\n    });\n\n    lh\n}\n\n#[cfg(test)]\nfn verify_roundtrip(\n    header: &LeptonHeader,\n    enabled_features: &EnabledFeatures,\n) -> (Box<LeptonHeader>, EnabledFeatures) {\n    let mut output = Vec::new();\n    header\n        .write_lepton_header(&mut output, &enabled_features)\n        .unwrap();\n\n    let mut read_header = LeptonHeader::default_boxed();\n    let mut read_enabled_features = EnabledFeatures::compat_lepton_vector_read();\n\n    println!(\"output: {:?}\", &output[0..FIXED_HEADER_SIZE]);\n\n    read_header\n        .read_lepton_fixed_header(\n            &output[..FIXED_HEADER_SIZE].try_into().unwrap(),\n            &mut read_enabled_features,\n        )\n        .unwrap();\n    read_header\n        .read_compressed_lepton_header(\n            &mut Cursor::new(&output[FIXED_HEADER_SIZE..]),\n            &mut read_enabled_features,\n            output.len() - FIXED_HEADER_SIZE,\n        )\n        .unwrap();\n\n    (read_header, read_enabled_features)\n}\n"
  },
  {
    "path": "lib/src/structs/mod.rs",
    "content": "/*---------------------------------------------------------------------------------------------\n *  Copyright (c) Microsoft Corporation. All rights reserved.\n *  Licensed under the Apache License, Version 2.0. See LICENSE.txt in the project root for license information.\n *  This software incorporates material from third parties. See NOTICE.txt for details.\n *--------------------------------------------------------------------------------------------*/\n\nmod block_context;\nmod branch;\nmod idct;\nmod lepton_decoder;\nmod lepton_encoder;\npub mod lepton_file_reader;\npub mod lepton_file_writer;\npub mod lepton_header;\nmod model;\npub mod multiplexer;\nmod neighbor_summary;\nmod partial_buffer;\nmod probability_tables;\nmod quantization_tables;\nmod simple_hash;\n\npub mod simple_threadpool;\n\nmod thread_handoff;\nmod vpx_bool_reader;\nmod vpx_bool_writer;\n\n#[cfg(feature = \"micro_benchmark\")]\npub use idct::benchmark_idct;\n#[cfg(feature = \"micro_benchmark\")]\npub use lepton_encoder::benchmark_roundtrip_coefficient;\n"
  },
  {
    "path": "lib/src/structs/model.rs",
    "content": "/*---------------------------------------------------------------------------------------------\n *  Copyright (c) Microsoft Corporation. All rights reserved.\n *  Licensed under the Apache License, Version 2.0. See LICENSE.txt in the project root for license information.\n *  This software incorporates material from third parties. See NOTICE.txt for details.\n *--------------------------------------------------------------------------------------------*/\n\nuse std::cmp;\nuse std::io::{Read, Write};\n\nuse default_boxed::DefaultBoxed;\n\nuse crate::consts::*;\nuse crate::helpers::{calc_sign_index, u16_bit_length, u32_bit_length};\nuse crate::lepton_error::{AddContext, ExitCode, Result, err_exit_code};\nuse crate::metrics::{ModelComponent, ModelSubComponent};\nuse crate::structs::branch::Branch;\nuse crate::structs::quantization_tables::QuantizationTables;\nuse crate::structs::vpx_bool_reader::VPXBoolReader;\nuse crate::structs::vpx_bool_writer::VPXBoolWriter;\n\nconst BLOCK_TYPES: usize = 2; // setting this to 3 gives us ~1% savings.. 2/3 from BLOCK_TYPES=2\n\nconst NUMERIC_LENGTH_MAX: usize = 12;\npub const MAX_EXPONENT: usize = 11; // range from 0 to 1023 requires 11 bins to describe\nconst COEF_BITS: usize = MAX_EXPONENT - 1; // the MSB of the value is always 1\n\nconst NON_ZERO_7X7_COUNT_BITS: usize = 49_usize.ilog2() as usize + 1;\nconst NON_ZERO_EDGE_COUNT_BITS: usize = 7_usize.ilog2() as usize + 1;\n// 0th bin corresponds to 0 non-zeros and therefore is not used for encoding/decoding.\nconst NUM_NON_ZERO_7X7_BINS: usize = 9;\nconst NUM_NON_ZERO_EDGE_BINS: usize = 7;\n\ntype NumNonZerosCountsT = [[[Branch; 1 << NON_ZERO_EDGE_COUNT_BITS]; 8]; 8];\n\nconst RESIDUAL_THRESHOLD_COUNTS_D1: usize = 1 << (1 + RESIDUAL_NOISE_FLOOR);\n// The array was used only on indices [2,7] of [0,7]\nconst RESIDUAL_THRESHOLD_COUNTS_D2: usize = 1 + RESIDUAL_NOISE_FLOOR - 2;\nconst RESIDUAL_THRESHOLD_COUNTS_D3: usize = 1 << RESIDUAL_NOISE_FLOOR;\n\n#[derive(DefaultBoxed)]\npub struct Model {\n    per_color: [ModelPerColor; BLOCK_TYPES],\n\n    counts_dc: [CountsDC; NUMERIC_LENGTH_MAX],\n}\n\nimpl Model {\n    /// Walks through the model and applies the walker function to each branch\n    /// This is used by testing to randomize the model so we can detect\n    /// any mismatches in the way that updates are handled.\n    ///\n    /// This is not used in the normal operation of the codec.\n    ///\n    /// Note: the order of the branch walking must be maintained between the model and the walker,\n    /// otherwise you will break the unit tests.\n    #[cfg(test)]\n    pub fn walk(&mut self, mut walker: impl FnMut(&mut Branch)) {\n        for x in self.per_color.iter_mut() {\n            for y in x.num_non_zeros_counts7x7.iter_mut() {\n                for z in y.iter_mut() {\n                    walker(z);\n                }\n            }\n\n            for y in x.counts.iter_mut() {\n                for z in y.iter_mut() {\n                    for w in z.exponent_counts.iter_mut() {\n                        for q in w.iter_mut() {\n                            walker(q);\n                        }\n                    }\n\n                    for w in z.residual_noise_counts.iter_mut() {\n                        walker(w);\n                    }\n                }\n            }\n\n            for y in x.num_non_zeros_counts1x8.iter_mut() {\n                for z in y.iter_mut() {\n                    for w in z.iter_mut() {\n                        walker(w);\n                    }\n                }\n            }\n\n            for y in x.num_non_zeros_counts8x1.iter_mut() {\n                for z in y.iter_mut() {\n                    for w in z.iter_mut() {\n                        walker(w);\n                    }\n                }\n            }\n\n            for y in x.counts_x.iter_mut() {\n                for z in y.iter_mut() {\n                    for w in z.exponent_counts.iter_mut() {\n                        for q in w.iter_mut() {\n                            walker(q);\n                        }\n                    }\n\n                    for w in z.residual_noise_counts.iter_mut() {\n                        walker(w);\n                    }\n                }\n            }\n\n            for y in x.residual_threshold_counts.iter_mut() {\n                for z in y.iter_mut() {\n                    for w in z.iter_mut() {\n                        walker(w);\n                    }\n                }\n            }\n\n            for y in x.sign_counts.iter_mut() {\n                for z in y.iter_mut() {\n                    walker(z);\n                }\n            }\n        }\n    }\n\n    /// calculates a checksum of the model so we can compare two models for equality\n    #[cfg(test)]\n    pub fn model_checksum(&mut self) -> u64 {\n        use std::hash::Hasher;\n\n        use siphasher::sip::SipHasher13;\n\n        let mut h = SipHasher13::new();\n        self.walk(|x| {\n            h.write_u16(x.get_count());\n        });\n\n        h.finish()\n    }\n}\n\n// Arrays are more or less in the order of access.\n// Array `residual_noise_counts` is split into 7x7 and edge parts to save memory.\n// Some dimensions are exchanged to get lower changing rate outer, lowering cache misses frequency.\n\n#[derive(DefaultBoxed)]\npub struct ModelPerColor {\n    // `num_non_zeros_context` cannot exceed 25, see `calc_non_zero_counts_context_7x7`\n    num_non_zeros_counts7x7:\n        [[Branch; 1 << NON_ZERO_7X7_COUNT_BITS]; 1 + NON_ZERO_TO_BIN[25] as usize],\n\n    counts: [[Counts7x7; 49]; NUM_NON_ZERO_7X7_BINS],\n\n    num_non_zeros_counts1x8: NumNonZerosCountsT,\n    num_non_zeros_counts8x1: NumNonZerosCountsT,\n\n    counts_x: [[CountsEdge; 14]; NUM_NON_ZERO_EDGE_BINS],\n\n    residual_threshold_counts: [[[Branch; RESIDUAL_THRESHOLD_COUNTS_D3];\n        RESIDUAL_THRESHOLD_COUNTS_D2]; RESIDUAL_THRESHOLD_COUNTS_D1],\n\n    sign_counts: [[Branch; NUMERIC_LENGTH_MAX]; 3],\n}\n\n#[derive(DefaultBoxed)]\nstruct Counts7x7 {\n    exponent_counts: [[Branch; MAX_EXPONENT]; NUMERIC_LENGTH_MAX],\n    residual_noise_counts: [Branch; COEF_BITS],\n}\n\n#[derive(DefaultBoxed)]\nstruct CountsEdge {\n    // predictors for exponents are max 11 bits wide, not 12 since they are clamped\n    exponent_counts: [[Branch; MAX_EXPONENT]; MAX_EXPONENT],\n    // size by possible range of `min_threshold - 1`\n    // that is from 0 up to `bit_width(max(freq_max)) - RESIDUAL_NOISE_FLOOR - 1`\n    residual_noise_counts: [Branch; 3],\n}\n\n#[derive(DefaultBoxed)]\nstruct CountsDC {\n    exponent_counts: [[Branch; MAX_EXPONENT]; 17],\n    residual_noise_counts: [Branch; COEF_BITS],\n}\n\nimpl ModelPerColor {\n    #[inline(never)]\n    pub fn read_coef<R: Read>(\n        &mut self,\n        bool_reader: &mut VPXBoolReader<R>,\n        zig49: usize,\n        num_non_zeros_bin: usize,\n        best_prior_bit_len: usize,\n    ) -> std::io::Result<i16> {\n        let (exp, sign, bits) =\n            self.get_coef_branches(num_non_zeros_bin, zig49, best_prior_bit_len);\n\n        return Model::read_length_sign_coef(\n            bool_reader,\n            exp,\n            sign,\n            bits,\n            ModelComponent::Coef(ModelSubComponent::Exp),\n            ModelComponent::Coef(ModelSubComponent::Sign),\n            ModelComponent::Coef(ModelSubComponent::Noise),\n        );\n    }\n\n    #[inline(never)]\n    pub fn write_coef<W: Write>(\n        &mut self,\n        bool_writer: &mut VPXBoolWriter<W>,\n        coef: i16,\n        zig49: usize,\n        num_non_zeros_bin: usize,\n        best_prior_bit_len: usize,\n    ) -> Result<()> {\n        let (exp, sign, bits) =\n            self.get_coef_branches(num_non_zeros_bin, zig49, best_prior_bit_len);\n\n        return Model::write_length_sign_coef(\n            bool_writer,\n            coef,\n            exp,\n            sign,\n            bits,\n            ModelComponent::Coef(ModelSubComponent::Exp),\n            ModelComponent::Coef(ModelSubComponent::Sign),\n            ModelComponent::Coef(ModelSubComponent::Noise),\n        )\n        .context();\n    }\n\n    #[inline(always)]\n    fn get_coef_branches(\n        &mut self,\n        num_non_zeros_bin: usize,\n        zig49: usize,\n        best_prior_bit_len: usize,\n    ) -> (\n        &mut [Branch; MAX_EXPONENT],\n        &mut Branch,\n        &mut [Branch; COEF_BITS],\n    ) {\n        // these bounds checks happen anyway, but we can provide more helpful error messages\n        // and it also means that the compiler can move the actual array references around\n        // if it helps with performance\n        assert!(\n            num_non_zeros_bin < NUM_NON_ZERO_7X7_BINS,\n            \"num_non_zeros_bin {0} too high\",\n            num_non_zeros_bin\n        );\n        assert!(zig49 < 49, \"zig49 {0} too high\", num_non_zeros_bin);\n        assert!(\n            best_prior_bit_len < NUMERIC_LENGTH_MAX,\n            \"best_prior_bit_len {0} too high\",\n            best_prior_bit_len\n        );\n\n        let exp = &mut self.counts[num_non_zeros_bin][zig49].exponent_counts[best_prior_bit_len];\n        let sign = &mut self.sign_counts[0][0];\n        let bits = &mut self.counts[num_non_zeros_bin][zig49].residual_noise_counts;\n\n        (exp, sign, bits)\n    }\n\n    pub fn write_non_zero_7x7_count<W: Write>(\n        &mut self,\n        bool_writer: &mut VPXBoolWriter<W>,\n        num_non_zeros_7x7_context_bin: u8,\n        num_non_zeros_7x7: u8,\n    ) -> Result<()> {\n        let num_non_zeros_prob =\n            &mut self.num_non_zeros_counts7x7[usize::from(num_non_zeros_7x7_context_bin)];\n\n        return bool_writer\n            .put_grid(\n                num_non_zeros_7x7,\n                num_non_zeros_prob,\n                ModelComponent::NonZero7x7Count,\n            )\n            .context();\n    }\n\n    pub fn write_non_zero_edge_count<W: Write, const HORIZONTAL: bool>(\n        &mut self,\n        bool_writer: &mut VPXBoolWriter<W>,\n        est_eob: u8,\n        num_non_zeros_bin: u8,\n        num_non_zeros_edge: u8,\n    ) -> Result<()> {\n        let prob_edge_eob =\n            self.get_non_zero_counts_edge_mut::<HORIZONTAL>(est_eob, num_non_zeros_bin);\n\n        return bool_writer\n            .put_grid(\n                num_non_zeros_edge,\n                prob_edge_eob,\n                ModelComponent::NonZeroEdgeCount,\n            )\n            .context();\n    }\n\n    pub fn read_non_zero_7x7_count<R: Read>(\n        &mut self,\n        bool_reader: &mut VPXBoolReader<R>,\n        num_non_zeros_7x7_context_bin: u8,\n    ) -> Result<u8> {\n        let num_non_zeros_prob =\n            &mut self.num_non_zeros_counts7x7[usize::from(num_non_zeros_7x7_context_bin)];\n\n        return Ok(bool_reader\n            .get_grid(num_non_zeros_prob, ModelComponent::NonZero7x7Count)\n            .context()? as u8);\n    }\n\n    pub fn read_non_zero_edge_count<R: Read, const HORIZONTAL: bool>(\n        &mut self,\n        bool_reader: &mut VPXBoolReader<R>,\n        est_eob: u8,\n        num_non_zeros_bin: u8,\n    ) -> Result<u8> {\n        let prob_edge_eob =\n            self.get_non_zero_counts_edge_mut::<HORIZONTAL>(est_eob, num_non_zeros_bin);\n\n        return Ok(bool_reader\n            .get_grid(prob_edge_eob, ModelComponent::NonZeroEdgeCount)\n            .context()? as u8);\n    }\n\n    pub fn read_edge_coefficient<R: Read>(\n        &mut self,\n        bool_reader: &mut VPXBoolReader<R>,\n        qt: &QuantizationTables,\n        zig15offset: usize,\n        num_non_zeros_edge: u8,\n        best_prior: i32,\n    ) -> Result<i16> {\n        let num_non_zeros_edge_bin = usize::from(num_non_zeros_edge) - 1;\n\n        // bounds checks will test these anyway, so check here for better\n        // error messages and also gives the optimizer more freedom to move code around\n        assert!(\n            num_non_zeros_edge_bin < NUM_NON_ZERO_EDGE_BINS,\n            \"num_non_zeros_edge_bin {0} too high\",\n            num_non_zeros_edge_bin\n        );\n\n        assert!(zig15offset < 14, \"zig15offset {0} too high\", zig15offset);\n\n        // we cap the bit length since the prior prediction can be wonky\n        let best_prior_abs = best_prior.unsigned_abs();\n        let best_prior_bit_len =\n            cmp::min(MAX_EXPONENT - 1, u32_bit_length(best_prior_abs) as usize);\n\n        let length_branches = &mut self.counts_x[num_non_zeros_edge_bin][zig15offset]\n            .exponent_counts[best_prior_bit_len];\n\n        let length = bool_reader\n            .get_unary_encoded(\n                length_branches,\n                ModelComponent::Edge(ModelSubComponent::Exp),\n            )\n            .context()? as i32;\n\n        let mut coef = 0;\n        if length != 0 {\n            // best_prior in the initial Lepton implementation is stored as i32,\n            // but the sign here is taken from its truncated i16 value\n            let sign =\n                &mut self.sign_counts[calc_sign_index(best_prior as i16)][best_prior_bit_len];\n\n            let neg = !bool_reader\n                .get_bit(sign, ModelComponent::Edge(ModelSubComponent::Sign))\n                .context()?;\n\n            coef = 1;\n\n            if length > 1 {\n                let min_threshold: i32 = qt.get_min_noise_threshold(zig15offset).into();\n                let mut i: i32 = length - 2;\n\n                if i >= min_threshold {\n                    let thresh_prob = self.get_residual_threshold_counts_mut(\n                        best_prior_abs,\n                        min_threshold,\n                        length,\n                    );\n\n                    let mut decoded_so_far = 1;\n                    while i >= min_threshold {\n                        let cur_bit = bool_reader.get_bit(\n                            &mut thresh_prob[decoded_so_far],\n                            ModelComponent::Edge(ModelSubComponent::Residual),\n                        )? as i16;\n\n                        coef <<= 1;\n                        coef |= cur_bit;\n\n                        // since we are not strict about rejecting jpegs with out of range coefs\n                        // we just make those less efficient by reusing the same probability bucket\n                        decoded_so_far = cmp::min(coef as usize, thresh_prob.len() - 1);\n\n                        i -= 1;\n                    }\n                }\n\n                if i >= 0 {\n                    let res_prob = &mut self.counts_x[num_non_zeros_edge_bin][zig15offset]\n                        .residual_noise_counts;\n\n                    coef <<= i + 1;\n                    coef |= bool_reader.get_n_bits(\n                        i as usize + 1,\n                        res_prob,\n                        ModelComponent::Edge(ModelSubComponent::Noise),\n                    )? as i16;\n                }\n            }\n\n            if neg {\n                coef = -coef;\n            }\n        }\n        Ok(coef)\n    }\n\n    pub fn write_edge_coefficient<W: Write>(\n        &mut self,\n        bool_writer: &mut VPXBoolWriter<W>,\n        qt: &QuantizationTables,\n        coef: i16,\n        zig15offset: usize,\n        num_non_zeros_edge: u8,\n        best_prior: i32,\n    ) -> Result<()> {\n        let num_non_zeros_edge_bin = usize::from(num_non_zeros_edge) - 1;\n\n        // bounds checks will test these anyway, so check here for better\n        // error messages and also gives the optimizer more freedom to move code around\n        assert!(\n            num_non_zeros_edge_bin < NUM_NON_ZERO_EDGE_BINS,\n            \"num_non_zeros_edge_bin {0} too high\",\n            num_non_zeros_edge_bin\n        );\n\n        assert!(zig15offset < 14, \"zig15offset {0} too high\", zig15offset);\n\n        // we cap the bit length since the prior prediction can be wonky\n        let best_prior_abs = best_prior.unsigned_abs();\n        let best_prior_bit_len =\n            cmp::min(MAX_EXPONENT - 1, u32_bit_length(best_prior_abs) as usize);\n\n        let abs_coef = coef.unsigned_abs();\n        let length = u16_bit_length(abs_coef) as usize;\n\n        let exp_array = &mut self.counts_x[num_non_zeros_edge_bin][zig15offset].exponent_counts\n            [best_prior_bit_len];\n\n        if length > MAX_EXPONENT {\n            return err_exit_code(ExitCode::CoefficientOutOfRange, \"CoefficientOutOfRange\");\n        }\n\n        bool_writer.put_unary_encoded(\n            length,\n            exp_array,\n            ModelComponent::Edge(ModelSubComponent::Exp),\n        )?;\n\n        if coef != 0 {\n            // best_prior in the initial Lepton implementation is stored as i32,\n            // but the sign here is taken from its truncated i16 value\n            let sign =\n                &mut self.sign_counts[calc_sign_index(best_prior as i16)][best_prior_bit_len];\n\n            bool_writer.put_bit(\n                coef >= 0,\n                sign,\n                ModelComponent::Edge(ModelSubComponent::Sign),\n            )?;\n\n            if length > 1 {\n                let min_threshold = i32::from(qt.get_min_noise_threshold(zig15offset));\n                let mut i: i32 = length as i32 - 2;\n\n                if i >= min_threshold {\n                    let thresh_prob = self.get_residual_threshold_counts_mut(\n                        best_prior_abs,\n                        min_threshold,\n                        length as i32,\n                    );\n\n                    let mut encoded_so_far = 1;\n                    while i >= min_threshold {\n                        let cur_bit = (abs_coef & (1 << i)) != 0;\n                        bool_writer.put_bit(\n                            cur_bit,\n                            &mut thresh_prob[encoded_so_far],\n                            ModelComponent::Edge(ModelSubComponent::Residual),\n                        )?;\n\n                        encoded_so_far <<= 1;\n                        if cur_bit {\n                            encoded_so_far |= 1;\n                        }\n\n                        // since we are not strict about rejecting jpegs with out of range coefs\n                        // we just make those less efficient by reusing the same probability bucket\n                        encoded_so_far = cmp::min(encoded_so_far, thresh_prob.len() - 1);\n\n                        i -= 1;\n                    }\n                }\n\n                if i >= 0 {\n                    let res_prob = &mut self.counts_x[num_non_zeros_edge_bin][zig15offset]\n                        .residual_noise_counts;\n\n                    bool_writer\n                        .put_n_bits(\n                            abs_coef as usize,\n                            i as usize + 1,\n                            res_prob,\n                            ModelComponent::Edge(ModelSubComponent::Noise),\n                        )\n                        .context()?;\n                }\n            }\n        }\n\n        Ok(())\n    }\n\n    fn get_residual_threshold_counts_mut(\n        &mut self,\n        best_prior_abs: u32,\n        min_threshold: i32,\n        length: i32,\n    ) -> &mut [Branch; RESIDUAL_THRESHOLD_COUNTS_D3] {\n        // Need to & 0xffff since C++ version casts to a uint16_t in the array lookup\n        // and we need to match that behavior. It's unlikely that this will be a problem\n        // since it would require an extremely large best_prior, which is difficult\n        // due to the range limits of 2047 of the coefficients but still in the\n        // interest of correctness we should match the C++ behavior.\n        // This function was invoked only with `length - 2 >= min_threshold`,\n        // then 2nd array index range can be shortened by 2.\n        return &mut self.residual_threshold_counts[cmp::min(\n            ((best_prior_abs & 0xffff) >> min_threshold) as usize,\n            self.residual_threshold_counts.len() - 1,\n        )][cmp::min(\n            (length - min_threshold - 2) as usize,\n            self.residual_threshold_counts[0].len() - 1,\n        )];\n    }\n\n    fn get_non_zero_counts_edge_mut<const HORIZONTAL: bool>(\n        &mut self,\n        est_eob: u8,\n        num_nonzeros_bin: u8,\n    ) -> &mut [Branch; 8] {\n        if HORIZONTAL {\n            return &mut self.num_non_zeros_counts8x1[est_eob as usize][num_nonzeros_bin as usize];\n        } else {\n            return &mut self.num_non_zeros_counts1x8[est_eob as usize][num_nonzeros_bin as usize];\n        }\n    }\n}\n\nimpl Model {\n    pub fn get_per_color(&mut self, color_index: usize) -> &mut ModelPerColor {\n        &mut self.per_color[color_index]\n    }\n\n    pub fn read_dc<R: Read>(\n        &mut self,\n        bool_reader: &mut VPXBoolReader<R>,\n        color_index: usize,\n        uncertainty: i16,\n        uncertainty2: i16,\n    ) -> Result<i16> {\n        let (exp, sign, bits) = self.get_dc_branches(uncertainty, uncertainty2, color_index);\n\n        return Model::read_length_sign_coef(\n            bool_reader,\n            exp,\n            sign,\n            bits,\n            ModelComponent::DC(ModelSubComponent::Exp),\n            ModelComponent::DC(ModelSubComponent::Sign),\n            ModelComponent::DC(ModelSubComponent::Noise),\n        )\n        .context();\n    }\n\n    pub fn write_dc<W: Write>(\n        &mut self,\n        bool_writer: &mut VPXBoolWriter<W>,\n        color_index: usize,\n        coef: i16,\n        uncertainty: i16,\n        uncertainty2: i16,\n    ) -> Result<()> {\n        let (exp, sign, bits) = self.get_dc_branches(uncertainty, uncertainty2, color_index);\n\n        return Model::write_length_sign_coef(\n            bool_writer,\n            coef,\n            exp,\n            sign,\n            bits,\n            ModelComponent::DC(ModelSubComponent::Exp),\n            ModelComponent::DC(ModelSubComponent::Sign),\n            ModelComponent::DC(ModelSubComponent::Noise),\n        )\n        .context();\n    }\n\n    #[inline(always)]\n    fn get_dc_branches(\n        &mut self,\n        uncertainty: i16,\n        uncertainty2: i16,\n        color_index: usize,\n    ) -> (\n        &mut [Branch; MAX_EXPONENT],\n        &mut Branch,\n        &mut [Branch; COEF_BITS],\n    ) {\n        let len_abs_mxm = u16_bit_length(uncertainty.unsigned_abs());\n        let len_abs_offset_to_closest_edge = u16_bit_length(uncertainty2.unsigned_abs());\n        let len_abs_mxm_clamp = cmp::min(len_abs_mxm as usize, self.counts_dc.len() - 1);\n\n        let exp = &mut self.counts_dc[len_abs_mxm_clamp].exponent_counts\n            [len_abs_offset_to_closest_edge as usize];\n        let sign =\n            &mut self.per_color[color_index].sign_counts[0][calc_sign_index(uncertainty2) + 1]; // +1 to separate from sign_counts[0][0]\n        let bits = &mut self.counts_dc[len_abs_mxm_clamp].residual_noise_counts;\n\n        (exp, sign, bits)\n    }\n\n    #[inline(always)]\n    fn read_length_sign_coef<const A: usize, const B: usize, R: Read>(\n        bool_reader: &mut VPXBoolReader<R>,\n        magnitude_branches: &mut [Branch; A],\n        sign_branch: &mut Branch,\n        bits_branch: &mut [Branch; B],\n        mag_cmp: ModelComponent,\n        sign_cmp: ModelComponent,\n        bits_cmp: ModelComponent,\n    ) -> std::io::Result<i16> {\n        debug_assert!(\n            A - 1 <= B,\n            \"A (max mag) should be not more than B+1 (max bits). A={0} B={1} from {2:?}\",\n            A,\n            B,\n            mag_cmp\n        );\n\n        let length = bool_reader.get_unary_encoded(magnitude_branches, mag_cmp)?;\n\n        let mut coef: i16 = 0;\n        if length != 0 {\n            let neg = !bool_reader.get_bit(sign_branch, sign_cmp)?;\n            if length > 1 {\n                coef = bool_reader.get_n_bits(length - 1, bits_branch, bits_cmp)? as i16;\n            }\n\n            coef |= (1 << (length - 1)) as i16;\n\n            if neg {\n                coef = -coef;\n            }\n        }\n\n        return Ok(coef);\n    }\n\n    fn write_length_sign_coef<const A: usize, const B: usize, W: Write>(\n        bool_writer: &mut VPXBoolWriter<W>,\n        coef: i16,\n        magnitude_branches: &mut [Branch; A],\n        sign_branch: &mut Branch,\n        bits_branch: &mut [Branch; B],\n        mag_cmp: ModelComponent,\n        sign_cmp: ModelComponent,\n        bits_cmp: ModelComponent,\n    ) -> Result<()> {\n        debug_assert!(\n            A - 1 <= B,\n            \"A (max mag) should be not more than B+1 (max bits). A={0} B={1} from {2:?}\",\n            A,\n            B,\n            mag_cmp,\n        );\n\n        let abs_coef = coef.unsigned_abs();\n        let coef_bit_len = u16_bit_length(abs_coef);\n\n        if coef_bit_len > A as u8 {\n            return err_exit_code(\n                ExitCode::CoefficientOutOfRange,\n                \"coefficient > MAX_EXPONENT\",\n            );\n        }\n\n        bool_writer.put_unary_encoded(coef_bit_len as usize, magnitude_branches, mag_cmp)?;\n        if coef != 0 {\n            bool_writer.put_bit(coef > 0, sign_branch, sign_cmp)?;\n        }\n\n        if coef_bit_len > 1 {\n            debug_assert!(\n                (abs_coef & (1 << (coef_bit_len - 1))) != 0,\n                \"Biggest bit must be set\"\n            );\n            debug_assert!(\n                (abs_coef & (1 << coef_bit_len)) == 0,\n                \"Beyond Biggest bit must be zero\"\n            );\n\n            bool_writer.put_n_bits(\n                abs_coef as usize,\n                coef_bit_len as usize - 1,\n                bits_branch,\n                bits_cmp,\n            )?;\n        }\n\n        Ok(())\n    }\n}\n"
  },
  {
    "path": "lib/src/structs/multiplexer.rs",
    "content": "//! Implements a multiplexer that reads and writes blocks to a stream from multiple partitions. Each\n//! partition can run on it own thread to allow for increased parallelism when processing large images.\n//!\n//! The write implementation identifies the blocks by partition_id and tries to write in 64K blocks. The file\n//! ends up with an interleaved stream of blocks from each partition.\n//!\n//! The read implementation reads the blocks from the file and sends them to the appropriate worker thread\n//! for the partition.\n\nuse std::cmp;\nuse std::collections::VecDeque;\nuse std::io::{Cursor, Read, Write};\nuse std::mem::swap;\nuse std::sync::mpsc::{Receiver, Sender, TryRecvError, channel};\nuse std::sync::{Arc, Mutex};\n\nuse byteorder::WriteBytesExt;\n\nuse super::simple_threadpool::LeptonThreadPool;\n\nuse crate::lepton_error::{AddContext, ExitCode, Result};\nuse crate::{LeptonError, Metrics};\nuse crate::{helpers::*, lepton_error::err_exit_code, structs::partial_buffer::PartialBuffer};\n\n/// The message that is sent between the threads\nenum Message {\n    Eof(usize),\n    WriteBlock(usize, Vec<u8>),\n}\n\npub struct MultiplexWriter {\n    partition_id: usize,\n    sender: Sender<Message>,\n    buffer: Vec<u8>,\n}\n\nconst WRITE_BUFFER_SIZE: usize = 65536;\n\nimpl Write for MultiplexWriter {\n    fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {\n        let mut copy_start = 0;\n        while copy_start < buf.len() {\n            let amount_to_copy = cmp::min(\n                WRITE_BUFFER_SIZE - self.buffer.len(),\n                buf.len() - copy_start,\n            );\n            self.buffer\n                .extend_from_slice(&buf[copy_start..copy_start + amount_to_copy]);\n\n            if self.buffer.len() == WRITE_BUFFER_SIZE {\n                self.flush()?;\n            }\n\n            copy_start += amount_to_copy;\n        }\n\n        Ok(buf.len())\n    }\n\n    fn flush(&mut self) -> std::io::Result<()> {\n        if self.buffer.len() > 0 {\n            let mut new_buffer = Vec::with_capacity(WRITE_BUFFER_SIZE);\n            swap(&mut new_buffer, &mut self.buffer);\n\n            self.sender\n                .send(Message::WriteBlock(self.partition_id, new_buffer))\n                .unwrap();\n        }\n        Ok(())\n    }\n}\n\n/// Collects the thread results and errors and returns them as a vector\nstruct ThreadResults<RESULT> {\n    results: Vec<Receiver<Result<RESULT>>>,\n}\n\nimpl<RESULT> ThreadResults<RESULT> {\n    fn new() -> Self {\n        ThreadResults {\n            results: Vec::new(),\n        }\n    }\n    /// creates a closure that wraps the passed in closure, catches any panics,\n    /// collects the return result and send it to the receiver to collect.\n    fn send_results<T: FnOnce() -> Result<RESULT> + Send + 'static>(\n        &mut self,\n        f: T,\n    ) -> impl FnOnce() + use<RESULT, T> {\n        let (tx, rx) = channel();\n\n        self.results.push(rx);\n\n        move || {\n            let r = catch_unwind_result(f);\n            let _ = tx.send(r);\n        }\n    }\n\n    /// extracts the results from all the receivers and returns them as a vector, or returns an\n    /// error if any of the threads errored out.\n    fn receive_results(&mut self) -> Result<Vec<RESULT>> {\n        let mut final_results = Vec::new();\n\n        let mut error_found = None;\n        for r in self.results.drain(..) {\n            match r.recv() {\n                Ok(Ok(r)) => final_results.push(r),\n                Ok(Err(e)) => {\n                    error_found = Some(e);\n                }\n                Err(e) => {\n                    // prefer real errors over broken channel errors\n                    if error_found.is_none() {\n                        error_found = Some(e.into());\n                    }\n                }\n            }\n        }\n\n        if let Some(error) = error_found {\n            Err(error)\n        } else {\n            Ok(final_results)\n        }\n    }\n}\n\n/// Given an arbitrary writer, this function will launch the given number of partitions and call the processor function\n/// on each of them, and collect the output written by each partition to the writer in blocks identified by the partition_id.\n///\n/// This output stream can be processed by multiple_read to get the data back, using the same number of threads.\npub fn multiplex_write<WRITE, FN, RESULT>(\n    writer: &mut WRITE,\n    num_partitions: usize,\n    max_processor_threads: usize,\n    thread_pool: &dyn LeptonThreadPool,\n    processor: FN,\n) -> Result<Vec<RESULT>>\nwhere\n    WRITE: Write,\n    FN: Fn(&mut MultiplexWriter, usize) -> Result<RESULT> + Send + Sync + 'static,\n    RESULT: Send + 'static,\n{\n    let mut thread_results = ThreadResults::new();\n\n    // receives packets from threads as they are generated\n    let mut packet_receivers = Vec::new();\n\n    let arc_processor = Arc::new(Box::new(processor));\n\n    let mut work: VecDeque<Box<dyn FnOnce() + Send>> = VecDeque::new();\n\n    for partition_id in 0..num_partitions {\n        let (tx, rx) = channel();\n\n        let mut thread_writer = MultiplexWriter {\n            partition_id,\n            sender: tx,\n            buffer: Vec::with_capacity(WRITE_BUFFER_SIZE),\n        };\n\n        let processor_clone = arc_processor.clone();\n\n        let f = Box::new(thread_results.send_results(move || {\n            let r = processor_clone(&mut thread_writer, partition_id)?;\n\n            thread_writer.flush().context()?;\n\n            thread_writer\n                .sender\n                .send(Message::Eof(partition_id))\n                .context()?;\n            Ok(r)\n        }));\n        work.push_back(f);\n\n        packet_receivers.push(rx);\n    }\n\n    drop(arc_processor);\n\n    if thread_pool.max_parallelism() > 1 {\n        spawn_processor_threads(thread_pool, max_processor_threads, work);\n    } else {\n        // single threaded, just run all the work inline, which will\n        // fill build up the receiver queue to write the image\n        for f in work.drain(..) {\n            f();\n        }\n    }\n\n    // now we have all the threads running, we can write the data to the writer\n    // carusel through the threads and write the data to the writer so that they\n    // get written in a deterministic order.\n    let mut current_thread_writer = 0;\n    loop {\n        match packet_receivers[current_thread_writer].recv() {\n            Ok(Message::WriteBlock(partition_id, b)) => {\n                // block length and partition header\n                let tid = partition_id as u8;\n                let l = b.len() - 1;\n                if l == 4095 || l == 16383 || l == 65535 {\n                    // length is a special power of 2 - standard block length is 2^16\n                    writer.write_u8(tid | ((l.ilog2() as u8 >> 1) - 4) << 4)?;\n                } else {\n                    writer.write_u8(tid)?;\n                    writer.write_u8((l & 0xff) as u8)?;\n                    writer.write_u8(((l >> 8) & 0xff) as u8)?;\n                }\n                // block itself\n                writer.write_all(&b[..])?;\n\n                // go to next thread\n                current_thread_writer = (current_thread_writer + 1) % packet_receivers.len();\n            }\n            Ok(Message::Eof(_)) | Err(_) => {\n                packet_receivers.remove(current_thread_writer);\n                if packet_receivers.len() == 0 {\n                    break;\n                }\n\n                current_thread_writer = current_thread_writer % packet_receivers.len();\n            }\n        }\n    }\n\n    thread_results.receive_results()\n}\n\n/// Used by the processor thread to read data in a blocking way.\n/// The partition_id is used only to assert that we are only\n/// getting the data that we are expecting.\npub struct MultiplexReader {\n    /// the multiplexed thread stream we are processing\n    partition_id: usize,\n\n    /// the receiver part of the channel to get more buffers\n    receiver: Receiver<Message>,\n\n    /// what we are reading. When this returns zero, we try to\n    /// refill the buffer if we haven't reached the end of the stream\n    current_buffer: Cursor<Vec<u8>>,\n\n    /// once we get told we are at the end of the stream, we just\n    /// always return 0 bytes\n    end_of_file: bool,\n}\n\nimpl Read for MultiplexReader {\n    /// fast path for reads. If we run out of data, take the slow path\n    #[inline(always)]\n    fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {\n        let amount_read = self.current_buffer.read(buf)?;\n        if amount_read > 0 {\n            return Ok(amount_read);\n        }\n\n        self.read_slow(buf)\n    }\n}\n\nimpl MultiplexReader {\n    /// slow path for reads, try to get a new buffer or\n    /// return zero if at the end of the stream\n    #[cold]\n    #[inline(never)]\n    fn read_slow(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {\n        while !self.end_of_file {\n            let amount_read = self.current_buffer.read(buf)?;\n            if amount_read > 0 {\n                return Ok(amount_read);\n            }\n\n            match self.receiver.recv() {\n                Ok(r) => match r {\n                    Message::Eof(_tid) => {\n                        self.end_of_file = true;\n                    }\n                    Message::WriteBlock(tid, block) => {\n                        debug_assert_eq!(\n                            tid, self.partition_id,\n                            \"incoming thread must be equal to processing thread\"\n                        );\n                        self.current_buffer = Cursor::new(block);\n                    }\n                },\n                Err(e) => {\n                    return std::io::Result::Err(std::io::Error::new(std::io::ErrorKind::Other, e));\n                }\n            }\n        }\n\n        // nothing if we reached the end of file\n        return Ok(0);\n    }\n}\n\n/// Reads data in multiplexed format and sends it to the appropriate processor, each\n/// running on its own thread. The processor function is called with the partition_id and\n/// a blocking reader that it can use to read its own data.\n///\n/// Once the multiplexed data is finished reading, we break the channel to the worker threads\n/// causing processor that is trying to read from the channel to error out and exit. After all\n/// the readers have exited, we collect the results/errors from all the processors and return a vector\n/// of the results back to the caller.\npub struct MultiplexReaderState<RESULT> {\n    sender_channels: Vec<Sender<Message>>,\n    receiver_channels: Vec<Receiver<MultiplexReadResult<RESULT>>>,\n    retention_bytes: usize,\n    current_state: State,\n    single_thread_work: Option<VecDeque<Box<dyn FnOnce() + Send>>>,\n    merged_metrics: Metrics,\n}\n\nenum State {\n    StartBlock,\n    U16Length(u8),\n    Block(u8, usize),\n}\n\npub enum MultiplexReadResult<RESULT> {\n    Result(RESULT),\n    Error(LeptonError),\n    Complete(Metrics),\n}\n\n/// Given a number of threads, this function will create a multiplexed reader state that\n/// can be used to process incoming multiplexed data. The processor function is called\n/// on each thread with the partition_id and a blocking reader that it can use to read its own data.\n///\n/// Each processor is also given a sender channel that it can use to send back results or errors.\n/// Partial results can be sent back by sending multiple results before the end of file is reached.\n///\n/// The state object returned can be used to process incoming data and retrieve results/errors\n/// from the threads.\npub fn multiplex_read<FN, RESULT>(\n    num_partitions: usize,\n    max_processor_threads: usize,\n    thread_pool: &dyn LeptonThreadPool,\n    retention_bytes: usize,\n    processor: FN,\n) -> MultiplexReaderState<RESULT>\nwhere\n    FN: Fn(usize, &mut MultiplexReader, &Sender<MultiplexReadResult<RESULT>>) -> Result<()>\n        + Send\n        + Sync\n        + 'static,\n    RESULT: Send + 'static,\n{\n    let arc_processor = Arc::new(Box::new(processor));\n\n    let mut channel_to_sender = Vec::new();\n\n    // collect the worker threads in a queue so we can spawn them\n    let mut work = VecDeque::new();\n    let mut result_receiver = Vec::new();\n\n    for partition_id in 0..num_partitions {\n        let (tx, rx) = channel::<Message>();\n        channel_to_sender.push(tx);\n\n        let cloned_processor = arc_processor.clone();\n\n        let (result_tx, result_rx) = channel::<MultiplexReadResult<RESULT>>();\n        result_receiver.push(result_rx);\n\n        let f: Box<dyn FnOnce() + Send> = Box::new(move || {\n            // get the appropriate receiver so we can read out data from it\n            let mut proc_reader = MultiplexReader {\n                partition_id,\n                current_buffer: Cursor::new(Vec::new()),\n                receiver: rx,\n                end_of_file: false,\n            };\n\n            if let Err(e) =\n                catch_unwind_result(|| cloned_processor(partition_id, &mut proc_reader, &result_tx))\n            {\n                let _ = result_tx.send(MultiplexReadResult::Error(e));\n            }\n        });\n\n        work.push_back(f);\n    }\n\n    let single_thread_work = if thread_pool.max_parallelism() > 1 {\n        spawn_processor_threads(thread_pool, max_processor_threads, work);\n        None\n    } else {\n        Some(work)\n    };\n\n    MultiplexReaderState {\n        sender_channels: channel_to_sender,\n        receiver_channels: result_receiver,\n        current_state: State::StartBlock,\n        retention_bytes,\n        single_thread_work,\n        merged_metrics: Metrics::default(),\n    }\n}\n\n/// spawns the processor threads to handle the work items in the queue. There may be fewer workers\n/// than work items.\nfn spawn_processor_threads(\n    thread_pool: &dyn LeptonThreadPool,\n    max_processor_threads: usize,\n    work: VecDeque<Box<dyn FnOnce() + Send>>,\n) {\n    let work_threads = work.len().min(max_processor_threads);\n    let shared_queue = Arc::new(Mutex::new(work));\n\n    // spawn the worker threads to process all the items\n    // (there may be less processor threads than the number of threads in the image)\n    for _i in 0..work_threads {\n        let q = shared_queue.clone();\n\n        thread_pool.run(Box::new(move || {\n            loop {\n                // do this to make sure the lock gets\n                let w = q.lock().unwrap().pop_front();\n\n                if let Some(f) = w {\n                    f();\n                } else {\n                    break;\n                }\n            }\n        }));\n    }\n}\n\nimpl<RESULT> MultiplexReaderState<RESULT> {\n    /// process as much incoming data as we can and send it to the appropriate thread\n    pub fn process_buffer(&mut self, source: &mut PartialBuffer<'_>) -> Result<()> {\n        while source.continue_processing() {\n            match self.current_state {\n                State::StartBlock => {\n                    if let Some(a) = source.take_n::<1>(self.retention_bytes) {\n                        let thread_marker = a[0];\n\n                        let partition_id = thread_marker & 0xf;\n\n                        if usize::from(partition_id) >= self.sender_channels.len() {\n                            return err_exit_code(\n                                ExitCode::BadLeptonFile,\n                                format!(\"invalid partition_id {0}\", partition_id),\n                            );\n                        }\n\n                        if thread_marker < 16 {\n                            self.current_state = State::U16Length(partition_id);\n                        } else {\n                            let flags = (thread_marker >> 4) & 3;\n                            self.current_state = State::Block(partition_id, 1024 << (2 * flags));\n                        }\n                    } else {\n                        break;\n                    }\n                }\n                State::U16Length(thread_marker) => {\n                    if let Some(a) = source.take_n::<2>(self.retention_bytes) {\n                        let b0 = usize::from(a[0]);\n                        let b1 = usize::from(a[1]);\n\n                        self.current_state = State::Block(thread_marker, (b1 << 8) + b0 + 1);\n                    } else {\n                        break;\n                    }\n                }\n                State::Block(partition_id, data_length) => {\n                    if let Some(a) = source.take(data_length, self.retention_bytes) {\n                        // ignore if we get error sending because channel died since we will collect\n                        // the error later. We don't want to interrupt the other threads that are processing\n                        // so we only get the error from the thread that actually errored out.\n                        let tid = usize::from(partition_id);\n                        let _ = self.sender_channels[tid].send(Message::WriteBlock(tid, a));\n                        self.current_state = State::StartBlock;\n                    } else {\n                        break;\n                    }\n                }\n            }\n        }\n\n        Ok(())\n    }\n\n    /// retrieves the next available result from the threads. If complete is true, this function\n    /// will block until all threads are complete and return the first result or error it finds.\n    /// If complete is false, this function will return immediately if no results are available.\n    pub fn retrieve_result(&mut self, complete: bool) -> Result<Option<RESULT>> {\n        if let Some(value) =\n            Self::try_get_result(&mut self.receiver_channels, &mut self.merged_metrics)?\n        {\n            return Ok(Some(value));\n        }\n\n        if complete {\n            // if we are complete, send eof to all threads\n            for partition_id in 0..self.sender_channels.len() {\n                // send eof to all threads (ignore results since they might be dead already)\n                let _ = self.sender_channels[partition_id].send(Message::Eof(partition_id));\n            }\n            self.sender_channels.clear();\n\n            // if we are running single threaded, now do all the work since we've buffered up everything\n            // and broken the sender channels, so there's no danger of deadlock\n            if let Some(single_thread_work) = &mut self.single_thread_work {\n                while let Some(f) = single_thread_work.pop_front() {\n                    f();\n\n                    if let Some(value) =\n                        Self::try_get_result(&mut self.receiver_channels, &mut self.merged_metrics)?\n                    {\n                        return Ok(Some(value));\n                    }\n                }\n            }\n\n            // if we are complete, then walk through all the channels to get the first result by blocking\n            while let Some(r) = self.receiver_channels.get_mut(0) {\n                match r.recv() {\n                    Ok(v) => match v {\n                        MultiplexReadResult::Result(v) => return Ok(Some(v)),\n                        MultiplexReadResult::Error(e) => return Err(e),\n                        MultiplexReadResult::Complete(m) => {\n                            // finished, so remove it and try the next one\n                            self.merged_metrics.merge_from(m);\n                            self.receiver_channels.remove(0);\n                        }\n                    },\n                    Err(e) => {\n                        // channel is closed unexpectedly, clear out all channels and return error\n                        self.receiver_channels.clear();\n                        return Err(e.into());\n                    }\n                }\n            }\n        }\n        // nothing left to read\n        Ok(None)\n    }\n\n    /// tries to get a result from the receiver channels without blocking\n    fn try_get_result(\n        receiver_channels: &mut Vec<Receiver<MultiplexReadResult<RESULT>>>,\n        metrics: &mut Metrics,\n    ) -> Result<Option<RESULT>> {\n        // if we aren't complete, use non-blocking to try to get some results\n        // from the first thread\n        while let Some(r) = receiver_channels.get_mut(0) {\n            match r.try_recv() {\n                Ok(v) => match v {\n                    MultiplexReadResult::Result(v) => return Ok(Some(v)),\n                    MultiplexReadResult::Error(e) => return Err(e),\n                    MultiplexReadResult::Complete(m) => {\n                        // finished, so remove it and try the next one\n                        metrics.merge_from(m);\n                        receiver_channels.remove(0);\n                    }\n                },\n                Err(TryRecvError::Disconnected) => {\n                    // finished, so remove it and try the next one\n                    return Err(LeptonError::new(\n                        ExitCode::AssertionFailure,\n                        \"multiplexed reader channel disconnected unexpectedly\",\n                    ));\n                }\n                Err(TryRecvError::Empty) => {\n                    // no result yet, exit loop without result\n                    break;\n                }\n            }\n        }\n        Ok(None)\n    }\n\n    /// takes the merged metrics from all the threads\n    pub fn take_metrics(&mut self) -> Metrics {\n        std::mem::take(&mut self.merged_metrics)\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use std::time::Duration;\n\n    use byteorder::ReadBytesExt;\n\n    use super::*;\n    use crate::lepton_error::{ExitCode, LeptonError};\n    use crate::{DEFAULT_THREAD_POOL, SingleThreadPool};\n\n    /// simple end to end test that write the thread id and reads it back\n    #[test]\n    fn test_multiplex_end_to_end() {\n        let mut output = Vec::new();\n\n        let w = multiplex_write(\n            &mut output,\n            10,\n            10,\n            &DEFAULT_THREAD_POOL,\n            |writer, partition_id| -> Result<usize> {\n                for i in partition_id as u32..10000 {\n                    writer.write_u32::<byteorder::LittleEndian>(i)?;\n                }\n\n                Ok(partition_id)\n            },\n        )\n        .unwrap();\n\n        assert_eq!(w[..], [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]);\n\n        for max_processor_threads in 1..=10 {\n            test_read(&output, &w, max_processor_threads);\n        }\n    }\n\n    fn test_read(output: &[u8], w: &[usize], max_processor_threads: usize) {\n        let mut extra = Vec::new();\n        let single = SingleThreadPool::default();\n\n        let mut multiplex_state = multiplex_read(\n            10,\n            max_processor_threads,\n            if max_processor_threads == 1 {\n                // for a single thread we shouldn't spawn any threads\n                &single\n            } else {\n                &DEFAULT_THREAD_POOL\n            },\n            0,\n            |partition_id, reader, result_tx: &Sender<MultiplexReadResult<usize>>| {\n                for i in partition_id as u32..10000 {\n                    let read_partition_id = reader.read_u32::<byteorder::LittleEndian>()?;\n                    assert_eq!(read_partition_id, i);\n                }\n                result_tx.send(MultiplexReadResult::Result(partition_id))?;\n\n                let mut metrics = Metrics::default();\n                metrics.record_cpu_worker_time(Duration::new(1, 0));\n\n                result_tx.send(MultiplexReadResult::Complete(metrics))?;\n                Ok(())\n            },\n        );\n\n        // do worst case, we are just given byte at a time\n        let mut r = Vec::new();\n\n        for i in 0..output.len() {\n            let mut i = PartialBuffer::new(&output[i..=i], &mut extra);\n            multiplex_state.process_buffer(&mut i).unwrap();\n\n            if let Some(res) = multiplex_state.retrieve_result(false).unwrap() {\n                r.push(res);\n            }\n        }\n\n        while let Some(res) = multiplex_state.retrieve_result(true).unwrap() {\n            r.push(res);\n        }\n\n        let metrics = multiplex_state.take_metrics();\n        assert_eq!(metrics.get_cpu_time_worker_time(), Duration::new(10, 0));\n\n        assert_eq!(r[..], w[..]);\n    }\n\n    #[test]\n    fn test_multiplex_read_error() {\n        let mut multiplex_state = multiplex_read(\n            10,\n            10,\n            &DEFAULT_THREAD_POOL,\n            0,\n            |_, _, _: &Sender<MultiplexReadResult<()>>| -> Result<()> {\n                Err(LeptonError::new(ExitCode::FileNotFound, \"test error\"))?\n            },\n        );\n\n        let e: LeptonError = multiplex_state.retrieve_result(true).unwrap_err().into();\n        assert_eq!(e.exit_code(), ExitCode::FileNotFound);\n        assert!(e.message().starts_with(\"test error\"));\n    }\n\n    #[test]\n    fn test_multiplex_read_panic() {\n        let mut multiplex_state = multiplex_read(\n            10,\n            10,\n            &DEFAULT_THREAD_POOL,\n            0,\n            |_, _, _: &Sender<MultiplexReadResult<()>>| -> Result<()> {\n                panic!();\n            },\n        );\n\n        let e: LeptonError = multiplex_state.retrieve_result(true).unwrap_err().into();\n        assert_eq!(e.exit_code(), ExitCode::AssertionFailure);\n    }\n\n    // test catching errors in the multiplex_write function\n    #[test]\n    fn test_multiplex_write_error() {\n        let mut output = Vec::new();\n\n        let e: LeptonError = multiplex_write(\n            &mut output,\n            10,\n            10,\n            &DEFAULT_THREAD_POOL,\n            |_, partition_id| -> Result<usize> {\n                if partition_id == 3 {\n                    // have one partition fail\n                    Err(LeptonError::new(ExitCode::FileNotFound, \"test error\"))?\n                } else {\n                    Ok(0)\n                }\n            },\n        )\n        .unwrap_err()\n        .into();\n\n        assert_eq!(e.exit_code(), ExitCode::FileNotFound);\n        assert!(e.message().starts_with(\"test error\"));\n    }\n\n    // test catching errors in the multiplex_write function\n    #[test]\n    fn test_multiplex_write_panic() {\n        let mut output = Vec::new();\n\n        let e: LeptonError = multiplex_write(\n            &mut output,\n            10,\n            10,\n            &DEFAULT_THREAD_POOL,\n            |_, partition_id| -> Result<usize> {\n                if partition_id == 5 {\n                    panic!();\n                }\n                Ok(0)\n            },\n        )\n        .unwrap_err()\n        .into();\n\n        assert_eq!(e.exit_code(), ExitCode::AssertionFailure);\n    }\n}\n"
  },
  {
    "path": "lib/src/structs/neighbor_summary.rs",
    "content": "/*---------------------------------------------------------------------------------------------\n *  Copyright (c) Microsoft Corporation. All rights reserved.\n *  Licensed under the Apache License, Version 2.0. See LICENSE.txt in the project root for license information.\n *  This software incorporates material from third parties. See NOTICE.txt for details.\n *--------------------------------------------------------------------------------------------*/\n\nuse std::num::Wrapping;\n\nuse wide::{i16x8, i32x8};\n\n#[derive(Copy, Clone, PartialEq, Debug)]\npub struct NeighborSummary {\n    edge_pixels_h: i16x8,\n    edge_pixels_v: i16x8,\n\n    edge_coefs_h: i32x8,\n    edge_coefs_v: i32x8,\n\n    num_non_zeros: u8,\n}\n\npub static NEIGHBOR_DATA_EMPTY: NeighborSummary = NeighborSummary {\n    edge_pixels_h: i16x8::ZERO,\n    edge_pixels_v: i16x8::ZERO,\n    edge_coefs_h: i32x8::ZERO,\n    edge_coefs_v: i32x8::ZERO,\n    num_non_zeros: 0,\n};\n\nimpl Default for NeighborSummary {\n    fn default() -> Self {\n        NEIGHBOR_DATA_EMPTY\n    }\n}\n\nimpl NeighborSummary {\n    #[inline(always)]\n    pub fn new(\n        edge_pixels_h: i16x8,\n        edge_pixels_v: i16x8,\n        dc_deq: i32,\n        num_non_zeros_7x7: u8,\n        horiz_pred: i32x8,\n        vert_pred: i32x8,\n    ) -> Self {\n        NeighborSummary {\n            edge_pixels_h: edge_pixels_h + (dc_deq as i16),\n            edge_pixels_v: edge_pixels_v + (dc_deq as i16),\n            edge_coefs_h: horiz_pred,\n            edge_coefs_v: vert_pred,\n            num_non_zeros: num_non_zeros_7x7,\n        }\n    }\n\n    pub fn get_num_non_zeros(&self) -> u8 {\n        self.num_non_zeros\n    }\n\n    pub fn get_vertical_pix(&self) -> i16x8 {\n        return self.edge_pixels_v;\n    }\n\n    pub fn get_horizontal_pix(&self) -> i16x8 {\n        return self.edge_pixels_h;\n    }\n\n    pub fn get_vertical_coef(&self) -> i32x8 {\n        return self.edge_coefs_v;\n    }\n\n    pub fn get_horizontal_coef(&self) -> i32x8 {\n        return self.edge_coefs_h;\n    }\n\n    // used for debugging\n    #[allow(dead_code)]\n    pub fn checksum(&self) -> u32 {\n        let mut sum: Wrapping<u32> =\n            Wrapping(i32x8::from_i16x8(self.edge_pixels_h).reduce_add() as u32);\n        sum += Wrapping(i32x8::from_i16x8(self.edge_pixels_v).reduce_add() as u32);\n        sum += Wrapping(self.num_non_zeros as u32);\n        return sum.0;\n    }\n}\n"
  },
  {
    "path": "lib/src/structs/partial_buffer.rs",
    "content": "use std::cmp::min;\n\n/// This struct is used to the fact that we have to take buffers\n/// as they arrive, and we might not have all the data we need.\n///\n/// This used is via the take function, which attempts to grab\n/// the amount of data specified, and if it isn't available, stores\n/// the partial data in the extra buffer, and returns None.\n///\n/// Next time around, the extra data will be prepended to the next\n/// buffer, so eventually the amount of data requested will become\n/// available.\n///\n/// The concept of retention_bytes is used to handle the case where we need\n/// to leave a certain amount of data in the buffer, perticularly\n/// where Lepton files have the 32bit file size appended.\n///\n/// We don't want this to get parsed out, so we ensure that there are\n/// always at least 4 bytes in the buffer.\npub struct PartialBuffer<'a> {\n    slice: &'a [u8],\n    extra_buffer: &'a mut Vec<u8>,\n    continue_processing: bool,\n}\n\nimpl<'a> PartialBuffer<'a> {\n    /// Instantiates a new buffer with a slice and a place to store extra data\n    /// between calls.\n    ///\n    /// Extra data is used both to remember extra data from the previous buffer\n    /// and is updated with any data that is left over after a take call.\n    pub fn new(slice: &'a [u8], extra_buffer: &'a mut Vec<u8>) -> PartialBuffer<'a> {\n        PartialBuffer {\n            slice,\n            extra_buffer,\n            continue_processing: true,\n        }\n    }\n\n    /// returns true if we haven't yet run out of data (ie take returned empty)\n    pub fn continue_processing(&self) -> bool {\n        self.continue_processing\n    }\n\n    /// Attempts to get \"size\" bytes of data from the buffer. If that much\n    /// is available (including the extra buffer from the previous call), it is\n    /// returned as a vector exactly that size, otherwise the data is appended\n    /// to the extra buffer and None is returned.\n    ///\n    /// retention_bytes (see comment at top of file) indicates that we should never\n    /// consume the last x bytes of the buffer. This is useful because of the particular\n    /// way that Lepton files are encoded, the file size is appended without any sort\n    /// of header or marker, so the only way to know we are at the end is if there\n    /// are only 4 bytes left.\n    pub fn take(&mut self, size: usize, retention_bytes: usize) -> Option<Vec<u8>> {\n        if self.extra_buffer.len() + self.slice.len() < size + retention_bytes {\n            self.extra_buffer.extend_from_slice(self.slice);\n            self.slice = &[];\n            self.continue_processing = false;\n            return None;\n        }\n\n        let mut retval = Vec::with_capacity(size);\n        let amount_from_extra = min(self.extra_buffer.len(), size);\n        if amount_from_extra > 0 {\n            retval.extend_from_slice(&self.extra_buffer[0..amount_from_extra]);\n            self.extra_buffer.drain(0..amount_from_extra);\n        }\n\n        let amount_from_slice = size - amount_from_extra;\n        if amount_from_slice > 0 {\n            retval.extend_from_slice(&self.slice[0..amount_from_slice]);\n            self.slice = &self.slice[amount_from_slice..];\n        }\n\n        debug_assert!(retval.len() == size);\n        return Some(retval);\n    }\n\n    /// Same as take, except returns a fixed size array instead of a vector.\n    ///\n    /// Useful when we are expecting a small fixed number of bytes like a header\n    /// or signature.\n    pub fn take_n<const N: usize>(&mut self, retention_bytes: usize) -> Option<[u8; N]> {\n        if self.extra_buffer.len() + self.slice.len() < N + retention_bytes {\n            self.extra_buffer.extend_from_slice(self.slice);\n            self.slice = &[];\n            self.continue_processing = false;\n            return None;\n        }\n\n        let mut retval = [0; N];\n        let amount_from_extra = min(self.extra_buffer.len(), N);\n        if amount_from_extra > 0 {\n            retval[0..amount_from_extra].copy_from_slice(&self.extra_buffer[0..amount_from_extra]);\n            self.extra_buffer.drain(0..amount_from_extra);\n        }\n\n        let amount_from_slice = N - amount_from_extra;\n        if amount_from_slice > 0 {\n            retval[amount_from_extra..N].copy_from_slice(&self.slice[0..amount_from_slice]);\n            self.slice = &self.slice[amount_from_slice..];\n        }\n\n        Some(retval)\n    }\n}\n\n#[test]\nfn test_taking_simple() {\n    let mut extra = Vec::new();\n    let mut pb = PartialBuffer::new(&[1, 2, 3, 4], &mut extra);\n\n    let taken = pb.take(4, 0).unwrap();\n    assert_eq!(taken, vec![1, 2, 3, 4]);\n    assert_eq!(&extra[..], []);\n}\n\n#[test]\nfn test_taking_simple_n() {\n    let mut extra = Vec::new();\n    let mut pb = PartialBuffer::new(&[1, 2, 3, 4], &mut extra);\n\n    let taken = pb.take_n::<4>(0).unwrap();\n    assert_eq!(taken, [1, 2, 3, 4]);\n    assert_eq!(&extra[..], []);\n}\n\n#[test]\nfn test_taking_extra() {\n    let mut extra = Vec::new();\n    let mut pb = PartialBuffer::new(&[1, 2, 3, 4], &mut extra);\n\n    // try to take 5 characters, but there are only 4, so it should return None and\n    // leave the data read in extra\n    assert_eq!(pb.take(5, 0), None);\n    assert_eq!(&extra, &vec![1, 2, 3, 4]);\n\n    // now we should be able to take the 4 characters\n    let mut pb = PartialBuffer::new(&[5, 6, 7, 8], &mut extra);\n\n    assert_eq!(pb.take(5, 0), Some(vec![1, 2, 3, 4, 5]));\n\n    // try to take another 5, but there aren't\n    assert_eq!(pb.take(5, 0), None);\n\n    // the 3 characters we couldn't get should be in extra\n    assert!(!pb.continue_processing());\n    assert_eq!(&extra, &vec![6, 7, 8]);\n}\n\n#[test]\nfn test_taking_extra_n() {\n    let mut extra = Vec::new();\n    let mut pb = PartialBuffer::new(&[1, 2, 3, 4], &mut extra);\n\n    // try to take 5 characters, but there are only 4, so it should return None and\n    // leave the data read in extra\n    assert_eq!(pb.take_n::<5>(0), None);\n    assert_eq!(&extra, &vec![1, 2, 3, 4]);\n\n    // now we should be able to take the 4 characters\n    let mut pb = PartialBuffer::new(&[5, 6, 7, 8], &mut extra);\n\n    assert_eq!(pb.take_n::<5>(0), Some([1, 2, 3, 4, 5]));\n\n    // try to take another 5, but there aren't\n    assert_eq!(pb.take_n::<5>(0), None);\n\n    // the 3 characters we couldn't get should be in extra\n    assert!(!pb.continue_processing());\n    assert_eq!(&extra, &vec![6, 7, 8]);\n}\n\n#[test]\nfn test_taking_reserve() {\n    let mut extra = Vec::new();\n    let mut pb = PartialBuffer::new(&[1, 2, 3, 4, 5], &mut extra);\n\n    // taking 5 should fail because we wanted a reserve\n    assert_eq!(pb.take(5, 1), None);\n\n    let mut pb = PartialBuffer::new(&[], &mut extra);\n    assert_eq!(pb.take(5, 0), Some(vec![1, 2, 3, 4, 5]));\n}\n"
  },
  {
    "path": "lib/src/structs/probability_tables.rs",
    "content": "/*---------------------------------------------------------------------------------------------\n *  Copyright (c) Microsoft Corporation. All rights reserved.\n *  Licensed under the Apache License, Version 2.0. See LICENSE.txt in the project root for license information.\n *  This software incorporates material from third parties. See NOTICE.txt for details.\n *--------------------------------------------------------------------------------------------*/\n\nuse bytemuck::cast;\nuse wide::{i16x8, i32x8, u16x8};\n\nuse crate::consts::*;\nuse crate::enabled_features;\nuse crate::jpeg::block_based_image::AlignedBlock;\nuse crate::lepton_error::err_exit_code;\nuse crate::structs::block_context::NeighborData;\nuse crate::structs::idct::*;\nuse crate::structs::model::*;\nuse crate::structs::quantization_tables::*;\nuse crate::{ExitCode, Result};\n\npub struct ProbabilityTables {\n    left_present: bool,\n    above_present: bool,\n    all_present: bool,\n}\n\npub static NO_NEIGHBORS: ProbabilityTables = ProbabilityTables::new(false, false);\npub static TOP_ONLY: ProbabilityTables = ProbabilityTables::new(false, true);\npub static LEFT_ONLY: ProbabilityTables = ProbabilityTables::new(true, false);\npub static ALL: ProbabilityTables = ProbabilityTables::new(true, true);\n\npub struct PredictDCResult {\n    pub predicted_dc: i32,\n    pub uncertainty: i16,\n    pub uncertainty2: i16,\n    pub next_edge_pixels_h: i16x8,\n    pub next_edge_pixels_v: i16x8,\n}\n\nimpl ProbabilityTables {\n    pub const fn new(in_left_present: bool, in_above_present: bool) -> ProbabilityTables {\n        return ProbabilityTables {\n            left_present: in_left_present,\n            above_present: in_above_present,\n            all_present: in_left_present && in_above_present,\n        };\n    }\n\n    pub fn is_all_present(&self) -> bool {\n        self.all_present\n    }\n\n    pub fn is_left_present(&self) -> bool {\n        self.left_present\n    }\n    pub fn is_above_present(&self) -> bool {\n        self.above_present\n    }\n\n    pub fn adv_predict_or_unpredict_dc(\n        saved_dc: i16,\n        recover_original: bool,\n        predicted_val: i32,\n    ) -> i32 {\n        let max_value = 1 << (MAX_EXPONENT - 1);\n        let min_value = -max_value;\n        let adjustment_factor = (2 * max_value) + 1;\n        let mut retval = predicted_val;\n        retval = saved_dc as i32 + if recover_original { retval } else { -retval };\n\n        if retval < min_value {\n            retval += adjustment_factor;\n        }\n\n        if retval > max_value {\n            retval -= adjustment_factor;\n        }\n\n        return retval;\n    }\n\n    pub fn get_color_index(component: usize) -> usize {\n        return if component == 0 { 0 } else { 1 };\n    }\n\n    #[inline(always)]\n    pub fn num_non_zeros_to_bin_7x7(num_non_zeros: usize) -> usize {\n        return usize::from(NON_ZERO_TO_BIN_7X7[num_non_zeros]);\n    }\n\n    pub fn calc_num_non_zeros_7x7_context_bin<const ALL_PRESENT: bool>(\n        &self,\n        neighbor_data: &NeighborData,\n    ) -> u8 {\n        let mut num_non_zeros_above = 0;\n        let mut num_non_zeros_left = 0;\n        if ALL_PRESENT || self.above_present {\n            num_non_zeros_above = neighbor_data.neighbor_context_above.get_num_non_zeros();\n        }\n\n        if ALL_PRESENT || self.left_present {\n            num_non_zeros_left = neighbor_data.neighbor_context_left.get_num_non_zeros();\n        }\n\n        let num_non_zeros_context;\n        if (!ALL_PRESENT) && self.above_present && !self.left_present {\n            num_non_zeros_context = (num_non_zeros_above + 1) / 2;\n        } else if (!ALL_PRESENT) && self.left_present && !self.above_present {\n            num_non_zeros_context = (num_non_zeros_left + 1) / 2;\n        } else if ALL_PRESENT || (self.left_present && self.above_present) {\n            num_non_zeros_context = (num_non_zeros_above + num_non_zeros_left + 2) / 4;\n        } else {\n            num_non_zeros_context = 0;\n        }\n\n        return NON_ZERO_TO_BIN[usize::from(num_non_zeros_context)];\n    }\n\n    // calculates the average of the prior values from their corresponding value in the left, above and above/left block\n    // the C++ version does one coefficient at a time, but if we do it all at the same time, the compiler vectorizes everything\n    #[inline(never)]\n    pub fn calc_coefficient_context_7x7_aavg_block<const ALL_PRESENT: bool>(\n        &self,\n        left: &AlignedBlock,\n        above: &AlignedBlock,\n        above_left: &AlignedBlock,\n    ) -> [u16; 64] {\n        let mut best_prior = [u16x8::ZERO; 8];\n\n        if ALL_PRESENT {\n            for i in 1..8 {\n                // approximate average of 3 without a divide with double the weight for left/top vs diagonal\n                //\n                // No need to go to 32 bits since max exponent is 11, ie 2047, so\n                // (2047 + 2047) * 13 + 2047 * 6 = 65504 which still fits in 16 bits.\n                // In addition, if we ever returned anything higher that 2047, it would\n                // assert in the array lookup in the model.\n                best_prior[i] =\n                    ((left.as_i16x8(i).unsigned_abs() + above.as_i16x8(i).unsigned_abs()) * 13\n                        + above_left.as_i16x8(i).unsigned_abs() * 6)\n                        >> 5;\n            }\n        } else {\n            // handle edge case :) where we are on the top or left edge\n\n            if self.left_present {\n                for i in 1..8 {\n                    best_prior[i] = left.as_i16x8(i).unsigned_abs();\n                }\n            } else if self.above_present {\n                for i in 1..8 {\n                    best_prior[i] = above.as_i16x8(i).unsigned_abs();\n                }\n            }\n        }\n\n        cast(best_prior)\n    }\n\n    // Predictor calculations in `compute_lak` are made using partial IDCT along only one dimension\n    // on neighbor and current blocks row/column and finding predictor that makes current block edge\n    // \"almost-pixel\" equal to that of neighbor block (see https://arxiv.org/abs/1704.06192, section A.2.2).\n    // These 1D IDCT can be conveniently done separately for current block and neighbor one\n    // storing components of predictor formula - dot products of dequantized DCT coefficients columns/rows\n    // with `ICOS_BASED_8192_SCALED/_PM` (equivalent to former dot products of quantized DCT coefficients\n    // with `icos_idct_edge_8192_dequantized_x/y`) - inside `NeighborSummary` of corresponding block.\n    // Instead of non-continuous memory accesses to blocks we can use dequantized raster DCT coefficients\n    // needed for DC prediction and apply horizontal SIMD instructions for direction along the raster order.\n\n    // Produce current block predictors for edge DCT coefficients\n    #[inline(always)]\n    pub fn predict_current_edges(\n        neighbors_data: &NeighborData,\n        raster: &[i32x8; 8],\n    ) -> (i32x8, i32x8) {\n        // don't bother about DC in encoding - 0th component of ICOS_BASED_8192_SCALED is 0\n        let mult: i32x8 = i32x8::from(ICOS_BASED_8192_SCALED);\n\n        // load initial predictors data from neighborhood blocks\n        let mut horiz_pred: [i32; 8] = neighbors_data\n            .neighbor_context_above\n            .get_horizontal_coef()\n            .to_array();\n        let mut vert_pred: i32x8 = neighbors_data.neighbor_context_left.get_vertical_coef();\n\n        for col in 1..8 {\n            // some extreme coefficents can cause overflows, but since this is just predictors, no need to panic\n            vert_pred -= raster[col] * ICOS_BASED_8192_SCALED[col];\n            horiz_pred[col] = horiz_pred[col].wrapping_sub((raster[col] * mult).reduce_add());\n        }\n\n        (i32x8::from(horiz_pred), vert_pred)\n    }\n\n    // Produce first part of edge DCT coefficients predictions for neighborhood blocks\n    #[inline(always)]\n    pub fn predict_next_edges(raster: &[i32x8; 8]) -> (i32x8, i32x8) {\n        let mult = i32x8::from(ICOS_BASED_8192_SCALED_PM);\n\n        let mut horiz_pred: [i32; 8] = [0; 8];\n        let mut vert_pred = ICOS_BASED_8192_SCALED_PM[0] * raster[0];\n        for col in 1..8 {\n            // produce predictions for edge DCT coefficientss for the block below\n            horiz_pred[col] = (mult * raster[col]).reduce_add();\n            // and for the block to the right\n            vert_pred += ICOS_BASED_8192_SCALED_PM[col] * raster[col];\n        }\n\n        (i32x8::from(horiz_pred), vert_pred)\n    }\n\n    #[inline(always)]\n    pub fn calc_coefficient_context8_lak<const ALL_PRESENT: bool, const HORIZONTAL: bool>(\n        &self,\n        qt: &QuantizationTables,\n        coefficient_tr: usize,\n        pred: &[i32; 8],\n    ) -> Result<i32> {\n        if !ALL_PRESENT\n            && ((HORIZONTAL && !self.above_present) || (!HORIZONTAL && !self.left_present))\n        {\n            return Ok(0);\n        }\n\n        let best_prior: i32 = pred[if HORIZONTAL {\n            coefficient_tr >> 3\n        } else {\n            coefficient_tr\n        }];\n\n        let div = (qt.get_quantization_table_transposed()[coefficient_tr] as i32) << 13;\n        if let Some(x) = best_prior.checked_div(div) {\n            return Ok(x);\n        } else {\n            return err_exit_code(\n                ExitCode::UnsupportedJpegWithZeroIdct0,\n                \"integer overflow in coefficient context calculation\",\n            );\n        }\n    }\n\n    pub fn adv_predict_dc_pix<const ALL_PRESENT: bool>(\n        &self,\n        raster_cols: &[i32x8; 8],\n        q0: i32,\n        neighbor_data: &NeighborData,\n        enabled_features: &enabled_features::EnabledFeatures,\n    ) -> PredictDCResult {\n        // here DC in raster_cols should be 0\n        let pixels_sans_dc = run_idct(raster_cols);\n\n        // helper functions to avoid code duplication that calculate prediction values\n        #[inline]\n        fn calc_pred(a1: i16x8, a2: i16x8, is_16_bit: bool) -> i16x8 {\n            if is_16_bit {\n                let pixel_delta = a1 - a2;\n                let half_delta = (pixel_delta - (pixel_delta >> 15)) >> 1; /* divide pixel_delta by 2 rounding towards 0 */\n\n                a1 + half_delta\n            } else {\n                let a1 = i32x8::from_i16x8(a1);\n                let a2 = i32x8::from_i16x8(a2);\n                let pixel_delta = a1 - a2;\n                let half_delta = (pixel_delta - (pixel_delta >> 31)) >> 1; /* divide pixel_delta by 2 rounding towards 0 */\n                let result = a1 + half_delta;\n\n                i16x8::from_i32x8_truncate(result)\n            }\n        }\n\n        let a1 = pixels_sans_dc.as_i16x8(0);\n        let a2 = pixels_sans_dc.as_i16x8(1);\n        let v_pred = calc_pred(a1, a2, enabled_features.use_16bit_adv_predict);\n\n        let a1 = pixels_sans_dc.from_stride(0, 8);\n        let a2 = pixels_sans_dc.from_stride(1, 8);\n        let h_pred = calc_pred(a1, a2, enabled_features.use_16bit_adv_predict);\n\n        let a1 = pixels_sans_dc.as_i16x8(7);\n        let a2 = pixels_sans_dc.as_i16x8(6);\n        let next_edge_pixels_v = calc_pred(a1, a2, enabled_features.use_16bit_dc_estimate);\n\n        let a1 = pixels_sans_dc.from_stride(7, 8);\n        let a2 = pixels_sans_dc.from_stride(6, 8);\n\n        let next_edge_pixels_h = calc_pred(a1, a2, enabled_features.use_16bit_dc_estimate);\n\n        let min_dc;\n        let max_dc;\n        let mut avg_horizontal: i32;\n        let mut avg_vertical: i32;\n\n        if ALL_PRESENT {\n            // most common case where we have both left and above\n            let horiz = neighbor_data.neighbor_context_left.get_horizontal_pix() - h_pred;\n            let vert = neighbor_data.neighbor_context_above.get_vertical_pix() - v_pred;\n\n            min_dc = horiz.min(vert).reduce_min();\n            max_dc = horiz.max(vert).reduce_max();\n\n            avg_horizontal = i32x8::from_i16x8(horiz).reduce_add();\n            avg_vertical = i32x8::from_i16x8(vert).reduce_add();\n        } else if self.left_present {\n            let horiz = neighbor_data.neighbor_context_left.get_horizontal_pix() - h_pred;\n            min_dc = horiz.reduce_min();\n            max_dc = horiz.reduce_max();\n\n            avg_horizontal = i32x8::from_i16x8(horiz).reduce_add();\n            avg_vertical = avg_horizontal;\n        } else if self.above_present {\n            let vert = neighbor_data.neighbor_context_above.get_vertical_pix() - v_pred;\n            min_dc = vert.reduce_min();\n            max_dc = vert.reduce_max();\n\n            avg_vertical = i32x8::from_i16x8(vert).reduce_add();\n            avg_horizontal = avg_vertical;\n        } else {\n            return PredictDCResult {\n                predicted_dc: 0,\n                uncertainty: 0,\n                uncertainty2: 0,\n                next_edge_pixels_h,\n                next_edge_pixels_v,\n            };\n        }\n\n        let avgmed: i32 = (avg_vertical + avg_horizontal) >> 1;\n        let uncertainty_val = ((i32::from(max_dc) - i32::from(min_dc)) >> 3) as i16;\n        avg_horizontal -= avgmed;\n        avg_vertical -= avgmed;\n\n        let mut far_afield_value = avg_vertical;\n        if avg_horizontal.abs() < avg_vertical.abs() {\n            far_afield_value = avg_horizontal;\n        }\n\n        let uncertainty2_val = (far_afield_value >> 3) as i16;\n\n        return PredictDCResult {\n            predicted_dc: (avgmed / q0 + 4) >> 3,\n            uncertainty: uncertainty_val,\n            uncertainty2: uncertainty2_val,\n            next_edge_pixels_h,\n            next_edge_pixels_v,\n        };\n    }\n}\n"
  },
  {
    "path": "lib/src/structs/quantization_tables.rs",
    "content": "/*---------------------------------------------------------------------------------------------\n *  Copyright (c) Microsoft Corporation. All rights reserved.\n *  Licensed under the Apache License, Version 2.0. See LICENSE.txt in the project root for license information.\n *  This software incorporates material from third parties. See NOTICE.txt for details.\n *--------------------------------------------------------------------------------------------*/\n\nuse crate::Result;\nuse crate::consts::*;\nuse crate::helpers::*;\nuse crate::jpeg::jpeg_header::JpegHeader;\n\npub struct QuantizationTables {\n    quantization_table: [u16; 64],\n    quantization_table_transposed: [u16; 64],\n    // Values for discrimination between \"regular\" and \"noise\" part of\n    // edge AC coefficients, used in `read/write_edge_coefficient`.\n    // Calculated using approximate maximal magnitudes\n    // of these coefficients `FREQ_MAX`\n    min_noise_threshold: [u8; 14],\n}\n\nimpl QuantizationTables {\n    pub fn new(jpeg_header: &JpegHeader, component: usize) -> Self {\n        Self::new_from_table(\n            &jpeg_header.q_tables[usize::from(jpeg_header.cmp_info[component].q_table_index)],\n        )\n    }\n\n    pub fn new_from_table(quantization_table: &[u16; 64]) -> Self {\n        let mut retval = QuantizationTables {\n            quantization_table: [0; 64],\n            quantization_table_transposed: [0; 64],\n            min_noise_threshold: [0; 14],\n        };\n\n        for pixel_row in 0..8 {\n            for pixel_column in 0..8 {\n                let coord = (pixel_row * 8) + pixel_column;\n                let coord_tr = (pixel_column * 8) + pixel_row;\n                let q = quantization_table[RASTER_TO_ZIGZAG[coord] as usize];\n\n                retval.quantization_table[coord] = q;\n                retval.quantization_table_transposed[coord_tr] = q;\n            }\n        }\n\n        for i in 0..14 {\n            let coord = if i < 7 { i + 1 } else { (i - 6) * 8 };\n            if retval.quantization_table[coord] < 9 {\n                let mut freq_max = FREQ_MAX[i] + retval.quantization_table[coord] - 1;\n                if retval.quantization_table[coord] != 0 {\n                    freq_max /= retval.quantization_table[coord];\n                }\n\n                let max_len = u16_bit_length(freq_max);\n                if max_len > RESIDUAL_NOISE_FLOOR as u8 {\n                    retval.min_noise_threshold[i] = max_len - RESIDUAL_NOISE_FLOOR as u8;\n                }\n            }\n        }\n\n        retval\n    }\n\n    /// constructs the quantization table based on the jpeg header\n    pub fn construct_quantization_tables(\n        jpeg_header: &JpegHeader,\n    ) -> Result<Vec<QuantizationTables>> {\n        let mut quantization_tables = Vec::new();\n        for i in 0..jpeg_header.cmpc {\n            let qtables = QuantizationTables::new(jpeg_header, i);\n            quantization_tables.push(qtables);\n        }\n        Ok(quantization_tables)\n    }\n\n    pub fn get_quantization_table(&self) -> &[u16; 64] {\n        &self.quantization_table\n    }\n\n    pub fn get_quantization_table_transposed(&self) -> &[u16; 64] {\n        &self.quantization_table_transposed\n    }\n\n    pub fn get_min_noise_threshold(&self, coef: usize) -> u8 {\n        self.min_noise_threshold[coef]\n    }\n}\n"
  },
  {
    "path": "lib/src/structs/simple_hash.rs",
    "content": "/*---------------------------------------------------------------------------------------------\n *  Copyright (c) Microsoft Corporation. All rights reserved.\n *  Licensed under the Apache License, Version 2.0. See LICENSE.txt in the project root for license information.\n *  This software incorporates material from third parties. See NOTICE.txt for details.\n *--------------------------------------------------------------------------------------------*/\n\n#![allow(dead_code)]\n\nuse std::num::Wrapping;\n\n/// used for debugging when there are divergences between encoder and decoder\npub struct SimpleHash {\n    hash: u64,\n}\n\npub trait SimpleHashProvider {\n    fn get_u64(&self) -> u64;\n}\n\nimpl SimpleHashProvider for i32 {\n    fn get_u64(&self) -> u64 {\n        return *self as u64;\n    }\n}\n\nimpl SimpleHashProvider for u32 {\n    fn get_u64(&self) -> u64 {\n        return *self as u64;\n    }\n}\n\nimpl SimpleHashProvider for u64 {\n    fn get_u64(&self) -> u64 {\n        return *self;\n    }\n}\n\nimpl SimpleHash {\n    pub fn new() -> Self {\n        return SimpleHash { hash: 0 };\n    }\n\n    pub fn hash<T: SimpleHashProvider>(&mut self, v: T) {\n        self.hash = (Wrapping(self.hash) * Wrapping(13u64) + Wrapping(v.get_u64())).0;\n    }\n\n    pub fn get(&self) -> u32 {\n        return self.hash as u32;\n    }\n}\n"
  },
  {
    "path": "lib/src/structs/simple_threadpool.rs",
    "content": "/// A simple thread pool implementation that can be used to evaluate closures on separate threads.\n///\n/// The pool will keep a number of threads equal to the number of CPUs available on the system, and\n/// will reuse threads that are idle.\n///\n/// If more tasks are submitted than there are threads, the pool will spawn new threads to handle\n/// the extra tasks.\n///\n/// Why write yet another threadpool? There wasn't one that was that supported dynamically growing\n/// the threadpool (rayon and tokio are all fixed), which is important since otherwise there is\n/// unpredicable latency when the number of tasks submitted is greater than the number of threads.\n///\n/// No unsafe code is used.\nuse std::{\n    sync::{\n        Arc, LazyLock, Mutex,\n        mpsc::{Sender, channel},\n    },\n    thread::{self, spawn},\n};\n\n/// A trait that defines the interface for a Lepton thread pool.\n/// It has a simple fire-and-forget interface, which is sufficient for the current use cases,\n/// but also requires the thread pool to be static, since we don't require the thread\n/// to return within a specific lifetime.\npub trait LeptonThreadPool {\n    /// Returns the maximum parallelism supported by the thread pool.\n    fn max_parallelism(&self) -> usize;\n    /// Runs a closure on a thread from the thread pool. The thread\n    /// thread lifetime is not specified, so it can must be static.\n    fn run(&self, f: Box<dyn FnOnce() + Send + 'static>);\n}\n\n/// Holds either a reference to a LeptonThreadPool or an owned Box<dyn LeptonThreadPool>.\n///\n/// This is useful for APIs that want to accept either a reference to a static or global thread pool\n/// or an owned thread pool.\npub enum ThreadPoolHolder<'a> {\n    /// Reference to a LeptonThreadPool\n    Dyn(&'a dyn LeptonThreadPool),\n    /// Owned Box<dyn LeptonThreadPool>\n    Owned(Box<dyn LeptonThreadPool>),\n}\n\nimpl LeptonThreadPool for ThreadPoolHolder<'_> {\n    fn max_parallelism(&self) -> usize {\n        match self {\n            ThreadPoolHolder::Dyn(p) => p.max_parallelism(),\n            ThreadPoolHolder::Owned(p) => p.max_parallelism(),\n        }\n    }\n    fn run(&self, f: Box<dyn FnOnce() + Send + 'static>) {\n        match self {\n            ThreadPoolHolder::Dyn(p) => p.run(f),\n            ThreadPoolHolder::Owned(p) => p.run(f),\n        }\n    }\n}\n\n/// Priority levels for threads in the thread pool.\n#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]\npub enum LeptonThreadPriority {\n    /// Low priority thread\n    Low,\n    /// Normal priority thread, we don't touch the priority of these threads.\n    #[default]\n    Normal,\n    /// High priority thread\n    High,\n}\n\n/// A simple thread pool that spawns threads on demand and reuses them for executing closures.\n/// There is no limit on the number of threads, but the number of idle threads is limited to the number of CPUs available.\n#[derive(Default)]\npub struct SimpleThreadPool {\n    priority: LeptonThreadPriority,\n    idle_threads: LazyLock<Arc<Mutex<Vec<Sender<Box<dyn FnOnce() + Send + 'static>>>>>>,\n}\n\nimpl SimpleThreadPool {\n    /// Creates a new thread pool with the specified priority.\n    pub const fn new(priority: LeptonThreadPriority) -> Self {\n        SimpleThreadPool {\n            priority,\n            idle_threads: LazyLock::new(|| Arc::new(Mutex::new(Vec::new()))),\n        }\n    }\n\n    /// Returns the number of idle threads in the thread pool.\n    #[allow(dead_code)]\n    pub fn get_idle_threads(&self) -> usize {\n        self.idle_threads.lock().unwrap().len()\n    }\n\n    /// Executes a closure on a thread from the thread pool. Does not block or return any result.\n    fn execute<F>(&self, f: F)\n    where\n        F: FnOnce() + Send + 'static,\n    {\n        if let Some(sender) = self.idle_threads.lock().unwrap().pop() {\n            sender.send(Box::new(f)).unwrap();\n        } else {\n            // channel for receiving future work on this thread\n            let (tx_schedule, rx_schedule) = channel();\n\n            let priority = self.priority;\n            let idle_threads = self.idle_threads.clone();\n\n            spawn(move || {\n                #[cfg(any(target_os = \"windows\", target_os = \"linux\"))]\n                match priority {\n                    LeptonThreadPriority::Low => thread_priority::set_current_thread_priority(\n                        thread_priority::ThreadPriority::Min,\n                    )\n                    .unwrap(),\n                    LeptonThreadPriority::Normal => {}\n                    LeptonThreadPriority::High => thread_priority::set_current_thread_priority(\n                        thread_priority::ThreadPriority::Max,\n                    )\n                    .unwrap(),\n                }\n\n                f();\n\n                loop {\n                    if let Ok(mut i) = idle_threads.lock() {\n                        // stick back into list of idle threads if there aren't more than\n                        // the number of cpus already there.\n                        if i.len() > *NUM_CPUS {\n                            // just exits the thread\n                            break;\n                        }\n                        i.push(tx_schedule.clone());\n                    } else {\n                        break;\n                    }\n\n                    if let Ok(f) = rx_schedule.recv() {\n                        f();\n                    } else {\n                        // channel broken, exit thread\n                        break;\n                    }\n                }\n            });\n        }\n    }\n}\n\n/// A default instance of the `SimpleThreadPool` that can be used for encoding and decoding operations.\npub static DEFAULT_THREAD_POOL: SimpleThreadPool =\n    SimpleThreadPool::new(LeptonThreadPriority::Normal);\n\nimpl LeptonThreadPool for SimpleThreadPool {\n    fn max_parallelism(&self) -> usize {\n        *NUM_CPUS\n    }\n    fn run(&self, f: Box<dyn FnOnce() + Send + 'static>) {\n        self.execute(f);\n    }\n}\n\nstatic NUM_CPUS: LazyLock<usize> = LazyLock::new(|| thread::available_parallelism().unwrap().get());\n\n#[test]\nfn test_threadpool() {\n    use std::sync::Arc;\n    use std::sync::atomic::{AtomicU32, Ordering};\n\n    let a: Arc<AtomicU32> = Arc::new(AtomicU32::new(0));\n\n    for _i in 0usize..100 {\n        let aref = a.clone();\n        DEFAULT_THREAD_POOL.execute(move || {\n            aref.fetch_add(1, Ordering::AcqRel);\n        });\n    }\n\n    while a.load(std::sync::atomic::Ordering::Acquire) < 100 {\n        thread::yield_now();\n    }\n\n    println!(\"Idle threads: {}\", DEFAULT_THREAD_POOL.get_idle_threads());\n}\n\n/// single thread pool that creates that doesn't create any threads\n#[derive(Default)]\npub struct SingleThreadPool {}\n\nimpl LeptonThreadPool for SingleThreadPool {\n    fn max_parallelism(&self) -> usize {\n        1\n    }\n    fn run(&self, _f: Box<dyn FnOnce() + Send + 'static>) {\n        panic!(\"SingleThreadPool does not support run; execute directly instead\");\n    }\n}\n"
  },
  {
    "path": "lib/src/structs/thread_handoff.rs",
    "content": "/*---------------------------------------------------------------------------------------------\n *  Copyright (c) Microsoft Corporation. All rights reserved.\n *  Licensed under the Apache License, Version 2.0. See LICENSE.txt in the project root for license information.\n *  This software incorporates material from third parties. See NOTICE.txt for details.\n *--------------------------------------------------------------------------------------------*/\n\nuse std::io::{Read, Result, Write};\n\nuse byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt};\n\nuse crate::consts::COLOR_CHANNEL_NUM_BLOCK_TYPES;\n\n#[derive(Debug, Clone, PartialEq)]\npub struct ThreadHandoff {\n    pub luma_y_start: u32,\n    pub luma_y_end: u32,\n    pub segment_offset_in_file: u32,\n    pub segment_size: u32,\n    pub overhang_byte: u8,\n    pub num_overhang_bits: u8,\n    pub last_dc: [i16; 4],\n}\n\nimpl ThreadHandoff {\n    pub fn deserialize<R: Read>(num_threads: u8, data: &mut R) -> Result<Vec<ThreadHandoff>> {\n        let mut retval: Vec<ThreadHandoff> = Vec::with_capacity(num_threads as usize);\n\n        for _i in 0..num_threads {\n            let mut th = ThreadHandoff {\n                luma_y_start: data.read_u16::<LittleEndian>()? as u32,\n                luma_y_end: 0,             // filled in later\n                segment_offset_in_file: 0, // not serialized\n                segment_size: data.read_u32::<LittleEndian>()?,\n                overhang_byte: data.read_u8()?,\n                num_overhang_bits: data.read_u8()?,\n                last_dc: [0; 4],\n            };\n\n            for j in 0..COLOR_CHANNEL_NUM_BLOCK_TYPES {\n                th.last_dc[j] = data.read_i16::<LittleEndian>()?\n            }\n            for _j in COLOR_CHANNEL_NUM_BLOCK_TYPES..4 {\n                data.read_u16::<LittleEndian>()?;\n            }\n\n            retval.push(th);\n        }\n\n        for i in 1..retval.len() {\n            retval[i - 1].luma_y_end = retval[i].luma_y_start;\n        }\n\n        // last LumaYEnd is not serialzed, filled in later\n        return Ok(retval);\n    }\n\n    pub fn serialize<W: Write>(data: &Vec<ThreadHandoff>, retval: &mut W) -> Result<()> {\n        retval.write_u8(data.len() as u8)?;\n\n        for th in data {\n            retval.write_u16::<LittleEndian>(th.luma_y_start as u16)?;\n            // SegmentOffsetInFile is not serialized to preserve compatibility with original Lepton format\n            retval.write_i32::<LittleEndian>(th.segment_size as i32)?;\n            retval.write_u8(th.overhang_byte)?;\n            retval.write_u8(th.num_overhang_bits)?;\n\n            for i in 0..COLOR_CHANNEL_NUM_BLOCK_TYPES {\n                retval.write_i16::<LittleEndian>(th.last_dc[i])?;\n            }\n            for _i in COLOR_CHANNEL_NUM_BLOCK_TYPES..4 {\n                retval.write_u16::<LittleEndian>(0)?;\n            }\n        }\n\n        return Ok(());\n    }\n\n    // Combine two ThreadHandoff objects into a range, starting with the \"from\" segment, and\n    // continuing until the end of the \"to\" segment [from, to]\n    pub fn get_combine_thread_range_segment_size(\n        from: &ThreadHandoff,\n        to: &ThreadHandoff,\n    ) -> usize {\n        return (to.segment_offset_in_file - from.segment_offset_in_file + to.segment_size)\n            as usize;\n    }\n\n    pub fn combine_thread_ranges(from: &ThreadHandoff, to: &ThreadHandoff) -> ThreadHandoff {\n        let ret = ThreadHandoff {\n            segment_offset_in_file: from.segment_offset_in_file,\n            luma_y_start: from.luma_y_start,\n            overhang_byte: from.overhang_byte,\n            num_overhang_bits: from.num_overhang_bits,\n            luma_y_end: to.luma_y_end,\n            segment_size: ThreadHandoff::get_combine_thread_range_segment_size(from, to) as u32,\n            last_dc: from.last_dc,\n        };\n\n        return ret;\n    }\n}\n"
  },
  {
    "path": "lib/src/structs/vpx_bool_reader.rs",
    "content": "/*---------------------------------------------------------------------------------------------\n *  Copyright (c) Microsoft Corporation. All rights reserved.\n *  Licensed under the Apache License, Version 2.0. See LICENSE.txt in the project root for license information.\n *  This software incorporates material from third parties. See NOTICE.txt for details.\n *--------------------------------------------------------------------------------------------*/\n\n/*\n *  Copyright (c) 2010 The WebM project authors. All Rights Reserved.\n *\n *  Use of this source code is governed by a BSD-style license\n *  that can be found in the LICENSE banner below\n *  An additional intellectual property rights grant can be found\n *  in the file PATENTS.  All contributing project authors may\n *  be found in the VPX_AUTHORS file in this directory\n */\n/*\nCopyright (c) 2010, Google Inc. All rights reserved.\nRedistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:\nRedistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.\nRedistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.\nNeither the name of Google nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.\nTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS “AS IS” AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n*/\n\nuse std::io::{Read, Result};\n\nuse crate::lepton_error;\nuse crate::lepton_error::{ExitCode, err_exit_code};\nuse crate::metrics::{Metrics, ModelComponent};\nuse crate::structs::branch::Branch;\nuse crate::structs::simple_hash::SimpleHash;\n\nconst BITS_IN_BYTE: u32 = 8;\nconst BITS_IN_VALUE: u32 = 64;\nconst BITS_IN_VALUE_MINUS_LAST_BYTE: u32 = BITS_IN_VALUE - BITS_IN_BYTE;\nconst VALUE_MASK: u64 = (1 << BITS_IN_VALUE_MINUS_LAST_BYTE) - 1;\n\npub struct VPXBoolReader<R> {\n    value: u64,\n    range: u64, // 128 << BITS_IN_VALUE_MINUS_LAST_BYTE <= range <= 255 << BITS_IN_VALUE_MINUS_LAST_BYTE\n    upstream_reader: R,\n    model_statistics: Metrics,\n    #[allow(dead_code)]\n    pub hash: SimpleHash,\n}\n\nimpl<R: Read> VPXBoolReader<R> {\n    pub fn new(reader: R) -> lepton_error::Result<Self> {\n        let mut r = VPXBoolReader {\n            upstream_reader: reader,\n            value: 1 << (BITS_IN_VALUE - 1), // guard bit\n            range: 255 << BITS_IN_VALUE_MINUS_LAST_BYTE,\n            model_statistics: Metrics::default(),\n            hash: SimpleHash::new(),\n        };\n\n        let mut dummy_branch = Branch::new();\n        let bit = r.get_bit(&mut dummy_branch, ModelComponent::Dummy)?; // marker false bit\n        if bit {\n            return err_exit_code(ExitCode::StreamInconsistent, \"StreamInconsistent\");\n        }\n\n        return Ok(r);\n    }\n\n    pub fn drain_stats(&mut self) -> Metrics {\n        self.model_statistics.drain()\n    }\n\n    // Lepton uses VP8 adaptive arithmetic coding scheme, where bits are extracted from file stream\n    // by \"division\" of current 8-bit stream `value` by adaptive 8-bit `split`. Adaptation is achieved by\n    // combination of predicted probability to get false bit (`1 <= probability <= 255`, in 1/256 units),\n    // and `range` that represents maximum possible value of yet-not-decoded stream part (so that\n    // `range > value`, `128 <= range <= 256` in units of $2^{-n-8}$ for the `n` bits already consumed)\n    // by forming predictor `split = 1 + (((range - 1) * probability) >> BITS_IN_BYTE)`,\n    // `1 <= split <= range - 1`. Comparison of predictor with stream gives the next decoded bit:\n    // true for `value >= split` and false otherwise - this is effectively division step.\n    // After this we shrink `value` and `range` by `split` for true or shrink `range` to `split`\n    // for false and update `probability`. Now `range` can get out of allowable range and we restore it\n    // by shifting left both `range` and `value` with corresponding filling of `value` by further\n    // stream bits (it corresponds to bring down new digit in division, and since `range > value` is invariant\n    // of the operations, shifted out `value` bits are guaranteed to be 0). Repeat until stream ends.\n    //\n    // Reference: https://datatracker.ietf.org/doc/html/rfc6386#section-7.\n    //\n    // Here some improvements to the basic scheme are implemented. First, we store more stream bits\n    // in `value` to reduce refill rate, so that 8 MSBs of `value` represent `value` of the scheme\n    // (it was already implemented in DropBox version, however, with shorter 16-bit `value`).\n    // Second, `range` and `split` are also stored in 8 MSBs of the same size variables (it is new\n    // and it allows to reduce number of operations to compute `split` - previously `big_split` -\n    // and to update `range` and `shift`). Third, we use local values for all stream state variables\n    // to reduce number of memory load/store operations in decoding of many-bit values. Fourth,\n    // we use in `value` a set bit after the stream bits as a guard - completely getting rid\n    // of bit counter and not changing comparison result `value >= split`.\n    #[inline(always)]\n    pub fn get(\n        &mut self,\n        branch: &mut Branch,\n        tmp_value: &mut u64,\n        tmp_range: &mut u64,\n        _cmp: ModelComponent,\n    ) -> bool {\n        let probability = branch.get_probability() as u64;\n\n        let split = mul_prob(*tmp_range, probability);\n\n        // So optimizer understands that 0 should never happen and uses a cold jump\n        // if we don't have LZCNT on x86 CPUs (older BSR instruction requires check for zero).\n        // This is better since the branch prediction figures quickly this never happens and can run\n        // the code sequentially.\n        #[cfg(all(\n            not(target_feature = \"lzcnt\"),\n            any(target_arch = \"x86\", target_arch = \"x86_64\")\n        ))]\n        assert!(*tmp_range - split > 0);\n\n        let bit = *tmp_value >= split;\n\n        branch.record_and_update_bit(bit);\n\n        if bit {\n            *tmp_range -= split;\n            *tmp_value -= split;\n        } else {\n            *tmp_range = split;\n        }\n\n        let shift = (*tmp_range).leading_zeros();\n\n        *tmp_value <<= shift;\n        *tmp_range <<= shift;\n\n        #[cfg(feature = \"compression_stats\")]\n        {\n            self.model_statistics\n                .record_compression_stats(_cmp, 1, i64::from(shift));\n        }\n\n        #[cfg(feature = \"detailed_tracing\")]\n        {\n            self.hash.hash(branch.get_u64());\n            self.hash.hash(*tmp_value);\n            self.hash.hash(*tmp_range);\n\n            let hash = self.hash.get();\n            //if hash == 0x88f9c945\n            {\n                print!(\"({0}:{1:x})\", bit as u8, hash);\n                if hash % 8 == 0 {\n                    println!();\n                }\n            }\n        }\n\n        bit\n    }\n\n    #[inline(always)]\n    pub fn get_grid<const A: usize>(\n        &mut self,\n        branches: &mut [Branch; A],\n        _cmp: ModelComponent,\n    ) -> Result<usize> {\n        // check if A is a power of 2\n        debug_assert!((A & (A - 1)) == 0);\n\n        let mut tmp_value = self.value;\n        let mut tmp_range = self.range;\n\n        let mut decoded_so_far = 1;\n        // We can read only each 7-th iteration: minimum 56 bits are in `value` after `vpx_reader_fill`,\n        // and one `get` needs 8 bits but consumes at most 7 bits (with `range` coming from >127 to 1).\n        // As Lepton uses only 3 and 6 iterations, we can read only once.\n        debug_assert!(A <= 128);\n        tmp_value = Self::vpx_reader_fill(tmp_value, &mut self.upstream_reader)?;\n\n        for _index in 0..A.ilog2() {\n            let cur_bit = self.get(\n                &mut branches[decoded_so_far],\n                &mut tmp_value,\n                &mut tmp_range,\n                _cmp,\n            ) as usize;\n            decoded_so_far <<= 1;\n            decoded_so_far |= cur_bit;\n        }\n\n        // remove set leading bit\n        let value = decoded_so_far ^ A;\n\n        self.value = tmp_value;\n        self.range = tmp_range;\n\n        Ok(value)\n    }\n\n    #[inline(always)]\n    pub fn get_unary_encoded<const A: usize>(\n        &mut self,\n        branches: &mut [Branch; A],\n        _cmp: ModelComponent,\n    ) -> Result<usize> {\n        let mut tmp_value = self.value;\n        let mut tmp_range = self.range;\n\n        for value in 0..A {\n            let split = mul_prob(tmp_range, branches[value].get_probability() as u64);\n\n            // We know that after this we have min 56 stream bits in `tmp_value`,\n            // and can have at least 7 iterations, so we can decode 7 bits at once.\n            // Each iteration needs at least 8 bits of stream in `tmp_value` and\n            // consumes max 7 of them.\n            debug_assert!(A <= 14);\n            if value == 0 || value == 7 {\n                tmp_value = Self::vpx_reader_fill(tmp_value, &mut self.upstream_reader)?;\n            }\n\n            if tmp_value >= split {\n                branches[value].record_and_update_bit(true);\n\n                tmp_range -= split;\n                tmp_value -= split;\n\n                let shift = tmp_range.leading_zeros();\n\n                tmp_value <<= shift;\n                tmp_range <<= shift;\n\n                #[cfg(feature = \"compression_stats\")]\n                {\n                    self.model_statistics\n                        .record_compression_stats(_cmp, 1, i64::from(shift));\n                }\n\n                #[cfg(feature = \"detailed_tracing\")]\n                {\n                    self.hash.hash(branches[value].get_u64());\n                    self.hash.hash(tmp_value);\n                    self.hash.hash(tmp_range);\n\n                    let hash = self.hash.get();\n                    //if hash == 0x88f9c945\n                    {\n                        print!(\"({0}:{1:x})\", true as u8, hash);\n                        if hash % 8 == 0 {\n                            println!();\n                        }\n                    }\n                }\n            } else {\n                branches[value].record_and_update_bit(false);\n\n                tmp_range = split;\n\n                let shift = tmp_range.leading_zeros();\n\n                tmp_value <<= shift;\n                tmp_range <<= shift;\n\n                #[cfg(feature = \"compression_stats\")]\n                {\n                    self.model_statistics\n                        .record_compression_stats(_cmp, 1, i64::from(shift));\n                }\n\n                #[cfg(feature = \"detailed_tracing\")]\n                {\n                    self.hash.hash(branches[value].get_u64());\n                    self.hash.hash(tmp_value);\n                    self.hash.hash(tmp_range);\n\n                    let hash = self.hash.get();\n                    //if hash == 0x88f9c945\n                    {\n                        print!(\"({0}:{1:x})\", false as u8, hash);\n                        if hash % 8 == 0 {\n                            println!();\n                        }\n                    }\n                }\n\n                self.value = tmp_value;\n                self.range = tmp_range;\n\n                return Ok(value);\n            }\n        }\n\n        self.value = tmp_value;\n        self.range = tmp_range;\n\n        Ok(A)\n    }\n\n    #[inline(always)]\n    pub fn get_n_bits<const A: usize>(\n        &mut self,\n        n: usize,\n        branches: &mut [Branch; A],\n        _cmp: ModelComponent,\n    ) -> Result<usize> {\n        assert!(n <= branches.len());\n\n        let mut tmp_value = self.value;\n        let mut tmp_range = self.range;\n\n        let mut coef = 0;\n        for i in (0..n).rev() {\n            // Here the fastest way is to use condition of `get_bit`, presumably as\n            // this loop cannot be unrolled due to vaiable iterations number.\n            // Moreover, this condition holds very rarely as `value` is usually already filled\n            // by previous `get_bit` sign reading.\n            if tmp_value & VALUE_MASK == 0 {\n                tmp_value = Self::vpx_reader_fill(tmp_value, &mut self.upstream_reader)?;\n            }\n\n            coef |=\n                (self.get(&mut branches[i], &mut tmp_value, &mut tmp_range, _cmp) as usize) << i;\n        }\n\n        self.value = tmp_value;\n        self.range = tmp_range;\n\n        return Ok(coef);\n    }\n\n    #[inline(always)]\n    pub fn get_bit(&mut self, branch: &mut Branch, _cmp: ModelComponent) -> Result<bool> {\n        let mut tmp_value = self.value;\n        let mut tmp_range = self.range;\n\n        // We ensure that the guard bit never comes into the first byte,\n        // thus having in `value` at least 8 stream bits.\n        if tmp_value & VALUE_MASK == 0 {\n            tmp_value = Self::vpx_reader_fill(tmp_value, &mut self.upstream_reader)?;\n        }\n\n        let bit = self.get(branch, &mut tmp_value, &mut tmp_range, _cmp);\n\n        self.value = tmp_value;\n        self.range = tmp_range;\n\n        return Ok(bit);\n    }\n\n    // Fill `tmp_value` maximally still preserving space for the guard bit,\n    // after this returned value has `56 | (63 - shift)` stream bits\n    #[inline(always)]\n    fn vpx_reader_fill(mut tmp_value: u64, upstream_reader: &mut R) -> Result<u64> {\n        // This `if` does not change performance but drops down instructions count by 3 %\n        if tmp_value & 0xFF == 0 {\n            let mut shift: i32 = tmp_value.trailing_zeros() as i32;\n            // Unset the last guard bit and set a new one\n            tmp_value &= tmp_value - 1;\n            tmp_value |= 1 << (shift & 7);\n\n            // BufReader is already pretty efficient handling small reads, so optimization doesn't help that much\n            let mut v = [0u8; 1];\n            shift -= 7;\n\n            while shift > 0 {\n                let bytes_read = upstream_reader.read(&mut v)?;\n                if bytes_read == 0 {\n                    break;\n                }\n\n                tmp_value |= (v[0] as u64) << shift;\n                shift -= 8;\n            }\n        }\n\n        return Ok(tmp_value);\n    }\n}\n\nfn mul_prob(tmp_range: u64, probability: u64) -> u64 {\n    ((((tmp_range - (1 << BITS_IN_VALUE_MINUS_LAST_BYTE)) >> 8) * probability)\n        & (0xFF << BITS_IN_VALUE_MINUS_LAST_BYTE))\n        + (1 << BITS_IN_VALUE_MINUS_LAST_BYTE)\n}\n"
  },
  {
    "path": "lib/src/structs/vpx_bool_writer.rs",
    "content": "/*---------------------------------------------------------------------------------------------\n *  Copyright (c) Microsoft Corporation. All rights reserved.\n *  Licensed under the Apache License, Version 2.0. See LICENSE.txt in the project root for license information.\n *  This software incorporates material from third parties. See NOTICE.txt for details.\n *--------------------------------------------------------------------------------------------*/\n\n/*\n *  Copyright (c) 2010 The WebM project authors. All Rights Reserved.\n *\n *  Use of this source code is governed by a BSD-style license\n *  that can be found in the LICENSE banner below\n *  An additional intellectual property rights grant can be found\n *  in the file PATENTS.  All contributing project authors may\n *  be found in the VPX_AUTHORS file in this directory\n */\n/*\nCopyright (c) 2010, Google Inc. All rights reserved.\nRedistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:\nRedistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.\nRedistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.\nNeither the name of Google nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.\nTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS “AS IS” AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n*/\n\nuse std::io::{Result, Write};\n\nuse crate::helpers::needs_to_grow;\nuse crate::metrics::{Metrics, ModelComponent};\nuse crate::structs::branch::Branch;\nuse crate::structs::simple_hash::SimpleHash;\n\npub struct VPXBoolWriter<W> {\n    low_value: u64,\n    range: u32,\n    writer: W,\n    buffer: Vec<u8>,\n    model_statistics: Metrics,\n    #[allow(dead_code)]\n    pub hash: SimpleHash,\n}\n\nimpl<W: Write> VPXBoolWriter<W> {\n    pub fn new(writer: W) -> Result<Self> {\n        let mut retval = VPXBoolWriter {\n            low_value: 1 << 9, // this divider bit keeps track of stream bits number\n            range: 255,\n            buffer: Vec::new(),\n            writer: writer,\n            model_statistics: Metrics::default(),\n            hash: SimpleHash::new(),\n        };\n\n        let mut dummy_branch = Branch::new();\n        // initial false bit is put to not get carry out of stream bits\n        retval.put_bit(false, &mut dummy_branch, ModelComponent::Dummy)?;\n\n        Ok(retval)\n    }\n\n    pub fn drain_stats(&mut self) -> Metrics {\n        self.model_statistics.drain()\n    }\n\n    #[inline(always)]\n    pub fn put(\n        &mut self,\n        bit: bool,\n        branch: &mut Branch,\n        mut tmp_value: u64,\n        mut tmp_range: u32,\n        _cmp: ModelComponent,\n    ) -> (u64, u32) {\n        #[cfg(feature = \"detailed_tracing\")]\n        {\n            // used to detect divergences between the C++ and rust versions\n            self.hash.hash(branch.get_u64());\n            self.hash.hash(tmp_value);\n            self.hash.hash(tmp_range);\n\n            let hashed_value = self.hash.get();\n            //if hashedValue == 0xe35c28fd\n            {\n                print!(\"({0}:{1:x})\", bit as u8, hashed_value);\n                if hashed_value % 8 == 0 {\n                    println!();\n                }\n            }\n        }\n\n        let probability = branch.get_probability() as u32;\n\n        let split = 1 + (((tmp_range - 1) * probability) >> 8);\n\n        branch.record_and_update_bit(bit);\n\n        if bit {\n            tmp_value += split as u64;\n            tmp_range -= split;\n        } else {\n            tmp_range = split;\n        }\n\n        let shift = (tmp_range as u8).leading_zeros();\n\n        #[cfg(feature = \"compression_stats\")]\n        {\n            self.model_statistics\n                .record_compression_stats(_cmp, 1, i64::from(shift));\n        }\n\n        tmp_range <<= shift;\n        tmp_value <<= shift;\n\n        // check whether we cannot put next bit into stream\n        if tmp_value & (u64::MAX << 57) != 0 {\n            // calculate the number odd bits left over after we remove:\n            // - 48 bits (6 bytes) flushed to buffer\n            // - 8 bits need to keep for coding accuracy (since probability resolution is 8 bits)\n            // - 1 bit for marker\n            // - 1 bit for overflow\n            //\n            // leftover_bits will always be <= 8\n            let leftover_bits = tmp_value.leading_zeros() + 2;\n\n            // shift align so that the top 6 bytes are ones we want to write, if there\n            // was an overflow it gets rotated down to the bottom bit\n            let v_aligned = tmp_value.rotate_left(leftover_bits);\n\n            if (v_aligned & 1) != 0 {\n                self.carry();\n            }\n\n            // Append the top six bytes of the u64 into buffer in big endian so that the top byte goes first.\n            if needs_to_grow(&self.buffer, 8) {\n                // avoid inlining slow path to allocate more memory that happens almost never\n                put_6bytes(&mut self.buffer, v_aligned);\n            } else {\n                // Faster to add all 8 and then shrink the buffer than add 6 that creates a temporary buffer.\n                let b = v_aligned.to_be_bytes();\n                self.buffer.extend_from_slice(&b);\n                self.buffer.truncate(self.buffer.len() - 2);\n            }\n\n            // mask the remaining bits (between 8 and 16) and put them back to where they were\n            // adding the marker bit to the top\n            tmp_value = ((v_aligned & 0xffff) | 0x20000/*marker bit*/) >> leftover_bits;\n        }\n\n        (tmp_value, tmp_range)\n    }\n\n    /// Safe as: at the stream beginning initially put `false` ensure that carry cannot get out\n    /// of the first stream byte - then `carry` cannot be invoked on empty `buffer`,\n    /// and after the stream beginning `flush_non_final_data` keeps carry-terminating\n    /// byte sequence (one non-255-byte before any number of 255-bytes) inside the `buffer`.\n    ///\n    /// Cold to keep this out of the inner loop since carries are pretty rare\n    #[cold]\n    #[inline(never)]\n    fn carry(&mut self) {\n        let mut x = self.buffer.len() - 1;\n\n        while self.buffer[x] == 0xFF {\n            self.buffer[x] = 0;\n\n            assert!(x > 0);\n            x -= 1;\n        }\n\n        self.buffer[x] += 1;\n    }\n\n    #[inline(always)]\n    pub fn put_grid<const A: usize>(\n        &mut self,\n        v: u8,\n        branches: &mut [Branch; A],\n        cmp: ModelComponent,\n    ) -> Result<()> {\n        // check if A is a power of 2\n        assert!((A & (A - 1)) == 0);\n        let mut tmp_value = self.low_value;\n        let mut tmp_range = self.range;\n\n        let mut index = A.ilog2() - 1;\n        let mut serialized_so_far = 1;\n\n        loop {\n            let cur_bit = (v & (1 << index)) != 0;\n            (tmp_value, tmp_range) = self.put(\n                cur_bit,\n                &mut branches[serialized_so_far],\n                tmp_value,\n                tmp_range,\n                cmp,\n            );\n\n            if index == 0 {\n                break;\n            }\n\n            serialized_so_far <<= 1;\n            serialized_so_far |= cur_bit as usize;\n\n            index -= 1;\n        }\n\n        self.low_value = tmp_value;\n        self.range = tmp_range;\n\n        Ok(())\n    }\n\n    #[inline(always)]\n    pub fn put_n_bits<const A: usize>(\n        &mut self,\n        bits: usize,\n        num_bits: usize,\n        branches: &mut [Branch; A],\n        cmp: ModelComponent,\n    ) -> Result<()> {\n        let mut tmp_value = self.low_value;\n        let mut tmp_range = self.range;\n\n        let mut i: i32 = (num_bits - 1) as i32;\n        while i >= 0 {\n            (tmp_value, tmp_range) = self.put(\n                (bits & (1 << i)) != 0,\n                &mut branches[i as usize],\n                tmp_value,\n                tmp_range,\n                cmp,\n            );\n            i -= 1;\n        }\n\n        self.low_value = tmp_value;\n        self.range = tmp_range;\n\n        Ok(())\n    }\n\n    #[inline(always)]\n    pub fn put_unary_encoded<const A: usize>(\n        &mut self,\n        v: usize,\n        branches: &mut [Branch; A],\n        cmp: ModelComponent,\n    ) -> Result<()> {\n        assert!(v <= A);\n\n        let mut tmp_value = self.low_value;\n        let mut tmp_range = self.range;\n\n        for i in 0..A {\n            let cur_bit = v != i;\n\n            (tmp_value, tmp_range) = self.put(cur_bit, &mut branches[i], tmp_value, tmp_range, cmp);\n            if !cur_bit {\n                break;\n            }\n        }\n\n        self.low_value = tmp_value;\n        self.range = tmp_range;\n\n        Ok(())\n    }\n\n    #[inline(always)]\n    pub fn put_bit(\n        &mut self,\n        value: bool,\n        branch: &mut Branch,\n        _cmp: ModelComponent,\n    ) -> Result<()> {\n        let mut tmp_value = self.low_value;\n        let mut tmp_range = self.range;\n\n        (tmp_value, tmp_range) = self.put(value, branch, tmp_value, tmp_range, _cmp);\n\n        self.low_value = tmp_value;\n        self.range = tmp_range;\n\n        Ok(())\n    }\n\n    // Here we write down only bytes of the stream necessary for decoding -\n    // opposite to initial Lepton implementation that writes down all the buffer.\n    pub fn finish(&mut self) -> Result<()> {\n        let mut tmp_value = self.low_value;\n        let stream_bits = 64 - tmp_value.leading_zeros() - 2;\n        // 55 >= stream_bits >= 8\n\n        tmp_value <<= 63 - stream_bits;\n        if tmp_value & (1 << 63) != 0 {\n            self.carry();\n        }\n\n        let mut shift = 63;\n        for _stream_bytes in 0..(stream_bits + 7) >> 3 {\n            shift -= 8;\n            self.buffer.push((tmp_value >> shift) as u8);\n        }\n        // check that no stream bits remain in the buffer\n        debug_assert!(!(u64::MAX << shift) & tmp_value == 0);\n\n        self.writer.write_all(&self.buffer[..])?;\n        Ok(())\n    }\n\n    /// When buffer is full and is going to be sent to output, preserve buffer data that\n    /// is not final and should be carried over to the next buffer. At least one byte\n    /// will remain in `buffer` if it is non-empty.\n    pub fn flush_non_final_data(&mut self) -> Result<()> {\n        // carry over buffer data that might be not final\n        let mut i = self.buffer.len();\n        if i > 1 {\n            i -= 1;\n            while self.buffer[i] == 0xFF {\n                assert!(i > 0);\n                i -= 1;\n            }\n\n            self.writer.write_all(&self.buffer[..i])?;\n            self.buffer.drain(..i);\n        }\n\n        Ok(())\n    }\n}\n\n#[cold]\n#[inline(never)]\nfn put_6bytes(buffer: &mut Vec<u8>, v: u64) {\n    let b = v.to_be_bytes();\n    buffer.extend_from_slice(b[0..6].as_ref());\n}\n\n#[cfg(test)]\nuse crate::structs::vpx_bool_reader::VPXBoolReader;\n\n#[test]\nfn test_roundtrip_vpxboolwriter_n_bits() {\n    const MAX_N: usize = 8;\n\n    #[derive(Default)]\n    struct BranchData {\n        branches: [Branch; MAX_N],\n    }\n\n    let mut buffer = Vec::new();\n    let mut writer = VPXBoolWriter::new(&mut buffer).unwrap();\n\n    let mut branches = BranchData::default();\n\n    for i in 0..1024 {\n        writer\n            .put_n_bits(\n                i as usize % 256,\n                MAX_N,\n                &mut branches.branches,\n                ModelComponent::Dummy,\n            )\n            .unwrap();\n    }\n\n    writer.finish().unwrap();\n\n    let mut branches = BranchData::default();\n\n    let mut reader = VPXBoolReader::new(&buffer[..]).unwrap();\n    for i in 0..1024 {\n        let read_value = reader\n            .get_n_bits(MAX_N, &mut branches.branches, ModelComponent::Dummy)\n            .unwrap();\n        assert_eq!(read_value, i as usize % 256);\n    }\n}\n\n#[test]\nfn test_roundtrip_vpxboolwriter_unary() {\n    const MAX_UNARY: usize = 11; // the size used in Lepton\n\n    #[derive(Default)]\n    struct BranchData {\n        branches: [Branch; MAX_UNARY],\n    }\n\n    let mut buffer = Vec::new();\n    let mut writer = VPXBoolWriter::new(&mut buffer).unwrap();\n\n    let mut branches = BranchData::default();\n\n    for i in 0..1024 {\n        writer\n            .put_unary_encoded(\n                i as usize % (MAX_UNARY + 1),\n                &mut branches.branches,\n                ModelComponent::Dummy,\n            )\n            .unwrap();\n    }\n\n    writer.finish().unwrap();\n\n    let mut branches = BranchData::default();\n\n    let mut reader = VPXBoolReader::new(&buffer[..]).unwrap();\n    for i in 0..1024 {\n        let read_value = reader\n            .get_unary_encoded(&mut branches.branches, ModelComponent::Dummy)\n            .unwrap();\n        assert_eq!(read_value, i as usize % (MAX_UNARY + 1));\n    }\n}\n\n#[test]\nfn test_roundtrip_vpxboolwriter_grid() {\n    #[derive(Default)]\n    struct BranchData {\n        branches: [Branch; 8],\n    }\n\n    let mut buffer = Vec::new();\n    let mut writer = VPXBoolWriter::new(&mut buffer).unwrap();\n\n    let mut branches = BranchData::default();\n\n    for i in 0..1024 {\n        writer\n            .put_grid(i as u8 % 8, &mut branches.branches, ModelComponent::Dummy)\n            .unwrap();\n    }\n\n    writer.finish().unwrap();\n\n    let mut branches = BranchData::default();\n\n    let mut reader = VPXBoolReader::new(&buffer[..]).unwrap();\n    for i in 0..1024 {\n        let read_value = reader\n            .get_grid(&mut branches.branches, ModelComponent::Dummy)\n            .unwrap();\n        assert_eq!(read_value, i as usize % 8);\n    }\n}\n\n#[test]\nfn test_roundtrip_vpxboolwriter_single_bit() {\n    let mut buffer = Vec::new();\n    let mut writer = VPXBoolWriter::new(&mut buffer).unwrap();\n\n    let mut branch = Branch::default();\n\n    for i in 0..1024 {\n        writer\n            .put_bit(i % 10 == 0, &mut branch, ModelComponent::Dummy)\n            .unwrap();\n    }\n\n    writer.finish().unwrap();\n\n    let mut branch = Branch::default();\n\n    let mut reader = VPXBoolReader::new(&buffer[..]).unwrap();\n    for i in 0..1024 {\n        let read_value = reader.get_bit(&mut branch, ModelComponent::Dummy).unwrap();\n        assert_eq!(read_value, i % 10 == 0);\n    }\n}\n"
  },
  {
    "path": "package/Lepton.Jpeg.Rust.nuspec",
    "content": "﻿<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<package xmlns=\"http://schemas.microsoft.com/packaging/2011/08/nuspec.xsd\">\n  <metadata>\n    <id>Lepton.Jpeg.Rust</id>\n    <version>0.5.5.8</version>\n    <title>Lepton JPEG Compression Rust version binaries and libraries</title>\n    <authors>kristofr</authors>\n    <owners>kristofr</owners>\n    <requireLicenseAcceptance>false</requireLicenseAcceptance>\n    <description>Lepton Rust binaries and libraries</description>\n    <tags>lepton</tags>\n  </metadata>\n  <files>\n    <file src=\"..\\target\\release\\lepton_jpeg_util.exe\" target=\"exe\\release\\x64\" />\n    <file src=\"..\\target\\release\\lepton_jpeg_util.pdb\" target=\"exe\\release\\x64\" />\n    <file src=\"..\\target\\release\\lepton_jpeg_util_avx2.exe\" target=\"exe\\release\\x64\" />\n    <file src=\"..\\target\\release\\lepton_jpeg_util_avx2.pdb\" target=\"exe\\release\\x64\" />\n    <file src=\"..\\target\\release\\lepton_jpeg.dll\" target=\"lib\\release\\x64\" />\n    <file src=\"..\\target\\release\\lepton_jpeg.pdb\" target=\"lib\\release\\x64\" />\n    <file src=\"..\\target\\release\\lepton_jpeg_avx2.dll\" target=\"lib\\release\\x64\" />\n    <file src=\"..\\target\\release\\lepton_jpeg_avx2.pdb\" target=\"lib\\release\\x64\" />\n  </files>\n</package>"
  },
  {
    "path": "python/Cargo.toml",
    "content": "[package]\nname = \"lepton_jpeg_python\"\nversion.workspace = true\nedition = \"2024\"\n\n[lib]\ncrate-type = [\"cdylib\"]\n\n[dependencies]\npyo3 = { version = \"0.27\", features = [\"extension-module\"] }\nlepton_jpeg = { path = \"../lib\" }\nrayon = \"1\"\n\n"
  },
  {
    "path": "python/README.md",
    "content": "# Lepton JPEG Compression \n\nThis is a port of the C++ Lepton JPEG compression tool that was released by DropBox [dropbox/lepton](https://github.com/dropbox/lepton). We developed a port of the library to Rust, which has basically the same performance characteristics with the advantage of all the safety features that Rust has to offer, due to the work involved in performing an exhaustive security check on the C++ code and the fact that DropBox has deprecated the codebase.\n\nWith precise bit-by-bit recovery of the original JPEG, the Lepton compression library is designed for lossless compression of baseline and progressive JPEGs up to 22%. JPEG storage in a cloud storage system is the main application case. Even metadata headers and invalid content are kept in good condition.\n\n\n## How to Use This Library\n\nThe library exposes two methods, compress and decompress, which can be invoked as follows:\n\n``` python\n\n    with open(\"my image\", \"rb\") as f:\n        jpg_data = f.read()\n\n    config = {\"max_jpeg_width\": 4096 }\n    compressed = lepton_jpeg_python.compress_bytes(jpg_data, config)\n    decompressed = lepton_jpeg_python.decompress_bytes(compressed)\n\n    assert jpg_data == decompressed\n```    \n\nThe following config options are supported:\n- max_jpeg_width: reject compressing images wider than this\n- max_jpeg_height: reject compressioning images taller than this\n- progressive: false to forbid compressing progressive JPEGs\n- reject_dqts_with_zeros: true if we should reject JPEGs with 0 in their quantitization table\n- max_partitions: maximum number of partitions to split JPEG into in order to allow for parallel compression/decompression\n- max_jpeg_file_size: reject JPEGs larger than this\n\n## Contributing\n\nThere are many ways in which you can participate in this project, for example:\n\n* [Submit bugs and feature requests](https://github.com/microsoft/lepton_jpeg_rust/issues), and help us verify as they are checked in\n* Review [source code changes](https://github.com/microsoft/lepton_jpeg_rust/pulls) or submit your own features as pull requests.\n* The library uses only **stable features**, so if you want to take advantage of SIMD features such as AVX2, use the Wide crate (see the idct.rs as an example) rather than intrinsics. \n\n## Code of Conduct\n\nThis project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments.\n\n## License\n\nCopyright (c) Microsoft Corporation. All rights reserved.\n\nLicensed under the [Apache 2.0](LICENSE.txt) license.\n\n"
  },
  {
    "path": "python/pyproject.toml",
    "content": "[build-system]\nrequires = [\"maturin>=1.0,<2.0\"]\nbuild-backend = \"maturin\"\n\n[project]\nname = \"lepton_jpeg_python\"\nversion = \"0.5.8\"\ndescription = \"Rust port of the Lepton JPEG compression library\"\nauthors = [{ name = \"Kristof Roomp  \", email = \"kristofr@gmail.com\" }]\nreadme = \"README.md\"\nlicense = \"Apache-2.0\"\nrequires-python = \">=3.8\"\nclassifiers = [\n    \"Programming Language :: Python :: 3\",\n    \"Programming Language :: Rust\",\n    \"Operating System :: OS Independent\",\n]\n\n[project.urls]\nHomepage = \"https://github.com/microsoft/lepton_jpeg_rust\"\n"
  },
  {
    "path": "python/src/lib.rs",
    "content": "use lepton_jpeg::{DEFAULT_THREAD_POOL, LeptonThreadPool, SingleThreadPool};\nuse pyo3::prelude::*;\nuse pyo3::types::{PyBytes, PyDict};\nuse std::io::Cursor;\nuse std::sync::LazyLock;\n\nenum ThreadOptions {\n    SingleThread,\n    PerCpu,\n    NoLimit,\n}\n\nstruct RayonThreadPool {\n    pool: LazyLock<rayon::ThreadPool>,\n}\n\nimpl LeptonThreadPool for RayonThreadPool {\n    fn run(&self, f: Box<dyn FnOnce() + Send + 'static>) {\n        self.pool.spawn(f);\n    }\n    fn max_parallelism(&self) -> usize {\n        std::thread::available_parallelism().unwrap().get()\n    }\n}\n\nstatic RAYON_THREAD_POOL: RayonThreadPool = RayonThreadPool {\n    pool: LazyLock::new(|| rayon::ThreadPoolBuilder::new().build().unwrap()),\n};\n\nfn parse_config(\n    config: Option<&Bound<'_, PyDict>>,\n) -> PyResult<(lepton_jpeg::EnabledFeatures, ThreadOptions)> {\n    let mut features = lepton_jpeg::EnabledFeatures::compat_lepton_vector_write();\n\n    let mut threads = ThreadOptions::PerCpu;\n\n    if let Some(cfg) = config {\n        for (key, value) in cfg.iter() {\n            let key_str: &str = key.extract()?;\n            match key_str {\n                \"max_jpeg_width\" => {\n                    let val: u32 = value.extract()?;\n                    features.max_jpeg_width = val;\n                }\n                \"max_jpeg_height\" => {\n                    let val: u32 = value.extract()?;\n                    features.max_jpeg_height = val;\n                }\n                \"progressive\" => {\n                    let val: bool = value.extract()?;\n                    features.progressive = val;\n                }\n                \"reject_dqts_with_zeros\" => {\n                    let val: bool = value.extract()?;\n                    features.reject_dqts_with_zeros = val;\n                }\n                \"max_partitions\" => {\n                    let val: u32 = value.extract()?;\n                    features.max_partitions = val;\n                }\n                \"max_jpeg_file_size\" => {\n                    let val: u32 = value.extract()?;\n                    features.max_jpeg_file_size = val;\n                }\n                \"threads\" => match value.extract::<&str>()? {\n                    \"single\" => threads = ThreadOptions::SingleThread,\n                    \"per_cpu\" => threads = ThreadOptions::PerCpu,\n                    \"no_limit\" => threads = ThreadOptions::NoLimit,\n                    _ => {\n                        return Err(pyo3::exceptions::PyValueError::new_err(format!(\n                            \"Invalid threads option: {}\",\n                            value.extract::<&str>()?\n                        )));\n                    }\n                },\n                _ => {\n                    return Err(pyo3::exceptions::PyValueError::new_err(format!(\n                        \"Unknown configuration key: {}\",\n                        key_str\n                    )));\n                }\n            }\n        }\n    }\n    Ok((features, threads))\n}\n\n#[pyfunction]\n#[pyo3(signature = (data, config=None))]\npub fn compress_bytes(\n    py: Python,\n    data: &[u8],\n    config: Option<&Bound<'_, PyDict>>,\n) -> PyResult<Py<PyAny>> {\n    let mut compressed = Vec::new();\n\n    let (features, threads) = parse_config(config)?;\n    let single = SingleThreadPool::default();\n\n    lepton_jpeg::encode_lepton(\n        &mut Cursor::new(data),\n        &mut Cursor::new(&mut compressed),\n        &features,\n        match threads {\n            ThreadOptions::SingleThread => &single,\n            ThreadOptions::PerCpu => &RAYON_THREAD_POOL,\n            ThreadOptions::NoLimit => &DEFAULT_THREAD_POOL,\n        },\n    )\n    .map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!(\"Compression failed: {}\", e)))?;\n\n    Ok(PyBytes::new(py, &compressed).into())\n}\n\n#[pyfunction]\n#[pyo3(signature = (data, config=None))]\npub fn decompress_bytes(\n    py: Python,\n    data: &[u8],\n    config: Option<&Bound<'_, PyDict>>,\n) -> PyResult<Py<PyAny>> {\n    let mut decompressed = Vec::new();\n\n    let (features, threads) = parse_config(config)?;\n    let single = SingleThreadPool::default();\n\n    lepton_jpeg::decode_lepton(\n        &mut Cursor::new(data),\n        &mut Cursor::new(&mut decompressed),\n        &features,\n        match threads {\n            ThreadOptions::SingleThread => &single,\n            ThreadOptions::PerCpu => &RAYON_THREAD_POOL,\n            ThreadOptions::NoLimit => &DEFAULT_THREAD_POOL,\n        },\n    )\n    .map_err(|e| {\n        pyo3::exceptions::PyRuntimeError::new_err(format!(\"Decompression failed: {}\", e))\n    })?;\n\n    Ok(PyBytes::new(py, &decompressed).into())\n}\n\n#[pymodule]\nfn lepton_jpeg_python(m: &Bound<'_, PyModule>) -> PyResult<()> {\n    m.add_function(wrap_pyfunction!(compress_bytes, m)?)?;\n    m.add_function(wrap_pyfunction!(decompress_bytes, m)?)?;\n    Ok(())\n}\n"
  },
  {
    "path": "python/tests/test_compress.py",
    "content": "import lepton_jpeg_python\n\ndef test_compress_decompress():\n    # load slr city from images directory\n    with open(\"../images/slrcity.jpg\", \"rb\") as f:\n        jpg_data = f.read()\n\n    config = {\n        \"max_jpeg_width\": 8196,\n        \"max_jpeg_height\": 8196,\n        \"progressive\": False,\n        \"reject_dqts_with_zeros\": True,\n        \"max_partitions\": 8,\n        \"max_jpeg_file_size\": 128 * 1024 * 1024 }\n\n    compressed = lepton_jpeg_python.compress_bytes(jpg_data, config)\n    decompressed = lepton_jpeg_python.decompress_bytes(compressed, config)\n\n    assert jpg_data == decompressed\n    print(\"Compression and decompression successful!\")"
  },
  {
    "path": "rustfmt.toml",
    "content": ""
  },
  {
    "path": "tests/end_to_end.rs",
    "content": "/*---------------------------------------------------------------------------------------------\n *  Copyright (c) Microsoft Corporation. All rights reserved.\n *  Licensed under the Apache License, Version 2.0. See LICENSE.txt in the project root for license information.\n *  This software incorporates material from third parties. See NOTICE.txt for details.\n *--------------------------------------------------------------------------------------------*/\n\nuse core::result::Result;\nuse std::fs::read_dir;\nuse std::io::Cursor;\nuse std::path::Path;\n\nuse lepton_jpeg::{\n    DEFAULT_THREAD_POOL, EnabledFeatures, decode_lepton, encode_lepton, encode_lepton_verify,\n};\nuse lepton_jpeg::{ExitCode, LeptonError};\nuse rstest::rstest;\n\n/// handy function to compare two arrays, and print the first mismatch. Useful for debugging.\n#[track_caller]\npub fn assert_eq_array<T: PartialEq + std::fmt::Debug>(a: &[T], b: &[T]) {\n    use core::panic;\n\n    if a.len() != b.len() {\n        for i in 0..std::cmp::min(a.len(), b.len()) {\n            assert_eq!(\n                a[i],\n                b[i],\n                \"length mismatch {},{} and first mismatch at offset {}\",\n                a.len(),\n                b.len(),\n                i\n            );\n        }\n        panic!(\n            \"length mismatch {} and {}, but common prefix identical\",\n            a.len(),\n            b.len()\n        );\n    } else {\n        for i in 0..a.len() {\n            assert_eq!(\n                a[i],\n                b[i],\n                \"length identical {}, but first mismatch at offset {}\",\n                a.len(),\n                i\n            );\n        }\n    }\n}\n\n/// reads a file from the images directory for testing or benchmarking purposes\npub fn read_file(filename: &str, ext: &str) -> Vec<u8> {\n    use std::io::Read;\n\n    let filename = std::path::Path::new(env!(\"WORKSPACE_ROOT\"))\n        .join(\"images\")\n        .join(filename.to_owned() + ext);\n    let mut f = std::fs::File::open(filename).unwrap();\n\n    let mut content = Vec::new();\n    f.read_to_end(&mut content).unwrap();\n\n    content\n}\n\n/// verifies that the decode will accept existing Lepton files and generate\n/// exactly the same jpeg from them. Used to detect unexpected divergences in coding format.\n#[rstest]\nfn verify_decode(\n    #[values(\n        \"android\",\n        \"androidcrop\",\n        \"androidcropoptions\",\n        \"androidprogressive\",\n        \"androidprogressive_garbage\",\n        \"androidtrail\",\n        \"colorswap\",\n        \"cathedral_db_non_int\",\n        \"cathedral_db_non_int_rustold\",\n        \"gray2sf\",\n        \"grayscale\",\n        \"hq\",\n        \"half_scan\",\n        \"half_scan_rust55\",\n        \"iphone\",\n        \"iphonecity\",\n        \"iphonecity_with_16KGarbage\",\n        \"iphonecity_with_1MGarbage\",\n        \"iphonecrop\",\n        \"iphonecrop2\",\n        \"iphoneprogressive\",\n        \"iphoneprogressive2\",\n        \"progressive_late_dht\", // image has huffman tables that come very late which causes a verification failure \n        \"out_of_order_dqt\",     // image with quanatization table dqt that comes after image definition SOF\n        \"narrowrst\",\n        \"nofsync\",\n        \"slrcity\",\n        \"slrhills\",\n        \"slrindoor\",\n        \"tiny\",\n        \"trailingrst\",\n        \"trailingrst2\",\n        \"trunc\",\n        \"truncbad\",          // the lepton format is truncated and invalid\n        \"eof_and_trailingrst\",    // the lepton format has a wrongly set unexpected eof and trailing rst\n        \"eof_and_trailinghdrdata\" // the lepton format has a wrongly set unexpected eof and trailing header data\n    )]\n    file: &str,\n) {\n    use lepton_jpeg::DEFAULT_THREAD_POOL;\n\n    println!(\"decoding {0:?}\", file);\n\n    let input = read_file(file, \".lep\");\n    let expected = read_file(file, \".jpg\");\n\n    let mut output = Vec::new();\n\n    decode_lepton(\n        &mut Cursor::new(input),\n        &mut output,\n        &EnabledFeatures::compat_lepton_vector_read(),\n        &DEFAULT_THREAD_POOL,\n    )\n    .unwrap();\n\n    assert_eq_array(&output, &expected);\n}\n\n/// verifies that the decode will accept existing Lepton files and generate\n/// exactly the same jpeg from them. Used to detect unexpected divergences in coding format.\n#[test]\nfn verify_decode_scalar_overflow() {\n    let file = \"mathoverflow_scalar\";\n\n    println!(\"decoding {0:?}\", file);\n\n    let input = read_file(file, \".lep\");\n    let expected = read_file(file, \".jpg\");\n\n    let mut output = Vec::new();\n\n    let features = EnabledFeatures::compat_lepton_scalar_read();\n\n    decode_lepton(\n        &mut Cursor::new(input),\n        &mut output,\n        &features,\n        &DEFAULT_THREAD_POOL,\n    )\n    .unwrap();\n\n    assert_eq_array(&output, &expected);\n}\n\n/// encodes as LEP and codes back to JPG to mostly test the encoder. Can't check against\n/// the original LEP file since there's no guarantee they are binary identical (especially the zlib encoded part)\n#[rstest]\nfn verify_encode(\n    #[values(\n            \"android\",\n            \"androidcrop\",\n            \"androidcropoptions\",\n            \"androidprogressive\",\n            \"androidprogressive_garbage\",\n            \"androidtrail\",\n            \"colorswap\",\n            \"gray2sf\",\n            \"grayscale\",\n            \"hq\",\n            //\"half_scan\",\n            \"iphone\",\n            \"iphonecity\",\n            \"iphonecity_with_16KGarbage\",\n            \"iphonecity_with_1MGarbage\",\n            \"iphonecrop\",\n            \"iphonecrop2\",\n            \"iphoneprogressive\",\n            \"iphoneprogressive2\",\n            \"progressive_late_dht\", // image has huffman tables that come very late which caused a verification failure \n            \"out_of_order_dqt\",\n            //\"narrowrst\",\n            //\"nofsync\",\n            \"slrcity\",\n            \"slrhills\",\n            \"slrindoor\",\n            \"tiny\",\n            \"trailingrst\",\n            \"trailingrst2\",\n            \"trunc\",\n        )]\n    file: &str,\n) {\n    let input = read_file(file, \".jpg\");\n\n    let mut lepton = Vec::new();\n    let mut output = Vec::new();\n\n    encode_lepton(\n        &mut Cursor::new(&input),\n        &mut Cursor::new(&mut lepton),\n        &EnabledFeatures::compat_lepton_vector_write(),\n        &DEFAULT_THREAD_POOL,\n    )\n    .unwrap();\n\n    decode_lepton(\n        &mut Cursor::new(lepton),\n        &mut output,\n        &EnabledFeatures::compat_lepton_vector_read(),\n        &DEFAULT_THREAD_POOL,\n    )\n    .unwrap();\n\n    assert_eq_array(&input, &output);\n}\n\n/// these files are expected to fail encoding due to unsupported features or roundtrip errors\n#[rstest]\nfn verify_fail_encode(#[values(\"half_scan\", \"narrowrst\", \"nofsync\")] file: &str) {\n    let input = read_file(file, \".jpg\");\n\n    let result = encode_lepton_verify(\n        &input,\n        &EnabledFeatures::compat_lepton_vector_write(),\n        &DEFAULT_THREAD_POOL,\n    );\n\n    assert!(result.is_err(), \"encoding was expected to fail\");\n}\n\n#[test]\nfn verify_16bitmath() {\n    // verifies that we can decode 16 bit encoded images from the C++ version\n    {\n        let input = read_file(\"mathoverflow_16\", \".lep\");\n        let expected = read_file(\"mathoverflow\", \".jpg\");\n\n        let mut output = Vec::new();\n\n        let features = EnabledFeatures::compat_lepton_vector_read();\n\n        decode_lepton(\n            &mut Cursor::new(input),\n            &mut output,\n            &features,\n            &DEFAULT_THREAD_POOL,\n        )\n        .unwrap();\n\n        assert_eq_array(&output, &expected);\n    }\n\n    // verify that we can decode the one generated by the Rust version\n    {\n        let input = read_file(\"mathoverflow_32\", \".lep\");\n        let expected = read_file(\"mathoverflow\", \".jpg\");\n\n        let mut output = Vec::new();\n\n        let mut features = EnabledFeatures::compat_lepton_vector_read();\n        features.use_16bit_dc_estimate = false;\n\n        decode_lepton(\n            &mut Cursor::new(input),\n            &mut output,\n            &features,\n            &DEFAULT_THREAD_POOL,\n        )\n        .unwrap();\n\n        assert_eq_array(&output, &expected);\n    }\n}\n\n/// encodes as LEP and codes back to JPG to mostly test the encoder. Can't check against\n/// the original LEP file since there's no guarantee they are binary identical (especially the zlib encoded part)\n#[rstest]\nfn verify_encode_verify(#[values(\"slrcity\")] file: &str) {\n    let input = read_file(file, \".jpg\");\n\n    encode_lepton_verify(\n        &input[..],\n        &EnabledFeatures::compat_lepton_vector_write(),\n        &DEFAULT_THREAD_POOL,\n    )\n    .unwrap();\n}\n\nfn assert_exception<T>(expected_error: ExitCode, result: Result<T, LeptonError>) {\n    match result {\n        Ok(_) => panic!(\"failure was expected\"),\n        Err(e) => {\n            assert_eq!(expected_error, e.exit_code(), \"unexpected error {0:?}\", e);\n        }\n    }\n}\n\n#[rstest]\nfn verify_encode_verify_fail(#[values(\"mismatch_encode\")] file: &str) {\n    let input = read_file(file, \".jpg\");\n\n    assert_exception(\n        ExitCode::VerificationContentMismatch,\n        encode_lepton_verify(\n            &input[..],\n            &EnabledFeatures::compat_lepton_vector_write(),\n            &DEFAULT_THREAD_POOL,\n        ),\n    );\n}\n\n/// ensures we error out if we have the progressive flag disabled\n#[rstest]\nfn verify_encode_progressive_false(\n    #[values(\"androidprogressive\", \"iphoneprogressive\", \"iphoneprogressive2\")] file: &str,\n) {\n    let input = read_file(file, \".jpg\");\n    let mut lepton = Vec::new();\n    assert_exception(\n        ExitCode::ProgressiveUnsupported,\n        encode_lepton(\n            &mut Cursor::new(&input),\n            &mut Cursor::new(&mut lepton),\n            &EnabledFeatures {\n                progressive: false,\n                ..EnabledFeatures::compat_lepton_vector_write()\n            },\n            &DEFAULT_THREAD_POOL,\n        ),\n    );\n}\n\n/// non-optimally zero length encoding progressive JPEGs cannot be recreated properly since the encoder always tries to create the longest zero runs\n/// legally allowed given the available huffman codes.\n#[test]\nfn verify_nonoptimal() {\n    let input = read_file(\"nonoptimalprogressive\", \".jpg\");\n    let mut lepton = Vec::new();\n    assert_exception(\n        ExitCode::UnsupportedJpeg,\n        encode_lepton(\n            &mut Cursor::new(&input),\n            &mut Cursor::new(&mut lepton),\n            &EnabledFeatures::compat_lepton_vector_write(),\n            &DEFAULT_THREAD_POOL,\n        ),\n    );\n}\n\n/// processing of images with zeros in DQT tables may lead to divide-by-zero, therefore these images are not supported\n#[test]\nfn verify_encode_image_with_zeros_in_dqt_tables() {\n    let input = read_file(\"zeros_in_dqt_tables\", \".jpg\");\n    let mut lepton = Vec::new();\n\n    assert_exception(\n        ExitCode::UnsupportedJpegWithZeroIdct0,\n        encode_lepton(\n            &mut Cursor::new(&input),\n            &mut Cursor::new(&mut lepton),\n            &EnabledFeatures::compat_lepton_vector_write(),\n            &DEFAULT_THREAD_POOL,\n        ),\n    );\n}\n\n/// tests all previous fuzzing failures to ensure they remain fixed. This requires them to be\n/// checked into the repository under the fuzz/artifacts/fuzz_target_1 directory as crash-xxxx files\n#[test]\nfn test_previous_fuzz_failures() {\n    for entry in read_dir(\n        Path::new(env!(\"WORKSPACE_ROOT\"))\n            .join(\"fuzz\")\n            .join(\"artifacts\")\n            .join(\"fuzz_target_1\"),\n    )\n    .unwrap()\n    {\n        let entry = entry.unwrap();\n        let path = entry.path();\n\n        // see if it starts with crash-\n        let filename = path.file_name().unwrap().to_str().unwrap();\n        if !filename.starts_with(\"crash-\") {\n            continue;\n        }\n\n        println!(\n            \"testing fuzz failure reproduction for file {}\",\n            path.display()\n        );\n\n        let data = std::fs::read(path).unwrap();\n        test_fuzz_failure(&data);\n    }\n\n    /// mirrors what we do for fuzz testing so that we can reproduce failures found by the fuzzer\n    /// and ensure that they remain fixed\n    fn test_fuzz_failure(data: &[u8]) {\n        let mut output = Vec::new();\n\n        let use_16bit = match data.len() % 2 {\n            0 => false,\n            _ => true,\n        };\n        let accept_invalid_dht = match (data.len() / 2) % 2 {\n            0 => false,\n            _ => true,\n        };\n\n        // keep the jpeg dimensions small otherwise the fuzzer gets really slow\n        let features = EnabledFeatures {\n            progressive: true,\n            reject_dqts_with_zeros: true,\n            max_jpeg_height: 1024,\n            max_jpeg_width: 1024,\n            use_16bit_dc_estimate: use_16bit,\n            use_16bit_adv_predict: use_16bit,\n            accept_invalid_dht: accept_invalid_dht,\n            ..EnabledFeatures::compat_lepton_vector_write()\n        };\n\n        let r;\n        {\n            let mut writer = Cursor::new(&mut output);\n\n            r = encode_lepton(\n                &mut Cursor::new(&data),\n                &mut writer,\n                &features,\n                &DEFAULT_THREAD_POOL,\n            );\n        }\n\n        let mut original = Vec::new();\n\n        match r {\n            Ok(_) => {\n                let _ = decode_lepton(\n                    &mut Cursor::new(&output),\n                    &mut original,\n                    &features,\n                    &DEFAULT_THREAD_POOL,\n                );\n            }\n            Err(_) => {}\n        }\n    }\n}\n"
  },
  {
    "path": "tests/verifycompression.cmd",
    "content": "@echo off\n..\\target\\release\\lepton_jpeg_util.exe %1 nul > error.txt\nif errorlevel 1 (\necho %1 failed %errorlevel%\necho ------------ >> failedlog.txt\necho %1 failed %errorlevel% >> failedlog.txt\ntype error.txt >> failedlog.txt\n)"
  },
  {
    "path": "tests/verifydir.cmd",
    "content": "@echo off\nsetlocal enabledelayedexpansion\nfor  /r %1 %%f in (*.jpg)  do call verifycompression.cmd ^\"%%f\"^"
  },
  {
    "path": "util/Cargo.toml",
    "content": "[package]\nname = \"lepton_jpeg_util\"\nversion.workspace = true\nedition = \"2024\"\nauthors = [\"Kristof Roomp <kristofr@microsoft.com>\"]\n\n[features]\ndefault = []\n\n[dependencies]\nlepton_jpeg = { path = \"../lib\" }\npico-args = \"0.5\"\nlog = \"0.4\"\nsimple_logger =\"5.0\"\nrayon = \"1\" \nuuid = { version = \"1.19\", features = [\"v4\"] }\nwinpipe = \"0.1\"\nmsvc_spectre_libs = \"0.1.3\"\n\n[[bin]]\nname=\"lepton_jpeg_util\"\n\n\n"
  },
  {
    "path": "util/src/main.rs",
    "content": "/*---------------------------------------------------------------------------------------------\n *  Copyright (c) Microsoft Corporation. All rights reserved.\n *  Licensed under the Apache License, Version 2.0. See LICENSE.txt in the project root for license information.\n *  This software incorporates material from third parties. See NOTICE.txt for details.\n *--------------------------------------------------------------------------------------------*/\n\nuse std::borrow::Cow;\nuse std::ffi::OsStr;\nuse std::fs::{File, OpenOptions};\nuse std::io::{Cursor, IsTerminal, Read, Seek, Write, stdin, stdout};\nuse std::path::{Path, PathBuf};\nuse std::process::{Command, Stdio};\nuse std::time::{Duration, Instant};\n\nuse lepton_jpeg::{\n    CpuTimeMeasure, DEFAULT_THREAD_POOL, EnabledFeatures, ExitCode, LeptonError, LeptonThreadPool,\n    LeptonThreadPriority, Metrics, SimpleThreadPool, SingleThreadPool, StreamPosition,\n    decode_lepton, dump_jpeg, encode_lepton, encode_lepton_verify,\n};\nuse log::{error, info};\nuse simple_logger::SimpleLogger;\n\nuse crate::verifydir::corrupt_data_if_enabled;\n\nmod verifydir;\n\nstatic LOW_PRIORITY_THREAD_POOL: SimpleThreadPool =\n    SimpleThreadPool::new(LeptonThreadPriority::Low);\nstatic HIGH_PRIORITY_THREAD_POOL: SimpleThreadPool =\n    SimpleThreadPool::new(LeptonThreadPriority::High);\n\n#[derive(Copy, Clone, Debug)]\nenum FileType {\n    Jpeg,\n    Lepton,\n}\n\nfn parse_i32(s: &str) -> Result<i32, &'static str> {\n    s.parse().map_err(|_| \"not a number\")\n}\n\nfn parse_u32(s: &str) -> Result<u32, &'static str> {\n    s.parse().map_err(|_| \"not a number\")\n}\n\nfn parse_u64(s: &str) -> Result<u64, &'static str> {\n    s.parse().map_err(|_| \"not a number\")\n}\n\nfn parse_path(s: &OsStr) -> Result<PathBuf, &'static str> {\n    Ok(PathBuf::from(s))\n}\n\nfn override_if<T>(\n    pargs: &mut pico_args::Arguments,\n    name: &'static str,\n    parse: fn(&str) -> Result<T, &'static str>,\n    value: &mut T,\n) -> Result<(), pico_args::Error> {\n    if let Some(v) = pargs.opt_value_from_fn(name, parse)? {\n        *value = v;\n    }\n    Ok(())\n}\n\nstruct UtilError(LeptonError);\n\nimpl UtilError {\n    fn message(&self) -> &str {\n        self.0.message()\n    }\n\n    fn exit_code(&self) -> ExitCode {\n        self.0.exit_code()\n    }\n}\n\nimpl From<pico_args::Error> for UtilError {\n    #[track_caller]\n    fn from(e: pico_args::Error) -> Self {\n        let mut e = LeptonError::new(ExitCode::SyntaxError, e.to_string());\n        e.add_context();\n        UtilError(e)\n    }\n}\n\nimpl From<LeptonError> for UtilError {\n    #[track_caller]\n    fn from(e: LeptonError) -> Self {\n        UtilError(e)\n    }\n}\n\nimpl From<std::io::Error> for UtilError {\n    #[track_caller]\n    fn from(e: std::io::Error) -> Self {\n        UtilError(e.into())\n    }\n}\n\nstruct RecordStreamPosition<W: Write> {\n    writer: W,\n    position: u64,\n}\n\nimpl<W: Write> Write for RecordStreamPosition<W> {\n    fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {\n        let n = self.writer.write(buf)?;\n        self.position += n as u64;\n        Ok(n)\n    }\n\n    fn flush(&mut self) -> std::io::Result<()> {\n        self.writer.flush()\n    }\n}\n\nimpl<W: Write> StreamPosition for RecordStreamPosition<W> {\n    fn position(&mut self) -> u64 {\n        self.position\n    }\n}\n\n// wrap main so that errors get printed nicely without a panic\nfn main_with_result() -> Result<(), UtilError> {\n    let mut pargs = pico_args::Arguments::from_env();\n\n    let mut enabled_features = EnabledFeatures::compat_lepton_vector_read();\n    let mut filter_level = log::LevelFilter::Info;\n\n    if pargs.contains([\"-h\", \"--help\"]) {\n        println!(\n\"lepton_jpeg_util - a fast JPEG compressor\n\nUsage: lepton_jpeg_util [options] inputfile [outputfile]\n\nOptions:\n    --iter <n>              number of iterations to run\n    --dump                  dump the JPEG file\n    --all                   dump includes the scan lines\n    --cppverify <exe path>  verify the output with the C++ decoder\n    --overwrite             overwrite the output file\n    --corrupt <seed>        randomly corrupt the input file (for testing)\n    --quiet                 suppress all output\n    --noverify              do not verify the output\n    --max-width <n>         maximum width of the JPEG file\n    --max-height <n>        maximum height of the JPEG file\n    --max-jpeg-file-size <n> maximum size of the JPEG file\n    --threads <n>           maximum number of threads to use\n    --rejectprogressive     reject progressive JPEG files\n    --rejectdqtswithzeros   reject DQT tables with zeros\n    --rejectinvalidhuffman  reject invalid Huffman tables\n    --use32bitdc            use 32 bit DC estimate\n    --use32bitadv           use 32 bit advanced prediction\n    --useleptonscalar       use the scalar version of the encoder\n    --highpriority          run on p-cores\n    --lowpriority           run on e-cores\n    --version               print the version\n    --help                  print this help message\n    --verifydir             Recursively verify all files in a directory can be compressed and decompressed\n\");\n        return Ok(());\n    }\n\n    let cppverify: Option<PathBuf> = pargs.opt_value_from_os_str(\"--cppverify\", parse_path)?;\n    let verify_dir = pargs.opt_value_from_os_str(\"--verifydir\", parse_path)?;\n\n    let iterations = pargs.opt_value_from_fn(\"--iter\", parse_i32)?.unwrap_or(1);\n    let dump = pargs.contains(\"--dump\");\n    let dumpall = pargs.contains(\"--dumpall\");\n    let verify = !pargs.contains(\"--noverify\");\n    let overwrite = pargs.contains(\"--overwrite\");\n    let mut corrupt = pargs.opt_value_from_fn(\"--corrupt\", parse_u64)?;\n\n    if pargs.contains(\"--quiet\") {\n        filter_level = log::LevelFilter::Warn;\n    }\n\n    override_if(\n        &mut pargs,\n        \"--max-width\",\n        parse_u32,\n        &mut enabled_features.max_jpeg_width,\n    )?;\n\n    override_if(\n        &mut pargs,\n        \"--max-height\",\n        parse_u32,\n        &mut enabled_features.max_jpeg_height,\n    )?;\n\n    override_if(\n        &mut pargs,\n        \"--threads\",\n        parse_u32,\n        &mut enabled_features.max_partitions,\n    )?;\n\n    override_if(\n        &mut pargs,\n        \"--rejectprogressive\",\n        |_| Ok(false),\n        &mut enabled_features.progressive,\n    )?;\n\n    override_if(\n        &mut pargs,\n        \"--rejectdqtswithzeros\",\n        |_| Ok(true),\n        &mut enabled_features.reject_dqts_with_zeros,\n    )?;\n\n    override_if(\n        &mut pargs,\n        \"--rejectinvalidhuffman\",\n        |_| Ok(false),\n        &mut enabled_features.accept_invalid_dht,\n    )?;\n\n    override_if(\n        &mut pargs,\n        \"--max-jpeg-file-size\",\n        parse_u32,\n        &mut enabled_features.max_jpeg_file_size,\n    )?;\n\n    if pargs.contains(\"--version\") {\n        println!(\n            \"compiled library Lepton version {}\",\n            lepton_jpeg::get_version_string()\n        );\n    }\n\n    if pargs.contains(\"--use32bitdc\") {\n        enabled_features.use_16bit_dc_estimate = false;\n    }\n    if pargs.contains(\"--use32bitadv\") {\n        enabled_features.use_16bit_adv_predict = false;\n    }\n    if pargs.contains(\"--useleptonscalar\") {\n        // use both these options if you are trying to read a file that was encoded with the scalar version of the C++ encoder\n        // sadly one old version of the Rust encoder used use_16bit_dc_estimate=false, use_16bit_adv_predict=true\n        // the latest version of the encoder put these options in the header so we ignore this if the file specifies it\n        enabled_features.use_16bit_adv_predict = false;\n        enabled_features.use_16bit_dc_estimate = false;\n    }\n\n    let singlethreaded = pargs.contains(\"--singlethreaded\");\n    let highpriority = pargs.contains(\"--highpriority\");\n    let lowpriority = pargs.contains(\"--lowpriority\");\n\n    let thread_pool: &dyn LeptonThreadPool = if highpriority {\n        // used to force to run on p-cores, make sure this and\n        // any threadpool threads are set to the highest priority\n        &HIGH_PRIORITY_THREAD_POOL\n    } else if lowpriority {\n        // used to force to run on e-cores, make sure this and\n        // any threadpool threads are set to the lowest priority\n        &LOW_PRIORITY_THREAD_POOL\n    } else {\n        if singlethreaded {\n            &SingleThreadPool {}\n        } else {\n            &DEFAULT_THREAD_POOL\n        }\n    };\n\n    let filenames = pargs.finish();\n\n    for i in filenames.iter() {\n        // no other options should be specified only the free standing filenames\n        if i.to_string_lossy().starts_with(\"-\") {\n            return Err(\n                LeptonError::new(ExitCode::SyntaxError, format!(\"unknown option {:?}\", i)).into(),\n            );\n        }\n    }\n\n    // only output the log if we are connected to a console (otherwise if there is redirection we would corrupt the file)\n    if stdout().is_terminal() {\n        SimpleLogger::new().with_level(filter_level).init().unwrap();\n    }\n\n    // if we are verifying a directory, then we need to recursively verify all files in the directory\n    if let Some(verify_dir) = verify_dir {\n        verifydir::verify_dir(\n            verify_dir.as_path(),\n            cppverify.as_ref().unwrap(),\n            &mut corrupt,\n        )?;\n        return Ok(());\n    }\n\n    if dump {\n        let mut file_in = File::open(filenames[0].as_os_str()).unwrap();\n\n        let mut contents = Vec::new();\n        file_in.read_to_end(&mut contents).unwrap();\n        dump_jpeg(&contents, dumpall, &enabled_features).unwrap();\n        return Ok(());\n    }\n\n    let mut input_data = Vec::new();\n    if filenames.len() != 2 {\n        if stdout().is_terminal() || stdin().is_terminal() {\n            return Err(LeptonError::new(\n                ExitCode::SyntaxError,\n                \"source and destination filename are needed or input needs to be redirected\",\n            )\n            .into());\n        }\n\n        // special case for piped input, stream output data as we process it instead of buffering it all\n        let mut data = Vec::new();\n        std::io::stdin().read_to_end(&mut data)?;\n\n        let mut cursor = Cursor::new(&data);\n\n        let mut output = RecordStreamPosition {\n            writer: std::io::stdout(),\n            position: 0,\n        };\n\n        match id_file_type(&data)? {\n            FileType::Jpeg => {\n                lepton_jpeg::encode_lepton(\n                    &mut cursor,\n                    &mut output,\n                    &enabled_features,\n                    thread_pool,\n                )?;\n            }\n            FileType::Lepton => {\n                lepton_jpeg::decode_lepton(\n                    &mut cursor,\n                    &mut output,\n                    &enabled_features,\n                    thread_pool,\n                )?;\n            }\n        }\n\n        return Ok(());\n    } else {\n        let mut file_in = File::open(filenames[0].as_os_str())\n            .map_err(|e| LeptonError::new(ExitCode::FileNotFound, e.to_string()))?;\n\n        file_in.read_to_end(&mut input_data)?;\n    }\n\n    if input_data.len() < 2 {\n        return Err(LeptonError::new(ExitCode::BadLeptonFile, \"ERROR input file too small\").into());\n    }\n\n    let mut metrics;\n    let mut output_data;\n\n    let mut overall_cpu = Duration::ZERO;\n\n    let mut current_iteration = 0;\n\n    // see what file type we have\n    let file_type = id_file_type(&input_data)?;\n\n    // get a writable version of the input data so we can corrupt it if the user wants to\n    let mut writable_input_data = Cow::from(&input_data);\n\n    loop {\n        let thread_cpu = CpuTimeMeasure::new();\n        let walltime = Instant::now();\n\n        corrupt_data_if_enabled(&mut corrupt, &mut writable_input_data.to_mut());\n\n        // do the encoding/decoding, if we got an error and were corrupting the file, then restore the\n        // original data and continue so we can try corrupting the file in different ways\n        // per iteration\n        match do_work(\n            file_type,\n            verify,\n            &writable_input_data,\n            &enabled_features,\n            thread_pool,\n        ) {\n            Err(e) => {\n                error!(\"error {0}\", e);\n\n                // if we corrupted the image, then restore and continue running\n                if corrupt.is_some() {\n                    // reset the input data not be be corrupt anymore\n                    writable_input_data = Cow::from(&input_data);\n                    output_data = Vec::new();\n                    metrics = Metrics::default();\n                } else {\n                    return Err(e.into());\n                }\n            }\n\n            Ok((data, m)) => {\n                output_data = data;\n                metrics = m;\n            }\n        }\n\n        let localthread = thread_cpu.elapsed();\n        let workers = metrics.get_cpu_time_worker_time();\n\n        info!(\n            \"Main thread CPU: {}ms, Worker thread CPU: {} ms, walltime: {} ms\",\n            localthread.as_millis(),\n            workers.as_millis(),\n            walltime.elapsed().as_millis()\n        );\n\n        overall_cpu += localthread + workers;\n\n        current_iteration += 1;\n        if current_iteration >= iterations {\n            break;\n        }\n    }\n\n    if filenames.len() != 2 {\n        std::io::stdout().write_all(&output_data[..])?\n    } else {\n        let output_filename = filenames[1].as_os_str();\n\n        let mut fileout = OpenOptions::new()\n            .write(true)\n            .create(overwrite)\n            .create_new(!overwrite)\n            .open(output_filename)?;\n\n        // ignore if this failed (etc on a pipe)\n        let _ = fileout.set_len(output_data.len() as u64);\n        fileout.write_all(&output_data[..])?;\n        drop(fileout);\n\n        // what we do is take the lepton output, and see if it recreates the input using the\n        // CPP version of the encoder/decoder\n        if let Some(cpp_path) = cppverify {\n            execute_cpp_verify(cpp_path.as_path(), output_filename, &writable_input_data)?;\n        }\n    }\n\n    if iterations > 1 {\n        info!(\n            \"Overall average CPU consumed per iteration {0}ms \",\n            overall_cpu.as_millis() / (iterations as u128)\n        );\n    }\n\n    Ok(())\n}\n\nfn id_file_type(input_data: &[u8]) -> Result<FileType, LeptonError> {\n    Ok(if input_data[0] == 0xff && input_data[1] == 0xd8 {\n        FileType::Jpeg\n    } else if input_data[0] == 0xcf && input_data[1] == 0x84 {\n        FileType::Lepton\n    } else {\n        return Err(LeptonError::new(\n            ExitCode::BadLeptonFile,\n            \"ERROR input file is not a valid JPEG or Lepton file\",\n        )\n        .into());\n    })\n}\n\nfn execute_cpp_verify(\n    cpp_executable: &Path,\n    compressed_file: &OsStr,\n    original_contents: &[u8],\n) -> Result<(), LeptonError> {\n    let (output, exit_code, stderr) =\n        call_executable_with_input(cpp_executable, compressed_file).unwrap();\n\n    if exit_code != 0 {\n        log::error!(\"cpp exit code: {}\", exit_code);\n\n        return Err(LeptonError::new(\n            ExitCode::ExternalVerificationFailed,\n            format!(\n                \"cpp verify failed with exit code {0} stderr: {1}\",\n                exit_code, stderr\n            ),\n        ))?;\n    }\n    if output[..].len() != original_contents.len() {\n        return Err(LeptonError::new(\n            ExitCode::ExternalVerificationFailed,\n            format!(\n                \"cpp verify failed with different length {0} != {1}\",\n                output[..].len(),\n                original_contents.len()\n            ),\n        ));\n    }\n    if output[..] != original_contents[..] {\n        return Err(LeptonError::new(\n            ExitCode::ExternalVerificationFailed,\n            \"verify failed with different data\",\n        )\n        .into());\n    }\n    log::info!(\"verify succeeded with cpp version\");\n    Ok(())\n}\n\n/// does the actual encoding/decoding work\nfn do_work(\n    file_type: FileType,\n    verify: bool,\n    input_data: &[u8],\n    enabled_features: &EnabledFeatures,\n    thread_pool: &dyn LeptonThreadPool,\n) -> Result<(Vec<u8>, Metrics), LeptonError> {\n    let metrics;\n    let mut output;\n\n    match file_type {\n        FileType::Jpeg => {\n            if verify {\n                (output, metrics) =\n                    encode_lepton_verify(input_data, enabled_features, thread_pool)?;\n            } else {\n                let mut reader = Cursor::new(input_data);\n                output = Vec::with_capacity(input_data.len());\n                let mut writer = Cursor::new(&mut output);\n\n                metrics = encode_lepton(&mut reader, &mut writer, enabled_features, thread_pool)?\n            }\n\n            info!(\n                \"compressed input {0}, output {1} bytes (compression = {2:.1}%)\",\n                input_data.len(),\n                output.len(),\n                ((input_data.len() as f64) / (output.len() as f64) - 1.0) * 100.0\n            );\n        }\n        FileType::Lepton => {\n            let mut reader = Cursor::new(&input_data);\n\n            output = Vec::with_capacity(input_data.len());\n\n            metrics = decode_lepton(&mut reader, &mut output, &enabled_features, thread_pool)?;\n        }\n    }\n\n    Ok((output, metrics))\n}\n\n/// internal debug utility used to figure out where in the output the JPG diverged if there was a coding error writing out the JPG\nstruct VerifyWriter<W> {\n    output: W,\n    good_data: Vec<u8>,\n    offset: usize,\n}\n\nimpl<W> VerifyWriter<W> {\n    // used for debugging\n    #[allow(dead_code)]\n    pub fn new<R: Read>(output: W, mut reader: R) -> Self {\n        let mut r = VerifyWriter {\n            output,\n            offset: 0,\n            good_data: Vec::new(),\n        };\n        reader.read_to_end(&mut r.good_data).unwrap();\n        r\n    }\n}\nimpl<W: Write + Seek> Seek for VerifyWriter<W> {\n    fn seek(&mut self, pos: std::io::SeekFrom) -> std::io::Result<u64> {\n        self.output.seek(pos)\n    }\n}\n\nimpl<W: Write + Seek> Write for VerifyWriter<W> {\n    fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {\n        let goodslice = &self.good_data[self.offset..self.offset + buf.len()];\n\n        if goodslice[..] != buf[..] {\n            for i in 0..goodslice.len() {\n                if goodslice[i] != buf[i] {\n                    eprintln!(\"at position {0}\", self.output.stream_position()? + i as u64);\n\n                    self.output.write_all(buf)?;\n                    self.output.flush()?;\n                    panic!(\"mismatched file!\");\n                }\n            }\n        }\n\n        self.offset += buf.len();\n        self.output.write_all(buf)?;\n        return Ok(buf.len());\n    }\n\n    fn flush(&mut self) -> std::io::Result<()> {\n        return self.output.flush();\n    }\n}\n\nfn main() {\n    match main_with_result() {\n        Ok(_) => {}\n        Err(e) => {\n            eprintln!(\n                \"error code: {0} {1} {2}\",\n                e.exit_code(),\n                e.exit_code().as_integer_error_code(),\n                e.message()\n            );\n            std::process::exit(e.exit_code().as_integer_error_code());\n        }\n    }\n}\n\n/// calls the CPP version of the encoder/decoder to verify the output of the Rust version\npub fn call_executable_with_input(\n    cpp_executable: &Path,\n    input_filename: &OsStr,\n) -> Result<(Vec<u8>, i32, String), LeptonError> {\n    // temporary file to store the output of the cpp version so we can\n    // compare it with the rust version\n\n    let temp_filename_buf = std::env::temp_dir().join(\"lepton_jpeg_util_cpp_recreate.jpg\");\n    let temp_filename = temp_filename_buf.as_os_str();\n\n    // delete if already exists\n    let _ = std::fs::remove_file(temp_filename);\n\n    log::info!(\n        \"verifying input filename with CPP {:?} with {:?}\",\n        temp_filename,\n        cpp_executable\n    );\n\n    // Spawn the command\n    let child = Command::new(cpp_executable)\n        .arg(input_filename)\n        .arg(temp_filename)\n        .stdout(Stdio::piped())\n        .stderr(Stdio::piped())\n        .spawn()?;\n\n    // Wait for the child process to exit and collect output\n    let output = child.wait_with_output()?;\n\n    let mut file_in = File::open(&temp_filename).unwrap();\n    let mut contents = Vec::new();\n    file_in.read_to_end(&mut contents).unwrap();\n\n    // remove the temporary file\n    let _ = std::fs::remove_file(temp_filename);\n\n    // Extract the stdout, stderr, and exit status\n    let stderr = String::from_utf8_lossy(&output.stderr).to_string();\n    let exit_code = output.status.code().unwrap_or(-10000); // Handle the case where exit code is None\n\n    Ok((contents, exit_code, stderr))\n}\n"
  },
  {
    "path": "util/src/verifydir.rs",
    "content": "use std::ffi::OsStr;\nuse std::fs::{self, ReadDir};\nuse std::io::Cursor;\nuse std::path::{Path, PathBuf};\nuse std::process::{Command, Stdio};\nuse uuid::Uuid;\n\nuse lepton_jpeg::{EnabledFeatures, LeptonError};\n\npub struct RecursiveFiles {\n    stack: Vec<ReadDir>,\n}\n\nimpl RecursiveFiles {\n    pub fn new(root: impl AsRef<Path>) -> std::io::Result<Self> {\n        Ok(Self {\n            stack: vec![fs::read_dir(root)?],\n        })\n    }\n}\n\nimpl Iterator for RecursiveFiles {\n    type Item = PathBuf;\n\n    fn next(&mut self) -> Option<Self::Item> {\n        while let Some(dir) = self.stack.last_mut() {\n            match dir.next() {\n                Some(Ok(entry)) => {\n                    let path = entry.path();\n                    match entry.file_type() {\n                        Ok(ft) if ft.is_dir() => {\n                            if let Ok(rd) = fs::read_dir(&path) {\n                                self.stack.push(rd);\n                            }\n                        }\n                        Ok(ft) if ft.is_file() => {\n                            return Some(path);\n                        }\n                        _ => {}\n                    }\n                }\n                Some(Err(_)) => continue,\n                None => {\n                    self.stack.pop();\n                }\n            }\n        }\n        None\n    }\n}\n\nstruct RayonPool {}\n\nimpl lepton_jpeg::LeptonThreadPool for RayonPool {\n    fn max_parallelism(&self) -> usize {\n        rayon::current_num_threads()\n    }\n\n    fn run(&self, f: Box<dyn FnOnce() + Send + 'static>) {\n        rayon::spawn(f);\n    }\n}\n\n/// randomly corrupts data if there is a seed\npub fn corrupt_data_if_enabled(seed: &mut Option<u64>, input_data: &mut Vec<u8>) {\n    fn simple_lcg(seed: &mut u64) -> u64 {\n        let r = seed.wrapping_mul(6364136223846793005) + 1;\n        *seed = r;\n        r\n    }\n\n    if let Some(seed) = seed {\n        if input_data.len() > 0 {\n            let op = simple_lcg(seed);\n            let r = simple_lcg(seed) as usize % input_data.len();\n\n            match op % 5 {\n                0 => {\n                    // flip bit\n                    let bitnumber = simple_lcg(seed) as usize % 8;\n                    input_data[r] ^= 1 << bitnumber;\n                }\n                1 => {\n                    // truncate file\n                    input_data.truncate(r);\n                }\n                2 => {\n                    // insert random byte\n                    let random_byte = (simple_lcg(seed) & 0xFF) as u8;\n                    input_data.insert(r, random_byte);\n                }\n                3 => {\n                    // delete byte\n                    if input_data.len() > 1 {\n                        input_data.remove(r);\n                    }\n                }\n                4 => {\n                    // truncate by 1\n                    if input_data.len() > 1 {\n                        input_data.truncate(input_data.len() - 1);\n                    }\n                }\n                _ => {\n                    // do nothing\n                }\n            }\n        }\n    }\n}\n\npub fn verify_dir(\n    root_path: &Path,\n    cpp_executable: &Path,\n    corruption_seed: &mut Option<u64>,\n) -> Result<(), LeptonError> {\n    let iter = RecursiveFiles::new(root_path).unwrap();\n\n    iter.for_each(|file_path| {\n        if file_path.extension().and_then(|s| s.to_str()) != Some(\"jpg\") {\n            return;\n        }\n        call_executable_with_input(cpp_executable, file_path.as_os_str(), corruption_seed);\n    });\n    Ok(())\n}\n\n/// calls the CPP version of the encoder/decoder to verify the output of the Rust version\npub fn call_executable_with_input(\n    cpp_executable: &Path,\n    input_filename: &OsStr,\n    corruption_seed: &mut Option<u64>,\n) {\n    let mut input_data = std::fs::read(input_filename).unwrap();\n    corrupt_data_if_enabled(corruption_seed, &mut input_data);\n\n    // write to temporary file with potential corruption\n    let temp_filename_input =\n        std::env::temp_dir().join(format!(\"lepton_jpeg_util_cpp_{}.jpg\", Uuid::new_v4()));\n    std::fs::write(&temp_filename_input, &input_data).unwrap();\n\n    let temp_filename_output =\n        std::env::temp_dir().join(format!(\"lepton_jpeg_util_cpp_{}.lep\", Uuid::new_v4()));\n\n    // delete output if already exists\n    let _ = std::fs::remove_file(&temp_filename_output);\n\n    // Spawn the command\n    let child = Command::new(cpp_executable)\n        .arg(&temp_filename_input)\n        .arg(&temp_filename_output)\n        .stdout(Stdio::piped())\n        .stderr(Stdio::piped())\n        .spawn()\n        .unwrap();\n\n    // Wait for the child process to exit and collect output\n    let output = child.wait_with_output().unwrap();\n\n    // Extract the stdout, stderr, and exit status\n    let stderr = String::from_utf8_lossy(&output.stderr).to_string();\n    let exit_code = output.status.code().unwrap_or(-10000); // Handle the case where exit code is None\n\n    if exit_code != 0 {\n        log::info!(\n            \"CPP executable failed for file {:?} with exit code {}: {}\",\n            input_filename,\n            exit_code,\n            stderr\n        );\n    } else {\n        let cpp_lepton_data = fs::read(&temp_filename_output).unwrap();\n\n        let mut writer = Vec::new();\n\n        if let Err(e) = lepton_jpeg::decode_lepton(\n            &mut Cursor::new(&cpp_lepton_data),\n            &mut writer,\n            &EnabledFeatures::compat_lepton_vector_read(),\n            &RayonPool {},\n        ) {\n            panic!(\n                \"Error decoding CPP output for file {}: {} {} seed:{:?}\",\n                input_filename.to_string_lossy(),\n                e,\n                stderr,\n                corruption_seed\n            );\n        }\n\n        if writer != input_data {\n            println!(\n                \"Original size: {}, Re-coded: {}\",\n                input_data.len(),\n                writer.len()\n            );\n\n            fs::write(\"r_corrupted_input.jpg\", &input_data).unwrap();\n            fs::write(\"r_cpp_lepton.lep\", &cpp_lepton_data).unwrap();\n            fs::write(\"r_rust_recorded.jpg\", &writer).unwrap();\n\n            panic!(\n                \"Verification failed for file {}: output does not match original {} {} seed:{:?}\",\n                input_filename.to_string_lossy(),\n                exit_code,\n                stderr,\n                corruption_seed\n            );\n        }\n\n        log::info!(\"Verified file: {}\", input_filename.to_string_lossy());\n    }\n\n    _ = std::fs::remove_file(&temp_filename_input);\n    _ = std::fs::remove_file(&temp_filename_output);\n}\n"
  }
]