diff --git a/.github/workflows/bench-native-smoke.yml b/.github/workflows/bench-native-smoke.yml index 26a53f16c2..a5233f7bfb 100644 --- a/.github/workflows/bench-native-smoke.yml +++ b/.github/workflows/bench-native-smoke.yml @@ -51,7 +51,7 @@ jobs: working-directory: packages/core run: | set -euo pipefail - output="$(bun run bench:native --mem --filter "UTF-8 Operations" --bench "isAsciiOnly: ASCII text (1KB)" --json)" + output="$(bun run bench:native --mem --filter "Terminal Image" --bench "Kitty cover placement" --json)" printf '%s\n' "$output" OUTPUT="$output" bun -e ' const output = process.env.OUTPUT ?? "" @@ -67,13 +67,17 @@ jobs: if (!jsonLine) throw new Error("Native benchmark smoke test produced no JSON output") const data = JSON.parse(jsonLine) - if (data.benchmark !== "UTF-8 Operations") { + if (data.benchmark !== "Terminal Image") { throw new Error(`Unexpected benchmark category: ${data.benchmark}`) } if (!Array.isArray(data.results) || data.results.length !== 1) { throw new Error(`Expected exactly one benchmark result, got ${data.results?.length ?? 0}`) } - if (data.results[0].name !== "isAsciiOnly: ASCII text (1KB)") { + const result = data.results[0] + if (result.name !== "Kitty cover placement") { throw new Error(`Unexpected benchmark result: ${data.results[0].name}`) } + if (!Number.isFinite(result.stddev_ns) || !Number.isFinite(result.rme_95)) { + throw new Error(`Benchmark result is missing confidence fields: ${JSON.stringify(result)}`) + } ' diff --git a/.github/workflows/build-examples.yml b/.github/workflows/build-examples.yml index 0d4f72f343..c13adafacd 100644 --- a/.github/workflows/build-examples.yml +++ b/.github/workflows/build-examples.yml @@ -125,12 +125,24 @@ jobs: echo "" ls -lah packages/examples/dist/*/ + - name: Stage example license notices + run: | + set -e + mkdir -p artifacts/example-notices + cp LICENSE artifacts/example-notices/LICENSE + cp packages/core/src/zig/vendor/wuffs/LICENSE artifacts/example-notices/LICENSE-WUFFS + cp packages/core/src/zig/vendor/stb/LICENSE artifacts/example-notices/LICENSE-STB + cp packages/core/src/zig/vendor/libwebp/COPYING artifacts/example-notices/LICENSE-LIBWEBP + cp packages/core/src/zig/vendor/libwebp/PATENTS artifacts/example-notices/PATENTS-LIBWEBP + cp packages/core/src/zig/vendor/libwebp/AUTHORS artifacts/example-notices/AUTHORS-LIBWEBP + # Create separate zips for each platform's examples - name: Package examples - darwin-x64 run: | set -e mkdir -p artifacts/examples-darwin-x64 cp packages/examples/dist/darwin-x64/opentui-examples artifacts/examples-darwin-x64/ + cp artifacts/example-notices/* artifacts/examples-darwin-x64/ cd artifacts zip -r examples-darwin-x64.zip examples-darwin-x64/ test -f examples-darwin-x64.zip || (echo "❌ Failed to create darwin-x64 zip" && exit 1) @@ -142,6 +154,7 @@ jobs: set -e mkdir -p artifacts/examples-darwin-arm64 cp packages/examples/dist/darwin-arm64/opentui-examples artifacts/examples-darwin-arm64/ + cp artifacts/example-notices/* artifacts/examples-darwin-arm64/ cd artifacts zip -r examples-darwin-arm64.zip examples-darwin-arm64/ test -f examples-darwin-arm64.zip || (echo "❌ Failed to create darwin-arm64 zip" && exit 1) @@ -153,6 +166,7 @@ jobs: set -e mkdir -p artifacts/examples-linux-x64 cp packages/examples/dist/linux-x64/opentui-examples artifacts/examples-linux-x64/ + cp artifacts/example-notices/* artifacts/examples-linux-x64/ cd artifacts zip -r examples-linux-x64.zip examples-linux-x64/ test -f examples-linux-x64.zip || (echo "❌ Failed to create linux-x64 zip" && exit 1) @@ -164,6 +178,7 @@ jobs: set -e mkdir -p artifacts/examples-windows-x64 cp packages/examples/dist/windows-x64/opentui-examples.exe artifacts/examples-windows-x64/ + cp artifacts/example-notices/* artifacts/examples-windows-x64/ cd artifacts zip -r examples-windows-x64.zip examples-windows-x64/ test -f examples-windows-x64.zip || (echo "❌ Failed to create windows-x64 zip" && exit 1) @@ -178,6 +193,13 @@ jobs: test -f artifacts/examples-darwin-arm64.zip || (echo "❌ examples-darwin-arm64.zip missing!" && exit 1) test -f artifacts/examples-linux-x64.zip || (echo "❌ examples-linux-x64.zip missing!" && exit 1) test -f artifacts/examples-windows-x64.zip || (echo "❌ examples-windows-x64.zip missing!" && exit 1) + for directory in artifacts/examples-{darwin-x64,darwin-arm64,linux-x64,windows-x64}; do + test -f "$directory/LICENSE-LIBWEBP" || (echo "❌ libwebp notice missing from $directory" && exit 1) + test -f "$directory/PATENTS-LIBWEBP" || (echo "❌ libwebp patents missing from $directory" && exit 1) + test -f "$directory/AUTHORS-LIBWEBP" || (echo "❌ libwebp authors missing from $directory" && exit 1) + test -f "$directory/LICENSE-STB" || (echo "❌ stb notice missing from $directory" && exit 1) + test -f "$directory/LICENSE-WUFFS" || (echo "❌ Wuffs notice missing from $directory" && exit 1) + done echo "" echo "✅ All artifacts verified. Ready to upload:" diff --git a/.github/workflows/build-native.yml b/.github/workflows/build-native.yml index 3e2a375d50..acdcc43856 100644 --- a/.github/workflows/build-native.yml +++ b/.github/workflows/build-native.yml @@ -101,12 +101,24 @@ jobs: echo "✅ All required build outputs verified (8 native targets, 7 package dists)" + - name: Stage native license notices + run: | + set -e + mkdir -p artifacts/native-notices + cp LICENSE artifacts/native-notices/LICENSE + cp packages/core/src/zig/vendor/wuffs/LICENSE artifacts/native-notices/LICENSE-WUFFS + cp packages/core/src/zig/vendor/stb/LICENSE artifacts/native-notices/LICENSE-STB + cp packages/core/src/zig/vendor/libwebp/COPYING artifacts/native-notices/LICENSE-LIBWEBP + cp packages/core/src/zig/vendor/libwebp/PATENTS artifacts/native-notices/PATENTS-LIBWEBP + cp packages/core/src/zig/vendor/libwebp/AUTHORS artifacts/native-notices/AUTHORS-LIBWEBP + # Create separate zips for each platform's native binaries - name: Package native binaries - darwin-x64 run: | set -e mkdir -p artifacts/native-darwin-x64 cp packages/core/node_modules/@opentui/core-darwin-x64/libopentui.dylib artifacts/native-darwin-x64/ + cp artifacts/native-notices/* artifacts/native-darwin-x64/ cd artifacts zip -r native-darwin-x64.zip native-darwin-x64/ test -f native-darwin-x64.zip || (echo "❌ Failed to create darwin-x64 zip" && exit 1) @@ -118,6 +130,7 @@ jobs: set -e mkdir -p artifacts/native-darwin-arm64 cp packages/core/node_modules/@opentui/core-darwin-arm64/libopentui.dylib artifacts/native-darwin-arm64/ + cp artifacts/native-notices/* artifacts/native-darwin-arm64/ cd artifacts zip -r native-darwin-arm64.zip native-darwin-arm64/ test -f native-darwin-arm64.zip || (echo "❌ Failed to create darwin-arm64 zip" && exit 1) @@ -129,6 +142,7 @@ jobs: set -e mkdir -p artifacts/native-linux-x64 cp packages/core/node_modules/@opentui/core-linux-x64/libopentui.so artifacts/native-linux-x64/ + cp artifacts/native-notices/* artifacts/native-linux-x64/ cd artifacts zip -r native-linux-x64.zip native-linux-x64/ test -f native-linux-x64.zip || (echo "❌ Failed to create linux-x64 zip" && exit 1) @@ -140,6 +154,7 @@ jobs: set -e mkdir -p artifacts/native-linux-x64-musl cp packages/core/node_modules/@opentui/core-linux-x64-musl/libopentui.so artifacts/native-linux-x64-musl/ + cp artifacts/native-notices/* artifacts/native-linux-x64-musl/ cd artifacts zip -r native-linux-x64-musl.zip native-linux-x64-musl/ test -f native-linux-x64-musl.zip || (echo "❌ Failed to create linux-x64-musl zip" && exit 1) @@ -151,6 +166,7 @@ jobs: set -e mkdir -p artifacts/native-windows-x64 cp packages/core/node_modules/@opentui/core-win32-x64/opentui.dll artifacts/native-windows-x64/ + cp artifacts/native-notices/* artifacts/native-windows-x64/ cd artifacts zip -r native-windows-x64.zip native-windows-x64/ test -f native-windows-x64.zip || (echo "❌ Failed to create windows-x64 zip" && exit 1) @@ -162,6 +178,7 @@ jobs: set -e mkdir -p artifacts/native-linux-arm64 cp packages/core/node_modules/@opentui/core-linux-arm64/libopentui.so artifacts/native-linux-arm64/ + cp artifacts/native-notices/* artifacts/native-linux-arm64/ cd artifacts zip -r native-linux-arm64.zip native-linux-arm64/ test -f native-linux-arm64.zip || (echo "❌ Failed to create linux-arm64 zip" && exit 1) @@ -173,6 +190,7 @@ jobs: set -e mkdir -p artifacts/native-linux-arm64-musl cp packages/core/node_modules/@opentui/core-linux-arm64-musl/libopentui.so artifacts/native-linux-arm64-musl/ + cp artifacts/native-notices/* artifacts/native-linux-arm64-musl/ cd artifacts zip -r native-linux-arm64-musl.zip native-linux-arm64-musl/ test -f native-linux-arm64-musl.zip || (echo "❌ Failed to create linux-arm64-musl zip" && exit 1) @@ -184,6 +202,7 @@ jobs: set -e mkdir -p artifacts/native-windows-arm64 cp packages/core/node_modules/@opentui/core-win32-arm64/opentui.dll artifacts/native-windows-arm64/ + cp artifacts/native-notices/* artifacts/native-windows-arm64/ cd artifacts zip -r native-windows-arm64.zip native-windows-arm64/ test -f native-windows-arm64.zip || (echo "❌ Failed to create windows-arm64 zip" && exit 1) @@ -273,6 +292,13 @@ jobs: test -f artifacts/native-windows-x64.zip || (echo "❌ native-windows-x64.zip missing!" && exit 1) test -f artifacts/native-windows-arm64.zip || (echo "❌ native-windows-arm64.zip missing!" && exit 1) test -f artifacts/npm-packages.zip || (echo "❌ npm-packages.zip missing!" && exit 1) + for directory in artifacts/native-{darwin-x64,darwin-arm64,linux-x64,linux-arm64,linux-x64-musl,linux-arm64-musl,windows-x64,windows-arm64}; do + test -f "$directory/LICENSE-LIBWEBP" || (echo "❌ libwebp notice missing from $directory" && exit 1) + test -f "$directory/PATENTS-LIBWEBP" || (echo "❌ libwebp patents missing from $directory" && exit 1) + test -f "$directory/AUTHORS-LIBWEBP" || (echo "❌ libwebp authors missing from $directory" && exit 1) + test -f "$directory/LICENSE-STB" || (echo "❌ stb notice missing from $directory" && exit 1) + test -f "$directory/LICENSE-WUFFS" || (echo "❌ Wuffs notice missing from $directory" && exit 1) + done echo "" echo "✅ All 8 native artifacts verified. Ready to upload:" diff --git a/packages/core/package.json b/packages/core/package.json index a27705bf97..a99921ff88 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -24,6 +24,7 @@ "build:lib": "bun scripts/build.ts --lib", "build:native": "bun scripts/build.ts --native", "build:native:dev": "bun scripts/build.ts --native --dev", + "vendor:update:images": "sh src/zig/vendor/update.sh", "test:native": "cd src/zig && zig build test --summary all", "bench:native": "cd src/zig && zig build bench -Dbench-optimize=ReleaseFast --", "bench:layout": "bun src/benchmark/layout-benchmark.ts", diff --git a/packages/core/scripts/build.ts b/packages/core/scripts/build.ts index 089013b926..9a538f0062 100644 --- a/packages/core/scripts/build.ts +++ b/packages/core/scripts/build.ts @@ -292,6 +292,16 @@ export default module.default ) if (existsSync(licensePath)) copyFileSync(licensePath, join(nativeDir, "LICENSE")) + for (const [source, destination] of [ + [join(rootDir, "src", "zig", "vendor", "wuffs", "LICENSE"), "LICENSE-WUFFS"], + [join(rootDir, "src", "zig", "vendor", "stb", "LICENSE"), "LICENSE-STB"], + [join(rootDir, "src", "zig", "vendor", "libwebp", "COPYING"), "LICENSE-LIBWEBP"], + [join(rootDir, "src", "zig", "vendor", "libwebp", "PATENTS"), "PATENTS-LIBWEBP"], + [join(rootDir, "src", "zig", "vendor", "libwebp", "AUTHORS"), "AUTHORS-LIBWEBP"], + ] as const) { + if (!existsSync(source)) throw new Error(`Required native image license file is missing: ${source}`) + copyFileSync(source, join(nativeDir, destination)) + } console.log("Built:", nativeName) } } diff --git a/packages/core/scripts/dist-test.ts b/packages/core/scripts/dist-test.ts index 107fba9343..158c297644 100644 --- a/packages/core/scripts/dist-test.ts +++ b/packages/core/scripts/dist-test.ts @@ -205,6 +205,9 @@ function writeConsumerPackage(consumerDir: string, coreTarball: string, nativeTa [packageJson.name]: coreDependency, [nativePackageName]: nativeDependency, }, + overrides: { + [nativePackageName]: nativeDependency, + }, }, null, 2, @@ -236,6 +239,11 @@ assert.equal(typeof core.AudioCaptureStreamError, "function") assert.equal(typeof core.AudioRecorder, "function") assert.equal(typeof core.AudioRecorderError, "function") assert.equal(typeof core.AudioStreamError, "function") +assert.equal(typeof core.NativeImage, "function") +assert.equal(typeof core.ImageRenderable, "function") +assert.equal(typeof core.createHostClipboard, "function") +assert.equal(typeof core.createClipboard, "function") +assert.equal(typeof core.createRendererClipboardAdapter, "function") assert.equal(typeof core.Audio.prototype.openCapture, "function") assert.equal(typeof core.Audio.prototype.recordToFile, "function") assert.equal(typeof core.createIcyStreamDemuxer, "function") @@ -263,6 +271,22 @@ const buffer = core.OptimizedBuffer.create(2, 1, "unicode") assert.equal(buffer.width, 2) buffer.destroy() +const image = core.NativeImage.fromRgba(Uint8Array.of(1, 2, 3, 255), 1, 1) +const raw = image.takeRaw() +try { + assert.deepEqual([...raw.data], [1, 2, 3, 255]) + assert.throws(() => image.info(), /disposed/) +} finally { + raw.dispose() +} + +const clipboard = core.createHostClipboard({ timeoutMs: 0 }) +try { + assert.equal((await clipboard.read({ preferredTypes: ["text/plain"] })).status, "timed-out") +} finally { + await clipboard.dispose() +} + const dataPath = mkdtempSync(join(tmpdir(), "opentui-node-dist-tree-sitter-")) const client = new core.TreeSitterClient({ dataPath }) try { @@ -341,6 +365,11 @@ describe("${packageJson.name} dist smoke test", () => { expect(typeof core.AudioRecorder).toBe("function") expect(typeof core.AudioRecorderError).toBe("function") expect(typeof core.AudioStreamError).toBe("function") + expect(typeof core.NativeImage).toBe("function") + expect(typeof core.ImageRenderable).toBe("function") + expect(typeof core.createHostClipboard).toBe("function") + expect(typeof core.createClipboard).toBe("function") + expect(typeof core.createRendererClipboardAdapter).toBe("function") expect(typeof core.Audio.prototype.openCapture).toBe("function") expect(typeof core.Audio.prototype.recordToFile).toBe("function") expect(core.NativeAudioStreamCloseReason.TransportError).toBe(1) @@ -351,6 +380,22 @@ describe("${packageJson.name} dist smoke test", () => { expect(typeof parserWorker).toBe("object") expect(typeof runtimePlugin.createRuntimePlugin).toBe("function") expect(typeof nativePackage.default).toBe("string") + + const image = core.NativeImage.fromRgba(Uint8Array.of(1, 2, 3, 255), 1, 1) + const raw = image.takeRaw() + try { + expect([...raw.data]).toEqual([1, 2, 3, 255]) + expect(() => image.info()).toThrow(/disposed/) + } finally { + raw.dispose() + } + + const clipboard = core.createHostClipboard({ timeoutMs: 0 }) + try { + expect((await clipboard.read({ preferredTypes: ["text/plain"] })).status).toBe("timed-out") + } finally { + await clipboard.dispose() + } }) }) `, diff --git a/packages/core/scripts/test-node.ts b/packages/core/scripts/test-node.ts index febe221e14..5dfcb444d5 100644 --- a/packages/core/scripts/test-node.ts +++ b/packages/core/scripts/test-node.ts @@ -17,6 +17,7 @@ const treeSitterMarkdownRenderableTestDataPath = resolve(tmpdir(), "tree-sitter- const textBufferTestDataPath = resolve(tmpdir(), "text-buffer-node-test") const runtimeAssetTestDataPath = resolve(tmpdir(), "opentui-runtime-asset-node-test") const audioRecorderTestDataPath = resolve(tmpdir(), "opentui-audio-recorder-node-test") +const imageTestDataPath = resolve(tmpdir(), "opentui-image-node-test") const treeSitterClientTestDataPaths = [ "tree-sitter-shared-test-data", "tree-sitter-injections-test-data", @@ -32,10 +33,12 @@ const treeSitterTestDataPaths = [ textBufferTestDataPath, runtimeAssetTestDataPath, audioRecorderTestDataPath, + imageTestDataPath, ...treeSitterClientTestDataPaths, ] const treeSitterAssetsDir = "src/lib/tree-sitter/assets" const audioFixturesDir = "src/tests/fixtures/audio" +const imageFixturesDir = "src/tests/fixtures/images" const nodeTestTimeoutMs = 30_000 const nodeProcessTimeoutMs = 10 * 60_000 const nodePath = requireNode26() @@ -46,6 +49,9 @@ const emittedAllowlist = [ ".node-test/src/lib/bunfs.test.js", ".node-test/src/lib/border.test.js", ".node-test/src/lib/clipboard.test.js", + ".node-test/src/lib/clipboard-service.test.js", + ".node-test/src/lib/host-clipboard.test.js", + ".node-test/src/lib/host-clipboard.native.scheduler.test.js", ".node-test/src/lib/extmarks.test.js", ".node-test/src/lib/detect-links.test.js", ".node-test/src/lib/extmarks-multiwidth.test.js", @@ -126,7 +132,10 @@ const emittedAllowlist = [ ".node-test/src/tests/renderable.snapshot.test.js", ".node-test/src/tests/allocator-stats.test.js", ".node-test/src/tests/audio-stream.test.js", + ".node-test/src/tests/clipboard-native-lifecycle.test.js", ".node-test/src/tests/audio.test.js", + ".node-test/src/tests/image-renderable.test.js", + ".node-test/src/tests/image.test.js", ".node-test/src/tests/destroy-on-exit.test.js", ".node-test/src/tests/destroy-during-render.test.js", ".node-test/src/tests/ffi-borrowed-pointer-callsites.test.js", @@ -195,6 +204,7 @@ try { if (exitCode === 0) { cpSync(resolve(packageRoot, treeSitterAssetsDir), resolve(outDir, treeSitterAssetsDir), { recursive: true }) cpSync(resolve(packageRoot, audioFixturesDir), resolve(outDir, audioFixturesDir), { recursive: true }) + cpSync(resolve(packageRoot, imageFixturesDir), resolve(outDir, imageFixturesDir), { recursive: true }) for (const dataPath of treeSitterTestDataPaths) { mkdirSync(dataPath, { recursive: true }) } @@ -226,6 +236,7 @@ try { OTUI_TEXT_BUFFER_TEST_TMPDIR: textBufferTestDataPath, OTUI_RUNTIME_ASSET_TEST_TMPDIR: runtimeAssetTestDataPath, OTUI_AUDIO_RECORDER_TEST_TMPDIR: audioRecorderTestDataPath, + OTUI_IMAGE_TEST_TMPDIR: imageTestDataPath, XDG_DATA_HOME: treeSitterDefaultDataPath, }, timeout: nodeProcessTimeoutMs, diff --git a/packages/core/src/buffer.test.ts b/packages/core/src/buffer.test.ts index 6dcf88c92a..a3cc77f886 100644 --- a/packages/core/src/buffer.test.ts +++ b/packages/core/src/buffer.test.ts @@ -1,6 +1,7 @@ import { describe, expect, it, beforeEach, afterEach } from "bun:test" import { OptimizedBuffer } from "./buffer.js" import { RGBA } from "./lib/RGBA.js" +import { NativeImage } from "./image.js" describe("OptimizedBuffer", () => { let buffer: OptimizedBuffer @@ -13,6 +14,69 @@ describe("OptimizedBuffer", () => { buffer.destroy() }) + it("draws images as reserved cells with resolved fallback glyphs", () => { + const image = NativeImage.fromRgba( + Uint8Array.of(255, 0, 0, 255, 0, 255, 0, 255, 0, 0, 255, 255, 255, 255, 255, 255), + 2, + 2, + ) + try { + expect(buffer.drawImage(image, 0, 0, 1, 1)).toBe(true) + const marker = buffer.buffers.char[0] + expect(marker >>> 30).toBe(1) + expect(new TextDecoder().decode(buffer.getRealCharBytes())).not.toContain("�") + buffer.setCell(0, 0, "X", RGBA.fromInts(255, 255, 255), RGBA.fromInts(0, 0, 0)) + expect(buffer.buffers.char[0]).toBe("X".codePointAt(0)!) + } finally { + image.dispose() + } + }) + + it("retains drawn images until the buffer releases them", () => { + const image = NativeImage.fromRgba(Uint8Array.of(1, 2, 3, 255), 1, 1) + let raw: ReturnType | undefined + try { + expect(buffer.drawImage(image, 0, 0, 1, 1)).toBe(true) + expect(() => image.takeRaw()).toThrow("native buffers retain the image") + + buffer.destroy() + raw = image.takeRaw() + expect([...raw.data]).toEqual([1, 2, 3, 255]) + } finally { + raw?.dispose() + image.dispose() + } + }) + + it("releases drawn images when cleared", () => { + const image = NativeImage.fromRgba(Uint8Array.of(1, 2, 3, 255), 1, 1) + let raw: ReturnType | undefined + try { + expect(buffer.drawImage(image, 0, 0, 1, 1)).toBe(true) + expect(() => image.takeRaw()).toThrow("native buffers retain the image") + + buffer.clear() + raw = image.takeRaw() + expect([...raw.data]).toEqual([1, 2, 3, 255]) + } finally { + raw?.dispose() + image.dispose() + } + }) + + it("rejects invalid image draw geometry before FFI", () => { + const image = NativeImage.fromRgba(Uint8Array.of(1, 2, 3, 255), 1, 1) + try { + expect(() => buffer.drawImage(image, 0, 0, Number.POSITIVE_INFINITY, 1)).toThrow(RangeError) + expect(() => buffer.drawImage(image, 0, 0, -1, 1)).toThrow(RangeError) + expect(() => buffer.drawImage(image, 0.5, 0, 1, 1)).toThrow(RangeError) + expect(() => buffer.drawImage(image, 0, 0, 0x80000000, 1)).toThrow(RangeError) + expect(() => buffer.drawImage(image, 0x7fffffff, 0, 1, 1)).toThrow(RangeError) + } finally { + image.dispose() + } + }) + describe("encodeUnicode", () => { it("should encode simple ASCII text", () => { const encoded = buffer.encodeUnicode("Hello") diff --git a/packages/core/src/buffer.ts b/packages/core/src/buffer.ts index f8e7669a73..fe5c5ffbe4 100644 --- a/packages/core/src/buffer.ts +++ b/packages/core/src/buffer.ts @@ -1,6 +1,14 @@ import { RGBA } from "./lib/index.js" import { resolveRenderLib, type OptimizedBufferHandle, type RenderLib } from "./zig.js" import { type Pointer, type PointerInput, toArrayBuffer, toPointer, ptr } from "./platform/ffi.js" +import type { NativeImage } from "./image.js" +import type { ImageRenderProtocol } from "./types.js" + +function requireInteger(value: number, name: string, min: number, max: number): void { + if (!Number.isSafeInteger(value) || value < min || value > max) { + throw new RangeError(`${name} must be an integer from ${min} to ${max}`) + } +} import { type BorderStyle, type BorderSides, BorderCharArrays, parseBorderStyle } from "./lib/index.js" import { TargetChannel, type WidthMethod, type CapturedSpan, type CapturedLine } from "./types.js" import type { TextBufferView } from "./text-buffer-view.js" @@ -372,6 +380,51 @@ export class OptimizedBuffer { ) } + public drawImage( + image: NativeImage, + x: number, + y: number, + width: number, + height: number, + pixelWidth: number = 0, + pixelHeight: number = 0, + sourceX: number = 0, + sourceY: number = 0, + sourceWidth: number = image.width, + sourceHeight: number = image.height, + protocol: ImageRenderProtocol = "auto", + ): boolean { + this.guard() + requireInteger(x, "x", -0x80000000, 0x7fffffff) + requireInteger(y, "y", -0x80000000, 0x7fffffff) + requireInteger(width, "width", 1, 0x7fffffff) + requireInteger(height, "height", 1, 0x7fffffff) + requireInteger(pixelWidth, "pixelWidth", 0, 0x7fffffff) + requireInteger(pixelHeight, "pixelHeight", 0, 0x7fffffff) + requireInteger(sourceX, "sourceX", 0, 0xffffffff) + requireInteger(sourceY, "sourceY", 0, 0xffffffff) + requireInteger(sourceWidth, "sourceWidth", 1, 0xffffffff) + requireInteger(sourceHeight, "sourceHeight", 1, 0xffffffff) + if (x + width > 0x7fffffff || y + height > 0x7fffffff) { + throw new RangeError("image destination coordinates and dimensions exceed i32 bounds") + } + return this.lib.bufferDrawImage( + this.bufferPtr, + image.ptr, + x, + y, + width, + height, + pixelWidth, + pixelHeight, + sourceX, + sourceY, + sourceWidth, + sourceHeight, + protocol, + ) + } + public drawPackedBuffer( dataPtr: PointerInput, dataLen: number, diff --git a/packages/core/src/image.ts b/packages/core/src/image.ts new file mode 100644 index 0000000000..eff5a3b3df --- /dev/null +++ b/packages/core/src/image.ts @@ -0,0 +1,597 @@ +import { open, stat } from "node:fs/promises" + +import { toArrayBuffer } from "./platform/ffi.js" +import { resolveRenderLib, type ImageHandle, type RenderLib } from "./zig.js" +import type { NativeImageInfo } from "./zig-structs.js" + +export type ImageFormat = "png" | "raw-rgba" | "jpeg" | "webp" | "gif" +export type ImageColorStatus = "assumed-srgb" | "explicit-srgb" +export type ResizeKernel = "default" | "area" | "triangle" | "cubic-bspline" | "catmull-rom" | "mitchell" | "nearest" +export type BlendMode = "source-over" | "source" | "destination-over" +export type PixelFormat = "rgba8" | "bgra8" +export type ImageSource = string | URL | Uint8Array | ArrayBuffer | Blob | Response + +export type ImageLoadErrorCode = "file-read" | "network" | "http-status" | "unsupported-url-scheme" + +export class ImageLoadError extends Error { + public readonly code: ImageLoadErrorCode + public readonly source: string + public readonly status?: number + + constructor( + code: ImageLoadErrorCode, + source: string, + message: string, + options?: { cause?: unknown; status?: number }, + ) { + super(message, { cause: options?.cause }) + this.name = "ImageLoadError" + this.code = code + this.source = source + this.status = options?.status + } +} + +export interface ImageLoadOptions { + signal?: AbortSignal + fetch?: (input: URL, init?: RequestInit) => Promise +} + +export interface ImageInfo { + width: number + height: number + sourceWidth: number + sourceHeight: number + format: ImageFormat + colorStatus: ImageColorStatus + orientation: number + hasAlpha: boolean +} + +export interface ResizeOptions { + width?: number + height?: number + kernel?: ResizeKernel +} + +export interface ExtractOptions { + left: number + top: number + width: number + height: number +} + +export interface ExtendOptions { + top?: number + right?: number + bottom?: number + left?: number + background?: readonly [number, number, number, number] +} + +export interface CompositeOptions { + left?: number + top?: number + blend?: BlendMode + opacity?: number +} + +export interface RawImage { + data: Uint8Array + width: number + height: number + stride: number + format: PixelFormat + colorSpace: "srgb" + alpha: "straight" +} + +export interface OwnedRawImage extends RawImage { + dispose(): void +} + +class OwnedRawImageImpl implements OwnedRawImage { + private handle: ImageHandle | null + + public readonly format = "rgba8" + public readonly colorSpace = "srgb" + public readonly alpha = "straight" + + constructor( + public readonly data: Uint8Array, + public readonly width: number, + public readonly height: number, + public readonly stride: number, + private readonly lib: RenderLib, + handle: ImageHandle, + ) { + this.handle = handle + } + + public dispose(): void { + if (!this.handle) return + this.lib.imageDestroy(this.handle) + this.handle = null + } +} + +const STATUS_MESSAGES = [ + "ok", + "invalid image handle", + "unsupported image format", + "unsupported image color space", + "malformed image data", + "image dimensions exceed limits", + "image memory limit exceeded", + "invalid image argument", + "out of memory", + "image output buffer is too small", + "internal image error", + "unsupported image feature", +] as const + +export type ImageErrorCode = + | "invalid-handle" + | "unsupported-format" + | "unsupported-color-space" + | "malformed-data" + | "dimension-limit" + | "memory-limit" + | "invalid-argument" + | "out-of-memory" + | "output-too-small" + | "internal-error" + | "unsupported-feature" + +const STATUS_CODES: readonly ImageErrorCode[] = [ + "internal-error", + "invalid-handle", + "unsupported-format", + "unsupported-color-space", + "malformed-data", + "dimension-limit", + "memory-limit", + "invalid-argument", + "out-of-memory", + "output-too-small", + "internal-error", + "unsupported-feature", +] + +export class ImageError extends Error { + public readonly code: ImageErrorCode + public readonly status: number + + constructor(status: number) { + super(`Native image operation failed: ${STATUS_MESSAGES[status] ?? `unknown status ${status}`}`) + this.name = "ImageError" + this.status = status + this.code = STATUS_CODES[status] ?? "internal-error" + } +} + +const FILTER_IDS: Record = { + default: 0, + area: 1, + triangle: 2, + "cubic-bspline": 3, + "catmull-rom": 4, + mitchell: 5, + nearest: 6, +} + +const BLEND_IDS: Record = { + "source-over": 0, + source: 1, + "destination-over": 2, +} + +const PIXEL_FORMAT_BGRA: Record = { + rgba8: false, + bgra8: true, +} + +const MAX_ENCODED_BYTES = 64 * 1024 * 1024 + +function imageError(status: number): Error { + return new ImageError(status) +} + +function checkStatus(status: number): void { + if (status !== 0) throw imageError(status) +} + +function requireMappedOption(mapping: Record, value: T, name: string): V { + if (!Object.prototype.hasOwnProperty.call(mapping, value)) + throw new TypeError(`Unsupported ${name}: ${String(value)}`) + return mapping[value] +} + +function requireU32(value: number, name: string, allowZero = false): number { + if (!Number.isSafeInteger(value) || value < (allowZero ? 0 : 1) || value > 0xffff_ffff) { + throw new RangeError(`${name} must be ${allowZero ? "a non-negative" : "a positive"} u32 integer`) + } + return value +} + +function requireI32(value: number, name: string): number { + if (!Number.isSafeInteger(value) || value < -0x8000_0000 || value > 0x7fff_ffff) { + throw new RangeError(`${name} must be an i32 integer`) + } + return value +} + +function requireByte(value: number, name: string): number { + if (!Number.isInteger(value) || value < 0 || value > 255) + throw new RangeError(`${name} must be an integer from 0 to 255`) + return value +} + +function unpackInfo(info: NativeImageInfo): ImageInfo { + const format = (["unknown", "png", "raw-rgba", "jpeg", "webp", "gif"] as const)[info.format] + if (!format || format === "unknown") throw new Error(`Unknown native image format ${info.format}`) + return { + width: info.width, + height: info.height, + sourceWidth: info.sourceWidth, + sourceHeight: info.sourceHeight, + format, + colorStatus: info.colorStatus === 1 ? "explicit-srgb" : "assumed-srgb", + orientation: info.orientation, + hasAlpha: info.hasAlpha !== 0, + } +} + +function encodedBytes(data: Uint8Array | ArrayBuffer): Uint8Array { + if (data instanceof Uint8Array) return data + if (data instanceof ArrayBuffer) return new Uint8Array(data) + throw new TypeError("image data must be a Uint8Array or ArrayBuffer") +} + +async function readResponseBytes(response: Response, signal?: AbortSignal): Promise { + const contentLength = response.headers.get("content-length") + if (contentLength !== null) { + const declaredLength = Number(contentLength) + if (Number.isFinite(declaredLength) && declaredLength > MAX_ENCODED_BYTES) { + void response.body?.cancel().catch(() => {}) + throw imageError(6) + } + } + + if (!response.body) return new Uint8Array() + const reader = response.body.getReader() + const abort = () => void reader.cancel(signal?.reason).catch(() => {}) + signal?.addEventListener("abort", abort, { once: true }) + let data = new Uint8Array() + let total = 0 + try { + while (true) { + signal?.throwIfAborted() + const { done, value } = await reader.read() + if (done) break + if (value.byteLength > MAX_ENCODED_BYTES - total) { + throw imageError(6) + } + if (value.byteLength === 0) continue + const required = total + value.byteLength + if (required > data.byteLength) { + const capacity = Math.min(MAX_ENCODED_BYTES, Math.max(required, data.byteLength * 2)) + const grown = new Uint8Array(capacity) + grown.set(data.subarray(0, total)) + data = grown + } + data.set(value, total) + total = required + } + } catch (error) { + void reader.cancel().catch(() => {}) + throw error + } finally { + signal?.removeEventListener("abort", abort) + reader.releaseLock() + } + + return data.byteLength === total ? data : data.slice(0, total) +} + +async function readFileBytes(path: string | URL, signal?: AbortSignal): Promise { + signal?.throwIfAborted() + if ((await stat(path)).size > MAX_ENCODED_BYTES) throw imageError(6) + + const file = await open(path, "r") + const chunks: Uint8Array[] = [] + let total = 0 + try { + while (true) { + signal?.throwIfAborted() + const chunk = new Uint8Array(Math.min(64 * 1024, MAX_ENCODED_BYTES - total + 1)) + const { bytesRead } = await file.read(chunk, 0, chunk.byteLength, null) + if (bytesRead === 0) break + total += bytesRead + if (total > MAX_ENCODED_BYTES) throw imageError(6) + chunks.push(chunk.subarray(0, bytesRead)) + } + } finally { + await file.close() + } + + const data = new Uint8Array(total) + let offset = 0 + for (const chunk of chunks) { + data.set(chunk, offset) + offset += chunk.byteLength + } + return data +} + +async function loadResponseBytes(response: Response, source: string, signal?: AbortSignal): Promise { + try { + signal?.throwIfAborted() + } catch (error) { + void response.body?.cancel().catch(() => {}) + throw error + } + if (!response.ok) { + void response.body?.cancel().catch(() => {}) + throw new ImageLoadError("http-status", source, `Failed to fetch image: HTTP ${response.status}`, { + status: response.status, + }) + } + try { + const data = await readResponseBytes(response, signal) + signal?.throwIfAborted() + return data + } catch (error) { + if (signal?.aborted) throw signal.reason + if (error instanceof ImageError) throw error + throw new ImageLoadError("network", source, `Failed to read image response: ${source}`, { cause: error }) + } +} + +export function imageInfo(data: Uint8Array | ArrayBuffer): ImageInfo { + const bytes = encodedBytes(data) + if (bytes.byteLength === 0) throw new TypeError("image data must not be empty") + const result = resolveRenderLib().imageInfo(bytes) + checkStatus(result.status) + return unpackInfo(result.info) +} + +export class NativeImage { + private readonly lib: RenderLib + private handle: ImageHandle | null + private imageInfo: ImageInfo + + private constructor(lib: RenderLib, handle: ImageHandle, info: ImageInfo) { + this.lib = lib + this.handle = handle + this.imageInfo = info + } + + public static decode(data: Uint8Array | ArrayBuffer): NativeImage { + const bytes = encodedBytes(data) + if (bytes.byteLength === 0) throw new TypeError("image data must not be empty") + const lib = resolveRenderLib() + const result = lib.imageDecode(bytes) + checkStatus(result.status) + if (!result.handle) throw imageError(10) + return NativeImage.fromHandle(lib, result.handle) + } + + public static async load(source: ImageSource, options: ImageLoadOptions = {}): Promise { + if (source instanceof Response) { + return NativeImage.decode(await loadResponseBytes(source, source.url || "Response", options.signal)) + } + options.signal?.throwIfAborted() + if (source instanceof Uint8Array || source instanceof ArrayBuffer) return NativeImage.decode(source) + if (source instanceof Blob) { + if (source.size > MAX_ENCODED_BYTES) throw imageError(6) + return NativeImage.decode(await loadResponseBytes(new Response(source), "Blob", options.signal)) + } + + const url = + source instanceof URL + ? source + : (/^(?:https?|file|blob|data):/i.test(source) || /^[a-z][a-z0-9+.-]*:\/\//i.test(source)) && + !/^[a-z]:[\\/]/i.test(source) + ? new URL(source) + : null + if (!url || url.protocol === "file:") { + const path = url ?? source + let data: Uint8Array + try { + data = await readFileBytes(path, options.signal) + } catch (error) { + if (options.signal?.aborted) throw options.signal.reason + if (error instanceof ImageError) throw error + throw new ImageLoadError("file-read", String(source), `Failed to read image: ${String(source)}`, { + cause: error, + }) + } + options.signal?.throwIfAborted() + return NativeImage.decode(data) + } + + if (url.protocol !== "http:" && url.protocol !== "https:" && url.protocol !== "blob:" && url.protocol !== "data:") { + throw new ImageLoadError("unsupported-url-scheme", url.href, `Unsupported image URL scheme: ${url.protocol}`) + } + + let response: Response + try { + response = await (options.fetch ?? globalThis.fetch)(url, { signal: options.signal }) + } catch (error) { + if (options.signal?.aborted) throw options.signal.reason + throw new ImageLoadError("network", url.href, `Failed to fetch image: ${url.href}`, { cause: error }) + } + return NativeImage.decode(await loadResponseBytes(response, url.href, options.signal)) + } + + public static fromRgba(pixels: Uint8Array, width: number, height: number, stride = width * 4): NativeImage { + if (!(pixels instanceof Uint8Array)) throw new TypeError("pixels must be a Uint8Array") + requireU32(width, "width") + requireU32(height, "height") + requireU32(stride, "stride") + const lib = resolveRenderLib() + const result = lib.imageCreateFromRgba(pixels, width, height, stride) + checkStatus(result.status) + if (!result.handle) throw imageError(10) + return NativeImage.fromHandle(lib, result.handle) + } + + private static fromHandle(lib: RenderLib, handle: ImageHandle): NativeImage { + const result = lib.imageGetInfo(handle) + if (result.status !== 0) { + lib.imageDestroy(handle) + throw imageError(result.status) + } + return new NativeImage(lib, handle, unpackInfo(result.info)) + } + + private guard(): ImageHandle { + if (!this.handle) throw new Error("NativeImage is disposed") + return this.handle + } + + public get ptr(): ImageHandle { + return this.guard() + } + + private wrap(result: { status: number; handle: ImageHandle | null }): NativeImage { + checkStatus(result.status) + if (!result.handle) throw imageError(10) + return NativeImage.fromHandle(this.lib, result.handle) + } + + public info(): ImageInfo { + this.guard() + return { ...this.imageInfo } + } + + public get width(): number { + this.guard() + return this.imageInfo.width + } + + public get height(): number { + this.guard() + return this.imageInfo.height + } + + public clone(): NativeImage { + return this.wrap(this.lib.imageClone(this.guard())) + } + + public resize(options: ResizeOptions): NativeImage { + if (!options || (options.width === undefined && options.height === undefined)) { + throw new TypeError("resize requires width, height, or both") + } + let width = options.width + let height = options.height + if (width !== undefined) requireU32(width, "width") + if (height !== undefined) requireU32(height, "height") + if (width === undefined) width = Math.max(1, Math.round((this.width * height!) / this.height)) + if (height === undefined) height = Math.max(1, Math.round((this.height * width) / this.width)) + requireU32(width, "width") + requireU32(height, "height") + const filter = requireMappedOption(FILTER_IDS, options.kernel ?? "area", "resize kernel") + return this.wrap(this.lib.imageResize(this.guard(), width, height, filter)) + } + + public extract(options: ExtractOptions): NativeImage { + return this.wrap( + this.lib.imageExtract( + this.guard(), + requireU32(options.left, "left", true), + requireU32(options.top, "top", true), + requireU32(options.width, "width"), + requireU32(options.height, "height"), + ), + ) + } + + public extend(options: ExtendOptions = {}): NativeImage { + const background = options.background ?? [0, 0, 0, 0] + if (background.length !== 4) throw new TypeError("background must contain four RGBA channels") + const color = Uint8Array.from(background.map((value, index) => requireByte(value, `background[${index}]`))) + return this.wrap( + this.lib.imageExtend( + this.guard(), + requireU32(options.top ?? 0, "top", true), + requireU32(options.right ?? 0, "right", true), + requireU32(options.bottom ?? 0, "bottom", true), + requireU32(options.left ?? 0, "left", true), + color, + ), + ) + } + + public rotate(angle: 90 | 180 | 270): NativeImage { + const operation = angle === 90 ? 0 : angle === 180 ? 1 : angle === 270 ? 2 : -1 + if (operation < 0) throw new RangeError("angle must be 90, 180, or 270") + return this.wrap(this.lib.imageTransform(this.guard(), operation)) + } + + public flip(): NativeImage { + return this.wrap(this.lib.imageTransform(this.guard(), 3)) + } + + public flop(): NativeImage { + return this.wrap(this.lib.imageTransform(this.guard(), 4)) + } + + public composite(overlay: NativeImage, options: CompositeOptions = {}): NativeImage { + if (!(overlay instanceof NativeImage)) throw new TypeError("overlay must be a NativeImage") + const opacity = options.opacity ?? 1 + if (!Number.isFinite(opacity) || opacity < 0 || opacity > 1) throw new RangeError("opacity must be between 0 and 1") + return this.wrap( + this.lib.imageComposite( + this.guard(), + overlay.guard(), + requireI32(options.left ?? 0, "left"), + requireI32(options.top ?? 0, "top"), + requireMappedOption(BLEND_IDS, options.blend ?? "source-over", "blend mode"), + Math.round(opacity * 255), + ), + ) + } + + public raw(format: PixelFormat = "rgba8"): RawImage { + const stride = this.width * 4 + const data = new Uint8Array(stride * this.height) + checkStatus( + this.lib.imageCopyPixels( + this.guard(), + data, + stride, + requireMappedOption(PIXEL_FORMAT_BGRA, format, "pixel format"), + ), + ) + return { data, width: this.width, height: this.height, stride, format, colorSpace: "srgb", alpha: "straight" } + } + + public takeRaw(): OwnedRawImage { + const handle = this.guard() + const pointer = this.lib.imageGetPixelsPtr(handle) + if (!pointer) throw new Error("Cannot transfer image pixels while native buffers retain the image") + const width = this.imageInfo.width + const height = this.imageInfo.height + const stride = width * 4 + const data = new Uint8Array(toArrayBuffer(pointer, 0, stride * height)) + const raw = new OwnedRawImageImpl(data, width, height, stride, this.lib, handle) + this.handle = null + return raw + } + + public copyTo(destination: Uint8Array, options: { stride?: number; format?: PixelFormat } = {}): void { + if (!(destination instanceof Uint8Array)) throw new TypeError("destination must be a Uint8Array") + const stride = options.stride ?? this.width * 4 + requireU32(stride, "stride") + const bgra = requireMappedOption(PIXEL_FORMAT_BGRA, options.format ?? "rgba8", "pixel format") + checkStatus(this.lib.imageCopyPixels(this.guard(), destination, stride, bgra)) + } + + public dispose(): void { + if (!this.handle) return + this.lib.imageDestroy(this.handle) + this.handle = null + } +} diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index 251d431925..59a4969395 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -22,6 +22,7 @@ export * from "./audio.js" export type { AudioStreamDemuxOutput, AudioStreamDemuxer, AudioStreamDemuxerFactory } from "./audio-stream/demuxer.js" export { createIcyStreamDemuxer } from "./audio-stream/icy/demuxer.js" export type { IcyStreamDemuxerOptions } from "./audio-stream/icy/demuxer.js" +export * from "./image.js" export * from "./renderables/index.js" export * from "./zig.js" export * from "./console.js" diff --git a/packages/core/src/lib/clipboard-service.test.ts b/packages/core/src/lib/clipboard-service.test.ts new file mode 100644 index 0000000000..c3b0e43494 --- /dev/null +++ b/packages/core/src/lib/clipboard-service.test.ts @@ -0,0 +1,245 @@ +import { describe, expect, it } from "bun:test" +import { + createClipboard, + createRendererClipboardAdapter, + type HostClipboardBackend, + type HostClipboardWriteOptions, + type TerminalClipboardAdapter, +} from "./clipboard.js" +import { createHostClipboardWithBackend } from "./host-clipboard.internal.js" + +const createHost = (backend: HostClipboardBackend, maxWriteBytes?: number) => + createHostClipboardWithBackend({ maxWriteBytes }, () => backend) + +const createServices = ( + options: { + remote?: boolean + hostStatus?: "written" | "unsupported" | "cancelled" + maxWriteBytes?: number + backend?: Partial + terminal?: Partial + } = {}, +) => { + const events: string[] = [] + const backend: HostClipboardBackend = { + async read() { + events.push("host-read") + return { status: "empty" } + }, + async writeText(_text, _operation) { + events.push("host-write") + return { status: options.hostStatus ?? "written" } + }, + async clear() { + events.push("host-clear") + return { status: "cleared" } + }, + async dispose() {}, + ...options.backend, + } + const terminal: TerminalClipboardAdapter = { + remote: options.remote ?? false, + writeText() { + events.push("terminal-write") + return { status: "attempted", capability: "supported" } + }, + clear() { + events.push("terminal-clear") + return { status: "attempted", capability: "supported" } + }, + ...options.terminal, + } + const clipboard = createClipboard({ + host: createHost(backend, options.maxWriteBytes), + terminal, + }) + return { clipboard, events } +} + +describe("createClipboard", () => { + it("applies local destination policies and best-available stop/fallback", async () => { + const cases = [ + ["terminal-only", undefined, "not-attempted", "attempted", ["terminal-write"]], + ["host-only", undefined, "written", "not-attempted", ["host-write"]], + ["best-available", "written", "written", "not-attempted", ["host-write"]], + ["best-available", "cancelled", "cancelled", "not-attempted", ["host-write"]], + ["best-available", "unsupported", "unsupported", "attempted", ["host-write", "terminal-write"]], + ["all-available", undefined, "written", "attempted", ["host-write", "terminal-write"]], + ] as const + for (const [destination, hostStatus, expectedHost, expectedTerminal, events] of cases) { + const service = createServices({ hostStatus }) + const result = await service.clipboard.writeText("text", { destination }) + expect([result.host.status, result.terminal.status]).toEqual([expectedHost, expectedTerminal]) + expect(service.events).toEqual([...events]) + await service.clipboard.dispose() + } + + const clear = createServices() + await clear.clipboard.clear({ destination: "all-available" }) + expect(clear.events).toEqual(["host-clear", "terminal-clear"]) + await clear.clipboard.dispose() + }) + + it("enforces remote host authorization for every policy", async () => { + const cases = [ + ["terminal-only", false, "not-attempted", "attempted", ["terminal-write"]], + ["host-only", false, "not-attempted", "not-attempted", []], + ["host-only", true, "written", "not-attempted", ["host-write"]], + ["best-available", true, "not-attempted", "attempted", ["terminal-write"]], + ["all-available", false, "not-attempted", "attempted", ["terminal-write"]], + ["all-available", true, "written", "attempted", ["host-write", "terminal-write"]], + ] as const + for (const [destination, allowRemoteHost, expectedHost, expectedTerminal, events] of cases) { + const service = createServices({ remote: true }) + const result = await service.clipboard.writeText("text", { destination, allowRemoteHost }) + expect([result.host.status, result.terminal.status]).toEqual([expectedHost, expectedTerminal]) + expect(service.events).toEqual([...events]) + await service.clipboard.dispose() + } + }) + + it("rejects invalid text before either destination", async () => { + const service = createServices({ maxWriteBytes: 4 }) + await expect(service.clipboard.writeText("", { destination: "all-available" })).rejects.toThrow("non-empty") + await expect(service.clipboard.writeText("bad\0text", { destination: "all-available" })).rejects.toThrow("NUL") + await expect(service.clipboard.writeText("hello", { destination: "terminal-only" })).rejects.toThrow(RangeError) + expect(service.events).toEqual([]) + await service.clipboard.dispose() + }) + + it("validates before handling pre-abort and does not dispatch pre-aborted operations", async () => { + const service = createServices({ maxWriteBytes: 4 }) + const signal = AbortSignal.abort() + const options = { destination: "all-available", signal } as const + + await expect(service.clipboard.writeText("", options)).rejects.toThrow("non-empty") + await expect( + service.clipboard.writeText("text", { + ...options, + destination: "invalid" as never, + }), + ).rejects.toThrow("destination") + await expect(service.clipboard.clear({ ...options, selection: "invalid" as never })).rejects.toThrow("selection") + await expect(service.clipboard.read({ preferredTypes: [] as never, signal })).rejects.toThrow("at least one") + + const write = await service.clipboard.writeText("text", options) + const clear = await service.clipboard.clear(options) + const notAttempted = { + host: { status: "not-attempted" }, + terminal: { status: "not-attempted", capability: "unknown" }, + } as const + expect(write).toEqual(notAttempted) + expect(clear).toEqual(notAttempted) + expect(service.events).toEqual([]) + await service.clipboard.dispose() + }) + + it("preserves terminal dispatch when all-available host work is later cancelled", async () => { + let operation: HostClipboardWriteOptions | undefined + const service = createServices({ + backend: { + async writeText(_text, options) { + operation = options + return await new Promise((resolve) => { + options.signal.addEventListener("abort", () => resolve({ status: "cancelled" }), { once: true }) + }) + }, + }, + }) + const controller = new AbortController() + const pending = service.clipboard.writeText("text", { destination: "all-available", signal: controller.signal }) + expect(service.events).toEqual(["terminal-write"]) + controller.abort() + expect(operation?.signal.aborted).toBe(true) + expect(await pending).toEqual({ + host: { status: "cancelled" }, + terminal: { status: "attempted", capability: "supported" }, + }) + await service.clipboard.dispose() + }) + + it("does not start a best-available terminal fallback after cancellation", async () => { + const service = createServices({ hostStatus: "unsupported" }) + const controller = new AbortController() + const pending = service.clipboard.writeText("text", { + destination: "best-available", + signal: controller.signal, + }) + + expect(service.events).toEqual(["host-write"]) + controller.abort() + + expect((await pending).terminal).toEqual({ status: "not-attempted", capability: "unknown" }) + expect(service.events).toEqual(["host-write"]) + await service.clipboard.dispose() + }) + + it("owns host disposal, waits for active composition, and rejects later operations", async () => { + let release: (() => void) | undefined + let disposeCount = 0 + const service = createServices({ + backend: { + async read(options) { + await new Promise((resolve) => { + options.signal.addEventListener( + "abort", + () => { + release = resolve + }, + { once: true }, + ) + }) + return { status: "cancelled" } + }, + async dispose() { + disposeCount++ + }, + }, + }) + const clipboard = service.clipboard + const read = clipboard.read({ preferredTypes: ["text/plain"] }) + const firstDispose = clipboard.dispose() + expect(clipboard.dispose()).toBe(firstDispose) + await Promise.resolve() + expect(disposeCount).toBe(0) + release?.() + await read + await firstDispose + expect(disposeCount).toBe(1) + await expect(clipboard.read({ preferredTypes: ["text/plain"] })).rejects.toThrow("disposed") + await expect(clipboard.writeText("text", { destination: "host-only" })).rejects.toThrow("disposed") + await expect(clipboard.clear({ destination: "host-only" })).rejects.toThrow("disposed") + }) +}) + +describe("createRendererClipboardAdapter", () => { + it("maps selections, capabilities, results, and conservative remote state", () => { + const calls: Array<[string, number]> = [] + const renderer = { + capabilities: null as null | { remote: boolean; osc52_support: "supported" | "unsupported" | "unknown" }, + copyToClipboardOSC52(_text: string, target: number) { + calls.push(["write", target]) + return true + }, + clearClipboardOSC52(target: number) { + calls.push(["clear", target]) + return false + }, + } + const adapter = createRendererClipboardAdapter(renderer) + expect(adapter.remote).toBe(true) + expect(adapter.writeText("text", "primary")).toEqual({ status: "attempted", capability: "unknown" }) + renderer.capabilities = { remote: false, osc52_support: "supported" } + expect(adapter.remote).toBe(false) + expect(adapter.clear("clipboard")).toEqual({ status: "local-failure", capability: "supported" }) + renderer.capabilities = { remote: false, osc52_support: "unsupported" } + expect(adapter.writeText("ignored", "clipboard")).toEqual({ + status: "not-attempted", + capability: "unsupported", + }) + expect(calls).toEqual([ + ["write", 1], + ["clear", 0], + ]) + }) +}) diff --git a/packages/core/src/lib/clipboard.ts b/packages/core/src/lib/clipboard.ts index 5052413ea4..047492d7ff 100644 --- a/packages/core/src/lib/clipboard.ts +++ b/packages/core/src/lib/clipboard.ts @@ -2,6 +2,293 @@ // Delegates to native Zig implementation for ANSI sequence generation. import type { RendererHandle, RenderLib } from "../zig.js" +import { + createHostClipboardWithBackend, + runTrackedOperation, + validateClipboardText, + type ActiveClipboardOperation, +} from "./host-clipboard.internal.js" +import { createNativeHostClipboardBackend } from "./host-clipboard.native.js" + +export interface ClipboardRepresentation { + // Identifies the content with a canonical, lowercase MIME essence without parameters. + readonly mimeType: string + // Contains stable, caller-owned encoded data. Image bytes are not decoded pixels and may require separate validation. + readonly bytes: Uint8Array +} + +export type ClipboardSelection = "clipboard" | "primary" + +export interface ClipboardReadOptions { + // Lists accepted MIME types in preference order. Include at least one type. + readonly preferredTypes: readonly [string, ...string[]] + // Selects the standard clipboard by default. Reads always use the process host. + readonly selection?: ClipboardSelection + // Cancels the read when aborted. + readonly signal?: AbortSignal +} + +// Reports whether a read returned data or why it did not. +export type ClipboardReadResult = + | { readonly status: "read"; readonly representation: ClipboardRepresentation } + | { readonly status: "empty" | "unsupported" | "cancelled" | "timed-out" | "limit-exceeded" } + | { readonly status: "failed"; readonly error: Error } + +export interface HostClipboardReadOptions { + // Lists accepted MIME types in preference order. Include at least one type. + readonly preferredTypes: readonly [string, ...string[]] + readonly selection: ClipboardSelection + // Rejects representations larger than this number of bytes. + readonly maxBytes: number + readonly timeoutMs: number + readonly signal: AbortSignal +} + +export interface HostClipboardWriteOptions { + readonly selection: ClipboardSelection + readonly timeoutMs: number + readonly signal: AbortSignal +} + +export type HostClipboardWriteResult = + | { readonly status: "written" | "unsupported" | "cancelled" | "timed-out" } + | { readonly status: "failed"; readonly error: Error } + +// `cleared` means the platform completed its clear operation. It does not guarantee durable erasure. +export type HostClipboardClearResult = + | { readonly status: "cleared" | "unsupported" | "cancelled" | "timed-out" } + | { readonly status: "failed"; readonly error: Error } + +export interface HostClipboardBackend { + // Returns the first usable representation from `preferredTypes`. + read(options: HostClipboardReadOptions): Promise + // Writes validated, nonempty text without NUL characters. + writeText(text: string, options: HostClipboardWriteOptions): Promise + // Clears the selection with the platform's clear operation, not an empty-text write. + clear(options: HostClipboardWriteOptions): Promise + dispose(): Promise +} + +// Controls whether an operation uses the terminal, the process host, or both. +export type ClipboardWriteDestination = "terminal-only" | "host-only" | "best-available" | "all-available" + +export interface ClipboardWriteOptions { + readonly destination: ClipboardWriteDestination + // Selects the standard clipboard by default. + readonly selection?: ClipboardSelection + // Allows a host write when the process runs through a remote terminal session. + readonly allowRemoteHost?: boolean + // Cancels unfinished work when aborted. + readonly signal?: AbortSignal +} + +export interface TerminalClipboardOperationResult { + // `attempted` only confirms synchronous local dispatch, including for clear operations. + readonly status: "attempted" | "local-failure" | "not-attempted" + // Reports OSC 52 support. It does not guarantee support for the requested selection. + readonly capability: "supported" | "unsupported" | "unknown" +} + +export interface ClipboardWriteResult { + readonly host: HostClipboardWriteResult | { readonly status: "not-attempted" } + readonly terminal: TerminalClipboardOperationResult +} + +export interface ClipboardClearResult { + readonly host: HostClipboardClearResult | { readonly status: "not-attempted" } + readonly terminal: TerminalClipboardOperationResult +} + +export interface ClipboardService { + // Reads the process host clipboard. It cannot read the terminal user's clipboard over SSH. + read(options: ClipboardReadOptions): Promise + // Rejects empty text and NUL characters before trying any destination. + writeText(text: string, options: ClipboardWriteOptions): Promise + // Clears the selected destinations without treating empty text as a clear request. + clear(options: ClipboardWriteOptions): Promise + dispose(): Promise +} + +export interface HostClipboardOperationOptions { + readonly selection?: ClipboardSelection + readonly signal?: AbortSignal +} + +export interface HostClipboardService { + readonly maxWriteBytes: number + read(options: ClipboardReadOptions): Promise + writeText(text: string, options?: HostClipboardOperationOptions): Promise + clear(options?: HostClipboardOperationOptions): Promise + dispose(): Promise +} + +export interface HostClipboardOptions { + readonly timeoutMs?: number + readonly maxReadBytes?: number + readonly maxWriteBytes?: number + // Bounds pixels inspected or transcoded by image conversion fallbacks, not direct PNG transfers. + readonly maxImagePixels?: number + // Bounds temporary decoded storage used by image conversion fallbacks, not direct PNG transfers. + readonly maxConversionBytes?: number + readonly maxConcurrentOperations?: number + readonly maxProviderTransfers?: number + readonly maxWorkUnitsPerDrain?: number + readonly waylandSeat?: string +} + +export interface TerminalClipboardAdapter { + readonly remote: boolean + writeText(text: string, selection: ClipboardSelection): TerminalClipboardOperationResult + clear(selection: ClipboardSelection): TerminalClipboardOperationResult +} + +export interface ClipboardOptions { + // Transfers ownership to the composed service. `ClipboardService.dispose()` disposes this host. + readonly host: HostClipboardService + readonly terminal: TerminalClipboardAdapter +} + +const NOT_ATTEMPTED_TERMINAL: TerminalClipboardOperationResult = { + status: "not-attempted", + capability: "unknown", +} + +const validateSelection = (selection: ClipboardSelection | undefined): ClipboardSelection => { + const normalized = selection ?? "clipboard" + if (normalized !== "clipboard" && normalized !== "primary") { + throw new TypeError("selection must be clipboard or primary") + } + return normalized +} + +// WAYLAND_SOCKET-only launches can create one host service per process because the inherited fd is one-shot. +export const createHostClipboard = (options: HostClipboardOptions = {}): HostClipboardService => + createHostClipboardWithBackend(options, createNativeHostClipboardBackend) + +const validateDestination = (destination: ClipboardWriteDestination): void => { + if ( + destination !== "terminal-only" && + destination !== "host-only" && + destination !== "best-available" && + destination !== "all-available" + ) { + throw new TypeError("destination is not a supported clipboard policy") + } +} + +export const createClipboard = ({ host, terminal }: ClipboardOptions): ClipboardService => { + const active = new Set() + let disposed = false + let disposePromise: Promise | undefined + + const assertUsable = (): void => { + if (disposed) throw new Error("Clipboard service is disposed") + } + + const canUseRemoteHost = (options: ClipboardWriteOptions): boolean => + !terminal.remote || options.allowRemoteHost === true + + type HostMutationResult = HostClipboardWriteResult | HostClipboardClearResult + type MutationResult = { + readonly host: Result | { readonly status: "not-attempted" } + readonly terminal: TerminalClipboardOperationResult + } + + const composeMutation = async ( + options: ClipboardWriteOptions, + signal: AbortSignal, + hostOperation: () => Promise, + terminalOperation: () => TerminalClipboardOperationResult, + ): Promise> => { + if (options.destination === "terminal-only") { + return { host: { status: "not-attempted" }, terminal: terminalOperation() } + } + if (options.destination === "host-only") { + const hostResult = canUseRemoteHost(options) ? await hostOperation() : { status: "not-attempted" as const } + return { host: hostResult, terminal: NOT_ATTEMPTED_TERMINAL } + } + if (options.destination === "best-available") { + if (terminal.remote) { + return { host: { status: "not-attempted" }, terminal: terminalOperation() } + } + const hostResult = await hostOperation() + const terminalResult = + !signal.aborted && (hostResult.status === "unsupported" || hostResult.status === "failed") + ? terminalOperation() + : NOT_ATTEMPTED_TERMINAL + return { host: hostResult, terminal: terminalResult } + } + const hostPromise = canUseRemoteHost(options) + ? hostOperation() + : Promise.resolve({ status: "not-attempted" as const }) + const terminalResult = terminalOperation() + return { host: await hostPromise, terminal: terminalResult } + } + return { + read(options) { + try { + assertUsable() + if (options.signal?.aborted) return host.read(options) + return runTrackedOperation(active, options.signal, (signal) => host.read({ ...options, signal })) + } catch (error) { + return Promise.reject(error) + } + }, + writeText(text, options) { + try { + assertUsable() + validateDestination(options.destination) + validateClipboardText(text, host.maxWriteBytes) + const selection = validateSelection(options.selection) + if (options.signal?.aborted) { + return Promise.resolve({ host: { status: "not-attempted" }, terminal: NOT_ATTEMPTED_TERMINAL }) + } + return runTrackedOperation(active, options.signal, (signal) => { + const operationOptions = { selection, signal } + return composeMutation( + options, + signal, + () => host.writeText(text, operationOptions), + () => terminal.writeText(text, selection), + ) + }) + } catch (error) { + return Promise.reject(error) + } + }, + clear(options) { + try { + assertUsable() + validateDestination(options.destination) + const selection = validateSelection(options.selection) + if (options.signal?.aborted) { + return Promise.resolve({ host: { status: "not-attempted" }, terminal: NOT_ATTEMPTED_TERMINAL }) + } + return runTrackedOperation(active, options.signal, (signal) => { + const operationOptions = { selection, signal } + return composeMutation( + options, + signal, + () => host.clear(operationOptions), + () => terminal.clear(selection), + ) + }) + } catch (error) { + return Promise.reject(error) + } + }, + dispose() { + if (disposePromise) return disposePromise + disposed = true + for (const operation of active) operation.controller.abort() + disposePromise = (async () => { + await Promise.all([...active].map((operation) => operation.settled)) + await host.dispose() + })() + return disposePromise + }, + } +} export enum ClipboardTarget { Clipboard = 0, @@ -10,6 +297,44 @@ export enum ClipboardTarget { Secondary = 3, } +export interface RendererClipboardBoundary { + readonly capabilities: { + readonly remote: boolean + readonly osc52_support: "supported" | "unsupported" | "unknown" + } | null + copyToClipboardOSC52(text: string, target?: ClipboardTarget): boolean + clearClipboardOSC52(target?: ClipboardTarget): boolean +} + +export const createRendererClipboardAdapter = (renderer: RendererClipboardBoundary): TerminalClipboardAdapter => { + const targetFor = (selection: ClipboardSelection): ClipboardTarget => + selection === "primary" ? ClipboardTarget.Primary : ClipboardTarget.Clipboard + const capability = (): TerminalClipboardOperationResult["capability"] => + renderer.capabilities?.osc52_support ?? "unknown" + + return { + get remote() { + return renderer.capabilities?.remote ?? true + }, + writeText(text, selection) { + const currentCapability = capability() + if (currentCapability === "unsupported") return { status: "not-attempted", capability: currentCapability } + return { + status: renderer.copyToClipboardOSC52(text, targetFor(selection)) ? "attempted" : "local-failure", + capability: currentCapability, + } + }, + clear(selection) { + const currentCapability = capability() + if (currentCapability === "unsupported") return { status: "not-attempted", capability: currentCapability } + return { + status: renderer.clearClipboardOSC52(targetFor(selection)) ? "attempted" : "local-failure", + capability: currentCapability, + } + }, + } +} + export class Clipboard { private lib: RenderLib private rendererPtr: RendererHandle diff --git a/packages/core/src/lib/host-clipboard.internal.ts b/packages/core/src/lib/host-clipboard.internal.ts new file mode 100644 index 0000000000..626eca8ac4 --- /dev/null +++ b/packages/core/src/lib/host-clipboard.internal.ts @@ -0,0 +1,284 @@ +import type { + ClipboardReadResult, + ClipboardSelection, + HostClipboardBackend, + HostClipboardOptions, + HostClipboardService, +} from "./clipboard.js" + +const DEFAULT_CLIPBOARD_TIMEOUT_MS = 1_000 +const DEFAULT_CLIPBOARD_MAX_BYTES = 8 * 1024 * 1024 +const DEFAULT_CLIPBOARD_MAX_IMAGE_PIXELS = 64 * 1024 * 1024 +const DEFAULT_CLIPBOARD_MAX_CONVERSION_BYTES = 512 * 1024 * 1024 +const DEFAULT_CLIPBOARD_MAX_CONCURRENT_OPERATIONS = 16 +const DEFAULT_CLIPBOARD_MAX_PROVIDER_TRANSFERS = 16 +const DEFAULT_CLIPBOARD_MAX_WORK_UNITS_PER_DRAIN = 64 +const MAX_U32 = 0xffff_ffff +const MIME_ESSENCE_PATTERN = /^[a-z0-9!#$%&'*+.^_`|~-]+\/[a-z0-9!#$%&'*+.^_`|~-]+$/i +export const HOST_CLIPBOARD_MIME_PREFERENCE_COUNT_MAX = 64 +export const HOST_CLIPBOARD_MIME_ESSENCE_BYTES_MAX = 255 + +export interface NormalizedHostClipboardOptions { + readonly timeoutMs: number + readonly maxReadBytes: number + readonly maxWriteBytes: number + readonly maxImagePixels: number + readonly maxConversionBytes: number + readonly maxConcurrentOperations: number + readonly maxProviderTransfers: number + readonly maxWorkUnitsPerDrain: number + readonly waylandSeat?: string +} + +export type HostClipboardBackendFactory = (options: NormalizedHostClipboardOptions) => HostClipboardBackend + +export const normalizeRemainingTimeout = (timeoutMs: number, elapsedMs: number): number => { + const exactRemainingMs = timeoutMs - elapsedMs + return exactRemainingMs <= 0 ? 0 : Math.max(1, Math.floor(exactRemainingMs)) +} + +export interface ActiveClipboardOperation { + readonly controller: AbortController + readonly settled: Promise + settle(): void +} + +const validateU32 = (name: string, value: number): number => { + if (!Number.isInteger(value) || value < 0 || value > MAX_U32) { + throw new RangeError(`${name} must be an integer from 0 through ${MAX_U32}`) + } + return value +} + +const validatePositiveU32 = (name: string, value: number): number => { + const validated = validateU32(name, value) + if (validated === 0) throw new RangeError(`${name} must be greater than zero`) + return validated +} + +const normalizeOptions = (options: HostClipboardOptions): NormalizedHostClipboardOptions => { + const waylandSeat = options.waylandSeat + if ( + waylandSeat !== undefined && + (typeof waylandSeat !== "string" || waylandSeat.length === 0 || waylandSeat.includes("\0")) + ) { + throw new TypeError("waylandSeat must be a non-empty string without NUL characters") + } + return { + timeoutMs: validateU32("timeoutMs", options.timeoutMs ?? DEFAULT_CLIPBOARD_TIMEOUT_MS), + maxReadBytes: validateU32("maxReadBytes", options.maxReadBytes ?? DEFAULT_CLIPBOARD_MAX_BYTES), + maxWriteBytes: validateU32("maxWriteBytes", options.maxWriteBytes ?? DEFAULT_CLIPBOARD_MAX_BYTES), + maxImagePixels: validateU32("maxImagePixels", options.maxImagePixels ?? DEFAULT_CLIPBOARD_MAX_IMAGE_PIXELS), + maxConversionBytes: validateU32( + "maxConversionBytes", + options.maxConversionBytes ?? DEFAULT_CLIPBOARD_MAX_CONVERSION_BYTES, + ), + maxConcurrentOperations: validatePositiveU32( + "maxConcurrentOperations", + options.maxConcurrentOperations ?? DEFAULT_CLIPBOARD_MAX_CONCURRENT_OPERATIONS, + ), + maxProviderTransfers: validatePositiveU32( + "maxProviderTransfers", + options.maxProviderTransfers ?? DEFAULT_CLIPBOARD_MAX_PROVIDER_TRANSFERS, + ), + maxWorkUnitsPerDrain: validatePositiveU32( + "maxWorkUnitsPerDrain", + options.maxWorkUnitsPerDrain ?? DEFAULT_CLIPBOARD_MAX_WORK_UNITS_PER_DRAIN, + ), + waylandSeat, + } +} + +const normalizePreferredTypes = (preferredTypes: readonly [string, ...string[]]): readonly [string, ...string[]] => { + if (!Array.isArray(preferredTypes) || preferredTypes.length === 0) { + throw new TypeError("preferredTypes must contain at least one MIME essence type") + } + if (preferredTypes.length > HOST_CLIPBOARD_MIME_PREFERENCE_COUNT_MAX) { + throw new RangeError( + `preferredTypes must contain at most ${HOST_CLIPBOARD_MIME_PREFERENCE_COUNT_MAX} MIME essence types`, + ) + } + const normalized = preferredTypes.map((mimeType) => { + if (typeof mimeType !== "string") { + throw new TypeError("preferredTypes must contain valid MIME essence types without parameters") + } + // Valid MIME essence tokens are ASCII, so code units equal encoded bytes. + if (mimeType.length > HOST_CLIPBOARD_MIME_ESSENCE_BYTES_MAX) { + throw new RangeError( + `preferredTypes MIME essences must be at most ${HOST_CLIPBOARD_MIME_ESSENCE_BYTES_MAX} ASCII bytes`, + ) + } + if (!MIME_ESSENCE_PATTERN.test(mimeType)) { + throw new TypeError("preferredTypes must contain valid MIME essence types without parameters") + } + return mimeType.toLowerCase() + }) + return normalized as [string, ...string[]] +} + +const normalizeSelection = (selection: ClipboardSelection | undefined): ClipboardSelection => { + const normalized = selection ?? "clipboard" + if (normalized !== "clipboard" && normalized !== "primary") { + throw new TypeError("selection must be clipboard or primary") + } + return normalized +} + +export const validateClipboardText = (text: string, maxWriteBytes: number): void => { + if (typeof text !== "string" || text.length === 0) throw new TypeError("writeText requires non-empty text") + if (text.includes("\0")) throw new TypeError("writeText does not support NUL characters") + const byteLimit = Math.min(maxWriteBytes, MAX_U32) + let byteLength = 0 + for (let index = 0; index < text.length; index += 1) { + const codeUnit = text.charCodeAt(index) + if (codeUnit <= 0x7f) { + byteLength += 1 + } else if (codeUnit <= 0x7ff) { + byteLength += 2 + } else if (codeUnit >= 0xd800 && codeUnit <= 0xdbff && index + 1 < text.length) { + const nextCodeUnit = text.charCodeAt(index + 1) + if (nextCodeUnit >= 0xdc00 && nextCodeUnit <= 0xdfff) { + byteLength += 4 + index += 1 + } else { + byteLength += 3 + } + } else { + byteLength += 3 + } + if (byteLength > byteLimit) { + throw new RangeError(`writeText exceeds the configured ${maxWriteBytes} byte limit`) + } + } +} + +const createActiveOperation = (callerSignal?: AbortSignal): ActiveClipboardOperation => { + const controller = new AbortController() + let settle = () => {} + const settled = new Promise((resolve) => { + settle = resolve + }) + if (callerSignal) { + callerSignal.addEventListener("abort", () => controller.abort(callerSignal.reason), { + once: true, + signal: controller.signal, + }) + } + return { controller, settled, settle } +} + +export const runTrackedOperation = ( + active: Set, + callerSignal: AbortSignal | undefined, + operation: (signal: AbortSignal) => Promise, +): Promise => { + const state = createActiveOperation(callerSignal) + active.add(state) + let result: Promise + try { + result = operation(state.controller.signal) + } catch (error) { + result = Promise.reject(error) + } + return result.finally(() => { + active.delete(state) + state.controller.abort() + state.settle() + }) +} + +export const createHostClipboardWithBackend = ( + options: HostClipboardOptions, + createBackend: HostClipboardBackendFactory, +): HostClipboardService => { + const config = normalizeOptions(options) + const backend = createBackend(config) + const active = new Set() + let disposed = false + let disposePromise: Promise | undefined + + const assertUsable = (): void => { + if (disposed) throw new Error("Host clipboard service is disposed") + } + const remainingTimeout = (startedAt: number): number => + normalizeRemainingTimeout(config.timeoutMs, performance.now() - startedAt) + const atCapacity = (): { readonly status: "failed"; readonly error: Error } | undefined => + active.size >= config.maxConcurrentOperations + ? { status: "failed", error: new Error("Host clipboard operation limit reached") } + : undefined + return { + maxWriteBytes: config.maxWriteBytes, + read(readOptions) { + const startedAt = performance.now() + try { + assertUsable() + const preferredTypes = normalizePreferredTypes(readOptions.preferredTypes) + const selection = normalizeSelection(readOptions.selection) + if (readOptions.signal?.aborted) return Promise.resolve({ status: "cancelled" }) + const capacityFailure = atCapacity() + if (capacityFailure) return Promise.resolve(capacityFailure) + const timeoutMs = remainingTimeout(startedAt) + if (timeoutMs === 0) return Promise.resolve({ status: "timed-out" }) + return runTrackedOperation(active, readOptions.signal, async (signal) => { + const result = await backend.read({ + preferredTypes, + selection, + maxBytes: config.maxReadBytes, + timeoutMs, + signal, + }) + if (result.status !== "read") return result + if (result.representation.bytes.byteLength > config.maxReadBytes) return { status: "limit-exceeded" } + return { status: "read", representation: result.representation } + }) + } catch (error) { + return Promise.reject(error) + } + }, + writeText(text, operationOptions = {}) { + const startedAt = performance.now() + try { + assertUsable() + validateClipboardText(text, config.maxWriteBytes) + const selection = normalizeSelection(operationOptions.selection) + if (operationOptions.signal?.aborted) return Promise.resolve({ status: "cancelled" }) + const capacityFailure = atCapacity() + if (capacityFailure) return Promise.resolve(capacityFailure) + const timeoutMs = remainingTimeout(startedAt) + if (timeoutMs === 0) return Promise.resolve({ status: "timed-out" }) + return runTrackedOperation(active, operationOptions.signal, (signal) => + backend.writeText(text, { selection, timeoutMs, signal }), + ) + } catch (error) { + return Promise.reject(error) + } + }, + clear(operationOptions = {}) { + const startedAt = performance.now() + try { + assertUsable() + const selection = normalizeSelection(operationOptions.selection) + if (operationOptions.signal?.aborted) return Promise.resolve({ status: "cancelled" }) + const capacityFailure = atCapacity() + if (capacityFailure) return Promise.resolve(capacityFailure) + const timeoutMs = remainingTimeout(startedAt) + if (timeoutMs === 0) return Promise.resolve({ status: "timed-out" }) + return runTrackedOperation(active, operationOptions.signal, (signal) => + backend.clear({ selection, timeoutMs, signal }), + ) + } catch (error) { + return Promise.reject(error) + } + }, + dispose() { + if (disposePromise) return disposePromise + disposed = true + for (const operation of active) operation.controller.abort() + disposePromise = (async () => { + await Promise.all([...active].map((operation) => operation.settled)) + await backend.dispose() + })() + return disposePromise + }, + } +} diff --git a/packages/core/src/lib/host-clipboard.native.scheduler.test.ts b/packages/core/src/lib/host-clipboard.native.scheduler.test.ts new file mode 100644 index 0000000000..6fdea051f4 --- /dev/null +++ b/packages/core/src/lib/host-clipboard.native.scheduler.test.ts @@ -0,0 +1,95 @@ +import { expect, test } from "bun:test" + +import { NativeClipboardPollScheduler } from "./host-clipboard.native.scheduler.js" + +interface FakeTimer { + cleared: boolean + refed: boolean + callback: () => void + ref(): void + unref(): void +} + +const createHarness = () => { + const timers: FakeTimer[] = [] + const scheduler = new NativeClipboardPollScheduler({ + set: (callback) => { + const timer: FakeTimer = { + cleared: false, + refed: true, + callback, + ref() { + timer.refed = true + }, + unref() { + timer.refed = false + }, + } + timers.push(timer) + return timer + }, + clear: (timer) => { + timer.cleared = true + }, + }) + return { scheduler, timers } +} + +test("tracks process liveness as provider and operation work changes", () => { + const { scheduler, timers } = createHarness() + + scheduler.schedule(false, true, () => {}) + expect(timers).toHaveLength(1) + expect(timers[0]).toMatchObject({ cleared: false, refed: false }) + + scheduler.schedule(true, true, () => {}) + expect(timers).toHaveLength(2) + expect(timers[0]?.cleared).toBe(true) + expect(timers[1]).toMatchObject({ cleared: false, refed: true }) + + scheduler.schedule(false, true, () => {}) + expect(timers[1]).toMatchObject({ cleared: false, refed: false }) + scheduler.schedule(true, true, () => {}) + expect(timers).toHaveLength(2) + expect(timers[1]?.refed).toBe(true) + + scheduler.schedule(false, false, () => {}) + expect(timers[1]?.cleared).toBe(true) + + let callbackCount = 0 + scheduler.schedule(true, false, () => { + callbackCount += 1 + }) + expect(timers[2]).toMatchObject({ cleared: false, refed: true }) + timers[2]!.callback() + timers[2]!.callback() + expect(callbackCount).toBe(1) + scheduler.schedule(true, false, () => {}) + expect(timers).toHaveLength(4) + scheduler.dispose() + scheduler.dispose() + expect(timers[3]?.cleared).toBe(true) +}) + +test("schedules one unrefed service turn after any final terminal operation poll", () => { + const { scheduler, timers } = createHarness() + let hasOperation = true + let providerActive = false + const drain = () => { + providerActive = false + if (!hasOperation) { + scheduler.schedule(hasOperation, providerActive, drain) + return + } + hasOperation = false + providerActive = true + scheduler.schedule(hasOperation, providerActive, drain) + } + + scheduler.schedule(hasOperation, false, drain) + timers[0]!.callback() + + expect(timers[1]).toMatchObject({ cleared: false, refed: false }) + timers[1]!.callback() + expect(timers).toHaveLength(2) +}) diff --git a/packages/core/src/lib/host-clipboard.native.scheduler.ts b/packages/core/src/lib/host-clipboard.native.scheduler.ts new file mode 100644 index 0000000000..4a1ed709ef --- /dev/null +++ b/packages/core/src/lib/host-clipboard.native.scheduler.ts @@ -0,0 +1,65 @@ +const OPERATION_POLL_INTERVAL_MS = 1 +const PROVIDER_POLL_INTERVAL_MS = 8 + +interface TimerFunctions { + readonly set: (callback: () => void, delayMs: number) => Timer + readonly clear: (timer: Timer) => void +} + +export class NativeClipboardPollScheduler> { + private timer: Timer | undefined + private timerForOperation = false + + constructor(private readonly timers: TimerFunctions) {} + + schedule(hasPendingOperation: boolean, providerActive: boolean, callback: () => void): void { + if (!hasPendingOperation && !providerActive) { + this.clearTimer() + return + } + + if (this.timer !== undefined) { + if (hasPendingOperation && !this.timerForOperation) { + this.clearTimer() + } else { + if (hasPendingOperation) refTimer(this.timer) + else unrefTimer(this.timer) + return + } + } + + this.timerForOperation = hasPendingOperation + const timer = this.timers.set( + () => { + if (this.timer !== timer) return + this.timer = undefined + this.timerForOperation = false + callback() + }, + hasPendingOperation ? OPERATION_POLL_INTERVAL_MS : PROVIDER_POLL_INTERVAL_MS, + ) + this.timer = timer + if (!hasPendingOperation) unrefTimer(timer) + } + + dispose(): void { + this.clearTimer() + } + + private clearTimer(): void { + if (this.timer === undefined) return + this.timers.clear(this.timer) + this.timer = undefined + this.timerForOperation = false + } +} + +const refTimer = (timer: unknown): void => { + if (typeof timer === "object" && timer !== null && "ref" in timer && typeof timer.ref === "function") timer.ref() +} + +const unrefTimer = (timer: unknown): void => { + if (typeof timer === "object" && timer !== null && "unref" in timer && typeof timer.unref === "function") { + timer.unref() + } +} diff --git a/packages/core/src/lib/host-clipboard.native.ts b/packages/core/src/lib/host-clipboard.native.ts new file mode 100644 index 0000000000..335f22e625 --- /dev/null +++ b/packages/core/src/lib/host-clipboard.native.ts @@ -0,0 +1,319 @@ +import { + NativeClipboardCopyStatus, + NativeClipboardDestroyStatus, + NativeClipboardOperationStatus, + NativeClipboardShutdownStatus, + NativeClipboardStartStatus, + resolveRenderLib, + type ClipboardOperationHandle, + type ClipboardServiceHandle, + type RenderLib, +} from "../zig.js" +import type { + ClipboardReadResult, + ClipboardSelection, + HostClipboardBackend, + HostClipboardClearResult, + HostClipboardWriteResult, +} from "./clipboard.js" +import { type HostClipboardBackendFactory } from "./host-clipboard.internal.js" +import { NativeClipboardPollScheduler } from "./host-clipboard.native.scheduler.js" + +type NativeResult = ClipboardReadResult | HostClipboardWriteResult | HostClipboardClearResult +const SHUTDOWN_POLL_INTERVAL_MS = 1 + +interface PendingOperation { + readonly handle: ClipboardOperationHandle + readonly kind: "read" | "write" | "clear" + readonly signal: AbortSignal + readonly resolve: (result: NativeResult) => void + readonly reject: (error: unknown) => void + cleanupError?: unknown +} + +const selectionValue = (selection: ClipboardSelection): number => (selection === "clipboard" ? 0 : 1) + +const encodeReadRequest = (preferredTypes: readonly [string, ...string[]]): Uint8Array => { + const encoder = new TextEncoder() + const encoded = preferredTypes.map((mimeType) => encoder.encode(mimeType)) + const size = encoded.reduce((total, mimeType) => total + 4 + mimeType.byteLength, 4) + const request = new Uint8Array(size) + const view = new DataView(request.buffer) + view.setUint32(0, encoded.length, true) + let offset = 4 + for (const mimeType of encoded) { + view.setUint32(offset, mimeType.byteLength, true) + offset += 4 + request.set(mimeType, offset) + offset += mimeType.byteLength + } + return request +} + +const startFailure = (status: NativeClipboardStartStatus): NativeResult => ({ + status: "failed", + error: new Error(`Native clipboard operation failed to start (${NativeClipboardStartStatus[status]})`), +}) + +class NativeClipboardBackend implements HostClipboardBackend { + private readonly library: RenderLib + private readonly service: ClipboardServiceHandle + private readonly pending = new Map() + private readonly scheduler = new NativeClipboardPollScheduler({ set: setTimeout, clear: clearTimeout }) + private providerActive = false + private disposed = false + private disposePromise: Promise | undefined + + constructor( + private readonly maxWorkUnitsPerDrain: number, + private readonly maxImagePixels: number, + private readonly maxConversionBytes: number, + maxConcurrentOperations: number, + maxProviderTransfers: number, + waylandSeat?: string, + ) { + this.library = resolveRenderLib() + const service = this.library.clipboardServiceCreate(maxConcurrentOperations, maxProviderTransfers, waylandSeat) + if (!service) throw new Error("Failed to create native clipboard service") + this.service = service + } + + read(options: Parameters[0]): Promise { + const request = encodeReadRequest(options.preferredTypes) + const started = this.library.clipboardReadOperationStart( + this.service, + request, + selectionValue(options.selection), + options.maxBytes, + this.maxImagePixels, + this.maxConversionBytes, + options.timeoutMs, + ) + return this.track(started, options.signal, "read") as Promise + } + + writeText( + text: string, + options: Parameters[1], + ): Promise { + const started = this.library.clipboardWriteOperationStart( + this.service, + new TextEncoder().encode(text), + selectionValue(options.selection), + options.timeoutMs, + ) + return this.track(started, options.signal, "write") as Promise + } + + clear(options: Parameters[0]): Promise { + const started = this.library.clipboardClearOperationStart( + this.service, + selectionValue(options.selection), + options.timeoutMs, + ) + return this.track(started, options.signal, "clear") as Promise + } + + dispose(): Promise { + if (this.disposePromise) return this.disposePromise + this.disposed = true + this.disposePromise = this.shutdown() + return this.disposePromise + } + + private track( + started: { status: NativeClipboardStartStatus; operation: ClipboardOperationHandle | null }, + signal: AbortSignal, + kind: PendingOperation["kind"], + ): Promise { + if (this.disposed) return Promise.reject(new Error("Native clipboard backend is disposed")) + if (started.status !== NativeClipboardStartStatus.Ok || !started.operation) { + return Promise.resolve(startFailure(started.status)) + } + return new Promise((resolve, reject) => { + const operation: PendingOperation = { handle: started.operation!, kind, signal, resolve, reject } + this.pending.set(operation.handle, operation) + signal.addEventListener("abort", () => this.requestCancel(operation), { once: true }) + this.ensureScheduled() + this.drain() + }) + } + + private requestCancel(operation: PendingOperation): void { + if (!this.pending.has(operation.handle)) return + try { + this.library.clipboardOperationCancel(operation.handle) + } catch (error) { + operation.cleanupError ??= error + } + this.ensureScheduled() + } + + private ensureScheduled(): void { + this.scheduler.schedule(this.pending.size > 0, this.providerActive, () => this.drain()) + } + + private drain(): void { + // A service drain gives cancellation cleanup and queued events their turn before operations settle. + this.providerActive = false + try { + this.providerActive = this.library.clipboardServiceDrain(this.service) === 1 + } catch (error) { + for (const operation of this.pending.values()) { + operation.cleanupError ??= error + try { + this.library.clipboardOperationCancel(operation.handle) + } catch {} + } + } + let workUnits = 0 + while (workUnits < this.maxWorkUnitsPerDrain && this.pending.size > 0) { + const operation = this.pending.values().next().value + if (!operation) break + workUnits += 1 + try { + if (operation.cleanupError !== undefined) { + try { + this.library.clipboardOperationCancel(operation.handle) + } catch {} + const status = this.library.clipboardOperationPoll(operation.handle) + if (status === NativeClipboardOperationStatus.Pending) { + this.rotate(operation) + continue + } + this.providerActive = true + const destroyed = this.library.clipboardOperationDestroy(operation.handle) + if (destroyed === NativeClipboardDestroyStatus.NotReady) { + this.rotate(operation) + continue + } + this.pending.delete(operation.handle) + operation.reject(operation.cleanupError) + continue + } + if (operation.signal.aborted) this.library.clipboardOperationCancel(operation.handle) + const status = this.library.clipboardOperationPoll(operation.handle) + if (status === NativeClipboardOperationStatus.Pending) { + this.rotate(operation) + continue + } + this.providerActive = true + const result = this.readResult(operation.handle, operation.kind, status) + const destroyed = this.library.clipboardOperationDestroy(operation.handle) + if (destroyed === NativeClipboardDestroyStatus.NotReady) { + this.rotate(operation) + continue + } + this.pending.delete(operation.handle) + operation.resolve( + destroyed === NativeClipboardDestroyStatus.Destroyed + ? result + : { status: "failed", error: new Error("Native clipboard operation became invalid before destruction") }, + ) + } catch (error) { + operation.cleanupError ??= error + try { + this.library.clipboardOperationCancel(operation.handle) + } catch {} + this.rotate(operation) + } + } + this.ensureScheduled() + } + + private rotate(operation: PendingOperation): void { + this.pending.delete(operation.handle) + this.pending.set(operation.handle, operation) + } + + private readResult( + handle: ClipboardOperationHandle, + kind: PendingOperation["kind"], + status: NativeClipboardOperationStatus, + ): NativeResult { + switch (status) { + case NativeClipboardOperationStatus.Read: + return kind === "read" ? this.readRepresentation(handle) : this.invalidResult(kind, status) + case NativeClipboardOperationStatus.Empty: + return kind === "read" ? { status: "empty" } : this.invalidResult(kind, status) + case NativeClipboardOperationStatus.Written: + return kind === "write" ? { status: "written" } : this.invalidResult(kind, status) + case NativeClipboardOperationStatus.Cleared: + return kind === "clear" ? { status: "cleared" } : this.invalidResult(kind, status) + case NativeClipboardOperationStatus.Unsupported: + return { status: "unsupported" } + case NativeClipboardOperationStatus.Cancelled: + return { status: "cancelled" } + case NativeClipboardOperationStatus.TimedOut: + return { status: "timed-out" } + case NativeClipboardOperationStatus.LimitExceeded: + return kind === "read" ? { status: "limit-exceeded" } : this.invalidResult(kind, status) + case NativeClipboardOperationStatus.Failed: + return { status: "failed", error: this.readError(handle) } + default: + return { status: "failed", error: new Error("Native clipboard operation returned an invalid status") } + } + } + + private invalidResult(kind: PendingOperation["kind"], status: NativeClipboardOperationStatus): NativeResult { + return { + status: "failed", + error: new Error( + `Native clipboard ${kind} returned inapplicable status ${NativeClipboardOperationStatus[status]}`, + ), + } + } + + private readRepresentation(handle: ClipboardOperationHandle): ClipboardReadResult { + const mimeLength = this.library.clipboardOperationResultMimeLength(handle) + const dataLength = this.library.clipboardOperationResultDataLength(handle) + if (mimeLength.status !== NativeClipboardCopyStatus.Ok || dataLength.status !== NativeClipboardCopyStatus.Ok) { + return { status: "failed", error: new Error("Failed to read native clipboard result lengths") } + } + const mime = new Uint8Array(mimeLength.length) + const bytes = new Uint8Array(dataLength.length) + if ( + this.library.clipboardOperationResultMimeCopy(handle, mime) !== NativeClipboardCopyStatus.Ok || + this.library.clipboardOperationResultDataCopy(handle, bytes) !== NativeClipboardCopyStatus.Ok + ) { + return { status: "failed", error: new Error("Failed to copy native clipboard result") } + } + return { status: "read", representation: { mimeType: new TextDecoder().decode(mime), bytes } } + } + + private readError(handle: ClipboardOperationHandle): Error { + const code = this.library.clipboardOperationResultErrorCode(handle) + const length = this.library.clipboardOperationResultDiagnosticLength(handle) + if (code.status !== NativeClipboardCopyStatus.Ok || length.status !== NativeClipboardCopyStatus.Ok) { + return new Error("Native clipboard operation failed without a readable diagnostic") + } + const diagnostic = new Uint8Array(length.length) + if (this.library.clipboardOperationResultDiagnosticCopy(handle, diagnostic) !== NativeClipboardCopyStatus.Ok) { + return new Error("Native clipboard operation failed without a readable diagnostic") + } + return Object.assign(new Error(new TextDecoder().decode(diagnostic)), { code: code.errorCode }) + } + + private async shutdown(): Promise { + this.scheduler.dispose() + let status = this.library.clipboardServiceBeginShutdown(this.service) + while (status === NativeClipboardShutdownStatus.Pending) { + await new Promise((resolve) => setTimeout(resolve, SHUTDOWN_POLL_INTERVAL_MS)) + status = this.library.clipboardServicePollShutdown(this.service) + } + if (status !== NativeClipboardShutdownStatus.Ready) throw new Error("Native clipboard service became invalid") + if (this.library.clipboardServiceDestroy(this.service) !== NativeClipboardDestroyStatus.Destroyed) { + throw new Error("Failed to destroy native clipboard service") + } + } +} + +export const createNativeHostClipboardBackend: HostClipboardBackendFactory = (options) => + new NativeClipboardBackend( + options.maxWorkUnitsPerDrain, + options.maxImagePixels, + options.maxConversionBytes, + options.maxConcurrentOperations, + options.maxProviderTransfers, + options.waylandSeat, + ) diff --git a/packages/core/src/lib/host-clipboard.test.ts b/packages/core/src/lib/host-clipboard.test.ts new file mode 100644 index 0000000000..94d7ccbcb5 --- /dev/null +++ b/packages/core/src/lib/host-clipboard.test.ts @@ -0,0 +1,336 @@ +import { describe, expect, it } from "bun:test" +import { NativeImage } from "../image.js" +import { + type ClipboardReadResult, + type HostClipboardBackend, + type HostClipboardOptions, + type HostClipboardReadOptions, + type HostClipboardWriteOptions, +} from "./clipboard.js" +import { + createHostClipboardWithBackend, + normalizeRemainingTimeout, + type NormalizedHostClipboardOptions, +} from "./host-clipboard.internal.js" + +const PNG_1X1 = Uint8Array.from( + Buffer.from( + "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR4AWP4z8DwHwAFAAH/e+m+7wAAAABJRU5ErkJggg==", + "base64", + ), +) + +const createBackend = (overrides: Partial = {}) => { + const reads: HostClipboardReadOptions[] = [] + const writes: Array<{ text: string; options: HostClipboardWriteOptions }> = [] + const clears: HostClipboardWriteOptions[] = [] + let disposeCount = 0 + const backend: HostClipboardBackend = { + async read(options) { + reads.push(options) + return { status: "empty" } + }, + async writeText(text, options) { + writes.push({ text, options }) + return { status: "written" } + }, + async clear(options) { + clears.push(options) + return { status: "cleared" } + }, + async dispose() { + disposeCount++ + }, + ...overrides, + } + return { + backend, + reads, + writes, + clears, + get disposeCount() { + return disposeCount + }, + } +} + +const createHost = (backend: HostClipboardBackend, options: HostClipboardOptions = {}) => + createHostClipboardWithBackend(options, () => backend) + +describe("createHostClipboard", () => { + it("validates configuration before dispatch", () => { + const { backend } = createBackend() + const expectInvalidNumbers = (names: readonly (keyof HostClipboardOptions)[], values: readonly number[]) => { + for (const name of names) { + for (const value of values) { + expect(() => createHost(backend, { [name]: value } as HostClipboardOptions)).toThrow(RangeError) + } + } + } + + const invalidU32 = [-1, 1.5, Number.NaN, Number.POSITIVE_INFINITY, 0x1_0000_0000] + expectInvalidNumbers( + [ + "timeoutMs", + "maxReadBytes", + "maxWriteBytes", + "maxImagePixels", + "maxConversionBytes", + "maxConcurrentOperations", + "maxProviderTransfers", + "maxWorkUnitsPerDrain", + ], + invalidU32, + ) + expectInvalidNumbers(["maxConcurrentOperations", "maxProviderTransfers", "maxWorkUnitsPerDrain"], [0]) + for (const waylandSeat of ["", "seat\0name"]) { + expect(() => createHost(backend, { waylandSeat })).toThrow(TypeError) + } + }) + + it("does not dispatch backend work when the operation timeout is zero", async () => { + const fake = createBackend() + const host = createHost(fake.backend, { timeoutMs: 0 }) + + const results = await Promise.all([ + host.read({ preferredTypes: ["text/plain"] }), + host.writeText("text"), + host.clear(), + ]) + expect(results.map(({ status }) => status)).toEqual(["timed-out", "timed-out", "timed-out"]) + expect([fake.reads.length, fake.writes.length, fake.clears.length]).toEqual([0, 0, 0]) + await host.dispose() + }) + + it("preserves a one millisecond backend budget while an exact timeout remainder is positive", () => { + expect(normalizeRemainingTimeout(1, 0.6)).toBe(1) + expect(normalizeRemainingTimeout(1, 1)).toBe(0) + expect(normalizeRemainingTimeout(1, 1.1)).toBe(0) + }) + + it("normalizes defaults, selections, MIME types, signals, and timeout values", async () => { + const fake = createBackend() + const host = createHost(fake.backend) + + await host.read({ preferredTypes: ["Image/PNG", "Text/Plain"] }) + await host.writeText("hello") + await host.clear({ selection: "primary" }) + + expect(fake.reads[0]?.preferredTypes).toEqual(["image/png", "text/plain"]) + expect(fake.reads[0]?.selection).toBe("clipboard") + expect(fake.reads[0]?.maxBytes).toBe(8 * 1024 * 1024) + expect(fake.reads[0]?.timeoutMs).toBeLessThanOrEqual(1_000) + expect(fake.reads[0]?.timeoutMs).toBeGreaterThanOrEqual(0) + expect(fake.reads[0]?.signal).toBeInstanceOf(AbortSignal) + expect(fake.writes[0]?.options.selection).toBe("clipboard") + expect(fake.writes[0]?.options.timeoutMs).toBeLessThanOrEqual(1_000) + expect(fake.clears[0]?.selection).toBe("primary") + await host.dispose() + }) + + it("passes every normalized construction option to the internal backend factory", async () => { + const fake = createBackend() + let received: NormalizedHostClipboardOptions | undefined + const options = { + timeoutMs: 9, + maxReadBytes: 10, + maxWriteBytes: 11, + maxImagePixels: 12, + maxConversionBytes: 13, + maxConcurrentOperations: 14, + maxProviderTransfers: 15, + maxWorkUnitsPerDrain: 16, + waylandSeat: "seat0", + } + const host = createHostClipboardWithBackend(options, (normalized) => { + received = normalized + return fake.backend + }) + + expect(received).toEqual(options) + await host.dispose() + }) + + it("validates and normalizes MIME preferences at native protocol boundaries", async () => { + const fake = createBackend() + const host = createHost(fake.backend) + + const invalidPreferences = [ + [[], TypeError], + [["text/plain; charset=utf-8"], TypeError], + [["text"], TypeError], + [Array.from({ length: 65 }, (_, index) => `application/x-${index}`), RangeError], + [[`application/${"a".repeat(244)}`], RangeError], + ] as const + for (const [preferredTypes, error] of invalidPreferences) { + await expect(host.read({ preferredTypes: preferredTypes as unknown as [string, ...string[]] })).rejects.toThrow( + error, + ) + } + expect(fake.reads).toHaveLength(0) + const preferredTypes = Array.from({ length: 64 }, (_, index) => `Application/X-${index}`) as [string, ...string[]] + preferredTypes[63] = `Application/${"A".repeat(243)}` + + await host.read({ preferredTypes }) + const normalizedTypes = preferredTypes.map((mimeType) => mimeType.toLowerCase()) as [string, ...string[]] + expect(fake.reads).toHaveLength(1) + expect(fake.reads[0]?.preferredTypes).toEqual(normalizedTypes) + expect(fake.reads[0]?.preferredTypes[63]).toHaveLength(255) + await host.read({ preferredTypes: ["Application/Foo*Bar"] }) + expect(fake.reads[1]?.preferredTypes).toEqual(["application/foo*bar"]) + await host.dispose() + }) + + it("keeps encoded PNG bytes and decoded images valid across disposal", async () => { + const fake = createBackend({ + async read() { + return { status: "read", representation: { mimeType: "image/png", bytes: PNG_1X1.slice() } } + }, + }) + const host = createHost(fake.backend) + try { + const result = await host.read({ preferredTypes: ["image/png"] }) + expect(result.status).toBe("read") + if (result.status !== "read") return + + const encoded = result.representation.bytes + const expectedEncoded = encoded.slice() + const image = NativeImage.decode(encoded) + try { + const expectedRaw = image.raw().data + await host.dispose() + expect(encoded).toEqual(expectedEncoded) + + encoded.fill(0) + expect(image.raw().data).toEqual(expectedRaw) + } finally { + image.dispose() + } + } finally { + await host.dispose() + } + }) + + it("passes through MIME-tagged PNG bytes that fail image decoding", async () => { + const malformed = PNG_1X1.slice() + malformed[29] ^= 1 + const fake = createBackend({ + async read() { + return { status: "read", representation: { mimeType: "image/png", bytes: malformed } } + }, + }) + const host = createHost(fake.backend) + try { + const result = await host.read({ preferredTypes: ["image/png"] }) + expect(result).toEqual({ status: "read", representation: { mimeType: "image/png", bytes: malformed } }) + expect(() => NativeImage.decode(malformed)).toThrow("malformed image data") + } finally { + await host.dispose() + } + }) + + it("validates UTF-8 text and the write limit before dispatch", async () => { + const fake = createBackend() + const host = createHost(fake.backend, { maxWriteBytes: 4 }) + + for (const [text, error] of [ + ["", "non-empty"], + ["a\0b", "NUL"], + ["hello", RangeError], + ["世界", RangeError], + ["ééé", RangeError], + ] as const) { + await expect(host.writeText(text)).rejects.toThrow(error) + } + expect(fake.writes).toHaveLength(0) + await host.writeText("four") + await host.writeText("éé") + expect(fake.writes).toHaveLength(2) + await host.dispose() + }) + + it("does not dispatch a pre-aborted operation and composes later caller cancellation", async () => { + let observedSignal: AbortSignal | undefined + const fake = createBackend({ + async read(options) { + observedSignal = options.signal + return await new Promise((resolve) => { + options.signal.addEventListener("abort", () => resolve({ status: "cancelled" }), { once: true }) + }) + }, + }) + const host = createHost(fake.backend) + const preAborted = AbortSignal.abort() + + expect(await host.read({ preferredTypes: ["text/plain"], signal: preAborted })).toEqual({ status: "cancelled" }) + expect(observedSignal).toBeUndefined() + + const controller = new AbortController() + const pending = host.read({ preferredTypes: ["text/plain"], signal: controller.signal }) + controller.abort() + expect(await pending).toEqual({ status: "cancelled" }) + expect(observedSignal?.aborted).toBe(true) + await host.dispose() + }) + + it("preserves a tighter caller operation limit", async () => { + let release: (() => void) | undefined + let readCount = 0 + const fake = createBackend({ + async read() { + readCount += 1 + await new Promise((resolve) => { + release = resolve + }) + return { status: "empty" } + }, + }) + const host = createHost(fake.backend, { maxConcurrentOperations: 1 }) + const first = host.read({ preferredTypes: ["text/plain"] }) + + const second = await host.read({ preferredTypes: ["text/plain"] }) + expect(second.status).toBe("failed") + if (second.status === "failed") expect(second.error.message).toContain("operation limit") + expect(readCount).toBe(1) + + release?.() + await first + await host.dispose() + }) + + it("aborts active operations, waits for cleanup, disposes once, and rejects later calls", async () => { + let releaseCleanup: (() => void) | undefined + let backendStarted = false + const fake = createBackend({ + async read(options) { + backendStarted = true + await new Promise((resolve) => { + options.signal.addEventListener( + "abort", + () => { + releaseCleanup = resolve + }, + { once: true }, + ) + }) + return { status: "cancelled" } + }, + }) + const host = createHost(fake.backend) + const read = host.read({ preferredTypes: ["text/plain"] }) + expect(backendStarted).toBe(true) + + const firstDispose = host.dispose() + const secondDispose = host.dispose() + expect(firstDispose).toBe(secondDispose) + await Promise.resolve() + expect(fake.disposeCount).toBe(0) + releaseCleanup?.() + expect(await read).toEqual({ status: "cancelled" }) + await firstDispose + expect(fake.disposeCount).toBe(1) + await expect(host.read({ preferredTypes: ["text/plain"] })).rejects.toThrow("disposed") + await expect(host.writeText("text")).rejects.toThrow("disposed") + await expect(host.clear()).rejects.toThrow("disposed") + }) +}) diff --git a/packages/core/src/lib/host-clipboard.x11-live.test.ts b/packages/core/src/lib/host-clipboard.x11-live.test.ts new file mode 100644 index 0000000000..4e0b1e50b4 --- /dev/null +++ b/packages/core/src/lib/host-clipboard.x11-live.test.ts @@ -0,0 +1,229 @@ +import { spawn, spawnSync, type ChildProcessWithoutNullStreams } from "node:child_process" + +import { expect, test } from "bun:test" +import { createHostClipboard, type ClipboardSelection, type HostClipboardService } from "./clipboard.js" + +const LIVE = process.platform === "linux" && process.env.OTUI_LIVE_X11_CLIPBOARD === "1" +const PROCESS_TIMEOUT_MS = 10_000 +const OWNER_TIMEOUT_MS = 30_000 +const MAX_ORACLE_OUTPUT_BYTES = 8 * 1024 * 1024 +const encoder = new TextEncoder() + +interface XclipResult { + readonly code: number | null + readonly signal: NodeJS.Signals | null + readonly stdout: Buffer + readonly stderr: Buffer + readonly error?: Error +} + +interface XclipOwner { + readonly process: ChildProcessWithoutNullStreams + readonly done: Promise + readonly ready: Promise +} + +const startOracle = ( + command: "xclip" | "xsel", + args: readonly string[], + input?: string, + timeoutMs = PROCESS_TIMEOUT_MS, +): XclipOwner => { + const child = spawn(command, [...args], { stdio: "pipe" }) + const stdout: Buffer[] = [] + const stderr: Buffer[] = [] + let stdoutBytes = 0 + let stderrBytes = 0 + let processError: Error | undefined + const timer = setTimeout(() => { + processError = new Error(`${command} exceeded its ${timeoutMs}ms timeout`) + child.kill("SIGKILL") + }, timeoutMs) + + child.stdout.on("data", (chunk: Buffer) => { + stdoutBytes += chunk.byteLength + if (stdoutBytes > MAX_ORACLE_OUTPUT_BYTES) { + processError = new Error(`xclip output exceeded ${MAX_ORACLE_OUTPUT_BYTES} bytes`) + child.kill("SIGKILL") + return + } + stdout.push(chunk) + }) + child.stderr.on("data", (chunk: Buffer) => { + stderrBytes += chunk.byteLength + if (stderrBytes > MAX_ORACLE_OUTPUT_BYTES) { + processError = new Error(`xclip diagnostics exceeded ${MAX_ORACLE_OUTPUT_BYTES} bytes`) + child.kill("SIGKILL") + return + } + stderr.push(chunk) + }) + + const done = new Promise((resolve) => { + child.once("error", (error) => { + processError = error + }) + child.once("close", (code, signal) => { + clearTimeout(timer) + resolve({ + code, + signal, + stdout: Buffer.concat(stdout), + stderr: Buffer.concat(stderr), + error: processError, + }) + }) + }) + + const ready = new Promise((resolve, reject) => { + child.stdin.end(input, (error?: Error | null) => { + if (error) reject(error) + else resolve() + }) + }) + return { process: child, done, ready } +} + +const xclipRead = async (selection: ClipboardSelection): Promise => { + const oracle = startOracle("xclip", ["-selection", selection, "-out"]) + await oracle.ready + return oracle.done +} + +const startXclipOwner = async (selection: ClipboardSelection, text: string): Promise => { + const owner = startOracle("xclip", ["-selection", selection, "-in", "-quiet"], text, OWNER_TIMEOUT_MS) + await owner.ready + if (owner.process.exitCode !== null || owner.process.signalCode !== null) { + const result = await owner.done + throw new Error(`xclip failed to own ${selection}: ${result.stderr.toString() || `exit ${result.code}`}`) + } + return owner +} + +const startXselOwner = async (selection: ClipboardSelection, text: string): Promise => { + const selectionFlag = selection === "clipboard" ? "--clipboard" : "--primary" + const owner = startOracle("xsel", [selectionFlag, "--input", "--nodetach"], text, OWNER_TIMEOUT_MS) + await owner.ready + if (owner.process.exitCode !== null || owner.process.signalCode !== null) { + const result = await owner.done + throw new Error(`xsel failed to own ${selection}: ${result.stderr.toString() || `exit ${result.code}`}`) + } + return owner +} + +const waitForOwner = async (owner: XclipOwner, selection: ClipboardSelection, expected: string): Promise => { + const expectedBytes = Buffer.from(expected) + for (let attempt = 0; attempt < 50; attempt += 1) { + if (owner.process.exitCode !== null || owner.process.signalCode !== null) { + const result = await owner.done + throw new Error(`external owner exited before owning ${selection}: ${result.stderr.toString()}`) + } + const result = await xclipRead(selection) + if (!result.error && result.signal === null && result.code === 0 && result.stdout.equals(expectedBytes)) return + await new Promise((resolve) => setTimeout(resolve, 10)) + } + throw new Error(`external owner did not acquire ${selection}`) +} + +const stopOwner = async (owner: XclipOwner | undefined): Promise => { + if (!owner) return + if (owner.process.exitCode === null && owner.process.signalCode === null) owner.process.kill("SIGTERM") + await owner.done +} + +const disposeHost = async (host: HostClipboardService | undefined): Promise => { + if (host) await host.dispose() +} + +const assertXclipRead = async (selection: ClipboardSelection, expected: string): Promise => { + const result = await xclipRead(selection) + expect(result.error).toBeUndefined() + expect(result.signal).toBeNull() + expect(result.code, result.stderr.toString()).toBe(0) + expect(result.stdout.equals(Buffer.from(expected))).toBe(true) +} + +const assertHostRead = async (host: HostClipboardService, selection: ClipboardSelection, expected: string) => { + const result = await host.read({ preferredTypes: ["text/plain"], selection }) + if (result.status !== "read") { + const detail = result.status === "failed" ? `: ${result.error.message}` : "" + throw new Error(`host ${selection} read returned ${result.status}${detail}`) + } + expect(result.representation.bytes).toEqual(encoder.encode(expected)) +} + +test.skipIf(!LIVE)( + "uses a live X11 server and external clipboard tools as bidirectional oracles", + async () => { + if (!process.env.DISPLAY) throw new Error("OTUI live X11 clipboard test requires DISPLAY") + if (process.env.WAYLAND_DISPLAY) { + throw new Error("OTUI live X11 clipboard test requires WAYLAND_DISPLAY to be empty") + } + const version = spawnSync("xclip", ["-version"], { encoding: "utf8", timeout: PROCESS_TIMEOUT_MS }) + if (version.error || version.status !== 0) { + const detail = version.error?.message ?? version.stderr.trim() ?? `exit ${version.status}` + throw new Error(`OTUI live X11 clipboard test requires a working xclip: ${detail}`) + } + const xselVersion = spawnSync("xsel", ["--version"], { encoding: "utf8", timeout: PROCESS_TIMEOUT_MS }) + if (xselVersion.error || xselVersion.status !== 0) { + const detail = xselVersion.error?.message ?? xselVersion.stderr.trim() ?? `exit ${xselVersion.status}` + throw new Error(`OTUI live X11 clipboard test requires a working xsel: ${detail}`) + } + + const exactText = "OpenTUI X11 clipboard: café, 世界, مرحبا, 🙂\nsecond line\tend" + const largeText = "INCR payload 世界 🙂 0123456789\n".repeat(30_000) + const xclipLargeText = "xclip-owned INCR payload 世界 🙂 9876543210\n".repeat(18_000) + let host: HostClipboardService | undefined + let owner: XclipOwner | undefined + + try { + host = createHostClipboard({ timeoutMs: PROCESS_TIMEOUT_MS, maxReadBytes: 6 * 1024 * 1024 }) + + for (const selection of ["clipboard", "primary"] as const) { + expect(await host.writeText(exactText, { selection })).toEqual({ status: "written" }) + await assertXclipRead(selection, exactText) + + owner = await startXclipOwner(selection, exactText) + await waitForOwner(owner, selection, exactText) + await assertHostRead(host, selection, exactText) + await stopOwner(owner) + owner = undefined + } + + expect(await host.writeText(largeText, { selection: "clipboard" })).toEqual({ status: "written" }) + await assertXclipRead("clipboard", largeText) + + owner = await startXclipOwner("clipboard", xclipLargeText) + await waitForOwner(owner, "clipboard", xclipLargeText) + await assertHostRead(host, "clipboard", xclipLargeText) + await stopOwner(owner) + owner = undefined + + await host.dispose() + host = createHostClipboard({ timeoutMs: PROCESS_TIMEOUT_MS, maxReadBytes: 6 * 1024 * 1024 }) + owner = await startXselOwner("primary", largeText) + await waitForOwner(owner, "primary", largeText) + await assertHostRead(host, "primary", largeText) + await stopOwner(owner) + owner = undefined + + for (const selection of ["clipboard", "primary"] as const) { + expect(await host.writeText(`clear-${selection}`, { selection })).toEqual({ status: "written" }) + expect(await host.clear({ selection })).toEqual({ status: "cleared" }) + const cleared = await xclipRead(selection) + expect(cleared.error).toBeUndefined() + expect(cleared.signal).toBeNull() + expect(cleared.code).not.toBe(0) + expect(cleared.stdout).toHaveLength(0) + } + + expect(await host.writeText("owner disposed", { selection: "clipboard" })).toEqual({ status: "written" }) + await host.dispose() + host = undefined + } finally { + await stopOwner(owner) + await disposeHost(host) + } + }, + 60_000, +) diff --git a/packages/core/src/lib/terminal-capability-detection.test.ts b/packages/core/src/lib/terminal-capability-detection.test.ts index dff697bd4d..cd7e192355 100644 --- a/packages/core/src/lib/terminal-capability-detection.test.ts +++ b/packages/core/src/lib/terminal-capability-detection.test.ts @@ -179,7 +179,7 @@ describe("renderer capabilities event", () => { const events: any[] = [] renderer.on("capabilities", (caps) => events.push({ ...caps })) - // Simulate all 10 Kitty capability responses (as they arrive separately) + // Simulate Kitty capability responses as they arrive separately. const kittyResponses = [ "\x1b[?1016;2$y", // 1. sgr_pixels "\x1b[?2027;0$y", // 2. unicode query @@ -191,6 +191,7 @@ describe("renderer capabilities event", () => { "\x1b[1;3R", // 8. scaled_text (CPR) "\x1bP>|kitty(0.42.2)\x1b\\", // 9. xtversion (triggers kitty detection) "\x1b[?0u", // 10. kitty keyboard query + "\x1b_Gi=31337;OK\x1b\\", // 11. exact graphics query response ] for (const response of kittyResponses) { @@ -198,8 +199,7 @@ describe("renderer capabilities event", () => { await new Promise((resolve) => setTimeout(resolve, 10)) } - // Should have received 10 capability events - expect(events.length).toBe(10) + expect(events.length).toBe(11) // First event: sgr_pixels detected expect(events[0].sgr_pixels).toBe(true) @@ -212,7 +212,7 @@ describe("renderer capabilities event", () => { expect(events[8].terminal.version).toBe("0.42.2") // Final state should have all kitty capabilities - const finalCaps = events[9] + const finalCaps = events[10] expect(finalCaps.kitty_keyboard).toBe(true) expect(finalCaps.sgr_pixels).toBe(true) expect(finalCaps.color_scheme_updates).toBe(true) @@ -220,6 +220,7 @@ describe("renderer capabilities event", () => { expect(finalCaps.sync).toBe(true) expect(finalCaps.explicit_width).toBe(true) expect(finalCaps.scaled_text).toBe(true) + expect(finalCaps.kitty_graphics).toBe(true) renderer.destroy() }) diff --git a/packages/core/src/renderables/Image.ts b/packages/core/src/renderables/Image.ts new file mode 100644 index 0000000000..4bd202f463 --- /dev/null +++ b/packages/core/src/renderables/Image.ts @@ -0,0 +1,237 @@ +import { Renderable, type RenderableOptions } from "../Renderable.js" +import { NativeImage, type ImageSource } from "../image.js" +import type { OptimizedBuffer } from "../buffer.js" +import { RGBA } from "../lib/RGBA.js" +import type { ImageRenderProtocol, RenderContext, TerminalCapabilities } from "../types.js" + +export type ImageFit = "fit" | "cover" | "fill" + +const TRANSPARENT = RGBA.fromValues(0, 0, 0, 0) + +export interface ImageRenderableOptions extends RenderableOptions { + source?: ImageSource + fit?: ImageFit + protocol?: ImageRenderProtocol + onLoad?: (image: NativeImage) => void + onError?: (error: unknown) => void +} + +export function resolveImageRenderProtocol( + requested: ImageRenderProtocol, + capabilities: TerminalCapabilities | null, + hasResolution: boolean, +): Exclude { + if (requested !== "auto") return requested === "sixel" && !hasResolution ? "blocks" : requested + const configured = capabilities?.image_protocol ?? "auto" + if (configured !== "auto") return configured === "sixel" && !hasResolution ? "blocks" : configured + if (!capabilities || capabilities.multiplexer === "tmux") return "blocks" + if (capabilities.kitty_graphics) return "kitty" + if (capabilities.sixel && hasResolution) return "sixel" + return "blocks" +} + +function pixelResolution(ctx: RenderContext): { width: number; height: number } | null { + const terminalWidth = ctx.terminalWidth ?? 0 + const terminalHeight = ctx.terminalHeight ?? 0 + const resolution = terminalWidth > 0 && terminalHeight > 0 ? ctx.resolution : null + return resolution && resolution.width > 0 && resolution.height > 0 ? resolution : null +} + +export class ImageRenderable extends Renderable { + private _source: ImageSource | undefined + private _image: NativeImage | null = null + private _loadError: unknown = null + private _loadController: AbortController | null = null + public onLoad?: (image: NativeImage) => void + public onError?: (error: unknown) => void + private _fit: ImageFit + private _protocol: ImageRenderProtocol + public loadPromise: Promise | null = null + + constructor(ctx: RenderContext, options: ImageRenderableOptions) { + super(ctx, options) + this._fit = options.fit ?? "fit" + this._protocol = options.protocol ?? "auto" + this.onLoad = options.onLoad + this.onError = options.onError + if (options.source !== undefined) this.source = options.source + } + + public get source(): ImageSource | undefined { + return this._source + } + + public set source(source: ImageSource | undefined) { + source ??= undefined + if (source === this._source) return + this._source = source + this._loadController?.abort() + this._loadController = null + + if (source === undefined) { + this._loadError = null + this._image?.dispose() + this._image = null + this.loadPromise = null + this.requestRender() + return + } + + const controller = new AbortController() + this._loadController = controller + this._loadError = null + this.loadPromise = this.load(source, controller) + } + + public get image(): NativeImage | null { + return this._image + } + + public get fit(): ImageFit { + return this._fit + } + + public set fit(value: ImageFit | null | undefined) { + const next = value ?? "fit" + if (this._fit === next) return + this._fit = next + this.requestRender() + } + + public get protocol(): ImageRenderProtocol { + return this._protocol + } + + public set protocol(value: ImageRenderProtocol | null | undefined) { + const next = value ?? "auto" + if (this._protocol === next) return + this._protocol = next + this.requestRender() + } + + public get effectiveProtocol(): Exclude { + return resolveImageRenderProtocol(this._protocol, this._ctx.capabilities, pixelResolution(this._ctx) !== null) + } + + public get cellAspectRatio(): number { + const resolution = pixelResolution(this._ctx) + if (!resolution) return 2 + const cellWidth = resolution.width / this._ctx.terminalWidth! + const cellHeight = resolution.height / this._ctx.terminalHeight! + return cellWidth > 0 && cellHeight > 0 ? cellHeight / cellWidth : 2 + } + + public getFittedSize( + targetWidth: number, + targetHeight: number, + cellAspectRatio: number = this.cellAspectRatio, + sourceWidth: number = this._image?.width ?? 0, + sourceHeight: number = this._image?.height ?? 0, + ): { width: number; height: number } { + if (sourceWidth <= 0 || sourceHeight <= 0 || targetWidth <= 0 || targetHeight <= 0) return { width: 0, height: 0 } + if (this._fit === "fill") return { width: targetWidth, height: targetHeight } + + const displayAspect = (sourceWidth / sourceHeight) * cellAspectRatio + const scale = + this._fit === "fit" + ? Math.min(targetWidth / displayAspect, targetHeight) + : Math.max(targetWidth / displayAspect, targetHeight) + return { + width: Math.max(1, Math.round(displayAspect * scale)), + height: Math.max(1, Math.round(scale)), + } + } + + public get loading(): boolean { + return this._loadController !== null + } + + public get loadError(): unknown { + return this._loadError + } + + public override render(buffer: OptimizedBuffer, deltaTime: number): void { + if (this.buffered) this.frameBuffer?.clear(TRANSPARENT) + super.render(buffer, deltaTime) + } + + protected renderSelf(buffer: OptimizedBuffer): void { + if (!this._image || this.width <= 0 || this.height <= 0) return + const fitted = + this._fit === "cover" ? { width: this.width, height: this.height } : this.getFittedSize(this.width, this.height) + if (fitted.width <= 0 || fitted.height <= 0) return + const originX = this.buffered ? 0 : this._screenX + const originY = this.buffered ? 0 : this._screenY + const x = originX + Math.floor((this.width - fitted.width) / 2) + const y = originY + Math.floor((this.height - fitted.height) / 2) + const resolution = pixelResolution(this._ctx) + const pixelWidth = resolution + ? Math.max(1, Math.round((fitted.width * resolution.width) / this._ctx.terminalWidth!)) + : 0 + const pixelHeight = resolution + ? Math.max(1, Math.round((fitted.height * resolution.height) / this._ctx.terminalHeight!)) + : 0 + let sourceX = 0 + let sourceY = 0 + let sourceWidth = this._image.width + let sourceHeight = this._image.height + if (this._fit === "cover") { + const targetAspect = this.width / (this.height * this.cellAspectRatio) + const sourceAspect = sourceWidth / sourceHeight + if (sourceAspect > targetAspect) { + sourceWidth = Math.max(1, Math.round(sourceHeight * targetAspect)) + sourceX = Math.floor((this._image.width - sourceWidth) / 2) + } else { + sourceHeight = Math.max(1, Math.round(sourceWidth / targetAspect)) + sourceY = Math.floor((this._image.height - sourceHeight) / 2) + } + } + buffer.drawImage( + this._image, + x, + y, + fitted.width, + fitted.height, + pixelWidth, + pixelHeight, + sourceX, + sourceY, + sourceWidth, + sourceHeight, + this._protocol, + ) + } + + private async load(source: ImageSource, controller: AbortController): Promise { + let image: NativeImage + try { + image = await NativeImage.load(source, { signal: controller.signal }) + } catch (error) { + if (controller.signal.aborted || this.isDestroyed || this._loadController !== controller) return + this._loadController = null + this._loadError = error + this.onError?.(error) + return + } + + if (this.isDestroyed || this._loadController !== controller) { + image.dispose() + return + } + + const previous = this._image + this._image = image + this._loadController = null + previous?.dispose() + this.requestRender() + this.onLoad?.(image) + } + + protected destroySelf(): void { + this._loadController?.abort() + this._loadController = null + this._image?.dispose() + this._image = null + super.destroySelf() + } +} diff --git a/packages/core/src/renderables/index.ts b/packages/core/src/renderables/index.ts index 5bf892004f..eb21c8c841 100644 --- a/packages/core/src/renderables/index.ts +++ b/packages/core/src/renderables/index.ts @@ -8,6 +8,7 @@ export * from "./Diff.js" export * from "./EditBufferRenderable.js" export * from "./FrameBuffer.js" export * from "./Input.js" +export * from "./Image.js" export * from "./LineNumberRenderable.js" export * from "./Markdown.js" export * from "./ScrollBar.js" diff --git a/packages/core/src/renderer.ts b/packages/core/src/renderer.ts index 78b25709a5..d1b0bc0d39 100644 --- a/packages/core/src/renderer.ts +++ b/packages/core/src/renderer.ts @@ -440,9 +440,12 @@ const CHAR_FLAG_MASK = 0xc0000000 >>> 0 class ScrollbackSnapshotRenderContext extends EventEmitter implements RenderContext { public width: number public height: number + public terminalWidth: number + public terminalHeight: number + public resolution: PixelResolution | null public frameId = 0 public widthMethod: WidthMethod - public capabilities: TerminalCapabilities | null = null + public capabilities: TerminalCapabilities | null public hasSelection: boolean = false public currentFocusedRenderable: Renderable | null = null public keyInput: KeyHandler @@ -450,10 +453,22 @@ class ScrollbackSnapshotRenderContext extends EventEmitter implements RenderCont private lifecyclePasses: Set = new Set() - constructor(width: number, height: number, widthMethod: WidthMethod) { + constructor( + width: number, + height: number, + widthMethod: WidthMethod, + terminalWidth: number = width, + terminalHeight: number = height, + resolution: PixelResolution | null = null, + capabilities: TerminalCapabilities | null = null, + ) { super() this.width = width this.height = height + this.terminalWidth = terminalWidth + this.terminalHeight = terminalHeight + this.resolution = resolution + this.capabilities = capabilities this.widthMethod = widthMethod this.keyInput = new KeyHandler() this._internalKeyInput = new InternalKeyHandler() @@ -513,6 +528,7 @@ const DEFAULT_FORWARDED_ENV_KEYS = [ "ZELLIJ_PANE_ID", "TERM", "OPENTUI_GRAPHICS", + "OPENTUI_IMAGE_PROTOCOL", "TERM_PROGRAM", "TERM_PROGRAM_VERSION", "TERM_FEATURES", @@ -812,6 +828,7 @@ export class CliRenderer extends EventEmitter implements RenderContext { private resizeTimeoutId: TimerHandle | null = null private capabilityTimeoutId: TimerHandle | null = null + private terminalKeepAliveTimer: ReturnType | null = null private xtVersionWaiters = new Set<() => void>() private splitStartupSeedTimeoutId: TimerHandle | null = null private pendingSplitStartupCursorSeed: boolean = false @@ -985,8 +1002,8 @@ export class CliRenderer extends EventEmitter implements RenderContext { * - Calls `lib.createRenderer` → native Zig allocation * - Registers in the process-wide `rendererTracker` * - Adds `process.on(...)` listeners for SIGWINCH (process.stdout only), - * "warning", "uncaughtException", "unhandledRejection", "beforeExit", - * plus the configured `exitSignals` + * "warning", "uncaughtException", "unhandledRejection", plus the + * configured `exitSignals` * - Replaces `global.requestAnimationFrame` with the renderer's impl * - When `setupTerminal()` is called, it will put `stdin` in raw mode and * call `stdin.resume()` @@ -1029,6 +1046,8 @@ export class CliRenderer extends EventEmitter implements RenderContext { let feed: NativeSpanFeed | null = null if (useFeedOutput) { try { + // Keep high-level feeds growable and uncapped so control/shutdown writes + // can publish while async Writable callbacks still pin earlier chunks. feed = NativeSpanFeed.create() } catch (error) { throw new Error( @@ -1120,8 +1139,8 @@ export class CliRenderer extends EventEmitter implements RenderContext { "SIGQUIT", // Ctrl+\ "SIGABRT", // Abort signal "SIGHUP", // Hangup (terminal closed) + "SIGPIPE", // Broken output pipe "SIGBREAK", // Ctrl+Break on Windows - "SIGPIPE", // Broken pipe "SIGBUS", // Bus error ] @@ -1178,8 +1197,6 @@ export class CliRenderer extends EventEmitter implements RenderContext { process.on("uncaughtException", this.handleError) process.on("unhandledRejection", this.handleError) - process.on("beforeExit", this.exitHandler) - const useKittyForParsing = kittyConfig !== null this._keyHandler = new InternalKeyHandler() this._keyHandler.on("keypress", (event) => { @@ -1276,6 +1293,17 @@ export class CliRenderer extends EventEmitter implements RenderContext { this._exitListenersAdded = true } + private startTerminalKeepAlive(): void { + if (this.stdin !== process.stdin || this.terminalKeepAliveTimer !== null) return + this.terminalKeepAliveTimer = setInterval(() => {}, 60_000) + } + + private stopTerminalKeepAlive(): void { + if (this.terminalKeepAliveTimer === null) return + clearInterval(this.terminalKeepAliveTimer) + this.terminalKeepAliveTimer = null + } + private removeExitListeners(): void { if (!this._exitListenersAdded || this.exitSignals.length === 0) return @@ -1872,7 +1900,15 @@ export class CliRenderer extends EventEmitter implements RenderContext { const tailColumn = renderer.getPendingSplitTailColumn() const firstLineOffset = !startOnNewLine && tailColumn > 0 && tailColumn < renderer.width ? tailColumn : 0 - const snapshotContext = new ScrollbackSnapshotRenderContext(renderer.width, 1, renderer.widthMethod) + const snapshotContext = new ScrollbackSnapshotRenderContext( + renderer.width, + 1, + renderer.widthMethod, + renderer._terminalWidth, + renderer._terminalHeight, + renderer.resolution, + renderer.capabilities, + ) let firstLineOffsetOwner: Renderable | null = null const renderContext = Object.create(snapshotContext) as RenderContext Object.defineProperty(renderContext, "claimFirstLineOffset", { @@ -1910,6 +1946,10 @@ export class CliRenderer extends EventEmitter implements RenderContext { let surfaceWidth = renderer.width let surfaceHeight = 1 let surfaceWidthMethod = renderer.widthMethod + let surfaceTerminalWidth = renderer._terminalWidth + let surfaceTerminalHeight = renderer._terminalHeight + let surfaceResolutionWidth = renderer.resolution?.width ?? null + let surfaceResolutionHeight = renderer.resolution?.height ?? null let surfaceDestroyed = false let hasRendered = false let nextCommitStartOnNewLine = startOnNewLine @@ -1934,7 +1974,14 @@ export class CliRenderer extends EventEmitter implements RenderContext { } const assertGeometryStillCurrent = (): void => { - if (renderer.width !== surfaceWidth || renderer.widthMethod !== surfaceWidthMethod) { + if ( + renderer.width !== surfaceWidth || + renderer.widthMethod !== surfaceWidthMethod || + renderer._terminalWidth !== surfaceTerminalWidth || + renderer._terminalHeight !== surfaceTerminalHeight || + (renderer.resolution?.width ?? null) !== surfaceResolutionWidth || + (renderer.resolution?.height ?? null) !== surfaceResolutionHeight + ) { throw new Error("ScrollbackSurface.commitRows requires render() after renderer geometry changes") } } @@ -2014,6 +2061,10 @@ export class CliRenderer extends EventEmitter implements RenderContext { snapshotContext.width = width snapshotContext.widthMethod = widthMethod + snapshotContext.terminalWidth = renderer._terminalWidth + snapshotContext.terminalHeight = renderer._terminalHeight + snapshotContext.resolution = renderer.resolution + snapshotContext.capabilities = renderer.capabilities publicRoot.width = width const renderPass = (height: number): void => { @@ -2044,6 +2095,10 @@ export class CliRenderer extends EventEmitter implements RenderContext { surfaceWidth = width surfaceHeight = measuredHeight surfaceWidthMethod = widthMethod + surfaceTerminalWidth = renderer._terminalWidth + surfaceTerminalHeight = renderer._terminalHeight + surfaceResolutionWidth = renderer.resolution?.width ?? null + surfaceResolutionHeight = renderer.resolution?.height ?? null hasRendered = true return } @@ -2056,6 +2111,10 @@ export class CliRenderer extends EventEmitter implements RenderContext { surfaceWidth = width surfaceHeight = targetHeight surfaceWidthMethod = widthMethod + surfaceTerminalWidth = renderer._terminalWidth + surfaceTerminalHeight = renderer._terminalHeight + surfaceResolutionWidth = renderer.resolution?.width ?? null + surfaceResolutionHeight = renderer.resolution?.height ?? null hasRendered = true } @@ -2206,7 +2265,15 @@ export class CliRenderer extends EventEmitter implements RenderContext { throw new Error('writeToScrollback requires screenMode "split-footer" and externalOutputMode "capture-stdout"') } - const snapshotContext = new ScrollbackSnapshotRenderContext(this.width, this.height, this.widthMethod) + const snapshotContext = new ScrollbackSnapshotRenderContext( + this.width, + this.height, + this.widthMethod, + this._terminalWidth, + this._terminalHeight, + this.resolution, + this.capabilities, + ) const snapshot = write({ width: this.width, widthMethod: this.widthMethod, @@ -2448,7 +2515,15 @@ export class CliRenderer extends EventEmitter implements RenderContext { private createStdoutSnapshotCommit(line: string, trailingNewline: boolean): ExternalOutputCommit { // Convert captured stdout into the same commit shape used by writeToScrollback. // One commit format keeps split append behavior consistent across both sources. - const snapshotContext = new ScrollbackSnapshotRenderContext(this.width, 1, this.widthMethod) + const snapshotContext = new ScrollbackSnapshotRenderContext( + this.width, + 1, + this.widthMethod, + this._terminalWidth, + this._terminalHeight, + this.resolution, + this.capabilities, + ) const maxWidth = Math.max(1, this.width) const lineCells = [...line] const rowColumns = Math.min(lineCells.length, maxWidth) @@ -2565,6 +2640,7 @@ export class CliRenderer extends EventEmitter implements RenderContext { let acceptedCommits = 0 let nativeBackpressured = false let nativeFailed = false + let nextRenderOffset = this.renderOffset for (const [index, commit] of commits.entries()) { // Force repaint only on the last commit in a frame. Repainting after every @@ -2598,16 +2674,11 @@ export class CliRenderer extends EventEmitter implements RenderContext { break } - this.renderOffset = nativeResult.renderOffset - this.recordSplitCommit(commit) + nextRenderOffset = nativeResult.renderOffset hasCommittedOutput = true acceptedCommits++ } - if (acceptedCommits > 0) { - this.externalOutputQueue.drop(acceptedCommits) - } - if (nativeFailed) { return this.reportNativeRenderFailure() } @@ -2617,6 +2688,12 @@ export class CliRenderer extends EventEmitter implements RenderContext { return "backpressured" } + if (acceptedCommits > 0) { + this.renderOffset = nextRenderOffset + for (const commit of commits.slice(0, acceptedCommits)) this.recordSplitCommit(commit) + this.externalOutputQueue.drop(acceptedCommits) + } + if (!hasCommittedOutput) { const nativeResult = this.lib.repaintSplitFooter( this.rendererPtr, @@ -3380,6 +3457,7 @@ export class CliRenderer extends EventEmitter implements RenderContext { const resolution = parsePixelResolution(sequence) if (resolution) { this._resolution = resolution + this.requestRender() } this.waitingForPixelResolution = false this.updateStdinParserProtocolContext({ pixelResolutionQueryActive: false }, true) @@ -3397,6 +3475,7 @@ export class CliRenderer extends EventEmitter implements RenderContext { this.stdin.on("data", this.stdinListener) this.stdin.resume() + this.startTerminalKeepAlive() } private dispatchMouseEvent( @@ -3746,6 +3825,7 @@ export class CliRenderer extends EventEmitter implements RenderContext { this._terminalWidth = width this._terminalHeight = height + this._resolution = null this.queryPixelResolution() this.setCapturedRenderable(undefined) @@ -4027,6 +4107,7 @@ export class CliRenderer extends EventEmitter implements RenderContext { }) this.stdinParser?.reset() this.stdin.removeListener("data", this.stdinListener) + this.stopTerminalKeepAlive() this.themeModeState.cancelRefresh() @@ -4051,6 +4132,7 @@ export class CliRenderer extends EventEmitter implements RenderContext { while (this.stdin.read() !== null) {} this.stdin.on("data", this.stdinListener) this.stdin.resume() + this.startTerminalKeepAlive() this.addExitListeners() const resumePreservedNonAltSurface = @@ -4160,7 +4242,6 @@ export class CliRenderer extends EventEmitter implements RenderContext { process.removeListener("uncaughtException", this.handleError) process.removeListener("unhandledRejection", this.handleError) process.removeListener("warning", this.warningHandler) - process.removeListener("beforeExit", this.exitHandler) this.removeExitListeners() if (this.resizeTimeoutId !== null) { @@ -4202,6 +4283,7 @@ export class CliRenderer extends EventEmitter implements RenderContext { this.setCapturedRenderable(undefined) this.stdin.removeListener("data", this.stdinListener) + this.stopTerminalKeepAlive() if (this.stdin.setRawMode) { try { this.stdin.setRawMode(false) @@ -4305,11 +4387,9 @@ export class CliRenderer extends EventEmitter implements RenderContext { // d) detach the handler now that no more data will flow // e) close the feed (releases chunk memory once async handlers settle) // - // Memory-lifetime invariant: `lib.destroyRenderer` calls into Zig's - // `FeedBackend.deinit`, which is a DOCUMENTED NO-OP — feed memory is - // owned by the TS side and only released by `feed.close()` at step (e). - // Consequently, step (c)'s drain operates on still-valid chunk memory; - // there is no use-after-free window between (b) and (e). + // Memory-lifetime invariant: `FeedBackend.deinit` releases its staging + // buffer but does not own feed chunks. Those remain valid until the TS side + // calls `feed.close()` at step (e), so step (c) can safely drain them. // // Caller note: `feed.close()` is queued as a microtask when async handlers // from the final drain are still pending. If the caller tears down the diff --git a/packages/core/src/terminal-env-registry.test.ts b/packages/core/src/terminal-env-registry.test.ts index 0295f79feb..c91a8148ce 100644 --- a/packages/core/src/terminal-env-registry.test.ts +++ b/packages/core/src/terminal-env-registry.test.ts @@ -13,4 +13,5 @@ test("native terminal environment registrations match native string semantics", const generated = generateEnvMarkdown() expect(generated).toContain("## OPENTUI_FORCE_WCWIDTH") expect(generated).toContain("**Default:** *unset*") + expect(generated).toContain("Control Kitty and Sixel graphics detection") }) diff --git a/packages/core/src/testing/terminal-capabilities.ts b/packages/core/src/testing/terminal-capabilities.ts index 52f0f9decf..f516c15bbf 100644 --- a/packages/core/src/testing/terminal-capabilities.ts +++ b/packages/core/src/testing/terminal-capabilities.ts @@ -27,6 +27,7 @@ export function createTerminalCapabilities(overrides: TerminalCapabilitiesOverri explicit_cursor_positioning: false, remote: false, multiplexer: "none", + image_protocol: "auto", ...overrides, terminal: { name: "", diff --git a/packages/core/src/tests/clipboard-native-lifecycle.test.ts b/packages/core/src/tests/clipboard-native-lifecycle.test.ts new file mode 100644 index 0000000000..969f6aac8b --- /dev/null +++ b/packages/core/src/tests/clipboard-native-lifecycle.test.ts @@ -0,0 +1,91 @@ +import { setTimeout as sleep } from "node:timers/promises" + +import { expect, test } from "bun:test" + +import { + NativeClipboardCopyStatus, + NativeClipboardDestroyStatus, + NativeClipboardOperationStatus, + NativeClipboardShutdownStatus, + NativeClipboardStartStatus, + resolveRenderLib, + type ClipboardOperationHandle, + type ClipboardServiceHandle, + type ImageHandle, +} from "../zig.js" + +const READ_REQUEST = Uint8Array.of(1, 0, 0, 0, 10, 0, 0, 0, ...new TextEncoder().encode("text/plain")) + +function expectTimedOutAndDestroy(operation: ClipboardOperationHandle): void { + const lib = resolveRenderLib() + expect(lib.clipboardOperationPoll(operation)).toBe(NativeClipboardOperationStatus.TimedOut) + expect(lib.clipboardOperationResultMimeLength(operation).status).toBe(NativeClipboardCopyStatus.InvalidState) + expect(lib.clipboardOperationDestroy(operation)).toBe(NativeClipboardDestroyStatus.Destroyed) + expect(lib.clipboardOperationPoll(operation)).toBe(NativeClipboardOperationStatus.InvalidHandle) + expect(lib.clipboardOperationDestroy(operation)).toBe(NativeClipboardDestroyStatus.InvalidHandle) +} + +test("native clipboard production-symbol ABI lifecycle", async () => { + const lib = resolveRenderLib() + let service = lib.clipboardServiceCreate(3, 2) + expect(service).not.toBeNull() + if (!service) return + const operations = new Set() + try { + expect(() => (lib as typeof lib & { dispose(): void }).dispose()).toThrow("clipboard services are active") + expect(lib.clipboardServiceDrain(service)).not.toBe(2) + const createdImage = lib.imageCreateFromRgba(Uint8Array.of(1, 2, 3, 255), 1, 1, 4) + expect(createdImage.status).toBe(0) + expect(createdImage.handle).not.toBeNull() + if (createdImage.handle) { + try { + expect(lib.clipboardServicePollShutdown(createdImage.handle as unknown as ClipboardServiceHandle)).toBe( + NativeClipboardShutdownStatus.InvalidHandle, + ) + expect(lib.clipboardOperationPoll(createdImage.handle as unknown as ClipboardOperationHandle)).toBe( + NativeClipboardOperationStatus.InvalidHandle, + ) + expect(lib.imageGetInfo(service as unknown as ImageHandle).status).toBe(1) + } finally { + lib.imageDestroy(createdImage.handle) + } + } + const starts = [ + lib.clipboardReadOperationStart(service, READ_REQUEST, 0, 1024, 4096, 8192, 0), + lib.clipboardWriteOperationStart(service, new TextEncoder().encode("text"), 0, 0), + lib.clipboardClearOperationStart(service, 0, 0), + ] + for (const { operation } of starts) { + if (operation) operations.add(operation) + } + expect(starts.map(({ status }) => status)).toEqual(Array(3).fill(NativeClipboardStartStatus.Ok)) + expect(operations.size).toBe(3) + for (const operation of operations) expectTimedOutAndDestroy(operation) + operations.clear() + + expect(lib.clipboardServiceBeginShutdown(service)).toBe(NativeClipboardShutdownStatus.Pending) + expect(lib.clipboardClearOperationStart(service, 0, 0).status).toBe(NativeClipboardStartStatus.ShuttingDown) + const destroyedService = service + const destroyStatus = await finishShutdown(destroyedService) + service = null + expect(destroyStatus).toBe(NativeClipboardDestroyStatus.Destroyed) + expect(lib.clipboardServicePollShutdown(destroyedService)).toBe(NativeClipboardShutdownStatus.InvalidHandle) + } finally { + for (const operation of operations) lib.clipboardOperationDestroy(operation) + if (service) { + lib.clipboardServiceBeginShutdown(service) + await finishShutdown(service) + } + } +}) + +async function finishShutdown(service: ClipboardServiceHandle): Promise { + const lib = resolveRenderLib() + let status = lib.clipboardServicePollShutdown(service) + for (let attempt = 0; status === NativeClipboardShutdownStatus.Pending && attempt < 2_000; attempt += 1) { + await sleep(1) + status = lib.clipboardServicePollShutdown(service) + } + expect(status).toBe(NativeClipboardShutdownStatus.Ready) + return lib.clipboardServiceDestroy(service) +} diff --git a/packages/core/src/tests/ffi-borrowed-pointer-callsites.test.ts b/packages/core/src/tests/ffi-borrowed-pointer-callsites.test.ts index 38dbb17e52..e6b89007ad 100644 --- a/packages/core/src/tests/ffi-borrowed-pointer-callsites.test.ts +++ b/packages/core/src/tests/ffi-borrowed-pointer-callsites.test.ts @@ -6,6 +6,7 @@ import { AudioStreamStatsStruct, CursorStyleOptionsStruct, GridDrawOptionsStruct, + ImageDrawOptionsStruct, LogicalCursorStruct, MeasureResultStruct, NativeAudioStreamCloseReason, @@ -25,19 +26,31 @@ import { toArrayBuffer, type Pointer } from "../platform/ffi.js" const lib = resolveRenderLib() const symbols = (lib as any).opentui.symbols as Record any> -function withStubbedSymbol(name: string, fn: (calls: any[][]) => void): void { - const calls: any[][] = [] - const original = symbols[name] - symbols[name] = (...args: any[]) => { - calls.push(args) +function withStubbedSymbols( + replacements: Record any>, + fn: (calls: Record) => void, +): void { + const originals: Record any> = {} + const calls: Record = {} + for (const [name, replacement] of Object.entries(replacements)) { + originals[name] = symbols[name]! + calls[name] = [] + symbols[name] = (...args: any[]) => { + calls[name]!.push(args) + return replacement(...args) + } } try { fn(calls) } finally { - symbols[name] = original + for (const [name, original] of Object.entries(originals)) symbols[name] = original } } +function withStubbedSymbol(name: string, fn: (calls: any[][]) => void): void { + withStubbedSymbols({ [name]: () => undefined }, (calls) => fn(calls[name]!)) +} + async function forceGc(): Promise { if (typeof Bun !== "undefined") { Bun.gc(true) @@ -652,6 +665,153 @@ describe("borrowed pointer call sites", () => { expect((calls[0]![1] as ArrayBuffer).byteLength).toBe(CursorStyleOptionsStruct.size) }) }) + + test("image calls pass transient buffer owners directly", () => { + const names = [ + "bufferDrawImage", + "imageInfo", + "imageDecode", + "imageCreateFromRgba", + "imageGetInfo", + "imageClone", + "imageCopyPixels", + "imageResize", + "imageExtract", + "imageExtend", + "imageTransform", + "imageComposite", + ] as const + const originals = new Map any>() + const calls = new Map() + for (const name of names) { + originals.set(name, symbols[name]!) + symbols[name] = (...args: any[]) => { + calls.set(name, args) + return 0 + } + } + + try { + const data = Uint8Array.of(1, 2, 3, 4) + const pixels = Uint8Array.of(5, 6, 7, 255) + const destination = new Uint8Array(4) + const background = Uint8Array.of(8, 9, 10, 255) + const handle = 1 as any + + lib.bufferDrawImage(handle, handle, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, "auto") + const firstImageOptions = calls.get("bufferDrawImage")![2] + lib.bufferDrawImage(handle, handle, 2, 3, 4, 5, 6, 7, 0, 0, 1, 1, "sixel") + lib.imageInfo(data) + lib.imageDecode(data) + lib.imageCreateFromRgba(pixels, 1, 1, 4) + lib.imageGetInfo(handle) + lib.imageClone(handle) + lib.imageCopyPixels(handle, destination, 4, false) + lib.imageResize(handle, 1, 1, 0) + lib.imageExtract(handle, 0, 0, 1, 1) + lib.imageExtend(handle, 0, 0, 0, 0, background) + lib.imageTransform(handle, 0) + lib.imageComposite(handle, handle, 0, 0, 0, 255) + + expect(calls.get("bufferDrawImage")![2]).toBe(firstImageOptions) + expect(firstImageOptions).toBeInstanceOf(ArrayBuffer) + expect((firstImageOptions as ArrayBuffer).byteLength).toBe(ImageDrawOptionsStruct.size) + expect(ImageDrawOptionsStruct.unpack(firstImageOptions as ArrayBuffer)).toMatchObject({ + x: 2, + y: 3, + width: 4, + height: 5, + pixelWidth: 6, + pixelHeight: 7, + protocol: 2, + }) + expect(calls.get("imageInfo")![0]).toBe(data) + expect(calls.get("imageInfo")![2]).toBeInstanceOf(ArrayBuffer) + expect(calls.get("imageDecode")![0]).toBe(data) + expect(calls.get("imageDecode")![2]).toBeInstanceOf(Uint32Array) + expect(calls.get("imageCreateFromRgba")![0]).toBe(pixels) + expect(calls.get("imageCreateFromRgba")![5]).toBeInstanceOf(Uint32Array) + expect(calls.get("imageGetInfo")![1]).toBeInstanceOf(ArrayBuffer) + expect(calls.get("imageClone")![1]).toBeInstanceOf(Uint32Array) + expect(calls.get("imageCopyPixels")![1]).toBe(destination) + expect(calls.get("imageResize")![4]).toBeInstanceOf(Uint32Array) + expect(calls.get("imageExtract")![5]).toBeInstanceOf(Uint32Array) + expect(calls.get("imageExtend")![5]).toBe(background) + expect(calls.get("imageExtend")![6]).toBeInstanceOf(Uint32Array) + expect(calls.get("imageTransform")![2]).toBeInstanceOf(Uint32Array) + expect(calls.get("imageComposite")![6]).toBeInstanceOf(Uint32Array) + } finally { + for (const [name, original] of originals) symbols[name] = original + } + }) + + test("imageGetPixelsPtr preserves portable pointer returns", () => { + const original = symbols.imageGetPixelsPtr + const pointer = 1234n as Pointer + symbols.imageGetPixelsPtr = (handle) => { + expect(handle).toBe(1) + return pointer + } + try { + expect(lib.imageGetPixelsPtr(1 as any)).toBe(pointer) + symbols.imageGetPixelsPtr = () => 0n + expect(lib.imageGetPixelsPtr(1 as any)).toBeNull() + } finally { + symbols.imageGetPixelsPtr = original + } + }) + + test("imageExtend rejects a short background before native access", () => { + withStubbedSymbol("imageExtend", (calls) => { + expect(lib.imageExtend(1 as any, 0, 0, 0, 0, Uint8Array.of(1, 2, 3))).toEqual({ + status: 7, + handle: null, + }) + expect(calls).toHaveLength(0) + }) + }) + + test("clipboard calls pass transient request and output buffers as object values", () => { + withStubbedSymbols( + { + clipboardServiceCreate: () => 1, + clipboardServiceDestroy: () => 0, + clipboardReadOperationStart: () => 0, + clipboardWriteOperationStart: () => 0, + clipboardClearOperationStart: () => 0, + clipboardOperationResultMimeLength: () => 0, + clipboardOperationResultMimeCopy: () => 0, + clipboardOperationResultDataCopy: () => 0, + clipboardOperationResultErrorCode: () => 0, + clipboardOperationResultDiagnosticCopy: () => 0, + }, + (calls) => { + const service = lib.clipboardServiceCreate(4, 5, "seat0")! + lib.clipboardReadOperationStart(service, Uint8Array.of(1, 2), 0, 16, 32, 64, 100) + lib.clipboardWriteOperationStart(service, Uint8Array.of(3, 4), 0, 100) + lib.clipboardClearOperationStart(service, 0, 100) + lib.clipboardOperationResultMimeLength(1 as any) + lib.clipboardOperationResultMimeCopy(1 as any, new Uint8Array(2)) + lib.clipboardOperationResultDataCopy(1 as any, new Uint8Array(2)) + lib.clipboardOperationResultErrorCode(1 as any) + lib.clipboardOperationResultDiagnosticCopy(1 as any, new Uint8Array(2)) + lib.clipboardServiceDestroy(service) + + expect(calls.clipboardServiceCreate![0]![2]).toBeInstanceOf(Uint8Array) + expect(calls.clipboardReadOperationStart![0]![1]).toBeInstanceOf(Uint8Array) + expect(calls.clipboardReadOperationStart![0]!.slice(4, 8)).toEqual([16, 32, 64, 100]) + expect(calls.clipboardReadOperationStart![0]![8]).toBeInstanceOf(Uint32Array) + expect(calls.clipboardWriteOperationStart![0]![1]).toBeInstanceOf(Uint8Array) + expect(calls.clipboardWriteOperationStart![0]![5]).toBeInstanceOf(Uint32Array) + expect(calls.clipboardClearOperationStart![0]![3]).toBeInstanceOf(Uint32Array) + expect(calls.clipboardOperationResultMimeLength![0]![1]).toBeInstanceOf(Uint32Array) + expect(calls.clipboardOperationResultMimeCopy![0]![1]).toBeInstanceOf(Uint8Array) + expect(calls.clipboardOperationResultDataCopy![0]![1]).toBeInstanceOf(Uint8Array) + expect(calls.clipboardOperationResultErrorCode![0]![1]).toBeInstanceOf(Uint32Array) + expect(calls.clipboardOperationResultDiagnosticCopy![0]![1]).toBeInstanceOf(Uint8Array) + }, + ) + }) }) describe("packed color owner retention", () => { diff --git a/packages/core/src/tests/fixtures/images/alpha.webp b/packages/core/src/tests/fixtures/images/alpha.webp new file mode 100644 index 0000000000..278d3a3173 Binary files /dev/null and b/packages/core/src/tests/fixtures/images/alpha.webp differ diff --git a/packages/core/src/tests/fixtures/images/baseline.jpg b/packages/core/src/tests/fixtures/images/baseline.jpg new file mode 100644 index 0000000000..47cec79e75 Binary files /dev/null and b/packages/core/src/tests/fixtures/images/baseline.jpg differ diff --git a/packages/core/src/tests/fixtures/images/first-frame.gif b/packages/core/src/tests/fixtures/images/first-frame.gif new file mode 100644 index 0000000000..4796f5d91c Binary files /dev/null and b/packages/core/src/tests/fixtures/images/first-frame.gif differ diff --git a/packages/core/src/tests/fixtures/images/halves.jpg b/packages/core/src/tests/fixtures/images/halves.jpg new file mode 100644 index 0000000000..678d15535d Binary files /dev/null and b/packages/core/src/tests/fixtures/images/halves.jpg differ diff --git a/packages/core/src/tests/fixtures/images/lossless.webp b/packages/core/src/tests/fixtures/images/lossless.webp new file mode 100644 index 0000000000..8518215881 Binary files /dev/null and b/packages/core/src/tests/fixtures/images/lossless.webp differ diff --git a/packages/core/src/tests/fixtures/images/lossy.webp b/packages/core/src/tests/fixtures/images/lossy.webp new file mode 100644 index 0000000000..75d3f2da97 Binary files /dev/null and b/packages/core/src/tests/fixtures/images/lossy.webp differ diff --git a/packages/core/src/tests/fixtures/images/orientation.jpg b/packages/core/src/tests/fixtures/images/orientation.jpg new file mode 100644 index 0000000000..78f2f8796a Binary files /dev/null and b/packages/core/src/tests/fixtures/images/orientation.jpg differ diff --git a/packages/core/src/tests/fixtures/images/progressive.jpg b/packages/core/src/tests/fixtures/images/progressive.jpg new file mode 100644 index 0000000000..6b0d70f9c1 Binary files /dev/null and b/packages/core/src/tests/fixtures/images/progressive.jpg differ diff --git a/packages/core/src/tests/fixtures/images/rgba.png b/packages/core/src/tests/fixtures/images/rgba.png new file mode 100644 index 0000000000..b83713da0c Binary files /dev/null and b/packages/core/src/tests/fixtures/images/rgba.png differ diff --git a/packages/core/src/tests/fixtures/images/transparent.gif b/packages/core/src/tests/fixtures/images/transparent.gif new file mode 100644 index 0000000000..94c04357d1 Binary files /dev/null and b/packages/core/src/tests/fixtures/images/transparent.gif differ diff --git a/packages/core/src/tests/image-renderable.test.ts b/packages/core/src/tests/image-renderable.test.ts new file mode 100644 index 0000000000..143944c620 --- /dev/null +++ b/packages/core/src/tests/image-renderable.test.ts @@ -0,0 +1,505 @@ +import { createServer, type Server } from "node:http" +import { readFile, rm } from "node:fs/promises" +import { resolve } from "node:path" + +import { afterEach, beforeEach, describe, expect, mock, test } from "bun:test" +import { ImageRenderable, resolveImageRenderProtocol } from "../renderables/Image.js" +import { TextRenderable } from "../renderables/Text.js" +import { createTestRenderer, type TestRenderer, type TestRendererSetup } from "../testing/test-renderer.js" +import { createTerminalCapabilities } from "../testing/terminal-capabilities.js" +import type { RenderContext, TerminalCapabilities } from "../types.js" + +const FIXTURES = new URL("./fixtures/images/", import.meta.url) + +async function within(promise: Promise, message: string): Promise { + let timeout: ReturnType | undefined + try { + return await Promise.race([ + promise, + new Promise((_, reject) => { + timeout = setTimeout(() => reject(new Error(message)), 2_000) + }), + ]) + } finally { + clearTimeout(timeout) + } +} + +async function closeServer(server: Server): Promise { + if (!server.listening) return + const closed = new Promise((resolve, reject) => { + server.close((error) => (error ? reject(error) : resolve())) + }) + server.closeAllConnections() + await closed +} + +describe("ImageRenderable image loading", () => { + let setup: TestRendererSetup + let renderer: TestRenderer + + beforeEach(async () => { + setup = await createTestRenderer({}) + renderer = setup.renderer + }) + + afterEach(() => { + renderer.destroy() + }) + + test("loads encoded bytes and retains the image", async () => { + const loaded: string[] = [] + const renderable = new ImageRenderable(renderer, { + source: await readFile(new URL("rgba.png", FIXTURES)), + onLoad: (image) => loaded.push(image.info().format), + }) + await renderable.loadPromise + const image = renderable.image! + try { + expect(renderable.loading).toBe(false) + expect(renderable.loadError).toBeNull() + expect(image.info().format).toBe("png") + expect(loaded).toEqual(["png"]) + } finally { + renderable.destroy() + } + expect(() => image.info()).toThrow("disposed") + }) + + test("dumps image cells with their fallback glyphs", async () => { + const timestamp = Date.now() + const dumpDirectory = resolve("buffer_dump") + const currentDump = resolve(dumpDirectory, `current_buffer_${timestamp}.txt`) + const nextDump = resolve(dumpDirectory, `next_buffer_${timestamp}.txt`) + const outputDump = resolve(dumpDirectory, `output_buffer_${timestamp}.txt`) + const directOutputDump = resolve(dumpDirectory, `output_buffer_${timestamp + 1}.txt`) + const renderable = new ImageRenderable(renderer, { + source: await readFile(new URL("rgba.png", FIXTURES)), + protocol: "kitty", + width: 1, + height: 1, + }) + await renderable.loadPromise + + try { + expect(renderer.currentRenderBuffer.drawImage(renderable.image!, 0, 0, 1, 1, 0, 0, 0, 0, 2, 2, "kitty")).toBe( + true, + ) + const fallback = setup.captureCharFrame().match(/[^\s]/)?.[0] + expect(fallback).toBeDefined() + + renderer.dumpBuffers(timestamp) + renderer.dumpOutputBuffer(timestamp + 1) + + expect(await readFile(currentDump, "utf8")).toContain(fallback!) + expect(await readFile(directOutputDump, "utf8")).toContain("Last Rendered ANSI Output") + } finally { + if (process.versions.bun) { + await Promise.all( + [currentDump, nextDump, outputDump, directOutputDump].map((path) => rm(path, { force: true })), + ) + } + renderable.destroy() + } + }) + + test("defaults to aspect-preserving fit", async () => { + const renderable = new ImageRenderable(renderer, { + source: await readFile(new URL("rgba.png", FIXTURES)), + }) + await renderable.loadPromise + try { + expect(renderable.fit).toBe("fit") + expect(renderable.getFittedSize(60, 40, 2)).toEqual({ width: 60, height: 30 }) + } finally { + renderable.destroy() + } + }) + + test("calculates fit, cover, and fill using terminal cell aspect", async () => { + const renderable = new ImageRenderable(renderer, { + source: await readFile(new URL("rgba.png", FIXTURES)), + fit: "cover", + }) + await renderable.loadPromise + try { + expect(renderable.getFittedSize(60, 40, 2)).toEqual({ width: 80, height: 40 }) + renderable.fit = "fill" + expect(renderable.getFittedSize(60, 40, 2)).toEqual({ width: 60, height: 40 }) + } finally { + renderable.destroy() + } + }) + + test("renders the centered source crop for cover", async () => { + const renderable = new ImageRenderable(renderer, { + source: await readFile(new URL("orientation.jpg", FIXTURES)), + fit: "cover", + protocol: "blocks", + position: "absolute", + width: 2, + height: 2, + }) + renderer.root.add(renderable) + await renderable.loadPromise + await setup.renderOnce() + + const imageSpans = setup.captureSpans().lines[0].spans.filter((span) => span.text === "▀") + expect(imageSpans).toHaveLength(2) + const reds = imageSpans.map((span) => span.fg.toInts()[0]) + expect(reds[0]).toBeGreaterThan(64) + expect(reds[1]).toBeLessThan(192) + expect(reds[0]).toBeLessThan(reds[1]) + }) + + test("clears a buffered image from rendered output when its source is cleared", async () => { + const renderable = new ImageRenderable(renderer, { + source: await readFile(new URL("rgba.png", FIXTURES)), + buffered: true, + protocol: "blocks", + position: "absolute", + width: 2, + height: 1, + }) + renderer.root.add(renderable) + await renderable.loadPromise + await setup.renderOnce() + expect(setup.captureCharFrame()).toContain("█") + + renderable.source = undefined + await setup.renderOnce() + + expect(setup.captureCharFrame()).not.toContain("█") + }) + + test("clears the previous buffered image placement before rerendering", async () => { + const renderable = new ImageRenderable(renderer, { + source: await readFile(new URL("rgba.png", FIXTURES)), + buffered: true, + protocol: "blocks", + fit: "fill", + position: "absolute", + width: 4, + height: 4, + }) + renderer.root.add(renderable) + await renderable.loadPromise + await setup.renderOnce() + expect(setup.captureCharFrame().split("\n", 1)[0].slice(0, 4)).toBe("████") + + renderable.fit = "fit" + await setup.renderOnce() + + const lines = setup.captureCharFrame().split("\n") + expect(lines[0].slice(0, 4)).toBe(" ") + expect(lines[1].slice(0, 4)).toBe("████") + expect(lines[3].slice(0, 4)).toBe(" ") + }) + + test("preserves lower content beneath a zero-opacity image", async () => { + const text = new TextRenderable(renderer, { + content: "OK", + position: "absolute", + width: 2, + height: 1, + }) + const image = new ImageRenderable(renderer, { + source: await readFile(new URL("rgba.png", FIXTURES)), + protocol: "blocks", + opacity: 0, + position: "absolute", + width: 2, + height: 1, + }) + renderer.root.add(text) + renderer.root.add(image) + await image.loadPromise + await setup.renderOnce() + + expect(setup.captureCharFrame().split("\n", 1)[0].slice(0, 2)).toBe("OK") + }) + + test("exposes requested and effective image protocols", async () => { + const renderable = new ImageRenderable(renderer, { + source: await readFile(new URL("rgba.png", FIXTURES)), + protocol: "blocks", + }) + await renderable.loadPromise + try { + expect(renderable.protocol).toBe("blocks") + expect(renderable.effectiveProtocol).toBe("blocks") + renderable.protocol = "kitty" + expect(renderable.effectiveProtocol).toBe("kitty") + renderable.protocol = "auto" + expect(renderable.effectiveProtocol).toBe("blocks") + } finally { + renderable.destroy() + } + }) + + test("resolves automatic and configured image protocols", () => { + expect(resolveImageRenderProtocol("sixel", null, false)).toBe("blocks") + expect(resolveImageRenderProtocol("auto", createTerminalCapabilities({ image_protocol: "sixel" }), false)).toBe( + "blocks", + ) + expect(resolveImageRenderProtocol("auto", createTerminalCapabilities({ kitty_graphics: true }), false)).toBe( + "kitty", + ) + expect(resolveImageRenderProtocol("auto", createTerminalCapabilities({ sixel: true }), true)).toBe("sixel") + expect( + resolveImageRenderProtocol( + "auto", + createTerminalCapabilities({ kitty_graphics: true, multiplexer: "tmux" }), + true, + ), + ).toBe("blocks") + }) + + test("accepts legacy capability and render context shapes", () => { + const { image_protocol: _, ...legacyCapabilities } = createTerminalCapabilities({ kitty_graphics: true }) + const capabilities: TerminalCapabilities = legacyCapabilities + const omitted = new Set(["terminalWidth", "terminalHeight", "resolution"]) + const legacyContext: Omit = new Proxy(renderer, { + get: (target, property, receiver) => + omitted.has(property) ? undefined : Reflect.get(target, property, receiver), + }) + const renderable = new ImageRenderable(legacyContext, {}) + + expect(resolveImageRenderProtocol("auto", capabilities, false)).toBe("kitty") + expect((legacyContext as RenderContext).resolution).toBeUndefined() + expect(renderable.cellAspectRatio).toBe(2) + renderable.destroy() + }) + + test("reports decode failures without installing an image", async () => { + const onError = mock(() => {}) + const renderable = new ImageRenderable(renderer, { + source: Uint8Array.of(1, 2, 3), + onError, + }) + await renderable.loadPromise + try { + expect(renderable.loading).toBe(false) + expect(renderable.image).toBeNull() + expect(renderable.loadError).toBeDefined() + expect(onError).toHaveBeenCalledTimes(1) + } finally { + renderable.destroy() + } + }) + + test("propagates callback exceptions through loadPromise after settling state", async () => { + const loaded = new ImageRenderable(renderer, { + source: await readFile(new URL("rgba.png", FIXTURES)), + onLoad: () => { + throw new Error("onLoad failed") + }, + }) + try { + await expect(loaded.loadPromise!).rejects.toThrow("onLoad failed") + expect(loaded.loading).toBe(false) + expect(loaded.image).not.toBeNull() + } finally { + loaded.destroy() + } + + const failed = new ImageRenderable(renderer, { + source: Uint8Array.of(1, 2, 3), + onError: () => { + throw new Error("onError failed") + }, + }) + try { + await expect(failed.loadPromise!).rejects.toThrow("onError failed") + expect(failed.loading).toBe(false) + expect(failed.loadError).toBeDefined() + } finally { + failed.destroy() + } + }) + + test("replaces images atomically and disposes the previous image", async () => { + const renderable = new ImageRenderable(renderer, { source: await readFile(new URL("rgba.png", FIXTURES)) }) + await renderable.loadPromise + const previous = renderable.image + renderable.source = await readFile(new URL("transparent.gif", FIXTURES)) + expect(renderable.image).toBe(previous) + await renderable.loadPromise + try { + expect(renderable.image?.info().format).toBe("gif") + expect(() => previous?.raw()).toThrow("disposed") + } finally { + renderable.destroy() + } + }) + + test("keeps the previous image when a replacement fails", async () => { + const onError = mock(() => {}) + const renderable = new ImageRenderable(renderer, { + source: await readFile(new URL("rgba.png", FIXTURES)), + onError, + }) + await renderable.loadPromise + const previous = renderable.image + renderable.source = Uint8Array.of(1, 2, 3) + await renderable.loadPromise + try { + expect(renderable.image).toBe(previous) + expect(previous?.raw().data.byteLength).toBeGreaterThan(0) + expect(onError).toHaveBeenCalledTimes(1) + } finally { + renderable.destroy() + } + }) + + test("clearing the source aborts loading and disposes the retained image", async () => { + const png = await readFile(new URL("rgba.png", FIXTURES)) + const requestStarted = Promise.withResolvers() + const requestAborted = Promise.withResolvers() + const socketClosed = Promise.withResolvers() + let requestWasAborted = false + let socketWasClosed = false + const server = createServer((request) => { + request.once("aborted", () => { + requestWasAborted = true + requestAborted.resolve() + }) + request.socket.once("close", () => { + socketWasClosed = true + socketClosed.resolve() + }) + requestStarted.resolve() + }) + let renderable: ImageRenderable | undefined + try { + await new Promise((resolve) => server.listen(0, "127.0.0.1", resolve)) + const address = server.address() + if (!address || typeof address === "string") throw new Error("missing test server address") + renderable = new ImageRenderable(renderer, { source: png }) + await renderable.loadPromise + const previous = renderable.image + + renderable.source = `http://127.0.0.1:${address.port}/pending` + const pendingLoad = renderable.loadPromise + if (!pendingLoad) throw new Error("missing pending image load") + await within(requestStarted.promise, "server did not receive the pending image request") + renderable.source = undefined + await within( + Promise.all([pendingLoad, requestAborted.promise, socketClosed.promise]), + "image request was not aborted", + ) + + expect(renderable.image).toBeNull() + expect(renderable.loading).toBe(false) + expect(() => previous?.raw()).toThrow("disposed") + expect(requestWasAborted).toBe(true) + expect(socketWasClosed).toBe(true) + } finally { + renderable?.destroy() + await closeServer(server) + } + }) + + test("a newer source aborts the older request and replaces its image", async () => { + const gif = await readFile(new URL("transparent.gif", FIXTURES)) + const requestStarted = Promise.withResolvers() + const requestAborted = Promise.withResolvers() + const socketClosed = Promise.withResolvers() + let requestWasAborted = false + let socketWasClosed = false + const server = createServer((request, response) => { + if (request.url === "/slow") { + request.once("aborted", () => { + requestWasAborted = true + requestAborted.resolve() + }) + request.socket.once("close", () => { + socketWasClosed = true + socketClosed.resolve() + }) + requestStarted.resolve() + } else { + response.setHeader("Connection", "close") + response.end(gif) + } + }) + const onError = mock(() => {}) + let renderable: ImageRenderable | undefined + try { + await new Promise((resolve) => server.listen(0, "127.0.0.1", resolve)) + const address = server.address() + if (!address || typeof address === "string") throw new Error("missing test server address") + const base = `http://127.0.0.1:${address.port}` + renderable = new ImageRenderable(renderer, { source: `${base}/slow`, onError }) + const olderLoad = renderable.loadPromise + if (!olderLoad) throw new Error("missing older image load") + await within(requestStarted.promise, "server did not receive the older image request") + + renderable.source = `${base}/fast` + const newerLoad = renderable.loadPromise + if (!newerLoad) throw new Error("missing newer image load") + await within( + Promise.all([olderLoad, newerLoad, requestAborted.promise, socketClosed.promise]), + "older image request was not aborted", + ) + + expect(renderable.image?.info().format).toBe("gif") + expect(onError).not.toHaveBeenCalled() + expect(requestWasAborted).toBe(true) + expect(socketWasClosed).toBe(true) + } finally { + renderable?.destroy() + await closeServer(server) + } + }) + + test("destroy aborts an in-flight load and prevents callbacks", async () => { + const requestStarted = Promise.withResolvers() + const requestAborted = Promise.withResolvers() + const socketClosed = Promise.withResolvers() + let requestWasAborted = false + let socketWasClosed = false + const server = createServer((request) => { + request.once("aborted", () => { + requestWasAborted = true + requestAborted.resolve() + }) + request.socket.once("close", () => { + socketWasClosed = true + socketClosed.resolve() + }) + requestStarted.resolve() + }) + const onLoad = mock(() => {}) + const onError = mock(() => {}) + let renderable: ImageRenderable | undefined + try { + await new Promise((resolve) => server.listen(0, "127.0.0.1", resolve)) + const address = server.address() + if (!address || typeof address === "string") throw new Error("missing test server address") + renderable = new ImageRenderable(renderer, { + source: `http://127.0.0.1:${address.port}/pending`, + onLoad, + onError, + }) + const pendingLoad = renderable.loadPromise + if (!pendingLoad) throw new Error("missing pending image load") + await within(requestStarted.promise, "server did not receive the pending image request") + + renderable.destroy() + await within( + Promise.all([pendingLoad, requestAborted.promise, socketClosed.promise]), + "image request was not aborted", + ) + + expect(renderable.image).toBeNull() + expect(onLoad).not.toHaveBeenCalled() + expect(onError).not.toHaveBeenCalled() + expect(requestWasAborted).toBe(true) + expect(socketWasClosed).toBe(true) + } finally { + renderable?.destroy() + await closeServer(server) + } + }) +}) diff --git a/packages/core/src/tests/image.test.ts b/packages/core/src/tests/image.test.ts new file mode 100644 index 0000000000..b4f118c86d --- /dev/null +++ b/packages/core/src/tests/image.test.ts @@ -0,0 +1,950 @@ +import { createServer } from "node:http" +import { chmod, mkdtemp, open, readFile, rm } from "node:fs/promises" +import { tmpdir } from "node:os" +import { join } from "node:path" +import { fileURLToPath } from "node:url" + +import { describe, expect, test } from "bun:test" +import { ImageError, ImageLoadError, NativeImage, imageInfo } from "../image.js" +import { toArrayBuffer } from "../platform/ffi.js" +import { resolveRenderLib } from "../zig.js" + +const PNG_1X1 = Uint8Array.from( + Buffer.from( + "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR4AWP4z8DwHwAFAAH/e+m+7wAAAABJRU5ErkJggg==", + "base64", + ), +) + +const ANIMATED_WEBP = Uint8Array.from( + Buffer.from( + "UklGRoYAAABXRUJQVlA4WAoAAAASAAAAAQAAAQAAQU5JTQYAAAD/////AABBTk1GKAAAAAAAAAAAAAEAAAEAAGQAAAJWUDhMDwAAAC8BQAAABxD9j/4HIqL/AQBBTk1GKgAAAAAAAAAAAAEAAAEAAGQAAAJWUDhMEQAAAC8BQAAQDxDzH/MfjBWI6H8IAA==", + "base64", + ), +) + +const FIXTURES = new URL("./fixtures/images/", import.meta.url) + +function injectJpegExifOrientation(jpeg: Uint8Array, orientation: number): Uint8Array { + // Little-endian TIFF with a single IFD0 entry: tag 0x0112 (Orientation), + // type SHORT, count 1. + const tiff = Uint8Array.from([ + 0x49, + 0x49, + 0x2a, + 0x00, + 0x08, + 0x00, + 0x00, + 0x00, + 0x01, + 0x00, + 0x12, + 0x01, + 0x03, + 0x00, + 0x01, + 0x00, + 0x00, + 0x00, + orientation & 0xff, + (orientation >> 8) & 0xff, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + ]) + const identifier = Uint8Array.from([0x45, 0x78, 0x69, 0x66, 0x00, 0x00]) + const segmentLength = 2 + identifier.length + tiff.length + const segment = new Uint8Array(2 + segmentLength) + segment[0] = 0xff + segment[1] = 0xe1 + segment[2] = (segmentLength >> 8) & 0xff + segment[3] = segmentLength & 0xff + segment.set(identifier, 4) + segment.set(tiff, 4 + identifier.length) + + const result = new Uint8Array(jpeg.length + segment.length) + result.set(jpeg.slice(0, 2), 0) + result.set(segment, 2) + result.set(jpeg.slice(2), 2 + segment.length) + return result +} + +function pngCrc(bytes: Uint8Array): number { + let crc = 0xffffffff + for (const byte of bytes) { + crc ^= byte + for (let bit = 0; bit < 8; bit++) crc = (crc >>> 1) ^ (0xedb88320 & -(crc & 1)) + } + return (crc ^ 0xffffffff) >>> 0 +} + +function injectPngChunk(png: Uint8Array, type: string, payload: Uint8Array): Uint8Array { + const typeBytes = new TextEncoder().encode(type) + const chunk = new Uint8Array(payload.length + 12) + const chunkView = new DataView(chunk.buffer) + chunkView.setUint32(0, payload.length) + chunk.set(typeBytes, 4) + chunk.set(payload, 8) + chunkView.setUint32(8 + payload.length, pngCrc(chunk.subarray(4, 8 + payload.length))) + + let offset = 8 + while (new TextDecoder().decode(png.subarray(offset + 4, offset + 8)) !== "IDAT") { + offset += new DataView(png.buffer, png.byteOffset + offset, 4).getUint32(0) + 12 + } + const result = new Uint8Array(png.length + chunk.length) + result.set(png.subarray(0, offset)) + result.set(chunk, offset) + result.set(png.subarray(offset), offset + chunk.length) + return result +} + +function withPngDimensions(png: Uint8Array, width: number, height: number): Uint8Array { + const result = png.slice() + const view = new DataView(result.buffer, result.byteOffset) + view.setUint32(16, width) + view.setUint32(20, height) + view.setUint32(29, pngCrc(result.subarray(12, 29))) + return result +} + +const FORMATS = [ + ["rgba.png", "png", false], + ["baseline.jpg", "jpeg", false], + ["progressive.jpg", "jpeg", false], + ["lossy.webp", "webp", false], + ["lossless.webp", "webp", false], + ["alpha.webp", "webp", true], + ["first-frame.gif", "gif", false], + ["transparent.gif", "gif", true], +] as const + +describe("NativeImage", () => { + test("inspects and decodes PNG data", () => { + expect(imageInfo(PNG_1X1)).toMatchObject({ width: 1, height: 1, format: "png" }) + const image = NativeImage.decode(PNG_1X1) + try { + expect(image.width).toBe(1) + expect(image.height).toBe(1) + expect(image.raw().data).toHaveLength(4) + } finally { + image.dispose() + } + }) + + test("reports transparency consistently for opaque RGBA PNG data", () => { + const inspected = imageInfo(PNG_1X1) + const image = NativeImage.decode(PNG_1X1) + try { + expect(inspected.hasAlpha).toBe(false) + expect(image.info().hasAlpha).toBe(false) + } finally { + image.dispose() + } + }) + + test("rejects malformed PNG data", () => { + const corrupt = PNG_1X1.slice() + corrupt[29] ^= 1 + expect(() => imageInfo(corrupt)).toThrow("malformed image data") + }) + + test("applies the documented PNG color-space policy", async () => { + const explicitSrgb = await readFile(new URL("rgba.png", FIXTURES)) + expect(imageInfo(explicitSrgb).colorStatus).toBe("explicit-srgb") + + const iccp = injectPngChunk(PNG_1X1, "iCCP", Uint8Array.of(0)) + expect(() => imageInfo(iccp)).toThrow("unsupported image color space") + + const badGamma = injectPngChunk(PNG_1X1, "gAMA", Uint8Array.of(0, 0, 0, 1)) + expect(() => imageInfo(badGamma)).toThrow("unsupported image color space") + + const badChromaticities = injectPngChunk(PNG_1X1, "cHRM", new Uint8Array(32)) + expect(() => imageInfo(badChromaticities)).toThrow("unsupported image color space") + + const unsupportedCicp = injectPngChunk(PNG_1X1, "cICP", Uint8Array.of(9, 9, 9, 9)) + expect(imageInfo(unsupportedCicp).colorStatus).toBe("assumed-srgb") + + const supportedCicp = injectPngChunk(iccp, "cICP", Uint8Array.of(1, 13, 0, 1)) + expect(imageInfo(supportedCicp).colorStatus).toBe("explicit-srgb") + }) + + test("enforces the documented decoded image dimensions", () => { + for (const png of [withPngDimensions(PNG_1X1, 16_385, 1), withPngDimensions(PNG_1X1, 5_001, 5_000)]) { + try { + imageInfo(png) + throw new Error("expected oversized PNG to be rejected") + } catch (error) { + expect(error).toBeInstanceOf(ImageError) + expect((error as ImageError).code).toBe("dimension-limit") + } + } + }) + + test("rejects unsupported encoded formats", () => { + const bytes = Uint8Array.of(1, 2, 3, 4, 5, 6, 7, 8) + for (const operation of [() => imageInfo(bytes), () => NativeImage.decode(bytes)]) { + try { + operation() + throw new Error("expected image operation to fail") + } catch (error) { + expect(error).toBeInstanceOf(ImageError) + expect((error as ImageError).code).toBe("unsupported-format") + } + } + }) + + test("inspects and decodes every required encoded format", async () => { + for (const [name, format, hasAlpha] of FORMATS) { + const bytes = await readFile(new URL(name, FIXTURES)) + const inspected = imageInfo(bytes) + const image = NativeImage.decode(bytes) + try { + expect(inspected.format).toBe(format) + expect(inspected.hasAlpha).toBe(hasAlpha) + expect(image.info()).toEqual({ ...inspected, orientation: 1 }) + expect(image.raw().data).toHaveLength(image.width * image.height * 4) + } finally { + image.dispose() + } + } + }) + + test("rejects animated WebP", () => { + for (const operation of [() => imageInfo(ANIMATED_WEBP), () => NativeImage.decode(ANIMATED_WEBP)]) { + try { + operation() + throw new Error("expected animated WebP to be rejected") + } catch (error) { + expect(error).toBeInstanceOf(ImageError) + expect((error as ImageError).code).toBe("unsupported-feature") + } + } + }) + + test("reports malformed data for every recognized format", async () => { + for (const [name] of FORMATS) { + const bytes = await readFile(new URL(name, FIXTURES)) + const truncated = bytes.subarray(0, Math.max(2, Math.floor(bytes.byteLength / 2))) + expect(() => NativeImage.decode(truncated)).toThrow("malformed image data") + } + }) + + test("rejects a GIF with a missing or invalid mandatory trailer", async () => { + const gif = await readFile(new URL("first-frame.gif", FIXTURES)) + expect(gif.at(-1)).toBe(0x3b) + const invalidTrailer = new Uint8Array(gif) + invalidTrailer[invalidTrailer.length - 1] = 0 + + for (const malformed of [gif.subarray(0, -1), invalidTrailer]) { + for (const operation of [() => imageInfo(malformed), () => NativeImage.decode(malformed)]) { + try { + operation() + throw new Error("expected image operation to fail") + } catch (error) { + expect(error).toBeInstanceOf(ImageError) + expect((error as ImageError).code).toBe("malformed-data") + } + } + } + }) + + test("keeps a transparent GIF logical-screen background transparent", async () => { + const image = NativeImage.decode(await readFile(new URL("transparent.gif", FIXTURES))) + try { + expect([image.width, image.height]).toEqual([2, 2]) + expect(image.info().hasAlpha).toBe(true) + expect([...image.raw().data]).toEqual([255, 0, 0, 255, 0, 0, 0, 0, 0, 0, 0, 0, 255, 0, 0, 255]) + } finally { + image.dispose() + } + }) + + test("clones, flips, and flops decoded images", async () => { + const image = NativeImage.decode(await readFile(new URL("rgba.png", FIXTURES))) + const outputs = [image.clone(), image.flip(), image.flop()] + try { + for (const output of outputs) { + expect(output.raw().data).toHaveLength(output.width * output.height * 4) + } + } finally { + for (const output of outputs) output.dispose() + image.dispose() + } + }) + + test("rejects unsupported pixel formats, resize kernels, and blend modes", () => { + const image = NativeImage.fromRgba(Uint8Array.of(1, 2, 3, 255), 1, 1) + const destination = new Uint8Array(4) + const expectTypeError = (operation: () => unknown): void => { + let result: unknown + try { + result = operation() + } catch (error) { + expect(error).toBeInstanceOf(TypeError) + return + } + if (result instanceof NativeImage) result.dispose() + throw new Error("expected image operation to reject an unsupported option") + } + + try { + expectTypeError(() => image.raw("rgb8" as never)) + expectTypeError(() => image.copyTo(destination, { format: "rgb8" as never })) + expectTypeError(() => image.resize({ width: 1, kernel: "lanczos3" as never })) + expectTypeError(() => image.composite(image, { blend: "multiply" as never })) + } finally { + image.dispose() + } + }) + + test("constructs and exports immutable RGBA images", () => { + const source = Uint8Array.of(1, 2, 3, 4, 5, 6, 7, 8) + const image = NativeImage.fromRgba(source, 2, 1) + source.fill(0) + try { + expect([...image.raw().data]).toEqual([1, 2, 3, 4, 5, 6, 7, 8]) + expect([...image.raw("bgra8").data]).toEqual([3, 2, 1, 4, 7, 6, 5, 8]) + } finally { + image.dispose() + } + }) + + test("transfers native RGBA pixels without copying", () => { + const pixels = Uint8Array.of(1, 2, 3, 4, 5, 6, 7, 8) + const image = NativeImage.fromRgba(pixels, 2, 1) + const handle = image.ptr + const raw = image.takeRaw() + try { + expect(raw).toMatchObject({ + width: 2, + height: 1, + stride: 8, + format: "rgba8", + colorSpace: "srgb", + alpha: "straight", + }) + expect([...raw.data]).toEqual([...pixels]) + expect(() => image.info()).toThrow("disposed") + + const pointer = resolveRenderLib().imageGetPixelsPtr(handle) + expect(pointer).not.toBeNull() + const alias = new Uint8Array(toArrayBuffer(pointer!, 0, pixels.byteLength)) + raw.data[0] = 42 + expect(alias[0]).toBe(42) + } finally { + raw.dispose() + raw.dispose() + } + expect(resolveRenderLib().imageGetPixelsPtr(handle)).toBeNull() + }) + + test("keeps transferred pixels alive after the NativeImage wrapper is dropped", async () => { + let image: NativeImage | null = NativeImage.fromRgba(Uint8Array.of(9, 8, 7, 6), 1, 1) + const raw = image.takeRaw() + image = null + if (typeof Bun !== "undefined") Bun.gc(true) + ;(globalThis as any).gc?.() + await new Promise((resolve) => setTimeout(resolve, 0)) + try { + expect([...raw.data]).toEqual([9, 8, 7, 6]) + } finally { + raw.dispose() + } + }) + + test("supports exact transforms, extraction, and extension", () => { + const image = NativeImage.fromRgba( + Uint8Array.of(1, 0, 0, 255, 2, 0, 0, 255, 3, 0, 0, 255, 4, 0, 0, 255, 5, 0, 0, 255, 6, 0, 0, 255), + 3, + 2, + ) + let rotated: NativeImage | undefined + let rotated270: NativeImage | undefined + let extracted: NativeImage | undefined + let extended: NativeImage | undefined + try { + rotated = image.rotate(90) + rotated270 = image.rotate(270) + extracted = image.extract({ left: 1, top: 0, width: 2, height: 2 }) + extended = extracted.extend({ top: 1, left: 1, background: [9, 8, 7, 6] }) + expect([rotated.width, rotated.height]).toEqual([2, 3]) + expect([...rotated.raw().data.filter((_, index) => index % 4 === 0)]).toEqual([4, 1, 5, 2, 6, 3]) + expect([...rotated270.raw().data.filter((_, index) => index % 4 === 0)]).toEqual([3, 6, 2, 5, 1, 4]) + expect([...extended.raw().data.slice(0, 4)]).toEqual([9, 8, 7, 6]) + } finally { + extended?.dispose() + extracted?.dispose() + rotated270?.dispose() + rotated?.dispose() + image.dispose() + } + }) + + test("uses nearest-neighbor sampling when requested", () => { + const image = NativeImage.fromRgba(Uint8Array.of(0, 0, 0, 255, 255, 0, 0, 255), 2, 1) + let resized: NativeImage | undefined + try { + resized = image.resize({ width: 3, height: 1, kernel: "nearest" }) + expect([...resized.raw().data.filter((_, index) => index % 4 === 0)]).toEqual([0, 255, 255]) + } finally { + resized?.dispose() + image.dispose() + } + }) + + test("updates transparency metadata after extracting opaque pixels", () => { + const image = NativeImage.fromRgba(Uint8Array.of(1, 2, 3, 255, 4, 5, 6, 0), 2, 1) + const extracted = image.extract({ left: 0, top: 0, width: 1, height: 1 }) + try { + expect(image.info().hasAlpha).toBe(true) + expect(extracted.info().hasAlpha).toBe(false) + } finally { + extracted.dispose() + image.dispose() + } + }) + + test("zero-margin extension preserves opaque metadata and pixels", () => { + const image = NativeImage.fromRgba(Uint8Array.of(1, 2, 3, 255), 1, 1) + const extended = image.extend() + try { + expect(extended.info().hasAlpha).toBe(false) + expect(extended.raw().data).toEqual(image.raw().data) + } finally { + extended.dispose() + image.dispose() + } + }) + + test("preserves aspect ratio when one resize dimension is omitted", () => { + const image = NativeImage.fromRgba(new Uint8Array(4 * 4 * 2).fill(255), 4, 2) + const resized = image.resize({ width: 2 }) + try { + expect([resized.width, resized.height]).toEqual([2, 1]) + } finally { + resized.dispose() + image.dispose() + } + }) + + test("validates aspect-ratio-derived dimensions before FFI conversion", () => { + const image = NativeImage.fromRgba(new Uint8Array(8), 2, 1) + try { + expect(() => image.resize({ height: 0xffff_ffff })).toThrow("width must be a positive u32 integer") + } finally { + image.dispose() + } + }) + + test("composites in linear light", () => { + const base = NativeImage.fromRgba(Uint8Array.of(0, 0, 0, 255), 1, 1) + const overlay = NativeImage.fromRgba(Uint8Array.of(255, 255, 255, 128), 1, 1) + const output = base.composite(overlay) + try { + const red = output.raw().data[0] + expect(red).toBeGreaterThanOrEqual(187) + expect(red).toBeLessThanOrEqual(190) + expect(output.raw().data[3]).toBe(255) + } finally { + output.dispose() + overlay.dispose() + base.dispose() + } + }) + + test("updates transparency metadata after source compositing", () => { + const base = NativeImage.fromRgba(Uint8Array.of(0, 0, 0, 255), 1, 1) + const overlay = NativeImage.fromRgba(Uint8Array.of(255, 0, 0, 255), 1, 1) + const output = base.composite(overlay, { blend: "source", opacity: 0.5 }) + try { + expect(output.raw().data[3]).toBe(128) + expect(output.info().hasAlpha).toBe(true) + } finally { + output.dispose() + overlay.dispose() + base.dispose() + } + }) + + test("supports destination-over compositing", () => { + const base = NativeImage.fromRgba(Uint8Array.of(0, 0, 255, 128), 1, 1) + let overlay: NativeImage | undefined + let output: NativeImage | undefined + try { + overlay = NativeImage.fromRgba(Uint8Array.of(255, 0, 0, 255), 1, 1) + output = base.composite(overlay, { blend: "destination-over" }) + const pixels = output.raw().data + expect(pixels[0]).toBeGreaterThan(0) + expect(pixels[2]).toBeGreaterThan(0) + expect(pixels[3]).toBe(255) + } finally { + output?.dispose() + overlay?.dispose() + base.dispose() + } + }) + + test("dispose is idempotent and rejects later operations", () => { + const image = NativeImage.fromRgba(Uint8Array.of(0, 0, 0, 0), 1, 1) + image.dispose() + image.dispose() + expect(() => image.raw()).toThrow("disposed") + }) + + test("validates dimensions and destination buffers", () => { + expect(() => NativeImage.fromRgba(new Uint8Array(4), 0, 1)).toThrow("positive u32") + const image = NativeImage.fromRgba(new Uint8Array(4), 1, 1) + try { + expect(() => image.extract({ left: 1, top: 0, width: 1, height: 1 })).toThrow("invalid image argument") + expect(() => image.copyTo(new Uint8Array(3))).toThrow("too small") + } finally { + image.dispose() + } + }) + + test("loads encoded bytes and ArrayBuffer sources", async () => { + const bytes = await readFile(new URL("rgba.png", FIXTURES)) + const fromView = await NativeImage.load(bytes.subarray(0)) + const fromBuffer = await NativeImage.load(bytes.buffer.slice(bytes.byteOffset, bytes.byteOffset + bytes.byteLength)) + try { + expect(fromView.info().format).toBe("png") + expect(fromBuffer.info()).toEqual(fromView.info()) + } finally { + fromBuffer.dispose() + fromView.dispose() + } + }) + + test("loads Blob, Response, data URL, and blob URL sources", async () => { + const bytes = await readFile(new URL("rgba.png", FIXTURES)) + const blob = new Blob([bytes], { type: "image/png" }) + const objectUrl = URL.createObjectURL(blob) + const sources = [ + blob, + new Response(bytes), + `data:image/png;base64,${Buffer.from(bytes).toString("base64")}`, + objectUrl, + ] + try { + for (const source of sources) { + const image = await NativeImage.load(source) + try { + expect(image.info().format).toBe("png") + expect([image.width, image.height]).toEqual([2, 2]) + } finally { + image.dispose() + } + } + } finally { + URL.revokeObjectURL(objectUrl) + } + }) + + test("loads one-byte Response chunks without copying each source chunk", async () => { + const bytes = new Uint8Array(await readFile(new URL("rgba.png", FIXTURES))) + let sourceCopies = 0 + class SourceChunk extends Uint8Array { + public override slice(start?: number, end?: number): SourceChunk { + sourceCopies += 1 + return super.slice(start, end) as SourceChunk + } + } + const response = new Response( + new ReadableStream({ + start(controller) { + for (const byte of bytes) controller.enqueue(new SourceChunk([byte])) + controller.close() + }, + }), + ) + + const image = await NativeImage.load(response) + try { + expect(image.info().format).toBe("png") + expect([image.width, image.height]).toEqual([2, 2]) + expect(sourceCopies).toBe(0) + } finally { + image.dispose() + } + }) + + test("preserves HTTP status errors and cancels direct Response bodies", async () => { + let cancelled = false + const response = new Response( + new ReadableStream({ + cancel() { + cancelled = true + }, + }), + { status: 503 }, + ) + + try { + await NativeImage.load(response) + throw new Error("expected load to fail") + } catch (error) { + expect(error).toBeInstanceOf(ImageLoadError) + expect((error as ImageLoadError).code).toBe("http-status") + expect((error as ImageLoadError).status).toBe(503) + expect(cancelled).toBe(true) + } + }) + + test("aborts a pending direct Response read", async () => { + let cancelled = false + const response = new Response( + new ReadableStream({ + pull() {}, + cancel() { + cancelled = true + }, + }), + ) + const controller = new AbortController() + const reason = new Error("stop") + const loading = NativeImage.load(response, { signal: controller.signal }) + await new Promise((resolve) => setTimeout(resolve, 0)) + controller.abort(reason) + + await expect(loading).rejects.toBe(reason) + expect(cancelled).toBe(true) + }) + + test("cancels a direct Response body when already aborted", async () => { + let cancelled = false + const response = new Response( + new ReadableStream({ + cancel() { + cancelled = true + }, + }), + ) + const controller = new AbortController() + const reason = new Error("stop") + controller.abort(reason) + + await expect(NativeImage.load(response, { signal: controller.signal })).rejects.toBe(reason) + expect(cancelled).toBe(true) + }) + + test("loads local paths and file URLs", async () => { + const url = new URL("rgba.png", FIXTURES) + const fromPath = await NativeImage.load(fileURLToPath(url)) + const fromUrl = await NativeImage.load(url) + const fromUrlString = await NativeImage.load(url.href) + try { + expect(fromPath.info().format).toBe("png") + expect(fromUrl.info()).toEqual(fromPath.info()) + expect(fromUrlString.info()).toEqual(fromPath.info()) + } finally { + fromUrlString.dispose() + fromUrl.dispose() + fromPath.dispose() + } + }) + + test("rejects oversized local files before reading their contents", async () => { + const directory = await mkdtemp(join(process.env.OTUI_IMAGE_TEST_TMPDIR ?? tmpdir(), "opentui-image-limit-")) + const path = join(directory, "oversized.png") + const file = await open(path, "w") + try { + await file.truncate(64 * 1024 * 1024 + 1) + } finally { + await file.close() + } + if (process.platform !== "win32") await chmod(path, 0) + + try { + try { + await NativeImage.load(path) + throw new Error("expected oversized image load to fail") + } catch (error) { + expect(error).toBeInstanceOf(ImageError) + expect((error as ImageError).code).toBe("memory-limit") + } + } finally { + if (process.platform !== "win32") await chmod(path, 0o600) + await rm(directory, { recursive: true, force: true }) + } + }) + + test("recognizes URL string schemes case-insensitively", async () => { + const fixture = await readFile(new URL("rgba.png", FIXTURES)) + const image = await NativeImage.load("HTTPS://images.test/image", { + fetch: async () => new Response(fixture), + }) + try { + expect(image.info().format).toBe("png") + } finally { + image.dispose() + } + + const fileUrl = new URL("rgba.png", FIXTURES).href.replace(/^file:/, "FILE:") + const fileImage = await NativeImage.load(fileUrl) + try { + expect(fileImage.info().format).toBe("png") + } finally { + fileImage.dispose() + } + }) + + test("reports unsupported URL schemes consistently for strings and URL objects", async () => { + for (const source of ["ftp://images.test/image.png", new URL("ftp://images.test/image.png")]) { + try { + await NativeImage.load(source) + throw new Error("expected load to fail") + } catch (error) { + expect(error).toBeInstanceOf(ImageLoadError) + expect((error as ImageLoadError).code).toBe("unsupported-url-scheme") + expect((error as ImageLoadError).source).toBe("ftp://images.test/image.png") + } + } + + try { + await NativeImage.load("Z:\\opentui-definitely-missing-image.png") + throw new Error("expected load to fail") + } catch (error) { + expect(error).toBeInstanceOf(ImageLoadError) + expect((error as ImageLoadError).code).toBe("file-read") + } + }) + + test("treats a relative path whose first segment contains a colon as a filesystem path", async () => { + try { + await NativeImage.load("assets:dark/missing.png") + throw new Error("expected load to fail") + } catch (error) { + expect(error).toBeInstanceOf(ImageLoadError) + expect((error as ImageLoadError).code).toBe("file-read") + } + }) + + test("reports filesystem failures", async () => { + try { + await NativeImage.load(fileURLToPath(new URL("missing.png", FIXTURES))) + throw new Error("expected load to fail") + } catch (error) { + expect(error).toBeInstanceOf(ImageLoadError) + expect((error as ImageLoadError).code).toBe("file-read") + expect((error as ImageLoadError).cause).toBeDefined() + } + }) + + test("loads HTTP responses by bytes and reports status failures", async () => { + const fixture = await readFile(new URL("lossless.webp", FIXTURES)) + const server = createServer((request, response) => { + if (request.url === "/image.not-an-extension") { + response.writeHead(200, { "content-type": "text/plain" }) + response.end(fixture) + } else { + response.writeHead(404) + response.end("missing") + } + }) + await new Promise((resolve) => server.listen(0, "127.0.0.1", resolve)) + const address = server.address() + if (!address || typeof address === "string") throw new Error("missing test server address") + const base = `http://127.0.0.1:${address.port}` + try { + const image = await NativeImage.load(`${base}/image.not-an-extension`) + try { + expect(image.info().format).toBe("webp") + } finally { + image.dispose() + } + + try { + await NativeImage.load(new URL("/missing", base)) + throw new Error("expected HTTP load to fail") + } catch (error) { + expect(error).toBeInstanceOf(ImageLoadError) + expect((error as ImageLoadError).code).toBe("http-status") + expect((error as ImageLoadError).status).toBe(404) + } + } finally { + await new Promise((resolve, reject) => server.close((error) => (error ? reject(error) : resolve()))) + } + }) + + test("rejects an oversized HTTP response before consuming its body", async () => { + let cancelled = false + const body = new ReadableStream({ + cancel() { + cancelled = true + }, + }) + const response = new Response(body, { + headers: { "content-length": String(64 * 1024 * 1024 + 1) }, + }) + + try { + await NativeImage.load(new URL("https://images.test/oversized"), { + fetch: async () => response, + }) + throw new Error("expected load to fail") + } catch (error) { + expect(error).toBeInstanceOf(ImageError) + expect((error as ImageError).code).toBe("memory-limit") + expect(cancelled).toBe(true) + } + }) + + test("cancels an unsuccessful HTTP response body", async () => { + let cancelled = false + const body = new ReadableStream({ + cancel() { + cancelled = true + }, + }) + + try { + await NativeImage.load(new URL("https://images.test/missing"), { + fetch: async () => new Response(body, { status: 404 }), + }) + throw new Error("expected load to fail") + } catch (error) { + expect(error).toBeInstanceOf(ImageLoadError) + expect((error as ImageLoadError).code).toBe("http-status") + expect(cancelled).toBe(true) + } + }) + + test("stops consuming an HTTP stream when it exceeds the encoded byte limit", async () => { + const chunk = new Uint8Array(1024 * 1024) + let pulls = 0 + let cancelled = false + const body = new ReadableStream({ + pull(controller) { + pulls += 1 + if (pulls <= 70) controller.enqueue(chunk) + else controller.close() + }, + cancel() { + cancelled = true + }, + }) + + try { + await NativeImage.load(new URL("https://images.test/stream"), { + fetch: async () => new Response(body), + }) + throw new Error("expected load to fail") + } catch (error) { + expect(error).toBeInstanceOf(ImageError) + expect((error as ImageError).code).toBe("memory-limit") + expect(pulls).toBeLessThan(70) + expect(cancelled).toBe(true) + } + }) + + test("reports and applies JPEG EXIF orientation", async () => { + const plainBytes = new Uint8Array(await readFile(new URL("orientation.jpg", FIXTURES))) + const plain = NativeImage.decode(plainBytes) + const reference = plain.raw() + const sourceWidth = plain.width + const sourceHeight = plain.height + plain.dispose() + expect(sourceWidth).not.toBe(sourceHeight) + const sourcePixels = new Set() + for (let offset = 0; offset < reference.data.length; offset += 4) { + sourcePixels.add(reference.data.subarray(offset, offset + 4).join(",")) + } + expect(sourcePixels.size).toBe(sourceWidth * sourceHeight) + + const mappings: Record [number, number]> = { + // Orientation n: decoded output pixel (dx, dy) comes from source (sx, sy). + 2: (dx, dy) => [sourceWidth - 1 - dx, dy], + 3: (dx, dy) => [sourceWidth - 1 - dx, sourceHeight - 1 - dy], + 4: (dx, dy) => [dx, sourceHeight - 1 - dy], + 5: (dx, dy) => [dy, dx], + 6: (dx, dy) => [dy, sourceHeight - 1 - dx], + 7: (dx, dy) => [sourceWidth - 1 - dy, sourceHeight - 1 - dx], + 8: (dx, dy) => [sourceWidth - 1 - dy, dx], + } + + for (const [orientationText, mapSource] of Object.entries(mappings)) { + const orientation = Number(orientationText) + const swapsDimensions = orientation >= 5 + const bytes = injectJpegExifOrientation(plainBytes, orientation) + + const info = imageInfo(bytes) + expect(info.orientation).toBe(orientation) + expect(info.sourceWidth).toBe(sourceWidth) + expect(info.sourceHeight).toBe(sourceHeight) + expect(info.width).toBe(swapsDimensions ? sourceHeight : sourceWidth) + expect(info.height).toBe(swapsDimensions ? sourceWidth : sourceHeight) + + const oriented = NativeImage.decode(bytes) + try { + expect(oriented.width).toBe(info.width) + expect(oriented.height).toBe(info.height) + expect(oriented.info().orientation).toBe(1) + const raw = oriented.raw() + for (let dy = 0; dy < oriented.height; dy++) { + for (let dx = 0; dx < oriented.width; dx++) { + const [sx, sy] = mapSource(dx, dy) + const output = (dy * oriented.width + dx) * 4 + const source = (sy * sourceWidth + sx) * 4 + for (let channel = 0; channel < 4; channel++) { + if (raw.data[output + channel] !== reference.data[source + channel]) { + throw new Error(`orientation ${orientation}: pixel (${dx},${dy}) differs from source (${sx},${sy})`) + } + } + } + } + } finally { + oriented.dispose() + } + } + }) + + test("finds JPEG EXIF orientation after other application segments", async () => { + const plainBytes = new Uint8Array(await readFile(new URL("orientation.jpg", FIXTURES))) + // Insert a benign APP0 comment-style segment before the EXIF APP1 payload. + const app0 = Uint8Array.from([0xff, 0xe0, 0x00, 0x09, 0x4f, 0x50, 0x54, 0x55, 0x49, 0x00, 0x00]) + const withExif = injectJpegExifOrientation(plainBytes, 6) + const shifted = new Uint8Array(withExif.length + app0.length) + shifted.set(withExif.slice(0, 2), 0) + shifted.set(app0, 2) + shifted.set(withExif.slice(2), 2 + app0.length) + expect(imageInfo(shifted).orientation).toBe(6) + }) + + test("ignores invalid JPEG EXIF orientation values", async () => { + const plainBytes = new Uint8Array(await readFile(new URL("orientation.jpg", FIXTURES))) + for (const invalid of [0, 9]) { + const info = imageInfo(injectJpegExifOrientation(plainBytes, invalid)) + expect(info.orientation).toBe(1) + expect(info.width).toBe(16) + expect(info.height).toBe(8) + } + }) + + test("loads HTTPS URLs through fetch and reports network failures", async () => { + const fixture = await readFile(new URL("transparent.gif", FIXTURES)) + const image = await NativeImage.load(new URL("https://images.test/image"), { + fetch: async () => new Response(fixture), + }) + try { + expect(image.info().format).toBe("gif") + } finally { + image.dispose() + } + + try { + await NativeImage.load(new URL("https://images.test/failure"), { + fetch: async () => { + throw new Error("offline") + }, + }) + throw new Error("expected network load to fail") + } catch (error) { + expect(error).toBeInstanceOf(ImageLoadError) + expect((error as ImageLoadError).code).toBe("network") + } + }) +}) diff --git a/packages/core/src/tests/renderer.custom-stdout.test.ts b/packages/core/src/tests/renderer.custom-stdout.test.ts index 3031e414fd..b3d86189e9 100644 --- a/packages/core/src/tests/renderer.custom-stdout.test.ts +++ b/packages/core/src/tests/renderer.custom-stdout.test.ts @@ -1,9 +1,18 @@ import { test, expect, afterEach } from "bun:test" import { Writable } from "stream" import { createCliRenderer, CliRenderer, CliRenderEvents } from "../renderer.js" +import { BoxRenderable } from "../renderables/Box.js" +import { ImageRenderable } from "../renderables/Image.js" import { ManualClock } from "../testing/manual-clock.js" import { createTestStdin, TestWriteStream } from "../testing/test-streams.js" +const PNG_1X1 = Uint8Array.from( + Buffer.from( + "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR4AWP4z8DwHwAFAAH/e+m+7wAAAABJRU5ErkJggg==", + "base64", + ), +) + // Collecting Writable used as a mock stdout. Because it is !== process.stdout, // createCliRenderer allocates a NativeSpanFeed and pipes bytes through it. class CollectingWriteStream extends TestWriteStream { @@ -40,6 +49,12 @@ function createCollectingStdout(columns = 80, rows = 24): CollectingStdout { return new CollectingWriteStream(columns, rows) as CollectingStdout } +function flushWritable(stdout: NodeJS.WritableStream): Promise { + return new Promise((resolve, reject) => { + stdout.write(Buffer.alloc(0), (error) => (error ? reject(error) : resolve())) + }) +} + function createPlainStdout(): NodeJS.WriteStream { return new Writable({ write(_c, _e, cb) { @@ -145,6 +160,442 @@ test("non-process stdout: rendered bytes flow to the custom Writable", async () expect(received.includes(0x1b)).toBe(true) }) +test("auto images use detected Kitty graphics and delete cleared placements", async () => { + const stdin = createTestStdin() + const stdout = createCollectingStdout(8, 4) + const renderer = await createCliRenderer({ stdin, stdout }) + destroyFns.push(() => renderer.destroy()) + await flushWritable(stdout) + stdout.clearWrites() + + stdin.emit("data", Buffer.from("\x1b_Gi=31337;OK\x1b\\")) + const image = new ImageRenderable(renderer, { + source: PNG_1X1, + protocol: "auto", + position: "absolute", + width: 2, + height: 1, + }) + renderer.root.add(image) + await image.loadPromise + renderer.requestRender() + await renderer.idle() + await flushWritable(stdout) + + expect(renderer.capabilities?.kitty_graphics).toBe(true) + expect(stdout.getWrittenBytes().toString("binary")).toContain("\x1b_G") + + stdout.clearWrites() + image.source = undefined + await renderer.idle() + await flushWritable(stdout) + expect(stdout.getWrittenBytes().toString("binary")).toContain("a=d") +}) + +test("auto images use detected Sixel when pixel resolution is available", async () => { + const stdin = createTestStdin() + const stdout = createCollectingStdout(8, 4) + const renderer = await createCliRenderer({ stdin, stdout }) + destroyFns.push(() => renderer.destroy()) + await flushWritable(stdout) + stdout.clearWrites() + + stdin.emit("data", Buffer.from("\x1b[?1;4c\x1b[4;80;80t")) + const image = new ImageRenderable(renderer, { + source: PNG_1X1, + protocol: "auto", + position: "absolute", + width: 2, + height: 1, + }) + renderer.root.add(image) + await image.loadPromise + renderer.requestRender() + await renderer.idle() + await flushWritable(stdout) + + expect(renderer.capabilities?.sixel).toBe(true) + expect(renderer.resolution).toEqual({ width: 80, height: 80 }) + expect(stdout.getWrittenBytes().toString("binary")).toContain("\x1bP0;1;0q") +}) + +test("resized images wait for the new pixel resolution before using Sixel", async () => { + const stdin = createTestStdin() + const stdout = createCollectingStdout(8, 4) + const renderer = await createCliRenderer({ stdin, stdout }) + destroyFns.push(() => renderer.destroy()) + + stdin.emit("data", Buffer.from("\x1b[4;80;80t")) + const image = new ImageRenderable(renderer, { + source: PNG_1X1, + protocol: "sixel", + position: "absolute", + width: 2, + height: 1, + fit: "fill", + }) + renderer.root.add(image) + await image.loadPromise + renderer.requestRender() + await renderer.idle() + await flushWritable(stdout) + expect(image.effectiveProtocol).toBe("sixel") + expect(stdout.getWrittenBytes().toString("binary")).toContain('0;1;0q"1;1;20;20') + + stdout.clearWrites() + renderer.resize(16, 4) + await renderer.idle() + await flushWritable(stdout) + + const pendingOutput = stdout.getWrittenBytes().toString("binary") + expect(pendingOutput).toContain("\x1b[14t") + expect(pendingOutput).not.toContain('0;1;0q"1;1;10;20') + expect(renderer.resolution).toBeNull() + expect(image.effectiveProtocol).toBe("blocks") + expect(pendingOutput).not.toContain("\x1bP0;1;0q") + expect(stdout.getWrittenBytes().toString("utf8")).toContain("█") + + stdout.clearWrites() + stdin.emit("data", Buffer.from("\x1b[4;80;160t")) + await renderer.idle() + await flushWritable(stdout) + + expect(renderer.resolution).toEqual({ width: 160, height: 80 }) + expect(image.effectiveProtocol).toBe("sixel") + expect(stdout.getWrittenBytes().toString("binary")).toContain('0;1;0q"1;1;20;20') +}) + +test("split-footer Kitty scrollback does not rasterize images to terminal pixel dimensions", async () => { + const stdin = createTestStdin() + const stdout = createCollectingStdout(80, 60) + const renderer = await createCliRenderer({ + stdin, + stdout, + screenMode: "split-footer", + footerHeight: 12, + externalOutputMode: "capture-stdout", + consoleMode: "disabled", + }) + destroyFns.push(() => renderer.destroy()) + + stdin.emit("data", Buffer.from("\x1b[4;4320;7680t\x1b_Gi=31337;OK\x1b\\\x1b[48;1R")) + await renderer.idle() + await flushWritable(stdout) + stdout.clearWrites() + + const surface = renderer.createScrollbackSurface({ startOnNewLine: true }) + const image = new ImageRenderable(surface.renderContext, { + source: PNG_1X1, + protocol: "auto", + width: 80, + height: 48, + fit: "fill", + }) + surface.root.add(image) + await image.loadPromise + surface.render() + surface.commitRows(0, surface.height) + surface.destroy() + + stdout.write("after-image\n") + await renderer.idle() + await flushWritable(stdout) + + const output = stdout.getWrittenBytes().toString("binary") + expect(output).toContain("\x1b_Ga=t") + expect(output).toContain("a=t,f=100") + expect(output).toContain("c=80,r=48") + expect(output).toContain("after-image") +}) + +test("split-footer queues native image scrollback until the footer is pinned", async () => { + const stdin = createTestStdin() + const stdout = createCollectingStdout(8, 6) + const renderer = await createCliRenderer({ + stdin, + stdout, + screenMode: "split-footer", + footerHeight: 3, + externalOutputMode: "capture-stdout", + consoleMode: "disabled", + }) + destroyFns.push(() => renderer.destroy()) + await flushWritable(stdout) + stdout.clearWrites() + + const surface = renderer.createScrollbackSurface({ startOnNewLine: true }) + const image = new ImageRenderable(surface.renderContext, { + source: PNG_1X1, + protocol: "kitty", + width: 1, + height: 1, + }) + surface.root.add(image) + await image.loadPromise + surface.render() + surface.commitRows(0, surface.height) + surface.destroy() + await renderer.idle() + await flushWritable(stdout) + + expect(stdout.getWrittenBytes()).toHaveLength(0) + + stdin.emit("data", Buffer.from("\x1b[3;1R")) + await renderer.idle() + await flushWritable(stdout) + + expect(stdout.getWrittenBytes().toString("binary")).toContain("\x1b_Ga=t") + expect(stdout.getWrittenBytes().toString("utf8")).not.toContain("█") +}) + +test("split-footer scrollback uses blocks for mixed protocols and overlapping images", async () => { + const stdin = createTestStdin() + const stdout = createCollectingStdout(8, 6) + const renderer = await createCliRenderer({ + stdin, + stdout, + screenMode: "split-footer", + footerHeight: 3, + externalOutputMode: "capture-stdout", + consoleMode: "disabled", + }) + destroyFns.push(() => renderer.destroy()) + + stdin.emit("data", Buffer.from("\x1b[?1;4c\x1b[4;6;8t\x1b_Gi=31337;OK\x1b\\\x1b[3;1R")) + await renderer.idle() + await flushWritable(stdout) + stdout.clearWrites() + + const commitImages = async (images: Array<{ protocol: "kitty" | "sixel"; left?: number }>) => { + const surface = renderer.createScrollbackSurface({ startOnNewLine: true }) + const renderables = images.map( + ({ protocol, left }) => + new ImageRenderable(surface.renderContext, { + source: PNG_1X1, + protocol, + position: "absolute", + left, + width: 1, + height: 1, + }), + ) + for (const image of renderables) surface.root.add(image) + await Promise.all(renderables.map((image) => image.loadPromise)) + surface.render() + surface.commitRows(0, surface.height) + surface.destroy() + await renderer.idle() + await flushWritable(stdout) + } + + await commitImages([ + { protocol: "kitty", left: 0 }, + { protocol: "sixel", left: 1 }, + ]) + + let output = stdout.getWrittenBytes().toString("binary") + expect(output).not.toContain("\x1b_G") + expect(output).not.toContain("\x1bP0;1;0q") + expect(stdout.getWrittenBytes().toString("utf8")).toContain("█") + + stdout.clearWrites() + await commitImages([{ protocol: "kitty" }, { protocol: "kitty" }]) + + output = stdout.getWrittenBytes().toString("binary") + expect(output).not.toContain("\x1b_G") + expect(output).not.toContain("\x1bP0;1;0q") + expect(stdout.getWrittenBytes().toString("utf8")).toContain("█") +}) + +test("ScrollbackSurface rejects stale image geometry after a height-only resize", async () => { + const stdin = createTestStdin() + const stdout = createCollectingStdout(8, 12) + const renderer = await createCliRenderer({ + stdin, + stdout, + screenMode: "split-footer", + footerHeight: 3, + externalOutputMode: "capture-stdout", + consoleMode: "disabled", + }) + destroyFns.push(() => renderer.destroy()) + + stdin.emit("data", Buffer.from("\x1b[?1;4c\x1b[4;80;80t\x1b[9;1R")) + await renderer.idle() + + const surface = renderer.createScrollbackSurface({ startOnNewLine: true }) + const image = new ImageRenderable(surface.renderContext, { + source: PNG_1X1, + protocol: "auto", + width: 1, + height: 1, + fit: "fill", + }) + surface.root.add(image) + await image.loadPromise + surface.render() + + renderer.resize(8, 6) + stdin.emit("data", Buffer.from("\x1b[4;80;80t")) + expect(() => surface.commitRows(0, surface.height)).toThrow( + "ScrollbackSurface.commitRows requires render() after renderer geometry changes", + ) + + surface.render() + surface.commitRows(0, surface.height) + surface.destroy() + await renderer.idle() + await flushWritable(stdout) + + expect(stdout.getWrittenBytes().toString("binary")).toContain('0;1;0q"1;1;10;13') +}) + +test("ScrollbackSurface rejects stale image geometry after pixel resolution arrives", async () => { + const stdin = createTestStdin() + const stdout = createCollectingStdout(8, 6) + const renderer = await createCliRenderer({ + stdin, + stdout, + screenMode: "split-footer", + footerHeight: 3, + externalOutputMode: "capture-stdout", + consoleMode: "disabled", + }) + destroyFns.push(() => renderer.destroy()) + + stdin.emit("data", Buffer.from("\x1b[?1;4c\x1b[3;1R")) + await renderer.idle() + + const surface = renderer.createScrollbackSurface({ startOnNewLine: true }) + const image = new ImageRenderable(surface.renderContext, { + source: PNG_1X1, + protocol: "auto", + width: 1, + height: 1, + fit: "fill", + }) + surface.root.add(image) + await image.loadPromise + surface.render() + + stdin.emit("data", Buffer.from("\x1b[4;80;80t")) + expect(() => surface.commitRows(0, surface.height)).toThrow( + "ScrollbackSurface.commitRows requires render() after renderer geometry changes", + ) + + surface.render() + surface.commitRows(0, surface.height) + surface.destroy() + await renderer.idle() + await flushWritable(stdout) + + expect(stdout.getWrittenBytes().toString("binary")).toContain('0;1;0q"1;1;10;13') +}) + +test("tall scrollback surfaces composite translucent Sixel images over snapshot backgrounds", async () => { + const stdin = createTestStdin() + const stdout = createCollectingStdout(8, 6) + const renderer = await createCliRenderer({ + stdin, + stdout, + screenMode: "split-footer", + footerHeight: 3, + externalOutputMode: "capture-stdout", + consoleMode: "disabled", + }) + destroyFns.push(() => renderer.destroy()) + + stdin.emit("data", Buffer.from("\x1b[?1;4c\x1b[4;6;8t\x1b[3;1R")) + await renderer.idle() + await flushWritable(stdout) + stdout.clearWrites() + + const surface = renderer.createScrollbackSurface({ startOnNewLine: true }) + const background = new BoxRenderable(surface.renderContext, { + width: 1, + height: 5, + backgroundColor: "#0000ff", + }) + const image = new ImageRenderable(surface.renderContext, { + source: PNG_1X1, + protocol: "auto", + position: "absolute", + left: 0, + top: 4, + width: 1, + height: 1, + fit: "fill", + opacity: 0.5, + }) + background.add(image) + surface.root.add(background) + await image.loadPromise + surface.render() + surface.commitRows(0, surface.height) + surface.destroy() + await renderer.idle() + await flushWritable(stdout) + + expect(stdout.getWrittenBytes().toString("binary")).toContain("#0;2;50;0;50") +}) + +for (const testCase of [ + { + name: "Kitty", + capabilities: "\x1b[4;6;8t\x1b_Gi=31337;OK\x1b\\\x1b[3;1R", + placement: "\x1b_Ga=p", + }, + { + name: "Sixel", + capabilities: "\x1b[?1;4c\x1b[4;6;8t\x1b[3;1R", + placement: "\x1bP0;1;0q", + }, +]) { + test(`pinned split-footer appends repaint unchanged live ${testCase.name} images`, async () => { + const stdin = createTestStdin() + const stdout = createCollectingStdout(8, 6) + const renderer = await createCliRenderer({ + stdin, + stdout, + screenMode: "split-footer", + footerHeight: 3, + externalOutputMode: "capture-stdout", + consoleMode: "disabled", + }) + destroyFns.push(() => renderer.destroy()) + + stdin.emit("data", Buffer.from(testCase.capabilities)) + const image = new ImageRenderable(renderer, { + source: PNG_1X1, + protocol: "auto", + position: "absolute", + left: 0, + top: 0, + width: 1, + height: 1, + fit: "fill", + }) + renderer.root.add(image) + await image.loadPromise + renderer.requestRender() + await renderer.idle() + await (renderer as any)._feed.idle() + stdout.clearWrites() + + const appended = `pin${testCase.name[0]}` + stdout.write(`${appended}\n`) + renderer.requestRender() + await renderer.idle() + await (renderer as any)._feed.idle() + + const output = stdout.getWrittenBytes().toString("binary") + const appendIndex = output.indexOf(appended) + const placementIndex = output.indexOf(testCase.placement) + expect(output).toContain(appended) + expect(placementIndex).toBeGreaterThan(appendIndex) + }) +} + test("split-footer custom stdout: native feed bytes bypass stdout capture", async () => { const stdin = createTestStdin() const stdout = createCollectingStdout(80, 24) @@ -538,6 +989,32 @@ test("destroy emits shutdown ANSI sequence through the custom Writable", async ( expect(shutdownBytes).toContain("\x1b[?25h") // showCursor }) +test("destroy preserves shutdown output while slow writes pin initial feed chunks", async () => { + const stdin = createTestStdin() + const stdout = createCollectingStdout(80, 24) + const renderer = await createCliRenderer({ stdin, stdout }) + destroyFns.push(() => renderer.destroy()) + + const feed = (renderer as any)._feed + expect(feed).not.toBeNull() + await feed.idle() + stdout.clearWrites() + + stdout.delayMs = 100 + // Two delayed writes pin the default feed's initial chunks during shutdown. + renderer.setTerminalTitle("pin-feed-1") + renderer.setTerminalTitle("pin-feed-2") + renderer.destroy() + + await new Promise((resolve, reject) => { + stdout.write(Buffer.alloc(0), (error) => (error ? reject(error) : resolve())) + }) + const output = stdout.getWrittenBytes().toString("binary") + expect(output).toContain("\x1b]0;pin-feed-1\x07") + expect(output).toContain("\x1b]0;pin-feed-2\x07") + expect(output).toContain("\x1b[?25h") +}) + // ---- Backpressure ---- test("slow Writable marks feed as backpressured until write callback settles", async () => { @@ -705,6 +1182,45 @@ test("split-footer custom stdout retains captured commits when native fails and expect(stdout.getWrittenBytes().toString("binary")).toContain("captured-while-native-failed") }) +test("split-footer retains the whole batch when final native publication fails", async () => { + const clock = new ManualClock() + const stdout = createCollectingStdout(80, 24) + const renderer = new CliRenderer(createTestStdin(), stdout, 80, 24, { + screenMode: "split-footer", + consoleMode: "disabled", + clock, + }) + ;(renderer as any).updateScheduled = false + clock.runAll() + destroyFns.push(() => renderer.destroy()) + + const rendererAny = renderer as any + const originalCommit = rendererAny.lib.commitSplitFooterSnapshot.bind(rendererAny.lib) + let calls = 0 + rendererAny.lib.commitSplitFooterSnapshot = (...args: any[]) => { + calls++ + const finalizeFrame = args[8] + return { renderOffset: rendererAny.renderOffset, status: finalizeFrame ? 2 : 0 } + } + + stdout.write("first\nsecond\n") + expect(rendererAny.externalOutputQueue.size).toBe(2) + + try { + await rendererAny.loop() + expect(calls).toBe(2) + expect(rendererAny.externalOutputQueue.size).toBe(2) + } finally { + rendererAny.lib.commitSplitFooterSnapshot = originalCommit + } + + await rendererAny.loop() + await (rendererAny._feed?.idle() ?? Promise.resolve()) + const output = stdout.getWrittenBytes().toString("binary") + expect(output).toContain("first") + expect(output).toContain("second") +}) + test("split-footer native failure without a feed does not schedule automatic retries", async () => { const clock = new ManualClock() const stdout = createPlainStdout() diff --git a/packages/core/src/tests/renderer.scrollback-surface.test.ts b/packages/core/src/tests/renderer.scrollback-surface.test.ts index b9ad1151e1..aff31b7268 100644 --- a/packages/core/src/tests/renderer.scrollback-surface.test.ts +++ b/packages/core/src/tests/renderer.scrollback-surface.test.ts @@ -1,8 +1,11 @@ import { afterEach, expect, test } from "bun:test" +import { readFile } from "node:fs/promises" import { RGBA } from "../lib/RGBA.js" import { Renderable, type RenderableOptions } from "../Renderable.js" +import { BoxRenderable } from "../renderables/Box.js" import { CodeRenderable } from "../renderables/Code.js" +import { ImageRenderable } from "../renderables/Image.js" import { MarkdownRenderable } from "../renderables/Markdown.js" import { TextRenderable } from "../renderables/Text.js" import { SyntaxStyle } from "../syntax-style.js" @@ -12,6 +15,7 @@ import type { RenderContext } from "../types.js" type ClaimedCommit = { snapshot: { height: number + buffers: { char: Uint32Array } getRealCharBytes(addLineBreaks?: boolean): Uint8Array destroy(): void } @@ -121,6 +125,58 @@ test("ScrollbackSurface.commitRows reuses the last rendered buffer", async () => } }) +test("ScrollbackSurface retains loaded images for native scrollback rendering", async () => { + const { renderer } = await createSplitFooterRenderer({ width: 8, height: 6, footerHeight: 3 }) + const surface = renderer.createScrollbackSurface({ startOnNewLine: true }) + const source = new Uint8Array(await readFile(new URL("./fixtures/images/rgba.png", import.meta.url))) + const card = new BoxRenderable(surface.renderContext, { + id: "surface-image-card", + width: 8, + height: 4, + border: true, + }) + const image = new ImageRenderable(surface.renderContext, { + id: "surface-image", + source, + protocol: "kitty", + width: "100%", + height: "100%", + }) + card.add(image) + surface.root.add(card) + + await image.loadPromise + expect(image.image).not.toBeNull() + surface.render() + expect(surface.height).toBe(4) + surface.commitRows(0, surface.height, { rowColumns: 8, trailingNewline: false }) + surface.destroy() + + let nextTailColumn = -1 + renderer.writeToScrollback((ctx) => { + nextTailColumn = ctx.tailColumn + const root = new TextRenderable(ctx.renderContext, { + id: "after-surface-image", + position: "absolute", + left: 0, + top: 0, + width: 1, + height: 1, + content: "X", + }) + return { root, width: 1, height: 1, startOnNewLine: false, trailingNewline: false } + }) + + const commits = claimCommits(renderer) + try { + expect(commits).toHaveLength(2) + expect(commits[0]!.snapshot.buffers.char.some((char) => (char & 0xc0000000) >>> 0 === 0x40000000)).toBe(true) + expect(nextTailColumn).toBe(8) + } finally { + destroyClaimedCommits(commits) + } +}) + test("ScrollbackSurface.settle waits for code highlighting before commit", async () => { const { renderer } = await createSplitFooterRenderer() const surface = renderer.createScrollbackSurface({ startOnNewLine: true }) diff --git a/packages/core/src/types.ts b/packages/core/src/types.ts index 7e0328b53e..f7100e7ad5 100644 --- a/packages/core/src/types.ts +++ b/packages/core/src/types.ts @@ -58,6 +58,7 @@ export enum TargetChannel { export type WidthMethod = "wcwidth" | "unicode" export type TerminalMultiplexer = "none" | "tmux" | "zellij" | "screen" | "unknown" export type TerminalCapabilityState = "unknown" | "supported" | "unsupported" +export type ImageRenderProtocol = "auto" | "kitty" | "sixel" | "blocks" export interface TerminalInfo { name: string @@ -86,6 +87,7 @@ export interface TerminalCapabilities { explicit_cursor_positioning: boolean remote: boolean multiplexer: TerminalMultiplexer + image_protocol?: ImageRenderProtocol terminal: TerminalInfo } @@ -108,6 +110,9 @@ export interface RenderContext extends EventEmitter { clearHitGridScissorRects: () => void width: number height: number + terminalWidth?: number + terminalHeight?: number + resolution?: { width: number; height: number } | null /** Monotonic, bumped once per `loop()` iteration. Lets renderables dedupe per-frame work. */ frameId: number requestRender: () => void diff --git a/packages/core/src/zig-structs.ts b/packages/core/src/zig-structs.ts index 9f82665064..5606d69917 100644 --- a/packages/core/src/zig-structs.ts +++ b/packages/core/src/zig-structs.ts @@ -91,6 +91,7 @@ export const VisualCursorStruct = defineStruct([ const UnicodeMethodEnum = defineEnum({ wcwidth: 0, unicode: 1 }, "u8") const TerminalMultiplexerEnum = defineEnum({ none: 0, tmux: 1, zellij: 2, screen: 3, unknown: 4 }, "u8") const Osc52SupportEnum = defineEnum({ unknown: 0, supported: 1, unsupported: 2 }, "u8") +const ImageProtocolEnum = defineEnum({ auto: 0, kitty: 1, sixel: 2, blocks: 3 }, "u8") export const TerminalCapabilitiesStruct = defineStruct([ ["kitty_keyboard", "bool_u8"], @@ -112,6 +113,7 @@ export const TerminalCapabilitiesStruct = defineStruct([ ["explicit_cursor_positioning", "bool_u8"], ["remote", "bool_u8"], ["multiplexer", TerminalMultiplexerEnum], + ["image_protocol", ImageProtocolEnum], ["term_name", "char*"], ["term_name_len", "u64", { lengthOf: "term_name" }], ["term_version", "char*"], @@ -125,6 +127,42 @@ export const EncodedCharStruct = defineStruct([ ["char", "u32"], ]) +export interface NativeImageInfo { + width: number + height: number + sourceWidth: number + sourceHeight: number + format: number + colorStatus: number + orientation: number + hasAlpha: number +} + +export const NativeImageInfoStruct = defineStruct([ + ["width", "u32"], + ["height", "u32"], + ["sourceWidth", "u32"], + ["sourceHeight", "u32"], + ["format", "u32"], + ["colorStatus", "u32"], + ["orientation", "u32"], + ["hasAlpha", "u32"], +]) + +export const ImageDrawOptionsStruct = defineStruct([ + ["x", "i32"], + ["y", "i32"], + ["width", "u32"], + ["height", "u32"], + ["pixelWidth", "u32"], + ["pixelHeight", "u32"], + ["sourceX", "u32"], + ["sourceY", "u32"], + ["sourceWidth", "u32"], + ["sourceHeight", "u32"], + ["protocol", "u32"], +]) + export const LineInfoStruct = defineStruct([ ["startCols", ["u32"]], ["startColsLen", "u32", { lengthOf: "startCols" }], diff --git a/packages/core/src/zig.ts b/packages/core/src/zig.ts index 2e7da7b705..29358f2b00 100644 --- a/packages/core/src/zig.ts +++ b/packages/core/src/zig.ts @@ -22,6 +22,7 @@ import { type Highlight, type LineInfo, type MousePointerStyle, + type ImageRenderProtocol, } from "./types.js" export type { LineInfo, @@ -65,6 +66,8 @@ import { BuildOptionsStruct, AllocatorStatsStruct, NativeRenderStatsStruct, + NativeImageInfoStruct, + ImageDrawOptionsStruct, } from "./zig-structs.js" import type { NativeSpanFeedOptions, @@ -83,6 +86,7 @@ import type { BuildOptions, AllocatorStats, NativeRenderStats, + NativeImageInfo, } from "./zig-structs.js" export const NativeAudioStreamState = NativeAudioStreamStateValue export type NativeAudioStreamState = NativeAudioStreamStateType @@ -112,6 +116,58 @@ export type SyntaxStyleHandle = NativeHandle<"syntax_style"> export type EventSinkHandle = NativeHandle<"event_sink"> export type AudioEngineHandle = NativeHandle<"audio_engine"> export type NativeRenderableHandle = NativeHandle<"native_renderable"> +export type ImageHandle = NativeHandle<"image"> +export type ClipboardServiceHandle = number & { readonly __nativeHandle: "clipboard_service" } +export type ClipboardOperationHandle = number & { readonly __nativeHandle: "clipboard_operation" } + +export enum NativeClipboardOperationStatus { + Pending = 0, + Read = 1, + Empty = 2, + Written = 3, + Cleared = 4, + Unsupported = 5, + Cancelled = 6, + TimedOut = 7, + LimitExceeded = 8, + Failed = 9, + InvalidHandle = 10, +} + +export enum NativeClipboardStartStatus { + Ok = 0, + InvalidService = 1, + ShuttingDown = 2, + LimitExceeded = 3, + InvalidArgument = 4, + OutOfMemory = 5, +} + +export enum NativeClipboardCancelStatus { + Requested = 0, + AlreadyTerminal = 1, + InvalidHandle = 2, +} + +export enum NativeClipboardCopyStatus { + Ok = 0, + BufferTooSmall = 1, + InvalidHandle = 2, + InvalidState = 3, + InvalidArgument = 4, +} + +export enum NativeClipboardDestroyStatus { + Destroyed = 0, + NotReady = 1, + InvalidHandle = 2, +} + +export enum NativeClipboardShutdownStatus { + Pending = 0, + Ready = 1, + InvalidHandle = 2, +} let targetLibPath: string | undefined let targetLibError: Error | undefined @@ -156,10 +212,16 @@ registerEnvVar({ }) registerEnvVar({ name: "OPENTUI_GRAPHICS", - description: "Override Kitty graphics detection with the exact value true, 1, false, or 0", + description: "Control Kitty and Sixel graphics detection with the exact value true, 1, false, or 0", type: "string", required: false, }) +registerEnvVar({ + name: "OPENTUI_IMAGE_PROTOCOL", + description: "Override image rendering protocol: auto, kitty, sixel, or blocks", + type: "string", + default: "auto", +}) registerEnvVar({ name: "OPENTUI_FORCE_NOZWJ", description: "Use no_zwj width mode when the variable is present", @@ -512,6 +574,78 @@ function getOpenTUILib(libPath?: string) { args: ["u32", "u8"], returns: "bool", }, + clipboardServiceCreate: { + args: ["u32", "u32", "ptr", "u32"], + returns: "u32", + }, + clipboardServiceBeginShutdown: { + args: ["u32"], + returns: "u8", + }, + clipboardServicePollShutdown: { + args: ["u32"], + returns: "u8", + }, + clipboardServiceDestroy: { + args: ["u32"], + returns: "u8", + }, + clipboardServiceDrain: { + args: ["u32"], + returns: "u8", + }, + clipboardReadOperationStart: { + args: ["u32", "ptr", "u32", "u8", "u32", "u32", "u32", "u32", "ptr"], + returns: "u8", + }, + clipboardWriteOperationStart: { + args: ["u32", "ptr", "u32", "u8", "u32", "ptr"], + returns: "u8", + }, + clipboardClearOperationStart: { + args: ["u32", "u8", "u32", "ptr"], + returns: "u8", + }, + clipboardOperationPoll: { + args: ["u32"], + returns: "u8", + }, + clipboardOperationCancel: { + args: ["u32"], + returns: "u8", + }, + clipboardOperationResultMimeLength: { + args: ["u32", "ptr"], + returns: "u8", + }, + clipboardOperationResultMimeCopy: { + args: ["u32", "ptr", "u32"], + returns: "u8", + }, + clipboardOperationResultDataLength: { + args: ["u32", "ptr"], + returns: "u8", + }, + clipboardOperationResultDataCopy: { + args: ["u32", "ptr", "u32"], + returns: "u8", + }, + clipboardOperationResultErrorCode: { + args: ["u32", "ptr"], + returns: "u8", + }, + clipboardOperationResultDiagnosticLength: { + args: ["u32", "ptr"], + returns: "u8", + }, + clipboardOperationResultDiagnosticCopy: { + args: ["u32", "ptr", "u32"], + returns: "u8", + }, + clipboardOperationDestroy: { + args: ["u32"], + returns: "u8", + }, triggerNotification: { args: ["u32", "ptr", "u32", "ptr", "u32"], returns: "bool", @@ -521,6 +655,10 @@ function getOpenTUILib(libPath?: string) { args: ["u32", "u32", "u32", "ptr", "u32", "u8", "u32"], returns: "void", }, + bufferDrawImage: { + args: ["u32", "u32", "ptr"], + returns: "u8", + }, bufferDrawPackedBuffer: { args: ["u32", "ptr", "u32", "u32", "u32", "u32", "u32"], returns: "void", @@ -1240,6 +1378,20 @@ function getOpenTUILib(libPath?: string) { returns: "u32", }, + imageInfo: { args: ["ptr", "u32", "ptr"], returns: "u32" }, + imageDecode: { args: ["ptr", "u32", "ptr"], returns: "u32" }, + imageCreateFromRgba: { args: ["ptr", "u64", "u32", "u32", "u32", "ptr"], returns: "u32" }, + imageDestroy: { args: ["u32"], returns: "void" }, + imageGetInfo: { args: ["u32", "ptr"], returns: "u32" }, + imageGetPixelsPtr: { args: ["u32"], returns: "ptr" }, + imageClone: { args: ["u32", "ptr"], returns: "u32" }, + imageCopyPixels: { args: ["u32", "ptr", "u64", "u32", "u8"], returns: "u32" }, + imageResize: { args: ["u32", "u32", "u32", "u32", "ptr"], returns: "u32" }, + imageExtract: { args: ["u32", "u32", "u32", "u32", "u32", "ptr"], returns: "u32" }, + imageExtend: { args: ["u32", "u32", "u32", "u32", "u32", "ptr", "ptr"], returns: "u32" }, + imageTransform: { args: ["u32", "u32", "ptr"], returns: "u32" }, + imageComposite: { args: ["u32", "u32", "i32", "i32", "u32", "u8", "ptr"], returns: "u32" }, + // Terminal capability functions getTerminalCapabilities: { args: ["u32", "ptr"], @@ -2194,6 +2346,21 @@ export interface RenderLib extends AudioEngineLib { format: "bgra8unorm" | "rgba8unorm", alignedBytesPerRow: number, ) => void + bufferDrawImage: ( + buffer: OptimizedBufferHandle, + image: ImageHandle, + x: number, + y: number, + width: number, + height: number, + pixelWidth: number, + pixelHeight: number, + sourceX: number, + sourceY: number, + sourceWidth: number, + sourceHeight: number, + protocol: ImageRenderProtocol, + ) => boolean bufferDrawPackedBuffer: ( buffer: OptimizedBufferHandle, dataPtr: Pointer, @@ -2259,6 +2426,66 @@ export interface RenderLib extends AudioEngineLib { setTerminalTitle: (renderer: RendererHandle, title: string) => void copyToClipboardOSC52: (renderer: RendererHandle, target: number, textUtf8: Uint8Array) => boolean clearClipboardOSC52: (renderer: RendererHandle, target: number) => boolean + clipboardServiceCreate: ( + maxConcurrentOperations: number, + maxProviderTransfers: number, + waylandSeat?: string, + ) => ClipboardServiceHandle | null + clipboardServiceBeginShutdown: (service: ClipboardServiceHandle) => NativeClipboardShutdownStatus + clipboardServicePollShutdown: (service: ClipboardServiceHandle) => NativeClipboardShutdownStatus + clipboardServiceDestroy: (service: ClipboardServiceHandle) => NativeClipboardDestroyStatus + clipboardServiceDrain: (service: ClipboardServiceHandle) => number + clipboardReadOperationStart: ( + service: ClipboardServiceHandle, + request: Uint8Array, + selection: number, + maxBytes: number, + maxImagePixels: number, + maxConversionBytes: number, + timeoutMs: number, + ) => { status: NativeClipboardStartStatus; operation: ClipboardOperationHandle | null } + clipboardWriteOperationStart: ( + service: ClipboardServiceHandle, + textUtf8: Uint8Array, + selection: number, + timeoutMs: number, + ) => { status: NativeClipboardStartStatus; operation: ClipboardOperationHandle | null } + clipboardClearOperationStart: ( + service: ClipboardServiceHandle, + selection: number, + timeoutMs: number, + ) => { status: NativeClipboardStartStatus; operation: ClipboardOperationHandle | null } + clipboardOperationPoll: (operation: ClipboardOperationHandle) => NativeClipboardOperationStatus + clipboardOperationCancel: (operation: ClipboardOperationHandle) => NativeClipboardCancelStatus + clipboardOperationResultMimeLength: (operation: ClipboardOperationHandle) => { + status: NativeClipboardCopyStatus + length: number + } + clipboardOperationResultMimeCopy: ( + operation: ClipboardOperationHandle, + output: Uint8Array, + ) => NativeClipboardCopyStatus + clipboardOperationResultDataLength: (operation: ClipboardOperationHandle) => { + status: NativeClipboardCopyStatus + length: number + } + clipboardOperationResultDataCopy: ( + operation: ClipboardOperationHandle, + output: Uint8Array, + ) => NativeClipboardCopyStatus + clipboardOperationResultErrorCode: (operation: ClipboardOperationHandle) => { + status: NativeClipboardCopyStatus + errorCode: number + } + clipboardOperationResultDiagnosticLength: (operation: ClipboardOperationHandle) => { + status: NativeClipboardCopyStatus + length: number + } + clipboardOperationResultDiagnosticCopy: ( + operation: ClipboardOperationHandle, + output: Uint8Array, + ) => NativeClipboardCopyStatus + clipboardOperationDestroy: (operation: ClipboardOperationHandle) => NativeClipboardDestroyStatus triggerNotification: (renderer: RendererHandle, message: string, title?: string) => boolean addToHitGrid: (renderer: RendererHandle, x: number, y: number, width: number, height: number, id: number) => void clearCurrentHitGrid: (renderer: RendererHandle) => void @@ -2619,6 +2846,50 @@ export interface RenderLib extends AudioEngineLib { syntaxStyleResolveByName: (style: SyntaxStyleHandle, name: string) => number | null syntaxStyleGetStyleCount: (style: SyntaxStyleHandle) => number + imageInfo: (data: Uint8Array) => { status: number; info: NativeImageInfo } + imageDecode: (data: Uint8Array) => { status: number; handle: ImageHandle | null } + imageCreateFromRgba: ( + pixels: Uint8Array, + width: number, + height: number, + stride: number, + ) => { status: number; handle: ImageHandle | null } + imageDestroy: (image: ImageHandle) => void + imageGetInfo: (image: ImageHandle) => { status: number; info: NativeImageInfo } + imageGetPixelsPtr: (image: ImageHandle) => Pointer | null + imageClone: (image: ImageHandle) => { status: number; handle: ImageHandle | null } + imageCopyPixels: (image: ImageHandle, destination: Uint8Array, stride: number, bgra: boolean) => number + imageResize: ( + image: ImageHandle, + width: number, + height: number, + filter: number, + ) => { status: number; handle: ImageHandle | null } + imageExtract: ( + image: ImageHandle, + left: number, + top: number, + width: number, + height: number, + ) => { status: number; handle: ImageHandle | null } + imageExtend: ( + image: ImageHandle, + top: number, + right: number, + bottom: number, + left: number, + background: Uint8Array, + ) => { status: number; handle: ImageHandle | null } + imageTransform: (image: ImageHandle, operation: number) => { status: number; handle: ImageHandle | null } + imageComposite: ( + base: ImageHandle, + overlay: ImageHandle, + left: number, + top: number, + blend: number, + opacity: number, + ) => { status: number; handle: ImageHandle | null } + getTerminalCapabilities: (renderer: RendererHandle) => TerminalCapabilities processCapabilityResponse: (renderer: RendererHandle, response: string) => void @@ -2705,8 +2976,11 @@ class FFIRenderLib implements RenderLib { readyGeneration: 0, } as NativeAudioStreamStats, }, + imageDrawOptions: allocStruct(ImageDrawOptionsStruct), gridDrawOptions: allocStruct(GridDrawOptionsStruct), } + private disposed = false + private clipboardServices = new Set() public readonly encoder: TextEncoder = new TextEncoder() public readonly decoder: TextDecoder = new TextDecoder() private logCallbackWrapper: FFICallbackInstance | null = null @@ -2807,6 +3081,11 @@ class FFIRenderLib implements RenderLib { } public dispose(): void { + if (this.disposed) return + if (this.clipboardServices.size > 0) { + throw new Error("Cannot dispose OpenTUI native library while clipboard services are active") + } + this.disposed = true try { if (this.eventSinkPtr) { this.opentui.symbols.destroyEventSink(this.eventSinkPtr) @@ -3227,6 +3506,43 @@ class FFIRenderLib implements RenderLib { ) } + public bufferDrawImage( + buffer: OptimizedBufferHandle, + image: ImageHandle, + x: number, + y: number, + width: number, + height: number, + pixelWidth: number, + pixelHeight: number, + sourceX: number, + sourceY: number, + sourceWidth: number, + sourceHeight: number, + protocol: ImageRenderProtocol, + ): boolean { + const protocolId = { auto: 0, kitty: 1, sixel: 2, blocks: 3 }[protocol] + const storage = this.ffiStructStorage.imageDrawOptions + ImageDrawOptionsStruct.packInto( + { + x, + y, + width, + height, + pixelWidth, + pixelHeight, + sourceX, + sourceY, + sourceWidth, + sourceHeight, + protocol: protocolId, + }, + storage.view, + 0, + ) + return Boolean(this.opentui.symbols.bufferDrawImage(buffer, image, storage.buffer)) + } + public bufferDrawPackedBuffer( buffer: Pointer, dataPtr: Pointer, @@ -3543,6 +3859,197 @@ class FFIRenderLib implements RenderLib { return Boolean(this.opentui.symbols.clearClipboardOSC52(renderer, target)) } + public clipboardServiceCreate( + maxConcurrentOperations: number, + maxProviderTransfers: number, + waylandSeat?: string, + ): ClipboardServiceHandle | null { + const seat = waylandSeat === undefined ? null : this.encoder.encode(waylandSeat) + const handle = this.opentui.symbols.clipboardServiceCreate( + toSafeFFIU32Length(maxConcurrentOperations, "clipboard operation limit"), + toSafeFFIU32Length(maxProviderTransfers, "clipboard provider transfer limit"), + seat, + seat?.byteLength ?? 0, + ) + if (handle === 0) return null + const service = handle as ClipboardServiceHandle + this.clipboardServices.add(service) + return service + } + + public clipboardServiceBeginShutdown(service: ClipboardServiceHandle): NativeClipboardShutdownStatus { + if (!this.clipboardServices.has(service)) return NativeClipboardShutdownStatus.InvalidHandle + return this.opentui.symbols.clipboardServiceBeginShutdown(service) + } + + public clipboardServicePollShutdown(service: ClipboardServiceHandle): NativeClipboardShutdownStatus { + if (!this.clipboardServices.has(service)) return NativeClipboardShutdownStatus.InvalidHandle + return this.opentui.symbols.clipboardServicePollShutdown(service) + } + + public clipboardServiceDestroy(service: ClipboardServiceHandle): NativeClipboardDestroyStatus { + if (!this.clipboardServices.has(service)) return NativeClipboardDestroyStatus.InvalidHandle + const status = this.opentui.symbols.clipboardServiceDestroy(service) + if (status === NativeClipboardDestroyStatus.Destroyed) this.clipboardServices.delete(service) + return status + } + + public clipboardServiceDrain(service: ClipboardServiceHandle): number { + if (!this.clipboardServices.has(service)) return 2 + return this.opentui.symbols.clipboardServiceDrain(service) + } + + private clipboardStartResult( + status: NativeClipboardStartStatus, + output: Uint32Array, + ): { status: NativeClipboardStartStatus; operation: ClipboardOperationHandle | null } { + return { + status, + operation: output[0] === 0 ? null : (output[0] as ClipboardOperationHandle), + } + } + + public clipboardReadOperationStart( + service: ClipboardServiceHandle, + request: Uint8Array, + selection: number, + maxBytes: number, + maxImagePixels: number, + maxConversionBytes: number, + timeoutMs: number, + ): { status: NativeClipboardStartStatus; operation: ClipboardOperationHandle | null } { + const output = new Uint32Array(1) + const status = this.opentui.symbols.clipboardReadOperationStart( + service, + request, + toSafeFFIU32Length(request.byteLength, "clipboard read request"), + selection, + toSafeFFIU32Length(maxBytes, "clipboard read byte limit"), + toSafeFFIU32Length(maxImagePixels, "clipboard image pixel limit"), + toSafeFFIU32Length(maxConversionBytes, "clipboard conversion byte limit"), + toSafeFFIU32Length(timeoutMs, "clipboard read timeout"), + output, + ) + return this.clipboardStartResult(status, output) + } + + public clipboardWriteOperationStart( + service: ClipboardServiceHandle, + textUtf8: Uint8Array, + selection: number, + timeoutMs: number, + ): { status: NativeClipboardStartStatus; operation: ClipboardOperationHandle | null } { + const output = new Uint32Array(1) + const status = this.opentui.symbols.clipboardWriteOperationStart( + service, + textUtf8, + toSafeFFIU32Length(textUtf8.byteLength, "clipboard write text"), + selection, + toSafeFFIU32Length(timeoutMs, "clipboard write timeout"), + output, + ) + return this.clipboardStartResult(status, output) + } + + public clipboardClearOperationStart( + service: ClipboardServiceHandle, + selection: number, + timeoutMs: number, + ): { status: NativeClipboardStartStatus; operation: ClipboardOperationHandle | null } { + const output = new Uint32Array(1) + const status = this.opentui.symbols.clipboardClearOperationStart( + service, + selection, + toSafeFFIU32Length(timeoutMs, "clipboard clear timeout"), + output, + ) + return this.clipboardStartResult(status, output) + } + + public clipboardOperationPoll(operation: ClipboardOperationHandle): NativeClipboardOperationStatus { + return this.opentui.symbols.clipboardOperationPoll(operation) + } + + public clipboardOperationCancel(operation: ClipboardOperationHandle): NativeClipboardCancelStatus { + return this.opentui.symbols.clipboardOperationCancel(operation) + } + + private clipboardResultLength( + symbol: (operation: ClipboardOperationHandle, output: Uint32Array) => number, + operation: ClipboardOperationHandle, + ): { status: NativeClipboardCopyStatus; length: number } { + const output = new Uint32Array(1) + const status = symbol(operation, output) + return { status, length: output[0] } + } + + public clipboardOperationResultMimeLength(operation: ClipboardOperationHandle): { + status: NativeClipboardCopyStatus + length: number + } { + return this.clipboardResultLength(this.opentui.symbols.clipboardOperationResultMimeLength, operation) + } + + public clipboardOperationResultMimeCopy( + operation: ClipboardOperationHandle, + output: Uint8Array, + ): NativeClipboardCopyStatus { + return this.opentui.symbols.clipboardOperationResultMimeCopy( + operation, + output.byteLength === 0 ? null : output, + toSafeFFIU32Length(output.byteLength, "clipboard MIME output"), + ) + } + + public clipboardOperationResultDataLength(operation: ClipboardOperationHandle): { + status: NativeClipboardCopyStatus + length: number + } { + return this.clipboardResultLength(this.opentui.symbols.clipboardOperationResultDataLength, operation) + } + + public clipboardOperationResultDataCopy( + operation: ClipboardOperationHandle, + output: Uint8Array, + ): NativeClipboardCopyStatus { + return this.opentui.symbols.clipboardOperationResultDataCopy( + operation, + output.byteLength === 0 ? null : output, + toSafeFFIU32Length(output.byteLength, "clipboard data output"), + ) + } + + public clipboardOperationResultErrorCode(operation: ClipboardOperationHandle): { + status: NativeClipboardCopyStatus + errorCode: number + } { + const output = new Uint32Array(1) + const status = this.opentui.symbols.clipboardOperationResultErrorCode(operation, output) + return { status, errorCode: output[0] } + } + + public clipboardOperationResultDiagnosticLength(operation: ClipboardOperationHandle): { + status: NativeClipboardCopyStatus + length: number + } { + return this.clipboardResultLength(this.opentui.symbols.clipboardOperationResultDiagnosticLength, operation) + } + + public clipboardOperationResultDiagnosticCopy( + operation: ClipboardOperationHandle, + output: Uint8Array, + ): NativeClipboardCopyStatus { + return this.opentui.symbols.clipboardOperationResultDiagnosticCopy( + operation, + output.byteLength === 0 ? null : output, + toSafeFFIU32Length(output.byteLength, "clipboard diagnostic output"), + ) + } + + public clipboardOperationDestroy(operation: ClipboardOperationHandle): NativeClipboardDestroyStatus { + return this.opentui.symbols.clipboardOperationDestroy(operation) + } + public triggerNotification(renderer: Pointer, message: string, title?: string): boolean { const messageBytes = this.encoder.encode(message) const titleBytes = title === undefined ? null : this.encoder.encode(title) @@ -3601,12 +4108,12 @@ class FFIRenderLib implements RenderLib { } public dumpBuffers(renderer: Pointer, timestamp?: number): void { - const ts = timestamp ?? Date.now() + const ts = BigInt(timestamp ?? Date.now()) this.opentui.symbols.dumpBuffers(renderer, ts) } public dumpOutputBuffer(renderer: Pointer, timestamp?: number): void { - const ts = timestamp ?? Date.now() + const ts = BigInt(timestamp ?? Date.now()) this.opentui.symbols.dumpOutputBuffer(renderer, ts) } @@ -4969,6 +5476,7 @@ class FFIRenderLib implements RenderLib { explicit_cursor_positioning: caps.explicit_cursor_positioning, remote: caps.remote, multiplexer: caps.multiplexer, + image_protocol: caps.image_protocol, terminal: { name: caps.term_name ?? "", version: caps.term_version ?? "", @@ -5489,6 +5997,131 @@ class FFIRenderLib implements RenderLib { return this.opentui.symbols.syntaxStyleGetStyleCount(style) } + private imageHandleResult(status: number, output: Uint32Array): { status: number; handle: ImageHandle | null } { + return { status, handle: status === 0 && output[0] !== 0 ? (output[0] as ImageHandle) : null } + } + + public imageInfo(data: Uint8Array): { status: number; info: NativeImageInfo } { + const length = toSafeFFIU32Length(data.byteLength, "image data") + const output = new ArrayBuffer(NativeImageInfoStruct.size) + const status = this.opentui.symbols.imageInfo(data.byteLength === 0 ? null : data, length, output) + return { status, info: NativeImageInfoStruct.unpack(output) } + } + + public imageDecode(data: Uint8Array): { status: number; handle: ImageHandle | null } { + const length = toSafeFFIU32Length(data.byteLength, "image data") + const output = new Uint32Array(1) + return this.imageHandleResult( + this.opentui.symbols.imageDecode(data.byteLength === 0 ? null : data, length, output), + output, + ) + } + + public imageCreateFromRgba( + pixels: Uint8Array, + width: number, + height: number, + stride: number, + ): { status: number; handle: ImageHandle | null } { + const output = new Uint32Array(1) + const status = this.opentui.symbols.imageCreateFromRgba( + pixels.byteLength === 0 ? null : pixels, + BigInt(pixels.byteLength), + width, + height, + stride, + output, + ) + return this.imageHandleResult(status, output) + } + + public imageDestroy(image: ImageHandle): void { + this.opentui.symbols.imageDestroy(image) + } + + public imageGetInfo(image: ImageHandle): { status: number; info: NativeImageInfo } { + const output = new ArrayBuffer(NativeImageInfoStruct.size) + const status = this.opentui.symbols.imageGetInfo(image, output) + return { status, info: NativeImageInfoStruct.unpack(output) } + } + + public imageGetPixelsPtr(image: ImageHandle): Pointer | null { + const pointer = this.opentui.symbols.imageGetPixelsPtr(image) + return pointer === null || pointer === 0 || pointer === 0n ? null : pointer + } + + public imageClone(image: ImageHandle): { status: number; handle: ImageHandle | null } { + const output = new Uint32Array(1) + return this.imageHandleResult(this.opentui.symbols.imageClone(image, output), output) + } + + public imageCopyPixels(image: ImageHandle, destination: Uint8Array, stride: number, bgra: boolean): number { + return this.opentui.symbols.imageCopyPixels( + image, + destination.byteLength === 0 ? null : destination, + BigInt(destination.byteLength), + stride, + bgra ? 1 : 0, + ) + } + + public imageResize( + image: ImageHandle, + width: number, + height: number, + filter: number, + ): { status: number; handle: ImageHandle | null } { + const output = new Uint32Array(1) + return this.imageHandleResult(this.opentui.symbols.imageResize(image, width, height, filter, output), output) + } + + public imageExtract( + image: ImageHandle, + left: number, + top: number, + width: number, + height: number, + ): { status: number; handle: ImageHandle | null } { + const output = new Uint32Array(1) + return this.imageHandleResult(this.opentui.symbols.imageExtract(image, left, top, width, height, output), output) + } + + public imageExtend( + image: ImageHandle, + top: number, + right: number, + bottom: number, + left: number, + background: Uint8Array, + ): { status: number; handle: ImageHandle | null } { + if (!(background instanceof Uint8Array) || background.byteLength !== 4) return { status: 7, handle: null } + const output = new Uint32Array(1) + return this.imageHandleResult( + this.opentui.symbols.imageExtend(image, top, right, bottom, left, background, output), + output, + ) + } + + public imageTransform(image: ImageHandle, operation: number): { status: number; handle: ImageHandle | null } { + const output = new Uint32Array(1) + return this.imageHandleResult(this.opentui.symbols.imageTransform(image, operation, output), output) + } + + public imageComposite( + base: ImageHandle, + overlay: ImageHandle, + left: number, + top: number, + blend: number, + opacity: number, + ): { status: number; handle: ImageHandle | null } { + const output = new Uint32Array(1) + return this.imageHandleResult( + this.opentui.symbols.imageComposite(base, overlay, left, top, blend, opacity, output), + output, + ) + } + public editorViewSetPlaceholderStyledText( view: EditorViewHandle, chunks: Array<{ text: string; fg?: RGBA | null; bg?: RGBA | null; attributes?: number }>, diff --git a/packages/core/src/zig/ansi.zig b/packages/core/src/zig/ansi.zig index d1df068aa0..7e6b502924 100644 --- a/packages/core/src/zig/ansi.zig +++ b/packages/core/src/zig/ansi.zig @@ -321,7 +321,7 @@ pub const ANSI = struct { pub const decrqmColorScheme = "\x1b[?2031$p"; pub const csiUQuery = "\x1b[?u"; pub const xtgettcapMs = "\x1bP+q4d73\x1b\\"; - pub const kittyGraphicsQuery = "\x1b_Gi=31337,s=1,v=1,a=q,t=d,f=24;AAAA\x1b\\\x1b[c"; + pub const kittyGraphicsQuery = "\x1b_Gi=31337,s=1,v=1,a=q,t=d,f=24;AAAA\x1b\\"; pub const notificationQueries = "\x1b]99;i=opentui-notifications:p=?;\x1b\\\x1b]1337;Capabilities\x1b\\"; pub const capabilityQueriesBase = xtgettcapMs ++ @@ -358,6 +358,7 @@ pub const ANSI = struct { } pub const kittyGraphicsQueryTmux = wrapForTmux(kittyGraphicsQuery); + pub const primaryDeviceAttrsTmux = wrapForTmux(primaryDeviceAttrs); pub const capabilityQueriesTmux = wrapForTmux(capabilityQueriesBase) ++ csiUQuery ++ notificationQueries; pub const capabilityQueriesFootIsBrokenTmux = wrapForTmux(capabilityQueriesBase) ++ csiUQuery; pub const sixelGeometryQuery = "\x1b[?2;1;0S"; diff --git a/packages/core/src/zig/bench-utils.zig b/packages/core/src/zig/bench-utils.zig index 06dc2ac51d..5233702990 100644 --- a/packages/core/src/zig/bench-utils.zig +++ b/packages/core/src/zig/bench-utils.zig @@ -34,6 +34,8 @@ pub const BenchResult = struct { max_ns: u64, total_ns: u64, iterations: usize, + stddev_ns: ?f64 = null, + rme_95: ?f64 = null, mem_stats: ?[]const MemStat, }; @@ -43,18 +45,43 @@ pub const BenchStats = struct { max_ns: u64 = 0, total_ns: u64 = 0, count: usize = 0, + mean_ns: f64 = 0, + m2_ns: f64 = 0, pub fn record(self: *BenchStats, elapsed_ns: u64) void { self.min_ns = @min(self.min_ns, elapsed_ns); self.max_ns = @max(self.max_ns, elapsed_ns); self.total_ns += elapsed_ns; self.count += 1; + const value: f64 = @floatFromInt(elapsed_ns); + const delta = value - self.mean_ns; + self.mean_ns += delta / @as(f64, @floatFromInt(self.count)); + self.m2_ns += delta * (value - self.mean_ns); } pub fn avg(self: *const BenchStats) u64 { if (self.count == 0) return 0; return self.total_ns / self.count; } + + pub fn standardDeviation(self: *const BenchStats) ?f64 { + if (self.count < 2) return null; + return @sqrt(self.m2_ns / @as(f64, @floatFromInt(self.count - 1))); + } + + pub fn relativeMarginOfError95(self: *const BenchStats) ?f64 { + const stddev = self.standardDeviation() orelse return null; + if (self.mean_ns == 0) return null; + const degrees_of_freedom = self.count - 1; + const critical_values = [_]f64{ + 0, 12.706, 4.303, 3.182, 2.776, 2.571, 2.447, 2.365, 2.306, 2.262, 2.228, + 2.201, 2.179, 2.160, 2.145, 2.131, 2.120, 2.110, 2.101, 2.093, 2.086, 2.080, + 2.074, 2.069, 2.064, 2.060, 2.056, 2.052, 2.048, 2.045, 2.042, + }; + const critical = if (degrees_of_freedom < critical_values.len) critical_values[degrees_of_freedom] else 1.96; + const standard_error = stddev / @sqrt(@as(f64, @floatFromInt(self.count))); + return critical * standard_error / self.mean_ns * 100; + } }; /// Helper for running benchmark iterations with timing @@ -83,6 +110,8 @@ pub const BenchRunner = struct { .max_ns = stats.max_ns, .total_ns = stats.total_ns, .iterations = stats.count, + .stddev_ns = stats.standardDeviation(), + .rme_95 = stats.relativeMarginOfError95(), .mem_stats = mem_stats, }); } @@ -215,6 +244,7 @@ pub fn printResults(writer: anytype, results: []const BenchResult) !void { var min_col_width: usize = 3; // minimum for "Min" var avg_col_width: usize = 3; // minimum for "Avg" var max_col_width: usize = 3; // minimum for "Max" + var rme_col_width: usize = 6; // minimum for "RME95%" // Create a map to store column widths for each memory stat var mem_col_widths: std.ArrayListUnmanaged(usize) = .{}; @@ -231,6 +261,12 @@ pub fn printResults(writer: anytype, results: []const BenchResult) !void { const min = formatDuration(result.min_ns); const avg = formatDuration(result.avg_ns); const max = formatDuration(result.max_ns); + var rme_buf: [32]u8 = undefined; + const rme_str = if (result.rme_95) |rme| + std.fmt.bufPrint(&rme_buf, "{d:.2}%", .{rme}) catch unreachable + else + "-"; + if (rme_str.len > rme_col_width) rme_col_width = rme_str.len; var min_buf: [32]u8 = undefined; const min_str = std.fmt.bufPrint(&min_buf, "{d:.2}{s}", .{ min.value, min.unit }) catch unreachable; @@ -264,7 +300,7 @@ pub fn printResults(writer: anytype, results: []const BenchResult) !void { } // Print header - var total_width = max_name_len + 3 + min_col_width + 3 + avg_col_width + 3 + max_col_width; + var total_width = max_name_len + 3 + min_col_width + 3 + avg_col_width + 3 + max_col_width + 3 + rme_col_width; for (mem_col_widths.items) |width| { total_width += 3 + width; } @@ -291,6 +327,11 @@ pub fn printResults(writer: anytype, results: []const BenchResult) !void { try writer.writeAll("\x1b[36m"); try writer.writeAll("Max"); try writer.splatByteAll(' ', max_col_width - 3); + try writer.writeAll("\x1b[0m\x1b[2m | \x1b[0m"); + + try writer.writeAll("\x1b[36m"); + try writer.writeAll("RME95%"); + try writer.splatByteAll(' ', rme_col_width - 6); try writer.writeAll("\x1b[0m"); // Dynamic memory stat headers @@ -315,6 +356,8 @@ pub fn printResults(writer: anytype, results: []const BenchResult) !void { const min = formatDuration(result.min_ns); const avg = formatDuration(result.avg_ns); const max = formatDuration(result.max_ns); + var rme_buf: [32]u8 = undefined; + const rme_str = if (result.rme_95) |rme| try std.fmt.bufPrint(&rme_buf, "{d:.2}%", .{rme}) else "-"; // Format duration strings var min_buf: [32]u8 = undefined; @@ -370,6 +413,15 @@ pub fn printResults(writer: anytype, results: []const BenchResult) !void { try writer.writeAll(max_str); try writer.writeAll("\x1b[0m"); + try writer.writeAll("\x1b[2m | \x1b[0m"); + if (row_idx % 2 == 1) { + try writer.writeAll("\x1b[48;5;234m"); + } + if (rme_str.len < rme_col_width) { + try writer.splatByteAll(' ', rme_col_width - rme_str.len); + } + try writer.writeAll(rme_str); + // Dynamic memory stats columns for (mem_stat_names.items, 0..) |stat_name, i| { try writer.writeAll("\x1b[2m | \x1b[0m"); diff --git a/packages/core/src/zig/bench.zig b/packages/core/src/zig/bench.zig index d2ec8bb2ef..fb8d2be563 100644 --- a/packages/core/src/zig/bench.zig +++ b/packages/core/src/zig/bench.zig @@ -56,6 +56,11 @@ const buffer_draw_box_bench = @import("bench/buffer-draw-box_bench.zig"); const utf8_bench = @import("bench/utf8_bench.zig"); const text_chunk_graphemes_bench = @import("bench/text-chunk-graphemes_bench.zig"); const editor_view_bench = @import("bench/editor-view_bench.zig"); +const terminal_image_bench = @import("bench/terminal-image_bench.zig"); +const renderer_image_bench = @import("bench/renderer-image_bench.zig"); +const buffer_cell_drawing_bench = @import("bench/buffer-cell-drawing_bench.zig"); +const renderer_overhead_bench = @import("bench/renderer-overhead_bench.zig"); +const buffer_image_overlap_bench = @import("bench/buffer-image-overlap_bench.zig"); const BenchModule = struct { name: []const u8, @@ -107,6 +112,11 @@ pub fn main() !void { .{ .name = utf8_bench.benchName, .run = utf8_bench.run }, .{ .name = text_chunk_graphemes_bench.benchName, .run = text_chunk_graphemes_bench.run }, .{ .name = editor_view_bench.benchName, .run = editor_view_bench.run }, + .{ .name = terminal_image_bench.benchName, .run = terminal_image_bench.run }, + .{ .name = renderer_image_bench.benchName, .run = renderer_image_bench.run }, + .{ .name = buffer_cell_drawing_bench.benchName, .run = buffer_cell_drawing_bench.run }, + .{ .name = renderer_overhead_bench.benchName, .run = renderer_overhead_bench.run }, + .{ .name = buffer_image_overlap_bench.benchName, .run = buffer_image_overlap_bench.run }, }; const args = try std.process.argsAlloc(allocator); diff --git a/packages/core/src/zig/bench/buffer-cell-drawing_bench.zig b/packages/core/src/zig/bench/buffer-cell-drawing_bench.zig new file mode 100644 index 0000000000..85b556df91 --- /dev/null +++ b/packages/core/src/zig/bench/buffer-cell-drawing_bench.zig @@ -0,0 +1,196 @@ +const std = @import("std"); +const ansi = @import("../ansi.zig"); +const bench_utils = @import("../bench-utils.zig"); +const buffer = @import("../buffer.zig"); +const gp = @import("../grapheme.zig"); +const link = @import("../link.zig"); + +pub const benchName = "Buffer Cell Drawing"; + +const WIDTH: u32 = 200; +const HEIGHT: u32 = 50; +const SAMPLES: usize = 100; +const WARMUP_SAMPLES: usize = 10; +const BATCH_SIZE: usize = 10; +const BOX_CHARS = [_]u32{ '┌', '┐', '└', '┘', '─', '│', '┬', '┴', '├', '┤', '┼' }; + +const Scenario = enum { + transparent_char, + translucent_char, + transparent_text, + opaque_text, + transparent_boxes, + transparent_borders, + opaque_boxes, + translucent_boxes, + half_clipped_boxes, +}; + +fn runWorkload(target: *buffer.OptimizedBuffer, scenario: Scenario, text: []const u8) !void { + const transparent = ansi.rgbColor(10, 20, 30, 0); + const translucent = ansi.rgbColor(10, 20, 30, 128); + const opaque_color = ansi.rgbColor(10, 20, 30, 255); + switch (scenario) { + .transparent_char, .translucent_char => { + const color = if (scenario == .transparent_char) transparent else translucent; + const passes: usize = if (scenario == .transparent_char) 100 else 1; + for (0..passes) |_| { + var y: u32 = 0; + while (y < HEIGHT) : (y += 1) { + var x: u32 = 0; + while (x < WIDTH) : (x += 1) { + if (scenario == .transparent_char) std.mem.doNotOptimizeAway(target); + target.drawChar('X', x, y, color, color, 0); + } + } + } + }, + .transparent_text => { + for (0..1000) |_| { + var y: u32 = 0; + while (y < HEIGHT) : (y += 1) { + std.mem.doNotOptimizeAway(target); + try target.drawText(text, 0, y, transparent, transparent, 0); + } + } + }, + .opaque_text => { + var y: u32 = 0; + while (y < HEIGHT) : (y += 1) try target.drawText(text, 0, y, opaque_color, opaque_color, 0); + }, + .transparent_boxes, .transparent_borders, .opaque_boxes, .translucent_boxes, .half_clipped_boxes => { + const fully_transparent = scenario == .transparent_boxes; + const box_count: usize = if (fully_transparent) 100_000 else 1000; + const border_color = if (fully_transparent) transparent else opaque_color; + const background_color = if (fully_transparent or scenario == .transparent_borders) + transparent + else if (scenario == .translucent_boxes) + translucent + else + opaque_color; + for (0..box_count) |index| { + if (fully_transparent) std.mem.doNotOptimizeAway(target); + try target.drawBox( + @intCast(index % WIDTH), + if (scenario == .half_clipped_boxes) -10 else 0, + 40, + 20, + &BOX_CHARS, + .{ .top = true, .right = true, .bottom = true, .left = true }, + border_color, + background_color, + border_color, + scenario != .transparent_borders and !fully_transparent, + null, + 0, + null, + 0, + ); + } + }, + } +} + +fn runScenario(allocator: std.mem.Allocator, pool: *gp.GraphemePool, scenario: Scenario) !bench_utils.BenchStats { + var link_pool = link.LinkPool.init(allocator); + defer link_pool.deinit(); + const target = try buffer.OptimizedBuffer.init(allocator, WIDTH, HEIGHT, .{ .pool = pool, .link_pool = &link_pool }); + defer target.deinit(); + const text = "X" ** WIDTH; + + var stats: bench_utils.BenchStats = .{}; + for (0..WARMUP_SAMPLES + SAMPLES) |sample| { + var elapsed: u64 = 0; + for (0..BATCH_SIZE) |_| { + target.clear(ansi.rgbColor(0, 0, 0, 255), null); + var timer = try std.time.Timer.start(); + try runWorkload(target, scenario, text); + elapsed += timer.read(); + } + if (sample >= WARMUP_SAMPLES) stats.record(elapsed / BATCH_SIZE); + } + return stats; +} + +fn runFrameBufferScenario(allocator: std.mem.Allocator, pool: *gp.GraphemePool) !bench_utils.BenchStats { + var link_pool = link.LinkPool.init(allocator); + defer link_pool.deinit(); + const source = try buffer.OptimizedBuffer.init(allocator, WIDTH, HEIGHT, .{ .pool = pool, .link_pool = &link_pool }); + defer source.deinit(); + const target = try buffer.OptimizedBuffer.init(allocator, WIDTH, HEIGHT, .{ .pool = pool, .link_pool = &link_pool }); + defer target.deinit(); + var y: u32 = 0; + while (y < HEIGHT) : (y += 1) { + var x: u32 = 0; + while (x < WIDTH) : (x += 1) source.setRaw(x, y, .{ + .char = 'A', + .fg = ansi.rgbColor(200, 200, 200, 255), + .bg = ansi.rgbColor(20, 20, 40, 255), + .attributes = 0, + }); + } + + var stats: bench_utils.BenchStats = .{}; + for (0..WARMUP_SAMPLES + SAMPLES) |sample| { + var elapsed: u64 = 0; + for (0..BATCH_SIZE) |_| { + var timer = try std.time.Timer.start(); + target.drawFrameBuffer(0, 0, source, null, null, null, null); + elapsed += timer.read(); + target.clear(ansi.rgbColor(0, 0, 0, 255), null); + } + if (sample >= WARMUP_SAMPLES) stats.record(elapsed / BATCH_SIZE); + } + return stats; +} + +pub fn run(allocator: std.mem.Allocator, show_mem: bool, bench_filter: ?[]const u8) ![]bench_utils.BenchResult { + _ = show_mem; + const pool = gp.initGlobalPool(allocator); + defer gp.deinitGlobalPool(); + defer link.deinitGlobalLinkPool(); + + const scenarios = [_]struct { name: []const u8, kind: Scenario }{ + .{ .name = "1m transparent drawChar no images", .kind = .transparent_char }, + .{ .name = "10k translucent drawChar no images", .kind = .translucent_char }, + .{ .name = "50k transparent drawText calls no images", .kind = .transparent_text }, + .{ .name = "10k opaque drawText cells no images", .kind = .opaque_text }, + .{ .name = "100k fully transparent boxes no images", .kind = .transparent_boxes }, + .{ .name = "1k transparent borders no images", .kind = .transparent_borders }, + .{ .name = "1k opaque filled boxes no images", .kind = .opaque_boxes }, + .{ .name = "1k translucent filled boxes no images", .kind = .translucent_boxes }, + .{ .name = "1k half-clipped filled boxes no images", .kind = .half_clipped_boxes }, + }; + var results: std.ArrayListUnmanaged(bench_utils.BenchResult) = .{}; + for (scenarios) |scenario| { + if (!bench_utils.matchesBenchFilter(scenario.name, bench_filter)) continue; + const stats = try runScenario(allocator, pool, scenario.kind); + try results.append(allocator, .{ + .name = scenario.name, + .min_ns = stats.min_ns, + .avg_ns = stats.avg(), + .max_ns = stats.max_ns, + .total_ns = stats.total_ns, + .iterations = stats.count, + .stddev_ns = stats.standardDeviation(), + .rme_95 = stats.relativeMarginOfError95(), + .mem_stats = null, + }); + } + const framebuffer_name = "drawFrameBuffer 10k cells no images"; + if (bench_utils.matchesBenchFilter(framebuffer_name, bench_filter)) { + const stats = try runFrameBufferScenario(allocator, pool); + try results.append(allocator, .{ + .name = framebuffer_name, + .min_ns = stats.min_ns, + .avg_ns = stats.avg(), + .max_ns = stats.max_ns, + .total_ns = stats.total_ns, + .iterations = stats.count, + .stddev_ns = stats.standardDeviation(), + .rme_95 = stats.relativeMarginOfError95(), + .mem_stats = null, + }); + } + return results.toOwnedSlice(allocator); +} diff --git a/packages/core/src/zig/bench/buffer-color-blending_bench.zig b/packages/core/src/zig/bench/buffer-color-blending_bench.zig index 3504dae864..a772daa218 100644 --- a/packages/core/src/zig/bench/buffer-color-blending_bench.zig +++ b/packages/core/src/zig/bench/buffer-color-blending_bench.zig @@ -5,6 +5,7 @@ const buffer = @import("../buffer.zig"); const text_buffer = @import("../text-buffer.zig"); const text_buffer_view = @import("../text-buffer-view.zig"); const gp = @import("../grapheme.zig"); +const image = @import("../image.zig"); const link = @import("../link.zig"); const OptimizedBuffer = buffer.OptimizedBuffer; @@ -328,6 +329,46 @@ fn runTranslucentTextBuffers( return results.toOwnedSlice(allocator); } +fn runTranslucentFillOverImageMarkers( + allocator: std.mem.Allocator, + pool: *gp.GraphemePool, + iterations: usize, + bench_filter: ?[]const u8, +) ![]BenchResult { + const name = "translucent fillRect over image markers"; + if (!bench_utils.matchesBenchFilter(name, bench_filter)) return allocator.alloc(BenchResult, 0); + + const source = try image.createFromRgba(allocator, &[_]u8{ 10, 20, 30, 255 }, 1, 1, 4); + defer source.deinit(); + const buf = try OptimizedBuffer.init(allocator, BUFFER_WIDTH, BUFFER_HEIGHT, .{ .pool = pool }); + defer buf.deinit(); + const overlay = rgba(0.2, 0.3, 0.8, 0.5); + + var stats: BenchStats = .{}; + for (0..iterations) |_| { + buf.clear(CLEAR_BG, null); + _ = try buf.drawImage(source, 1, 0, 0, BUFFER_WIDTH, BUFFER_HEIGHT, 0, 0, 0, 0, 1, 1, .auto); + + var timer = try std.time.Timer.start(); + buf.fillRect(0, 0, BUFFER_WIDTH, BUFFER_HEIGHT, overlay); + stats.record(timer.read()); + } + + const results = try allocator.alloc(BenchResult, 1); + results[0] = .{ + .name = name, + .min_ns = stats.min_ns, + .avg_ns = stats.avg(), + .max_ns = stats.max_ns, + .total_ns = stats.total_ns, + .iterations = iterations, + .stddev_ns = stats.standardDeviation(), + .rme_95 = stats.relativeMarginOfError95(), + .mem_stats = null, + }; + return results; +} + pub fn run( allocator: std.mem.Allocator, show_mem: bool, @@ -346,5 +387,8 @@ pub fn run( const text_buffers_results = try runTranslucentTextBuffers(allocator, pool, show_mem, iterations, bench_filter); try all_results.appendSlice(allocator, text_buffers_results); + const image_marker_results = try runTranslucentFillOverImageMarkers(allocator, pool, iterations, bench_filter); + try all_results.appendSlice(allocator, image_marker_results); + return all_results.toOwnedSlice(allocator); } diff --git a/packages/core/src/zig/bench/buffer-image-overlap_bench.zig b/packages/core/src/zig/bench/buffer-image-overlap_bench.zig new file mode 100644 index 0000000000..a873dc7bef --- /dev/null +++ b/packages/core/src/zig/bench/buffer-image-overlap_bench.zig @@ -0,0 +1,181 @@ +const std = @import("std"); +const ansi = @import("../ansi.zig"); +const bench_utils = @import("../bench-utils.zig"); +const buffer = @import("../buffer.zig"); +const gp = @import("../grapheme.zig"); +const image = @import("../image.zig"); +const link = @import("../link.zig"); + +pub const benchName = "Buffer Image Overlap"; + +const WIDTH: u32 = 200; +const HEIGHT: u32 = 50; +const SAMPLES: usize = 100; +const WARMUP_SAMPLES: usize = 10; +const BOX_COUNT: usize = 1000; +const BOX_CHARS = [_]u32{ '┌', '┐', '└', '┘', '─', '│', '┬', '┴', '├', '┤', '┼' }; + +const Scenario = enum { + transparent_char_disjoint, + transparent_char_overlap, + transparent_wide_text_overlap, + transparent_boxes_disjoint, + transparent_boxes_overlap, + transparent_borders_disjoint, + transparent_fill_sparse, + transparent_fill_dense, +}; + +fn addPlacements(target: *buffer.OptimizedBuffer, source: *image.Image, scenario: Scenario) !void { + switch (scenario) { + .transparent_char_disjoint => { + _ = try target.drawImage(source, 1, 0, 0, WIDTH, 1, 0, 0, 0, 0, 1, 1, .auto); + }, + .transparent_char_overlap, .transparent_wide_text_overlap, .transparent_fill_dense => { + _ = try target.drawImage(source, 1, 0, 0, WIDTH, HEIGHT, 0, 0, 0, 0, 1, 1, .auto); + }, + .transparent_fill_sparse => { + _ = try target.drawImage(source, 1, 95, 23, 10, 5, 0, 0, 0, 0, 1, 1, .auto); + }, + .transparent_boxes_disjoint, .transparent_borders_disjoint => { + for (0..16) |index| { + _ = try target.drawImage(source, @intCast(index + 1), @intCast(index * 12), 49, 1, 1, 0, 0, 0, 0, 1, 1, .auto); + } + }, + .transparent_boxes_overlap => { + for (0..16) |index| { + _ = try target.drawImage(source, @intCast(index + 1), @intCast(index * 12), 0, 1, 1, 0, 0, 0, 0, 1, 1, .auto); + } + }, + } +} + +fn runWorkload(target: *buffer.OptimizedBuffer, scenario: Scenario) !void { + const transparent = ansi.rgbColor(10, 20, 30, 0); + switch (scenario) { + .transparent_char_disjoint => { + var y: u32 = 1; + while (y < HEIGHT) : (y += 1) { + var x: u32 = 0; + while (x < WIDTH) : (x += 1) target.drawChar('X', x, y, transparent, transparent, 0); + } + }, + .transparent_char_overlap => { + var y: u32 = 0; + while (y < HEIGHT) : (y += 1) { + var x: u32 = 0; + while (x < WIDTH) : (x += 1) target.drawChar('X', x, y, transparent, transparent, 0); + } + }, + .transparent_wide_text_overlap => { + const text = "界" ** (WIDTH / 2); + var y: u32 = 0; + while (y < HEIGHT) : (y += 1) try target.drawText(text, 0, y, transparent, transparent, 0); + }, + .transparent_boxes_disjoint, .transparent_boxes_overlap => { + for (0..BOX_COUNT) |index| { + try target.drawBox( + @intCast(index % WIDTH), + 0, + 40, + 20, + &BOX_CHARS, + .{ .top = true, .right = true, .bottom = true, .left = true }, + transparent, + transparent, + transparent, + false, + null, + 0, + null, + 0, + ); + } + }, + .transparent_borders_disjoint => { + const opaque_color = ansi.rgbColor(40, 50, 60, 255); + for (0..BOX_COUNT) |index| { + try target.drawBox( + @intCast(index % WIDTH), + 0, + 40, + 20, + &BOX_CHARS, + .{ .top = true, .right = true, .bottom = true, .left = true }, + opaque_color, + transparent, + opaque_color, + false, + null, + 0, + null, + 0, + ); + } + }, + .transparent_fill_sparse, .transparent_fill_dense => { + const iterations: usize = if (scenario == .transparent_fill_sparse) 1000 else 100; + for (0..iterations) |_| target.fillRect(0, 0, WIDTH, HEIGHT, transparent); + }, + } +} + +fn runScenario( + allocator: std.mem.Allocator, + pool: *gp.GraphemePool, + source: *image.Image, + scenario: Scenario, +) !bench_utils.BenchStats { + var link_pool = link.LinkPool.init(allocator); + defer link_pool.deinit(); + const target = try buffer.OptimizedBuffer.init(allocator, WIDTH, HEIGHT, .{ .pool = pool, .link_pool = &link_pool }); + defer target.deinit(); + + var stats: bench_utils.BenchStats = .{}; + for (0..WARMUP_SAMPLES + SAMPLES) |sample| { + target.clear(ansi.rgbColor(0, 0, 0, 255), null); + try addPlacements(target, source, scenario); + var timer = try std.time.Timer.start(); + try runWorkload(target, scenario); + const elapsed = timer.read(); + if (sample >= WARMUP_SAMPLES) stats.record(elapsed); + } + return stats; +} + +pub fn run(allocator: std.mem.Allocator, show_mem: bool, bench_filter: ?[]const u8) ![]bench_utils.BenchResult { + _ = show_mem; + const pool = gp.initGlobalPool(allocator); + defer gp.deinitGlobalPool(); + defer link.deinitGlobalLinkPool(); + const source = try image.createFromRgba(allocator, &[_]u8{ 10, 20, 30, 255 }, 1, 1, 4); + defer source.deinit(); + + const scenarios = [_]struct { name: []const u8, kind: Scenario }{ + .{ .name = "9.8k transparent drawChar placement disjoint", .kind = .transparent_char_disjoint }, + .{ .name = "10k transparent drawChar over image markers", .kind = .transparent_char_overlap }, + .{ .name = "5k transparent wide drawText over image markers", .kind = .transparent_wide_text_overlap }, + .{ .name = "1k transparent boxes scan 16 disjoint placements", .kind = .transparent_boxes_disjoint }, + .{ .name = "1k transparent boxes scan 16 overlapping placements", .kind = .transparent_boxes_overlap }, + .{ .name = "1k transparent borders scan 16 disjoint placements", .kind = .transparent_borders_disjoint }, + .{ .name = "1k transparent full fills with sparse placement", .kind = .transparent_fill_sparse }, + .{ .name = "100 transparent full fills with dense placement", .kind = .transparent_fill_dense }, + }; + var results: std.ArrayListUnmanaged(bench_utils.BenchResult) = .{}; + for (scenarios) |scenario| { + if (!bench_utils.matchesBenchFilter(scenario.name, bench_filter)) continue; + const stats = try runScenario(allocator, pool, source, scenario.kind); + try results.append(allocator, .{ + .name = scenario.name, + .min_ns = stats.min_ns, + .avg_ns = stats.avg(), + .max_ns = stats.max_ns, + .total_ns = stats.total_ns, + .iterations = stats.count, + .stddev_ns = stats.standardDeviation(), + .rme_95 = stats.relativeMarginOfError95(), + .mem_stats = null, + }); + } + return results.toOwnedSlice(allocator); +} diff --git a/packages/core/src/zig/bench/renderer-image_bench.zig b/packages/core/src/zig/bench/renderer-image_bench.zig new file mode 100644 index 0000000000..663a9f0e97 --- /dev/null +++ b/packages/core/src/zig/bench/renderer-image_bench.zig @@ -0,0 +1,457 @@ +const std = @import("std"); +const bench_utils = @import("../bench-utils.zig"); +const renderer = @import("../renderer.zig"); +const buffer = @import("../buffer.zig"); +const image = @import("../image.zig"); +const gp = @import("../grapheme.zig"); +const link = @import("../link.zig"); +const handles = @import("../handles.zig"); +const test_renderer_mod = @import("../tests/test-renderer.zig"); + +pub const benchName = "Renderer Image"; + +const TERM_WIDTH = 200; +const TERM_HEIGHT = 50; +const IMAGE_VARIANTS = 24; +const FRAME_ITERATIONS = 96; + +const Protocol = enum { kitty, sixel, blocks }; + +fn makeFrameImage(allocator: std.mem.Allocator, width: u32, height: u32, seed: u8) !*image.Image { + const pixels = try allocator.alloc(u8, @as(usize, width) * height * 4); + defer allocator.free(pixels); + for (0..height) |y| { + for (0..width) |x| { + const offset = (y * width + x) * 4; + pixels[offset] = @truncate(x + seed); + pixels[offset + 1] = @truncate(y +% seed *% 3); + pixels[offset + 2] = @truncate(x + y + seed *% 7); + pixels[offset + 3] = 255; + } + } + return image.createFromRgba(allocator, pixels, width, height, width * 4); +} + +fn drawTextBackdrop(target: *buffer.OptimizedBuffer) void { + var y: u32 = 0; + while (y < TERM_HEIGHT) : (y += 1) { + var x: u32 = 0; + while (x < TERM_WIDTH) : (x += 1) { + target.setRaw(x, y, .{ + .char = 'A' + (x + y) % 26, + .fg = .{ 200, 200, 200, 255 }, + .bg = .{ 20, 20, 40, 255 }, + .attributes = 0, + }); + } + } +} + +const FrameCost = struct { + stats: bench_utils.BenchStats = .{}, + total_bytes: u64 = 0, + frames: u64 = 0, + + fn bytesPerFrame(self: *const FrameCost) u64 { + if (self.frames == 0) return 0; + return self.total_bytes / self.frames; + } +}; + +fn runPlacementScenario( + allocator: std.mem.Allocator, + pool: *gp.GraphemePool, + protocol: Protocol, + image_width: u32, + image_height: u32, + animate: bool, + text_change: bool, +) !FrameCost { + var test_renderer = try test_renderer_mod.TestRenderer.create(allocator, TERM_WIDTH, TERM_HEIGHT, pool); + defer test_renderer.deinit(); + switch (protocol) { + .kitty => test_renderer.renderer.terminal.caps.kitty_graphics = true, + .sixel => test_renderer.renderer.terminal.caps.sixel = true, + .blocks => {}, + } + + var images: [IMAGE_VARIANTS]*image.Image = undefined; + var image_handles: [IMAGE_VARIANTS]u32 = undefined; + for (0..IMAGE_VARIANTS) |index| { + images[index] = try makeFrameImage(allocator, image_width, image_height, @truncate(index * 5)); + image_handles[index] = try handles.insert(.image, @ptrCast(images[index])); + } + defer for (image_handles) |handle| { + const token = handles.beginDestroy(handle, .image, image.Image).?; + token.ptr.deinit(); + handles.finishDestroy(token.handle); + }; + + var cost = FrameCost{}; + if (protocol == .sixel and animate) { + for (images, image_handles) |value, handle| { + const next = test_renderer.renderer.getNextBuffer(); + drawTextBackdrop(next); + _ = try next.drawImage(value, handle, 5, 5, 40, 20, 320, 200, 0, 0, image_width, image_height, .auto); + _ = test_renderer.renderer.render(true); + } + } + var frame: usize = 0; + while (frame < FRAME_ITERATIONS) : (frame += 1) { + const index = if (animate) frame % IMAGE_VARIANTS else 0; + const next = test_renderer.renderer.getNextBuffer(); + drawTextBackdrop(next); + if (text_change) { + next.setRaw(0, 0, .{ + .char = '0' + @as(u32, @intCast(frame % 10)), + .fg = .{ 255, 255, 0, 255 }, + .bg = .{ 0, 0, 0, 255 }, + .attributes = 0, + }); + } + _ = try next.drawImage(images[index], image_handles[index], 5, 5, 40, 20, 320, 200, 0, 0, image_width, image_height, .auto); + + test_renderer.memory.bytes.clearRetainingCapacity(); + test_renderer.memory.last_write_start = 0; + test_renderer.memory.last_write_len = 0; + var timer = try std.time.Timer.start(); + _ = test_renderer.renderer.render(false); + cost.stats.record(timer.read()); + // Skip the first frame: it pays the initial full paint for every scenario. + if (frame == 0) { + cost.stats = .{}; + continue; + } + cost.total_bytes += test_renderer.memory.bytes.items.len; + cost.frames += 1; + } + return cost; +} + +fn runLargeStillTransmit(allocator: std.mem.Allocator, pool: *gp.GraphemePool) !FrameCost { + var test_renderer = try test_renderer_mod.TestRenderer.create(allocator, TERM_WIDTH, TERM_HEIGHT, pool); + defer test_renderer.deinit(); + test_renderer.renderer.terminal.caps.kitty_graphics = true; + + const first = try makeFrameImage(allocator, 1600, 1200, 11); + const first_handle = handles.insert(.image, @ptrCast(first)) catch |err| { + first.deinit(); + return err; + }; + defer { + const token = handles.beginDestroy(first_handle, .image, image.Image).?; + token.ptr.deinit(); + handles.finishDestroy(token.handle); + } + const second = try makeFrameImage(allocator, 1600, 1200, 37); + const second_handle = handles.insert(.image, @ptrCast(second)) catch |err| { + second.deinit(); + return err; + }; + defer { + const token = handles.beginDestroy(second_handle, .image, image.Image).?; + token.ptr.deinit(); + handles.finishDestroy(token.handle); + } + const stills = [_]*image.Image{ first, second }; + const still_handles = [_]u32{ first_handle, second_handle }; + + var cost = FrameCost{}; + // Alternate content so every iteration exercises crop, downscale, and transmission. + var frame: usize = 0; + while (frame < 8) : (frame += 1) { + const index = frame % stills.len; + const next = test_renderer.renderer.getNextBuffer(); + _ = try next.drawImage(stills[index], still_handles[index], 5, 5, 40, 20, 320, 200, 0, 0, 1600, 1200, .auto); + test_renderer.memory.bytes.clearRetainingCapacity(); + test_renderer.memory.last_write_start = 0; + test_renderer.memory.last_write_len = 0; + var timer = try std.time.Timer.start(); + _ = test_renderer.renderer.render(false); + cost.stats.record(timer.read()); + // Match the other renderer scenarios by excluding initial full paint. + if (frame == 0) { + cost.stats = .{}; + continue; + } + cost.total_bytes += test_renderer.memory.bytes.items.len; + cost.frames += 1; + } + return cost; +} + +fn runDrawFrameBuffer(allocator: std.mem.Allocator, with_image: bool) !FrameCost { + var pool = gp.GraphemePool.init(allocator); + defer pool.deinit(); + var link_pool = link.LinkPool.init(allocator); + defer link_pool.deinit(); + const source = try buffer.OptimizedBuffer.init(allocator, TERM_WIDTH, TERM_HEIGHT, .{ .pool = &pool, .link_pool = &link_pool }); + defer source.deinit(); + const target = try buffer.OptimizedBuffer.init(allocator, TERM_WIDTH, TERM_HEIGHT, .{ .pool = &pool, .link_pool = &link_pool }); + defer target.deinit(); + drawTextBackdrop(source); + + const dot = if (with_image) try makeFrameImage(allocator, 8, 8, 3) else null; + defer if (dot) |value| value.deinit(); + if (dot) |value| _ = try source.drawImage(value, 51, 2, 2, 4, 2, 8, 8, 0, 0, 8, 8, .auto); + + var cost = FrameCost{}; + var iteration: usize = 0; + while (iteration < 400) : (iteration += 1) { + var timer = try std.time.Timer.start(); + target.drawFrameBuffer(0, 0, source, null, null, null, null); + cost.stats.record(timer.read()); + cost.frames += 1; + target.clear(.{ 0, 0, 0, 255 }, null); + } + return cost; +} + +fn drawStaticKittyPlacements(target: *buffer.OptimizedBuffer, value: *image.Image, image_handle: u32, count: usize) !void { + for (0..count) |index| { + const x: i32 = @intCast(index % TERM_WIDTH); + const y: i32 = @intCast(index / TERM_WIDTH); + _ = try target.drawImage(value, image_handle, x, y, 1, 1, 1, 1, 0, 0, 1, 1, .kitty); + } +} + +fn runStaticKittyPlacementCount( + allocator: std.mem.Allocator, + pool: *gp.GraphemePool, + count: usize, +) !FrameCost { + var test_renderer = try test_renderer_mod.TestRenderer.create(allocator, TERM_WIDTH, TERM_HEIGHT, pool); + defer test_renderer.deinit(); + test_renderer.renderer.terminal.caps.kitty_graphics = true; + + const value = try makeFrameImage(allocator, 1, 1, 7); + const image_handle = handles.insert(.image, @ptrCast(value)) catch |err| { + value.deinit(); + return err; + }; + defer { + const token = handles.beginDestroy(image_handle, .image, image.Image).?; + token.ptr.deinit(); + handles.finishDestroy(token.handle); + } + + try drawStaticKittyPlacements(test_renderer.renderer.getNextBuffer(), value, image_handle, count); + if (test_renderer.renderer.render(true) != .rendered) return error.RenderFailed; + + const iterations: usize = if (count <= 128) 100 else if (count <= 512) 50 else if (count <= 2048) 20 else 10; + var cost = FrameCost{}; + for (0..iterations) |_| { + try drawStaticKittyPlacements(test_renderer.renderer.getNextBuffer(), value, image_handle, count); + test_renderer.memory.bytes.clearRetainingCapacity(); + test_renderer.memory.last_write_start = 0; + test_renderer.memory.last_write_len = 0; + var timer = try std.time.Timer.start(); + if (test_renderer.renderer.render(false) == .failed) return error.RenderFailed; + cost.stats.record(timer.read()); + cost.total_bytes += test_renderer.memory.bytes.items.len; + cost.frames += 1; + } + return cost; +} + +fn drawOverlappingSixelPlacements( + target: *buffer.OptimizedBuffer, + base: *image.Image, + base_handle: u32, + replacement: *image.Image, + replacement_handle: u32, + replace_first: bool, + count: usize, +) !void { + for (0..count) |index| { + const value = if (replace_first and index == 0) replacement else base; + const image_handle = if (replace_first and index == 0) replacement_handle else base_handle; + _ = try target.drawImage(value, image_handle, 0, 0, 1, 1, 1, 1, 0, 0, 1, 1, .sixel); + } +} + +fn runDirtySixelOverlapCount( + allocator: std.mem.Allocator, + pool: *gp.GraphemePool, + count: usize, +) !FrameCost { + var test_renderer = try test_renderer_mod.TestRenderer.create(allocator, 1, 1, pool); + defer test_renderer.deinit(); + test_renderer.renderer.terminal.caps.sixel = true; + + const transparent = [_]u8{ 0, 0, 0, 0 }; + const base = try image.createFromRgba(allocator, &transparent, 1, 1, 4); + const base_handle = handles.insert(.image, @ptrCast(base)) catch |err| { + base.deinit(); + return err; + }; + defer { + const token = handles.beginDestroy(base_handle, .image, image.Image).?; + token.ptr.deinit(); + handles.finishDestroy(token.handle); + } + const replacement = try image.createFromRgba(allocator, &transparent, 1, 1, 4); + const replacement_handle = handles.insert(.image, @ptrCast(replacement)) catch |err| { + replacement.deinit(); + return err; + }; + defer { + const token = handles.beginDestroy(replacement_handle, .image, image.Image).?; + token.ptr.deinit(); + handles.finishDestroy(token.handle); + } + + try drawOverlappingSixelPlacements( + test_renderer.renderer.getNextBuffer(), + base, + base_handle, + replacement, + replacement_handle, + false, + count, + ); + if (test_renderer.renderer.render(true) != .rendered) return error.RenderFailed; + try drawOverlappingSixelPlacements( + test_renderer.renderer.getNextBuffer(), + base, + base_handle, + replacement, + replacement_handle, + true, + count, + ); + if (test_renderer.renderer.render(false) == .failed) return error.RenderFailed; + + const iterations: usize = if (count <= 128) 100 else if (count <= 512) 50 else if (count <= 2048) 20 else 10; + var cost = FrameCost{}; + for (0..iterations) |iteration| { + try drawOverlappingSixelPlacements( + test_renderer.renderer.getNextBuffer(), + base, + base_handle, + replacement, + replacement_handle, + iteration % 2 != 0, + count, + ); + test_renderer.memory.bytes.clearRetainingCapacity(); + test_renderer.memory.last_write_start = 0; + test_renderer.memory.last_write_len = 0; + var timer = try std.time.Timer.start(); + if (test_renderer.renderer.render(false) == .failed) return error.RenderFailed; + cost.stats.record(timer.read()); + cost.total_bytes += test_renderer.memory.bytes.items.len; + cost.frames += 1; + } + return cost; +} + +pub fn run(allocator: std.mem.Allocator, show_mem: bool, bench_filter: ?[]const u8) ![]bench_utils.BenchResult { + _ = show_mem; + const pool = gp.initGlobalPool(allocator); + defer gp.deinitGlobalPool(); + defer link.deinitGlobalLinkPool(); + + var results: std.ArrayListUnmanaged(bench_utils.BenchResult) = .{}; + + const Scenario = struct { + name: []const u8, + protocol: Protocol, + animate: bool, + text_change: bool, + }; + const scenarios = [_]Scenario{ + .{ .name = "kitty image replacements", .protocol = .kitty, .animate = true, .text_change = false }, + .{ .name = "kitty static image one text change", .protocol = .kitty, .animate = false, .text_change = true }, + .{ .name = "kitty static image no changes", .protocol = .kitty, .animate = false, .text_change = false }, + .{ .name = "sixel cached image replacements", .protocol = .sixel, .animate = true, .text_change = false }, + .{ .name = "sixel static image no changes", .protocol = .sixel, .animate = false, .text_change = false }, + .{ .name = "blocks image replacements", .protocol = .blocks, .animate = true, .text_change = false }, + }; + for (scenarios) |scenario| { + if (!bench_utils.matchesBenchFilter(scenario.name, bench_filter)) continue; + const cost = try runPlacementScenario(allocator, pool, scenario.protocol, 320, 200, scenario.animate, scenario.text_change); + try results.append(allocator, .{ + .name = try std.fmt.allocPrint(allocator, "{s} ({d} bytes/frame)", .{ scenario.name, cost.bytesPerFrame() }), + .min_ns = cost.stats.min_ns, + .avg_ns = cost.stats.avg(), + .max_ns = cost.stats.max_ns, + .total_ns = cost.stats.total_ns, + .iterations = cost.stats.count, + .stddev_ns = cost.stats.standardDeviation(), + .rme_95 = cost.stats.relativeMarginOfError95(), + .mem_stats = null, + }); + } + + if (bench_utils.matchesBenchFilter("kitty large still transmit", bench_filter)) { + const cost = try runLargeStillTransmit(allocator, pool); + try results.append(allocator, .{ + .name = try std.fmt.allocPrint(allocator, "kitty large still transmit ({d} bytes/frame)", .{cost.bytesPerFrame()}), + .min_ns = cost.stats.min_ns, + .avg_ns = cost.stats.avg(), + .max_ns = cost.stats.max_ns, + .total_ns = cost.stats.total_ns, + .iterations = cost.stats.count, + .stddev_ns = cost.stats.standardDeviation(), + .rme_95 = cost.stats.relativeMarginOfError95(), + .mem_stats = null, + }); + } + + const framebuffer_scenarios = [_]struct { name: []const u8, with_image: bool }{ + .{ .name = "drawFrameBuffer no images", .with_image = false }, + .{ .name = "drawFrameBuffer one image", .with_image = true }, + }; + for (framebuffer_scenarios) |scenario| { + if (!bench_utils.matchesBenchFilter(scenario.name, bench_filter)) continue; + const cost = try runDrawFrameBuffer(allocator, scenario.with_image); + try results.append(allocator, .{ + .name = scenario.name, + .min_ns = cost.stats.min_ns, + .avg_ns = cost.stats.avg(), + .max_ns = cost.stats.max_ns, + .total_ns = cost.stats.total_ns, + .iterations = cost.stats.count, + .stddev_ns = cost.stats.standardDeviation(), + .rme_95 = cost.stats.relativeMarginOfError95(), + .mem_stats = null, + }); + } + + for ([_]usize{ 8, 32, 128, 512, 2048, 4096 }) |count| { + const name = try std.fmt.allocPrint(allocator, "kitty static {d} placements", .{count}); + if (!bench_utils.matchesBenchFilter(name, bench_filter)) continue; + const cost = try runStaticKittyPlacementCount(allocator, pool, count); + try results.append(allocator, .{ + .name = try std.fmt.allocPrint(allocator, "{s} ({d} bytes/frame)", .{ name, cost.bytesPerFrame() }), + .min_ns = cost.stats.min_ns, + .avg_ns = cost.stats.avg(), + .max_ns = cost.stats.max_ns, + .total_ns = cost.stats.total_ns, + .iterations = cost.stats.count, + .stddev_ns = cost.stats.standardDeviation(), + .rme_95 = cost.stats.relativeMarginOfError95(), + .mem_stats = null, + }); + } + + for ([_]usize{ 8, 32, 128, 512, 2048, 4096 }) |count| { + const name = try std.fmt.allocPrint(allocator, "sixel dirty overlap {d} transparent placements", .{count}); + if (!bench_utils.matchesBenchFilter(name, bench_filter)) continue; + const cost = try runDirtySixelOverlapCount(allocator, pool, count); + try results.append(allocator, .{ + .name = try std.fmt.allocPrint(allocator, "{s} ({d} bytes/frame)", .{ name, cost.bytesPerFrame() }), + .min_ns = cost.stats.min_ns, + .avg_ns = cost.stats.avg(), + .max_ns = cost.stats.max_ns, + .total_ns = cost.stats.total_ns, + .iterations = cost.stats.count, + .stddev_ns = cost.stats.standardDeviation(), + .rme_95 = cost.stats.relativeMarginOfError95(), + .mem_stats = null, + }); + } + + return results.toOwnedSlice(allocator); +} diff --git a/packages/core/src/zig/bench/renderer-overhead_bench.zig b/packages/core/src/zig/bench/renderer-overhead_bench.zig new file mode 100644 index 0000000000..6c36efc5e7 --- /dev/null +++ b/packages/core/src/zig/bench/renderer-overhead_bench.zig @@ -0,0 +1,84 @@ +const std = @import("std"); +const bench_utils = @import("../bench-utils.zig"); +const gp = @import("../grapheme.zig"); +const link = @import("../link.zig"); +const test_renderer_mod = @import("../tests/test-renderer.zig"); + +pub const benchName = "Renderer Overhead"; + +const WIDTH: u32 = 200; +const HEIGHT: u32 = 50; +const SAMPLES: usize = 100; +const WARMUP_SAMPLES: usize = 10; +const BATCH_SIZE: usize = 10; + +const Scenario = enum { no_changes, one_change, full_change }; + +fn drawFrame(target: anytype, frame: usize, scenario: Scenario) void { + var y: u32 = 0; + while (y < HEIGHT) : (y += 1) { + var x: u32 = 0; + while (x < WIDTH) : (x += 1) { + const changed = scenario == .full_change or (scenario == .one_change and x == 0 and y == 0); + target.setRaw(x, y, .{ + .char = if (changed) 'A' + @as(u32, @intCast(frame % 2)) else 'A', + .fg = .{ 200, 200, 200, 255 }, + .bg = .{ 20, 20, 40, 255 }, + .attributes = 0, + }); + } + } +} + +fn runScenario(allocator: std.mem.Allocator, pool: *gp.GraphemePool, scenario: Scenario) !bench_utils.BenchStats { + var test_renderer = try test_renderer_mod.TestRenderer.create(allocator, WIDTH, HEIGHT, pool); + defer test_renderer.deinit(); + drawFrame(test_renderer.renderer.getNextBuffer(), 0, .full_change); + _ = test_renderer.renderer.render(true); + + var stats: bench_utils.BenchStats = .{}; + for (0..WARMUP_SAMPLES + SAMPLES) |sample| { + var elapsed: u64 = 0; + for (0..BATCH_SIZE) |batch| { + drawFrame(test_renderer.renderer.getNextBuffer(), sample * BATCH_SIZE + batch, scenario); + test_renderer.memory.bytes.clearRetainingCapacity(); + test_renderer.memory.last_write_start = 0; + test_renderer.memory.last_write_len = 0; + var timer = try std.time.Timer.start(); + _ = test_renderer.renderer.render(false); + elapsed += timer.read(); + } + if (sample >= WARMUP_SAMPLES) stats.record(elapsed / BATCH_SIZE); + } + return stats; +} + +pub fn run(allocator: std.mem.Allocator, show_mem: bool, bench_filter: ?[]const u8) ![]bench_utils.BenchResult { + _ = show_mem; + const pool = gp.initGlobalPool(allocator); + defer gp.deinitGlobalPool(); + defer link.deinitGlobalLinkPool(); + + const scenarios = [_]struct { name: []const u8, kind: Scenario }{ + .{ .name = "10k cells no changes no images", .kind = .no_changes }, + .{ .name = "10k cells one change no images", .kind = .one_change }, + .{ .name = "10k cells full change no images", .kind = .full_change }, + }; + var results: std.ArrayListUnmanaged(bench_utils.BenchResult) = .{}; + for (scenarios) |scenario| { + if (!bench_utils.matchesBenchFilter(scenario.name, bench_filter)) continue; + const stats = try runScenario(allocator, pool, scenario.kind); + try results.append(allocator, .{ + .name = scenario.name, + .min_ns = stats.min_ns, + .avg_ns = stats.avg(), + .max_ns = stats.max_ns, + .total_ns = stats.total_ns, + .iterations = stats.count, + .stddev_ns = stats.standardDeviation(), + .rme_95 = stats.relativeMarginOfError95(), + .mem_stats = null, + }); + } + return results.toOwnedSlice(allocator); +} diff --git a/packages/core/src/zig/bench/terminal-image_bench.zig b/packages/core/src/zig/bench/terminal-image_bench.zig new file mode 100644 index 0000000000..3bf5df91bd --- /dev/null +++ b/packages/core/src/zig/bench/terminal-image_bench.zig @@ -0,0 +1,670 @@ +const std = @import("std"); +const bench_utils = @import("../bench-utils.zig"); +const ansi = @import("../ansi.zig"); +const buffer = @import("../buffer.zig"); +const gp = @import("../grapheme.zig"); +const image = @import("../image.zig"); +const terminal_image = @import("../terminal-image.zig"); + +pub const benchName = "Terminal Image"; + +const Scenario = struct { + name: []const u8, + width: u32, + height: u32, + colors: usize = 255, + pattern: enum { flat, gradient, baseline, photo, noise, transparent }, +}; + +const scenarios = [_]Scenario{ + .{ .name = "Sixel 160x240 flat", .width = 160, .height = 240, .pattern = .flat }, + .{ .name = "Sixel 160x240 gradient", .width = 160, .height = 240, .pattern = .gradient }, + .{ .name = "Sixel 160x240 original baseline", .width = 160, .height = 240, .pattern = .baseline }, + .{ .name = "Sixel 160x240 original baseline 128 colors", .width = 160, .height = 240, .colors = 128, .pattern = .baseline }, + .{ .name = "Sixel 160x240 original baseline 64 colors", .width = 160, .height = 240, .colors = 64, .pattern = .baseline }, + .{ .name = "Sixel 160x240 photo-like", .width = 160, .height = 240, .pattern = .photo }, + .{ .name = "Sixel 160x240 noise", .width = 160, .height = 240, .pattern = .noise }, + .{ .name = "Sixel 160x240 transparent", .width = 160, .height = 240, .pattern = .transparent }, + .{ .name = "Sixel 320x480 photo-like", .width = 320, .height = 480, .pattern = .photo }, +}; + +fn writeScenario(allocator: std.mem.Allocator, writer: anytype, value: *const image.Image, colors: usize) !void { + if (colors == 255) return terminal_image.writeSixelPayload(allocator, writer, value); + var quantized = try terminal_image.quantizeSixel(allocator, value, colors); + defer quantized.deinit(); + try terminal_image.writeSixelIndexedPayload(allocator, writer, quantized.indices, quantized.palette[0..quantized.palette_len], value.width(), value.height()); +} + +const CountingWriter = struct { + bytes: usize = 0, + + pub fn writeAll(self: *CountingWriter, value: []const u8) !void { + self.bytes += value.len; + } + + pub fn writeByte(self: *CountingWriter, value: u8) !void { + _ = value; + self.bytes += 1; + } + + pub fn print(self: *CountingWriter, comptime format: []const u8, args: anytype) !void { + self.bytes += std.fmt.count(format, args); + } +}; + +fn fillPixels(pixels: []u8, scenario: Scenario) void { + var random = std.Random.DefaultPrng.init(0x1234_5678_9abc_def0); + for (0..@as(usize, scenario.width) * scenario.height) |index| { + const x = index % scenario.width; + const y = index / scenario.width; + const offset = index * 4; + const rgba: [4]u8 = switch (scenario.pattern) { + .flat => .{ 48, 112, 192, 255 }, + .gradient => .{ + @intCast(x * 255 / (scenario.width - 1)), + @intCast(y * 255 / (scenario.height - 1)), + @intCast((x + y) * 255 / (scenario.width + scenario.height - 2)), + 255, + }, + .baseline => .{ + @truncate(x * 13 + y * 3), + @truncate(x * 5 + y * 11), + @truncate(x * 7 + y * 17), + 255, + }, + .photo => .{ + @truncate(x * 13 + y * 3 + (x * y) / 17), + @truncate(x * 5 + y * 11 + (x * y) / 29), + @truncate(x * 7 + y * 17 + (x * y) / 41), + 255, + }, + .noise => .{ random.random().int(u8), random.random().int(u8), random.random().int(u8), 255 }, + .transparent => .{ + @truncate(x * 13 + y * 3), + @truncate(x * 5 + y * 11), + @truncate(x * 7 + y * 17), + if ((x + y) % 4 == 0) 255 else 0, + }, + }; + @memcpy(pixels[offset..][0..4], &rgba); + } +} + +fn appendDragonGeometryBenchmarks( + allocator: std.mem.Allocator, + results: *std.ArrayListUnmanaged(bench_utils.BenchResult), + show_mem: bool, + bench_filter: ?[]const u8, +) !void { + const names = [_][]const u8{ + "Sixel dragon fit 200x300", + "Sixel dragon cover 300x300", + "Sixel dragon cover 300x300 128 colors", + "Sixel dragon cover 300x300 64 colors", + }; + var run_any = false; + for (names) |name| run_any = run_any or bench_utils.matchesBenchFilter(name, bench_filter); + if (!run_any) return; + var gpa: std.heap.GeneralPurposeAllocator(.{}) = .{}; + defer _ = gpa.deinit(); + const work_allocator = gpa.allocator(); + const encoded = try std.fs.cwd().readFileAlloc(work_allocator, "../../../examples/src/assets/image-demo.gif", 2 * 1024 * 1024); + defer work_allocator.free(encoded); + const decoded = try image.decode(work_allocator, encoded, .{}); + defer decoded.deinit(); + const geometries = [_]struct { x: u32, y: u32, width: u32, height: u32, output_width: u32, output_height: u32, colors: usize }{ + .{ .x = 0, .y = 0, .width = 256, .height = 384, .output_width = 200, .output_height = 300, .colors = 255 }, + .{ .x = 0, .y = 64, .width = 256, .height = 256, .output_width = 300, .output_height = 300, .colors = 255 }, + .{ .x = 0, .y = 64, .width = 256, .height = 256, .output_width = 300, .output_height = 300, .colors = 128 }, + .{ .x = 0, .y = 64, .width = 256, .height = 256, .output_width = 300, .output_height = 300, .colors = 64 }, + }; + for (names, geometries) |name, geometry| { + if (!bench_utils.matchesBenchFilter(name, bench_filter)) continue; + var output: std.ArrayList(u8) = .empty; + defer output.deinit(work_allocator); + var stats: bench_utils.BenchStats = .{}; + for (0..10) |_| { + output.clearRetainingCapacity(); + var timer = try std.time.Timer.start(); + const cropped = try image.extract(work_allocator, decoded, geometry.x, geometry.y, geometry.width, geometry.height); + defer cropped.deinit(); + const resized = try image.resize(work_allocator, cropped, geometry.output_width, geometry.output_height, .area); + defer resized.deinit(); + try writeScenario(work_allocator, output.writer(work_allocator), resized, geometry.colors); + stats.record(timer.read()); + } + const mem_stats: ?[]const bench_utils.MemStat = if (show_mem) blk: { + const values = try allocator.alloc(bench_utils.MemStat, 1); + values[0] = .{ .name = "Payload", .bytes = output.items.len }; + break :blk values; + } else null; + try results.append(allocator, .{ + .name = name, + .min_ns = stats.min_ns, + .avg_ns = stats.avg(), + .max_ns = stats.max_ns, + .total_ns = stats.total_ns, + .iterations = stats.count, + .stddev_ns = stats.standardDeviation(), + .rme_95 = stats.relativeMarginOfError95(), + .mem_stats = mem_stats, + }); + } +} + +fn appendResult( + allocator: std.mem.Allocator, + results: *std.ArrayListUnmanaged(bench_utils.BenchResult), + name: []const u8, + stats: bench_utils.BenchStats, + mem_stats: ?[]const bench_utils.MemStat, +) !void { + try results.append(allocator, .{ + .name = name, + .min_ns = stats.min_ns, + .avg_ns = stats.avg(), + .max_ns = stats.max_ns, + .total_ns = stats.total_ns, + .iterations = stats.count, + .stddev_ns = stats.standardDeviation(), + .rme_95 = stats.relativeMarginOfError95(), + .mem_stats = mem_stats, + }); +} + +fn appendKittyBenchmarks( + allocator: std.mem.Allocator, + results: *std.ArrayListUnmanaged(bench_utils.BenchResult), + show_mem: bool, + bench_filter: ?[]const u8, +) !void { + const names = [_][]const u8{ + "Kitty source auto direct", + "Kitty cover auto direct", + "Kitty cover auto count-only", + "Kitty cover placement", + "Kitty cover forced RGBA direct", + "Kitty cover forced RGB direct", + "Kitty photo create+auto direct", + "Kitty photo create+RGBA direct", + "Kitty cover opacity preparation", + }; + var run_any = false; + for (names) |name| run_any = run_any or bench_utils.matchesBenchFilter(name, bench_filter); + if (!run_any) return; + + var gpa: std.heap.GeneralPurposeAllocator(.{}) = .{}; + defer _ = gpa.deinit(); + const work_allocator = gpa.allocator(); + const encoded = try std.fs.cwd().readFileAlloc(work_allocator, "../../../examples/src/assets/image-demo.gif", 2 * 1024 * 1024); + defer work_allocator.free(encoded); + const decoded = try image.decode(work_allocator, encoded, .{}); + defer decoded.deinit(); + const cropped = try image.extract(work_allocator, decoded, 19, 0, 218, 384); + defer cropped.deinit(); + const cover = try image.resize(work_allocator, cropped, 576, 1015, .area); + defer cover.deinit(); + + for ([_]struct { name: []const u8, source: *const image.Image }{ + .{ .name = names[0], .source = decoded }, + .{ .name = names[1], .source = cover }, + }) |scenario| { + if (!bench_utils.matchesBenchFilter(scenario.name, bench_filter)) continue; + var output: std.ArrayList(u8) = .empty; + defer output.deinit(work_allocator); + try output.ensureTotalCapacity(work_allocator, scenario.source.pixels.len * 4 / 3 + 8192); + var stats: bench_utils.BenchStats = .{}; + for (0..20) |_| { + output.clearRetainingCapacity(); + var timer = try std.time.Timer.start(); + try terminal_image.writeKittyTransmit(output.writer(work_allocator), scenario.source, 7, false); + stats.record(timer.read()); + } + const mem_stats: ?[]const bench_utils.MemStat = if (show_mem) blk: { + const values = try allocator.alloc(bench_utils.MemStat, 1); + values[0] = .{ .name = "Payload", .bytes = output.items.len }; + break :blk values; + } else null; + try appendResult(allocator, results, scenario.name, stats, mem_stats); + } + + if (bench_utils.matchesBenchFilter(names[2], bench_filter)) { + var stats: bench_utils.BenchStats = .{}; + for (0..20) |_| { + var counting: CountingWriter = .{}; + var timer = try std.time.Timer.start(); + try terminal_image.writeKittyTransmit(&counting, cover, 7, false); + stats.record(timer.read()); + } + try appendResult(allocator, results, names[2], stats, null); + } + + if (bench_utils.matchesBenchFilter(names[3], bench_filter)) { + var output: std.ArrayList(u8) = .empty; + defer output.deinit(work_allocator); + var stats: bench_utils.BenchStats = .{}; + for (0..1000) |_| { + output.clearRetainingCapacity(); + var timer = try std.time.Timer.start(); + try terminal_image.writeKittyPlacement(output.writer(work_allocator), 7, 8, 0, 0, 36, 29, 0, 0, 576, 1015, -1, false); + stats.record(timer.read()); + } + const mem_stats: ?[]const bench_utils.MemStat = if (show_mem) blk: { + const values = try allocator.alloc(bench_utils.MemStat, 1); + values[0] = .{ .name = "Payload", .bytes = output.items.len }; + break :blk values; + } else null; + try appendResult(allocator, results, names[3], stats, mem_stats); + } + + for ([_]struct { name: []const u8, format: terminal_image.KittyPixelFormat }{ + .{ .name = names[4], .format = .rgba }, + .{ .name = names[5], .format = .rgb }, + }) |scenario| { + if (!bench_utils.matchesBenchFilter(scenario.name, bench_filter)) continue; + var output: std.ArrayList(u8) = .empty; + defer output.deinit(work_allocator); + try output.ensureTotalCapacity(work_allocator, cover.pixels.len * 4 / 3 + 8192); + var stats: bench_utils.BenchStats = .{}; + for (0..20) |_| { + output.clearRetainingCapacity(); + var timer = try std.time.Timer.start(); + try terminal_image.writeKittyTransmitFormat(output.writer(work_allocator), cover, 7, false, scenario.format); + stats.record(timer.read()); + } + const mem_stats: ?[]const bench_utils.MemStat = if (show_mem) blk: { + const values = try allocator.alloc(bench_utils.MemStat, 1); + values[0] = .{ .name = "Payload", .bytes = output.items.len }; + break :blk values; + } else null; + try appendResult(allocator, results, scenario.name, stats, mem_stats); + } + + const photo_pixels = try work_allocator.alloc(u8, 576 * 1015 * 4); + defer work_allocator.free(photo_pixels); + fillPixels(photo_pixels, .{ .name = "", .width = 576, .height = 1015, .pattern = .photo }); + for ([_]struct { name: []const u8, format: terminal_image.KittyPixelFormat }{ + .{ .name = names[6], .format = .auto }, + .{ .name = names[7], .format = .rgba }, + }) |scenario| { + if (!bench_utils.matchesBenchFilter(scenario.name, bench_filter)) continue; + var output: std.ArrayList(u8) = .empty; + defer output.deinit(work_allocator); + try output.ensureTotalCapacity(work_allocator, photo_pixels.len * 4 / 3 + 8192); + var stats: bench_utils.BenchStats = .{}; + for (0..20) |_| { + output.clearRetainingCapacity(); + var timer = try std.time.Timer.start(); + const value = try image.createFromRgba(work_allocator, photo_pixels, 576, 1015, 576 * 4); + try terminal_image.writeKittyTransmitFormat(output.writer(work_allocator), value, 7, false, scenario.format); + value.deinit(); + stats.record(timer.read()); + } + const mem_stats: ?[]const bench_utils.MemStat = if (show_mem) blk: { + const values = try allocator.alloc(bench_utils.MemStat, 1); + values[0] = .{ .name = "Payload", .bytes = output.items.len }; + break :blk values; + } else null; + try appendResult(allocator, results, scenario.name, stats, mem_stats); + } + + if (bench_utils.matchesBenchFilter(names[8], bench_filter)) { + var stats: bench_utils.BenchStats = .{}; + var checksum: u64 = 0; + for (0..100) |_| { + var timer = try std.time.Timer.start(); + const copy = try cover.clone(); + copy.discardEncoded(); + var index: usize = 3; + while (index < copy.pixels.len) : (index += 4) { + copy.pixels[index] = @intCast((@as(u16, copy.pixels[index]) * 128 + 127) / 255); + } + stats.record(timer.read()); + checksum +%= copy.pixels[3]; + copy.deinit(); + } + if (checksum == 0) return error.InvalidKittyOpacityBenchmark; + try appendResult(allocator, results, names[8], stats, null); + } +} + +fn appendImageSwitchBenchmarks( + allocator: std.mem.Allocator, + results: *std.ArrayListUnmanaged(bench_utils.BenchResult), + show_mem: bool, + bench_filter: ?[]const u8, +) !void { + const names = [_][]const u8{ + "Image switch fit extract full", + "Image switch cover extract crop", + "Image switch fit area resize", + "Image switch cover area resize", + "Image switch fit extract+resize", + "Image switch cover extract+resize", + "Image switch fit Sixel encode", + "Image switch cover Sixel encode", + "Image switch fit cold pipeline", + "Image switch cover cold pipeline", + "Image switch fit warm payload", + "Image switch cover warm payload", + "Image switch fit draw placement", + "Image switch cover draw placement", + "Image switch cover Sixel encode 128 colors", + "Image switch cover Sixel encode 64 colors", + }; + var run_any = false; + for (names) |name| run_any = run_any or bench_utils.matchesBenchFilter(name, bench_filter); + if (!run_any) return; + + var gpa: std.heap.GeneralPurposeAllocator(.{}) = .{}; + defer _ = gpa.deinit(); + const work_allocator = gpa.allocator(); + const encoded = try std.fs.cwd().readFileAlloc(work_allocator, "../../../examples/src/assets/image-demo.gif", 2 * 1024 * 1024); + defer work_allocator.free(encoded); + const decoded = try image.decode(work_allocator, encoded, .{}); + defer decoded.deinit(); + + const Geometry = struct { x: u32, y: u32, width: u32, height: u32, output_width: u32, output_height: u32 }; + const fit = Geometry{ .x = 0, .y = 0, .width = 256, .height = 384, .output_width = 576, .output_height = 875 }; + const cover = Geometry{ .x = 19, .y = 0, .width = 218, .height = 384, .output_width = 576, .output_height = 1015 }; + const iterations: usize = 20; + + const fit_crop = try image.extract(work_allocator, decoded, fit.x, fit.y, fit.width, fit.height); + defer fit_crop.deinit(); + const cover_crop = try image.extract(work_allocator, decoded, cover.x, cover.y, cover.width, cover.height); + defer cover_crop.deinit(); + const fit_resized = try image.resize(work_allocator, fit_crop, fit.output_width, fit.output_height, .area); + defer fit_resized.deinit(); + const cover_resized = try image.resize(work_allocator, cover_crop, cover.output_width, cover.output_height, .area); + defer cover_resized.deinit(); + var fit_payload: std.ArrayList(u8) = .empty; + defer fit_payload.deinit(work_allocator); + try terminal_image.writeSixelPayload(work_allocator, fit_payload.writer(work_allocator), fit_resized); + var cover_payload: std.ArrayList(u8) = .empty; + defer cover_payload.deinit(work_allocator); + try terminal_image.writeSixelPayload(work_allocator, cover_payload.writer(work_allocator), cover_resized); + + for ([_]struct { name: []const u8, geometry: Geometry }{ + .{ .name = names[0], .geometry = fit }, + .{ .name = names[1], .geometry = cover }, + }) |scenario| { + if (!bench_utils.matchesBenchFilter(scenario.name, bench_filter)) continue; + var stats: bench_utils.BenchStats = .{}; + for (0..iterations) |_| { + var timer = try std.time.Timer.start(); + const extracted = try image.extract(work_allocator, decoded, scenario.geometry.x, scenario.geometry.y, scenario.geometry.width, scenario.geometry.height); + stats.record(timer.read()); + extracted.deinit(); + } + try appendResult(allocator, results, scenario.name, stats, null); + } + + for ([_]struct { name: []const u8, source: *const image.Image, geometry: Geometry }{ + .{ .name = names[2], .source = fit_crop, .geometry = fit }, + .{ .name = names[3], .source = cover_crop, .geometry = cover }, + }) |scenario| { + if (!bench_utils.matchesBenchFilter(scenario.name, bench_filter)) continue; + var stats: bench_utils.BenchStats = .{}; + for (0..iterations) |_| { + var timer = try std.time.Timer.start(); + const resized = try image.resize(work_allocator, scenario.source, scenario.geometry.output_width, scenario.geometry.output_height, .area); + stats.record(timer.read()); + resized.deinit(); + } + try appendResult(allocator, results, scenario.name, stats, null); + } + + for ([_]struct { name: []const u8, geometry: Geometry }{ + .{ .name = names[4], .geometry = fit }, + .{ .name = names[5], .geometry = cover }, + }) |scenario| { + if (!bench_utils.matchesBenchFilter(scenario.name, bench_filter)) continue; + var stats: bench_utils.BenchStats = .{}; + for (0..iterations) |_| { + var timer = try std.time.Timer.start(); + const extracted = try image.extract(work_allocator, decoded, scenario.geometry.x, scenario.geometry.y, scenario.geometry.width, scenario.geometry.height); + const resized = try image.resize(work_allocator, extracted, scenario.geometry.output_width, scenario.geometry.output_height, .area); + stats.record(timer.read()); + resized.deinit(); + extracted.deinit(); + } + try appendResult(allocator, results, scenario.name, stats, null); + } + + for ([_]struct { name: []const u8, source: *const image.Image, payload_bytes: usize }{ + .{ .name = names[6], .source = fit_resized, .payload_bytes = fit_payload.items.len }, + .{ .name = names[7], .source = cover_resized, .payload_bytes = cover_payload.items.len }, + }) |scenario| { + if (!bench_utils.matchesBenchFilter(scenario.name, bench_filter)) continue; + var output: std.ArrayList(u8) = .empty; + defer output.deinit(work_allocator); + try output.ensureTotalCapacity(work_allocator, scenario.payload_bytes); + var stats: bench_utils.BenchStats = .{}; + for (0..iterations) |_| { + output.clearRetainingCapacity(); + var timer = try std.time.Timer.start(); + try terminal_image.writeSixelPayload(work_allocator, output.writer(work_allocator), scenario.source); + stats.record(timer.read()); + } + const mem_stats: ?[]const bench_utils.MemStat = if (show_mem) blk: { + const values = try allocator.alloc(bench_utils.MemStat, 1); + values[0] = .{ .name = "Payload", .bytes = output.items.len }; + break :blk values; + } else null; + try appendResult(allocator, results, scenario.name, stats, mem_stats); + } + + for ([_]struct { name: []const u8, colors: usize }{ + .{ .name = names[14], .colors = 128 }, + .{ .name = names[15], .colors = 64 }, + }) |scenario| { + if (!bench_utils.matchesBenchFilter(scenario.name, bench_filter)) continue; + var output: std.ArrayList(u8) = .empty; + defer output.deinit(work_allocator); + try writeScenario(work_allocator, output.writer(work_allocator), cover_resized, scenario.colors); + var stats: bench_utils.BenchStats = .{}; + for (0..iterations) |_| { + output.clearRetainingCapacity(); + var timer = try std.time.Timer.start(); + try writeScenario(work_allocator, output.writer(work_allocator), cover_resized, scenario.colors); + stats.record(timer.read()); + } + const mem_stats: ?[]const bench_utils.MemStat = if (show_mem) blk: { + const values = try allocator.alloc(bench_utils.MemStat, 1); + values[0] = .{ .name = "Payload", .bytes = output.items.len }; + break :blk values; + } else null; + try appendResult(allocator, results, scenario.name, stats, mem_stats); + } + + for ([_]struct { name: []const u8, geometry: Geometry, payload_bytes: usize }{ + .{ .name = names[8], .geometry = fit, .payload_bytes = fit_payload.items.len }, + .{ .name = names[9], .geometry = cover, .payload_bytes = cover_payload.items.len }, + }) |scenario| { + if (!bench_utils.matchesBenchFilter(scenario.name, bench_filter)) continue; + var output: std.ArrayList(u8) = .empty; + defer output.deinit(work_allocator); + try output.ensureTotalCapacity(work_allocator, scenario.payload_bytes); + var stats: bench_utils.BenchStats = .{}; + for (0..iterations) |_| { + output.clearRetainingCapacity(); + var timer = try std.time.Timer.start(); + const extracted = try image.extract(work_allocator, decoded, scenario.geometry.x, scenario.geometry.y, scenario.geometry.width, scenario.geometry.height); + const resized = try image.resize(work_allocator, extracted, scenario.geometry.output_width, scenario.geometry.output_height, .area); + try terminal_image.writeSixelPayload(work_allocator, output.writer(work_allocator), resized); + stats.record(timer.read()); + resized.deinit(); + extracted.deinit(); + } + const mem_stats: ?[]const bench_utils.MemStat = if (show_mem) blk: { + const values = try allocator.alloc(bench_utils.MemStat, 1); + values[0] = .{ .name = "Payload", .bytes = output.items.len }; + break :blk values; + } else null; + try appendResult(allocator, results, scenario.name, stats, mem_stats); + } + + for ([_]struct { name: []const u8, payload: []const u8 }{ + .{ .name = names[10], .payload = fit_payload.items }, + .{ .name = names[11], .payload = cover_payload.items }, + }) |scenario| { + if (!bench_utils.matchesBenchFilter(scenario.name, bench_filter)) continue; + var stats: bench_utils.BenchStats = .{}; + var output: std.ArrayList(u8) = .empty; + defer output.deinit(work_allocator); + try output.ensureTotalCapacity(work_allocator, scenario.payload.len + 32); + var checksum: usize = 0; + for (0..100) |_| { + output.clearRetainingCapacity(); + var timer = try std.time.Timer.start(); + try terminal_image.writeSixelFramedPayload(output.writer(work_allocator), scenario.payload, false); + stats.record(timer.read()); + checksum +%= output.items.len + output.items[output.items.len / 2]; + } + if (checksum == 0) return error.InvalidWarmPayloadBenchmark; + const mem_stats: ?[]const bench_utils.MemStat = if (show_mem) blk: { + const values = try allocator.alloc(bench_utils.MemStat, 1); + values[0] = .{ .name = "Payload", .bytes = scenario.payload.len }; + break :blk values; + } else null; + try appendResult(allocator, results, scenario.name, stats, mem_stats); + } + + const draw_buffer = try buffer.OptimizedBuffer.init(work_allocator, 40, 32, .{ + .pool = gp.initGlobalPool(work_allocator), + .id = "image-switch-bench", + }); + defer draw_buffer.deinit(); + for ([_]struct { + name: []const u8, + cell_width: u32, + cell_height: u32, + geometry: Geometry, + }{ + .{ .name = names[12], .cell_width = 36, .cell_height = 25, .geometry = fit }, + .{ .name = names[13], .cell_width = 36, .cell_height = 29, .geometry = cover }, + }) |scenario| { + if (!bench_utils.matchesBenchFilter(scenario.name, bench_filter)) continue; + var stats: bench_utils.BenchStats = .{}; + for (0..100) |_| { + draw_buffer.clear(ansi.rgbColor(0, 0, 0, 0), null); + var timer = try std.time.Timer.start(); + if (!try draw_buffer.drawImage( + decoded, + 1, + 0, + 0, + scenario.cell_width, + scenario.cell_height, + scenario.geometry.output_width, + scenario.geometry.output_height, + scenario.geometry.x, + scenario.geometry.y, + scenario.geometry.width, + scenario.geometry.height, + .sixel, + )) return error.ImagePlacementFailed; + stats.record(timer.read()); + } + try appendResult(allocator, results, scenario.name, stats, null); + } +} + +pub fn run(allocator: std.mem.Allocator, show_mem: bool, bench_filter: ?[]const u8) ![]bench_utils.BenchResult { + var results: std.ArrayListUnmanaged(bench_utils.BenchResult) = .{}; + for (scenarios) |scenario| { + const count_name = try std.fmt.allocPrint(allocator, "{s} count-only", .{scenario.name}); + const quantize_name = "Sixel 160x240 original baseline quantize-only"; + const run_materialized = bench_utils.matchesBenchFilter(scenario.name, bench_filter); + const run_counted = bench_utils.matchesBenchFilter(count_name, bench_filter); + const run_quantized = scenario.pattern == .baseline and scenario.colors == 255 and bench_utils.matchesBenchFilter(quantize_name, bench_filter); + if (!run_materialized and !run_counted and !run_quantized) continue; + var gpa: std.heap.GeneralPurposeAllocator(.{}) = .{}; + defer _ = gpa.deinit(); + const work_allocator = gpa.allocator(); + const pixels = try work_allocator.alloc(u8, @as(usize, scenario.width) * scenario.height * 4); + defer work_allocator.free(pixels); + fillPixels(pixels, scenario); + const value = try image.createFromRgba(work_allocator, pixels, scenario.width, scenario.height, scenario.width * 4); + defer value.deinit(); + var output: std.ArrayList(u8) = .empty; + defer output.deinit(work_allocator); + + try writeScenario(work_allocator, output.writer(work_allocator), value, scenario.colors); + const iterations: usize = if (scenario.width > 160) 20 else 50; + if (run_materialized) { + var stats: bench_utils.BenchStats = .{}; + for (0..iterations) |_| { + output.clearRetainingCapacity(); + var timer = try std.time.Timer.start(); + try writeScenario(work_allocator, output.writer(work_allocator), value, scenario.colors); + stats.record(timer.read()); + } + const mem_stats: ?[]const bench_utils.MemStat = if (show_mem) blk: { + const values = try allocator.alloc(bench_utils.MemStat, 1); + values[0] = .{ .name = "Payload", .bytes = output.items.len }; + break :blk values; + } else null; + try results.append(allocator, .{ + .name = scenario.name, + .min_ns = stats.min_ns, + .avg_ns = stats.avg(), + .max_ns = stats.max_ns, + .total_ns = stats.total_ns, + .iterations = stats.count, + .stddev_ns = stats.standardDeviation(), + .rme_95 = stats.relativeMarginOfError95(), + .mem_stats = mem_stats, + }); + } + + if (run_counted) { + var count_stats: bench_utils.BenchStats = .{}; + for (0..iterations) |_| { + var counting: CountingWriter = .{}; + var timer = try std.time.Timer.start(); + try writeScenario(work_allocator, &counting, value, scenario.colors); + count_stats.record(timer.read()); + if (counting.bytes != output.items.len) return error.IncorrectSixelByteCount; + } + try results.append(allocator, .{ + .name = count_name, + .min_ns = count_stats.min_ns, + .avg_ns = count_stats.avg(), + .max_ns = count_stats.max_ns, + .total_ns = count_stats.total_ns, + .iterations = count_stats.count, + .stddev_ns = count_stats.standardDeviation(), + .rme_95 = count_stats.relativeMarginOfError95(), + .mem_stats = null, + }); + } + if (run_quantized) { + var quantize_stats: bench_utils.BenchStats = .{}; + var checksum: usize = 0; + for (0..iterations) |_| { + var timer = try std.time.Timer.start(); + var quantized = try terminal_image.quantizeSixel(work_allocator, value, scenario.colors); + quantize_stats.record(timer.read()); + checksum +%= quantized.palette_len + quantized.indices[0]; + quantized.deinit(); + } + if (checksum == 0) return error.InvalidQuantizeBenchmark; + try results.append(allocator, .{ + .name = quantize_name, + .min_ns = quantize_stats.min_ns, + .avg_ns = quantize_stats.avg(), + .max_ns = quantize_stats.max_ns, + .total_ns = quantize_stats.total_ns, + .iterations = quantize_stats.count, + .stddev_ns = quantize_stats.standardDeviation(), + .rme_95 = quantize_stats.relativeMarginOfError95(), + .mem_stats = null, + }); + } + } + try appendKittyBenchmarks(allocator, &results, show_mem, bench_filter); + try appendDragonGeometryBenchmarks(allocator, &results, show_mem, bench_filter); + try appendImageSwitchBenchmarks(allocator, &results, show_mem, bench_filter); + return results.toOwnedSlice(allocator); +} diff --git a/packages/core/src/zig/buffer.zig b/packages/core/src/zig/buffer.zig index a41386966d..928588df84 100644 --- a/packages/core/src/zig/buffer.zig +++ b/packages/core/src/zig/buffer.zig @@ -9,6 +9,7 @@ const assert = std.debug.assert; const gp = @import("grapheme.zig"); const link = @import("link.zig"); +const native_image = @import("image.zig"); const logger = @import("logger.zig"); const utf8 = @import("utf8.zig"); @@ -157,6 +158,24 @@ fn applyOpacity(color: RGBA, opacity: u8) RGBA { /// Optimized buffer for terminal rendering pub const OptimizedBuffer = struct { + pub const ImagePlacement = struct { + placement_id: u32, + image_handle: u32, + image: *native_image.Image, + x: i32, + y: i32, + width: u32, + height: u32, + pixel_width: u32, + pixel_height: u32, + source_x: u32, + source_y: u32, + source_width: u32, + source_height: u32, + opacity: u8, + protocol: native_image.RenderProtocol, + }; + buffer: struct { char: []u32, fg: []RGBA, @@ -177,6 +196,7 @@ pub const OptimizedBuffer = struct { id: []const u8, scissor_stack: std.ArrayListUnmanaged(ClipRect), opacity_stack: std.ArrayListUnmanaged(f32), + image_placements: std.ArrayListUnmanaged(ImagePlacement), const InitOptions = struct { respectAlpha: bool = false, @@ -247,6 +267,7 @@ pub const OptimizedBuffer = struct { .id = owned_id, .scissor_stack = scissor_stack, .opacity_stack = opacity_stack, + .image_placements = .{}, }; @memset(self.buffer.char, 0); @@ -277,7 +298,9 @@ pub const OptimizedBuffer = struct { const allocator = self.allocator; defer allocator.destroy(self); + self.clearImagePlacements(); self.opacity_stack.deinit(self.allocator); + self.image_placements.deinit(self.allocator); self.scissor_stack.deinit(self.allocator); self.link_tracker.deinit(); self.grapheme_tracker.deinit(); @@ -433,12 +456,18 @@ pub const OptimizedBuffer = struct { const cellChar = char orelse DEFAULT_SPACE_CHAR; self.link_tracker.clear(); self.grapheme_tracker.clear(); + self.clearImagePlacements(); @memset(self.buffer.char, @intCast(cellChar)); @memset(self.buffer.attributes, 0); @memset(self.buffer.fg, ansi.rgbColor(255, 255, 255, 255)); @memset(self.buffer.bg, bg); } + fn clearImagePlacements(self: *OptimizedBuffer) void { + for (self.image_placements.items) |placement| placement.image.deinit(); + self.image_placements.clearRetainingCapacity(); + } + /// Write a single cell and update link tracker. No grapheme tracking, /// span cleanup, or continuation propagation. pub fn setRaw(self: *OptimizedBuffer, x: u32, y: u32, cell: Cell) void { @@ -682,7 +711,14 @@ pub const OptimizedBuffer = struct { while (i < total_cells) : (i += 1) { const char_code = self.buffer.char[i]; - if (gp.isGraphemeChar(char_code)) { + if (gp.isImageChar(char_code)) { + const fallback = quadrantChars[gp.imageFallbackFromChar(char_code)]; + var utf8_bytes: [4]u8 = undefined; + const utf8_len = std.unicode.utf8Encode(@intCast(fallback), &utf8_bytes) catch unreachable; + if (bytes_written + utf8_len > output_buffer.len) return BufferError.BufferTooSmall; + @memcpy(output_buffer[bytes_written .. bytes_written + utf8_len], utf8_bytes[0..utf8_len]); + bytes_written += @intCast(utf8_len); + } else if (gp.isGraphemeChar(char_code)) { const gid = gp.graphemeIdFromChar(char_code); if (self.pool.get(gid)) |grapheme_bytes| { if (bytes_written + grapheme_bytes.len > output_buffer.len) { @@ -792,6 +828,47 @@ pub const OptimizedBuffer = struct { return overlayCell; } + inline fn opaqueCell(cell: Cell) Cell { + return makeCell( + cell.char, + ansi.packRGBA8(ansi.red(cell.fg), ansi.green(cell.fg), ansi.blue(cell.fg), 255, ansi.getMeta(cell.fg)), + ansi.packRGBA8(ansi.red(cell.bg), ansi.green(cell.bg), ansi.blue(cell.bg), 255, ansi.getMeta(cell.bg)), + cell.attributes, + ); + } + + inline fn cellSpanOverlapsImage(self: *const OptimizedBuffer, x: u32, y: u32, char: u32) bool { + if (self.image_placements.items.len == 0 or y >= self.height) return false; + + const width = if (gp.isGraphemeChar(char)) gp.charRightExtent(char) + 1 else 1; + var offset: u32 = 0; + while (offset < width and x + offset < self.width) : (offset += 1) { + if (gp.isImageChar(self.buffer.char[self.coordsToIndex(x + offset, y)])) return true; + } + return false; + } + + inline fn cellSpanTailOverlapsImage(self: *const OptimizedBuffer, x: u32, y: u32, char: u32) bool { + if (!gp.isGraphemeChar(char) or y >= self.height) return false; + const width = gp.charRightExtent(char) + 1; + var offset: u32 = 1; + while (offset < width and x + offset < self.width) : (offset += 1) { + if (gp.isImageChar(self.buffer.char[self.coordsToIndex(x + offset, y)])) return true; + } + return false; + } + + inline fn rectOverlapsImagePlacement(self: *const OptimizedBuffer, x: i32, y: i32, width: u32, height: u32) bool { + for (self.image_placements.items) |placement| { + if (@as(i64, x) < @as(i64, placement.x) + placement.width and @as(i64, placement.x) < @as(i64, x) + width and + @as(i64, y) < @as(i64, placement.y) + placement.height and @as(i64, placement.y) < @as(i64, y) + height) + { + return true; + } + } + return false; + } + pub fn setCellWithAlphaBlending( self: *OptimizedBuffer, x: u32, @@ -804,30 +881,68 @@ pub const OptimizedBuffer = struct { self.setCellWithAlphaBlendingCell(x, y, makeCell(char, fg, bg, attributes)); } - fn setCellWithAlphaBlendingCell(self: *OptimizedBuffer, x: u32, y: u32, cell: Cell) void { - if (!self.isPointInScissor(@intCast(x), @intCast(y))) return; + inline fn blendCellWithOpacity(self: *OptimizedBuffer, x: u32, y: u32, cell: Cell, opacity: f32, dest_cell: ?Cell) void { + const opacity_u8 = opacityToU8(opacity); + const effective_cell = makeCell( + cell.char, + applyOpacity(cell.fg, opacity_u8), + applyOpacity(cell.bg, opacity_u8), + cell.attributes, + ); + + if (dest_cell) |dest| { + const blended_cell = self.blendCells(effective_cell, dest); + if (!self.grapheme_tracker.hasAny() and !self.link_tracker.hasAny() and !gp.isClusterChar(blended_cell.char)) { + self.setRaw(x, y, blended_cell); + } else { + self.set(x, y, blended_cell); + } + } else { + self.set(x, y, effective_cell); + } + } + inline fn setCellWithAlphaBlendingCellWithoutImages(self: *OptimizedBuffer, x: u32, y: u32, cell: Cell) void { + if (!self.isPointInScissor(@intCast(x), @intCast(y))) return; const opacity = self.getCurrentOpacity(); if (isFullyTransparent(opacity, cell.fg, cell.bg)) return; if (isFullyOpaque(opacity, cell.fg, cell.bg)) { self.set(x, y, cell); return; } + self.blendCellWithOpacity(x, y, cell, opacity, self.get(x, y)); + } - const opacity_u8 = opacityToU8(opacity); - const effectiveCell = makeCell( - cell.char, - applyOpacity(cell.fg, opacity_u8), - applyOpacity(cell.bg, opacity_u8), - cell.attributes, - ); + inline fn skipTransparentCellDraw(self: *const OptimizedBuffer, opacity: f32, fully_transparent: bool) bool { + if (fully_transparent) { + if (self.image_placements.items.len == 0) return true; + if (opacity == 0.0) return true; + } + return false; + } - if (self.get(x, y)) |destCell| { - const blendedCell = self.blendCells(effectiveCell, destCell); - self.set(x, y, blendedCell); - } else { - self.set(x, y, effectiveCell); + inline fn setVisibleCellWithAlphaBlending(self: *OptimizedBuffer, x: u32, y: u32, cell: Cell, opacity: f32, fully_transparent: bool) void { + if (!self.isPointInScissor(@intCast(x), @intCast(y))) return; + if (isFullyOpaque(opacity, cell.fg, cell.bg)) { + self.set(x, y, cell); + return; } + + const destCell = self.get(x, y); + const first_cell_overlaps_image = if (destCell) |dest| gp.isImageChar(dest.char) else false; + if (first_cell_overlaps_image or self.cellSpanTailOverlapsImage(x, y, cell.char)) { + self.set(x, y, opaqueCell(cell)); + return; + } + if (fully_transparent) return; + self.blendCellWithOpacity(x, y, cell, opacity, destCell); + } + + fn setCellWithAlphaBlendingCell(self: *OptimizedBuffer, x: u32, y: u32, cell: Cell) void { + const opacity = self.getCurrentOpacity(); + const fully_transparent = isFullyTransparent(opacity, cell.fg, cell.bg); + if (self.skipTransparentCellDraw(opacity, fully_transparent)) return; + self.setVisibleCellWithAlphaBlending(x, y, cell, opacity, fully_transparent); } pub fn setCellWithAlphaBlendingRaw( @@ -846,7 +961,7 @@ pub const OptimizedBuffer = struct { if (!self.isPointInScissor(@intCast(x), @intCast(y))) return; const opacity = self.getCurrentOpacity(); - if (isFullyTransparent(opacity, cell.fg, cell.bg)) return; + if (opacity == 0.0) return; if (isFullyOpaque(opacity, cell.fg, cell.bg)) { assert(!gp.isGraphemeChar(cell.char)); assert(!gp.isContinuationChar(cell.char)); @@ -854,6 +969,8 @@ pub const OptimizedBuffer = struct { return; } + if (isFullyTransparent(opacity, cell.fg, cell.bg)) return; + const opacity_u8 = opacityToU8(opacity); const effectiveCell = makeCell( cell.char, @@ -862,8 +979,8 @@ pub const OptimizedBuffer = struct { cell.attributes, ); - if (self.get(x, y)) |destCell| { - const blendedCell = self.blendCells(effectiveCell, destCell); + if (self.get(x, y)) |dest| { + const blendedCell = self.blendCells(effectiveCell, dest); assert(!gp.isGraphemeChar(blendedCell.char)); assert(!gp.isContinuationChar(blendedCell.char)); self.setRaw(x, y, blendedCell); @@ -874,6 +991,16 @@ pub const OptimizedBuffer = struct { } } + inline fn setCellWithAlphaBlendingRawImageAware(self: *OptimizedBuffer, x: u32, y: u32, cell: Cell) void { + if (self.get(x, y)) |dest| { + if (gp.isImageChar(dest.char) and self.getCurrentOpacity() > 0.0) { + self.setRaw(x, y, opaqueCell(cell)); + return; + } + } + self.setCellWithAlphaBlendingRawCell(x, y, cell); + } + inline fn trySetTransparentTextCellFast( self: *OptimizedBuffer, index: u32, @@ -895,6 +1022,7 @@ pub const OptimizedBuffer = struct { const dest_attributes = self.buffer.attributes[index]; if (ansi.TextAttributes.getLinkId(dest_attributes) != 0) return false; if (gp.isGraphemeChar(dest_char) or gp.isContinuationChar(dest_char)) return false; + if (self.image_placements.items.len != 0 and gp.isImageChar(dest_char)) return false; if (char == DEFAULT_SPACE_CHAR and dest_char != 0 and dest_char != DEFAULT_SPACE_CHAR and gp.encodedCharWidth(dest_char) == 1) { return true; @@ -906,7 +1034,7 @@ pub const OptimizedBuffer = struct { return true; } - pub fn drawChar( + pub inline fn drawChar( self: *OptimizedBuffer, char: u32, x: u32, @@ -915,7 +1043,11 @@ pub const OptimizedBuffer = struct { bg: RGBA, attributes: u32, ) void { - self.setCellWithAlphaBlendingCell(x, y, makeCell(char, fg, bg, attributes)); + const cell = makeCell(char, fg, bg, attributes); + const opacity = self.getCurrentOpacity(); + const fully_transparent = isFullyTransparent(opacity, fg, bg); + if (self.skipTransparentCellDraw(opacity, fully_transparent)) return; + self.setVisibleCellWithAlphaBlending(x, y, cell, opacity, fully_transparent); } pub fn fillRect( @@ -932,7 +1064,8 @@ pub const OptimizedBuffer = struct { if (!self.isRectInScissor(@intCast(x), @intCast(y), width, height)) return; const opacity = self.getCurrentOpacity(); - if (isFullyTransparent(opacity, ansi.rgbColor(0, 0, 0, 0), bg)) return; + const fully_transparent = isFullyTransparent(opacity, ansi.rgbColor(0, 0, 0, 0), bg); + if (fully_transparent and (opacity == 0.0 or self.image_placements.items.len == 0)) return; const startX = x; const startY = y; @@ -951,6 +1084,52 @@ pub const OptimizedBuffer = struct { const clippedEndX = @min(endX, @as(u32, @intCast(clippedRect.x + @as(i32, @intCast(clippedRect.width)) - 1))); const clippedEndY = @min(endY, @as(u32, @intCast(clippedRect.y + @as(i32, @intCast(clippedRect.height)) - 1))); + if (fully_transparent) { + const cell = makeCell(DEFAULT_SPACE_CHAR, ansi.rgbColor(255, 255, 255, 255), bg, 0); + const clipped_area = @as(u64, clippedEndX - clippedStartX + 1) * (clippedEndY - clippedStartY + 1); + var intersection_area: u64 = 0; + for (self.image_placements.items) |placement| { + const intersection_start_x = @max(@as(i64, clippedStartX), placement.x); + const intersection_start_y = @max(@as(i64, clippedStartY), placement.y); + const intersection_end_x = @min(@as(i64, clippedEndX) + 1, @as(i64, placement.x) + placement.width); + const intersection_end_y = @min(@as(i64, clippedEndY) + 1, @as(i64, placement.y) + placement.height); + if (intersection_start_x >= intersection_end_x or intersection_start_y >= intersection_end_y) continue; + + const width_u64: u64 = @intCast(intersection_end_x - intersection_start_x); + const height_u64: u64 = @intCast(intersection_end_y - intersection_start_y); + intersection_area = @min(clipped_area, intersection_area +| width_u64 *| height_u64); + } + if (intersection_area == 0) return; + + if (intersection_area >= clipped_area / 2 + clipped_area % 2) { + var fill_y = clippedStartY; + while (fill_y <= clippedEndY) : (fill_y += 1) { + var fill_x = clippedStartX; + while (fill_x <= clippedEndX) : (fill_x += 1) { + if (gp.isImageChar(self.buffer.char[self.coordsToIndex(fill_x, fill_y)])) self.setRaw(fill_x, fill_y, opaqueCell(cell)); + } + } + return; + } + + for (self.image_placements.items) |placement| { + const intersection_start_x = @max(@as(i64, clippedStartX), placement.x); + const intersection_start_y = @max(@as(i64, clippedStartY), placement.y); + const intersection_end_x = @min(@as(i64, clippedEndX) + 1, @as(i64, placement.x) + placement.width); + const intersection_end_y = @min(@as(i64, clippedEndY) + 1, @as(i64, placement.y) + placement.height); + if (intersection_start_x >= intersection_end_x or intersection_start_y >= intersection_end_y) continue; + + var fill_y: u32 = @intCast(intersection_start_y); + while (fill_y < intersection_end_y) : (fill_y += 1) { + var fill_x: u32 = @intCast(intersection_start_x); + while (fill_x < intersection_end_x) : (fill_x += 1) { + if (gp.isImageChar(self.buffer.char[self.coordsToIndex(fill_x, fill_y)])) self.setRaw(fill_x, fill_y, opaqueCell(cell)); + } + } + } + return; + } + const hasAlpha = isRGBAWithAlpha(bg) or opacity < 1.0; const graphemeAware = self.grapheme_tracker.hasAny(); const linkAware = self.link_tracker.hasAny(); @@ -970,11 +1149,13 @@ pub const OptimizedBuffer = struct { } else if (hasAlpha) { // No grapheme/link bookkeeping is needed here, so the raw blend // path avoids the extra tracker work done by the generic setter. + const image_aware = self.image_placements.items.len != 0; var fillY = clippedStartY; while (fillY <= clippedEndY) : (fillY += 1) { var fillX = clippedStartX; while (fillX <= clippedEndX) : (fillX += 1) { - self.setCellWithAlphaBlendingRaw(fillX, fillY, DEFAULT_SPACE_CHAR, ansi.rgbColor(255, 255, 255, 255), bg, 0); + const cell = makeCell(DEFAULT_SPACE_CHAR, ansi.rgbColor(255, 255, 255, 255), bg, 0); + if (image_aware) self.setCellWithAlphaBlendingRawImageAware(fillX, fillY, cell) else self.setCellWithAlphaBlendingRawCell(fillX, fillY, cell); } } } else { @@ -1023,7 +1204,33 @@ pub const OptimizedBuffer = struct { ); } - pub fn drawText( + inline fn setTextCell(self: *OptimizedBuffer, x: u32, y: u32, cell: Cell) void { + if (self.image_placements.items.len != 0 and self.cellSpanOverlapsImage(x, y, cell.char)) { + self.set(x, y, opaqueCell(cell)); + return; + } + if (isRGBAWithAlpha(cell.bg)) { + self.setCellWithAlphaBlendingCellWithoutImages(x, y, cell); + return; + } + self.set(x, y, cell); + } + + pub inline fn drawText( + self: *OptimizedBuffer, + text: []const u8, + x: u32, + y: u32, + fg: RGBA, + bg: ?RGBA, + attributes: u32, + ) BufferError!void { + const opacity = self.getCurrentOpacity(); + if (isFullyTransparent(opacity, fg, bg orelse ansi.rgbColor(0, 0, 0, 0)) and (opacity == 0.0 or self.image_placements.items.len == 0)) return; + return self.drawVisibleText(text, x, y, fg, bg, attributes); + } + + fn drawVisibleText( self: *OptimizedBuffer, text: []const u8, x: u32, @@ -1034,11 +1241,30 @@ pub const OptimizedBuffer = struct { ) BufferError!void { if (x >= self.width or y >= self.height) return; if (text.len == 0) return; - - const opacity = self.getCurrentOpacity(); - if (isFullyTransparent(opacity, fg, bg orelse ansi.rgbColor(0, 0, 0, 0))) return; + const explicit_colors_opaque = if (bg) |background| + !isRGBAWithAlpha(fg) and !isRGBAWithAlpha(background) + else + false; const is_ascii_only = utf8.isAsciiOnly(text); + if (explicit_colors_opaque and is_ascii_only) { + var printable = true; + for (text) |byte| { + if (byte < 32 or byte > 126) { + printable = false; + break; + } + } + if (printable) { + const background = bg.?; + for (text, 0..) |byte, offset| { + const char_x = x + @as(u32, @intCast(offset)); + if (char_x >= self.width) break; + self.set(char_x, y, makeCell(byte, fg, background, attributes)); + } + return; + } + } var grapheme_list: std.ArrayListUnmanaged(utf8.GraphemeInfo) = .{}; defer grapheme_list.deinit(self.allocator); @@ -1052,7 +1278,7 @@ pub const OptimizedBuffer = struct { var col: u32 = 0; var special_idx: usize = 0; - while (byte_offset < text.len) { + text_loop: while (byte_offset < text.len) { const charX = x + advance_cells; if (charX >= self.width) break; @@ -1074,7 +1300,8 @@ pub const OptimizedBuffer = struct { byte_offset += 1; } - if (!self.isPointInScissor(@intCast(charX), @intCast(y))) { + const is_tab = grapheme_bytes.len == 1 and grapheme_bytes[0] == '\t'; + if (!is_tab and !self.isPointInScissor(@intCast(charX), @intCast(y))) { advance_cells += g_width; col += g_width; continue; @@ -1094,22 +1321,30 @@ pub const OptimizedBuffer = struct { col += g_width; continue; } + if (cell_width > 1 and !is_tab) { + if (charX + cell_width > self.width) { + advance_cells += g_width; + col += g_width; + continue; + } + for (1..cell_width) |span_offset| { + if (!self.isPointInScissor(@intCast(charX + @as(u32, @intCast(span_offset))), @intCast(y))) { + advance_cells += g_width; + col += g_width; + continue :text_loop; + } + } + } - if (grapheme_bytes.len == 1 and grapheme_bytes[0] == '\t') { + if (is_tab) { var tab_col: u32 = 0; while (tab_col < g_width) : (tab_col += 1) { const tab_x = charX + tab_col; if (tab_x >= self.width) break; + if (!self.isPointInScissor(@intCast(tab_x), @intCast(y))) continue; - if (isRGBAWithAlpha(bgColor)) { - self.setCellWithAlphaBlendingCell( - tab_x, - y, - makeCell(DEFAULT_SPACE_CHAR, fg, bgColor, attributes), - ); - } else { - self.set(tab_x, y, makeCell(DEFAULT_SPACE_CHAR, fg, bgColor, attributes)); - } + const cell = makeCell(DEFAULT_SPACE_CHAR, fg, bgColor, attributes); + if (explicit_colors_opaque) self.set(tab_x, y, cell) else self.setTextCell(tab_x, y, cell); } advance_cells += g_width; col += g_width; @@ -1124,15 +1359,8 @@ pub const OptimizedBuffer = struct { encoded_char = gp.packGraphemeStart(gid & gp.GRAPHEME_ID_MASK, cell_width); } - if (isRGBAWithAlpha(bgColor)) { - self.setCellWithAlphaBlendingCell( - charX, - y, - makeCell(encoded_char, fg, bgColor, attributes), - ); - } else { - self.set(charX, y, makeCell(encoded_char, fg, bgColor, attributes)); - } + const cell = makeCell(encoded_char, fg, bgColor, attributes); + if (explicit_colors_opaque) self.set(charX, y, cell) else self.setTextCell(charX, y, cell); advance_cells += cell_width; col += g_width; @@ -1170,6 +1398,7 @@ pub const OptimizedBuffer = struct { const graphemeAware = self.grapheme_tracker.hasAny() or frameBuffer.grapheme_tracker.hasAny(); const linkAware = self.link_tracker.hasAny() or frameBuffer.link_tracker.hasAny(); + const imageAware = self.image_placements.items.len != 0 or frameBuffer.image_placements.items.len != 0; // Calculate clipping once for both paths const clippedRect = self.clipRectToScissor(startDestX, startDestY, destWidth, destHeight) orelse return; @@ -1178,8 +1407,23 @@ pub const OptimizedBuffer = struct { const clippedEndX = @min(endDestX, @as(i32, @intCast(clippedRect.x + @as(i32, @intCast(clippedRect.width)) - 1))); const clippedEndY = @min(endDestY, @as(i32, @intCast(clippedRect.y + @as(i32, @intCast(clippedRect.height)) - 1))); - if (!graphemeAware and !frameBuffer.respectAlpha and !linkAware) { + if (!graphemeAware and !frameBuffer.respectAlpha and !linkAware and !imageAware) { // Fast path: direct memory copy + const first_source_y = srcY + @as(u32, @intCast(clippedStartY - destY)); + const first_source_x = srcX + @as(u32, @intCast(clippedStartX - destX)); + const copy_width = @min(@as(u32, @intCast(clippedEndX - clippedStartX + 1)), frameBuffer.width - first_source_x); + if (clippedStartX == 0 and first_source_x == 0 and copy_width == self.width and copy_width == frameBuffer.width) { + const row_count: u32 = @intCast(clippedEndY - clippedStartY + 1); + const cell_count = copy_width * row_count; + const dest_start = self.coordsToIndex(0, @intCast(clippedStartY)); + const source_start = frameBuffer.coordsToIndex(0, first_source_y); + @memcpy(self.buffer.char[dest_start .. dest_start + cell_count], frameBuffer.buffer.char[source_start .. source_start + cell_count]); + @memcpy(self.buffer.fg[dest_start .. dest_start + cell_count], frameBuffer.buffer.fg[source_start .. source_start + cell_count]); + @memcpy(self.buffer.bg[dest_start .. dest_start + cell_count], frameBuffer.buffer.bg[source_start .. source_start + cell_count]); + @memcpy(self.buffer.attributes[dest_start .. dest_start + cell_count], frameBuffer.buffer.attributes[source_start .. source_start + cell_count]); + return; + } + var dY = clippedStartY; while (dY <= clippedEndY) : (dY += 1) { @@ -1205,6 +1449,64 @@ pub const OptimizedBuffer = struct { return; } + const has_source_images = frameBuffer.image_placements.items.len != 0; + var empty_image_id_map = [_]u32{0}; + const allocated_image_id_map = if (has_source_images) + self.allocator.alloc(u32, frameBuffer.image_placements.items.len + 1) catch null + else + null; + const image_id_map = allocated_image_id_map orelse empty_image_id_map[0..]; + defer if (allocated_image_id_map) |allocated| self.allocator.free(allocated); + @memset(image_id_map, 0); + var can_copy_images = allocated_image_id_map != null; + if (can_copy_images) { + self.image_placements.ensureTotalCapacity( + self.allocator, + self.image_placements.items.len + frameBuffer.image_placements.items.len, + ) catch { + can_copy_images = false; + }; + } + for (frameBuffer.image_placements.items, 1..) |placement, source_id| { + if (!can_copy_images or self.image_placements.items.len >= gp.IMAGE_ID_MASK) break; + const full_x = destX + placement.x - @as(i32, @intCast(srcX)); + const full_y = destY + placement.y - @as(i32, @intCast(srcY)); + const x0 = @max(full_x, clippedStartX); + const y0 = @max(full_y, clippedStartY); + const x1 = @min(full_x + @as(i32, @intCast(placement.width)), clippedEndX + 1); + const y1 = @min(full_y + @as(i32, @intCast(placement.height)), clippedEndY + 1); + if (x0 >= x1 or y0 >= y1) continue; + const left: u32 = @intCast(x0 - full_x); + const top: u32 = @intCast(y0 - full_y); + const right: u32 = @intCast(x1 - full_x); + const bottom: u32 = @intCast(y1 - full_y); + const source_start_x = placement.source_x + @as(u32, @intCast((@as(u64, left) * placement.source_width) / placement.width)); + const source_start_y = placement.source_y + @as(u32, @intCast((@as(u64, top) * placement.source_height) / placement.height)); + const source_end_x = placement.source_x + @as(u32, @intCast((@as(u64, right) * placement.source_width + placement.width - 1) / placement.width)); + const source_end_y = placement.source_y + @as(u32, @intCast((@as(u64, bottom) * placement.source_height + placement.height - 1) / placement.height)); + const visible_width: u32 = @intCast(x1 - x0); + const visible_height: u32 = @intCast(y1 - y0); + self.image_placements.appendAssumeCapacity(.{ + .placement_id = @intCast(self.image_placements.items.len + 1), + .image_handle = placement.image_handle, + .image = placement.image, + .x = x0, + .y = y0, + .width = visible_width, + .height = visible_height, + .pixel_width = if (placement.pixel_width == 0) 0 else @intCast((@as(u64, visible_width) * placement.pixel_width + placement.width - 1) / placement.width), + .pixel_height = if (placement.pixel_height == 0) 0 else @intCast((@as(u64, visible_height) * placement.pixel_height + placement.height - 1) / placement.height), + .source_x = source_start_x, + .source_y = source_start_y, + .source_width = source_end_x - source_start_x, + .source_height = source_end_y - source_start_y, + .opacity = @intCast(mulDiv255(placement.opacity, opacityToU8(self.getCurrentOpacity()))), + .protocol = placement.protocol, + }); + placement.image.retain(); + image_id_map[source_id] = @intCast(self.image_placements.items.len); + } + var dY = clippedStartY; while (dY <= clippedEndY) : (dY += 1) { var lastDrawnGraphemeId: u32 = 0; @@ -1221,12 +1523,30 @@ pub const OptimizedBuffer = struct { const srcIndex = frameBuffer.coordsToIndex(sX, sY); if (srcIndex >= frameBuffer.buffer.char.len) continue; - const srcChar = frameBuffer.buffer.char[srcIndex]; + var srcChar = frameBuffer.buffer.char[srcIndex]; + if (gp.isImageChar(srcChar)) { + const source_id = gp.imageIdFromChar(srcChar); + if (source_id < image_id_map.len) { + const mapped_id = image_id_map[source_id]; + srcChar = if (mapped_id != 0) + gp.packImageCell(mapped_id, gp.imageFallbackFromChar(srcChar)) + else + quadrantChars[gp.imageFallbackFromChar(srcChar)]; + } else { + srcChar = quadrantChars[gp.imageFallbackFromChar(srcChar)]; + } + } const srcFg = frameBuffer.buffer.fg[srcIndex]; const srcBg = frameBuffer.buffer.bg[srcIndex]; const srcAttr = frameBuffer.buffer.attributes[srcIndex]; - if (ansi.alpha(srcBg) == 0 and ansi.alpha(srcFg) == 0) continue; + if (ansi.alpha(srcBg) == 0 and ansi.alpha(srcFg) == 0) { + if (gp.isImageChar(srcChar)) { + const current = self.get(@intCast(dX), @intCast(dY)) orelse continue; + self.set(@intCast(dX), @intCast(dY), makeCell(srcChar, current.fg, current.bg, current.attributes)); + } + continue; + } if (graphemeAware) { if (gp.isContinuationChar(srcChar)) { @@ -1415,7 +1735,7 @@ pub const OptimizedBuffer = struct { } } - while (col < col_end) { + text_buffer_loop: while (col < col_end) { const at_special = special_idx < specials.len and specials[special_idx].col_offset == col; var grapheme_bytes: []const u8 = undefined; @@ -1461,7 +1781,8 @@ pub const OptimizedBuffer = struct { break; } - if (!self.isPointInScissor(currentX, currentY)) { + const is_tab = grapheme_bytes.len == 1 and grapheme_bytes[0] == '\t'; + if (!is_tab and !self.isPointInScissor(currentX, currentY)) { globalCharPos += g_width; currentX += @as(i32, @intCast(g_width)); column_in_line += g_width; @@ -1469,6 +1790,27 @@ pub const OptimizedBuffer = struct { continue; } + if (g_width > 1 and !is_tab) { + if (column_in_line + g_width > horizontal_offset + viewport_width or + currentX < 0 or currentX + @as(i32, @intCast(g_width)) > @as(i32, @intCast(self.width))) + { + globalCharPos += g_width; + currentX += @as(i32, @intCast(g_width)); + column_in_line += g_width; + col += g_width; + continue; + } + for (1..g_width) |span_offset| { + if (!self.isPointInScissor(currentX + @as(i32, @intCast(span_offset)), currentY)) { + globalCharPos += g_width; + currentX += @as(i32, @intCast(g_width)); + column_in_line += g_width; + col += g_width; + continue :text_buffer_loop; + } + } + } + var selection_offset = globalCharPos; if (vline.is_truncated and globalCharPos >= line_col_offset) { const ellipsis_width: u32 = 3; @@ -1615,26 +1957,30 @@ pub const OptimizedBuffer = struct { // path instead of paying for generic per-cell blending. const useTransparentTextFastPath = self.getCurrentOpacity() == 1.0 and ansi.alpha(drawBg) == 0; - if (grapheme_bytes.len == 1 and grapheme_bytes[0] == '\t') { + if (is_tab) { const tab_indicator = view.getTabIndicator(); const tab_indicator_color = view.getTabIndicatorColor(); var tab_col: u32 = 0; while (tab_col < g_width) : (tab_col += 1) { - if (currentX + @as(i32, @intCast(tab_col)) >= @as(i32, @intCast(self.width))) break; + if (column_in_line + tab_col >= horizontal_offset + viewport_width) break; + const tab_x = currentX + @as(i32, @intCast(tab_col)); + if (tab_x < 0) continue; + if (tab_x >= @as(i32, @intCast(self.width))) break; + if (!self.isPointInScissor(tab_x, currentY)) continue; const char = if (tab_col == 0 and tab_indicator != null) tab_indicator.? else DEFAULT_SPACE_CHAR; const fg = if (tab_col == 0 and tab_indicator_color != null) tab_indicator_color.? else drawFg; if (useTransparentTextFastPath) { - const index = self.coordsToIndex(@intCast(currentX + @as(i32, @intCast(tab_col))), @intCast(currentY)); + const index = self.coordsToIndex(@intCast(tab_x), @intCast(currentY)); if (self.trySetTransparentTextCellFast(index, char, fg, drawAttributes)) { continue; } } self.setCellWithAlphaBlendingCell( - @intCast(currentX + @as(i32, @intCast(tab_col))), + @intCast(tab_x), @intCast(currentY), makeCell(char, fg, drawBg, drawAttributes), ); @@ -1838,11 +2184,21 @@ pub const OptimizedBuffer = struct { inline fn isSingleWidthBorderChar(char: u32) bool { if (char == 0) return true; + if ((char >= 32 and char <= 126) or (char >= 0x2500 and char <= 0x257F)) return true; if (char > MAX_UNICODE_CODEPOINT) return false; return utf8.eastAsianWidth(@intCast(char)) == 1; } - inline fn canUseTransparentBorderFastPath(self: *const OptimizedBuffer, borderChars: [*]const u32, borderColor: RGBA, backgroundColor: RGBA) bool { + inline fn canUseTransparentBorderFastPath( + self: *const OptimizedBuffer, + borderChars: [*]const u32, + borderColor: RGBA, + backgroundColor: RGBA, + x: i32, + y: i32, + width: u32, + height: u32, + ) bool { // When border glyphs are width-1, opaque, and tracker-free, drawing them // over a transparent background is just a direct char/fg/attrs write // while keeping the destination background unchanged. @@ -1856,11 +2212,12 @@ pub const OptimizedBuffer = struct { isSingleWidthBorderChar(borderChars[@intFromEnum(BorderCharIndex.bottomLeft)]) and isSingleWidthBorderChar(borderChars[@intFromEnum(BorderCharIndex.bottomRight)]) and isSingleWidthBorderChar(borderChars[@intFromEnum(BorderCharIndex.horizontal)]) and - isSingleWidthBorderChar(borderChars[@intFromEnum(BorderCharIndex.vertical)]); + isSingleWidthBorderChar(borderChars[@intFromEnum(BorderCharIndex.vertical)]) and + (self.image_placements.items.len == 0 or !self.rectOverlapsImagePlacement(x, y, width, height)); } /// Draw a box with borders and optional fill - pub fn drawBox( + pub inline fn drawBox( self: *OptimizedBuffer, x: i32, y: i32, @@ -1882,9 +2239,51 @@ pub const OptimizedBuffer = struct { const border_bg_transparent = isFullyTransparent(opacity, borderColor, backgroundColor); const has_title = title != null or bottomTitle != null; const title_visible = has_title and !isFullyTransparent(opacity, titleColor, backgroundColor); + if (border_bg_transparent and !title_visible) { + if (self.image_placements.items.len == 0) return; + if (opacity == 0.0) return; + } + return self.drawVisibleBox( + x, + y, + width, + height, + borderChars, + borderSides, + borderColor, + backgroundColor, + titleColor, + shouldFill, + title, + titleAlignment, + bottomTitle, + bottomTitleAlignment, + border_bg_transparent, + title_visible, + opacity, + ); + } - if (border_bg_transparent and !title_visible) return; - + fn drawVisibleBox( + self: *OptimizedBuffer, + x: i32, + y: i32, + width: u32, + height: u32, + borderChars: [*]const u32, + borderSides: BorderSides, + borderColor: RGBA, + backgroundColor: RGBA, + titleColor: RGBA, + shouldFill: bool, + title: ?[]const u8, + titleAlignment: u8, + bottomTitle: ?[]const u8, + bottomTitleAlignment: u8, + border_bg_transparent: bool, + title_visible: bool, + opacity: f32, + ) !void { const startX = @max(0, x); const startY = @max(0, y); const endX = @min(@as(i32, @intCast(self.width)) - 1, x + @as(i32, @intCast(width)) - 1); @@ -1895,6 +2294,7 @@ pub const OptimizedBuffer = struct { const boxWidth = @as(u32, @intCast(endX - startX + 1)); const boxHeight = @as(u32, @intCast(endY - startY + 1)); if (!self.isRectInScissor(startX, startY, boxWidth, boxHeight)) return; + if (border_bg_transparent and !title_visible and !self.rectOverlapsImagePlacement(startX, startY, boxWidth, boxHeight)) return; const isAtActualLeft = startX == x; const isAtActualRight = endX == x + @as(i32, @intCast(width)) - 1; @@ -1931,7 +2331,9 @@ pub const OptimizedBuffer = struct { const extendVerticalsToTop = leftBorderOnly or rightBorderOnly or bottomOnlyWithVerticals; const extendVerticalsToBottom = leftBorderOnly or rightBorderOnly or topOnlyWithVerticals; - const useTransparentBorderFastPath = canUseTransparentBorderFastPath(self, borderChars, borderColor, backgroundColor); + const useTransparentBorderFastPath = canUseTransparentBorderFastPath(self, borderChars, borderColor, backgroundColor, startX, startY, boxWidth, boxHeight); + const useOpaqueBorderFastPath = isFullyOpaque(opacity, borderColor, backgroundColor); + const image_aware = self.image_placements.items.len != 0; // Draw horizontal borders if (borderSides.top or borderSides.bottom) { @@ -1958,12 +2360,14 @@ pub const OptimizedBuffer = struct { self.buffer.char[index] = char; self.buffer.fg[index] = borderColor; self.buffer.attributes[index] = 0; + } else if (useOpaqueBorderFastPath) { + self.set(@intCast(drawX), @intCast(startY), makeCell(char, borderColor, backgroundColor, 0)); } else { - self.setCellWithAlphaBlendingCell( - @intCast(drawX), - @intCast(startY), - makeCell(char, borderColor, backgroundColor, 0), - ); + const cell = makeCell(char, borderColor, backgroundColor, 0); + if (image_aware) + self.setCellWithAlphaBlendingCell(@intCast(drawX), @intCast(startY), cell) + else + self.setCellWithAlphaBlendingCellWithoutImages(@intCast(drawX), @intCast(startY), cell); } } } @@ -1992,12 +2396,14 @@ pub const OptimizedBuffer = struct { self.buffer.char[index] = char; self.buffer.fg[index] = borderColor; self.buffer.attributes[index] = 0; + } else if (useOpaqueBorderFastPath) { + self.set(@intCast(drawX), @intCast(endY), makeCell(char, borderColor, backgroundColor, 0)); } else { - self.setCellWithAlphaBlendingCell( - @intCast(drawX), - @intCast(endY), - makeCell(char, borderColor, backgroundColor, 0), - ); + const cell = makeCell(char, borderColor, backgroundColor, 0); + if (image_aware) + self.setCellWithAlphaBlendingCell(@intCast(drawX), @intCast(endY), cell) + else + self.setCellWithAlphaBlendingCellWithoutImages(@intCast(drawX), @intCast(endY), cell); } } } @@ -2018,12 +2424,18 @@ pub const OptimizedBuffer = struct { self.buffer.char[index] = borderChars[@intFromEnum(BorderCharIndex.vertical)]; self.buffer.fg[index] = borderColor; self.buffer.attributes[index] = 0; - } else { - self.setCellWithAlphaBlendingCell( + } else if (useOpaqueBorderFastPath) { + self.set( @intCast(startX), @intCast(drawY), makeCell(borderChars[@intFromEnum(BorderCharIndex.vertical)], borderColor, backgroundColor, 0), ); + } else { + const cell = makeCell(borderChars[@intFromEnum(BorderCharIndex.vertical)], borderColor, backgroundColor, 0); + if (image_aware) + self.setCellWithAlphaBlendingCell(@intCast(startX), @intCast(drawY), cell) + else + self.setCellWithAlphaBlendingCellWithoutImages(@intCast(startX), @intCast(drawY), cell); } } @@ -2034,12 +2446,18 @@ pub const OptimizedBuffer = struct { self.buffer.char[index] = borderChars[@intFromEnum(BorderCharIndex.vertical)]; self.buffer.fg[index] = borderColor; self.buffer.attributes[index] = 0; - } else { - self.setCellWithAlphaBlendingCell( + } else if (useOpaqueBorderFastPath) { + self.set( @intCast(endX), @intCast(drawY), makeCell(borderChars[@intFromEnum(BorderCharIndex.vertical)], borderColor, backgroundColor, 0), ); + } else { + const cell = makeCell(borderChars[@intFromEnum(BorderCharIndex.vertical)], borderColor, backgroundColor, 0); + if (image_aware) + self.setCellWithAlphaBlendingCell(@intCast(endX), @intCast(drawY), cell) + else + self.setCellWithAlphaBlendingCellWithoutImages(@intCast(endX), @intCast(drawY), cell); } } } @@ -2101,6 +2519,149 @@ pub const OptimizedBuffer = struct { }; } + pub fn drawImage( + self: *OptimizedBuffer, + image: *const native_image.Image, + image_handle: u32, + pos_x: i32, + pos_y: i32, + width: u32, + height: u32, + pixel_width: u32, + pixel_height: u32, + source_x: u32, + source_y: u32, + source_width: u32, + source_height: u32, + protocol: native_image.RenderProtocol, + ) !bool { + const opacity = opacityToU8(self.getCurrentOpacity()); + if (opacity == 0) return false; + if (width == 0 or height == 0 or source_width == 0 or source_height == 0 or + source_x >= image.width() or source_y >= image.height() or source_width > image.width() - source_x or + source_height > image.height() - source_y or self.image_placements.items.len >= gp.IMAGE_ID_MASK or + self.width > std.math.maxInt(i32) or self.height > std.math.maxInt(i32)) return false; + var clip_x0 = @max(@as(i64, pos_x), 0); + var clip_y0 = @max(@as(i64, pos_y), 0); + var clip_x1 = @min(@as(i64, pos_x) + width, self.width); + var clip_y1 = @min(@as(i64, pos_y) + height, self.height); + if (self.getCurrentScissorRect()) |scissor| { + clip_x0 = @max(clip_x0, scissor.x); + clip_y0 = @max(clip_y0, scissor.y); + clip_x1 = @min(clip_x1, @as(i64, scissor.x) + scissor.width); + clip_y1 = @min(clip_y1, @as(i64, scissor.y) + scissor.height); + } + if (clip_x0 >= clip_x1 or clip_y0 >= clip_y1) return false; + + const left: u32 = @intCast(clip_x0 - @as(i64, pos_x)); + const top: u32 = @intCast(clip_y0 - @as(i64, pos_y)); + const right: u32 = @intCast(clip_x1 - @as(i64, pos_x)); + const bottom: u32 = @intCast(clip_y1 - @as(i64, pos_y)); + const clipped_source_x = source_x + @as(u32, @intCast((@as(u64, left) * source_width) / width)); + const clipped_source_y = source_y + @as(u32, @intCast((@as(u64, top) * source_height) / height)); + const source_end_x = source_x + @as(u32, @intCast((@as(u64, right) * source_width + width - 1) / width)); + const source_end_y = source_y + @as(u32, @intCast((@as(u64, bottom) * source_height + height - 1) / height)); + const clipped_width: u32 = @intCast(clip_x1 - clip_x0); + const clipped_height: u32 = @intCast(clip_y1 - clip_y0); + const clipped_pixel_width = if (pixel_width == 0) 0 else @as(u32, @intCast((@as(u64, clipped_width) * pixel_width + width - 1) / width)); + const clipped_pixel_height = if (pixel_height == 0) 0 else @as(u32, @intCast((@as(u64, clipped_height) * pixel_height + height - 1) / height)); + const placement_id: u32 = @intCast(self.image_placements.items.len + 1); + try self.image_placements.append(self.allocator, .{ + .placement_id = placement_id, + .image_handle = image_handle, + .image = @constCast(image), + .x = @intCast(clip_x0), + .y = @intCast(clip_y0), + .width = clipped_width, + .height = clipped_height, + .pixel_width = clipped_pixel_width, + .pixel_height = clipped_pixel_height, + .source_x = clipped_source_x, + .source_y = clipped_source_y, + .source_width = source_end_x - clipped_source_x, + .source_height = source_end_y - clipped_source_y, + .opacity = opacity, + .protocol = protocol, + }); + @constCast(image).retain(); + + var cell_y: u32 = 0; + while (cell_y < clipped_height) : (cell_y += 1) { + const dest_y: u32 = @intCast(clip_y0 + cell_y); + var cell_x: u32 = 0; + while (cell_x < clipped_width) : (cell_x += 1) { + const dest_x: u32 = @intCast(clip_x0 + cell_x); + const current = self.get(dest_x, dest_y) orelse continue; + self.set(dest_x, dest_y, makeCell(gp.packImageCell(placement_id, 0), current.fg, current.bg, current.attributes)); + } + } + return true; + } + + pub fn materializeImageFallback(self: *OptimizedBuffer, placement_id: u32) void { + if (placement_id == 0 or placement_id > self.image_placements.items.len) return; + const placement = self.image_placements.items[placement_id - 1]; + var cell_y: u32 = 0; + while (cell_y < placement.height) : (cell_y += 1) { + const dest_y: u32 = @intCast(placement.y + @as(i32, @intCast(cell_y))); + var cell_x: u32 = 0; + while (cell_x < placement.width) : (cell_x += 1) { + const dest_x: u32 = @intCast(placement.x + @as(i32, @intCast(cell_x))); + const current = self.get(dest_x, dest_y) orelse continue; + if (!gp.isImageChar(current.char)) continue; + const current_placement_id = gp.imageIdFromChar(current.char); + if (current_placement_id < placement_id) continue; + + var pixels: [4]RGBA = undefined; + inline for (0..4) |quadrant| { + const sample_x = cell_x * 2 + @as(u32, @intCast(quadrant & 1)); + const sample_y = cell_y * 2 + @as(u32, @intCast(quadrant >> 1)); + const sx = placement.source_x + @min(placement.source_width - 1, @as(u32, @intCast((@as(u64, sample_x) * placement.source_width) / (@as(u64, placement.width) * 2)))); + const sy = placement.source_y + @min(placement.source_height - 1, @as(u32, @intCast((@as(u64, sample_y) * placement.source_height) / (@as(u64, placement.height) * 2)))); + const offset = (@as(usize, sy) * placement.image.width() + sx) * 4; + pixels[quadrant] = ansi.rgbColor( + placement.image.pixels[offset], + placement.image.pixels[offset + 1], + placement.image.pixels[offset + 2], + placement.image.pixels[offset + 3], + ); + } + const rendered = renderQuadrantBlock(pixels); + const fallback = makeCell( + if (current_placement_id == placement_id) + gp.packImageCell(placement_id, quadrantIndex(rendered.char)) + else + current.char, + rendered.fg, + rendered.bg, + if (current_placement_id == placement_id) 0 else current.attributes, + ); + if (placement.opacity == 255 and !isRGBAWithAlpha(fallback.fg) and !isRGBAWithAlpha(fallback.bg)) { + self.setRaw(dest_x, dest_y, fallback); + } else { + const effective = makeCell( + fallback.char, + applyOpacity(fallback.fg, placement.opacity), + applyOpacity(fallback.bg, placement.opacity), + fallback.attributes, + ); + self.setRaw(dest_x, dest_y, self.blendCells(effective, current)); + } + } + } + } + + pub fn materializeImageFallbacks(self: *OptimizedBuffer) void { + if (self.image_placements.items.len == 0) return; + for (1..self.image_placements.items.len + 1) |placement_id| { + self.materializeImageFallback(@intCast(placement_id)); + } + for (self.buffer.char) |*char| { + if (gp.isImageChar(char.*)) char.* = quadrantChars[gp.imageFallbackFromChar(char.*)]; + } + self.clearImagePlacements(); + } + /// Draw a buffer of pixel data using super sampling (2x2 pixels per character cell) /// alignedBytesPerRow: The number of bytes per row in the pixelData buffer, considering alignment/padding. pub fn drawSuperSampleBuffer( @@ -2385,7 +2946,7 @@ fn getPixelColor(idx: usize, data: [*]const u8, dataLen: usize, bgra: bool) RGBA return ansi.rgbColor(rByte, gByte, bByte, aByte); } -const quadrantChars = [_]u32{ +pub const quadrantChars = [_]u32{ 32, // 0000 0x2597, // 0001 BR ░ 0x2596, // 0010 BL ░ @@ -2404,6 +2965,13 @@ const quadrantChars = [_]u32{ 0x2588, // 1111 Full Block █ }; +fn quadrantIndex(char: u32) u4 { + for (quadrantChars, 0..) |candidate, index| { + if (candidate == char) return @intCast(index); + } + return 15; +} + fn colorDistance(a: RGBA, b: RGBA) f32 { const dr = @as(f32, @floatFromInt(ansi.red(a))) - @as(f32, @floatFromInt(ansi.red(b))); const dg = @as(f32, @floatFromInt(ansi.green(a))) - @as(f32, @floatFromInt(ansi.green(b))); diff --git a/packages/core/src/zig/build.zig b/packages/core/src/zig/build.zig index 0c82ba3d1a..f4f789ec73 100644 --- a/packages/core/src/zig/build.zig +++ b/packages/core/src/zig/build.zig @@ -98,6 +98,9 @@ fn isMacOSSDKAvailable(b: *std.Build, sdk_path: []const u8) bool { return isMacOSSDKPath(sdk_path) and pathExists(b.pathJoin(&.{ sdk_path, "usr", "lib" })) and macOSSDKHasFramework(b, sdk_path, "CoreFoundation") and + macOSSDKHasFramework(b, sdk_path, "AppKit") and + macOSSDKHasFramework(b, sdk_path, "Foundation") and + macOSSDKHasFramework(b, sdk_path, "ImageIO") and macOSSDKHasFramework(b, sdk_path, "CoreAudio") and macOSSDKHasFramework(b, sdk_path, "AudioToolbox"); } @@ -160,6 +163,129 @@ fn addMiniaudioShim( }); } +fn appendCFlags(b: *std.Build, base: []const []const u8, extra: []const []const u8) []const []const u8 { + const flags = b.allocator.alloc([]const u8, base.len + extra.len) catch @panic("OOM"); + @memcpy(flags[0..base.len], base); + @memcpy(flags[base.len..], extra); + return flags; +} + +fn addImageShim(b: *std.Build, artifact: *std.Build.Step.Compile, target: std.Build.ResolvedTarget, macos_sdk_path: ?[]const u8) void { + const flags: []const []const u8 = switch (target.result.os.tag) { + .macos => &.{ "-std=c99", "-ffp-contract=off", "-fvisibility=hidden", "-isysroot", macos_sdk_path.? }, + else => &.{ "-std=c99", "-ffp-contract=off", "-fvisibility=hidden" }, + }; + + artifact.addCSourceFile(.{ + .file = b.path("image-shim.c"), + .flags = flags, + }); + + // One upstream SIMD sRGB idiom forms `fp32_to_srgb8_tab4 - 912`; its + // clamped indexes 912...1015 resolve to actual table elements 0...103. + // The reads are in range, but the pre-array pointer trips C bounds + // instrumentation. Disable only `bounds` for this translation unit; + // pointer-overflow, alignment, and other sanitizers remain enabled. This + // is separate from our coefficient-copy alignment patch. See vendor/stb/README.md. + const resize_flags: []const []const u8 = switch (target.result.os.tag) { + .macos => &.{ "-std=c99", "-ffp-contract=off", "-fvisibility=hidden", "-fno-sanitize=bounds", "-isysroot", macos_sdk_path.? }, + else => &.{ "-std=c99", "-ffp-contract=off", "-fvisibility=hidden", "-fno-sanitize=bounds" }, + }; + artifact.addCSourceFile(.{ + .file = b.path("image-resize-shim.c"), + .flags = resize_flags, + }); + + const webp_flags: []const []const u8 = switch (target.result.os.tag) { + .macos => &.{ "-std=c99", "-fvisibility=hidden", "-DWEBP_EXTERN=extern", "-isysroot", macos_sdk_path.? }, + else => &.{ "-std=c99", "-fvisibility=hidden", "-DWEBP_EXTERN=extern" }, + }; + const webp_dispatch_flags = if (target.result.cpu.arch == .x86_64) + appendCFlags(b, webp_flags, &.{ "-DWEBP_HAVE_SSE2", "-DWEBP_HAVE_SSE41", "-DWEBP_HAVE_AVX2" }) + else + webp_flags; + artifact.addIncludePath(b.path("vendor/libwebp")); + artifact.addCSourceFile(.{ + .file = b.path("image-webp-config.c"), + .flags = webp_dispatch_flags, + }); + artifact.addCSourceFiles(.{ + .root = b.path("vendor/libwebp"), + .files = &.{ + "src/dec/alpha_dec.c", + "src/dec/buffer_dec.c", + "src/dec/frame_dec.c", + "src/dec/idec_dec.c", + "src/dec/io_dec.c", + "src/dec/quant_dec.c", + "src/dec/tree_dec.c", + "src/dec/vp8_dec.c", + "src/dec/vp8l_dec.c", + "src/dec/webp_dec.c", + "src/dsp/alpha_processing.c", + "src/dsp/cpu.c", + "src/dsp/dec.c", + "src/dsp/dec_clip_tables.c", + "src/dsp/filters.c", + "src/dsp/lossless.c", + "src/dsp/rescaler.c", + "src/dsp/upsampling.c", + "src/dsp/yuv.c", + "src/utils/bit_reader_utils.c", + "src/utils/color_cache_utils.c", + "src/utils/filters_utils.c", + "src/utils/huffman_utils.c", + "src/utils/palette.c", + "src/utils/quant_levels_dec_utils.c", + "src/utils/random_utils.c", + "src/utils/rescaler_utils.c", + "src/utils/thread_utils.c", + "src/utils/utils.c", + }, + .flags = webp_dispatch_flags, + }); + + switch (target.result.cpu.arch) { + .x86_64 => { + artifact.addCSourceFiles(.{ + .root = b.path("vendor/libwebp"), + .files = &.{ + "src/dsp/alpha_processing_sse2.c", + "src/dsp/dec_sse2.c", + "src/dsp/filters_sse2.c", + "src/dsp/lossless_sse2.c", + "src/dsp/rescaler_sse2.c", + "src/dsp/upsampling_sse2.c", + "src/dsp/yuv_sse2.c", + }, + .flags = webp_flags, + }); + artifact.addCSourceFile(.{ + .file = b.path("image-webp-sse41.c"), + .flags = webp_flags, + }); + artifact.addCSourceFile(.{ + .file = b.path("image-webp-avx2.c"), + .flags = webp_flags, + }); + }, + .aarch64 => artifact.addCSourceFiles(.{ + .root = b.path("vendor/libwebp"), + .files = &.{ + "src/dsp/alpha_processing_neon.c", + "src/dsp/dec_neon.c", + "src/dsp/filters_neon.c", + "src/dsp/lossless_neon.c", + "src/dsp/rescaler_neon.c", + "src/dsp/upsampling_neon.c", + "src/dsp/yuv_neon.c", + }, + .flags = webp_flags, + }), + else => {}, + } +} + fn addMacOSSDKSearchPaths(b: *std.Build, artifact: *std.Build.Step.Compile, sdk_path: []const u8) void { const include_path = b.pathJoin(&.{ sdk_path, "usr", "include" }); const framework_path = b.pathJoin(&.{ sdk_path, "System", "Library", "Frameworks" }); @@ -171,12 +297,23 @@ fn addMacOSSDKSearchPaths(b: *std.Build, artifact: *std.Build.Step.Compile, sdk_ artifact.addLibraryPath(.{ .cwd_relative = lib_path }); } +fn addMacOSClipboardDependencies(b: *std.Build, artifact: *std.Build.Step.Compile, sdk_path: []const u8) void { + artifact.addCSourceFile(.{ + .file = b.path("clipboard/macos-shim.m"), + .flags = &.{ "-fobjc-arc", "-fobjc-arc-exceptions", "-isysroot", sdk_path }, + }); + artifact.linkFramework("AppKit"); + artifact.linkFramework("Foundation"); + artifact.linkFramework("ImageIO"); + artifact.linkSystemLibrary("pthread"); + addMacOSSDKSearchPaths(b, artifact, sdk_path); +} + fn addMacOSSystemLibraries(b: *std.Build, artifact: *std.Build.Step.Compile, sdk_path: []const u8) void { + addMacOSClipboardDependencies(b, artifact, sdk_path); artifact.linkFramework("CoreFoundation"); artifact.linkFramework("CoreAudio"); artifact.linkFramework("AudioToolbox"); - artifact.linkSystemLibrary("pthread"); - addMacOSSDKSearchPaths(b, artifact, sdk_path); } fn addNativeAudioDependencies( @@ -186,12 +323,14 @@ fn addNativeAudioDependencies( macos_sdk_path: ?[]const u8, ) void { addMiniaudioShim(b, artifact, target, macos_sdk_path); + addImageShim(b, artifact, target, macos_sdk_path); switch (target.result.os.tag) { .macos => addMacOSSystemLibraries(b, artifact, macos_sdk_path.?), .linux => { artifact.linkSystemLibrary("dl"); artifact.linkSystemLibrary("pthread"); + artifact.linkSystemLibrary("m"); }, else => {}, } @@ -337,6 +476,9 @@ pub fn build(b: *std.Build) void { .name = "opentui-bench", .root_module = bench_mod, }); + bench_exe.linkLibC(); + addImageShim(b, bench_exe, native_target, macos_sdk_path); + if (native_target.result.os.tag == .macos) addMacOSSDKSearchPaths(b, bench_exe, macos_sdk_path.?); const run_bench = b.addRunArtifact(bench_exe); if (b.args) |args| { run_bench.addArgs(args); diff --git a/packages/core/src/zig/clipboard/clock.zig b/packages/core/src/zig/clipboard/clock.zig new file mode 100644 index 0000000000..5c2edb2277 --- /dev/null +++ b/packages/core/src/zig/clipboard/clock.zig @@ -0,0 +1,25 @@ +const std = @import("std"); + +var mutex: std.Thread.Mutex = .{}; +var timer: ?std.time.Timer = null; + +pub fn init() !void { + mutex.lock(); + defer mutex.unlock(); + + if (timer != null) return; + timer = try std.time.Timer.start(); +} + +pub fn nowNs() i128 { + mutex.lock(); + defer mutex.unlock(); + + return @intCast(timer.?.read()); +} + +test "clipboard clock is monotonic process-relative time" { + try init(); + const before_ns = nowNs(); + try std.testing.expect(nowNs() >= before_ns); +} diff --git a/packages/core/src/zig/clipboard/host.zig b/packages/core/src/zig/clipboard/host.zig new file mode 100644 index 0000000000..ee3a5dc437 --- /dev/null +++ b/packages/core/src/zig/clipboard/host.zig @@ -0,0 +1,2507 @@ +const std = @import("std"); +const builtin = @import("builtin"); +const handles = @import("../handles.zig"); +const clipboard_clock = @import("clock.zig"); +const clipboard_linux = @import("linux.zig"); +const clipboard_wayland = @import("wayland.zig"); +const clipboard_x11 = @import("x11.zig"); +const clipboard_windows = @import("windows.zig"); +const clipboard_windows_dib = @import("windows-dib.zig"); +const clipboard_macos = @import("macos.zig"); + +test { + _ = clipboard_clock; + _ = clipboard_linux; + _ = clipboard_wayland; + _ = clipboard_x11; + _ = clipboard_windows; + _ = clipboard_windows_dib; + _ = clipboard_macos; +} + +const Allocator = std.mem.Allocator; +pub const Handle = handles.Handle; + +pub const OperationStatus = enum(u8) { + pending = 0, + read = 1, + empty = 2, + written = 3, + cleared = 4, + unsupported = 5, + cancelled = 6, + timed_out = 7, + limit_exceeded = 8, + failed = 9, + invalid_handle = 10, +}; + +pub const StartStatus = enum(u8) { + ok = 0, + invalid_service = 1, + shutting_down = 2, + limit_exceeded = 3, + invalid_argument = 4, + out_of_memory = 5, +}; + +pub const CancelStatus = enum(u8) { + requested = 0, + already_terminal = 1, + invalid_handle = 2, +}; + +pub const CopyStatus = enum(u8) { + ok = 0, + buffer_too_small = 1, + invalid_handle = 2, + invalid_state = 3, + invalid_argument = 4, +}; + +pub const DestroyStatus = enum(u8) { + destroyed = 0, + not_ready = 1, + invalid_handle = 2, +}; + +pub const ShutdownStatus = enum(u8) { + pending = 0, + ready = 1, + invalid_handle = 2, +}; + +const READ_MIME_COUNT_MAX: u32 = 64; +const READ_MIME_ESSENCE_BYTES_MAX: u32 = 255; +// Bounds within-operation restarts when a chosen offer's source vanishes mid-read. +const WAYLAND_READ_STALE_RETRY_MAX: u8 = 2; +const OPERATIONS_MAX_DEFAULT: u32 = 16; +const PROVIDER_TRANSFERS_MAX_DEFAULT: u32 = 16; +const ResultKind = enum { mime, data, diagnostic }; +const Selection = enum(u8) { clipboard = 0, primary = 1 }; +const OperationKind = enum { read, write, clear }; +const WaylandTransferFormat = enum { direct, bmp_to_png }; + +const ErrorCode = enum(u32) { + internal = 1, + out_of_memory = 2, + wayland_protocol = 100, + wayland_dispatch = 101, + wayland_flush = 102, + wayland_provider = 103, + wayland_transfer = 104, + x11_protocol = 200, + x11_flush = 201, + x11_provider = 202, + x11_transfer = 203, +}; + +extern "c" fn pthread_mach_thread_np(thread: std.c.pthread_t) std.c.mach_port_t; +extern "c" fn pthread_tryjoin_np(thread: std.Thread.Handle, result: ?*?*anyopaque) c_int; + +fn tryJoinThread(thread: std.Thread) bool { + return switch (builtin.os.tag) { + .linux => switch (pthread_tryjoin_np(thread.getHandle(), null)) { + 0 => true, + @intFromEnum(std.posix.E.BUSY) => false, + else => false, + }, + .windows => blk: { + std.os.windows.WaitForSingleObject(thread.getHandle(), 0) catch |err| switch (err) { + error.WaitTimeOut => break :blk false, + else => break :blk false, + }; + thread.join(); + break :blk true; + }, + .macos => blk: { + var info: std.c.thread_basic_info = undefined; + var count = std.c.THREAD_BASIC_INFO_COUNT; + const result = std.c.thread_info( + pthread_mach_thread_np(thread.getHandle()), + std.c.THREAD_BASIC_INFO, + @ptrCast(&info), + &count, + ); + if (result != 0 and result != 4) break :blk false; // KERN_INVALID_ARGUMENT means the thread is gone. + if (result == 0 and info.run_state != 5) break :blk false; // TH_STATE_HALTED + thread.join(); + break :blk true; + }, + else => @compileError("Unsupported clipboard worker target"), + }; +} + +const Operation = struct { + allocator: Allocator, + handle: Handle = 0, + service: *Service, + mutex: std.Thread.Mutex = .{}, + thread: ?std.Thread = null, + status: OperationStatus = .pending, + cancel_requested: bool = false, + kind: OperationKind, + request: []u8 = &.{}, + result: []u8 = &.{}, + error_code: u32 = 0, + diagnostic: []const u8 = &.{}, + result_mime: []u8 = &.{}, + transfer_data: std.ArrayListUnmanaged(u8) = .{}, + transfer_fd: ?std.posix.fd_t = null, + wayland_transfer_format: WaylandTransferFormat = .direct, + wayland_core_focus_acquired: bool = false, + wayland_conversion_started: bool = false, + wayland_barrier_serial: u64 = 0, + wayland_stale_retry_count: u8 = 0, + max_bytes: u32 = 0, + max_image_pixels: u32 = 0, + max_conversion_bytes: u32 = 0, + selection: Selection = .clipboard, + preference_offset: usize = 4, + candidate_failed: bool = false, + implemented_candidate_attempted: bool = false, + mechanism: ?clipboard_linux.Mechanism = null, + x11_read: clipboard_x11.ReadState = .{}, + x11_write: clipboard_x11.WriteState = .{}, + x11_targets: [5]u32 = undefined, + x11_target_count: u8 = 0, + x11_target_index: u8 = 0, + platform_cancel: std.atomic.Value(bool) = .init(false), + platform_mutation_started: std.atomic.Value(bool) = .init(false), + platform_terminal_request: ?OperationStatus = null, + mutation_sequence: u64 = 0, + timeout_ms: u32 = 0, + started_ns: i128 = 0, + + fn waylandBmpWorker(operation: *Operation) void { + const converted = clipboard_windows_dib.convertBmpToPng( + operation.allocator, + operation.transfer_data.items, + .{ + .max_output_bytes = operation.max_bytes, + .max_image_pixels = operation.max_image_pixels, + .max_conversion_bytes = operation.max_conversion_bytes, + .cancel_requested = &operation.platform_cancel, + .deadline_ns = operation.started_ns + @as(i128, operation.timeout_ms) * std.time.ns_per_ms, + }, + ); + + operation.mutex.lock(); + defer operation.mutex.unlock(); + operation.transfer_data.deinit(operation.allocator); + operation.transfer_data = .{}; + if (operation.status != .pending) { + if (converted) |png| operation.allocator.free(png) else |_| {} + operation.wayland_conversion_started = false; + return; + } + if (operation.cancel_requested) { + if (converted) |png| operation.allocator.free(png) else |_| {} + operation.status = .cancelled; + operation.wayland_conversion_started = false; + return; + } + if (platformDeadlineExpired(operation, clipboard_clock.nowNs())) { + if (converted) |png| operation.allocator.free(png) else |_| {} + operation.status = .timed_out; + operation.wayland_conversion_started = false; + return; + } + if (converted) |png| { + operation.result = png; + operation.status = .read; + } else |err| switch (err) { + error.Unsupported => { + if (operation.result_mime.len > 0) operation.allocator.free(operation.result_mime); + operation.result_mime = &.{}; + }, + error.InvalidData => { + operation.rememberFailure(.wayland_transfer, "Wayland BMP clipboard image is malformed"); + operation.candidate_failed = true; + if (operation.result_mime.len > 0) operation.allocator.free(operation.result_mime); + operation.result_mime = &.{}; + }, + error.LimitExceeded => operation.status = .limit_exceeded, + error.Cancelled => operation.status = .cancelled, + error.TimedOut => operation.status = .timed_out, + error.OutOfMemory => { + operation.rememberFailure(.out_of_memory, "Failed to convert Wayland BMP clipboard image"); + operation.status = .failed; + }, + } + operation.wayland_conversion_started = false; + } + + fn requestCancel(operation: *Operation) CancelStatus { + operation.mutex.lock(); + if (operation.status != .pending) { + operation.mutex.unlock(); + return .already_terminal; + } + if (operation.wayland_conversion_started) { + operation.cancel_requested = true; + operation.platform_cancel.store(true, .release); + operation.mutex.unlock(); + return .requested; + } + if (comptime builtin.os.tag == .windows or builtin.os.tag == .macos) { + if (operation.platform_mutation_started.load(.acquire)) { + operation.mutex.unlock(); + return .already_terminal; + } + operation.cancel_requested = true; + operation.platform_cancel.store(true, .release); + if (operation.platform_terminal_request == null) operation.platform_terminal_request = .cancelled; + operation.mutex.unlock(); + if (!operation.service.completeQueuedPlatformOperation(operation, .cancelled)) { + operation.service.wakePlatformWorker(); + } + return .requested; + } + defer operation.mutex.unlock(); + if (operation.x11_write.mutation_dispatched) { + if (operation.x11_write.committed) { + operation.status = if (operation.kind == .write) .written else .cleared; + return .already_terminal; + } + if (operation.service.x11) |x11| x11.abandonMutationConfirmation(&operation.x11_write); + operation.cancel_requested = true; + operation.status = .cancelled; + return .requested; + } + operation.cancel_requested = true; + operation.platform_cancel.store(true, .release); + operation.cleanupTransfer(); + operation.cleanupX11(); + operation.status = .cancelled; + return .requested; + } + + fn poll(operation: *Operation) OperationStatus { + if (!operation.joinCompletedWorker()) return .pending; + operation.mutex.lock(); + if (operation.status != .pending) { + const status = operation.status; + operation.mutex.unlock(); + return status; + } + if (operation.x11_write.committed) { + operation.status = if (operation.kind == .write) .written else .cleared; + const status = operation.status; + operation.mutex.unlock(); + return status; + } + if (comptime builtin.os.tag == .windows or builtin.os.tag == .macos) { + const now_ns = clipboard_clock.nowNs(); + if (platformDeadlineExpired(operation, now_ns) and operation.platform_terminal_request == null and + !operation.platform_mutation_started.load(.acquire)) + { + operation.platform_terminal_request = .timed_out; + operation.platform_cancel.store(true, .release); + } + if (operation.platform_terminal_request) |requested| { + operation.mutex.unlock(); + if (operation.service.completeQueuedPlatformOperation(operation, requested)) return requested; + operation.service.wakePlatformWorker(); + return .pending; + } + operation.mutex.unlock(); + return .pending; + } + if (operation.cancel_requested) { + if (operation.wayland_conversion_started) { + operation.platform_cancel.store(true, .release); + operation.mutex.unlock(); + return .pending; + } + operation.status = .cancelled; + operation.mutex.unlock(); + return .cancelled; + } + const elapsed_ns = clipboard_clock.nowNs() - operation.started_ns; + if (elapsed_ns >= @as(i128, operation.timeout_ms) * std.time.ns_per_ms) { + if (operation.x11_write.mutation_dispatched) { + if (operation.service.x11) |x11| x11.abandonMutationConfirmation(&operation.x11_write); + operation.status = .timed_out; + operation.mutex.unlock(); + return .timed_out; + } + if (operation.wayland_conversion_started) { + operation.platform_cancel.store(true, .release); + operation.mutex.unlock(); + return .pending; + } + operation.cleanupTransfer(); + operation.cleanupX11(); + operation.status = .timed_out; + operation.mutex.unlock(); + return .timed_out; + } + operation.mutex.unlock(); + return operation.service.driveOperation(operation); + } + + fn isReadyToDestroy(operation: *Operation) bool { + operation.mutex.lock(); + const terminal = operation.status != .pending; + operation.mutex.unlock(); + if (!terminal) return false; + return operation.joinCompletedWorker(); + } + + fn joinCompletedWorker(operation: *Operation) bool { + operation.mutex.lock(); + const thread = operation.thread orelse { + operation.mutex.unlock(); + return true; + }; + if (operation.wayland_conversion_started) { + operation.mutex.unlock(); + return false; + } + operation.mutex.unlock(); + if (!tryJoinThread(thread)) return false; + operation.mutex.lock(); + operation.thread = null; + operation.mutex.unlock(); + return true; + } + + fn deinit(operation: *Operation) void { + std.debug.assert(operation.thread == null); + if (operation.request.len > 0) operation.allocator.free(operation.request); + if (operation.result.len > 0) operation.allocator.free(operation.result); + if (operation.result_mime.len > 0) operation.allocator.free(operation.result_mime); + operation.transfer_data.deinit(operation.allocator); + operation.cleanupTransfer(); + operation.cleanupX11(); + operation.allocator.destroy(operation); + } + + fn beginPlatformMutation(operation: *Operation) ?OperationStatus { + operation.mutex.lock(); + defer operation.mutex.unlock(); + if (operation.platform_terminal_request) |requested| return requested; + if (platformDeadlineExpired(operation, clipboard_clock.nowNs())) { + operation.platform_terminal_request = .timed_out; + operation.platform_cancel.store(true, .release); + return .timed_out; + } + operation.platform_mutation_started.store(true, .release); + return null; + } + + fn cleanupTransfer(operation: *Operation) void { + operation.releaseCoreFocus(); + operation.wayland_transfer_format = .direct; + if (comptime builtin.os.tag != .linux) { + operation.transfer_fd = null; + return; + } + if (operation.transfer_fd) |fd| std.posix.close(fd); + operation.transfer_fd = null; + } + + fn releaseCoreFocus(operation: *Operation) void { + if (!operation.wayland_core_focus_acquired) return; + operation.wayland_core_focus_acquired = false; + if (operation.service.wayland) |wayland| wayland.releaseCoreSelection(); + } + + fn rememberFailure(operation: *Operation, code: ErrorCode, diagnostic: []const u8) void { + if (operation.error_code != 0) return; + operation.error_code = @intFromEnum(code); + operation.diagnostic = diagnostic; + } + + fn cleanupX11(operation: *Operation) void { + if (comptime builtin.os.tag != .linux) return; + const x11 = operation.service.x11 orelse return; + x11.cleanupRead(&operation.x11_read); + x11.cleanupWrite(&operation.x11_write); + } +}; + +fn wslOperationUnsupported(libraries: clipboard_linux.Libraries, operation: *const Operation) bool { + return libraries.is_wsl and (operation.selection == .primary or operation.kind == .clear); +} + +const Service = struct { + allocator: Allocator, + max_operations: u32 = OPERATIONS_MAX_DEFAULT, + max_provider_transfers: u32 = PROVIDER_TRANSFERS_MAX_DEFAULT, + libraries: clipboard_linux.Libraries, + wayland: ?*clipboard_wayland.Connection = null, + x11: ?*clipboard_x11.Connection = null, + drain_mechanism: clipboard_linux.Mechanism = .wayland, + wayland_drain_provider: bool = false, + requested_wayland_seat: []u8, + environment_wayland_seat: []u8, + shutting_down: bool = false, + next_mutation_sequence: u64 = 1, + operations: std.ArrayListUnmanaged(*Operation) = .{}, + platform_mutex: std.Thread.Mutex = .{}, + platform_condition: std.Thread.Condition = .{}, + platform_queue: std.ArrayListUnmanaged(*Operation) = .{}, + platform_thread: ?std.Thread = null, + platform_stop: bool = false, + platform_exited: bool = false, + platform_failed: bool = false, + + fn takeMutationSequence(service: *Service) u64 { + const sequence = service.next_mutation_sequence; + std.debug.assert(sequence != std.math.maxInt(u64)); + service.next_mutation_sequence += 1; + return sequence; + } + + fn removeOperation(service: *Service, operation: *Operation) void { + for (service.operations.items, 0..) |candidate, index| { + if (candidate == operation) { + _ = service.operations.swapRemove(index); + return; + } + } + unreachable; + } + + fn enqueuePlatformOperation(service: *Service, operation: *Operation) void { + if (comptime builtin.os.tag != .windows and builtin.os.tag != .macos) return; + service.platform_mutex.lock(); + if (service.platform_failed) { + service.platform_mutex.unlock(); + operation.rememberFailure(.internal, "Native clipboard worker initialization failed"); + operation.status = .failed; + return; + } + std.debug.assert(service.platform_queue.items.len < service.platform_queue.capacity); + service.platform_queue.appendAssumeCapacity(operation); + service.platform_condition.signal(); + service.platform_mutex.unlock(); + } + + fn wakePlatformWorker(service: *Service) void { + if (comptime builtin.os.tag != .windows and builtin.os.tag != .macos) return; + service.platform_mutex.lock(); + service.platform_condition.signal(); + service.platform_mutex.unlock(); + } + + fn completeQueuedPlatformOperation( + service: *Service, + operation: *Operation, + status: OperationStatus, + ) bool { + if (comptime builtin.os.tag != .windows and builtin.os.tag != .macos and !builtin.is_test) return false; + service.platform_mutex.lock(); + var found = false; + for (service.platform_queue.items, 0..) |candidate, index| { + if (candidate != operation) continue; + _ = service.platform_queue.orderedRemove(index); + found = true; + break; + } + if (!found) { + service.platform_mutex.unlock(); + return false; + } + + operation.mutex.lock(); + if (operation.status == .pending) { + operation.platform_terminal_request = status; + operation.platform_cancel.store(true, .release); + operation.status = status; + } + operation.mutex.unlock(); + service.platform_mutex.unlock(); + return true; + } + + fn platformWorker(service: *Service) void { + if (comptime builtin.os.tag == .windows) { + var worker = clipboard_windows.Worker.init() catch { + service.failPlatformWorker(); + return; + }; + defer worker.deinit(); + service.platformWorkerLoop(&worker); + } else if (comptime builtin.os.tag == .macos) { + service.platformWorkerLoop({}); + } else unreachable; + + service.platform_mutex.lock(); + service.platform_exited = true; + service.platform_mutex.unlock(); + } + + fn failPlatformWorker(service: *Service) void { + service.platform_mutex.lock(); + service.platform_failed = true; + while (service.platform_queue.items.len > 0) { + const operation = service.platform_queue.orderedRemove(0); + operation.mutex.lock(); + operation.rememberFailure(.internal, "Native clipboard worker initialization failed"); + operation.status = .failed; + operation.mutex.unlock(); + } + service.platform_exited = true; + service.platform_mutex.unlock(); + } + + fn platformWorkerLoop(service: *Service, worker: anytype) void { + while (true) { + service.platform_mutex.lock(); + while (service.platform_queue.items.len == 0 and !service.platform_stop) { + if (comptime builtin.os.tag == .windows) { + service.platform_condition.timedWait(&service.platform_mutex, 10 * std.time.ns_per_ms) catch {}; + if (service.platform_queue.items.len == 0 and !service.platform_stop) { + service.platform_mutex.unlock(); + _ = worker.pumpMessages(); + service.platform_mutex.lock(); + } + } else { + service.platform_condition.wait(&service.platform_mutex); + } + } + if (service.platform_queue.items.len == 0 and service.platform_stop) { + service.platform_mutex.unlock(); + return; + } + const operation = service.platform_queue.orderedRemove(0); + service.platform_mutex.unlock(); + if (comptime builtin.os.tag == .windows) _ = worker.pumpMessages(); + service.executePlatformOperation(worker, operation); + if (comptime builtin.os.tag == .windows) _ = worker.pumpMessages(); + } + } + + fn executePlatformOperation(service: *Service, worker: anytype, operation: *Operation) void { + operation.mutex.lock(); + const now_ns = clipboard_clock.nowNs(); + const requested = operation.platform_terminal_request orelse + if (platformDeadlineExpired(operation, now_ns)) OperationStatus.timed_out else null; + operation.mutex.unlock(); + if (requested) |status| { + service.publishPlatformResult(operation, status, &.{}, &.{}, 0); + return; + } + if (comptime builtin.os.tag == .windows) { + if (operation.selection == .primary) { + service.publishPlatformResult(operation, .unsupported, &.{}, &.{}, 0); + return; + } + } + + if (comptime builtin.os.tag == .windows) { + const job: clipboard_windows.Job = switch (operation.kind) { + .read => .{ .read = .{ + .request = operation.request, + .max_bytes = operation.max_bytes, + .max_image_pixels = operation.max_image_pixels, + .max_conversion_bytes = operation.max_conversion_bytes, + } }, + .write => .{ .write = operation.request }, + .clear => .clear, + }; + var result = worker.execute(service.allocator, job, .{ + .cancel_requested = &operation.platform_cancel, + .begin_mutation = beginWindowsPlatformMutation, + .mutation_context = operation, + .deadline_ns = operation.started_ns + @as(i128, operation.timeout_ms) * std.time.ns_per_ms, + }); + defer result.deinit(service.allocator); + const status: OperationStatus = switch (result.status) { + .read => .read, + .empty => .empty, + .written => .written, + .cleared => .cleared, + .unsupported => .unsupported, + .cancelled => .cancelled, + .timed_out => .timed_out, + .limit_exceeded => .limit_exceeded, + .invalid_request, .failed => .failed, + }; + service.publishPlatformResult(operation, status, result.mime, result.data, result.error_code); + } else if (comptime builtin.os.tag == .macos) { + if (operation.selection == .primary) { + service.publishPlatformResult(operation, .unsupported, &.{}, &.{}, 0); + return; + } + const job: clipboard_macos.Job = switch (operation.kind) { + .read => .{ .read = .{ + .request = operation.request, + .max_bytes = operation.max_bytes, + .max_image_pixels = operation.max_image_pixels, + .max_conversion_bytes = operation.max_conversion_bytes, + } }, + .write => .{ .write_text = .{ .text = operation.request } }, + .clear => .clear, + }; + var result = clipboard_macos.runJob(service.allocator, job, .{ + .cancel_requested = &operation.platform_cancel, + .begin_mutation = beginMacOSPlatformMutation, + .mutation_context = operation, + .deadline_ns = operation.started_ns + @as(i128, operation.timeout_ms) * std.time.ns_per_ms, + }) catch |err| { + const status: OperationStatus = if (err == error.LimitExceeded) .limit_exceeded else .failed; + service.publishPlatformResult(operation, status, &.{}, &.{}, 0); + return; + }; + defer result.deinit(service.allocator); + switch (result) { + .read => |read| service.publishPlatformResult(operation, .read, read.mime.name(), read.data, 0), + .empty => service.publishPlatformResult(operation, .empty, &.{}, &.{}, 0), + .written => service.publishPlatformResult(operation, .written, &.{}, &.{}, 0), + .cleared => service.publishPlatformResult(operation, .cleared, &.{}, &.{}, 0), + .unsupported => service.publishPlatformResult(operation, .unsupported, &.{}, &.{}, 0), + .cancelled => service.publishPlatformResult(operation, .cancelled, &.{}, &.{}, 0), + .timed_out => service.publishPlatformResult(operation, .timed_out, &.{}, &.{}, 0), + .failed => service.publishPlatformResult(operation, .failed, &.{}, &.{}, 0), + } + } else unreachable; + } + + fn publishPlatformResult( + service: *Service, + operation: *Operation, + platform_status: OperationStatus, + mime: []const u8, + data: []const u8, + error_code: u32, + ) void { + service.publishPlatformResultAt(operation, platform_status, mime, data, error_code, clipboard_clock.nowNs()); + } + + fn publishPlatformResultAt( + service: *Service, + operation: *Operation, + platform_status: OperationStatus, + mime: []const u8, + data: []const u8, + error_code: u32, + now_ns: i128, + ) void { + operation.mutex.lock(); + defer operation.mutex.unlock(); + var status = resolvePlatformStatus(operation, platform_status, now_ns); + if (status == .read) { + operation.result_mime = service.allocator.dupe(u8, mime) catch { + status = .failed; + operation.rememberFailure(.out_of_memory, "Failed to allocate native clipboard MIME result"); + operation.status = status; + return; + }; + operation.result = service.allocator.dupe(u8, data) catch blk: { + service.allocator.free(operation.result_mime); + operation.result_mime = &.{}; + status = .failed; + operation.rememberFailure(.out_of_memory, "Failed to allocate native clipboard result"); + break :blk &.{}; + }; + } + if (status == .failed) { + if (operation.error_code == 0) { + operation.error_code = if (error_code != 0) error_code else @intFromEnum(ErrorCode.internal); + } + if (operation.diagnostic.len == 0) operation.diagnostic = "Native platform clipboard operation failed"; + } + operation.status = status; + } + + fn beginShutdown(service: *Service) void { + if (service.shutting_down) return; + service.shutting_down = true; + for (service.operations.items) |operation| { + _ = operation.requestCancel(); + } + if (comptime builtin.os.tag == .linux) { + if (service.x11) |x11| x11.requestShutdown(); + if (service.wayland) |wayland| wayland.releaseProviders(); + if (service.x11) |x11| x11.releaseProviders(); + } + if (comptime builtin.os.tag == .windows or builtin.os.tag == .macos) { + service.platform_mutex.lock(); + service.platform_stop = true; + service.platform_condition.signal(); + service.platform_mutex.unlock(); + } + } + + fn pollShutdown(service: *Service) ShutdownStatus { + if (!service.shutting_down) return .pending; + for (service.operations.items) |operation| { + if (!operation.isReadyToDestroy()) return .pending; + } + if (comptime builtin.os.tag == .windows or builtin.os.tag == .macos) { + service.platform_mutex.lock(); + const exited = service.platform_exited; + service.platform_mutex.unlock(); + if (!exited) return .pending; + if (service.platform_thread) |thread| { + if (comptime builtin.os.tag == .macos) { + // The worker publishes exited immediately before returning; Mach does not + // reliably expose terminated pthreads as TH_STATE_HALTED. + thread.join(); + } else if (!tryJoinThread(thread)) return .pending; + service.platform_thread = null; + } + } + if (comptime builtin.os.tag == .linux) { + if (service.x11) |x11| if (!x11.shutdownReady()) return .pending; + } + return .ready; + } + + fn deinit(service: *Service) void { + for (service.operations.items) |operation| { + std.debug.assert(operation.thread == null); + handles.invalidate(operation.handle, .clipboard_operation); + operation.deinit(); + } + service.operations.deinit(service.allocator); + service.platform_queue.deinit(service.allocator); + if (comptime builtin.os.tag == .linux) { + if (service.wayland) |wayland| { + wayland.deinit(); + service.allocator.destroy(wayland); + } + if (service.x11) |x11| { + x11.deinit(); + service.allocator.destroy(x11); + } + } + if (service.requested_wayland_seat.len > 0) service.allocator.free(service.requested_wayland_seat); + if (service.environment_wayland_seat.len > 0) service.allocator.free(service.environment_wayland_seat); + service.allocator.destroy(service); + } + + fn driveOperation(service: *Service, operation: *Operation) OperationStatus { + if (comptime builtin.os.tag == .windows or builtin.os.tag == .macos) { + return operation.status; + } + if (comptime builtin.os.tag != .linux) return service.finishOperation(operation, .unsupported); + if (operation.kind == .read and !implementsNativeReadType(operation.request)) { + return service.finishOperation(operation, .unsupported); + } + const libraries = service.libraries; + if (wslOperationUnsupported(libraries, operation)) { + return service.finishOperation(operation, .unsupported); + } + operation.mechanism = operation.mechanism orelse if (libraries.wayland) + .wayland + else if (libraries.x11) + .x11 + else + return service.finishOperation(operation, .unsupported); + return switch (operation.mechanism.?) { + .wayland => service.driveWayland(operation, libraries), + .x11 => service.driveX11(operation), + }; + } + + fn driveWayland( + service: *Service, + operation: *Operation, + libraries: clipboard_linux.Libraries, + ) OperationStatus { + if (service.wayland == null) { + const symbols = clipboard_linux.waylandSymbols() orelse + return service.fallbackWayland(operation, libraries); + const wayland = service.allocator.create(clipboard_wayland.Connection) catch { + operation.rememberFailure(.out_of_memory, "Failed to allocate Wayland clipboard connection"); + return service.finishOperation(operation, .failed); + }; + wayland.* = clipboard_wayland.Connection.init( + service.allocator, + symbols, + service.requested_wayland_seat, + service.environment_wayland_seat, + service.max_provider_transfers, + ); + wayland.allow_core_data_device = libraries.is_wsl; + service.wayland = wayland; + } + return switch (service.wayland.?.drive()) { + .pending => .pending, + .ready => service.driveWaylandOperation(operation), + .unsupported => service.fallbackWayland(operation, libraries), + .failed => service.finishWaylandFailure(operation), + }; + } + + fn fallbackWayland( + service: *Service, + operation: *Operation, + libraries: clipboard_linux.Libraries, + ) OperationStatus { + if (!libraries.x11) return service.finishOperation(operation, .unsupported); + operation.cleanupTransfer(); + operation.mechanism = .x11; + operation.preference_offset = 4; + operation.candidate_failed = false; + operation.implemented_candidate_attempted = false; + return .pending; + } + + fn ensureX11(service: *Service, operation: *Operation) ?OperationStatus { + if (service.x11 == null) { + const symbols = clipboard_linux.xcbSymbols() orelse + return service.finishOperation(operation, .unsupported); + const x11 = service.allocator.create(clipboard_x11.Connection) catch { + operation.rememberFailure(.out_of_memory, "Failed to allocate X11 clipboard connection"); + return service.finishOperation(operation, .failed); + }; + x11.* = clipboard_x11.Connection.init(service.allocator, symbols, service.max_provider_transfers); + service.x11 = x11; + } + return switch (service.x11.?.drive()) { + .pending => .pending, + .ready => null, + .unsupported => service.finishOperation(operation, .unsupported), + .failed => service.finishX11Failure(operation), + }; + } + + fn driveX11(service: *Service, operation: *Operation) OperationStatus { + if (service.ensureX11(operation)) |status| return status; + service.driveX11EventUnit(); + return switch (operation.kind) { + .read => service.driveX11Read(operation), + .write => service.driveX11Write(operation), + .clear => service.driveX11Clear(operation), + }; + } + + fn driveWaylandOperation(service: *Service, operation: *Operation) OperationStatus { + if (comptime builtin.os.tag != .linux) return service.finishOperation(operation, .unsupported); + return switch (operation.kind) { + .read => service.driveWaylandRead(operation), + .write => service.driveWaylandWrite(operation), + .clear => service.driveWaylandClear(operation), + }; + } + + fn driveWaylandWrite(service: *Service, operation: *Operation) OperationStatus { + if (comptime builtin.os.tag != .linux) return service.finishOperation(operation, .unsupported); + if (service.hasEarlierSelectionMutation(operation)) return .pending; + const result = service.wayland.?.publishText(operation.selection == .primary, operation.request); + switch (result) { + .ok, .committed => std.debug.assert(result == .ok or result == .committed), + .pending => unreachable, + .unsupported => return service.fallbackCurrentWayland(operation), + .failed => return service.finishWaylandFailure(operation), + } + operation.request = &.{}; + return service.finishOperation(operation, .written); + } + + fn driveWaylandClear(service: *Service, operation: *Operation) OperationStatus { + if (comptime builtin.os.tag != .linux) return service.finishOperation(operation, .unsupported); + if (service.hasEarlierSelectionMutation(operation)) return .pending; + const result = service.wayland.?.clearSelection(operation.selection == .primary); + switch (result) { + .ok, .committed => std.debug.assert(result == .ok or result == .committed), + .pending => unreachable, + .unsupported => return service.fallbackCurrentWayland(operation), + .failed => return service.finishWaylandFailure(operation), + } + return service.finishOperation(operation, .cleared); + } + + fn driveWaylandRead(service: *Service, operation: *Operation) OperationStatus { + if (comptime builtin.os.tag != .linux) return service.finishOperation(operation, .unsupported); + if (operation.transfer_fd) |fd| { + var buffer: [64 * 1024]u8 = undefined; + const count = std.posix.read(fd, &buffer) catch |err| switch (err) { + error.WouldBlock => return .pending, + else => { + operation.rememberFailure(.wayland_transfer, "Wayland clipboard transfer read failed"); + operation.cleanupTransfer(); + operation.candidate_failed = true; + if (operation.result_mime.len > 0) operation.allocator.free(operation.result_mime); + operation.result_mime = &.{}; + operation.transfer_data.clearRetainingCapacity(); + return .pending; + }, + }; + if (count == 0) { + const transfer_format = operation.wayland_transfer_format; + operation.cleanupTransfer(); + // Zero-byte text is valid clipboard content and stays a successful + // read, matching the macOS and X11 backends. Zero-byte image data + // is invalid and means the offer's source vanished mid-read, so + // only image candidates retry. Wayland cannot distinguish empty + // text from a vanished source; on compositors that skip the nil + // selection update after a clear (Hyprland 0.55), a cleared + // clipboard reads as zero-byte text instead of empty. + const empty_image = operation.transfer_data.items.len == 0 and + !std.ascii.eqlIgnoreCase(operation.result_mime, "text/plain"); + if (empty_image) return service.retryStaleWaylandRead(operation); + return service.completeWaylandRead(operation, transfer_format); + } + const transfer_limit: usize = @intCast(switch (operation.wayland_transfer_format) { + .direct => operation.max_bytes, + .bmp_to_png => operation.max_conversion_bytes, + }); + if (count > transfer_limit -| operation.transfer_data.items.len) { + operation.cleanupTransfer(); + return service.finishOperation(operation, .limit_exceeded); + } + operation.transfer_data.appendSlice(operation.allocator, buffer[0..count]) catch { + operation.cleanupTransfer(); + operation.rememberFailure(.out_of_memory, "Failed to grow Wayland clipboard result"); + return service.finishOperation(operation, .failed); + }; + return .pending; + } + + const primary = operation.selection == .primary; + if (primary and !service.wayland.?.primary_supported) { + return service.fallbackCurrentWayland(operation); + } + if (service.wayland.?.usesCoreDataDevice()) { + if (!operation.wayland_core_focus_acquired) { + const acquired = service.wayland.?.acquireCoreSelection(); + switch (acquired) { + .pending => {}, + .ready => unreachable, + .unsupported => return service.fallbackCurrentWayland(operation), + .failed => return service.finishWaylandFailure(operation), + } + operation.wayland_core_focus_acquired = true; + } + switch (service.wayland.?.coreSelectionProgress()) { + .pending => return .pending, + .ready => {}, + .unsupported => return service.fallbackCurrentWayland(operation), + .failed => return service.finishWaylandFailure(operation), + } + } + // Order every read after selection events emitted before its admission. + if (operation.wayland_barrier_serial == 0) { + operation.wayland_barrier_serial = service.wayland.?.requestSelectionBarrier() orelse + return service.finishWaylandFailure(operation); + } + if (!service.wayland.?.selectionBarrierReached(operation.wayland_barrier_serial)) { + return .pending; + } + const offer = service.wayland.?.currentOffer(primary) orelse { + operation.releaseCoreFocus(); + return service.finishOperation(operation, .empty); + }; + var implemented = false; + var offset = operation.preference_offset; + while (offset < operation.request.len) { + const length = std.mem.readInt(u32, operation.request[offset..][0..4], .little); + offset += 4; + const preferred = operation.request[offset .. offset + length]; + offset += length; + operation.preference_offset = offset; + const preferred_essence = clipboard_wayland.canonicalMimeEssence(preferred) orelse continue; + if (!std.ascii.eqlIgnoreCase(preferred_essence, "text/plain") and + !std.ascii.eqlIgnoreCase(preferred_essence, "image/png")) continue; + implemented = true; + operation.implemented_candidate_attempted = true; + const match = service.wayland.?.offeredMime(offer, preferred) orelse continue; + return service.beginWaylandRead(operation, offer, match.requested, match.offered); + } + operation.releaseCoreFocus(); + return service.finishOperation(operation, waylandReadExhaustionStatus( + implemented or operation.implemented_candidate_attempted, + operation.candidate_failed, + )); + } + + // A zero-byte image transfer means the offer's source vanished, usually + // because the selection changed after the offer was chosen. Restart candidate + // selection behind a fresh barrier a bounded number of times; afterwards the + // preference loop finishes the read as empty. + fn retryStaleWaylandRead(_: *Service, operation: *Operation) OperationStatus { + operation.implemented_candidate_attempted = true; + if (operation.result_mime.len > 0) operation.allocator.free(operation.result_mime); + operation.result_mime = &.{}; + operation.transfer_data.clearRetainingCapacity(); + if (operation.wayland_stale_retry_count < WAYLAND_READ_STALE_RETRY_MAX) { + operation.wayland_stale_retry_count += 1; + operation.wayland_barrier_serial = 0; + operation.preference_offset = 4; + } + return .pending; + } + + fn completeWaylandRead( + service: *Service, + operation: *Operation, + transfer_format: WaylandTransferFormat, + ) OperationStatus { + if (transfer_format == .direct) { + operation.result = operation.transfer_data.toOwnedSlice(operation.allocator) catch { + operation.rememberFailure(.out_of_memory, "Failed to allocate Wayland clipboard result"); + return service.finishOperation(operation, .failed); + }; + return service.finishOperation(operation, .read); + } + + operation.wayland_conversion_started = true; + operation.thread = std.Thread.spawn(.{}, Operation.waylandBmpWorker, .{operation}) catch { + operation.wayland_conversion_started = false; + operation.rememberFailure(.internal, "Failed to start Wayland BMP conversion worker"); + return service.finishOperation(operation, .failed); + }; + return .pending; + } + + fn implementsNativeReadType(request: []const u8) bool { + var offset: usize = 4; + while (offset < request.len) { + const length = std.mem.readInt(u32, request[offset..][0..4], .little); + offset += 4; + const preferred = request[offset .. offset + length]; + offset += length; + const essence = clipboard_wayland.canonicalMimeEssence(preferred) orelse continue; + if (std.ascii.eqlIgnoreCase(essence, "text/plain") or + std.ascii.eqlIgnoreCase(essence, "image/png")) return true; + } + return false; + } + + fn beginWaylandRead( + service: *Service, + operation: *Operation, + offer: *const clipboard_wayland.Offer, + preferred: []const u8, + offered: []const u8, + ) OperationStatus { + if (comptime builtin.os.tag != .linux) return service.finishOperation(operation, .unsupported); + const pipe = std.posix.pipe2(.{ .CLOEXEC = true }) catch { + operation.rememberFailure(.wayland_transfer, "Failed to create Wayland clipboard transfer pipe"); + return service.rememberWaylandReadFailure(operation); + }; + // Keep the transferred write end blocking; only the locally polled read end may return WouldBlock. + const flags = std.posix.fcntl(pipe[0], std.posix.F.GETFL, 0) catch { + std.posix.close(pipe[0]); + std.posix.close(pipe[1]); + operation.rememberFailure(.wayland_transfer, "Failed to configure Wayland clipboard transfer pipe"); + return service.rememberWaylandReadFailure(operation); + }; + const nonblocking: u32 = @bitCast(std.posix.O{ .NONBLOCK = true }); + _ = std.posix.fcntl(pipe[0], std.posix.F.SETFL, flags | nonblocking) catch { + std.posix.close(pipe[0]); + std.posix.close(pipe[1]); + operation.rememberFailure(.wayland_transfer, "Failed to configure Wayland clipboard transfer pipe"); + return service.rememberWaylandReadFailure(operation); + }; + const requested = service.wayland.?.receive(offer, offered, pipe[1]); + std.posix.close(pipe[1]); + if (!requested) { + std.posix.close(pipe[0]); + operation.rememberFailure(.wayland_flush, "Failed to request Wayland clipboard transfer"); + return service.rememberWaylandReadFailure(operation); + } + operation.releaseCoreFocus(); + operation.result_mime = operation.allocator.dupe(u8, preferred) catch { + std.posix.close(pipe[0]); + operation.rememberFailure(.out_of_memory, "Failed to allocate Wayland clipboard MIME result"); + return service.rememberWaylandReadFailure(operation); + }; + operation.wayland_transfer_format = waylandTransferFormat(preferred, offered); + operation.transfer_fd = pipe[0]; + return .pending; + } + + fn rememberWaylandReadFailure(_: *Service, operation: *Operation) OperationStatus { + operation.candidate_failed = true; + return .pending; + } + + fn fallbackCurrentWayland(service: *Service, operation: *Operation) OperationStatus { + return service.fallbackWayland(operation, service.libraries); + } + + fn driveX11EventUnit(service: *Service) void { + const x11 = service.x11 orelse return; + const event = x11.pollEvent() orelse return; + defer std.c.free(event); + if (x11.consumeRetiredTimestampEvent(event)) return; + for (service.operations.items) |candidate| { + if (candidate.mechanism != .x11) continue; + if (candidate.kind == .read and x11.routeReadEvent(&candidate.x11_read, event)) return; + if (candidate.kind != .write and candidate.kind != .clear) continue; + candidate.mutex.lock(); + if (candidate.status != .pending) { + candidate.mutex.unlock(); + continue; + } + if (x11.isMutationTimestampEvent(&candidate.x11_write, event)) { + const terminal: ?OperationStatus = if (candidate.cancel_requested) + .cancelled + else if (platformDeadlineExpired(candidate, clipboard_clock.nowNs())) + .timed_out + else + null; + if (terminal) |status| { + candidate.cleanupX11(); + candidate.status = status; + candidate.mutex.unlock(); + _ = x11.consumeRetiredTimestampEvent(event); + return; + } + } + const routed = x11.routeWriteEvent(&candidate.x11_write, event); + if (routed and candidate.kind == .write and candidate.x11_write.mutation_dispatched) { + candidate.request = &.{}; + } + if (routed and candidate.x11_write.committed) { + candidate.status = if (candidate.kind == .write) .written else .cleared; + } + candidate.mutex.unlock(); + if (routed) return; + } + x11.handleProviderEvent(event); + } + + fn driveX11Read(service: *Service, operation: *Operation) OperationStatus { + const x11 = service.x11.?; + switch (x11.driveRead(&operation.x11_read, &operation.transfer_data, operation.max_bytes)) { + .pending => { + if (operation.x11_read.phase != .idle) return .pending; + }, + .ready => { + const empty_png = operation.result_mime.len > 0 and + std.ascii.eqlIgnoreCase(operation.result_mime, "image/png") and + operation.transfer_data.items.len == 0; + if (!empty_png) { + operation.result = operation.transfer_data.toOwnedSlice(operation.allocator) catch { + x11.cleanupRead(&operation.x11_read); + operation.rememberFailure(.out_of_memory, "Failed to allocate X11 clipboard result"); + return service.finishOperation(operation, .failed); + }; + x11.cleanupRead(&operation.x11_read); + return service.finishOperation(operation, .read); + } + x11.cleanupRead(&operation.x11_read); + operation.transfer_data.clearRetainingCapacity(); + operation.x11_target_index = operation.x11_target_count; + }, + .refused => { + x11.cleanupRead(&operation.x11_read); + operation.x11_target_index += 1; + }, + .limit_exceeded => { + x11.cleanupRead(&operation.x11_read); + return service.finishOperation(operation, .limit_exceeded); + }, + .candidate_failed => { + x11.cleanupRead(&operation.x11_read); + operation.rememberFailure(.x11_transfer, "X11 clipboard property transfer failed"); + operation.candidate_failed = true; + operation.transfer_data.clearRetainingCapacity(); + operation.x11_target_index += 1; + return .pending; + }, + .out_of_memory => { + x11.cleanupRead(&operation.x11_read); + operation.error_code = @intFromEnum(ErrorCode.out_of_memory); + operation.diagnostic = "Failed to allocate X11 clipboard result"; + return service.finishOperation(operation, .failed); + }, + .failed => { + x11.cleanupRead(&operation.x11_read); + return service.finishX11Failure(operation); + }, + } + + while (true) { + if (operation.x11_target_index < operation.x11_target_count) { + operation.x11_read.phase = .refused; + if (!x11.beginRead( + &operation.x11_read, + operation.selection == .primary, + operation.x11_targets[operation.x11_target_index], + operation.max_bytes, + )) { + x11.cleanupRead(&operation.x11_read); + operation.rememberFailure(.x11_flush, "Failed to request X11 clipboard conversion"); + return service.finishOperation(operation, .failed); + } + return .pending; + } + if (operation.result_mime.len > 0) { + operation.allocator.free(operation.result_mime); + operation.result_mime = &.{}; + } + if (operation.preference_offset >= operation.request.len) { + x11.cleanupRead(&operation.x11_read); + return service.finishOperation( + operation, + if (operation.candidate_failed) + .failed + else if (operation.implemented_candidate_attempted) + .empty + else + .unsupported, + ); + } + const length = std.mem.readInt(u32, operation.request[operation.preference_offset..][0..4], .little); + operation.preference_offset += 4; + const preferred = operation.request[operation.preference_offset .. operation.preference_offset + length]; + operation.preference_offset += length; + const targets = x11.targetAtoms(preferred, &operation.x11_targets); + if (targets.len == 0) continue; + operation.implemented_candidate_attempted = true; + operation.result_mime = operation.allocator.dupe(u8, preferred) catch { + x11.cleanupRead(&operation.x11_read); + operation.rememberFailure(.out_of_memory, "Failed to allocate X11 clipboard MIME result"); + return service.finishOperation(operation, .failed); + }; + operation.x11_target_count = @intCast(targets.len); + operation.x11_target_index = 0; + } + } + + fn driveX11Write(service: *Service, operation: *Operation) OperationStatus { + const x11 = service.x11.?; + if (service.hasEarlierSelectionMutation(operation)) return .pending; + if (operation.x11_write.selection == 0 and !operation.x11_write.committed and !operation.x11_write.failed) { + return switch (x11.beginWrite( + &operation.x11_write, + operation.selection == .primary, + operation.request, + )) { + .pending => .pending, + .unsupported => service.finishOperation(operation, .unsupported), + .failed => service.finishX11Failure(operation), + .ok, .committed => unreachable, + }; + } + return switch (x11.driveWrite(&operation.x11_write)) { + .pending => .pending, + .ok, .committed => blk: { + operation.request = &.{}; + break :blk service.finishOperation(operation, .written); + }, + .unsupported => service.finishOperation(operation, .unsupported), + .failed => service.finishX11Failure(operation), + }; + } + + fn driveX11Clear(service: *Service, operation: *Operation) OperationStatus { + const x11 = service.x11.?; + if (service.hasEarlierSelectionMutation(operation)) return .pending; + if (operation.x11_write.selection == 0 and !operation.x11_write.committed and !operation.x11_write.failed) { + return switch (x11.beginClear(&operation.x11_write, operation.selection == .primary)) { + .pending => .pending, + .unsupported => service.finishOperation(operation, .unsupported), + .failed => service.finishX11Failure(operation), + .ok, .committed => unreachable, + }; + } + return switch (x11.driveWrite(&operation.x11_write)) { + .pending => .pending, + .ok, .committed => service.finishOperation(operation, .cleared), + .unsupported => service.finishOperation(operation, .unsupported), + .failed => service.finishX11Failure(operation), + }; + } + + fn finishX11Failure(service: *Service, operation: *Operation) OperationStatus { + const failure = if (service.x11) |x11| x11.takeFailure() else .protocol; + switch (failure) { + .none, .connection, .protocol, .atom => operation.rememberFailure(.x11_protocol, "X11 clipboard protocol failed"), + .flush => operation.rememberFailure(.x11_flush, "X11 clipboard output flush failed"), + .provider => operation.rememberFailure(.x11_provider, "X11 clipboard provider failed"), + } + operation.cleanupX11(); + return service.finishOperation(operation, .failed); + } + + fn hasEarlierSelectionMutation(service: *const Service, operation: *const Operation) bool { + for (service.operations.items) |candidate| { + if (candidate.kind != .write and candidate.kind != .clear) continue; + if (candidate.selection != operation.selection) continue; + candidate.mutex.lock(); + const pending = candidate.status == .pending; + candidate.mutex.unlock(); + if (!pending) continue; + if (candidate.mutation_sequence < operation.mutation_sequence) return true; + } + return false; + } + + fn finishOperation(_: *Service, operation: *Operation, status: OperationStatus) OperationStatus { + if (status == .failed and operation.error_code == 0) { + operation.rememberFailure(.internal, "Native clipboard operation failed"); + } + operation.mutex.lock(); + defer operation.mutex.unlock(); + if (operation.status == .pending) operation.status = status; + return operation.status; + } + + fn finishWaylandFailure(service: *Service, operation: *Operation) OperationStatus { + const failure = if (service.wayland) |wayland| wayland.takeFailure() else .protocol; + switch (failure) { + .none, .protocol => operation.rememberFailure(.wayland_protocol, "Wayland clipboard protocol failed"), + .dispatch => operation.rememberFailure(.wayland_dispatch, "Wayland clipboard event dispatch failed"), + .flush => operation.rememberFailure(.wayland_flush, "Wayland clipboard output flush failed"), + .provider => operation.rememberFailure(.wayland_provider, "Wayland clipboard provider publication failed"), + } + operation.cleanupTransfer(); + return service.finishOperation(operation, .failed); + } +}; + +fn platformDeadlineExpired(operation: *const Operation, now_ns: i128) bool { + return now_ns - operation.started_ns >= @as(i128, operation.timeout_ms) * std.time.ns_per_ms; +} + +fn beginWindowsPlatformMutation(context: ?*anyopaque) ?clipboard_windows.Status { + const operation: *Operation = @ptrCast(@alignCast(context orelse return .invalid_request)); + const status = operation.beginPlatformMutation() orelse return null; + return switch (status) { + .cancelled => .cancelled, + .timed_out => .timed_out, + else => .failed, + }; +} + +fn beginMacOSPlatformMutation(context: ?*anyopaque) ?clipboard_macos.Status { + const operation: *Operation = @ptrCast(@alignCast(context orelse return .failed)); + const status = operation.beginPlatformMutation() orelse return null; + return switch (status) { + .cancelled => .cancelled, + .timed_out => .timed_out, + else => .failed, + }; +} + +fn resolvePlatformStatus( + operation: *const Operation, + platform_status: OperationStatus, + now_ns: i128, +) OperationStatus { + if ((platform_status == .written or platform_status == .cleared) and + operation.platform_mutation_started.load(.acquire)) return platform_status; + if (operation.platform_terminal_request) |requested| return requested; + if (!operation.platform_mutation_started.load(.acquire) and platformDeadlineExpired(operation, now_ns)) return .timed_out; + return platform_status; +} + +fn waylandReadExhaustionStatus(implemented: bool, failed: bool) OperationStatus { + if (failed) return .failed; + return if (implemented) .empty else .unsupported; +} + +fn waylandTransferFormat(preferred: []const u8, offered: []const u8) WaylandTransferFormat { + const preferred_essence = clipboard_wayland.canonicalMimeEssence(preferred) orelse return .direct; + const offered_essence = clipboard_wayland.canonicalMimeEssence(offered) orelse return .direct; + if (std.ascii.eqlIgnoreCase(preferred_essence, "image/png") and + std.ascii.eqlIgnoreCase(offered_essence, "image/bmp")) return .bmp_to_png; + return .direct; +} + +fn erasePtr(pointer: anytype) *anyopaque { + return @ptrCast(pointer); +} + +fn acquireService(handle: Handle) ?*Service { + return handles.acquire(handle, .clipboard_service, Service); +} + +fn acquireOperation(handle: Handle) ?*Operation { + return handles.acquire(handle, .clipboard_operation, Operation); +} + +fn sliceFromPointer(pointer: ?[*]const u8, length: u32) ?[]const u8 { + if (length == 0) return ""; + const valid_pointer = pointer orelse return null; + return valid_pointer[0..@as(usize, length)]; +} + +pub fn createService( + allocator: Allocator, + max_operations: u32, + max_provider_transfers: u32, + wayland_seat_pointer: ?[*]const u8, + wayland_seat_length: u32, +) Handle { + if (max_operations == 0 or max_provider_transfers == 0) return 0; + const configured_seat = sliceFromPointer(wayland_seat_pointer, wayland_seat_length) orelse return 0; + clipboard_clock.init() catch return 0; + var requested_wayland_seat: []u8 = &.{}; + var environment_wayland_seat: []u8 = &.{}; + const libraries: clipboard_linux.Libraries = switch (builtin.os.tag) { + .linux => blk: { + var env = std.process.getEnvMap(allocator) catch return 0; + defer env.deinit(); + if (configured_seat.len > 0) { + requested_wayland_seat = allocator.dupe(u8, configured_seat) catch return 0; + } else if (env.get("XDG_SEAT")) |seat| { + if (seat.len > 0) environment_wayland_seat = allocator.dupe(u8, seat) catch return 0; + } + break :blk clipboard_linux.initialize(clipboard_linux.Environment.detect(&env)); + }, + else => .{}, + }; + const service = allocator.create(Service) catch { + if (requested_wayland_seat.len > 0) allocator.free(requested_wayland_seat); + if (environment_wayland_seat.len > 0) allocator.free(environment_wayland_seat); + return 0; + }; + service.* = .{ + .allocator = allocator, + .max_operations = max_operations, + .max_provider_transfers = max_provider_transfers, + .libraries = libraries, + .requested_wayland_seat = requested_wayland_seat, + .environment_wayland_seat = environment_wayland_seat, + }; + if (comptime builtin.os.tag == .windows or builtin.os.tag == .macos) { + service.platform_queue.ensureTotalCapacity(allocator, max_operations) catch { + service.deinit(); + return 0; + }; + } + const service_handle = handles.insert(.clipboard_service, erasePtr(service)) catch { + service.deinit(); + return 0; + }; + if (comptime builtin.os.tag == .windows or builtin.os.tag == .macos) { + service.platform_thread = std.Thread.spawn(.{}, Service.platformWorker, .{service}) catch { + handles.invalidate(service_handle, .clipboard_service); + service.deinit(); + return 0; + }; + } + return service_handle; +} + +fn parseSelection(value: u8) ?Selection { + return std.meta.intToEnum(Selection, value) catch null; +} + +fn validateReadRequest(request: []const u8) bool { + if (request.len < @sizeOf(u32)) return false; + const count = std.mem.readInt(u32, request[0..4], .little); + if (count == 0 or count > READ_MIME_COUNT_MAX) return false; + + var offset: usize = 4; + var index: u32 = 0; + while (index < count) : (index += 1) { + if (request.len - offset < @sizeOf(u32)) return false; + const length = std.mem.readInt(u32, request[offset..][0..4], .little); + offset += 4; + if (length == 0 or length > READ_MIME_ESSENCE_BYTES_MAX or length > request.len - offset) return false; + offset += length; + } + return offset == request.len; +} + +fn startImmediateOperation( + service: *Service, + service_handle: Handle, + kind: OperationKind, + request: []const u8, + timeout_ms: u32, + max_bytes: u32, + max_image_pixels: u32, + max_conversion_bytes: u32, + selection: Selection, + out_handle: *Handle, +) StartStatus { + const owned_request = service.allocator.dupe(u8, request) catch return .out_of_memory; + const operation = service.allocator.create(Operation) catch { + if (owned_request.len > 0) service.allocator.free(owned_request); + return .out_of_memory; + }; + operation.* = .{ + .allocator = service.allocator, + .service = service, + .kind = kind, + .request = owned_request, + .status = if (timeout_ms == 0) .timed_out else .pending, + .timeout_ms = timeout_ms, + .started_ns = clipboard_clock.nowNs(), + .max_bytes = max_bytes, + .max_image_pixels = max_image_pixels, + .max_conversion_bytes = max_conversion_bytes, + .selection = selection, + .mutation_sequence = if (kind == .write or kind == .clear) service.takeMutationSequence() else 0, + }; + const operation_handle = handles.insertOwnedChild( + .clipboard_operation, + erasePtr(operation), + service_handle, + ) catch { + if (owned_request.len > 0) service.allocator.free(owned_request); + service.allocator.destroy(operation); + return .out_of_memory; + }; + operation.handle = operation_handle; + service.operations.append(service.allocator, operation) catch { + handles.invalidate(operation_handle, .clipboard_operation); + if (owned_request.len > 0) service.allocator.free(owned_request); + service.allocator.destroy(operation); + return .out_of_memory; + }; + if (comptime builtin.os.tag == .windows or builtin.os.tag == .macos) { + if (operation.status == .pending) service.enqueuePlatformOperation(operation); + } + out_handle.* = operation_handle; + return .ok; +} + +pub fn startReadOperation( + service_handle: Handle, + request_pointer: ?[*]const u8, + request_length: u32, + selection_value: u8, + max_bytes: u32, + max_image_pixels: u32, + max_conversion_bytes: u32, + timeout_ms: u32, + out_operation_handle: ?*Handle, +) StartStatus { + const out_handle = out_operation_handle orelse return .invalid_argument; + out_handle.* = 0; + const service = acquireService(service_handle) orelse return .invalid_service; + if (service.shutting_down) return .shutting_down; + if (service.operations.items.len >= service.max_operations) return .limit_exceeded; + const selection = parseSelection(selection_value) orelse return .invalid_argument; + const request = sliceFromPointer(request_pointer, request_length) orelse return .invalid_argument; + if (!validateReadRequest(request)) return .invalid_argument; + return startImmediateOperation( + service, + service_handle, + .read, + request, + timeout_ms, + max_bytes, + max_image_pixels, + max_conversion_bytes, + selection, + out_handle, + ); +} + +pub fn startWriteOperation( + service_handle: Handle, + text_pointer: ?[*]const u8, + text_length: u32, + selection_value: u8, + timeout_ms: u32, + out_operation_handle: ?*Handle, +) StartStatus { + const out_handle = out_operation_handle orelse return .invalid_argument; + out_handle.* = 0; + const service = acquireService(service_handle) orelse return .invalid_service; + if (service.shutting_down) return .shutting_down; + if (service.operations.items.len >= service.max_operations) return .limit_exceeded; + const selection = parseSelection(selection_value) orelse return .invalid_argument; + const text = sliceFromPointer(text_pointer, text_length) orelse return .invalid_argument; + if (text.len == 0 or std.mem.indexOfScalar(u8, text, 0) != null) return .invalid_argument; + return startImmediateOperation(service, service_handle, .write, text, timeout_ms, 0, 0, 0, selection, out_handle); +} + +pub fn startClearOperation( + service_handle: Handle, + selection_value: u8, + timeout_ms: u32, + out_operation_handle: ?*Handle, +) StartStatus { + const out_handle = out_operation_handle orelse return .invalid_argument; + out_handle.* = 0; + const service = acquireService(service_handle) orelse return .invalid_service; + if (service.shutting_down) return .shutting_down; + if (service.operations.items.len >= service.max_operations) return .limit_exceeded; + const selection = parseSelection(selection_value) orelse return .invalid_argument; + return startImmediateOperation(service, service_handle, .clear, "", timeout_ms, 0, 0, 0, selection, out_handle); +} + +pub fn pollOperation(operation_handle: Handle) OperationStatus { + const operation = acquireOperation(operation_handle) orelse return .invalid_handle; + return operation.poll(); +} + +pub fn cancelOperation(operation_handle: Handle) CancelStatus { + const operation = acquireOperation(operation_handle) orelse return .invalid_handle; + return operation.requestCancel(); +} + +fn resultSlice(operation: *Operation, kind: ResultKind) ?[]const u8 { + operation.mutex.lock(); + defer operation.mutex.unlock(); + return switch (kind) { + .mime => if (operation.status == .read) operation.result_mime else null, + .data => if (operation.status == .read) operation.result else null, + .diagnostic => if (operation.status == .failed) operation.diagnostic else null, + }; +} + +fn resultLength(operation_handle: Handle, out_length: ?*u32, kind: ResultKind) CopyStatus { + const operation = acquireOperation(operation_handle) orelse return .invalid_handle; + const output = out_length orelse return .invalid_argument; + const result = resultSlice(operation, kind) orelse return .invalid_state; + output.* = @intCast(result.len); + return .ok; +} + +fn resultCopy( + operation_handle: Handle, + output_pointer: ?[*]u8, + output_capacity: u32, + kind: ResultKind, +) CopyStatus { + const operation = acquireOperation(operation_handle) orelse return .invalid_handle; + const result = resultSlice(operation, kind) orelse return .invalid_state; + if (output_capacity < result.len) return .buffer_too_small; + if (result.len == 0) return .ok; + const output = output_pointer orelse return .invalid_argument; + @memcpy(output[0..result.len], result); + return .ok; +} + +pub fn resultMimeLength(operation_handle: Handle, out_length: ?*u32) CopyStatus { + return resultLength(operation_handle, out_length, .mime); +} + +pub fn resultMimeCopy(operation_handle: Handle, output_pointer: ?[*]u8, output_capacity: u32) CopyStatus { + return resultCopy(operation_handle, output_pointer, output_capacity, .mime); +} + +pub fn resultDataLength(operation_handle: Handle, out_length: ?*u32) CopyStatus { + return resultLength(operation_handle, out_length, .data); +} + +pub fn resultDataCopy(operation_handle: Handle, output_pointer: ?[*]u8, output_capacity: u32) CopyStatus { + return resultCopy(operation_handle, output_pointer, output_capacity, .data); +} + +pub fn resultErrorCode(operation_handle: Handle, out_error_code: ?*u32) CopyStatus { + const operation = acquireOperation(operation_handle) orelse return .invalid_handle; + const output = out_error_code orelse return .invalid_argument; + operation.mutex.lock(); + defer operation.mutex.unlock(); + if (operation.status != .failed) return .invalid_state; + output.* = operation.error_code; + return .ok; +} + +pub fn resultDiagnosticLength(operation_handle: Handle, out_length: ?*u32) CopyStatus { + return resultLength(operation_handle, out_length, .diagnostic); +} + +pub fn resultDiagnosticCopy(operation_handle: Handle, output_pointer: ?[*]u8, output_capacity: u32) CopyStatus { + return resultCopy(operation_handle, output_pointer, output_capacity, .diagnostic); +} + +pub fn destroyOperation(operation_handle: Handle) DestroyStatus { + const operation = acquireOperation(operation_handle) orelse return .invalid_handle; + if (!operation.isReadyToDestroy()) return .not_ready; + operation.service.removeOperation(operation); + handles.invalidate(operation_handle, .clipboard_operation); + operation.deinit(); + return .destroyed; +} + +pub fn beginServiceShutdown(service_handle: Handle) ShutdownStatus { + const service = acquireService(service_handle) orelse return .invalid_handle; + service.beginShutdown(); + return .pending; +} + +pub fn pollServiceShutdown(service_handle: Handle) ShutdownStatus { + const service = acquireService(service_handle) orelse return .invalid_handle; + return service.pollShutdown(); +} + +pub fn destroyService(service_handle: Handle) DestroyStatus { + const service = acquireService(service_handle) orelse return .invalid_handle; + if (service.pollShutdown() != .ready) return .not_ready; + handles.invalidate(service_handle, .clipboard_service); + service.deinit(); + return .destroyed; +} + +pub fn drainService(service_handle: Handle) u8 { + const service = acquireService(service_handle) orelse return 2; + if (service.shutting_down) return 0; + if (comptime builtin.os.tag != .linux) return 0; + var active = false; + const mechanism = service.drain_mechanism; + service.drain_mechanism = if (mechanism == .wayland) .x11 else .wayland; + switch (mechanism) { + .wayland => if (service.wayland) |wayland| { + if (service.wayland_drain_provider) { + active = wayland.driveProviderUnit(); + } else { + switch (wayland.drive()) { + .failed => wayland.retireProviders(), + else => {}, + } + } + service.wayland_drain_provider = !service.wayland_drain_provider; + }, + .x11 => if (service.x11) |x11| { + service.driveX11EventUnit(); + active = x11.driveProviderUnit(); + }, + } + if (service.wayland) |wayland| active = active or wayland.hasWork(); + if (service.x11) |x11| active = active or x11.hasWork(); + return if (active) 1 else 0; +} + +fn destroyTestService(service: Handle) void { + _ = beginServiceShutdown(service); + var status = pollServiceShutdown(service); + var attempts: u32 = 0; + while (status == .pending and attempts < 2_000) : (attempts += 1) { + std.Thread.sleep(std.time.ns_per_ms); + status = pollServiceShutdown(service); + } + if (status != .ready) @panic("clipboard service shutdown exceeded 2 seconds"); + _ = destroyService(service); +} + +test "clipboard status values are stable" { + try std.testing.expectEqual(@as(u8, 0), @intFromEnum(OperationStatus.pending)); + try std.testing.expectEqual(@as(u8, 1), @intFromEnum(OperationStatus.read)); + try std.testing.expectEqual(@as(u8, 10), @intFromEnum(OperationStatus.invalid_handle)); + try std.testing.expectEqual(@as(u8, 1), @intFromEnum(CopyStatus.buffer_too_small)); + try std.testing.expectEqual(@as(u8, 2), @intFromEnum(DestroyStatus.invalid_handle)); +} + +test "clipboard service preserves a configured native operation limit" { + const operation_limit = 2; + const service = createService(std.testing.allocator, operation_limit, PROVIDER_TRANSFERS_MAX_DEFAULT, null, 0); + try std.testing.expect(service != 0); + defer destroyTestService(service); + + var operations: [operation_limit]Handle = @splat(0); + for (&operations) |*operation| { + try std.testing.expectEqual(StartStatus.ok, startClearOperation(service, 0, 0, operation)); + } + var excess_operation: Handle = 99; + try std.testing.expectEqual(StartStatus.limit_exceeded, startClearOperation(service, 0, 0, &excess_operation)); + try std.testing.expectEqual(@as(Handle, 0), excess_operation); + for (operations) |operation| { + try std.testing.expectEqual(DestroyStatus.destroyed, destroyOperation(operation)); + } +} + +test "clipboard cancellation and service shutdown are asynchronous and isolated" { + if (comptime builtin.os.tag == .windows or builtin.os.tag == .macos) return error.SkipZigTest; + const first_service = createService(std.testing.allocator, 1, PROVIDER_TRANSFERS_MAX_DEFAULT, null, 0); + const second_service = createService(std.testing.allocator, 1, PROVIDER_TRANSFERS_MAX_DEFAULT, null, 0); + try std.testing.expect(first_service != 0); + try std.testing.expect(second_service != 0); + acquireService(first_service).?.libraries = .{}; + acquireService(second_service).?.libraries = .{}; + defer destroyTestService(second_service); + + var first_operation: Handle = 0; + var second_operation: Handle = 0; + const read_request = [_]u8{ 1, 0, 0, 0, 10, 0, 0, 0 } ++ "text/plain".*; + try std.testing.expectEqual( + StartStatus.ok, + startReadOperation(first_service, &read_request, read_request.len, 0, 1024, 1024, 4096, 100, &first_operation), + ); + try std.testing.expectEqual( + StartStatus.ok, + startReadOperation(second_service, &read_request, read_request.len, 0, 1024, 1024, 4096, 100, &second_operation), + ); + try std.testing.expectEqual(CancelStatus.requested, cancelOperation(first_operation)); + try std.testing.expectEqual(CancelStatus.already_terminal, cancelOperation(first_operation)); + _ = beginServiceShutdown(first_service); + var first_shutdown = pollServiceShutdown(first_service); + var first_shutdown_attempts: u32 = 0; + while (first_shutdown == .pending and first_shutdown_attempts < 2_000) : (first_shutdown_attempts += 1) { + std.Thread.sleep(std.time.ns_per_ms); + first_shutdown = pollServiceShutdown(first_service); + } + try std.testing.expectEqual(ShutdownStatus.ready, first_shutdown); + try std.testing.expectEqual(DestroyStatus.destroyed, destroyService(first_service)); + try std.testing.expectEqual(OperationStatus.invalid_handle, pollOperation(first_operation)); + + try std.testing.expectEqual(OperationStatus.unsupported, pollOperation(second_operation)); + try std.testing.expectEqual(DestroyStatus.destroyed, destroyOperation(second_operation)); +} + +test "clipboard production operations validate requests and remain unsupported until platform protocols exist" { + if (comptime builtin.os.tag == .windows or builtin.os.tag == .macos) return error.SkipZigTest; + const service = createService(std.testing.allocator, 3, PROVIDER_TRANSFERS_MAX_DEFAULT, null, 0); + try std.testing.expect(service != 0); + acquireService(service).?.libraries = .{}; + defer destroyTestService(service); + + var operation: Handle = 0; + const malformed_read = [_]u8{ 1, 0, 0, 0, 4, 0, 0, 0, 't' }; + try std.testing.expectEqual( + StartStatus.invalid_argument, + startReadOperation(service, &malformed_read, malformed_read.len, 0, 1024, 1024, 4096, 100, &operation), + ); + try std.testing.expectEqual(@as(Handle, 0), operation); + + const read_request = [_]u8{ 1, 0, 0, 0, 10, 0, 0, 0 } ++ "text/plain".*; + try std.testing.expectEqual( + StartStatus.ok, + startReadOperation(service, &read_request, read_request.len, 0, 1024, 1024, 4096, 100, &operation), + ); + try std.testing.expectEqual(OperationStatus.unsupported, pollOperation(operation)); + try std.testing.expectEqual(DestroyStatus.destroyed, destroyOperation(operation)); + try std.testing.expectEqual(OperationStatus.invalid_handle, pollOperation(operation)); + try std.testing.expectEqual(DestroyStatus.invalid_handle, destroyOperation(operation)); + + try std.testing.expectEqual( + StartStatus.invalid_argument, + startWriteOperation(service, "bad\x00text", 8, 0, 100, &operation), + ); + try std.testing.expectEqual( + StartStatus.ok, + startClearOperation(service, 1, 0, &operation), + ); + try std.testing.expectEqual(OperationStatus.timed_out, pollOperation(operation)); + try std.testing.expectEqual(DestroyStatus.destroyed, destroyOperation(operation)); +} + +test "clipboard read request validation enforces exact native MIME bounds" { + var exact: [4 + 64 * 5]u8 = @splat(0); + std.mem.writeInt(u32, exact[0..4], 64, .little); + var offset: usize = 4; + var index: u32 = 0; + while (index < 64) : (index += 1) { + std.mem.writeInt(u32, exact[offset..][0..4], 1, .little); + exact[offset + 4] = 'x'; + offset += 5; + } + try std.testing.expect(validateReadRequest(&exact)); + + var too_many: [4 + 65 * 5]u8 = @splat(0); + std.mem.writeInt(u32, too_many[0..4], 65, .little); + offset = 4; + index = 0; + while (index < 65) : (index += 1) { + std.mem.writeInt(u32, too_many[offset..][0..4], 1, .little); + too_many[offset + 4] = 'x'; + offset += 5; + } + try std.testing.expect(!validateReadRequest(&too_many)); + + var exact_essence: [4 + 4 + 255]u8 = @splat('x'); + std.mem.writeInt(u32, exact_essence[0..4], 1, .little); + std.mem.writeInt(u32, exact_essence[4..8], 255, .little); + try std.testing.expect(validateReadRequest(&exact_essence)); + + var oversized_essence: [4 + 4 + 256]u8 = @splat('x'); + std.mem.writeInt(u32, oversized_essence[0..4], 1, .little); + std.mem.writeInt(u32, oversized_essence[4..8], 256, .little); + try std.testing.expect(!validateReadRequest(&oversized_essence)); +} + +test "clipboard over-limit read request returns invalid argument before allocation" { + const service = createService(std.testing.allocator, 1, 1, null, 0); + try std.testing.expect(service != 0); + defer destroyTestService(service); + var request: [4 + 4 + 256]u8 = @splat('x'); + std.mem.writeInt(u32, request[0..4], 1, .little); + std.mem.writeInt(u32, request[4..8], 256, .little); + var operation: Handle = 99; + + try std.testing.expectEqual( + StartStatus.invalid_argument, + startReadOperation(service, &request, request.len, 0, 1, 1, 1, 1, &operation), + ); + try std.testing.expectEqual(@as(Handle, 0), operation); + try std.testing.expectEqual(@as(usize, 0), acquireService(service).?.operations.items.len); +} + +test "clipboard Wayland transfer format uses canonical offered essence" { + try std.testing.expectEqual( + WaylandTransferFormat.bmp_to_png, + waylandTransferFormat("image/png", "image/bmp; version=3"), + ); + try std.testing.expectEqual( + WaylandTransferFormat.direct, + waylandTransferFormat("image/png", "image/png; version=3"), + ); +} + +test "clipboard zero-byte final image candidate exhausts as empty" { + try std.testing.expectEqual(OperationStatus.empty, waylandReadExhaustionStatus(true, false)); + try std.testing.expectEqual(OperationStatus.unsupported, waylandReadExhaustionStatus(false, false)); + try std.testing.expectEqual(OperationStatus.failed, waylandReadExhaustionStatus(true, true)); +} + +test "clipboard WSL policy rejects primary and clear without blocking standard read and write" { + const libraries: clipboard_linux.Libraries = .{ .wayland = true, .x11 = true, .is_wsl = true }; + const TestCase = struct { + kind: OperationKind, + selection: Selection, + unsupported: bool, + }; + const cases = [_]TestCase{ + .{ .kind = .read, .selection = .primary, .unsupported = true }, + .{ .kind = .write, .selection = .primary, .unsupported = true }, + .{ .kind = .clear, .selection = .primary, .unsupported = true }, + .{ .kind = .clear, .selection = .clipboard, .unsupported = true }, + .{ .kind = .read, .selection = .clipboard, .unsupported = false }, + .{ .kind = .write, .selection = .clipboard, .unsupported = false }, + }; + for (cases) |case| { + var operation: Operation = .{ + .allocator = std.testing.allocator, + .service = undefined, + .kind = case.kind, + .selection = case.selection, + }; + try std.testing.expectEqual(case.unsupported, wslOperationUnsupported(libraries, &operation)); + } +} + +test "clipboard Wayland BMP transfer converts to PNG and releases source bytes" { + if (comptime builtin.os.tag != .linux) return error.SkipZigTest; + try clipboard_clock.init(); + var service: Service = .{ + .allocator = std.testing.allocator, + .libraries = .{}, + .requested_wayland_seat = &.{}, + .environment_wayland_seat = &.{}, + }; + var operation: Operation = .{ + .allocator = std.testing.allocator, + .service = &service, + .kind = .read, + .max_bytes = 1024, + .max_image_pixels = 1, + .max_conversion_bytes = 1024, + .timeout_ms = 1000, + .started_ns = clipboard_clock.nowNs(), + }; + const operation_handle = try handles.insert(.clipboard_operation, erasePtr(&operation)); + defer handles.invalidate(operation_handle, .clipboard_operation); + defer { + if (operation.result.len > 0) std.testing.allocator.free(operation.result); + operation.transfer_data.deinit(std.testing.allocator); + } + var bmp: [58]u8 = @splat(0); + bmp[0..2].* = "BM".*; + std.mem.writeInt(u32, bmp[2..6], bmp.len, .little); + std.mem.writeInt(u32, bmp[10..14], 54, .little); + std.mem.writeInt(u32, bmp[14..18], 40, .little); + std.mem.writeInt(i32, bmp[18..22], 1, .little); + std.mem.writeInt(i32, bmp[22..26], 1, .little); + std.mem.writeInt(u16, bmp[26..28], 1, .little); + std.mem.writeInt(u16, bmp[28..30], 24, .little); + std.mem.writeInt(u32, bmp[34..38], 4, .little); + bmp[54..58].* = .{ 0, 0, 255, 0 }; + try operation.transfer_data.appendSlice(std.testing.allocator, &bmp); + + try std.testing.expectEqual( + OperationStatus.pending, + service.completeWaylandRead(&operation, .bmp_to_png), + ); + var status = operation.poll(); + var attempts: u32 = 0; + while (status == .pending and attempts < 2_000) : (attempts += 1) { + std.Thread.sleep(std.time.ns_per_ms); + status = operation.poll(); + } + try std.testing.expectEqual(OperationStatus.read, status); + try std.testing.expect(operation.isReadyToDestroy()); + var length: u32 = 0; + try std.testing.expectEqual(CopyStatus.ok, resultDataLength(operation_handle, &length)); + try std.testing.expectEqual(@as(u32, @intCast(operation.result.len)), length); + var too_small = [_]u8{0xaa} ** 7; + try std.testing.expectEqual(CopyStatus.buffer_too_small, resultDataCopy(operation_handle, &too_small, too_small.len)); + try std.testing.expectEqualSlices(u8, &([_]u8{0xaa} ** 7), &too_small); + var output: [1024]u8 = undefined; + try std.testing.expectEqual(CopyStatus.ok, resultDataCopy(operation_handle, &output, @intCast(operation.result.len))); + try std.testing.expectEqualStrings("\x89PNG\r\n\x1a\n", output[0..8]); + try std.testing.expectEqualStrings("\x89PNG\r\n\x1a\n", operation.result[0..8]); + try std.testing.expectEqual(@as(usize, 0), operation.transfer_data.items.len); + try std.testing.expectEqual(@as(usize, 0), operation.transfer_data.capacity); +} + +test "clipboard failed operations always publish a portable diagnostic" { + var operation: Operation = .{ + .allocator = std.testing.allocator, + .service = undefined, + .kind = .read, + }; + var service: Service = undefined; + + try std.testing.expectEqual(OperationStatus.failed, service.finishOperation(&operation, .failed)); + try std.testing.expect(operation.error_code != 0); + try std.testing.expect(operation.diagnostic.len > 0); +} + +test "clipboard queued platform terminal requests complete before worker execution" { + var service: Service = .{ + .allocator = std.testing.allocator, + .libraries = .{}, + .requested_wayland_seat = &.{}, + .environment_wayland_seat = &.{}, + }; + var first: Operation = .{ + .allocator = std.testing.allocator, + .service = &service, + .kind = .read, + }; + var second: Operation = .{ + .allocator = std.testing.allocator, + .service = &service, + .kind = .read, + }; + var queue_storage: [2]*Operation = undefined; + service.platform_queue = .{ .items = queue_storage[0..0], .capacity = queue_storage.len }; + service.platform_queue.appendAssumeCapacity(&first); + service.platform_queue.appendAssumeCapacity(&second); + + try std.testing.expect(service.completeQueuedPlatformOperation(&second, .cancelled)); + try std.testing.expectEqual(OperationStatus.cancelled, second.status); + try std.testing.expectEqual(@as(usize, 1), service.platform_queue.items.len); + try std.testing.expect(service.platform_queue.items[0] == &first); +} + +test "clipboard platform result resolution enforces deadline before late read success" { + var service: Service = .{ + .allocator = std.testing.allocator, + .libraries = .{}, + .requested_wayland_seat = &.{}, + .environment_wayland_seat = &.{}, + }; + var operation: Operation = .{ + .allocator = std.testing.allocator, + .service = &service, + .kind = .read, + .timeout_ms = 5, + .started_ns = 100, + }; + + service.publishPlatformResultAt( + &operation, + .read, + "text/plain", + "late result", + 0, + 100 + 5 * std.time.ns_per_ms, + ); + + try std.testing.expectEqual(OperationStatus.timed_out, operation.status); + try std.testing.expectEqual(@as(usize, 0), operation.result.len); + try std.testing.expectEqual(@as(usize, 0), operation.result_mime.len); +} + +test "clipboard platform result preserves out of memory after data allocation failure" { + var failing = std.testing.FailingAllocator.init(std.testing.allocator, .{ .fail_index = 1 }); + const allocator = failing.allocator(); + var service: Service = .{ + .allocator = allocator, + .libraries = .{}, + .requested_wayland_seat = &.{}, + .environment_wayland_seat = &.{}, + }; + var operation: Operation = .{ + .allocator = allocator, + .service = &service, + .kind = .read, + .timeout_ms = 100, + }; + + service.publishPlatformResultAt(&operation, .read, "text/plain", "data", 999, 0); + + try std.testing.expectEqual(OperationStatus.failed, operation.status); + try std.testing.expectEqual(@intFromEnum(ErrorCode.out_of_memory), operation.error_code); +} + +test "clipboard cancellation recorded before a platform mutation wins over late success" { + var operation: Operation = .{ + .allocator = std.testing.allocator, + .service = undefined, + .kind = .write, + .timeout_ms = 100, + .started_ns = 100, + .platform_terminal_request = .cancelled, + }; + + try std.testing.expectEqual( + OperationStatus.cancelled, + resolvePlatformStatus(&operation, .written, 100 + std.time.ns_per_ms), + ); +} + +test "clipboard platform mutation commit wins over later cancellation" { + var operation: Operation = .{ + .allocator = std.testing.allocator, + .service = undefined, + .kind = .write, + .timeout_ms = 100, + .started_ns = 100, + .platform_terminal_request = .cancelled, + }; + operation.platform_mutation_started.store(true, .release); + + try std.testing.expectEqual( + OperationStatus.written, + resolvePlatformStatus(&operation, .written, 100 + std.time.ns_per_ms), + ); +} + +test "clipboard platform mutation callbacks mark the shared operation state" { + try clipboard_clock.init(); + var operation: Operation = .{ + .allocator = std.testing.allocator, + .service = undefined, + .kind = .write, + .timeout_ms = 100, + .started_ns = clipboard_clock.nowNs(), + }; + + try std.testing.expect(beginWindowsPlatformMutation(&operation) == null); + try std.testing.expect(operation.platform_mutation_started.load(.acquire)); + + operation.platform_mutation_started.store(false, .release); + try std.testing.expect(beginMacOSPlatformMutation(&operation) == null); + try std.testing.expect(operation.platform_mutation_started.load(.acquire)); +} + +test "clipboard X11 cancellation and timeout settle pending ownership confirmation" { + if (comptime builtin.os.tag != .linux) return error.SkipZigTest; + try clipboard_clock.init(); + var symbols: clipboard_linux.XcbSymbols = undefined; + symbols.xcb_discard_reply = testX11DiscardReply; + var fake_connection: u8 = 0; + var x11 = clipboard_x11.Connection.init(std.testing.allocator, &symbols, 1); + x11.connection = @ptrCast(&fake_connection); + var service: Service = .{ + .allocator = std.testing.allocator, + .libraries = .{}, + .requested_wayland_seat = &.{}, + .environment_wayland_seat = &.{}, + .x11 = &x11, + }; + var operation: Operation = .{ + .allocator = std.testing.allocator, + .service = &service, + .kind = .write, + .x11_write = .{ .owner_cookie = .{ .sequence = 1 }, .mutation_dispatched = true }, + }; + + try std.testing.expectEqual(CancelStatus.requested, operation.requestCancel()); + try std.testing.expectEqual(OperationStatus.cancelled, operation.status); + try std.testing.expect(operation.cancel_requested); + try std.testing.expect(operation.x11_write.owner_cookie == null); + + operation.status = .pending; + operation.cancel_requested = false; + operation.timeout_ms = 1; + operation.started_ns = 0; + operation.x11_write = .{ .owner_cookie = .{ .sequence = 2 }, .mutation_dispatched = true }; + try std.testing.expectEqual(OperationStatus.timed_out, operation.poll()); + try std.testing.expect(operation.x11_write.owner_cookie == null); +} + +test "clipboard mutation ordering is selection-scoped when operation storage is reordered" { + var service: Service = undefined; + var earlier: Operation = .{ + .allocator = std.testing.allocator, + .service = &service, + .kind = .write, + .selection = .clipboard, + .mutation_sequence = 2, + }; + var later: Operation = .{ + .allocator = std.testing.allocator, + .service = &service, + .kind = .clear, + .selection = .primary, + .mutation_sequence = 3, + }; + var storage = [_]*Operation{ &later, &earlier }; + service.operations = .{ .items = &storage, .capacity = storage.len }; + + try std.testing.expect(!service.hasEarlierSelectionMutation(&later)); + try std.testing.expect(!service.hasEarlierSelectionMutation(&earlier)); + + later.selection = .clipboard; + try std.testing.expect(service.hasEarlierSelectionMutation(&later)); +} + +test "clipboard expired X11 mutation cannot commit while draining timestamp events" { + if (comptime builtin.os.tag != .linux) return error.SkipZigTest; + try clipboard_clock.init(); + var symbols: clipboard_linux.XcbSymbols = undefined; + symbols.xcb_poll_for_event = testX11PollTimestampEvent; + symbols.xcb_destroy_window = testX11DestroyWindow; + symbols.xcb_set_selection_owner = testX11SetSelectionOwner; + symbols.xcb_flush = testX11Flush; + var fake_connection: u8 = 0; + var x11 = clipboard_x11.Connection.init(std.testing.allocator, &symbols, 1); + x11.connection = @ptrCast(&fake_connection); + x11.phase = .ready; + x11.output_ready_override = true; + x11.owner_window = 9; + x11.atom_values[10] = 110; + var service: Service = .{ + .allocator = std.testing.allocator, + .libraries = .{}, + .requested_wayland_seat = &.{}, + .environment_wayland_seat = &.{}, + .x11 = &x11, + }; + var operation: Operation = .{ + .allocator = std.testing.allocator, + .service = &service, + .kind = .clear, + .selection = .primary, + .mechanism = .x11, + .timeout_ms = 1, + .started_ns = clipboard_clock.nowNs() - 2 * std.time.ns_per_ms, + .x11_write = .{ + .clear = true, + .selection = clipboard_x11.ATOM_PRIMARY, + .waiting_timestamp = true, + .timestamp_window = 22, + }, + }; + var storage = [_]*Operation{&operation}; + service.operations = .{ .items = &storage, .capacity = storage.len }; + test_x11_set_owner_count = 0; + + service.driveX11EventUnit(); + + try std.testing.expectEqual(OperationStatus.timed_out, operation.status); + try std.testing.expectEqual(@as(u32, 0), test_x11_set_owner_count); + try std.testing.expect(!operation.x11_write.mutation_dispatched); +} + +test "clipboard Wayland BMP worker cancellation beats late conversion publication" { + try clipboard_clock.init(); + var service: Service = .{ + .allocator = std.testing.allocator, + .libraries = .{}, + .requested_wayland_seat = &.{}, + .environment_wayland_seat = &.{}, + .shutting_down = true, + }; + var operation: Operation = .{ + .allocator = std.testing.allocator, + .service = &service, + .kind = .read, + .wayland_conversion_started = true, + .cancel_requested = true, + .max_bytes = 1024, + .timeout_ms = 1000, + .started_ns = clipboard_clock.nowNs(), + }; + var bmp: [58]u8 = @splat(0); + bmp[0..2].* = "BM".*; + std.mem.writeInt(u32, bmp[2..6], bmp.len, .little); + std.mem.writeInt(u32, bmp[10..14], 54, .little); + std.mem.writeInt(u32, bmp[14..18], 40, .little); + std.mem.writeInt(i32, bmp[18..22], 1, .little); + std.mem.writeInt(i32, bmp[22..26], 1, .little); + std.mem.writeInt(u16, bmp[26..28], 1, .little); + std.mem.writeInt(u16, bmp[28..30], 24, .little); + std.mem.writeInt(u32, bmp[34..38], 4, .little); + bmp[54..58].* = .{ 0, 0, 255, 0 }; + try operation.transfer_data.appendSlice(std.testing.allocator, &bmp); + + Operation.waylandBmpWorker(&operation); + + try std.testing.expectEqual(OperationStatus.cancelled, operation.status); + try std.testing.expect(!operation.wayland_conversion_started); + try std.testing.expectEqual(@as(usize, 0), operation.result.len); +} + +test "clipboard failed core selection progress releases operation focus" { + if (comptime builtin.os.tag != .linux) return error.SkipZigTest; + var symbols: clipboard_linux.WaylandSymbols = undefined; + var wayland = clipboard_wayland.Connection.init(std.testing.allocator, &symbols, "", "", 1); + wayland.phase = .ready; + wayland.core_data_device = true; + wayland.core_focus_users = 1; + var service: Service = .{ + .allocator = std.testing.allocator, + .libraries = .{ .wayland = true, .x11 = true, .is_wsl = true }, + .wayland = &wayland, + .requested_wayland_seat = &.{}, + .environment_wayland_seat = &.{}, + }; + var request: [18]u8 = @splat(0); + std.mem.writeInt(u32, request[0..4], 1, .little); + std.mem.writeInt(u32, request[4..8], 10, .little); + request[8..18].* = "text/plain".*; + var operation: Operation = .{ + .allocator = std.testing.allocator, + .service = &service, + .kind = .read, + .request = &request, + .mechanism = .wayland, + }; + + try std.testing.expectEqual(OperationStatus.failed, service.driveWaylandRead(&operation)); + try std.testing.expectEqual(clipboard_linux.Mechanism.wayland, operation.mechanism.?); + try std.testing.expect(!operation.wayland_core_focus_acquired); + try std.testing.expectEqual(@as(u32, 1), wayland.core_focus_users); +} + +test "clipboard Wayland fallback transitions to X11 only when available" { + var service: Service = .{ + .allocator = std.testing.allocator, + .libraries = .{ .wayland = true, .x11 = true }, + .requested_wayland_seat = &.{}, + .environment_wayland_seat = &.{}, + }; + var operation: Operation = .{ + .allocator = std.testing.allocator, + .service = &service, + .kind = .write, + }; + + try std.testing.expectEqual(OperationStatus.pending, service.fallbackWayland(&operation, service.libraries)); + try std.testing.expectEqual(clipboard_linux.Mechanism.x11, operation.mechanism.?); + + operation.mechanism = .wayland; + service.libraries.x11 = false; + try std.testing.expectEqual(OperationStatus.unsupported, service.fallbackWayland(&operation, service.libraries)); + try std.testing.expectEqual(clipboard_linux.Mechanism.wayland, operation.mechanism.?); +} + +test "clipboard X11 candidate failure advances to the next compatible target" { + var symbols: clipboard_linux.XcbSymbols = undefined; + var fake_connection: u8 = 0; + var x11 = clipboard_x11.Connection.init(std.testing.allocator, &symbols, 1); + x11.connection = @ptrCast(&fake_connection); + var service: Service = .{ + .allocator = std.testing.allocator, + .libraries = .{}, + .requested_wayland_seat = &.{}, + .environment_wayland_seat = &.{}, + .x11 = &x11, + }; + var operation: Operation = .{ + .allocator = std.testing.allocator, + .service = &service, + .kind = .read, + .x11_read = .{ .phase = .failed }, + .x11_target_count = 2, + }; + try operation.transfer_data.appendSlice(std.testing.allocator, "partial"); + defer operation.transfer_data.deinit(std.testing.allocator); + + try std.testing.expectEqual(OperationStatus.pending, service.driveX11Read(&operation)); + try std.testing.expectEqual(@as(u8, 1), operation.x11_target_index); + try std.testing.expect(operation.candidate_failed); + try std.testing.expectEqual(@as(usize, 0), operation.transfer_data.items.len); +} + +test "clipboard final X11 refusal cleans read state before publication" { + var symbols: clipboard_linux.XcbSymbols = undefined; + symbols.xcb_delete_property = testX11DeleteProperty; + symbols.xcb_destroy_window = testX11DestroyWindow; + var fake_connection: u8 = 0; + var x11 = clipboard_x11.Connection.init(std.testing.allocator, &symbols, 1); + x11.connection = @ptrCast(&fake_connection); + var service: Service = .{ + .allocator = std.testing.allocator, + .libraries = .{}, + .x11 = &x11, + .requested_wayland_seat = &.{}, + .environment_wayland_seat = &.{}, + }; + var request = [_]u8{ 0, 0, 0, 0 }; + var operation: Operation = .{ + .allocator = std.testing.allocator, + .service = &service, + .kind = .read, + .request = &request, + .implemented_candidate_attempted = true, + .x11_read = .{ .phase = .refused, .window = 42 }, + .x11_target_count = 1, + }; + + try std.testing.expectEqual(OperationStatus.empty, service.driveX11Read(&operation)); + try std.testing.expectEqual(clipboard_x11.ReadState{}, operation.x11_read); +} + +test "clipboard shutdown cancels unconfirmed X11 mutations before releasing providers" { + if (comptime builtin.os.tag != .linux) return error.SkipZigTest; + var symbols: clipboard_linux.XcbSymbols = undefined; + symbols.xcb_discard_reply = testX11DiscardReply; + var fake_connection: u8 = 0; + var x11 = clipboard_x11.Connection.init(std.testing.allocator, &symbols, 1); + x11.connection = @ptrCast(&fake_connection); + const provider = try std.testing.allocator.create(clipboard_x11.Provider); + provider.* = .{ .selection = 1, .data = &.{} }; + x11.providers[0] = provider; + x11.primary_provider = provider; + var service: Service = .{ + .allocator = std.testing.allocator, + .libraries = .{}, + .requested_wayland_seat = &.{}, + .environment_wayland_seat = &.{}, + .x11 = &x11, + }; + var operation: Operation = .{ + .allocator = std.testing.allocator, + .service = &service, + .kind = .write, + .x11_write = .{ + .selection = 1, + .owner_cookie = .{ .sequence = 1 }, + .mutation_dispatched = true, + }, + }; + var storage = [_]*Operation{&operation}; + service.operations = .{ .items = &storage, .capacity = storage.len }; + + service.beginShutdown(); + + try std.testing.expectEqual(OperationStatus.cancelled, operation.status); + try std.testing.expect(operation.x11_write.provider == null); + try std.testing.expect(x11.providers[0] == null); +} + +fn testX11DiscardReply(_: *clipboard_linux.XcbConnection, _: u32) callconv(.c) void {} + +var test_x11_set_owner_count: u32 = 0; + +fn testX11PollTimestampEvent(_: *clipboard_linux.XcbConnection) callconv(.c) ?*clipboard_linux.XcbGenericEvent { + const memory = std.c.malloc(@sizeOf(clipboard_linux.XcbPropertyNotifyEvent)) orelse unreachable; + const event: *clipboard_linux.XcbPropertyNotifyEvent = @ptrCast(@alignCast(memory)); + event.* = .{ + .response_type = 28, + .pad0 = 0, + .sequence = 0, + .window = 22, + .atom = 110, + .time = 7, + .state = 0, + .pad1 = .{0} ** 3, + }; + return @ptrCast(event); +} + +fn testX11DestroyWindow(_: *clipboard_linux.XcbConnection, _: u32) callconv(.c) clipboard_linux.XcbCookie { + return .{ .sequence = 1 }; +} + +fn testX11DeleteProperty(_: *clipboard_linux.XcbConnection, _: u32, _: u32) callconv(.c) clipboard_linux.XcbCookie { + return .{ .sequence = 1 }; +} + +fn testX11SetSelectionOwner( + _: *clipboard_linux.XcbConnection, + _: u32, + _: u32, + _: u32, +) callconv(.c) clipboard_linux.XcbCookie { + test_x11_set_owner_count += 1; + return .{ .sequence = 1 }; +} + +fn testX11Flush(_: *clipboard_linux.XcbConnection) callconv(.c) c_int { + return 1; +} diff --git a/packages/core/src/zig/clipboard/linux.zig b/packages/core/src/zig/clipboard/linux.zig new file mode 100644 index 0000000000..d910f64903 --- /dev/null +++ b/packages/core/src/zig/clipboard/linux.zig @@ -0,0 +1,465 @@ +const std = @import("std"); + +pub const Environment = struct { + is_wsl: bool, + has_wayland_display: bool, + has_x11_display: bool, + + pub fn fromMap(env: *const std.process.EnvMap) Environment { + return .{ + .is_wsl = env.get("WSL_DISTRO_NAME") != null or env.get("WSL_INTEROP") != null, + .has_wayland_display = hasNonEmptyValue(env, "WAYLAND_DISPLAY") or hasNonEmptyValue(env, "WAYLAND_SOCKET"), + .has_x11_display = hasNonEmptyValue(env, "DISPLAY"), + }; + } + + pub fn detect(env: *const std.process.EnvMap) Environment { + var result = fromMap(env); + if (!result.is_wsl) { + const uts = std.posix.uname(); + result.is_wsl = isWslKernelRelease(std.mem.sliceTo(&uts.release, 0)); + } + return result; + } +}; + +pub const Libraries = struct { + wayland: bool = false, + x11: bool = false, + is_wsl: bool = false, +}; + +pub const Mechanism = enum { wayland, x11 }; + +pub const WlDisplay = opaque {}; +pub const WlProxy = opaque {}; +pub const WlInterface = extern struct { + name: [*:0]const u8, + version: c_int, + method_count: c_int, + methods: ?[*]const WlMessage, + event_count: c_int, + events: ?[*]const WlMessage, +}; +pub const WlMessage = extern struct { + name: [*:0]const u8, + signature: [*:0]const u8, + types: ?[*]const ?*const WlInterface, +}; +pub const WlArgument = extern union { + i: i32, + u: u32, + f: i32, + s: ?[*:0]const u8, + o: ?*WlProxy, + n: u32, + a: ?*anyopaque, + h: i32, +}; + +pub const XcbConnection = opaque {}; +pub const XcbAuthInfo = extern struct { + name_length: c_int, + name: [*]u8, + data_length: c_int, + data: [*]u8, +}; +pub const XcbGenericEvent = extern struct { + response_type: u8, + pad0: u8, + sequence: u16, + pad: [7]u32, + full_sequence: u32, +}; +pub const XcbGenericError = extern struct { + response_type: u8, + error_code: u8, + sequence: u16, + resource_id: u32, + minor_code: u16, + major_code: u8, + pad0: u8, + pad: [5]u32, + full_sequence: u32, +}; +pub const XcbCookie = extern struct { sequence: u32 }; +pub const XcbInternAtomReply = extern struct { + response_type: u8, + pad0: u8, + sequence: u16, + length: u32, + atom: u32, +}; +pub const XcbGetSelectionOwnerReply = extern struct { + response_type: u8, + pad0: u8, + sequence: u16, + length: u32, + owner: u32, + pad1: [20]u8, +}; +pub const XcbGetPropertyReply = extern struct { + response_type: u8, + format: u8, + sequence: u16, + length: u32, + atom_type: u32, + bytes_after: u32, + value_length: u32, + pad0: [12]u8, +}; +pub const XcbSetup = extern struct { + status: u8, + pad0: u8, + protocol_major_version: u16, + protocol_minor_version: u16, + length: u16, + release_number: u32, + resource_id_base: u32, + resource_id_mask: u32, + motion_buffer_size: u32, + vendor_length: u16, + maximum_request_length: u16, + roots_length: u8, + pixmap_formats_length: u8, + image_byte_order: u8, + bitmap_format_bit_order: u8, + bitmap_format_scanline_unit: u8, + bitmap_format_scanline_pad: u8, + min_keycode: u8, + max_keycode: u8, + pad1: [4]u8, +}; +pub const XcbScreen = extern struct { + root: u32, + default_colormap: u32, + white_pixel: u32, + black_pixel: u32, + current_input_masks: u32, + width_in_pixels: u16, + height_in_pixels: u16, + width_in_millimeters: u16, + height_in_millimeters: u16, + min_installed_maps: u16, + max_installed_maps: u16, + root_visual: u32, + backing_stores: u8, + save_unders: u8, + root_depth: u8, + allowed_depths_length: u8, +}; +pub const XcbScreenIterator = extern struct { + data: *XcbScreen, + remaining: c_int, + index: c_int, +}; +pub const XcbPropertyNotifyEvent = extern struct { + response_type: u8, + pad0: u8, + sequence: u16, + window: u32, + atom: u32, + time: u32, + state: u8, + pad1: [3]u8, +}; +pub const XcbSelectionClearEvent = extern struct { + response_type: u8, + pad0: u8, + sequence: u16, + time: u32, + owner: u32, + selection: u32, +}; +pub const XcbSelectionRequestEvent = extern struct { + response_type: u8, + pad0: u8, + sequence: u16, + time: u32, + owner: u32, + requestor: u32, + selection: u32, + target: u32, + property: u32, +}; +pub const XcbSelectionNotifyEvent = extern struct { + response_type: u8, + pad0: u8, + sequence: u16, + time: u32, + requestor: u32, + selection: u32, + target: u32, + property: u32, +}; + +const LibraryKind = enum { wayland, x11 }; +pub const WaylandSymbols = struct { + wl_display_connect: *const fn (?[*:0]const u8) callconv(.c) ?*WlDisplay, + wl_display_disconnect: *const fn (*WlDisplay) callconv(.c) void, + wl_display_get_fd: *const fn (*WlDisplay) callconv(.c) c_int, + // Deliberately require Wayland 1.25 rather than bulk-dispatching an unbounded event queue. + wl_display_dispatch_pending_single: *const fn (*WlDisplay) callconv(.c) c_int, + wl_display_get_error: *const fn (*WlDisplay) callconv(.c) c_int, + wl_display_set_max_buffer_size: *const fn (*WlDisplay, usize) callconv(.c) void, + wl_display_flush: *const fn (*WlDisplay) callconv(.c) c_int, + wl_display_prepare_read: *const fn (*WlDisplay) callconv(.c) c_int, + wl_display_read_events: *const fn (*WlDisplay) callconv(.c) c_int, + wl_display_cancel_read: *const fn (*WlDisplay) callconv(.c) void, + wl_proxy_marshal_array_flags: *const fn (*WlProxy, u32, ?*const WlInterface, u32, u32, [*]WlArgument) callconv(.c) ?*WlProxy, + wl_proxy_add_listener: *const fn (*WlProxy, [*]const *const anyopaque, ?*anyopaque) callconv(.c) c_int, + wl_proxy_destroy: *const fn (*WlProxy) callconv(.c) void, + wl_proxy_get_version: *const fn (*WlProxy) callconv(.c) u32, + wl_registry_interface: *const WlInterface, + wl_callback_interface: *const WlInterface, + wl_seat_interface: *const WlInterface, + wl_compositor_interface: *const WlInterface, + wl_surface_interface: *const WlInterface, + wl_shm_interface: *const WlInterface, + wl_shm_pool_interface: *const WlInterface, + wl_buffer_interface: *const WlInterface, + wl_shell_interface: *const WlInterface, + wl_shell_surface_interface: *const WlInterface, + wl_keyboard_interface: *const WlInterface, + wl_data_device_manager_interface: *const WlInterface, + wl_data_device_interface: *const WlInterface, +}; + +pub const XcbSymbols = struct { + xcb_connect_to_fd: *const fn (c_int, ?*XcbAuthInfo) callconv(.c) ?*XcbConnection, + xcb_connection_has_error: *const fn (*XcbConnection) callconv(.c) c_int, + xcb_disconnect: *const fn (*XcbConnection) callconv(.c) void, + xcb_get_file_descriptor: *const fn (*XcbConnection) callconv(.c) c_int, + xcb_get_setup: *const fn (*XcbConnection) callconv(.c) *const XcbSetup, + xcb_setup_roots_iterator: *const fn (*const XcbSetup) callconv(.c) XcbScreenIterator, + xcb_screen_next: *const fn (*XcbScreenIterator) callconv(.c) void, + xcb_poll_for_event: *const fn (*XcbConnection) callconv(.c) ?*XcbGenericEvent, + xcb_poll_for_reply: *const fn (*XcbConnection, u32, *?*anyopaque, *?*XcbGenericError) callconv(.c) c_int, + xcb_discard_reply: *const fn (*XcbConnection, u32) callconv(.c) void, + xcb_request_check: *const fn (*XcbConnection, XcbCookie) callconv(.c) ?*XcbGenericError, + xcb_flush: *const fn (*XcbConnection) callconv(.c) c_int, + xcb_generate_id: *const fn (*XcbConnection) callconv(.c) u32, + xcb_create_window: *const fn (*XcbConnection, u8, u32, u32, i16, i16, u16, u16, u16, u16, u32, u32, ?*const anyopaque) callconv(.c) XcbCookie, + xcb_change_window_attributes: *const fn (*XcbConnection, u32, u32, ?*const anyopaque) callconv(.c) XcbCookie, + xcb_destroy_window: *const fn (*XcbConnection, u32) callconv(.c) XcbCookie, + xcb_intern_atom: *const fn (*XcbConnection, u8, u16, [*]const u8) callconv(.c) XcbCookie, + xcb_get_property: *const fn (*XcbConnection, u8, u32, u32, u32, u32, u32) callconv(.c) XcbCookie, + xcb_change_property: *const fn (*XcbConnection, u8, u32, u32, u32, u8, u32, ?*const anyopaque) callconv(.c) XcbCookie, + xcb_change_property_checked: *const fn (*XcbConnection, u8, u32, u32, u32, u8, u32, ?*const anyopaque) callconv(.c) XcbCookie, + xcb_delete_property: *const fn (*XcbConnection, u32, u32) callconv(.c) XcbCookie, + xcb_convert_selection: *const fn (*XcbConnection, u32, u32, u32, u32, u32) callconv(.c) XcbCookie, + xcb_set_selection_owner: *const fn (*XcbConnection, u32, u32, u32) callconv(.c) XcbCookie, + xcb_get_selection_owner: *const fn (*XcbConnection, u32) callconv(.c) XcbCookie, + xcb_send_event: *const fn (*XcbConnection, u8, u32, u32, [*]const u8) callconv(.c) XcbCookie, +}; + +fn CachedLibrary(comptime Symbols: type) type { + return struct { + library: ?std.DynLib = null, + symbols: ?Symbols = null, + }; +} + +var cache_mutex: std.Thread.Mutex = .{}; +var wayland_cache: CachedLibrary(WaylandSymbols) = .{}; +var xcb_cache: CachedLibrary(XcbSymbols) = .{}; + +pub fn initialize(env: Environment) Libraries { + return selectLibraries(env, ProductionLoader{}); +} + +pub fn waylandSymbols() ?*const WaylandSymbols { + cache_mutex.lock(); + defer cache_mutex.unlock(); + if (wayland_cache.symbols) |*symbols| return symbols; + return null; +} + +pub fn xcbSymbols() ?*const XcbSymbols { + cache_mutex.lock(); + defer cache_mutex.unlock(); + if (xcb_cache.symbols) |*symbols| return symbols; + return null; +} + +fn selectLibraries(env: Environment, loader: anytype) Libraries { + var libraries: Libraries = .{ .is_wsl = env.is_wsl }; + if (env.has_wayland_display) libraries.wayland = loader.load(.wayland); + if (env.has_x11_display) libraries.x11 = loader.load(.x11); + return libraries; +} + +const ProductionLoader = struct { + fn load(_: ProductionLoader, kind: LibraryKind) bool { + cache_mutex.lock(); + defer cache_mutex.unlock(); + + return switch (kind) { + .wayland => loadCachedLibrary(WaylandSymbols, &wayland_cache, "libwayland-client.so.0"), + .x11 => loadCachedLibrary(XcbSymbols, &xcb_cache, "libxcb.so.1"), + }; + } +}; + +fn loadCachedLibrary( + comptime Symbols: type, + cache: *CachedLibrary(Symbols), + name: []const u8, +) bool { + if (cache.symbols != null) return true; + + var library = std.DynLib.open(name) catch return false; + const symbols = loadSymbols(Symbols, &library) orelse { + library.close(); + return false; + }; + cache.library = library; + cache.symbols = symbols; + return true; +} + +fn loadSymbols(comptime Symbols: type, library: *std.DynLib) ?Symbols { + var symbols: Symbols = undefined; + inline for (@typeInfo(Symbols).@"struct".fields) |field| { + @field(symbols, field.name) = library.lookup(field.type, field.name) orelse return null; + } + return symbols; +} + +fn hasNonEmptyValue(env: *const std.process.EnvMap, name: []const u8) bool { + const value = env.get(name) orelse return false; + return value.len > 0; +} + +fn isWslKernelRelease(release: []const u8) bool { + return std.ascii.indexOfIgnoreCase(release, "microsoft") != null or + std.ascii.indexOfIgnoreCase(release, "wsl") != null; +} + +const FakeLoader = struct { + wayland_available: bool = false, + x11_available: bool = false, + wayland_attempts: u32 = 0, + x11_attempts: u32 = 0, + + fn load(self: *FakeLoader, kind: LibraryKind) bool { + return switch (kind) { + .wayland => blk: { + self.wayland_attempts += 1; + break :blk self.wayland_available; + }, + .x11 => blk: { + self.x11_attempts += 1; + break :blk self.x11_available; + }, + }; + } +}; + +fn expectLibraries(libraries: Libraries, wayland: bool, x11: bool) !void { + try std.testing.expectEqual(wayland, libraries.wayland); + try std.testing.expectEqual(x11, libraries.x11); +} + +test "clipboard linux routing loads WSLg libraries and represents headless WSL" { + var loader: FakeLoader = .{ .wayland_available = true, .x11_available = true }; + const wslg_libraries = selectLibraries(.{ + .is_wsl = true, + .has_wayland_display = true, + .has_x11_display = true, + }, &loader); + + try std.testing.expect(wslg_libraries.is_wsl); + try std.testing.expect(wslg_libraries.wayland); + try std.testing.expect(wslg_libraries.x11); + try std.testing.expectEqual(@as(u32, 1), loader.wayland_attempts); + try std.testing.expectEqual(@as(u32, 1), loader.x11_attempts); + + loader.wayland_attempts = 0; + loader.x11_attempts = 0; + const headless_libraries = selectLibraries(.{ + .is_wsl = true, + .has_wayland_display = false, + .has_x11_display = false, + }, &loader); + try std.testing.expect(headless_libraries.is_wsl); + try std.testing.expect(!headless_libraries.wayland); + try std.testing.expect(!headless_libraries.x11); + try std.testing.expectEqual(@as(u32, 0), loader.wayland_attempts); + try std.testing.expectEqual(@as(u32, 0), loader.x11_attempts); +} + +test "clipboard linux routing loads only libraries for applicable displays" { + var headless_loader: FakeLoader = .{ .wayland_available = true, .x11_available = true }; + try expectLibraries(selectLibraries(.{ + .is_wsl = false, + .has_wayland_display = false, + .has_x11_display = false, + }, &headless_loader), false, false); + try std.testing.expectEqual(@as(u32, 0), headless_loader.wayland_attempts); + try std.testing.expectEqual(@as(u32, 0), headless_loader.x11_attempts); + + var wayland_loader: FakeLoader = .{ .wayland_available = true }; + try expectLibraries(selectLibraries(.{ + .is_wsl = false, + .has_wayland_display = true, + .has_x11_display = false, + }, &wayland_loader), true, false); + try std.testing.expectEqual(@as(u32, 1), wayland_loader.wayland_attempts); + try std.testing.expectEqual(@as(u32, 0), wayland_loader.x11_attempts); + + var x11_loader: FakeLoader = .{ .x11_available = true }; + try expectLibraries(selectLibraries(.{ + .is_wsl = false, + .has_wayland_display = false, + .has_x11_display = true, + }, &x11_loader), false, true); + try std.testing.expectEqual(@as(u32, 0), x11_loader.wayland_attempts); + try std.testing.expectEqual(@as(u32, 1), x11_loader.x11_attempts); +} + +test "clipboard linux routing preserves independent Wayland and X11 load results" { + var loader: FakeLoader = .{ .x11_available = true }; + try expectLibraries(selectLibraries(.{ + .is_wsl = false, + .has_wayland_display = true, + .has_x11_display = true, + }, &loader), false, true); + try std.testing.expectEqual(@as(u32, 1), loader.wayland_attempts); + try std.testing.expectEqual(@as(u32, 1), loader.x11_attempts); +} + +test "clipboard linux environment treats display variables as nonempty and WSL as presence based" { + var env = std.process.EnvMap.init(std.testing.allocator); + defer env.deinit(); + try env.put("WAYLAND_DISPLAY", ""); + try env.put("DISPLAY", ":0"); + try env.put("WSL_INTEROP", ""); + + const detected = Environment.fromMap(&env); + try std.testing.expect(detected.is_wsl); + try std.testing.expect(!detected.has_wayland_display); + try std.testing.expect(detected.has_x11_display); + + try env.put("WAYLAND_SOCKET", "7"); + try std.testing.expect(Environment.fromMap(&env).has_wayland_display); +} + +test "clipboard linux environment recognizes WSL kernel releases without environment markers" { + try std.testing.expect(isWslKernelRelease("4.4.0-19041-Microsoft")); + try std.testing.expect(isWslKernelRelease("5.15.153.1-microsoft-standard-WSL2")); + try std.testing.expect(!isWslKernelRelease("6.12.31-1-lts")); +} + +test "clipboard XCB ABI types match the core protocol layouts" { + try std.testing.expectEqual(@as(usize, 4), @sizeOf(XcbCookie)); + try std.testing.expectEqual(@as(usize, 12), @sizeOf(XcbInternAtomReply)); + try std.testing.expectEqual(@as(usize, 32), @sizeOf(XcbGetSelectionOwnerReply)); + try std.testing.expectEqual(@as(usize, 32), @sizeOf(XcbGetPropertyReply)); + try std.testing.expectEqual(@as(usize, 36), @sizeOf(XcbGenericEvent)); + try std.testing.expectEqual(@as(usize, 36), @sizeOf(XcbGenericError)); + try std.testing.expectEqual(@as(usize, 40), @sizeOf(XcbSetup)); + try std.testing.expectEqual(@as(usize, 40), @sizeOf(XcbScreen)); + try std.testing.expectEqual(@as(usize, 28), @sizeOf(XcbSelectionRequestEvent)); + try std.testing.expectEqual(@as(usize, 24), @sizeOf(XcbSelectionNotifyEvent)); +} diff --git a/packages/core/src/zig/clipboard/macos-shim.m b/packages/core/src/zig/clipboard/macos-shim.m new file mode 100644 index 0000000000..383bdbe20b --- /dev/null +++ b/packages/core/src/zig/clipboard/macos-shim.m @@ -0,0 +1,279 @@ +#import +#import + +#include +#include +#include + +enum { + OT_CLIPBOARD_MACOS_STATUS_OK = 0, + OT_CLIPBOARD_MACOS_STATUS_EMPTY = 1, + OT_CLIPBOARD_MACOS_STATUS_LIMIT_EXCEEDED = 2, + OT_CLIPBOARD_MACOS_STATUS_INVALID_ARGUMENT = 3, + OT_CLIPBOARD_MACOS_STATUS_INVALID_TEXT = 4, + OT_CLIPBOARD_MACOS_STATUS_FAILED = 5, + OT_CLIPBOARD_MACOS_STATUS_CANCELLED = 6, + OT_CLIPBOARD_MACOS_STATUS_TIMED_OUT = 7, +}; + +enum { + OT_CLIPBOARD_MACOS_MIME_TEXT_PLAIN = 1, + OT_CLIPBOARD_MACOS_MIME_IMAGE_PNG = 2, +}; + +typedef int32_t (*ot_clipboard_macos_stop_callback)(const void *context); + +static int32_t ot_clipboard_macos_check_stop(ot_clipboard_macos_stop_callback stop_callback, + const void *stop_context) { + return stop_callback == NULL ? OT_CLIPBOARD_MACOS_STATUS_OK : stop_callback(stop_context); +} + +static int32_t ot_clipboard_macos_read_png(NSPasteboard *pasteboard, uint32_t max_image_pixels, + uint32_t max_conversion_bytes, + ot_clipboard_macos_stop_callback stop_callback, + const void *stop_context, NSData **out_data) { + BOOL png_failed = NO; + if ([pasteboard availableTypeFromArray:@[ NSPasteboardTypePNG ]] != nil) { + NSData *data = [pasteboard dataForType:NSPasteboardTypePNG]; + int32_t status = ot_clipboard_macos_check_stop(stop_callback, stop_context); + if (status != OT_CLIPBOARD_MACOS_STATUS_OK) { + return status; + } + if (data != nil && [data length] > 0) { + *out_data = data; + return OT_CLIPBOARD_MACOS_STATUS_OK; + } + png_failed = data == nil; + } + + if ([pasteboard availableTypeFromArray:@[ NSPasteboardTypeTIFF ]] == nil) { + return png_failed ? OT_CLIPBOARD_MACOS_STATUS_FAILED : OT_CLIPBOARD_MACOS_STATUS_EMPTY; + } + NSData *tiff = [pasteboard dataForType:NSPasteboardTypeTIFF]; + if (tiff == nil) { + return OT_CLIPBOARD_MACOS_STATUS_FAILED; + } + int32_t status = ot_clipboard_macos_check_stop(stop_callback, stop_context); + if (status != OT_CLIPBOARD_MACOS_STATUS_OK) { + return status; + } + if ([tiff length] == 0) { + return png_failed ? OT_CLIPBOARD_MACOS_STATUS_FAILED : OT_CLIPBOARD_MACOS_STATUS_EMPTY; + } + if ([tiff length] > max_conversion_bytes) { + return OT_CLIPBOARD_MACOS_STATUS_LIMIT_EXCEEDED; + } + + NSDictionary *metadata_options = @{ + (__bridge NSString *)kCGImageSourceShouldCache : @NO, + }; + id image_source_owner = CFBridgingRelease(CGImageSourceCreateWithData( + (__bridge CFDataRef)tiff, (__bridge CFDictionaryRef)metadata_options)); + if (image_source_owner == nil) { + return OT_CLIPBOARD_MACOS_STATUS_FAILED; + } + CGImageSourceRef image_source = (__bridge CGImageSourceRef)image_source_owner; + NSDictionary *properties = CFBridgingRelease(CGImageSourceCopyPropertiesAtIndex( + image_source, 0, (__bridge CFDictionaryRef)metadata_options)); + status = ot_clipboard_macos_check_stop(stop_callback, stop_context); + if (status != OT_CLIPBOARD_MACOS_STATUS_OK) { + return status; + } + NSNumber *pixels_wide = properties[(__bridge NSString *)kCGImagePropertyPixelWidth]; + NSNumber *pixels_high = properties[(__bridge NSString *)kCGImagePropertyPixelHeight]; + NSNumber *depth = properties[(__bridge NSString *)kCGImagePropertyDepth]; + if (pixels_wide == nil || pixels_high == nil || depth == nil) { + return OT_CLIPBOARD_MACOS_STATUS_FAILED; + } + + uint64_t width = [pixels_wide unsignedLongLongValue]; + uint64_t height = [pixels_high unsignedLongLongValue]; + if (width == 0 || height == 0) { + return OT_CLIPBOARD_MACOS_STATUS_FAILED; + } + if (width > max_image_pixels || height > max_image_pixels / width) { + return OT_CLIPBOARD_MACOS_STATUS_LIMIT_EXCEEDED; + } + uint64_t pixel_count = width * height; + uint64_t depth_bits = [depth unsignedLongLongValue]; + if (depth_bits == 0 || depth_bits > 64) { + return OT_CLIPBOARD_MACOS_STATUS_FAILED; + } + uint64_t component_count = 4; + NSString *color_model = properties[(__bridge NSString *)kCGImagePropertyColorModel]; + if ([color_model isEqualToString:(__bridge NSString *)kCGImagePropertyColorModelGray]) { + component_count = 1; + } else if ([color_model isEqualToString:(__bridge NSString *)kCGImagePropertyColorModelRGB] || + [color_model isEqualToString:(__bridge NSString *)kCGImagePropertyColorModelLab]) { + component_count = 3; + } + NSNumber *has_alpha = properties[(__bridge NSString *)kCGImagePropertyHasAlpha]; + if ([has_alpha boolValue]) { + component_count += 1; + } + uint64_t bytes_per_pixel = component_count * ((depth_bits + 7) / 8); + if (pixel_count > max_conversion_bytes / bytes_per_pixel) { + return OT_CLIPBOARD_MACOS_STATUS_LIMIT_EXCEEDED; + } + + NSDictionary *decode_options = @{ + (__bridge NSString *)kCGImageSourceShouldAllowFloat : @NO, + (__bridge NSString *)kCGImageSourceShouldCacheImmediately : @YES, + }; + id image_owner = CFBridgingRelease(CGImageSourceCreateImageAtIndex( + image_source, 0, (__bridge CFDictionaryRef)decode_options)); + if (image_owner == nil) { + return OT_CLIPBOARD_MACOS_STATUS_FAILED; + } + status = ot_clipboard_macos_check_stop(stop_callback, stop_context); + if (status != OT_CLIPBOARD_MACOS_STATUS_OK) { + return status; + } + CGImageRef image = (__bridge CGImageRef)image_owner; + if (CGImageGetWidth(image) != width || CGImageGetHeight(image) != height) { + return OT_CLIPBOARD_MACOS_STATUS_FAILED; + } + if (CGImageGetBytesPerRow(image) > max_conversion_bytes / height) { + return OT_CLIPBOARD_MACOS_STATUS_LIMIT_EXCEEDED; + } + + NSMutableData *png = [NSMutableData data]; + id destination_owner = CFBridgingRelease(CGImageDestinationCreateWithData( + (__bridge CFMutableDataRef)png, CFSTR("public.png"), 1, NULL)); + if (destination_owner == nil) { + return OT_CLIPBOARD_MACOS_STATUS_FAILED; + } + CGImageDestinationRef destination = (__bridge CGImageDestinationRef)destination_owner; + NSNumber *orientation = properties[(__bridge NSString *)kCGImagePropertyOrientation]; + NSDictionary *destination_properties = + orientation == nil ? nil : @{ (__bridge NSString *)kCGImagePropertyOrientation : orientation }; + CGImageDestinationAddImage(destination, image, (__bridge CFDictionaryRef)destination_properties); + BOOL finalized = CGImageDestinationFinalize(destination); + status = ot_clipboard_macos_check_stop(stop_callback, stop_context); + if (status != OT_CLIPBOARD_MACOS_STATUS_OK) { + return status; + } + if (!finalized || [png length] == 0) { + return OT_CLIPBOARD_MACOS_STATUS_FAILED; + } + *out_data = png; + return OT_CLIPBOARD_MACOS_STATUS_OK; +} + +int32_t ot_clipboard_macos_read(uint32_t mime, uint32_t max_bytes, uint32_t max_image_pixels, + uint32_t max_conversion_bytes, + ot_clipboard_macos_stop_callback stop_callback, + const void *stop_context, uint8_t **out_bytes, uint32_t *out_length) { + if (out_bytes == NULL || out_length == NULL) { + return OT_CLIPBOARD_MACOS_STATUS_INVALID_ARGUMENT; + } + + *out_bytes = NULL; + *out_length = 0; + + @autoreleasepool { + @try { + int32_t status = ot_clipboard_macos_check_stop(stop_callback, stop_context); + if (status != OT_CLIPBOARD_MACOS_STATUS_OK) { + return status; + } + NSPasteboard *pasteboard = [NSPasteboard generalPasteboard]; + const void *source = NULL; + NSUInteger length = 0; + NSString *text = nil; + NSData *data = nil; + + if (mime == OT_CLIPBOARD_MACOS_MIME_TEXT_PLAIN) { + if ([pasteboard availableTypeFromArray:@[ NSPasteboardTypeString ]] == nil) { + return OT_CLIPBOARD_MACOS_STATUS_EMPTY; + } + text = [pasteboard stringForType:NSPasteboardTypeString]; + if (text == nil) { + return OT_CLIPBOARD_MACOS_STATUS_FAILED; + } + status = ot_clipboard_macos_check_stop(stop_callback, stop_context); + if (status != OT_CLIPBOARD_MACOS_STATUS_OK) { + return status; + } + length = [text lengthOfBytesUsingEncoding:NSUTF8StringEncoding]; + source = [text UTF8String]; + } else if (mime == OT_CLIPBOARD_MACOS_MIME_IMAGE_PNG) { + status = ot_clipboard_macos_read_png(pasteboard, max_image_pixels, + max_conversion_bytes, stop_callback, + stop_context, &data); + if (status != OT_CLIPBOARD_MACOS_STATUS_OK) { + return status; + } + length = [data length]; + source = [data bytes]; + } else { + return OT_CLIPBOARD_MACOS_STATUS_EMPTY; + } + + if (length > max_bytes || length > UINT32_MAX) { + return OT_CLIPBOARD_MACOS_STATUS_LIMIT_EXCEEDED; + } + if (length > 0 && source == NULL) { + return OT_CLIPBOARD_MACOS_STATUS_FAILED; + } + + uint8_t *copy = NULL; + if (length > 0) { + copy = malloc(length); + if (copy == NULL) { + return OT_CLIPBOARD_MACOS_STATUS_FAILED; + } + memcpy(copy, source, length); + } + + *out_bytes = copy; + *out_length = (uint32_t)length; + return OT_CLIPBOARD_MACOS_STATUS_OK; + } @catch (__unused NSException *exception) { + return OT_CLIPBOARD_MACOS_STATUS_FAILED; + } + } +} + +int32_t ot_clipboard_macos_write_text(const uint8_t *bytes, uint32_t length) { + if (length > 0 && bytes == NULL) { + return OT_CLIPBOARD_MACOS_STATUS_INVALID_ARGUMENT; + } + + @autoreleasepool { + @try { + NSString *text = length == 0 + ? @"" + : [[NSString alloc] initWithBytes:bytes + length:length + encoding:NSUTF8StringEncoding]; + if (text == nil) { + return OT_CLIPBOARD_MACOS_STATUS_INVALID_TEXT; + } + + NSPasteboard *pasteboard = [NSPasteboard generalPasteboard]; + [pasteboard clearContents]; + if (![pasteboard setString:text forType:NSPasteboardTypeString]) { + return OT_CLIPBOARD_MACOS_STATUS_FAILED; + } + return OT_CLIPBOARD_MACOS_STATUS_OK; + } @catch (__unused NSException *exception) { + return OT_CLIPBOARD_MACOS_STATUS_FAILED; + } + } +} + +int32_t ot_clipboard_macos_clear(void) { + @autoreleasepool { + @try { + [[NSPasteboard generalPasteboard] clearContents]; + return OT_CLIPBOARD_MACOS_STATUS_OK; + } @catch (__unused NSException *exception) { + return OT_CLIPBOARD_MACOS_STATUS_FAILED; + } + } +} + +void ot_clipboard_macos_free_bytes(uint8_t *bytes) { + free(bytes); +} diff --git a/packages/core/src/zig/clipboard/macos.zig b/packages/core/src/zig/clipboard/macos.zig new file mode 100644 index 0000000000..e3d32ea4ee --- /dev/null +++ b/packages/core/src/zig/clipboard/macos.zig @@ -0,0 +1,443 @@ +const std = @import("std"); +const clipboard_clock = @import("clock.zig"); + +const Allocator = std.mem.Allocator; +const LOCK_RETRY_SLEEP_NS: u64 = std.time.ns_per_ms; + +var pasteboard_mutex: std.Thread.Mutex = .{}; + +pub const MimeType = enum(u32) { + text_plain = 1, + image_png = 2, + + pub fn name(mime: MimeType) []const u8 { + return switch (mime) { + .text_plain => "text/plain", + .image_png => "image/png", + }; + } +}; + +pub const ReadJob = struct { + request: []const u8, + max_bytes: u32, + max_image_pixels: u32, + max_conversion_bytes: u32, +}; + +pub const WriteTextJob = struct { + text: []const u8, +}; + +pub const Job = union(enum) { + read: ReadJob, + write_text: WriteTextJob, + clear, +}; + +pub const ReadResult = struct { + mime: MimeType, + data: []u8, +}; + +pub const Result = union(enum) { + read: ReadResult, + empty, + written, + cleared, + unsupported, + cancelled, + timed_out, + failed, + + pub fn deinit(result: *Result, allocator: Allocator) void { + switch (result.*) { + .read => |read_result| allocator.free(read_result.data), + else => {}, + } + result.* = undefined; + } +}; + +pub const Status = enum { + cancelled, + timed_out, + failed, +}; + +pub const JobError = error{ + InvalidArgument, + InvalidText, + LimitExceeded, + NativeFailure, + OutOfMemory, +}; + +pub const ExecuteOptions = struct { + cancel_requested: ?*const std.atomic.Value(bool) = null, + begin_mutation: ?*const fn (?*anyopaque) ?Status = null, + mutation_context: ?*anyopaque = null, + deadline_ns: i128, +}; + +const ShimStatus = enum(i32) { + ok = 0, + empty = 1, + limit_exceeded = 2, + invalid_argument = 3, + invalid_text = 4, + failed = 5, + cancelled = 6, + timed_out = 7, +}; + +extern fn ot_clipboard_macos_read( + mime: u32, + max_bytes: u32, + max_image_pixels: u32, + max_conversion_bytes: u32, + stop_callback: ?*const fn (?*const anyopaque) callconv(.c) i32, + stop_context: ?*const anyopaque, + out_bytes: *?[*]u8, + out_length: *u32, +) i32; +extern fn ot_clipboard_macos_write_text(bytes: ?[*]const u8, length: u32) i32; +extern fn ot_clipboard_macos_clear() i32; +extern fn ot_clipboard_macos_free_bytes(bytes: ?[*]u8) void; + +comptime { + std.debug.assert(@sizeOf(MimeType) == @sizeOf(u32)); +} + +pub fn runJob(allocator: Allocator, job: Job, options: ExecuteOptions) JobError!Result { + return runJobWithMutex(allocator, job, options, &pasteboard_mutex); +} + +fn runJobWithMutex( + allocator: Allocator, + job: Job, + options: ExecuteOptions, + mutex: *std.Thread.Mutex, +) JobError!Result { + if (job == .write_text and job.write_text.text.len > std.math.maxInt(u32)) return error.InvalidArgument; + if (acquireJobLock(mutex, job, options)) |status| return statusResult(status); + defer mutex.unlock(); + + return switch (job) { + .read => |read_job| read(allocator, read_job, options), + .write_text => |write_job| writeText(write_job), + .clear => clear(), + }; +} + +fn read(allocator: Allocator, job: ReadJob, options: ExecuteOptions) JobError!Result { + var iterator = PreferenceIterator.init(job.request) catch return error.InvalidArgument; + var supported = false; + var first_failure: ?JobError = null; + while (iterator.next() catch return error.InvalidArgument) |name| { + if (stopStatus(options)) |status| return statusResult(status); + const mime: MimeType = if (std.ascii.eqlIgnoreCase(name, "text/plain")) + .text_plain + else if (std.ascii.eqlIgnoreCase(name, "image/png")) + .image_png + else + continue; + supported = true; + const result = readMime( + allocator, + mime, + job.max_bytes, + job.max_image_pixels, + job.max_conversion_bytes, + options, + ) catch |err| switch (err) { + error.NativeFailure => { + if (first_failure == null) first_failure = err; + continue; + }, + else => return err, + }; + if (result != .empty) return result; + } + if (first_failure) |failure| return failure; + return if (supported) .empty else .unsupported; +} + +fn stopStatus(options: ExecuteOptions) ?Status { + if (options.cancel_requested) |cancelled| { + if (cancelled.load(.acquire)) return .cancelled; + } + if (options.deadline_ns == std.math.maxInt(i128)) return null; + if (clipboard_clock.nowNs() >= options.deadline_ns) return .timed_out; + return null; +} + +fn acquirePasteboardLock(mutex: *std.Thread.Mutex, options: ExecuteOptions) ?Status { + while (true) { + if (stopStatus(options)) |status| return status; + if (mutex.tryLock()) { + if (stopStatus(options)) |status| { + mutex.unlock(); + return status; + } + return null; + } + + const sleep_ns = if (options.deadline_ns == std.math.maxInt(i128)) + LOCK_RETRY_SLEEP_NS + else blk: { + const now_ns = clipboard_clock.nowNs(); + if (now_ns >= options.deadline_ns) return .timed_out; + const remaining_ns: u64 = @intCast(@min(options.deadline_ns - now_ns, std.math.maxInt(u64))); + break :blk @min(LOCK_RETRY_SLEEP_NS, remaining_ns); + }; + std.debug.assert(sleep_ns > 0); + std.Thread.sleep(sleep_ns); + } +} + +fn acquireJobLock(mutex: *std.Thread.Mutex, job: Job, options: ExecuteOptions) ?Status { + if (acquirePasteboardLock(mutex, options)) |status| return status; + if (job != .write_text and job != .clear) return null; + if (beginMutation(options)) |status| { + mutex.unlock(); + return status; + } + return null; +} + +fn readMime( + allocator: Allocator, + mime: MimeType, + max_bytes: u32, + max_image_pixels: u32, + max_conversion_bytes: u32, + options: ExecuteOptions, +) JobError!Result { + var shim_bytes: ?[*]u8 = null; + var length: u32 = 0; + const status = shimStatus(ot_clipboard_macos_read( + @intFromEnum(mime), + max_bytes, + max_image_pixels, + max_conversion_bytes, + shimStop, + &options, + &shim_bytes, + &length, + )); + + switch (status) { + .empty => return .empty, + .limit_exceeded => return error.LimitExceeded, + .invalid_argument => return error.InvalidArgument, + .invalid_text => return error.NativeFailure, + .failed => return error.NativeFailure, + .cancelled => return .cancelled, + .timed_out => return .timed_out, + .ok => {}, + } + + defer ot_clipboard_macos_free_bytes(shim_bytes); + if (postShimStop(options)) |result| return result; + if (length > max_bytes) return error.NativeFailure; + const source: []const u8 = if (length == 0) + "" + else + (shim_bytes orelse return error.NativeFailure)[0..length]; + const data = allocator.dupe(u8, source) catch return error.OutOfMemory; + return .{ .read = .{ .mime = mime, .data = data } }; +} + +fn shimStop(context: ?*const anyopaque) callconv(.c) i32 { + const options: *const ExecuteOptions = @ptrCast(@alignCast(context orelse return @intFromEnum(ShimStatus.failed))); + const status = stopStatus(options.*) orelse return @intFromEnum(ShimStatus.ok); + return @intFromEnum(switch (status) { + .cancelled => ShimStatus.cancelled, + .timed_out => ShimStatus.timed_out, + .failed => ShimStatus.failed, + }); +} + +const PreferenceIterator = struct { + request: []const u8, + count: u32, + index: u32 = 0, + offset: usize = 4, + + fn init(request: []const u8) error{InvalidRequest}!PreferenceIterator { + if (request.len < 4) return error.InvalidRequest; + const count = std.mem.readInt(u32, request[0..4], .little); + if (count == 0) return error.InvalidRequest; + return .{ .request = request, .count = count }; + } + + fn next(iterator: *PreferenceIterator) error{InvalidRequest}!?[]const u8 { + if (iterator.index == iterator.count) { + if (iterator.offset != iterator.request.len) return error.InvalidRequest; + return null; + } + if (iterator.request.len - iterator.offset < 4) return error.InvalidRequest; + const length = std.mem.readInt(u32, iterator.request[iterator.offset..][0..4], .little); + iterator.offset += 4; + if (length == 0 or length > iterator.request.len - iterator.offset) return error.InvalidRequest; + const mime = iterator.request[iterator.offset..][0..length]; + iterator.offset += length; + iterator.index += 1; + return mime; + } +}; + +fn writeText(job: WriteTextJob) JobError!Result { + const bytes: ?[*]const u8 = if (job.text.len == 0) null else job.text.ptr; + return switch (shimStatus(ot_clipboard_macos_write_text(bytes, @intCast(job.text.len)))) { + .ok => .written, + .invalid_argument => error.InvalidArgument, + .invalid_text => error.InvalidText, + .empty, .limit_exceeded, .failed, .cancelled, .timed_out => error.NativeFailure, + }; +} + +fn clear() JobError!Result { + return switch (shimStatus(ot_clipboard_macos_clear())) { + .ok => .cleared, + .invalid_argument => error.InvalidArgument, + .empty, .limit_exceeded, .invalid_text, .failed, .cancelled, .timed_out => error.NativeFailure, + }; +} + +fn beginMutation(options: ExecuteOptions) ?Status { + if (stopStatus(options)) |status| return status; + const begin = options.begin_mutation orelse return .failed; + return begin(options.mutation_context); +} + +fn postShimStop(options: ExecuteOptions) ?Result { + const status = stopStatus(options) orelse return null; + return statusResult(status); +} + +fn statusResult(status: Status) Result { + return switch (status) { + .cancelled => .cancelled, + .timed_out => .timed_out, + .failed => .failed, + }; +} + +fn shimStatus(value: i32) ShimStatus { + return std.meta.intToEnum(ShimStatus, value) catch .failed; +} + +test "macOS clipboard shim initializes absent output" { + if (comptime @import("builtin").os.tag != .macos) return error.SkipZigTest; + + var bytes: ?[*]u8 = undefined; + var length: u32 = 99; + const status = shimStatus(ot_clipboard_macos_read(0, 0, 0, 0, null, null, &bytes, &length)); + try std.testing.expectEqual(ShimStatus.empty, status); + try std.testing.expect(bytes == null); + try std.testing.expectEqual(@as(u32, 0), length); +} + +test "macOS clipboard MIME request parsing preserves order" { + const request = [_]u8{ 2, 0, 0, 0, 9, 0, 0, 0 } ++ "image/png".* ++ + [_]u8{ 10, 0, 0, 0 } ++ "text/plain".*; + var iterator = try PreferenceIterator.init(&request); + try std.testing.expectEqualStrings("image/png", (try iterator.next()).?); + try std.testing.expectEqualStrings("text/plain", (try iterator.next()).?); + try std.testing.expect((try iterator.next()) == null); +} + +test "macOS clipboard read results have explicit allocator ownership" { + var result: Result = .{ .read = .{ + .mime = .image_png, + .data = try std.testing.allocator.dupe(u8, &.{ 0x89, 0x50, 0x4e, 0x47 }), + } }; + result.deinit(std.testing.allocator); +} + +const TestMutationContext = struct { + called: bool = false, +}; + +fn testBeginMutation(context: ?*anyopaque) ?Status { + const mutation: *TestMutationContext = @ptrCast(@alignCast(context.?)); + mutation.called = true; + return null; +} + +test "macOS clipboard lock contention observes cancellation and deadline" { + try clipboard_clock.init(); + var mutex: std.Thread.Mutex = .{}; + mutex.lock(); + defer mutex.unlock(); + + var cancelled = std.atomic.Value(bool).init(true); + try std.testing.expectEqual( + Status.cancelled, + acquirePasteboardLock(&mutex, .{ + .cancel_requested = &cancelled, + .deadline_ns = std.math.maxInt(i128), + }).?, + ); + cancelled.store(false, .release); + try std.testing.expectEqual( + Status.timed_out, + acquirePasteboardLock(&mutex, .{ + .cancel_requested = &cancelled, + .deadline_ns = clipboard_clock.nowNs(), + }).?, + ); +} + +test "macOS clipboard mutation callback is not called before the lock" { + try clipboard_clock.init(); + var mutex: std.Thread.Mutex = .{}; + mutex.lock(); + defer mutex.unlock(); + var cancelled = std.atomic.Value(bool).init(true); + var mutation = TestMutationContext{}; + + const status = acquireJobLock( + &mutex, + .{ .write_text = .{ .text = "text" } }, + .{ + .cancel_requested = &cancelled, + .begin_mutation = testBeginMutation, + .mutation_context = &mutation, + .deadline_ns = std.math.maxInt(i128), + }, + ); + try std.testing.expectEqual(Status.cancelled, status.?); + try std.testing.expect(!mutation.called); +} + +test "macOS clipboard post-shim stop maps exact terminal status" { + try clipboard_clock.init(); + var cancelled = std.atomic.Value(bool).init(true); + try std.testing.expect(postShimStop(.{ + .cancel_requested = &cancelled, + .deadline_ns = std.math.maxInt(i128), + }).? == .cancelled); + + cancelled.store(false, .release); + try std.testing.expect(postShimStop(.{ + .cancel_requested = &cancelled, + .deadline_ns = clipboard_clock.nowNs(), + }).? == .timed_out); +} + +test "macOS clipboard shim callback maps exact terminal status" { + try clipboard_clock.init(); + var cancelled = std.atomic.Value(bool).init(true); + var options = ExecuteOptions{ + .cancel_requested = &cancelled, + .deadline_ns = std.math.maxInt(i128), + }; + try std.testing.expectEqual(ShimStatus.cancelled, shimStatus(shimStop(&options))); + + cancelled.store(false, .release); + options.deadline_ns = clipboard_clock.nowNs(); + try std.testing.expectEqual(ShimStatus.timed_out, shimStatus(shimStop(&options))); +} diff --git a/packages/core/src/zig/clipboard/wayland-protocol.zig b/packages/core/src/zig/clipboard/wayland-protocol.zig new file mode 100644 index 0000000000..168ca24a38 --- /dev/null +++ b/packages/core/src/zig/clipboard/wayland-protocol.zig @@ -0,0 +1,142 @@ +const linux = @import("linux.zig"); + +const WlInterface = linux.WlInterface; +const WlMessage = linux.WlMessage; + +pub const Kind = enum { ext, wlr }; + +const Names = struct { + manager: [*:0]const u8, + device: [*:0]const u8, + source: [*:0]const u8, + offer: [*:0]const u8, + version: c_int, + primary_signature: [*:0]const u8, +}; + +pub const Metadata = struct { + types: [10]?*const WlInterface, + manager_requests: [3]WlMessage, + device_requests: [3]WlMessage, + device_events: [4]WlMessage, + source_requests: [2]WlMessage, + source_events: [2]WlMessage, + offer_requests: [2]WlMessage, + offer_events: [1]WlMessage, + manager: WlInterface, + device: WlInterface, + source: WlInterface, + offer: WlInterface, + + pub fn init(self: *Metadata, kind: Kind, seat: *const WlInterface) void { + const names: Names = switch (kind) { + .ext => .{ + .manager = "ext_data_control_manager_v1", + .device = "ext_data_control_device_v1", + .source = "ext_data_control_source_v1", + .offer = "ext_data_control_offer_v1", + .version = 1, + .primary_signature = "?o", + }, + .wlr => .{ + .manager = "zwlr_data_control_manager_v1", + .device = "zwlr_data_control_device_v1", + .source = "zwlr_data_control_source_v1", + .offer = "zwlr_data_control_offer_v1", + .version = 2, + .primary_signature = "2?o", + }, + }; + + self.manager = interface(names.manager, names.version, 3, &self.manager_requests, 0, null); + self.device = interface(names.device, names.version, 3, &self.device_requests, 4, &self.device_events); + self.source = interface(names.source, 1, 2, &self.source_requests, 2, &self.source_events); + self.offer = interface(names.offer, 1, 2, &self.offer_requests, 1, &self.offer_events); + self.types = .{ + null, + null, + &self.source, + &self.device, + seat, + &self.source, + &self.source, + &self.offer, + &self.offer, + &self.offer, + }; + self.manager_requests = .{ + message("create_data_source", "n", self.types[2..].ptr), + message("get_data_device", "no", self.types[3..].ptr), + message("destroy", "", self.types[0..].ptr), + }; + self.device_requests = .{ + message("set_selection", "?o", self.types[5..].ptr), + message("destroy", "", self.types[0..].ptr), + message("set_primary_selection", names.primary_signature, self.types[6..].ptr), + }; + self.device_events = .{ + message("data_offer", "n", self.types[7..].ptr), + message("selection", "?o", self.types[8..].ptr), + message("finished", "", self.types[0..].ptr), + message("primary_selection", names.primary_signature, self.types[9..].ptr), + }; + self.source_requests = .{ + message("offer", "s", self.types[0..].ptr), + message("destroy", "", self.types[0..].ptr), + }; + self.source_events = .{ + message("send", "sh", self.types[0..].ptr), + message("cancelled", "", self.types[0..].ptr), + }; + self.offer_requests = .{ + message("receive", "sh", self.types[0..].ptr), + message("destroy", "", self.types[0..].ptr), + }; + self.offer_events = .{message("offer", "s", self.types[0..].ptr)}; + } +}; + +fn interface( + name: [*:0]const u8, + version: c_int, + method_count: c_int, + methods: ?[*]const WlMessage, + event_count: c_int, + events: ?[*]const WlMessage, +) WlInterface { + return .{ + .name = name, + .version = version, + .method_count = method_count, + .methods = methods, + .event_count = event_count, + .events = events, + }; +} + +fn message(name: [*:0]const u8, signature: [*:0]const u8, types: [*]const ?*const WlInterface) WlMessage { + return .{ .name = name, .signature = signature, .types = types }; +} + +test "Wayland data-control metadata preserves protocol names and versions" { + const std = @import("std"); + const seat = WlInterface{ + .name = "wl_seat", + .version = 9, + .method_count = 0, + .methods = null, + .event_count = 0, + .events = null, + }; + var ext: Metadata = undefined; + ext.init(.ext, &seat); + try std.testing.expectEqualStrings("ext_data_control_manager_v1", std.mem.span(ext.manager.name)); + try std.testing.expectEqual(@as(c_int, 1), ext.manager.version); + try std.testing.expectEqualStrings("?o", std.mem.span(ext.device_requests[2].signature)); + + var wlr: Metadata = undefined; + wlr.init(.wlr, &seat); + try std.testing.expectEqualStrings("zwlr_data_control_manager_v1", std.mem.span(wlr.manager.name)); + try std.testing.expectEqual(@as(c_int, 2), wlr.manager.version); + try std.testing.expectEqualStrings("2?o", std.mem.span(wlr.device_requests[2].signature)); +} diff --git a/packages/core/src/zig/clipboard/wayland.zig b/packages/core/src/zig/clipboard/wayland.zig new file mode 100644 index 0000000000..ef0b5d98a7 --- /dev/null +++ b/packages/core/src/zig/clipboard/wayland.zig @@ -0,0 +1,2219 @@ +const std = @import("std"); +const builtin = @import("builtin"); +const clipboard_clock = @import("clock.zig"); +const linux = @import("linux.zig"); +const protocol = @import("wayland-protocol.zig"); + +const WlArgument = linux.WlArgument; +const WlInterface = linux.WlInterface; +const WlProxy = linux.WlProxy; +const MAX_SEATS = 16; +const MAX_SEAT_NAME_BYTES = 255; +const MAX_OFFERS = 8; +const MAX_OFFER_MIME_TYPES = 3; +const MAX_MIME_BYTES = 255; +const MAX_PROVIDERS = 4; +// Clipboard payloads use file descriptors, so protocol metadata does not need unbounded connection buffers. +const WAYLAND_CONNECTION_BUFFER_SIZE_MAX = 1024 * 1024; +const WL_SEAT_CAPABILITY_KEYBOARD = 2; +const PROVIDER_TRANSFER_IDLE_TIMEOUT_NS = 30 * std.time.ns_per_s; +// One drive call dispatches at most this many queued events, enough to settle a +// selection-change backlog in one call while keeping per-call work bounded. +const DISPATCH_EVENTS_PER_DRIVE_MAX = 64; + +pub const Progress = enum { pending, ready, unsupported, failed }; +pub const SelectionResult = enum { ok, pending, committed, unsupported, failed }; +const FlushResult = enum { complete, pending, failed }; +const FlushOutcome = struct { result: c_int, errno: std.posix.E }; + +pub const Failure = enum { + none, + protocol, + dispatch, + flush, + provider, +}; + +const Phase = enum { idle, registry, seats, device, ready, unsupported, failed }; + +const Seat = struct { + global_name: u32, + proxy: *WlProxy, + name: [MAX_SEAT_NAME_BYTES]u8 = undefined, + name_length: u8 = 0, + capabilities: u32 = 0, + + fn nameSlice(self: *const Seat) []const u8 { + return self.name[0..self.name_length]; + } +}; + +const Mime = struct { + bytes: [MAX_MIME_BYTES]u8, + length: u8, + + fn slice(self: *const Mime) []const u8 { + return self.bytes[0..self.length]; + } +}; + +pub const Offer = struct { + proxy: *WlProxy, + mimes: [MAX_OFFER_MIME_TYPES]Mime = undefined, + mime_count: u8 = 0, +}; + +const Transfer = struct { + fd: std.posix.fd_t, + offset: usize = 0, + last_progress_ns: i128, +}; + +const Provider = struct { + connection: *Connection, + source: *WlProxy, + primary: bool, + data: []u8, + transfers: []Transfer, + transfer_count: u32 = 0, + transfer_cursor: u32 = 0, + cancelled: bool = false, +}; + +pub const MimeMatch = struct { + offered: []const u8, + requested: []const u8, +}; + +pub const Connection = struct { + allocator: std.mem.Allocator, + symbols: *const linux.WaylandSymbols, + max_provider_transfers: u32, + display: ?*linux.WlDisplay = null, + registry: ?*WlProxy = null, + sync_callback: ?*WlProxy = null, + phase: Phase = .idle, + sync_done: bool = false, + // Selection barriers are wl_display.sync roundtrips issued at read admission. + // At most one is in flight; later admissions wait for the follow-up barrier. + barrier_callback: ?*WlProxy = null, + barrier_serial_completed: u64 = 0, + barrier_serial_inflight: u64 = 0, + barrier_serial_requested: u64 = 0, + output_pending: bool = false, + failure: Failure = .none, + ext_global: ?u32 = null, + wlr_global: ?u32 = null, + wlr_version: u32 = 0, + core_manager_global: ?u32 = null, + compositor_global: ?u32 = null, + shm_global: ?u32 = null, + shell_global: ?u32 = null, + seats: [MAX_SEATS]Seat = undefined, + seat_count: u8 = 0, + seats_overflowed: bool = false, + requested_seat: []const u8, + environment_seat: []const u8, + metadata: protocol.Metadata = undefined, + manager: ?*WlProxy = null, + core_data_device: bool = false, + bound_manager_global: ?u32 = null, + device: ?*WlProxy = null, + compositor: ?*WlProxy = null, + shm: ?*WlProxy = null, + shell: ?*WlProxy = null, + keyboard: ?*WlProxy = null, + helper_surface: ?*WlProxy = null, + helper_shell_surface: ?*WlProxy = null, + helper_pool: ?*WlProxy = null, + helper_buffer: ?*WlProxy = null, + helper_fd: ?std.posix.fd_t = null, + core_focus_entered: bool = false, + core_focus_lost: bool = false, + core_selection_seen: bool = false, + core_focus_users: u32 = 0, + offers: [MAX_OFFERS]Offer = undefined, + offer_count: u8 = 0, + clipboard_offer: ?*WlProxy = null, + primary_offer: ?*WlProxy = null, + primary_supported: bool = false, + bound_seat_global: ?u32 = null, + providers: [MAX_PROVIDERS]?*Provider = .{null} ** MAX_PROVIDERS, + clipboard_provider: ?*Provider = null, + primary_provider: ?*Provider = null, + provider_cursor: u8 = 0, + allow_core_data_device: bool = false, + flush_outcome_override: ?FlushOutcome = null, + test_marshal_count: u8 = 0, + test_flush_marshal_count: u8 = 0, + display_error_override: ?c_int = null, + + pub fn init( + allocator: std.mem.Allocator, + symbols: *const linux.WaylandSymbols, + requested_seat: []const u8, + environment_seat: []const u8, + max_provider_transfers: u32, + ) Connection { + return .{ + .allocator = allocator, + .symbols = symbols, + .requested_seat = requested_seat, + .environment_seat = environment_seat, + .max_provider_transfers = max_provider_transfers, + }; + } + + pub fn deinit(self: *Connection) void { + self.releaseProviders(); + self.destroyCoreHelper(); + for (self.offers[0..self.offer_count]) |offer| self.destroyOffer(offer.proxy); + if (self.device) |device| { + if (self.core_data_device) self.symbols.wl_proxy_destroy(device) else self.destroyProtocolProxy(device, 1); + } + if (self.manager) |manager| { + if (self.core_data_device) self.symbols.wl_proxy_destroy(manager) else self.destroyProtocolProxy(manager, 2); + } + if (self.keyboard) |keyboard| self.symbols.wl_proxy_destroy(keyboard); + if (self.shell) |shell| self.symbols.wl_proxy_destroy(shell); + if (self.shm) |shm| self.symbols.wl_proxy_destroy(shm); + if (self.compositor) |compositor| self.symbols.wl_proxy_destroy(compositor); + for (self.seats[0..self.seat_count]) |seat| self.symbols.wl_proxy_destroy(seat.proxy); + if (self.barrier_callback) |callback| self.symbols.wl_proxy_destroy(callback); + if (self.sync_callback) |callback| self.symbols.wl_proxy_destroy(callback); + if (self.registry) |registry| self.symbols.wl_proxy_destroy(registry); + if (self.display != null) _ = self.queueFlush(); + if (self.display) |display| self.symbols.wl_display_disconnect(display); + self.* = undefined; + } + + pub fn drive(self: *Connection) Progress { + if (self.output_pending) { + switch (self.flushOutput()) { + .complete => {}, + .pending => return .pending, + .failed => return self.fail(.flush), + } + } + switch (self.phase) { + .idle => self.start() catch |err| switch (err) { + error.ConnectFailed => { + self.phase = .unsupported; + return .unsupported; + }, + else => return self.fail(.protocol), + }, + .unsupported => return .unsupported, + .failed => return .failed, + .ready => { + if (!self.dispatchAvailable()) return self.fail(.dispatch); + return if (self.phase == .ready) .ready else .failed; + }, + else => {}, + } + + if (!self.dispatchAvailable()) { + return self.fail(.dispatch); + } + if (!self.sync_done) return .pending; + + self.sync_done = false; + switch (self.phase) { + .registry => { + if (self.ext_global == null and self.wlr_global == null and !self.coreDataDeviceAvailable()) { + self.phase = .unsupported; + return .unsupported; + } + self.phase = .seats; + if (!self.sendSync()) return self.fail(.protocol); + }, + .seats => { + switch (self.bindDevice()) { + .ok, .pending => {}, + .committed => unreachable, + .unsupported => { + self.phase = .unsupported; + return .unsupported; + }, + .failed => return self.fail(.protocol), + } + self.phase = .device; + if (!self.sendSync()) return self.fail(.protocol); + }, + .device => { + self.phase = .ready; + }, + else => {}, + } + return if (self.phase == .ready) .ready else .pending; + } + + pub fn currentOffer(self: *Connection, primary: bool) ?*const Offer { + const proxy = if (primary) self.primary_offer else self.clipboard_offer; + const selected = proxy orelse return null; + for (self.offers[0..self.offer_count]) |*offer| { + if (offer.proxy == selected) return offer; + } + return null; + } + + pub fn usesCoreDataDevice(self: *const Connection) bool { + return self.core_data_device; + } + + // A selection barrier is one wl_display.sync roundtrip. Its completion proves + // every selection event the compositor emitted before the request has been + // dispatched, so a read admitted before the barrier cannot choose an offer + // that predates its admission. + pub fn requestSelectionBarrier(self: *Connection) ?u64 { + if (self.barrier_callback != null) { + // The in-flight sync was requested before this admission, so only the + // follow-up barrier issued at its completion covers this read. + self.barrier_serial_requested = self.barrier_serial_inflight + 1; + return self.barrier_serial_requested; + } + return self.issueSelectionBarrier(); + } + + pub fn selectionBarrierReached(self: *const Connection, serial: u64) bool { + return self.barrier_serial_completed >= serial; + } + + fn issueSelectionBarrier(self: *Connection) ?u64 { + std.debug.assert(self.barrier_callback == null); + const display = self.display orelse return null; + const callback = self.marshal(@ptrCast(display), 0, self.symbols.wl_callback_interface, 1, 0, &.{}) orelse + return null; + if (self.addListener(callback, &barrier_listener) != 0) { + self.symbols.wl_proxy_destroy(callback); + return null; + } + self.barrier_callback = callback; + self.barrier_serial_inflight = self.barrier_serial_completed + 1; + if (self.barrier_serial_requested < self.barrier_serial_inflight) { + self.barrier_serial_requested = self.barrier_serial_inflight; + } + if (self.queueFlush() == .failed) { + _ = self.fail(.flush); + return null; + } + return self.barrier_serial_inflight; + } + + fn barrierDone(data: ?*anyopaque, callback: ?*WlProxy, _: u32) callconv(.c) void { + const self: *Connection = @ptrCast(@alignCast(data.?)); + std.debug.assert(self.barrier_callback == callback); + std.debug.assert(self.barrier_serial_inflight == self.barrier_serial_completed + 1); + if (callback) |proxy| self.symbols.wl_proxy_destroy(proxy); + self.barrier_callback = null; + self.barrier_serial_completed += 1; + if (self.barrier_serial_requested <= self.barrier_serial_completed) return; + if (self.issueSelectionBarrier() == null) { + // Reads waiting on the follow-up barrier cannot progress on a + // connection that cannot even queue a sync request. + _ = self.fail(.protocol); + } + } + + pub fn acquireCoreSelection(self: *Connection) Progress { + if (!self.core_data_device) return .unsupported; + if (self.phase == .failed) return .failed; + std.debug.assert(self.core_focus_users < std.math.maxInt(u32)); + self.core_focus_users += 1; + if (self.core_focus_users > 1) return .pending; + self.core_selection_seen = false; + self.core_focus_lost = false; + if (self.clipboard_offer) |offer| self.removeOffer(offer); + self.clipboard_offer = null; + if (!self.createCoreHelper()) { + self.core_focus_users = 0; + return .failed; + } + return .pending; + } + + pub fn coreSelectionProgress(self: *const Connection) Progress { + if (!self.core_data_device) return .unsupported; + if (self.phase == .failed) return .failed; + if (self.core_focus_users == 0 or self.helper_surface == null) return .failed; + if (self.core_focus_lost) return .failed; + return if (self.core_focus_entered and self.core_selection_seen) .ready else .pending; + } + + pub fn releaseCoreSelection(self: *Connection) void { + if (!self.core_data_device or self.core_focus_users == 0) return; + self.core_focus_users -= 1; + if (self.core_focus_users == 0) self.destroyCoreHelper(); + } + + pub fn takeFailure(self: *Connection) Failure { + const failure = self.failure; + if (self.phase == .ready) self.failure = .none; + return failure; + } + + pub fn receive(self: *Connection, offer: *const Offer, mime: []const u8, fd: std.posix.fd_t) bool { + if (mime.len > MAX_MIME_BYTES) return false; + var mime_z: [MAX_MIME_BYTES:0]u8 = undefined; + @memcpy(mime_z[0..mime.len], mime); + mime_z[mime.len] = 0; + var arguments = [_]WlArgument{ .{ .s = mime_z[0..mime.len :0].ptr }, .{ .h = fd } }; + _ = self.marshal(offer.proxy, if (self.core_data_device) 1 else 0, null, 1, 0, &arguments); + return self.queueFlush() != .failed; + } + + pub fn publishText(self: *Connection, primary: bool, data: []u8) SelectionResult { + self.failure = .none; + if (self.core_data_device) return .unsupported; + if (primary and !self.primary_supported) return .unsupported; + const manager = self.manager orelse return self.selectionFailure(.protocol); + const device = self.device orelse return self.selectionFailure(.protocol); + if (!self.canPublishProvider(primary)) return self.selectionFailure(.provider); + const slot = self.freeProviderSlot() orelse return self.selectionFailure(.provider); + const provider = self.allocator.create(Provider) catch return self.selectionFailure(.provider); + const transfers = self.allocator.alloc(Transfer, self.max_provider_transfers) catch { + self.allocator.destroy(provider); + return self.selectionFailure(.provider); + }; + const source = self.marshal(manager, 0, &self.metadata.source, 1, 0, &.{}) orelse { + self.allocator.free(transfers); + self.allocator.destroy(provider); + return self.selectionFailure(.provider); + }; + provider.* = .{ + .connection = self, + .source = source, + .primary = primary, + .data = data, + .transfers = transfers, + }; + if (self.symbols.wl_proxy_add_listener(source, source_listener[0..].ptr, provider) != 0 or + !self.offerSource(source, "text/plain") or + !self.offerSource(source, "text/plain;charset=utf-8")) + { + self.symbols.wl_proxy_destroy(source); + self.allocator.free(transfers); + self.allocator.destroy(provider); + return self.selectionFailure(.provider); + } + var arguments = [_]WlArgument{.{ .o = source }}; + _ = self.marshal(device, if (primary) 2 else 0, null, self.symbols.wl_proxy_get_version(device), 0, &arguments); + if (!self.displayHealthy()) { + self.symbols.wl_proxy_destroy(source); + self.allocator.free(transfers); + self.allocator.destroy(provider); + return self.selectionFailure(.protocol); + } + const flush_result = self.queueFlush(); + if (flush_result == .failed) { + self.symbols.wl_proxy_destroy(source); + self.allocator.free(transfers); + self.allocator.destroy(provider); + _ = self.fail(.flush); + return .failed; + } + const previous = if (primary) self.primary_provider else self.clipboard_provider; + if (previous) |old| self.retireProvider(old); + slot.* = provider; + if (primary) self.primary_provider = provider else self.clipboard_provider = provider; + return if (flush_result == .complete) .ok else .committed; + } + + pub fn clearSelection(self: *Connection, primary: bool) SelectionResult { + self.failure = .none; + if (self.core_data_device) return .unsupported; + if (primary and !self.primary_supported) return .unsupported; + const device = self.device orelse return self.selectionFailure(.protocol); + var arguments = [_]WlArgument{.{ .o = null }}; + _ = self.marshal(device, if (primary) 2 else 0, null, self.symbols.wl_proxy_get_version(device), 0, &arguments); + if (!self.displayHealthy()) return self.selectionFailure(.protocol); + const flush_result = self.queueFlush(); + if (flush_result == .failed) { + _ = self.fail(.flush); + return .failed; + } + const provider = if (primary) self.primary_provider else self.clipboard_provider; + if (provider) |value| self.retireProvider(value); + if (primary) self.primary_provider = null else self.clipboard_provider = null; + return if (flush_result == .complete) .ok else .committed; + } + + pub fn hasProviders(self: *const Connection) bool { + for (self.providers) |provider| if (provider != null) return true; + return false; + } + + pub fn hasWork(self: *const Connection) bool { + // An in-flight barrier counts as work so a cancelled read cannot strand + // its sync callback once no operation keeps the connection draining. + return self.output_pending or self.barrier_callback != null or self.hasProviders(); + } + + pub fn releaseProviders(self: *Connection) void { + for (&self.providers) |*slot| { + const provider = slot.* orelse continue; + self.freeProvider(provider); + slot.* = null; + } + self.clipboard_provider = null; + self.primary_provider = null; + if (self.display != null) _ = self.queueFlush(); + } + + pub fn retireProviders(self: *Connection) void { + self.clipboard_provider = null; + self.primary_provider = null; + self.output_pending = false; + for (&self.providers) |*slot| { + const provider = slot.* orelse continue; + provider.cancelled = true; + if (provider.transfer_count > 0) continue; + self.freeProvider(provider); + slot.* = null; + } + } + + pub fn driveProviderUnit(self: *Connection) bool { + var visited: u8 = 0; + while (visited < MAX_PROVIDERS) : (visited += 1) { + const slot_index = self.provider_cursor; + self.provider_cursor = (self.provider_cursor + 1) % MAX_PROVIDERS; + const provider = self.providers[slot_index] orelse continue; + if (provider.cancelled and provider.transfer_count == 0) { + self.retireProvider(provider); + continue; + } + if (provider.transfer_count == 0) continue; + const transfer_index = provider.transfer_cursor % provider.transfer_count; + provider.transfer_cursor = (provider.transfer_cursor + 1) % provider.transfer_count; + self.driveProviderTransfer(provider, transfer_index); + break; + } + return self.hasWork(); + } + + pub fn offeredMime(self: *const Connection, offer: *const Offer, preferred: []const u8) ?MimeMatch { + const requested = canonicalMimeEssence(preferred) orelse return null; + for (offer.mimes[0..offer.mime_count]) |*mime| { + const offered = canonicalMimeEssence(mime.slice()) orelse continue; + if (std.ascii.eqlIgnoreCase(offered, requested)) return .{ .offered = mime.slice(), .requested = requested }; + if (self.core_data_device and std.ascii.eqlIgnoreCase(requested, "image/png") and + std.ascii.eqlIgnoreCase(offered, "image/bmp")) return .{ .offered = mime.slice(), .requested = requested }; + } + return null; + } + + fn start(self: *Connection) !void { + const display = self.symbols.wl_display_connect(null) orelse return error.ConnectFailed; + self.symbols.wl_display_set_max_buffer_size(display, WAYLAND_CONNECTION_BUFFER_SIZE_MAX); + self.display = display; + const registry = self.marshal(@ptrCast(display), 1, self.symbols.wl_registry_interface, 1, 0, &.{}) orelse + return error.RegistryFailed; + self.registry = registry; + if (self.addListener(registry, ®istry_listener) != 0) return error.ListenerFailed; + self.phase = .registry; + if (!self.sendSync()) return error.SyncFailed; + if (self.queueFlush() == .failed) return error.FlushFailed; + } + + fn sendSync(self: *Connection) bool { + const display = self.display orelse return false; + const callback = self.marshal(@ptrCast(display), 0, self.symbols.wl_callback_interface, 1, 0, &.{}) orelse + return false; + self.sync_callback = callback; + return self.addListener(callback, &callback_listener) == 0 and self.queueFlush() != .failed; + } + + fn dispatchAvailable(self: *Connection) bool { + if (comptime builtin.os.tag != .linux) return false; + const display = self.display orelse return false; + var dispatched_count: u32 = 0; + while (dispatched_count < DISPATCH_EVENTS_PER_DRIVE_MAX) { + const dispatched = self.symbols.wl_display_dispatch_pending_single(display); + if (dispatched < 0) return false; + if (dispatched == 0) break; + dispatched_count += 1; + } + if (self.queueFlush() == .failed) return false; + if (dispatched_count == DISPATCH_EVENTS_PER_DRIVE_MAX) return true; + if (self.symbols.wl_display_prepare_read(display) != 0) { + return self.symbols.wl_display_dispatch_pending_single(display) >= 0; + } + + var descriptor = [_]std.posix.pollfd{.{ + .fd = self.symbols.wl_display_get_fd(display), + .events = std.posix.POLL.IN, + .revents = 0, + }}; + const count = std.posix.poll(&descriptor, 0) catch { + self.symbols.wl_display_cancel_read(display); + return false; + }; + if (count == 0 or descriptor[0].revents & std.posix.POLL.IN == 0) { + self.symbols.wl_display_cancel_read(display); + return descriptor[0].revents & (std.posix.POLL.ERR | std.posix.POLL.HUP | std.posix.POLL.NVAL) == 0; + } + if (self.symbols.wl_display_read_events(display) < 0) return false; + return true; + } + + fn queueFlush(self: *Connection) FlushResult { + const result = self.flushOutput(); + if (result == .failed and self.failure == .none) self.failure = .flush; + self.output_pending = result == .pending; + return result; + } + + fn flushOutput(self: *Connection) FlushResult { + if (comptime builtin.is_test) { + if (self.flush_outcome_override) |outcome| { + self.test_flush_marshal_count = self.test_marshal_count; + const flush_result = classifyFlush(outcome.result, outcome.errno); + self.output_pending = flush_result == .pending; + return flush_result; + } + } + const result = self.symbols.wl_display_flush(self.display.?); + const flush_result = classifyFlush(result, if (result < 0) std.posix.errno(result) else .SUCCESS); + self.output_pending = flush_result == .pending; + return flush_result; + } + + fn offerSource(self: *Connection, source: *WlProxy, mime: []const u8) bool { + if (mime.len > MAX_MIME_BYTES) return false; + var mime_z: [MAX_MIME_BYTES:0]u8 = undefined; + @memcpy(mime_z[0..mime.len], mime); + mime_z[mime.len] = 0; + var arguments = [_]WlArgument{.{ .s = mime_z[0..mime.len :0].ptr }}; + _ = self.marshal(source, 0, null, 1, 0, &arguments); + return self.displayHealthy(); + } + + fn freeProviderSlot(self: *Connection) ?*?*Provider { + for (&self.providers) |*slot| if (slot.* == null) return slot; + return null; + } + + fn canPublishProvider(self: *Connection, primary: bool) bool { + self.reclaimCancelledProviders(); + var count: u8 = 0; + for (self.providers) |candidate| { + const provider = candidate orelse continue; + if (provider.primary == primary) count += 1; + } + return count < 2; + } + + fn reclaimCancelledProviders(self: *Connection) void { + for (self.providers) |slot| { + const provider = slot orelse continue; + if (!provider.cancelled or provider.transfer_count > 0) continue; + self.retireProvider(provider); + } + } + + fn retireProvider(self: *Connection, provider: *Provider) void { + if (self.clipboard_provider == provider) self.clipboard_provider = null; + if (self.primary_provider == provider) self.primary_provider = null; + if (!provider.cancelled or provider.transfer_count > 0) return; + for (&self.providers) |*slot| { + if (slot.* != provider) continue; + self.freeProvider(provider); + slot.* = null; + return; + } + } + + fn freeProvider(self: *Connection, provider: *Provider) void { + for (provider.transfers[0..provider.transfer_count]) |transfer| std.posix.close(transfer.fd); + self.allocator.free(provider.transfers); + if (provider.data.len > 0) self.allocator.free(provider.data); + self.destroyProtocolProxy(provider.source, 1); + self.allocator.destroy(provider); + } + + fn destroyProtocolProxy(self: *Connection, proxy: *WlProxy, opcode: u32) void { + _ = self.marshal(proxy, opcode, null, self.symbols.wl_proxy_get_version(proxy), 1, &.{}); + } + + fn destroyOffer(self: *Connection, proxy: *WlProxy) void { + self.destroyProtocolProxy(proxy, if (self.core_data_device) 2 else 1); + } + + fn createCoreHelper(self: *Connection) bool { + if (comptime builtin.os.tag != .linux) return false; + std.debug.assert(self.core_data_device); + std.debug.assert(self.helper_surface == null); + // Core data-device selection is focus-scoped, so WSLg needs a transparent 1x1 focused surface. + const fd = std.posix.memfd_create("opentui-clipboard", std.os.linux.MFD.CLOEXEC) catch return false; + self.helper_fd = fd; + std.posix.ftruncate(fd, 4) catch { + self.destroyCoreHelper(); + return false; + }; + + var new_id = [_]WlArgument{.{ .n = 0 }}; + self.helper_surface = self.marshal( + self.compositor.?, + 0, + self.symbols.wl_surface_interface, + 2, + 0, + &new_id, + ) orelse { + self.destroyCoreHelper(); + return false; + }; + var shell_arguments = [_]WlArgument{ .{ .n = 0 }, .{ .o = self.helper_surface.? } }; + self.helper_shell_surface = self.marshal( + self.shell.?, + 0, + self.symbols.wl_shell_surface_interface, + 1, + 0, + &shell_arguments, + ) orelse { + self.destroyCoreHelper(); + return false; + }; + if (self.addListener(self.helper_shell_surface.?, &shell_surface_listener) != 0) { + self.destroyCoreHelper(); + return false; + } + _ = self.marshal(self.helper_shell_surface.?, 3, null, 1, 0, &.{}); + var title = [_]WlArgument{.{ .s = "OpenTUI Clipboard" }}; + _ = self.marshal(self.helper_shell_surface.?, 8, null, 1, 0, &title); + + var pool_arguments = [_]WlArgument{ .{ .n = 0 }, .{ .h = fd }, .{ .i = 4 } }; + self.helper_pool = self.marshal( + self.shm.?, + 0, + self.symbols.wl_shm_pool_interface, + 1, + 0, + &pool_arguments, + ) orelse { + self.destroyCoreHelper(); + return false; + }; + var buffer_arguments = [_]WlArgument{ + .{ .n = 0 }, + .{ .i = 0 }, + .{ .i = 1 }, + .{ .i = 1 }, + .{ .i = 4 }, + .{ .u = 0 }, + }; + self.helper_buffer = self.marshal( + self.helper_pool.?, + 0, + self.symbols.wl_buffer_interface, + 1, + 0, + &buffer_arguments, + ) orelse { + self.destroyCoreHelper(); + return false; + }; + var attach_arguments = [_]WlArgument{ .{ .o = self.helper_buffer.? }, .{ .i = 0 }, .{ .i = 0 } }; + _ = self.marshal(self.helper_surface.?, 1, null, 2, 0, &attach_arguments); + var damage_arguments = [_]WlArgument{ .{ .i = 0 }, .{ .i = 0 }, .{ .i = 1 }, .{ .i = 1 } }; + _ = self.marshal(self.helper_surface.?, 2, null, 2, 0, &damage_arguments); + _ = self.marshal(self.helper_surface.?, 6, null, 2, 0, &.{}); + if (self.queueFlush() != .failed) return true; + self.destroyCoreHelper(); + return false; + } + + fn destroyCoreHelper(self: *Connection) void { + if (self.helper_buffer) |proxy| self.destroyProtocolProxy(proxy, 0); + if (self.helper_pool) |proxy| self.destroyProtocolProxy(proxy, 1); + if (self.helper_shell_surface) |proxy| self.symbols.wl_proxy_destroy(proxy); + if (self.helper_surface) |proxy| self.destroyProtocolProxy(proxy, 0); + if (self.helper_fd) |fd| std.posix.close(fd); + self.helper_shell_surface = null; + self.helper_surface = null; + self.helper_buffer = null; + self.helper_pool = null; + self.helper_fd = null; + self.core_focus_entered = false; + self.core_focus_lost = false; + self.core_selection_seen = false; + if (self.display != null) _ = self.queueFlush(); + } + + fn driveProviderTransfer(_: *Connection, provider: *Provider, index: u32) void { + const transfer = &provider.transfers[index]; + const now_ns = clipboard_clock.nowNs(); + if (providerTransferExpired(transfer.last_progress_ns, now_ns)) { + finishProviderTransfer(provider, index); + return; + } + const remaining = provider.data[transfer.offset..]; + const chunk = remaining[0..@min(remaining.len, 64 * 1024)]; + const count = writeProviderPipe(transfer.fd, chunk) catch |err| switch (err) { + error.WouldBlock => return, + else => 0, + }; + transfer.offset += count; + if (count > 0) transfer.last_progress_ns = now_ns; + if (count == 0 or transfer.offset == provider.data.len) { + finishProviderTransfer(provider, index); + } + } + + fn bindDevice(self: *Connection) SelectionResult { + const seat = self.selectSeat() orelse return .unsupported; + if (self.ext_global == null and self.wlr_global == null) return self.bindCoreDevice(seat); + const kind: protocol.Kind = if (self.ext_global != null) .ext else .wlr; + self.metadata.init(kind, self.symbols.wl_seat_interface); + const manager_version: u32 = if (kind == .ext) 1 else @min(self.wlr_version, 2); + const manager_global = if (kind == .ext) self.ext_global.? else self.wlr_global.?; + const manager = self.bind(manager_global, &self.metadata.manager, manager_version) orelse return .failed; + self.manager = manager; + self.bound_manager_global = manager_global; + var arguments = [_]WlArgument{ .{ .n = 0 }, .{ .o = seat.proxy } }; + const device = self.marshal(manager, 1, &self.metadata.device, manager_version, 0, &arguments) orelse return .failed; + self.device = device; + self.bound_seat_global = seat.global_name; + if (self.addListener(device, &device_listener) != 0) return .failed; + self.primary_supported = false; + return switch (self.queueFlush()) { + .complete => .ok, + .pending => .pending, + .failed => .failed, + }; + } + + fn coreDataDeviceAvailable(self: *const Connection) bool { + return self.allow_core_data_device and self.core_manager_global != null and + self.compositor_global != null and self.shm_global != null and self.shell_global != null; + } + + fn bindCoreDevice(self: *Connection, seat: *const Seat) SelectionResult { + if (!self.coreDataDeviceAvailable()) return .unsupported; + if (seat.capabilities & WL_SEAT_CAPABILITY_KEYBOARD == 0) return .unsupported; + const manager = self.bind( + self.core_manager_global.?, + self.symbols.wl_data_device_manager_interface, + 1, + ) orelse return .failed; + self.manager = manager; + self.core_data_device = true; + self.bound_manager_global = self.core_manager_global.?; + self.compositor = self.bind( + self.compositor_global.?, + self.symbols.wl_compositor_interface, + 2, + ) orelse return .failed; + self.shm = self.bind(self.shm_global.?, self.symbols.wl_shm_interface, 1) orelse return .failed; + self.shell = self.bind(self.shell_global.?, self.symbols.wl_shell_interface, 1) orelse return .failed; + + var device_arguments = [_]WlArgument{ .{ .n = 0 }, .{ .o = seat.proxy } }; + self.device = self.marshal( + manager, + 1, + self.symbols.wl_data_device_interface, + 1, + 0, + &device_arguments, + ) orelse return .failed; + if (self.addListener(self.device.?, &core_device_listener) != 0) return .failed; + + var keyboard_arguments = [_]WlArgument{.{ .n = 0 }}; + self.keyboard = self.marshal( + seat.proxy, + 1, + self.symbols.wl_keyboard_interface, + 1, + 0, + &keyboard_arguments, + ) orelse return .failed; + if (self.addListener(self.keyboard.?, &keyboard_listener) != 0) return .failed; + self.bound_seat_global = seat.global_name; + self.primary_supported = false; + return switch (self.queueFlush()) { + .complete => .ok, + .pending => .pending, + .failed => .failed, + }; + } + + fn selectSeat(self: *Connection) ?*Seat { + if (self.seat_count == 0) return null; + if (self.requested_seat.len > 0) { + for (self.seats[0..self.seat_count]) |*seat| { + if (std.mem.eql(u8, seat.nameSlice(), self.requested_seat)) return seat; + } + return null; + } + if (self.seats_overflowed) return null; + if (self.environment_seat.len > 0) { + for (self.seats[0..self.seat_count]) |*seat| { + if (std.mem.eql(u8, seat.nameSlice(), self.environment_seat)) return seat; + } + } + if (self.seat_count != 1) return null; + return &self.seats[0]; + } + + fn bind(self: *Connection, name: u32, interface: *const WlInterface, version: u32) ?*WlProxy { + var arguments = [_]WlArgument{ + .{ .u = name }, + .{ .s = interface.name }, + .{ .u = version }, + .{ .n = 0 }, + }; + return self.marshal(self.registry.?, 0, interface, version, 0, &arguments); + } + + fn marshal( + self: *Connection, + proxy: *WlProxy, + opcode: u32, + interface: ?*const WlInterface, + version: u32, + flags: u32, + arguments: []const WlArgument, + ) ?*WlProxy { + if (comptime builtin.is_test) self.test_marshal_count += 1; + var empty: [1]WlArgument = undefined; + const pointer: [*]WlArgument = if (arguments.len == 0) &empty else @constCast(arguments.ptr); + return self.symbols.wl_proxy_marshal_array_flags(proxy, opcode, interface, version, flags, pointer); + } + + fn displayHealthy(self: *const Connection) bool { + if (comptime builtin.is_test) { + if (self.display_error_override) |display_error| return display_error == 0; + } + const display = self.display orelse return false; + return self.symbols.wl_display_get_error(display) == 0; + } + + fn addListener(self: *Connection, proxy: *WlProxy, listener: []const *const anyopaque) c_int { + return self.symbols.wl_proxy_add_listener(proxy, listener.ptr, self); + } + + fn fail(self: *Connection, failure: Failure) Progress { + if (self.barrier_callback) |callback| self.symbols.wl_proxy_destroy(callback); + self.barrier_callback = null; + if (self.failure == .none) self.failure = failure; + self.phase = .failed; + return .failed; + } + + fn selectionFailure(self: *Connection, failure: Failure) SelectionResult { + if (self.failure == .none) self.failure = failure; + return .failed; + } + + fn registryGlobal( + data: ?*anyopaque, + _: ?*WlProxy, + name: u32, + interface_pointer: [*:0]const u8, + version: u32, + ) callconv(.c) void { + const self: *Connection = @ptrCast(@alignCast(data.?)); + const interface = std.mem.span(interface_pointer); + if (std.mem.eql(u8, interface, "ext_data_control_manager_v1")) { + self.ext_global = name; + } else if (std.mem.eql(u8, interface, "zwlr_data_control_manager_v1")) { + self.wlr_global = name; + self.wlr_version = version; + } else if (std.mem.eql(u8, interface, "wl_data_device_manager")) { + self.core_manager_global = name; + } else if (std.mem.eql(u8, interface, "wl_compositor")) { + self.compositor_global = name; + } else if (std.mem.eql(u8, interface, "wl_shm")) { + self.shm_global = name; + } else if (std.mem.eql(u8, interface, "wl_shell")) { + self.shell_global = name; + } else if (std.mem.eql(u8, interface, "wl_seat")) { + self.addSeat(name, version); + } + } + + fn registryGlobalRemove(data: ?*anyopaque, _: ?*WlProxy, name: u32) callconv(.c) void { + const self: *Connection = @ptrCast(@alignCast(data.?)); + if (self.removeGlobal(name)) |proxy| self.symbols.wl_proxy_destroy(proxy); + } + + fn removeGlobal(self: *Connection, name: u32) ?*WlProxy { + if (self.ext_global == name) self.ext_global = null; + if (self.wlr_global == name) { + self.wlr_global = null; + self.wlr_version = 0; + } + if (self.core_manager_global == name) self.core_manager_global = null; + if (self.compositor_global == name) self.compositor_global = null; + if (self.shm_global == name) self.shm_global = null; + if (self.shell_global == name) self.shell_global = null; + if (self.bound_manager_global == name) { + const manager = self.manager; + self.manager = null; + self.bound_manager_global = null; + return manager; + } + + var index: u8 = 0; + while (index < self.seat_count) : (index += 1) { + if (self.seats[index].global_name != name) continue; + const proxy = self.seats[index].proxy; + self.seat_count -= 1; + self.seats[index] = self.seats[self.seat_count]; + if (self.bound_seat_global == name) { + _ = self.fail(.protocol); + } + return proxy; + } + return null; + } + + fn addSeat(self: *Connection, name: u32, version: u32) void { + if (self.seat_count == MAX_SEATS) { + self.seats_overflowed = true; + return; + } + const proxy = self.bind(name, self.symbols.wl_seat_interface, @min(version, 2)) orelse { + self.seats_overflowed = true; + return; + }; + const seat = &self.seats[self.seat_count]; + seat.* = .{ .global_name = name, .proxy = proxy }; + self.seat_count += 1; + _ = self.addListener(proxy, &seat_listener); + } + + fn seatCapabilities(data: ?*anyopaque, proxy: ?*WlProxy, capabilities: u32) callconv(.c) void { + const self: *Connection = @ptrCast(@alignCast(data.?)); + for (self.seats[0..self.seat_count]) |*seat| { + if (seat.proxy != proxy) continue; + const had_keyboard = seat.capabilities & WL_SEAT_CAPABILITY_KEYBOARD != 0; + seat.capabilities = capabilities; + const has_keyboard = capabilities & WL_SEAT_CAPABILITY_KEYBOARD != 0; + if (self.core_data_device and self.bound_seat_global == seat.global_name and had_keyboard and !has_keyboard) { + self.core_focus_lost = true; + _ = self.fail(.protocol); + } + return; + } + } + + fn seatName(data: ?*anyopaque, proxy: ?*WlProxy, name_pointer: [*:0]const u8) callconv(.c) void { + const self: *Connection = @ptrCast(@alignCast(data.?)); + const name = std.mem.span(name_pointer); + if (name.len > MAX_SEAT_NAME_BYTES) return; + for (self.seats[0..self.seat_count]) |*seat| { + if (seat.proxy != proxy) continue; + @memcpy(seat.name[0..name.len], name); + seat.name_length = @intCast(name.len); + return; + } + } + + fn callbackDone(data: ?*anyopaque, callback: ?*WlProxy, _: u32) callconv(.c) void { + const self: *Connection = @ptrCast(@alignCast(data.?)); + if (callback) |proxy| self.symbols.wl_proxy_destroy(proxy); + self.sync_callback = null; + self.sync_done = true; + } + + fn deviceDataOffer(data: ?*anyopaque, _: ?*WlProxy, offer_proxy: ?*WlProxy) callconv(.c) void { + const self: *Connection = @ptrCast(@alignCast(data.?)); + const proxy = offer_proxy orelse return; + if (self.offer_count == MAX_OFFERS) { + self.destroyOffer(proxy); + return; + } + self.offers[self.offer_count] = .{ .proxy = proxy }; + self.offer_count += 1; + _ = self.addListener(proxy, &offer_listener); + } + + fn deviceSelection(data: ?*anyopaque, _: ?*WlProxy, offer: ?*WlProxy) callconv(.c) void { + const self: *Connection = @ptrCast(@alignCast(data.?)); + if (self.core_data_device and (self.core_focus_users == 0 or self.helper_surface == null)) { + if (offer) |proxy| self.removeOffer(proxy); + return; + } + if (self.clipboard_offer) |previous| { + if (previous != offer and previous != self.primary_offer) self.removeOffer(previous); + } + self.clipboard_offer = offer; + if (self.core_data_device) self.core_selection_seen = true; + } + + fn coreDeviceEnter( + data: ?*anyopaque, + _: ?*WlProxy, + _: u32, + _: ?*WlProxy, + _: i32, + _: i32, + offer: ?*WlProxy, + ) callconv(.c) void { + const self: *Connection = @ptrCast(@alignCast(data.?)); + if (offer) |proxy| self.removeOffer(proxy); + } + + fn coreDeviceLeave(_: ?*anyopaque, _: ?*WlProxy) callconv(.c) void {} + + fn coreDeviceMotion(_: ?*anyopaque, _: ?*WlProxy, _: u32, _: i32, _: i32) callconv(.c) void {} + + fn coreDeviceDrop(_: ?*anyopaque, _: ?*WlProxy) callconv(.c) void {} + + fn keyboardKeymap(_: ?*anyopaque, _: ?*WlProxy, _: u32, fd: std.posix.fd_t, _: u32) callconv(.c) void { + std.posix.close(fd); + } + + fn keyboardEnter( + data: ?*anyopaque, + _: ?*WlProxy, + _: u32, + surface: ?*WlProxy, + _: ?*anyopaque, + ) callconv(.c) void { + const self: *Connection = @ptrCast(@alignCast(data.?)); + if (surface == self.helper_surface) self.core_focus_entered = true; + } + + fn keyboardLeave(data: ?*anyopaque, _: ?*WlProxy, _: u32, surface: ?*WlProxy) callconv(.c) void { + const self: *Connection = @ptrCast(@alignCast(data.?)); + if (surface != self.helper_surface) return; + self.core_focus_entered = false; + self.core_selection_seen = false; + self.core_focus_lost = true; + if (self.clipboard_offer) |offer| self.removeOffer(offer); + self.clipboard_offer = null; + } + + fn keyboardKey(_: ?*anyopaque, _: ?*WlProxy, _: u32, _: u32, _: u32, _: u32) callconv(.c) void {} + + fn keyboardModifiers( + _: ?*anyopaque, + _: ?*WlProxy, + _: u32, + _: u32, + _: u32, + _: u32, + _: u32, + ) callconv(.c) void {} + + fn shellSurfacePing(data: ?*anyopaque, shell_surface: ?*WlProxy, serial: u32) callconv(.c) void { + const self: *Connection = @ptrCast(@alignCast(data.?)); + var arguments = [_]WlArgument{.{ .u = serial }}; + _ = self.marshal(shell_surface.?, 0, null, 1, 0, &arguments); + _ = self.queueFlush(); + } + + fn shellSurfaceConfigure(_: ?*anyopaque, _: ?*WlProxy, _: u32, _: i32, _: i32) callconv(.c) void {} + + fn shellSurfacePopupDone(_: ?*anyopaque, _: ?*WlProxy) callconv(.c) void {} + + fn deviceFinished(data: ?*anyopaque, _: ?*WlProxy) callconv(.c) void { + const self: *Connection = @ptrCast(@alignCast(data.?)); + _ = self.fail(.protocol); + } + + fn devicePrimarySelection(data: ?*anyopaque, _: ?*WlProxy, offer: ?*WlProxy) callconv(.c) void { + const self: *Connection = @ptrCast(@alignCast(data.?)); + self.primary_supported = true; + if (self.primary_offer) |previous| { + if (previous != offer and previous != self.clipboard_offer) self.removeOffer(previous); + } + self.primary_offer = offer; + } + + fn removeOffer(self: *Connection, proxy: *WlProxy) void { + var index: u8 = 0; + while (index < self.offer_count) : (index += 1) { + if (self.offers[index].proxy != proxy) continue; + self.destroyOffer(proxy); + self.offer_count -= 1; + self.offers[index] = self.offers[self.offer_count]; + return; + } + } + + fn offerMime(data: ?*anyopaque, offer_proxy: ?*WlProxy, mime_pointer: [*:0]const u8) callconv(.c) void { + const self: *Connection = @ptrCast(@alignCast(data.?)); + const proxy = offer_proxy orelse return; + const mime = std.mem.span(mime_pointer); + if (!isRelevantMime(mime)) return; + for (self.offers[0..self.offer_count]) |*offer| { + if (offer.proxy != proxy) continue; + const essence = canonicalMimeEssence(mime).?; + for (offer.mimes[0..offer.mime_count]) |*existing| { + const existing_essence = canonicalMimeEssence(existing.slice()).?; + if (std.ascii.eqlIgnoreCase(existing_essence, essence)) return; + } + if (offer.mime_count == MAX_OFFER_MIME_TYPES) return; + const entry = &offer.mimes[offer.mime_count]; + @memcpy(entry.bytes[0..mime.len], mime); + entry.length = @intCast(mime.len); + offer.mime_count += 1; + return; + } + } + + fn sourceSend(data: ?*anyopaque, _: ?*WlProxy, mime_pointer: [*:0]const u8, fd: i32) callconv(.c) void { + if (comptime builtin.os.tag != .linux) return; + const provider: *Provider = @ptrCast(@alignCast(data.?)); + const mime = std.mem.span(mime_pointer); + const essence = canonicalMimeEssence(mime); + if (essence == null or !std.ascii.eqlIgnoreCase(essence.?, "text/plain") or + provider.cancelled or provider.transfer_count == provider.transfers.len) + { + std.posix.close(fd); + return; + } + const flags = std.posix.fcntl(fd, std.posix.F.GETFL, 0) catch { + std.posix.close(fd); + return; + }; + const nonblocking: u32 = @bitCast(std.posix.O{ .NONBLOCK = true }); + _ = std.posix.fcntl(fd, std.posix.F.SETFL, flags | nonblocking) catch { + std.posix.close(fd); + return; + }; + provider.transfers[provider.transfer_count] = .{ + .fd = fd, + .last_progress_ns = clipboard_clock.nowNs(), + }; + provider.transfer_count += 1; + } + + fn sourceCancelled(data: ?*anyopaque, _: ?*WlProxy) callconv(.c) void { + const provider: *Provider = @ptrCast(@alignCast(data.?)); + provider.cancelled = true; + if (provider.connection.clipboard_provider == provider) provider.connection.clipboard_provider = null; + if (provider.connection.primary_provider == provider) provider.connection.primary_provider = null; + } +}; + +fn writeProviderPipe(fd: std.posix.fd_t, bytes: []const u8) std.posix.WriteError!usize { + if (comptime builtin.os.tag != .linux) return std.posix.write(fd, bytes); + + const Signal = struct { + extern "c" fn sigpending(set: *std.posix.sigset_t) c_int; + extern "c" fn sigtimedwait( + set: *const std.posix.sigset_t, + info: ?*std.posix.siginfo_t, + timeout: *const std.posix.timespec, + ) c_int; + }; + + var blocked = std.posix.sigemptyset(); + std.posix.sigaddset(&blocked, std.posix.SIG.PIPE); + var previous: std.posix.sigset_t = undefined; + std.posix.sigprocmask(std.posix.SIG.BLOCK, &blocked, &previous); + defer std.posix.sigprocmask(std.posix.SIG.SETMASK, &previous, null); + + var pending = std.posix.sigemptyset(); + const pipe_was_pending = Signal.sigpending(&pending) != 0 or std.posix.sigismember(&pending, std.posix.SIG.PIPE); + return std.posix.write(fd, bytes) catch |err| { + if (err == error.BrokenPipe and !pipe_was_pending) { + const timeout: std.posix.timespec = .{ .sec = 0, .nsec = 0 }; + while (true) { + const result = Signal.sigtimedwait(&blocked, null, &timeout); + if (result >= 0 or std.posix.errno(result) != .INTR) break; + } + } + return err; + }; +} + +fn finishProviderTransfer(provider: *Provider, index: u32) void { + std.posix.close(provider.transfers[index].fd); + provider.transfer_count -= 1; + provider.transfers[index] = provider.transfers[provider.transfer_count]; + if (provider.transfer_count == 0) provider.transfer_cursor = 0 else provider.transfer_cursor %= provider.transfer_count; +} + +fn providerTransferExpired(last_progress_ns: i128, now_ns: i128) bool { + return now_ns - last_progress_ns >= PROVIDER_TRANSFER_IDLE_TIMEOUT_NS; +} + +fn isRelevantMime(mime: []const u8) bool { + if (mime.len > MAX_MIME_BYTES) return false; + return canonicalMimeEssence(mime) != null; +} + +pub fn canonicalMimeEssence(mime: []const u8) ?[]const u8 { + var parts = std.mem.splitScalar(u8, mime, ';'); + const raw_essence = std.mem.trim(u8, parts.next() orelse return null, " \t\r\n"); + const essence: []const u8 = if (std.ascii.eqlIgnoreCase(raw_essence, "text/plain")) + "text/plain" + else if (std.ascii.eqlIgnoreCase(raw_essence, "image/png")) + "image/png" + else if (std.ascii.eqlIgnoreCase(raw_essence, "image/bmp")) + "image/bmp" + else + return null; + + while (parts.next()) |raw_parameter| { + const parameter = std.mem.trim(u8, raw_parameter, " \t\r\n"); + if (parameter.len == 0) continue; + const separator = std.mem.indexOfScalar(u8, parameter, '=') orelse continue; + const name = std.mem.trim(u8, parameter[0..separator], " \t\r\n"); + if (!std.ascii.eqlIgnoreCase(name, "charset")) continue; + if (!std.ascii.eqlIgnoreCase(essence, "text/plain")) continue; + var value = std.mem.trim(u8, parameter[separator + 1 ..], " \t\r\n"); + if (value.len >= 2 and value[0] == '"' and value[value.len - 1] == '"') { + value = std.mem.trim(u8, value[1 .. value.len - 1], " \t\r\n"); + } + if (!std.ascii.eqlIgnoreCase(value, "utf-8")) return null; + } + return essence; +} + +fn classifyFlush(result: c_int, errno: std.posix.E) FlushResult { + if (result >= 0) return .complete; + return if (errno == .AGAIN) .pending else .failed; +} + +const registry_listener = [_]*const anyopaque{ + @ptrCast(&Connection.registryGlobal), + @ptrCast(&Connection.registryGlobalRemove), +}; +const seat_listener = [_]*const anyopaque{ + @ptrCast(&Connection.seatCapabilities), + @ptrCast(&Connection.seatName), +}; +const callback_listener = [_]*const anyopaque{@ptrCast(&Connection.callbackDone)}; +const barrier_listener = [_]*const anyopaque{@ptrCast(&Connection.barrierDone)}; +const device_listener = [_]*const anyopaque{ + @ptrCast(&Connection.deviceDataOffer), + @ptrCast(&Connection.deviceSelection), + @ptrCast(&Connection.deviceFinished), + @ptrCast(&Connection.devicePrimarySelection), +}; +const core_device_listener = [_]*const anyopaque{ + @ptrCast(&Connection.deviceDataOffer), + @ptrCast(&Connection.coreDeviceEnter), + @ptrCast(&Connection.coreDeviceLeave), + @ptrCast(&Connection.coreDeviceMotion), + @ptrCast(&Connection.coreDeviceDrop), + @ptrCast(&Connection.deviceSelection), +}; +const keyboard_listener = [_]*const anyopaque{ + @ptrCast(&Connection.keyboardKeymap), + @ptrCast(&Connection.keyboardEnter), + @ptrCast(&Connection.keyboardLeave), + @ptrCast(&Connection.keyboardKey), + @ptrCast(&Connection.keyboardModifiers), +}; +const shell_surface_listener = [_]*const anyopaque{ + @ptrCast(&Connection.shellSurfacePing), + @ptrCast(&Connection.shellSurfaceConfigure), + @ptrCast(&Connection.shellSurfacePopupDone), +}; +const offer_listener = [_]*const anyopaque{@ptrCast(&Connection.offerMime)}; +const source_listener = [_]*const anyopaque{ + @ptrCast(&Connection.sourceSend), + @ptrCast(&Connection.sourceCancelled), +}; + +test "Wayland seat selection treats XDG_SEAT as advisory but explicit configuration as strict" { + const seat_interface: WlInterface = .{ + .name = "wl_seat", + .version = 2, + .method_count = 0, + .methods = null, + .event_count = 0, + .events = null, + }; + var symbols: linux.WaylandSymbols = undefined; + symbols.wl_proxy_marshal_array_flags = testMarshalFailure; + symbols.wl_seat_interface = &seat_interface; + var connection = Connection.init(std.testing.allocator, &symbols, "", "", 1); + connection.registry = @ptrFromInt(3); + connection.seat_count = 1; + connection.seats[0] = .{ .global_name = 1, .proxy = @ptrFromInt(1) }; + @memcpy(connection.seats[0].name[0..8], "Hyprland"); + connection.seats[0].name_length = 8; + + connection.requested_seat = ""; + connection.environment_seat = "seat0"; + try std.testing.expectEqual(@as(u32, 1), connection.selectSeat().?.global_name); + + connection.requested_seat = "seat0"; + try std.testing.expect(connection.selectSeat() == null); + + connection.requested_seat = "Hyprland"; + try std.testing.expectEqual(@as(u32, 1), connection.selectSeat().?.global_name); + + connection.requested_seat = ""; + connection.environment_seat = "seat0"; + connection.seat_count = 2; + connection.seats[1] = .{ .global_name = 2, .proxy = @ptrFromInt(2) }; + @memcpy(connection.seats[1].name[0..5], "other"); + connection.seats[1].name_length = 5; + try std.testing.expect(connection.selectSeat() == null); + + // Removing one valid global leaves the remaining sole seat unambiguous. + try std.testing.expect(connection.removeGlobal(2) == @as(*WlProxy, @ptrFromInt(2))); + try std.testing.expect(connection.selectSeat() == &connection.seats[0]); + + // A failed bind makes automatic fallback conservative without retaining removed globals. + connection.addSeat(3, 2); + try std.testing.expect(connection.seats_overflowed); + connection.requested_seat = "Hyprland"; + try std.testing.expect(connection.selectSeat() == &connection.seats[0]); + connection.requested_seat = ""; + try std.testing.expect(connection.selectSeat() == null); +} + +test "Wayland core helper destroys its role before its surface" { + var symbols: linux.WaylandSymbols = undefined; + symbols.wl_proxy_marshal_array_flags = testMarshal; + symbols.wl_proxy_destroy = testRoleDestroy; + symbols.wl_proxy_get_version = testProxyVersion; + var connection = Connection.init(std.testing.allocator, &symbols, "", "", 1); + connection.helper_surface = @ptrFromInt(1); + connection.helper_shell_surface = @ptrFromInt(2); + test_role_destroyed_before_surface = false; + test_role_connection = &connection; + + connection.destroyCoreHelper(); + + try std.testing.expect(test_role_destroyed_before_surface); +} + +test "Wayland flush completion distinguishes EAGAIN from completion and hard failure" { + try std.testing.expectEqual(FlushResult.complete, classifyFlush(0, .SUCCESS)); + try std.testing.expectEqual(FlushResult.pending, classifyFlush(-1, .AGAIN)); + try std.testing.expectEqual(FlushResult.failed, classifyFlush(-1, .PIPE)); +} + +test "Wayland connection setup applies the finite receive buffer cap" { + var symbols: linux.WaylandSymbols = undefined; + symbols.wl_display_connect = testDisplayConnect; + symbols.wl_display_set_max_buffer_size = testSetMaxBufferSize; + symbols.wl_proxy_marshal_array_flags = testMarshal; + symbols.wl_proxy_add_listener = testAddListener; + var connection = Connection.init(std.testing.allocator, &symbols, "", "", 1); + connection.flush_outcome_override = .{ .result = 0, .errno = .SUCCESS }; + test_max_buffer_size = 0; + + try connection.start(); + + try std.testing.expectEqual(@as(usize, WAYLAND_CONNECTION_BUFFER_SIZE_MAX), test_max_buffer_size); +} + +test "Wayland dispatch drains queued callbacks in one invocation" { + if (comptime builtin.os.tag != .linux) return error.SkipZigTest; + var symbols: linux.WaylandSymbols = undefined; + symbols.wl_display_dispatch_pending_single = testDispatchQueue; + symbols.wl_display_prepare_read = testPrepareReadBlocked; + var connection = Connection.init(std.testing.allocator, &symbols, "", "", 1); + connection.display = @ptrFromInt(1); + connection.flush_outcome_override = .{ .result = 0, .errno = .SUCCESS }; + test_dispatch_call_count = 0; + test_dispatched_callback_count = 0; + test_dispatch_queue_length = 3; + + try std.testing.expect(connection.dispatchAvailable()); + + try std.testing.expectEqual(@as(u32, 3), test_dispatched_callback_count); + try std.testing.expectEqual(@as(u32, 0), test_dispatch_queue_length); +} + +test "Wayland dispatch bounds the events drained per invocation" { + if (comptime builtin.os.tag != .linux) return error.SkipZigTest; + var symbols: linux.WaylandSymbols = undefined; + symbols.wl_display_dispatch_pending_single = testDispatchQueue; + symbols.wl_display_prepare_read = testPrepareReadBlocked; + var connection = Connection.init(std.testing.allocator, &symbols, "", "", 1); + connection.display = @ptrFromInt(1); + connection.flush_outcome_override = .{ .result = 0, .errno = .SUCCESS }; + test_dispatch_call_count = 0; + test_dispatched_callback_count = 0; + test_dispatch_queue_length = DISPATCH_EVENTS_PER_DRIVE_MAX + 5; + + try std.testing.expect(connection.dispatchAvailable()); + + try std.testing.expectEqual(@as(u32, DISPATCH_EVENTS_PER_DRIVE_MAX), test_dispatched_callback_count); + try std.testing.expectEqual(@as(u32, 5), test_dispatch_queue_length); +} + +test "Wayland selection barrier orders admissions across in-flight syncs" { + var symbols: linux.WaylandSymbols = undefined; + symbols.wl_proxy_marshal_array_flags = testMarshal; + symbols.wl_proxy_add_listener = testAddListener; + symbols.wl_proxy_destroy = testDestroyProxy; + var connection = Connection.init(std.testing.allocator, &symbols, "", "", 1); + connection.display = @ptrFromInt(1); + connection.flush_outcome_override = .{ .result = 0, .errno = .SUCCESS }; + + try std.testing.expect(!connection.hasWork()); + const first = connection.requestSelectionBarrier().?; + try std.testing.expectEqual(@as(u64, 1), first); + try std.testing.expectEqual(@as(u8, 1), connection.test_marshal_count); + try std.testing.expect(!connection.selectionBarrierReached(first)); + try std.testing.expect(connection.hasWork()); + + // A read admitted while a sync is in flight must wait for the follow-up. + const second = connection.requestSelectionBarrier().?; + try std.testing.expectEqual(@as(u64, 2), second); + try std.testing.expectEqual(@as(u8, 1), connection.test_marshal_count); + + Connection.barrierDone(&connection, connection.barrier_callback, 0); + try std.testing.expect(connection.selectionBarrierReached(first)); + try std.testing.expect(!connection.selectionBarrierReached(second)); + try std.testing.expectEqual(@as(u8, 2), connection.test_marshal_count); + + Connection.barrierDone(&connection, connection.barrier_callback, 0); + try std.testing.expect(connection.selectionBarrierReached(second)); + try std.testing.expect(connection.barrier_callback == null); + try std.testing.expectEqual(@as(u8, 2), connection.test_marshal_count); + try std.testing.expect(!connection.hasWork()); +} + +test "Wayland selection barrier reports sync issue failure to the caller" { + var symbols: linux.WaylandSymbols = undefined; + symbols.wl_proxy_marshal_array_flags = testMarshalFailure; + var connection = Connection.init(std.testing.allocator, &symbols, "", "", 1); + connection.display = @ptrFromInt(1); + + try std.testing.expect(connection.requestSelectionBarrier() == null); + try std.testing.expect(connection.barrier_callback == null); + + symbols.wl_proxy_marshal_array_flags = testMarshal; + symbols.wl_proxy_add_listener = testAddListener; + symbols.wl_proxy_destroy = testDestroyProxy; + var flush_failure = Connection.init(std.testing.allocator, &symbols, "", "", 1); + flush_failure.display = @ptrFromInt(1); + flush_failure.flush_outcome_override = .{ .result = -1, .errno = .PIPE }; + try std.testing.expect(flush_failure.requestSelectionBarrier() == null); + try std.testing.expect(flush_failure.barrier_callback == null); + try std.testing.expectEqual(Phase.failed, flush_failure.phase); + try std.testing.expect(!flush_failure.hasWork()); +} + +test "Wayland read queues callbacks for the next dispatch invocation" { + if (comptime builtin.os.tag != .linux) return error.SkipZigTest; + const pipe = try std.posix.pipe2(.{ .CLOEXEC = true }); + defer std.posix.close(pipe[0]); + defer std.posix.close(pipe[1]); + _ = try std.posix.write(pipe[1], "x"); + var symbols: linux.WaylandSymbols = undefined; + symbols.wl_display_dispatch_pending_single = testDispatchNoPending; + symbols.wl_display_prepare_read = testPrepareReadReady; + symbols.wl_display_get_fd = testDisplayGetFd; + symbols.wl_display_read_events = testReadEvents; + symbols.wl_display_cancel_read = testCancelRead; + var connection = Connection.init(std.testing.allocator, &symbols, "", "", 1); + connection.display = @ptrFromInt(1); + connection.flush_outcome_override = .{ .result = 0, .errno = .SUCCESS }; + test_display_fd = pipe[0]; + test_dispatch_call_count = 0; + test_read_event_count = 0; + + try std.testing.expect(connection.dispatchAvailable()); + + try std.testing.expectEqual(@as(u32, 1), test_dispatch_call_count); + try std.testing.expectEqual(@as(u32, 1), test_read_event_count); +} + +test "Wayland writes and clears settle deterministically after marshalling for every flush outcome" { + const TestCase = struct { + flush: FlushOutcome, + selection: SelectionResult, + output_pending: bool, + phase: Phase, + }; + const cases = [_]TestCase{ + .{ .flush = .{ .result = 0, .errno = .SUCCESS }, .selection = .ok, .output_pending = false, .phase = .ready }, + .{ .flush = .{ .result = -1, .errno = .AGAIN }, .selection = .committed, .output_pending = true, .phase = .ready }, + .{ .flush = .{ .result = -1, .errno = .PIPE }, .selection = .failed, .output_pending = false, .phase = .failed }, + }; + + var symbols: linux.WaylandSymbols = undefined; + symbols.wl_proxy_marshal_array_flags = testMarshal; + symbols.wl_proxy_add_listener = testAddListener; + symbols.wl_proxy_destroy = testDestroyProxy; + symbols.wl_proxy_get_version = testProxyVersion; + + for (cases) |case| { + var write = testReadyConnection(&symbols, case.flush); + try std.testing.expectEqual(case.selection, write.publishText(false, &.{})); + try std.testing.expectEqual(case.output_pending, write.output_pending); + try std.testing.expectEqual(case.phase, write.phase); + try std.testing.expectEqual(@as(u8, 4), write.test_flush_marshal_count); + write.releaseProviders(); + + var clear = testReadyConnection(&symbols, case.flush); + try std.testing.expectEqual(case.selection, clear.clearSelection(false)); + try std.testing.expectEqual(case.output_pending, clear.output_pending); + try std.testing.expectEqual(case.phase, clear.phase); + try std.testing.expectEqual(@as(u8, 1), clear.test_flush_marshal_count); + } +} + +test "Wayland fatal selection flush preserves local ownership state" { + var symbols: linux.WaylandSymbols = undefined; + symbols.wl_proxy_marshal_array_flags = testMarshal; + symbols.wl_proxy_add_listener = testAddListener; + symbols.wl_proxy_destroy = testDestroyProxy; + symbols.wl_proxy_get_version = testProxyVersion; + + var failed_write = testReadyConnection(&symbols, .{ .result = -1, .errno = .PIPE }); + const caller_data = try std.testing.allocator.dupe(u8, "caller-owned"); + defer std.testing.allocator.free(caller_data); + try std.testing.expectEqual(SelectionResult.failed, failed_write.publishText(false, caller_data)); + try std.testing.expect(!failed_write.hasProviders()); + + var failed_clear = testReadyConnection(&symbols, .{ .result = 0, .errno = .SUCCESS }); + const provider_data = try std.testing.allocator.dupe(u8, "provider-owned"); + try std.testing.expectEqual(SelectionResult.ok, failed_clear.publishText(false, provider_data)); + const provider = failed_clear.clipboard_provider.?; + failed_clear.flush_outcome_override = .{ .result = -1, .errno = .PIPE }; + try std.testing.expectEqual(SelectionResult.failed, failed_clear.clearSelection(false)); + try std.testing.expect(failed_clear.clipboard_provider == provider); + failed_clear.releaseProviders(); +} + +fn testReadyConnection(symbols: *const linux.WaylandSymbols, flush: FlushOutcome) Connection { + var connection = Connection.init(std.testing.allocator, symbols, "", "", 1); + connection.display = @ptrFromInt(1); + connection.manager = @ptrFromInt(2); + connection.device = @ptrFromInt(3); + connection.phase = .ready; + connection.flush_outcome_override = flush; + connection.display_error_override = 0; + return connection; +} + +var test_max_buffer_size: usize = 0; +var test_dispatch_call_count: u32 = 0; +var test_dispatched_callback_count: u32 = 0; +var test_dispatch_queue_length: u32 = 0; +var test_display_fd: std.posix.fd_t = -1; +var test_read_event_count: u32 = 0; +var test_display_error_call_count: u32 = 0; +var test_role_connection: ?*Connection = null; +var test_role_destroyed_before_surface = false; + +fn testDisplayConnect(_: ?[*:0]const u8) callconv(.c) ?*linux.WlDisplay { + return @ptrFromInt(1); +} + +fn testSetMaxBufferSize(_: *linux.WlDisplay, size: usize) callconv(.c) void { + test_max_buffer_size = size; +} + +fn testDispatchQueue(_: *linux.WlDisplay) callconv(.c) c_int { + test_dispatch_call_count += 1; + if (test_dispatch_queue_length == 0) return 0; + test_dispatch_queue_length -= 1; + test_dispatched_callback_count += 1; + return 1; +} + +fn testPrepareReadBlocked(_: *linux.WlDisplay) callconv(.c) c_int { + return -1; +} + +fn testDispatchNoPending(_: *linux.WlDisplay) callconv(.c) c_int { + test_dispatch_call_count += 1; + return 0; +} + +fn testPrepareReadReady(_: *linux.WlDisplay) callconv(.c) c_int { + return 0; +} + +fn testDisplayGetFd(_: *linux.WlDisplay) callconv(.c) c_int { + return test_display_fd; +} + +fn testReadEvents(_: *linux.WlDisplay) callconv(.c) c_int { + test_read_event_count += 1; + return 0; +} + +fn testCancelRead(_: *linux.WlDisplay) callconv(.c) void {} + +fn testDisplayError(_: *linux.WlDisplay) callconv(.c) c_int { + test_display_error_call_count += 1; + return 1; +} + +fn testMarshal( + _: *WlProxy, + _: u32, + _: ?*const WlInterface, + _: u32, + _: u32, + _: [*]WlArgument, +) callconv(.c) ?*WlProxy { + return @ptrFromInt(4); +} + +fn testMarshalFailure( + _: *WlProxy, + _: u32, + _: ?*const WlInterface, + _: u32, + _: u32, + _: [*]WlArgument, +) callconv(.c) ?*WlProxy { + return null; +} + +fn testRoleDestroy(proxy: *WlProxy) callconv(.c) void { + test_role_destroyed_before_surface = @intFromPtr(proxy) == 2 and test_role_connection.?.test_marshal_count == 0; +} + +fn testAddListener(_: *WlProxy, _: [*]const *const anyopaque, _: ?*anyopaque) callconv(.c) c_int { + return 0; +} + +fn testDestroyProxy(_: *WlProxy) callconv(.c) void {} + +fn testProxyVersion(_: *WlProxy) callconv(.c) u32 { + return 1; +} + +test "Wayland MIME retention ignores irrelevant metadata without consuming the bounded set" { + try std.testing.expect(isRelevantMime("image/png")); + try std.testing.expect(isRelevantMime("image/bmp")); + try std.testing.expect(isRelevantMime("TEXT/PLAIN;CHARSET=UTF-8")); + try std.testing.expect(!isRelevantMime("application/x-irrelevant")); + try std.testing.expect(!isRelevantMime(&([_]u8{'x'} ** (MAX_MIME_BYTES + 1)))); + + const proxy: *WlProxy = @ptrFromInt(1); + var connection = testOfferConnection(proxy); + var index: u8 = 0; + while (index < MAX_OFFER_MIME_TYPES) : (index += 1) { + Connection.offerMime(&connection, proxy, "application/x-irrelevant"); + } + Connection.offerMime(&connection, proxy, "image/png"); + try std.testing.expectEqual(@as(u8, 1), connection.offers[0].mime_count); + try std.testing.expectEqualStrings("image/png", connection.offers[0].mimes[0].slice()); +} + +test "Wayland MIME matching parses essence and UTF-8 charset parameters" { + const proxy: *WlProxy = @ptrFromInt(1); + var connection = testOfferConnection(proxy); + Connection.offerMime(&connection, proxy, " Text/Plain ; format=flowed ; charset=\"UtF-8\" "); + Connection.offerMime(&connection, proxy, "text/plain;charset=iso-8859-1"); + Connection.offerMime(&connection, proxy, " IMAGE/PNG ; version=1 "); + + try std.testing.expectEqualStrings( + "text/plain", + connection.offeredMime(&connection.offers[0], " TEXT/PLAIN ; charset=UTF-8 ").?.requested, + ); + try std.testing.expectEqualStrings( + " Text/Plain ; format=flowed ; charset=\"UtF-8\" ", + connection.offeredMime(&connection.offers[0], "text/plain").?.offered, + ); + try std.testing.expectEqualStrings( + "image/png", + connection.offeredMime(&connection.offers[0], "image/png").?.requested, + ); +} + +test "Wayland MIME retention deduplicates canonical supported essences" { + const proxy: *WlProxy = @ptrFromInt(1); + var connection = testOfferConnection(proxy); + + var index: u8 = 0; + while (index < MAX_OFFER_MIME_TYPES) : (index += 1) { + Connection.offerMime(&connection, proxy, "text/plain;charset=UTF-8"); + } + Connection.offerMime(&connection, proxy, "text/plain; charset=utf-8; format=flowed"); + Connection.offerMime(&connection, proxy, "image/png; version=1"); + + try std.testing.expectEqual(@as(u8, 2), connection.offers[0].mime_count); + try std.testing.expectEqualStrings("text/plain;charset=UTF-8", connection.offers[0].mimes[0].slice()); + try std.testing.expectEqualStrings("image/png; version=1", connection.offers[0].mimes[1].slice()); +} + +test "Wayland BMP conversion fallback is restricted to WSL core compatibility" { + const proxy: *WlProxy = @ptrFromInt(1); + var connection = testOfferConnection(proxy); + Connection.offerMime(&connection, proxy, "image/bmp"); + + try std.testing.expect(connection.offeredMime(&connection.offers[0], "image/png") == null); + + connection.core_data_device = true; + try std.testing.expectEqualStrings( + "image/bmp", + connection.offeredMime(&connection.offers[0], "image/png").?.offered, + ); +} + +fn testOfferConnection(proxy: *WlProxy) Connection { + var connection: Connection = undefined; + connection.core_data_device = false; + connection.offer_count = 1; + connection.offers[0] = .{ .proxy = proxy }; + return connection; +} + +test "Wayland core data-device fallback is WSL-gated and read-only" { + var connection: Connection = undefined; + connection.allow_core_data_device = false; + connection.core_manager_global = 1; + connection.compositor_global = 2; + connection.shm_global = 3; + connection.shell_global = 4; + try std.testing.expect(!connection.coreDataDeviceAvailable()); + connection.allow_core_data_device = true; + try std.testing.expect(connection.coreDataDeviceAvailable()); + + connection.core_data_device = true; + connection.failure = .provider; + try std.testing.expectEqual(SelectionResult.unsupported, connection.publishText(false, &.{})); + try std.testing.expectEqual(SelectionResult.unsupported, connection.clearSelection(false)); + try std.testing.expectEqual(Failure.none, connection.failure); +} + +test "Wayland core helper acquisition fails closed outside Linux" { + if (comptime builtin.os.tag == .linux) return error.SkipZigTest; + var connection: Connection = undefined; + connection.core_data_device = true; + connection.phase = .ready; + connection.core_focus_users = 0; + connection.clipboard_offer = null; + + try std.testing.expectEqual(Progress.failed, connection.acquireCoreSelection()); + try std.testing.expectEqual(@as(u32, 0), connection.core_focus_users); +} + +test "Wayland core helper requires an advertised keyboard capability" { + const seat_proxy: *WlProxy = @ptrFromInt(1); + var connection: Connection = undefined; + connection.seat_count = 1; + connection.seats[0] = .{ .global_name = 1, .proxy = seat_proxy }; + + try std.testing.expectEqual(@as(u32, 0), connection.seats[0].capabilities); + Connection.seatCapabilities(&connection, seat_proxy, WL_SEAT_CAPABILITY_KEYBOARD); + try std.testing.expectEqual(WL_SEAT_CAPABILITY_KEYBOARD, connection.seats[0].capabilities); + Connection.seatCapabilities(&connection, seat_proxy, 0); + try std.testing.expectEqual(@as(u32, 0), connection.seats[0].capabilities); + + connection.allow_core_data_device = true; + connection.core_manager_global = 1; + connection.compositor_global = 2; + connection.shm_global = 3; + connection.shell_global = 4; + try std.testing.expectEqual(SelectionResult.unsupported, connection.bindCoreDevice(&connection.seats[0])); +} + +test "Wayland core focus lifecycle rejects stale and unfocused offers" { + var symbols: linux.WaylandSymbols = undefined; + symbols.wl_proxy_marshal_array_flags = testMarshal; + symbols.wl_proxy_get_version = testProxyVersion; + const offer: *WlProxy = @ptrFromInt(1); + const surface: *WlProxy = @ptrFromInt(2); + var connection: Connection = undefined; + connection.symbols = &symbols; + connection.core_data_device = true; + connection.core_focus_entered = false; + connection.core_focus_users = 0; + connection.helper_surface = null; + connection.offer_count = 1; + connection.offers[0] = .{ .proxy = offer }; + connection.clipboard_offer = null; + connection.primary_offer = null; + connection.test_marshal_count = 0; + + Connection.deviceSelection(&connection, null, offer); + try std.testing.expectEqual(@as(u8, 0), connection.offer_count); + try std.testing.expect(connection.clipboard_offer == null); + + connection.core_focus_entered = true; + connection.core_focus_lost = false; + connection.core_selection_seen = true; + connection.helper_surface = surface; + connection.offer_count = 1; + connection.offers[0] = .{ .proxy = offer }; + connection.clipboard_offer = offer; + Connection.keyboardLeave(&connection, null, 1, surface); + try std.testing.expect(!connection.core_focus_entered); + try std.testing.expect(connection.core_focus_lost); + try std.testing.expect(!connection.core_selection_seen); + try std.testing.expectEqual(@as(u8, 0), connection.offer_count); + try std.testing.expect(connection.clipboard_offer == null); +} + +test "Wayland core selection becomes ready after focus and selection in either order" { + const offer: *WlProxy = @ptrFromInt(1); + const surface: *WlProxy = @ptrFromInt(2); + var connection: Connection = undefined; + connection.core_data_device = true; + connection.phase = .ready; + connection.core_focus_users = 1; + connection.helper_surface = surface; + connection.core_focus_entered = false; + connection.core_focus_lost = false; + connection.core_selection_seen = false; + connection.clipboard_offer = null; + connection.primary_offer = null; + connection.offer_count = 1; + connection.offers[0] = .{ .proxy = offer }; + + Connection.deviceSelection(&connection, null, offer); + try std.testing.expectEqual(offer, connection.clipboard_offer.?); + try std.testing.expectEqual(Progress.pending, connection.coreSelectionProgress()); + Connection.keyboardEnter(&connection, null, 1, surface, null); + try std.testing.expectEqual(Progress.ready, connection.coreSelectionProgress()); + + connection.core_focus_entered = false; + connection.core_selection_seen = false; + connection.clipboard_offer = null; + Connection.keyboardEnter(&connection, null, 2, surface, null); + try std.testing.expectEqual(Progress.pending, connection.coreSelectionProgress()); + Connection.deviceSelection(&connection, null, offer); + try std.testing.expectEqual(Progress.ready, connection.coreSelectionProgress()); +} + +test "Wayland bound core seat keyboard loss fails active and future acquisitions" { + const seat_proxy: *WlProxy = @ptrFromInt(1); + var connection: Connection = undefined; + connection.core_data_device = true; + connection.bound_seat_global = 7; + connection.seat_count = 1; + connection.seats[0] = .{ + .global_name = 7, + .proxy = seat_proxy, + .capabilities = WL_SEAT_CAPABILITY_KEYBOARD, + }; + connection.core_focus_users = 1; + connection.helper_surface = @ptrFromInt(2); + connection.core_focus_lost = false; + connection.failure = .none; + connection.phase = .ready; + connection.barrier_callback = null; + + Connection.seatCapabilities(&connection, seat_proxy, 0); + + try std.testing.expectEqual(Progress.failed, connection.coreSelectionProgress()); + connection.core_focus_users = 0; + try std.testing.expectEqual(Progress.failed, connection.acquireCoreSelection()); + try std.testing.expectEqual(Phase.failed, connection.phase); + try std.testing.expectEqual(Failure.protocol, connection.failure); +} + +test "Wayland provider retirement preserves active transfers" { + var connection: Connection = undefined; + var transfer: [1]Transfer = undefined; + var provider: Provider = .{ + .connection = &connection, + .source = undefined, + .primary = false, + .data = &.{}, + .transfers = &transfer, + .transfer_count = 1, + }; + connection.clipboard_provider = &provider; + connection.primary_provider = null; + connection.providers = .{ &provider, null, null, null }; + + connection.retireProvider(&provider); + + try std.testing.expect(connection.clipboard_provider == null); + try std.testing.expect(connection.providers[0] == &provider); + + connection.clipboard_provider = &provider; + connection.retireProviders(); + try std.testing.expect(connection.providers[0] == &provider); +} + +test "Wayland failed connection cancels providers and frees idle generations" { + var symbols: linux.WaylandSymbols = undefined; + symbols.wl_proxy_marshal_array_flags = testMarshal; + symbols.wl_proxy_get_version = testProxyVersion; + var connection = Connection.init(std.testing.allocator, &symbols, "", "", 1); + const provider = try std.testing.allocator.create(Provider); + provider.* = .{ + .connection = &connection, + .source = @ptrFromInt(1), + .primary = false, + .data = &.{}, + .transfers = try std.testing.allocator.alloc(Transfer, 1), + }; + connection.providers[0] = provider; + connection.clipboard_provider = provider; + + connection.retireProviders(); + + try std.testing.expect(connection.providers[0] == null); + try std.testing.expect(!connection.hasWork()); +} + +test "Wayland failed connection lets active transfers drain but rejects new sends" { + if (comptime builtin.os.tag != .linux) return error.SkipZigTest; + var symbols: linux.WaylandSymbols = undefined; + symbols.wl_proxy_marshal_array_flags = testMarshal; + symbols.wl_proxy_get_version = testProxyVersion; + var connection = Connection.init(std.testing.allocator, &symbols, "", "", 1); + const provider = try std.testing.allocator.create(Provider); + provider.* = .{ + .connection = &connection, + .source = @ptrFromInt(1), + .primary = false, + .data = &.{}, + .transfers = try std.testing.allocator.alloc(Transfer, 1), + .transfer_count = 1, + }; + const active_pipe = try std.posix.pipe2(.{ .CLOEXEC = true }); + defer std.posix.close(active_pipe[0]); + provider.transfers[0] = .{ .fd = active_pipe[1], .last_progress_ns = 0 }; + connection.providers[0] = provider; + connection.clipboard_provider = provider; + + connection.retireProviders(); + try std.testing.expect(provider.cancelled); + try std.testing.expect(connection.providers[0] == provider); + + const rejected_pipe = try std.posix.pipe2(.{ .CLOEXEC = true }); + defer std.posix.close(rejected_pipe[0]); + Connection.sourceSend(provider, null, "text/plain", rejected_pipe[1]); + try std.testing.expectEqual(@as(u32, 1), provider.transfer_count); + + finishProviderTransfer(provider, 0); + _ = connection.driveProviderUnit(); + try std.testing.expect(connection.providers[0] == null); + try std.testing.expect(!connection.hasWork()); +} + +test "Wayland retired provider accepts queued sends until compositor cancellation" { + if (comptime builtin.os.tag != .linux) return error.SkipZigTest; + try clipboard_clock.init(); + var connection: Connection = undefined; + var transfers: [1]Transfer = undefined; + var provider: Provider = .{ + .connection = &connection, + .source = undefined, + .primary = false, + .data = &.{}, + .transfers = &transfers, + }; + connection.clipboard_provider = null; + connection.primary_provider = null; + connection.providers = .{ &provider, null, null, null }; + const pipe = try std.posix.pipe2(.{ .CLOEXEC = true }); + defer std.posix.close(pipe[0]); + + Connection.sourceSend(&provider, null, "text/plain", pipe[1]); + + try std.testing.expectEqual(@as(u32, 1), provider.transfer_count); + Connection.sourceCancelled(&provider, null); + finishProviderTransfer(&provider, 0); +} + +test "Wayland reserves provider capacity independently for each selection" { + var connection: Connection = undefined; + var current: Provider = undefined; + current.primary = false; + var retired: Provider = undefined; + retired.primary = false; + connection.clipboard_provider = ¤t; + connection.primary_provider = null; + connection.providers = .{ ¤t, &retired, null, null }; + + try std.testing.expect(!connection.canPublishProvider(false)); + try std.testing.expect(connection.canPublishProvider(true)); + connection.clipboard_provider = null; + try std.testing.expect(!connection.canPublishProvider(false)); + connection.providers[1] = null; + try std.testing.expect(connection.canPublishProvider(false)); +} + +test "Wayland publication reclaims cancelled providers before checking capacity" { + var symbols: linux.WaylandSymbols = undefined; + symbols.wl_proxy_marshal_array_flags = testMarshal; + symbols.wl_proxy_add_listener = testAddListener; + symbols.wl_proxy_destroy = testDestroyProxy; + symbols.wl_proxy_get_version = testProxyVersion; + var connection = testReadyConnection(&symbols, .{ .result = 0, .errno = .SUCCESS }); + + for (connection.providers[0..2], 0..) |*slot, index| { + const provider = try std.testing.allocator.create(Provider); + provider.* = .{ + .connection = &connection, + .source = @ptrFromInt(10 + index), + .primary = false, + .data = &.{}, + .transfers = try std.testing.allocator.alloc(Transfer, 1), + .cancelled = index == 1, + }; + slot.* = provider; + } + connection.clipboard_provider = connection.providers[0]; + + try std.testing.expectEqual(SelectionResult.ok, connection.publishText(false, &.{})); + connection.releaseProviders(); +} + +test "Wayland clear retires the current provider at the generation bound" { + var symbols: linux.WaylandSymbols = undefined; + symbols.wl_proxy_marshal_array_flags = testMarshal; + symbols.wl_proxy_destroy = testDestroyProxy; + symbols.wl_proxy_get_version = testProxyVersion; + var connection = testReadyConnection(&symbols, .{ .result = 0, .errno = .SUCCESS }); + var old_transfers: [1]Transfer = undefined; + var current_transfers: [1]Transfer = undefined; + var old: Provider = .{ + .connection = &connection, + .source = @ptrFromInt(4), + .primary = false, + .data = &.{}, + .transfers = &old_transfers, + .transfer_count = 1, + }; + var current: Provider = .{ + .connection = &connection, + .source = @ptrFromInt(5), + .primary = false, + .data = &.{}, + .transfers = ¤t_transfers, + .transfer_count = 1, + }; + connection.providers = .{ &old, ¤t, null, null }; + connection.clipboard_provider = ¤t; + + try std.testing.expectEqual(SelectionResult.ok, connection.clearSelection(false)); + try std.testing.expect(connection.clipboard_provider == null); +} + +test "Wayland provider transfers expire after a bounded idle interval" { + try std.testing.expect(!providerTransferExpired(1, 1 + PROVIDER_TRANSFER_IDLE_TIMEOUT_NS - 1)); + try std.testing.expect(providerTransferExpired(1, 1 + PROVIDER_TRANSFER_IDLE_TIMEOUT_NS)); +} + +test "Wayland provider handles a closed consumer pipe locally" { + if (comptime builtin.os.tag != .linux) return error.SkipZigTest; + + const State = struct { + var sigpipe_count: std.atomic.Value(u32) = .init(0); + + fn handleSigpipe(_: c_int) callconv(.c) void { + _ = sigpipe_count.fetchAdd(1, .seq_cst); + } + }; + + var old_action: std.posix.Sigaction = undefined; + const action: std.posix.Sigaction = .{ + .handler = .{ .handler = &State.handleSigpipe }, + .mask = std.posix.sigemptyset(), + .flags = 0, + }; + std.posix.sigaction(std.posix.SIG.PIPE, &action, &old_action); + defer std.posix.sigaction(std.posix.SIG.PIPE, &old_action, null); + + var old_mask: std.posix.sigset_t = undefined; + var unblocked = std.posix.sigemptyset(); + std.posix.sigaddset(&unblocked, std.posix.SIG.PIPE); + std.posix.sigprocmask(std.posix.SIG.UNBLOCK, &unblocked, &old_mask); + defer std.posix.sigprocmask(std.posix.SIG.SETMASK, &old_mask, null); + + const pipe = try std.posix.pipe2(.{ .CLOEXEC = true }); + std.posix.close(pipe[0]); + defer std.posix.close(pipe[1]); + + try std.testing.expectError(error.BrokenPipe, writeProviderPipe(pipe[1], "clipboard")); + try std.testing.expectEqual(@as(u32, 0), State.sigpipe_count.load(.seq_cst)); +} + +test "Wayland registry removal invalidates cached globals and selected seats" { + var connection: Connection = undefined; + connection.ext_global = 10; + connection.wlr_global = 11; + connection.wlr_version = 2; + connection.seat_count = 1; + connection.seats[0] = .{ .global_name = 12, .proxy = @ptrFromInt(1) }; + connection.bound_seat_global = 12; + connection.failure = .none; + connection.phase = .ready; + connection.barrier_callback = null; + + try std.testing.expect(connection.removeGlobal(10) == null); + try std.testing.expect(connection.ext_global == null); + try std.testing.expectEqual(@as(u32, 11), connection.wlr_global.?); + try std.testing.expect(connection.removeGlobal(12) != null); + try std.testing.expectEqual(@as(u8, 0), connection.seat_count); + try std.testing.expectEqual(Phase.failed, connection.phase); + try std.testing.expectEqual(Failure.protocol, connection.failure); +} + +test "Wayland registry removal invalidates the bound manager" { + const manager: *WlProxy = @ptrFromInt(1); + var connection: Connection = undefined; + connection.ext_global = 20; + connection.wlr_global = null; + connection.manager = manager; + connection.bound_manager_global = 20; + + try std.testing.expectEqual(manager, connection.removeGlobal(20).?); + try std.testing.expect(connection.ext_global == null); + try std.testing.expect(connection.manager == null); + try std.testing.expect(connection.bound_manager_global == null); +} + +test "Wayland selection failures do not leak across operations" { + var connection: Connection = undefined; + connection.failure = .provider; + connection.phase = .ready; + connection.primary_supported = false; + connection.manager = null; + + try std.testing.expectEqual(SelectionResult.failed, connection.publishText(false, &.{})); + try std.testing.expectEqual(Failure.protocol, connection.failure); + try std.testing.expectEqual(Failure.protocol, connection.takeFailure()); + try std.testing.expectEqual(Failure.none, connection.failure); +} + +test "Wayland local marshal failure preserves the current provider" { + var symbols: linux.WaylandSymbols = undefined; + symbols.wl_proxy_marshal_array_flags = testMarshal; + symbols.wl_proxy_add_listener = testAddListener; + symbols.wl_proxy_destroy = testDestroyProxy; + symbols.wl_proxy_get_version = testProxyVersion; + symbols.wl_display_get_error = testDisplayError; + var connection = testReadyConnection(&symbols, .{ .result = 0, .errno = .SUCCESS }); + connection.display_error_override = null; + var transfers: [1]Transfer = undefined; + var current: Provider = .{ + .connection = &connection, + .source = @ptrFromInt(8), + .primary = false, + .data = &.{}, + .transfers = &transfers, + }; + connection.providers = .{ ¤t, null, null, null }; + connection.clipboard_provider = ¤t; + + test_display_error_call_count = 0; + try std.testing.expectEqual(SelectionResult.failed, connection.clearSelection(false)); + try std.testing.expectEqual(@as(u32, 1), test_display_error_call_count); + try std.testing.expect(connection.clipboard_provider == ¤t); +} + +test "Wayland failed source offer does not replace the current provider" { + var symbols: linux.WaylandSymbols = undefined; + symbols.wl_proxy_marshal_array_flags = testMarshal; + symbols.wl_proxy_add_listener = testAddListener; + symbols.wl_proxy_destroy = testDestroyProxy; + symbols.wl_proxy_get_version = testProxyVersion; + var connection = testReadyConnection(&symbols, .{ .result = 0, .errno = .SUCCESS }); + connection.display_error_override = 1; + var transfers: [1]Transfer = undefined; + var current: Provider = .{ + .connection = &connection, + .source = @ptrFromInt(8), + .primary = false, + .data = &.{}, + .transfers = &transfers, + }; + connection.providers = .{ ¤t, null, null, null }; + connection.clipboard_provider = ¤t; + + try std.testing.expectEqual(SelectionResult.failed, connection.publishText(false, &.{})); + try std.testing.expect(connection.clipboard_provider == ¤t); +} diff --git a/packages/core/src/zig/clipboard/windows-dib.zig b/packages/core/src/zig/clipboard/windows-dib.zig new file mode 100644 index 0000000000..a412c19bb1 --- /dev/null +++ b/packages/core/src/zig/clipboard/windows-dib.zig @@ -0,0 +1,758 @@ +const std = @import("std"); +const clipboard_clock = @import("clock.zig"); + +const Allocator = std.mem.Allocator; + +const BI_RGB: u32 = 0; +const BI_BITFIELDS: u32 = 3; +const BI_ALPHABITFIELDS: u32 = 6; +const LCS_WINDOWS_COLOR_SPACE: u32 = 0x57696e20; +const LCS_SRGB: u32 = 0x73524742; +const CONVERSION_STOP_INTERVAL: usize = 4096; +const PNG_CHUNK_LENGTH_MAX: usize = 0x7fff_ffff; +const PNG_SIGNATURE = "\x89PNG\r\n\x1a\n"; + +pub const ConvertError = error{ + InvalidData, + Unsupported, + LimitExceeded, + OutOfMemory, + Cancelled, + TimedOut, +}; + +pub const ConvertOptions = struct { + max_output_bytes: u32, + max_image_pixels: u32, + max_conversion_bytes: u32, + cancel_requested: ?*const std.atomic.Value(bool) = null, + deadline_ns: i128, +}; + +const ChannelMask = struct { + mask: u32, + shift: u5, + maximum: u32, +}; + +const DibInfo = struct { + width: u32, + height: u32, + top_down: bool, + bits_per_pixel: u16, + row_stride: usize, + pixel_offset: usize, + red: ?ChannelMask, + green: ?ChannelMask, + blue: ?ChannelMask, + alpha: ?ChannelMask, + + fn channelCount(info: DibInfo) u8 { + return if (info.alpha == null) 3 else 4; + } +}; + +const BoundedOutput = struct { + allocator: Allocator, + bytes: std.ArrayListUnmanaged(u8) = .empty, + max_bytes: usize, + + fn deinit(output: *BoundedOutput) void { + output.bytes.deinit(output.allocator); + } + + fn append(output: *BoundedOutput, data: []const u8) ConvertError!void { + try output.ensureUnusedCapacity(data.len); + output.bytes.appendSliceAssumeCapacity(data); + } + + fn appendByte(output: *BoundedOutput, byte: u8) ConvertError!void { + try output.ensureUnusedCapacity(1); + output.bytes.appendAssumeCapacity(byte); + } + + fn appendInt(output: *BoundedOutput, value: u32) ConvertError!void { + var bytes: [4]u8 = undefined; + std.mem.writeInt(u32, &bytes, value, .big); + try output.append(&bytes); + } + + fn ensureUnusedCapacity(output: *BoundedOutput, additional: usize) ConvertError!void { + const required = std.math.add(usize, output.bytes.items.len, additional) catch return error.LimitExceeded; + if (required > output.max_bytes) return error.LimitExceeded; + if (required <= output.bytes.capacity) return; + + const doubled = std.math.mul(usize, output.bytes.capacity, 2) catch output.max_bytes; + const preferred = @max(required, @max(@as(usize, 256), doubled)); + const capacity = @min(output.max_bytes, preferred); + output.bytes.ensureTotalCapacityPrecise(output.allocator, capacity) catch return error.OutOfMemory; + } + + fn toOwnedSlice(output: *BoundedOutput) ConvertError![]u8 { + return output.bytes.toOwnedSlice(output.allocator) catch error.OutOfMemory; + } +}; + +const DeflateWriter = struct { + output: *BoundedOutput, + bits: u64 = 0, + bit_count: u6 = 0, + adler: std.hash.Adler32 = .{}, + previous_byte: u8 = 0, + has_previous_byte: bool = false, + + fn init(output: *BoundedOutput) ConvertError!DeflateWriter { + try output.append(&.{ 0x78, 0x01 }); + var writer = DeflateWriter{ .output = output }; + try writer.writeBits(1, 1); // Final block. + try writer.writeBits(1, 2); // Fixed Huffman block. + return writer; + } + + fn writeData(writer: *DeflateWriter, data: []const u8, options: ConvertOptions) ConvertError!void { + try writer.updateAdler(data, options); + var index: usize = 0; + var next_stop: usize = 0; + while (index < data.len) { + if (index >= next_stop) { + try checkStop(options); + next_stop = std.math.add(usize, index, CONVERSION_STOP_INTERVAL) catch std.math.maxInt(usize); + } + if (writer.has_previous_byte and data[index] == writer.previous_byte) { + var run_length: usize = 1; + while (run_length < 258 and index + run_length < data.len and data[index + run_length] == writer.previous_byte) { + run_length += 1; + } + if (run_length >= 3) { + try writer.writeRun(@intCast(run_length)); + index += run_length; + continue; + } + } + + try writer.writeFixedSymbol(data[index]); + writer.previous_byte = data[index]; + writer.has_previous_byte = true; + index += 1; + } + } + + fn finish(writer: *DeflateWriter) ConvertError!void { + try writer.writeFixedSymbol(256); + try writer.flushBits(); + try writer.output.appendInt(writer.adler.adler); + } + + fn writeRun(writer: *DeflateWriter, length: u16) ConvertError!void { + const bases = [_]u16{ 3, 4, 5, 6, 7, 8, 9, 10, 11, 13, 15, 17, 19, 23, 27, 31, 35, 43, 51, 59, 67, 83, 99, 115, 131, 163, 195, 227, 258 }; + const extra_bits = [_]u5{ 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 0 }; + std.debug.assert(length >= 3 and length <= 258); + + var index: usize = 0; + while (index + 1 < bases.len and length >= bases[index + 1]) : (index += 1) {} + try writer.writeFixedSymbol(@intCast(257 + index)); + const count = extra_bits[index]; + if (count > 0) try writer.writeBits(length - bases[index], count); + try writer.writeBits(0, 5); // Distance one. + } + + fn writeFixedSymbol(writer: *DeflateWriter, symbol: u16) ConvertError!void { + const code: u16, const count: u5 = if (symbol <= 143) + .{ 0x30 + symbol, 8 } + else if (symbol <= 255) + .{ 0x190 + symbol - 144, 9 } + else if (symbol <= 279) + .{ symbol - 256, 7 } + else + .{ 0xc0 + symbol - 280, 8 }; + try writer.writeBits(reverseBits(code, count), count); + } + + fn writeBits(writer: *DeflateWriter, value: anytype, count: u5) ConvertError!void { + std.debug.assert(count <= 16); + const typed_value: u64 = @intCast(value); + const mask: u64 = if (count == 0) 0 else (@as(u64, 1) << count) - 1; + writer.bits |= (typed_value & mask) << writer.bit_count; + writer.bit_count += count; + while (writer.bit_count >= 8) { + try writer.output.appendByte(@truncate(writer.bits)); + writer.bits >>= 8; + writer.bit_count -= 8; + } + } + + fn flushBits(writer: *DeflateWriter) ConvertError!void { + if (writer.bit_count > 0) try writer.output.appendByte(@truncate(writer.bits)); + writer.bits = 0; + writer.bit_count = 0; + } + + fn updateAdler(writer: *DeflateWriter, data: []const u8, options: ConvertOptions) ConvertError!void { + var offset: usize = 0; + while (offset < data.len) { + try checkStop(options); + const end = @min(data.len, offset + CONVERSION_STOP_INTERVAL); + writer.adler.update(data[offset..end]); + offset = end; + } + } +}; + +pub fn convertToPng( + allocator: Allocator, + dib: []const u8, + options: ConvertOptions, +) ConvertError![]u8 { + try checkStop(options); + const info = try parseDib(dib, null, options); + return convertDibToPng(allocator, dib, info, options); +} + +pub fn convertBmpToPng( + allocator: Allocator, + bmp: []const u8, + options: ConvertOptions, +) ConvertError![]u8 { + try checkStop(options); + if (bmp.len < 14) return error.InvalidData; + if (!std.mem.eql(u8, bmp[0..2], "BM")) return error.InvalidData; + if ((readInt(u16, bmp, 6) catch return error.InvalidData) != 0 or + (readInt(u16, bmp, 8) catch return error.InvalidData) != 0) + { + return error.InvalidData; + } + + const file_size = std.math.cast(usize, readInt(u32, bmp, 2) catch return error.InvalidData) orelse + return error.InvalidData; + if (file_size != bmp.len) return error.InvalidData; + const pixel_offset = std.math.cast(usize, readInt(u32, bmp, 10) catch return error.InvalidData) orelse + return error.InvalidData; + if (pixel_offset < 14 or pixel_offset > file_size) return error.InvalidData; + + const dib = bmp[14..]; + const info = try parseDib(dib, pixel_offset - 14, options); + return convertDibToPng(allocator, dib, info, options); +} + +fn convertDibToPng( + allocator: Allocator, + dib: []const u8, + info: DibInfo, + options: ConvertOptions, +) ConvertError![]u8 { + const channel_count = info.channelCount(); + const row_size = std.math.mul(usize, info.width, channel_count) catch return error.LimitExceeded; + const filtered_size = std.math.add(usize, row_size, 1) catch return error.LimitExceeded; + const filtered = allocator.alloc(u8, filtered_size) catch return error.OutOfMemory; + defer allocator.free(filtered); + + var output = BoundedOutput{ + .allocator = allocator, + .max_bytes = options.max_output_bytes, + }; + defer output.deinit(); + try output.append(PNG_SIGNATURE); + + var ihdr: [13]u8 = @splat(0); + std.mem.writeInt(u32, ihdr[0..4], info.width, .big); + std.mem.writeInt(u32, ihdr[4..8], info.height, .big); + ihdr[8] = 8; + ihdr[9] = if (channel_count == 3) 2 else 6; + try appendChunk(&output, "IHDR", &ihdr); + + const idat_length_offset = output.bytes.items.len; + try output.append(&.{ 0, 0, 0, 0 }); + try output.append("IDAT"); + const idat_data_offset = output.bytes.items.len; + const final_chunk_bytes = 4 + 12; // IDAT CRC and IEND chunk. + if (options.max_output_bytes < final_chunk_bytes) return error.LimitExceeded; + const configured_body_limit = options.max_output_bytes - final_chunk_bytes; + const png_body_limit = std.math.add(usize, idat_data_offset, PNG_CHUNK_LENGTH_MAX) catch unreachable; + output.max_bytes = @min(configured_body_limit, png_body_limit); + var deflate = try DeflateWriter.init(&output); + + var output_y: usize = 0; + while (output_y < info.height) : (output_y += 1) { + try checkStop(options); + const source_y = if (info.top_down) output_y else info.height - 1 - output_y; + const source_offset = info.pixel_offset + source_y * info.row_stride; + try filterRow(dib[source_offset..][0..info.row_stride], filtered, info, options); + try deflate.writeData(filtered, options); + } + try checkStop(options); + try deflate.finish(); + + const idat_length = output.bytes.items.len - idat_data_offset; + std.debug.assert(idat_length <= PNG_CHUNK_LENGTH_MAX); + const idat_length_u32: u32 = @intCast(idat_length); + std.mem.writeInt(u32, output.bytes.items[idat_length_offset..][0..4], idat_length_u32, .big); + var crc = std.hash.Crc32.init(); + crc.update("IDAT"); + var crc_offset = idat_data_offset; + while (crc_offset < output.bytes.items.len) { + try checkStop(options); + const crc_end = @min(output.bytes.items.len, crc_offset + CONVERSION_STOP_INTERVAL); + crc.update(output.bytes.items[crc_offset..crc_end]); + crc_offset = crc_end; + } + output.max_bytes = options.max_output_bytes; + try output.appendInt(crc.final()); + try appendChunk(&output, "IEND", &.{}); + return output.toOwnedSlice(); +} + +fn parseDib(dib: []const u8, explicit_pixel_offset: ?usize, options: ConvertOptions) ConvertError!DibInfo { + if (dib.len < 4) return error.InvalidData; + const header_size_u32 = readInt(u32, dib, 0) catch return error.InvalidData; + if (header_size_u32 < 40) return error.Unsupported; + const header_size = std.math.cast(usize, header_size_u32) orelse return error.InvalidData; + if (header_size > dib.len) return error.InvalidData; + switch (header_size_u32) { + 40, 52, 56, 108, 124 => {}, + else => return error.Unsupported, + } + if (header_size_u32 >= 108) { + const color_space = readInt(u32, dib, 56) catch return error.InvalidData; + if (color_space != LCS_SRGB and color_space != LCS_WINDOWS_COLOR_SPACE) return error.Unsupported; + } + + const width_signed = readInt(i32, dib, 4) catch return error.InvalidData; + const height_signed = readInt(i32, dib, 8) catch return error.InvalidData; + if (width_signed <= 0 or height_signed == 0 or height_signed == std.math.minInt(i32)) return error.InvalidData; + if ((readInt(u16, dib, 12) catch return error.InvalidData) != 1) return error.InvalidData; + const bits_per_pixel = readInt(u16, dib, 14) catch return error.InvalidData; + if (bits_per_pixel != 16 and bits_per_pixel != 24 and bits_per_pixel != 32) return error.Unsupported; + const compression = readInt(u32, dib, 16) catch return error.InvalidData; + if (compression != BI_RGB and compression != BI_BITFIELDS and compression != BI_ALPHABITFIELDS) { + return error.Unsupported; + } + if (bits_per_pixel == 24 and compression != BI_RGB) return error.Unsupported; + if (height_signed < 0 and compression == BI_ALPHABITFIELDS) return error.InvalidData; + + const width: u32 = @intCast(width_signed); + const height: u32 = @intCast(if (height_signed < 0) -height_signed else height_signed); + const pixel_count = std.math.mul(u64, width, height) catch return error.LimitExceeded; + if (pixel_count > options.max_image_pixels) return error.LimitExceeded; + const rgba_size = std.math.mul(u64, pixel_count, 4) catch return error.LimitExceeded; + if (rgba_size > options.max_conversion_bytes) return error.LimitExceeded; + + var external_mask_bytes: usize = 0; + var red_mask: u32 = 0; + var green_mask: u32 = 0; + var blue_mask: u32 = 0; + var alpha_mask: u32 = 0; + if (compression == BI_RGB) { + if (bits_per_pixel == 16) { + red_mask = 0x7c00; + green_mask = 0x03e0; + blue_mask = 0x001f; + } else if (bits_per_pixel == 32 and header_size_u32 >= 108) { + alpha_mask = readInt(u32, dib, 52) catch return error.InvalidData; + if (alpha_mask != 0) { + red_mask = 0x00ff0000; + green_mask = 0x0000ff00; + blue_mask = 0x000000ff; + } + } + } else if (header_size_u32 == 40) { + external_mask_bytes = if (compression == BI_ALPHABITFIELDS) 16 else 12; + red_mask = readInt(u32, dib, 40) catch return error.InvalidData; + green_mask = readInt(u32, dib, 44) catch return error.InvalidData; + blue_mask = readInt(u32, dib, 48) catch return error.InvalidData; + if (external_mask_bytes == 16) alpha_mask = readInt(u32, dib, 52) catch return error.InvalidData; + } else { + if (header_size_u32 < 52) return error.Unsupported; + red_mask = readInt(u32, dib, 40) catch return error.InvalidData; + green_mask = readInt(u32, dib, 44) catch return error.InvalidData; + blue_mask = readInt(u32, dib, 48) catch return error.InvalidData; + if (compression == BI_ALPHABITFIELDS and header_size_u32 < 56) return error.Unsupported; + if (header_size_u32 >= 56) alpha_mask = readInt(u32, dib, 52) catch return error.InvalidData; + } + + var red: ?ChannelMask = null; + var green: ?ChannelMask = null; + var blue: ?ChannelMask = null; + var alpha: ?ChannelMask = null; + if (bits_per_pixel == 16 or compression != BI_RGB or alpha_mask != 0) { + red = try parseMask(red_mask, bits_per_pixel, false); + green = try parseMask(green_mask, bits_per_pixel, false); + blue = try parseMask(blue_mask, bits_per_pixel, false); + alpha = try parseMask(alpha_mask, bits_per_pixel, true); + if (red.?.mask & green.?.mask != 0 or red.?.mask & blue.?.mask != 0 or green.?.mask & blue.?.mask != 0) { + return error.InvalidData; + } + if (alpha) |alpha_value| { + if ((alpha_value.mask & red.?.mask) != 0 or + (alpha_value.mask & green.?.mask) != 0 or + (alpha_value.mask & blue.?.mask) != 0) return error.InvalidData; + } + } + + const color_count = readInt(u32, dib, 32) catch return error.InvalidData; + const color_table_bytes = std.math.mul(usize, color_count, 4) catch return error.InvalidData; + const masks_end = std.math.add(usize, header_size, external_mask_bytes) catch return error.InvalidData; + const minimum_pixel_offset = std.math.add(usize, masks_end, color_table_bytes) catch return error.InvalidData; + const pixel_offset = explicit_pixel_offset orelse minimum_pixel_offset; + if (pixel_offset < minimum_pixel_offset) return error.InvalidData; + const row_bits = std.math.mul(u64, width, bits_per_pixel) catch return error.InvalidData; + const row_words = std.math.divCeil(u64, row_bits, 32) catch return error.InvalidData; + const row_stride_u64 = std.math.mul(u64, row_words, 4) catch return error.InvalidData; + const row_stride = std.math.cast(usize, row_stride_u64) orelse return error.LimitExceeded; + const pixel_bytes = std.math.mul(usize, row_stride, height) catch return error.InvalidData; + const pixel_end = std.math.add(usize, pixel_offset, pixel_bytes) catch return error.InvalidData; + if (pixel_end > dib.len) return error.InvalidData; + + return .{ + .width = width, + .height = height, + .top_down = height_signed < 0, + .bits_per_pixel = bits_per_pixel, + .row_stride = row_stride, + .pixel_offset = pixel_offset, + .red = red, + .green = green, + .blue = blue, + .alpha = alpha, + }; +} + +fn parseMask(mask: u32, bits_per_pixel: u16, optional: bool) ConvertError!?ChannelMask { + if (mask == 0) return if (optional) null else error.InvalidData; + if (bits_per_pixel < 32 and mask >= (@as(u32, 1) << @intCast(bits_per_pixel))) return error.InvalidData; + const shift: u5 = @intCast(@ctz(mask)); + const maximum = mask >> shift; + if (maximum != std.math.maxInt(u32) and maximum & (maximum + 1) != 0) return error.InvalidData; + return .{ .mask = mask, .shift = shift, .maximum = maximum }; +} + +fn filterRow(source: []const u8, filtered: []u8, info: DibInfo, options: ConvertOptions) ConvertError!void { + filtered[0] = 1; // Sub filter. + var left = [_]u8{ 0, 0, 0, 0 }; + const channel_count = info.channelCount(); + var x: usize = 0; + var output_offset: usize = 1; + while (x < info.width) : (x += 1) { + if (x % CONVERSION_STOP_INTERVAL == 0) try checkStop(options); + var channels: [4]u8 = undefined; + if (info.bits_per_pixel == 24) { + const source_offset = x * 3; + channels = .{ source[source_offset + 2], source[source_offset + 1], source[source_offset], 255 }; + } else if (info.bits_per_pixel == 32 and info.red == null) { + const source_offset = x * 4; + channels = .{ source[source_offset + 2], source[source_offset + 1], source[source_offset], 255 }; + } else { + const value: u32 = if (info.bits_per_pixel == 16) + readInt(u16, source, x * 2) catch unreachable + else + readInt(u32, source, x * 4) catch unreachable; + channels[0] = extractChannel(value, info.red.?); + channels[1] = extractChannel(value, info.green.?); + channels[2] = extractChannel(value, info.blue.?); + channels[3] = if (info.alpha) |alpha| extractChannel(value, alpha) else 255; + } + for (0..channel_count) |channel| { + filtered[output_offset] = channels[channel] -% left[channel]; + left[channel] = channels[channel]; + output_offset += 1; + } + } + std.debug.assert(output_offset == filtered.len); +} + +fn extractChannel(value: u32, channel: ChannelMask) u8 { + const sample = (value & channel.mask) >> channel.shift; + const scaled = @as(u64, sample) * 255 + channel.maximum / 2; + return @intCast(scaled / channel.maximum); +} + +fn appendChunk(output: *BoundedOutput, chunk_type: *const [4]u8, data: []const u8) ConvertError!void { + const length = std.math.cast(u32, data.len) orelse return error.LimitExceeded; + try output.appendInt(length); + try output.append(chunk_type); + try output.append(data); + var crc = std.hash.Crc32.init(); + crc.update(chunk_type); + crc.update(data); + try output.appendInt(crc.final()); +} + +fn reverseBits(value: u16, count: u5) u16 { + var result: u16 = 0; + var index: u5 = 0; + while (index < count) : (index += 1) { + result = (result << 1) | ((value >> @as(u4, @intCast(index))) & 1); + } + return result; +} + +fn readInt(comptime T: type, data: []const u8, offset: usize) error{InvalidData}!T { + if (offset > data.len or data.len - offset < @sizeOf(T)) return error.InvalidData; + return std.mem.readInt(T, data[offset..][0..@sizeOf(T)], .little); +} + +fn checkStop(options: ConvertOptions) ConvertError!void { + if (options.cancel_requested) |cancelled| { + if (cancelled.load(.acquire)) return error.Cancelled; + } + if (options.deadline_ns == std.math.maxInt(i128)) return; + if (clipboard_clock.nowNs() >= options.deadline_ns) return error.TimedOut; +} + +fn testOptions() ConvertOptions { + return .{ + .max_output_bytes = 1024 * 1024, + .max_image_pixels = 1024, + .max_conversion_bytes = 4096, + .deadline_ns = std.math.maxInt(i128), + }; +} + +fn expectPngPixels(png: []const u8, width: u32, height: u32, expected: []const u8) !void { + try std.testing.expectEqualStrings(PNG_SIGNATURE, png[0..8]); + try std.testing.expectEqual(@as(u32, 13), std.mem.readInt(u32, png[8..12], .big)); + try std.testing.expectEqualStrings("IHDR", png[12..16]); + try std.testing.expectEqual(width, std.mem.readInt(u32, png[16..20], .big)); + try std.testing.expectEqual(height, std.mem.readInt(u32, png[20..24], .big)); + const channel_count: usize = if (png[25] == 2) 3 else 4; + try std.testing.expectEqual(width * height * channel_count, expected.len); + + const idat_offset: usize = 33; + const idat_length = std.mem.readInt(u32, png[idat_offset..][0..4], .big); + try std.testing.expectEqualStrings("IDAT", png[idat_offset + 4 ..][0..4]); + const compressed = png[idat_offset + 8 ..][0..idat_length]; + var idat_crc = std.hash.Crc32.init(); + idat_crc.update("IDAT"); + idat_crc.update(compressed); + try std.testing.expectEqual( + idat_crc.final(), + std.mem.readInt(u32, png[idat_offset + 8 + idat_length ..][0..4], .big), + ); + var input: std.Io.Reader = .fixed(compressed); + var decompressed: std.Io.Writer.Allocating = .init(std.testing.allocator); + defer decompressed.deinit(); + var inflater: std.compress.flate.Decompress = .init(&input, .zlib, &.{}); + _ = try inflater.reader.streamRemaining(&decompressed.writer); + + const row_size = width * channel_count; + try std.testing.expectEqual(height * (row_size + 1), decompressed.written().len); + const pixels = try std.testing.allocator.alloc(u8, expected.len); + defer std.testing.allocator.free(pixels); + var y: usize = 0; + while (y < height) : (y += 1) { + const row = decompressed.written()[y * (row_size + 1) ..][0 .. row_size + 1]; + try std.testing.expectEqual(@as(u8, 1), row[0]); + for (0..row_size) |index| { + const left = if (index < channel_count) 0 else pixels[y * row_size + index - channel_count]; + pixels[y * row_size + index] = row[index + 1] +% left; + } + } + try std.testing.expectEqualSlices(u8, expected, pixels); +} + +fn dibFixture() [56]u8 { + var dib: [56]u8 = @splat(0); + std.mem.writeInt(u32, dib[0..4], 40, .little); + std.mem.writeInt(i32, dib[4..8], 2, .little); + std.mem.writeInt(i32, dib[8..12], 2, .little); + std.mem.writeInt(u16, dib[12..14], 1, .little); + std.mem.writeInt(u16, dib[14..16], 24, .little); + std.mem.writeInt(u32, dib[20..24], 16, .little); + dib[40..48].* = .{ 255, 0, 0, 255, 255, 255, 0, 0 }; + dib[48..56].* = .{ 0, 0, 255, 0, 255, 0, 0, 0 }; + return dib; +} + +fn bmpFixture() [77]u8 { + const dib = dibFixture(); + var bmp: [77]u8 = @splat(0); + bmp[0..2].* = "BM".*; + std.mem.writeInt(u32, bmp[2..6], bmp.len, .little); + std.mem.writeInt(u32, bmp[10..14], 61, .little); + @memcpy(bmp[14..54], dib[0..40]); + @memcpy(bmp[61..77], dib[40..56]); + return bmp; +} + +fn dibV5Fixture() [132]u8 { + var dib: [132]u8 = @splat(0); + std.mem.writeInt(u32, dib[0..4], 124, .little); + std.mem.writeInt(i32, dib[4..8], 2, .little); + std.mem.writeInt(i32, dib[8..12], -1, .little); + std.mem.writeInt(u16, dib[12..14], 1, .little); + std.mem.writeInt(u16, dib[14..16], 32, .little); + std.mem.writeInt(u32, dib[16..20], BI_BITFIELDS, .little); + std.mem.writeInt(u32, dib[20..24], 8, .little); + std.mem.writeInt(u32, dib[40..44], 0x00ff0000, .little); + std.mem.writeInt(u32, dib[44..48], 0x0000ff00, .little); + std.mem.writeInt(u32, dib[48..52], 0x000000ff, .little); + std.mem.writeInt(u32, dib[52..56], 0xff000000, .little); + std.mem.writeInt(u32, dib[56..60], LCS_SRGB, .little); + dib[124..132].* = .{ 10, 20, 30, 40, 50, 60, 70, 255 }; + return dib; +} + +fn dibV5RgbFixture(alpha_mask: u32) [132]u8 { + var dib: [132]u8 = @splat(0); + std.mem.writeInt(u32, dib[0..4], 124, .little); + std.mem.writeInt(i32, dib[4..8], 2, .little); + std.mem.writeInt(i32, dib[8..12], 1, .little); + std.mem.writeInt(u16, dib[12..14], 1, .little); + std.mem.writeInt(u16, dib[14..16], 32, .little); + std.mem.writeInt(u32, dib[16..20], BI_RGB, .little); + std.mem.writeInt(u32, dib[52..56], alpha_mask, .little); + std.mem.writeInt(u32, dib[56..60], LCS_WINDOWS_COLOR_SPACE, .little); + dib[124..132].* = .{ 10, 20, 30, 40, 50, 60, 70, 255 }; + return dib; +} + +test "Windows CF_DIB converts bottom-up padded BGR pixels to PNG" { + const dib = dibFixture(); + const png = try convertToPng(std.testing.allocator, &dib, testOptions()); + defer std.testing.allocator.free(png); + try expectPngPixels(png, 2, 2, &.{ + 255, 0, 0, 0, 255, 0, + 0, 0, 255, 255, 255, 255, + }); +} + +test "Windows CF_DIBV5 converts top-down bitfields and alpha to PNG" { + const dib = dibV5Fixture(); + const png = try convertToPng(std.testing.allocator, &dib, testOptions()); + defer std.testing.allocator.free(png); + try expectPngPixels(png, 2, 1, &.{ 30, 20, 10, 40, 70, 60, 50, 255 }); +} + +test "Windows CF_DIBV5 honors an explicit BI_RGB alpha mask" { + const dib = dibV5RgbFixture(0xff000000); + const png = try convertToPng(std.testing.allocator, &dib, testOptions()); + defer std.testing.allocator.free(png); + try expectPngPixels(png, 2, 1, &.{ 30, 20, 10, 40, 70, 60, 50, 255 }); +} + +test "Windows CF_DIBV5 keeps BI_RGB padding opaque without an alpha mask" { + const dib = dibV5RgbFixture(0); + const png = try convertToPng(std.testing.allocator, &dib, testOptions()); + defer std.testing.allocator.free(png); + try expectPngPixels(png, 2, 1, &.{ 30, 20, 10, 70, 60, 50 }); +} + +test "Windows DIB conversion honors a noncanonical BMP pixel gap" { + const bmp = bmpFixture(); + const png = try convertBmpToPng(std.testing.allocator, &bmp, testOptions()); + defer std.testing.allocator.free(png); + try expectPngPixels(png, 2, 2, &.{ + 255, 0, 0, 0, 255, 0, + 0, 0, 255, 255, 255, 255, + }); +} + +test "Windows DIB conversion rejects invalid BMP file headers and offsets" { + var bmp = bmpFixture(); + try std.testing.expectError(error.InvalidData, convertBmpToPng(std.testing.allocator, bmp[0..1], testOptions())); + try std.testing.expectError(error.InvalidData, convertBmpToPng(std.testing.allocator, bmp[0..13], testOptions())); + + bmp[0] = 'Z'; + try std.testing.expectError(error.InvalidData, convertBmpToPng(std.testing.allocator, &bmp, testOptions())); + bmp = bmpFixture(); + std.mem.writeInt(u32, bmp[2..6], bmp.len - 1, .little); + try std.testing.expectError(error.InvalidData, convertBmpToPng(std.testing.allocator, &bmp, testOptions())); + bmp = bmpFixture(); + std.mem.writeInt(u16, bmp[6..8], 1, .little); + try std.testing.expectError(error.InvalidData, convertBmpToPng(std.testing.allocator, &bmp, testOptions())); + bmp = bmpFixture(); + std.mem.writeInt(u16, bmp[8..10], 1, .little); + try std.testing.expectError(error.InvalidData, convertBmpToPng(std.testing.allocator, &bmp, testOptions())); + bmp = bmpFixture(); + std.mem.writeInt(u32, bmp[10..14], 13, .little); + try std.testing.expectError(error.InvalidData, convertBmpToPng(std.testing.allocator, &bmp, testOptions())); + bmp = bmpFixture(); + std.mem.writeInt(u32, bmp[10..14], 53, .little); + try std.testing.expectError(error.InvalidData, convertBmpToPng(std.testing.allocator, &bmp, testOptions())); + bmp = bmpFixture(); + std.mem.writeInt(u32, bmp[10..14], 62, .little); + try std.testing.expectError(error.InvalidData, convertBmpToPng(std.testing.allocator, &bmp, testOptions())); + bmp = bmpFixture(); + std.mem.writeInt(u32, bmp[10..14], 78, .little); + try std.testing.expectError(error.InvalidData, convertBmpToPng(std.testing.allocator, &bmp, testOptions())); +} + +test "Windows DIB conversion rejects malformed and unsupported headers" { + var dib = dibFixture(); + try std.testing.expectError(error.InvalidData, convertToPng(std.testing.allocator, dib[0..39], testOptions())); + try std.testing.expectError(error.InvalidData, convertToPng(std.testing.allocator, dib[0 .. dib.len - 1], testOptions())); + std.mem.writeInt(i32, dib[4..8], 0, .little); + try std.testing.expectError(error.InvalidData, convertToPng(std.testing.allocator, &dib, testOptions())); + dib = dibFixture(); + std.mem.writeInt(u16, dib[12..14], 2, .little); + try std.testing.expectError(error.InvalidData, convertToPng(std.testing.allocator, &dib, testOptions())); + dib = dibFixture(); + std.mem.writeInt(u32, dib[16..20], 1, .little); + try std.testing.expectError(error.Unsupported, convertToPng(std.testing.allocator, &dib, testOptions())); + + var dib_v5 = dibV5Fixture(); + std.mem.writeInt(u32, dib_v5[40..44], 0x00ff00ff, .little); + try std.testing.expectError(error.InvalidData, convertToPng(std.testing.allocator, &dib_v5, testOptions())); + dib_v5 = dibV5Fixture(); + std.mem.writeInt(u32, dib_v5[56..60], 0, .little); + try std.testing.expectError(error.Unsupported, convertToPng(std.testing.allocator, &dib_v5, testOptions())); +} + +test "Windows DIB conversion rejects top-down BI_ALPHABITFIELDS" { + var dib = dibV5Fixture(); + std.mem.writeInt(u32, dib[16..20], BI_ALPHABITFIELDS, .little); + try std.testing.expectError(error.InvalidData, convertToPng(std.testing.allocator, &dib, testOptions())); +} + +test "Windows DIB conversion enforces pixel conversion and output limits" { + const dib = dibFixture(); + var options = testOptions(); + options.max_image_pixels = 3; + try std.testing.expectError(error.LimitExceeded, convertToPng(std.testing.allocator, &dib, options)); + options = testOptions(); + options.max_conversion_bytes = 15; + try std.testing.expectError(error.LimitExceeded, convertToPng(std.testing.allocator, &dib, options)); + options = testOptions(); + options.max_output_bytes = 64; + try std.testing.expectError(error.LimitExceeded, convertToPng(std.testing.allocator, &dib, options)); + var oversized = dib; + std.mem.writeInt(i32, oversized[4..8], std.math.maxInt(i32), .little); + std.mem.writeInt(i32, oversized[8..12], std.math.maxInt(i32), .little); + options = testOptions(); + options.max_image_pixels = std.math.maxInt(u32); + options.max_conversion_bytes = std.math.maxInt(u32); + try std.testing.expectError(error.LimitExceeded, convertToPng(std.testing.allocator, &oversized, options)); +} + +test "Windows DIB conversion observes cancellation and deadlines" { + try clipboard_clock.init(); + const dib = dibFixture(); + var cancelled = std.atomic.Value(bool).init(true); + var options = testOptions(); + options.cancel_requested = &cancelled; + try std.testing.expectError(error.Cancelled, convertToPng(std.testing.allocator, &dib, options)); + cancelled.store(false, .release); + options.deadline_ns = clipboard_clock.nowNs() - 1; + try std.testing.expectError(error.TimedOut, convertToPng(std.testing.allocator, &dib, options)); +} + +test "Windows DIB conversion deflate stream round trips literals and long runs" { + var source: [1280]u8 = undefined; + for (source[0..256], 0..) |*byte, value| byte.* = @intCast(value); + @memset(source[256..], 0x80); + + var output = BoundedOutput{ + .allocator = std.testing.allocator, + .max_bytes = 4096, + }; + defer output.deinit(); + var deflate = try DeflateWriter.init(&output); + try deflate.writeData(&source, testOptions()); + try deflate.finish(); + + var input: std.Io.Reader = .fixed(output.bytes.items); + var decompressed: std.Io.Writer.Allocating = .init(std.testing.allocator); + defer decompressed.deinit(); + var inflater: std.compress.flate.Decompress = .init(&input, .zlib, &.{}); + _ = try inflater.reader.streamRemaining(&decompressed.writer); + try std.testing.expectEqualSlices(u8, &source, decompressed.written()); +} diff --git a/packages/core/src/zig/clipboard/windows.zig b/packages/core/src/zig/clipboard/windows.zig new file mode 100644 index 0000000000..a23bef5163 --- /dev/null +++ b/packages/core/src/zig/clipboard/windows.zig @@ -0,0 +1,829 @@ +const std = @import("std"); +const builtin = @import("builtin"); +const clipboard_clock = @import("clock.zig"); +const clipboard_windows_dib = @import("windows-dib.zig"); + +const Allocator = std.mem.Allocator; + +const CF_DIB: u32 = 8; +const CF_UNICODETEXT: u32 = 13; +const CF_DIBV5: u32 = 17; +const GMEM_MOVEABLE: u32 = 0x2; +const OPEN_RETRY_SLEEP_NS: u64 = 5 * std.time.ns_per_ms; +const COPY_STOP_INTERVAL: usize = 4096; +const PUMP_MESSAGES_MAX: usize = 64; +const ERROR_OUTOFMEMORY: u32 = 14; +const ERROR_INVALID_DATA: u32 = 13; +const PM_REMOVE: u32 = 1; + +const win32 = struct { + const Point = extern struct { x: i32, y: i32 }; + const Message = extern struct { + window: ?*anyopaque, + message: u32, + wparam: usize, + lparam: isize, + time: u32, + point: Point, + private: u32, + }; + + extern "user32" fn OpenClipboard(owner: ?*anyopaque) callconv(.winapi) i32; + extern "user32" fn CloseClipboard() callconv(.winapi) i32; + extern "user32" fn EmptyClipboard() callconv(.winapi) i32; + extern "user32" fn GetClipboardData(format: u32) callconv(.winapi) ?*anyopaque; + extern "user32" fn IsClipboardFormatAvailable(format: u32) callconv(.winapi) i32; + extern "user32" fn RegisterClipboardFormatW(name: [*:0]const u16) callconv(.winapi) u32; + extern "user32" fn SetClipboardData(format: u32, memory: ?*anyopaque) callconv(.winapi) ?*anyopaque; + extern "user32" fn PeekMessageW(message: *Message, window: ?*anyopaque, minimum: u32, maximum: u32, remove: u32) callconv(.winapi) i32; + extern "user32" fn TranslateMessage(message: *const Message) callconv(.winapi) i32; + extern "user32" fn DispatchMessageW(message: *const Message) callconv(.winapi) isize; + extern "user32" fn CreateWindowExW( + extended_style: u32, + class_name: [*:0]const u16, + window_name: [*:0]const u16, + style: u32, + x: i32, + y: i32, + width: i32, + height: i32, + parent: ?*anyopaque, + menu: ?*anyopaque, + instance: ?*anyopaque, + parameter: ?*anyopaque, + ) callconv(.winapi) ?*anyopaque; + extern "user32" fn DestroyWindow(window: *anyopaque) callconv(.winapi) i32; + + extern "kernel32" fn GetCurrentThreadId() callconv(.winapi) u32; + extern "kernel32" fn GetLastError() callconv(.winapi) u32; + extern "kernel32" fn GlobalAlloc(flags: u32, size_bytes: usize) callconv(.winapi) ?*anyopaque; + extern "kernel32" fn GlobalFree(memory: ?*anyopaque) callconv(.winapi) ?*anyopaque; + extern "kernel32" fn GlobalLock(memory: ?*anyopaque) callconv(.winapi) ?*anyopaque; + extern "kernel32" fn GlobalSize(memory: ?*anyopaque) callconv(.winapi) usize; + extern "kernel32" fn GlobalUnlock(memory: ?*anyopaque) callconv(.winapi) i32; +}; + +pub const Status = enum { + read, + empty, + written, + cleared, + unsupported, + cancelled, + timed_out, + limit_exceeded, + invalid_request, + failed, +}; + +pub const Result = struct { + status: Status, + mime: []u8 = &.{}, + data: []u8 = &.{}, + error_code: u32 = 0, + + pub fn deinit(result: *Result, allocator: Allocator) void { + if (result.status == .read) { + allocator.free(result.mime); + allocator.free(result.data); + } + result.* = .{ .status = .failed }; + } +}; + +pub const ReadJob = struct { + // Same framing as host.zig: u32 count, then repeated u32 byte length and MIME bytes. + request: []const u8, + max_bytes: u32, + max_image_pixels: u32, + max_conversion_bytes: u32, +}; + +pub const Job = union(enum) { + read: ReadJob, + write: []const u8, + clear, +}; + +pub const ExecuteOptions = struct { + cancel_requested: ?*const std.atomic.Value(bool) = null, + begin_mutation: ?*const fn (?*anyopaque) ?Status = null, + mutation_context: ?*anyopaque = null, + deadline_ns: i128, +}; + +pub const InitError = error{ + UnsupportedPlatform, + ClipboardFormatRegistrationFailed, + WindowCreationFailed, +}; + +pub const Worker = struct { + thread_id: u32, + png_format: u32, + owner_window: *anyopaque, + initialized: bool, + + pub fn init() InitError!Worker { + if (comptime builtin.os.tag != .windows) return error.UnsupportedPlatform; + + const png_format = win32.RegisterClipboardFormatW(std.unicode.utf8ToUtf16LeStringLiteral("PNG")); + if (png_format == 0) return error.ClipboardFormatRegistrationFailed; + const owner_window = win32.CreateWindowExW( + 0, + std.unicode.utf8ToUtf16LeStringLiteral("STATIC"), + std.unicode.utf8ToUtf16LeStringLiteral("OpenTUI Clipboard"), + 0, + 0, + 0, + 0, + 0, + null, + null, + null, + null, + ) orelse return error.WindowCreationFailed; + return .{ + .thread_id = win32.GetCurrentThreadId(), + .png_format = png_format, + .owner_window = owner_window, + .initialized = true, + }; + } + + pub fn deinit(worker: *Worker) void { + if (comptime builtin.os.tag != .windows) return; + std.debug.assert(worker.initialized); + std.debug.assert(worker.thread_id == win32.GetCurrentThreadId()); + std.debug.assert(win32.DestroyWindow(worker.owner_window) != 0); + worker.initialized = false; + } + + pub fn execute(worker: *Worker, allocator: Allocator, job: Job, options: ExecuteOptions) Result { + if (comptime builtin.os.tag != .windows) return .{ .status = .unsupported }; + std.debug.assert(worker.initialized); + std.debug.assert(worker.thread_id == win32.GetCurrentThreadId()); + + if (job == .read and !validateReadRequest(job.read.request)) { + return .{ .status = .invalid_request, .error_code = ERROR_INVALID_DATA }; + } + + var prepared: PreparedWrite = if (job == .write) + prepareWrite(job.write, options) catch |err| { + return preparationFailure(err); + } + else + .{ .memory = null }; + defer prepared.deinit(); + + if (checkStop(options)) |status| return .{ .status = status }; + if (worker.openClipboard(options)) |failure| return failure; + var clipboard = ClipboardSession{}; + defer clipboard.close(); + if (checkStop(options)) |status| return .{ .status = status }; + + return switch (job) { + .read => |read| worker.executeRead(allocator, read, options, &clipboard), + .write => if (beginMutation(options)) |status| .{ .status = status } else worker.executeWrite(&prepared), + .clear => if (beginMutation(options)) |status| .{ .status = status } else executeClear(), + }; + } + + pub fn pumpMessages(worker: *const Worker) bool { + if (comptime builtin.os.tag != .windows) return false; + std.debug.assert(worker.initialized); + std.debug.assert(worker.thread_id == win32.GetCurrentThreadId()); + var message: win32.Message = undefined; + var message_count: usize = 0; + while (message_count < PUMP_MESSAGES_MAX) : (message_count += 1) { + if (win32.PeekMessageW(&message, null, 0, 0, PM_REMOVE) == 0) return false; + _ = win32.TranslateMessage(&message); + _ = win32.DispatchMessageW(&message); + } + return true; + } + + fn openClipboard(worker: *const Worker, options: ExecuteOptions) ?Result { + while (true) { + if (checkStop(options)) |status| return .{ .status = status }; + if (win32.OpenClipboard(worker.owner_window) != 0) return null; + + _ = worker.pumpMessages(); + if (checkStop(options)) |status| return .{ .status = status }; + const now_ns = clipboard_clock.nowNs(); + if (now_ns >= options.deadline_ns) return .{ .status = .timed_out }; + const remaining_ns: u64 = @intCast(@min(options.deadline_ns - now_ns, std.math.maxInt(u64))); + const sleep_ns = @min(OPEN_RETRY_SLEEP_NS, remaining_ns); + std.debug.assert(sleep_ns > 0); + std.Thread.sleep(sleep_ns); + } + } + + fn executeRead( + worker: *const Worker, + allocator: Allocator, + job: ReadJob, + options: ExecuteOptions, + clipboard: *ClipboardSession, + ) Result { + var iterator = PreferenceIterator.init(job.request) catch unreachable; + var supported = false; + var first_failure: ?Result = null; + while (iterator.next() catch unreachable) |mime| { + if (checkStop(options)) |status| return .{ .status = status }; + const result = if (std.ascii.eqlIgnoreCase(mime, "text/plain")) blk: { + supported = true; + if (worker.ensureClipboardOpen(options, clipboard)) |failure| return failure; + std.debug.assert(clipboard.is_open); + if (win32.IsClipboardFormatAvailable(CF_UNICODETEXT) == 0) continue; + if (checkStop(options)) |status| return .{ .status = status }; + break :blk readText(allocator, mime, job.max_bytes, options); + } else if (std.ascii.eqlIgnoreCase(mime, "image/png")) blk: { + supported = true; + if (worker.ensureClipboardOpen(options, clipboard)) |failure| return failure; + break :blk worker.readImage(allocator, mime, job, options, clipboard); + } else continue; + switch (readCandidateAction(result)) { + .return_result => return result, + .continue_candidate => {}, + .remember_failure => rememberCandidateFailure(&first_failure, result), + } + } + if (first_failure) |failure| return failure; + return .{ .status = if (supported) .empty else .unsupported }; + } + + fn readImage( + worker: *const Worker, + allocator: Allocator, + mime: []const u8, + job: ReadJob, + options: ExecuteOptions, + clipboard: *ClipboardSession, + ) Result { + const formats = [_]u32{ worker.png_format, CF_DIBV5, CF_DIB }; + var first_failure: ?Result = null; + for (formats, 0..) |format, format_index| { + std.debug.assert(clipboard.is_open); + if (checkStop(options)) |status| return .{ .status = status }; + if (win32.IsClipboardFormatAvailable(format) == 0) continue; + const result = if (format == worker.png_format) + readBytes(allocator, mime, format, job.max_bytes, options) + else + readDib(allocator, mime, format, job, options, clipboard); + const action = readCandidateAction(result); + switch (action) { + .return_result => return result, + .continue_candidate => {}, + .remember_failure => rememberCandidateFailure(&first_failure, result), + } + if (!clipboard.is_open and format_index + 1 < formats.len) { + if (worker.ensureClipboardOpen(options, clipboard)) |failure| return failure; + } + } + return first_failure orelse .{ .status = .empty }; + } + + fn ensureClipboardOpen( + worker: *const Worker, + options: ExecuteOptions, + clipboard: *ClipboardSession, + ) ?Result { + if (clipboard.is_open) return null; + if (worker.openClipboard(options)) |failure| return failure; + clipboard.is_open = true; + return null; + } + + fn executeWrite(_: *const Worker, prepared: *PreparedWrite) Result { + if (win32.EmptyClipboard() == 0) return lastErrorResult(); + const memory = prepared.memory orelse unreachable; + if (win32.SetClipboardData(CF_UNICODETEXT, memory) == null) return lastErrorResult(); + prepared.memory = null; // SetClipboardData owns the HGLOBAL after success. + return .{ .status = .written }; + } +}; + +const PreparedWrite = struct { + memory: ?*anyopaque, + + fn deinit(prepared: *PreparedWrite) void { + if (prepared.memory) |memory| std.debug.assert(win32.GlobalFree(memory) == null); + prepared.memory = null; + } +}; + +const ClipboardSession = struct { + is_open: bool = true, + + fn close(clipboard: *ClipboardSession) void { + if (!clipboard.is_open) return; + clipboard.is_open = false; + _ = win32.CloseClipboard(); + } +}; + +const PreferenceIterator = struct { + request: []const u8, + count: u32, + index: u32 = 0, + offset: usize = 4, + + fn init(request: []const u8) error{InvalidRequest}!PreferenceIterator { + if (!validateReadRequest(request)) return error.InvalidRequest; + return .{ .request = request, .count = std.mem.readInt(u32, request[0..4], .little) }; + } + + fn next(iterator: *PreferenceIterator) error{InvalidRequest}!?[]const u8 { + if (iterator.index == iterator.count) { + if (iterator.offset != iterator.request.len) return error.InvalidRequest; + return null; + } + if (iterator.request.len - iterator.offset < 4) return error.InvalidRequest; + const length = std.mem.readInt(u32, iterator.request[iterator.offset..][0..4], .little); + iterator.offset += 4; + if (length == 0 or length > iterator.request.len - iterator.offset) return error.InvalidRequest; + const mime = iterator.request[iterator.offset..][0..length]; + iterator.offset += length; + iterator.index += 1; + return mime; + } +}; + +const ConversionError = error{ + InvalidUtf8, + InvalidUtf16, + EmbeddedNul, + MissingNul, + LimitExceeded, + OutOfMemory, + Cancelled, + TimedOut, +}; + +const ReadCandidateAction = enum { return_result, continue_candidate, remember_failure }; + +fn validateReadRequest(request: []const u8) bool { + if (request.len < 4) return false; + const count = std.mem.readInt(u32, request[0..4], .little); + if (count == 0) return false; + var iterator = PreferenceIterator{ + .request = request, + .count = count, + }; + while (iterator.next() catch return false) |_| {} + return true; +} + +const NormalizedUtf8Iterator = struct { + bytes: []const u8, + index: usize = 0, + pending_lf: bool = false, + + fn next(iterator: *NormalizedUtf8Iterator) ConversionError!?u21 { + if (iterator.pending_lf) { + iterator.pending_lf = false; + return '\n'; + } + if (iterator.index == iterator.bytes.len) return null; + const sequence_length = std.unicode.utf8ByteSequenceLength(iterator.bytes[iterator.index]) catch + return error.InvalidUtf8; + if (sequence_length > iterator.bytes.len - iterator.index) return error.InvalidUtf8; + const codepoint = std.unicode.utf8Decode( + iterator.bytes[iterator.index..][0..sequence_length], + ) catch return error.InvalidUtf8; + iterator.index += sequence_length; + if (codepoint == 0) return error.EmbeddedNul; + if (codepoint == '\r') { + if (iterator.index < iterator.bytes.len and iterator.bytes[iterator.index] == '\n') iterator.index += 1; + iterator.pending_lf = true; + return '\r'; + } + if (codepoint == '\n') { + iterator.pending_lf = true; + return '\r'; + } + return codepoint; + } +}; + +fn encodeClipboardText( + utf8: []const u8, + output: ?[]u16, + options: ExecuteOptions, +) ConversionError!usize { + var iterator = NormalizedUtf8Iterator{ .bytes = utf8 }; + var output_index: usize = 0; + var next_stop: usize = 0; + while (try iterator.next()) |codepoint| { + if (iterator.index >= next_stop) { + try checkConversionStop(options); + next_stop = std.math.add(usize, iterator.index, COPY_STOP_INTERVAL) catch std.math.maxInt(usize); + } + const sequence_length = std.unicode.utf16CodepointSequenceLength(codepoint) catch unreachable; + const next_index = std.math.add(usize, output_index, sequence_length) catch return error.LimitExceeded; + if (output) |destination| { + std.debug.assert(next_index < destination.len); + if (codepoint <= 0xffff) { + destination[output_index] = @intCast(codepoint); + } else { + const value = codepoint - 0x10000; + destination[output_index] = @intCast(0xd800 + (value >> 10)); + destination[output_index + 1] = @intCast(0xdc00 + (value & 0x3ff)); + } + } + output_index = next_index; + } + try checkConversionStop(options); + if (output) |destination| { + std.debug.assert(output_index + 1 == destination.len); + destination[output_index] = 0; + } + return output_index; +} + +fn clipboardTextToUtf8( + allocator: Allocator, + utf16: []const u16, + max_bytes: u32, + options: ExecuteOptions, +) ConversionError![]u8 { + const scan_length = @min(utf16.len, @as(usize, max_bytes) + 1); + var nul_index: ?usize = null; + var scan_index: usize = 0; + while (scan_index < scan_length) : (scan_index += 1) { + if (scan_index % COPY_STOP_INTERVAL == 0) try checkConversionStop(options); + if (utf16[scan_index] == 0) { + nul_index = scan_index; + break; + } + } + const terminator = nul_index orelse { + if (utf16.len > max_bytes) return error.LimitExceeded; + return error.MissingNul; + }; + const text = utf16[0..terminator]; + var iterator = std.unicode.Utf16LeIterator.init(text); + var size_bytes: usize = 0; + var next_stop: usize = 0; + while (iterator.nextCodepoint() catch return error.InvalidUtf16) |codepoint| { + if (iterator.i >= next_stop) { + try checkConversionStop(options); + next_stop = std.math.add(usize, iterator.i, COPY_STOP_INTERVAL) catch std.math.maxInt(usize); + } + const sequence_length = std.unicode.utf8CodepointSequenceLength(codepoint) catch unreachable; + size_bytes = std.math.add(usize, size_bytes, sequence_length) catch return error.LimitExceeded; + if (size_bytes > max_bytes) return error.LimitExceeded; + } + + const output = try allocator.alloc(u8, size_bytes); + errdefer allocator.free(output); + iterator = std.unicode.Utf16LeIterator.init(text); + var offset: usize = 0; + next_stop = 0; + while (iterator.nextCodepoint() catch unreachable) |codepoint| { + if (iterator.i >= next_stop) { + try checkConversionStop(options); + next_stop = std.math.add(usize, iterator.i, COPY_STOP_INTERVAL) catch std.math.maxInt(usize); + } + offset += std.unicode.utf8Encode(codepoint, output[offset..]) catch unreachable; + } + try checkConversionStop(options); + std.debug.assert(offset == output.len); + return output; +} + +fn prepareWrite(data: []const u8, options: ExecuteOptions) ConversionError!PreparedWrite { + const length = try encodeClipboardText(data, null, options); + const length_with_nul = std.math.add(usize, length, 1) catch return error.LimitExceeded; + const size_bytes = std.math.mul(usize, length_with_nul, @sizeOf(u16)) catch return error.LimitExceeded; + const memory = win32.GlobalAlloc(GMEM_MOVEABLE, size_bytes) orelse return error.OutOfMemory; + errdefer std.debug.assert(win32.GlobalFree(memory) == null); + const pointer = win32.GlobalLock(memory) orelse return error.OutOfMemory; + defer _ = win32.GlobalUnlock(memory); + const destination: [*]u16 = @ptrCast(@alignCast(pointer)); + const written = try encodeClipboardText(data, destination[0..length_with_nul], options); + std.debug.assert(written == length); + return .{ .memory = memory }; +} + +fn preparationFailure(err: ConversionError) Result { + return switch (err) { + error.OutOfMemory => .{ .status = .failed, .error_code = ERROR_OUTOFMEMORY }, + error.LimitExceeded => .{ .status = .limit_exceeded }, + error.Cancelled => .{ .status = .cancelled }, + error.TimedOut => .{ .status = .timed_out }, + error.InvalidUtf8, error.InvalidUtf16, error.EmbeddedNul, error.MissingNul => .{ .status = .invalid_request, .error_code = ERROR_INVALID_DATA }, + }; +} + +fn checkStop(options: ExecuteOptions) ?Status { + if (options.cancel_requested) |cancelled| { + if (cancelled.load(.acquire)) return .cancelled; + } + if (options.deadline_ns == std.math.maxInt(i128)) return null; + if (clipboard_clock.nowNs() >= options.deadline_ns) return .timed_out; + return null; +} + +fn checkConversionStop(options: ExecuteOptions) ConversionError!void { + if (checkStop(options)) |status| return switch (status) { + .cancelled => error.Cancelled, + .timed_out => error.TimedOut, + else => unreachable, + }; +} + +fn beginMutation(options: ExecuteOptions) ?Status { + if (checkStop(options)) |status| return status; + const begin = options.begin_mutation orelse return .invalid_request; + return begin(options.mutation_context); +} + +fn executeClear() Result { + if (win32.EmptyClipboard() == 0) return lastErrorResult(); + return .{ .status = .cleared }; +} + +fn copyBytesChecked(destination: []u8, source: []const u8, options: ExecuteOptions) ConversionError!void { + std.debug.assert(destination.len == source.len); + var offset: usize = 0; + while (offset < source.len) { + try checkConversionStop(options); + const end = @min(source.len, offset + COPY_STOP_INTERVAL); + @memcpy(destination[offset..end], source[offset..end]); + offset = end; + } + try checkConversionStop(options); +} + +fn copyBytesBounded( + allocator: Allocator, + source: []const u8, + max_bytes: usize, + options: ExecuteOptions, +) ConversionError![]u8 { + if (source.len > max_bytes) return error.LimitExceeded; + try checkConversionStop(options); + const destination = allocator.alloc(u8, source.len) catch return error.OutOfMemory; + errdefer allocator.free(destination); + try copyBytesChecked(destination, source, options); + return destination; +} + +fn testOptions() ExecuteOptions { + return .{ .deadline_ns = std.math.maxInt(i128) }; +} + +fn readText(allocator: Allocator, mime: []const u8, max_bytes: u32, options: ExecuteOptions) Result { + const memory = win32.GetClipboardData(CF_UNICODETEXT) orelse return lastErrorResult(); + const size_bytes = win32.GlobalSize(memory); + if (size_bytes < @sizeOf(u16) or size_bytes % @sizeOf(u16) != 0) { + return .{ .status = .failed, .error_code = ERROR_INVALID_DATA }; + } + const pointer = win32.GlobalLock(memory) orelse return lastErrorResult(); + defer _ = win32.GlobalUnlock(memory); + const utf16_pointer: [*]const u16 = @ptrCast(@alignCast(pointer)); + const data = clipboardTextToUtf8(allocator, utf16_pointer[0 .. size_bytes / 2], max_bytes, options) catch |err| { + return switch (err) { + error.LimitExceeded => .{ .status = .limit_exceeded }, + error.Cancelled => .{ .status = .cancelled }, + error.TimedOut => .{ .status = .timed_out }, + error.OutOfMemory => .{ .status = .failed, .error_code = ERROR_OUTOFMEMORY }, + else => .{ .status = .failed, .error_code = ERROR_INVALID_DATA }, + }; + }; + return readResult(allocator, mime, data, options); +} + +fn readBytes(allocator: Allocator, mime: []const u8, format: u32, max_bytes: u32, options: ExecuteOptions) Result { + const memory = win32.GetClipboardData(format) orelse return lastErrorResult(); + const size_bytes = win32.GlobalSize(memory); + if (size_bytes == 0) return .{ .status = .empty }; + if (size_bytes > max_bytes) return .{ .status = .limit_exceeded }; + const pointer = win32.GlobalLock(memory) orelse return lastErrorResult(); + defer _ = win32.GlobalUnlock(memory); + const source: [*]const u8 = @ptrCast(pointer); + const data = copyBytesBounded(allocator, source[0..size_bytes], max_bytes, options) catch |err| { + return conversionFailure(err); + }; + return readResult(allocator, mime, data, options); +} + +fn readDib( + allocator: Allocator, + mime: []const u8, + format: u32, + job: ReadJob, + options: ExecuteOptions, + clipboard: *ClipboardSession, +) Result { + const memory = win32.GetClipboardData(format) orelse return lastErrorResult(); + const size_bytes = win32.GlobalSize(memory); + if (size_bytes == 0) return .{ .status = .empty }; + if (size_bytes > job.max_conversion_bytes) return .{ .status = .limit_exceeded }; + const pointer = win32.GlobalLock(memory) orelse return lastErrorResult(); + const source: [*]const u8 = @ptrCast(pointer); + const dib = copyBytesBounded(allocator, source[0..size_bytes], job.max_conversion_bytes, options) catch |err| { + _ = win32.GlobalUnlock(memory); + return conversionFailure(err); + }; + _ = win32.GlobalUnlock(memory); + clipboard.close(); + defer allocator.free(dib); + + const data = clipboard_windows_dib.convertToPng(allocator, dib, .{ + .max_output_bytes = job.max_bytes, + .max_image_pixels = job.max_image_pixels, + .max_conversion_bytes = job.max_conversion_bytes, + .cancel_requested = options.cancel_requested, + .deadline_ns = options.deadline_ns, + }) catch |err| { + return switch (err) { + error.Unsupported => .{ .status = .empty }, + error.LimitExceeded => .{ .status = .limit_exceeded }, + error.Cancelled => .{ .status = .cancelled }, + error.TimedOut => .{ .status = .timed_out }, + error.OutOfMemory => .{ .status = .failed, .error_code = ERROR_OUTOFMEMORY }, + error.InvalidData => .{ .status = .failed, .error_code = ERROR_INVALID_DATA }, + }; + }; + return readResult(allocator, mime, data, options); +} + +fn conversionFailure(err: ConversionError) Result { + return switch (err) { + error.LimitExceeded => .{ .status = .limit_exceeded }, + error.Cancelled => .{ .status = .cancelled }, + error.TimedOut => .{ .status = .timed_out }, + error.OutOfMemory => .{ .status = .failed, .error_code = ERROR_OUTOFMEMORY }, + else => .{ .status = .failed, .error_code = ERROR_INVALID_DATA }, + }; +} + +fn readResult(allocator: Allocator, mime: []const u8, data: []u8, options: ExecuteOptions) Result { + if (checkStop(options)) |status| { + allocator.free(data); + return .{ .status = status }; + } + const owned_mime = allocator.dupe(u8, mime) catch { + allocator.free(data); + return .{ .status = .failed, .error_code = ERROR_OUTOFMEMORY }; + }; + if (checkStop(options)) |status| { + allocator.free(owned_mime); + allocator.free(data); + return .{ .status = status }; + } + return .{ .status = .read, .mime = owned_mime, .data = data }; +} + +fn readCandidateAction(result: Result) ReadCandidateAction { + return switch (result.status) { + .empty, .unsupported => .continue_candidate, + .failed => if (result.error_code == ERROR_OUTOFMEMORY) .return_result else .remember_failure, + else => .return_result, + }; +} + +fn rememberCandidateFailure(first_failure: *?Result, result: Result) void { + std.debug.assert(readCandidateAction(result) == .remember_failure); + if (first_failure.* == null) first_failure.* = result; +} + +fn lastErrorResult() Result { + return .{ .status = .failed, .error_code = win32.GetLastError() }; +} + +test "Windows clipboard MIME request parsing preserves preference order" { + const request = [_]u8{ + 3, 0, 0, 0, + 9, 0, 0, 0, + 'i', 'm', 'a', 'g', + 'e', '/', 'p', 'n', + 'g', 10, 0, 0, + 0, 't', 'e', 'x', + 't', '/', 'p', 'l', + 'a', 'i', 'n', 3, + 0, 0, 0, 'f', + 'o', 'o', + }; + var iterator = try PreferenceIterator.init(&request); + try std.testing.expectEqualStrings("image/png", (try iterator.next()).?); + try std.testing.expectEqualStrings("text/plain", (try iterator.next()).?); + try std.testing.expectEqualStrings("foo", (try iterator.next()).?); + try std.testing.expect((try iterator.next()) == null); +} + +test "Windows clipboard MIME request parsing rejects malformed framing" { + try std.testing.expect(!validateReadRequest(&.{ 0, 0, 0, 0 })); + try std.testing.expect(!validateReadRequest(&.{ 1, 0, 0, 0, 0, 0, 0, 0 })); + try std.testing.expect(!validateReadRequest(&.{ 1, 0, 0, 0, 2, 0, 0, 0, 'x' })); + try std.testing.expect(!validateReadRequest(&.{ 1, 0, 0, 0, 1, 0, 0, 0, 'x', 'y' })); +} + +test "Windows clipboard reads retain candidate failures while trying later preferences" { + try std.testing.expectEqual(ReadCandidateAction.continue_candidate, readCandidateAction(.{ .status = .empty })); + try std.testing.expectEqual(ReadCandidateAction.continue_candidate, readCandidateAction(.{ .status = .unsupported })); + try std.testing.expectEqual( + ReadCandidateAction.remember_failure, + readCandidateAction(.{ .status = .failed, .error_code = ERROR_INVALID_DATA }), + ); + try std.testing.expectEqual( + ReadCandidateAction.return_result, + readCandidateAction(.{ .status = .failed, .error_code = ERROR_OUTOFMEMORY }), + ); + try std.testing.expectEqual(ReadCandidateAction.return_result, readCandidateAction(.{ .status = .limit_exceeded })); + + var first_failure: ?Result = null; + rememberCandidateFailure(&first_failure, .{ .status = .failed, .error_code = ERROR_INVALID_DATA }); + rememberCandidateFailure(&first_failure, .{ .status = .failed, .error_code = 1 }); + try std.testing.expectEqual(ERROR_INVALID_DATA, first_failure.?.error_code); +} + +test "Windows clipboard text conversion round trips Unicode and terminates UTF-16" { + var utf16: [9]u16 = undefined; + _ = try encodeClipboardText("plain \u{1f642}", &utf16, testOptions()); + try std.testing.expectEqual(@as(u16, 0), utf16[utf16.len - 1]); + + const utf8 = try clipboardTextToUtf8(std.testing.allocator, &utf16, 64, testOptions()); + defer std.testing.allocator.free(utf8); + try std.testing.expectEqualStrings("plain \u{1f642}", utf8); +} + +test "Windows clipboard text conversion normalizes CF_UNICODETEXT line endings" { + var utf16: [11]u16 = undefined; + _ = try encodeClipboardText("a\nb\r\nc\rd", &utf16, testOptions()); + const utf8 = try clipboardTextToUtf8(std.testing.allocator, &utf16, 64, testOptions()); + defer std.testing.allocator.free(utf8); + try std.testing.expectEqualStrings("a\r\nb\r\nc\r\nd", utf8); +} + +test "Windows clipboard text conversion validates NUL and UTF-16" { + try std.testing.expectError(error.EmbeddedNul, encodeClipboardText("a\x00b", null, testOptions())); + try std.testing.expectError( + error.MissingNul, + clipboardTextToUtf8(std.testing.allocator, &.{ 'a', 'b' }, 8, testOptions()), + ); + try std.testing.expectError( + error.InvalidUtf16, + clipboardTextToUtf8(std.testing.allocator, &.{ 0xd800, 0 }, 8, testOptions()), + ); +} + +test "Windows clipboard bounded copy accepts exact limit and chunk boundaries" { + var source: [COPY_STOP_INTERVAL + 1]u8 = undefined; + for (&source, 0..) |*byte, index| byte.* = @truncate(index); + + const exact = try copyBytesBounded(std.testing.allocator, source[0..COPY_STOP_INTERVAL], COPY_STOP_INTERVAL, testOptions()); + defer std.testing.allocator.free(exact); + try std.testing.expectEqualSlices(u8, source[0..COPY_STOP_INTERVAL], exact); + + const across_boundary = try copyBytesBounded(std.testing.allocator, &source, source.len, testOptions()); + defer std.testing.allocator.free(across_boundary); + try std.testing.expectEqualSlices(u8, &source, across_boundary); + try std.testing.expectError( + error.LimitExceeded, + copyBytesBounded(std.testing.allocator, &source, source.len - 1, testOptions()), + ); +} + +test "Windows clipboard bounded helpers observe cancellation and deadlines" { + try clipboard_clock.init(); + var cancelled = std.atomic.Value(bool).init(true); + var options = testOptions(); + options.cancel_requested = &cancelled; + try std.testing.expectError( + error.Cancelled, + copyBytesBounded(std.testing.allocator, "bytes", 5, options), + ); + try std.testing.expectError( + error.Cancelled, + encodeClipboardText("text", null, options), + ); + + cancelled.store(false, .release); + options.deadline_ns = clipboard_clock.nowNs() - 1; + try std.testing.expectError( + error.TimedOut, + copyBytesBounded(std.testing.allocator, "bytes", 5, options), + ); + try std.testing.expectError( + error.TimedOut, + clipboardTextToUtf8(std.testing.allocator, &.{ 't', 0 }, 1, options), + ); +} + +test "Windows clipboard UTF-16 scan is bounded by output limit" { + const exact = try clipboardTextToUtf8(std.testing.allocator, &.{ 'a', 'b', 0 }, 2, testOptions()); + defer std.testing.allocator.free(exact); + try std.testing.expectEqualStrings("ab", exact); + try std.testing.expectError( + error.LimitExceeded, + clipboardTextToUtf8(std.testing.allocator, &.{ 'a', 'b', 0 }, 1, testOptions()), + ); + try std.testing.expectError( + error.MissingNul, + clipboardTextToUtf8(std.testing.allocator, &.{ 'a', 'b' }, 2, testOptions()), + ); +} diff --git a/packages/core/src/zig/clipboard/x11.zig b/packages/core/src/zig/clipboard/x11.zig new file mode 100644 index 0000000000..12bd92b066 --- /dev/null +++ b/packages/core/src/zig/clipboard/x11.zig @@ -0,0 +1,3137 @@ +const std = @import("std"); +const builtin = @import("builtin"); +const clipboard_clock = @import("clock.zig"); +const linux = @import("linux.zig"); + +pub const ATOM_PRIMARY: u32 = 1; +pub const ATOM_ATOM: u32 = 4; +pub const ATOM_STRING: u32 = 31; +const ATOM_INTEGER: u32 = 19; +const EVENT_PROPERTY_NOTIFY: u8 = 28; +const EVENT_SELECTION_CLEAR: u8 = 29; +const EVENT_SELECTION_REQUEST: u8 = 30; +const EVENT_SELECTION_NOTIFY: u8 = 31; +const EVENT_MASK_PROPERTY_CHANGE: u32 = 1 << 22; +const PROPERTY_DELETE: u8 = 1; +const MAX_PROVIDERS = 4; +const TRANSFER_IDLE_TIMEOUT_NS = 30 * std.time.ns_per_s; +const CONNECT_POLL_SLICE_MS: i32 = 20; +const CONNECT_POLL_COUNT_MAX: u16 = 500; +const XAUTHORITY_SIZE_MAX: usize = 1024 * 1024; +const XAUTHORITY_READ_CHUNK_SIZE: usize = 16 * 1024; +const DISPLAY_SIZE_MAX: usize = 4096; +const XAUTH_NAME = "MIT-MAGIC-COOKIE-1"; +const XAUTH_FAMILY_INTERNET: u16 = 0; +const XAUTH_FAMILY_INTERNET6: u16 = 6; +const XAUTH_FAMILY_LOCAL: u16 = 256; +const XAUTH_FAMILY_WILD: u16 = 65535; + +const ATOM_NAMES = [_][]const u8{ + "CLIPBOARD", + "TARGETS", + "UTF8_STRING", + "TEXT", + "text/plain", + "text/plain;charset=utf-8", + "image/png", + "OPENTUI_CLIPBOARD", + "INCR", + "TIMESTAMP", +}; +const ATOM_VALUE_COUNT = 11; +const ATOM_TIMESTAMP_INDEX = 10; + +pub const Progress = enum { pending, ready, unsupported, failed }; +pub const Failure = enum { none, connection, flush, atom, protocol, provider }; +pub const SelectionResult = enum { ok, pending, committed, unsupported, failed }; +pub const ReadResult = enum { pending, ready, refused, candidate_failed, limit_exceeded, out_of_memory, failed }; + +const Phase = enum { idle, atoms, flush, replies, window, window_flush, ready, unsupported, failed }; +const FlushReadiness = enum { pending, ready, failed }; +const OutputResult = enum { complete, pending, failed }; + +const DisplayKind = enum { unix, tcp4, tcp6 }; +const DisplayEndpoint = struct { + kind: DisplayKind, + display: u16, + screen: c_int, + unix_path: [108]u8 = @splat(0), + unix_path_length: u8 = 0, + unix_abstract_first: bool = false, + tcp4_fallback: bool = false, +}; + +const DisplayAddress = struct { + address: std.net.Address, + length: std.posix.socklen_t, + kind: DisplayKind, +}; + +const XauthorityMatch = struct { + storage: []u8 = &.{}, + name: []u8 = &.{}, + data: []u8 = &.{}, + + fn deinit(match: *XauthorityMatch, allocator: std.mem.Allocator) void { + if (match.storage.len > 0) allocator.free(match.storage); + match.* = .{}; + } +}; + +const ReadPhase = enum { idle, selection, property, incremental, ready, refused, limit_exceeded, failed }; +pub const ReadState = struct { + phase: ReadPhase = .idle, + window: u32 = 0, + selection: u32 = 0, + target: u32 = 0, + property_cookie: ?linux.XcbCookie = null, + incremental: bool = false, + max_bytes: u32 = 0, + notification_pending: bool = false, + actual_type: u32 = 0, +}; + +pub const WriteState = struct { + provider: ?*Provider = null, + clear: bool = false, + selection: u32 = 0, + owner_cookie: ?linux.XcbCookie = null, + waiting_timestamp: bool = false, + mutation_dispatched: bool = false, + committed: bool = false, + failed: bool = false, + timestamp_window: u32 = 0, + timestamp_window_sequence: u32 = 0, + timestamp_property_sequence: u32 = 0, +}; + +const RetiredTimestamp = struct { + window: u32 = 0, + window_sequence: u32 = 0, + property_sequence: u32 = 0, +}; + +const Transfer = struct { + id: u64, + provider: *Provider, + data: []const u8, + requestor: u32, + property: u32, + target: u32, + offset: u32 = 0, + sent_terminal: bool = false, + last_progress_ns: i128, + delete_pending: bool = false, +}; + +const PendingResponse = struct { + request: linux.XcbSelectionRequestEvent, + property_cookie: linux.XcbCookie, + barrier_cookie: linux.XcbCookie, + property: u32, + transfer_id: u64 = 0, + notify: bool = true, + transfer_expired: bool = false, +}; + +pub const Provider = struct { + selection: u32, + data: []u8, + latin1: []u8 = &.{}, + timestamp: u32 = 0, + owns_data: bool = false, + retired: bool = false, + transfer_count: u32 = 0, +}; + +pub const Atoms = struct { + clipboard: u32, + targets: u32, + utf8_string: u32, + text: u32, + text_plain: u32, + text_plain_utf8: u32, + png: u32, + property: u32, + incr: u32, + timestamp: u32, +}; + +pub const Connection = struct { + allocator: std.mem.Allocator, + symbols: *const linux.XcbSymbols, + max_provider_transfers: u32, + connection: ?*linux.XcbConnection = null, + phase: Phase = .idle, + failure: Failure = .none, + cookies: [ATOM_NAMES.len]linux.XcbCookie = undefined, + // Keep the former MULTIPLE slot so host-side timestamp fixtures retain their stable index. + atom_values: [ATOM_VALUE_COUNT]u32 = undefined, + request_index: u8 = 0, + reply_index: u8 = 0, + output_ready_override: ?bool = null, + screen_index: c_int = 0, + root_window: u32 = 0, + owner_window: u32 = 0, + maximum_request_bytes: u32 = 0, + providers: [MAX_PROVIDERS]?*Provider = .{null} ** MAX_PROVIDERS, + clipboard_provider: ?*Provider = null, + primary_provider: ?*Provider = null, + transfers: []Transfer = &.{}, + transfer_count: u32 = 0, + transfer_cursor: u32 = 0, + transfer_id_next: u64 = 1, + responses: []PendingResponse = &.{}, + response_count: u32 = 0, + output_pending: bool = false, + retired_timestamps: [2]RetiredTimestamp = .{ .{}, .{} }, + connect_thread: ?std.Thread = null, + connect_exited: std.atomic.Value(bool) = .init(false), + connect_result: ?*linux.XcbConnection = null, + connect_screen_index: c_int = 0, + connect_mutex: std.Thread.Mutex = .{}, + connect_cancel_requested: bool = false, + connect_cancel_fd: ?std.posix.fd_t = null, + test_connected_fd: ?std.posix.fd_t = null, + + pub fn init( + allocator: std.mem.Allocator, + symbols: *const linux.XcbSymbols, + max_provider_transfers: u32, + ) Connection { + return .{ + .allocator = allocator, + .symbols = symbols, + .max_provider_transfers = max_provider_transfers, + }; + } + + pub fn deinit(self: *Connection) void { + std.debug.assert(self.connect_thread == null); + if (self.connection) |connection| { + var index = self.reply_index; + while (index < self.request_index) : (index += 1) { + self.symbols.xcb_discard_reply(connection, self.cookies[index].sequence); + } + self.releaseProviders(); + for (self.retired_timestamps) |retired| { + if (retired.window != 0) _ = self.symbols.xcb_destroy_window(connection, retired.window); + } + if (self.owner_window != 0) _ = self.symbols.xcb_destroy_window(connection, self.owner_window); + self.symbols.xcb_disconnect(connection); + } + if (self.transfers.len > 0) self.allocator.free(self.transfers); + if (self.responses.len > 0) self.allocator.free(self.responses); + self.* = undefined; + } + + pub fn drive(self: *Connection) Progress { + switch (self.phase) { + .idle => return self.connect(), + .atoms => return self.requestAtom(), + .flush => return self.flushAtoms(), + .replies => return self.pollAtom(), + .window => return self.createOwnerWindow(), + .window_flush => return self.flushOwnerWindow(), + .ready => return .ready, + .unsupported => return .unsupported, + .failed => return .failed, + } + } + + pub fn atoms(self: *const Connection) ?Atoms { + if (self.phase != .ready) return null; + return .{ + .clipboard = self.atom_values[0], + .targets = self.atom_values[1], + .utf8_string = self.atom_values[2], + .text = self.atom_values[3], + .text_plain = self.atom_values[4], + .text_plain_utf8 = self.atom_values[5], + .png = self.atom_values[6], + .property = self.atom_values[7], + .incr = self.atom_values[8], + .timestamp = self.atom_values[ATOM_TIMESTAMP_INDEX], + }; + } + + pub fn takeFailure(self: *Connection) Failure { + const failure = self.failure; + if (self.phase == .ready) self.failure = .none; + return failure; + } + + fn connect(self: *Connection) Progress { + if (self.connect_thread == null and self.connection == null) { + self.connect_thread = std.Thread.spawn(.{}, connectWorker, .{self}) catch { + self.phase = .unsupported; + return .unsupported; + }; + return .pending; + } + if (!self.joinConnectThread()) return .pending; + const connection = self.connection orelse { + self.phase = .unsupported; + return .unsupported; + }; + if (self.symbols.xcb_connection_has_error(connection) != 0) { + self.phase = .unsupported; + return .unsupported; + } + self.phase = .atoms; + return .pending; + } + + fn connectWorker(self: *Connection) void { + self.connectWorkerRun() catch {}; + self.connect_mutex.lock(); + std.debug.assert(self.connect_cancel_fd == null); + self.connect_mutex.unlock(); + self.connect_exited.store(true, .release); + } + + fn connectWorkerRun(self: *Connection) !void { + if (self.cancelRequested()) return; + var endpoint: DisplayEndpoint = undefined; + var fd: std.posix.fd_t = undefined; + var fd_owned = false; + defer if (fd_owned) std.posix.close(fd); + if (comptime builtin.is_test) { + if (self.test_connected_fd) |test_fd| { + self.test_connected_fd = null; + endpoint = .{ .kind = .unix, .display = 0, .screen = 0 }; + fd = test_fd; + fd_owned = true; + } else { + endpoint = try parseDisplay(std.posix.getenv("DISPLAY") orelse return error.UnsupportedDisplay); + fd = try self.connectSocket(&endpoint); + fd_owned = true; + } + } else { + endpoint = try parseDisplay(std.posix.getenv("DISPLAY") orelse return error.UnsupportedDisplay); + fd = try self.connectSocket(&endpoint); + fd_owned = true; + } + + const cancel_fd = try duplicateCancellationFd(fd); + if (!self.publishCancellationFd(cancel_fd)) { + std.posix.close(cancel_fd); + return; + } + defer self.unpublishCancellationFd(cancel_fd); + if (self.cancelRequested()) return; + + var auth = if (builtin.is_test and endpoint.unix_path_length == 0) + XauthorityMatch{} + else + try loadXauthority(self, endpoint); + defer auth.deinit(self.allocator); + var auth_info: linux.XcbAuthInfo = undefined; + const auth_pointer: ?*linux.XcbAuthInfo = if (auth.name.len == 0) null else blk: { + auth_info = .{ + .name_length = @intCast(auth.name.len), + .name = auth.name.ptr, + .data_length = @intCast(auth.data.len), + .data = auth.data.ptr, + }; + break :blk &auth_info; + }; + fd_owned = false; + const result = self.symbols.xcb_connect_to_fd(fd, auth_pointer); + + self.connect_mutex.lock(); + if (self.connect_cancel_requested) { + self.connect_mutex.unlock(); + if (result) |connection| self.symbols.xcb_disconnect(connection); + return; + } + self.connect_result = result; + self.connect_screen_index = endpoint.screen; + self.connect_mutex.unlock(); + } + + fn joinConnectThread(self: *Connection) bool { + const thread = self.connect_thread orelse return true; + if (!self.connect_exited.load(.acquire)) return false; + thread.join(); + self.connect_thread = null; + self.connection = self.connect_result; + self.connect_result = null; + self.screen_index = self.connect_screen_index; + return true; + } + + pub fn shutdownReady(self: *Connection) bool { + return self.joinConnectThread(); + } + + pub fn requestShutdown(self: *Connection) void { + self.connect_mutex.lock(); + self.connect_cancel_requested = true; + if (self.connect_cancel_fd) |fd| std.posix.shutdown(fd, .recv) catch {}; + self.connect_mutex.unlock(); + } + + fn cancelRequested(self: *Connection) bool { + self.connect_mutex.lock(); + defer self.connect_mutex.unlock(); + return self.connect_cancel_requested; + } + + fn publishCancellationFd(self: *Connection, fd: std.posix.fd_t) bool { + self.connect_mutex.lock(); + defer self.connect_mutex.unlock(); + std.debug.assert(self.connect_cancel_fd == null); + if (self.connect_cancel_requested) return false; + self.connect_cancel_fd = fd; + return true; + } + + fn unpublishCancellationFd(self: *Connection, fd: std.posix.fd_t) void { + self.connect_mutex.lock(); + std.debug.assert(self.connect_cancel_fd == fd); + self.connect_cancel_fd = null; + std.posix.close(fd); + self.connect_mutex.unlock(); + } + + fn connectSocket(self: *Connection, endpoint: *DisplayEndpoint) !std.posix.fd_t { + const candidate_count: u8 = displayCandidateCount(endpoint.*); + var candidate_index: u8 = 0; + while (candidate_index < candidate_count) : (candidate_index += 1) { + const candidate = try displayAddress(endpoint.*, candidate_index); + const fd = self.connectCandidate(candidate) catch |err| { + if (err == error.Cancelled) return err; + continue; + }; + endpoint.kind = candidate.kind; + return fd; + } + return error.ConnectionRefused; + } + + fn connectCandidate(self: *Connection, candidate: DisplayAddress) !std.posix.fd_t { + if (self.cancelRequested()) return error.Cancelled; + const fd = try std.posix.socket( + candidate.address.any.family, + std.posix.SOCK.STREAM | std.posix.SOCK.NONBLOCK | std.posix.SOCK.CLOEXEC, + 0, + ); + errdefer std.posix.close(fd); + + std.posix.connect(fd, &candidate.address.any, candidate.length) catch |err| switch (err) { + error.WouldBlock, error.ConnectionPending => { + var poll_count: u16 = 0; + while (poll_count < CONNECT_POLL_COUNT_MAX) : (poll_count += 1) { + if (self.cancelRequested()) return error.Cancelled; + var descriptors = [_]std.posix.pollfd{.{ + .fd = fd, + .events = std.posix.POLL.OUT, + .revents = 0, + }}; + const count = try std.posix.poll(&descriptors, CONNECT_POLL_SLICE_MS); + if (count == 0) continue; + if (descriptors[0].revents & std.posix.POLL.NVAL != 0) return error.SocketInvalid; + try std.posix.getsockoptError(fd); + break; + } + if (poll_count == CONNECT_POLL_COUNT_MAX) return error.ConnectionTimedOut; + }, + else => return err, + }; + return fd; + } + + fn requestAtom(self: *Connection) Progress { + const connection = self.connection orelse return self.fail(.connection); + if (self.symbols.xcb_connection_has_error(connection) != 0) return self.fail(.connection); + std.debug.assert(self.request_index < ATOM_NAMES.len); + + const name = ATOM_NAMES[self.request_index]; + self.cookies[self.request_index] = self.symbols.xcb_intern_atom( + connection, + 0, + @intCast(name.len), + name.ptr, + ); + self.request_index += 1; + if (self.request_index < ATOM_NAMES.len) return .pending; + self.phase = .flush; + return .pending; + } + + fn flushAtoms(self: *Connection) Progress { + const connection = self.connection orelse return self.fail(.connection); + switch (self.flushReadiness(connection)) { + .pending => return .pending, + .failed => return self.fail(.connection), + .ready => {}, + } + // This fresh private connection has only the fixed atom batch queued. + if (self.symbols.xcb_flush(connection) <= 0) return self.fail(.flush); + self.phase = .replies; + return .pending; + } + + fn flushReadiness(self: *Connection, connection: *linux.XcbConnection) FlushReadiness { + if (comptime builtin.is_test) { + if (self.output_ready_override) |ready| return if (ready) .ready else .pending; + } + if (comptime builtin.os.tag != .linux) return .failed; + var descriptor = [_]std.posix.pollfd{.{ + .fd = self.symbols.xcb_get_file_descriptor(connection), + .events = std.posix.POLL.OUT, + .revents = 0, + }}; + const count = std.posix.poll(&descriptor, 0) catch return .failed; + if (descriptor[0].revents & (std.posix.POLL.ERR | std.posix.POLL.HUP | std.posix.POLL.NVAL) != 0) { + return .failed; + } + if (count == 0 or descriptor[0].revents & std.posix.POLL.OUT == 0) return .pending; + return .ready; + } + + fn pollAtom(self: *Connection) Progress { + const connection = self.connection orelse return self.fail(.connection); + if (self.symbols.xcb_connection_has_error(connection) != 0) return self.fail(.connection); + std.debug.assert(self.reply_index < self.request_index); + + var reply_pointer: ?*anyopaque = null; + var error_pointer: ?*linux.XcbGenericError = null; + const available = self.symbols.xcb_poll_for_reply( + connection, + self.cookies[self.reply_index].sequence, + &reply_pointer, + &error_pointer, + ); + if (available == 0) return .pending; + self.reply_index += 1; + defer if (reply_pointer) |pointer| std.c.free(pointer); + defer if (error_pointer) |pointer| std.c.free(pointer); + if (error_pointer != null) return self.fail(.atom); + const opaque_reply = reply_pointer orelse return self.fail(.atom); + const reply: *const linux.XcbInternAtomReply = @ptrCast(@alignCast(opaque_reply)); + if (reply.atom == 0) return self.fail(.atom); + const atom_index = self.reply_index - 1; + self.atom_values[if (atom_index < 9) atom_index else ATOM_TIMESTAMP_INDEX] = reply.atom; + + if (self.reply_index < self.request_index) return .pending; + self.phase = .window; + return .pending; + } + + pub fn selectionAtom(self: *const Connection, primary: bool) u32 { + return if (primary) ATOM_PRIMARY else self.atoms().?.clipboard; + } + + pub fn targetAtoms(self: *const Connection, mime: []const u8, output: *[5]u32) []const u32 { + const atoms_value = self.atoms().?; + if (std.ascii.eqlIgnoreCase(mime, "image/png")) { + output[0] = atoms_value.png; + return output[0..1]; + } + if (!std.ascii.eqlIgnoreCase(mime, "text/plain")) return output[0..0]; + output.* = .{ + atoms_value.text_plain_utf8, + atoms_value.utf8_string, + atoms_value.text_plain, + atoms_value.text, + ATOM_STRING, + }; + return output; + } + + pub fn beginRead(self: *Connection, state: *ReadState, primary: bool, target: u32, max_bytes: u32) bool { + const connection = self.connection orelse return false; + std.debug.assert(state.phase == .idle or state.phase == .refused); + if (state.window == 0) { + state.window = self.symbols.xcb_generate_id(connection); + if (state.window == 0 or state.window == std.math.maxInt(u32)) return false; + const event_mask = [_]u32{EVENT_MASK_PROPERTY_CHANGE}; + _ = self.symbols.xcb_create_window( + connection, + 0, + state.window, + self.root_window, + 0, + 0, + 1, + 1, + 0, + 1, + 0, + 1 << 11, + &event_mask, + ); + } + const atoms_value = self.atoms().?; + state.selection = self.selectionAtom(primary); + state.target = target; + state.max_bytes = max_bytes; + state.phase = .selection; + _ = self.symbols.xcb_delete_property(connection, state.window, atoms_value.property); + _ = self.symbols.xcb_convert_selection( + connection, + state.window, + state.selection, + target, + atoms_value.property, + 0, + ); + return self.queueFlush() != .failed; + } + + pub fn routeReadEvent(self: *Connection, state: *ReadState, event: *const linux.XcbGenericEvent) bool { + const event_type = event.response_type & 0x7f; + if (event_type == EVENT_SELECTION_NOTIFY) { + const notify: *const linux.XcbSelectionNotifyEvent = @ptrCast(@alignCast(event)); + if (state.phase != .selection or notify.requestor != state.window or + notify.selection != state.selection) return false; + if (notify.target != state.target) { + const atoms_value = self.atoms() orelse return false; + if (state.target != atoms_value.text or notify.target != ATOM_STRING) return false; + } + if (notify.property == 0) { + state.phase = .refused; + return true; + } + state.property_cookie = self.symbols.xcb_get_property( + self.connection.?, + 0, + state.window, + self.atoms().?.property, + 0, + 0, + propertyLongLength(state.max_bytes +| 1), + ); + state.phase = .property; + _ = self.queueFlush(); + return true; + } + if (event_type == EVENT_PROPERTY_NOTIFY and state.phase == .incremental) { + const notify: *const linux.XcbPropertyNotifyEvent = @ptrCast(@alignCast(event)); + if (notify.window != state.window or notify.atom != self.atoms().?.property or notify.state != 0) return false; + state.property_cookie = self.symbols.xcb_get_property( + self.connection.?, + 1, + state.window, + self.atoms().?.property, + 0, + 0, + propertyLongLength(state.max_bytes +| 1), + ); + state.phase = .property; + _ = self.queueFlush(); + return true; + } + if (event_type == EVENT_PROPERTY_NOTIFY and state.phase == .property) { + const notify: *const linux.XcbPropertyNotifyEvent = @ptrCast(@alignCast(event)); + if (notify.window != state.window or notify.atom != self.atoms().?.property or notify.state != 0) return false; + state.notification_pending = true; + return true; + } + return false; + } + + pub fn driveRead( + self: *Connection, + state: *ReadState, + data: *std.ArrayListUnmanaged(u8), + max_bytes: u32, + ) ReadResult { + if (self.output_pending) switch (self.flushOutput()) { + .complete => {}, + .pending => return .pending, + .failed => { + self.failure = .flush; + self.phase = .failed; + state.phase = .failed; + return .failed; + }, + }; + switch (state.phase) { + .ready => return .ready, + .refused => return .refused, + .limit_exceeded => return .limit_exceeded, + .failed => return .candidate_failed, + .idle, .selection, .incremental => return .pending, + .property => {}, + } + const cookie = state.property_cookie orelse return .pending; + var reply_pointer: ?*anyopaque = null; + var error_pointer: ?*linux.XcbGenericError = null; + const available = self.symbols.xcb_poll_for_reply( + self.connection.?, + cookie.sequence, + &reply_pointer, + &error_pointer, + ); + if (available == 0) return .pending; + state.property_cookie = null; + defer if (reply_pointer) |pointer| std.c.free(pointer); + defer if (error_pointer) |pointer| std.c.free(pointer); + if (error_pointer != null) return failReadCandidate(state); + const opaque_reply = reply_pointer orelse return failReadCandidate(state); + const reply: *const linux.XcbGetPropertyReply = @ptrCast(@alignCast(opaque_reply)); + const bytes = propertyBytes(reply) orelse return failReadCandidate(state); + if (reply.atom_type == self.atoms().?.incr) { + if (reply.format != 32 or (bytes.len != 0 and bytes.len < 4)) return failReadCandidate(state); + // xclip sends an empty INCR property instead of the ICCCM size hint. + if (bytes.len >= 4) { + const announced = std.mem.readInt(u32, bytes[0..4], builtin.cpu.arch.endian()); + if (announced > max_bytes) { + state.phase = .limit_exceeded; + return .limit_exceeded; + } + } + state.incremental = true; + state.phase = .incremental; + _ = self.symbols.xcb_delete_property(self.connection.?, state.window, self.atoms().?.property); + _ = self.queueFlush(); + return .pending; + } + const atoms_value = self.atoms().?; + const text_type_supported = reply.atom_type == atoms_value.utf8_string or + reply.atom_type == atoms_value.text_plain_utf8 or reply.atom_type == atoms_value.text_plain or + reply.atom_type == ATOM_STRING; + const target_is_text = state.target == atoms_value.text or state.target == atoms_value.utf8_string or + state.target == atoms_value.text_plain_utf8 or state.target == atoms_value.text_plain or + state.target == ATOM_STRING; + const accepted_type = if (target_is_text) + ((state.actual_type == 0 and text_type_supported) or reply.atom_type == state.actual_type) + else + reply.atom_type == state.target; + if (reply.format != 8 or !accepted_type) { + if (state.target == atoms_value.text and state.actual_type == 0 and reply.format == 8) { + state.phase = .refused; + return .refused; + } + return failReadCandidate(state); + } + if (state.actual_type == 0) state.actual_type = reply.atom_type; + if (bytes.len == 0 and state.incremental) { + state.phase = .ready; + return .ready; + } + if (reply.bytes_after > 0 or bytes.len > max_bytes -| data.items.len) { + state.phase = .limit_exceeded; + return .limit_exceeded; + } + if (state.actual_type == ATOM_STRING) { + const appended = appendLatin1(self.allocator, data, bytes, max_bytes) catch { + state.phase = .failed; + return .out_of_memory; + }; + if (!appended) { + state.phase = .limit_exceeded; + return .limit_exceeded; + } + } else { + data.appendSlice(self.allocator, bytes) catch { + state.phase = .failed; + return .out_of_memory; + }; + } + if (state.incremental) { + state.phase = .incremental; + if (state.notification_pending) { + state.notification_pending = false; + self.requestIncrementalProperty(state); + } + return .pending; + } + _ = self.symbols.xcb_delete_property(self.connection.?, state.window, self.atoms().?.property); + _ = self.queueFlush(); + state.phase = .ready; + return .ready; + } + + pub fn cleanupRead(self: *Connection, state: *ReadState) void { + defer state.* = .{}; + const connection = self.connection orelse return; + if (state.property_cookie) |cookie| self.symbols.xcb_discard_reply(connection, cookie.sequence); + if (state.window != 0) { + _ = self.symbols.xcb_delete_property(connection, state.window, self.atom_values[7]); + _ = self.symbols.xcb_destroy_window(connection, state.window); + if (self.phase == .ready) _ = self.queueFlush(); + } + } + + pub fn beginWrite(self: *Connection, state: *WriteState, primary: bool, data: []u8) SelectionResult { + if (self.retiredTimestamp(self.selectionAtom(primary)).window != 0) return .pending; + if (!self.canPublish(primary)) return self.selectionFailure(.provider); + const slot = self.freeProviderSlot() orelse return self.selectionFailure(.provider); + const provider = self.allocator.create(Provider) catch return self.selectionFailure(.provider); + provider.* = .{ + .selection = self.selectionAtom(primary), + .data = data, + .latin1 = encodeLatin1(self.allocator, data) catch &.{}, + }; + const connection = self.connection orelse { + self.allocator.destroy(provider); + return self.selectionFailure(.connection); + }; + state.* = .{ + .provider = provider, + .selection = provider.selection, + .waiting_timestamp = true, + }; + slot.* = provider; + if (!self.createTimestampWindow(state)) { + self.removeProvider(provider, false); + state.* = .{}; + return self.selectionFailure(.protocol); + } + state.timestamp_property_sequence = self.symbols.xcb_change_property( + connection, + 0, + state.timestamp_window, + self.atoms().?.timestamp, + ATOM_INTEGER, + 8, + 0, + null, + ).sequence; + if (self.queueFlush() != .failed) return .pending; + self.removeProvider(provider, false); + self.destroyTimestampWindow(state); + state.* = .{}; + return self.selectionFailure(.flush); + } + + pub fn beginClear(self: *Connection, state: *WriteState, primary: bool) SelectionResult { + if (self.retiredTimestamp(self.selectionAtom(primary)).window != 0) return .pending; + const connection = self.connection orelse return self.selectionFailure(.connection); + const selection = self.selectionAtom(primary); + state.* = .{ + .clear = true, + .selection = selection, + .waiting_timestamp = true, + }; + if (!self.createTimestampWindow(state)) { + state.* = .{}; + return self.selectionFailure(.protocol); + } + state.timestamp_property_sequence = self.symbols.xcb_change_property( + connection, + 0, + state.timestamp_window, + self.atoms().?.timestamp, + ATOM_INTEGER, + 8, + 0, + null, + ).sequence; + if (self.queueFlush() != .failed) return .pending; + self.destroyTimestampWindow(state); + state.* = .{}; + return self.selectionFailure(.flush); + } + + pub fn driveWrite(self: *Connection, state: *WriteState) SelectionResult { + if (state.failed) return .failed; + if (state.committed) return .committed; + if (self.output_pending) switch (self.flushOutput()) { + .complete => {}, + .pending => return .pending, + .failed => { + if (!state.mutation_dispatched) return self.abortWrite(state); + self.output_pending = false; + _ = self.fail(.flush); + state.failed = true; + return .failed; + }, + }; + const cookie = state.owner_cookie orelse return .pending; + var reply_pointer: ?*anyopaque = null; + var error_pointer: ?*linux.XcbGenericError = null; + const available = self.symbols.xcb_poll_for_reply( + self.connection.?, + cookie.sequence, + &reply_pointer, + &error_pointer, + ); + if (available == 0) return .pending; + state.owner_cookie = null; + defer if (reply_pointer) |pointer| std.c.free(pointer); + defer if (error_pointer) |pointer| std.c.free(pointer); + const opaque_reply = reply_pointer orelse { + state.failed = true; + return .failed; + }; + if (error_pointer != null) { + state.failed = true; + return .failed; + } + const reply: *const linux.XcbGetSelectionOwnerReply = @ptrCast(@alignCast(opaque_reply)); + const expected_owner = if (state.clear) 0 else self.owner_window; + if (reply.owner != expected_owner) { + if (!state.clear) self.retireCurrent(state.selection); + state.failed = true; + return .failed; + } + state.committed = true; + return .committed; + } + + pub fn cleanupWrite(self: *Connection, state: *WriteState) void { + if (state.owner_cookie) |cookie| { + if (self.connection) |connection| self.symbols.xcb_discard_reply(connection, cookie.sequence); + } + if (state.provider) |provider| self.removeProvider(provider, false); + if (state.waiting_timestamp) self.retireTimestampWindow(state) else self.destroyTimestampWindow(state); + state.* = .{}; + } + + pub fn abandonMutationConfirmation(self: *Connection, state: *WriteState) void { + std.debug.assert(state.mutation_dispatched); + if (state.owner_cookie) |cookie| { + if (self.connection) |connection| self.symbols.xcb_discard_reply(connection, cookie.sequence); + } + state.* = .{}; + } + + pub fn consumeRetiredTimestampEvent(self: *Connection, event: *const linux.XcbGenericEvent) bool { + for (&self.retired_timestamps) |*retired| { + if (retired.window == 0) continue; + if (event.response_type == 0) { + const x_error: *const linux.XcbGenericError = @ptrCast(@alignCast(event)); + if (x_error.resource_id != retired.window and + !requestSequenceMatches(retired.window_sequence, x_error.full_sequence) and + !requestSequenceMatches(retired.property_sequence, x_error.full_sequence)) + { + continue; + } + self.destroyRetiredTimestampWindow(retired); + return true; + } + if ((event.response_type & 0x7f) != EVENT_PROPERTY_NOTIFY) continue; + const notify: *const linux.XcbPropertyNotifyEvent = @ptrCast(@alignCast(event)); + if (notify.window != retired.window or + notify.atom != self.atom_values[ATOM_TIMESTAMP_INDEX] or notify.state != 0) + { + continue; + } + self.destroyRetiredTimestampWindow(retired); + return true; + } + return false; + } + + pub fn routeWriteEvent(self: *Connection, state: *WriteState, event: *const linux.XcbGenericEvent) bool { + if (state.waiting_timestamp and event.response_type == 0) { + const x_error: *const linux.XcbGenericError = @ptrCast(@alignCast(event)); + if (x_error.resource_id != state.timestamp_window and + !requestSequenceMatches(state.timestamp_window_sequence, x_error.full_sequence) and + !requestSequenceMatches(state.timestamp_property_sequence, x_error.full_sequence)) + { + return false; + } + state.waiting_timestamp = false; + self.destroyTimestampWindow(state); + if (state.provider) |provider| { + self.removeProvider(provider, false); + state.provider = null; + } + state.failed = true; + return true; + } + if (!state.waiting_timestamp or (event.response_type & 0x7f) != EVENT_PROPERTY_NOTIFY) return false; + const notify: *const linux.XcbPropertyNotifyEvent = @ptrCast(@alignCast(event)); + if (notify.window != state.timestamp_window or notify.atom != self.atoms().?.timestamp or notify.state != 0) return false; + state.waiting_timestamp = false; + self.destroyTimestampWindow(state); + _ = self.symbols.xcb_set_selection_owner( + self.connection.?, + if (state.clear) 0 else self.owner_window, + state.selection, + notify.time, + ); + state.mutation_dispatched = true; + if (state.clear) { + self.retireCurrent(state.selection); + } else { + const provider = state.provider.?; + provider.timestamp = notify.time; + provider.owns_data = true; + if (self.currentProvider(provider.selection)) |old| self.retireProvider(old); + self.setCurrentProvider(provider); + state.provider = null; + } + state.owner_cookie = self.symbols.xcb_get_selection_owner(self.connection.?, state.selection); + _ = self.queueFlush(); + return true; + } + + pub fn isMutationTimestampEvent( + self: *const Connection, + state: *const WriteState, + event: *const linux.XcbGenericEvent, + ) bool { + if (!state.waiting_timestamp or (event.response_type & 0x7f) != EVENT_PROPERTY_NOTIFY) return false; + const notify: *const linux.XcbPropertyNotifyEvent = @ptrCast(@alignCast(event)); + return notify.window == state.timestamp_window and + notify.atom == self.atom_values[ATOM_TIMESTAMP_INDEX] and notify.state == 0; + } + + pub fn pollEvent(self: *Connection) ?*linux.XcbGenericEvent { + const connection = self.connection orelse return null; + if (self.phase != .ready) return null; + return self.symbols.xcb_poll_for_event(connection); + } + + pub fn handleProviderEvent(self: *Connection, event: *const linux.XcbGenericEvent) void { + switch (event.response_type & 0x7f) { + EVENT_SELECTION_REQUEST => self.handleSelectionRequest(@ptrCast(@alignCast(event))), + EVENT_SELECTION_CLEAR => self.handleSelectionClear(@ptrCast(@alignCast(event))), + EVENT_PROPERTY_NOTIFY => self.handleTransferProperty(@ptrCast(@alignCast(event))), + else => {}, + } + } + + pub fn driveProviderUnit(self: *Connection) bool { + if (self.phase == .failed) { + self.output_pending = false; + self.releaseProviders(); + return false; + } + if (self.output_pending and self.flushOutput() == .failed) { + self.failure = .flush; + self.phase = .failed; + self.releaseProviders(); + return false; + } + if (self.transfer_count > 0) { + const now = clipboard_clock.nowNs(); + const index = self.transfer_cursor % self.transfer_count; + self.transfer_cursor = (self.transfer_cursor + 1) % self.transfer_count; + if (now - self.transfers[index].last_progress_ns >= TRANSFER_IDLE_TIMEOUT_NS) { + self.expireTransferResponse(self.transfers[index].id); + self.removeTransfer(index); + return self.hasWork(); + } + } + if (self.response_count > 0) { + self.drivePendingResponse(); + return self.hasWork(); + } + return self.hasWork(); + } + + pub fn hasWork(self: *const Connection) bool { + if (self.phase == .failed) return false; + return self.output_pending or self.retired_timestamps[0].window != 0 or self.retired_timestamps[1].window != 0 or + self.response_count > 0 or self.clipboard_provider != null or + self.primary_provider != null or self.transfer_count > 0; + } + + pub fn releaseProviders(self: *Connection) void { + if (self.connection) |connection| { + for (self.responses[0..self.response_count]) |response| { + self.symbols.xcb_discard_reply(connection, response.barrier_cookie.sequence); + } + } + self.response_count = 0; + self.transfer_count = 0; + for (&self.providers) |*slot| { + const provider = slot.* orelse continue; + if (provider.owns_data and provider.data.len > 0) self.allocator.free(provider.data); + if (provider.latin1.len > 0) self.allocator.free(provider.latin1); + self.allocator.destroy(provider); + slot.* = null; + } + self.clipboard_provider = null; + self.primary_provider = null; + } + + fn queueFlush(self: *Connection) OutputResult { + const result = self.flushOutput(); + if (result == .failed) { + if (self.failure == .none) self.failure = .flush; + self.output_pending = false; + self.phase = .failed; + } + return result; + } + + fn flushOutput(self: *Connection) OutputResult { + const connection = self.connection orelse return .failed; + switch (self.flushReadiness(connection)) { + .pending => { + self.output_pending = true; + return .pending; + }, + .failed => return .failed, + .ready => {}, + } + if (self.symbols.xcb_flush(connection) <= 0) return .failed; + self.output_pending = false; + return .complete; + } + + fn selectionFailure(self: *Connection, failure: Failure) SelectionResult { + if (self.failure == .none) self.failure = failure; + return .failed; + } + + fn abortWrite(self: *Connection, state: *WriteState) SelectionResult { + std.debug.assert(!state.mutation_dispatched); + if (state.provider) |provider| self.removeProvider(provider, false); + self.destroyTimestampWindow(state); + state.* = .{}; + state.failed = true; + return self.selectionFailure(.protocol); + } + + fn requestIncrementalProperty(self: *Connection, state: *ReadState) void { + state.property_cookie = self.symbols.xcb_get_property( + self.connection.?, + 1, + state.window, + self.atoms().?.property, + 0, + 0, + propertyLongLength(state.max_bytes +| 1), + ); + state.phase = .property; + _ = self.queueFlush(); + } + + fn createTimestampWindow(self: *Connection, state: *WriteState) bool { + const connection = self.connection orelse return false; + state.timestamp_window = self.symbols.xcb_generate_id(connection); + if (state.timestamp_window == 0 or state.timestamp_window == std.math.maxInt(u32)) return false; + const event_mask = [_]u32{EVENT_MASK_PROPERTY_CHANGE}; + state.timestamp_window_sequence = self.symbols.xcb_create_window( + connection, + 0, + state.timestamp_window, + self.root_window, + 0, + 0, + 1, + 1, + 0, + 1, + 0, + 1 << 11, + &event_mask, + ).sequence; + return true; + } + + fn destroyTimestampWindow(self: *Connection, state: *WriteState) void { + if (state.timestamp_window == 0) return; + if (self.connection) |connection| _ = self.symbols.xcb_destroy_window(connection, state.timestamp_window); + state.timestamp_window = 0; + state.timestamp_window_sequence = 0; + state.timestamp_property_sequence = 0; + } + + fn retireTimestampWindow(self: *Connection, state: *WriteState) void { + if (state.timestamp_window == 0) return; + const retired = self.retiredTimestamp(state.selection); + std.debug.assert(retired.window == 0); + retired.* = .{ + .window = state.timestamp_window, + .window_sequence = state.timestamp_window_sequence, + .property_sequence = state.timestamp_property_sequence, + }; + state.timestamp_window = 0; + state.timestamp_window_sequence = 0; + state.timestamp_property_sequence = 0; + _ = self.queueFlush(); + } + + fn destroyRetiredTimestampWindow(self: *Connection, retired: *RetiredTimestamp) void { + _ = self.symbols.xcb_destroy_window(self.connection.?, retired.window); + retired.* = .{}; + _ = self.queueFlush(); + } + + fn retiredTimestamp(self: *Connection, selection: u32) *RetiredTimestamp { + return &self.retired_timestamps[@intFromBool(selection == ATOM_PRIMARY)]; + } + + fn freeProviderSlot(self: *Connection) ?*?*Provider { + for (&self.providers) |*slot| if (slot.* == null) return slot; + return null; + } + + fn canPublish(self: *const Connection, primary: bool) bool { + const selection = self.selectionAtom(primary); + var count: u8 = 0; + for (self.providers) |candidate| { + const provider = candidate orelse continue; + if (provider.selection == selection) count += 1; + } + return count < 2; + } + + fn currentProvider(self: *const Connection, selection: u32) ?*Provider { + return if (selection == ATOM_PRIMARY) self.primary_provider else self.clipboard_provider; + } + + fn setCurrentProvider(self: *Connection, provider: *Provider) void { + if (provider.selection == ATOM_PRIMARY) self.primary_provider = provider else self.clipboard_provider = provider; + } + + fn retireCurrent(self: *Connection, selection: u32) void { + const provider = self.currentProvider(selection) orelse return; + self.retireProvider(provider); + } + + fn retireProvider(self: *Connection, provider: *Provider) void { + if (self.clipboard_provider == provider) self.clipboard_provider = null; + if (self.primary_provider == provider) self.primary_provider = null; + provider.retired = true; + if (provider.transfer_count == 0) self.removeProvider(provider, true); + } + + fn removeProvider(self: *Connection, provider: *Provider, free_data: bool) void { + for (&self.providers) |*slot| { + if (slot.* != provider) continue; + if (free_data and provider.data.len > 0) self.allocator.free(provider.data); + if (provider.latin1.len > 0) self.allocator.free(provider.latin1); + self.allocator.destroy(provider); + slot.* = null; + return; + } + } + + fn handleSelectionClear(self: *Connection, event: *const linux.XcbSelectionClearEvent) void { + if (event.owner != self.owner_window) return; + const provider = self.currentProvider(event.selection) orelse return; + if (timestampBefore(event.time, provider.timestamp)) return; + self.retireProvider(provider); + } + + fn handleSelectionRequest(self: *Connection, event: *const linux.XcbSelectionRequestEvent) void { + const provider = self.currentProvider(event.selection) orelse { + self.sendSelectionNotify(event, 0); + return; + }; + if (event.time != 0 and timestampBefore(event.time, provider.timestamp)) { + self.sendSelectionNotify(event, 0); + return; + } + const property = if (event.property == 0) event.target else event.property; + const atoms_value = self.atoms().?; + if (self.response_count >= self.responses.len) { + self.sendSelectionNotify(event, 0); + return; + } + if (event.target == atoms_value.targets) { + const targets = [_]u32{ + atoms_value.targets, + atoms_value.timestamp, + atoms_value.utf8_string, + atoms_value.text_plain_utf8, + atoms_value.text_plain, + atoms_value.text, + ATOM_STRING, + }; + const target_count: usize = if (provider.latin1.len > 0) targets.len else targets.len - 1; + const property_cookie = self.symbols.xcb_change_property_checked( + self.connection.?, + 0, + event.requestor, + property, + ATOM_ATOM, + 32, + @intCast(target_count), + &targets, + ); + self.queuePropertyResponse(event, property_cookie, property, 0); + return; + } + if (event.target == atoms_value.timestamp) { + const timestamp = [_]u32{provider.timestamp}; + const property_cookie = self.symbols.xcb_change_property_checked( + self.connection.?, + 0, + event.requestor, + property, + ATOM_INTEGER, + 32, + 1, + ×tamp, + ); + self.queuePropertyResponse(event, property_cookie, property, 0); + return; + } + const output_type = if (event.target == atoms_value.text) atoms_value.utf8_string else event.target; + if (event.target != atoms_value.utf8_string and event.target != atoms_value.text_plain_utf8 and + event.target != atoms_value.text_plain and event.target != atoms_value.text and + (event.target != ATOM_STRING or provider.latin1.len == 0)) + { + self.sendSelectionNotify(event, 0); + return; + } + const output_data = if (event.target == ATOM_STRING) provider.latin1 else provider.data; + if (output_data.len <= self.directPayloadBytes()) { + const property_cookie = self.symbols.xcb_change_property_checked( + self.connection.?, + 0, + event.requestor, + property, + output_type, + 8, + @intCast(output_data.len), + if (output_data.len == 0) null else output_data.ptr, + ); + self.queuePropertyResponse(event, property_cookie, property, 0); + return; + } + if (self.transfer_count >= self.transfers.len or self.hasTransfer(event.requestor, property)) { + self.sendSelectionNotify(event, 0); + return; + } + const mask = [_]u32{EVENT_MASK_PROPERTY_CHANGE}; + _ = self.symbols.xcb_change_window_attributes(self.connection.?, event.requestor, 1 << 11, &mask); + const length = [_]u32{@intCast(output_data.len)}; + const property_cookie = self.symbols.xcb_change_property_checked( + self.connection.?, + 0, + event.requestor, + property, + atoms_value.incr, + 32, + 1, + &length, + ); + const transfer_id = self.allocateTransferID(); + self.transfers[self.transfer_count] = .{ + .id = transfer_id, + .provider = provider, + .data = output_data, + .requestor = event.requestor, + .property = property, + .target = output_type, + .last_progress_ns = clipboard_clock.nowNs(), + }; + self.transfer_count += 1; + provider.transfer_count += 1; + self.queuePropertyResponse(event, property_cookie, property, transfer_id); + } + + fn sendSelectionNotify(self: *Connection, request: *const linux.XcbSelectionRequestEvent, property: u32) void { + const notify: linux.XcbSelectionNotifyEvent = .{ + .response_type = EVENT_SELECTION_NOTIFY, + .pad0 = 0, + .sequence = 0, + .time = request.time, + .requestor = request.requestor, + .selection = request.selection, + .target = request.target, + .property = property, + }; + var event_bytes = [_]u8{0} ** 32; + @memcpy(event_bytes[0..@sizeOf(linux.XcbSelectionNotifyEvent)], std.mem.asBytes(¬ify)); + _ = self.symbols.xcb_send_event(self.connection.?, 0, request.requestor, 0, &event_bytes); + _ = self.queueFlush(); + } + + fn queuePropertyResponse( + self: *Connection, + request: *const linux.XcbSelectionRequestEvent, + property_cookie: linux.XcbCookie, + property: u32, + transfer_id: u64, + ) void { + std.debug.assert(self.response_count < self.responses.len); + self.responses[self.response_count] = .{ + .request = request.*, + .property_cookie = property_cookie, + .barrier_cookie = self.symbols.xcb_get_selection_owner(self.connection.?, request.selection), + .property = property, + .transfer_id = transfer_id, + }; + self.response_count += 1; + _ = self.queueFlush(); + } + + fn drivePendingResponse(self: *Connection) void { + const response = self.responses[0]; + var reply_pointer: ?*anyopaque = null; + var error_pointer: ?*linux.XcbGenericError = null; + const available = self.symbols.xcb_poll_for_reply( + self.connection.?, + response.barrier_cookie.sequence, + &reply_pointer, + &error_pointer, + ); + if (available == 0) return; + defer if (reply_pointer) |pointer| std.c.free(pointer); + defer if (error_pointer) |pointer| std.c.free(pointer); + const property_error = self.symbols.xcb_request_check(self.connection.?, response.property_cookie); + defer if (property_error) |pointer| std.c.free(pointer); + const success = !response.transfer_expired and reply_pointer != null and error_pointer == null and property_error == null; + if (response.notify) self.sendSelectionNotify(&response.request, if (success) response.property else 0); + if (!success and response.transfer_id != 0) { + self.removeTransferByID(response.transfer_id); + } + self.response_count -= 1; + if (self.response_count > 0) { + std.mem.copyForwards(PendingResponse, self.responses[0..self.response_count], self.responses[1 .. self.response_count + 1]); + } + if (success and response.transfer_id != 0) { + self.advancePendingTransfer(response.transfer_id); + } + } + + fn handleTransferProperty(self: *Connection, event: *const linux.XcbPropertyNotifyEvent) void { + if (event.state != PROPERTY_DELETE) return; + var index: u32 = 0; + while (index < self.transfer_count) : (index += 1) { + const transfer = &self.transfers[index]; + if (transfer.requestor != event.window or transfer.property != event.atom) continue; + if (transfer.sent_terminal) { + self.removeTransfer(index); + return; + } + if (self.response_count >= self.responses.len or self.hasPendingResponseForTransfer(transfer.id)) { + transfer.delete_pending = true; + return; + } + self.advanceTransfer(index); + return; + } + } + + fn advanceTransfer(self: *Connection, index: u32) void { + const transfer = &self.transfers[index]; + transfer.delete_pending = false; + const remaining = transfer.data.len - transfer.offset; + const count = @min(remaining, self.directPayloadBytes()); + const property_cookie = self.symbols.xcb_change_property_checked( + self.connection.?, + 0, + transfer.requestor, + transfer.property, + transfer.target, + 8, + @intCast(count), + if (count == 0) null else transfer.data.ptr + transfer.offset, + ); + transfer.offset += @intCast(count); + transfer.sent_terminal = count == 0; + transfer.last_progress_ns = clipboard_clock.nowNs(); + self.responses[self.response_count] = .{ + .request = std.mem.zeroes(linux.XcbSelectionRequestEvent), + .property_cookie = property_cookie, + .barrier_cookie = self.symbols.xcb_get_selection_owner(self.connection.?, transfer.provider.selection), + .property = transfer.property, + .transfer_id = transfer.id, + .notify = false, + }; + self.response_count += 1; + _ = self.queueFlush(); + } + + fn directPayloadBytes(self: *const Connection) u32 { + return @max(@as(u32, 1), self.maximum_request_bytes -| 24); + } + + fn hasTransfer(self: *const Connection, requestor: u32, property: u32) bool { + for (self.transfers[0..self.transfer_count]) |transfer| { + if (transfer.requestor == requestor and transfer.property == property) return true; + } + return false; + } + + fn hasPendingResponseForTransfer(self: *const Connection, transfer_id: u64) bool { + std.debug.assert(transfer_id != 0); + for (self.responses[0..self.response_count]) |response| { + if (response.transfer_id == transfer_id) return true; + } + return false; + } + + fn expireTransferResponse(self: *Connection, transfer_id: u64) void { + std.debug.assert(transfer_id != 0); + for (self.responses[0..self.response_count]) |*response| { + if (response.transfer_id != transfer_id) continue; + if (response.notify) response.transfer_expired = true; + return; + } + } + + fn advancePendingTransfer(self: *Connection, transfer_id: u64) void { + std.debug.assert(transfer_id != 0); + var index: u32 = 0; + while (index < self.transfer_count) : (index += 1) { + const transfer = &self.transfers[index]; + if (transfer.id != transfer_id or !transfer.delete_pending) continue; + if (transfer.sent_terminal) { + self.removeTransfer(index); + } else if (self.response_count < self.responses.len) { + self.advanceTransfer(index); + } + return; + } + } + + fn removeTransfer(self: *Connection, index: u32) void { + std.debug.assert(self.transfers[index].id != 0); + const requestor = self.transfers[index].requestor; + const provider = self.transfers[index].provider; + std.debug.assert(provider.transfer_count > 0); + provider.transfer_count -= 1; + self.transfer_count -= 1; + if (index != self.transfer_count) self.transfers[index] = self.transfers[self.transfer_count]; + if (self.transfer_count == 0) self.transfer_cursor = 0 else self.transfer_cursor %= self.transfer_count; + if (provider.retired and provider.transfer_count == 0) self.removeProvider(provider, true); + if (self.phase == .ready) { + for (self.transfers[0..self.transfer_count]) |transfer| { + if (transfer.requestor == requestor) return; + } + const mask = [_]u32{0}; + _ = self.symbols.xcb_change_window_attributes(self.connection.?, requestor, 1 << 11, &mask); + _ = self.queueFlush(); + } + } + + fn removeTransferByID(self: *Connection, transfer_id: u64) void { + std.debug.assert(transfer_id != 0); + var index: u32 = 0; + while (index < self.transfer_count) : (index += 1) { + if (self.transfers[index].id != transfer_id) continue; + self.removeTransfer(index); + return; + } + } + + fn allocateTransferID(self: *Connection) u64 { + const transfer_id = self.transfer_id_next; + std.debug.assert(transfer_id != 0); + std.debug.assert(transfer_id < std.math.maxInt(u64)); + self.transfer_id_next = transfer_id + 1; + return transfer_id; + } + + fn createOwnerWindow(self: *Connection) Progress { + const connection = self.connection orelse return self.fail(.connection); + const setup = self.symbols.xcb_get_setup(connection); + var iterator = self.symbols.xcb_setup_roots_iterator(setup); + var screen_index: c_int = 0; + while (screen_index < self.screen_index and iterator.remaining > 0) : (screen_index += 1) { + self.symbols.xcb_screen_next(&iterator); + } + if (iterator.remaining <= 0) return self.fail(.protocol); + const screen = iterator.data; + self.root_window = screen.root; + self.maximum_request_bytes = @as(u32, setup.maximum_request_length) * 4; + self.owner_window = self.symbols.xcb_generate_id(connection); + if (self.owner_window == 0 or self.owner_window == std.math.maxInt(u32)) return self.fail(.protocol); + const event_mask = [_]u32{EVENT_MASK_PROPERTY_CHANGE}; + _ = self.symbols.xcb_create_window( + connection, + 0, + self.owner_window, + self.root_window, + 0, + 0, + 1, + 1, + 0, + 1, + 0, + 1 << 11, + &event_mask, + ); + self.phase = .window_flush; + return .pending; + } + + fn flushOwnerWindow(self: *Connection) Progress { + const connection = self.connection orelse return self.fail(.connection); + switch (self.flushReadiness(connection)) { + .pending => return .pending, + .failed => return self.fail(.connection), + .ready => {}, + } + if (self.symbols.xcb_flush(connection) <= 0) return self.fail(.flush); + self.transfers = self.allocator.alloc(Transfer, self.max_provider_transfers) catch return self.fail(.provider); + self.responses = self.allocator.alloc(PendingResponse, self.max_provider_transfers) catch return self.fail(.provider); + self.phase = .ready; + return .ready; + } + + fn fail(self: *Connection, failure: Failure) Progress { + self.failure = failure; + self.phase = .failed; + return .failed; + } +}; + +fn parseDisplay(display: []const u8) !DisplayEndpoint { + if (display.len == 0 or display.len > DISPLAY_SIZE_MAX) return error.UnsupportedDisplay; + if (display[0] == '/') { + const base = std.fs.path.basename(display); + if (base.len < 2 or base[0] != 'X') return error.UnsupportedDisplay; + const number = try parseDisplayNumber(base[1..]); + return unixEndpoint(number, 0, display); + } + + var host: []const u8 = undefined; + var suffix: []const u8 = undefined; + if (display[0] == '[') { + const close = std.mem.indexOfScalar(u8, display, ']') orelse return error.UnsupportedDisplay; + if (close + 1 >= display.len or display[close + 1] != ':') return error.UnsupportedDisplay; + host = display[1..close]; + suffix = display[close + 2 ..]; + } else { + const colon = std.mem.lastIndexOfScalar(u8, display, ':') orelse return error.UnsupportedDisplay; + host = display[0..colon]; + suffix = display[colon + 1 ..]; + } + const dot = std.mem.indexOfScalar(u8, suffix, '.'); + const number_text = if (dot) |index| suffix[0..index] else suffix; + const screen_text = if (dot) |index| suffix[index + 1 ..] else "0"; + const number = try parseDisplayNumber(number_text); + const screen = std.fmt.parseInt(c_int, screen_text, 10) catch return error.UnsupportedDisplay; + if (screen < 0) return error.UnsupportedDisplay; + + if (host.len == 0 or std.mem.eql(u8, host, "unix") or std.mem.eql(u8, host, "unix/")) { + var path_buffer: [108]u8 = undefined; + const path = std.fmt.bufPrint(&path_buffer, "/tmp/.X11-unix/X{d}", .{number}) catch + return error.UnsupportedDisplay; + var endpoint = try unixEndpoint(number, screen, path); + endpoint.unix_abstract_first = true; + return endpoint; + } + if (std.mem.endsWith(u8, host, "/unix")) { + var path_buffer: [108]u8 = undefined; + const path = std.fmt.bufPrint(&path_buffer, "/tmp/.X11-unix/X{d}", .{number}) catch + return error.UnsupportedDisplay; + var endpoint = try unixEndpoint(number, screen, path); + endpoint.unix_abstract_first = true; + return endpoint; + } + if (std.mem.eql(u8, host, "localhost")) { + if (number > 59535) return error.UnsupportedDisplay; + return .{ .kind = .tcp6, .display = number, .screen = screen, .tcp4_fallback = true }; + } + if (std.mem.eql(u8, host, "127.0.0.1")) { + if (number > 59535) return error.UnsupportedDisplay; + return .{ .kind = .tcp4, .display = number, .screen = screen }; + } + if (std.mem.eql(u8, host, "::1")) { + if (number > 59535) return error.UnsupportedDisplay; + return .{ .kind = .tcp6, .display = number, .screen = screen }; + } + // Remote DNS is intentionally unsupported; this backend only connects to local displays. + return error.UnsupportedDisplay; +} + +fn duplicateCancellationFd(fd: std.posix.fd_t) !std.posix.fd_t { + const duplicate = try std.posix.dup(fd); + errdefer std.posix.close(duplicate); + _ = try std.posix.fcntl(duplicate, std.posix.F.SETFD, std.posix.FD_CLOEXEC); + return duplicate; +} + +fn parseDisplayNumber(text: []const u8) !u16 { + if (text.len == 0 or text.len > 5) return error.UnsupportedDisplay; + return std.fmt.parseInt(u16, text, 10) catch error.UnsupportedDisplay; +} + +fn unixEndpoint(display: u16, screen: c_int, path: []const u8) !DisplayEndpoint { + if (path.len == 0 or path.len >= 108) return error.UnsupportedDisplay; + var endpoint: DisplayEndpoint = .{ .kind = .unix, .display = display, .screen = screen }; + @memcpy(endpoint.unix_path[0..path.len], path); + endpoint.unix_path_length = @intCast(path.len); + return endpoint; +} + +fn displayCandidateCount(endpoint: DisplayEndpoint) u8 { + if (endpoint.kind == .unix and endpoint.unix_abstract_first) return 2; + if (endpoint.kind == .tcp6 and endpoint.tcp4_fallback) return 2; + return 1; +} + +fn displayAddress(endpoint: DisplayEndpoint, candidate_index: u8) !DisplayAddress { + std.debug.assert(candidate_index < displayCandidateCount(endpoint)); + if (endpoint.kind == .unix) { + if (endpoint.unix_abstract_first and candidate_index == 0) { + var abstract_path: [108]u8 = @splat(0); + const path_length: usize = endpoint.unix_path_length; + if (path_length + 1 > abstract_path.len) return error.UnsupportedDisplay; + @memcpy(abstract_path[1 .. path_length + 1], endpoint.unix_path[0..path_length]); + const address = try std.net.Address.initUnix(abstract_path[0 .. path_length + 1]); + return .{ + .address = address, + .length = @intCast(@offsetOf(std.posix.sockaddr.un, "path") + path_length + 1), + .kind = .unix, + }; + } + const address = try std.net.Address.initUnix(endpoint.unix_path[0..endpoint.unix_path_length]); + return .{ .address = address, .length = address.getOsSockLen(), .kind = .unix }; + } + const kind: DisplayKind = if (endpoint.kind == .tcp6 and endpoint.tcp4_fallback and candidate_index == 1) + .tcp4 + else + endpoint.kind; + const address = switch (kind) { + .tcp4 => std.net.Address.initIp4(.{ 127, 0, 0, 1 }, 6000 + endpoint.display), + .tcp6 => std.net.Address.initIp6( + .{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1 }, + 6000 + endpoint.display, + 0, + 0, + ), + .unix => unreachable, + }; + return .{ .address = address, .length = address.getOsSockLen(), .kind = kind }; +} + +fn loadXauthority(self: *Connection, endpoint: DisplayEndpoint) !XauthorityMatch { + const allocator = self.allocator; + var allocated_path: []u8 = &.{}; + defer if (allocated_path.len > 0) allocator.free(allocated_path); + const path = if (std.posix.getenv("XAUTHORITY")) |configured| + configured + else if (std.posix.getenv("HOME")) |home| blk: { + allocated_path = try std.fs.path.join(allocator, &.{ home, ".Xauthority" }); + break :blk allocated_path; + } else return .{}; + if (path.len == 0 or path.len > std.fs.max_path_bytes) return .{}; + return loadXauthorityPath(self, endpoint, path); +} + +fn loadXauthorityPath(self: *Connection, endpoint: DisplayEndpoint, path: []const u8) !XauthorityMatch { + const open_flags: std.posix.O = .{ + .ACCMODE = .RDONLY, + .NONBLOCK = true, + .CLOEXEC = true, + .NOFOLLOW = true, + }; + // O_NONBLOCK bounds FIFOs and devices. A hostile FUSE filesystem may still block open itself. + const fd = std.posix.open(path, open_flags, 0) catch return .{}; + defer std.posix.close(fd); + const stat = std.posix.fstat(fd) catch return .{}; + if (!std.posix.S.ISREG(stat.mode)) return .{}; + if (stat.size > XAUTHORITY_SIZE_MAX) return .{}; + const size_bytes: usize = @intCast(stat.size); + const storage = try self.allocator.alloc(u8, size_bytes); + errdefer self.allocator.free(storage); + var offset: usize = 0; + while (offset < storage.len) { + if (self.cancelRequested()) return error.Cancelled; + const chunk_end = @min(storage.len, offset + XAUTHORITY_READ_CHUNK_SIZE); + const count = std.posix.read(fd, storage[offset..chunk_end]) catch { + self.allocator.free(storage); + return .{}; + }; + if (count == 0) break; + offset += count; + } + if (offset != storage.len) { + const resized = self.allocator.realloc(storage, offset) catch { + self.allocator.free(storage); + return .{}; + }; + return parseXauthority(resized, endpoint) catch return .{ .storage = resized }; + } + return parseXauthority(storage, endpoint) catch return .{ .storage = storage }; +} + +fn parseXauthority(storage: []u8, endpoint: DisplayEndpoint) !XauthorityMatch { + var hostname_buffer: [std.posix.HOST_NAME_MAX]u8 = undefined; + const hostname = std.posix.getenv("XAUTHLOCALHOSTNAME") orelse + (std.posix.gethostname(&hostname_buffer) catch &.{}); + var best_score: u8 = 0; + var best_name: []u8 = &.{}; + var best_data: []u8 = &.{}; + var offset: usize = 0; + while (offset < storage.len) { + const family = try readXauthorityU16(storage, &offset); + const address = try readXauthorityField(storage, &offset); + const number = try readXauthorityField(storage, &offset); + const name = try readXauthorityField(storage, &offset); + const data = try readXauthorityField(storage, &offset); + // XDM-AUTHORIZATION-1 is intentionally unsupported; local MIT cookies cover the supported transports. + if (!std.mem.eql(u8, name, XAUTH_NAME)) continue; + if (!displayNumberMatches(number, endpoint.display)) continue; + const score = xauthorityAddressScore(family, address, hostname, endpoint.kind); + if (score <= best_score) continue; + best_score = score; + best_name = name; + best_data = data; + } + return .{ .storage = storage, .name = best_name, .data = best_data }; +} + +fn readXauthorityU16(storage: []const u8, offset: *usize) !u16 { + if (storage.len -| offset.* < 2) return error.InvalidXauthority; + const value = std.mem.readInt(u16, storage[offset.*..][0..2], .big); + offset.* += 2; + return value; +} + +fn readXauthorityField(storage: []u8, offset: *usize) ![]u8 { + const length = try readXauthorityU16(storage, offset); + if (length > storage.len -| offset.*) return error.InvalidXauthority; + const field = storage[offset.*..][0..length]; + offset.* += length; + return field; +} + +fn displayNumberMatches(number: []const u8, display: u16) bool { + if (number.len == 0 or number.len > 5) return false; + return (std.fmt.parseInt(u16, number, 10) catch return false) == display; +} + +fn xauthorityAddressScore(family: u16, address: []const u8, hostname: []const u8, kind: DisplayKind) u8 { + if (family == XAUTH_FAMILY_WILD) return 1; + if (family == XAUTH_FAMILY_LOCAL and std.mem.eql(u8, address, hostname)) return 4; + if (kind == .tcp4 and family == XAUTH_FAMILY_INTERNET and std.mem.eql(u8, address, &.{ 127, 0, 0, 1 })) return 5; + if (kind == .tcp6 and family == XAUTH_FAMILY_INTERNET6 and + std.mem.eql(u8, address, &.{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1 })) return 5; + return 0; +} + +fn propertyLongLength(max_bytes: u32) u32 { + return max_bytes / 4 + @intFromBool(max_bytes % 4 != 0); +} + +fn propertyBytes(reply: *const linux.XcbGetPropertyReply) ?[]const u8 { + const element_bytes: u32 = switch (reply.format) { + 0 => 0, + 8 => 1, + 16 => 2, + 32 => 4, + else => return null, + }; + const length = std.math.mul(u32, reply.value_length, element_bytes) catch return null; + const framed_length = std.math.mul(u32, reply.length, 4) catch return null; + if (length > framed_length) return null; + const pointer: [*]const u8 = @ptrCast(reply); + return pointer[@sizeOf(linux.XcbGetPropertyReply)..][0..length]; +} + +fn failReadCandidate(state: *ReadState) ReadResult { + state.phase = .failed; + return .candidate_failed; +} + +fn timestampBefore(left: u32, right: u32) bool { + const difference: i32 = @bitCast(left -% right); + return difference < 0; +} + +fn requestSequenceMatches(expected: u32, actual: u32) bool { + return expected != 0 and expected == actual; +} + +fn encodeLatin1(allocator: std.mem.Allocator, utf8: []const u8) ![]u8 { + var count: usize = 0; + var offset: usize = 0; + while (offset < utf8.len) : (count += 1) { + const sequence_length = std.unicode.utf8ByteSequenceLength(utf8[offset]) catch return error.InvalidUtf8; + if (sequence_length > utf8.len - offset) return error.InvalidUtf8; + const codepoint = std.unicode.utf8Decode(utf8[offset .. offset + sequence_length]) catch return error.InvalidUtf8; + if (codepoint > 255) return error.NotRepresentable; + offset += sequence_length; + } + const output = try allocator.alloc(u8, count); + offset = 0; + var output_index: usize = 0; + while (offset < utf8.len) : (output_index += 1) { + const sequence_length = std.unicode.utf8ByteSequenceLength(utf8[offset]) catch unreachable; + output[output_index] = @intCast(std.unicode.utf8Decode(utf8[offset .. offset + sequence_length]) catch unreachable); + offset += sequence_length; + } + return output; +} + +fn appendLatin1( + allocator: std.mem.Allocator, + output: *std.ArrayListUnmanaged(u8), + latin1: []const u8, + max_bytes: u32, +) !bool { + var required: usize = 0; + for (latin1) |byte| required += if (byte < 0x80) 1 else 2; + if (required > max_bytes -| output.items.len) return false; + try output.ensureUnusedCapacity(allocator, required); + for (latin1) |byte| { + if (byte < 0x80) { + output.appendAssumeCapacity(byte); + } else { + output.appendAssumeCapacity(0xc0 | (byte >> 6)); + output.appendAssumeCapacity(0x80 | (byte & 0x3f)); + } + } + return true; +} + +test "X11 MIME candidates preserve caller order and deterministic target compatibility" { + var connection: Connection = undefined; + connection.phase = .ready; + for (&connection.atom_values, 0..) |*atom, index| atom.* = @intCast(100 + index); + var output: [5]u32 = undefined; + + const text = connection.targetAtoms("text/plain", &output); + try std.testing.expectEqualSlices(u32, &.{ 105, 102, 104, 103, 31 }, text); + const png = connection.targetAtoms("image/png", &output); + try std.testing.expectEqualSlices(u32, &.{106}, png); + try std.testing.expectEqual(@as(usize, 0), connection.targetAtoms("application/octet-stream", &output).len); +} + +test "X11 SelectionNotify routing isolates concurrent requestor windows" { + var connection: Connection = undefined; + var first: ReadState = .{ .phase = .selection, .window = 10, .selection = 1, .target = 2 }; + var second: ReadState = .{ .phase = .selection, .window = 11, .selection = 1, .target = 2 }; + const event: linux.XcbSelectionNotifyEvent = .{ + .response_type = EVENT_SELECTION_NOTIFY, + .pad0 = 0, + .sequence = 0, + .time = 0, + .requestor = 11, + .selection = 1, + .target = 2, + .property = 0, + }; + + try std.testing.expect(!connection.routeReadEvent(&first, @ptrCast(&event))); + try std.testing.expect(connection.routeReadEvent(&second, @ptrCast(&event))); + try std.testing.expectEqual(ReadPhase.selection, first.phase); + try std.testing.expectEqual(ReadPhase.refused, second.phase); +} + +test "X11 SelectionNotify routing accepts the xsel TEXT to STRING alias" { + var symbols: linux.XcbSymbols = undefined; + symbols.xcb_get_property = fakeGetProperty; + var connection = Connection.init(std.testing.allocator, &symbols, 1); + connection.connection = @ptrFromInt(1); + connection.phase = .ready; + connection.output_ready_override = false; + for (&connection.atom_values, 0..) |*atom, index| atom.* = @intCast(100 + index); + var state: ReadState = .{ + .phase = .selection, + .window = 10, + .selection = 1, + .target = connection.atoms().?.text, + }; + var event: linux.XcbSelectionNotifyEvent = .{ + .response_type = EVENT_SELECTION_NOTIFY, + .pad0 = 0, + .sequence = 0, + .time = 0, + .requestor = 10, + .selection = 1, + .target = ATOM_STRING, + .property = connection.atoms().?.property, + }; + + try std.testing.expect(connection.routeReadEvent(&state, @ptrCast(&event))); + try std.testing.expectEqual(ReadPhase.property, state.phase); + try std.testing.expect(state.property_cookie != null); + + state.phase = .selection; + state.target = connection.atoms().?.utf8_string; + try std.testing.expect(!connection.routeReadEvent(&state, @ptrCast(&event))); + try std.testing.expectEqual(ReadPhase.selection, state.phase); +} + +test "X11 property parsing enforces reply framing bounds" { + const Property = extern struct { + reply: linux.XcbGetPropertyReply, + data: [4]u8, + }; + var property: Property = .{ + .reply = .{ + .response_type = 1, + .format = 8, + .sequence = 0, + .length = 1, + .atom_type = 1, + .bytes_after = 0, + .value_length = 4, + .pad0 = .{0} ** 12, + }, + .data = "test".*, + }; + try std.testing.expectEqualStrings("test", propertyBytes(&property.reply).?); + property.reply.value_length = 5; + try std.testing.expect(propertyBytes(&property.reply) == null); +} + +test "X11 provider generations reserve capacity independently per selection" { + var connection: Connection = undefined; + connection.phase = .ready; + connection.atom_values[0] = 100; + var clipboard_current: Provider = .{ .selection = 100, .data = &.{} }; + var clipboard_retired: Provider = .{ .selection = 100, .data = &.{}, .retired = true, .transfer_count = 1 }; + connection.providers = .{ &clipboard_current, &clipboard_retired, null, null }; + connection.clipboard_provider = &clipboard_current; + connection.primary_provider = null; + + try std.testing.expect(!connection.canPublish(false)); + try std.testing.expect(connection.canPublish(true)); +} + +test "X11 STRING conversion is Latin-1 aware and bounded after UTF-8 expansion" { + const latin1 = try encodeLatin1(std.testing.allocator, "A\u{e9}"); + defer std.testing.allocator.free(latin1); + try std.testing.expectEqualSlices(u8, &.{ 'A', 0xe9 }, latin1); + try std.testing.expectError(error.NotRepresentable, encodeLatin1(std.testing.allocator, "\u{20ac}")); + + var output: std.ArrayListUnmanaged(u8) = .{}; + defer output.deinit(std.testing.allocator); + try std.testing.expect(try appendLatin1(std.testing.allocator, &output, latin1, 3)); + try std.testing.expectEqualStrings("A\u{e9}", output.items); + try std.testing.expect(!(try appendLatin1(std.testing.allocator, &output, &.{0xff}, 4))); +} + +test "X11 timestamp ordering handles server timestamp wraparound" { + try std.testing.expect(timestampBefore(10, 20)); + try std.testing.expect(!timestampBefore(20, 10)); + try std.testing.expect(timestampBefore(std.math.maxInt(u32) - 2, 2)); +} + +test "X11 buffers early INCR notifications while the previous property reply is pending" { + var connection: Connection = undefined; + connection.phase = .ready; + connection.atom_values[7] = 107; + var state: ReadState = .{ + .phase = .property, + .window = 10, + .max_bytes = 1024, + .incremental = true, + }; + const event: linux.XcbPropertyNotifyEvent = .{ + .response_type = EVENT_PROPERTY_NOTIFY, + .pad0 = 0, + .sequence = 0, + .window = 10, + .atom = 107, + .time = 1, + .state = 0, + .pad1 = .{0} ** 3, + }; + try std.testing.expect(connection.routeReadEvent(&state, @ptrCast(&event))); + try std.testing.expect(state.notification_pending); + try std.testing.expectEqual(ReadPhase.property, state.phase); +} + +test "X11 buffers INCR deletions while a checked chunk response is pending" { + var connection: Connection = undefined; + var provider: Provider = .{ .selection = 1, .data = &.{}, .transfer_count = 1 }; + var transfers: [1]Transfer = .{.{ + .id = 1, + .provider = &provider, + .data = &.{}, + .requestor = 10, + .property = 20, + .target = 30, + .last_progress_ns = 1, + }}; + var responses = [_]PendingResponse{.{ + .request = std.mem.zeroes(linux.XcbSelectionRequestEvent), + .property_cookie = .{ .sequence = 1 }, + .barrier_cookie = .{ .sequence = 2 }, + .property = 20, + .transfer_id = 1, + }}; + connection.transfers = &transfers; + connection.transfer_count = 1; + connection.responses = &responses; + connection.response_count = 1; + const event: linux.XcbPropertyNotifyEvent = .{ + .response_type = EVENT_PROPERTY_NOTIFY, + .pad0 = 0, + .sequence = 0, + .window = 10, + .atom = 20, + .time = 1, + .state = PROPERTY_DELETE, + .pad1 = .{0} ** 3, + }; + connection.handleTransferProperty(&event); + try std.testing.expect(connection.transfers[0].delete_pending); + try std.testing.expectEqual(@as(u32, 1), connection.transfer_count); +} + +test "X11 read cleanup remains safe after the connection enters failed phase" { + var symbols: linux.XcbSymbols = undefined; + symbols.xcb_discard_reply = fakeDiscardReply; + symbols.xcb_delete_property = fakeDeleteProperty; + symbols.xcb_destroy_window = fakeDestroyWindow; + var fake: FakeXcb = .{}; + var connection: Connection = undefined; + connection.allocator = std.testing.allocator; + connection.symbols = &symbols; + connection.connection = @ptrCast(&fake); + connection.phase = .failed; + connection.atom_values[7] = 107; + var state: ReadState = .{ .window = 42 }; + + connection.cleanupRead(&state); + + try std.testing.expectEqual(@as(u32, 0), state.window); + + connection.connection = null; + state = .{ .phase = .failed, .window = 43 }; + connection.cleanupRead(&state); + try std.testing.expectEqual(ReadState{}, state); +} + +test "X11 timestamp events are isolated by per-mutation windows" { + var connection: Connection = undefined; + connection.phase = .ready; + connection.owner_window = 1; + connection.atom_values[10] = 110; + var successor: WriteState = .{ + .selection = ATOM_PRIMARY, + .waiting_timestamp = true, + .timestamp_window = 22, + }; + const stale: linux.XcbPropertyNotifyEvent = .{ + .response_type = EVENT_PROPERTY_NOTIFY, + .pad0 = 0, + .sequence = 0, + .window = 21, + .atom = 110, + .time = 7, + .state = 0, + .pad1 = .{0} ** 3, + }; + + try std.testing.expect(!connection.routeWriteEvent(&successor, @ptrCast(&stale))); + try std.testing.expect(successor.waiting_timestamp); +} + +test "X11 write and clear commit after the server confirms selection ownership" { + var symbols: linux.XcbSymbols = undefined; + symbols.xcb_destroy_window = fakeDestroyWindow; + symbols.xcb_set_selection_owner = fakeSetSelectionOwner; + symbols.xcb_get_selection_owner = fakeGetSelectionOwner; + symbols.xcb_poll_for_reply = fakePollSelectionOwnerReply; + symbols.xcb_flush = fakeFlush; + var fake: FakeXcb = .{ .replies_ready = true }; + var connection = Connection.init(std.testing.allocator, &symbols, 1); + connection.connection = @ptrCast(&fake); + connection.phase = .ready; + connection.output_ready_override = true; + connection.owner_window = 1; + connection.atom_values[10] = 110; + const old_provider = try std.testing.allocator.create(Provider); + old_provider.* = .{ .selection = ATOM_PRIMARY, .data = &.{}, .owns_data = true, .transfer_count = 1 }; + const new_provider = try std.testing.allocator.create(Provider); + new_provider.* = .{ .selection = ATOM_PRIMARY, .data = &.{} }; + connection.providers = .{ old_provider, new_provider, null, null }; + connection.primary_provider = old_provider; + var state: WriteState = .{ + .provider = new_provider, + .selection = ATOM_PRIMARY, + .waiting_timestamp = true, + .timestamp_window = 22, + }; + const event: linux.XcbPropertyNotifyEvent = .{ + .response_type = EVENT_PROPERTY_NOTIFY, + .pad0 = 0, + .sequence = 0, + .window = 22, + .atom = 110, + .time = 7, + .state = 0, + .pad1 = .{0} ** 3, + }; + + try std.testing.expect(connection.routeWriteEvent(&state, @ptrCast(&event))); + try std.testing.expect(state.mutation_dispatched); + try std.testing.expect(!state.committed); + try std.testing.expect(state.provider == null); + try std.testing.expect(connection.primary_provider == new_provider); + try std.testing.expect(old_provider.retired); + try std.testing.expectEqual(SelectionResult.committed, connection.driveWrite(&state)); + try std.testing.expectEqual(@as(u32, 1), fake.get_owner_count); + + var clear_state: WriteState = .{ + .clear = true, + .selection = ATOM_PRIMARY, + .waiting_timestamp = true, + .timestamp_window = 23, + }; + var clear_event = event; + clear_event.window = 23; + clear_event.time = 8; + try std.testing.expect(connection.routeWriteEvent(&clear_state, @ptrCast(&clear_event))); + try std.testing.expect(!clear_state.committed); + try std.testing.expect(connection.primary_provider == null); + try std.testing.expectEqual(SelectionResult.committed, connection.driveWrite(&clear_state)); + try std.testing.expectEqual(@as(u32, 2), fake.get_owner_count); + + const rejected_provider = try std.testing.allocator.create(Provider); + rejected_provider.* = .{ .selection = ATOM_PRIMARY, .data = &.{} }; + connection.providers[1] = rejected_provider; + var rejected_state: WriteState = .{ + .provider = rejected_provider, + .selection = ATOM_PRIMARY, + .waiting_timestamp = true, + .timestamp_window = 24, + }; + var rejected_event = event; + rejected_event.window = 24; + rejected_event.time = 9; + fake.reject_selection_owner = true; + try std.testing.expect(connection.routeWriteEvent(&rejected_state, @ptrCast(&rejected_event))); + try std.testing.expectEqual(SelectionResult.failed, connection.driveWrite(&rejected_state)); + try std.testing.expect(connection.primary_provider == null); + try std.testing.expect(connection.providers[1] == null); + + fake.flush_fails = true; + connection.phase = .ready; + connection.output_pending = true; + var flush_failure: WriteState = .{ .mutation_dispatched = true }; + try std.testing.expectEqual(SelectionResult.failed, connection.driveWrite(&flush_failure)); + try std.testing.expectEqual(Failure.flush, connection.failure); + try std.testing.expectEqual(Phase.failed, connection.phase); + try std.testing.expect(!connection.output_pending); + + connection.releaseProviders(); +} + +test "X11 SelectionClear ignores older timestamps and accepts equal timestamps" { + var connection: Connection = undefined; + connection.owner_window = 1; + var provider: Provider = .{ + .selection = ATOM_PRIMARY, + .data = &.{}, + .timestamp = 7, + .owns_data = true, + .transfer_count = 1, + }; + connection.providers = .{ &provider, null, null, null }; + connection.primary_provider = &provider; + var event: linux.XcbSelectionClearEvent = .{ + .response_type = EVENT_SELECTION_CLEAR, + .pad0 = 0, + .sequence = 0, + .time = 6, + .owner = 1, + .selection = ATOM_PRIMARY, + }; + + connection.handleSelectionClear(&event); + try std.testing.expect(connection.primary_provider == &provider); + event.time = 7; + connection.handleSelectionClear(&event); + try std.testing.expect(connection.primary_provider == null); + try std.testing.expect(provider.retired); +} + +test "X11 INCR expiry runs while provider responses remain pending" { + try clipboard_clock.init(); + var symbols: linux.XcbSymbols = undefined; + symbols.xcb_poll_for_reply = fakePendingReply; + symbols.xcb_change_window_attributes = fakeChangeWindowAttributes; + symbols.xcb_flush = fakeFlush; + var fake: FakeXcb = .{}; + var connection = Connection.init(std.testing.allocator, &symbols, 1); + connection.connection = @ptrCast(&fake); + connection.phase = .ready; + connection.output_ready_override = true; + var provider: Provider = .{ .selection = ATOM_PRIMARY, .data = &.{}, .transfer_count = 1 }; + var transfers = [_]Transfer{.{ + .id = 1, + .provider = &provider, + .data = &.{}, + .requestor = 10, + .property = 20, + .target = 30, + .last_progress_ns = clipboard_clock.nowNs() - TRANSFER_IDLE_TIMEOUT_NS, + }}; + var responses = [_]PendingResponse{.{ + .request = std.mem.zeroes(linux.XcbSelectionRequestEvent), + .property_cookie = .{ .sequence = 1 }, + .barrier_cookie = .{ .sequence = 2 }, + .property = 20, + }}; + connection.transfers = &transfers; + connection.transfer_count = 1; + connection.responses = &responses; + connection.response_count = 1; + + _ = connection.driveProviderUnit(); + + try std.testing.expectEqual(@as(u32, 0), connection.transfer_count); + try std.testing.expectEqual(@as(u32, 0), provider.transfer_count); + try std.testing.expectEqual(@as(u32, 1), connection.response_count); + try std.testing.expectEqual(@as(u32, 1), fake.event_mask_clear_count); +} + +test "X11 expired initial INCR response refuses its delayed checked reply" { + if (comptime builtin.os.tag != .linux) return error.SkipZigTest; + try clipboard_clock.init(); + var symbols: linux.XcbSymbols = undefined; + symbols.xcb_poll_for_reply = fakePollForReply; + symbols.xcb_request_check = fakeRequestCheck; + symbols.xcb_send_event = fakeSendEvent; + symbols.xcb_change_window_attributes = fakeChangeWindowAttributes; + symbols.xcb_flush = fakeFlush; + var fake: FakeXcb = .{ .replies_ready = true }; + var connection = Connection.init(std.testing.allocator, &symbols, 1); + connection.connection = @ptrCast(&fake); + connection.phase = .ready; + connection.output_ready_override = true; + var provider: Provider = .{ .selection = ATOM_PRIMARY, .data = &.{}, .transfer_count = 1 }; + var transfers = [_]Transfer{.{ + .id = 1, + .provider = &provider, + .data = &.{}, + .requestor = 10, + .property = 20, + .target = 30, + .last_progress_ns = clipboard_clock.nowNs() - TRANSFER_IDLE_TIMEOUT_NS, + }}; + var responses = [_]PendingResponse{.{ + .request = .{ + .response_type = EVENT_SELECTION_REQUEST, + .pad0 = 0, + .sequence = 0, + .time = 1, + .owner = 2, + .requestor = 10, + .selection = ATOM_PRIMARY, + .target = 30, + .property = 20, + }, + .property_cookie = .{ .sequence = 1 }, + .barrier_cookie = .{ .sequence = 2 }, + .property = 20, + .transfer_id = 1, + }}; + connection.transfers = &transfers; + connection.transfer_count = 1; + connection.responses = &responses; + connection.response_count = 1; + + _ = connection.driveProviderUnit(); + _ = connection.driveProviderUnit(); + + try std.testing.expectEqual(@as(u32, 0), connection.transfer_count); + try std.testing.expectEqual(@as(u32, 0), connection.response_count); + try std.testing.expectEqual(@as(u32, 0), fake.last_notify_property); + try std.testing.expectEqual(@as(u32, 1), fake.send_event_count); +} + +test "X11 delayed expired INCR response preserves replacement transfer" { + if (comptime builtin.os.tag != .linux) return error.SkipZigTest; + try clipboard_clock.init(); + var symbols: linux.XcbSymbols = undefined; + symbols.xcb_poll_for_reply = fakePollForReply; + symbols.xcb_request_check = fakeRequestCheck; + symbols.xcb_send_event = fakeSendEvent; + symbols.xcb_change_window_attributes = fakeChangeWindowAttributes; + symbols.xcb_flush = fakeFlush; + var fake: FakeXcb = .{ .replies_ready = true }; + var connection = Connection.init(std.testing.allocator, &symbols, 2); + connection.connection = @ptrCast(&fake); + connection.phase = .ready; + connection.output_ready_override = true; + var provider_a: Provider = .{ .selection = ATOM_PRIMARY, .data = &.{}, .transfer_count = 1 }; + var provider_b: Provider = .{ .selection = ATOM_PRIMARY, .data = &.{}, .transfer_count = 0 }; + var transfers: [2]Transfer = undefined; + transfers[0] = .{ + .id = 1, + .provider = &provider_a, + .data = &.{}, + .requestor = 10, + .property = 20, + .target = 30, + .last_progress_ns = clipboard_clock.nowNs() - TRANSFER_IDLE_TIMEOUT_NS, + }; + var responses = [_]PendingResponse{.{ + .request = std.mem.zeroes(linux.XcbSelectionRequestEvent), + .property_cookie = .{ .sequence = 1 }, + .barrier_cookie = .{ .sequence = 2 }, + .property = 20, + .transfer_id = 1, + }}; + connection.transfers = &transfers; + connection.transfer_count = 1; + connection.responses = &responses; + connection.response_count = 1; + + fake.replies_ready = false; + _ = connection.driveProviderUnit(); + try std.testing.expectEqual(@as(u32, 0), connection.transfer_count); + + transfers[0] = .{ + .id = 2, + .provider = &provider_b, + .data = &.{}, + .requestor = 10, + .property = 20, + .target = 30, + .last_progress_ns = clipboard_clock.nowNs(), + }; + connection.transfer_count = 1; + provider_b.transfer_count = 1; + fake.replies_ready = true; + _ = connection.driveProviderUnit(); + + try std.testing.expectEqual(@as(u32, 1), connection.transfer_count); + try std.testing.expect(connection.transfers[0].provider == &provider_b); + try std.testing.expectEqual(@as(u32, 1), provider_b.transfer_count); +} + +test "X11 cancelled timestamp windows remain tombstoned until their event is consumed" { + var symbols: linux.XcbSymbols = undefined; + symbols.xcb_destroy_window = fakeDestroyWindow; + symbols.xcb_flush = fakeFlush; + var fake: FakeXcb = .{}; + var connection = Connection.init(std.testing.allocator, &symbols, 1); + connection.connection = @ptrCast(&fake); + connection.phase = .ready; + connection.output_ready_override = true; + connection.atom_values[10] = 110; + var clipboard: WriteState = .{ .selection = 100, .waiting_timestamp = true, .timestamp_window = 21 }; + var primary: WriteState = .{ .selection = ATOM_PRIMARY, .waiting_timestamp = true, .timestamp_window = 22 }; + connection.cleanupWrite(&clipboard); + connection.cleanupWrite(&primary); + const event: linux.XcbPropertyNotifyEvent = .{ + .response_type = EVENT_PROPERTY_NOTIFY, + .pad0 = 0, + .sequence = 0, + .window = 21, + .atom = 110, + .time = 7, + .state = 0, + .pad1 = .{0} ** 3, + }; + + try std.testing.expect(connection.consumeRetiredTimestampEvent(@ptrCast(&event))); + try std.testing.expectEqual(@as(u32, 0), connection.retired_timestamps[0].window); + try std.testing.expectEqual(@as(u32, 22), connection.retired_timestamps[1].window); +} + +test "X11 timestamp request errors retire active and tombstoned windows" { + var symbols: linux.XcbSymbols = undefined; + symbols.xcb_destroy_window = fakeDestroyWindow; + symbols.xcb_flush = fakeFlush; + var fake: FakeXcb = .{}; + var connection: Connection = undefined; + connection.allocator = std.testing.allocator; + connection.symbols = &symbols; + connection.connection = @ptrCast(&fake); + connection.phase = .ready; + connection.output_ready_override = true; + + const provider = try std.testing.allocator.create(Provider); + provider.* = .{ .selection = 1, .data = &.{} }; + connection.providers = .{ provider, null, null, null }; + var active: WriteState = .{ .provider = provider, .waiting_timestamp = true, .timestamp_window = 21 }; + const active_error: linux.XcbGenericError = .{ + .response_type = 0, + .error_code = 3, + .sequence = 0, + .resource_id = 21, + .minor_code = 0, + .major_code = 18, + .pad0 = 0, + .pad = .{0} ** 5, + .full_sequence = 0, + }; + try std.testing.expect(connection.routeWriteEvent(&active, @ptrCast(&active_error))); + try std.testing.expect(active.failed); + try std.testing.expect(!active.waiting_timestamp); + try std.testing.expect(active.provider == null); + try std.testing.expect(connection.providers[0] == null); + + connection.retired_timestamps[0].window = 22; + var retired_error = active_error; + retired_error.resource_id = 22; + try std.testing.expect(connection.consumeRetiredTimestampEvent(@ptrCast(&retired_error))); + try std.testing.expectEqual(@as(u32, 0), connection.retired_timestamps[0].window); +} + +test "X11 DISPLAY parser accepts bounded local transports and rejects remote hosts" { + const accepted = [_]struct { display: []const u8, kind: DisplayKind, number: u16, screen: c_int }{ + .{ .display = ":0", .kind = .unix, .number = 0, .screen = 0 }, + .{ .display = ":12.3", .kind = .unix, .number = 12, .screen = 3 }, + .{ .display = "unix:1", .kind = .unix, .number = 1, .screen = 0 }, + .{ .display = "unix/:2", .kind = .unix, .number = 2, .screen = 0 }, + .{ .display = "host/unix:3", .kind = .unix, .number = 3, .screen = 0 }, + .{ .display = "/tmp/.X11-unix/X4", .kind = .unix, .number = 4, .screen = 0 }, + .{ .display = "localhost:10.1", .kind = .tcp6, .number = 10, .screen = 1 }, + .{ .display = "127.0.0.1:11", .kind = .tcp4, .number = 11, .screen = 0 }, + .{ .display = "[::1]:12", .kind = .tcp6, .number = 12, .screen = 0 }, + }; + for (accepted) |expected| { + const endpoint = try parseDisplay(expected.display); + try std.testing.expectEqual(expected.kind, endpoint.kind); + try std.testing.expectEqual(expected.number, endpoint.display); + try std.testing.expectEqual(expected.screen, endpoint.screen); + } + try std.testing.expect((try parseDisplay(":0")).unix_abstract_first); + try std.testing.expect((try parseDisplay("localhost:0")).tcp4_fallback); + try std.testing.expect(!(try parseDisplay("127.0.0.1:0")).tcp4_fallback); + try std.testing.expect(!(try parseDisplay("[::1]:0")).tcp4_fallback); + const rejected = [_][]const u8{ + "", "example.com:0", "192.0.2.1:0", "[::2]:0", "localhost", ":", ":1.-1", "localhost:59536", + }; + for (rejected) |display| try std.testing.expectError(error.UnsupportedDisplay, parseDisplay(display)); +} + +test "X11 Xauthority parser selects exact loopback MIT cookie and rejects truncation" { + if (comptime builtin.os.tag != .linux) return error.SkipZigTest; + var bytes: std.ArrayListUnmanaged(u8) = .{}; + defer bytes.deinit(std.testing.allocator); + try appendTestXauthorityRecord(&bytes, XAUTH_FAMILY_WILD, &.{}, "10", XAUTH_NAME, "wild"); + try appendTestXauthorityRecord(&bytes, XAUTH_FAMILY_INTERNET, &.{ 127, 0, 0, 1 }, "10", XAUTH_NAME, "best"); + const endpoint = try parseDisplay("127.0.0.1:10"); + const match = try parseXauthority(bytes.items, endpoint); + try std.testing.expectEqualStrings(XAUTH_NAME, match.name); + try std.testing.expectEqualStrings("best", match.data); + try std.testing.expectError(error.InvalidXauthority, parseXauthority(bytes.items[0 .. bytes.items.len - 1], endpoint)); +} + +test "X11 Xauthority loading accepts only bounded regular files and observes cancellation" { + if (comptime builtin.os.tag != .linux) return error.SkipZigTest; + try clipboard_clock.init(); + var symbols: linux.XcbSymbols = undefined; + var connection = Connection.init(std.testing.allocator, &symbols, 1); + const endpoint = try parseDisplay("127.0.0.1:10"); + var bytes: std.ArrayListUnmanaged(u8) = .{}; + defer bytes.deinit(std.testing.allocator); + try appendTestXauthorityRecord(&bytes, XAUTH_FAMILY_INTERNET, &.{ 127, 0, 0, 1 }, "10", XAUTH_NAME, "cookie"); + + var tmp = std.testing.tmpDir(.{}); + defer tmp.cleanup(); + const file = try tmp.dir.createFile("authority", .{}); + try file.writeAll(bytes.items); + file.close(); + const dir_path = try tmp.dir.realpathAlloc(std.testing.allocator, "."); + defer std.testing.allocator.free(dir_path); + const authority_path = try std.fs.path.join(std.testing.allocator, &.{ dir_path, "authority" }); + defer std.testing.allocator.free(authority_path); + + var match = try loadXauthorityPath(&connection, endpoint, authority_path); + defer match.deinit(std.testing.allocator); + try std.testing.expectEqualStrings("cookie", match.data); + + const oversized = try tmp.dir.createFile("oversized", .{}); + try oversized.setEndPos(XAUTHORITY_SIZE_MAX + 1); + oversized.close(); + const oversized_path = try std.fs.path.join(std.testing.allocator, &.{ dir_path, "oversized" }); + defer std.testing.allocator.free(oversized_path); + var oversized_match = try loadXauthorityPath(&connection, endpoint, oversized_path); + defer oversized_match.deinit(std.testing.allocator); + try std.testing.expectEqual(@as(usize, 0), oversized_match.storage.len); + + connection.requestShutdown(); + try std.testing.expectError(error.Cancelled, loadXauthorityPath(&connection, endpoint, authority_path)); +} + +test "X11 Xauthority FIFO is rejected without waiting for a writer" { + if (comptime builtin.os.tag != .linux) return error.SkipZigTest; + try clipboard_clock.init(); + var symbols: linux.XcbSymbols = undefined; + var connection = Connection.init(std.testing.allocator, &symbols, 1); + const endpoint = try parseDisplay(":0"); + var tmp = std.testing.tmpDir(.{}); + defer tmp.cleanup(); + const dir_path = try tmp.dir.realpathAlloc(std.testing.allocator, "."); + defer std.testing.allocator.free(dir_path); + const fifo_path = try std.fs.path.join(std.testing.allocator, &.{ dir_path, "authority.fifo" }); + defer std.testing.allocator.free(fifo_path); + const fifo_path_z = try std.testing.allocator.dupeZ(u8, fifo_path); + defer std.testing.allocator.free(fifo_path_z); + if (mkfifo(fifo_path_z, 0o600) != 0) return error.MkfifoFailed; + + const started_ns = clipboard_clock.nowNs(); + var match = try loadXauthorityPath(&connection, endpoint, fifo_path); + defer match.deinit(std.testing.allocator); + try std.testing.expectEqual(@as(usize, 0), match.storage.len); + try std.testing.expect(clipboard_clock.nowNs() - started_ns < 500 * std.time.ns_per_ms); +} + +test "X11 bare DISPLAY connects to an abstract-only listener" { + if (comptime builtin.os.tag != .linux) return error.SkipZigTest; + try clipboard_clock.init(); + var symbols: linux.XcbSymbols = undefined; + var connection = Connection.init(std.testing.allocator, &symbols, 1); + const listener = try testDisplayListener(.unix, 0); + defer std.posix.close(listener.fd); + var endpoint = listener.endpoint; + + const fd = try connection.connectSocket(&endpoint); + defer std.posix.close(fd); + try std.testing.expectEqual(DisplayKind.unix, endpoint.kind); +} + +test "X11 bare DISPLAY falls back to a filesystem listener" { + if (comptime builtin.os.tag != .linux) return error.SkipZigTest; + try clipboard_clock.init(); + var symbols: linux.XcbSymbols = undefined; + var connection = Connection.init(std.testing.allocator, &symbols, 1); + const listener = try testDisplayListener(.unix, 1); + defer std.posix.close(listener.fd); + var endpoint = listener.endpoint; + defer std.posix.unlink(endpoint.unix_path[0..endpoint.unix_path_length]) catch {}; + + const fd = try connection.connectSocket(&endpoint); + defer std.posix.close(fd); + try std.testing.expectEqual(DisplayKind.unix, endpoint.kind); +} + +test "X11 localhost DISPLAY connects to an IPv6-only listener" { + if (comptime builtin.os.tag != .linux) return error.SkipZigTest; + try clipboard_clock.init(); + var symbols: linux.XcbSymbols = undefined; + var connection = Connection.init(std.testing.allocator, &symbols, 1); + const listener = testDisplayListener(.tcp6, 0) catch |err| switch (err) { + error.AddressFamilyNotSupported => return error.SkipZigTest, + else => return err, + }; + defer std.posix.close(listener.fd); + var endpoint = listener.endpoint; + + const fd = try connection.connectSocket(&endpoint); + defer std.posix.close(fd); + try std.testing.expectEqual(DisplayKind.tcp6, endpoint.kind); +} + +test "X11 localhost DISPLAY falls back to an IPv4-only listener" { + if (comptime builtin.os.tag != .linux) return error.SkipZigTest; + try clipboard_clock.init(); + var symbols: linux.XcbSymbols = undefined; + var connection = Connection.init(std.testing.allocator, &symbols, 1); + const listener = try testDisplayListener(.tcp4, 1); + defer std.posix.close(listener.fd); + var endpoint = listener.endpoint; + + const fd = try connection.connectSocket(&endpoint); + defer std.posix.close(fd); + try std.testing.expectEqual(DisplayKind.tcp4, endpoint.kind); +} + +test "X11 shutdown before fd publication exits without joining early" { + if (comptime builtin.os.tag != .linux) return error.SkipZigTest; + try clipboard_clock.init(); + var symbols: linux.XcbSymbols = undefined; + var connection = Connection.init(std.testing.allocator, &symbols, 1); + connection.requestShutdown(); + connection.requestShutdown(); + try std.testing.expectEqual(Progress.pending, connection.drive()); + try expectShutdownReady(&connection); + connection.deinit(); +} + +test "X11 connection establishment does not block a drive unit" { + if (comptime builtin.os.tag != .linux) return error.SkipZigTest; + try clipboard_clock.init(); + var symbols: linux.XcbSymbols = undefined; + symbols.xcb_connect_to_fd = fakeSlowConnectToFd; + symbols.xcb_connection_has_error = fakeConnectionHasError; + symbols.xcb_disconnect = fakeDisconnect; + var connection = Connection.init(std.testing.allocator, &symbols, 1); + const sockets = try testSocketPair(); + defer std.posix.close(sockets[1]); + connection.test_connected_fd = sockets[0]; + + const started_ns = clipboard_clock.nowNs(); + try std.testing.expectEqual(Progress.pending, connection.drive()); + try std.testing.expect(clipboard_clock.nowNs() - started_ns < 50 * std.time.ns_per_ms); + + std.Thread.sleep(250 * std.time.ns_per_ms); + try std.testing.expectEqual(Progress.pending, connection.drive()); + connection.deinit(); +} + +test "X11 shutdown cancels a silent setup connection" { + if (comptime builtin.os.tag != .linux) return error.SkipZigTest; + try clipboard_clock.init(); + var symbols: linux.XcbSymbols = undefined; + symbols.xcb_connect_to_fd = fakeSilentConnectToFd; + symbols.xcb_connection_has_error = fakeConnectionHasError; + symbols.xcb_disconnect = fakeDisconnect; + var connection = Connection.init(std.testing.allocator, &symbols, 1); + const sockets = try testSocketPair(); + defer std.posix.close(sockets[1]); + connection.test_connected_fd = sockets[0]; + fake_setup_started.store(false, .release); + try std.testing.expectEqual(Progress.pending, connection.drive()); + try expectSetupStarted(&connection, &fake_setup_started); + connection.requestShutdown(); + connection.requestShutdown(); + try expectShutdownReady(&connection); + connection.deinit(); + var byte: [1]u8 = undefined; + try std.testing.expectEqual(@as(usize, 0), try std.posix.read(sockets[1], &byte)); +} + +test "X11 shutdown immediately after setup completion still joins and closes" { + if (comptime builtin.os.tag != .linux) return error.SkipZigTest; + try clipboard_clock.init(); + var symbols: linux.XcbSymbols = undefined; + symbols.xcb_connect_to_fd = fakeImmediateConnectToFd; + symbols.xcb_disconnect = fakeDisconnect; + var connection = Connection.init(std.testing.allocator, &symbols, 1); + const sockets = try testSocketPair(); + defer std.posix.close(sockets[1]); + connection.test_connected_fd = sockets[0]; + fake_slow_connection = .{}; + fake_setup_completed.store(false, .release); + + try std.testing.expectEqual(Progress.pending, connection.drive()); + try expectSetupStarted(&connection, &fake_setup_completed); + connection.requestShutdown(); + try expectShutdownReady(&connection); + connection.deinit(); + try std.testing.expect(fake_slow_connection.disconnected); +} + +const FakeXcb = struct { + next_sequence: u32 = 1, + replies_ready: bool = false, + error_sequence: u32 = 0, + flush_count: u32 = 0, + discard_count: u32 = 0, + get_owner_count: u32 = 0, + selection_owner: u32 = 0, + reject_selection_owner: bool = false, + event_mask_clear_count: u32 = 0, + flush_fails: bool = false, + disconnected: bool = false, + send_event_count: u32 = 0, + last_notify_property: u32 = std.math.maxInt(u32), +}; + +fn expectSetupStarted(connection: *Connection, flag: *std.atomic.Value(bool)) !void { + const deadline_ns = clipboard_clock.nowNs() + 2 * std.time.ns_per_s; + while (clipboard_clock.nowNs() < deadline_ns) { + if (flag.load(.acquire)) return; + std.Thread.sleep(std.time.ns_per_ms); + } + connection.requestShutdown(); + try expectShutdownReady(connection); + connection.deinit(); + return error.TestConnectSetupTimeout; +} + +fn expectShutdownReady(connection: *Connection) !void { + const deadline_ns = clipboard_clock.nowNs() + 2 * std.time.ns_per_s; + while (clipboard_clock.nowNs() < deadline_ns) { + if (connection.shutdownReady()) return; + std.Thread.sleep(std.time.ns_per_ms); + } + return error.TestConnectShutdownTimeout; +} + +test "X11 atom initialization polls one reply per drive without blocking" { + if (comptime builtin.os.tag != .linux) return error.SkipZigTest; + var symbols: linux.XcbSymbols = undefined; + symbols.xcb_connection_has_error = fakeConnectionHasError; + symbols.xcb_disconnect = fakeDisconnect; + symbols.xcb_poll_for_reply = fakePollForReply; + symbols.xcb_discard_reply = fakeDiscardReply; + symbols.xcb_flush = fakeFlush; + symbols.xcb_intern_atom = fakeInternAtom; + symbols.xcb_get_setup = fakeGetSetup; + symbols.xcb_setup_roots_iterator = fakeRootsIterator; + symbols.xcb_screen_next = fakeScreenNext; + symbols.xcb_generate_id = fakeGenerateId; + symbols.xcb_create_window = fakeCreateWindow; + symbols.xcb_destroy_window = fakeDestroyWindow; + + var fake: FakeXcb = .{}; + var connection = Connection.init(std.testing.allocator, &symbols, 2); + connection.connection = @ptrCast(&fake); + connection.phase = .atoms; + connection.output_ready_override = false; + defer connection.deinit(); + + for (0..ATOM_NAMES.len) |_| try std.testing.expectEqual(Progress.pending, connection.drive()); + try std.testing.expectEqual(Progress.pending, connection.drive()); + try std.testing.expectEqual(@as(u32, 0), fake.flush_count); + connection.output_ready_override = true; + try std.testing.expectEqual(Progress.pending, connection.drive()); + try std.testing.expectEqual(@as(u32, 1), fake.flush_count); + try std.testing.expectEqual(Progress.pending, connection.drive()); + try std.testing.expectEqual(@as(u8, 0), connection.reply_index); + + fake.replies_ready = true; + for (0..ATOM_NAMES.len - 1) |_| try std.testing.expectEqual(Progress.pending, connection.drive()); + try std.testing.expectEqual(Progress.pending, connection.drive()); + try std.testing.expectEqual(Progress.pending, connection.drive()); + try std.testing.expectEqual(Progress.ready, connection.drive()); + const atoms = connection.atoms().?; + try std.testing.expectEqual(@as(u32, 101), atoms.clipboard); + try std.testing.expectEqual(@as(u32, 110), atoms.timestamp); + try std.testing.expectEqual(@as(u32, 0), fake.discard_count); +} + +test "X11 atom initialization fails on a polled protocol error and discards remaining replies" { + if (comptime builtin.os.tag != .linux) return error.SkipZigTest; + var symbols: linux.XcbSymbols = undefined; + symbols.xcb_connection_has_error = fakeConnectionHasError; + symbols.xcb_disconnect = fakeDisconnect; + symbols.xcb_poll_for_reply = fakePollForReply; + symbols.xcb_discard_reply = fakeDiscardReply; + symbols.xcb_flush = fakeFlush; + symbols.xcb_intern_atom = fakeInternAtom; + + var fake: FakeXcb = .{ .replies_ready = true, .error_sequence = 2 }; + var connection = Connection.init(std.testing.allocator, &symbols, 2); + connection.connection = @ptrCast(&fake); + connection.phase = .atoms; + connection.output_ready_override = true; + + for (0..ATOM_NAMES.len) |_| try std.testing.expectEqual(Progress.pending, connection.drive()); + try std.testing.expectEqual(Progress.pending, connection.drive()); + try std.testing.expectEqual(Progress.pending, connection.drive()); + try std.testing.expectEqual(Progress.failed, connection.drive()); + try std.testing.expectEqual(Failure.atom, connection.failure); + connection.deinit(); + try std.testing.expectEqual(@as(u32, ATOM_NAMES.len - 2), fake.discard_count); + try std.testing.expect(fake.disconnected); +} + +fn fakeConnectionHasError(_: *linux.XcbConnection) callconv(.c) c_int { + return 0; +} + +fn fakeSlowConnectToFd(fd: c_int, _: ?*linux.XcbAuthInfo) callconv(.c) ?*linux.XcbConnection { + std.Thread.sleep(200 * std.time.ns_per_ms); + std.posix.close(fd); + return @ptrCast(&fake_slow_connection); +} + +fn fakeSilentConnectToFd(fd: c_int, _: ?*linux.XcbAuthInfo) callconv(.c) ?*linux.XcbConnection { + fake_setup_started.store(true, .release); + var byte: [1]u8 = undefined; + _ = std.posix.read(fd, &byte) catch {}; + std.posix.close(fd); + return null; +} + +var fake_setup_started: std.atomic.Value(bool) = .init(false); +var fake_setup_completed: std.atomic.Value(bool) = .init(false); + +fn fakeImmediateConnectToFd(fd: c_int, _: ?*linux.XcbAuthInfo) callconv(.c) ?*linux.XcbConnection { + std.posix.close(fd); + fake_setup_completed.store(true, .release); + return @ptrCast(&fake_slow_connection); +} + +fn testSocketPair() ![2]std.posix.fd_t { + var sockets: [2]std.posix.fd_t = undefined; + if (std.c.socketpair(std.posix.AF.UNIX, std.posix.SOCK.STREAM | std.posix.SOCK.CLOEXEC, 0, &sockets) != 0) { + return error.SocketPairFailed; + } + return sockets; +} + +const TestDisplayListener = struct { + fd: std.posix.fd_t, + endpoint: DisplayEndpoint, +}; + +fn testDisplayListener(kind: DisplayKind, candidate_index: u8) !TestDisplayListener { + std.debug.assert(kind == .unix or kind == .tcp4 or kind == .tcp6); + std.debug.assert(candidate_index < 2); + const seed: u16 = @truncate(@as(u128, @bitCast(clipboard_clock.nowNs()))); + var attempt: u16 = 0; + while (attempt < 128) : (attempt += 1) { + const display: u16 = if (kind == .unix) + seed +% attempt + else + (seed +% attempt) % 59536; + const endpoint = if (kind == .unix) + try parseDisplayNumberEndpoint(display) + else + DisplayEndpoint{ .kind = .tcp6, .display = display, .screen = 0, .tcp4_fallback = true }; + const candidate = try displayAddress(endpoint, candidate_index); + std.debug.assert(candidate.kind == kind); + const fd = try std.posix.socket( + candidate.address.any.family, + std.posix.SOCK.STREAM | std.posix.SOCK.CLOEXEC, + 0, + ); + std.posix.bind(fd, &candidate.address.any, candidate.length) catch |err| { + std.posix.close(fd); + if (err == error.AddressInUse or err == error.AccessDenied) continue; + return err; + }; + errdefer std.posix.close(fd); + try std.posix.listen(fd, 1); + return .{ .fd = fd, .endpoint = endpoint }; + } + return error.NoTestDisplayAddress; +} + +fn parseDisplayNumberEndpoint(display: u16) !DisplayEndpoint { + var path_buffer: [108]u8 = undefined; + const path = try std.fmt.bufPrint(&path_buffer, "/tmp/.X11-unix/X{d}", .{display}); + var endpoint = try unixEndpoint(display, 0, path); + endpoint.unix_abstract_first = true; + return endpoint; +} + +extern fn mkfifo(path: [*:0]const u8, mode: std.posix.mode_t) c_int; + +fn appendTestXauthorityRecord( + bytes: *std.ArrayListUnmanaged(u8), + family: u16, + address: []const u8, + number: []const u8, + name: []const u8, + data: []const u8, +) !void { + var family_bytes: [2]u8 = undefined; + std.mem.writeInt(u16, &family_bytes, family, .big); + try bytes.appendSlice(std.testing.allocator, &family_bytes); + try appendTestXauthorityField(bytes, address); + try appendTestXauthorityField(bytes, number); + try appendTestXauthorityField(bytes, name); + try appendTestXauthorityField(bytes, data); +} + +fn appendTestXauthorityField(bytes: *std.ArrayListUnmanaged(u8), field: []const u8) !void { + var length_bytes: [2]u8 = undefined; + std.mem.writeInt(u16, &length_bytes, @intCast(field.len), .big); + try bytes.appendSlice(std.testing.allocator, &length_bytes); + try bytes.appendSlice(std.testing.allocator, field); +} + +var fake_slow_connection: FakeXcb = .{}; + +fn fakeDisconnect(connection: *linux.XcbConnection) callconv(.c) void { + const fake: *FakeXcb = @ptrCast(@alignCast(connection)); + fake.disconnected = true; +} + +fn fakeInternAtom( + connection: *linux.XcbConnection, + _: u8, + _: u16, + _: [*]const u8, +) callconv(.c) linux.XcbCookie { + const fake: *FakeXcb = @ptrCast(@alignCast(connection)); + const sequence = fake.next_sequence; + fake.next_sequence += 1; + return .{ .sequence = sequence }; +} + +fn fakeFlush(connection: *linux.XcbConnection) callconv(.c) c_int { + const fake: *FakeXcb = @ptrCast(@alignCast(connection)); + fake.flush_count += 1; + return if (fake.flush_fails) 0 else 1; +} + +fn fakeSetSelectionOwner( + connection: *linux.XcbConnection, + owner: u32, + _: u32, + _: u32, +) callconv(.c) linux.XcbCookie { + const fake: *FakeXcb = @ptrCast(@alignCast(connection)); + if (!fake.reject_selection_owner) fake.selection_owner = owner; + const sequence = fake.next_sequence; + fake.next_sequence += 1; + return .{ .sequence = sequence }; +} + +fn fakeGetSelectionOwner(connection: *linux.XcbConnection, _: u32) callconv(.c) linux.XcbCookie { + const fake: *FakeXcb = @ptrCast(@alignCast(connection)); + fake.get_owner_count += 1; + const sequence = fake.next_sequence; + fake.next_sequence += 1; + return .{ .sequence = sequence }; +} + +fn fakeGetProperty( + _: *linux.XcbConnection, + _: u8, + _: u32, + _: u32, + _: u32, + _: u32, + _: u32, +) callconv(.c) linux.XcbCookie { + return .{ .sequence = 1 }; +} + +fn fakePollSelectionOwnerReply( + connection: *linux.XcbConnection, + _: u32, + reply_pointer: *?*anyopaque, + _: *?*linux.XcbGenericError, +) callconv(.c) c_int { + const fake: *FakeXcb = @ptrCast(@alignCast(connection)); + if (!fake.replies_ready) return 0; + const reply_memory = std.c.malloc(@sizeOf(linux.XcbGetSelectionOwnerReply)) orelse unreachable; + const reply: *linux.XcbGetSelectionOwnerReply = @ptrCast(@alignCast(reply_memory)); + reply.* = .{ + .response_type = 1, + .pad0 = 0, + .sequence = 0, + .length = 0, + .owner = fake.selection_owner, + .pad1 = .{0} ** 20, + }; + reply_pointer.* = reply; + return 1; +} + +fn fakeChangeWindowAttributes( + connection: *linux.XcbConnection, + _: u32, + _: u32, + values: ?*const anyopaque, +) callconv(.c) linux.XcbCookie { + const fake: *FakeXcb = @ptrCast(@alignCast(connection)); + const event_mask: *const u32 = @ptrCast(@alignCast(values.?)); + if (event_mask.* == 0) fake.event_mask_clear_count += 1; + return .{ .sequence = fake.next_sequence }; +} + +fn fakePendingReply( + _: *linux.XcbConnection, + _: u32, + _: *?*anyopaque, + _: *?*linux.XcbGenericError, +) callconv(.c) c_int { + return 0; +} + +fn fakeRequestCheck(_: *linux.XcbConnection, _: linux.XcbCookie) callconv(.c) ?*linux.XcbGenericError { + return null; +} + +fn fakeSendEvent( + connection: *linux.XcbConnection, + _: u8, + _: u32, + _: u32, + event: [*]const u8, +) callconv(.c) linux.XcbCookie { + const fake: *FakeXcb = @ptrCast(@alignCast(connection)); + const notify = std.mem.bytesToValue( + linux.XcbSelectionNotifyEvent, + event[0..@sizeOf(linux.XcbSelectionNotifyEvent)], + ); + fake.send_event_count += 1; + fake.last_notify_property = notify.property; + return .{ .sequence = fake.next_sequence }; +} + +fn fakePollForReply( + connection: *linux.XcbConnection, + sequence: u32, + reply_pointer: *?*anyopaque, + error_pointer: *?*linux.XcbGenericError, +) callconv(.c) c_int { + const fake: *FakeXcb = @ptrCast(@alignCast(connection)); + if (!fake.replies_ready) return 0; + if (fake.error_sequence == sequence) { + const error_memory = std.c.malloc(@sizeOf(linux.XcbGenericError)) orelse unreachable; + error_pointer.* = @ptrCast(@alignCast(error_memory)); + return 1; + } + const reply_memory = std.c.malloc(@sizeOf(linux.XcbInternAtomReply)) orelse unreachable; + const reply: *linux.XcbInternAtomReply = @ptrCast(@alignCast(reply_memory)); + reply.* = .{ + .response_type = 1, + .pad0 = 0, + .sequence = @truncate(sequence), + .length = 0, + .atom = 100 + sequence, + }; + reply_pointer.* = reply; + return 1; +} + +fn fakeDiscardReply(connection: *linux.XcbConnection, _: u32) callconv(.c) void { + const fake: *FakeXcb = @ptrCast(@alignCast(connection)); + fake.discard_count += 1; +} + +var fake_setup: linux.XcbSetup = .{ + .status = 1, + .pad0 = 0, + .protocol_major_version = 11, + .protocol_minor_version = 0, + .length = 0, + .release_number = 0, + .resource_id_base = 0, + .resource_id_mask = 0, + .motion_buffer_size = 0, + .vendor_length = 0, + .maximum_request_length = 65535, + .roots_length = 1, + .pixmap_formats_length = 0, + .image_byte_order = 0, + .bitmap_format_bit_order = 0, + .bitmap_format_scanline_unit = 0, + .bitmap_format_scanline_pad = 0, + .min_keycode = 0, + .max_keycode = 0, + .pad1 = .{0} ** 4, +}; +var fake_screen: linux.XcbScreen = .{ + .root = 1, + .default_colormap = 0, + .white_pixel = 0, + .black_pixel = 0, + .current_input_masks = 0, + .width_in_pixels = 1, + .height_in_pixels = 1, + .width_in_millimeters = 1, + .height_in_millimeters = 1, + .min_installed_maps = 0, + .max_installed_maps = 0, + .root_visual = 0, + .backing_stores = 0, + .save_unders = 0, + .root_depth = 0, + .allowed_depths_length = 0, +}; + +fn fakeGetSetup(_: *linux.XcbConnection) callconv(.c) *const linux.XcbSetup { + return &fake_setup; +} + +fn fakeRootsIterator(_: *const linux.XcbSetup) callconv(.c) linux.XcbScreenIterator { + return .{ .data = &fake_screen, .remaining = 1, .index = 0 }; +} + +fn fakeScreenNext(iterator: *linux.XcbScreenIterator) callconv(.c) void { + iterator.remaining = 0; +} + +fn fakeGenerateId(_: *linux.XcbConnection) callconv(.c) u32 { + return 2; +} + +fn fakeCreateWindow( + _: *linux.XcbConnection, + _: u8, + _: u32, + _: u32, + _: i16, + _: i16, + _: u16, + _: u16, + _: u16, + _: u16, + _: u32, + _: u32, + _: ?*const anyopaque, +) callconv(.c) linux.XcbCookie { + return .{ .sequence = 1 }; +} + +fn fakeDestroyWindow(_: *linux.XcbConnection, _: u32) callconv(.c) linux.XcbCookie { + return .{ .sequence = 1 }; +} + +fn fakeDeleteProperty(_: *linux.XcbConnection, _: u32, _: u32) callconv(.c) linux.XcbCookie { + return .{ .sequence = 1 }; +} diff --git a/packages/core/src/zig/grapheme.zig b/packages/core/src/zig/grapheme.zig index 6192f66dc7..e7aabee7c9 100644 --- a/packages/core/src/zig/grapheme.zig +++ b/packages/core/src/zig/grapheme.zig @@ -11,10 +11,33 @@ pub const GraphemePoolError = error{ // Encoding flags for char buffer entries (u32) // Bits 31-30: encoding type // 00xxxxxxxx: direct unicode scalar value (30 bits, as-is) +// 01xxxxxxxx: image cell with reservation ID and fallback quadrant // 10xxxxxxxx: grapheme start cell with pool ID (26 bits total payload) // 11xxxxxxxx: continuation cell marker for wide/grapheme rendering +pub const CHAR_FLAG_IMAGE: u32 = 0x4000_0000; pub const CHAR_FLAG_GRAPHEME: u32 = 0x8000_0000; pub const CHAR_FLAG_CONTINUATION: u32 = 0xC000_0000; +pub const CHAR_TYPE_MASK: u32 = 0xC000_0000; +pub const IMAGE_ID_MASK: u32 = 0x03FF_FFFF; + +pub fn packImageCell(id: u32, fallback: u4) u32 { + assert(id <= IMAGE_ID_MASK); + return CHAR_FLAG_IMAGE | (id << 4) | fallback; +} + +pub fn isImageChar(char: u32) bool { + return char & CHAR_TYPE_MASK == CHAR_FLAG_IMAGE; +} + +pub fn imageIdFromChar(char: u32) u32 { + assert(isImageChar(char)); + return (char >> 4) & IMAGE_ID_MASK; +} + +pub fn imageFallbackFromChar(char: u32) u4 { + assert(isImageChar(char)); + return @truncate(char); +} // For grapheme start and continuation cells: // Bits 29..28: right extent (u2), Bits 27..26: left extent (u2) diff --git a/packages/core/src/zig/handles.zig b/packages/core/src/zig/handles.zig index d197bf207f..48faf72ea8 100644 --- a/packages/core/src/zig/handles.zig +++ b/packages/core/src/zig/handles.zig @@ -24,6 +24,9 @@ pub const ObjectKind = enum(u4) { event_sink = 7, audio_engine = 8, native_renderable = 9, + image = 10, + clipboard_service = 11, + clipboard_operation = 12, }; const SlotState = enum(u8) { diff --git a/packages/core/src/zig/image-resize-shim.c b/packages/core/src/zig/image-resize-shim.c new file mode 100644 index 0000000000..912061f5b8 --- /dev/null +++ b/packages/core/src/zig/image-resize-shim.c @@ -0,0 +1,39 @@ +#include +#include +#include + +// Keep stb_image_resize2 isolated for one accepted upstream exception. Three +// SIMD sRGB lookups form `fp32_to_srgb8_tab4 - 912`; clamped indexes 912...1015 +// resolve to the real 104-entry table at 0...103. The effective reads are in +// range, but forming the pre-array pointer violates C's pointer model and trips +// bounds instrumentation. Only `bounds` is disabled for this translation unit; +// pointer-overflow, alignment, and other sanitizers remain enabled, and +// image-shim.c keeps normal instrumentation. This is unrelated to OpenTUI's +// coefficient-copy alignment patch. See vendor/stb/README.md for evidence, +// scope, accepted policy, and the permanent remediation. +#define STB_IMAGE_RESIZE_IMPLEMENTATION +#define STB_IMAGE_RESIZE_STATIC +#include "vendor/stb/stb_image_resize2.h" + +enum { + OT_IMAGE_RESIZE_SHIM_OK = 0, + OT_IMAGE_RESIZE_SHIM_INVALID = 1, + OT_IMAGE_RESIZE_SHIM_OUT_OF_MEMORY = 2, +}; + +int ot_image_resize_rgba(const uint8_t *input, uint32_t input_width, uint32_t input_height, + uint32_t input_stride, uint8_t *output, uint32_t output_width, + uint32_t output_height, uint32_t output_stride, uint32_t filter) { + if (!input || !output || input_width == 0 || input_height == 0 || + output_width == 0 || output_height == 0 || input_width > INT32_MAX || + input_height > INT32_MAX || output_width > INT32_MAX || output_height > INT32_MAX || + input_stride > INT32_MAX || output_stride > INT32_MAX || filter > STBIR_FILTER_POINT_SAMPLE) { + return OT_IMAGE_RESIZE_SHIM_INVALID; + } + + void *result = stbir_resize( + input, (int)input_width, (int)input_height, (int)input_stride, + output, (int)output_width, (int)output_height, (int)output_stride, + STBIR_RGBA, STBIR_TYPE_UINT8_SRGB, STBIR_EDGE_CLAMP, (stbir_filter)filter); + return result ? OT_IMAGE_RESIZE_SHIM_OK : OT_IMAGE_RESIZE_SHIM_OUT_OF_MEMORY; +} diff --git a/packages/core/src/zig/image-shim.c b/packages/core/src/zig/image-shim.c new file mode 100644 index 0000000000..102980fa5a --- /dev/null +++ b/packages/core/src/zig/image-shim.c @@ -0,0 +1,439 @@ +#include +#include +#include + +#define WUFFS_IMPLEMENTATION +#define WUFFS_CONFIG__STATIC_FUNCTIONS +#define WUFFS_CONFIG__MODULES +#define WUFFS_CONFIG__MODULE__BASE +#define WUFFS_CONFIG__MODULE__ADLER32 +#define WUFFS_CONFIG__MODULE__CRC32 +#define WUFFS_CONFIG__MODULE__DEFLATE +#define WUFFS_CONFIG__MODULE__LZW +#define WUFFS_CONFIG__MODULE__ZLIB +#define WUFFS_CONFIG__MODULE__GIF +#define WUFFS_CONFIG__MODULE__PNG +#include "vendor/wuffs/wuffs-v0.3.c" + +static _Thread_local int ot_image_stbi_out_of_memory = 0; + +static void *ot_image_stbi_malloc(size_t size) { + void *result = malloc(size); + if (!result && size > 0) ot_image_stbi_out_of_memory = 1; + return result; +} + +static void *ot_image_stbi_realloc(void *pointer, size_t old_size, size_t new_size) { + (void)old_size; + void *result = realloc(pointer, new_size); + if (!result && new_size > 0) ot_image_stbi_out_of_memory = 1; + return result; +} + +#define STBI_MALLOC(size) ot_image_stbi_malloc(size) +#define STBI_REALLOC_SIZED(pointer, old_size, new_size) ot_image_stbi_realloc(pointer, old_size, new_size) +#define STBI_FREE(pointer) free(pointer) +#define STB_IMAGE_IMPLEMENTATION +#define STB_IMAGE_STATIC +#define STBI_ONLY_JPEG +#define STBI_NO_STDIO +#define STBI_STRICT_JPEG +#include "vendor/stb/stb_image.h" + +#include "src/webp/decode.h" + +enum { + OT_IMAGE_SHIM_OK = 0, + OT_IMAGE_SHIM_INVALID = 1, + OT_IMAGE_SHIM_OUT_OF_MEMORY = 2, + OT_IMAGE_SHIM_OUTPUT_TOO_SMALL = 3, + OT_IMAGE_SHIM_UNSUPPORTED = 4, +}; + +static int ot_image_init_png_decoder(wuffs_png__decoder *decoder) { + wuffs_base__status status = wuffs_png__decoder__initialize( + decoder, sizeof(*decoder), WUFFS_VERSION, 0); + return wuffs_base__status__is_ok(&status) ? OT_IMAGE_SHIM_OK : OT_IMAGE_SHIM_INVALID; +} + +int ot_image_png_probe(const uint8_t *data, uint32_t data_len, uint32_t *width, uint32_t *height) { + if (!data || data_len == 0 || !width || !height) return OT_IMAGE_SHIM_INVALID; + + wuffs_png__decoder *decoder = malloc(sizeof(*decoder)); + if (!decoder) return OT_IMAGE_SHIM_OUT_OF_MEMORY; + + int result = ot_image_init_png_decoder(decoder); + if (result != OT_IMAGE_SHIM_OK) { + free(decoder); + return result; + } + + wuffs_base__io_buffer src = wuffs_base__ptr_u8__reader((uint8_t *)data, data_len, true); + wuffs_base__image_config config = wuffs_base__null_image_config(); + wuffs_base__status status = wuffs_png__decoder__decode_image_config(decoder, &config, &src); + if (!wuffs_base__status__is_ok(&status) || !wuffs_base__pixel_config__is_valid(&config.pixcfg)) { + free(decoder); + return OT_IMAGE_SHIM_INVALID; + } + + *width = wuffs_base__pixel_config__width(&config.pixcfg); + *height = wuffs_base__pixel_config__height(&config.pixcfg); + free(decoder); + return (*width > 0 && *height > 0) ? OT_IMAGE_SHIM_OK : OT_IMAGE_SHIM_INVALID; +} + +int ot_image_png_decode(const uint8_t *data, uint32_t data_len, uint8_t *output, + uint64_t output_len, uint32_t expected_width, uint32_t expected_height) { + if (!data || data_len == 0 || !output || expected_width == 0 || expected_height == 0) { + return OT_IMAGE_SHIM_INVALID; + } + + uint64_t required = (uint64_t)expected_width * (uint64_t)expected_height * 4u; + if (required > output_len || required > SIZE_MAX) return OT_IMAGE_SHIM_OUTPUT_TOO_SMALL; + + wuffs_png__decoder *decoder = malloc(sizeof(*decoder)); + if (!decoder) return OT_IMAGE_SHIM_OUT_OF_MEMORY; + + int result = ot_image_init_png_decoder(decoder); + if (result != OT_IMAGE_SHIM_OK) { + free(decoder); + return result; + } + + wuffs_base__io_buffer src = wuffs_base__ptr_u8__reader((uint8_t *)data, data_len, true); + wuffs_base__image_config config = wuffs_base__null_image_config(); + wuffs_base__status status = wuffs_png__decoder__decode_image_config(decoder, &config, &src); + if (!wuffs_base__status__is_ok(&status) || + wuffs_base__pixel_config__width(&config.pixcfg) != expected_width || + wuffs_base__pixel_config__height(&config.pixcfg) != expected_height) { + free(decoder); + return OT_IMAGE_SHIM_INVALID; + } + + wuffs_base__pixel_config output_config = wuffs_base__null_pixel_config(); + wuffs_base__pixel_config__set(&output_config, WUFFS_BASE__PIXEL_FORMAT__RGBA_NONPREMUL, + WUFFS_BASE__PIXEL_SUBSAMPLING__NONE, + expected_width, expected_height); + wuffs_base__pixel_buffer pixel_buffer; + status = wuffs_base__pixel_buffer__set_from_slice( + &pixel_buffer, &output_config, wuffs_base__make_slice_u8(output, (size_t)required)); + if (!wuffs_base__status__is_ok(&status)) { + free(decoder); + return OT_IMAGE_SHIM_INVALID; + } + + wuffs_base__range_ii_u64 workbuf_range = wuffs_png__decoder__workbuf_len(decoder); + uint64_t workbuf_len = workbuf_range.max_incl; + if (workbuf_len > SIZE_MAX) { + free(decoder); + return OT_IMAGE_SHIM_OUT_OF_MEMORY; + } + + uint8_t *workbuf = workbuf_len ? malloc((size_t)workbuf_len) : NULL; + if (workbuf_len && !workbuf) { + free(decoder); + return OT_IMAGE_SHIM_OUT_OF_MEMORY; + } + + status = wuffs_png__decoder__decode_frame( + decoder, &pixel_buffer, &src, WUFFS_BASE__PIXEL_BLEND__SRC, + wuffs_base__make_slice_u8(workbuf, (size_t)workbuf_len), NULL); + free(workbuf); + free(decoder); + return wuffs_base__status__is_ok(&status) ? OT_IMAGE_SHIM_OK : OT_IMAGE_SHIM_INVALID; +} + +static int ot_image_init_gif_decoder(wuffs_gif__decoder *decoder) { + wuffs_base__status status = wuffs_gif__decoder__initialize( + decoder, sizeof(*decoder), WUFFS_VERSION, 0); + if (!wuffs_base__status__is_ok(&status)) return OT_IMAGE_SHIM_INVALID; + wuffs_gif__decoder__set_quirk_enabled( + decoder, WUFFS_GIF__QUIRK_IMAGE_BOUNDS_ARE_STRICT, true); + wuffs_gif__decoder__set_quirk_enabled( + decoder, WUFFS_GIF__QUIRK_HONOR_BACKGROUND_COLOR, true); + return OT_IMAGE_SHIM_OK; +} + +static wuffs_base__color_u32_argb_premul ot_image_gif_background_color( + const uint8_t *data, uint32_t data_len, + wuffs_base__color_u32_argb_premul decoded_background) { + if ((decoded_background >> 24) == 0 || data_len < 13 || (data[10] & 0x80) == 0) { + return decoded_background; + } + + uint32_t palette_entries = 2u << (data[10] & 0x07); + uint32_t background_index = data[11]; + uint32_t palette_offset = 13u + (background_index * 3u); + if (background_index >= palette_entries || palette_offset + 3u > data_len) { + return decoded_background; + } + return 0xFF000000u | ((uint32_t)data[palette_offset] << 16) | + ((uint32_t)data[palette_offset + 1] << 8) | data[palette_offset + 2]; +} + +static int ot_image_gif_validate_remainder(wuffs_gif__decoder *decoder, + wuffs_base__io_buffer *src) { + while (1) { + wuffs_base__status status = wuffs_gif__decoder__decode_frame_config(decoder, NULL, src); + if (status.repr == wuffs_base__note__end_of_data) { + return (src->meta.ri > 0 && src->data.ptr[src->meta.ri - 1] == 0x3B) + ? OT_IMAGE_SHIM_OK + : OT_IMAGE_SHIM_INVALID; + } + if (!wuffs_base__status__is_ok(&status)) return OT_IMAGE_SHIM_INVALID; + } +} + +int ot_image_gif_probe(const uint8_t *data, uint32_t data_len, uint32_t *width, + uint32_t *height, uint32_t *has_alpha) { + if (!data || data_len == 0 || !width || !height || !has_alpha) return OT_IMAGE_SHIM_INVALID; + + wuffs_gif__decoder *decoder = malloc(sizeof(*decoder)); + if (!decoder) return OT_IMAGE_SHIM_OUT_OF_MEMORY; + int result = ot_image_init_gif_decoder(decoder); + if (result != OT_IMAGE_SHIM_OK) { + free(decoder); + return result; + } + + wuffs_base__io_buffer src = wuffs_base__ptr_u8__reader((uint8_t *)data, data_len, true); + wuffs_base__image_config config = wuffs_base__null_image_config(); + wuffs_base__status status = wuffs_gif__decoder__decode_image_config(decoder, &config, &src); + if (!wuffs_base__status__is_ok(&status) || !wuffs_base__pixel_config__is_valid(&config.pixcfg)) { + free(decoder); + return OT_IMAGE_SHIM_INVALID; + } + + *width = wuffs_base__pixel_config__width(&config.pixcfg); + *height = wuffs_base__pixel_config__height(&config.pixcfg); + *has_alpha = wuffs_base__image_config__first_frame_is_opaque(&config) ? 0u : 1u; + result = ot_image_gif_validate_remainder(decoder, &src); + free(decoder); + return (*width > 0 && *height > 0 && result == OT_IMAGE_SHIM_OK) ? OT_IMAGE_SHIM_OK : OT_IMAGE_SHIM_INVALID; +} + +int ot_image_gif_decode_first_frame(const uint8_t *data, uint32_t data_len, uint8_t *output, + uint64_t output_len, uint32_t expected_width, + uint32_t expected_height) { + if (!data || data_len == 0 || !output || expected_width == 0 || expected_height == 0) { + return OT_IMAGE_SHIM_INVALID; + } + uint64_t required = (uint64_t)expected_width * (uint64_t)expected_height * 4u; + if (required > output_len || required > SIZE_MAX) return OT_IMAGE_SHIM_OUTPUT_TOO_SMALL; + + wuffs_gif__decoder *decoder = malloc(sizeof(*decoder)); + if (!decoder) return OT_IMAGE_SHIM_OUT_OF_MEMORY; + int result = ot_image_init_gif_decoder(decoder); + if (result != OT_IMAGE_SHIM_OK) { + free(decoder); + return result; + } + + wuffs_base__io_buffer src = wuffs_base__ptr_u8__reader((uint8_t *)data, data_len, true); + wuffs_base__image_config config = wuffs_base__null_image_config(); + wuffs_base__status status = wuffs_gif__decoder__decode_image_config(decoder, &config, &src); + if (!wuffs_base__status__is_ok(&status) || + wuffs_base__pixel_config__width(&config.pixcfg) != expected_width || + wuffs_base__pixel_config__height(&config.pixcfg) != expected_height) { + free(decoder); + return OT_IMAGE_SHIM_INVALID; + } + + wuffs_base__frame_config frame_config = wuffs_base__null_frame_config(); + status = wuffs_gif__decoder__decode_frame_config(decoder, &frame_config, &src); + if (!wuffs_base__status__is_ok(&status)) { + free(decoder); + return OT_IMAGE_SHIM_INVALID; + } + + wuffs_base__pixel_config output_config = wuffs_base__null_pixel_config(); + wuffs_base__pixel_config__set(&output_config, WUFFS_BASE__PIXEL_FORMAT__RGBA_NONPREMUL, + WUFFS_BASE__PIXEL_SUBSAMPLING__NONE, + expected_width, expected_height); + wuffs_base__pixel_buffer pixel_buffer; + status = wuffs_base__pixel_buffer__set_from_slice( + &pixel_buffer, &output_config, wuffs_base__make_slice_u8(output, (size_t)required)); + if (!wuffs_base__status__is_ok(&status)) { + free(decoder); + return OT_IMAGE_SHIM_INVALID; + } + + wuffs_base__color_u32_argb_premul background_color = + wuffs_base__frame_config__background_color(&frame_config); + background_color = ot_image_gif_background_color(data, data_len, background_color); + if (!wuffs_base__color_u32_argb_premul__is_valid(background_color)) { + free(decoder); + return OT_IMAGE_SHIM_INVALID; + } + status = wuffs_base__pixel_buffer__set_color_u32_fill_rect( + &pixel_buffer, wuffs_base__make_rect_ie_u32(0, 0, expected_width, expected_height), background_color); + if (!wuffs_base__status__is_ok(&status)) { + free(decoder); + return OT_IMAGE_SHIM_INVALID; + } + wuffs_base__range_ii_u64 workbuf_range = wuffs_gif__decoder__workbuf_len(decoder); + uint64_t workbuf_len = workbuf_range.max_incl; + if (workbuf_len > SIZE_MAX) { + free(decoder); + return OT_IMAGE_SHIM_OUT_OF_MEMORY; + } + uint8_t *workbuf = workbuf_len ? malloc((size_t)workbuf_len) : NULL; + if (workbuf_len && !workbuf) { + free(decoder); + return OT_IMAGE_SHIM_OUT_OF_MEMORY; + } + + status = wuffs_gif__decoder__decode_frame( + decoder, &pixel_buffer, &src, WUFFS_BASE__PIXEL_BLEND__SRC_OVER, + wuffs_base__make_slice_u8(workbuf, (size_t)workbuf_len), NULL); + free(workbuf); + if (wuffs_base__status__is_ok(&status)) { + result = ot_image_gif_validate_remainder(decoder, &src); + } else { + result = OT_IMAGE_SHIM_INVALID; + } + free(decoder); + return result; +} + +static int ot_image_jpeg_has_complete_structure(const uint8_t *data, uint32_t data_len) { + if (!data || data_len < 4 || data[0] != 0xFF || data[1] != 0xD8) return 0; + + uint32_t pos = 2; + int entropy_data = 0; + int saw_scan = 0; + while (pos < data_len) { + uint8_t marker = 0; + if (entropy_data) { + while (pos < data_len) { + if (data[pos++] != 0xFF) continue; + while (pos < data_len && data[pos] == 0xFF) ++pos; + if (pos >= data_len) return 0; + marker = data[pos++]; + if (marker == 0x00 || (marker >= 0xD0 && marker <= 0xD7)) continue; + break; + } + if (marker == 0) return 0; + } else { + if (data[pos++] != 0xFF) return 0; + while (pos < data_len && data[pos] == 0xFF) ++pos; + if (pos >= data_len) return 0; + marker = data[pos++]; + if (marker == 0x00 || marker == 0xD8) return 0; + } + + if (marker == 0xD9) return saw_scan; + if (marker == 0x01) continue; + if (marker >= 0xD0 && marker <= 0xD7) { + entropy_data = 0; + continue; + } + if (pos + 2 > data_len) return 0; + uint32_t segment_len = ((uint32_t)data[pos] << 8) | data[pos + 1]; + if (segment_len < 2 || segment_len > data_len - pos) return 0; + pos += segment_len; + if (marker == 0xDA) saw_scan = 1; + entropy_data = marker == 0xDA || (entropy_data && marker == 0xDC); + } + return 0; +} + +int ot_image_jpeg_header_probe(const uint8_t *data, uint32_t data_len, uint32_t *width, uint32_t *height) { + if (!data || data_len == 0 || data_len > INT32_MAX || !width || !height) return OT_IMAGE_SHIM_INVALID; + int w = 0; + int h = 0; + int channels = 0; + ot_image_stbi_out_of_memory = 0; + if (!stbi_info_from_memory(data, (int)data_len, &w, &h, &channels) || w <= 0 || h <= 0) { + return ot_image_stbi_out_of_memory ? OT_IMAGE_SHIM_OUT_OF_MEMORY : OT_IMAGE_SHIM_INVALID; + } + *width = (uint32_t)w; + *height = (uint32_t)h; + return OT_IMAGE_SHIM_OK; +} + +int ot_image_jpeg_probe(const uint8_t *data, uint32_t data_len, uint32_t *width, uint32_t *height) { + if (!data || data_len == 0 || data_len > INT32_MAX || !width || !height) return OT_IMAGE_SHIM_INVALID; + if (!ot_image_jpeg_has_complete_structure(data, data_len)) return OT_IMAGE_SHIM_INVALID; + int w = 0; + int h = 0; + int channels = 0; + ot_image_stbi_out_of_memory = 0; + uint8_t *decoded = stbi_load_from_memory(data, (int)data_len, &w, &h, &channels, 4); + if (!decoded) return ot_image_stbi_out_of_memory ? OT_IMAGE_SHIM_OUT_OF_MEMORY : OT_IMAGE_SHIM_INVALID; + stbi_image_free(decoded); + if (w <= 0 || h <= 0) return OT_IMAGE_SHIM_INVALID; + *width = (uint32_t)w; + *height = (uint32_t)h; + return OT_IMAGE_SHIM_OK; +} + +int ot_image_jpeg_decode(const uint8_t *data, uint32_t data_len, uint8_t *output, + uint64_t output_len, uint32_t expected_width, uint32_t expected_height) { + if (!data || data_len == 0 || data_len > INT32_MAX || !output || + expected_width == 0 || expected_height == 0) return OT_IMAGE_SHIM_INVALID; + uint64_t required = (uint64_t)expected_width * (uint64_t)expected_height * 4u; + if (required > output_len || required > SIZE_MAX) return OT_IMAGE_SHIM_OUTPUT_TOO_SMALL; + if (!ot_image_jpeg_has_complete_structure(data, data_len)) return OT_IMAGE_SHIM_INVALID; + + int width = 0; + int height = 0; + int channels = 0; + ot_image_stbi_out_of_memory = 0; + uint8_t *decoded = stbi_load_from_memory(data, (int)data_len, &width, &height, &channels, 4); + if (!decoded) return ot_image_stbi_out_of_memory ? OT_IMAGE_SHIM_OUT_OF_MEMORY : OT_IMAGE_SHIM_INVALID; + if ((uint32_t)width != expected_width || (uint32_t)height != expected_height) { + stbi_image_free(decoded); + return OT_IMAGE_SHIM_INVALID; + } + memcpy(output, decoded, (size_t)required); + stbi_image_free(decoded); + return OT_IMAGE_SHIM_OK; +} + +int ot_image_webp_probe(const uint8_t *data, uint32_t data_len, uint32_t *width, + uint32_t *height, uint32_t *has_alpha) { + if (!data || data_len == 0 || !width || !height || !has_alpha) return OT_IMAGE_SHIM_INVALID; + WebPBitstreamFeatures features; + VP8StatusCode status = WebPGetFeatures(data, data_len, &features); + if (status != VP8_STATUS_OK) return status == VP8_STATUS_OUT_OF_MEMORY ? OT_IMAGE_SHIM_OUT_OF_MEMORY : OT_IMAGE_SHIM_INVALID; + if (features.has_animation) return OT_IMAGE_SHIM_UNSUPPORTED; + if (features.width <= 0 || features.height <= 0) return OT_IMAGE_SHIM_INVALID; + *width = (uint32_t)features.width; + *height = (uint32_t)features.height; + *has_alpha = features.has_alpha ? 1u : 0u; + return OT_IMAGE_SHIM_OK; +} + +int ot_image_webp_decode(const uint8_t *data, uint32_t data_len, uint8_t *output, + uint64_t output_len, uint32_t expected_width, uint32_t expected_height) { + if (!data || data_len == 0 || !output || expected_width == 0 || expected_height == 0) { + return OT_IMAGE_SHIM_INVALID; + } + uint64_t required = (uint64_t)expected_width * (uint64_t)expected_height * 4u; + if (required > output_len || required > SIZE_MAX || expected_width > INT32_MAX) { + return OT_IMAGE_SHIM_OUTPUT_TOO_SMALL; + } + + WebPDecoderConfig config; + if (!WebPInitDecoderConfig(&config)) return OT_IMAGE_SHIM_INVALID; + VP8StatusCode status = WebPGetFeatures(data, data_len, &config.input); + if (status != VP8_STATUS_OK) return status == VP8_STATUS_OUT_OF_MEMORY ? OT_IMAGE_SHIM_OUT_OF_MEMORY : OT_IMAGE_SHIM_INVALID; + if (config.input.has_animation) return OT_IMAGE_SHIM_UNSUPPORTED; + if ((uint32_t)config.input.width != expected_width || (uint32_t)config.input.height != expected_height) { + return OT_IMAGE_SHIM_INVALID; + } + + config.output.colorspace = MODE_RGBA; + config.output.is_external_memory = 1; + config.output.u.RGBA.rgba = output; + config.output.u.RGBA.stride = (int)(expected_width * 4u); + config.output.u.RGBA.size = (size_t)required; + config.options.use_threads = 0; + status = WebPDecode(data, data_len, &config); + WebPFreeDecBuffer(&config.output); + if (status == VP8_STATUS_OK) return OT_IMAGE_SHIM_OK; + if (status == VP8_STATUS_OUT_OF_MEMORY) return OT_IMAGE_SHIM_OUT_OF_MEMORY; + if (status == VP8_STATUS_UNSUPPORTED_FEATURE) return OT_IMAGE_SHIM_UNSUPPORTED; + return OT_IMAGE_SHIM_INVALID; +} diff --git a/packages/core/src/zig/image-webp-avx2.c b/packages/core/src/zig/image-webp-avx2.c new file mode 100644 index 0000000000..b9c2529c73 --- /dev/null +++ b/packages/core/src/zig/image-webp-avx2.c @@ -0,0 +1,8 @@ +#if defined(__x86_64__) || defined(_M_X64) +#define WEBP_USE_AVX2 +#pragma clang attribute push(__attribute__((target("avx2"))), apply_to = function) + +#include "vendor/libwebp/src/dsp/lossless_avx2.c" + +#pragma clang attribute pop +#endif diff --git a/packages/core/src/zig/image-webp-config.c b/packages/core/src/zig/image-webp-config.c new file mode 100644 index 0000000000..10707dbb78 --- /dev/null +++ b/packages/core/src/zig/image-webp-config.c @@ -0,0 +1,13 @@ +#include "src/dsp/cpu.h" + +#if defined(__x86_64__) || defined(_M_X64) +#if !defined(WEBP_HAVE_SSE2) +#error "x64 WebP builds must include SSE2 runtime dispatch" +#endif +#if !defined(WEBP_HAVE_SSE41) +#error "x64 WebP builds must include SSE4.1 runtime dispatch" +#endif +#if !defined(WEBP_HAVE_AVX2) +#error "x64 WebP builds must include AVX2 runtime dispatch" +#endif +#endif diff --git a/packages/core/src/zig/image-webp-sse41.c b/packages/core/src/zig/image-webp-sse41.c new file mode 100644 index 0000000000..0fdc8e159f --- /dev/null +++ b/packages/core/src/zig/image-webp-sse41.c @@ -0,0 +1,12 @@ +#if defined(__x86_64__) || defined(_M_X64) +#define WEBP_USE_SSE41 +#pragma clang attribute push(__attribute__((target("sse4.1"))), apply_to = function) + +#include "vendor/libwebp/src/dsp/alpha_processing_sse41.c" +#include "vendor/libwebp/src/dsp/dec_sse41.c" +#include "vendor/libwebp/src/dsp/lossless_sse41.c" +#include "vendor/libwebp/src/dsp/upsampling_sse41.c" +#include "vendor/libwebp/src/dsp/yuv_sse41.c" + +#pragma clang attribute pop +#endif diff --git a/packages/core/src/zig/image.zig b/packages/core/src/zig/image.zig new file mode 100644 index 0000000000..7d0a852b97 --- /dev/null +++ b/packages/core/src/zig/image.zig @@ -0,0 +1,885 @@ +const std = @import("std"); + +const Allocator = std.mem.Allocator; + +extern fn ot_image_png_probe(data: [*]const u8, data_len: u32, width: *u32, height: *u32) c_int; +extern fn ot_image_png_decode( + data: [*]const u8, + data_len: u32, + output: [*]u8, + output_len: u64, + expected_width: u32, + expected_height: u32, +) c_int; +extern fn ot_image_gif_probe(data: [*]const u8, data_len: u32, width: *u32, height: *u32, has_alpha: *u32) c_int; +extern fn ot_image_gif_decode_first_frame( + data: [*]const u8, + data_len: u32, + output: [*]u8, + output_len: u64, + expected_width: u32, + expected_height: u32, +) c_int; +extern fn ot_image_jpeg_probe(data: [*]const u8, data_len: u32, width: *u32, height: *u32) c_int; +extern fn ot_image_jpeg_header_probe(data: [*]const u8, data_len: u32, width: *u32, height: *u32) c_int; +extern fn ot_image_jpeg_decode( + data: [*]const u8, + data_len: u32, + output: [*]u8, + output_len: u64, + expected_width: u32, + expected_height: u32, +) c_int; +extern fn ot_image_webp_probe(data: [*]const u8, data_len: u32, width: *u32, height: *u32, has_alpha: *u32) c_int; +extern fn ot_image_webp_decode( + data: [*]const u8, + data_len: u32, + output: [*]u8, + output_len: u64, + expected_width: u32, + expected_height: u32, +) c_int; +extern fn ot_image_resize_rgba( + input: [*]const u8, + input_width: u32, + input_height: u32, + input_stride: u32, + output: [*]u8, + output_width: u32, + output_height: u32, + output_stride: u32, + filter: u32, +) c_int; + +pub const Status = enum(u32) { + ok = 0, + invalid_handle = 1, + unsupported_format = 2, + unsupported_color_space = 3, + malformed_input = 4, + dimension_limit = 5, + memory_limit = 6, + invalid_argument = 7, + out_of_memory = 8, + output_too_small = 9, + internal_error = 10, + unsupported_feature = 11, +}; + +pub const Format = enum(u32) { + unknown = 0, + png = 1, + raw_rgba = 2, + jpeg = 3, + webp = 4, + gif = 5, +}; + +pub const ColorStatus = enum(u32) { + assumed_srgb = 0, + explicit_srgb = 1, +}; + +pub const Info = extern struct { + width: u32 = 0, + height: u32 = 0, + source_width: u32 = 0, + source_height: u32 = 0, + format: u32 = @intFromEnum(Format.unknown), + color_status: u32 = @intFromEnum(ColorStatus.assumed_srgb), + orientation: u32 = 1, + has_alpha: u32 = 0, +}; + +pub const Limits = struct { + max_encoded_bytes: u64 = 64 * 1024 * 1024, + max_width: u32 = 16_384, + max_height: u32 = 16_384, + max_pixels: u64 = 25_000_000, + max_decoded_bytes: u64 = 100 * 1024 * 1024, +}; + +pub const ResizeFilter = enum(u32) { + default = 0, + area = 1, + triangle = 2, + cubic_bspline = 3, + catmull_rom = 4, + mitchell = 5, + nearest = 6, +}; + +pub const Transform = enum(u32) { + rotate_90 = 0, + rotate_180 = 1, + rotate_270 = 2, + flip = 3, + flop = 4, +}; + +pub const Blend = enum(u32) { + source_over = 0, + source = 1, + destination_over = 2, +}; + +pub const RenderProtocol = enum(u32) { + auto, + kitty, + sixel, + blocks, +}; + +pub const Image = struct { + allocator: Allocator, + pixels: []u8, + metadata: Info, + encoded_png: ?[]u8 = null, + ref_count: u32 = 1, + + pub fn width(self: *const Image) u32 { + return self.metadata.width; + } + + pub fn height(self: *const Image) u32 { + return self.metadata.height; + } + + pub fn deinit(self: *Image) void { + std.debug.assert(self.ref_count > 0); + self.ref_count -= 1; + if (self.ref_count > 0) return; + if (self.encoded_png) |bytes| self.allocator.free(bytes); + self.allocator.free(self.pixels); + self.allocator.destroy(self); + } + + pub fn retain(self: *Image) void { + std.debug.assert(self.ref_count < std.math.maxInt(u32)); + self.ref_count += 1; + } + + pub fn info(self: *const Image) Info { + return self.metadata; + } + + pub fn discardEncoded(self: *Image) void { + if (self.encoded_png) |bytes| self.allocator.free(bytes); + self.encoded_png = null; + } + + pub fn clone(self: *const Image) !*Image { + const cloned = try copyImage(self.allocator, self.pixels, self.metadata); + errdefer cloned.deinit(); + if (self.encoded_png) |bytes| cloned.encoded_png = try self.allocator.dupe(u8, bytes); + return cloned; + } +}; + +const png_signature = [_]u8{ 137, 80, 78, 71, 13, 10, 26, 10 }; +const srgb_chromaticities = [_]u32{ 31_270, 32_900, 64_000, 33_000, 30_000, 60_000, 15_000, 6_000 }; + +fn readU32Be(bytes: []const u8) u32 { + return std.mem.readInt(u32, bytes[0..4], .big); +} + +fn detectFormat(data: []const u8) Format { + if (data.len >= png_signature.len and std.mem.eql(u8, data[0..png_signature.len], &png_signature)) return .png; + if (data.len >= 6 and (std.mem.eql(u8, data[0..6], "GIF87a") or std.mem.eql(u8, data[0..6], "GIF89a"))) return .gif; + if (data.len >= 2 and data[0] == 0xFF and data[1] == 0xD8) return .jpeg; + if (data.len >= 12 and std.mem.eql(u8, data[0..4], "RIFF") and std.mem.eql(u8, data[8..12], "WEBP")) return .webp; + return .unknown; +} + +fn checkedPixelBytes(width: u32, height: u32, limits: Limits) !usize { + if (width == 0 or height == 0 or width > limits.max_width or height > limits.max_height) { + return error.DimensionLimit; + } + const pixels = std.math.mul(u64, width, height) catch return error.DimensionLimit; + if (pixels > limits.max_pixels) return error.DimensionLimit; + const bytes = std.math.mul(u64, pixels, 4) catch return error.MemoryLimit; + if (bytes > limits.max_decoded_bytes or bytes > std.math.maxInt(usize)) return error.MemoryLimit; + return @intCast(bytes); +} + +pub fn statusFromError(err: anyerror) Status { + return switch (err) { + error.UnsupportedFormat => .unsupported_format, + error.UnsupportedFeature => .unsupported_feature, + error.DimensionLimit => .dimension_limit, + error.MemoryLimit => .memory_limit, + error.OutOfMemory => .out_of_memory, + error.UnsupportedColorSpace => .unsupported_color_space, + error.InvalidArgument => .invalid_argument, + else => .malformed_input, + }; +} + +const PngMetadata = struct { + width: u32, + height: u32, + orientation: u8 = 1, + has_alpha: bool, + color_status: ColorStatus = .assumed_srgb, +}; + +fn parseExifOrientation(data: []const u8) u8 { + var tiff = data; + if (tiff.len >= 6 and std.mem.eql(u8, tiff[0..6], "Exif\x00\x00")) tiff = tiff[6..]; + if (tiff.len < 8) return 1; + + const endian: std.builtin.Endian = if (std.mem.eql(u8, tiff[0..2], "II")) + .little + else if (std.mem.eql(u8, tiff[0..2], "MM")) + .big + else + return 1; + if (std.mem.readInt(u16, tiff[2..4], endian) != 42) return 1; + + const ifd_offset = std.mem.readInt(u32, tiff[4..8], endian); + if (ifd_offset > tiff.len -| 2) return 1; + const ifd: usize = @intCast(ifd_offset); + const count = std.mem.readInt(u16, tiff[ifd..][0..2], endian); + const entries_bytes = std.math.mul(usize, count, 12) catch return 1; + if (entries_bytes > tiff.len -| (ifd + 2)) return 1; + + var found: u8 = 1; + var seen = false; + for (0..count) |index| { + const offset = ifd + 2 + index * 12; + const entry = tiff[offset..][0..12]; + if (std.mem.readInt(u16, entry[0..2], endian) != 0x0112) continue; + if (seen or std.mem.readInt(u16, entry[2..4], endian) != 3 or + std.mem.readInt(u32, entry[4..8], endian) != 1) + { + return 1; + } + const value = std.mem.readInt(u16, entry[8..10], endian); + if (value < 1 or value > 8) return 1; + found = @intCast(value); + seen = true; + } + return found; +} + +fn scanJpegOrientation(data: []const u8) u8 { + if (data.len < 4 or data[0] != 0xFF or data[1] != 0xD8) return 1; + var position: usize = 2; + while (position + 2 <= data.len) { + if (data[position] != 0xFF) return 1; + var marker_position = position + 1; + while (marker_position < data.len and data[marker_position] == 0xFF) marker_position += 1; + if (marker_position >= data.len) return 1; + const marker = data[marker_position]; + position = marker_position + 1; + if (marker == 0x01 or (marker >= 0xD0 and marker <= 0xD8)) continue; + // EXIF metadata must precede entropy-coded data; stop at SOS or EOI. + if (marker == 0xDA or marker == 0xD9) return 1; + if (position + 2 > data.len) return 1; + const segment_length = (@as(usize, data[position]) << 8) | data[position + 1]; + if (segment_length < 2 or segment_length > data.len - position) return 1; + if (marker == 0xE1) { + const payload = data[position + 2 .. position + segment_length]; + if (payload.len >= 6 and std.mem.eql(u8, payload[0..6], "Exif\x00\x00")) { + return parseExifOrientation(payload); + } + } + position += segment_length; + } + return 1; +} + +fn scanPng(data: []const u8) !PngMetadata { + if (data.len < 8 or !std.mem.eql(u8, data[0..8], &png_signature)) return error.UnsupportedFormat; + if (data.len < 33) return error.MalformedInput; + + var offset: usize = 8; + var metadata: ?PngMetadata = null; + var saw_srgb = false; + var saw_cicp = false; + var cicp_supported = false; + var saw_iccp = false; + var saw_gamma = false; + var gamma_supported = false; + var saw_chrm = false; + var chrm_supported = false; + var saw_plte = false; + var saw_idat = false; + var saw_iend = false; + + while (offset <= data.len -| 12) { + const length: usize = readU32Be(data[offset..][0..4]); + const chunk_end = std.math.add(usize, offset + 12, length) catch return error.MalformedInput; + if (chunk_end > data.len) return error.MalformedInput; + const kind = data[offset + 4 .. offset + 8]; + const payload = data[offset + 8 .. offset + 8 + length]; + const expected_crc = readU32Be(data[offset + 8 + length ..][0..4]); + if (std.hash.Crc32.hash(data[offset + 4 .. offset + 8 + length]) != expected_crc) return error.MalformedInput; + + if (std.mem.eql(u8, kind, "IHDR")) { + if (metadata != null or length != 13 or offset != 8) return error.MalformedInput; + const color_type = payload[9]; + if (color_type != 0 and color_type != 2 and color_type != 3 and color_type != 4 and color_type != 6) { + return error.MalformedInput; + } + metadata = .{ + .width = readU32Be(payload[0..4]), + .height = readU32Be(payload[4..8]), + .has_alpha = color_type == 4 or color_type == 6, + }; + } else if (std.mem.eql(u8, kind, "iCCP")) { + if (saw_iccp) return error.MalformedInput; + saw_iccp = true; + } else if (std.mem.eql(u8, kind, "cICP")) { + if (saw_cicp or saw_plte or saw_idat or length != 4) return error.MalformedInput; + saw_cicp = true; + cicp_supported = std.mem.eql(u8, payload, &[_]u8{ 1, 13, 0, 1 }); + } else if (std.mem.eql(u8, kind, "PLTE")) { + if (saw_plte or saw_idat) return error.MalformedInput; + saw_plte = true; + } else if (std.mem.eql(u8, kind, "sRGB")) { + if (saw_srgb or length != 1 or payload[0] > 3) return error.MalformedInput; + saw_srgb = true; + if (metadata) |*value| value.color_status = .explicit_srgb; + } else if (std.mem.eql(u8, kind, "gAMA")) { + if (saw_gamma or length != 4) return error.MalformedInput; + saw_gamma = true; + gamma_supported = readU32Be(payload) == 45_455; + } else if (std.mem.eql(u8, kind, "cHRM")) { + if (saw_chrm or length != 32) return error.MalformedInput; + saw_chrm = true; + chrm_supported = true; + for (srgb_chromaticities, 0..) |expected, index| { + if (readU32Be(payload[index * 4 ..][0..4]) != expected) chrm_supported = false; + } + } else if (std.mem.eql(u8, kind, "tRNS")) { + if (metadata) |*value| value.has_alpha = true; + } else if (std.mem.eql(u8, kind, "eXIf")) { + if (metadata) |*value| value.orientation = parseExifOrientation(payload); + } else if (std.mem.eql(u8, kind, "IDAT")) { + saw_idat = true; + } else if (std.mem.eql(u8, kind, "IEND")) { + if (length != 0 or !saw_idat or chunk_end != data.len) return error.MalformedInput; + saw_iend = true; + break; + } + offset = chunk_end; + } + if (!saw_iend) return error.MalformedInput; + var result = metadata orelse return error.MalformedInput; + if (saw_cicp and cicp_supported) { + result.color_status = .explicit_srgb; + } else if (saw_iccp) { + return error.UnsupportedColorSpace; + } else if (saw_srgb) { + result.color_status = .explicit_srgb; + } else if ((saw_gamma and !gamma_supported) or (saw_chrm and !chrm_supported)) { + return error.UnsupportedColorSpace; + } else if (saw_gamma or saw_chrm) { + result.color_status = .explicit_srgb; + } + return result; +} + +fn probeInternal(data: []const u8, limits: Limits, out: *Info, validate_jpeg: bool) Status { + if (data.len == 0 or data.len > std.math.maxInt(u32)) return .invalid_argument; + if (data.len > limits.max_encoded_bytes) return .memory_limit; + const format = detectFormat(data); + if (format == .unknown) return .unsupported_format; + if (format == .jpeg) { + var width: u32 = 0; + var height: u32 = 0; + const result = ot_image_jpeg_header_probe(data.ptr, @intCast(data.len), &width, &height); + if (result == 2) return .out_of_memory; + if (result != 0) return .malformed_input; + _ = checkedPixelBytes(width, height, limits) catch |err| return statusFromError(err); + if (validate_jpeg) { + var validated_width: u32 = 0; + var validated_height: u32 = 0; + const validation_result = ot_image_jpeg_probe( + data.ptr, + @intCast(data.len), + &validated_width, + &validated_height, + ); + if (validation_result == 2) return .out_of_memory; + if (validation_result != 0 or validated_width != width or validated_height != height) return .malformed_input; + } + const orientation = scanJpegOrientation(data); + const swaps_jpeg_dimensions = orientation >= 5 and orientation <= 8; + out.* = .{ + .width = if (swaps_jpeg_dimensions) height else width, + .height = if (swaps_jpeg_dimensions) width else height, + .source_width = width, + .source_height = height, + .format = @intFromEnum(Format.jpeg), + .color_status = @intFromEnum(ColorStatus.assumed_srgb), + .orientation = orientation, + .has_alpha = 0, + }; + return .ok; + } + if (format == .webp) { + var width: u32 = 0; + var height: u32 = 0; + var has_alpha: u32 = 0; + const result = ot_image_webp_probe(data.ptr, @intCast(data.len), &width, &height, &has_alpha); + if (result == 2) return .out_of_memory; + if (result == 4) return .unsupported_feature; + if (result != 0) return .malformed_input; + _ = checkedPixelBytes(width, height, limits) catch |err| return statusFromError(err); + out.* = .{ + .width = width, + .height = height, + .source_width = width, + .source_height = height, + .format = @intFromEnum(Format.webp), + .color_status = @intFromEnum(ColorStatus.assumed_srgb), + .orientation = 1, + .has_alpha = has_alpha, + }; + return .ok; + } + if (format == .gif) { + var width: u32 = 0; + var height: u32 = 0; + var has_alpha: u32 = 0; + const result = ot_image_gif_probe(data.ptr, @intCast(data.len), &width, &height, &has_alpha); + if (result == 2) return .out_of_memory; + if (result != 0) return .malformed_input; + _ = checkedPixelBytes(width, height, limits) catch |err| return statusFromError(err); + out.* = .{ + .width = width, + .height = height, + .source_width = width, + .source_height = height, + .format = @intFromEnum(Format.gif), + .color_status = @intFromEnum(ColorStatus.assumed_srgb), + .orientation = 1, + .has_alpha = has_alpha, + }; + return .ok; + } + const metadata = scanPng(data) catch |err| return statusFromError(err); + _ = checkedPixelBytes(metadata.width, metadata.height, limits) catch |err| return statusFromError(err); + + var decoder_width: u32 = 0; + var decoder_height: u32 = 0; + const png_probe_status = ot_image_png_probe(data.ptr, @intCast(data.len), &decoder_width, &decoder_height); + if (png_probe_status == 2) return .out_of_memory; + if (png_probe_status != 0 or decoder_width != metadata.width or decoder_height != metadata.height) return .malformed_input; + + const swaps_dimensions = metadata.orientation >= 5 and metadata.orientation <= 8; + out.* = .{ + .width = if (swaps_dimensions) metadata.height else metadata.width, + .height = if (swaps_dimensions) metadata.width else metadata.height, + .source_width = metadata.width, + .source_height = metadata.height, + .format = @intFromEnum(Format.png), + .color_status = @intFromEnum(metadata.color_status), + .orientation = metadata.orientation, + .has_alpha = @intFromBool(metadata.has_alpha), + }; + return .ok; +} + +pub fn probe(data: []const u8, limits: Limits, out: *Info) Status { + return probeInternal(data, limits, out, true); +} + +pub fn inspect(allocator: Allocator, data: []const u8, limits: Limits, out: *Info) Status { + var encoded_info: Info = .{}; + const probe_status = probeInternal(data, limits, &encoded_info, false); + if (probe_status != .ok) return probe_status; + + const decoded = decode(allocator, data, limits) catch |err| return statusFromError(err); + defer decoded.deinit(); + encoded_info.has_alpha = decoded.metadata.has_alpha; + out.* = encoded_info; + return .ok; +} + +fn allocateImage(allocator: Allocator, metadata: Info) !*Image { + const len = try checkedPixelBytes(metadata.width, metadata.height, .{}); + const image = try allocator.create(Image); + errdefer allocator.destroy(image); + image.* = .{ + .allocator = allocator, + .pixels = try allocator.alloc(u8, len), + .metadata = metadata, + }; + return image; +} + +fn copyImage(allocator: Allocator, pixels: []const u8, metadata: Info) !*Image { + const image = try allocateImage(allocator, metadata); + errdefer image.deinit(); + @memcpy(image.pixels, pixels); + return image; +} + +fn pixelsHaveTransparency(pixels: []const u8) bool { + var offset: usize = 3; + while (offset < pixels.len) : (offset += 4) { + if (pixels[offset] != 255) return true; + } + return false; +} + +pub fn createFromRgba(allocator: Allocator, pixels: []const u8, width: u32, height: u32, stride: u32) !*Image { + const row_bytes = std.math.mul(u32, width, 4) catch return error.InvalidArgument; + if (stride < row_bytes) return error.InvalidArgument; + const preceding_rows = std.math.mul(u64, stride, height -| 1) catch return error.InvalidArgument; + const required = std.math.add(u64, preceding_rows, row_bytes) catch return error.InvalidArgument; + if (required > pixels.len) return error.InvalidArgument; + + const image = try allocateImage(allocator, .{ + .width = width, + .height = height, + .source_width = width, + .source_height = height, + .format = @intFromEnum(Format.raw_rgba), + .color_status = @intFromEnum(ColorStatus.explicit_srgb), + .orientation = 1, + .has_alpha = 0, + }); + errdefer image.deinit(); + for (0..height) |y| { + const src_offset = y * stride; + const dst_offset = y * row_bytes; + @memcpy(image.pixels[dst_offset .. dst_offset + row_bytes], pixels[src_offset .. src_offset + row_bytes]); + if (image.metadata.has_alpha == 0 and pixelsHaveTransparency(pixels[src_offset .. src_offset + row_bytes])) image.metadata.has_alpha = 1; + } + return image; +} + +pub fn decode(allocator: Allocator, data: []const u8, limits: Limits) !*Image { + var image_info: Info = .{}; + const probe_status = probeInternal(data, limits, &image_info, false); + if (probe_status != .ok) return switch (probe_status) { + .unsupported_format => error.UnsupportedFormat, + .unsupported_feature => error.UnsupportedFeature, + .unsupported_color_space => error.UnsupportedColorSpace, + .dimension_limit => error.DimensionLimit, + .memory_limit => error.MemoryLimit, + .out_of_memory => error.OutOfMemory, + else => error.MalformedInput, + }; + + const source_len = try checkedPixelBytes(image_info.source_width, image_info.source_height, limits); + const source = try allocator.alloc(u8, source_len); + var source_owned = true; + defer if (source_owned) allocator.free(source); + const format: Format = @enumFromInt(image_info.format); + const decode_status = switch (format) { + .png => ot_image_png_decode( + data.ptr, + @intCast(data.len), + source.ptr, + source.len, + image_info.source_width, + image_info.source_height, + ), + .gif => ot_image_gif_decode_first_frame( + data.ptr, + @intCast(data.len), + source.ptr, + source.len, + image_info.source_width, + image_info.source_height, + ), + .jpeg => ot_image_jpeg_decode( + data.ptr, + @intCast(data.len), + source.ptr, + source.len, + image_info.source_width, + image_info.source_height, + ), + .webp => ot_image_webp_decode( + data.ptr, + @intCast(data.len), + source.ptr, + source.len, + image_info.source_width, + image_info.source_height, + ), + else => return error.UnsupportedFormat, + }; + if (decode_status != 0) return switch (decode_status) { + 2 => error.OutOfMemory, + 4 => error.UnsupportedFeature, + else => error.MalformedInput, + }; + image_info.has_alpha = @intFromBool(pixelsHaveTransparency(source)); + + const color_status: ColorStatus = @enumFromInt(image_info.color_status); + if (image_info.orientation == 1) { + const image = try allocator.create(Image); + errdefer allocator.destroy(image); + const encoded_png = if (format == .png) try allocator.dupe(u8, data) else null; + errdefer if (encoded_png) |bytes| allocator.free(bytes); + image.* = .{ + .allocator = allocator, + .pixels = source, + .encoded_png = encoded_png, + .metadata = .{ + .width = image_info.width, + .height = image_info.height, + .source_width = image_info.source_width, + .source_height = image_info.source_height, + .format = image_info.format, + .color_status = @intFromEnum(color_status), + .orientation = 1, + .has_alpha = image_info.has_alpha, + }, + }; + source_owned = false; + return image; + } + + const unoriented = Image{ + .allocator = allocator, + .pixels = source, + .metadata = .{ + .width = image_info.source_width, + .height = image_info.source_height, + .source_width = image_info.source_width, + .source_height = image_info.source_height, + .format = image_info.format, + .color_status = @intFromEnum(color_status), + .orientation = 1, + .has_alpha = image_info.has_alpha, + }, + }; + return try orient(allocator, &unoriented, @intCast(image_info.orientation)); +} + +fn pixelOffset(width: u32, x: u32, y: u32) usize { + return (@as(usize, y) * width + x) * 4; +} + +fn copyPixel(dst: []u8, dst_width: u32, dx: u32, dy: u32, src: []const u8, src_width: u32, sx: u32, sy: u32) void { + const dst_offset = pixelOffset(dst_width, dx, dy); + const src_offset = pixelOffset(src_width, sx, sy); + @memcpy(dst[dst_offset .. dst_offset + 4], src[src_offset .. src_offset + 4]); +} + +fn orient(allocator: Allocator, source: *const Image, orientation: u8) !*Image { + if (orientation == 1) return source.clone(); + const swap = orientation >= 5 and orientation <= 8; + var metadata = source.metadata; + metadata.width = if (swap) source.height() else source.width(); + metadata.height = if (swap) source.width() else source.height(); + metadata.orientation = 1; + const output = try allocateImage(allocator, metadata); + errdefer output.deinit(); + + for (0..output.height()) |dy_usize| { + for (0..output.width()) |dx_usize| { + const dx: u32 = @intCast(dx_usize); + const dy: u32 = @intCast(dy_usize); + const coords: [2]u32 = switch (orientation) { + 2 => .{ source.width() - 1 - dx, dy }, + 3 => .{ source.width() - 1 - dx, source.height() - 1 - dy }, + 4 => .{ dx, source.height() - 1 - dy }, + 5 => .{ dy, dx }, + 6 => .{ dy, source.height() - 1 - dx }, + 7 => .{ source.width() - 1 - dy, source.height() - 1 - dx }, + 8 => .{ source.width() - 1 - dy, dx }, + else => return error.InvalidArgument, + }; + copyPixel(output.pixels, output.width(), dx, dy, source.pixels, source.width(), coords[0], coords[1]); + } + } + return output; +} + +pub fn transform(allocator: Allocator, source: *const Image, operation: Transform) !*Image { + return orient(allocator, source, switch (operation) { + .rotate_90 => 6, + .rotate_180 => 3, + .rotate_270 => 8, + .flip => 4, + .flop => 2, + }); +} + +pub fn extract(allocator: Allocator, source: *const Image, left: u32, top: u32, width: u32, height: u32) !*Image { + if (width == 0 or height == 0 or left > source.width() or top > source.height() or + width > source.width() - left or height > source.height() - top) + { + return error.InvalidArgument; + } + if (left == 0 and top == 0 and width == source.width() and height == source.height()) return source.clone(); + + var metadata = source.metadata; + metadata.width = width; + metadata.height = height; + const output = try allocateImage(allocator, metadata); + errdefer output.deinit(); + const src_stride = source.width() * 4; + const dst_stride = width * 4; + for (0..height) |y| { + const src_offset = @as(usize, top + @as(u32, @intCast(y))) * src_stride + @as(usize, left) * 4; + const dst_offset = y * dst_stride; + @memcpy(output.pixels[dst_offset .. dst_offset + dst_stride], source.pixels[src_offset .. src_offset + dst_stride]); + } + output.metadata.has_alpha = @intFromBool(pixelsHaveTransparency(output.pixels)); + return output; +} + +pub fn extend( + allocator: Allocator, + source: *const Image, + top: u32, + right: u32, + bottom: u32, + left: u32, + background: [4]u8, +) !*Image { + if (top == 0 and right == 0 and bottom == 0 and left == 0) return source.clone(); + const width = std.math.add(u32, source.width(), left) catch return error.InvalidArgument; + const final_width = std.math.add(u32, width, right) catch return error.InvalidArgument; + const height = std.math.add(u32, source.height(), top) catch return error.InvalidArgument; + const final_height = std.math.add(u32, height, bottom) catch return error.InvalidArgument; + var metadata = source.metadata; + metadata.width = final_width; + metadata.height = final_height; + if (background[3] < 255) metadata.has_alpha = 1; + const output = try allocateImage(allocator, metadata); + errdefer output.deinit(); + + var index: usize = 0; + while (index < output.pixels.len) : (index += 4) @memcpy(output.pixels[index .. index + 4], &background); + const src_stride = source.width() * 4; + const dst_stride = final_width * 4; + for (0..source.height()) |y| { + const src_offset = y * src_stride; + const dst_offset = @as(usize, top + @as(u32, @intCast(y))) * dst_stride + @as(usize, left) * 4; + @memcpy(output.pixels[dst_offset .. dst_offset + src_stride], source.pixels[src_offset .. src_offset + src_stride]); + } + return output; +} + +pub fn resize(allocator: Allocator, source: *const Image, width: u32, height: u32, filter: ResizeFilter) !*Image { + if (width == 0 or height == 0) return error.InvalidArgument; + if (width == source.width() and height == source.height()) return source.clone(); + var metadata = source.metadata; + metadata.width = width; + metadata.height = height; + const output = try allocateImage(allocator, metadata); + errdefer output.deinit(); + if (ot_image_resize_rgba( + source.pixels.ptr, + source.width(), + source.height(), + source.width() * 4, + output.pixels.ptr, + width, + height, + width * 4, + @intFromEnum(filter), + ) != 0) return error.OutOfMemory; + output.metadata.has_alpha = @intFromBool(pixelsHaveTransparency(output.pixels)); + return output; +} + +fn srgbToLinear(value: u8) f32 { + const v: f32 = @as(f32, @floatFromInt(value)) / 255.0; + return if (v <= 0.04045) v / 12.92 else std.math.pow(f32, (v + 0.055) / 1.055, 2.4); +} + +fn linearToSrgb(value: f32) u8 { + const v = std.math.clamp(value, 0.0, 1.0); + const encoded = if (v <= 0.0031308) v * 12.92 else 1.055 * std.math.pow(f32, v, 1.0 / 2.4) - 0.055; + return @intFromFloat(@round(encoded * 255.0)); +} + +fn blendPixel(dst: *[4]u8, src: *const [4]u8, mode: Blend, opacity: u8) void { + const opacity_f = @as(f32, @floatFromInt(opacity)) / 255.0; + const sa = (@as(f32, @floatFromInt(src[3])) / 255.0) * opacity_f; + const da = @as(f32, @floatFromInt(dst[3])) / 255.0; + if (mode == .source) { + dst[0] = src[0]; + dst[1] = src[1]; + dst[2] = src[2]; + dst[3] = @intFromFloat(@round(sa * 255.0)); + return; + } + + const source_first = mode == .source_over; + const out_a = if (source_first) sa + da * (1.0 - sa) else da + sa * (1.0 - da); + for (0..3) |channel| { + const sp = srgbToLinear(src[channel]) * sa; + const dp = srgbToLinear(dst[channel]) * da; + const out_p = if (source_first) sp + dp * (1.0 - sa) else dp + sp * (1.0 - da); + dst[channel] = if (out_a > 0.0) linearToSrgb(out_p / out_a) else 0; + } + dst[3] = @intFromFloat(@round(std.math.clamp(out_a, 0.0, 1.0) * 255.0)); +} + +pub fn composite( + allocator: Allocator, + base: *const Image, + overlay: *const Image, + left: i32, + top: i32, + mode: Blend, + opacity: u8, +) !*Image { + const output = try copyImage(allocator, base.pixels, base.metadata); + errdefer output.deinit(); + + const start_x: u32 = if (left < 0) @intCast(-@as(i64, left)) else 0; + const start_y: u32 = if (top < 0) @intCast(-@as(i64, top)) else 0; + const dest_x: u32 = if (left < 0) 0 else @intCast(left); + const dest_y: u32 = if (top < 0) 0 else @intCast(top); + if (start_x >= overlay.width() or start_y >= overlay.height() or dest_x >= base.width() or dest_y >= base.height()) return output; + const copy_width = @min(overlay.width() - start_x, base.width() - dest_x); + const copy_height = @min(overlay.height() - start_y, base.height() - dest_y); + + for (0..copy_height) |y| { + for (0..copy_width) |x| { + const dst_offset = pixelOffset(base.width(), dest_x + @as(u32, @intCast(x)), dest_y + @as(u32, @intCast(y))); + const src_offset = pixelOffset(overlay.width(), start_x + @as(u32, @intCast(x)), start_y + @as(u32, @intCast(y))); + const dst: *[4]u8 = @ptrCast(output.pixels[dst_offset .. dst_offset + 4].ptr); + const src: *const [4]u8 = @ptrCast(overlay.pixels[src_offset .. src_offset + 4].ptr); + blendPixel(dst, src, mode, opacity); + } + } + output.metadata.has_alpha = @intFromBool(pixelsHaveTransparency(output.pixels)); + return output; +} + +pub fn copyPixels(image: *const Image, destination: []u8, stride: u32, bgra: bool) Status { + const row_bytes = image.width() * 4; + if (stride < row_bytes) return .invalid_argument; + const preceding_rows = std.math.mul(u64, stride, image.height() - 1) catch return .invalid_argument; + const required = std.math.add(u64, preceding_rows, row_bytes) catch return .invalid_argument; + if (required > destination.len) return .output_too_small; + for (0..image.height()) |y| { + const src_offset = y * row_bytes; + const dst_offset = y * stride; + if (!bgra) { + @memcpy(destination[dst_offset .. dst_offset + row_bytes], image.pixels[src_offset .. src_offset + row_bytes]); + continue; + } + for (0..image.width()) |x| { + const src = src_offset + x * 4; + const dst = dst_offset + x * 4; + destination[dst + 0] = image.pixels[src + 2]; + destination[dst + 1] = image.pixels[src + 1]; + destination[dst + 2] = image.pixels[src + 0]; + destination[dst + 3] = image.pixels[src + 3]; + } + } + return .ok; +} + +test "Exif orientation parser handles little and big endian" { + const little = [_]u8{ 'I', 'I', 42, 0, 8, 0, 0, 0, 1, 0, 0x12, 0x01, 3, 0, 1, 0, 0, 0, 6, 0, 0, 0 }; + const big = [_]u8{ 'M', 'M', 0, 42, 0, 0, 0, 8, 0, 1, 0x01, 0x12, 0, 3, 0, 0, 0, 1, 0, 8, 0, 0 }; + try std.testing.expectEqual(@as(u8, 6), parseExifOrientation(&little)); + try std.testing.expectEqual(@as(u8, 8), parseExifOrientation(&big)); +} diff --git a/packages/core/src/zig/lib.zig b/packages/core/src/zig/lib.zig index ef2bda96c2..8bd45a79ec 100644 --- a/packages/core/src/zig/lib.zig +++ b/packages/core/src/zig/lib.zig @@ -23,6 +23,8 @@ const native_renderable = @import("native-renderable.zig"); const buffer_effects = @import("buffer-methods.zig"); const handles = @import("handles.zig"); const native_yoga = @import("yoga.zig"); +const native_image = @import("image.zig"); +const clipboard = @import("clipboard/host.zig"); pub const OptimizedBuffer = buffer.OptimizedBuffer; pub const CliRenderer = renderer.CliRenderer; @@ -90,6 +92,10 @@ fn acquireNativeRenderable(handle: NativeHandle) ?*native_renderable.NativeRende return handles.acquire(handle, .native_renderable, native_renderable.NativeRenderable); } +fn acquireImage(handle: NativeHandle) ?*native_image.Image { + return handles.acquire(handle, .image, native_image.Image); +} + fn emptyLineInfo(outPtr: *ExternalLineInfo) void { outPtr.* = .{ .start_cols_ptr = EMPTY_U32[0..].ptr, @@ -124,6 +130,7 @@ comptime { _ = native_audio; _ = native_renderable; _ = native_yoga; + _ = native_image; } export fn setLogCallback(callback: ?*const fn (level: u8, msgPtr: [*]const u8, msgLen: u32) callconv(.c) void) void { @@ -359,6 +366,133 @@ export fn destroyAudioEngine(engine_handle: NativeHandle) void { handles.finishDestroy(token.handle); } +export fn clipboardServiceCreate( + max_operations: u32, + max_provider_transfers: u32, + wayland_seat_pointer: ?[*]const u8, + wayland_seat_length: u32, +) NativeHandle { + return clipboard.createService( + globalAllocator, + max_operations, + max_provider_transfers, + wayland_seat_pointer, + wayland_seat_length, + ); +} + +export fn clipboardServiceBeginShutdown(service_handle: NativeHandle) u8 { + return @intFromEnum(clipboard.beginServiceShutdown(service_handle)); +} + +export fn clipboardServicePollShutdown(service_handle: NativeHandle) u8 { + return @intFromEnum(clipboard.pollServiceShutdown(service_handle)); +} + +export fn clipboardServiceDestroy(service_handle: NativeHandle) u8 { + return @intFromEnum(clipboard.destroyService(service_handle)); +} + +export fn clipboardServiceDrain(service_handle: NativeHandle) u8 { + return clipboard.drainService(service_handle); +} + +export fn clipboardReadOperationStart( + service_handle: NativeHandle, + request_pointer: ?[*]const u8, + request_length: u32, + selection: u8, + max_bytes: u32, + max_image_pixels: u32, + max_conversion_bytes: u32, + timeout_ms: u32, + out_operation_handle: ?*NativeHandle, +) u8 { + return @intFromEnum(clipboard.startReadOperation( + service_handle, + request_pointer, + request_length, + selection, + max_bytes, + max_image_pixels, + max_conversion_bytes, + timeout_ms, + out_operation_handle, + )); +} + +export fn clipboardWriteOperationStart( + service_handle: NativeHandle, + text_pointer: ?[*]const u8, + text_length: u32, + selection: u8, + timeout_ms: u32, + out_operation_handle: ?*NativeHandle, +) u8 { + return @intFromEnum(clipboard.startWriteOperation( + service_handle, + text_pointer, + text_length, + selection, + timeout_ms, + out_operation_handle, + )); +} + +export fn clipboardClearOperationStart( + service_handle: NativeHandle, + selection: u8, + timeout_ms: u32, + out_operation_handle: ?*NativeHandle, +) u8 { + return @intFromEnum(clipboard.startClearOperation( + service_handle, + selection, + timeout_ms, + out_operation_handle, + )); +} + +export fn clipboardOperationPoll(operation_handle: NativeHandle) u8 { + return @intFromEnum(clipboard.pollOperation(operation_handle)); +} + +export fn clipboardOperationCancel(operation_handle: NativeHandle) u8 { + return @intFromEnum(clipboard.cancelOperation(operation_handle)); +} + +export fn clipboardOperationResultMimeLength(operation_handle: NativeHandle, out_length: ?*u32) u8 { + return @intFromEnum(clipboard.resultMimeLength(operation_handle, out_length)); +} + +export fn clipboardOperationResultMimeCopy(operation_handle: NativeHandle, out_pointer: ?[*]u8, capacity: u32) u8 { + return @intFromEnum(clipboard.resultMimeCopy(operation_handle, out_pointer, capacity)); +} + +export fn clipboardOperationResultDataLength(operation_handle: NativeHandle, out_length: ?*u32) u8 { + return @intFromEnum(clipboard.resultDataLength(operation_handle, out_length)); +} + +export fn clipboardOperationResultDataCopy(operation_handle: NativeHandle, out_pointer: ?[*]u8, capacity: u32) u8 { + return @intFromEnum(clipboard.resultDataCopy(operation_handle, out_pointer, capacity)); +} + +export fn clipboardOperationResultErrorCode(operation_handle: NativeHandle, out_error_code: ?*u32) u8 { + return @intFromEnum(clipboard.resultErrorCode(operation_handle, out_error_code)); +} + +export fn clipboardOperationResultDiagnosticLength(operation_handle: NativeHandle, out_length: ?*u32) u8 { + return @intFromEnum(clipboard.resultDiagnosticLength(operation_handle, out_length)); +} + +export fn clipboardOperationResultDiagnosticCopy(operation_handle: NativeHandle, out_pointer: ?[*]u8, capacity: u32) u8 { + return @intFromEnum(clipboard.resultDiagnosticCopy(operation_handle, out_pointer, capacity)); +} + +export fn clipboardOperationDestroy(operation_handle: NativeHandle) u8 { + return @intFromEnum(clipboard.destroyOperation(operation_handle)); +} + export fn audioRefreshPlaybackDevices(engine_handle: NativeHandle) i32 { const object_ptr = acquireAudioEngine(engine_handle) orelse return native_audio.Status.err_invalid; return native_audio.refreshPlaybackDevices(object_ptr); @@ -960,6 +1094,7 @@ pub const ExternalCapabilities = extern struct { explicit_cursor_positioning: bool, remote: bool, multiplexer: u8, + image_protocol: u8, term_name_ptr: [*]const u8, term_name_len: usize, term_version_ptr: [*]const u8, @@ -999,6 +1134,7 @@ export fn getTerminalCapabilities(renderer_handle: NativeHandle, capsPtr: *Exter .explicit_cursor_positioning = caps.explicit_cursor_positioning, .remote = caps.remote, .multiplexer = @intFromEnum(term.multiplexer), + .image_protocol = @intFromEnum(term.image_protocol), .term_name_ptr = &term.term_info.name, .term_name_len = term.term_info.name_len, .term_version_ptr = &term.term_info.version, @@ -1330,6 +1466,45 @@ export fn bufferDrawSuperSampleBuffer(buffer_handle: NativeHandle, x: u32, y: u3 object_ptr.drawSuperSampleBuffer(x, y, pixelData, len, format, alignedBytesPerRow); } +pub const ExternalImageDrawOptions = extern struct { + x: i32, + y: i32, + width: u32, + height: u32, + pixel_width: u32, + pixel_height: u32, + source_x: u32, + source_y: u32, + source_width: u32, + source_height: u32, + protocol: u32, +}; + +export fn bufferDrawImage( + buffer_handle: NativeHandle, + image_handle: NativeHandle, + options: *const ExternalImageDrawOptions, +) u8 { + const buffer_ptr = acquireBuffer(buffer_handle) orelse return 0; + const image_ptr = acquireImage(image_handle) orelse return 0; + const protocol = std.meta.intToEnum(native_image.RenderProtocol, options.protocol) catch return 0; + return @intFromBool(buffer_ptr.drawImage( + image_ptr, + image_handle, + options.x, + options.y, + options.width, + options.height, + options.pixel_width, + options.pixel_height, + options.source_x, + options.source_y, + options.source_width, + options.source_height, + protocol, + ) catch false); +} + export fn linkAlloc(urlPtr: ?[*]const u8, urlLen: u32) u32 { const url = sliceFromPtrLen(urlPtr, urlLen); const link_pool = link.initGlobalLinkPool(globalArena); @@ -2766,6 +2941,211 @@ export fn syntaxStyleGetStyleCount(style_handle: NativeHandle) u32 { return @intCast(object_ptr.getStyleCount()); } +// Image functions +fn insertImage(image: *native_image.Image, out_handle: *NativeHandle) native_image.Status { + out_handle.* = handles.insert(.image, erasePtr(image)) catch { + image.deinit(); + return .out_of_memory; + }; + return .ok; +} + +export fn imageInfo(data_ptr: ?[*]const u8, data_len: u32, out_info: ?*native_image.Info) u32 { + const output = out_info orelse return @intFromEnum(native_image.Status.invalid_argument); + if (data_len == 0 or data_ptr == null) return @intFromEnum(native_image.Status.invalid_argument); + return @intFromEnum(native_image.inspect(globalAllocator, data_ptr.?[0..data_len], .{}, output)); +} + +export fn imageDecode(data_ptr: ?[*]const u8, data_len: u32, out_handle: ?*NativeHandle) u32 { + const output = out_handle orelse return @intFromEnum(native_image.Status.invalid_argument); + output.* = INVALID_HANDLE; + if (data_len == 0 or data_ptr == null) return @intFromEnum(native_image.Status.invalid_argument); + const image = native_image.decode(globalAllocator, data_ptr.?[0..data_len], .{}) catch |err| { + return @intFromEnum(native_image.statusFromError(err)); + }; + return @intFromEnum(insertImage(image, output)); +} + +export fn imageCreateFromRgba( + pixels_ptr: ?[*]const u8, + pixels_len: u64, + width: u32, + height: u32, + stride: u32, + out_handle: ?*NativeHandle, +) u32 { + const output = out_handle orelse return @intFromEnum(native_image.Status.invalid_argument); + output.* = INVALID_HANDLE; + if (pixels_len > std.math.maxInt(usize) or (pixels_len > 0 and pixels_ptr == null)) { + return @intFromEnum(native_image.Status.invalid_argument); + } + const pixels = if (pixels_len == 0) "" else pixels_ptr.?[0..@intCast(pixels_len)]; + const image = native_image.createFromRgba(globalAllocator, pixels, width, height, stride) catch |err| { + return @intFromEnum(native_image.statusFromError(err)); + }; + return @intFromEnum(insertImage(image, output)); +} + +export fn imageDestroy(image_handle: NativeHandle) void { + const token = handles.beginDestroy(image_handle, .image, native_image.Image) orelse return; + token.ptr.deinit(); + handles.finishDestroy(token.handle); +} + +export fn imageGetInfo(image_handle: NativeHandle, out_info: ?*native_image.Info) u32 { + const image = acquireImage(image_handle) orelse return @intFromEnum(native_image.Status.invalid_handle); + const output = out_info orelse return @intFromEnum(native_image.Status.invalid_argument); + output.* = image.info(); + return @intFromEnum(native_image.Status.ok); +} + +export fn imageGetPixelsPtr(image_handle: NativeHandle) ?[*]u8 { + const image = acquireImage(image_handle) orelse return null; + if (image.ref_count != 1) return null; + image.discardEncoded(); + // Callers receive mutable pixels, so opacity can no longer be proven. + image.metadata.has_alpha = 1; + return image.pixels.ptr; +} + +export fn imageClone(image_handle: NativeHandle, out_handle: ?*NativeHandle) u32 { + const image = acquireImage(image_handle) orelse return @intFromEnum(native_image.Status.invalid_handle); + const output = out_handle orelse return @intFromEnum(native_image.Status.invalid_argument); + output.* = INVALID_HANDLE; + const cloned = image.clone() catch |err| return @intFromEnum(native_image.statusFromError(err)); + return @intFromEnum(insertImage(cloned, output)); +} + +export fn imageCopyPixels( + image_handle: NativeHandle, + destination_ptr: ?[*]u8, + destination_len: u64, + stride: u32, + bgra: u8, +) u32 { + const image = acquireImage(image_handle) orelse return @intFromEnum(native_image.Status.invalid_handle); + if (destination_len > std.math.maxInt(usize) or destination_ptr == null or bgra > 1) { + return @intFromEnum(native_image.Status.invalid_argument); + } + const destination = destination_ptr.?[0..@intCast(destination_len)]; + return @intFromEnum(native_image.copyPixels(image, destination, stride, bgra == 1)); +} + +test "imageGetPixelsPtr aliases the image pixel allocation" { + const pixels = [_]u8{ 1, 2, 3, 4, 5, 6, 7, 8 }; + var handle: NativeHandle = INVALID_HANDLE; + try std.testing.expectEqual( + @as(u32, @intFromEnum(native_image.Status.ok)), + imageCreateFromRgba(&pixels, pixels.len, 2, 1, 8, &handle), + ); + defer imageDestroy(handle); + + const pointer = imageGetPixelsPtr(handle) orelse return error.TestUnexpectedResult; + try std.testing.expectEqualSlices(u8, &pixels, pointer[0..pixels.len]); + pointer[0] = 42; + + var copied: [pixels.len]u8 = undefined; + try std.testing.expectEqual( + @as(u32, @intFromEnum(native_image.Status.ok)), + imageCopyPixels(handle, &copied, copied.len, 8, 0), + ); + try std.testing.expectEqual(@as(u8, 42), copied[0]); +} + +test "imageGetPixelsPtr requires exclusive ownership and invalidates encoded state" { + const value = try native_image.createFromRgba(globalAllocator, &[_]u8{ 1, 2, 3, 255 }, 1, 1, 4); + value.encoded_png = try globalAllocator.dupe(u8, "encoded"); + var handle: NativeHandle = INVALID_HANDLE; + try std.testing.expectEqual(native_image.Status.ok, insertImage(value, &handle)); + defer imageDestroy(handle); + + value.retain(); + try std.testing.expectEqual(@as(?[*]u8, null), imageGetPixelsPtr(handle)); + value.deinit(); + + try std.testing.expect(imageGetPixelsPtr(handle) != null); + try std.testing.expectEqual(@as(?[]u8, null), value.encoded_png); + try std.testing.expectEqual(@as(u32, 1), value.metadata.has_alpha); +} + +export fn imageResize(image_handle: NativeHandle, width: u32, height: u32, filter: u32, out_handle: ?*NativeHandle) u32 { + const image = acquireImage(image_handle) orelse return @intFromEnum(native_image.Status.invalid_handle); + const output = out_handle orelse return @intFromEnum(native_image.Status.invalid_argument); + output.* = INVALID_HANDLE; + const resize_filter = std.meta.intToEnum(native_image.ResizeFilter, filter) catch return @intFromEnum(native_image.Status.invalid_argument); + const resized = native_image.resize(globalAllocator, image, width, height, resize_filter) catch |err| { + return @intFromEnum(native_image.statusFromError(err)); + }; + return @intFromEnum(insertImage(resized, output)); +} + +export fn imageExtract( + image_handle: NativeHandle, + left: u32, + top: u32, + width: u32, + height: u32, + out_handle: ?*NativeHandle, +) u32 { + const image = acquireImage(image_handle) orelse return @intFromEnum(native_image.Status.invalid_handle); + const output = out_handle orelse return @intFromEnum(native_image.Status.invalid_argument); + output.* = INVALID_HANDLE; + const extracted = native_image.extract(globalAllocator, image, left, top, width, height) catch |err| { + return @intFromEnum(native_image.statusFromError(err)); + }; + return @intFromEnum(insertImage(extracted, output)); +} + +export fn imageExtend( + image_handle: NativeHandle, + top: u32, + right: u32, + bottom: u32, + left: u32, + background_ptr: ?[*]const u8, + out_handle: ?*NativeHandle, +) u32 { + const image = acquireImage(image_handle) orelse return @intFromEnum(native_image.Status.invalid_handle); + const output = out_handle orelse return @intFromEnum(native_image.Status.invalid_argument); + output.* = INVALID_HANDLE; + const background = background_ptr orelse return @intFromEnum(native_image.Status.invalid_argument); + const extended = native_image.extend(globalAllocator, image, top, right, bottom, left, .{ + background[0], background[1], background[2], background[3], + }) catch |err| return @intFromEnum(native_image.statusFromError(err)); + return @intFromEnum(insertImage(extended, output)); +} + +export fn imageTransform(image_handle: NativeHandle, operation: u32, out_handle: ?*NativeHandle) u32 { + const image = acquireImage(image_handle) orelse return @intFromEnum(native_image.Status.invalid_handle); + const output = out_handle orelse return @intFromEnum(native_image.Status.invalid_argument); + output.* = INVALID_HANDLE; + const transform_operation = std.meta.intToEnum(native_image.Transform, operation) catch return @intFromEnum(native_image.Status.invalid_argument); + const transformed = native_image.transform(globalAllocator, image, transform_operation) catch |err| { + return @intFromEnum(native_image.statusFromError(err)); + }; + return @intFromEnum(insertImage(transformed, output)); +} + +export fn imageComposite( + base_handle: NativeHandle, + overlay_handle: NativeHandle, + left: i32, + top: i32, + blend: u32, + opacity: u8, + out_handle: ?*NativeHandle, +) u32 { + const base = acquireImage(base_handle) orelse return @intFromEnum(native_image.Status.invalid_handle); + const overlay = acquireImage(overlay_handle) orelse return @intFromEnum(native_image.Status.invalid_handle); + const output = out_handle orelse return @intFromEnum(native_image.Status.invalid_argument); + output.* = INVALID_HANDLE; + const blend_mode = std.meta.intToEnum(native_image.Blend, blend) catch return @intFromEnum(native_image.Status.invalid_argument); + const composited = native_image.composite(globalAllocator, base, overlay, left, top, blend_mode, opacity) catch |err| { + return @intFromEnum(native_image.statusFromError(err)); + }; + return @intFromEnum(insertImage(composited, output)); +} + // Unicode encoding API pub const EncodedChar = extern struct { diff --git a/packages/core/src/zig/native-span-feed.zig b/packages/core/src/zig/native-span-feed.zig index 772e67a348..fdeb8e1103 100644 --- a/packages/core/src/zig/native-span-feed.zig +++ b/packages/core/src/zig/native-span-feed.zig @@ -106,6 +106,11 @@ const SpanRing = struct { } } + fn ensureAdditionalCapacity(self: *SpanRing, stream: *Stream, additional: u32) StreamError!void { + const required = @as(u64, self.count()) + additional; + while (required > self.capacity) try self.grow(stream); + } + pub fn popMany(self: *SpanRing, out: []SpanInfo) u32 { const available = self.tail -% self.head; if (available == 0) return 0; @@ -299,6 +304,60 @@ pub const Stream = struct { } } + /// Publish one complete byte sequence or nothing. Renderer frames use this + /// so a capacity/allocation failure cannot expose a truncated ANSI sequence. + pub fn writeAtomic(self: *Stream, data: []const u8) StreamError!void { + if (self.closed) return StreamError.Invalid; + if (data.len == 0) return; + if (self.reserved_active or self.pending_len != 0) return StreamError.Busy; + + const chunk_size = @as(usize, self.options.chunk_size); + const required_chunks_usize = std.math.divCeil(usize, data.len, chunk_size) catch return StreamError.Invalid; + const required_chunks = std.math.cast(u32, required_chunks_usize) orelse return StreamError.NoSpace; + + var free_chunks: usize = 0; + for (0..self.chunks.items.len) |index| { + if (self.isChunkFree(index)) free_chunks += 1; + } + if (free_chunks < required_chunks_usize) { + if (self.options.growth_policy == @intFromEnum(GrowthPolicy.block)) return StreamError.NoSpace; + var missing = required_chunks_usize - free_chunks; + while (missing > 0) : (missing -= 1) try self.addChunkLocked(); + } + try self.span_ring.ensureAdditionalCapacity(self, required_chunks); + + var notify = false; + defer self.finish(notify, 0); + var source_offset: usize = 0; + var last_chunk_index: usize = self.current_chunk_index; + for (self.chunks.items, 0..) |chunk, index| { + if (!self.isChunkFree(index)) continue; + const len = @min(chunk_size, data.len - source_offset); + @memcpy(chunk.ptr[0..len], data[source_offset .. source_offset + len]); + const info: SpanInfo = .{ + .chunk_ptr = @intFromPtr(chunk.ptr), + .offset = 0, + .len = @intCast(len), + .chunk_index = @intCast(index), + .reserved = 0, + }; + // Capacity was secured before the first byte was copied. + self.span_ring.push(self, info, ¬ify) catch unreachable; + self.markSpanPending(info.chunk_index); + self.stats.spans_committed += 1; + source_offset += len; + last_chunk_index = index; + if (source_offset == data.len) break; + } + std.debug.assert(source_offset == data.len); + self.current_chunk_index = last_chunk_index; + self.write_offset = @as(usize, self.chunks.items[last_chunk_index].len); + self.pending_chunk_index = last_chunk_index; + self.pending_offset = self.write_offset; + self.pending_len = 0; + self.stats.bytes_written += @as(u64, @intCast(data.len)); + } + pub fn reserve(self: *Stream, min_len: u32) StreamError!ReserveInfo { if (self.closed) return StreamError.Invalid; return self.reserveLocked(min_len); diff --git a/packages/core/src/zig/renderer-output.zig b/packages/core/src/zig/renderer-output.zig index 24700d94b2..7f3f77f3d3 100644 --- a/packages/core/src/zig/renderer-output.zig +++ b/packages/core/src/zig/renderer-output.zig @@ -6,9 +6,9 @@ //! - `BufferedBackend`: writes into per-renderer A/B frame buffers, then //! flushes committed bytes to an injected `BufferedOutput`. //! -//! - `FeedBackend`: writes into a `NativeSpanFeed.Stream` whose chunks are -//! consumed from TypeScript and piped to a user-supplied Writable -//! (typically an SSH channel). +//! - `FeedBackend`: stages each complete frame, then atomically publishes it +//! to a `NativeSpanFeed.Stream` whose chunks are consumed from TypeScript +//! and piped to a user-supplied Writable (typically an SSH channel). //! //! The backend is a tagged union. `CliRenderer.render` performs exactly one //! `switch` on the backend using `inline else` to pick the right variant's @@ -566,6 +566,10 @@ pub const BufferedBackend = struct { } } + pub fn failFrame(self: *BufferedBackend) void { + self.frameWriteFailed = true; + } + /// Give spike memory back once frames have been consistently small again. /// Runs at frame start when the active buffer is exclusively owned by the /// producer: in threaded mode the render thread only ever reads the buffer @@ -733,10 +737,9 @@ pub const BufferedBackend = struct { } }; -/// Backend that writes to a `NativeSpanFeed.Stream`. The feed owns its own -/// chunk memory; we hold only a non-owning pointer. The TypeScript side is -/// responsible for allocating and destroying the feed; this backend simply -/// writes into it and commits on frame boundaries. +/// Backend that atomically publishes complete frames to a +/// `NativeSpanFeed.Stream`. The feed owns its chunk memory; the staging buffer +/// exists only to keep failed frames from exposing partial ANSI sequences. /// /// Feed writes are in-memory ring-buffer ops with no I/O, so threading adds /// synchronization cost without latency-hiding benefit. Backpressure is @@ -747,10 +750,10 @@ pub const BufferedBackend = struct { /// Zig tests that want to exercise the feed path should drain the feed directly. pub const FeedBackend = struct { feed: *NativeSpanFeed.Stream, + frameBytes: std.ArrayListUnmanaged(u8) = .{}, - /// Set when a frame's write to the feed fails. The backend never discards - /// feed bytes; failures are reported so the renderer can force a later full - /// repaint after the durable queue drains or accepts pending bytes. + /// Set when staging a frame fails. No bytes from a failed frame are + /// published; the renderer forces a later full repaint. frameWriteFailed: bool = false, lastWriteTimeUs: ?f64 = null, @@ -759,8 +762,9 @@ pub const FeedBackend = struct { return FeedBackend{ .feed = feed }; } - pub fn deinit(_: *FeedBackend) void { - // Feed memory is owned by the TypeScript side. Nothing to free here. + pub fn deinit(self: *FeedBackend) void { + // Feed memory is owned by the TypeScript side. + self.frameBytes.deinit(self.feed.allocator); } pub fn shouldSkipFrame(self: *FeedBackend) bool { @@ -803,7 +807,7 @@ pub const FeedBackend = struct { fn frameWrite(ctx: WriterCtx, data: []const u8) error{BufferFull}!usize { const self = ctx.backend; - self.feed.write(data) catch { + self.frameBytes.appendSlice(self.feed.allocator, data) catch { self.frameWriteFailed = true; return error.BufferFull; }; @@ -816,6 +820,11 @@ pub const FeedBackend = struct { pub fn beginFrame(self: *FeedBackend) void { self.frameWriteFailed = false; + self.frameBytes.clearRetainingCapacity(); + } + + pub fn failFrame(self: *FeedBackend) void { + self.frameWriteFailed = true; } pub fn endFrame(self: *FeedBackend) WriteStatus { @@ -823,12 +832,9 @@ pub const FeedBackend = struct { var status: WriteStatus = .ok; if (self.frameWriteFailed) { - if (self.feed.hasPendingBytes()) { - self.feed.commit() catch {}; - } status = .failed; } else { - self.feed.commit() catch { + self.feed.writeAtomic(self.frameBytes.items) catch { status = .failed; }; } @@ -839,21 +845,24 @@ pub const FeedBackend = struct { pub fn writeOut(self: *FeedBackend, data: []const u8) void { if (data.len == 0) return; - self.feed.write(data) catch return; - self.feed.commit() catch {}; + // High-level renderers use a growable, uncapped feed. Manually bounded + // low-level feeds intentionally get atomic best-effort control writes. + self.feed.writeAtomic(data) catch {}; } pub fn writeOutMultiple(self: *FeedBackend, data_slices: []const []const u8) void { var totalLen: usize = 0; - for (data_slices) |slice| totalLen += slice.len; + for (data_slices) |slice| totalLen = std.math.add(usize, totalLen, slice.len) catch return; if (totalLen == 0) return; - var wrote_any = false; + const data = self.feed.allocator.alloc(u8, totalLen) catch return; + defer self.feed.allocator.free(data); + var offset: usize = 0; for (data_slices) |slice| { - self.feed.write(slice) catch return; - wrote_any = true; + @memcpy(data[offset .. offset + slice.len], slice); + offset += slice.len; } - if (wrote_any) self.feed.commit() catch {}; + self.feed.writeAtomic(data) catch {}; } /// Write a debug dump placeholder. FeedBackend has no flat previous-frame diff --git a/packages/core/src/zig/renderer.zig b/packages/core/src/zig/renderer.zig index 5ffcbfdd38..185bd448aa 100644 --- a/packages/core/src/zig/renderer.zig +++ b/packages/core/src/zig/renderer.zig @@ -9,6 +9,8 @@ const Terminal = @import("terminal.zig"); const logger = @import("logger.zig"); const NativeSpanFeed = @import("native-span-feed.zig"); const output = @import("renderer-output.zig"); +const terminal_image = @import("terminal-image.zig"); +const native_image = @import("image.zig"); pub const RGBA = ansi.RGBA; pub const OptimizedBuffer = buf.OptimizedBuffer; @@ -109,11 +111,87 @@ const SplitFooterTransition = struct { } }; +const SplitFrameState = struct { + scrollback: split_scrollback.SplitScrollback, + render_offset: u32, + transition: SplitFooterTransition, + kitty_history_next_image_id: ?u32 = null, +}; + +const CommittedImage = struct { + placement_id: u32, + image_handle: u32, + x: i32, + y: i32, + width: u32, + height: u32, + pixel_width: u32, + pixel_height: u32, + source_x: u32, + source_y: u32, + source_width: u32, + source_height: u32, + opacity: u8, + protocol: ImageProtocol, + background_hash: u64 = 0, + lower_occupancy_hash: u64 = 0, +}; + +// Per-frame invalidation state for one placement. Sixel pixels live inside the +// covered cells, so a changed Sixel placement must clear and repaint its own +// rectangle; everything outside placements diffs normally. The resolved +// protocol is cached here so the per-cell render loop stays a table lookup. +const ImageDirty = struct { + clear: bool, + protocol: ImageProtocol, + background_hash: u64, + lower_occupancy_hash: u64, + propagated: bool = false, +}; + +const ImageProtocol = enum { fallback, sixel, kitty }; + +const SnapshotImageState = struct { + protocol: ImageProtocol = .fallback, + kitty_base: ?u32 = null, +}; + +const SixelCacheKey = struct { + image_handle: u32, + source_x: u32, + source_y: u32, + source_width: u32, + source_height: u32, + pixel_width: u32, + pixel_height: u32, + opacity: u8, + background_hash: u64, +}; + +const SixelCacheEntry = struct { + payload: []u8, + last_used: u64, +}; + +const SIXEL_CACHE_MAX_BYTES = 32 * 1024 * 1024; +const SIXEL_CACHE_MAX_ENTRIES = 256; + +// Cache the transformed, encoded payload: crop, size, opacity, and source identity determine its bytes; +// terminal position and DCS/tmux framing do not. Translucent placements also blend toward the covered +// cell backgrounds, so those payloads carry a background fingerprint. Byte and entry limits bound +// payload and metadata retention. An empty payload is cached for fully transparent placements. + pub const CliRenderer = struct { width: u32, height: u32, currentRenderBuffer: *OptimizedBuffer, nextRenderBuffer: *OptimizedBuffer, + currentImages: std.ArrayListUnmanaged(CommittedImage) = .{}, + pendingImages: std.ArrayListUnmanaged(CommittedImage) = .{}, + imageDirty: std.ArrayListUnmanaged(ImageDirty) = .{}, + imageIdSalt: u32, + kittyHistoryNextImageId: ?u32, + imageRenderFailed: bool = false, pool: *gp.GraphemePool, backgroundColor: RGBA, renderOffset: u32, @@ -129,6 +207,7 @@ pub const CliRenderer = struct { splitBatchActive: bool = false, splitBatchRedrawFooter: bool = false, splitBatchDeltaTime: f64 = 0, + splitBatchStartState: SplitFrameState = .{ .scrollback = .{}, .render_offset = 0, .transition = .{} }, pendingSplitFooterTransition: SplitFooterTransition = .{}, /// Output transport. Owned by the renderer; destroyed in `destroy()`. @@ -204,6 +283,7 @@ pub const CliRenderer = struct { lastCursorY: ?u32 = null, lastCursorVisible: ?bool = null, lastMousePointerStyle: Terminal.MousePointerStyle = .default, + mousePointerStateValid: bool = true, palette_rgba: [256]RGBA, default_fg_rgba: RGBA, default_bg_rgba: RGBA, @@ -211,6 +291,11 @@ pub const CliRenderer = struct { last_rendered_palette_epoch: ?u32 = null, force_full_repaint: bool = false, palette_index_cache: std.AutoHashMapUnmanaged(u64, u8) = .{}, + sixelCache: std.AutoHashMapUnmanaged(SixelCacheKey, SixelCacheEntry) = .{}, + sixelCacheBytes: usize = 0, + sixelCacheClock: u64 = 0, + sixelCacheHits: u64 = 0, + sixelCacheMisses: u64 = 0, pub const OutputTarget = union(enum) { stdout, @@ -291,11 +376,14 @@ pub const CliRenderer = struct { }; errdefer backend.deinit(); + const image_id_salt = 1 + @as(u32, @truncate(@as(u128, @bitCast(std.time.nanoTimestamp())) ^ @as(u128, @intFromPtr(self)))) % (std.math.maxInt(u32) - gp.IMAGE_ID_MASK - 1); self.* = .{ .width = width, .height = height, .currentRenderBuffer = currentBuffer, .nextRenderBuffer = nextBuffer, + .imageIdSalt = image_id_salt, + .kittyHistoryNextImageId = image_id_salt + gp.IMAGE_ID_MASK + 1, .pool = pool, .backgroundColor = ansi.rgbColor(0, 0, 0, 0), .renderOffset = 0, @@ -364,6 +452,9 @@ pub const CliRenderer = struct { self.currentRenderBuffer.deinit(); self.nextRenderBuffer.deinit(); + self.currentImages.deinit(self.allocator); + self.pendingImages.deinit(self.allocator); + self.imageDirty.deinit(self.allocator); // Free stat sample arrays self.statSamples.lastFrameTime.deinit(self.allocator); @@ -374,6 +465,9 @@ pub const CliRenderer = struct { self.statSamples.cellsUpdated.deinit(self.allocator); self.statSamples.frameCallbackTime.deinit(self.allocator); self.palette_index_cache.deinit(self.allocator); + var sixel_cache = self.sixelCache.iterator(); + while (sixel_cache.next()) |entry| self.allocator.free(entry.value_ptr.payload); + self.sixelCache.deinit(self.allocator); self.allocator.free(self.currentHitGrid); self.allocator.free(self.nextHitGrid); @@ -441,6 +535,23 @@ pub const CliRenderer = struct { pub fn performShutdownSequence(self: *CliRenderer) void { if (!self.terminalSetup) return; + if (self.hasCommittedProtocol(.kitty)) { + for (self.currentImages.items) |current| { + if (current.protocol != .kitty) continue; + var delete_buf: [128]u8 = undefined; + var delete_stream = std.io.fixedBufferStream(&delete_buf); + terminal_image.writeKittyDelete( + delete_stream.writer(), + self.kittyImageId(current.placement_id), + null, + true, + self.terminal.isInTmux(), + ) catch {}; + self.backend.writeOut(delete_stream.getWritten()); + } + } + self.currentImages.clearRetainingCapacity(); + // Build the shutdown ANSI sequence into a stack buffer, then emit. var shutdownBuf: [4096]u8 = undefined; var stream = std.io.fixedBufferStream(&shutdownBuf); @@ -734,19 +845,60 @@ pub const CliRenderer = struct { } fn finishSkippedFrame(self: *CliRenderer) RenderStatus { + self.pendingImages.clearRetainingCapacity(); self.clearSkippedFrameState(); return .skipped; } fn finishFailedFrame(self: *CliRenderer) RenderStatus { + self.pendingImages.clearRetainingCapacity(); + @memset(self.nextHitGrid, 0); self.force_full_repaint = true; + self.lastCursorStyleTag = null; + self.lastCursorBlinking = null; + self.lastCursorColorRGB = null; + self.lastCursorX = null; + self.lastCursorY = null; + self.lastCursorVisible = null; + self.mousePointerStateValid = false; return .failed; } + fn commitPendingHitGrid(self: *CliRenderer) void { + self.hitGridDirty = self.hitGridResizeInvalidated or !std.mem.eql(u32, self.currentHitGrid, self.nextHitGrid); + const previous = self.currentHitGrid; + self.currentHitGrid = self.nextHitGrid; + self.nextHitGrid = previous; + @memset(self.nextHitGrid, 0); + } + fn renderResult(self: *CliRenderer, status: RenderStatus) RenderResult { return .{ .renderOffset = self.renderOffset, .status = status }; } + fn splitFrameState(self: *const CliRenderer) SplitFrameState { + return .{ + .scrollback = self.splitScrollback, + .render_offset = self.renderOffset, + .transition = self.pendingSplitFooterTransition, + .kitty_history_next_image_id = self.kittyHistoryNextImageId, + }; + } + + fn restoreSplitFrameState(self: *CliRenderer, state: SplitFrameState) void { + self.splitScrollback = state.scrollback; + self.renderOffset = state.render_offset; + self.pendingSplitFooterTransition = state.transition; + self.kittyHistoryNextImageId = state.kitty_history_next_image_id; + } + + fn finishSplitBatch(self: *CliRenderer, published: bool) void { + if (!published) self.restoreSplitFrameState(self.splitBatchStartState); + self.splitBatchActive = false; + self.splitBatchRedrawFooter = false; + self.splitBatchDeltaTime = 0; + } + // One code path; backend selects writer type at compile time. pub fn render(self: *CliRenderer, force: bool) RenderStatus { // Backpressure: skipping must NOT update lastRenderTime so the next @@ -761,6 +913,8 @@ pub const CliRenderer = struct { self.lastRenderTime = now; self.renderDebugOverlay(); + const start_split_state = self.splitFrameState(); + self.imageRenderFailed = false; // `inline else` monomorphizes the writer type per variant — one // dispatch site, zero vtable cost. @@ -770,14 +924,19 @@ pub const CliRenderer = struct { b.beginFrame(); var w = b.writer(); self.prepareRenderFrameWithWriter(&w, force, false); + if (self.imageRenderFailed) b.failFrame(); write_status = b.endFrame(); }, } const status = renderStatusFromWrite(write_status); - if (status == .failed) { - return self.finishFailedFrame(); + if (status == .failed or self.imageRenderFailed) { + const result = self.finishFailedFrame(); + self.restoreSplitFrameState(start_split_state); + return result; } + self.commitPendingHitGrid(); + self.commitPendingImageState(); self.collectFrameStats(deltaTime); return status; @@ -891,10 +1050,12 @@ pub const CliRenderer = struct { self.lastRenderTime = now; self.renderDebugOverlay(); + const start_split_state = self.splitFrameState(); const status = self.prepareSplitFooterRepaintFrame(pinned_render_offset, force); var result_status = status; if (status == .failed) { result_status = self.finishFailedFrame(); + self.restoreSplitFrameState(start_split_state); } else { self.collectFrameStats(deltaTime); } @@ -904,7 +1065,7 @@ pub const CliRenderer = struct { pub fn commitSplitFooterSnapshotBatched( self: *CliRenderer, - snapshot: *const OptimizedBuffer, + snapshot: *OptimizedBuffer, row_columns: u32, start_on_new_line: bool, trailing_newline: bool, @@ -930,6 +1091,7 @@ pub const CliRenderer = struct { self.lastRenderTime = now; self.renderDebugOverlay(); + self.imageRenderFailed = false; var write_status: output.WriteStatus = .ok; var result_status: RenderStatus = .rendered; @@ -939,6 +1101,7 @@ pub const CliRenderer = struct { var w = b.writer(); beginRenderFrame(&w); var frame_started = true; + self.splitBatchStartState = self.splitFrameState(); self.applyPendingSplitFooterTransition(&w, &frame_started); // Track batch lifetime so subsequent calls can append into the same @@ -955,22 +1118,26 @@ pub const CliRenderer = struct { trailing_newline, pinned_render_offset, force, - ); + ) catch blk: { + self.imageRenderFailed = true; + break :blk false; + }; if (finalize_frame) { self.prepareRenderFrameWithWriter(&w, redraw_footer, true); + if (self.imageRenderFailed) b.failFrame(); write_status = b.endFrame(); const status = renderStatusFromWrite(write_status); - if (status == .failed) { + if (status == .failed or self.imageRenderFailed) { result_status = self.finishFailedFrame(); } else { + self.commitPendingHitGrid(); + self.commitPendingImageState(); result_status = status; self.collectFrameStats(deltaTime); } - self.splitBatchActive = false; - self.splitBatchRedrawFooter = false; - self.splitBatchDeltaTime = 0; + self.finishSplitBatch(result_status != .failed); } else { result_status = .rendered; self.splitBatchRedrawFooter = redraw_footer; @@ -1009,24 +1176,28 @@ pub const CliRenderer = struct { trailing_newline, pinned_render_offset, force, - ); + ) catch blk: { + self.imageRenderFailed = true; + break :blk false; + }; self.splitBatchRedrawFooter = self.splitBatchRedrawFooter or redraw_footer; if (finalize_frame) { self.prepareRenderFrameWithWriter(&w, self.splitBatchRedrawFooter, true); + if (self.imageRenderFailed) b.failFrame(); write_status = b.endFrame(); const status = renderStatusFromWrite(write_status); - if (status == .failed) { + if (status == .failed or self.imageRenderFailed) { result_status = self.finishFailedFrame(); } else { + self.commitPendingHitGrid(); + self.commitPendingImageState(); result_status = status; self.collectFrameStats(self.splitBatchDeltaTime); } - self.splitBatchActive = false; - self.splitBatchRedrawFooter = false; - self.splitBatchDeltaTime = 0; + self.finishSplitBatch(result_status != .failed); } else { result_status = .rendered; } @@ -1036,6 +1207,208 @@ pub const CliRenderer = struct { return self.renderResult(result_status); } + fn reserveKittyHistoryImageIds(self: *CliRenderer, count: usize) ?u32 { + if (count == 0 or count > std.math.maxInt(u32)) return null; + const first = self.kittyHistoryNextImageId orelse return null; + const last = std.math.add(u32, first, @as(u32, @intCast(count)) - 1) catch return null; + self.kittyHistoryNextImageId = if (last == std.math.maxInt(u32)) null else last + 1; + return first - 1; + } + + fn snapshotDirectImagesAddressable( + placements: []const OptimizedBuffer.ImagePlacement, + row_columns: u32, + start_on_new_line: bool, + previous_output_column: u32, + previous_output_offset: u32, + pinned_render_offset: u32, + ) bool { + if (pinned_render_offset == 0 or previous_output_offset != pinned_render_offset or + (!start_on_new_line and previous_output_column != 0)) return false; + for (placements) |placement| { + if (placement.x < 0 or placement.y < 0 or + @as(u64, @intCast(placement.x)) + placement.width > row_columns or + placement.height > previous_output_offset) return false; + } + return true; + } + + fn snapshotPlacementUncovered(snapshot: *const OptimizedBuffer, placement: OptimizedBuffer.ImagePlacement) bool { + var y: u32 = 0; + while (y < placement.height) : (y += 1) { + var x: u32 = 0; + while (x < placement.width) : (x += 1) { + const cell = snapshot.get( + @intCast(placement.x + @as(i32, @intCast(x))), + @intCast(placement.y + @as(i32, @intCast(y))), + ) orelse return false; + if (!gp.isImageChar(cell.char) or gp.imageIdFromChar(cell.char) != placement.placement_id) return false; + } + } + return true; + } + + fn prepareSnapshotImages( + self: *CliRenderer, + snapshot: *OptimizedBuffer, + row_columns: u32, + start_on_new_line: bool, + previous_output_column: u32, + previous_output_offset: u32, + pinned_render_offset: u32, + ) !SnapshotImageState { + const placements = snapshot.image_placements.items; + if (placements.len == 0) return .{}; + const protocol = self.nextPlacementProtocol(placements[0]); + for (placements, 0..) |placement, index| { + if (self.nextPlacementProtocol(placement) != protocol) { + snapshot.materializeImageFallbacks(); + return .{}; + } + for (placements[0..index]) |previous| { + if (placementsOverlap(previous, placement)) { + snapshot.materializeImageFallbacks(); + return .{}; + } + } + } + if (protocol == .sixel) { + if (!snapshotDirectImagesAddressable( + placements, + row_columns, + start_on_new_line, + previous_output_column, + previous_output_offset, + pinned_render_offset, + )) { + snapshot.materializeImageFallbacks(); + return .{}; + } + for (placements) |placement| { + if (!snapshotPlacementUncovered(snapshot, placement)) { + snapshot.materializeImageFallbacks(); + return .{}; + } + } + return .{ .protocol = .sixel }; + } + if (protocol != .kitty) { + snapshot.materializeImageFallbacks(); + return .{}; + } + if (!snapshotDirectImagesAddressable( + placements, + row_columns, + start_on_new_line, + previous_output_column, + previous_output_offset, + pinned_render_offset, + )) { + snapshot.materializeImageFallbacks(); + return .{}; + } + const base = self.reserveKittyHistoryImageIds(placements.len) orelse { + snapshot.materializeImageFallbacks(); + return .{}; + }; + return .{ .protocol = .kitty, .kitty_base = base }; + } + + fn writeSnapshotSixelImage( + self: *CliRenderer, + writer: anytype, + snapshot: *OptimizedBuffer, + placement: OptimizedBuffer.ImagePlacement, + ) !void { + const source = placement.image; + var prepared = source; + var prepared_owned = false; + defer if (prepared_owned) prepared.deinit(); + if (placement.source_x != 0 or placement.source_y != 0 or + placement.source_width != source.width() or placement.source_height != source.height()) + { + prepared = try native_image.extract( + self.allocator, + source, + placement.source_x, + placement.source_y, + placement.source_width, + placement.source_height, + ); + prepared_owned = true; + } + if (placement.pixel_width != prepared.width() or placement.pixel_height != prepared.height()) { + const resized = try native_image.resize(self.allocator, prepared, placement.pixel_width, placement.pixel_height, .area); + if (prepared_owned) prepared.deinit(); + prepared = resized; + prepared_owned = true; + } + if (placement.opacity < 255) { + if (!prepared_owned) { + prepared = try source.clone(); + prepared_owned = true; + } + dimSixelPixels(snapshot, placement, prepared); + } + var quantized = try terminal_image.quantizeSixel(self.allocator, prepared, 255); + defer quantized.deinit(); + if (quantized.palette_len == 0) return; + var payload: std.ArrayList(u8) = .empty; + defer payload.deinit(self.allocator); + try terminal_image.writeSixelIndexedPayload( + self.allocator, + payload.writer(self.allocator), + quantized.indices, + quantized.palette[0..quantized.palette_len], + prepared.width(), + prepared.height(), + ); + try terminal_image.writeSixelFramedPayload(writer, payload.items, self.terminal.isInTmux()); + } + + fn writeSnapshotKittyImage( + self: *CliRenderer, + writer: anytype, + placement: OptimizedBuffer.ImagePlacement, + image_id: u32, + ) !void { + const transmit = try self.kittyPlacementTransmit(placement); + defer if (transmit.owned) transmit.image.deinit(); + try terminal_image.writeKittyTransmit(writer, transmit.image, image_id, self.terminal.isInTmux()); + try terminal_image.writeKittyPlacementAtCursor( + writer, + image_id, + placement.placement_id, + placement.width, + placement.height, + -1_500_000_000 + @as(i32, @intCast(placement.placement_id)), + self.terminal.isInTmux(), + ); + } + + fn writeSnapshotNativeImagesForRow( + self: *CliRenderer, + writer: anytype, + snapshot: *OptimizedBuffer, + row: u32, + image_state: SnapshotImageState, + ) !void { + for (snapshot.image_placements.items) |placement| { + const last_row = @as(u32, @intCast(placement.y)) + placement.height - 1; + if (last_row != row) continue; + try writer.writeAll(ansi.ANSI.saveCursorState); + if (placement.height > 1) try writer.print("\x1b[{d}A", .{placement.height - 1}); + try writer.writeByte('\r'); + if (placement.x > 0) try writer.print("\x1b[{d}C", .{placement.x}); + if (image_state.protocol == .sixel) { + try self.writeSnapshotSixelImage(writer, snapshot, placement); + } else if (image_state.protocol == .kitty) { + try self.writeSnapshotKittyImage(writer, placement, image_state.kitty_base.? + placement.placement_id); + } + try writer.writeAll(ansi.ANSI.restoreCursorState); + } + } + /// Serialization for one split append payload. /// /// This function intentionally does not emit syncSet/syncReset or footer @@ -1051,10 +1424,11 @@ pub const CliRenderer = struct { fn writeSnapshotCommit( self: *CliRenderer, writer: anytype, - snapshot: *const OptimizedBuffer, + snapshot: *OptimizedBuffer, row_columns: u32, trailing_newline: bool, - ) void { + image_state: SnapshotImageState, + ) !void { var currentFg: ?RGBA = null; var currentBg: ?RGBA = null; var currentAttributes: ?u32 = null; @@ -1072,6 +1446,15 @@ pub const CliRenderer = struct { const x = @as(u32, @intCast(ux)); const cell = snapshot.get(x, y) orelse continue; + if (image_state.protocol == .kitty and gp.isImageChar(cell.char)) { + writer.writeAll(ansi.ANSI.reset) catch {}; + writer.writeByte(' ') catch {}; + currentFg = null; + currentBg = null; + currentAttributes = null; + continue; + } + const fgMatch = currentFg != null and buf.rgbaEqual(currentFg.?, cell.fg); const bgMatch = currentBg != null and buf.rgbaEqual(currentBg.?, cell.bg); const sameAttributes = fgMatch and bgMatch and currentAttributes != null and cell.attributes == currentAttributes.?; @@ -1107,6 +1490,14 @@ pub const CliRenderer = struct { if (cell.char == 0) { writer.writeByte(' ') catch {}; + } else if (gp.isImageChar(cell.char)) { + if (image_state.protocol == .sixel or image_state.protocol == .kitty) { + writer.writeByte(' ') catch {}; + } else { + const fallback = buf.quadrantChars[gp.imageFallbackFromChar(cell.char)]; + const len = std.unicode.utf8Encode(@intCast(fallback), &utf8Buf) catch unreachable; + writer.writeAll(utf8Buf[0..len]) catch {}; + } } else if (gp.isGraphemeChar(cell.char)) { const gid: u32 = gp.graphemeIdFromChar(cell.char); const bytes = self.pool.get(gid) catch { @@ -1142,6 +1533,9 @@ pub const CliRenderer = struct { writer.writeAll(ansi.ANSI.reset) catch {}; // Guarantee short rows do not leave stale content from prior frame data. writer.writeAll(ansi.ANSI.eraseToEndOfLine) catch {}; + if (image_state.protocol == .sixel or image_state.protocol == .kitty) { + try self.writeSnapshotNativeImagesForRow(writer, snapshot, y, image_state); + } currentFg = null; currentBg = null; currentAttributes = null; @@ -1168,18 +1562,26 @@ pub const CliRenderer = struct { fn appendSplitFooterSnapshotCommit( self: *CliRenderer, writer: anytype, - snapshot: *const OptimizedBuffer, + snapshot: *OptimizedBuffer, row_columns: u32, start_on_new_line: bool, trailing_newline: bool, pinned_render_offset: u32, force: bool, - ) bool { + ) !bool { const previousSurfaceOffset = self.renderOffset; const previousOutputOffset = self.splitOutputOffset(previousSurfaceOffset); const previousOutputColumn = self.splitScrollback.tail_column; const snapshot_has_content = snapshot.width > 0 and snapshot.height > 0; const normalized_row_columns = @min(row_columns, snapshot.width); + const kitty_history_state = try self.prepareSnapshotImages( + snapshot, + normalized_row_columns, + start_on_new_line, + previousOutputColumn, + previousOutputOffset, + pinned_render_offset, + ); const starts_mid_line = previousOutputColumn > 0 and start_on_new_line; const starts_wrapped_line = previousOutputColumn >= self.width; const previousFooterTopLine: u32 = @max(previousSurfaceOffset + 1, @as(u32, 1)); @@ -1206,9 +1608,6 @@ pub const CliRenderer = struct { const next_output_offset = self.splitScrollback.renderOffset(pinned_render_offset); const next_render_offset = self.clampSplitSurfaceOffset(previousSurfaceOffset, pinned_render_offset); const targetFooterTopLine: u32 = @max(next_render_offset + 1, @as(u32, 1)); - // Footer redraw is only needed when offset changes (settling/pinning) or - // when an explicit force was requested by the caller. - const redraw_footer = force or previousSurfaceOffset != next_render_offset; // When split scrollback is settled at the pinned boundary, newlines/wraps from // appended output must scroll only the upper pane. Without a temporary DECSTBM // region, terminals advance into the footer rows and overwrite them in place. @@ -1216,6 +1615,11 @@ pub const CliRenderer = struct { pinned_render_offset > 0 and next_render_offset == pinned_render_offset and next_output_offset == next_render_offset; + // DECSTBM protects footer cells, but terminals can still scroll native + // graphics placements. Repaint those placements after every pinned append. + const repaint_native_images = use_bounded_scroll_region and + (self.hasCommittedProtocol(.kitty) or self.hasCommittedProtocol(.sixel)); + const redraw_footer = force or previousSurfaceOffset != next_render_offset or repaint_native_images; if (snapshot_has_content or force) { if (snapshot_has_content) { @@ -1249,7 +1653,7 @@ pub const CliRenderer = struct { } // Serialize payload rows at current output cursor. - self.writeSnapshotCommit(writer, snapshot, normalized_row_columns, trailing_newline); + try self.writeSnapshotCommit(writer, snapshot, normalized_row_columns, trailing_newline, kitty_history_state); if (use_bounded_scroll_region) { // Restore default full-height scroll region for regular repaint @@ -1291,6 +1695,7 @@ pub const CliRenderer = struct { const redraw_footer = force or previousRenderOffset != next_render_offset; self.renderOffset = next_render_offset; + self.imageRenderFailed = false; // Do not pre-start sync frame here. prepareRenderFrameWithWriter now lazily starts // frame output only when something actually changes; this prevents no-op // repaint ticks from emitting hide/show cursor and sync envelopes. @@ -1300,10 +1705,15 @@ pub const CliRenderer = struct { b.beginFrame(); var w = b.writer(); self.prepareRenderFrameWithWriter(&w, redraw_footer, false); + if (self.imageRenderFailed) b.failFrame(); write_status = b.endFrame(); }, } - return renderStatusFromWrite(write_status); + const status = renderStatusFromWrite(write_status); + if (status == .failed or self.imageRenderFailed) return self.finishFailedFrame(); + self.commitPendingHitGrid(); + self.commitPendingImageState(); + return status; } pub fn getNextBuffer(self: *CliRenderer) *OptimizedBuffer { @@ -1314,6 +1724,556 @@ pub const CliRenderer = struct { return self.currentRenderBuffer; } + fn imageStateChanged(self: *CliRenderer) bool { + const next = self.nextRenderBuffer.image_placements.items; + if (next.len != self.currentImages.items.len) return true; + for (next, self.currentImages.items, 0..) |a, b, index| { + if (a.placement_id != b.placement_id or a.image_handle != b.image_handle or a.x != b.x or a.y != b.y or a.width != b.width or a.height != b.height or + a.pixel_width != b.pixel_width or a.pixel_height != b.pixel_height) return true; + if (a.source_x != b.source_x or a.source_y != b.source_y or a.source_width != b.source_width or a.source_height != b.source_height or a.opacity != b.opacity) return true; + if (self.nextPlacementProtocol(a) != b.protocol) return true; + if (index < self.imageDirty.items.len and self.imageDirty.items[index].background_hash != b.background_hash) return true; + if (index < self.imageDirty.items.len and self.imageDirty.items[index].lower_occupancy_hash != b.lower_occupancy_hash) return true; + } + return false; + } + + fn resolveImageProtocol(self: *CliRenderer, requested: native_image.RenderProtocol) ImageProtocol { + const configured = if (requested == .auto) self.terminal.image_protocol else switch (requested) { + .auto => unreachable, + .kitty => Terminal.ImageProtocol.kitty, + .sixel => Terminal.ImageProtocol.sixel, + .blocks => Terminal.ImageProtocol.blocks, + }; + switch (configured) { + .kitty => return .kitty, + .sixel => return .sixel, + .blocks => return .fallback, + .auto => {}, + } + // Multiplexers need pane-aware placeholder/native graphics handling. + if (self.terminal.isInTmux()) return .fallback; + const caps = self.terminal.getCapabilities(); + if (caps.kitty_graphics) return .kitty; + if (caps.sixel) return .sixel; + return .fallback; + } + + fn hasCommittedProtocol(self: *CliRenderer, protocol: ImageProtocol) bool { + for (self.currentImages.items) |image| if (image.protocol == protocol) return true; + return false; + } + + fn hasNextProtocol(self: *CliRenderer, protocol: ImageProtocol) bool { + for (self.nextRenderBuffer.image_placements.items) |placement| { + if (self.nextPlacementProtocol(placement) == protocol) return true; + } + return false; + } + + fn nextPlacementProtocol(self: *CliRenderer, placement: OptimizedBuffer.ImagePlacement) ImageProtocol { + const protocol = self.resolveImageProtocol(placement.protocol); + if (protocol == .sixel and (placement.pixel_width == 0 or placement.pixel_height == 0)) return .fallback; + return protocol; + } + + // Existing overlay cells are repainted by normal cell diffing. The Sixel + // image only needs retransmission when an overlay disappears and exposes it. + fn placementExposed(self: *CliRenderer, placement: OptimizedBuffer.ImagePlacement) bool { + var py: u32 = 0; + while (py < placement.height) : (py += 1) { + const y = placement.y + @as(i32, @intCast(py)); + if (y < 0 or y >= self.height) continue; + var px: u32 = 0; + while (px < placement.width) : (px += 1) { + const x = placement.x + @as(i32, @intCast(px)); + if (x < 0 or x >= self.width) continue; + const current = self.currentRenderBuffer.get(@intCast(x), @intCast(y)) orelse continue; + const next = self.nextRenderBuffer.get(@intCast(x), @intCast(y)) orelse continue; + if (gp.isImageChar(next.char) and gp.imageIdFromChar(next.char) == placement.placement_id and + (!gp.isImageChar(current.char) or gp.imageIdFromChar(current.char) != placement.placement_id)) return true; + } + } + return false; + } + + fn placementNeedsClear( + self: *CliRenderer, + placement: OptimizedBuffer.ImagePlacement, + protocol: ImageProtocol, + background_hash: u64, + lower_occupancy_hash: u64, + forced: bool, + ) bool { + // Fallback placements materialize into ordinary cells; diffing covers them. + if (protocol == .fallback) return false; + if (forced) return true; + const committed = self.currentImageForPlacement(placement.placement_id) orelse return true; + if (committed.protocol != protocol or committed.x != placement.x or committed.y != placement.y or + committed.width != placement.width or committed.height != placement.height or + committed.pixel_width != placement.pixel_width or committed.pixel_height != placement.pixel_height or + committed.source_x != placement.source_x or committed.source_y != placement.source_y or + committed.source_width != placement.source_width or committed.source_height != placement.source_height or + committed.opacity != placement.opacity) return true; + // Kitty replaces image data server side, so content changes do not + // require clearing cells. Sixel pixels are the cells, so content and + // blended-background changes repaint the rectangle. + if (protocol == .sixel and (committed.image_handle != placement.image_handle or + committed.background_hash != background_hash or + committed.lower_occupancy_hash != lower_occupancy_hash)) return true; + return protocol == .sixel and self.placementExposed(placement); + } + + fn currentImageForPlacement(self: *const CliRenderer, placement_id: u32) ?CommittedImage { + if (placement_id == 0 or placement_id > self.currentImages.items.len) return null; + const committed = self.currentImages.items[placement_id - 1]; + return if (committed.placement_id == placement_id) committed else null; + } + + fn placementsOverlap(a: OptimizedBuffer.ImagePlacement, b: OptimizedBuffer.ImagePlacement) bool { + const a_left: i64 = a.x; + const a_top: i64 = a.y; + const b_left: i64 = b.x; + const b_top: i64 = b.y; + return a_left < b_left + b.width and b_left < a_left + a.width and + a_top < b_top + b.height and b_top < a_top + a.height; + } + + fn computeImageDirtyFlags(self: *CliRenderer, forced: bool) void { + self.imageDirty.clearRetainingCapacity(); + const placements = self.nextRenderBuffer.image_placements.items; + self.imageDirty.ensureTotalCapacity(self.allocator, placements.len) catch { + self.imageRenderFailed = true; + self.force_full_repaint = true; + return; + }; + for (placements) |placement| { + const protocol = self.nextPlacementProtocol(placement); + const background_hash = if (protocol == .sixel and placement.opacity < 255) + self.placementBackgroundHash(placement) + else + 0; + const lower_occupancy_hash = if (protocol == .sixel and placement.image.metadata.has_alpha != 0) + self.placementLowerOccupancyHash(placement) + else + 0; + self.imageDirty.appendAssumeCapacity(.{ + .clear = self.placementNeedsClear(placement, protocol, background_hash, lower_occupancy_hash, forced), + .protocol = protocol, + .background_hash = background_hash, + .lower_occupancy_hash = lower_occupancy_hash, + }); + } + while (true) { + var dirty_index: ?usize = null; + for (self.imageDirty.items, 0..) |state, index| { + if (state.protocol == .sixel and state.clear and !state.propagated) { + dirty_index = index; + break; + } + } + const index = dirty_index orelse break; + self.imageDirty.items[index].propagated = true; + for (placements, 0..) |overlap, overlap_index| { + if (index == overlap_index) continue; + const overlap_state = &self.imageDirty.items[overlap_index]; + if (overlap_state.protocol == .sixel and placementsOverlap(placements[index], overlap)) overlap_state.clear = true; + } + } + } + + fn placementDirty(self: *const CliRenderer, placement_id: u32) bool { + if (placement_id == 0 or placement_id > self.imageDirty.items.len) return true; + return self.imageDirty.items[placement_id - 1].clear; + } + + fn placementFrameState(self: *const CliRenderer, char: u32) ?ImageDirty { + const id = gp.imageIdFromChar(char); + if (id == 0 or id > self.imageDirty.items.len) return null; + return self.imageDirty.items[id - 1]; + } + + inline fn dirtyImageChar(items: []const ImageDirty, char: u32) bool { + const id = gp.imageIdFromChar(char); + if (id == 0 or id > items.len) return false; + const state = &items[id - 1]; + return state.clear and state.protocol != .fallback; + } + + fn materializeFallbackImages(self: *CliRenderer) void { + for (self.nextRenderBuffer.image_placements.items) |placement| { + if (self.nextPlacementProtocol(placement) == .fallback) { + self.nextRenderBuffer.materializeImageFallback(placement.placement_id); + } + } + } + + fn stageImageState(self: *CliRenderer) void { + self.pendingImages.clearRetainingCapacity(); + std.debug.assert(self.pendingImages.capacity >= self.nextRenderBuffer.image_placements.items.len); + for (self.nextRenderBuffer.image_placements.items, 0..) |placement, index| { + self.pendingImages.appendAssumeCapacity(.{ + .image_handle = placement.image_handle, + .placement_id = placement.placement_id, + .x = placement.x, + .y = placement.y, + .width = placement.width, + .height = placement.height, + .pixel_width = placement.pixel_width, + .pixel_height = placement.pixel_height, + .source_x = placement.source_x, + .source_y = placement.source_y, + .source_width = placement.source_width, + .source_height = placement.source_height, + .opacity = placement.opacity, + .protocol = self.nextPlacementProtocol(placement), + .background_hash = if (index < self.imageDirty.items.len) self.imageDirty.items[index].background_hash else 0, + .lower_occupancy_hash = if (index < self.imageDirty.items.len) self.imageDirty.items[index].lower_occupancy_hash else 0, + }); + } + } + + fn commitPendingImageState(self: *CliRenderer) void { + if (self.imageRenderFailed) { + self.pendingImages.clearRetainingCapacity(); + return; + } + if (self.currentImages.items.len == 0 and self.pendingImages.items.len == 0) return; + std.mem.swap(std.ArrayListUnmanaged(CommittedImage), &self.currentImages, &self.pendingImages); + self.pendingImages.clearRetainingCapacity(); + } + + fn kittyImageId(self: *CliRenderer, placement_id: u32) u32 { + return self.imageIdSalt + placement_id; + } + + fn hasLowerImageAtCell(self: *CliRenderer, placement: OptimizedBuffer.ImagePlacement, protocol: ImageProtocol, x: i32, y: i32) bool { + for (self.nextRenderBuffer.image_placements.items) |candidate| { + if (candidate.placement_id >= placement.placement_id) break; + if (self.nextPlacementProtocol(candidate) != protocol) continue; + if (x >= candidate.x and y >= candidate.y and + @as(i64, x) < @as(i64, candidate.x) + candidate.width and + @as(i64, y) < @as(i64, candidate.y) + candidate.height) return true; + } + return false; + } + + fn applyAlphaOpacity(target: *native_image.Image, opacity: u8) void { + target.discardEncoded(); + var index: usize = 3; + while (index < target.pixels.len) : (index += 4) { + target.pixels[index] = @intCast((@as(u16, target.pixels[index]) * opacity + 127) / 255); + } + target.metadata.has_alpha = 1; + } + + fn imageWithOpacity(source: *const native_image.Image, opacity: u8) !?*native_image.Image { + if (opacity == 255) return null; + const copy = try source.clone(); + applyAlphaOpacity(copy, opacity); + return copy; + } + + // Large stills carry far more pixels than the placement can show. With a + // known pixel size and at least 4x the area, transmit a downscaled copy; + // the terminal scales the remainder. PNG passthrough is skipped for these + // because the raw downscaled payload is smaller than the original file. + fn kittyDownscaleAppliesTo(source_width: u32, source_height: u32, pixel_width: u32, pixel_height: u32) bool { + const pixel_area = @as(u64, pixel_width) * pixel_height; + const source_area = @as(u64, source_width) * source_height; + return pixel_area > 0 and source_area >= pixel_area * 4; + } + + fn kittyDownscaleApplies(placement: OptimizedBuffer.ImagePlacement) bool { + return kittyDownscaleAppliesTo(placement.source_width, placement.source_height, placement.pixel_width, placement.pixel_height); + } + + fn kittyCropApplies(placement: OptimizedBuffer.ImagePlacement) bool { + return placement.source_x != 0 or placement.source_y != 0 or + placement.source_width != placement.image.width() or placement.source_height != placement.image.height(); + } + + const KittyTransmit = struct { + image: *native_image.Image, + owned: bool, + }; + + fn kittyPlacementTransmit(self: *CliRenderer, placement: OptimizedBuffer.ImagePlacement) !KittyTransmit { + const source = placement.image; + const downscaled = kittyDownscaleApplies(placement); + if (downscaled or kittyCropApplies(placement)) { + const cropped = try native_image.extract( + self.allocator, + source, + placement.source_x, + placement.source_y, + placement.source_width, + placement.source_height, + ); + if (downscaled) { + defer cropped.deinit(); + const resized = try native_image.resize(self.allocator, cropped, placement.pixel_width, placement.pixel_height, .area); + if (placement.opacity < 255) applyAlphaOpacity(resized, placement.opacity); + return .{ .image = resized, .owned = true }; + } + if (placement.opacity < 255) applyAlphaOpacity(cropped, placement.opacity); + return .{ .image = cropped, .owned = true }; + } + const opacity_image = try imageWithOpacity(source, placement.opacity); + return .{ .image = opacity_image orelse source, .owned = opacity_image != null }; + } + + fn writeKittyImages(self: *CliRenderer, writer: anytype, force_place: bool) !void { + const tmux = self.terminal.isInTmux(); + const next = self.nextRenderBuffer.image_placements.items; + for (self.currentImages.items) |current| { + if (current.protocol != .kitty) continue; + const placement_found = if (current.placement_id > 0 and current.placement_id <= next.len) blk: { + const placement = next[current.placement_id - 1]; + break :blk placement.placement_id == current.placement_id and placement.image_handle == current.image_handle and + self.nextPlacementProtocol(placement) == .kitty; + } else false; + if (!placement_found) try terminal_image.writeKittyDelete( + writer, + self.kittyImageId(current.placement_id), + null, + true, + tmux, + ); + } + for (next) |placement| { + if (self.nextPlacementProtocol(placement) != .kitty) continue; + const previous = if (self.currentImageForPlacement(placement.placement_id)) |current| + if (current.protocol == .kitty and current.image_handle == placement.image_handle) current else null + else + null; + const image_id = self.kittyImageId(placement.placement_id); + const downscaled = kittyDownscaleApplies(placement); + const retransmit = if (previous) |committed| blk: { + const previous_downscaled = kittyDownscaleAppliesTo( + committed.source_width, + committed.source_height, + committed.pixel_width, + committed.pixel_height, + ); + const source_changed = committed.source_x != placement.source_x or committed.source_y != placement.source_y or + committed.source_width != placement.source_width or committed.source_height != placement.source_height; + break :blk committed.opacity != placement.opacity or source_changed or previous_downscaled != downscaled or + (downscaled and (committed.pixel_width != placement.pixel_width or committed.pixel_height != placement.pixel_height)); + } else false; + if (previous == null or retransmit) { + if (retransmit) try terminal_image.writeKittyDelete(writer, image_id, null, true, tmux); + const transmit = try self.kittyPlacementTransmit(placement); + defer if (transmit.owned) transmit.image.deinit(); + try terminal_image.writeKittyTransmit(writer, transmit.image, image_id, tmux); + } else if (force_place or previous.?.x != placement.x or previous.?.y != placement.y or previous.?.width != placement.width or previous.?.height != placement.height or + previous.?.source_x != placement.source_x or previous.?.source_y != placement.source_y or previous.?.source_width != placement.source_width or + previous.?.source_height != placement.source_height) + { + try terminal_image.writeKittyDelete(writer, image_id, placement.placement_id, false, tmux); + } else continue; + if (placement.x < 0 or placement.y < 0) continue; + const normalized = downscaled or kittyCropApplies(placement); + try terminal_image.writeKittyPlacement( + writer, + image_id, + placement.placement_id, + @intCast(placement.x), + @intCast(placement.y + @as(i32, @intCast(self.renderOffset))), + placement.width, + placement.height, + if (normalized) 0 else placement.source_x, + if (normalized) 0 else placement.source_y, + if (downscaled) placement.pixel_width else placement.source_width, + if (downscaled) placement.pixel_height else placement.source_height, + -1_500_000_000 + @as(i32, @intCast(placement.placement_id)), + tmux, + ); + } + } + + fn writeSixelImages(self: *CliRenderer, writer: anytype) !void { + for (self.nextRenderBuffer.image_placements.items) |placement| { + if (self.nextPlacementProtocol(placement) != .sixel or !self.placementDirty(placement.placement_id)) continue; + if (placement.pixel_width == 0 or placement.pixel_height == 0 or placement.x < 0 or placement.y < 0) continue; + const cache_key = SixelCacheKey{ + .image_handle = placement.image_handle, + .source_x = placement.source_x, + .source_y = placement.source_y, + .source_width = placement.source_width, + .source_height = placement.source_height, + .pixel_width = placement.pixel_width, + .pixel_height = placement.pixel_height, + .opacity = placement.opacity, + .background_hash = if (placement.placement_id <= self.imageDirty.items.len) + self.imageDirty.items[placement.placement_id - 1].background_hash + else + 0, + }; + self.advanceSixelCacheClock(); + if (self.sixelCache.getPtr(cache_key)) |cached| { + cached.last_used = self.sixelCacheClock; + self.sixelCacheHits += 1; + if (cached.payload.len == 0) continue; + try ansi.ANSI.moveToOutput(writer, @intCast(placement.x + 1), @intCast(placement.y + 1 + @as(i32, @intCast(self.renderOffset)))); + try terminal_image.writeSixelFramedPayload(writer, cached.payload, self.terminal.isInTmux()); + continue; + } + self.sixelCacheMisses += 1; + const source = placement.image; + var prepared = source; + var prepared_owned = false; + defer if (prepared_owned) prepared.deinit(); + if (placement.source_x != 0 or placement.source_y != 0 or + placement.source_width != source.width() or placement.source_height != source.height()) + { + prepared = try native_image.extract( + self.allocator, + source, + placement.source_x, + placement.source_y, + placement.source_width, + placement.source_height, + ); + prepared_owned = true; + } + if (placement.pixel_width != prepared.width() or placement.pixel_height != prepared.height()) { + const resized = try native_image.resize(self.allocator, prepared, placement.pixel_width, placement.pixel_height, .area); + if (prepared_owned) prepared.deinit(); + prepared = resized; + prepared_owned = true; + } + if (placement.opacity < 255) { + if (!prepared_owned) { + prepared = try source.clone(); + prepared_owned = true; + } + dimSixelPixels(self.nextRenderBuffer, placement, prepared); + } + var quantized = try terminal_image.quantizeSixel(self.allocator, prepared, 255); + defer quantized.deinit(); + var payload: std.ArrayList(u8) = .empty; + defer payload.deinit(self.allocator); + if (quantized.palette_len > 0) { + try terminal_image.writeSixelIndexedPayload( + self.allocator, + payload.writer(self.allocator), + quantized.indices, + quantized.palette[0..quantized.palette_len], + prepared.width(), + prepared.height(), + ); + try ansi.ANSI.moveToOutput(writer, @intCast(placement.x + 1), @intCast(placement.y + 1 + @as(i32, @intCast(self.renderOffset)))); + try terminal_image.writeSixelFramedPayload(writer, payload.items, self.terminal.isInTmux()); + } + self.cacheSixelPayload(cache_key, &payload); + } + } + + // Sixel cannot composite in the terminal, so placement opacity blends pixel + // colors toward the covered cell backgrounds (composited over black for + // non-opaque backgrounds). Image alpha is left untouched: holes stay holes + // and the encoder's visibility threshold keeps applying to the image alpha. + fn dimSixelPixels( + source_buffer: *OptimizedBuffer, + placement: OptimizedBuffer.ImagePlacement, + resized: *native_image.Image, + ) void { + const opacity: u32 = placement.opacity; + const inverse: u32 = 255 - opacity; + var py: u32 = 0; + while (py < resized.height()) : (py += 1) { + const cell_y = placement.y + @as(i32, @intCast((@as(u64, py) * placement.height) / placement.pixel_height)); + var px: u32 = 0; + while (px < resized.width()) : (px += 1) { + const cell_x = placement.x + @as(i32, @intCast((@as(u64, px) * placement.width) / placement.pixel_width)); + const cell = if (cell_x >= 0 and cell_y >= 0 and + cell_x < @as(i32, @intCast(source_buffer.width)) and cell_y < @as(i32, @intCast(source_buffer.height))) + source_buffer.get(@intCast(cell_x), @intCast(cell_y)) + else + null; + const bg = if (cell) |value| value.bg else ansi.rgbColor(0, 0, 0, 0); + const bg_alpha: u32 = ansi.alpha(bg); + const bg_channels = [3]u32{ ansi.red(bg), ansi.green(bg), ansi.blue(bg) }; + const offset = (@as(usize, py) * resized.width() + px) * 4; + inline for (0..3) |channel| { + const bg_effective = (bg_channels[channel] * bg_alpha + 127) / 255; + const value: u32 = resized.pixels[offset + channel]; + resized.pixels[offset + channel] = @intCast((value * opacity + bg_effective * inverse + 127) / 255); + } + } + } + } + + fn placementBackgroundHash(self: *CliRenderer, placement: OptimizedBuffer.ImagePlacement) u64 { + var hasher = std.hash.Wyhash.init(0x6f70656e_74756921); + var cy: u32 = 0; + while (cy < placement.height) : (cy += 1) { + const y = placement.y + @as(i32, @intCast(cy)); + if (y < 0 or y >= self.height) continue; + var cx: u32 = 0; + while (cx < placement.width) : (cx += 1) { + const x = placement.x + @as(i32, @intCast(cx)); + if (x < 0 or x >= self.width) continue; + const cell = self.nextRenderBuffer.get(@intCast(x), @intCast(y)) orelse continue; + hasher.update(std.mem.asBytes(&cell.bg)); + } + } + return hasher.final(); + } + + fn placementLowerOccupancyHash(self: *CliRenderer, placement: OptimizedBuffer.ImagePlacement) u64 { + var hasher = std.hash.Wyhash.init(0x696d6167_652d6c6f); + var cy: u32 = 0; + while (cy < placement.height) : (cy += 1) { + const y = placement.y + @as(i32, @intCast(cy)); + if (y < 0 or y >= self.height) continue; + var cx: u32 = 0; + while (cx < placement.width) : (cx += 1) { + const x = placement.x + @as(i32, @intCast(cx)); + if (x < 0 or x >= self.width) continue; + const lower: u8 = @intFromBool(self.hasLowerImageAtCell(placement, .sixel, x, y)); + hasher.update(&.{lower}); + } + } + return hasher.final(); + } + + fn cacheSixelPayload(self: *CliRenderer, key: SixelCacheKey, payload: *std.ArrayList(u8)) void { + if (payload.items.len > SIXEL_CACHE_MAX_BYTES) return; + const owned = payload.toOwnedSlice(self.allocator) catch return; + self.sixelCache.ensureUnusedCapacity(self.allocator, 1) catch { + self.allocator.free(owned); + return; + }; + while ((self.sixelCacheBytes + owned.len > SIXEL_CACHE_MAX_BYTES or self.sixelCache.count() >= SIXEL_CACHE_MAX_ENTRIES) and self.sixelCache.count() > 0) { + var iterator = self.sixelCache.iterator(); + var oldest_key: ?SixelCacheKey = null; + var oldest_tick: u64 = std.math.maxInt(u64); + while (iterator.next()) |entry| { + if (oldest_key == null or entry.value_ptr.last_used < oldest_tick) { + oldest_tick = entry.value_ptr.last_used; + oldest_key = entry.key_ptr.*; + } + } + const removed = self.sixelCache.fetchRemove(oldest_key.?) orelse break; + self.sixelCacheBytes -= removed.value.payload.len; + self.allocator.free(removed.value.payload); + } + self.sixelCache.putAssumeCapacity(key, .{ .payload = owned, .last_used = self.sixelCacheClock }); + self.sixelCacheBytes += owned.len; + } + + fn advanceSixelCacheClock(self: *CliRenderer) void { + if (self.sixelCacheClock == std.math.maxInt(u64)) { + var iterator = self.sixelCache.iterator(); + while (iterator.next()) |entry| entry.value_ptr.last_used = 0; + self.sixelCacheClock = 1; + } else { + self.sixelCacheClock += 1; + } + } + /// Generic over the writer type so each backend can provide its own writer /// (buffered frame append or feed streaming) without dispatch in the render path. /// `sync_started` is true only when the caller already opened the @@ -1323,6 +2283,23 @@ pub const CliRenderer = struct { var cellsUpdated: u32 = 0; const palette_force = self.last_rendered_palette_epoch == null or self.last_rendered_palette_epoch.? != self.palette_epoch; const should_force = force or self.force_full_repaint or palette_force; + const has_image_state = self.nextRenderBuffer.image_placements.items.len != 0 or self.currentImages.items.len != 0; + if (self.nextRenderBuffer.image_placements.items.len != 0) { + self.materializeFallbackImages(); + self.computeImageDirtyFlags(should_force); + } else { + self.imageDirty.clearRetainingCapacity(); + } + self.pendingImages.clearRetainingCapacity(); + if (has_image_state) { + self.pendingImages.ensureTotalCapacity(self.allocator, self.nextRenderBuffer.image_placements.items.len) catch { + self.imageRenderFailed = true; + self.force_full_repaint = true; + self.clearSkippedFrameState(); + return; + }; + } + const images_changed = has_image_state and self.imageStateChanged(); // Lazy frame start is the core no-op suppression mechanism. If diffing, // cursor state, and pointer state are unchanged, frame_started stays false @@ -1330,19 +2307,53 @@ pub const CliRenderer = struct { var frame_started = sync_started; self.applyPendingSplitFooterTransition(writer, &frame_started); + if ((images_changed or should_force) and (self.hasCommittedProtocol(.kitty) or self.hasNextProtocol(.kitty))) { + if (!frame_started) { + beginRenderFrame(writer); + frame_started = true; + } + self.writeKittyImages(writer, should_force) catch { + self.force_full_repaint = true; + self.imageRenderFailed = true; + }; + } + var currentFg: ?RGBA = null; var currentBg: ?RGBA = null; var currentAttributes: ?u32 = null; var currentLinkId: u32 = 0; var utf8Buf: [4]u8 = undefined; + var clearRunY: i64 = -1; + var clearRunEnd: i64 = -1; + const image_dirty_items = self.imageDirty.items; + // Whether any identical-looking reserved cell may still need a clear + // this frame; keeps the per-cell diff free of graphics work otherwise. + var clears_pending = should_force; + for (image_dirty_items) |entry| { + if (entry.clear and entry.protocol != .fallback) { + clears_pending = true; + break; + } + } const hyperlinksEnabled = self.terminal.getCapabilities().hyperlinks; + var use_row_equality = !should_force and !clears_pending; for (0..self.height) |uy| { const y = @as(u32, @intCast(uy)); + if (use_row_equality) { + const row_start = @as(usize, y) * self.width; + const row_end = row_start + self.width; + if (std.mem.eql(u32, self.currentRenderBuffer.buffer.char[row_start..row_end], self.nextRenderBuffer.buffer.char[row_start..row_end]) and + std.mem.eql(RGBA, self.currentRenderBuffer.buffer.fg[row_start..row_end], self.nextRenderBuffer.buffer.fg[row_start..row_end]) and + std.mem.eql(RGBA, self.currentRenderBuffer.buffer.bg[row_start..row_end], self.nextRenderBuffer.buffer.bg[row_start..row_end]) and + std.mem.eql(u32, self.currentRenderBuffer.buffer.attributes[row_start..row_end], self.nextRenderBuffer.buffer.attributes[row_start..row_end])) continue; + } + var runStart: i64 = -1; var runLength: u32 = 0; + const cells_updated_before_row = cellsUpdated; for (0..self.width) |ux| { const x = @as(u32, @intCast(ux)); @@ -1351,14 +2362,15 @@ pub const CliRenderer = struct { if (currentCell == null or nextCell == null) continue; - if (!should_force) { - const charEqual = currentCell.?.char == nextCell.?.char; - const attrEqual = currentCell.?.attributes == nextCell.?.attributes; + const cell = nextCell.?; + const cell_type = cell.char & gp.CHAR_TYPE_MASK; - if (charEqual and attrEqual and - buf.rgbaEqual(currentCell.?.fg, nextCell.?.fg) and - buf.rgbaEqual(currentCell.?.bg, nextCell.?.bg)) - { + if (!should_force) { + const cellsEqual = currentCell.?.char == cell.char and currentCell.?.attributes == cell.attributes and + buf.rgbaEqual(currentCell.?.fg, cell.fg) and buf.rgbaEqual(currentCell.?.bg, cell.bg); + // Identical reserved cells still repaint when their + // placement's pixels are dirty (e.g. Sixel content change). + if (cellsEqual and !(clears_pending and cell_type == gp.CHAR_FLAG_IMAGE and dirtyImageChar(image_dirty_items, cell.char))) { if (runLength > 0) { writer.writeAll(ansi.ANSI.reset) catch {}; runStart = -1; @@ -1368,7 +2380,45 @@ pub const CliRenderer = struct { } } - const cell = nextCell.?; + // Reserved graphics cells display as cleared space; their diff + // is placement-level. Only placements whose pixels must be + // (re)painted clear their cells, as one batched space run. + // Fallback placements materialize as ordinary cells and fall + // through to normal diffing. + if (cell_type == gp.CHAR_FLAG_IMAGE) blk: { + const id = gp.imageIdFromChar(cell.char); + if (id == 0 or id > image_dirty_items.len) break :blk; + const state = &image_dirty_items[id - 1]; + if (state.protocol == .fallback) break :blk; + if (!should_force and !state.clear and gp.isImageChar(currentCell.?.char)) { + if (runLength > 0) { + writer.writeAll(ansi.ANSI.reset) catch {}; + runStart = -1; + runLength = 0; + } + if (currentCell.?.char != cell.char) self.currentRenderBuffer.syncCell(x, y, cell); + continue; + } + if (!frame_started) { + beginRenderFrame(writer); + frame_started = true; + } + if (clearRunY != y or clearRunEnd != x) { + writer.writeAll(ansi.ANSI.reset) catch {}; + ansi.ANSI.moveToOutput(writer, x + 1, y + 1 + self.renderOffset) catch {}; + } + writer.writeByte(' ') catch {}; + clearRunY = y; + clearRunEnd = x + 1; + currentFg = null; + currentBg = null; + currentAttributes = null; + runStart = -1; + runLength = 0; + self.currentRenderBuffer.syncCell(x, y, cell); + cellsUpdated += 1; + continue; + } if (!frame_started) { beginRenderFrame(writer); @@ -1418,36 +2468,43 @@ pub const CliRenderer = struct { } // Handle grapheme characters - if (gp.isGraphemeChar(cell.char)) { - const gid: u32 = gp.graphemeIdFromChar(cell.char); - const bytes = self.pool.get(gid) catch |err| { - self.performShutdownSequence(); - std.debug.panic("Fatal: no grapheme bytes in pool for gid {d}: {}", .{ gid, err }); - }; - if (bytes.len > 0) { - const capabilities = self.terminal.getCapabilities(); - const graphemeWidth = gp.charRightExtent(cell.char) + 1; - if (capabilities.explicit_width) { - ansi.ANSI.explicitWidthOutput(writer, graphemeWidth, bytes) catch {}; - } else { - writer.writeAll(bytes) catch {}; - if (capabilities.explicit_cursor_positioning) { - const nextX = x + graphemeWidth; - if (nextX < self.width) { - ansi.ANSI.moveToOutput(writer, nextX + 1, y + 1 + self.renderOffset) catch {}; + switch (cell_type) { + gp.CHAR_FLAG_IMAGE => { + const fallback = buf.quadrantChars[gp.imageFallbackFromChar(cell.char)]; + const len = std.unicode.utf8Encode(@intCast(fallback), &utf8Buf) catch unreachable; + writer.writeAll(utf8Buf[0..len]) catch {}; + }, + gp.CHAR_FLAG_GRAPHEME => { + const gid: u32 = gp.graphemeIdFromChar(cell.char); + const bytes = self.pool.get(gid) catch |err| { + self.performShutdownSequence(); + std.debug.panic("Fatal: no grapheme bytes in pool for gid {d}: {}", .{ gid, err }); + }; + if (bytes.len > 0) { + const capabilities = self.terminal.getCapabilities(); + const graphemeWidth = gp.charRightExtent(cell.char) + 1; + if (capabilities.explicit_width) { + ansi.ANSI.explicitWidthOutput(writer, graphemeWidth, bytes) catch {}; + } else { + writer.writeAll(bytes) catch {}; + if (capabilities.explicit_cursor_positioning) { + const nextX = x + graphemeWidth; + if (nextX < self.width) { + ansi.ANSI.moveToOutput(writer, nextX + 1, y + 1 + self.renderOffset) catch {}; + } } } } - } - } else if (gp.isContinuationChar(cell.char)) { - // Intentionally do not write a space for continuation cells. - // NOTE: disabled to fix 2-cell emoji rendering when the two - // cells have distinct colors (space overwrite can break glyph output) - - // writer.writeByte(' ') catch {}; - } else { - const len = std.unicode.utf8Encode(@intCast(cell.char), &utf8Buf) catch 1; - writer.writeAll(utf8Buf[0..len]) catch {}; + }, + gp.CHAR_FLAG_CONTINUATION => {}, + else => { + if (cell.char >= 32 and cell.char <= 126) { + writer.writeByte(@intCast(cell.char)) catch {}; + } else { + const len = std.unicode.utf8Encode(@intCast(cell.char), &utf8Buf) catch 1; + writer.writeAll(utf8Buf[0..len]) catch {}; + } + }, } runLength += 1; @@ -1459,6 +2516,87 @@ pub const CliRenderer = struct { cellsUpdated += 1; } + if (cellsUpdated - cells_updated_before_row == self.width) use_row_equality = false; + } + + var sixel_dirty = false; + for (self.nextRenderBuffer.image_placements.items) |placement| { + if (self.nextPlacementProtocol(placement) == .sixel and self.placementDirty(placement.placement_id)) { + sixel_dirty = true; + break; + } + } + if (sixel_dirty) { + if (!frame_started) { + beginRenderFrame(writer); + frame_started = true; + } + if (hyperlinksEnabled and currentLinkId != 0) { + writer.writeAll("\x1b]8;;\x1b\\") catch {}; + currentLinkId = 0; + } + self.writeSixelImages(writer) catch { + self.force_full_repaint = true; + self.imageRenderFailed = true; + }; + // Cells that replaced reservation markers were painted after the image. + for (self.nextRenderBuffer.image_placements.items) |placement| { + if (self.nextPlacementProtocol(placement) != .sixel or !self.placementDirty(placement.placement_id)) continue; + var py: u32 = 0; + while (py < placement.height) : (py += 1) { + const y_i = placement.y + @as(i32, @intCast(py)); + if (y_i < 0 or y_i >= self.height) continue; + var px: u32 = 0; + while (px < placement.width) : (px += 1) { + const x_i = placement.x + @as(i32, @intCast(px)); + if (x_i < 0 or x_i >= self.width) continue; + var draw_x_i = x_i; + var cell = self.nextRenderBuffer.get(@intCast(x_i), @intCast(y_i)) orelse continue; + if (gp.isImageChar(cell.char)) { + if (self.placementFrameState(cell.char)) |state| { + if (state.protocol != .fallback) continue; + } + } + if (gp.isContinuationChar(cell.char)) { + const start_x_i = x_i - @as(i32, @intCast(gp.charLeftExtent(cell.char))); + const first_visible_placement_x = @max(placement.x, 0); + if (start_x_i >= placement.x or x_i != first_visible_placement_x or start_x_i < 0) continue; + draw_x_i = start_x_i; + cell = self.nextRenderBuffer.get(@intCast(draw_x_i), @intCast(y_i)) orelse continue; + if (!gp.isGraphemeChar(cell.char)) continue; + } + writer.writeAll(ansi.ANSI.reset) catch {}; + ansi.ANSI.moveToOutput(writer, @intCast(draw_x_i + 1), @intCast(y_i + 1 + @as(i32, @intCast(self.renderOffset)))) catch {}; + self.emitColor(writer, cell.fg, false); + self.emitColor(writer, cell.bg, true); + ansi.TextAttributes.applyAttributesOutputWriter(writer, cell.attributes) catch {}; + const replay_link_id = if (hyperlinksEnabled) ansi.TextAttributes.getLinkId(cell.attributes) else 0; + if (replay_link_id != 0) { + const lp = link.initGlobalLinkPool(self.allocator); + if (lp.get(replay_link_id)) |url_bytes| { + writer.print("\x1b]8;id={d};{s}\x1b\\", .{ replay_link_id, url_bytes }) catch {}; + } else |_| {} + } + if (gp.isGraphemeChar(cell.char)) { + if (self.pool.get(gp.graphemeIdFromChar(cell.char))) |bytes| { + const capabilities = self.terminal.getCapabilities(); + const grapheme_width = gp.charRightExtent(cell.char) + 1; + if (capabilities.explicit_width) { + ansi.ANSI.explicitWidthOutput(writer, grapheme_width, bytes) catch {}; + } else { + writer.writeAll(bytes) catch {}; + } + } else |_| {} + } else if (!gp.isContinuationChar(cell.char)) { + const draw_char = if (gp.isImageChar(cell.char)) buf.quadrantChars[gp.imageFallbackFromChar(cell.char)] else cell.char; + if (std.unicode.utf8Encode(@intCast(draw_char), &utf8Buf)) |len| { + writer.writeAll(utf8Buf[0..len]) catch {}; + } else |_| {} + } + if (replay_link_id != 0) writer.writeAll("\x1b]8;;\x1b\\") catch {}; + } + } + } } if (hyperlinksEnabled and currentLinkId != 0) { @@ -1560,13 +2698,14 @@ pub const CliRenderer = struct { } const mousePointer = self.terminal.getMousePointer(); - if (mousePointer != self.lastMousePointerStyle) { + if (!self.mousePointerStateValid or mousePointer != self.lastMousePointerStyle) { if (!frame_started) { beginRenderFrame(writer); frame_started = true; } ansi.ANSI.setMousePointerOutput(writer, mousePointer.toName()) catch {}; self.lastMousePointerStyle = mousePointer; + self.mousePointerStateValid = true; } // Only close sync if we opened it. This keeps true no-op frames empty. @@ -1580,21 +2719,19 @@ pub const CliRenderer = struct { self.renderStats.cellsUpdated = cellsUpdated; self.renderStats.renderTime = renderTime; self.last_rendered_palette_epoch = self.palette_epoch; - self.force_full_repaint = false; + if (self.imageRenderFailed) { + self.force_full_repaint = true; + self.pendingImages.clearRetainingCapacity(); + } else { + self.force_full_repaint = false; + if (has_image_state) { + self.stageImageState(); + } else { + self.pendingImages.clearRetainingCapacity(); + } + } self.nextRenderBuffer.clear(self.backgroundColor, null); - - // Compare hit grids before swap to detect changes. This allows TypeScript to - // know if hover state needs rechecking without manually tracking dirty state. - self.hitGridDirty = self.hitGridResizeInvalidated or !std.mem.eql(u32, self.currentHitGrid, self.nextHitGrid); - - // Swap hit grids: nextHitGrid (built this frame) becomes the active grid for - // hit testing. The old currentHitGrid becomes nextHitGrid and is cleared for - // the next frame. - const temp = self.currentHitGrid; - self.currentHitGrid = self.nextHitGrid; - self.nextHitGrid = temp; - @memset(self.nextHitGrid, 0); } pub fn setDebugOverlay(self: *CliRenderer, enabled: bool, corner: DebugOverlayCorner) void { @@ -1603,7 +2740,24 @@ pub const CliRenderer = struct { } pub fn clearTerminal(self: *CliRenderer) void { + if (self.hasCommittedProtocol(.kitty)) { + for (self.currentImages.items) |current| { + if (current.protocol != .kitty) continue; + var delete_buf: [128]u8 = undefined; + var stream = std.io.fixedBufferStream(&delete_buf); + terminal_image.writeKittyDelete( + stream.writer(), + self.kittyImageId(current.placement_id), + null, + true, + self.terminal.isInTmux(), + ) catch {}; + self.writeOut(stream.getWritten()); + } + } self.writeOut(ansi.ANSI.clearAndHome); + self.currentImages.clearRetainingCapacity(); + self.force_full_repaint = true; } pub fn writeOut(self: *CliRenderer, data: []const u8) void { @@ -1830,6 +2984,11 @@ pub const CliRenderer = struct { if (cell) |c| { if (gp.isContinuationChar(c.char)) { // skip + } else if (gp.isImageChar(c.char)) { + const fallback = buf.quadrantChars[gp.imageFallbackFromChar(c.char)]; + var utf8Buf: [4]u8 = undefined; + const len = std.unicode.utf8Encode(@intCast(fallback), &utf8Buf) catch unreachable; + writer.writeAll(utf8Buf[0..len]) catch return; } else if (gp.isGraphemeChar(c.char)) { const gid: u32 = gp.graphemeIdFromChar(c.char); const bytes = self.pool.get(gid) catch &[_]u8{}; diff --git a/packages/core/src/zig/terminal-image.zig b/packages/core/src/zig/terminal-image.zig new file mode 100644 index 0000000000..5cba410808 --- /dev/null +++ b/packages/core/src/zig/terminal-image.zig @@ -0,0 +1,525 @@ +const std = @import("std"); +const native_image = @import("image.zig"); + +pub const KittyPixelFormat = enum { auto, rgb, rgba }; + +pub fn writeKittyTransmit(writer: anytype, image: *const native_image.Image, id: u32, tmux: bool) !void { + return writeKittyTransmitFormat(writer, image, id, tmux, .auto); +} + +pub fn writeKittyTransmitFormat(writer: anytype, image: *const native_image.Image, id: u32, tmux: bool, requested_format: KittyPixelFormat) !void { + const raw_chunk = 3072; + const pixel_count = std.math.mul(usize, image.width(), image.height()) catch return error.InvalidImageData; + const rgba_len = std.math.mul(usize, pixel_count, 4) catch return error.InvalidImageData; + if (image.pixels.len < rgba_len) return error.InvalidImageData; + if (requested_format == .auto) { + if (image.encoded_png) |png| { + var offset: usize = 0; + var first = true; + while (offset < png.len) { + const end = @min(offset + raw_chunk, png.len); + const more = end < png.len; + if (tmux) try writer.writeAll("\x1bPtmux;\x1b\x1b_G") else try writer.writeAll("\x1b_G"); + if (first) { + try writer.print("a=t,f=100,i={d},m={d},q=2;", .{ id, @intFromBool(more) }); + } else { + try writer.print("m={d},q=2;", .{@intFromBool(more)}); + } + var encoded: [4096]u8 = undefined; + const payload = std.base64.standard.Encoder.encode(encoded[0..std.base64.standard.Encoder.calcSize(end - offset)], png[offset..end]); + try writer.writeAll(payload); + if (tmux) try writer.writeAll("\x1b\x1b\\\x1b\\") else try writer.writeAll("\x1b\\"); + offset = end; + first = false; + } + return; + } + } + const format: KittyPixelFormat = if (requested_format == .auto) + (if (image.metadata.has_alpha == 0) .rgb else .rgba) + else + requested_format; + if (format == .rgb) { + var raw: [raw_chunk]u8 = undefined; + const rgb_len = std.math.mul(usize, pixel_count, 3) catch return error.InvalidImageData; + var rgb_offset: usize = 0; + var first = true; + while (rgb_offset < rgb_len) { + const chunk_len = @min(raw.len, rgb_len - rgb_offset); + const first_pixel = rgb_offset / 3; + const chunk_pixels = chunk_len / 3; + for (0..chunk_pixels) |index| { + const source = (first_pixel + index) * 4; + @memcpy(raw[index * 3 ..][0..3], image.pixels[source..][0..3]); + } + const more = rgb_offset + chunk_len < rgb_len; + if (tmux) try writer.writeAll("\x1bPtmux;\x1b\x1b_G") else try writer.writeAll("\x1b_G"); + if (first) { + try writer.print("a=t,f=24,s={d},v={d},i={d},m={d},q=2;", .{ image.width(), image.height(), id, @intFromBool(more) }); + } else { + try writer.print("m={d},q=2;", .{@intFromBool(more)}); + } + var encoded: [4096]u8 = undefined; + const encoded_len = std.base64.standard.Encoder.calcSize(chunk_len); + const payload = std.base64.standard.Encoder.encode(encoded[0..encoded_len], raw[0..chunk_len]); + try writer.writeAll(payload); + if (tmux) try writer.writeAll("\x1b\x1b\\\x1b\\") else try writer.writeAll("\x1b\\"); + rgb_offset += chunk_len; + first = false; + } + return; + } + var offset: usize = 0; + var first = true; + while (offset < rgba_len) { + const end = @min(offset + raw_chunk, rgba_len); + const more = end < rgba_len; + if (tmux) try writer.writeAll("\x1bPtmux;\x1b\x1b_G") else try writer.writeAll("\x1b_G"); + if (first) { + try writer.print("a=t,f=32,s={d},v={d},i={d},m={d},q=2;", .{ image.width(), image.height(), id, @intFromBool(more) }); + } else { + try writer.print("m={d},q=2;", .{@intFromBool(more)}); + } + var encoded: [4096]u8 = undefined; + const payload = std.base64.standard.Encoder.encode(encoded[0..std.base64.standard.Encoder.calcSize(end - offset)], image.pixels[offset..end]); + try writer.writeAll(payload); + if (tmux) try writer.writeAll("\x1b\x1b\\\x1b\\") else try writer.writeAll("\x1b\\"); + offset = end; + first = false; + } +} + +pub fn writeKittyPlacement( + writer: anytype, + id: u32, + placement_id: u32, + x: u32, + y: u32, + width: u32, + height: u32, + source_x: u32, + source_y: u32, + source_width: u32, + source_height: u32, + z: i32, + tmux: bool, +) !void { + try writer.print("\x1b[{d};{d}H", .{ y + 1, x + 1 }); + if (tmux) try writer.writeAll("\x1bPtmux;\x1b\x1b_G") else try writer.writeAll("\x1b_G"); + try writer.print("a=p,i={d},p={d},c={d},r={d},x={d},y={d},w={d},h={d},C=1,z={d},q=2", .{ + id, placement_id, width, height, source_x, source_y, source_width, source_height, z, + }); + if (tmux) try writer.writeAll("\x1b\x1b\\\x1b\\") else try writer.writeAll("\x1b\\"); +} + +pub fn writeKittyPlacementAtCursor( + writer: anytype, + id: u32, + placement_id: u32, + width: u32, + height: u32, + z: i32, + tmux: bool, +) !void { + if (tmux) try writer.writeAll("\x1bPtmux;\x1b\x1b_G") else try writer.writeAll("\x1b_G"); + try writer.print("a=p,i={d},p={d},c={d},r={d},C=1,z={d},q=2", .{ id, placement_id, width, height, z }); + if (tmux) try writer.writeAll("\x1b\x1b\\\x1b\\") else try writer.writeAll("\x1b\\"); +} + +pub fn writeKittyDelete(writer: anytype, id: u32, placement_id: ?u32, free_image: bool, tmux: bool) !void { + if (tmux) try writer.writeAll("\x1bPtmux;\x1b\x1b_G") else try writer.writeAll("\x1b_G"); + try writer.print("a=d,d={c},i={d}", .{ if (free_image) @as(u8, 'I') else @as(u8, 'i'), id }); + if (placement_id) |p| try writer.print(",p={d}", .{p}); + try writer.writeAll(",q=2"); + if (tmux) try writer.writeAll("\x1b\x1b\\\x1b\\") else try writer.writeAll("\x1b\\"); +} + +const SixelColor = struct { r: u8, g: u8, b: u8 }; +const SixelMoment = struct { count: u64 = 0, r: u64 = 0, g: u64 = 0, b: u64 = 0, square: u64 = 0 }; +const SixelBox = struct { r0: u8 = 0, r1: u8 = 32, g0: u8 = 0, g1: u8 = 32, b0: u8 = 0, b1: u8 = 32 }; +const SixelAxis = enum { r, g, b }; +const SixelCut = struct { axis: SixelAxis, at: u8, score: f64 }; +const SIXEL_HISTOGRAM_SIDE = 33; +const SIXEL_HISTOGRAM_LEN = SIXEL_HISTOGRAM_SIDE * SIXEL_HISTOGRAM_SIDE * SIXEL_HISTOGRAM_SIDE; + +pub const QuantizedSixel = struct { + allocator: std.mem.Allocator, + palette: [255][3]u8, + palette_len: usize, + indices: []u8, + + pub fn deinit(self: *QuantizedSixel) void { + self.allocator.free(self.indices); + } +}; + +// Wu's fixed RGB moment cube gives a deterministic adaptive palette; tagging its final boxes maps +// pixels without a nearest-color search: https://github.com/erich666/GraphicsGems/blob/master/gemsii/quantizer.c +fn momentIndex(r: usize, g: usize, b: usize) usize { + return (r * SIXEL_HISTOGRAM_SIDE + g) * SIXEL_HISTOGRAM_SIDE + b; +} + +fn addMoment(target: *SixelMoment, value: SixelMoment) void { + target.count += value.count; + target.r += value.r; + target.g += value.g; + target.b += value.b; + target.square += value.square; +} + +fn volumeValue(comptime field: []const u8, moments: []const SixelMoment, box: SixelBox) u64 { + const value = @as(i128, @field(moments[momentIndex(box.r1, box.g1, box.b1)], field)) - + @as(i128, @field(moments[momentIndex(box.r1, box.g1, box.b0)], field)) - + @as(i128, @field(moments[momentIndex(box.r1, box.g0, box.b1)], field)) + + @as(i128, @field(moments[momentIndex(box.r1, box.g0, box.b0)], field)) - + @as(i128, @field(moments[momentIndex(box.r0, box.g1, box.b1)], field)) + + @as(i128, @field(moments[momentIndex(box.r0, box.g1, box.b0)], field)) + + @as(i128, @field(moments[momentIndex(box.r0, box.g0, box.b1)], field)) - + @as(i128, @field(moments[momentIndex(box.r0, box.g0, box.b0)], field)); + return @intCast(value); +} + +const BoxStats = struct { count: u64, r: u64, g: u64, b: u64 }; + +fn volumeStats(moments: []const SixelMoment, box: SixelBox) BoxStats { + const corners = [_]*const SixelMoment{ + &moments[momentIndex(box.r1, box.g1, box.b1)], &moments[momentIndex(box.r1, box.g1, box.b0)], + &moments[momentIndex(box.r1, box.g0, box.b1)], &moments[momentIndex(box.r1, box.g0, box.b0)], + &moments[momentIndex(box.r0, box.g1, box.b1)], &moments[momentIndex(box.r0, box.g1, box.b0)], + &moments[momentIndex(box.r0, box.g0, box.b1)], &moments[momentIndex(box.r0, box.g0, box.b0)], + }; + var result = BoxStats{ .count = 0, .r = 0, .g = 0, .b = 0 }; + inline for (.{ "count", "r", "g", "b" }) |field| { + const value = @as(i128, @field(corners[0], field)) - @as(i128, @field(corners[1], field)) - + @as(i128, @field(corners[2], field)) + @as(i128, @field(corners[3], field)) - + @as(i128, @field(corners[4], field)) + @as(i128, @field(corners[5], field)) + + @as(i128, @field(corners[6], field)) - @as(i128, @field(corners[7], field)); + @field(result, field) = @intCast(value); + } + return result; +} + +fn statsEnergy(stats: BoxStats) f64 { + if (stats.count == 0) return 0; + const r: f64 = @floatFromInt(stats.r); + const g: f64 = @floatFromInt(stats.g); + const b: f64 = @floatFromInt(stats.b); + return (2.0 * r * r + 4.0 * g * g + 3.0 * b * b) / (8.0 * @as(f64, @floatFromInt(stats.count))); +} + +fn boxVariance(moments: []const SixelMoment, box: SixelBox) f64 { + return @max(0, @as(f64, @floatFromInt(volumeValue("square", moments, box))) - statsEnergy(volumeStats(moments, box))); +} + +fn bestBoxCut(moments: []const SixelMoment, box: SixelBox) ?SixelCut { + var best: ?SixelCut = null; + inline for ([_]SixelAxis{ .r, .g, .b }) |axis| { + const low = switch (axis) { + .r => box.r0, + .g => box.g0, + .b => box.b0, + }; + const high = switch (axis) { + .r => box.r1, + .g => box.g1, + .b => box.b1, + }; + var at = low + 1; + while (at < high) : (at += 1) { + var first = box; + var second = box; + switch (axis) { + .r => { + first.r1 = at; + second.r0 = at; + }, + .g => { + first.g1 = at; + second.g0 = at; + }, + .b => { + first.b1 = at; + second.b0 = at; + }, + } + const first_stats = volumeStats(moments, first); + const second_stats = volumeStats(moments, second); + if (first_stats.count == 0 or second_stats.count == 0) continue; + const score = statsEnergy(first_stats) + statsEnergy(second_stats); + if (best == null or score > best.?.score) best = .{ .axis = axis, .at = at, .score = score }; + } + } + return best; +} + +pub fn quantizeSixel(allocator: std.mem.Allocator, image: *const native_image.Image, max_colors: usize) !QuantizedSixel { + if (max_colors == 0 or max_colors > 255) return error.InvalidArgument; + const pixel_count = std.math.mul(usize, image.width(), image.height()) catch return error.InvalidImageData; + if (image.pixels.len < std.math.mul(usize, pixel_count, 4) catch return error.InvalidImageData) return error.InvalidImageData; + const moments = try allocator.alloc(SixelMoment, SIXEL_HISTOGRAM_LEN); + defer allocator.free(moments); + @memset(moments, .{}); + var active_bins: usize = 0; + for (0..pixel_count) |pixel| { + const offset = pixel * 4; + if (image.pixels[offset + 3] < 128) continue; + const r = image.pixels[offset]; + const g = image.pixels[offset + 1]; + const b = image.pixels[offset + 2]; + const bin = &moments[momentIndex(@as(usize, r >> 3) + 1, @as(usize, g >> 3) + 1, @as(usize, b >> 3) + 1)]; + if (bin.count == 0) active_bins += 1; + bin.count += 1; + bin.r += r; + bin.g += g; + bin.b += b; + bin.square += (2 * @as(u64, r) * r + 4 * @as(u64, g) * g + 3 * @as(u64, b) * b + 4) >> 3; + } + for (1..SIXEL_HISTOGRAM_SIDE) |r| { + var area = [_]SixelMoment{.{}} ** SIXEL_HISTOGRAM_SIDE; + for (1..SIXEL_HISTOGRAM_SIDE) |g| { + var line: SixelMoment = .{}; + for (1..SIXEL_HISTOGRAM_SIDE) |b| { + addMoment(&line, moments[momentIndex(r, g, b)]); + addMoment(&area[b], line); + var cumulative = moments[momentIndex(r - 1, g, b)]; + addMoment(&cumulative, area[b]); + moments[momentIndex(r, g, b)] = cumulative; + } + } + } + var result = QuantizedSixel{ .allocator = allocator, .palette = undefined, .palette_len = 0, .indices = try allocator.alloc(u8, pixel_count) }; + errdefer allocator.free(result.indices); + if (active_bins == 0) { + @memset(result.indices, 255); + return result; + } + var boxes: [255]SixelBox = undefined; + var variances = [_]f64{0} ** 255; + boxes[0] = .{}; + variances[0] = boxVariance(moments, boxes[0]); + var box_count: usize = 1; + while (box_count < @min(active_bins, max_colors)) { + var split_index: ?usize = null; + var highest: f64 = 0; + for (variances[0..box_count], 0..) |variance, index| if (variance > highest) { + highest = variance; + split_index = index; + }; + if (split_index == null) break; + const cut = bestBoxCut(moments, boxes[split_index.?]) orelse { + variances[split_index.?] = 0; + continue; + }; + var second = boxes[split_index.?]; + switch (cut.axis) { + .r => { + boxes[split_index.?].r1 = cut.at; + second.r0 = cut.at; + }, + .g => { + boxes[split_index.?].g1 = cut.at; + second.g0 = cut.at; + }, + .b => { + boxes[split_index.?].b1 = cut.at; + second.b0 = cut.at; + }, + } + boxes[box_count] = second; + variances[split_index.?] = boxVariance(moments, boxes[split_index.?]); + variances[box_count] = boxVariance(moments, second); + box_count += 1; + } + result.palette_len = box_count; + // Common colors get shorter palette selectors, reducing repeated #N designations in each band. + var box_counts: [255]u64 = undefined; + for (boxes[0..box_count], 0..) |box, index| box_counts[index] = volumeStats(moments, box).count; + for (1..box_count) |index| { + const box = boxes[index]; + const count = box_counts[index]; + var destination = index; + while (destination > 0 and box_counts[destination - 1] < count) : (destination -= 1) { + boxes[destination] = boxes[destination - 1]; + box_counts[destination] = box_counts[destination - 1]; + } + boxes[destination] = box; + box_counts[destination] = count; + } + var tags = [_]u8{0} ** (32 * 32 * 32); + for (boxes[0..box_count], 0..) |box, palette_index| { + const stats = volumeStats(moments, box); + result.palette[palette_index] = .{ + @intCast((stats.r + stats.count / 2) / stats.count), + @intCast((stats.g + stats.count / 2) / stats.count), + @intCast((stats.b + stats.count / 2) / stats.count), + }; + for (box.r0..box.r1) |r| { + for (box.g0..box.g1) |g| { + for (box.b0..box.b1) |b| tags[(r << 10) | (g << 5) | b] = @intCast(palette_index); + } + } + } + for (0..image.height()) |y| { + for (0..image.width()) |x| { + const pixel = y * image.width() + x; + const offset = pixel * 4; + result.indices[pixel] = if (image.pixels[offset + 3] < 128) 255 else tags[ + (@as(usize, image.pixels[offset] >> 3) << 10) | + (@as(usize, image.pixels[offset + 1] >> 3) << 5) | + (image.pixels[offset + 2] >> 3) + ]; + } + } + + return result; +} + +fn writeUnsigned(writer: anytype, value: usize) !void { + var buffer: [20]u8 = undefined; + var index = buffer.len; + var remaining = value; + while (true) { + index -= 1; + buffer[index] = @intCast('0' + remaining % 10); + remaining /= 10; + if (remaining == 0) break; + } + try writer.writeAll(buffer[index..]); +} + +fn BufferedWriter(comptime Writer: type) type { + return struct { + writer: Writer, + buffer: [8192]u8 = undefined, + len: usize = 0, + + fn writeByte(self: *@This(), value: u8) !void { + if (self.len == self.buffer.len) try self.flush(); + self.buffer[self.len] = value; + self.len += 1; + } + + fn writeAll(self: *@This(), value: []const u8) !void { + if (value.len >= self.buffer.len) { + try self.flush(); + try self.writer.writeAll(value); + return; + } + if (self.len + value.len > self.buffer.len) try self.flush(); + @memcpy(self.buffer[self.len..][0..value.len], value); + self.len += value.len; + } + + fn flush(self: *@This()) !void { + if (self.len == 0) return; + try self.writer.writeAll(self.buffer[0..self.len]); + self.len = 0; + } + }; +} + +pub fn writeSixelPayload(allocator: std.mem.Allocator, writer: anytype, image: *const native_image.Image) !void { + var quantized = try quantizeSixel(allocator, image, 255); + defer quantized.deinit(); + try writeSixelIndexedPayload( + allocator, + writer, + quantized.indices, + quantized.palette[0..quantized.palette_len], + image.width(), + image.height(), + ); +} + +pub fn writeSixelIndexedPayload( + allocator: std.mem.Allocator, + writer: anytype, + indices: []const u8, + palette: []const [3]u8, + width: u32, + height: u32, +) !void { + if (palette.len == 0 or palette.len > 255) return error.InvalidImageData; + const pixel_count = std.math.mul(usize, width, height) catch return error.InvalidImageData; + if (indices.len < pixel_count) return error.InvalidImageData; + const mask_count = std.math.mul(usize, palette.len, width) catch return error.InvalidImageData; + const masks = try allocator.alloc(u8, mask_count); + defer allocator.free(masks); + var buffered = BufferedWriter(@TypeOf(writer)){ .writer = writer }; + const output = &buffered; + var generations = [_]u32{0} ** 256; + var last_nonzero = [_]usize{0} ** 256; + try output.writeAll("0;1;0q\"1;1;"); + try writeUnsigned(output, width); + try output.writeByte(';'); + try writeUnsigned(output, height); + for (palette, 0..) |color, index| { + try output.writeByte('#'); + try writeUnsigned(output, index); + try output.writeAll(";2;"); + for (color, 0..) |channel, channel_index| { + try writeUnsigned(output, (@as(u16, channel) * 100 + 127) / 255); + if (channel_index < 2) try output.writeByte(';'); + } + } + + const output_height: usize = height; + var band_y: usize = 0; + var generation: u32 = 0; + while (band_y < output_height) : (band_y += 6) { + generation += 1; + for (0..6) |bit| { + const y = band_y + bit; + if (y >= output_height) continue; + for (0..width) |x| { + const palette_index = indices[y * width + x]; + if (palette_index == 255) continue; + if (palette_index >= palette.len) return error.InvalidImageData; + if (generations[palette_index] != generation) { + generations[palette_index] = generation; + @memset(masks[@as(usize, palette_index) * width ..][0..width], 0); + last_nonzero[palette_index] = 0; + } + masks[@as(usize, palette_index) * width + x] |= @as(u8, 1) << @intCast(bit); + last_nonzero[palette_index] = @max(last_nonzero[palette_index], x + 1); + } + } + var first_plane = true; + for (0..palette.len) |palette_index| { + if (generations[palette_index] != generation) continue; + const plane = masks[palette_index * width ..][0..last_nonzero[palette_index]]; + if (!first_plane) try output.writeByte('$'); + first_plane = false; + try output.writeByte('#'); + try writeUnsigned(output, palette_index); + var x: usize = 0; + while (x < plane.len) { + const mask = plane[x]; + const char: u8 = '?' + mask; + var run: usize = 1; + while (x + run < plane.len and plane[x + run] == mask) : (run += 1) {} + if (run >= 4) { + try output.writeByte('!'); + try writeUnsigned(output, run); + try output.writeByte(char); + } else for (0..run) |_| try output.writeByte(char); + x += run; + } + } + if (band_y + 6 < output_height) try output.writeByte('-'); + } + try output.flush(); +} + +pub fn writeSixelFramedPayload(writer: anytype, payload: []const u8, tmux: bool) !void { + if (tmux) try writer.writeAll("\x1bPtmux;\x1b\x1bP") else try writer.writeAll("\x1bP"); + try writer.writeAll(payload); + if (tmux) try writer.writeAll("\x1b\x1b\\\x1b\\") else try writer.writeAll("\x1b\\"); +} + +pub fn writeSixel(allocator: std.mem.Allocator, writer: anytype, image: *const native_image.Image, tmux: bool) !void { + var payload: std.ArrayList(u8) = .empty; + defer payload.deinit(allocator); + try writeSixelPayload(allocator, payload.writer(allocator), image); + try writeSixelFramedPayload(writer, payload.items, tmux); +} diff --git a/packages/core/src/zig/terminal.zig b/packages/core/src/zig/terminal.zig index 6597b7c187..92dce32c81 100644 --- a/packages/core/src/zig/terminal.zig +++ b/packages/core/src/zig/terminal.zig @@ -53,6 +53,13 @@ pub const RemoteMode = enum(u8) { remote, }; +pub const ImageProtocol = enum(u8) { + auto, + kitty, + sixel, + blocks, +}; + pub const Multiplexer = enum(u8) { none, tmux, @@ -149,8 +156,13 @@ multiplexer: Multiplexer = .none, osc52_support: Osc52Support = .unknown, is_foot: bool = false, skip_graphics_query: bool = false, +graphics_enabled: bool = true, +image_protocol: ImageProtocol = .auto, +kitty_graphics_queried: bool = false, +sixel_queried: bool = false, skip_explicit_width_query: bool = false, graphics_query_pending: bool = false, +sixel_query_pending: bool = false, capability_queries_pending: bool = false, startup_cursor_query_pending: bool = false, startup_cursor_query_captured: bool = false, @@ -284,6 +296,7 @@ pub fn exitAltScreen(self: *Terminal, tty: anytype) !void { pub fn queryTerminalSend(self: *Terminal, tty: anytype) !void { self.checkEnvironmentOverrides(); self.graphics_query_pending = !self.skip_graphics_query; + self.sixel_query_pending = !self.skip_graphics_query; self.capability_queries_pending = false; self.startup_cursor_query_pending = true; self.startup_cursor_query_captured = false; @@ -319,6 +332,16 @@ pub fn queryTerminalSend(self: *Terminal, tty: anytype) !void { self.capability_queries_pending = true; } + if (!self.skip_graphics_query) { + if (self.isInTmux()) { + try tty.writeAll(ansi.ANSI.kittyGraphicsQueryTmux); + try tty.writeAll(ansi.ANSI.primaryDeviceAttrsTmux); + } else { + try tty.writeAll(ansi.ANSI.kittyGraphicsQuery); + try tty.writeAll(ansi.ANSI.primaryDeviceAttrs); + } + } + if (!self.skip_explicit_width_query) { try tty.writeAll(ansi.ANSI.home ++ ansi.ANSI.explicitWidthQuery ++ @@ -335,6 +358,10 @@ pub fn sendPendingQueries(self: *Terminal, tty: anytype) !bool { var sent = false; const is_tmux = self.isInTmux(); + // Initial probes were already sent using environment-derived multiplexer + // state. Only XTVERSION can justify a differently wrapped retry. + if (!self.term_info.from_xtversion) return false; + // Re-send capability queries DCS wrapped if tmux detected via xtversion // Only needed if we got xtversion response indicating tmux if (self.capability_queries_pending) { @@ -349,11 +376,17 @@ pub fn sendPendingQueries(self: *Terminal, tty: anytype) !bool { if (self.graphics_query_pending and !self.skip_graphics_query) { if (is_tmux) { try tty.writeAll(ansi.ANSI.kittyGraphicsQueryTmux); - } else { - try tty.writeAll(ansi.ANSI.kittyGraphicsQuery); + sent = true; } self.graphics_query_pending = false; - sent = true; + } + + if (self.sixel_query_pending and !self.skip_graphics_query) { + if (is_tmux) { + try tty.writeAll(ansi.ANSI.primaryDeviceAttrsTmux); + sent = true; + } + self.sixel_query_pending = false; } return sent; @@ -628,6 +661,8 @@ fn checkEnvironmentOverrides(self: *Terminal) void { } self.is_foot = self.term_info.from_xtversion and std.ascii.indexOfIgnoreCase(self.getTerminalName(), "foot") != null; self.skip_graphics_query = false; + self.graphics_enabled = true; + self.image_protocol = .auto; self.skip_explicit_width_query = false; // Always just try to enable bracketed paste, even if it was reported as not supported @@ -723,11 +758,28 @@ fn checkEnvironmentOverrides(self: *Terminal) void { if (env_map.get("OPENTUI_GRAPHICS")) |val| { if (std.mem.eql(u8, val, "false") or std.mem.eql(u8, val, "0")) { self.skip_graphics_query = true; + self.graphics_enabled = false; + self.kitty_graphics_queried = false; + self.sixel_queried = false; + self.caps.kitty_graphics = false; + self.caps.sixel = false; } else if (std.mem.eql(u8, val, "true") or std.mem.eql(u8, val, "1")) { self.skip_graphics_query = false; } } + if (env_map.get("OPENTUI_IMAGE_PROTOCOL")) |value| { + if (std.ascii.eqlIgnoreCase(value, "auto")) { + self.image_protocol = .auto; + } else if (std.ascii.eqlIgnoreCase(value, "kitty")) { + self.image_protocol = .kitty; + } else if (std.ascii.eqlIgnoreCase(value, "sixel")) { + self.image_protocol = .sixel; + } else if (std.ascii.eqlIgnoreCase(value, "blocks")) { + self.image_protocol = .blocks; + } + } + if (!self.term_info.from_xtversion) { if (env_map.get("TERM_PROGRAM")) |prog| { if (!self.isInZellij()) { @@ -1049,10 +1101,125 @@ pub fn restoreTerminalModes(self: *Terminal, tty: anytype) !void { /// alacritty - '\x1B[?1016;0$y\x1B[?2027;0$y\x1B[?2031;0$y\x1B[?1004;2$y\x1B[?2004;2$y\x1B[?2026;2$y\x1B[1;1R\x1B[1;1R\x1B[?0u\x1B[?6c' /// /// Parsing these is not complete yet +fn parseKittyGraphicsResponse(self: *Terminal, response: []const u8) void { + if (!self.graphics_enabled) return; + var offset: usize = 0; + while (std.mem.indexOfPos(u8, response, offset, "\x1b_G")) |start| { + const end = std.mem.indexOfPos(u8, response, start + 3, "\x1b\\") orelse return; + const frame = response[start + 3 .. end]; + const control_end = std.mem.indexOfScalar(u8, frame, ';') orelse frame.len; + var fields = std.mem.splitScalar(u8, frame[0..control_end], ','); + while (fields.next()) |field| { + if (std.mem.eql(u8, field, "i=31337")) { + self.kitty_graphics_queried = true; + self.caps.kitty_graphics = true; + return; + } + } + offset = end + 2; + } +} + +fn parseSixelDeviceAttributes(self: *Terminal, response: []const u8) void { + if (!self.graphics_enabled) return; + var offset: usize = 0; + while (std.mem.indexOfPos(u8, response, offset, "\x1b[?")) |start| { + var end = start + 3; + while (end < response.len and (std.ascii.isDigit(response[end]) or response[end] == ';')) : (end += 1) {} + if (end >= response.len) return; + if (response[end] != 'c') { + offset = end + 1; + continue; + } + var params = std.mem.splitScalar(u8, response[start + 3 .. end], ';'); + while (params.next()) |param| { + if (std.mem.eql(u8, param, "4")) { + self.sixel_queried = true; + self.caps.sixel = true; + return; + } + } + offset = end + 1; + } +} + +fn semanticVersionAtLeast(version: []const u8, required_major: u32, required_minor: u32) bool { + const suffix_start = std.mem.indexOfScalar(u8, version, '-'); + const core = if (suffix_start) |index| version[0..index] else version; + var parts = std.mem.splitScalar(u8, core, '.'); + const major_text = parts.next() orelse return false; + const minor_text = parts.next() orelse return false; + const patch_text = parts.next() orelse return false; + if (parts.next() != null) return false; + const major = std.fmt.parseInt(u32, major_text, 10) catch return false; + const minor = std.fmt.parseInt(u32, minor_text, 10) catch return false; + const patch = std.fmt.parseInt(u32, patch_text, 10) catch return false; + if (major > required_major or (major == required_major and minor > required_minor)) return true; + if (major != required_major or minor != required_minor) return false; + if (patch > 0) return true; + const suffix = if (suffix_start) |index| version[index + 1 ..] else return true; + var suffix_parts = std.mem.splitScalar(u8, suffix, '-'); + const commits = suffix_parts.next() orelse return false; + const revision = suffix_parts.next() orelse return false; + if (suffix_parts.next() != null or commits.len == 0 or revision.len < 2 or revision[0] != 'g') return false; + _ = std.fmt.parseInt(u32, commits, 10) catch return false; + for (revision[1..]) |char| if (!std.ascii.isHex(char)) return false; + return true; +} + +fn wezTermBuildAtLeast(version: []const u8, required_date: u32) bool { + var parts = std.mem.splitAny(u8, version, "-._"); + const date_text = parts.next() orelse return false; + const time_text = parts.next() orelse return false; + const hash_text = parts.next() orelse return false; + if (date_text.len != 8 or time_text.len != 6 or hash_text.len != 8) return false; + while (parts.next()) |supplement| if (supplement.len == 0) return false; + for (date_text) |char| if (!std.ascii.isDigit(char)) return false; + for (time_text) |char| if (!std.ascii.isDigit(char)) return false; + for (hash_text) |char| if (!std.ascii.isHex(char)) return false; + const year = std.fmt.parseInt(u16, date_text[0..4], 10) catch return false; + const month = std.fmt.parseInt(u8, date_text[4..6], 10) catch return false; + const day = std.fmt.parseInt(u8, date_text[6..8], 10) catch return false; + if (month < 1 or month > 12) return false; + const leap = year % 4 == 0 and (year % 100 != 0 or year % 400 == 0); + const days = [_]u8{ 31, if (leap) 29 else 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }; + if (day < 1 or day > days[month - 1]) return false; + const hour = std.fmt.parseInt(u8, time_text[0..2], 10) catch return false; + const minute = std.fmt.parseInt(u8, time_text[2..4], 10) catch return false; + const second = std.fmt.parseInt(u8, time_text[4..6], 10) catch return false; + if (hour > 23 or minute > 59 or second > 59) return false; + const date = std.fmt.parseInt(u32, date_text, 10) catch return false; + return date >= required_date; +} + +fn applyKnownGraphicsIdentity(self: *Terminal) void { + if (!self.graphics_enabled or self.multiplexer != .none or !self.term_info.from_xtversion) return; + const name = self.getTerminalName(); + const version = self.getTerminalVersion(); + + if (std.ascii.eqlIgnoreCase(name, "kitty")) { + self.caps.kitty_graphics = true; + return; + } + if (std.ascii.eqlIgnoreCase(name, "ghostty")) { + self.caps.kitty_graphics = true; + return; + } + if (std.ascii.eqlIgnoreCase(name, "foot") and semanticVersionAtLeast(version, 1, 2)) { + self.caps.sixel = true; + return; + } + if (std.ascii.eqlIgnoreCase(name, "wezterm") and wezTermBuildAtLeast(version, 20200620)) { + self.caps.sixel = true; + } +} + pub fn processCapabilityResponse(self: *Terminal, response: []const u8) void { self.parseOsc99NotificationQuery(response); self.parseItermCapabilities(response); self.parseXtgettcapMs(response); + self.parseKittyGraphicsResponse(response); + self.parseSixelDeviceAttributes(response); // DECRPM responses if (std.mem.indexOf(u8, response, "1016;2$y")) |_| { @@ -1135,14 +1302,13 @@ pub fn processCapabilityResponse(self: *Terminal, response: []const u8) void { } } - // Kitty detection - if (std.mem.indexOf(u8, response, "kitty")) |_| { + // Exact Kitty identity features. Graphics identity is applied after a + // complete XTVERSION response determines the direct endpoint. + if (self.term_info.from_xtversion and std.ascii.eqlIgnoreCase(self.getTerminalName(), "kitty")) { self.caps.kitty_keyboard = true; - self.caps.kitty_graphics = true; self.caps.unicode = .unicode; self.caps.rgb = true; self.caps.ansi256 = true; - self.caps.sixel = true; self.caps.bracketed_paste = true; self.caps.hyperlinks = true; } @@ -1174,36 +1340,6 @@ pub fn processCapabilityResponse(self: *Terminal, response: []const u8) void { self.caps.explicit_cursor_positioning = true; } - // Sixel detection via device attributes (capability 4 in DA1 response ending with 'c') - if (std.mem.indexOf(u8, response, ";c")) |pos| { - var start: usize = 0; - if (pos >= 4) { - start = pos; - while (start > 0 and response[start] != '\x1b') { - start -= 1; - } - - const da_response = response[start .. pos + 2]; - - if (std.mem.indexOf(u8, da_response, "\x1b[?") == 0) { - if (std.mem.indexOf(u8, da_response, "4;") != null or std.mem.indexOf(u8, da_response, ";4;") != null or std.mem.indexOf(u8, da_response, ";4c") != null) { - self.caps.sixel = true; - } - } - } - } - - // Kitty graphics response: ESC_Gi=31337;OK ESC\ or ESC_Gi=31337;EERROR... ESC\ - // We look for our specific query ID (31337) to avoid false positives - if (std.mem.indexOf(u8, response, "\x1b_G")) |_| { - if (std.mem.indexOf(u8, response, "i=31337")) |_| { - // Got a response to our graphics query with our ID - // If it contains "OK" or even an error, the protocol is supported - // (errors mean the query was understood, just parameters were wrong) - self.caps.kitty_graphics = true; - } - } - if (!self.caps.osc52 and isOsc52Term(response)) { self.caps.osc52 = true; } @@ -1570,6 +1706,11 @@ fn canWriteClipboard(self: *Terminal) bool { fn parseXtversion(self: *Terminal, term_str: []const u8) void { if (term_str.len == 0) return; + self.term_info.name_len = 0; + self.term_info.version_len = 0; + self.caps.kitty_graphics = self.kitty_graphics_queried; + self.caps.sixel = self.sixel_queried; + if (std.mem.indexOf(u8, term_str, "(")) |paren_pos| { const name_len = @min(paren_pos, self.term_info.name.len); @memcpy(self.term_info.name[0..name_len], term_str[0..name_len]); @@ -1617,6 +1758,7 @@ fn parseXtversion(self: *Terminal, term_str: []const u8) void { } self.enforceNotificationProtocolForMultiplexer(); + self.applyKnownGraphicsIdentity(); } pub fn isXtversionTmux(self: *Terminal) bool { diff --git a/packages/core/src/zig/test.zig b/packages/core/src/zig/test.zig index 93883222a4..8bc3c9828d 100644 --- a/packages/core/src/zig/test.zig +++ b/packages/core/src/zig/test.zig @@ -36,6 +36,10 @@ const audio_tests = @import("tests/audio_test.zig"); const handles_tests = @import("tests/handles_test.zig"); const yoga_tests = @import("tests/yoga_test.zig"); const ansi_tests = @import("tests/ansi_test.zig"); +const image_tests = @import("tests/image_test.zig"); +const terminal_image_tests = @import("tests/terminal-image_test.zig"); +const lib_tests = @import("lib.zig"); +const clipboard_tests = @import("clipboard/host.zig"); // const example_tests = @import("example_test.zig"); // Re-export test declarations from individual test files @@ -78,5 +82,9 @@ comptime { _ = handles_tests; _ = yoga_tests; _ = ansi_tests; + _ = image_tests; + _ = terminal_image_tests; + _ = lib_tests; + _ = clipboard_tests; // _ = example_tests; } diff --git a/packages/core/src/zig/tests/buffer_test.zig b/packages/core/src/zig/tests/buffer_test.zig index 1b1e0923bf..7c2670b7c2 100644 --- a/packages/core/src/zig/tests/buffer_test.zig +++ b/packages/core/src/zig/tests/buffer_test.zig @@ -6,6 +6,7 @@ const gp = @import("../grapheme.zig"); const link = @import("../link.zig"); const ansi = @import("../ansi.zig"); const test_renderer_mod = @import("test-renderer.zig"); +const image = @import("../image.zig"); const OptimizedBuffer = buffer_mod.OptimizedBuffer; const TextBuffer = text_buffer.UnifiedTextBuffer; @@ -13,6 +14,587 @@ const TextBufferView = text_buffer_view.UnifiedTextBufferView; const RGBA = buffer_mod.RGBA; const TestRenderer = test_renderer_mod.TestRenderer; +test "OptimizedBuffer draws image reservation markers" { + var pool = gp.GraphemePool.init(std.testing.allocator); + defer pool.deinit(); + var link_pool = link.LinkPool.init(std.testing.allocator); + defer link_pool.deinit(); + const target = try OptimizedBuffer.init(std.testing.allocator, 2, 2, .{ .pool = &pool, .link_pool = &link_pool }); + defer target.deinit(); + const source = try image.createFromRgba(std.testing.allocator, &[_]u8{ + 255, 0, 0, 255, 0, 255, 0, 255, + 0, 0, 255, 255, 255, 255, 255, 255, + }, 2, 2, 8); + defer source.deinit(); + const before = target.get(0, 0).?; + try std.testing.expect(try target.drawImage(source, 1, 0, 0, 1, 1, 0, 0, 0, 0, 2, 2, .auto)); + const marker = target.get(0, 0).?; + try std.testing.expect(gp.isImageChar(marker.char)); + try std.testing.expectEqual(@as(u4, 0), gp.imageFallbackFromChar(marker.char)); + try std.testing.expect(buffer_mod.rgbaEqual(before.fg, marker.fg)); + try std.testing.expect(buffer_mod.rgbaEqual(before.bg, marker.bg)); +} + +test "OptimizedBuffer materializes block fallback on demand" { + var pool = gp.GraphemePool.init(std.testing.allocator); + defer pool.deinit(); + var link_pool = link.LinkPool.init(std.testing.allocator); + defer link_pool.deinit(); + const target = try OptimizedBuffer.init(std.testing.allocator, 1, 1, .{ .pool = &pool, .link_pool = &link_pool }); + defer target.deinit(); + const source = try image.createFromRgba(std.testing.allocator, &[_]u8{ + 255, 0, 0, 255, 0, 255, 0, 255, + 0, 0, 255, 255, 255, 255, 255, 255, + }, 2, 2, 8); + defer source.deinit(); + + try std.testing.expect(try target.drawImage(source, 1, 0, 0, 1, 1, 0, 0, 0, 0, 2, 2, .auto)); + target.materializeImageFallback(1); + const cell = target.get(0, 0).?; + try std.testing.expect(gp.isImageChar(cell.char)); + try std.testing.expect(gp.imageFallbackFromChar(cell.char) != 0); +} + +test "OptimizedBuffer flattens image placements into owned block cells" { + var pool = gp.GraphemePool.init(std.testing.allocator); + defer pool.deinit(); + var link_pool = link.LinkPool.init(std.testing.allocator); + defer link_pool.deinit(); + const target = try OptimizedBuffer.init(std.testing.allocator, 1, 1, .{ .pool = &pool, .link_pool = &link_pool }); + defer target.deinit(); + const source = try image.createFromRgba(std.testing.allocator, &[_]u8{ + 255, 0, 0, 255, 0, 255, 0, 255, + 0, 0, 255, 255, 255, 255, 255, 255, + }, 2, 2, 8); + defer source.deinit(); + + try std.testing.expect(try target.drawImage(source, 1, 0, 0, 1, 1, 0, 0, 0, 0, 2, 2, .kitty)); + try std.testing.expectEqual(@as(u32, 2), source.ref_count); + + target.materializeImageFallbacks(); + + const cell = target.get(0, 0).?; + try std.testing.expect(!gp.isImageChar(cell.char)); + try std.testing.expect(std.mem.indexOfScalar(u32, &buffer_mod.quadrantChars, cell.char) != null); + try std.testing.expectEqual(@as(usize, 0), target.image_placements.items.len); + try std.testing.expectEqual(@as(u32, 1), source.ref_count); +} + +test "OptimizedBuffer clips image placements and source crop to scissor" { + var pool = gp.GraphemePool.init(std.testing.allocator); + defer pool.deinit(); + var link_pool = link.LinkPool.init(std.testing.allocator); + defer link_pool.deinit(); + const target = try OptimizedBuffer.init(std.testing.allocator, 4, 2, .{ .pool = &pool, .link_pool = &link_pool }); + defer target.deinit(); + const source = try image.createFromRgba(std.testing.allocator, &([_]u8{ 255, 0, 0, 255 } ** 16), 4, 4, 16); + defer source.deinit(); + try target.pushScissorRect(1, 0, 2, 2); + try std.testing.expect(try target.drawImage(source, 1, -1, 0, 4, 2, 40, 20, 0, 0, 4, 4, .auto)); + const placement = target.image_placements.items[0]; + try std.testing.expectEqual(@as(i32, 1), placement.x); + try std.testing.expectEqual(@as(u32, 2), placement.width); + try std.testing.expectEqual(@as(u32, 2), placement.source_x); + try std.testing.expectEqual(@as(u32, 2), placement.source_width); + try std.testing.expectEqual(@as(u32, 20), placement.pixel_width); +} + +test "OptimizedBuffer retains image data for deferred protocol rendering" { + var pool = gp.GraphemePool.init(std.testing.allocator); + defer pool.deinit(); + var link_pool = link.LinkPool.init(std.testing.allocator); + defer link_pool.deinit(); + const target = try OptimizedBuffer.init(std.testing.allocator, 1, 1, .{ .pool = &pool, .link_pool = &link_pool }); + defer target.deinit(); + const source = try image.createFromRgba(std.testing.allocator, &[_]u8{ 7, 8, 9, 255 }, 1, 1, 4); + try std.testing.expect(try target.drawImage(source, 1, 0, 0, 1, 1, 1, 1, 0, 0, 1, 1, .auto)); + source.deinit(); + try std.testing.expectEqual(@as(u8, 7), target.image_placements.items[0].image.pixels[0]); +} + +test "OptimizedBuffer blocks fallback composites transparent images over lower placements" { + var pool = gp.GraphemePool.init(std.testing.allocator); + defer pool.deinit(); + var link_pool = link.LinkPool.init(std.testing.allocator); + defer link_pool.deinit(); + const target = try OptimizedBuffer.init(std.testing.allocator, 1, 1, .{ .pool = &pool, .link_pool = &link_pool }); + defer target.deinit(); + const lower = try image.createFromRgba(std.testing.allocator, &[_]u8{ 0, 0, 255, 255 }, 1, 1, 4); + defer lower.deinit(); + const upper = try image.createFromRgba(std.testing.allocator, &[_]u8{ 255, 0, 0, 0 }, 1, 1, 4); + defer upper.deinit(); + try std.testing.expect(try target.drawImage(lower, 1, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, .blocks)); + try std.testing.expect(try target.drawImage(upper, 2, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, .blocks)); + + target.materializeImageFallback(1); + target.materializeImageFallback(2); + + const cell = target.get(0, 0).?; + try std.testing.expectEqual(@as(u32, 2), gp.imageIdFromChar(cell.char)); + try std.testing.expectEqual(ansi.rgbColor(0, 0, 255, 255), cell.fg); + try std.testing.expectEqual(ansi.rgbColor(0, 0, 255, 255), cell.bg); +} + +test "OptimizedBuffer copies transparent image reservation markers from frame buffers" { + var pool = gp.GraphemePool.init(std.testing.allocator); + defer pool.deinit(); + var link_pool = link.LinkPool.init(std.testing.allocator); + defer link_pool.deinit(); + const source_buffer = try OptimizedBuffer.init(std.testing.allocator, 1, 1, .{ .pool = &pool, .link_pool = &link_pool }); + defer source_buffer.deinit(); + const target = try OptimizedBuffer.init(std.testing.allocator, 1, 1, .{ .pool = &pool, .link_pool = &link_pool }); + defer target.deinit(); + const source = try image.createFromRgba(std.testing.allocator, &[_]u8{ 7, 8, 9, 255 }, 1, 1, 4); + defer source.deinit(); + + try std.testing.expect(try source_buffer.drawImage(source, 1, 0, 0, 1, 1, 1, 1, 0, 0, 1, 1, .auto)); + target.drawFrameBuffer(0, 0, source_buffer, null, null, null, null); + try std.testing.expect(gp.isImageChar(target.get(0, 0).?.char)); + try std.testing.expectEqual(@as(usize, 1), target.image_placements.items.len); +} + +test "OptimizedBuffer flattening a framebuffer copy preserves source image ownership" { + var pool = gp.GraphemePool.init(std.testing.allocator); + defer pool.deinit(); + var link_pool = link.LinkPool.init(std.testing.allocator); + defer link_pool.deinit(); + const source_buffer = try OptimizedBuffer.init(std.testing.allocator, 1, 1, .{ .pool = &pool, .link_pool = &link_pool }); + defer source_buffer.deinit(); + const target = try OptimizedBuffer.init(std.testing.allocator, 1, 1, .{ .pool = &pool, .link_pool = &link_pool }); + defer target.deinit(); + const value = try image.createFromRgba(std.testing.allocator, &[_]u8{ 7, 8, 9, 255 }, 1, 1, 4); + defer value.deinit(); + + try std.testing.expect(try source_buffer.drawImage(value, 1, 0, 0, 1, 1, 1, 1, 0, 0, 1, 1, .auto)); + target.drawFrameBuffer(0, 0, source_buffer, null, null, null, null); + try std.testing.expectEqual(@as(u32, 3), value.ref_count); + + target.materializeImageFallbacks(); + + try std.testing.expect(!gp.isImageChar(target.get(0, 0).?.char)); + try std.testing.expectEqual(@as(usize, 0), target.image_placements.items.len); + try std.testing.expect(gp.isImageChar(source_buffer.get(0, 0).?.char)); + try std.testing.expectEqual(@as(usize, 1), source_buffer.image_placements.items.len); + try std.testing.expectEqual(@as(u32, 2), value.ref_count); +} + +test "OptimizedBuffer copies malformed image markers as fallback cells" { + var pool = gp.GraphemePool.init(std.testing.allocator); + defer pool.deinit(); + var link_pool = link.LinkPool.init(std.testing.allocator); + defer link_pool.deinit(); + const source_buffer = try OptimizedBuffer.init(std.testing.allocator, 2, 1, .{ .pool = &pool, .link_pool = &link_pool }); + defer source_buffer.deinit(); + const target = try OptimizedBuffer.init(std.testing.allocator, 2, 1, .{ .pool = &pool, .link_pool = &link_pool }); + defer target.deinit(); + const source = try image.createFromRgba(std.testing.allocator, &[_]u8{ 7, 8, 9, 255 }, 1, 1, 4); + defer source.deinit(); + try std.testing.expect(try source_buffer.drawImage(source, 1, 0, 0, 1, 1, 1, 1, 0, 0, 1, 1, .auto)); + source_buffer.setRaw(1, 0, .{ + .char = gp.packImageCell(100, 15), + .fg = ansi.rgbColor(1, 2, 3, 255), + .bg = ansi.rgbColor(4, 5, 6, 255), + .attributes = 0, + }); + + target.drawFrameBuffer(0, 0, source_buffer, null, null, null, null); + + try std.testing.expect(gp.isImageChar(target.get(0, 0).?.char)); + try std.testing.expectEqual(@as(u32, 0x2588), target.get(1, 0).?.char); +} + +test "OptimizedBuffer copies ordinary cells when image bookkeeping allocation fails" { + var pool = gp.GraphemePool.init(std.testing.allocator); + defer pool.deinit(); + var link_pool = link.LinkPool.init(std.testing.allocator); + defer link_pool.deinit(); + const source_buffer = try OptimizedBuffer.init(std.testing.allocator, 2, 1, .{ .pool = &pool, .link_pool = &link_pool }); + defer source_buffer.deinit(); + const target = try OptimizedBuffer.init(std.testing.allocator, 2, 1, .{ .pool = &pool, .link_pool = &link_pool }); + defer target.deinit(); + const source = try image.createFromRgba(std.testing.allocator, &[_]u8{ 7, 8, 9, 255 }, 1, 1, 4); + defer source.deinit(); + try std.testing.expect(try source_buffer.drawImage(source, 1, 0, 0, 1, 1, 1, 1, 0, 0, 1, 1, .auto)); + source_buffer.setRaw(1, 0, .{ + .char = 'X', + .fg = ansi.rgbColor(1, 2, 3, 255), + .bg = ansi.rgbColor(4, 5, 6, 255), + .attributes = 0, + }); + + for (0..2) |fail_index| { + target.clear(ansi.rgbColor(0, 0, 0, 255), null); + var failing = std.testing.FailingAllocator.init(std.testing.allocator, .{ .fail_index = fail_index }); + target.allocator = failing.allocator(); + target.drawFrameBuffer(0, 0, source_buffer, null, null, null, null); + target.allocator = std.testing.allocator; + + try std.testing.expect(failing.has_induced_failure); + try std.testing.expectEqual(@as(u32, 'X'), target.get(1, 0).?.char); + try std.testing.expect(!gp.isImageChar(target.get(0, 0).?.char)); + try std.testing.expectEqual(failing.allocated_bytes, failing.freed_bytes); + } +} + +test "OptimizedBuffer clips image geometry without signed overflow" { + var pool = gp.GraphemePool.init(std.testing.allocator); + defer pool.deinit(); + var link_pool = link.LinkPool.init(std.testing.allocator); + defer link_pool.deinit(); + const target = try OptimizedBuffer.init(std.testing.allocator, 2, 1, .{ .pool = &pool, .link_pool = &link_pool }); + defer target.deinit(); + const source = try image.createFromRgba(std.testing.allocator, &[_]u8{ 7, 8, 9, 255 }, 1, 1, 4); + defer source.deinit(); + + try std.testing.expect(!try target.drawImage( + source, + 1, + std.math.maxInt(i32), + std.math.maxInt(i32), + std.math.maxInt(u32), + std.math.maxInt(u32), + 0, + 0, + 0, + 0, + 1, + 1, + .auto, + )); +} + +test "OptimizedBuffer plane fills ignore color alpha over image markers" { + var pool = gp.GraphemePool.init(std.testing.allocator); + defer pool.deinit(); + var link_pool = link.LinkPool.init(std.testing.allocator); + defer link_pool.deinit(); + const source = try image.createFromRgba(std.testing.allocator, &[_]u8{ 7, 8, 9, 255 }, 1, 1, 4); + defer source.deinit(); + + for ([_]u8{ 0, 128, 255 }) |alpha| { + const target = try OptimizedBuffer.init(std.testing.allocator, 2, 1, .{ .pool = &pool, .link_pool = &link_pool }); + defer target.deinit(); + try std.testing.expect(try target.drawImage(source, 1, 0, 0, 2, 1, 0, 0, 0, 0, 1, 1, .auto)); + + target.fillRect(0, 0, 1, 1, ansi.rgbColor(10, 20, 30, alpha)); + + const covered = target.get(0, 0).?; + try std.testing.expectEqual(@as(u32, ' '), covered.char); + try std.testing.expectEqual(ansi.rgbColor(10, 20, 30, 255), covered.bg); + try std.testing.expect(gp.isImageChar(target.get(1, 0).?.char)); + try std.testing.expectEqual(@as(usize, 1), target.image_placements.items.len); + } +} + +test "OptimizedBuffer transparent drawChar only writes over an image marker" { + var pool = gp.GraphemePool.init(std.testing.allocator); + defer pool.deinit(); + var link_pool = link.LinkPool.init(std.testing.allocator); + defer link_pool.deinit(); + const target = try OptimizedBuffer.init(std.testing.allocator, 2, 1, .{ .pool = &pool, .link_pool = &link_pool }); + defer target.deinit(); + const source = try image.createFromRgba(std.testing.allocator, &[_]u8{ 7, 8, 9, 255 }, 1, 1, 4); + defer source.deinit(); + target.setRaw(1, 0, .{ + .char = 'B', + .fg = ansi.rgbColor(1, 2, 3, 255), + .bg = ansi.rgbColor(4, 5, 6, 255), + .attributes = 0, + }); + try std.testing.expect(try target.drawImage(source, 1, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, .auto)); + + target.drawChar('X', 0, 0, ansi.rgbColor(40, 50, 60, 0), ansi.rgbColor(10, 20, 30, 0), 0); + target.drawChar('X', 1, 0, ansi.rgbColor(40, 50, 60, 0), ansi.rgbColor(10, 20, 30, 0), 0); + + const covered = target.get(0, 0).?; + try std.testing.expectEqual(@as(u32, 'X'), covered.char); + try std.testing.expectEqual(@as(u8, 255), ansi.alpha(covered.fg)); + try std.testing.expectEqual(@as(u8, 255), ansi.alpha(covered.bg)); + try std.testing.expectEqual(@as(u32, 'B'), target.get(1, 0).?.char); +} + +test "OptimizedBuffer transparent text space covers an image marker" { + var pool = gp.GraphemePool.init(std.testing.allocator); + defer pool.deinit(); + var link_pool = link.LinkPool.init(std.testing.allocator); + defer link_pool.deinit(); + const target = try OptimizedBuffer.init(std.testing.allocator, 1, 1, .{ .pool = &pool, .link_pool = &link_pool }); + defer target.deinit(); + const source = try image.createFromRgba(std.testing.allocator, &[_]u8{ 7, 8, 9, 255 }, 1, 1, 4); + defer source.deinit(); + try std.testing.expect(try target.drawImage(source, 1, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, .auto)); + + try target.drawText(" ", 0, 0, ansi.rgbColor(40, 50, 60, 64), ansi.rgbColor(10, 20, 30, 0), 0); + + const cell = target.get(0, 0).?; + try std.testing.expectEqual(@as(u32, ' '), cell.char); + try std.testing.expectEqual(ansi.rgbColor(10, 20, 30, 255), cell.bg); + try std.testing.expectEqual(ansi.rgbColor(40, 50, 60, 255), cell.fg); +} + +test "OptimizedBuffer text ignores foreground alpha over an image marker" { + var pool = gp.GraphemePool.init(std.testing.allocator); + defer pool.deinit(); + var link_pool = link.LinkPool.init(std.testing.allocator); + defer link_pool.deinit(); + const target = try OptimizedBuffer.init(std.testing.allocator, 1, 1, .{ .pool = &pool, .link_pool = &link_pool }); + defer target.deinit(); + const source = try image.createFromRgba(std.testing.allocator, &[_]u8{ 7, 8, 9, 255 }, 1, 1, 4); + defer source.deinit(); + try std.testing.expect(try target.drawImage(source, 1, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, .auto)); + + try target.drawText("X", 0, 0, ansi.rgbColor(40, 50, 60, 64), ansi.rgbColor(10, 20, 30, 255), 0); + + const cell = target.get(0, 0).?; + try std.testing.expectEqual(@as(u32, 'X'), cell.char); + try std.testing.expectEqual(ansi.rgbColor(10, 20, 30, 255), cell.bg); + try std.testing.expectEqual(ansi.rgbColor(40, 50, 60, 255), cell.fg); +} + +test "OptimizedBuffer transparent tab covers only clipped image markers" { + var pool = gp.GraphemePool.init(std.testing.allocator); + defer pool.deinit(); + var link_pool = link.LinkPool.init(std.testing.allocator); + defer link_pool.deinit(); + const target = try OptimizedBuffer.init(std.testing.allocator, 2, 1, .{ .pool = &pool, .link_pool = &link_pool }); + defer target.deinit(); + const source = try image.createFromRgba(std.testing.allocator, &[_]u8{ 7, 8, 9, 255 }, 1, 1, 4); + defer source.deinit(); + try std.testing.expect(try target.drawImage(source, 1, 0, 0, 2, 1, 0, 0, 0, 0, 1, 1, .auto)); + try target.pushScissorRect(0, 0, 1, 1); + + try target.drawText("\t", 0, 0, ansi.rgbColor(40, 50, 60, 0), ansi.rgbColor(10, 20, 30, 0), 0); + + try std.testing.expectEqual(@as(u32, ' '), target.get(0, 0).?.char); + try std.testing.expectEqual(ansi.rgbColor(10, 20, 30, 255), target.get(0, 0).?.bg); + try std.testing.expect(gp.isImageChar(target.get(1, 0).?.char)); +} + +test "OptimizedBuffer transparent tab covers image markers after its clipped start" { + var pool = gp.GraphemePool.init(std.testing.allocator); + defer pool.deinit(); + var link_pool = link.LinkPool.init(std.testing.allocator); + defer link_pool.deinit(); + const target = try OptimizedBuffer.init(std.testing.allocator, 2, 1, .{ .pool = &pool, .link_pool = &link_pool }); + defer target.deinit(); + const source = try image.createFromRgba(std.testing.allocator, &[_]u8{ 7, 8, 9, 255 }, 1, 1, 4); + defer source.deinit(); + try std.testing.expect(try target.drawImage(source, 1, 0, 0, 2, 1, 0, 0, 0, 0, 1, 1, .auto)); + try target.pushScissorRect(1, 0, 1, 1); + + try target.drawText("\t", 0, 0, ansi.rgbColor(40, 50, 60, 0), ansi.rgbColor(10, 20, 30, 0), 0); + + try std.testing.expect(gp.isImageChar(target.get(0, 0).?.char)); + try std.testing.expectEqual(@as(u32, ' '), target.get(1, 0).?.char); + try std.testing.expectEqual(ansi.rgbColor(10, 20, 30, 255), target.get(1, 0).?.bg); +} + +test "OptimizedBuffer transparent box border covers only clipped image markers" { + var pool = gp.GraphemePool.init(std.testing.allocator); + defer pool.deinit(); + var link_pool = link.LinkPool.init(std.testing.allocator); + defer link_pool.deinit(); + const target = try OptimizedBuffer.init(std.testing.allocator, 2, 1, .{ .pool = &pool, .link_pool = &link_pool }); + defer target.deinit(); + const source = try image.createFromRgba(std.testing.allocator, &[_]u8{ 7, 8, 9, 255 }, 1, 1, 4); + defer source.deinit(); + try std.testing.expect(try target.drawImage(source, 1, 0, 0, 2, 1, 0, 0, 0, 0, 1, 1, .auto)); + try target.pushScissorRect(0, 0, 1, 1); + const border_chars = [_]u32{ '┌', '┐', '└', '┘', '─', '│', '┬', '┴', '├', '┤', '┼' }; + + try target.drawBox(0, 0, 2, 1, &border_chars, .{ .top = true }, ansi.rgbColor(40, 50, 60, 0), ansi.rgbColor(10, 20, 30, 0), ansi.rgbColor(40, 50, 60, 0), false, null, 0, null, 0); + + try std.testing.expect(!gp.isImageChar(target.get(0, 0).?.char)); + try std.testing.expectEqual(ansi.rgbColor(10, 20, 30, 255), target.get(0, 0).?.bg); + try std.testing.expectEqual(ansi.rgbColor(40, 50, 60, 255), target.get(0, 0).?.fg); + try std.testing.expect(gp.isImageChar(target.get(1, 0).?.char)); +} + +test "OptimizedBuffer clipped wide text does not cover image markers" { + var pool = gp.GraphemePool.init(std.testing.allocator); + defer pool.deinit(); + var link_pool = link.LinkPool.init(std.testing.allocator); + defer link_pool.deinit(); + const target = try OptimizedBuffer.init(std.testing.allocator, 2, 1, .{ .pool = &pool, .link_pool = &link_pool }); + defer target.deinit(); + const source = try image.createFromRgba(std.testing.allocator, &[_]u8{ 7, 8, 9, 255 }, 1, 1, 4); + defer source.deinit(); + try std.testing.expect(try target.drawImage(source, 1, 0, 0, 2, 1, 0, 0, 0, 0, 1, 1, .auto)); + try target.pushScissorRect(0, 0, 1, 1); + + try target.drawText("界", 0, 0, ansi.rgbColor(40, 50, 60, 0), ansi.rgbColor(10, 20, 30, 0), 0); + + try std.testing.expect(gp.isImageChar(target.get(0, 0).?.char)); + try std.testing.expect(gp.isImageChar(target.get(1, 0).?.char)); +} + +test "OptimizedBuffer wide text is opaque when its continuation covers an image marker" { + var pool = gp.GraphemePool.init(std.testing.allocator); + defer pool.deinit(); + var link_pool = link.LinkPool.init(std.testing.allocator); + defer link_pool.deinit(); + const target = try OptimizedBuffer.init(std.testing.allocator, 2, 1, .{ .pool = &pool, .link_pool = &link_pool }); + defer target.deinit(); + const source = try image.createFromRgba(std.testing.allocator, &[_]u8{ 7, 8, 9, 255 }, 1, 1, 4); + defer source.deinit(); + try std.testing.expect(try target.drawImage(source, 1, 1, 0, 1, 1, 0, 0, 0, 0, 1, 1, .auto)); + + try target.drawText("界", 0, 0, ansi.rgbColor(40, 50, 60, 64), ansi.rgbColor(10, 20, 30, 0), 0); + + try std.testing.expect(gp.isGraphemeChar(target.get(0, 0).?.char)); + try std.testing.expect(gp.isContinuationChar(target.get(1, 0).?.char)); + try std.testing.expectEqual(@as(u8, 255), ansi.alpha(target.get(0, 0).?.fg)); + try std.testing.expectEqual(@as(u8, 255), ansi.alpha(target.get(0, 0).?.bg)); +} + +test "OptimizedBuffer clipped wide text buffer does not cover image markers" { + const pool = gp.initGlobalPool(std.testing.allocator); + defer gp.deinitGlobalPool(); + var local_link_pool = link.LinkPool.init(std.testing.allocator); + defer local_link_pool.deinit(); + var text = try TextBuffer.init(std.testing.allocator, pool, &local_link_pool, .unicode); + defer text.deinit(); + try text.setText("界"); + var view = try TextBufferView.init(std.testing.allocator, text); + defer view.deinit(); + + const target = try OptimizedBuffer.init(std.testing.allocator, 2, 1, .{ .pool = pool, .id = "clipped-wide-text-buffer" }); + defer target.deinit(); + const source = try image.createFromRgba(std.testing.allocator, &[_]u8{ 7, 8, 9, 255 }, 1, 1, 4); + defer source.deinit(); + try std.testing.expect(try target.drawImage(source, 1, 0, 0, 2, 1, 0, 0, 0, 0, 1, 1, .auto)); + try target.pushScissorRect(0, 0, 1, 1); + + target.drawTextBuffer(view, 0, 0); + + try std.testing.expect(gp.isImageChar(target.get(0, 0).?.char)); + try std.testing.expect(gp.isImageChar(target.get(1, 0).?.char)); +} + +test "OptimizedBuffer text buffer does not draw a wide grapheme past its viewport" { + const pool = gp.initGlobalPool(std.testing.allocator); + defer gp.deinitGlobalPool(); + var local_link_pool = link.LinkPool.init(std.testing.allocator); + defer local_link_pool.deinit(); + var text = try TextBuffer.init(std.testing.allocator, pool, &local_link_pool, .unicode); + defer text.deinit(); + try text.setText("界"); + var view = try TextBufferView.init(std.testing.allocator, text); + defer view.deinit(); + view.setViewport(.{ .x = 0, .y = 0, .width = 1, .height = 1 }); + + const target = try OptimizedBuffer.init(std.testing.allocator, 2, 1, .{ .pool = pool, .id = "wide-text-buffer-viewport" }); + defer target.deinit(); + const source = try image.createFromRgba(std.testing.allocator, &[_]u8{ 7, 8, 9, 255 }, 1, 1, 4); + defer source.deinit(); + try std.testing.expect(try target.drawImage(source, 1, 0, 0, 2, 1, 0, 0, 0, 0, 1, 1, .auto)); + + target.drawTextBuffer(view, 0, 0); + + try std.testing.expect(gp.isImageChar(target.get(0, 0).?.char)); + try std.testing.expect(gp.isImageChar(target.get(1, 0).?.char)); +} + +test "OptimizedBuffer text buffer tab covers image markers after its clipped start" { + const pool = gp.initGlobalPool(std.testing.allocator); + defer gp.deinitGlobalPool(); + var local_link_pool = link.LinkPool.init(std.testing.allocator); + defer local_link_pool.deinit(); + var text = try TextBuffer.init(std.testing.allocator, pool, &local_link_pool, .unicode); + defer text.deinit(); + try text.setText("\t"); + var view = try TextBufferView.init(std.testing.allocator, text); + defer view.deinit(); + + const target = try OptimizedBuffer.init(std.testing.allocator, 2, 1, .{ .pool = pool, .id = "clipped-text-buffer-tab" }); + defer target.deinit(); + const source = try image.createFromRgba(std.testing.allocator, &[_]u8{ 7, 8, 9, 255 }, 1, 1, 4); + defer source.deinit(); + try std.testing.expect(try target.drawImage(source, 1, 0, 0, 2, 1, 0, 0, 0, 0, 1, 1, .auto)); + try target.pushScissorRect(1, 0, 1, 1); + + target.drawTextBuffer(view, 0, 0); + + try std.testing.expect(gp.isImageChar(target.get(0, 0).?.char)); + try std.testing.expectEqual(@as(u32, ' '), target.get(1, 0).?.char); +} + +test "OptimizedBuffer text buffer tab clips a negative draw origin" { + const pool = gp.initGlobalPool(std.testing.allocator); + defer gp.deinitGlobalPool(); + var local_link_pool = link.LinkPool.init(std.testing.allocator); + defer local_link_pool.deinit(); + var text = try TextBuffer.init(std.testing.allocator, pool, &local_link_pool, .unicode); + defer text.deinit(); + try text.setText("\t"); + var view = try TextBufferView.init(std.testing.allocator, text); + defer view.deinit(); + + const target = try OptimizedBuffer.init(std.testing.allocator, 1, 1, .{ .pool = pool, .id = "negative-text-buffer-tab" }); + defer target.deinit(); + const source = try image.createFromRgba(std.testing.allocator, &[_]u8{ 7, 8, 9, 255 }, 1, 1, 4); + defer source.deinit(); + try std.testing.expect(try target.drawImage(source, 1, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, .auto)); + + target.drawTextBuffer(view, -1, 0); + + try std.testing.expectEqual(@as(u32, ' '), target.get(0, 0).?.char); +} + +test "OptimizedBuffer image-free frame buffer copy does not allocate" { + var pool = gp.GraphemePool.init(std.testing.allocator); + defer pool.deinit(); + var link_pool = link.LinkPool.init(std.testing.allocator); + defer link_pool.deinit(); + const source = try OptimizedBuffer.init(std.testing.allocator, 1, 1, .{ .pool = &pool, .link_pool = &link_pool }); + defer source.deinit(); + const target = try OptimizedBuffer.init(std.testing.allocator, 1, 1, .{ .pool = &pool, .link_pool = &link_pool }); + defer target.deinit(); + + source.set(0, 0, .{ + .char = 'X', + .fg = ansi.rgbColor(1, 2, 3, 255), + .bg = ansi.rgbColor(4, 5, 6, 255), + .attributes = 7, + }); + var failing = std.testing.FailingAllocator.init(std.testing.allocator, .{ .fail_index = 0 }); + target.allocator = failing.allocator(); + target.drawFrameBuffer(0, 0, source, null, null, null, null); + target.allocator = std.testing.allocator; + + try std.testing.expect(!failing.has_induced_failure); + try std.testing.expectEqual(@as(u32, 'X'), target.get(0, 0).?.char); +} + +test "OptimizedBuffer image-free alpha frame buffer copy does not allocate" { + var pool = gp.GraphemePool.init(std.testing.allocator); + defer pool.deinit(); + var link_pool = link.LinkPool.init(std.testing.allocator); + defer link_pool.deinit(); + const source = try OptimizedBuffer.init(std.testing.allocator, 1, 1, .{ + .pool = &pool, + .link_pool = &link_pool, + .respectAlpha = true, + }); + defer source.deinit(); + const target = try OptimizedBuffer.init(std.testing.allocator, 1, 1, .{ .pool = &pool, .link_pool = &link_pool }); + defer target.deinit(); + + source.set(0, 0, .{ + .char = 'X', + .fg = ansi.rgbColor(1, 2, 3, 255), + .bg = ansi.rgbColor(4, 5, 6, 255), + .attributes = 7, + }); + var failing = std.testing.FailingAllocator.init(std.testing.allocator, .{ .fail_index = 0 }); + target.allocator = failing.allocator(); + target.drawFrameBuffer(0, 0, source, null, null, null, null); + target.allocator = std.testing.allocator; + + try std.testing.expect(!failing.has_induced_failure); + try std.testing.expectEqual(@as(u32, 'X'), target.get(0, 0).?.char); +} + fn initBufferForOomRegression(allocator: std.mem.Allocator) !void { var local_pool = gp.GraphemePool.initWithOptions(allocator, .{}); defer local_pool.deinit(); @@ -2924,3 +3506,89 @@ test "renderer - CJK graphemes shifting left must preserve continuation cells (# const id8 = gp.graphemeIdFromChar(cell8.char); try std.testing.expectEqual(id7, id8); } + +test "OptimizedBuffer merges frame buffer placements with clipping scissor and opacity" { + var pool = gp.GraphemePool.init(std.testing.allocator); + defer pool.deinit(); + var link_pool = link.LinkPool.init(std.testing.allocator); + defer link_pool.deinit(); + const source_buffer = try OptimizedBuffer.init(std.testing.allocator, 6, 4, .{ .pool = &pool, .link_pool = &link_pool }); + defer source_buffer.deinit(); + const target = try OptimizedBuffer.init(std.testing.allocator, 4, 4, .{ .pool = &pool, .link_pool = &link_pool }); + defer target.deinit(); + + const wide = try image.createFromRgba(std.testing.allocator, &([_]u8{ 10, 20, 30, 255 } ** 32), 8, 4, 32); + defer wide.deinit(); + const dot = try image.createFromRgba(std.testing.allocator, &[_]u8{ 1, 2, 3, 255 }, 1, 1, 4); + defer dot.deinit(); + + // Placement A covers cells (1,1)-(4,2) of the frame buffer; placement B + // sits at (5,3) and will fall entirely outside the destination clip. + try std.testing.expect(try source_buffer.drawImage(wide, 41, 1, 1, 4, 2, 8, 4, 0, 0, 8, 4, .auto)); + try std.testing.expect(try source_buffer.drawImage(dot, 42, 5, 3, 1, 1, 1, 1, 0, 0, 1, 1, .auto)); + + // The target already owns a direct placement, so merged ids must shift. + try std.testing.expect(try target.drawImage(dot, 43, 0, 0, 1, 1, 1, 1, 0, 0, 1, 1, .auto)); + + try target.pushScissorRect(0, 0, 4, 2); + try target.pushOpacity(0.5); + target.drawFrameBuffer(2, 0, source_buffer, null, null, null, null); + target.popOpacity(); + target.popScissorRect(); + + try std.testing.expectEqual(@as(usize, 2), target.image_placements.items.len); + const direct = target.image_placements.items[0]; + try std.testing.expectEqual(@as(u32, 1), direct.placement_id); + try std.testing.expectEqual(@as(u32, 43), direct.image_handle); + + const merged = target.image_placements.items[1]; + try std.testing.expectEqual(@as(u32, 2), merged.placement_id); + try std.testing.expectEqual(@as(u32, 41), merged.image_handle); + try std.testing.expectEqual(@as(i32, 3), merged.x); + try std.testing.expectEqual(@as(i32, 1), merged.y); + try std.testing.expectEqual(@as(u32, 1), merged.width); + try std.testing.expectEqual(@as(u32, 1), merged.height); + try std.testing.expectEqual(@as(u32, 2), merged.pixel_width); + try std.testing.expectEqual(@as(u32, 2), merged.pixel_height); + try std.testing.expectEqual(@as(u32, 0), merged.source_x); + try std.testing.expectEqual(@as(u32, 0), merged.source_y); + try std.testing.expectEqual(@as(u32, 2), merged.source_width); + try std.testing.expectEqual(@as(u32, 2), merged.source_height); + try std.testing.expectEqual(@as(u8, 128), merged.opacity); + + // Cells: the direct placement keeps id 1, the merged visible cell maps to + // id 2, and cells outside the scissor were not copied. + try std.testing.expectEqual(@as(u32, 1), gp.imageIdFromChar(target.get(0, 0).?.char)); + try std.testing.expectEqual(@as(u32, 2), gp.imageIdFromChar(target.get(3, 1).?.char)); + try std.testing.expect(!gp.isImageChar(target.get(3, 2).?.char)); + + // Placement B was clipped away entirely. + for (target.image_placements.items) |placement| { + try std.testing.expect(placement.image_handle != 42); + } +} + +test "OptimizedBuffer frame buffer merge multiplies nested placement opacity" { + var pool = gp.GraphemePool.init(std.testing.allocator); + defer pool.deinit(); + var link_pool = link.LinkPool.init(std.testing.allocator); + defer link_pool.deinit(); + const source_buffer = try OptimizedBuffer.init(std.testing.allocator, 2, 1, .{ .pool = &pool, .link_pool = &link_pool }); + defer source_buffer.deinit(); + const target = try OptimizedBuffer.init(std.testing.allocator, 2, 1, .{ .pool = &pool, .link_pool = &link_pool }); + defer target.deinit(); + const dot = try image.createFromRgba(std.testing.allocator, &[_]u8{ 1, 2, 3, 255 }, 1, 1, 4); + defer dot.deinit(); + + try source_buffer.pushOpacity(0.5); + try std.testing.expect(try source_buffer.drawImage(dot, 44, 0, 0, 1, 1, 1, 1, 0, 0, 1, 1, .auto)); + source_buffer.popOpacity(); + try std.testing.expectEqual(@as(u8, 128), source_buffer.image_placements.items[0].opacity); + + try target.pushOpacity(0.5); + target.drawFrameBuffer(0, 0, source_buffer, null, null, null, null); + target.popOpacity(); + try std.testing.expectEqual(@as(usize, 1), target.image_placements.items.len); + // 0.5 * 0.5 = 0.25 -> 64 of 255. + try std.testing.expect(@abs(@as(i16, target.image_placements.items[0].opacity) - 64) <= 1); +} diff --git a/packages/core/src/zig/tests/grapheme_test.zig b/packages/core/src/zig/tests/grapheme_test.zig index f4c9992198..2bac0c1983 100644 --- a/packages/core/src/zig/tests/grapheme_test.zig +++ b/packages/core/src/zig/tests/grapheme_test.zig @@ -4,6 +4,15 @@ const gp = @import("../grapheme.zig"); const GraphemePool = gp.GraphemePool; const GraphemeTracker = gp.GraphemeTracker; +test "image cell markers use the unused character tag" { + const marker = gp.packImageCell(12345, 9); + try std.testing.expect(gp.isImageChar(marker)); + try std.testing.expect(!gp.isGraphemeChar(marker)); + try std.testing.expect(!gp.isContinuationChar(marker)); + try std.testing.expectEqual(@as(u32, 12345), gp.imageIdFromChar(marker)); + try std.testing.expectEqual(@as(u4, 9), gp.imageFallbackFromChar(marker)); +} + test "GraphemePool - can initialize and cleanup" { // Just verify init/deinit don't crash var pool = GraphemePool.init(std.testing.allocator); diff --git a/packages/core/src/zig/tests/image_test.zig b/packages/core/src/zig/tests/image_test.zig new file mode 100644 index 0000000000..e4bd7af075 --- /dev/null +++ b/packages/core/src/zig/tests/image_test.zig @@ -0,0 +1,611 @@ +const std = @import("std"); +const image = @import("../image.zig"); + +fn makeImage(pixels: []const u8, width: u32, height: u32) !*image.Image { + return image.createFromRgba(std.testing.allocator, pixels, width, height, width * 4); +} + +fn decodeBase64(encoded: []const u8) ![]u8 { + const size = try std.base64.standard.Decoder.calcSizeForSlice(encoded); + const decoded = try std.testing.allocator.alloc(u8, size); + errdefer std.testing.allocator.free(decoded); + try std.base64.standard.Decoder.decode(decoded, encoded); + return decoded; +} + +test "PNG probe and decode return canonical red RGBA" { + const encoded = "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR4AWP4z8DwHwAFAAH/e+m+7wAAAABJRU5ErkJggg=="; + const png = try decodeBase64(encoded); + defer std.testing.allocator.free(png); + + var info: image.Info = .{}; + try std.testing.expectEqual(image.Status.ok, image.probe(png, .{}, &info)); + try std.testing.expectEqual(@as(u32, 1), info.width); + try std.testing.expectEqual(@as(u32, 1), info.height); + try std.testing.expectEqual(@as(u32, 1), info.has_alpha); + + const decoded = try image.decode(std.testing.allocator, png, .{}); + defer decoded.deinit(); + try std.testing.expectEqualSlices(u8, &[_]u8{ 255, 0, 0, 255 }, decoded.pixels); +} + +test "PNG rejects cICP after image data" { + const png = try decodeBase64("iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR4AWP4z8DwHwAFAAH/e+m+7wAAAABJRU5ErkJggg=="); + defer std.testing.allocator.free(png); + + var chunk = [_]u8{ 0, 0, 0, 4, 'c', 'I', 'C', 'P', 1, 13, 0, 1, 0, 0, 0, 0 }; + const crc = std.hash.Crc32.hash(chunk[4..12]); + std.mem.writeInt(u32, chunk[12..16], crc, .big); + const late = try std.testing.allocator.alloc(u8, png.len + chunk.len); + defer std.testing.allocator.free(late); + const iend = png.len - 12; + @memcpy(late[0..iend], png[0..iend]); + @memcpy(late[iend .. iend + chunk.len], &chunk); + @memcpy(late[iend + chunk.len ..], png[iend..]); + + var info: image.Info = .{}; + try std.testing.expectEqual(image.Status.malformed_input, image.probe(late, .{}, &info)); + try std.testing.expectError(error.MalformedInput, image.decode(std.testing.allocator, late, .{})); +} + +test "GIF probe and first frame decode preserve logical canvas transparency" { + const gif = try decodeBase64("R0lGODlhAgACAPAAAAAAAP8AACH5BAEAAAAALAAAAAACAAIAAAIDDBAFADs="); + defer std.testing.allocator.free(gif); + var info: image.Info = .{}; + try std.testing.expectEqual(image.Status.ok, image.probe(gif, .{}, &info)); + try std.testing.expectEqual(@as(u32, @intFromEnum(image.Format.gif)), info.format); + try std.testing.expectEqual(@as(u32, 2), info.width); + try std.testing.expectEqual(@as(u32, 2), info.height); + try std.testing.expectEqual(@as(u32, 1), info.has_alpha); + + const decoded = try image.decode(std.testing.allocator, gif, .{}); + defer decoded.deinit(); + try std.testing.expectEqual(info, decoded.info()); + try std.testing.expectEqualSlices(u8, &[_]u8{ + 255, 0, 0, 255, 0, 0, 0, 0, + 0, 0, 0, 0, 255, 0, 0, 255, + }, decoded.pixels); +} + +test "GIF first frame offset exposes the logical background palette index" { + const encoded = "R0lGODlhAwADAPAAAP8AAAAAACH5BAAAAAAALAEAAQABAAEAAAICRAEAOw=="; + const gif = try decodeBase64(encoded); + defer std.testing.allocator.free(gif); + gif[11] = 1; + + const decoded = try image.decode(std.testing.allocator, gif, .{}); + defer decoded.deinit(); + try std.testing.expectEqual(@as(u32, 3), decoded.width()); + try std.testing.expectEqual(@as(u32, 3), decoded.height()); + const center = (1 * 3 + 1) * 4; + try std.testing.expectEqualSlices(u8, &[_]u8{ 0, 0, 0, 255 }, decoded.pixels[0..4]); + try std.testing.expectEqualSlices(u8, &[_]u8{ 255, 0, 0, 255 }, decoded.pixels[center .. center + 4]); +} + +test "animated GIF decode returns only the first displayed frame" { + const gif = try decodeBase64("R0lGODlhAgACAPAAAP8AAAAAACH/C05FVFNDQVBFMi4wAwEAAAAh+QQACgAAACwAAAAAAgACAAACAoRRACH5BAAKAAAALAAAAAACAAIAgAAA/wAAAAIChFEAOw=="); + defer std.testing.allocator.free(gif); + const decoded = try image.decode(std.testing.allocator, gif, .{}); + defer decoded.deinit(); + var offset: usize = 0; + while (offset < decoded.pixels.len) : (offset += 4) { + try std.testing.expectEqualSlices(u8, &[_]u8{ 255, 0, 0, 255 }, decoded.pixels[offset .. offset + 4]); + } +} + +test "baseline and progressive JPEG decode to opaque RGBA" { + const fixtures = [_][]const u8{ + "/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDAAMCAgMCAgMDAwMEAwMEBQgFBQQEBQoHBwYIDAoMDAsKCwsNDhIQDQ4RDgsLEBYQERMUFRUVDA8XGBYUGBIUFRT/2wBDAQMEBAUEBQkFBQkUDQsNFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBT/wAARCAACAAMDAREAAhEBAxEB/8QAFAABAAAAAAAAAAAAAAAAAAAACP/EABQQAQAAAAAAAAAAAAAAAAAAAAD/xAAVAQEBAAAAAAAAAAAAAAAAAAAHCf/EABQRAQAAAAAAAAAAAAAAAAAAAAD/2gAMAwEAAhEDEQA/ADoDFU3/2Q==", + "/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDAAMCAgMCAgMDAwMEAwMEBQgFBQQEBQoHBwYIDAoMDAsKCwsNDhIQDQ4RDgsLEBYQERMUFRUVDA8XGBYUGBIUFRT/2wBDAQMEBAUEBQkFBQkUDQsNFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBT/wgARCAACAAMDAREAAhEBAxEB/8QAFAABAAAAAAAAAAAAAAAAAAAAB//EABUBAQEAAAAAAAAAAAAAAAAAAAYI/9oADAMBAAIQAxAAAAE5C1T/AP/EABQQAQAAAAAAAAAAAAAAAAAAAAD/2gAIAQEAAQUCf//EABQRAQAAAAAAAAAAAAAAAAAAAAD/2gAIAQMBAT8Bf//EABQRAQAAAAAAAAAAAAAAAAAAAAD/2gAIAQIBAT8Bf//EABQQAQAAAAAAAAAAAAAAAAAAAAD/2gAIAQEABj8Cf//EABQQAQAAAAAAAAAAAAAAAAAAAAD/2gAIAQEAAT8hf//aAAwDAQACAAMAAAAQ/wD/xAAUEQEAAAAAAAAAAAAAAAAAAAAA/9oACAEDAQE/EH//xAAUEQEAAAAAAAAAAAAAAAAAAAAA/9oACAECAQE/EH//xAAUEAEAAAAAAAAAAAAAAAAAAAAA/9oACAEBAAE/EH//2Q==", + }; + for (fixtures) |encoded| { + const jpeg = try decodeBase64(encoded); + defer std.testing.allocator.free(jpeg); + var info: image.Info = .{}; + try std.testing.expectEqual(image.Status.ok, image.probe(jpeg, .{}, &info)); + try std.testing.expectEqual(@as(u32, @intFromEnum(image.Format.jpeg)), info.format); + try std.testing.expectEqual(@as(u32, 3), info.width); + try std.testing.expectEqual(@as(u32, 2), info.height); + try std.testing.expectEqual(@as(u32, 0), info.has_alpha); + const decoded = try image.decode(std.testing.allocator, jpeg, .{}); + defer decoded.deinit(); + try std.testing.expectEqual(info, decoded.info()); + for (decoded.pixels[3..], 0..) |channel, index| { + if (index % 4 == 0) try std.testing.expectEqual(@as(u8, 255), channel); + } + } +} + +test "JPEG decode rejects EOI bytes embedded in a comment without a terminal EOI marker" { + const encoded = "/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDAAMCAgMCAgMDAwMEAwMEBQgFBQQEBQoHBwYIDAoMDAsKCwsNDhIQDQ4RDgsLEBYQERMUFRUVDA8XGBYUGBIUFRT/2wBDAQMEBAUEBQkFBQkUDQsNFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBT/wAARCAACAAMDAREAAhEBAxEB/8QAFAABAAAAAAAAAAAAAAAAAAAACP/EABQQAQAAAAAAAAAAAAAAAAAAAAD/xAAVAQEBAAAAAAAAAAAAAAAAAAAHCf/EABQRAQAAAAAAAAAAAAAAAAAAAAD/2gAMAwEAAhEDEQA/ADoDFU3/2Q=="; + const jpeg = try decodeBase64(encoded); + defer std.testing.allocator.free(jpeg); + + const malformed = try std.testing.allocator.alloc(u8, jpeg.len + 4); + defer std.testing.allocator.free(malformed); + @memcpy(malformed[0..2], jpeg[0..2]); + @memcpy(malformed[2..8], &[_]u8{ 0xFF, 0xFE, 0x00, 0x04, 0xFF, 0xD9 }); + @memcpy(malformed[8..], jpeg[2 .. jpeg.len - 2]); + + try std.testing.expectError(error.MalformedInput, image.decode(std.testing.allocator, malformed, .{})); +} + +test "JPEG decode rejects EOI before the first scan" { + const encoded = "/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDAAMCAgMCAgMDAwMEAwMEBQgFBQQEBQoHBwYIDAoMDAsKCwsNDhIQDQ4RDgsLEBYQERMUFRUVDA8XGBYUGBIUFRT/2wBDAQMEBAUEBQkFBQkUDQsNFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBT/wAARCAACAAMDAREAAhEBAxEB/8QAFAABAAAAAAAAAAAAAAAAAAAACP/EABQQAQAAAAAAAAAAAAAAAAAAAAD/xAAVAQEBAAAAAAAAAAAAAAAAAAAHCf/EABQRAQAAAAAAAAAAAAAAAAAAAAD/2gAMAwEAAhEDEQA/ADoDFU3/2Q=="; + const jpeg = try decodeBase64(encoded); + defer std.testing.allocator.free(jpeg); + const sos = std.mem.indexOf(u8, jpeg, &[_]u8{ 0xFF, 0xDA }) orelse return error.TestUnexpectedResult; + + const malformed = try std.testing.allocator.alloc(u8, sos + 2); + defer std.testing.allocator.free(malformed); + @memcpy(malformed[0..sos], jpeg[0..sos]); + @memcpy(malformed[sos..], &[_]u8{ 0xFF, 0xD9 }); + + try std.testing.expectError(error.MalformedInput, image.decode(std.testing.allocator, malformed, .{})); +} + +test "JPEG decode rejects a scan without entropy data" { + const encoded = "/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDAAMCAgMCAgMDAwMEAwMEBQgFBQQEBQoHBwYIDAoMDAsKCwsNDhIQDQ4RDgsLEBYQERMUFRUVDA8XGBYUGBIUFRT/2wBDAQMEBAUEBQkFBQkUDQsNFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBT/wAARCAACAAMDAREAAhEBAxEB/8QAFAABAAAAAAAAAAAAAAAAAAAACP/EABQQAQAAAAAAAAAAAAAAAAAAAAD/xAAVAQEBAAAAAAAAAAAAAAAAAAAHCf/EABQRAQAAAAAAAAAAAAAAAAAAAAD/2gAMAwEAAhEDEQA/ADoDFU3/2Q=="; + const jpeg = try decodeBase64(encoded); + defer std.testing.allocator.free(jpeg); + const sos = std.mem.indexOf(u8, jpeg, &[_]u8{ 0xFF, 0xDA }) orelse return error.TestUnexpectedResult; + const scan_header_length = std.mem.readInt(u16, jpeg[sos + 2 ..][0..2], .big); + const after_scan_header = sos + 2 + scan_header_length; + + const malformed = try std.testing.allocator.alloc(u8, after_scan_header + 2); + defer std.testing.allocator.free(malformed); + @memcpy(malformed[0..after_scan_header], jpeg[0..after_scan_header]); + @memcpy(malformed[after_scan_header..], &[_]u8{ 0xFF, 0xD9 }); + + try std.testing.expectError(error.MalformedInput, image.decode(std.testing.allocator, malformed, .{})); +} + +test "JPEG decode rejects an incomplete entropy-coded scan" { + const encoded = "/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDAAMCAgMCAgMDAwMEAwMEBQgFBQQEBQoHBwYIDAoMDAsKCwsNDhIQDQ4RDgsLEBYQERMUFRUVDA8XGBYUGBIUFRT/2wBDAQMEBAUEBQkFBQkUDQsNFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBT/wAARCAACAAMDAREAAhEBAxEB/8QAFAABAAAAAAAAAAAAAAAAAAAACP/EABQQAQAAAAAAAAAAAAAAAAAAAAD/xAAVAQEBAAAAAAAAAAAAAAAAAAAHCf/EABQRAQAAAAAAAAAAAAAAAAAAAAD/2gAMAwEAAhEDEQA/ADoDFU3/2Q=="; + const jpeg = try decodeBase64(encoded); + defer std.testing.allocator.free(jpeg); + const sos = std.mem.indexOf(u8, jpeg, &[_]u8{ 0xFF, 0xDA }) orelse return error.TestUnexpectedResult; + const scan_header_length = std.mem.readInt(u16, jpeg[sos + 2 ..][0..2], .big); + const after_scan_header = sos + 2 + scan_header_length; + + const malformed = try std.testing.allocator.alloc(u8, after_scan_header + 3); + defer std.testing.allocator.free(malformed); + @memcpy(malformed[0 .. after_scan_header + 1], jpeg[0 .. after_scan_header + 1]); + @memcpy(malformed[after_scan_header + 1 ..], &[_]u8{ 0xFF, 0xD9 }); + + try std.testing.expectError(error.MalformedInput, image.decode(std.testing.allocator, malformed, .{})); +} + +test "JPEG probe applies dimension limits before full scan validation" { + const encoded = "/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDAAMCAgMCAgMDAwMEAwMEBQgFBQQEBQoHBwYIDAoMDAsKCwsNDhIQDQ4RDgsLEBYQERMUFRUVDA8XGBYUGBIUFRT/2wBDAQMEBAUEBQkFBQkUDQsNFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBT/wAARCAACAAMDAREAAhEBAxEB/8QAFAABAAAAAAAAAAAAAAAAAAAACP/EABQQAQAAAAAAAAAAAAAAAAAAAAD/xAAVAQEBAAAAAAAAAAAAAAAAAAAHCf/EABQRAQAAAAAAAAAAAAAAAAAAAAD/2gAMAwEAAhEDEQA/ADoDFU3/2Q=="; + const jpeg = try decodeBase64(encoded); + defer std.testing.allocator.free(jpeg); + const sos = std.mem.indexOf(u8, jpeg, &[_]u8{ 0xFF, 0xDA }) orelse return error.TestUnexpectedResult; + const scan_header_length = std.mem.readInt(u16, jpeg[sos + 2 ..][0..2], .big); + const after_scan_header = sos + 2 + scan_header_length; + + const malformed = try std.testing.allocator.alloc(u8, after_scan_header + 3); + defer std.testing.allocator.free(malformed); + @memcpy(malformed[0 .. after_scan_header + 1], jpeg[0 .. after_scan_header + 1]); + @memcpy(malformed[after_scan_header + 1 ..], &[_]u8{ 0xFF, 0xD9 }); + + var info: image.Info = .{}; + try std.testing.expectEqual(image.Status.dimension_limit, image.probe(malformed, .{ .max_pixels = 0 }, &info)); + try std.testing.expectEqual(image.Status.malformed_input, image.probe(malformed, .{}, &info)); +} + +test "progressive JPEG decode rejects a final scan without entropy data" { + const encoded = "/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDAAMCAgMCAgMDAwMEAwMEBQgFBQQEBQoHBwYIDAoMDAsKCwsNDhIQDQ4RDgsLEBYQERMUFRUVDA8XGBYUGBIUFRT/2wBDAQMEBAUEBQkFBQkUDQsNFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBT/wgARCAACAAMDAREAAhEBAxEB/8QAFAABAAAAAAAAAAAAAAAAAAAAB//EABUBAQEAAAAAAAAAAAAAAAAAAAYI/9oADAMBAAIQAxAAAAE5C1T/AP/EABQQAQAAAAAAAAAAAAAAAAAAAAD/2gAIAQEAAQUCf//EABQRAQAAAAAAAAAAAAAAAAAAAAD/2gAIAQMBAT8Bf//EABQRAQAAAAAAAAAAAAAAAAAAAAD/2gAIAQIBAT8Bf//EABQQAQAAAAAAAAAAAAAAAAAAAAD/2gAIAQEABj8Cf//EABQQAQAAAAAAAAAAAAAAAAAAAAD/2gAIAQEAAT8hf//aAAwDAQACAAMAAAAQ/wD/xAAUEQEAAAAAAAAAAAAAAAAAAAAA/9oACAEDAQE/EH//xAAUEQEAAAAAAAAAAAAAAAAAAAAA/9oACAECAQE/EH//xAAUEAEAAAAAAAAAAAAAAAAAAAAA/9oACAEBAAE/EH//2Q=="; + const jpeg = try decodeBase64(encoded); + defer std.testing.allocator.free(jpeg); + + var search_start: usize = 0; + var final_sos: ?usize = null; + while (std.mem.indexOfPos(u8, jpeg, search_start, &[_]u8{ 0xFF, 0xDA })) |sos| { + final_sos = sos; + search_start = sos + 2; + } + const sos = final_sos orelse return error.TestUnexpectedResult; + const scan_header_length = std.mem.readInt(u16, jpeg[sos + 2 ..][0..2], .big); + const after_scan_header = sos + 2 + scan_header_length; + + const malformed = try std.testing.allocator.alloc(u8, after_scan_header + 2); + defer std.testing.allocator.free(malformed); + @memcpy(malformed[0..after_scan_header], jpeg[0..after_scan_header]); + @memcpy(malformed[after_scan_header..], &[_]u8{ 0xFF, 0xD9 }); + + try std.testing.expectError(error.MalformedInput, image.decode(std.testing.allocator, malformed, .{})); +} + +test "JPEG decode accepts trailing data after a complete stream" { + const encoded = "/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDAAMCAgMCAgMDAwMEAwMEBQgFBQQEBQoHBwYIDAoMDAsKCwsNDhIQDQ4RDgsLEBYQERMUFRUVDA8XGBYUGBIUFRT/2wBDAQMEBAUEBQkFBQkUDQsNFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBT/wAARCAACAAMDAREAAhEBAxEB/8QAFAABAAAAAAAAAAAAAAAAAAAACP/EABQQAQAAAAAAAAAAAAAAAAAAAAD/xAAVAQEBAAAAAAAAAAAAAAAAAAAHCf/EABQRAQAAAAAAAAAAAAAAAAAAAAD/2gAMAwEAAhEDEQA/ADoDFU3/2Q=="; + const jpeg = try decodeBase64(encoded); + defer std.testing.allocator.free(jpeg); + + const with_trailing_data = try std.testing.allocator.alloc(u8, jpeg.len + 3); + defer std.testing.allocator.free(with_trailing_data); + @memcpy(with_trailing_data[0..jpeg.len], jpeg); + @memcpy(with_trailing_data[jpeg.len..], &[_]u8{ 1, 2, 3 }); + + const decoded = try image.decode(std.testing.allocator, with_trailing_data, .{}); + defer decoded.deinit(); + try std.testing.expectEqual(@as(u32, 3), decoded.width()); + try std.testing.expectEqual(@as(u32, 2), decoded.height()); +} + +test "lossy lossless and alpha WebP decode to canonical RGBA" { + const fixtures = [_]struct { + encoded: []const u8, + width: u32, + height: u32, + has_alpha: u32, + pixels: []const u8, + }{ + .{ + .encoded = "UklGRjwAAABXRUJQVlA4IDAAAADQAQCdASoDAAIAAUAmJaACdLoB+AADsAD+8ut//NgVzXPv9//S4P0uD9Lg/9KQAAA=", + .width = 3, + .height = 2, + .has_alpha = 0, + .pixels = &([_]u8{ 255, 1, 0, 255 } ** 6), + }, + .{ + .encoded = "UklGRhwAAABXRUJQVlA4TA8AAAAvAkAAAAcQ/Y/+ByKi/wEA", + .width = 3, + .height = 2, + .has_alpha = 0, + .pixels = &([_]u8{ 255, 0, 0, 255 } ** 6), + }, + .{ + .encoded = "UklGRh4AAABXRUJQVlA4TBEAAAAvAUAAEA8Q8x/zH4wViOh/CAA=", + .width = 2, + .height = 2, + .has_alpha = 1, + .pixels = &[_]u8{ + 255, 0, 0, 255, 0, 0, 0, 0, + 0, 0, 0, 0, 255, 0, 0, 255, + }, + }, + }; + for (fixtures) |fixture| { + const webp = try decodeBase64(fixture.encoded); + defer std.testing.allocator.free(webp); + var info: image.Info = .{}; + try std.testing.expectEqual(image.Status.ok, image.probe(webp, .{}, &info)); + try std.testing.expectEqual(@as(u32, @intFromEnum(image.Format.webp)), info.format); + try std.testing.expectEqual(fixture.width, info.width); + try std.testing.expectEqual(fixture.height, info.height); + try std.testing.expectEqual(fixture.has_alpha, info.has_alpha); + const decoded = try image.decode(std.testing.allocator, webp, .{}); + defer decoded.deinit(); + try std.testing.expectEqual(info, decoded.info()); + try std.testing.expectEqual(@as(usize, fixture.width * fixture.height * 4), decoded.pixels.len); + try std.testing.expectEqualSlices(u8, fixture.pixels, decoded.pixels); + } +} + +test "PNG probe distinguishes unsupported input, corruption, and limits" { + var info: image.Info = .{}; + try std.testing.expectEqual(image.Status.unsupported_format, image.probe("not png", .{}, &info)); + + const encoded = "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR4AWP4z8DwHwAFAAH/e+m+7wAAAABJRU5ErkJggg=="; + const png = try decodeBase64(encoded); + defer std.testing.allocator.free(png); + png[29] ^= 1; + try std.testing.expectEqual(image.Status.malformed_input, image.probe(png, .{}, &info)); + png[29] ^= 1; + try std.testing.expectEqual(image.Status.memory_limit, image.probe(png, .{ .max_encoded_bytes = 1 }, &info)); + try std.testing.expectEqual(image.Status.dimension_limit, image.probe(png, .{ .max_pixels = 0 }, &info)); +} + +test "image creation copies strided RGBA input" { + const pixels = [_]u8{ + 1, 2, 3, 4, 5, 6, 7, 8, 99, 99, 99, 99, + 9, 10, 11, 12, 13, 14, 15, 16, 99, 99, 99, 99, + }; + const value = try image.createFromRgba(std.testing.allocator, &pixels, 2, 2, 12); + defer value.deinit(); + try std.testing.expectEqualSlices(u8, &[_]u8{ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16 }, value.pixels); +} + +test "image creation records actual transparency" { + const opaque_image = try image.createFromRgba(std.testing.allocator, &[_]u8{ 1, 2, 3, 255 }, 1, 1, 4); + defer opaque_image.deinit(); + try std.testing.expectEqual(@as(u32, 0), opaque_image.metadata.has_alpha); + + const transparent = try image.createFromRgba(std.testing.allocator, &[_]u8{ 1, 2, 3, 254 }, 1, 1, 4); + defer transparent.deinit(); + try std.testing.expectEqual(@as(u32, 1), transparent.metadata.has_alpha); +} + +test "image creation rejects invalid stride and short input" { + const pixels = [_]u8{0} ** 16; + try std.testing.expectError(error.InvalidArgument, image.createFromRgba(std.testing.allocator, &pixels, 2, 2, 7)); + try std.testing.expectError(error.InvalidArgument, image.createFromRgba(std.testing.allocator, pixels[0..15], 2, 2, 8)); +} + +test "extract copies the exact requested rectangle" { + const pixels = [_]u8{ + 1, 0, 0, 255, 2, 0, 0, 255, 3, 0, 0, 255, + 4, 0, 0, 255, 5, 0, 0, 255, 6, 0, 0, 255, + }; + const source = try makeImage(&pixels, 3, 2); + defer source.deinit(); + const output = try image.extract(std.testing.allocator, source, 1, 0, 2, 2); + defer output.deinit(); + try std.testing.expectEqualSlices(u8, &[_]u8{ + 2, 0, 0, 255, 3, 0, 0, 255, + 5, 0, 0, 255, 6, 0, 0, 255, + }, output.pixels); + try std.testing.expectError(error.InvalidArgument, image.extract(std.testing.allocator, source, 2, 0, 2, 1)); +} + +test "extend fills every edge and preserves source pixels" { + const source = try makeImage(&[_]u8{ 10, 20, 30, 40 }, 1, 1); + defer source.deinit(); + const output = try image.extend(std.testing.allocator, source, 1, 2, 1, 1, .{ 1, 2, 3, 4 }); + defer output.deinit(); + try std.testing.expectEqual(@as(u32, 4), output.width()); + try std.testing.expectEqual(@as(u32, 3), output.height()); + try std.testing.expectEqualSlices(u8, &[_]u8{ + 1, 2, 3, 4, 1, 2, 3, 4, 1, 2, 3, 4, 1, 2, 3, 4, + 1, 2, 3, 4, 10, 20, 30, 40, 1, 2, 3, 4, 1, 2, 3, 4, + 1, 2, 3, 4, 1, 2, 3, 4, 1, 2, 3, 4, 1, 2, 3, 4, + }, output.pixels); +} + +test "orthogonal transforms map pixels exactly" { + const pixels = [_]u8{ + 1, 0, 0, 255, 2, 0, 0, 255, 3, 0, 0, 255, + 4, 0, 0, 255, 5, 0, 0, 255, 6, 0, 0, 255, + }; + const source = try makeImage(&pixels, 3, 2); + defer source.deinit(); + + const rotated = try image.transform(std.testing.allocator, source, .rotate_90); + defer rotated.deinit(); + try std.testing.expectEqual(@as(u32, 2), rotated.width()); + try std.testing.expectEqual(@as(u32, 3), rotated.height()); + try std.testing.expectEqualSlices(u8, &[_]u8{ + 4, 0, 0, 255, 1, 0, 0, 255, + 5, 0, 0, 255, 2, 0, 0, 255, + 6, 0, 0, 255, 3, 0, 0, 255, + }, rotated.pixels); + + const flopped = try image.transform(std.testing.allocator, source, .flop); + defer flopped.deinit(); + try std.testing.expectEqualSlices(u8, &[_]u8{ + 3, 0, 0, 255, 2, 0, 0, 255, 1, 0, 0, 255, + 6, 0, 0, 255, 5, 0, 0, 255, 4, 0, 0, 255, + }, flopped.pixels); + + const rotated_180 = try image.transform(std.testing.allocator, source, .rotate_180); + defer rotated_180.deinit(); + try std.testing.expectEqualSlices(u8, &[_]u8{ + 6, 0, 0, 255, 5, 0, 0, 255, 4, 0, 0, 255, + 3, 0, 0, 255, 2, 0, 0, 255, 1, 0, 0, 255, + }, rotated_180.pixels); + + const flipped = try image.transform(std.testing.allocator, source, .flip); + defer flipped.deinit(); + try std.testing.expectEqualSlices(u8, &[_]u8{ + 4, 0, 0, 255, 5, 0, 0, 255, 6, 0, 0, 255, + 1, 0, 0, 255, 2, 0, 0, 255, 3, 0, 0, 255, + }, flipped.pixels); +} + +test "copyPixels supports RGBA, BGRA, and padded rows" { + const source = try makeImage(&[_]u8{ + 1, 2, 3, 4, 5, 6, 7, 8, + 9, 10, 11, 12, 13, 14, 15, 16, + }, 2, 2); + defer source.deinit(); + var rgba = [_]u8{99} ** 24; + try std.testing.expectEqual(image.Status.ok, image.copyPixels(source, &rgba, 12, false)); + try std.testing.expectEqualSlices(u8, &[_]u8{ + 1, 2, 3, 4, 5, 6, 7, 8, 99, 99, 99, 99, + 9, 10, 11, 12, 13, 14, 15, 16, 99, 99, 99, 99, + }, &rgba); + + var bgra: [16]u8 = undefined; + try std.testing.expectEqual(image.Status.ok, image.copyPixels(source, &bgra, 8, true)); + try std.testing.expectEqualSlices(u8, &[_]u8{ + 3, 2, 1, 4, 7, 6, 5, 8, + 11, 10, 9, 12, 15, 14, 13, 16, + }, &bgra); +} + +test "source-over composite uses linear light and correct alpha" { + const base = try makeImage(&[_]u8{ 0, 0, 0, 255 }, 1, 1); + defer base.deinit(); + const overlay = try makeImage(&[_]u8{ 255, 255, 255, 128 }, 1, 1); + defer overlay.deinit(); + const output = try image.composite(std.testing.allocator, base, overlay, 0, 0, .source_over, 255); + defer output.deinit(); + try std.testing.expect(@abs(@as(i16, output.pixels[0]) - 188) <= 1); + try std.testing.expectEqual(@as(u8, 255), output.pixels[3]); +} + +test "composite clips negative offsets and supports source mode" { + const base = try makeImage(&([_]u8{ 0, 0, 0, 255 } ** 4), 2, 2); + defer base.deinit(); + const overlay = try makeImage(&([_]u8{ 255, 0, 0, 255 } ** 4), 2, 2); + defer overlay.deinit(); + const output = try image.composite(std.testing.allocator, base, overlay, -1, -1, .source, 128); + defer output.deinit(); + try std.testing.expectEqualSlices(u8, &[_]u8{ 255, 0, 0, 128 }, output.pixels[0..4]); + try std.testing.expectEqualSlices(u8, &[_]u8{ 0, 0, 0, 255 }, output.pixels[4..8]); +} + +test "resize performs alpha-aware sRGB reduction" { + const source = try makeImage(&[_]u8{ + 255, 0, 0, 0, + 0, 0, 0, 255, + }, 2, 1); + defer source.deinit(); + const output = try image.resize(std.testing.allocator, source, 1, 1, .area); + defer output.deinit(); + try std.testing.expect(output.pixels[0] <= 2); + try std.testing.expect(@abs(@as(i16, output.pixels[3]) - 128) <= 1); +} + +fn injectJpegExifOrientation( + allocator: std.mem.Allocator, + jpeg: []const u8, + orientation: u16, + endian: std.builtin.Endian, +) ![]u8 { + const tiff_le = [_]u8{ + 'I', 'I', 42, 0, 8, 0, 0, 0, + 1, 0, 0x12, 0x01, 3, 0, 1, 0, + 0, 0, @truncate(orientation), @truncate(orientation >> 8), 0, 0, 0, 0, + 0, 0, + }; + const tiff_be = [_]u8{ + 'M', 'M', 0, 42, 0, 0, 0, 8, + 0, 1, 0x01, 0x12, 0, 3, 0, 0, + 0, 1, @truncate(orientation >> 8), @truncate(orientation), 0, 0, 0, 0, + 0, 0, + }; + const tiff = if (endian == .little) &tiff_le else &tiff_be; + const identifier = "Exif\x00\x00"; + const segment_length: u16 = @intCast(2 + identifier.len + tiff.len); + var output = try allocator.alloc(u8, jpeg.len + 4 + identifier.len + tiff.len); + errdefer allocator.free(output); + @memcpy(output[0..2], jpeg[0..2]); + output[2] = 0xFF; + output[3] = 0xE1; + output[4] = @truncate(segment_length >> 8); + output[5] = @truncate(segment_length); + @memcpy(output[6 .. 6 + identifier.len], identifier); + @memcpy(output[6 + identifier.len .. 6 + identifier.len + tiff.len], tiff); + @memcpy(output[6 + identifier.len + tiff.len ..], jpeg[2..]); + return output; +} + +test "JPEG EXIF orientation swaps probe and decode dimensions" { + const jpeg = try std.fs.cwd().readFileAlloc(std.testing.allocator, "../tests/fixtures/images/orientation.jpg", 1 << 20); + defer std.testing.allocator.free(jpeg); + const plain = try image.decode(std.testing.allocator, jpeg, .{}); + defer plain.deinit(); + + var plain_info: image.Info = .{}; + try std.testing.expectEqual(image.Status.ok, image.probe(jpeg, .{}, &plain_info)); + try std.testing.expectEqual(@as(u32, 16), plain_info.width); + try std.testing.expectEqual(@as(u32, 8), plain_info.height); + try std.testing.expectEqual(@as(u32, 1), plain_info.orientation); + + for ([_]std.builtin.Endian{ .little, .big }) |endian| { + const rotated = try injectJpegExifOrientation(std.testing.allocator, jpeg, 6, endian); + defer std.testing.allocator.free(rotated); + + var info: image.Info = .{}; + try std.testing.expectEqual(image.Status.ok, image.probe(rotated, .{}, &info)); + try std.testing.expectEqual(@as(u32, 6), info.orientation); + try std.testing.expectEqual(@as(u32, 8), info.width); + try std.testing.expectEqual(@as(u32, 16), info.height); + try std.testing.expectEqual(@as(u32, 16), info.source_width); + try std.testing.expectEqual(@as(u32, 8), info.source_height); + + const decoded = try image.decode(std.testing.allocator, rotated, .{}); + defer decoded.deinit(); + try std.testing.expectEqual(@as(u32, 8), decoded.width()); + try std.testing.expectEqual(@as(u32, 16), decoded.height()); + try std.testing.expectEqual(@as(u32, 1), decoded.metadata.orientation); + // Orientation 6: output (dx, dy) = source (dy, srcH - 1 - dx). + const source_top = (@as(usize, 7) * 16) * 4; + const source_bottom = (@as(usize, 7) * 16 + 15) * 4; + const bottom_offset = (@as(usize, 15) * 8) * 4; + try std.testing.expectEqualSlices(u8, plain.pixels[source_top .. source_top + 4], decoded.pixels[0..4]); + try std.testing.expectEqualSlices( + u8, + plain.pixels[source_bottom .. source_bottom + 4], + decoded.pixels[bottom_offset .. bottom_offset + 4], + ); + } +} + +test "JPEG EXIF orientation 180 keeps dimensions" { + const jpeg = try std.fs.cwd().readFileAlloc(std.testing.allocator, "../tests/fixtures/images/orientation.jpg", 1 << 20); + defer std.testing.allocator.free(jpeg); + const plain = try image.decode(std.testing.allocator, jpeg, .{}); + defer plain.deinit(); + const flipped = try injectJpegExifOrientation(std.testing.allocator, jpeg, 3, .little); + defer std.testing.allocator.free(flipped); + + var info: image.Info = .{}; + try std.testing.expectEqual(image.Status.ok, image.probe(flipped, .{}, &info)); + try std.testing.expectEqual(@as(u32, 3), info.orientation); + try std.testing.expectEqual(@as(u32, 16), info.width); + try std.testing.expectEqual(@as(u32, 8), info.height); + + const decoded = try image.decode(std.testing.allocator, flipped, .{}); + defer decoded.deinit(); + try std.testing.expectEqual(@as(u32, 16), decoded.width()); + // Orientation 3: output (dx, dy) = source (srcW - 1 - dx, srcH - 1 - dy). + const source_left = (@as(usize, 7) * 16 + 15) * 4; + const source_right = (@as(usize, 7) * 16) * 4; + const right_offset = (@as(usize, 15)) * 4; + try std.testing.expectEqualSlices(u8, plain.pixels[source_left .. source_left + 4], decoded.pixels[0..4]); + try std.testing.expectEqualSlices( + u8, + plain.pixels[source_right .. source_right + 4], + decoded.pixels[right_offset .. right_offset + 4], + ); +} + +test "JPEG EXIF orientation ignores invalid values and uses the default" { + const jpeg = try std.fs.cwd().readFileAlloc(std.testing.allocator, "../tests/fixtures/images/orientation.jpg", 1 << 20); + defer std.testing.allocator.free(jpeg); + for ([_]u16{ 0, 9, 200 }) |invalid| { + const bytes = try injectJpegExifOrientation(std.testing.allocator, jpeg, invalid, .little); + defer std.testing.allocator.free(bytes); + var info: image.Info = .{}; + try std.testing.expectEqual(image.Status.ok, image.probe(bytes, .{}, &info)); + try std.testing.expectEqual(@as(u32, 1), info.orientation); + try std.testing.expectEqual(@as(u32, 16), info.width); + } +} + +test "PNG eXIf orientation applies during decode" { + // 2x1 PNG (left red, right green) carrying an eXIf chunk with orientation 6. + const png = try decodeBase64( + "iVBORw0KGgoAAAANSUhEUgAAAAIAAAABCAYAAAD0In+KAAAAGmVYSWZJSSoACAAAAAEAEgEDAAEAAAAGAAAAAAAAALdIESkAAAAOSURBVHicY/jPwPAfBAEQ+AP9TpXBbwAAAABJRU5ErkJggg==", + ); + defer std.testing.allocator.free(png); + + var info: image.Info = .{}; + try std.testing.expectEqual(image.Status.ok, image.probe(png, .{}, &info)); + try std.testing.expectEqual(@as(u32, 6), info.orientation); + try std.testing.expectEqual(@as(u32, 1), info.width); + try std.testing.expectEqual(@as(u32, 2), info.height); + try std.testing.expectEqual(@as(u32, 2), info.source_width); + try std.testing.expectEqual(@as(u32, 1), info.source_height); + + const decoded = try image.decode(std.testing.allocator, png, .{}); + defer decoded.deinit(); + try std.testing.expectEqual(@as(u32, 1), decoded.width()); + try std.testing.expectEqual(@as(u32, 2), decoded.height()); + try std.testing.expectEqual(@as(u32, 1), decoded.metadata.orientation); + // Orientation 6 rotates the row into a column: red on top, green below. + try std.testing.expectEqualSlices(u8, &[_]u8{ 255, 0, 0, 255, 0, 255, 0, 255 }, decoded.pixels); +} + +test "area resize upscales tiny sources exactly" { + // Regression: bounds instrumentation aborts on stb's upstream sRGB + // table-bias idiom. On supported native x86_64/aarch64 builds, the first + // case reaches the SIMD RGBA sRGB path and checks every output pixel. This + // is evidence for the scoped exception, not a general stb safety proof. + const source = try makeImage(&[_]u8{ 200, 40, 10, 255 }, 1, 1); + defer source.deinit(); + const output = try image.resize(std.testing.allocator, source, 12, 2, .area); + defer output.deinit(); + try std.testing.expectEqual(@as(u32, 12), output.width()); + for (0..12 * 2) |pixel| { + try std.testing.expectEqualSlices(u8, &[_]u8{ 200, 40, 10, 255 }, output.pixels[pixel * 4 ..][0..4]); + } + + const mixed = try image.resize(std.testing.allocator, source, 1, 7, .area); + defer mixed.deinit(); + try std.testing.expectEqual(@as(u32, 7), mixed.height()); + try std.testing.expectEqual(@as(u8, 200), mixed.pixels[0]); +} diff --git a/packages/core/src/zig/tests/native-span-feed_test.zig b/packages/core/src/zig/tests/native-span-feed_test.zig index a05ac4a3a9..42d58334b5 100644 --- a/packages/core/src/zig/tests/native-span-feed_test.zig +++ b/packages/core/src/zig/tests/native-span-feed_test.zig @@ -42,6 +42,40 @@ test "Stream - create and destroy with testing allocator" { try testing.expectEqual(@as(u64, 0), stats.spans_committed); } +test "Stream - atomic write spans chunks without changing bytes" { + const stream = try raw.Stream.create(testing.allocator, testOptions(64, 1, true)); + defer stream.destroy(); + const input = [_]u8{'x'} ** 150; + + try stream.writeAtomic(&input); + + var spans: [4]raw.SpanInfo = undefined; + const count = stream.drainSpans(&spans); + try testing.expectEqual(@as(u32, 3), count); + var output: [input.len]u8 = undefined; + var offset: usize = 0; + for (spans[0..count]) |span| { + @memcpy(output[offset .. offset + span.len], span.slice()); + offset += span.len; + stream.markSpanConsumed(span); + } + try testing.expectEqualSlices(u8, &input, &output); +} + +test "Stream - failed atomic write publishes nothing" { + var options = testOptionsFull(32, 1, 32, true); + options.growth_policy = @intFromEnum(raw.GrowthPolicy.block); + const stream = try raw.Stream.create(testing.allocator, options); + defer stream.destroy(); + const input = [_]u8{'x'} ** 33; + + try testing.expectError(error.NoSpace, stream.writeAtomic(&input)); + + var spans: [2]raw.SpanInfo = undefined; + try testing.expectEqual(@as(u32, 0), stream.drainSpans(&spans)); + try testing.expectEqual(@as(u64, 0), stream.getStats().bytes_written); +} + test "Stream - create with default options" { const stream = try raw.Stream.create(testing.allocator, null); defer stream.destroy(); diff --git a/packages/core/src/zig/tests/renderer_test.zig b/packages/core/src/zig/tests/renderer_test.zig index 3744f3a823..3fa7fe3df4 100644 --- a/packages/core/src/zig/tests/renderer_test.zig +++ b/packages/core/src/zig/tests/renderer_test.zig @@ -7,7 +7,10 @@ const gp = @import("../grapheme.zig"); const ss = @import("../syntax-style.zig"); const link = @import("../link.zig"); const ansi = @import("../ansi.zig"); +const image = @import("../image.zig"); +const handles = @import("../handles.zig"); const test_renderer_mod = @import("test-renderer.zig"); +const terminal_image_test = @import("terminal-image_test.zig"); const CliRenderer = renderer.CliRenderer; const TextBuffer = text_buffer.TextBuffer; @@ -51,6 +54,775 @@ const SlowThreadSafeOutput = struct { std.Thread.sleep(self.delay_ns); } }; +test "renderer emits Kitty image once and leaves unchanged frame empty" { + const pool = gp.initGlobalPool(std.testing.allocator); + defer gp.deinitGlobalPool(); + defer link.deinitGlobalLinkPool(); + var test_renderer = try TestRenderer.create(std.testing.allocator, 4, 2, pool); + defer test_renderer.deinit(); + const value = try image.createFromRgba(std.testing.allocator, &[_]u8{ 255, 0, 0, 255 }, 1, 1, 4); + const image_handle = try handles.insert(.image, @ptrCast(value)); + defer { + const token = handles.beginDestroy(image_handle, .image, image.Image).?; + token.ptr.deinit(); + handles.finishDestroy(token.handle); + } + test_renderer.renderer.terminal.caps.kitty_graphics = true; + test_renderer.renderer.terminal.multiplexer = .none; + + try std.testing.expect(try test_renderer.renderer.getNextBuffer().drawImage(value, image_handle, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, .auto)); + try std.testing.expectEqual(renderer.RenderStatus.rendered, test_renderer.renderer.render(true)); + try std.testing.expect(std.mem.indexOf(u8, test_renderer.memory.lastWrite(), "\x1b_Ga=t,f=24,s=1,v=1,i=") != null); + try std.testing.expect(std.mem.indexOf(u8, test_renderer.memory.lastWrite(), "c=1,r=1,x=0,y=0,w=1,h=1,C=1,z=-1499999999") != null); + + try std.testing.expect(try test_renderer.renderer.getNextBuffer().drawImage(value, image_handle, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, .auto)); + try std.testing.expectEqual(renderer.RenderStatus.rendered, test_renderer.renderer.render(false)); + try std.testing.expectEqual(@as(usize, 0), test_renderer.memory.lastWrite().len); +} + +test "renderer emits Sixel only with known pixel dimensions" { + const pool = gp.initGlobalPool(std.testing.allocator); + defer gp.deinitGlobalPool(); + defer link.deinitGlobalLinkPool(); + var test_renderer = try TestRenderer.create(std.testing.allocator, 4, 2, pool); + defer test_renderer.deinit(); + const value = try image.createFromRgba(std.testing.allocator, &[_]u8{ 255, 0, 0, 255 }, 1, 1, 4); + const image_handle = try handles.insert(.image, @ptrCast(value)); + defer { + const token = handles.beginDestroy(image_handle, .image, image.Image).?; + token.ptr.deinit(); + handles.finishDestroy(token.handle); + } + test_renderer.renderer.terminal.caps.sixel = true; + test_renderer.renderer.terminal.multiplexer = .none; + + try std.testing.expect(try test_renderer.renderer.getNextBuffer().drawImage(value, image_handle, 0, 0, 1, 1, 2, 2, 0, 0, 1, 1, .auto)); + try std.testing.expectEqual(renderer.RenderStatus.rendered, test_renderer.renderer.render(true)); + try std.testing.expect(std.mem.indexOf(u8, test_renderer.memory.lastWrite(), "\x1bP0;1;0q") != null); + try std.testing.expectEqual(@as(u64, 1), test_renderer.renderer.sixelCacheMisses); + try std.testing.expect(try test_renderer.renderer.getNextBuffer().drawImage(value, image_handle, 0, 0, 1, 1, 2, 2, 0, 0, 1, 1, .auto)); + try std.testing.expectEqual(renderer.RenderStatus.rendered, test_renderer.renderer.render(true)); + try std.testing.expect(std.mem.indexOf(u8, test_renderer.memory.lastWrite(), "\x1bP0;1;0q") != null); + try std.testing.expectEqual(@as(u64, 1), test_renderer.renderer.sixelCacheHits); + + try std.testing.expect(try test_renderer.renderer.getNextBuffer().drawImage(value, image_handle, 1, 0, 1, 1, 2, 2, 0, 0, 1, 1, .auto)); + try std.testing.expectEqual(renderer.RenderStatus.rendered, test_renderer.renderer.render(true)); + try std.testing.expectEqual(@as(u64, 2), test_renderer.renderer.sixelCacheHits); + + try test_renderer.renderer.getNextBuffer().pushOpacity(0.5); + try std.testing.expect(try test_renderer.renderer.getNextBuffer().drawImage(value, image_handle, 1, 0, 1, 1, 2, 2, 0, 0, 1, 1, .auto)); + test_renderer.renderer.getNextBuffer().popOpacity(); + try std.testing.expectEqual(renderer.RenderStatus.rendered, test_renderer.renderer.render(true)); + try std.testing.expectEqual(@as(u64, 2), test_renderer.renderer.sixelCacheMisses); +} + +test "renderer does not copy identity Sixel geometry" { + const pool = gp.initGlobalPool(std.testing.allocator); + defer gp.deinitGlobalPool(); + defer link.deinitGlobalLinkPool(); + var test_renderer = try TestRenderer.create(std.testing.allocator, 1, 1, pool); + defer test_renderer.deinit(); + + var source_allocator = std.testing.FailingAllocator.init(std.testing.allocator, .{}); + const value = try image.createFromRgba(source_allocator.allocator(), &[_]u8{ 255, 0, 0, 255 }, 1, 1, 4); + const image_handle = try handles.insert(.image, @ptrCast(value)); + defer { + const token = handles.beginDestroy(image_handle, .image, image.Image).?; + token.ptr.deinit(); + handles.finishDestroy(token.handle); + } + // A full-image extract consumes two allocations. Fail if rendering tries + // to allocate a second image copy for the identity resize. + source_allocator.fail_index = source_allocator.alloc_index + 2; + const source_allocations_before_render = source_allocator.allocations; + test_renderer.renderer.terminal.caps.sixel = true; + + try std.testing.expect(try test_renderer.renderer.getNextBuffer().drawImage(value, image_handle, 0, 0, 1, 1, 1, 1, 0, 0, 1, 1, .sixel)); + try std.testing.expectEqual(renderer.RenderStatus.rendered, test_renderer.renderer.render(true)); + try std.testing.expect(!source_allocator.has_induced_failure); + try std.testing.expectEqual(source_allocations_before_render, source_allocator.allocations); + try std.testing.expect(std.mem.indexOf(u8, test_renderer.memory.lastWrite(), "\x1bP0;1;0q") != null); +} + +test "renderer repaints unchanged upper Sixel after an overlapping lower image changes" { + const pool = gp.initGlobalPool(std.testing.allocator); + defer gp.deinitGlobalPool(); + defer link.deinitGlobalLinkPool(); + var test_renderer = try TestRenderer.create(std.testing.allocator, 1, 1, pool); + defer test_renderer.deinit(); + + const red = try image.createFromRgba(std.testing.allocator, &[_]u8{ 255, 0, 0, 255 }, 1, 1, 4); + const blue = try image.createFromRgba(std.testing.allocator, &[_]u8{ 0, 0, 255, 255 }, 1, 1, 4); + const green = try image.createFromRgba(std.testing.allocator, &[_]u8{ 0, 255, 0, 255 }, 1, 1, 4); + const red_handle = try handles.insert(.image, @ptrCast(red)); + const blue_handle = try handles.insert(.image, @ptrCast(blue)); + const green_handle = try handles.insert(.image, @ptrCast(green)); + defer for ([_]u32{ green_handle, blue_handle, red_handle }) |handle| { + const token = handles.beginDestroy(handle, .image, image.Image).?; + token.ptr.deinit(); + handles.finishDestroy(token.handle); + }; + test_renderer.renderer.terminal.caps.sixel = true; + test_renderer.renderer.terminal.multiplexer = .none; + + var next = test_renderer.renderer.getNextBuffer(); + try std.testing.expect(try next.drawImage(red, red_handle, 0, 0, 1, 1, 1, 1, 0, 0, 1, 1, .sixel)); + try std.testing.expect(try next.drawImage(blue, blue_handle, 0, 0, 1, 1, 1, 1, 0, 0, 1, 1, .sixel)); + try std.testing.expectEqual(renderer.RenderStatus.rendered, test_renderer.renderer.render(true)); + + next = test_renderer.renderer.getNextBuffer(); + try std.testing.expect(try next.drawImage(green, green_handle, 0, 0, 1, 1, 1, 1, 0, 0, 1, 1, .sixel)); + try std.testing.expect(try next.drawImage(blue, blue_handle, 0, 0, 1, 1, 1, 1, 0, 0, 1, 1, .sixel)); + try std.testing.expectEqual(renderer.RenderStatus.rendered, test_renderer.renderer.render(false)); + + const output = test_renderer.memory.lastWrite(); + try std.testing.expectEqual(@as(usize, 2), std.mem.count(u8, output, "\x1bP0;1;0q")); + try expectSinglePaintedSixelColor(output, .{ 0, 2 }, .{ 0, 2 }, .{ 95, 100 }); +} + +test "renderer repaints overlapping Sixel images when their order changes" { + const pool = gp.initGlobalPool(std.testing.allocator); + defer gp.deinitGlobalPool(); + defer link.deinitGlobalLinkPool(); + var test_renderer = try TestRenderer.create(std.testing.allocator, 1, 1, pool); + defer test_renderer.deinit(); + + const red = try image.createFromRgba(std.testing.allocator, &[_]u8{ 255, 0, 0, 255 }, 1, 1, 4); + const blue = try image.createFromRgba(std.testing.allocator, &[_]u8{ 0, 0, 255, 255 }, 1, 1, 4); + const red_handle = try handles.insert(.image, @ptrCast(red)); + const blue_handle = try handles.insert(.image, @ptrCast(blue)); + defer for ([_]u32{ blue_handle, red_handle }) |handle| { + const token = handles.beginDestroy(handle, .image, image.Image).?; + token.ptr.deinit(); + handles.finishDestroy(token.handle); + }; + test_renderer.renderer.terminal.caps.sixel = true; + test_renderer.renderer.terminal.multiplexer = .none; + + var next = test_renderer.renderer.getNextBuffer(); + try std.testing.expect(try next.drawImage(red, red_handle, 0, 0, 1, 1, 1, 1, 0, 0, 1, 1, .sixel)); + try std.testing.expect(try next.drawImage(blue, blue_handle, 0, 0, 1, 1, 1, 1, 0, 0, 1, 1, .sixel)); + try std.testing.expectEqual(renderer.RenderStatus.rendered, test_renderer.renderer.render(true)); + + next = test_renderer.renderer.getNextBuffer(); + try std.testing.expect(try next.drawImage(blue, blue_handle, 0, 0, 1, 1, 1, 1, 0, 0, 1, 1, .sixel)); + try std.testing.expect(try next.drawImage(red, red_handle, 0, 0, 1, 1, 1, 1, 0, 0, 1, 1, .sixel)); + try std.testing.expectEqual(renderer.RenderStatus.rendered, test_renderer.renderer.render(false)); + + const output = test_renderer.memory.lastWrite(); + try std.testing.expectEqual(@as(usize, 2), std.mem.count(u8, output, "\x1bP0;1;0q")); + try expectSinglePaintedSixelColor(output, .{ 95, 100 }, .{ 0, 2 }, .{ 0, 2 }); +} + +test "renderer propagates Sixel repaint through an overlap chain" { + const pool = gp.initGlobalPool(std.testing.allocator); + defer gp.deinitGlobalPool(); + defer link.deinitGlobalLinkPool(); + var test_renderer = try TestRenderer.create(std.testing.allocator, 6, 1, pool); + defer test_renderer.deinit(); + test_renderer.renderer.terminal.caps.sixel = true; + + const red = try image.createFromRgba(std.testing.allocator, &[_]u8{ 255, 0, 0, 255 }, 1, 1, 4); + const green = try image.createFromRgba(std.testing.allocator, &[_]u8{ 0, 255, 0, 255 }, 1, 1, 4); + const blue = try image.createFromRgba(std.testing.allocator, &[_]u8{ 0, 0, 255, 255 }, 1, 1, 4); + const white = try image.createFromRgba(std.testing.allocator, &[_]u8{ 255, 255, 255, 255 }, 1, 1, 4); + const red_handle = try handles.insert(.image, @ptrCast(red)); + const green_handle = try handles.insert(.image, @ptrCast(green)); + const blue_handle = try handles.insert(.image, @ptrCast(blue)); + const white_handle = try handles.insert(.image, @ptrCast(white)); + defer for ([_]u32{ white_handle, blue_handle, green_handle, red_handle }) |handle| { + const token = handles.beginDestroy(handle, .image, image.Image).?; + token.ptr.deinit(); + handles.finishDestroy(token.handle); + }; + + var next = test_renderer.renderer.getNextBuffer(); + try std.testing.expect(try next.drawImage(red, red_handle, 0, 0, 2, 1, 2, 1, 0, 0, 1, 1, .sixel)); + try std.testing.expect(try next.drawImage(blue, blue_handle, 1, 0, 2, 1, 2, 1, 0, 0, 1, 1, .sixel)); + try std.testing.expect(try next.drawImage(white, white_handle, 2, 0, 2, 1, 2, 1, 0, 0, 1, 1, .sixel)); + try std.testing.expect(try next.drawImage(red, red_handle, 5, 0, 1, 1, 1, 1, 0, 0, 1, 1, .sixel)); + try std.testing.expectEqual(renderer.RenderStatus.rendered, test_renderer.renderer.render(true)); + + next = test_renderer.renderer.getNextBuffer(); + try std.testing.expect(try next.drawImage(green, green_handle, 0, 0, 2, 1, 2, 1, 0, 0, 1, 1, .sixel)); + try std.testing.expect(try next.drawImage(blue, blue_handle, 1, 0, 2, 1, 2, 1, 0, 0, 1, 1, .sixel)); + try std.testing.expect(try next.drawImage(white, white_handle, 2, 0, 2, 1, 2, 1, 0, 0, 1, 1, .sixel)); + try std.testing.expect(try next.drawImage(red, red_handle, 5, 0, 1, 1, 1, 1, 0, 0, 1, 1, .sixel)); + try std.testing.expectEqual(renderer.RenderStatus.rendered, test_renderer.renderer.render(false)); + + try std.testing.expectEqual(@as(usize, 3), std.mem.count(u8, test_renderer.memory.lastWrite(), "\x1bP0;1;0q")); +} + +test "renderer repaints lower Sixel after removing an overlapping upper image" { + const pool = gp.initGlobalPool(std.testing.allocator); + defer gp.deinitGlobalPool(); + defer link.deinitGlobalLinkPool(); + var test_renderer = try TestRenderer.create(std.testing.allocator, 1, 1, pool); + defer test_renderer.deinit(); + + const red = try image.createFromRgba(std.testing.allocator, &[_]u8{ 255, 0, 0, 255 }, 1, 1, 4); + const blue = try image.createFromRgba(std.testing.allocator, &[_]u8{ 0, 0, 255, 255 }, 1, 1, 4); + const red_handle = try handles.insert(.image, @ptrCast(red)); + const blue_handle = try handles.insert(.image, @ptrCast(blue)); + defer for ([_]u32{ blue_handle, red_handle }) |handle| { + const token = handles.beginDestroy(handle, .image, image.Image).?; + token.ptr.deinit(); + handles.finishDestroy(token.handle); + }; + test_renderer.renderer.terminal.caps.sixel = true; + test_renderer.renderer.terminal.multiplexer = .none; + + var next = test_renderer.renderer.getNextBuffer(); + try std.testing.expect(try next.drawImage(red, red_handle, 0, 0, 1, 1, 1, 1, 0, 0, 1, 1, .sixel)); + try std.testing.expect(try next.drawImage(blue, blue_handle, 0, 0, 1, 1, 1, 1, 0, 0, 1, 1, .sixel)); + try std.testing.expectEqual(renderer.RenderStatus.rendered, test_renderer.renderer.render(true)); + + next = test_renderer.renderer.getNextBuffer(); + try std.testing.expect(try next.drawImage(red, red_handle, 0, 0, 1, 1, 1, 1, 0, 0, 1, 1, .sixel)); + try std.testing.expectEqual(renderer.RenderStatus.rendered, test_renderer.renderer.render(false)); + + const output = test_renderer.memory.lastWrite(); + try std.testing.expectEqual(@as(usize, 1), std.mem.count(u8, output, "\x1bP0;1;0q")); + try expectSinglePaintedSixelColor(output, .{ 95, 100 }, .{ 0, 2 }, .{ 0, 2 }); +} + +test "renderer repaints lower Sixel when an overlapping upper image becomes transparent" { + const pool = gp.initGlobalPool(std.testing.allocator); + defer gp.deinitGlobalPool(); + defer link.deinitGlobalLinkPool(); + var test_renderer = try TestRenderer.create(std.testing.allocator, 1, 1, pool); + defer test_renderer.deinit(); + + const red = try image.createFromRgba(std.testing.allocator, &[_]u8{ 255, 0, 0, 255 }, 1, 1, 4); + const blue = try image.createFromRgba(std.testing.allocator, &[_]u8{ 0, 0, 255, 255 }, 1, 1, 4); + const transparent = try image.createFromRgba(std.testing.allocator, &[_]u8{ 0, 0, 0, 0 }, 1, 1, 4); + const red_handle = try handles.insert(.image, @ptrCast(red)); + const blue_handle = try handles.insert(.image, @ptrCast(blue)); + const transparent_handle = try handles.insert(.image, @ptrCast(transparent)); + defer for ([_]u32{ transparent_handle, blue_handle, red_handle }) |handle| { + const token = handles.beginDestroy(handle, .image, image.Image).?; + token.ptr.deinit(); + handles.finishDestroy(token.handle); + }; + test_renderer.renderer.terminal.caps.sixel = true; + test_renderer.renderer.terminal.multiplexer = .none; + + var next = test_renderer.renderer.getNextBuffer(); + try std.testing.expect(try next.drawImage(red, red_handle, 0, 0, 1, 1, 1, 1, 0, 0, 1, 1, .sixel)); + try std.testing.expect(try next.drawImage(blue, blue_handle, 0, 0, 1, 1, 1, 1, 0, 0, 1, 1, .sixel)); + try std.testing.expectEqual(renderer.RenderStatus.rendered, test_renderer.renderer.render(true)); + + next = test_renderer.renderer.getNextBuffer(); + try std.testing.expect(try next.drawImage(red, red_handle, 0, 0, 1, 1, 1, 1, 0, 0, 1, 1, .sixel)); + try std.testing.expect(try next.drawImage(transparent, transparent_handle, 0, 0, 1, 1, 1, 1, 0, 0, 1, 1, .sixel)); + try std.testing.expectEqual(renderer.RenderStatus.rendered, test_renderer.renderer.render(false)); + + const output = test_renderer.memory.lastWrite(); + try std.testing.expectEqual(@as(usize, 1), std.mem.count(u8, output, "\x1bP0;1;0q")); + try expectSinglePaintedSixelColor(output, .{ 95, 100 }, .{ 0, 2 }, .{ 0, 2 }); +} + +test "renderer honors per-placement protocol overrides" { + const pool = gp.initGlobalPool(std.testing.allocator); + defer gp.deinitGlobalPool(); + defer link.deinitGlobalLinkPool(); + var test_renderer = try TestRenderer.create(std.testing.allocator, 4, 2, pool); + defer test_renderer.deinit(); + const value = try image.createFromRgba(std.testing.allocator, &[_]u8{ 255, 0, 0, 255 }, 1, 1, 4); + const image_handle = try handles.insert(.image, @ptrCast(value)); + defer { + const token = handles.beginDestroy(image_handle, .image, image.Image).?; + token.ptr.deinit(); + handles.finishDestroy(token.handle); + } + + try std.testing.expect(try test_renderer.renderer.getNextBuffer().drawImage(value, image_handle, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, .kitty)); + try std.testing.expectEqual(renderer.RenderStatus.rendered, test_renderer.renderer.render(true)); + try std.testing.expect(std.mem.indexOf(u8, test_renderer.memory.lastWrite(), "\x1b_Ga=t") != null); + + test_renderer.renderer.terminal.caps.kitty_graphics = true; + try std.testing.expect(try test_renderer.renderer.getNextBuffer().drawImage(value, image_handle, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, .blocks)); + try std.testing.expectEqual(renderer.RenderStatus.rendered, test_renderer.renderer.render(true)); + try std.testing.expect(std.mem.indexOf(u8, test_renderer.memory.lastWrite(), "\x1b_Ga=t") == null); +} + +test "renderer honors global image protocol override for auto placements" { + const pool = gp.initGlobalPool(std.testing.allocator); + defer gp.deinitGlobalPool(); + defer link.deinitGlobalLinkPool(); + var test_renderer = try TestRenderer.create(std.testing.allocator, 4, 2, pool); + defer test_renderer.deinit(); + const value = try image.createFromRgba(std.testing.allocator, &[_]u8{ 255, 0, 0, 255 }, 1, 1, 4); + const image_handle = try handles.insert(.image, @ptrCast(value)); + defer { + const token = handles.beginDestroy(image_handle, .image, image.Image).?; + token.ptr.deinit(); + handles.finishDestroy(token.handle); + } + test_renderer.renderer.terminal.image_protocol = .kitty; + try std.testing.expect(try test_renderer.renderer.getNextBuffer().drawImage(value, image_handle, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, .auto)); + try std.testing.expectEqual(renderer.RenderStatus.rendered, test_renderer.renderer.render(true)); + try std.testing.expect(std.mem.indexOf(u8, test_renderer.memory.lastWrite(), "\x1b_Ga=t") != null); +} + +test "renderer keeps unresolved Sixel fallback frames as no-ops" { + const pool = gp.initGlobalPool(std.testing.allocator); + defer gp.deinitGlobalPool(); + defer link.deinitGlobalLinkPool(); + var test_renderer = try TestRenderer.create(std.testing.allocator, 2, 1, pool); + defer test_renderer.deinit(); + const value = try image.createFromRgba(std.testing.allocator, &[_]u8{ 255, 0, 0, 255 }, 1, 1, 4); + const image_handle = try handles.insert(.image, @ptrCast(value)); + defer { + const token = handles.beginDestroy(image_handle, .image, image.Image).?; + token.ptr.deinit(); + handles.finishDestroy(token.handle); + } + test_renderer.renderer.terminal.caps.sixel = true; + try std.testing.expect(try test_renderer.renderer.getNextBuffer().drawImage(value, image_handle, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, .auto)); + try std.testing.expectEqual(renderer.RenderStatus.rendered, test_renderer.renderer.render(true)); + const first = test_renderer.memory.lastWrite(); + try std.testing.expect(std.mem.indexOf(u8, first, "\x1bP0;1;0q") == null); + try std.testing.expect(std.mem.indexOf(u8, first, "█") != null); + try std.testing.expect(try test_renderer.renderer.getNextBuffer().drawImage(value, image_handle, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, .auto)); + try std.testing.expectEqual(renderer.RenderStatus.rendered, test_renderer.renderer.render(false)); + try std.testing.expectEqual(@as(usize, 0), test_renderer.memory.lastWrite().len); +} + +test "renderer repaints a final blocks placement over Sixel" { + const pool = gp.initGlobalPool(std.testing.allocator); + defer gp.deinitGlobalPool(); + defer link.deinitGlobalLinkPool(); + var test_renderer = try TestRenderer.create(std.testing.allocator, 2, 1, pool); + defer test_renderer.deinit(); + const value = try image.createFromRgba(std.testing.allocator, &[_]u8{ 255, 0, 0, 255 }, 1, 1, 4); + const image_handle = try handles.insert(.image, @ptrCast(value)); + defer { + const token = handles.beginDestroy(image_handle, .image, image.Image).?; + token.ptr.deinit(); + handles.finishDestroy(token.handle); + } + try std.testing.expect(try test_renderer.renderer.getNextBuffer().drawImage(value, image_handle, 0, 0, 1, 1, 2, 2, 0, 0, 1, 1, .sixel)); + try std.testing.expect(try test_renderer.renderer.getNextBuffer().drawImage(value, image_handle, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, .blocks)); + _ = test_renderer.renderer.render(true); + const output = test_renderer.memory.lastWrite(); + const sixel = std.mem.indexOf(u8, output, "\x1bP0;1;0q") orelse return error.TestUnexpectedResult; + const block = std.mem.lastIndexOf(u8, output, "█") orelse return error.TestUnexpectedResult; + try std.testing.expect(block > sixel); +} + +test "renderer does not retransmit Sixel when overlay text changes" { + const pool = gp.initGlobalPool(std.testing.allocator); + defer gp.deinitGlobalPool(); + defer link.deinitGlobalLinkPool(); + var test_renderer = try TestRenderer.create(std.testing.allocator, 2, 1, pool); + defer test_renderer.deinit(); + const value = try image.createFromRgba(std.testing.allocator, &[_]u8{ 255, 0, 0, 255 }, 1, 1, 4); + const image_handle = try handles.insert(.image, @ptrCast(value)); + defer { + const token = handles.beginDestroy(image_handle, .image, image.Image).?; + token.ptr.deinit(); + handles.finishDestroy(token.handle); + } + const fg = RGBA{ 1.0, 1.0, 1.0, 1.0 }; + const bg = RGBA{ 0.0, 0.0, 0.0, 1.0 }; + + var next = test_renderer.renderer.getNextBuffer(); + try std.testing.expect(try next.drawImage(value, image_handle, 0, 0, 2, 1, 2, 2, 0, 0, 1, 1, .sixel)); + _ = test_renderer.renderer.render(true); + + next = test_renderer.renderer.getNextBuffer(); + try std.testing.expect(try next.drawImage(value, image_handle, 0, 0, 2, 1, 2, 2, 0, 0, 1, 1, .sixel)); + try next.drawText("1", 1, 0, fg, bg, 0); + _ = test_renderer.renderer.render(false); + try std.testing.expect(std.mem.indexOf(u8, test_renderer.memory.lastWrite(), "\x1bP0;1;0q") == null); + try std.testing.expect(std.mem.indexOfScalar(u8, test_renderer.memory.lastWrite(), '1') != null); + + next = test_renderer.renderer.getNextBuffer(); + try std.testing.expect(try next.drawImage(value, image_handle, 0, 0, 2, 1, 2, 2, 0, 0, 1, 1, .sixel)); + try next.drawText("2", 1, 0, fg, bg, 0); + _ = test_renderer.renderer.render(false); + try std.testing.expect(std.mem.indexOf(u8, test_renderer.memory.lastWrite(), "\x1bP0;1;0q") == null); + try std.testing.expect(std.mem.indexOfScalar(u8, test_renderer.memory.lastWrite(), '2') != null); + + next = test_renderer.renderer.getNextBuffer(); + try std.testing.expect(try next.drawImage(value, image_handle, 0, 0, 2, 1, 2, 2, 0, 0, 1, 1, .sixel)); + _ = test_renderer.renderer.render(false); + try std.testing.expect(std.mem.indexOf(u8, test_renderer.memory.lastWrite(), "\x1bP0;1;0q") != null); +} + +test "renderer replays a wide grapheme that starts before a Sixel placement" { + const pool = gp.initGlobalPool(std.testing.allocator); + defer gp.deinitGlobalPool(); + defer link.deinitGlobalLinkPool(); + var test_renderer = try TestRenderer.create(std.testing.allocator, 2, 1, pool); + defer test_renderer.deinit(); + const value = try image.createFromRgba(std.testing.allocator, &[_]u8{ 255, 0, 0, 255 }, 1, 1, 4); + const image_handle = try handles.insert(.image, @ptrCast(value)); + defer { + const token = handles.beginDestroy(image_handle, .image, image.Image).?; + token.ptr.deinit(); + handles.finishDestroy(token.handle); + } + + const next = test_renderer.renderer.getNextBuffer(); + try std.testing.expect(try next.drawImage(value, image_handle, 1, 0, 1, 1, 2, 2, 0, 0, 1, 1, .sixel)); + try next.drawText("界", 0, 0, ansi.rgbColor(255, 255, 255, 0), ansi.rgbColor(0, 0, 0, 0), 0); + _ = test_renderer.renderer.render(true); + + const output = test_renderer.memory.lastWrite(); + const sixel = std.mem.indexOf(u8, output, "\x1bP0;1;0q") orelse return error.TestUnexpectedResult; + const overlay = std.mem.lastIndexOf(u8, output, "界") orelse return error.TestUnexpectedResult; + try std.testing.expect(overlay > sixel); +} + +test "renderer preserves terminal semantics when replaying cells over Sixel" { + const pool = gp.initGlobalPool(std.testing.allocator); + defer gp.deinitGlobalPool(); + const link_pool = link.initGlobalLinkPool(std.testing.allocator); + defer link.deinitGlobalLinkPool(); + var test_renderer = try TestRenderer.create(std.testing.allocator, 3, 1, pool); + defer test_renderer.deinit(); + test_renderer.renderer.terminal.caps.sixel = true; + test_renderer.renderer.terminal.caps.hyperlinks = true; + test_renderer.renderer.terminal.caps.explicit_width = true; + const value = try image.createFromRgba(std.testing.allocator, &[_]u8{ 255, 0, 0, 255 }, 1, 1, 4); + const image_handle = try handles.insert(.image, @ptrCast(value)); + defer { + const token = handles.beginDestroy(image_handle, .image, image.Image).?; + token.ptr.deinit(); + handles.finishDestroy(token.handle); + } + const link_id = try link_pool.alloc("https://example.com/replayed"); + const linked_bold = ansi.TextAttributes.setLinkId(ansi.TextAttributes.BOLD, link_id); + + const next = test_renderer.renderer.getNextBuffer(); + try std.testing.expect(try next.drawImage(value, image_handle, 0, 0, 3, 1, 6, 2, 0, 0, 1, 1, .sixel)); + try next.drawText("界", 0, 0, ansi.rgbColor(255, 255, 255, 255), ansi.rgbColor(0, 0, 0, 255), linked_bold); + try next.drawText("X", 2, 0, ansi.rgbColor(255, 255, 255, 255), ansi.rgbColor(0, 0, 0, 255), 0); + _ = test_renderer.renderer.render(true); + + const output = test_renderer.memory.lastWrite(); + const sixel = std.mem.lastIndexOf(u8, output, "\x1bP0;1;0q") orelse return error.TestUnexpectedResult; + const replay = output[sixel..]; + try std.testing.expect(std.mem.indexOf(u8, replay, "\x1b]8;id=") != null); + try std.testing.expect(std.mem.indexOf(u8, replay, ";https://example.com/replayed\x1b\\") != null); + try std.testing.expect(std.mem.indexOf(u8, replay, "\x1b]8;;\x1b\\") != null); + try std.testing.expect(std.mem.indexOf(u8, replay, "\x1b]66;w=2;界\x1b\\") != null); + try std.testing.expect(std.mem.indexOf(u8, replay, "\x1b[0m\x1b[1;3H") != null); +} + +fn expectPlaneCoversImage(protocol: image.RenderProtocol) !void { + const pool = gp.initGlobalPool(std.testing.allocator); + defer gp.deinitGlobalPool(); + defer link.deinitGlobalLinkPool(); + var test_renderer = try TestRenderer.create(std.testing.allocator, 2, 1, pool); + defer test_renderer.deinit(); + const value = try image.createFromRgba(std.testing.allocator, &[_]u8{ 255, 0, 0, 255 }, 1, 1, 4); + const image_handle = try handles.insert(.image, @ptrCast(value)); + defer { + const token = handles.beginDestroy(image_handle, .image, image.Image).?; + token.ptr.deinit(); + handles.finishDestroy(token.handle); + } + + var next = test_renderer.renderer.getNextBuffer(); + try std.testing.expect(try next.drawImage(value, image_handle, 0, 0, 2, 1, 2, 2, 0, 0, 1, 1, protocol)); + _ = test_renderer.renderer.render(true); + + next = test_renderer.renderer.getNextBuffer(); + try std.testing.expect(try next.drawImage(value, image_handle, 0, 0, 2, 1, 2, 2, 0, 0, 1, 1, protocol)); + next.fillRect(0, 0, 1, 1, ansi.rgbaFromFloats(0.0, 0.0, 1.0, 0.5)); + try std.testing.expectEqual(@as(usize, 1), next.image_placements.items.len); + try std.testing.expectEqual(@as(u32, ' '), next.get(0, 0).?.char); + try std.testing.expectEqual(ansi.rgbColor(0, 0, 255, 255), next.get(0, 0).?.bg); + try std.testing.expect(gp.isImageChar(next.get(1, 0).?.char)); + _ = test_renderer.renderer.render(false); + const covered_output = test_renderer.memory.lastWrite(); + try std.testing.expect(covered_output.len > 0); + try std.testing.expect(std.mem.indexOf(u8, covered_output, "\x1b_Ga=t") == null); + try std.testing.expect(std.mem.indexOf(u8, covered_output, "a=p") == null); + try std.testing.expect(std.mem.indexOf(u8, covered_output, "a=d") == null); + try std.testing.expect(std.mem.indexOf(u8, covered_output, "\x1bP0;1;0q") == null); + + next = test_renderer.renderer.getNextBuffer(); + try std.testing.expect(try next.drawImage(value, image_handle, 0, 0, 2, 1, 2, 2, 0, 0, 1, 1, protocol)); + try std.testing.expectEqual(@as(usize, 1), next.image_placements.items.len); + _ = test_renderer.renderer.render(false); + const restored_output = test_renderer.memory.lastWrite(); + switch (protocol) { + .kitty => { + try std.testing.expect(restored_output.len > 0); + try std.testing.expect(std.mem.indexOf(u8, restored_output, "\x1b_Ga=t") == null); + try std.testing.expect(std.mem.indexOf(u8, restored_output, "a=p") == null); + }, + .sixel => try std.testing.expect(std.mem.indexOf(u8, restored_output, "\x1bP0;1;0q") != null), + .blocks => try std.testing.expect(std.mem.indexOf(u8, restored_output, "█") != null), + .auto => unreachable, + } +} + +test "renderer keeps Kitty placement under an alpha-colored plane" { + try expectPlaneCoversImage(.kitty); +} + +test "renderer splits changed background runs around clean Kitty image cells" { + const pool = gp.initGlobalPool(std.testing.allocator); + defer gp.deinitGlobalPool(); + defer link.deinitGlobalLinkPool(); + var test_renderer = try TestRenderer.create(std.testing.allocator, 5, 1, pool); + defer test_renderer.deinit(); + const value = try image.createFromRgba(std.testing.allocator, &[_]u8{ 255, 0, 0, 255 }, 1, 1, 4); + const image_handle = try handles.insert(.image, @ptrCast(value)); + defer { + const token = handles.beginDestroy(image_handle, .image, image.Image).?; + token.ptr.deinit(); + handles.finishDestroy(token.handle); + } + test_renderer.renderer.terminal.caps.kitty_graphics = true; + + var next = test_renderer.renderer.getNextBuffer(); + next.fillRect(0, 0, 5, 1, ansi.rgbColor(8, 11, 18, 255)); + try std.testing.expect(try next.drawImage(value, image_handle, 1, 0, 2, 1, 2, 2, 0, 0, 1, 1, .kitty)); + try std.testing.expectEqual(renderer.RenderStatus.rendered, test_renderer.renderer.render(true)); + + next = test_renderer.renderer.getNextBuffer(); + next.fillRect(0, 0, 5, 1, ansi.rgbColor(32, 43, 61, 255)); + try std.testing.expect(try next.drawImage(value, image_handle, 1, 0, 2, 1, 2, 2, 0, 0, 1, 1, .kitty)); + try std.testing.expectEqual(renderer.RenderStatus.rendered, test_renderer.renderer.render(false)); + + const output = test_renderer.memory.lastWrite(); + try std.testing.expect(std.mem.indexOf(u8, output, "\x1b[1;1H") != null); + try std.testing.expect(std.mem.indexOf(u8, output, "\x1b[0m\x1b[1;4H") != null); + try std.testing.expect(std.mem.indexOf(u8, output, "\x1b_G") == null); +} + +test "renderer retransmits Sixel after removing an alpha-colored plane" { + try expectPlaneCoversImage(.sixel); +} + +test "renderer redraws blocks after removing an alpha-colored plane" { + try expectPlaneCoversImage(.blocks); +} + +test "renderer clears old Sixel pixels when replacing an image" { + const pool = gp.initGlobalPool(std.testing.allocator); + defer gp.deinitGlobalPool(); + defer link.deinitGlobalLinkPool(); + var test_renderer = try TestRenderer.create(std.testing.allocator, 2, 1, pool); + defer test_renderer.deinit(); + const opaque_image = try image.createFromRgba(std.testing.allocator, &[_]u8{ + 255, 0, 0, 255, 255, 0, 0, 255, + }, 2, 1, 8); + const replacement = try image.createFromRgba(std.testing.allocator, &[_]u8{ + 0, 0, 0, 0, 0, 0, 255, 255, + }, 2, 1, 8); + const opaque_handle = try handles.insert(.image, @ptrCast(opaque_image)); + const replacement_handle = try handles.insert(.image, @ptrCast(replacement)); + defer { + const replacement_token = handles.beginDestroy(replacement_handle, .image, image.Image).?; + replacement_token.ptr.deinit(); + handles.finishDestroy(replacement_token.handle); + const opaque_token = handles.beginDestroy(opaque_handle, .image, image.Image).?; + opaque_token.ptr.deinit(); + handles.finishDestroy(opaque_token.handle); + } + + var next = test_renderer.renderer.getNextBuffer(); + try std.testing.expect(try next.drawImage(opaque_image, opaque_handle, 0, 0, 2, 1, 2, 2, 0, 0, 2, 1, .sixel)); + _ = test_renderer.renderer.render(true); + + next = test_renderer.renderer.getNextBuffer(); + try std.testing.expect(try next.drawImage(replacement, replacement_handle, 0, 0, 2, 1, 2, 2, 0, 0, 2, 1, .sixel)); + _ = test_renderer.renderer.render(false); + const output = test_renderer.memory.lastWrite(); + const sixel = std.mem.indexOf(u8, output, "\x1bP0;1;0q") orelse return error.TestUnexpectedResult; + try std.testing.expect(std.mem.indexOfScalar(u8, output[0..sixel], ' ') != null); +} + +const PaintedSixelColor = struct { r: u8, g: u8, b: u8 }; + +// Parses the last Sixel DCS in an output stream and returns the palette colors +// that actually paint at least one pixel (RGB in the 0-100 Sixel scale). +fn paintedSixelColors(output: []const u8, colors: *[8]PaintedSixelColor) !usize { + const start = std.mem.lastIndexOf(u8, output, "\x1bP0;1;0q") orelse return error.NoSixelPayload; + const end = std.mem.indexOfPos(u8, output, start, "\x1b\\") orelse return error.NoSixelPayload; + const payload = output[start + 8 .. end]; + + var palette = [_][3]u8{.{ 0, 0, 0 }} ** 256; + var painted = [_]bool{false} ** 256; + var selected: usize = 0; + var position: usize = 0; + if (position < payload.len and payload[position] == '"') { + position += 1; + var separators: usize = 0; + while (position < payload.len and separators < 3) : (position += 1) { + if (payload[position] == ';') separators += 1; + } + while (position < payload.len and std.ascii.isDigit(payload[position])) position += 1; + } + while (position < payload.len) { + const byte = payload[position]; + position += 1; + switch (byte) { + '#' => { + var value: usize = 0; + while (position < payload.len and std.ascii.isDigit(payload[position])) : (position += 1) { + value = value * 10 + (payload[position] - '0'); + } + selected = value; + if (position < payload.len and payload[position] == ';') { + var channels: [4]u8 = .{ 0, 0, 0, 0 }; + for (0..4) |channel| { + if (position >= payload.len or payload[position] != ';') return error.InvalidSixel; + position += 1; + var channel_value: usize = 0; + while (position < payload.len and std.ascii.isDigit(payload[position])) : (position += 1) { + channel_value = channel_value * 10 + (payload[position] - '0'); + } + channels[channel] = @intCast(@min(channel_value, 255)); + } + palette[selected] = .{ channels[1], channels[2], channels[3] }; + } + }, + '!' => { + while (position < payload.len and std.ascii.isDigit(payload[position])) position += 1; + if (position < payload.len) { + if (payload[position] > '?' and payload[position] <= '~') painted[selected] = true; + position += 1; + } + }, + '$', '-' => {}, + '?' => {}, + '@'...'~' => painted[selected] = true, + else => {}, + } + } + var count: usize = 0; + for (painted, 0..) |used, index| { + if (!used or count >= colors.len) continue; + colors[count] = .{ .r = palette[index][0], .g = palette[index][1], .b = palette[index][2] }; + count += 1; + } + return count; +} + +fn expectSinglePaintedSixelColor(output: []const u8, r: [2]u8, g: [2]u8, b: [2]u8) !void { + var colors: [8]PaintedSixelColor = undefined; + const count = try paintedSixelColors(output, &colors); + try std.testing.expectEqual(@as(usize, 1), count); + try std.testing.expect(colors[0].r >= r[0] and colors[0].r <= r[1]); + try std.testing.expect(colors[0].g >= g[0] and colors[0].g <= g[1]); + try std.testing.expect(colors[0].b >= b[0] and colors[0].b <= b[1]); +} + +test "renderer dims sixel placements by opacity instead of hiding them" { + const pool = gp.initGlobalPool(std.testing.allocator); + defer gp.deinitGlobalPool(); + defer link.deinitGlobalLinkPool(); + var test_renderer = try TestRenderer.create(std.testing.allocator, 4, 2, pool); + defer test_renderer.deinit(); + const red = try image.createFromRgba(std.testing.allocator, &[_]u8{ + 255, 0, 0, 255, 255, 0, 0, 255, + 255, 0, 0, 255, 255, 0, 0, 255, + }, 2, 2, 8); + const red_handle = try handles.insert(.image, @ptrCast(red)); + defer { + const token = handles.beginDestroy(red_handle, .image, image.Image).?; + token.ptr.deinit(); + handles.finishDestroy(token.handle); + } + test_renderer.renderer.terminal.caps.sixel = true; + + // Opacity 0.4 over the default (transparent) background must stay visible, + // dimmed toward black: red 255 * 0.4 = 102 -> 40 on the Sixel 0-100 scale. + var next = test_renderer.renderer.getNextBuffer(); + try next.pushOpacity(0.4); + try std.testing.expect(try next.drawImage(red, red_handle, 0, 0, 1, 1, 2, 2, 0, 0, 2, 2, .sixel)); + next.popOpacity(); + try std.testing.expectEqual(renderer.RenderStatus.rendered, test_renderer.renderer.render(true)); + try expectSinglePaintedSixelColor(test_renderer.memory.lastWrite(), .{ 35, 45 }, .{ 0, 2 }, .{ 0, 2 }); + + // The same placement over an opaque blue background must dim toward blue: + // 0.4 * red + 0.6 * blue = (102, 0, 153) -> (40, 0, 60). + test_renderer.renderer.setBackgroundColor(ansi.rgbColor(0, 0, 255, 255)); + _ = test_renderer.renderer.render(true); + next = test_renderer.renderer.getNextBuffer(); + try next.pushOpacity(0.4); + try std.testing.expect(try next.drawImage(red, red_handle, 0, 0, 1, 1, 2, 2, 0, 0, 2, 2, .sixel)); + next.popOpacity(); + try std.testing.expectEqual(renderer.RenderStatus.rendered, test_renderer.renderer.render(true)); + try expectSinglePaintedSixelColor(test_renderer.memory.lastWrite(), .{ 35, 45 }, .{ 0, 2 }, .{ 55, 65 }); + + // Opacity 0.75 over the same blue background: (191, 0, 64) -> (75, 0, 25). + // Today this renders at full brightness because opacity only rescales alpha. + next = test_renderer.renderer.getNextBuffer(); + try next.pushOpacity(0.75); + try std.testing.expect(try next.drawImage(red, red_handle, 0, 0, 1, 1, 2, 2, 0, 0, 2, 2, .sixel)); + next.popOpacity(); + try std.testing.expectEqual(renderer.RenderStatus.rendered, test_renderer.renderer.render(true)); + try expectSinglePaintedSixelColor(test_renderer.memory.lastWrite(), .{ 70, 80 }, .{ 0, 2 }, .{ 20, 30 }); + + // Full opacity is unaffected by the background: pure red (100, 0, 0). + next = test_renderer.renderer.getNextBuffer(); + try std.testing.expect(try next.drawImage(red, red_handle, 0, 0, 1, 1, 2, 2, 0, 0, 2, 2, .sixel)); + try std.testing.expectEqual(renderer.RenderStatus.rendered, test_renderer.renderer.render(true)); + try expectSinglePaintedSixelColor(test_renderer.memory.lastWrite(), .{ 95, 100 }, .{ 0, 2 }, .{ 0, 2 }); +} + +test "renderer keeps image alpha holes transparent while dimming by opacity" { + const pool = gp.initGlobalPool(std.testing.allocator); + defer gp.deinitGlobalPool(); + defer link.deinitGlobalLinkPool(); + var test_renderer = try TestRenderer.create(std.testing.allocator, 4, 2, pool); + defer test_renderer.deinit(); + // Left pixel opaque red, right pixel fully transparent. + const value = try image.createFromRgba(std.testing.allocator, &[_]u8{ + 255, 0, 0, 255, 0, 0, 0, 0, + }, 2, 1, 8); + const value_handle = try handles.insert(.image, @ptrCast(value)); + defer { + const token = handles.beginDestroy(value_handle, .image, image.Image).?; + token.ptr.deinit(); + handles.finishDestroy(token.handle); + } + test_renderer.renderer.terminal.caps.sixel = true; + + var next = test_renderer.renderer.getNextBuffer(); + try next.pushOpacity(0.6); + try std.testing.expect(try next.drawImage(value, value_handle, 0, 0, 2, 1, 2, 1, 0, 0, 2, 1, .sixel)); + next.popOpacity(); + try std.testing.expectEqual(renderer.RenderStatus.rendered, test_renderer.renderer.render(true)); + + const output = test_renderer.memory.lastWrite(); + var colors: [8]PaintedSixelColor = undefined; + const count = try paintedSixelColors(output, &colors); + // Only the dimmed red pixel paints; the transparent pixel stays a hole. + try std.testing.expectEqual(@as(usize, 1), count); + try std.testing.expect(colors[0].r >= 55 and colors[0].r <= 65); + + const start = std.mem.lastIndexOf(u8, output, "\x1bP0;1;0q").?; + const end = std.mem.indexOfPos(u8, output, start, "\x1b\\").?; + const payload = output[start + 8 .. end]; + // Exactly one column paints in the single 2x1 band: one '@' data char. + try std.testing.expectEqual(@as(usize, 1), std.mem.count(u8, payload, "@")); +} + +test "buffered backend grows and commits a complete large frame" { + var memory = TestMemoryOutput.init(std.testing.allocator); + defer memory.deinit(); + var backend = try renderer.BufferedBackend.create(std.testing.allocator, memory.bufferedOutput()); + defer backend.deinit(); + backend.beginFrame(); + var writer = backend.writer(); + const oversized = try std.testing.allocator.alloc(u8, renderer.OUTPUT_BUFFER_SIZE + 1); + defer std.testing.allocator.free(oversized); + @memset(oversized, 42); + try writer.writeAll(oversized); + try std.testing.expectEqual(@import("../renderer-output.zig").WriteStatus.ok, backend.endFrame()); + try std.testing.expectEqual(oversized.len, memory.bytes.items.len); + try std.testing.expectEqualSlices(u8, oversized, memory.bytes.items); +} fn createWithOptionsOnce(allocator: std.mem.Allocator, width: u32, height: u32) !void { const pool = gp.initGlobalPool(allocator); @@ -1500,6 +2272,182 @@ test "renderer - commitSplitFooterSnapshot writes append before footer repaint i try std.testing.expectEqual(@as(usize, 1), sync_count); } +test "renderer - pinned split scrollback repaints live native images after append" { + const pool = gp.initGlobalPool(std.testing.allocator); + defer gp.deinitGlobalPool(); + var test_renderer = try TestRenderer.create(std.testing.allocator, 4, 3, pool); + defer test_renderer.deinit(); + const value = try image.createFromRgba(std.testing.allocator, &[_]u8{ 255, 0, 0, 255 }, 1, 1, 4); + const image_handle = try handles.insert(.image, @ptrCast(value)); + defer { + const token = handles.beginDestroy(image_handle, .image, image.Image).?; + token.ptr.deinit(); + handles.finishDestroy(token.handle); + } + test_renderer.renderer.terminal.caps.kitty_graphics = true; + _ = test_renderer.renderer.resetSplitScrollback(2, 2); + + var next = test_renderer.renderer.getNextBuffer(); + try std.testing.expect(try next.drawImage(value, image_handle, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, .kitty)); + try std.testing.expectEqual(renderer.RenderStatus.rendered, test_renderer.renderer.render(true)); + + var snapshot = try OptimizedBuffer.init(std.testing.allocator, 4, 1, .{ .pool = pool }); + defer snapshot.deinit(); + try snapshot.drawText("line", 0, 0, .{ 255, 255, 255, 255 }, null, 0); + + next = test_renderer.renderer.getNextBuffer(); + try std.testing.expect(try next.drawImage(value, image_handle, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, .kitty)); + const result = test_renderer.renderer.commitSplitFooterSnapshotBatched(snapshot, 4, false, true, 2, false, true, true); + try std.testing.expectEqual(renderer.RenderStatus.rendered, result.status); + + const output = test_renderer.memory.lastWrite(); + const append_index = std.mem.indexOf(u8, output, "line") orelse return error.TestUnexpectedResult; + const placement_index = std.mem.indexOf(u8, output, "\x1b_Ga=p") orelse return error.TestUnexpectedResult; + try std.testing.expect(placement_index > append_index); +} + +test "renderer - split scrollback materializes image fallback cells" { + const pool = gp.initGlobalPool(std.testing.allocator); + defer gp.deinitGlobalPool(); + var local_link_pool = link.LinkPool.init(std.testing.allocator); + defer local_link_pool.deinit(); + var test_renderer = try TestRenderer.create(std.testing.allocator, 4, 3, pool); + defer test_renderer.deinit(); + const value = try image.createFromRgba(std.testing.allocator, &[_]u8{ 255, 0, 0, 255 }, 1, 1, 4); + const image_handle = try handles.insert(.image, @ptrCast(value)); + defer { + const token = handles.beginDestroy(image_handle, .image, image.Image).?; + token.ptr.deinit(); + handles.finishDestroy(token.handle); + } + const snapshot = try OptimizedBuffer.init(std.testing.allocator, 2, 1, .{ .pool = pool, .link_pool = &local_link_pool }); + defer snapshot.deinit(); + try std.testing.expect(try snapshot.drawImage(value, image_handle, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, .auto)); + try snapshot.drawText("X", 1, 0, .{ 255, 255, 255, 255 }, null, 0); + _ = test_renderer.renderer.resetSplitScrollback(2, 2); + _ = test_renderer.renderer.commitSplitFooterSnapshotBatched(snapshot, 2, false, true, 2, false, true, true); + const output = test_renderer.memory.lastWrite(); + try std.testing.expect(std.mem.indexOfScalar(u8, output, 'X') != null); + try std.testing.expect(std.mem.indexOf(u8, output, "\x1b_G") == null); + try std.testing.expect(std.mem.indexOf(u8, output, "\x1bP0;1;0q") == null); + try std.testing.expect(std.mem.indexOf(u8, output, "█") != null); +} + +test "renderer - split scrollback uses native Kitty when Kitty is selected" { + const pool = gp.initGlobalPool(std.testing.allocator); + defer gp.deinitGlobalPool(); + var local_link_pool = link.LinkPool.init(std.testing.allocator); + defer local_link_pool.deinit(); + var test_renderer = try TestRenderer.create(std.testing.allocator, 4, 3, pool); + defer test_renderer.deinit(); + const value = try image.createFromRgba(std.testing.allocator, &[_]u8{ 255, 0, 0, 255 }, 1, 1, 4); + const image_handle = try handles.insert(.image, @ptrCast(value)); + defer { + const token = handles.beginDestroy(image_handle, .image, image.Image).?; + token.ptr.deinit(); + handles.finishDestroy(token.handle); + } + test_renderer.renderer.terminal.processCapabilityResponse("\x1b_Gi=31337;OK\x1b\\"); + var snapshot = try OptimizedBuffer.init(std.testing.allocator, 1, 1, .{ .pool = pool, .link_pool = &local_link_pool }); + defer snapshot.deinit(); + snapshot.clear(ansi.rgbColor(1, 2, 3, 255), null); + try std.testing.expect(try snapshot.drawImage(value, image_handle, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, .auto)); + _ = test_renderer.renderer.resetSplitScrollback(2, 2); + + const result = test_renderer.renderer.commitSplitFooterSnapshotBatched(snapshot, 1, false, true, 2, false, true, true); + try std.testing.expectEqual(renderer.RenderStatus.rendered, result.status); + + const output = test_renderer.memory.lastWrite(); + try std.testing.expect(std.mem.indexOf(u8, output, "\x1b_Ga=t") != null); + try std.testing.expect(std.mem.indexOf(u8, output, "\x1b_Ga=p") != null); + try std.testing.expect(std.mem.indexOf(u8, output, ",U=1,") == null); + try std.testing.expect(std.mem.indexOf(u8, output, "\u{10EEEE}") == null); + try std.testing.expect(std.mem.indexOf(u8, output, "█") == null); + try std.testing.expect(std.mem.indexOf(u8, output, "48;2;1;2;3") == null); +} + +test "renderer - failed Kitty scrollback preparation does not publish the batch" { + const pool = gp.initGlobalPool(std.testing.allocator); + defer gp.deinitGlobalPool(); + var local_link_pool = link.LinkPool.init(std.testing.allocator); + defer local_link_pool.deinit(); + var test_renderer = try TestRenderer.create(std.testing.allocator, 4, 3, pool); + defer test_renderer.deinit(); + const value = try image.createFromRgba(std.testing.allocator, &[_]u8{ + 255, 0, 0, 255, 0, 0, 255, 255, + }, 2, 1, 8); + const image_handle = try handles.insert(.image, @ptrCast(value)); + defer { + const token = handles.beginDestroy(image_handle, .image, image.Image).?; + token.ptr.deinit(); + handles.finishDestroy(token.handle); + } + test_renderer.renderer.terminal.processCapabilityResponse("\x1bP>|kitty(0.40.1)\x1b\\\x1b_Gi=31337;OK\x1b\\"); + var snapshot = try OptimizedBuffer.init(std.testing.allocator, 1, 1, .{ .pool = pool, .link_pool = &local_link_pool }); + defer snapshot.deinit(); + try std.testing.expect(try snapshot.drawImage(value, image_handle, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, .auto)); + _ = test_renderer.renderer.resetSplitScrollback(2, 2); + test_renderer.renderer.kittyHistoryNextImageId = std.math.maxInt(u32); + const split_scrollback = test_renderer.renderer.splitScrollback; + const render_offset = test_renderer.renderer.renderOffset; + + var failing = std.testing.FailingAllocator.init(std.testing.allocator, .{ .fail_index = 0 }); + test_renderer.renderer.allocator = failing.allocator(); + const result = test_renderer.renderer.commitSplitFooterSnapshotBatched(snapshot, 1, false, true, 2, false, true, true); + test_renderer.renderer.allocator = std.testing.allocator; + + try std.testing.expect(failing.has_induced_failure); + try std.testing.expectEqual(renderer.RenderStatus.failed, result.status); + try std.testing.expectEqual(@as(usize, 0), test_renderer.memory.lastWrite().len); + try std.testing.expectEqualDeep(split_scrollback, test_renderer.renderer.splitScrollback); + try std.testing.expectEqual(render_offset, test_renderer.renderer.renderOffset); + try std.testing.expectEqual(@as(usize, 1), snapshot.image_placements.items.len); + try std.testing.expectEqual(@as(?u32, std.math.maxInt(u32)), test_renderer.renderer.kittyHistoryNextImageId); + + const retry = test_renderer.renderer.commitSplitFooterSnapshotBatched(snapshot, 1, false, true, 2, false, true, true); + try std.testing.expectEqual(renderer.RenderStatus.rendered, retry.status); + try std.testing.expect(std.mem.indexOf(u8, test_renderer.memory.lastWrite(), "\x1b_Ga=t") != null); +} + +test "renderer - split scrollback emits native Sixel images when placement geometry is available" { + const pool = gp.initGlobalPool(std.testing.allocator); + defer gp.deinitGlobalPool(); + var local_link_pool = link.LinkPool.init(std.testing.allocator); + defer local_link_pool.deinit(); + var test_renderer = try TestRenderer.create(std.testing.allocator, 4, 3, pool); + defer test_renderer.deinit(); + const value = try image.createFromRgba(std.testing.allocator, &[_]u8{ 255, 0, 0, 255 }, 1, 1, 4); + const image_handle = try handles.insert(.image, @ptrCast(value)); + defer { + const token = handles.beginDestroy(image_handle, .image, image.Image).?; + token.ptr.deinit(); + handles.finishDestroy(token.handle); + } + test_renderer.renderer.terminal.caps.sixel = true; + var snapshot = try OptimizedBuffer.init(std.testing.allocator, 1, 1, .{ .pool = pool, .link_pool = &local_link_pool }); + defer snapshot.deinit(); + try std.testing.expect(try snapshot.drawImage(value, image_handle, 0, 0, 1, 1, 2, 2, 0, 0, 1, 1, .auto)); + _ = test_renderer.renderer.resetSplitScrollback(2, 2); + + const result = test_renderer.renderer.commitSplitFooterSnapshotBatched(snapshot, 1, false, true, 2, false, true, true); + try std.testing.expectEqual(renderer.RenderStatus.rendered, result.status); + + const output = test_renderer.memory.lastWrite(); + try std.testing.expect(std.mem.indexOf(u8, output, "\x1bP0;1;0q") != null); + try std.testing.expect(std.mem.indexOf(u8, output, ansi.ANSI.saveCursorState) != null); + try std.testing.expect(std.mem.indexOf(u8, output, ansi.ANSI.restoreCursorState) != null); + try std.testing.expect(std.mem.indexOf(u8, output, "█") == null); + + var covered = try OptimizedBuffer.init(std.testing.allocator, 1, 1, .{ .pool = pool, .link_pool = &local_link_pool }); + defer covered.deinit(); + try std.testing.expect(try covered.drawImage(value, image_handle, 0, 0, 1, 1, 2, 2, 0, 0, 1, 1, .auto)); + try covered.drawText("X", 0, 0, .{ 255, 255, 255, 255 }, null, 0); + const covered_result = test_renderer.renderer.commitSplitFooterSnapshotBatched(covered, 1, false, true, 2, false, true, true); + try std.testing.expectEqual(renderer.RenderStatus.rendered, covered_result.status); + try std.testing.expect(std.mem.indexOf(u8, test_renderer.memory.lastWrite(), "\x1bP0;1;0q") == null); + try std.testing.expect(std.mem.indexOfScalar(u8, test_renderer.memory.lastWrite(), 'X') != null); +} + test "renderer - commitSplitFooterSnapshot settling phase moves footer downward" { const pool = gp.initGlobalPool(std.testing.allocator); defer gp.deinitGlobalPool(); @@ -2317,31 +3265,317 @@ test "FeedBackend - prepareFrame commits existing pending bytes before new frame try std.testing.expectEqual(.ok, backend.prepareFrame()); } -test "FeedBackend - failed frame write preserves pending bytes" { +test "FeedBackend - failed frame publishes no partial bytes" { var opts = native_span_feed.defaultOptions(); opts.chunk_size = 32; opts.initial_chunks = 1; + opts.max_bytes = 32; + opts.growth_policy = @intFromEnum(native_span_feed.GrowthPolicy.block); opts.auto_commit_on_full = 0; const feed = try native_span_feed.Stream.create(std.testing.allocator, opts); defer feed.destroy(); var backend = renderer.FeedBackend.create(feed); + defer backend.deinit(); backend.beginFrame(); var failed_writer = backend.writer(); try failed_writer.writeAll("pending"); - try std.testing.expectError(error.BufferFull, failed_writer.writeAll("this-write-is-too-large-for-the-current-chunk")); + try failed_writer.writeAll("this-write-is-too-large-for-the-current-chunk"); try std.testing.expectEqual(.failed, backend.endFrame()); var span_out: [4]native_span_feed.SpanInfo = undefined; const count = feed.drainSpans(&span_out); - try std.testing.expectEqual(@as(u32, 1), count); - const pending_span = span_out[0].slice(); - try std.testing.expect(std.mem.indexOf(u8, pending_span, "pending") != null); + try std.testing.expectEqual(@as(u32, 0), count); } -test "FeedBackend - writeOut keeps pending bytes when commit is blocked" { +test "FeedBackend - failed split batch restores unpublished scrollback state" { + const pool = gp.initGlobalPool(std.testing.allocator); + defer gp.deinitGlobalPool(); + _ = link.initGlobalLinkPool(std.testing.allocator); + defer link.deinitGlobalLinkPool(); + + var opts = native_span_feed.defaultOptions(); + opts.chunk_size = 64; + opts.initial_chunks = 1; + opts.max_bytes = 64; + opts.growth_policy = @intFromEnum(native_span_feed.GrowthPolicy.block); + opts.auto_commit_on_full = 0; + const feed = try native_span_feed.Stream.create(std.testing.allocator, opts); + var cli_renderer = try CliRenderer.createWithOptions(std.testing.allocator, 40, 4, pool, .{ + .remote_mode = .remote, + .output = .{ .feed = feed }, + }); + defer feed.destroy(); + defer cli_renderer.destroy(); + + var snapshot = try OptimizedBuffer.init( + std.testing.allocator, + 32, + 1, + .{ .pool = pool, .width_method = .unicode, .respectAlpha = false }, + ); + defer snapshot.deinit(); + try snapshot.drawText("first split batch row", 0, 0, .{ 255, 255, 255, 255 }, null, 0); + + _ = cli_renderer.resetSplitScrollback(1, 3); + const before_scrollback = cli_renderer.splitScrollback; + const before_offset = cli_renderer.renderOffset; + + const first = cli_renderer.commitSplitFooterSnapshotBatched(snapshot, 32, false, true, 3, false, true, false); + try std.testing.expectEqual(renderer.RenderStatus.rendered, first.status); + const final = cli_renderer.commitSplitFooterSnapshotBatched(snapshot, 32, false, true, 3, false, false, true); + try std.testing.expectEqual(renderer.RenderStatus.failed, final.status); + + try std.testing.expectEqual(before_scrollback, cli_renderer.splitScrollback); + try std.testing.expectEqual(before_offset, cli_renderer.renderOffset); + var spans: [4]native_span_feed.SpanInfo = undefined; + try std.testing.expectEqual(@as(u32, 0), feed.drainSpans(&spans)); +} + +test "FeedBackend - failed split repaint restores unpublished transition state" { + const pool = gp.initGlobalPool(std.testing.allocator); + defer gp.deinitGlobalPool(); + _ = link.initGlobalLinkPool(std.testing.allocator); + defer link.deinitGlobalLinkPool(); + + var opts = native_span_feed.defaultOptions(); + opts.chunk_size = 32; + opts.initial_chunks = 4; + opts.max_bytes = 128; + opts.growth_policy = @intFromEnum(native_span_feed.GrowthPolicy.block); + opts.auto_commit_on_full = 0; + const feed = try native_span_feed.Stream.create(std.testing.allocator, opts); + var cli_renderer = try CliRenderer.createWithOptions(std.testing.allocator, 4, 2, pool, .{ + .remote_mode = .remote, + .output = .{ .feed = feed }, + .clearOnShutdown = false, + }); + defer feed.destroy(); + defer cli_renderer.destroy(); + + _ = cli_renderer.resetSplitScrollback(2, 2); + cli_renderer.setPendingSplitFooterTransition(.viewport_scroll, 1, 1, 2, 1, 1); + const before_scrollback = cli_renderer.splitScrollback; + const before_offset = cli_renderer.renderOffset; + const before_transition = cli_renderer.pendingSplitFooterTransition; + + const blocker = [_]u8{'x'} ** 65; + try feed.writeAtomic(&blocker); + const result = cli_renderer.repaintSplitFooter(2, true); + try std.testing.expectEqual(renderer.RenderStatus.failed, result.status); + try std.testing.expectEqual(before_scrollback, cli_renderer.splitScrollback); + try std.testing.expectEqual(before_offset, cli_renderer.renderOffset); + try std.testing.expectEqual(before_transition, cli_renderer.pendingSplitFooterTransition); +} + +test "FeedBackend - failed ordinary render retries split transition" { + const pool = gp.initGlobalPool(std.testing.allocator); + defer gp.deinitGlobalPool(); + _ = link.initGlobalLinkPool(std.testing.allocator); + defer link.deinitGlobalLinkPool(); + + var opts = native_span_feed.defaultOptions(); + opts.chunk_size = 32; + opts.initial_chunks = 8; + opts.max_bytes = 256; + opts.growth_policy = @intFromEnum(native_span_feed.GrowthPolicy.block); + opts.auto_commit_on_full = 0; + const feed = try native_span_feed.Stream.create(std.testing.allocator, opts); + var cli_renderer = try CliRenderer.createWithOptions(std.testing.allocator, 4, 2, pool, .{ + .remote_mode = .remote, + .output = .{ .feed = feed }, + .clearOnShutdown = false, + }); + defer feed.destroy(); + defer cli_renderer.destroy(); + + _ = cli_renderer.resetSplitScrollback(2, 2); + cli_renderer.setPendingSplitFooterTransition(.viewport_scroll, 1, 1, 2, 1, 1); + const before_scrollback = cli_renderer.splitScrollback; + const before_offset = cli_renderer.renderOffset; + const before_transition = cli_renderer.pendingSplitFooterTransition; + + const blocker = [_]u8{'x'} ** 193; + try feed.writeAtomic(&blocker); + try std.testing.expectEqual(renderer.RenderStatus.failed, cli_renderer.render(true)); + try std.testing.expectEqual(before_scrollback, cli_renderer.splitScrollback); + try std.testing.expectEqual(before_offset, cli_renderer.renderOffset); + try std.testing.expectEqual(before_transition, cli_renderer.pendingSplitFooterTransition); + + var spans: [8]native_span_feed.SpanInfo = undefined; + var count = feed.drainSpans(&spans); + for (spans[0..count]) |span| feed.markSpanConsumed(span); + + try std.testing.expectEqual(renderer.RenderStatus.rendered, cli_renderer.render(true)); + count = feed.drainSpans(&spans); + var output: [256]u8 = undefined; + var output_len: usize = 0; + for (spans[0..count]) |span| { + const bytes = span.slice(); + @memcpy(output[output_len .. output_len + bytes.len], bytes); + output_len += bytes.len; + feed.markSpanConsumed(span); + } + try std.testing.expect(std.mem.indexOf(u8, output[0..output_len], "\x1b[1T") != null); +} + +test "FeedBackend - failed frame keeps the published hit grid" { + const pool = gp.initGlobalPool(std.testing.allocator); + defer gp.deinitGlobalPool(); + _ = link.initGlobalLinkPool(std.testing.allocator); + defer link.deinitGlobalLinkPool(); + + var opts = native_span_feed.defaultOptions(); + opts.chunk_size = 32; + opts.initial_chunks = 8; + opts.max_bytes = 256; + opts.growth_policy = @intFromEnum(native_span_feed.GrowthPolicy.block); + opts.auto_commit_on_full = 0; + const feed = try native_span_feed.Stream.create(std.testing.allocator, opts); + var cli_renderer = try CliRenderer.createWithOptions(std.testing.allocator, 1, 1, pool, .{ + .remote_mode = .remote, + .output = .{ .feed = feed }, + .clearOnShutdown = false, + }); + defer feed.destroy(); + defer cli_renderer.destroy(); + + cli_renderer.addToHitGrid(0, 0, 1, 1, 11); + try std.testing.expectEqual(renderer.RenderStatus.rendered, cli_renderer.render(true)); + var spans: [8]native_span_feed.SpanInfo = undefined; + var count = feed.drainSpans(&spans); + for (spans[0..count]) |span| feed.markSpanConsumed(span); + try std.testing.expectEqual(@as(u32, 11), cli_renderer.checkHit(0, 0)); + + cli_renderer.addToHitGrid(0, 0, 1, 1, 22); + const blocker = [_]u8{'x'} ** 193; + try feed.writeAtomic(&blocker); + try std.testing.expectEqual(renderer.RenderStatus.failed, cli_renderer.render(true)); + try std.testing.expectEqual(@as(u32, 11), cli_renderer.checkHit(0, 0)); + + count = feed.drainSpans(&spans); + for (spans[0..count]) |span| feed.markSpanConsumed(span); + cli_renderer.addToHitGrid(0, 0, 1, 1, 22); + try std.testing.expectEqual(renderer.RenderStatus.rendered, cli_renderer.render(true)); + try std.testing.expectEqual(@as(u32, 22), cli_renderer.checkHit(0, 0)); +} + +test "FeedBackend - failed frame retries unsent terminal controls" { + const pool = gp.initGlobalPool(std.testing.allocator); + defer gp.deinitGlobalPool(); + _ = link.initGlobalLinkPool(std.testing.allocator); + defer link.deinitGlobalLinkPool(); + + var opts = native_span_feed.defaultOptions(); + opts.chunk_size = 32; + opts.initial_chunks = 4; + opts.max_bytes = 128; + opts.growth_policy = @intFromEnum(native_span_feed.GrowthPolicy.block); + opts.auto_commit_on_full = 0; + const feed = try native_span_feed.Stream.create(std.testing.allocator, opts); + var cli_renderer = try CliRenderer.createWithOptions(std.testing.allocator, 1, 1, pool, .{ + .remote_mode = .remote, + .output = .{ .feed = feed }, + .clearOnShutdown = false, + }); + defer feed.destroy(); + defer cli_renderer.destroy(); + + cli_renderer.terminal.setCursorPosition(1, 1, true); + try std.testing.expectEqual(renderer.RenderStatus.rendered, cli_renderer.render(false)); + var spans: [8]native_span_feed.SpanInfo = undefined; + var count = feed.drainSpans(&spans); + for (spans[0..count]) |span| feed.markSpanConsumed(span); + + cli_renderer.terminal.setCursorColor(ansi.rgbColor(0x12, 0x34, 0x56, 255)); + cli_renderer.terminal.setCursorStyle(.line, false); + cli_renderer.terminal.setMousePointerStyle(.pointer); + + const blocker = [_]u8{'x'} ** 65; + try feed.writeAtomic(&blocker); + try std.testing.expectEqual(renderer.RenderStatus.failed, cli_renderer.render(false)); + + count = feed.drainSpans(&spans); + for (spans[0..count]) |span| feed.markSpanConsumed(span); + + try std.testing.expectEqual(renderer.RenderStatus.rendered, cli_renderer.render(false)); + count = feed.drainSpans(&spans); + var output: [256]u8 = undefined; + var output_len: usize = 0; + for (spans[0..count]) |span| { + const bytes = span.slice(); + @memcpy(output[output_len .. output_len + bytes.len], bytes); + output_len += bytes.len; + feed.markSpanConsumed(span); + } + + try std.testing.expect(std.mem.indexOf(u8, output[0..output_len], "\x1b]12;#123456\x07") != null); + try std.testing.expect(std.mem.indexOf(u8, output[0..output_len], ansi.ANSI.cursorLine) != null); + try std.testing.expect(std.mem.indexOf(u8, output[0..output_len], "\x1b]22;pointer\x07") != null); +} + +test "FeedBackend - failed Sixel frame does not publish an unterminated DCS" { + const pool = gp.initGlobalPool(std.testing.allocator); + defer gp.deinitGlobalPool(); + _ = link.initGlobalLinkPool(std.testing.allocator); + defer link.deinitGlobalLinkPool(); + + var opts = native_span_feed.defaultOptions(); + opts.chunk_size = 256; + opts.initial_chunks = 1; + opts.max_bytes = 256; + opts.growth_policy = @intFromEnum(native_span_feed.GrowthPolicy.block); + opts.auto_commit_on_full = 0; + const feed = try native_span_feed.Stream.create(std.testing.allocator, opts); + var cli_renderer = try CliRenderer.createWithOptions(std.testing.allocator, 8, 4, pool, .{ + .remote_mode = .remote, + .output = .{ .feed = feed }, + }); + defer feed.destroy(); + defer cli_renderer.destroy(); + cli_renderer.terminal.caps.sixel = true; + + const pixels = try std.testing.allocator.alloc(u8, 32 * 16 * 4); + defer std.testing.allocator.free(pixels); + for (0..32 * 16) |index| { + pixels[index * 4] = @truncate(index * 17); + pixels[index * 4 + 1] = @truncate(index * 31); + pixels[index * 4 + 2] = @truncate(index * 47); + pixels[index * 4 + 3] = 255; + } + const value = try image.createFromRgba(std.testing.allocator, pixels, 32, 16, 32 * 4); + const image_handle = try handles.insert(.image, @ptrCast(value)); + defer { + const token = handles.beginDestroy(image_handle, .image, image.Image).?; + token.ptr.deinit(); + handles.finishDestroy(token.handle); + } + + try std.testing.expect(try cli_renderer.getNextBuffer().drawImage( + value, + image_handle, + 0, + 0, + 8, + 4, + 32, + 16, + 0, + 0, + 32, + 16, + .sixel, + )); + try std.testing.expectEqual(renderer.RenderStatus.failed, cli_renderer.render(true)); + + var spans: [8]native_span_feed.SpanInfo = undefined; + const count = feed.drainSpans(&spans); + try std.testing.expectEqual(@as(u32, 0), count); +} + +test "FeedBackend - writeOut publishes nothing when the queue is blocked" { var opts = native_span_feed.defaultOptions(); opts.chunk_size = 64; opts.initial_chunks = 2; @@ -2359,24 +3593,24 @@ test "FeedBackend - writeOut keeps pending bytes when commit is blocked" { try std.testing.expect(backend.shouldSkipFrame()); backend.writeOut("shutdown"); - try std.testing.expect(feed.hasPendingBytes()); + try std.testing.expect(!feed.hasPendingBytes()); var span_out: [4]native_span_feed.SpanInfo = undefined; var count = feed.drainSpans(&span_out); try std.testing.expectEqual(@as(u32, 1), count); feed.markSpanConsumed(span_out[0]); - try std.testing.expectEqual(.skipped, backend.prepareFrame()); - + try std.testing.expectEqual(.ok, backend.prepareFrame()); count = feed.drainSpans(&span_out); - try std.testing.expectEqual(@as(u32, 1), count); - try std.testing.expectEqualStrings("shutdown", span_out[0].slice()); + try std.testing.expectEqual(@as(u32, 0), count); } -test "FeedBackend - writeOutMultiple keeps partial pending batch bytes" { +test "FeedBackend - writeOutMultiple publishes no partial batch" { var opts = native_span_feed.defaultOptions(); opts.chunk_size = 32; opts.initial_chunks = 1; + opts.max_bytes = 32; + opts.growth_policy = @intFromEnum(native_span_feed.GrowthPolicy.block); opts.auto_commit_on_full = 0; const feed = try native_span_feed.Stream.create(std.testing.allocator, opts); @@ -2389,14 +3623,12 @@ test "FeedBackend - writeOutMultiple keeps partial pending batch bytes" { }; backend.writeOutMultiple(&failed_batch); - try std.testing.expect(feed.hasPendingBytes()); - - try std.testing.expectEqual(.skipped, backend.prepareFrame()); + try std.testing.expect(!feed.hasPendingBytes()); + try std.testing.expectEqual(.ok, backend.prepareFrame()); var span_out: [4]native_span_feed.SpanInfo = undefined; const count = feed.drainSpans(&span_out); - try std.testing.expectEqual(@as(u32, 1), count); - try std.testing.expectEqualStrings("pending", span_out[0].slice()); + try std.testing.expectEqual(@as(u32, 0), count); } test "FeedBackend - supportsThreading is false" { @@ -2517,15 +3749,12 @@ test "threaded buffered backend skips instead of blocking behind output" { test "buffered backend reports a failed frame when growth allocation fails" { const rout = @import("../renderer-output.zig"); - // Initial create performs exactly two allocations (buffer A and B). The - // growth realloc is the next allocation/resize, which must fail. - var failing = std.testing.FailingAllocator.init(std.testing.allocator, .{ - .fail_index = 2, - .resize_fail_index = 0, - }); var out = CountingOutput{}; - var backend = try renderer.BufferedBackend.create(failing.allocator(), out.bufferedOutput()); + var backend = try renderer.BufferedBackend.create(std.testing.allocator, out.bufferedOutput()); defer backend.deinit(); + var failing = std.testing.FailingAllocator.init(std.testing.allocator, .{ .fail_index = 0, .resize_fail_index = 0 }); + backend.allocator = failing.allocator(); + defer backend.allocator = std.testing.allocator; backend.beginFrame(); var w = backend.writer(); @@ -2539,6 +3768,7 @@ test "buffered backend reports a failed frame when growth allocation fails" { }; } try std.testing.expect(write_failed); + try std.testing.expect(failing.has_induced_failure); // A frame whose bytes were dropped must be reported as failed so the // renderer can force a full repaint, and the truncated ANSI stream must @@ -2602,3 +3832,505 @@ test "buffered backend frees grown buffers cleanly on deinit" { } try std.testing.expectEqual(@import("../renderer-output.zig").WriteStatus.ok, backend.endFrame()); } + +test "renderer scales kitty transmission alpha by placement opacity" { + const pool = gp.initGlobalPool(std.testing.allocator); + defer gp.deinitGlobalPool(); + defer link.deinitGlobalLinkPool(); + var test_renderer = try TestRenderer.create(std.testing.allocator, 4, 2, pool); + defer test_renderer.deinit(); + const value = try image.createFromRgba(std.testing.allocator, &[_]u8{ 200, 100, 50, 255 }, 1, 1, 4); + const value_handle = try handles.insert(.image, @ptrCast(value)); + defer { + const token = handles.beginDestroy(value_handle, .image, image.Image).?; + token.ptr.deinit(); + handles.finishDestroy(token.handle); + } + test_renderer.renderer.terminal.caps.kitty_graphics = true; + + var next = test_renderer.renderer.getNextBuffer(); + try next.pushOpacity(0.5); + try std.testing.expect(try next.drawImage(value, value_handle, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, .auto)); + next.popOpacity(); + try std.testing.expectEqual(renderer.RenderStatus.rendered, test_renderer.renderer.render(true)); + const output = test_renderer.memory.lastWrite(); + + // Translucent placements transmit RGBA with the alpha channel scaled so the + // terminal composites the fade; colors are left for kitty to blend. + try std.testing.expect(std.mem.indexOf(u8, output, "f=32") != null); + const transmit_start = std.mem.indexOf(u8, output, "\x1b_Ga=t").?; + const transmit_end = std.mem.indexOfPos(u8, output, transmit_start, "\x1b\\").? + 2; + const transmitted = try terminal_image_test.decodeKittyChunks(output[transmit_start..transmit_end]); + defer std.testing.allocator.free(transmitted); + try std.testing.expectEqual(@as(usize, 4), transmitted.len); + try std.testing.expectEqual(@as(u8, 200), transmitted[0]); + try std.testing.expectEqual(@as(u8, 100), transmitted[1]); + try std.testing.expectEqual(@as(u8, 50), transmitted[2]); + try std.testing.expect(@abs(@as(i16, transmitted[3]) - 128) <= 1); + + // Opacity changes retransmit under the same kitty id: delete then new data. + next = test_renderer.renderer.getNextBuffer(); + try next.pushOpacity(0.25); + try std.testing.expect(try next.drawImage(value, value_handle, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, .auto)); + next.popOpacity(); + try std.testing.expectEqual(renderer.RenderStatus.rendered, test_renderer.renderer.render(false)); + const second = test_renderer.memory.lastWrite(); + try std.testing.expect(std.mem.indexOf(u8, second, "a=d,d=I") != null); + const second_start = std.mem.indexOf(u8, second, "\x1b_Ga=t").?; + const second_end = std.mem.indexOfPos(u8, second, second_start, "\x1b\\").? + 2; + const retransmitted = try terminal_image_test.decodeKittyChunks(second[second_start..second_end]); + defer std.testing.allocator.free(retransmitted); + try std.testing.expect(@abs(@as(i16, retransmitted[3]) - 64) <= 1); +} + +test "renderer bounds sixel cache entries and evicts least recently used payloads" { + const pool = gp.initGlobalPool(std.testing.allocator); + defer gp.deinitGlobalPool(); + defer link.deinitGlobalLinkPool(); + var test_renderer = try TestRenderer.create(std.testing.allocator, 4, 2, pool); + defer test_renderer.deinit(); + test_renderer.renderer.terminal.caps.sixel = true; + + const image_count = 260; + var images: [image_count]*image.Image = undefined; + var image_handles: [image_count]u32 = undefined; + for (0..image_count) |index| { + const shade: u8 = @intCast(index % 256); + images[index] = try image.createFromRgba(std.testing.allocator, &[_]u8{ shade, 255 - shade, @intCast((index / 4) % 256), 255 }, 1, 1, 4); + image_handles[index] = try handles.insert(.image, @ptrCast(images[index])); + } + defer for (image_handles) |handle| { + const token = handles.beginDestroy(handle, .image, image.Image).?; + token.ptr.deinit(); + handles.finishDestroy(token.handle); + }; + + var next = test_renderer.renderer.getNextBuffer(); + for (0..image_count) |index| { + try std.testing.expect(try next.drawImage(images[index], image_handles[index], 0, 0, 1, 1, 2, 2, 0, 0, 1, 1, .auto)); + } + try std.testing.expectEqual(renderer.RenderStatus.rendered, test_renderer.renderer.render(true)); + try std.testing.expectEqual(@as(u64, image_count), test_renderer.renderer.sixelCacheMisses); + try std.testing.expect(test_renderer.renderer.sixelCache.count() <= 256); + try std.testing.expect(test_renderer.renderer.sixelCacheBytes > 0); + + // The first payloads were the least recently used and must have been evicted. + next = test_renderer.renderer.getNextBuffer(); + try std.testing.expect(try next.drawImage(images[0], image_handles[0], 0, 0, 1, 1, 2, 2, 0, 0, 1, 1, .auto)); + try std.testing.expectEqual(renderer.RenderStatus.rendered, test_renderer.renderer.render(true)); + try std.testing.expectEqual(@as(u64, image_count + 1), test_renderer.renderer.sixelCacheMisses); + + // A recently used payload is still cached. + next = test_renderer.renderer.getNextBuffer(); + try std.testing.expect(try next.drawImage(images[image_count - 1], image_handles[image_count - 1], 0, 0, 1, 1, 2, 2, 0, 0, 1, 1, .auto)); + try std.testing.expectEqual(renderer.RenderStatus.rendered, test_renderer.renderer.render(true)); + try std.testing.expectEqual(@as(u64, 1), test_renderer.renderer.sixelCacheHits); +} + +test "renderer treats fully transparent sixel placements as empty without failing" { + const pool = gp.initGlobalPool(std.testing.allocator); + defer gp.deinitGlobalPool(); + defer link.deinitGlobalLinkPool(); + var test_renderer = try TestRenderer.create(std.testing.allocator, 4, 2, pool); + defer test_renderer.deinit(); + const value = try image.createFromRgba(std.testing.allocator, &[_]u8{ 255, 0, 0, 0 }, 1, 1, 4); + const value_handle = try handles.insert(.image, @ptrCast(value)); + defer { + const token = handles.beginDestroy(value_handle, .image, image.Image).?; + token.ptr.deinit(); + handles.finishDestroy(token.handle); + } + test_renderer.renderer.terminal.caps.sixel = true; + + var next = test_renderer.renderer.getNextBuffer(); + try std.testing.expect(try next.drawImage(value, value_handle, 0, 0, 1, 1, 2, 2, 0, 0, 1, 1, .auto)); + try std.testing.expectEqual(renderer.RenderStatus.rendered, test_renderer.renderer.render(true)); + try std.testing.expect(std.mem.indexOf(u8, test_renderer.memory.lastWrite(), "\x1bP0") == null); + try std.testing.expect(!test_renderer.renderer.imageRenderFailed); + try std.testing.expect(!test_renderer.renderer.force_full_repaint); + + // The empty payload is cached; the placement stays a cheap no-op. + next = test_renderer.renderer.getNextBuffer(); + try std.testing.expect(try next.drawImage(value, value_handle, 0, 0, 1, 1, 2, 2, 0, 0, 1, 1, .auto)); + try std.testing.expectEqual(renderer.RenderStatus.rendered, test_renderer.renderer.render(true)); + try std.testing.expectEqual(@as(u64, 1), test_renderer.renderer.sixelCacheHits); + try std.testing.expect(std.mem.indexOf(u8, test_renderer.memory.lastWrite(), "\x1bP0") == null); + try std.testing.expect(!test_renderer.renderer.imageRenderFailed); +} + +test "renderer retransmits sixel placements on a forced full repaint" { + const pool = gp.initGlobalPool(std.testing.allocator); + defer gp.deinitGlobalPool(); + defer link.deinitGlobalLinkPool(); + var test_renderer = try TestRenderer.create(std.testing.allocator, 4, 2, pool); + defer test_renderer.deinit(); + const value = try image.createFromRgba(std.testing.allocator, &[_]u8{ 255, 0, 0, 255 }, 1, 1, 4); + const value_handle = try handles.insert(.image, @ptrCast(value)); + defer { + const token = handles.beginDestroy(value_handle, .image, image.Image).?; + token.ptr.deinit(); + handles.finishDestroy(token.handle); + } + test_renderer.renderer.terminal.caps.sixel = true; + + var next = test_renderer.renderer.getNextBuffer(); + try std.testing.expect(try next.drawImage(value, value_handle, 0, 0, 1, 1, 2, 2, 0, 0, 1, 1, .auto)); + try std.testing.expectEqual(renderer.RenderStatus.rendered, test_renderer.renderer.render(true)); + try std.testing.expect(std.mem.indexOf(u8, test_renderer.memory.lastWrite(), "\x1bP0;1;0q") != null); + + // A full repaint (failed-frame recovery, palette change) rewrites every + // cell, so the unchanged placement's pixels must be transmitted again. + test_renderer.renderer.force_full_repaint = true; + next = test_renderer.renderer.getNextBuffer(); + try std.testing.expect(try next.drawImage(value, value_handle, 0, 0, 1, 1, 2, 2, 0, 0, 1, 1, .auto)); + try std.testing.expectEqual(renderer.RenderStatus.rendered, test_renderer.renderer.render(false)); + try std.testing.expect(std.mem.indexOf(u8, test_renderer.memory.lastWrite(), "\x1bP0;1;0q") != null); +} + +test "renderer leaves clean text alone when graphics content changes" { + const pool = gp.initGlobalPool(std.testing.allocator); + defer gp.deinitGlobalPool(); + defer link.deinitGlobalLinkPool(); + var test_renderer = try TestRenderer.create(std.testing.allocator, 8, 4, pool); + defer test_renderer.deinit(); + const first = try image.createFromRgba(std.testing.allocator, &[_]u8{ 255, 0, 0, 255 }, 1, 1, 4); + const second = try image.createFromRgba(std.testing.allocator, &[_]u8{ 0, 255, 0, 255 }, 1, 1, 4); + const first_handle = try handles.insert(.image, @ptrCast(first)); + const second_handle = try handles.insert(.image, @ptrCast(second)); + defer for ([_]u32{ second_handle, first_handle }) |handle| { + const token = handles.beginDestroy(handle, .image, image.Image).?; + token.ptr.deinit(); + handles.finishDestroy(token.handle); + }; + test_renderer.renderer.terminal.caps.kitty_graphics = true; + + var next = test_renderer.renderer.getNextBuffer(); + try next.drawText("HELLO", 0, 3, .{ 255, 255, 255, 255 }, null, 0); + try std.testing.expect(try next.drawImage(first, first_handle, 0, 0, 2, 2, 0, 0, 0, 0, 1, 1, .auto)); + try std.testing.expectEqual(renderer.RenderStatus.rendered, test_renderer.renderer.render(true)); + try std.testing.expect(std.mem.indexOf(u8, test_renderer.memory.lastWrite(), "HELLO") != null); + + // Replace the image while preserving text and placement geometry. + // Kitty swaps the image server side; unchanged text must not be re-emitted + // and the reserved cells must not be cleared again. + next = test_renderer.renderer.getNextBuffer(); + try next.drawText("HELLO", 0, 3, .{ 255, 255, 255, 255 }, null, 0); + try std.testing.expect(try next.drawImage(second, second_handle, 0, 0, 2, 2, 0, 0, 0, 0, 1, 1, .auto)); + try std.testing.expectEqual(renderer.RenderStatus.rendered, test_renderer.renderer.render(false)); + const output = test_renderer.memory.lastWrite(); + try std.testing.expect(std.mem.indexOf(u8, output, "\x1b_Ga=t") != null); + try std.testing.expect(std.mem.indexOf(u8, output, "HELLO") == null); + try std.testing.expect(std.mem.indexOf(u8, output, " ") == null); +} + +test "renderer clears dirty sixel cells as one batched space run" { + const pool = gp.initGlobalPool(std.testing.allocator); + defer gp.deinitGlobalPool(); + defer link.deinitGlobalLinkPool(); + var test_renderer = try TestRenderer.create(std.testing.allocator, 8, 2, pool); + defer test_renderer.deinit(); + const first = try image.createFromRgba(std.testing.allocator, &[_]u8{ 255, 0, 0, 255 }, 1, 1, 4); + const second = try image.createFromRgba(std.testing.allocator, &[_]u8{ 0, 255, 0, 255 }, 1, 1, 4); + const first_handle = try handles.insert(.image, @ptrCast(first)); + const second_handle = try handles.insert(.image, @ptrCast(second)); + defer for ([_]u32{ second_handle, first_handle }) |handle| { + const token = handles.beginDestroy(handle, .image, image.Image).?; + token.ptr.deinit(); + handles.finishDestroy(token.handle); + }; + test_renderer.renderer.terminal.caps.sixel = true; + + var next = test_renderer.renderer.getNextBuffer(); + try std.testing.expect(try next.drawImage(first, first_handle, 0, 0, 6, 1, 12, 2, 0, 0, 1, 1, .auto)); + try std.testing.expectEqual(renderer.RenderStatus.rendered, test_renderer.renderer.render(true)); + + next = test_renderer.renderer.getNextBuffer(); + try std.testing.expect(try next.drawImage(second, second_handle, 0, 0, 6, 1, 12, 2, 0, 0, 1, 1, .auto)); + try std.testing.expectEqual(renderer.RenderStatus.rendered, test_renderer.renderer.render(false)); + const output = test_renderer.memory.lastWrite(); + const sixel = std.mem.indexOf(u8, output, "\x1bP0;1;0q") orelse return error.TestUnexpectedResult; + // The six reserved cells clear with a single cursor move and six spaces; + // the only other CUP before the payload positions the Sixel itself. + try std.testing.expect(std.mem.indexOf(u8, output[0..sixel], "\x1b[1;1H ") != null); + try std.testing.expectEqual(@as(usize, 2), std.mem.count(u8, output[0..sixel], "\x1b[1;1H")); +} + +test "renderer downscales large kitty stills to their placement pixel size" { + const pool = gp.initGlobalPool(std.testing.allocator); + defer gp.deinitGlobalPool(); + defer link.deinitGlobalLinkPool(); + var test_renderer = try TestRenderer.create(std.testing.allocator, 8, 4, pool); + defer test_renderer.deinit(); + // 64x64 source shown at 16x16 pixels: 16x the pixel area. + const pixels = try std.testing.allocator.alloc(u8, 64 * 64 * 4); + defer std.testing.allocator.free(pixels); + for (0..64 * 64) |index| { + pixels[index * 4] = 200; + pixels[index * 4 + 1] = @truncate(index); + pixels[index * 4 + 2] = 30; + pixels[index * 4 + 3] = 255; + } + const value = try image.createFromRgba(std.testing.allocator, pixels, 64, 64, 64 * 4); + const value_handle = try handles.insert(.image, @ptrCast(value)); + defer { + const token = handles.beginDestroy(value_handle, .image, image.Image).?; + token.ptr.deinit(); + handles.finishDestroy(token.handle); + } + test_renderer.renderer.terminal.caps.kitty_graphics = true; + + var next = test_renderer.renderer.getNextBuffer(); + try std.testing.expect(try next.drawImage(value, value_handle, 0, 0, 2, 2, 16, 16, 0, 0, 64, 64, .auto)); + try std.testing.expectEqual(renderer.RenderStatus.rendered, test_renderer.renderer.render(true)); + const output = test_renderer.memory.lastWrite(); + + // The transmission carries the downscaled pixels and the placement crops + // the full downscaled image. + try std.testing.expect(std.mem.indexOf(u8, output, "a=t,f=24,s=16,v=16") != null); + try std.testing.expect(std.mem.indexOf(u8, output, "x=0,y=0,w=16,h=16,C=1") != null); + const transmit_start = std.mem.indexOf(u8, output, "\x1b_Ga=t").?; + const transmit_end = std.mem.indexOfPos(u8, output, transmit_start, "\x1b[").?; + const transmitted = try terminal_image_test.decodeKittyChunks(output[transmit_start..transmit_end]); + defer std.testing.allocator.free(transmitted); + try std.testing.expectEqual(@as(usize, 16 * 16 * 3), transmitted.len); + try std.testing.expectEqual(@as(u8, 200), transmitted[0]); + try std.testing.expectEqual(@as(u8, 30), transmitted[2]); + + // Changing the placement pixel size invalidates the downscaled pixels. + next = test_renderer.renderer.getNextBuffer(); + try std.testing.expect(try next.drawImage(value, value_handle, 0, 0, 2, 2, 20, 20, 0, 0, 64, 64, .auto)); + try std.testing.expectEqual(renderer.RenderStatus.rendered, test_renderer.renderer.render(false)); + const second = test_renderer.memory.lastWrite(); + try std.testing.expect(std.mem.indexOf(u8, second, "a=d,d=I") != null); + try std.testing.expect(std.mem.indexOf(u8, second, "a=t,f=24,s=20,v=20") != null); +} + +test "renderer transmits small kitty images at native size" { + const pool = gp.initGlobalPool(std.testing.allocator); + defer gp.deinitGlobalPool(); + defer link.deinitGlobalLinkPool(); + var test_renderer = try TestRenderer.create(std.testing.allocator, 8, 4, pool); + defer test_renderer.deinit(); + const value = try image.createFromRgba(std.testing.allocator, &([_]u8{ 9, 8, 7, 255 } ** 64), 8, 8, 32); + const value_handle = try handles.insert(.image, @ptrCast(value)); + defer { + const token = handles.beginDestroy(value_handle, .image, image.Image).?; + token.ptr.deinit(); + handles.finishDestroy(token.handle); + } + test_renderer.renderer.terminal.caps.kitty_graphics = true; + + // Displayed at 16x16 pixels (upscale): the source pixels are transmitted + // untouched and kitty performs the scaling. + const next = test_renderer.renderer.getNextBuffer(); + try std.testing.expect(try next.drawImage(value, value_handle, 0, 0, 2, 2, 16, 16, 0, 0, 8, 8, .auto)); + try std.testing.expectEqual(renderer.RenderStatus.rendered, test_renderer.renderer.render(true)); + const output = test_renderer.memory.lastWrite(); + try std.testing.expect(std.mem.indexOf(u8, output, "a=t,f=24,s=8,v=8") != null); + try std.testing.expect(std.mem.indexOf(u8, output, "x=0,y=0,w=8,h=8,C=1") != null); +} + +test "renderer transmits only cropped kitty source pixels" { + const pool = gp.initGlobalPool(std.testing.allocator); + defer gp.deinitGlobalPool(); + defer link.deinitGlobalLinkPool(); + var test_renderer = try TestRenderer.create(std.testing.allocator, 8, 4, pool); + defer test_renderer.deinit(); + const pixels = try std.testing.allocator.alloc(u8, 64 * 64 * 4); + defer std.testing.allocator.free(pixels); + for (0..64 * 64) |index| { + pixels[index * 4] = @truncate(index); + pixels[index * 4 + 1] = 100; + pixels[index * 4 + 2] = 200; + pixels[index * 4 + 3] = 255; + } + const value = try image.createFromRgba(std.testing.allocator, pixels, 64, 64, 64 * 4); + const value_handle = try handles.insert(.image, @ptrCast(value)); + defer { + const token = handles.beginDestroy(value_handle, .image, image.Image).?; + token.ptr.deinit(); + handles.finishDestroy(token.handle); + } + test_renderer.renderer.terminal.caps.kitty_graphics = true; + + const next = test_renderer.renderer.getNextBuffer(); + try std.testing.expect(try next.drawImage(value, value_handle, 0, 0, 2, 2, 16, 16, 8, 12, 8, 8, .auto)); + try std.testing.expectEqual(renderer.RenderStatus.rendered, test_renderer.renderer.render(true)); + const output = test_renderer.memory.lastWrite(); + + try std.testing.expect(std.mem.indexOf(u8, output, "a=t,f=24,s=8,v=8") != null); + try std.testing.expect(std.mem.indexOf(u8, output, "x=0,y=0,w=8,h=8,C=1") != null); + const transmit_start = std.mem.indexOf(u8, output, "\x1b_Ga=t").?; + const transmit_end = std.mem.indexOfPos(u8, output, transmit_start, "\x1b[").?; + const transmitted = try terminal_image_test.decodeKittyChunks(output[transmit_start..transmit_end]); + defer std.testing.allocator.free(transmitted); + try std.testing.expectEqual(@as(usize, 8 * 8 * 3), transmitted.len); + var expected: [8 * 8 * 3]u8 = undefined; + for (0..8) |y| { + for (0..8) |x| { + const offset = (y * 8 + x) * 3; + expected[offset] = @truncate((12 + y) * 64 + 8 + x); + expected[offset + 1] = 100; + expected[offset + 2] = 200; + } + } + try std.testing.expectEqualSlices(u8, &expected, transmitted); + + const changed = test_renderer.renderer.getNextBuffer(); + try std.testing.expect(try changed.drawImage(value, value_handle, 0, 0, 2, 2, 16, 16, 16, 12, 8, 8, .auto)); + try std.testing.expectEqual(renderer.RenderStatus.rendered, test_renderer.renderer.render(false)); + const changed_output = test_renderer.memory.lastWrite(); + try std.testing.expect(std.mem.indexOf(u8, changed_output, "a=d,d=I") != null); + try std.testing.expect(std.mem.indexOf(u8, changed_output, "a=t,f=24,s=8,v=8") != null); + const changed_start = std.mem.indexOf(u8, changed_output, "\x1b_Ga=t").?; + const changed_end = std.mem.indexOfPos(u8, changed_output, changed_start, "\x1b[").?; + const changed_transmitted = try terminal_image_test.decodeKittyChunks(changed_output[changed_start..changed_end]); + defer std.testing.allocator.free(changed_transmitted); + for (0..8) |y| { + for (0..8) |x| { + const offset = (y * 8 + x) * 3; + expected[offset] = @truncate((12 + y) * 64 + 16 + x); + } + } + try std.testing.expectEqualSlices(u8, &expected, changed_transmitted); + try std.testing.expect(!std.mem.eql(u8, transmitted, changed_transmitted)); +} + +test "renderer does not publish a frame when image dirty preparation fails" { + const pool = gp.initGlobalPool(std.testing.allocator); + defer gp.deinitGlobalPool(); + defer link.deinitGlobalLinkPool(); + var test_renderer = try TestRenderer.create(std.testing.allocator, 2, 1, pool); + defer test_renderer.deinit(); + test_renderer.renderer.terminal.caps.sixel = true; + + const value = try image.createFromRgba(std.testing.allocator, &[_]u8{ 255, 0, 0, 255 }, 1, 1, 4); + const value_handle = try handles.insert(.image, @ptrCast(value)); + defer { + const token = handles.beginDestroy(value_handle, .image, image.Image).?; + token.ptr.deinit(); + handles.finishDestroy(token.handle); + } + try test_renderer.renderer.pendingImages.ensureTotalCapacity(std.testing.allocator, 1); + const next = test_renderer.renderer.getNextBuffer(); + try std.testing.expect(try next.drawImage(value, value_handle, 0, 0, 1, 1, 2, 2, 0, 0, 1, 1, .sixel)); + + var failing = std.testing.FailingAllocator.init(std.testing.allocator, .{ .fail_index = 0 }); + test_renderer.renderer.allocator = failing.allocator(); + const status = test_renderer.renderer.render(true); + test_renderer.renderer.allocator = std.testing.allocator; + + try std.testing.expect(failing.has_induced_failure); + try std.testing.expectEqual(renderer.RenderStatus.failed, status); + try std.testing.expectEqual(@as(usize, 0), test_renderer.renderer.currentImages.items.len); + try std.testing.expectEqual(@as(usize, 0), test_renderer.memory.lastWrite().len); + + const retry = test_renderer.renderer.getNextBuffer(); + try std.testing.expect(try retry.drawImage(value, value_handle, 0, 0, 1, 1, 2, 2, 0, 0, 1, 1, .sixel)); + try std.testing.expectEqual(renderer.RenderStatus.rendered, test_renderer.renderer.render(true)); + try std.testing.expect(std.mem.indexOf(u8, test_renderer.memory.lastWrite(), "\x1bP0;1;0q") != null); + try std.testing.expectEqual(@as(usize, 1), test_renderer.renderer.currentImages.items.len); +} + +test "renderer does not publish a frame when Sixel preparation fails" { + const pool = gp.initGlobalPool(std.testing.allocator); + defer gp.deinitGlobalPool(); + defer link.deinitGlobalLinkPool(); + var test_renderer = try TestRenderer.create(std.testing.allocator, 1, 1, pool); + defer test_renderer.deinit(); + test_renderer.renderer.terminal.caps.sixel = true; + + const value = try image.createFromRgba(std.testing.allocator, &[_]u8{ 255, 0, 0, 255 }, 1, 1, 4); + const value_handle = try handles.insert(.image, @ptrCast(value)); + defer { + const token = handles.beginDestroy(value_handle, .image, image.Image).?; + token.ptr.deinit(); + handles.finishDestroy(token.handle); + } + try test_renderer.renderer.imageDirty.ensureTotalCapacity(std.testing.allocator, 1); + try test_renderer.renderer.pendingImages.ensureTotalCapacity(std.testing.allocator, 1); + const next = test_renderer.renderer.getNextBuffer(); + try std.testing.expect(try next.drawImage(value, value_handle, 0, 0, 1, 1, 2, 2, 0, 0, 1, 1, .sixel)); + + var failing = std.testing.FailingAllocator.init(std.testing.allocator, .{ .fail_index = 0 }); + test_renderer.renderer.allocator = failing.allocator(); + const status = test_renderer.renderer.render(true); + test_renderer.renderer.allocator = std.testing.allocator; + + try std.testing.expect(failing.has_induced_failure); + try std.testing.expectEqual(renderer.RenderStatus.failed, status); + try std.testing.expectEqual(@as(usize, 0), test_renderer.renderer.currentImages.items.len); + try std.testing.expectEqual(@as(usize, 0), test_renderer.memory.lastWrite().len); + + const retry = test_renderer.renderer.getNextBuffer(); + try std.testing.expect(try retry.drawImage(value, value_handle, 0, 0, 1, 1, 2, 2, 0, 0, 1, 1, .sixel)); + try std.testing.expectEqual(renderer.RenderStatus.rendered, test_renderer.renderer.render(true)); + try std.testing.expect(std.mem.indexOf(u8, test_renderer.memory.lastWrite(), "\x1bP0;1;0q") != null); + try std.testing.expectEqual(@as(usize, 1), test_renderer.renderer.currentImages.items.len); +} + +test "renderer does not publish Kitty output when image state staging fails" { + const pool = gp.initGlobalPool(std.testing.allocator); + defer gp.deinitGlobalPool(); + defer link.deinitGlobalLinkPool(); + var test_renderer = try TestRenderer.create(std.testing.allocator, 1, 1, pool); + defer test_renderer.deinit(); + test_renderer.renderer.terminal.caps.kitty_graphics = true; + + const value = try image.createFromRgba(std.testing.allocator, &[_]u8{ 255, 0, 0, 255 }, 1, 1, 4); + const value_handle = try handles.insert(.image, @ptrCast(value)); + defer { + const token = handles.beginDestroy(value_handle, .image, image.Image).?; + token.ptr.deinit(); + handles.finishDestroy(token.handle); + } + try test_renderer.renderer.imageDirty.ensureTotalCapacity(std.testing.allocator, 1); + const next = test_renderer.renderer.getNextBuffer(); + try std.testing.expect(try next.drawImage(value, value_handle, 0, 0, 1, 1, 1, 1, 0, 0, 1, 1, .kitty)); + + var failing = std.testing.FailingAllocator.init(std.testing.allocator, .{ .fail_index = 0 }); + test_renderer.renderer.allocator = failing.allocator(); + const status = test_renderer.renderer.render(true); + test_renderer.renderer.allocator = std.testing.allocator; + + try std.testing.expect(failing.has_induced_failure); + try std.testing.expectEqual(renderer.RenderStatus.failed, status); + try std.testing.expectEqual(@as(usize, 0), test_renderer.renderer.currentImages.items.len); + try std.testing.expectEqual(@as(usize, 0), test_renderer.memory.lastWrite().len); + + const retry = test_renderer.renderer.getNextBuffer(); + try std.testing.expect(try retry.drawImage(value, value_handle, 0, 0, 1, 1, 1, 1, 0, 0, 1, 1, .kitty)); + try std.testing.expectEqual(renderer.RenderStatus.rendered, test_renderer.renderer.render(true)); + try std.testing.expect(std.mem.indexOf(u8, test_renderer.memory.lastWrite(), "\x1b_Ga=t") != null); + try std.testing.expectEqual(@as(usize, 1), test_renderer.renderer.currentImages.items.len); +} + +test "renderer clears an upper sixel hole when its lower image moves away" { + const pool = gp.initGlobalPool(std.testing.allocator); + defer gp.deinitGlobalPool(); + defer link.deinitGlobalLinkPool(); + var test_renderer = try TestRenderer.create(std.testing.allocator, 2, 1, pool); + defer test_renderer.deinit(); + test_renderer.renderer.terminal.caps.sixel = true; + + const lower = try image.createFromRgba(std.testing.allocator, &[_]u8{ 0, 0, 255, 255 }, 1, 1, 4); + const upper = try image.createFromRgba(std.testing.allocator, &[_]u8{ 0, 0, 0, 0 }, 1, 1, 4); + const lower_handle = try handles.insert(.image, @ptrCast(lower)); + const upper_handle = try handles.insert(.image, @ptrCast(upper)); + defer for ([_]u32{ upper_handle, lower_handle }) |handle| { + const token = handles.beginDestroy(handle, .image, image.Image).?; + token.ptr.deinit(); + handles.finishDestroy(token.handle); + }; + + var next = test_renderer.renderer.getNextBuffer(); + try std.testing.expect(try next.drawImage(lower, lower_handle, 0, 0, 1, 1, 2, 2, 0, 0, 1, 1, .sixel)); + try std.testing.expect(try next.drawImage(upper, upper_handle, 0, 0, 1, 1, 2, 2, 0, 0, 1, 1, .sixel)); + try std.testing.expectEqual(renderer.RenderStatus.rendered, test_renderer.renderer.render(true)); + + next = test_renderer.renderer.getNextBuffer(); + try std.testing.expect(try next.drawImage(lower, lower_handle, 1, 0, 1, 1, 2, 2, 0, 0, 1, 1, .sixel)); + try std.testing.expect(try next.drawImage(upper, upper_handle, 0, 0, 1, 1, 2, 2, 0, 0, 1, 1, .sixel)); + try std.testing.expectEqual(renderer.RenderStatus.rendered, test_renderer.renderer.render(false)); + try std.testing.expect(std.mem.indexOf(u8, test_renderer.memory.lastWrite(), "\x1b[1;1H") != null); +} diff --git a/packages/core/src/zig/tests/terminal-image_test.zig b/packages/core/src/zig/tests/terminal-image_test.zig new file mode 100644 index 0000000000..efc55d59d7 --- /dev/null +++ b/packages/core/src/zig/tests/terminal-image_test.zig @@ -0,0 +1,562 @@ +const std = @import("std"); +const terminal_image = @import("../terminal-image.zig"); +const image = @import("../image.zig"); + +const DecodedSixel = struct { + indices: []u8, + width: usize, + height: usize, + + fn deinit(self: DecodedSixel) void { + std.testing.allocator.free(self.indices); + } +}; + +fn parseUnsigned(bytes: []const u8, position: *usize) !usize { + const start = position.*; + var value: usize = 0; + while (position.* < bytes.len and std.ascii.isDigit(bytes[position.*])) : (position.* += 1) { + value = value * 10 + bytes[position.*] - '0'; + } + if (position.* == start) return error.InvalidSixel; + return value; +} + +fn decodeSixelIndices(payload: []const u8) !DecodedSixel { + const quote = std.mem.indexOfScalar(u8, payload, '"') orelse return error.InvalidSixel; + var position = quote + 1; + _ = try parseUnsigned(payload, &position); + if (position >= payload.len or payload[position] != ';') return error.InvalidSixel; + position += 1; + _ = try parseUnsigned(payload, &position); + if (position >= payload.len or payload[position] != ';') return error.InvalidSixel; + position += 1; + const width = try parseUnsigned(payload, &position); + if (position >= payload.len or payload[position] != ';') return error.InvalidSixel; + position += 1; + const height = try parseUnsigned(payload, &position); + const indices = try std.testing.allocator.alloc(u8, width * height); + errdefer std.testing.allocator.free(indices); + @memset(indices, 255); + + var selected: ?u8 = null; + var x: usize = 0; + var band_y: usize = 0; + while (position < payload.len) { + const byte = payload[position]; + position += 1; + switch (byte) { + '#' => { + selected = @intCast(try parseUnsigned(payload, &position)); + if (position < payload.len and payload[position] == ';') { + // Skip color mode and three color components. + for (0..4) |_| { + if (position >= payload.len or payload[position] != ';') return error.InvalidSixel; + position += 1; + _ = try parseUnsigned(payload, &position); + } + } + }, + '!' => { + const count = try parseUnsigned(payload, &position); + if (position >= payload.len) return error.InvalidSixel; + const data = payload[position]; + position += 1; + if (data < '?' or data > '~') return error.InvalidSixel; + for (0..count) |_| { + const mask = data - '?'; + for (0..6) |bit| { + const y = band_y + bit; + if (selected != null and x < width and y < height and mask & (@as(u8, 1) << @intCast(bit)) != 0) { + indices[y * width + x] = selected.?; + } + } + x += 1; + } + }, + '$' => x = 0, + '-' => { + x = 0; + band_y += 6; + }, + '?'...'~' => { + const mask = byte - '?'; + for (0..6) |bit| { + const y = band_y + bit; + if (selected != null and x < width and y < height and mask & (@as(u8, 1) << @intCast(bit)) != 0) { + indices[y * width + x] = selected.?; + } + } + x += 1; + }, + else => {}, + } + } + return .{ .indices = indices, .width = width, .height = height }; +} + +pub fn decodeKittyChunks(payload: []const u8) ![]u8 { + var decoded: std.ArrayList(u8) = .empty; + errdefer decoded.deinit(std.testing.allocator); + var offset: usize = 0; + while (std.mem.indexOfPos(u8, payload, offset, "\x1b_G")) |start| { + const separator = std.mem.indexOfScalarPos(u8, payload, start + 3, ';') orelse return error.InvalidKittyPayload; + const end = std.mem.indexOfPos(u8, payload, separator + 1, "\x1b\\") orelse return error.InvalidKittyPayload; + const encoded = payload[separator + 1 .. end]; + const decoded_len = try std.base64.standard.Decoder.calcSizeForSlice(encoded); + const destination = try decoded.addManyAsSlice(std.testing.allocator, decoded_len); + try std.base64.standard.Decoder.decode(destination, encoded); + offset = end + 2; + } + return decoded.toOwnedSlice(std.testing.allocator); +} + +test "kitty transmission chunks RGBA payloads and places without cursor movement" { + const pixels = try std.testing.allocator.alloc(u8, 1025 * 4); + defer std.testing.allocator.free(pixels); + @memset(pixels, 42); + const value = image.Image{ + .allocator = std.testing.allocator, + .pixels = pixels, + .metadata = .{ .width = 1025, .height = 1, .has_alpha = 1 }, + }; + var output: std.ArrayList(u8) = .empty; + defer output.deinit(std.testing.allocator); + try terminal_image.writeKittyTransmit(output.writer(std.testing.allocator), &value, 7, false); + try terminal_image.writeKittyPlacement(output.writer(std.testing.allocator), 7, 8, 2, 3, 4, 5, 0, 0, 1, 1, -99, false); + try std.testing.expect(std.mem.indexOf(u8, output.items, "i=7,m=1,q=2;") != null); + try std.testing.expect(std.mem.indexOf(u8, output.items, "\x1b_Gm=0,q=2;") != null); + try std.testing.expect(std.mem.indexOf(u8, output.items, "a=p,i=7,p=8,c=4,r=5,x=0,y=0,w=1,h=1,C=1,z=-99") != null); +} + +test "kitty transmission uses RGB only when every pixel is opaque" { + const opaque_image = try image.createFromRgba(std.testing.allocator, &[_]u8{ 1, 2, 3, 255 }, 1, 1, 4); + defer opaque_image.deinit(); + var rgb: std.ArrayList(u8) = .empty; + defer rgb.deinit(std.testing.allocator); + try terminal_image.writeKittyTransmit(rgb.writer(std.testing.allocator), opaque_image, 1, false); + try std.testing.expect(std.mem.indexOf(u8, rgb.items, "f=24") != null); + try std.testing.expect(std.mem.indexOf(u8, rgb.items, ";AQID\x1b\\") != null); + + const transparent = try image.createFromRgba(std.testing.allocator, &[_]u8{ 1, 2, 3, 4 }, 1, 1, 4); + defer transparent.deinit(); + var rgba: std.ArrayList(u8) = .empty; + defer rgba.deinit(std.testing.allocator); + try terminal_image.writeKittyTransmit(rgba.writer(std.testing.allocator), transparent, 1, false); + try std.testing.expect(std.mem.indexOf(u8, rgba.items, "f=32") != null); + try std.testing.expect(std.mem.indexOf(u8, rgba.items, ";AQIDBA==\x1b\\") != null); +} + +test "kitty transmission sends retained PNG bytes as f=100" { + const encoded = "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR4AWP4z8DwHwAFAAH/e+m+7wAAAABJRU5ErkJggg=="; + const png_len = try std.base64.standard.Decoder.calcSizeForSlice(encoded); + const png = try std.testing.allocator.alloc(u8, png_len); + defer std.testing.allocator.free(png); + try std.base64.standard.Decoder.decode(png, encoded); + const value = try image.decode(std.testing.allocator, png, .{}); + defer value.deinit(); + var output: std.ArrayList(u8) = .empty; + defer output.deinit(std.testing.allocator); + + try terminal_image.writeKittyTransmit(output.writer(std.testing.allocator), value, 9, false); + try std.testing.expect(std.mem.indexOf(u8, output.items, "a=t,f=100,i=9") != null); + const decoded = try decodeKittyChunks(output.items); + defer std.testing.allocator.free(decoded); + try std.testing.expectEqualSlices(u8, png, decoded); +} + +test "kitty transmission preserves retained PNG bytes through clone" { + const encoded = "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR4AWP4z8DwHwAFAAH/e+m+7wAAAABJRU5ErkJggg=="; + const png_len = try std.base64.standard.Decoder.calcSizeForSlice(encoded); + const png = try std.testing.allocator.alloc(u8, png_len); + defer std.testing.allocator.free(png); + try std.base64.standard.Decoder.decode(png, encoded); + const value = try image.decode(std.testing.allocator, png, .{}); + defer value.deinit(); + const cloned = try value.clone(); + defer cloned.deinit(); + var output: std.ArrayList(u8) = .empty; + defer output.deinit(std.testing.allocator); + + try terminal_image.writeKittyTransmit(output.writer(std.testing.allocator), cloned, 10, false); + try std.testing.expect(std.mem.indexOf(u8, output.items, "a=t,f=100,i=10") != null); + const decoded = try decodeKittyChunks(output.items); + defer std.testing.allocator.free(decoded); + try std.testing.expectEqualSlices(u8, png, decoded); +} + +test "kitty transmission rejects truncated image storage before writing" { + var pixels = [_]u8{ 1, 2, 3 }; + const value = image.Image{ + .allocator = std.testing.allocator, + .pixels = &pixels, + .metadata = .{ .width = 1, .height = 1 }, + }; + var output: std.ArrayList(u8) = .empty; + defer output.deinit(std.testing.allocator); + try std.testing.expectError(error.InvalidImageData, terminal_image.writeKittyTransmit(output.writer(std.testing.allocator), &value, 1, false)); + try std.testing.expectEqual(@as(usize, 0), output.items.len); +} + +test "kitty RGB transmission preserves pixels across chunk boundaries" { + const width = 2050; + const height = 1; + const pixels = try std.testing.allocator.alloc(u8, width * height * 4); + defer std.testing.allocator.free(pixels); + const expected = try std.testing.allocator.alloc(u8, width * height * 3); + defer std.testing.allocator.free(expected); + for (0..width) |pixel| { + pixels[pixel * 4] = @truncate(pixel); + pixels[pixel * 4 + 1] = @truncate(pixel * 3); + pixels[pixel * 4 + 2] = @truncate(pixel * 7); + pixels[pixel * 4 + 3] = 255; + @memcpy(expected[pixel * 3 ..][0..3], pixels[pixel * 4 ..][0..3]); + } + const value = try image.createFromRgba(std.testing.allocator, pixels, width, height, width * 4); + defer value.deinit(); + var output: std.ArrayList(u8) = .empty; + defer output.deinit(std.testing.allocator); + try terminal_image.writeKittyTransmit(output.writer(std.testing.allocator), value, 1, false); + const decoded = try decodeKittyChunks(output.items); + defer std.testing.allocator.free(decoded); + try std.testing.expectEqualSlices(u8, expected, decoded); +} + +test "sixel encoding writes palette raster and terminator" { + const value = try image.createFromRgba(std.testing.allocator, &[_]u8{ 255, 0, 0, 255 }, 1, 1, 4); + defer value.deinit(); + var output: std.ArrayList(u8) = .empty; + defer output.deinit(std.testing.allocator); + try terminal_image.writeSixel(std.testing.allocator, output.writer(std.testing.allocator), value, false); + try std.testing.expect(std.mem.startsWith(u8, output.items, "\x1bP0;1;0q\"1;1;1;1")); + try std.testing.expect(std.mem.endsWith(u8, output.items, "\x1b\\")); + try std.testing.expect(std.mem.indexOf(u8, output.items, ";2;100;0;0") != null); +} + +test "sixel encoding does not open a DCS when payload generation fails" { + const pixels = try std.testing.allocator.alloc(u8, 0); + defer std.testing.allocator.free(pixels); + const value = image.Image{ + .allocator = std.testing.allocator, + .pixels = pixels, + .metadata = .{ .width = 1, .height = 1 }, + }; + var output: std.ArrayList(u8) = .empty; + defer output.deinit(std.testing.allocator); + try std.testing.expectError(error.InvalidImageData, terminal_image.writeSixel(std.testing.allocator, output.writer(std.testing.allocator), &value, false)); + try std.testing.expectEqual(@as(usize, 0), output.items.len); +} + +test "kitty tmux passthrough doubles inner escape bytes" { + const value = try image.createFromRgba(std.testing.allocator, &[_]u8{ 1, 2, 3, 4 }, 1, 1, 4); + defer value.deinit(); + var output: std.ArrayList(u8) = .empty; + defer output.deinit(std.testing.allocator); + try terminal_image.writeKittyTransmit(output.writer(std.testing.allocator), value, 11, true); + try std.testing.expect(std.mem.startsWith(u8, output.items, "\x1bPtmux;\x1b\x1b_G")); + try std.testing.expect(std.mem.endsWith(u8, output.items, "\x1b\x1b\\\x1b\\")); +} + +test "sixel encoding uses RLE and omits transparent pixels" { + const pixels = [_]u8{ + 255, 0, 0, 255, 255, 0, 0, 255, 255, 0, 0, 255, 255, 0, 0, 255, + 0, 0, 0, 0, + }; + const value = try image.createFromRgba(std.testing.allocator, &pixels, 5, 1, 20); + defer value.deinit(); + var output: std.ArrayList(u8) = .empty; + defer output.deinit(std.testing.allocator); + try terminal_image.writeSixel(std.testing.allocator, output.writer(std.testing.allocator), value, false); + try std.testing.expect(std.mem.indexOf(u8, output.items, "!4@") != null); +} + +test "sixel adaptive palette caps at 255 colors deterministically" { + const width = 512; + const pixels = try std.testing.allocator.alloc(u8, width * 4); + defer std.testing.allocator.free(pixels); + for (0..width) |index| { + const offset = index * 4; + pixels[offset] = @intCast(((index >> 6) & 0x1F) << 3); + pixels[offset + 1] = @intCast(((index >> 3) & 0x1F) << 3); + pixels[offset + 2] = @intCast((index & 0x1F) << 3); + pixels[offset + 3] = 255; + } + const value = try image.createFromRgba(std.testing.allocator, pixels, width, 1, width * 4); + defer value.deinit(); + var first: std.ArrayList(u8) = .empty; + defer first.deinit(std.testing.allocator); + var second: std.ArrayList(u8) = .empty; + defer second.deinit(std.testing.allocator); + try terminal_image.writeSixel(std.testing.allocator, first.writer(std.testing.allocator), value, false); + try terminal_image.writeSixel(std.testing.allocator, second.writer(std.testing.allocator), value, false); + try std.testing.expectEqualSlices(u8, first.items, second.items); + try std.testing.expectEqual(@as(usize, 255), std.mem.count(u8, first.items, ";2;")); +} + +test "sixel adaptive palette assigns shorter indices to common colors" { + const width = 100; + const pixels = try std.testing.allocator.alloc(u8, width * 4); + defer std.testing.allocator.free(pixels); + for (0..width) |index| { + const offset = index * 4; + const color: [4]u8 = if (index < 90) .{ 240, 32, 16, 255 } else .{ 8, 64, 224, 255 }; + @memcpy(pixels[offset..][0..4], &color); + } + const value = try image.createFromRgba(std.testing.allocator, pixels, width, 1, width * 4); + defer value.deinit(); + var quantized = try terminal_image.quantizeSixel(std.testing.allocator, value, 2); + defer quantized.deinit(); + try std.testing.expectEqual(@as(usize, 2), quantized.palette_len); + try std.testing.expectEqual(@as(usize, 90), std.mem.count(u8, quantized.indices, &[_]u8{0})); +} + +test "sixel indexed encoding preserves the supplied palette" { + const indices = [_]u8{ 0, 1, 0, 1 }; + const palette = [_][3]u8{ .{ 12, 34, 56 }, .{ 210, 180, 90 } }; + var output: std.ArrayList(u8) = .empty; + defer output.deinit(std.testing.allocator); + try terminal_image.writeSixelIndexedPayload(std.testing.allocator, output.writer(std.testing.allocator), &indices, &palette, 4, 1); + try std.testing.expect(std.mem.indexOf(u8, output.items, "#0;2;5;13;22") != null); + try std.testing.expect(std.mem.indexOf(u8, output.items, "#1;2;82;71;35") != null); + const decoded = try decodeSixelIndices(output.items); + defer decoded.deinit(); + try std.testing.expectEqualSlices(u8, &indices, decoded.indices); +} + +test "sixel indexed encoding reserves index 255 for transparency" { + const palette = [_][3]u8{.{ 0, 0, 0 }} ** 256; + var output: std.ArrayList(u8) = .empty; + defer output.deinit(std.testing.allocator); + try std.testing.expectError( + error.InvalidImageData, + terminal_image.writeSixelIndexedPayload(std.testing.allocator, output.writer(std.testing.allocator), &[_]u8{255}, &palette, 1, 1), + ); + try std.testing.expectEqual(@as(usize, 0), output.items.len); +} + +test "sixel indexed scheduling preserves cursor resets bands and transparency" { + const width = 65; + const height = 13; + const palette = [_][3]u8{ .{ 255, 0, 0 }, .{ 0, 255, 0 }, .{ 0, 0, 255 } }; + const indices = try std.testing.allocator.alloc(u8, width * height); + defer std.testing.allocator.free(indices); + @memset(indices, 255); + indices[0] = 0; + indices[64] = 1; + indices[1 * width + 40] = 0; + indices[2 * width + 40] = 1; + indices[6 * width + 63] = 2; + indices[12 * width] = 1; + indices[12 * width + 64] = 0; + + var output: std.ArrayList(u8) = .empty; + defer output.deinit(std.testing.allocator); + try terminal_image.writeSixelIndexedPayload(std.testing.allocator, output.writer(std.testing.allocator), indices, &palette, width, height); + const decoded = try decodeSixelIndices(output.items); + defer decoded.deinit(); + try std.testing.expectEqual(width, decoded.width); + try std.testing.expectEqual(height, decoded.height); + try std.testing.expectEqualSlices(u8, indices, decoded.indices); + try std.testing.expectEqual(@as(usize, 2), std.mem.count(u8, output.items, "-")); + try std.testing.expect(std.mem.indexOfScalar(u8, output.items, '$') != null); +} + +test "sixel indexed scheduling handles cover-sized geometry" { + const width = 576; + const height = 1015; + const palette = [_][3]u8{ .{ 255, 255, 255 }, .{ 64, 128, 255 } }; + const indices = try std.testing.allocator.alloc(u8, width * height); + defer std.testing.allocator.free(indices); + @memset(indices, 255); + indices[0] = 0; + indices[width - 1] = 1; + indices[63] = 1; + indices[64] = 0; + indices[(height - 1) * width] = 1; + indices[height * width - 1] = 0; + + var output: std.ArrayList(u8) = .empty; + defer output.deinit(std.testing.allocator); + try terminal_image.writeSixelIndexedPayload(std.testing.allocator, output.writer(std.testing.allocator), indices, &palette, width, height); + const decoded = try decodeSixelIndices(output.items); + defer decoded.deinit(); + try std.testing.expectEqualSlices(u8, indices, decoded.indices); + try std.testing.expect(output.items.len < 10_000); +} + +test "sixel indexed scheduling handles width and band boundaries" { + const palette = [_][3]u8{ .{ 255, 255, 255 }, .{ 32, 96, 192 } }; + for ([_]usize{ 1, 5, 6, 7, 12, 13 }) |height| { + for ([_]usize{ 1, 63, 64, 65, 127, 128, 129 }) |width| { + const indices = try std.testing.allocator.alloc(u8, width * height); + defer std.testing.allocator.free(indices); + @memset(indices, 255); + indices[0] = 0; + indices[width - 1] = 1; + indices[(height - 1) * width] = 1; + indices[height * width - 1] = 0; + if (width > 64) { + indices[63] = 1; + indices[64] = 0; + } + + var output: std.ArrayList(u8) = .empty; + defer output.deinit(std.testing.allocator); + try terminal_image.writeSixelIndexedPayload( + std.testing.allocator, + output.writer(std.testing.allocator), + indices, + &palette, + @intCast(width), + @intCast(height), + ); + const decoded = try decodeSixelIndices(output.items); + defer decoded.deinit(); + try std.testing.expectEqualSlices(u8, indices, decoded.indices); + try std.testing.expect(std.mem.indexOf(u8, output.items, "$-") == null); + } + } +} + +test "adaptive Sixel palette quality by color limit" { + const width = 160; + const height = 240; + const pixels = try std.testing.allocator.alloc(u8, width * height * 4); + defer std.testing.allocator.free(pixels); + for (0..width * height) |index| { + const x = index % width; + const y = index / width; + const offset = index * 4; + pixels[offset] = @truncate(x * 13 + y * 3); + pixels[offset + 1] = @truncate(x * 5 + y * 11); + pixels[offset + 2] = @truncate(x * 7 + y * 17); + pixels[offset + 3] = 255; + } + const value = try image.createFromRgba(std.testing.allocator, pixels, width, height, width * 4); + defer value.deinit(); + const weights = [_]u64{ 2, 4, 3 }; + for ([_]usize{ 64, 128, 255 }) |color_limit| { + var quantized = try terminal_image.quantizeSixel(std.testing.allocator, value, color_limit); + defer quantized.deinit(); + var error_sum: u64 = 0; + for (quantized.indices, 0..) |palette_index, pixel| { + const color = quantized.palette[palette_index]; + const offset = pixel * 4; + for (0..3) |channel| { + const difference = @as(i32, pixels[offset + channel]) - color[channel]; + error_sum += @as(u64, @intCast(difference * difference)) * weights[channel]; + } + } + const rmse = @sqrt(@as(f64, @floatFromInt(error_sum)) / (width * height * 9)); + var filtered_error: f64 = 0; + var blocks: usize = 0; + var block_y: usize = 0; + while (block_y < height) : (block_y += 4) { + var block_x: usize = 0; + while (block_x < width) : (block_x += 4) { + for (0..3) |channel| { + var source_sum: i32 = 0; + var output_sum: i32 = 0; + for (block_y..@min(block_y + 4, height)) |y| { + for (block_x..@min(block_x + 4, width)) |x| { + const pixel = y * width + x; + source_sum += pixels[pixel * 4 + channel]; + output_sum += quantized.palette[quantized.indices[pixel]][channel]; + } + } + const difference = @as(f64, @floatFromInt(source_sum - output_sum)) / 16.0; + filtered_error += difference * difference * @as(f64, @floatFromInt(weights[channel])); + } + blocks += 1; + } + } + const filtered_rmse = @sqrt(filtered_error / @as(f64, @floatFromInt(blocks * 9))); + const maximum_rmse: f64 = switch (color_limit) { + 64 => 19, + 128 => 15.5, + 255 => 12.2, + else => unreachable, + }; + const maximum_filtered_rmse: f64 = switch (color_limit) { + 64 => 4.3, + 128 => 2.7, + 255 => 2.5, + else => unreachable, + }; + try std.testing.expect(rmse <= maximum_rmse); + try std.testing.expect(filtered_rmse <= maximum_filtered_rmse); + } +} + +test "kitty transmits decoded JPEG images as raw RGB pixels" { + const jpeg = try std.fs.cwd().readFileAlloc(std.testing.allocator, "../tests/fixtures/images/halves.jpg", 1 << 20); + defer std.testing.allocator.free(jpeg); + const decoded = try image.decode(std.testing.allocator, jpeg, .{}); + defer decoded.deinit(); + try std.testing.expect(decoded.encoded_png == null); + try std.testing.expectEqual(@as(u32, 0), decoded.metadata.has_alpha); + + var output: std.ArrayList(u8) = .empty; + defer output.deinit(std.testing.allocator); + try terminal_image.writeKittyTransmit(output.writer(std.testing.allocator), decoded, 21, false); + try std.testing.expect(std.mem.indexOf(u8, output.items, "a=t,f=24,s=16,v=8,i=21") != null); + + const transmitted = try decodeKittyChunks(output.items); + defer std.testing.allocator.free(transmitted); + try std.testing.expectEqual(@as(usize, 16 * 8 * 3), transmitted.len); + for (0..16 * 8) |pixel| { + try std.testing.expectEqual(decoded.pixels[pixel * 4], transmitted[pixel * 3]); + try std.testing.expectEqual(decoded.pixels[pixel * 4 + 1], transmitted[pixel * 3 + 1]); + try std.testing.expectEqual(decoded.pixels[pixel * 4 + 2], transmitted[pixel * 3 + 2]); + } +} + +test "kitty transmits decoded WebP alpha images as raw RGBA pixels" { + const webp = try std.fs.cwd().readFileAlloc(std.testing.allocator, "../tests/fixtures/images/alpha.webp", 1 << 20); + defer std.testing.allocator.free(webp); + const decoded = try image.decode(std.testing.allocator, webp, .{}); + defer decoded.deinit(); + try std.testing.expect(decoded.encoded_png == null); + try std.testing.expectEqual(@as(u32, 1), decoded.metadata.has_alpha); + + var output: std.ArrayList(u8) = .empty; + defer output.deinit(std.testing.allocator); + try terminal_image.writeKittyTransmit(output.writer(std.testing.allocator), decoded, 22, false); + try std.testing.expect(std.mem.indexOf(u8, output.items, "f=32") != null); + + const transmitted = try decodeKittyChunks(output.items); + defer std.testing.allocator.free(transmitted); + try std.testing.expectEqualSlices(u8, decoded.pixels, transmitted); +} + +test "kitty tmux passthrough wraps placement and delete frames" { + var output: std.ArrayList(u8) = .empty; + defer output.deinit(std.testing.allocator); + try terminal_image.writeKittyPlacement(output.writer(std.testing.allocator), 5, 6, 1, 2, 3, 4, 0, 0, 3, 4, -7, true); + // The cursor move stays outside the passthrough; the graphics frame is wrapped. + try std.testing.expect(std.mem.startsWith(u8, output.items, "\x1b[3;2H\x1bPtmux;\x1b\x1b_G")); + try std.testing.expect(std.mem.endsWith(u8, output.items, "\x1b\x1b\\\x1b\\")); + try std.testing.expect(std.mem.indexOf(u8, output.items, "a=p,i=5,p=6") != null); + + output.clearRetainingCapacity(); + try terminal_image.writeKittyDelete(output.writer(std.testing.allocator), 5, 6, true, true); + try std.testing.expect(std.mem.startsWith(u8, output.items, "\x1bPtmux;\x1b\x1b_G")); + try std.testing.expect(std.mem.endsWith(u8, output.items, "\x1b\x1b\\\x1b\\")); + try std.testing.expect(std.mem.indexOf(u8, output.items, "a=d,d=I,i=5,p=6") != null); + + output.clearRetainingCapacity(); + try terminal_image.writeKittyDelete(output.writer(std.testing.allocator), 9, null, false, true); + try std.testing.expect(std.mem.indexOf(u8, output.items, "a=d,d=i,i=9") != null); + try std.testing.expect(std.mem.indexOf(u8, output.items, "p=") == null); +} + +test "sixel tmux passthrough wraps the framed payload" { + var output: std.ArrayList(u8) = .empty; + defer output.deinit(std.testing.allocator); + try terminal_image.writeSixelFramedPayload(output.writer(std.testing.allocator), "0;1;0qPAYLOAD", true); + try std.testing.expectEqualStrings("\x1bPtmux;\x1b\x1bP0;1;0qPAYLOAD\x1b\x1b\\\x1b\\", output.items); + + output.clearRetainingCapacity(); + try terminal_image.writeSixelFramedPayload(output.writer(std.testing.allocator), "0;1;0qPAYLOAD", false); + try std.testing.expectEqualStrings("\x1bP0;1;0qPAYLOAD\x1b\\", output.items); +} diff --git a/packages/core/src/zig/tests/terminal_test.zig b/packages/core/src/zig/tests/terminal_test.zig index c5375a63e5..36a9acac9f 100644 --- a/packages/core/src/zig/tests/terminal_test.zig +++ b/packages/core/src/zig/tests/terminal_test.zig @@ -51,7 +51,7 @@ test "parseXtversion - with prefix data" { test "parseXtversion - full kitty response" { var term = Terminal.init(.{}); - const response = "\x1b[?1016;2$y\x1b[?2027;0$y\x1b[?2031;2$y\x1b[?1004;1$y\x1b[?2026;2$y\x1b[1;2R\x1b[1;3R\x1bP>|kitty(0.40.1)\x1b\\\x1b[?0u\x1b_Gi=1;EINVAL:Zero width/height not allowed\x1b\\\x1b[?62;c"; + const response = "\x1b[?1016;2$y\x1b[?2027;0$y\x1b[?2031;2$y\x1b[?1004;1$y\x1b[?2026;2$y\x1b[1;2R\x1b[1;3R\x1bP>|kitty(0.40.1)\x1b\\\x1b[?0u\x1b_Gi=31337;OK\x1b\\\x1b[?62;c"; term.processCapabilityResponse(response); try testing.expectEqualStrings("kitty", term.getTerminalName()); @@ -62,6 +62,250 @@ test "parseXtversion - full kitty response" { try testing.expect(term.caps.osc52); } +test "graphics identity - exact direct Kitty enables Kitty graphics only" { + var kitty = Terminal.init(.{}); + kitty.processCapabilityResponse("\x1bP>|kitty(0.46.2)\x1b\\"); + try testing.expect(kitty.caps.kitty_graphics); + try testing.expect(!kitty.caps.sixel); + + var substring = Terminal.init(.{}); + substring.processCapabilityResponse("\x1bP>|notkitty(1.0)\x1b\\"); + try testing.expect(!substring.caps.kitty_graphics); + try testing.expect(!substring.caps.sixel); + + var uppercase = Terminal.init(.{}); + uppercase.processCapabilityResponse("\x1bP>|KITTY(0.47.2)\x1b\\"); + try testing.expect(uppercase.caps.kitty_graphics); +} + +test "graphics identity - exact direct Ghostty enables Kitty graphics only" { + var term = Terminal.init(.{}); + term.processCapabilityResponse("\x1bP>|ghostty 1.3.1\x1b\\"); + try testing.expect(term.caps.kitty_graphics); + try testing.expect(!term.caps.sixel); + + var substring = Terminal.init(.{}); + substring.processCapabilityResponse("\x1bP>|ghostty-wrapper 1.3.1\x1b\\"); + try testing.expect(!substring.caps.kitty_graphics); +} + +test "graphics identity - foot enables Sixel from version 1.2" { + var supported = Terminal.init(.{}); + supported.processCapabilityResponse("\x1bP>|foot(1.2.0)\x1b\\"); + try testing.expect(!supported.caps.kitty_graphics); + try testing.expect(supported.caps.sixel); + + var old = Terminal.init(.{}); + old.processCapabilityResponse("\x1bP>|foot(1.1.0)\x1b\\"); + try testing.expect(!old.caps.sixel); + + var unknown_version = Terminal.init(.{}); + unknown_version.processCapabilityResponse("\x1bP>|foot\x1b\\"); + try testing.expect(!unknown_version.caps.sixel); + + var later_minor = Terminal.init(.{}); + later_minor.processCapabilityResponse("\x1bP>|foot(1.10.0)\x1b\\"); + try testing.expect(later_minor.caps.sixel); + + var patch_release = Terminal.init(.{}); + patch_release.processCapabilityResponse("\x1bP>|foot(1.2.3)\x1b\\"); + try testing.expect(patch_release.caps.sixel); + + var malformed = Terminal.init(.{}); + malformed.processCapabilityResponse("\x1bP>|foot(next)\x1b\\"); + try testing.expect(!malformed.caps.sixel); + + var incomplete = Terminal.init(.{}); + incomplete.processCapabilityResponse("\x1bP>|foot(1.2)\x1b\\"); + try testing.expect(!incomplete.caps.sixel); + + var malformed_patch = Terminal.init(.{}); + malformed_patch.processCapabilityResponse("\x1bP>|foot(1.2.invalid)\x1b\\"); + try testing.expect(!malformed_patch.caps.sixel); + + var prerelease = Terminal.init(.{}); + prerelease.processCapabilityResponse("\x1bP>|foot(1.2.0-rc1)\x1b\\"); + try testing.expect(!prerelease.caps.sixel); + + var git_build = Terminal.init(.{}); + git_build.processCapabilityResponse("\x1bP>|foot(1.2.0-36-g7db8e06f)\x1b\\"); + try testing.expect(git_build.caps.sixel); +} + +test "graphics identity - WezTerm enables Sixel from documented build" { + var supported = Terminal.init(.{}); + supported.processCapabilityResponse("\x1bP>|WezTerm 20200620-160318-e00b076c\x1b\\"); + try testing.expect(supported.caps.sixel); + try testing.expect(!supported.caps.kitty_graphics); + + var old = Terminal.init(.{}); + old.processCapabilityResponse("\x1bP>|WezTerm 20200619-000000-old\x1b\\"); + try testing.expect(!old.caps.sixel); + + var malformed = Terminal.init(.{}); + malformed.processCapabilityResponse("\x1bP>|WezTerm nightly\x1b\\"); + try testing.expect(!malformed.caps.sixel); + + var missing_separator = Terminal.init(.{}); + missing_separator.processCapabilityResponse("\x1bP>|WezTerm 20200620invalid\x1b\\"); + try testing.expect(!missing_separator.caps.sixel); + + var invalid_date = Terminal.init(.{}); + invalid_date.processCapabilityResponse("\x1bP>|WezTerm 20201340-160318-abcdef12\x1b\\"); + try testing.expect(!invalid_date.caps.sixel); + + var invalid_time = Terminal.init(.{}); + invalid_time.processCapabilityResponse("\x1bP>|WezTerm 20200620-256199-abcdef12\x1b\\"); + try testing.expect(!invalid_time.caps.sixel); + + var invalid_hash = Terminal.init(.{}); + invalid_hash.processCapabilityResponse("\x1bP>|WezTerm 20200620-160318-invalid!\x1b\\"); + try testing.expect(!invalid_hash.caps.sixel); + + var extra_field = Terminal.init(.{}); + extra_field.processCapabilityResponse("\x1bP>|WezTerm 20200620-160318-abcdef12-extra\x1b\\"); + try testing.expect(extra_field.caps.sixel); + + var empty_extra = Terminal.init(.{}); + empty_extra.processCapabilityResponse("\x1bP>|WezTerm 20200620-160318-abcdef12-\x1b\\"); + try testing.expect(!empty_extra.caps.sixel); + + var dot_decorated = Terminal.init(.{}); + dot_decorated.processCapabilityResponse("\x1bP>|WezTerm 20200620.160318.abcdef12.package\x1b\\"); + try testing.expect(dot_decorated.caps.sixel); + + var underscore_decorated = Terminal.init(.{}); + underscore_decorated.processCapabilityResponse("\x1bP>|WezTerm 20200620_160318_abcdef12_package\x1b\\"); + try testing.expect(underscore_decorated.caps.sixel); +} + +test "graphics identity - multiplexers do not imply outer graphics" { + var tmux = Terminal.init(.{}); + tmux.processCapabilityResponse("\x1bP>|tmux 3.5a\x1b\\"); + try testing.expect(!tmux.caps.kitty_graphics); + try testing.expect(!tmux.caps.sixel); + + var zellij = Terminal.init(.{}); + zellij.processCapabilityResponse("\x1bP>|Zellij 0.41.2\x1b\\"); + try testing.expect(!zellij.caps.kitty_graphics); + try testing.expect(!zellij.caps.sixel); +} + +test "graphics identity - query response upgrades an unknown terminal" { + var term = Terminal.init(.{}); + term.processCapabilityResponse("\x1bP>|unknown 1.0\x1b\\"); + try testing.expect(!term.caps.kitty_graphics); + term.processCapabilityResponse("\x1b_Gi=31337;OK\x1b\\"); + try testing.expect(term.caps.kitty_graphics); + + var old_foot = Terminal.init(.{}); + old_foot.processCapabilityResponse("\x1bP>|foot(1.1.0)\x1b\\"); + try testing.expect(!old_foot.caps.sixel); + old_foot.processCapabilityResponse("\x1b[?62;1;2;4;6c"); + try testing.expect(old_foot.caps.sixel); + + term.processCapabilityResponse("\x1bP>|another-unknown 2.0\x1b\\"); + try testing.expect(term.caps.kitty_graphics); +} + +test "graphics identity - a new identity replaces only identity-derived capabilities" { + var term = Terminal.init(.{}); + term.processCapabilityResponse("\x1bP>|kitty(0.47.2)\x1b\\"); + try testing.expect(term.caps.kitty_graphics); + term.processCapabilityResponse("\x1bP>|unknown 1.0\x1b\\"); + try testing.expect(!term.caps.kitty_graphics); +} + +test "graphics identity - malformed XTVERSION cannot reuse environment version" { + var env = std.process.EnvMap.init(testing.allocator); + defer env.deinit(); + try env.put("TERM_PROGRAM", "foot"); + try env.put("TERM_PROGRAM_VERSION", "1.20.2"); + var term = Terminal.init(.{ .env_map = &env }); + term.processCapabilityResponse("\x1bP>|foot(\x1b\\"); + try testing.expectEqualStrings("", term.getTerminalVersion()); + try testing.expect(!term.caps.sixel); +} + +test "graphics identity - environment name alone is not authoritative" { + var env = std.process.EnvMap.init(testing.allocator); + defer env.deinit(); + try env.put("TERM_PROGRAM", "kitty"); + try env.put("TERM_PROGRAM_VERSION", "0.47.2"); + const term = Terminal.init(.{ .env_map = &env }); + try testing.expect(!term.term_info.from_xtversion); + try testing.expect(!term.caps.kitty_graphics); + try testing.expect(!term.caps.sixel); +} + +test "graphics identity - explicit graphics disable blocks identity and queries" { + var env = std.process.EnvMap.init(testing.allocator); + defer env.deinit(); + try env.put("OPENTUI_GRAPHICS", "0"); + + var kitty = Terminal.init(.{ .env_map = &env }); + kitty.processCapabilityResponse("\x1bP>|kitty(0.47.2)\x1b\\"); + kitty.processCapabilityResponse("\x1b_Gi=31337;OK\x1b\\"); + try testing.expect(!kitty.caps.kitty_graphics); + + var foot = Terminal.init(.{ .env_map = &env }); + foot.processCapabilityResponse("\x1bP>|foot(1.20.2)\x1b\\"); + foot.processCapabilityResponse("\x1b[?62;1;2;4;6c"); + try testing.expect(!foot.caps.sixel); +} + +test "image protocol override - parses every forced protocol" { + const cases = [_]struct { value: []const u8, expected: Terminal.ImageProtocol }{ + .{ .value = "auto", .expected = .auto }, + .{ .value = "kitty", .expected = .kitty }, + .{ .value = "sixel", .expected = .sixel }, + .{ .value = "blocks", .expected = .blocks }, + }; + for (cases) |case| { + var env = std.process.EnvMap.init(testing.allocator); + defer env.deinit(); + try env.put("OPENTUI_IMAGE_PROTOCOL", case.value); + const term = Terminal.init(.{ .env_map = &env }); + try testing.expectEqual(case.expected, term.image_protocol); + } +} + +test "image protocol override - ignores invalid value" { + var env = std.process.EnvMap.init(testing.allocator); + defer env.deinit(); + try env.put("OPENTUI_IMAGE_PROTOCOL", "invalid"); + const term = Terminal.init(.{ .env_map = &env }); + try testing.expectEqual(Terminal.ImageProtocol.auto, term.image_protocol); +} + +test "graphics detection - kitty response matches exact id in the same APC" { + var term = Terminal.init(.{}); + term.processCapabilityResponse("\x1b_Gi=31337;OK\x1b\\"); + try testing.expect(term.caps.kitty_graphics); + + var wrong = Terminal.init(.{}); + wrong.processCapabilityResponse("\x1b_Gi=313370;OK\x1b\\"); + try testing.expect(!wrong.caps.kitty_graphics); + + var split = Terminal.init(.{}); + split.processCapabilityResponse("\x1b_Gi=1;OK\x1b\\unrelated i=31337"); + try testing.expect(!split.caps.kitty_graphics); +} + +test "graphics detection - parses exact sixel DA1 parameter" { + var direct = Terminal.init(.{}); + direct.processCapabilityResponse("\x1b[?1;2;4c"); + try testing.expect(direct.caps.sixel); + + var later = Terminal.init(.{}); + later.processCapabilityResponse("\x1b[?62;22c\x1b[?62;1;2;4;6c"); + try testing.expect(later.caps.sixel); + + var absent = Terminal.init(.{}); + absent.processCapabilityResponse("\x1b[?62;14;40c"); + try testing.expect(!absent.caps.sixel); +} + test "parseXtversion - full ghostty response" { var term = Terminal.init(.{}); const response = "\x1b[?1016;1$y\x1b[?2027;1$y\x1b[?2031;2$y\x1b[?1004;1$y\x1b[?2004;2$y\x1b[?2026;2$y\x1b[1;1R\x1b[1;1R\x1bP>|ghostty 1.1.3\x1b\\\x1b[?0u\x1b_Gi=1;OK\x1b\\\x1b[?62;22c"; @@ -504,12 +748,16 @@ test "queryTerminalSend - sends unwrapped queries when not in tmux" { try testing.expect(std.mem.indexOf(u8, output, "\x1b[?2027$p") != null); try testing.expect(std.mem.indexOf(u8, output, "\x1b[?u") != null); try testing.expect(std.mem.indexOf(u8, output, "\x1bP+q4d73\x1b\\") != null); + try testing.expect(std.mem.indexOf(u8, output, ansi.ANSI.kittyGraphicsQuery) != null); + try testing.expect(std.mem.indexOf(u8, output, ansi.ANSI.primaryDeviceAttrs) != null); // Should NOT contain tmux DCS wrapper try testing.expect(std.mem.indexOf(u8, output, "\x1bPtmux;") == null); // Should mark capability queries as pending try testing.expect(term.capability_queries_pending); + try testing.expect(term.graphics_query_pending); + try testing.expect(term.sixel_query_pending); } test "queryTerminalSend - sends DCS wrapped queries when in tmux" { @@ -598,7 +846,7 @@ test "sendPendingQueries - sends wrapped queries after tmux detected via xtversi try testing.expect(!term.graphics_query_pending); } -test "sendPendingQueries - sends unwrapped graphics query for non-tmux terminal" { +test "sendPendingQueries - clears already-sent direct graphics probes after non-tmux xtversion" { var term = Terminal.init(.{}); term.multiplexer = .none; term.capability_queries_pending = true; @@ -614,24 +862,22 @@ test "sendPendingQueries - sends unwrapped graphics query for non-tmux terminal" const did_send = try term.sendPendingQueries(&writer); - try testing.expect(did_send); + try testing.expect(!did_send); const output = writer.getWritten(); // Should NOT send DCS wrapped capability queries (not tmux) try testing.expect(std.mem.indexOf(u8, output, "\x1bPtmux;") == null); - // Should send unwrapped graphics query - try testing.expect(std.mem.indexOf(u8, output, "\x1b_Gi=31337") != null); + // Initial startup already sent the direct graphics query. + try testing.expect(std.mem.indexOf(u8, output, "\x1b_Gi=31337") == null); // Should clear pending flags try testing.expect(!term.capability_queries_pending); try testing.expect(!term.graphics_query_pending); } -test "sendPendingQueries - sends unwrapped graphics query even without xtversion response" { - // This covers terminals that support kitty graphics but don't respond to xtversion. - // The graphics query should still be sent (unwrapped) so we can detect graphics support. +test "sendPendingQueries - waits for xtversion before any passthrough retry" { var term = Terminal.init(.{}); term.multiplexer = .none; term.term_info.from_xtversion = false; @@ -643,20 +889,19 @@ test "sendPendingQueries - sends unwrapped graphics query even without xtversion const did_send = try term.sendPendingQueries(&writer); - try testing.expect(did_send); + try testing.expect(!did_send); const output = writer.getWritten(); - // Should send unwrapped graphics query (not tmux, so no DCS wrapper) - try testing.expect(std.mem.indexOf(u8, output, "\x1b_Gi=31337") != null); + // Initial startup already sent the direct graphics query. + try testing.expect(std.mem.indexOf(u8, output, "\x1b_Gi=31337") == null); try testing.expect(std.mem.indexOf(u8, output, "\x1bPtmux;") == null); - // Should clear graphics pending flag - try testing.expect(!term.graphics_query_pending); + try testing.expect(term.graphics_query_pending); // Capability queries should NOT be re-sent (no xtversion means we don't know if tmux, // but they were already sent unwrapped in queryTerminalSend) - try testing.expect(!term.capability_queries_pending); + try testing.expect(term.capability_queries_pending); } test "sendPendingQueries - skips graphics when skip_graphics_query is set" { diff --git a/packages/core/src/zig/vendor/README.md b/packages/core/src/zig/vendor/README.md new file mode 100644 index 0000000000..3fc78f38c1 --- /dev/null +++ b/packages/core/src/zig/vendor/README.md @@ -0,0 +1,19 @@ +# Image vendors + +Run the updater from `packages/core`: + +```sh +bun run vendor:update:images +``` + +The script requires `curl`, `git`, `tar`, and either `sha256sum` or `shasum`. It downloads the exact pins declared at the top of `update.sh`, verifies upstream SHA-256 hashes, applies the committed stb patches, and copies the libwebp subset listed in `libwebp/FILES`. + +To update a dependency: + +1. Change its pin and matching hashes in `update.sh`; stb updates must also refresh the patch and `*_PATCHED_SHA256`. +2. Update the matching vendor README. +3. Refresh an stb patch if it no longer applies, or update `libwebp/FILES` if the required source closure changed. +4. Run `bun run vendor:update:images` and review the diff. +5. Run `bun run test:native` and `bun run build:native`. + +Do not edit vendored stb headers directly. Keep OpenTUI changes in `stb/patches` so a clean upstream file plus the patches always reproduces the checked-in result. diff --git a/packages/core/src/zig/vendor/libwebp/AUTHORS b/packages/core/src/zig/vendor/libwebp/AUTHORS new file mode 100644 index 0000000000..6fe9552cd0 --- /dev/null +++ b/packages/core/src/zig/vendor/libwebp/AUTHORS @@ -0,0 +1,72 @@ +Contributors: +- Aidan O'Loan (aidanol at gmail dot com) +- Alan Browning (browning at google dot com) +- Alexandru Ardelean (ardeleanalex at gmail dot com) +- Anuraag Agrawal (anuraaga at gmail dot com) +- Arthur Eubanks (aeubanks at google dot com) +- Brian Ledger (brianpl at google dot com) +- Charles Munger (clm at google dot com) +- Cheng Yi (cyi at google dot com) +- Christian Duvivier (cduvivier at google dot com) +- Christopher Degawa (ccom at randomderp dot com) +- Clement Courbet (courbet at google dot com) +- devtools-clrobot at google dot com (devtools-clrobot@google dot com) +- Djordje Pesut (djordje dot pesut at imgtec dot com) +- Frank (1433351828 at qq dot com) +- Frank Barchard (fbarchard at google dot com) +- Henner Zeller (hzeller at google dot com) +- Hui Su (huisu at google dot com) +- H. Vetinari (h dot vetinari at gmx dot com) +- Ilya Kurdyukov (jpegqs at gmail dot com) +- Ingvar Stepanyan (rreverser at google dot com) +- Istvan Stefan (Istvan dot Stefan at arm dot com) +- James Zern (jzern at google dot com) +- Jan Engelhardt (jengelh at medozas dot de) +- Jehan (jehan at girinstud dot io) +- Jeremy Maitin-Shepard (jbms at google dot com) +- Johann Koenig (johann dot koenig at duck dot com) +- Jonathan Grant (jgrantinfotech at gmail dot com) +- Jonliu1993 (13720414433 at 163 dot com) +- Jovan Zelincevic (jovan dot zelincevic at imgtec dot com) +- Jyrki Alakuijala (jyrki at google dot com) +- Konstantin Ivlev (tomskside at gmail dot com) +- Lode Vandevenne (lode at google dot com) +- Lou Quillio (louquillio at google dot com) +- Mans Rullgard (mans at mansr dot com) +- Marcin Kowalczyk (qrczak at google dot com) +- Martin Olsson (mnemo at minimum dot se) +- Maryla Ustarroz-Calonge (maryla at google dot com) +- Masahiro Hanada (hanada at atmark-techno dot com) +- Mikołaj Zalewski (mikolajz at google dot com) +- Mislav Bradac (mislavm at google dot com) +- natewood (natewood at fb dot com) +- Nico Weber (thakis at chromium dot org) +- Noel Chromium (noel at chromium dot org) +- Nozomi Isozaki (nontan at pixiv dot co dot jp) +- Oliver Wolff (oliver dot wolff at qt dot io) +- Owen Rodley (orodley at google dot com) +- Ozkan Sezer (sezeroz at gmail dot com) +- Parag Salasakar (img dot mips1 at gmail dot com) +- Pascal Massimino (pascal dot massimino at gmail dot com) +- Paweł Hajdan, Jr (phajdan dot jr at chromium dot org) +- Pierre Joye (pierre dot php at gmail dot com) +- Roberto Alanis (alanisbaez at google dot com) +- Sam Clegg (sbc at chromium dot org) +- Scott Hancher (seh at google dot com) +- Scott LaVarnway (slavarnway at google dot com) +- Scott Talbot (s at chikachow dot org) +- Slobodan Prijic (slobodan dot prijic at imgtec dot com) +- Somnath Banerjee (somnath dot banerjee at gmail dot com) +- Sriraman Tallam (tmsriram at google dot com) +- Tamar Levy (tamar dot levy at intel dot com) +- Thiago Perrotta (tperrotta at google dot com) +- Timothy Gu (timothygu99 at gmail dot com) +- Urvang Joshi (urvang at google dot com) +- Vikas Arora (vikasa at google dot com) +- Vincent Rabaud (vrabaud at google dot com) +- Vlad Tsyrklevich (vtsyrklevich at chromium dot org) +- Wan-Teh Chang (wtc at google dot com) +- wrv (wrv at utexas dot edu) +- Yang Zhang (yang dot zhang at arm dot com) +- Yannis Guyon (yguyon at google dot com) +- Zhi An Ng (zhin at chromium dot org) diff --git a/packages/core/src/zig/vendor/libwebp/COPYING b/packages/core/src/zig/vendor/libwebp/COPYING new file mode 100644 index 0000000000..7a6f99547d --- /dev/null +++ b/packages/core/src/zig/vendor/libwebp/COPYING @@ -0,0 +1,30 @@ +Copyright (c) 2010, Google Inc. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions 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. + + * Neither 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. + +THIS 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. + diff --git a/packages/core/src/zig/vendor/libwebp/FILES b/packages/core/src/zig/vendor/libwebp/FILES new file mode 100644 index 0000000000..bceab5e8c3 --- /dev/null +++ b/packages/core/src/zig/vendor/libwebp/FILES @@ -0,0 +1,80 @@ +src/dec/alpha_dec.c +src/dec/alphai_dec.h +src/dec/buffer_dec.c +src/dec/common_dec.h +src/dec/frame_dec.c +src/dec/idec_dec.c +src/dec/io_dec.c +src/dec/quant_dec.c +src/dec/tree_dec.c +src/dec/vp8_dec.c +src/dec/vp8_dec.h +src/dec/vp8i_dec.h +src/dec/vp8l_dec.c +src/dec/vp8li_dec.h +src/dec/webp_dec.c +src/dec/webpi_dec.h +src/dsp/alpha_processing.c +src/dsp/alpha_processing_neon.c +src/dsp/alpha_processing_sse2.c +src/dsp/alpha_processing_sse41.c +src/dsp/common_sse2.h +src/dsp/common_sse41.h +src/dsp/cpu.c +src/dsp/cpu.h +src/dsp/dec.c +src/dsp/dec_clip_tables.c +src/dsp/dec_neon.c +src/dsp/dec_sse2.c +src/dsp/dec_sse41.c +src/dsp/dsp.h +src/dsp/filters.c +src/dsp/filters_neon.c +src/dsp/filters_sse2.c +src/dsp/lossless.c +src/dsp/lossless.h +src/dsp/lossless_avx2.c +src/dsp/lossless_common.h +src/dsp/lossless_neon.c +src/dsp/lossless_sse2.c +src/dsp/lossless_sse41.c +src/dsp/neon.h +src/dsp/rescaler.c +src/dsp/rescaler_neon.c +src/dsp/rescaler_sse2.c +src/dsp/upsampling.c +src/dsp/upsampling_neon.c +src/dsp/upsampling_sse2.c +src/dsp/upsampling_sse41.c +src/dsp/yuv.c +src/dsp/yuv.h +src/dsp/yuv_neon.c +src/dsp/yuv_sse2.c +src/dsp/yuv_sse41.c +src/utils/bit_reader_inl_utils.h +src/utils/bit_reader_utils.c +src/utils/bit_reader_utils.h +src/utils/color_cache_utils.c +src/utils/color_cache_utils.h +src/utils/endian_inl_utils.h +src/utils/filters_utils.c +src/utils/filters_utils.h +src/utils/huffman_utils.c +src/utils/huffman_utils.h +src/utils/palette.c +src/utils/palette.h +src/utils/quant_levels_dec_utils.c +src/utils/quant_levels_dec_utils.h +src/utils/random_utils.c +src/utils/random_utils.h +src/utils/rescaler_utils.c +src/utils/rescaler_utils.h +src/utils/thread_utils.c +src/utils/thread_utils.h +src/utils/utils.c +src/utils/utils.h +src/webp/decode.h +src/webp/encode.h +src/webp/format_constants.h +src/webp/mux_types.h +src/webp/types.h diff --git a/packages/core/src/zig/vendor/libwebp/PATENTS b/packages/core/src/zig/vendor/libwebp/PATENTS new file mode 100644 index 0000000000..caedf607e9 --- /dev/null +++ b/packages/core/src/zig/vendor/libwebp/PATENTS @@ -0,0 +1,23 @@ +Additional IP Rights Grant (Patents) +------------------------------------ + +"These implementations" means the copyrightable works that implement the WebM +codecs distributed by Google as part of the WebM Project. + +Google hereby grants to you a perpetual, worldwide, non-exclusive, no-charge, +royalty-free, irrevocable (except as stated in this section) patent license to +make, have made, use, offer to sell, sell, import, transfer, and otherwise +run, modify and propagate the contents of these implementations of WebM, where +such license applies only to those patent claims, both currently owned by +Google and acquired in the future, licensable by Google that are necessarily +infringed by these implementations of WebM. This grant does not include claims +that would be infringed only as a consequence of further modification of these +implementations. If you or your agent or exclusive licensee institute or order +or agree to the institution of patent litigation or any other patent +enforcement activity against any entity (including a cross-claim or +counterclaim in a lawsuit) alleging that any of these implementations of WebM +or any code incorporated within any of these implementations of WebM +constitute direct or contributory patent infringement, or inducement of +patent infringement, then any patent rights granted to you under this License +for these implementations of WebM shall terminate as of the date such +litigation is filed. diff --git a/packages/core/src/zig/vendor/libwebp/README.md b/packages/core/src/zig/vendor/libwebp/README.md new file mode 100644 index 0000000000..c03f8c2bfa --- /dev/null +++ b/packages/core/src/zig/vendor/libwebp/README.md @@ -0,0 +1,15 @@ +# libwebp + +Pinned to libwebp 1.6.0 from the official release archive. + +Archive SHA-256: +`e4ab7009bf0629fd11982d4c2aa83964cf244cffba7347ecd39019a9e38c4564`. + +Only decoder sources, the supported x64/ARM64 decoder DSP paths, and their +transitive headers and utilities are vendored. `encode.h` and `mux_types.h` +remain because upstream decoder-common sources include them. Encoders, muxers, +demuxers, build-system files, tools, examples, and animation utilities are +excluded. The exact subset is listed in `FILES`. See `COPYING`, `PATENTS`, and +`AUTHORS`. + +Update with `bun run vendor:update:images` from `packages/core`; see `../README.md`. diff --git a/packages/core/src/zig/vendor/libwebp/src/dec/alpha_dec.c b/packages/core/src/zig/vendor/libwebp/src/dec/alpha_dec.c new file mode 100644 index 0000000000..d90bfd2be0 --- /dev/null +++ b/packages/core/src/zig/vendor/libwebp/src/dec/alpha_dec.c @@ -0,0 +1,243 @@ +// Copyright 2011 Google Inc. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the COPYING file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +// ----------------------------------------------------------------------------- +// +// Alpha-plane decompression. +// +// Author: Skal (pascal.massimino@gmail.com) + +#include +#include + +#include "src/dec/alphai_dec.h" +#include "src/dec/vp8_dec.h" +#include "src/dec/vp8i_dec.h" +#include "src/dec/vp8li_dec.h" +#include "src/dec/webpi_dec.h" +#include "src/dsp/dsp.h" +#include "src/utils/quant_levels_dec_utils.h" +#include "src/utils/utils.h" +#include "src/webp/decode.h" +#include "src/webp/format_constants.h" +#include "src/webp/types.h" + +//------------------------------------------------------------------------------ +// ALPHDecoder object. + +// Allocates a new alpha decoder instance. +WEBP_NODISCARD static ALPHDecoder* ALPHNew(void) { + ALPHDecoder* const dec = (ALPHDecoder*)WebPSafeCalloc(1ULL, sizeof(*dec)); + return dec; +} + +// Clears and deallocates an alpha decoder instance. +static void ALPHDelete(ALPHDecoder* const dec) { + if (dec != NULL) { + VP8LDelete(dec->vp8l_dec); + dec->vp8l_dec = NULL; + WebPSafeFree(dec); + } +} + +//------------------------------------------------------------------------------ +// Decoding. + +// Initialize alpha decoding by parsing the alpha header and decoding the image +// header for alpha data stored using lossless compression. +// Returns false in case of error in alpha header (data too short, invalid +// compression method or filter, error in lossless header data etc). +WEBP_NODISCARD static int ALPHInit(ALPHDecoder* const dec, const uint8_t* data, + size_t data_size, const VP8Io* const src_io, + uint8_t* output) { + int ok = 0; + const uint8_t* const alpha_data = data + ALPHA_HEADER_LEN; + const size_t alpha_data_size = data_size - ALPHA_HEADER_LEN; + int rsrv; + VP8Io* const io = &dec->io; + + assert(data != NULL && output != NULL && src_io != NULL); + + VP8FiltersInit(); + dec->output = output; + dec->width = src_io->width; + dec->height = src_io->height; + assert(dec->width > 0 && dec->height > 0); + + if (data_size <= ALPHA_HEADER_LEN) { + return 0; + } + + dec->method = (data[0] >> 0) & 0x03; + dec->filter = (WEBP_FILTER_TYPE)((data[0] >> 2) & 0x03); + dec->pre_processing = (data[0] >> 4) & 0x03; + rsrv = (data[0] >> 6) & 0x03; + if (dec->method < ALPHA_NO_COMPRESSION || + dec->method > ALPHA_LOSSLESS_COMPRESSION || + dec->filter >= WEBP_FILTER_LAST || + dec->pre_processing > ALPHA_PREPROCESSED_LEVELS || + rsrv != 0) { + return 0; + } + + // Copy the necessary parameters from src_io to io + if (!VP8InitIo(io)) { + return 0; + } + WebPInitCustomIo(NULL, io); + io->opaque = dec; + io->width = src_io->width; + io->height = src_io->height; + + io->use_cropping = src_io->use_cropping; + io->crop_left = src_io->crop_left; + io->crop_right = src_io->crop_right; + io->crop_top = src_io->crop_top; + io->crop_bottom = src_io->crop_bottom; + // No need to copy the scaling parameters. + + if (dec->method == ALPHA_NO_COMPRESSION) { + const size_t alpha_decoded_size = dec->width * dec->height; + ok = (alpha_data_size >= alpha_decoded_size); + } else { + assert(dec->method == ALPHA_LOSSLESS_COMPRESSION); + ok = VP8LDecodeAlphaHeader(dec, alpha_data, alpha_data_size); + } + + return ok; +} + +// Decodes, unfilters and dequantizes *at least* 'num_rows' rows of alpha +// starting from row number 'row'. It assumes that rows up to (row - 1) have +// already been decoded. +// Returns false in case of bitstream error. +WEBP_NODISCARD static int ALPHDecode(VP8Decoder* const dec, int row, + int num_rows) { + ALPHDecoder* const alph_dec = dec->alph_dec; + const int width = alph_dec->width; + const int height = alph_dec->io.crop_bottom; + if (alph_dec->method == ALPHA_NO_COMPRESSION) { + int y; + const uint8_t* prev_line = dec->alpha_prev_line; + const uint8_t* deltas = dec->alpha_data + ALPHA_HEADER_LEN + row * width; + uint8_t* dst = dec->alpha_plane + row * width; + assert(deltas <= &dec->alpha_data[dec->alpha_data_size]); + assert(WebPUnfilters[alph_dec->filter] != NULL); + for (y = 0; y < num_rows; ++y) { + WebPUnfilters[alph_dec->filter](prev_line, deltas, dst, width); + prev_line = dst; + dst += width; + deltas += width; + } + dec->alpha_prev_line = prev_line; + } else { // alph_dec->method == ALPHA_LOSSLESS_COMPRESSION + assert(alph_dec->vp8l_dec != NULL); + if (!VP8LDecodeAlphaImageStream(alph_dec, row + num_rows)) { + return 0; + } + } + + if (row + num_rows >= height) { + dec->is_alpha_decoded = 1; + } + return 1; +} + +WEBP_NODISCARD static int AllocateAlphaPlane(VP8Decoder* const dec, + const VP8Io* const io) { + const int stride = io->width; + const int height = io->crop_bottom; + const uint64_t alpha_size = (uint64_t)stride * height; + assert(dec->alpha_plane_mem == NULL); + dec->alpha_plane_mem = + (uint8_t*)WebPSafeMalloc(alpha_size, sizeof(*dec->alpha_plane)); + if (dec->alpha_plane_mem == NULL) { + return VP8SetError(dec, VP8_STATUS_OUT_OF_MEMORY, + "Alpha decoder initialization failed."); + } + dec->alpha_plane = dec->alpha_plane_mem; + dec->alpha_prev_line = NULL; + return 1; +} + +void WebPDeallocateAlphaMemory(VP8Decoder* const dec) { + assert(dec != NULL); + WebPSafeFree(dec->alpha_plane_mem); + dec->alpha_plane_mem = NULL; + dec->alpha_plane = NULL; + ALPHDelete(dec->alph_dec); + dec->alph_dec = NULL; +} + +//------------------------------------------------------------------------------ +// Main entry point. + +WEBP_NODISCARD const uint8_t* VP8DecompressAlphaRows(VP8Decoder* const dec, + const VP8Io* const io, + int row, int num_rows) { + const int width = io->width; + const int height = io->crop_bottom; + + assert(dec != NULL && io != NULL); + + if (row < 0 || num_rows <= 0 || row + num_rows > height) { + return NULL; + } + + if (!dec->is_alpha_decoded) { + if (dec->alph_dec == NULL) { // Initialize decoder. + dec->alph_dec = ALPHNew(); + if (dec->alph_dec == NULL) { + VP8SetError(dec, VP8_STATUS_OUT_OF_MEMORY, + "Alpha decoder initialization failed."); + return NULL; + } + if (!AllocateAlphaPlane(dec, io)) goto Error; + if (!ALPHInit(dec->alph_dec, dec->alpha_data, dec->alpha_data_size, + io, dec->alpha_plane)) { + VP8LDecoder* const vp8l_dec = dec->alph_dec->vp8l_dec; + VP8SetError(dec, + (vp8l_dec == NULL) ? VP8_STATUS_OUT_OF_MEMORY + : vp8l_dec->status, + "Alpha decoder initialization failed."); + goto Error; + } + // if we allowed use of alpha dithering, check whether it's needed at all + if (dec->alph_dec->pre_processing != ALPHA_PREPROCESSED_LEVELS) { + dec->alpha_dithering = 0; // disable dithering + } else { + num_rows = height - row; // decode everything in one pass + } + } + + assert(dec->alph_dec != NULL); + assert(row + num_rows <= height); + if (!ALPHDecode(dec, row, num_rows)) goto Error; + + if (dec->is_alpha_decoded) { // finished? + ALPHDelete(dec->alph_dec); + dec->alph_dec = NULL; + if (dec->alpha_dithering > 0) { + uint8_t* const alpha = dec->alpha_plane + io->crop_top * width + + io->crop_left; + if (!WebPDequantizeLevels(alpha, + io->crop_right - io->crop_left, + io->crop_bottom - io->crop_top, + width, dec->alpha_dithering)) { + goto Error; + } + } + } + } + + // Return a pointer to the current decoded row. + return dec->alpha_plane + row * width; + + Error: + WebPDeallocateAlphaMemory(dec); + return NULL; +} diff --git a/packages/core/src/zig/vendor/libwebp/src/dec/alphai_dec.h b/packages/core/src/zig/vendor/libwebp/src/dec/alphai_dec.h new file mode 100644 index 0000000000..49150318fb --- /dev/null +++ b/packages/core/src/zig/vendor/libwebp/src/dec/alphai_dec.h @@ -0,0 +1,57 @@ +// Copyright 2013 Google Inc. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the COPYING file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +// ----------------------------------------------------------------------------- +// +// Alpha decoder: internal header. +// +// Author: Urvang (urvang@google.com) + +#ifndef WEBP_DEC_ALPHAI_DEC_H_ +#define WEBP_DEC_ALPHAI_DEC_H_ + +#include "src/dec/vp8_dec.h" +#include "src/webp/types.h" +#include "src/dec/webpi_dec.h" +#include "src/dsp/dsp.h" +#include "src/utils/filters_utils.h" + +#ifdef __cplusplus +extern "C" { +#endif + +struct VP8LDecoder; // Defined in dec/vp8li.h. + +typedef struct ALPHDecoder ALPHDecoder; +struct ALPHDecoder { + int width; + int height; + int method; + WEBP_FILTER_TYPE filter; + int pre_processing; + struct VP8LDecoder* vp8l_dec; + VP8Io io; + int use_8b_decode; // Although alpha channel requires only 1 byte per + // pixel, sometimes VP8LDecoder may need to allocate + // 4 bytes per pixel internally during decode. + uint8_t* output; + const uint8_t* prev_line; // last output row (or NULL) +}; + +//------------------------------------------------------------------------------ +// internal functions. Not public. + +// Deallocate memory associated to dec->alpha_plane decoding +void WebPDeallocateAlphaMemory(VP8Decoder* const dec); + +//------------------------------------------------------------------------------ + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif // WEBP_DEC_ALPHAI_DEC_H_ diff --git a/packages/core/src/zig/vendor/libwebp/src/dec/buffer_dec.c b/packages/core/src/zig/vendor/libwebp/src/dec/buffer_dec.c new file mode 100644 index 0000000000..290f7ab3b5 --- /dev/null +++ b/packages/core/src/zig/vendor/libwebp/src/dec/buffer_dec.c @@ -0,0 +1,314 @@ +// Copyright 2011 Google Inc. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the COPYING file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +// ----------------------------------------------------------------------------- +// +// Everything about WebPDecBuffer +// +// Author: Skal (pascal.massimino@gmail.com) + +#include +#include +#include + +#include "src/dec/vp8i_dec.h" +#include "src/dec/webpi_dec.h" +#include "src/utils/rescaler_utils.h" +#include "src/utils/utils.h" +#include "src/webp/decode.h" +#include "src/webp/types.h" + +//------------------------------------------------------------------------------ +// WebPDecBuffer + +// Number of bytes per pixel for the different color-spaces. +static const uint8_t kModeBpp[MODE_LAST] = { + 3, 4, 3, 4, 4, 2, 2, + 4, 4, 4, 2, // pre-multiplied modes + 1, 1 }; + +// Convert to an integer to handle both the unsigned/signed enum cases +// without the need for casting to remove type limit warnings. +int IsValidColorspace(int webp_csp_mode) { + return (webp_csp_mode >= MODE_RGB && webp_csp_mode < MODE_LAST); +} + +// strictly speaking, the very last (or first, if flipped) row +// doesn't require padding. +#define MIN_BUFFER_SIZE(WIDTH, HEIGHT, STRIDE) \ + ((uint64_t)(STRIDE) * ((HEIGHT) - 1) + (WIDTH)) + +static VP8StatusCode CheckDecBuffer(const WebPDecBuffer* const buffer) { + int ok = 1; + const WEBP_CSP_MODE mode = buffer->colorspace; + const int width = buffer->width; + const int height = buffer->height; + if (!IsValidColorspace(mode)) { + ok = 0; + } else if (!WebPIsRGBMode(mode)) { // YUV checks + const WebPYUVABuffer* const buf = &buffer->u.YUVA; + const int uv_width = (width + 1) / 2; + const int uv_height = (height + 1) / 2; + const int y_stride = abs(buf->y_stride); + const int u_stride = abs(buf->u_stride); + const int v_stride = abs(buf->v_stride); + const int a_stride = abs(buf->a_stride); + const uint64_t y_size = MIN_BUFFER_SIZE(width, height, y_stride); + const uint64_t u_size = MIN_BUFFER_SIZE(uv_width, uv_height, u_stride); + const uint64_t v_size = MIN_BUFFER_SIZE(uv_width, uv_height, v_stride); + const uint64_t a_size = MIN_BUFFER_SIZE(width, height, a_stride); + ok &= (y_size <= buf->y_size); + ok &= (u_size <= buf->u_size); + ok &= (v_size <= buf->v_size); + ok &= (y_stride >= width); + ok &= (u_stride >= uv_width); + ok &= (v_stride >= uv_width); + ok &= (buf->y != NULL); + ok &= (buf->u != NULL); + ok &= (buf->v != NULL); + if (mode == MODE_YUVA) { + ok &= (a_stride >= width); + ok &= (a_size <= buf->a_size); + ok &= (buf->a != NULL); + } + } else { // RGB checks + const WebPRGBABuffer* const buf = &buffer->u.RGBA; + const int stride = abs(buf->stride); + const uint64_t size = + MIN_BUFFER_SIZE((uint64_t)width * kModeBpp[mode], height, stride); + ok &= (size <= buf->size); + ok &= (stride >= width * kModeBpp[mode]); + ok &= (buf->rgba != NULL); + } + return ok ? VP8_STATUS_OK : VP8_STATUS_INVALID_PARAM; +} +#undef MIN_BUFFER_SIZE + +static VP8StatusCode AllocateBuffer(WebPDecBuffer* const buffer) { + const int w = buffer->width; + const int h = buffer->height; + const WEBP_CSP_MODE mode = buffer->colorspace; + + if (w <= 0 || h <= 0 || !IsValidColorspace(mode)) { + return VP8_STATUS_INVALID_PARAM; + } + + if (buffer->is_external_memory <= 0 && buffer->private_memory == NULL) { + uint8_t* output; + int uv_stride = 0, a_stride = 0; + uint64_t uv_size = 0, a_size = 0, total_size; + // We need memory and it hasn't been allocated yet. + // => initialize output buffer, now that dimensions are known. + int stride; + uint64_t size; + + if ((uint64_t)w * kModeBpp[mode] >= (1ull << 31)) { + return VP8_STATUS_INVALID_PARAM; + } + stride = w * kModeBpp[mode]; + size = (uint64_t)stride * h; + if (!WebPIsRGBMode(mode)) { + uv_stride = (w + 1) / 2; + uv_size = (uint64_t)uv_stride * ((h + 1) / 2); + if (mode == MODE_YUVA) { + a_stride = w; + a_size = (uint64_t)a_stride * h; + } + } + total_size = size + 2 * uv_size + a_size; + + output = (uint8_t*)WebPSafeMalloc(total_size, sizeof(*output)); + if (output == NULL) { + return VP8_STATUS_OUT_OF_MEMORY; + } + buffer->private_memory = output; + + if (!WebPIsRGBMode(mode)) { // YUVA initialization + WebPYUVABuffer* const buf = &buffer->u.YUVA; + buf->y = output; + buf->y_stride = stride; + buf->y_size = (size_t)size; + buf->u = output + size; + buf->u_stride = uv_stride; + buf->u_size = (size_t)uv_size; + buf->v = output + size + uv_size; + buf->v_stride = uv_stride; + buf->v_size = (size_t)uv_size; + if (mode == MODE_YUVA) { + buf->a = output + size + 2 * uv_size; + } + buf->a_size = (size_t)a_size; + buf->a_stride = a_stride; + } else { // RGBA initialization + WebPRGBABuffer* const buf = &buffer->u.RGBA; + buf->rgba = output; + buf->stride = stride; + buf->size = (size_t)size; + } + } + return CheckDecBuffer(buffer); +} + +VP8StatusCode WebPFlipBuffer(WebPDecBuffer* const buffer) { + if (buffer == NULL) { + return VP8_STATUS_INVALID_PARAM; + } + if (WebPIsRGBMode(buffer->colorspace)) { + WebPRGBABuffer* const buf = &buffer->u.RGBA; + buf->rgba += (int64_t)(buffer->height - 1) * buf->stride; + buf->stride = -buf->stride; + } else { + WebPYUVABuffer* const buf = &buffer->u.YUVA; + const int64_t H = buffer->height; + buf->y += (H - 1) * buf->y_stride; + buf->y_stride = -buf->y_stride; + buf->u += ((H - 1) >> 1) * buf->u_stride; + buf->u_stride = -buf->u_stride; + buf->v += ((H - 1) >> 1) * buf->v_stride; + buf->v_stride = -buf->v_stride; + if (buf->a != NULL) { + buf->a += (H - 1) * buf->a_stride; + buf->a_stride = -buf->a_stride; + } + } + return VP8_STATUS_OK; +} + +VP8StatusCode WebPAllocateDecBuffer(int width, int height, + const WebPDecoderOptions* const options, + WebPDecBuffer* const buffer) { + VP8StatusCode status; + if (buffer == NULL || width <= 0 || height <= 0) { + return VP8_STATUS_INVALID_PARAM; + } + if (options != NULL) { // First, apply options if there is any. + if (options->use_cropping) { + const int cw = options->crop_width; + const int ch = options->crop_height; + const int x = options->crop_left & ~1; + const int y = options->crop_top & ~1; + if (!WebPCheckCropDimensions(width, height, x, y, cw, ch)) { + return VP8_STATUS_INVALID_PARAM; // out of frame boundary. + } + width = cw; + height = ch; + } + + if (options->use_scaling) { +#if !defined(WEBP_REDUCE_SIZE) + int scaled_width = options->scaled_width; + int scaled_height = options->scaled_height; + if (!WebPRescalerGetScaledDimensions( + width, height, &scaled_width, &scaled_height)) { + return VP8_STATUS_INVALID_PARAM; + } + width = scaled_width; + height = scaled_height; +#else + return VP8_STATUS_INVALID_PARAM; // rescaling not supported +#endif + } + } + buffer->width = width; + buffer->height = height; + + // Then, allocate buffer for real. + status = AllocateBuffer(buffer); + if (status != VP8_STATUS_OK) return status; + + // Use the stride trick if vertical flip is needed. + if (options != NULL && options->flip) { + status = WebPFlipBuffer(buffer); + } + return status; +} + +//------------------------------------------------------------------------------ +// constructors / destructors + +int WebPInitDecBufferInternal(WebPDecBuffer* buffer, int version) { + if (WEBP_ABI_IS_INCOMPATIBLE(version, WEBP_DECODER_ABI_VERSION)) { + return 0; // version mismatch + } + if (buffer == NULL) return 0; + memset(buffer, 0, sizeof(*buffer)); + return 1; +} + +void WebPFreeDecBuffer(WebPDecBuffer* buffer) { + if (buffer != NULL) { + if (buffer->is_external_memory <= 0) { + WebPSafeFree(buffer->private_memory); + } + buffer->private_memory = NULL; + } +} + +void WebPCopyDecBuffer(const WebPDecBuffer* const src, + WebPDecBuffer* const dst) { + if (src != NULL && dst != NULL) { + *dst = *src; + if (src->private_memory != NULL) { + dst->is_external_memory = 1; // dst buffer doesn't own the memory. + dst->private_memory = NULL; + } + } +} + +// Copy and transfer ownership from src to dst (beware of parameter order!) +void WebPGrabDecBuffer(WebPDecBuffer* const src, WebPDecBuffer* const dst) { + if (src != NULL && dst != NULL) { + *dst = *src; + if (src->private_memory != NULL) { + src->is_external_memory = 1; // src relinquishes ownership + src->private_memory = NULL; + } + } +} + +VP8StatusCode WebPCopyDecBufferPixels(const WebPDecBuffer* const src_buf, + WebPDecBuffer* const dst_buf) { + assert(src_buf != NULL && dst_buf != NULL); + assert(src_buf->colorspace == dst_buf->colorspace); + + dst_buf->width = src_buf->width; + dst_buf->height = src_buf->height; + if (CheckDecBuffer(dst_buf) != VP8_STATUS_OK) { + return VP8_STATUS_INVALID_PARAM; + } + if (WebPIsRGBMode(src_buf->colorspace)) { + const WebPRGBABuffer* const src = &src_buf->u.RGBA; + const WebPRGBABuffer* const dst = &dst_buf->u.RGBA; + WebPCopyPlane(src->rgba, src->stride, dst->rgba, dst->stride, + src_buf->width * kModeBpp[src_buf->colorspace], + src_buf->height); + } else { + const WebPYUVABuffer* const src = &src_buf->u.YUVA; + const WebPYUVABuffer* const dst = &dst_buf->u.YUVA; + WebPCopyPlane(src->y, src->y_stride, dst->y, dst->y_stride, + src_buf->width, src_buf->height); + WebPCopyPlane(src->u, src->u_stride, dst->u, dst->u_stride, + (src_buf->width + 1) / 2, (src_buf->height + 1) / 2); + WebPCopyPlane(src->v, src->v_stride, dst->v, dst->v_stride, + (src_buf->width + 1) / 2, (src_buf->height + 1) / 2); + if (WebPIsAlphaMode(src_buf->colorspace)) { + WebPCopyPlane(src->a, src->a_stride, dst->a, dst->a_stride, + src_buf->width, src_buf->height); + } + } + return VP8_STATUS_OK; +} + +int WebPAvoidSlowMemory(const WebPDecBuffer* const output, + const WebPBitstreamFeatures* const features) { + assert(output != NULL); + return (output->is_external_memory >= 2) && + WebPIsPremultipliedMode(output->colorspace) && + (features != NULL && features->has_alpha); +} + +//------------------------------------------------------------------------------ diff --git a/packages/core/src/zig/vendor/libwebp/src/dec/common_dec.h b/packages/core/src/zig/vendor/libwebp/src/dec/common_dec.h new file mode 100644 index 0000000000..4a581cb365 --- /dev/null +++ b/packages/core/src/zig/vendor/libwebp/src/dec/common_dec.h @@ -0,0 +1,57 @@ +// Copyright 2015 Google Inc. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the COPYING file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +// ----------------------------------------------------------------------------- +// +// Definitions and macros common to encoding and decoding +// +// Author: Skal (pascal.massimino@gmail.com) + +#ifndef WEBP_DEC_COMMON_DEC_H_ +#define WEBP_DEC_COMMON_DEC_H_ + +// intra prediction modes +enum { B_DC_PRED = 0, // 4x4 modes + B_TM_PRED = 1, + B_VE_PRED = 2, + B_HE_PRED = 3, + B_RD_PRED = 4, + B_VR_PRED = 5, + B_LD_PRED = 6, + B_VL_PRED = 7, + B_HD_PRED = 8, + B_HU_PRED = 9, + NUM_BMODES = B_HU_PRED + 1 - B_DC_PRED, // = 10 + + // Luma16 or UV modes + DC_PRED = B_DC_PRED, V_PRED = B_VE_PRED, + H_PRED = B_HE_PRED, TM_PRED = B_TM_PRED, + B_PRED = NUM_BMODES, // refined I4x4 mode + NUM_PRED_MODES = 4, + + // special modes + B_DC_PRED_NOTOP = 4, + B_DC_PRED_NOLEFT = 5, + B_DC_PRED_NOTOPLEFT = 6, + NUM_B_DC_MODES = 7 }; + +enum { MB_FEATURE_TREE_PROBS = 3, + NUM_MB_SEGMENTS = 4, + NUM_REF_LF_DELTAS = 4, + NUM_MODE_LF_DELTAS = 4, // I4x4, ZERO, *, SPLIT + MAX_NUM_PARTITIONS = 8, + // Probabilities + NUM_TYPES = 4, // 0: i16-AC, 1: i16-DC, 2:chroma-AC, 3:i4-AC + NUM_BANDS = 8, + NUM_CTX = 3, + NUM_PROBAS = 11 + }; + +// Check that webp_csp_mode is within the bounds of WEBP_CSP_MODE. +int IsValidColorspace(int webp_csp_mode); + +#endif // WEBP_DEC_COMMON_DEC_H_ diff --git a/packages/core/src/zig/vendor/libwebp/src/dec/frame_dec.c b/packages/core/src/zig/vendor/libwebp/src/dec/frame_dec.c new file mode 100644 index 0000000000..8780772238 --- /dev/null +++ b/packages/core/src/zig/vendor/libwebp/src/dec/frame_dec.c @@ -0,0 +1,814 @@ +// Copyright 2010 Google Inc. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the COPYING file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +// ----------------------------------------------------------------------------- +// +// Frame-reconstruction function. Memory allocation. +// +// Author: Skal (pascal.massimino@gmail.com) + +#include +#include +#include + +#include "src/dec/common_dec.h" +#include "src/dec/vp8_dec.h" +#include "src/dec/vp8i_dec.h" +#include "src/dec/webpi_dec.h" +#include "src/dsp/dsp.h" +#include "src/utils/random_utils.h" +#include "src/utils/thread_utils.h" +#include "src/utils/utils.h" +#include "src/webp/decode.h" +#include "src/webp/types.h" + +//------------------------------------------------------------------------------ +// Main reconstruction function. + +static const uint16_t kScan[16] = { + 0 + 0 * BPS, 4 + 0 * BPS, 8 + 0 * BPS, 12 + 0 * BPS, + 0 + 4 * BPS, 4 + 4 * BPS, 8 + 4 * BPS, 12 + 4 * BPS, + 0 + 8 * BPS, 4 + 8 * BPS, 8 + 8 * BPS, 12 + 8 * BPS, + 0 + 12 * BPS, 4 + 12 * BPS, 8 + 12 * BPS, 12 + 12 * BPS +}; + +static int CheckMode(int mb_x, int mb_y, int mode) { + if (mode == B_DC_PRED) { + if (mb_x == 0) { + return (mb_y == 0) ? B_DC_PRED_NOTOPLEFT : B_DC_PRED_NOLEFT; + } else { + return (mb_y == 0) ? B_DC_PRED_NOTOP : B_DC_PRED; + } + } + return mode; +} + +static void Copy32b(uint8_t* const dst, const uint8_t* const src) { + memcpy(dst, src, 4); +} + +static WEBP_INLINE void DoTransform(uint32_t bits, const int16_t* const src, + uint8_t* const dst) { + switch (bits >> 30) { + case 3: + VP8Transform(src, dst, 0); + break; + case 2: + VP8TransformAC3(src, dst); + break; + case 1: + VP8TransformDC(src, dst); + break; + default: + break; + } +} + +static void DoUVTransform(uint32_t bits, const int16_t* const src, + uint8_t* const dst) { + if (bits & 0xff) { // any non-zero coeff at all? + if (bits & 0xaa) { // any non-zero AC coefficient? + VP8TransformUV(src, dst); // note we don't use the AC3 variant for U/V + } else { + VP8TransformDCUV(src, dst); + } + } +} + +static void ReconstructRow(const VP8Decoder* const dec, + const VP8ThreadContext* ctx) { + int j; + int mb_x; + const int mb_y = ctx->mb_y; + const int cache_id = ctx->id; + uint8_t* const y_dst = dec->yuv_b + Y_OFF; + uint8_t* const u_dst = dec->yuv_b + U_OFF; + uint8_t* const v_dst = dec->yuv_b + V_OFF; + + // Initialize left-most block. + for (j = 0; j < 16; ++j) { + y_dst[j * BPS - 1] = 129; + } + for (j = 0; j < 8; ++j) { + u_dst[j * BPS - 1] = 129; + v_dst[j * BPS - 1] = 129; + } + + // Init top-left sample on left column too. + if (mb_y > 0) { + y_dst[-1 - BPS] = u_dst[-1 - BPS] = v_dst[-1 - BPS] = 129; + } else { + // we only need to do this init once at block (0,0). + // Afterward, it remains valid for the whole topmost row. + memset(y_dst - BPS - 1, 127, 16 + 4 + 1); + memset(u_dst - BPS - 1, 127, 8 + 1); + memset(v_dst - BPS - 1, 127, 8 + 1); + } + + // Reconstruct one row. + for (mb_x = 0; mb_x < dec->mb_w; ++mb_x) { + const VP8MBData* const block = ctx->mb_data + mb_x; + + // Rotate in the left samples from previously decoded block. We move four + // pixels at a time for alignment reason, and because of in-loop filter. + if (mb_x > 0) { + for (j = -1; j < 16; ++j) { + Copy32b(&y_dst[j * BPS - 4], &y_dst[j * BPS + 12]); + } + for (j = -1; j < 8; ++j) { + Copy32b(&u_dst[j * BPS - 4], &u_dst[j * BPS + 4]); + Copy32b(&v_dst[j * BPS - 4], &v_dst[j * BPS + 4]); + } + } + { + // bring top samples into the cache + VP8TopSamples* const top_yuv = dec->yuv_t + mb_x; + const int16_t* const coeffs = block->coeffs; + uint32_t bits = block->non_zero_y; + int n; + + if (mb_y > 0) { + memcpy(y_dst - BPS, top_yuv[0].y, 16); + memcpy(u_dst - BPS, top_yuv[0].u, 8); + memcpy(v_dst - BPS, top_yuv[0].v, 8); + } + + // predict and add residuals + if (block->is_i4x4) { // 4x4 + uint32_t* const top_right = (uint32_t*)(y_dst - BPS + 16); + + if (mb_y > 0) { + if (mb_x >= dec->mb_w - 1) { // on rightmost border + memset(top_right, top_yuv[0].y[15], sizeof(*top_right)); + } else { + memcpy(top_right, top_yuv[1].y, sizeof(*top_right)); + } + } + // replicate the top-right pixels below + top_right[BPS] = top_right[2 * BPS] = top_right[3 * BPS] = top_right[0]; + + // predict and add residuals for all 4x4 blocks in turn. + for (n = 0; n < 16; ++n, bits <<= 2) { + uint8_t* const dst = y_dst + kScan[n]; + VP8PredLuma4[block->imodes[n]](dst); + DoTransform(bits, coeffs + n * 16, dst); + } + } else { // 16x16 + const int pred_func = CheckMode(mb_x, mb_y, block->imodes[0]); + VP8PredLuma16[pred_func](y_dst); + if (bits != 0) { + for (n = 0; n < 16; ++n, bits <<= 2) { + DoTransform(bits, coeffs + n * 16, y_dst + kScan[n]); + } + } + } + { + // Chroma + const uint32_t bits_uv = block->non_zero_uv; + const int pred_func = CheckMode(mb_x, mb_y, block->uvmode); + VP8PredChroma8[pred_func](u_dst); + VP8PredChroma8[pred_func](v_dst); + DoUVTransform(bits_uv >> 0, coeffs + 16 * 16, u_dst); + DoUVTransform(bits_uv >> 8, coeffs + 20 * 16, v_dst); + } + + // stash away top samples for next block + if (mb_y < dec->mb_h - 1) { + memcpy(top_yuv[0].y, y_dst + 15 * BPS, 16); + memcpy(top_yuv[0].u, u_dst + 7 * BPS, 8); + memcpy(top_yuv[0].v, v_dst + 7 * BPS, 8); + } + } + // Transfer reconstructed samples from yuv_b cache to final destination. + { + const int y_offset = cache_id * 16 * dec->cache_y_stride; + const int uv_offset = cache_id * 8 * dec->cache_uv_stride; + uint8_t* const y_out = dec->cache_y + mb_x * 16 + y_offset; + uint8_t* const u_out = dec->cache_u + mb_x * 8 + uv_offset; + uint8_t* const v_out = dec->cache_v + mb_x * 8 + uv_offset; + for (j = 0; j < 16; ++j) { + memcpy(y_out + j * dec->cache_y_stride, y_dst + j * BPS, 16); + } + for (j = 0; j < 8; ++j) { + memcpy(u_out + j * dec->cache_uv_stride, u_dst + j * BPS, 8); + memcpy(v_out + j * dec->cache_uv_stride, v_dst + j * BPS, 8); + } + } + } +} + +//------------------------------------------------------------------------------ +// Filtering + +// kFilterExtraRows[] = How many extra lines are needed on the MB boundary +// for caching, given a filtering level. +// Simple filter: up to 2 luma samples are read and 1 is written. +// Complex filter: up to 4 luma samples are read and 3 are written. Same for +// U/V, so it's 8 samples total (because of the 2x upsampling). +static const uint8_t kFilterExtraRows[3] = { 0, 2, 8 }; + +static void DoFilter(const VP8Decoder* const dec, int mb_x, int mb_y) { + const VP8ThreadContext* const ctx = &dec->thread_ctx; + const int cache_id = ctx->id; + const int y_bps = dec->cache_y_stride; + const VP8FInfo* const f_info = ctx->f_info + mb_x; + uint8_t* const y_dst = dec->cache_y + cache_id * 16 * y_bps + mb_x * 16; + const int ilevel = f_info->f_ilevel; + const int limit = f_info->f_limit; + if (limit == 0) { + return; + } + assert(limit >= 3); + if (dec->filter_type == 1) { // simple + if (mb_x > 0) { + VP8SimpleHFilter16(y_dst, y_bps, limit + 4); + } + if (f_info->f_inner) { + VP8SimpleHFilter16i(y_dst, y_bps, limit); + } + if (mb_y > 0) { + VP8SimpleVFilter16(y_dst, y_bps, limit + 4); + } + if (f_info->f_inner) { + VP8SimpleVFilter16i(y_dst, y_bps, limit); + } + } else { // complex + const int uv_bps = dec->cache_uv_stride; + uint8_t* const u_dst = dec->cache_u + cache_id * 8 * uv_bps + mb_x * 8; + uint8_t* const v_dst = dec->cache_v + cache_id * 8 * uv_bps + mb_x * 8; + const int hev_thresh = f_info->hev_thresh; + if (mb_x > 0) { + VP8HFilter16(y_dst, y_bps, limit + 4, ilevel, hev_thresh); + VP8HFilter8(u_dst, v_dst, uv_bps, limit + 4, ilevel, hev_thresh); + } + if (f_info->f_inner) { + VP8HFilter16i(y_dst, y_bps, limit, ilevel, hev_thresh); + VP8HFilter8i(u_dst, v_dst, uv_bps, limit, ilevel, hev_thresh); + } + if (mb_y > 0) { + VP8VFilter16(y_dst, y_bps, limit + 4, ilevel, hev_thresh); + VP8VFilter8(u_dst, v_dst, uv_bps, limit + 4, ilevel, hev_thresh); + } + if (f_info->f_inner) { + VP8VFilter16i(y_dst, y_bps, limit, ilevel, hev_thresh); + VP8VFilter8i(u_dst, v_dst, uv_bps, limit, ilevel, hev_thresh); + } + } +} + +// Filter the decoded macroblock row (if needed) +static void FilterRow(const VP8Decoder* const dec) { + int mb_x; + const int mb_y = dec->thread_ctx.mb_y; + assert(dec->thread_ctx.filter_row); + for (mb_x = dec->tl_mb_x; mb_x < dec->br_mb_x; ++mb_x) { + DoFilter(dec, mb_x, mb_y); + } +} + +//------------------------------------------------------------------------------ +// Precompute the filtering strength for each segment and each i4x4/i16x16 mode. + +static void PrecomputeFilterStrengths(VP8Decoder* const dec) { + if (dec->filter_type > 0) { + int s; + const VP8FilterHeader* const hdr = &dec->filter_hdr; + for (s = 0; s < NUM_MB_SEGMENTS; ++s) { + int i4x4; + // First, compute the initial level + int base_level; + if (dec->segment_hdr.use_segment) { + base_level = dec->segment_hdr.filter_strength[s]; + if (!dec->segment_hdr.absolute_delta) { + base_level += hdr->level; + } + } else { + base_level = hdr->level; + } + for (i4x4 = 0; i4x4 <= 1; ++i4x4) { + VP8FInfo* const info = &dec->fstrengths[s][i4x4]; + int level = base_level; + if (hdr->use_lf_delta) { + level += hdr->ref_lf_delta[0]; + if (i4x4) { + level += hdr->mode_lf_delta[0]; + } + } + level = (level < 0) ? 0 : (level > 63) ? 63 : level; + if (level > 0) { + int ilevel = level; + if (hdr->sharpness > 0) { + if (hdr->sharpness > 4) { + ilevel >>= 2; + } else { + ilevel >>= 1; + } + if (ilevel > 9 - hdr->sharpness) { + ilevel = 9 - hdr->sharpness; + } + } + if (ilevel < 1) ilevel = 1; + info->f_ilevel = ilevel; + info->f_limit = 2 * level + ilevel; + info->hev_thresh = (level >= 40) ? 2 : (level >= 15) ? 1 : 0; + } else { + info->f_limit = 0; // no filtering + } + info->f_inner = i4x4; + } + } + } +} + +//------------------------------------------------------------------------------ +// Dithering + +// minimal amp that will provide a non-zero dithering effect +#define MIN_DITHER_AMP 4 + +#define DITHER_AMP_TAB_SIZE 12 +static const uint8_t kQuantToDitherAmp[DITHER_AMP_TAB_SIZE] = { + // roughly, it's dqm->uv_mat[1] + 8, 7, 6, 4, 4, 2, 2, 2, 1, 1, 1, 1 +}; + +void VP8InitDithering(const WebPDecoderOptions* const options, + VP8Decoder* const dec) { + assert(dec != NULL); + if (options != NULL) { + const int d = options->dithering_strength; + const int max_amp = (1 << VP8_RANDOM_DITHER_FIX) - 1; + const int f = (d < 0) ? 0 : (d > 100) ? max_amp : (d * max_amp / 100); + if (f > 0) { + int s; + int all_amp = 0; + for (s = 0; s < NUM_MB_SEGMENTS; ++s) { + VP8QuantMatrix* const dqm = &dec->dqm[s]; + if (dqm->uv_quant < DITHER_AMP_TAB_SIZE) { + const int idx = (dqm->uv_quant < 0) ? 0 : dqm->uv_quant; + dqm->dither = (f * kQuantToDitherAmp[idx]) >> 3; + } + all_amp |= dqm->dither; + } + if (all_amp != 0) { + VP8InitRandom(&dec->dithering_rg, 1.0f); + dec->dither = 1; + } + } + // potentially allow alpha dithering + dec->alpha_dithering = options->alpha_dithering_strength; + if (dec->alpha_dithering > 100) { + dec->alpha_dithering = 100; + } else if (dec->alpha_dithering < 0) { + dec->alpha_dithering = 0; + } + } +} + +// Convert to range: [-2,2] for dither=50, [-4,4] for dither=100 +static void Dither8x8(VP8Random* const rg, uint8_t* dst, int bps, int amp) { + uint8_t dither[64]; + int i; + for (i = 0; i < 8 * 8; ++i) { + dither[i] = VP8RandomBits2(rg, VP8_DITHER_AMP_BITS + 1, amp); + } + VP8DitherCombine8x8(dither, dst, bps); +} + +static void DitherRow(VP8Decoder* const dec) { + int mb_x; + assert(dec->dither); + for (mb_x = dec->tl_mb_x; mb_x < dec->br_mb_x; ++mb_x) { + const VP8ThreadContext* const ctx = &dec->thread_ctx; + const VP8MBData* const data = ctx->mb_data + mb_x; + const int cache_id = ctx->id; + const int uv_bps = dec->cache_uv_stride; + if (data->dither >= MIN_DITHER_AMP) { + uint8_t* const u_dst = dec->cache_u + cache_id * 8 * uv_bps + mb_x * 8; + uint8_t* const v_dst = dec->cache_v + cache_id * 8 * uv_bps + mb_x * 8; + Dither8x8(&dec->dithering_rg, u_dst, uv_bps, data->dither); + Dither8x8(&dec->dithering_rg, v_dst, uv_bps, data->dither); + } + } +} + +//------------------------------------------------------------------------------ +// This function is called after a row of macroblocks is finished decoding. +// It also takes into account the following restrictions: +// * In case of in-loop filtering, we must hold off sending some of the bottom +// pixels as they are yet unfiltered. They will be when the next macroblock +// row is decoded. Meanwhile, we must preserve them by rotating them in the +// cache area. This doesn't hold for the very bottom row of the uncropped +// picture of course. +// * we must clip the remaining pixels against the cropping area. The VP8Io +// struct must have the following fields set correctly before calling put(): + +#define MACROBLOCK_VPOS(mb_y) ((mb_y) * 16) // vertical position of a MB + +// Finalize and transmit a complete row. Return false in case of user-abort. +static int FinishRow(void* arg1, void* arg2) { + VP8Decoder* const dec = (VP8Decoder*)arg1; + VP8Io* const io = (VP8Io*)arg2; + int ok = 1; + const VP8ThreadContext* const ctx = &dec->thread_ctx; + const int cache_id = ctx->id; + const int extra_y_rows = kFilterExtraRows[dec->filter_type]; + const int ysize = extra_y_rows * dec->cache_y_stride; + const int uvsize = (extra_y_rows / 2) * dec->cache_uv_stride; + const int y_offset = cache_id * 16 * dec->cache_y_stride; + const int uv_offset = cache_id * 8 * dec->cache_uv_stride; + uint8_t* const ydst = dec->cache_y - ysize + y_offset; + uint8_t* const udst = dec->cache_u - uvsize + uv_offset; + uint8_t* const vdst = dec->cache_v - uvsize + uv_offset; + const int mb_y = ctx->mb_y; + const int is_first_row = (mb_y == 0); + const int is_last_row = (mb_y >= dec->br_mb_y - 1); + + if (dec->mt_method == 2) { + ReconstructRow(dec, ctx); + } + + if (ctx->filter_row) { + FilterRow(dec); + } + + if (dec->dither) { + DitherRow(dec); + } + + if (io->put != NULL) { + int y_start = MACROBLOCK_VPOS(mb_y); + int y_end = MACROBLOCK_VPOS(mb_y + 1); + if (!is_first_row) { + y_start -= extra_y_rows; + io->y = ydst; + io->u = udst; + io->v = vdst; + } else { + io->y = dec->cache_y + y_offset; + io->u = dec->cache_u + uv_offset; + io->v = dec->cache_v + uv_offset; + } + + if (!is_last_row) { + y_end -= extra_y_rows; + } + if (y_end > io->crop_bottom) { + y_end = io->crop_bottom; // make sure we don't overflow on last row. + } + // If dec->alpha_data is not NULL, we have some alpha plane present. + io->a = NULL; + if (dec->alpha_data != NULL && y_start < y_end) { + io->a = VP8DecompressAlphaRows(dec, io, y_start, y_end - y_start); + if (io->a == NULL) { + return VP8SetError(dec, VP8_STATUS_BITSTREAM_ERROR, + "Could not decode alpha data."); + } + } + if (y_start < io->crop_top) { + const int delta_y = io->crop_top - y_start; + y_start = io->crop_top; + assert(!(delta_y & 1)); + io->y += dec->cache_y_stride * delta_y; + io->u += dec->cache_uv_stride * (delta_y >> 1); + io->v += dec->cache_uv_stride * (delta_y >> 1); + if (io->a != NULL) { + io->a += io->width * delta_y; + } + } + if (y_start < y_end) { + io->y += io->crop_left; + io->u += io->crop_left >> 1; + io->v += io->crop_left >> 1; + if (io->a != NULL) { + io->a += io->crop_left; + } + io->mb_y = y_start - io->crop_top; + io->mb_w = io->crop_right - io->crop_left; + io->mb_h = y_end - y_start; + ok = io->put(io); + } + } + // rotate top samples if needed + if (cache_id + 1 == dec->num_caches) { + if (!is_last_row) { + memcpy(dec->cache_y - ysize, ydst + 16 * dec->cache_y_stride, ysize); + memcpy(dec->cache_u - uvsize, udst + 8 * dec->cache_uv_stride, uvsize); + memcpy(dec->cache_v - uvsize, vdst + 8 * dec->cache_uv_stride, uvsize); + } + } + + return ok; +} + +#undef MACROBLOCK_VPOS + +//------------------------------------------------------------------------------ + +int VP8ProcessRow(VP8Decoder* const dec, VP8Io* const io) { + int ok = 1; + VP8ThreadContext* const ctx = &dec->thread_ctx; + const int filter_row = + (dec->filter_type > 0) && + (dec->mb_y >= dec->tl_mb_y) && (dec->mb_y <= dec->br_mb_y); + if (dec->mt_method == 0) { + // ctx->id and ctx->f_info are already set + ctx->mb_y = dec->mb_y; + ctx->filter_row = filter_row; + ReconstructRow(dec, ctx); + ok = FinishRow(dec, io); + } else { + WebPWorker* const worker = &dec->worker; + // Finish previous job *before* updating context + ok &= WebPGetWorkerInterface()->Sync(worker); + assert(worker->status == OK); + if (ok) { // spawn a new deblocking/output job + ctx->io = *io; + ctx->id = dec->cache_id; + ctx->mb_y = dec->mb_y; + ctx->filter_row = filter_row; + if (dec->mt_method == 2) { // swap macroblock data + VP8MBData* const tmp = ctx->mb_data; + ctx->mb_data = dec->mb_data; + dec->mb_data = tmp; + } else { + // perform reconstruction directly in main thread + ReconstructRow(dec, ctx); + } + if (filter_row) { // swap filter info + VP8FInfo* const tmp = ctx->f_info; + ctx->f_info = dec->f_info; + dec->f_info = tmp; + } + // (reconstruct)+filter in parallel + WebPGetWorkerInterface()->Launch(worker); + if (++dec->cache_id == dec->num_caches) { + dec->cache_id = 0; + } + } + } + return ok; +} + +//------------------------------------------------------------------------------ +// Finish setting up the decoding parameter once user's setup() is called. + +VP8StatusCode VP8EnterCritical(VP8Decoder* const dec, VP8Io* const io) { + // Call setup() first. This may trigger additional decoding features on 'io'. + // Note: Afterward, we must call teardown() no matter what. + if (io->setup != NULL && !io->setup(io)) { + VP8SetError(dec, VP8_STATUS_USER_ABORT, "Frame setup failed"); + return dec->status; + } + + // Disable filtering per user request + if (io->bypass_filtering) { + dec->filter_type = 0; + } + + // Define the area where we can skip in-loop filtering, in case of cropping. + // + // 'Simple' filter reads two luma samples outside of the macroblock + // and filters one. It doesn't filter the chroma samples. Hence, we can + // avoid doing the in-loop filtering before crop_top/crop_left position. + // For the 'Complex' filter, 3 samples are read and up to 3 are filtered. + // Means: there's a dependency chain that goes all the way up to the + // top-left corner of the picture (MB #0). We must filter all the previous + // macroblocks. + { + const int extra_pixels = kFilterExtraRows[dec->filter_type]; + if (dec->filter_type == 2) { + // For complex filter, we need to preserve the dependency chain. + dec->tl_mb_x = 0; + dec->tl_mb_y = 0; + } else { + // For simple filter, we can filter only the cropped region. + // We include 'extra_pixels' on the other side of the boundary, since + // vertical or horizontal filtering of the previous macroblock can + // modify some abutting pixels. + dec->tl_mb_x = (io->crop_left - extra_pixels) >> 4; + dec->tl_mb_y = (io->crop_top - extra_pixels) >> 4; + if (dec->tl_mb_x < 0) dec->tl_mb_x = 0; + if (dec->tl_mb_y < 0) dec->tl_mb_y = 0; + } + // We need some 'extra' pixels on the right/bottom. + dec->br_mb_y = (io->crop_bottom + 15 + extra_pixels) >> 4; + dec->br_mb_x = (io->crop_right + 15 + extra_pixels) >> 4; + if (dec->br_mb_x > dec->mb_w) { + dec->br_mb_x = dec->mb_w; + } + if (dec->br_mb_y > dec->mb_h) { + dec->br_mb_y = dec->mb_h; + } + } + PrecomputeFilterStrengths(dec); + return VP8_STATUS_OK; +} + +int VP8ExitCritical(VP8Decoder* const dec, VP8Io* const io) { + int ok = 1; + if (dec->mt_method > 0) { + ok = WebPGetWorkerInterface()->Sync(&dec->worker); + } + + if (io->teardown != NULL) { + io->teardown(io); + } + return ok; +} + +//------------------------------------------------------------------------------ +// For multi-threaded decoding we need to use 3 rows of 16 pixels as delay line. +// +// Reason is: the deblocking filter cannot deblock the bottom horizontal edges +// immediately, and needs to wait for first few rows of the next macroblock to +// be decoded. Hence, deblocking is lagging behind by 4 or 8 pixels (depending +// on strength). +// With two threads, the vertical positions of the rows being decoded are: +// Decode: [ 0..15][16..31][32..47][48..63][64..79][... +// Deblock: [ 0..11][12..27][28..43][44..59][... +// If we use two threads and two caches of 16 pixels, the sequence would be: +// Decode: [ 0..15][16..31][ 0..15!!][16..31][ 0..15][... +// Deblock: [ 0..11][12..27!!][-4..11][12..27][... +// The problem occurs during row [12..15!!] that both the decoding and +// deblocking threads are writing simultaneously. +// With 3 cache lines, one get a safe write pattern: +// Decode: [ 0..15][16..31][32..47][ 0..15][16..31][32..47][0.. +// Deblock: [ 0..11][12..27][28..43][-4..11][12..27][28... +// Note that multi-threaded output _without_ deblocking can make use of two +// cache lines of 16 pixels only, since there's no lagging behind. The decoding +// and output process have non-concurrent writing: +// Decode: [ 0..15][16..31][ 0..15][16..31][... +// io->put: [ 0..15][16..31][ 0..15][... + +#define MT_CACHE_LINES 3 +#define ST_CACHE_LINES 1 // 1 cache row only for single-threaded case + +// Initialize multi/single-thread worker +static int InitThreadContext(VP8Decoder* const dec) { + dec->cache_id = 0; + if (dec->mt_method > 0) { + WebPWorker* const worker = &dec->worker; + if (!WebPGetWorkerInterface()->Reset(worker)) { + return VP8SetError(dec, VP8_STATUS_OUT_OF_MEMORY, + "thread initialization failed."); + } + worker->data1 = dec; + worker->data2 = (void*)&dec->thread_ctx.io; + worker->hook = FinishRow; + dec->num_caches = + (dec->filter_type > 0) ? MT_CACHE_LINES : MT_CACHE_LINES - 1; + } else { + dec->num_caches = ST_CACHE_LINES; + } + return 1; +} + +int VP8GetThreadMethod(const WebPDecoderOptions* const options, + const WebPHeaderStructure* const headers, + int width, int height) { + if (options == NULL || options->use_threads == 0) { + return 0; + } + (void)headers; + (void)width; + (void)height; + assert(headers == NULL || !headers->is_lossless); +#if defined(WEBP_USE_THREAD) + if (width >= MIN_WIDTH_FOR_THREADS) return 2; +#endif + return 0; +} + +#undef MT_CACHE_LINES +#undef ST_CACHE_LINES + +//------------------------------------------------------------------------------ +// Memory setup + +static int AllocateMemory(VP8Decoder* const dec) { + const int num_caches = dec->num_caches; + const int mb_w = dec->mb_w; + // Note: we use 'size_t' when there's no overflow risk, uint64_t otherwise. + const size_t intra_pred_mode_size = 4 * mb_w * sizeof(uint8_t); + const size_t top_size = sizeof(VP8TopSamples) * mb_w; + const size_t mb_info_size = (mb_w + 1) * sizeof(VP8MB); + const size_t f_info_size = + (dec->filter_type > 0) ? + mb_w * (dec->mt_method > 0 ? 2 : 1) * sizeof(VP8FInfo) + : 0; + const size_t yuv_size = YUV_SIZE * sizeof(*dec->yuv_b); + const size_t mb_data_size = + (dec->mt_method == 2 ? 2 : 1) * mb_w * sizeof(*dec->mb_data); + const size_t cache_height = (16 * num_caches + + kFilterExtraRows[dec->filter_type]) * 3 / 2; + const size_t cache_size = top_size * cache_height; + // alpha_size is the only one that scales as width x height. + const uint64_t alpha_size = (dec->alpha_data != NULL) ? + (uint64_t)dec->pic_hdr.width * dec->pic_hdr.height : 0ULL; + const uint64_t needed = (uint64_t)intra_pred_mode_size + + top_size + mb_info_size + f_info_size + + yuv_size + mb_data_size + + cache_size + alpha_size + WEBP_ALIGN_CST; + uint8_t* mem; + + if (!CheckSizeOverflow(needed)) return 0; // check for overflow + if (needed > dec->mem_size) { + WebPSafeFree(dec->mem); + dec->mem_size = 0; + dec->mem = WebPSafeMalloc(needed, sizeof(uint8_t)); + if (dec->mem == NULL) { + return VP8SetError(dec, VP8_STATUS_OUT_OF_MEMORY, + "no memory during frame initialization."); + } + // down-cast is ok, thanks to WebPSafeMalloc() above. + dec->mem_size = (size_t)needed; + } + + mem = (uint8_t*)dec->mem; + dec->intra_t = mem; + mem += intra_pred_mode_size; + + dec->yuv_t = (VP8TopSamples*)mem; + mem += top_size; + + dec->mb_info = ((VP8MB*)mem) + 1; + mem += mb_info_size; + + dec->f_info = f_info_size ? (VP8FInfo*)mem : NULL; + mem += f_info_size; + dec->thread_ctx.id = 0; + dec->thread_ctx.f_info = dec->f_info; + if (dec->filter_type > 0 && dec->mt_method > 0) { + // secondary cache line. The deblocking process need to make use of the + // filtering strength from previous macroblock row, while the new ones + // are being decoded in parallel. We'll just swap the pointers. + dec->thread_ctx.f_info += mb_w; + } + + mem = (uint8_t*)WEBP_ALIGN(mem); + assert((yuv_size & WEBP_ALIGN_CST) == 0); + dec->yuv_b = mem; + mem += yuv_size; + + dec->mb_data = (VP8MBData*)mem; + dec->thread_ctx.mb_data = (VP8MBData*)mem; + if (dec->mt_method == 2) { + dec->thread_ctx.mb_data += mb_w; + } + mem += mb_data_size; + + dec->cache_y_stride = 16 * mb_w; + dec->cache_uv_stride = 8 * mb_w; + { + const int extra_rows = kFilterExtraRows[dec->filter_type]; + const int extra_y = extra_rows * dec->cache_y_stride; + const int extra_uv = (extra_rows / 2) * dec->cache_uv_stride; + dec->cache_y = mem + extra_y; + dec->cache_u = dec->cache_y + + 16 * num_caches * dec->cache_y_stride + extra_uv; + dec->cache_v = dec->cache_u + + 8 * num_caches * dec->cache_uv_stride + extra_uv; + dec->cache_id = 0; + } + mem += cache_size; + + // alpha plane + dec->alpha_plane = alpha_size ? mem : NULL; + mem += alpha_size; + assert(mem <= (uint8_t*)dec->mem + dec->mem_size); + + // note: left/top-info is initialized once for all. + memset(dec->mb_info - 1, 0, mb_info_size); + VP8InitScanline(dec); // initialize left too. + + // initialize top + memset(dec->intra_t, B_DC_PRED, intra_pred_mode_size); + + return 1; +} + +static void InitIo(VP8Decoder* const dec, VP8Io* io) { + // prepare 'io' + io->mb_y = 0; + io->y = dec->cache_y; + io->u = dec->cache_u; + io->v = dec->cache_v; + io->y_stride = dec->cache_y_stride; + io->uv_stride = dec->cache_uv_stride; + io->a = NULL; +} + +int VP8InitFrame(VP8Decoder* const dec, VP8Io* const io) { + if (!InitThreadContext(dec)) return 0; // call first. Sets dec->num_caches. + if (!AllocateMemory(dec)) return 0; + InitIo(dec, io); + VP8DspInit(); // Init critical function pointers and look-up tables. + return 1; +} + +//------------------------------------------------------------------------------ diff --git a/packages/core/src/zig/vendor/libwebp/src/dec/idec_dec.c b/packages/core/src/zig/vendor/libwebp/src/dec/idec_dec.c new file mode 100644 index 0000000000..cf8a33a495 --- /dev/null +++ b/packages/core/src/zig/vendor/libwebp/src/dec/idec_dec.c @@ -0,0 +1,931 @@ +// Copyright 2011 Google Inc. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the COPYING file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +// ----------------------------------------------------------------------------- +// +// Incremental decoding +// +// Author: somnath@google.com (Somnath Banerjee) + +#include +#include +#include + +#include "src/dec/alphai_dec.h" +#include "src/dec/vp8_dec.h" +#include "src/dec/vp8i_dec.h" +#include "src/dec/vp8li_dec.h" +#include "src/dec/webpi_dec.h" +#include "src/utils/bit_reader_utils.h" +#include "src/utils/thread_utils.h" +#include "src/utils/utils.h" +#include "src/webp/decode.h" +#include "src/webp/format_constants.h" +#include "src/webp/types.h" + +// In append mode, buffer allocations increase as multiples of this value. +// Needs to be a power of 2. +#define CHUNK_SIZE 4096 +#define MAX_MB_SIZE 4096 + +//------------------------------------------------------------------------------ +// Data structures for memory and states + +// Decoding states. State normally flows as: +// WEBP_HEADER->VP8_HEADER->VP8_PARTS0->VP8_DATA->DONE for a lossy image, and +// WEBP_HEADER->VP8L_HEADER->VP8L_DATA->DONE for a lossless image. +// If there is any error the decoder goes into state ERROR. +typedef enum { + STATE_WEBP_HEADER, // All the data before that of the VP8/VP8L chunk. + STATE_VP8_HEADER, // The VP8 Frame header (within the VP8 chunk). + STATE_VP8_PARTS0, + STATE_VP8_DATA, + STATE_VP8L_HEADER, + STATE_VP8L_DATA, + STATE_DONE, + STATE_ERROR +} DecState; + +// Operating state for the MemBuffer +typedef enum { + MEM_MODE_NONE = 0, + MEM_MODE_APPEND, + MEM_MODE_MAP +} MemBufferMode; + +// storage for partition #0 and partial data (in a rolling fashion) +typedef struct { + MemBufferMode mode; // Operation mode + size_t start; // start location of the data to be decoded + size_t end; // end location + size_t buf_size; // size of the allocated buffer + uint8_t* buf; // We don't own this buffer in case WebPIUpdate() + + size_t part0_size; // size of partition #0 + const uint8_t* part0_buf; // buffer to store partition #0 +} MemBuffer; + +struct WebPIDecoder { + DecState state; // current decoding state + WebPDecParams params; // Params to store output info + int is_lossless; // for down-casting 'dec'. + void* dec; // either a VP8Decoder or a VP8LDecoder instance + VP8Io io; + + MemBuffer mem; // input memory buffer. + WebPDecBuffer output; // output buffer (when no external one is supplied, + // or if the external one has slow-memory) + WebPDecBuffer* final_output; // Slow-memory output to copy to eventually. + size_t chunk_size; // Compressed VP8/VP8L size extracted from Header. + + int last_mb_y; // last row reached for intra-mode decoding +}; + +// MB context to restore in case VP8DecodeMB() fails +typedef struct { + VP8MB left; + VP8MB info; + VP8BitReader token_br; +} MBContext; + +//------------------------------------------------------------------------------ +// MemBuffer: incoming data handling + +static WEBP_INLINE size_t MemDataSize(const MemBuffer* mem) { + return (mem->end - mem->start); +} + +// Check if we need to preserve the compressed alpha data, as it may not have +// been decoded yet. +static int NeedCompressedAlpha(const WebPIDecoder* const idec) { + if (idec->state == STATE_WEBP_HEADER) { + // We haven't parsed the headers yet, so we don't know whether the image is + // lossy or lossless. This also means that we haven't parsed the ALPH chunk. + return 0; + } + if (idec->is_lossless) { + return 0; // ALPH chunk is not present for lossless images. + } else { + const VP8Decoder* const dec = (VP8Decoder*)idec->dec; + assert(dec != NULL); // Must be true as idec->state != STATE_WEBP_HEADER. + return (dec->alpha_data != NULL) && !dec->is_alpha_decoded; + } +} + +static void DoRemap(WebPIDecoder* const idec, ptrdiff_t offset) { + MemBuffer* const mem = &idec->mem; + const uint8_t* const new_base = mem->buf + mem->start; + // note: for VP8, setting up idec->io is only really needed at the beginning + // of the decoding, till partition #0 is complete. + idec->io.data = new_base; + idec->io.data_size = MemDataSize(mem); + + if (idec->dec != NULL) { + if (!idec->is_lossless) { + VP8Decoder* const dec = (VP8Decoder*)idec->dec; + const uint32_t last_part = dec->num_parts_minus_one; + if (offset != 0) { + uint32_t p; + for (p = 0; p <= last_part; ++p) { + VP8RemapBitReader(dec->parts + p, offset); + } + // Remap partition #0 data pointer to new offset, but only in MAP + // mode (in APPEND mode, partition #0 is copied into a fixed memory). + if (mem->mode == MEM_MODE_MAP) { + VP8RemapBitReader(&dec->br, offset); + } + } + { + const uint8_t* const last_start = dec->parts[last_part].buf; + // 'last_start' will be NULL when 'idec->state' is < STATE_VP8_PARTS0 + // and through a portion of that state (when there isn't enough data to + // parse the partitions). The bitreader is only used meaningfully when + // there is enough data to begin parsing partition 0. + if (last_start != NULL) { + VP8BitReaderSetBuffer(&dec->parts[last_part], last_start, + mem->buf + mem->end - last_start); + } + } + if (NeedCompressedAlpha(idec)) { + ALPHDecoder* const alph_dec = dec->alph_dec; + dec->alpha_data += offset; + if (alph_dec != NULL && alph_dec->vp8l_dec != NULL) { + if (alph_dec->method == ALPHA_LOSSLESS_COMPRESSION) { + VP8LDecoder* const alph_vp8l_dec = alph_dec->vp8l_dec; + assert(dec->alpha_data_size >= ALPHA_HEADER_LEN); + VP8LBitReaderSetBuffer(&alph_vp8l_dec->br, + dec->alpha_data + ALPHA_HEADER_LEN, + dec->alpha_data_size - ALPHA_HEADER_LEN); + } else { // alph_dec->method == ALPHA_NO_COMPRESSION + // Nothing special to do in this case. + } + } + } + } else { // Resize lossless bitreader + VP8LDecoder* const dec = (VP8LDecoder*)idec->dec; + VP8LBitReaderSetBuffer(&dec->br, new_base, MemDataSize(mem)); + } + } +} + +// Appends data to the end of MemBuffer->buf. It expands the allocated memory +// size if required and also updates VP8BitReader's if new memory is allocated. +WEBP_NODISCARD static int AppendToMemBuffer(WebPIDecoder* const idec, + const uint8_t* const data, + size_t data_size) { + VP8Decoder* const dec = (VP8Decoder*)idec->dec; + MemBuffer* const mem = &idec->mem; + const int need_compressed_alpha = NeedCompressedAlpha(idec); + const uint8_t* const old_start = + (mem->buf == NULL) ? NULL : mem->buf + mem->start; + const uint8_t* const old_base = + need_compressed_alpha ? dec->alpha_data : old_start; + assert(mem->buf != NULL || mem->start == 0); + assert(mem->mode == MEM_MODE_APPEND); + if (data_size > MAX_CHUNK_PAYLOAD) { + // security safeguard: trying to allocate more than what the format + // allows for a chunk should be considered a smoke smell. + return 0; + } + + if (mem->end + data_size > mem->buf_size) { // Need some free memory + const size_t new_mem_start = old_start - old_base; + const size_t current_size = MemDataSize(mem) + new_mem_start; + const uint64_t new_size = (uint64_t)current_size + data_size; + const uint64_t extra_size = (new_size + CHUNK_SIZE - 1) & ~(CHUNK_SIZE - 1); + uint8_t* const new_buf = + (uint8_t*)WebPSafeMalloc(extra_size, sizeof(*new_buf)); + if (new_buf == NULL) return 0; + if (old_base != NULL) memcpy(new_buf, old_base, current_size); + WebPSafeFree(mem->buf); + mem->buf = new_buf; + mem->buf_size = (size_t)extra_size; + mem->start = new_mem_start; + mem->end = current_size; + } + + assert(mem->buf != NULL); + memcpy(mem->buf + mem->end, data, data_size); + mem->end += data_size; + assert(mem->end <= mem->buf_size); + + DoRemap(idec, mem->buf + mem->start - old_start); + return 1; +} + +WEBP_NODISCARD static int RemapMemBuffer(WebPIDecoder* const idec, + const uint8_t* const data, + size_t data_size) { + MemBuffer* const mem = &idec->mem; + const uint8_t* const old_buf = mem->buf; + const uint8_t* const old_start = + (old_buf == NULL) ? NULL : old_buf + mem->start; + assert(old_buf != NULL || mem->start == 0); + assert(mem->mode == MEM_MODE_MAP); + + if (data_size < mem->buf_size) return 0; // can't remap to a shorter buffer! + + mem->buf = (uint8_t*)data; + mem->end = mem->buf_size = data_size; + + DoRemap(idec, mem->buf + mem->start - old_start); + return 1; +} + +static void InitMemBuffer(MemBuffer* const mem) { + mem->mode = MEM_MODE_NONE; + mem->buf = NULL; + mem->buf_size = 0; + mem->part0_buf = NULL; + mem->part0_size = 0; +} + +static void ClearMemBuffer(MemBuffer* const mem) { + assert(mem); + if (mem->mode == MEM_MODE_APPEND) { + WebPSafeFree(mem->buf); + WebPSafeFree((void*)mem->part0_buf); + } +} + +WEBP_NODISCARD static int CheckMemBufferMode(MemBuffer* const mem, + MemBufferMode expected) { + if (mem->mode == MEM_MODE_NONE) { + mem->mode = expected; // switch to the expected mode + } else if (mem->mode != expected) { + return 0; // we mixed the modes => error + } + assert(mem->mode == expected); // mode is ok + return 1; +} + +// To be called last. +WEBP_NODISCARD static VP8StatusCode FinishDecoding(WebPIDecoder* const idec) { + const WebPDecoderOptions* const options = idec->params.options; + WebPDecBuffer* const output = idec->params.output; + + idec->state = STATE_DONE; + if (options != NULL && options->flip) { + const VP8StatusCode status = WebPFlipBuffer(output); + if (status != VP8_STATUS_OK) return status; + } + if (idec->final_output != NULL) { + const VP8StatusCode status = WebPCopyDecBufferPixels( + output, idec->final_output); // do the slow-copy + WebPFreeDecBuffer(&idec->output); + if (status != VP8_STATUS_OK) return status; + *output = *idec->final_output; + idec->final_output = NULL; + } + return VP8_STATUS_OK; +} + +//------------------------------------------------------------------------------ +// Macroblock-decoding contexts + +static void SaveContext(const VP8Decoder* dec, const VP8BitReader* token_br, + MBContext* const context) { + context->left = dec->mb_info[-1]; + context->info = dec->mb_info[dec->mb_x]; + context->token_br = *token_br; +} + +static void RestoreContext(const MBContext* context, VP8Decoder* const dec, + VP8BitReader* const token_br) { + dec->mb_info[-1] = context->left; + dec->mb_info[dec->mb_x] = context->info; + *token_br = context->token_br; +} + +//------------------------------------------------------------------------------ + +static VP8StatusCode IDecError(WebPIDecoder* const idec, VP8StatusCode error) { + if (idec->state == STATE_VP8_DATA) { + // Synchronize the thread, clean-up and check for errors. + (void)VP8ExitCritical((VP8Decoder*)idec->dec, &idec->io); + } + idec->state = STATE_ERROR; + return error; +} + +static void ChangeState(WebPIDecoder* const idec, DecState new_state, + size_t consumed_bytes) { + MemBuffer* const mem = &idec->mem; + idec->state = new_state; + mem->start += consumed_bytes; + assert(mem->start <= mem->end); + idec->io.data = mem->buf + mem->start; + idec->io.data_size = MemDataSize(mem); +} + +// Headers +static VP8StatusCode DecodeWebPHeaders(WebPIDecoder* const idec) { + MemBuffer* const mem = &idec->mem; + const uint8_t* data = mem->buf + mem->start; + size_t curr_size = MemDataSize(mem); + VP8StatusCode status; + WebPHeaderStructure headers; + + headers.data = data; + headers.data_size = curr_size; + headers.have_all_data = 0; + status = WebPParseHeaders(&headers); + if (status == VP8_STATUS_NOT_ENOUGH_DATA) { + return VP8_STATUS_SUSPENDED; // We haven't found a VP8 chunk yet. + } else if (status != VP8_STATUS_OK) { + return IDecError(idec, status); + } + + idec->chunk_size = headers.compressed_size; + idec->is_lossless = headers.is_lossless; + if (!idec->is_lossless) { + VP8Decoder* const dec = VP8New(); + if (dec == NULL) { + return VP8_STATUS_OUT_OF_MEMORY; + } + dec->incremental = 1; + idec->dec = dec; + dec->alpha_data = headers.alpha_data; + dec->alpha_data_size = headers.alpha_data_size; + ChangeState(idec, STATE_VP8_HEADER, headers.offset); + } else { + VP8LDecoder* const dec = VP8LNew(); + if (dec == NULL) { + return VP8_STATUS_OUT_OF_MEMORY; + } + idec->dec = dec; + ChangeState(idec, STATE_VP8L_HEADER, headers.offset); + } + return VP8_STATUS_OK; +} + +static VP8StatusCode DecodeVP8FrameHeader(WebPIDecoder* const idec) { + const uint8_t* data = idec->mem.buf + idec->mem.start; + const size_t curr_size = MemDataSize(&idec->mem); + int width, height; + uint32_t bits; + + if (curr_size < VP8_FRAME_HEADER_SIZE) { + // Not enough data bytes to extract VP8 Frame Header. + return VP8_STATUS_SUSPENDED; + } + if (!VP8GetInfo(data, curr_size, idec->chunk_size, &width, &height)) { + return IDecError(idec, VP8_STATUS_BITSTREAM_ERROR); + } + + bits = data[0] | (data[1] << 8) | (data[2] << 16); + idec->mem.part0_size = (bits >> 5) + VP8_FRAME_HEADER_SIZE; + + idec->io.data = data; + idec->io.data_size = curr_size; + idec->state = STATE_VP8_PARTS0; + return VP8_STATUS_OK; +} + +// Partition #0 +static VP8StatusCode CopyParts0Data(WebPIDecoder* const idec) { + VP8Decoder* const dec = (VP8Decoder*)idec->dec; + VP8BitReader* const br = &dec->br; + const size_t part_size = br->buf_end - br->buf; + MemBuffer* const mem = &idec->mem; + assert(!idec->is_lossless); + assert(mem->part0_buf == NULL); + // the following is a format limitation, no need for runtime check: + assert(part_size <= mem->part0_size); + if (part_size == 0) { // can't have zero-size partition #0 + return VP8_STATUS_BITSTREAM_ERROR; + } + if (mem->mode == MEM_MODE_APPEND) { + // We copy and grab ownership of the partition #0 data. + uint8_t* const part0_buf = (uint8_t*)WebPSafeMalloc(1ULL, part_size); + if (part0_buf == NULL) { + return VP8_STATUS_OUT_OF_MEMORY; + } + memcpy(part0_buf, br->buf, part_size); + mem->part0_buf = part0_buf; + VP8BitReaderSetBuffer(br, part0_buf, part_size); + } else { + // Else: just keep pointers to the partition #0's data in dec->br. + } + mem->start += part_size; + return VP8_STATUS_OK; +} + +static VP8StatusCode DecodePartition0(WebPIDecoder* const idec) { + VP8Decoder* const dec = (VP8Decoder*)idec->dec; + VP8Io* const io = &idec->io; + const WebPDecParams* const params = &idec->params; + WebPDecBuffer* const output = params->output; + + // Wait till we have enough data for the whole partition #0 + if (MemDataSize(&idec->mem) < idec->mem.part0_size) { + return VP8_STATUS_SUSPENDED; + } + + if (!VP8GetHeaders(dec, io)) { + const VP8StatusCode status = dec->status; + if (status == VP8_STATUS_SUSPENDED || + status == VP8_STATUS_NOT_ENOUGH_DATA) { + // treating NOT_ENOUGH_DATA as SUSPENDED state + return VP8_STATUS_SUSPENDED; + } + return IDecError(idec, status); + } + + // Allocate/Verify output buffer now + dec->status = WebPAllocateDecBuffer(io->width, io->height, params->options, + output); + if (dec->status != VP8_STATUS_OK) { + return IDecError(idec, dec->status); + } + // This change must be done before calling VP8InitFrame() + dec->mt_method = VP8GetThreadMethod(params->options, NULL, + io->width, io->height); + VP8InitDithering(params->options, dec); + + dec->status = CopyParts0Data(idec); + if (dec->status != VP8_STATUS_OK) { + return IDecError(idec, dec->status); + } + + // Finish setting up the decoding parameters. Will call io->setup(). + if (VP8EnterCritical(dec, io) != VP8_STATUS_OK) { + return IDecError(idec, dec->status); + } + + // Note: past this point, teardown() must always be called + // in case of error. + idec->state = STATE_VP8_DATA; + // Allocate memory and prepare everything. + if (!VP8InitFrame(dec, io)) { + return IDecError(idec, dec->status); + } + return VP8_STATUS_OK; +} + +// Remaining partitions +static VP8StatusCode DecodeRemaining(WebPIDecoder* const idec) { + VP8Decoder* const dec = (VP8Decoder*)idec->dec; + VP8Io* const io = &idec->io; + + // Make sure partition #0 has been read before, to set dec to ready. + if (!dec->ready) { + return IDecError(idec, VP8_STATUS_BITSTREAM_ERROR); + } + for (; dec->mb_y < dec->mb_h; ++dec->mb_y) { + if (idec->last_mb_y != dec->mb_y) { + if (!VP8ParseIntraModeRow(&dec->br, dec)) { + // note: normally, error shouldn't occur since we already have the whole + // partition0 available here in DecodeRemaining(). Reaching EOF while + // reading intra modes really means a BITSTREAM_ERROR. + return IDecError(idec, VP8_STATUS_BITSTREAM_ERROR); + } + idec->last_mb_y = dec->mb_y; + } + for (; dec->mb_x < dec->mb_w; ++dec->mb_x) { + VP8BitReader* const token_br = + &dec->parts[dec->mb_y & dec->num_parts_minus_one]; + MBContext context; + SaveContext(dec, token_br, &context); + if (!VP8DecodeMB(dec, token_br)) { + // We shouldn't fail when MAX_MB data was available + if (dec->num_parts_minus_one == 0 && + MemDataSize(&idec->mem) > MAX_MB_SIZE) { + return IDecError(idec, VP8_STATUS_BITSTREAM_ERROR); + } + // Synchronize the threads. + if (dec->mt_method > 0) { + if (!WebPGetWorkerInterface()->Sync(&dec->worker)) { + return IDecError(idec, VP8_STATUS_BITSTREAM_ERROR); + } + } + RestoreContext(&context, dec, token_br); + return VP8_STATUS_SUSPENDED; + } + // Release buffer only if there is only one partition + if (dec->num_parts_minus_one == 0) { + idec->mem.start = token_br->buf - idec->mem.buf; + assert(idec->mem.start <= idec->mem.end); + } + } + VP8InitScanline(dec); // Prepare for next scanline + + // Reconstruct, filter and emit the row. + if (!VP8ProcessRow(dec, io)) { + return IDecError(idec, VP8_STATUS_USER_ABORT); + } + } + // Synchronize the thread and check for errors. + if (!VP8ExitCritical(dec, io)) { + idec->state = STATE_ERROR; // prevent re-entry in IDecError + return IDecError(idec, VP8_STATUS_USER_ABORT); + } + dec->ready = 0; + return FinishDecoding(idec); +} + +static VP8StatusCode ErrorStatusLossless(WebPIDecoder* const idec, + VP8StatusCode status) { + if (status == VP8_STATUS_SUSPENDED || status == VP8_STATUS_NOT_ENOUGH_DATA) { + return VP8_STATUS_SUSPENDED; + } + return IDecError(idec, status); +} + +static VP8StatusCode DecodeVP8LHeader(WebPIDecoder* const idec) { + VP8Io* const io = &idec->io; + VP8LDecoder* const dec = (VP8LDecoder*)idec->dec; + const WebPDecParams* const params = &idec->params; + WebPDecBuffer* const output = params->output; + size_t curr_size = MemDataSize(&idec->mem); + assert(idec->is_lossless); + + // Wait until there's enough data for decoding header. + if (curr_size < (idec->chunk_size >> 3)) { + dec->status = VP8_STATUS_SUSPENDED; + return ErrorStatusLossless(idec, dec->status); + } + + if (!VP8LDecodeHeader(dec, io)) { + if (dec->status == VP8_STATUS_BITSTREAM_ERROR && + curr_size < idec->chunk_size) { + dec->status = VP8_STATUS_SUSPENDED; + } + return ErrorStatusLossless(idec, dec->status); + } + // Allocate/verify output buffer now. + dec->status = WebPAllocateDecBuffer(io->width, io->height, params->options, + output); + if (dec->status != VP8_STATUS_OK) { + return IDecError(idec, dec->status); + } + + idec->state = STATE_VP8L_DATA; + return VP8_STATUS_OK; +} + +static VP8StatusCode DecodeVP8LData(WebPIDecoder* const idec) { + VP8LDecoder* const dec = (VP8LDecoder*)idec->dec; + const size_t curr_size = MemDataSize(&idec->mem); + assert(idec->is_lossless); + + // Switch to incremental decoding if we don't have all the bytes available. + dec->incremental = (curr_size < idec->chunk_size); + + if (!VP8LDecodeImage(dec)) { + return ErrorStatusLossless(idec, dec->status); + } + assert(dec->status == VP8_STATUS_OK || dec->status == VP8_STATUS_SUSPENDED); + return (dec->status == VP8_STATUS_SUSPENDED) ? dec->status + : FinishDecoding(idec); +} + + // Main decoding loop +static VP8StatusCode IDecode(WebPIDecoder* idec) { + VP8StatusCode status = VP8_STATUS_SUSPENDED; + + if (idec->state == STATE_WEBP_HEADER) { + status = DecodeWebPHeaders(idec); + } else { + if (idec->dec == NULL) { + return VP8_STATUS_SUSPENDED; // can't continue if we have no decoder. + } + } + if (idec->state == STATE_VP8_HEADER) { + status = DecodeVP8FrameHeader(idec); + } + if (idec->state == STATE_VP8_PARTS0) { + status = DecodePartition0(idec); + } + if (idec->state == STATE_VP8_DATA) { + const VP8Decoder* const dec = (VP8Decoder*)idec->dec; + if (dec == NULL) { + return VP8_STATUS_SUSPENDED; // can't continue if we have no decoder. + } + status = DecodeRemaining(idec); + } + if (idec->state == STATE_VP8L_HEADER) { + status = DecodeVP8LHeader(idec); + } + if (idec->state == STATE_VP8L_DATA) { + status = DecodeVP8LData(idec); + } + return status; +} + +//------------------------------------------------------------------------------ +// Internal constructor + +WEBP_NODISCARD static WebPIDecoder* NewDecoder( + WebPDecBuffer* const output_buffer, + const WebPBitstreamFeatures* const features) { + WebPIDecoder* idec = (WebPIDecoder*)WebPSafeCalloc(1ULL, sizeof(*idec)); + if (idec == NULL) { + return NULL; + } + + idec->state = STATE_WEBP_HEADER; + idec->chunk_size = 0; + + idec->last_mb_y = -1; + + InitMemBuffer(&idec->mem); + if (!WebPInitDecBuffer(&idec->output) || !VP8InitIo(&idec->io)) { + WebPSafeFree(idec); + return NULL; + } + + WebPResetDecParams(&idec->params); + if (output_buffer == NULL || WebPAvoidSlowMemory(output_buffer, features)) { + idec->params.output = &idec->output; + idec->final_output = output_buffer; + if (output_buffer != NULL) { + idec->params.output->colorspace = output_buffer->colorspace; + } + } else { + idec->params.output = output_buffer; + idec->final_output = NULL; + } + WebPInitCustomIo(&idec->params, &idec->io); // Plug the I/O functions. + + return idec; +} + +//------------------------------------------------------------------------------ +// Public functions + +WebPIDecoder* WebPINewDecoder(WebPDecBuffer* output_buffer) { + return NewDecoder(output_buffer, NULL); +} + +WebPIDecoder* WebPIDecode(const uint8_t* data, size_t data_size, + WebPDecoderConfig* config) { + WebPIDecoder* idec; + WebPBitstreamFeatures tmp_features; + WebPBitstreamFeatures* const features = + (config == NULL) ? &tmp_features : &config->input; + memset(&tmp_features, 0, sizeof(tmp_features)); + + // Parse the bitstream's features, if requested: + if (data != NULL && data_size > 0) { + if (WebPGetFeatures(data, data_size, features) != VP8_STATUS_OK) { + return NULL; + } + } + + // Create an instance of the incremental decoder + idec = (config != NULL) ? NewDecoder(&config->output, features) + : NewDecoder(NULL, features); + if (idec == NULL) { + return NULL; + } + // Finish initialization + if (config != NULL) { + idec->params.options = &config->options; + } + return idec; +} + +void WebPIDelete(WebPIDecoder* idec) { + if (idec == NULL) return; + if (idec->dec != NULL) { + if (!idec->is_lossless) { + if (idec->state == STATE_VP8_DATA) { + // Synchronize the thread, clean-up and check for errors. + // TODO(vrabaud) do we care about the return result? + (void)VP8ExitCritical((VP8Decoder*)idec->dec, &idec->io); + } + VP8Delete((VP8Decoder*)idec->dec); + } else { + VP8LDelete((VP8LDecoder*)idec->dec); + } + } + ClearMemBuffer(&idec->mem); + WebPFreeDecBuffer(&idec->output); + WebPSafeFree(idec); +} + +//------------------------------------------------------------------------------ +// Wrapper toward WebPINewDecoder + +WebPIDecoder* WebPINewRGB(WEBP_CSP_MODE csp, uint8_t* output_buffer, + size_t output_buffer_size, int output_stride) { + const int is_external_memory = (output_buffer != NULL) ? 1 : 0; + WebPIDecoder* idec; + + if (csp >= MODE_YUV) return NULL; + if (is_external_memory == 0) { // Overwrite parameters to sane values. + output_buffer_size = 0; + output_stride = 0; + } else { // A buffer was passed. Validate the other params. + if (output_stride == 0 || output_buffer_size == 0) { + return NULL; // invalid parameter. + } + } + idec = WebPINewDecoder(NULL); + if (idec == NULL) return NULL; + idec->output.colorspace = csp; + idec->output.is_external_memory = is_external_memory; + idec->output.u.RGBA.rgba = output_buffer; + idec->output.u.RGBA.stride = output_stride; + idec->output.u.RGBA.size = output_buffer_size; + return idec; +} + +WebPIDecoder* WebPINewYUVA(uint8_t* luma, size_t luma_size, int luma_stride, + uint8_t* u, size_t u_size, int u_stride, + uint8_t* v, size_t v_size, int v_stride, + uint8_t* a, size_t a_size, int a_stride) { + const int is_external_memory = (luma != NULL) ? 1 : 0; + WebPIDecoder* idec; + WEBP_CSP_MODE colorspace; + + if (is_external_memory == 0) { // Overwrite parameters to sane values. + luma_size = u_size = v_size = a_size = 0; + luma_stride = u_stride = v_stride = a_stride = 0; + u = v = a = NULL; + colorspace = MODE_YUVA; + } else { // A luma buffer was passed. Validate the other parameters. + if (u == NULL || v == NULL) return NULL; + if (luma_size == 0 || u_size == 0 || v_size == 0) return NULL; + if (luma_stride == 0 || u_stride == 0 || v_stride == 0) return NULL; + if (a != NULL) { + if (a_size == 0 || a_stride == 0) return NULL; + } + colorspace = (a == NULL) ? MODE_YUV : MODE_YUVA; + } + + idec = WebPINewDecoder(NULL); + if (idec == NULL) return NULL; + + idec->output.colorspace = colorspace; + idec->output.is_external_memory = is_external_memory; + idec->output.u.YUVA.y = luma; + idec->output.u.YUVA.y_stride = luma_stride; + idec->output.u.YUVA.y_size = luma_size; + idec->output.u.YUVA.u = u; + idec->output.u.YUVA.u_stride = u_stride; + idec->output.u.YUVA.u_size = u_size; + idec->output.u.YUVA.v = v; + idec->output.u.YUVA.v_stride = v_stride; + idec->output.u.YUVA.v_size = v_size; + idec->output.u.YUVA.a = a; + idec->output.u.YUVA.a_stride = a_stride; + idec->output.u.YUVA.a_size = a_size; + return idec; +} + +WebPIDecoder* WebPINewYUV(uint8_t* luma, size_t luma_size, int luma_stride, + uint8_t* u, size_t u_size, int u_stride, + uint8_t* v, size_t v_size, int v_stride) { + return WebPINewYUVA(luma, luma_size, luma_stride, + u, u_size, u_stride, + v, v_size, v_stride, + NULL, 0, 0); +} + +//------------------------------------------------------------------------------ + +static VP8StatusCode IDecCheckStatus(const WebPIDecoder* const idec) { + assert(idec); + if (idec->state == STATE_ERROR) { + return VP8_STATUS_BITSTREAM_ERROR; + } + if (idec->state == STATE_DONE) { + return VP8_STATUS_OK; + } + return VP8_STATUS_SUSPENDED; +} + +VP8StatusCode WebPIAppend(WebPIDecoder* idec, + const uint8_t* data, size_t data_size) { + VP8StatusCode status; + if (idec == NULL || data == NULL) { + return VP8_STATUS_INVALID_PARAM; + } + status = IDecCheckStatus(idec); + if (status != VP8_STATUS_SUSPENDED) { + return status; + } + // Check mixed calls between RemapMemBuffer and AppendToMemBuffer. + if (!CheckMemBufferMode(&idec->mem, MEM_MODE_APPEND)) { + return VP8_STATUS_INVALID_PARAM; + } + // Append data to memory buffer + if (!AppendToMemBuffer(idec, data, data_size)) { + return VP8_STATUS_OUT_OF_MEMORY; + } + return IDecode(idec); +} + +VP8StatusCode WebPIUpdate(WebPIDecoder* idec, + const uint8_t* data, size_t data_size) { + VP8StatusCode status; + if (idec == NULL || data == NULL) { + return VP8_STATUS_INVALID_PARAM; + } + status = IDecCheckStatus(idec); + if (status != VP8_STATUS_SUSPENDED) { + return status; + } + // Check mixed calls between RemapMemBuffer and AppendToMemBuffer. + if (!CheckMemBufferMode(&idec->mem, MEM_MODE_MAP)) { + return VP8_STATUS_INVALID_PARAM; + } + // Make the memory buffer point to the new buffer + if (!RemapMemBuffer(idec, data, data_size)) { + return VP8_STATUS_INVALID_PARAM; + } + return IDecode(idec); +} + +//------------------------------------------------------------------------------ + +static const WebPDecBuffer* GetOutputBuffer(const WebPIDecoder* const idec) { + if (idec == NULL || idec->dec == NULL) { + return NULL; + } + if (idec->state <= STATE_VP8_PARTS0) { + return NULL; + } + if (idec->final_output != NULL) { + return NULL; // not yet slow-copied + } + return idec->params.output; +} + +const WebPDecBuffer* WebPIDecodedArea(const WebPIDecoder* idec, + int* left, int* top, + int* width, int* height) { + const WebPDecBuffer* const src = GetOutputBuffer(idec); + if (left != NULL) *left = 0; + if (top != NULL) *top = 0; + if (src != NULL) { + if (width != NULL) *width = src->width; + if (height != NULL) *height = idec->params.last_y; + } else { + if (width != NULL) *width = 0; + if (height != NULL) *height = 0; + } + return src; +} + +WEBP_NODISCARD uint8_t* WebPIDecGetRGB(const WebPIDecoder* idec, int* last_y, + int* width, int* height, int* stride) { + const WebPDecBuffer* const src = GetOutputBuffer(idec); + if (src == NULL) return NULL; + if (src->colorspace >= MODE_YUV) { + return NULL; + } + + if (last_y != NULL) *last_y = idec->params.last_y; + if (width != NULL) *width = src->width; + if (height != NULL) *height = src->height; + if (stride != NULL) *stride = src->u.RGBA.stride; + + return src->u.RGBA.rgba; +} + +WEBP_NODISCARD uint8_t* WebPIDecGetYUVA(const WebPIDecoder* idec, int* last_y, + uint8_t** u, uint8_t** v, uint8_t** a, + int* width, int* height, int* stride, + int* uv_stride, int* a_stride) { + const WebPDecBuffer* const src = GetOutputBuffer(idec); + if (src == NULL) return NULL; + if (src->colorspace < MODE_YUV) { + return NULL; + } + + if (last_y != NULL) *last_y = idec->params.last_y; + if (u != NULL) *u = src->u.YUVA.u; + if (v != NULL) *v = src->u.YUVA.v; + if (a != NULL) *a = src->u.YUVA.a; + if (width != NULL) *width = src->width; + if (height != NULL) *height = src->height; + if (stride != NULL) *stride = src->u.YUVA.y_stride; + if (uv_stride != NULL) *uv_stride = src->u.YUVA.u_stride; + if (a_stride != NULL) *a_stride = src->u.YUVA.a_stride; + + return src->u.YUVA.y; +} + +int WebPISetIOHooks(WebPIDecoder* const idec, + VP8IoPutHook put, + VP8IoSetupHook setup, + VP8IoTeardownHook teardown, + void* user_data) { + if (idec == NULL || idec->state > STATE_WEBP_HEADER) { + return 0; + } + + idec->io.put = put; + idec->io.setup = setup; + idec->io.teardown = teardown; + idec->io.opaque = user_data; + + return 1; +} diff --git a/packages/core/src/zig/vendor/libwebp/src/dec/io_dec.c b/packages/core/src/zig/vendor/libwebp/src/dec/io_dec.c new file mode 100644 index 0000000000..b6e720ede6 --- /dev/null +++ b/packages/core/src/zig/vendor/libwebp/src/dec/io_dec.c @@ -0,0 +1,670 @@ +// Copyright 2011 Google Inc. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the COPYING file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +// ----------------------------------------------------------------------------- +// +// functions for sample output. +// +// Author: Skal (pascal.massimino@gmail.com) + +#include +#include +#include +#include + +#include "src/dec/vp8_dec.h" +#include "src/webp/types.h" +#include "src/dec/vp8i_dec.h" +#include "src/dec/webpi_dec.h" +#include "src/dsp/cpu.h" +#include "src/dsp/dsp.h" +#include "src/dsp/yuv.h" +#include "src/utils/rescaler_utils.h" +#include "src/utils/utils.h" +#include "src/webp/decode.h" + +//------------------------------------------------------------------------------ +// Main YUV<->RGB conversion functions + +static int EmitYUV(const VP8Io* const io, WebPDecParams* const p) { + WebPDecBuffer* output = p->output; + const WebPYUVABuffer* const buf = &output->u.YUVA; + uint8_t* const y_dst = buf->y + (ptrdiff_t)io->mb_y * buf->y_stride; + uint8_t* const u_dst = buf->u + (ptrdiff_t)(io->mb_y >> 1) * buf->u_stride; + uint8_t* const v_dst = buf->v + (ptrdiff_t)(io->mb_y >> 1) * buf->v_stride; + const int mb_w = io->mb_w; + const int mb_h = io->mb_h; + const int uv_w = (mb_w + 1) / 2; + const int uv_h = (mb_h + 1) / 2; + WebPCopyPlane(io->y, io->y_stride, y_dst, buf->y_stride, mb_w, mb_h); + WebPCopyPlane(io->u, io->uv_stride, u_dst, buf->u_stride, uv_w, uv_h); + WebPCopyPlane(io->v, io->uv_stride, v_dst, buf->v_stride, uv_w, uv_h); + return io->mb_h; +} + +// Point-sampling U/V sampler. +static int EmitSampledRGB(const VP8Io* const io, WebPDecParams* const p) { + WebPDecBuffer* const output = p->output; + WebPRGBABuffer* const buf = &output->u.RGBA; + uint8_t* const dst = buf->rgba + (ptrdiff_t)io->mb_y * buf->stride; + WebPSamplerProcessPlane(io->y, io->y_stride, + io->u, io->v, io->uv_stride, + dst, buf->stride, io->mb_w, io->mb_h, + WebPSamplers[output->colorspace]); + return io->mb_h; +} + +//------------------------------------------------------------------------------ +// Fancy upsampling + +#ifdef FANCY_UPSAMPLING +static int EmitFancyRGB(const VP8Io* const io, WebPDecParams* const p) { + int num_lines_out = io->mb_h; // a priori guess + const WebPRGBABuffer* const buf = &p->output->u.RGBA; + uint8_t* dst = buf->rgba + (ptrdiff_t)io->mb_y * buf->stride; + WebPUpsampleLinePairFunc upsample = WebPUpsamplers[p->output->colorspace]; + const uint8_t* cur_y = io->y; + const uint8_t* cur_u = io->u; + const uint8_t* cur_v = io->v; + const uint8_t* top_u = p->tmp_u; + const uint8_t* top_v = p->tmp_v; + int y = io->mb_y; + const int y_end = io->mb_y + io->mb_h; + const int mb_w = io->mb_w; + const int uv_w = (mb_w + 1) / 2; + + if (y == 0) { + // First line is special cased. We mirror the u/v samples at boundary. + upsample(cur_y, NULL, cur_u, cur_v, cur_u, cur_v, dst, NULL, mb_w); + } else { + // We can finish the left-over line from previous call. + upsample(p->tmp_y, cur_y, top_u, top_v, cur_u, cur_v, + dst - buf->stride, dst, mb_w); + ++num_lines_out; + } + // Loop over each output pairs of row. + for (; y + 2 < y_end; y += 2) { + top_u = cur_u; + top_v = cur_v; + cur_u += io->uv_stride; + cur_v += io->uv_stride; + dst += 2 * buf->stride; + cur_y += 2 * io->y_stride; + upsample(cur_y - io->y_stride, cur_y, + top_u, top_v, cur_u, cur_v, + dst - buf->stride, dst, mb_w); + } + // move to last row + cur_y += io->y_stride; + if (io->crop_top + y_end < io->crop_bottom) { + // Save the unfinished samples for next call (as we're not done yet). + memcpy(p->tmp_y, cur_y, mb_w * sizeof(*p->tmp_y)); + memcpy(p->tmp_u, cur_u, uv_w * sizeof(*p->tmp_u)); + memcpy(p->tmp_v, cur_v, uv_w * sizeof(*p->tmp_v)); + // The fancy upsampler leaves a row unfinished behind + // (except for the very last row) + num_lines_out--; + } else { + // Process the very last row of even-sized picture + if (!(y_end & 1)) { + upsample(cur_y, NULL, cur_u, cur_v, cur_u, cur_v, + dst + buf->stride, NULL, mb_w); + } + } + return num_lines_out; +} + +#endif /* FANCY_UPSAMPLING */ + +//------------------------------------------------------------------------------ + +static void FillAlphaPlane(uint8_t* dst, int w, int h, int stride) { + int j; + for (j = 0; j < h; ++j) { + memset(dst, 0xff, w * sizeof(*dst)); + dst += stride; + } +} + +static int EmitAlphaYUV(const VP8Io* const io, WebPDecParams* const p, + int expected_num_lines_out) { + const uint8_t* alpha = io->a; + const WebPYUVABuffer* const buf = &p->output->u.YUVA; + const int mb_w = io->mb_w; + const int mb_h = io->mb_h; + uint8_t* dst = buf->a + (ptrdiff_t)io->mb_y * buf->a_stride; + int j; + (void)expected_num_lines_out; + assert(expected_num_lines_out == mb_h); + if (alpha != NULL) { + for (j = 0; j < mb_h; ++j) { + memcpy(dst, alpha, mb_w * sizeof(*dst)); + alpha += io->width; + dst += buf->a_stride; + } + } else if (buf->a != NULL) { + // the user requested alpha, but there is none, set it to opaque. + FillAlphaPlane(dst, mb_w, mb_h, buf->a_stride); + } + return 0; +} + +static int GetAlphaSourceRow(const VP8Io* const io, + const uint8_t** alpha, int* const num_rows) { + int start_y = io->mb_y; + *num_rows = io->mb_h; + + // Compensate for the 1-line delay of the fancy upscaler. + // This is similar to EmitFancyRGB(). + if (io->fancy_upsampling) { + if (start_y == 0) { + // We don't process the last row yet. It'll be done during the next call. + --*num_rows; + } else { + --start_y; + // Fortunately, *alpha data is persistent, so we can go back + // one row and finish alpha blending, now that the fancy upscaler + // completed the YUV->RGB interpolation. + *alpha -= io->width; + } + if (io->crop_top + io->mb_y + io->mb_h == io->crop_bottom) { + // If it's the very last call, we process all the remaining rows! + *num_rows = io->crop_bottom - io->crop_top - start_y; + } + } + return start_y; +} + +static int EmitAlphaRGB(const VP8Io* const io, WebPDecParams* const p, + int expected_num_lines_out) { + const uint8_t* alpha = io->a; + if (alpha != NULL) { + const int mb_w = io->mb_w; + const WEBP_CSP_MODE colorspace = p->output->colorspace; + const int alpha_first = + (colorspace == MODE_ARGB || colorspace == MODE_Argb); + const WebPRGBABuffer* const buf = &p->output->u.RGBA; + int num_rows; + const int start_y = GetAlphaSourceRow(io, &alpha, &num_rows); + uint8_t* const base_rgba = buf->rgba + (ptrdiff_t)start_y * buf->stride; + uint8_t* const dst = base_rgba + (alpha_first ? 0 : 3); + const int has_alpha = WebPDispatchAlpha(alpha, io->width, mb_w, + num_rows, dst, buf->stride); + (void)expected_num_lines_out; + assert(expected_num_lines_out == num_rows); + // has_alpha is true if there's non-trivial alpha to premultiply with. + if (has_alpha && WebPIsPremultipliedMode(colorspace)) { + WebPApplyAlphaMultiply(base_rgba, alpha_first, + mb_w, num_rows, buf->stride); + } + } + return 0; +} + +static int EmitAlphaRGBA4444(const VP8Io* const io, WebPDecParams* const p, + int expected_num_lines_out) { + const uint8_t* alpha = io->a; + if (alpha != NULL) { + const int mb_w = io->mb_w; + const WEBP_CSP_MODE colorspace = p->output->colorspace; + const WebPRGBABuffer* const buf = &p->output->u.RGBA; + int num_rows; + const int start_y = GetAlphaSourceRow(io, &alpha, &num_rows); + uint8_t* const base_rgba = buf->rgba + (ptrdiff_t)start_y * buf->stride; +#if (WEBP_SWAP_16BIT_CSP == 1) + uint8_t* alpha_dst = base_rgba; +#else + uint8_t* alpha_dst = base_rgba + 1; +#endif + uint32_t alpha_mask = 0x0f; + int i, j; + for (j = 0; j < num_rows; ++j) { + for (i = 0; i < mb_w; ++i) { + // Fill in the alpha value (converted to 4 bits). + const uint32_t alpha_value = alpha[i] >> 4; + alpha_dst[2 * i] = (alpha_dst[2 * i] & 0xf0) | alpha_value; + alpha_mask &= alpha_value; + } + alpha += io->width; + alpha_dst += buf->stride; + } + (void)expected_num_lines_out; + assert(expected_num_lines_out == num_rows); + if (alpha_mask != 0x0f && WebPIsPremultipliedMode(colorspace)) { + WebPApplyAlphaMultiply4444(base_rgba, mb_w, num_rows, buf->stride); + } + } + return 0; +} + +//------------------------------------------------------------------------------ +// YUV rescaling (no final RGB conversion needed) + +#if !defined(WEBP_REDUCE_SIZE) +static int Rescale(const uint8_t* src, int src_stride, + int new_lines, WebPRescaler* const wrk) { + int num_lines_out = 0; + while (new_lines > 0) { // import new contributions of source rows. + const int lines_in = WebPRescalerImport(wrk, new_lines, src, src_stride); + src += lines_in * src_stride; + new_lines -= lines_in; + num_lines_out += WebPRescalerExport(wrk); // emit output row(s) + } + return num_lines_out; +} + +static int EmitRescaledYUV(const VP8Io* const io, WebPDecParams* const p) { + const int mb_h = io->mb_h; + const int uv_mb_h = (mb_h + 1) >> 1; + WebPRescaler* const scaler = p->scaler_y; + int num_lines_out = 0; + if (WebPIsAlphaMode(p->output->colorspace) && io->a != NULL) { + // Before rescaling, we premultiply the luma directly into the io->y + // internal buffer. This is OK since these samples are not used for + // intra-prediction (the top samples are saved in cache_y/u/v). + // But we need to cast the const away, though. + WebPMultRows((uint8_t*)io->y, io->y_stride, + io->a, io->width, io->mb_w, mb_h, 0); + } + num_lines_out = Rescale(io->y, io->y_stride, mb_h, scaler); + Rescale(io->u, io->uv_stride, uv_mb_h, p->scaler_u); + Rescale(io->v, io->uv_stride, uv_mb_h, p->scaler_v); + return num_lines_out; +} + +static int EmitRescaledAlphaYUV(const VP8Io* const io, WebPDecParams* const p, + int expected_num_lines_out) { + const WebPYUVABuffer* const buf = &p->output->u.YUVA; + uint8_t* const dst_a = buf->a + (ptrdiff_t)p->last_y * buf->a_stride; + if (io->a != NULL) { + uint8_t* const dst_y = buf->y + (ptrdiff_t)p->last_y * buf->y_stride; + const int num_lines_out = Rescale(io->a, io->width, io->mb_h, p->scaler_a); + assert(expected_num_lines_out == num_lines_out); + if (num_lines_out > 0) { // unmultiply the Y + WebPMultRows(dst_y, buf->y_stride, dst_a, buf->a_stride, + p->scaler_a->dst_width, num_lines_out, 1); + } + } else if (buf->a != NULL) { + // the user requested alpha, but there is none, set it to opaque. + assert(p->last_y + expected_num_lines_out <= io->scaled_height); + FillAlphaPlane(dst_a, io->scaled_width, expected_num_lines_out, + buf->a_stride); + } + return 0; +} + +static int InitYUVRescaler(const VP8Io* const io, WebPDecParams* const p) { + const int has_alpha = WebPIsAlphaMode(p->output->colorspace); + const WebPYUVABuffer* const buf = &p->output->u.YUVA; + const int out_width = io->scaled_width; + const int out_height = io->scaled_height; + const int uv_out_width = (out_width + 1) >> 1; + const int uv_out_height = (out_height + 1) >> 1; + const int uv_in_width = (io->mb_w + 1) >> 1; + const int uv_in_height = (io->mb_h + 1) >> 1; + // scratch memory for luma rescaler + const size_t work_size = 2 * (size_t)out_width; + const size_t uv_work_size = 2 * uv_out_width; // and for each u/v ones + uint64_t total_size; + size_t rescaler_size; + rescaler_t* work; + WebPRescaler* scalers; + const int num_rescalers = has_alpha ? 4 : 3; + + total_size = ((uint64_t)work_size + 2 * uv_work_size) * sizeof(*work); + if (has_alpha) { + total_size += (uint64_t)work_size * sizeof(*work); + } + rescaler_size = num_rescalers * sizeof(*p->scaler_y) + WEBP_ALIGN_CST; + total_size += rescaler_size; + if (!CheckSizeOverflow(total_size)) { + return 0; + } + + p->memory = WebPSafeMalloc(1ULL, (size_t)total_size); + if (p->memory == NULL) { + return 0; // memory error + } + work = (rescaler_t*)p->memory; + + scalers = (WebPRescaler*)WEBP_ALIGN( + (const uint8_t*)work + total_size - rescaler_size); + p->scaler_y = &scalers[0]; + p->scaler_u = &scalers[1]; + p->scaler_v = &scalers[2]; + p->scaler_a = has_alpha ? &scalers[3] : NULL; + + if (!WebPRescalerInit(p->scaler_y, io->mb_w, io->mb_h, + buf->y, out_width, out_height, buf->y_stride, 1, + work) || + !WebPRescalerInit(p->scaler_u, uv_in_width, uv_in_height, + buf->u, uv_out_width, uv_out_height, buf->u_stride, 1, + work + work_size) || + !WebPRescalerInit(p->scaler_v, uv_in_width, uv_in_height, + buf->v, uv_out_width, uv_out_height, buf->v_stride, 1, + work + work_size + uv_work_size)) { + return 0; + } + p->emit = EmitRescaledYUV; + + if (has_alpha) { + if (!WebPRescalerInit(p->scaler_a, io->mb_w, io->mb_h, + buf->a, out_width, out_height, buf->a_stride, 1, + work + work_size + 2 * uv_work_size)) { + return 0; + } + p->emit_alpha = EmitRescaledAlphaYUV; + WebPInitAlphaProcessing(); + } + return 1; +} + +//------------------------------------------------------------------------------ +// RGBA rescaling + +static int ExportRGB(WebPDecParams* const p, int y_pos) { + const WebPYUV444Converter convert = + WebPYUV444Converters[p->output->colorspace]; + const WebPRGBABuffer* const buf = &p->output->u.RGBA; + uint8_t* dst = buf->rgba + (ptrdiff_t)y_pos * buf->stride; + int num_lines_out = 0; + // For RGB rescaling, because of the YUV420, current scan position + // U/V can be +1/-1 line from the Y one. Hence the double test. + while (WebPRescalerHasPendingOutput(p->scaler_y) && + WebPRescalerHasPendingOutput(p->scaler_u)) { + assert(y_pos + num_lines_out < p->output->height); + assert(p->scaler_u->y_accum == p->scaler_v->y_accum); + WebPRescalerExportRow(p->scaler_y); + WebPRescalerExportRow(p->scaler_u); + WebPRescalerExportRow(p->scaler_v); + convert(p->scaler_y->dst, p->scaler_u->dst, p->scaler_v->dst, + dst, p->scaler_y->dst_width); + dst += buf->stride; + ++num_lines_out; + } + return num_lines_out; +} + +static int EmitRescaledRGB(const VP8Io* const io, WebPDecParams* const p) { + const int mb_h = io->mb_h; + const int uv_mb_h = (mb_h + 1) >> 1; + int j = 0, uv_j = 0; + int num_lines_out = 0; + while (j < mb_h) { + const int y_lines_in = + WebPRescalerImport(p->scaler_y, mb_h - j, + io->y + (ptrdiff_t)j * io->y_stride, io->y_stride); + j += y_lines_in; + if (WebPRescaleNeededLines(p->scaler_u, uv_mb_h - uv_j)) { + const int u_lines_in = WebPRescalerImport( + p->scaler_u, uv_mb_h - uv_j, io->u + (ptrdiff_t)uv_j * io->uv_stride, + io->uv_stride); + const int v_lines_in = WebPRescalerImport( + p->scaler_v, uv_mb_h - uv_j, io->v + (ptrdiff_t)uv_j * io->uv_stride, + io->uv_stride); + (void)v_lines_in; // remove a gcc warning + assert(u_lines_in == v_lines_in); + uv_j += u_lines_in; + } + num_lines_out += ExportRGB(p, p->last_y + num_lines_out); + } + return num_lines_out; +} + +static int ExportAlpha(WebPDecParams* const p, int y_pos, int max_lines_out) { + const WebPRGBABuffer* const buf = &p->output->u.RGBA; + uint8_t* const base_rgba = buf->rgba + (ptrdiff_t)y_pos * buf->stride; + const WEBP_CSP_MODE colorspace = p->output->colorspace; + const int alpha_first = + (colorspace == MODE_ARGB || colorspace == MODE_Argb); + uint8_t* dst = base_rgba + (alpha_first ? 0 : 3); + int num_lines_out = 0; + const int is_premult_alpha = WebPIsPremultipliedMode(colorspace); + uint32_t non_opaque = 0; + const int width = p->scaler_a->dst_width; + + while (WebPRescalerHasPendingOutput(p->scaler_a) && + num_lines_out < max_lines_out) { + assert(y_pos + num_lines_out < p->output->height); + WebPRescalerExportRow(p->scaler_a); + non_opaque |= WebPDispatchAlpha(p->scaler_a->dst, 0, width, 1, dst, 0); + dst += buf->stride; + ++num_lines_out; + } + if (is_premult_alpha && non_opaque) { + WebPApplyAlphaMultiply(base_rgba, alpha_first, + width, num_lines_out, buf->stride); + } + return num_lines_out; +} + +static int ExportAlphaRGBA4444(WebPDecParams* const p, int y_pos, + int max_lines_out) { + const WebPRGBABuffer* const buf = &p->output->u.RGBA; + uint8_t* const base_rgba = buf->rgba + (ptrdiff_t)y_pos * buf->stride; +#if (WEBP_SWAP_16BIT_CSP == 1) + uint8_t* alpha_dst = base_rgba; +#else + uint8_t* alpha_dst = base_rgba + 1; +#endif + int num_lines_out = 0; + const WEBP_CSP_MODE colorspace = p->output->colorspace; + const int width = p->scaler_a->dst_width; + const int is_premult_alpha = WebPIsPremultipliedMode(colorspace); + uint32_t alpha_mask = 0x0f; + + while (WebPRescalerHasPendingOutput(p->scaler_a) && + num_lines_out < max_lines_out) { + int i; + assert(y_pos + num_lines_out < p->output->height); + WebPRescalerExportRow(p->scaler_a); + for (i = 0; i < width; ++i) { + // Fill in the alpha value (converted to 4 bits). + const uint32_t alpha_value = p->scaler_a->dst[i] >> 4; + alpha_dst[2 * i] = (alpha_dst[2 * i] & 0xf0) | alpha_value; + alpha_mask &= alpha_value; + } + alpha_dst += buf->stride; + ++num_lines_out; + } + if (is_premult_alpha && alpha_mask != 0x0f) { + WebPApplyAlphaMultiply4444(base_rgba, width, num_lines_out, buf->stride); + } + return num_lines_out; +} + +static int EmitRescaledAlphaRGB(const VP8Io* const io, WebPDecParams* const p, + int expected_num_out_lines) { + if (io->a != NULL) { + WebPRescaler* const scaler = p->scaler_a; + int lines_left = expected_num_out_lines; + const int y_end = p->last_y + lines_left; + while (lines_left > 0) { + const int64_t row_offset = (ptrdiff_t)scaler->src_y - io->mb_y; + WebPRescalerImport(scaler, io->mb_h + io->mb_y - scaler->src_y, + io->a + row_offset * io->width, io->width); + lines_left -= p->emit_alpha_row(p, y_end - lines_left, lines_left); + } + } + return 0; +} + +static int InitRGBRescaler(const VP8Io* const io, WebPDecParams* const p) { + const int has_alpha = WebPIsAlphaMode(p->output->colorspace); + const int out_width = io->scaled_width; + const int out_height = io->scaled_height; + const int uv_in_width = (io->mb_w + 1) >> 1; + const int uv_in_height = (io->mb_h + 1) >> 1; + // scratch memory for one rescaler + const size_t work_size = 2 * (size_t)out_width; + rescaler_t* work; // rescalers work area + uint8_t* tmp; // tmp storage for scaled YUV444 samples before RGB conversion + uint64_t tmp_size1, tmp_size2, total_size; + size_t rescaler_size; + WebPRescaler* scalers; + const int num_rescalers = has_alpha ? 4 : 3; + + tmp_size1 = (uint64_t)num_rescalers * work_size; + tmp_size2 = (uint64_t)num_rescalers * out_width; + total_size = tmp_size1 * sizeof(*work) + tmp_size2 * sizeof(*tmp); + rescaler_size = num_rescalers * sizeof(*p->scaler_y) + WEBP_ALIGN_CST; + total_size += rescaler_size; + if (!CheckSizeOverflow(total_size)) { + return 0; + } + + p->memory = WebPSafeMalloc(1ULL, (size_t)total_size); + if (p->memory == NULL) { + return 0; // memory error + } + work = (rescaler_t*)p->memory; + tmp = (uint8_t*)(work + tmp_size1); + + scalers = (WebPRescaler*)WEBP_ALIGN( + (const uint8_t*)work + total_size - rescaler_size); + p->scaler_y = &scalers[0]; + p->scaler_u = &scalers[1]; + p->scaler_v = &scalers[2]; + p->scaler_a = has_alpha ? &scalers[3] : NULL; + + if (!WebPRescalerInit(p->scaler_y, io->mb_w, io->mb_h, + tmp + 0 * out_width, out_width, out_height, 0, 1, + work + 0 * work_size) || + !WebPRescalerInit(p->scaler_u, uv_in_width, uv_in_height, + tmp + 1 * out_width, out_width, out_height, 0, 1, + work + 1 * work_size) || + !WebPRescalerInit(p->scaler_v, uv_in_width, uv_in_height, + tmp + 2 * out_width, out_width, out_height, 0, 1, + work + 2 * work_size)) { + return 0; + } + p->emit = EmitRescaledRGB; + WebPInitYUV444Converters(); + + if (has_alpha) { + if (!WebPRescalerInit(p->scaler_a, io->mb_w, io->mb_h, + tmp + 3 * out_width, out_width, out_height, 0, 1, + work + 3 * work_size)) { + return 0; + } + p->emit_alpha = EmitRescaledAlphaRGB; + if (p->output->colorspace == MODE_RGBA_4444 || + p->output->colorspace == MODE_rgbA_4444) { + p->emit_alpha_row = ExportAlphaRGBA4444; + } else { + p->emit_alpha_row = ExportAlpha; + } + WebPInitAlphaProcessing(); + } + return 1; +} + +#endif // WEBP_REDUCE_SIZE + +//------------------------------------------------------------------------------ +// Default custom functions + +static int CustomSetup(VP8Io* io) { + WebPDecParams* const p = (WebPDecParams*)io->opaque; + const WEBP_CSP_MODE colorspace = p->output->colorspace; + const int is_rgb = WebPIsRGBMode(colorspace); + const int is_alpha = WebPIsAlphaMode(colorspace); + + p->memory = NULL; + p->emit = NULL; + p->emit_alpha = NULL; + p->emit_alpha_row = NULL; + if (!WebPIoInitFromOptions(p->options, io, is_alpha ? MODE_YUV : MODE_YUVA)) { + return 0; + } + if (is_alpha && WebPIsPremultipliedMode(colorspace)) { + WebPInitUpsamplers(); + } + if (io->use_scaling) { +#if !defined(WEBP_REDUCE_SIZE) + const int ok = is_rgb ? InitRGBRescaler(io, p) : InitYUVRescaler(io, p); + if (!ok) { + return 0; // memory error + } +#else + return 0; // rescaling support not compiled +#endif + } else { + if (is_rgb) { + WebPInitSamplers(); + p->emit = EmitSampledRGB; // default + if (io->fancy_upsampling) { +#ifdef FANCY_UPSAMPLING + const int uv_width = (io->mb_w + 1) >> 1; + p->memory = WebPSafeMalloc(1ULL, (size_t)(io->mb_w + 2 * uv_width)); + if (p->memory == NULL) { + return 0; // memory error. + } + p->tmp_y = (uint8_t*)p->memory; + p->tmp_u = p->tmp_y + io->mb_w; + p->tmp_v = p->tmp_u + uv_width; + p->emit = EmitFancyRGB; + WebPInitUpsamplers(); +#endif + } + } else { + p->emit = EmitYUV; + } + if (is_alpha) { // need transparency output + p->emit_alpha = + (colorspace == MODE_RGBA_4444 || colorspace == MODE_rgbA_4444) ? + EmitAlphaRGBA4444 + : is_rgb ? EmitAlphaRGB + : EmitAlphaYUV; + if (is_rgb) { + WebPInitAlphaProcessing(); + } + } + } + + return 1; +} + +//------------------------------------------------------------------------------ + +static int CustomPut(const VP8Io* io) { + WebPDecParams* const p = (WebPDecParams*)io->opaque; + const int mb_w = io->mb_w; + const int mb_h = io->mb_h; + int num_lines_out; + assert(!(io->mb_y & 1)); + + if (mb_w <= 0 || mb_h <= 0) { + return 0; + } + num_lines_out = p->emit(io, p); + if (p->emit_alpha != NULL) { + p->emit_alpha(io, p, num_lines_out); + } + p->last_y += num_lines_out; + return 1; +} + +//------------------------------------------------------------------------------ + +static void CustomTeardown(const VP8Io* io) { + WebPDecParams* const p = (WebPDecParams*)io->opaque; + WebPSafeFree(p->memory); + p->memory = NULL; +} + +//------------------------------------------------------------------------------ +// Main entry point + +void WebPInitCustomIo(WebPDecParams* const params, VP8Io* const io) { + io->put = CustomPut; + io->setup = CustomSetup; + io->teardown = CustomTeardown; + io->opaque = params; +} + +//------------------------------------------------------------------------------ diff --git a/packages/core/src/zig/vendor/libwebp/src/dec/quant_dec.c b/packages/core/src/zig/vendor/libwebp/src/dec/quant_dec.c new file mode 100644 index 0000000000..977bec56cd --- /dev/null +++ b/packages/core/src/zig/vendor/libwebp/src/dec/quant_dec.c @@ -0,0 +1,118 @@ +// Copyright 2010 Google Inc. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the COPYING file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +// ----------------------------------------------------------------------------- +// +// Quantizer initialization +// +// Author: Skal (pascal.massimino@gmail.com) + +#include "src/dec/common_dec.h" +#include "src/dec/vp8_dec.h" +#include "src/dec/vp8i_dec.h" +#include "src/utils/bit_reader_utils.h" +#include "src/webp/types.h" + +static WEBP_INLINE int clip(int v, int M) { + return v < 0 ? 0 : v > M ? M : v; +} + +// Paragraph 14.1 +static const uint8_t kDcTable[128] = { + 4, 5, 6, 7, 8, 9, 10, 10, + 11, 12, 13, 14, 15, 16, 17, 17, + 18, 19, 20, 20, 21, 21, 22, 22, + 23, 23, 24, 25, 25, 26, 27, 28, + 29, 30, 31, 32, 33, 34, 35, 36, + 37, 37, 38, 39, 40, 41, 42, 43, + 44, 45, 46, 46, 47, 48, 49, 50, + 51, 52, 53, 54, 55, 56, 57, 58, + 59, 60, 61, 62, 63, 64, 65, 66, + 67, 68, 69, 70, 71, 72, 73, 74, + 75, 76, 76, 77, 78, 79, 80, 81, + 82, 83, 84, 85, 86, 87, 88, 89, + 91, 93, 95, 96, 98, 100, 101, 102, + 104, 106, 108, 110, 112, 114, 116, 118, + 122, 124, 126, 128, 130, 132, 134, 136, + 138, 140, 143, 145, 148, 151, 154, 157 +}; + +static const uint16_t kAcTable[128] = { + 4, 5, 6, 7, 8, 9, 10, 11, + 12, 13, 14, 15, 16, 17, 18, 19, + 20, 21, 22, 23, 24, 25, 26, 27, + 28, 29, 30, 31, 32, 33, 34, 35, + 36, 37, 38, 39, 40, 41, 42, 43, + 44, 45, 46, 47, 48, 49, 50, 51, + 52, 53, 54, 55, 56, 57, 58, 60, + 62, 64, 66, 68, 70, 72, 74, 76, + 78, 80, 82, 84, 86, 88, 90, 92, + 94, 96, 98, 100, 102, 104, 106, 108, + 110, 112, 114, 116, 119, 122, 125, 128, + 131, 134, 137, 140, 143, 146, 149, 152, + 155, 158, 161, 164, 167, 170, 173, 177, + 181, 185, 189, 193, 197, 201, 205, 209, + 213, 217, 221, 225, 229, 234, 239, 245, + 249, 254, 259, 264, 269, 274, 279, 284 +}; + +//------------------------------------------------------------------------------ +// Paragraph 9.6 + +void VP8ParseQuant(VP8Decoder* const dec) { + VP8BitReader* const br = &dec->br; + const int base_q0 = VP8GetValue(br, 7, "global-header"); + const int dqy1_dc = VP8Get(br, "global-header") ? + VP8GetSignedValue(br, 4, "global-header") : 0; + const int dqy2_dc = VP8Get(br, "global-header") ? + VP8GetSignedValue(br, 4, "global-header") : 0; + const int dqy2_ac = VP8Get(br, "global-header") ? + VP8GetSignedValue(br, 4, "global-header") : 0; + const int dquv_dc = VP8Get(br, "global-header") ? + VP8GetSignedValue(br, 4, "global-header") : 0; + const int dquv_ac = VP8Get(br, "global-header") ? + VP8GetSignedValue(br, 4, "global-header") : 0; + + const VP8SegmentHeader* const hdr = &dec->segment_hdr; + int i; + + for (i = 0; i < NUM_MB_SEGMENTS; ++i) { + int q; + if (hdr->use_segment) { + q = hdr->quantizer[i]; + if (!hdr->absolute_delta) { + q += base_q0; + } + } else { + if (i > 0) { + dec->dqm[i] = dec->dqm[0]; + continue; + } else { + q = base_q0; + } + } + { + VP8QuantMatrix* const m = &dec->dqm[i]; + m->y1_mat[0] = kDcTable[clip(q + dqy1_dc, 127)]; + m->y1_mat[1] = kAcTable[clip(q + 0, 127)]; + + m->y2_mat[0] = kDcTable[clip(q + dqy2_dc, 127)] * 2; + // For all x in [0..284], x*155/100 is bitwise equal to (x*101581) >> 16. + // The smallest precision for that is '(x*6349) >> 12' but 16 is a good + // word size. + m->y2_mat[1] = (kAcTable[clip(q + dqy2_ac, 127)] * 101581) >> 16; + if (m->y2_mat[1] < 8) m->y2_mat[1] = 8; + + m->uv_mat[0] = kDcTable[clip(q + dquv_dc, 117)]; + m->uv_mat[1] = kAcTable[clip(q + dquv_ac, 127)]; + + m->uv_quant = q + dquv_ac; // for dithering strength evaluation + } + } +} + +//------------------------------------------------------------------------------ diff --git a/packages/core/src/zig/vendor/libwebp/src/dec/tree_dec.c b/packages/core/src/zig/vendor/libwebp/src/dec/tree_dec.c new file mode 100644 index 0000000000..a3b00ef7b9 --- /dev/null +++ b/packages/core/src/zig/vendor/libwebp/src/dec/tree_dec.c @@ -0,0 +1,545 @@ +// Copyright 2010 Google Inc. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the COPYING file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +// ----------------------------------------------------------------------------- +// +// Coding trees and probas +// +// Author: Skal (pascal.massimino@gmail.com) + +#include + +#include "src/dec/common_dec.h" +#include "src/webp/types.h" +#include "src/dec/vp8_dec.h" +#include "src/dec/vp8i_dec.h" +#include "src/dsp/cpu.h" +#include "src/utils/bit_reader_inl_utils.h" +#include "src/utils/bit_reader_utils.h" + +#if !defined(USE_GENERIC_TREE) +#if !defined(__arm__) && !defined(_M_ARM) && !WEBP_AARCH64 && \ + !defined(__wasm__) +// using a table is ~1-2% slower on ARM. Prefer the coded-tree approach then. +#define USE_GENERIC_TREE 1 // ALTERNATE_CODE +#else +#define USE_GENERIC_TREE 0 +#endif +#endif // USE_GENERIC_TREE + +#if (USE_GENERIC_TREE == 1) +static const int8_t kYModesIntra4[18] = { + -B_DC_PRED, 1, + -B_TM_PRED, 2, + -B_VE_PRED, 3, + 4, 6, + -B_HE_PRED, 5, + -B_RD_PRED, -B_VR_PRED, + -B_LD_PRED, 7, + -B_VL_PRED, 8, + -B_HD_PRED, -B_HU_PRED +}; +#endif + +//------------------------------------------------------------------------------ +// Default probabilities + +// Paragraph 13.5 +static const uint8_t + CoeffsProba0[NUM_TYPES][NUM_BANDS][NUM_CTX][NUM_PROBAS] = { + { { { 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128 }, + { 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128 }, + { 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128 } + }, + { { 253, 136, 254, 255, 228, 219, 128, 128, 128, 128, 128 }, + { 189, 129, 242, 255, 227, 213, 255, 219, 128, 128, 128 }, + { 106, 126, 227, 252, 214, 209, 255, 255, 128, 128, 128 } + }, + { { 1, 98, 248, 255, 236, 226, 255, 255, 128, 128, 128 }, + { 181, 133, 238, 254, 221, 234, 255, 154, 128, 128, 128 }, + { 78, 134, 202, 247, 198, 180, 255, 219, 128, 128, 128 }, + }, + { { 1, 185, 249, 255, 243, 255, 128, 128, 128, 128, 128 }, + { 184, 150, 247, 255, 236, 224, 128, 128, 128, 128, 128 }, + { 77, 110, 216, 255, 236, 230, 128, 128, 128, 128, 128 }, + }, + { { 1, 101, 251, 255, 241, 255, 128, 128, 128, 128, 128 }, + { 170, 139, 241, 252, 236, 209, 255, 255, 128, 128, 128 }, + { 37, 116, 196, 243, 228, 255, 255, 255, 128, 128, 128 } + }, + { { 1, 204, 254, 255, 245, 255, 128, 128, 128, 128, 128 }, + { 207, 160, 250, 255, 238, 128, 128, 128, 128, 128, 128 }, + { 102, 103, 231, 255, 211, 171, 128, 128, 128, 128, 128 } + }, + { { 1, 152, 252, 255, 240, 255, 128, 128, 128, 128, 128 }, + { 177, 135, 243, 255, 234, 225, 128, 128, 128, 128, 128 }, + { 80, 129, 211, 255, 194, 224, 128, 128, 128, 128, 128 } + }, + { { 1, 1, 255, 128, 128, 128, 128, 128, 128, 128, 128 }, + { 246, 1, 255, 128, 128, 128, 128, 128, 128, 128, 128 }, + { 255, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128 } + } + }, + { { { 198, 35, 237, 223, 193, 187, 162, 160, 145, 155, 62 }, + { 131, 45, 198, 221, 172, 176, 220, 157, 252, 221, 1 }, + { 68, 47, 146, 208, 149, 167, 221, 162, 255, 223, 128 } + }, + { { 1, 149, 241, 255, 221, 224, 255, 255, 128, 128, 128 }, + { 184, 141, 234, 253, 222, 220, 255, 199, 128, 128, 128 }, + { 81, 99, 181, 242, 176, 190, 249, 202, 255, 255, 128 } + }, + { { 1, 129, 232, 253, 214, 197, 242, 196, 255, 255, 128 }, + { 99, 121, 210, 250, 201, 198, 255, 202, 128, 128, 128 }, + { 23, 91, 163, 242, 170, 187, 247, 210, 255, 255, 128 } + }, + { { 1, 200, 246, 255, 234, 255, 128, 128, 128, 128, 128 }, + { 109, 178, 241, 255, 231, 245, 255, 255, 128, 128, 128 }, + { 44, 130, 201, 253, 205, 192, 255, 255, 128, 128, 128 } + }, + { { 1, 132, 239, 251, 219, 209, 255, 165, 128, 128, 128 }, + { 94, 136, 225, 251, 218, 190, 255, 255, 128, 128, 128 }, + { 22, 100, 174, 245, 186, 161, 255, 199, 128, 128, 128 } + }, + { { 1, 182, 249, 255, 232, 235, 128, 128, 128, 128, 128 }, + { 124, 143, 241, 255, 227, 234, 128, 128, 128, 128, 128 }, + { 35, 77, 181, 251, 193, 211, 255, 205, 128, 128, 128 } + }, + { { 1, 157, 247, 255, 236, 231, 255, 255, 128, 128, 128 }, + { 121, 141, 235, 255, 225, 227, 255, 255, 128, 128, 128 }, + { 45, 99, 188, 251, 195, 217, 255, 224, 128, 128, 128 } + }, + { { 1, 1, 251, 255, 213, 255, 128, 128, 128, 128, 128 }, + { 203, 1, 248, 255, 255, 128, 128, 128, 128, 128, 128 }, + { 137, 1, 177, 255, 224, 255, 128, 128, 128, 128, 128 } + } + }, + { { { 253, 9, 248, 251, 207, 208, 255, 192, 128, 128, 128 }, + { 175, 13, 224, 243, 193, 185, 249, 198, 255, 255, 128 }, + { 73, 17, 171, 221, 161, 179, 236, 167, 255, 234, 128 } + }, + { { 1, 95, 247, 253, 212, 183, 255, 255, 128, 128, 128 }, + { 239, 90, 244, 250, 211, 209, 255, 255, 128, 128, 128 }, + { 155, 77, 195, 248, 188, 195, 255, 255, 128, 128, 128 } + }, + { { 1, 24, 239, 251, 218, 219, 255, 205, 128, 128, 128 }, + { 201, 51, 219, 255, 196, 186, 128, 128, 128, 128, 128 }, + { 69, 46, 190, 239, 201, 218, 255, 228, 128, 128, 128 } + }, + { { 1, 191, 251, 255, 255, 128, 128, 128, 128, 128, 128 }, + { 223, 165, 249, 255, 213, 255, 128, 128, 128, 128, 128 }, + { 141, 124, 248, 255, 255, 128, 128, 128, 128, 128, 128 } + }, + { { 1, 16, 248, 255, 255, 128, 128, 128, 128, 128, 128 }, + { 190, 36, 230, 255, 236, 255, 128, 128, 128, 128, 128 }, + { 149, 1, 255, 128, 128, 128, 128, 128, 128, 128, 128 } + }, + { { 1, 226, 255, 128, 128, 128, 128, 128, 128, 128, 128 }, + { 247, 192, 255, 128, 128, 128, 128, 128, 128, 128, 128 }, + { 240, 128, 255, 128, 128, 128, 128, 128, 128, 128, 128 } + }, + { { 1, 134, 252, 255, 255, 128, 128, 128, 128, 128, 128 }, + { 213, 62, 250, 255, 255, 128, 128, 128, 128, 128, 128 }, + { 55, 93, 255, 128, 128, 128, 128, 128, 128, 128, 128 } + }, + { { 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128 }, + { 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128 }, + { 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128 } + } + }, + { { { 202, 24, 213, 235, 186, 191, 220, 160, 240, 175, 255 }, + { 126, 38, 182, 232, 169, 184, 228, 174, 255, 187, 128 }, + { 61, 46, 138, 219, 151, 178, 240, 170, 255, 216, 128 } + }, + { { 1, 112, 230, 250, 199, 191, 247, 159, 255, 255, 128 }, + { 166, 109, 228, 252, 211, 215, 255, 174, 128, 128, 128 }, + { 39, 77, 162, 232, 172, 180, 245, 178, 255, 255, 128 } + }, + { { 1, 52, 220, 246, 198, 199, 249, 220, 255, 255, 128 }, + { 124, 74, 191, 243, 183, 193, 250, 221, 255, 255, 128 }, + { 24, 71, 130, 219, 154, 170, 243, 182, 255, 255, 128 } + }, + { { 1, 182, 225, 249, 219, 240, 255, 224, 128, 128, 128 }, + { 149, 150, 226, 252, 216, 205, 255, 171, 128, 128, 128 }, + { 28, 108, 170, 242, 183, 194, 254, 223, 255, 255, 128 } + }, + { { 1, 81, 230, 252, 204, 203, 255, 192, 128, 128, 128 }, + { 123, 102, 209, 247, 188, 196, 255, 233, 128, 128, 128 }, + { 20, 95, 153, 243, 164, 173, 255, 203, 128, 128, 128 } + }, + { { 1, 222, 248, 255, 216, 213, 128, 128, 128, 128, 128 }, + { 168, 175, 246, 252, 235, 205, 255, 255, 128, 128, 128 }, + { 47, 116, 215, 255, 211, 212, 255, 255, 128, 128, 128 } + }, + { { 1, 121, 236, 253, 212, 214, 255, 255, 128, 128, 128 }, + { 141, 84, 213, 252, 201, 202, 255, 219, 128, 128, 128 }, + { 42, 80, 160, 240, 162, 185, 255, 205, 128, 128, 128 } + }, + { { 1, 1, 255, 128, 128, 128, 128, 128, 128, 128, 128 }, + { 244, 1, 255, 128, 128, 128, 128, 128, 128, 128, 128 }, + { 238, 1, 255, 128, 128, 128, 128, 128, 128, 128, 128 } + } + } +}; + +// Paragraph 11.5 +static const uint8_t kBModesProba[NUM_BMODES][NUM_BMODES][NUM_BMODES - 1] = { + { { 231, 120, 48, 89, 115, 113, 120, 152, 112 }, + { 152, 179, 64, 126, 170, 118, 46, 70, 95 }, + { 175, 69, 143, 80, 85, 82, 72, 155, 103 }, + { 56, 58, 10, 171, 218, 189, 17, 13, 152 }, + { 114, 26, 17, 163, 44, 195, 21, 10, 173 }, + { 121, 24, 80, 195, 26, 62, 44, 64, 85 }, + { 144, 71, 10, 38, 171, 213, 144, 34, 26 }, + { 170, 46, 55, 19, 136, 160, 33, 206, 71 }, + { 63, 20, 8, 114, 114, 208, 12, 9, 226 }, + { 81, 40, 11, 96, 182, 84, 29, 16, 36 } }, + { { 134, 183, 89, 137, 98, 101, 106, 165, 148 }, + { 72, 187, 100, 130, 157, 111, 32, 75, 80 }, + { 66, 102, 167, 99, 74, 62, 40, 234, 128 }, + { 41, 53, 9, 178, 241, 141, 26, 8, 107 }, + { 74, 43, 26, 146, 73, 166, 49, 23, 157 }, + { 65, 38, 105, 160, 51, 52, 31, 115, 128 }, + { 104, 79, 12, 27, 217, 255, 87, 17, 7 }, + { 87, 68, 71, 44, 114, 51, 15, 186, 23 }, + { 47, 41, 14, 110, 182, 183, 21, 17, 194 }, + { 66, 45, 25, 102, 197, 189, 23, 18, 22 } }, + { { 88, 88, 147, 150, 42, 46, 45, 196, 205 }, + { 43, 97, 183, 117, 85, 38, 35, 179, 61 }, + { 39, 53, 200, 87, 26, 21, 43, 232, 171 }, + { 56, 34, 51, 104, 114, 102, 29, 93, 77 }, + { 39, 28, 85, 171, 58, 165, 90, 98, 64 }, + { 34, 22, 116, 206, 23, 34, 43, 166, 73 }, + { 107, 54, 32, 26, 51, 1, 81, 43, 31 }, + { 68, 25, 106, 22, 64, 171, 36, 225, 114 }, + { 34, 19, 21, 102, 132, 188, 16, 76, 124 }, + { 62, 18, 78, 95, 85, 57, 50, 48, 51 } }, + { { 193, 101, 35, 159, 215, 111, 89, 46, 111 }, + { 60, 148, 31, 172, 219, 228, 21, 18, 111 }, + { 112, 113, 77, 85, 179, 255, 38, 120, 114 }, + { 40, 42, 1, 196, 245, 209, 10, 25, 109 }, + { 88, 43, 29, 140, 166, 213, 37, 43, 154 }, + { 61, 63, 30, 155, 67, 45, 68, 1, 209 }, + { 100, 80, 8, 43, 154, 1, 51, 26, 71 }, + { 142, 78, 78, 16, 255, 128, 34, 197, 171 }, + { 41, 40, 5, 102, 211, 183, 4, 1, 221 }, + { 51, 50, 17, 168, 209, 192, 23, 25, 82 } }, + { { 138, 31, 36, 171, 27, 166, 38, 44, 229 }, + { 67, 87, 58, 169, 82, 115, 26, 59, 179 }, + { 63, 59, 90, 180, 59, 166, 93, 73, 154 }, + { 40, 40, 21, 116, 143, 209, 34, 39, 175 }, + { 47, 15, 16, 183, 34, 223, 49, 45, 183 }, + { 46, 17, 33, 183, 6, 98, 15, 32, 183 }, + { 57, 46, 22, 24, 128, 1, 54, 17, 37 }, + { 65, 32, 73, 115, 28, 128, 23, 128, 205 }, + { 40, 3, 9, 115, 51, 192, 18, 6, 223 }, + { 87, 37, 9, 115, 59, 77, 64, 21, 47 } }, + { { 104, 55, 44, 218, 9, 54, 53, 130, 226 }, + { 64, 90, 70, 205, 40, 41, 23, 26, 57 }, + { 54, 57, 112, 184, 5, 41, 38, 166, 213 }, + { 30, 34, 26, 133, 152, 116, 10, 32, 134 }, + { 39, 19, 53, 221, 26, 114, 32, 73, 255 }, + { 31, 9, 65, 234, 2, 15, 1, 118, 73 }, + { 75, 32, 12, 51, 192, 255, 160, 43, 51 }, + { 88, 31, 35, 67, 102, 85, 55, 186, 85 }, + { 56, 21, 23, 111, 59, 205, 45, 37, 192 }, + { 55, 38, 70, 124, 73, 102, 1, 34, 98 } }, + { { 125, 98, 42, 88, 104, 85, 117, 175, 82 }, + { 95, 84, 53, 89, 128, 100, 113, 101, 45 }, + { 75, 79, 123, 47, 51, 128, 81, 171, 1 }, + { 57, 17, 5, 71, 102, 57, 53, 41, 49 }, + { 38, 33, 13, 121, 57, 73, 26, 1, 85 }, + { 41, 10, 67, 138, 77, 110, 90, 47, 114 }, + { 115, 21, 2, 10, 102, 255, 166, 23, 6 }, + { 101, 29, 16, 10, 85, 128, 101, 196, 26 }, + { 57, 18, 10, 102, 102, 213, 34, 20, 43 }, + { 117, 20, 15, 36, 163, 128, 68, 1, 26 } }, + { { 102, 61, 71, 37, 34, 53, 31, 243, 192 }, + { 69, 60, 71, 38, 73, 119, 28, 222, 37 }, + { 68, 45, 128, 34, 1, 47, 11, 245, 171 }, + { 62, 17, 19, 70, 146, 85, 55, 62, 70 }, + { 37, 43, 37, 154, 100, 163, 85, 160, 1 }, + { 63, 9, 92, 136, 28, 64, 32, 201, 85 }, + { 75, 15, 9, 9, 64, 255, 184, 119, 16 }, + { 86, 6, 28, 5, 64, 255, 25, 248, 1 }, + { 56, 8, 17, 132, 137, 255, 55, 116, 128 }, + { 58, 15, 20, 82, 135, 57, 26, 121, 40 } }, + { { 164, 50, 31, 137, 154, 133, 25, 35, 218 }, + { 51, 103, 44, 131, 131, 123, 31, 6, 158 }, + { 86, 40, 64, 135, 148, 224, 45, 183, 128 }, + { 22, 26, 17, 131, 240, 154, 14, 1, 209 }, + { 45, 16, 21, 91, 64, 222, 7, 1, 197 }, + { 56, 21, 39, 155, 60, 138, 23, 102, 213 }, + { 83, 12, 13, 54, 192, 255, 68, 47, 28 }, + { 85, 26, 85, 85, 128, 128, 32, 146, 171 }, + { 18, 11, 7, 63, 144, 171, 4, 4, 246 }, + { 35, 27, 10, 146, 174, 171, 12, 26, 128 } }, + { { 190, 80, 35, 99, 180, 80, 126, 54, 45 }, + { 85, 126, 47, 87, 176, 51, 41, 20, 32 }, + { 101, 75, 128, 139, 118, 146, 116, 128, 85 }, + { 56, 41, 15, 176, 236, 85, 37, 9, 62 }, + { 71, 30, 17, 119, 118, 255, 17, 18, 138 }, + { 101, 38, 60, 138, 55, 70, 43, 26, 142 }, + { 146, 36, 19, 30, 171, 255, 97, 27, 20 }, + { 138, 45, 61, 62, 219, 1, 81, 188, 64 }, + { 32, 41, 20, 117, 151, 142, 20, 21, 163 }, + { 112, 19, 12, 61, 195, 128, 48, 4, 24 } } +}; + +void VP8ResetProba(VP8Proba* const proba) { + memset(proba->segments, 255u, sizeof(proba->segments)); + // proba->bands[][] is initialized later +} + +static void ParseIntraMode(VP8BitReader* const br, + VP8Decoder* const dec, int mb_x) { + uint8_t* const top = dec->intra_t + 4 * mb_x; + uint8_t* const left = dec->intra_l; + VP8MBData* const block = dec->mb_data + mb_x; + + // Note: we don't save segment map (yet), as we don't expect + // to decode more than 1 keyframe. + if (dec->segment_hdr.update_map) { + // Hardcoded tree parsing + block->segment = !VP8GetBit(br, dec->proba.segments[0], "segments") + ? VP8GetBit(br, dec->proba.segments[1], "segments") + : VP8GetBit(br, dec->proba.segments[2], "segments") + 2; + } else { + block->segment = 0; // default for intra + } + if (dec->use_skip_proba) block->skip = VP8GetBit(br, dec->skip_p, "skip"); + + block->is_i4x4 = !VP8GetBit(br, 145, "block-size"); + if (!block->is_i4x4) { + // Hardcoded 16x16 intra-mode decision tree. + const int ymode = + VP8GetBit(br, 156, "pred-modes") ? + (VP8GetBit(br, 128, "pred-modes") ? TM_PRED : H_PRED) : + (VP8GetBit(br, 163, "pred-modes") ? V_PRED : DC_PRED); + block->imodes[0] = ymode; + memset(top, ymode, 4 * sizeof(*top)); + memset(left, ymode, 4 * sizeof(*left)); + } else { + uint8_t* modes = block->imodes; + int y; + for (y = 0; y < 4; ++y) { + int ymode = left[y]; + int x; + for (x = 0; x < 4; ++x) { + const uint8_t* const prob = kBModesProba[top[x]][ymode]; +#if (USE_GENERIC_TREE == 1) + // Generic tree-parsing + int i = kYModesIntra4[VP8GetBit(br, prob[0], "pred-modes")]; + while (i > 0) { + i = kYModesIntra4[2 * i + VP8GetBit(br, prob[i], "pred-modes")]; + } + ymode = -i; +#else + // Hardcoded tree parsing + ymode = !VP8GetBit(br, prob[0], "pred-modes") ? B_DC_PRED : + !VP8GetBit(br, prob[1], "pred-modes") ? B_TM_PRED : + !VP8GetBit(br, prob[2], "pred-modes") ? B_VE_PRED : + !VP8GetBit(br, prob[3], "pred-modes") ? + (!VP8GetBit(br, prob[4], "pred-modes") ? B_HE_PRED : + (!VP8GetBit(br, prob[5], "pred-modes") ? B_RD_PRED + : B_VR_PRED)) : + (!VP8GetBit(br, prob[6], "pred-modes") ? B_LD_PRED : + (!VP8GetBit(br, prob[7], "pred-modes") ? B_VL_PRED : + (!VP8GetBit(br, prob[8], "pred-modes") ? B_HD_PRED + : B_HU_PRED)) + ); +#endif // USE_GENERIC_TREE + top[x] = ymode; + } + memcpy(modes, top, 4 * sizeof(*top)); + modes += 4; + left[y] = ymode; + } + } + // Hardcoded UVMode decision tree + block->uvmode = !VP8GetBit(br, 142, "pred-modes-uv") ? DC_PRED + : !VP8GetBit(br, 114, "pred-modes-uv") ? V_PRED + : VP8GetBit(br, 183, "pred-modes-uv") ? TM_PRED : H_PRED; +} + +int VP8ParseIntraModeRow(VP8BitReader* const br, VP8Decoder* const dec) { + int mb_x; + for (mb_x = 0; mb_x < dec->mb_w; ++mb_x) { + ParseIntraMode(br, dec, mb_x); + } + return !dec->br.eof; +} + +//------------------------------------------------------------------------------ +// Paragraph 13 + +static const uint8_t + CoeffsUpdateProba[NUM_TYPES][NUM_BANDS][NUM_CTX][NUM_PROBAS] = { + { { { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 }, + { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 }, + { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 } + }, + { { 176, 246, 255, 255, 255, 255, 255, 255, 255, 255, 255 }, + { 223, 241, 252, 255, 255, 255, 255, 255, 255, 255, 255 }, + { 249, 253, 253, 255, 255, 255, 255, 255, 255, 255, 255 } + }, + { { 255, 244, 252, 255, 255, 255, 255, 255, 255, 255, 255 }, + { 234, 254, 254, 255, 255, 255, 255, 255, 255, 255, 255 }, + { 253, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 } + }, + { { 255, 246, 254, 255, 255, 255, 255, 255, 255, 255, 255 }, + { 239, 253, 254, 255, 255, 255, 255, 255, 255, 255, 255 }, + { 254, 255, 254, 255, 255, 255, 255, 255, 255, 255, 255 } + }, + { { 255, 248, 254, 255, 255, 255, 255, 255, 255, 255, 255 }, + { 251, 255, 254, 255, 255, 255, 255, 255, 255, 255, 255 }, + { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 } + }, + { { 255, 253, 254, 255, 255, 255, 255, 255, 255, 255, 255 }, + { 251, 254, 254, 255, 255, 255, 255, 255, 255, 255, 255 }, + { 254, 255, 254, 255, 255, 255, 255, 255, 255, 255, 255 } + }, + { { 255, 254, 253, 255, 254, 255, 255, 255, 255, 255, 255 }, + { 250, 255, 254, 255, 254, 255, 255, 255, 255, 255, 255 }, + { 254, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 } + }, + { { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 }, + { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 }, + { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 } + } + }, + { { { 217, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 }, + { 225, 252, 241, 253, 255, 255, 254, 255, 255, 255, 255 }, + { 234, 250, 241, 250, 253, 255, 253, 254, 255, 255, 255 } + }, + { { 255, 254, 255, 255, 255, 255, 255, 255, 255, 255, 255 }, + { 223, 254, 254, 255, 255, 255, 255, 255, 255, 255, 255 }, + { 238, 253, 254, 254, 255, 255, 255, 255, 255, 255, 255 } + }, + { { 255, 248, 254, 255, 255, 255, 255, 255, 255, 255, 255 }, + { 249, 254, 255, 255, 255, 255, 255, 255, 255, 255, 255 }, + { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 } + }, + { { 255, 253, 255, 255, 255, 255, 255, 255, 255, 255, 255 }, + { 247, 254, 255, 255, 255, 255, 255, 255, 255, 255, 255 }, + { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 } + }, + { { 255, 253, 254, 255, 255, 255, 255, 255, 255, 255, 255 }, + { 252, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 }, + { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 } + }, + { { 255, 254, 254, 255, 255, 255, 255, 255, 255, 255, 255 }, + { 253, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 }, + { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 } + }, + { { 255, 254, 253, 255, 255, 255, 255, 255, 255, 255, 255 }, + { 250, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 }, + { 254, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 } + }, + { { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 }, + { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 }, + { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 } + } + }, + { { { 186, 251, 250, 255, 255, 255, 255, 255, 255, 255, 255 }, + { 234, 251, 244, 254, 255, 255, 255, 255, 255, 255, 255 }, + { 251, 251, 243, 253, 254, 255, 254, 255, 255, 255, 255 } + }, + { { 255, 253, 254, 255, 255, 255, 255, 255, 255, 255, 255 }, + { 236, 253, 254, 255, 255, 255, 255, 255, 255, 255, 255 }, + { 251, 253, 253, 254, 254, 255, 255, 255, 255, 255, 255 } + }, + { { 255, 254, 254, 255, 255, 255, 255, 255, 255, 255, 255 }, + { 254, 254, 254, 255, 255, 255, 255, 255, 255, 255, 255 }, + { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 } + }, + { { 255, 254, 255, 255, 255, 255, 255, 255, 255, 255, 255 }, + { 254, 254, 255, 255, 255, 255, 255, 255, 255, 255, 255 }, + { 254, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 } + }, + { { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 }, + { 254, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 }, + { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 } + }, + { { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 }, + { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 }, + { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 } + }, + { { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 }, + { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 }, + { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 } + }, + { { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 }, + { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 }, + { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 } + } + }, + { { { 248, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 }, + { 250, 254, 252, 254, 255, 255, 255, 255, 255, 255, 255 }, + { 248, 254, 249, 253, 255, 255, 255, 255, 255, 255, 255 } + }, + { { 255, 253, 253, 255, 255, 255, 255, 255, 255, 255, 255 }, + { 246, 253, 253, 255, 255, 255, 255, 255, 255, 255, 255 }, + { 252, 254, 251, 254, 254, 255, 255, 255, 255, 255, 255 } + }, + { { 255, 254, 252, 255, 255, 255, 255, 255, 255, 255, 255 }, + { 248, 254, 253, 255, 255, 255, 255, 255, 255, 255, 255 }, + { 253, 255, 254, 254, 255, 255, 255, 255, 255, 255, 255 } + }, + { { 255, 251, 254, 255, 255, 255, 255, 255, 255, 255, 255 }, + { 245, 251, 254, 255, 255, 255, 255, 255, 255, 255, 255 }, + { 253, 253, 254, 255, 255, 255, 255, 255, 255, 255, 255 } + }, + { { 255, 251, 253, 255, 255, 255, 255, 255, 255, 255, 255 }, + { 252, 253, 254, 255, 255, 255, 255, 255, 255, 255, 255 }, + { 255, 254, 255, 255, 255, 255, 255, 255, 255, 255, 255 } + }, + { { 255, 252, 255, 255, 255, 255, 255, 255, 255, 255, 255 }, + { 249, 255, 254, 255, 255, 255, 255, 255, 255, 255, 255 }, + { 255, 255, 254, 255, 255, 255, 255, 255, 255, 255, 255 } + }, + { { 255, 255, 253, 255, 255, 255, 255, 255, 255, 255, 255 }, + { 250, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 }, + { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 } + }, + { { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 }, + { 254, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 }, + { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 } + } + } +}; + +// Paragraph 9.9 + +static const uint8_t kBands[16 + 1] = { + 0, 1, 2, 3, 6, 4, 5, 6, 6, 6, 6, 6, 6, 6, 6, 7, + 0 // extra entry as sentinel +}; + +void VP8ParseProba(VP8BitReader* const br, VP8Decoder* const dec) { + VP8Proba* const proba = &dec->proba; + int t, b, c, p; + for (t = 0; t < NUM_TYPES; ++t) { + for (b = 0; b < NUM_BANDS; ++b) { + for (c = 0; c < NUM_CTX; ++c) { + for (p = 0; p < NUM_PROBAS; ++p) { + const int v = + VP8GetBit(br, CoeffsUpdateProba[t][b][c][p], "global-header") ? + VP8GetValue(br, 8, "global-header") : + CoeffsProba0[t][b][c][p]; + proba->bands[t][b].probas[c][p] = v; + } + } + } + for (b = 0; b < 16 + 1; ++b) { + proba->bands_ptr[t][b] = &proba->bands[t][kBands[b]]; + } + } + dec->use_skip_proba = VP8Get(br, "global-header"); + if (dec->use_skip_proba) { + dec->skip_p = VP8GetValue(br, 8, "global-header"); + } +} diff --git a/packages/core/src/zig/vendor/libwebp/src/dec/vp8_dec.c b/packages/core/src/zig/vendor/libwebp/src/dec/vp8_dec.c new file mode 100644 index 0000000000..b1df3a0af0 --- /dev/null +++ b/packages/core/src/zig/vendor/libwebp/src/dec/vp8_dec.c @@ -0,0 +1,739 @@ +// Copyright 2010 Google Inc. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the COPYING file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +// ----------------------------------------------------------------------------- +// +// main entry for the decoder +// +// Author: Skal (pascal.massimino@gmail.com) + +#include +#include +#include + +#include "src/dec/alphai_dec.h" +#include "src/dec/common_dec.h" +#include "src/dec/vp8_dec.h" +#include "src/dec/vp8i_dec.h" +#include "src/dec/vp8li_dec.h" +#include "src/dec/webpi_dec.h" +#include "src/dsp/cpu.h" +#include "src/dsp/dsp.h" +#include "src/utils/bit_reader_inl_utils.h" +#include "src/utils/bit_reader_utils.h" +#include "src/utils/thread_utils.h" +#include "src/utils/utils.h" +#include "src/webp/decode.h" +#include "src/webp/format_constants.h" +#include "src/webp/types.h" + +//------------------------------------------------------------------------------ + +int WebPGetDecoderVersion(void) { + return (DEC_MAJ_VERSION << 16) | (DEC_MIN_VERSION << 8) | DEC_REV_VERSION; +} + +//------------------------------------------------------------------------------ +// Signature and pointer-to-function for GetCoeffs() variants below. + +typedef int (*GetCoeffsFunc)(VP8BitReader* const br, + const VP8BandProbas* const prob[], + int ctx, const quant_t dq, int n, int16_t* out); +static volatile GetCoeffsFunc GetCoeffs = NULL; + +static void InitGetCoeffs(void); + +//------------------------------------------------------------------------------ +// VP8Decoder + +static void SetOk(VP8Decoder* const dec) { + dec->status = VP8_STATUS_OK; + dec->error_msg = "OK"; +} + +int VP8InitIoInternal(VP8Io* const io, int version) { + if (WEBP_ABI_IS_INCOMPATIBLE(version, WEBP_DECODER_ABI_VERSION)) { + return 0; // mismatch error + } + if (io != NULL) { + memset(io, 0, sizeof(*io)); + } + return 1; +} + +VP8Decoder* VP8New(void) { + VP8Decoder* const dec = (VP8Decoder*)WebPSafeCalloc(1ULL, sizeof(*dec)); + if (dec != NULL) { + SetOk(dec); + WebPGetWorkerInterface()->Init(&dec->worker); + dec->ready = 0; + dec->num_parts_minus_one = 0; + InitGetCoeffs(); + } + return dec; +} + +VP8StatusCode VP8Status(VP8Decoder* const dec) { + if (!dec) return VP8_STATUS_INVALID_PARAM; + return dec->status; +} + +const char* VP8StatusMessage(VP8Decoder* const dec) { + if (dec == NULL) return "no object"; + if (!dec->error_msg) return "OK"; + return dec->error_msg; +} + +void VP8Delete(VP8Decoder* const dec) { + if (dec != NULL) { + VP8Clear(dec); + WebPSafeFree(dec); + } +} + +int VP8SetError(VP8Decoder* const dec, + VP8StatusCode error, const char* const msg) { + // VP8_STATUS_SUSPENDED is only meaningful in incremental decoding. + assert(dec->incremental || error != VP8_STATUS_SUSPENDED); + // The oldest error reported takes precedence over the new one. + if (dec->status == VP8_STATUS_OK) { + dec->status = error; + dec->error_msg = msg; + dec->ready = 0; + } + return 0; +} + +//------------------------------------------------------------------------------ + +int VP8CheckSignature(const uint8_t* const data, size_t data_size) { + return (data_size >= 3 && + data[0] == 0x9d && data[1] == 0x01 && data[2] == 0x2a); +} + +int VP8GetInfo(const uint8_t* data, size_t data_size, size_t chunk_size, + int* const width, int* const height) { + if (data == NULL || data_size < VP8_FRAME_HEADER_SIZE) { + return 0; // not enough data + } + // check signature + if (!VP8CheckSignature(data + 3, data_size - 3)) { + return 0; // Wrong signature. + } else { + const uint32_t bits = data[0] | (data[1] << 8) | (data[2] << 16); + const int key_frame = !(bits & 1); + const int w = ((data[7] << 8) | data[6]) & 0x3fff; + const int h = ((data[9] << 8) | data[8]) & 0x3fff; + + if (!key_frame) { // Not a keyframe. + return 0; + } + + if (((bits >> 1) & 7) > 3) { + return 0; // unknown profile + } + if (!((bits >> 4) & 1)) { + return 0; // first frame is invisible! + } + if (((bits >> 5)) >= chunk_size) { // partition_length + return 0; // inconsistent size information. + } + if (w == 0 || h == 0) { + return 0; // We don't support both width and height to be zero. + } + + if (width) { + *width = w; + } + if (height) { + *height = h; + } + + return 1; + } +} + +//------------------------------------------------------------------------------ +// Header parsing + +static void ResetSegmentHeader(VP8SegmentHeader* const hdr) { + assert(hdr != NULL); + hdr->use_segment = 0; + hdr->update_map = 0; + hdr->absolute_delta = 1; + memset(hdr->quantizer, 0, sizeof(hdr->quantizer)); + memset(hdr->filter_strength, 0, sizeof(hdr->filter_strength)); +} + +// Paragraph 9.3 +static int ParseSegmentHeader(VP8BitReader* br, + VP8SegmentHeader* hdr, VP8Proba* proba) { + assert(br != NULL); + assert(hdr != NULL); + hdr->use_segment = VP8Get(br, "global-header"); + if (hdr->use_segment) { + hdr->update_map = VP8Get(br, "global-header"); + if (VP8Get(br, "global-header")) { // update data + int s; + hdr->absolute_delta = VP8Get(br, "global-header"); + for (s = 0; s < NUM_MB_SEGMENTS; ++s) { + hdr->quantizer[s] = VP8Get(br, "global-header") ? + VP8GetSignedValue(br, 7, "global-header") : 0; + } + for (s = 0; s < NUM_MB_SEGMENTS; ++s) { + hdr->filter_strength[s] = VP8Get(br, "global-header") ? + VP8GetSignedValue(br, 6, "global-header") : 0; + } + } + if (hdr->update_map) { + int s; + for (s = 0; s < MB_FEATURE_TREE_PROBS; ++s) { + proba->segments[s] = VP8Get(br, "global-header") ? + VP8GetValue(br, 8, "global-header") : 255u; + } + } + } else { + hdr->update_map = 0; + } + return !br->eof; +} + +// Paragraph 9.5 +// If we don't have all the necessary data in 'buf', this function returns +// VP8_STATUS_SUSPENDED in incremental decoding, VP8_STATUS_NOT_ENOUGH_DATA +// otherwise. +// In incremental decoding, this case is not necessarily an error. Still, no +// bitreader is ever initialized to make it possible to read unavailable memory. +// If we don't even have the partitions' sizes, then VP8_STATUS_NOT_ENOUGH_DATA +// is returned, and this is an unrecoverable error. +// If the partitions were positioned ok, VP8_STATUS_OK is returned. +static VP8StatusCode ParsePartitions(VP8Decoder* const dec, + const uint8_t* buf, size_t size) { + VP8BitReader* const br = &dec->br; + const uint8_t* sz = buf; + const uint8_t* buf_end = buf + size; + const uint8_t* part_start; + size_t size_left = size; + size_t last_part; + size_t p; + + dec->num_parts_minus_one = (1 << VP8GetValue(br, 2, "global-header")) - 1; + last_part = dec->num_parts_minus_one; + if (size < 3 * last_part) { + // we can't even read the sizes with sz[]! That's a failure. + return VP8_STATUS_NOT_ENOUGH_DATA; + } + part_start = buf + last_part * 3; + size_left -= last_part * 3; + for (p = 0; p < last_part; ++p) { + size_t psize = sz[0] | (sz[1] << 8) | (sz[2] << 16); + if (psize > size_left) psize = size_left; + VP8InitBitReader(dec->parts + p, part_start, psize); + part_start += psize; + size_left -= psize; + sz += 3; + } + VP8InitBitReader(dec->parts + last_part, part_start, size_left); + if (part_start < buf_end) return VP8_STATUS_OK; + return dec->incremental + ? VP8_STATUS_SUSPENDED // Init is ok, but there's not enough data + : VP8_STATUS_NOT_ENOUGH_DATA; +} + +// Paragraph 9.4 +static int ParseFilterHeader(VP8BitReader* br, VP8Decoder* const dec) { + VP8FilterHeader* const hdr = &dec->filter_hdr; + hdr->simple = VP8Get(br, "global-header"); + hdr->level = VP8GetValue(br, 6, "global-header"); + hdr->sharpness = VP8GetValue(br, 3, "global-header"); + hdr->use_lf_delta = VP8Get(br, "global-header"); + if (hdr->use_lf_delta) { + if (VP8Get(br, "global-header")) { // update lf-delta? + int i; + for (i = 0; i < NUM_REF_LF_DELTAS; ++i) { + if (VP8Get(br, "global-header")) { + hdr->ref_lf_delta[i] = VP8GetSignedValue(br, 6, "global-header"); + } + } + for (i = 0; i < NUM_MODE_LF_DELTAS; ++i) { + if (VP8Get(br, "global-header")) { + hdr->mode_lf_delta[i] = VP8GetSignedValue(br, 6, "global-header"); + } + } + } + } + dec->filter_type = (hdr->level == 0) ? 0 : hdr->simple ? 1 : 2; + return !br->eof; +} + +// Topmost call +int VP8GetHeaders(VP8Decoder* const dec, VP8Io* const io) { + const uint8_t* buf; + size_t buf_size; + VP8FrameHeader* frm_hdr; + VP8PictureHeader* pic_hdr; + VP8BitReader* br; + VP8StatusCode status; + + if (dec == NULL) { + return 0; + } + SetOk(dec); + if (io == NULL) { + return VP8SetError(dec, VP8_STATUS_INVALID_PARAM, + "null VP8Io passed to VP8GetHeaders()"); + } + buf = io->data; + buf_size = io->data_size; + if (buf_size < 4) { + return VP8SetError(dec, VP8_STATUS_NOT_ENOUGH_DATA, + "Truncated header."); + } + + // Paragraph 9.1 + { + const uint32_t bits = buf[0] | (buf[1] << 8) | (buf[2] << 16); + frm_hdr = &dec->frm_hdr; + frm_hdr->key_frame = !(bits & 1); + frm_hdr->profile = (bits >> 1) & 7; + frm_hdr->show = (bits >> 4) & 1; + frm_hdr->partition_length = (bits >> 5); + if (frm_hdr->profile > 3) { + return VP8SetError(dec, VP8_STATUS_BITSTREAM_ERROR, + "Incorrect keyframe parameters."); + } + if (!frm_hdr->show) { + return VP8SetError(dec, VP8_STATUS_UNSUPPORTED_FEATURE, + "Frame not displayable."); + } + buf += 3; + buf_size -= 3; + } + + pic_hdr = &dec->pic_hdr; + if (frm_hdr->key_frame) { + // Paragraph 9.2 + if (buf_size < 7) { + return VP8SetError(dec, VP8_STATUS_NOT_ENOUGH_DATA, + "cannot parse picture header"); + } + if (!VP8CheckSignature(buf, buf_size)) { + return VP8SetError(dec, VP8_STATUS_BITSTREAM_ERROR, + "Bad code word"); + } + pic_hdr->width = ((buf[4] << 8) | buf[3]) & 0x3fff; + pic_hdr->xscale = buf[4] >> 6; // ratio: 1, 5/4 5/3 or 2 + pic_hdr->height = ((buf[6] << 8) | buf[5]) & 0x3fff; + pic_hdr->yscale = buf[6] >> 6; + buf += 7; + buf_size -= 7; + + dec->mb_w = (pic_hdr->width + 15) >> 4; + dec->mb_h = (pic_hdr->height + 15) >> 4; + + // Setup default output area (can be later modified during io->setup()) + io->width = pic_hdr->width; + io->height = pic_hdr->height; + // IMPORTANT! use some sane dimensions in crop* and scaled* fields. + // So they can be used interchangeably without always testing for + // 'use_cropping'. + io->use_cropping = 0; + io->crop_top = 0; + io->crop_left = 0; + io->crop_right = io->width; + io->crop_bottom = io->height; + io->use_scaling = 0; + io->scaled_width = io->width; + io->scaled_height = io->height; + + io->mb_w = io->width; // for soundness + io->mb_h = io->height; // ditto + + VP8ResetProba(&dec->proba); + ResetSegmentHeader(&dec->segment_hdr); + } + + // Check if we have all the partition #0 available, and initialize dec->br + // to read this partition (and this partition only). + if (frm_hdr->partition_length > buf_size) { + return VP8SetError(dec, VP8_STATUS_NOT_ENOUGH_DATA, + "bad partition length"); + } + + br = &dec->br; + VP8InitBitReader(br, buf, frm_hdr->partition_length); + buf += frm_hdr->partition_length; + buf_size -= frm_hdr->partition_length; + + if (frm_hdr->key_frame) { + pic_hdr->colorspace = VP8Get(br, "global-header"); + pic_hdr->clamp_type = VP8Get(br, "global-header"); + } + if (!ParseSegmentHeader(br, &dec->segment_hdr, &dec->proba)) { + return VP8SetError(dec, VP8_STATUS_BITSTREAM_ERROR, + "cannot parse segment header"); + } + // Filter specs + if (!ParseFilterHeader(br, dec)) { + return VP8SetError(dec, VP8_STATUS_BITSTREAM_ERROR, + "cannot parse filter header"); + } + status = ParsePartitions(dec, buf, buf_size); + if (status != VP8_STATUS_OK) { + return VP8SetError(dec, status, "cannot parse partitions"); + } + + // quantizer change + VP8ParseQuant(dec); + + // Frame buffer marking + if (!frm_hdr->key_frame) { + return VP8SetError(dec, VP8_STATUS_UNSUPPORTED_FEATURE, + "Not a key frame."); + } + + VP8Get(br, "global-header"); // ignore the value of 'update_proba' + + VP8ParseProba(br, dec); + + // sanitized state + dec->ready = 1; + return 1; +} + +//------------------------------------------------------------------------------ +// Residual decoding (Paragraph 13.2 / 13.3) + +static const uint8_t kCat3[] = { 173, 148, 140, 0 }; +static const uint8_t kCat4[] = { 176, 155, 140, 135, 0 }; +static const uint8_t kCat5[] = { 180, 157, 141, 134, 130, 0 }; +static const uint8_t kCat6[] = + { 254, 254, 243, 230, 196, 177, 153, 140, 133, 130, 129, 0 }; +static const uint8_t* const kCat3456[] = { kCat3, kCat4, kCat5, kCat6 }; +static const uint8_t kZigzag[16] = { + 0, 1, 4, 8, 5, 2, 3, 6, 9, 12, 13, 10, 7, 11, 14, 15 +}; + +// See section 13-2: https://datatracker.ietf.org/doc/html/rfc6386#section-13.2 +static int GetLargeValue(VP8BitReader* const br, const uint8_t* const p) { + int v; + if (!VP8GetBit(br, p[3], "coeffs")) { + if (!VP8GetBit(br, p[4], "coeffs")) { + v = 2; + } else { + v = 3 + VP8GetBit(br, p[5], "coeffs"); + } + } else { + if (!VP8GetBit(br, p[6], "coeffs")) { + if (!VP8GetBit(br, p[7], "coeffs")) { + v = 5 + VP8GetBit(br, 159, "coeffs"); + } else { + v = 7 + 2 * VP8GetBit(br, 165, "coeffs"); + v += VP8GetBit(br, 145, "coeffs"); + } + } else { + const uint8_t* tab; + const int bit1 = VP8GetBit(br, p[8], "coeffs"); + const int bit0 = VP8GetBit(br, p[9 + bit1], "coeffs"); + const int cat = 2 * bit1 + bit0; + v = 0; + for (tab = kCat3456[cat]; *tab; ++tab) { + v += v + VP8GetBit(br, *tab, "coeffs"); + } + v += 3 + (8 << cat); + } + } + return v; +} + +// Returns the position of the last non-zero coeff plus one +static int GetCoeffsFast(VP8BitReader* const br, + const VP8BandProbas* const prob[], + int ctx, const quant_t dq, int n, int16_t* out) { + const uint8_t* p = prob[n]->probas[ctx]; + for (; n < 16; ++n) { + if (!VP8GetBit(br, p[0], "coeffs")) { + return n; // previous coeff was last non-zero coeff + } + while (!VP8GetBit(br, p[1], "coeffs")) { // sequence of zero coeffs + p = prob[++n]->probas[0]; + if (n == 16) return 16; + } + { // non zero coeff + const VP8ProbaArray* const p_ctx = &prob[n + 1]->probas[0]; + int v; + if (!VP8GetBit(br, p[2], "coeffs")) { + v = 1; + p = p_ctx[1]; + } else { + v = GetLargeValue(br, p); + p = p_ctx[2]; + } + out[kZigzag[n]] = VP8GetSigned(br, v, "coeffs") * dq[n > 0]; + } + } + return 16; +} + +// This version of GetCoeffs() uses VP8GetBitAlt() which is an alternate version +// of VP8GetBitAlt() targeting specific platforms. +static int GetCoeffsAlt(VP8BitReader* const br, + const VP8BandProbas* const prob[], + int ctx, const quant_t dq, int n, int16_t* out) { + const uint8_t* p = prob[n]->probas[ctx]; + for (; n < 16; ++n) { + if (!VP8GetBitAlt(br, p[0], "coeffs")) { + return n; // previous coeff was last non-zero coeff + } + while (!VP8GetBitAlt(br, p[1], "coeffs")) { // sequence of zero coeffs + p = prob[++n]->probas[0]; + if (n == 16) return 16; + } + { // non zero coeff + const VP8ProbaArray* const p_ctx = &prob[n + 1]->probas[0]; + int v; + if (!VP8GetBitAlt(br, p[2], "coeffs")) { + v = 1; + p = p_ctx[1]; + } else { + v = GetLargeValue(br, p); + p = p_ctx[2]; + } + out[kZigzag[n]] = VP8GetSigned(br, v, "coeffs") * dq[n > 0]; + } + } + return 16; +} + +extern VP8CPUInfo VP8GetCPUInfo; + +WEBP_DSP_INIT_FUNC(InitGetCoeffs) { + if (VP8GetCPUInfo != NULL && VP8GetCPUInfo(kSlowSSSE3)) { + GetCoeffs = GetCoeffsAlt; + } else { + GetCoeffs = GetCoeffsFast; + } +} + +static WEBP_INLINE uint32_t NzCodeBits(uint32_t nz_coeffs, int nz, int dc_nz) { + nz_coeffs <<= 2; + nz_coeffs |= (nz > 3) ? 3 : (nz > 1) ? 2 : dc_nz; + return nz_coeffs; +} + +static int ParseResiduals(VP8Decoder* const dec, + VP8MB* const mb, VP8BitReader* const token_br) { + const VP8BandProbas* (* const bands)[16 + 1] = dec->proba.bands_ptr; + const VP8BandProbas* const * ac_proba; + VP8MBData* const block = dec->mb_data + dec->mb_x; + const VP8QuantMatrix* const q = &dec->dqm[block->segment]; + int16_t* dst = block->coeffs; + VP8MB* const left_mb = dec->mb_info - 1; + uint8_t tnz, lnz; + uint32_t non_zero_y = 0; + uint32_t non_zero_uv = 0; + int x, y, ch; + uint32_t out_t_nz, out_l_nz; + int first; + + memset(dst, 0, 384 * sizeof(*dst)); + if (!block->is_i4x4) { // parse DC + int16_t dc[16] = { 0 }; + const int ctx = mb->nz_dc + left_mb->nz_dc; + const int nz = GetCoeffs(token_br, bands[1], ctx, q->y2_mat, 0, dc); + mb->nz_dc = left_mb->nz_dc = (nz > 0); + if (nz > 1) { // more than just the DC -> perform the full transform + VP8TransformWHT(dc, dst); + } else { // only DC is non-zero -> inlined simplified transform + int i; + const int dc0 = (dc[0] + 3) >> 3; + for (i = 0; i < 16 * 16; i += 16) dst[i] = dc0; + } + first = 1; + ac_proba = bands[0]; + } else { + first = 0; + ac_proba = bands[3]; + } + + tnz = mb->nz & 0x0f; + lnz = left_mb->nz & 0x0f; + for (y = 0; y < 4; ++y) { + int l = lnz & 1; + uint32_t nz_coeffs = 0; + for (x = 0; x < 4; ++x) { + const int ctx = l + (tnz & 1); + const int nz = GetCoeffs(token_br, ac_proba, ctx, q->y1_mat, first, dst); + l = (nz > first); + tnz = (tnz >> 1) | (l << 7); + nz_coeffs = NzCodeBits(nz_coeffs, nz, dst[0] != 0); + dst += 16; + } + tnz >>= 4; + lnz = (lnz >> 1) | (l << 7); + non_zero_y = (non_zero_y << 8) | nz_coeffs; + } + out_t_nz = tnz; + out_l_nz = lnz >> 4; + + for (ch = 0; ch < 4; ch += 2) { + uint32_t nz_coeffs = 0; + tnz = mb->nz >> (4 + ch); + lnz = left_mb->nz >> (4 + ch); + for (y = 0; y < 2; ++y) { + int l = lnz & 1; + for (x = 0; x < 2; ++x) { + const int ctx = l + (tnz & 1); + const int nz = GetCoeffs(token_br, bands[2], ctx, q->uv_mat, 0, dst); + l = (nz > 0); + tnz = (tnz >> 1) | (l << 3); + nz_coeffs = NzCodeBits(nz_coeffs, nz, dst[0] != 0); + dst += 16; + } + tnz >>= 2; + lnz = (lnz >> 1) | (l << 5); + } + // Note: we don't really need the per-4x4 details for U/V blocks. + non_zero_uv |= nz_coeffs << (4 * ch); + out_t_nz |= (tnz << 4) << ch; + out_l_nz |= (lnz & 0xf0) << ch; + } + mb->nz = out_t_nz; + left_mb->nz = out_l_nz; + + block->non_zero_y = non_zero_y; + block->non_zero_uv = non_zero_uv; + + // We look at the mode-code of each block and check if some blocks have less + // than three non-zero coeffs (code < 2). This is to avoid dithering flat and + // empty blocks. + block->dither = (non_zero_uv & 0xaaaa) ? 0 : q->dither; + + return !(non_zero_y | non_zero_uv); // will be used for further optimization +} + +//------------------------------------------------------------------------------ +// Main loop + +int VP8DecodeMB(VP8Decoder* const dec, VP8BitReader* const token_br) { + VP8MB* const left = dec->mb_info - 1; + VP8MB* const mb = dec->mb_info + dec->mb_x; + VP8MBData* const block = dec->mb_data + dec->mb_x; + int skip = dec->use_skip_proba ? block->skip : 0; + + if (!skip) { + skip = ParseResiduals(dec, mb, token_br); + } else { + left->nz = mb->nz = 0; + if (!block->is_i4x4) { + left->nz_dc = mb->nz_dc = 0; + } + block->non_zero_y = 0; + block->non_zero_uv = 0; + block->dither = 0; + } + + if (dec->filter_type > 0) { // store filter info + VP8FInfo* const finfo = dec->f_info + dec->mb_x; + *finfo = dec->fstrengths[block->segment][block->is_i4x4]; + finfo->f_inner |= !skip; + } + + return !token_br->eof; +} + +void VP8InitScanline(VP8Decoder* const dec) { + VP8MB* const left = dec->mb_info - 1; + left->nz = 0; + left->nz_dc = 0; + memset(dec->intra_l, B_DC_PRED, sizeof(dec->intra_l)); + dec->mb_x = 0; +} + +static int ParseFrame(VP8Decoder* const dec, VP8Io* io) { + for (dec->mb_y = 0; dec->mb_y < dec->br_mb_y; ++dec->mb_y) { + // Parse bitstream for this row. + VP8BitReader* const token_br = + &dec->parts[dec->mb_y & dec->num_parts_minus_one]; + if (!VP8ParseIntraModeRow(&dec->br, dec)) { + return VP8SetError(dec, VP8_STATUS_NOT_ENOUGH_DATA, + "Premature end-of-partition0 encountered."); + } + for (; dec->mb_x < dec->mb_w; ++dec->mb_x) { + if (!VP8DecodeMB(dec, token_br)) { + return VP8SetError(dec, VP8_STATUS_NOT_ENOUGH_DATA, + "Premature end-of-file encountered."); + } + } + VP8InitScanline(dec); // Prepare for next scanline + + // Reconstruct, filter and emit the row. + if (!VP8ProcessRow(dec, io)) { + return VP8SetError(dec, VP8_STATUS_USER_ABORT, "Output aborted."); + } + } + if (dec->mt_method > 0) { + if (!WebPGetWorkerInterface()->Sync(&dec->worker)) return 0; + } + + return 1; +} + +// Main entry point +int VP8Decode(VP8Decoder* const dec, VP8Io* const io) { + int ok = 0; + if (dec == NULL) { + return 0; + } + if (io == NULL) { + return VP8SetError(dec, VP8_STATUS_INVALID_PARAM, + "NULL VP8Io parameter in VP8Decode()."); + } + + if (!dec->ready) { + if (!VP8GetHeaders(dec, io)) { + return 0; + } + } + assert(dec->ready); + + // Finish setting up the decoding parameter. Will call io->setup(). + ok = (VP8EnterCritical(dec, io) == VP8_STATUS_OK); + if (ok) { // good to go. + // Will allocate memory and prepare everything. + if (ok) ok = VP8InitFrame(dec, io); + + // Main decoding loop + if (ok) ok = ParseFrame(dec, io); + + // Exit. + ok &= VP8ExitCritical(dec, io); + } + + if (!ok) { + VP8Clear(dec); + return 0; + } + + dec->ready = 0; + return ok; +} + +void VP8Clear(VP8Decoder* const dec) { + if (dec == NULL) { + return; + } + WebPGetWorkerInterface()->End(&dec->worker); + WebPDeallocateAlphaMemory(dec); + WebPSafeFree(dec->mem); + dec->mem = NULL; + dec->mem_size = 0; + memset(&dec->br, 0, sizeof(dec->br)); + dec->ready = 0; +} + +//------------------------------------------------------------------------------ diff --git a/packages/core/src/zig/vendor/libwebp/src/dec/vp8_dec.h b/packages/core/src/zig/vendor/libwebp/src/dec/vp8_dec.h new file mode 100644 index 0000000000..eb292b14b8 --- /dev/null +++ b/packages/core/src/zig/vendor/libwebp/src/dec/vp8_dec.h @@ -0,0 +1,186 @@ +// Copyright 2010 Google Inc. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the COPYING file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +// ----------------------------------------------------------------------------- +// +// Low-level API for VP8 decoder +// +// Author: Skal (pascal.massimino@gmail.com) + +#ifndef WEBP_DEC_VP8_DEC_H_ +#define WEBP_DEC_VP8_DEC_H_ + +#include + +#include "src/webp/decode.h" +#include "src/webp/types.h" + +#ifdef __cplusplus +extern "C" { +#endif + +//------------------------------------------------------------------------------ +// Lower-level API +// +// These functions provide fine-grained control of the decoding process. +// The call flow should resemble: +// +// VP8Io io; +// VP8InitIo(&io); +// io.data = data; +// io.data_size = size; +// /* customize io's functions (setup()/put()/teardown()) if needed. */ +// +// VP8Decoder* dec = VP8New(); +// int ok = VP8Decode(dec, &io); +// if (!ok) printf("Error: %s\n", VP8StatusMessage(dec)); +// VP8Delete(dec); +// return ok; + +// Input / Output +typedef struct VP8Io VP8Io; +typedef int (*VP8IoPutHook)(const VP8Io* io); +typedef int (*VP8IoSetupHook)(VP8Io* io); +typedef void (*VP8IoTeardownHook)(const VP8Io* io); + +struct VP8Io { + // set by VP8GetHeaders() + int width, height; // picture dimensions, in pixels (invariable). + // These are the original, uncropped dimensions. + // The actual area passed to put() is stored + // in mb_w / mb_h fields. + + // set before calling put() + int mb_y; // position of the current rows (in pixels) + int mb_w; // number of columns in the sample + int mb_h; // number of rows in the sample + const uint8_t* y, *u, *v; // rows to copy (in yuv420 format) + int y_stride; // row stride for luma + int uv_stride; // row stride for chroma + + void* opaque; // user data + + // called when fresh samples are available. Currently, samples are in + // YUV420 format, and can be up to width x 24 in size (depending on the + // in-loop filtering level, e.g.). Should return false in case of error + // or abort request. The actual size of the area to update is mb_w x mb_h + // in size, taking cropping into account. + VP8IoPutHook put; + + // called just before starting to decode the blocks. + // Must return false in case of setup error, true otherwise. If false is + // returned, teardown() will NOT be called. But if the setup succeeded + // and true is returned, then teardown() will always be called afterward. + VP8IoSetupHook setup; + + // Called just after block decoding is finished (or when an error occurred + // during put()). Is NOT called if setup() failed. + VP8IoTeardownHook teardown; + + // this is a recommendation for the user-side yuv->rgb converter. This flag + // is set when calling setup() hook and can be overwritten by it. It then + // can be taken into consideration during the put() method. + int fancy_upsampling; + + // Input buffer. + size_t data_size; + const uint8_t* data; + + // If true, in-loop filtering will not be performed even if present in the + // bitstream. Switching off filtering may speed up decoding at the expense + // of more visible blocking. Note that output will also be non-compliant + // with the VP8 specifications. + int bypass_filtering; + + // Cropping parameters. + int use_cropping; + int crop_left, crop_right, crop_top, crop_bottom; + + // Scaling parameters. + int use_scaling; + int scaled_width, scaled_height; + + // If non NULL, pointer to the alpha data (if present) corresponding to the + // start of the current row (That is: it is pre-offset by mb_y and takes + // cropping into account). + const uint8_t* a; +}; + +// Internal, version-checked, entry point +WEBP_NODISCARD int VP8InitIoInternal(VP8Io* const, int); + +// Set the custom IO function pointers and user-data. The setter for IO hooks +// should be called before initiating incremental decoding. Returns true if +// WebPIDecoder object is successfully modified, false otherwise. +WEBP_NODISCARD int WebPISetIOHooks(WebPIDecoder* const idec, VP8IoPutHook put, + VP8IoSetupHook setup, + VP8IoTeardownHook teardown, void* user_data); + +// Main decoding object. This is an opaque structure. +typedef struct VP8Decoder VP8Decoder; + +// Create a new decoder object. +VP8Decoder* VP8New(void); + +// Must be called to make sure 'io' is initialized properly. +// Returns false in case of version mismatch. Upon such failure, no other +// decoding function should be called (VP8Decode, VP8GetHeaders, ...) +WEBP_NODISCARD static WEBP_INLINE int VP8InitIo(VP8Io* const io) { + return VP8InitIoInternal(io, WEBP_DECODER_ABI_VERSION); +} + +// Decode the VP8 frame header. Returns true if ok. +// Note: 'io->data' must be pointing to the start of the VP8 frame header. +WEBP_NODISCARD int VP8GetHeaders(VP8Decoder* const dec, VP8Io* const io); + +// Decode a picture. Will call VP8GetHeaders() if it wasn't done already. +// Returns false in case of error. +WEBP_NODISCARD int VP8Decode(VP8Decoder* const dec, VP8Io* const io); + +// Return current status of the decoder: +VP8StatusCode VP8Status(VP8Decoder* const dec); + +// return readable string corresponding to the last status. +const char* VP8StatusMessage(VP8Decoder* const dec); + +// Resets the decoder in its initial state, reclaiming memory. +// Not a mandatory call between calls to VP8Decode(). +void VP8Clear(VP8Decoder* const dec); + +// Destroy the decoder object. +void VP8Delete(VP8Decoder* const dec); + +//------------------------------------------------------------------------------ +// Miscellaneous VP8/VP8L bitstream probing functions. + +// Returns true if the next 3 bytes in data contain the VP8 signature. +WEBP_EXTERN int VP8CheckSignature(const uint8_t* const data, size_t data_size); + +// Validates the VP8 data-header and retrieves basic header information viz +// width and height. Returns 0 in case of formatting error. *width/*height +// can be passed NULL. +WEBP_EXTERN int VP8GetInfo( + const uint8_t* data, + size_t data_size, // data available so far + size_t chunk_size, // total data size expected in the chunk + int* const width, int* const height); + +// Returns true if the next byte(s) in data is a VP8L signature. +WEBP_EXTERN int VP8LCheckSignature(const uint8_t* const data, size_t size); + +// Validates the VP8L data-header and retrieves basic header information viz +// width, height and alpha. Returns 0 in case of formatting error. +// width/height/has_alpha can be passed NULL. +WEBP_EXTERN int VP8LGetInfo( + const uint8_t* data, size_t data_size, // data available so far + int* const width, int* const height, int* const has_alpha); + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif // WEBP_DEC_VP8_DEC_H_ diff --git a/packages/core/src/zig/vendor/libwebp/src/dec/vp8i_dec.h b/packages/core/src/zig/vendor/libwebp/src/dec/vp8i_dec.h new file mode 100644 index 0000000000..6d4c092bcc --- /dev/null +++ b/packages/core/src/zig/vendor/libwebp/src/dec/vp8i_dec.h @@ -0,0 +1,326 @@ +// Copyright 2010 Google Inc. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the COPYING file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +// ----------------------------------------------------------------------------- +// +// VP8 decoder: internal header. +// +// Author: Skal (pascal.massimino@gmail.com) + +#ifndef WEBP_DEC_VP8I_DEC_H_ +#define WEBP_DEC_VP8I_DEC_H_ + +#include // for memcpy() + +#include "src/dec/common_dec.h" +#include "src/dec/vp8_dec.h" +#include "src/dec/vp8li_dec.h" +#include "src/dec/webpi_dec.h" +#include "src/dsp/dsp.h" +#include "src/utils/bit_reader_utils.h" +#include "src/utils/random_utils.h" +#include "src/utils/thread_utils.h" +#include "src/webp/decode.h" +#include "src/webp/types.h" + +#ifdef __cplusplus +extern "C" { +#endif + +//------------------------------------------------------------------------------ +// Various defines and enums + +// version numbers +#define DEC_MAJ_VERSION 1 +#define DEC_MIN_VERSION 6 +#define DEC_REV_VERSION 0 + +// YUV-cache parameters. Cache is 32-bytes wide (= one cacheline). +// Constraints are: We need to store one 16x16 block of luma samples (y), +// and two 8x8 chroma blocks (u/v). These are better be 16-bytes aligned, +// in order to be SIMD-friendly. We also need to store the top, left and +// top-left samples (from previously decoded blocks), along with four +// extra top-right samples for luma (intra4x4 prediction only). +// One possible layout is, using 32 * (17 + 9) bytes: +// +// .+------ <- only 1 pixel high +// .|yyyyt. +// .|yyyyt. +// .|yyyyt. +// .|yyyy.. +// .+--.+-- <- only 1 pixel high +// .|uu.|vv +// .|uu.|vv +// +// Every character is a 4x4 block, with legend: +// '.' = unused +// 'y' = y-samples 'u' = u-samples 'v' = u-samples +// '|' = left sample, '-' = top sample, '+' = top-left sample +// 't' = extra top-right sample for 4x4 modes +#define YUV_SIZE (BPS * 17 + BPS * 9) +#define Y_OFF (BPS * 1 + 8) +#define U_OFF (Y_OFF + BPS * 16 + BPS) +#define V_OFF (U_OFF + 16) + +// minimal width under which lossy multi-threading is always disabled +#define MIN_WIDTH_FOR_THREADS 512 + +//------------------------------------------------------------------------------ +// Headers + +typedef struct { + uint8_t key_frame; + uint8_t profile; + uint8_t show; + uint32_t partition_length; +} VP8FrameHeader; + +typedef struct { + uint16_t width; + uint16_t height; + uint8_t xscale; + uint8_t yscale; + uint8_t colorspace; // 0 = YCbCr + uint8_t clamp_type; +} VP8PictureHeader; + +// segment features +typedef struct { + int use_segment; + int update_map; // whether to update the segment map or not + int absolute_delta; // absolute or delta values for quantizer and filter + int8_t quantizer[NUM_MB_SEGMENTS]; // quantization changes + int8_t filter_strength[NUM_MB_SEGMENTS]; // filter strength for segments +} VP8SegmentHeader; + +// probas associated to one of the contexts +typedef uint8_t VP8ProbaArray[NUM_PROBAS]; + +typedef struct { // all the probas associated to one band + VP8ProbaArray probas[NUM_CTX]; +} VP8BandProbas; + +// Struct collecting all frame-persistent probabilities. +typedef struct { + uint8_t segments[MB_FEATURE_TREE_PROBS]; + // Type: 0:Intra16-AC 1:Intra16-DC 2:Chroma 3:Intra4 + VP8BandProbas bands[NUM_TYPES][NUM_BANDS]; + const VP8BandProbas* bands_ptr[NUM_TYPES][16 + 1]; +} VP8Proba; + +// Filter parameters +typedef struct { + int simple; // 0=complex, 1=simple + int level; // [0..63] + int sharpness; // [0..7] + int use_lf_delta; + int ref_lf_delta[NUM_REF_LF_DELTAS]; + int mode_lf_delta[NUM_MODE_LF_DELTAS]; +} VP8FilterHeader; + +//------------------------------------------------------------------------------ +// Informations about the macroblocks. + +typedef struct { // filter specs + uint8_t f_limit; // filter limit in [3..189], or 0 if no filtering + uint8_t f_ilevel; // inner limit in [1..63] + uint8_t f_inner; // do inner filtering? + uint8_t hev_thresh; // high edge variance threshold in [0..2] +} VP8FInfo; + +typedef struct { // Top/Left Contexts used for syntax-parsing + uint8_t nz; // non-zero AC/DC coeffs (4bit for luma + 4bit for chroma) + uint8_t nz_dc; // non-zero DC coeff (1bit) +} VP8MB; + +// Dequantization matrices +typedef int quant_t[2]; // [DC / AC]. Can be 'uint16_t[2]' too (~slower). +typedef struct { + quant_t y1_mat, y2_mat, uv_mat; + + int uv_quant; // U/V quantizer value + int dither; // dithering amplitude (0 = off, max=255) +} VP8QuantMatrix; + +// Data needed to reconstruct a macroblock +typedef struct { + int16_t coeffs[384]; // 384 coeffs = (16+4+4) * 4*4 + uint8_t is_i4x4; // true if intra4x4 + uint8_t imodes[16]; // one 16x16 mode (#0) or sixteen 4x4 modes + uint8_t uvmode; // chroma prediction mode + // bit-wise info about the content of each sub-4x4 blocks (in decoding order). + // Each of the 4x4 blocks for y/u/v is associated with a 2b code according to: + // code=0 -> no coefficient + // code=1 -> only DC + // code=2 -> first three coefficients are non-zero + // code=3 -> more than three coefficients are non-zero + // This allows to call specialized transform functions. + uint32_t non_zero_y; + uint32_t non_zero_uv; + uint8_t dither; // local dithering strength (deduced from non_zero*) + uint8_t skip; + uint8_t segment; +} VP8MBData; + +// Persistent information needed by the parallel processing +typedef struct { + int id; // cache row to process (in [0..2]) + int mb_y; // macroblock position of the row + int filter_row; // true if row-filtering is needed + VP8FInfo* f_info; // filter strengths (swapped with dec->f_info) + VP8MBData* mb_data; // reconstruction data (swapped with dec->mb_data) + VP8Io io; // copy of the VP8Io to pass to put() +} VP8ThreadContext; + +// Saved top samples, per macroblock. Fits into a cache-line. +typedef struct { + uint8_t y[16], u[8], v[8]; +} VP8TopSamples; + +//------------------------------------------------------------------------------ +// VP8Decoder: the main opaque structure handed over to user + +struct VP8Decoder { + VP8StatusCode status; + int ready; // true if ready to decode a picture with VP8Decode() + const char* error_msg; // set when status is not OK. + + // Main data source + VP8BitReader br; + int incremental; // if true, incremental decoding is expected + + // headers + VP8FrameHeader frm_hdr; + VP8PictureHeader pic_hdr; + VP8FilterHeader filter_hdr; + VP8SegmentHeader segment_hdr; + + // Worker + WebPWorker worker; + int mt_method; // multi-thread method: 0=off, 1=[parse+recon][filter] + // 2=[parse][recon+filter] + int cache_id; // current cache row + int num_caches; // number of cached rows of 16 pixels (1, 2 or 3) + VP8ThreadContext thread_ctx; // Thread context + + // dimension, in macroblock units. + int mb_w, mb_h; + + // Macroblock to process/filter, depending on cropping and filter_type. + int tl_mb_x, tl_mb_y; // top-left MB that must be in-loop filtered + int br_mb_x, br_mb_y; // last bottom-right MB that must be decoded + + // number of partitions minus one. + uint32_t num_parts_minus_one; + // per-partition boolean decoders. + VP8BitReader parts[MAX_NUM_PARTITIONS]; + + // Dithering strength, deduced from decoding options + int dither; // whether to use dithering or not + VP8Random dithering_rg; // random generator for dithering + + // dequantization (one set of DC/AC dequant factor per segment) + VP8QuantMatrix dqm[NUM_MB_SEGMENTS]; + + // probabilities + VP8Proba proba; + int use_skip_proba; + uint8_t skip_p; + + // Boundary data cache and persistent buffers. + uint8_t* intra_t; // top intra modes values: 4 * mb_w + uint8_t intra_l[4]; // left intra modes values + + VP8TopSamples* yuv_t; // top y/u/v samples + + VP8MB* mb_info; // contextual macroblock info (mb_w + 1) + VP8FInfo* f_info; // filter strength info + uint8_t* yuv_b; // main block for Y/U/V (size = YUV_SIZE) + + uint8_t* cache_y; // macroblock row for storing unfiltered samples + uint8_t* cache_u; + uint8_t* cache_v; + int cache_y_stride; + int cache_uv_stride; + + // main memory chunk for the above data. Persistent. + void* mem; + size_t mem_size; + + // Per macroblock non-persistent infos. + int mb_x, mb_y; // current position, in macroblock units + VP8MBData* mb_data; // parsed reconstruction data + + // Filtering side-info + int filter_type; // 0=off, 1=simple, 2=complex + VP8FInfo fstrengths[NUM_MB_SEGMENTS][2]; // precalculated per-segment/type + + // Alpha + struct ALPHDecoder* alph_dec; // alpha-plane decoder object + const uint8_t* alpha_data; // compressed alpha data (if present) + size_t alpha_data_size; + int is_alpha_decoded; // true if alpha_data is decoded in alpha_plane + uint8_t* alpha_plane_mem; // memory allocated for alpha_plane + uint8_t* alpha_plane; // output. Persistent, contains the whole data. + const uint8_t* alpha_prev_line; // last decoded alpha row (or NULL) + int alpha_dithering; // derived from decoding options (0=off, 100=full) +}; + +//------------------------------------------------------------------------------ +// internal functions. Not public. + +// in vp8.c +int VP8SetError(VP8Decoder* const dec, + VP8StatusCode error, const char* const msg); + +// in tree.c +void VP8ResetProba(VP8Proba* const proba); +void VP8ParseProba(VP8BitReader* const br, VP8Decoder* const dec); +// parses one row of intra mode data in partition 0, returns !eof +int VP8ParseIntraModeRow(VP8BitReader* const br, VP8Decoder* const dec); + +// in quant.c +void VP8ParseQuant(VP8Decoder* const dec); + +// in frame.c +WEBP_NODISCARD int VP8InitFrame(VP8Decoder* const dec, VP8Io* const io); +// Call io->setup() and finish setting up scan parameters. +// After this call returns, one must always call VP8ExitCritical() with the +// same parameters. Both functions should be used in pair. Returns VP8_STATUS_OK +// if ok, otherwise sets and returns the error status on *dec. +VP8StatusCode VP8EnterCritical(VP8Decoder* const dec, VP8Io* const io); +// Must always be called in pair with VP8EnterCritical(). +// Returns false in case of error. +WEBP_NODISCARD int VP8ExitCritical(VP8Decoder* const dec, VP8Io* const io); +// Return the multi-threading method to use (0=off), depending +// on options and bitstream size. Only for lossy decoding. +int VP8GetThreadMethod(const WebPDecoderOptions* const options, + const WebPHeaderStructure* const headers, + int width, int height); +// Initialize dithering post-process if needed. +void VP8InitDithering(const WebPDecoderOptions* const options, + VP8Decoder* const dec); +// Process the last decoded row (filtering + output). +WEBP_NODISCARD int VP8ProcessRow(VP8Decoder* const dec, VP8Io* const io); +// To be called at the start of a new scanline, to initialize predictors. +void VP8InitScanline(VP8Decoder* const dec); +// Decode one macroblock. Returns false if there is not enough data. +WEBP_NODISCARD int VP8DecodeMB(VP8Decoder* const dec, + VP8BitReader* const token_br); + +// in alpha.c +const uint8_t* VP8DecompressAlphaRows(VP8Decoder* const dec, + const VP8Io* const io, + int row, int num_rows); + +//------------------------------------------------------------------------------ + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif // WEBP_DEC_VP8I_DEC_H_ diff --git a/packages/core/src/zig/vendor/libwebp/src/dec/vp8l_dec.c b/packages/core/src/zig/vendor/libwebp/src/dec/vp8l_dec.c new file mode 100644 index 0000000000..cf8cd82ccb --- /dev/null +++ b/packages/core/src/zig/vendor/libwebp/src/dec/vp8l_dec.c @@ -0,0 +1,1790 @@ +// Copyright 2012 Google Inc. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the COPYING file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +// ----------------------------------------------------------------------------- +// +// main entry for the decoder +// +// Authors: Vikas Arora (vikaas.arora@gmail.com) +// Jyrki Alakuijala (jyrki@google.com) + +#include +#include +#include +#include + +#include "src/dec/alphai_dec.h" +#include "src/dec/vp8_dec.h" +#include "src/dec/vp8li_dec.h" +#include "src/dec/webpi_dec.h" +#include "src/dsp/dsp.h" +#include "src/dsp/lossless.h" +#include "src/dsp/lossless_common.h" +#include "src/utils/bit_reader_utils.h" +#include "src/utils/color_cache_utils.h" +#include "src/utils/huffman_utils.h" +#include "src/utils/rescaler_utils.h" +#include "src/utils/utils.h" +#include "src/webp/decode.h" +#include "src/webp/format_constants.h" +#include "src/webp/types.h" + +#define NUM_ARGB_CACHE_ROWS 16 + +static const int kCodeLengthLiterals = 16; +static const int kCodeLengthRepeatCode = 16; +static const uint8_t kCodeLengthExtraBits[3] = { 2, 3, 7 }; +static const uint8_t kCodeLengthRepeatOffsets[3] = { 3, 3, 11 }; + +// ----------------------------------------------------------------------------- +// Five Huffman codes are used at each meta code: +// 1. green + length prefix codes + color cache codes, +// 2. alpha, +// 3. red, +// 4. blue, and, +// 5. distance prefix codes. +typedef enum { + GREEN = 0, + RED = 1, + BLUE = 2, + ALPHA = 3, + DIST = 4 +} HuffIndex; + +static const uint16_t kAlphabetSize[HUFFMAN_CODES_PER_META_CODE] = { + NUM_LITERAL_CODES + NUM_LENGTH_CODES, + NUM_LITERAL_CODES, NUM_LITERAL_CODES, NUM_LITERAL_CODES, + NUM_DISTANCE_CODES +}; + +static const uint8_t kLiteralMap[HUFFMAN_CODES_PER_META_CODE] = { + 0, 1, 1, 1, 0 +}; + +#define NUM_CODE_LENGTH_CODES 19 +static const uint8_t kCodeLengthCodeOrder[NUM_CODE_LENGTH_CODES] = { + 17, 18, 0, 1, 2, 3, 4, 5, 16, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 +}; + +#define CODE_TO_PLANE_CODES 120 +static const uint8_t kCodeToPlane[CODE_TO_PLANE_CODES] = { + 0x18, 0x07, 0x17, 0x19, 0x28, 0x06, 0x27, 0x29, 0x16, 0x1a, + 0x26, 0x2a, 0x38, 0x05, 0x37, 0x39, 0x15, 0x1b, 0x36, 0x3a, + 0x25, 0x2b, 0x48, 0x04, 0x47, 0x49, 0x14, 0x1c, 0x35, 0x3b, + 0x46, 0x4a, 0x24, 0x2c, 0x58, 0x45, 0x4b, 0x34, 0x3c, 0x03, + 0x57, 0x59, 0x13, 0x1d, 0x56, 0x5a, 0x23, 0x2d, 0x44, 0x4c, + 0x55, 0x5b, 0x33, 0x3d, 0x68, 0x02, 0x67, 0x69, 0x12, 0x1e, + 0x66, 0x6a, 0x22, 0x2e, 0x54, 0x5c, 0x43, 0x4d, 0x65, 0x6b, + 0x32, 0x3e, 0x78, 0x01, 0x77, 0x79, 0x53, 0x5d, 0x11, 0x1f, + 0x64, 0x6c, 0x42, 0x4e, 0x76, 0x7a, 0x21, 0x2f, 0x75, 0x7b, + 0x31, 0x3f, 0x63, 0x6d, 0x52, 0x5e, 0x00, 0x74, 0x7c, 0x41, + 0x4f, 0x10, 0x20, 0x62, 0x6e, 0x30, 0x73, 0x7d, 0x51, 0x5f, + 0x40, 0x72, 0x7e, 0x61, 0x6f, 0x50, 0x71, 0x7f, 0x60, 0x70 +}; + +// Memory needed for lookup tables of one Huffman tree group. Red, blue, alpha +// and distance alphabets are constant (256 for red, blue and alpha, 40 for +// distance) and lookup table sizes for them in worst case are 630 and 410 +// respectively. Size of green alphabet depends on color cache size and is equal +// to 256 (green component values) + 24 (length prefix values) +// + color_cache_size (between 0 and 2048). +// All values computed for 8-bit first level lookup with Mark Adler's tool: +// https://github.com/madler/zlib/blob/v1.2.5/examples/enough.c +#define FIXED_TABLE_SIZE (630 * 3 + 410) +static const uint16_t kTableSize[12] = { + FIXED_TABLE_SIZE + 654, + FIXED_TABLE_SIZE + 656, + FIXED_TABLE_SIZE + 658, + FIXED_TABLE_SIZE + 662, + FIXED_TABLE_SIZE + 670, + FIXED_TABLE_SIZE + 686, + FIXED_TABLE_SIZE + 718, + FIXED_TABLE_SIZE + 782, + FIXED_TABLE_SIZE + 912, + FIXED_TABLE_SIZE + 1168, + FIXED_TABLE_SIZE + 1680, + FIXED_TABLE_SIZE + 2704 +}; + +static int VP8LSetError(VP8LDecoder* const dec, VP8StatusCode error) { + // The oldest error reported takes precedence over the new one. + if (dec->status == VP8_STATUS_OK || dec->status == VP8_STATUS_SUSPENDED) { + dec->status = error; + } + return 0; +} + +static int DecodeImageStream(int xsize, int ysize, + int is_level0, + VP8LDecoder* const dec, + uint32_t** const decoded_data); + +//------------------------------------------------------------------------------ + +int VP8LCheckSignature(const uint8_t* const data, size_t size) { + return (size >= VP8L_FRAME_HEADER_SIZE && + data[0] == VP8L_MAGIC_BYTE && + (data[4] >> 5) == 0); // version +} + +static int ReadImageInfo(VP8LBitReader* const br, + int* const width, int* const height, + int* const has_alpha) { + if (VP8LReadBits(br, 8) != VP8L_MAGIC_BYTE) return 0; + *width = VP8LReadBits(br, VP8L_IMAGE_SIZE_BITS) + 1; + *height = VP8LReadBits(br, VP8L_IMAGE_SIZE_BITS) + 1; + *has_alpha = VP8LReadBits(br, 1); + if (VP8LReadBits(br, VP8L_VERSION_BITS) != 0) return 0; + return !br->eos; +} + +int VP8LGetInfo(const uint8_t* data, size_t data_size, + int* const width, int* const height, int* const has_alpha) { + if (data == NULL || data_size < VP8L_FRAME_HEADER_SIZE) { + return 0; // not enough data + } else if (!VP8LCheckSignature(data, data_size)) { + return 0; // bad signature + } else { + int w, h, a; + VP8LBitReader br; + VP8LInitBitReader(&br, data, data_size); + if (!ReadImageInfo(&br, &w, &h, &a)) { + return 0; + } + if (width != NULL) *width = w; + if (height != NULL) *height = h; + if (has_alpha != NULL) *has_alpha = a; + return 1; + } +} + +//------------------------------------------------------------------------------ + +static WEBP_INLINE int GetCopyDistance(int distance_symbol, + VP8LBitReader* const br) { + int extra_bits, offset; + if (distance_symbol < 4) { + return distance_symbol + 1; + } + extra_bits = (distance_symbol - 2) >> 1; + offset = (2 + (distance_symbol & 1)) << extra_bits; + return offset + VP8LReadBits(br, extra_bits) + 1; +} + +static WEBP_INLINE int GetCopyLength(int length_symbol, + VP8LBitReader* const br) { + // Length and distance prefixes are encoded the same way. + return GetCopyDistance(length_symbol, br); +} + +static WEBP_INLINE int PlaneCodeToDistance(int xsize, int plane_code) { + if (plane_code > CODE_TO_PLANE_CODES) { + return plane_code - CODE_TO_PLANE_CODES; + } else { + const int dist_code = kCodeToPlane[plane_code - 1]; + const int yoffset = dist_code >> 4; + const int xoffset = 8 - (dist_code & 0xf); + const int dist = yoffset * xsize + xoffset; + return (dist >= 1) ? dist : 1; // dist<1 can happen if xsize is very small + } +} + +//------------------------------------------------------------------------------ +// Decodes the next Huffman code from bit-stream. +// VP8LFillBitWindow(br) needs to be called at minimum every second call +// to ReadSymbol, in order to pre-fetch enough bits. +static WEBP_INLINE int ReadSymbol(const HuffmanCode* table, + VP8LBitReader* const br) { + int nbits; + uint32_t val = VP8LPrefetchBits(br); + table += val & HUFFMAN_TABLE_MASK; + nbits = table->bits - HUFFMAN_TABLE_BITS; + if (nbits > 0) { + VP8LSetBitPos(br, br->bit_pos + HUFFMAN_TABLE_BITS); + val = VP8LPrefetchBits(br); + table += table->value; + table += val & ((1 << nbits) - 1); + } + VP8LSetBitPos(br, br->bit_pos + table->bits); + return table->value; +} + +// Reads packed symbol depending on GREEN channel +#define BITS_SPECIAL_MARKER 0x100 // something large enough (and a bit-mask) +#define PACKED_NON_LITERAL_CODE 0 // must be < NUM_LITERAL_CODES +static WEBP_INLINE int ReadPackedSymbols(const HTreeGroup* group, + VP8LBitReader* const br, + uint32_t* const dst) { + const uint32_t val = VP8LPrefetchBits(br) & (HUFFMAN_PACKED_TABLE_SIZE - 1); + const HuffmanCode32 code = group->packed_table[val]; + assert(group->use_packed_table); + if (code.bits < BITS_SPECIAL_MARKER) { + VP8LSetBitPos(br, br->bit_pos + code.bits); + *dst = code.value; + return PACKED_NON_LITERAL_CODE; + } else { + VP8LSetBitPos(br, br->bit_pos + code.bits - BITS_SPECIAL_MARKER); + assert(code.value >= NUM_LITERAL_CODES); + return code.value; + } +} + +static int AccumulateHCode(HuffmanCode hcode, int shift, + HuffmanCode32* const huff) { + huff->bits += hcode.bits; + huff->value |= (uint32_t)hcode.value << shift; + assert(huff->bits <= HUFFMAN_TABLE_BITS); + return hcode.bits; +} + +static void BuildPackedTable(HTreeGroup* const htree_group) { + uint32_t code; + for (code = 0; code < HUFFMAN_PACKED_TABLE_SIZE; ++code) { + uint32_t bits = code; + HuffmanCode32* const huff = &htree_group->packed_table[bits]; + HuffmanCode hcode = htree_group->htrees[GREEN][bits]; + if (hcode.value >= NUM_LITERAL_CODES) { + huff->bits = hcode.bits + BITS_SPECIAL_MARKER; + huff->value = hcode.value; + } else { + huff->bits = 0; + huff->value = 0; + bits >>= AccumulateHCode(hcode, 8, huff); + bits >>= AccumulateHCode(htree_group->htrees[RED][bits], 16, huff); + bits >>= AccumulateHCode(htree_group->htrees[BLUE][bits], 0, huff); + bits >>= AccumulateHCode(htree_group->htrees[ALPHA][bits], 24, huff); + (void)bits; + } + } +} + +static int ReadHuffmanCodeLengths( + VP8LDecoder* const dec, const int* const code_length_code_lengths, + int num_symbols, int* const code_lengths) { + int ok = 0; + VP8LBitReader* const br = &dec->br; + int symbol; + int max_symbol; + int prev_code_len = DEFAULT_CODE_LENGTH; + HuffmanTables tables; + + if (!VP8LHuffmanTablesAllocate(1 << LENGTHS_TABLE_BITS, &tables) || + !VP8LBuildHuffmanTable(&tables, LENGTHS_TABLE_BITS, + code_length_code_lengths, NUM_CODE_LENGTH_CODES)) { + goto End; + } + + if (VP8LReadBits(br, 1)) { // use length + const int length_nbits = 2 + 2 * VP8LReadBits(br, 3); + max_symbol = 2 + VP8LReadBits(br, length_nbits); + if (max_symbol > num_symbols) { + goto End; + } + } else { + max_symbol = num_symbols; + } + + symbol = 0; + while (symbol < num_symbols) { + const HuffmanCode* p; + int code_len; + if (max_symbol-- == 0) break; + VP8LFillBitWindow(br); + p = &tables.curr_segment->start[VP8LPrefetchBits(br) & LENGTHS_TABLE_MASK]; + VP8LSetBitPos(br, br->bit_pos + p->bits); + code_len = p->value; + if (code_len < kCodeLengthLiterals) { + code_lengths[symbol++] = code_len; + if (code_len != 0) prev_code_len = code_len; + } else { + const int use_prev = (code_len == kCodeLengthRepeatCode); + const int slot = code_len - kCodeLengthLiterals; + const int extra_bits = kCodeLengthExtraBits[slot]; + const int repeat_offset = kCodeLengthRepeatOffsets[slot]; + int repeat = VP8LReadBits(br, extra_bits) + repeat_offset; + if (symbol + repeat > num_symbols) { + goto End; + } else { + const int length = use_prev ? prev_code_len : 0; + while (repeat-- > 0) code_lengths[symbol++] = length; + } + } + } + ok = 1; + + End: + VP8LHuffmanTablesDeallocate(&tables); + if (!ok) return VP8LSetError(dec, VP8_STATUS_BITSTREAM_ERROR); + return ok; +} + +// 'code_lengths' is pre-allocated temporary buffer, used for creating Huffman +// tree. +static int ReadHuffmanCode(int alphabet_size, VP8LDecoder* const dec, + int* const code_lengths, + HuffmanTables* const table) { + int ok = 0; + int size = 0; + VP8LBitReader* const br = &dec->br; + const int simple_code = VP8LReadBits(br, 1); + + memset(code_lengths, 0, alphabet_size * sizeof(*code_lengths)); + + if (simple_code) { // Read symbols, codes & code lengths directly. + const int num_symbols = VP8LReadBits(br, 1) + 1; + const int first_symbol_len_code = VP8LReadBits(br, 1); + // The first code is either 1 bit or 8 bit code. + int symbol = VP8LReadBits(br, (first_symbol_len_code == 0) ? 1 : 8); + code_lengths[symbol] = 1; + // The second code (if present), is always 8 bits long. + if (num_symbols == 2) { + symbol = VP8LReadBits(br, 8); + code_lengths[symbol] = 1; + } + ok = 1; + } else { // Decode Huffman-coded code lengths. + int i; + int code_length_code_lengths[NUM_CODE_LENGTH_CODES] = { 0 }; + const int num_codes = VP8LReadBits(br, 4) + 4; + assert(num_codes <= NUM_CODE_LENGTH_CODES); + + for (i = 0; i < num_codes; ++i) { + code_length_code_lengths[kCodeLengthCodeOrder[i]] = VP8LReadBits(br, 3); + } + ok = ReadHuffmanCodeLengths(dec, code_length_code_lengths, alphabet_size, + code_lengths); + } + + ok = ok && !br->eos; + if (ok) { + size = VP8LBuildHuffmanTable(table, HUFFMAN_TABLE_BITS, + code_lengths, alphabet_size); + } + if (!ok || size == 0) { + return VP8LSetError(dec, VP8_STATUS_BITSTREAM_ERROR); + } + return size; +} + +static int ReadHuffmanCodes(VP8LDecoder* const dec, int xsize, int ysize, + int color_cache_bits, int allow_recursion) { + int i; + VP8LBitReader* const br = &dec->br; + VP8LMetadata* const hdr = &dec->hdr; + uint32_t* huffman_image = NULL; + HTreeGroup* htree_groups = NULL; + HuffmanTables* huffman_tables = &hdr->huffman_tables; + int num_htree_groups = 1; + int num_htree_groups_max = 1; + int* mapping = NULL; + int ok = 0; + + // Check the table has been 0 initialized (through InitMetadata). + assert(huffman_tables->root.start == NULL); + assert(huffman_tables->curr_segment == NULL); + + if (allow_recursion && VP8LReadBits(br, 1)) { + // use meta Huffman codes. + const int huffman_precision = + MIN_HUFFMAN_BITS + VP8LReadBits(br, NUM_HUFFMAN_BITS); + const int huffman_xsize = VP8LSubSampleSize(xsize, huffman_precision); + const int huffman_ysize = VP8LSubSampleSize(ysize, huffman_precision); + const int huffman_pixs = huffman_xsize * huffman_ysize; + if (!DecodeImageStream(huffman_xsize, huffman_ysize, /*is_level0=*/0, dec, + &huffman_image)) { + goto Error; + } + hdr->huffman_subsample_bits = huffman_precision; + for (i = 0; i < huffman_pixs; ++i) { + // The huffman data is stored in red and green bytes. + const int group = (huffman_image[i] >> 8) & 0xffff; + huffman_image[i] = group; + if (group >= num_htree_groups_max) { + num_htree_groups_max = group + 1; + } + } + // Check the validity of num_htree_groups_max. If it seems too big, use a + // smaller value for later. This will prevent big memory allocations to end + // up with a bad bitstream anyway. + // The value of 1000 is totally arbitrary. We know that num_htree_groups_max + // is smaller than (1 << 16) and should be smaller than the number of pixels + // (though the format allows it to be bigger). + if (num_htree_groups_max > 1000 || num_htree_groups_max > xsize * ysize) { + // Create a mapping from the used indices to the minimal set of used + // values [0, num_htree_groups) + mapping = (int*)WebPSafeMalloc(num_htree_groups_max, sizeof(*mapping)); + if (mapping == NULL) { + VP8LSetError(dec, VP8_STATUS_OUT_OF_MEMORY); + goto Error; + } + // -1 means a value is unmapped, and therefore unused in the Huffman + // image. + memset(mapping, 0xff, num_htree_groups_max * sizeof(*mapping)); + for (num_htree_groups = 0, i = 0; i < huffman_pixs; ++i) { + // Get the current mapping for the group and remap the Huffman image. + int* const mapped_group = &mapping[huffman_image[i]]; + if (*mapped_group == -1) *mapped_group = num_htree_groups++; + huffman_image[i] = *mapped_group; + } + } else { + num_htree_groups = num_htree_groups_max; + } + } + + if (br->eos) goto Error; + + if (!ReadHuffmanCodesHelper(color_cache_bits, num_htree_groups, + num_htree_groups_max, mapping, dec, + huffman_tables, &htree_groups)) { + goto Error; + } + ok = 1; + + // All OK. Finalize pointers. + hdr->huffman_image = huffman_image; + hdr->num_htree_groups = num_htree_groups; + hdr->htree_groups = htree_groups; + + Error: + WebPSafeFree(mapping); + if (!ok) { + WebPSafeFree(huffman_image); + VP8LHuffmanTablesDeallocate(huffman_tables); + VP8LHtreeGroupsFree(htree_groups); + } + return ok; +} + +int ReadHuffmanCodesHelper(int color_cache_bits, int num_htree_groups, + int num_htree_groups_max, const int* const mapping, + VP8LDecoder* const dec, + HuffmanTables* const huffman_tables, + HTreeGroup** const htree_groups) { + int i, j, ok = 0; + const int max_alphabet_size = + kAlphabetSize[0] + ((color_cache_bits > 0) ? 1 << color_cache_bits : 0); + const int table_size = kTableSize[color_cache_bits]; + int* code_lengths = NULL; + + if ((mapping == NULL && num_htree_groups != num_htree_groups_max) || + num_htree_groups > num_htree_groups_max) { + goto Error; + } + + code_lengths = + (int*)WebPSafeCalloc((uint64_t)max_alphabet_size, sizeof(*code_lengths)); + *htree_groups = VP8LHtreeGroupsNew(num_htree_groups); + + if (*htree_groups == NULL || code_lengths == NULL || + !VP8LHuffmanTablesAllocate(num_htree_groups * table_size, + huffman_tables)) { + VP8LSetError(dec, VP8_STATUS_OUT_OF_MEMORY); + goto Error; + } + + for (i = 0; i < num_htree_groups_max; ++i) { + // If the index "i" is unused in the Huffman image, just make sure the + // coefficients are valid but do not store them. + if (mapping != NULL && mapping[i] == -1) { + for (j = 0; j < HUFFMAN_CODES_PER_META_CODE; ++j) { + int alphabet_size = kAlphabetSize[j]; + if (j == 0 && color_cache_bits > 0) { + alphabet_size += (1 << color_cache_bits); + } + // Passing in NULL so that nothing gets filled. + if (!ReadHuffmanCode(alphabet_size, dec, code_lengths, NULL)) { + goto Error; + } + } + } else { + HTreeGroup* const htree_group = + &(*htree_groups)[(mapping == NULL) ? i : mapping[i]]; + HuffmanCode** const htrees = htree_group->htrees; + int size; + int total_size = 0; + int is_trivial_literal = 1; + int max_bits = 0; + for (j = 0; j < HUFFMAN_CODES_PER_META_CODE; ++j) { + int alphabet_size = kAlphabetSize[j]; + if (j == 0 && color_cache_bits > 0) { + alphabet_size += (1 << color_cache_bits); + } + size = + ReadHuffmanCode(alphabet_size, dec, code_lengths, huffman_tables); + htrees[j] = huffman_tables->curr_segment->curr_table; + if (size == 0) { + goto Error; + } + if (is_trivial_literal && kLiteralMap[j] == 1) { + is_trivial_literal = (htrees[j]->bits == 0); + } + total_size += htrees[j]->bits; + huffman_tables->curr_segment->curr_table += size; + if (j <= ALPHA) { + int local_max_bits = code_lengths[0]; + int k; + for (k = 1; k < alphabet_size; ++k) { + if (code_lengths[k] > local_max_bits) { + local_max_bits = code_lengths[k]; + } + } + max_bits += local_max_bits; + } + } + htree_group->is_trivial_literal = is_trivial_literal; + htree_group->is_trivial_code = 0; + if (is_trivial_literal) { + const int red = htrees[RED][0].value; + const int blue = htrees[BLUE][0].value; + const int alpha = htrees[ALPHA][0].value; + htree_group->literal_arb = ((uint32_t)alpha << 24) | (red << 16) | blue; + if (total_size == 0 && htrees[GREEN][0].value < NUM_LITERAL_CODES) { + htree_group->is_trivial_code = 1; + htree_group->literal_arb |= htrees[GREEN][0].value << 8; + } + } + htree_group->use_packed_table = + !htree_group->is_trivial_code && (max_bits < HUFFMAN_PACKED_BITS); + if (htree_group->use_packed_table) BuildPackedTable(htree_group); + } + } + ok = 1; + + Error: + WebPSafeFree(code_lengths); + if (!ok) { + VP8LHuffmanTablesDeallocate(huffman_tables); + VP8LHtreeGroupsFree(*htree_groups); + *htree_groups = NULL; + } + return ok; +} + +//------------------------------------------------------------------------------ +// Scaling. + +#if !defined(WEBP_REDUCE_SIZE) +static int AllocateAndInitRescaler(VP8LDecoder* const dec, VP8Io* const io) { + const int num_channels = 4; + const int in_width = io->mb_w; + const int out_width = io->scaled_width; + const int in_height = io->mb_h; + const int out_height = io->scaled_height; + const uint64_t work_size = 2 * num_channels * (uint64_t)out_width; + rescaler_t* work; // Rescaler work area. + const uint64_t scaled_data_size = (uint64_t)out_width; + uint32_t* scaled_data; // Temporary storage for scaled BGRA data. + const uint64_t memory_size = sizeof(*dec->rescaler) + + work_size * sizeof(*work) + + scaled_data_size * sizeof(*scaled_data); + uint8_t* memory = (uint8_t*)WebPSafeMalloc(memory_size, sizeof(*memory)); + if (memory == NULL) { + return VP8LSetError(dec, VP8_STATUS_OUT_OF_MEMORY); + } + assert(dec->rescaler_memory == NULL); + dec->rescaler_memory = memory; + + dec->rescaler = (WebPRescaler*)memory; + memory += sizeof(*dec->rescaler); + work = (rescaler_t*)memory; + memory += work_size * sizeof(*work); + scaled_data = (uint32_t*)memory; + + if (!WebPRescalerInit(dec->rescaler, in_width, in_height, + (uint8_t*)scaled_data, out_width, out_height, + 0, num_channels, work)) { + return 0; + } + return 1; +} +#endif // WEBP_REDUCE_SIZE + +//------------------------------------------------------------------------------ +// Export to ARGB + +#if !defined(WEBP_REDUCE_SIZE) + +// We have special "export" function since we need to convert from BGRA +static int Export(WebPRescaler* const rescaler, WEBP_CSP_MODE colorspace, + int rgba_stride, uint8_t* const rgba) { + uint32_t* const src = (uint32_t*)rescaler->dst; + uint8_t* dst = rgba; + const int dst_width = rescaler->dst_width; + int num_lines_out = 0; + while (WebPRescalerHasPendingOutput(rescaler)) { + WebPRescalerExportRow(rescaler); + WebPMultARGBRow(src, dst_width, 1); + VP8LConvertFromBGRA(src, dst_width, colorspace, dst); + dst += rgba_stride; + ++num_lines_out; + } + return num_lines_out; +} + +// Emit scaled rows. +static int EmitRescaledRowsRGBA(const VP8LDecoder* const dec, + uint8_t* in, int in_stride, int mb_h, + uint8_t* const out, int out_stride) { + const WEBP_CSP_MODE colorspace = dec->output->colorspace; + int num_lines_in = 0; + int num_lines_out = 0; + while (num_lines_in < mb_h) { + uint8_t* const row_in = in + (ptrdiff_t)num_lines_in * in_stride; + uint8_t* const row_out = out + (ptrdiff_t)num_lines_out * out_stride; + const int lines_left = mb_h - num_lines_in; + const int needed_lines = WebPRescaleNeededLines(dec->rescaler, lines_left); + int lines_imported; + assert(needed_lines > 0 && needed_lines <= lines_left); + WebPMultARGBRows(row_in, in_stride, + dec->rescaler->src_width, needed_lines, 0); + lines_imported = + WebPRescalerImport(dec->rescaler, lines_left, row_in, in_stride); + assert(lines_imported == needed_lines); + num_lines_in += lines_imported; + num_lines_out += Export(dec->rescaler, colorspace, out_stride, row_out); + } + return num_lines_out; +} + +#endif // WEBP_REDUCE_SIZE + +// Emit rows without any scaling. +static int EmitRows(WEBP_CSP_MODE colorspace, + const uint8_t* row_in, int in_stride, + int mb_w, int mb_h, + uint8_t* const out, int out_stride) { + int lines = mb_h; + uint8_t* row_out = out; + while (lines-- > 0) { + VP8LConvertFromBGRA((const uint32_t*)row_in, mb_w, colorspace, row_out); + row_in += in_stride; + row_out += out_stride; + } + return mb_h; // Num rows out == num rows in. +} + +//------------------------------------------------------------------------------ +// Export to YUVA + +static void ConvertToYUVA(const uint32_t* const src, int width, int y_pos, + const WebPDecBuffer* const output) { + const WebPYUVABuffer* const buf = &output->u.YUVA; + + // first, the luma plane + WebPConvertARGBToY(src, buf->y + y_pos * buf->y_stride, width); + + // then U/V planes + { + uint8_t* const u = buf->u + (y_pos >> 1) * buf->u_stride; + uint8_t* const v = buf->v + (y_pos >> 1) * buf->v_stride; + // even lines: store values + // odd lines: average with previous values + WebPConvertARGBToUV(src, u, v, width, !(y_pos & 1)); + } + // Lastly, store alpha if needed. + if (buf->a != NULL) { + uint8_t* const a = buf->a + y_pos * buf->a_stride; +#if defined(WORDS_BIGENDIAN) + WebPExtractAlpha((uint8_t*)src + 0, 0, width, 1, a, 0); +#else + WebPExtractAlpha((uint8_t*)src + 3, 0, width, 1, a, 0); +#endif + } +} + +static int ExportYUVA(const VP8LDecoder* const dec, int y_pos) { + WebPRescaler* const rescaler = dec->rescaler; + uint32_t* const src = (uint32_t*)rescaler->dst; + const int dst_width = rescaler->dst_width; + int num_lines_out = 0; + while (WebPRescalerHasPendingOutput(rescaler)) { + WebPRescalerExportRow(rescaler); + WebPMultARGBRow(src, dst_width, 1); + ConvertToYUVA(src, dst_width, y_pos, dec->output); + ++y_pos; + ++num_lines_out; + } + return num_lines_out; +} + +static int EmitRescaledRowsYUVA(const VP8LDecoder* const dec, + uint8_t* in, int in_stride, int mb_h) { + int num_lines_in = 0; + int y_pos = dec->last_out_row; + while (num_lines_in < mb_h) { + const int lines_left = mb_h - num_lines_in; + const int needed_lines = WebPRescaleNeededLines(dec->rescaler, lines_left); + int lines_imported; + WebPMultARGBRows(in, in_stride, dec->rescaler->src_width, needed_lines, 0); + lines_imported = + WebPRescalerImport(dec->rescaler, lines_left, in, in_stride); + assert(lines_imported == needed_lines); + num_lines_in += lines_imported; + in += needed_lines * in_stride; + y_pos += ExportYUVA(dec, y_pos); + } + return y_pos; +} + +static int EmitRowsYUVA(const VP8LDecoder* const dec, + const uint8_t* in, int in_stride, + int mb_w, int num_rows) { + int y_pos = dec->last_out_row; + while (num_rows-- > 0) { + ConvertToYUVA((const uint32_t*)in, mb_w, y_pos, dec->output); + in += in_stride; + ++y_pos; + } + return y_pos; +} + +//------------------------------------------------------------------------------ +// Cropping. + +// Sets io->mb_y, io->mb_h & io->mb_w according to start row, end row and +// crop options. Also updates the input data pointer, so that it points to the +// start of the cropped window. Note that pixels are in ARGB format even if +// 'in_data' is uint8_t*. +// Returns true if the crop window is not empty. +static int SetCropWindow(VP8Io* const io, int y_start, int y_end, + uint8_t** const in_data, int pixel_stride) { + assert(y_start < y_end); + assert(io->crop_left < io->crop_right); + if (y_end > io->crop_bottom) { + y_end = io->crop_bottom; // make sure we don't overflow on last row. + } + if (y_start < io->crop_top) { + const int delta = io->crop_top - y_start; + y_start = io->crop_top; + *in_data += delta * pixel_stride; + } + if (y_start >= y_end) return 0; // Crop window is empty. + + *in_data += io->crop_left * sizeof(uint32_t); + + io->mb_y = y_start - io->crop_top; + io->mb_w = io->crop_right - io->crop_left; + io->mb_h = y_end - y_start; + return 1; // Non-empty crop window. +} + +//------------------------------------------------------------------------------ + +static WEBP_INLINE int GetMetaIndex( + const uint32_t* const image, int xsize, int bits, int x, int y) { + if (bits == 0) return 0; + return image[xsize * (y >> bits) + (x >> bits)]; +} + +static WEBP_INLINE HTreeGroup* GetHtreeGroupForPos(VP8LMetadata* const hdr, + int x, int y) { + const int meta_index = GetMetaIndex(hdr->huffman_image, hdr->huffman_xsize, + hdr->huffman_subsample_bits, x, y); + assert(meta_index < hdr->num_htree_groups); + return hdr->htree_groups + meta_index; +} + +//------------------------------------------------------------------------------ +// Main loop, with custom row-processing function + +typedef void (*ProcessRowsFunc)(VP8LDecoder* const dec, int row); + +static void ApplyInverseTransforms(VP8LDecoder* const dec, + int start_row, int num_rows, + const uint32_t* const rows) { + int n = dec->next_transform; + const int cache_pixs = dec->width * num_rows; + const int end_row = start_row + num_rows; + const uint32_t* rows_in = rows; + uint32_t* const rows_out = dec->argb_cache; + + // Inverse transforms. + while (n-- > 0) { + VP8LTransform* const transform = &dec->transforms[n]; + VP8LInverseTransform(transform, start_row, end_row, rows_in, rows_out); + rows_in = rows_out; + } + if (rows_in != rows_out) { + // No transform called, hence just copy. + memcpy(rows_out, rows_in, cache_pixs * sizeof(*rows_out)); + } +} + +// Processes (transforms, scales & color-converts) the rows decoded after the +// last call. +static void ProcessRows(VP8LDecoder* const dec, int row) { + const uint32_t* const rows = dec->pixels + dec->width * dec->last_row; + const int num_rows = row - dec->last_row; + + assert(row <= dec->io->crop_bottom); + // We can't process more than NUM_ARGB_CACHE_ROWS at a time (that's the size + // of argb_cache), but we currently don't need more than that. + assert(num_rows <= NUM_ARGB_CACHE_ROWS); + if (num_rows > 0) { // Emit output. + VP8Io* const io = dec->io; + uint8_t* rows_data = (uint8_t*)dec->argb_cache; + const int in_stride = io->width * sizeof(uint32_t); // in unit of RGBA + ApplyInverseTransforms(dec, dec->last_row, num_rows, rows); + if (!SetCropWindow(io, dec->last_row, row, &rows_data, in_stride)) { + // Nothing to output (this time). + } else { + const WebPDecBuffer* const output = dec->output; + if (WebPIsRGBMode(output->colorspace)) { // convert to RGBA + const WebPRGBABuffer* const buf = &output->u.RGBA; + uint8_t* const rgba = + buf->rgba + (ptrdiff_t)dec->last_out_row * buf->stride; + const int num_rows_out = +#if !defined(WEBP_REDUCE_SIZE) + io->use_scaling ? + EmitRescaledRowsRGBA(dec, rows_data, in_stride, io->mb_h, + rgba, buf->stride) : +#endif // WEBP_REDUCE_SIZE + EmitRows(output->colorspace, rows_data, in_stride, + io->mb_w, io->mb_h, rgba, buf->stride); + // Update 'last_out_row'. + dec->last_out_row += num_rows_out; + } else { // convert to YUVA + dec->last_out_row = io->use_scaling ? + EmitRescaledRowsYUVA(dec, rows_data, in_stride, io->mb_h) : + EmitRowsYUVA(dec, rows_data, in_stride, io->mb_w, io->mb_h); + } + assert(dec->last_out_row <= output->height); + } + } + + // Update 'last_row'. + dec->last_row = row; + assert(dec->last_row <= dec->height); +} + +// Row-processing for the special case when alpha data contains only one +// transform (color indexing), and trivial non-green literals. +static int Is8bOptimizable(const VP8LMetadata* const hdr) { + int i; + if (hdr->color_cache_size > 0) return 0; + // When the Huffman tree contains only one symbol, we can skip the + // call to ReadSymbol() for red/blue/alpha channels. + for (i = 0; i < hdr->num_htree_groups; ++i) { + HuffmanCode** const htrees = hdr->htree_groups[i].htrees; + if (htrees[RED][0].bits > 0) return 0; + if (htrees[BLUE][0].bits > 0) return 0; + if (htrees[ALPHA][0].bits > 0) return 0; + } + return 1; +} + +static void AlphaApplyFilter(ALPHDecoder* const alph_dec, + int first_row, int last_row, + uint8_t* out, int stride) { + if (alph_dec->filter != WEBP_FILTER_NONE) { + int y; + const uint8_t* prev_line = alph_dec->prev_line; + assert(WebPUnfilters[alph_dec->filter] != NULL); + for (y = first_row; y < last_row; ++y) { + WebPUnfilters[alph_dec->filter](prev_line, out, out, stride); + prev_line = out; + out += stride; + } + alph_dec->prev_line = prev_line; + } +} + +static void ExtractPalettedAlphaRows(VP8LDecoder* const dec, int last_row) { + // For vertical and gradient filtering, we need to decode the part above the + // crop_top row, in order to have the correct spatial predictors. + ALPHDecoder* const alph_dec = (ALPHDecoder*)dec->io->opaque; + const int top_row = + (alph_dec->filter == WEBP_FILTER_NONE || + alph_dec->filter == WEBP_FILTER_HORIZONTAL) ? dec->io->crop_top + : dec->last_row; + const int first_row = (dec->last_row < top_row) ? top_row : dec->last_row; + assert(last_row <= dec->io->crop_bottom); + if (last_row > first_row) { + // Special method for paletted alpha data. We only process the cropped area. + const int width = dec->io->width; + uint8_t* out = alph_dec->output + width * first_row; + const uint8_t* const in = + (uint8_t*)dec->pixels + dec->width * first_row; + VP8LTransform* const transform = &dec->transforms[0]; + assert(dec->next_transform == 1); + assert(transform->type == COLOR_INDEXING_TRANSFORM); + VP8LColorIndexInverseTransformAlpha(transform, first_row, last_row, + in, out); + AlphaApplyFilter(alph_dec, first_row, last_row, out, width); + } + dec->last_row = dec->last_out_row = last_row; +} + +//------------------------------------------------------------------------------ +// Helper functions for fast pattern copy (8b and 32b) + +// cyclic rotation of pattern word +static WEBP_INLINE uint32_t Rotate8b(uint32_t V) { +#if defined(WORDS_BIGENDIAN) + return ((V & 0xff000000u) >> 24) | (V << 8); +#else + return ((V & 0xffu) << 24) | (V >> 8); +#endif +} + +// copy 1, 2 or 4-bytes pattern +static WEBP_INLINE void CopySmallPattern8b(const uint8_t* src, uint8_t* dst, + int length, uint32_t pattern) { + int i; + // align 'dst' to 4-bytes boundary. Adjust the pattern along the way. + while ((uintptr_t)dst & 3) { + *dst++ = *src++; + pattern = Rotate8b(pattern); + --length; + } + // Copy the pattern 4 bytes at a time. + for (i = 0; i < (length >> 2); ++i) { + ((uint32_t*)dst)[i] = pattern; + } + // Finish with left-overs. 'pattern' is still correctly positioned, + // so no Rotate8b() call is needed. + for (i <<= 2; i < length; ++i) { + dst[i] = src[i]; + } +} + +static WEBP_INLINE void CopyBlock8b(uint8_t* const dst, int dist, int length) { + const uint8_t* src = dst - dist; + if (length >= 8) { + uint32_t pattern = 0; + switch (dist) { + case 1: + pattern = src[0]; +#if defined(__arm__) || defined(_M_ARM) // arm doesn't like multiply that much + pattern |= pattern << 8; + pattern |= pattern << 16; +#elif defined(WEBP_USE_MIPS_DSP_R2) + __asm__ volatile ("replv.qb %0, %0" : "+r"(pattern)); +#else + pattern = 0x01010101u * pattern; +#endif + break; + case 2: +#if !defined(WORDS_BIGENDIAN) + memcpy(&pattern, src, sizeof(uint16_t)); +#else + pattern = ((uint32_t)src[0] << 8) | src[1]; +#endif +#if defined(__arm__) || defined(_M_ARM) + pattern |= pattern << 16; +#elif defined(WEBP_USE_MIPS_DSP_R2) + __asm__ volatile ("replv.ph %0, %0" : "+r"(pattern)); +#else + pattern = 0x00010001u * pattern; +#endif + break; + case 4: + memcpy(&pattern, src, sizeof(uint32_t)); + break; + default: + goto Copy; + } + CopySmallPattern8b(src, dst, length, pattern); + return; + } + Copy: + if (dist >= length) { // no overlap -> use memcpy() + memcpy(dst, src, length * sizeof(*dst)); + } else { + int i; + for (i = 0; i < length; ++i) dst[i] = src[i]; + } +} + +// copy pattern of 1 or 2 uint32_t's +static WEBP_INLINE void CopySmallPattern32b(const uint32_t* src, + uint32_t* dst, + int length, uint64_t pattern) { + int i; + if ((uintptr_t)dst & 4) { // Align 'dst' to 8-bytes boundary. + *dst++ = *src++; + pattern = (pattern >> 32) | (pattern << 32); + --length; + } + assert(0 == ((uintptr_t)dst & 7)); + for (i = 0; i < (length >> 1); ++i) { + ((uint64_t*)dst)[i] = pattern; // Copy the pattern 8 bytes at a time. + } + if (length & 1) { // Finish with left-over. + dst[i << 1] = src[i << 1]; + } +} + +static WEBP_INLINE void CopyBlock32b(uint32_t* const dst, + int dist, int length) { + const uint32_t* const src = dst - dist; + if (dist <= 2 && length >= 4 && ((uintptr_t)dst & 3) == 0) { + uint64_t pattern; + if (dist == 1) { + pattern = (uint64_t)src[0]; + pattern |= pattern << 32; + } else { + memcpy(&pattern, src, sizeof(pattern)); + } + CopySmallPattern32b(src, dst, length, pattern); + } else if (dist >= length) { // no overlap + memcpy(dst, src, length * sizeof(*dst)); + } else { + int i; + for (i = 0; i < length; ++i) dst[i] = src[i]; + } +} + +//------------------------------------------------------------------------------ + +static int DecodeAlphaData(VP8LDecoder* const dec, uint8_t* const data, + int width, int height, int last_row) { + int ok = 1; + int row = dec->last_pixel / width; + int col = dec->last_pixel % width; + VP8LBitReader* const br = &dec->br; + VP8LMetadata* const hdr = &dec->hdr; + int pos = dec->last_pixel; // current position + const int end = width * height; // End of data + const int last = width * last_row; // Last pixel to decode + const int len_code_limit = NUM_LITERAL_CODES + NUM_LENGTH_CODES; + const int mask = hdr->huffman_mask; + const HTreeGroup* htree_group = + (pos < last) ? GetHtreeGroupForPos(hdr, col, row) : NULL; + assert(pos <= end); + assert(last_row <= height); + assert(Is8bOptimizable(hdr)); + + while (!br->eos && pos < last) { + int code; + // Only update when changing tile. + if ((col & mask) == 0) { + htree_group = GetHtreeGroupForPos(hdr, col, row); + } + assert(htree_group != NULL); + VP8LFillBitWindow(br); + code = ReadSymbol(htree_group->htrees[GREEN], br); + if (code < NUM_LITERAL_CODES) { // Literal + data[pos] = code; + ++pos; + ++col; + if (col >= width) { + col = 0; + ++row; + if (row <= last_row && (row % NUM_ARGB_CACHE_ROWS == 0)) { + ExtractPalettedAlphaRows(dec, row); + } + } + } else if (code < len_code_limit) { // Backward reference + int dist_code, dist; + const int length_sym = code - NUM_LITERAL_CODES; + const int length = GetCopyLength(length_sym, br); + const int dist_symbol = ReadSymbol(htree_group->htrees[DIST], br); + VP8LFillBitWindow(br); + dist_code = GetCopyDistance(dist_symbol, br); + dist = PlaneCodeToDistance(width, dist_code); + if (pos >= dist && end - pos >= length) { + CopyBlock8b(data + pos, dist, length); + } else { + ok = 0; + goto End; + } + pos += length; + col += length; + while (col >= width) { + col -= width; + ++row; + if (row <= last_row && (row % NUM_ARGB_CACHE_ROWS == 0)) { + ExtractPalettedAlphaRows(dec, row); + } + } + if (pos < last && (col & mask)) { + htree_group = GetHtreeGroupForPos(hdr, col, row); + } + } else { // Not reached + ok = 0; + goto End; + } + br->eos = VP8LIsEndOfStream(br); + } + // Process the remaining rows corresponding to last row-block. + ExtractPalettedAlphaRows(dec, row > last_row ? last_row : row); + + End: + br->eos = VP8LIsEndOfStream(br); + if (!ok || (br->eos && pos < end)) { + return VP8LSetError( + dec, br->eos ? VP8_STATUS_SUSPENDED : VP8_STATUS_BITSTREAM_ERROR); + } + dec->last_pixel = pos; + return ok; +} + +static void SaveState(VP8LDecoder* const dec, int last_pixel) { + assert(dec->incremental); + dec->saved_br = dec->br; + dec->saved_last_pixel = last_pixel; + if (dec->hdr.color_cache_size > 0) { + VP8LColorCacheCopy(&dec->hdr.color_cache, &dec->hdr.saved_color_cache); + } +} + +static void RestoreState(VP8LDecoder* const dec) { + assert(dec->br.eos); + dec->status = VP8_STATUS_SUSPENDED; + dec->br = dec->saved_br; + dec->last_pixel = dec->saved_last_pixel; + if (dec->hdr.color_cache_size > 0) { + VP8LColorCacheCopy(&dec->hdr.saved_color_cache, &dec->hdr.color_cache); + } +} + +#define SYNC_EVERY_N_ROWS 8 // minimum number of rows between check-points +static int DecodeImageData(VP8LDecoder* const dec, uint32_t* const data, + int width, int height, int last_row, + ProcessRowsFunc process_func) { + int row = dec->last_pixel / width; + int col = dec->last_pixel % width; + VP8LBitReader* const br = &dec->br; + VP8LMetadata* const hdr = &dec->hdr; + uint32_t* src = data + dec->last_pixel; + uint32_t* last_cached = src; + uint32_t* const src_end = data + width * height; // End of data + uint32_t* const src_last = data + width * last_row; // Last pixel to decode + const int len_code_limit = NUM_LITERAL_CODES + NUM_LENGTH_CODES; + const int color_cache_limit = len_code_limit + hdr->color_cache_size; + int next_sync_row = dec->incremental ? row : 1 << 24; + VP8LColorCache* const color_cache = + (hdr->color_cache_size > 0) ? &hdr->color_cache : NULL; + const int mask = hdr->huffman_mask; + const HTreeGroup* htree_group = + (src < src_last) ? GetHtreeGroupForPos(hdr, col, row) : NULL; + assert(dec->last_row < last_row); + assert(src_last <= src_end); + + while (src < src_last) { + int code; + if (row >= next_sync_row) { + SaveState(dec, (int)(src - data)); + next_sync_row = row + SYNC_EVERY_N_ROWS; + } + // Only update when changing tile. Note we could use this test: + // if "((((prev_col ^ col) | prev_row ^ row)) > mask)" -> tile changed + // but that's actually slower and needs storing the previous col/row. + if ((col & mask) == 0) { + htree_group = GetHtreeGroupForPos(hdr, col, row); + } + assert(htree_group != NULL); + if (htree_group->is_trivial_code) { + *src = htree_group->literal_arb; + goto AdvanceByOne; + } + VP8LFillBitWindow(br); + if (htree_group->use_packed_table) { + code = ReadPackedSymbols(htree_group, br, src); + if (VP8LIsEndOfStream(br)) break; + if (code == PACKED_NON_LITERAL_CODE) goto AdvanceByOne; + } else { + code = ReadSymbol(htree_group->htrees[GREEN], br); + } + if (VP8LIsEndOfStream(br)) break; + if (code < NUM_LITERAL_CODES) { // Literal + if (htree_group->is_trivial_literal) { + *src = htree_group->literal_arb | (code << 8); + } else { + int red, blue, alpha; + red = ReadSymbol(htree_group->htrees[RED], br); + VP8LFillBitWindow(br); + blue = ReadSymbol(htree_group->htrees[BLUE], br); + alpha = ReadSymbol(htree_group->htrees[ALPHA], br); + if (VP8LIsEndOfStream(br)) break; + *src = ((uint32_t)alpha << 24) | (red << 16) | (code << 8) | blue; + } + AdvanceByOne: + ++src; + ++col; + if (col >= width) { + col = 0; + ++row; + if (process_func != NULL) { + if (row <= last_row && (row % NUM_ARGB_CACHE_ROWS == 0)) { + process_func(dec, row); + } + } + if (color_cache != NULL) { + while (last_cached < src) { + VP8LColorCacheInsert(color_cache, *last_cached++); + } + } + } + } else if (code < len_code_limit) { // Backward reference + int dist_code, dist; + const int length_sym = code - NUM_LITERAL_CODES; + const int length = GetCopyLength(length_sym, br); + const int dist_symbol = ReadSymbol(htree_group->htrees[DIST], br); + VP8LFillBitWindow(br); + dist_code = GetCopyDistance(dist_symbol, br); + dist = PlaneCodeToDistance(width, dist_code); + + if (VP8LIsEndOfStream(br)) break; + if (src - data < (ptrdiff_t)dist || src_end - src < (ptrdiff_t)length) { + goto Error; + } else { + CopyBlock32b(src, dist, length); + } + src += length; + col += length; + while (col >= width) { + col -= width; + ++row; + if (process_func != NULL) { + if (row <= last_row && (row % NUM_ARGB_CACHE_ROWS == 0)) { + process_func(dec, row); + } + } + } + // Because of the check done above (before 'src' was incremented by + // 'length'), the following holds true. + assert(src <= src_end); + if (col & mask) htree_group = GetHtreeGroupForPos(hdr, col, row); + if (color_cache != NULL) { + while (last_cached < src) { + VP8LColorCacheInsert(color_cache, *last_cached++); + } + } + } else if (code < color_cache_limit) { // Color cache + const int key = code - len_code_limit; + assert(color_cache != NULL); + while (last_cached < src) { + VP8LColorCacheInsert(color_cache, *last_cached++); + } + *src = VP8LColorCacheLookup(color_cache, key); + goto AdvanceByOne; + } else { // Not reached + goto Error; + } + } + + br->eos = VP8LIsEndOfStream(br); + // In incremental decoding: + // br->eos && src < src_last: if 'br' reached the end of the buffer and + // 'src_last' has not been reached yet, there is not enough data. 'dec' has to + // be reset until there is more data. + // !br->eos && src < src_last: this cannot happen as either the buffer is + // fully read, either enough has been read to reach 'src_last'. + // src >= src_last: 'src_last' is reached, all is fine. 'src' can actually go + // beyond 'src_last' in case the image is cropped and an LZ77 goes further. + // The buffer might have been enough or there is some left. 'br->eos' does + // not matter. + assert(!dec->incremental || (br->eos && src < src_last) || src >= src_last); + if (dec->incremental && br->eos && src < src_last) { + RestoreState(dec); + } else if ((dec->incremental && src >= src_last) || !br->eos) { + // Process the remaining rows corresponding to last row-block. + if (process_func != NULL) { + process_func(dec, row > last_row ? last_row : row); + } + dec->status = VP8_STATUS_OK; + dec->last_pixel = (int)(src - data); // end-of-scan marker + } else { + // if not incremental, and we are past the end of buffer (eos=1), then this + // is a real bitstream error. + goto Error; + } + return 1; + + Error: + return VP8LSetError(dec, VP8_STATUS_BITSTREAM_ERROR); +} + +// ----------------------------------------------------------------------------- +// VP8LTransform + +static void ClearTransform(VP8LTransform* const transform) { + WebPSafeFree(transform->data); + transform->data = NULL; +} + +// For security reason, we need to remap the color map to span +// the total possible bundled values, and not just the num_colors. +static int ExpandColorMap(int num_colors, VP8LTransform* const transform) { + int i; + const int final_num_colors = 1 << (8 >> transform->bits); + uint32_t* const new_color_map = + (uint32_t*)WebPSafeMalloc((uint64_t)final_num_colors, + sizeof(*new_color_map)); + if (new_color_map == NULL) { + return 0; + } else { + uint8_t* const data = (uint8_t*)transform->data; + uint8_t* const new_data = (uint8_t*)new_color_map; + new_color_map[0] = transform->data[0]; + for (i = 4; i < 4 * num_colors; ++i) { + // Equivalent to VP8LAddPixels(), on a byte-basis. + new_data[i] = (data[i] + new_data[i - 4]) & 0xff; + } + for (; i < 4 * final_num_colors; ++i) { + new_data[i] = 0; // black tail. + } + WebPSafeFree(transform->data); + transform->data = new_color_map; + } + return 1; +} + +static int ReadTransform(int* const xsize, int const* ysize, + VP8LDecoder* const dec) { + int ok = 1; + VP8LBitReader* const br = &dec->br; + VP8LTransform* transform = &dec->transforms[dec->next_transform]; + const VP8LImageTransformType type = + (VP8LImageTransformType)VP8LReadBits(br, 2); + + // Each transform type can only be present once in the stream. + if (dec->transforms_seen & (1U << type)) { + return 0; // Already there, let's not accept the second same transform. + } + dec->transforms_seen |= (1U << type); + + transform->type = type; + transform->xsize = *xsize; + transform->ysize = *ysize; + transform->data = NULL; + ++dec->next_transform; + assert(dec->next_transform <= NUM_TRANSFORMS); + + switch (type) { + case PREDICTOR_TRANSFORM: + case CROSS_COLOR_TRANSFORM: + transform->bits = + MIN_TRANSFORM_BITS + VP8LReadBits(br, NUM_TRANSFORM_BITS); + ok = DecodeImageStream(VP8LSubSampleSize(transform->xsize, + transform->bits), + VP8LSubSampleSize(transform->ysize, + transform->bits), + /*is_level0=*/0, dec, &transform->data); + break; + case COLOR_INDEXING_TRANSFORM: { + const int num_colors = VP8LReadBits(br, 8) + 1; + const int bits = (num_colors > 16) ? 0 + : (num_colors > 4) ? 1 + : (num_colors > 2) ? 2 + : 3; + *xsize = VP8LSubSampleSize(transform->xsize, bits); + transform->bits = bits; + ok = DecodeImageStream(num_colors, /*ysize=*/1, /*is_level0=*/0, dec, + &transform->data); + if (ok && !ExpandColorMap(num_colors, transform)) { + return VP8LSetError(dec, VP8_STATUS_OUT_OF_MEMORY); + } + break; + } + case SUBTRACT_GREEN_TRANSFORM: + break; + default: + assert(0); // can't happen + break; + } + + return ok; +} + +// ----------------------------------------------------------------------------- +// VP8LMetadata + +static void InitMetadata(VP8LMetadata* const hdr) { + assert(hdr != NULL); + memset(hdr, 0, sizeof(*hdr)); +} + +static void ClearMetadata(VP8LMetadata* const hdr) { + assert(hdr != NULL); + + WebPSafeFree(hdr->huffman_image); + VP8LHuffmanTablesDeallocate(&hdr->huffman_tables); + VP8LHtreeGroupsFree(hdr->htree_groups); + VP8LColorCacheClear(&hdr->color_cache); + VP8LColorCacheClear(&hdr->saved_color_cache); + InitMetadata(hdr); +} + +// ----------------------------------------------------------------------------- +// VP8LDecoder + +VP8LDecoder* VP8LNew(void) { + VP8LDecoder* const dec = (VP8LDecoder*)WebPSafeCalloc(1ULL, sizeof(*dec)); + if (dec == NULL) return NULL; + dec->status = VP8_STATUS_OK; + dec->state = READ_DIM; + + VP8LDspInit(); // Init critical function pointers. + + return dec; +} + +// Resets the decoder in its initial state, reclaiming memory. +// Preserves the dec->status value. +static void VP8LClear(VP8LDecoder* const dec) { + int i; + if (dec == NULL) return; + ClearMetadata(&dec->hdr); + + WebPSafeFree(dec->pixels); + dec->pixels = NULL; + for (i = 0; i < dec->next_transform; ++i) { + ClearTransform(&dec->transforms[i]); + } + dec->next_transform = 0; + dec->transforms_seen = 0; + + WebPSafeFree(dec->rescaler_memory); + dec->rescaler_memory = NULL; + + dec->output = NULL; // leave no trace behind +} + +void VP8LDelete(VP8LDecoder* const dec) { + if (dec != NULL) { + VP8LClear(dec); + WebPSafeFree(dec); + } +} + +static void UpdateDecoder(VP8LDecoder* const dec, int width, int height) { + VP8LMetadata* const hdr = &dec->hdr; + const int num_bits = hdr->huffman_subsample_bits; + dec->width = width; + dec->height = height; + + hdr->huffman_xsize = VP8LSubSampleSize(width, num_bits); + hdr->huffman_mask = (num_bits == 0) ? ~0 : (1 << num_bits) - 1; +} + +static int DecodeImageStream(int xsize, int ysize, + int is_level0, + VP8LDecoder* const dec, + uint32_t** const decoded_data) { + int ok = 1; + int transform_xsize = xsize; + int transform_ysize = ysize; + VP8LBitReader* const br = &dec->br; + VP8LMetadata* const hdr = &dec->hdr; + uint32_t* data = NULL; + int color_cache_bits = 0; + + // Read the transforms (may recurse). + if (is_level0) { + while (ok && VP8LReadBits(br, 1)) { + ok = ReadTransform(&transform_xsize, &transform_ysize, dec); + } + } + + // Color cache + if (ok && VP8LReadBits(br, 1)) { + color_cache_bits = VP8LReadBits(br, 4); + ok = (color_cache_bits >= 1 && color_cache_bits <= MAX_CACHE_BITS); + if (!ok) { + VP8LSetError(dec, VP8_STATUS_BITSTREAM_ERROR); + goto End; + } + } + + // Read the Huffman codes (may recurse). + ok = ok && ReadHuffmanCodes(dec, transform_xsize, transform_ysize, + color_cache_bits, is_level0); + if (!ok) { + VP8LSetError(dec, VP8_STATUS_BITSTREAM_ERROR); + goto End; + } + + // Finish setting up the color-cache + if (color_cache_bits > 0) { + hdr->color_cache_size = 1 << color_cache_bits; + if (!VP8LColorCacheInit(&hdr->color_cache, color_cache_bits)) { + ok = VP8LSetError(dec, VP8_STATUS_OUT_OF_MEMORY); + goto End; + } + } else { + hdr->color_cache_size = 0; + } + UpdateDecoder(dec, transform_xsize, transform_ysize); + + if (is_level0) { // level 0 complete + dec->state = READ_HDR; + goto End; + } + + { + const uint64_t total_size = (uint64_t)transform_xsize * transform_ysize; + data = (uint32_t*)WebPSafeMalloc(total_size, sizeof(*data)); + if (data == NULL) { + ok = VP8LSetError(dec, VP8_STATUS_OUT_OF_MEMORY); + goto End; + } + } + + // Use the Huffman trees to decode the LZ77 encoded data. + ok = DecodeImageData(dec, data, transform_xsize, transform_ysize, + transform_ysize, NULL); + ok = ok && !br->eos; + + End: + if (!ok) { + WebPSafeFree(data); + ClearMetadata(hdr); + } else { + if (decoded_data != NULL) { + *decoded_data = data; + } else { + // We allocate image data in this function only for transforms. At level 0 + // (that is: not the transforms), we shouldn't have allocated anything. + assert(data == NULL); + assert(is_level0); + } + dec->last_pixel = 0; // Reset for future DECODE_DATA_FUNC() calls. + if (!is_level0) ClearMetadata(hdr); // Clean up temporary data behind. + } + return ok; +} + +//------------------------------------------------------------------------------ +// Allocate internal buffers dec->pixels and dec->argb_cache. +static int AllocateInternalBuffers32b(VP8LDecoder* const dec, int final_width) { + const uint64_t num_pixels = (uint64_t)dec->width * dec->height; + // Scratch buffer corresponding to top-prediction row for transforming the + // first row in the row-blocks. Not needed for paletted alpha. + const uint64_t cache_top_pixels = (uint16_t)final_width; + // Scratch buffer for temporary BGRA storage. Not needed for paletted alpha. + const uint64_t cache_pixels = (uint64_t)final_width * NUM_ARGB_CACHE_ROWS; + const uint64_t total_num_pixels = + num_pixels + cache_top_pixels + cache_pixels; + + assert(dec->width <= final_width); + dec->pixels = (uint32_t*)WebPSafeMalloc(total_num_pixels, sizeof(uint32_t)); + if (dec->pixels == NULL) { + dec->argb_cache = NULL; // for soundness + return VP8LSetError(dec, VP8_STATUS_OUT_OF_MEMORY); + } + dec->argb_cache = dec->pixels + num_pixels + cache_top_pixels; + return 1; +} + +static int AllocateInternalBuffers8b(VP8LDecoder* const dec) { + const uint64_t total_num_pixels = (uint64_t)dec->width * dec->height; + dec->argb_cache = NULL; // for soundness + dec->pixels = (uint32_t*)WebPSafeMalloc(total_num_pixels, sizeof(uint8_t)); + if (dec->pixels == NULL) { + return VP8LSetError(dec, VP8_STATUS_OUT_OF_MEMORY); + } + return 1; +} + +//------------------------------------------------------------------------------ + +// Special row-processing that only stores the alpha data. +static void ExtractAlphaRows(VP8LDecoder* const dec, int last_row) { + int cur_row = dec->last_row; + int num_rows = last_row - cur_row; + const uint32_t* in = dec->pixels + dec->width * cur_row; + + assert(last_row <= dec->io->crop_bottom); + while (num_rows > 0) { + const int num_rows_to_process = + (num_rows > NUM_ARGB_CACHE_ROWS) ? NUM_ARGB_CACHE_ROWS : num_rows; + // Extract alpha (which is stored in the green plane). + ALPHDecoder* const alph_dec = (ALPHDecoder*)dec->io->opaque; + uint8_t* const output = alph_dec->output; + const int width = dec->io->width; // the final width (!= dec->width) + const int cache_pixs = width * num_rows_to_process; + uint8_t* const dst = output + width * cur_row; + const uint32_t* const src = dec->argb_cache; + ApplyInverseTransforms(dec, cur_row, num_rows_to_process, in); + WebPExtractGreen(src, dst, cache_pixs); + AlphaApplyFilter(alph_dec, + cur_row, cur_row + num_rows_to_process, dst, width); + num_rows -= num_rows_to_process; + in += num_rows_to_process * dec->width; + cur_row += num_rows_to_process; + } + assert(cur_row == last_row); + dec->last_row = dec->last_out_row = last_row; +} + +int VP8LDecodeAlphaHeader(ALPHDecoder* const alph_dec, + const uint8_t* const data, size_t data_size) { + int ok = 0; + VP8LDecoder* dec = VP8LNew(); + + if (dec == NULL) return 0; + + assert(alph_dec != NULL); + + dec->width = alph_dec->width; + dec->height = alph_dec->height; + dec->io = &alph_dec->io; + dec->io->opaque = alph_dec; + dec->io->width = alph_dec->width; + dec->io->height = alph_dec->height; + + dec->status = VP8_STATUS_OK; + VP8LInitBitReader(&dec->br, data, data_size); + + if (!DecodeImageStream(alph_dec->width, alph_dec->height, /*is_level0=*/1, + dec, /*decoded_data=*/NULL)) { + goto Err; + } + + // Special case: if alpha data uses only the color indexing transform and + // doesn't use color cache (a frequent case), we will use DecodeAlphaData() + // method that only needs allocation of 1 byte per pixel (alpha channel). + if (dec->next_transform == 1 && + dec->transforms[0].type == COLOR_INDEXING_TRANSFORM && + Is8bOptimizable(&dec->hdr)) { + alph_dec->use_8b_decode = 1; + ok = AllocateInternalBuffers8b(dec); + } else { + // Allocate internal buffers (note that dec->width may have changed here). + alph_dec->use_8b_decode = 0; + ok = AllocateInternalBuffers32b(dec, alph_dec->width); + } + + if (!ok) goto Err; + + // Only set here, once we are sure it is valid (to avoid thread races). + alph_dec->vp8l_dec = dec; + return 1; + + Err: + VP8LDelete(dec); + return 0; +} + +int VP8LDecodeAlphaImageStream(ALPHDecoder* const alph_dec, int last_row) { + VP8LDecoder* const dec = alph_dec->vp8l_dec; + assert(dec != NULL); + assert(last_row <= dec->height); + + if (dec->last_row >= last_row) { + return 1; // done + } + + if (!alph_dec->use_8b_decode) WebPInitAlphaProcessing(); + + // Decode (with special row processing). + return alph_dec->use_8b_decode ? + DecodeAlphaData(dec, (uint8_t*)dec->pixels, dec->width, dec->height, + last_row) : + DecodeImageData(dec, dec->pixels, dec->width, dec->height, + last_row, ExtractAlphaRows); +} + +//------------------------------------------------------------------------------ + +int VP8LDecodeHeader(VP8LDecoder* const dec, VP8Io* const io) { + int width, height, has_alpha; + + if (dec == NULL) return 0; + if (io == NULL) { + return VP8LSetError(dec, VP8_STATUS_INVALID_PARAM); + } + + dec->io = io; + dec->status = VP8_STATUS_OK; + VP8LInitBitReader(&dec->br, io->data, io->data_size); + if (!ReadImageInfo(&dec->br, &width, &height, &has_alpha)) { + VP8LSetError(dec, VP8_STATUS_BITSTREAM_ERROR); + goto Error; + } + dec->state = READ_DIM; + io->width = width; + io->height = height; + + if (!DecodeImageStream(width, height, /*is_level0=*/1, dec, + /*decoded_data=*/NULL)) { + goto Error; + } + return 1; + + Error: + VP8LClear(dec); + assert(dec->status != VP8_STATUS_OK); + return 0; +} + +int VP8LDecodeImage(VP8LDecoder* const dec) { + VP8Io* io = NULL; + WebPDecParams* params = NULL; + + if (dec == NULL) return 0; + + assert(dec->hdr.huffman_tables.root.start != NULL); + assert(dec->hdr.htree_groups != NULL); + assert(dec->hdr.num_htree_groups > 0); + + io = dec->io; + assert(io != NULL); + params = (WebPDecParams*)io->opaque; + assert(params != NULL); + + // Initialization. + if (dec->state != READ_DATA) { + dec->output = params->output; + assert(dec->output != NULL); + + if (!WebPIoInitFromOptions(params->options, io, MODE_BGRA)) { + VP8LSetError(dec, VP8_STATUS_INVALID_PARAM); + goto Err; + } + + if (!AllocateInternalBuffers32b(dec, io->width)) goto Err; + +#if !defined(WEBP_REDUCE_SIZE) + if (io->use_scaling && !AllocateAndInitRescaler(dec, io)) goto Err; +#else + if (io->use_scaling) { + VP8LSetError(dec, VP8_STATUS_INVALID_PARAM); + goto Err; + } +#endif + if (io->use_scaling || WebPIsPremultipliedMode(dec->output->colorspace)) { + // need the alpha-multiply functions for premultiplied output or rescaling + WebPInitAlphaProcessing(); + } + + if (!WebPIsRGBMode(dec->output->colorspace)) { + WebPInitConvertARGBToYUV(); + if (dec->output->u.YUVA.a != NULL) WebPInitAlphaProcessing(); + } + if (dec->incremental) { + if (dec->hdr.color_cache_size > 0 && + dec->hdr.saved_color_cache.colors == NULL) { + if (!VP8LColorCacheInit(&dec->hdr.saved_color_cache, + dec->hdr.color_cache.hash_bits)) { + VP8LSetError(dec, VP8_STATUS_OUT_OF_MEMORY); + goto Err; + } + } + } + dec->state = READ_DATA; + } + + // Decode. + if (!DecodeImageData(dec, dec->pixels, dec->width, dec->height, + io->crop_bottom, ProcessRows)) { + goto Err; + } + + params->last_y = dec->last_out_row; + return 1; + + Err: + VP8LClear(dec); + assert(dec->status != VP8_STATUS_OK); + return 0; +} + +//------------------------------------------------------------------------------ diff --git a/packages/core/src/zig/vendor/libwebp/src/dec/vp8li_dec.h b/packages/core/src/zig/vendor/libwebp/src/dec/vp8li_dec.h new file mode 100644 index 0000000000..4e2eadb3e4 --- /dev/null +++ b/packages/core/src/zig/vendor/libwebp/src/dec/vp8li_dec.h @@ -0,0 +1,150 @@ +// Copyright 2012 Google Inc. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the COPYING file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +// ----------------------------------------------------------------------------- +// +// Lossless decoder: internal header. +// +// Author: Skal (pascal.massimino@gmail.com) +// Vikas Arora(vikaas.arora@gmail.com) + +#ifndef WEBP_DEC_VP8LI_DEC_H_ +#define WEBP_DEC_VP8LI_DEC_H_ + +#include // for memcpy() + +#include "src/dec/vp8_dec.h" +#include "src/dec/webpi_dec.h" +#include "src/utils/bit_reader_utils.h" +#include "src/utils/color_cache_utils.h" +#include "src/utils/huffman_utils.h" +#include "src/utils/rescaler_utils.h" +#include "src/webp/decode.h" +#include "src/webp/format_constants.h" +#include "src/webp/types.h" + +#ifdef __cplusplus +extern "C" { +#endif + +typedef enum { + READ_DATA = 0, + READ_HDR = 1, + READ_DIM = 2 +} VP8LDecodeState; + +typedef struct VP8LTransform VP8LTransform; +struct VP8LTransform { + VP8LImageTransformType type; // transform type. + int bits; // subsampling bits defining transform window. + int xsize; // transform window X index. + int ysize; // transform window Y index. + uint32_t* data; // transform data. +}; + +typedef struct { + int color_cache_size; + VP8LColorCache color_cache; + VP8LColorCache saved_color_cache; // for incremental + + int huffman_mask; + int huffman_subsample_bits; + int huffman_xsize; + uint32_t* huffman_image; + int num_htree_groups; + HTreeGroup* htree_groups; + HuffmanTables huffman_tables; +} VP8LMetadata; + +typedef struct VP8LDecoder VP8LDecoder; +struct VP8LDecoder { + VP8StatusCode status; + VP8LDecodeState state; + VP8Io* io; + + const WebPDecBuffer* output; // shortcut to io->opaque->output + + uint32_t* pixels; // Internal data: either uint8_t* for alpha + // or uint32_t* for BGRA. + uint32_t* argb_cache; // Scratch buffer for temporary BGRA storage. + + VP8LBitReader br; + int incremental; // if true, incremental decoding is expected + VP8LBitReader saved_br; // note: could be local variables too + int saved_last_pixel; + + int width; + int height; + int last_row; // last input row decoded so far. + int last_pixel; // last pixel decoded so far. However, it may + // not be transformed, scaled and + // color-converted yet. + int last_out_row; // last row output so far. + + VP8LMetadata hdr; + + int next_transform; + VP8LTransform transforms[NUM_TRANSFORMS]; + // or'd bitset storing the transforms types. + uint32_t transforms_seen; + + uint8_t* rescaler_memory; // Working memory for rescaling work. + WebPRescaler* rescaler; // Common rescaler for all channels. +}; + +//------------------------------------------------------------------------------ +// internal functions. Not public. + +struct ALPHDecoder; // Defined in dec/alphai.h. + +// in vp8l.c + +// Decodes image header for alpha data stored using lossless compression. +// Returns false in case of error. +WEBP_NODISCARD int VP8LDecodeAlphaHeader(struct ALPHDecoder* const alph_dec, + const uint8_t* const data, + size_t data_size); + +// Decodes *at least* 'last_row' rows of alpha. If some of the initial rows are +// already decoded in previous call(s), it will resume decoding from where it +// was paused. +// Returns false in case of bitstream error. +WEBP_NODISCARD int VP8LDecodeAlphaImageStream( + struct ALPHDecoder* const alph_dec, int last_row); + +// Allocates and initialize a new lossless decoder instance. +WEBP_NODISCARD VP8LDecoder* VP8LNew(void); + +// Decodes the image header. Returns false in case of error. +WEBP_NODISCARD int VP8LDecodeHeader(VP8LDecoder* const dec, VP8Io* const io); + +// Decodes an image. It's required to decode the lossless header before calling +// this function. Returns false in case of error, with updated dec->status. +WEBP_NODISCARD int VP8LDecodeImage(VP8LDecoder* const dec); + +// Clears and deallocate a lossless decoder instance. +void VP8LDelete(VP8LDecoder* const dec); + +// Helper function for reading the different Huffman codes and storing them in +// 'huffman_tables' and 'htree_groups'. +// If mapping is NULL 'num_htree_groups_max' must equal 'num_htree_groups'. +// If it is not NULL, it maps 'num_htree_groups_max' indices to the +// 'num_htree_groups' groups. If 'num_htree_groups_max' > 'num_htree_groups', +// some of those indices map to -1. This is used for non-balanced codes to +// limit memory usage. +WEBP_NODISCARD int ReadHuffmanCodesHelper( + int color_cache_bits, int num_htree_groups, int num_htree_groups_max, + const int* const mapping, VP8LDecoder* const dec, + HuffmanTables* const huffman_tables, HTreeGroup** const htree_groups); + +//------------------------------------------------------------------------------ + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif // WEBP_DEC_VP8LI_DEC_H_ diff --git a/packages/core/src/zig/vendor/libwebp/src/dec/webp_dec.c b/packages/core/src/zig/vendor/libwebp/src/dec/webp_dec.c new file mode 100644 index 0000000000..5e7c23feb2 --- /dev/null +++ b/packages/core/src/zig/vendor/libwebp/src/dec/webp_dec.c @@ -0,0 +1,931 @@ +// Copyright 2010 Google Inc. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the COPYING file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +// ----------------------------------------------------------------------------- +// +// Main decoding functions for WEBP images. +// +// Author: Skal (pascal.massimino@gmail.com) + +#include +#include +#include + +#include "src/dec/common_dec.h" +#include "src/dec/vp8_dec.h" +#include "src/dec/vp8i_dec.h" +#include "src/dec/vp8li_dec.h" +#include "src/dec/webpi_dec.h" +#include "src/utils/rescaler_utils.h" +#include "src/utils/utils.h" +#include "src/webp/decode.h" +#include "src/webp/format_constants.h" +#include "src/webp/mux_types.h" // ALPHA_FLAG +#include "src/webp/types.h" + +//------------------------------------------------------------------------------ +// RIFF layout is: +// Offset tag +// 0...3 "RIFF" 4-byte tag +// 4...7 size of image data (including metadata) starting at offset 8 +// 8...11 "WEBP" our form-type signature +// The RIFF container (12 bytes) is followed by appropriate chunks: +// 12..15 "VP8 ": 4-bytes tags, signaling the use of VP8 video format +// 16..19 size of the raw VP8 image data, starting at offset 20 +// 20.... the VP8 bytes +// Or, +// 12..15 "VP8L": 4-bytes tags, signaling the use of VP8L lossless format +// 16..19 size of the raw VP8L image data, starting at offset 20 +// 20.... the VP8L bytes +// Or, +// 12..15 "VP8X": 4-bytes tags, describing the extended-VP8 chunk. +// 16..19 size of the VP8X chunk starting at offset 20. +// 20..23 VP8X flags bit-map corresponding to the chunk-types present. +// 24..26 Width of the Canvas Image. +// 27..29 Height of the Canvas Image. +// There can be extra chunks after the "VP8X" chunk (ICCP, ANMF, VP8, VP8L, +// XMP, EXIF ...) +// All sizes are in little-endian order. +// Note: chunk data size must be padded to multiple of 2 when written. + +// Validates the RIFF container (if detected) and skips over it. +// If a RIFF container is detected, returns: +// VP8_STATUS_BITSTREAM_ERROR for invalid header, +// VP8_STATUS_NOT_ENOUGH_DATA for truncated data if have_all_data is true, +// and VP8_STATUS_OK otherwise. +// In case there are not enough bytes (partial RIFF container), return 0 for +// *riff_size. Else return the RIFF size extracted from the header. +static VP8StatusCode ParseRIFF(const uint8_t** const data, + size_t* const data_size, int have_all_data, + size_t* const riff_size) { + assert(data != NULL); + assert(data_size != NULL); + assert(riff_size != NULL); + + *riff_size = 0; // Default: no RIFF present. + if (*data_size >= RIFF_HEADER_SIZE && !memcmp(*data, "RIFF", TAG_SIZE)) { + if (memcmp(*data + 8, "WEBP", TAG_SIZE)) { + return VP8_STATUS_BITSTREAM_ERROR; // Wrong image file signature. + } else { + const uint32_t size = GetLE32(*data + TAG_SIZE); + // Check that we have at least one chunk (i.e "WEBP" + "VP8?nnnn"). + if (size < TAG_SIZE + CHUNK_HEADER_SIZE) { + return VP8_STATUS_BITSTREAM_ERROR; + } + if (size > MAX_CHUNK_PAYLOAD) { + return VP8_STATUS_BITSTREAM_ERROR; + } + if (have_all_data && (size > *data_size - CHUNK_HEADER_SIZE)) { + return VP8_STATUS_NOT_ENOUGH_DATA; // Truncated bitstream. + } + // We have a RIFF container. Skip it. + *riff_size = size; + *data += RIFF_HEADER_SIZE; + *data_size -= RIFF_HEADER_SIZE; + } + } + return VP8_STATUS_OK; +} + +// Validates the VP8X header and skips over it. +// Returns VP8_STATUS_BITSTREAM_ERROR for invalid VP8X header, +// VP8_STATUS_NOT_ENOUGH_DATA in case of insufficient data, and +// VP8_STATUS_OK otherwise. +// If a VP8X chunk is found, found_vp8x is set to true and *width_ptr, +// *height_ptr and *flags_ptr are set to the corresponding values extracted +// from the VP8X chunk. +static VP8StatusCode ParseVP8X(const uint8_t** const data, + size_t* const data_size, + int* const found_vp8x, + int* const width_ptr, int* const height_ptr, + uint32_t* const flags_ptr) { + const uint32_t vp8x_size = CHUNK_HEADER_SIZE + VP8X_CHUNK_SIZE; + assert(data != NULL); + assert(data_size != NULL); + assert(found_vp8x != NULL); + + *found_vp8x = 0; + + if (*data_size < CHUNK_HEADER_SIZE) { + return VP8_STATUS_NOT_ENOUGH_DATA; // Insufficient data. + } + + if (!memcmp(*data, "VP8X", TAG_SIZE)) { + int width, height; + uint32_t flags; + const uint32_t chunk_size = GetLE32(*data + TAG_SIZE); + if (chunk_size != VP8X_CHUNK_SIZE) { + return VP8_STATUS_BITSTREAM_ERROR; // Wrong chunk size. + } + + // Verify if enough data is available to validate the VP8X chunk. + if (*data_size < vp8x_size) { + return VP8_STATUS_NOT_ENOUGH_DATA; // Insufficient data. + } + flags = GetLE32(*data + 8); + width = 1 + GetLE24(*data + 12); + height = 1 + GetLE24(*data + 15); + if (width * (uint64_t)height >= MAX_IMAGE_AREA) { + return VP8_STATUS_BITSTREAM_ERROR; // image is too large + } + + if (flags_ptr != NULL) *flags_ptr = flags; + if (width_ptr != NULL) *width_ptr = width; + if (height_ptr != NULL) *height_ptr = height; + // Skip over VP8X header bytes. + *data += vp8x_size; + *data_size -= vp8x_size; + *found_vp8x = 1; + } + return VP8_STATUS_OK; +} + +// Skips to the next VP8/VP8L chunk header in the data given the size of the +// RIFF chunk 'riff_size'. +// Returns VP8_STATUS_BITSTREAM_ERROR if any invalid chunk size is encountered, +// VP8_STATUS_NOT_ENOUGH_DATA in case of insufficient data, and +// VP8_STATUS_OK otherwise. +// If an alpha chunk is found, *alpha_data and *alpha_size are set +// appropriately. +static VP8StatusCode ParseOptionalChunks(const uint8_t** const data, + size_t* const data_size, + size_t const riff_size, + const uint8_t** const alpha_data, + size_t* const alpha_size) { + const uint8_t* buf; + size_t buf_size; + uint32_t total_size = TAG_SIZE + // "WEBP". + CHUNK_HEADER_SIZE + // "VP8Xnnnn". + VP8X_CHUNK_SIZE; // data. + assert(data != NULL); + assert(data_size != NULL); + buf = *data; + buf_size = *data_size; + + assert(alpha_data != NULL); + assert(alpha_size != NULL); + *alpha_data = NULL; + *alpha_size = 0; + + while (1) { + uint32_t chunk_size; + uint32_t disk_chunk_size; // chunk_size with padding + + *data = buf; + *data_size = buf_size; + + if (buf_size < CHUNK_HEADER_SIZE) { // Insufficient data. + return VP8_STATUS_NOT_ENOUGH_DATA; + } + + chunk_size = GetLE32(buf + TAG_SIZE); + if (chunk_size > MAX_CHUNK_PAYLOAD) { + return VP8_STATUS_BITSTREAM_ERROR; // Not a valid chunk size. + } + // For odd-sized chunk-payload, there's one byte padding at the end. + disk_chunk_size = (CHUNK_HEADER_SIZE + chunk_size + 1) & ~1u; + total_size += disk_chunk_size; + + // Check that total bytes skipped so far does not exceed riff_size. + if (riff_size > 0 && (total_size > riff_size)) { + return VP8_STATUS_BITSTREAM_ERROR; // Not a valid chunk size. + } + + // Start of a (possibly incomplete) VP8/VP8L chunk implies that we have + // parsed all the optional chunks. + // Note: This check must occur before the check 'buf_size < disk_chunk_size' + // below to allow incomplete VP8/VP8L chunks. + if (!memcmp(buf, "VP8 ", TAG_SIZE) || + !memcmp(buf, "VP8L", TAG_SIZE)) { + return VP8_STATUS_OK; + } + + if (buf_size < disk_chunk_size) { // Insufficient data. + return VP8_STATUS_NOT_ENOUGH_DATA; + } + + if (!memcmp(buf, "ALPH", TAG_SIZE)) { // A valid ALPH header. + *alpha_data = buf + CHUNK_HEADER_SIZE; + *alpha_size = chunk_size; + } + + // We have a full and valid chunk; skip it. + buf += disk_chunk_size; + buf_size -= disk_chunk_size; + } +} + +// Validates the VP8/VP8L Header ("VP8 nnnn" or "VP8L nnnn") and skips over it. +// Returns VP8_STATUS_BITSTREAM_ERROR for invalid (chunk larger than +// riff_size) VP8/VP8L header, +// VP8_STATUS_NOT_ENOUGH_DATA in case of insufficient data, and +// VP8_STATUS_OK otherwise. +// If a VP8/VP8L chunk is found, *chunk_size is set to the total number of bytes +// extracted from the VP8/VP8L chunk header. +// The flag '*is_lossless' is set to 1 in case of VP8L chunk / raw VP8L data. +static VP8StatusCode ParseVP8Header(const uint8_t** const data_ptr, + size_t* const data_size, int have_all_data, + size_t riff_size, size_t* const chunk_size, + int* const is_lossless) { + const uint8_t* const data = *data_ptr; + const int is_vp8 = !memcmp(data, "VP8 ", TAG_SIZE); + const int is_vp8l = !memcmp(data, "VP8L", TAG_SIZE); + const uint32_t minimal_size = + TAG_SIZE + CHUNK_HEADER_SIZE; // "WEBP" + "VP8 nnnn" OR + // "WEBP" + "VP8Lnnnn" + assert(data != NULL); + assert(data_size != NULL); + assert(chunk_size != NULL); + assert(is_lossless != NULL); + + if (*data_size < CHUNK_HEADER_SIZE) { + return VP8_STATUS_NOT_ENOUGH_DATA; // Insufficient data. + } + + if (is_vp8 || is_vp8l) { + // Bitstream contains VP8/VP8L header. + const uint32_t size = GetLE32(data + TAG_SIZE); + if ((riff_size >= minimal_size) && (size > riff_size - minimal_size)) { + return VP8_STATUS_BITSTREAM_ERROR; // Inconsistent size information. + } + if (have_all_data && (size > *data_size - CHUNK_HEADER_SIZE)) { + return VP8_STATUS_NOT_ENOUGH_DATA; // Truncated bitstream. + } + // Skip over CHUNK_HEADER_SIZE bytes from VP8/VP8L Header. + *chunk_size = size; + *data_ptr += CHUNK_HEADER_SIZE; + *data_size -= CHUNK_HEADER_SIZE; + *is_lossless = is_vp8l; + } else { + // Raw VP8/VP8L bitstream (no header). + *is_lossless = VP8LCheckSignature(data, *data_size); + *chunk_size = *data_size; + } + + return VP8_STATUS_OK; +} + +//------------------------------------------------------------------------------ + +// Fetch '*width', '*height', '*has_alpha' and fill out 'headers' based on +// 'data'. All the output parameters may be NULL. If 'headers' is NULL only the +// minimal amount will be read to fetch the remaining parameters. +// If 'headers' is non-NULL this function will attempt to locate both alpha +// data (with or without a VP8X chunk) and the bitstream chunk (VP8/VP8L). +// Note: The following chunk sequences (before the raw VP8/VP8L data) are +// considered valid by this function: +// RIFF + VP8(L) +// RIFF + VP8X + (optional chunks) + VP8(L) +// ALPH + VP8 <-- Not a valid WebP format: only allowed for internal purpose. +// VP8(L) <-- Not a valid WebP format: only allowed for internal purpose. +static VP8StatusCode ParseHeadersInternal(const uint8_t* data, + size_t data_size, + int* const width, + int* const height, + int* const has_alpha, + int* const has_animation, + int* const format, + WebPHeaderStructure* const headers) { + int canvas_width = 0; + int canvas_height = 0; + int image_width = 0; + int image_height = 0; + int found_riff = 0; + int found_vp8x = 0; + int animation_present = 0; + const int have_all_data = (headers != NULL) ? headers->have_all_data : 0; + + VP8StatusCode status; + WebPHeaderStructure hdrs; + + if (data == NULL || data_size < RIFF_HEADER_SIZE) { + return VP8_STATUS_NOT_ENOUGH_DATA; + } + memset(&hdrs, 0, sizeof(hdrs)); + hdrs.data = data; + hdrs.data_size = data_size; + + // Skip over RIFF header. + status = ParseRIFF(&data, &data_size, have_all_data, &hdrs.riff_size); + if (status != VP8_STATUS_OK) { + return status; // Wrong RIFF header / insufficient data. + } + found_riff = (hdrs.riff_size > 0); + + // Skip over VP8X. + { + uint32_t flags = 0; + status = ParseVP8X(&data, &data_size, &found_vp8x, + &canvas_width, &canvas_height, &flags); + if (status != VP8_STATUS_OK) { + return status; // Wrong VP8X / insufficient data. + } + animation_present = !!(flags & ANIMATION_FLAG); + if (!found_riff && found_vp8x) { + // Note: This restriction may be removed in the future, if it becomes + // necessary to send VP8X chunk to the decoder. + return VP8_STATUS_BITSTREAM_ERROR; + } + if (has_alpha != NULL) *has_alpha = !!(flags & ALPHA_FLAG); + if (has_animation != NULL) *has_animation = animation_present; + if (format != NULL) *format = 0; // default = undefined + + image_width = canvas_width; + image_height = canvas_height; + if (found_vp8x && animation_present && headers == NULL) { + status = VP8_STATUS_OK; + goto ReturnWidthHeight; // Just return features from VP8X header. + } + } + + if (data_size < TAG_SIZE) { + status = VP8_STATUS_NOT_ENOUGH_DATA; + goto ReturnWidthHeight; + } + + // Skip over optional chunks if data started with "RIFF + VP8X" or "ALPH". + if ((found_riff && found_vp8x) || + (!found_riff && !found_vp8x && !memcmp(data, "ALPH", TAG_SIZE))) { + status = ParseOptionalChunks(&data, &data_size, hdrs.riff_size, + &hdrs.alpha_data, &hdrs.alpha_data_size); + if (status != VP8_STATUS_OK) { + goto ReturnWidthHeight; // Invalid chunk size / insufficient data. + } + } + + // Skip over VP8/VP8L header. + status = ParseVP8Header(&data, &data_size, have_all_data, hdrs.riff_size, + &hdrs.compressed_size, &hdrs.is_lossless); + if (status != VP8_STATUS_OK) { + goto ReturnWidthHeight; // Wrong VP8/VP8L chunk-header / insufficient data. + } + if (hdrs.compressed_size > MAX_CHUNK_PAYLOAD) { + return VP8_STATUS_BITSTREAM_ERROR; + } + + if (format != NULL && !animation_present) { + *format = hdrs.is_lossless ? 2 : 1; + } + + if (!hdrs.is_lossless) { + if (data_size < VP8_FRAME_HEADER_SIZE) { + status = VP8_STATUS_NOT_ENOUGH_DATA; + goto ReturnWidthHeight; + } + // Validates raw VP8 data. + if (!VP8GetInfo(data, data_size, (uint32_t)hdrs.compressed_size, + &image_width, &image_height)) { + return VP8_STATUS_BITSTREAM_ERROR; + } + } else { + if (data_size < VP8L_FRAME_HEADER_SIZE) { + status = VP8_STATUS_NOT_ENOUGH_DATA; + goto ReturnWidthHeight; + } + // Validates raw VP8L data. + if (!VP8LGetInfo(data, data_size, &image_width, &image_height, has_alpha)) { + return VP8_STATUS_BITSTREAM_ERROR; + } + } + // Validates image size coherency. + if (found_vp8x) { + if (canvas_width != image_width || canvas_height != image_height) { + return VP8_STATUS_BITSTREAM_ERROR; + } + } + if (headers != NULL) { + *headers = hdrs; + headers->offset = data - headers->data; + assert((uint64_t)(data - headers->data) < MAX_CHUNK_PAYLOAD); + assert(headers->offset == headers->data_size - data_size); + } + ReturnWidthHeight: + if (status == VP8_STATUS_OK || + (status == VP8_STATUS_NOT_ENOUGH_DATA && found_vp8x && headers == NULL)) { + if (has_alpha != NULL) { + // If the data did not contain a VP8X/VP8L chunk the only definitive way + // to set this is by looking for alpha data (from an ALPH chunk). + *has_alpha |= (hdrs.alpha_data != NULL); + } + if (width != NULL) *width = image_width; + if (height != NULL) *height = image_height; + return VP8_STATUS_OK; + } else { + return status; + } +} + +VP8StatusCode WebPParseHeaders(WebPHeaderStructure* const headers) { + // status is marked volatile as a workaround for a clang-3.8 (aarch64) bug + volatile VP8StatusCode status; + int has_animation = 0; + assert(headers != NULL); + // fill out headers, ignore width/height/has_alpha. + status = ParseHeadersInternal(headers->data, headers->data_size, + NULL, NULL, NULL, &has_animation, + NULL, headers); + if (status == VP8_STATUS_OK || status == VP8_STATUS_NOT_ENOUGH_DATA) { + // The WebPDemux API + libwebp can be used to decode individual + // uncomposited frames or the WebPAnimDecoder can be used to fully + // reconstruct them (see webp/demux.h). + if (has_animation) { + status = VP8_STATUS_UNSUPPORTED_FEATURE; + } + } + return status; +} + +//------------------------------------------------------------------------------ +// WebPDecParams + +void WebPResetDecParams(WebPDecParams* const params) { + if (params != NULL) { + memset(params, 0, sizeof(*params)); + } +} + +//------------------------------------------------------------------------------ +// "Into" decoding variants + +// Main flow +WEBP_NODISCARD static VP8StatusCode DecodeInto(const uint8_t* const data, + size_t data_size, + WebPDecParams* const params) { + VP8StatusCode status; + VP8Io io; + WebPHeaderStructure headers; + + headers.data = data; + headers.data_size = data_size; + headers.have_all_data = 1; + status = WebPParseHeaders(&headers); // Process Pre-VP8 chunks. + if (status != VP8_STATUS_OK) { + return status; + } + + assert(params != NULL); + if (!VP8InitIo(&io)) { + return VP8_STATUS_INVALID_PARAM; + } + io.data = headers.data + headers.offset; + io.data_size = headers.data_size - headers.offset; + WebPInitCustomIo(params, &io); // Plug the I/O functions. + + if (!headers.is_lossless) { + VP8Decoder* const dec = VP8New(); + if (dec == NULL) { + return VP8_STATUS_OUT_OF_MEMORY; + } + dec->alpha_data = headers.alpha_data; + dec->alpha_data_size = headers.alpha_data_size; + + // Decode bitstream header, update io->width/io->height. + if (!VP8GetHeaders(dec, &io)) { + status = dec->status; // An error occurred. Grab error status. + } else { + // Allocate/check output buffers. + status = WebPAllocateDecBuffer(io.width, io.height, params->options, + params->output); + if (status == VP8_STATUS_OK) { // Decode + // This change must be done before calling VP8Decode() + dec->mt_method = VP8GetThreadMethod(params->options, &headers, + io.width, io.height); + VP8InitDithering(params->options, dec); + if (!VP8Decode(dec, &io)) { + status = dec->status; + } + } + } + VP8Delete(dec); + } else { + VP8LDecoder* const dec = VP8LNew(); + if (dec == NULL) { + return VP8_STATUS_OUT_OF_MEMORY; + } + if (!VP8LDecodeHeader(dec, &io)) { + status = dec->status; // An error occurred. Grab error status. + } else { + // Allocate/check output buffers. + status = WebPAllocateDecBuffer(io.width, io.height, params->options, + params->output); + if (status == VP8_STATUS_OK) { // Decode + if (!VP8LDecodeImage(dec)) { + status = dec->status; + } + } + } + VP8LDelete(dec); + } + + if (status != VP8_STATUS_OK) { + WebPFreeDecBuffer(params->output); + } else { + if (params->options != NULL && params->options->flip) { + // This restores the original stride values if options->flip was used + // during the call to WebPAllocateDecBuffer above. + status = WebPFlipBuffer(params->output); + } + } + return status; +} + +// Helpers +WEBP_NODISCARD static uint8_t* DecodeIntoRGBABuffer(WEBP_CSP_MODE colorspace, + const uint8_t* const data, + size_t data_size, + uint8_t* const rgba, + int stride, size_t size) { + WebPDecParams params; + WebPDecBuffer buf; + if (rgba == NULL || !WebPInitDecBuffer(&buf)) { + return NULL; + } + WebPResetDecParams(¶ms); + params.output = &buf; + buf.colorspace = colorspace; + buf.u.RGBA.rgba = rgba; + buf.u.RGBA.stride = stride; + buf.u.RGBA.size = size; + buf.is_external_memory = 1; + if (DecodeInto(data, data_size, ¶ms) != VP8_STATUS_OK) { + return NULL; + } + return rgba; +} + +uint8_t* WebPDecodeRGBInto(const uint8_t* data, size_t data_size, + uint8_t* output, size_t size, int stride) { + return DecodeIntoRGBABuffer(MODE_RGB, data, data_size, output, stride, size); +} + +uint8_t* WebPDecodeRGBAInto(const uint8_t* data, size_t data_size, + uint8_t* output, size_t size, int stride) { + return DecodeIntoRGBABuffer(MODE_RGBA, data, data_size, output, stride, size); +} + +uint8_t* WebPDecodeARGBInto(const uint8_t* data, size_t data_size, + uint8_t* output, size_t size, int stride) { + return DecodeIntoRGBABuffer(MODE_ARGB, data, data_size, output, stride, size); +} + +uint8_t* WebPDecodeBGRInto(const uint8_t* data, size_t data_size, + uint8_t* output, size_t size, int stride) { + return DecodeIntoRGBABuffer(MODE_BGR, data, data_size, output, stride, size); +} + +uint8_t* WebPDecodeBGRAInto(const uint8_t* data, size_t data_size, + uint8_t* output, size_t size, int stride) { + return DecodeIntoRGBABuffer(MODE_BGRA, data, data_size, output, stride, size); +} + +uint8_t* WebPDecodeYUVInto(const uint8_t* data, size_t data_size, + uint8_t* luma, size_t luma_size, int luma_stride, + uint8_t* u, size_t u_size, int u_stride, + uint8_t* v, size_t v_size, int v_stride) { + WebPDecParams params; + WebPDecBuffer output; + if (luma == NULL || !WebPInitDecBuffer(&output)) return NULL; + WebPResetDecParams(¶ms); + params.output = &output; + output.colorspace = MODE_YUV; + output.u.YUVA.y = luma; + output.u.YUVA.y_stride = luma_stride; + output.u.YUVA.y_size = luma_size; + output.u.YUVA.u = u; + output.u.YUVA.u_stride = u_stride; + output.u.YUVA.u_size = u_size; + output.u.YUVA.v = v; + output.u.YUVA.v_stride = v_stride; + output.u.YUVA.v_size = v_size; + output.is_external_memory = 1; + if (DecodeInto(data, data_size, ¶ms) != VP8_STATUS_OK) { + return NULL; + } + return luma; +} + +//------------------------------------------------------------------------------ + +WEBP_NODISCARD static uint8_t* Decode(WEBP_CSP_MODE mode, + const uint8_t* const data, + size_t data_size, int* const width, + int* const height, + WebPDecBuffer* const keep_info) { + WebPDecParams params; + WebPDecBuffer output; + + if (!WebPInitDecBuffer(&output)) { + return NULL; + } + WebPResetDecParams(¶ms); + params.output = &output; + output.colorspace = mode; + + // Retrieve (and report back) the required dimensions from bitstream. + if (!WebPGetInfo(data, data_size, &output.width, &output.height)) { + return NULL; + } + if (width != NULL) *width = output.width; + if (height != NULL) *height = output.height; + + // Decode + if (DecodeInto(data, data_size, ¶ms) != VP8_STATUS_OK) { + return NULL; + } + if (keep_info != NULL) { // keep track of the side-info + WebPCopyDecBuffer(&output, keep_info); + } + // return decoded samples (don't clear 'output'!) + return WebPIsRGBMode(mode) ? output.u.RGBA.rgba : output.u.YUVA.y; +} + +uint8_t* WebPDecodeRGB(const uint8_t* data, size_t data_size, + int* width, int* height) { + return Decode(MODE_RGB, data, data_size, width, height, NULL); +} + +uint8_t* WebPDecodeRGBA(const uint8_t* data, size_t data_size, + int* width, int* height) { + return Decode(MODE_RGBA, data, data_size, width, height, NULL); +} + +uint8_t* WebPDecodeARGB(const uint8_t* data, size_t data_size, + int* width, int* height) { + return Decode(MODE_ARGB, data, data_size, width, height, NULL); +} + +uint8_t* WebPDecodeBGR(const uint8_t* data, size_t data_size, + int* width, int* height) { + return Decode(MODE_BGR, data, data_size, width, height, NULL); +} + +uint8_t* WebPDecodeBGRA(const uint8_t* data, size_t data_size, + int* width, int* height) { + return Decode(MODE_BGRA, data, data_size, width, height, NULL); +} + +uint8_t* WebPDecodeYUV(const uint8_t* data, size_t data_size, + int* width, int* height, uint8_t** u, uint8_t** v, + int* stride, int* uv_stride) { + // data, width and height are checked by Decode(). + if (u == NULL || v == NULL || stride == NULL || uv_stride == NULL) { + return NULL; + } + + { + WebPDecBuffer output; // only to preserve the side-infos + uint8_t* const out = Decode(MODE_YUV, data, data_size, + width, height, &output); + + if (out != NULL) { + const WebPYUVABuffer* const buf = &output.u.YUVA; + *u = buf->u; + *v = buf->v; + *stride = buf->y_stride; + *uv_stride = buf->u_stride; + assert(buf->u_stride == buf->v_stride); + } + return out; + } +} + +static void DefaultFeatures(WebPBitstreamFeatures* const features) { + assert(features != NULL); + memset(features, 0, sizeof(*features)); +} + +static VP8StatusCode GetFeatures(const uint8_t* const data, size_t data_size, + WebPBitstreamFeatures* const features) { + if (features == NULL || data == NULL) { + return VP8_STATUS_INVALID_PARAM; + } + DefaultFeatures(features); + + // Only parse enough of the data to retrieve the features. + return ParseHeadersInternal(data, data_size, + &features->width, &features->height, + &features->has_alpha, &features->has_animation, + &features->format, NULL); +} + +//------------------------------------------------------------------------------ +// WebPGetInfo() + +int WebPGetInfo(const uint8_t* data, size_t data_size, + int* width, int* height) { + WebPBitstreamFeatures features; + + if (GetFeatures(data, data_size, &features) != VP8_STATUS_OK) { + return 0; + } + + if (width != NULL) { + *width = features.width; + } + if (height != NULL) { + *height = features.height; + } + + return 1; +} + +//------------------------------------------------------------------------------ +// Advance decoding API + +int WebPInitDecoderConfigInternal(WebPDecoderConfig* config, + int version) { + if (WEBP_ABI_IS_INCOMPATIBLE(version, WEBP_DECODER_ABI_VERSION)) { + return 0; // version mismatch + } + if (config == NULL) { + return 0; + } + memset(config, 0, sizeof(*config)); + DefaultFeatures(&config->input); + if (!WebPInitDecBuffer(&config->output)) { + return 0; + } + return 1; +} + +static int WebPCheckCropDimensionsBasic(int x, int y, int w, int h) { + return !(x < 0 || y < 0 || w <= 0 || h <= 0); +} + +int WebPValidateDecoderConfig(const WebPDecoderConfig* config) { + const WebPDecoderOptions* options; + if (config == NULL) return 0; + if (!IsValidColorspace(config->output.colorspace)) { + return 0; + } + + options = &config->options; + // bypass_filtering, no_fancy_upsampling, use_cropping, use_scaling, + // use_threads, flip can be any integer and are interpreted as boolean. + + // Check for cropping. + if (options->use_cropping && !WebPCheckCropDimensionsBasic( + options->crop_left, options->crop_top, + options->crop_width, options->crop_height)) { + return 0; + } + // Check for scaling. + if (options->use_scaling && + (options->scaled_width < 0 || options->scaled_height < 0 || + (options->scaled_width == 0 && options->scaled_height == 0))) { + return 0; + } + + // In case the WebPBitstreamFeatures has been filled in, check further. + if (config->input.width > 0 || config->input.height > 0) { + int scaled_width = options->scaled_width; + int scaled_height = options->scaled_height; + if (options->use_cropping && + !WebPCheckCropDimensions(config->input.width, config->input.height, + options->crop_left, options->crop_top, + options->crop_width, options->crop_height)) { + return 0; + } + if (options->use_scaling && !WebPRescalerGetScaledDimensions( + config->input.width, config->input.height, + &scaled_width, &scaled_height)) { + return 0; + } + } + + // Check for dithering. + if (options->dithering_strength < 0 || options->dithering_strength > 100 || + options->alpha_dithering_strength < 0 || + options->alpha_dithering_strength > 100) { + return 0; + } + + return 1; +} + +VP8StatusCode WebPGetFeaturesInternal(const uint8_t* data, size_t data_size, + WebPBitstreamFeatures* features, + int version) { + if (WEBP_ABI_IS_INCOMPATIBLE(version, WEBP_DECODER_ABI_VERSION)) { + return VP8_STATUS_INVALID_PARAM; // version mismatch + } + if (features == NULL) { + return VP8_STATUS_INVALID_PARAM; + } + return GetFeatures(data, data_size, features); +} + +VP8StatusCode WebPDecode(const uint8_t* data, size_t data_size, + WebPDecoderConfig* config) { + WebPDecParams params; + VP8StatusCode status; + + if (config == NULL) { + return VP8_STATUS_INVALID_PARAM; + } + + status = GetFeatures(data, data_size, &config->input); + if (status != VP8_STATUS_OK) { + if (status == VP8_STATUS_NOT_ENOUGH_DATA) { + return VP8_STATUS_BITSTREAM_ERROR; // Not-enough-data treated as error. + } + return status; + } + + WebPResetDecParams(¶ms); + params.options = &config->options; + params.output = &config->output; + if (WebPAvoidSlowMemory(params.output, &config->input)) { + // decoding to slow memory: use a temporary in-mem buffer to decode into. + WebPDecBuffer in_mem_buffer; + if (!WebPInitDecBuffer(&in_mem_buffer)) { + return VP8_STATUS_INVALID_PARAM; + } + in_mem_buffer.colorspace = config->output.colorspace; + in_mem_buffer.width = config->input.width; + in_mem_buffer.height = config->input.height; + params.output = &in_mem_buffer; + status = DecodeInto(data, data_size, ¶ms); + if (status == VP8_STATUS_OK) { // do the slow-copy + status = WebPCopyDecBufferPixels(&in_mem_buffer, &config->output); + } + WebPFreeDecBuffer(&in_mem_buffer); + } else { + status = DecodeInto(data, data_size, ¶ms); + } + + return status; +} + +//------------------------------------------------------------------------------ +// Cropping and rescaling. + +int WebPCheckCropDimensions(int image_width, int image_height, + int x, int y, int w, int h) { + return WebPCheckCropDimensionsBasic(x, y, w, h) && + !(x >= image_width || w > image_width || w > image_width - x || + y >= image_height || h > image_height || h > image_height - y); +} + +int WebPIoInitFromOptions(const WebPDecoderOptions* const options, + VP8Io* const io, WEBP_CSP_MODE src_colorspace) { + const int W = io->width; + const int H = io->height; + int x = 0, y = 0, w = W, h = H; + + // Cropping + io->use_cropping = (options != NULL) && options->use_cropping; + if (io->use_cropping) { + w = options->crop_width; + h = options->crop_height; + x = options->crop_left; + y = options->crop_top; + if (!WebPIsRGBMode(src_colorspace)) { // only snap for YUV420 + x &= ~1; + y &= ~1; + } + if (!WebPCheckCropDimensions(W, H, x, y, w, h)) { + return 0; // out of frame boundary error + } + } + io->crop_left = x; + io->crop_top = y; + io->crop_right = x + w; + io->crop_bottom = y + h; + io->mb_w = w; + io->mb_h = h; + + // Scaling + io->use_scaling = (options != NULL) && options->use_scaling; + if (io->use_scaling) { + int scaled_width = options->scaled_width; + int scaled_height = options->scaled_height; + if (!WebPRescalerGetScaledDimensions(w, h, &scaled_width, &scaled_height)) { + return 0; + } + io->scaled_width = scaled_width; + io->scaled_height = scaled_height; + } + + // Filter + io->bypass_filtering = (options != NULL) && options->bypass_filtering; + + // Fancy upsampler +#ifdef FANCY_UPSAMPLING + io->fancy_upsampling = (options == NULL) || (!options->no_fancy_upsampling); +#endif + + if (io->use_scaling) { + // disable filter (only for large downscaling ratio). + io->bypass_filtering |= (io->scaled_width < W * 3 / 4) && + (io->scaled_height < H * 3 / 4); + io->fancy_upsampling = 0; + } + return 1; +} + +//------------------------------------------------------------------------------ diff --git a/packages/core/src/zig/vendor/libwebp/src/dec/webpi_dec.h b/packages/core/src/zig/vendor/libwebp/src/dec/webpi_dec.h new file mode 100644 index 0000000000..1929796483 --- /dev/null +++ b/packages/core/src/zig/vendor/libwebp/src/dec/webpi_dec.h @@ -0,0 +1,142 @@ +// Copyright 2011 Google Inc. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the COPYING file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +// ----------------------------------------------------------------------------- +// +// Internal header: WebP decoding parameters and custom IO on buffer +// +// Author: somnath@google.com (Somnath Banerjee) + +#ifndef WEBP_DEC_WEBPI_DEC_H_ +#define WEBP_DEC_WEBPI_DEC_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +#include + +#include "src/dec/vp8_dec.h" +#include "src/utils/rescaler_utils.h" +#include "src/webp/decode.h" +#include "src/webp/types.h" + +//------------------------------------------------------------------------------ +// WebPDecParams: Decoding output parameters. Transient internal object. + +typedef struct WebPDecParams WebPDecParams; +typedef int (*OutputFunc)(const VP8Io* const io, WebPDecParams* const p); +typedef int (*OutputAlphaFunc)(const VP8Io* const io, WebPDecParams* const p, + int expected_num_out_lines); +typedef int (*OutputRowFunc)(WebPDecParams* const p, int y_pos, + int max_out_lines); + +struct WebPDecParams { + WebPDecBuffer* output; // output buffer. + uint8_t* tmp_y, *tmp_u, *tmp_v; // cache for the fancy upsampler + // or used for tmp rescaling + + int last_y; // coordinate of the line that was last output + const WebPDecoderOptions* options; // if not NULL, use alt decoding features + + WebPRescaler* scaler_y, *scaler_u, *scaler_v, *scaler_a; // rescalers + void* memory; // overall scratch memory for the output work. + + OutputFunc emit; // output RGB or YUV samples + OutputAlphaFunc emit_alpha; // output alpha channel + OutputRowFunc emit_alpha_row; // output one line of rescaled alpha values +}; + +// Should be called first, before any use of the WebPDecParams object. +void WebPResetDecParams(WebPDecParams* const params); + +//------------------------------------------------------------------------------ +// Header parsing helpers + +// Structure storing a description of the RIFF headers. +typedef struct { + const uint8_t* data; // input buffer + size_t data_size; // input buffer size + int have_all_data; // true if all data is known to be available + size_t offset; // offset to main data chunk (VP8 or VP8L) + const uint8_t* alpha_data; // points to alpha chunk (if present) + size_t alpha_data_size; // alpha chunk size + size_t compressed_size; // VP8/VP8L compressed data size + size_t riff_size; // size of the riff payload (or 0 if absent) + int is_lossless; // true if a VP8L chunk is present +} WebPHeaderStructure; + +// Skips over all valid chunks prior to the first VP8/VP8L frame header. +// Returns: VP8_STATUS_OK, VP8_STATUS_BITSTREAM_ERROR (invalid header/chunk), +// VP8_STATUS_NOT_ENOUGH_DATA (partial input) or VP8_STATUS_UNSUPPORTED_FEATURE +// in the case of non-decodable features (animation for instance). +// In 'headers', compressed_size, offset, alpha_data, alpha_size, and lossless +// fields are updated appropriately upon success. +VP8StatusCode WebPParseHeaders(WebPHeaderStructure* const headers); + +//------------------------------------------------------------------------------ +// Misc utils + +// Returns true if crop dimensions are within image bounds. +int WebPCheckCropDimensions(int image_width, int image_height, + int x, int y, int w, int h); + +// Initializes VP8Io with custom setup, io and teardown functions. The default +// hooks will use the supplied 'params' as io->opaque handle. +void WebPInitCustomIo(WebPDecParams* const params, VP8Io* const io); + +// Setup crop_xxx fields, mb_w and mb_h in io. 'src_colorspace' refers +// to the *compressed* format, not the output one. +WEBP_NODISCARD int WebPIoInitFromOptions( + const WebPDecoderOptions* const options, VP8Io* const io, + WEBP_CSP_MODE src_colorspace); + +//------------------------------------------------------------------------------ +// Internal functions regarding WebPDecBuffer memory (in buffer.c). +// Don't really need to be externally visible for now. + +// Prepare 'buffer' with the requested initial dimensions width/height. +// If no external storage is supplied, initializes buffer by allocating output +// memory and setting up the stride information. Validate the parameters. Return +// an error code in case of problem (no memory, or invalid stride / size / +// dimension / etc.). If *options is not NULL, also verify that the options' +// parameters are valid and apply them to the width/height dimensions of the +// output buffer. This takes cropping / scaling / rotation into account. +// Also incorporates the options->flip flag to flip the buffer parameters if +// needed. +VP8StatusCode WebPAllocateDecBuffer(int width, int height, + const WebPDecoderOptions* const options, + WebPDecBuffer* const buffer); + +// Flip buffer vertically by negating the various strides. +VP8StatusCode WebPFlipBuffer(WebPDecBuffer* const buffer); + +// Copy 'src' into 'dst' buffer, making sure 'dst' is not marked as owner of the +// memory (still held by 'src'). No pixels are copied. +void WebPCopyDecBuffer(const WebPDecBuffer* const src, + WebPDecBuffer* const dst); + +// Copy and transfer ownership from src to dst (beware of parameter order!) +void WebPGrabDecBuffer(WebPDecBuffer* const src, WebPDecBuffer* const dst); + +// Copy pixels from 'src' into a *preallocated* 'dst' buffer. Returns +// VP8_STATUS_INVALID_PARAM if the 'dst' is not set up correctly for the copy. +VP8StatusCode WebPCopyDecBufferPixels(const WebPDecBuffer* const src, + WebPDecBuffer* const dst); + +// Returns true if decoding will be slow with the current configuration +// and bitstream features. +int WebPAvoidSlowMemory(const WebPDecBuffer* const output, + const WebPBitstreamFeatures* const features); + +//------------------------------------------------------------------------------ + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif // WEBP_DEC_WEBPI_DEC_H_ diff --git a/packages/core/src/zig/vendor/libwebp/src/dsp/alpha_processing.c b/packages/core/src/zig/vendor/libwebp/src/dsp/alpha_processing.c new file mode 100644 index 0000000000..4927e73e81 --- /dev/null +++ b/packages/core/src/zig/vendor/libwebp/src/dsp/alpha_processing.c @@ -0,0 +1,500 @@ +// Copyright 2013 Google Inc. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the COPYING file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +// ----------------------------------------------------------------------------- +// +// Utilities for processing transparent channel. +// +// Author: Skal (pascal.massimino@gmail.com) + +#include +#include + +#include "src/dsp/cpu.h" +#include "src/dsp/dsp.h" +#include "src/webp/types.h" + +// Tables can be faster on some platform but incur some extra binary size (~2k). +#if !defined(USE_TABLES_FOR_ALPHA_MULT) +#define USE_TABLES_FOR_ALPHA_MULT 0 // ALTERNATE_CODE +#endif + + +// ----------------------------------------------------------------------------- + +#define MFIX 24 // 24bit fixed-point arithmetic +#define HALF ((1u << MFIX) >> 1) +#define KINV_255 ((1u << MFIX) / 255u) + +static uint32_t Mult(uint8_t x, uint32_t mult) { + const uint32_t v = (x * mult + HALF) >> MFIX; + assert(v <= 255); // <- 24bit precision is enough to ensure that. + return v; +} + +#if (USE_TABLES_FOR_ALPHA_MULT == 1) + +static const uint32_t kMultTables[2][256] = { + { // (255u << MFIX) / alpha + 0x00000000, 0xff000000, 0x7f800000, 0x55000000, 0x3fc00000, 0x33000000, + 0x2a800000, 0x246db6db, 0x1fe00000, 0x1c555555, 0x19800000, 0x172e8ba2, + 0x15400000, 0x139d89d8, 0x1236db6d, 0x11000000, 0x0ff00000, 0x0f000000, + 0x0e2aaaaa, 0x0d6bca1a, 0x0cc00000, 0x0c249249, 0x0b9745d1, 0x0b1642c8, + 0x0aa00000, 0x0a333333, 0x09cec4ec, 0x0971c71c, 0x091b6db6, 0x08cb08d3, + 0x08800000, 0x0839ce73, 0x07f80000, 0x07ba2e8b, 0x07800000, 0x07492492, + 0x07155555, 0x06e45306, 0x06b5e50d, 0x0689d89d, 0x06600000, 0x063831f3, + 0x06124924, 0x05ee23b8, 0x05cba2e8, 0x05aaaaaa, 0x058b2164, 0x056cefa8, + 0x05500000, 0x05343eb1, 0x05199999, 0x05000000, 0x04e76276, 0x04cfb2b7, + 0x04b8e38e, 0x04a2e8ba, 0x048db6db, 0x0479435e, 0x04658469, 0x045270d0, + 0x04400000, 0x042e29f7, 0x041ce739, 0x040c30c3, 0x03fc0000, 0x03ec4ec4, + 0x03dd1745, 0x03ce540f, 0x03c00000, 0x03b21642, 0x03a49249, 0x03976fc6, + 0x038aaaaa, 0x037e3f1f, 0x03722983, 0x03666666, 0x035af286, 0x034fcace, + 0x0344ec4e, 0x033a5440, 0x03300000, 0x0325ed09, 0x031c18f9, 0x0312818a, + 0x03092492, 0x03000000, 0x02f711dc, 0x02ee5846, 0x02e5d174, 0x02dd7baf, + 0x02d55555, 0x02cd5cd5, 0x02c590b2, 0x02bdef7b, 0x02b677d4, 0x02af286b, + 0x02a80000, 0x02a0fd5c, 0x029a1f58, 0x029364d9, 0x028ccccc, 0x0286562d, + 0x02800000, 0x0279c952, 0x0273b13b, 0x026db6db, 0x0267d95b, 0x026217ec, + 0x025c71c7, 0x0256e62a, 0x0251745d, 0x024c1bac, 0x0246db6d, 0x0241b2f9, + 0x023ca1af, 0x0237a6f4, 0x0232c234, 0x022df2df, 0x02293868, 0x02249249, + 0x02200000, 0x021b810e, 0x021714fb, 0x0212bb51, 0x020e739c, 0x020a3d70, + 0x02061861, 0x02020408, 0x01fe0000, 0x01fa0be8, 0x01f62762, 0x01f25213, + 0x01ee8ba2, 0x01ead3ba, 0x01e72a07, 0x01e38e38, 0x01e00000, 0x01dc7f10, + 0x01d90b21, 0x01d5a3e9, 0x01d24924, 0x01cefa8d, 0x01cbb7e3, 0x01c880e5, + 0x01c55555, 0x01c234f7, 0x01bf1f8f, 0x01bc14e5, 0x01b914c1, 0x01b61eed, + 0x01b33333, 0x01b05160, 0x01ad7943, 0x01aaaaaa, 0x01a7e567, 0x01a5294a, + 0x01a27627, 0x019fcbd2, 0x019d2a20, 0x019a90e7, 0x01980000, 0x01957741, + 0x0192f684, 0x01907da4, 0x018e0c7c, 0x018ba2e8, 0x018940c5, 0x0186e5f0, + 0x01849249, 0x018245ae, 0x01800000, 0x017dc11f, 0x017b88ee, 0x0179574e, + 0x01772c23, 0x01750750, 0x0172e8ba, 0x0170d045, 0x016ebdd7, 0x016cb157, + 0x016aaaaa, 0x0168a9b9, 0x0166ae6a, 0x0164b8a7, 0x0162c859, 0x0160dd67, + 0x015ef7bd, 0x015d1745, 0x015b3bea, 0x01596596, 0x01579435, 0x0155c7b4, + 0x01540000, 0x01523d03, 0x01507eae, 0x014ec4ec, 0x014d0fac, 0x014b5edc, + 0x0149b26c, 0x01480a4a, 0x01466666, 0x0144c6af, 0x01432b16, 0x0141938b, + 0x01400000, 0x013e7063, 0x013ce4a9, 0x013b5cc0, 0x0139d89d, 0x01385830, + 0x0136db6d, 0x01356246, 0x0133ecad, 0x01327a97, 0x01310bf6, 0x012fa0be, + 0x012e38e3, 0x012cd459, 0x012b7315, 0x012a150a, 0x0128ba2e, 0x01276276, + 0x01260dd6, 0x0124bc44, 0x01236db6, 0x01222222, 0x0120d97c, 0x011f93bc, + 0x011e50d7, 0x011d10c4, 0x011bd37a, 0x011a98ef, 0x0119611a, 0x01182bf2, + 0x0116f96f, 0x0115c988, 0x01149c34, 0x0113716a, 0x01124924, 0x01112358, + 0x01100000, 0x010edf12, 0x010dc087, 0x010ca458, 0x010b8a7d, 0x010a72f0, + 0x01095da8, 0x01084a9f, 0x010739ce, 0x01062b2e, 0x01051eb8, 0x01041465, + 0x01030c30, 0x01020612, 0x01010204, 0x01000000 }, + { // alpha * KINV_255 + 0x00000000, 0x00010101, 0x00020202, 0x00030303, 0x00040404, 0x00050505, + 0x00060606, 0x00070707, 0x00080808, 0x00090909, 0x000a0a0a, 0x000b0b0b, + 0x000c0c0c, 0x000d0d0d, 0x000e0e0e, 0x000f0f0f, 0x00101010, 0x00111111, + 0x00121212, 0x00131313, 0x00141414, 0x00151515, 0x00161616, 0x00171717, + 0x00181818, 0x00191919, 0x001a1a1a, 0x001b1b1b, 0x001c1c1c, 0x001d1d1d, + 0x001e1e1e, 0x001f1f1f, 0x00202020, 0x00212121, 0x00222222, 0x00232323, + 0x00242424, 0x00252525, 0x00262626, 0x00272727, 0x00282828, 0x00292929, + 0x002a2a2a, 0x002b2b2b, 0x002c2c2c, 0x002d2d2d, 0x002e2e2e, 0x002f2f2f, + 0x00303030, 0x00313131, 0x00323232, 0x00333333, 0x00343434, 0x00353535, + 0x00363636, 0x00373737, 0x00383838, 0x00393939, 0x003a3a3a, 0x003b3b3b, + 0x003c3c3c, 0x003d3d3d, 0x003e3e3e, 0x003f3f3f, 0x00404040, 0x00414141, + 0x00424242, 0x00434343, 0x00444444, 0x00454545, 0x00464646, 0x00474747, + 0x00484848, 0x00494949, 0x004a4a4a, 0x004b4b4b, 0x004c4c4c, 0x004d4d4d, + 0x004e4e4e, 0x004f4f4f, 0x00505050, 0x00515151, 0x00525252, 0x00535353, + 0x00545454, 0x00555555, 0x00565656, 0x00575757, 0x00585858, 0x00595959, + 0x005a5a5a, 0x005b5b5b, 0x005c5c5c, 0x005d5d5d, 0x005e5e5e, 0x005f5f5f, + 0x00606060, 0x00616161, 0x00626262, 0x00636363, 0x00646464, 0x00656565, + 0x00666666, 0x00676767, 0x00686868, 0x00696969, 0x006a6a6a, 0x006b6b6b, + 0x006c6c6c, 0x006d6d6d, 0x006e6e6e, 0x006f6f6f, 0x00707070, 0x00717171, + 0x00727272, 0x00737373, 0x00747474, 0x00757575, 0x00767676, 0x00777777, + 0x00787878, 0x00797979, 0x007a7a7a, 0x007b7b7b, 0x007c7c7c, 0x007d7d7d, + 0x007e7e7e, 0x007f7f7f, 0x00808080, 0x00818181, 0x00828282, 0x00838383, + 0x00848484, 0x00858585, 0x00868686, 0x00878787, 0x00888888, 0x00898989, + 0x008a8a8a, 0x008b8b8b, 0x008c8c8c, 0x008d8d8d, 0x008e8e8e, 0x008f8f8f, + 0x00909090, 0x00919191, 0x00929292, 0x00939393, 0x00949494, 0x00959595, + 0x00969696, 0x00979797, 0x00989898, 0x00999999, 0x009a9a9a, 0x009b9b9b, + 0x009c9c9c, 0x009d9d9d, 0x009e9e9e, 0x009f9f9f, 0x00a0a0a0, 0x00a1a1a1, + 0x00a2a2a2, 0x00a3a3a3, 0x00a4a4a4, 0x00a5a5a5, 0x00a6a6a6, 0x00a7a7a7, + 0x00a8a8a8, 0x00a9a9a9, 0x00aaaaaa, 0x00ababab, 0x00acacac, 0x00adadad, + 0x00aeaeae, 0x00afafaf, 0x00b0b0b0, 0x00b1b1b1, 0x00b2b2b2, 0x00b3b3b3, + 0x00b4b4b4, 0x00b5b5b5, 0x00b6b6b6, 0x00b7b7b7, 0x00b8b8b8, 0x00b9b9b9, + 0x00bababa, 0x00bbbbbb, 0x00bcbcbc, 0x00bdbdbd, 0x00bebebe, 0x00bfbfbf, + 0x00c0c0c0, 0x00c1c1c1, 0x00c2c2c2, 0x00c3c3c3, 0x00c4c4c4, 0x00c5c5c5, + 0x00c6c6c6, 0x00c7c7c7, 0x00c8c8c8, 0x00c9c9c9, 0x00cacaca, 0x00cbcbcb, + 0x00cccccc, 0x00cdcdcd, 0x00cecece, 0x00cfcfcf, 0x00d0d0d0, 0x00d1d1d1, + 0x00d2d2d2, 0x00d3d3d3, 0x00d4d4d4, 0x00d5d5d5, 0x00d6d6d6, 0x00d7d7d7, + 0x00d8d8d8, 0x00d9d9d9, 0x00dadada, 0x00dbdbdb, 0x00dcdcdc, 0x00dddddd, + 0x00dedede, 0x00dfdfdf, 0x00e0e0e0, 0x00e1e1e1, 0x00e2e2e2, 0x00e3e3e3, + 0x00e4e4e4, 0x00e5e5e5, 0x00e6e6e6, 0x00e7e7e7, 0x00e8e8e8, 0x00e9e9e9, + 0x00eaeaea, 0x00ebebeb, 0x00ececec, 0x00ededed, 0x00eeeeee, 0x00efefef, + 0x00f0f0f0, 0x00f1f1f1, 0x00f2f2f2, 0x00f3f3f3, 0x00f4f4f4, 0x00f5f5f5, + 0x00f6f6f6, 0x00f7f7f7, 0x00f8f8f8, 0x00f9f9f9, 0x00fafafa, 0x00fbfbfb, + 0x00fcfcfc, 0x00fdfdfd, 0x00fefefe, 0x00ffffff } +}; + +static WEBP_INLINE uint32_t GetScale(uint32_t a, int inverse) { + return kMultTables[!inverse][a]; +} + +#else + +static WEBP_INLINE uint32_t GetScale(uint32_t a, int inverse) { + return inverse ? (255u << MFIX) / a : a * KINV_255; +} + +#endif // USE_TABLES_FOR_ALPHA_MULT + +void WebPMultARGBRow_C(uint32_t* const ptr, int width, int inverse) { + int x; + for (x = 0; x < width; ++x) { + const uint32_t argb = ptr[x]; + if (argb < 0xff000000u) { // alpha < 255 + if (argb <= 0x00ffffffu) { // alpha == 0 + ptr[x] = 0; + } else { + const uint32_t alpha = (argb >> 24) & 0xff; + const uint32_t scale = GetScale(alpha, inverse); + uint32_t out = argb & 0xff000000u; + out |= Mult(argb >> 0, scale) << 0; + out |= Mult(argb >> 8, scale) << 8; + out |= Mult(argb >> 16, scale) << 16; + ptr[x] = out; + } + } + } +} + +void WebPMultRow_C(uint8_t* WEBP_RESTRICT const ptr, + const uint8_t* WEBP_RESTRICT const alpha, + int width, int inverse) { + int x; + for (x = 0; x < width; ++x) { + const uint32_t a = alpha[x]; + if (a != 255) { + if (a == 0) { + ptr[x] = 0; + } else { + const uint32_t scale = GetScale(a, inverse); + ptr[x] = Mult(ptr[x], scale); + } + } + } +} + +#undef KINV_255 +#undef HALF +#undef MFIX + +void (*WebPMultARGBRow)(uint32_t* const ptr, int width, int inverse); +void (*WebPMultRow)(uint8_t* WEBP_RESTRICT const ptr, + const uint8_t* WEBP_RESTRICT const alpha, + int width, int inverse); + +//------------------------------------------------------------------------------ +// Generic per-plane calls + +void WebPMultARGBRows(uint8_t* ptr, int stride, int width, int num_rows, + int inverse) { + int n; + for (n = 0; n < num_rows; ++n) { + WebPMultARGBRow((uint32_t*)ptr, width, inverse); + ptr += stride; + } +} + +void WebPMultRows(uint8_t* WEBP_RESTRICT ptr, int stride, + const uint8_t* WEBP_RESTRICT alpha, int alpha_stride, + int width, int num_rows, int inverse) { + int n; + for (n = 0; n < num_rows; ++n) { + WebPMultRow(ptr, alpha, width, inverse); + ptr += stride; + alpha += alpha_stride; + } +} + +//------------------------------------------------------------------------------ +// Premultiplied modes + +// non dithered-modes + +// (x * a * 32897) >> 23 is bit-wise equivalent to (int)(x * a / 255.) +// for all 8bit x or a. For bit-wise equivalence to (int)(x * a / 255. + .5), +// one can use instead: (x * a * 65793 + (1 << 23)) >> 24 +#if 1 // (int)(x * a / 255.) +#define MULTIPLIER(a) ((a) * 32897U) +#define PREMULTIPLY(x, m) (((x) * (m)) >> 23) +#else // (int)(x * a / 255. + .5) +#define MULTIPLIER(a) ((a) * 65793U) +#define PREMULTIPLY(x, m) (((x) * (m) + (1U << 23)) >> 24) +#endif + +#if !WEBP_NEON_OMIT_C_CODE +static void ApplyAlphaMultiply_C(uint8_t* rgba, int alpha_first, + int w, int h, int stride) { + while (h-- > 0) { + uint8_t* const rgb = rgba + (alpha_first ? 1 : 0); + const uint8_t* const alpha = rgba + (alpha_first ? 0 : 3); + int i; + for (i = 0; i < w; ++i) { + const uint32_t a = alpha[4 * i]; + if (a != 0xff) { + const uint32_t mult = MULTIPLIER(a); + rgb[4 * i + 0] = PREMULTIPLY(rgb[4 * i + 0], mult); + rgb[4 * i + 1] = PREMULTIPLY(rgb[4 * i + 1], mult); + rgb[4 * i + 2] = PREMULTIPLY(rgb[4 * i + 2], mult); + } + } + rgba += stride; + } +} +#endif // !WEBP_NEON_OMIT_C_CODE +#undef MULTIPLIER +#undef PREMULTIPLY + +// rgbA4444 + +#define MULTIPLIER(a) ((a) * 0x1111) // 0x1111 ~= (1 << 16) / 15 + +static WEBP_INLINE uint8_t dither_hi(uint8_t x) { + return (x & 0xf0) | (x >> 4); +} + +static WEBP_INLINE uint8_t dither_lo(uint8_t x) { + return (x & 0x0f) | (x << 4); +} + +static WEBP_INLINE uint8_t multiply(uint8_t x, uint32_t m) { + return (x * m) >> 16; +} + +static WEBP_INLINE void ApplyAlphaMultiply4444_C(uint8_t* rgba4444, + int w, int h, int stride, + int rg_byte_pos /* 0 or 1 */) { + while (h-- > 0) { + int i; + for (i = 0; i < w; ++i) { + const uint32_t rg = rgba4444[2 * i + rg_byte_pos]; + const uint32_t ba = rgba4444[2 * i + (rg_byte_pos ^ 1)]; + const uint8_t a = ba & 0x0f; + const uint32_t mult = MULTIPLIER(a); + const uint8_t r = multiply(dither_hi(rg), mult); + const uint8_t g = multiply(dither_lo(rg), mult); + const uint8_t b = multiply(dither_hi(ba), mult); + rgba4444[2 * i + rg_byte_pos] = (r & 0xf0) | ((g >> 4) & 0x0f); + rgba4444[2 * i + (rg_byte_pos ^ 1)] = (b & 0xf0) | a; + } + rgba4444 += stride; + } +} +#undef MULTIPLIER + +static void ApplyAlphaMultiply_16b_C(uint8_t* rgba4444, + int w, int h, int stride) { +#if (WEBP_SWAP_16BIT_CSP == 1) + ApplyAlphaMultiply4444_C(rgba4444, w, h, stride, 1); +#else + ApplyAlphaMultiply4444_C(rgba4444, w, h, stride, 0); +#endif +} + +#if !WEBP_NEON_OMIT_C_CODE +static int DispatchAlpha_C(const uint8_t* WEBP_RESTRICT alpha, int alpha_stride, + int width, int height, + uint8_t* WEBP_RESTRICT dst, int dst_stride) { + uint32_t alpha_mask = 0xff; + int i, j; + + for (j = 0; j < height; ++j) { + for (i = 0; i < width; ++i) { + const uint32_t alpha_value = alpha[i]; + dst[4 * i] = alpha_value; + alpha_mask &= alpha_value; + } + alpha += alpha_stride; + dst += dst_stride; + } + + return (alpha_mask != 0xff); +} + +static void DispatchAlphaToGreen_C(const uint8_t* WEBP_RESTRICT alpha, + int alpha_stride, int width, int height, + uint32_t* WEBP_RESTRICT dst, + int dst_stride) { + int i, j; + for (j = 0; j < height; ++j) { + for (i = 0; i < width; ++i) { + dst[i] = alpha[i] << 8; // leave A/R/B channels zero'd. + } + alpha += alpha_stride; + dst += dst_stride; + } +} + +static int ExtractAlpha_C(const uint8_t* WEBP_RESTRICT argb, int argb_stride, + int width, int height, + uint8_t* WEBP_RESTRICT alpha, int alpha_stride) { + uint8_t alpha_mask = 0xff; + int i, j; + + for (j = 0; j < height; ++j) { + for (i = 0; i < width; ++i) { + const uint8_t alpha_value = argb[4 * i]; + alpha[i] = alpha_value; + alpha_mask &= alpha_value; + } + argb += argb_stride; + alpha += alpha_stride; + } + return (alpha_mask == 0xff); +} + +static void ExtractGreen_C(const uint32_t* WEBP_RESTRICT argb, + uint8_t* WEBP_RESTRICT alpha, int size) { + int i; + for (i = 0; i < size; ++i) alpha[i] = argb[i] >> 8; +} +#endif // !WEBP_NEON_OMIT_C_CODE + +//------------------------------------------------------------------------------ + +static int HasAlpha8b_C(const uint8_t* src, int length) { + while (length-- > 0) if (*src++ != 0xff) return 1; + return 0; +} + +static int HasAlpha32b_C(const uint8_t* src, int length) { + int x; + for (x = 0; length-- > 0; x += 4) if (src[x] != 0xff) return 1; + return 0; +} + +static void AlphaReplace_C(uint32_t* src, int length, uint32_t color) { + int x; + for (x = 0; x < length; ++x) if ((src[x] >> 24) == 0) src[x] = color; +} + +//------------------------------------------------------------------------------ +// Simple channel manipulations. + +static WEBP_INLINE uint32_t MakeARGB32(int a, int r, int g, int b) { + return (((uint32_t)a << 24) | (r << 16) | (g << 8) | b); +} + +#ifdef WORDS_BIGENDIAN +static void PackARGB_C(const uint8_t* WEBP_RESTRICT a, + const uint8_t* WEBP_RESTRICT r, + const uint8_t* WEBP_RESTRICT g, + const uint8_t* WEBP_RESTRICT b, + int len, uint32_t* WEBP_RESTRICT out) { + int i; + for (i = 0; i < len; ++i) { + out[i] = MakeARGB32(a[4 * i], r[4 * i], g[4 * i], b[4 * i]); + } +} +#endif + +static void PackRGB_C(const uint8_t* WEBP_RESTRICT r, + const uint8_t* WEBP_RESTRICT g, + const uint8_t* WEBP_RESTRICT b, + int len, int step, uint32_t* WEBP_RESTRICT out) { + int i, offset = 0; + for (i = 0; i < len; ++i) { + out[i] = MakeARGB32(0xff, r[offset], g[offset], b[offset]); + offset += step; + } +} + +void (*WebPApplyAlphaMultiply)(uint8_t*, int, int, int, int); +void (*WebPApplyAlphaMultiply4444)(uint8_t*, int, int, int); +int (*WebPDispatchAlpha)(const uint8_t* WEBP_RESTRICT, int, int, int, + uint8_t* WEBP_RESTRICT, int); +void (*WebPDispatchAlphaToGreen)(const uint8_t* WEBP_RESTRICT, int, int, int, + uint32_t* WEBP_RESTRICT, int); +int (*WebPExtractAlpha)(const uint8_t* WEBP_RESTRICT, int, int, int, + uint8_t* WEBP_RESTRICT, int); +void (*WebPExtractGreen)(const uint32_t* WEBP_RESTRICT argb, + uint8_t* WEBP_RESTRICT alpha, int size); +#ifdef WORDS_BIGENDIAN +void (*WebPPackARGB)(const uint8_t* a, const uint8_t* r, const uint8_t* g, + const uint8_t* b, int, uint32_t*); +#endif +void (*WebPPackRGB)(const uint8_t* WEBP_RESTRICT r, + const uint8_t* WEBP_RESTRICT g, + const uint8_t* WEBP_RESTRICT b, + int len, int step, uint32_t* WEBP_RESTRICT out); + +int (*WebPHasAlpha8b)(const uint8_t* src, int length); +int (*WebPHasAlpha32b)(const uint8_t* src, int length); +void (*WebPAlphaReplace)(uint32_t* src, int length, uint32_t color); + +//------------------------------------------------------------------------------ +// Init function + +extern VP8CPUInfo VP8GetCPUInfo; +extern void WebPInitAlphaProcessingMIPSdspR2(void); +extern void WebPInitAlphaProcessingSSE2(void); +extern void WebPInitAlphaProcessingSSE41(void); +extern void WebPInitAlphaProcessingNEON(void); + +WEBP_DSP_INIT_FUNC(WebPInitAlphaProcessing) { + WebPMultARGBRow = WebPMultARGBRow_C; + WebPMultRow = WebPMultRow_C; + WebPApplyAlphaMultiply4444 = ApplyAlphaMultiply_16b_C; + +#ifdef WORDS_BIGENDIAN + WebPPackARGB = PackARGB_C; +#endif + WebPPackRGB = PackRGB_C; +#if !WEBP_NEON_OMIT_C_CODE + WebPApplyAlphaMultiply = ApplyAlphaMultiply_C; + WebPDispatchAlpha = DispatchAlpha_C; + WebPDispatchAlphaToGreen = DispatchAlphaToGreen_C; + WebPExtractAlpha = ExtractAlpha_C; + WebPExtractGreen = ExtractGreen_C; +#endif + + WebPHasAlpha8b = HasAlpha8b_C; + WebPHasAlpha32b = HasAlpha32b_C; + WebPAlphaReplace = AlphaReplace_C; + + // If defined, use CPUInfo() to overwrite some pointers with faster versions. + if (VP8GetCPUInfo != NULL) { +#if defined(WEBP_HAVE_SSE2) + if (VP8GetCPUInfo(kSSE2)) { + WebPInitAlphaProcessingSSE2(); +#if defined(WEBP_HAVE_SSE41) + if (VP8GetCPUInfo(kSSE4_1)) { + WebPInitAlphaProcessingSSE41(); + } +#endif + } +#endif +#if defined(WEBP_USE_MIPS_DSP_R2) + if (VP8GetCPUInfo(kMIPSdspR2)) { + WebPInitAlphaProcessingMIPSdspR2(); + } +#endif + } + +#if defined(WEBP_HAVE_NEON) + if (WEBP_NEON_OMIT_C_CODE || + (VP8GetCPUInfo != NULL && VP8GetCPUInfo(kNEON))) { + WebPInitAlphaProcessingNEON(); + } +#endif + + assert(WebPMultARGBRow != NULL); + assert(WebPMultRow != NULL); + assert(WebPApplyAlphaMultiply != NULL); + assert(WebPApplyAlphaMultiply4444 != NULL); + assert(WebPDispatchAlpha != NULL); + assert(WebPDispatchAlphaToGreen != NULL); + assert(WebPExtractAlpha != NULL); + assert(WebPExtractGreen != NULL); +#ifdef WORDS_BIGENDIAN + assert(WebPPackARGB != NULL); +#endif + assert(WebPPackRGB != NULL); + assert(WebPHasAlpha8b != NULL); + assert(WebPHasAlpha32b != NULL); + assert(WebPAlphaReplace != NULL); +} diff --git a/packages/core/src/zig/vendor/libwebp/src/dsp/alpha_processing_neon.c b/packages/core/src/zig/vendor/libwebp/src/dsp/alpha_processing_neon.c new file mode 100644 index 0000000000..6716fb77f0 --- /dev/null +++ b/packages/core/src/zig/vendor/libwebp/src/dsp/alpha_processing_neon.c @@ -0,0 +1,194 @@ +// Copyright 2017 Google Inc. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the COPYING file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +// ----------------------------------------------------------------------------- +// +// Utilities for processing transparent channel, NEON version. +// +// Author: Skal (pascal.massimino@gmail.com) + +#include "src/dsp/dsp.h" + +#if defined(WEBP_USE_NEON) + +#include "src/dsp/neon.h" + +//------------------------------------------------------------------------------ + +#define MULTIPLIER(a) ((a) * 0x8081) +#define PREMULTIPLY(x, m) (((x) * (m)) >> 23) + +#define MULTIPLY_BY_ALPHA(V, ALPHA, OTHER) do { \ + const uint8x8_t alpha = (V).val[(ALPHA)]; \ + const uint16x8_t r1 = vmull_u8((V).val[1], alpha); \ + const uint16x8_t g1 = vmull_u8((V).val[2], alpha); \ + const uint16x8_t b1 = vmull_u8((V).val[(OTHER)], alpha); \ + /* we use: v / 255 = (v + 1 + (v >> 8)) >> 8 */ \ + const uint16x8_t r2 = vsraq_n_u16(r1, r1, 8); \ + const uint16x8_t g2 = vsraq_n_u16(g1, g1, 8); \ + const uint16x8_t b2 = vsraq_n_u16(b1, b1, 8); \ + const uint16x8_t r3 = vaddq_u16(r2, kOne); \ + const uint16x8_t g3 = vaddq_u16(g2, kOne); \ + const uint16x8_t b3 = vaddq_u16(b2, kOne); \ + (V).val[1] = vshrn_n_u16(r3, 8); \ + (V).val[2] = vshrn_n_u16(g3, 8); \ + (V).val[(OTHER)] = vshrn_n_u16(b3, 8); \ +} while (0) + +static void ApplyAlphaMultiply_NEON(uint8_t* rgba, int alpha_first, + int w, int h, int stride) { + const uint16x8_t kOne = vdupq_n_u16(1u); + while (h-- > 0) { + uint32_t* const rgbx = (uint32_t*)rgba; + int i = 0; + if (alpha_first) { + for (; i + 8 <= w; i += 8) { + // load aaaa...|rrrr...|gggg...|bbbb... + uint8x8x4_t RGBX = vld4_u8((const uint8_t*)(rgbx + i)); + MULTIPLY_BY_ALPHA(RGBX, 0, 3); + vst4_u8((uint8_t*)(rgbx + i), RGBX); + } + } else { + for (; i + 8 <= w; i += 8) { + uint8x8x4_t RGBX = vld4_u8((const uint8_t*)(rgbx + i)); + MULTIPLY_BY_ALPHA(RGBX, 3, 0); + vst4_u8((uint8_t*)(rgbx + i), RGBX); + } + } + // Finish with left-overs. + for (; i < w; ++i) { + uint8_t* const rgb = rgba + (alpha_first ? 1 : 0); + const uint8_t* const alpha = rgba + (alpha_first ? 0 : 3); + const uint32_t a = alpha[4 * i]; + if (a != 0xff) { + const uint32_t mult = MULTIPLIER(a); + rgb[4 * i + 0] = PREMULTIPLY(rgb[4 * i + 0], mult); + rgb[4 * i + 1] = PREMULTIPLY(rgb[4 * i + 1], mult); + rgb[4 * i + 2] = PREMULTIPLY(rgb[4 * i + 2], mult); + } + } + rgba += stride; + } +} +#undef MULTIPLY_BY_ALPHA +#undef MULTIPLIER +#undef PREMULTIPLY + +//------------------------------------------------------------------------------ + +static int DispatchAlpha_NEON(const uint8_t* WEBP_RESTRICT alpha, + int alpha_stride, int width, int height, + uint8_t* WEBP_RESTRICT dst, int dst_stride) { + uint32_t alpha_mask = 0xffu; + uint8x8_t mask8 = vdup_n_u8(0xff); + uint32_t tmp[2]; + int i, j; + for (j = 0; j < height; ++j) { + // We don't know if alpha is first or last in dst[] (depending on rgbA/Argb + // mode). So we must be sure dst[4*i + 8 - 1] is writable for the store. + // Hence the test with 'width - 1' instead of just 'width'. + for (i = 0; i + 8 <= width - 1; i += 8) { + uint8x8x4_t rgbX = vld4_u8((const uint8_t*)(dst + 4 * i)); + const uint8x8_t alphas = vld1_u8(alpha + i); + rgbX.val[0] = alphas; + vst4_u8((uint8_t*)(dst + 4 * i), rgbX); + mask8 = vand_u8(mask8, alphas); + } + for (; i < width; ++i) { + const uint32_t alpha_value = alpha[i]; + dst[4 * i] = alpha_value; + alpha_mask &= alpha_value; + } + alpha += alpha_stride; + dst += dst_stride; + } + vst1_u8((uint8_t*)tmp, mask8); + alpha_mask *= 0x01010101; + alpha_mask &= tmp[0]; + alpha_mask &= tmp[1]; + return (alpha_mask != 0xffffffffu); +} + +static void DispatchAlphaToGreen_NEON(const uint8_t* WEBP_RESTRICT alpha, + int alpha_stride, int width, int height, + uint32_t* WEBP_RESTRICT dst, + int dst_stride) { + int i, j; + uint8x8x4_t greens; // leave A/R/B channels zero'd. + greens.val[0] = vdup_n_u8(0); + greens.val[2] = vdup_n_u8(0); + greens.val[3] = vdup_n_u8(0); + for (j = 0; j < height; ++j) { + for (i = 0; i + 8 <= width; i += 8) { + greens.val[1] = vld1_u8(alpha + i); + vst4_u8((uint8_t*)(dst + i), greens); + } + for (; i < width; ++i) dst[i] = alpha[i] << 8; + alpha += alpha_stride; + dst += dst_stride; + } +} + +static int ExtractAlpha_NEON(const uint8_t* WEBP_RESTRICT argb, int argb_stride, + int width, int height, + uint8_t* WEBP_RESTRICT alpha, int alpha_stride) { + uint32_t alpha_mask = 0xffu; + uint8x8_t mask8 = vdup_n_u8(0xff); + uint32_t tmp[2]; + int i, j; + for (j = 0; j < height; ++j) { + // We don't know if alpha is first or last in dst[] (depending on rgbA/Argb + // mode). So we must be sure dst[4*i + 8 - 1] is writable for the store. + // Hence the test with 'width - 1' instead of just 'width'. + for (i = 0; i + 8 <= width - 1; i += 8) { + const uint8x8x4_t rgbX = vld4_u8((const uint8_t*)(argb + 4 * i)); + const uint8x8_t alphas = rgbX.val[0]; + vst1_u8((uint8_t*)(alpha + i), alphas); + mask8 = vand_u8(mask8, alphas); + } + for (; i < width; ++i) { + alpha[i] = argb[4 * i]; + alpha_mask &= alpha[i]; + } + argb += argb_stride; + alpha += alpha_stride; + } + vst1_u8((uint8_t*)tmp, mask8); + alpha_mask *= 0x01010101; + alpha_mask &= tmp[0]; + alpha_mask &= tmp[1]; + return (alpha_mask == 0xffffffffu); +} + +static void ExtractGreen_NEON(const uint32_t* WEBP_RESTRICT argb, + uint8_t* WEBP_RESTRICT alpha, int size) { + int i; + for (i = 0; i + 16 <= size; i += 16) { + const uint8x16x4_t rgbX = vld4q_u8((const uint8_t*)(argb + i)); + const uint8x16_t greens = rgbX.val[1]; + vst1q_u8(alpha + i, greens); + } + for (; i < size; ++i) alpha[i] = (argb[i] >> 8) & 0xff; +} + +//------------------------------------------------------------------------------ + +extern void WebPInitAlphaProcessingNEON(void); + +WEBP_TSAN_IGNORE_FUNCTION void WebPInitAlphaProcessingNEON(void) { + WebPApplyAlphaMultiply = ApplyAlphaMultiply_NEON; + WebPDispatchAlpha = DispatchAlpha_NEON; + WebPDispatchAlphaToGreen = DispatchAlphaToGreen_NEON; + WebPExtractAlpha = ExtractAlpha_NEON; + WebPExtractGreen = ExtractGreen_NEON; +} + +#else // !WEBP_USE_NEON + +WEBP_DSP_INIT_STUB(WebPInitAlphaProcessingNEON) + +#endif // WEBP_USE_NEON diff --git a/packages/core/src/zig/vendor/libwebp/src/dsp/alpha_processing_sse2.c b/packages/core/src/zig/vendor/libwebp/src/dsp/alpha_processing_sse2.c new file mode 100644 index 0000000000..1a6bfcb917 --- /dev/null +++ b/packages/core/src/zig/vendor/libwebp/src/dsp/alpha_processing_sse2.c @@ -0,0 +1,418 @@ +// Copyright 2014 Google Inc. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the COPYING file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +// ----------------------------------------------------------------------------- +// +// Utilities for processing transparent channel. +// +// Author: Skal (pascal.massimino@gmail.com) + +#include "src/dsp/dsp.h" + +#if defined(WEBP_USE_SSE2) +#include + +#include "src/webp/types.h" +#include "src/dsp/cpu.h" + +//------------------------------------------------------------------------------ + +static int DispatchAlpha_SSE2(const uint8_t* WEBP_RESTRICT alpha, + int alpha_stride, int width, int height, + uint8_t* WEBP_RESTRICT dst, int dst_stride) { + // alpha_and stores an 'and' operation of all the alpha[] values. The final + // value is not 0xff if any of the alpha[] is not equal to 0xff. + uint32_t alpha_and = 0xff; + int i, j; + const __m128i zero = _mm_setzero_si128(); + const __m128i alpha_mask = _mm_set1_epi32((int)0xff); // to preserve A + const __m128i all_0xff = _mm_set1_epi8((char)0xff); + __m128i all_alphas16 = all_0xff; + __m128i all_alphas8 = all_0xff; + + // We must be able to access 3 extra bytes after the last written byte + // 'dst[4 * width - 4]', because we don't know if alpha is the first or the + // last byte of the quadruplet. + for (j = 0; j < height; ++j) { + char* ptr = (char*)dst; + for (i = 0; i + 16 <= width - 1; i += 16) { + // load 16 alpha bytes + const __m128i a0 = _mm_loadu_si128((const __m128i*)&alpha[i]); + const __m128i a1_lo = _mm_unpacklo_epi8(a0, zero); + const __m128i a1_hi = _mm_unpackhi_epi8(a0, zero); + const __m128i a2_lo_lo = _mm_unpacklo_epi16(a1_lo, zero); + const __m128i a2_lo_hi = _mm_unpackhi_epi16(a1_lo, zero); + const __m128i a2_hi_lo = _mm_unpacklo_epi16(a1_hi, zero); + const __m128i a2_hi_hi = _mm_unpackhi_epi16(a1_hi, zero); + _mm_maskmoveu_si128(a2_lo_lo, alpha_mask, ptr + 0); + _mm_maskmoveu_si128(a2_lo_hi, alpha_mask, ptr + 16); + _mm_maskmoveu_si128(a2_hi_lo, alpha_mask, ptr + 32); + _mm_maskmoveu_si128(a2_hi_hi, alpha_mask, ptr + 48); + // accumulate 16 alpha 'and' in parallel + all_alphas16 = _mm_and_si128(all_alphas16, a0); + ptr += 64; + } + if (i + 8 <= width - 1) { + // load 8 alpha bytes + const __m128i a0 = _mm_loadl_epi64((const __m128i*)&alpha[i]); + const __m128i a1 = _mm_unpacklo_epi8(a0, zero); + const __m128i a2_lo = _mm_unpacklo_epi16(a1, zero); + const __m128i a2_hi = _mm_unpackhi_epi16(a1, zero); + _mm_maskmoveu_si128(a2_lo, alpha_mask, ptr); + _mm_maskmoveu_si128(a2_hi, alpha_mask, ptr + 16); + // accumulate 8 alpha 'and' in parallel + all_alphas8 = _mm_and_si128(all_alphas8, a0); + i += 8; + } + for (; i < width; ++i) { + const uint32_t alpha_value = alpha[i]; + dst[4 * i] = alpha_value; + alpha_and &= alpha_value; + } + alpha += alpha_stride; + dst += dst_stride; + } + // Combine the eight alpha 'and' into a 8-bit mask. + alpha_and &= _mm_movemask_epi8(_mm_cmpeq_epi8(all_alphas8, all_0xff)) & 0xff; + return (alpha_and != 0xff || + _mm_movemask_epi8(_mm_cmpeq_epi8(all_alphas16, all_0xff)) != 0xffff); +} + +static void DispatchAlphaToGreen_SSE2(const uint8_t* WEBP_RESTRICT alpha, + int alpha_stride, int width, int height, + uint32_t* WEBP_RESTRICT dst, + int dst_stride) { + int i, j; + const __m128i zero = _mm_setzero_si128(); + const int limit = width & ~15; + for (j = 0; j < height; ++j) { + for (i = 0; i < limit; i += 16) { // process 16 alpha bytes + const __m128i a0 = _mm_loadu_si128((const __m128i*)&alpha[i]); + const __m128i a1 = _mm_unpacklo_epi8(zero, a0); // note the 'zero' first! + const __m128i b1 = _mm_unpackhi_epi8(zero, a0); + const __m128i a2_lo = _mm_unpacklo_epi16(a1, zero); + const __m128i b2_lo = _mm_unpacklo_epi16(b1, zero); + const __m128i a2_hi = _mm_unpackhi_epi16(a1, zero); + const __m128i b2_hi = _mm_unpackhi_epi16(b1, zero); + _mm_storeu_si128((__m128i*)&dst[i + 0], a2_lo); + _mm_storeu_si128((__m128i*)&dst[i + 4], a2_hi); + _mm_storeu_si128((__m128i*)&dst[i + 8], b2_lo); + _mm_storeu_si128((__m128i*)&dst[i + 12], b2_hi); + } + for (; i < width; ++i) dst[i] = alpha[i] << 8; + alpha += alpha_stride; + dst += dst_stride; + } +} + +static int ExtractAlpha_SSE2(const uint8_t* WEBP_RESTRICT argb, int argb_stride, + int width, int height, + uint8_t* WEBP_RESTRICT alpha, int alpha_stride) { + // alpha_and stores an 'and' operation of all the alpha[] values. The final + // value is not 0xff if any of the alpha[] is not equal to 0xff. + uint32_t alpha_and = 0xff; + int i, j; + const __m128i a_mask = _mm_set1_epi32(0xff); // to preserve alpha + const __m128i all_0xff = _mm_set_epi32(0, 0, ~0, ~0); + __m128i all_alphas = all_0xff; + + // We must be able to access 3 extra bytes after the last written byte + // 'src[4 * width - 4]', because we don't know if alpha is the first or the + // last byte of the quadruplet. + const int limit = (width - 1) & ~7; + + for (j = 0; j < height; ++j) { + const __m128i* src = (const __m128i*)argb; + for (i = 0; i < limit; i += 8) { + // load 32 argb bytes + const __m128i a0 = _mm_loadu_si128(src + 0); + const __m128i a1 = _mm_loadu_si128(src + 1); + const __m128i b0 = _mm_and_si128(a0, a_mask); + const __m128i b1 = _mm_and_si128(a1, a_mask); + const __m128i c0 = _mm_packs_epi32(b0, b1); + const __m128i d0 = _mm_packus_epi16(c0, c0); + // store + _mm_storel_epi64((__m128i*)&alpha[i], d0); + // accumulate eight alpha 'and' in parallel + all_alphas = _mm_and_si128(all_alphas, d0); + src += 2; + } + for (; i < width; ++i) { + const uint32_t alpha_value = argb[4 * i]; + alpha[i] = alpha_value; + alpha_and &= alpha_value; + } + argb += argb_stride; + alpha += alpha_stride; + } + // Combine the eight alpha 'and' into a 8-bit mask. + alpha_and &= _mm_movemask_epi8(_mm_cmpeq_epi8(all_alphas, all_0xff)); + return (alpha_and == 0xff); +} + +static void ExtractGreen_SSE2(const uint32_t* WEBP_RESTRICT argb, + uint8_t* WEBP_RESTRICT alpha, int size) { + int i; + const __m128i mask = _mm_set1_epi32(0xff); + const __m128i* src = (const __m128i*)argb; + + for (i = 0; i + 16 <= size; i += 16, src += 4) { + const __m128i a0 = _mm_loadu_si128(src + 0); + const __m128i a1 = _mm_loadu_si128(src + 1); + const __m128i a2 = _mm_loadu_si128(src + 2); + const __m128i a3 = _mm_loadu_si128(src + 3); + const __m128i b0 = _mm_srli_epi32(a0, 8); + const __m128i b1 = _mm_srli_epi32(a1, 8); + const __m128i b2 = _mm_srli_epi32(a2, 8); + const __m128i b3 = _mm_srli_epi32(a3, 8); + const __m128i c0 = _mm_and_si128(b0, mask); + const __m128i c1 = _mm_and_si128(b1, mask); + const __m128i c2 = _mm_and_si128(b2, mask); + const __m128i c3 = _mm_and_si128(b3, mask); + const __m128i d0 = _mm_packs_epi32(c0, c1); + const __m128i d1 = _mm_packs_epi32(c2, c3); + const __m128i e = _mm_packus_epi16(d0, d1); + // store + _mm_storeu_si128((__m128i*)&alpha[i], e); + } + if (i + 8 <= size) { + const __m128i a0 = _mm_loadu_si128(src + 0); + const __m128i a1 = _mm_loadu_si128(src + 1); + const __m128i b0 = _mm_srli_epi32(a0, 8); + const __m128i b1 = _mm_srli_epi32(a1, 8); + const __m128i c0 = _mm_and_si128(b0, mask); + const __m128i c1 = _mm_and_si128(b1, mask); + const __m128i d = _mm_packs_epi32(c0, c1); + const __m128i e = _mm_packus_epi16(d, d); + _mm_storel_epi64((__m128i*)&alpha[i], e); + i += 8; + } + for (; i < size; ++i) alpha[i] = argb[i] >> 8; +} + +//------------------------------------------------------------------------------ +// Non-dither premultiplied modes + +#define MULTIPLIER(a) ((a) * 0x8081) +#define PREMULTIPLY(x, m) (((x) * (m)) >> 23) + +// We can't use a 'const int' for the SHUFFLE value, because it has to be an +// immediate in the _mm_shufflexx_epi16() instruction. We really need a macro. +// We use: v / 255 = (v * 0x8081) >> 23, where v = alpha * {r,g,b} is a 16bit +// value. +#define APPLY_ALPHA(RGBX, SHUFFLE) do { \ + const __m128i argb0 = _mm_loadu_si128((const __m128i*)&(RGBX)); \ + const __m128i argb1_lo = _mm_unpacklo_epi8(argb0, zero); \ + const __m128i argb1_hi = _mm_unpackhi_epi8(argb0, zero); \ + const __m128i alpha0_lo = _mm_or_si128(argb1_lo, kMask); \ + const __m128i alpha0_hi = _mm_or_si128(argb1_hi, kMask); \ + const __m128i alpha1_lo = _mm_shufflelo_epi16(alpha0_lo, SHUFFLE); \ + const __m128i alpha1_hi = _mm_shufflelo_epi16(alpha0_hi, SHUFFLE); \ + const __m128i alpha2_lo = _mm_shufflehi_epi16(alpha1_lo, SHUFFLE); \ + const __m128i alpha2_hi = _mm_shufflehi_epi16(alpha1_hi, SHUFFLE); \ + /* alpha2 = [ff a0 a0 a0][ff a1 a1 a1] */ \ + const __m128i A0_lo = _mm_mullo_epi16(alpha2_lo, argb1_lo); \ + const __m128i A0_hi = _mm_mullo_epi16(alpha2_hi, argb1_hi); \ + const __m128i A1_lo = _mm_mulhi_epu16(A0_lo, kMult); \ + const __m128i A1_hi = _mm_mulhi_epu16(A0_hi, kMult); \ + const __m128i A2_lo = _mm_srli_epi16(A1_lo, 7); \ + const __m128i A2_hi = _mm_srli_epi16(A1_hi, 7); \ + const __m128i A3 = _mm_packus_epi16(A2_lo, A2_hi); \ + _mm_storeu_si128((__m128i*)&(RGBX), A3); \ +} while (0) + +static void ApplyAlphaMultiply_SSE2(uint8_t* rgba, int alpha_first, + int w, int h, int stride) { + const __m128i zero = _mm_setzero_si128(); + const __m128i kMult = _mm_set1_epi16((short)0x8081); + const __m128i kMask = _mm_set_epi16(0, 0xff, 0xff, 0, 0, 0xff, 0xff, 0); + const int kSpan = 4; + while (h-- > 0) { + uint32_t* const rgbx = (uint32_t*)rgba; + int i; + if (!alpha_first) { + for (i = 0; i + kSpan <= w; i += kSpan) { + APPLY_ALPHA(rgbx[i], _MM_SHUFFLE(2, 3, 3, 3)); + } + } else { + for (i = 0; i + kSpan <= w; i += kSpan) { + APPLY_ALPHA(rgbx[i], _MM_SHUFFLE(0, 0, 0, 1)); + } + } + // Finish with left-overs. + for (; i < w; ++i) { + uint8_t* const rgb = rgba + (alpha_first ? 1 : 0); + const uint8_t* const alpha = rgba + (alpha_first ? 0 : 3); + const uint32_t a = alpha[4 * i]; + if (a != 0xff) { + const uint32_t mult = MULTIPLIER(a); + rgb[4 * i + 0] = PREMULTIPLY(rgb[4 * i + 0], mult); + rgb[4 * i + 1] = PREMULTIPLY(rgb[4 * i + 1], mult); + rgb[4 * i + 2] = PREMULTIPLY(rgb[4 * i + 2], mult); + } + } + rgba += stride; + } +} +#undef MULTIPLIER +#undef PREMULTIPLY + +//------------------------------------------------------------------------------ +// Alpha detection + +static int HasAlpha8b_SSE2(const uint8_t* src, int length) { + const __m128i all_0xff = _mm_set1_epi8((char)0xff); + int i = 0; + for (; i + 16 <= length; i += 16) { + const __m128i v = _mm_loadu_si128((const __m128i*)(src + i)); + const __m128i bits = _mm_cmpeq_epi8(v, all_0xff); + const int mask = _mm_movemask_epi8(bits); + if (mask != 0xffff) return 1; + } + for (; i < length; ++i) if (src[i] != 0xff) return 1; + return 0; +} + +static int HasAlpha32b_SSE2(const uint8_t* src, int length) { + const __m128i alpha_mask = _mm_set1_epi32(0xff); + const __m128i all_0xff = _mm_set1_epi8((char)0xff); + int i = 0; + // We don't know if we can access the last 3 bytes after the last alpha + // value 'src[4 * length - 4]' (because we don't know if alpha is the first + // or the last byte of the quadruplet). Hence the '-3' protection below. + length = length * 4 - 3; // size in bytes + for (; i + 64 <= length; i += 64) { + const __m128i a0 = _mm_loadu_si128((const __m128i*)(src + i + 0)); + const __m128i a1 = _mm_loadu_si128((const __m128i*)(src + i + 16)); + const __m128i a2 = _mm_loadu_si128((const __m128i*)(src + i + 32)); + const __m128i a3 = _mm_loadu_si128((const __m128i*)(src + i + 48)); + const __m128i b0 = _mm_and_si128(a0, alpha_mask); + const __m128i b1 = _mm_and_si128(a1, alpha_mask); + const __m128i b2 = _mm_and_si128(a2, alpha_mask); + const __m128i b3 = _mm_and_si128(a3, alpha_mask); + const __m128i c0 = _mm_packs_epi32(b0, b1); + const __m128i c1 = _mm_packs_epi32(b2, b3); + const __m128i d = _mm_packus_epi16(c0, c1); + const __m128i bits = _mm_cmpeq_epi8(d, all_0xff); + const int mask = _mm_movemask_epi8(bits); + if (mask != 0xffff) return 1; + } + for (; i + 32 <= length; i += 32) { + const __m128i a0 = _mm_loadu_si128((const __m128i*)(src + i + 0)); + const __m128i a1 = _mm_loadu_si128((const __m128i*)(src + i + 16)); + const __m128i b0 = _mm_and_si128(a0, alpha_mask); + const __m128i b1 = _mm_and_si128(a1, alpha_mask); + const __m128i c = _mm_packs_epi32(b0, b1); + const __m128i d = _mm_packus_epi16(c, c); + const __m128i bits = _mm_cmpeq_epi8(d, all_0xff); + const int mask = _mm_movemask_epi8(bits); + if (mask != 0xffff) return 1; + } + for (; i <= length; i += 4) if (src[i] != 0xff) return 1; + return 0; +} + +static void AlphaReplace_SSE2(uint32_t* src, int length, uint32_t color) { + const __m128i m_color = _mm_set1_epi32((int)color); + const __m128i zero = _mm_setzero_si128(); + int i = 0; + for (; i + 8 <= length; i += 8) { + const __m128i a0 = _mm_loadu_si128((const __m128i*)(src + i + 0)); + const __m128i a1 = _mm_loadu_si128((const __m128i*)(src + i + 4)); + const __m128i b0 = _mm_srai_epi32(a0, 24); + const __m128i b1 = _mm_srai_epi32(a1, 24); + const __m128i c0 = _mm_cmpeq_epi32(b0, zero); + const __m128i c1 = _mm_cmpeq_epi32(b1, zero); + const __m128i d0 = _mm_and_si128(c0, m_color); + const __m128i d1 = _mm_and_si128(c1, m_color); + const __m128i e0 = _mm_andnot_si128(c0, a0); + const __m128i e1 = _mm_andnot_si128(c1, a1); + _mm_storeu_si128((__m128i*)(src + i + 0), _mm_or_si128(d0, e0)); + _mm_storeu_si128((__m128i*)(src + i + 4), _mm_or_si128(d1, e1)); + } + for (; i < length; ++i) if ((src[i] >> 24) == 0) src[i] = color; +} + +// ----------------------------------------------------------------------------- +// Apply alpha value to rows + +static void MultARGBRow_SSE2(uint32_t* const ptr, int width, int inverse) { + int x = 0; + if (!inverse) { + const int kSpan = 2; + const __m128i zero = _mm_setzero_si128(); + const __m128i k128 = _mm_set1_epi16(128); + const __m128i kMult = _mm_set1_epi16(0x0101); + const __m128i kMask = _mm_set_epi16(0, 0xff, 0, 0, 0, 0xff, 0, 0); + for (x = 0; x + kSpan <= width; x += kSpan) { + // To compute 'result = (int)(a * x / 255. + .5)', we use: + // tmp = a * v + 128, result = (tmp * 0x0101u) >> 16 + const __m128i A0 = _mm_loadl_epi64((const __m128i*)&ptr[x]); + const __m128i A1 = _mm_unpacklo_epi8(A0, zero); + const __m128i A2 = _mm_or_si128(A1, kMask); + const __m128i A3 = _mm_shufflelo_epi16(A2, _MM_SHUFFLE(2, 3, 3, 3)); + const __m128i A4 = _mm_shufflehi_epi16(A3, _MM_SHUFFLE(2, 3, 3, 3)); + // here, A4 = [ff a0 a0 a0][ff a1 a1 a1] + const __m128i A5 = _mm_mullo_epi16(A4, A1); + const __m128i A6 = _mm_add_epi16(A5, k128); + const __m128i A7 = _mm_mulhi_epu16(A6, kMult); + const __m128i A10 = _mm_packus_epi16(A7, zero); + _mm_storel_epi64((__m128i*)&ptr[x], A10); + } + } + width -= x; + if (width > 0) WebPMultARGBRow_C(ptr + x, width, inverse); +} + +static void MultRow_SSE2(uint8_t* WEBP_RESTRICT const ptr, + const uint8_t* WEBP_RESTRICT const alpha, + int width, int inverse) { + int x = 0; + if (!inverse) { + const __m128i zero = _mm_setzero_si128(); + const __m128i k128 = _mm_set1_epi16(128); + const __m128i kMult = _mm_set1_epi16(0x0101); + for (x = 0; x + 8 <= width; x += 8) { + const __m128i v0 = _mm_loadl_epi64((__m128i*)&ptr[x]); + const __m128i a0 = _mm_loadl_epi64((const __m128i*)&alpha[x]); + const __m128i v1 = _mm_unpacklo_epi8(v0, zero); + const __m128i a1 = _mm_unpacklo_epi8(a0, zero); + const __m128i v2 = _mm_mullo_epi16(v1, a1); + const __m128i v3 = _mm_add_epi16(v2, k128); + const __m128i v4 = _mm_mulhi_epu16(v3, kMult); + const __m128i v5 = _mm_packus_epi16(v4, zero); + _mm_storel_epi64((__m128i*)&ptr[x], v5); + } + } + width -= x; + if (width > 0) WebPMultRow_C(ptr + x, alpha + x, width, inverse); +} + +//------------------------------------------------------------------------------ +// Entry point + +extern void WebPInitAlphaProcessingSSE2(void); + +WEBP_TSAN_IGNORE_FUNCTION void WebPInitAlphaProcessingSSE2(void) { + WebPMultARGBRow = MultARGBRow_SSE2; + WebPMultRow = MultRow_SSE2; + WebPApplyAlphaMultiply = ApplyAlphaMultiply_SSE2; + WebPDispatchAlpha = DispatchAlpha_SSE2; + WebPDispatchAlphaToGreen = DispatchAlphaToGreen_SSE2; + WebPExtractAlpha = ExtractAlpha_SSE2; + WebPExtractGreen = ExtractGreen_SSE2; + + WebPHasAlpha8b = HasAlpha8b_SSE2; + WebPHasAlpha32b = HasAlpha32b_SSE2; + WebPAlphaReplace = AlphaReplace_SSE2; +} + +#else // !WEBP_USE_SSE2 + +WEBP_DSP_INIT_STUB(WebPInitAlphaProcessingSSE2) + +#endif // WEBP_USE_SSE2 diff --git a/packages/core/src/zig/vendor/libwebp/src/dsp/alpha_processing_sse41.c b/packages/core/src/zig/vendor/libwebp/src/dsp/alpha_processing_sse41.c new file mode 100644 index 0000000000..ed95ea4ef6 --- /dev/null +++ b/packages/core/src/zig/vendor/libwebp/src/dsp/alpha_processing_sse41.c @@ -0,0 +1,94 @@ +// Copyright 2015 Google Inc. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the COPYING file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +// ----------------------------------------------------------------------------- +// +// Utilities for processing transparent channel, SSE4.1 variant. +// +// Author: Skal (pascal.massimino@gmail.com) + +#include "src/dsp/cpu.h" +#include "src/webp/types.h" +#include "src/dsp/dsp.h" + +#if defined(WEBP_USE_SSE41) +#include +#include + +//------------------------------------------------------------------------------ + +static int ExtractAlpha_SSE41(const uint8_t* WEBP_RESTRICT argb, + int argb_stride, int width, int height, + uint8_t* WEBP_RESTRICT alpha, int alpha_stride) { + // alpha_and stores an 'and' operation of all the alpha[] values. The final + // value is not 0xff if any of the alpha[] is not equal to 0xff. + uint32_t alpha_and = 0xff; + int i, j; + const __m128i all_0xff = _mm_set1_epi32(~0); + __m128i all_alphas = all_0xff; + + // We must be able to access 3 extra bytes after the last written byte + // 'src[4 * width - 4]', because we don't know if alpha is the first or the + // last byte of the quadruplet. + const int limit = (width - 1) & ~15; + const __m128i kCstAlpha0 = _mm_set_epi8(-1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, 12, 8, 4, 0); + const __m128i kCstAlpha1 = _mm_set_epi8(-1, -1, -1, -1, -1, -1, -1, -1, + 12, 8, 4, 0, -1, -1, -1, -1); + const __m128i kCstAlpha2 = _mm_set_epi8(-1, -1, -1, -1, 12, 8, 4, 0, + -1, -1, -1, -1, -1, -1, -1, -1); + const __m128i kCstAlpha3 = _mm_set_epi8(12, 8, 4, 0, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1); + for (j = 0; j < height; ++j) { + const __m128i* src = (const __m128i*)argb; + for (i = 0; i < limit; i += 16) { + // load 64 argb bytes + const __m128i a0 = _mm_loadu_si128(src + 0); + const __m128i a1 = _mm_loadu_si128(src + 1); + const __m128i a2 = _mm_loadu_si128(src + 2); + const __m128i a3 = _mm_loadu_si128(src + 3); + const __m128i b0 = _mm_shuffle_epi8(a0, kCstAlpha0); + const __m128i b1 = _mm_shuffle_epi8(a1, kCstAlpha1); + const __m128i b2 = _mm_shuffle_epi8(a2, kCstAlpha2); + const __m128i b3 = _mm_shuffle_epi8(a3, kCstAlpha3); + const __m128i c0 = _mm_or_si128(b0, b1); + const __m128i c1 = _mm_or_si128(b2, b3); + const __m128i d0 = _mm_or_si128(c0, c1); + // store + _mm_storeu_si128((__m128i*)&alpha[i], d0); + // accumulate sixteen alpha 'and' in parallel + all_alphas = _mm_and_si128(all_alphas, d0); + src += 4; + } + for (; i < width; ++i) { + const uint32_t alpha_value = argb[4 * i]; + alpha[i] = alpha_value; + alpha_and &= alpha_value; + } + argb += argb_stride; + alpha += alpha_stride; + } + // Combine the sixteen alpha 'and' into an 8-bit mask. + alpha_and |= 0xff00u; // pretend the upper bits [8..15] were tested ok. + alpha_and &= _mm_movemask_epi8(_mm_cmpeq_epi8(all_alphas, all_0xff)); + return (alpha_and == 0xffffu); +} + +//------------------------------------------------------------------------------ +// Entry point + +extern void WebPInitAlphaProcessingSSE41(void); + +WEBP_TSAN_IGNORE_FUNCTION void WebPInitAlphaProcessingSSE41(void) { + WebPExtractAlpha = ExtractAlpha_SSE41; +} + +#else // !WEBP_USE_SSE41 + +WEBP_DSP_INIT_STUB(WebPInitAlphaProcessingSSE41) + +#endif // WEBP_USE_SSE41 diff --git a/packages/core/src/zig/vendor/libwebp/src/dsp/common_sse2.h b/packages/core/src/zig/vendor/libwebp/src/dsp/common_sse2.h new file mode 100644 index 0000000000..e9f1ebff44 --- /dev/null +++ b/packages/core/src/zig/vendor/libwebp/src/dsp/common_sse2.h @@ -0,0 +1,194 @@ +// Copyright 2016 Google Inc. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the COPYING file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +// ----------------------------------------------------------------------------- +// +// SSE2 code common to several files. +// +// Author: Vincent Rabaud (vrabaud@google.com) + +#ifndef WEBP_DSP_COMMON_SSE2_H_ +#define WEBP_DSP_COMMON_SSE2_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +#if defined(WEBP_USE_SSE2) + +#include + +//------------------------------------------------------------------------------ +// Quite useful macro for debugging. Left here for convenience. + +#if 0 +#include +static WEBP_INLINE void PrintReg(const __m128i r, const char* const name, + int size) { + int n; + union { + __m128i r; + uint8_t i8[16]; + uint16_t i16[8]; + uint32_t i32[4]; + uint64_t i64[2]; + } tmp; + tmp.r = r; + fprintf(stderr, "%s\t: ", name); + if (size == 8) { + for (n = 0; n < 16; ++n) fprintf(stderr, "%.2x ", tmp.i8[n]); + } else if (size == 16) { + for (n = 0; n < 8; ++n) fprintf(stderr, "%.4x ", tmp.i16[n]); + } else if (size == 32) { + for (n = 0; n < 4; ++n) fprintf(stderr, "%.8x ", tmp.i32[n]); + } else { + for (n = 0; n < 2; ++n) fprintf(stderr, "%.16lx ", tmp.i64[n]); + } + fprintf(stderr, "\n"); +} +#endif + +//------------------------------------------------------------------------------ +// Math functions. + +// Return the sum of all the 8b in the register. +static WEBP_INLINE int VP8HorizontalAdd8b(const __m128i* const a) { + const __m128i zero = _mm_setzero_si128(); + const __m128i sad8x2 = _mm_sad_epu8(*a, zero); + // sum the two sads: sad8x2[0:1] + sad8x2[8:9] + const __m128i sum = _mm_add_epi32(sad8x2, _mm_shuffle_epi32(sad8x2, 2)); + return _mm_cvtsi128_si32(sum); +} + +// Transpose two 4x4 16b matrices horizontally stored in registers. +static WEBP_INLINE void VP8Transpose_2_4x4_16b( + const __m128i* const in0, const __m128i* const in1, + const __m128i* const in2, const __m128i* const in3, __m128i* const out0, + __m128i* const out1, __m128i* const out2, __m128i* const out3) { + // Transpose the two 4x4. + // a00 a01 a02 a03 b00 b01 b02 b03 + // a10 a11 a12 a13 b10 b11 b12 b13 + // a20 a21 a22 a23 b20 b21 b22 b23 + // a30 a31 a32 a33 b30 b31 b32 b33 + const __m128i transpose0_0 = _mm_unpacklo_epi16(*in0, *in1); + const __m128i transpose0_1 = _mm_unpacklo_epi16(*in2, *in3); + const __m128i transpose0_2 = _mm_unpackhi_epi16(*in0, *in1); + const __m128i transpose0_3 = _mm_unpackhi_epi16(*in2, *in3); + // a00 a10 a01 a11 a02 a12 a03 a13 + // a20 a30 a21 a31 a22 a32 a23 a33 + // b00 b10 b01 b11 b02 b12 b03 b13 + // b20 b30 b21 b31 b22 b32 b23 b33 + const __m128i transpose1_0 = _mm_unpacklo_epi32(transpose0_0, transpose0_1); + const __m128i transpose1_1 = _mm_unpacklo_epi32(transpose0_2, transpose0_3); + const __m128i transpose1_2 = _mm_unpackhi_epi32(transpose0_0, transpose0_1); + const __m128i transpose1_3 = _mm_unpackhi_epi32(transpose0_2, transpose0_3); + // a00 a10 a20 a30 a01 a11 a21 a31 + // b00 b10 b20 b30 b01 b11 b21 b31 + // a02 a12 a22 a32 a03 a13 a23 a33 + // b02 b12 a22 b32 b03 b13 b23 b33 + *out0 = _mm_unpacklo_epi64(transpose1_0, transpose1_1); + *out1 = _mm_unpackhi_epi64(transpose1_0, transpose1_1); + *out2 = _mm_unpacklo_epi64(transpose1_2, transpose1_3); + *out3 = _mm_unpackhi_epi64(transpose1_2, transpose1_3); + // a00 a10 a20 a30 b00 b10 b20 b30 + // a01 a11 a21 a31 b01 b11 b21 b31 + // a02 a12 a22 a32 b02 b12 b22 b32 + // a03 a13 a23 a33 b03 b13 b23 b33 +} + +//------------------------------------------------------------------------------ +// Channel mixing. + +// Function used several times in VP8PlanarTo24b. +// It samples the in buffer as follows: one every two unsigned char is stored +// at the beginning of the buffer, while the other half is stored at the end. +#define VP8PlanarTo24bHelper(IN, OUT) \ + do { \ + const __m128i v_mask = _mm_set1_epi16(0x00ff); \ + /* Take one every two upper 8b values.*/ \ + (OUT##0) = _mm_packus_epi16(_mm_and_si128((IN##0), v_mask), \ + _mm_and_si128((IN##1), v_mask)); \ + (OUT##1) = _mm_packus_epi16(_mm_and_si128((IN##2), v_mask), \ + _mm_and_si128((IN##3), v_mask)); \ + (OUT##2) = _mm_packus_epi16(_mm_and_si128((IN##4), v_mask), \ + _mm_and_si128((IN##5), v_mask)); \ + /* Take one every two lower 8b values.*/ \ + (OUT##3) = _mm_packus_epi16(_mm_srli_epi16((IN##0), 8), \ + _mm_srli_epi16((IN##1), 8)); \ + (OUT##4) = _mm_packus_epi16(_mm_srli_epi16((IN##2), 8), \ + _mm_srli_epi16((IN##3), 8)); \ + (OUT##5) = _mm_packus_epi16(_mm_srli_epi16((IN##4), 8), \ + _mm_srli_epi16((IN##5), 8)); \ + } while (0) + +// Pack the planar buffers +// rrrr... rrrr... gggg... gggg... bbbb... bbbb.... +// triplet by triplet in the output buffer rgb as rgbrgbrgbrgb ... +static WEBP_INLINE void VP8PlanarTo24b_SSE2( + __m128i* const in0, __m128i* const in1, __m128i* const in2, + __m128i* const in3, __m128i* const in4, __m128i* const in5) { + // The input is 6 registers of sixteen 8b but for the sake of explanation, + // let's take 6 registers of four 8b values. + // To pack, we will keep taking one every two 8b integer and move it + // around as follows: + // Input: + // r0r1r2r3 | r4r5r6r7 | g0g1g2g3 | g4g5g6g7 | b0b1b2b3 | b4b5b6b7 + // Split the 6 registers in two sets of 3 registers: the first set as the even + // 8b bytes, the second the odd ones: + // r0r2r4r6 | g0g2g4g6 | b0b2b4b6 | r1r3r5r7 | g1g3g5g7 | b1b3b5b7 + // Repeat the same permutations twice more: + // r0r4g0g4 | b0b4r1r5 | g1g5b1b5 | r2r6g2g6 | b2b6r3r7 | g3g7b3b7 + // r0g0b0r1 | g1b1r2g2 | b2r3g3b3 | r4g4b4r5 | g5b5r6g6 | b6r7g7b7 + __m128i tmp0, tmp1, tmp2, tmp3, tmp4, tmp5; + VP8PlanarTo24bHelper(*in, tmp); + VP8PlanarTo24bHelper(tmp, *in); + VP8PlanarTo24bHelper(*in, tmp); + // We need to do it two more times than the example as we have sixteen bytes. + { + __m128i out0, out1, out2, out3, out4, out5; + VP8PlanarTo24bHelper(tmp, out); + VP8PlanarTo24bHelper(out, *in); + } +} + +#undef VP8PlanarTo24bHelper + +// Convert four packed four-channel buffers like argbargbargbargb... into the +// split channels aaaaa ... rrrr ... gggg .... bbbbb ...... +static WEBP_INLINE void VP8L32bToPlanar_SSE2(__m128i* const in0, + __m128i* const in1, + __m128i* const in2, + __m128i* const in3) { + // Column-wise transpose. + const __m128i A0 = _mm_unpacklo_epi8(*in0, *in1); + const __m128i A1 = _mm_unpackhi_epi8(*in0, *in1); + const __m128i A2 = _mm_unpacklo_epi8(*in2, *in3); + const __m128i A3 = _mm_unpackhi_epi8(*in2, *in3); + const __m128i B0 = _mm_unpacklo_epi8(A0, A1); + const __m128i B1 = _mm_unpackhi_epi8(A0, A1); + const __m128i B2 = _mm_unpacklo_epi8(A2, A3); + const __m128i B3 = _mm_unpackhi_epi8(A2, A3); + // C0 = g7 g6 ... g1 g0 | b7 b6 ... b1 b0 + // C1 = a7 a6 ... a1 a0 | r7 r6 ... r1 r0 + const __m128i C0 = _mm_unpacklo_epi8(B0, B1); + const __m128i C1 = _mm_unpackhi_epi8(B0, B1); + const __m128i C2 = _mm_unpacklo_epi8(B2, B3); + const __m128i C3 = _mm_unpackhi_epi8(B2, B3); + // Gather the channels. + *in0 = _mm_unpackhi_epi64(C1, C3); + *in1 = _mm_unpacklo_epi64(C1, C3); + *in2 = _mm_unpackhi_epi64(C0, C2); + *in3 = _mm_unpacklo_epi64(C0, C2); +} + +#endif // WEBP_USE_SSE2 + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif // WEBP_DSP_COMMON_SSE2_H_ diff --git a/packages/core/src/zig/vendor/libwebp/src/dsp/common_sse41.h b/packages/core/src/zig/vendor/libwebp/src/dsp/common_sse41.h new file mode 100644 index 0000000000..2f173c024a --- /dev/null +++ b/packages/core/src/zig/vendor/libwebp/src/dsp/common_sse41.h @@ -0,0 +1,132 @@ +// Copyright 2016 Google Inc. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the COPYING file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +// ----------------------------------------------------------------------------- +// +// SSE4 code common to several files. +// +// Author: Vincent Rabaud (vrabaud@google.com) + +#ifndef WEBP_DSP_COMMON_SSE41_H_ +#define WEBP_DSP_COMMON_SSE41_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +#if defined(WEBP_USE_SSE41) +#include + +//------------------------------------------------------------------------------ +// Channel mixing. +// Shuffles the input buffer as A0 0 0 A1 0 0 A2 ... +#define WEBP_SSE41_SHUFF(OUT, IN0, IN1) \ + OUT##0 = _mm_shuffle_epi8(*IN0, shuff0); \ + OUT##1 = _mm_shuffle_epi8(*IN0, shuff1); \ + OUT##2 = _mm_shuffle_epi8(*IN0, shuff2); \ + OUT##3 = _mm_shuffle_epi8(*IN1, shuff0); \ + OUT##4 = _mm_shuffle_epi8(*IN1, shuff1); \ + OUT##5 = _mm_shuffle_epi8(*IN1, shuff2); + +// Pack the planar buffers +// rrrr... rrrr... gggg... gggg... bbbb... bbbb.... +// triplet by triplet in the output buffer rgb as rgbrgbrgbrgb ... +static WEBP_INLINE void VP8PlanarTo24b_SSE41( + __m128i* const in0, __m128i* const in1, __m128i* const in2, + __m128i* const in3, __m128i* const in4, __m128i* const in5) { + __m128i R0, R1, R2, R3, R4, R5; + __m128i G0, G1, G2, G3, G4, G5; + __m128i B0, B1, B2, B3, B4, B5; + + // Process R. + { + const __m128i shuff0 = _mm_set_epi8( + 5, -1, -1, 4, -1, -1, 3, -1, -1, 2, -1, -1, 1, -1, -1, 0); + const __m128i shuff1 = _mm_set_epi8( + -1, 10, -1, -1, 9, -1, -1, 8, -1, -1, 7, -1, -1, 6, -1, -1); + const __m128i shuff2 = _mm_set_epi8( + -1, -1, 15, -1, -1, 14, -1, -1, 13, -1, -1, 12, -1, -1, 11, -1); + WEBP_SSE41_SHUFF(R, in0, in1) + } + + // Process G. + { + // Same as before, just shifted to the left by one and including the right + // padding. + const __m128i shuff0 = _mm_set_epi8( + -1, -1, 4, -1, -1, 3, -1, -1, 2, -1, -1, 1, -1, -1, 0, -1); + const __m128i shuff1 = _mm_set_epi8( + 10, -1, -1, 9, -1, -1, 8, -1, -1, 7, -1, -1, 6, -1, -1, 5); + const __m128i shuff2 = _mm_set_epi8( + -1, 15, -1, -1, 14, -1, -1, 13, -1, -1, 12, -1, -1, 11, -1, -1); + WEBP_SSE41_SHUFF(G, in2, in3) + } + + // Process B. + { + const __m128i shuff0 = _mm_set_epi8( + -1, 4, -1, -1, 3, -1, -1, 2, -1, -1, 1, -1, -1, 0, -1, -1); + const __m128i shuff1 = _mm_set_epi8( + -1, -1, 9, -1, -1, 8, -1, -1, 7, -1, -1, 6, -1, -1, 5, -1); + const __m128i shuff2 = _mm_set_epi8( + 15, -1, -1, 14, -1, -1, 13, -1, -1, 12, -1, -1, 11, -1, -1, 10); + WEBP_SSE41_SHUFF(B, in4, in5) + } + + // OR the different channels. + { + const __m128i RG0 = _mm_or_si128(R0, G0); + const __m128i RG1 = _mm_or_si128(R1, G1); + const __m128i RG2 = _mm_or_si128(R2, G2); + const __m128i RG3 = _mm_or_si128(R3, G3); + const __m128i RG4 = _mm_or_si128(R4, G4); + const __m128i RG5 = _mm_or_si128(R5, G5); + *in0 = _mm_or_si128(RG0, B0); + *in1 = _mm_or_si128(RG1, B1); + *in2 = _mm_or_si128(RG2, B2); + *in3 = _mm_or_si128(RG3, B3); + *in4 = _mm_or_si128(RG4, B4); + *in5 = _mm_or_si128(RG5, B5); + } +} + +#undef WEBP_SSE41_SHUFF + +// Convert four packed four-channel buffers like argbargbargbargb... into the +// split channels aaaaa ... rrrr ... gggg .... bbbbb ...... +static WEBP_INLINE void VP8L32bToPlanar_SSE41(__m128i* const in0, + __m128i* const in1, + __m128i* const in2, + __m128i* const in3) { + // aaaarrrrggggbbbb + const __m128i shuff0 = + _mm_set_epi8(15, 11, 7, 3, 14, 10, 6, 2, 13, 9, 5, 1, 12, 8, 4, 0); + const __m128i A0 = _mm_shuffle_epi8(*in0, shuff0); + const __m128i A1 = _mm_shuffle_epi8(*in1, shuff0); + const __m128i A2 = _mm_shuffle_epi8(*in2, shuff0); + const __m128i A3 = _mm_shuffle_epi8(*in3, shuff0); + // A0A1R0R1 + // G0G1B0B1 + // A2A3R2R3 + // G0G1B0B1 + const __m128i B0 = _mm_unpacklo_epi32(A0, A1); + const __m128i B1 = _mm_unpackhi_epi32(A0, A1); + const __m128i B2 = _mm_unpacklo_epi32(A2, A3); + const __m128i B3 = _mm_unpackhi_epi32(A2, A3); + *in3 = _mm_unpacklo_epi64(B0, B2); + *in2 = _mm_unpackhi_epi64(B0, B2); + *in1 = _mm_unpacklo_epi64(B1, B3); + *in0 = _mm_unpackhi_epi64(B1, B3); +} + +#endif // WEBP_USE_SSE41 + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif // WEBP_DSP_COMMON_SSE41_H_ diff --git a/packages/core/src/zig/vendor/libwebp/src/dsp/cpu.c b/packages/core/src/zig/vendor/libwebp/src/dsp/cpu.c new file mode 100644 index 0000000000..816892fbc8 --- /dev/null +++ b/packages/core/src/zig/vendor/libwebp/src/dsp/cpu.c @@ -0,0 +1,251 @@ +// Copyright 2011 Google Inc. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the COPYING file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +// ----------------------------------------------------------------------------- +// +// CPU detection +// +// Author: Christian Duvivier (cduvivier@google.com) + +#include "src/dsp/cpu.h" + +#if defined(WEBP_HAVE_NEON_RTCD) +#include +#include +#endif + +#if defined(WEBP_ANDROID_NEON) +#include +#endif + +#include + +#include "src/webp/types.h" + +//------------------------------------------------------------------------------ +// SSE2 detection. +// + +// apple/darwin gcc-4.0.1 defines __PIC__, but not __pic__ with -fPIC. +#if (defined(__pic__) || defined(__PIC__)) && defined(__i386__) +static WEBP_INLINE void GetCPUInfo(int cpu_info[4], int info_type) { + __asm__ volatile ( + "mov %%ebx, %%edi\n" + "cpuid\n" + "xchg %%edi, %%ebx\n" + : "=a"(cpu_info[0]), "=D"(cpu_info[1]), "=c"(cpu_info[2]), "=d"(cpu_info[3]) + : "a"(info_type), "c"(0)); +} +#elif defined(__i386__) || defined(__x86_64__) +static WEBP_INLINE void GetCPUInfo(int cpu_info[4], int info_type) { + __asm__ volatile ( + "cpuid\n" + : "=a"(cpu_info[0]), "=b"(cpu_info[1]), "=c"(cpu_info[2]), "=d"(cpu_info[3]) + : "a"(info_type), "c"(0)); +} +#elif defined(_MSC_VER) && (defined(_M_X64) || defined(_M_IX86)) + +#if defined(_MSC_FULL_VER) && _MSC_FULL_VER >= 150030729 // >= VS2008 SP1 +#include +#define GetCPUInfo(info, type) __cpuidex(info, type, 0) // set ecx=0 +#define WEBP_HAVE_MSC_CPUID +#elif _MSC_VER > 1310 +#include +#define GetCPUInfo __cpuid +#define WEBP_HAVE_MSC_CPUID +#endif + +#endif + +// NaCl has no support for xgetbv or the raw opcode. +#if !defined(__native_client__) && (defined(__i386__) || defined(__x86_64__)) +static WEBP_INLINE uint64_t xgetbv(void) { + const uint32_t ecx = 0; + uint32_t eax, edx; + // Use the raw opcode for xgetbv for compatibility with older toolchains. + __asm__ volatile ( + ".byte 0x0f, 0x01, 0xd0\n" + : "=a"(eax), "=d"(edx) : "c" (ecx)); + return ((uint64_t)edx << 32) | eax; +} +#elif (defined(_M_X64) || defined(_M_IX86)) && \ + defined(_MSC_FULL_VER) && _MSC_FULL_VER >= 160040219 // >= VS2010 SP1 +#include +#define xgetbv() _xgetbv(0) +#elif defined(_MSC_VER) && defined(_M_IX86) +static WEBP_INLINE uint64_t xgetbv(void) { + uint32_t eax_, edx_; + __asm { + xor ecx, ecx // ecx = 0 + // Use the raw opcode for xgetbv for compatibility with older toolchains. + __asm _emit 0x0f __asm _emit 0x01 __asm _emit 0xd0 + mov eax_, eax + mov edx_, edx + } + return ((uint64_t)edx_ << 32) | eax_; +} +#else +#define xgetbv() 0U // no AVX for older x64 or unrecognized toolchains. +#endif + +#if defined(__i386__) || defined(__x86_64__) || defined(WEBP_HAVE_MSC_CPUID) + +// helper function for run-time detection of slow SSSE3 platforms +static int CheckSlowModel(int info) { + // Table listing display models with longer latencies for the bsr instruction + // (ie 2 cycles vs 10/16 cycles) and some SSSE3 instructions like pshufb. + // Refer to Intel 64 and IA-32 Architectures Optimization Reference Manual. + static const uint8_t kSlowModels[] = { + 0x37, 0x4a, 0x4d, // Silvermont Microarchitecture + 0x1c, 0x26, 0x27 // Atom Microarchitecture + }; + const uint32_t model = ((info & 0xf0000) >> 12) | ((info >> 4) & 0xf); + const uint32_t family = (info >> 8) & 0xf; + if (family == 0x06) { + size_t i; + for (i = 0; i < sizeof(kSlowModels) / sizeof(kSlowModels[0]); ++i) { + if (model == kSlowModels[i]) return 1; + } + } + return 0; +} + +static int x86CPUInfo(CPUFeature feature) { + int max_cpuid_value; + int cpu_info[4]; + int is_intel = 0; + + // get the highest feature value cpuid supports + GetCPUInfo(cpu_info, 0); + max_cpuid_value = cpu_info[0]; + if (max_cpuid_value < 1) { + return 0; + } else { + const int VENDOR_ID_INTEL_EBX = 0x756e6547; // uneG + const int VENDOR_ID_INTEL_EDX = 0x49656e69; // Ieni + const int VENDOR_ID_INTEL_ECX = 0x6c65746e; // letn + is_intel = (cpu_info[1] == VENDOR_ID_INTEL_EBX && + cpu_info[2] == VENDOR_ID_INTEL_ECX && + cpu_info[3] == VENDOR_ID_INTEL_EDX); // genuine Intel? + } + + GetCPUInfo(cpu_info, 1); + if (feature == kSSE2) { + return !!(cpu_info[3] & (1 << 26)); + } + if (feature == kSSE3) { + return !!(cpu_info[2] & (1 << 0)); + } + if (feature == kSlowSSSE3) { + if (is_intel && (cpu_info[2] & (1 << 9))) { // SSSE3? + return CheckSlowModel(cpu_info[0]); + } + return 0; + } + + if (feature == kSSE4_1) { + return !!(cpu_info[2] & (1 << 19)); + } + if (feature == kAVX) { + // bits 27 (OSXSAVE) & 28 (256-bit AVX) + if ((cpu_info[2] & 0x18000000) == 0x18000000) { + // XMM state and YMM state enabled by the OS. + return (xgetbv() & 0x6) == 0x6; + } + } + if (feature == kAVX2) { + if (x86CPUInfo(kAVX) && max_cpuid_value >= 7) { + GetCPUInfo(cpu_info, 7); + return !!(cpu_info[1] & (1 << 5)); + } + } + return 0; +} +WEBP_EXTERN VP8CPUInfo VP8GetCPUInfo; +VP8CPUInfo VP8GetCPUInfo = x86CPUInfo; +#elif defined(WEBP_ANDROID_NEON) // NB: needs to be before generic NEON test. +static int AndroidCPUInfo(CPUFeature feature) { + const AndroidCpuFamily cpu_family = android_getCpuFamily(); + const uint64_t cpu_features = android_getCpuFeatures(); + if (feature == kNEON) { + return cpu_family == ANDROID_CPU_FAMILY_ARM && + (cpu_features & ANDROID_CPU_ARM_FEATURE_NEON) != 0; + } + return 0; +} +WEBP_EXTERN VP8CPUInfo VP8GetCPUInfo; +VP8CPUInfo VP8GetCPUInfo = AndroidCPUInfo; +#elif defined(EMSCRIPTEN) // also needs to be before generic NEON test +// Use compile flags as an indicator of SIMD support instead of a runtime check. +static int wasmCPUInfo(CPUFeature feature) { + switch (feature) { +#ifdef WEBP_HAVE_SSE2 + case kSSE2: + return 1; +#endif +#ifdef WEBP_HAVE_SSE41 + case kSSE3: + case kSlowSSSE3: + case kSSE4_1: + return 1; +#endif +#ifdef WEBP_HAVE_NEON + case kNEON: + return 1; +#endif + default: + break; + } + return 0; +} +WEBP_EXTERN VP8CPUInfo VP8GetCPUInfo; +VP8CPUInfo VP8GetCPUInfo = wasmCPUInfo; +#elif defined(WEBP_HAVE_NEON) +// In most cases this function doesn't check for NEON support (it's assumed by +// the configuration), but enables turning off NEON at runtime, for testing +// purposes, by setting VP8GetCPUInfo = NULL. +static int armCPUInfo(CPUFeature feature) { + if (feature != kNEON) return 0; +#if defined(__linux__) && defined(WEBP_HAVE_NEON_RTCD) + { + int has_neon = 0; + char line[200]; + FILE* const cpuinfo = fopen("/proc/cpuinfo", "r"); + if (cpuinfo == NULL) return 0; + while (fgets(line, sizeof(line), cpuinfo)) { + if (!strncmp(line, "Features", 8)) { + if (strstr(line, " neon ") != NULL) { + has_neon = 1; + break; + } + } + } + fclose(cpuinfo); + return has_neon; + } +#else + return 1; +#endif +} +WEBP_EXTERN VP8CPUInfo VP8GetCPUInfo; +VP8CPUInfo VP8GetCPUInfo = armCPUInfo; +#elif defined(WEBP_USE_MIPS32) || defined(WEBP_USE_MIPS_DSP_R2) || \ + defined(WEBP_USE_MSA) +static int mipsCPUInfo(CPUFeature feature) { + if ((feature == kMIPS32) || (feature == kMIPSdspR2) || (feature == kMSA)) { + return 1; + } else { + return 0; + } + +} +WEBP_EXTERN VP8CPUInfo VP8GetCPUInfo; +VP8CPUInfo VP8GetCPUInfo = mipsCPUInfo; +#else +WEBP_EXTERN VP8CPUInfo VP8GetCPUInfo; +VP8CPUInfo VP8GetCPUInfo = NULL; +#endif diff --git a/packages/core/src/zig/vendor/libwebp/src/dsp/cpu.h b/packages/core/src/zig/vendor/libwebp/src/dsp/cpu.h new file mode 100644 index 0000000000..7f87d7daaa --- /dev/null +++ b/packages/core/src/zig/vendor/libwebp/src/dsp/cpu.h @@ -0,0 +1,281 @@ +// Copyright 2022 Google Inc. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the COPYING file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +// ----------------------------------------------------------------------------- +// +// CPU detection functions and macros. +// +// Author: Skal (pascal.massimino@gmail.com) + +#ifndef WEBP_DSP_CPU_H_ +#define WEBP_DSP_CPU_H_ + +#include + +#ifdef HAVE_CONFIG_H +#include "src/webp/config.h" +#endif + +#include "src/webp/types.h" + +#if defined(__GNUC__) +#define LOCAL_GCC_VERSION ((__GNUC__ << 8) | __GNUC_MINOR__) +#define LOCAL_GCC_PREREQ(maj, min) (LOCAL_GCC_VERSION >= (((maj) << 8) | (min))) +#else +#define LOCAL_GCC_VERSION 0 +#define LOCAL_GCC_PREREQ(maj, min) 0 +#endif + +#if defined(__clang__) +#define LOCAL_CLANG_VERSION ((__clang_major__ << 8) | __clang_minor__) +#define LOCAL_CLANG_PREREQ(maj, min) \ + (LOCAL_CLANG_VERSION >= (((maj) << 8) | (min))) +#else +#define LOCAL_CLANG_VERSION 0 +#define LOCAL_CLANG_PREREQ(maj, min) 0 +#endif + +#ifndef __has_builtin +#define __has_builtin(x) 0 +#endif + +//------------------------------------------------------------------------------ +// x86 defines. + +#if !defined(HAVE_CONFIG_H) +#if defined(_MSC_VER) && _MSC_VER > 1310 && \ + (defined(_M_X64) || defined(_M_IX86)) +#define WEBP_MSC_SSE2 // Visual C++ SSE2 targets +#endif + +#if defined(_MSC_VER) && _MSC_VER >= 1500 && \ + (defined(_M_X64) || defined(_M_IX86)) +#define WEBP_MSC_SSE41 // Visual C++ SSE4.1 targets +#endif + +#if defined(_MSC_VER) && _MSC_VER >= 1700 && \ + (defined(_M_X64) || defined(_M_IX86)) +#define WEBP_MSC_AVX2 // Visual C++ AVX2 targets +#endif +#endif + +// WEBP_HAVE_* are used to indicate the presence of the instruction set in dsp +// files without intrinsics, allowing the corresponding Init() to be called. +// Files containing intrinsics will need to be built targeting the instruction +// set so should succeed on one of the earlier tests. +#if (defined(__SSE2__) || defined(WEBP_MSC_SSE2)) && \ + (!defined(HAVE_CONFIG_H) || defined(WEBP_HAVE_SSE2)) +#define WEBP_USE_SSE2 +#endif + +#if defined(WEBP_USE_SSE2) && !defined(WEBP_HAVE_SSE2) +#define WEBP_HAVE_SSE2 +#endif + +#if (defined(__SSE4_1__) || defined(WEBP_MSC_SSE41)) && \ + (!defined(HAVE_CONFIG_H) || defined(WEBP_HAVE_SSE41)) +#define WEBP_USE_SSE41 +#endif + +#if defined(WEBP_USE_SSE41) && !defined(WEBP_HAVE_SSE41) +#define WEBP_HAVE_SSE41 +#endif + +#if (defined(__AVX2__) || defined(WEBP_MSC_AVX2)) && \ + (!defined(HAVE_CONFIG_H) || defined(WEBP_HAVE_AVX2)) +#define WEBP_USE_AVX2 +#endif + +#if defined(WEBP_USE_AVX2) && !defined(WEBP_HAVE_AVX2) +#define WEBP_HAVE_AVX2 +#endif + +#undef WEBP_MSC_AVX2 +#undef WEBP_MSC_SSE41 +#undef WEBP_MSC_SSE2 + +//------------------------------------------------------------------------------ +// Arm defines. + +// The intrinsics currently cause compiler errors with arm-nacl-gcc and the +// inline assembly would need to be modified for use with Native Client. +#if ((defined(__ARM_NEON__) || defined(__aarch64__)) && \ + (!defined(HAVE_CONFIG_H) || defined(WEBP_HAVE_NEON))) && \ + !defined(__native_client__) +#define WEBP_USE_NEON +#endif + +#if !defined(WEBP_USE_NEON) && defined(__ANDROID__) && \ + defined(__ARM_ARCH_7A__) && defined(HAVE_CPU_FEATURES_H) +#define WEBP_ANDROID_NEON // Android targets that may have NEON +#define WEBP_USE_NEON +#endif + +// Note: ARM64 is supported in Visual Studio 2017, but requires the direct +// inclusion of arm64_neon.h; Visual Studio 2019 includes this file in +// arm_neon.h. Compile errors were seen with Visual Studio 2019 16.4 with +// vtbl4_u8(); a fix was made in 16.6. +#if defined(_MSC_VER) && \ + ((_MSC_VER >= 1700 && defined(_M_ARM)) || \ + (_MSC_VER >= 1926 && (defined(_M_ARM64) || defined(_M_ARM64EC)))) +#define WEBP_USE_NEON +#define WEBP_USE_INTRINSICS +#endif + +#if defined(__aarch64__) || defined(_M_ARM64) || defined(_M_ARM64EC) +#define WEBP_AARCH64 1 +#else +#define WEBP_AARCH64 0 +#endif + +#if defined(WEBP_USE_NEON) && !defined(WEBP_HAVE_NEON) +#define WEBP_HAVE_NEON +#endif + +//------------------------------------------------------------------------------ +// MIPS defines. + +#if defined(__mips__) && !defined(__mips64) && defined(__mips_isa_rev) && \ + (__mips_isa_rev >= 1) && (__mips_isa_rev < 6) +#define WEBP_USE_MIPS32 +#if (__mips_isa_rev >= 2) +#define WEBP_USE_MIPS32_R2 +#if defined(__mips_dspr2) || (defined(__mips_dsp_rev) && __mips_dsp_rev >= 2) +#define WEBP_USE_MIPS_DSP_R2 +#endif +#endif +#endif + +#if defined(__mips_msa) && defined(__mips_isa_rev) && (__mips_isa_rev >= 5) +#define WEBP_USE_MSA +#endif + +//------------------------------------------------------------------------------ + +#ifndef WEBP_DSP_OMIT_C_CODE +#define WEBP_DSP_OMIT_C_CODE 1 +#endif + +#if defined(WEBP_USE_NEON) && WEBP_DSP_OMIT_C_CODE +#define WEBP_NEON_OMIT_C_CODE 1 +#else +#define WEBP_NEON_OMIT_C_CODE 0 +#endif + +#if !(LOCAL_CLANG_PREREQ(3, 8) || LOCAL_GCC_PREREQ(4, 8) || WEBP_AARCH64) +#define WEBP_NEON_WORK_AROUND_GCC 1 +#else +#define WEBP_NEON_WORK_AROUND_GCC 0 +#endif + +//------------------------------------------------------------------------------ + +// This macro prevents thread_sanitizer from reporting known concurrent writes. +#define WEBP_TSAN_IGNORE_FUNCTION +#if defined(__has_feature) +#if __has_feature(thread_sanitizer) +#undef WEBP_TSAN_IGNORE_FUNCTION +#define WEBP_TSAN_IGNORE_FUNCTION __attribute__((no_sanitize_thread)) +#endif +#endif + +#if defined(__has_feature) +#if __has_feature(memory_sanitizer) +#define WEBP_MSAN +#endif +#endif + +#if defined(WEBP_USE_THREAD) && !defined(_WIN32) +#include // NOLINT + +#define WEBP_DSP_INIT(func) \ + do { \ + static volatile VP8CPUInfo func##_last_cpuinfo_used = \ + (VP8CPUInfo)&func##_last_cpuinfo_used; \ + static pthread_mutex_t func##_lock = PTHREAD_MUTEX_INITIALIZER; \ + if (pthread_mutex_lock(&func##_lock)) break; \ + if (func##_last_cpuinfo_used != VP8GetCPUInfo) func(); \ + func##_last_cpuinfo_used = VP8GetCPUInfo; \ + (void)pthread_mutex_unlock(&func##_lock); \ + } while (0) +#else // !(defined(WEBP_USE_THREAD) && !defined(_WIN32)) +#define WEBP_DSP_INIT(func) \ + do { \ + static volatile VP8CPUInfo func##_last_cpuinfo_used = \ + (VP8CPUInfo)&func##_last_cpuinfo_used; \ + if (func##_last_cpuinfo_used == VP8GetCPUInfo) break; \ + func(); \ + func##_last_cpuinfo_used = VP8GetCPUInfo; \ + } while (0) +#endif // defined(WEBP_USE_THREAD) && !defined(_WIN32) + +// Defines an Init + helper function that control multiple initialization of +// function pointers / tables. +/* Usage: + WEBP_DSP_INIT_FUNC(InitFunc) { + ...function body + } +*/ +#define WEBP_DSP_INIT_FUNC(name) \ + static WEBP_TSAN_IGNORE_FUNCTION void name##_body(void); \ + WEBP_TSAN_IGNORE_FUNCTION void name(void) { WEBP_DSP_INIT(name##_body); } \ + static WEBP_TSAN_IGNORE_FUNCTION void name##_body(void) + +#define WEBP_UBSAN_IGNORE_UNDEF +#define WEBP_UBSAN_IGNORE_UNSIGNED_OVERFLOW +#if defined(__clang__) && defined(__has_attribute) +#if __has_attribute(no_sanitize) +// This macro prevents the undefined behavior sanitizer from reporting +// failures. This is only meant to silence unaligned loads on platforms that +// are known to support them. +#undef WEBP_UBSAN_IGNORE_UNDEF +#define WEBP_UBSAN_IGNORE_UNDEF __attribute__((no_sanitize("undefined"))) + +// This macro prevents the undefined behavior sanitizer from reporting +// failures related to unsigned integer overflows. This is only meant to +// silence cases where this well defined behavior is expected. +#undef WEBP_UBSAN_IGNORE_UNSIGNED_OVERFLOW +#define WEBP_UBSAN_IGNORE_UNSIGNED_OVERFLOW \ + __attribute__((no_sanitize("unsigned-integer-overflow"))) +#endif +#endif + +// If 'ptr' is NULL, returns NULL. Otherwise returns 'ptr + off'. +// Prevents undefined behavior sanitizer nullptr-with-nonzero-offset warning. +#if !defined(WEBP_OFFSET_PTR) +#define WEBP_OFFSET_PTR(ptr, off) (((ptr) == NULL) ? NULL : ((ptr) + (off))) +#endif + +// Regularize the definition of WEBP_SWAP_16BIT_CSP (backward compatibility) +#if !defined(WEBP_SWAP_16BIT_CSP) +#define WEBP_SWAP_16BIT_CSP 0 +#endif + +// some endian fix (e.g.: mips-gcc doesn't define __BIG_ENDIAN__) +#if !defined(WORDS_BIGENDIAN) && \ + (defined(__BIG_ENDIAN__) || defined(_M_PPC) || \ + (defined(__BYTE_ORDER__) && (__BYTE_ORDER__ == __ORDER_BIG_ENDIAN__))) +#define WORDS_BIGENDIAN +#endif + +typedef enum { + kSSE2, + kSSE3, + kSlowSSSE3, // special feature for slow SSSE3 architectures + kSSE4_1, + kAVX, + kAVX2, + kNEON, + kMIPS32, + kMIPSdspR2, + kMSA +} CPUFeature; + +// returns true if the CPU supports the feature. +typedef int (*VP8CPUInfo)(CPUFeature feature); + +#endif // WEBP_DSP_CPU_H_ diff --git a/packages/core/src/zig/vendor/libwebp/src/dsp/dec.c b/packages/core/src/zig/vendor/libwebp/src/dsp/dec.c new file mode 100644 index 0000000000..4f38309980 --- /dev/null +++ b/packages/core/src/zig/vendor/libwebp/src/dsp/dec.c @@ -0,0 +1,899 @@ +// Copyright 2010 Google Inc. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the COPYING file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +// ----------------------------------------------------------------------------- +// +// Speed-critical decoding functions, default plain-C implementations. +// +// Author: Skal (pascal.massimino@gmail.com) + +#include +#include +#include + +#include "src/dec/common_dec.h" +#include "src/dec/vp8i_dec.h" +#include "src/dsp/cpu.h" +#include "src/dsp/dsp.h" +#include "src/utils/utils.h" +#include "src/webp/types.h" + +//------------------------------------------------------------------------------ + +static WEBP_INLINE uint8_t clip_8b(int v) { + return (!(v & ~0xff)) ? v : (v < 0) ? 0 : 255; +} + +//------------------------------------------------------------------------------ +// Transforms (Paragraph 14.4) + +#define STORE(x, y, v) \ + dst[(x) + (y) * BPS] = clip_8b(dst[(x) + (y) * BPS] + ((v) >> 3)) + +#define STORE2(y, dc, d, c) do { \ + const int DC = (dc); \ + STORE(0, y, DC + (d)); \ + STORE(1, y, DC + (c)); \ + STORE(2, y, DC - (c)); \ + STORE(3, y, DC - (d)); \ +} while (0) + +#if !WEBP_NEON_OMIT_C_CODE +static void TransformOne_C(const int16_t* WEBP_RESTRICT in, + uint8_t* WEBP_RESTRICT dst) { + int C[4 * 4], *tmp; + int i; + tmp = C; + for (i = 0; i < 4; ++i) { // vertical pass + const int a = in[0] + in[8]; // [-4096, 4094] + const int b = in[0] - in[8]; // [-4095, 4095] + const int c = WEBP_TRANSFORM_AC3_MUL2(in[4]) - + WEBP_TRANSFORM_AC3_MUL1(in[12]); // [-3783, 3783] + const int d = WEBP_TRANSFORM_AC3_MUL1(in[4]) + + WEBP_TRANSFORM_AC3_MUL2(in[12]); // [-3785, 3781] + tmp[0] = a + d; // [-7881, 7875] + tmp[1] = b + c; // [-7878, 7878] + tmp[2] = b - c; // [-7878, 7878] + tmp[3] = a - d; // [-7877, 7879] + tmp += 4; + in++; + } + // Each pass is expanding the dynamic range by ~3.85 (upper bound). + // The exact value is (2. + (20091 + 35468) / 65536). + // After the second pass, maximum interval is [-3794, 3794], assuming + // an input in [-2048, 2047] interval. We then need to add a dst value + // in the [0, 255] range. + // In the worst case scenario, the input to clip_8b() can be as large as + // [-60713, 60968]. + tmp = C; + for (i = 0; i < 4; ++i) { // horizontal pass + const int dc = tmp[0] + 4; + const int a = dc + tmp[8]; + const int b = dc - tmp[8]; + const int c = + WEBP_TRANSFORM_AC3_MUL2(tmp[4]) - WEBP_TRANSFORM_AC3_MUL1(tmp[12]); + const int d = + WEBP_TRANSFORM_AC3_MUL1(tmp[4]) + WEBP_TRANSFORM_AC3_MUL2(tmp[12]); + STORE(0, 0, a + d); + STORE(1, 0, b + c); + STORE(2, 0, b - c); + STORE(3, 0, a - d); + tmp++; + dst += BPS; + } +} + +// Simplified transform when only in[0], in[1] and in[4] are non-zero +static void TransformAC3_C(const int16_t* WEBP_RESTRICT in, + uint8_t* WEBP_RESTRICT dst) { + const int a = in[0] + 4; + const int c4 = WEBP_TRANSFORM_AC3_MUL2(in[4]); + const int d4 = WEBP_TRANSFORM_AC3_MUL1(in[4]); + const int c1 = WEBP_TRANSFORM_AC3_MUL2(in[1]); + const int d1 = WEBP_TRANSFORM_AC3_MUL1(in[1]); + STORE2(0, a + d4, d1, c1); + STORE2(1, a + c4, d1, c1); + STORE2(2, a - c4, d1, c1); + STORE2(3, a - d4, d1, c1); +} +#undef STORE2 + +static void TransformTwo_C(const int16_t* WEBP_RESTRICT in, + uint8_t* WEBP_RESTRICT dst, int do_two) { + TransformOne_C(in, dst); + if (do_two) { + TransformOne_C(in + 16, dst + 4); + } +} +#endif // !WEBP_NEON_OMIT_C_CODE + +static void TransformUV_C(const int16_t* WEBP_RESTRICT in, + uint8_t* WEBP_RESTRICT dst) { + VP8Transform(in + 0 * 16, dst, 1); + VP8Transform(in + 2 * 16, dst + 4 * BPS, 1); +} + +#if !WEBP_NEON_OMIT_C_CODE +static void TransformDC_C(const int16_t* WEBP_RESTRICT in, + uint8_t* WEBP_RESTRICT dst) { + const int DC = in[0] + 4; + int i, j; + for (j = 0; j < 4; ++j) { + for (i = 0; i < 4; ++i) { + STORE(i, j, DC); + } + } +} +#endif // !WEBP_NEON_OMIT_C_CODE + +static void TransformDCUV_C(const int16_t* WEBP_RESTRICT in, + uint8_t* WEBP_RESTRICT dst) { + if (in[0 * 16]) VP8TransformDC(in + 0 * 16, dst); + if (in[1 * 16]) VP8TransformDC(in + 1 * 16, dst + 4); + if (in[2 * 16]) VP8TransformDC(in + 2 * 16, dst + 4 * BPS); + if (in[3 * 16]) VP8TransformDC(in + 3 * 16, dst + 4 * BPS + 4); +} + +#undef STORE + +//------------------------------------------------------------------------------ +// Paragraph 14.3 + +#if !WEBP_NEON_OMIT_C_CODE +static void TransformWHT_C(const int16_t* WEBP_RESTRICT in, + int16_t* WEBP_RESTRICT out) { + int tmp[16]; + int i; + for (i = 0; i < 4; ++i) { + const int a0 = in[0 + i] + in[12 + i]; + const int a1 = in[4 + i] + in[ 8 + i]; + const int a2 = in[4 + i] - in[ 8 + i]; + const int a3 = in[0 + i] - in[12 + i]; + tmp[0 + i] = a0 + a1; + tmp[8 + i] = a0 - a1; + tmp[4 + i] = a3 + a2; + tmp[12 + i] = a3 - a2; + } + for (i = 0; i < 4; ++i) { + const int dc = tmp[0 + i * 4] + 3; // w/ rounder + const int a0 = dc + tmp[3 + i * 4]; + const int a1 = tmp[1 + i * 4] + tmp[2 + i * 4]; + const int a2 = tmp[1 + i * 4] - tmp[2 + i * 4]; + const int a3 = dc - tmp[3 + i * 4]; + out[ 0] = (a0 + a1) >> 3; + out[16] = (a3 + a2) >> 3; + out[32] = (a0 - a1) >> 3; + out[48] = (a3 - a2) >> 3; + out += 64; + } +} +#endif // !WEBP_NEON_OMIT_C_CODE + +VP8WHT VP8TransformWHT; + +//------------------------------------------------------------------------------ +// Intra predictions + +#define DST(x, y) dst[(x) + (y) * BPS] + +#if !WEBP_NEON_OMIT_C_CODE +static WEBP_INLINE void TrueMotion(uint8_t* dst, int size) { + const uint8_t* top = dst - BPS; + const uint8_t* const clip0 = VP8kclip1 - top[-1]; + int y; + for (y = 0; y < size; ++y) { + const uint8_t* const clip = clip0 + dst[-1]; + int x; + for (x = 0; x < size; ++x) { + dst[x] = clip[top[x]]; + } + dst += BPS; + } +} +static void TM4_C(uint8_t* dst) { TrueMotion(dst, 4); } +static void TM8uv_C(uint8_t* dst) { TrueMotion(dst, 8); } +static void TM16_C(uint8_t* dst) { TrueMotion(dst, 16); } + +//------------------------------------------------------------------------------ +// 16x16 + +static void VE16_C(uint8_t* dst) { // vertical + int j; + for (j = 0; j < 16; ++j) { + memcpy(dst + j * BPS, dst - BPS, 16); + } +} + +static void HE16_C(uint8_t* dst) { // horizontal + int j; + for (j = 16; j > 0; --j) { + memset(dst, dst[-1], 16); + dst += BPS; + } +} + +static WEBP_INLINE void Put16(int v, uint8_t* dst) { + int j; + for (j = 0; j < 16; ++j) { + memset(dst + j * BPS, v, 16); + } +} + +static void DC16_C(uint8_t* dst) { // DC + int DC = 16; + int j; + for (j = 0; j < 16; ++j) { + DC += dst[-1 + j * BPS] + dst[j - BPS]; + } + Put16(DC >> 5, dst); +} + +static void DC16NoTop_C(uint8_t* dst) { // DC with top samples not available + int DC = 8; + int j; + for (j = 0; j < 16; ++j) { + DC += dst[-1 + j * BPS]; + } + Put16(DC >> 4, dst); +} + +static void DC16NoLeft_C(uint8_t* dst) { // DC with left samples not available + int DC = 8; + int i; + for (i = 0; i < 16; ++i) { + DC += dst[i - BPS]; + } + Put16(DC >> 4, dst); +} + +static void DC16NoTopLeft_C(uint8_t* dst) { // DC with no top and left samples + Put16(0x80, dst); +} +#endif // !WEBP_NEON_OMIT_C_CODE + +VP8PredFunc VP8PredLuma16[NUM_B_DC_MODES]; + +//------------------------------------------------------------------------------ +// 4x4 + +#define AVG3(a, b, c) ((uint8_t)(((a) + 2 * (b) + (c) + 2) >> 2)) +#define AVG2(a, b) (((a) + (b) + 1) >> 1) + +#if !WEBP_NEON_OMIT_C_CODE +static void VE4_C(uint8_t* dst) { // vertical + const uint8_t* top = dst - BPS; + const uint8_t vals[4] = { + AVG3(top[-1], top[0], top[1]), + AVG3(top[ 0], top[1], top[2]), + AVG3(top[ 1], top[2], top[3]), + AVG3(top[ 2], top[3], top[4]) + }; + int i; + for (i = 0; i < 4; ++i) { + memcpy(dst + i * BPS, vals, sizeof(vals)); + } +} +#endif // !WEBP_NEON_OMIT_C_CODE + +static void HE4_C(uint8_t* dst) { // horizontal + const int A = dst[-1 - BPS]; + const int B = dst[-1]; + const int C = dst[-1 + BPS]; + const int D = dst[-1 + 2 * BPS]; + const int E = dst[-1 + 3 * BPS]; + WebPUint32ToMem(dst + 0 * BPS, 0x01010101U * AVG3(A, B, C)); + WebPUint32ToMem(dst + 1 * BPS, 0x01010101U * AVG3(B, C, D)); + WebPUint32ToMem(dst + 2 * BPS, 0x01010101U * AVG3(C, D, E)); + WebPUint32ToMem(dst + 3 * BPS, 0x01010101U * AVG3(D, E, E)); +} + +#if !WEBP_NEON_OMIT_C_CODE +static void DC4_C(uint8_t* dst) { // DC + uint32_t dc = 4; + int i; + for (i = 0; i < 4; ++i) dc += dst[i - BPS] + dst[-1 + i * BPS]; + dc >>= 3; + for (i = 0; i < 4; ++i) memset(dst + i * BPS, dc, 4); +} + +static void RD4_C(uint8_t* dst) { // Down-right + const int I = dst[-1 + 0 * BPS]; + const int J = dst[-1 + 1 * BPS]; + const int K = dst[-1 + 2 * BPS]; + const int L = dst[-1 + 3 * BPS]; + const int X = dst[-1 - BPS]; + const int A = dst[0 - BPS]; + const int B = dst[1 - BPS]; + const int C = dst[2 - BPS]; + const int D = dst[3 - BPS]; + DST(0, 3) = AVG3(J, K, L); + DST(1, 3) = DST(0, 2) = AVG3(I, J, K); + DST(2, 3) = DST(1, 2) = DST(0, 1) = AVG3(X, I, J); + DST(3, 3) = DST(2, 2) = DST(1, 1) = DST(0, 0) = AVG3(A, X, I); + DST(3, 2) = DST(2, 1) = DST(1, 0) = AVG3(B, A, X); + DST(3, 1) = DST(2, 0) = AVG3(C, B, A); + DST(3, 0) = AVG3(D, C, B); +} + +static void LD4_C(uint8_t* dst) { // Down-Left + const int A = dst[0 - BPS]; + const int B = dst[1 - BPS]; + const int C = dst[2 - BPS]; + const int D = dst[3 - BPS]; + const int E = dst[4 - BPS]; + const int F = dst[5 - BPS]; + const int G = dst[6 - BPS]; + const int H = dst[7 - BPS]; + DST(0, 0) = AVG3(A, B, C); + DST(1, 0) = DST(0, 1) = AVG3(B, C, D); + DST(2, 0) = DST(1, 1) = DST(0, 2) = AVG3(C, D, E); + DST(3, 0) = DST(2, 1) = DST(1, 2) = DST(0, 3) = AVG3(D, E, F); + DST(3, 1) = DST(2, 2) = DST(1, 3) = AVG3(E, F, G); + DST(3, 2) = DST(2, 3) = AVG3(F, G, H); + DST(3, 3) = AVG3(G, H, H); +} +#endif // !WEBP_NEON_OMIT_C_CODE + +static void VR4_C(uint8_t* dst) { // Vertical-Right + const int I = dst[-1 + 0 * BPS]; + const int J = dst[-1 + 1 * BPS]; + const int K = dst[-1 + 2 * BPS]; + const int X = dst[-1 - BPS]; + const int A = dst[0 - BPS]; + const int B = dst[1 - BPS]; + const int C = dst[2 - BPS]; + const int D = dst[3 - BPS]; + DST(0, 0) = DST(1, 2) = AVG2(X, A); + DST(1, 0) = DST(2, 2) = AVG2(A, B); + DST(2, 0) = DST(3, 2) = AVG2(B, C); + DST(3, 0) = AVG2(C, D); + + DST(0, 3) = AVG3(K, J, I); + DST(0, 2) = AVG3(J, I, X); + DST(0, 1) = DST(1, 3) = AVG3(I, X, A); + DST(1, 1) = DST(2, 3) = AVG3(X, A, B); + DST(2, 1) = DST(3, 3) = AVG3(A, B, C); + DST(3, 1) = AVG3(B, C, D); +} + +static void VL4_C(uint8_t* dst) { // Vertical-Left + const int A = dst[0 - BPS]; + const int B = dst[1 - BPS]; + const int C = dst[2 - BPS]; + const int D = dst[3 - BPS]; + const int E = dst[4 - BPS]; + const int F = dst[5 - BPS]; + const int G = dst[6 - BPS]; + const int H = dst[7 - BPS]; + DST(0, 0) = AVG2(A, B); + DST(1, 0) = DST(0, 2) = AVG2(B, C); + DST(2, 0) = DST(1, 2) = AVG2(C, D); + DST(3, 0) = DST(2, 2) = AVG2(D, E); + + DST(0, 1) = AVG3(A, B, C); + DST(1, 1) = DST(0, 3) = AVG3(B, C, D); + DST(2, 1) = DST(1, 3) = AVG3(C, D, E); + DST(3, 1) = DST(2, 3) = AVG3(D, E, F); + DST(3, 2) = AVG3(E, F, G); + DST(3, 3) = AVG3(F, G, H); +} + +static void HU4_C(uint8_t* dst) { // Horizontal-Up + const int I = dst[-1 + 0 * BPS]; + const int J = dst[-1 + 1 * BPS]; + const int K = dst[-1 + 2 * BPS]; + const int L = dst[-1 + 3 * BPS]; + DST(0, 0) = AVG2(I, J); + DST(2, 0) = DST(0, 1) = AVG2(J, K); + DST(2, 1) = DST(0, 2) = AVG2(K, L); + DST(1, 0) = AVG3(I, J, K); + DST(3, 0) = DST(1, 1) = AVG3(J, K, L); + DST(3, 1) = DST(1, 2) = AVG3(K, L, L); + DST(3, 2) = DST(2, 2) = + DST(0, 3) = DST(1, 3) = DST(2, 3) = DST(3, 3) = L; +} + +static void HD4_C(uint8_t* dst) { // Horizontal-Down + const int I = dst[-1 + 0 * BPS]; + const int J = dst[-1 + 1 * BPS]; + const int K = dst[-1 + 2 * BPS]; + const int L = dst[-1 + 3 * BPS]; + const int X = dst[-1 - BPS]; + const int A = dst[0 - BPS]; + const int B = dst[1 - BPS]; + const int C = dst[2 - BPS]; + + DST(0, 0) = DST(2, 1) = AVG2(I, X); + DST(0, 1) = DST(2, 2) = AVG2(J, I); + DST(0, 2) = DST(2, 3) = AVG2(K, J); + DST(0, 3) = AVG2(L, K); + + DST(3, 0) = AVG3(A, B, C); + DST(2, 0) = AVG3(X, A, B); + DST(1, 0) = DST(3, 1) = AVG3(I, X, A); + DST(1, 1) = DST(3, 2) = AVG3(J, I, X); + DST(1, 2) = DST(3, 3) = AVG3(K, J, I); + DST(1, 3) = AVG3(L, K, J); +} + +#undef DST +#undef AVG3 +#undef AVG2 + +VP8PredFunc VP8PredLuma4[NUM_BMODES]; + +//------------------------------------------------------------------------------ +// Chroma + +#if !WEBP_NEON_OMIT_C_CODE +static void VE8uv_C(uint8_t* dst) { // vertical + int j; + for (j = 0; j < 8; ++j) { + memcpy(dst + j * BPS, dst - BPS, 8); + } +} + +static void HE8uv_C(uint8_t* dst) { // horizontal + int j; + for (j = 0; j < 8; ++j) { + memset(dst, dst[-1], 8); + dst += BPS; + } +} + +// helper for chroma-DC predictions +static WEBP_INLINE void Put8x8uv(uint8_t value, uint8_t* dst) { + int j; + for (j = 0; j < 8; ++j) { + memset(dst + j * BPS, value, 8); + } +} + +static void DC8uv_C(uint8_t* dst) { // DC + int dc0 = 8; + int i; + for (i = 0; i < 8; ++i) { + dc0 += dst[i - BPS] + dst[-1 + i * BPS]; + } + Put8x8uv(dc0 >> 4, dst); +} + +static void DC8uvNoLeft_C(uint8_t* dst) { // DC with no left samples + int dc0 = 4; + int i; + for (i = 0; i < 8; ++i) { + dc0 += dst[i - BPS]; + } + Put8x8uv(dc0 >> 3, dst); +} + +static void DC8uvNoTop_C(uint8_t* dst) { // DC with no top samples + int dc0 = 4; + int i; + for (i = 0; i < 8; ++i) { + dc0 += dst[-1 + i * BPS]; + } + Put8x8uv(dc0 >> 3, dst); +} + +static void DC8uvNoTopLeft_C(uint8_t* dst) { // DC with nothing + Put8x8uv(0x80, dst); +} +#endif // !WEBP_NEON_OMIT_C_CODE + +VP8PredFunc VP8PredChroma8[NUM_B_DC_MODES]; + +//------------------------------------------------------------------------------ +// Edge filtering functions + +#if !WEBP_NEON_OMIT_C_CODE || WEBP_NEON_WORK_AROUND_GCC +// 4 pixels in, 2 pixels out +static WEBP_INLINE void DoFilter2_C(uint8_t* p, int step) { + const int p1 = p[-2*step], p0 = p[-step], q0 = p[0], q1 = p[step]; + const int a = 3 * (q0 - p0) + VP8ksclip1[p1 - q1]; // in [-893,892] + const int a1 = VP8ksclip2[(a + 4) >> 3]; // in [-16,15] + const int a2 = VP8ksclip2[(a + 3) >> 3]; + p[-step] = VP8kclip1[p0 + a2]; + p[ 0] = VP8kclip1[q0 - a1]; +} + +// 4 pixels in, 4 pixels out +static WEBP_INLINE void DoFilter4_C(uint8_t* p, int step) { + const int p1 = p[-2*step], p0 = p[-step], q0 = p[0], q1 = p[step]; + const int a = 3 * (q0 - p0); + const int a1 = VP8ksclip2[(a + 4) >> 3]; + const int a2 = VP8ksclip2[(a + 3) >> 3]; + const int a3 = (a1 + 1) >> 1; + p[-2*step] = VP8kclip1[p1 + a3]; + p[- step] = VP8kclip1[p0 + a2]; + p[ 0] = VP8kclip1[q0 - a1]; + p[ step] = VP8kclip1[q1 - a3]; +} + +// 6 pixels in, 6 pixels out +static WEBP_INLINE void DoFilter6_C(uint8_t* p, int step) { + const int p2 = p[-3*step], p1 = p[-2*step], p0 = p[-step]; + const int q0 = p[0], q1 = p[step], q2 = p[2*step]; + const int a = VP8ksclip1[3 * (q0 - p0) + VP8ksclip1[p1 - q1]]; + // a is in [-128,127], a1 in [-27,27], a2 in [-18,18] and a3 in [-9,9] + const int a1 = (27 * a + 63) >> 7; // eq. to ((3 * a + 7) * 9) >> 7 + const int a2 = (18 * a + 63) >> 7; // eq. to ((2 * a + 7) * 9) >> 7 + const int a3 = (9 * a + 63) >> 7; // eq. to ((1 * a + 7) * 9) >> 7 + p[-3*step] = VP8kclip1[p2 + a3]; + p[-2*step] = VP8kclip1[p1 + a2]; + p[- step] = VP8kclip1[p0 + a1]; + p[ 0] = VP8kclip1[q0 - a1]; + p[ step] = VP8kclip1[q1 - a2]; + p[ 2*step] = VP8kclip1[q2 - a3]; +} + +static WEBP_INLINE int Hev(const uint8_t* p, int step, int thresh) { + const int p1 = p[-2*step], p0 = p[-step], q0 = p[0], q1 = p[step]; + return (VP8kabs0[p1 - p0] > thresh) || (VP8kabs0[q1 - q0] > thresh); +} +#endif // !WEBP_NEON_OMIT_C_CODE || WEBP_NEON_WORK_AROUND_GCC + +#if !WEBP_NEON_OMIT_C_CODE +static WEBP_INLINE int NeedsFilter_C(const uint8_t* p, int step, int t) { + const int p1 = p[-2 * step], p0 = p[-step], q0 = p[0], q1 = p[step]; + return ((4 * VP8kabs0[p0 - q0] + VP8kabs0[p1 - q1]) <= t); +} +#endif // !WEBP_NEON_OMIT_C_CODE + +#if !WEBP_NEON_OMIT_C_CODE || WEBP_NEON_WORK_AROUND_GCC +static WEBP_INLINE int NeedsFilter2_C(const uint8_t* p, + int step, int t, int it) { + const int p3 = p[-4 * step], p2 = p[-3 * step], p1 = p[-2 * step]; + const int p0 = p[-step], q0 = p[0]; + const int q1 = p[step], q2 = p[2 * step], q3 = p[3 * step]; + if ((4 * VP8kabs0[p0 - q0] + VP8kabs0[p1 - q1]) > t) return 0; + return VP8kabs0[p3 - p2] <= it && VP8kabs0[p2 - p1] <= it && + VP8kabs0[p1 - p0] <= it && VP8kabs0[q3 - q2] <= it && + VP8kabs0[q2 - q1] <= it && VP8kabs0[q1 - q0] <= it; +} +#endif // !WEBP_NEON_OMIT_C_CODE || WEBP_NEON_WORK_AROUND_GCC + +//------------------------------------------------------------------------------ +// Simple In-loop filtering (Paragraph 15.2) + +#if !WEBP_NEON_OMIT_C_CODE +static void SimpleVFilter16_C(uint8_t* p, int stride, int thresh) { + int i; + const int thresh2 = 2 * thresh + 1; + for (i = 0; i < 16; ++i) { + if (NeedsFilter_C(p + i, stride, thresh2)) { + DoFilter2_C(p + i, stride); + } + } +} + +static void SimpleHFilter16_C(uint8_t* p, int stride, int thresh) { + int i; + const int thresh2 = 2 * thresh + 1; + for (i = 0; i < 16; ++i) { + if (NeedsFilter_C(p + i * stride, 1, thresh2)) { + DoFilter2_C(p + i * stride, 1); + } + } +} + +static void SimpleVFilter16i_C(uint8_t* p, int stride, int thresh) { + int k; + for (k = 3; k > 0; --k) { + p += 4 * stride; + SimpleVFilter16_C(p, stride, thresh); + } +} + +static void SimpleHFilter16i_C(uint8_t* p, int stride, int thresh) { + int k; + for (k = 3; k > 0; --k) { + p += 4; + SimpleHFilter16_C(p, stride, thresh); + } +} +#endif // !WEBP_NEON_OMIT_C_CODE + +//------------------------------------------------------------------------------ +// Complex In-loop filtering (Paragraph 15.3) + +#if !WEBP_NEON_OMIT_C_CODE || WEBP_NEON_WORK_AROUND_GCC +static WEBP_INLINE void FilterLoop26_C(uint8_t* p, + int hstride, int vstride, int size, + int thresh, int ithresh, + int hev_thresh) { + const int thresh2 = 2 * thresh + 1; + while (size-- > 0) { + if (NeedsFilter2_C(p, hstride, thresh2, ithresh)) { + if (Hev(p, hstride, hev_thresh)) { + DoFilter2_C(p, hstride); + } else { + DoFilter6_C(p, hstride); + } + } + p += vstride; + } +} + +static WEBP_INLINE void FilterLoop24_C(uint8_t* p, + int hstride, int vstride, int size, + int thresh, int ithresh, + int hev_thresh) { + const int thresh2 = 2 * thresh + 1; + while (size-- > 0) { + if (NeedsFilter2_C(p, hstride, thresh2, ithresh)) { + if (Hev(p, hstride, hev_thresh)) { + DoFilter2_C(p, hstride); + } else { + DoFilter4_C(p, hstride); + } + } + p += vstride; + } +} +#endif // !WEBP_NEON_OMIT_C_CODE || WEBP_NEON_WORK_AROUND_GCC + +#if !WEBP_NEON_OMIT_C_CODE +// on macroblock edges +static void VFilter16_C(uint8_t* p, int stride, + int thresh, int ithresh, int hev_thresh) { + FilterLoop26_C(p, stride, 1, 16, thresh, ithresh, hev_thresh); +} + +static void HFilter16_C(uint8_t* p, int stride, + int thresh, int ithresh, int hev_thresh) { + FilterLoop26_C(p, 1, stride, 16, thresh, ithresh, hev_thresh); +} + +// on three inner edges +static void VFilter16i_C(uint8_t* p, int stride, + int thresh, int ithresh, int hev_thresh) { + int k; + for (k = 3; k > 0; --k) { + p += 4 * stride; + FilterLoop24_C(p, stride, 1, 16, thresh, ithresh, hev_thresh); + } +} +#endif // !WEBP_NEON_OMIT_C_CODE + +#if !WEBP_NEON_OMIT_C_CODE || WEBP_NEON_WORK_AROUND_GCC +static void HFilter16i_C(uint8_t* p, int stride, + int thresh, int ithresh, int hev_thresh) { + int k; + for (k = 3; k > 0; --k) { + p += 4; + FilterLoop24_C(p, 1, stride, 16, thresh, ithresh, hev_thresh); + } +} +#endif // !WEBP_NEON_OMIT_C_CODE || WEBP_NEON_WORK_AROUND_GCC + +#if !WEBP_NEON_OMIT_C_CODE +// 8-pixels wide variant, for chroma filtering +static void VFilter8_C(uint8_t* WEBP_RESTRICT u, uint8_t* WEBP_RESTRICT v, + int stride, int thresh, int ithresh, int hev_thresh) { + FilterLoop26_C(u, stride, 1, 8, thresh, ithresh, hev_thresh); + FilterLoop26_C(v, stride, 1, 8, thresh, ithresh, hev_thresh); +} +#endif // !WEBP_NEON_OMIT_C_CODE + +#if !WEBP_NEON_OMIT_C_CODE || WEBP_NEON_WORK_AROUND_GCC +static void HFilter8_C(uint8_t* WEBP_RESTRICT u, uint8_t* WEBP_RESTRICT v, + int stride, int thresh, int ithresh, int hev_thresh) { + FilterLoop26_C(u, 1, stride, 8, thresh, ithresh, hev_thresh); + FilterLoop26_C(v, 1, stride, 8, thresh, ithresh, hev_thresh); +} +#endif // !WEBP_NEON_OMIT_C_CODE || WEBP_NEON_WORK_AROUND_GCC + +#if !WEBP_NEON_OMIT_C_CODE +static void VFilter8i_C(uint8_t* WEBP_RESTRICT u, uint8_t* WEBP_RESTRICT v, + int stride, int thresh, int ithresh, int hev_thresh) { + FilterLoop24_C(u + 4 * stride, stride, 1, 8, thresh, ithresh, hev_thresh); + FilterLoop24_C(v + 4 * stride, stride, 1, 8, thresh, ithresh, hev_thresh); +} +#endif // !WEBP_NEON_OMIT_C_CODE + +#if !WEBP_NEON_OMIT_C_CODE || WEBP_NEON_WORK_AROUND_GCC +static void HFilter8i_C(uint8_t* WEBP_RESTRICT u, uint8_t* WEBP_RESTRICT v, + int stride, int thresh, int ithresh, int hev_thresh) { + FilterLoop24_C(u + 4, 1, stride, 8, thresh, ithresh, hev_thresh); + FilterLoop24_C(v + 4, 1, stride, 8, thresh, ithresh, hev_thresh); +} +#endif // !WEBP_NEON_OMIT_C_CODE || WEBP_NEON_WORK_AROUND_GCC + +//------------------------------------------------------------------------------ + +static void DitherCombine8x8_C(const uint8_t* WEBP_RESTRICT dither, + uint8_t* WEBP_RESTRICT dst, int dst_stride) { + int i, j; + for (j = 0; j < 8; ++j) { + for (i = 0; i < 8; ++i) { + const int delta0 = dither[i] - VP8_DITHER_AMP_CENTER; + const int delta1 = + (delta0 + VP8_DITHER_DESCALE_ROUNDER) >> VP8_DITHER_DESCALE; + dst[i] = clip_8b((int)dst[i] + delta1); + } + dst += dst_stride; + dither += 8; + } +} + +//------------------------------------------------------------------------------ + +VP8DecIdct2 VP8Transform; +VP8DecIdct VP8TransformAC3; +VP8DecIdct VP8TransformUV; +VP8DecIdct VP8TransformDC; +VP8DecIdct VP8TransformDCUV; + +VP8LumaFilterFunc VP8VFilter16; +VP8LumaFilterFunc VP8HFilter16; +VP8ChromaFilterFunc VP8VFilter8; +VP8ChromaFilterFunc VP8HFilter8; +VP8LumaFilterFunc VP8VFilter16i; +VP8LumaFilterFunc VP8HFilter16i; +VP8ChromaFilterFunc VP8VFilter8i; +VP8ChromaFilterFunc VP8HFilter8i; +VP8SimpleFilterFunc VP8SimpleVFilter16; +VP8SimpleFilterFunc VP8SimpleHFilter16; +VP8SimpleFilterFunc VP8SimpleVFilter16i; +VP8SimpleFilterFunc VP8SimpleHFilter16i; + +void (*VP8DitherCombine8x8)(const uint8_t* WEBP_RESTRICT dither, + uint8_t* WEBP_RESTRICT dst, int dst_stride); + +extern VP8CPUInfo VP8GetCPUInfo; +extern void VP8DspInitSSE2(void); +extern void VP8DspInitSSE41(void); +extern void VP8DspInitNEON(void); +extern void VP8DspInitMIPS32(void); +extern void VP8DspInitMIPSdspR2(void); +extern void VP8DspInitMSA(void); + +WEBP_DSP_INIT_FUNC(VP8DspInit) { + VP8InitClipTables(); + +#if !WEBP_NEON_OMIT_C_CODE + VP8TransformWHT = TransformWHT_C; + VP8Transform = TransformTwo_C; + VP8TransformDC = TransformDC_C; + VP8TransformAC3 = TransformAC3_C; +#endif + VP8TransformUV = TransformUV_C; + VP8TransformDCUV = TransformDCUV_C; + +#if !WEBP_NEON_OMIT_C_CODE + VP8VFilter16 = VFilter16_C; + VP8VFilter16i = VFilter16i_C; + VP8HFilter16 = HFilter16_C; + VP8VFilter8 = VFilter8_C; + VP8VFilter8i = VFilter8i_C; + VP8SimpleVFilter16 = SimpleVFilter16_C; + VP8SimpleHFilter16 = SimpleHFilter16_C; + VP8SimpleVFilter16i = SimpleVFilter16i_C; + VP8SimpleHFilter16i = SimpleHFilter16i_C; +#endif + +#if !WEBP_NEON_OMIT_C_CODE || WEBP_NEON_WORK_AROUND_GCC + VP8HFilter16i = HFilter16i_C; + VP8HFilter8 = HFilter8_C; + VP8HFilter8i = HFilter8i_C; +#endif + +#if !WEBP_NEON_OMIT_C_CODE + VP8PredLuma4[0] = DC4_C; + VP8PredLuma4[1] = TM4_C; + VP8PredLuma4[2] = VE4_C; + VP8PredLuma4[4] = RD4_C; + VP8PredLuma4[6] = LD4_C; +#endif + + VP8PredLuma4[3] = HE4_C; + VP8PredLuma4[5] = VR4_C; + VP8PredLuma4[7] = VL4_C; + VP8PredLuma4[8] = HD4_C; + VP8PredLuma4[9] = HU4_C; + +#if !WEBP_NEON_OMIT_C_CODE + VP8PredLuma16[0] = DC16_C; + VP8PredLuma16[1] = TM16_C; + VP8PredLuma16[2] = VE16_C; + VP8PredLuma16[3] = HE16_C; + VP8PredLuma16[4] = DC16NoTop_C; + VP8PredLuma16[5] = DC16NoLeft_C; + VP8PredLuma16[6] = DC16NoTopLeft_C; + + VP8PredChroma8[0] = DC8uv_C; + VP8PredChroma8[1] = TM8uv_C; + VP8PredChroma8[2] = VE8uv_C; + VP8PredChroma8[3] = HE8uv_C; + VP8PredChroma8[4] = DC8uvNoTop_C; + VP8PredChroma8[5] = DC8uvNoLeft_C; + VP8PredChroma8[6] = DC8uvNoTopLeft_C; +#endif + + VP8DitherCombine8x8 = DitherCombine8x8_C; + + // If defined, use CPUInfo() to overwrite some pointers with faster versions. + if (VP8GetCPUInfo != NULL) { +#if defined(WEBP_HAVE_SSE2) + if (VP8GetCPUInfo(kSSE2)) { + VP8DspInitSSE2(); +#if defined(WEBP_HAVE_SSE41) + if (VP8GetCPUInfo(kSSE4_1)) { + VP8DspInitSSE41(); + } +#endif + } +#endif +#if defined(WEBP_USE_MIPS32) + if (VP8GetCPUInfo(kMIPS32)) { + VP8DspInitMIPS32(); + } +#endif +#if defined(WEBP_USE_MIPS_DSP_R2) + if (VP8GetCPUInfo(kMIPSdspR2)) { + VP8DspInitMIPSdspR2(); + } +#endif +#if defined(WEBP_USE_MSA) + if (VP8GetCPUInfo(kMSA)) { + VP8DspInitMSA(); + } +#endif + } + +#if defined(WEBP_HAVE_NEON) + if (WEBP_NEON_OMIT_C_CODE || + (VP8GetCPUInfo != NULL && VP8GetCPUInfo(kNEON))) { + VP8DspInitNEON(); + } +#endif + + assert(VP8TransformWHT != NULL); + assert(VP8Transform != NULL); + assert(VP8TransformDC != NULL); + assert(VP8TransformAC3 != NULL); + assert(VP8TransformUV != NULL); + assert(VP8TransformDCUV != NULL); + assert(VP8VFilter16 != NULL); + assert(VP8HFilter16 != NULL); + assert(VP8VFilter8 != NULL); + assert(VP8HFilter8 != NULL); + assert(VP8VFilter16i != NULL); + assert(VP8HFilter16i != NULL); + assert(VP8VFilter8i != NULL); + assert(VP8HFilter8i != NULL); + assert(VP8SimpleVFilter16 != NULL); + assert(VP8SimpleHFilter16 != NULL); + assert(VP8SimpleVFilter16i != NULL); + assert(VP8SimpleHFilter16i != NULL); + assert(VP8PredLuma4[0] != NULL); + assert(VP8PredLuma4[1] != NULL); + assert(VP8PredLuma4[2] != NULL); + assert(VP8PredLuma4[3] != NULL); + assert(VP8PredLuma4[4] != NULL); + assert(VP8PredLuma4[5] != NULL); + assert(VP8PredLuma4[6] != NULL); + assert(VP8PredLuma4[7] != NULL); + assert(VP8PredLuma4[8] != NULL); + assert(VP8PredLuma4[9] != NULL); + assert(VP8PredLuma16[0] != NULL); + assert(VP8PredLuma16[1] != NULL); + assert(VP8PredLuma16[2] != NULL); + assert(VP8PredLuma16[3] != NULL); + assert(VP8PredLuma16[4] != NULL); + assert(VP8PredLuma16[5] != NULL); + assert(VP8PredLuma16[6] != NULL); + assert(VP8PredChroma8[0] != NULL); + assert(VP8PredChroma8[1] != NULL); + assert(VP8PredChroma8[2] != NULL); + assert(VP8PredChroma8[3] != NULL); + assert(VP8PredChroma8[4] != NULL); + assert(VP8PredChroma8[5] != NULL); + assert(VP8PredChroma8[6] != NULL); + assert(VP8DitherCombine8x8 != NULL); +} diff --git a/packages/core/src/zig/vendor/libwebp/src/dsp/dec_clip_tables.c b/packages/core/src/zig/vendor/libwebp/src/dsp/dec_clip_tables.c new file mode 100644 index 0000000000..4c816ddbd4 --- /dev/null +++ b/packages/core/src/zig/vendor/libwebp/src/dsp/dec_clip_tables.c @@ -0,0 +1,371 @@ +// Copyright 2014 Google Inc. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the COPYING file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +// ----------------------------------------------------------------------------- +// +// Clipping tables for filtering +// +// Author: Skal (pascal.massimino@gmail.com) + +#include "src/dsp/cpu.h" +#include "src/webp/types.h" +#include "src/dsp/dsp.h" + +// define to 0 to have run-time table initialization +#if !defined(USE_STATIC_TABLES) +#define USE_STATIC_TABLES 1 // ALTERNATE_CODE +#endif + +#if (USE_STATIC_TABLES == 1) + +static const uint8_t abs0[255 + 255 + 1] = { + 0xff, 0xfe, 0xfd, 0xfc, 0xfb, 0xfa, 0xf9, 0xf8, 0xf7, 0xf6, 0xf5, 0xf4, + 0xf3, 0xf2, 0xf1, 0xf0, 0xef, 0xee, 0xed, 0xec, 0xeb, 0xea, 0xe9, 0xe8, + 0xe7, 0xe6, 0xe5, 0xe4, 0xe3, 0xe2, 0xe1, 0xe0, 0xdf, 0xde, 0xdd, 0xdc, + 0xdb, 0xda, 0xd9, 0xd8, 0xd7, 0xd6, 0xd5, 0xd4, 0xd3, 0xd2, 0xd1, 0xd0, + 0xcf, 0xce, 0xcd, 0xcc, 0xcb, 0xca, 0xc9, 0xc8, 0xc7, 0xc6, 0xc5, 0xc4, + 0xc3, 0xc2, 0xc1, 0xc0, 0xbf, 0xbe, 0xbd, 0xbc, 0xbb, 0xba, 0xb9, 0xb8, + 0xb7, 0xb6, 0xb5, 0xb4, 0xb3, 0xb2, 0xb1, 0xb0, 0xaf, 0xae, 0xad, 0xac, + 0xab, 0xaa, 0xa9, 0xa8, 0xa7, 0xa6, 0xa5, 0xa4, 0xa3, 0xa2, 0xa1, 0xa0, + 0x9f, 0x9e, 0x9d, 0x9c, 0x9b, 0x9a, 0x99, 0x98, 0x97, 0x96, 0x95, 0x94, + 0x93, 0x92, 0x91, 0x90, 0x8f, 0x8e, 0x8d, 0x8c, 0x8b, 0x8a, 0x89, 0x88, + 0x87, 0x86, 0x85, 0x84, 0x83, 0x82, 0x81, 0x80, 0x7f, 0x7e, 0x7d, 0x7c, + 0x7b, 0x7a, 0x79, 0x78, 0x77, 0x76, 0x75, 0x74, 0x73, 0x72, 0x71, 0x70, + 0x6f, 0x6e, 0x6d, 0x6c, 0x6b, 0x6a, 0x69, 0x68, 0x67, 0x66, 0x65, 0x64, + 0x63, 0x62, 0x61, 0x60, 0x5f, 0x5e, 0x5d, 0x5c, 0x5b, 0x5a, 0x59, 0x58, + 0x57, 0x56, 0x55, 0x54, 0x53, 0x52, 0x51, 0x50, 0x4f, 0x4e, 0x4d, 0x4c, + 0x4b, 0x4a, 0x49, 0x48, 0x47, 0x46, 0x45, 0x44, 0x43, 0x42, 0x41, 0x40, + 0x3f, 0x3e, 0x3d, 0x3c, 0x3b, 0x3a, 0x39, 0x38, 0x37, 0x36, 0x35, 0x34, + 0x33, 0x32, 0x31, 0x30, 0x2f, 0x2e, 0x2d, 0x2c, 0x2b, 0x2a, 0x29, 0x28, + 0x27, 0x26, 0x25, 0x24, 0x23, 0x22, 0x21, 0x20, 0x1f, 0x1e, 0x1d, 0x1c, + 0x1b, 0x1a, 0x19, 0x18, 0x17, 0x16, 0x15, 0x14, 0x13, 0x12, 0x11, 0x10, + 0x0f, 0x0e, 0x0d, 0x0c, 0x0b, 0x0a, 0x09, 0x08, 0x07, 0x06, 0x05, 0x04, + 0x03, 0x02, 0x01, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, + 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, + 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20, + 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x2b, 0x2c, + 0x2d, 0x2e, 0x2f, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, + 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, 0x40, 0x41, 0x42, 0x43, 0x44, + 0x45, 0x46, 0x47, 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, 0x50, + 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5a, 0x5b, 0x5c, + 0x5d, 0x5e, 0x5f, 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, + 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, 0x70, 0x71, 0x72, 0x73, 0x74, + 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a, 0x7b, 0x7c, 0x7d, 0x7e, 0x7f, 0x80, + 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8a, 0x8b, 0x8c, + 0x8d, 0x8e, 0x8f, 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, + 0x99, 0x9a, 0x9b, 0x9c, 0x9d, 0x9e, 0x9f, 0xa0, 0xa1, 0xa2, 0xa3, 0xa4, + 0xa5, 0xa6, 0xa7, 0xa8, 0xa9, 0xaa, 0xab, 0xac, 0xad, 0xae, 0xaf, 0xb0, + 0xb1, 0xb2, 0xb3, 0xb4, 0xb5, 0xb6, 0xb7, 0xb8, 0xb9, 0xba, 0xbb, 0xbc, + 0xbd, 0xbe, 0xbf, 0xc0, 0xc1, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7, 0xc8, + 0xc9, 0xca, 0xcb, 0xcc, 0xcd, 0xce, 0xcf, 0xd0, 0xd1, 0xd2, 0xd3, 0xd4, + 0xd5, 0xd6, 0xd7, 0xd8, 0xd9, 0xda, 0xdb, 0xdc, 0xdd, 0xde, 0xdf, 0xe0, + 0xe1, 0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, 0xea, 0xeb, 0xec, + 0xed, 0xee, 0xef, 0xf0, 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, + 0xf9, 0xfa, 0xfb, 0xfc, 0xfd, 0xfe, 0xff +}; + +static const uint8_t sclip1[1020 + 1020 + 1] = { + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, + 0x88, 0x89, 0x8a, 0x8b, 0x8c, 0x8d, 0x8e, 0x8f, 0x90, 0x91, 0x92, 0x93, + 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9a, 0x9b, 0x9c, 0x9d, 0x9e, 0x9f, + 0xa0, 0xa1, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7, 0xa8, 0xa9, 0xaa, 0xab, + 0xac, 0xad, 0xae, 0xaf, 0xb0, 0xb1, 0xb2, 0xb3, 0xb4, 0xb5, 0xb6, 0xb7, + 0xb8, 0xb9, 0xba, 0xbb, 0xbc, 0xbd, 0xbe, 0xbf, 0xc0, 0xc1, 0xc2, 0xc3, + 0xc4, 0xc5, 0xc6, 0xc7, 0xc8, 0xc9, 0xca, 0xcb, 0xcc, 0xcd, 0xce, 0xcf, + 0xd0, 0xd1, 0xd2, 0xd3, 0xd4, 0xd5, 0xd6, 0xd7, 0xd8, 0xd9, 0xda, 0xdb, + 0xdc, 0xdd, 0xde, 0xdf, 0xe0, 0xe1, 0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xe7, + 0xe8, 0xe9, 0xea, 0xeb, 0xec, 0xed, 0xee, 0xef, 0xf0, 0xf1, 0xf2, 0xf3, + 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, 0xf9, 0xfa, 0xfb, 0xfc, 0xfd, 0xfe, 0xff, + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, + 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, + 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20, 0x21, 0x22, 0x23, + 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, + 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x3b, + 0x3c, 0x3d, 0x3e, 0x3f, 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, + 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, 0x50, 0x51, 0x52, 0x53, + 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f, + 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6a, 0x6b, + 0x6c, 0x6d, 0x6e, 0x6f, 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, + 0x78, 0x79, 0x7a, 0x7b, 0x7c, 0x7d, 0x7e, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, + 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, + 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, + 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, + 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, + 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, + 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, + 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, + 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, + 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, + 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, + 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, + 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, + 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, + 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, + 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, + 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, + 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, + 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, + 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, + 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, + 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, + 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, + 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, + 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, + 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, + 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, + 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, + 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, + 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, + 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, + 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, + 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, + 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, + 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, + 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, + 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, + 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, + 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, + 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, + 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, + 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, + 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, + 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, + 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, + 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, + 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, + 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, + 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, + 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, + 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, + 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, + 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, + 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, + 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, + 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, + 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, + 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, + 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, + 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, + 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, + 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, + 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, + 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, + 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, + 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, + 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, + 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, + 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, + 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, + 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, + 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, + 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, + 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, + 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f +}; + +static const uint8_t sclip2[112 + 112 + 1] = { + 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, + 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, + 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, + 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, + 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, + 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, + 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, + 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, + 0xf0, 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, 0xf9, 0xfa, 0xfb, + 0xfc, 0xfd, 0xfe, 0xff, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, + 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, + 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, + 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, + 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, + 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, + 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, + 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, + 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f +}; + +static const uint8_t clip1[255 + 511 + 1] = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, + 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, + 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20, + 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x2b, 0x2c, + 0x2d, 0x2e, 0x2f, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, + 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, 0x40, 0x41, 0x42, 0x43, 0x44, + 0x45, 0x46, 0x47, 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, 0x50, + 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5a, 0x5b, 0x5c, + 0x5d, 0x5e, 0x5f, 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, + 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, 0x70, 0x71, 0x72, 0x73, 0x74, + 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a, 0x7b, 0x7c, 0x7d, 0x7e, 0x7f, 0x80, + 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8a, 0x8b, 0x8c, + 0x8d, 0x8e, 0x8f, 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, + 0x99, 0x9a, 0x9b, 0x9c, 0x9d, 0x9e, 0x9f, 0xa0, 0xa1, 0xa2, 0xa3, 0xa4, + 0xa5, 0xa6, 0xa7, 0xa8, 0xa9, 0xaa, 0xab, 0xac, 0xad, 0xae, 0xaf, 0xb0, + 0xb1, 0xb2, 0xb3, 0xb4, 0xb5, 0xb6, 0xb7, 0xb8, 0xb9, 0xba, 0xbb, 0xbc, + 0xbd, 0xbe, 0xbf, 0xc0, 0xc1, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7, 0xc8, + 0xc9, 0xca, 0xcb, 0xcc, 0xcd, 0xce, 0xcf, 0xd0, 0xd1, 0xd2, 0xd3, 0xd4, + 0xd5, 0xd6, 0xd7, 0xd8, 0xd9, 0xda, 0xdb, 0xdc, 0xdd, 0xde, 0xdf, 0xe0, + 0xe1, 0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, 0xea, 0xeb, 0xec, + 0xed, 0xee, 0xef, 0xf0, 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, + 0xf9, 0xfa, 0xfb, 0xfc, 0xfd, 0xfe, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff +}; + +#else + +// uninitialized tables +static uint8_t abs0[255 + 255 + 1]; +static int8_t sclip1[1020 + 1020 + 1]; +static int8_t sclip2[112 + 112 + 1]; +static uint8_t clip1[255 + 511 + 1]; + +// We declare this variable 'volatile' to prevent instruction reordering +// and make sure it's set to true _last_ (so as to be thread-safe) +static volatile int tables_ok = 0; + +#endif // USE_STATIC_TABLES + +const int8_t* const VP8ksclip1 = (const int8_t*)&sclip1[1020]; +const int8_t* const VP8ksclip2 = (const int8_t*)&sclip2[112]; +const uint8_t* const VP8kclip1 = &clip1[255]; +const uint8_t* const VP8kabs0 = &abs0[255]; + +WEBP_TSAN_IGNORE_FUNCTION void VP8InitClipTables(void) { +#if (USE_STATIC_TABLES == 0) + int i; + if (!tables_ok) { + for (i = -255; i <= 255; ++i) { + abs0[255 + i] = (i < 0) ? -i : i; + } + for (i = -1020; i <= 1020; ++i) { + sclip1[1020 + i] = (i < -128) ? -128 : (i > 127) ? 127 : i; + } + for (i = -112; i <= 112; ++i) { + sclip2[112 + i] = (i < -16) ? -16 : (i > 15) ? 15 : i; + } + for (i = -255; i <= 255 + 255; ++i) { + clip1[255 + i] = (i < 0) ? 0 : (i > 255) ? 255 : i; + } + tables_ok = 1; + } +#endif // USE_STATIC_TABLES +} diff --git a/packages/core/src/zig/vendor/libwebp/src/dsp/dec_neon.c b/packages/core/src/zig/vendor/libwebp/src/dsp/dec_neon.c new file mode 100644 index 0000000000..f150692a4b --- /dev/null +++ b/packages/core/src/zig/vendor/libwebp/src/dsp/dec_neon.c @@ -0,0 +1,1670 @@ +// Copyright 2012 Google Inc. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the COPYING file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +// ----------------------------------------------------------------------------- +// +// ARM NEON version of dsp functions and loop filtering. +// +// Authors: Somnath Banerjee (somnath@google.com) +// Johann Koenig (johannkoenig@google.com) + +#include "src/dsp/dsp.h" + +#if defined(WEBP_USE_NEON) + +#include "src/dsp/neon.h" +#include "src/dec/vp8i_dec.h" + +//------------------------------------------------------------------------------ +// NxM Loading functions + +#if !defined(WORK_AROUND_GCC) + +// This intrinsics version makes gcc-4.6.3 crash during Load4x??() compilation +// (register alloc, probably). The variants somewhat mitigate the problem, but +// not quite. HFilter16i() remains problematic. +static WEBP_INLINE uint8x8x4_t Load4x8_NEON(const uint8_t* const src, + int stride) { + const uint8x8_t zero = vdup_n_u8(0); + uint8x8x4_t out; + INIT_VECTOR4(out, zero, zero, zero, zero); + out = vld4_lane_u8(src + 0 * stride, out, 0); + out = vld4_lane_u8(src + 1 * stride, out, 1); + out = vld4_lane_u8(src + 2 * stride, out, 2); + out = vld4_lane_u8(src + 3 * stride, out, 3); + out = vld4_lane_u8(src + 4 * stride, out, 4); + out = vld4_lane_u8(src + 5 * stride, out, 5); + out = vld4_lane_u8(src + 6 * stride, out, 6); + out = vld4_lane_u8(src + 7 * stride, out, 7); + return out; +} + +static WEBP_INLINE void Load4x16_NEON(const uint8_t* const src, int stride, + uint8x16_t* const p1, + uint8x16_t* const p0, + uint8x16_t* const q0, + uint8x16_t* const q1) { + // row0 = p1[0..7]|p0[0..7]|q0[0..7]|q1[0..7] + // row8 = p1[8..15]|p0[8..15]|q0[8..15]|q1[8..15] + const uint8x8x4_t row0 = Load4x8_NEON(src - 2 + 0 * stride, stride); + const uint8x8x4_t row8 = Load4x8_NEON(src - 2 + 8 * stride, stride); + *p1 = vcombine_u8(row0.val[0], row8.val[0]); + *p0 = vcombine_u8(row0.val[1], row8.val[1]); + *q0 = vcombine_u8(row0.val[2], row8.val[2]); + *q1 = vcombine_u8(row0.val[3], row8.val[3]); +} + +#else // WORK_AROUND_GCC + +#define LOADQ_LANE_32b(VALUE, LANE) do { \ + (VALUE) = vld1q_lane_u32((const uint32_t*)src, (VALUE), (LANE)); \ + src += stride; \ +} while (0) + +static WEBP_INLINE void Load4x16_NEON(const uint8_t* src, int stride, + uint8x16_t* const p1, + uint8x16_t* const p0, + uint8x16_t* const q0, + uint8x16_t* const q1) { + const uint32x4_t zero = vdupq_n_u32(0); + uint32x4x4_t in; + INIT_VECTOR4(in, zero, zero, zero, zero); + src -= 2; + LOADQ_LANE_32b(in.val[0], 0); + LOADQ_LANE_32b(in.val[1], 0); + LOADQ_LANE_32b(in.val[2], 0); + LOADQ_LANE_32b(in.val[3], 0); + LOADQ_LANE_32b(in.val[0], 1); + LOADQ_LANE_32b(in.val[1], 1); + LOADQ_LANE_32b(in.val[2], 1); + LOADQ_LANE_32b(in.val[3], 1); + LOADQ_LANE_32b(in.val[0], 2); + LOADQ_LANE_32b(in.val[1], 2); + LOADQ_LANE_32b(in.val[2], 2); + LOADQ_LANE_32b(in.val[3], 2); + LOADQ_LANE_32b(in.val[0], 3); + LOADQ_LANE_32b(in.val[1], 3); + LOADQ_LANE_32b(in.val[2], 3); + LOADQ_LANE_32b(in.val[3], 3); + // Transpose four 4x4 parts: + { + const uint8x16x2_t row01 = vtrnq_u8(vreinterpretq_u8_u32(in.val[0]), + vreinterpretq_u8_u32(in.val[1])); + const uint8x16x2_t row23 = vtrnq_u8(vreinterpretq_u8_u32(in.val[2]), + vreinterpretq_u8_u32(in.val[3])); + const uint16x8x2_t row02 = vtrnq_u16(vreinterpretq_u16_u8(row01.val[0]), + vreinterpretq_u16_u8(row23.val[0])); + const uint16x8x2_t row13 = vtrnq_u16(vreinterpretq_u16_u8(row01.val[1]), + vreinterpretq_u16_u8(row23.val[1])); + *p1 = vreinterpretq_u8_u16(row02.val[0]); + *p0 = vreinterpretq_u8_u16(row13.val[0]); + *q0 = vreinterpretq_u8_u16(row02.val[1]); + *q1 = vreinterpretq_u8_u16(row13.val[1]); + } +} +#undef LOADQ_LANE_32b + +#endif // !WORK_AROUND_GCC + +static WEBP_INLINE void Load8x16_NEON( + const uint8_t* const src, int stride, + uint8x16_t* const p3, uint8x16_t* const p2, uint8x16_t* const p1, + uint8x16_t* const p0, uint8x16_t* const q0, uint8x16_t* const q1, + uint8x16_t* const q2, uint8x16_t* const q3) { + Load4x16_NEON(src - 2, stride, p3, p2, p1, p0); + Load4x16_NEON(src + 2, stride, q0, q1, q2, q3); +} + +static WEBP_INLINE void Load16x4_NEON(const uint8_t* const src, int stride, + uint8x16_t* const p1, + uint8x16_t* const p0, + uint8x16_t* const q0, + uint8x16_t* const q1) { + *p1 = vld1q_u8(src - 2 * stride); + *p0 = vld1q_u8(src - 1 * stride); + *q0 = vld1q_u8(src + 0 * stride); + *q1 = vld1q_u8(src + 1 * stride); +} + +static WEBP_INLINE void Load16x8_NEON( + const uint8_t* const src, int stride, + uint8x16_t* const p3, uint8x16_t* const p2, uint8x16_t* const p1, + uint8x16_t* const p0, uint8x16_t* const q0, uint8x16_t* const q1, + uint8x16_t* const q2, uint8x16_t* const q3) { + Load16x4_NEON(src - 2 * stride, stride, p3, p2, p1, p0); + Load16x4_NEON(src + 2 * stride, stride, q0, q1, q2, q3); +} + +static WEBP_INLINE void Load8x8x2_NEON( + const uint8_t* const u, const uint8_t* const v, int stride, + uint8x16_t* const p3, uint8x16_t* const p2, uint8x16_t* const p1, + uint8x16_t* const p0, uint8x16_t* const q0, uint8x16_t* const q1, + uint8x16_t* const q2, uint8x16_t* const q3) { + // We pack the 8x8 u-samples in the lower half of the uint8x16_t destination + // and the v-samples on the higher half. + *p3 = vcombine_u8(vld1_u8(u - 4 * stride), vld1_u8(v - 4 * stride)); + *p2 = vcombine_u8(vld1_u8(u - 3 * stride), vld1_u8(v - 3 * stride)); + *p1 = vcombine_u8(vld1_u8(u - 2 * stride), vld1_u8(v - 2 * stride)); + *p0 = vcombine_u8(vld1_u8(u - 1 * stride), vld1_u8(v - 1 * stride)); + *q0 = vcombine_u8(vld1_u8(u + 0 * stride), vld1_u8(v + 0 * stride)); + *q1 = vcombine_u8(vld1_u8(u + 1 * stride), vld1_u8(v + 1 * stride)); + *q2 = vcombine_u8(vld1_u8(u + 2 * stride), vld1_u8(v + 2 * stride)); + *q3 = vcombine_u8(vld1_u8(u + 3 * stride), vld1_u8(v + 3 * stride)); +} + +#if !defined(WORK_AROUND_GCC) + +#define LOAD_UV_8(ROW) \ + vcombine_u8(vld1_u8(u - 4 + (ROW) * stride), vld1_u8(v - 4 + (ROW) * stride)) + +static WEBP_INLINE void Load8x8x2T_NEON( + const uint8_t* const u, const uint8_t* const v, int stride, + uint8x16_t* const p3, uint8x16_t* const p2, uint8x16_t* const p1, + uint8x16_t* const p0, uint8x16_t* const q0, uint8x16_t* const q1, + uint8x16_t* const q2, uint8x16_t* const q3) { + // We pack the 8x8 u-samples in the lower half of the uint8x16_t destination + // and the v-samples on the higher half. + const uint8x16_t row0 = LOAD_UV_8(0); + const uint8x16_t row1 = LOAD_UV_8(1); + const uint8x16_t row2 = LOAD_UV_8(2); + const uint8x16_t row3 = LOAD_UV_8(3); + const uint8x16_t row4 = LOAD_UV_8(4); + const uint8x16_t row5 = LOAD_UV_8(5); + const uint8x16_t row6 = LOAD_UV_8(6); + const uint8x16_t row7 = LOAD_UV_8(7); + // Perform two side-by-side 8x8 transposes + // u00 u01 u02 u03 u04 u05 u06 u07 | v00 v01 v02 v03 v04 v05 v06 v07 + // u10 u11 u12 u13 u14 u15 u16 u17 | v10 v11 v12 ... + // u20 u21 u22 u23 u24 u25 u26 u27 | v20 v21 ... + // u30 u31 u32 u33 u34 u35 u36 u37 | ... + // u40 u41 u42 u43 u44 u45 u46 u47 | ... + // u50 u51 u52 u53 u54 u55 u56 u57 | ... + // u60 u61 u62 u63 u64 u65 u66 u67 | v60 ... + // u70 u71 u72 u73 u74 u75 u76 u77 | v70 v71 v72 ... + const uint8x16x2_t row01 = vtrnq_u8(row0, row1); // u00 u10 u02 u12 ... + // u01 u11 u03 u13 ... + const uint8x16x2_t row23 = vtrnq_u8(row2, row3); // u20 u30 u22 u32 ... + // u21 u31 u23 u33 ... + const uint8x16x2_t row45 = vtrnq_u8(row4, row5); // ... + const uint8x16x2_t row67 = vtrnq_u8(row6, row7); // ... + const uint16x8x2_t row02 = vtrnq_u16(vreinterpretq_u16_u8(row01.val[0]), + vreinterpretq_u16_u8(row23.val[0])); + const uint16x8x2_t row13 = vtrnq_u16(vreinterpretq_u16_u8(row01.val[1]), + vreinterpretq_u16_u8(row23.val[1])); + const uint16x8x2_t row46 = vtrnq_u16(vreinterpretq_u16_u8(row45.val[0]), + vreinterpretq_u16_u8(row67.val[0])); + const uint16x8x2_t row57 = vtrnq_u16(vreinterpretq_u16_u8(row45.val[1]), + vreinterpretq_u16_u8(row67.val[1])); + const uint32x4x2_t row04 = vtrnq_u32(vreinterpretq_u32_u16(row02.val[0]), + vreinterpretq_u32_u16(row46.val[0])); + const uint32x4x2_t row26 = vtrnq_u32(vreinterpretq_u32_u16(row02.val[1]), + vreinterpretq_u32_u16(row46.val[1])); + const uint32x4x2_t row15 = vtrnq_u32(vreinterpretq_u32_u16(row13.val[0]), + vreinterpretq_u32_u16(row57.val[0])); + const uint32x4x2_t row37 = vtrnq_u32(vreinterpretq_u32_u16(row13.val[1]), + vreinterpretq_u32_u16(row57.val[1])); + *p3 = vreinterpretq_u8_u32(row04.val[0]); + *p2 = vreinterpretq_u8_u32(row15.val[0]); + *p1 = vreinterpretq_u8_u32(row26.val[0]); + *p0 = vreinterpretq_u8_u32(row37.val[0]); + *q0 = vreinterpretq_u8_u32(row04.val[1]); + *q1 = vreinterpretq_u8_u32(row15.val[1]); + *q2 = vreinterpretq_u8_u32(row26.val[1]); + *q3 = vreinterpretq_u8_u32(row37.val[1]); +} +#undef LOAD_UV_8 + +#endif // !WORK_AROUND_GCC + +static WEBP_INLINE void Store2x8_NEON(const uint8x8x2_t v, + uint8_t* const dst, int stride) { + vst2_lane_u8(dst + 0 * stride, v, 0); + vst2_lane_u8(dst + 1 * stride, v, 1); + vst2_lane_u8(dst + 2 * stride, v, 2); + vst2_lane_u8(dst + 3 * stride, v, 3); + vst2_lane_u8(dst + 4 * stride, v, 4); + vst2_lane_u8(dst + 5 * stride, v, 5); + vst2_lane_u8(dst + 6 * stride, v, 6); + vst2_lane_u8(dst + 7 * stride, v, 7); +} + +static WEBP_INLINE void Store2x16_NEON(const uint8x16_t p0, const uint8x16_t q0, + uint8_t* const dst, int stride) { + uint8x8x2_t lo, hi; + lo.val[0] = vget_low_u8(p0); + lo.val[1] = vget_low_u8(q0); + hi.val[0] = vget_high_u8(p0); + hi.val[1] = vget_high_u8(q0); + Store2x8_NEON(lo, dst - 1 + 0 * stride, stride); + Store2x8_NEON(hi, dst - 1 + 8 * stride, stride); +} + +#if !defined(WORK_AROUND_GCC) +static WEBP_INLINE void Store4x8_NEON(const uint8x8x4_t v, + uint8_t* const dst, int stride) { + vst4_lane_u8(dst + 0 * stride, v, 0); + vst4_lane_u8(dst + 1 * stride, v, 1); + vst4_lane_u8(dst + 2 * stride, v, 2); + vst4_lane_u8(dst + 3 * stride, v, 3); + vst4_lane_u8(dst + 4 * stride, v, 4); + vst4_lane_u8(dst + 5 * stride, v, 5); + vst4_lane_u8(dst + 6 * stride, v, 6); + vst4_lane_u8(dst + 7 * stride, v, 7); +} + +static WEBP_INLINE void Store4x16_NEON(const uint8x16_t p1, const uint8x16_t p0, + const uint8x16_t q0, const uint8x16_t q1, + uint8_t* const dst, int stride) { + uint8x8x4_t lo, hi; + INIT_VECTOR4(lo, + vget_low_u8(p1), vget_low_u8(p0), + vget_low_u8(q0), vget_low_u8(q1)); + INIT_VECTOR4(hi, + vget_high_u8(p1), vget_high_u8(p0), + vget_high_u8(q0), vget_high_u8(q1)); + Store4x8_NEON(lo, dst - 2 + 0 * stride, stride); + Store4x8_NEON(hi, dst - 2 + 8 * stride, stride); +} +#endif // !WORK_AROUND_GCC + +static WEBP_INLINE void Store16x2_NEON(const uint8x16_t p0, const uint8x16_t q0, + uint8_t* const dst, int stride) { + vst1q_u8(dst - stride, p0); + vst1q_u8(dst, q0); +} + +static WEBP_INLINE void Store16x4_NEON(const uint8x16_t p1, const uint8x16_t p0, + const uint8x16_t q0, const uint8x16_t q1, + uint8_t* const dst, int stride) { + Store16x2_NEON(p1, p0, dst - stride, stride); + Store16x2_NEON(q0, q1, dst + stride, stride); +} + +static WEBP_INLINE void Store8x2x2_NEON(const uint8x16_t p0, + const uint8x16_t q0, + uint8_t* const u, uint8_t* const v, + int stride) { + // p0 and q0 contain the u+v samples packed in low/high halves. + vst1_u8(u - stride, vget_low_u8(p0)); + vst1_u8(u, vget_low_u8(q0)); + vst1_u8(v - stride, vget_high_u8(p0)); + vst1_u8(v, vget_high_u8(q0)); +} + +static WEBP_INLINE void Store8x4x2_NEON(const uint8x16_t p1, + const uint8x16_t p0, + const uint8x16_t q0, + const uint8x16_t q1, + uint8_t* const u, uint8_t* const v, + int stride) { + // The p1...q1 registers contain the u+v samples packed in low/high halves. + Store8x2x2_NEON(p1, p0, u - stride, v - stride, stride); + Store8x2x2_NEON(q0, q1, u + stride, v + stride, stride); +} + +#if !defined(WORK_AROUND_GCC) + +#define STORE6_LANE(DST, VAL0, VAL1, LANE) do { \ + vst3_lane_u8((DST) - 3, (VAL0), (LANE)); \ + vst3_lane_u8((DST) + 0, (VAL1), (LANE)); \ + (DST) += stride; \ +} while (0) + +static WEBP_INLINE void Store6x8x2_NEON( + const uint8x16_t p2, const uint8x16_t p1, const uint8x16_t p0, + const uint8x16_t q0, const uint8x16_t q1, const uint8x16_t q2, + uint8_t* u, uint8_t* v, int stride) { + uint8x8x3_t u0, u1, v0, v1; + INIT_VECTOR3(u0, vget_low_u8(p2), vget_low_u8(p1), vget_low_u8(p0)); + INIT_VECTOR3(u1, vget_low_u8(q0), vget_low_u8(q1), vget_low_u8(q2)); + INIT_VECTOR3(v0, vget_high_u8(p2), vget_high_u8(p1), vget_high_u8(p0)); + INIT_VECTOR3(v1, vget_high_u8(q0), vget_high_u8(q1), vget_high_u8(q2)); + STORE6_LANE(u, u0, u1, 0); + STORE6_LANE(u, u0, u1, 1); + STORE6_LANE(u, u0, u1, 2); + STORE6_LANE(u, u0, u1, 3); + STORE6_LANE(u, u0, u1, 4); + STORE6_LANE(u, u0, u1, 5); + STORE6_LANE(u, u0, u1, 6); + STORE6_LANE(u, u0, u1, 7); + STORE6_LANE(v, v0, v1, 0); + STORE6_LANE(v, v0, v1, 1); + STORE6_LANE(v, v0, v1, 2); + STORE6_LANE(v, v0, v1, 3); + STORE6_LANE(v, v0, v1, 4); + STORE6_LANE(v, v0, v1, 5); + STORE6_LANE(v, v0, v1, 6); + STORE6_LANE(v, v0, v1, 7); +} +#undef STORE6_LANE + +static WEBP_INLINE void Store4x8x2_NEON(const uint8x16_t p1, + const uint8x16_t p0, + const uint8x16_t q0, + const uint8x16_t q1, + uint8_t* const u, uint8_t* const v, + int stride) { + uint8x8x4_t u0, v0; + INIT_VECTOR4(u0, + vget_low_u8(p1), vget_low_u8(p0), + vget_low_u8(q0), vget_low_u8(q1)); + INIT_VECTOR4(v0, + vget_high_u8(p1), vget_high_u8(p0), + vget_high_u8(q0), vget_high_u8(q1)); + vst4_lane_u8(u - 2 + 0 * stride, u0, 0); + vst4_lane_u8(u - 2 + 1 * stride, u0, 1); + vst4_lane_u8(u - 2 + 2 * stride, u0, 2); + vst4_lane_u8(u - 2 + 3 * stride, u0, 3); + vst4_lane_u8(u - 2 + 4 * stride, u0, 4); + vst4_lane_u8(u - 2 + 5 * stride, u0, 5); + vst4_lane_u8(u - 2 + 6 * stride, u0, 6); + vst4_lane_u8(u - 2 + 7 * stride, u0, 7); + vst4_lane_u8(v - 2 + 0 * stride, v0, 0); + vst4_lane_u8(v - 2 + 1 * stride, v0, 1); + vst4_lane_u8(v - 2 + 2 * stride, v0, 2); + vst4_lane_u8(v - 2 + 3 * stride, v0, 3); + vst4_lane_u8(v - 2 + 4 * stride, v0, 4); + vst4_lane_u8(v - 2 + 5 * stride, v0, 5); + vst4_lane_u8(v - 2 + 6 * stride, v0, 6); + vst4_lane_u8(v - 2 + 7 * stride, v0, 7); +} + +#endif // !WORK_AROUND_GCC + +// Zero extend 'v' to an int16x8_t. +static WEBP_INLINE int16x8_t ConvertU8ToS16_NEON(uint8x8_t v) { + return vreinterpretq_s16_u16(vmovl_u8(v)); +} + +// Performs unsigned 8b saturation on 'dst01' and 'dst23' storing the result +// to the corresponding rows of 'dst'. +static WEBP_INLINE void SaturateAndStore4x4_NEON(uint8_t* const dst, + const int16x8_t dst01, + const int16x8_t dst23) { + // Unsigned saturate to 8b. + const uint8x8_t dst01_u8 = vqmovun_s16(dst01); + const uint8x8_t dst23_u8 = vqmovun_s16(dst23); + + // Store the results. + vst1_lane_u32((uint32_t*)(dst + 0 * BPS), vreinterpret_u32_u8(dst01_u8), 0); + vst1_lane_u32((uint32_t*)(dst + 1 * BPS), vreinterpret_u32_u8(dst01_u8), 1); + vst1_lane_u32((uint32_t*)(dst + 2 * BPS), vreinterpret_u32_u8(dst23_u8), 0); + vst1_lane_u32((uint32_t*)(dst + 3 * BPS), vreinterpret_u32_u8(dst23_u8), 1); +} + +static WEBP_INLINE void Add4x4_NEON(const int16x8_t row01, + const int16x8_t row23, + uint8_t* const dst) { + uint32x2_t dst01 = vdup_n_u32(0); + uint32x2_t dst23 = vdup_n_u32(0); + + // Load the source pixels. + dst01 = vld1_lane_u32((uint32_t*)(dst + 0 * BPS), dst01, 0); + dst23 = vld1_lane_u32((uint32_t*)(dst + 2 * BPS), dst23, 0); + dst01 = vld1_lane_u32((uint32_t*)(dst + 1 * BPS), dst01, 1); + dst23 = vld1_lane_u32((uint32_t*)(dst + 3 * BPS), dst23, 1); + + { + // Convert to 16b. + const int16x8_t dst01_s16 = ConvertU8ToS16_NEON(vreinterpret_u8_u32(dst01)); + const int16x8_t dst23_s16 = ConvertU8ToS16_NEON(vreinterpret_u8_u32(dst23)); + + // Descale with rounding. + const int16x8_t out01 = vrsraq_n_s16(dst01_s16, row01, 3); + const int16x8_t out23 = vrsraq_n_s16(dst23_s16, row23, 3); + // Add the inverse transform. + SaturateAndStore4x4_NEON(dst, out01, out23); + } +} + +//----------------------------------------------------------------------------- +// Simple In-loop filtering (Paragraph 15.2) + +static uint8x16_t NeedsFilter_NEON(const uint8x16_t p1, const uint8x16_t p0, + const uint8x16_t q0, const uint8x16_t q1, + int thresh) { + const uint8x16_t thresh_v = vdupq_n_u8((uint8_t)thresh); + const uint8x16_t a_p0_q0 = vabdq_u8(p0, q0); // abs(p0-q0) + const uint8x16_t a_p1_q1 = vabdq_u8(p1, q1); // abs(p1-q1) + const uint8x16_t a_p0_q0_2 = vqaddq_u8(a_p0_q0, a_p0_q0); // 2 * abs(p0-q0) + const uint8x16_t a_p1_q1_2 = vshrq_n_u8(a_p1_q1, 1); // abs(p1-q1) / 2 + const uint8x16_t sum = vqaddq_u8(a_p0_q0_2, a_p1_q1_2); + const uint8x16_t mask = vcgeq_u8(thresh_v, sum); + return mask; +} + +static int8x16_t FlipSign_NEON(const uint8x16_t v) { + const uint8x16_t sign_bit = vdupq_n_u8(0x80); + return vreinterpretq_s8_u8(veorq_u8(v, sign_bit)); +} + +static uint8x16_t FlipSignBack_NEON(const int8x16_t v) { + const int8x16_t sign_bit = vdupq_n_s8(0x80); + return vreinterpretq_u8_s8(veorq_s8(v, sign_bit)); +} + +static int8x16_t GetBaseDelta_NEON(const int8x16_t p1, const int8x16_t p0, + const int8x16_t q0, const int8x16_t q1) { + const int8x16_t q0_p0 = vqsubq_s8(q0, p0); // (q0-p0) + const int8x16_t p1_q1 = vqsubq_s8(p1, q1); // (p1-q1) + const int8x16_t s1 = vqaddq_s8(p1_q1, q0_p0); // (p1-q1) + 1 * (q0 - p0) + const int8x16_t s2 = vqaddq_s8(q0_p0, s1); // (p1-q1) + 2 * (q0 - p0) + const int8x16_t s3 = vqaddq_s8(q0_p0, s2); // (p1-q1) + 3 * (q0 - p0) + return s3; +} + +static int8x16_t GetBaseDelta0_NEON(const int8x16_t p0, const int8x16_t q0) { + const int8x16_t q0_p0 = vqsubq_s8(q0, p0); // (q0-p0) + const int8x16_t s1 = vqaddq_s8(q0_p0, q0_p0); // 2 * (q0 - p0) + const int8x16_t s2 = vqaddq_s8(q0_p0, s1); // 3 * (q0 - p0) + return s2; +} + +//------------------------------------------------------------------------------ + +static void ApplyFilter2NoFlip_NEON(const int8x16_t p0s, const int8x16_t q0s, + const int8x16_t delta, + int8x16_t* const op0, + int8x16_t* const oq0) { + const int8x16_t kCst3 = vdupq_n_s8(0x03); + const int8x16_t kCst4 = vdupq_n_s8(0x04); + const int8x16_t delta_p3 = vqaddq_s8(delta, kCst3); + const int8x16_t delta_p4 = vqaddq_s8(delta, kCst4); + const int8x16_t delta3 = vshrq_n_s8(delta_p3, 3); + const int8x16_t delta4 = vshrq_n_s8(delta_p4, 3); + *op0 = vqaddq_s8(p0s, delta3); + *oq0 = vqsubq_s8(q0s, delta4); +} + +#if defined(WEBP_USE_INTRINSICS) + +static void ApplyFilter2_NEON(const int8x16_t p0s, const int8x16_t q0s, + const int8x16_t delta, + uint8x16_t* const op0, uint8x16_t* const oq0) { + const int8x16_t kCst3 = vdupq_n_s8(0x03); + const int8x16_t kCst4 = vdupq_n_s8(0x04); + const int8x16_t delta_p3 = vqaddq_s8(delta, kCst3); + const int8x16_t delta_p4 = vqaddq_s8(delta, kCst4); + const int8x16_t delta3 = vshrq_n_s8(delta_p3, 3); + const int8x16_t delta4 = vshrq_n_s8(delta_p4, 3); + const int8x16_t sp0 = vqaddq_s8(p0s, delta3); + const int8x16_t sq0 = vqsubq_s8(q0s, delta4); + *op0 = FlipSignBack_NEON(sp0); + *oq0 = FlipSignBack_NEON(sq0); +} + +static void DoFilter2_NEON(const uint8x16_t p1, const uint8x16_t p0, + const uint8x16_t q0, const uint8x16_t q1, + const uint8x16_t mask, + uint8x16_t* const op0, uint8x16_t* const oq0) { + const int8x16_t p1s = FlipSign_NEON(p1); + const int8x16_t p0s = FlipSign_NEON(p0); + const int8x16_t q0s = FlipSign_NEON(q0); + const int8x16_t q1s = FlipSign_NEON(q1); + const int8x16_t delta0 = GetBaseDelta_NEON(p1s, p0s, q0s, q1s); + const int8x16_t delta1 = vandq_s8(delta0, vreinterpretq_s8_u8(mask)); + ApplyFilter2_NEON(p0s, q0s, delta1, op0, oq0); +} + +static void SimpleVFilter16_NEON(uint8_t* p, int stride, int thresh) { + uint8x16_t p1, p0, q0, q1, op0, oq0; + Load16x4_NEON(p, stride, &p1, &p0, &q0, &q1); + { + const uint8x16_t mask = NeedsFilter_NEON(p1, p0, q0, q1, thresh); + DoFilter2_NEON(p1, p0, q0, q1, mask, &op0, &oq0); + } + Store16x2_NEON(op0, oq0, p, stride); +} + +static void SimpleHFilter16_NEON(uint8_t* p, int stride, int thresh) { + uint8x16_t p1, p0, q0, q1, oq0, op0; + Load4x16_NEON(p, stride, &p1, &p0, &q0, &q1); + { + const uint8x16_t mask = NeedsFilter_NEON(p1, p0, q0, q1, thresh); + DoFilter2_NEON(p1, p0, q0, q1, mask, &op0, &oq0); + } + Store2x16_NEON(op0, oq0, p, stride); +} + +#else + +// Load/Store vertical edge +#define LOAD8x4(c1, c2, c3, c4, b1, b2, stride) \ + "vld4.8 {" #c1 "[0]," #c2 "[0]," #c3 "[0]," #c4 "[0]}," #b1 "," #stride "\n" \ + "vld4.8 {" #c1 "[1]," #c2 "[1]," #c3 "[1]," #c4 "[1]}," #b2 "," #stride "\n" \ + "vld4.8 {" #c1 "[2]," #c2 "[2]," #c3 "[2]," #c4 "[2]}," #b1 "," #stride "\n" \ + "vld4.8 {" #c1 "[3]," #c2 "[3]," #c3 "[3]," #c4 "[3]}," #b2 "," #stride "\n" \ + "vld4.8 {" #c1 "[4]," #c2 "[4]," #c3 "[4]," #c4 "[4]}," #b1 "," #stride "\n" \ + "vld4.8 {" #c1 "[5]," #c2 "[5]," #c3 "[5]," #c4 "[5]}," #b2 "," #stride "\n" \ + "vld4.8 {" #c1 "[6]," #c2 "[6]," #c3 "[6]," #c4 "[6]}," #b1 "," #stride "\n" \ + "vld4.8 {" #c1 "[7]," #c2 "[7]," #c3 "[7]," #c4 "[7]}," #b2 "," #stride "\n" + +#define STORE8x2(c1, c2, p, stride) \ + "vst2.8 {" #c1 "[0], " #c2 "[0]}," #p "," #stride " \n" \ + "vst2.8 {" #c1 "[1], " #c2 "[1]}," #p "," #stride " \n" \ + "vst2.8 {" #c1 "[2], " #c2 "[2]}," #p "," #stride " \n" \ + "vst2.8 {" #c1 "[3], " #c2 "[3]}," #p "," #stride " \n" \ + "vst2.8 {" #c1 "[4], " #c2 "[4]}," #p "," #stride " \n" \ + "vst2.8 {" #c1 "[5], " #c2 "[5]}," #p "," #stride " \n" \ + "vst2.8 {" #c1 "[6], " #c2 "[6]}," #p "," #stride " \n" \ + "vst2.8 {" #c1 "[7], " #c2 "[7]}," #p "," #stride " \n" + +#define QRegs "q0", "q1", "q2", "q3", \ + "q8", "q9", "q10", "q11", "q12", "q13", "q14", "q15" + +#define FLIP_SIGN_BIT2(a, b, s) \ + "veor " #a "," #a "," #s " \n" \ + "veor " #b "," #b "," #s " \n" \ + +#define FLIP_SIGN_BIT4(a, b, c, d, s) \ + FLIP_SIGN_BIT2(a, b, s) \ + FLIP_SIGN_BIT2(c, d, s) \ + +#define NEEDS_FILTER(p1, p0, q0, q1, thresh, mask) \ + "vabd.u8 q15," #p0 "," #q0 " \n" /* abs(p0 - q0) */ \ + "vabd.u8 q14," #p1 "," #q1 " \n" /* abs(p1 - q1) */ \ + "vqadd.u8 q15, q15, q15 \n" /* abs(p0 - q0) * 2 */ \ + "vshr.u8 q14, q14, #1 \n" /* abs(p1 - q1) / 2 */ \ + "vqadd.u8 q15, q15, q14 \n" /* abs(p0 - q0) * 2 + abs(p1 - q1) / 2 */ \ + "vdup.8 q14, " #thresh " \n" \ + "vcge.u8 " #mask ", q14, q15 \n" /* mask <= thresh */ + +#define GET_BASE_DELTA(p1, p0, q0, q1, o) \ + "vqsub.s8 q15," #q0 "," #p0 " \n" /* (q0 - p0) */ \ + "vqsub.s8 " #o "," #p1 "," #q1 " \n" /* (p1 - q1) */ \ + "vqadd.s8 " #o "," #o ", q15 \n" /* (p1 - q1) + 1 * (p0 - q0) */ \ + "vqadd.s8 " #o "," #o ", q15 \n" /* (p1 - q1) + 2 * (p0 - q0) */ \ + "vqadd.s8 " #o "," #o ", q15 \n" /* (p1 - q1) + 3 * (p0 - q0) */ + +#define DO_SIMPLE_FILTER(p0, q0, fl) \ + "vmov.i8 q15, #0x03 \n" \ + "vqadd.s8 q15, q15, " #fl " \n" /* filter1 = filter + 3 */ \ + "vshr.s8 q15, q15, #3 \n" /* filter1 >> 3 */ \ + "vqadd.s8 " #p0 "," #p0 ", q15 \n" /* p0 += filter1 */ \ + \ + "vmov.i8 q15, #0x04 \n" \ + "vqadd.s8 q15, q15, " #fl " \n" /* filter1 = filter + 4 */ \ + "vshr.s8 q15, q15, #3 \n" /* filter2 >> 3 */ \ + "vqsub.s8 " #q0 "," #q0 ", q15 \n" /* q0 -= filter2 */ + +// Applies filter on 2 pixels (p0 and q0) +#define DO_FILTER2(p1, p0, q0, q1, thresh) \ + NEEDS_FILTER(p1, p0, q0, q1, thresh, q9) /* filter mask in q9 */ \ + "vmov.i8 q10, #0x80 \n" /* sign bit */ \ + FLIP_SIGN_BIT4(p1, p0, q0, q1, q10) /* convert to signed value */ \ + GET_BASE_DELTA(p1, p0, q0, q1, q11) /* get filter level */ \ + "vand q9, q9, q11 \n" /* apply filter mask */ \ + DO_SIMPLE_FILTER(p0, q0, q9) /* apply filter */ \ + FLIP_SIGN_BIT2(p0, q0, q10) + +static void SimpleVFilter16_NEON(uint8_t* p, int stride, int thresh) { + __asm__ volatile ( + "sub %[p], %[p], %[stride], lsl #1 \n" // p -= 2 * stride + + "vld1.u8 {q1}, [%[p]], %[stride] \n" // p1 + "vld1.u8 {q2}, [%[p]], %[stride] \n" // p0 + "vld1.u8 {q3}, [%[p]], %[stride] \n" // q0 + "vld1.u8 {q12}, [%[p]] \n" // q1 + + DO_FILTER2(q1, q2, q3, q12, %[thresh]) + + "sub %[p], %[p], %[stride], lsl #1 \n" // p -= 2 * stride + + "vst1.u8 {q2}, [%[p]], %[stride] \n" // store op0 + "vst1.u8 {q3}, [%[p]] \n" // store oq0 + : [p] "+r"(p) + : [stride] "r"(stride), [thresh] "r"(thresh) + : "memory", QRegs + ); +} + +static void SimpleHFilter16_NEON(uint8_t* p, int stride, int thresh) { + __asm__ volatile ( + "sub r4, %[p], #2 \n" // base1 = p - 2 + "lsl r6, %[stride], #1 \n" // r6 = 2 * stride + "add r5, r4, %[stride] \n" // base2 = base1 + stride + + LOAD8x4(d2, d3, d4, d5, [r4], [r5], r6) + LOAD8x4(d24, d25, d26, d27, [r4], [r5], r6) + "vswp d3, d24 \n" // p1:q1 p0:q3 + "vswp d5, d26 \n" // q0:q2 q1:q4 + "vswp q2, q12 \n" // p1:q1 p0:q2 q0:q3 q1:q4 + + DO_FILTER2(q1, q2, q12, q13, %[thresh]) + + "sub %[p], %[p], #1 \n" // p - 1 + + "vswp d5, d24 \n" + STORE8x2(d4, d5, [%[p]], %[stride]) + STORE8x2(d24, d25, [%[p]], %[stride]) + + : [p] "+r"(p) + : [stride] "r"(stride), [thresh] "r"(thresh) + : "memory", "r4", "r5", "r6", QRegs + ); +} + +#undef LOAD8x4 +#undef STORE8x2 + +#endif // WEBP_USE_INTRINSICS + +static void SimpleVFilter16i_NEON(uint8_t* p, int stride, int thresh) { + uint32_t k; + for (k = 3; k != 0; --k) { + p += 4 * stride; + SimpleVFilter16_NEON(p, stride, thresh); + } +} + +static void SimpleHFilter16i_NEON(uint8_t* p, int stride, int thresh) { + uint32_t k; + for (k = 3; k != 0; --k) { + p += 4; + SimpleHFilter16_NEON(p, stride, thresh); + } +} + +//------------------------------------------------------------------------------ +// Complex In-loop filtering (Paragraph 15.3) + +static uint8x16_t NeedsHev_NEON(const uint8x16_t p1, const uint8x16_t p0, + const uint8x16_t q0, const uint8x16_t q1, + int hev_thresh) { + const uint8x16_t hev_thresh_v = vdupq_n_u8((uint8_t)hev_thresh); + const uint8x16_t a_p1_p0 = vabdq_u8(p1, p0); // abs(p1 - p0) + const uint8x16_t a_q1_q0 = vabdq_u8(q1, q0); // abs(q1 - q0) + const uint8x16_t a_max = vmaxq_u8(a_p1_p0, a_q1_q0); + const uint8x16_t mask = vcgtq_u8(a_max, hev_thresh_v); + return mask; +} + +static uint8x16_t NeedsFilter2_NEON(const uint8x16_t p3, const uint8x16_t p2, + const uint8x16_t p1, const uint8x16_t p0, + const uint8x16_t q0, const uint8x16_t q1, + const uint8x16_t q2, const uint8x16_t q3, + int ithresh, int thresh) { + const uint8x16_t ithresh_v = vdupq_n_u8((uint8_t)ithresh); + const uint8x16_t a_p3_p2 = vabdq_u8(p3, p2); // abs(p3 - p2) + const uint8x16_t a_p2_p1 = vabdq_u8(p2, p1); // abs(p2 - p1) + const uint8x16_t a_p1_p0 = vabdq_u8(p1, p0); // abs(p1 - p0) + const uint8x16_t a_q3_q2 = vabdq_u8(q3, q2); // abs(q3 - q2) + const uint8x16_t a_q2_q1 = vabdq_u8(q2, q1); // abs(q2 - q1) + const uint8x16_t a_q1_q0 = vabdq_u8(q1, q0); // abs(q1 - q0) + const uint8x16_t max1 = vmaxq_u8(a_p3_p2, a_p2_p1); + const uint8x16_t max2 = vmaxq_u8(a_p1_p0, a_q3_q2); + const uint8x16_t max3 = vmaxq_u8(a_q2_q1, a_q1_q0); + const uint8x16_t max12 = vmaxq_u8(max1, max2); + const uint8x16_t max123 = vmaxq_u8(max12, max3); + const uint8x16_t mask2 = vcgeq_u8(ithresh_v, max123); + const uint8x16_t mask1 = NeedsFilter_NEON(p1, p0, q0, q1, thresh); + const uint8x16_t mask = vandq_u8(mask1, mask2); + return mask; +} + +// 4-points filter + +static void ApplyFilter4_NEON( + const int8x16_t p1, const int8x16_t p0, + const int8x16_t q0, const int8x16_t q1, + const int8x16_t delta0, + uint8x16_t* const op1, uint8x16_t* const op0, + uint8x16_t* const oq0, uint8x16_t* const oq1) { + const int8x16_t kCst3 = vdupq_n_s8(0x03); + const int8x16_t kCst4 = vdupq_n_s8(0x04); + const int8x16_t delta1 = vqaddq_s8(delta0, kCst4); + const int8x16_t delta2 = vqaddq_s8(delta0, kCst3); + const int8x16_t a1 = vshrq_n_s8(delta1, 3); + const int8x16_t a2 = vshrq_n_s8(delta2, 3); + const int8x16_t a3 = vrshrq_n_s8(a1, 1); // a3 = (a1 + 1) >> 1 + *op0 = FlipSignBack_NEON(vqaddq_s8(p0, a2)); // clip(p0 + a2) + *oq0 = FlipSignBack_NEON(vqsubq_s8(q0, a1)); // clip(q0 - a1) + *op1 = FlipSignBack_NEON(vqaddq_s8(p1, a3)); // clip(p1 + a3) + *oq1 = FlipSignBack_NEON(vqsubq_s8(q1, a3)); // clip(q1 - a3) +} + +static void DoFilter4_NEON( + const uint8x16_t p1, const uint8x16_t p0, + const uint8x16_t q0, const uint8x16_t q1, + const uint8x16_t mask, const uint8x16_t hev_mask, + uint8x16_t* const op1, uint8x16_t* const op0, + uint8x16_t* const oq0, uint8x16_t* const oq1) { + // This is a fused version of DoFilter2() calling ApplyFilter2 directly + const int8x16_t p1s = FlipSign_NEON(p1); + int8x16_t p0s = FlipSign_NEON(p0); + int8x16_t q0s = FlipSign_NEON(q0); + const int8x16_t q1s = FlipSign_NEON(q1); + const uint8x16_t simple_lf_mask = vandq_u8(mask, hev_mask); + + // do_filter2 part (simple loopfilter on pixels with hev) + { + const int8x16_t delta = GetBaseDelta_NEON(p1s, p0s, q0s, q1s); + const int8x16_t simple_lf_delta = + vandq_s8(delta, vreinterpretq_s8_u8(simple_lf_mask)); + ApplyFilter2NoFlip_NEON(p0s, q0s, simple_lf_delta, &p0s, &q0s); + } + + // do_filter4 part (complex loopfilter on pixels without hev) + { + const int8x16_t delta0 = GetBaseDelta0_NEON(p0s, q0s); + // we use: (mask & hev_mask) ^ mask = mask & !hev_mask + const uint8x16_t complex_lf_mask = veorq_u8(simple_lf_mask, mask); + const int8x16_t complex_lf_delta = + vandq_s8(delta0, vreinterpretq_s8_u8(complex_lf_mask)); + ApplyFilter4_NEON(p1s, p0s, q0s, q1s, complex_lf_delta, op1, op0, oq0, oq1); + } +} + +// 6-points filter + +static void ApplyFilter6_NEON( + const int8x16_t p2, const int8x16_t p1, const int8x16_t p0, + const int8x16_t q0, const int8x16_t q1, const int8x16_t q2, + const int8x16_t delta, + uint8x16_t* const op2, uint8x16_t* const op1, uint8x16_t* const op0, + uint8x16_t* const oq0, uint8x16_t* const oq1, uint8x16_t* const oq2) { + // We have to compute: X = (9*a+63) >> 7, Y = (18*a+63)>>7, Z = (27*a+63) >> 7 + // Turns out, there's a common sub-expression S=9 * a - 1 that can be used + // with the special vqrshrn_n_s16 rounding-shift-and-narrow instruction: + // X = (S + 64) >> 7, Y = (S + 32) >> 6, Z = (18 * a + S + 64) >> 7 + const int8x8_t delta_lo = vget_low_s8(delta); + const int8x8_t delta_hi = vget_high_s8(delta); + const int8x8_t kCst9 = vdup_n_s8(9); + const int16x8_t kCstm1 = vdupq_n_s16(-1); + const int8x8_t kCst18 = vdup_n_s8(18); + const int16x8_t S_lo = vmlal_s8(kCstm1, kCst9, delta_lo); // S = 9 * a - 1 + const int16x8_t S_hi = vmlal_s8(kCstm1, kCst9, delta_hi); + const int16x8_t Z_lo = vmlal_s8(S_lo, kCst18, delta_lo); // S + 18 * a + const int16x8_t Z_hi = vmlal_s8(S_hi, kCst18, delta_hi); + const int8x8_t a3_lo = vqrshrn_n_s16(S_lo, 7); // (9 * a + 63) >> 7 + const int8x8_t a3_hi = vqrshrn_n_s16(S_hi, 7); + const int8x8_t a2_lo = vqrshrn_n_s16(S_lo, 6); // (9 * a + 31) >> 6 + const int8x8_t a2_hi = vqrshrn_n_s16(S_hi, 6); + const int8x8_t a1_lo = vqrshrn_n_s16(Z_lo, 7); // (27 * a + 63) >> 7 + const int8x8_t a1_hi = vqrshrn_n_s16(Z_hi, 7); + const int8x16_t a1 = vcombine_s8(a1_lo, a1_hi); + const int8x16_t a2 = vcombine_s8(a2_lo, a2_hi); + const int8x16_t a3 = vcombine_s8(a3_lo, a3_hi); + + *op0 = FlipSignBack_NEON(vqaddq_s8(p0, a1)); // clip(p0 + a1) + *oq0 = FlipSignBack_NEON(vqsubq_s8(q0, a1)); // clip(q0 - q1) + *oq1 = FlipSignBack_NEON(vqsubq_s8(q1, a2)); // clip(q1 - a2) + *op1 = FlipSignBack_NEON(vqaddq_s8(p1, a2)); // clip(p1 + a2) + *oq2 = FlipSignBack_NEON(vqsubq_s8(q2, a3)); // clip(q2 - a3) + *op2 = FlipSignBack_NEON(vqaddq_s8(p2, a3)); // clip(p2 + a3) +} + +static void DoFilter6_NEON( + const uint8x16_t p2, const uint8x16_t p1, const uint8x16_t p0, + const uint8x16_t q0, const uint8x16_t q1, const uint8x16_t q2, + const uint8x16_t mask, const uint8x16_t hev_mask, + uint8x16_t* const op2, uint8x16_t* const op1, uint8x16_t* const op0, + uint8x16_t* const oq0, uint8x16_t* const oq1, uint8x16_t* const oq2) { + // This is a fused version of DoFilter2() calling ApplyFilter2 directly + const int8x16_t p2s = FlipSign_NEON(p2); + const int8x16_t p1s = FlipSign_NEON(p1); + int8x16_t p0s = FlipSign_NEON(p0); + int8x16_t q0s = FlipSign_NEON(q0); + const int8x16_t q1s = FlipSign_NEON(q1); + const int8x16_t q2s = FlipSign_NEON(q2); + const uint8x16_t simple_lf_mask = vandq_u8(mask, hev_mask); + const int8x16_t delta0 = GetBaseDelta_NEON(p1s, p0s, q0s, q1s); + + // do_filter2 part (simple loopfilter on pixels with hev) + { + const int8x16_t simple_lf_delta = + vandq_s8(delta0, vreinterpretq_s8_u8(simple_lf_mask)); + ApplyFilter2NoFlip_NEON(p0s, q0s, simple_lf_delta, &p0s, &q0s); + } + + // do_filter6 part (complex loopfilter on pixels without hev) + { + // we use: (mask & hev_mask) ^ mask = mask & !hev_mask + const uint8x16_t complex_lf_mask = veorq_u8(simple_lf_mask, mask); + const int8x16_t complex_lf_delta = + vandq_s8(delta0, vreinterpretq_s8_u8(complex_lf_mask)); + ApplyFilter6_NEON(p2s, p1s, p0s, q0s, q1s, q2s, complex_lf_delta, + op2, op1, op0, oq0, oq1, oq2); + } +} + +// on macroblock edges + +static void VFilter16_NEON(uint8_t* p, int stride, + int thresh, int ithresh, int hev_thresh) { + uint8x16_t p3, p2, p1, p0, q0, q1, q2, q3; + Load16x8_NEON(p, stride, &p3, &p2, &p1, &p0, &q0, &q1, &q2, &q3); + { + const uint8x16_t mask = NeedsFilter2_NEON(p3, p2, p1, p0, q0, q1, q2, q3, + ithresh, thresh); + const uint8x16_t hev_mask = NeedsHev_NEON(p1, p0, q0, q1, hev_thresh); + uint8x16_t op2, op1, op0, oq0, oq1, oq2; + DoFilter6_NEON(p2, p1, p0, q0, q1, q2, mask, hev_mask, + &op2, &op1, &op0, &oq0, &oq1, &oq2); + Store16x2_NEON(op2, op1, p - 2 * stride, stride); + Store16x2_NEON(op0, oq0, p + 0 * stride, stride); + Store16x2_NEON(oq1, oq2, p + 2 * stride, stride); + } +} + +static void HFilter16_NEON(uint8_t* p, int stride, + int thresh, int ithresh, int hev_thresh) { + uint8x16_t p3, p2, p1, p0, q0, q1, q2, q3; + Load8x16_NEON(p, stride, &p3, &p2, &p1, &p0, &q0, &q1, &q2, &q3); + { + const uint8x16_t mask = NeedsFilter2_NEON(p3, p2, p1, p0, q0, q1, q2, q3, + ithresh, thresh); + const uint8x16_t hev_mask = NeedsHev_NEON(p1, p0, q0, q1, hev_thresh); + uint8x16_t op2, op1, op0, oq0, oq1, oq2; + DoFilter6_NEON(p2, p1, p0, q0, q1, q2, mask, hev_mask, + &op2, &op1, &op0, &oq0, &oq1, &oq2); + Store2x16_NEON(op2, op1, p - 2, stride); + Store2x16_NEON(op0, oq0, p + 0, stride); + Store2x16_NEON(oq1, oq2, p + 2, stride); + } +} + +// on three inner edges +static void VFilter16i_NEON(uint8_t* p, int stride, + int thresh, int ithresh, int hev_thresh) { + uint32_t k; + uint8x16_t p3, p2, p1, p0; + Load16x4_NEON(p + 2 * stride, stride, &p3, &p2, &p1, &p0); + for (k = 3; k != 0; --k) { + uint8x16_t q0, q1, q2, q3; + p += 4 * stride; + Load16x4_NEON(p + 2 * stride, stride, &q0, &q1, &q2, &q3); + { + const uint8x16_t mask = + NeedsFilter2_NEON(p3, p2, p1, p0, q0, q1, q2, q3, ithresh, thresh); + const uint8x16_t hev_mask = NeedsHev_NEON(p1, p0, q0, q1, hev_thresh); + // p3 and p2 are not just temporary variables here: they will be + // re-used for next span. And q2/q3 will become p1/p0 accordingly. + DoFilter4_NEON(p1, p0, q0, q1, mask, hev_mask, &p1, &p0, &p3, &p2); + Store16x4_NEON(p1, p0, p3, p2, p, stride); + p1 = q2; + p0 = q3; + } + } +} + +#if !defined(WORK_AROUND_GCC) +static void HFilter16i_NEON(uint8_t* p, int stride, + int thresh, int ithresh, int hev_thresh) { + uint32_t k; + uint8x16_t p3, p2, p1, p0; + Load4x16_NEON(p + 2, stride, &p3, &p2, &p1, &p0); + for (k = 3; k != 0; --k) { + uint8x16_t q0, q1, q2, q3; + p += 4; + Load4x16_NEON(p + 2, stride, &q0, &q1, &q2, &q3); + { + const uint8x16_t mask = + NeedsFilter2_NEON(p3, p2, p1, p0, q0, q1, q2, q3, ithresh, thresh); + const uint8x16_t hev_mask = NeedsHev_NEON(p1, p0, q0, q1, hev_thresh); + DoFilter4_NEON(p1, p0, q0, q1, mask, hev_mask, &p1, &p0, &p3, &p2); + Store4x16_NEON(p1, p0, p3, p2, p, stride); + p1 = q2; + p0 = q3; + } + } +} +#endif // !WORK_AROUND_GCC + +// 8-pixels wide variant, for chroma filtering +static void VFilter8_NEON(uint8_t* WEBP_RESTRICT u, uint8_t* WEBP_RESTRICT v, + int stride, int thresh, int ithresh, int hev_thresh) { + uint8x16_t p3, p2, p1, p0, q0, q1, q2, q3; + Load8x8x2_NEON(u, v, stride, &p3, &p2, &p1, &p0, &q0, &q1, &q2, &q3); + { + const uint8x16_t mask = NeedsFilter2_NEON(p3, p2, p1, p0, q0, q1, q2, q3, + ithresh, thresh); + const uint8x16_t hev_mask = NeedsHev_NEON(p1, p0, q0, q1, hev_thresh); + uint8x16_t op2, op1, op0, oq0, oq1, oq2; + DoFilter6_NEON(p2, p1, p0, q0, q1, q2, mask, hev_mask, + &op2, &op1, &op0, &oq0, &oq1, &oq2); + Store8x2x2_NEON(op2, op1, u - 2 * stride, v - 2 * stride, stride); + Store8x2x2_NEON(op0, oq0, u + 0 * stride, v + 0 * stride, stride); + Store8x2x2_NEON(oq1, oq2, u + 2 * stride, v + 2 * stride, stride); + } +} +static void VFilter8i_NEON(uint8_t* WEBP_RESTRICT u, uint8_t* WEBP_RESTRICT v, + int stride, + int thresh, int ithresh, int hev_thresh) { + uint8x16_t p3, p2, p1, p0, q0, q1, q2, q3; + u += 4 * stride; + v += 4 * stride; + Load8x8x2_NEON(u, v, stride, &p3, &p2, &p1, &p0, &q0, &q1, &q2, &q3); + { + const uint8x16_t mask = NeedsFilter2_NEON(p3, p2, p1, p0, q0, q1, q2, q3, + ithresh, thresh); + const uint8x16_t hev_mask = NeedsHev_NEON(p1, p0, q0, q1, hev_thresh); + uint8x16_t op1, op0, oq0, oq1; + DoFilter4_NEON(p1, p0, q0, q1, mask, hev_mask, &op1, &op0, &oq0, &oq1); + Store8x4x2_NEON(op1, op0, oq0, oq1, u, v, stride); + } +} + +#if !defined(WORK_AROUND_GCC) +static void HFilter8_NEON(uint8_t* WEBP_RESTRICT u, uint8_t* WEBP_RESTRICT v, + int stride, int thresh, int ithresh, int hev_thresh) { + uint8x16_t p3, p2, p1, p0, q0, q1, q2, q3; + Load8x8x2T_NEON(u, v, stride, &p3, &p2, &p1, &p0, &q0, &q1, &q2, &q3); + { + const uint8x16_t mask = NeedsFilter2_NEON(p3, p2, p1, p0, q0, q1, q2, q3, + ithresh, thresh); + const uint8x16_t hev_mask = NeedsHev_NEON(p1, p0, q0, q1, hev_thresh); + uint8x16_t op2, op1, op0, oq0, oq1, oq2; + DoFilter6_NEON(p2, p1, p0, q0, q1, q2, mask, hev_mask, + &op2, &op1, &op0, &oq0, &oq1, &oq2); + Store6x8x2_NEON(op2, op1, op0, oq0, oq1, oq2, u, v, stride); + } +} + +static void HFilter8i_NEON(uint8_t* WEBP_RESTRICT u, uint8_t* WEBP_RESTRICT v, + int stride, + int thresh, int ithresh, int hev_thresh) { + uint8x16_t p3, p2, p1, p0, q0, q1, q2, q3; + u += 4; + v += 4; + Load8x8x2T_NEON(u, v, stride, &p3, &p2, &p1, &p0, &q0, &q1, &q2, &q3); + { + const uint8x16_t mask = NeedsFilter2_NEON(p3, p2, p1, p0, q0, q1, q2, q3, + ithresh, thresh); + const uint8x16_t hev_mask = NeedsHev_NEON(p1, p0, q0, q1, hev_thresh); + uint8x16_t op1, op0, oq0, oq1; + DoFilter4_NEON(p1, p0, q0, q1, mask, hev_mask, &op1, &op0, &oq0, &oq1); + Store4x8x2_NEON(op1, op0, oq0, oq1, u, v, stride); + } +} +#endif // !WORK_AROUND_GCC + +//----------------------------------------------------------------------------- +// Inverse transforms (Paragraph 14.4) + +// Technically these are unsigned but vqdmulh is only available in signed. +// vqdmulh returns high half (effectively >> 16) but also doubles the value, +// changing the >> 16 to >> 15 and requiring an additional >> 1. +// We use this to our advantage with kC2. The canonical value is 35468. +// However, the high bit is set so treating it as signed will give incorrect +// results. We avoid this by down shifting by 1 here to clear the highest bit. +// Combined with the doubling effect of vqdmulh we get >> 16. +// This can not be applied to kC1 because the lowest bit is set. Down shifting +// the constant would reduce precision. + +// libwebp uses a trick to avoid some extra addition that libvpx does. +// Instead of: +// temp2 = ip[12] + ((ip[12] * cospi8sqrt2minus1) >> 16); +// libwebp adds 1 << 16 to cospi8sqrt2minus1 (kC1). However, this causes the +// same issue with kC1 and vqdmulh that we work around by down shifting kC2 + +static const int16_t kC1 = WEBP_TRANSFORM_AC3_C1; +static const int16_t kC2 = + WEBP_TRANSFORM_AC3_C2 / 2; // half of kC2, actually. See comment above. + +#if defined(WEBP_USE_INTRINSICS) +static WEBP_INLINE void Transpose8x2_NEON(const int16x8_t in0, + const int16x8_t in1, + int16x8x2_t* const out) { + // a0 a1 a2 a3 | b0 b1 b2 b3 => a0 b0 c0 d0 | a1 b1 c1 d1 + // c0 c1 c2 c3 | d0 d1 d2 d3 a2 b2 c2 d2 | a3 b3 c3 d3 + const int16x8x2_t tmp0 = vzipq_s16(in0, in1); // a0 c0 a1 c1 a2 c2 ... + // b0 d0 b1 d1 b2 d2 ... + *out = vzipq_s16(tmp0.val[0], tmp0.val[1]); +} + +static WEBP_INLINE void TransformPass_NEON(int16x8x2_t* const rows) { + // {rows} = in0 | in4 + // in8 | in12 + // B1 = in4 | in12 + const int16x8_t B1 = + vcombine_s16(vget_high_s16(rows->val[0]), vget_high_s16(rows->val[1])); + // C0 = kC1 * in4 | kC1 * in12 + // C1 = kC2 * in4 | kC2 * in12 + const int16x8_t C0 = vsraq_n_s16(B1, vqdmulhq_n_s16(B1, kC1), 1); + const int16x8_t C1 = vqdmulhq_n_s16(B1, kC2); + const int16x4_t a = vqadd_s16(vget_low_s16(rows->val[0]), + vget_low_s16(rows->val[1])); // in0 + in8 + const int16x4_t b = vqsub_s16(vget_low_s16(rows->val[0]), + vget_low_s16(rows->val[1])); // in0 - in8 + // c = kC2 * in4 - kC1 * in12 + // d = kC1 * in4 + kC2 * in12 + const int16x4_t c = vqsub_s16(vget_low_s16(C1), vget_high_s16(C0)); + const int16x4_t d = vqadd_s16(vget_low_s16(C0), vget_high_s16(C1)); + const int16x8_t D0 = vcombine_s16(a, b); // D0 = a | b + const int16x8_t D1 = vcombine_s16(d, c); // D1 = d | c + const int16x8_t E0 = vqaddq_s16(D0, D1); // a+d | b+c + const int16x8_t E_tmp = vqsubq_s16(D0, D1); // a-d | b-c + const int16x8_t E1 = vcombine_s16(vget_high_s16(E_tmp), vget_low_s16(E_tmp)); + Transpose8x2_NEON(E0, E1, rows); +} + +static void TransformOne_NEON(const int16_t* WEBP_RESTRICT in, + uint8_t* WEBP_RESTRICT dst) { + int16x8x2_t rows; + INIT_VECTOR2(rows, vld1q_s16(in + 0), vld1q_s16(in + 8)); + TransformPass_NEON(&rows); + TransformPass_NEON(&rows); + Add4x4_NEON(rows.val[0], rows.val[1], dst); +} + +#else + +static void TransformOne_NEON(const int16_t* WEBP_RESTRICT in, + uint8_t* WEBP_RESTRICT dst) { + const int kBPS = BPS; + // kC1, kC2. Padded because vld1.16 loads 8 bytes + const int16_t constants[4] = { kC1, kC2, 0, 0 }; + /* Adapted from libvpx: vp8/common/arm/neon/shortidct4x4llm_neon.asm */ + __asm__ volatile ( + "vld1.16 {q1, q2}, [%[in]] \n" + "vld1.16 {d0}, [%[constants]] \n" + + /* d2: in[0] + * d3: in[8] + * d4: in[4] + * d5: in[12] + */ + "vswp d3, d4 \n" + + /* q8 = {in[4], in[12]} * kC1 * 2 >> 16 + * q9 = {in[4], in[12]} * kC2 >> 16 + */ + "vqdmulh.s16 q8, q2, d0[0] \n" + "vqdmulh.s16 q9, q2, d0[1] \n" + + /* d22 = a = in[0] + in[8] + * d23 = b = in[0] - in[8] + */ + "vqadd.s16 d22, d2, d3 \n" + "vqsub.s16 d23, d2, d3 \n" + + /* The multiplication should be x * kC1 >> 16 + * However, with vqdmulh we get x * kC1 * 2 >> 16 + * (multiply, double, return high half) + * We avoided this in kC2 by pre-shifting the constant. + * q8 = in[4]/[12] * kC1 >> 16 + */ + "vshr.s16 q8, q8, #1 \n" + + /* Add {in[4], in[12]} back after the multiplication. This is handled by + * adding 1 << 16 to kC1 in the libwebp C code. + */ + "vqadd.s16 q8, q2, q8 \n" + + /* d20 = c = in[4]*kC2 - in[12]*kC1 + * d21 = d = in[4]*kC1 + in[12]*kC2 + */ + "vqsub.s16 d20, d18, d17 \n" + "vqadd.s16 d21, d19, d16 \n" + + /* d2 = tmp[0] = a + d + * d3 = tmp[1] = b + c + * d4 = tmp[2] = b - c + * d5 = tmp[3] = a - d + */ + "vqadd.s16 d2, d22, d21 \n" + "vqadd.s16 d3, d23, d20 \n" + "vqsub.s16 d4, d23, d20 \n" + "vqsub.s16 d5, d22, d21 \n" + + "vzip.16 q1, q2 \n" + "vzip.16 q1, q2 \n" + + "vswp d3, d4 \n" + + /* q8 = {tmp[4], tmp[12]} * kC1 * 2 >> 16 + * q9 = {tmp[4], tmp[12]} * kC2 >> 16 + */ + "vqdmulh.s16 q8, q2, d0[0] \n" + "vqdmulh.s16 q9, q2, d0[1] \n" + + /* d22 = a = tmp[0] + tmp[8] + * d23 = b = tmp[0] - tmp[8] + */ + "vqadd.s16 d22, d2, d3 \n" + "vqsub.s16 d23, d2, d3 \n" + + /* See long winded explanations prior */ + "vshr.s16 q8, q8, #1 \n" + "vqadd.s16 q8, q2, q8 \n" + + /* d20 = c = in[4]*kC2 - in[12]*kC1 + * d21 = d = in[4]*kC1 + in[12]*kC2 + */ + "vqsub.s16 d20, d18, d17 \n" + "vqadd.s16 d21, d19, d16 \n" + + /* d2 = tmp[0] = a + d + * d3 = tmp[1] = b + c + * d4 = tmp[2] = b - c + * d5 = tmp[3] = a - d + */ + "vqadd.s16 d2, d22, d21 \n" + "vqadd.s16 d3, d23, d20 \n" + "vqsub.s16 d4, d23, d20 \n" + "vqsub.s16 d5, d22, d21 \n" + + "vld1.32 d6[0], [%[dst]], %[kBPS] \n" + "vld1.32 d6[1], [%[dst]], %[kBPS] \n" + "vld1.32 d7[0], [%[dst]], %[kBPS] \n" + "vld1.32 d7[1], [%[dst]], %[kBPS] \n" + + "sub %[dst], %[dst], %[kBPS], lsl #2 \n" + + /* (val) + 4 >> 3 */ + "vrshr.s16 d2, d2, #3 \n" + "vrshr.s16 d3, d3, #3 \n" + "vrshr.s16 d4, d4, #3 \n" + "vrshr.s16 d5, d5, #3 \n" + + "vzip.16 q1, q2 \n" + "vzip.16 q1, q2 \n" + + /* Must accumulate before saturating */ + "vmovl.u8 q8, d6 \n" + "vmovl.u8 q9, d7 \n" + + "vqadd.s16 q1, q1, q8 \n" + "vqadd.s16 q2, q2, q9 \n" + + "vqmovun.s16 d0, q1 \n" + "vqmovun.s16 d1, q2 \n" + + "vst1.32 d0[0], [%[dst]], %[kBPS] \n" + "vst1.32 d0[1], [%[dst]], %[kBPS] \n" + "vst1.32 d1[0], [%[dst]], %[kBPS] \n" + "vst1.32 d1[1], [%[dst]] \n" + + : [in] "+r"(in), [dst] "+r"(dst) /* modified registers */ + : [kBPS] "r"(kBPS), [constants] "r"(constants) /* constants */ + : "memory", "q0", "q1", "q2", "q8", "q9", "q10", "q11" /* clobbered */ + ); +} + +#endif // WEBP_USE_INTRINSICS + +static void TransformTwo_NEON(const int16_t* WEBP_RESTRICT in, + uint8_t* WEBP_RESTRICT dst, int do_two) { + TransformOne_NEON(in, dst); + if (do_two) { + TransformOne_NEON(in + 16, dst + 4); + } +} + +static void TransformDC_NEON(const int16_t* WEBP_RESTRICT in, + uint8_t* WEBP_RESTRICT dst) { + const int16x8_t DC = vdupq_n_s16(in[0]); + Add4x4_NEON(DC, DC, dst); +} + +//------------------------------------------------------------------------------ + +#define STORE_WHT(dst, col, rows) do { \ + *dst = vgetq_lane_s32(rows.val[0], col); (dst) += 16; \ + *dst = vgetq_lane_s32(rows.val[1], col); (dst) += 16; \ + *dst = vgetq_lane_s32(rows.val[2], col); (dst) += 16; \ + *dst = vgetq_lane_s32(rows.val[3], col); (dst) += 16; \ +} while (0) + +static void TransformWHT_NEON(const int16_t* WEBP_RESTRICT in, + int16_t* WEBP_RESTRICT out) { + int32x4x4_t tmp; + + { + // Load the source. + const int16x4_t in00_03 = vld1_s16(in + 0); + const int16x4_t in04_07 = vld1_s16(in + 4); + const int16x4_t in08_11 = vld1_s16(in + 8); + const int16x4_t in12_15 = vld1_s16(in + 12); + const int32x4_t a0 = vaddl_s16(in00_03, in12_15); // in[0..3] + in[12..15] + const int32x4_t a1 = vaddl_s16(in04_07, in08_11); // in[4..7] + in[8..11] + const int32x4_t a2 = vsubl_s16(in04_07, in08_11); // in[4..7] - in[8..11] + const int32x4_t a3 = vsubl_s16(in00_03, in12_15); // in[0..3] - in[12..15] + tmp.val[0] = vaddq_s32(a0, a1); + tmp.val[1] = vaddq_s32(a3, a2); + tmp.val[2] = vsubq_s32(a0, a1); + tmp.val[3] = vsubq_s32(a3, a2); + // Arrange the temporary results column-wise. + tmp = Transpose4x4_NEON(tmp); + } + + { + const int32x4_t kCst3 = vdupq_n_s32(3); + const int32x4_t dc = vaddq_s32(tmp.val[0], kCst3); // add rounder + const int32x4_t a0 = vaddq_s32(dc, tmp.val[3]); + const int32x4_t a1 = vaddq_s32(tmp.val[1], tmp.val[2]); + const int32x4_t a2 = vsubq_s32(tmp.val[1], tmp.val[2]); + const int32x4_t a3 = vsubq_s32(dc, tmp.val[3]); + + tmp.val[0] = vaddq_s32(a0, a1); + tmp.val[1] = vaddq_s32(a3, a2); + tmp.val[2] = vsubq_s32(a0, a1); + tmp.val[3] = vsubq_s32(a3, a2); + + // right shift the results by 3. + tmp.val[0] = vshrq_n_s32(tmp.val[0], 3); + tmp.val[1] = vshrq_n_s32(tmp.val[1], 3); + tmp.val[2] = vshrq_n_s32(tmp.val[2], 3); + tmp.val[3] = vshrq_n_s32(tmp.val[3], 3); + + STORE_WHT(out, 0, tmp); + STORE_WHT(out, 1, tmp); + STORE_WHT(out, 2, tmp); + STORE_WHT(out, 3, tmp); + } +} + +#undef STORE_WHT + +//------------------------------------------------------------------------------ + +static void TransformAC3_NEON(const int16_t* WEBP_RESTRICT in, + uint8_t* WEBP_RESTRICT dst) { + const int16x4_t A = vld1_dup_s16(in); + const int16x4_t c4 = vdup_n_s16(WEBP_TRANSFORM_AC3_MUL2(in[4])); + const int16x4_t d4 = vdup_n_s16(WEBP_TRANSFORM_AC3_MUL1(in[4])); + const int c1 = WEBP_TRANSFORM_AC3_MUL2(in[1]); + const int d1 = WEBP_TRANSFORM_AC3_MUL1(in[1]); + const uint64_t cd = (uint64_t)( d1 & 0xffff) << 0 | + (uint64_t)( c1 & 0xffff) << 16 | + (uint64_t)(-c1 & 0xffff) << 32 | + (uint64_t)(-d1 & 0xffff) << 48; + const int16x4_t CD = vcreate_s16(cd); + const int16x4_t B = vqadd_s16(A, CD); + const int16x8_t m0_m1 = vcombine_s16(vqadd_s16(B, d4), vqadd_s16(B, c4)); + const int16x8_t m2_m3 = vcombine_s16(vqsub_s16(B, c4), vqsub_s16(B, d4)); + Add4x4_NEON(m0_m1, m2_m3, dst); +} + +//------------------------------------------------------------------------------ +// 4x4 + +static void DC4_NEON(uint8_t* dst) { // DC + const uint8x8_t A = vld1_u8(dst - BPS); // top row + const uint16x4_t p0 = vpaddl_u8(A); // cascading summation of the top + const uint16x4_t p1 = vpadd_u16(p0, p0); + const uint8x8_t L0 = vld1_u8(dst + 0 * BPS - 1); + const uint8x8_t L1 = vld1_u8(dst + 1 * BPS - 1); + const uint8x8_t L2 = vld1_u8(dst + 2 * BPS - 1); + const uint8x8_t L3 = vld1_u8(dst + 3 * BPS - 1); + const uint16x8_t s0 = vaddl_u8(L0, L1); + const uint16x8_t s1 = vaddl_u8(L2, L3); + const uint16x8_t s01 = vaddq_u16(s0, s1); + const uint16x8_t sum = vaddq_u16(s01, vcombine_u16(p1, p1)); + const uint8x8_t dc0 = vrshrn_n_u16(sum, 3); // (sum + 4) >> 3 + const uint8x8_t dc = vdup_lane_u8(dc0, 0); + int i; + for (i = 0; i < 4; ++i) { + vst1_lane_u32((uint32_t*)(dst + i * BPS), vreinterpret_u32_u8(dc), 0); + } +} + +// TrueMotion (4x4 + 8x8) +static WEBP_INLINE void TrueMotion_NEON(uint8_t* dst, int size) { + const uint8x8_t TL = vld1_dup_u8(dst - BPS - 1); // top-left pixel 'A[-1]' + const uint8x8_t T = vld1_u8(dst - BPS); // top row 'A[0..3]' + const uint16x8_t d = vsubl_u8(T, TL); // A[c] - A[-1] + int y; + for (y = 0; y < size; y += 4) { + // left edge + const uint8x8_t L0 = vld1_dup_u8(dst + 0 * BPS - 1); + const uint8x8_t L1 = vld1_dup_u8(dst + 1 * BPS - 1); + const uint8x8_t L2 = vld1_dup_u8(dst + 2 * BPS - 1); + const uint8x8_t L3 = vld1_dup_u8(dst + 3 * BPS - 1); + // L[r] + A[c] - A[-1] + const int16x8_t r0 = vreinterpretq_s16_u16(vaddw_u8(d, L0)); + const int16x8_t r1 = vreinterpretq_s16_u16(vaddw_u8(d, L1)); + const int16x8_t r2 = vreinterpretq_s16_u16(vaddw_u8(d, L2)); + const int16x8_t r3 = vreinterpretq_s16_u16(vaddw_u8(d, L3)); + // Saturate and store the result. + const uint32x2_t r0_u32 = vreinterpret_u32_u8(vqmovun_s16(r0)); + const uint32x2_t r1_u32 = vreinterpret_u32_u8(vqmovun_s16(r1)); + const uint32x2_t r2_u32 = vreinterpret_u32_u8(vqmovun_s16(r2)); + const uint32x2_t r3_u32 = vreinterpret_u32_u8(vqmovun_s16(r3)); + if (size == 4) { + vst1_lane_u32((uint32_t*)(dst + 0 * BPS), r0_u32, 0); + vst1_lane_u32((uint32_t*)(dst + 1 * BPS), r1_u32, 0); + vst1_lane_u32((uint32_t*)(dst + 2 * BPS), r2_u32, 0); + vst1_lane_u32((uint32_t*)(dst + 3 * BPS), r3_u32, 0); + } else { + vst1_u32((uint32_t*)(dst + 0 * BPS), r0_u32); + vst1_u32((uint32_t*)(dst + 1 * BPS), r1_u32); + vst1_u32((uint32_t*)(dst + 2 * BPS), r2_u32); + vst1_u32((uint32_t*)(dst + 3 * BPS), r3_u32); + } + dst += 4 * BPS; + } +} + +static void TM4_NEON(uint8_t* dst) { TrueMotion_NEON(dst, 4); } + +static void VE4_NEON(uint8_t* dst) { // vertical + // NB: avoid vld1_u64 here as an alignment hint may be added -> SIGBUS. + const uint64x1_t A0 = vreinterpret_u64_u8(vld1_u8(dst - BPS - 1)); // top row + const uint64x1_t A1 = vshr_n_u64(A0, 8); + const uint64x1_t A2 = vshr_n_u64(A0, 16); + const uint8x8_t ABCDEFGH = vreinterpret_u8_u64(A0); + const uint8x8_t BCDEFGH0 = vreinterpret_u8_u64(A1); + const uint8x8_t CDEFGH00 = vreinterpret_u8_u64(A2); + const uint8x8_t b = vhadd_u8(ABCDEFGH, CDEFGH00); + const uint8x8_t avg = vrhadd_u8(b, BCDEFGH0); + int i; + for (i = 0; i < 4; ++i) { + vst1_lane_u32((uint32_t*)(dst + i * BPS), vreinterpret_u32_u8(avg), 0); + } +} + +static void RD4_NEON(uint8_t* dst) { // Down-right + const uint8x8_t XABCD_u8 = vld1_u8(dst - BPS - 1); + const uint64x1_t XABCD = vreinterpret_u64_u8(XABCD_u8); + const uint64x1_t ____XABC = vshl_n_u64(XABCD, 32); + const uint32_t I = dst[-1 + 0 * BPS]; + const uint32_t J = dst[-1 + 1 * BPS]; + const uint32_t K = dst[-1 + 2 * BPS]; + const uint32_t L = dst[-1 + 3 * BPS]; + const uint64x1_t LKJI____ = + vcreate_u64((uint64_t)L | (K << 8) | (J << 16) | (I << 24)); + const uint64x1_t LKJIXABC = vorr_u64(LKJI____, ____XABC); + const uint8x8_t KJIXABC_ = vreinterpret_u8_u64(vshr_n_u64(LKJIXABC, 8)); + const uint8x8_t JIXABC__ = vreinterpret_u8_u64(vshr_n_u64(LKJIXABC, 16)); + const uint8_t D = vget_lane_u8(XABCD_u8, 4); + const uint8x8_t JIXABCD_ = vset_lane_u8(D, JIXABC__, 6); + const uint8x8_t LKJIXABC_u8 = vreinterpret_u8_u64(LKJIXABC); + const uint8x8_t avg1 = vhadd_u8(JIXABCD_, LKJIXABC_u8); + const uint8x8_t avg2 = vrhadd_u8(avg1, KJIXABC_); + const uint64x1_t avg2_u64 = vreinterpret_u64_u8(avg2); + const uint32x2_t r3 = vreinterpret_u32_u8(avg2); + const uint32x2_t r2 = vreinterpret_u32_u64(vshr_n_u64(avg2_u64, 8)); + const uint32x2_t r1 = vreinterpret_u32_u64(vshr_n_u64(avg2_u64, 16)); + const uint32x2_t r0 = vreinterpret_u32_u64(vshr_n_u64(avg2_u64, 24)); + vst1_lane_u32((uint32_t*)(dst + 0 * BPS), r0, 0); + vst1_lane_u32((uint32_t*)(dst + 1 * BPS), r1, 0); + vst1_lane_u32((uint32_t*)(dst + 2 * BPS), r2, 0); + vst1_lane_u32((uint32_t*)(dst + 3 * BPS), r3, 0); +} + +static void LD4_NEON(uint8_t* dst) { // Down-left + // Note using the same shift trick as VE4() is slower here. + const uint8x8_t ABCDEFGH = vld1_u8(dst - BPS + 0); + const uint8x8_t BCDEFGH0 = vld1_u8(dst - BPS + 1); + const uint8x8_t CDEFGH00 = vld1_u8(dst - BPS + 2); + const uint8x8_t CDEFGHH0 = vset_lane_u8(dst[-BPS + 7], CDEFGH00, 6); + const uint8x8_t avg1 = vhadd_u8(ABCDEFGH, CDEFGHH0); + const uint8x8_t avg2 = vrhadd_u8(avg1, BCDEFGH0); + const uint64x1_t avg2_u64 = vreinterpret_u64_u8(avg2); + const uint32x2_t r0 = vreinterpret_u32_u8(avg2); + const uint32x2_t r1 = vreinterpret_u32_u64(vshr_n_u64(avg2_u64, 8)); + const uint32x2_t r2 = vreinterpret_u32_u64(vshr_n_u64(avg2_u64, 16)); + const uint32x2_t r3 = vreinterpret_u32_u64(vshr_n_u64(avg2_u64, 24)); + vst1_lane_u32((uint32_t*)(dst + 0 * BPS), r0, 0); + vst1_lane_u32((uint32_t*)(dst + 1 * BPS), r1, 0); + vst1_lane_u32((uint32_t*)(dst + 2 * BPS), r2, 0); + vst1_lane_u32((uint32_t*)(dst + 3 * BPS), r3, 0); +} + +//------------------------------------------------------------------------------ +// Chroma + +static void VE8uv_NEON(uint8_t* dst) { // vertical + const uint8x8_t top = vld1_u8(dst - BPS); + int j; + for (j = 0; j < 8; ++j) { + vst1_u8(dst + j * BPS, top); + } +} + +static void HE8uv_NEON(uint8_t* dst) { // horizontal + int j; + for (j = 0; j < 8; ++j) { + const uint8x8_t left = vld1_dup_u8(dst - 1); + vst1_u8(dst, left); + dst += BPS; + } +} + +static WEBP_INLINE void DC8_NEON(uint8_t* dst, int do_top, int do_left) { + uint16x8_t sum_top; + uint16x8_t sum_left; + uint8x8_t dc0; + + if (do_top) { + const uint8x8_t A = vld1_u8(dst - BPS); // top row +#if WEBP_AARCH64 + const uint16_t p2 = vaddlv_u8(A); + sum_top = vdupq_n_u16(p2); +#else + const uint16x4_t p0 = vpaddl_u8(A); // cascading summation of the top + const uint16x4_t p1 = vpadd_u16(p0, p0); + const uint16x4_t p2 = vpadd_u16(p1, p1); + sum_top = vcombine_u16(p2, p2); +#endif + } + + if (do_left) { + const uint8x8_t L0 = vld1_u8(dst + 0 * BPS - 1); + const uint8x8_t L1 = vld1_u8(dst + 1 * BPS - 1); + const uint8x8_t L2 = vld1_u8(dst + 2 * BPS - 1); + const uint8x8_t L3 = vld1_u8(dst + 3 * BPS - 1); + const uint8x8_t L4 = vld1_u8(dst + 4 * BPS - 1); + const uint8x8_t L5 = vld1_u8(dst + 5 * BPS - 1); + const uint8x8_t L6 = vld1_u8(dst + 6 * BPS - 1); + const uint8x8_t L7 = vld1_u8(dst + 7 * BPS - 1); + const uint16x8_t s0 = vaddl_u8(L0, L1); + const uint16x8_t s1 = vaddl_u8(L2, L3); + const uint16x8_t s2 = vaddl_u8(L4, L5); + const uint16x8_t s3 = vaddl_u8(L6, L7); + const uint16x8_t s01 = vaddq_u16(s0, s1); + const uint16x8_t s23 = vaddq_u16(s2, s3); + sum_left = vaddq_u16(s01, s23); + } + + if (do_top && do_left) { + const uint16x8_t sum = vaddq_u16(sum_left, sum_top); + dc0 = vrshrn_n_u16(sum, 4); + } else if (do_top) { + dc0 = vrshrn_n_u16(sum_top, 3); + } else if (do_left) { + dc0 = vrshrn_n_u16(sum_left, 3); + } else { + dc0 = vdup_n_u8(0x80); + } + + { + const uint8x8_t dc = vdup_lane_u8(dc0, 0); + int i; + for (i = 0; i < 8; ++i) { + vst1_u32((uint32_t*)(dst + i * BPS), vreinterpret_u32_u8(dc)); + } + } +} + +static void DC8uv_NEON(uint8_t* dst) { DC8_NEON(dst, 1, 1); } +static void DC8uvNoTop_NEON(uint8_t* dst) { DC8_NEON(dst, 0, 1); } +static void DC8uvNoLeft_NEON(uint8_t* dst) { DC8_NEON(dst, 1, 0); } +static void DC8uvNoTopLeft_NEON(uint8_t* dst) { DC8_NEON(dst, 0, 0); } + +static void TM8uv_NEON(uint8_t* dst) { TrueMotion_NEON(dst, 8); } + +//------------------------------------------------------------------------------ +// 16x16 + +static void VE16_NEON(uint8_t* dst) { // vertical + const uint8x16_t top = vld1q_u8(dst - BPS); + int j; + for (j = 0; j < 16; ++j) { + vst1q_u8(dst + j * BPS, top); + } +} + +static void HE16_NEON(uint8_t* dst) { // horizontal + int j; + for (j = 0; j < 16; ++j) { + const uint8x16_t left = vld1q_dup_u8(dst - 1); + vst1q_u8(dst, left); + dst += BPS; + } +} + +static WEBP_INLINE void DC16_NEON(uint8_t* dst, int do_top, int do_left) { + uint16x8_t sum_top; + uint16x8_t sum_left; + uint8x8_t dc0; + + if (do_top) { + const uint8x16_t A = vld1q_u8(dst - BPS); // top row +#if WEBP_AARCH64 + const uint16_t p3 = vaddlvq_u8(A); + sum_top = vdupq_n_u16(p3); +#else + const uint16x8_t p0 = vpaddlq_u8(A); // cascading summation of the top + const uint16x4_t p1 = vadd_u16(vget_low_u16(p0), vget_high_u16(p0)); + const uint16x4_t p2 = vpadd_u16(p1, p1); + const uint16x4_t p3 = vpadd_u16(p2, p2); + sum_top = vcombine_u16(p3, p3); +#endif + } + + if (do_left) { + int i; + sum_left = vdupq_n_u16(0); + for (i = 0; i < 16; i += 8) { + const uint8x8_t L0 = vld1_u8(dst + (i + 0) * BPS - 1); + const uint8x8_t L1 = vld1_u8(dst + (i + 1) * BPS - 1); + const uint8x8_t L2 = vld1_u8(dst + (i + 2) * BPS - 1); + const uint8x8_t L3 = vld1_u8(dst + (i + 3) * BPS - 1); + const uint8x8_t L4 = vld1_u8(dst + (i + 4) * BPS - 1); + const uint8x8_t L5 = vld1_u8(dst + (i + 5) * BPS - 1); + const uint8x8_t L6 = vld1_u8(dst + (i + 6) * BPS - 1); + const uint8x8_t L7 = vld1_u8(dst + (i + 7) * BPS - 1); + const uint16x8_t s0 = vaddl_u8(L0, L1); + const uint16x8_t s1 = vaddl_u8(L2, L3); + const uint16x8_t s2 = vaddl_u8(L4, L5); + const uint16x8_t s3 = vaddl_u8(L6, L7); + const uint16x8_t s01 = vaddq_u16(s0, s1); + const uint16x8_t s23 = vaddq_u16(s2, s3); + const uint16x8_t sum = vaddq_u16(s01, s23); + sum_left = vaddq_u16(sum_left, sum); + } + } + + if (do_top && do_left) { + const uint16x8_t sum = vaddq_u16(sum_left, sum_top); + dc0 = vrshrn_n_u16(sum, 5); + } else if (do_top) { + dc0 = vrshrn_n_u16(sum_top, 4); + } else if (do_left) { + dc0 = vrshrn_n_u16(sum_left, 4); + } else { + dc0 = vdup_n_u8(0x80); + } + + { + const uint8x16_t dc = vdupq_lane_u8(dc0, 0); + int i; + for (i = 0; i < 16; ++i) { + vst1q_u8(dst + i * BPS, dc); + } + } +} + +static void DC16TopLeft_NEON(uint8_t* dst) { DC16_NEON(dst, 1, 1); } +static void DC16NoTop_NEON(uint8_t* dst) { DC16_NEON(dst, 0, 1); } +static void DC16NoLeft_NEON(uint8_t* dst) { DC16_NEON(dst, 1, 0); } +static void DC16NoTopLeft_NEON(uint8_t* dst) { DC16_NEON(dst, 0, 0); } + +static void TM16_NEON(uint8_t* dst) { + const uint8x8_t TL = vld1_dup_u8(dst - BPS - 1); // top-left pixel 'A[-1]' + const uint8x16_t T = vld1q_u8(dst - BPS); // top row 'A[0..15]' + // A[c] - A[-1] + const uint16x8_t d_lo = vsubl_u8(vget_low_u8(T), TL); + const uint16x8_t d_hi = vsubl_u8(vget_high_u8(T), TL); + int y; + for (y = 0; y < 16; y += 4) { + // left edge + const uint8x8_t L0 = vld1_dup_u8(dst + 0 * BPS - 1); + const uint8x8_t L1 = vld1_dup_u8(dst + 1 * BPS - 1); + const uint8x8_t L2 = vld1_dup_u8(dst + 2 * BPS - 1); + const uint8x8_t L3 = vld1_dup_u8(dst + 3 * BPS - 1); + // L[r] + A[c] - A[-1] + const int16x8_t r0_lo = vreinterpretq_s16_u16(vaddw_u8(d_lo, L0)); + const int16x8_t r1_lo = vreinterpretq_s16_u16(vaddw_u8(d_lo, L1)); + const int16x8_t r2_lo = vreinterpretq_s16_u16(vaddw_u8(d_lo, L2)); + const int16x8_t r3_lo = vreinterpretq_s16_u16(vaddw_u8(d_lo, L3)); + const int16x8_t r0_hi = vreinterpretq_s16_u16(vaddw_u8(d_hi, L0)); + const int16x8_t r1_hi = vreinterpretq_s16_u16(vaddw_u8(d_hi, L1)); + const int16x8_t r2_hi = vreinterpretq_s16_u16(vaddw_u8(d_hi, L2)); + const int16x8_t r3_hi = vreinterpretq_s16_u16(vaddw_u8(d_hi, L3)); + // Saturate and store the result. + const uint8x16_t row0 = vcombine_u8(vqmovun_s16(r0_lo), vqmovun_s16(r0_hi)); + const uint8x16_t row1 = vcombine_u8(vqmovun_s16(r1_lo), vqmovun_s16(r1_hi)); + const uint8x16_t row2 = vcombine_u8(vqmovun_s16(r2_lo), vqmovun_s16(r2_hi)); + const uint8x16_t row3 = vcombine_u8(vqmovun_s16(r3_lo), vqmovun_s16(r3_hi)); + vst1q_u8(dst + 0 * BPS, row0); + vst1q_u8(dst + 1 * BPS, row1); + vst1q_u8(dst + 2 * BPS, row2); + vst1q_u8(dst + 3 * BPS, row3); + dst += 4 * BPS; + } +} + +//------------------------------------------------------------------------------ +// Entry point + +extern void VP8DspInitNEON(void); + +WEBP_TSAN_IGNORE_FUNCTION void VP8DspInitNEON(void) { + VP8Transform = TransformTwo_NEON; + VP8TransformAC3 = TransformAC3_NEON; + VP8TransformDC = TransformDC_NEON; + VP8TransformWHT = TransformWHT_NEON; + + VP8VFilter16 = VFilter16_NEON; + VP8VFilter16i = VFilter16i_NEON; + VP8HFilter16 = HFilter16_NEON; +#if !defined(WORK_AROUND_GCC) + VP8HFilter16i = HFilter16i_NEON; +#endif + VP8VFilter8 = VFilter8_NEON; + VP8VFilter8i = VFilter8i_NEON; +#if !defined(WORK_AROUND_GCC) + VP8HFilter8 = HFilter8_NEON; + VP8HFilter8i = HFilter8i_NEON; +#endif + VP8SimpleVFilter16 = SimpleVFilter16_NEON; + VP8SimpleHFilter16 = SimpleHFilter16_NEON; + VP8SimpleVFilter16i = SimpleVFilter16i_NEON; + VP8SimpleHFilter16i = SimpleHFilter16i_NEON; + + VP8PredLuma4[0] = DC4_NEON; + VP8PredLuma4[1] = TM4_NEON; + VP8PredLuma4[2] = VE4_NEON; + VP8PredLuma4[4] = RD4_NEON; + VP8PredLuma4[6] = LD4_NEON; + + VP8PredLuma16[0] = DC16TopLeft_NEON; + VP8PredLuma16[1] = TM16_NEON; + VP8PredLuma16[2] = VE16_NEON; + VP8PredLuma16[3] = HE16_NEON; + VP8PredLuma16[4] = DC16NoTop_NEON; + VP8PredLuma16[5] = DC16NoLeft_NEON; + VP8PredLuma16[6] = DC16NoTopLeft_NEON; + + VP8PredChroma8[0] = DC8uv_NEON; + VP8PredChroma8[1] = TM8uv_NEON; + VP8PredChroma8[2] = VE8uv_NEON; + VP8PredChroma8[3] = HE8uv_NEON; + VP8PredChroma8[4] = DC8uvNoTop_NEON; + VP8PredChroma8[5] = DC8uvNoLeft_NEON; + VP8PredChroma8[6] = DC8uvNoTopLeft_NEON; +} + +#else // !WEBP_USE_NEON + +WEBP_DSP_INIT_STUB(VP8DspInitNEON) + +#endif // WEBP_USE_NEON diff --git a/packages/core/src/zig/vendor/libwebp/src/dsp/dec_sse2.c b/packages/core/src/zig/vendor/libwebp/src/dsp/dec_sse2.c new file mode 100644 index 0000000000..d5f273e33e --- /dev/null +++ b/packages/core/src/zig/vendor/libwebp/src/dsp/dec_sse2.c @@ -0,0 +1,1233 @@ +// Copyright 2011 Google Inc. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the COPYING file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +// ----------------------------------------------------------------------------- +// +// SSE2 version of some decoding functions (idct, loop filtering). +// +// Author: somnath@google.com (Somnath Banerjee) +// cduvivier@google.com (Christian Duvivier) + +#include "src/dsp/dsp.h" + +#if defined(WEBP_USE_SSE2) + +// The 3-coeff sparse transform in SSE2 is not really faster than the plain-C +// one it seems => disable it by default. Uncomment the following to enable: +#if !defined(USE_TRANSFORM_AC3) +#define USE_TRANSFORM_AC3 0 // ALTERNATE_CODE +#endif + +#include + +#include "src/dec/vp8i_dec.h" +#include "src/dsp/common_sse2.h" +#include "src/dsp/cpu.h" +#include "src/utils/utils.h" +#include "src/webp/types.h" + +//------------------------------------------------------------------------------ +// Transforms (Paragraph 14.4) + +static void Transform_SSE2(const int16_t* WEBP_RESTRICT in, + uint8_t* WEBP_RESTRICT dst, int do_two) { + // This implementation makes use of 16-bit fixed point versions of two + // multiply constants: + // K1 = sqrt(2) * cos (pi/8) ~= 85627 / 2^16 + // K2 = sqrt(2) * sin (pi/8) ~= 35468 / 2^16 + // + // To be able to use signed 16-bit integers, we use the following trick to + // have constants within range: + // - Associated constants are obtained by subtracting the 16-bit fixed point + // version of one: + // k = K - (1 << 16) => K = k + (1 << 16) + // K1 = 85267 => k1 = 20091 + // K2 = 35468 => k2 = -30068 + // - The multiplication of a variable by a constant become the sum of the + // variable and the multiplication of that variable by the associated + // constant: + // (x * K) >> 16 = (x * (k + (1 << 16))) >> 16 = ((x * k ) >> 16) + x + const __m128i k1 = _mm_set1_epi16(20091); + const __m128i k2 = _mm_set1_epi16(-30068); + __m128i T0, T1, T2, T3; + + // Load and concatenate the transform coefficients (we'll do two transforms + // in parallel). In the case of only one transform, the second half of the + // vectors will just contain random value we'll never use nor store. + __m128i in0, in1, in2, in3; + { + in0 = _mm_loadl_epi64((const __m128i*)&in[0]); + in1 = _mm_loadl_epi64((const __m128i*)&in[4]); + in2 = _mm_loadl_epi64((const __m128i*)&in[8]); + in3 = _mm_loadl_epi64((const __m128i*)&in[12]); + // a00 a10 a20 a30 x x x x + // a01 a11 a21 a31 x x x x + // a02 a12 a22 a32 x x x x + // a03 a13 a23 a33 x x x x + if (do_two) { + const __m128i inB0 = _mm_loadl_epi64((const __m128i*)&in[16]); + const __m128i inB1 = _mm_loadl_epi64((const __m128i*)&in[20]); + const __m128i inB2 = _mm_loadl_epi64((const __m128i*)&in[24]); + const __m128i inB3 = _mm_loadl_epi64((const __m128i*)&in[28]); + in0 = _mm_unpacklo_epi64(in0, inB0); + in1 = _mm_unpacklo_epi64(in1, inB1); + in2 = _mm_unpacklo_epi64(in2, inB2); + in3 = _mm_unpacklo_epi64(in3, inB3); + // a00 a10 a20 a30 b00 b10 b20 b30 + // a01 a11 a21 a31 b01 b11 b21 b31 + // a02 a12 a22 a32 b02 b12 b22 b32 + // a03 a13 a23 a33 b03 b13 b23 b33 + } + } + + // Vertical pass and subsequent transpose. + { + // First pass, c and d calculations are longer because of the "trick" + // multiplications. + const __m128i a = _mm_add_epi16(in0, in2); + const __m128i b = _mm_sub_epi16(in0, in2); + // c = MUL(in1, K2) - MUL(in3, K1) = MUL(in1, k2) - MUL(in3, k1) + in1 - in3 + const __m128i c1 = _mm_mulhi_epi16(in1, k2); + const __m128i c2 = _mm_mulhi_epi16(in3, k1); + const __m128i c3 = _mm_sub_epi16(in1, in3); + const __m128i c4 = _mm_sub_epi16(c1, c2); + const __m128i c = _mm_add_epi16(c3, c4); + // d = MUL(in1, K1) + MUL(in3, K2) = MUL(in1, k1) + MUL(in3, k2) + in1 + in3 + const __m128i d1 = _mm_mulhi_epi16(in1, k1); + const __m128i d2 = _mm_mulhi_epi16(in3, k2); + const __m128i d3 = _mm_add_epi16(in1, in3); + const __m128i d4 = _mm_add_epi16(d1, d2); + const __m128i d = _mm_add_epi16(d3, d4); + + // Second pass. + const __m128i tmp0 = _mm_add_epi16(a, d); + const __m128i tmp1 = _mm_add_epi16(b, c); + const __m128i tmp2 = _mm_sub_epi16(b, c); + const __m128i tmp3 = _mm_sub_epi16(a, d); + + // Transpose the two 4x4. + VP8Transpose_2_4x4_16b(&tmp0, &tmp1, &tmp2, &tmp3, &T0, &T1, &T2, &T3); + } + + // Horizontal pass and subsequent transpose. + { + // First pass, c and d calculations are longer because of the "trick" + // multiplications. + const __m128i four = _mm_set1_epi16(4); + const __m128i dc = _mm_add_epi16(T0, four); + const __m128i a = _mm_add_epi16(dc, T2); + const __m128i b = _mm_sub_epi16(dc, T2); + // c = MUL(T1, K2) - MUL(T3, K1) = MUL(T1, k2) - MUL(T3, k1) + T1 - T3 + const __m128i c1 = _mm_mulhi_epi16(T1, k2); + const __m128i c2 = _mm_mulhi_epi16(T3, k1); + const __m128i c3 = _mm_sub_epi16(T1, T3); + const __m128i c4 = _mm_sub_epi16(c1, c2); + const __m128i c = _mm_add_epi16(c3, c4); + // d = MUL(T1, K1) + MUL(T3, K2) = MUL(T1, k1) + MUL(T3, k2) + T1 + T3 + const __m128i d1 = _mm_mulhi_epi16(T1, k1); + const __m128i d2 = _mm_mulhi_epi16(T3, k2); + const __m128i d3 = _mm_add_epi16(T1, T3); + const __m128i d4 = _mm_add_epi16(d1, d2); + const __m128i d = _mm_add_epi16(d3, d4); + + // Second pass. + const __m128i tmp0 = _mm_add_epi16(a, d); + const __m128i tmp1 = _mm_add_epi16(b, c); + const __m128i tmp2 = _mm_sub_epi16(b, c); + const __m128i tmp3 = _mm_sub_epi16(a, d); + const __m128i shifted0 = _mm_srai_epi16(tmp0, 3); + const __m128i shifted1 = _mm_srai_epi16(tmp1, 3); + const __m128i shifted2 = _mm_srai_epi16(tmp2, 3); + const __m128i shifted3 = _mm_srai_epi16(tmp3, 3); + + // Transpose the two 4x4. + VP8Transpose_2_4x4_16b(&shifted0, &shifted1, &shifted2, &shifted3, &T0, &T1, + &T2, &T3); + } + + // Add inverse transform to 'dst' and store. + { + const __m128i zero = _mm_setzero_si128(); + // Load the reference(s). + __m128i dst0, dst1, dst2, dst3; + if (do_two) { + // Load eight bytes/pixels per line. + dst0 = _mm_loadl_epi64((__m128i*)(dst + 0 * BPS)); + dst1 = _mm_loadl_epi64((__m128i*)(dst + 1 * BPS)); + dst2 = _mm_loadl_epi64((__m128i*)(dst + 2 * BPS)); + dst3 = _mm_loadl_epi64((__m128i*)(dst + 3 * BPS)); + } else { + // Load four bytes/pixels per line. + dst0 = _mm_cvtsi32_si128(WebPMemToInt32(dst + 0 * BPS)); + dst1 = _mm_cvtsi32_si128(WebPMemToInt32(dst + 1 * BPS)); + dst2 = _mm_cvtsi32_si128(WebPMemToInt32(dst + 2 * BPS)); + dst3 = _mm_cvtsi32_si128(WebPMemToInt32(dst + 3 * BPS)); + } + // Convert to 16b. + dst0 = _mm_unpacklo_epi8(dst0, zero); + dst1 = _mm_unpacklo_epi8(dst1, zero); + dst2 = _mm_unpacklo_epi8(dst2, zero); + dst3 = _mm_unpacklo_epi8(dst3, zero); + // Add the inverse transform(s). + dst0 = _mm_add_epi16(dst0, T0); + dst1 = _mm_add_epi16(dst1, T1); + dst2 = _mm_add_epi16(dst2, T2); + dst3 = _mm_add_epi16(dst3, T3); + // Unsigned saturate to 8b. + dst0 = _mm_packus_epi16(dst0, dst0); + dst1 = _mm_packus_epi16(dst1, dst1); + dst2 = _mm_packus_epi16(dst2, dst2); + dst3 = _mm_packus_epi16(dst3, dst3); + // Store the results. + if (do_two) { + // Store eight bytes/pixels per line. + _mm_storel_epi64((__m128i*)(dst + 0 * BPS), dst0); + _mm_storel_epi64((__m128i*)(dst + 1 * BPS), dst1); + _mm_storel_epi64((__m128i*)(dst + 2 * BPS), dst2); + _mm_storel_epi64((__m128i*)(dst + 3 * BPS), dst3); + } else { + // Store four bytes/pixels per line. + WebPInt32ToMem(dst + 0 * BPS, _mm_cvtsi128_si32(dst0)); + WebPInt32ToMem(dst + 1 * BPS, _mm_cvtsi128_si32(dst1)); + WebPInt32ToMem(dst + 2 * BPS, _mm_cvtsi128_si32(dst2)); + WebPInt32ToMem(dst + 3 * BPS, _mm_cvtsi128_si32(dst3)); + } + } +} + +#if (USE_TRANSFORM_AC3 == 1) + +static void TransformAC3_SSE2(const int16_t* WEBP_RESTRICT in, + uint8_t* WEBP_RESTRICT dst) { + const __m128i A = _mm_set1_epi16(in[0] + 4); + const __m128i c4 = _mm_set1_epi16(WEBP_TRANSFORM_AC3_MUL2(in[4])); + const __m128i d4 = _mm_set1_epi16(WEBP_TRANSFORM_AC3_MUL1(in[4])); + const int c1 = WEBP_TRANSFORM_AC3_MUL2(in[1]); + const int d1 = WEBP_TRANSFORM_AC3_MUL1(in[1]); + const __m128i CD = _mm_set_epi16(0, 0, 0, 0, -d1, -c1, c1, d1); + const __m128i B = _mm_adds_epi16(A, CD); + const __m128i m0 = _mm_adds_epi16(B, d4); + const __m128i m1 = _mm_adds_epi16(B, c4); + const __m128i m2 = _mm_subs_epi16(B, c4); + const __m128i m3 = _mm_subs_epi16(B, d4); + const __m128i zero = _mm_setzero_si128(); + // Load the source pixels. + __m128i dst0 = _mm_cvtsi32_si128(WebPMemToInt32(dst + 0 * BPS)); + __m128i dst1 = _mm_cvtsi32_si128(WebPMemToInt32(dst + 1 * BPS)); + __m128i dst2 = _mm_cvtsi32_si128(WebPMemToInt32(dst + 2 * BPS)); + __m128i dst3 = _mm_cvtsi32_si128(WebPMemToInt32(dst + 3 * BPS)); + // Convert to 16b. + dst0 = _mm_unpacklo_epi8(dst0, zero); + dst1 = _mm_unpacklo_epi8(dst1, zero); + dst2 = _mm_unpacklo_epi8(dst2, zero); + dst3 = _mm_unpacklo_epi8(dst3, zero); + // Add the inverse transform. + dst0 = _mm_adds_epi16(dst0, _mm_srai_epi16(m0, 3)); + dst1 = _mm_adds_epi16(dst1, _mm_srai_epi16(m1, 3)); + dst2 = _mm_adds_epi16(dst2, _mm_srai_epi16(m2, 3)); + dst3 = _mm_adds_epi16(dst3, _mm_srai_epi16(m3, 3)); + // Unsigned saturate to 8b. + dst0 = _mm_packus_epi16(dst0, dst0); + dst1 = _mm_packus_epi16(dst1, dst1); + dst2 = _mm_packus_epi16(dst2, dst2); + dst3 = _mm_packus_epi16(dst3, dst3); + // Store the results. + WebPInt32ToMem(dst + 0 * BPS, _mm_cvtsi128_si32(dst0)); + WebPInt32ToMem(dst + 1 * BPS, _mm_cvtsi128_si32(dst1)); + WebPInt32ToMem(dst + 2 * BPS, _mm_cvtsi128_si32(dst2)); + WebPInt32ToMem(dst + 3 * BPS, _mm_cvtsi128_si32(dst3)); +} + +#endif // USE_TRANSFORM_AC3 + +//------------------------------------------------------------------------------ +// Loop Filter (Paragraph 15) + +// Compute abs(p - q) = subs(p - q) OR subs(q - p) +#define MM_ABS(p, q) _mm_or_si128( \ + _mm_subs_epu8((q), (p)), \ + _mm_subs_epu8((p), (q))) + +// Shift each byte of "x" by 3 bits while preserving by the sign bit. +static WEBP_INLINE void SignedShift8b_SSE2(__m128i* const x) { + const __m128i zero = _mm_setzero_si128(); + const __m128i lo_0 = _mm_unpacklo_epi8(zero, *x); + const __m128i hi_0 = _mm_unpackhi_epi8(zero, *x); + const __m128i lo_1 = _mm_srai_epi16(lo_0, 3 + 8); + const __m128i hi_1 = _mm_srai_epi16(hi_0, 3 + 8); + *x = _mm_packs_epi16(lo_1, hi_1); +} + +#define FLIP_SIGN_BIT2(a, b) do { \ + (a) = _mm_xor_si128(a, sign_bit); \ + (b) = _mm_xor_si128(b, sign_bit); \ +} while (0) + +#define FLIP_SIGN_BIT4(a, b, c, d) do { \ + FLIP_SIGN_BIT2(a, b); \ + FLIP_SIGN_BIT2(c, d); \ +} while (0) + +// input/output is uint8_t +static WEBP_INLINE void GetNotHEV_SSE2(const __m128i* const p1, + const __m128i* const p0, + const __m128i* const q0, + const __m128i* const q1, + int hev_thresh, __m128i* const not_hev) { + const __m128i zero = _mm_setzero_si128(); + const __m128i t_1 = MM_ABS(*p1, *p0); + const __m128i t_2 = MM_ABS(*q1, *q0); + + const __m128i h = _mm_set1_epi8(hev_thresh); + const __m128i t_max = _mm_max_epu8(t_1, t_2); + + const __m128i t_max_h = _mm_subs_epu8(t_max, h); + *not_hev = _mm_cmpeq_epi8(t_max_h, zero); // not_hev <= t1 && not_hev <= t2 +} + +// input pixels are int8_t +static WEBP_INLINE void GetBaseDelta_SSE2(const __m128i* const p1, + const __m128i* const p0, + const __m128i* const q0, + const __m128i* const q1, + __m128i* const delta) { + // beware of addition order, for saturation! + const __m128i p1_q1 = _mm_subs_epi8(*p1, *q1); // p1 - q1 + const __m128i q0_p0 = _mm_subs_epi8(*q0, *p0); // q0 - p0 + const __m128i s1 = _mm_adds_epi8(p1_q1, q0_p0); // p1 - q1 + 1 * (q0 - p0) + const __m128i s2 = _mm_adds_epi8(q0_p0, s1); // p1 - q1 + 2 * (q0 - p0) + const __m128i s3 = _mm_adds_epi8(q0_p0, s2); // p1 - q1 + 3 * (q0 - p0) + *delta = s3; +} + +// input and output are int8_t +static WEBP_INLINE void DoSimpleFilter_SSE2(__m128i* const p0, + __m128i* const q0, + const __m128i* const fl) { + const __m128i k3 = _mm_set1_epi8(3); + const __m128i k4 = _mm_set1_epi8(4); + __m128i v3 = _mm_adds_epi8(*fl, k3); + __m128i v4 = _mm_adds_epi8(*fl, k4); + + SignedShift8b_SSE2(&v4); // v4 >> 3 + SignedShift8b_SSE2(&v3); // v3 >> 3 + *q0 = _mm_subs_epi8(*q0, v4); // q0 -= v4 + *p0 = _mm_adds_epi8(*p0, v3); // p0 += v3 +} + +// Updates values of 2 pixels at MB edge during complex filtering. +// Update operations: +// q = q - delta and p = p + delta; where delta = [(a_hi >> 7), (a_lo >> 7)] +// Pixels 'pi' and 'qi' are int8_t on input, uint8_t on output (sign flip). +static WEBP_INLINE void Update2Pixels_SSE2(__m128i* const pi, __m128i* const qi, + const __m128i* const a0_lo, + const __m128i* const a0_hi) { + const __m128i a1_lo = _mm_srai_epi16(*a0_lo, 7); + const __m128i a1_hi = _mm_srai_epi16(*a0_hi, 7); + const __m128i delta = _mm_packs_epi16(a1_lo, a1_hi); + const __m128i sign_bit = _mm_set1_epi8((char)0x80); + *pi = _mm_adds_epi8(*pi, delta); + *qi = _mm_subs_epi8(*qi, delta); + FLIP_SIGN_BIT2(*pi, *qi); +} + +// input pixels are uint8_t +static WEBP_INLINE void NeedsFilter_SSE2(const __m128i* const p1, + const __m128i* const p0, + const __m128i* const q0, + const __m128i* const q1, + int thresh, __m128i* const mask) { + const __m128i m_thresh = _mm_set1_epi8((char)thresh); + const __m128i t1 = MM_ABS(*p1, *q1); // abs(p1 - q1) + const __m128i kFE = _mm_set1_epi8((char)0xFE); + const __m128i t2 = _mm_and_si128(t1, kFE); // set lsb of each byte to zero + const __m128i t3 = _mm_srli_epi16(t2, 1); // abs(p1 - q1) / 2 + + const __m128i t4 = MM_ABS(*p0, *q0); // abs(p0 - q0) + const __m128i t5 = _mm_adds_epu8(t4, t4); // abs(p0 - q0) * 2 + const __m128i t6 = _mm_adds_epu8(t5, t3); // abs(p0-q0)*2 + abs(p1-q1)/2 + + const __m128i t7 = _mm_subs_epu8(t6, m_thresh); // mask <= m_thresh + *mask = _mm_cmpeq_epi8(t7, _mm_setzero_si128()); +} + +//------------------------------------------------------------------------------ +// Edge filtering functions + +// Applies filter on 2 pixels (p0 and q0) +static WEBP_INLINE void DoFilter2_SSE2(__m128i* const p1, __m128i* const p0, + __m128i* const q0, __m128i* const q1, + int thresh) { + __m128i a, mask; + const __m128i sign_bit = _mm_set1_epi8((char)0x80); + // convert p1/q1 to int8_t (for GetBaseDelta_SSE2) + const __m128i p1s = _mm_xor_si128(*p1, sign_bit); + const __m128i q1s = _mm_xor_si128(*q1, sign_bit); + + NeedsFilter_SSE2(p1, p0, q0, q1, thresh, &mask); + + FLIP_SIGN_BIT2(*p0, *q0); + GetBaseDelta_SSE2(&p1s, p0, q0, &q1s, &a); + a = _mm_and_si128(a, mask); // mask filter values we don't care about + DoSimpleFilter_SSE2(p0, q0, &a); + FLIP_SIGN_BIT2(*p0, *q0); +} + +// Applies filter on 4 pixels (p1, p0, q0 and q1) +static WEBP_INLINE void DoFilter4_SSE2(__m128i* const p1, __m128i* const p0, + __m128i* const q0, __m128i* const q1, + const __m128i* const mask, + int hev_thresh) { + const __m128i zero = _mm_setzero_si128(); + const __m128i sign_bit = _mm_set1_epi8((char)0x80); + const __m128i k64 = _mm_set1_epi8(64); + const __m128i k3 = _mm_set1_epi8(3); + const __m128i k4 = _mm_set1_epi8(4); + __m128i not_hev; + __m128i t1, t2, t3; + + // compute hev mask + GetNotHEV_SSE2(p1, p0, q0, q1, hev_thresh, ¬_hev); + + // convert to signed values + FLIP_SIGN_BIT4(*p1, *p0, *q0, *q1); + + t1 = _mm_subs_epi8(*p1, *q1); // p1 - q1 + t1 = _mm_andnot_si128(not_hev, t1); // hev(p1 - q1) + t2 = _mm_subs_epi8(*q0, *p0); // q0 - p0 + t1 = _mm_adds_epi8(t1, t2); // hev(p1 - q1) + 1 * (q0 - p0) + t1 = _mm_adds_epi8(t1, t2); // hev(p1 - q1) + 2 * (q0 - p0) + t1 = _mm_adds_epi8(t1, t2); // hev(p1 - q1) + 3 * (q0 - p0) + t1 = _mm_and_si128(t1, *mask); // mask filter values we don't care about + + t2 = _mm_adds_epi8(t1, k3); // 3 * (q0 - p0) + hev(p1 - q1) + 3 + t3 = _mm_adds_epi8(t1, k4); // 3 * (q0 - p0) + hev(p1 - q1) + 4 + SignedShift8b_SSE2(&t2); // (3 * (q0 - p0) + hev(p1 - q1) + 3) >> 3 + SignedShift8b_SSE2(&t3); // (3 * (q0 - p0) + hev(p1 - q1) + 4) >> 3 + *p0 = _mm_adds_epi8(*p0, t2); // p0 += t2 + *q0 = _mm_subs_epi8(*q0, t3); // q0 -= t3 + FLIP_SIGN_BIT2(*p0, *q0); + + // this is equivalent to signed (a + 1) >> 1 calculation + t2 = _mm_add_epi8(t3, sign_bit); + t3 = _mm_avg_epu8(t2, zero); + t3 = _mm_sub_epi8(t3, k64); + + t3 = _mm_and_si128(not_hev, t3); // if !hev + *q1 = _mm_subs_epi8(*q1, t3); // q1 -= t3 + *p1 = _mm_adds_epi8(*p1, t3); // p1 += t3 + FLIP_SIGN_BIT2(*p1, *q1); +} + +// Applies filter on 6 pixels (p2, p1, p0, q0, q1 and q2) +static WEBP_INLINE void DoFilter6_SSE2(__m128i* const p2, __m128i* const p1, + __m128i* const p0, __m128i* const q0, + __m128i* const q1, __m128i* const q2, + const __m128i* const mask, + int hev_thresh) { + const __m128i zero = _mm_setzero_si128(); + const __m128i sign_bit = _mm_set1_epi8((char)0x80); + __m128i a, not_hev; + + // compute hev mask + GetNotHEV_SSE2(p1, p0, q0, q1, hev_thresh, ¬_hev); + + FLIP_SIGN_BIT4(*p1, *p0, *q0, *q1); + FLIP_SIGN_BIT2(*p2, *q2); + GetBaseDelta_SSE2(p1, p0, q0, q1, &a); + + { // do simple filter on pixels with hev + const __m128i m = _mm_andnot_si128(not_hev, *mask); + const __m128i f = _mm_and_si128(a, m); + DoSimpleFilter_SSE2(p0, q0, &f); + } + + { // do strong filter on pixels with not hev + const __m128i k9 = _mm_set1_epi16(0x0900); + const __m128i k63 = _mm_set1_epi16(63); + + const __m128i m = _mm_and_si128(not_hev, *mask); + const __m128i f = _mm_and_si128(a, m); + + const __m128i f_lo = _mm_unpacklo_epi8(zero, f); + const __m128i f_hi = _mm_unpackhi_epi8(zero, f); + + const __m128i f9_lo = _mm_mulhi_epi16(f_lo, k9); // Filter (lo) * 9 + const __m128i f9_hi = _mm_mulhi_epi16(f_hi, k9); // Filter (hi) * 9 + + const __m128i a2_lo = _mm_add_epi16(f9_lo, k63); // Filter * 9 + 63 + const __m128i a2_hi = _mm_add_epi16(f9_hi, k63); // Filter * 9 + 63 + + const __m128i a1_lo = _mm_add_epi16(a2_lo, f9_lo); // Filter * 18 + 63 + const __m128i a1_hi = _mm_add_epi16(a2_hi, f9_hi); // Filter * 18 + 63 + + const __m128i a0_lo = _mm_add_epi16(a1_lo, f9_lo); // Filter * 27 + 63 + const __m128i a0_hi = _mm_add_epi16(a1_hi, f9_hi); // Filter * 27 + 63 + + Update2Pixels_SSE2(p2, q2, &a2_lo, &a2_hi); + Update2Pixels_SSE2(p1, q1, &a1_lo, &a1_hi); + Update2Pixels_SSE2(p0, q0, &a0_lo, &a0_hi); + } +} + +// reads 8 rows across a vertical edge. +static WEBP_INLINE void Load8x4_SSE2(const uint8_t* const b, int stride, + __m128i* const p, __m128i* const q) { + // A0 = 63 62 61 60 23 22 21 20 43 42 41 40 03 02 01 00 + // A1 = 73 72 71 70 33 32 31 30 53 52 51 50 13 12 11 10 + const __m128i A0 = _mm_set_epi32( + WebPMemToInt32(&b[6 * stride]), WebPMemToInt32(&b[2 * stride]), + WebPMemToInt32(&b[4 * stride]), WebPMemToInt32(&b[0 * stride])); + const __m128i A1 = _mm_set_epi32( + WebPMemToInt32(&b[7 * stride]), WebPMemToInt32(&b[3 * stride]), + WebPMemToInt32(&b[5 * stride]), WebPMemToInt32(&b[1 * stride])); + + // B0 = 53 43 52 42 51 41 50 40 13 03 12 02 11 01 10 00 + // B1 = 73 63 72 62 71 61 70 60 33 23 32 22 31 21 30 20 + const __m128i B0 = _mm_unpacklo_epi8(A0, A1); + const __m128i B1 = _mm_unpackhi_epi8(A0, A1); + + // C0 = 33 23 13 03 32 22 12 02 31 21 11 01 30 20 10 00 + // C1 = 73 63 53 43 72 62 52 42 71 61 51 41 70 60 50 40 + const __m128i C0 = _mm_unpacklo_epi16(B0, B1); + const __m128i C1 = _mm_unpackhi_epi16(B0, B1); + + // *p = 71 61 51 41 31 21 11 01 70 60 50 40 30 20 10 00 + // *q = 73 63 53 43 33 23 13 03 72 62 52 42 32 22 12 02 + *p = _mm_unpacklo_epi32(C0, C1); + *q = _mm_unpackhi_epi32(C0, C1); +} + +static WEBP_INLINE void Load16x4_SSE2(const uint8_t* const r0, + const uint8_t* const r8, + int stride, + __m128i* const p1, __m128i* const p0, + __m128i* const q0, __m128i* const q1) { + // Assume the pixels around the edge (|) are numbered as follows + // 00 01 | 02 03 + // 10 11 | 12 13 + // ... | ... + // e0 e1 | e2 e3 + // f0 f1 | f2 f3 + // + // r0 is pointing to the 0th row (00) + // r8 is pointing to the 8th row (80) + + // Load + // p1 = 71 61 51 41 31 21 11 01 70 60 50 40 30 20 10 00 + // q0 = 73 63 53 43 33 23 13 03 72 62 52 42 32 22 12 02 + // p0 = f1 e1 d1 c1 b1 a1 91 81 f0 e0 d0 c0 b0 a0 90 80 + // q1 = f3 e3 d3 c3 b3 a3 93 83 f2 e2 d2 c2 b2 a2 92 82 + Load8x4_SSE2(r0, stride, p1, q0); + Load8x4_SSE2(r8, stride, p0, q1); + + { + // p1 = f0 e0 d0 c0 b0 a0 90 80 70 60 50 40 30 20 10 00 + // p0 = f1 e1 d1 c1 b1 a1 91 81 71 61 51 41 31 21 11 01 + // q0 = f2 e2 d2 c2 b2 a2 92 82 72 62 52 42 32 22 12 02 + // q1 = f3 e3 d3 c3 b3 a3 93 83 73 63 53 43 33 23 13 03 + const __m128i t1 = *p1; + const __m128i t2 = *q0; + *p1 = _mm_unpacklo_epi64(t1, *p0); + *p0 = _mm_unpackhi_epi64(t1, *p0); + *q0 = _mm_unpacklo_epi64(t2, *q1); + *q1 = _mm_unpackhi_epi64(t2, *q1); + } +} + +static WEBP_INLINE void Store4x4_SSE2(__m128i* const x, + uint8_t* dst, int stride) { + int i; + for (i = 0; i < 4; ++i, dst += stride) { + WebPInt32ToMem(dst, _mm_cvtsi128_si32(*x)); + *x = _mm_srli_si128(*x, 4); + } +} + +// Transpose back and store +static WEBP_INLINE void Store16x4_SSE2(const __m128i* const p1, + const __m128i* const p0, + const __m128i* const q0, + const __m128i* const q1, + uint8_t* r0, uint8_t* r8, + int stride) { + __m128i t1, p1_s, p0_s, q0_s, q1_s; + + // p0 = 71 70 61 60 51 50 41 40 31 30 21 20 11 10 01 00 + // p1 = f1 f0 e1 e0 d1 d0 c1 c0 b1 b0 a1 a0 91 90 81 80 + t1 = *p0; + p0_s = _mm_unpacklo_epi8(*p1, t1); + p1_s = _mm_unpackhi_epi8(*p1, t1); + + // q0 = 73 72 63 62 53 52 43 42 33 32 23 22 13 12 03 02 + // q1 = f3 f2 e3 e2 d3 d2 c3 c2 b3 b2 a3 a2 93 92 83 82 + t1 = *q0; + q0_s = _mm_unpacklo_epi8(t1, *q1); + q1_s = _mm_unpackhi_epi8(t1, *q1); + + // p0 = 33 32 31 30 23 22 21 20 13 12 11 10 03 02 01 00 + // q0 = 73 72 71 70 63 62 61 60 53 52 51 50 43 42 41 40 + t1 = p0_s; + p0_s = _mm_unpacklo_epi16(t1, q0_s); + q0_s = _mm_unpackhi_epi16(t1, q0_s); + + // p1 = b3 b2 b1 b0 a3 a2 a1 a0 93 92 91 90 83 82 81 80 + // q1 = f3 f2 f1 f0 e3 e2 e1 e0 d3 d2 d1 d0 c3 c2 c1 c0 + t1 = p1_s; + p1_s = _mm_unpacklo_epi16(t1, q1_s); + q1_s = _mm_unpackhi_epi16(t1, q1_s); + + Store4x4_SSE2(&p0_s, r0, stride); + r0 += 4 * stride; + Store4x4_SSE2(&q0_s, r0, stride); + + Store4x4_SSE2(&p1_s, r8, stride); + r8 += 4 * stride; + Store4x4_SSE2(&q1_s, r8, stride); +} + +//------------------------------------------------------------------------------ +// Simple In-loop filtering (Paragraph 15.2) + +static void SimpleVFilter16_SSE2(uint8_t* p, int stride, int thresh) { + // Load + __m128i p1 = _mm_loadu_si128((__m128i*)&p[-2 * stride]); + __m128i p0 = _mm_loadu_si128((__m128i*)&p[-stride]); + __m128i q0 = _mm_loadu_si128((__m128i*)&p[0]); + __m128i q1 = _mm_loadu_si128((__m128i*)&p[stride]); + + DoFilter2_SSE2(&p1, &p0, &q0, &q1, thresh); + + // Store + _mm_storeu_si128((__m128i*)&p[-stride], p0); + _mm_storeu_si128((__m128i*)&p[0], q0); +} + +static void SimpleHFilter16_SSE2(uint8_t* p, int stride, int thresh) { + __m128i p1, p0, q0, q1; + + p -= 2; // beginning of p1 + + Load16x4_SSE2(p, p + 8 * stride, stride, &p1, &p0, &q0, &q1); + DoFilter2_SSE2(&p1, &p0, &q0, &q1, thresh); + Store16x4_SSE2(&p1, &p0, &q0, &q1, p, p + 8 * stride, stride); +} + +static void SimpleVFilter16i_SSE2(uint8_t* p, int stride, int thresh) { + int k; + for (k = 3; k > 0; --k) { + p += 4 * stride; + SimpleVFilter16_SSE2(p, stride, thresh); + } +} + +static void SimpleHFilter16i_SSE2(uint8_t* p, int stride, int thresh) { + int k; + for (k = 3; k > 0; --k) { + p += 4; + SimpleHFilter16_SSE2(p, stride, thresh); + } +} + +//------------------------------------------------------------------------------ +// Complex In-loop filtering (Paragraph 15.3) + +#define MAX_DIFF1(p3, p2, p1, p0, m) do { \ + (m) = MM_ABS(p1, p0); \ + (m) = _mm_max_epu8(m, MM_ABS(p3, p2)); \ + (m) = _mm_max_epu8(m, MM_ABS(p2, p1)); \ +} while (0) + +#define MAX_DIFF2(p3, p2, p1, p0, m) do { \ + (m) = _mm_max_epu8(m, MM_ABS(p1, p0)); \ + (m) = _mm_max_epu8(m, MM_ABS(p3, p2)); \ + (m) = _mm_max_epu8(m, MM_ABS(p2, p1)); \ +} while (0) + +#define LOAD_H_EDGES4(p, stride, e1, e2, e3, e4) do { \ + (e1) = _mm_loadu_si128((__m128i*)&(p)[0 * (stride)]); \ + (e2) = _mm_loadu_si128((__m128i*)&(p)[1 * (stride)]); \ + (e3) = _mm_loadu_si128((__m128i*)&(p)[2 * (stride)]); \ + (e4) = _mm_loadu_si128((__m128i*)&(p)[3 * (stride)]); \ +} while (0) + +#define LOADUV_H_EDGE(p, u, v, stride) do { \ + const __m128i U = _mm_loadl_epi64((__m128i*)&(u)[(stride)]); \ + const __m128i V = _mm_loadl_epi64((__m128i*)&(v)[(stride)]); \ + (p) = _mm_unpacklo_epi64(U, V); \ +} while (0) + +#define LOADUV_H_EDGES4(u, v, stride, e1, e2, e3, e4) do { \ + LOADUV_H_EDGE(e1, u, v, 0 * (stride)); \ + LOADUV_H_EDGE(e2, u, v, 1 * (stride)); \ + LOADUV_H_EDGE(e3, u, v, 2 * (stride)); \ + LOADUV_H_EDGE(e4, u, v, 3 * (stride)); \ +} while (0) + +#define STOREUV(p, u, v, stride) do { \ + _mm_storel_epi64((__m128i*)&(u)[(stride)], p); \ + (p) = _mm_srli_si128(p, 8); \ + _mm_storel_epi64((__m128i*)&(v)[(stride)], p); \ +} while (0) + +static WEBP_INLINE void ComplexMask_SSE2(const __m128i* const p1, + const __m128i* const p0, + const __m128i* const q0, + const __m128i* const q1, + int thresh, int ithresh, + __m128i* const mask) { + const __m128i it = _mm_set1_epi8(ithresh); + const __m128i diff = _mm_subs_epu8(*mask, it); + const __m128i thresh_mask = _mm_cmpeq_epi8(diff, _mm_setzero_si128()); + __m128i filter_mask; + NeedsFilter_SSE2(p1, p0, q0, q1, thresh, &filter_mask); + *mask = _mm_and_si128(thresh_mask, filter_mask); +} + +// on macroblock edges +static void VFilter16_SSE2(uint8_t* p, int stride, + int thresh, int ithresh, int hev_thresh) { + __m128i t1; + __m128i mask; + __m128i p2, p1, p0, q0, q1, q2; + + // Load p3, p2, p1, p0 + LOAD_H_EDGES4(p - 4 * stride, stride, t1, p2, p1, p0); + MAX_DIFF1(t1, p2, p1, p0, mask); + + // Load q0, q1, q2, q3 + LOAD_H_EDGES4(p, stride, q0, q1, q2, t1); + MAX_DIFF2(t1, q2, q1, q0, mask); + + ComplexMask_SSE2(&p1, &p0, &q0, &q1, thresh, ithresh, &mask); + DoFilter6_SSE2(&p2, &p1, &p0, &q0, &q1, &q2, &mask, hev_thresh); + + // Store + _mm_storeu_si128((__m128i*)&p[-3 * stride], p2); + _mm_storeu_si128((__m128i*)&p[-2 * stride], p1); + _mm_storeu_si128((__m128i*)&p[-1 * stride], p0); + _mm_storeu_si128((__m128i*)&p[+0 * stride], q0); + _mm_storeu_si128((__m128i*)&p[+1 * stride], q1); + _mm_storeu_si128((__m128i*)&p[+2 * stride], q2); +} + +static void HFilter16_SSE2(uint8_t* p, int stride, + int thresh, int ithresh, int hev_thresh) { + __m128i mask; + __m128i p3, p2, p1, p0, q0, q1, q2, q3; + + uint8_t* const b = p - 4; + Load16x4_SSE2(b, b + 8 * stride, stride, &p3, &p2, &p1, &p0); + MAX_DIFF1(p3, p2, p1, p0, mask); + + Load16x4_SSE2(p, p + 8 * stride, stride, &q0, &q1, &q2, &q3); + MAX_DIFF2(q3, q2, q1, q0, mask); + + ComplexMask_SSE2(&p1, &p0, &q0, &q1, thresh, ithresh, &mask); + DoFilter6_SSE2(&p2, &p1, &p0, &q0, &q1, &q2, &mask, hev_thresh); + + Store16x4_SSE2(&p3, &p2, &p1, &p0, b, b + 8 * stride, stride); + Store16x4_SSE2(&q0, &q1, &q2, &q3, p, p + 8 * stride, stride); +} + +// on three inner edges +static void VFilter16i_SSE2(uint8_t* p, int stride, + int thresh, int ithresh, int hev_thresh) { + int k; + __m128i p3, p2, p1, p0; // loop invariants + + LOAD_H_EDGES4(p, stride, p3, p2, p1, p0); // prologue + + for (k = 3; k > 0; --k) { + __m128i mask, tmp1, tmp2; + uint8_t* const b = p + 2 * stride; // beginning of p1 + p += 4 * stride; + + MAX_DIFF1(p3, p2, p1, p0, mask); // compute partial mask + LOAD_H_EDGES4(p, stride, p3, p2, tmp1, tmp2); + MAX_DIFF2(p3, p2, tmp1, tmp2, mask); + + // p3 and p2 are not just temporary variables here: they will be + // re-used for next span. And q2/q3 will become p1/p0 accordingly. + ComplexMask_SSE2(&p1, &p0, &p3, &p2, thresh, ithresh, &mask); + DoFilter4_SSE2(&p1, &p0, &p3, &p2, &mask, hev_thresh); + + // Store + _mm_storeu_si128((__m128i*)&b[0 * stride], p1); + _mm_storeu_si128((__m128i*)&b[1 * stride], p0); + _mm_storeu_si128((__m128i*)&b[2 * stride], p3); + _mm_storeu_si128((__m128i*)&b[3 * stride], p2); + + // rotate samples + p1 = tmp1; + p0 = tmp2; + } +} + +static void HFilter16i_SSE2(uint8_t* p, int stride, + int thresh, int ithresh, int hev_thresh) { + int k; + __m128i p3, p2, p1, p0; // loop invariants + + Load16x4_SSE2(p, p + 8 * stride, stride, &p3, &p2, &p1, &p0); // prologue + + for (k = 3; k > 0; --k) { + __m128i mask, tmp1, tmp2; + uint8_t* const b = p + 2; // beginning of p1 + + p += 4; // beginning of q0 (and next span) + + MAX_DIFF1(p3, p2, p1, p0, mask); // compute partial mask + Load16x4_SSE2(p, p + 8 * stride, stride, &p3, &p2, &tmp1, &tmp2); + MAX_DIFF2(p3, p2, tmp1, tmp2, mask); + + ComplexMask_SSE2(&p1, &p0, &p3, &p2, thresh, ithresh, &mask); + DoFilter4_SSE2(&p1, &p0, &p3, &p2, &mask, hev_thresh); + + Store16x4_SSE2(&p1, &p0, &p3, &p2, b, b + 8 * stride, stride); + + // rotate samples + p1 = tmp1; + p0 = tmp2; + } +} + +// 8-pixels wide variant, for chroma filtering +static void VFilter8_SSE2(uint8_t* WEBP_RESTRICT u, uint8_t* WEBP_RESTRICT v, + int stride, int thresh, int ithresh, int hev_thresh) { + __m128i mask; + __m128i t1, p2, p1, p0, q0, q1, q2; + + // Load p3, p2, p1, p0 + LOADUV_H_EDGES4(u - 4 * stride, v - 4 * stride, stride, t1, p2, p1, p0); + MAX_DIFF1(t1, p2, p1, p0, mask); + + // Load q0, q1, q2, q3 + LOADUV_H_EDGES4(u, v, stride, q0, q1, q2, t1); + MAX_DIFF2(t1, q2, q1, q0, mask); + + ComplexMask_SSE2(&p1, &p0, &q0, &q1, thresh, ithresh, &mask); + DoFilter6_SSE2(&p2, &p1, &p0, &q0, &q1, &q2, &mask, hev_thresh); + + // Store + STOREUV(p2, u, v, -3 * stride); + STOREUV(p1, u, v, -2 * stride); + STOREUV(p0, u, v, -1 * stride); + STOREUV(q0, u, v, 0 * stride); + STOREUV(q1, u, v, 1 * stride); + STOREUV(q2, u, v, 2 * stride); +} + +static void HFilter8_SSE2(uint8_t* WEBP_RESTRICT u, uint8_t* WEBP_RESTRICT v, + int stride, int thresh, int ithresh, int hev_thresh) { + __m128i mask; + __m128i p3, p2, p1, p0, q0, q1, q2, q3; + + uint8_t* const tu = u - 4; + uint8_t* const tv = v - 4; + Load16x4_SSE2(tu, tv, stride, &p3, &p2, &p1, &p0); + MAX_DIFF1(p3, p2, p1, p0, mask); + + Load16x4_SSE2(u, v, stride, &q0, &q1, &q2, &q3); + MAX_DIFF2(q3, q2, q1, q0, mask); + + ComplexMask_SSE2(&p1, &p0, &q0, &q1, thresh, ithresh, &mask); + DoFilter6_SSE2(&p2, &p1, &p0, &q0, &q1, &q2, &mask, hev_thresh); + + Store16x4_SSE2(&p3, &p2, &p1, &p0, tu, tv, stride); + Store16x4_SSE2(&q0, &q1, &q2, &q3, u, v, stride); +} + +static void VFilter8i_SSE2(uint8_t* WEBP_RESTRICT u, uint8_t* WEBP_RESTRICT v, + int stride, + int thresh, int ithresh, int hev_thresh) { + __m128i mask; + __m128i t1, t2, p1, p0, q0, q1; + + // Load p3, p2, p1, p0 + LOADUV_H_EDGES4(u, v, stride, t2, t1, p1, p0); + MAX_DIFF1(t2, t1, p1, p0, mask); + + u += 4 * stride; + v += 4 * stride; + + // Load q0, q1, q2, q3 + LOADUV_H_EDGES4(u, v, stride, q0, q1, t1, t2); + MAX_DIFF2(t2, t1, q1, q0, mask); + + ComplexMask_SSE2(&p1, &p0, &q0, &q1, thresh, ithresh, &mask); + DoFilter4_SSE2(&p1, &p0, &q0, &q1, &mask, hev_thresh); + + // Store + STOREUV(p1, u, v, -2 * stride); + STOREUV(p0, u, v, -1 * stride); + STOREUV(q0, u, v, 0 * stride); + STOREUV(q1, u, v, 1 * stride); +} + +static void HFilter8i_SSE2(uint8_t* WEBP_RESTRICT u, uint8_t* WEBP_RESTRICT v, + int stride, + int thresh, int ithresh, int hev_thresh) { + __m128i mask; + __m128i t1, t2, p1, p0, q0, q1; + Load16x4_SSE2(u, v, stride, &t2, &t1, &p1, &p0); // p3, p2, p1, p0 + MAX_DIFF1(t2, t1, p1, p0, mask); + + u += 4; // beginning of q0 + v += 4; + Load16x4_SSE2(u, v, stride, &q0, &q1, &t1, &t2); // q0, q1, q2, q3 + MAX_DIFF2(t2, t1, q1, q0, mask); + + ComplexMask_SSE2(&p1, &p0, &q0, &q1, thresh, ithresh, &mask); + DoFilter4_SSE2(&p1, &p0, &q0, &q1, &mask, hev_thresh); + + u -= 2; // beginning of p1 + v -= 2; + Store16x4_SSE2(&p1, &p0, &q0, &q1, u, v, stride); +} + +//------------------------------------------------------------------------------ +// 4x4 predictions + +#define DST(x, y) dst[(x) + (y) * BPS] +#define AVG3(a, b, c) (((a) + 2 * (b) + (c) + 2) >> 2) + +// We use the following 8b-arithmetic tricks: +// (a + 2 * b + c + 2) >> 2 = (AC + b + 1) >> 1 +// where: AC = (a + c) >> 1 = [(a + c + 1) >> 1] - [(a^c) & 1] +// and: +// (a + 2 * b + c + 2) >> 2 = (AB + BC + 1) >> 1 - (ab|bc)&lsb +// where: AC = (a + b + 1) >> 1, BC = (b + c + 1) >> 1 +// and ab = a ^ b, bc = b ^ c, lsb = (AC^BC)&1 + +static void VE4_SSE2(uint8_t* dst) { // vertical + const __m128i one = _mm_set1_epi8(1); + const __m128i ABCDEFGH = _mm_loadl_epi64((__m128i*)(dst - BPS - 1)); + const __m128i BCDEFGH0 = _mm_srli_si128(ABCDEFGH, 1); + const __m128i CDEFGH00 = _mm_srli_si128(ABCDEFGH, 2); + const __m128i a = _mm_avg_epu8(ABCDEFGH, CDEFGH00); + const __m128i lsb = _mm_and_si128(_mm_xor_si128(ABCDEFGH, CDEFGH00), one); + const __m128i b = _mm_subs_epu8(a, lsb); + const __m128i avg = _mm_avg_epu8(b, BCDEFGH0); + const int vals = _mm_cvtsi128_si32(avg); + int i; + for (i = 0; i < 4; ++i) { + WebPInt32ToMem(dst + i * BPS, vals); + } +} + +static void LD4_SSE2(uint8_t* dst) { // Down-Left + const __m128i one = _mm_set1_epi8(1); + const __m128i ABCDEFGH = _mm_loadl_epi64((__m128i*)(dst - BPS)); + const __m128i BCDEFGH0 = _mm_srli_si128(ABCDEFGH, 1); + const __m128i CDEFGH00 = _mm_srli_si128(ABCDEFGH, 2); + const __m128i CDEFGHH0 = _mm_insert_epi16(CDEFGH00, dst[-BPS + 7], 3); + const __m128i avg1 = _mm_avg_epu8(ABCDEFGH, CDEFGHH0); + const __m128i lsb = _mm_and_si128(_mm_xor_si128(ABCDEFGH, CDEFGHH0), one); + const __m128i avg2 = _mm_subs_epu8(avg1, lsb); + const __m128i abcdefg = _mm_avg_epu8(avg2, BCDEFGH0); + WebPInt32ToMem(dst + 0 * BPS, _mm_cvtsi128_si32( abcdefg )); + WebPInt32ToMem(dst + 1 * BPS, _mm_cvtsi128_si32(_mm_srli_si128(abcdefg, 1))); + WebPInt32ToMem(dst + 2 * BPS, _mm_cvtsi128_si32(_mm_srli_si128(abcdefg, 2))); + WebPInt32ToMem(dst + 3 * BPS, _mm_cvtsi128_si32(_mm_srli_si128(abcdefg, 3))); +} + +static void VR4_SSE2(uint8_t* dst) { // Vertical-Right + const __m128i one = _mm_set1_epi8(1); + const int I = dst[-1 + 0 * BPS]; + const int J = dst[-1 + 1 * BPS]; + const int K = dst[-1 + 2 * BPS]; + const int X = dst[-1 - BPS]; + const __m128i XABCD = _mm_loadl_epi64((__m128i*)(dst - BPS - 1)); + const __m128i ABCD0 = _mm_srli_si128(XABCD, 1); + const __m128i abcd = _mm_avg_epu8(XABCD, ABCD0); + const __m128i _XABCD = _mm_slli_si128(XABCD, 1); + const __m128i IXABCD = _mm_insert_epi16(_XABCD, (short)(I | (X << 8)), 0); + const __m128i avg1 = _mm_avg_epu8(IXABCD, ABCD0); + const __m128i lsb = _mm_and_si128(_mm_xor_si128(IXABCD, ABCD0), one); + const __m128i avg2 = _mm_subs_epu8(avg1, lsb); + const __m128i efgh = _mm_avg_epu8(avg2, XABCD); + WebPInt32ToMem(dst + 0 * BPS, _mm_cvtsi128_si32( abcd )); + WebPInt32ToMem(dst + 1 * BPS, _mm_cvtsi128_si32( efgh )); + WebPInt32ToMem(dst + 2 * BPS, _mm_cvtsi128_si32(_mm_slli_si128(abcd, 1))); + WebPInt32ToMem(dst + 3 * BPS, _mm_cvtsi128_si32(_mm_slli_si128(efgh, 1))); + + // these two are hard to implement in SSE2, so we keep the C-version: + DST(0, 2) = AVG3(J, I, X); + DST(0, 3) = AVG3(K, J, I); +} + +static void VL4_SSE2(uint8_t* dst) { // Vertical-Left + const __m128i one = _mm_set1_epi8(1); + const __m128i ABCDEFGH = _mm_loadl_epi64((__m128i*)(dst - BPS)); + const __m128i BCDEFGH_ = _mm_srli_si128(ABCDEFGH, 1); + const __m128i CDEFGH__ = _mm_srli_si128(ABCDEFGH, 2); + const __m128i avg1 = _mm_avg_epu8(ABCDEFGH, BCDEFGH_); + const __m128i avg2 = _mm_avg_epu8(CDEFGH__, BCDEFGH_); + const __m128i avg3 = _mm_avg_epu8(avg1, avg2); + const __m128i lsb1 = _mm_and_si128(_mm_xor_si128(avg1, avg2), one); + const __m128i ab = _mm_xor_si128(ABCDEFGH, BCDEFGH_); + const __m128i bc = _mm_xor_si128(CDEFGH__, BCDEFGH_); + const __m128i abbc = _mm_or_si128(ab, bc); + const __m128i lsb2 = _mm_and_si128(abbc, lsb1); + const __m128i avg4 = _mm_subs_epu8(avg3, lsb2); + const uint32_t extra_out = + (uint32_t)_mm_cvtsi128_si32(_mm_srli_si128(avg4, 4)); + WebPInt32ToMem(dst + 0 * BPS, _mm_cvtsi128_si32( avg1 )); + WebPInt32ToMem(dst + 1 * BPS, _mm_cvtsi128_si32( avg4 )); + WebPInt32ToMem(dst + 2 * BPS, _mm_cvtsi128_si32(_mm_srli_si128(avg1, 1))); + WebPInt32ToMem(dst + 3 * BPS, _mm_cvtsi128_si32(_mm_srli_si128(avg4, 1))); + + // these two are hard to get and irregular + DST(3, 2) = (extra_out >> 0) & 0xff; + DST(3, 3) = (extra_out >> 8) & 0xff; +} + +static void RD4_SSE2(uint8_t* dst) { // Down-right + const __m128i one = _mm_set1_epi8(1); + const __m128i XABCD = _mm_loadl_epi64((__m128i*)(dst - BPS - 1)); + const __m128i ____XABCD = _mm_slli_si128(XABCD, 4); + const uint32_t I = dst[-1 + 0 * BPS]; + const uint32_t J = dst[-1 + 1 * BPS]; + const uint32_t K = dst[-1 + 2 * BPS]; + const uint32_t L = dst[-1 + 3 * BPS]; + const __m128i LKJI_____ = + _mm_cvtsi32_si128((int)(L | (K << 8) | (J << 16) | (I << 24))); + const __m128i LKJIXABCD = _mm_or_si128(LKJI_____, ____XABCD); + const __m128i KJIXABCD_ = _mm_srli_si128(LKJIXABCD, 1); + const __m128i JIXABCD__ = _mm_srli_si128(LKJIXABCD, 2); + const __m128i avg1 = _mm_avg_epu8(JIXABCD__, LKJIXABCD); + const __m128i lsb = _mm_and_si128(_mm_xor_si128(JIXABCD__, LKJIXABCD), one); + const __m128i avg2 = _mm_subs_epu8(avg1, lsb); + const __m128i abcdefg = _mm_avg_epu8(avg2, KJIXABCD_); + WebPInt32ToMem(dst + 3 * BPS, _mm_cvtsi128_si32( abcdefg )); + WebPInt32ToMem(dst + 2 * BPS, _mm_cvtsi128_si32(_mm_srli_si128(abcdefg, 1))); + WebPInt32ToMem(dst + 1 * BPS, _mm_cvtsi128_si32(_mm_srli_si128(abcdefg, 2))); + WebPInt32ToMem(dst + 0 * BPS, _mm_cvtsi128_si32(_mm_srli_si128(abcdefg, 3))); +} + +#undef DST +#undef AVG3 + +//------------------------------------------------------------------------------ +// Luma 16x16 + +static WEBP_INLINE void TrueMotion_SSE2(uint8_t* dst, int size) { + const uint8_t* top = dst - BPS; + const __m128i zero = _mm_setzero_si128(); + int y; + if (size == 4) { + const __m128i top_values = _mm_cvtsi32_si128(WebPMemToInt32(top)); + const __m128i top_base = _mm_unpacklo_epi8(top_values, zero); + for (y = 0; y < 4; ++y, dst += BPS) { + const int val = dst[-1] - top[-1]; + const __m128i base = _mm_set1_epi16(val); + const __m128i out = _mm_packus_epi16(_mm_add_epi16(base, top_base), zero); + WebPInt32ToMem(dst, _mm_cvtsi128_si32(out)); + } + } else if (size == 8) { + const __m128i top_values = _mm_loadl_epi64((const __m128i*)top); + const __m128i top_base = _mm_unpacklo_epi8(top_values, zero); + for (y = 0; y < 8; ++y, dst += BPS) { + const int val = dst[-1] - top[-1]; + const __m128i base = _mm_set1_epi16(val); + const __m128i out = _mm_packus_epi16(_mm_add_epi16(base, top_base), zero); + _mm_storel_epi64((__m128i*)dst, out); + } + } else { + const __m128i top_values = _mm_loadu_si128((const __m128i*)top); + const __m128i top_base_0 = _mm_unpacklo_epi8(top_values, zero); + const __m128i top_base_1 = _mm_unpackhi_epi8(top_values, zero); + for (y = 0; y < 16; ++y, dst += BPS) { + const int val = dst[-1] - top[-1]; + const __m128i base = _mm_set1_epi16(val); + const __m128i out_0 = _mm_add_epi16(base, top_base_0); + const __m128i out_1 = _mm_add_epi16(base, top_base_1); + const __m128i out = _mm_packus_epi16(out_0, out_1); + _mm_storeu_si128((__m128i*)dst, out); + } + } +} + +static void TM4_SSE2(uint8_t* dst) { TrueMotion_SSE2(dst, 4); } +static void TM8uv_SSE2(uint8_t* dst) { TrueMotion_SSE2(dst, 8); } +static void TM16_SSE2(uint8_t* dst) { TrueMotion_SSE2(dst, 16); } + +static void VE16_SSE2(uint8_t* dst) { + const __m128i top = _mm_loadu_si128((const __m128i*)(dst - BPS)); + int j; + for (j = 0; j < 16; ++j) { + _mm_storeu_si128((__m128i*)(dst + j * BPS), top); + } +} + +static void HE16_SSE2(uint8_t* dst) { // horizontal + int j; + for (j = 16; j > 0; --j) { + const __m128i values = _mm_set1_epi8((char)dst[-1]); + _mm_storeu_si128((__m128i*)dst, values); + dst += BPS; + } +} + +static WEBP_INLINE void Put16_SSE2(uint8_t v, uint8_t* dst) { + int j; + const __m128i values = _mm_set1_epi8((char)v); + for (j = 0; j < 16; ++j) { + _mm_storeu_si128((__m128i*)(dst + j * BPS), values); + } +} + +static void DC16_SSE2(uint8_t* dst) { // DC + const __m128i zero = _mm_setzero_si128(); + const __m128i top = _mm_loadu_si128((const __m128i*)(dst - BPS)); + const __m128i sad8x2 = _mm_sad_epu8(top, zero); + // sum the two sads: sad8x2[0:1] + sad8x2[8:9] + const __m128i sum = _mm_add_epi16(sad8x2, _mm_shuffle_epi32(sad8x2, 2)); + int left = 0; + int j; + for (j = 0; j < 16; ++j) { + left += dst[-1 + j * BPS]; + } + { + const int DC = _mm_cvtsi128_si32(sum) + left + 16; + Put16_SSE2(DC >> 5, dst); + } +} + +static void DC16NoTop_SSE2(uint8_t* dst) { // DC with top samples unavailable + int DC = 8; + int j; + for (j = 0; j < 16; ++j) { + DC += dst[-1 + j * BPS]; + } + Put16_SSE2(DC >> 4, dst); +} + +static void DC16NoLeft_SSE2(uint8_t* dst) { // DC with left samples unavailable + const __m128i zero = _mm_setzero_si128(); + const __m128i top = _mm_loadu_si128((const __m128i*)(dst - BPS)); + const __m128i sad8x2 = _mm_sad_epu8(top, zero); + // sum the two sads: sad8x2[0:1] + sad8x2[8:9] + const __m128i sum = _mm_add_epi16(sad8x2, _mm_shuffle_epi32(sad8x2, 2)); + const int DC = _mm_cvtsi128_si32(sum) + 8; + Put16_SSE2(DC >> 4, dst); +} + +static void DC16NoTopLeft_SSE2(uint8_t* dst) { // DC with no top & left samples + Put16_SSE2(0x80, dst); +} + +//------------------------------------------------------------------------------ +// Chroma + +static void VE8uv_SSE2(uint8_t* dst) { // vertical + int j; + const __m128i top = _mm_loadl_epi64((const __m128i*)(dst - BPS)); + for (j = 0; j < 8; ++j) { + _mm_storel_epi64((__m128i*)(dst + j * BPS), top); + } +} + +// helper for chroma-DC predictions +static WEBP_INLINE void Put8x8uv_SSE2(uint8_t v, uint8_t* dst) { + int j; + const __m128i values = _mm_set1_epi8((char)v); + for (j = 0; j < 8; ++j) { + _mm_storel_epi64((__m128i*)(dst + j * BPS), values); + } +} + +static void DC8uv_SSE2(uint8_t* dst) { // DC + const __m128i zero = _mm_setzero_si128(); + const __m128i top = _mm_loadl_epi64((const __m128i*)(dst - BPS)); + const __m128i sum = _mm_sad_epu8(top, zero); + int left = 0; + int j; + for (j = 0; j < 8; ++j) { + left += dst[-1 + j * BPS]; + } + { + const int DC = _mm_cvtsi128_si32(sum) + left + 8; + Put8x8uv_SSE2(DC >> 4, dst); + } +} + +static void DC8uvNoLeft_SSE2(uint8_t* dst) { // DC with no left samples + const __m128i zero = _mm_setzero_si128(); + const __m128i top = _mm_loadl_epi64((const __m128i*)(dst - BPS)); + const __m128i sum = _mm_sad_epu8(top, zero); + const int DC = _mm_cvtsi128_si32(sum) + 4; + Put8x8uv_SSE2(DC >> 3, dst); +} + +static void DC8uvNoTop_SSE2(uint8_t* dst) { // DC with no top samples + int dc0 = 4; + int i; + for (i = 0; i < 8; ++i) { + dc0 += dst[-1 + i * BPS]; + } + Put8x8uv_SSE2(dc0 >> 3, dst); +} + +static void DC8uvNoTopLeft_SSE2(uint8_t* dst) { // DC with nothing + Put8x8uv_SSE2(0x80, dst); +} + +//------------------------------------------------------------------------------ +// Entry point + +extern void VP8DspInitSSE2(void); + +WEBP_TSAN_IGNORE_FUNCTION void VP8DspInitSSE2(void) { + VP8Transform = Transform_SSE2; +#if (USE_TRANSFORM_AC3 == 1) + VP8TransformAC3 = TransformAC3_SSE2; +#endif + + VP8VFilter16 = VFilter16_SSE2; + VP8HFilter16 = HFilter16_SSE2; + VP8VFilter8 = VFilter8_SSE2; + VP8HFilter8 = HFilter8_SSE2; + VP8VFilter16i = VFilter16i_SSE2; + VP8HFilter16i = HFilter16i_SSE2; + VP8VFilter8i = VFilter8i_SSE2; + VP8HFilter8i = HFilter8i_SSE2; + + VP8SimpleVFilter16 = SimpleVFilter16_SSE2; + VP8SimpleHFilter16 = SimpleHFilter16_SSE2; + VP8SimpleVFilter16i = SimpleVFilter16i_SSE2; + VP8SimpleHFilter16i = SimpleHFilter16i_SSE2; + + VP8PredLuma4[1] = TM4_SSE2; + VP8PredLuma4[2] = VE4_SSE2; + VP8PredLuma4[4] = RD4_SSE2; + VP8PredLuma4[5] = VR4_SSE2; + VP8PredLuma4[6] = LD4_SSE2; + VP8PredLuma4[7] = VL4_SSE2; + + VP8PredLuma16[0] = DC16_SSE2; + VP8PredLuma16[1] = TM16_SSE2; + VP8PredLuma16[2] = VE16_SSE2; + VP8PredLuma16[3] = HE16_SSE2; + VP8PredLuma16[4] = DC16NoTop_SSE2; + VP8PredLuma16[5] = DC16NoLeft_SSE2; + VP8PredLuma16[6] = DC16NoTopLeft_SSE2; + + VP8PredChroma8[0] = DC8uv_SSE2; + VP8PredChroma8[1] = TM8uv_SSE2; + VP8PredChroma8[2] = VE8uv_SSE2; + VP8PredChroma8[4] = DC8uvNoTop_SSE2; + VP8PredChroma8[5] = DC8uvNoLeft_SSE2; + VP8PredChroma8[6] = DC8uvNoTopLeft_SSE2; +} + +#else // !WEBP_USE_SSE2 + +WEBP_DSP_INIT_STUB(VP8DspInitSSE2) + +#endif // WEBP_USE_SSE2 diff --git a/packages/core/src/zig/vendor/libwebp/src/dsp/dec_sse41.c b/packages/core/src/zig/vendor/libwebp/src/dsp/dec_sse41.c new file mode 100644 index 0000000000..4f514b9b38 --- /dev/null +++ b/packages/core/src/zig/vendor/libwebp/src/dsp/dec_sse41.c @@ -0,0 +1,49 @@ +// Copyright 2015 Google Inc. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the COPYING file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +// ----------------------------------------------------------------------------- +// +// SSE4 version of some decoding functions. +// +// Author: Skal (pascal.massimino@gmail.com) + +#include "src/dsp/dsp.h" + +#if defined(WEBP_USE_SSE41) +#include +#include + +#include "src/webp/types.h" +#include "src/dec/vp8i_dec.h" +#include "src/dsp/cpu.h" +#include "src/utils/utils.h" + +static void HE16_SSE41(uint8_t* dst) { // horizontal + int j; + const __m128i kShuffle3 = _mm_set1_epi8(3); + for (j = 16; j > 0; --j) { + const __m128i in = _mm_cvtsi32_si128(WebPMemToInt32(dst - 4)); + const __m128i values = _mm_shuffle_epi8(in, kShuffle3); + _mm_storeu_si128((__m128i*)dst, values); + dst += BPS; + } +} + +//------------------------------------------------------------------------------ +// Entry point + +extern void VP8DspInitSSE41(void); + +WEBP_TSAN_IGNORE_FUNCTION void VP8DspInitSSE41(void) { + VP8PredLuma16[3] = HE16_SSE41; +} + +#else // !WEBP_USE_SSE41 + +WEBP_DSP_INIT_STUB(VP8DspInitSSE41) + +#endif // WEBP_USE_SSE41 diff --git a/packages/core/src/zig/vendor/libwebp/src/dsp/dsp.h b/packages/core/src/zig/vendor/libwebp/src/dsp/dsp.h new file mode 100644 index 0000000000..1b37ef4b90 --- /dev/null +++ b/packages/core/src/zig/vendor/libwebp/src/dsp/dsp.h @@ -0,0 +1,544 @@ +// Copyright 2011 Google Inc. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the COPYING file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +// ----------------------------------------------------------------------------- +// +// Speed-critical functions. +// +// Author: Skal (pascal.massimino@gmail.com) + +#ifndef WEBP_DSP_DSP_H_ +#define WEBP_DSP_DSP_H_ + +#ifdef HAVE_CONFIG_H +#include "src/webp/config.h" +#endif + +#include "src/dsp/cpu.h" +#include "src/webp/types.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#define BPS 32 // this is the common stride for enc/dec + +//------------------------------------------------------------------------------ +// WEBP_RESTRICT + +// Declares a pointer with the restrict type qualifier if available. +// This allows code to hint to the compiler that only this pointer references a +// particular object or memory region within the scope of the block in which it +// is declared. This may allow for improved optimizations due to the lack of +// pointer aliasing. See also: +// https://en.cppreference.com/w/c/language/restrict +#if defined(__GNUC__) +#define WEBP_RESTRICT __restrict__ +#elif defined(_MSC_VER) +#define WEBP_RESTRICT __restrict +#else +#define WEBP_RESTRICT +#endif + + +//------------------------------------------------------------------------------ +// Init stub generator + +// Defines an init function stub to ensure each module exposes a symbol, +// avoiding a compiler warning. +#define WEBP_DSP_INIT_STUB(func) \ + extern void func(void); \ + void func(void) {} + +//------------------------------------------------------------------------------ +// Encoding + +// Transforms +// VP8Idct: Does one of two inverse transforms. If do_two is set, the transforms +// will be done for (ref, in, dst) and (ref + 4, in + 16, dst + 4). +typedef void (*VP8Idct)(const uint8_t* WEBP_RESTRICT ref, + const int16_t* WEBP_RESTRICT in, + uint8_t* WEBP_RESTRICT dst, int do_two); +typedef void (*VP8Fdct)(const uint8_t* WEBP_RESTRICT src, + const uint8_t* WEBP_RESTRICT ref, + int16_t* WEBP_RESTRICT out); +typedef void (*VP8WHT)(const int16_t* WEBP_RESTRICT in, + int16_t* WEBP_RESTRICT out); +extern VP8Idct VP8ITransform; +extern VP8Fdct VP8FTransform; +extern VP8Fdct VP8FTransform2; // performs two transforms at a time +extern VP8WHT VP8FTransformWHT; +// Predictions +// *dst is the destination block. *top and *left can be NULL. +typedef void (*VP8IntraPreds)(uint8_t* WEBP_RESTRICT dst, + const uint8_t* WEBP_RESTRICT left, + const uint8_t* WEBP_RESTRICT top); +typedef void (*VP8Intra4Preds)(uint8_t* WEBP_RESTRICT dst, + const uint8_t* WEBP_RESTRICT top); +extern VP8Intra4Preds VP8EncPredLuma4; +extern VP8IntraPreds VP8EncPredLuma16; +extern VP8IntraPreds VP8EncPredChroma8; + +typedef int (*VP8Metric)(const uint8_t* WEBP_RESTRICT pix, + const uint8_t* WEBP_RESTRICT ref); +extern VP8Metric VP8SSE16x16, VP8SSE16x8, VP8SSE8x8, VP8SSE4x4; +typedef int (*VP8WMetric)(const uint8_t* WEBP_RESTRICT pix, + const uint8_t* WEBP_RESTRICT ref, + const uint16_t* WEBP_RESTRICT const weights); +// The weights for VP8TDisto4x4 and VP8TDisto16x16 contain a row-major +// 4 by 4 symmetric matrix. +extern VP8WMetric VP8TDisto4x4, VP8TDisto16x16; + +// Compute the average (DC) of four 4x4 blocks. +// Each sub-4x4 block #i sum is stored in dc[i]. +typedef void (*VP8MeanMetric)(const uint8_t* WEBP_RESTRICT ref, + uint32_t dc[4]); +extern VP8MeanMetric VP8Mean16x4; + +typedef void (*VP8BlockCopy)(const uint8_t* WEBP_RESTRICT src, + uint8_t* WEBP_RESTRICT dst); +extern VP8BlockCopy VP8Copy4x4; +extern VP8BlockCopy VP8Copy16x8; +// Quantization +struct VP8Matrix; // forward declaration +typedef int (*VP8QuantizeBlock)( + int16_t in[16], int16_t out[16], + const struct VP8Matrix* WEBP_RESTRICT const mtx); +// Same as VP8QuantizeBlock, but quantizes two consecutive blocks. +typedef int (*VP8Quantize2Blocks)( + int16_t in[32], int16_t out[32], + const struct VP8Matrix* WEBP_RESTRICT const mtx); + +extern VP8QuantizeBlock VP8EncQuantizeBlock; +extern VP8Quantize2Blocks VP8EncQuantize2Blocks; + +// specific to 2nd transform: +typedef int (*VP8QuantizeBlockWHT)( + int16_t in[16], int16_t out[16], + const struct VP8Matrix* WEBP_RESTRICT const mtx); +extern VP8QuantizeBlockWHT VP8EncQuantizeBlockWHT; + +extern const int VP8DspScan[16 + 4 + 4]; + +// Collect histogram for susceptibility calculation. +#define MAX_COEFF_THRESH 31 // size of histogram used by CollectHistogram. +typedef struct { + // We only need to store max_value and last_non_zero, not the distribution. + int max_value; + int last_non_zero; +} VP8Histogram; +typedef void (*VP8CHisto)(const uint8_t* WEBP_RESTRICT ref, + const uint8_t* WEBP_RESTRICT pred, + int start_block, int end_block, + VP8Histogram* WEBP_RESTRICT const histo); +extern VP8CHisto VP8CollectHistogram; +// General-purpose util function to help VP8CollectHistogram(). +void VP8SetHistogramData(const int distribution[MAX_COEFF_THRESH + 1], + VP8Histogram* const histo); + +// must be called before using any of the above +void VP8EncDspInit(void); + +//------------------------------------------------------------------------------ +// cost functions (encoding) + +extern const uint16_t VP8EntropyCost[256]; // 8bit fixed-point log(p) +// approximate cost per level: +extern const uint16_t VP8LevelFixedCosts[2047 /*MAX_LEVEL*/ + 1]; +extern const uint8_t VP8EncBands[16 + 1]; + +struct VP8Residual; +typedef void (*VP8SetResidualCoeffsFunc)( + const int16_t* WEBP_RESTRICT const coeffs, + struct VP8Residual* WEBP_RESTRICT const res); +extern VP8SetResidualCoeffsFunc VP8SetResidualCoeffs; + +// Cost calculation function. +typedef int (*VP8GetResidualCostFunc)(int ctx0, + const struct VP8Residual* const res); +extern VP8GetResidualCostFunc VP8GetResidualCost; + +// must be called before anything using the above +void VP8EncDspCostInit(void); + +//------------------------------------------------------------------------------ +// SSIM / PSNR utils + +// struct for accumulating statistical moments +typedef struct { + uint32_t w; // sum(w_i) : sum of weights + uint32_t xm, ym; // sum(w_i * x_i), sum(w_i * y_i) + uint32_t xxm, xym, yym; // sum(w_i * x_i * x_i), etc. +} VP8DistoStats; + +// Compute the final SSIM value +// The non-clipped version assumes stats->w = (2 * VP8_SSIM_KERNEL + 1)^2. +double VP8SSIMFromStats(const VP8DistoStats* const stats); +double VP8SSIMFromStatsClipped(const VP8DistoStats* const stats); + +#define VP8_SSIM_KERNEL 3 // total size of the kernel: 2 * VP8_SSIM_KERNEL + 1 +typedef double (*VP8SSIMGetClippedFunc)(const uint8_t* src1, int stride1, + const uint8_t* src2, int stride2, + int xo, int yo, // center position + int W, int H); // plane dimension + +#if !defined(WEBP_REDUCE_SIZE) +// This version is called with the guarantee that you can load 8 bytes and +// 8 rows at offset src1 and src2 +typedef double (*VP8SSIMGetFunc)(const uint8_t* src1, int stride1, + const uint8_t* src2, int stride2); + +extern VP8SSIMGetFunc VP8SSIMGet; // unclipped / unchecked +extern VP8SSIMGetClippedFunc VP8SSIMGetClipped; // with clipping +#endif + +#if !defined(WEBP_DISABLE_STATS) +typedef uint32_t (*VP8AccumulateSSEFunc)(const uint8_t* src1, + const uint8_t* src2, int len); +extern VP8AccumulateSSEFunc VP8AccumulateSSE; +#endif + +// must be called before using any of the above directly +void VP8SSIMDspInit(void); + +//------------------------------------------------------------------------------ +// Decoding + +typedef void (*VP8DecIdct)(const int16_t* WEBP_RESTRICT coeffs, + uint8_t* WEBP_RESTRICT dst); +// when doing two transforms, coeffs is actually int16_t[2][16]. +typedef void (*VP8DecIdct2)(const int16_t* WEBP_RESTRICT coeffs, + uint8_t* WEBP_RESTRICT dst, int do_two); +extern VP8DecIdct2 VP8Transform; +extern VP8DecIdct VP8TransformAC3; +extern VP8DecIdct VP8TransformUV; +extern VP8DecIdct VP8TransformDC; +extern VP8DecIdct VP8TransformDCUV; +extern VP8WHT VP8TransformWHT; + +#define WEBP_TRANSFORM_AC3_C1 20091 +#define WEBP_TRANSFORM_AC3_C2 35468 +#define WEBP_TRANSFORM_AC3_MUL1(a) ((((a) * WEBP_TRANSFORM_AC3_C1) >> 16) + (a)) +#define WEBP_TRANSFORM_AC3_MUL2(a) (((a) * WEBP_TRANSFORM_AC3_C2) >> 16) + +// *dst is the destination block, with stride BPS. Boundary samples are +// assumed accessible when needed. +typedef void (*VP8PredFunc)(uint8_t* dst); +extern VP8PredFunc VP8PredLuma16[/* NUM_B_DC_MODES */]; +extern VP8PredFunc VP8PredChroma8[/* NUM_B_DC_MODES */]; +extern VP8PredFunc VP8PredLuma4[/* NUM_BMODES */]; + +// clipping tables (for filtering) +extern const int8_t* const VP8ksclip1; // clips [-1020, 1020] to [-128, 127] +extern const int8_t* const VP8ksclip2; // clips [-112, 112] to [-16, 15] +extern const uint8_t* const VP8kclip1; // clips [-255,511] to [0,255] +extern const uint8_t* const VP8kabs0; // abs(x) for x in [-255,255] +// must be called first +void VP8InitClipTables(void); + +// simple filter (only for luma) +typedef void (*VP8SimpleFilterFunc)(uint8_t* p, int stride, int thresh); +extern VP8SimpleFilterFunc VP8SimpleVFilter16; +extern VP8SimpleFilterFunc VP8SimpleHFilter16; +extern VP8SimpleFilterFunc VP8SimpleVFilter16i; // filter 3 inner edges +extern VP8SimpleFilterFunc VP8SimpleHFilter16i; + +// regular filter (on both macroblock edges and inner edges) +typedef void (*VP8LumaFilterFunc)(uint8_t* luma, int stride, + int thresh, int ithresh, int hev_t); +typedef void (*VP8ChromaFilterFunc)(uint8_t* WEBP_RESTRICT u, + uint8_t* WEBP_RESTRICT v, int stride, + int thresh, int ithresh, int hev_t); +// on outer edge +extern VP8LumaFilterFunc VP8VFilter16; +extern VP8LumaFilterFunc VP8HFilter16; +extern VP8ChromaFilterFunc VP8VFilter8; +extern VP8ChromaFilterFunc VP8HFilter8; + +// on inner edge +extern VP8LumaFilterFunc VP8VFilter16i; // filtering 3 inner edges altogether +extern VP8LumaFilterFunc VP8HFilter16i; +extern VP8ChromaFilterFunc VP8VFilter8i; // filtering u and v altogether +extern VP8ChromaFilterFunc VP8HFilter8i; + +// Dithering. Combines dithering values (centered around 128) with dst[], +// according to: dst[] = clip(dst[] + (((dither[]-128) + 8) >> 4) +#define VP8_DITHER_DESCALE 4 +#define VP8_DITHER_DESCALE_ROUNDER (1 << (VP8_DITHER_DESCALE - 1)) +#define VP8_DITHER_AMP_BITS 7 +#define VP8_DITHER_AMP_CENTER (1 << VP8_DITHER_AMP_BITS) +extern void (*VP8DitherCombine8x8)(const uint8_t* WEBP_RESTRICT dither, + uint8_t* WEBP_RESTRICT dst, int dst_stride); + +// must be called before anything using the above +void VP8DspInit(void); + +//------------------------------------------------------------------------------ +// WebP I/O + +#define FANCY_UPSAMPLING // undefined to remove fancy upsampling support + +// Convert a pair of y/u/v lines together to the output rgb/a colorspace. +// bottom_y can be NULL if only one line of output is needed (at top/bottom). +typedef void (*WebPUpsampleLinePairFunc)( + const uint8_t* WEBP_RESTRICT top_y, const uint8_t* WEBP_RESTRICT bottom_y, + const uint8_t* WEBP_RESTRICT top_u, const uint8_t* WEBP_RESTRICT top_v, + const uint8_t* WEBP_RESTRICT cur_u, const uint8_t* WEBP_RESTRICT cur_v, + uint8_t* WEBP_RESTRICT top_dst, uint8_t* WEBP_RESTRICT bottom_dst, int len); + +#ifdef FANCY_UPSAMPLING + +// Fancy upsampling functions to convert YUV to RGB(A) modes +extern WebPUpsampleLinePairFunc WebPUpsamplers[/* MODE_LAST */]; + +#endif // FANCY_UPSAMPLING + +// Per-row point-sampling methods. +typedef void (*WebPSamplerRowFunc)(const uint8_t* WEBP_RESTRICT y, + const uint8_t* WEBP_RESTRICT u, + const uint8_t* WEBP_RESTRICT v, + uint8_t* WEBP_RESTRICT dst, int len); +// Generic function to apply 'WebPSamplerRowFunc' to the whole plane: +void WebPSamplerProcessPlane(const uint8_t* WEBP_RESTRICT y, int y_stride, + const uint8_t* WEBP_RESTRICT u, + const uint8_t* WEBP_RESTRICT v, int uv_stride, + uint8_t* WEBP_RESTRICT dst, int dst_stride, + int width, int height, WebPSamplerRowFunc func); + +// Sampling functions to convert rows of YUV to RGB(A) +extern WebPSamplerRowFunc WebPSamplers[/* MODE_LAST */]; + +// General function for converting two lines of ARGB or RGBA. +// 'alpha_is_last' should be true if 0xff000000 is stored in memory as +// as 0x00, 0x00, 0x00, 0xff (little endian). +WebPUpsampleLinePairFunc WebPGetLinePairConverter(int alpha_is_last); + +// YUV444->RGB converters +typedef void (*WebPYUV444Converter)(const uint8_t* WEBP_RESTRICT y, + const uint8_t* WEBP_RESTRICT u, + const uint8_t* WEBP_RESTRICT v, + uint8_t* WEBP_RESTRICT dst, int len); + +extern WebPYUV444Converter WebPYUV444Converters[/* MODE_LAST */]; + +// Must be called before using the WebPUpsamplers[] (and for premultiplied +// colorspaces like rgbA, rgbA4444, etc) +void WebPInitUpsamplers(void); +// Must be called before using WebPSamplers[] +void WebPInitSamplers(void); +// Must be called before using WebPYUV444Converters[] +void WebPInitYUV444Converters(void); + +//------------------------------------------------------------------------------ +// ARGB -> YUV converters + +// Convert ARGB samples to luma Y. +extern void (*WebPConvertARGBToY)(const uint32_t* WEBP_RESTRICT argb, + uint8_t* WEBP_RESTRICT y, int width); +// Convert ARGB samples to U/V with downsampling. do_store should be '1' for +// even lines and '0' for odd ones. 'src_width' is the original width, not +// the U/V one. +extern void (*WebPConvertARGBToUV)(const uint32_t* WEBP_RESTRICT argb, + uint8_t* WEBP_RESTRICT u, + uint8_t* WEBP_RESTRICT v, + int src_width, int do_store); + +// Convert a row of accumulated (four-values) of rgba32 toward U/V +extern void (*WebPConvertRGBA32ToUV)(const uint16_t* WEBP_RESTRICT rgb, + uint8_t* WEBP_RESTRICT u, + uint8_t* WEBP_RESTRICT v, int width); + +// Convert RGB or BGR to Y +extern void (*WebPConvertRGB24ToY)(const uint8_t* WEBP_RESTRICT rgb, + uint8_t* WEBP_RESTRICT y, int width); +extern void (*WebPConvertBGR24ToY)(const uint8_t* WEBP_RESTRICT bgr, + uint8_t* WEBP_RESTRICT y, int width); + +// used for plain-C fallback. +extern void WebPConvertARGBToUV_C(const uint32_t* WEBP_RESTRICT argb, + uint8_t* WEBP_RESTRICT u, + uint8_t* WEBP_RESTRICT v, + int src_width, int do_store); +extern void WebPConvertRGBA32ToUV_C(const uint16_t* WEBP_RESTRICT rgb, + uint8_t* WEBP_RESTRICT u, + uint8_t* WEBP_RESTRICT v, int width); + +// Must be called before using the above. +void WebPInitConvertARGBToYUV(void); + +//------------------------------------------------------------------------------ +// Rescaler + +struct WebPRescaler; + +// Import a row of data and save its contribution in the rescaler. +// 'channel' denotes the channel number to be imported. 'Expand' corresponds to +// the wrk->x_expand case. Otherwise, 'Shrink' is to be used. +typedef void (*WebPRescalerImportRowFunc)( + struct WebPRescaler* WEBP_RESTRICT const wrk, + const uint8_t* WEBP_RESTRICT src); + +extern WebPRescalerImportRowFunc WebPRescalerImportRowExpand; +extern WebPRescalerImportRowFunc WebPRescalerImportRowShrink; + +// Export one row (starting at x_out position) from rescaler. +// 'Expand' corresponds to the wrk->y_expand case. +// Otherwise 'Shrink' is to be used +typedef void (*WebPRescalerExportRowFunc)(struct WebPRescaler* const wrk); +extern WebPRescalerExportRowFunc WebPRescalerExportRowExpand; +extern WebPRescalerExportRowFunc WebPRescalerExportRowShrink; + +// Plain-C implementation, as fall-back. +extern void WebPRescalerImportRowExpand_C( + struct WebPRescaler* WEBP_RESTRICT const wrk, + const uint8_t* WEBP_RESTRICT src); +extern void WebPRescalerImportRowShrink_C( + struct WebPRescaler* WEBP_RESTRICT const wrk, + const uint8_t* WEBP_RESTRICT src); +extern void WebPRescalerExportRowExpand_C(struct WebPRescaler* const wrk); +extern void WebPRescalerExportRowShrink_C(struct WebPRescaler* const wrk); + +// Main entry calls: +extern void WebPRescalerImportRow( + struct WebPRescaler* WEBP_RESTRICT const wrk, + const uint8_t* WEBP_RESTRICT src); +// Export one row (starting at x_out position) from rescaler. +extern void WebPRescalerExportRow(struct WebPRescaler* const wrk); + +// Must be called first before using the above. +void WebPRescalerDspInit(void); + +//------------------------------------------------------------------------------ +// Utilities for processing transparent channel. + +// Apply alpha pre-multiply on an rgba, bgra or argb plane of size w * h. +// alpha_first should be 0 for argb, 1 for rgba or bgra (where alpha is last). +extern void (*WebPApplyAlphaMultiply)( + uint8_t* rgba, int alpha_first, int w, int h, int stride); + +// Same, buf specifically for RGBA4444 format +extern void (*WebPApplyAlphaMultiply4444)( + uint8_t* rgba4444, int w, int h, int stride); + +// Dispatch the values from alpha[] plane to the ARGB destination 'dst'. +// Returns true if alpha[] plane has non-trivial values different from 0xff. +extern int (*WebPDispatchAlpha)(const uint8_t* WEBP_RESTRICT alpha, + int alpha_stride, int width, int height, + uint8_t* WEBP_RESTRICT dst, int dst_stride); + +// Transfer packed 8b alpha[] values to green channel in dst[], zero'ing the +// A/R/B values. 'dst_stride' is the stride for dst[] in uint32_t units. +extern void (*WebPDispatchAlphaToGreen)(const uint8_t* WEBP_RESTRICT alpha, + int alpha_stride, int width, int height, + uint32_t* WEBP_RESTRICT dst, + int dst_stride); + +// Extract the alpha values from 32b values in argb[] and pack them into alpha[] +// (this is the opposite of WebPDispatchAlpha). +// Returns true if there's only trivial 0xff alpha values. +extern int (*WebPExtractAlpha)(const uint8_t* WEBP_RESTRICT argb, + int argb_stride, int width, int height, + uint8_t* WEBP_RESTRICT alpha, + int alpha_stride); + +// Extract the green values from 32b values in argb[] and pack them into alpha[] +// (this is the opposite of WebPDispatchAlphaToGreen). +extern void (*WebPExtractGreen)(const uint32_t* WEBP_RESTRICT argb, + uint8_t* WEBP_RESTRICT alpha, int size); + +// Pre-Multiply operation transforms x into x * A / 255 (where x=Y,R,G or B). +// Un-Multiply operation transforms x into x * 255 / A. + +// Pre-Multiply or Un-Multiply (if 'inverse' is true) argb values in a row. +extern void (*WebPMultARGBRow)(uint32_t* const ptr, int width, int inverse); + +// Same a WebPMultARGBRow(), but for several rows. +void WebPMultARGBRows(uint8_t* ptr, int stride, int width, int num_rows, + int inverse); + +// Same for a row of single values, with side alpha values. +extern void (*WebPMultRow)(uint8_t* WEBP_RESTRICT const ptr, + const uint8_t* WEBP_RESTRICT const alpha, + int width, int inverse); + +// Same a WebPMultRow(), but for several 'num_rows' rows. +void WebPMultRows(uint8_t* WEBP_RESTRICT ptr, int stride, + const uint8_t* WEBP_RESTRICT alpha, int alpha_stride, + int width, int num_rows, int inverse); + +// Plain-C versions, used as fallback by some implementations. +void WebPMultRow_C(uint8_t* WEBP_RESTRICT const ptr, + const uint8_t* WEBP_RESTRICT const alpha, + int width, int inverse); +void WebPMultARGBRow_C(uint32_t* const ptr, int width, int inverse); + +#ifdef WORDS_BIGENDIAN +// ARGB packing function: a/r/g/b input is rgba or bgra order. +extern void (*WebPPackARGB)(const uint8_t* WEBP_RESTRICT a, + const uint8_t* WEBP_RESTRICT r, + const uint8_t* WEBP_RESTRICT g, + const uint8_t* WEBP_RESTRICT b, + int len, uint32_t* WEBP_RESTRICT out); +#endif + +// RGB packing function. 'step' can be 3 or 4. r/g/b input is rgb or bgr order. +extern void (*WebPPackRGB)(const uint8_t* WEBP_RESTRICT r, + const uint8_t* WEBP_RESTRICT g, + const uint8_t* WEBP_RESTRICT b, + int len, int step, uint32_t* WEBP_RESTRICT out); + +// This function returns true if src[i] contains a value different from 0xff. +extern int (*WebPHasAlpha8b)(const uint8_t* src, int length); +// This function returns true if src[4*i] contains a value different from 0xff. +extern int (*WebPHasAlpha32b)(const uint8_t* src, int length); +// replaces transparent values in src[] by 'color'. +extern void (*WebPAlphaReplace)(uint32_t* src, int length, uint32_t color); + +// To be called first before using the above. +void WebPInitAlphaProcessing(void); + +//------------------------------------------------------------------------------ +// Filter functions + +typedef enum { // Filter types. + WEBP_FILTER_NONE = 0, + WEBP_FILTER_HORIZONTAL, + WEBP_FILTER_VERTICAL, + WEBP_FILTER_GRADIENT, + WEBP_FILTER_LAST = WEBP_FILTER_GRADIENT + 1, // end marker + WEBP_FILTER_BEST, // meta-types + WEBP_FILTER_FAST +} WEBP_FILTER_TYPE; + +typedef void (*WebPFilterFunc)(const uint8_t* WEBP_RESTRICT in, + int width, int height, int stride, + uint8_t* WEBP_RESTRICT out); +// In-place un-filtering. +// Warning! 'prev_line' pointer can be equal to 'cur_line' or 'preds'. +typedef void (*WebPUnfilterFunc)(const uint8_t* prev_line, const uint8_t* preds, + uint8_t* cur_line, int width); + +// Filter the given data using the given predictor. +// 'in' corresponds to a 2-dimensional pixel array of size (stride * height) +// in raster order. +// 'stride' is number of bytes per scan line (with possible padding). +// 'out' should be pre-allocated. +extern WebPFilterFunc WebPFilters[WEBP_FILTER_LAST]; + +// In-place reconstruct the original data from the given filtered data. +// The reconstruction will be done for 'num_rows' rows starting from 'row' +// (assuming rows upto 'row - 1' are already reconstructed). +extern WebPUnfilterFunc WebPUnfilters[WEBP_FILTER_LAST]; + +// To be called first before using the above. +void VP8FiltersInit(void); + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif // WEBP_DSP_DSP_H_ diff --git a/packages/core/src/zig/vendor/libwebp/src/dsp/filters.c b/packages/core/src/zig/vendor/libwebp/src/dsp/filters.c new file mode 100644 index 0000000000..38da5252df --- /dev/null +++ b/packages/core/src/zig/vendor/libwebp/src/dsp/filters.c @@ -0,0 +1,268 @@ +// Copyright 2011 Google Inc. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the COPYING file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +// ----------------------------------------------------------------------------- +// +// Spatial prediction using various filters +// +// Author: Urvang (urvang@google.com) + +#include +#include +#include + +#include "src/dsp/cpu.h" +#include "src/dsp/dsp.h" +#include "src/webp/types.h" + +//------------------------------------------------------------------------------ +// Helpful macro. + +#define DCHECK(in, out) \ + do { \ + assert((in) != NULL); \ + assert((out) != NULL); \ + assert((in) != (out)); \ + assert(width > 0); \ + assert(height > 0); \ + assert(stride >= width); \ + } while (0) + +#if !WEBP_NEON_OMIT_C_CODE +static WEBP_INLINE void PredictLine_C(const uint8_t* WEBP_RESTRICT src, + const uint8_t* WEBP_RESTRICT pred, + uint8_t* WEBP_RESTRICT dst, int length) { + int i; + for (i = 0; i < length; ++i) dst[i] = (uint8_t)(src[i] - pred[i]); +} + +//------------------------------------------------------------------------------ +// Horizontal filter. + +static WEBP_INLINE void DoHorizontalFilter_C(const uint8_t* WEBP_RESTRICT in, + int width, int height, int stride, + uint8_t* WEBP_RESTRICT out) { + const uint8_t* preds = in; + int row; + DCHECK(in, out); + + // Leftmost pixel is the same as input for topmost scanline. + out[0] = in[0]; + PredictLine_C(in + 1, preds, out + 1, width - 1); + preds += stride; + in += stride; + out += stride; + + // Filter line-by-line. + for (row = 1; row < height; ++row) { + // Leftmost pixel is predicted from above. + PredictLine_C(in, preds - stride, out, 1); + PredictLine_C(in + 1, preds, out + 1, width - 1); + preds += stride; + in += stride; + out += stride; + } +} + +//------------------------------------------------------------------------------ +// Vertical filter. + +static WEBP_INLINE void DoVerticalFilter_C(const uint8_t* WEBP_RESTRICT in, + int width, int height, int stride, + uint8_t* WEBP_RESTRICT out) { + const uint8_t* preds = in; + int row; + DCHECK(in, out); + + // Very first top-left pixel is copied. + out[0] = in[0]; + // Rest of top scan-line is left-predicted. + PredictLine_C(in + 1, preds, out + 1, width - 1); + in += stride; + out += stride; + + // Filter line-by-line. + for (row = 1; row < height; ++row) { + PredictLine_C(in, preds, out, width); + preds += stride; + in += stride; + out += stride; + } +} +#endif // !WEBP_NEON_OMIT_C_CODE + +//------------------------------------------------------------------------------ +// Gradient filter. + +static WEBP_INLINE int GradientPredictor_C(uint8_t a, uint8_t b, uint8_t c) { + const int g = a + b - c; + return ((g & ~0xff) == 0) ? g : (g < 0) ? 0 : 255; // clip to 8bit +} + +#if !WEBP_NEON_OMIT_C_CODE +static WEBP_INLINE void DoGradientFilter_C(const uint8_t* WEBP_RESTRICT in, + int width, int height, int stride, + uint8_t* WEBP_RESTRICT out) { + const uint8_t* preds = in; + int row; + DCHECK(in, out); + + // left prediction for top scan-line + out[0] = in[0]; + PredictLine_C(in + 1, preds, out + 1, width - 1); + preds += stride; + in += stride; + out += stride; + + // Filter line-by-line. + for (row = 1; row < height; ++row) { + int w; + // leftmost pixel: predict from above. + PredictLine_C(in, preds - stride, out, 1); + for (w = 1; w < width; ++w) { + const int pred = GradientPredictor_C(preds[w - 1], + preds[w - stride], + preds[w - stride - 1]); + out[w] = (uint8_t)(in[w] - pred); + } + preds += stride; + in += stride; + out += stride; + } +} +#endif // !WEBP_NEON_OMIT_C_CODE + +#undef DCHECK + +//------------------------------------------------------------------------------ + +#if !WEBP_NEON_OMIT_C_CODE +static void HorizontalFilter_C(const uint8_t* WEBP_RESTRICT data, + int width, int height, int stride, + uint8_t* WEBP_RESTRICT filtered_data) { + DoHorizontalFilter_C(data, width, height, stride, filtered_data); +} + +static void VerticalFilter_C(const uint8_t* WEBP_RESTRICT data, + int width, int height, int stride, + uint8_t* WEBP_RESTRICT filtered_data) { + DoVerticalFilter_C(data, width, height, stride, filtered_data); +} + +static void GradientFilter_C(const uint8_t* WEBP_RESTRICT data, + int width, int height, int stride, + uint8_t* WEBP_RESTRICT filtered_data) { + DoGradientFilter_C(data, width, height, stride, filtered_data); +} +#endif // !WEBP_NEON_OMIT_C_CODE + +//------------------------------------------------------------------------------ + +static void NoneUnfilter_C(const uint8_t* prev, const uint8_t* in, + uint8_t* out, int width) { + (void)prev; + if (out != in) memcpy(out, in, width * sizeof(*out)); +} + +static void HorizontalUnfilter_C(const uint8_t* prev, const uint8_t* in, + uint8_t* out, int width) { + uint8_t pred = (prev == NULL) ? 0 : prev[0]; + int i; + for (i = 0; i < width; ++i) { + out[i] = (uint8_t)(pred + in[i]); + pred = out[i]; + } +} + +#if !WEBP_NEON_OMIT_C_CODE +static void VerticalUnfilter_C(const uint8_t* prev, const uint8_t* in, + uint8_t* out, int width) { + if (prev == NULL) { + HorizontalUnfilter_C(NULL, in, out, width); + } else { + int i; + for (i = 0; i < width; ++i) out[i] = (uint8_t)(prev[i] + in[i]); + } +} +#endif // !WEBP_NEON_OMIT_C_CODE + +static void GradientUnfilter_C(const uint8_t* prev, const uint8_t* in, + uint8_t* out, int width) { + if (prev == NULL) { + HorizontalUnfilter_C(NULL, in, out, width); + } else { + uint8_t top = prev[0], top_left = top, left = top; + int i; + for (i = 0; i < width; ++i) { + top = prev[i]; // need to read this first, in case prev==out + left = (uint8_t)(in[i] + GradientPredictor_C(left, top, top_left)); + top_left = top; + out[i] = left; + } + } +} + +//------------------------------------------------------------------------------ +// Init function + +WebPFilterFunc WebPFilters[WEBP_FILTER_LAST]; +WebPUnfilterFunc WebPUnfilters[WEBP_FILTER_LAST]; + +extern VP8CPUInfo VP8GetCPUInfo; +extern void VP8FiltersInitMIPSdspR2(void); +extern void VP8FiltersInitMSA(void); +extern void VP8FiltersInitNEON(void); +extern void VP8FiltersInitSSE2(void); + +WEBP_DSP_INIT_FUNC(VP8FiltersInit) { + WebPUnfilters[WEBP_FILTER_NONE] = NoneUnfilter_C; +#if !WEBP_NEON_OMIT_C_CODE + WebPUnfilters[WEBP_FILTER_HORIZONTAL] = HorizontalUnfilter_C; + WebPUnfilters[WEBP_FILTER_VERTICAL] = VerticalUnfilter_C; +#endif + WebPUnfilters[WEBP_FILTER_GRADIENT] = GradientUnfilter_C; + + WebPFilters[WEBP_FILTER_NONE] = NULL; +#if !WEBP_NEON_OMIT_C_CODE + WebPFilters[WEBP_FILTER_HORIZONTAL] = HorizontalFilter_C; + WebPFilters[WEBP_FILTER_VERTICAL] = VerticalFilter_C; + WebPFilters[WEBP_FILTER_GRADIENT] = GradientFilter_C; +#endif + + if (VP8GetCPUInfo != NULL) { +#if defined(WEBP_HAVE_SSE2) + if (VP8GetCPUInfo(kSSE2)) { + VP8FiltersInitSSE2(); + } +#endif +#if defined(WEBP_USE_MIPS_DSP_R2) + if (VP8GetCPUInfo(kMIPSdspR2)) { + VP8FiltersInitMIPSdspR2(); + } +#endif +#if defined(WEBP_USE_MSA) + if (VP8GetCPUInfo(kMSA)) { + VP8FiltersInitMSA(); + } +#endif + } + +#if defined(WEBP_HAVE_NEON) + if (WEBP_NEON_OMIT_C_CODE || + (VP8GetCPUInfo != NULL && VP8GetCPUInfo(kNEON))) { + VP8FiltersInitNEON(); + } +#endif + + assert(WebPUnfilters[WEBP_FILTER_NONE] != NULL); + assert(WebPUnfilters[WEBP_FILTER_HORIZONTAL] != NULL); + assert(WebPUnfilters[WEBP_FILTER_VERTICAL] != NULL); + assert(WebPUnfilters[WEBP_FILTER_GRADIENT] != NULL); + assert(WebPFilters[WEBP_FILTER_HORIZONTAL] != NULL); + assert(WebPFilters[WEBP_FILTER_VERTICAL] != NULL); + assert(WebPFilters[WEBP_FILTER_GRADIENT] != NULL); +} diff --git a/packages/core/src/zig/vendor/libwebp/src/dsp/filters_neon.c b/packages/core/src/zig/vendor/libwebp/src/dsp/filters_neon.c new file mode 100644 index 0000000000..4df1017260 --- /dev/null +++ b/packages/core/src/zig/vendor/libwebp/src/dsp/filters_neon.c @@ -0,0 +1,306 @@ +// Copyright 2017 Google Inc. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the COPYING file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +// ----------------------------------------------------------------------------- +// +// NEON variant of alpha filters +// +// Author: Skal (pascal.massimino@gmail.com) + +#include "src/dsp/dsp.h" + +#if defined(WEBP_USE_NEON) + +#include +#include "src/dsp/neon.h" + +//------------------------------------------------------------------------------ +// Helpful macros. + +#define DCHECK(in, out) \ + do { \ + assert((in) != NULL); \ + assert((out) != NULL); \ + assert((in) != (out)); \ + assert(width > 0); \ + assert(height > 0); \ + assert(stride >= width); \ + } while (0) + +// load eight u8 and widen to s16 +#define U8_TO_S16(A) vreinterpretq_s16_u16(vmovl_u8(A)) +#define LOAD_U8_TO_S16(A) U8_TO_S16(vld1_u8(A)) + +// shift left or right by N byte, inserting zeros +#define SHIFT_RIGHT_N_Q(A, N) vextq_u8((A), zero, (N)) +#define SHIFT_LEFT_N_Q(A, N) vextq_u8(zero, (A), (16 - (N)) % 16) + +// rotate left by N bytes +#define ROTATE_LEFT_N(A, N) vext_u8((A), (A), (N)) +// rotate right by N bytes +#define ROTATE_RIGHT_N(A, N) vext_u8((A), (A), (8 - (N)) % 8) + +static void PredictLine_NEON(const uint8_t* src, const uint8_t* pred, + uint8_t* WEBP_RESTRICT dst, int length) { + int i; + assert(length >= 0); + for (i = 0; i + 16 <= length; i += 16) { + const uint8x16_t A = vld1q_u8(&src[i]); + const uint8x16_t B = vld1q_u8(&pred[i]); + const uint8x16_t C = vsubq_u8(A, B); + vst1q_u8(&dst[i], C); + } + for (; i < length; ++i) dst[i] = src[i] - pred[i]; +} + +// Special case for left-based prediction (when preds==dst-1 or preds==src-1). +static void PredictLineLeft_NEON(const uint8_t* WEBP_RESTRICT src, + uint8_t* WEBP_RESTRICT dst, int length) { + PredictLine_NEON(src, src - 1, dst, length); +} + +//------------------------------------------------------------------------------ +// Horizontal filter. + +static WEBP_INLINE void DoHorizontalFilter_NEON( + const uint8_t* WEBP_RESTRICT in, int width, int height, int stride, + uint8_t* WEBP_RESTRICT out) { + int row; + DCHECK(in, out); + + // Leftmost pixel is the same as input for topmost scanline. + out[0] = in[0]; + PredictLineLeft_NEON(in + 1, out + 1, width - 1); + in += stride; + out += stride; + + // Filter line-by-line. + for (row = 1; row < height; ++row) { + // Leftmost pixel is predicted from above. + out[0] = in[0] - in[-stride]; + PredictLineLeft_NEON(in + 1, out + 1, width - 1); + in += stride; + out += stride; + } +} + +static void HorizontalFilter_NEON(const uint8_t* WEBP_RESTRICT data, + int width, int height, int stride, + uint8_t* WEBP_RESTRICT filtered_data) { + DoHorizontalFilter_NEON(data, width, height, stride, filtered_data); +} + +//------------------------------------------------------------------------------ +// Vertical filter. + +static WEBP_INLINE void DoVerticalFilter_NEON(const uint8_t* WEBP_RESTRICT in, + int width, int height, int stride, + uint8_t* WEBP_RESTRICT out) { + int row; + DCHECK(in, out); + + // Very first top-left pixel is copied. + out[0] = in[0]; + // Rest of top scan-line is left-predicted. + PredictLineLeft_NEON(in + 1, out + 1, width - 1); + in += stride; + out += stride; + + // Filter line-by-line. + for (row = 1; row < height; ++row) { + PredictLine_NEON(in, in - stride, out, width); + in += stride; + out += stride; + } +} + +static void VerticalFilter_NEON(const uint8_t* WEBP_RESTRICT data, + int width, int height, int stride, + uint8_t* WEBP_RESTRICT filtered_data) { + DoVerticalFilter_NEON(data, width, height, stride, filtered_data); +} + +//------------------------------------------------------------------------------ +// Gradient filter. + +static WEBP_INLINE int GradientPredictor_C(uint8_t a, uint8_t b, uint8_t c) { + const int g = a + b - c; + return ((g & ~0xff) == 0) ? g : (g < 0) ? 0 : 255; // clip to 8bit +} + +static void GradientPredictDirect_NEON(const uint8_t* const row, + const uint8_t* const top, + uint8_t* WEBP_RESTRICT const out, + int length) { + int i; + for (i = 0; i + 8 <= length; i += 8) { + const uint8x8_t A = vld1_u8(&row[i - 1]); + const uint8x8_t B = vld1_u8(&top[i + 0]); + const int16x8_t C = vreinterpretq_s16_u16(vaddl_u8(A, B)); + const int16x8_t D = LOAD_U8_TO_S16(&top[i - 1]); + const uint8x8_t E = vqmovun_s16(vsubq_s16(C, D)); + const uint8x8_t F = vld1_u8(&row[i + 0]); + vst1_u8(&out[i], vsub_u8(F, E)); + } + for (; i < length; ++i) { + out[i] = row[i] - GradientPredictor_C(row[i - 1], top[i], top[i - 1]); + } +} + +static WEBP_INLINE void DoGradientFilter_NEON(const uint8_t* WEBP_RESTRICT in, + int width, int height, int stride, + uint8_t* WEBP_RESTRICT out) { + int row; + DCHECK(in, out); + + // left prediction for top scan-line + out[0] = in[0]; + PredictLineLeft_NEON(in + 1, out + 1, width - 1); + in += stride; + out += stride; + + // Filter line-by-line. + for (row = 1; row < height; ++row) { + out[0] = in[0] - in[-stride]; + GradientPredictDirect_NEON(in + 1, in + 1 - stride, out + 1, width - 1); + in += stride; + out += stride; + } +} + +static void GradientFilter_NEON(const uint8_t* WEBP_RESTRICT data, + int width, int height, int stride, + uint8_t* WEBP_RESTRICT filtered_data) { + DoGradientFilter_NEON(data, width, height, stride, filtered_data); +} + +#undef DCHECK + +//------------------------------------------------------------------------------ +// Inverse transforms + +static void HorizontalUnfilter_NEON(const uint8_t* prev, const uint8_t* in, + uint8_t* out, int width) { + int i; + const uint8x16_t zero = vdupq_n_u8(0); + uint8x16_t last; + out[0] = in[0] + (prev == NULL ? 0 : prev[0]); + if (width <= 1) return; + last = vsetq_lane_u8(out[0], zero, 0); + for (i = 1; i + 16 <= width; i += 16) { + const uint8x16_t A0 = vld1q_u8(&in[i]); + const uint8x16_t A1 = vaddq_u8(A0, last); + const uint8x16_t A2 = SHIFT_LEFT_N_Q(A1, 1); + const uint8x16_t A3 = vaddq_u8(A1, A2); + const uint8x16_t A4 = SHIFT_LEFT_N_Q(A3, 2); + const uint8x16_t A5 = vaddq_u8(A3, A4); + const uint8x16_t A6 = SHIFT_LEFT_N_Q(A5, 4); + const uint8x16_t A7 = vaddq_u8(A5, A6); + const uint8x16_t A8 = SHIFT_LEFT_N_Q(A7, 8); + const uint8x16_t A9 = vaddq_u8(A7, A8); + vst1q_u8(&out[i], A9); + last = SHIFT_RIGHT_N_Q(A9, 15); + } + for (; i < width; ++i) out[i] = in[i] + out[i - 1]; +} + +static void VerticalUnfilter_NEON(const uint8_t* prev, const uint8_t* in, + uint8_t* out, int width) { + if (prev == NULL) { + HorizontalUnfilter_NEON(NULL, in, out, width); + } else { + int i; + assert(width >= 0); + for (i = 0; i + 16 <= width; i += 16) { + const uint8x16_t A = vld1q_u8(&in[i]); + const uint8x16_t B = vld1q_u8(&prev[i]); + const uint8x16_t C = vaddq_u8(A, B); + vst1q_u8(&out[i], C); + } + for (; i < width; ++i) out[i] = in[i] + prev[i]; + } +} + +// GradientUnfilter_NEON is correct but slower than the C-version, +// at least on ARM64. For armv7, it's a wash. +// So best is to disable it for now, but keep the idea around... +#if !defined(USE_GRADIENT_UNFILTER) +#define USE_GRADIENT_UNFILTER 0 // ALTERNATE_CODE +#endif + +#if (USE_GRADIENT_UNFILTER == 1) +#define GRAD_PROCESS_LANE(L) do { \ + const uint8x8_t tmp1 = ROTATE_RIGHT_N(pred, 1); /* rotate predictor in */ \ + const int16x8_t tmp2 = vaddq_s16(BC, U8_TO_S16(tmp1)); \ + const uint8x8_t delta = vqmovun_s16(tmp2); \ + pred = vadd_u8(D, delta); \ + out = vext_u8(out, ROTATE_LEFT_N(pred, (L)), 1); \ +} while (0) + +static void GradientPredictInverse_NEON(const uint8_t* const in, + const uint8_t* const top, + uint8_t* const row, int length) { + if (length > 0) { + int i; + uint8x8_t pred = vdup_n_u8(row[-1]); // left sample + uint8x8_t out = vdup_n_u8(0); + for (i = 0; i + 8 <= length; i += 8) { + const int16x8_t B = LOAD_U8_TO_S16(&top[i + 0]); + const int16x8_t C = LOAD_U8_TO_S16(&top[i - 1]); + const int16x8_t BC = vsubq_s16(B, C); // unclipped gradient basis B - C + const uint8x8_t D = vld1_u8(&in[i]); // base input + GRAD_PROCESS_LANE(0); + GRAD_PROCESS_LANE(1); + GRAD_PROCESS_LANE(2); + GRAD_PROCESS_LANE(3); + GRAD_PROCESS_LANE(4); + GRAD_PROCESS_LANE(5); + GRAD_PROCESS_LANE(6); + GRAD_PROCESS_LANE(7); + vst1_u8(&row[i], out); + } + for (; i < length; ++i) { + row[i] = in[i] + GradientPredictor_C(row[i - 1], top[i], top[i - 1]); + } + } +} +#undef GRAD_PROCESS_LANE + +static void GradientUnfilter_NEON(const uint8_t* prev, const uint8_t* in, + uint8_t* out, int width) { + if (prev == NULL) { + HorizontalUnfilter_NEON(NULL, in, out, width); + } else { + out[0] = in[0] + prev[0]; // predict from above + GradientPredictInverse_NEON(in + 1, prev + 1, out + 1, width - 1); + } +} + +#endif // USE_GRADIENT_UNFILTER + +//------------------------------------------------------------------------------ +// Entry point + +extern void VP8FiltersInitNEON(void); + +WEBP_TSAN_IGNORE_FUNCTION void VP8FiltersInitNEON(void) { + WebPUnfilters[WEBP_FILTER_HORIZONTAL] = HorizontalUnfilter_NEON; + WebPUnfilters[WEBP_FILTER_VERTICAL] = VerticalUnfilter_NEON; +#if (USE_GRADIENT_UNFILTER == 1) + WebPUnfilters[WEBP_FILTER_GRADIENT] = GradientUnfilter_NEON; +#endif + + WebPFilters[WEBP_FILTER_HORIZONTAL] = HorizontalFilter_NEON; + WebPFilters[WEBP_FILTER_VERTICAL] = VerticalFilter_NEON; + WebPFilters[WEBP_FILTER_GRADIENT] = GradientFilter_NEON; +} + +#else // !WEBP_USE_NEON + +WEBP_DSP_INIT_STUB(VP8FiltersInitNEON) + +#endif // WEBP_USE_NEON diff --git a/packages/core/src/zig/vendor/libwebp/src/dsp/filters_sse2.c b/packages/core/src/zig/vendor/libwebp/src/dsp/filters_sse2.c new file mode 100644 index 0000000000..b9a7aefdd9 --- /dev/null +++ b/packages/core/src/zig/vendor/libwebp/src/dsp/filters_sse2.c @@ -0,0 +1,324 @@ +// Copyright 2015 Google Inc. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the COPYING file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +// ----------------------------------------------------------------------------- +// +// SSE2 variant of alpha filters +// +// Author: Skal (pascal.massimino@gmail.com) + +#include "src/dsp/dsp.h" + +#if defined(WEBP_USE_SSE2) + +#include +#include +#include +#include + +#include "src/dsp/cpu.h" +#include "src/webp/types.h" + +//------------------------------------------------------------------------------ +// Helpful macro. + +#define DCHECK(in, out) \ + do { \ + assert((in) != NULL); \ + assert((out) != NULL); \ + assert((in) != (out)); \ + assert(width > 0); \ + assert(height > 0); \ + assert(stride >= width); \ + } while (0) + +static void PredictLineTop_SSE2(const uint8_t* WEBP_RESTRICT src, + const uint8_t* WEBP_RESTRICT pred, + uint8_t* WEBP_RESTRICT dst, int length) { + int i; + const int max_pos = length & ~31; + assert(length >= 0); + for (i = 0; i < max_pos; i += 32) { + const __m128i A0 = _mm_loadu_si128((const __m128i*)&src[i + 0]); + const __m128i A1 = _mm_loadu_si128((const __m128i*)&src[i + 16]); + const __m128i B0 = _mm_loadu_si128((const __m128i*)&pred[i + 0]); + const __m128i B1 = _mm_loadu_si128((const __m128i*)&pred[i + 16]); + const __m128i C0 = _mm_sub_epi8(A0, B0); + const __m128i C1 = _mm_sub_epi8(A1, B1); + _mm_storeu_si128((__m128i*)&dst[i + 0], C0); + _mm_storeu_si128((__m128i*)&dst[i + 16], C1); + } + for (; i < length; ++i) dst[i] = src[i] - pred[i]; +} + +// Special case for left-based prediction (when preds==dst-1 or preds==src-1). +static void PredictLineLeft_SSE2(const uint8_t* WEBP_RESTRICT src, + uint8_t* WEBP_RESTRICT dst, int length) { + int i; + const int max_pos = length & ~31; + assert(length >= 0); + for (i = 0; i < max_pos; i += 32) { + const __m128i A0 = _mm_loadu_si128((const __m128i*)(src + i + 0 )); + const __m128i B0 = _mm_loadu_si128((const __m128i*)(src + i + 0 - 1)); + const __m128i A1 = _mm_loadu_si128((const __m128i*)(src + i + 16 )); + const __m128i B1 = _mm_loadu_si128((const __m128i*)(src + i + 16 - 1)); + const __m128i C0 = _mm_sub_epi8(A0, B0); + const __m128i C1 = _mm_sub_epi8(A1, B1); + _mm_storeu_si128((__m128i*)(dst + i + 0), C0); + _mm_storeu_si128((__m128i*)(dst + i + 16), C1); + } + for (; i < length; ++i) dst[i] = src[i] - src[i - 1]; +} + +//------------------------------------------------------------------------------ +// Horizontal filter. + +static WEBP_INLINE void DoHorizontalFilter_SSE2( + const uint8_t* WEBP_RESTRICT in, int width, int height, int stride, + uint8_t* WEBP_RESTRICT out) { + int row; + DCHECK(in, out); + + // Leftmost pixel is the same as input for topmost scanline. + out[0] = in[0]; + PredictLineLeft_SSE2(in + 1, out + 1, width - 1); + in += stride; + out += stride; + + // Filter line-by-line. + for (row = 1; row < height; ++row) { + // Leftmost pixel is predicted from above. + out[0] = in[0] - in[-stride]; + PredictLineLeft_SSE2(in + 1, out + 1, width - 1); + in += stride; + out += stride; + } +} + +//------------------------------------------------------------------------------ +// Vertical filter. + +static WEBP_INLINE void DoVerticalFilter_SSE2(const uint8_t* WEBP_RESTRICT in, + int width, int height, int stride, + uint8_t* WEBP_RESTRICT out) { + int row; + DCHECK(in, out); + + // Very first top-left pixel is copied. + out[0] = in[0]; + // Rest of top scan-line is left-predicted. + PredictLineLeft_SSE2(in + 1, out + 1, width - 1); + in += stride; + out += stride; + + // Filter line-by-line. + for (row = 1; row < height; ++row) { + PredictLineTop_SSE2(in, in - stride, out, width); + in += stride; + out += stride; + } +} + +//------------------------------------------------------------------------------ +// Gradient filter. + +static WEBP_INLINE int GradientPredictor_SSE2(uint8_t a, uint8_t b, uint8_t c) { + const int g = a + b - c; + return ((g & ~0xff) == 0) ? g : (g < 0) ? 0 : 255; // clip to 8bit +} + +static void GradientPredictDirect_SSE2(const uint8_t* const row, + const uint8_t* const top, + uint8_t* WEBP_RESTRICT const out, + int length) { + const int max_pos = length & ~7; + int i; + const __m128i zero = _mm_setzero_si128(); + for (i = 0; i < max_pos; i += 8) { + const __m128i A0 = _mm_loadl_epi64((const __m128i*)&row[i - 1]); + const __m128i B0 = _mm_loadl_epi64((const __m128i*)&top[i]); + const __m128i C0 = _mm_loadl_epi64((const __m128i*)&top[i - 1]); + const __m128i D = _mm_loadl_epi64((const __m128i*)&row[i]); + const __m128i A1 = _mm_unpacklo_epi8(A0, zero); + const __m128i B1 = _mm_unpacklo_epi8(B0, zero); + const __m128i C1 = _mm_unpacklo_epi8(C0, zero); + const __m128i E = _mm_add_epi16(A1, B1); + const __m128i F = _mm_sub_epi16(E, C1); + const __m128i G = _mm_packus_epi16(F, zero); + const __m128i H = _mm_sub_epi8(D, G); + _mm_storel_epi64((__m128i*)(out + i), H); + } + for (; i < length; ++i) { + const int delta = GradientPredictor_SSE2(row[i - 1], top[i], top[i - 1]); + out[i] = (uint8_t)(row[i] - delta); + } +} + +static WEBP_INLINE void DoGradientFilter_SSE2(const uint8_t* WEBP_RESTRICT in, + int width, int height, int stride, + uint8_t* WEBP_RESTRICT out) { + int row; + DCHECK(in, out); + + // left prediction for top scan-line + out[0] = in[0]; + PredictLineLeft_SSE2(in + 1, out + 1, width - 1); + in += stride; + out += stride; + + // Filter line-by-line. + for (row = 1; row < height; ++row) { + out[0] = (uint8_t)(in[0] - in[-stride]); + GradientPredictDirect_SSE2(in + 1, in + 1 - stride, out + 1, width - 1); + in += stride; + out += stride; + } +} + +#undef DCHECK + +//------------------------------------------------------------------------------ + +static void HorizontalFilter_SSE2(const uint8_t* WEBP_RESTRICT data, + int width, int height, int stride, + uint8_t* WEBP_RESTRICT filtered_data) { + DoHorizontalFilter_SSE2(data, width, height, stride, filtered_data); +} + +static void VerticalFilter_SSE2(const uint8_t* WEBP_RESTRICT data, + int width, int height, int stride, + uint8_t* WEBP_RESTRICT filtered_data) { + DoVerticalFilter_SSE2(data, width, height, stride, filtered_data); +} + +static void GradientFilter_SSE2(const uint8_t* WEBP_RESTRICT data, + int width, int height, int stride, + uint8_t* WEBP_RESTRICT filtered_data) { + DoGradientFilter_SSE2(data, width, height, stride, filtered_data); +} + +//------------------------------------------------------------------------------ +// Inverse transforms + +static void HorizontalUnfilter_SSE2(const uint8_t* prev, const uint8_t* in, + uint8_t* out, int width) { + int i; + __m128i last; + out[0] = (uint8_t)(in[0] + (prev == NULL ? 0 : prev[0])); + if (width <= 1) return; + last = _mm_set_epi32(0, 0, 0, out[0]); + for (i = 1; i + 8 <= width; i += 8) { + const __m128i A0 = _mm_loadl_epi64((const __m128i*)(in + i)); + const __m128i A1 = _mm_add_epi8(A0, last); + const __m128i A2 = _mm_slli_si128(A1, 1); + const __m128i A3 = _mm_add_epi8(A1, A2); + const __m128i A4 = _mm_slli_si128(A3, 2); + const __m128i A5 = _mm_add_epi8(A3, A4); + const __m128i A6 = _mm_slli_si128(A5, 4); + const __m128i A7 = _mm_add_epi8(A5, A6); + _mm_storel_epi64((__m128i*)(out + i), A7); + last = _mm_srli_epi64(A7, 56); + } + for (; i < width; ++i) out[i] = (uint8_t)(in[i] + out[i - 1]); +} + +static void VerticalUnfilter_SSE2(const uint8_t* prev, const uint8_t* in, + uint8_t* out, int width) { + if (prev == NULL) { + HorizontalUnfilter_SSE2(NULL, in, out, width); + } else { + int i; + const int max_pos = width & ~31; + assert(width >= 0); + for (i = 0; i < max_pos; i += 32) { + const __m128i A0 = _mm_loadu_si128((const __m128i*)&in[i + 0]); + const __m128i A1 = _mm_loadu_si128((const __m128i*)&in[i + 16]); + const __m128i B0 = _mm_loadu_si128((const __m128i*)&prev[i + 0]); + const __m128i B1 = _mm_loadu_si128((const __m128i*)&prev[i + 16]); + const __m128i C0 = _mm_add_epi8(A0, B0); + const __m128i C1 = _mm_add_epi8(A1, B1); + _mm_storeu_si128((__m128i*)&out[i + 0], C0); + _mm_storeu_si128((__m128i*)&out[i + 16], C1); + } + for (; i < width; ++i) out[i] = (uint8_t)(in[i] + prev[i]); + } +} + +static void GradientPredictInverse_SSE2(const uint8_t* const in, + const uint8_t* const top, + uint8_t* const row, int length) { + if (length > 0) { + int i; + const int max_pos = length & ~7; + const __m128i zero = _mm_setzero_si128(); + __m128i A = _mm_set_epi32(0, 0, 0, row[-1]); // left sample + for (i = 0; i < max_pos; i += 8) { + const __m128i tmp0 = _mm_loadl_epi64((const __m128i*)&top[i]); + const __m128i tmp1 = _mm_loadl_epi64((const __m128i*)&top[i - 1]); + const __m128i B = _mm_unpacklo_epi8(tmp0, zero); + const __m128i C = _mm_unpacklo_epi8(tmp1, zero); + const __m128i D = _mm_loadl_epi64((const __m128i*)&in[i]); // base input + const __m128i E = _mm_sub_epi16(B, C); // unclipped gradient basis B - C + __m128i out = zero; // accumulator for output + __m128i mask_hi = _mm_set_epi32(0, 0, 0, 0xff); + int k = 8; + while (1) { + const __m128i tmp3 = _mm_add_epi16(A, E); // delta = A + B - C + const __m128i tmp4 = _mm_packus_epi16(tmp3, zero); // saturate delta + const __m128i tmp5 = _mm_add_epi8(tmp4, D); // add to in[] + A = _mm_and_si128(tmp5, mask_hi); // 1-complement clip + out = _mm_or_si128(out, A); // accumulate output + if (--k == 0) break; + A = _mm_slli_si128(A, 1); // rotate left sample + mask_hi = _mm_slli_si128(mask_hi, 1); // rotate mask + A = _mm_unpacklo_epi8(A, zero); // convert 8b->16b + } + A = _mm_srli_si128(A, 7); // prepare left sample for next iteration + _mm_storel_epi64((__m128i*)&row[i], out); + } + for (; i < length; ++i) { + const int delta = GradientPredictor_SSE2(row[i - 1], top[i], top[i - 1]); + row[i] = (uint8_t)(in[i] + delta); + } + } +} + +static void GradientUnfilter_SSE2(const uint8_t* prev, const uint8_t* in, + uint8_t* out, int width) { + if (prev == NULL) { + HorizontalUnfilter_SSE2(NULL, in, out, width); + } else { + out[0] = (uint8_t)(in[0] + prev[0]); // predict from above + GradientPredictInverse_SSE2(in + 1, prev + 1, out + 1, width - 1); + } +} + +//------------------------------------------------------------------------------ +// Entry point + +extern void VP8FiltersInitSSE2(void); + +WEBP_TSAN_IGNORE_FUNCTION void VP8FiltersInitSSE2(void) { + WebPUnfilters[WEBP_FILTER_HORIZONTAL] = HorizontalUnfilter_SSE2; +#if defined(CHROMIUM) + // TODO(crbug.com/654974) + (void)VerticalUnfilter_SSE2; +#else + WebPUnfilters[WEBP_FILTER_VERTICAL] = VerticalUnfilter_SSE2; +#endif + WebPUnfilters[WEBP_FILTER_GRADIENT] = GradientUnfilter_SSE2; + + WebPFilters[WEBP_FILTER_HORIZONTAL] = HorizontalFilter_SSE2; + WebPFilters[WEBP_FILTER_VERTICAL] = VerticalFilter_SSE2; + WebPFilters[WEBP_FILTER_GRADIENT] = GradientFilter_SSE2; +} + +#else // !WEBP_USE_SSE2 + +WEBP_DSP_INIT_STUB(VP8FiltersInitSSE2) + +#endif // WEBP_USE_SSE2 diff --git a/packages/core/src/zig/vendor/libwebp/src/dsp/lossless.c b/packages/core/src/zig/vendor/libwebp/src/dsp/lossless.c new file mode 100644 index 0000000000..1a3d800c3f --- /dev/null +++ b/packages/core/src/zig/vendor/libwebp/src/dsp/lossless.c @@ -0,0 +1,698 @@ +// Copyright 2012 Google Inc. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the COPYING file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +// ----------------------------------------------------------------------------- +// +// Image transforms and color space conversion methods for lossless decoder. +// +// Authors: Vikas Arora (vikaas.arora@gmail.com) +// Jyrki Alakuijala (jyrki@google.com) +// Urvang Joshi (urvang@google.com) + +#include "src/dsp/lossless.h" + +#include +#include +#include + +#include "src/dec/vp8li_dec.h" +#include "src/dsp/cpu.h" +#include "src/dsp/dsp.h" +#include "src/dsp/lossless_common.h" +#include "src/utils/endian_inl_utils.h" +#include "src/utils/utils.h" +#include "src/webp/decode.h" +#include "src/webp/format_constants.h" +#include "src/webp/types.h" + +//------------------------------------------------------------------------------ +// Image transforms. + +static WEBP_INLINE uint32_t Average2(uint32_t a0, uint32_t a1) { + return (((a0 ^ a1) & 0xfefefefeu) >> 1) + (a0 & a1); +} + +static WEBP_INLINE uint32_t Average3(uint32_t a0, uint32_t a1, uint32_t a2) { + return Average2(Average2(a0, a2), a1); +} + +static WEBP_INLINE uint32_t Average4(uint32_t a0, uint32_t a1, + uint32_t a2, uint32_t a3) { + return Average2(Average2(a0, a1), Average2(a2, a3)); +} + +static WEBP_INLINE uint32_t Clip255(uint32_t a) { + if (a < 256) { + return a; + } + // return 0, when a is a negative integer. + // return 255, when a is positive. + return ~a >> 24; +} + +static WEBP_INLINE int AddSubtractComponentFull(int a, int b, int c) { + return Clip255((uint32_t)(a + b - c)); +} + +static WEBP_INLINE uint32_t ClampedAddSubtractFull(uint32_t c0, uint32_t c1, + uint32_t c2) { + const int a = AddSubtractComponentFull(c0 >> 24, c1 >> 24, c2 >> 24); + const int r = AddSubtractComponentFull((c0 >> 16) & 0xff, + (c1 >> 16) & 0xff, + (c2 >> 16) & 0xff); + const int g = AddSubtractComponentFull((c0 >> 8) & 0xff, + (c1 >> 8) & 0xff, + (c2 >> 8) & 0xff); + const int b = AddSubtractComponentFull(c0 & 0xff, c1 & 0xff, c2 & 0xff); + return ((uint32_t)a << 24) | (r << 16) | (g << 8) | b; +} + +static WEBP_INLINE int AddSubtractComponentHalf(int a, int b) { + return Clip255((uint32_t)(a + (a - b) / 2)); +} + +static WEBP_INLINE uint32_t ClampedAddSubtractHalf(uint32_t c0, uint32_t c1, + uint32_t c2) { + const uint32_t ave = Average2(c0, c1); + const int a = AddSubtractComponentHalf(ave >> 24, c2 >> 24); + const int r = AddSubtractComponentHalf((ave >> 16) & 0xff, (c2 >> 16) & 0xff); + const int g = AddSubtractComponentHalf((ave >> 8) & 0xff, (c2 >> 8) & 0xff); + const int b = AddSubtractComponentHalf((ave >> 0) & 0xff, (c2 >> 0) & 0xff); + return ((uint32_t)a << 24) | (r << 16) | (g << 8) | b; +} + +// gcc <= 4.9 on ARM generates incorrect code in Select() when Sub3() is +// inlined. +#if defined(__arm__) && defined(__GNUC__) && LOCAL_GCC_VERSION <= 0x409 +# define LOCAL_INLINE __attribute__ ((noinline)) +#else +# define LOCAL_INLINE WEBP_INLINE +#endif + +static LOCAL_INLINE int Sub3(int a, int b, int c) { + const int pb = b - c; + const int pa = a - c; + return abs(pb) - abs(pa); +} + +#undef LOCAL_INLINE + +static WEBP_INLINE uint32_t Select(uint32_t a, uint32_t b, uint32_t c) { + const int pa_minus_pb = + Sub3((a >> 24) , (b >> 24) , (c >> 24) ) + + Sub3((a >> 16) & 0xff, (b >> 16) & 0xff, (c >> 16) & 0xff) + + Sub3((a >> 8) & 0xff, (b >> 8) & 0xff, (c >> 8) & 0xff) + + Sub3((a ) & 0xff, (b ) & 0xff, (c ) & 0xff); + return (pa_minus_pb <= 0) ? a : b; +} + +//------------------------------------------------------------------------------ +// Predictors + +static uint32_t VP8LPredictor0_C(const uint32_t* const left, + const uint32_t* const top) { + (void)top; + (void)left; + return ARGB_BLACK; +} +static uint32_t VP8LPredictor1_C(const uint32_t* const left, + const uint32_t* const top) { + (void)top; + return *left; +} +uint32_t VP8LPredictor2_C(const uint32_t* const left, + const uint32_t* const top) { + (void)left; + return top[0]; +} +uint32_t VP8LPredictor3_C(const uint32_t* const left, + const uint32_t* const top) { + (void)left; + return top[1]; +} +uint32_t VP8LPredictor4_C(const uint32_t* const left, + const uint32_t* const top) { + (void)left; + return top[-1]; +} +uint32_t VP8LPredictor5_C(const uint32_t* const left, + const uint32_t* const top) { + const uint32_t pred = Average3(*left, top[0], top[1]); + return pred; +} +uint32_t VP8LPredictor6_C(const uint32_t* const left, + const uint32_t* const top) { + const uint32_t pred = Average2(*left, top[-1]); + return pred; +} +uint32_t VP8LPredictor7_C(const uint32_t* const left, + const uint32_t* const top) { + const uint32_t pred = Average2(*left, top[0]); + return pred; +} +uint32_t VP8LPredictor8_C(const uint32_t* const left, + const uint32_t* const top) { + const uint32_t pred = Average2(top[-1], top[0]); + (void)left; + return pred; +} +uint32_t VP8LPredictor9_C(const uint32_t* const left, + const uint32_t* const top) { + const uint32_t pred = Average2(top[0], top[1]); + (void)left; + return pred; +} +uint32_t VP8LPredictor10_C(const uint32_t* const left, + const uint32_t* const top) { + const uint32_t pred = Average4(*left, top[-1], top[0], top[1]); + return pred; +} +uint32_t VP8LPredictor11_C(const uint32_t* const left, + const uint32_t* const top) { + const uint32_t pred = Select(top[0], *left, top[-1]); + return pred; +} +uint32_t VP8LPredictor12_C(const uint32_t* const left, + const uint32_t* const top) { + const uint32_t pred = ClampedAddSubtractFull(*left, top[0], top[-1]); + return pred; +} +uint32_t VP8LPredictor13_C(const uint32_t* const left, + const uint32_t* const top) { + const uint32_t pred = ClampedAddSubtractHalf(*left, top[0], top[-1]); + return pred; +} + +static void PredictorAdd0_C(const uint32_t* in, const uint32_t* upper, + int num_pixels, uint32_t* WEBP_RESTRICT out) { + int x; + (void)upper; + for (x = 0; x < num_pixels; ++x) out[x] = VP8LAddPixels(in[x], ARGB_BLACK); +} +static void PredictorAdd1_C(const uint32_t* in, const uint32_t* upper, + int num_pixels, uint32_t* WEBP_RESTRICT out) { + int i; + uint32_t left = out[-1]; + (void)upper; + for (i = 0; i < num_pixels; ++i) { + out[i] = left = VP8LAddPixels(in[i], left); + } +} +GENERATE_PREDICTOR_ADD(VP8LPredictor2_C, PredictorAdd2_C) +GENERATE_PREDICTOR_ADD(VP8LPredictor3_C, PredictorAdd3_C) +GENERATE_PREDICTOR_ADD(VP8LPredictor4_C, PredictorAdd4_C) +GENERATE_PREDICTOR_ADD(VP8LPredictor5_C, PredictorAdd5_C) +GENERATE_PREDICTOR_ADD(VP8LPredictor6_C, PredictorAdd6_C) +GENERATE_PREDICTOR_ADD(VP8LPredictor7_C, PredictorAdd7_C) +GENERATE_PREDICTOR_ADD(VP8LPredictor8_C, PredictorAdd8_C) +GENERATE_PREDICTOR_ADD(VP8LPredictor9_C, PredictorAdd9_C) +GENERATE_PREDICTOR_ADD(VP8LPredictor10_C, PredictorAdd10_C) +GENERATE_PREDICTOR_ADD(VP8LPredictor11_C, PredictorAdd11_C) +GENERATE_PREDICTOR_ADD(VP8LPredictor12_C, PredictorAdd12_C) +GENERATE_PREDICTOR_ADD(VP8LPredictor13_C, PredictorAdd13_C) + +//------------------------------------------------------------------------------ + +// Inverse prediction. +static void PredictorInverseTransform_C(const VP8LTransform* const transform, + int y_start, int y_end, + const uint32_t* in, uint32_t* out) { + const int width = transform->xsize; + if (y_start == 0) { // First Row follows the L (mode=1) mode. + PredictorAdd0_C(in, NULL, 1, out); + PredictorAdd1_C(in + 1, NULL, width - 1, out + 1); + in += width; + out += width; + ++y_start; + } + + { + int y = y_start; + const int tile_width = 1 << transform->bits; + const int mask = tile_width - 1; + const int tiles_per_row = VP8LSubSampleSize(width, transform->bits); + const uint32_t* pred_mode_base = + transform->data + (y >> transform->bits) * tiles_per_row; + + while (y < y_end) { + const uint32_t* pred_mode_src = pred_mode_base; + int x = 1; + // First pixel follows the T (mode=2) mode. + PredictorAdd2_C(in, out - width, 1, out); + // .. the rest: + while (x < width) { + const VP8LPredictorAddSubFunc pred_func = + VP8LPredictorsAdd[((*pred_mode_src++) >> 8) & 0xf]; + int x_end = (x & ~mask) + tile_width; + if (x_end > width) x_end = width; + pred_func(in + x, out + x - width, x_end - x, out + x); + x = x_end; + } + in += width; + out += width; + ++y; + if ((y & mask) == 0) { // Use the same mask, since tiles are squares. + pred_mode_base += tiles_per_row; + } + } + } +} + +// Add green to blue and red channels (i.e. perform the inverse transform of +// 'subtract green'). +void VP8LAddGreenToBlueAndRed_C(const uint32_t* src, int num_pixels, + uint32_t* dst) { + int i; + for (i = 0; i < num_pixels; ++i) { + const uint32_t argb = src[i]; + const uint32_t green = ((argb >> 8) & 0xff); + uint32_t red_blue = (argb & 0x00ff00ffu); + red_blue += (green << 16) | green; + red_blue &= 0x00ff00ffu; + dst[i] = (argb & 0xff00ff00u) | red_blue; + } +} + +static WEBP_INLINE int ColorTransformDelta(int8_t color_pred, + int8_t color) { + return ((int)color_pred * color) >> 5; +} + +static WEBP_INLINE void ColorCodeToMultipliers(uint32_t color_code, + VP8LMultipliers* const m) { + m->green_to_red = (color_code >> 0) & 0xff; + m->green_to_blue = (color_code >> 8) & 0xff; + m->red_to_blue = (color_code >> 16) & 0xff; +} + +void VP8LTransformColorInverse_C(const VP8LMultipliers* const m, + const uint32_t* src, int num_pixels, + uint32_t* dst) { + int i; + for (i = 0; i < num_pixels; ++i) { + const uint32_t argb = src[i]; + const int8_t green = (int8_t)(argb >> 8); + const uint32_t red = argb >> 16; + int new_red = red & 0xff; + int new_blue = argb & 0xff; + new_red += ColorTransformDelta((int8_t)m->green_to_red, green); + new_red &= 0xff; + new_blue += ColorTransformDelta((int8_t)m->green_to_blue, green); + new_blue += ColorTransformDelta((int8_t)m->red_to_blue, (int8_t)new_red); + new_blue &= 0xff; + dst[i] = (argb & 0xff00ff00u) | (new_red << 16) | (new_blue); + } +} + +// Color space inverse transform. +static void ColorSpaceInverseTransform_C(const VP8LTransform* const transform, + int y_start, int y_end, + const uint32_t* src, uint32_t* dst) { + const int width = transform->xsize; + const int tile_width = 1 << transform->bits; + const int mask = tile_width - 1; + const int safe_width = width & ~mask; + const int remaining_width = width - safe_width; + const int tiles_per_row = VP8LSubSampleSize(width, transform->bits); + int y = y_start; + const uint32_t* pred_row = + transform->data + (y >> transform->bits) * tiles_per_row; + + while (y < y_end) { + const uint32_t* pred = pred_row; + VP8LMultipliers m = { 0, 0, 0 }; + const uint32_t* const src_safe_end = src + safe_width; + const uint32_t* const src_end = src + width; + while (src < src_safe_end) { + ColorCodeToMultipliers(*pred++, &m); + VP8LTransformColorInverse(&m, src, tile_width, dst); + src += tile_width; + dst += tile_width; + } + if (src < src_end) { // Left-overs using C-version. + ColorCodeToMultipliers(*pred++, &m); + VP8LTransformColorInverse(&m, src, remaining_width, dst); + src += remaining_width; + dst += remaining_width; + } + ++y; + if ((y & mask) == 0) pred_row += tiles_per_row; + } +} + +// Separate out pixels packed together using pixel-bundling. +// We define two methods for ARGB data (uint32_t) and alpha-only data (uint8_t). +#define COLOR_INDEX_INVERSE(FUNC_NAME, F_NAME, STATIC_DECL, TYPE, BIT_SUFFIX, \ + GET_INDEX, GET_VALUE) \ +static void F_NAME(const TYPE* src, const uint32_t* const color_map, \ + TYPE* dst, int y_start, int y_end, int width) { \ + int y; \ + for (y = y_start; y < y_end; ++y) { \ + int x; \ + for (x = 0; x < width; ++x) { \ + *dst++ = GET_VALUE(color_map[GET_INDEX(*src++)]); \ + } \ + } \ +} \ +STATIC_DECL void FUNC_NAME(const VP8LTransform* const transform, \ + int y_start, int y_end, const TYPE* src, \ + TYPE* dst) { \ + int y; \ + const int bits_per_pixel = 8 >> transform->bits; \ + const int width = transform->xsize; \ + const uint32_t* const color_map = transform->data; \ + if (bits_per_pixel < 8) { \ + const int pixels_per_byte = 1 << transform->bits; \ + const int count_mask = pixels_per_byte - 1; \ + const uint32_t bit_mask = (1 << bits_per_pixel) - 1; \ + for (y = y_start; y < y_end; ++y) { \ + uint32_t packed_pixels = 0; \ + int x; \ + for (x = 0; x < width; ++x) { \ + /* We need to load fresh 'packed_pixels' once every */ \ + /* 'pixels_per_byte' increments of x. Fortunately, pixels_per_byte */ \ + /* is a power of 2, so can just use a mask for that, instead of */ \ + /* decrementing a counter. */ \ + if ((x & count_mask) == 0) packed_pixels = GET_INDEX(*src++); \ + *dst++ = GET_VALUE(color_map[packed_pixels & bit_mask]); \ + packed_pixels >>= bits_per_pixel; \ + } \ + } \ + } else { \ + VP8LMapColor##BIT_SUFFIX(src, color_map, dst, y_start, y_end, width); \ + } \ +} + +COLOR_INDEX_INVERSE(ColorIndexInverseTransform_C, MapARGB_C, static, + uint32_t, 32b, VP8GetARGBIndex, VP8GetARGBValue) +COLOR_INDEX_INVERSE(VP8LColorIndexInverseTransformAlpha, MapAlpha_C, , + uint8_t, 8b, VP8GetAlphaIndex, VP8GetAlphaValue) + +#undef COLOR_INDEX_INVERSE + +void VP8LInverseTransform(const VP8LTransform* const transform, + int row_start, int row_end, + const uint32_t* const in, uint32_t* const out) { + const int width = transform->xsize; + assert(row_start < row_end); + assert(row_end <= transform->ysize); + switch (transform->type) { + case SUBTRACT_GREEN_TRANSFORM: + VP8LAddGreenToBlueAndRed(in, (row_end - row_start) * width, out); + break; + case PREDICTOR_TRANSFORM: + PredictorInverseTransform_C(transform, row_start, row_end, in, out); + if (row_end != transform->ysize) { + // The last predicted row in this iteration will be the top-pred row + // for the first row in next iteration. + memcpy(out - width, out + (row_end - row_start - 1) * width, + width * sizeof(*out)); + } + break; + case CROSS_COLOR_TRANSFORM: + ColorSpaceInverseTransform_C(transform, row_start, row_end, in, out); + break; + case COLOR_INDEXING_TRANSFORM: + if (in == out && transform->bits > 0) { + // Move packed pixels to the end of unpacked region, so that unpacking + // can occur seamlessly. + // Also, note that this is the only transform that applies on + // the effective width of VP8LSubSampleSize(xsize, bits). All other + // transforms work on effective width of 'xsize'. + const int out_stride = (row_end - row_start) * width; + const int in_stride = (row_end - row_start) * + VP8LSubSampleSize(transform->xsize, transform->bits); + uint32_t* const src = out + out_stride - in_stride; + memmove(src, out, in_stride * sizeof(*src)); + ColorIndexInverseTransform_C(transform, row_start, row_end, src, out); + } else { + ColorIndexInverseTransform_C(transform, row_start, row_end, in, out); + } + break; + } +} + +//------------------------------------------------------------------------------ +// Color space conversion. + +static int is_big_endian(void) { + static const union { + uint16_t w; + uint8_t b[2]; + } tmp = { 1 }; + return (tmp.b[0] != 1); +} + +void VP8LConvertBGRAToRGB_C(const uint32_t* WEBP_RESTRICT src, + int num_pixels, uint8_t* WEBP_RESTRICT dst) { + const uint32_t* const src_end = src + num_pixels; + while (src < src_end) { + const uint32_t argb = *src++; + *dst++ = (argb >> 16) & 0xff; + *dst++ = (argb >> 8) & 0xff; + *dst++ = (argb >> 0) & 0xff; + } +} + +void VP8LConvertBGRAToRGBA_C(const uint32_t* WEBP_RESTRICT src, + int num_pixels, uint8_t* WEBP_RESTRICT dst) { + const uint32_t* const src_end = src + num_pixels; + while (src < src_end) { + const uint32_t argb = *src++; + *dst++ = (argb >> 16) & 0xff; + *dst++ = (argb >> 8) & 0xff; + *dst++ = (argb >> 0) & 0xff; + *dst++ = (argb >> 24) & 0xff; + } +} + +void VP8LConvertBGRAToRGBA4444_C(const uint32_t* WEBP_RESTRICT src, + int num_pixels, uint8_t* WEBP_RESTRICT dst) { + const uint32_t* const src_end = src + num_pixels; + while (src < src_end) { + const uint32_t argb = *src++; + const uint8_t rg = ((argb >> 16) & 0xf0) | ((argb >> 12) & 0xf); + const uint8_t ba = ((argb >> 0) & 0xf0) | ((argb >> 28) & 0xf); +#if (WEBP_SWAP_16BIT_CSP == 1) + *dst++ = ba; + *dst++ = rg; +#else + *dst++ = rg; + *dst++ = ba; +#endif + } +} + +void VP8LConvertBGRAToRGB565_C(const uint32_t* WEBP_RESTRICT src, + int num_pixels, uint8_t* WEBP_RESTRICT dst) { + const uint32_t* const src_end = src + num_pixels; + while (src < src_end) { + const uint32_t argb = *src++; + const uint8_t rg = ((argb >> 16) & 0xf8) | ((argb >> 13) & 0x7); + const uint8_t gb = ((argb >> 5) & 0xe0) | ((argb >> 3) & 0x1f); +#if (WEBP_SWAP_16BIT_CSP == 1) + *dst++ = gb; + *dst++ = rg; +#else + *dst++ = rg; + *dst++ = gb; +#endif + } +} + +void VP8LConvertBGRAToBGR_C(const uint32_t* WEBP_RESTRICT src, + int num_pixels, uint8_t* WEBP_RESTRICT dst) { + const uint32_t* const src_end = src + num_pixels; + while (src < src_end) { + const uint32_t argb = *src++; + *dst++ = (argb >> 0) & 0xff; + *dst++ = (argb >> 8) & 0xff; + *dst++ = (argb >> 16) & 0xff; + } +} + +static void CopyOrSwap(const uint32_t* WEBP_RESTRICT src, int num_pixels, + uint8_t* WEBP_RESTRICT dst, int swap_on_big_endian) { + if (is_big_endian() == swap_on_big_endian) { + const uint32_t* const src_end = src + num_pixels; + while (src < src_end) { + const uint32_t argb = *src++; + WebPUint32ToMem(dst, BSwap32(argb)); + dst += sizeof(argb); + } + } else { + memcpy(dst, src, num_pixels * sizeof(*src)); + } +} + +void VP8LConvertFromBGRA(const uint32_t* const in_data, int num_pixels, + WEBP_CSP_MODE out_colorspace, uint8_t* const rgba) { + switch (out_colorspace) { + case MODE_RGB: + VP8LConvertBGRAToRGB(in_data, num_pixels, rgba); + break; + case MODE_RGBA: + VP8LConvertBGRAToRGBA(in_data, num_pixels, rgba); + break; + case MODE_rgbA: + VP8LConvertBGRAToRGBA(in_data, num_pixels, rgba); + WebPApplyAlphaMultiply(rgba, 0, num_pixels, 1, 0); + break; + case MODE_BGR: + VP8LConvertBGRAToBGR(in_data, num_pixels, rgba); + break; + case MODE_BGRA: + CopyOrSwap(in_data, num_pixels, rgba, 1); + break; + case MODE_bgrA: + CopyOrSwap(in_data, num_pixels, rgba, 1); + WebPApplyAlphaMultiply(rgba, 0, num_pixels, 1, 0); + break; + case MODE_ARGB: + CopyOrSwap(in_data, num_pixels, rgba, 0); + break; + case MODE_Argb: + CopyOrSwap(in_data, num_pixels, rgba, 0); + WebPApplyAlphaMultiply(rgba, 1, num_pixels, 1, 0); + break; + case MODE_RGBA_4444: + VP8LConvertBGRAToRGBA4444(in_data, num_pixels, rgba); + break; + case MODE_rgbA_4444: + VP8LConvertBGRAToRGBA4444(in_data, num_pixels, rgba); + WebPApplyAlphaMultiply4444(rgba, num_pixels, 1, 0); + break; + case MODE_RGB_565: + VP8LConvertBGRAToRGB565(in_data, num_pixels, rgba); + break; + default: + assert(0); // Code flow should not reach here. + } +} + +//------------------------------------------------------------------------------ + +VP8LProcessDecBlueAndRedFunc VP8LAddGreenToBlueAndRed; +VP8LProcessDecBlueAndRedFunc VP8LAddGreenToBlueAndRed_SSE; +VP8LPredictorAddSubFunc VP8LPredictorsAdd[16]; +VP8LPredictorAddSubFunc VP8LPredictorsAdd_SSE[16]; +VP8LPredictorFunc VP8LPredictors[16]; + +// exposed plain-C implementations +VP8LPredictorAddSubFunc VP8LPredictorsAdd_C[16]; + +VP8LTransformColorInverseFunc VP8LTransformColorInverse; +VP8LTransformColorInverseFunc VP8LTransformColorInverse_SSE; + +VP8LConvertFunc VP8LConvertBGRAToRGB; +VP8LConvertFunc VP8LConvertBGRAToRGB_SSE; +VP8LConvertFunc VP8LConvertBGRAToRGBA; +VP8LConvertFunc VP8LConvertBGRAToRGBA_SSE; +VP8LConvertFunc VP8LConvertBGRAToRGBA4444; +VP8LConvertFunc VP8LConvertBGRAToRGB565; +VP8LConvertFunc VP8LConvertBGRAToBGR; + +VP8LMapARGBFunc VP8LMapColor32b; +VP8LMapAlphaFunc VP8LMapColor8b; + +extern VP8CPUInfo VP8GetCPUInfo; +extern void VP8LDspInitSSE2(void); +extern void VP8LDspInitSSE41(void); +extern void VP8LDspInitAVX2(void); +extern void VP8LDspInitNEON(void); +extern void VP8LDspInitMIPSdspR2(void); +extern void VP8LDspInitMSA(void); + +#define COPY_PREDICTOR_ARRAY(IN, OUT) do { \ + (OUT)[0] = IN##0_C; \ + (OUT)[1] = IN##1_C; \ + (OUT)[2] = IN##2_C; \ + (OUT)[3] = IN##3_C; \ + (OUT)[4] = IN##4_C; \ + (OUT)[5] = IN##5_C; \ + (OUT)[6] = IN##6_C; \ + (OUT)[7] = IN##7_C; \ + (OUT)[8] = IN##8_C; \ + (OUT)[9] = IN##9_C; \ + (OUT)[10] = IN##10_C; \ + (OUT)[11] = IN##11_C; \ + (OUT)[12] = IN##12_C; \ + (OUT)[13] = IN##13_C; \ + (OUT)[14] = IN##0_C; /* <- padding security sentinels*/ \ + (OUT)[15] = IN##0_C; \ +} while (0); + +WEBP_DSP_INIT_FUNC(VP8LDspInit) { + COPY_PREDICTOR_ARRAY(VP8LPredictor, VP8LPredictors) + COPY_PREDICTOR_ARRAY(PredictorAdd, VP8LPredictorsAdd) + COPY_PREDICTOR_ARRAY(PredictorAdd, VP8LPredictorsAdd_C) + +#if !WEBP_NEON_OMIT_C_CODE + VP8LAddGreenToBlueAndRed = VP8LAddGreenToBlueAndRed_C; + + VP8LTransformColorInverse = VP8LTransformColorInverse_C; + + VP8LConvertBGRAToRGBA = VP8LConvertBGRAToRGBA_C; + VP8LConvertBGRAToRGB = VP8LConvertBGRAToRGB_C; + VP8LConvertBGRAToBGR = VP8LConvertBGRAToBGR_C; +#endif + + VP8LConvertBGRAToRGBA4444 = VP8LConvertBGRAToRGBA4444_C; + VP8LConvertBGRAToRGB565 = VP8LConvertBGRAToRGB565_C; + + VP8LMapColor32b = MapARGB_C; + VP8LMapColor8b = MapAlpha_C; + + // If defined, use CPUInfo() to overwrite some pointers with faster versions. + if (VP8GetCPUInfo != NULL) { +#if defined(WEBP_HAVE_SSE2) + if (VP8GetCPUInfo(kSSE2)) { + VP8LDspInitSSE2(); +#if defined(WEBP_HAVE_SSE41) + if (VP8GetCPUInfo(kSSE4_1)) { + VP8LDspInitSSE41(); +#if defined(WEBP_HAVE_AVX2) + if (VP8GetCPUInfo(kAVX2)) { + VP8LDspInitAVX2(); + } +#endif + } +#endif + } +#endif +#if defined(WEBP_USE_MIPS_DSP_R2) + if (VP8GetCPUInfo(kMIPSdspR2)) { + VP8LDspInitMIPSdspR2(); + } +#endif +#if defined(WEBP_USE_MSA) + if (VP8GetCPUInfo(kMSA)) { + VP8LDspInitMSA(); + } +#endif + } + +#if defined(WEBP_HAVE_NEON) + if (WEBP_NEON_OMIT_C_CODE || + (VP8GetCPUInfo != NULL && VP8GetCPUInfo(kNEON))) { + VP8LDspInitNEON(); + } +#endif + + assert(VP8LAddGreenToBlueAndRed != NULL); + assert(VP8LTransformColorInverse != NULL); + assert(VP8LConvertBGRAToRGBA != NULL); + assert(VP8LConvertBGRAToRGB != NULL); + assert(VP8LConvertBGRAToBGR != NULL); + assert(VP8LConvertBGRAToRGBA4444 != NULL); + assert(VP8LConvertBGRAToRGB565 != NULL); + assert(VP8LMapColor32b != NULL); + assert(VP8LMapColor8b != NULL); +} +#undef COPY_PREDICTOR_ARRAY + +//------------------------------------------------------------------------------ diff --git a/packages/core/src/zig/vendor/libwebp/src/dsp/lossless.h b/packages/core/src/zig/vendor/libwebp/src/dsp/lossless.h new file mode 100644 index 0000000000..c66ec5da64 --- /dev/null +++ b/packages/core/src/zig/vendor/libwebp/src/dsp/lossless.h @@ -0,0 +1,271 @@ +// Copyright 2012 Google Inc. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the COPYING file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +// ----------------------------------------------------------------------------- +// +// Image transforms and color space conversion methods for lossless decoder. +// +// Authors: Vikas Arora (vikaas.arora@gmail.com) +// Jyrki Alakuijala (jyrki@google.com) + +#ifndef WEBP_DSP_LOSSLESS_H_ +#define WEBP_DSP_LOSSLESS_H_ + +#include "src/dsp/dsp.h" +#include "src/webp/types.h" +#include "src/webp/decode.h" + +#ifdef __cplusplus +extern "C" { +#endif + +//------------------------------------------------------------------------------ +// Decoding + +typedef uint32_t (*VP8LPredictorFunc)(const uint32_t* const left, + const uint32_t* const top); +extern VP8LPredictorFunc VP8LPredictors[16]; + +uint32_t VP8LPredictor2_C(const uint32_t* const left, + const uint32_t* const top); +uint32_t VP8LPredictor3_C(const uint32_t* const left, + const uint32_t* const top); +uint32_t VP8LPredictor4_C(const uint32_t* const left, + const uint32_t* const top); +uint32_t VP8LPredictor5_C(const uint32_t* const left, + const uint32_t* const top); +uint32_t VP8LPredictor6_C(const uint32_t* const left, + const uint32_t* const top); +uint32_t VP8LPredictor7_C(const uint32_t* const left, + const uint32_t* const top); +uint32_t VP8LPredictor8_C(const uint32_t* const left, + const uint32_t* const top); +uint32_t VP8LPredictor9_C(const uint32_t* const left, + const uint32_t* const top); +uint32_t VP8LPredictor10_C(const uint32_t* const left, + const uint32_t* const top); +uint32_t VP8LPredictor11_C(const uint32_t* const left, + const uint32_t* const top); +uint32_t VP8LPredictor12_C(const uint32_t* const left, + const uint32_t* const top); +uint32_t VP8LPredictor13_C(const uint32_t* const left, + const uint32_t* const top); + +// These Add/Sub function expects upper[-1] and out[-1] to be readable. +typedef void (*VP8LPredictorAddSubFunc)(const uint32_t* in, + const uint32_t* upper, int num_pixels, + uint32_t* WEBP_RESTRICT out); +extern VP8LPredictorAddSubFunc VP8LPredictorsAdd[16]; +extern VP8LPredictorAddSubFunc VP8LPredictorsAdd_C[16]; +extern VP8LPredictorAddSubFunc VP8LPredictorsAdd_SSE[16]; + +typedef void (*VP8LProcessDecBlueAndRedFunc)(const uint32_t* src, + int num_pixels, uint32_t* dst); +extern VP8LProcessDecBlueAndRedFunc VP8LAddGreenToBlueAndRed; +extern VP8LProcessDecBlueAndRedFunc VP8LAddGreenToBlueAndRed_SSE; + +typedef struct { + // Note: the members are uint8_t, so that any negative values are + // automatically converted to "mod 256" values. + uint8_t green_to_red; + uint8_t green_to_blue; + uint8_t red_to_blue; +} VP8LMultipliers; +typedef void (*VP8LTransformColorInverseFunc)(const VP8LMultipliers* const m, + const uint32_t* src, + int num_pixels, uint32_t* dst); +extern VP8LTransformColorInverseFunc VP8LTransformColorInverse; +extern VP8LTransformColorInverseFunc VP8LTransformColorInverse_SSE; + +struct VP8LTransform; // Defined in dec/vp8li.h. + +// Performs inverse transform of data given transform information, start and end +// rows. Transform will be applied to rows [row_start, row_end[. +// The *in and *out pointers refer to source and destination data respectively +// corresponding to the intermediate row (row_start). +void VP8LInverseTransform(const struct VP8LTransform* const transform, + int row_start, int row_end, + const uint32_t* const in, uint32_t* const out); + +// Color space conversion. +typedef void (*VP8LConvertFunc)(const uint32_t* WEBP_RESTRICT src, + int num_pixels, uint8_t* WEBP_RESTRICT dst); +extern VP8LConvertFunc VP8LConvertBGRAToRGB; +extern VP8LConvertFunc VP8LConvertBGRAToRGBA; +extern VP8LConvertFunc VP8LConvertBGRAToRGBA4444; +extern VP8LConvertFunc VP8LConvertBGRAToRGB565; +extern VP8LConvertFunc VP8LConvertBGRAToBGR; +extern VP8LConvertFunc VP8LConvertBGRAToRGB_SSE; +extern VP8LConvertFunc VP8LConvertBGRAToRGBA_SSE; + +// Converts from BGRA to other color spaces. +void VP8LConvertFromBGRA(const uint32_t* const in_data, int num_pixels, + WEBP_CSP_MODE out_colorspace, uint8_t* const rgba); + +typedef void (*VP8LMapARGBFunc)(const uint32_t* src, + const uint32_t* const color_map, + uint32_t* dst, int y_start, + int y_end, int width); +typedef void (*VP8LMapAlphaFunc)(const uint8_t* src, + const uint32_t* const color_map, + uint8_t* dst, int y_start, + int y_end, int width); + +extern VP8LMapARGBFunc VP8LMapColor32b; +extern VP8LMapAlphaFunc VP8LMapColor8b; + +// Similar to the static method ColorIndexInverseTransform() that is part of +// lossless.c, but used only for alpha decoding. It takes uint8_t (rather than +// uint32_t) arguments for 'src' and 'dst'. +void VP8LColorIndexInverseTransformAlpha( + const struct VP8LTransform* const transform, int y_start, int y_end, + const uint8_t* src, uint8_t* dst); + +// Expose some C-only fallback functions +void VP8LTransformColorInverse_C(const VP8LMultipliers* const m, + const uint32_t* src, int num_pixels, + uint32_t* dst); + +void VP8LConvertBGRAToRGB_C(const uint32_t* WEBP_RESTRICT src, int num_pixels, + uint8_t* WEBP_RESTRICT dst); +void VP8LConvertBGRAToRGBA_C(const uint32_t* WEBP_RESTRICT src, int num_pixels, + uint8_t* WEBP_RESTRICT dst); +void VP8LConvertBGRAToRGBA4444_C(const uint32_t* WEBP_RESTRICT src, + int num_pixels, uint8_t* WEBP_RESTRICT dst); +void VP8LConvertBGRAToRGB565_C(const uint32_t* WEBP_RESTRICT src, + int num_pixels, uint8_t* WEBP_RESTRICT dst); +void VP8LConvertBGRAToBGR_C(const uint32_t* WEBP_RESTRICT src, int num_pixels, + uint8_t* WEBP_RESTRICT dst); +void VP8LAddGreenToBlueAndRed_C(const uint32_t* src, int num_pixels, + uint32_t* dst); + +// Must be called before calling any of the above methods. +void VP8LDspInit(void); + +//------------------------------------------------------------------------------ +// Encoding + +typedef void (*VP8LProcessEncBlueAndRedFunc)(uint32_t* dst, int num_pixels); +extern VP8LProcessEncBlueAndRedFunc VP8LSubtractGreenFromBlueAndRed; +extern VP8LProcessEncBlueAndRedFunc VP8LSubtractGreenFromBlueAndRed_SSE; +typedef void (*VP8LTransformColorFunc)( + const VP8LMultipliers* WEBP_RESTRICT const m, uint32_t* WEBP_RESTRICT dst, + int num_pixels); +extern VP8LTransformColorFunc VP8LTransformColor; +extern VP8LTransformColorFunc VP8LTransformColor_SSE; +typedef void (*VP8LCollectColorBlueTransformsFunc)( + const uint32_t* WEBP_RESTRICT argb, int stride, + int tile_width, int tile_height, + int green_to_blue, int red_to_blue, uint32_t histo[]); +extern VP8LCollectColorBlueTransformsFunc VP8LCollectColorBlueTransforms; +extern VP8LCollectColorBlueTransformsFunc VP8LCollectColorBlueTransforms_SSE; + +typedef void (*VP8LCollectColorRedTransformsFunc)( + const uint32_t* WEBP_RESTRICT argb, int stride, + int tile_width, int tile_height, + int green_to_red, uint32_t histo[]); +extern VP8LCollectColorRedTransformsFunc VP8LCollectColorRedTransforms; +extern VP8LCollectColorRedTransformsFunc VP8LCollectColorRedTransforms_SSE; + +// Expose some C-only fallback functions +void VP8LTransformColor_C(const VP8LMultipliers* WEBP_RESTRICT const m, + uint32_t* WEBP_RESTRICT data, int num_pixels); +void VP8LSubtractGreenFromBlueAndRed_C(uint32_t* argb_data, int num_pixels); +void VP8LCollectColorRedTransforms_C(const uint32_t* WEBP_RESTRICT argb, + int stride, + int tile_width, int tile_height, + int green_to_red, uint32_t histo[]); +void VP8LCollectColorBlueTransforms_C(const uint32_t* WEBP_RESTRICT argb, + int stride, + int tile_width, int tile_height, + int green_to_blue, int red_to_blue, + uint32_t histo[]); + +extern VP8LPredictorAddSubFunc VP8LPredictorsSub[16]; +extern VP8LPredictorAddSubFunc VP8LPredictorsSub_C[16]; +extern VP8LPredictorAddSubFunc VP8LPredictorsSub_SSE[16]; + +// ----------------------------------------------------------------------------- +// Huffman-cost related functions. + +typedef uint32_t (*VP8LCostFunc)(const uint32_t* population, int length); +typedef uint64_t (*VP8LCombinedShannonEntropyFunc)(const uint32_t X[256], + const uint32_t Y[256]); +typedef uint64_t (*VP8LShannonEntropyFunc)(const uint32_t* X, int length); + +extern VP8LCostFunc VP8LExtraCost; +extern VP8LCombinedShannonEntropyFunc VP8LCombinedShannonEntropy; +extern VP8LShannonEntropyFunc VP8LShannonEntropy; + +typedef struct { // small struct to hold counters + int counts[2]; // index: 0=zero streak, 1=non-zero streak + int streaks[2][2]; // [zero/non-zero][streak<3 / streak>=3] +} VP8LStreaks; + +typedef struct { // small struct to hold bit entropy results + uint64_t entropy; // entropy + uint32_t sum; // sum of the population + int nonzeros; // number of non-zero elements in the population + uint32_t max_val; // maximum value in the population + uint32_t nonzero_code; // index of the last non-zero in the population +} VP8LBitEntropy; + +void VP8LBitEntropyInit(VP8LBitEntropy* const entropy); + +// Get the combined symbol bit entropy and Huffman cost stats for the +// distributions 'X' and 'Y'. Those results can then be refined according to +// codec specific heuristics. +typedef void (*VP8LGetCombinedEntropyUnrefinedFunc)( + const uint32_t X[], const uint32_t Y[], int length, + VP8LBitEntropy* WEBP_RESTRICT const bit_entropy, + VP8LStreaks* WEBP_RESTRICT const stats); +extern VP8LGetCombinedEntropyUnrefinedFunc VP8LGetCombinedEntropyUnrefined; + +// Get the entropy for the distribution 'X'. +typedef void (*VP8LGetEntropyUnrefinedFunc)( + const uint32_t X[], int length, + VP8LBitEntropy* WEBP_RESTRICT const bit_entropy, + VP8LStreaks* WEBP_RESTRICT const stats); +extern VP8LGetEntropyUnrefinedFunc VP8LGetEntropyUnrefined; + +void VP8LBitsEntropyUnrefined(const uint32_t* WEBP_RESTRICT const array, int n, + VP8LBitEntropy* WEBP_RESTRICT const entropy); + +typedef void (*VP8LAddVectorFunc)(const uint32_t* WEBP_RESTRICT a, + const uint32_t* WEBP_RESTRICT b, + uint32_t* WEBP_RESTRICT out, int size); +extern VP8LAddVectorFunc VP8LAddVector; +typedef void (*VP8LAddVectorEqFunc)(const uint32_t* WEBP_RESTRICT a, + uint32_t* WEBP_RESTRICT out, int size); +extern VP8LAddVectorEqFunc VP8LAddVectorEq; + +// ----------------------------------------------------------------------------- +// PrefixEncode() + +typedef int (*VP8LVectorMismatchFunc)(const uint32_t* const array1, + const uint32_t* const array2, int length); +// Returns the first index where array1 and array2 are different. +extern VP8LVectorMismatchFunc VP8LVectorMismatch; + +typedef void (*VP8LBundleColorMapFunc)(const uint8_t* WEBP_RESTRICT const row, + int width, int xbits, + uint32_t* WEBP_RESTRICT dst); +extern VP8LBundleColorMapFunc VP8LBundleColorMap; +extern VP8LBundleColorMapFunc VP8LBundleColorMap_SSE; +void VP8LBundleColorMap_C(const uint8_t* WEBP_RESTRICT const row, + int width, int xbits, uint32_t* WEBP_RESTRICT dst); + +// Must be called before calling any of the above methods. +void VP8LEncDspInit(void); + +//------------------------------------------------------------------------------ + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif // WEBP_DSP_LOSSLESS_H_ diff --git a/packages/core/src/zig/vendor/libwebp/src/dsp/lossless_avx2.c b/packages/core/src/zig/vendor/libwebp/src/dsp/lossless_avx2.c new file mode 100644 index 0000000000..dc866049e8 --- /dev/null +++ b/packages/core/src/zig/vendor/libwebp/src/dsp/lossless_avx2.c @@ -0,0 +1,443 @@ +// Copyright 2025 Google Inc. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the COPYING file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +// ----------------------------------------------------------------------------- +// +// AVX2 variant of methods for lossless decoder +// +// Author: Vincent Rabaud (vrabaud@google.com) + +#include "src/dsp/dsp.h" + +#if defined(WEBP_USE_AVX2) + +#include +#include + +#include "src/dsp/cpu.h" +#include "src/dsp/lossless.h" +#include "src/webp/format_constants.h" +#include "src/webp/types.h" + +//------------------------------------------------------------------------------ +// Predictor Transform + +static WEBP_INLINE void Average2_m256i(const __m256i* const a0, + const __m256i* const a1, + __m256i* const avg) { + // (a + b) >> 1 = ((a + b + 1) >> 1) - ((a ^ b) & 1) + const __m256i ones = _mm256_set1_epi8(1); + const __m256i avg1 = _mm256_avg_epu8(*a0, *a1); + const __m256i one = _mm256_and_si256(_mm256_xor_si256(*a0, *a1), ones); + *avg = _mm256_sub_epi8(avg1, one); +} + +// Batch versions of those functions. + +// Predictor0: ARGB_BLACK. +static void PredictorAdd0_AVX2(const uint32_t* in, const uint32_t* upper, + int num_pixels, uint32_t* WEBP_RESTRICT out) { + int i; + const __m256i black = _mm256_set1_epi32((int)ARGB_BLACK); + for (i = 0; i + 8 <= num_pixels; i += 8) { + const __m256i src = _mm256_loadu_si256((const __m256i*)&in[i]); + const __m256i res = _mm256_add_epi8(src, black); + _mm256_storeu_si256((__m256i*)&out[i], res); + } + if (i != num_pixels) { + VP8LPredictorsAdd_SSE[0](in + i, NULL, num_pixels - i, out + i); + } + (void)upper; +} + +// Predictor1: left. +static void PredictorAdd1_AVX2(const uint32_t* in, const uint32_t* upper, + int num_pixels, uint32_t* WEBP_RESTRICT out) { + int i; + __m256i prev = _mm256_set1_epi32((int)out[-1]); + for (i = 0; i + 8 <= num_pixels; i += 8) { + // h | g | f | e | d | c | b | a + const __m256i src = _mm256_loadu_si256((const __m256i*)&in[i]); + // g | f | e | 0 | c | b | a | 0 + const __m256i shift0 = _mm256_slli_si256(src, 4); + // g + h | f + g | e + f | e | c + d | b + c | a + b | a + const __m256i sum0 = _mm256_add_epi8(src, shift0); + // e + f | e | 0 | 0 | a + b | a | 0 | 0 + const __m256i shift1 = _mm256_slli_si256(sum0, 8); + // e + f + g + h | e + f + g | e + f | e | a + b + c + d | a + b + c | a + b + // | a + const __m256i sum1 = _mm256_add_epi8(sum0, shift1); + // Add a + b + c + d to the upper lane. + const int32_t sum_abcd = _mm256_extract_epi32(sum1, 3); + const __m256i sum2 = _mm256_add_epi8( + sum1, + _mm256_set_epi32(sum_abcd, sum_abcd, sum_abcd, sum_abcd, 0, 0, 0, 0)); + + const __m256i res = _mm256_add_epi8(sum2, prev); + _mm256_storeu_si256((__m256i*)&out[i], res); + // replicate last res output in prev. + prev = _mm256_permutevar8x32_epi32( + res, _mm256_set_epi32(7, 7, 7, 7, 7, 7, 7, 7)); + } + if (i != num_pixels) { + VP8LPredictorsAdd_SSE[1](in + i, upper + i, num_pixels - i, out + i); + } +} + +// Macro that adds 32-bit integers from IN using mod 256 arithmetic +// per 8 bit channel. +#define GENERATE_PREDICTOR_1(X, IN) \ + static void PredictorAdd##X##_AVX2(const uint32_t* in, \ + const uint32_t* upper, int num_pixels, \ + uint32_t* WEBP_RESTRICT out) { \ + int i; \ + for (i = 0; i + 8 <= num_pixels; i += 8) { \ + const __m256i src = _mm256_loadu_si256((const __m256i*)&in[i]); \ + const __m256i other = _mm256_loadu_si256((const __m256i*)&(IN)); \ + const __m256i res = _mm256_add_epi8(src, other); \ + _mm256_storeu_si256((__m256i*)&out[i], res); \ + } \ + if (i != num_pixels) { \ + VP8LPredictorsAdd_SSE[(X)](in + i, upper + i, num_pixels - i, out + i); \ + } \ + } + +// Predictor2: Top. +GENERATE_PREDICTOR_1(2, upper[i]) +// Predictor3: Top-right. +GENERATE_PREDICTOR_1(3, upper[i + 1]) +// Predictor4: Top-left. +GENERATE_PREDICTOR_1(4, upper[i - 1]) +#undef GENERATE_PREDICTOR_1 + +// Due to averages with integers, values cannot be accumulated in parallel for +// predictors 5 to 7. + +#define GENERATE_PREDICTOR_2(X, IN) \ + static void PredictorAdd##X##_AVX2(const uint32_t* in, \ + const uint32_t* upper, int num_pixels, \ + uint32_t* WEBP_RESTRICT out) { \ + int i; \ + for (i = 0; i + 8 <= num_pixels; i += 8) { \ + const __m256i Tother = _mm256_loadu_si256((const __m256i*)&(IN)); \ + const __m256i T = _mm256_loadu_si256((const __m256i*)&upper[i]); \ + const __m256i src = _mm256_loadu_si256((const __m256i*)&in[i]); \ + __m256i avg, res; \ + Average2_m256i(&T, &Tother, &avg); \ + res = _mm256_add_epi8(avg, src); \ + _mm256_storeu_si256((__m256i*)&out[i], res); \ + } \ + if (i != num_pixels) { \ + VP8LPredictorsAdd_SSE[(X)](in + i, upper + i, num_pixels - i, out + i); \ + } \ + } +// Predictor8: average TL T. +GENERATE_PREDICTOR_2(8, upper[i - 1]) +// Predictor9: average T TR. +GENERATE_PREDICTOR_2(9, upper[i + 1]) +#undef GENERATE_PREDICTOR_2 + +// Predictor10: average of (average of (L,TL), average of (T, TR)). +#define DO_PRED10(OUT) \ + do { \ + __m256i avgLTL, avg; \ + Average2_m256i(&L, &TL, &avgLTL); \ + Average2_m256i(&avgTTR, &avgLTL, &avg); \ + L = _mm256_add_epi8(avg, src); \ + out[i + (OUT)] = (uint32_t)_mm256_cvtsi256_si32(L); \ + } while (0) + +#define DO_PRED10_SHIFT \ + do { \ + /* Rotate the pre-computed values for the next iteration.*/ \ + avgTTR = _mm256_srli_si256(avgTTR, 4); \ + TL = _mm256_srli_si256(TL, 4); \ + src = _mm256_srli_si256(src, 4); \ + } while (0) + +static void PredictorAdd10_AVX2(const uint32_t* in, const uint32_t* upper, + int num_pixels, uint32_t* WEBP_RESTRICT out) { + int i, j; + __m256i L = _mm256_setr_epi32((int)out[-1], 0, 0, 0, 0, 0, 0, 0); + for (i = 0; i + 8 <= num_pixels; i += 8) { + __m256i src = _mm256_loadu_si256((const __m256i*)&in[i]); + __m256i TL = _mm256_loadu_si256((const __m256i*)&upper[i - 1]); + const __m256i T = _mm256_loadu_si256((const __m256i*)&upper[i]); + const __m256i TR = _mm256_loadu_si256((const __m256i*)&upper[i + 1]); + __m256i avgTTR; + Average2_m256i(&T, &TR, &avgTTR); + { + const __m256i avgTTR_bak = avgTTR; + const __m256i TL_bak = TL; + const __m256i src_bak = src; + for (j = 0; j < 4; ++j) { + DO_PRED10(j); + DO_PRED10_SHIFT; + } + avgTTR = _mm256_permute2x128_si256(avgTTR_bak, avgTTR_bak, 1); + TL = _mm256_permute2x128_si256(TL_bak, TL_bak, 1); + src = _mm256_permute2x128_si256(src_bak, src_bak, 1); + for (; j < 8; ++j) { + DO_PRED10(j); + DO_PRED10_SHIFT; + } + } + } + if (i != num_pixels) { + VP8LPredictorsAdd_SSE[10](in + i, upper + i, num_pixels - i, out + i); + } +} +#undef DO_PRED10 +#undef DO_PRED10_SHIFT + +// Predictor11: select. +#define DO_PRED11(OUT) \ + do { \ + const __m256i L_lo = _mm256_unpacklo_epi32(L, T); \ + const __m256i TL_lo = _mm256_unpacklo_epi32(TL, T); \ + const __m256i pb = _mm256_sad_epu8(L_lo, TL_lo); /* pb = sum |L-TL|*/ \ + const __m256i mask = _mm256_cmpgt_epi32(pb, pa); \ + const __m256i A = _mm256_and_si256(mask, L); \ + const __m256i B = _mm256_andnot_si256(mask, T); \ + const __m256i pred = _mm256_or_si256(A, B); /* pred = (pa > b)? L : T*/ \ + L = _mm256_add_epi8(src, pred); \ + out[i + (OUT)] = (uint32_t)_mm256_cvtsi256_si32(L); \ + } while (0) + +#define DO_PRED11_SHIFT \ + do { \ + /* Shift the pre-computed value for the next iteration.*/ \ + T = _mm256_srli_si256(T, 4); \ + TL = _mm256_srli_si256(TL, 4); \ + src = _mm256_srli_si256(src, 4); \ + pa = _mm256_srli_si256(pa, 4); \ + } while (0) + +static void PredictorAdd11_AVX2(const uint32_t* in, const uint32_t* upper, + int num_pixels, uint32_t* WEBP_RESTRICT out) { + int i, j; + __m256i pa; + __m256i L = _mm256_setr_epi32((int)out[-1], 0, 0, 0, 0, 0, 0, 0); + for (i = 0; i + 8 <= num_pixels; i += 8) { + __m256i T = _mm256_loadu_si256((const __m256i*)&upper[i]); + __m256i TL = _mm256_loadu_si256((const __m256i*)&upper[i - 1]); + __m256i src = _mm256_loadu_si256((const __m256i*)&in[i]); + { + // We can unpack with any value on the upper 32 bits, provided it's the + // same on both operands (so that their sum of abs diff is zero). Here we + // use T. + const __m256i T_lo = _mm256_unpacklo_epi32(T, T); + const __m256i TL_lo = _mm256_unpacklo_epi32(TL, T); + const __m256i T_hi = _mm256_unpackhi_epi32(T, T); + const __m256i TL_hi = _mm256_unpackhi_epi32(TL, T); + const __m256i s_lo = _mm256_sad_epu8(T_lo, TL_lo); + const __m256i s_hi = _mm256_sad_epu8(T_hi, TL_hi); + pa = _mm256_packs_epi32(s_lo, s_hi); // pa = sum |T-TL| + } + { + const __m256i T_bak = T; + const __m256i TL_bak = TL; + const __m256i src_bak = src; + const __m256i pa_bak = pa; + for (j = 0; j < 4; ++j) { + DO_PRED11(j); + DO_PRED11_SHIFT; + } + T = _mm256_permute2x128_si256(T_bak, T_bak, 1); + TL = _mm256_permute2x128_si256(TL_bak, TL_bak, 1); + src = _mm256_permute2x128_si256(src_bak, src_bak, 1); + pa = _mm256_permute2x128_si256(pa_bak, pa_bak, 1); + for (; j < 8; ++j) { + DO_PRED11(j); + DO_PRED11_SHIFT; + } + } + } + if (i != num_pixels) { + VP8LPredictorsAdd_SSE[11](in + i, upper + i, num_pixels - i, out + i); + } +} +#undef DO_PRED11 +#undef DO_PRED11_SHIFT + +// Predictor12: ClampedAddSubtractFull. +#define DO_PRED12(DIFF, OUT) \ + do { \ + const __m256i all = _mm256_add_epi16(L, (DIFF)); \ + const __m256i alls = _mm256_packus_epi16(all, all); \ + const __m256i res = _mm256_add_epi8(src, alls); \ + out[i + (OUT)] = (uint32_t)_mm256_cvtsi256_si32(res); \ + L = _mm256_unpacklo_epi8(res, zero); \ + } while (0) + +#define DO_PRED12_SHIFT(DIFF, LANE) \ + do { \ + /* Shift the pre-computed value for the next iteration.*/ \ + if ((LANE) == 0) (DIFF) = _mm256_srli_si256(DIFF, 8); \ + src = _mm256_srli_si256(src, 4); \ + } while (0) + +static void PredictorAdd12_AVX2(const uint32_t* in, const uint32_t* upper, + int num_pixels, uint32_t* WEBP_RESTRICT out) { + int i; + const __m256i zero = _mm256_setzero_si256(); + const __m256i L8 = _mm256_setr_epi32((int)out[-1], 0, 0, 0, 0, 0, 0, 0); + __m256i L = _mm256_unpacklo_epi8(L8, zero); + for (i = 0; i + 8 <= num_pixels; i += 8) { + // Load 8 pixels at a time. + __m256i src = _mm256_loadu_si256((const __m256i*)&in[i]); + const __m256i T = _mm256_loadu_si256((const __m256i*)&upper[i]); + const __m256i T_lo = _mm256_unpacklo_epi8(T, zero); + const __m256i T_hi = _mm256_unpackhi_epi8(T, zero); + const __m256i TL = _mm256_loadu_si256((const __m256i*)&upper[i - 1]); + const __m256i TL_lo = _mm256_unpacklo_epi8(TL, zero); + const __m256i TL_hi = _mm256_unpackhi_epi8(TL, zero); + __m256i diff_lo = _mm256_sub_epi16(T_lo, TL_lo); + __m256i diff_hi = _mm256_sub_epi16(T_hi, TL_hi); + const __m256i diff_lo_bak = diff_lo; + const __m256i diff_hi_bak = diff_hi; + const __m256i src_bak = src; + DO_PRED12(diff_lo, 0); + DO_PRED12_SHIFT(diff_lo, 0); + DO_PRED12(diff_lo, 1); + DO_PRED12_SHIFT(diff_lo, 0); + DO_PRED12(diff_hi, 2); + DO_PRED12_SHIFT(diff_hi, 0); + DO_PRED12(diff_hi, 3); + DO_PRED12_SHIFT(diff_hi, 0); + + // Process the upper lane. + diff_lo = _mm256_permute2x128_si256(diff_lo_bak, diff_lo_bak, 1); + diff_hi = _mm256_permute2x128_si256(diff_hi_bak, diff_hi_bak, 1); + src = _mm256_permute2x128_si256(src_bak, src_bak, 1); + + DO_PRED12(diff_lo, 4); + DO_PRED12_SHIFT(diff_lo, 0); + DO_PRED12(diff_lo, 5); + DO_PRED12_SHIFT(diff_lo, 1); + DO_PRED12(diff_hi, 6); + DO_PRED12_SHIFT(diff_hi, 0); + DO_PRED12(diff_hi, 7); + } + if (i != num_pixels) { + VP8LPredictorsAdd_SSE[12](in + i, upper + i, num_pixels - i, out + i); + } +} +#undef DO_PRED12 +#undef DO_PRED12_SHIFT + +// Due to averages with integers, values cannot be accumulated in parallel for +// predictors 13. + +//------------------------------------------------------------------------------ +// Subtract-Green Transform + +static void AddGreenToBlueAndRed_AVX2(const uint32_t* const src, int num_pixels, + uint32_t* dst) { + int i; + const __m256i kCstShuffle = _mm256_set_epi8( + -1, 29, -1, 29, -1, 25, -1, 25, -1, 21, -1, 21, -1, 17, -1, 17, -1, 13, + -1, 13, -1, 9, -1, 9, -1, 5, -1, 5, -1, 1, -1, 1); + for (i = 0; i + 8 <= num_pixels; i += 8) { + const __m256i in = _mm256_loadu_si256((const __m256i*)&src[i]); // argb + const __m256i in_0g0g = _mm256_shuffle_epi8(in, kCstShuffle); // 0g0g + const __m256i out = _mm256_add_epi8(in, in_0g0g); + _mm256_storeu_si256((__m256i*)&dst[i], out); + } + // fallthrough and finish off with SSE. + if (i != num_pixels) { + VP8LAddGreenToBlueAndRed_SSE(src + i, num_pixels - i, dst + i); + } +} + +//------------------------------------------------------------------------------ +// Color Transform + +static void TransformColorInverse_AVX2(const VP8LMultipliers* const m, + const uint32_t* const src, + int num_pixels, uint32_t* dst) { +// sign-extended multiplying constants, pre-shifted by 5. +#define CST(X) (((int16_t)(m->X << 8)) >> 5) // sign-extend + const __m256i mults_rb = + _mm256_set1_epi32((int)((uint32_t)CST(green_to_red) << 16 | + (CST(green_to_blue) & 0xffff))); + const __m256i mults_b2 = _mm256_set1_epi32(CST(red_to_blue)); +#undef CST + const __m256i mask_ag = _mm256_set1_epi32((int)0xff00ff00); + const __m256i perm1 = _mm256_setr_epi8( + -1, 1, -1, 1, -1, 5, -1, 5, -1, 9, -1, 9, -1, 13, -1, 13, -1, 17, -1, 17, + -1, 21, -1, 21, -1, 25, -1, 25, -1, 29, -1, 29); + const __m256i perm2 = _mm256_setr_epi8( + -1, 2, -1, -1, -1, 6, -1, -1, -1, 10, -1, -1, -1, 14, -1, -1, -1, 18, -1, + -1, -1, 22, -1, -1, -1, 26, -1, -1, -1, 30, -1, -1); + int i; + for (i = 0; i + 8 <= num_pixels; i += 8) { + const __m256i A = _mm256_loadu_si256((const __m256i*)(src + i)); + const __m256i B = _mm256_shuffle_epi8(A, perm1); // argb -> g0g0 + const __m256i C = _mm256_mulhi_epi16(B, mults_rb); + const __m256i D = _mm256_add_epi8(A, C); + const __m256i E = _mm256_shuffle_epi8(D, perm2); + const __m256i F = _mm256_mulhi_epi16(E, mults_b2); + const __m256i G = _mm256_add_epi8(D, F); + const __m256i out = _mm256_blendv_epi8(G, A, mask_ag); + _mm256_storeu_si256((__m256i*)&dst[i], out); + } + // Fall-back to SSE-version for left-overs. + if (i != num_pixels) { + VP8LTransformColorInverse_SSE(m, src + i, num_pixels - i, dst + i); + } +} + +//------------------------------------------------------------------------------ +// Color-space conversion functions + +static void ConvertBGRAToRGBA_AVX2(const uint32_t* WEBP_RESTRICT src, + int num_pixels, uint8_t* WEBP_RESTRICT dst) { + const __m256i* in = (const __m256i*)src; + __m256i* out = (__m256i*)dst; + while (num_pixels >= 8) { + const __m256i A = _mm256_loadu_si256(in++); + const __m256i B = _mm256_shuffle_epi8( + A, + _mm256_set_epi8(15, 12, 13, 14, 11, 8, 9, 10, 7, 4, 5, 6, 3, 0, 1, 2, + 15, 12, 13, 14, 11, 8, 9, 10, 7, 4, 5, 6, 3, 0, 1, 2)); + _mm256_storeu_si256(out++, B); + num_pixels -= 8; + } + // left-overs + if (num_pixels > 0) { + VP8LConvertBGRAToRGBA_SSE((const uint32_t*)in, num_pixels, (uint8_t*)out); + } +} + +//------------------------------------------------------------------------------ +// Entry point + +extern void VP8LDspInitAVX2(void); + +WEBP_TSAN_IGNORE_FUNCTION void VP8LDspInitAVX2(void) { + VP8LPredictorsAdd[0] = PredictorAdd0_AVX2; + VP8LPredictorsAdd[1] = PredictorAdd1_AVX2; + VP8LPredictorsAdd[2] = PredictorAdd2_AVX2; + VP8LPredictorsAdd[3] = PredictorAdd3_AVX2; + VP8LPredictorsAdd[4] = PredictorAdd4_AVX2; + VP8LPredictorsAdd[8] = PredictorAdd8_AVX2; + VP8LPredictorsAdd[9] = PredictorAdd9_AVX2; + VP8LPredictorsAdd[10] = PredictorAdd10_AVX2; + VP8LPredictorsAdd[11] = PredictorAdd11_AVX2; + VP8LPredictorsAdd[12] = PredictorAdd12_AVX2; + + VP8LAddGreenToBlueAndRed = AddGreenToBlueAndRed_AVX2; + VP8LTransformColorInverse = TransformColorInverse_AVX2; + VP8LConvertBGRAToRGBA = ConvertBGRAToRGBA_AVX2; +} + +#else // !WEBP_USE_AVX2 + +WEBP_DSP_INIT_STUB(VP8LDspInitAVX2) + +#endif // WEBP_USE_AVX2 diff --git a/packages/core/src/zig/vendor/libwebp/src/dsp/lossless_common.h b/packages/core/src/zig/vendor/libwebp/src/dsp/lossless_common.h new file mode 100644 index 0000000000..c856679d1e --- /dev/null +++ b/packages/core/src/zig/vendor/libwebp/src/dsp/lossless_common.h @@ -0,0 +1,215 @@ +// Copyright 2012 Google Inc. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the COPYING file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +// ----------------------------------------------------------------------------- +// +// Image transforms and color space conversion methods for lossless decoder. +// +// Authors: Vikas Arora (vikaas.arora@gmail.com) +// Jyrki Alakuijala (jyrki@google.com) +// Vincent Rabaud (vrabaud@google.com) + +#ifndef WEBP_DSP_LOSSLESS_COMMON_H_ +#define WEBP_DSP_LOSSLESS_COMMON_H_ + +#include +#include + +#include "src/dsp/cpu.h" +#include "src/utils/utils.h" +#include "src/webp/types.h" + +#ifdef __cplusplus +extern "C" { +#endif + +//------------------------------------------------------------------------------ +// Decoding + +// color mapping related functions. +static WEBP_INLINE uint32_t VP8GetARGBIndex(uint32_t idx) { + return (idx >> 8) & 0xff; +} + +static WEBP_INLINE uint8_t VP8GetAlphaIndex(uint8_t idx) { + return idx; +} + +static WEBP_INLINE uint32_t VP8GetARGBValue(uint32_t val) { + return val; +} + +static WEBP_INLINE uint8_t VP8GetAlphaValue(uint32_t val) { + return (val >> 8) & 0xff; +} + +//------------------------------------------------------------------------------ +// Misc methods. + +// Computes sampled size of 'size' when sampling using 'sampling bits'. +static WEBP_INLINE uint32_t VP8LSubSampleSize(uint32_t size, + uint32_t sampling_bits) { + return (size + (1 << sampling_bits) - 1) >> sampling_bits; +} + +// Converts near lossless quality into max number of bits shaved off. +static WEBP_INLINE int VP8LNearLosslessBits(int near_lossless_quality) { + // 100 -> 0 + // 80..99 -> 1 + // 60..79 -> 2 + // 40..59 -> 3 + // 20..39 -> 4 + // 0..19 -> 5 + return 5 - near_lossless_quality / 20; +} + +// ----------------------------------------------------------------------------- +// Faster logarithm for integers. Small values use a look-up table. + +// The threshold till approximate version of log_2 can be used. +// Practically, we can get rid of the call to log() as the two values match to +// very high degree (the ratio of these two is 0.99999x). +// Keeping a high threshold for now. +#define APPROX_LOG_WITH_CORRECTION_MAX 65536 +#define APPROX_LOG_MAX 4096 +// VP8LFastLog2 and VP8LFastSLog2 are used on elements from image histograms. +// The histogram values cannot exceed the maximum number of pixels, which +// is (1 << 14) * (1 << 14). Therefore S * log(S) < (1 << 33). +// No more than 32 bits of precision should be chosen. +// To match the original float implementation, 23 bits of precision are used. +#define LOG_2_PRECISION_BITS 23 +#define LOG_2_RECIPROCAL 1.44269504088896338700465094007086 +// LOG_2_RECIPROCAL * (1 << LOG_2_PRECISION_BITS) +#define LOG_2_RECIPROCAL_FIXED_DOUBLE 12102203.161561485379934310913085937500 +#define LOG_2_RECIPROCAL_FIXED ((uint64_t)12102203) +#define LOG_LOOKUP_IDX_MAX 256 +extern const uint32_t kLog2Table[LOG_LOOKUP_IDX_MAX]; +extern const uint64_t kSLog2Table[LOG_LOOKUP_IDX_MAX]; +typedef uint32_t (*VP8LFastLog2SlowFunc)(uint32_t v); +typedef uint64_t (*VP8LFastSLog2SlowFunc)(uint32_t v); + +extern VP8LFastLog2SlowFunc VP8LFastLog2Slow; +extern VP8LFastSLog2SlowFunc VP8LFastSLog2Slow; + +static WEBP_INLINE uint32_t VP8LFastLog2(uint32_t v) { + return (v < LOG_LOOKUP_IDX_MAX) ? kLog2Table[v] : VP8LFastLog2Slow(v); +} +// Fast calculation of v * log2(v) for integer input. +static WEBP_INLINE uint64_t VP8LFastSLog2(uint32_t v) { + return (v < LOG_LOOKUP_IDX_MAX) ? kSLog2Table[v] : VP8LFastSLog2Slow(v); +} + +static WEBP_INLINE uint64_t RightShiftRound(uint64_t v, uint32_t shift) { + return (v + (1ull << shift >> 1)) >> shift; +} + +static WEBP_INLINE int64_t DivRound(int64_t a, int64_t b) { + return ((a < 0) == (b < 0)) ? ((a + b / 2) / b) : ((a - b / 2) / b); +} + +#define WEBP_INT64_MAX ((int64_t)((1ull << 63) - 1)) +#define WEBP_UINT64_MAX (~0ull) + +// ----------------------------------------------------------------------------- +// PrefixEncode() + +// Splitting of distance and length codes into prefixes and +// extra bits. The prefixes are encoded with an entropy code +// while the extra bits are stored just as normal bits. +static WEBP_INLINE void VP8LPrefixEncodeBitsNoLUT(int distance, int* const code, + int* const extra_bits) { + const int highest_bit = BitsLog2Floor(--distance); + const int second_highest_bit = (distance >> (highest_bit - 1)) & 1; + *extra_bits = highest_bit - 1; + *code = 2 * highest_bit + second_highest_bit; +} + +static WEBP_INLINE void VP8LPrefixEncodeNoLUT(int distance, int* const code, + int* const extra_bits, + int* const extra_bits_value) { + const int highest_bit = BitsLog2Floor(--distance); + const int second_highest_bit = (distance >> (highest_bit - 1)) & 1; + *extra_bits = highest_bit - 1; + *extra_bits_value = distance & ((1 << *extra_bits) - 1); + *code = 2 * highest_bit + second_highest_bit; +} + +#define PREFIX_LOOKUP_IDX_MAX 512 +typedef struct { + int8_t code; + int8_t extra_bits; +} VP8LPrefixCode; + +// These tables are derived using VP8LPrefixEncodeNoLUT. +extern const VP8LPrefixCode kPrefixEncodeCode[PREFIX_LOOKUP_IDX_MAX]; +extern const uint8_t kPrefixEncodeExtraBitsValue[PREFIX_LOOKUP_IDX_MAX]; +static WEBP_INLINE void VP8LPrefixEncodeBits(int distance, int* const code, + int* const extra_bits) { + if (distance < PREFIX_LOOKUP_IDX_MAX) { + const VP8LPrefixCode prefix_code = kPrefixEncodeCode[distance]; + *code = prefix_code.code; + *extra_bits = prefix_code.extra_bits; + } else { + VP8LPrefixEncodeBitsNoLUT(distance, code, extra_bits); + } +} + +static WEBP_INLINE void VP8LPrefixEncode(int distance, int* const code, + int* const extra_bits, + int* const extra_bits_value) { + if (distance < PREFIX_LOOKUP_IDX_MAX) { + const VP8LPrefixCode prefix_code = kPrefixEncodeCode[distance]; + *code = prefix_code.code; + *extra_bits = prefix_code.extra_bits; + *extra_bits_value = kPrefixEncodeExtraBitsValue[distance]; + } else { + VP8LPrefixEncodeNoLUT(distance, code, extra_bits, extra_bits_value); + } +} + +// Sum of each component, mod 256. +static WEBP_UBSAN_IGNORE_UNSIGNED_OVERFLOW WEBP_INLINE +uint32_t VP8LAddPixels(uint32_t a, uint32_t b) { + const uint32_t alpha_and_green = (a & 0xff00ff00u) + (b & 0xff00ff00u); + const uint32_t red_and_blue = (a & 0x00ff00ffu) + (b & 0x00ff00ffu); + return (alpha_and_green & 0xff00ff00u) | (red_and_blue & 0x00ff00ffu); +} + +// Difference of each component, mod 256. +static WEBP_UBSAN_IGNORE_UNSIGNED_OVERFLOW WEBP_INLINE +uint32_t VP8LSubPixels(uint32_t a, uint32_t b) { + const uint32_t alpha_and_green = + 0x00ff00ffu + (a & 0xff00ff00u) - (b & 0xff00ff00u); + const uint32_t red_and_blue = + 0xff00ff00u + (a & 0x00ff00ffu) - (b & 0x00ff00ffu); + return (alpha_and_green & 0xff00ff00u) | (red_and_blue & 0x00ff00ffu); +} + +//------------------------------------------------------------------------------ +// Transform-related functions used in both encoding and decoding. + +// Macros used to create a batch predictor that iteratively uses a +// one-pixel predictor. + +// The predictor is added to the output pixel (which +// is therefore considered as a residual) to get the final prediction. +#define GENERATE_PREDICTOR_ADD(PREDICTOR, PREDICTOR_ADD) \ +static void PREDICTOR_ADD(const uint32_t* in, const uint32_t* upper, \ + int num_pixels, uint32_t* WEBP_RESTRICT out) { \ + int x; \ + assert(upper != NULL); \ + for (x = 0; x < num_pixels; ++x) { \ + const uint32_t pred = (PREDICTOR)(&out[x - 1], upper + x); \ + out[x] = VP8LAddPixels(in[x], pred); \ + } \ +} + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif // WEBP_DSP_LOSSLESS_COMMON_H_ diff --git a/packages/core/src/zig/vendor/libwebp/src/dsp/lossless_neon.c b/packages/core/src/zig/vendor/libwebp/src/dsp/lossless_neon.c new file mode 100644 index 0000000000..0a85edd4b8 --- /dev/null +++ b/packages/core/src/zig/vendor/libwebp/src/dsp/lossless_neon.c @@ -0,0 +1,646 @@ +// Copyright 2014 Google Inc. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the COPYING file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +// ----------------------------------------------------------------------------- +// +// NEON variant of methods for lossless decoder +// +// Author: Skal (pascal.massimino@gmail.com) + +#include "src/dsp/dsp.h" + +#if defined(WEBP_USE_NEON) + +#include + +#include "src/dsp/lossless.h" +#include "src/dsp/neon.h" +#include "src/webp/format_constants.h" + +//------------------------------------------------------------------------------ +// Colorspace conversion functions + +#if !defined(WORK_AROUND_GCC) +// gcc 4.6.0 had some trouble (NDK-r9) with this code. We only use it for +// gcc-4.8.x at least. +static void ConvertBGRAToRGBA_NEON(const uint32_t* WEBP_RESTRICT src, + int num_pixels, uint8_t* WEBP_RESTRICT dst) { + const uint32_t* const end = src + (num_pixels & ~15); + for (; src < end; src += 16) { + uint8x16x4_t pixel = vld4q_u8((uint8_t*)src); + // swap B and R. (VSWP d0,d2 has no intrinsics equivalent!) + const uint8x16_t tmp = pixel.val[0]; + pixel.val[0] = pixel.val[2]; + pixel.val[2] = tmp; + vst4q_u8(dst, pixel); + dst += 64; + } + VP8LConvertBGRAToRGBA_C(src, num_pixels & 15, dst); // left-overs +} + +static void ConvertBGRAToBGR_NEON(const uint32_t* WEBP_RESTRICT src, + int num_pixels, uint8_t* WEBP_RESTRICT dst) { + const uint32_t* const end = src + (num_pixels & ~15); + for (; src < end; src += 16) { + const uint8x16x4_t pixel = vld4q_u8((uint8_t*)src); + const uint8x16x3_t tmp = { { pixel.val[0], pixel.val[1], pixel.val[2] } }; + vst3q_u8(dst, tmp); + dst += 48; + } + VP8LConvertBGRAToBGR_C(src, num_pixels & 15, dst); // left-overs +} + +static void ConvertBGRAToRGB_NEON(const uint32_t* WEBP_RESTRICT src, + int num_pixels, uint8_t* WEBP_RESTRICT dst) { + const uint32_t* const end = src + (num_pixels & ~15); + for (; src < end; src += 16) { + const uint8x16x4_t pixel = vld4q_u8((uint8_t*)src); + const uint8x16x3_t tmp = { { pixel.val[2], pixel.val[1], pixel.val[0] } }; + vst3q_u8(dst, tmp); + dst += 48; + } + VP8LConvertBGRAToRGB_C(src, num_pixels & 15, dst); // left-overs +} + +#else // WORK_AROUND_GCC + +// gcc-4.6.0 fallback + +static const uint8_t kRGBAShuffle[8] = { 2, 1, 0, 3, 6, 5, 4, 7 }; + +static void ConvertBGRAToRGBA_NEON(const uint32_t* WEBP_RESTRICT src, + int num_pixels, uint8_t* WEBP_RESTRICT dst) { + const uint32_t* const end = src + (num_pixels & ~1); + const uint8x8_t shuffle = vld1_u8(kRGBAShuffle); + for (; src < end; src += 2) { + const uint8x8_t pixels = vld1_u8((uint8_t*)src); + vst1_u8(dst, vtbl1_u8(pixels, shuffle)); + dst += 8; + } + VP8LConvertBGRAToRGBA_C(src, num_pixels & 1, dst); // left-overs +} + +static const uint8_t kBGRShuffle[3][8] = { + { 0, 1, 2, 4, 5, 6, 8, 9 }, + { 10, 12, 13, 14, 16, 17, 18, 20 }, + { 21, 22, 24, 25, 26, 28, 29, 30 } +}; + +static void ConvertBGRAToBGR_NEON(const uint32_t* WEBP_RESTRICT src, + int num_pixels, uint8_t* WEBP_RESTRICT dst) { + const uint32_t* const end = src + (num_pixels & ~7); + const uint8x8_t shuffle0 = vld1_u8(kBGRShuffle[0]); + const uint8x8_t shuffle1 = vld1_u8(kBGRShuffle[1]); + const uint8x8_t shuffle2 = vld1_u8(kBGRShuffle[2]); + for (; src < end; src += 8) { + uint8x8x4_t pixels; + INIT_VECTOR4(pixels, + vld1_u8((const uint8_t*)(src + 0)), + vld1_u8((const uint8_t*)(src + 2)), + vld1_u8((const uint8_t*)(src + 4)), + vld1_u8((const uint8_t*)(src + 6))); + vst1_u8(dst + 0, vtbl4_u8(pixels, shuffle0)); + vst1_u8(dst + 8, vtbl4_u8(pixels, shuffle1)); + vst1_u8(dst + 16, vtbl4_u8(pixels, shuffle2)); + dst += 8 * 3; + } + VP8LConvertBGRAToBGR_C(src, num_pixels & 7, dst); // left-overs +} + +static const uint8_t kRGBShuffle[3][8] = { + { 2, 1, 0, 6, 5, 4, 10, 9 }, + { 8, 14, 13, 12, 18, 17, 16, 22 }, + { 21, 20, 26, 25, 24, 30, 29, 28 } +}; + +static void ConvertBGRAToRGB_NEON(const uint32_t* WEBP_RESTRICT src, + int num_pixels, uint8_t* WEBP_RESTRICT dst) { + const uint32_t* const end = src + (num_pixels & ~7); + const uint8x8_t shuffle0 = vld1_u8(kRGBShuffle[0]); + const uint8x8_t shuffle1 = vld1_u8(kRGBShuffle[1]); + const uint8x8_t shuffle2 = vld1_u8(kRGBShuffle[2]); + for (; src < end; src += 8) { + uint8x8x4_t pixels; + INIT_VECTOR4(pixels, + vld1_u8((const uint8_t*)(src + 0)), + vld1_u8((const uint8_t*)(src + 2)), + vld1_u8((const uint8_t*)(src + 4)), + vld1_u8((const uint8_t*)(src + 6))); + vst1_u8(dst + 0, vtbl4_u8(pixels, shuffle0)); + vst1_u8(dst + 8, vtbl4_u8(pixels, shuffle1)); + vst1_u8(dst + 16, vtbl4_u8(pixels, shuffle2)); + dst += 8 * 3; + } + VP8LConvertBGRAToRGB_C(src, num_pixels & 7, dst); // left-overs +} + +#endif // !WORK_AROUND_GCC + +//------------------------------------------------------------------------------ +// Predictor Transform + +#define LOAD_U32_AS_U8(IN) vreinterpret_u8_u32(vdup_n_u32((IN))) +#define LOAD_U32P_AS_U8(IN) vreinterpret_u8_u32(vld1_u32((IN))) +#define LOADQ_U32_AS_U8(IN) vreinterpretq_u8_u32(vdupq_n_u32((IN))) +#define LOADQ_U32P_AS_U8(IN) vreinterpretq_u8_u32(vld1q_u32((IN))) +#define GET_U8_AS_U32(IN) vget_lane_u32(vreinterpret_u32_u8((IN)), 0) +#define GETQ_U8_AS_U32(IN) vgetq_lane_u32(vreinterpretq_u32_u8((IN)), 0) +#define STOREQ_U8_AS_U32P(OUT, IN) vst1q_u32((OUT), vreinterpretq_u32_u8((IN))) +#define ROTATE32_LEFT(L) vextq_u8((L), (L), 12) // D|C|B|A -> C|B|A|D + +static WEBP_INLINE uint8x8_t Average2_u8_NEON(uint32_t a0, uint32_t a1) { + const uint8x8_t A0 = LOAD_U32_AS_U8(a0); + const uint8x8_t A1 = LOAD_U32_AS_U8(a1); + return vhadd_u8(A0, A1); +} + +static WEBP_INLINE uint32_t ClampedAddSubtractHalf_NEON(uint32_t c0, + uint32_t c1, + uint32_t c2) { + const uint8x8_t avg = Average2_u8_NEON(c0, c1); + // Remove one to c2 when bigger than avg. + const uint8x8_t C2 = LOAD_U32_AS_U8(c2); + const uint8x8_t cmp = vcgt_u8(C2, avg); + const uint8x8_t C2_1 = vadd_u8(C2, cmp); + // Compute half of the difference between avg and c2. + const int8x8_t diff_avg = vreinterpret_s8_u8(vhsub_u8(avg, C2_1)); + // Compute the sum with avg and saturate. + const int16x8_t avg_16 = vreinterpretq_s16_u16(vmovl_u8(avg)); + const uint8x8_t res = vqmovun_s16(vaddw_s8(avg_16, diff_avg)); + const uint32_t output = GET_U8_AS_U32(res); + return output; +} + +static WEBP_INLINE uint32_t Average2_NEON(uint32_t a0, uint32_t a1) { + const uint8x8_t avg_u8x8 = Average2_u8_NEON(a0, a1); + const uint32_t avg = GET_U8_AS_U32(avg_u8x8); + return avg; +} + +static WEBP_INLINE uint32_t Average3_NEON(uint32_t a0, uint32_t a1, + uint32_t a2) { + const uint8x8_t avg0 = Average2_u8_NEON(a0, a2); + const uint8x8_t A1 = LOAD_U32_AS_U8(a1); + const uint32_t avg = GET_U8_AS_U32(vhadd_u8(avg0, A1)); + return avg; +} + +static uint32_t Predictor5_NEON(const uint32_t* const left, + const uint32_t* const top) { + return Average3_NEON(*left, top[0], top[1]); +} +static uint32_t Predictor6_NEON(const uint32_t* const left, + const uint32_t* const top) { + return Average2_NEON(*left, top[-1]); +} +static uint32_t Predictor7_NEON(const uint32_t* const left, + const uint32_t* const top) { + return Average2_NEON(*left, top[0]); +} +static uint32_t Predictor13_NEON(const uint32_t* const left, + const uint32_t* const top) { + return ClampedAddSubtractHalf_NEON(*left, top[0], top[-1]); +} + +// Batch versions of those functions. + +// Predictor0: ARGB_BLACK. +static void PredictorAdd0_NEON(const uint32_t* in, const uint32_t* upper, + int num_pixels, uint32_t* WEBP_RESTRICT out) { + int i; + const uint8x16_t black = vreinterpretq_u8_u32(vdupq_n_u32(ARGB_BLACK)); + for (i = 0; i + 4 <= num_pixels; i += 4) { + const uint8x16_t src = LOADQ_U32P_AS_U8(&in[i]); + const uint8x16_t res = vaddq_u8(src, black); + STOREQ_U8_AS_U32P(&out[i], res); + } + VP8LPredictorsAdd_C[0](in + i, upper + i, num_pixels - i, out + i); +} + +// Predictor1: left. +static void PredictorAdd1_NEON(const uint32_t* in, const uint32_t* upper, + int num_pixels, uint32_t* WEBP_RESTRICT out) { + int i; + const uint8x16_t zero = LOADQ_U32_AS_U8(0); + for (i = 0; i + 4 <= num_pixels; i += 4) { + // a | b | c | d + const uint8x16_t src = LOADQ_U32P_AS_U8(&in[i]); + // 0 | a | b | c + const uint8x16_t shift0 = vextq_u8(zero, src, 12); + // a | a + b | b + c | c + d + const uint8x16_t sum0 = vaddq_u8(src, shift0); + // 0 | 0 | a | a + b + const uint8x16_t shift1 = vextq_u8(zero, sum0, 8); + // a | a + b | a + b + c | a + b + c + d + const uint8x16_t sum1 = vaddq_u8(sum0, shift1); + const uint8x16_t prev = LOADQ_U32_AS_U8(out[i - 1]); + const uint8x16_t res = vaddq_u8(sum1, prev); + STOREQ_U8_AS_U32P(&out[i], res); + } + VP8LPredictorsAdd_C[1](in + i, upper + i, num_pixels - i, out + i); +} + +// Macro that adds 32-bit integers from IN using mod 256 arithmetic +// per 8 bit channel. +#define GENERATE_PREDICTOR_1(X, IN) \ +static void PredictorAdd##X##_NEON(const uint32_t* in, \ + const uint32_t* upper, int num_pixels, \ + uint32_t* WEBP_RESTRICT out) { \ + int i; \ + for (i = 0; i + 4 <= num_pixels; i += 4) { \ + const uint8x16_t src = LOADQ_U32P_AS_U8(&in[i]); \ + const uint8x16_t other = LOADQ_U32P_AS_U8(&(IN)); \ + const uint8x16_t res = vaddq_u8(src, other); \ + STOREQ_U8_AS_U32P(&out[i], res); \ + } \ + VP8LPredictorsAdd_C[(X)](in + i, upper + i, num_pixels - i, out + i); \ +} +// Predictor2: Top. +GENERATE_PREDICTOR_1(2, upper[i]) +// Predictor3: Top-right. +GENERATE_PREDICTOR_1(3, upper[i + 1]) +// Predictor4: Top-left. +GENERATE_PREDICTOR_1(4, upper[i - 1]) +#undef GENERATE_PREDICTOR_1 + +// Predictor5: average(average(left, TR), T) +#define DO_PRED5(LANE) do { \ + const uint8x16_t avgLTR = vhaddq_u8(L, TR); \ + const uint8x16_t avg = vhaddq_u8(avgLTR, T); \ + const uint8x16_t res = vaddq_u8(avg, src); \ + vst1q_lane_u32(&out[i + (LANE)], vreinterpretq_u32_u8(res), (LANE)); \ + L = ROTATE32_LEFT(res); \ +} while (0) + +static void PredictorAdd5_NEON(const uint32_t* in, const uint32_t* upper, + int num_pixels, uint32_t* WEBP_RESTRICT out) { + int i; + uint8x16_t L = LOADQ_U32_AS_U8(out[-1]); + for (i = 0; i + 4 <= num_pixels; i += 4) { + const uint8x16_t src = LOADQ_U32P_AS_U8(&in[i]); + const uint8x16_t T = LOADQ_U32P_AS_U8(&upper[i + 0]); + const uint8x16_t TR = LOADQ_U32P_AS_U8(&upper[i + 1]); + DO_PRED5(0); + DO_PRED5(1); + DO_PRED5(2); + DO_PRED5(3); + } + VP8LPredictorsAdd_C[5](in + i, upper + i, num_pixels - i, out + i); +} +#undef DO_PRED5 + +#define DO_PRED67(LANE) do { \ + const uint8x16_t avg = vhaddq_u8(L, top); \ + const uint8x16_t res = vaddq_u8(avg, src); \ + vst1q_lane_u32(&out[i + (LANE)], vreinterpretq_u32_u8(res), (LANE)); \ + L = ROTATE32_LEFT(res); \ +} while (0) + +// Predictor6: average(left, TL) +static void PredictorAdd6_NEON(const uint32_t* in, const uint32_t* upper, + int num_pixels, uint32_t* WEBP_RESTRICT out) { + int i; + uint8x16_t L = LOADQ_U32_AS_U8(out[-1]); + for (i = 0; i + 4 <= num_pixels; i += 4) { + const uint8x16_t src = LOADQ_U32P_AS_U8(&in[i]); + const uint8x16_t top = LOADQ_U32P_AS_U8(&upper[i - 1]); + DO_PRED67(0); + DO_PRED67(1); + DO_PRED67(2); + DO_PRED67(3); + } + VP8LPredictorsAdd_C[6](in + i, upper + i, num_pixels - i, out + i); +} + +// Predictor7: average(left, T) +static void PredictorAdd7_NEON(const uint32_t* in, const uint32_t* upper, + int num_pixels, uint32_t* WEBP_RESTRICT out) { + int i; + uint8x16_t L = LOADQ_U32_AS_U8(out[-1]); + for (i = 0; i + 4 <= num_pixels; i += 4) { + const uint8x16_t src = LOADQ_U32P_AS_U8(&in[i]); + const uint8x16_t top = LOADQ_U32P_AS_U8(&upper[i]); + DO_PRED67(0); + DO_PRED67(1); + DO_PRED67(2); + DO_PRED67(3); + } + VP8LPredictorsAdd_C[7](in + i, upper + i, num_pixels - i, out + i); +} +#undef DO_PRED67 + +#define GENERATE_PREDICTOR_2(X, IN) \ +static void PredictorAdd##X##_NEON(const uint32_t* in, \ + const uint32_t* upper, int num_pixels, \ + uint32_t* WEBP_RESTRICT out) { \ + int i; \ + for (i = 0; i + 4 <= num_pixels; i += 4) { \ + const uint8x16_t src = LOADQ_U32P_AS_U8(&in[i]); \ + const uint8x16_t Tother = LOADQ_U32P_AS_U8(&(IN)); \ + const uint8x16_t T = LOADQ_U32P_AS_U8(&upper[i]); \ + const uint8x16_t avg = vhaddq_u8(T, Tother); \ + const uint8x16_t res = vaddq_u8(avg, src); \ + STOREQ_U8_AS_U32P(&out[i], res); \ + } \ + VP8LPredictorsAdd_C[(X)](in + i, upper + i, num_pixels - i, out + i); \ +} +// Predictor8: average TL T. +GENERATE_PREDICTOR_2(8, upper[i - 1]) +// Predictor9: average T TR. +GENERATE_PREDICTOR_2(9, upper[i + 1]) +#undef GENERATE_PREDICTOR_2 + +// Predictor10: average of (average of (L,TL), average of (T, TR)). +#define DO_PRED10(LANE) do { \ + const uint8x16_t avgLTL = vhaddq_u8(L, TL); \ + const uint8x16_t avg = vhaddq_u8(avgTTR, avgLTL); \ + const uint8x16_t res = vaddq_u8(avg, src); \ + vst1q_lane_u32(&out[i + (LANE)], vreinterpretq_u32_u8(res), (LANE)); \ + L = ROTATE32_LEFT(res); \ +} while (0) + +static void PredictorAdd10_NEON(const uint32_t* in, const uint32_t* upper, + int num_pixels, uint32_t* WEBP_RESTRICT out) { + int i; + uint8x16_t L = LOADQ_U32_AS_U8(out[-1]); + for (i = 0; i + 4 <= num_pixels; i += 4) { + const uint8x16_t src = LOADQ_U32P_AS_U8(&in[i]); + const uint8x16_t TL = LOADQ_U32P_AS_U8(&upper[i - 1]); + const uint8x16_t T = LOADQ_U32P_AS_U8(&upper[i]); + const uint8x16_t TR = LOADQ_U32P_AS_U8(&upper[i + 1]); + const uint8x16_t avgTTR = vhaddq_u8(T, TR); + DO_PRED10(0); + DO_PRED10(1); + DO_PRED10(2); + DO_PRED10(3); + } + VP8LPredictorsAdd_C[10](in + i, upper + i, num_pixels - i, out + i); +} +#undef DO_PRED10 + +// Predictor11: select. +#define DO_PRED11(LANE) do { \ + const uint8x16_t sumLin = vaddq_u8(L, src); /* in + L */ \ + const uint8x16_t pLTL = vabdq_u8(L, TL); /* |L - TL| */ \ + const uint16x8_t sum_LTL = vpaddlq_u8(pLTL); \ + const uint32x4_t pa = vpaddlq_u16(sum_LTL); \ + const uint32x4_t mask = vcleq_u32(pa, pb); \ + const uint8x16_t res = vbslq_u8(vreinterpretq_u8_u32(mask), sumTin, sumLin); \ + vst1q_lane_u32(&out[i + (LANE)], vreinterpretq_u32_u8(res), (LANE)); \ + L = ROTATE32_LEFT(res); \ +} while (0) + +static void PredictorAdd11_NEON(const uint32_t* in, const uint32_t* upper, + int num_pixels, uint32_t* WEBP_RESTRICT out) { + int i; + uint8x16_t L = LOADQ_U32_AS_U8(out[-1]); + for (i = 0; i + 4 <= num_pixels; i += 4) { + const uint8x16_t T = LOADQ_U32P_AS_U8(&upper[i]); + const uint8x16_t TL = LOADQ_U32P_AS_U8(&upper[i - 1]); + const uint8x16_t pTTL = vabdq_u8(T, TL); // |T - TL| + const uint16x8_t sum_TTL = vpaddlq_u8(pTTL); + const uint32x4_t pb = vpaddlq_u16(sum_TTL); + const uint8x16_t src = LOADQ_U32P_AS_U8(&in[i]); + const uint8x16_t sumTin = vaddq_u8(T, src); // in + T + DO_PRED11(0); + DO_PRED11(1); + DO_PRED11(2); + DO_PRED11(3); + } + VP8LPredictorsAdd_C[11](in + i, upper + i, num_pixels - i, out + i); +} +#undef DO_PRED11 + +// Predictor12: ClampedAddSubtractFull. +#define DO_PRED12(DIFF, LANE) do { \ + const uint8x8_t pred = \ + vqmovun_s16(vaddq_s16(vreinterpretq_s16_u16(L), (DIFF))); \ + const uint8x8_t res = \ + vadd_u8(pred, (LANE <= 1) ? vget_low_u8(src) : vget_high_u8(src)); \ + const uint16x8_t res16 = vmovl_u8(res); \ + vst1_lane_u32(&out[i + (LANE)], vreinterpret_u32_u8(res), (LANE) & 1); \ + /* rotate in the left predictor for next iteration */ \ + L = vextq_u16(res16, res16, 4); \ +} while (0) + +static void PredictorAdd12_NEON(const uint32_t* in, const uint32_t* upper, + int num_pixels, uint32_t* WEBP_RESTRICT out) { + int i; + uint16x8_t L = vmovl_u8(LOAD_U32_AS_U8(out[-1])); + for (i = 0; i + 4 <= num_pixels; i += 4) { + // load four pixels of source + const uint8x16_t src = LOADQ_U32P_AS_U8(&in[i]); + // precompute the difference T - TL once for all, stored as s16 + const uint8x16_t TL = LOADQ_U32P_AS_U8(&upper[i - 1]); + const uint8x16_t T = LOADQ_U32P_AS_U8(&upper[i]); + const int16x8_t diff_lo = + vreinterpretq_s16_u16(vsubl_u8(vget_low_u8(T), vget_low_u8(TL))); + const int16x8_t diff_hi = + vreinterpretq_s16_u16(vsubl_u8(vget_high_u8(T), vget_high_u8(TL))); + // loop over the four reconstructed pixels + DO_PRED12(diff_lo, 0); + DO_PRED12(diff_lo, 1); + DO_PRED12(diff_hi, 2); + DO_PRED12(diff_hi, 3); + } + VP8LPredictorsAdd_C[12](in + i, upper + i, num_pixels - i, out + i); +} +#undef DO_PRED12 + +// Predictor13: ClampedAddSubtractHalf +#define DO_PRED13(LANE, LOW_OR_HI) do { \ + const uint8x16_t avg = vhaddq_u8(L, T); \ + const uint8x16_t cmp = vcgtq_u8(TL, avg); \ + const uint8x16_t TL_1 = vaddq_u8(TL, cmp); \ + /* Compute half of the difference between avg and TL'. */ \ + const int8x8_t diff_avg = \ + vreinterpret_s8_u8(LOW_OR_HI(vhsubq_u8(avg, TL_1))); \ + /* Compute the sum with avg and saturate. */ \ + const int16x8_t avg_16 = vreinterpretq_s16_u16(vmovl_u8(LOW_OR_HI(avg))); \ + const uint8x8_t delta = vqmovun_s16(vaddw_s8(avg_16, diff_avg)); \ + const uint8x8_t res = vadd_u8(LOW_OR_HI(src), delta); \ + const uint8x16_t res2 = vcombine_u8(res, res); \ + vst1_lane_u32(&out[i + (LANE)], vreinterpret_u32_u8(res), (LANE) & 1); \ + L = ROTATE32_LEFT(res2); \ +} while (0) + +static void PredictorAdd13_NEON(const uint32_t* in, const uint32_t* upper, + int num_pixels, uint32_t* WEBP_RESTRICT out) { + int i; + uint8x16_t L = LOADQ_U32_AS_U8(out[-1]); + for (i = 0; i + 4 <= num_pixels; i += 4) { + const uint8x16_t src = LOADQ_U32P_AS_U8(&in[i]); + const uint8x16_t T = LOADQ_U32P_AS_U8(&upper[i]); + const uint8x16_t TL = LOADQ_U32P_AS_U8(&upper[i - 1]); + DO_PRED13(0, vget_low_u8); + DO_PRED13(1, vget_low_u8); + DO_PRED13(2, vget_high_u8); + DO_PRED13(3, vget_high_u8); + } + VP8LPredictorsAdd_C[13](in + i, upper + i, num_pixels - i, out + i); +} +#undef DO_PRED13 + +#undef LOAD_U32_AS_U8 +#undef LOAD_U32P_AS_U8 +#undef LOADQ_U32_AS_U8 +#undef LOADQ_U32P_AS_U8 +#undef GET_U8_AS_U32 +#undef GETQ_U8_AS_U32 +#undef STOREQ_U8_AS_U32P +#undef ROTATE32_LEFT + +//------------------------------------------------------------------------------ +// Subtract-Green Transform + +// vtbl?_u8 are marked unavailable for iOS arm64 with Xcode < 6.3, use +// non-standard versions there. +#if defined(__APPLE__) && WEBP_AARCH64 && \ + defined(__apple_build_version__) && (__apple_build_version__< 6020037) +#define USE_VTBLQ +#endif + +#ifdef USE_VTBLQ +// 255 = byte will be zeroed +static const uint8_t kGreenShuffle[16] = { + 1, 255, 1, 255, 5, 255, 5, 255, 9, 255, 9, 255, 13, 255, 13, 255 +}; + +static WEBP_INLINE uint8x16_t DoGreenShuffle_NEON(const uint8x16_t argb, + const uint8x16_t shuffle) { + return vcombine_u8(vtbl1q_u8(argb, vget_low_u8(shuffle)), + vtbl1q_u8(argb, vget_high_u8(shuffle))); +} +#else // !USE_VTBLQ +// 255 = byte will be zeroed +static const uint8_t kGreenShuffle[8] = { 1, 255, 1, 255, 5, 255, 5, 255 }; + +static WEBP_INLINE uint8x16_t DoGreenShuffle_NEON(const uint8x16_t argb, + const uint8x8_t shuffle) { + return vcombine_u8(vtbl1_u8(vget_low_u8(argb), shuffle), + vtbl1_u8(vget_high_u8(argb), shuffle)); +} +#endif // USE_VTBLQ + +static void AddGreenToBlueAndRed_NEON(const uint32_t* src, int num_pixels, + uint32_t* dst) { + const uint32_t* const end = src + (num_pixels & ~3); +#ifdef USE_VTBLQ + const uint8x16_t shuffle = vld1q_u8(kGreenShuffle); +#else + const uint8x8_t shuffle = vld1_u8(kGreenShuffle); +#endif + for (; src < end; src += 4, dst += 4) { + const uint8x16_t argb = vld1q_u8((const uint8_t*)src); + const uint8x16_t greens = DoGreenShuffle_NEON(argb, shuffle); + vst1q_u8((uint8_t*)dst, vaddq_u8(argb, greens)); + } + // fallthrough and finish off with plain-C + VP8LAddGreenToBlueAndRed_C(src, num_pixels & 3, dst); +} + +//------------------------------------------------------------------------------ +// Color Transform + +static void TransformColorInverse_NEON(const VP8LMultipliers* const m, + const uint32_t* const src, + int num_pixels, uint32_t* dst) { +// sign-extended multiplying constants, pre-shifted by 6. +#define CST(X) (((int16_t)(m->X << 8)) >> 6) + const int16_t rb[8] = { + CST(green_to_blue), CST(green_to_red), + CST(green_to_blue), CST(green_to_red), + CST(green_to_blue), CST(green_to_red), + CST(green_to_blue), CST(green_to_red) + }; + const int16x8_t mults_rb = vld1q_s16(rb); + const int16_t b2[8] = { + 0, CST(red_to_blue), 0, CST(red_to_blue), + 0, CST(red_to_blue), 0, CST(red_to_blue), + }; + const int16x8_t mults_b2 = vld1q_s16(b2); +#undef CST +#ifdef USE_VTBLQ + static const uint8_t kg0g0[16] = { + 255, 1, 255, 1, 255, 5, 255, 5, 255, 9, 255, 9, 255, 13, 255, 13 + }; + const uint8x16_t shuffle = vld1q_u8(kg0g0); +#else + static const uint8_t k0g0g[8] = { 255, 1, 255, 1, 255, 5, 255, 5 }; + const uint8x8_t shuffle = vld1_u8(k0g0g); +#endif + const uint32x4_t mask_ag = vdupq_n_u32(0xff00ff00u); + int i; + for (i = 0; i + 4 <= num_pixels; i += 4) { + const uint8x16_t in = vld1q_u8((const uint8_t*)(src + i)); + const uint32x4_t a0g0 = vandq_u32(vreinterpretq_u32_u8(in), mask_ag); + // 0 g 0 g + const uint8x16_t greens = DoGreenShuffle_NEON(in, shuffle); + // x dr x db1 + const int16x8_t A = vqdmulhq_s16(vreinterpretq_s16_u8(greens), mults_rb); + // x r' x b' + const int8x16_t B = vaddq_s8(vreinterpretq_s8_u8(in), + vreinterpretq_s8_s16(A)); + // r' 0 b' 0 + const int16x8_t C = vshlq_n_s16(vreinterpretq_s16_s8(B), 8); + // x db2 0 0 + const int16x8_t D = vqdmulhq_s16(C, mults_b2); + // 0 x db2 0 + const uint32x4_t E = vshrq_n_u32(vreinterpretq_u32_s16(D), 8); + // r' x b'' 0 + const int8x16_t F = vaddq_s8(vreinterpretq_s8_u32(E), + vreinterpretq_s8_s16(C)); + // 0 r' 0 b'' + const uint16x8_t G = vshrq_n_u16(vreinterpretq_u16_s8(F), 8); + const uint32x4_t out = vorrq_u32(vreinterpretq_u32_u16(G), a0g0); + vst1q_u32(dst + i, out); + } + // Fall-back to C-version for left-overs. + VP8LTransformColorInverse_C(m, src + i, num_pixels - i, dst + i); +} + +#undef USE_VTBLQ + +//------------------------------------------------------------------------------ +// Entry point + +extern void VP8LDspInitNEON(void); + +WEBP_TSAN_IGNORE_FUNCTION void VP8LDspInitNEON(void) { + VP8LPredictors[5] = Predictor5_NEON; + VP8LPredictors[6] = Predictor6_NEON; + VP8LPredictors[7] = Predictor7_NEON; + VP8LPredictors[13] = Predictor13_NEON; + + VP8LPredictorsAdd[0] = PredictorAdd0_NEON; + VP8LPredictorsAdd[1] = PredictorAdd1_NEON; + VP8LPredictorsAdd[2] = PredictorAdd2_NEON; + VP8LPredictorsAdd[3] = PredictorAdd3_NEON; + VP8LPredictorsAdd[4] = PredictorAdd4_NEON; + VP8LPredictorsAdd[5] = PredictorAdd5_NEON; + VP8LPredictorsAdd[6] = PredictorAdd6_NEON; + VP8LPredictorsAdd[7] = PredictorAdd7_NEON; + VP8LPredictorsAdd[8] = PredictorAdd8_NEON; + VP8LPredictorsAdd[9] = PredictorAdd9_NEON; + VP8LPredictorsAdd[10] = PredictorAdd10_NEON; + VP8LPredictorsAdd[11] = PredictorAdd11_NEON; + VP8LPredictorsAdd[12] = PredictorAdd12_NEON; + VP8LPredictorsAdd[13] = PredictorAdd13_NEON; + + VP8LConvertBGRAToRGBA = ConvertBGRAToRGBA_NEON; + VP8LConvertBGRAToBGR = ConvertBGRAToBGR_NEON; + VP8LConvertBGRAToRGB = ConvertBGRAToRGB_NEON; + + VP8LAddGreenToBlueAndRed = AddGreenToBlueAndRed_NEON; + VP8LTransformColorInverse = TransformColorInverse_NEON; +} + +#else // !WEBP_USE_NEON + +WEBP_DSP_INIT_STUB(VP8LDspInitNEON) + +#endif // WEBP_USE_NEON diff --git a/packages/core/src/zig/vendor/libwebp/src/dsp/lossless_sse2.c b/packages/core/src/zig/vendor/libwebp/src/dsp/lossless_sse2.c new file mode 100644 index 0000000000..3c6608be07 --- /dev/null +++ b/packages/core/src/zig/vendor/libwebp/src/dsp/lossless_sse2.c @@ -0,0 +1,730 @@ +// Copyright 2014 Google Inc. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the COPYING file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +// ----------------------------------------------------------------------------- +// +// SSE2 variant of methods for lossless decoder +// +// Author: Skal (pascal.massimino@gmail.com) + +#include "src/dsp/dsp.h" + +#if defined(WEBP_USE_SSE2) + +#include +#include + +#include "src/dsp/common_sse2.h" +#include "src/dsp/cpu.h" +#include "src/dsp/lossless.h" +#include "src/dsp/lossless_common.h" +#include "src/webp/format_constants.h" +#include "src/webp/types.h" + +//------------------------------------------------------------------------------ +// Predictor Transform + +static WEBP_INLINE uint32_t ClampedAddSubtractFull_SSE2(uint32_t c0, + uint32_t c1, + uint32_t c2) { + const __m128i zero = _mm_setzero_si128(); + const __m128i C0 = _mm_unpacklo_epi8(_mm_cvtsi32_si128((int)c0), zero); + const __m128i C1 = _mm_unpacklo_epi8(_mm_cvtsi32_si128((int)c1), zero); + const __m128i C2 = _mm_unpacklo_epi8(_mm_cvtsi32_si128((int)c2), zero); + const __m128i V1 = _mm_add_epi16(C0, C1); + const __m128i V2 = _mm_sub_epi16(V1, C2); + const __m128i b = _mm_packus_epi16(V2, V2); + return (uint32_t)_mm_cvtsi128_si32(b); +} + +static WEBP_INLINE uint32_t ClampedAddSubtractHalf_SSE2(uint32_t c0, + uint32_t c1, + uint32_t c2) { + const __m128i zero = _mm_setzero_si128(); + const __m128i C0 = _mm_unpacklo_epi8(_mm_cvtsi32_si128((int)c0), zero); + const __m128i C1 = _mm_unpacklo_epi8(_mm_cvtsi32_si128((int)c1), zero); + const __m128i B0 = _mm_unpacklo_epi8(_mm_cvtsi32_si128((int)c2), zero); + const __m128i avg = _mm_add_epi16(C1, C0); + const __m128i A0 = _mm_srli_epi16(avg, 1); + const __m128i A1 = _mm_sub_epi16(A0, B0); + const __m128i BgtA = _mm_cmpgt_epi16(B0, A0); + const __m128i A2 = _mm_sub_epi16(A1, BgtA); + const __m128i A3 = _mm_srai_epi16(A2, 1); + const __m128i A4 = _mm_add_epi16(A0, A3); + const __m128i A5 = _mm_packus_epi16(A4, A4); + return (uint32_t)_mm_cvtsi128_si32(A5); +} + +static WEBP_INLINE uint32_t Select_SSE2(uint32_t a, uint32_t b, uint32_t c) { + int pa_minus_pb; + const __m128i zero = _mm_setzero_si128(); + const __m128i A0 = _mm_cvtsi32_si128((int)a); + const __m128i B0 = _mm_cvtsi32_si128((int)b); + const __m128i C0 = _mm_cvtsi32_si128((int)c); + const __m128i AC0 = _mm_subs_epu8(A0, C0); + const __m128i CA0 = _mm_subs_epu8(C0, A0); + const __m128i BC0 = _mm_subs_epu8(B0, C0); + const __m128i CB0 = _mm_subs_epu8(C0, B0); + const __m128i AC = _mm_or_si128(AC0, CA0); + const __m128i BC = _mm_or_si128(BC0, CB0); + const __m128i pa = _mm_unpacklo_epi8(AC, zero); // |a - c| + const __m128i pb = _mm_unpacklo_epi8(BC, zero); // |b - c| + const __m128i diff = _mm_sub_epi16(pb, pa); + { + int16_t out[8]; + _mm_storeu_si128((__m128i*)out, diff); + pa_minus_pb = out[0] + out[1] + out[2] + out[3]; + } + return (pa_minus_pb <= 0) ? a : b; +} + +static WEBP_INLINE void Average2_m128i(const __m128i* const a0, + const __m128i* const a1, + __m128i* const avg) { + // (a + b) >> 1 = ((a + b + 1) >> 1) - ((a ^ b) & 1) + const __m128i ones = _mm_set1_epi8(1); + const __m128i avg1 = _mm_avg_epu8(*a0, *a1); + const __m128i one = _mm_and_si128(_mm_xor_si128(*a0, *a1), ones); + *avg = _mm_sub_epi8(avg1, one); +} + +static WEBP_INLINE void Average2_uint32_SSE2(const uint32_t a0, + const uint32_t a1, + __m128i* const avg) { + // (a + b) >> 1 = ((a + b + 1) >> 1) - ((a ^ b) & 1) + const __m128i ones = _mm_set1_epi8(1); + const __m128i A0 = _mm_cvtsi32_si128((int)a0); + const __m128i A1 = _mm_cvtsi32_si128((int)a1); + const __m128i avg1 = _mm_avg_epu8(A0, A1); + const __m128i one = _mm_and_si128(_mm_xor_si128(A0, A1), ones); + *avg = _mm_sub_epi8(avg1, one); +} + +static WEBP_INLINE __m128i Average2_uint32_16_SSE2(uint32_t a0, uint32_t a1) { + const __m128i zero = _mm_setzero_si128(); + const __m128i A0 = _mm_unpacklo_epi8(_mm_cvtsi32_si128((int)a0), zero); + const __m128i A1 = _mm_unpacklo_epi8(_mm_cvtsi32_si128((int)a1), zero); + const __m128i sum = _mm_add_epi16(A1, A0); + return _mm_srli_epi16(sum, 1); +} + +static WEBP_INLINE uint32_t Average2_SSE2(uint32_t a0, uint32_t a1) { + __m128i output; + Average2_uint32_SSE2(a0, a1, &output); + return (uint32_t)_mm_cvtsi128_si32(output); +} + +static WEBP_INLINE uint32_t Average3_SSE2(uint32_t a0, uint32_t a1, + uint32_t a2) { + const __m128i zero = _mm_setzero_si128(); + const __m128i avg1 = Average2_uint32_16_SSE2(a0, a2); + const __m128i A1 = _mm_unpacklo_epi8(_mm_cvtsi32_si128((int)a1), zero); + const __m128i sum = _mm_add_epi16(avg1, A1); + const __m128i avg2 = _mm_srli_epi16(sum, 1); + const __m128i A2 = _mm_packus_epi16(avg2, avg2); + return (uint32_t)_mm_cvtsi128_si32(A2); +} + +static WEBP_INLINE uint32_t Average4_SSE2(uint32_t a0, uint32_t a1, + uint32_t a2, uint32_t a3) { + const __m128i avg1 = Average2_uint32_16_SSE2(a0, a1); + const __m128i avg2 = Average2_uint32_16_SSE2(a2, a3); + const __m128i sum = _mm_add_epi16(avg2, avg1); + const __m128i avg3 = _mm_srli_epi16(sum, 1); + const __m128i A0 = _mm_packus_epi16(avg3, avg3); + return (uint32_t)_mm_cvtsi128_si32(A0); +} + +static uint32_t Predictor5_SSE2(const uint32_t* const left, + const uint32_t* const top) { + const uint32_t pred = Average3_SSE2(*left, top[0], top[1]); + return pred; +} +static uint32_t Predictor6_SSE2(const uint32_t* const left, + const uint32_t* const top) { + const uint32_t pred = Average2_SSE2(*left, top[-1]); + return pred; +} +static uint32_t Predictor7_SSE2(const uint32_t* const left, + const uint32_t* const top) { + const uint32_t pred = Average2_SSE2(*left, top[0]); + return pred; +} +static uint32_t Predictor8_SSE2(const uint32_t* const left, + const uint32_t* const top) { + const uint32_t pred = Average2_SSE2(top[-1], top[0]); + (void)left; + return pred; +} +static uint32_t Predictor9_SSE2(const uint32_t* const left, + const uint32_t* const top) { + const uint32_t pred = Average2_SSE2(top[0], top[1]); + (void)left; + return pred; +} +static uint32_t Predictor10_SSE2(const uint32_t* const left, + const uint32_t* const top) { + const uint32_t pred = Average4_SSE2(*left, top[-1], top[0], top[1]); + return pred; +} +static uint32_t Predictor11_SSE2(const uint32_t* const left, + const uint32_t* const top) { + const uint32_t pred = Select_SSE2(top[0], *left, top[-1]); + return pred; +} +static uint32_t Predictor12_SSE2(const uint32_t* const left, + const uint32_t* const top) { + const uint32_t pred = ClampedAddSubtractFull_SSE2(*left, top[0], top[-1]); + return pred; +} +static uint32_t Predictor13_SSE2(const uint32_t* const left, + const uint32_t* const top) { + const uint32_t pred = ClampedAddSubtractHalf_SSE2(*left, top[0], top[-1]); + return pred; +} + +// Batch versions of those functions. + +// Predictor0: ARGB_BLACK. +static void PredictorAdd0_SSE2(const uint32_t* in, const uint32_t* upper, + int num_pixels, uint32_t* WEBP_RESTRICT out) { + int i; + const __m128i black = _mm_set1_epi32((int)ARGB_BLACK); + for (i = 0; i + 4 <= num_pixels; i += 4) { + const __m128i src = _mm_loadu_si128((const __m128i*)&in[i]); + const __m128i res = _mm_add_epi8(src, black); + _mm_storeu_si128((__m128i*)&out[i], res); + } + if (i != num_pixels) { + VP8LPredictorsAdd_C[0](in + i, NULL, num_pixels - i, out + i); + } + (void)upper; +} + +// Predictor1: left. +static void PredictorAdd1_SSE2(const uint32_t* in, const uint32_t* upper, + int num_pixels, uint32_t* WEBP_RESTRICT out) { + int i; + __m128i prev = _mm_set1_epi32((int)out[-1]); + for (i = 0; i + 4 <= num_pixels; i += 4) { + // a | b | c | d + const __m128i src = _mm_loadu_si128((const __m128i*)&in[i]); + // 0 | a | b | c + const __m128i shift0 = _mm_slli_si128(src, 4); + // a | a + b | b + c | c + d + const __m128i sum0 = _mm_add_epi8(src, shift0); + // 0 | 0 | a | a + b + const __m128i shift1 = _mm_slli_si128(sum0, 8); + // a | a + b | a + b + c | a + b + c + d + const __m128i sum1 = _mm_add_epi8(sum0, shift1); + const __m128i res = _mm_add_epi8(sum1, prev); + _mm_storeu_si128((__m128i*)&out[i], res); + // replicate prev output on the four lanes + prev = _mm_shuffle_epi32(res, (3 << 0) | (3 << 2) | (3 << 4) | (3 << 6)); + } + if (i != num_pixels) { + VP8LPredictorsAdd_C[1](in + i, upper + i, num_pixels - i, out + i); + } +} + +// Macro that adds 32-bit integers from IN using mod 256 arithmetic +// per 8 bit channel. +#define GENERATE_PREDICTOR_1(X, IN) \ +static void PredictorAdd##X##_SSE2(const uint32_t* in, const uint32_t* upper, \ + int num_pixels, \ + uint32_t* WEBP_RESTRICT out) { \ + int i; \ + for (i = 0; i + 4 <= num_pixels; i += 4) { \ + const __m128i src = _mm_loadu_si128((const __m128i*)&in[i]); \ + const __m128i other = _mm_loadu_si128((const __m128i*)&(IN)); \ + const __m128i res = _mm_add_epi8(src, other); \ + _mm_storeu_si128((__m128i*)&out[i], res); \ + } \ + if (i != num_pixels) { \ + VP8LPredictorsAdd_C[(X)](in + i, upper + i, num_pixels - i, out + i); \ + } \ +} + +// Predictor2: Top. +GENERATE_PREDICTOR_1(2, upper[i]) +// Predictor3: Top-right. +GENERATE_PREDICTOR_1(3, upper[i + 1]) +// Predictor4: Top-left. +GENERATE_PREDICTOR_1(4, upper[i - 1]) +#undef GENERATE_PREDICTOR_1 + +// Due to averages with integers, values cannot be accumulated in parallel for +// predictors 5 to 7. +GENERATE_PREDICTOR_ADD(Predictor5_SSE2, PredictorAdd5_SSE2) +GENERATE_PREDICTOR_ADD(Predictor6_SSE2, PredictorAdd6_SSE2) +GENERATE_PREDICTOR_ADD(Predictor7_SSE2, PredictorAdd7_SSE2) + +#define GENERATE_PREDICTOR_2(X, IN) \ +static void PredictorAdd##X##_SSE2(const uint32_t* in, const uint32_t* upper, \ + int num_pixels, \ + uint32_t* WEBP_RESTRICT out) { \ + int i; \ + for (i = 0; i + 4 <= num_pixels; i += 4) { \ + const __m128i Tother = _mm_loadu_si128((const __m128i*)&(IN)); \ + const __m128i T = _mm_loadu_si128((const __m128i*)&upper[i]); \ + const __m128i src = _mm_loadu_si128((const __m128i*)&in[i]); \ + __m128i avg, res; \ + Average2_m128i(&T, &Tother, &avg); \ + res = _mm_add_epi8(avg, src); \ + _mm_storeu_si128((__m128i*)&out[i], res); \ + } \ + if (i != num_pixels) { \ + VP8LPredictorsAdd_C[(X)](in + i, upper + i, num_pixels - i, out + i); \ + } \ +} +// Predictor8: average TL T. +GENERATE_PREDICTOR_2(8, upper[i - 1]) +// Predictor9: average T TR. +GENERATE_PREDICTOR_2(9, upper[i + 1]) +#undef GENERATE_PREDICTOR_2 + +// Predictor10: average of (average of (L,TL), average of (T, TR)). +#define DO_PRED10(OUT) do { \ + __m128i avgLTL, avg; \ + Average2_m128i(&L, &TL, &avgLTL); \ + Average2_m128i(&avgTTR, &avgLTL, &avg); \ + L = _mm_add_epi8(avg, src); \ + out[i + (OUT)] = (uint32_t)_mm_cvtsi128_si32(L); \ +} while (0) + +#define DO_PRED10_SHIFT do { \ + /* Rotate the pre-computed values for the next iteration.*/ \ + avgTTR = _mm_srli_si128(avgTTR, 4); \ + TL = _mm_srli_si128(TL, 4); \ + src = _mm_srli_si128(src, 4); \ +} while (0) + +static void PredictorAdd10_SSE2(const uint32_t* in, const uint32_t* upper, + int num_pixels, uint32_t* WEBP_RESTRICT out) { + int i; + __m128i L = _mm_cvtsi32_si128((int)out[-1]); + for (i = 0; i + 4 <= num_pixels; i += 4) { + __m128i src = _mm_loadu_si128((const __m128i*)&in[i]); + __m128i TL = _mm_loadu_si128((const __m128i*)&upper[i - 1]); + const __m128i T = _mm_loadu_si128((const __m128i*)&upper[i]); + const __m128i TR = _mm_loadu_si128((const __m128i*)&upper[i + 1]); + __m128i avgTTR; + Average2_m128i(&T, &TR, &avgTTR); + DO_PRED10(0); + DO_PRED10_SHIFT; + DO_PRED10(1); + DO_PRED10_SHIFT; + DO_PRED10(2); + DO_PRED10_SHIFT; + DO_PRED10(3); + } + if (i != num_pixels) { + VP8LPredictorsAdd_C[10](in + i, upper + i, num_pixels - i, out + i); + } +} +#undef DO_PRED10 +#undef DO_PRED10_SHIFT + +// Predictor11: select. +#define DO_PRED11(OUT) do { \ + const __m128i L_lo = _mm_unpacklo_epi32(L, T); \ + const __m128i TL_lo = _mm_unpacklo_epi32(TL, T); \ + const __m128i pb = _mm_sad_epu8(L_lo, TL_lo); /* pb = sum |L-TL|*/ \ + const __m128i mask = _mm_cmpgt_epi32(pb, pa); \ + const __m128i A = _mm_and_si128(mask, L); \ + const __m128i B = _mm_andnot_si128(mask, T); \ + const __m128i pred = _mm_or_si128(A, B); /* pred = (pa > b)? L : T*/ \ + L = _mm_add_epi8(src, pred); \ + out[i + (OUT)] = (uint32_t)_mm_cvtsi128_si32(L); \ +} while (0) + +#define DO_PRED11_SHIFT do { \ + /* Shift the pre-computed value for the next iteration.*/ \ + T = _mm_srli_si128(T, 4); \ + TL = _mm_srli_si128(TL, 4); \ + src = _mm_srli_si128(src, 4); \ + pa = _mm_srli_si128(pa, 4); \ +} while (0) + +static void PredictorAdd11_SSE2(const uint32_t* in, const uint32_t* upper, + int num_pixels, uint32_t* WEBP_RESTRICT out) { + int i; + __m128i pa; + __m128i L = _mm_cvtsi32_si128((int)out[-1]); + for (i = 0; i + 4 <= num_pixels; i += 4) { + __m128i T = _mm_loadu_si128((const __m128i*)&upper[i]); + __m128i TL = _mm_loadu_si128((const __m128i*)&upper[i - 1]); + __m128i src = _mm_loadu_si128((const __m128i*)&in[i]); + { + // We can unpack with any value on the upper 32 bits, provided it's the + // same on both operands (so that their sum of abs diff is zero). Here we + // use T. + const __m128i T_lo = _mm_unpacklo_epi32(T, T); + const __m128i TL_lo = _mm_unpacklo_epi32(TL, T); + const __m128i T_hi = _mm_unpackhi_epi32(T, T); + const __m128i TL_hi = _mm_unpackhi_epi32(TL, T); + const __m128i s_lo = _mm_sad_epu8(T_lo, TL_lo); + const __m128i s_hi = _mm_sad_epu8(T_hi, TL_hi); + pa = _mm_packs_epi32(s_lo, s_hi); // pa = sum |T-TL| + } + DO_PRED11(0); + DO_PRED11_SHIFT; + DO_PRED11(1); + DO_PRED11_SHIFT; + DO_PRED11(2); + DO_PRED11_SHIFT; + DO_PRED11(3); + } + if (i != num_pixels) { + VP8LPredictorsAdd_C[11](in + i, upper + i, num_pixels - i, out + i); + } +} +#undef DO_PRED11 +#undef DO_PRED11_SHIFT + +// Predictor12: ClampedAddSubtractFull. +#define DO_PRED12(DIFF, LANE, OUT) do { \ + const __m128i all = _mm_add_epi16(L, (DIFF)); \ + const __m128i alls = _mm_packus_epi16(all, all); \ + const __m128i res = _mm_add_epi8(src, alls); \ + out[i + (OUT)] = (uint32_t)_mm_cvtsi128_si32(res); \ + L = _mm_unpacklo_epi8(res, zero); \ +} while (0) + +#define DO_PRED12_SHIFT(DIFF, LANE) do { \ + /* Shift the pre-computed value for the next iteration.*/ \ + if ((LANE) == 0) (DIFF) = _mm_srli_si128((DIFF), 8); \ + src = _mm_srli_si128(src, 4); \ +} while (0) + +static void PredictorAdd12_SSE2(const uint32_t* in, const uint32_t* upper, + int num_pixels, uint32_t* WEBP_RESTRICT out) { + int i; + const __m128i zero = _mm_setzero_si128(); + const __m128i L8 = _mm_cvtsi32_si128((int)out[-1]); + __m128i L = _mm_unpacklo_epi8(L8, zero); + for (i = 0; i + 4 <= num_pixels; i += 4) { + // Load 4 pixels at a time. + __m128i src = _mm_loadu_si128((const __m128i*)&in[i]); + const __m128i T = _mm_loadu_si128((const __m128i*)&upper[i]); + const __m128i T_lo = _mm_unpacklo_epi8(T, zero); + const __m128i T_hi = _mm_unpackhi_epi8(T, zero); + const __m128i TL = _mm_loadu_si128((const __m128i*)&upper[i - 1]); + const __m128i TL_lo = _mm_unpacklo_epi8(TL, zero); + const __m128i TL_hi = _mm_unpackhi_epi8(TL, zero); + __m128i diff_lo = _mm_sub_epi16(T_lo, TL_lo); + __m128i diff_hi = _mm_sub_epi16(T_hi, TL_hi); + DO_PRED12(diff_lo, 0, 0); + DO_PRED12_SHIFT(diff_lo, 0); + DO_PRED12(diff_lo, 1, 1); + DO_PRED12_SHIFT(diff_lo, 1); + DO_PRED12(diff_hi, 0, 2); + DO_PRED12_SHIFT(diff_hi, 0); + DO_PRED12(diff_hi, 1, 3); + } + if (i != num_pixels) { + VP8LPredictorsAdd_C[12](in + i, upper + i, num_pixels - i, out + i); + } +} +#undef DO_PRED12 +#undef DO_PRED12_SHIFT + +// Due to averages with integers, values cannot be accumulated in parallel for +// predictors 13. +GENERATE_PREDICTOR_ADD(Predictor13_SSE2, PredictorAdd13_SSE2) + +//------------------------------------------------------------------------------ +// Subtract-Green Transform + +static void AddGreenToBlueAndRed_SSE2(const uint32_t* const src, int num_pixels, + uint32_t* dst) { + int i; + for (i = 0; i + 4 <= num_pixels; i += 4) { + const __m128i in = _mm_loadu_si128((const __m128i*)&src[i]); // argb + const __m128i A = _mm_srli_epi16(in, 8); // 0 a 0 g + const __m128i B = _mm_shufflelo_epi16(A, _MM_SHUFFLE(2, 2, 0, 0)); + const __m128i C = _mm_shufflehi_epi16(B, _MM_SHUFFLE(2, 2, 0, 0)); // 0g0g + const __m128i out = _mm_add_epi8(in, C); + _mm_storeu_si128((__m128i*)&dst[i], out); + } + // fallthrough and finish off with plain-C + if (i != num_pixels) { + VP8LAddGreenToBlueAndRed_C(src + i, num_pixels - i, dst + i); + } +} + +//------------------------------------------------------------------------------ +// Color Transform + +static void TransformColorInverse_SSE2(const VP8LMultipliers* const m, + const uint32_t* const src, + int num_pixels, uint32_t* dst) { +// sign-extended multiplying constants, pre-shifted by 5. +#define CST(X) (((int16_t)(m->X << 8)) >> 5) // sign-extend +#define MK_CST_16(HI, LO) \ + _mm_set1_epi32((int)(((uint32_t)(HI) << 16) | ((LO) & 0xffff))) + const __m128i mults_rb = MK_CST_16(CST(green_to_red), CST(green_to_blue)); + const __m128i mults_b2 = MK_CST_16(CST(red_to_blue), 0); +#undef MK_CST_16 +#undef CST + const __m128i mask_ag = _mm_set1_epi32((int)0xff00ff00); // alpha-green masks + int i; + for (i = 0; i + 4 <= num_pixels; i += 4) { + const __m128i in = _mm_loadu_si128((const __m128i*)&src[i]); // argb + const __m128i A = _mm_and_si128(in, mask_ag); // a 0 g 0 + const __m128i B = _mm_shufflelo_epi16(A, _MM_SHUFFLE(2, 2, 0, 0)); + const __m128i C = _mm_shufflehi_epi16(B, _MM_SHUFFLE(2, 2, 0, 0)); // g0g0 + const __m128i D = _mm_mulhi_epi16(C, mults_rb); // x dr x db1 + const __m128i E = _mm_add_epi8(in, D); // x r' x b' + const __m128i F = _mm_slli_epi16(E, 8); // r' 0 b' 0 + const __m128i G = _mm_mulhi_epi16(F, mults_b2); // x db2 0 0 + const __m128i H = _mm_srli_epi32(G, 8); // 0 x db2 0 + const __m128i I = _mm_add_epi8(H, F); // r' x b'' 0 + const __m128i J = _mm_srli_epi16(I, 8); // 0 r' 0 b'' + const __m128i out = _mm_or_si128(J, A); + _mm_storeu_si128((__m128i*)&dst[i], out); + } + // Fall-back to C-version for left-overs. + if (i != num_pixels) { + VP8LTransformColorInverse_C(m, src + i, num_pixels - i, dst + i); + } +} + +//------------------------------------------------------------------------------ +// Color-space conversion functions + +static void ConvertBGRAToRGB_SSE2(const uint32_t* WEBP_RESTRICT src, + int num_pixels, uint8_t* WEBP_RESTRICT dst) { + const __m128i* in = (const __m128i*)src; + __m128i* out = (__m128i*)dst; + + while (num_pixels >= 32) { + // Load the BGRA buffers. + __m128i in0 = _mm_loadu_si128(in + 0); + __m128i in1 = _mm_loadu_si128(in + 1); + __m128i in2 = _mm_loadu_si128(in + 2); + __m128i in3 = _mm_loadu_si128(in + 3); + __m128i in4 = _mm_loadu_si128(in + 4); + __m128i in5 = _mm_loadu_si128(in + 5); + __m128i in6 = _mm_loadu_si128(in + 6); + __m128i in7 = _mm_loadu_si128(in + 7); + VP8L32bToPlanar_SSE2(&in0, &in1, &in2, &in3); + VP8L32bToPlanar_SSE2(&in4, &in5, &in6, &in7); + // At this points, in1/in5 contains red only, in2/in6 green only ... + // Pack the colors in 24b RGB. + VP8PlanarTo24b_SSE2(&in1, &in5, &in2, &in6, &in3, &in7); + _mm_storeu_si128(out + 0, in1); + _mm_storeu_si128(out + 1, in5); + _mm_storeu_si128(out + 2, in2); + _mm_storeu_si128(out + 3, in6); + _mm_storeu_si128(out + 4, in3); + _mm_storeu_si128(out + 5, in7); + in += 8; + out += 6; + num_pixels -= 32; + } + // left-overs + if (num_pixels > 0) { + VP8LConvertBGRAToRGB_C((const uint32_t*)in, num_pixels, (uint8_t*)out); + } +} + +static void ConvertBGRAToRGBA_SSE2(const uint32_t* WEBP_RESTRICT src, + int num_pixels, uint8_t* WEBP_RESTRICT dst) { + const __m128i red_blue_mask = _mm_set1_epi32(0x00ff00ff); + const __m128i* in = (const __m128i*)src; + __m128i* out = (__m128i*)dst; + while (num_pixels >= 8) { + const __m128i A1 = _mm_loadu_si128(in++); + const __m128i A2 = _mm_loadu_si128(in++); + const __m128i B1 = _mm_and_si128(A1, red_blue_mask); // R 0 B 0 + const __m128i B2 = _mm_and_si128(A2, red_blue_mask); // R 0 B 0 + const __m128i C1 = _mm_andnot_si128(red_blue_mask, A1); // 0 G 0 A + const __m128i C2 = _mm_andnot_si128(red_blue_mask, A2); // 0 G 0 A + const __m128i D1 = _mm_shufflelo_epi16(B1, _MM_SHUFFLE(2, 3, 0, 1)); + const __m128i D2 = _mm_shufflelo_epi16(B2, _MM_SHUFFLE(2, 3, 0, 1)); + const __m128i E1 = _mm_shufflehi_epi16(D1, _MM_SHUFFLE(2, 3, 0, 1)); + const __m128i E2 = _mm_shufflehi_epi16(D2, _MM_SHUFFLE(2, 3, 0, 1)); + const __m128i F1 = _mm_or_si128(E1, C1); + const __m128i F2 = _mm_or_si128(E2, C2); + _mm_storeu_si128(out++, F1); + _mm_storeu_si128(out++, F2); + num_pixels -= 8; + } + // left-overs + if (num_pixels > 0) { + VP8LConvertBGRAToRGBA_C((const uint32_t*)in, num_pixels, (uint8_t*)out); + } +} + +static void ConvertBGRAToRGBA4444_SSE2(const uint32_t* WEBP_RESTRICT src, + int num_pixels, + uint8_t* WEBP_RESTRICT dst) { + const __m128i mask_0x0f = _mm_set1_epi8(0x0f); + const __m128i mask_0xf0 = _mm_set1_epi8((char)0xf0); + const __m128i* in = (const __m128i*)src; + __m128i* out = (__m128i*)dst; + while (num_pixels >= 8) { + const __m128i bgra0 = _mm_loadu_si128(in++); // bgra0|bgra1|bgra2|bgra3 + const __m128i bgra4 = _mm_loadu_si128(in++); // bgra4|bgra5|bgra6|bgra7 + const __m128i v0l = _mm_unpacklo_epi8(bgra0, bgra4); // b0b4g0g4r0r4a0a4... + const __m128i v0h = _mm_unpackhi_epi8(bgra0, bgra4); // b2b6g2g6r2r6a2a6... + const __m128i v1l = _mm_unpacklo_epi8(v0l, v0h); // b0b2b4b6g0g2g4g6... + const __m128i v1h = _mm_unpackhi_epi8(v0l, v0h); // b1b3b5b7g1g3g5g7... + const __m128i v2l = _mm_unpacklo_epi8(v1l, v1h); // b0...b7 | g0...g7 + const __m128i v2h = _mm_unpackhi_epi8(v1l, v1h); // r0...r7 | a0...a7 + const __m128i ga0 = _mm_unpackhi_epi64(v2l, v2h); // g0...g7 | a0...a7 + const __m128i rb0 = _mm_unpacklo_epi64(v2h, v2l); // r0...r7 | b0...b7 + const __m128i ga1 = _mm_srli_epi16(ga0, 4); // g0-|g1-|...|a6-|a7- + const __m128i rb1 = _mm_and_si128(rb0, mask_0xf0); // -r0|-r1|...|-b6|-a7 + const __m128i ga2 = _mm_and_si128(ga1, mask_0x0f); // g0-|g1-|...|a6-|a7- + const __m128i rgba0 = _mm_or_si128(ga2, rb1); // rg0..rg7 | ba0..ba7 + const __m128i rgba1 = _mm_srli_si128(rgba0, 8); // ba0..ba7 | 0 +#if (WEBP_SWAP_16BIT_CSP == 1) + const __m128i rgba = _mm_unpacklo_epi8(rgba1, rgba0); // barg0...barg7 +#else + const __m128i rgba = _mm_unpacklo_epi8(rgba0, rgba1); // rgba0...rgba7 +#endif + _mm_storeu_si128(out++, rgba); + num_pixels -= 8; + } + // left-overs + if (num_pixels > 0) { + VP8LConvertBGRAToRGBA4444_C((const uint32_t*)in, num_pixels, (uint8_t*)out); + } +} + +static void ConvertBGRAToRGB565_SSE2(const uint32_t* WEBP_RESTRICT src, + int num_pixels, + uint8_t* WEBP_RESTRICT dst) { + const __m128i mask_0xe0 = _mm_set1_epi8((char)0xe0); + const __m128i mask_0xf8 = _mm_set1_epi8((char)0xf8); + const __m128i mask_0x07 = _mm_set1_epi8(0x07); + const __m128i* in = (const __m128i*)src; + __m128i* out = (__m128i*)dst; + while (num_pixels >= 8) { + const __m128i bgra0 = _mm_loadu_si128(in++); // bgra0|bgra1|bgra2|bgra3 + const __m128i bgra4 = _mm_loadu_si128(in++); // bgra4|bgra5|bgra6|bgra7 + const __m128i v0l = _mm_unpacklo_epi8(bgra0, bgra4); // b0b4g0g4r0r4a0a4... + const __m128i v0h = _mm_unpackhi_epi8(bgra0, bgra4); // b2b6g2g6r2r6a2a6... + const __m128i v1l = _mm_unpacklo_epi8(v0l, v0h); // b0b2b4b6g0g2g4g6... + const __m128i v1h = _mm_unpackhi_epi8(v0l, v0h); // b1b3b5b7g1g3g5g7... + const __m128i v2l = _mm_unpacklo_epi8(v1l, v1h); // b0...b7 | g0...g7 + const __m128i v2h = _mm_unpackhi_epi8(v1l, v1h); // r0...r7 | a0...a7 + const __m128i ga0 = _mm_unpackhi_epi64(v2l, v2h); // g0...g7 | a0...a7 + const __m128i rb0 = _mm_unpacklo_epi64(v2h, v2l); // r0...r7 | b0...b7 + const __m128i rb1 = _mm_and_si128(rb0, mask_0xf8); // -r0..-r7|-b0..-b7 + const __m128i g_lo1 = _mm_srli_epi16(ga0, 5); + const __m128i g_lo2 = _mm_and_si128(g_lo1, mask_0x07); // g0-...g7-|xx (3b) + const __m128i g_hi1 = _mm_slli_epi16(ga0, 3); + const __m128i g_hi2 = _mm_and_si128(g_hi1, mask_0xe0); // -g0...-g7|xx (3b) + const __m128i b0 = _mm_srli_si128(rb1, 8); // -b0...-b7|0 + const __m128i rg1 = _mm_or_si128(rb1, g_lo2); // gr0...gr7|xx + const __m128i b1 = _mm_srli_epi16(b0, 3); + const __m128i gb1 = _mm_or_si128(b1, g_hi2); // bg0...bg7|xx +#if (WEBP_SWAP_16BIT_CSP == 1) + const __m128i rgba = _mm_unpacklo_epi8(gb1, rg1); // rggb0...rggb7 +#else + const __m128i rgba = _mm_unpacklo_epi8(rg1, gb1); // bgrb0...bgrb7 +#endif + _mm_storeu_si128(out++, rgba); + num_pixels -= 8; + } + // left-overs + if (num_pixels > 0) { + VP8LConvertBGRAToRGB565_C((const uint32_t*)in, num_pixels, (uint8_t*)out); + } +} + +static void ConvertBGRAToBGR_SSE2(const uint32_t* WEBP_RESTRICT src, + int num_pixels, uint8_t* WEBP_RESTRICT dst) { + const __m128i mask_l = _mm_set_epi32(0, 0x00ffffff, 0, 0x00ffffff); + const __m128i mask_h = _mm_set_epi32(0x00ffffff, 0, 0x00ffffff, 0); + const __m128i* in = (const __m128i*)src; + const uint8_t* const end = dst + num_pixels * 3; + // the last storel_epi64 below writes 8 bytes starting at offset 18 + while (dst + 26 <= end) { + const __m128i bgra0 = _mm_loadu_si128(in++); // bgra0|bgra1|bgra2|bgra3 + const __m128i bgra4 = _mm_loadu_si128(in++); // bgra4|bgra5|bgra6|bgra7 + const __m128i a0l = _mm_and_si128(bgra0, mask_l); // bgr0|0|bgr0|0 + const __m128i a4l = _mm_and_si128(bgra4, mask_l); // bgr0|0|bgr0|0 + const __m128i a0h = _mm_and_si128(bgra0, mask_h); // 0|bgr0|0|bgr0 + const __m128i a4h = _mm_and_si128(bgra4, mask_h); // 0|bgr0|0|bgr0 + const __m128i b0h = _mm_srli_epi64(a0h, 8); // 000b|gr00|000b|gr00 + const __m128i b4h = _mm_srli_epi64(a4h, 8); // 000b|gr00|000b|gr00 + const __m128i c0 = _mm_or_si128(a0l, b0h); // rgbrgb00|rgbrgb00 + const __m128i c4 = _mm_or_si128(a4l, b4h); // rgbrgb00|rgbrgb00 + const __m128i c2 = _mm_srli_si128(c0, 8); + const __m128i c6 = _mm_srli_si128(c4, 8); + _mm_storel_epi64((__m128i*)(dst + 0), c0); + _mm_storel_epi64((__m128i*)(dst + 6), c2); + _mm_storel_epi64((__m128i*)(dst + 12), c4); + _mm_storel_epi64((__m128i*)(dst + 18), c6); + dst += 24; + num_pixels -= 8; + } + // left-overs + if (num_pixels > 0) { + VP8LConvertBGRAToBGR_C((const uint32_t*)in, num_pixels, dst); + } +} + +//------------------------------------------------------------------------------ +// Entry point + +extern void VP8LDspInitSSE2(void); + +WEBP_TSAN_IGNORE_FUNCTION void VP8LDspInitSSE2(void) { + VP8LPredictors[5] = Predictor5_SSE2; + VP8LPredictors[6] = Predictor6_SSE2; + VP8LPredictors[7] = Predictor7_SSE2; + VP8LPredictors[8] = Predictor8_SSE2; + VP8LPredictors[9] = Predictor9_SSE2; + VP8LPredictors[10] = Predictor10_SSE2; + VP8LPredictors[11] = Predictor11_SSE2; + VP8LPredictors[12] = Predictor12_SSE2; + VP8LPredictors[13] = Predictor13_SSE2; + + VP8LPredictorsAdd[0] = PredictorAdd0_SSE2; + VP8LPredictorsAdd[1] = PredictorAdd1_SSE2; + VP8LPredictorsAdd[2] = PredictorAdd2_SSE2; + VP8LPredictorsAdd[3] = PredictorAdd3_SSE2; + VP8LPredictorsAdd[4] = PredictorAdd4_SSE2; + VP8LPredictorsAdd[5] = PredictorAdd5_SSE2; + VP8LPredictorsAdd[6] = PredictorAdd6_SSE2; + VP8LPredictorsAdd[7] = PredictorAdd7_SSE2; + VP8LPredictorsAdd[8] = PredictorAdd8_SSE2; + VP8LPredictorsAdd[9] = PredictorAdd9_SSE2; + VP8LPredictorsAdd[10] = PredictorAdd10_SSE2; + VP8LPredictorsAdd[11] = PredictorAdd11_SSE2; + VP8LPredictorsAdd[12] = PredictorAdd12_SSE2; + VP8LPredictorsAdd[13] = PredictorAdd13_SSE2; + + VP8LAddGreenToBlueAndRed = AddGreenToBlueAndRed_SSE2; + VP8LTransformColorInverse = TransformColorInverse_SSE2; + + VP8LConvertBGRAToRGB = ConvertBGRAToRGB_SSE2; + VP8LConvertBGRAToRGBA = ConvertBGRAToRGBA_SSE2; + VP8LConvertBGRAToRGBA4444 = ConvertBGRAToRGBA4444_SSE2; + VP8LConvertBGRAToRGB565 = ConvertBGRAToRGB565_SSE2; + VP8LConvertBGRAToBGR = ConvertBGRAToBGR_SSE2; + + // SSE exports for AVX and above. + memcpy(VP8LPredictorsAdd_SSE, VP8LPredictorsAdd, sizeof(VP8LPredictorsAdd)); + + VP8LAddGreenToBlueAndRed_SSE = AddGreenToBlueAndRed_SSE2; + VP8LTransformColorInverse_SSE = TransformColorInverse_SSE2; + + VP8LConvertBGRAToRGB_SSE = ConvertBGRAToRGB_SSE2; + VP8LConvertBGRAToRGBA_SSE = ConvertBGRAToRGBA_SSE2; +} + +#else // !WEBP_USE_SSE2 + +WEBP_DSP_INIT_STUB(VP8LDspInitSSE2) + +#endif // WEBP_USE_SSE2 diff --git a/packages/core/src/zig/vendor/libwebp/src/dsp/lossless_sse41.c b/packages/core/src/zig/vendor/libwebp/src/dsp/lossless_sse41.c new file mode 100644 index 0000000000..598e679410 --- /dev/null +++ b/packages/core/src/zig/vendor/libwebp/src/dsp/lossless_sse41.c @@ -0,0 +1,139 @@ +// Copyright 2021 Google Inc. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the COPYING file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +// ----------------------------------------------------------------------------- +// +// SSE41 variant of methods for lossless decoder + +#include "src/dsp/dsp.h" + +#if defined(WEBP_USE_SSE41) +#include +#include + +#include "src/webp/types.h" +#include "src/dsp/cpu.h" +#include "src/dsp/lossless.h" + +//------------------------------------------------------------------------------ +// Color-space conversion functions + +static void TransformColorInverse_SSE41(const VP8LMultipliers* const m, + const uint32_t* const src, + int num_pixels, uint32_t* dst) { +// sign-extended multiplying constants, pre-shifted by 5. +#define CST(X) (((int16_t)(m->X << 8)) >> 5) // sign-extend + const __m128i mults_rb = + _mm_set1_epi32((int)((uint32_t)CST(green_to_red) << 16 | + (CST(green_to_blue) & 0xffff))); + const __m128i mults_b2 = _mm_set1_epi32(CST(red_to_blue)); +#undef CST + const __m128i mask_ag = _mm_set1_epi32((int)0xff00ff00); + const __m128i perm1 = _mm_setr_epi8(-1, 1, -1, 1, -1, 5, -1, 5, + -1, 9, -1, 9, -1, 13, -1, 13); + const __m128i perm2 = _mm_setr_epi8(-1, 2, -1, -1, -1, 6, -1, -1, + -1, 10, -1, -1, -1, 14, -1, -1); + int i; + for (i = 0; i + 4 <= num_pixels; i += 4) { + const __m128i A = _mm_loadu_si128((const __m128i*)(src + i)); + const __m128i B = _mm_shuffle_epi8(A, perm1); // argb -> g0g0 + const __m128i C = _mm_mulhi_epi16(B, mults_rb); + const __m128i D = _mm_add_epi8(A, C); + const __m128i E = _mm_shuffle_epi8(D, perm2); + const __m128i F = _mm_mulhi_epi16(E, mults_b2); + const __m128i G = _mm_add_epi8(D, F); + const __m128i out = _mm_blendv_epi8(G, A, mask_ag); + _mm_storeu_si128((__m128i*)&dst[i], out); + } + // Fall-back to C-version for left-overs. + if (i != num_pixels) { + VP8LTransformColorInverse_C(m, src + i, num_pixels - i, dst + i); + } +} + +//------------------------------------------------------------------------------ + +#define ARGB_TO_RGB_SSE41 do { \ + while (num_pixels >= 16) { \ + const __m128i in0 = _mm_loadu_si128(in + 0); \ + const __m128i in1 = _mm_loadu_si128(in + 1); \ + const __m128i in2 = _mm_loadu_si128(in + 2); \ + const __m128i in3 = _mm_loadu_si128(in + 3); \ + const __m128i a0 = _mm_shuffle_epi8(in0, perm0); \ + const __m128i a1 = _mm_shuffle_epi8(in1, perm1); \ + const __m128i a2 = _mm_shuffle_epi8(in2, perm2); \ + const __m128i a3 = _mm_shuffle_epi8(in3, perm3); \ + const __m128i b0 = _mm_blend_epi16(a0, a1, 0xc0); \ + const __m128i b1 = _mm_blend_epi16(a1, a2, 0xf0); \ + const __m128i b2 = _mm_blend_epi16(a2, a3, 0xfc); \ + _mm_storeu_si128(out + 0, b0); \ + _mm_storeu_si128(out + 1, b1); \ + _mm_storeu_si128(out + 2, b2); \ + in += 4; \ + out += 3; \ + num_pixels -= 16; \ + } \ +} while (0) + +static void ConvertBGRAToRGB_SSE41(const uint32_t* WEBP_RESTRICT src, + int num_pixels, uint8_t* WEBP_RESTRICT dst) { + const __m128i* in = (const __m128i*)src; + __m128i* out = (__m128i*)dst; + const __m128i perm0 = _mm_setr_epi8(2, 1, 0, 6, 5, 4, 10, 9, + 8, 14, 13, 12, -1, -1, -1, -1); + const __m128i perm1 = _mm_shuffle_epi32(perm0, 0x39); + const __m128i perm2 = _mm_shuffle_epi32(perm0, 0x4e); + const __m128i perm3 = _mm_shuffle_epi32(perm0, 0x93); + + ARGB_TO_RGB_SSE41; + + // left-overs + if (num_pixels > 0) { + VP8LConvertBGRAToRGB_C((const uint32_t*)in, num_pixels, (uint8_t*)out); + } +} + +static void ConvertBGRAToBGR_SSE41(const uint32_t* WEBP_RESTRICT src, + int num_pixels, uint8_t* WEBP_RESTRICT dst) { + const __m128i* in = (const __m128i*)src; + __m128i* out = (__m128i*)dst; + const __m128i perm0 = _mm_setr_epi8(0, 1, 2, 4, 5, 6, 8, 9, 10, + 12, 13, 14, -1, -1, -1, -1); + const __m128i perm1 = _mm_shuffle_epi32(perm0, 0x39); + const __m128i perm2 = _mm_shuffle_epi32(perm0, 0x4e); + const __m128i perm3 = _mm_shuffle_epi32(perm0, 0x93); + + ARGB_TO_RGB_SSE41; + + // left-overs + if (num_pixels > 0) { + VP8LConvertBGRAToBGR_C((const uint32_t*)in, num_pixels, (uint8_t*)out); + } +} + +#undef ARGB_TO_RGB_SSE41 + +//------------------------------------------------------------------------------ +// Entry point + +extern void VP8LDspInitSSE41(void); + +WEBP_TSAN_IGNORE_FUNCTION void VP8LDspInitSSE41(void) { + VP8LTransformColorInverse = TransformColorInverse_SSE41; + VP8LConvertBGRAToRGB = ConvertBGRAToRGB_SSE41; + VP8LConvertBGRAToBGR = ConvertBGRAToBGR_SSE41; + + // SSE exports for AVX and above. + VP8LTransformColorInverse_SSE = TransformColorInverse_SSE41; + VP8LConvertBGRAToRGB_SSE = ConvertBGRAToRGB_SSE41; +} + +#else // !WEBP_USE_SSE41 + +WEBP_DSP_INIT_STUB(VP8LDspInitSSE41) + +#endif // WEBP_USE_SSE41 diff --git a/packages/core/src/zig/vendor/libwebp/src/dsp/neon.h b/packages/core/src/zig/vendor/libwebp/src/dsp/neon.h new file mode 100644 index 0000000000..14acb4044b --- /dev/null +++ b/packages/core/src/zig/vendor/libwebp/src/dsp/neon.h @@ -0,0 +1,104 @@ +// Copyright 2014 Google Inc. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the COPYING file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +// ----------------------------------------------------------------------------- +// +// NEON common code. + +#ifndef WEBP_DSP_NEON_H_ +#define WEBP_DSP_NEON_H_ + +#include "src/dsp/dsp.h" + +#if defined(WEBP_USE_NEON) + +#include + +// Right now, some intrinsics functions seem slower, so we disable them +// everywhere except newer clang/gcc or aarch64 where the inline assembly is +// incompatible. +#if LOCAL_CLANG_PREREQ(3, 8) || LOCAL_GCC_PREREQ(4, 9) || WEBP_AARCH64 +#define WEBP_USE_INTRINSICS // use intrinsics when possible +#endif + +#define INIT_VECTOR2(v, a, b) do { \ + v.val[0] = a; \ + v.val[1] = b; \ +} while (0) + +#define INIT_VECTOR3(v, a, b, c) do { \ + v.val[0] = a; \ + v.val[1] = b; \ + v.val[2] = c; \ +} while (0) + +#define INIT_VECTOR4(v, a, b, c, d) do { \ + v.val[0] = a; \ + v.val[1] = b; \ + v.val[2] = c; \ + v.val[3] = d; \ +} while (0) + +// if using intrinsics, this flag avoids some functions that make gcc-4.6.3 +// crash ("internal compiler error: in immed_double_const, at emit-rtl."). +// (probably similar to gcc.gnu.org/bugzilla/show_bug.cgi?id=48183) +#if !(LOCAL_CLANG_PREREQ(3, 8) || LOCAL_GCC_PREREQ(4, 8) || WEBP_AARCH64) +#define WORK_AROUND_GCC +#endif + +static WEBP_INLINE int32x4x4_t Transpose4x4_NEON(const int32x4x4_t rows) { + uint64x2x2_t row01, row23; + + row01.val[0] = vreinterpretq_u64_s32(rows.val[0]); + row01.val[1] = vreinterpretq_u64_s32(rows.val[1]); + row23.val[0] = vreinterpretq_u64_s32(rows.val[2]); + row23.val[1] = vreinterpretq_u64_s32(rows.val[3]); + // Transpose 64-bit values (there's no vswp equivalent) + { + const uint64x1_t row0h = vget_high_u64(row01.val[0]); + const uint64x1_t row2l = vget_low_u64(row23.val[0]); + const uint64x1_t row1h = vget_high_u64(row01.val[1]); + const uint64x1_t row3l = vget_low_u64(row23.val[1]); + row01.val[0] = vcombine_u64(vget_low_u64(row01.val[0]), row2l); + row23.val[0] = vcombine_u64(row0h, vget_high_u64(row23.val[0])); + row01.val[1] = vcombine_u64(vget_low_u64(row01.val[1]), row3l); + row23.val[1] = vcombine_u64(row1h, vget_high_u64(row23.val[1])); + } + { + const int32x4x2_t out01 = vtrnq_s32(vreinterpretq_s32_u64(row01.val[0]), + vreinterpretq_s32_u64(row01.val[1])); + const int32x4x2_t out23 = vtrnq_s32(vreinterpretq_s32_u64(row23.val[0]), + vreinterpretq_s32_u64(row23.val[1])); + int32x4x4_t out; + out.val[0] = out01.val[0]; + out.val[1] = out01.val[1]; + out.val[2] = out23.val[0]; + out.val[3] = out23.val[1]; + return out; + } +} + +#if 0 // Useful debug macro. +#include +#define PRINT_REG(REG, SIZE) do { \ + int i; \ + printf("%s \t[%d]: 0x", #REG, SIZE); \ + if (SIZE == 8) { \ + uint8_t _tmp[8]; \ + vst1_u8(_tmp, (REG)); \ + for (i = 0; i < 8; ++i) printf("%.2x ", _tmp[i]); \ + } else if (SIZE == 16) { \ + uint16_t _tmp[4]; \ + vst1_u16(_tmp, (REG)); \ + for (i = 0; i < 4; ++i) printf("%.4x ", _tmp[i]); \ + } \ + printf("\n"); \ +} while (0) +#endif + +#endif // WEBP_USE_NEON +#endif // WEBP_DSP_NEON_H_ diff --git a/packages/core/src/zig/vendor/libwebp/src/dsp/rescaler.c b/packages/core/src/zig/vendor/libwebp/src/dsp/rescaler.c new file mode 100644 index 0000000000..eafccd442f --- /dev/null +++ b/packages/core/src/zig/vendor/libwebp/src/dsp/rescaler.c @@ -0,0 +1,256 @@ +// Copyright 2014 Google Inc. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the COPYING file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +// ----------------------------------------------------------------------------- +// +// Rescaling functions +// +// Author: Skal (pascal.massimino@gmail.com) + +#include +#include + +#include "src/dsp/cpu.h" +#include "src/webp/types.h" +#include "src/dsp/dsp.h" +#include "src/utils/rescaler_utils.h" + +//------------------------------------------------------------------------------ +// Implementations of critical functions ImportRow / ExportRow + +#define ROUNDER (WEBP_RESCALER_ONE >> 1) +#define MULT_FIX(x, y) (((uint64_t)(x) * (y) + ROUNDER) >> WEBP_RESCALER_RFIX) +#define MULT_FIX_FLOOR(x, y) (((uint64_t)(x) * (y)) >> WEBP_RESCALER_RFIX) + +//------------------------------------------------------------------------------ +// Row import + +void WebPRescalerImportRowExpand_C(WebPRescaler* WEBP_RESTRICT const wrk, + const uint8_t* WEBP_RESTRICT src) { + const int x_stride = wrk->num_channels; + const int x_out_max = wrk->dst_width * wrk->num_channels; + int channel; + assert(!WebPRescalerInputDone(wrk)); + assert(wrk->x_expand); + for (channel = 0; channel < x_stride; ++channel) { + int x_in = channel; + int x_out = channel; + // simple bilinear interpolation + int accum = wrk->x_add; + rescaler_t left = (rescaler_t)src[x_in]; + rescaler_t right = + (wrk->src_width > 1) ? (rescaler_t)src[x_in + x_stride] : left; + x_in += x_stride; + while (1) { + wrk->frow[x_out] = right * wrk->x_add + (left - right) * accum; + x_out += x_stride; + if (x_out >= x_out_max) break; + accum -= wrk->x_sub; + if (accum < 0) { + left = right; + x_in += x_stride; + assert(x_in < wrk->src_width * x_stride); + right = (rescaler_t)src[x_in]; + accum += wrk->x_add; + } + } + assert(wrk->x_sub == 0 /* <- special case for src_width=1 */ || accum == 0); + } +} + +void WebPRescalerImportRowShrink_C(WebPRescaler* WEBP_RESTRICT const wrk, + const uint8_t* WEBP_RESTRICT src) { + const int x_stride = wrk->num_channels; + const int x_out_max = wrk->dst_width * wrk->num_channels; + int channel; + assert(!WebPRescalerInputDone(wrk)); + assert(!wrk->x_expand); + for (channel = 0; channel < x_stride; ++channel) { + int x_in = channel; + int x_out = channel; + uint32_t sum = 0; + int accum = 0; + while (x_out < x_out_max) { + uint32_t base = 0; + accum += wrk->x_add; + while (accum > 0) { + accum -= wrk->x_sub; + assert(x_in < wrk->src_width * x_stride); + base = src[x_in]; + sum += base; + x_in += x_stride; + } + { // Emit next horizontal pixel. + const rescaler_t frac = base * (-accum); + wrk->frow[x_out] = sum * wrk->x_sub - frac; + // fresh fractional start for next pixel + sum = (int)MULT_FIX(frac, wrk->fx_scale); + } + x_out += x_stride; + } + assert(accum == 0); + } +} + +//------------------------------------------------------------------------------ +// Row export + +void WebPRescalerExportRowExpand_C(WebPRescaler* const wrk) { + int x_out; + uint8_t* const dst = wrk->dst; + rescaler_t* const irow = wrk->irow; + const int x_out_max = wrk->dst_width * wrk->num_channels; + const rescaler_t* const frow = wrk->frow; + assert(!WebPRescalerOutputDone(wrk)); + assert(wrk->y_accum <= 0); + assert(wrk->y_expand); + assert(wrk->y_sub != 0); + if (wrk->y_accum == 0) { + for (x_out = 0; x_out < x_out_max; ++x_out) { + const uint32_t J = frow[x_out]; + const int v = (int)MULT_FIX(J, wrk->fy_scale); + dst[x_out] = (v > 255) ? 255u : (uint8_t)v; + } + } else { + const uint32_t B = WEBP_RESCALER_FRAC(-wrk->y_accum, wrk->y_sub); + const uint32_t A = (uint32_t)(WEBP_RESCALER_ONE - B); + for (x_out = 0; x_out < x_out_max; ++x_out) { + const uint64_t I = (uint64_t)A * frow[x_out] + + (uint64_t)B * irow[x_out]; + const uint32_t J = (uint32_t)((I + ROUNDER) >> WEBP_RESCALER_RFIX); + const int v = (int)MULT_FIX(J, wrk->fy_scale); + dst[x_out] = (v > 255) ? 255u : (uint8_t)v; + } + } +} + +void WebPRescalerExportRowShrink_C(WebPRescaler* const wrk) { + int x_out; + uint8_t* const dst = wrk->dst; + rescaler_t* const irow = wrk->irow; + const int x_out_max = wrk->dst_width * wrk->num_channels; + const rescaler_t* const frow = wrk->frow; + const uint32_t yscale = wrk->fy_scale * (-wrk->y_accum); + assert(!WebPRescalerOutputDone(wrk)); + assert(wrk->y_accum <= 0); + assert(!wrk->y_expand); + if (yscale) { + for (x_out = 0; x_out < x_out_max; ++x_out) { + const uint32_t frac = (uint32_t)MULT_FIX_FLOOR(frow[x_out], yscale); + const int v = (int)MULT_FIX(irow[x_out] - frac, wrk->fxy_scale); + dst[x_out] = (v > 255) ? 255u : (uint8_t)v; + irow[x_out] = frac; // new fractional start + } + } else { + for (x_out = 0; x_out < x_out_max; ++x_out) { + const int v = (int)MULT_FIX(irow[x_out], wrk->fxy_scale); + dst[x_out] = (v > 255) ? 255u : (uint8_t)v; + irow[x_out] = 0; + } + } +} + +#undef MULT_FIX_FLOOR +#undef MULT_FIX +#undef ROUNDER + +//------------------------------------------------------------------------------ +// Main entry calls + +void WebPRescalerImportRow(WebPRescaler* WEBP_RESTRICT const wrk, + const uint8_t* WEBP_RESTRICT src) { + assert(!WebPRescalerInputDone(wrk)); + if (!wrk->x_expand) { + WebPRescalerImportRowShrink(wrk, src); + } else { + WebPRescalerImportRowExpand(wrk, src); + } +} + +void WebPRescalerExportRow(WebPRescaler* const wrk) { + if (wrk->y_accum <= 0) { + assert(!WebPRescalerOutputDone(wrk)); + if (wrk->y_expand) { + WebPRescalerExportRowExpand(wrk); + } else if (wrk->fxy_scale) { + WebPRescalerExportRowShrink(wrk); + } else { // special case + int i; + assert(wrk->src_height == wrk->dst_height && wrk->x_add == 1); + assert(wrk->src_width == 1 && wrk->dst_width <= 2); + for (i = 0; i < wrk->num_channels * wrk->dst_width; ++i) { + wrk->dst[i] = wrk->irow[i]; + wrk->irow[i] = 0; + } + } + wrk->y_accum += wrk->y_add; + wrk->dst += wrk->dst_stride; + ++wrk->dst_y; + } +} + +//------------------------------------------------------------------------------ + +WebPRescalerImportRowFunc WebPRescalerImportRowExpand; +WebPRescalerImportRowFunc WebPRescalerImportRowShrink; + +WebPRescalerExportRowFunc WebPRescalerExportRowExpand; +WebPRescalerExportRowFunc WebPRescalerExportRowShrink; + +extern VP8CPUInfo VP8GetCPUInfo; +extern void WebPRescalerDspInitSSE2(void); +extern void WebPRescalerDspInitMIPS32(void); +extern void WebPRescalerDspInitMIPSdspR2(void); +extern void WebPRescalerDspInitMSA(void); +extern void WebPRescalerDspInitNEON(void); + +WEBP_DSP_INIT_FUNC(WebPRescalerDspInit) { +#if !defined(WEBP_REDUCE_SIZE) +#if !WEBP_NEON_OMIT_C_CODE + WebPRescalerExportRowExpand = WebPRescalerExportRowExpand_C; + WebPRescalerExportRowShrink = WebPRescalerExportRowShrink_C; +#endif + + WebPRescalerImportRowExpand = WebPRescalerImportRowExpand_C; + WebPRescalerImportRowShrink = WebPRescalerImportRowShrink_C; + + if (VP8GetCPUInfo != NULL) { +#if defined(WEBP_HAVE_SSE2) + if (VP8GetCPUInfo(kSSE2)) { + WebPRescalerDspInitSSE2(); + } +#endif +#if defined(WEBP_USE_MIPS32) + if (VP8GetCPUInfo(kMIPS32)) { + WebPRescalerDspInitMIPS32(); + } +#endif +#if defined(WEBP_USE_MIPS_DSP_R2) + if (VP8GetCPUInfo(kMIPSdspR2)) { + WebPRescalerDspInitMIPSdspR2(); + } +#endif +#if defined(WEBP_USE_MSA) + if (VP8GetCPUInfo(kMSA)) { + WebPRescalerDspInitMSA(); + } +#endif + } + +#if defined(WEBP_HAVE_NEON) + if (WEBP_NEON_OMIT_C_CODE || + (VP8GetCPUInfo != NULL && VP8GetCPUInfo(kNEON))) { + WebPRescalerDspInitNEON(); + } +#endif + + assert(WebPRescalerExportRowExpand != NULL); + assert(WebPRescalerExportRowShrink != NULL); + assert(WebPRescalerImportRowExpand != NULL); + assert(WebPRescalerImportRowShrink != NULL); +#endif // WEBP_REDUCE_SIZE +} diff --git a/packages/core/src/zig/vendor/libwebp/src/dsp/rescaler_neon.c b/packages/core/src/zig/vendor/libwebp/src/dsp/rescaler_neon.c new file mode 100644 index 0000000000..ab4ddc0090 --- /dev/null +++ b/packages/core/src/zig/vendor/libwebp/src/dsp/rescaler_neon.c @@ -0,0 +1,192 @@ +// Copyright 2015 Google Inc. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the COPYING file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +// ----------------------------------------------------------------------------- +// +// NEON version of rescaling functions +// +// Author: Skal (pascal.massimino@gmail.com) + +#include "src/dsp/dsp.h" + +#if defined(WEBP_USE_NEON) && !defined(WEBP_REDUCE_SIZE) + +#include +#include +#include "src/dsp/neon.h" +#include "src/utils/rescaler_utils.h" + +#define ROUNDER (WEBP_RESCALER_ONE >> 1) +#define MULT_FIX_C(x, y) (((uint64_t)(x) * (y) + ROUNDER) >> WEBP_RESCALER_RFIX) +#define MULT_FIX_FLOOR_C(x, y) (((uint64_t)(x) * (y)) >> WEBP_RESCALER_RFIX) + +#define LOAD_32x4(SRC, DST) const uint32x4_t DST = vld1q_u32((SRC)) +#define LOAD_32x8(SRC, DST0, DST1) \ + LOAD_32x4(SRC + 0, DST0); \ + LOAD_32x4(SRC + 4, DST1) + +#define STORE_32x8(SRC0, SRC1, DST) do { \ + vst1q_u32((DST) + 0, SRC0); \ + vst1q_u32((DST) + 4, SRC1); \ +} while (0) + +#if (WEBP_RESCALER_RFIX == 32) +#define MAKE_HALF_CST(C) vdupq_n_s32((int32_t)((C) >> 1)) +// note: B is actualy scale>>1. See MAKE_HALF_CST +#define MULT_FIX(A, B) \ + vreinterpretq_u32_s32(vqrdmulhq_s32(vreinterpretq_s32_u32((A)), (B))) +#define MULT_FIX_FLOOR(A, B) \ + vreinterpretq_u32_s32(vqdmulhq_s32(vreinterpretq_s32_u32((A)), (B))) +#else +#error "MULT_FIX/WEBP_RESCALER_RFIX need some more work" +#endif + +static uint32x4_t Interpolate_NEON(const rescaler_t* WEBP_RESTRICT const frow, + const rescaler_t* WEBP_RESTRICT const irow, + uint32_t A, uint32_t B) { + LOAD_32x4(frow, A0); + LOAD_32x4(irow, B0); + const uint64x2_t C0 = vmull_n_u32(vget_low_u32(A0), A); + const uint64x2_t C1 = vmull_n_u32(vget_high_u32(A0), A); + const uint64x2_t D0 = vmlal_n_u32(C0, vget_low_u32(B0), B); + const uint64x2_t D1 = vmlal_n_u32(C1, vget_high_u32(B0), B); + const uint32x4_t E = vcombine_u32( + vrshrn_n_u64(D0, WEBP_RESCALER_RFIX), + vrshrn_n_u64(D1, WEBP_RESCALER_RFIX)); + return E; +} + +static void RescalerExportRowExpand_NEON(WebPRescaler* const wrk) { + int x_out; + uint8_t* const dst = wrk->dst; + rescaler_t* const irow = wrk->irow; + const int x_out_max = wrk->dst_width * wrk->num_channels; + const int max_span = x_out_max & ~7; + const rescaler_t* const frow = wrk->frow; + const uint32_t fy_scale = wrk->fy_scale; + const int32x4_t fy_scale_half = MAKE_HALF_CST(fy_scale); + assert(!WebPRescalerOutputDone(wrk)); + assert(wrk->y_accum <= 0); + assert(wrk->y_expand); + assert(wrk->y_sub != 0); + if (wrk->y_accum == 0) { + for (x_out = 0; x_out < max_span; x_out += 8) { + LOAD_32x4(frow + x_out + 0, A0); + LOAD_32x4(frow + x_out + 4, A1); + const uint32x4_t B0 = MULT_FIX(A0, fy_scale_half); + const uint32x4_t B1 = MULT_FIX(A1, fy_scale_half); + const uint16x4_t C0 = vmovn_u32(B0); + const uint16x4_t C1 = vmovn_u32(B1); + const uint8x8_t D = vqmovn_u16(vcombine_u16(C0, C1)); + vst1_u8(dst + x_out, D); + } + for (; x_out < x_out_max; ++x_out) { + const uint32_t J = frow[x_out]; + const int v = (int)MULT_FIX_C(J, fy_scale); + dst[x_out] = (v > 255) ? 255u : (uint8_t)v; + } + } else { + const uint32_t B = WEBP_RESCALER_FRAC(-wrk->y_accum, wrk->y_sub); + const uint32_t A = (uint32_t)(WEBP_RESCALER_ONE - B); + for (x_out = 0; x_out < max_span; x_out += 8) { + const uint32x4_t C0 = + Interpolate_NEON(frow + x_out + 0, irow + x_out + 0, A, B); + const uint32x4_t C1 = + Interpolate_NEON(frow + x_out + 4, irow + x_out + 4, A, B); + const uint32x4_t D0 = MULT_FIX(C0, fy_scale_half); + const uint32x4_t D1 = MULT_FIX(C1, fy_scale_half); + const uint16x4_t E0 = vmovn_u32(D0); + const uint16x4_t E1 = vmovn_u32(D1); + const uint8x8_t F = vqmovn_u16(vcombine_u16(E0, E1)); + vst1_u8(dst + x_out, F); + } + for (; x_out < x_out_max; ++x_out) { + const uint64_t I = (uint64_t)A * frow[x_out] + + (uint64_t)B * irow[x_out]; + const uint32_t J = (uint32_t)((I + ROUNDER) >> WEBP_RESCALER_RFIX); + const int v = (int)MULT_FIX_C(J, fy_scale); + dst[x_out] = (v > 255) ? 255u : (uint8_t)v; + } + } +} + +static void RescalerExportRowShrink_NEON(WebPRescaler* const wrk) { + int x_out; + uint8_t* const dst = wrk->dst; + rescaler_t* const irow = wrk->irow; + const int x_out_max = wrk->dst_width * wrk->num_channels; + const int max_span = x_out_max & ~7; + const rescaler_t* const frow = wrk->frow; + const uint32_t yscale = wrk->fy_scale * (-wrk->y_accum); + const uint32_t fxy_scale = wrk->fxy_scale; + const uint32x4_t zero = vdupq_n_u32(0); + const int32x4_t yscale_half = MAKE_HALF_CST(yscale); + const int32x4_t fxy_scale_half = MAKE_HALF_CST(fxy_scale); + assert(!WebPRescalerOutputDone(wrk)); + assert(wrk->y_accum <= 0); + assert(!wrk->y_expand); + if (yscale) { + for (x_out = 0; x_out < max_span; x_out += 8) { + LOAD_32x8(frow + x_out, in0, in1); + LOAD_32x8(irow + x_out, in2, in3); + const uint32x4_t A0 = MULT_FIX_FLOOR(in0, yscale_half); + const uint32x4_t A1 = MULT_FIX_FLOOR(in1, yscale_half); + const uint32x4_t B0 = vqsubq_u32(in2, A0); + const uint32x4_t B1 = vqsubq_u32(in3, A1); + const uint32x4_t C0 = MULT_FIX(B0, fxy_scale_half); + const uint32x4_t C1 = MULT_FIX(B1, fxy_scale_half); + const uint16x4_t D0 = vmovn_u32(C0); + const uint16x4_t D1 = vmovn_u32(C1); + const uint8x8_t E = vqmovn_u16(vcombine_u16(D0, D1)); + vst1_u8(dst + x_out, E); + STORE_32x8(A0, A1, irow + x_out); + } + for (; x_out < x_out_max; ++x_out) { + const uint32_t frac = (uint32_t)MULT_FIX_FLOOR_C(frow[x_out], yscale); + const int v = (int)MULT_FIX_C(irow[x_out] - frac, fxy_scale); + dst[x_out] = (v > 255) ? 255u : (uint8_t)v; + irow[x_out] = frac; // new fractional start + } + } else { + for (x_out = 0; x_out < max_span; x_out += 8) { + LOAD_32x8(irow + x_out, in0, in1); + const uint32x4_t A0 = MULT_FIX(in0, fxy_scale_half); + const uint32x4_t A1 = MULT_FIX(in1, fxy_scale_half); + const uint16x4_t B0 = vmovn_u32(A0); + const uint16x4_t B1 = vmovn_u32(A1); + const uint8x8_t C = vqmovn_u16(vcombine_u16(B0, B1)); + vst1_u8(dst + x_out, C); + STORE_32x8(zero, zero, irow + x_out); + } + for (; x_out < x_out_max; ++x_out) { + const int v = (int)MULT_FIX_C(irow[x_out], fxy_scale); + dst[x_out] = (v > 255) ? 255u : (uint8_t)v; + irow[x_out] = 0; + } + } +} + +#undef MULT_FIX_FLOOR_C +#undef MULT_FIX_C +#undef MULT_FIX_FLOOR +#undef MULT_FIX +#undef ROUNDER + +//------------------------------------------------------------------------------ + +extern void WebPRescalerDspInitNEON(void); + +WEBP_TSAN_IGNORE_FUNCTION void WebPRescalerDspInitNEON(void) { + WebPRescalerExportRowExpand = RescalerExportRowExpand_NEON; + WebPRescalerExportRowShrink = RescalerExportRowShrink_NEON; +} + +#else // !WEBP_USE_NEON + +WEBP_DSP_INIT_STUB(WebPRescalerDspInitNEON) + +#endif // WEBP_USE_NEON diff --git a/packages/core/src/zig/vendor/libwebp/src/dsp/rescaler_sse2.c b/packages/core/src/zig/vendor/libwebp/src/dsp/rescaler_sse2.c new file mode 100644 index 0000000000..be5508ca1f --- /dev/null +++ b/packages/core/src/zig/vendor/libwebp/src/dsp/rescaler_sse2.c @@ -0,0 +1,368 @@ +// Copyright 2015 Google Inc. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the COPYING file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +// ----------------------------------------------------------------------------- +// +// SSE2 Rescaling functions +// +// Author: Skal (pascal.massimino@gmail.com) + +#include "src/dsp/dsp.h" + +#if defined(WEBP_USE_SSE2) && !defined(WEBP_REDUCE_SIZE) +#include + +#include +#include + +#include "src/dsp/cpu.h" +#include "src/utils/rescaler_utils.h" +#include "src/utils/utils.h" +#include "src/webp/types.h" + +//------------------------------------------------------------------------------ +// Implementations of critical functions ImportRow / ExportRow + +#define ROUNDER (WEBP_RESCALER_ONE >> 1) +#define MULT_FIX(x, y) (((uint64_t)(x) * (y) + ROUNDER) >> WEBP_RESCALER_RFIX) +#define MULT_FIX_FLOOR(x, y) (((uint64_t)(x) * (y)) >> WEBP_RESCALER_RFIX) + +// input: 8 bytes ABCDEFGH -> output: A0E0B0F0C0G0D0H0 +static void LoadTwoPixels_SSE2(const uint8_t* const src, __m128i* out) { + const __m128i zero = _mm_setzero_si128(); + const __m128i A = _mm_loadl_epi64((const __m128i*)(src)); // ABCDEFGH + const __m128i B = _mm_unpacklo_epi8(A, zero); // A0B0C0D0E0F0G0H0 + const __m128i C = _mm_srli_si128(B, 8); // E0F0G0H0 + *out = _mm_unpacklo_epi16(B, C); +} + +// input: 8 bytes ABCDEFGH -> output: A0B0C0D0E0F0G0H0 +static void LoadEightPixels_SSE2(const uint8_t* const src, __m128i* out) { + const __m128i zero = _mm_setzero_si128(); + const __m128i A = _mm_loadl_epi64((const __m128i*)(src)); // ABCDEFGH + *out = _mm_unpacklo_epi8(A, zero); +} + +static void RescalerImportRowExpand_SSE2(WebPRescaler* WEBP_RESTRICT const wrk, + const uint8_t* WEBP_RESTRICT src) { + rescaler_t* frow = wrk->frow; + const rescaler_t* const frow_end = frow + wrk->dst_width * wrk->num_channels; + const int x_add = wrk->x_add; + int accum = x_add; + __m128i cur_pixels; + + // SSE2 implementation only works with 16b signed arithmetic at max. + if (wrk->src_width < 8 || accum >= (1 << 15)) { + WebPRescalerImportRowExpand_C(wrk, src); + return; + } + + assert(!WebPRescalerInputDone(wrk)); + assert(wrk->x_expand); + if (wrk->num_channels == 4) { + LoadTwoPixels_SSE2(src, &cur_pixels); + src += 4; + while (1) { + const __m128i mult = _mm_set1_epi32(((x_add - accum) << 16) | accum); + const __m128i out = _mm_madd_epi16(cur_pixels, mult); + _mm_storeu_si128((__m128i*)frow, out); + frow += 4; + if (frow >= frow_end) break; + accum -= wrk->x_sub; + if (accum < 0) { + LoadTwoPixels_SSE2(src, &cur_pixels); + src += 4; + accum += x_add; + } + } + } else { + int left; + const uint8_t* const src_limit = src + wrk->src_width - 8; + LoadEightPixels_SSE2(src, &cur_pixels); + src += 7; + left = 7; + while (1) { + const __m128i mult = _mm_cvtsi32_si128(((x_add - accum) << 16) | accum); + const __m128i out = _mm_madd_epi16(cur_pixels, mult); + assert(sizeof(*frow) == sizeof(uint32_t)); + WebPInt32ToMem((uint8_t*)frow, _mm_cvtsi128_si32(out)); + frow += 1; + if (frow >= frow_end) break; + accum -= wrk->x_sub; + if (accum < 0) { + if (--left) { + cur_pixels = _mm_srli_si128(cur_pixels, 2); + } else if (src <= src_limit) { + LoadEightPixels_SSE2(src, &cur_pixels); + src += 7; + left = 7; + } else { // tail + cur_pixels = _mm_srli_si128(cur_pixels, 2); + cur_pixels = _mm_insert_epi16(cur_pixels, src[1], 1); + src += 1; + left = 1; + } + accum += x_add; + } + } + } + assert(accum == 0); +} + +static void RescalerImportRowShrink_SSE2(WebPRescaler* WEBP_RESTRICT const wrk, + const uint8_t* WEBP_RESTRICT src) { + const int x_sub = wrk->x_sub; + int accum = 0; + const __m128i zero = _mm_setzero_si128(); + const __m128i mult0 = _mm_set1_epi16(x_sub); + const __m128i mult1 = _mm_set1_epi32(wrk->fx_scale); + const __m128i rounder = _mm_set_epi32(0, ROUNDER, 0, ROUNDER); + __m128i sum = zero; + rescaler_t* frow = wrk->frow; + const rescaler_t* const frow_end = wrk->frow + 4 * wrk->dst_width; + + if (wrk->num_channels != 4 || wrk->x_add > (x_sub << 7)) { + WebPRescalerImportRowShrink_C(wrk, src); + return; + } + assert(!WebPRescalerInputDone(wrk)); + assert(!wrk->x_expand); + + for (; frow < frow_end; frow += 4) { + __m128i base = zero; + accum += wrk->x_add; + while (accum > 0) { + const __m128i A = _mm_cvtsi32_si128(WebPMemToInt32(src)); + src += 4; + base = _mm_unpacklo_epi8(A, zero); + // To avoid overflow, we need: base * x_add / x_sub < 32768 + // => x_add < x_sub << 7. That's a 1/128 reduction ratio limit. + sum = _mm_add_epi16(sum, base); + accum -= x_sub; + } + { // Emit next horizontal pixel. + const __m128i mult = _mm_set1_epi16(-accum); + const __m128i frac0 = _mm_mullo_epi16(base, mult); // 16b x 16b -> 32b + const __m128i frac1 = _mm_mulhi_epu16(base, mult); + const __m128i frac = _mm_unpacklo_epi16(frac0, frac1); // frac is 32b + const __m128i A0 = _mm_mullo_epi16(sum, mult0); + const __m128i A1 = _mm_mulhi_epu16(sum, mult0); + const __m128i B0 = _mm_unpacklo_epi16(A0, A1); // sum * x_sub + const __m128i frow_out = _mm_sub_epi32(B0, frac); // sum * x_sub - frac + const __m128i D0 = _mm_srli_epi64(frac, 32); + const __m128i D1 = _mm_mul_epu32(frac, mult1); // 32b x 16b -> 64b + const __m128i D2 = _mm_mul_epu32(D0, mult1); + const __m128i E1 = _mm_add_epi64(D1, rounder); + const __m128i E2 = _mm_add_epi64(D2, rounder); + const __m128i F1 = _mm_shuffle_epi32(E1, 1 | (3 << 2)); + const __m128i F2 = _mm_shuffle_epi32(E2, 1 | (3 << 2)); + const __m128i G = _mm_unpacklo_epi32(F1, F2); + sum = _mm_packs_epi32(G, zero); + _mm_storeu_si128((__m128i*)frow, frow_out); + } + } + assert(accum == 0); +} + +//------------------------------------------------------------------------------ +// Row export + +// load *src as epi64, multiply by mult and store result in [out0 ... out3] +static WEBP_INLINE void LoadDispatchAndMult_SSE2( + const rescaler_t* WEBP_RESTRICT const src, const __m128i* const mult, + __m128i* const out0, __m128i* const out1, __m128i* const out2, + __m128i* const out3) { + const __m128i A0 = _mm_loadu_si128((const __m128i*)(src + 0)); + const __m128i A1 = _mm_loadu_si128((const __m128i*)(src + 4)); + const __m128i A2 = _mm_srli_epi64(A0, 32); + const __m128i A3 = _mm_srli_epi64(A1, 32); + if (mult != NULL) { + *out0 = _mm_mul_epu32(A0, *mult); + *out1 = _mm_mul_epu32(A1, *mult); + *out2 = _mm_mul_epu32(A2, *mult); + *out3 = _mm_mul_epu32(A3, *mult); + } else { + *out0 = A0; + *out1 = A1; + *out2 = A2; + *out3 = A3; + } +} + +static WEBP_INLINE void ProcessRow_SSE2(const __m128i* const A0, + const __m128i* const A1, + const __m128i* const A2, + const __m128i* const A3, + const __m128i* const mult, + uint8_t* const dst) { + const __m128i rounder = _mm_set_epi32(0, ROUNDER, 0, ROUNDER); + const __m128i mask = _mm_set_epi32(~0, 0, ~0, 0); + const __m128i B0 = _mm_mul_epu32(*A0, *mult); + const __m128i B1 = _mm_mul_epu32(*A1, *mult); + const __m128i B2 = _mm_mul_epu32(*A2, *mult); + const __m128i B3 = _mm_mul_epu32(*A3, *mult); + const __m128i C0 = _mm_add_epi64(B0, rounder); + const __m128i C1 = _mm_add_epi64(B1, rounder); + const __m128i C2 = _mm_add_epi64(B2, rounder); + const __m128i C3 = _mm_add_epi64(B3, rounder); + const __m128i D0 = _mm_srli_epi64(C0, WEBP_RESCALER_RFIX); + const __m128i D1 = _mm_srli_epi64(C1, WEBP_RESCALER_RFIX); +#if (WEBP_RESCALER_RFIX < 32) + const __m128i D2 = + _mm_and_si128(_mm_slli_epi64(C2, 32 - WEBP_RESCALER_RFIX), mask); + const __m128i D3 = + _mm_and_si128(_mm_slli_epi64(C3, 32 - WEBP_RESCALER_RFIX), mask); +#else + const __m128i D2 = _mm_and_si128(C2, mask); + const __m128i D3 = _mm_and_si128(C3, mask); +#endif + const __m128i E0 = _mm_or_si128(D0, D2); + const __m128i E1 = _mm_or_si128(D1, D3); + const __m128i F = _mm_packs_epi32(E0, E1); + const __m128i G = _mm_packus_epi16(F, F); + _mm_storel_epi64((__m128i*)dst, G); +} + +static void RescalerExportRowExpand_SSE2(WebPRescaler* const wrk) { + int x_out; + uint8_t* const dst = wrk->dst; + rescaler_t* const irow = wrk->irow; + const int x_out_max = wrk->dst_width * wrk->num_channels; + const rescaler_t* const frow = wrk->frow; + const __m128i mult = _mm_set_epi32(0, wrk->fy_scale, 0, wrk->fy_scale); + + assert(!WebPRescalerOutputDone(wrk)); + assert(wrk->y_accum <= 0 && wrk->y_sub + wrk->y_accum >= 0); + assert(wrk->y_expand); + if (wrk->y_accum == 0) { + for (x_out = 0; x_out + 8 <= x_out_max; x_out += 8) { + __m128i A0, A1, A2, A3; + LoadDispatchAndMult_SSE2(frow + x_out, NULL, &A0, &A1, &A2, &A3); + ProcessRow_SSE2(&A0, &A1, &A2, &A3, &mult, dst + x_out); + } + for (; x_out < x_out_max; ++x_out) { + const uint32_t J = frow[x_out]; + const int v = (int)MULT_FIX(J, wrk->fy_scale); + dst[x_out] = (v > 255) ? 255u : (uint8_t)v; + } + } else { + const uint32_t B = WEBP_RESCALER_FRAC(-wrk->y_accum, wrk->y_sub); + const uint32_t A = (uint32_t)(WEBP_RESCALER_ONE - B); + const __m128i mA = _mm_set_epi32(0, A, 0, A); + const __m128i mB = _mm_set_epi32(0, B, 0, B); + const __m128i rounder = _mm_set_epi32(0, ROUNDER, 0, ROUNDER); + for (x_out = 0; x_out + 8 <= x_out_max; x_out += 8) { + __m128i A0, A1, A2, A3, B0, B1, B2, B3; + LoadDispatchAndMult_SSE2(frow + x_out, &mA, &A0, &A1, &A2, &A3); + LoadDispatchAndMult_SSE2(irow + x_out, &mB, &B0, &B1, &B2, &B3); + { + const __m128i C0 = _mm_add_epi64(A0, B0); + const __m128i C1 = _mm_add_epi64(A1, B1); + const __m128i C2 = _mm_add_epi64(A2, B2); + const __m128i C3 = _mm_add_epi64(A3, B3); + const __m128i D0 = _mm_add_epi64(C0, rounder); + const __m128i D1 = _mm_add_epi64(C1, rounder); + const __m128i D2 = _mm_add_epi64(C2, rounder); + const __m128i D3 = _mm_add_epi64(C3, rounder); + const __m128i E0 = _mm_srli_epi64(D0, WEBP_RESCALER_RFIX); + const __m128i E1 = _mm_srli_epi64(D1, WEBP_RESCALER_RFIX); + const __m128i E2 = _mm_srli_epi64(D2, WEBP_RESCALER_RFIX); + const __m128i E3 = _mm_srli_epi64(D3, WEBP_RESCALER_RFIX); + ProcessRow_SSE2(&E0, &E1, &E2, &E3, &mult, dst + x_out); + } + } + for (; x_out < x_out_max; ++x_out) { + const uint64_t I = (uint64_t)A * frow[x_out] + + (uint64_t)B * irow[x_out]; + const uint32_t J = (uint32_t)((I + ROUNDER) >> WEBP_RESCALER_RFIX); + const int v = (int)MULT_FIX(J, wrk->fy_scale); + dst[x_out] = (v > 255) ? 255u : (uint8_t)v; + } + } +} + +static void RescalerExportRowShrink_SSE2(WebPRescaler* const wrk) { + int x_out; + uint8_t* const dst = wrk->dst; + rescaler_t* const irow = wrk->irow; + const int x_out_max = wrk->dst_width * wrk->num_channels; + const rescaler_t* const frow = wrk->frow; + const uint32_t yscale = wrk->fy_scale * (-wrk->y_accum); + assert(!WebPRescalerOutputDone(wrk)); + assert(wrk->y_accum <= 0); + assert(!wrk->y_expand); + if (yscale) { + const int scale_xy = wrk->fxy_scale; + const __m128i mult_xy = _mm_set_epi32(0, scale_xy, 0, scale_xy); + const __m128i mult_y = _mm_set_epi32(0, yscale, 0, yscale); + for (x_out = 0; x_out + 8 <= x_out_max; x_out += 8) { + __m128i A0, A1, A2, A3, B0, B1, B2, B3; + LoadDispatchAndMult_SSE2(irow + x_out, NULL, &A0, &A1, &A2, &A3); + LoadDispatchAndMult_SSE2(frow + x_out, &mult_y, &B0, &B1, &B2, &B3); + { + const __m128i D0 = _mm_srli_epi64(B0, WEBP_RESCALER_RFIX); // = frac + const __m128i D1 = _mm_srli_epi64(B1, WEBP_RESCALER_RFIX); + const __m128i D2 = _mm_srli_epi64(B2, WEBP_RESCALER_RFIX); + const __m128i D3 = _mm_srli_epi64(B3, WEBP_RESCALER_RFIX); + const __m128i E0 = _mm_sub_epi64(A0, D0); // irow[x] - frac + const __m128i E1 = _mm_sub_epi64(A1, D1); + const __m128i E2 = _mm_sub_epi64(A2, D2); + const __m128i E3 = _mm_sub_epi64(A3, D3); + const __m128i F2 = _mm_slli_epi64(D2, 32); + const __m128i F3 = _mm_slli_epi64(D3, 32); + const __m128i G0 = _mm_or_si128(D0, F2); + const __m128i G1 = _mm_or_si128(D1, F3); + _mm_storeu_si128((__m128i*)(irow + x_out + 0), G0); + _mm_storeu_si128((__m128i*)(irow + x_out + 4), G1); + ProcessRow_SSE2(&E0, &E1, &E2, &E3, &mult_xy, dst + x_out); + } + } + for (; x_out < x_out_max; ++x_out) { + const uint32_t frac = (int)MULT_FIX_FLOOR(frow[x_out], yscale); + const int v = (int)MULT_FIX(irow[x_out] - frac, wrk->fxy_scale); + dst[x_out] = (v > 255) ? 255u : (uint8_t)v; + irow[x_out] = frac; // new fractional start + } + } else { + const uint32_t scale = wrk->fxy_scale; + const __m128i mult = _mm_set_epi32(0, scale, 0, scale); + const __m128i zero = _mm_setzero_si128(); + for (x_out = 0; x_out + 8 <= x_out_max; x_out += 8) { + __m128i A0, A1, A2, A3; + LoadDispatchAndMult_SSE2(irow + x_out, NULL, &A0, &A1, &A2, &A3); + _mm_storeu_si128((__m128i*)(irow + x_out + 0), zero); + _mm_storeu_si128((__m128i*)(irow + x_out + 4), zero); + ProcessRow_SSE2(&A0, &A1, &A2, &A3, &mult, dst + x_out); + } + for (; x_out < x_out_max; ++x_out) { + const int v = (int)MULT_FIX(irow[x_out], scale); + dst[x_out] = (v > 255) ? 255u : (uint8_t)v; + irow[x_out] = 0; + } + } +} + +#undef MULT_FIX_FLOOR +#undef MULT_FIX +#undef ROUNDER + +//------------------------------------------------------------------------------ + +extern void WebPRescalerDspInitSSE2(void); + +WEBP_TSAN_IGNORE_FUNCTION void WebPRescalerDspInitSSE2(void) { + WebPRescalerImportRowExpand = RescalerImportRowExpand_SSE2; + WebPRescalerImportRowShrink = RescalerImportRowShrink_SSE2; + WebPRescalerExportRowExpand = RescalerExportRowExpand_SSE2; + WebPRescalerExportRowShrink = RescalerExportRowShrink_SSE2; +} + +#else // !WEBP_USE_SSE2 + +WEBP_DSP_INIT_STUB(WebPRescalerDspInitSSE2) + +#endif // WEBP_USE_SSE2 diff --git a/packages/core/src/zig/vendor/libwebp/src/dsp/upsampling.c b/packages/core/src/zig/vendor/libwebp/src/dsp/upsampling.c new file mode 100644 index 0000000000..c57f66c355 --- /dev/null +++ b/packages/core/src/zig/vendor/libwebp/src/dsp/upsampling.c @@ -0,0 +1,344 @@ +// Copyright 2011 Google Inc. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the COPYING file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +// ----------------------------------------------------------------------------- +// +// YUV to RGB upsampling functions. +// +// Author: somnath@google.com (Somnath Banerjee) + +#include +#include + +#include "src/dsp/cpu.h" +#include "src/webp/types.h" +#include "src/dsp/dsp.h" +#include "src/dsp/yuv.h" +#include "src/webp/decode.h" + +//------------------------------------------------------------------------------ +// Fancy upsampler + +#ifdef FANCY_UPSAMPLING + +// Fancy upsampling functions to convert YUV to RGB +WebPUpsampleLinePairFunc WebPUpsamplers[MODE_LAST]; + +// Given samples laid out in a square as: +// [a b] +// [c d] +// we interpolate u/v as: +// ([9*a + 3*b + 3*c + d 3*a + 9*b + 3*c + d] + [8 8]) / 16 +// ([3*a + b + 9*c + 3*d a + 3*b + 3*c + 9*d] [8 8]) / 16 + +// We process u and v together stashed into 32bit (16bit each). +#define LOAD_UV(u, v) ((u) | ((v) << 16)) + +#define UPSAMPLE_FUNC(FUNC_NAME, FUNC, XSTEP) \ +static void FUNC_NAME(const uint8_t* WEBP_RESTRICT top_y, \ + const uint8_t* WEBP_RESTRICT bottom_y, \ + const uint8_t* WEBP_RESTRICT top_u, \ + const uint8_t* WEBP_RESTRICT top_v, \ + const uint8_t* WEBP_RESTRICT cur_u, \ + const uint8_t* WEBP_RESTRICT cur_v, \ + uint8_t* WEBP_RESTRICT top_dst, \ + uint8_t* WEBP_RESTRICT bottom_dst, int len) { \ + int x; \ + const int last_pixel_pair = (len - 1) >> 1; \ + uint32_t tl_uv = LOAD_UV(top_u[0], top_v[0]); /* top-left sample */ \ + uint32_t l_uv = LOAD_UV(cur_u[0], cur_v[0]); /* left-sample */ \ + assert(top_y != NULL); \ + { \ + const uint32_t uv0 = (3 * tl_uv + l_uv + 0x00020002u) >> 2; \ + FUNC(top_y[0], uv0 & 0xff, (uv0 >> 16), top_dst); \ + } \ + if (bottom_y != NULL) { \ + const uint32_t uv0 = (3 * l_uv + tl_uv + 0x00020002u) >> 2; \ + FUNC(bottom_y[0], uv0 & 0xff, (uv0 >> 16), bottom_dst); \ + } \ + for (x = 1; x <= last_pixel_pair; ++x) { \ + const uint32_t t_uv = LOAD_UV(top_u[x], top_v[x]); /* top sample */ \ + const uint32_t uv = LOAD_UV(cur_u[x], cur_v[x]); /* sample */ \ + /* precompute invariant values associated with first and second diagonals*/\ + const uint32_t avg = tl_uv + t_uv + l_uv + uv + 0x00080008u; \ + const uint32_t diag_12 = (avg + 2 * (t_uv + l_uv)) >> 3; \ + const uint32_t diag_03 = (avg + 2 * (tl_uv + uv)) >> 3; \ + { \ + const uint32_t uv0 = (diag_12 + tl_uv) >> 1; \ + const uint32_t uv1 = (diag_03 + t_uv) >> 1; \ + FUNC(top_y[2 * x - 1], uv0 & 0xff, (uv0 >> 16), \ + top_dst + (2 * x - 1) * (XSTEP)); \ + FUNC(top_y[2 * x - 0], uv1 & 0xff, (uv1 >> 16), \ + top_dst + (2 * x - 0) * (XSTEP)); \ + } \ + if (bottom_y != NULL) { \ + const uint32_t uv0 = (diag_03 + l_uv) >> 1; \ + const uint32_t uv1 = (diag_12 + uv) >> 1; \ + FUNC(bottom_y[2 * x - 1], uv0 & 0xff, (uv0 >> 16), \ + bottom_dst + (2 * x - 1) * (XSTEP)); \ + FUNC(bottom_y[2 * x + 0], uv1 & 0xff, (uv1 >> 16), \ + bottom_dst + (2 * x + 0) * (XSTEP)); \ + } \ + tl_uv = t_uv; \ + l_uv = uv; \ + } \ + if (!(len & 1)) { \ + { \ + const uint32_t uv0 = (3 * tl_uv + l_uv + 0x00020002u) >> 2; \ + FUNC(top_y[len - 1], uv0 & 0xff, (uv0 >> 16), \ + top_dst + (len - 1) * (XSTEP)); \ + } \ + if (bottom_y != NULL) { \ + const uint32_t uv0 = (3 * l_uv + tl_uv + 0x00020002u) >> 2; \ + FUNC(bottom_y[len - 1], uv0 & 0xff, (uv0 >> 16), \ + bottom_dst + (len - 1) * (XSTEP)); \ + } \ + } \ +} + +// All variants implemented. +#if !WEBP_NEON_OMIT_C_CODE +UPSAMPLE_FUNC(UpsampleRgbaLinePair_C, VP8YuvToRgba, 4) +UPSAMPLE_FUNC(UpsampleBgraLinePair_C, VP8YuvToBgra, 4) +#if !defined(WEBP_REDUCE_CSP) +UPSAMPLE_FUNC(UpsampleArgbLinePair_C, VP8YuvToArgb, 4) +UPSAMPLE_FUNC(UpsampleRgbLinePair_C, VP8YuvToRgb, 3) +UPSAMPLE_FUNC(UpsampleBgrLinePair_C, VP8YuvToBgr, 3) +UPSAMPLE_FUNC(UpsampleRgba4444LinePair_C, VP8YuvToRgba4444, 2) +UPSAMPLE_FUNC(UpsampleRgb565LinePair_C, VP8YuvToRgb565, 2) +#else +static void EmptyUpsampleFunc(const uint8_t* top_y, const uint8_t* bottom_y, + const uint8_t* top_u, const uint8_t* top_v, + const uint8_t* cur_u, const uint8_t* cur_v, + uint8_t* top_dst, uint8_t* bottom_dst, int len) { + (void)top_y; + (void)bottom_y; + (void)top_u; + (void)top_v; + (void)cur_u; + (void)cur_v; + (void)top_dst; + (void)bottom_dst; + (void)len; + assert(0); // COLORSPACE SUPPORT NOT COMPILED +} +#define UpsampleArgbLinePair_C EmptyUpsampleFunc +#define UpsampleRgbLinePair_C EmptyUpsampleFunc +#define UpsampleBgrLinePair_C EmptyUpsampleFunc +#define UpsampleRgba4444LinePair_C EmptyUpsampleFunc +#define UpsampleRgb565LinePair_C EmptyUpsampleFunc +#endif // WEBP_REDUCE_CSP + +#endif + +#undef LOAD_UV +#undef UPSAMPLE_FUNC + +#endif // FANCY_UPSAMPLING + +//------------------------------------------------------------------------------ + +#if !defined(FANCY_UPSAMPLING) +#define DUAL_SAMPLE_FUNC(FUNC_NAME, FUNC) \ +static void FUNC_NAME(const uint8_t* WEBP_RESTRICT top_y, \ + const uint8_t* WEBP_RESTRICT bot_y, \ + const uint8_t* WEBP_RESTRICT top_u, \ + const uint8_t* WEBP_RESTRICT top_v, \ + const uint8_t* WEBP_RESTRICT bot_u, \ + const uint8_t* WEBP_RESTRICT bot_v, \ + uint8_t* WEBP_RESTRICT top_dst, \ + uint8_t* WEBP_RESTRICT bot_dst, int len) { \ + const int half_len = len >> 1; \ + int x; \ + assert(top_dst != NULL); \ + { \ + for (x = 0; x < half_len; ++x) { \ + FUNC(top_y[2 * x + 0], top_u[x], top_v[x], top_dst + 8 * x + 0); \ + FUNC(top_y[2 * x + 1], top_u[x], top_v[x], top_dst + 8 * x + 4); \ + } \ + if (len & 1) FUNC(top_y[2 * x + 0], top_u[x], top_v[x], top_dst + 8 * x); \ + } \ + if (bot_dst != NULL) { \ + for (x = 0; x < half_len; ++x) { \ + FUNC(bot_y[2 * x + 0], bot_u[x], bot_v[x], bot_dst + 8 * x + 0); \ + FUNC(bot_y[2 * x + 1], bot_u[x], bot_v[x], bot_dst + 8 * x + 4); \ + } \ + if (len & 1) FUNC(bot_y[2 * x + 0], bot_u[x], bot_v[x], bot_dst + 8 * x); \ + } \ +} + +DUAL_SAMPLE_FUNC(DualLineSamplerBGRA, VP8YuvToBgra) +DUAL_SAMPLE_FUNC(DualLineSamplerARGB, VP8YuvToArgb) +#undef DUAL_SAMPLE_FUNC + +#endif // !FANCY_UPSAMPLING + +WebPUpsampleLinePairFunc WebPGetLinePairConverter(int alpha_is_last) { + WebPInitUpsamplers(); +#ifdef FANCY_UPSAMPLING + return WebPUpsamplers[alpha_is_last ? MODE_BGRA : MODE_ARGB]; +#else + return (alpha_is_last ? DualLineSamplerBGRA : DualLineSamplerARGB); +#endif +} + +//------------------------------------------------------------------------------ +// YUV444 converter + +#define YUV444_FUNC(FUNC_NAME, FUNC, XSTEP) \ +extern void FUNC_NAME(const uint8_t* WEBP_RESTRICT y, \ + const uint8_t* WEBP_RESTRICT u, \ + const uint8_t* WEBP_RESTRICT v, \ + uint8_t* WEBP_RESTRICT dst, int len); \ +void FUNC_NAME(const uint8_t* WEBP_RESTRICT y, \ + const uint8_t* WEBP_RESTRICT u, \ + const uint8_t* WEBP_RESTRICT v, \ + uint8_t* WEBP_RESTRICT dst, int len) { \ + int i; \ + for (i = 0; i < len; ++i) FUNC(y[i], u[i], v[i], &dst[i * (XSTEP)]); \ +} + +YUV444_FUNC(WebPYuv444ToRgba_C, VP8YuvToRgba, 4) +YUV444_FUNC(WebPYuv444ToBgra_C, VP8YuvToBgra, 4) +#if !defined(WEBP_REDUCE_CSP) +YUV444_FUNC(WebPYuv444ToRgb_C, VP8YuvToRgb, 3) +YUV444_FUNC(WebPYuv444ToBgr_C, VP8YuvToBgr, 3) +YUV444_FUNC(WebPYuv444ToArgb_C, VP8YuvToArgb, 4) +YUV444_FUNC(WebPYuv444ToRgba4444_C, VP8YuvToRgba4444, 2) +YUV444_FUNC(WebPYuv444ToRgb565_C, VP8YuvToRgb565, 2) +#else +static void EmptyYuv444Func(const uint8_t* y, + const uint8_t* u, const uint8_t* v, + uint8_t* dst, int len) { + (void)y; + (void)u; + (void)v; + (void)dst; + (void)len; +} +#define WebPYuv444ToRgb_C EmptyYuv444Func +#define WebPYuv444ToBgr_C EmptyYuv444Func +#define WebPYuv444ToArgb_C EmptyYuv444Func +#define WebPYuv444ToRgba4444_C EmptyYuv444Func +#define WebPYuv444ToRgb565_C EmptyYuv444Func +#endif // WEBP_REDUCE_CSP + +#undef YUV444_FUNC + +WebPYUV444Converter WebPYUV444Converters[MODE_LAST]; + +extern VP8CPUInfo VP8GetCPUInfo; +extern void WebPInitYUV444ConvertersMIPSdspR2(void); +extern void WebPInitYUV444ConvertersSSE2(void); +extern void WebPInitYUV444ConvertersSSE41(void); + +WEBP_DSP_INIT_FUNC(WebPInitYUV444Converters) { + WebPYUV444Converters[MODE_RGBA] = WebPYuv444ToRgba_C; + WebPYUV444Converters[MODE_BGRA] = WebPYuv444ToBgra_C; + WebPYUV444Converters[MODE_RGB] = WebPYuv444ToRgb_C; + WebPYUV444Converters[MODE_BGR] = WebPYuv444ToBgr_C; + WebPYUV444Converters[MODE_ARGB] = WebPYuv444ToArgb_C; + WebPYUV444Converters[MODE_RGBA_4444] = WebPYuv444ToRgba4444_C; + WebPYUV444Converters[MODE_RGB_565] = WebPYuv444ToRgb565_C; + WebPYUV444Converters[MODE_rgbA] = WebPYuv444ToRgba_C; + WebPYUV444Converters[MODE_bgrA] = WebPYuv444ToBgra_C; + WebPYUV444Converters[MODE_Argb] = WebPYuv444ToArgb_C; + WebPYUV444Converters[MODE_rgbA_4444] = WebPYuv444ToRgba4444_C; + + if (VP8GetCPUInfo != NULL) { +#if defined(WEBP_HAVE_SSE2) + if (VP8GetCPUInfo(kSSE2)) { + WebPInitYUV444ConvertersSSE2(); + } +#endif +#if defined(WEBP_HAVE_SSE41) + if (VP8GetCPUInfo(kSSE4_1)) { + WebPInitYUV444ConvertersSSE41(); + } +#endif +#if defined(WEBP_USE_MIPS_DSP_R2) + if (VP8GetCPUInfo(kMIPSdspR2)) { + WebPInitYUV444ConvertersMIPSdspR2(); + } +#endif + } +} + +//------------------------------------------------------------------------------ +// Main calls + +extern void WebPInitUpsamplersSSE2(void); +extern void WebPInitUpsamplersSSE41(void); +extern void WebPInitUpsamplersNEON(void); +extern void WebPInitUpsamplersMIPSdspR2(void); +extern void WebPInitUpsamplersMSA(void); + +WEBP_DSP_INIT_FUNC(WebPInitUpsamplers) { +#ifdef FANCY_UPSAMPLING +#if !WEBP_NEON_OMIT_C_CODE + WebPUpsamplers[MODE_RGBA] = UpsampleRgbaLinePair_C; + WebPUpsamplers[MODE_BGRA] = UpsampleBgraLinePair_C; + WebPUpsamplers[MODE_rgbA] = UpsampleRgbaLinePair_C; + WebPUpsamplers[MODE_bgrA] = UpsampleBgraLinePair_C; + WebPUpsamplers[MODE_RGB] = UpsampleRgbLinePair_C; + WebPUpsamplers[MODE_BGR] = UpsampleBgrLinePair_C; + WebPUpsamplers[MODE_ARGB] = UpsampleArgbLinePair_C; + WebPUpsamplers[MODE_RGBA_4444] = UpsampleRgba4444LinePair_C; + WebPUpsamplers[MODE_RGB_565] = UpsampleRgb565LinePair_C; + WebPUpsamplers[MODE_Argb] = UpsampleArgbLinePair_C; + WebPUpsamplers[MODE_rgbA_4444] = UpsampleRgba4444LinePair_C; +#endif + + // If defined, use CPUInfo() to overwrite some pointers with faster versions. + if (VP8GetCPUInfo != NULL) { +#if defined(WEBP_HAVE_SSE2) + if (VP8GetCPUInfo(kSSE2)) { + WebPInitUpsamplersSSE2(); + } +#endif +#if defined(WEBP_HAVE_SSE41) + if (VP8GetCPUInfo(kSSE4_1)) { + WebPInitUpsamplersSSE41(); + } +#endif +#if defined(WEBP_USE_MIPS_DSP_R2) + if (VP8GetCPUInfo(kMIPSdspR2)) { + WebPInitUpsamplersMIPSdspR2(); + } +#endif +#if defined(WEBP_USE_MSA) + if (VP8GetCPUInfo(kMSA)) { + WebPInitUpsamplersMSA(); + } +#endif + } + +#if defined(WEBP_HAVE_NEON) + if (WEBP_NEON_OMIT_C_CODE || + (VP8GetCPUInfo != NULL && VP8GetCPUInfo(kNEON))) { + WebPInitUpsamplersNEON(); + } +#endif + + assert(WebPUpsamplers[MODE_RGBA] != NULL); + assert(WebPUpsamplers[MODE_BGRA] != NULL); + assert(WebPUpsamplers[MODE_rgbA] != NULL); + assert(WebPUpsamplers[MODE_bgrA] != NULL); +#if !defined(WEBP_REDUCE_CSP) || !WEBP_NEON_OMIT_C_CODE + assert(WebPUpsamplers[MODE_RGB] != NULL); + assert(WebPUpsamplers[MODE_BGR] != NULL); + assert(WebPUpsamplers[MODE_ARGB] != NULL); + assert(WebPUpsamplers[MODE_RGBA_4444] != NULL); + assert(WebPUpsamplers[MODE_RGB_565] != NULL); + assert(WebPUpsamplers[MODE_Argb] != NULL); + assert(WebPUpsamplers[MODE_rgbA_4444] != NULL); +#endif + +#endif // FANCY_UPSAMPLING +} + +//------------------------------------------------------------------------------ diff --git a/packages/core/src/zig/vendor/libwebp/src/dsp/upsampling_neon.c b/packages/core/src/zig/vendor/libwebp/src/dsp/upsampling_neon.c new file mode 100644 index 0000000000..2bd3e931e8 --- /dev/null +++ b/packages/core/src/zig/vendor/libwebp/src/dsp/upsampling_neon.c @@ -0,0 +1,290 @@ +// Copyright 2011 Google Inc. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the COPYING file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +// ----------------------------------------------------------------------------- +// +// NEON version of YUV to RGB upsampling functions. +// +// Author: mans@mansr.com (Mans Rullgard) +// Based on SSE code by: somnath@google.com (Somnath Banerjee) + +#include "src/dsp/dsp.h" + +#if defined(WEBP_USE_NEON) + +#include +#include +#include +#include "src/dsp/neon.h" +#include "src/dsp/yuv.h" + +#ifdef FANCY_UPSAMPLING + +//----------------------------------------------------------------------------- +// U/V upsampling + +// Loads 9 pixels each from rows r1 and r2 and generates 16 pixels. +#define UPSAMPLE_16PIXELS(r1, r2, out) do { \ + const uint8x8_t a = vld1_u8(r1 + 0); \ + const uint8x8_t b = vld1_u8(r1 + 1); \ + const uint8x8_t c = vld1_u8(r2 + 0); \ + const uint8x8_t d = vld1_u8(r2 + 1); \ + /* a + b + c + d */ \ + const uint16x8_t ad = vaddl_u8(a, d); \ + const uint16x8_t bc = vaddl_u8(b, c); \ + const uint16x8_t abcd = vaddq_u16(ad, bc); \ + /* 3a + b + c + 3d */ \ + const uint16x8_t al = vaddq_u16(abcd, vshlq_n_u16(ad, 1)); \ + /* a + 3b + 3c + d */ \ + const uint16x8_t bl = vaddq_u16(abcd, vshlq_n_u16(bc, 1)); \ + \ + const uint8x8_t diag2 = vshrn_n_u16(al, 3); \ + const uint8x8_t diag1 = vshrn_n_u16(bl, 3); \ + \ + const uint8x8_t A = vrhadd_u8(a, diag1); \ + const uint8x8_t B = vrhadd_u8(b, diag2); \ + const uint8x8_t C = vrhadd_u8(c, diag2); \ + const uint8x8_t D = vrhadd_u8(d, diag1); \ + \ + uint8x8x2_t A_B, C_D; \ + INIT_VECTOR2(A_B, A, B); \ + INIT_VECTOR2(C_D, C, D); \ + vst2_u8(out + 0, A_B); \ + vst2_u8(out + 32, C_D); \ +} while (0) + +// Turn the macro into a function for reducing code-size when non-critical +static void Upsample16Pixels_NEON(const uint8_t* WEBP_RESTRICT const r1, + const uint8_t* WEBP_RESTRICT const r2, + uint8_t* WEBP_RESTRICT const out) { + UPSAMPLE_16PIXELS(r1, r2, out); +} + +#define UPSAMPLE_LAST_BLOCK(tb, bb, num_pixels, out) { \ + uint8_t r1[9], r2[9]; \ + memcpy(r1, (tb), (num_pixels)); \ + memcpy(r2, (bb), (num_pixels)); \ + /* replicate last byte */ \ + memset(r1 + (num_pixels), r1[(num_pixels) - 1], 9 - (num_pixels)); \ + memset(r2 + (num_pixels), r2[(num_pixels) - 1], 9 - (num_pixels)); \ + Upsample16Pixels_NEON(r1, r2, out); \ +} + +//----------------------------------------------------------------------------- +// YUV->RGB conversion + +// note: we represent the 33050 large constant as 32768 + 282 +static const int16_t kCoeffs1[4] = { 19077, 26149, 6419, 13320 }; + +#define v255 vdup_n_u8(255) + +#define STORE_Rgb(out, r, g, b) do { \ + uint8x8x3_t r_g_b; \ + INIT_VECTOR3(r_g_b, r, g, b); \ + vst3_u8(out, r_g_b); \ +} while (0) + +#define STORE_Bgr(out, r, g, b) do { \ + uint8x8x3_t b_g_r; \ + INIT_VECTOR3(b_g_r, b, g, r); \ + vst3_u8(out, b_g_r); \ +} while (0) + +#define STORE_Rgba(out, r, g, b) do { \ + uint8x8x4_t r_g_b_v255; \ + INIT_VECTOR4(r_g_b_v255, r, g, b, v255); \ + vst4_u8(out, r_g_b_v255); \ +} while (0) + +#define STORE_Bgra(out, r, g, b) do { \ + uint8x8x4_t b_g_r_v255; \ + INIT_VECTOR4(b_g_r_v255, b, g, r, v255); \ + vst4_u8(out, b_g_r_v255); \ +} while (0) + +#define STORE_Argb(out, r, g, b) do { \ + uint8x8x4_t v255_r_g_b; \ + INIT_VECTOR4(v255_r_g_b, v255, r, g, b); \ + vst4_u8(out, v255_r_g_b); \ +} while (0) + +#if (WEBP_SWAP_16BIT_CSP == 0) +#define ZIP_U8(lo, hi) vzip_u8((lo), (hi)) +#else +#define ZIP_U8(lo, hi) vzip_u8((hi), (lo)) +#endif + +#define STORE_Rgba4444(out, r, g, b) do { \ + const uint8x8_t rg = vsri_n_u8(r, g, 4); /* shift g, insert r */ \ + const uint8x8_t ba = vsri_n_u8(b, v255, 4); /* shift a, insert b */ \ + const uint8x8x2_t rgba4444 = ZIP_U8(rg, ba); \ + vst1q_u8(out, vcombine_u8(rgba4444.val[0], rgba4444.val[1])); \ +} while (0) + +#define STORE_Rgb565(out, r, g, b) do { \ + const uint8x8_t rg = vsri_n_u8(r, g, 5); /* shift g and insert r */ \ + const uint8x8_t g1 = vshl_n_u8(g, 3); /* pre-shift g: 3bits */ \ + const uint8x8_t gb = vsri_n_u8(g1, b, 3); /* shift b and insert g */ \ + const uint8x8x2_t rgb565 = ZIP_U8(rg, gb); \ + vst1q_u8(out, vcombine_u8(rgb565.val[0], rgb565.val[1])); \ +} while (0) + +#define CONVERT8(FMT, XSTEP, N, src_y, src_uv, out, cur_x) do { \ + int i; \ + for (i = 0; i < N; i += 8) { \ + const int off = ((cur_x) + i) * XSTEP; \ + const uint8x8_t y = vld1_u8((src_y) + (cur_x) + i); \ + const uint8x8_t u = vld1_u8((src_uv) + i + 0); \ + const uint8x8_t v = vld1_u8((src_uv) + i + 16); \ + const int16x8_t Y0 = vreinterpretq_s16_u16(vshll_n_u8(y, 7)); \ + const int16x8_t U0 = vreinterpretq_s16_u16(vshll_n_u8(u, 7)); \ + const int16x8_t V0 = vreinterpretq_s16_u16(vshll_n_u8(v, 7)); \ + const int16x8_t Y1 = vqdmulhq_lane_s16(Y0, coeff1, 0); \ + const int16x8_t R0 = vqdmulhq_lane_s16(V0, coeff1, 1); \ + const int16x8_t G0 = vqdmulhq_lane_s16(U0, coeff1, 2); \ + const int16x8_t G1 = vqdmulhq_lane_s16(V0, coeff1, 3); \ + const int16x8_t B0 = vqdmulhq_n_s16(U0, 282); \ + const int16x8_t R1 = vqaddq_s16(Y1, R_Rounder); \ + const int16x8_t G2 = vqaddq_s16(Y1, G_Rounder); \ + const int16x8_t B1 = vqaddq_s16(Y1, B_Rounder); \ + const int16x8_t R2 = vqaddq_s16(R0, R1); \ + const int16x8_t G3 = vqaddq_s16(G0, G1); \ + const int16x8_t B2 = vqaddq_s16(B0, B1); \ + const int16x8_t G4 = vqsubq_s16(G2, G3); \ + const int16x8_t B3 = vqaddq_s16(B2, U0); \ + const uint8x8_t R = vqshrun_n_s16(R2, YUV_FIX2); \ + const uint8x8_t G = vqshrun_n_s16(G4, YUV_FIX2); \ + const uint8x8_t B = vqshrun_n_s16(B3, YUV_FIX2); \ + STORE_ ## FMT(out + off, R, G, B); \ + } \ +} while (0) + +#define CONVERT1(FUNC, XSTEP, N, src_y, src_uv, rgb, cur_x) { \ + int i; \ + for (i = 0; i < N; i++) { \ + const int off = ((cur_x) + i) * XSTEP; \ + const int y = src_y[(cur_x) + i]; \ + const int u = (src_uv)[i]; \ + const int v = (src_uv)[i + 16]; \ + FUNC(y, u, v, rgb + off); \ + } \ +} + +#define CONVERT2RGB_8(FMT, XSTEP, top_y, bottom_y, uv, \ + top_dst, bottom_dst, cur_x, len) { \ + CONVERT8(FMT, XSTEP, len, top_y, uv, top_dst, cur_x); \ + if (bottom_y != NULL) { \ + CONVERT8(FMT, XSTEP, len, bottom_y, (uv) + 32, bottom_dst, cur_x); \ + } \ +} + +#define CONVERT2RGB_1(FUNC, XSTEP, top_y, bottom_y, uv, \ + top_dst, bottom_dst, cur_x, len) { \ + CONVERT1(FUNC, XSTEP, len, top_y, uv, top_dst, cur_x); \ + if (bottom_y != NULL) { \ + CONVERT1(FUNC, XSTEP, len, bottom_y, (uv) + 32, bottom_dst, cur_x); \ + } \ +} + +#define NEON_UPSAMPLE_FUNC(FUNC_NAME, FMT, XSTEP) \ +static void FUNC_NAME(const uint8_t* WEBP_RESTRICT top_y, \ + const uint8_t* WEBP_RESTRICT bottom_y, \ + const uint8_t* WEBP_RESTRICT top_u, \ + const uint8_t* WEBP_RESTRICT top_v, \ + const uint8_t* WEBP_RESTRICT cur_u, \ + const uint8_t* WEBP_RESTRICT cur_v, \ + uint8_t* WEBP_RESTRICT top_dst, \ + uint8_t* WEBP_RESTRICT bottom_dst, int len) { \ + int block; \ + /* 16 byte aligned array to cache reconstructed u and v */ \ + uint8_t uv_buf[2 * 32 + 15]; \ + uint8_t* const r_uv = (uint8_t*)((uintptr_t)(uv_buf + 15) & ~(uintptr_t)15); \ + const int uv_len = (len + 1) >> 1; \ + /* 9 pixels must be read-able for each block */ \ + const int num_blocks = (uv_len - 1) >> 3; \ + const int leftover = uv_len - num_blocks * 8; \ + const int last_pos = 1 + 16 * num_blocks; \ + \ + const int u_diag = ((top_u[0] + cur_u[0]) >> 1) + 1; \ + const int v_diag = ((top_v[0] + cur_v[0]) >> 1) + 1; \ + \ + const int16x4_t coeff1 = vld1_s16(kCoeffs1); \ + const int16x8_t R_Rounder = vdupq_n_s16(-14234); \ + const int16x8_t G_Rounder = vdupq_n_s16(8708); \ + const int16x8_t B_Rounder = vdupq_n_s16(-17685); \ + \ + /* Treat the first pixel in regular way */ \ + assert(top_y != NULL); \ + { \ + const int u0 = (top_u[0] + u_diag) >> 1; \ + const int v0 = (top_v[0] + v_diag) >> 1; \ + VP8YuvTo ## FMT(top_y[0], u0, v0, top_dst); \ + } \ + if (bottom_y != NULL) { \ + const int u0 = (cur_u[0] + u_diag) >> 1; \ + const int v0 = (cur_v[0] + v_diag) >> 1; \ + VP8YuvTo ## FMT(bottom_y[0], u0, v0, bottom_dst); \ + } \ + \ + for (block = 0; block < num_blocks; ++block) { \ + UPSAMPLE_16PIXELS(top_u, cur_u, r_uv); \ + UPSAMPLE_16PIXELS(top_v, cur_v, r_uv + 16); \ + CONVERT2RGB_8(FMT, XSTEP, top_y, bottom_y, r_uv, \ + top_dst, bottom_dst, 16 * block + 1, 16); \ + top_u += 8; \ + cur_u += 8; \ + top_v += 8; \ + cur_v += 8; \ + } \ + \ + UPSAMPLE_LAST_BLOCK(top_u, cur_u, leftover, r_uv); \ + UPSAMPLE_LAST_BLOCK(top_v, cur_v, leftover, r_uv + 16); \ + CONVERT2RGB_1(VP8YuvTo ## FMT, XSTEP, top_y, bottom_y, r_uv, \ + top_dst, bottom_dst, last_pos, len - last_pos); \ +} + +// NEON variants of the fancy upsampler. +NEON_UPSAMPLE_FUNC(UpsampleRgbaLinePair_NEON, Rgba, 4) +NEON_UPSAMPLE_FUNC(UpsampleBgraLinePair_NEON, Bgra, 4) +#if !defined(WEBP_REDUCE_CSP) +NEON_UPSAMPLE_FUNC(UpsampleRgbLinePair_NEON, Rgb, 3) +NEON_UPSAMPLE_FUNC(UpsampleBgrLinePair_NEON, Bgr, 3) +NEON_UPSAMPLE_FUNC(UpsampleArgbLinePair_NEON, Argb, 4) +NEON_UPSAMPLE_FUNC(UpsampleRgba4444LinePair_NEON, Rgba4444, 2) +NEON_UPSAMPLE_FUNC(UpsampleRgb565LinePair_NEON, Rgb565, 2) +#endif // WEBP_REDUCE_CSP + +//------------------------------------------------------------------------------ +// Entry point + +extern WebPUpsampleLinePairFunc WebPUpsamplers[/* MODE_LAST */]; + +extern void WebPInitUpsamplersNEON(void); + +WEBP_TSAN_IGNORE_FUNCTION void WebPInitUpsamplersNEON(void) { + WebPUpsamplers[MODE_RGBA] = UpsampleRgbaLinePair_NEON; + WebPUpsamplers[MODE_BGRA] = UpsampleBgraLinePair_NEON; + WebPUpsamplers[MODE_rgbA] = UpsampleRgbaLinePair_NEON; + WebPUpsamplers[MODE_bgrA] = UpsampleBgraLinePair_NEON; +#if !defined(WEBP_REDUCE_CSP) + WebPUpsamplers[MODE_RGB] = UpsampleRgbLinePair_NEON; + WebPUpsamplers[MODE_BGR] = UpsampleBgrLinePair_NEON; + WebPUpsamplers[MODE_ARGB] = UpsampleArgbLinePair_NEON; + WebPUpsamplers[MODE_Argb] = UpsampleArgbLinePair_NEON; + WebPUpsamplers[MODE_RGB_565] = UpsampleRgb565LinePair_NEON; + WebPUpsamplers[MODE_RGBA_4444] = UpsampleRgba4444LinePair_NEON; + WebPUpsamplers[MODE_rgbA_4444] = UpsampleRgba4444LinePair_NEON; +#endif // WEBP_REDUCE_CSP +} + +#endif // FANCY_UPSAMPLING + +#endif // WEBP_USE_NEON + +#if !(defined(FANCY_UPSAMPLING) && defined(WEBP_USE_NEON)) +WEBP_DSP_INIT_STUB(WebPInitUpsamplersNEON) +#endif diff --git a/packages/core/src/zig/vendor/libwebp/src/dsp/upsampling_sse2.c b/packages/core/src/zig/vendor/libwebp/src/dsp/upsampling_sse2.c new file mode 100644 index 0000000000..0eecb11c5a --- /dev/null +++ b/packages/core/src/zig/vendor/libwebp/src/dsp/upsampling_sse2.c @@ -0,0 +1,280 @@ +// Copyright 2011 Google Inc. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the COPYING file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +// ----------------------------------------------------------------------------- +// +// SSE2 version of YUV to RGB upsampling functions. +// +// Author: somnath@google.com (Somnath Banerjee) + +#include "src/dsp/dsp.h" + +#if defined(WEBP_USE_SSE2) +#include + +#include +#include + +#include "src/webp/types.h" +#include "src/dsp/cpu.h" +#include "src/dsp/yuv.h" +#include "src/webp/decode.h" + +#ifdef FANCY_UPSAMPLING + +// We compute (9*a + 3*b + 3*c + d + 8) / 16 as follows +// u = (9*a + 3*b + 3*c + d + 8) / 16 +// = (a + (a + 3*b + 3*c + d) / 8 + 1) / 2 +// = (a + m + 1) / 2 +// where m = (a + 3*b + 3*c + d) / 8 +// = ((a + b + c + d) / 2 + b + c) / 4 +// +// Let's say k = (a + b + c + d) / 4. +// We can compute k as +// k = (s + t + 1) / 2 - ((a^d) | (b^c) | (s^t)) & 1 +// where s = (a + d + 1) / 2 and t = (b + c + 1) / 2 +// +// Then m can be written as +// m = (k + t + 1) / 2 - (((b^c) & (s^t)) | (k^t)) & 1 + +// Computes out = (k + in + 1) / 2 - ((ij & (s^t)) | (k^in)) & 1 +#define GET_M(ij, in, out) do { \ + const __m128i tmp0 = _mm_avg_epu8(k, (in)); /* (k + in + 1) / 2 */ \ + const __m128i tmp1 = _mm_and_si128((ij), st); /* (ij) & (s^t) */ \ + const __m128i tmp2 = _mm_xor_si128(k, (in)); /* (k^in) */ \ + const __m128i tmp3 = _mm_or_si128(tmp1, tmp2); /* ((ij) & (s^t)) | (k^in) */\ + const __m128i tmp4 = _mm_and_si128(tmp3, one); /* & 1 -> lsb_correction */ \ + (out) = _mm_sub_epi8(tmp0, tmp4); /* (k + in + 1) / 2 - lsb_correction */ \ +} while (0) + +// pack and store two alternating pixel rows +#define PACK_AND_STORE(a, b, da, db, out) do { \ + const __m128i t_a = _mm_avg_epu8(a, da); /* (9a + 3b + 3c + d + 8) / 16 */ \ + const __m128i t_b = _mm_avg_epu8(b, db); /* (3a + 9b + c + 3d + 8) / 16 */ \ + const __m128i t_1 = _mm_unpacklo_epi8(t_a, t_b); \ + const __m128i t_2 = _mm_unpackhi_epi8(t_a, t_b); \ + _mm_store_si128(((__m128i*)(out)) + 0, t_1); \ + _mm_store_si128(((__m128i*)(out)) + 1, t_2); \ +} while (0) + +// Loads 17 pixels each from rows r1 and r2 and generates 32 pixels. +#define UPSAMPLE_32PIXELS(r1, r2, out) do { \ + const __m128i one = _mm_set1_epi8(1); \ + const __m128i a = _mm_loadu_si128((const __m128i*)&(r1)[0]); \ + const __m128i b = _mm_loadu_si128((const __m128i*)&(r1)[1]); \ + const __m128i c = _mm_loadu_si128((const __m128i*)&(r2)[0]); \ + const __m128i d = _mm_loadu_si128((const __m128i*)&(r2)[1]); \ + \ + const __m128i s = _mm_avg_epu8(a, d); /* s = (a + d + 1) / 2 */ \ + const __m128i t = _mm_avg_epu8(b, c); /* t = (b + c + 1) / 2 */ \ + const __m128i st = _mm_xor_si128(s, t); /* st = s^t */ \ + \ + const __m128i ad = _mm_xor_si128(a, d); /* ad = a^d */ \ + const __m128i bc = _mm_xor_si128(b, c); /* bc = b^c */ \ + \ + const __m128i t1 = _mm_or_si128(ad, bc); /* (a^d) | (b^c) */ \ + const __m128i t2 = _mm_or_si128(t1, st); /* (a^d) | (b^c) | (s^t) */ \ + const __m128i t3 = _mm_and_si128(t2, one); /* (a^d) | (b^c) | (s^t) & 1 */ \ + const __m128i t4 = _mm_avg_epu8(s, t); \ + const __m128i k = _mm_sub_epi8(t4, t3); /* k = (a + b + c + d) / 4 */ \ + __m128i diag1, diag2; \ + \ + GET_M(bc, t, diag1); /* diag1 = (a + 3b + 3c + d) / 8 */ \ + GET_M(ad, s, diag2); /* diag2 = (3a + b + c + 3d) / 8 */ \ + \ + /* pack the alternate pixels */ \ + PACK_AND_STORE(a, b, diag1, diag2, (out) + 0); /* store top */ \ + PACK_AND_STORE(c, d, diag2, diag1, (out) + 2 * 32); /* store bottom */ \ +} while (0) + +// Turn the macro into a function for reducing code-size when non-critical +static void Upsample32Pixels_SSE2(const uint8_t* WEBP_RESTRICT const r1, + const uint8_t* WEBP_RESTRICT const r2, + uint8_t* WEBP_RESTRICT const out) { + UPSAMPLE_32PIXELS(r1, r2, out); +} + +#define UPSAMPLE_LAST_BLOCK(tb, bb, num_pixels, out) { \ + uint8_t r1[17], r2[17]; \ + memcpy(r1, (tb), (num_pixels)); \ + memcpy(r2, (bb), (num_pixels)); \ + /* replicate last byte */ \ + memset(r1 + (num_pixels), r1[(num_pixels) - 1], 17 - (num_pixels)); \ + memset(r2 + (num_pixels), r2[(num_pixels) - 1], 17 - (num_pixels)); \ + /* using the shared function instead of the macro saves ~3k code size */ \ + Upsample32Pixels_SSE2(r1, r2, out); \ +} + +#define CONVERT2RGB_32(FUNC, XSTEP, top_y, bottom_y, \ + top_dst, bottom_dst, cur_x) do { \ + FUNC##32_SSE2((top_y) + (cur_x), r_u, r_v, (top_dst) + (cur_x) * (XSTEP)); \ + if ((bottom_y) != NULL) { \ + FUNC##32_SSE2((bottom_y) + (cur_x), r_u + 64, r_v + 64, \ + (bottom_dst) + (cur_x) * (XSTEP)); \ + } \ +} while (0) + +#define SSE2_UPSAMPLE_FUNC(FUNC_NAME, FUNC, XSTEP) \ +static void FUNC_NAME(const uint8_t* WEBP_RESTRICT top_y, \ + const uint8_t* WEBP_RESTRICT bottom_y, \ + const uint8_t* WEBP_RESTRICT top_u, \ + const uint8_t* WEBP_RESTRICT top_v, \ + const uint8_t* WEBP_RESTRICT cur_u, \ + const uint8_t* WEBP_RESTRICT cur_v, \ + uint8_t* WEBP_RESTRICT top_dst, \ + uint8_t* WEBP_RESTRICT bottom_dst, int len) { \ + int uv_pos, pos; \ + /* 16byte-aligned array to cache reconstructed u and v */ \ + uint8_t uv_buf[14 * 32 + 15] = { 0 }; \ + uint8_t* const r_u = (uint8_t*)((uintptr_t)(uv_buf + 15) & ~(uintptr_t)15); \ + uint8_t* const r_v = r_u + 32; \ + \ + assert(top_y != NULL); \ + { /* Treat the first pixel in regular way */ \ + const int u_diag = ((top_u[0] + cur_u[0]) >> 1) + 1; \ + const int v_diag = ((top_v[0] + cur_v[0]) >> 1) + 1; \ + const int u0_t = (top_u[0] + u_diag) >> 1; \ + const int v0_t = (top_v[0] + v_diag) >> 1; \ + FUNC(top_y[0], u0_t, v0_t, top_dst); \ + if (bottom_y != NULL) { \ + const int u0_b = (cur_u[0] + u_diag) >> 1; \ + const int v0_b = (cur_v[0] + v_diag) >> 1; \ + FUNC(bottom_y[0], u0_b, v0_b, bottom_dst); \ + } \ + } \ + /* For UPSAMPLE_32PIXELS, 17 u/v values must be read-able for each block */ \ + for (pos = 1, uv_pos = 0; pos + 32 + 1 <= len; pos += 32, uv_pos += 16) { \ + UPSAMPLE_32PIXELS(top_u + uv_pos, cur_u + uv_pos, r_u); \ + UPSAMPLE_32PIXELS(top_v + uv_pos, cur_v + uv_pos, r_v); \ + CONVERT2RGB_32(FUNC, XSTEP, top_y, bottom_y, top_dst, bottom_dst, pos); \ + } \ + if (len > 1) { \ + const int left_over = ((len + 1) >> 1) - (pos >> 1); \ + uint8_t* const tmp_top_dst = r_u + 4 * 32; \ + uint8_t* const tmp_bottom_dst = tmp_top_dst + 4 * 32; \ + uint8_t* const tmp_top = tmp_bottom_dst + 4 * 32; \ + uint8_t* const tmp_bottom = (bottom_y == NULL) ? NULL : tmp_top + 32; \ + assert(left_over > 0); \ + UPSAMPLE_LAST_BLOCK(top_u + uv_pos, cur_u + uv_pos, left_over, r_u); \ + UPSAMPLE_LAST_BLOCK(top_v + uv_pos, cur_v + uv_pos, left_over, r_v); \ + memcpy(tmp_top, top_y + pos, len - pos); \ + if (bottom_y != NULL) memcpy(tmp_bottom, bottom_y + pos, len - pos); \ + CONVERT2RGB_32(FUNC, XSTEP, tmp_top, tmp_bottom, tmp_top_dst, \ + tmp_bottom_dst, 0); \ + memcpy(top_dst + pos * (XSTEP), tmp_top_dst, (len - pos) * (XSTEP)); \ + if (bottom_y != NULL) { \ + memcpy(bottom_dst + pos * (XSTEP), tmp_bottom_dst, \ + (len - pos) * (XSTEP)); \ + } \ + } \ +} + +// SSE2 variants of the fancy upsampler. +SSE2_UPSAMPLE_FUNC(UpsampleRgbaLinePair_SSE2, VP8YuvToRgba, 4) +SSE2_UPSAMPLE_FUNC(UpsampleBgraLinePair_SSE2, VP8YuvToBgra, 4) + +#if !defined(WEBP_REDUCE_CSP) +SSE2_UPSAMPLE_FUNC(UpsampleRgbLinePair_SSE2, VP8YuvToRgb, 3) +SSE2_UPSAMPLE_FUNC(UpsampleBgrLinePair_SSE2, VP8YuvToBgr, 3) +SSE2_UPSAMPLE_FUNC(UpsampleArgbLinePair_SSE2, VP8YuvToArgb, 4) +SSE2_UPSAMPLE_FUNC(UpsampleRgba4444LinePair_SSE2, VP8YuvToRgba4444, 2) +SSE2_UPSAMPLE_FUNC(UpsampleRgb565LinePair_SSE2, VP8YuvToRgb565, 2) +#endif // WEBP_REDUCE_CSP + +#undef GET_M +#undef PACK_AND_STORE +#undef UPSAMPLE_32PIXELS +#undef UPSAMPLE_LAST_BLOCK +#undef CONVERT2RGB +#undef CONVERT2RGB_32 +#undef SSE2_UPSAMPLE_FUNC + +//------------------------------------------------------------------------------ +// Entry point + +extern WebPUpsampleLinePairFunc WebPUpsamplers[/* MODE_LAST */]; + +extern void WebPInitUpsamplersSSE2(void); + +WEBP_TSAN_IGNORE_FUNCTION void WebPInitUpsamplersSSE2(void) { + WebPUpsamplers[MODE_RGBA] = UpsampleRgbaLinePair_SSE2; + WebPUpsamplers[MODE_BGRA] = UpsampleBgraLinePair_SSE2; + WebPUpsamplers[MODE_rgbA] = UpsampleRgbaLinePair_SSE2; + WebPUpsamplers[MODE_bgrA] = UpsampleBgraLinePair_SSE2; +#if !defined(WEBP_REDUCE_CSP) + WebPUpsamplers[MODE_RGB] = UpsampleRgbLinePair_SSE2; + WebPUpsamplers[MODE_BGR] = UpsampleBgrLinePair_SSE2; + WebPUpsamplers[MODE_ARGB] = UpsampleArgbLinePair_SSE2; + WebPUpsamplers[MODE_Argb] = UpsampleArgbLinePair_SSE2; + WebPUpsamplers[MODE_RGB_565] = UpsampleRgb565LinePair_SSE2; + WebPUpsamplers[MODE_RGBA_4444] = UpsampleRgba4444LinePair_SSE2; + WebPUpsamplers[MODE_rgbA_4444] = UpsampleRgba4444LinePair_SSE2; +#endif // WEBP_REDUCE_CSP +} + +#endif // FANCY_UPSAMPLING + +//------------------------------------------------------------------------------ + +extern WebPYUV444Converter WebPYUV444Converters[/* MODE_LAST */]; +extern void WebPInitYUV444ConvertersSSE2(void); + +#define YUV444_FUNC(FUNC_NAME, CALL, CALL_C, XSTEP) \ +extern void CALL_C(const uint8_t* WEBP_RESTRICT y, \ + const uint8_t* WEBP_RESTRICT u, \ + const uint8_t* WEBP_RESTRICT v, \ + uint8_t* WEBP_RESTRICT dst, int len); \ +static void FUNC_NAME(const uint8_t* WEBP_RESTRICT y, \ + const uint8_t* WEBP_RESTRICT u, \ + const uint8_t* WEBP_RESTRICT v, \ + uint8_t* WEBP_RESTRICT dst, int len) { \ + int i; \ + const int max_len = len & ~31; \ + for (i = 0; i < max_len; i += 32) { \ + CALL(y + i, u + i, v + i, dst + i * (XSTEP)); \ + } \ + if (i < len) { /* C-fallback */ \ + CALL_C(y + i, u + i, v + i, dst + i * (XSTEP), len - i); \ + } \ +} + +YUV444_FUNC(Yuv444ToRgba_SSE2, VP8YuvToRgba32_SSE2, WebPYuv444ToRgba_C, 4) +YUV444_FUNC(Yuv444ToBgra_SSE2, VP8YuvToBgra32_SSE2, WebPYuv444ToBgra_C, 4) +#if !defined(WEBP_REDUCE_CSP) +YUV444_FUNC(Yuv444ToRgb_SSE2, VP8YuvToRgb32_SSE2, WebPYuv444ToRgb_C, 3) +YUV444_FUNC(Yuv444ToBgr_SSE2, VP8YuvToBgr32_SSE2, WebPYuv444ToBgr_C, 3) +YUV444_FUNC(Yuv444ToArgb_SSE2, VP8YuvToArgb32_SSE2, WebPYuv444ToArgb_C, 4) +YUV444_FUNC(Yuv444ToRgba4444_SSE2, VP8YuvToRgba444432_SSE2, \ + WebPYuv444ToRgba4444_C, 2) +YUV444_FUNC(Yuv444ToRgb565_SSE2, VP8YuvToRgb56532_SSE2, WebPYuv444ToRgb565_C, 2) +#endif // WEBP_REDUCE_CSP + +WEBP_TSAN_IGNORE_FUNCTION void WebPInitYUV444ConvertersSSE2(void) { + WebPYUV444Converters[MODE_RGBA] = Yuv444ToRgba_SSE2; + WebPYUV444Converters[MODE_BGRA] = Yuv444ToBgra_SSE2; + WebPYUV444Converters[MODE_rgbA] = Yuv444ToRgba_SSE2; + WebPYUV444Converters[MODE_bgrA] = Yuv444ToBgra_SSE2; +#if !defined(WEBP_REDUCE_CSP) + WebPYUV444Converters[MODE_RGB] = Yuv444ToRgb_SSE2; + WebPYUV444Converters[MODE_BGR] = Yuv444ToBgr_SSE2; + WebPYUV444Converters[MODE_ARGB] = Yuv444ToArgb_SSE2; + WebPYUV444Converters[MODE_RGBA_4444] = Yuv444ToRgba4444_SSE2; + WebPYUV444Converters[MODE_RGB_565] = Yuv444ToRgb565_SSE2; + WebPYUV444Converters[MODE_Argb] = Yuv444ToArgb_SSE2; + WebPYUV444Converters[MODE_rgbA_4444] = Yuv444ToRgba4444_SSE2; +#endif // WEBP_REDUCE_CSP +} + +#else + +WEBP_DSP_INIT_STUB(WebPInitYUV444ConvertersSSE2) + +#endif // WEBP_USE_SSE2 + +#if !(defined(FANCY_UPSAMPLING) && defined(WEBP_USE_SSE2)) +WEBP_DSP_INIT_STUB(WebPInitUpsamplersSSE2) +#endif diff --git a/packages/core/src/zig/vendor/libwebp/src/dsp/upsampling_sse41.c b/packages/core/src/zig/vendor/libwebp/src/dsp/upsampling_sse41.c new file mode 100644 index 0000000000..cac9567aac --- /dev/null +++ b/packages/core/src/zig/vendor/libwebp/src/dsp/upsampling_sse41.c @@ -0,0 +1,252 @@ +// Copyright 2011 Google Inc. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the COPYING file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +// ----------------------------------------------------------------------------- +// +// SSE41 version of YUV to RGB upsampling functions. +// +// Author: somnath@google.com (Somnath Banerjee) + +#include "src/dsp/dsp.h" + +#if defined(WEBP_USE_SSE41) +#include + +#include +#include + +#include "src/webp/types.h" +#include "src/dsp/cpu.h" +#include "src/dsp/yuv.h" +#include "src/webp/decode.h" + +#ifdef FANCY_UPSAMPLING + +#if !defined(WEBP_REDUCE_CSP) + +// We compute (9*a + 3*b + 3*c + d + 8) / 16 as follows +// u = (9*a + 3*b + 3*c + d + 8) / 16 +// = (a + (a + 3*b + 3*c + d) / 8 + 1) / 2 +// = (a + m + 1) / 2 +// where m = (a + 3*b + 3*c + d) / 8 +// = ((a + b + c + d) / 2 + b + c) / 4 +// +// Let's say k = (a + b + c + d) / 4. +// We can compute k as +// k = (s + t + 1) / 2 - ((a^d) | (b^c) | (s^t)) & 1 +// where s = (a + d + 1) / 2 and t = (b + c + 1) / 2 +// +// Then m can be written as +// m = (k + t + 1) / 2 - (((b^c) & (s^t)) | (k^t)) & 1 + +// Computes out = (k + in + 1) / 2 - ((ij & (s^t)) | (k^in)) & 1 +#define GET_M(ij, in, out) do { \ + const __m128i tmp0 = _mm_avg_epu8(k, (in)); /* (k + in + 1) / 2 */ \ + const __m128i tmp1 = _mm_and_si128((ij), st); /* (ij) & (s^t) */ \ + const __m128i tmp2 = _mm_xor_si128(k, (in)); /* (k^in) */ \ + const __m128i tmp3 = _mm_or_si128(tmp1, tmp2); /* ((ij) & (s^t)) | (k^in) */\ + const __m128i tmp4 = _mm_and_si128(tmp3, one); /* & 1 -> lsb_correction */ \ + (out) = _mm_sub_epi8(tmp0, tmp4); /* (k + in + 1) / 2 - lsb_correction */ \ +} while (0) + +// pack and store two alternating pixel rows +#define PACK_AND_STORE(a, b, da, db, out) do { \ + const __m128i t_a = _mm_avg_epu8(a, da); /* (9a + 3b + 3c + d + 8) / 16 */ \ + const __m128i t_b = _mm_avg_epu8(b, db); /* (3a + 9b + c + 3d + 8) / 16 */ \ + const __m128i t_1 = _mm_unpacklo_epi8(t_a, t_b); \ + const __m128i t_2 = _mm_unpackhi_epi8(t_a, t_b); \ + _mm_store_si128(((__m128i*)(out)) + 0, t_1); \ + _mm_store_si128(((__m128i*)(out)) + 1, t_2); \ +} while (0) + +// Loads 17 pixels each from rows r1 and r2 and generates 32 pixels. +#define UPSAMPLE_32PIXELS(r1, r2, out) do { \ + const __m128i one = _mm_set1_epi8(1); \ + const __m128i a = _mm_loadu_si128((const __m128i*)&(r1)[0]); \ + const __m128i b = _mm_loadu_si128((const __m128i*)&(r1)[1]); \ + const __m128i c = _mm_loadu_si128((const __m128i*)&(r2)[0]); \ + const __m128i d = _mm_loadu_si128((const __m128i*)&(r2)[1]); \ + \ + const __m128i s = _mm_avg_epu8(a, d); /* s = (a + d + 1) / 2 */ \ + const __m128i t = _mm_avg_epu8(b, c); /* t = (b + c + 1) / 2 */ \ + const __m128i st = _mm_xor_si128(s, t); /* st = s^t */ \ + \ + const __m128i ad = _mm_xor_si128(a, d); /* ad = a^d */ \ + const __m128i bc = _mm_xor_si128(b, c); /* bc = b^c */ \ + \ + const __m128i t1 = _mm_or_si128(ad, bc); /* (a^d) | (b^c) */ \ + const __m128i t2 = _mm_or_si128(t1, st); /* (a^d) | (b^c) | (s^t) */ \ + const __m128i t3 = _mm_and_si128(t2, one); /* (a^d) | (b^c) | (s^t) & 1 */ \ + const __m128i t4 = _mm_avg_epu8(s, t); \ + const __m128i k = _mm_sub_epi8(t4, t3); /* k = (a + b + c + d) / 4 */ \ + __m128i diag1, diag2; \ + \ + GET_M(bc, t, diag1); /* diag1 = (a + 3b + 3c + d) / 8 */ \ + GET_M(ad, s, diag2); /* diag2 = (3a + b + c + 3d) / 8 */ \ + \ + /* pack the alternate pixels */ \ + PACK_AND_STORE(a, b, diag1, diag2, (out) + 0); /* store top */ \ + PACK_AND_STORE(c, d, diag2, diag1, (out) + 2 * 32); /* store bottom */ \ +} while (0) + +// Turn the macro into a function for reducing code-size when non-critical +static void Upsample32Pixels_SSE41(const uint8_t* WEBP_RESTRICT const r1, + const uint8_t* WEBP_RESTRICT const r2, + uint8_t* WEBP_RESTRICT const out) { + UPSAMPLE_32PIXELS(r1, r2, out); +} + +#define UPSAMPLE_LAST_BLOCK(tb, bb, num_pixels, out) { \ + uint8_t r1[17], r2[17]; \ + memcpy(r1, (tb), (num_pixels)); \ + memcpy(r2, (bb), (num_pixels)); \ + /* replicate last byte */ \ + memset(r1 + (num_pixels), r1[(num_pixels) - 1], 17 - (num_pixels)); \ + memset(r2 + (num_pixels), r2[(num_pixels) - 1], 17 - (num_pixels)); \ + /* using the shared function instead of the macro saves ~3k code size */ \ + Upsample32Pixels_SSE41(r1, r2, out); \ +} + +#define CONVERT2RGB_32(FUNC, XSTEP, top_y, bottom_y, \ + top_dst, bottom_dst, cur_x) do { \ + FUNC##32_SSE41((top_y) + (cur_x), r_u, r_v, (top_dst) + (cur_x) * (XSTEP)); \ + if ((bottom_y) != NULL) { \ + FUNC##32_SSE41((bottom_y) + (cur_x), r_u + 64, r_v + 64, \ + (bottom_dst) + (cur_x) * (XSTEP)); \ + } \ +} while (0) + +#define SSE4_UPSAMPLE_FUNC(FUNC_NAME, FUNC, XSTEP) \ +static void FUNC_NAME(const uint8_t* WEBP_RESTRICT top_y, \ + const uint8_t* WEBP_RESTRICT bottom_y, \ + const uint8_t* WEBP_RESTRICT top_u, \ + const uint8_t* WEBP_RESTRICT top_v, \ + const uint8_t* WEBP_RESTRICT cur_u, \ + const uint8_t* WEBP_RESTRICT cur_v, \ + uint8_t* WEBP_RESTRICT top_dst, \ + uint8_t* WEBP_RESTRICT bottom_dst, int len) { \ + int uv_pos, pos; \ + /* 16byte-aligned array to cache reconstructed u and v */ \ + uint8_t uv_buf[14 * 32 + 15] = { 0 }; \ + uint8_t* const r_u = (uint8_t*)((uintptr_t)(uv_buf + 15) & ~(uintptr_t)15); \ + uint8_t* const r_v = r_u + 32; \ + \ + assert(top_y != NULL); \ + { /* Treat the first pixel in regular way */ \ + const int u_diag = ((top_u[0] + cur_u[0]) >> 1) + 1; \ + const int v_diag = ((top_v[0] + cur_v[0]) >> 1) + 1; \ + const int u0_t = (top_u[0] + u_diag) >> 1; \ + const int v0_t = (top_v[0] + v_diag) >> 1; \ + FUNC(top_y[0], u0_t, v0_t, top_dst); \ + if (bottom_y != NULL) { \ + const int u0_b = (cur_u[0] + u_diag) >> 1; \ + const int v0_b = (cur_v[0] + v_diag) >> 1; \ + FUNC(bottom_y[0], u0_b, v0_b, bottom_dst); \ + } \ + } \ + /* For UPSAMPLE_32PIXELS, 17 u/v values must be read-able for each block */ \ + for (pos = 1, uv_pos = 0; pos + 32 + 1 <= len; pos += 32, uv_pos += 16) { \ + UPSAMPLE_32PIXELS(top_u + uv_pos, cur_u + uv_pos, r_u); \ + UPSAMPLE_32PIXELS(top_v + uv_pos, cur_v + uv_pos, r_v); \ + CONVERT2RGB_32(FUNC, XSTEP, top_y, bottom_y, top_dst, bottom_dst, pos); \ + } \ + if (len > 1) { \ + const int left_over = ((len + 1) >> 1) - (pos >> 1); \ + uint8_t* const tmp_top_dst = r_u + 4 * 32; \ + uint8_t* const tmp_bottom_dst = tmp_top_dst + 4 * 32; \ + uint8_t* const tmp_top = tmp_bottom_dst + 4 * 32; \ + uint8_t* const tmp_bottom = (bottom_y == NULL) ? NULL : tmp_top + 32; \ + assert(left_over > 0); \ + UPSAMPLE_LAST_BLOCK(top_u + uv_pos, cur_u + uv_pos, left_over, r_u); \ + UPSAMPLE_LAST_BLOCK(top_v + uv_pos, cur_v + uv_pos, left_over, r_v); \ + memcpy(tmp_top, top_y + pos, len - pos); \ + if (bottom_y != NULL) memcpy(tmp_bottom, bottom_y + pos, len - pos); \ + CONVERT2RGB_32(FUNC, XSTEP, tmp_top, tmp_bottom, tmp_top_dst, \ + tmp_bottom_dst, 0); \ + memcpy(top_dst + pos * (XSTEP), tmp_top_dst, (len - pos) * (XSTEP)); \ + if (bottom_y != NULL) { \ + memcpy(bottom_dst + pos * (XSTEP), tmp_bottom_dst, \ + (len - pos) * (XSTEP)); \ + } \ + } \ +} + +// SSE4 variants of the fancy upsampler. +SSE4_UPSAMPLE_FUNC(UpsampleRgbLinePair_SSE41, VP8YuvToRgb, 3) +SSE4_UPSAMPLE_FUNC(UpsampleBgrLinePair_SSE41, VP8YuvToBgr, 3) + +#undef GET_M +#undef PACK_AND_STORE +#undef UPSAMPLE_32PIXELS +#undef UPSAMPLE_LAST_BLOCK +#undef CONVERT2RGB +#undef CONVERT2RGB_32 +#undef SSE4_UPSAMPLE_FUNC + +#endif // WEBP_REDUCE_CSP + +//------------------------------------------------------------------------------ +// Entry point + +extern WebPUpsampleLinePairFunc WebPUpsamplers[/* MODE_LAST */]; + +extern void WebPInitUpsamplersSSE41(void); + +WEBP_TSAN_IGNORE_FUNCTION void WebPInitUpsamplersSSE41(void) { +#if !defined(WEBP_REDUCE_CSP) + WebPUpsamplers[MODE_RGB] = UpsampleRgbLinePair_SSE41; + WebPUpsamplers[MODE_BGR] = UpsampleBgrLinePair_SSE41; +#endif // WEBP_REDUCE_CSP +} + +#endif // FANCY_UPSAMPLING + +//------------------------------------------------------------------------------ + +extern WebPYUV444Converter WebPYUV444Converters[/* MODE_LAST */]; +extern void WebPInitYUV444ConvertersSSE41(void); + +#define YUV444_FUNC(FUNC_NAME, CALL, CALL_C, XSTEP) \ +extern void CALL_C(const uint8_t* WEBP_RESTRICT y, \ + const uint8_t* WEBP_RESTRICT u, \ + const uint8_t* WEBP_RESTRICT v, \ + uint8_t* WEBP_RESTRICT dst, int len); \ +static void FUNC_NAME(const uint8_t* WEBP_RESTRICT y, \ + const uint8_t* WEBP_RESTRICT u, \ + const uint8_t* WEBP_RESTRICT v, \ + uint8_t* WEBP_RESTRICT dst, int len) { \ + int i; \ + const int max_len = len & ~31; \ + for (i = 0; i < max_len; i += 32) { \ + CALL(y + i, u + i, v + i, dst + i * (XSTEP)); \ + } \ + if (i < len) { /* C-fallback */ \ + CALL_C(y + i, u + i, v + i, dst + i * (XSTEP), len - i); \ + } \ +} + +#if !defined(WEBP_REDUCE_CSP) +YUV444_FUNC(Yuv444ToRgb_SSE41, VP8YuvToRgb32_SSE41, WebPYuv444ToRgb_C, 3) +YUV444_FUNC(Yuv444ToBgr_SSE41, VP8YuvToBgr32_SSE41, WebPYuv444ToBgr_C, 3) +#endif // WEBP_REDUCE_CSP + +WEBP_TSAN_IGNORE_FUNCTION void WebPInitYUV444ConvertersSSE41(void) { +#if !defined(WEBP_REDUCE_CSP) + WebPYUV444Converters[MODE_RGB] = Yuv444ToRgb_SSE41; + WebPYUV444Converters[MODE_BGR] = Yuv444ToBgr_SSE41; +#endif // WEBP_REDUCE_CSP +} + +#else + +WEBP_DSP_INIT_STUB(WebPInitYUV444ConvertersSSE41) + +#endif // WEBP_USE_SSE41 + +#if !(defined(FANCY_UPSAMPLING) && defined(WEBP_USE_SSE41)) +WEBP_DSP_INIT_STUB(WebPInitUpsamplersSSE41) +#endif diff --git a/packages/core/src/zig/vendor/libwebp/src/dsp/yuv.c b/packages/core/src/zig/vendor/libwebp/src/dsp/yuv.c new file mode 100644 index 0000000000..62f1ecc156 --- /dev/null +++ b/packages/core/src/zig/vendor/libwebp/src/dsp/yuv.c @@ -0,0 +1,261 @@ +// Copyright 2010 Google Inc. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the COPYING file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +// ----------------------------------------------------------------------------- +// +// YUV->RGB conversion functions +// +// Author: Skal (pascal.massimino@gmail.com) + +#include +#include + +#include "src/dsp/cpu.h" +#include "src/webp/types.h" +#include "src/dsp/dsp.h" +#include "src/dsp/yuv.h" +#include "src/webp/decode.h" + +//----------------------------------------------------------------------------- +// Plain-C version + +#define ROW_FUNC(FUNC_NAME, FUNC, XSTEP) \ +static void FUNC_NAME(const uint8_t* WEBP_RESTRICT y, \ + const uint8_t* WEBP_RESTRICT u, \ + const uint8_t* WEBP_RESTRICT v, \ + uint8_t* WEBP_RESTRICT dst, int len) { \ + const uint8_t* const end = dst + (len & ~1) * (XSTEP); \ + while (dst != end) { \ + FUNC(y[0], u[0], v[0], dst); \ + FUNC(y[1], u[0], v[0], dst + (XSTEP)); \ + y += 2; \ + ++u; \ + ++v; \ + dst += 2 * (XSTEP); \ + } \ + if (len & 1) { \ + FUNC(y[0], u[0], v[0], dst); \ + } \ +} \ + +// All variants implemented. +ROW_FUNC(YuvToRgbRow, VP8YuvToRgb, 3) +ROW_FUNC(YuvToBgrRow, VP8YuvToBgr, 3) +ROW_FUNC(YuvToRgbaRow, VP8YuvToRgba, 4) +ROW_FUNC(YuvToBgraRow, VP8YuvToBgra, 4) +ROW_FUNC(YuvToArgbRow, VP8YuvToArgb, 4) +ROW_FUNC(YuvToRgba4444Row, VP8YuvToRgba4444, 2) +ROW_FUNC(YuvToRgb565Row, VP8YuvToRgb565, 2) + +#undef ROW_FUNC + +// Main call for processing a plane with a WebPSamplerRowFunc function: +void WebPSamplerProcessPlane(const uint8_t* WEBP_RESTRICT y, int y_stride, + const uint8_t* WEBP_RESTRICT u, + const uint8_t* WEBP_RESTRICT v, int uv_stride, + uint8_t* WEBP_RESTRICT dst, int dst_stride, + int width, int height, WebPSamplerRowFunc func) { + int j; + for (j = 0; j < height; ++j) { + func(y, u, v, dst, width); + y += y_stride; + if (j & 1) { + u += uv_stride; + v += uv_stride; + } + dst += dst_stride; + } +} + +//----------------------------------------------------------------------------- +// Main call + +WebPSamplerRowFunc WebPSamplers[MODE_LAST]; + +extern VP8CPUInfo VP8GetCPUInfo; +extern void WebPInitSamplersSSE2(void); +extern void WebPInitSamplersSSE41(void); +extern void WebPInitSamplersMIPS32(void); +extern void WebPInitSamplersMIPSdspR2(void); + +WEBP_DSP_INIT_FUNC(WebPInitSamplers) { + WebPSamplers[MODE_RGB] = YuvToRgbRow; + WebPSamplers[MODE_RGBA] = YuvToRgbaRow; + WebPSamplers[MODE_BGR] = YuvToBgrRow; + WebPSamplers[MODE_BGRA] = YuvToBgraRow; + WebPSamplers[MODE_ARGB] = YuvToArgbRow; + WebPSamplers[MODE_RGBA_4444] = YuvToRgba4444Row; + WebPSamplers[MODE_RGB_565] = YuvToRgb565Row; + WebPSamplers[MODE_rgbA] = YuvToRgbaRow; + WebPSamplers[MODE_bgrA] = YuvToBgraRow; + WebPSamplers[MODE_Argb] = YuvToArgbRow; + WebPSamplers[MODE_rgbA_4444] = YuvToRgba4444Row; + + // If defined, use CPUInfo() to overwrite some pointers with faster versions. + if (VP8GetCPUInfo != NULL) { +#if defined(WEBP_HAVE_SSE2) + if (VP8GetCPUInfo(kSSE2)) { + WebPInitSamplersSSE2(); + } +#endif // WEBP_HAVE_SSE2 +#if defined(WEBP_HAVE_SSE41) + if (VP8GetCPUInfo(kSSE4_1)) { + WebPInitSamplersSSE41(); + } +#endif // WEBP_HAVE_SSE41 +#if defined(WEBP_USE_MIPS32) + if (VP8GetCPUInfo(kMIPS32)) { + WebPInitSamplersMIPS32(); + } +#endif // WEBP_USE_MIPS32 +#if defined(WEBP_USE_MIPS_DSP_R2) + if (VP8GetCPUInfo(kMIPSdspR2)) { + WebPInitSamplersMIPSdspR2(); + } +#endif // WEBP_USE_MIPS_DSP_R2 + } +} + +//----------------------------------------------------------------------------- +// ARGB -> YUV converters + +static void ConvertARGBToY_C(const uint32_t* WEBP_RESTRICT argb, + uint8_t* WEBP_RESTRICT y, int width) { + int i; + for (i = 0; i < width; ++i) { + const uint32_t p = argb[i]; + y[i] = VP8RGBToY((p >> 16) & 0xff, (p >> 8) & 0xff, (p >> 0) & 0xff, + YUV_HALF); + } +} + +void WebPConvertARGBToUV_C(const uint32_t* WEBP_RESTRICT argb, + uint8_t* WEBP_RESTRICT u, uint8_t* WEBP_RESTRICT v, + int src_width, int do_store) { + // No rounding. Last pixel is dealt with separately. + const int uv_width = src_width >> 1; + int i; + for (i = 0; i < uv_width; ++i) { + const uint32_t v0 = argb[2 * i + 0]; + const uint32_t v1 = argb[2 * i + 1]; + // VP8RGBToU/V expects four accumulated pixels. Hence we need to + // scale r/g/b value by a factor 2. We just shift v0/v1 one bit less. + const int r = ((v0 >> 15) & 0x1fe) + ((v1 >> 15) & 0x1fe); + const int g = ((v0 >> 7) & 0x1fe) + ((v1 >> 7) & 0x1fe); + const int b = ((v0 << 1) & 0x1fe) + ((v1 << 1) & 0x1fe); + const int tmp_u = VP8RGBToU(r, g, b, YUV_HALF << 2); + const int tmp_v = VP8RGBToV(r, g, b, YUV_HALF << 2); + if (do_store) { + u[i] = tmp_u; + v[i] = tmp_v; + } else { + // Approximated average-of-four. But it's an acceptable diff. + u[i] = (u[i] + tmp_u + 1) >> 1; + v[i] = (v[i] + tmp_v + 1) >> 1; + } + } + if (src_width & 1) { // last pixel + const uint32_t v0 = argb[2 * i + 0]; + const int r = (v0 >> 14) & 0x3fc; + const int g = (v0 >> 6) & 0x3fc; + const int b = (v0 << 2) & 0x3fc; + const int tmp_u = VP8RGBToU(r, g, b, YUV_HALF << 2); + const int tmp_v = VP8RGBToV(r, g, b, YUV_HALF << 2); + if (do_store) { + u[i] = tmp_u; + v[i] = tmp_v; + } else { + u[i] = (u[i] + tmp_u + 1) >> 1; + v[i] = (v[i] + tmp_v + 1) >> 1; + } + } +} + +//----------------------------------------------------------------------------- + +static void ConvertRGB24ToY_C(const uint8_t* WEBP_RESTRICT rgb, + uint8_t* WEBP_RESTRICT y, int width) { + int i; + for (i = 0; i < width; ++i, rgb += 3) { + y[i] = VP8RGBToY(rgb[0], rgb[1], rgb[2], YUV_HALF); + } +} + +static void ConvertBGR24ToY_C(const uint8_t* WEBP_RESTRICT bgr, + uint8_t* WEBP_RESTRICT y, int width) { + int i; + for (i = 0; i < width; ++i, bgr += 3) { + y[i] = VP8RGBToY(bgr[2], bgr[1], bgr[0], YUV_HALF); + } +} + +void WebPConvertRGBA32ToUV_C(const uint16_t* WEBP_RESTRICT rgb, + uint8_t* WEBP_RESTRICT u, uint8_t* WEBP_RESTRICT v, + int width) { + int i; + for (i = 0; i < width; i += 1, rgb += 4) { + const int r = rgb[0], g = rgb[1], b = rgb[2]; + u[i] = VP8RGBToU(r, g, b, YUV_HALF << 2); + v[i] = VP8RGBToV(r, g, b, YUV_HALF << 2); + } +} + +//----------------------------------------------------------------------------- + +void (*WebPConvertRGB24ToY)(const uint8_t* WEBP_RESTRICT rgb, + uint8_t* WEBP_RESTRICT y, int width); +void (*WebPConvertBGR24ToY)(const uint8_t* WEBP_RESTRICT bgr, + uint8_t* WEBP_RESTRICT y, int width); +void (*WebPConvertRGBA32ToUV)(const uint16_t* WEBP_RESTRICT rgb, + uint8_t* WEBP_RESTRICT u, + uint8_t* WEBP_RESTRICT v, int width); + +void (*WebPConvertARGBToY)(const uint32_t* WEBP_RESTRICT argb, + uint8_t* WEBP_RESTRICT y, int width); +void (*WebPConvertARGBToUV)(const uint32_t* WEBP_RESTRICT argb, + uint8_t* WEBP_RESTRICT u, uint8_t* WEBP_RESTRICT v, + int src_width, int do_store); + +extern void WebPInitConvertARGBToYUVSSE2(void); +extern void WebPInitConvertARGBToYUVSSE41(void); +extern void WebPInitConvertARGBToYUVNEON(void); + +WEBP_DSP_INIT_FUNC(WebPInitConvertARGBToYUV) { + WebPConvertARGBToY = ConvertARGBToY_C; + WebPConvertARGBToUV = WebPConvertARGBToUV_C; + + WebPConvertRGB24ToY = ConvertRGB24ToY_C; + WebPConvertBGR24ToY = ConvertBGR24ToY_C; + + WebPConvertRGBA32ToUV = WebPConvertRGBA32ToUV_C; + + if (VP8GetCPUInfo != NULL) { +#if defined(WEBP_HAVE_SSE2) + if (VP8GetCPUInfo(kSSE2)) { + WebPInitConvertARGBToYUVSSE2(); + } +#endif // WEBP_HAVE_SSE2 +#if defined(WEBP_HAVE_SSE41) + if (VP8GetCPUInfo(kSSE4_1)) { + WebPInitConvertARGBToYUVSSE41(); + } +#endif // WEBP_HAVE_SSE41 + } + +#if defined(WEBP_HAVE_NEON) + if (WEBP_NEON_OMIT_C_CODE || + (VP8GetCPUInfo != NULL && VP8GetCPUInfo(kNEON))) { + WebPInitConvertARGBToYUVNEON(); + } +#endif // WEBP_HAVE_NEON + + assert(WebPConvertARGBToY != NULL); + assert(WebPConvertARGBToUV != NULL); + assert(WebPConvertRGB24ToY != NULL); + assert(WebPConvertBGR24ToY != NULL); + assert(WebPConvertRGBA32ToUV != NULL); +} diff --git a/packages/core/src/zig/vendor/libwebp/src/dsp/yuv.h b/packages/core/src/zig/vendor/libwebp/src/dsp/yuv.h new file mode 100644 index 0000000000..6f218cf7e0 --- /dev/null +++ b/packages/core/src/zig/vendor/libwebp/src/dsp/yuv.h @@ -0,0 +1,230 @@ +// Copyright 2010 Google Inc. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the COPYING file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +// ----------------------------------------------------------------------------- +// +// inline YUV<->RGB conversion function +// +// The exact naming is Y'CbCr, following the ITU-R BT.601 standard. +// More information at: https://en.wikipedia.org/wiki/YCbCr +// Y = 0.2568 * R + 0.5041 * G + 0.0979 * B + 16 +// U = -0.1482 * R - 0.2910 * G + 0.4392 * B + 128 +// V = 0.4392 * R - 0.3678 * G - 0.0714 * B + 128 +// We use 16bit fixed point operations for RGB->YUV conversion (YUV_FIX). +// +// For the Y'CbCr to RGB conversion, the BT.601 specification reads: +// R = 1.164 * (Y-16) + 1.596 * (V-128) +// G = 1.164 * (Y-16) - 0.813 * (V-128) - 0.392 * (U-128) +// B = 1.164 * (Y-16) + 2.017 * (U-128) +// where Y is in the [16,235] range, and U/V in the [16,240] range. +// +// The fixed-point implementation used here is: +// R = (19077 . y + 26149 . v - 14234) >> 6 +// G = (19077 . y - 6419 . u - 13320 . v + 8708) >> 6 +// B = (19077 . y + 33050 . u - 17685) >> 6 +// where the '.' operator is the mulhi_epu16 variant: +// a . b = ((a << 8) * b) >> 16 +// that preserves 8 bits of fractional precision before final descaling. + +// Author: Skal (pascal.massimino@gmail.com) + +#ifndef WEBP_DSP_YUV_H_ +#define WEBP_DSP_YUV_H_ + +#include "src/dec/vp8_dec.h" +#include "src/dsp/cpu.h" +#include "src/dsp/dsp.h" +#include "src/webp/types.h" + +//------------------------------------------------------------------------------ +// YUV -> RGB conversion + +#ifdef __cplusplus +extern "C" { +#endif + +enum { + YUV_FIX = 16, // fixed-point precision for RGB->YUV + YUV_HALF = 1 << (YUV_FIX - 1), + + YUV_FIX2 = 6, // fixed-point precision for YUV->RGB + YUV_MASK2 = (256 << YUV_FIX2) - 1 +}; + +//------------------------------------------------------------------------------ +// slower on x86 by ~7-8%, but bit-exact with the SSE2/NEON version + +static WEBP_INLINE int MultHi(int v, int coeff) { // _mm_mulhi_epu16 emulation + return (v * coeff) >> 8; +} + +static WEBP_INLINE int VP8Clip8(int v) { + return ((v & ~YUV_MASK2) == 0) ? (v >> YUV_FIX2) : (v < 0) ? 0 : 255; +} + +static WEBP_INLINE int VP8YUVToR(int y, int v) { + return VP8Clip8(MultHi(y, 19077) + MultHi(v, 26149) - 14234); +} + +static WEBP_INLINE int VP8YUVToG(int y, int u, int v) { + return VP8Clip8(MultHi(y, 19077) - MultHi(u, 6419) - MultHi(v, 13320) + 8708); +} + +static WEBP_INLINE int VP8YUVToB(int y, int u) { + return VP8Clip8(MultHi(y, 19077) + MultHi(u, 33050) - 17685); +} + +static WEBP_INLINE void VP8YuvToRgb(int y, int u, int v, + uint8_t* const rgb) { + rgb[0] = VP8YUVToR(y, v); + rgb[1] = VP8YUVToG(y, u, v); + rgb[2] = VP8YUVToB(y, u); +} + +static WEBP_INLINE void VP8YuvToBgr(int y, int u, int v, + uint8_t* const bgr) { + bgr[0] = VP8YUVToB(y, u); + bgr[1] = VP8YUVToG(y, u, v); + bgr[2] = VP8YUVToR(y, v); +} + +static WEBP_INLINE void VP8YuvToRgb565(int y, int u, int v, + uint8_t* const rgb) { + const int r = VP8YUVToR(y, v); // 5 usable bits + const int g = VP8YUVToG(y, u, v); // 6 usable bits + const int b = VP8YUVToB(y, u); // 5 usable bits + const int rg = (r & 0xf8) | (g >> 5); + const int gb = ((g << 3) & 0xe0) | (b >> 3); +#if (WEBP_SWAP_16BIT_CSP == 1) + rgb[0] = gb; + rgb[1] = rg; +#else + rgb[0] = rg; + rgb[1] = gb; +#endif +} + +static WEBP_INLINE void VP8YuvToRgba4444(int y, int u, int v, + uint8_t* const argb) { + const int r = VP8YUVToR(y, v); // 4 usable bits + const int g = VP8YUVToG(y, u, v); // 4 usable bits + const int b = VP8YUVToB(y, u); // 4 usable bits + const int rg = (r & 0xf0) | (g >> 4); + const int ba = (b & 0xf0) | 0x0f; // overwrite the lower 4 bits +#if (WEBP_SWAP_16BIT_CSP == 1) + argb[0] = ba; + argb[1] = rg; +#else + argb[0] = rg; + argb[1] = ba; +#endif +} + +//----------------------------------------------------------------------------- +// Alpha handling variants + +static WEBP_INLINE void VP8YuvToArgb(uint8_t y, uint8_t u, uint8_t v, + uint8_t* const argb) { + argb[0] = 0xff; + VP8YuvToRgb(y, u, v, argb + 1); +} + +static WEBP_INLINE void VP8YuvToBgra(uint8_t y, uint8_t u, uint8_t v, + uint8_t* const bgra) { + VP8YuvToBgr(y, u, v, bgra); + bgra[3] = 0xff; +} + +static WEBP_INLINE void VP8YuvToRgba(uint8_t y, uint8_t u, uint8_t v, + uint8_t* const rgba) { + VP8YuvToRgb(y, u, v, rgba); + rgba[3] = 0xff; +} + +//----------------------------------------------------------------------------- +// SSE2 extra functions (mostly for upsampling_sse2.c) + +#if defined(WEBP_USE_SSE2) + +// Process 32 pixels and store the result (16b, 24b or 32b per pixel) in *dst. +void VP8YuvToRgba32_SSE2(const uint8_t* WEBP_RESTRICT y, + const uint8_t* WEBP_RESTRICT u, + const uint8_t* WEBP_RESTRICT v, + uint8_t* WEBP_RESTRICT dst); +void VP8YuvToRgb32_SSE2(const uint8_t* WEBP_RESTRICT y, + const uint8_t* WEBP_RESTRICT u, + const uint8_t* WEBP_RESTRICT v, + uint8_t* WEBP_RESTRICT dst); +void VP8YuvToBgra32_SSE2(const uint8_t* WEBP_RESTRICT y, + const uint8_t* WEBP_RESTRICT u, + const uint8_t* WEBP_RESTRICT v, + uint8_t* WEBP_RESTRICT dst); +void VP8YuvToBgr32_SSE2(const uint8_t* WEBP_RESTRICT y, + const uint8_t* WEBP_RESTRICT u, + const uint8_t* WEBP_RESTRICT v, + uint8_t* WEBP_RESTRICT dst); +void VP8YuvToArgb32_SSE2(const uint8_t* WEBP_RESTRICT y, + const uint8_t* WEBP_RESTRICT u, + const uint8_t* WEBP_RESTRICT v, + uint8_t* WEBP_RESTRICT dst); +void VP8YuvToRgba444432_SSE2(const uint8_t* WEBP_RESTRICT y, + const uint8_t* WEBP_RESTRICT u, + const uint8_t* WEBP_RESTRICT v, + uint8_t* WEBP_RESTRICT dst); +void VP8YuvToRgb56532_SSE2(const uint8_t* WEBP_RESTRICT y, + const uint8_t* WEBP_RESTRICT u, + const uint8_t* WEBP_RESTRICT v, + uint8_t* WEBP_RESTRICT dst); + +#endif // WEBP_USE_SSE2 + +//----------------------------------------------------------------------------- +// SSE41 extra functions (mostly for upsampling_sse41.c) + +#if defined(WEBP_USE_SSE41) + +// Process 32 pixels and store the result (16b, 24b or 32b per pixel) in *dst. +void VP8YuvToRgb32_SSE41(const uint8_t* WEBP_RESTRICT y, + const uint8_t* WEBP_RESTRICT u, + const uint8_t* WEBP_RESTRICT v, + uint8_t* WEBP_RESTRICT dst); +void VP8YuvToBgr32_SSE41(const uint8_t* WEBP_RESTRICT y, + const uint8_t* WEBP_RESTRICT u, + const uint8_t* WEBP_RESTRICT v, + uint8_t* WEBP_RESTRICT dst); + +#endif // WEBP_USE_SSE41 + +//------------------------------------------------------------------------------ +// RGB -> YUV conversion + +// Stub functions that can be called with various rounding values: +static WEBP_INLINE int VP8ClipUV(int uv, int rounding) { + uv = (uv + rounding + (128 << (YUV_FIX + 2))) >> (YUV_FIX + 2); + return ((uv & ~0xff) == 0) ? uv : (uv < 0) ? 0 : 255; +} + +static WEBP_INLINE int VP8RGBToY(int r, int g, int b, int rounding) { + const int luma = 16839 * r + 33059 * g + 6420 * b; + return (luma + rounding + (16 << YUV_FIX)) >> YUV_FIX; // no need to clip +} + +static WEBP_INLINE int VP8RGBToU(int r, int g, int b, int rounding) { + const int u = -9719 * r - 19081 * g + 28800 * b; + return VP8ClipUV(u, rounding); +} + +static WEBP_INLINE int VP8RGBToV(int r, int g, int b, int rounding) { + const int v = +28800 * r - 24116 * g - 4684 * b; + return VP8ClipUV(v, rounding); +} + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif // WEBP_DSP_YUV_H_ diff --git a/packages/core/src/zig/vendor/libwebp/src/dsp/yuv_neon.c b/packages/core/src/zig/vendor/libwebp/src/dsp/yuv_neon.c new file mode 100644 index 0000000000..44745cf786 --- /dev/null +++ b/packages/core/src/zig/vendor/libwebp/src/dsp/yuv_neon.c @@ -0,0 +1,187 @@ +// Copyright 2017 Google Inc. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the COPYING file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +// ----------------------------------------------------------------------------- +// +// YUV->RGB conversion functions +// +// Author: Skal (pascal.massimino@gmail.com) + +#include "src/dsp/yuv.h" + +#if defined(WEBP_USE_NEON) + +#include +#include + +#include "src/dsp/dsp.h" +#include "src/dsp/neon.h" + +//----------------------------------------------------------------------------- + +static uint8x8_t ConvertRGBToY_NEON(const uint8x8_t R, + const uint8x8_t G, + const uint8x8_t B) { + const uint16x8_t r = vmovl_u8(R); + const uint16x8_t g = vmovl_u8(G); + const uint16x8_t b = vmovl_u8(B); + const uint16x4_t r_lo = vget_low_u16(r); + const uint16x4_t r_hi = vget_high_u16(r); + const uint16x4_t g_lo = vget_low_u16(g); + const uint16x4_t g_hi = vget_high_u16(g); + const uint16x4_t b_lo = vget_low_u16(b); + const uint16x4_t b_hi = vget_high_u16(b); + const uint32x4_t tmp0_lo = vmull_n_u16( r_lo, 16839u); + const uint32x4_t tmp0_hi = vmull_n_u16( r_hi, 16839u); + const uint32x4_t tmp1_lo = vmlal_n_u16(tmp0_lo, g_lo, 33059u); + const uint32x4_t tmp1_hi = vmlal_n_u16(tmp0_hi, g_hi, 33059u); + const uint32x4_t tmp2_lo = vmlal_n_u16(tmp1_lo, b_lo, 6420u); + const uint32x4_t tmp2_hi = vmlal_n_u16(tmp1_hi, b_hi, 6420u); + const uint16x8_t Y1 = vcombine_u16(vrshrn_n_u32(tmp2_lo, 16), + vrshrn_n_u32(tmp2_hi, 16)); + const uint16x8_t Y2 = vaddq_u16(Y1, vdupq_n_u16(16)); + return vqmovn_u16(Y2); +} + +static void ConvertRGB24ToY_NEON(const uint8_t* WEBP_RESTRICT rgb, + uint8_t* WEBP_RESTRICT y, int width) { + int i; + for (i = 0; i + 8 <= width; i += 8, rgb += 3 * 8) { + const uint8x8x3_t RGB = vld3_u8(rgb); + const uint8x8_t Y = ConvertRGBToY_NEON(RGB.val[0], RGB.val[1], RGB.val[2]); + vst1_u8(y + i, Y); + } + for (; i < width; ++i, rgb += 3) { // left-over + y[i] = VP8RGBToY(rgb[0], rgb[1], rgb[2], YUV_HALF); + } +} + +static void ConvertBGR24ToY_NEON(const uint8_t* WEBP_RESTRICT bgr, + uint8_t* WEBP_RESTRICT y, int width) { + int i; + for (i = 0; i + 8 <= width; i += 8, bgr += 3 * 8) { + const uint8x8x3_t BGR = vld3_u8(bgr); + const uint8x8_t Y = ConvertRGBToY_NEON(BGR.val[2], BGR.val[1], BGR.val[0]); + vst1_u8(y + i, Y); + } + for (; i < width; ++i, bgr += 3) { // left-over + y[i] = VP8RGBToY(bgr[2], bgr[1], bgr[0], YUV_HALF); + } +} + +static void ConvertARGBToY_NEON(const uint32_t* WEBP_RESTRICT argb, + uint8_t* WEBP_RESTRICT y, int width) { + int i; + for (i = 0; i + 8 <= width; i += 8) { + const uint8x8x4_t RGB = vld4_u8((const uint8_t*)&argb[i]); + const uint8x8_t Y = ConvertRGBToY_NEON(RGB.val[2], RGB.val[1], RGB.val[0]); + vst1_u8(y + i, Y); + } + for (; i < width; ++i) { // left-over + const uint32_t p = argb[i]; + y[i] = VP8RGBToY((p >> 16) & 0xff, (p >> 8) & 0xff, (p >> 0) & 0xff, + YUV_HALF); + } +} + +//----------------------------------------------------------------------------- + +// computes: DST_s16 = [(C0 * r + C1 * g + C2 * b) >> 16] + CST +#define MULTIPLY_16b_PREAMBLE(r, g, b) \ + const int16x4_t r_lo = vreinterpret_s16_u16(vget_low_u16(r)); \ + const int16x4_t r_hi = vreinterpret_s16_u16(vget_high_u16(r)); \ + const int16x4_t g_lo = vreinterpret_s16_u16(vget_low_u16(g)); \ + const int16x4_t g_hi = vreinterpret_s16_u16(vget_high_u16(g)); \ + const int16x4_t b_lo = vreinterpret_s16_u16(vget_low_u16(b)); \ + const int16x4_t b_hi = vreinterpret_s16_u16(vget_high_u16(b)) + +#define MULTIPLY_16b(C0, C1, C2, CST, DST_s16) do { \ + const int32x4_t tmp0_lo = vmull_n_s16( r_lo, C0); \ + const int32x4_t tmp0_hi = vmull_n_s16( r_hi, C0); \ + const int32x4_t tmp1_lo = vmlal_n_s16(tmp0_lo, g_lo, C1); \ + const int32x4_t tmp1_hi = vmlal_n_s16(tmp0_hi, g_hi, C1); \ + const int32x4_t tmp2_lo = vmlal_n_s16(tmp1_lo, b_lo, C2); \ + const int32x4_t tmp2_hi = vmlal_n_s16(tmp1_hi, b_hi, C2); \ + const int16x8_t tmp3 = vcombine_s16(vshrn_n_s32(tmp2_lo, 16), \ + vshrn_n_s32(tmp2_hi, 16)); \ + DST_s16 = vaddq_s16(tmp3, vdupq_n_s16(CST)); \ +} while (0) + +// This needs to be a macro, since (128 << SHIFT) needs to be an immediate. +#define CONVERT_RGB_TO_UV(r, g, b, SHIFT, U_DST, V_DST) do { \ + MULTIPLY_16b_PREAMBLE(r, g, b); \ + MULTIPLY_16b(-9719, -19081, 28800, 128 << SHIFT, U_DST); \ + MULTIPLY_16b(28800, -24116, -4684, 128 << SHIFT, V_DST); \ +} while (0) + +static void ConvertRGBA32ToUV_NEON(const uint16_t* WEBP_RESTRICT rgb, + uint8_t* WEBP_RESTRICT u, + uint8_t* WEBP_RESTRICT v, int width) { + int i; + for (i = 0; i + 8 <= width; i += 8, rgb += 4 * 8) { + const uint16x8x4_t RGB = vld4q_u16((const uint16_t*)rgb); + int16x8_t U, V; + CONVERT_RGB_TO_UV(RGB.val[0], RGB.val[1], RGB.val[2], 2, U, V); + vst1_u8(u + i, vqrshrun_n_s16(U, 2)); + vst1_u8(v + i, vqrshrun_n_s16(V, 2)); + } + for (; i < width; i += 1, rgb += 4) { + const int r = rgb[0], g = rgb[1], b = rgb[2]; + u[i] = VP8RGBToU(r, g, b, YUV_HALF << 2); + v[i] = VP8RGBToV(r, g, b, YUV_HALF << 2); + } +} + +static void ConvertARGBToUV_NEON(const uint32_t* WEBP_RESTRICT argb, + uint8_t* WEBP_RESTRICT u, + uint8_t* WEBP_RESTRICT v, + int src_width, int do_store) { + int i; + for (i = 0; i + 16 <= src_width; i += 16, u += 8, v += 8) { + const uint8x16x4_t RGB = vld4q_u8((const uint8_t*)&argb[i]); + const uint16x8_t R = vpaddlq_u8(RGB.val[2]); // pair-wise adds + const uint16x8_t G = vpaddlq_u8(RGB.val[1]); + const uint16x8_t B = vpaddlq_u8(RGB.val[0]); + int16x8_t U_tmp, V_tmp; + CONVERT_RGB_TO_UV(R, G, B, 1, U_tmp, V_tmp); + { + const uint8x8_t U = vqrshrun_n_s16(U_tmp, 1); + const uint8x8_t V = vqrshrun_n_s16(V_tmp, 1); + if (do_store) { + vst1_u8(u, U); + vst1_u8(v, V); + } else { + const uint8x8_t prev_u = vld1_u8(u); + const uint8x8_t prev_v = vld1_u8(v); + vst1_u8(u, vrhadd_u8(U, prev_u)); + vst1_u8(v, vrhadd_u8(V, prev_v)); + } + } + } + if (i < src_width) { // left-over + WebPConvertARGBToUV_C(argb + i, u, v, src_width - i, do_store); + } +} + + +//------------------------------------------------------------------------------ + +extern void WebPInitConvertARGBToYUVNEON(void); + +WEBP_TSAN_IGNORE_FUNCTION void WebPInitConvertARGBToYUVNEON(void) { + WebPConvertRGB24ToY = ConvertRGB24ToY_NEON; + WebPConvertBGR24ToY = ConvertBGR24ToY_NEON; + WebPConvertARGBToY = ConvertARGBToY_NEON; + WebPConvertARGBToUV = ConvertARGBToUV_NEON; + WebPConvertRGBA32ToUV = ConvertRGBA32ToUV_NEON; +} + +#else // !WEBP_USE_NEON + +WEBP_DSP_INIT_STUB(WebPInitConvertARGBToYUVNEON) + +#endif // WEBP_USE_NEON diff --git a/packages/core/src/zig/vendor/libwebp/src/dsp/yuv_sse2.c b/packages/core/src/zig/vendor/libwebp/src/dsp/yuv_sse2.c new file mode 100644 index 0000000000..f1abf217af --- /dev/null +++ b/packages/core/src/zig/vendor/libwebp/src/dsp/yuv_sse2.c @@ -0,0 +1,784 @@ +// Copyright 2014 Google Inc. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the COPYING file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +// ----------------------------------------------------------------------------- +// +// YUV->RGB conversion functions +// +// Author: Skal (pascal.massimino@gmail.com) + +#include "src/dsp/yuv.h" + +#if defined(WEBP_USE_SSE2) +#include + +#include + +#include "src/dsp/common_sse2.h" +#include "src/dsp/cpu.h" +#include "src/dsp/dsp.h" +#include "src/utils/utils.h" +#include "src/webp/decode.h" +#include "src/webp/types.h" + +//----------------------------------------------------------------------------- +// Convert spans of 32 pixels to various RGB formats for the fancy upsampler. + +// These constants are 14b fixed-point version of ITU-R BT.601 constants. +// R = (19077 * y + 26149 * v - 14234) >> 6 +// G = (19077 * y - 6419 * u - 13320 * v + 8708) >> 6 +// B = (19077 * y + 33050 * u - 17685) >> 6 +static void ConvertYUV444ToRGB_SSE2(const __m128i* const Y0, + const __m128i* const U0, + const __m128i* const V0, + __m128i* const R, + __m128i* const G, + __m128i* const B) { + const __m128i k19077 = _mm_set1_epi16(19077); + const __m128i k26149 = _mm_set1_epi16(26149); + const __m128i k14234 = _mm_set1_epi16(14234); + // 33050 doesn't fit in a signed short: only use this with unsigned arithmetic + const __m128i k33050 = _mm_set1_epi16((short)33050); + const __m128i k17685 = _mm_set1_epi16(17685); + const __m128i k6419 = _mm_set1_epi16(6419); + const __m128i k13320 = _mm_set1_epi16(13320); + const __m128i k8708 = _mm_set1_epi16(8708); + + const __m128i Y1 = _mm_mulhi_epu16(*Y0, k19077); + + const __m128i R0 = _mm_mulhi_epu16(*V0, k26149); + const __m128i R1 = _mm_sub_epi16(Y1, k14234); + const __m128i R2 = _mm_add_epi16(R1, R0); + + const __m128i G0 = _mm_mulhi_epu16(*U0, k6419); + const __m128i G1 = _mm_mulhi_epu16(*V0, k13320); + const __m128i G2 = _mm_add_epi16(Y1, k8708); + const __m128i G3 = _mm_add_epi16(G0, G1); + const __m128i G4 = _mm_sub_epi16(G2, G3); + + // be careful with the saturated *unsigned* arithmetic here! + const __m128i B0 = _mm_mulhi_epu16(*U0, k33050); + const __m128i B1 = _mm_adds_epu16(B0, Y1); + const __m128i B2 = _mm_subs_epu16(B1, k17685); + + // use logical shift for B2, which can be larger than 32767 + *R = _mm_srai_epi16(R2, 6); // range: [-14234, 30815] + *G = _mm_srai_epi16(G4, 6); // range: [-10953, 27710] + *B = _mm_srli_epi16(B2, 6); // range: [0, 34238] +} + +// Load the bytes into the *upper* part of 16b words. That's "<< 8", basically. +static WEBP_INLINE __m128i Load_HI_16_SSE2(const uint8_t* src) { + const __m128i zero = _mm_setzero_si128(); + return _mm_unpacklo_epi8(zero, _mm_loadl_epi64((const __m128i*)src)); +} + +// Load and replicate the U/V samples +static WEBP_INLINE __m128i Load_UV_HI_8_SSE2(const uint8_t* src) { + const __m128i zero = _mm_setzero_si128(); + const __m128i tmp0 = _mm_cvtsi32_si128(WebPMemToInt32(src)); + const __m128i tmp1 = _mm_unpacklo_epi8(zero, tmp0); + return _mm_unpacklo_epi16(tmp1, tmp1); // replicate samples +} + +// Convert 32 samples of YUV444 to R/G/B +static void YUV444ToRGB_SSE2(const uint8_t* WEBP_RESTRICT const y, + const uint8_t* WEBP_RESTRICT const u, + const uint8_t* WEBP_RESTRICT const v, + __m128i* const R, __m128i* const G, + __m128i* const B) { + const __m128i Y0 = Load_HI_16_SSE2(y), U0 = Load_HI_16_SSE2(u), + V0 = Load_HI_16_SSE2(v); + ConvertYUV444ToRGB_SSE2(&Y0, &U0, &V0, R, G, B); +} + +// Convert 32 samples of YUV420 to R/G/B +static void YUV420ToRGB_SSE2(const uint8_t* WEBP_RESTRICT const y, + const uint8_t* WEBP_RESTRICT const u, + const uint8_t* WEBP_RESTRICT const v, + __m128i* const R, __m128i* const G, + __m128i* const B) { + const __m128i Y0 = Load_HI_16_SSE2(y), U0 = Load_UV_HI_8_SSE2(u), + V0 = Load_UV_HI_8_SSE2(v); + ConvertYUV444ToRGB_SSE2(&Y0, &U0, &V0, R, G, B); +} + +// Pack R/G/B/A results into 32b output. +static WEBP_INLINE void PackAndStore4_SSE2(const __m128i* const R, + const __m128i* const G, + const __m128i* const B, + const __m128i* const A, + uint8_t* WEBP_RESTRICT const dst) { + const __m128i rb = _mm_packus_epi16(*R, *B); + const __m128i ga = _mm_packus_epi16(*G, *A); + const __m128i rg = _mm_unpacklo_epi8(rb, ga); + const __m128i ba = _mm_unpackhi_epi8(rb, ga); + const __m128i RGBA_lo = _mm_unpacklo_epi16(rg, ba); + const __m128i RGBA_hi = _mm_unpackhi_epi16(rg, ba); + _mm_storeu_si128((__m128i*)(dst + 0), RGBA_lo); + _mm_storeu_si128((__m128i*)(dst + 16), RGBA_hi); +} + +// Pack R/G/B/A results into 16b output. +static WEBP_INLINE void PackAndStore4444_SSE2( + const __m128i* const R, const __m128i* const G, const __m128i* const B, + const __m128i* const A, uint8_t* WEBP_RESTRICT const dst) { +#if (WEBP_SWAP_16BIT_CSP == 0) + const __m128i rg0 = _mm_packus_epi16(*R, *G); + const __m128i ba0 = _mm_packus_epi16(*B, *A); +#else + const __m128i rg0 = _mm_packus_epi16(*B, *A); + const __m128i ba0 = _mm_packus_epi16(*R, *G); +#endif + const __m128i mask_0xf0 = _mm_set1_epi8((char)0xf0); + const __m128i rb1 = _mm_unpacklo_epi8(rg0, ba0); // rbrbrbrbrb... + const __m128i ga1 = _mm_unpackhi_epi8(rg0, ba0); // gagagagaga... + const __m128i rb2 = _mm_and_si128(rb1, mask_0xf0); + const __m128i ga2 = _mm_srli_epi16(_mm_and_si128(ga1, mask_0xf0), 4); + const __m128i rgba4444 = _mm_or_si128(rb2, ga2); + _mm_storeu_si128((__m128i*)dst, rgba4444); +} + +// Pack R/G/B results into 16b output. +static WEBP_INLINE void PackAndStore565_SSE2(const __m128i* const R, + const __m128i* const G, + const __m128i* const B, + uint8_t* WEBP_RESTRICT const dst) { + const __m128i r0 = _mm_packus_epi16(*R, *R); + const __m128i g0 = _mm_packus_epi16(*G, *G); + const __m128i b0 = _mm_packus_epi16(*B, *B); + const __m128i r1 = _mm_and_si128(r0, _mm_set1_epi8((char)0xf8)); + const __m128i b1 = _mm_and_si128(_mm_srli_epi16(b0, 3), _mm_set1_epi8(0x1f)); + const __m128i g1 = + _mm_srli_epi16(_mm_and_si128(g0, _mm_set1_epi8((char)0xe0)), 5); + const __m128i g2 = _mm_slli_epi16(_mm_and_si128(g0, _mm_set1_epi8(0x1c)), 3); + const __m128i rg = _mm_or_si128(r1, g1); + const __m128i gb = _mm_or_si128(g2, b1); +#if (WEBP_SWAP_16BIT_CSP == 0) + const __m128i rgb565 = _mm_unpacklo_epi8(rg, gb); +#else + const __m128i rgb565 = _mm_unpacklo_epi8(gb, rg); +#endif + _mm_storeu_si128((__m128i*)dst, rgb565); +} + +// Pack the planar buffers +// rrrr... rrrr... gggg... gggg... bbbb... bbbb.... +// triplet by triplet in the output buffer rgb as rgbrgbrgbrgb ... +static WEBP_INLINE void PlanarTo24b_SSE2(__m128i* const in0, __m128i* const in1, + __m128i* const in2, __m128i* const in3, + __m128i* const in4, __m128i* const in5, + uint8_t* WEBP_RESTRICT const rgb) { + // The input is 6 registers of sixteen 8b but for the sake of explanation, + // let's take 6 registers of four 8b values. + // To pack, we will keep taking one every two 8b integer and move it + // around as follows: + // Input: + // r0r1r2r3 | r4r5r6r7 | g0g1g2g3 | g4g5g6g7 | b0b1b2b3 | b4b5b6b7 + // Split the 6 registers in two sets of 3 registers: the first set as the even + // 8b bytes, the second the odd ones: + // r0r2r4r6 | g0g2g4g6 | b0b2b4b6 | r1r3r5r7 | g1g3g5g7 | b1b3b5b7 + // Repeat the same permutations twice more: + // r0r4g0g4 | b0b4r1r5 | g1g5b1b5 | r2r6g2g6 | b2b6r3r7 | g3g7b3b7 + // r0g0b0r1 | g1b1r2g2 | b2r3g3b3 | r4g4b4r5 | g5b5r6g6 | b6r7g7b7 + VP8PlanarTo24b_SSE2(in0, in1, in2, in3, in4, in5); + + _mm_storeu_si128((__m128i*)(rgb + 0), *in0); + _mm_storeu_si128((__m128i*)(rgb + 16), *in1); + _mm_storeu_si128((__m128i*)(rgb + 32), *in2); + _mm_storeu_si128((__m128i*)(rgb + 48), *in3); + _mm_storeu_si128((__m128i*)(rgb + 64), *in4); + _mm_storeu_si128((__m128i*)(rgb + 80), *in5); +} + +void VP8YuvToRgba32_SSE2(const uint8_t* WEBP_RESTRICT y, + const uint8_t* WEBP_RESTRICT u, + const uint8_t* WEBP_RESTRICT v, + uint8_t* WEBP_RESTRICT dst) { + const __m128i kAlpha = _mm_set1_epi16(255); + int n; + for (n = 0; n < 32; n += 8, dst += 32) { + __m128i R, G, B; + YUV444ToRGB_SSE2(y + n, u + n, v + n, &R, &G, &B); + PackAndStore4_SSE2(&R, &G, &B, &kAlpha, dst); + } +} + +void VP8YuvToBgra32_SSE2(const uint8_t* WEBP_RESTRICT y, + const uint8_t* WEBP_RESTRICT u, + const uint8_t* WEBP_RESTRICT v, + uint8_t* WEBP_RESTRICT dst) { + const __m128i kAlpha = _mm_set1_epi16(255); + int n; + for (n = 0; n < 32; n += 8, dst += 32) { + __m128i R, G, B; + YUV444ToRGB_SSE2(y + n, u + n, v + n, &R, &G, &B); + PackAndStore4_SSE2(&B, &G, &R, &kAlpha, dst); + } +} + +void VP8YuvToArgb32_SSE2(const uint8_t* WEBP_RESTRICT y, + const uint8_t* WEBP_RESTRICT u, + const uint8_t* WEBP_RESTRICT v, + uint8_t* WEBP_RESTRICT dst) { + const __m128i kAlpha = _mm_set1_epi16(255); + int n; + for (n = 0; n < 32; n += 8, dst += 32) { + __m128i R, G, B; + YUV444ToRGB_SSE2(y + n, u + n, v + n, &R, &G, &B); + PackAndStore4_SSE2(&kAlpha, &R, &G, &B, dst); + } +} + +void VP8YuvToRgba444432_SSE2(const uint8_t* WEBP_RESTRICT y, + const uint8_t* WEBP_RESTRICT u, + const uint8_t* WEBP_RESTRICT v, + uint8_t* WEBP_RESTRICT dst) { + const __m128i kAlpha = _mm_set1_epi16(255); + int n; + for (n = 0; n < 32; n += 8, dst += 16) { + __m128i R, G, B; + YUV444ToRGB_SSE2(y + n, u + n, v + n, &R, &G, &B); + PackAndStore4444_SSE2(&R, &G, &B, &kAlpha, dst); + } +} + +void VP8YuvToRgb56532_SSE2(const uint8_t* WEBP_RESTRICT y, + const uint8_t* WEBP_RESTRICT u, + const uint8_t* WEBP_RESTRICT v, + uint8_t* WEBP_RESTRICT dst) { + int n; + for (n = 0; n < 32; n += 8, dst += 16) { + __m128i R, G, B; + YUV444ToRGB_SSE2(y + n, u + n, v + n, &R, &G, &B); + PackAndStore565_SSE2(&R, &G, &B, dst); + } +} + +void VP8YuvToRgb32_SSE2(const uint8_t* WEBP_RESTRICT y, + const uint8_t* WEBP_RESTRICT u, + const uint8_t* WEBP_RESTRICT v, + uint8_t* WEBP_RESTRICT dst) { + __m128i R0, R1, R2, R3, G0, G1, G2, G3, B0, B1, B2, B3; + __m128i rgb0, rgb1, rgb2, rgb3, rgb4, rgb5; + + YUV444ToRGB_SSE2(y + 0, u + 0, v + 0, &R0, &G0, &B0); + YUV444ToRGB_SSE2(y + 8, u + 8, v + 8, &R1, &G1, &B1); + YUV444ToRGB_SSE2(y + 16, u + 16, v + 16, &R2, &G2, &B2); + YUV444ToRGB_SSE2(y + 24, u + 24, v + 24, &R3, &G3, &B3); + + // Cast to 8b and store as RRRRGGGGBBBB. + rgb0 = _mm_packus_epi16(R0, R1); + rgb1 = _mm_packus_epi16(R2, R3); + rgb2 = _mm_packus_epi16(G0, G1); + rgb3 = _mm_packus_epi16(G2, G3); + rgb4 = _mm_packus_epi16(B0, B1); + rgb5 = _mm_packus_epi16(B2, B3); + + // Pack as RGBRGBRGBRGB. + PlanarTo24b_SSE2(&rgb0, &rgb1, &rgb2, &rgb3, &rgb4, &rgb5, dst); +} + +void VP8YuvToBgr32_SSE2(const uint8_t* WEBP_RESTRICT y, + const uint8_t* WEBP_RESTRICT u, + const uint8_t* WEBP_RESTRICT v, + uint8_t* WEBP_RESTRICT dst) { + __m128i R0, R1, R2, R3, G0, G1, G2, G3, B0, B1, B2, B3; + __m128i bgr0, bgr1, bgr2, bgr3, bgr4, bgr5; + + YUV444ToRGB_SSE2(y + 0, u + 0, v + 0, &R0, &G0, &B0); + YUV444ToRGB_SSE2(y + 8, u + 8, v + 8, &R1, &G1, &B1); + YUV444ToRGB_SSE2(y + 16, u + 16, v + 16, &R2, &G2, &B2); + YUV444ToRGB_SSE2(y + 24, u + 24, v + 24, &R3, &G3, &B3); + + // Cast to 8b and store as BBBBGGGGRRRR. + bgr0 = _mm_packus_epi16(B0, B1); + bgr1 = _mm_packus_epi16(B2, B3); + bgr2 = _mm_packus_epi16(G0, G1); + bgr3 = _mm_packus_epi16(G2, G3); + bgr4 = _mm_packus_epi16(R0, R1); + bgr5= _mm_packus_epi16(R2, R3); + + // Pack as BGRBGRBGRBGR. + PlanarTo24b_SSE2(&bgr0, &bgr1, &bgr2, &bgr3, &bgr4, &bgr5, dst); +} + +//----------------------------------------------------------------------------- +// Arbitrary-length row conversion functions + +static void YuvToRgbaRow_SSE2(const uint8_t* WEBP_RESTRICT y, + const uint8_t* WEBP_RESTRICT u, + const uint8_t* WEBP_RESTRICT v, + uint8_t* WEBP_RESTRICT dst, int len) { + const __m128i kAlpha = _mm_set1_epi16(255); + int n; + for (n = 0; n + 8 <= len; n += 8, dst += 32) { + __m128i R, G, B; + YUV420ToRGB_SSE2(y, u, v, &R, &G, &B); + PackAndStore4_SSE2(&R, &G, &B, &kAlpha, dst); + y += 8; + u += 4; + v += 4; + } + for (; n < len; ++n) { // Finish off + VP8YuvToRgba(y[0], u[0], v[0], dst); + dst += 4; + y += 1; + u += (n & 1); + v += (n & 1); + } +} + +static void YuvToBgraRow_SSE2(const uint8_t* WEBP_RESTRICT y, + const uint8_t* WEBP_RESTRICT u, + const uint8_t* WEBP_RESTRICT v, + uint8_t* WEBP_RESTRICT dst, int len) { + const __m128i kAlpha = _mm_set1_epi16(255); + int n; + for (n = 0; n + 8 <= len; n += 8, dst += 32) { + __m128i R, G, B; + YUV420ToRGB_SSE2(y, u, v, &R, &G, &B); + PackAndStore4_SSE2(&B, &G, &R, &kAlpha, dst); + y += 8; + u += 4; + v += 4; + } + for (; n < len; ++n) { // Finish off + VP8YuvToBgra(y[0], u[0], v[0], dst); + dst += 4; + y += 1; + u += (n & 1); + v += (n & 1); + } +} + +static void YuvToArgbRow_SSE2(const uint8_t* WEBP_RESTRICT y, + const uint8_t* WEBP_RESTRICT u, + const uint8_t* WEBP_RESTRICT v, + uint8_t* WEBP_RESTRICT dst, int len) { + const __m128i kAlpha = _mm_set1_epi16(255); + int n; + for (n = 0; n + 8 <= len; n += 8, dst += 32) { + __m128i R, G, B; + YUV420ToRGB_SSE2(y, u, v, &R, &G, &B); + PackAndStore4_SSE2(&kAlpha, &R, &G, &B, dst); + y += 8; + u += 4; + v += 4; + } + for (; n < len; ++n) { // Finish off + VP8YuvToArgb(y[0], u[0], v[0], dst); + dst += 4; + y += 1; + u += (n & 1); + v += (n & 1); + } +} + +static void YuvToRgbRow_SSE2(const uint8_t* WEBP_RESTRICT y, + const uint8_t* WEBP_RESTRICT u, + const uint8_t* WEBP_RESTRICT v, + uint8_t* WEBP_RESTRICT dst, int len) { + int n; + for (n = 0; n + 32 <= len; n += 32, dst += 32 * 3) { + __m128i R0, R1, R2, R3, G0, G1, G2, G3, B0, B1, B2, B3; + __m128i rgb0, rgb1, rgb2, rgb3, rgb4, rgb5; + + YUV420ToRGB_SSE2(y + 0, u + 0, v + 0, &R0, &G0, &B0); + YUV420ToRGB_SSE2(y + 8, u + 4, v + 4, &R1, &G1, &B1); + YUV420ToRGB_SSE2(y + 16, u + 8, v + 8, &R2, &G2, &B2); + YUV420ToRGB_SSE2(y + 24, u + 12, v + 12, &R3, &G3, &B3); + + // Cast to 8b and store as RRRRGGGGBBBB. + rgb0 = _mm_packus_epi16(R0, R1); + rgb1 = _mm_packus_epi16(R2, R3); + rgb2 = _mm_packus_epi16(G0, G1); + rgb3 = _mm_packus_epi16(G2, G3); + rgb4 = _mm_packus_epi16(B0, B1); + rgb5 = _mm_packus_epi16(B2, B3); + + // Pack as RGBRGBRGBRGB. + PlanarTo24b_SSE2(&rgb0, &rgb1, &rgb2, &rgb3, &rgb4, &rgb5, dst); + + y += 32; + u += 16; + v += 16; + } + for (; n < len; ++n) { // Finish off + VP8YuvToRgb(y[0], u[0], v[0], dst); + dst += 3; + y += 1; + u += (n & 1); + v += (n & 1); + } +} + +static void YuvToBgrRow_SSE2(const uint8_t* WEBP_RESTRICT y, + const uint8_t* WEBP_RESTRICT u, + const uint8_t* WEBP_RESTRICT v, + uint8_t* WEBP_RESTRICT dst, int len) { + int n; + for (n = 0; n + 32 <= len; n += 32, dst += 32 * 3) { + __m128i R0, R1, R2, R3, G0, G1, G2, G3, B0, B1, B2, B3; + __m128i bgr0, bgr1, bgr2, bgr3, bgr4, bgr5; + + YUV420ToRGB_SSE2(y + 0, u + 0, v + 0, &R0, &G0, &B0); + YUV420ToRGB_SSE2(y + 8, u + 4, v + 4, &R1, &G1, &B1); + YUV420ToRGB_SSE2(y + 16, u + 8, v + 8, &R2, &G2, &B2); + YUV420ToRGB_SSE2(y + 24, u + 12, v + 12, &R3, &G3, &B3); + + // Cast to 8b and store as BBBBGGGGRRRR. + bgr0 = _mm_packus_epi16(B0, B1); + bgr1 = _mm_packus_epi16(B2, B3); + bgr2 = _mm_packus_epi16(G0, G1); + bgr3 = _mm_packus_epi16(G2, G3); + bgr4 = _mm_packus_epi16(R0, R1); + bgr5 = _mm_packus_epi16(R2, R3); + + // Pack as BGRBGRBGRBGR. + PlanarTo24b_SSE2(&bgr0, &bgr1, &bgr2, &bgr3, &bgr4, &bgr5, dst); + + y += 32; + u += 16; + v += 16; + } + for (; n < len; ++n) { // Finish off + VP8YuvToBgr(y[0], u[0], v[0], dst); + dst += 3; + y += 1; + u += (n & 1); + v += (n & 1); + } +} + +//------------------------------------------------------------------------------ +// Entry point + +extern void WebPInitSamplersSSE2(void); + +WEBP_TSAN_IGNORE_FUNCTION void WebPInitSamplersSSE2(void) { + WebPSamplers[MODE_RGB] = YuvToRgbRow_SSE2; + WebPSamplers[MODE_RGBA] = YuvToRgbaRow_SSE2; + WebPSamplers[MODE_BGR] = YuvToBgrRow_SSE2; + WebPSamplers[MODE_BGRA] = YuvToBgraRow_SSE2; + WebPSamplers[MODE_ARGB] = YuvToArgbRow_SSE2; +} + +//------------------------------------------------------------------------------ +// RGB24/32 -> YUV converters + +// Load eight 16b-words from *src. +#define LOAD_16(src) _mm_loadu_si128((const __m128i*)(src)) +// Store either 16b-words into *dst +#define STORE_16(V, dst) _mm_storeu_si128((__m128i*)(dst), (V)) + +// Function that inserts a value of the second half of the in buffer in between +// every two char of the first half. +static WEBP_INLINE void RGB24PackedToPlanarHelper_SSE2( + const __m128i* const in /*in[6]*/, __m128i* const out /*out[6]*/) { + out[0] = _mm_unpacklo_epi8(in[0], in[3]); + out[1] = _mm_unpackhi_epi8(in[0], in[3]); + out[2] = _mm_unpacklo_epi8(in[1], in[4]); + out[3] = _mm_unpackhi_epi8(in[1], in[4]); + out[4] = _mm_unpacklo_epi8(in[2], in[5]); + out[5] = _mm_unpackhi_epi8(in[2], in[5]); +} + +// Unpack the 8b input rgbrgbrgbrgb ... as contiguous registers: +// rrrr... rrrr... gggg... gggg... bbbb... bbbb.... +// Similar to PlanarTo24bHelper(), but in reverse order. +static WEBP_INLINE void RGB24PackedToPlanar_SSE2( + const uint8_t* WEBP_RESTRICT const rgb, __m128i* const out /*out[6]*/) { + __m128i tmp[6]; + tmp[0] = _mm_loadu_si128((const __m128i*)(rgb + 0)); + tmp[1] = _mm_loadu_si128((const __m128i*)(rgb + 16)); + tmp[2] = _mm_loadu_si128((const __m128i*)(rgb + 32)); + tmp[3] = _mm_loadu_si128((const __m128i*)(rgb + 48)); + tmp[4] = _mm_loadu_si128((const __m128i*)(rgb + 64)); + tmp[5] = _mm_loadu_si128((const __m128i*)(rgb + 80)); + + RGB24PackedToPlanarHelper_SSE2(tmp, out); + RGB24PackedToPlanarHelper_SSE2(out, tmp); + RGB24PackedToPlanarHelper_SSE2(tmp, out); + RGB24PackedToPlanarHelper_SSE2(out, tmp); + RGB24PackedToPlanarHelper_SSE2(tmp, out); +} + +// Convert 8 packed ARGB to r[], g[], b[] +static WEBP_INLINE void RGB32PackedToPlanar_SSE2( + const uint32_t* WEBP_RESTRICT const argb, __m128i* const rgb /*in[6]*/) { + const __m128i zero = _mm_setzero_si128(); + __m128i a0 = LOAD_16(argb + 0); + __m128i a1 = LOAD_16(argb + 4); + __m128i a2 = LOAD_16(argb + 8); + __m128i a3 = LOAD_16(argb + 12); + VP8L32bToPlanar_SSE2(&a0, &a1, &a2, &a3); + rgb[0] = _mm_unpacklo_epi8(a1, zero); + rgb[1] = _mm_unpackhi_epi8(a1, zero); + rgb[2] = _mm_unpacklo_epi8(a2, zero); + rgb[3] = _mm_unpackhi_epi8(a2, zero); + rgb[4] = _mm_unpacklo_epi8(a3, zero); + rgb[5] = _mm_unpackhi_epi8(a3, zero); +} + +// This macro computes (RG * MULT_RG + GB * MULT_GB + ROUNDER) >> DESCALE_FIX +// It's a macro and not a function because we need to use immediate values with +// srai_epi32, e.g. +#define TRANSFORM(RG_LO, RG_HI, GB_LO, GB_HI, MULT_RG, MULT_GB, \ + ROUNDER, DESCALE_FIX, OUT) do { \ + const __m128i V0_lo = _mm_madd_epi16(RG_LO, MULT_RG); \ + const __m128i V0_hi = _mm_madd_epi16(RG_HI, MULT_RG); \ + const __m128i V1_lo = _mm_madd_epi16(GB_LO, MULT_GB); \ + const __m128i V1_hi = _mm_madd_epi16(GB_HI, MULT_GB); \ + const __m128i V2_lo = _mm_add_epi32(V0_lo, V1_lo); \ + const __m128i V2_hi = _mm_add_epi32(V0_hi, V1_hi); \ + const __m128i V3_lo = _mm_add_epi32(V2_lo, ROUNDER); \ + const __m128i V3_hi = _mm_add_epi32(V2_hi, ROUNDER); \ + const __m128i V5_lo = _mm_srai_epi32(V3_lo, DESCALE_FIX); \ + const __m128i V5_hi = _mm_srai_epi32(V3_hi, DESCALE_FIX); \ + (OUT) = _mm_packs_epi32(V5_lo, V5_hi); \ +} while (0) + +#define MK_CST_16(A, B) _mm_set_epi16((B), (A), (B), (A), (B), (A), (B), (A)) +static WEBP_INLINE void ConvertRGBToY_SSE2(const __m128i* const R, + const __m128i* const G, + const __m128i* const B, + __m128i* const Y) { + const __m128i kRG_y = MK_CST_16(16839, 33059 - 16384); + const __m128i kGB_y = MK_CST_16(16384, 6420); + const __m128i kHALF_Y = _mm_set1_epi32((16 << YUV_FIX) + YUV_HALF); + + const __m128i RG_lo = _mm_unpacklo_epi16(*R, *G); + const __m128i RG_hi = _mm_unpackhi_epi16(*R, *G); + const __m128i GB_lo = _mm_unpacklo_epi16(*G, *B); + const __m128i GB_hi = _mm_unpackhi_epi16(*G, *B); + TRANSFORM(RG_lo, RG_hi, GB_lo, GB_hi, kRG_y, kGB_y, kHALF_Y, YUV_FIX, *Y); +} + +static WEBP_INLINE void ConvertRGBToUV_SSE2(const __m128i* const R, + const __m128i* const G, + const __m128i* const B, + __m128i* const U, + __m128i* const V) { + const __m128i kRG_u = MK_CST_16(-9719, -19081); + const __m128i kGB_u = MK_CST_16(0, 28800); + const __m128i kRG_v = MK_CST_16(28800, 0); + const __m128i kGB_v = MK_CST_16(-24116, -4684); + const __m128i kHALF_UV = _mm_set1_epi32(((128 << YUV_FIX) + YUV_HALF) << 2); + + const __m128i RG_lo = _mm_unpacklo_epi16(*R, *G); + const __m128i RG_hi = _mm_unpackhi_epi16(*R, *G); + const __m128i GB_lo = _mm_unpacklo_epi16(*G, *B); + const __m128i GB_hi = _mm_unpackhi_epi16(*G, *B); + TRANSFORM(RG_lo, RG_hi, GB_lo, GB_hi, kRG_u, kGB_u, + kHALF_UV, YUV_FIX + 2, *U); + TRANSFORM(RG_lo, RG_hi, GB_lo, GB_hi, kRG_v, kGB_v, + kHALF_UV, YUV_FIX + 2, *V); +} + +#undef MK_CST_16 +#undef TRANSFORM + +static void ConvertRGB24ToY_SSE2(const uint8_t* WEBP_RESTRICT rgb, + uint8_t* WEBP_RESTRICT y, int width) { + const int max_width = width & ~31; + int i; + for (i = 0; i < max_width; rgb += 3 * 16 * 2) { + __m128i rgb_plane[6]; + int j; + + RGB24PackedToPlanar_SSE2(rgb, rgb_plane); + + for (j = 0; j < 2; ++j, i += 16) { + const __m128i zero = _mm_setzero_si128(); + __m128i r, g, b, Y0, Y1; + + // Convert to 16-bit Y. + r = _mm_unpacklo_epi8(rgb_plane[0 + j], zero); + g = _mm_unpacklo_epi8(rgb_plane[2 + j], zero); + b = _mm_unpacklo_epi8(rgb_plane[4 + j], zero); + ConvertRGBToY_SSE2(&r, &g, &b, &Y0); + + // Convert to 16-bit Y. + r = _mm_unpackhi_epi8(rgb_plane[0 + j], zero); + g = _mm_unpackhi_epi8(rgb_plane[2 + j], zero); + b = _mm_unpackhi_epi8(rgb_plane[4 + j], zero); + ConvertRGBToY_SSE2(&r, &g, &b, &Y1); + + // Cast to 8-bit and store. + STORE_16(_mm_packus_epi16(Y0, Y1), y + i); + } + } + for (; i < width; ++i, rgb += 3) { // left-over + y[i] = VP8RGBToY(rgb[0], rgb[1], rgb[2], YUV_HALF); + } +} + +static void ConvertBGR24ToY_SSE2(const uint8_t* WEBP_RESTRICT bgr, + uint8_t* WEBP_RESTRICT y, int width) { + const int max_width = width & ~31; + int i; + for (i = 0; i < max_width; bgr += 3 * 16 * 2) { + __m128i bgr_plane[6]; + int j; + + RGB24PackedToPlanar_SSE2(bgr, bgr_plane); + + for (j = 0; j < 2; ++j, i += 16) { + const __m128i zero = _mm_setzero_si128(); + __m128i r, g, b, Y0, Y1; + + // Convert to 16-bit Y. + b = _mm_unpacklo_epi8(bgr_plane[0 + j], zero); + g = _mm_unpacklo_epi8(bgr_plane[2 + j], zero); + r = _mm_unpacklo_epi8(bgr_plane[4 + j], zero); + ConvertRGBToY_SSE2(&r, &g, &b, &Y0); + + // Convert to 16-bit Y. + b = _mm_unpackhi_epi8(bgr_plane[0 + j], zero); + g = _mm_unpackhi_epi8(bgr_plane[2 + j], zero); + r = _mm_unpackhi_epi8(bgr_plane[4 + j], zero); + ConvertRGBToY_SSE2(&r, &g, &b, &Y1); + + // Cast to 8-bit and store. + STORE_16(_mm_packus_epi16(Y0, Y1), y + i); + } + } + for (; i < width; ++i, bgr += 3) { // left-over + y[i] = VP8RGBToY(bgr[2], bgr[1], bgr[0], YUV_HALF); + } +} + +static void ConvertARGBToY_SSE2(const uint32_t* WEBP_RESTRICT argb, + uint8_t* WEBP_RESTRICT y, int width) { + const int max_width = width & ~15; + int i; + for (i = 0; i < max_width; i += 16) { + __m128i Y0, Y1, rgb[6]; + RGB32PackedToPlanar_SSE2(&argb[i], rgb); + ConvertRGBToY_SSE2(&rgb[0], &rgb[2], &rgb[4], &Y0); + ConvertRGBToY_SSE2(&rgb[1], &rgb[3], &rgb[5], &Y1); + STORE_16(_mm_packus_epi16(Y0, Y1), y + i); + } + for (; i < width; ++i) { // left-over + const uint32_t p = argb[i]; + y[i] = VP8RGBToY((p >> 16) & 0xff, (p >> 8) & 0xff, (p >> 0) & 0xff, + YUV_HALF); + } +} + +// Horizontal add (doubled) of two 16b values, result is 16b. +// in: A | B | C | D | ... -> out: 2*(A+B) | 2*(C+D) | ... +static void HorizontalAddPack_SSE2(const __m128i* const A, + const __m128i* const B, + __m128i* const out) { + const __m128i k2 = _mm_set1_epi16(2); + const __m128i C = _mm_madd_epi16(*A, k2); + const __m128i D = _mm_madd_epi16(*B, k2); + *out = _mm_packs_epi32(C, D); +} + +static void ConvertARGBToUV_SSE2(const uint32_t* WEBP_RESTRICT argb, + uint8_t* WEBP_RESTRICT u, + uint8_t* WEBP_RESTRICT v, + int src_width, int do_store) { + const int max_width = src_width & ~31; + int i; + for (i = 0; i < max_width; i += 32, u += 16, v += 16) { + __m128i rgb[6], U0, V0, U1, V1; + RGB32PackedToPlanar_SSE2(&argb[i], rgb); + HorizontalAddPack_SSE2(&rgb[0], &rgb[1], &rgb[0]); + HorizontalAddPack_SSE2(&rgb[2], &rgb[3], &rgb[2]); + HorizontalAddPack_SSE2(&rgb[4], &rgb[5], &rgb[4]); + ConvertRGBToUV_SSE2(&rgb[0], &rgb[2], &rgb[4], &U0, &V0); + + RGB32PackedToPlanar_SSE2(&argb[i + 16], rgb); + HorizontalAddPack_SSE2(&rgb[0], &rgb[1], &rgb[0]); + HorizontalAddPack_SSE2(&rgb[2], &rgb[3], &rgb[2]); + HorizontalAddPack_SSE2(&rgb[4], &rgb[5], &rgb[4]); + ConvertRGBToUV_SSE2(&rgb[0], &rgb[2], &rgb[4], &U1, &V1); + + U0 = _mm_packus_epi16(U0, U1); + V0 = _mm_packus_epi16(V0, V1); + if (!do_store) { + const __m128i prev_u = LOAD_16(u); + const __m128i prev_v = LOAD_16(v); + U0 = _mm_avg_epu8(U0, prev_u); + V0 = _mm_avg_epu8(V0, prev_v); + } + STORE_16(U0, u); + STORE_16(V0, v); + } + if (i < src_width) { // left-over + WebPConvertARGBToUV_C(argb + i, u, v, src_width - i, do_store); + } +} + +// Convert 16 packed ARGB 16b-values to r[], g[], b[] +static WEBP_INLINE void RGBA32PackedToPlanar_16b_SSE2( + const uint16_t* WEBP_RESTRICT const rgbx, + __m128i* const r, __m128i* const g, __m128i* const b) { + const __m128i in0 = LOAD_16(rgbx + 0); // r0 | g0 | b0 |x| r1 | g1 | b1 |x + const __m128i in1 = LOAD_16(rgbx + 8); // r2 | g2 | b2 |x| r3 | g3 | b3 |x + const __m128i in2 = LOAD_16(rgbx + 16); // r4 | ... + const __m128i in3 = LOAD_16(rgbx + 24); // r6 | ... + // column-wise transpose + const __m128i A0 = _mm_unpacklo_epi16(in0, in1); + const __m128i A1 = _mm_unpackhi_epi16(in0, in1); + const __m128i A2 = _mm_unpacklo_epi16(in2, in3); + const __m128i A3 = _mm_unpackhi_epi16(in2, in3); + const __m128i B0 = _mm_unpacklo_epi16(A0, A1); // r0 r1 r2 r3 | g0 g1 .. + const __m128i B1 = _mm_unpackhi_epi16(A0, A1); // b0 b1 b2 b3 | x x x x + const __m128i B2 = _mm_unpacklo_epi16(A2, A3); // r4 r5 r6 r7 | g4 g5 .. + const __m128i B3 = _mm_unpackhi_epi16(A2, A3); // b4 b5 b6 b7 | x x x x + *r = _mm_unpacklo_epi64(B0, B2); + *g = _mm_unpackhi_epi64(B0, B2); + *b = _mm_unpacklo_epi64(B1, B3); +} + +static void ConvertRGBA32ToUV_SSE2(const uint16_t* WEBP_RESTRICT rgb, + uint8_t* WEBP_RESTRICT u, + uint8_t* WEBP_RESTRICT v, int width) { + const int max_width = width & ~15; + const uint16_t* const last_rgb = rgb + 4 * max_width; + while (rgb < last_rgb) { + __m128i r, g, b, U0, V0, U1, V1; + RGBA32PackedToPlanar_16b_SSE2(rgb + 0, &r, &g, &b); + ConvertRGBToUV_SSE2(&r, &g, &b, &U0, &V0); + RGBA32PackedToPlanar_16b_SSE2(rgb + 32, &r, &g, &b); + ConvertRGBToUV_SSE2(&r, &g, &b, &U1, &V1); + STORE_16(_mm_packus_epi16(U0, U1), u); + STORE_16(_mm_packus_epi16(V0, V1), v); + u += 16; + v += 16; + rgb += 2 * 32; + } + if (max_width < width) { // left-over + WebPConvertRGBA32ToUV_C(rgb, u, v, width - max_width); + } +} + +//------------------------------------------------------------------------------ + +extern void WebPInitConvertARGBToYUVSSE2(void); + +WEBP_TSAN_IGNORE_FUNCTION void WebPInitConvertARGBToYUVSSE2(void) { + WebPConvertARGBToY = ConvertARGBToY_SSE2; + WebPConvertARGBToUV = ConvertARGBToUV_SSE2; + + WebPConvertRGB24ToY = ConvertRGB24ToY_SSE2; + WebPConvertBGR24ToY = ConvertBGR24ToY_SSE2; + + WebPConvertRGBA32ToUV = ConvertRGBA32ToUV_SSE2; +} + +#else // !WEBP_USE_SSE2 + +WEBP_DSP_INIT_STUB(WebPInitSamplersSSE2) +WEBP_DSP_INIT_STUB(WebPInitConvertARGBToYUVSSE2) + +#endif // WEBP_USE_SSE2 diff --git a/packages/core/src/zig/vendor/libwebp/src/dsp/yuv_sse41.c b/packages/core/src/zig/vendor/libwebp/src/dsp/yuv_sse41.c new file mode 100644 index 0000000000..e1b8084648 --- /dev/null +++ b/packages/core/src/zig/vendor/libwebp/src/dsp/yuv_sse41.c @@ -0,0 +1,631 @@ +// Copyright 2014 Google Inc. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the COPYING file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +// ----------------------------------------------------------------------------- +// +// YUV->RGB conversion functions +// +// Author: Skal (pascal.massimino@gmail.com) + +#include "src/dsp/yuv.h" + +#if defined(WEBP_USE_SSE41) +#include +#include + +#include + +#include "src/dsp/common_sse41.h" +#include "src/dsp/cpu.h" +#include "src/dsp/dsp.h" +#include "src/utils/utils.h" +#include "src/webp/decode.h" +#include "src/webp/types.h" + +//----------------------------------------------------------------------------- +// Convert spans of 32 pixels to various RGB formats for the fancy upsampler. + +// These constants are 14b fixed-point version of ITU-R BT.601 constants. +// R = (19077 * y + 26149 * v - 14234) >> 6 +// G = (19077 * y - 6419 * u - 13320 * v + 8708) >> 6 +// B = (19077 * y + 33050 * u - 17685) >> 6 +static void ConvertYUV444ToRGB_SSE41(const __m128i* const Y0, + const __m128i* const U0, + const __m128i* const V0, + __m128i* const R, + __m128i* const G, + __m128i* const B) { + const __m128i k19077 = _mm_set1_epi16(19077); + const __m128i k26149 = _mm_set1_epi16(26149); + const __m128i k14234 = _mm_set1_epi16(14234); + // 33050 doesn't fit in a signed short: only use this with unsigned arithmetic + const __m128i k33050 = _mm_set1_epi16((short)33050); + const __m128i k17685 = _mm_set1_epi16(17685); + const __m128i k6419 = _mm_set1_epi16(6419); + const __m128i k13320 = _mm_set1_epi16(13320); + const __m128i k8708 = _mm_set1_epi16(8708); + + const __m128i Y1 = _mm_mulhi_epu16(*Y0, k19077); + + const __m128i R0 = _mm_mulhi_epu16(*V0, k26149); + const __m128i R1 = _mm_sub_epi16(Y1, k14234); + const __m128i R2 = _mm_add_epi16(R1, R0); + + const __m128i G0 = _mm_mulhi_epu16(*U0, k6419); + const __m128i G1 = _mm_mulhi_epu16(*V0, k13320); + const __m128i G2 = _mm_add_epi16(Y1, k8708); + const __m128i G3 = _mm_add_epi16(G0, G1); + const __m128i G4 = _mm_sub_epi16(G2, G3); + + // be careful with the saturated *unsigned* arithmetic here! + const __m128i B0 = _mm_mulhi_epu16(*U0, k33050); + const __m128i B1 = _mm_adds_epu16(B0, Y1); + const __m128i B2 = _mm_subs_epu16(B1, k17685); + + // use logical shift for B2, which can be larger than 32767 + *R = _mm_srai_epi16(R2, 6); // range: [-14234, 30815] + *G = _mm_srai_epi16(G4, 6); // range: [-10953, 27710] + *B = _mm_srli_epi16(B2, 6); // range: [0, 34238] +} + +// Load the bytes into the *upper* part of 16b words. That's "<< 8", basically. +static WEBP_INLINE __m128i Load_HI_16_SSE41(const uint8_t* src) { + const __m128i zero = _mm_setzero_si128(); + return _mm_unpacklo_epi8(zero, _mm_loadl_epi64((const __m128i*)src)); +} + +// Load and replicate the U/V samples +static WEBP_INLINE __m128i Load_UV_HI_8_SSE41(const uint8_t* src) { + const __m128i zero = _mm_setzero_si128(); + const __m128i tmp0 = _mm_cvtsi32_si128(WebPMemToInt32(src)); + const __m128i tmp1 = _mm_unpacklo_epi8(zero, tmp0); + return _mm_unpacklo_epi16(tmp1, tmp1); // replicate samples +} + +// Convert 32 samples of YUV444 to R/G/B +static void YUV444ToRGB_SSE41(const uint8_t* WEBP_RESTRICT const y, + const uint8_t* WEBP_RESTRICT const u, + const uint8_t* WEBP_RESTRICT const v, + __m128i* const R, __m128i* const G, + __m128i* const B) { + const __m128i Y0 = Load_HI_16_SSE41(y), U0 = Load_HI_16_SSE41(u), + V0 = Load_HI_16_SSE41(v); + ConvertYUV444ToRGB_SSE41(&Y0, &U0, &V0, R, G, B); +} + +// Convert 32 samples of YUV420 to R/G/B +static void YUV420ToRGB_SSE41(const uint8_t* WEBP_RESTRICT const y, + const uint8_t* WEBP_RESTRICT const u, + const uint8_t* WEBP_RESTRICT const v, + __m128i* const R, __m128i* const G, + __m128i* const B) { + const __m128i Y0 = Load_HI_16_SSE41(y), U0 = Load_UV_HI_8_SSE41(u), + V0 = Load_UV_HI_8_SSE41(v); + ConvertYUV444ToRGB_SSE41(&Y0, &U0, &V0, R, G, B); +} + +// Pack the planar buffers +// rrrr... rrrr... gggg... gggg... bbbb... bbbb.... +// triplet by triplet in the output buffer rgb as rgbrgbrgbrgb ... +static WEBP_INLINE void PlanarTo24b_SSE41( + __m128i* const in0, __m128i* const in1, __m128i* const in2, + __m128i* const in3, __m128i* const in4, __m128i* const in5, + uint8_t* WEBP_RESTRICT const rgb) { + // The input is 6 registers of sixteen 8b but for the sake of explanation, + // let's take 6 registers of four 8b values. + // To pack, we will keep taking one every two 8b integer and move it + // around as follows: + // Input: + // r0r1r2r3 | r4r5r6r7 | g0g1g2g3 | g4g5g6g7 | b0b1b2b3 | b4b5b6b7 + // Split the 6 registers in two sets of 3 registers: the first set as the even + // 8b bytes, the second the odd ones: + // r0r2r4r6 | g0g2g4g6 | b0b2b4b6 | r1r3r5r7 | g1g3g5g7 | b1b3b5b7 + // Repeat the same permutations twice more: + // r0r4g0g4 | b0b4r1r5 | g1g5b1b5 | r2r6g2g6 | b2b6r3r7 | g3g7b3b7 + // r0g0b0r1 | g1b1r2g2 | b2r3g3b3 | r4g4b4r5 | g5b5r6g6 | b6r7g7b7 + VP8PlanarTo24b_SSE41(in0, in1, in2, in3, in4, in5); + + _mm_storeu_si128((__m128i*)(rgb + 0), *in0); + _mm_storeu_si128((__m128i*)(rgb + 16), *in1); + _mm_storeu_si128((__m128i*)(rgb + 32), *in2); + _mm_storeu_si128((__m128i*)(rgb + 48), *in3); + _mm_storeu_si128((__m128i*)(rgb + 64), *in4); + _mm_storeu_si128((__m128i*)(rgb + 80), *in5); +} + +void VP8YuvToRgb32_SSE41(const uint8_t* WEBP_RESTRICT y, + const uint8_t* WEBP_RESTRICT u, + const uint8_t* WEBP_RESTRICT v, + uint8_t* WEBP_RESTRICT dst) { + __m128i R0, R1, R2, R3, G0, G1, G2, G3, B0, B1, B2, B3; + __m128i rgb0, rgb1, rgb2, rgb3, rgb4, rgb5; + + YUV444ToRGB_SSE41(y + 0, u + 0, v + 0, &R0, &G0, &B0); + YUV444ToRGB_SSE41(y + 8, u + 8, v + 8, &R1, &G1, &B1); + YUV444ToRGB_SSE41(y + 16, u + 16, v + 16, &R2, &G2, &B2); + YUV444ToRGB_SSE41(y + 24, u + 24, v + 24, &R3, &G3, &B3); + + // Cast to 8b and store as RRRRGGGGBBBB. + rgb0 = _mm_packus_epi16(R0, R1); + rgb1 = _mm_packus_epi16(R2, R3); + rgb2 = _mm_packus_epi16(G0, G1); + rgb3 = _mm_packus_epi16(G2, G3); + rgb4 = _mm_packus_epi16(B0, B1); + rgb5 = _mm_packus_epi16(B2, B3); + + // Pack as RGBRGBRGBRGB. + PlanarTo24b_SSE41(&rgb0, &rgb1, &rgb2, &rgb3, &rgb4, &rgb5, dst); +} + +void VP8YuvToBgr32_SSE41(const uint8_t* WEBP_RESTRICT y, + const uint8_t* WEBP_RESTRICT u, + const uint8_t* WEBP_RESTRICT v, + uint8_t* WEBP_RESTRICT dst) { + __m128i R0, R1, R2, R3, G0, G1, G2, G3, B0, B1, B2, B3; + __m128i bgr0, bgr1, bgr2, bgr3, bgr4, bgr5; + + YUV444ToRGB_SSE41(y + 0, u + 0, v + 0, &R0, &G0, &B0); + YUV444ToRGB_SSE41(y + 8, u + 8, v + 8, &R1, &G1, &B1); + YUV444ToRGB_SSE41(y + 16, u + 16, v + 16, &R2, &G2, &B2); + YUV444ToRGB_SSE41(y + 24, u + 24, v + 24, &R3, &G3, &B3); + + // Cast to 8b and store as BBBBGGGGRRRR. + bgr0 = _mm_packus_epi16(B0, B1); + bgr1 = _mm_packus_epi16(B2, B3); + bgr2 = _mm_packus_epi16(G0, G1); + bgr3 = _mm_packus_epi16(G2, G3); + bgr4 = _mm_packus_epi16(R0, R1); + bgr5= _mm_packus_epi16(R2, R3); + + // Pack as BGRBGRBGRBGR. + PlanarTo24b_SSE41(&bgr0, &bgr1, &bgr2, &bgr3, &bgr4, &bgr5, dst); +} + +//----------------------------------------------------------------------------- +// Arbitrary-length row conversion functions + +static void YuvToRgbRow_SSE41(const uint8_t* WEBP_RESTRICT y, + const uint8_t* WEBP_RESTRICT u, + const uint8_t* WEBP_RESTRICT v, + uint8_t* WEBP_RESTRICT dst, int len) { + int n; + for (n = 0; n + 32 <= len; n += 32, dst += 32 * 3) { + __m128i R0, R1, R2, R3, G0, G1, G2, G3, B0, B1, B2, B3; + __m128i rgb0, rgb1, rgb2, rgb3, rgb4, rgb5; + + YUV420ToRGB_SSE41(y + 0, u + 0, v + 0, &R0, &G0, &B0); + YUV420ToRGB_SSE41(y + 8, u + 4, v + 4, &R1, &G1, &B1); + YUV420ToRGB_SSE41(y + 16, u + 8, v + 8, &R2, &G2, &B2); + YUV420ToRGB_SSE41(y + 24, u + 12, v + 12, &R3, &G3, &B3); + + // Cast to 8b and store as RRRRGGGGBBBB. + rgb0 = _mm_packus_epi16(R0, R1); + rgb1 = _mm_packus_epi16(R2, R3); + rgb2 = _mm_packus_epi16(G0, G1); + rgb3 = _mm_packus_epi16(G2, G3); + rgb4 = _mm_packus_epi16(B0, B1); + rgb5 = _mm_packus_epi16(B2, B3); + + // Pack as RGBRGBRGBRGB. + PlanarTo24b_SSE41(&rgb0, &rgb1, &rgb2, &rgb3, &rgb4, &rgb5, dst); + + y += 32; + u += 16; + v += 16; + } + for (; n < len; ++n) { // Finish off + VP8YuvToRgb(y[0], u[0], v[0], dst); + dst += 3; + y += 1; + u += (n & 1); + v += (n & 1); + } +} + +static void YuvToBgrRow_SSE41(const uint8_t* WEBP_RESTRICT y, + const uint8_t* WEBP_RESTRICT u, + const uint8_t* WEBP_RESTRICT v, + uint8_t* WEBP_RESTRICT dst, int len) { + int n; + for (n = 0; n + 32 <= len; n += 32, dst += 32 * 3) { + __m128i R0, R1, R2, R3, G0, G1, G2, G3, B0, B1, B2, B3; + __m128i bgr0, bgr1, bgr2, bgr3, bgr4, bgr5; + + YUV420ToRGB_SSE41(y + 0, u + 0, v + 0, &R0, &G0, &B0); + YUV420ToRGB_SSE41(y + 8, u + 4, v + 4, &R1, &G1, &B1); + YUV420ToRGB_SSE41(y + 16, u + 8, v + 8, &R2, &G2, &B2); + YUV420ToRGB_SSE41(y + 24, u + 12, v + 12, &R3, &G3, &B3); + + // Cast to 8b and store as BBBBGGGGRRRR. + bgr0 = _mm_packus_epi16(B0, B1); + bgr1 = _mm_packus_epi16(B2, B3); + bgr2 = _mm_packus_epi16(G0, G1); + bgr3 = _mm_packus_epi16(G2, G3); + bgr4 = _mm_packus_epi16(R0, R1); + bgr5 = _mm_packus_epi16(R2, R3); + + // Pack as BGRBGRBGRBGR. + PlanarTo24b_SSE41(&bgr0, &bgr1, &bgr2, &bgr3, &bgr4, &bgr5, dst); + + y += 32; + u += 16; + v += 16; + } + for (; n < len; ++n) { // Finish off + VP8YuvToBgr(y[0], u[0], v[0], dst); + dst += 3; + y += 1; + u += (n & 1); + v += (n & 1); + } +} + +//------------------------------------------------------------------------------ +// Entry point + +extern void WebPInitSamplersSSE41(void); + +WEBP_TSAN_IGNORE_FUNCTION void WebPInitSamplersSSE41(void) { + WebPSamplers[MODE_RGB] = YuvToRgbRow_SSE41; + WebPSamplers[MODE_BGR] = YuvToBgrRow_SSE41; +} + +//------------------------------------------------------------------------------ +// RGB24/32 -> YUV converters + +// Load eight 16b-words from *src. +#define LOAD_16(src) _mm_loadu_si128((const __m128i*)(src)) +// Store either 16b-words into *dst +#define STORE_16(V, dst) _mm_storeu_si128((__m128i*)(dst), (V)) + +#define WEBP_SSE41_SHUFF(OUT) do { \ + const __m128i tmp0 = _mm_shuffle_epi8(A0, shuff0); \ + const __m128i tmp1 = _mm_shuffle_epi8(A1, shuff1); \ + const __m128i tmp2 = _mm_shuffle_epi8(A2, shuff2); \ + const __m128i tmp3 = _mm_shuffle_epi8(A3, shuff0); \ + const __m128i tmp4 = _mm_shuffle_epi8(A4, shuff1); \ + const __m128i tmp5 = _mm_shuffle_epi8(A5, shuff2); \ + \ + /* OR everything to get one channel */ \ + const __m128i tmp6 = _mm_or_si128(tmp0, tmp1); \ + const __m128i tmp7 = _mm_or_si128(tmp3, tmp4); \ + out[OUT + 0] = _mm_or_si128(tmp6, tmp2); \ + out[OUT + 1] = _mm_or_si128(tmp7, tmp5); \ +} while (0); + +// Unpack the 8b input rgbrgbrgbrgb ... as contiguous registers: +// rrrr... rrrr... gggg... gggg... bbbb... bbbb.... +// Similar to PlanarTo24bHelper(), but in reverse order. +static WEBP_INLINE void RGB24PackedToPlanar_SSE41( + const uint8_t* WEBP_RESTRICT const rgb, __m128i* const out /*out[6]*/) { + const __m128i A0 = _mm_loadu_si128((const __m128i*)(rgb + 0)); + const __m128i A1 = _mm_loadu_si128((const __m128i*)(rgb + 16)); + const __m128i A2 = _mm_loadu_si128((const __m128i*)(rgb + 32)); + const __m128i A3 = _mm_loadu_si128((const __m128i*)(rgb + 48)); + const __m128i A4 = _mm_loadu_si128((const __m128i*)(rgb + 64)); + const __m128i A5 = _mm_loadu_si128((const __m128i*)(rgb + 80)); + + // Compute RR. + { + const __m128i shuff0 = _mm_set_epi8( + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 15, 12, 9, 6, 3, 0); + const __m128i shuff1 = _mm_set_epi8( + -1, -1, -1, -1, -1, 14, 11, 8, 5, 2, -1, -1, -1, -1, -1, -1); + const __m128i shuff2 = _mm_set_epi8( + 13, 10, 7, 4, 1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1); + WEBP_SSE41_SHUFF(0) + } + // Compute GG. + { + const __m128i shuff0 = _mm_set_epi8( + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 13, 10, 7, 4, 1); + const __m128i shuff1 = _mm_set_epi8( + -1, -1, -1, -1, -1, 15, 12, 9, 6, 3, 0, -1, -1, -1, -1, -1); + const __m128i shuff2 = _mm_set_epi8( + 14, 11, 8, 5, 2, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1); + WEBP_SSE41_SHUFF(2) + } + // Compute BB. + { + const __m128i shuff0 = _mm_set_epi8( + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 14, 11, 8, 5, 2); + const __m128i shuff1 = _mm_set_epi8( + -1, -1, -1, -1, -1, -1, 13, 10, 7, 4, 1, -1, -1, -1, -1, -1); + const __m128i shuff2 = _mm_set_epi8( + 15, 12, 9, 6, 3, 0, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1); + WEBP_SSE41_SHUFF(4) + } +} + +#undef WEBP_SSE41_SHUFF + +// Convert 8 packed ARGB to r[], g[], b[] +static WEBP_INLINE void RGB32PackedToPlanar_SSE41( + const uint32_t* WEBP_RESTRICT const argb, __m128i* const rgb /*in[6]*/) { + const __m128i zero = _mm_setzero_si128(); + __m128i a0 = LOAD_16(argb + 0); + __m128i a1 = LOAD_16(argb + 4); + __m128i a2 = LOAD_16(argb + 8); + __m128i a3 = LOAD_16(argb + 12); + VP8L32bToPlanar_SSE41(&a0, &a1, &a2, &a3); + rgb[0] = _mm_unpacklo_epi8(a1, zero); + rgb[1] = _mm_unpackhi_epi8(a1, zero); + rgb[2] = _mm_unpacklo_epi8(a2, zero); + rgb[3] = _mm_unpackhi_epi8(a2, zero); + rgb[4] = _mm_unpacklo_epi8(a3, zero); + rgb[5] = _mm_unpackhi_epi8(a3, zero); +} + +// This macro computes (RG * MULT_RG + GB * MULT_GB + ROUNDER) >> DESCALE_FIX +// It's a macro and not a function because we need to use immediate values with +// srai_epi32, e.g. +#define TRANSFORM(RG_LO, RG_HI, GB_LO, GB_HI, MULT_RG, MULT_GB, \ + ROUNDER, DESCALE_FIX, OUT) do { \ + const __m128i V0_lo = _mm_madd_epi16(RG_LO, MULT_RG); \ + const __m128i V0_hi = _mm_madd_epi16(RG_HI, MULT_RG); \ + const __m128i V1_lo = _mm_madd_epi16(GB_LO, MULT_GB); \ + const __m128i V1_hi = _mm_madd_epi16(GB_HI, MULT_GB); \ + const __m128i V2_lo = _mm_add_epi32(V0_lo, V1_lo); \ + const __m128i V2_hi = _mm_add_epi32(V0_hi, V1_hi); \ + const __m128i V3_lo = _mm_add_epi32(V2_lo, ROUNDER); \ + const __m128i V3_hi = _mm_add_epi32(V2_hi, ROUNDER); \ + const __m128i V5_lo = _mm_srai_epi32(V3_lo, DESCALE_FIX); \ + const __m128i V5_hi = _mm_srai_epi32(V3_hi, DESCALE_FIX); \ + (OUT) = _mm_packs_epi32(V5_lo, V5_hi); \ +} while (0) + +#define MK_CST_16(A, B) _mm_set_epi16((B), (A), (B), (A), (B), (A), (B), (A)) +static WEBP_INLINE void ConvertRGBToY_SSE41(const __m128i* const R, + const __m128i* const G, + const __m128i* const B, + __m128i* const Y) { + const __m128i kRG_y = MK_CST_16(16839, 33059 - 16384); + const __m128i kGB_y = MK_CST_16(16384, 6420); + const __m128i kHALF_Y = _mm_set1_epi32((16 << YUV_FIX) + YUV_HALF); + + const __m128i RG_lo = _mm_unpacklo_epi16(*R, *G); + const __m128i RG_hi = _mm_unpackhi_epi16(*R, *G); + const __m128i GB_lo = _mm_unpacklo_epi16(*G, *B); + const __m128i GB_hi = _mm_unpackhi_epi16(*G, *B); + TRANSFORM(RG_lo, RG_hi, GB_lo, GB_hi, kRG_y, kGB_y, kHALF_Y, YUV_FIX, *Y); +} + +static WEBP_INLINE void ConvertRGBToUV_SSE41(const __m128i* const R, + const __m128i* const G, + const __m128i* const B, + __m128i* const U, + __m128i* const V) { + const __m128i kRG_u = MK_CST_16(-9719, -19081); + const __m128i kGB_u = MK_CST_16(0, 28800); + const __m128i kRG_v = MK_CST_16(28800, 0); + const __m128i kGB_v = MK_CST_16(-24116, -4684); + const __m128i kHALF_UV = _mm_set1_epi32(((128 << YUV_FIX) + YUV_HALF) << 2); + + const __m128i RG_lo = _mm_unpacklo_epi16(*R, *G); + const __m128i RG_hi = _mm_unpackhi_epi16(*R, *G); + const __m128i GB_lo = _mm_unpacklo_epi16(*G, *B); + const __m128i GB_hi = _mm_unpackhi_epi16(*G, *B); + TRANSFORM(RG_lo, RG_hi, GB_lo, GB_hi, kRG_u, kGB_u, + kHALF_UV, YUV_FIX + 2, *U); + TRANSFORM(RG_lo, RG_hi, GB_lo, GB_hi, kRG_v, kGB_v, + kHALF_UV, YUV_FIX + 2, *V); +} + +#undef MK_CST_16 +#undef TRANSFORM + +static void ConvertRGB24ToY_SSE41(const uint8_t* WEBP_RESTRICT rgb, + uint8_t* WEBP_RESTRICT y, int width) { + const int max_width = width & ~31; + int i; + for (i = 0; i < max_width; rgb += 3 * 16 * 2) { + __m128i rgb_plane[6]; + int j; + + RGB24PackedToPlanar_SSE41(rgb, rgb_plane); + + for (j = 0; j < 2; ++j, i += 16) { + const __m128i zero = _mm_setzero_si128(); + __m128i r, g, b, Y0, Y1; + + // Convert to 16-bit Y. + r = _mm_unpacklo_epi8(rgb_plane[0 + j], zero); + g = _mm_unpacklo_epi8(rgb_plane[2 + j], zero); + b = _mm_unpacklo_epi8(rgb_plane[4 + j], zero); + ConvertRGBToY_SSE41(&r, &g, &b, &Y0); + + // Convert to 16-bit Y. + r = _mm_unpackhi_epi8(rgb_plane[0 + j], zero); + g = _mm_unpackhi_epi8(rgb_plane[2 + j], zero); + b = _mm_unpackhi_epi8(rgb_plane[4 + j], zero); + ConvertRGBToY_SSE41(&r, &g, &b, &Y1); + + // Cast to 8-bit and store. + STORE_16(_mm_packus_epi16(Y0, Y1), y + i); + } + } + for (; i < width; ++i, rgb += 3) { // left-over + y[i] = VP8RGBToY(rgb[0], rgb[1], rgb[2], YUV_HALF); + } +} + +static void ConvertBGR24ToY_SSE41(const uint8_t* WEBP_RESTRICT bgr, + uint8_t* WEBP_RESTRICT y, int width) { + const int max_width = width & ~31; + int i; + for (i = 0; i < max_width; bgr += 3 * 16 * 2) { + __m128i bgr_plane[6]; + int j; + + RGB24PackedToPlanar_SSE41(bgr, bgr_plane); + + for (j = 0; j < 2; ++j, i += 16) { + const __m128i zero = _mm_setzero_si128(); + __m128i r, g, b, Y0, Y1; + + // Convert to 16-bit Y. + b = _mm_unpacklo_epi8(bgr_plane[0 + j], zero); + g = _mm_unpacklo_epi8(bgr_plane[2 + j], zero); + r = _mm_unpacklo_epi8(bgr_plane[4 + j], zero); + ConvertRGBToY_SSE41(&r, &g, &b, &Y0); + + // Convert to 16-bit Y. + b = _mm_unpackhi_epi8(bgr_plane[0 + j], zero); + g = _mm_unpackhi_epi8(bgr_plane[2 + j], zero); + r = _mm_unpackhi_epi8(bgr_plane[4 + j], zero); + ConvertRGBToY_SSE41(&r, &g, &b, &Y1); + + // Cast to 8-bit and store. + STORE_16(_mm_packus_epi16(Y0, Y1), y + i); + } + } + for (; i < width; ++i, bgr += 3) { // left-over + y[i] = VP8RGBToY(bgr[2], bgr[1], bgr[0], YUV_HALF); + } +} + +static void ConvertARGBToY_SSE41(const uint32_t* WEBP_RESTRICT argb, + uint8_t* WEBP_RESTRICT y, int width) { + const int max_width = width & ~15; + int i; + for (i = 0; i < max_width; i += 16) { + __m128i Y0, Y1, rgb[6]; + RGB32PackedToPlanar_SSE41(&argb[i], rgb); + ConvertRGBToY_SSE41(&rgb[0], &rgb[2], &rgb[4], &Y0); + ConvertRGBToY_SSE41(&rgb[1], &rgb[3], &rgb[5], &Y1); + STORE_16(_mm_packus_epi16(Y0, Y1), y + i); + } + for (; i < width; ++i) { // left-over + const uint32_t p = argb[i]; + y[i] = VP8RGBToY((p >> 16) & 0xff, (p >> 8) & 0xff, (p >> 0) & 0xff, + YUV_HALF); + } +} + +// Horizontal add (doubled) of two 16b values, result is 16b. +// in: A | B | C | D | ... -> out: 2*(A+B) | 2*(C+D) | ... +static void HorizontalAddPack_SSE41(const __m128i* const A, + const __m128i* const B, + __m128i* const out) { + const __m128i k2 = _mm_set1_epi16(2); + const __m128i C = _mm_madd_epi16(*A, k2); + const __m128i D = _mm_madd_epi16(*B, k2); + *out = _mm_packs_epi32(C, D); +} + +static void ConvertARGBToUV_SSE41(const uint32_t* WEBP_RESTRICT argb, + uint8_t* WEBP_RESTRICT u, + uint8_t* WEBP_RESTRICT v, + int src_width, int do_store) { + const int max_width = src_width & ~31; + int i; + for (i = 0; i < max_width; i += 32, u += 16, v += 16) { + __m128i rgb[6], U0, V0, U1, V1; + RGB32PackedToPlanar_SSE41(&argb[i], rgb); + HorizontalAddPack_SSE41(&rgb[0], &rgb[1], &rgb[0]); + HorizontalAddPack_SSE41(&rgb[2], &rgb[3], &rgb[2]); + HorizontalAddPack_SSE41(&rgb[4], &rgb[5], &rgb[4]); + ConvertRGBToUV_SSE41(&rgb[0], &rgb[2], &rgb[4], &U0, &V0); + + RGB32PackedToPlanar_SSE41(&argb[i + 16], rgb); + HorizontalAddPack_SSE41(&rgb[0], &rgb[1], &rgb[0]); + HorizontalAddPack_SSE41(&rgb[2], &rgb[3], &rgb[2]); + HorizontalAddPack_SSE41(&rgb[4], &rgb[5], &rgb[4]); + ConvertRGBToUV_SSE41(&rgb[0], &rgb[2], &rgb[4], &U1, &V1); + + U0 = _mm_packus_epi16(U0, U1); + V0 = _mm_packus_epi16(V0, V1); + if (!do_store) { + const __m128i prev_u = LOAD_16(u); + const __m128i prev_v = LOAD_16(v); + U0 = _mm_avg_epu8(U0, prev_u); + V0 = _mm_avg_epu8(V0, prev_v); + } + STORE_16(U0, u); + STORE_16(V0, v); + } + if (i < src_width) { // left-over + WebPConvertARGBToUV_C(argb + i, u, v, src_width - i, do_store); + } +} + +// Convert 16 packed ARGB 16b-values to r[], g[], b[] +static WEBP_INLINE void RGBA32PackedToPlanar_16b_SSE41( + const uint16_t* WEBP_RESTRICT const rgbx, + __m128i* const r, __m128i* const g, __m128i* const b) { + const __m128i in0 = LOAD_16(rgbx + 0); // r0 | g0 | b0 |x| r1 | g1 | b1 |x + const __m128i in1 = LOAD_16(rgbx + 8); // r2 | g2 | b2 |x| r3 | g3 | b3 |x + const __m128i in2 = LOAD_16(rgbx + 16); // r4 | ... + const __m128i in3 = LOAD_16(rgbx + 24); // r6 | ... + // aarrggbb as 16-bit. + const __m128i shuff0 = + _mm_set_epi8(-1, -1, -1, -1, 13, 12, 5, 4, 11, 10, 3, 2, 9, 8, 1, 0); + const __m128i shuff1 = + _mm_set_epi8(13, 12, 5, 4, -1, -1, -1, -1, 11, 10, 3, 2, 9, 8, 1, 0); + const __m128i A0 = _mm_shuffle_epi8(in0, shuff0); + const __m128i A1 = _mm_shuffle_epi8(in1, shuff1); + const __m128i A2 = _mm_shuffle_epi8(in2, shuff0); + const __m128i A3 = _mm_shuffle_epi8(in3, shuff1); + // R0R1G0G1 + // B0B1**** + // R2R3G2G3 + // B2B3**** + // (OR is used to free port 5 for the unpack) + const __m128i B0 = _mm_unpacklo_epi32(A0, A1); + const __m128i B1 = _mm_or_si128(A0, A1); + const __m128i B2 = _mm_unpacklo_epi32(A2, A3); + const __m128i B3 = _mm_or_si128(A2, A3); + // Gather the channels. + *r = _mm_unpacklo_epi64(B0, B2); + *g = _mm_unpackhi_epi64(B0, B2); + *b = _mm_unpackhi_epi64(B1, B3); +} + +static void ConvertRGBA32ToUV_SSE41(const uint16_t* WEBP_RESTRICT rgb, + uint8_t* WEBP_RESTRICT u, + uint8_t* WEBP_RESTRICT v, int width) { + const int max_width = width & ~15; + const uint16_t* const last_rgb = rgb + 4 * max_width; + while (rgb < last_rgb) { + __m128i r, g, b, U0, V0, U1, V1; + RGBA32PackedToPlanar_16b_SSE41(rgb + 0, &r, &g, &b); + ConvertRGBToUV_SSE41(&r, &g, &b, &U0, &V0); + RGBA32PackedToPlanar_16b_SSE41(rgb + 32, &r, &g, &b); + ConvertRGBToUV_SSE41(&r, &g, &b, &U1, &V1); + STORE_16(_mm_packus_epi16(U0, U1), u); + STORE_16(_mm_packus_epi16(V0, V1), v); + u += 16; + v += 16; + rgb += 2 * 32; + } + if (max_width < width) { // left-over + WebPConvertRGBA32ToUV_C(rgb, u, v, width - max_width); + } +} + +//------------------------------------------------------------------------------ + +extern void WebPInitConvertARGBToYUVSSE41(void); + +WEBP_TSAN_IGNORE_FUNCTION void WebPInitConvertARGBToYUVSSE41(void) { + WebPConvertARGBToY = ConvertARGBToY_SSE41; + WebPConvertARGBToUV = ConvertARGBToUV_SSE41; + + WebPConvertRGB24ToY = ConvertRGB24ToY_SSE41; + WebPConvertBGR24ToY = ConvertBGR24ToY_SSE41; + + WebPConvertRGBA32ToUV = ConvertRGBA32ToUV_SSE41; +} + +//------------------------------------------------------------------------------ + +#else // !WEBP_USE_SSE41 + +WEBP_DSP_INIT_STUB(WebPInitSamplersSSE41) +WEBP_DSP_INIT_STUB(WebPInitConvertARGBToYUVSSE41) + +#endif // WEBP_USE_SSE41 diff --git a/packages/core/src/zig/vendor/libwebp/src/utils/bit_reader_inl_utils.h b/packages/core/src/zig/vendor/libwebp/src/utils/bit_reader_inl_utils.h new file mode 100644 index 0000000000..8179c2fa5d --- /dev/null +++ b/packages/core/src/zig/vendor/libwebp/src/utils/bit_reader_inl_utils.h @@ -0,0 +1,199 @@ +// Copyright 2014 Google Inc. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the COPYING file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +// ----------------------------------------------------------------------------- +// +// Specific inlined methods for boolean decoder [VP8GetBit() ...] +// This file should be included by the .c sources that actually need to call +// these methods. +// +// Author: Skal (pascal.massimino@gmail.com) + +#ifndef WEBP_UTILS_BIT_READER_INL_UTILS_H_ +#define WEBP_UTILS_BIT_READER_INL_UTILS_H_ + +#ifdef HAVE_CONFIG_H +#include "src/webp/config.h" +#endif + +#include +#include // for memcpy + +#include "src/dsp/cpu.h" +#include "src/dsp/dsp.h" +#include "src/utils/bit_reader_utils.h" +#include "src/utils/endian_inl_utils.h" +#include "src/utils/utils.h" +#include "src/webp/types.h" + +#ifdef __cplusplus +extern "C" { +#endif + +//------------------------------------------------------------------------------ +// Derived type lbit_t = natural type for memory I/O + +#if (BITS > 32) +typedef uint64_t lbit_t; +#elif (BITS > 16) +typedef uint32_t lbit_t; +#elif (BITS > 8) +typedef uint16_t lbit_t; +#else +typedef uint8_t lbit_t; +#endif + +extern const uint8_t kVP8Log2Range[128]; +extern const uint8_t kVP8NewRange[128]; + +// special case for the tail byte-reading +void VP8LoadFinalBytes(VP8BitReader* const br); + +//------------------------------------------------------------------------------ +// Inlined critical functions + +// makes sure br->value has at least BITS bits worth of data +static WEBP_UBSAN_IGNORE_UNDEF WEBP_INLINE +void VP8LoadNewBytes(VP8BitReader* WEBP_RESTRICT const br) { + assert(br != NULL && br->buf != NULL); + // Read 'BITS' bits at a time if possible. + if (br->buf < br->buf_max) { + // convert memory type to register type (with some zero'ing!) + bit_t bits; +#if defined(WEBP_USE_MIPS32) + // This is needed because of un-aligned read. + lbit_t in_bits; + lbit_t* p_buf = (lbit_t*)br->buf; + __asm__ volatile( + ".set push \n\t" + ".set at \n\t" + ".set macro \n\t" + "ulw %[in_bits], 0(%[p_buf]) \n\t" + ".set pop \n\t" + : [in_bits]"=r"(in_bits) + : [p_buf]"r"(p_buf) + : "memory", "at" + ); +#else + lbit_t in_bits; + memcpy(&in_bits, br->buf, sizeof(in_bits)); +#endif + br->buf += BITS >> 3; +#if !defined(WORDS_BIGENDIAN) +#if (BITS > 32) + bits = BSwap64(in_bits); + bits >>= 64 - BITS; +#elif (BITS >= 24) + bits = BSwap32(in_bits); + bits >>= (32 - BITS); +#elif (BITS == 16) + bits = BSwap16(in_bits); +#else // BITS == 8 + bits = (bit_t)in_bits; +#endif // BITS > 32 +#else // WORDS_BIGENDIAN + bits = (bit_t)in_bits; + if (BITS != 8 * sizeof(bit_t)) bits >>= (8 * sizeof(bit_t) - BITS); +#endif + br->value = bits | (br->value << BITS); + br->bits += BITS; + } else { + VP8LoadFinalBytes(br); // no need to be inlined + } +} + +// Read a bit with proba 'prob'. Speed-critical function! +static WEBP_INLINE int VP8GetBit(VP8BitReader* WEBP_RESTRICT const br, + int prob, const char label[]) { + // Don't move this declaration! It makes a big speed difference to store + // 'range' *before* calling VP8LoadNewBytes(), even if this function doesn't + // alter br->range value. + range_t range = br->range; + if (br->bits < 0) { + VP8LoadNewBytes(br); + } + { + const int pos = br->bits; + const range_t split = (range * prob) >> 8; + const range_t value = (range_t)(br->value >> pos); + const int bit = (value > split); + if (bit) { + range -= split; + br->value -= (bit_t)(split + 1) << pos; + } else { + range = split + 1; + } + { + const int shift = 7 ^ BitsLog2Floor(range); + range <<= shift; + br->bits -= shift; + } + br->range = range - 1; + BT_TRACK(br); + return bit; + } +} + +// simplified version of VP8GetBit() for prob=0x80 (note shift is always 1 here) +static WEBP_UBSAN_IGNORE_UNSIGNED_OVERFLOW WEBP_INLINE +int VP8GetSigned(VP8BitReader* WEBP_RESTRICT const br, int v, + const char label[]) { + if (br->bits < 0) { + VP8LoadNewBytes(br); + } + { + const int pos = br->bits; + const range_t split = br->range >> 1; + const range_t value = (range_t)(br->value >> pos); + const int32_t mask = (int32_t)(split - value) >> 31; // -1 or 0 + br->bits -= 1; + br->range += (range_t)mask; + br->range |= 1; + br->value -= (bit_t)((split + 1) & (uint32_t)mask) << pos; + BT_TRACK(br); + return (v ^ mask) - mask; + } +} + +static WEBP_INLINE int VP8GetBitAlt(VP8BitReader* WEBP_RESTRICT const br, + int prob, const char label[]) { + // Don't move this declaration! It makes a big speed difference to store + // 'range' *before* calling VP8LoadNewBytes(), even if this function doesn't + // alter br->range value. + range_t range = br->range; + if (br->bits < 0) { + VP8LoadNewBytes(br); + } + { + const int pos = br->bits; + const range_t split = (range * prob) >> 8; + const range_t value = (range_t)(br->value >> pos); + int bit; // Don't use 'const int bit = (value > split);", it's slower. + if (value > split) { + range -= split + 1; + br->value -= (bit_t)(split + 1) << pos; + bit = 1; + } else { + range = split; + bit = 0; + } + if (range <= (range_t)0x7e) { + const int shift = kVP8Log2Range[range]; + range = kVP8NewRange[range]; + br->bits -= shift; + } + br->range = range; + BT_TRACK(br); + return bit; + } +} + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif // WEBP_UTILS_BIT_READER_INL_UTILS_H_ diff --git a/packages/core/src/zig/vendor/libwebp/src/utils/bit_reader_utils.c b/packages/core/src/zig/vendor/libwebp/src/utils/bit_reader_utils.c new file mode 100644 index 0000000000..5e3a8b37ef --- /dev/null +++ b/packages/core/src/zig/vendor/libwebp/src/utils/bit_reader_utils.c @@ -0,0 +1,306 @@ +// Copyright 2010 Google Inc. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the COPYING file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +// ----------------------------------------------------------------------------- +// +// Boolean decoder non-inlined methods +// +// Author: Skal (pascal.massimino@gmail.com) + +#ifdef HAVE_CONFIG_H +#include "src/webp/config.h" +#endif + +#include +#include + +#include "src/webp/types.h" +#include "src/dsp/cpu.h" +#include "src/utils/bit_reader_inl_utils.h" +#include "src/utils/bit_reader_utils.h" +#include "src/utils/endian_inl_utils.h" +#include "src/utils/utils.h" + +//------------------------------------------------------------------------------ +// VP8BitReader + +void VP8BitReaderSetBuffer(VP8BitReader* const br, + const uint8_t* const start, + size_t size) { + assert(start != NULL); + br->buf = start; + br->buf_end = start + size; + br->buf_max = + (size >= sizeof(lbit_t)) ? start + size - sizeof(lbit_t) + 1 : start; +} + +void VP8InitBitReader(VP8BitReader* const br, + const uint8_t* const start, size_t size) { + assert(br != NULL); + assert(start != NULL); + assert(size < (1u << 31)); // limit ensured by format and upstream checks + br->range = 255 - 1; + br->value = 0; + br->bits = -8; // to load the very first 8bits + br->eof = 0; + VP8BitReaderSetBuffer(br, start, size); + VP8LoadNewBytes(br); +} + +void VP8RemapBitReader(VP8BitReader* const br, ptrdiff_t offset) { + if (br->buf != NULL) { + br->buf += offset; + br->buf_end += offset; + br->buf_max += offset; + } +} + +const uint8_t kVP8Log2Range[128] = { + 7, 6, 6, 5, 5, 5, 5, 4, 4, 4, 4, 4, 4, 4, 4, + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 0 +}; + +// range = ((range - 1) << kVP8Log2Range[range]) + 1 +const uint8_t kVP8NewRange[128] = { + 127, 127, 191, 127, 159, 191, 223, 127, + 143, 159, 175, 191, 207, 223, 239, 127, + 135, 143, 151, 159, 167, 175, 183, 191, + 199, 207, 215, 223, 231, 239, 247, 127, + 131, 135, 139, 143, 147, 151, 155, 159, + 163, 167, 171, 175, 179, 183, 187, 191, + 195, 199, 203, 207, 211, 215, 219, 223, + 227, 231, 235, 239, 243, 247, 251, 127, + 129, 131, 133, 135, 137, 139, 141, 143, + 145, 147, 149, 151, 153, 155, 157, 159, + 161, 163, 165, 167, 169, 171, 173, 175, + 177, 179, 181, 183, 185, 187, 189, 191, + 193, 195, 197, 199, 201, 203, 205, 207, + 209, 211, 213, 215, 217, 219, 221, 223, + 225, 227, 229, 231, 233, 235, 237, 239, + 241, 243, 245, 247, 249, 251, 253, 127 +}; + +void VP8LoadFinalBytes(VP8BitReader* const br) { + assert(br != NULL && br->buf != NULL); + // Only read 8bits at a time + if (br->buf < br->buf_end) { + br->bits += 8; + br->value = (bit_t)(*br->buf++) | (br->value << 8); + } else if (!br->eof) { + br->value <<= 8; + br->bits += 8; + br->eof = 1; + } else { + br->bits = 0; // This is to avoid undefined behaviour with shifts. + } +} + +//------------------------------------------------------------------------------ +// Higher-level calls + +uint32_t VP8GetValue(VP8BitReader* const br, int bits, const char label[]) { + uint32_t v = 0; + while (bits-- > 0) { + v |= VP8GetBit(br, 0x80, label) << bits; + } + return v; +} + +int32_t VP8GetSignedValue(VP8BitReader* const br, int bits, + const char label[]) { + const int value = VP8GetValue(br, bits, label); + return VP8Get(br, label) ? -value : value; +} + +//------------------------------------------------------------------------------ +// VP8LBitReader + +#define VP8L_LOG8_WBITS 4 // Number of bytes needed to store VP8L_WBITS bits. + +#if defined(__arm__) || defined(_M_ARM) || WEBP_AARCH64 || \ + defined(__i386__) || defined(_M_IX86) || \ + defined(__x86_64__) || defined(_M_X64) || \ + defined(__wasm__) +#define VP8L_USE_FAST_LOAD +#endif + +static const uint32_t kBitMask[VP8L_MAX_NUM_BIT_READ + 1] = { + 0, + 0x000001, 0x000003, 0x000007, 0x00000f, + 0x00001f, 0x00003f, 0x00007f, 0x0000ff, + 0x0001ff, 0x0003ff, 0x0007ff, 0x000fff, + 0x001fff, 0x003fff, 0x007fff, 0x00ffff, + 0x01ffff, 0x03ffff, 0x07ffff, 0x0fffff, + 0x1fffff, 0x3fffff, 0x7fffff, 0xffffff +}; + +void VP8LInitBitReader(VP8LBitReader* const br, const uint8_t* const start, + size_t length) { + size_t i; + vp8l_val_t value = 0; + assert(br != NULL); + assert(start != NULL); + assert(length < 0xfffffff8u); // can't happen with a RIFF chunk. + + br->len = length; + br->val = 0; + br->bit_pos = 0; + br->eos = 0; + + if (length > sizeof(br->val)) { + length = sizeof(br->val); + } + for (i = 0; i < length; ++i) { + value |= (vp8l_val_t)start[i] << (8 * i); + } + br->val = value; + br->pos = length; + br->buf = start; +} + +void VP8LBitReaderSetBuffer(VP8LBitReader* const br, + const uint8_t* const buf, size_t len) { + assert(br != NULL); + assert(buf != NULL); + assert(len < 0xfffffff8u); // can't happen with a RIFF chunk. + br->buf = buf; + br->len = len; + // 'pos' > 'len' should be considered a param error. + br->eos = (br->pos > br->len) || VP8LIsEndOfStream(br); +} + +static void VP8LSetEndOfStream(VP8LBitReader* const br) { + br->eos = 1; + br->bit_pos = 0; // To avoid undefined behaviour with shifts. +} + +// If not at EOS, reload up to VP8L_LBITS byte-by-byte +static void ShiftBytes(VP8LBitReader* const br) { + while (br->bit_pos >= 8 && br->pos < br->len) { + br->val >>= 8; + br->val |= ((vp8l_val_t)br->buf[br->pos]) << (VP8L_LBITS - 8); + ++br->pos; + br->bit_pos -= 8; + } + if (VP8LIsEndOfStream(br)) { + VP8LSetEndOfStream(br); + } +} + +void VP8LDoFillBitWindow(VP8LBitReader* const br) { + assert(br->bit_pos >= VP8L_WBITS); +#if defined(VP8L_USE_FAST_LOAD) + if (br->pos + sizeof(br->val) < br->len) { + br->val >>= VP8L_WBITS; + br->bit_pos -= VP8L_WBITS; + br->val |= (vp8l_val_t)HToLE32(WebPMemToUint32(br->buf + br->pos)) << + (VP8L_LBITS - VP8L_WBITS); + br->pos += VP8L_LOG8_WBITS; + return; + } +#endif + ShiftBytes(br); // Slow path. +} + +uint32_t VP8LReadBits(VP8LBitReader* const br, int n_bits) { + assert(n_bits >= 0); + // Flag an error if end_of_stream or n_bits is more than allowed limit. + if (!br->eos && n_bits <= VP8L_MAX_NUM_BIT_READ) { + const uint32_t val = VP8LPrefetchBits(br) & kBitMask[n_bits]; + const int new_bits = br->bit_pos + n_bits; + br->bit_pos = new_bits; + ShiftBytes(br); + return val; + } else { + VP8LSetEndOfStream(br); + return 0; + } +} + +//------------------------------------------------------------------------------ +// Bit-tracing tool + +#if (BITTRACE > 0) + +#include // for atexit() +#include +#include + +#define MAX_NUM_LABELS 32 +static struct { + const char* label; + int size; + int count; +} kLabels[MAX_NUM_LABELS]; + +static int last_label = 0; +static int last_pos = 0; +static const uint8_t* buf_start = NULL; +static int init_done = 0; + +static void PrintBitTraces(void) { + int i; + int scale = 1; + int total = 0; + const char* units = "bits"; +#if (BITTRACE == 2) + scale = 8; + units = "bytes"; +#endif + for (i = 0; i < last_label; ++i) total += kLabels[i].size; + if (total < 1) total = 1; // avoid rounding errors + printf("=== Bit traces ===\n"); + for (i = 0; i < last_label; ++i) { + const int skip = 16 - (int)strlen(kLabels[i].label); + const int value = (kLabels[i].size + scale - 1) / scale; + assert(skip > 0); + printf("%s \%*s: %6d %s \t[%5.2f%%] [count: %7d]\n", + kLabels[i].label, skip, "", value, units, + 100.f * kLabels[i].size / total, + kLabels[i].count); + } + total = (total + scale - 1) / scale; + printf("Total: %d %s\n", total, units); +} + +void BitTrace(const struct VP8BitReader* const br, const char label[]) { + int i, pos; + if (!init_done) { + memset(kLabels, 0, sizeof(kLabels)); + atexit(PrintBitTraces); + buf_start = br->buf; + init_done = 1; + } + pos = (int)(br->buf - buf_start) * 8 - br->bits; + // if there's a too large jump, we've changed partition -> reset counter + if (abs(pos - last_pos) > 32) { + buf_start = br->buf; + pos = 0; + last_pos = 0; + } + if (br->range >= 0x7f) pos += kVP8Log2Range[br->range - 0x7f]; + for (i = 0; i < last_label; ++i) { + if (!strcmp(label, kLabels[i].label)) break; + } + if (i == MAX_NUM_LABELS) abort(); // overflow! + kLabels[i].label = label; + kLabels[i].size += pos - last_pos; + kLabels[i].count += 1; + if (i == last_label) ++last_label; + last_pos = pos; +} + +#endif // BITTRACE > 0 + +//------------------------------------------------------------------------------ diff --git a/packages/core/src/zig/vendor/libwebp/src/utils/bit_reader_utils.h b/packages/core/src/zig/vendor/libwebp/src/utils/bit_reader_utils.h new file mode 100644 index 0000000000..ddce860645 --- /dev/null +++ b/packages/core/src/zig/vendor/libwebp/src/utils/bit_reader_utils.h @@ -0,0 +1,199 @@ +// Copyright 2010 Google Inc. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the COPYING file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +// ----------------------------------------------------------------------------- +// +// Boolean decoder +// +// Author: Skal (pascal.massimino@gmail.com) +// Vikas Arora (vikaas.arora@gmail.com) + +#ifndef WEBP_UTILS_BIT_READER_UTILS_H_ +#define WEBP_UTILS_BIT_READER_UTILS_H_ + +#include +#include + +#ifdef _MSC_VER +#include // _byteswap_ulong +#endif +#include "src/dsp/cpu.h" +#include "src/webp/types.h" + +// Warning! This macro triggers quite some MACRO wizardry around func signature! +#if !defined(BITTRACE) +#define BITTRACE 0 // 0 = off, 1 = print bits, 2 = print bytes +#endif + +#if (BITTRACE > 0) +struct VP8BitReader; +extern void BitTrace(const struct VP8BitReader* const br, const char label[]); +#define BT_TRACK(br) BitTrace(br, label) +#define VP8Get(BR, L) VP8GetValue(BR, 1, L) +#else +#define BT_TRACK(br) +// We'll REMOVE the 'const char label[]' from all signatures and calls (!!): +#define VP8GetValue(BR, N, L) VP8GetValue(BR, N) +#define VP8Get(BR, L) VP8GetValue(BR, 1, L) +#define VP8GetSignedValue(BR, N, L) VP8GetSignedValue(BR, N) +#define VP8GetBit(BR, P, L) VP8GetBit(BR, P) +#define VP8GetBitAlt(BR, P, L) VP8GetBitAlt(BR, P) +#define VP8GetSigned(BR, V, L) VP8GetSigned(BR, V) +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +// The Boolean decoder needs to maintain infinite precision on the 'value' +// field. However, since 'range' is only 8bit, we only need an active window of +// 8 bits for 'value". Left bits (MSB) gets zeroed and shifted away when +// 'value' falls below 128, 'range' is updated, and fresh bits read from the +// bitstream are brought in as LSB. To avoid reading the fresh bits one by one +// (slow), we cache BITS of them ahead. The total of (BITS + 8) bits must fit +// into a natural register (with type bit_t). To fetch BITS bits from bitstream +// we use a type lbit_t. +// +// BITS can be any multiple of 8 from 8 to 56 (inclusive). +// Pick values that fit natural register size. + +#if defined(__i386__) || defined(_M_IX86) // x86 32bit +#define BITS 24 +#elif defined(__x86_64__) || defined(_M_X64) // x86 64bit +#define BITS 56 +#elif defined(__arm__) || defined(_M_ARM) // ARM +#define BITS 24 +#elif WEBP_AARCH64 // ARM 64bit +#define BITS 56 +#elif defined(__mips__) // MIPS +#define BITS 24 +#elif defined(__wasm__) // WASM +#define BITS 56 +#else // reasonable default +#define BITS 24 +#endif + +//------------------------------------------------------------------------------ +// Derived types and constants: +// bit_t = natural register type for storing 'value' (which is BITS+8 bits) +// range_t = register for 'range' (which is 8bits only) + +#if (BITS > 24) +typedef uint64_t bit_t; +#else +typedef uint32_t bit_t; +#endif + +typedef uint32_t range_t; + +//------------------------------------------------------------------------------ +// Bitreader + +typedef struct VP8BitReader VP8BitReader; +struct VP8BitReader { + // boolean decoder (keep the field ordering as is!) + bit_t value; // current value + range_t range; // current range minus 1. In [127, 254] interval. + int bits; // number of valid bits left + // read buffer + const uint8_t* buf; // next byte to be read + const uint8_t* buf_end; // end of read buffer + const uint8_t* buf_max; // max packed-read position on buffer + int eof; // true if input is exhausted +}; + +// Initialize the bit reader and the boolean decoder. +void VP8InitBitReader(VP8BitReader* const br, + const uint8_t* const start, size_t size); +// Sets the working read buffer. +void VP8BitReaderSetBuffer(VP8BitReader* const br, + const uint8_t* const start, size_t size); + +// Update internal pointers to displace the byte buffer by the +// relative offset 'offset'. +void VP8RemapBitReader(VP8BitReader* const br, ptrdiff_t offset); + +// return the next value made of 'num_bits' bits +uint32_t VP8GetValue(VP8BitReader* const br, int num_bits, const char label[]); + +// return the next value with sign-extension. +int32_t VP8GetSignedValue(VP8BitReader* const br, int num_bits, + const char label[]); + +// bit_reader_inl.h will implement the following methods: +// static WEBP_INLINE int VP8GetBit(VP8BitReader* const br, int prob, ...) +// static WEBP_INLINE int VP8GetSigned(VP8BitReader* const br, int v, ...) +// and should be included by the .c files that actually need them. +// This is to avoid recompiling the whole library whenever this file is touched, +// and also allowing platform-specific ad-hoc hacks. + +// ----------------------------------------------------------------------------- +// Bitreader for lossless format + +// maximum number of bits (inclusive) the bit-reader can handle: +#define VP8L_MAX_NUM_BIT_READ 24 + +#define VP8L_LBITS 64 // Number of bits prefetched (= bit-size of vp8l_val_t). +#define VP8L_WBITS 32 // Minimum number of bytes ready after VP8LFillBitWindow. + +typedef uint64_t vp8l_val_t; // right now, this bit-reader can only use 64bit. + +typedef struct { + vp8l_val_t val; // pre-fetched bits + const uint8_t* buf; // input byte buffer + size_t len; // buffer length + size_t pos; // byte position in buf + int bit_pos; // current bit-reading position in val + int eos; // true if a bit was read past the end of buffer +} VP8LBitReader; + +void VP8LInitBitReader(VP8LBitReader* const br, + const uint8_t* const start, + size_t length); + +// Sets a new data buffer. +void VP8LBitReaderSetBuffer(VP8LBitReader* const br, + const uint8_t* const buffer, size_t length); + +// Reads the specified number of bits from read buffer. +// Flags an error in case end_of_stream or n_bits is more than the allowed limit +// of VP8L_MAX_NUM_BIT_READ (inclusive). +// Flags 'eos' if this read attempt is going to cross the read buffer. +uint32_t VP8LReadBits(VP8LBitReader* const br, int n_bits); + +// Return the prefetched bits, so they can be looked up. +static WEBP_INLINE uint32_t VP8LPrefetchBits(VP8LBitReader* const br) { + return (uint32_t)(br->val >> (br->bit_pos & (VP8L_LBITS - 1))); +} + +// Returns true if there was an attempt at reading bit past the end of +// the buffer. Doesn't set br->eos flag. +static WEBP_INLINE int VP8LIsEndOfStream(const VP8LBitReader* const br) { + assert(br->pos <= br->len); + return br->eos || ((br->pos == br->len) && (br->bit_pos > VP8L_LBITS)); +} + +// For jumping over a number of bits in the bit stream when accessed with +// VP8LPrefetchBits and VP8LFillBitWindow. +// This function does *not* set br->eos, since it's speed-critical. +// Use with extreme care! +static WEBP_INLINE void VP8LSetBitPos(VP8LBitReader* const br, int val) { + br->bit_pos = val; +} + +// Advances the read buffer by 4 bytes to make room for reading next 32 bits. +// Speed critical, but infrequent part of the code can be non-inlined. +extern void VP8LDoFillBitWindow(VP8LBitReader* const br); +static WEBP_INLINE void VP8LFillBitWindow(VP8LBitReader* const br) { + if (br->bit_pos >= VP8L_WBITS) VP8LDoFillBitWindow(br); +} + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif // WEBP_UTILS_BIT_READER_UTILS_H_ diff --git a/packages/core/src/zig/vendor/libwebp/src/utils/color_cache_utils.c b/packages/core/src/zig/vendor/libwebp/src/utils/color_cache_utils.c new file mode 100644 index 0000000000..cd8be1f73a --- /dev/null +++ b/packages/core/src/zig/vendor/libwebp/src/utils/color_cache_utils.c @@ -0,0 +1,51 @@ +// Copyright 2012 Google Inc. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the COPYING file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +// ----------------------------------------------------------------------------- +// +// Color Cache for WebP Lossless +// +// Author: Jyrki Alakuijala (jyrki@google.com) + +#include +#include +#include + +#include "src/utils/color_cache_utils.h" +#include "src/webp/types.h" +#include "src/utils/utils.h" + +//------------------------------------------------------------------------------ +// VP8LColorCache. + +int VP8LColorCacheInit(VP8LColorCache* const color_cache, int hash_bits) { + const int hash_size = 1 << hash_bits; + assert(color_cache != NULL); + assert(hash_bits > 0); + color_cache->colors = (uint32_t*)WebPSafeCalloc( + (uint64_t)hash_size, sizeof(*color_cache->colors)); + if (color_cache->colors == NULL) return 0; + color_cache->hash_shift = 32 - hash_bits; + color_cache->hash_bits = hash_bits; + return 1; +} + +void VP8LColorCacheClear(VP8LColorCache* const color_cache) { + if (color_cache != NULL) { + WebPSafeFree(color_cache->colors); + color_cache->colors = NULL; + } +} + +void VP8LColorCacheCopy(const VP8LColorCache* const src, + VP8LColorCache* const dst) { + assert(src != NULL); + assert(dst != NULL); + assert(src->hash_bits == dst->hash_bits); + memcpy(dst->colors, src->colors, + ((size_t)1u << dst->hash_bits) * sizeof(*dst->colors)); +} diff --git a/packages/core/src/zig/vendor/libwebp/src/utils/color_cache_utils.h b/packages/core/src/zig/vendor/libwebp/src/utils/color_cache_utils.h new file mode 100644 index 0000000000..ac8d981548 --- /dev/null +++ b/packages/core/src/zig/vendor/libwebp/src/utils/color_cache_utils.h @@ -0,0 +1,90 @@ +// Copyright 2012 Google Inc. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the COPYING file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +// ----------------------------------------------------------------------------- +// +// Color Cache for WebP Lossless +// +// Authors: Jyrki Alakuijala (jyrki@google.com) +// Urvang Joshi (urvang@google.com) + +#ifndef WEBP_UTILS_COLOR_CACHE_UTILS_H_ +#define WEBP_UTILS_COLOR_CACHE_UTILS_H_ + +#include + +#include "src/dsp/cpu.h" +#include "src/dsp/dsp.h" +#include "src/webp/types.h" + +#ifdef __cplusplus +extern "C" { +#endif + +// Main color cache struct. +typedef struct { + uint32_t* colors; // color entries + int hash_shift; // Hash shift: 32 - 'hash_bits'. + int hash_bits; +} VP8LColorCache; + +static const uint32_t kHashMul = 0x1e35a7bdu; + +static WEBP_UBSAN_IGNORE_UNSIGNED_OVERFLOW WEBP_INLINE +int VP8LHashPix(uint32_t argb, int shift) { + return (int)((argb * kHashMul) >> shift); +} + +static WEBP_INLINE uint32_t VP8LColorCacheLookup( + const VP8LColorCache* const cc, uint32_t key) { + assert((key >> cc->hash_bits) == 0u); + return cc->colors[key]; +} + +static WEBP_INLINE void VP8LColorCacheSet(const VP8LColorCache* const cc, + uint32_t key, uint32_t argb) { + assert((key >> cc->hash_bits) == 0u); + cc->colors[key] = argb; +} + +static WEBP_INLINE void VP8LColorCacheInsert(const VP8LColorCache* const cc, + uint32_t argb) { + const int key = VP8LHashPix(argb, cc->hash_shift); + cc->colors[key] = argb; +} + +static WEBP_INLINE int VP8LColorCacheGetIndex(const VP8LColorCache* const cc, + uint32_t argb) { + return VP8LHashPix(argb, cc->hash_shift); +} + +// Return the key if cc contains argb, and -1 otherwise. +static WEBP_INLINE int VP8LColorCacheContains(const VP8LColorCache* const cc, + uint32_t argb) { + const int key = VP8LHashPix(argb, cc->hash_shift); + return (cc->colors[key] == argb) ? key : -1; +} + +//------------------------------------------------------------------------------ + +// Initializes the color cache with 'hash_bits' bits for the keys. +// Returns false in case of memory error. +int VP8LColorCacheInit(VP8LColorCache* const color_cache, int hash_bits); + +void VP8LColorCacheCopy(const VP8LColorCache* const src, + VP8LColorCache* const dst); + +// Delete the memory associated to color cache. +void VP8LColorCacheClear(VP8LColorCache* const color_cache); + +//------------------------------------------------------------------------------ + +#ifdef __cplusplus +} +#endif + +#endif // WEBP_UTILS_COLOR_CACHE_UTILS_H_ diff --git a/packages/core/src/zig/vendor/libwebp/src/utils/endian_inl_utils.h b/packages/core/src/zig/vendor/libwebp/src/utils/endian_inl_utils.h new file mode 100644 index 0000000000..3630a293bf --- /dev/null +++ b/packages/core/src/zig/vendor/libwebp/src/utils/endian_inl_utils.h @@ -0,0 +1,93 @@ +// Copyright 2014 Google Inc. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the COPYING file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +// ----------------------------------------------------------------------------- +// +// Endian related functions. + +#ifndef WEBP_UTILS_ENDIAN_INL_UTILS_H_ +#define WEBP_UTILS_ENDIAN_INL_UTILS_H_ + +#ifdef HAVE_CONFIG_H +#include "src/webp/config.h" +#endif + +#include "src/dsp/dsp.h" +#include "src/webp/types.h" + +#if defined(WORDS_BIGENDIAN) +#define HToLE32 BSwap32 +#define HToLE16 BSwap16 +#else +#define HToLE32(x) (x) +#define HToLE16(x) (x) +#endif + +#if !defined(HAVE_CONFIG_H) +#if LOCAL_GCC_PREREQ(4,8) || __has_builtin(__builtin_bswap16) +#define HAVE_BUILTIN_BSWAP16 +#endif +#if LOCAL_GCC_PREREQ(4,3) || __has_builtin(__builtin_bswap32) +#define HAVE_BUILTIN_BSWAP32 +#endif +#if LOCAL_GCC_PREREQ(4,3) || __has_builtin(__builtin_bswap64) +#define HAVE_BUILTIN_BSWAP64 +#endif +#endif // !HAVE_CONFIG_H + +static WEBP_INLINE uint16_t BSwap16(uint16_t x) { +#if defined(HAVE_BUILTIN_BSWAP16) + return __builtin_bswap16(x); +#elif defined(_MSC_VER) + return _byteswap_ushort(x); +#else + // gcc will recognize a 'rorw $8, ...' here: + return (x >> 8) | ((x & 0xff) << 8); +#endif // HAVE_BUILTIN_BSWAP16 +} + +static WEBP_INLINE uint32_t BSwap32(uint32_t x) { +#if defined(WEBP_USE_MIPS32_R2) + uint32_t ret; + __asm__ volatile ( + "wsbh %[ret], %[x] \n\t" + "rotr %[ret], %[ret], 16 \n\t" + : [ret]"=r"(ret) + : [x]"r"(x) + ); + return ret; +#elif defined(HAVE_BUILTIN_BSWAP32) + return __builtin_bswap32(x); +#elif defined(__i386__) || defined(__x86_64__) + uint32_t swapped_bytes; + __asm__ volatile("bswap %0" : "=r"(swapped_bytes) : "0"(x)); + return swapped_bytes; +#elif defined(_MSC_VER) + return (uint32_t)_byteswap_ulong(x); +#else + return (x >> 24) | ((x >> 8) & 0xff00) | ((x << 8) & 0xff0000) | (x << 24); +#endif // HAVE_BUILTIN_BSWAP32 +} + +static WEBP_INLINE uint64_t BSwap64(uint64_t x) { +#if defined(HAVE_BUILTIN_BSWAP64) + return __builtin_bswap64(x); +#elif defined(__x86_64__) + uint64_t swapped_bytes; + __asm__ volatile("bswapq %0" : "=r"(swapped_bytes) : "0"(x)); + return swapped_bytes; +#elif defined(_MSC_VER) + return (uint64_t)_byteswap_uint64(x); +#else // generic code for swapping 64-bit values (suggested by bdb@) + x = ((x & 0xffffffff00000000ull) >> 32) | ((x & 0x00000000ffffffffull) << 32); + x = ((x & 0xffff0000ffff0000ull) >> 16) | ((x & 0x0000ffff0000ffffull) << 16); + x = ((x & 0xff00ff00ff00ff00ull) >> 8) | ((x & 0x00ff00ff00ff00ffull) << 8); + return x; +#endif // HAVE_BUILTIN_BSWAP64 +} + +#endif // WEBP_UTILS_ENDIAN_INL_UTILS_H_ diff --git a/packages/core/src/zig/vendor/libwebp/src/utils/filters_utils.c b/packages/core/src/zig/vendor/libwebp/src/utils/filters_utils.c new file mode 100644 index 0000000000..9286d3715a --- /dev/null +++ b/packages/core/src/zig/vendor/libwebp/src/utils/filters_utils.c @@ -0,0 +1,79 @@ +// Copyright 2011 Google Inc. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the COPYING file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +// ----------------------------------------------------------------------------- +// +// filter estimation +// +// Author: Urvang (urvang@google.com) + +#include +#include + +#include "src/dsp/dsp.h" +#include "src/webp/types.h" +#include "src/utils/filters_utils.h" + +// ----------------------------------------------------------------------------- +// Quick estimate of a potentially interesting filter mode to try. + +#define SMAX 16 +#define SDIFF(a, b) (abs((a) - (b)) >> 4) // Scoring diff, in [0..SMAX) + +static WEBP_INLINE int GradientPredictor(uint8_t a, uint8_t b, uint8_t c) { + const int g = a + b - c; + return ((g & ~0xff) == 0) ? g : (g < 0) ? 0 : 255; // clip to 8bit +} + +WEBP_FILTER_TYPE WebPEstimateBestFilter(const uint8_t* data, + int width, int height, int stride) { + int i, j; + int bins[WEBP_FILTER_LAST][SMAX]; + memset(bins, 0, sizeof(bins)); + + // We only sample every other pixels. That's enough. + for (j = 2; j < height - 1; j += 2) { + const uint8_t* const p = data + j * stride; + int mean = p[0]; + for (i = 2; i < width - 1; i += 2) { + const int diff0 = SDIFF(p[i], mean); + const int diff1 = SDIFF(p[i], p[i - 1]); + const int diff2 = SDIFF(p[i], p[i - width]); + const int grad_pred = + GradientPredictor(p[i - 1], p[i - width], p[i - width - 1]); + const int diff3 = SDIFF(p[i], grad_pred); + bins[WEBP_FILTER_NONE][diff0] = 1; + bins[WEBP_FILTER_HORIZONTAL][diff1] = 1; + bins[WEBP_FILTER_VERTICAL][diff2] = 1; + bins[WEBP_FILTER_GRADIENT][diff3] = 1; + mean = (3 * mean + p[i] + 2) >> 2; + } + } + { + int filter; + WEBP_FILTER_TYPE best_filter = WEBP_FILTER_NONE; + int best_score = 0x7fffffff; + for (filter = WEBP_FILTER_NONE; filter < WEBP_FILTER_LAST; ++filter) { + int score = 0; + for (i = 0; i < SMAX; ++i) { + if (bins[filter][i] > 0) { + score += i; + } + } + if (score < best_score) { + best_score = score; + best_filter = (WEBP_FILTER_TYPE)filter; + } + } + return best_filter; + } +} + +#undef SMAX +#undef SDIFF + +//------------------------------------------------------------------------------ diff --git a/packages/core/src/zig/vendor/libwebp/src/utils/filters_utils.h b/packages/core/src/zig/vendor/libwebp/src/utils/filters_utils.h new file mode 100644 index 0000000000..8e9418df21 --- /dev/null +++ b/packages/core/src/zig/vendor/libwebp/src/utils/filters_utils.h @@ -0,0 +1,32 @@ +// Copyright 2011 Google Inc. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the COPYING file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +// ----------------------------------------------------------------------------- +// +// Spatial prediction using various filters +// +// Author: Urvang (urvang@google.com) + +#ifndef WEBP_UTILS_FILTERS_UTILS_H_ +#define WEBP_UTILS_FILTERS_UTILS_H_ + +#include "src/dsp/dsp.h" +#include "src/webp/types.h" + +#ifdef __cplusplus +extern "C" { +#endif + +// Fast estimate of a potentially good filter. +WEBP_FILTER_TYPE WebPEstimateBestFilter(const uint8_t* data, + int width, int height, int stride); + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif // WEBP_UTILS_FILTERS_UTILS_H_ diff --git a/packages/core/src/zig/vendor/libwebp/src/utils/huffman_utils.c b/packages/core/src/zig/vendor/libwebp/src/utils/huffman_utils.c new file mode 100644 index 0000000000..b3f93a0f05 --- /dev/null +++ b/packages/core/src/zig/vendor/libwebp/src/utils/huffman_utils.c @@ -0,0 +1,301 @@ +// Copyright 2012 Google Inc. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the COPYING file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +// ----------------------------------------------------------------------------- +// +// Utilities for building and looking up Huffman trees. +// +// Author: Urvang Joshi (urvang@google.com) + +#include +#include +#include + +#include "src/utils/huffman_utils.h" +#include "src/utils/utils.h" +#include "src/webp/format_constants.h" +#include "src/webp/types.h" + +// Huffman data read via DecodeImageStream is represented in two (red and green) +// bytes. +#define MAX_HTREE_GROUPS 0x10000 + +HTreeGroup* VP8LHtreeGroupsNew(int num_htree_groups) { + HTreeGroup* const htree_groups = + (HTreeGroup*)WebPSafeMalloc(num_htree_groups, sizeof(*htree_groups)); + if (htree_groups == NULL) { + return NULL; + } + assert(num_htree_groups <= MAX_HTREE_GROUPS); + return htree_groups; +} + +void VP8LHtreeGroupsFree(HTreeGroup* const htree_groups) { + if (htree_groups != NULL) { + WebPSafeFree(htree_groups); + } +} + +// Returns reverse(reverse(key, len) + 1, len), where reverse(key, len) is the +// bit-wise reversal of the len least significant bits of key. +static WEBP_INLINE uint32_t GetNextKey(uint32_t key, int len) { + uint32_t step = 1 << (len - 1); + while (key & step) { + step >>= 1; + } + return step ? (key & (step - 1)) + step : key; +} + +// Stores code in table[0], table[step], table[2*step], ..., table[end]. +// Assumes that end is an integer multiple of step. +static WEBP_INLINE void ReplicateValue(HuffmanCode* table, + int step, int end, + HuffmanCode code) { + assert(end % step == 0); + do { + end -= step; + table[end] = code; + } while (end > 0); +} + +// Returns the table width of the next 2nd level table. count is the histogram +// of bit lengths for the remaining symbols, len is the code length of the next +// processed symbol +static WEBP_INLINE int NextTableBitSize(const int* const count, + int len, int root_bits) { + int left = 1 << (len - root_bits); + while (len < MAX_ALLOWED_CODE_LENGTH) { + left -= count[len]; + if (left <= 0) break; + ++len; + left <<= 1; + } + return len - root_bits; +} + +// sorted[code_lengths_size] is a pre-allocated array for sorting symbols +// by code length. +static int BuildHuffmanTable(HuffmanCode* const root_table, int root_bits, + const int code_lengths[], int code_lengths_size, + uint16_t sorted[]) { + HuffmanCode* table = root_table; // next available space in table + int total_size = 1 << root_bits; // total size root table + 2nd level table + int len; // current code length + int symbol; // symbol index in original or sorted table + // number of codes of each length: + int count[MAX_ALLOWED_CODE_LENGTH + 1] = { 0 }; + // offsets in sorted table for each length: + int offset[MAX_ALLOWED_CODE_LENGTH + 1]; + + assert(code_lengths_size != 0); + assert(code_lengths != NULL); + assert((root_table != NULL && sorted != NULL) || + (root_table == NULL && sorted == NULL)); + assert(root_bits > 0); + + // Build histogram of code lengths. + for (symbol = 0; symbol < code_lengths_size; ++symbol) { + if (code_lengths[symbol] > MAX_ALLOWED_CODE_LENGTH) { + return 0; + } + ++count[code_lengths[symbol]]; + } + + // Error, all code lengths are zeros. + if (count[0] == code_lengths_size) { + return 0; + } + + // Generate offsets into sorted symbol table by code length. + offset[1] = 0; + for (len = 1; len < MAX_ALLOWED_CODE_LENGTH; ++len) { + if (count[len] > (1 << len)) { + return 0; + } + offset[len + 1] = offset[len] + count[len]; + } + + // Sort symbols by length, by symbol order within each length. + for (symbol = 0; symbol < code_lengths_size; ++symbol) { + const int symbol_code_length = code_lengths[symbol]; + if (code_lengths[symbol] > 0) { + if (sorted != NULL) { + if(offset[symbol_code_length] >= code_lengths_size) { + return 0; + } + sorted[offset[symbol_code_length]++] = symbol; + } else { + offset[symbol_code_length]++; + } + } + } + + // Special case code with only one value. + if (offset[MAX_ALLOWED_CODE_LENGTH] == 1) { + if (sorted != NULL) { + HuffmanCode code; + code.bits = 0; + code.value = (uint16_t)sorted[0]; + ReplicateValue(table, 1, total_size, code); + } + return total_size; + } + + { + int step; // step size to replicate values in current table + uint32_t low = 0xffffffffu; // low bits for current root entry + uint32_t mask = total_size - 1; // mask for low bits + uint32_t key = 0; // reversed prefix code + int num_nodes = 1; // number of Huffman tree nodes + int num_open = 1; // number of open branches in current tree level + int table_bits = root_bits; // key length of current table + int table_size = 1 << table_bits; // size of current table + symbol = 0; + // Fill in root table. + for (len = 1, step = 2; len <= root_bits; ++len, step <<= 1) { + num_open <<= 1; + num_nodes += num_open; + num_open -= count[len]; + if (num_open < 0) { + return 0; + } + if (root_table == NULL) continue; + for (; count[len] > 0; --count[len]) { + HuffmanCode code; + code.bits = (uint8_t)len; + code.value = (uint16_t)sorted[symbol++]; + ReplicateValue(&table[key], step, table_size, code); + key = GetNextKey(key, len); + } + } + + // Fill in 2nd level tables and add pointers to root table. + for (len = root_bits + 1, step = 2; len <= MAX_ALLOWED_CODE_LENGTH; + ++len, step <<= 1) { + num_open <<= 1; + num_nodes += num_open; + num_open -= count[len]; + if (num_open < 0) { + return 0; + } + for (; count[len] > 0; --count[len]) { + HuffmanCode code; + if ((key & mask) != low) { + if (root_table != NULL) table += table_size; + table_bits = NextTableBitSize(count, len, root_bits); + table_size = 1 << table_bits; + total_size += table_size; + low = key & mask; + if (root_table != NULL) { + root_table[low].bits = (uint8_t)(table_bits + root_bits); + root_table[low].value = (uint16_t)((table - root_table) - low); + } + } + if (root_table != NULL) { + code.bits = (uint8_t)(len - root_bits); + code.value = (uint16_t)sorted[symbol++]; + ReplicateValue(&table[key >> root_bits], step, table_size, code); + } + key = GetNextKey(key, len); + } + } + + // Check if tree is full. + if (num_nodes != 2 * offset[MAX_ALLOWED_CODE_LENGTH] - 1) { + return 0; + } + } + + return total_size; +} + +// Maximum code_lengths_size is 2328 (reached for 11-bit color_cache_bits). +// More commonly, the value is around ~280. +#define MAX_CODE_LENGTHS_SIZE \ + ((1 << MAX_CACHE_BITS) + NUM_LITERAL_CODES + NUM_LENGTH_CODES) +// Cut-off value for switching between heap and stack allocation. +#define SORTED_SIZE_CUTOFF 512 +int VP8LBuildHuffmanTable(HuffmanTables* const root_table, int root_bits, + const int code_lengths[], int code_lengths_size) { + const int total_size = + BuildHuffmanTable(NULL, root_bits, code_lengths, code_lengths_size, NULL); + assert(code_lengths_size <= MAX_CODE_LENGTHS_SIZE); + if (total_size == 0 || root_table == NULL) return total_size; + + if (root_table->curr_segment->curr_table + total_size >= + root_table->curr_segment->start + root_table->curr_segment->size) { + // If 'root_table' does not have enough memory, allocate a new segment. + // The available part of root_table->curr_segment is left unused because we + // need a contiguous buffer. + const int segment_size = root_table->curr_segment->size; + struct HuffmanTablesSegment* next = + (HuffmanTablesSegment*)WebPSafeMalloc(1, sizeof(*next)); + if (next == NULL) return 0; + // Fill the new segment. + // We need at least 'total_size' but if that value is small, it is better to + // allocate a big chunk to prevent more allocations later. 'segment_size' is + // therefore chosen (any other arbitrary value could be chosen). + next->size = total_size > segment_size ? total_size : segment_size; + next->start = + (HuffmanCode*)WebPSafeMalloc(next->size, sizeof(*next->start)); + if (next->start == NULL) { + WebPSafeFree(next); + return 0; + } + next->curr_table = next->start; + next->next = NULL; + // Point to the new segment. + root_table->curr_segment->next = next; + root_table->curr_segment = next; + } + if (code_lengths_size <= SORTED_SIZE_CUTOFF) { + // use local stack-allocated array. + uint16_t sorted[SORTED_SIZE_CUTOFF]; + BuildHuffmanTable(root_table->curr_segment->curr_table, root_bits, + code_lengths, code_lengths_size, sorted); + } else { // rare case. Use heap allocation. + uint16_t* const sorted = + (uint16_t*)WebPSafeMalloc(code_lengths_size, sizeof(*sorted)); + if (sorted == NULL) return 0; + BuildHuffmanTable(root_table->curr_segment->curr_table, root_bits, + code_lengths, code_lengths_size, sorted); + WebPSafeFree(sorted); + } + return total_size; +} + +int VP8LHuffmanTablesAllocate(int size, HuffmanTables* huffman_tables) { + // Have 'segment' point to the first segment for now, 'root'. + HuffmanTablesSegment* const root = &huffman_tables->root; + huffman_tables->curr_segment = root; + root->next = NULL; + // Allocate root. + root->start = (HuffmanCode*)WebPSafeMalloc(size, sizeof(*root->start)); + if (root->start == NULL) return 0; + root->curr_table = root->start; + root->size = size; + return 1; +} + +void VP8LHuffmanTablesDeallocate(HuffmanTables* const huffman_tables) { + HuffmanTablesSegment *current, *next; + if (huffman_tables == NULL) return; + // Free the root node. + current = &huffman_tables->root; + next = current->next; + WebPSafeFree(current->start); + current->start = NULL; + current->next = NULL; + current = next; + // Free the following nodes. + while (current != NULL) { + next = current->next; + WebPSafeFree(current->start); + WebPSafeFree(current); + current = next; + } +} diff --git a/packages/core/src/zig/vendor/libwebp/src/utils/huffman_utils.h b/packages/core/src/zig/vendor/libwebp/src/utils/huffman_utils.h new file mode 100644 index 0000000000..5e19a7e2c2 --- /dev/null +++ b/packages/core/src/zig/vendor/libwebp/src/utils/huffman_utils.h @@ -0,0 +1,115 @@ +// Copyright 2012 Google Inc. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the COPYING file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +// ----------------------------------------------------------------------------- +// +// Utilities for building and looking up Huffman trees. +// +// Author: Urvang Joshi (urvang@google.com) + +#ifndef WEBP_UTILS_HUFFMAN_UTILS_H_ +#define WEBP_UTILS_HUFFMAN_UTILS_H_ + +#include + +#include "src/webp/format_constants.h" +#include "src/webp/types.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#define HUFFMAN_TABLE_BITS 8 +#define HUFFMAN_TABLE_MASK ((1 << HUFFMAN_TABLE_BITS) - 1) + +#define LENGTHS_TABLE_BITS 7 +#define LENGTHS_TABLE_MASK ((1 << LENGTHS_TABLE_BITS) - 1) + + +// Huffman lookup table entry +typedef struct { + uint8_t bits; // number of bits used for this symbol + uint16_t value; // symbol value or table offset +} HuffmanCode; + +// long version for holding 32b values +typedef struct { + int bits; // number of bits used for this symbol, + // or an impossible value if not a literal code. + uint32_t value; // 32b packed ARGB value if literal, + // or non-literal symbol otherwise +} HuffmanCode32; + +// Contiguous memory segment of HuffmanCodes. +typedef struct HuffmanTablesSegment { + HuffmanCode* start; + // Pointer to where we are writing into the segment. Starts at 'start' and + // cannot go beyond 'start' + 'size'. + HuffmanCode* curr_table; + // Pointer to the next segment in the chain. + struct HuffmanTablesSegment* next; + int size; +} HuffmanTablesSegment; + +// Chained memory segments of HuffmanCodes. +typedef struct HuffmanTables { + HuffmanTablesSegment root; + // Currently processed segment. At first, this is 'root'. + HuffmanTablesSegment* curr_segment; +} HuffmanTables; + +// Allocates a HuffmanTables with 'size' contiguous HuffmanCodes. Returns 0 on +// memory allocation error, 1 otherwise. +WEBP_NODISCARD int VP8LHuffmanTablesAllocate(int size, + HuffmanTables* huffman_tables); +void VP8LHuffmanTablesDeallocate(HuffmanTables* const huffman_tables); + +#define HUFFMAN_PACKED_BITS 6 +#define HUFFMAN_PACKED_TABLE_SIZE (1u << HUFFMAN_PACKED_BITS) + +// Huffman table group. +// Includes special handling for the following cases: +// - is_trivial_literal: one common literal base for RED/BLUE/ALPHA (not GREEN) +// - is_trivial_code: only 1 code (no bit is read from bitstream) +// - use_packed_table: few enough literal symbols, so all the bit codes +// can fit into a small look-up table packed_table[] +// The common literal base, if applicable, is stored in 'literal_arb'. +typedef struct HTreeGroup HTreeGroup; +struct HTreeGroup { + HuffmanCode* htrees[HUFFMAN_CODES_PER_META_CODE]; + int is_trivial_literal; // True, if huffman trees for Red, Blue & Alpha + // Symbols are trivial (have a single code). + uint32_t literal_arb; // If is_trivial_literal is true, this is the + // ARGB value of the pixel, with Green channel + // being set to zero. + int is_trivial_code; // true if is_trivial_literal with only one code + int use_packed_table; // use packed table below for short literal code + // table mapping input bits to a packed values, or escape case to literal code + HuffmanCode32 packed_table[HUFFMAN_PACKED_TABLE_SIZE]; +}; + +// Creates the instance of HTreeGroup with specified number of tree-groups. +WEBP_NODISCARD HTreeGroup* VP8LHtreeGroupsNew(int num_htree_groups); + +// Releases the memory allocated for HTreeGroup. +void VP8LHtreeGroupsFree(HTreeGroup* const htree_groups); + +// Builds Huffman lookup table assuming code lengths are in symbol order. +// The 'code_lengths' is pre-allocated temporary memory buffer used for creating +// the huffman table. +// Returns built table size or 0 in case of error (invalid tree or +// memory error). +WEBP_NODISCARD int VP8LBuildHuffmanTable(HuffmanTables* const root_table, + int root_bits, + const int code_lengths[], + int code_lengths_size); + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif // WEBP_UTILS_HUFFMAN_UTILS_H_ diff --git a/packages/core/src/zig/vendor/libwebp/src/utils/palette.c b/packages/core/src/zig/vendor/libwebp/src/utils/palette.c new file mode 100644 index 0000000000..6251db19d1 --- /dev/null +++ b/packages/core/src/zig/vendor/libwebp/src/utils/palette.c @@ -0,0 +1,415 @@ +// Copyright 2023 Google Inc. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the COPYING file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +// ----------------------------------------------------------------------------- +// +// Utilities for palette analysis. +// +// Author: Vincent Rabaud (vrabaud@google.com) + +#include "src/utils/palette.h" + +#include +#include +#include + +#include "src/dsp/lossless_common.h" +#include "src/utils/color_cache_utils.h" +#include "src/utils/utils.h" +#include "src/webp/encode.h" +#include "src/webp/format_constants.h" +#include "src/webp/types.h" + +// ----------------------------------------------------------------------------- + +// Palette reordering for smaller sum of deltas (and for smaller storage). + +static int PaletteCompareColorsForQsort(const void* p1, const void* p2) { + const uint32_t a = WebPMemToUint32((uint8_t*)p1); + const uint32_t b = WebPMemToUint32((uint8_t*)p2); + assert(a != b); + return (a < b) ? -1 : 1; +} + +static WEBP_INLINE uint32_t PaletteComponentDistance(uint32_t v) { + return (v <= 128) ? v : (256 - v); +} + +// Computes a value that is related to the entropy created by the +// palette entry diff. +// +// Note that the last & 0xff is a no-operation in the next statement, but +// removed by most compilers and is here only for regularity of the code. +static WEBP_INLINE uint32_t PaletteColorDistance(uint32_t col1, uint32_t col2) { + const uint32_t diff = VP8LSubPixels(col1, col2); + const int kMoreWeightForRGBThanForAlpha = 9; + uint32_t score; + score = PaletteComponentDistance((diff >> 0) & 0xff); + score += PaletteComponentDistance((diff >> 8) & 0xff); + score += PaletteComponentDistance((diff >> 16) & 0xff); + score *= kMoreWeightForRGBThanForAlpha; + score += PaletteComponentDistance((diff >> 24) & 0xff); + return score; +} + +static WEBP_INLINE void SwapColor(uint32_t* const col1, uint32_t* const col2) { + const uint32_t tmp = *col1; + *col1 = *col2; + *col2 = tmp; +} + +int SearchColorNoIdx(const uint32_t sorted[], uint32_t color, int num_colors) { + int low = 0, hi = num_colors; + if (sorted[low] == color) return low; // loop invariant: sorted[low] != color + while (1) { + const int mid = (low + hi) >> 1; + if (sorted[mid] == color) { + return mid; + } else if (sorted[mid] < color) { + low = mid; + } else { + hi = mid; + } + } + assert(0); + return 0; +} + +void PrepareMapToPalette(const uint32_t palette[], uint32_t num_colors, + uint32_t sorted[], uint32_t idx_map[]) { + uint32_t i; + memcpy(sorted, palette, num_colors * sizeof(*sorted)); + qsort(sorted, num_colors, sizeof(*sorted), PaletteCompareColorsForQsort); + for (i = 0; i < num_colors; ++i) { + idx_map[SearchColorNoIdx(sorted, palette[i], num_colors)] = i; + } +} + +//------------------------------------------------------------------------------ + +#define COLOR_HASH_SIZE (MAX_PALETTE_SIZE * 4) +#define COLOR_HASH_RIGHT_SHIFT 22 // 32 - log2(COLOR_HASH_SIZE). + +int GetColorPalette(const WebPPicture* const pic, uint32_t* const palette) { + int i; + int x, y; + int num_colors = 0; + uint8_t in_use[COLOR_HASH_SIZE] = {0}; + uint32_t colors[COLOR_HASH_SIZE] = {0}; + const uint32_t* argb = pic->argb; + const int width = pic->width; + const int height = pic->height; + uint32_t last_pix = ~argb[0]; // so we're sure that last_pix != argb[0] + assert(pic != NULL); + assert(pic->use_argb); + + for (y = 0; y < height; ++y) { + for (x = 0; x < width; ++x) { + int key; + if (argb[x] == last_pix) { + continue; + } + last_pix = argb[x]; + key = VP8LHashPix(last_pix, COLOR_HASH_RIGHT_SHIFT); + while (1) { + if (!in_use[key]) { + colors[key] = last_pix; + in_use[key] = 1; + ++num_colors; + if (num_colors > MAX_PALETTE_SIZE) { + return MAX_PALETTE_SIZE + 1; // Exact count not needed. + } + break; + } else if (colors[key] == last_pix) { + break; // The color is already there. + } else { + // Some other color sits here, so do linear conflict resolution. + ++key; + key &= (COLOR_HASH_SIZE - 1); // Key mask. + } + } + } + argb += pic->argb_stride; + } + + if (palette != NULL) { // Fill the colors into palette. + num_colors = 0; + for (i = 0; i < COLOR_HASH_SIZE; ++i) { + if (in_use[i]) { + palette[num_colors] = colors[i]; + ++num_colors; + } + } + qsort(palette, num_colors, sizeof(*palette), PaletteCompareColorsForQsort); + } + return num_colors; +} + +#undef COLOR_HASH_SIZE +#undef COLOR_HASH_RIGHT_SHIFT + +// ----------------------------------------------------------------------------- + +// The palette has been sorted by alpha. This function checks if the other +// components of the palette have a monotonic development with regards to +// position in the palette. If all have monotonic development, there is +// no benefit to re-organize them greedily. A monotonic development +// would be spotted in green-only situations (like lossy alpha) or gray-scale +// images. +static int PaletteHasNonMonotonousDeltas(const uint32_t* const palette, + int num_colors) { + uint32_t predict = 0x000000; + int i; + uint8_t sign_found = 0x00; + for (i = 0; i < num_colors; ++i) { + const uint32_t diff = VP8LSubPixels(palette[i], predict); + const uint8_t rd = (diff >> 16) & 0xff; + const uint8_t gd = (diff >> 8) & 0xff; + const uint8_t bd = (diff >> 0) & 0xff; + if (rd != 0x00) { + sign_found |= (rd < 0x80) ? 1 : 2; + } + if (gd != 0x00) { + sign_found |= (gd < 0x80) ? 8 : 16; + } + if (bd != 0x00) { + sign_found |= (bd < 0x80) ? 64 : 128; + } + predict = palette[i]; + } + return (sign_found & (sign_found << 1)) != 0; // two consequent signs. +} + +static void PaletteSortMinimizeDeltas(const uint32_t* const palette_sorted, + int num_colors, uint32_t* const palette) { + uint32_t predict = 0x00000000; + int i, k; + memcpy(palette, palette_sorted, num_colors * sizeof(*palette)); + if (!PaletteHasNonMonotonousDeltas(palette_sorted, num_colors)) return; + // Find greedily always the closest color of the predicted color to minimize + // deltas in the palette. This reduces storage needs since the + // palette is stored with delta encoding. + if (num_colors > 17) { + if (palette[0] == 0) { + --num_colors; + SwapColor(&palette[num_colors], &palette[0]); + } + } + for (i = 0; i < num_colors; ++i) { + int best_ix = i; + uint32_t best_score = ~0U; + for (k = i; k < num_colors; ++k) { + const uint32_t cur_score = PaletteColorDistance(palette[k], predict); + if (best_score > cur_score) { + best_score = cur_score; + best_ix = k; + } + } + SwapColor(&palette[best_ix], &palette[i]); + predict = palette[i]; + } +} + +// ----------------------------------------------------------------------------- +// Modified Zeng method from "A Survey on Palette Reordering +// Methods for Improving the Compression of Color-Indexed Images" by Armando J. +// Pinho and Antonio J. R. Neves. + +// Finds the biggest cooccurrence in the matrix. +static void CoOccurrenceFindMax(const uint32_t* const cooccurrence, + uint32_t num_colors, uint8_t* const c1, + uint8_t* const c2) { + // Find the index that is most frequently located adjacent to other + // (different) indexes. + uint32_t best_sum = 0u; + uint32_t i, j, best_cooccurrence; + *c1 = 0u; + for (i = 0; i < num_colors; ++i) { + uint32_t sum = 0; + for (j = 0; j < num_colors; ++j) sum += cooccurrence[i * num_colors + j]; + if (sum > best_sum) { + best_sum = sum; + *c1 = i; + } + } + // Find the index that is most frequently found adjacent to *c1. + *c2 = 0u; + best_cooccurrence = 0u; + for (i = 0; i < num_colors; ++i) { + if (cooccurrence[*c1 * num_colors + i] > best_cooccurrence) { + best_cooccurrence = cooccurrence[*c1 * num_colors + i]; + *c2 = i; + } + } + assert(*c1 != *c2); +} + +// Builds the cooccurrence matrix +static int CoOccurrenceBuild(const WebPPicture* const pic, + const uint32_t* const palette, uint32_t num_colors, + uint32_t* cooccurrence) { + uint32_t *lines, *line_top, *line_current, *line_tmp; + int x, y; + const uint32_t* src = pic->argb; + uint32_t prev_pix = ~src[0]; + uint32_t prev_idx = 0u; + uint32_t idx_map[MAX_PALETTE_SIZE] = {0}; + uint32_t palette_sorted[MAX_PALETTE_SIZE]; + lines = (uint32_t*)WebPSafeMalloc(2 * pic->width, sizeof(*lines)); + if (lines == NULL) { + return 0; + } + line_top = &lines[0]; + line_current = &lines[pic->width]; + PrepareMapToPalette(palette, num_colors, palette_sorted, idx_map); + for (y = 0; y < pic->height; ++y) { + for (x = 0; x < pic->width; ++x) { + const uint32_t pix = src[x]; + if (pix != prev_pix) { + prev_idx = idx_map[SearchColorNoIdx(palette_sorted, pix, num_colors)]; + prev_pix = pix; + } + line_current[x] = prev_idx; + // 4-connectivity is what works best as mentioned in "On the relation + // between Memon's and the modified Zeng's palette reordering methods". + if (x > 0 && prev_idx != line_current[x - 1]) { + const uint32_t left_idx = line_current[x - 1]; + ++cooccurrence[prev_idx * num_colors + left_idx]; + ++cooccurrence[left_idx * num_colors + prev_idx]; + } + if (y > 0 && prev_idx != line_top[x]) { + const uint32_t top_idx = line_top[x]; + ++cooccurrence[prev_idx * num_colors + top_idx]; + ++cooccurrence[top_idx * num_colors + prev_idx]; + } + } + line_tmp = line_top; + line_top = line_current; + line_current = line_tmp; + src += pic->argb_stride; + } + WebPSafeFree(lines); + return 1; +} + +struct Sum { + uint8_t index; + uint32_t sum; +}; + +static int PaletteSortModifiedZeng(const WebPPicture* const pic, + const uint32_t* const palette_in, + uint32_t num_colors, + uint32_t* const palette) { + uint32_t i, j, ind; + uint8_t remapping[MAX_PALETTE_SIZE]; + uint32_t* cooccurrence; + struct Sum sums[MAX_PALETTE_SIZE]; + uint32_t first, last; + uint32_t num_sums; + // TODO(vrabaud) check whether one color images should use palette or not. + if (num_colors <= 1) return 1; + // Build the co-occurrence matrix. + cooccurrence = + (uint32_t*)WebPSafeCalloc(num_colors * num_colors, sizeof(*cooccurrence)); + if (cooccurrence == NULL) { + return 0; + } + if (!CoOccurrenceBuild(pic, palette_in, num_colors, cooccurrence)) { + WebPSafeFree(cooccurrence); + return 0; + } + + // Initialize the mapping list with the two best indices. + CoOccurrenceFindMax(cooccurrence, num_colors, &remapping[0], &remapping[1]); + + // We need to append and prepend to the list of remapping. To this end, we + // actually define the next start/end of the list as indices in a vector (with + // a wrap around when the end is reached). + first = 0; + last = 1; + num_sums = num_colors - 2; // -2 because we know the first two values + if (num_sums > 0) { + // Initialize the sums with the first two remappings and find the best one + struct Sum* best_sum = &sums[0]; + best_sum->index = 0u; + best_sum->sum = 0u; + for (i = 0, j = 0; i < num_colors; ++i) { + if (i == remapping[0] || i == remapping[1]) continue; + sums[j].index = i; + sums[j].sum = cooccurrence[i * num_colors + remapping[0]] + + cooccurrence[i * num_colors + remapping[1]]; + if (sums[j].sum > best_sum->sum) best_sum = &sums[j]; + ++j; + } + + while (num_sums > 0) { + const uint8_t best_index = best_sum->index; + // Compute delta to know if we need to prepend or append the best index. + int32_t delta = 0; + const int32_t n = num_colors - num_sums; + for (ind = first, j = 0; (ind + j) % num_colors != last + 1; ++j) { + const uint16_t l_j = remapping[(ind + j) % num_colors]; + delta += (n - 1 - 2 * (int32_t)j) * + (int32_t)cooccurrence[best_index * num_colors + l_j]; + } + if (delta > 0) { + first = (first == 0) ? num_colors - 1 : first - 1; + remapping[first] = best_index; + } else { + ++last; + remapping[last] = best_index; + } + // Remove best_sum from sums. + *best_sum = sums[num_sums - 1]; + --num_sums; + // Update all the sums and find the best one. + best_sum = &sums[0]; + for (i = 0; i < num_sums; ++i) { + sums[i].sum += cooccurrence[best_index * num_colors + sums[i].index]; + if (sums[i].sum > best_sum->sum) best_sum = &sums[i]; + } + } + } + assert((last + 1) % num_colors == first); + WebPSafeFree(cooccurrence); + + // Re-map the palette. + for (i = 0; i < num_colors; ++i) { + palette[i] = palette_in[remapping[(first + i) % num_colors]]; + } + return 1; +} + +// ----------------------------------------------------------------------------- + +int PaletteSort(PaletteSorting method, const struct WebPPicture* const pic, + const uint32_t* const palette_sorted, uint32_t num_colors, + uint32_t* const palette) { + switch (method) { + case kSortedDefault: + if (palette_sorted[0] == 0 && num_colors > 17) { + memcpy(palette, palette_sorted + 1, + (num_colors - 1) * sizeof(*palette_sorted)); + palette[num_colors - 1] = 0; + } else { + memcpy(palette, palette_sorted, num_colors * sizeof(*palette)); + } + return 1; + case kMinimizeDelta: + PaletteSortMinimizeDeltas(palette_sorted, num_colors, palette); + return 1; + case kModifiedZeng: + return PaletteSortModifiedZeng(pic, palette_sorted, num_colors, palette); + case kUnusedPalette: + case kPaletteSortingNum: + break; + } + + assert(0); + return 0; +} diff --git a/packages/core/src/zig/vendor/libwebp/src/utils/palette.h b/packages/core/src/zig/vendor/libwebp/src/utils/palette.h new file mode 100644 index 0000000000..417c61fa5e --- /dev/null +++ b/packages/core/src/zig/vendor/libwebp/src/utils/palette.h @@ -0,0 +1,62 @@ +// Copyright 2023 Google Inc. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the COPYING file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +// ----------------------------------------------------------------------------- +// +// Utilities for palette analysis. +// +// Author: Vincent Rabaud (vrabaud@google.com) + +#ifndef WEBP_UTILS_PALETTE_H_ +#define WEBP_UTILS_PALETTE_H_ + +#include "src/webp/types.h" + +struct WebPPicture; + +// The different ways a palette can be sorted. +typedef enum PaletteSorting { + kSortedDefault = 0, + // Sorts by minimizing L1 deltas between consecutive colors, giving more + // weight to RGB colors. + kMinimizeDelta = 1, + // Implements the modified Zeng method from "A Survey on Palette Reordering + // Methods for Improving the Compression of Color-Indexed Images" by Armando + // J. Pinho and Antonio J. R. Neves. + kModifiedZeng = 2, + kUnusedPalette = 3, + kPaletteSortingNum = 4 +} PaletteSorting; + +// Returns the index of 'color' in the sorted palette 'sorted' of size +// 'num_colors'. +int SearchColorNoIdx(const uint32_t sorted[], uint32_t color, int num_colors); + +// Sort palette in increasing order and prepare an inverse mapping array. +void PrepareMapToPalette(const uint32_t palette[], uint32_t num_colors, + uint32_t sorted[], uint32_t idx_map[]); + +// Returns count of unique colors in 'pic', assuming pic->use_argb is true. +// If the unique color count is more than MAX_PALETTE_SIZE, returns +// MAX_PALETTE_SIZE+1. +// If 'palette' is not NULL and the number of unique colors is less than or +// equal to MAX_PALETTE_SIZE, also outputs the actual unique colors into +// 'palette' in a sorted order. Note: 'palette' is assumed to be an array +// already allocated with at least MAX_PALETTE_SIZE elements. +int GetColorPalette(const struct WebPPicture* const pic, + uint32_t* const palette); + +// Sorts the palette according to the criterion defined by 'method'. +// 'palette_sorted' is the input palette sorted lexicographically, as done in +// PrepareMapToPalette. Returns 0 on memory allocation error. +// For kSortedDefault and kMinimizeDelta methods, 0 (if present) is set as the +// last element to optimize later storage. +int PaletteSort(PaletteSorting method, const struct WebPPicture* const pic, + const uint32_t* const palette_sorted, uint32_t num_colors, + uint32_t* const palette); + +#endif // WEBP_UTILS_PALETTE_H_ diff --git a/packages/core/src/zig/vendor/libwebp/src/utils/quant_levels_dec_utils.c b/packages/core/src/zig/vendor/libwebp/src/utils/quant_levels_dec_utils.c new file mode 100644 index 0000000000..b42aa1b377 --- /dev/null +++ b/packages/core/src/zig/vendor/libwebp/src/utils/quant_levels_dec_utils.c @@ -0,0 +1,292 @@ +// Copyright 2013 Google Inc. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the COPYING file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +// ----------------------------------------------------------------------------- +// +// Implement gradient smoothing: we replace a current alpha value by its +// surrounding average if it's close enough (that is: the change will be less +// than the minimum distance between two quantized level). +// We use sliding window for computing the 2d moving average. +// +// Author: Skal (pascal.massimino@gmail.com) + +#include "src/utils/quant_levels_dec_utils.h" + +#include // for memset + +#include "src/utils/utils.h" +#include "src/webp/types.h" + +// #define USE_DITHERING // uncomment to enable ordered dithering (not vital) + +#define FIX 16 // fix-point precision for averaging +#define LFIX 2 // extra precision for look-up table +#define LUT_SIZE ((1 << (8 + LFIX)) - 1) // look-up table size + +#if defined(USE_DITHERING) + +#define DFIX 4 // extra precision for ordered dithering +#define DSIZE 4 // dithering size (must be a power of two) +// cf. https://en.wikipedia.org/wiki/Ordered_dithering +static const uint8_t kOrderedDither[DSIZE][DSIZE] = { + { 0, 8, 2, 10 }, // coefficients are in DFIX fixed-point precision + { 12, 4, 14, 6 }, + { 3, 11, 1, 9 }, + { 15, 7, 13, 5 } +}; + +#else +#define DFIX 0 +#endif + +typedef struct { + int width, height; // dimension + int stride; // stride in bytes + int row; // current input row being processed + uint8_t* src; // input pointer + uint8_t* dst; // output pointer + + int radius; // filter radius (=delay) + int scale; // normalization factor, in FIX bits precision + + void* mem; // all memory + + // various scratch buffers + uint16_t* start; + uint16_t* cur; + uint16_t* end; + uint16_t* top; + uint16_t* average; + + // input levels distribution + int num_levels; // number of quantized levels + int min, max; // min and max level values + int min_level_dist; // smallest distance between two consecutive levels + + int16_t* correction; // size = 1 + 2*LUT_SIZE -> ~4k memory +} SmoothParams; + +//------------------------------------------------------------------------------ + +#define CLIP_8b_MASK (int)(~0U << (8 + DFIX)) +static WEBP_INLINE uint8_t clip_8b(int v) { + return (!(v & CLIP_8b_MASK)) ? (uint8_t)(v >> DFIX) : (v < 0) ? 0u : 255u; +} +#undef CLIP_8b_MASK + +// vertical accumulation +static void VFilter(SmoothParams* const p) { + const uint8_t* src = p->src; + const int w = p->width; + uint16_t* const cur = p->cur; + const uint16_t* const top = p->top; + uint16_t* const out = p->end; + uint16_t sum = 0; // all arithmetic is modulo 16bit + int x; + + for (x = 0; x < w; ++x) { + uint16_t new_value; + sum += src[x]; + new_value = top[x] + sum; + out[x] = new_value - cur[x]; // vertical sum of 'r' pixels. + cur[x] = new_value; + } + // move input pointers one row down + p->top = p->cur; + p->cur += w; + if (p->cur == p->end) p->cur = p->start; // roll-over + // We replicate edges, as it's somewhat easier as a boundary condition. + // That's why we don't update the 'src' pointer on top/bottom area: + if (p->row >= 0 && p->row < p->height - 1) { + p->src += p->stride; + } +} + +// horizontal accumulation. We use mirror replication of missing pixels, as it's +// a little easier to implement (surprisingly). +static void HFilter(SmoothParams* const p) { + const uint16_t* const in = p->end; + uint16_t* const out = p->average; + const uint32_t scale = p->scale; + const int w = p->width; + const int r = p->radius; + + int x; + for (x = 0; x <= r; ++x) { // left mirroring + const uint16_t delta = in[x + r - 1] + in[r - x]; + out[x] = (delta * scale) >> FIX; + } + for (; x < w - r; ++x) { // bulk middle run + const uint16_t delta = in[x + r] - in[x - r - 1]; + out[x] = (delta * scale) >> FIX; + } + for (; x < w; ++x) { // right mirroring + const uint16_t delta = + 2 * in[w - 1] - in[2 * w - 2 - r - x] - in[x - r - 1]; + out[x] = (delta * scale) >> FIX; + } +} + +// emit one filtered output row +static void ApplyFilter(SmoothParams* const p) { + const uint16_t* const average = p->average; + const int w = p->width; + const int16_t* const correction = p->correction; +#if defined(USE_DITHERING) + const uint8_t* const dither = kOrderedDither[p->row % DSIZE]; +#endif + uint8_t* const dst = p->dst; + int x; + for (x = 0; x < w; ++x) { + const int v = dst[x]; + if (v < p->max && v > p->min) { + const int c = (v << DFIX) + correction[average[x] - (v << LFIX)]; +#if defined(USE_DITHERING) + dst[x] = clip_8b(c + dither[x % DSIZE]); +#else + dst[x] = clip_8b(c); +#endif + } + } + p->dst += p->stride; // advance output pointer +} + +//------------------------------------------------------------------------------ +// Initialize correction table + +static void InitCorrectionLUT(int16_t* const lut, int min_dist) { + // The correction curve is: + // f(x) = x for x <= threshold2 + // f(x) = 0 for x >= threshold1 + // and a linear interpolation for range x=[threshold2, threshold1] + // (along with f(-x) = -f(x) symmetry). + // Note that: threshold2 = 3/4 * threshold1 + const int threshold1 = min_dist << LFIX; + const int threshold2 = (3 * threshold1) >> 2; + const int max_threshold = threshold2 << DFIX; + const int delta = threshold1 - threshold2; + int i; + for (i = 1; i <= LUT_SIZE; ++i) { + int c = (i <= threshold2) ? (i << DFIX) + : (i < threshold1) ? max_threshold * (threshold1 - i) / delta + : 0; + c >>= LFIX; + lut[+i] = +c; + lut[-i] = -c; + } + lut[0] = 0; +} + +static void CountLevels(SmoothParams* const p) { + int i, j, last_level; + uint8_t used_levels[256] = { 0 }; + const uint8_t* data = p->src; + p->min = 255; + p->max = 0; + for (j = 0; j < p->height; ++j) { + for (i = 0; i < p->width; ++i) { + const int v = data[i]; + if (v < p->min) p->min = v; + if (v > p->max) p->max = v; + used_levels[v] = 1; + } + data += p->stride; + } + // Compute the mininum distance between two non-zero levels. + p->min_level_dist = p->max - p->min; + last_level = -1; + for (i = 0; i < 256; ++i) { + if (used_levels[i]) { + ++p->num_levels; + if (last_level >= 0) { + const int level_dist = i - last_level; + if (level_dist < p->min_level_dist) { + p->min_level_dist = level_dist; + } + } + last_level = i; + } + } +} + +// Initialize all params. +static int InitParams(uint8_t* const data, int width, int height, int stride, + int radius, SmoothParams* const p) { + const int R = 2 * radius + 1; // total size of the kernel + + const size_t size_scratch_m = (R + 1) * width * sizeof(*p->start); + const size_t size_m = width * sizeof(*p->average); + const size_t size_lut = (1 + 2 * LUT_SIZE) * sizeof(*p->correction); + const size_t total_size = size_scratch_m + size_m + size_lut; + uint8_t* mem = (uint8_t*)WebPSafeMalloc(1U, total_size); + + if (mem == NULL) return 0; + p->mem = (void*)mem; + + p->start = (uint16_t*)mem; + p->cur = p->start; + p->end = p->start + R * width; + p->top = p->end - width; + memset(p->top, 0, width * sizeof(*p->top)); + mem += size_scratch_m; + + p->average = (uint16_t*)mem; + mem += size_m; + + p->width = width; + p->height = height; + p->stride = stride; + p->src = data; + p->dst = data; + p->radius = radius; + p->scale = (1 << (FIX + LFIX)) / (R * R); // normalization constant + p->row = -radius; + + // analyze the input distribution so we can best-fit the threshold + CountLevels(p); + + // correction table + p->correction = ((int16_t*)mem) + LUT_SIZE; + InitCorrectionLUT(p->correction, p->min_level_dist); + + return 1; +} + +static void CleanupParams(SmoothParams* const p) { + WebPSafeFree(p->mem); +} + +int WebPDequantizeLevels(uint8_t* const data, int width, int height, int stride, + int strength) { + int radius = 4 * strength / 100; + + if (strength < 0 || strength > 100) return 0; + if (data == NULL || width <= 0 || height <= 0) return 0; // bad params + + // limit the filter size to not exceed the image dimensions + if (2 * radius + 1 > width) radius = (width - 1) >> 1; + if (2 * radius + 1 > height) radius = (height - 1) >> 1; + + if (radius > 0) { + SmoothParams p; + memset(&p, 0, sizeof(p)); + if (!InitParams(data, width, height, stride, radius, &p)) return 0; + if (p.num_levels > 2) { + for (; p.row < p.height; ++p.row) { + VFilter(&p); // accumulate average of input + // Need to wait few rows in order to prime the filter, + // before emitting some output. + if (p.row >= p.radius) { + HFilter(&p); + ApplyFilter(&p); + } + } + } + CleanupParams(&p); + } + return 1; +} diff --git a/packages/core/src/zig/vendor/libwebp/src/utils/quant_levels_dec_utils.h b/packages/core/src/zig/vendor/libwebp/src/utils/quant_levels_dec_utils.h new file mode 100644 index 0000000000..327f19f336 --- /dev/null +++ b/packages/core/src/zig/vendor/libwebp/src/utils/quant_levels_dec_utils.h @@ -0,0 +1,35 @@ +// Copyright 2013 Google Inc. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the COPYING file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +// ----------------------------------------------------------------------------- +// +// Alpha plane de-quantization utility +// +// Author: Vikas Arora (vikasa@google.com) + +#ifndef WEBP_UTILS_QUANT_LEVELS_DEC_UTILS_H_ +#define WEBP_UTILS_QUANT_LEVELS_DEC_UTILS_H_ + +#include "src/webp/types.h" + +#ifdef __cplusplus +extern "C" { +#endif + +// Apply post-processing to input 'data' of size 'width'x'height' assuming that +// the source was quantized to a reduced number of levels. 'stride' is in bytes. +// Strength is in [0..100] and controls the amount of dithering applied. +// Returns false in case of error (data is NULL, invalid parameters, +// malloc failure, ...). +int WebPDequantizeLevels(uint8_t* const data, int width, int height, int stride, + int strength); + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif // WEBP_UTILS_QUANT_LEVELS_DEC_UTILS_H_ diff --git a/packages/core/src/zig/vendor/libwebp/src/utils/random_utils.c b/packages/core/src/zig/vendor/libwebp/src/utils/random_utils.c new file mode 100644 index 0000000000..56380610fa --- /dev/null +++ b/packages/core/src/zig/vendor/libwebp/src/utils/random_utils.c @@ -0,0 +1,44 @@ +// Copyright 2013 Google Inc. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the COPYING file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +// ----------------------------------------------------------------------------- +// +// Pseudo-random utilities +// +// Author: Skal (pascal.massimino@gmail.com) + +#include + +#include "src/webp/types.h" +#include "src/utils/random_utils.h" + +//------------------------------------------------------------------------------ + +// 31b-range values +static const uint32_t kRandomTable[VP8_RANDOM_TABLE_SIZE] = { + 0x0de15230, 0x03b31886, 0x775faccb, 0x1c88626a, 0x68385c55, 0x14b3b828, + 0x4a85fef8, 0x49ddb84b, 0x64fcf397, 0x5c550289, 0x4a290000, 0x0d7ec1da, + 0x5940b7ab, 0x5492577d, 0x4e19ca72, 0x38d38c69, 0x0c01ee65, 0x32a1755f, + 0x5437f652, 0x5abb2c32, 0x0faa57b1, 0x73f533e7, 0x685feeda, 0x7563cce2, + 0x6e990e83, 0x4730a7ed, 0x4fc0d9c6, 0x496b153c, 0x4f1403fa, 0x541afb0c, + 0x73990b32, 0x26d7cb1c, 0x6fcc3706, 0x2cbb77d8, 0x75762f2a, 0x6425ccdd, + 0x24b35461, 0x0a7d8715, 0x220414a8, 0x141ebf67, 0x56b41583, 0x73e502e3, + 0x44cab16f, 0x28264d42, 0x73baaefb, 0x0a50ebed, 0x1d6ab6fb, 0x0d3ad40b, + 0x35db3b68, 0x2b081e83, 0x77ce6b95, 0x5181e5f0, 0x78853bbc, 0x009f9494, + 0x27e5ed3c +}; + +void VP8InitRandom(VP8Random* const rg, float dithering) { + memcpy(rg->tab, kRandomTable, sizeof(rg->tab)); + rg->index1 = 0; + rg->index2 = 31; + rg->amp = (dithering < 0.0) ? 0 + : (dithering > 1.0) ? (1 << VP8_RANDOM_DITHER_FIX) + : (uint32_t)((1 << VP8_RANDOM_DITHER_FIX) * dithering); +} + +//------------------------------------------------------------------------------ diff --git a/packages/core/src/zig/vendor/libwebp/src/utils/random_utils.h b/packages/core/src/zig/vendor/libwebp/src/utils/random_utils.h new file mode 100644 index 0000000000..2fbb200257 --- /dev/null +++ b/packages/core/src/zig/vendor/libwebp/src/utils/random_utils.h @@ -0,0 +1,64 @@ +// Copyright 2013 Google Inc. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the COPYING file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +// ----------------------------------------------------------------------------- +// +// Pseudo-random utilities +// +// Author: Skal (pascal.massimino@gmail.com) + +#ifndef WEBP_UTILS_RANDOM_UTILS_H_ +#define WEBP_UTILS_RANDOM_UTILS_H_ + +#include + +#include "src/webp/types.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#define VP8_RANDOM_DITHER_FIX 8 // fixed-point precision for dithering +#define VP8_RANDOM_TABLE_SIZE 55 + +typedef struct { + int index1, index2; + uint32_t tab[VP8_RANDOM_TABLE_SIZE]; + int amp; +} VP8Random; + +// Initializes random generator with an amplitude 'dithering' in range [0..1]. +void VP8InitRandom(VP8Random* const rg, float dithering); + +// Returns a centered pseudo-random number with 'num_bits' amplitude. +// (uses D.Knuth's Difference-based random generator). +// 'amp' is in VP8_RANDOM_DITHER_FIX fixed-point precision. +static WEBP_INLINE int VP8RandomBits2(VP8Random* const rg, int num_bits, + int amp) { + int diff; + assert(num_bits + VP8_RANDOM_DITHER_FIX <= 31); + diff = rg->tab[rg->index1] - rg->tab[rg->index2]; + if (diff < 0) diff += (1u << 31); + rg->tab[rg->index1] = diff; + if (++rg->index1 == VP8_RANDOM_TABLE_SIZE) rg->index1 = 0; + if (++rg->index2 == VP8_RANDOM_TABLE_SIZE) rg->index2 = 0; + // sign-extend, 0-center + diff = (int)((uint32_t)diff << 1) >> (32 - num_bits); + diff = (diff * amp) >> VP8_RANDOM_DITHER_FIX; // restrict range + diff += 1 << (num_bits - 1); // shift back to 0.5-center + return diff; +} + +static WEBP_INLINE int VP8RandomBits(VP8Random* const rg, int num_bits) { + return VP8RandomBits2(rg, num_bits, rg->amp); +} + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif // WEBP_UTILS_RANDOM_UTILS_H_ diff --git a/packages/core/src/zig/vendor/libwebp/src/utils/rescaler_utils.c b/packages/core/src/zig/vendor/libwebp/src/utils/rescaler_utils.c new file mode 100644 index 0000000000..32dd0af2de --- /dev/null +++ b/packages/core/src/zig/vendor/libwebp/src/utils/rescaler_utils.c @@ -0,0 +1,162 @@ +// Copyright 2012 Google Inc. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the COPYING file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +// ----------------------------------------------------------------------------- +// +// Rescaling functions +// +// Author: Skal (pascal.massimino@gmail.com) + +#include +#include +#include +#include + +#include "src/dsp/dsp.h" +#include "src/webp/types.h" +#include "src/utils/rescaler_utils.h" +#include "src/utils/utils.h" + +//------------------------------------------------------------------------------ + +int WebPRescalerInit(WebPRescaler* const rescaler, + int src_width, int src_height, + uint8_t* const dst, + int dst_width, int dst_height, int dst_stride, + int num_channels, rescaler_t* const work) { + const int x_add = src_width, x_sub = dst_width; + const int y_add = src_height, y_sub = dst_height; + const uint64_t total_size = 2ull * dst_width * num_channels * sizeof(*work); + if (!CheckSizeOverflow(total_size)) return 0; + + rescaler->x_expand = (src_width < dst_width); + rescaler->y_expand = (src_height < dst_height); + rescaler->src_width = src_width; + rescaler->src_height = src_height; + rescaler->dst_width = dst_width; + rescaler->dst_height = dst_height; + rescaler->src_y = 0; + rescaler->dst_y = 0; + rescaler->dst = dst; + rescaler->dst_stride = dst_stride; + rescaler->num_channels = num_channels; + + // for 'x_expand', we use bilinear interpolation + rescaler->x_add = rescaler->x_expand ? (x_sub - 1) : x_add; + rescaler->x_sub = rescaler->x_expand ? (x_add - 1) : x_sub; + if (!rescaler->x_expand) { // fx_scale is not used otherwise + rescaler->fx_scale = WEBP_RESCALER_FRAC(1, rescaler->x_sub); + } + // vertical scaling parameters + rescaler->y_add = rescaler->y_expand ? y_add - 1 : y_add; + rescaler->y_sub = rescaler->y_expand ? y_sub - 1 : y_sub; + rescaler->y_accum = rescaler->y_expand ? rescaler->y_sub : rescaler->y_add; + if (!rescaler->y_expand) { + // This is WEBP_RESCALER_FRAC(dst_height, x_add * y_add) without the cast. + // Its value is <= WEBP_RESCALER_ONE, because dst_height <= rescaler->y_add + // and rescaler->x_add >= 1; + const uint64_t num = (uint64_t)dst_height * WEBP_RESCALER_ONE; + const uint64_t den = (uint64_t)rescaler->x_add * rescaler->y_add; + const uint64_t ratio = num / den; + if (ratio != (uint32_t)ratio) { + // When ratio == WEBP_RESCALER_ONE, we can't represent the ratio with the + // current fixed-point precision. This happens when src_height == + // rescaler->y_add (which == src_height), and rescaler->x_add == 1. + // => We special-case fxy_scale = 0, in WebPRescalerExportRow(). + rescaler->fxy_scale = 0; + } else { + rescaler->fxy_scale = (uint32_t)ratio; + } + rescaler->fy_scale = WEBP_RESCALER_FRAC(1, rescaler->y_sub); + } else { + rescaler->fy_scale = WEBP_RESCALER_FRAC(1, rescaler->x_add); + // rescaler->fxy_scale is unused here. + } + rescaler->irow = work; + rescaler->frow = work + num_channels * dst_width; + memset(work, 0, (size_t)total_size); + + WebPRescalerDspInit(); + return 1; +} + +int WebPRescalerGetScaledDimensions(int src_width, int src_height, + int* const scaled_width, + int* const scaled_height) { + assert(scaled_width != NULL); + assert(scaled_height != NULL); + { + int width = *scaled_width; + int height = *scaled_height; + const int max_size = INT_MAX / 2; + + // if width is unspecified, scale original proportionally to height ratio. + if (width == 0 && src_height > 0) { + width = + (int)(((uint64_t)src_width * height + src_height - 1) / src_height); + } + // if height is unspecified, scale original proportionally to width ratio. + if (height == 0 && src_width > 0) { + height = + (int)(((uint64_t)src_height * width + src_width - 1) / src_width); + } + // Check if the overall dimensions still make sense. + if (width <= 0 || height <= 0 || width > max_size || height > max_size) { + return 0; + } + + *scaled_width = width; + *scaled_height = height; + return 1; + } +} + +//------------------------------------------------------------------------------ +// all-in-one calls + +int WebPRescaleNeededLines(const WebPRescaler* const rescaler, + int max_num_lines) { + const int num_lines = + (rescaler->y_accum + rescaler->y_sub - 1) / rescaler->y_sub; + return (num_lines > max_num_lines) ? max_num_lines : num_lines; +} + +int WebPRescalerImport(WebPRescaler* const rescaler, int num_lines, + const uint8_t* src, int src_stride) { + int total_imported = 0; + while (total_imported < num_lines && + !WebPRescalerHasPendingOutput(rescaler)) { + if (rescaler->y_expand) { + rescaler_t* const tmp = rescaler->irow; + rescaler->irow = rescaler->frow; + rescaler->frow = tmp; + } + WebPRescalerImportRow(rescaler, src); + if (!rescaler->y_expand) { // Accumulate the contribution of the new row. + int x; + for (x = 0; x < rescaler->num_channels * rescaler->dst_width; ++x) { + rescaler->irow[x] += rescaler->frow[x]; + } + } + ++rescaler->src_y; + src += src_stride; + ++total_imported; + rescaler->y_accum -= rescaler->y_sub; + } + return total_imported; +} + +int WebPRescalerExport(WebPRescaler* const rescaler) { + int total_exported = 0; + while (WebPRescalerHasPendingOutput(rescaler)) { + WebPRescalerExportRow(rescaler); + ++total_exported; + } + return total_exported; +} + +//------------------------------------------------------------------------------ diff --git a/packages/core/src/zig/vendor/libwebp/src/utils/rescaler_utils.h b/packages/core/src/zig/vendor/libwebp/src/utils/rescaler_utils.h new file mode 100644 index 0000000000..ef201ef86c --- /dev/null +++ b/packages/core/src/zig/vendor/libwebp/src/utils/rescaler_utils.h @@ -0,0 +1,102 @@ +// Copyright 2012 Google Inc. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the COPYING file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +// ----------------------------------------------------------------------------- +// +// Rescaling functions +// +// Author: Skal (pascal.massimino@gmail.com) + +#ifndef WEBP_UTILS_RESCALER_UTILS_H_ +#define WEBP_UTILS_RESCALER_UTILS_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +#include "src/webp/types.h" + +#define WEBP_RESCALER_RFIX 32 // fixed-point precision for multiplies +#define WEBP_RESCALER_ONE (1ull << WEBP_RESCALER_RFIX) +#define WEBP_RESCALER_FRAC(x, y) \ + ((uint32_t)(((uint64_t)(x) << WEBP_RESCALER_RFIX) / (y))) + +// Structure used for on-the-fly rescaling +typedef uint32_t rescaler_t; // type for side-buffer +typedef struct WebPRescaler WebPRescaler; +struct WebPRescaler { + int x_expand; // true if we're expanding in the x direction + int y_expand; // true if we're expanding in the y direction + int num_channels; // bytes to jump between pixels + uint32_t fx_scale; // fixed-point scaling factors + uint32_t fy_scale; // '' + uint32_t fxy_scale; // '' + int y_accum; // vertical accumulator + int y_add, y_sub; // vertical increments + int x_add, x_sub; // horizontal increments + int src_width, src_height; // source dimensions + int dst_width, dst_height; // destination dimensions + int src_y, dst_y; // row counters for input and output + uint8_t* dst; + int dst_stride; + rescaler_t* irow, *frow; // work buffer +}; + +// Initialize a rescaler given scratch area 'work' and dimensions of src & dst. +// Returns false in case of error. +int WebPRescalerInit(WebPRescaler* const rescaler, + int src_width, int src_height, + uint8_t* const dst, + int dst_width, int dst_height, int dst_stride, + int num_channels, + rescaler_t* const work); + +// If either 'scaled_width' or 'scaled_height' (but not both) is 0 the value +// will be calculated preserving the aspect ratio, otherwise the values are +// left unmodified. Returns true on success, false if either value is 0 after +// performing the scaling calculation. +int WebPRescalerGetScaledDimensions(int src_width, int src_height, + int* const scaled_width, + int* const scaled_height); + +// Returns the number of input lines needed next to produce one output line, +// considering that the maximum available input lines are 'max_num_lines'. +int WebPRescaleNeededLines(const WebPRescaler* const rescaler, + int max_num_lines); + +// Import multiple rows over all channels, until at least one row is ready to +// be exported. Returns the actual number of lines that were imported. +int WebPRescalerImport(WebPRescaler* const rescaler, int num_rows, + const uint8_t* src, int src_stride); + +// Export as many rows as possible. Return the numbers of rows written. +int WebPRescalerExport(WebPRescaler* const rescaler); + +// Return true if input is finished +static WEBP_INLINE +int WebPRescalerInputDone(const WebPRescaler* const rescaler) { + return (rescaler->src_y >= rescaler->src_height); +} +// Return true if output is finished +static WEBP_INLINE +int WebPRescalerOutputDone(const WebPRescaler* const rescaler) { + return (rescaler->dst_y >= rescaler->dst_height); +} + +// Return true if there are pending output rows ready. +static WEBP_INLINE +int WebPRescalerHasPendingOutput(const WebPRescaler* const rescaler) { + return !WebPRescalerOutputDone(rescaler) && (rescaler->y_accum <= 0); +} + +//------------------------------------------------------------------------------ + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif // WEBP_UTILS_RESCALER_UTILS_H_ diff --git a/packages/core/src/zig/vendor/libwebp/src/utils/thread_utils.c b/packages/core/src/zig/vendor/libwebp/src/utils/thread_utils.c new file mode 100644 index 0000000000..d61a0bb78b --- /dev/null +++ b/packages/core/src/zig/vendor/libwebp/src/utils/thread_utils.c @@ -0,0 +1,370 @@ +// Copyright 2011 Google Inc. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the COPYING file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +// ----------------------------------------------------------------------------- +// +// Multi-threaded worker +// +// Author: Skal (pascal.massimino@gmail.com) + +#include +#include // for memset() + +#include "src/utils/thread_utils.h" +#include "src/utils/utils.h" + +#ifdef WEBP_USE_THREAD + +#if defined(_WIN32) + +#include +typedef HANDLE pthread_t; +typedef CRITICAL_SECTION pthread_mutex_t; + +#if _WIN32_WINNT >= 0x0600 // Windows Vista / Server 2008 or greater +#define USE_WINDOWS_CONDITION_VARIABLE +typedef CONDITION_VARIABLE pthread_cond_t; +#else +typedef struct { + HANDLE waiting_sem; + HANDLE received_sem; + HANDLE signal_event; +} pthread_cond_t; +#endif // _WIN32_WINNT >= 0x600 + +#ifndef WINAPI_FAMILY_PARTITION +#define WINAPI_PARTITION_DESKTOP 1 +#define WINAPI_FAMILY_PARTITION(x) x +#endif + +#if !WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_DESKTOP) +#define USE_CREATE_THREAD +#endif + +#else // !_WIN32 + +#include + +#endif // _WIN32 + +typedef struct { + pthread_mutex_t mutex; + pthread_cond_t condition; + pthread_t thread; +} WebPWorkerImpl; + +#if defined(_WIN32) + +//------------------------------------------------------------------------------ +// simplistic pthread emulation layer + +#include + +// _beginthreadex requires __stdcall +#define THREADFN unsigned int __stdcall +#define THREAD_RETURN(val) (unsigned int)((DWORD_PTR)val) + +#if _WIN32_WINNT >= 0x0501 // Windows XP or greater +#define WaitForSingleObject(obj, timeout) \ + WaitForSingleObjectEx(obj, timeout, FALSE /*bAlertable*/) +#endif + +static int pthread_create(pthread_t* const thread, const void* attr, + unsigned int (__stdcall* start)(void*), void* arg) { + (void)attr; +#ifdef USE_CREATE_THREAD + *thread = CreateThread(NULL, /* lpThreadAttributes */ + 0, /* dwStackSize */ + start, + arg, + 0, /* dwStackSize */ + NULL); /* lpThreadId */ +#else + *thread = (pthread_t)_beginthreadex(NULL, /* void *security */ + 0, /* unsigned stack_size */ + start, + arg, + 0, /* unsigned initflag */ + NULL); /* unsigned *thrdaddr */ +#endif + if (*thread == NULL) return 1; + SetThreadPriority(*thread, THREAD_PRIORITY_ABOVE_NORMAL); + return 0; +} + +static int pthread_join(pthread_t thread, void** value_ptr) { + (void)value_ptr; + return (WaitForSingleObject(thread, INFINITE) != WAIT_OBJECT_0 || + CloseHandle(thread) == 0); +} + +// Mutex +static int pthread_mutex_init(pthread_mutex_t* const mutex, void* mutexattr) { + (void)mutexattr; +#if _WIN32_WINNT >= 0x0600 // Windows Vista / Server 2008 or greater + InitializeCriticalSectionEx(mutex, 0 /*dwSpinCount*/, 0 /*Flags*/); +#else + InitializeCriticalSection(mutex); +#endif + return 0; +} + +static int pthread_mutex_lock(pthread_mutex_t* const mutex) { + EnterCriticalSection(mutex); + return 0; +} + +static int pthread_mutex_unlock(pthread_mutex_t* const mutex) { + LeaveCriticalSection(mutex); + return 0; +} + +static int pthread_mutex_destroy(pthread_mutex_t* const mutex) { + DeleteCriticalSection(mutex); + return 0; +} + +// Condition +static int pthread_cond_destroy(pthread_cond_t* const condition) { + int ok = 1; +#ifdef USE_WINDOWS_CONDITION_VARIABLE + (void)condition; +#else + ok &= (CloseHandle(condition->waiting_sem) != 0); + ok &= (CloseHandle(condition->received_sem) != 0); + ok &= (CloseHandle(condition->signal_event) != 0); +#endif + return !ok; +} + +static int pthread_cond_init(pthread_cond_t* const condition, void* cond_attr) { + (void)cond_attr; +#ifdef USE_WINDOWS_CONDITION_VARIABLE + InitializeConditionVariable(condition); +#else + condition->waiting_sem = CreateSemaphore(NULL, 0, 1, NULL); + condition->received_sem = CreateSemaphore(NULL, 0, 1, NULL); + condition->signal_event = CreateEvent(NULL, FALSE, FALSE, NULL); + if (condition->waiting_sem == NULL || + condition->received_sem == NULL || + condition->signal_event == NULL) { + pthread_cond_destroy(condition); + return 1; + } +#endif + return 0; +} + +static int pthread_cond_signal(pthread_cond_t* const condition) { + int ok = 1; +#ifdef USE_WINDOWS_CONDITION_VARIABLE + WakeConditionVariable(condition); +#else + if (WaitForSingleObject(condition->waiting_sem, 0) == WAIT_OBJECT_0) { + // a thread is waiting in pthread_cond_wait: allow it to be notified + ok = SetEvent(condition->signal_event); + // wait until the event is consumed so the signaler cannot consume + // the event via its own pthread_cond_wait. + ok &= (WaitForSingleObject(condition->received_sem, INFINITE) != + WAIT_OBJECT_0); + } +#endif + return !ok; +} + +static int pthread_cond_wait(pthread_cond_t* const condition, + pthread_mutex_t* const mutex) { + int ok; +#ifdef USE_WINDOWS_CONDITION_VARIABLE + ok = SleepConditionVariableCS(condition, mutex, INFINITE); +#else + // note that there is a consumer available so the signal isn't dropped in + // pthread_cond_signal + if (!ReleaseSemaphore(condition->waiting_sem, 1, NULL)) return 1; + // now unlock the mutex so pthread_cond_signal may be issued + pthread_mutex_unlock(mutex); + ok = (WaitForSingleObject(condition->signal_event, INFINITE) == + WAIT_OBJECT_0); + ok &= ReleaseSemaphore(condition->received_sem, 1, NULL); + pthread_mutex_lock(mutex); +#endif + return !ok; +} + +#else // !_WIN32 +# define THREADFN void* +# define THREAD_RETURN(val) val +#endif // _WIN32 + +//------------------------------------------------------------------------------ + +static THREADFN ThreadLoop(void* ptr) { + WebPWorker* const worker = (WebPWorker*)ptr; + WebPWorkerImpl* const impl = (WebPWorkerImpl*)worker->impl; + int done = 0; + while (!done) { + pthread_mutex_lock(&impl->mutex); + while (worker->status == OK) { // wait in idling mode + pthread_cond_wait(&impl->condition, &impl->mutex); + } + if (worker->status == WORK) { + WebPGetWorkerInterface()->Execute(worker); + worker->status = OK; + } else if (worker->status == NOT_OK) { // finish the worker + done = 1; + } + // signal to the main thread that we're done (for Sync()) + // Note the associated mutex does not need to be held when signaling the + // condition. Unlocking the mutex first may improve performance in some + // implementations, avoiding the case where the waiting thread can't + // reacquire the mutex when woken. + pthread_mutex_unlock(&impl->mutex); + pthread_cond_signal(&impl->condition); + } + return THREAD_RETURN(NULL); // Thread is finished +} + +// main thread state control +static void ChangeState(WebPWorker* const worker, WebPWorkerStatus new_status) { + // No-op when attempting to change state on a thread that didn't come up. + // Checking 'status' without acquiring the lock first would result in a data + // race. + WebPWorkerImpl* const impl = (WebPWorkerImpl*)worker->impl; + if (impl == NULL) return; + + pthread_mutex_lock(&impl->mutex); + if (worker->status >= OK) { + // wait for the worker to finish + while (worker->status != OK) { + pthread_cond_wait(&impl->condition, &impl->mutex); + } + // assign new status and release the working thread if needed + if (new_status != OK) { + worker->status = new_status; + // Note the associated mutex does not need to be held when signaling the + // condition. Unlocking the mutex first may improve performance in some + // implementations, avoiding the case where the waiting thread can't + // reacquire the mutex when woken. + pthread_mutex_unlock(&impl->mutex); + pthread_cond_signal(&impl->condition); + return; + } + } + pthread_mutex_unlock(&impl->mutex); +} + +#endif // WEBP_USE_THREAD + +//------------------------------------------------------------------------------ + +static void Init(WebPWorker* const worker) { + memset(worker, 0, sizeof(*worker)); + worker->status = NOT_OK; +} + +static int Sync(WebPWorker* const worker) { +#ifdef WEBP_USE_THREAD + ChangeState(worker, OK); +#endif + assert(worker->status <= OK); + return !worker->had_error; +} + +static int Reset(WebPWorker* const worker) { + int ok = 1; + worker->had_error = 0; + if (worker->status < OK) { +#ifdef WEBP_USE_THREAD + WebPWorkerImpl* const impl = + (WebPWorkerImpl*)WebPSafeCalloc(1, sizeof(WebPWorkerImpl)); + worker->impl = (void*)impl; + if (worker->impl == NULL) { + return 0; + } + if (pthread_mutex_init(&impl->mutex, NULL)) { + goto Error; + } + if (pthread_cond_init(&impl->condition, NULL)) { + pthread_mutex_destroy(&impl->mutex); + goto Error; + } + pthread_mutex_lock(&impl->mutex); + ok = !pthread_create(&impl->thread, NULL, ThreadLoop, worker); + if (ok) worker->status = OK; + pthread_mutex_unlock(&impl->mutex); + if (!ok) { + pthread_mutex_destroy(&impl->mutex); + pthread_cond_destroy(&impl->condition); + Error: + WebPSafeFree(impl); + worker->impl = NULL; + return 0; + } +#else + worker->status = OK; +#endif + } else if (worker->status > OK) { + ok = Sync(worker); + } + assert(!ok || (worker->status == OK)); + return ok; +} + +static void Execute(WebPWorker* const worker) { + if (worker->hook != NULL) { + worker->had_error |= !worker->hook(worker->data1, worker->data2); + } +} + +static void Launch(WebPWorker* const worker) { +#ifdef WEBP_USE_THREAD + ChangeState(worker, WORK); +#else + Execute(worker); +#endif +} + +static void End(WebPWorker* const worker) { +#ifdef WEBP_USE_THREAD + if (worker->impl != NULL) { + WebPWorkerImpl* const impl = (WebPWorkerImpl*)worker->impl; + ChangeState(worker, NOT_OK); + pthread_join(impl->thread, NULL); + pthread_mutex_destroy(&impl->mutex); + pthread_cond_destroy(&impl->condition); + WebPSafeFree(impl); + worker->impl = NULL; + } +#else + worker->status = NOT_OK; + assert(worker->impl == NULL); +#endif + assert(worker->status == NOT_OK); +} + +//------------------------------------------------------------------------------ + +static WebPWorkerInterface g_worker_interface = { + Init, Reset, Sync, Launch, Execute, End +}; + +int WebPSetWorkerInterface(const WebPWorkerInterface* const winterface) { + if (winterface == NULL || + winterface->Init == NULL || winterface->Reset == NULL || + winterface->Sync == NULL || winterface->Launch == NULL || + winterface->Execute == NULL || winterface->End == NULL) { + return 0; + } + g_worker_interface = *winterface; + return 1; +} + +const WebPWorkerInterface* WebPGetWorkerInterface(void) { + return &g_worker_interface; +} + +//------------------------------------------------------------------------------ diff --git a/packages/core/src/zig/vendor/libwebp/src/utils/thread_utils.h b/packages/core/src/zig/vendor/libwebp/src/utils/thread_utils.h new file mode 100644 index 0000000000..3575815d98 --- /dev/null +++ b/packages/core/src/zig/vendor/libwebp/src/utils/thread_utils.h @@ -0,0 +1,90 @@ +// Copyright 2011 Google Inc. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the COPYING file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +// ----------------------------------------------------------------------------- +// +// Multi-threaded worker +// +// Author: Skal (pascal.massimino@gmail.com) + +#ifndef WEBP_UTILS_THREAD_UTILS_H_ +#define WEBP_UTILS_THREAD_UTILS_H_ + +#ifdef HAVE_CONFIG_H +#include "src/webp/config.h" +#endif + +#include "src/webp/types.h" + +#ifdef __cplusplus +extern "C" { +#endif + +// State of the worker thread object +typedef enum { + NOT_OK = 0, // object is unusable + OK, // ready to work + WORK // busy finishing the current task +} WebPWorkerStatus; + +// Function to be called by the worker thread. Takes two opaque pointers as +// arguments (data1 and data2), and should return false in case of error. +typedef int (*WebPWorkerHook)(void*, void*); + +// Synchronization object used to launch job in the worker thread +typedef struct { + void* impl; // platform-dependent implementation worker details + WebPWorkerStatus status; + WebPWorkerHook hook; // hook to call + void* data1; // first argument passed to 'hook' + void* data2; // second argument passed to 'hook' + int had_error; // return value of the last call to 'hook' +} WebPWorker; + +// The interface for all thread-worker related functions. All these functions +// must be implemented. +typedef struct { + // Must be called first, before any other method. + void (*Init)(WebPWorker* const worker); + // Must be called to initialize the object and spawn the thread. Re-entrant. + // Will potentially launch the thread. Returns false in case of error. + int (*Reset)(WebPWorker* const worker); + // Makes sure the previous work is finished. Returns true if worker->had_error + // was not set and no error condition was triggered by the working thread. + int (*Sync)(WebPWorker* const worker); + // Triggers the thread to call hook() with data1 and data2 arguments. These + // hook/data1/data2 values can be changed at any time before calling this + // function, but not be changed afterward until the next call to Sync(). + void (*Launch)(WebPWorker* const worker); + // This function is similar to Launch() except that it calls the + // hook directly instead of using a thread. Convenient to bypass the thread + // mechanism while still using the WebPWorker structs. Sync() must + // still be called afterward (for error reporting). + void (*Execute)(WebPWorker* const worker); + // Kill the thread and terminate the object. To use the object again, one + // must call Reset() again. + void (*End)(WebPWorker* const worker); +} WebPWorkerInterface; + +// Install a new set of threading functions, overriding the defaults. This +// should be done before any workers are started, i.e., before any encoding or +// decoding takes place. The contents of the interface struct are copied, it +// is safe to free the corresponding memory after this call. This function is +// not thread-safe. Return false in case of invalid pointer or methods. +WEBP_EXTERN int WebPSetWorkerInterface( + const WebPWorkerInterface* const winterface); + +// Retrieve the currently set thread worker interface. +WEBP_EXTERN const WebPWorkerInterface* WebPGetWorkerInterface(void); + +//------------------------------------------------------------------------------ + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif // WEBP_UTILS_THREAD_UTILS_H_ diff --git a/packages/core/src/zig/vendor/libwebp/src/utils/utils.c b/packages/core/src/zig/vendor/libwebp/src/utils/utils.c new file mode 100644 index 0000000000..b80bae0125 --- /dev/null +++ b/packages/core/src/zig/vendor/libwebp/src/utils/utils.c @@ -0,0 +1,284 @@ +// Copyright 2012 Google Inc. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the COPYING file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +// ----------------------------------------------------------------------------- +// +// Misc. common utility functions +// +// Author: Skal (pascal.massimino@gmail.com) + +#include "src/utils/utils.h" + +#include +#include +#include // for memcpy() + +#include "src/webp/types.h" +#include "src/utils/palette.h" +#include "src/webp/encode.h" + +// If PRINT_MEM_INFO is defined, extra info (like total memory used, number of +// alloc/free etc) is printed. For debugging/tuning purpose only (it's slow, +// and not multi-thread safe!). +// An interesting alternative is valgrind's 'massif' tool: +// https://valgrind.org/docs/manual/ms-manual.html +// Here is an example command line: +/* valgrind --tool=massif --massif-out-file=massif.out \ + --stacks=yes --alloc-fn=WebPSafeMalloc --alloc-fn=WebPSafeCalloc + ms_print massif.out +*/ +// In addition: +// * if PRINT_MEM_TRAFFIC is defined, all the details of the malloc/free cycles +// are printed. +// * if MALLOC_FAIL_AT is defined, the global environment variable +// $MALLOC_FAIL_AT is used to simulate a memory error when calloc or malloc +// is called for the nth time. Example usage: +// export MALLOC_FAIL_AT=50 && ./examples/cwebp input.png +// * if MALLOC_LIMIT is defined, the global environment variable $MALLOC_LIMIT +// sets the maximum amount of memory (in bytes) made available to libwebp. +// This can be used to emulate environment with very limited memory. +// Example: export MALLOC_LIMIT=64000000 && ./examples/dwebp picture.webp + +// #define PRINT_MEM_INFO +// #define PRINT_MEM_TRAFFIC +// #define MALLOC_FAIL_AT +// #define MALLOC_LIMIT + +//------------------------------------------------------------------------------ +// Checked memory allocation + +#if defined(PRINT_MEM_INFO) + +#include + +static int num_malloc_calls = 0; +static int num_calloc_calls = 0; +static int num_free_calls = 0; +static int countdown_to_fail = 0; // 0 = off + +typedef struct MemBlock MemBlock; +struct MemBlock { + void* ptr; + size_t size; + MemBlock* next; +}; + +static MemBlock* all_blocks = NULL; +static size_t total_mem = 0; +static size_t total_mem_allocated = 0; +static size_t high_water_mark = 0; +static size_t mem_limit = 0; + +static int exit_registered = 0; + +static void PrintMemInfo(void) { + fprintf(stderr, "\nMEMORY INFO:\n"); + fprintf(stderr, "num calls to: malloc = %4d\n", num_malloc_calls); + fprintf(stderr, " calloc = %4d\n", num_calloc_calls); + fprintf(stderr, " free = %4d\n", num_free_calls); + fprintf(stderr, "total_mem: %u\n", (uint32_t)total_mem); + fprintf(stderr, "total_mem allocated: %u\n", (uint32_t)total_mem_allocated); + fprintf(stderr, "high-water mark: %u\n", (uint32_t)high_water_mark); + while (all_blocks != NULL) { + MemBlock* b = all_blocks; + all_blocks = b->next; + free(b); + } +} + +static void Increment(int* const v) { + if (!exit_registered) { +#if defined(MALLOC_FAIL_AT) + { + const char* const malloc_fail_at_str = getenv("MALLOC_FAIL_AT"); + if (malloc_fail_at_str != NULL) { + countdown_to_fail = atoi(malloc_fail_at_str); + } + } +#endif +#if defined(MALLOC_LIMIT) + { + const char* const malloc_limit_str = getenv("MALLOC_LIMIT"); +#if MALLOC_LIMIT > 1 + mem_limit = (size_t)MALLOC_LIMIT; +#endif + if (malloc_limit_str != NULL) { + mem_limit = atoi(malloc_limit_str); + } + } +#endif + (void)countdown_to_fail; + (void)mem_limit; + atexit(PrintMemInfo); + exit_registered = 1; + } + ++*v; +} + +static void AddMem(void* ptr, size_t size) { + if (ptr != NULL) { + MemBlock* const b = (MemBlock*)malloc(sizeof(*b)); + if (b == NULL) abort(); + b->next = all_blocks; + all_blocks = b; + b->ptr = ptr; + b->size = size; + total_mem += size; + total_mem_allocated += size; +#if defined(PRINT_MEM_TRAFFIC) +#if defined(MALLOC_FAIL_AT) + fprintf(stderr, "fail-count: %5d [mem=%u]\n", + num_malloc_calls + num_calloc_calls, (uint32_t)total_mem); +#else + fprintf(stderr, "Mem: %u (+%u)\n", (uint32_t)total_mem, (uint32_t)size); +#endif +#endif + if (total_mem > high_water_mark) high_water_mark = total_mem; + } +} + +static void SubMem(void* ptr) { + if (ptr != NULL) { + MemBlock** b = &all_blocks; + // Inefficient search, but that's just for debugging. + while (*b != NULL && (*b)->ptr != ptr) b = &(*b)->next; + if (*b == NULL) { + fprintf(stderr, "Invalid pointer free! (%p)\n", ptr); + abort(); + } + { + MemBlock* const block = *b; + *b = block->next; + total_mem -= block->size; +#if defined(PRINT_MEM_TRAFFIC) + fprintf(stderr, "Mem: %u (-%u)\n", + (uint32_t)total_mem, (uint32_t)block->size); +#endif + free(block); + } + } +} + +#else +#define Increment(v) do {} while (0) +#define AddMem(p, s) do {} while (0) +#define SubMem(p) do {} while (0) +#endif + +// Returns 0 in case of overflow of nmemb * size. +static int CheckSizeArgumentsOverflow(uint64_t nmemb, size_t size) { + const uint64_t total_size = nmemb * size; + if (nmemb == 0) return 1; + if ((uint64_t)size > WEBP_MAX_ALLOCABLE_MEMORY / nmemb) return 0; + if (!CheckSizeOverflow(total_size)) return 0; +#if defined(PRINT_MEM_INFO) && defined(MALLOC_FAIL_AT) + if (countdown_to_fail > 0 && --countdown_to_fail == 0) { + return 0; // fake fail! + } +#endif +#if defined(PRINT_MEM_INFO) && defined(MALLOC_LIMIT) + if (mem_limit > 0) { + const uint64_t new_total_mem = (uint64_t)total_mem + total_size; + if (!CheckSizeOverflow(new_total_mem) || + new_total_mem > mem_limit) { + return 0; // fake fail! + } + } +#endif + + return 1; +} + +void* WebPSafeMalloc(uint64_t nmemb, size_t size) { + void* ptr; + Increment(&num_malloc_calls); + if (!CheckSizeArgumentsOverflow(nmemb, size)) return NULL; + assert(nmemb * size > 0); + ptr = malloc((size_t)(nmemb * size)); + AddMem(ptr, (size_t)(nmemb * size)); + return ptr; +} + +void* WebPSafeCalloc(uint64_t nmemb, size_t size) { + void* ptr; + Increment(&num_calloc_calls); + if (!CheckSizeArgumentsOverflow(nmemb, size)) return NULL; + assert(nmemb * size > 0); + ptr = calloc((size_t)nmemb, size); + AddMem(ptr, (size_t)(nmemb * size)); + return ptr; +} + +void WebPSafeFree(void* const ptr) { + if (ptr != NULL) { + Increment(&num_free_calls); + SubMem(ptr); + } + free(ptr); +} + +// Public API functions. + +void* WebPMalloc(size_t size) { + return WebPSafeMalloc(1, size); +} + +void WebPFree(void* ptr) { + WebPSafeFree(ptr); +} + +//------------------------------------------------------------------------------ + +void WebPCopyPlane(const uint8_t* src, int src_stride, + uint8_t* dst, int dst_stride, int width, int height) { + assert(src != NULL && dst != NULL); + assert(abs(src_stride) >= width && abs(dst_stride) >= width); + while (height-- > 0) { + memcpy(dst, src, width); + src += src_stride; + dst += dst_stride; + } +} + +void WebPCopyPixels(const WebPPicture* const src, WebPPicture* const dst) { + assert(src != NULL && dst != NULL); + assert(src->width == dst->width && src->height == dst->height); + assert(src->use_argb && dst->use_argb); + WebPCopyPlane((uint8_t*)src->argb, 4 * src->argb_stride, (uint8_t*)dst->argb, + 4 * dst->argb_stride, 4 * src->width, src->height); +} + +//------------------------------------------------------------------------------ + +int WebPGetColorPalette(const WebPPicture* const pic, uint32_t* const palette) { + return GetColorPalette(pic, palette); +} + +//------------------------------------------------------------------------------ + +#if defined(WEBP_NEED_LOG_TABLE_8BIT) +const uint8_t WebPLogTable8bit[256] = { // 31 ^ clz(i) + 0, 0, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3, + 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, + 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, + 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, + 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, + 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, + 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, + 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, + 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, + 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, + 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, + 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, + 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7 +}; +#endif + +//------------------------------------------------------------------------------ diff --git a/packages/core/src/zig/vendor/libwebp/src/utils/utils.h b/packages/core/src/zig/vendor/libwebp/src/utils/utils.h new file mode 100644 index 0000000000..b2241fbf9b --- /dev/null +++ b/packages/core/src/zig/vendor/libwebp/src/utils/utils.h @@ -0,0 +1,209 @@ +// Copyright 2012 Google Inc. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the COPYING file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +// ----------------------------------------------------------------------------- +// +// Misc. common utility functions +// +// Authors: Skal (pascal.massimino@gmail.com) +// Urvang (urvang@google.com) + +#ifndef WEBP_UTILS_UTILS_H_ +#define WEBP_UTILS_UTILS_H_ + +#ifdef HAVE_CONFIG_H +#include "src/webp/config.h" +#endif + +#include + +#include "src/webp/types.h" + +#ifdef __cplusplus +extern "C" { +#endif + +//------------------------------------------------------------------------------ +// Memory allocation + +// This is the maximum memory amount that libwebp will ever try to allocate. +#ifndef WEBP_MAX_ALLOCABLE_MEMORY +#if SIZE_MAX > (1ULL << 34) +#define WEBP_MAX_ALLOCABLE_MEMORY (1ULL << 34) +#else +// For 32-bit targets keep this below INT_MAX to avoid valgrind warnings. +#define WEBP_MAX_ALLOCABLE_MEMORY ((1ULL << 31) - (1 << 16)) +#endif +#endif // WEBP_MAX_ALLOCABLE_MEMORY + +static WEBP_INLINE int CheckSizeOverflow(uint64_t size) { + return size == (size_t)size; +} + +// size-checking safe malloc/calloc: verify that the requested size is not too +// large, or return NULL. You don't need to call these for constructs like +// malloc(sizeof(foo)), but only if there's picture-dependent size involved +// somewhere (like: malloc(num_pixels * sizeof(*something))). That's why this +// safe malloc() borrows the signature from calloc(), pointing at the dangerous +// underlying multiply involved. +WEBP_EXTERN void* WebPSafeMalloc(uint64_t nmemb, size_t size); +// Note that WebPSafeCalloc() expects the second argument type to be 'size_t' +// in order to favor the "calloc(num_foo, sizeof(foo))" pattern. +WEBP_EXTERN void* WebPSafeCalloc(uint64_t nmemb, size_t size); + +// Companion deallocation function to the above allocations. +WEBP_EXTERN void WebPSafeFree(void* const ptr); + +//------------------------------------------------------------------------------ +// Alignment + +#define WEBP_ALIGN_CST 31 +#define WEBP_ALIGN(PTR) (((uintptr_t)(PTR) + WEBP_ALIGN_CST) & \ + ~(uintptr_t)WEBP_ALIGN_CST) + +#include +// memcpy() is the safe way of moving potentially unaligned 32b memory. +static WEBP_INLINE uint32_t WebPMemToUint32(const uint8_t* const ptr) { + uint32_t A; + memcpy(&A, ptr, sizeof(A)); + return A; +} + +static WEBP_INLINE int32_t WebPMemToInt32(const uint8_t* const ptr) { + return (int32_t)WebPMemToUint32(ptr); +} + +static WEBP_INLINE void WebPUint32ToMem(uint8_t* const ptr, uint32_t val) { + memcpy(ptr, &val, sizeof(val)); +} + +static WEBP_INLINE void WebPInt32ToMem(uint8_t* const ptr, int val) { + WebPUint32ToMem(ptr, (uint32_t)val); +} + +//------------------------------------------------------------------------------ +// Reading/writing data. + +// Read 16, 24 or 32 bits stored in little-endian order. +static WEBP_INLINE int GetLE16(const uint8_t* const data) { + return (int)(data[0] << 0) | (data[1] << 8); +} + +static WEBP_INLINE int GetLE24(const uint8_t* const data) { + return GetLE16(data) | (data[2] << 16); +} + +static WEBP_INLINE uint32_t GetLE32(const uint8_t* const data) { + return GetLE16(data) | ((uint32_t)GetLE16(data + 2) << 16); +} + +// Store 16, 24 or 32 bits in little-endian order. +static WEBP_INLINE void PutLE16(uint8_t* const data, int val) { + assert(val < (1 << 16)); + data[0] = (val >> 0) & 0xff; + data[1] = (val >> 8) & 0xff; +} + +static WEBP_INLINE void PutLE24(uint8_t* const data, int val) { + assert(val < (1 << 24)); + PutLE16(data, val & 0xffff); + data[2] = (val >> 16) & 0xff; +} + +static WEBP_INLINE void PutLE32(uint8_t* const data, uint32_t val) { + PutLE16(data, (int)(val & 0xffff)); + PutLE16(data + 2, (int)(val >> 16)); +} + +// use GNU builtins where available. +#if defined(__GNUC__) && \ + ((__GNUC__ == 3 && __GNUC_MINOR__ >= 4) || __GNUC__ >= 4) +// Returns (int)floor(log2(n)). n must be > 0. +static WEBP_INLINE int BitsLog2Floor(uint32_t n) { + return 31 ^ __builtin_clz(n); +} +// counts the number of trailing zero +static WEBP_INLINE int BitsCtz(uint32_t n) { return __builtin_ctz(n); } +#elif defined(_MSC_VER) && _MSC_VER > 1310 && \ + (defined(_M_X64) || defined(_M_IX86)) +#include +#pragma intrinsic(_BitScanReverse) +#pragma intrinsic(_BitScanForward) + +static WEBP_INLINE int BitsLog2Floor(uint32_t n) { + unsigned long first_set_bit; // NOLINT (runtime/int) + _BitScanReverse(&first_set_bit, n); + return first_set_bit; +} +static WEBP_INLINE int BitsCtz(uint32_t n) { + unsigned long first_set_bit; // NOLINT (runtime/int) + _BitScanForward(&first_set_bit, n); + return first_set_bit; +} +#else // default: use the (slow) C-version. +#define WEBP_HAVE_SLOW_CLZ_CTZ // signal that the Clz/Ctz function are slow +// Returns 31 ^ clz(n) = log2(n). This is the default C-implementation, either +// based on table or not. Can be used as fallback if clz() is not available. +#define WEBP_NEED_LOG_TABLE_8BIT +extern const uint8_t WebPLogTable8bit[256]; +static WEBP_INLINE int WebPLog2FloorC(uint32_t n) { + int log_value = 0; + while (n >= 256) { + log_value += 8; + n >>= 8; + } + return log_value + WebPLogTable8bit[n]; +} + +static WEBP_INLINE int BitsLog2Floor(uint32_t n) { return WebPLog2FloorC(n); } + +static WEBP_INLINE int BitsCtz(uint32_t n) { + int i; + for (i = 0; i < 32; ++i, n >>= 1) { + if (n & 1) return i; + } + return 32; +} + +#endif + +//------------------------------------------------------------------------------ +// Pixel copying. + +struct WebPPicture; + +// Copy width x height pixels from 'src' to 'dst' honoring the strides. +WEBP_EXTERN void WebPCopyPlane(const uint8_t* src, int src_stride, + uint8_t* dst, int dst_stride, + int width, int height); + +// Copy ARGB pixels from 'src' to 'dst' honoring strides. 'src' and 'dst' are +// assumed to be already allocated and using ARGB data. +WEBP_EXTERN void WebPCopyPixels(const struct WebPPicture* const src, + struct WebPPicture* const dst); + +//------------------------------------------------------------------------------ +// Unique colors. + +// Returns count of unique colors in 'pic', assuming pic->use_argb is true. +// If the unique color count is more than MAX_PALETTE_SIZE, returns +// MAX_PALETTE_SIZE+1. +// If 'palette' is not NULL and number of unique colors is less than or equal to +// MAX_PALETTE_SIZE, also outputs the actual unique colors into 'palette'. +// Note: 'palette' is assumed to be an array already allocated with at least +// MAX_PALETTE_SIZE elements. +// TODO(vrabaud) remove whenever we can break the ABI. +WEBP_EXTERN int WebPGetColorPalette(const struct WebPPicture* const pic, + uint32_t* const palette); + +//------------------------------------------------------------------------------ + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif // WEBP_UTILS_UTILS_H_ diff --git a/packages/core/src/zig/vendor/libwebp/src/webp/decode.h b/packages/core/src/zig/vendor/libwebp/src/webp/decode.h new file mode 100644 index 0000000000..1f0d0c22ea --- /dev/null +++ b/packages/core/src/zig/vendor/libwebp/src/webp/decode.h @@ -0,0 +1,515 @@ +// Copyright 2010 Google Inc. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the COPYING file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +// ----------------------------------------------------------------------------- +// +// Main decoding functions for WebP images. +// +// Author: Skal (pascal.massimino@gmail.com) + +#ifndef WEBP_WEBP_DECODE_H_ +#define WEBP_WEBP_DECODE_H_ + +#include + +#include "./types.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#define WEBP_DECODER_ABI_VERSION 0x0210 // MAJOR(8b) + MINOR(8b) + +// Note: forward declaring enumerations is not allowed in (strict) C and C++, +// the types are left here for reference. +// typedef enum VP8StatusCode VP8StatusCode; +// typedef enum WEBP_CSP_MODE WEBP_CSP_MODE; +typedef struct WebPRGBABuffer WebPRGBABuffer; +typedef struct WebPYUVABuffer WebPYUVABuffer; +typedef struct WebPDecBuffer WebPDecBuffer; +typedef struct WebPIDecoder WebPIDecoder; +typedef struct WebPBitstreamFeatures WebPBitstreamFeatures; +typedef struct WebPDecoderOptions WebPDecoderOptions; +typedef struct WebPDecoderConfig WebPDecoderConfig; + +// Return the decoder's version number, packed in hexadecimal using 8bits for +// each of major/minor/revision. E.g: v2.5.7 is 0x020507. +WEBP_EXTERN int WebPGetDecoderVersion(void); + +// Retrieve basic header information: width, height. +// This function will also validate the header, returning true on success, +// false otherwise. '*width' and '*height' are only valid on successful return. +// Pointers 'width' and 'height' can be passed NULL if deemed irrelevant. +// Note: The following chunk sequences (before the raw VP8/VP8L data) are +// considered valid by this function: +// RIFF + VP8(L) +// RIFF + VP8X + (optional chunks) + VP8(L) +// ALPH + VP8 <-- Not a valid WebP format: only allowed for internal purpose. +// VP8(L) <-- Not a valid WebP format: only allowed for internal purpose. +WEBP_NODISCARD WEBP_EXTERN int WebPGetInfo( + const uint8_t* data, size_t data_size, int* width, int* height); + +// Decodes WebP images pointed to by 'data' and returns RGBA samples, along +// with the dimensions in *width and *height. The ordering of samples in +// memory is R, G, B, A, R, G, B, A... in scan order (endian-independent). +// The returned pointer should be deleted calling WebPFree(). +// Returns NULL in case of error. +WEBP_NODISCARD WEBP_EXTERN uint8_t* WebPDecodeRGBA( + const uint8_t* data, size_t data_size, int* width, int* height); + +// Same as WebPDecodeRGBA, but returning A, R, G, B, A, R, G, B... ordered data. +WEBP_NODISCARD WEBP_EXTERN uint8_t* WebPDecodeARGB( + const uint8_t* data, size_t data_size, int* width, int* height); + +// Same as WebPDecodeRGBA, but returning B, G, R, A, B, G, R, A... ordered data. +WEBP_NODISCARD WEBP_EXTERN uint8_t* WebPDecodeBGRA( + const uint8_t* data, size_t data_size, int* width, int* height); + +// Same as WebPDecodeRGBA, but returning R, G, B, R, G, B... ordered data. +// If the bitstream contains transparency, it is ignored. +WEBP_NODISCARD WEBP_EXTERN uint8_t* WebPDecodeRGB( + const uint8_t* data, size_t data_size, int* width, int* height); + +// Same as WebPDecodeRGB, but returning B, G, R, B, G, R... ordered data. +WEBP_NODISCARD WEBP_EXTERN uint8_t* WebPDecodeBGR( + const uint8_t* data, size_t data_size, int* width, int* height); + +// Decode WebP images pointed to by 'data' to Y'UV format(*). The pointer +// returned is the Y samples buffer. Upon return, *u and *v will point to +// the U and V chroma data. These U and V buffers need NOT be passed to +// WebPFree(), unlike the returned Y luma one. The dimension of the U and V +// planes are both (*width + 1) / 2 and (*height + 1) / 2. +// Upon return, the Y buffer has a stride returned as '*stride', while U and V +// have a common stride returned as '*uv_stride'. +// 'width' and 'height' may be NULL, the other pointers must not be. +// Returns NULL in case of error. +// (*) Also named Y'CbCr. See: https://en.wikipedia.org/wiki/YCbCr +WEBP_NODISCARD WEBP_EXTERN uint8_t* WebPDecodeYUV( + const uint8_t* data, size_t data_size, int* width, int* height, + uint8_t** u, uint8_t** v, int* stride, int* uv_stride); + +// These five functions are variants of the above ones, that decode the image +// directly into a pre-allocated buffer 'output_buffer'. The maximum storage +// available in this buffer is indicated by 'output_buffer_size'. If this +// storage is not sufficient (or an error occurred), NULL is returned. +// Otherwise, output_buffer is returned, for convenience. +// The parameter 'output_stride' specifies the distance (in bytes) +// between scanlines. Hence, output_buffer_size is expected to be at least +// output_stride x picture-height. +WEBP_NODISCARD WEBP_EXTERN uint8_t* WebPDecodeRGBAInto( + const uint8_t* data, size_t data_size, + uint8_t* output_buffer, size_t output_buffer_size, int output_stride); +WEBP_NODISCARD WEBP_EXTERN uint8_t* WebPDecodeARGBInto( + const uint8_t* data, size_t data_size, + uint8_t* output_buffer, size_t output_buffer_size, int output_stride); +WEBP_NODISCARD WEBP_EXTERN uint8_t* WebPDecodeBGRAInto( + const uint8_t* data, size_t data_size, + uint8_t* output_buffer, size_t output_buffer_size, int output_stride); + +// RGB and BGR variants. Here too the transparency information, if present, +// will be dropped and ignored. +WEBP_NODISCARD WEBP_EXTERN uint8_t* WebPDecodeRGBInto( + const uint8_t* data, size_t data_size, + uint8_t* output_buffer, size_t output_buffer_size, int output_stride); +WEBP_NODISCARD WEBP_EXTERN uint8_t* WebPDecodeBGRInto( + const uint8_t* data, size_t data_size, + uint8_t* output_buffer, size_t output_buffer_size, int output_stride); + +// WebPDecodeYUVInto() is a variant of WebPDecodeYUV() that operates directly +// into pre-allocated luma/chroma plane buffers. This function requires the +// strides to be passed: one for the luma plane and one for each of the +// chroma ones. The size of each plane buffer is passed as 'luma_size', +// 'u_size' and 'v_size' respectively. +// Pointer to the luma plane ('*luma') is returned or NULL if an error occurred +// during decoding (or because some buffers were found to be too small). +WEBP_NODISCARD WEBP_EXTERN uint8_t* WebPDecodeYUVInto( + const uint8_t* data, size_t data_size, + uint8_t* luma, size_t luma_size, int luma_stride, + uint8_t* u, size_t u_size, int u_stride, + uint8_t* v, size_t v_size, int v_stride); + +//------------------------------------------------------------------------------ +// Output colorspaces and buffer + +// Colorspaces +// Note: the naming describes the byte-ordering of packed samples in memory. +// For instance, MODE_BGRA relates to samples ordered as B,G,R,A,B,G,R,A,... +// Non-capital names (e.g.:MODE_Argb) relates to pre-multiplied RGB channels. +// RGBA-4444 and RGB-565 colorspaces are represented by following byte-order: +// RGBA-4444: [r3 r2 r1 r0 g3 g2 g1 g0], [b3 b2 b1 b0 a3 a2 a1 a0], ... +// RGB-565: [r4 r3 r2 r1 r0 g5 g4 g3], [g2 g1 g0 b4 b3 b2 b1 b0], ... +// In the case WEBP_SWAP_16BITS_CSP is defined, the bytes are swapped for +// these two modes: +// RGBA-4444: [b3 b2 b1 b0 a3 a2 a1 a0], [r3 r2 r1 r0 g3 g2 g1 g0], ... +// RGB-565: [g2 g1 g0 b4 b3 b2 b1 b0], [r4 r3 r2 r1 r0 g5 g4 g3], ... + +typedef enum WEBP_CSP_MODE { + MODE_RGB = 0, MODE_RGBA = 1, + MODE_BGR = 2, MODE_BGRA = 3, + MODE_ARGB = 4, MODE_RGBA_4444 = 5, + MODE_RGB_565 = 6, + // RGB-premultiplied transparent modes (alpha value is preserved) + MODE_rgbA = 7, + MODE_bgrA = 8, + MODE_Argb = 9, + MODE_rgbA_4444 = 10, + // YUV modes must come after RGB ones. + MODE_YUV = 11, MODE_YUVA = 12, // yuv 4:2:0 + MODE_LAST = 13 +} WEBP_CSP_MODE; + +// Some useful macros: +static WEBP_INLINE int WebPIsPremultipliedMode(WEBP_CSP_MODE mode) { + return (mode == MODE_rgbA || mode == MODE_bgrA || mode == MODE_Argb || + mode == MODE_rgbA_4444); +} + +static WEBP_INLINE int WebPIsAlphaMode(WEBP_CSP_MODE mode) { + return (mode == MODE_RGBA || mode == MODE_BGRA || mode == MODE_ARGB || + mode == MODE_RGBA_4444 || mode == MODE_YUVA || + WebPIsPremultipliedMode(mode)); +} + +static WEBP_INLINE int WebPIsRGBMode(WEBP_CSP_MODE mode) { + return (mode < MODE_YUV); +} + +//------------------------------------------------------------------------------ +// WebPDecBuffer: Generic structure for describing the output sample buffer. + +struct WebPRGBABuffer { // view as RGBA + uint8_t* rgba; // pointer to RGBA samples + int stride; // stride in bytes from one scanline to the next. + size_t size; // total size of the *rgba buffer. +}; + +struct WebPYUVABuffer { // view as YUVA + uint8_t* y, *u, *v, *a; // pointer to luma, chroma U/V, alpha samples + int y_stride; // luma stride + int u_stride, v_stride; // chroma strides + int a_stride; // alpha stride + size_t y_size; // luma plane size + size_t u_size, v_size; // chroma planes size + size_t a_size; // alpha-plane size +}; + +// Output buffer +struct WebPDecBuffer { + WEBP_CSP_MODE colorspace; // Colorspace. + int width, height; // Dimensions. + int is_external_memory; // If non-zero, 'internal_memory' pointer is not + // used. If value is '2' or more, the external + // memory is considered 'slow' and multiple + // read/write will be avoided. + union { + WebPRGBABuffer RGBA; + WebPYUVABuffer YUVA; + } u; // Nameless union of buffer parameters. + uint32_t pad[4]; // padding for later use + + uint8_t* private_memory; // Internally allocated memory (only when + // is_external_memory is 0). Should not be used + // externally, but accessed via the buffer union. +}; + +// Internal, version-checked, entry point +WEBP_NODISCARD WEBP_EXTERN int WebPInitDecBufferInternal(WebPDecBuffer*, int); + +// Initialize the structure as empty. Must be called before any other use. +// Returns false in case of version mismatch +WEBP_NODISCARD static WEBP_INLINE int WebPInitDecBuffer(WebPDecBuffer* buffer) { + return WebPInitDecBufferInternal(buffer, WEBP_DECODER_ABI_VERSION); +} + +// Free any memory associated with the buffer. Must always be called last. +// Note: doesn't free the 'buffer' structure itself. +WEBP_EXTERN void WebPFreeDecBuffer(WebPDecBuffer* buffer); + +//------------------------------------------------------------------------------ +// Enumeration of the status codes + +typedef enum WEBP_NODISCARD VP8StatusCode { + VP8_STATUS_OK = 0, + VP8_STATUS_OUT_OF_MEMORY, + VP8_STATUS_INVALID_PARAM, + VP8_STATUS_BITSTREAM_ERROR, + VP8_STATUS_UNSUPPORTED_FEATURE, + VP8_STATUS_SUSPENDED, + VP8_STATUS_USER_ABORT, + VP8_STATUS_NOT_ENOUGH_DATA +} VP8StatusCode; + +//------------------------------------------------------------------------------ +// Incremental decoding +// +// This API allows streamlined decoding of partial data. +// Picture can be incrementally decoded as data become available thanks to the +// WebPIDecoder object. This object can be left in a SUSPENDED state if the +// picture is only partially decoded, pending additional input. +// Code example: +/* + WebPInitDecBuffer(&output_buffer); + output_buffer.colorspace = mode; + ... + WebPIDecoder* idec = WebPINewDecoder(&output_buffer); + while (additional_data_is_available) { + // ... (get additional data in some new_data[] buffer) + status = WebPIAppend(idec, new_data, new_data_size); + if (status != VP8_STATUS_OK && status != VP8_STATUS_SUSPENDED) { + break; // an error occurred. + } + + // The above call decodes the current available buffer. + // Part of the image can now be refreshed by calling + // WebPIDecGetRGB()/WebPIDecGetYUVA() etc. + } + WebPIDelete(idec); +*/ + +// Creates a new incremental decoder with the supplied buffer parameter. +// This output_buffer can be passed NULL, in which case a default output buffer +// is used (with MODE_RGB). Otherwise, an internal reference to 'output_buffer' +// is kept, which means that the lifespan of 'output_buffer' must be larger than +// that of the returned WebPIDecoder object. +// The supplied 'output_buffer' content MUST NOT be changed between calls to +// WebPIAppend() or WebPIUpdate() unless 'output_buffer.is_external_memory' is +// not set to 0. In such a case, it is allowed to modify the pointers, size and +// stride of output_buffer.u.RGBA or output_buffer.u.YUVA, provided they remain +// within valid bounds. +// All other fields of WebPDecBuffer MUST remain constant between calls. +// Returns NULL if the allocation failed. +WEBP_NODISCARD WEBP_EXTERN WebPIDecoder* WebPINewDecoder( + WebPDecBuffer* output_buffer); + +// This function allocates and initializes an incremental-decoder object, which +// will output the RGB/A samples specified by 'csp' into a preallocated +// buffer 'output_buffer'. The size of this buffer is at least +// 'output_buffer_size' and the stride (distance in bytes between two scanlines) +// is specified by 'output_stride'. +// Additionally, output_buffer can be passed NULL in which case the output +// buffer will be allocated automatically when the decoding starts. The +// colorspace 'csp' is taken into account for allocating this buffer. All other +// parameters are ignored. +// Returns NULL if the allocation failed, or if some parameters are invalid. +WEBP_NODISCARD WEBP_EXTERN WebPIDecoder* WebPINewRGB( + WEBP_CSP_MODE csp, + uint8_t* output_buffer, size_t output_buffer_size, int output_stride); + +// This function allocates and initializes an incremental-decoder object, which +// will output the raw luma/chroma samples into a preallocated planes if +// supplied. The luma plane is specified by its pointer 'luma', its size +// 'luma_size' and its stride 'luma_stride'. Similarly, the chroma-u plane +// is specified by the 'u', 'u_size' and 'u_stride' parameters, and the chroma-v +// plane by 'v' and 'v_size'. And same for the alpha-plane. The 'a' pointer +// can be pass NULL in case one is not interested in the transparency plane. +// Conversely, 'luma' can be passed NULL if no preallocated planes are supplied. +// In this case, the output buffer will be automatically allocated (using +// MODE_YUVA) when decoding starts. All parameters are then ignored. +// Returns NULL if the allocation failed or if a parameter is invalid. +WEBP_NODISCARD WEBP_EXTERN WebPIDecoder* WebPINewYUVA( + uint8_t* luma, size_t luma_size, int luma_stride, + uint8_t* u, size_t u_size, int u_stride, + uint8_t* v, size_t v_size, int v_stride, + uint8_t* a, size_t a_size, int a_stride); + +// Deprecated version of the above, without the alpha plane. +// Kept for backward compatibility. +WEBP_NODISCARD WEBP_EXTERN WebPIDecoder* WebPINewYUV( + uint8_t* luma, size_t luma_size, int luma_stride, + uint8_t* u, size_t u_size, int u_stride, + uint8_t* v, size_t v_size, int v_stride); + +// Deletes the WebPIDecoder object and associated memory. Must always be called +// if WebPINewDecoder, WebPINewRGB or WebPINewYUV succeeded. +WEBP_EXTERN void WebPIDelete(WebPIDecoder* idec); + +// Copies and decodes the next available data. Returns VP8_STATUS_OK when +// the image is successfully decoded. Returns VP8_STATUS_SUSPENDED when more +// data is expected. Returns error in other cases. +WEBP_EXTERN VP8StatusCode WebPIAppend( + WebPIDecoder* idec, const uint8_t* data, size_t data_size); + +// A variant of the above function to be used when data buffer contains +// partial data from the beginning. In this case data buffer is not copied +// to the internal memory. +// Note that the value of the 'data' pointer can change between calls to +// WebPIUpdate, for instance when the data buffer is resized to fit larger data. +WEBP_EXTERN VP8StatusCode WebPIUpdate( + WebPIDecoder* idec, const uint8_t* data, size_t data_size); + +// Returns the RGB/A image decoded so far. Returns NULL if output params +// are not initialized yet. The RGB/A output type corresponds to the colorspace +// specified during call to WebPINewDecoder() or WebPINewRGB(). +// *last_y is the index of last decoded row in raster scan order. Some pointers +// (*last_y, *width etc.) can be NULL if corresponding information is not +// needed. The values in these pointers are only valid on successful (non-NULL) +// return. +WEBP_NODISCARD WEBP_EXTERN uint8_t* WebPIDecGetRGB( + const WebPIDecoder* idec, int* last_y, + int* width, int* height, int* stride); + +// Same as above function to get a YUVA image. Returns pointer to the luma +// plane or NULL in case of error. If there is no alpha information +// the alpha pointer '*a' will be returned NULL. +WEBP_NODISCARD WEBP_EXTERN uint8_t* WebPIDecGetYUVA( + const WebPIDecoder* idec, int* last_y, + uint8_t** u, uint8_t** v, uint8_t** a, + int* width, int* height, int* stride, int* uv_stride, int* a_stride); + +// Deprecated alpha-less version of WebPIDecGetYUVA(): it will ignore the +// alpha information (if present). Kept for backward compatibility. +WEBP_NODISCARD static WEBP_INLINE uint8_t* WebPIDecGetYUV( + const WebPIDecoder* idec, int* last_y, uint8_t** u, uint8_t** v, + int* width, int* height, int* stride, int* uv_stride) { + return WebPIDecGetYUVA(idec, last_y, u, v, NULL, width, height, + stride, uv_stride, NULL); +} + +// Generic call to retrieve information about the displayable area. +// If non NULL, the left/right/width/height pointers are filled with the visible +// rectangular area so far. +// Returns NULL in case the incremental decoder object is in an invalid state. +// Otherwise returns the pointer to the internal representation. This structure +// is read-only, tied to WebPIDecoder's lifespan and should not be modified. +WEBP_NODISCARD WEBP_EXTERN const WebPDecBuffer* WebPIDecodedArea( + const WebPIDecoder* idec, int* left, int* top, int* width, int* height); + +//------------------------------------------------------------------------------ +// Advanced decoding parametrization +// +// Code sample for using the advanced decoding API +/* + // A) Init a configuration object + WebPDecoderConfig config; + CHECK(WebPInitDecoderConfig(&config)); + + // B) optional: retrieve the bitstream's features. + CHECK(WebPGetFeatures(data, data_size, &config.input) == VP8_STATUS_OK); + + // C) Adjust 'config', if needed + config.options.no_fancy_upsampling = 1; + config.output.colorspace = MODE_BGRA; + // etc. + + // Note that you can also make config.output point to an externally + // supplied memory buffer, provided it's big enough to store the decoded + // picture. Otherwise, config.output will just be used to allocate memory + // and store the decoded picture. + + // D) Decode! + CHECK(WebPDecode(data, data_size, &config) == VP8_STATUS_OK); + + // E) Decoded image is now in config.output (and config.output.u.RGBA) + + // F) Reclaim memory allocated in config's object. It's safe to call + // this function even if the memory is external and wasn't allocated + // by WebPDecode(). + WebPFreeDecBuffer(&config.output); +*/ + +// Features gathered from the bitstream +struct WebPBitstreamFeatures { + int width; // Width in pixels, as read from the bitstream. + int height; // Height in pixels, as read from the bitstream. + int has_alpha; // True if the bitstream contains an alpha channel. + int has_animation; // True if the bitstream is an animation. + int format; // 0 = undefined (/mixed), 1 = lossy, 2 = lossless + + uint32_t pad[5]; // padding for later use +}; + +// Internal, version-checked, entry point +WEBP_EXTERN VP8StatusCode WebPGetFeaturesInternal( + const uint8_t*, size_t, WebPBitstreamFeatures*, int); + +// Retrieve features from the bitstream. The *features structure is filled +// with information gathered from the bitstream. +// Returns VP8_STATUS_OK when the features are successfully retrieved. Returns +// VP8_STATUS_NOT_ENOUGH_DATA when more data is needed to retrieve the +// features from headers. Returns error in other cases. +// Note: The following chunk sequences (before the raw VP8/VP8L data) are +// considered valid by this function: +// RIFF + VP8(L) +// RIFF + VP8X + (optional chunks) + VP8(L) +// ALPH + VP8 <-- Not a valid WebP format: only allowed for internal purpose. +// VP8(L) <-- Not a valid WebP format: only allowed for internal purpose. +static WEBP_INLINE VP8StatusCode WebPGetFeatures( + const uint8_t* data, size_t data_size, + WebPBitstreamFeatures* features) { + return WebPGetFeaturesInternal(data, data_size, features, + WEBP_DECODER_ABI_VERSION); +} + +// Decoding options +struct WebPDecoderOptions { + int bypass_filtering; // if true, skip the in-loop filtering + int no_fancy_upsampling; // if true, use faster pointwise upsampler + int use_cropping; // if true, cropping is applied _first_ + int crop_left, crop_top; // top-left position for cropping. + // Will be snapped to even values. + int crop_width, crop_height; // dimension of the cropping area + int use_scaling; // if true, scaling is applied _afterward_ + int scaled_width, scaled_height; // final resolution. if one is 0, it is + // guessed from the other one to keep the + // original ratio. + int use_threads; // if true, use multi-threaded decoding + int dithering_strength; // dithering strength (0=Off, 100=full) + int flip; // if true, flip output vertically + int alpha_dithering_strength; // alpha dithering strength in [0..100] + + uint32_t pad[5]; // padding for later use +}; + +// Main object storing the configuration for advanced decoding. +struct WebPDecoderConfig { + WebPBitstreamFeatures input; // Immutable bitstream features (optional) + WebPDecBuffer output; // Output buffer (can point to external mem) + WebPDecoderOptions options; // Decoding options +}; + +// Internal, version-checked, entry point +WEBP_NODISCARD WEBP_EXTERN int WebPInitDecoderConfigInternal(WebPDecoderConfig*, + int); + +// Initialize the configuration as empty. This function must always be +// called first, unless WebPGetFeatures() is to be called. +// Returns false in case of mismatched version. +WEBP_NODISCARD static WEBP_INLINE int WebPInitDecoderConfig( + WebPDecoderConfig* config) { + return WebPInitDecoderConfigInternal(config, WEBP_DECODER_ABI_VERSION); +} + +// Returns true if 'config' is non-NULL and all configuration parameters are +// within their valid ranges. +WEBP_NODISCARD WEBP_EXTERN int WebPValidateDecoderConfig( + const WebPDecoderConfig* config); + +// Instantiate a new incremental decoder object with the requested +// configuration. The bitstream can be passed using 'data' and 'data_size' +// parameter, in which case the features will be parsed and stored into +// config->input. Otherwise, 'data' can be NULL and no parsing will occur. +// Note that 'config' can be NULL too, in which case a default configuration +// is used. If 'config' is not NULL, it must outlive the WebPIDecoder object +// as some references to its fields will be used. No internal copy of 'config' +// is made. +// The return WebPIDecoder object must always be deleted calling WebPIDelete(). +// Returns NULL in case of error (and config->status will then reflect +// the error condition, if available). +WEBP_NODISCARD WEBP_EXTERN WebPIDecoder* WebPIDecode( + const uint8_t* data, size_t data_size, WebPDecoderConfig* config); + +// Non-incremental version. This version decodes the full data at once, taking +// 'config' into account. Returns decoding status (which should be VP8_STATUS_OK +// if the decoding was successful). Note that 'config' cannot be NULL. +WEBP_EXTERN VP8StatusCode WebPDecode(const uint8_t* data, size_t data_size, + WebPDecoderConfig* config); + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif // WEBP_WEBP_DECODE_H_ diff --git a/packages/core/src/zig/vendor/libwebp/src/webp/encode.h b/packages/core/src/zig/vendor/libwebp/src/webp/encode.h new file mode 100644 index 0000000000..ed49393234 --- /dev/null +++ b/packages/core/src/zig/vendor/libwebp/src/webp/encode.h @@ -0,0 +1,560 @@ +// Copyright 2011 Google Inc. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the COPYING file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +// ----------------------------------------------------------------------------- +// +// WebP encoder: main interface +// +// Author: Skal (pascal.massimino@gmail.com) + +#ifndef WEBP_WEBP_ENCODE_H_ +#define WEBP_WEBP_ENCODE_H_ + +#include + +#include "./types.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#define WEBP_ENCODER_ABI_VERSION 0x0210 // MAJOR(8b) + MINOR(8b) + +// Note: forward declaring enumerations is not allowed in (strict) C and C++, +// the types are left here for reference. +// typedef enum WebPImageHint WebPImageHint; +// typedef enum WebPEncCSP WebPEncCSP; +// typedef enum WebPPreset WebPPreset; +// typedef enum WebPEncodingError WebPEncodingError; +typedef struct WebPConfig WebPConfig; +typedef struct WebPPicture WebPPicture; // main structure for I/O +typedef struct WebPAuxStats WebPAuxStats; +typedef struct WebPMemoryWriter WebPMemoryWriter; + +// Return the encoder's version number, packed in hexadecimal using 8bits for +// each of major/minor/revision. E.g: v2.5.7 is 0x020507. +WEBP_EXTERN int WebPGetEncoderVersion(void); + +//------------------------------------------------------------------------------ +// One-stop-shop call! No questions asked: + +// Returns the size of the compressed data (pointed to by *output), or 0 if +// an error occurred. The compressed data must be released by the caller +// using the call 'WebPFree(*output)'. +// These functions compress using the lossy format, and the quality_factor +// can go from 0 (smaller output, lower quality) to 100 (best quality, +// larger output). +WEBP_EXTERN size_t WebPEncodeRGB(const uint8_t* rgb, + int width, int height, int stride, + float quality_factor, uint8_t** output); +WEBP_EXTERN size_t WebPEncodeBGR(const uint8_t* bgr, + int width, int height, int stride, + float quality_factor, uint8_t** output); +WEBP_EXTERN size_t WebPEncodeRGBA(const uint8_t* rgba, + int width, int height, int stride, + float quality_factor, uint8_t** output); +WEBP_EXTERN size_t WebPEncodeBGRA(const uint8_t* bgra, + int width, int height, int stride, + float quality_factor, uint8_t** output); + +// These functions are the equivalent of the above, but compressing in a +// lossless manner. Files are usually larger than lossy format, but will +// not suffer any compression loss. +// Note these functions, like the lossy versions, use the library's default +// settings. For lossless this means 'exact' is disabled. RGB values in +// transparent areas will be modified to improve compression. To avoid this, +// use WebPEncode() and set WebPConfig::exact to 1. +WEBP_EXTERN size_t WebPEncodeLosslessRGB(const uint8_t* rgb, + int width, int height, int stride, + uint8_t** output); +WEBP_EXTERN size_t WebPEncodeLosslessBGR(const uint8_t* bgr, + int width, int height, int stride, + uint8_t** output); +WEBP_EXTERN size_t WebPEncodeLosslessRGBA(const uint8_t* rgba, + int width, int height, int stride, + uint8_t** output); +WEBP_EXTERN size_t WebPEncodeLosslessBGRA(const uint8_t* bgra, + int width, int height, int stride, + uint8_t** output); + +//------------------------------------------------------------------------------ +// Coding parameters + +// Image characteristics hint for the underlying encoder. +typedef enum WebPImageHint { + WEBP_HINT_DEFAULT = 0, // default preset. + WEBP_HINT_PICTURE, // digital picture, like portrait, inner shot + WEBP_HINT_PHOTO, // outdoor photograph, with natural lighting + WEBP_HINT_GRAPH, // Discrete tone image (graph, map-tile etc). + WEBP_HINT_LAST +} WebPImageHint; + +// Compression parameters. +struct WebPConfig { + int lossless; // Lossless encoding (0=lossy(default), 1=lossless). + float quality; // between 0 and 100. For lossy, 0 gives the smallest + // size and 100 the largest. For lossless, this + // parameter is the amount of effort put into the + // compression: 0 is the fastest but gives larger + // files compared to the slowest, but best, 100. + int method; // quality/speed trade-off (0=fast, 6=slower-better) + + WebPImageHint image_hint; // Hint for image type (lossless only for now). + + int target_size; // if non-zero, set the desired target size in bytes. + // Takes precedence over the 'compression' parameter. + float target_PSNR; // if non-zero, specifies the minimal distortion to + // try to achieve. Takes precedence over target_size. + int segments; // maximum number of segments to use, in [1..4] + int sns_strength; // Spatial Noise Shaping. 0=off, 100=maximum. + int filter_strength; // range: [0 = off .. 100 = strongest] + int filter_sharpness; // range: [0 = off .. 7 = least sharp] + int filter_type; // filtering type: 0 = simple, 1 = strong (only used + // if filter_strength > 0 or autofilter > 0) + int autofilter; // Auto adjust filter's strength [0 = off, 1 = on] + int alpha_compression; // Algorithm for encoding the alpha plane (0 = none, + // 1 = compressed with WebP lossless). Default is 1. + int alpha_filtering; // Predictive filtering method for alpha plane. + // 0: none, 1: fast, 2: best. Default if 1. + int alpha_quality; // Between 0 (smallest size) and 100 (lossless). + // Default is 100. + int pass; // number of entropy-analysis passes (in [1..10]). + + int show_compressed; // if true, export the compressed picture back. + // In-loop filtering is not applied. + int preprocessing; // preprocessing filter: + // 0=none, 1=segment-smooth, 2=pseudo-random dithering + int partitions; // log2(number of token partitions) in [0..3]. Default + // is set to 0 for easier progressive decoding. + int partition_limit; // quality degradation allowed to fit the 512k limit + // on prediction modes coding (0: no degradation, + // 100: maximum possible degradation). + int emulate_jpeg_size; // If true, compression parameters will be remapped + // to better match the expected output size from + // JPEG compression. Generally, the output size will + // be similar but the degradation will be lower. + int thread_level; // If non-zero, try and use multi-threaded encoding. + int low_memory; // If set, reduce memory usage (but increase CPU use). + + int near_lossless; // Near lossless encoding [0 = max loss .. 100 = off + // (default)]. + int exact; // if non-zero, preserve the exact RGB values under + // transparent area. Otherwise, discard this invisible + // RGB information for better compression. The default + // value is 0. + + int use_delta_palette; // reserved + int use_sharp_yuv; // if needed, use sharp (and slow) RGB->YUV conversion + + int qmin; // minimum permissible quality factor + int qmax; // maximum permissible quality factor +}; + +// Enumerate some predefined settings for WebPConfig, depending on the type +// of source picture. These presets are used when calling WebPConfigPreset(). +typedef enum WebPPreset { + WEBP_PRESET_DEFAULT = 0, // default preset. + WEBP_PRESET_PICTURE, // digital picture, like portrait, inner shot + WEBP_PRESET_PHOTO, // outdoor photograph, with natural lighting + WEBP_PRESET_DRAWING, // hand or line drawing, with high-contrast details + WEBP_PRESET_ICON, // small-sized colorful images + WEBP_PRESET_TEXT // text-like +} WebPPreset; + +// Internal, version-checked, entry point +WEBP_NODISCARD WEBP_EXTERN int WebPConfigInitInternal(WebPConfig*, WebPPreset, + float, int); + +// Should always be called, to initialize a fresh WebPConfig structure before +// modification. Returns false in case of version mismatch. WebPConfigInit() +// must have succeeded before using the 'config' object. +// Note that the default values are lossless=0 and quality=75. +WEBP_NODISCARD static WEBP_INLINE int WebPConfigInit(WebPConfig* config) { + return WebPConfigInitInternal(config, WEBP_PRESET_DEFAULT, 75.f, + WEBP_ENCODER_ABI_VERSION); +} + +// This function will initialize the configuration according to a predefined +// set of parameters (referred to by 'preset') and a given quality factor. +// This function can be called as a replacement to WebPConfigInit(). Will +// return false in case of error. +WEBP_NODISCARD static WEBP_INLINE int WebPConfigPreset(WebPConfig* config, + WebPPreset preset, + float quality) { + return WebPConfigInitInternal(config, preset, quality, + WEBP_ENCODER_ABI_VERSION); +} + +// Activate the lossless compression mode with the desired efficiency level +// between 0 (fastest, lowest compression) and 9 (slower, best compression). +// A good default level is '6', providing a fair tradeoff between compression +// speed and final compressed size. +// This function will overwrite several fields from config: 'method', 'quality' +// and 'lossless'. Returns false in case of parameter error. +WEBP_NODISCARD WEBP_EXTERN int WebPConfigLosslessPreset(WebPConfig* config, + int level); + +// Returns true if 'config' is non-NULL and all configuration parameters are +// within their valid ranges. +WEBP_NODISCARD WEBP_EXTERN int WebPValidateConfig(const WebPConfig* config); + +//------------------------------------------------------------------------------ +// Input / Output +// Structure for storing auxiliary statistics. + +struct WebPAuxStats { + int coded_size; // final size + + float PSNR[5]; // peak-signal-to-noise ratio for Y/U/V/All/Alpha + int block_count[3]; // number of intra4/intra16/skipped macroblocks + int header_bytes[2]; // approximate number of bytes spent for header + // and mode-partition #0 + int residual_bytes[3][4]; // approximate number of bytes spent for + // DC/AC/uv coefficients for each (0..3) segments. + int segment_size[4]; // number of macroblocks in each segments + int segment_quant[4]; // quantizer values for each segments + int segment_level[4]; // filtering strength for each segments [0..63] + + int alpha_data_size; // size of the transparency data + int layer_data_size; // size of the enhancement layer data + + // lossless encoder statistics + uint32_t lossless_features; // bit0:predictor bit1:cross-color transform + // bit2:subtract-green bit3:color indexing + int histogram_bits; // number of precision bits of histogram + int transform_bits; // precision bits for predictor transform + int cache_bits; // number of bits for color cache lookup + int palette_size; // number of color in palette, if used + int lossless_size; // final lossless size + int lossless_hdr_size; // lossless header (transform, huffman etc) size + int lossless_data_size; // lossless image data size + int cross_color_transform_bits; // precision bits for cross-color transform + + uint32_t pad[1]; // padding for later use +}; + +// Signature for output function. Should return true if writing was successful. +// data/data_size is the segment of data to write, and 'picture' is for +// reference (and so one can make use of picture->custom_ptr). +typedef int (*WebPWriterFunction)(const uint8_t* data, size_t data_size, + const WebPPicture* picture); + +// WebPMemoryWrite: a special WebPWriterFunction that writes to memory using +// the following WebPMemoryWriter object (to be set as a custom_ptr). +struct WebPMemoryWriter { + uint8_t* mem; // final buffer (of size 'max_size', larger than 'size'). + size_t size; // final size + size_t max_size; // total capacity + uint32_t pad[1]; // padding for later use +}; + +// The following must be called first before any use. +WEBP_EXTERN void WebPMemoryWriterInit(WebPMemoryWriter* writer); + +// The following must be called to deallocate writer->mem memory. The 'writer' +// object itself is not deallocated. +WEBP_EXTERN void WebPMemoryWriterClear(WebPMemoryWriter* writer); +// The custom writer to be used with WebPMemoryWriter as custom_ptr. Upon +// completion, writer.mem and writer.size will hold the coded data. +// writer.mem must be freed by calling WebPMemoryWriterClear. +WEBP_NODISCARD WEBP_EXTERN int WebPMemoryWrite( + const uint8_t* data, size_t data_size, const WebPPicture* picture); + +// Progress hook, called from time to time to report progress. It can return +// false to request an abort of the encoding process, or true otherwise if +// everything is OK. +typedef int (*WebPProgressHook)(int percent, const WebPPicture* picture); + +// Color spaces. +typedef enum WebPEncCSP { + // chroma sampling + WEBP_YUV420 = 0, // 4:2:0 + WEBP_YUV420A = 4, // alpha channel variant + WEBP_CSP_UV_MASK = 3, // bit-mask to get the UV sampling factors + WEBP_CSP_ALPHA_BIT = 4 // bit that is set if alpha is present +} WebPEncCSP; + +// Encoding error conditions. +typedef enum WebPEncodingError { + VP8_ENC_OK = 0, + VP8_ENC_ERROR_OUT_OF_MEMORY, // memory error allocating objects + VP8_ENC_ERROR_BITSTREAM_OUT_OF_MEMORY, // memory error while flushing bits + VP8_ENC_ERROR_NULL_PARAMETER, // a pointer parameter is NULL + VP8_ENC_ERROR_INVALID_CONFIGURATION, // configuration is invalid + VP8_ENC_ERROR_BAD_DIMENSION, // picture has invalid width/height + VP8_ENC_ERROR_PARTITION0_OVERFLOW, // partition is bigger than 512k + VP8_ENC_ERROR_PARTITION_OVERFLOW, // partition is bigger than 16M + VP8_ENC_ERROR_BAD_WRITE, // error while flushing bytes + VP8_ENC_ERROR_FILE_TOO_BIG, // file is bigger than 4G + VP8_ENC_ERROR_USER_ABORT, // abort request by user + VP8_ENC_ERROR_LAST // list terminator. always last. +} WebPEncodingError; + +// maximum width/height allowed (inclusive), in pixels +#define WEBP_MAX_DIMENSION 16383 + +// Main exchange structure (input samples, output bytes, statistics) +// +// Once WebPPictureInit() has been called, it's ok to make all the INPUT fields +// (use_argb, y/u/v, argb, ...) point to user-owned data, even if +// WebPPictureAlloc() has been called. Depending on the value use_argb, +// it's guaranteed that either *argb or *y/*u/*v content will be kept untouched. +struct WebPPicture { + // INPUT + ////////////// + // Main flag for encoder selecting between ARGB or YUV input. + // It is recommended to use ARGB input (*argb, argb_stride) for lossless + // compression, and YUV input (*y, *u, *v, etc.) for lossy compression + // since these are the respective native colorspace for these formats. + int use_argb; + + // YUV input (mostly used for input to lossy compression) + WebPEncCSP colorspace; // colorspace: should be YUV420 for now (=Y'CbCr). + int width, height; // dimensions (less or equal to WEBP_MAX_DIMENSION) + uint8_t* y, *u, *v; // pointers to luma/chroma planes. + int y_stride, uv_stride; // luma/chroma strides. + uint8_t* a; // pointer to the alpha plane + int a_stride; // stride of the alpha plane + uint32_t pad1[2]; // padding for later use + + // ARGB input (mostly used for input to lossless compression) + uint32_t* argb; // Pointer to argb (32 bit) plane. + int argb_stride; // This is stride in pixels units, not bytes. + uint32_t pad2[3]; // padding for later use + + // OUTPUT + /////////////// + // Byte-emission hook, to store compressed bytes as they are ready. + WebPWriterFunction writer; // can be NULL + void* custom_ptr; // can be used by the writer. + + // map for extra information (only for lossy compression mode) + int extra_info_type; // 1: intra type, 2: segment, 3: quant + // 4: intra-16 prediction mode, + // 5: chroma prediction mode, + // 6: bit cost, 7: distortion + uint8_t* extra_info; // if not NULL, points to an array of size + // ((width + 15) / 16) * ((height + 15) / 16) that + // will be filled with a macroblock map, depending + // on extra_info_type. + + // STATS AND REPORTS + /////////////////////////// + // Pointer to side statistics (updated only if not NULL) + WebPAuxStats* stats; + + // Error code for the latest error encountered during encoding + WebPEncodingError error_code; + + // If not NULL, report progress during encoding. + WebPProgressHook progress_hook; + + void* user_data; // this field is free to be set to any value and + // used during callbacks (like progress-report e.g.). + + uint32_t pad3[3]; // padding for later use + + // Unused for now + uint8_t* pad4, *pad5; + uint32_t pad6[8]; // padding for later use + + // PRIVATE FIELDS + //////////////////// + void* memory_; // row chunk of memory for yuva planes + void* memory_argb_; // and for argb too. + void* pad7[2]; // padding for later use +}; + +// Internal, version-checked, entry point +WEBP_NODISCARD WEBP_EXTERN int WebPPictureInitInternal(WebPPicture*, int); + +// Should always be called, to initialize the structure. Returns false in case +// of version mismatch. WebPPictureInit() must have succeeded before using the +// 'picture' object. +// Note that, by default, use_argb is false and colorspace is WEBP_YUV420. +WEBP_NODISCARD static WEBP_INLINE int WebPPictureInit(WebPPicture* picture) { + return WebPPictureInitInternal(picture, WEBP_ENCODER_ABI_VERSION); +} + +//------------------------------------------------------------------------------ +// WebPPicture utils + +// Convenience allocation / deallocation based on picture->width/height: +// Allocate y/u/v buffers as per colorspace/width/height specification. +// Note! This function will free the previous buffer if needed. +// Returns false in case of memory error. +WEBP_NODISCARD WEBP_EXTERN int WebPPictureAlloc(WebPPicture* picture); + +// Release the memory allocated by WebPPictureAlloc() or WebPPictureImport*(). +// Note that this function does _not_ free the memory used by the 'picture' +// object itself. +// Besides memory (which is reclaimed) all other fields of 'picture' are +// preserved. +WEBP_EXTERN void WebPPictureFree(WebPPicture* picture); + +// Copy the pixels of *src into *dst, using WebPPictureAlloc. Upon return, *dst +// will fully own the copied pixels (this is not a view). The 'dst' picture need +// not be initialized as its content is overwritten. +// Returns false in case of memory allocation error. +WEBP_NODISCARD WEBP_EXTERN int WebPPictureCopy(const WebPPicture* src, + WebPPicture* dst); + +// Compute the single distortion for packed planes of samples. +// 'src' will be compared to 'ref', and the raw distortion stored into +// '*distortion'. The refined metric (log(MSE), log(1 - ssim),...' will be +// stored in '*result'. +// 'x_step' is the horizontal stride (in bytes) between samples. +// 'src/ref_stride' is the byte distance between rows. +// Returns false in case of error (bad parameter, memory allocation error, ...). +WEBP_NODISCARD WEBP_EXTERN int WebPPlaneDistortion( + const uint8_t* src, size_t src_stride, + const uint8_t* ref, size_t ref_stride, int width, int height, size_t x_step, + int type, // 0 = PSNR, 1 = SSIM, 2 = LSIM + float* distortion, float* result); + +// Compute PSNR, SSIM or LSIM distortion metric between two pictures. Results +// are in dB, stored in result[] in the B/G/R/A/All order. The distortion is +// always performed using ARGB samples. Hence if the input is YUV(A), the +// picture will be internally converted to ARGB (just for the measurement). +// Warning: this function is rather CPU-intensive. +WEBP_NODISCARD WEBP_EXTERN int WebPPictureDistortion( + const WebPPicture* src, const WebPPicture* ref, + int metric_type, // 0 = PSNR, 1 = SSIM, 2 = LSIM + float result[5]); + +// self-crops a picture to the rectangle defined by top/left/width/height. +// Returns false in case of memory allocation error, or if the rectangle is +// outside of the source picture. +// The rectangle for the view is defined by the top-left corner pixel +// coordinates (left, top) as well as its width and height. This rectangle +// must be fully be comprised inside the 'src' source picture. If the source +// picture uses the YUV420 colorspace, the top and left coordinates will be +// snapped to even values. +WEBP_NODISCARD WEBP_EXTERN int WebPPictureCrop( + WebPPicture* picture, int left, int top, int width, int height); + +// Extracts a view from 'src' picture into 'dst'. The rectangle for the view +// is defined by the top-left corner pixel coordinates (left, top) as well +// as its width and height. This rectangle must be fully be comprised inside +// the 'src' source picture. If the source picture uses the YUV420 colorspace, +// the top and left coordinates will be snapped to even values. +// Picture 'src' must out-live 'dst' picture. Self-extraction of view is allowed +// ('src' equal to 'dst') as a mean of fast-cropping (but note that doing so, +// the original dimension will be lost). Picture 'dst' need not be initialized +// with WebPPictureInit() if it is different from 'src', since its content will +// be overwritten. +// Returns false in case of invalid parameters. +WEBP_NODISCARD WEBP_EXTERN int WebPPictureView( + const WebPPicture* src, int left, int top, int width, int height, + WebPPicture* dst); + +// Returns true if the 'picture' is actually a view and therefore does +// not own the memory for pixels. +WEBP_EXTERN int WebPPictureIsView(const WebPPicture* picture); + +// Rescale a picture to new dimension width x height. +// If either 'width' or 'height' (but not both) is 0 the corresponding +// dimension will be calculated preserving the aspect ratio. +// No gamma correction is applied. +// Returns false in case of error (invalid parameter or insufficient memory). +WEBP_NODISCARD WEBP_EXTERN int WebPPictureRescale(WebPPicture* picture, + int width, int height); + +// Colorspace conversion function to import RGB samples. +// Previous buffer will be free'd, if any. +// *rgb buffer should have a size of at least height * rgb_stride. +// Returns false in case of memory error. +WEBP_NODISCARD WEBP_EXTERN int WebPPictureImportRGB( + WebPPicture* picture, const uint8_t* rgb, int rgb_stride); +// Same, but for RGBA buffer. +WEBP_NODISCARD WEBP_EXTERN int WebPPictureImportRGBA( + WebPPicture* picture, const uint8_t* rgba, int rgba_stride); +// Same, but for RGBA buffer. Imports the RGB direct from the 32-bit format +// input buffer ignoring the alpha channel. Avoids needing to copy the data +// to a temporary 24-bit RGB buffer to import the RGB only. +WEBP_NODISCARD WEBP_EXTERN int WebPPictureImportRGBX( + WebPPicture* picture, const uint8_t* rgbx, int rgbx_stride); + +// Variants of the above, but taking BGR(A|X) input. +WEBP_NODISCARD WEBP_EXTERN int WebPPictureImportBGR( + WebPPicture* picture, const uint8_t* bgr, int bgr_stride); +WEBP_NODISCARD WEBP_EXTERN int WebPPictureImportBGRA( + WebPPicture* picture, const uint8_t* bgra, int bgra_stride); +WEBP_NODISCARD WEBP_EXTERN int WebPPictureImportBGRX( + WebPPicture* picture, const uint8_t* bgrx, int bgrx_stride); + +// Converts picture->argb data to the YUV420A format. The 'colorspace' +// parameter is deprecated and should be equal to WEBP_YUV420. +// Upon return, picture->use_argb is set to false. The presence of real +// non-opaque transparent values is detected, and 'colorspace' will be +// adjusted accordingly. Note that this method is lossy. +// Returns false in case of error. +WEBP_NODISCARD WEBP_EXTERN int WebPPictureARGBToYUVA( + WebPPicture* picture, WebPEncCSP /*colorspace = WEBP_YUV420*/); + +// Same as WebPPictureARGBToYUVA(), but the conversion is done using +// pseudo-random dithering with a strength 'dithering' between +// 0.0 (no dithering) and 1.0 (maximum dithering). This is useful +// for photographic picture. +WEBP_NODISCARD WEBP_EXTERN int WebPPictureARGBToYUVADithered( + WebPPicture* picture, WebPEncCSP colorspace, float dithering); + +// Performs 'sharp' RGBA->YUVA420 downsampling and colorspace conversion +// Downsampling is handled with extra care in case of color clipping. This +// method is roughly 2x slower than WebPPictureARGBToYUVA() but produces better +// and sharper YUV representation. +// Returns false in case of error. +WEBP_NODISCARD WEBP_EXTERN int WebPPictureSharpARGBToYUVA(WebPPicture* picture); +// kept for backward compatibility: +WEBP_NODISCARD WEBP_EXTERN int WebPPictureSmartARGBToYUVA(WebPPicture* picture); + +// Converts picture->yuv to picture->argb and sets picture->use_argb to true. +// The input format must be YUV_420 or YUV_420A. The conversion from YUV420 to +// ARGB incurs a small loss too. +// Note that the use of this colorspace is discouraged if one has access to the +// raw ARGB samples, since using YUV420 is comparatively lossy. +// Returns false in case of error. +WEBP_NODISCARD WEBP_EXTERN int WebPPictureYUVAToARGB(WebPPicture* picture); + +// Helper function: given a width x height plane of RGBA or YUV(A) samples +// clean-up or smoothen the YUV or RGB samples under fully transparent area, +// to help compressibility (no guarantee, though). +WEBP_EXTERN void WebPCleanupTransparentArea(WebPPicture* picture); + +// Scan the picture 'picture' for the presence of non fully opaque alpha values. +// Returns true in such case. Otherwise returns false (indicating that the +// alpha plane can be ignored altogether e.g.). +WEBP_EXTERN int WebPPictureHasTransparency(const WebPPicture* picture); + +// Remove the transparency information (if present) by blending the color with +// the background color 'background_rgb' (specified as 24bit RGB triplet). +// After this call, all alpha values are reset to 0xff. +WEBP_EXTERN void WebPBlendAlpha(WebPPicture* picture, uint32_t background_rgb); + +//------------------------------------------------------------------------------ +// Main call + +// Main encoding call, after config and picture have been initialized. +// 'picture' must be less than 16384x16384 in dimension (cf WEBP_MAX_DIMENSION), +// and the 'config' object must be a valid one. +// Returns false in case of error, true otherwise. +// In case of error, picture->error_code is updated accordingly. +// 'picture' can hold the source samples in both YUV(A) or ARGB input, depending +// on the value of 'picture->use_argb'. It is highly recommended to use +// the former for lossy encoding, and the latter for lossless encoding +// (when config.lossless is true). Automatic conversion from one format to +// another is provided but they both incur some loss. +WEBP_NODISCARD WEBP_EXTERN int WebPEncode(const WebPConfig* config, + WebPPicture* picture); + +//------------------------------------------------------------------------------ + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif // WEBP_WEBP_ENCODE_H_ diff --git a/packages/core/src/zig/vendor/libwebp/src/webp/format_constants.h b/packages/core/src/zig/vendor/libwebp/src/webp/format_constants.h new file mode 100644 index 0000000000..9b007c8a9d --- /dev/null +++ b/packages/core/src/zig/vendor/libwebp/src/webp/format_constants.h @@ -0,0 +1,92 @@ +// Copyright 2012 Google Inc. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the COPYING file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +// ----------------------------------------------------------------------------- +// +// Internal header for constants related to WebP file format. +// +// Author: Urvang (urvang@google.com) + +#ifndef WEBP_WEBP_FORMAT_CONSTANTS_H_ +#define WEBP_WEBP_FORMAT_CONSTANTS_H_ + +// Create fourcc of the chunk from the chunk tag characters. +#define MKFOURCC(a, b, c, d) ((a) | (b) << 8 | (c) << 16 | (uint32_t)(d) << 24) + +// VP8 related constants. +#define VP8_SIGNATURE 0x9d012a // Signature in VP8 data. +#define VP8_MAX_PARTITION0_SIZE (1 << 19) // max size of mode partition +#define VP8_MAX_PARTITION_SIZE (1 << 24) // max size for token partition +#define VP8_FRAME_HEADER_SIZE 10 // Size of the frame header within VP8 data. + +// VP8L related constants. +#define VP8L_SIGNATURE_SIZE 1 // VP8L signature size. +#define VP8L_MAGIC_BYTE 0x2f // VP8L signature byte. +#define VP8L_IMAGE_SIZE_BITS 14 // Number of bits used to store + // width and height. +#define VP8L_VERSION_BITS 3 // 3 bits reserved for version. +#define VP8L_VERSION 0 // version 0 +#define VP8L_FRAME_HEADER_SIZE 5 // Size of the VP8L frame header. + +#define MAX_PALETTE_SIZE 256 +#define MAX_CACHE_BITS 11 +#define HUFFMAN_CODES_PER_META_CODE 5 +#define ARGB_BLACK 0xff000000 + +#define DEFAULT_CODE_LENGTH 8 +#define MAX_ALLOWED_CODE_LENGTH 15 + +#define NUM_LITERAL_CODES 256 +#define NUM_LENGTH_CODES 24 +#define NUM_DISTANCE_CODES 40 +#define CODE_LENGTH_CODES 19 + +#define MIN_HUFFMAN_BITS 2 // min number of Huffman bits +#define NUM_HUFFMAN_BITS 3 + +// the maximum number of bits defining a transform is +// MIN_TRANSFORM_BITS + (1 << NUM_TRANSFORM_BITS) - 1 +#define MIN_TRANSFORM_BITS 2 +#define NUM_TRANSFORM_BITS 3 + +#define TRANSFORM_PRESENT 1 // The bit to be written when next data + // to be read is a transform. +#define NUM_TRANSFORMS 4 // Maximum number of allowed transform + // in a bitstream. +typedef enum { + PREDICTOR_TRANSFORM = 0, + CROSS_COLOR_TRANSFORM = 1, + SUBTRACT_GREEN_TRANSFORM = 2, + COLOR_INDEXING_TRANSFORM = 3 +} VP8LImageTransformType; + +// Alpha related constants. +#define ALPHA_HEADER_LEN 1 +#define ALPHA_NO_COMPRESSION 0 +#define ALPHA_LOSSLESS_COMPRESSION 1 +#define ALPHA_PREPROCESSED_LEVELS 1 + +// Mux related constants. +#define TAG_SIZE 4 // Size of a chunk tag (e.g. "VP8L"). +#define CHUNK_SIZE_BYTES 4 // Size needed to store chunk's size. +#define CHUNK_HEADER_SIZE 8 // Size of a chunk header. +#define RIFF_HEADER_SIZE 12 // Size of the RIFF header ("RIFFnnnnWEBP"). +#define ANMF_CHUNK_SIZE 16 // Size of an ANMF chunk. +#define ANIM_CHUNK_SIZE 6 // Size of an ANIM chunk. +#define VP8X_CHUNK_SIZE 10 // Size of a VP8X chunk. + +#define MAX_CANVAS_SIZE (1 << 24) // 24-bit max for VP8X width/height. +#define MAX_IMAGE_AREA (1ULL << 32) // 32-bit max for width x height. +#define MAX_LOOP_COUNT (1 << 16) // maximum value for loop-count +#define MAX_DURATION (1 << 24) // maximum duration +#define MAX_POSITION_OFFSET (1 << 24) // maximum frame x/y offset + +// Maximum chunk payload is such that adding the header and padding won't +// overflow a uint32_t. +#define MAX_CHUNK_PAYLOAD (~0U - CHUNK_HEADER_SIZE - 1) + +#endif // WEBP_WEBP_FORMAT_CONSTANTS_H_ diff --git a/packages/core/src/zig/vendor/libwebp/src/webp/mux_types.h b/packages/core/src/zig/vendor/libwebp/src/webp/mux_types.h new file mode 100644 index 0000000000..c1bbad3971 --- /dev/null +++ b/packages/core/src/zig/vendor/libwebp/src/webp/mux_types.h @@ -0,0 +1,100 @@ +// Copyright 2012 Google Inc. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the COPYING file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +// ----------------------------------------------------------------------------- +// +// Data-types common to the mux and demux libraries. +// +// Author: Urvang (urvang@google.com) + +#ifndef WEBP_WEBP_MUX_TYPES_H_ +#define WEBP_WEBP_MUX_TYPES_H_ + +#include // memset() + +#include "./types.h" + +#ifdef __cplusplus +extern "C" { +#endif + +// Note: forward declaring enumerations is not allowed in (strict) C and C++, +// the types are left here for reference. +// typedef enum WebPFeatureFlags WebPFeatureFlags; +// typedef enum WebPMuxAnimDispose WebPMuxAnimDispose; +// typedef enum WebPMuxAnimBlend WebPMuxAnimBlend; +typedef struct WebPData WebPData; + +// VP8X Feature Flags. +typedef enum WebPFeatureFlags { + ANIMATION_FLAG = 0x00000002, + XMP_FLAG = 0x00000004, + EXIF_FLAG = 0x00000008, + ALPHA_FLAG = 0x00000010, + ICCP_FLAG = 0x00000020, + + ALL_VALID_FLAGS = 0x0000003e +} WebPFeatureFlags; + +// Dispose method (animation only). Indicates how the area used by the current +// frame is to be treated before rendering the next frame on the canvas. +typedef enum WebPMuxAnimDispose { + WEBP_MUX_DISPOSE_NONE, // Do not dispose. + WEBP_MUX_DISPOSE_BACKGROUND // Dispose to background color. +} WebPMuxAnimDispose; + +// Blend operation (animation only). Indicates how transparent pixels of the +// current frame are blended with those of the previous canvas. +typedef enum WebPMuxAnimBlend { + WEBP_MUX_BLEND, // Blend. + WEBP_MUX_NO_BLEND // Do not blend. +} WebPMuxAnimBlend; + +// Data type used to describe 'raw' data, e.g., chunk data +// (ICC profile, metadata) and WebP compressed image data. +// 'bytes' memory must be allocated using WebPMalloc() and such. +struct WebPData { + const uint8_t* bytes; + size_t size; +}; + +// Initializes the contents of the 'webp_data' object with default values. +static WEBP_INLINE void WebPDataInit(WebPData* webp_data) { + if (webp_data != NULL) { + memset(webp_data, 0, sizeof(*webp_data)); + } +} + +// Clears the contents of the 'webp_data' object by calling WebPFree(). +// Does not deallocate the object itself. +static WEBP_INLINE void WebPDataClear(WebPData* webp_data) { + if (webp_data != NULL) { + WebPFree((void*)webp_data->bytes); + WebPDataInit(webp_data); + } +} + +// Allocates necessary storage for 'dst' and copies the contents of 'src'. +// Returns true on success. +WEBP_NODISCARD static WEBP_INLINE int WebPDataCopy(const WebPData* src, + WebPData* dst) { + if (src == NULL || dst == NULL) return 0; + WebPDataInit(dst); + if (src->bytes != NULL && src->size != 0) { + dst->bytes = (uint8_t*)WebPMalloc(src->size); + if (dst->bytes == NULL) return 0; + memcpy((void*)dst->bytes, src->bytes, src->size); + dst->size = src->size; + } + return 1; +} + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif // WEBP_WEBP_MUX_TYPES_H_ diff --git a/packages/core/src/zig/vendor/libwebp/src/webp/types.h b/packages/core/src/zig/vendor/libwebp/src/webp/types.h new file mode 100644 index 0000000000..549a0a7d27 --- /dev/null +++ b/packages/core/src/zig/vendor/libwebp/src/webp/types.h @@ -0,0 +1,93 @@ +// Copyright 2010 Google Inc. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the COPYING file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +// ----------------------------------------------------------------------------- +// +// Common types + memory wrappers +// +// Author: Skal (pascal.massimino@gmail.com) + +#ifndef WEBP_WEBP_TYPES_H_ +#define WEBP_WEBP_TYPES_H_ + +#include // IWYU pragma: export for size_t + +#ifndef _MSC_VER +#include // IWYU pragma: export +#if defined(__cplusplus) || !defined(__STRICT_ANSI__) || \ + (defined(__STDC_VERSION__) && __STDC_VERSION__ >= 199901L) +#define WEBP_INLINE inline +#else +#define WEBP_INLINE +#endif +#else +typedef signed char int8_t; +typedef unsigned char uint8_t; +typedef signed short int16_t; +typedef unsigned short uint16_t; +typedef signed int int32_t; +typedef unsigned int uint32_t; +typedef unsigned long long int uint64_t; +typedef long long int int64_t; +#define WEBP_INLINE __forceinline +#endif /* _MSC_VER */ + +#ifndef WEBP_NODISCARD +#if defined(WEBP_ENABLE_NODISCARD) && WEBP_ENABLE_NODISCARD +#if (defined(__cplusplus) && __cplusplus >= 201703L) || \ + (defined(__STDC_VERSION__) && __STDC_VERSION__ >= 202311L) +#define WEBP_NODISCARD [[nodiscard]] +#else +// gcc's __attribute__((warn_unused_result)) does not work for enums. +#if defined(__clang__) && defined(__has_attribute) +#if __has_attribute(warn_unused_result) +#define WEBP_NODISCARD __attribute__((warn_unused_result)) +#else +#define WEBP_NODISCARD +#endif /* __has_attribute(warn_unused_result) */ +#else +#define WEBP_NODISCARD +#endif /* defined(__clang__) && defined(__has_attribute) */ +#endif /* (defined(__cplusplus) && __cplusplus >= 201700L) || + (defined(__STDC_VERSION__) && __STDC_VERSION__ >= 202311L) */ +#else +#define WEBP_NODISCARD +#endif /* defined(WEBP_ENABLE_NODISCARD) && WEBP_ENABLE_NODISCARD */ +#endif /* WEBP_NODISCARD */ + +#ifndef WEBP_EXTERN +// This explicitly marks library functions and allows for changing the +// signature for e.g., Windows DLL builds. +# if defined(_WIN32) && defined(WEBP_DLL) +# define WEBP_EXTERN __declspec(dllexport) +# elif defined(__GNUC__) && __GNUC__ >= 4 +# define WEBP_EXTERN extern __attribute__ ((visibility ("default"))) +# else +# define WEBP_EXTERN extern +# endif /* defined(_WIN32) && defined(WEBP_DLL) */ +#endif /* WEBP_EXTERN */ + +// Macro to check ABI compatibility (same major revision number) +#define WEBP_ABI_IS_INCOMPATIBLE(a, b) (((a) >> 8) != ((b) >> 8)) + +#ifdef __cplusplus +extern "C" { +#endif + +// Allocates 'size' bytes of memory. Returns NULL upon error. Memory +// must be deallocated by calling WebPFree(). This function is made available +// by the core 'libwebp' library. +WEBP_NODISCARD WEBP_EXTERN void* WebPMalloc(size_t size); + +// Releases memory returned by the WebPDecode*() functions (from decode.h). +WEBP_EXTERN void WebPFree(void* ptr); + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif // WEBP_WEBP_TYPES_H_ diff --git a/packages/core/src/zig/vendor/stb/LICENSE b/packages/core/src/zig/vendor/stb/LICENSE new file mode 100644 index 0000000000..a77ae91f3e --- /dev/null +++ b/packages/core/src/zig/vendor/stb/LICENSE @@ -0,0 +1,37 @@ +This software is available under 2 licenses -- choose whichever you prefer. +------------------------------------------------------------------------------ +ALTERNATIVE A - MIT License +Copyright (c) 2017 Sean Barrett +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +------------------------------------------------------------------------------ +ALTERNATIVE B - Public Domain (www.unlicense.org) +This is free and unencumbered software released into the public domain. +Anyone is free to copy, modify, publish, use, compile, sell, or distribute this +software, either in source code form or as a compiled binary, for any purpose, +commercial or non-commercial, and by any means. +In jurisdictions that recognize copyright laws, the author or authors of this +software dedicate any and all copyright interest in the software to the public +domain. We make this dedication for the benefit of the public at large and to +the detriment of our heirs and successors. We intend this dedication to be an +overt act of relinquishment in perpetuity of all present and future rights to +this software under copyright law. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN +ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/packages/core/src/zig/vendor/stb/README.md b/packages/core/src/zig/vendor/stb/README.md new file mode 100644 index 0000000000..5985227629 --- /dev/null +++ b/packages/core/src/zig/vendor/stb/README.md @@ -0,0 +1,77 @@ +# stb_image_resize2 + +Pinned to commit `904aa67e1e2d1dec92959df63e700b166d5c1022`, version 2.18. + +Upstream source SHA-256: +`173e654634f6ccaad98f603e686ea212eec1fe8ea6d2a5e5e8056efa10ae3880` + +Patched SHA-256: +`3cfc10a3aa7287fa1f1360df360b22e63b2e3426965d7696f8b5c273bc810d55` + +## Local coefficient-copy alignment fix + +Upstream's scalar `STBIR_MOVE_*` coefficient-copy macros access `float` storage +through `uint32_t *` and `uint64_t *` casts. Coefficient rows can be only +4-byte aligned, so the 64-bit accesses can be unaligned and are undefined +behavior. They trapped under Zig's safety instrumentation on Apple Silicon. + +`patches/stb_image_resize2-alignment.patch` replaces those scalar copies with +`memcpy`, which supports unaligned source and destination addresses. This is a +source-level correctness fix. It requires no sanitizer suppression and is +independent of the SIMD sRGB table issue below. + +## Accepted SIMD sRGB bounds exception + +The upstream SIMD linear-to-sRGB encoder has three lookup sites that pass +`fp32_to_srgb8_tab4 - (127-13)*8`, a pointer 912 elements before the 104-entry +table. Inputs are clamped before lookup. The resulting indexes range from 912 +through 1015, so the effective reads resolve to table elements 0 through 103. +This is the upstream idiom discussed in +[`nothings/stb#1616`](https://github.com/nothings/stb/issues/1616). It does not +perform a physical read outside that table range, but forming the pre-array +pointer is outside C's array-pointer model. Zig/Clang bounds instrumentation +therefore aborts before the effective lookup can complete. + +OpenTUI isolates the implementation in `image-resize-shim.c` and passes +`-fno-sanitize=bounds` only for that translation unit. Pointer-overflow, +alignment, and every other sanitizer category remain enabled when the build +enables them. In particular, do not add `pointer-overflow` to this suppression: +the recorded regression passes with pointer-overflow instrumentation enabled. +The decoder implementation remains in `image-shim.c` without this exception. + +The native test `area resize upscales tiny sources exactly` covers the SIMD RGBA +sRGB path on supported native x86_64/aarch64 builds. Its `1x1 -> 12x2` case +verifies every output RGBA pixel. Its `1x1 -> 1x7` case exercises a mixed-axis +tiny resize but checks only dimensions and the first red sample. This evidence +does not cover every SIMD lookup variant, architecture, or resize path, and it +is not a general memory-safety proof. + +The narrow strict-C/bounds-instrumentation exception described above is +accepted project policy. It is not a known physical out-of-range table read; +that statement applies only to these clamped lookups. Do not report the scoped +`-fno-sanitize=bounds` flag as an accidental sanitizer disablement, do not +conflate it with the resolved alignment issue, and do not broaden its scope. + +To close the exception permanently: + +1. Patch all three SIMD lookup sites so the pointer remains at + `fp32_to_srgb8_tab4` and the 912-element adjustment is applied to the integer + indexes. +2. Apply that patch reproducibly from `../update.sh` and update the patched + SHA-256 recorded here and in the updater. +3. Remove the resize translation unit's bounds sanitizer suppression from + `build.zig`. +4. Run `area resize upscales tiny sources exactly` with normal bounds + instrumentation, then run `bun run test:native` and `bun run build:native`. + +`stb_image.h` is pinned to commit +`f0569113c93ad095470c54bf34a17b36646bbbb5`, version 2.30, and compiled with +only its JPEG decoder enabled. SHA-256: +`594c2fe35d49488b4382dbfaec8f98366defca819d916ac95becf3e75f4200b3`. + +Patched SHA-256: `1657895e86c730668cc5af6d3c8ae8f80b67c64f2ade81c44c40cee70fba555e`. + +`STBI_STRICT_JPEG` is an OpenTUI-local extension that rejects streams when +decoding needs synthetic zero bits after reaching a marker or end of input. + +The exact local changes are in `patches/`. Update with `bun run vendor:update:images` from `packages/core`; see `../README.md`. diff --git a/packages/core/src/zig/vendor/stb/patches/stb_image-strict-jpeg.patch b/packages/core/src/zig/vendor/stb/patches/stb_image-strict-jpeg.patch new file mode 100644 index 0000000000..f02213c4fa --- /dev/null +++ b/packages/core/src/zig/vendor/stb/patches/stb_image-strict-jpeg.patch @@ -0,0 +1,55 @@ +diff --git a/stb_image.h b/stb_image.h +--- a/stb_image.h ++++ b/stb_image.h +@@ -1980,6 +1980,10 @@ typedef struct + int code_bits; // number of valid bits + unsigned char marker; // marker seen while filling entropy buffer + int nomore; // flag if we saw a marker so must stop ++#ifdef STBI_STRICT_JPEG ++ int truncated; // flag if decoding consumed synthetic bits ++ int synthetic_bits; // zero bits added after a marker or EOF ++#endif + + int progressive; + int spec_start; +@@ -2075,7 +2079,17 @@ static void stbi__build_fast_ac(stbi__int16 *fast_ac, stbi__huffman *h) + static void stbi__grow_buffer_unsafe(stbi__jpeg *j) + { + do { +- unsigned int b = j->nomore ? 0 : stbi__get8(j->s); ++ unsigned int b; ++#ifdef STBI_STRICT_JPEG ++ if (j->nomore || stbi__at_eof(j->s)) { ++ j->synthetic_bits += 8; ++ b = 0; ++ } else { ++ b = stbi__get8(j->s); ++ } ++#else ++ b = j->nomore ? 0 : stbi__get8(j->s); ++#endif + if (b == 0xff) { + int c = stbi__get8(j->s); + while (c == 0xff) c = stbi__get8(j->s); // consume fill bytes +@@ -2935,6 +2949,10 @@ static stbi_uc stbi__get_marker(stbi__jpeg *j) + // the dc prediction + static void stbi__jpeg_reset(stbi__jpeg *j) + { ++#ifdef STBI_STRICT_JPEG ++ if (j->synthetic_bits > j->code_bits) j->truncated = 1; ++ j->synthetic_bits = 0; ++#endif + j->code_bits = 0; + j->code_buffer = 0; + j->nomore = 0; +@@ -3441,6 +3459,10 @@ static int stbi__decode_jpeg_image(stbi__jpeg *j) + m = stbi__get_marker(j); + } + } ++#ifdef STBI_STRICT_JPEG ++ if (j->synthetic_bits > j->code_bits) j->truncated = 1; ++ if (j->truncated) return stbi__err("truncated entropy data", "Corrupt JPEG"); ++#endif + if (j->progressive) + stbi__jpeg_finish(j); + return 1; diff --git a/packages/core/src/zig/vendor/stb/patches/stb_image_resize2-alignment.patch b/packages/core/src/zig/vendor/stb/patches/stb_image_resize2-alignment.patch new file mode 100644 index 0000000000..6fbcb9bd50 --- /dev/null +++ b/packages/core/src/zig/vendor/stb/patches/stb_image_resize2-alignment.patch @@ -0,0 +1,19 @@ +diff --git a/stb_image_resize2.h b/stb_image_resize2.h +--- a/stb_image_resize2.h ++++ b/stb_image_resize2.h +@@ -3695,12 +3695,12 @@ static void stbir__cleanup_gathered_coefficients( stbir_edge edge, stbir__filter + + static int stbir__pack_coefficients( int num_contributors, stbir__contributors* contributors, float * coefficents, int coefficient_width, int widest, int row0, int row1 ) + { +- #define STBIR_MOVE_1( dest, src ) { STBIR_NO_UNROLL(dest); ((stbir_uint32*)(dest))[0] = ((stbir_uint32*)(src))[0]; } +- #define STBIR_MOVE_2( dest, src ) { STBIR_NO_UNROLL(dest); ((stbir_uint64*)(dest))[0] = ((stbir_uint64*)(src))[0]; } ++ #define STBIR_MOVE_1( dest, src ) { STBIR_NO_UNROLL(dest); memcpy((dest), (src), 1 * sizeof(float)); } ++ #define STBIR_MOVE_2( dest, src ) { STBIR_NO_UNROLL(dest); memcpy((dest), (src), 2 * sizeof(float)); } + #ifdef STBIR_SIMD + #define STBIR_MOVE_4( dest, src ) { stbir__simdf t; STBIR_NO_UNROLL(dest); stbir__simdf_load( t, src ); stbir__simdf_store( dest, t ); } + #else +- #define STBIR_MOVE_4( dest, src ) { STBIR_NO_UNROLL(dest); ((stbir_uint64*)(dest))[0] = ((stbir_uint64*)(src))[0]; ((stbir_uint64*)(dest))[1] = ((stbir_uint64*)(src))[1]; } ++ #define STBIR_MOVE_4( dest, src ) { STBIR_NO_UNROLL(dest); memcpy((dest), (src), 4 * sizeof(float)); } + #endif + + int row_end = row1 + 1; diff --git a/packages/core/src/zig/vendor/stb/stb_image.h b/packages/core/src/zig/vendor/stb/stb_image.h new file mode 100644 index 0000000000..ff70f525ee --- /dev/null +++ b/packages/core/src/zig/vendor/stb/stb_image.h @@ -0,0 +1,8010 @@ +/* stb_image - v2.30 - public domain image loader - http://nothings.org/stb + no warranty implied; use at your own risk + + Do this: + #define STB_IMAGE_IMPLEMENTATION + before you include this file in *one* C or C++ file to create the implementation. + + // i.e. it should look like this: + #include ... + #include ... + #include ... + #define STB_IMAGE_IMPLEMENTATION + #include "stb_image.h" + + You can #define STBI_ASSERT(x) before the #include to avoid using assert.h. + And #define STBI_MALLOC, STBI_REALLOC, and STBI_FREE to avoid using malloc,realloc,free + + + QUICK NOTES: + Primarily of interest to game developers and other people who can + avoid problematic images and only need the trivial interface + + JPEG baseline & progressive (12 bpc/arithmetic not supported, same as stock IJG lib) + PNG 1/2/4/8/16-bit-per-channel + + TGA (not sure what subset, if a subset) + BMP non-1bpp, non-RLE + PSD (composited view only, no extra channels, 8/16 bit-per-channel) + + GIF (*comp always reports as 4-channel) + HDR (radiance rgbE format) + PIC (Softimage PIC) + PNM (PPM and PGM binary only) + + Animated GIF still needs a proper API, but here's one way to do it: + http://gist.github.com/urraka/685d9a6340b26b830d49 + + - decode from memory or through FILE (define STBI_NO_STDIO to remove code) + - decode from arbitrary I/O callbacks + - SIMD acceleration on x86/x64 (SSE2) and ARM (NEON) + + Full documentation under "DOCUMENTATION" below. + + +LICENSE + + See end of file for license information. + +RECENT REVISION HISTORY: + + 2.30 (2024-05-31) avoid erroneous gcc warning + 2.29 (2023-05-xx) optimizations + 2.28 (2023-01-29) many error fixes, security errors, just tons of stuff + 2.27 (2021-07-11) document stbi_info better, 16-bit PNM support, bug fixes + 2.26 (2020-07-13) many minor fixes + 2.25 (2020-02-02) fix warnings + 2.24 (2020-02-02) fix warnings; thread-local failure_reason and flip_vertically + 2.23 (2019-08-11) fix clang static analysis warning + 2.22 (2019-03-04) gif fixes, fix warnings + 2.21 (2019-02-25) fix typo in comment + 2.20 (2019-02-07) support utf8 filenames in Windows; fix warnings and platform ifdefs + 2.19 (2018-02-11) fix warning + 2.18 (2018-01-30) fix warnings + 2.17 (2018-01-29) bugfix, 1-bit BMP, 16-bitness query, fix warnings + 2.16 (2017-07-23) all functions have 16-bit variants; optimizations; bugfixes + 2.15 (2017-03-18) fix png-1,2,4; all Imagenet JPGs; no runtime SSE detection on GCC + 2.14 (2017-03-03) remove deprecated STBI_JPEG_OLD; fixes for Imagenet JPGs + 2.13 (2016-12-04) experimental 16-bit API, only for PNG so far; fixes + 2.12 (2016-04-02) fix typo in 2.11 PSD fix that caused crashes + 2.11 (2016-04-02) 16-bit PNGS; enable SSE2 in non-gcc x64 + RGB-format JPEG; remove white matting in PSD; + allocate large structures on the stack; + correct channel count for PNG & BMP + 2.10 (2016-01-22) avoid warning introduced in 2.09 + 2.09 (2016-01-16) 16-bit TGA; comments in PNM files; STBI_REALLOC_SIZED + + See end of file for full revision history. + + + ============================ Contributors ========================= + + Image formats Extensions, features + Sean Barrett (jpeg, png, bmp) Jetro Lauha (stbi_info) + Nicolas Schulz (hdr, psd) Martin "SpartanJ" Golini (stbi_info) + Jonathan Dummer (tga) James "moose2000" Brown (iPhone PNG) + Jean-Marc Lienher (gif) Ben "Disch" Wenger (io callbacks) + Tom Seddon (pic) Omar Cornut (1/2/4-bit PNG) + Thatcher Ulrich (psd) Nicolas Guillemot (vertical flip) + Ken Miller (pgm, ppm) Richard Mitton (16-bit PSD) + github:urraka (animated gif) Junggon Kim (PNM comments) + Christopher Forseth (animated gif) Daniel Gibson (16-bit TGA) + socks-the-fox (16-bit PNG) + Jeremy Sawicki (handle all ImageNet JPGs) + Optimizations & bugfixes Mikhail Morozov (1-bit BMP) + Fabian "ryg" Giesen Anael Seghezzi (is-16-bit query) + Arseny Kapoulkine Simon Breuss (16-bit PNM) + John-Mark Allen + Carmelo J Fdez-Aguera + + Bug & warning fixes + Marc LeBlanc David Woo Guillaume George Martins Mozeiko + Christpher Lloyd Jerry Jansson Joseph Thomson Blazej Dariusz Roszkowski + Phil Jordan Dave Moore Roy Eltham + Hayaki Saito Nathan Reed Won Chun + Luke Graham Johan Duparc Nick Verigakis the Horde3D community + Thomas Ruf Ronny Chevalier github:rlyeh + Janez Zemva John Bartholomew Michal Cichon github:romigrou + Jonathan Blow Ken Hamada Tero Hanninen github:svdijk + Eugene Golushkov Laurent Gomila Cort Stratton github:snagar + Aruelien Pocheville Sergio Gonzalez Thibault Reuille github:Zelex + Cass Everitt Ryamond Barbiero github:grim210 + Paul Du Bois Engin Manap Aldo Culquicondor github:sammyhw + Philipp Wiesemann Dale Weiler Oriol Ferrer Mesia github:phprus + Josh Tobin Neil Bickford Matthew Gregan github:poppolopoppo + Julian Raschke Gregory Mullen Christian Floisand github:darealshinji + Baldur Karlsson Kevin Schmidt JR Smith github:Michaelangel007 + Brad Weinberger Matvey Cherevko github:mosra + Luca Sas Alexander Veselov Zack Middleton [reserved] + Ryan C. Gordon [reserved] [reserved] + DO NOT ADD YOUR NAME HERE + + Jacko Dirks + + To add your name to the credits, pick a random blank space in the middle and fill it. + 80% of merge conflicts on stb PRs are due to people adding their name at the end + of the credits. +*/ + +#ifndef STBI_INCLUDE_STB_IMAGE_H +#define STBI_INCLUDE_STB_IMAGE_H + +// DOCUMENTATION +// +// Limitations: +// - no 12-bit-per-channel JPEG +// - no JPEGs with arithmetic coding +// - GIF always returns *comp=4 +// +// Basic usage (see HDR discussion below for HDR usage): +// int x,y,n; +// unsigned char *data = stbi_load(filename, &x, &y, &n, 0); +// // ... process data if not NULL ... +// // ... x = width, y = height, n = # 8-bit components per pixel ... +// // ... replace '0' with '1'..'4' to force that many components per pixel +// // ... but 'n' will always be the number that it would have been if you said 0 +// stbi_image_free(data); +// +// Standard parameters: +// int *x -- outputs image width in pixels +// int *y -- outputs image height in pixels +// int *channels_in_file -- outputs # of image components in image file +// int desired_channels -- if non-zero, # of image components requested in result +// +// The return value from an image loader is an 'unsigned char *' which points +// to the pixel data, or NULL on an allocation failure or if the image is +// corrupt or invalid. The pixel data consists of *y scanlines of *x pixels, +// with each pixel consisting of N interleaved 8-bit components; the first +// pixel pointed to is top-left-most in the image. There is no padding between +// image scanlines or between pixels, regardless of format. The number of +// components N is 'desired_channels' if desired_channels is non-zero, or +// *channels_in_file otherwise. If desired_channels is non-zero, +// *channels_in_file has the number of components that _would_ have been +// output otherwise. E.g. if you set desired_channels to 4, you will always +// get RGBA output, but you can check *channels_in_file to see if it's trivially +// opaque because e.g. there were only 3 channels in the source image. +// +// An output image with N components has the following components interleaved +// in this order in each pixel: +// +// N=#comp components +// 1 grey +// 2 grey, alpha +// 3 red, green, blue +// 4 red, green, blue, alpha +// +// If image loading fails for any reason, the return value will be NULL, +// and *x, *y, *channels_in_file will be unchanged. The function +// stbi_failure_reason() can be queried for an extremely brief, end-user +// unfriendly explanation of why the load failed. Define STBI_NO_FAILURE_STRINGS +// to avoid compiling these strings at all, and STBI_FAILURE_USERMSG to get slightly +// more user-friendly ones. +// +// Paletted PNG, BMP, GIF, and PIC images are automatically depalettized. +// +// To query the width, height and component count of an image without having to +// decode the full file, you can use the stbi_info family of functions: +// +// int x,y,n,ok; +// ok = stbi_info(filename, &x, &y, &n); +// // returns ok=1 and sets x, y, n if image is a supported format, +// // 0 otherwise. +// +// Note that stb_image pervasively uses ints in its public API for sizes, +// including sizes of memory buffers. This is now part of the API and thus +// hard to change without causing breakage. As a result, the various image +// loaders all have certain limits on image size; these differ somewhat +// by format but generally boil down to either just under 2GB or just under +// 1GB. When the decoded image would be larger than this, stb_image decoding +// will fail. +// +// Additionally, stb_image will reject image files that have any of their +// dimensions set to a larger value than the configurable STBI_MAX_DIMENSIONS, +// which defaults to 2**24 = 16777216 pixels. Due to the above memory limit, +// the only way to have an image with such dimensions load correctly +// is for it to have a rather extreme aspect ratio. Either way, the +// assumption here is that such larger images are likely to be malformed +// or malicious. If you do need to load an image with individual dimensions +// larger than that, and it still fits in the overall size limit, you can +// #define STBI_MAX_DIMENSIONS on your own to be something larger. +// +// =========================================================================== +// +// UNICODE: +// +// If compiling for Windows and you wish to use Unicode filenames, compile +// with +// #define STBI_WINDOWS_UTF8 +// and pass utf8-encoded filenames. Call stbi_convert_wchar_to_utf8 to convert +// Windows wchar_t filenames to utf8. +// +// =========================================================================== +// +// Philosophy +// +// stb libraries are designed with the following priorities: +// +// 1. easy to use +// 2. easy to maintain +// 3. good performance +// +// Sometimes I let "good performance" creep up in priority over "easy to maintain", +// and for best performance I may provide less-easy-to-use APIs that give higher +// performance, in addition to the easy-to-use ones. Nevertheless, it's important +// to keep in mind that from the standpoint of you, a client of this library, +// all you care about is #1 and #3, and stb libraries DO NOT emphasize #3 above all. +// +// Some secondary priorities arise directly from the first two, some of which +// provide more explicit reasons why performance can't be emphasized. +// +// - Portable ("ease of use") +// - Small source code footprint ("easy to maintain") +// - No dependencies ("ease of use") +// +// =========================================================================== +// +// I/O callbacks +// +// I/O callbacks allow you to read from arbitrary sources, like packaged +// files or some other source. Data read from callbacks are processed +// through a small internal buffer (currently 128 bytes) to try to reduce +// overhead. +// +// The three functions you must define are "read" (reads some bytes of data), +// "skip" (skips some bytes of data), "eof" (reports if the stream is at the end). +// +// =========================================================================== +// +// SIMD support +// +// The JPEG decoder will try to automatically use SIMD kernels on x86 when +// supported by the compiler. For ARM Neon support, you must explicitly +// request it. +// +// (The old do-it-yourself SIMD API is no longer supported in the current +// code.) +// +// On x86, SSE2 will automatically be used when available based on a run-time +// test; if not, the generic C versions are used as a fall-back. On ARM targets, +// the typical path is to have separate builds for NEON and non-NEON devices +// (at least this is true for iOS and Android). Therefore, the NEON support is +// toggled by a build flag: define STBI_NEON to get NEON loops. +// +// If for some reason you do not want to use any of SIMD code, or if +// you have issues compiling it, you can disable it entirely by +// defining STBI_NO_SIMD. +// +// =========================================================================== +// +// HDR image support (disable by defining STBI_NO_HDR) +// +// stb_image supports loading HDR images in general, and currently the Radiance +// .HDR file format specifically. You can still load any file through the existing +// interface; if you attempt to load an HDR file, it will be automatically remapped +// to LDR, assuming gamma 2.2 and an arbitrary scale factor defaulting to 1; +// both of these constants can be reconfigured through this interface: +// +// stbi_hdr_to_ldr_gamma(2.2f); +// stbi_hdr_to_ldr_scale(1.0f); +// +// (note, do not use _inverse_ constants; stbi_image will invert them +// appropriately). +// +// Additionally, there is a new, parallel interface for loading files as +// (linear) floats to preserve the full dynamic range: +// +// float *data = stbi_loadf(filename, &x, &y, &n, 0); +// +// If you load LDR images through this interface, those images will +// be promoted to floating point values, run through the inverse of +// constants corresponding to the above: +// +// stbi_ldr_to_hdr_scale(1.0f); +// stbi_ldr_to_hdr_gamma(2.2f); +// +// Finally, given a filename (or an open file or memory block--see header +// file for details) containing image data, you can query for the "most +// appropriate" interface to use (that is, whether the image is HDR or +// not), using: +// +// stbi_is_hdr(char *filename); +// +// =========================================================================== +// +// iPhone PNG support: +// +// We optionally support converting iPhone-formatted PNGs (which store +// premultiplied BGRA) back to RGB, even though they're internally encoded +// differently. To enable this conversion, call +// stbi_convert_iphone_png_to_rgb(1). +// +// Call stbi_set_unpremultiply_on_load(1) as well to force a divide per +// pixel to remove any premultiplied alpha *only* if the image file explicitly +// says there's premultiplied data (currently only happens in iPhone images, +// and only if iPhone convert-to-rgb processing is on). +// +// =========================================================================== +// +// ADDITIONAL CONFIGURATION +// +// - You can suppress implementation of any of the decoders to reduce +// your code footprint by #defining one or more of the following +// symbols before creating the implementation. +// +// STBI_NO_JPEG +// STBI_NO_PNG +// STBI_NO_BMP +// STBI_NO_PSD +// STBI_NO_TGA +// STBI_NO_GIF +// STBI_NO_HDR +// STBI_NO_PIC +// STBI_NO_PNM (.ppm and .pgm) +// +// - You can request *only* certain decoders and suppress all other ones +// (this will be more forward-compatible, as addition of new decoders +// doesn't require you to disable them explicitly): +// +// STBI_ONLY_JPEG +// STBI_ONLY_PNG +// STBI_ONLY_BMP +// STBI_ONLY_PSD +// STBI_ONLY_TGA +// STBI_ONLY_GIF +// STBI_ONLY_HDR +// STBI_ONLY_PIC +// STBI_ONLY_PNM (.ppm and .pgm) +// +// - If you use STBI_NO_PNG (or _ONLY_ without PNG), and you still +// want the zlib decoder to be available, #define STBI_SUPPORT_ZLIB +// +// - If you define STBI_MAX_DIMENSIONS, stb_image will reject images greater +// than that size (in either width or height) without further processing. +// This is to let programs in the wild set an upper bound to prevent +// denial-of-service attacks on untrusted data, as one could generate a +// valid image of gigantic dimensions and force stb_image to allocate a +// huge block of memory and spend disproportionate time decoding it. By +// default this is set to (1 << 24), which is 16777216, but that's still +// very big. + +#ifndef STBI_NO_STDIO +#include +#endif // STBI_NO_STDIO + +#define STBI_VERSION 1 + +enum +{ + STBI_default = 0, // only used for desired_channels + + STBI_grey = 1, + STBI_grey_alpha = 2, + STBI_rgb = 3, + STBI_rgb_alpha = 4 +}; + +#include +typedef unsigned char stbi_uc; +typedef unsigned short stbi_us; + +#ifdef __cplusplus +extern "C" { +#endif + +#ifndef STBIDEF +#ifdef STB_IMAGE_STATIC +#define STBIDEF static +#else +#define STBIDEF extern +#endif +#endif + +////////////////////////////////////////////////////////////////////////////// +// +// PRIMARY API - works on images of any type +// + +// +// load image by filename, open file, or memory buffer +// + +typedef struct +{ + int (*read) (void *user,char *data,int size); // fill 'data' with 'size' bytes. return number of bytes actually read + void (*skip) (void *user,int n); // skip the next 'n' bytes, or 'unget' the last -n bytes if negative + int (*eof) (void *user); // returns nonzero if we are at end of file/data +} stbi_io_callbacks; + +//////////////////////////////////// +// +// 8-bits-per-channel interface +// + +STBIDEF stbi_uc *stbi_load_from_memory (stbi_uc const *buffer, int len , int *x, int *y, int *channels_in_file, int desired_channels); +STBIDEF stbi_uc *stbi_load_from_callbacks(stbi_io_callbacks const *clbk , void *user, int *x, int *y, int *channels_in_file, int desired_channels); + +#ifndef STBI_NO_STDIO +STBIDEF stbi_uc *stbi_load (char const *filename, int *x, int *y, int *channels_in_file, int desired_channels); +STBIDEF stbi_uc *stbi_load_from_file (FILE *f, int *x, int *y, int *channels_in_file, int desired_channels); +// for stbi_load_from_file, file pointer is left pointing immediately after image +#endif + +#ifndef STBI_NO_GIF +STBIDEF stbi_uc *stbi_load_gif_from_memory(stbi_uc const *buffer, int len, int **delays, int *x, int *y, int *z, int *comp, int req_comp); +#endif + +#ifdef STBI_WINDOWS_UTF8 +STBIDEF int stbi_convert_wchar_to_utf8(char *buffer, size_t bufferlen, const wchar_t* input); +#endif + +//////////////////////////////////// +// +// 16-bits-per-channel interface +// + +STBIDEF stbi_us *stbi_load_16_from_memory (stbi_uc const *buffer, int len, int *x, int *y, int *channels_in_file, int desired_channels); +STBIDEF stbi_us *stbi_load_16_from_callbacks(stbi_io_callbacks const *clbk, void *user, int *x, int *y, int *channels_in_file, int desired_channels); + +#ifndef STBI_NO_STDIO +STBIDEF stbi_us *stbi_load_16 (char const *filename, int *x, int *y, int *channels_in_file, int desired_channels); +STBIDEF stbi_us *stbi_load_from_file_16(FILE *f, int *x, int *y, int *channels_in_file, int desired_channels); +#endif + +//////////////////////////////////// +// +// float-per-channel interface +// +#ifndef STBI_NO_LINEAR + STBIDEF float *stbi_loadf_from_memory (stbi_uc const *buffer, int len, int *x, int *y, int *channels_in_file, int desired_channels); + STBIDEF float *stbi_loadf_from_callbacks (stbi_io_callbacks const *clbk, void *user, int *x, int *y, int *channels_in_file, int desired_channels); + + #ifndef STBI_NO_STDIO + STBIDEF float *stbi_loadf (char const *filename, int *x, int *y, int *channels_in_file, int desired_channels); + STBIDEF float *stbi_loadf_from_file (FILE *f, int *x, int *y, int *channels_in_file, int desired_channels); + #endif +#endif + +#ifndef STBI_NO_HDR + STBIDEF void stbi_hdr_to_ldr_gamma(float gamma); + STBIDEF void stbi_hdr_to_ldr_scale(float scale); +#endif // STBI_NO_HDR + +#ifndef STBI_NO_LINEAR + STBIDEF void stbi_ldr_to_hdr_gamma(float gamma); + STBIDEF void stbi_ldr_to_hdr_scale(float scale); +#endif // STBI_NO_LINEAR + +// stbi_is_hdr is always defined, but always returns false if STBI_NO_HDR +STBIDEF int stbi_is_hdr_from_callbacks(stbi_io_callbacks const *clbk, void *user); +STBIDEF int stbi_is_hdr_from_memory(stbi_uc const *buffer, int len); +#ifndef STBI_NO_STDIO +STBIDEF int stbi_is_hdr (char const *filename); +STBIDEF int stbi_is_hdr_from_file(FILE *f); +#endif // STBI_NO_STDIO + + +// get a VERY brief reason for failure +// on most compilers (and ALL modern mainstream compilers) this is threadsafe +STBIDEF const char *stbi_failure_reason (void); + +// free the loaded image -- this is just free() +STBIDEF void stbi_image_free (void *retval_from_stbi_load); + +// get image dimensions & components without fully decoding +STBIDEF int stbi_info_from_memory(stbi_uc const *buffer, int len, int *x, int *y, int *comp); +STBIDEF int stbi_info_from_callbacks(stbi_io_callbacks const *clbk, void *user, int *x, int *y, int *comp); +STBIDEF int stbi_is_16_bit_from_memory(stbi_uc const *buffer, int len); +STBIDEF int stbi_is_16_bit_from_callbacks(stbi_io_callbacks const *clbk, void *user); + +#ifndef STBI_NO_STDIO +STBIDEF int stbi_info (char const *filename, int *x, int *y, int *comp); +STBIDEF int stbi_info_from_file (FILE *f, int *x, int *y, int *comp); +STBIDEF int stbi_is_16_bit (char const *filename); +STBIDEF int stbi_is_16_bit_from_file(FILE *f); +#endif + + + +// for image formats that explicitly notate that they have premultiplied alpha, +// we just return the colors as stored in the file. set this flag to force +// unpremultiplication. results are undefined if the unpremultiply overflow. +STBIDEF void stbi_set_unpremultiply_on_load(int flag_true_if_should_unpremultiply); + +// indicate whether we should process iphone images back to canonical format, +// or just pass them through "as-is" +STBIDEF void stbi_convert_iphone_png_to_rgb(int flag_true_if_should_convert); + +// flip the image vertically, so the first pixel in the output array is the bottom left +STBIDEF void stbi_set_flip_vertically_on_load(int flag_true_if_should_flip); + +// as above, but only applies to images loaded on the thread that calls the function +// this function is only available if your compiler supports thread-local variables; +// calling it will fail to link if your compiler doesn't +STBIDEF void stbi_set_unpremultiply_on_load_thread(int flag_true_if_should_unpremultiply); +STBIDEF void stbi_convert_iphone_png_to_rgb_thread(int flag_true_if_should_convert); +STBIDEF void stbi_set_flip_vertically_on_load_thread(int flag_true_if_should_flip); + +// ZLIB client - used by PNG, available for other purposes + +STBIDEF char *stbi_zlib_decode_malloc_guesssize(const char *buffer, int len, int initial_size, int *outlen); +STBIDEF char *stbi_zlib_decode_malloc_guesssize_headerflag(const char *buffer, int len, int initial_size, int *outlen, int parse_header); +STBIDEF char *stbi_zlib_decode_malloc(const char *buffer, int len, int *outlen); +STBIDEF int stbi_zlib_decode_buffer(char *obuffer, int olen, const char *ibuffer, int ilen); + +STBIDEF char *stbi_zlib_decode_noheader_malloc(const char *buffer, int len, int *outlen); +STBIDEF int stbi_zlib_decode_noheader_buffer(char *obuffer, int olen, const char *ibuffer, int ilen); + + +#ifdef __cplusplus +} +#endif + +// +// +//// end header file ///////////////////////////////////////////////////// +#endif // STBI_INCLUDE_STB_IMAGE_H + +#ifdef STB_IMAGE_IMPLEMENTATION + +#if defined(STBI_ONLY_JPEG) || defined(STBI_ONLY_PNG) || defined(STBI_ONLY_BMP) \ + || defined(STBI_ONLY_TGA) || defined(STBI_ONLY_GIF) || defined(STBI_ONLY_PSD) \ + || defined(STBI_ONLY_HDR) || defined(STBI_ONLY_PIC) || defined(STBI_ONLY_PNM) \ + || defined(STBI_ONLY_ZLIB) + #ifndef STBI_ONLY_JPEG + #define STBI_NO_JPEG + #endif + #ifndef STBI_ONLY_PNG + #define STBI_NO_PNG + #endif + #ifndef STBI_ONLY_BMP + #define STBI_NO_BMP + #endif + #ifndef STBI_ONLY_PSD + #define STBI_NO_PSD + #endif + #ifndef STBI_ONLY_TGA + #define STBI_NO_TGA + #endif + #ifndef STBI_ONLY_GIF + #define STBI_NO_GIF + #endif + #ifndef STBI_ONLY_HDR + #define STBI_NO_HDR + #endif + #ifndef STBI_ONLY_PIC + #define STBI_NO_PIC + #endif + #ifndef STBI_ONLY_PNM + #define STBI_NO_PNM + #endif +#endif + +#if defined(STBI_NO_PNG) && !defined(STBI_SUPPORT_ZLIB) && !defined(STBI_NO_ZLIB) +#define STBI_NO_ZLIB +#endif + + +#include +#include // ptrdiff_t on osx +#include +#include +#include + +#if !defined(STBI_NO_LINEAR) || !defined(STBI_NO_HDR) +#include // ldexp, pow +#endif + +#ifndef STBI_NO_STDIO +#include +#endif + +#ifndef STBI_ASSERT +#include +#define STBI_ASSERT(x) assert(x) +#endif + +#ifdef __cplusplus +#define STBI_EXTERN extern "C" +#else +#define STBI_EXTERN extern +#endif + + +#ifndef _MSC_VER + #ifdef __cplusplus + #define stbi_inline inline + #else + #define stbi_inline + #endif +#else + #define stbi_inline __forceinline +#endif + +#ifndef STBI_NO_THREAD_LOCALS + #if defined(__cplusplus) && __cplusplus >= 201103L + #define STBI_THREAD_LOCAL thread_local + #elif defined(__GNUC__) && __GNUC__ < 5 + #define STBI_THREAD_LOCAL __thread + #elif defined(_MSC_VER) + #define STBI_THREAD_LOCAL __declspec(thread) + #elif defined (__STDC_VERSION__) && __STDC_VERSION__ >= 201112L && !defined(__STDC_NO_THREADS__) + #define STBI_THREAD_LOCAL _Thread_local + #endif + + #ifndef STBI_THREAD_LOCAL + #if defined(__GNUC__) + #define STBI_THREAD_LOCAL __thread + #endif + #endif +#endif + +#if defined(_MSC_VER) || defined(__SYMBIAN32__) +typedef unsigned short stbi__uint16; +typedef signed short stbi__int16; +typedef unsigned int stbi__uint32; +typedef signed int stbi__int32; +#else +#include +typedef uint16_t stbi__uint16; +typedef int16_t stbi__int16; +typedef uint32_t stbi__uint32; +typedef int32_t stbi__int32; +#endif + +// should produce compiler error if size is wrong +typedef unsigned char validate_uint32[sizeof(stbi__uint32)==4 ? 1 : -1]; + +#ifdef _MSC_VER +#define STBI_NOTUSED(v) (void)(v) +#else +#define STBI_NOTUSED(v) (void)sizeof(v) +#endif + +#ifdef _MSC_VER +#define STBI_HAS_LROTL +#endif + +#ifdef STBI_HAS_LROTL + #define stbi_lrot(x,y) _lrotl(x,y) +#else + #define stbi_lrot(x,y) (((x) << (y)) | ((x) >> (-(y) & 31))) +#endif + +#if defined(STBI_MALLOC) && defined(STBI_FREE) && (defined(STBI_REALLOC) || defined(STBI_REALLOC_SIZED)) +// ok +#elif !defined(STBI_MALLOC) && !defined(STBI_FREE) && !defined(STBI_REALLOC) && !defined(STBI_REALLOC_SIZED) +// ok +#else +#error "Must define all or none of STBI_MALLOC, STBI_FREE, and STBI_REALLOC (or STBI_REALLOC_SIZED)." +#endif + +#ifndef STBI_MALLOC +#define STBI_MALLOC(sz) malloc(sz) +#define STBI_REALLOC(p,newsz) realloc(p,newsz) +#define STBI_FREE(p) free(p) +#endif + +#ifndef STBI_REALLOC_SIZED +#define STBI_REALLOC_SIZED(p,oldsz,newsz) STBI_REALLOC(p,newsz) +#endif + +// x86/x64 detection +#if defined(__x86_64__) || defined(_M_X64) +#define STBI__X64_TARGET +#elif defined(__i386) || defined(_M_IX86) +#define STBI__X86_TARGET +#endif + +#if defined(__GNUC__) && defined(STBI__X86_TARGET) && !defined(__SSE2__) && !defined(STBI_NO_SIMD) +// gcc doesn't support sse2 intrinsics unless you compile with -msse2, +// which in turn means it gets to use SSE2 everywhere. This is unfortunate, +// but previous attempts to provide the SSE2 functions with runtime +// detection caused numerous issues. The way architecture extensions are +// exposed in GCC/Clang is, sadly, not really suited for one-file libs. +// New behavior: if compiled with -msse2, we use SSE2 without any +// detection; if not, we don't use it at all. +#define STBI_NO_SIMD +#endif + +#if defined(__MINGW32__) && defined(STBI__X86_TARGET) && !defined(STBI_MINGW_ENABLE_SSE2) && !defined(STBI_NO_SIMD) +// Note that __MINGW32__ doesn't actually mean 32-bit, so we have to avoid STBI__X64_TARGET +// +// 32-bit MinGW wants ESP to be 16-byte aligned, but this is not in the +// Windows ABI and VC++ as well as Windows DLLs don't maintain that invariant. +// As a result, enabling SSE2 on 32-bit MinGW is dangerous when not +// simultaneously enabling "-mstackrealign". +// +// See https://github.com/nothings/stb/issues/81 for more information. +// +// So default to no SSE2 on 32-bit MinGW. If you've read this far and added +// -mstackrealign to your build settings, feel free to #define STBI_MINGW_ENABLE_SSE2. +#define STBI_NO_SIMD +#endif + +#if !defined(STBI_NO_SIMD) && (defined(STBI__X86_TARGET) || defined(STBI__X64_TARGET)) +#define STBI_SSE2 +#include + +#ifdef _MSC_VER + +#if _MSC_VER >= 1400 // not VC6 +#include // __cpuid +static int stbi__cpuid3(void) +{ + int info[4]; + __cpuid(info,1); + return info[3]; +} +#else +static int stbi__cpuid3(void) +{ + int res; + __asm { + mov eax,1 + cpuid + mov res,edx + } + return res; +} +#endif + +#define STBI_SIMD_ALIGN(type, name) __declspec(align(16)) type name + +#if !defined(STBI_NO_JPEG) && defined(STBI_SSE2) +static int stbi__sse2_available(void) +{ + int info3 = stbi__cpuid3(); + return ((info3 >> 26) & 1) != 0; +} +#endif + +#else // assume GCC-style if not VC++ +#define STBI_SIMD_ALIGN(type, name) type name __attribute__((aligned(16))) + +#if !defined(STBI_NO_JPEG) && defined(STBI_SSE2) +static int stbi__sse2_available(void) +{ + // If we're even attempting to compile this on GCC/Clang, that means + // -msse2 is on, which means the compiler is allowed to use SSE2 + // instructions at will, and so are we. + return 1; +} +#endif + +#endif +#endif + +// ARM NEON +#if defined(STBI_NO_SIMD) && defined(STBI_NEON) +#undef STBI_NEON +#endif + +#ifdef STBI_NEON +#include +#ifdef _MSC_VER +#define STBI_SIMD_ALIGN(type, name) __declspec(align(16)) type name +#else +#define STBI_SIMD_ALIGN(type, name) type name __attribute__((aligned(16))) +#endif +#endif + +#ifndef STBI_SIMD_ALIGN +#define STBI_SIMD_ALIGN(type, name) type name +#endif + +#ifndef STBI_MAX_DIMENSIONS +#define STBI_MAX_DIMENSIONS (1 << 24) +#endif + +/////////////////////////////////////////////// +// +// stbi__context struct and start_xxx functions + +// stbi__context structure is our basic context used by all images, so it +// contains all the IO context, plus some basic image information +typedef struct +{ + stbi__uint32 img_x, img_y; + int img_n, img_out_n; + + stbi_io_callbacks io; + void *io_user_data; + + int read_from_callbacks; + int buflen; + stbi_uc buffer_start[128]; + int callback_already_read; + + stbi_uc *img_buffer, *img_buffer_end; + stbi_uc *img_buffer_original, *img_buffer_original_end; +} stbi__context; + + +static void stbi__refill_buffer(stbi__context *s); + +// initialize a memory-decode context +static void stbi__start_mem(stbi__context *s, stbi_uc const *buffer, int len) +{ + s->io.read = NULL; + s->read_from_callbacks = 0; + s->callback_already_read = 0; + s->img_buffer = s->img_buffer_original = (stbi_uc *) buffer; + s->img_buffer_end = s->img_buffer_original_end = (stbi_uc *) buffer+len; +} + +// initialize a callback-based context +static void stbi__start_callbacks(stbi__context *s, stbi_io_callbacks *c, void *user) +{ + s->io = *c; + s->io_user_data = user; + s->buflen = sizeof(s->buffer_start); + s->read_from_callbacks = 1; + s->callback_already_read = 0; + s->img_buffer = s->img_buffer_original = s->buffer_start; + stbi__refill_buffer(s); + s->img_buffer_original_end = s->img_buffer_end; +} + +#ifndef STBI_NO_STDIO + +static int stbi__stdio_read(void *user, char *data, int size) +{ + return (int) fread(data,1,size,(FILE*) user); +} + +static void stbi__stdio_skip(void *user, int n) +{ + int ch; + fseek((FILE*) user, n, SEEK_CUR); + ch = fgetc((FILE*) user); /* have to read a byte to reset feof()'s flag */ + if (ch != EOF) { + ungetc(ch, (FILE *) user); /* push byte back onto stream if valid. */ + } +} + +static int stbi__stdio_eof(void *user) +{ + return feof((FILE*) user) || ferror((FILE *) user); +} + +static stbi_io_callbacks stbi__stdio_callbacks = +{ + stbi__stdio_read, + stbi__stdio_skip, + stbi__stdio_eof, +}; + +static void stbi__start_file(stbi__context *s, FILE *f) +{ + stbi__start_callbacks(s, &stbi__stdio_callbacks, (void *) f); +} + +//static void stop_file(stbi__context *s) { } + +#endif // !STBI_NO_STDIO + +static void stbi__rewind(stbi__context *s) +{ + // conceptually rewind SHOULD rewind to the beginning of the stream, + // but we just rewind to the beginning of the initial buffer, because + // we only use it after doing 'test', which only ever looks at at most 92 bytes + s->img_buffer = s->img_buffer_original; + s->img_buffer_end = s->img_buffer_original_end; +} + +enum +{ + STBI_ORDER_RGB, + STBI_ORDER_BGR +}; + +typedef struct +{ + int bits_per_channel; + int num_channels; + int channel_order; +} stbi__result_info; + +#ifndef STBI_NO_JPEG +static int stbi__jpeg_test(stbi__context *s); +static void *stbi__jpeg_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri); +static int stbi__jpeg_info(stbi__context *s, int *x, int *y, int *comp); +#endif + +#ifndef STBI_NO_PNG +static int stbi__png_test(stbi__context *s); +static void *stbi__png_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri); +static int stbi__png_info(stbi__context *s, int *x, int *y, int *comp); +static int stbi__png_is16(stbi__context *s); +#endif + +#ifndef STBI_NO_BMP +static int stbi__bmp_test(stbi__context *s); +static void *stbi__bmp_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri); +static int stbi__bmp_info(stbi__context *s, int *x, int *y, int *comp); +#endif + +#ifndef STBI_NO_TGA +static int stbi__tga_test(stbi__context *s); +static void *stbi__tga_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri); +static int stbi__tga_info(stbi__context *s, int *x, int *y, int *comp); +#endif + +#ifndef STBI_NO_PSD +static int stbi__psd_test(stbi__context *s); +static void *stbi__psd_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri, int bpc); +static int stbi__psd_info(stbi__context *s, int *x, int *y, int *comp); +static int stbi__psd_is16(stbi__context *s); +#endif + +#ifndef STBI_NO_HDR +static int stbi__hdr_test(stbi__context *s); +static float *stbi__hdr_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri); +static int stbi__hdr_info(stbi__context *s, int *x, int *y, int *comp); +#endif + +#ifndef STBI_NO_PIC +static int stbi__pic_test(stbi__context *s); +static void *stbi__pic_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri); +static int stbi__pic_info(stbi__context *s, int *x, int *y, int *comp); +#endif + +#ifndef STBI_NO_GIF +static int stbi__gif_test(stbi__context *s); +static void *stbi__gif_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri); +static void *stbi__load_gif_main(stbi__context *s, int **delays, int *x, int *y, int *z, int *comp, int req_comp); +static int stbi__gif_info(stbi__context *s, int *x, int *y, int *comp); +#endif + +#ifndef STBI_NO_PNM +static int stbi__pnm_test(stbi__context *s); +static void *stbi__pnm_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri); +static int stbi__pnm_info(stbi__context *s, int *x, int *y, int *comp); +static int stbi__pnm_is16(stbi__context *s); +#endif + +static +#ifdef STBI_THREAD_LOCAL +STBI_THREAD_LOCAL +#endif +const char *stbi__g_failure_reason; + +STBIDEF const char *stbi_failure_reason(void) +{ + return stbi__g_failure_reason; +} + +#ifndef STBI_NO_FAILURE_STRINGS +static int stbi__err(const char *str) +{ + stbi__g_failure_reason = str; + return 0; +} +#endif + +static void *stbi__malloc(size_t size) +{ + return STBI_MALLOC(size); +} + +// stb_image uses ints pervasively, including for offset calculations. +// therefore the largest decoded image size we can support with the +// current code, even on 64-bit targets, is INT_MAX. this is not a +// significant limitation for the intended use case. +// +// we do, however, need to make sure our size calculations don't +// overflow. hence a few helper functions for size calculations that +// multiply integers together, making sure that they're non-negative +// and no overflow occurs. + +// return 1 if the sum is valid, 0 on overflow. +// negative terms are considered invalid. +static int stbi__addsizes_valid(int a, int b) +{ + if (b < 0) return 0; + // now 0 <= b <= INT_MAX, hence also + // 0 <= INT_MAX - b <= INTMAX. + // And "a + b <= INT_MAX" (which might overflow) is the + // same as a <= INT_MAX - b (no overflow) + return a <= INT_MAX - b; +} + +// returns 1 if the product is valid, 0 on overflow. +// negative factors are considered invalid. +static int stbi__mul2sizes_valid(int a, int b) +{ + if (a < 0 || b < 0) return 0; + if (b == 0) return 1; // mul-by-0 is always safe + // portable way to check for no overflows in a*b + return a <= INT_MAX/b; +} + +#if !defined(STBI_NO_JPEG) || !defined(STBI_NO_PNG) || !defined(STBI_NO_TGA) || !defined(STBI_NO_HDR) +// returns 1 if "a*b + add" has no negative terms/factors and doesn't overflow +static int stbi__mad2sizes_valid(int a, int b, int add) +{ + return stbi__mul2sizes_valid(a, b) && stbi__addsizes_valid(a*b, add); +} +#endif + +// returns 1 if "a*b*c + add" has no negative terms/factors and doesn't overflow +static int stbi__mad3sizes_valid(int a, int b, int c, int add) +{ + return stbi__mul2sizes_valid(a, b) && stbi__mul2sizes_valid(a*b, c) && + stbi__addsizes_valid(a*b*c, add); +} + +// returns 1 if "a*b*c*d + add" has no negative terms/factors and doesn't overflow +#if !defined(STBI_NO_LINEAR) || !defined(STBI_NO_HDR) || !defined(STBI_NO_PNM) +static int stbi__mad4sizes_valid(int a, int b, int c, int d, int add) +{ + return stbi__mul2sizes_valid(a, b) && stbi__mul2sizes_valid(a*b, c) && + stbi__mul2sizes_valid(a*b*c, d) && stbi__addsizes_valid(a*b*c*d, add); +} +#endif + +#if !defined(STBI_NO_JPEG) || !defined(STBI_NO_PNG) || !defined(STBI_NO_TGA) || !defined(STBI_NO_HDR) +// mallocs with size overflow checking +static void *stbi__malloc_mad2(int a, int b, int add) +{ + if (!stbi__mad2sizes_valid(a, b, add)) return NULL; + return stbi__malloc(a*b + add); +} +#endif + +static void *stbi__malloc_mad3(int a, int b, int c, int add) +{ + if (!stbi__mad3sizes_valid(a, b, c, add)) return NULL; + return stbi__malloc(a*b*c + add); +} + +#if !defined(STBI_NO_LINEAR) || !defined(STBI_NO_HDR) || !defined(STBI_NO_PNM) +static void *stbi__malloc_mad4(int a, int b, int c, int d, int add) +{ + if (!stbi__mad4sizes_valid(a, b, c, d, add)) return NULL; + return stbi__malloc(a*b*c*d + add); +} +#endif + +// returns 1 if the sum of two signed ints is valid (between -2^31 and 2^31-1 inclusive), 0 on overflow. +static int stbi__addints_valid(int a, int b) +{ + if ((a >= 0) != (b >= 0)) return 1; // a and b have different signs, so no overflow + if (a < 0 && b < 0) return a >= INT_MIN - b; // same as a + b >= INT_MIN; INT_MIN - b cannot overflow since b < 0. + return a <= INT_MAX - b; +} + +// returns 1 if the product of two ints fits in a signed short, 0 on overflow. +static int stbi__mul2shorts_valid(int a, int b) +{ + if (b == 0 || b == -1) return 1; // multiplication by 0 is always 0; check for -1 so SHRT_MIN/b doesn't overflow + if ((a >= 0) == (b >= 0)) return a <= SHRT_MAX/b; // product is positive, so similar to mul2sizes_valid + if (b < 0) return a <= SHRT_MIN / b; // same as a * b >= SHRT_MIN + return a >= SHRT_MIN / b; +} + +// stbi__err - error +// stbi__errpf - error returning pointer to float +// stbi__errpuc - error returning pointer to unsigned char + +#ifdef STBI_NO_FAILURE_STRINGS + #define stbi__err(x,y) 0 +#elif defined(STBI_FAILURE_USERMSG) + #define stbi__err(x,y) stbi__err(y) +#else + #define stbi__err(x,y) stbi__err(x) +#endif + +#define stbi__errpf(x,y) ((float *)(size_t) (stbi__err(x,y)?NULL:NULL)) +#define stbi__errpuc(x,y) ((unsigned char *)(size_t) (stbi__err(x,y)?NULL:NULL)) + +STBIDEF void stbi_image_free(void *retval_from_stbi_load) +{ + STBI_FREE(retval_from_stbi_load); +} + +#ifndef STBI_NO_LINEAR +static float *stbi__ldr_to_hdr(stbi_uc *data, int x, int y, int comp); +#endif + +#ifndef STBI_NO_HDR +static stbi_uc *stbi__hdr_to_ldr(float *data, int x, int y, int comp); +#endif + +static int stbi__vertically_flip_on_load_global = 0; + +STBIDEF void stbi_set_flip_vertically_on_load(int flag_true_if_should_flip) +{ + stbi__vertically_flip_on_load_global = flag_true_if_should_flip; +} + +#ifndef STBI_THREAD_LOCAL +#define stbi__vertically_flip_on_load stbi__vertically_flip_on_load_global +#else +static STBI_THREAD_LOCAL int stbi__vertically_flip_on_load_local, stbi__vertically_flip_on_load_set; + +STBIDEF void stbi_set_flip_vertically_on_load_thread(int flag_true_if_should_flip) +{ + stbi__vertically_flip_on_load_local = flag_true_if_should_flip; + stbi__vertically_flip_on_load_set = 1; +} + +#define stbi__vertically_flip_on_load (stbi__vertically_flip_on_load_set \ + ? stbi__vertically_flip_on_load_local \ + : stbi__vertically_flip_on_load_global) +#endif // STBI_THREAD_LOCAL + +static void *stbi__load_main(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri, int bpc) +{ + memset(ri, 0, sizeof(*ri)); // make sure it's initialized if we add new fields + ri->bits_per_channel = 8; // default is 8 so most paths don't have to be changed + ri->channel_order = STBI_ORDER_RGB; // all current input & output are this, but this is here so we can add BGR order + ri->num_channels = 0; + + // test the formats with a very explicit header first (at least a FOURCC + // or distinctive magic number first) + #ifndef STBI_NO_PNG + if (stbi__png_test(s)) return stbi__png_load(s,x,y,comp,req_comp, ri); + #endif + #ifndef STBI_NO_BMP + if (stbi__bmp_test(s)) return stbi__bmp_load(s,x,y,comp,req_comp, ri); + #endif + #ifndef STBI_NO_GIF + if (stbi__gif_test(s)) return stbi__gif_load(s,x,y,comp,req_comp, ri); + #endif + #ifndef STBI_NO_PSD + if (stbi__psd_test(s)) return stbi__psd_load(s,x,y,comp,req_comp, ri, bpc); + #else + STBI_NOTUSED(bpc); + #endif + #ifndef STBI_NO_PIC + if (stbi__pic_test(s)) return stbi__pic_load(s,x,y,comp,req_comp, ri); + #endif + + // then the formats that can end up attempting to load with just 1 or 2 + // bytes matching expectations; these are prone to false positives, so + // try them later + #ifndef STBI_NO_JPEG + if (stbi__jpeg_test(s)) return stbi__jpeg_load(s,x,y,comp,req_comp, ri); + #endif + #ifndef STBI_NO_PNM + if (stbi__pnm_test(s)) return stbi__pnm_load(s,x,y,comp,req_comp, ri); + #endif + + #ifndef STBI_NO_HDR + if (stbi__hdr_test(s)) { + float *hdr = stbi__hdr_load(s, x,y,comp,req_comp, ri); + return stbi__hdr_to_ldr(hdr, *x, *y, req_comp ? req_comp : *comp); + } + #endif + + #ifndef STBI_NO_TGA + // test tga last because it's a crappy test! + if (stbi__tga_test(s)) + return stbi__tga_load(s,x,y,comp,req_comp, ri); + #endif + + return stbi__errpuc("unknown image type", "Image not of any known type, or corrupt"); +} + +static stbi_uc *stbi__convert_16_to_8(stbi__uint16 *orig, int w, int h, int channels) +{ + int i; + int img_len = w * h * channels; + stbi_uc *reduced; + + reduced = (stbi_uc *) stbi__malloc(img_len); + if (reduced == NULL) return stbi__errpuc("outofmem", "Out of memory"); + + for (i = 0; i < img_len; ++i) + reduced[i] = (stbi_uc)((orig[i] >> 8) & 0xFF); // top half of each byte is sufficient approx of 16->8 bit scaling + + STBI_FREE(orig); + return reduced; +} + +static stbi__uint16 *stbi__convert_8_to_16(stbi_uc *orig, int w, int h, int channels) +{ + int i; + int img_len = w * h * channels; + stbi__uint16 *enlarged; + + enlarged = (stbi__uint16 *) stbi__malloc(img_len*2); + if (enlarged == NULL) return (stbi__uint16 *) stbi__errpuc("outofmem", "Out of memory"); + + for (i = 0; i < img_len; ++i) + enlarged[i] = (stbi__uint16)((orig[i] << 8) + orig[i]); // replicate to high and low byte, maps 0->0, 255->0xffff + + STBI_FREE(orig); + return enlarged; +} + +static void stbi__vertical_flip(void *image, int w, int h, int bytes_per_pixel) +{ + int row; + size_t bytes_per_row = (size_t)w * bytes_per_pixel; + stbi_uc temp[2048]; + stbi_uc *bytes = (stbi_uc *)image; + + for (row = 0; row < (h>>1); row++) { + stbi_uc *row0 = bytes + row*bytes_per_row; + stbi_uc *row1 = bytes + (h - row - 1)*bytes_per_row; + // swap row0 with row1 + size_t bytes_left = bytes_per_row; + while (bytes_left) { + size_t bytes_copy = (bytes_left < sizeof(temp)) ? bytes_left : sizeof(temp); + memcpy(temp, row0, bytes_copy); + memcpy(row0, row1, bytes_copy); + memcpy(row1, temp, bytes_copy); + row0 += bytes_copy; + row1 += bytes_copy; + bytes_left -= bytes_copy; + } + } +} + +#ifndef STBI_NO_GIF +static void stbi__vertical_flip_slices(void *image, int w, int h, int z, int bytes_per_pixel) +{ + int slice; + int slice_size = w * h * bytes_per_pixel; + + stbi_uc *bytes = (stbi_uc *)image; + for (slice = 0; slice < z; ++slice) { + stbi__vertical_flip(bytes, w, h, bytes_per_pixel); + bytes += slice_size; + } +} +#endif + +static unsigned char *stbi__load_and_postprocess_8bit(stbi__context *s, int *x, int *y, int *comp, int req_comp) +{ + stbi__result_info ri; + void *result = stbi__load_main(s, x, y, comp, req_comp, &ri, 8); + + if (result == NULL) + return NULL; + + // it is the responsibility of the loaders to make sure we get either 8 or 16 bit. + STBI_ASSERT(ri.bits_per_channel == 8 || ri.bits_per_channel == 16); + + if (ri.bits_per_channel != 8) { + result = stbi__convert_16_to_8((stbi__uint16 *) result, *x, *y, req_comp == 0 ? *comp : req_comp); + ri.bits_per_channel = 8; + } + + // @TODO: move stbi__convert_format to here + + if (stbi__vertically_flip_on_load) { + int channels = req_comp ? req_comp : *comp; + stbi__vertical_flip(result, *x, *y, channels * sizeof(stbi_uc)); + } + + return (unsigned char *) result; +} + +static stbi__uint16 *stbi__load_and_postprocess_16bit(stbi__context *s, int *x, int *y, int *comp, int req_comp) +{ + stbi__result_info ri; + void *result = stbi__load_main(s, x, y, comp, req_comp, &ri, 16); + + if (result == NULL) + return NULL; + + // it is the responsibility of the loaders to make sure we get either 8 or 16 bit. + STBI_ASSERT(ri.bits_per_channel == 8 || ri.bits_per_channel == 16); + + if (ri.bits_per_channel != 16) { + result = stbi__convert_8_to_16((stbi_uc *) result, *x, *y, req_comp == 0 ? *comp : req_comp); + ri.bits_per_channel = 16; + } + + // @TODO: move stbi__convert_format16 to here + // @TODO: special case RGB-to-Y (and RGBA-to-YA) for 8-bit-to-16-bit case to keep more precision + + if (stbi__vertically_flip_on_load) { + int channels = req_comp ? req_comp : *comp; + stbi__vertical_flip(result, *x, *y, channels * sizeof(stbi__uint16)); + } + + return (stbi__uint16 *) result; +} + +#if !defined(STBI_NO_HDR) && !defined(STBI_NO_LINEAR) +static void stbi__float_postprocess(float *result, int *x, int *y, int *comp, int req_comp) +{ + if (stbi__vertically_flip_on_load && result != NULL) { + int channels = req_comp ? req_comp : *comp; + stbi__vertical_flip(result, *x, *y, channels * sizeof(float)); + } +} +#endif + +#ifndef STBI_NO_STDIO + +#if defined(_WIN32) && defined(STBI_WINDOWS_UTF8) +STBI_EXTERN __declspec(dllimport) int __stdcall MultiByteToWideChar(unsigned int cp, unsigned long flags, const char *str, int cbmb, wchar_t *widestr, int cchwide); +STBI_EXTERN __declspec(dllimport) int __stdcall WideCharToMultiByte(unsigned int cp, unsigned long flags, const wchar_t *widestr, int cchwide, char *str, int cbmb, const char *defchar, int *used_default); +#endif + +#if defined(_WIN32) && defined(STBI_WINDOWS_UTF8) +STBIDEF int stbi_convert_wchar_to_utf8(char *buffer, size_t bufferlen, const wchar_t* input) +{ + return WideCharToMultiByte(65001 /* UTF8 */, 0, input, -1, buffer, (int) bufferlen, NULL, NULL); +} +#endif + +static FILE *stbi__fopen(char const *filename, char const *mode) +{ + FILE *f; +#if defined(_WIN32) && defined(STBI_WINDOWS_UTF8) + wchar_t wMode[64]; + wchar_t wFilename[1024]; + if (0 == MultiByteToWideChar(65001 /* UTF8 */, 0, filename, -1, wFilename, sizeof(wFilename)/sizeof(*wFilename))) + return 0; + + if (0 == MultiByteToWideChar(65001 /* UTF8 */, 0, mode, -1, wMode, sizeof(wMode)/sizeof(*wMode))) + return 0; + +#if defined(_MSC_VER) && _MSC_VER >= 1400 + if (0 != _wfopen_s(&f, wFilename, wMode)) + f = 0; +#else + f = _wfopen(wFilename, wMode); +#endif + +#elif defined(_MSC_VER) && _MSC_VER >= 1400 + if (0 != fopen_s(&f, filename, mode)) + f=0; +#else + f = fopen(filename, mode); +#endif + return f; +} + + +STBIDEF stbi_uc *stbi_load(char const *filename, int *x, int *y, int *comp, int req_comp) +{ + FILE *f = stbi__fopen(filename, "rb"); + unsigned char *result; + if (!f) return stbi__errpuc("can't fopen", "Unable to open file"); + result = stbi_load_from_file(f,x,y,comp,req_comp); + fclose(f); + return result; +} + +STBIDEF stbi_uc *stbi_load_from_file(FILE *f, int *x, int *y, int *comp, int req_comp) +{ + unsigned char *result; + stbi__context s; + stbi__start_file(&s,f); + result = stbi__load_and_postprocess_8bit(&s,x,y,comp,req_comp); + if (result) { + // need to 'unget' all the characters in the IO buffer + fseek(f, - (int) (s.img_buffer_end - s.img_buffer), SEEK_CUR); + } + return result; +} + +STBIDEF stbi__uint16 *stbi_load_from_file_16(FILE *f, int *x, int *y, int *comp, int req_comp) +{ + stbi__uint16 *result; + stbi__context s; + stbi__start_file(&s,f); + result = stbi__load_and_postprocess_16bit(&s,x,y,comp,req_comp); + if (result) { + // need to 'unget' all the characters in the IO buffer + fseek(f, - (int) (s.img_buffer_end - s.img_buffer), SEEK_CUR); + } + return result; +} + +STBIDEF stbi_us *stbi_load_16(char const *filename, int *x, int *y, int *comp, int req_comp) +{ + FILE *f = stbi__fopen(filename, "rb"); + stbi__uint16 *result; + if (!f) return (stbi_us *) stbi__errpuc("can't fopen", "Unable to open file"); + result = stbi_load_from_file_16(f,x,y,comp,req_comp); + fclose(f); + return result; +} + + +#endif //!STBI_NO_STDIO + +STBIDEF stbi_us *stbi_load_16_from_memory(stbi_uc const *buffer, int len, int *x, int *y, int *channels_in_file, int desired_channels) +{ + stbi__context s; + stbi__start_mem(&s,buffer,len); + return stbi__load_and_postprocess_16bit(&s,x,y,channels_in_file,desired_channels); +} + +STBIDEF stbi_us *stbi_load_16_from_callbacks(stbi_io_callbacks const *clbk, void *user, int *x, int *y, int *channels_in_file, int desired_channels) +{ + stbi__context s; + stbi__start_callbacks(&s, (stbi_io_callbacks *)clbk, user); + return stbi__load_and_postprocess_16bit(&s,x,y,channels_in_file,desired_channels); +} + +STBIDEF stbi_uc *stbi_load_from_memory(stbi_uc const *buffer, int len, int *x, int *y, int *comp, int req_comp) +{ + stbi__context s; + stbi__start_mem(&s,buffer,len); + return stbi__load_and_postprocess_8bit(&s,x,y,comp,req_comp); +} + +STBIDEF stbi_uc *stbi_load_from_callbacks(stbi_io_callbacks const *clbk, void *user, int *x, int *y, int *comp, int req_comp) +{ + stbi__context s; + stbi__start_callbacks(&s, (stbi_io_callbacks *) clbk, user); + return stbi__load_and_postprocess_8bit(&s,x,y,comp,req_comp); +} + +#ifndef STBI_NO_GIF +STBIDEF stbi_uc *stbi_load_gif_from_memory(stbi_uc const *buffer, int len, int **delays, int *x, int *y, int *z, int *comp, int req_comp) +{ + unsigned char *result; + stbi__context s; + stbi__start_mem(&s,buffer,len); + + result = (unsigned char*) stbi__load_gif_main(&s, delays, x, y, z, comp, req_comp); + if (stbi__vertically_flip_on_load) { + stbi__vertical_flip_slices( result, *x, *y, *z, *comp ); + } + + return result; +} +#endif + +#ifndef STBI_NO_LINEAR +static float *stbi__loadf_main(stbi__context *s, int *x, int *y, int *comp, int req_comp) +{ + unsigned char *data; + #ifndef STBI_NO_HDR + if (stbi__hdr_test(s)) { + stbi__result_info ri; + float *hdr_data = stbi__hdr_load(s,x,y,comp,req_comp, &ri); + if (hdr_data) + stbi__float_postprocess(hdr_data,x,y,comp,req_comp); + return hdr_data; + } + #endif + data = stbi__load_and_postprocess_8bit(s, x, y, comp, req_comp); + if (data) + return stbi__ldr_to_hdr(data, *x, *y, req_comp ? req_comp : *comp); + return stbi__errpf("unknown image type", "Image not of any known type, or corrupt"); +} + +STBIDEF float *stbi_loadf_from_memory(stbi_uc const *buffer, int len, int *x, int *y, int *comp, int req_comp) +{ + stbi__context s; + stbi__start_mem(&s,buffer,len); + return stbi__loadf_main(&s,x,y,comp,req_comp); +} + +STBIDEF float *stbi_loadf_from_callbacks(stbi_io_callbacks const *clbk, void *user, int *x, int *y, int *comp, int req_comp) +{ + stbi__context s; + stbi__start_callbacks(&s, (stbi_io_callbacks *) clbk, user); + return stbi__loadf_main(&s,x,y,comp,req_comp); +} + +#ifndef STBI_NO_STDIO +STBIDEF float *stbi_loadf(char const *filename, int *x, int *y, int *comp, int req_comp) +{ + float *result; + FILE *f = stbi__fopen(filename, "rb"); + if (!f) return stbi__errpf("can't fopen", "Unable to open file"); + result = stbi_loadf_from_file(f,x,y,comp,req_comp); + fclose(f); + return result; +} + +STBIDEF float *stbi_loadf_from_file(FILE *f, int *x, int *y, int *comp, int req_comp) +{ + stbi__context s; + stbi__start_file(&s,f); + return stbi__loadf_main(&s,x,y,comp,req_comp); +} +#endif // !STBI_NO_STDIO + +#endif // !STBI_NO_LINEAR + +// these is-hdr-or-not is defined independent of whether STBI_NO_LINEAR is +// defined, for API simplicity; if STBI_NO_LINEAR is defined, it always +// reports false! + +STBIDEF int stbi_is_hdr_from_memory(stbi_uc const *buffer, int len) +{ + #ifndef STBI_NO_HDR + stbi__context s; + stbi__start_mem(&s,buffer,len); + return stbi__hdr_test(&s); + #else + STBI_NOTUSED(buffer); + STBI_NOTUSED(len); + return 0; + #endif +} + +#ifndef STBI_NO_STDIO +STBIDEF int stbi_is_hdr (char const *filename) +{ + FILE *f = stbi__fopen(filename, "rb"); + int result=0; + if (f) { + result = stbi_is_hdr_from_file(f); + fclose(f); + } + return result; +} + +STBIDEF int stbi_is_hdr_from_file(FILE *f) +{ + #ifndef STBI_NO_HDR + long pos = ftell(f); + int res; + stbi__context s; + stbi__start_file(&s,f); + res = stbi__hdr_test(&s); + fseek(f, pos, SEEK_SET); + return res; + #else + STBI_NOTUSED(f); + return 0; + #endif +} +#endif // !STBI_NO_STDIO + +STBIDEF int stbi_is_hdr_from_callbacks(stbi_io_callbacks const *clbk, void *user) +{ + #ifndef STBI_NO_HDR + stbi__context s; + stbi__start_callbacks(&s, (stbi_io_callbacks *) clbk, user); + return stbi__hdr_test(&s); + #else + STBI_NOTUSED(clbk); + STBI_NOTUSED(user); + return 0; + #endif +} + +#ifndef STBI_NO_LINEAR +static float stbi__l2h_gamma=2.2f, stbi__l2h_scale=1.0f; + +STBIDEF void stbi_ldr_to_hdr_gamma(float gamma) { stbi__l2h_gamma = gamma; } +STBIDEF void stbi_ldr_to_hdr_scale(float scale) { stbi__l2h_scale = scale; } +#endif + +static float stbi__h2l_gamma_i=1.0f/2.2f, stbi__h2l_scale_i=1.0f; + +STBIDEF void stbi_hdr_to_ldr_gamma(float gamma) { stbi__h2l_gamma_i = 1/gamma; } +STBIDEF void stbi_hdr_to_ldr_scale(float scale) { stbi__h2l_scale_i = 1/scale; } + + +////////////////////////////////////////////////////////////////////////////// +// +// Common code used by all image loaders +// + +enum +{ + STBI__SCAN_load=0, + STBI__SCAN_type, + STBI__SCAN_header +}; + +static void stbi__refill_buffer(stbi__context *s) +{ + int n = (s->io.read)(s->io_user_data,(char*)s->buffer_start,s->buflen); + s->callback_already_read += (int) (s->img_buffer - s->img_buffer_original); + if (n == 0) { + // at end of file, treat same as if from memory, but need to handle case + // where s->img_buffer isn't pointing to safe memory, e.g. 0-byte file + s->read_from_callbacks = 0; + s->img_buffer = s->buffer_start; + s->img_buffer_end = s->buffer_start+1; + *s->img_buffer = 0; + } else { + s->img_buffer = s->buffer_start; + s->img_buffer_end = s->buffer_start + n; + } +} + +stbi_inline static stbi_uc stbi__get8(stbi__context *s) +{ + if (s->img_buffer < s->img_buffer_end) + return *s->img_buffer++; + if (s->read_from_callbacks) { + stbi__refill_buffer(s); + return *s->img_buffer++; + } + return 0; +} + +#if defined(STBI_NO_JPEG) && defined(STBI_NO_HDR) && defined(STBI_NO_PIC) && defined(STBI_NO_PNM) +// nothing +#else +stbi_inline static int stbi__at_eof(stbi__context *s) +{ + if (s->io.read) { + if (!(s->io.eof)(s->io_user_data)) return 0; + // if feof() is true, check if buffer = end + // special case: we've only got the special 0 character at the end + if (s->read_from_callbacks == 0) return 1; + } + + return s->img_buffer >= s->img_buffer_end; +} +#endif + +#if defined(STBI_NO_JPEG) && defined(STBI_NO_PNG) && defined(STBI_NO_BMP) && defined(STBI_NO_PSD) && defined(STBI_NO_TGA) && defined(STBI_NO_GIF) && defined(STBI_NO_PIC) +// nothing +#else +static void stbi__skip(stbi__context *s, int n) +{ + if (n == 0) return; // already there! + if (n < 0) { + s->img_buffer = s->img_buffer_end; + return; + } + if (s->io.read) { + int blen = (int) (s->img_buffer_end - s->img_buffer); + if (blen < n) { + s->img_buffer = s->img_buffer_end; + (s->io.skip)(s->io_user_data, n - blen); + return; + } + } + s->img_buffer += n; +} +#endif + +#if defined(STBI_NO_PNG) && defined(STBI_NO_TGA) && defined(STBI_NO_HDR) && defined(STBI_NO_PNM) +// nothing +#else +static int stbi__getn(stbi__context *s, stbi_uc *buffer, int n) +{ + if (s->io.read) { + int blen = (int) (s->img_buffer_end - s->img_buffer); + if (blen < n) { + int res, count; + + memcpy(buffer, s->img_buffer, blen); + + count = (s->io.read)(s->io_user_data, (char*) buffer + blen, n - blen); + res = (count == (n-blen)); + s->img_buffer = s->img_buffer_end; + return res; + } + } + + if (s->img_buffer+n <= s->img_buffer_end) { + memcpy(buffer, s->img_buffer, n); + s->img_buffer += n; + return 1; + } else + return 0; +} +#endif + +#if defined(STBI_NO_JPEG) && defined(STBI_NO_PNG) && defined(STBI_NO_PSD) && defined(STBI_NO_PIC) +// nothing +#else +static int stbi__get16be(stbi__context *s) +{ + int z = stbi__get8(s); + return (z << 8) + stbi__get8(s); +} +#endif + +#if defined(STBI_NO_PNG) && defined(STBI_NO_PSD) && defined(STBI_NO_PIC) +// nothing +#else +static stbi__uint32 stbi__get32be(stbi__context *s) +{ + stbi__uint32 z = stbi__get16be(s); + return (z << 16) + stbi__get16be(s); +} +#endif + +#if defined(STBI_NO_BMP) && defined(STBI_NO_TGA) && defined(STBI_NO_GIF) +// nothing +#else +static int stbi__get16le(stbi__context *s) +{ + int z = stbi__get8(s); + return z + (stbi__get8(s) << 8); +} +#endif + +#ifndef STBI_NO_BMP +static stbi__uint32 stbi__get32le(stbi__context *s) +{ + stbi__uint32 z = stbi__get16le(s); + z += (stbi__uint32)stbi__get16le(s) << 16; + return z; +} +#endif + +#define STBI__BYTECAST(x) ((stbi_uc) ((x) & 255)) // truncate int to byte without warnings + +#if defined(STBI_NO_JPEG) && defined(STBI_NO_PNG) && defined(STBI_NO_BMP) && defined(STBI_NO_PSD) && defined(STBI_NO_TGA) && defined(STBI_NO_GIF) && defined(STBI_NO_PIC) && defined(STBI_NO_PNM) +// nothing +#else +////////////////////////////////////////////////////////////////////////////// +// +// generic converter from built-in img_n to req_comp +// individual types do this automatically as much as possible (e.g. jpeg +// does all cases internally since it needs to colorspace convert anyway, +// and it never has alpha, so very few cases ). png can automatically +// interleave an alpha=255 channel, but falls back to this for other cases +// +// assume data buffer is malloced, so malloc a new one and free that one +// only failure mode is malloc failing + +static stbi_uc stbi__compute_y(int r, int g, int b) +{ + return (stbi_uc) (((r*77) + (g*150) + (29*b)) >> 8); +} +#endif + +#if defined(STBI_NO_PNG) && defined(STBI_NO_BMP) && defined(STBI_NO_PSD) && defined(STBI_NO_TGA) && defined(STBI_NO_GIF) && defined(STBI_NO_PIC) && defined(STBI_NO_PNM) +// nothing +#else +static unsigned char *stbi__convert_format(unsigned char *data, int img_n, int req_comp, unsigned int x, unsigned int y) +{ + int i,j; + unsigned char *good; + + if (req_comp == img_n) return data; + STBI_ASSERT(req_comp >= 1 && req_comp <= 4); + + good = (unsigned char *) stbi__malloc_mad3(req_comp, x, y, 0); + if (good == NULL) { + STBI_FREE(data); + return stbi__errpuc("outofmem", "Out of memory"); + } + + for (j=0; j < (int) y; ++j) { + unsigned char *src = data + j * x * img_n ; + unsigned char *dest = good + j * x * req_comp; + + #define STBI__COMBO(a,b) ((a)*8+(b)) + #define STBI__CASE(a,b) case STBI__COMBO(a,b): for(i=x-1; i >= 0; --i, src += a, dest += b) + // convert source image with img_n components to one with req_comp components; + // avoid switch per pixel, so use switch per scanline and massive macros + switch (STBI__COMBO(img_n, req_comp)) { + STBI__CASE(1,2) { dest[0]=src[0]; dest[1]=255; } break; + STBI__CASE(1,3) { dest[0]=dest[1]=dest[2]=src[0]; } break; + STBI__CASE(1,4) { dest[0]=dest[1]=dest[2]=src[0]; dest[3]=255; } break; + STBI__CASE(2,1) { dest[0]=src[0]; } break; + STBI__CASE(2,3) { dest[0]=dest[1]=dest[2]=src[0]; } break; + STBI__CASE(2,4) { dest[0]=dest[1]=dest[2]=src[0]; dest[3]=src[1]; } break; + STBI__CASE(3,4) { dest[0]=src[0];dest[1]=src[1];dest[2]=src[2];dest[3]=255; } break; + STBI__CASE(3,1) { dest[0]=stbi__compute_y(src[0],src[1],src[2]); } break; + STBI__CASE(3,2) { dest[0]=stbi__compute_y(src[0],src[1],src[2]); dest[1] = 255; } break; + STBI__CASE(4,1) { dest[0]=stbi__compute_y(src[0],src[1],src[2]); } break; + STBI__CASE(4,2) { dest[0]=stbi__compute_y(src[0],src[1],src[2]); dest[1] = src[3]; } break; + STBI__CASE(4,3) { dest[0]=src[0];dest[1]=src[1];dest[2]=src[2]; } break; + default: STBI_ASSERT(0); STBI_FREE(data); STBI_FREE(good); return stbi__errpuc("unsupported", "Unsupported format conversion"); + } + #undef STBI__CASE + } + + STBI_FREE(data); + return good; +} +#endif + +#if defined(STBI_NO_PNG) && defined(STBI_NO_PSD) +// nothing +#else +static stbi__uint16 stbi__compute_y_16(int r, int g, int b) +{ + return (stbi__uint16) (((r*77) + (g*150) + (29*b)) >> 8); +} +#endif + +#if defined(STBI_NO_PNG) && defined(STBI_NO_PSD) +// nothing +#else +static stbi__uint16 *stbi__convert_format16(stbi__uint16 *data, int img_n, int req_comp, unsigned int x, unsigned int y) +{ + int i,j; + stbi__uint16 *good; + + if (req_comp == img_n) return data; + STBI_ASSERT(req_comp >= 1 && req_comp <= 4); + + good = (stbi__uint16 *) stbi__malloc(req_comp * x * y * 2); + if (good == NULL) { + STBI_FREE(data); + return (stbi__uint16 *) stbi__errpuc("outofmem", "Out of memory"); + } + + for (j=0; j < (int) y; ++j) { + stbi__uint16 *src = data + j * x * img_n ; + stbi__uint16 *dest = good + j * x * req_comp; + + #define STBI__COMBO(a,b) ((a)*8+(b)) + #define STBI__CASE(a,b) case STBI__COMBO(a,b): for(i=x-1; i >= 0; --i, src += a, dest += b) + // convert source image with img_n components to one with req_comp components; + // avoid switch per pixel, so use switch per scanline and massive macros + switch (STBI__COMBO(img_n, req_comp)) { + STBI__CASE(1,2) { dest[0]=src[0]; dest[1]=0xffff; } break; + STBI__CASE(1,3) { dest[0]=dest[1]=dest[2]=src[0]; } break; + STBI__CASE(1,4) { dest[0]=dest[1]=dest[2]=src[0]; dest[3]=0xffff; } break; + STBI__CASE(2,1) { dest[0]=src[0]; } break; + STBI__CASE(2,3) { dest[0]=dest[1]=dest[2]=src[0]; } break; + STBI__CASE(2,4) { dest[0]=dest[1]=dest[2]=src[0]; dest[3]=src[1]; } break; + STBI__CASE(3,4) { dest[0]=src[0];dest[1]=src[1];dest[2]=src[2];dest[3]=0xffff; } break; + STBI__CASE(3,1) { dest[0]=stbi__compute_y_16(src[0],src[1],src[2]); } break; + STBI__CASE(3,2) { dest[0]=stbi__compute_y_16(src[0],src[1],src[2]); dest[1] = 0xffff; } break; + STBI__CASE(4,1) { dest[0]=stbi__compute_y_16(src[0],src[1],src[2]); } break; + STBI__CASE(4,2) { dest[0]=stbi__compute_y_16(src[0],src[1],src[2]); dest[1] = src[3]; } break; + STBI__CASE(4,3) { dest[0]=src[0];dest[1]=src[1];dest[2]=src[2]; } break; + default: STBI_ASSERT(0); STBI_FREE(data); STBI_FREE(good); return (stbi__uint16*) stbi__errpuc("unsupported", "Unsupported format conversion"); + } + #undef STBI__CASE + } + + STBI_FREE(data); + return good; +} +#endif + +#ifndef STBI_NO_LINEAR +static float *stbi__ldr_to_hdr(stbi_uc *data, int x, int y, int comp) +{ + int i,k,n; + float *output; + if (!data) return NULL; + output = (float *) stbi__malloc_mad4(x, y, comp, sizeof(float), 0); + if (output == NULL) { STBI_FREE(data); return stbi__errpf("outofmem", "Out of memory"); } + // compute number of non-alpha components + if (comp & 1) n = comp; else n = comp-1; + for (i=0; i < x*y; ++i) { + for (k=0; k < n; ++k) { + output[i*comp + k] = (float) (pow(data[i*comp+k]/255.0f, stbi__l2h_gamma) * stbi__l2h_scale); + } + } + if (n < comp) { + for (i=0; i < x*y; ++i) { + output[i*comp + n] = data[i*comp + n]/255.0f; + } + } + STBI_FREE(data); + return output; +} +#endif + +#ifndef STBI_NO_HDR +#define stbi__float2int(x) ((int) (x)) +static stbi_uc *stbi__hdr_to_ldr(float *data, int x, int y, int comp) +{ + int i,k,n; + stbi_uc *output; + if (!data) return NULL; + output = (stbi_uc *) stbi__malloc_mad3(x, y, comp, 0); + if (output == NULL) { STBI_FREE(data); return stbi__errpuc("outofmem", "Out of memory"); } + // compute number of non-alpha components + if (comp & 1) n = comp; else n = comp-1; + for (i=0; i < x*y; ++i) { + for (k=0; k < n; ++k) { + float z = (float) pow(data[i*comp+k]*stbi__h2l_scale_i, stbi__h2l_gamma_i) * 255 + 0.5f; + if (z < 0) z = 0; + if (z > 255) z = 255; + output[i*comp + k] = (stbi_uc) stbi__float2int(z); + } + if (k < comp) { + float z = data[i*comp+k] * 255 + 0.5f; + if (z < 0) z = 0; + if (z > 255) z = 255; + output[i*comp + k] = (stbi_uc) stbi__float2int(z); + } + } + STBI_FREE(data); + return output; +} +#endif + +////////////////////////////////////////////////////////////////////////////// +// +// "baseline" JPEG/JFIF decoder +// +// simple implementation +// - doesn't support delayed output of y-dimension +// - simple interface (only one output format: 8-bit interleaved RGB) +// - doesn't try to recover corrupt jpegs +// - doesn't allow partial loading, loading multiple at once +// - still fast on x86 (copying globals into locals doesn't help x86) +// - allocates lots of intermediate memory (full size of all components) +// - non-interleaved case requires this anyway +// - allows good upsampling (see next) +// high-quality +// - upsampled channels are bilinearly interpolated, even across blocks +// - quality integer IDCT derived from IJG's 'slow' +// performance +// - fast huffman; reasonable integer IDCT +// - some SIMD kernels for common paths on targets with SSE2/NEON +// - uses a lot of intermediate memory, could cache poorly + +#ifndef STBI_NO_JPEG + +// huffman decoding acceleration +#define FAST_BITS 9 // larger handles more cases; smaller stomps less cache + +typedef struct +{ + stbi_uc fast[1 << FAST_BITS]; + // weirdly, repacking this into AoS is a 10% speed loss, instead of a win + stbi__uint16 code[256]; + stbi_uc values[256]; + stbi_uc size[257]; + unsigned int maxcode[18]; + int delta[17]; // old 'firstsymbol' - old 'firstcode' +} stbi__huffman; + +typedef struct +{ + stbi__context *s; + stbi__huffman huff_dc[4]; + stbi__huffman huff_ac[4]; + stbi__uint16 dequant[4][64]; + stbi__int16 fast_ac[4][1 << FAST_BITS]; + +// sizes for components, interleaved MCUs + int img_h_max, img_v_max; + int img_mcu_x, img_mcu_y; + int img_mcu_w, img_mcu_h; + +// definition of jpeg image component + struct + { + int id; + int h,v; + int tq; + int hd,ha; + int dc_pred; + + int x,y,w2,h2; + stbi_uc *data; + void *raw_data, *raw_coeff; + stbi_uc *linebuf; + short *coeff; // progressive only + int coeff_w, coeff_h; // number of 8x8 coefficient blocks + } img_comp[4]; + + stbi__uint32 code_buffer; // jpeg entropy-coded buffer + int code_bits; // number of valid bits + unsigned char marker; // marker seen while filling entropy buffer + int nomore; // flag if we saw a marker so must stop +#ifdef STBI_STRICT_JPEG + int truncated; // flag if decoding consumed synthetic bits + int synthetic_bits; // zero bits added after a marker or EOF +#endif + + int progressive; + int spec_start; + int spec_end; + int succ_high; + int succ_low; + int eob_run; + int jfif; + int app14_color_transform; // Adobe APP14 tag + int rgb; + + int scan_n, order[4]; + int restart_interval, todo; + +// kernels + void (*idct_block_kernel)(stbi_uc *out, int out_stride, short data[64]); + void (*YCbCr_to_RGB_kernel)(stbi_uc *out, const stbi_uc *y, const stbi_uc *pcb, const stbi_uc *pcr, int count, int step); + stbi_uc *(*resample_row_hv_2_kernel)(stbi_uc *out, stbi_uc *in_near, stbi_uc *in_far, int w, int hs); +} stbi__jpeg; + +static int stbi__build_huffman(stbi__huffman *h, int *count) +{ + int i,j,k=0; + unsigned int code; + // build size list for each symbol (from JPEG spec) + for (i=0; i < 16; ++i) { + for (j=0; j < count[i]; ++j) { + h->size[k++] = (stbi_uc) (i+1); + if(k >= 257) return stbi__err("bad size list","Corrupt JPEG"); + } + } + h->size[k] = 0; + + // compute actual symbols (from jpeg spec) + code = 0; + k = 0; + for(j=1; j <= 16; ++j) { + // compute delta to add to code to compute symbol id + h->delta[j] = k - code; + if (h->size[k] == j) { + while (h->size[k] == j) + h->code[k++] = (stbi__uint16) (code++); + if (code-1 >= (1u << j)) return stbi__err("bad code lengths","Corrupt JPEG"); + } + // compute largest code + 1 for this size, preshifted as needed later + h->maxcode[j] = code << (16-j); + code <<= 1; + } + h->maxcode[j] = 0xffffffff; + + // build non-spec acceleration table; 255 is flag for not-accelerated + memset(h->fast, 255, 1 << FAST_BITS); + for (i=0; i < k; ++i) { + int s = h->size[i]; + if (s <= FAST_BITS) { + int c = h->code[i] << (FAST_BITS-s); + int m = 1 << (FAST_BITS-s); + for (j=0; j < m; ++j) { + h->fast[c+j] = (stbi_uc) i; + } + } + } + return 1; +} + +// build a table that decodes both magnitude and value of small ACs in +// one go. +static void stbi__build_fast_ac(stbi__int16 *fast_ac, stbi__huffman *h) +{ + int i; + for (i=0; i < (1 << FAST_BITS); ++i) { + stbi_uc fast = h->fast[i]; + fast_ac[i] = 0; + if (fast < 255) { + int rs = h->values[fast]; + int run = (rs >> 4) & 15; + int magbits = rs & 15; + int len = h->size[fast]; + + if (magbits && len + magbits <= FAST_BITS) { + // magnitude code followed by receive_extend code + int k = ((i << len) & ((1 << FAST_BITS) - 1)) >> (FAST_BITS - magbits); + int m = 1 << (magbits - 1); + if (k < m) k += (~0U << magbits) + 1; + // if the result is small enough, we can fit it in fast_ac table + if (k >= -128 && k <= 127) + fast_ac[i] = (stbi__int16) ((k * 256) + (run * 16) + (len + magbits)); + } + } + } +} + +static void stbi__grow_buffer_unsafe(stbi__jpeg *j) +{ + do { + unsigned int b; +#ifdef STBI_STRICT_JPEG + if (j->nomore || stbi__at_eof(j->s)) { + j->synthetic_bits += 8; + b = 0; + } else { + b = stbi__get8(j->s); + } +#else + b = j->nomore ? 0 : stbi__get8(j->s); +#endif + if (b == 0xff) { + int c = stbi__get8(j->s); + while (c == 0xff) c = stbi__get8(j->s); // consume fill bytes + if (c != 0) { + j->marker = (unsigned char) c; + j->nomore = 1; + return; + } + } + j->code_buffer |= b << (24 - j->code_bits); + j->code_bits += 8; + } while (j->code_bits <= 24); +} + +// (1 << n) - 1 +static const stbi__uint32 stbi__bmask[17]={0,1,3,7,15,31,63,127,255,511,1023,2047,4095,8191,16383,32767,65535}; + +// decode a jpeg huffman value from the bitstream +stbi_inline static int stbi__jpeg_huff_decode(stbi__jpeg *j, stbi__huffman *h) +{ + unsigned int temp; + int c,k; + + if (j->code_bits < 16) stbi__grow_buffer_unsafe(j); + + // look at the top FAST_BITS and determine what symbol ID it is, + // if the code is <= FAST_BITS + c = (j->code_buffer >> (32 - FAST_BITS)) & ((1 << FAST_BITS)-1); + k = h->fast[c]; + if (k < 255) { + int s = h->size[k]; + if (s > j->code_bits) + return -1; + j->code_buffer <<= s; + j->code_bits -= s; + return h->values[k]; + } + + // naive test is to shift the code_buffer down so k bits are + // valid, then test against maxcode. To speed this up, we've + // preshifted maxcode left so that it has (16-k) 0s at the + // end; in other words, regardless of the number of bits, it + // wants to be compared against something shifted to have 16; + // that way we don't need to shift inside the loop. + temp = j->code_buffer >> 16; + for (k=FAST_BITS+1 ; ; ++k) + if (temp < h->maxcode[k]) + break; + if (k == 17) { + // error! code not found + j->code_bits -= 16; + return -1; + } + + if (k > j->code_bits) + return -1; + + // convert the huffman code to the symbol id + c = ((j->code_buffer >> (32 - k)) & stbi__bmask[k]) + h->delta[k]; + if(c < 0 || c >= 256) // symbol id out of bounds! + return -1; + STBI_ASSERT((((j->code_buffer) >> (32 - h->size[c])) & stbi__bmask[h->size[c]]) == h->code[c]); + + // convert the id to a symbol + j->code_bits -= k; + j->code_buffer <<= k; + return h->values[c]; +} + +// bias[n] = (-1<code_bits < n) stbi__grow_buffer_unsafe(j); + if (j->code_bits < n) return 0; // ran out of bits from stream, return 0s intead of continuing + + sgn = j->code_buffer >> 31; // sign bit always in MSB; 0 if MSB clear (positive), 1 if MSB set (negative) + k = stbi_lrot(j->code_buffer, n); + j->code_buffer = k & ~stbi__bmask[n]; + k &= stbi__bmask[n]; + j->code_bits -= n; + return k + (stbi__jbias[n] & (sgn - 1)); +} + +// get some unsigned bits +stbi_inline static int stbi__jpeg_get_bits(stbi__jpeg *j, int n) +{ + unsigned int k; + if (j->code_bits < n) stbi__grow_buffer_unsafe(j); + if (j->code_bits < n) return 0; // ran out of bits from stream, return 0s intead of continuing + k = stbi_lrot(j->code_buffer, n); + j->code_buffer = k & ~stbi__bmask[n]; + k &= stbi__bmask[n]; + j->code_bits -= n; + return k; +} + +stbi_inline static int stbi__jpeg_get_bit(stbi__jpeg *j) +{ + unsigned int k; + if (j->code_bits < 1) stbi__grow_buffer_unsafe(j); + if (j->code_bits < 1) return 0; // ran out of bits from stream, return 0s intead of continuing + k = j->code_buffer; + j->code_buffer <<= 1; + --j->code_bits; + return k & 0x80000000; +} + +// given a value that's at position X in the zigzag stream, +// where does it appear in the 8x8 matrix coded as row-major? +static const stbi_uc stbi__jpeg_dezigzag[64+15] = +{ + 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, 13, 6, 7, 14, 21, 28, + 35, 42, 49, 56, 57, 50, 43, 36, + 29, 22, 15, 23, 30, 37, 44, 51, + 58, 59, 52, 45, 38, 31, 39, 46, + 53, 60, 61, 54, 47, 55, 62, 63, + // let corrupt input sample past end + 63, 63, 63, 63, 63, 63, 63, 63, + 63, 63, 63, 63, 63, 63, 63 +}; + +// decode one 64-entry block-- +static int stbi__jpeg_decode_block(stbi__jpeg *j, short data[64], stbi__huffman *hdc, stbi__huffman *hac, stbi__int16 *fac, int b, stbi__uint16 *dequant) +{ + int diff,dc,k; + int t; + + if (j->code_bits < 16) stbi__grow_buffer_unsafe(j); + t = stbi__jpeg_huff_decode(j, hdc); + if (t < 0 || t > 15) return stbi__err("bad huffman code","Corrupt JPEG"); + + // 0 all the ac values now so we can do it 32-bits at a time + memset(data,0,64*sizeof(data[0])); + + diff = t ? stbi__extend_receive(j, t) : 0; + if (!stbi__addints_valid(j->img_comp[b].dc_pred, diff)) return stbi__err("bad delta","Corrupt JPEG"); + dc = j->img_comp[b].dc_pred + diff; + j->img_comp[b].dc_pred = dc; + if (!stbi__mul2shorts_valid(dc, dequant[0])) return stbi__err("can't merge dc and ac", "Corrupt JPEG"); + data[0] = (short) (dc * dequant[0]); + + // decode AC components, see JPEG spec + k = 1; + do { + unsigned int zig; + int c,r,s; + if (j->code_bits < 16) stbi__grow_buffer_unsafe(j); + c = (j->code_buffer >> (32 - FAST_BITS)) & ((1 << FAST_BITS)-1); + r = fac[c]; + if (r) { // fast-AC path + k += (r >> 4) & 15; // run + s = r & 15; // combined length + if (s > j->code_bits) return stbi__err("bad huffman code", "Combined length longer than code bits available"); + j->code_buffer <<= s; + j->code_bits -= s; + // decode into unzigzag'd location + zig = stbi__jpeg_dezigzag[k++]; + data[zig] = (short) ((r >> 8) * dequant[zig]); + } else { + int rs = stbi__jpeg_huff_decode(j, hac); + if (rs < 0) return stbi__err("bad huffman code","Corrupt JPEG"); + s = rs & 15; + r = rs >> 4; + if (s == 0) { + if (rs != 0xf0) break; // end block + k += 16; + } else { + k += r; + // decode into unzigzag'd location + zig = stbi__jpeg_dezigzag[k++]; + data[zig] = (short) (stbi__extend_receive(j,s) * dequant[zig]); + } + } + } while (k < 64); + return 1; +} + +static int stbi__jpeg_decode_block_prog_dc(stbi__jpeg *j, short data[64], stbi__huffman *hdc, int b) +{ + int diff,dc; + int t; + if (j->spec_end != 0) return stbi__err("can't merge dc and ac", "Corrupt JPEG"); + + if (j->code_bits < 16) stbi__grow_buffer_unsafe(j); + + if (j->succ_high == 0) { + // first scan for DC coefficient, must be first + memset(data,0,64*sizeof(data[0])); // 0 all the ac values now + t = stbi__jpeg_huff_decode(j, hdc); + if (t < 0 || t > 15) return stbi__err("can't merge dc and ac", "Corrupt JPEG"); + diff = t ? stbi__extend_receive(j, t) : 0; + + if (!stbi__addints_valid(j->img_comp[b].dc_pred, diff)) return stbi__err("bad delta", "Corrupt JPEG"); + dc = j->img_comp[b].dc_pred + diff; + j->img_comp[b].dc_pred = dc; + if (!stbi__mul2shorts_valid(dc, 1 << j->succ_low)) return stbi__err("can't merge dc and ac", "Corrupt JPEG"); + data[0] = (short) (dc * (1 << j->succ_low)); + } else { + // refinement scan for DC coefficient + if (stbi__jpeg_get_bit(j)) + data[0] += (short) (1 << j->succ_low); + } + return 1; +} + +// @OPTIMIZE: store non-zigzagged during the decode passes, +// and only de-zigzag when dequantizing +static int stbi__jpeg_decode_block_prog_ac(stbi__jpeg *j, short data[64], stbi__huffman *hac, stbi__int16 *fac) +{ + int k; + if (j->spec_start == 0) return stbi__err("can't merge dc and ac", "Corrupt JPEG"); + + if (j->succ_high == 0) { + int shift = j->succ_low; + + if (j->eob_run) { + --j->eob_run; + return 1; + } + + k = j->spec_start; + do { + unsigned int zig; + int c,r,s; + if (j->code_bits < 16) stbi__grow_buffer_unsafe(j); + c = (j->code_buffer >> (32 - FAST_BITS)) & ((1 << FAST_BITS)-1); + r = fac[c]; + if (r) { // fast-AC path + k += (r >> 4) & 15; // run + s = r & 15; // combined length + if (s > j->code_bits) return stbi__err("bad huffman code", "Combined length longer than code bits available"); + j->code_buffer <<= s; + j->code_bits -= s; + zig = stbi__jpeg_dezigzag[k++]; + data[zig] = (short) ((r >> 8) * (1 << shift)); + } else { + int rs = stbi__jpeg_huff_decode(j, hac); + if (rs < 0) return stbi__err("bad huffman code","Corrupt JPEG"); + s = rs & 15; + r = rs >> 4; + if (s == 0) { + if (r < 15) { + j->eob_run = (1 << r); + if (r) + j->eob_run += stbi__jpeg_get_bits(j, r); + --j->eob_run; + break; + } + k += 16; + } else { + k += r; + zig = stbi__jpeg_dezigzag[k++]; + data[zig] = (short) (stbi__extend_receive(j,s) * (1 << shift)); + } + } + } while (k <= j->spec_end); + } else { + // refinement scan for these AC coefficients + + short bit = (short) (1 << j->succ_low); + + if (j->eob_run) { + --j->eob_run; + for (k = j->spec_start; k <= j->spec_end; ++k) { + short *p = &data[stbi__jpeg_dezigzag[k]]; + if (*p != 0) + if (stbi__jpeg_get_bit(j)) + if ((*p & bit)==0) { + if (*p > 0) + *p += bit; + else + *p -= bit; + } + } + } else { + k = j->spec_start; + do { + int r,s; + int rs = stbi__jpeg_huff_decode(j, hac); // @OPTIMIZE see if we can use the fast path here, advance-by-r is so slow, eh + if (rs < 0) return stbi__err("bad huffman code","Corrupt JPEG"); + s = rs & 15; + r = rs >> 4; + if (s == 0) { + if (r < 15) { + j->eob_run = (1 << r) - 1; + if (r) + j->eob_run += stbi__jpeg_get_bits(j, r); + r = 64; // force end of block + } else { + // r=15 s=0 should write 16 0s, so we just do + // a run of 15 0s and then write s (which is 0), + // so we don't have to do anything special here + } + } else { + if (s != 1) return stbi__err("bad huffman code", "Corrupt JPEG"); + // sign bit + if (stbi__jpeg_get_bit(j)) + s = bit; + else + s = -bit; + } + + // advance by r + while (k <= j->spec_end) { + short *p = &data[stbi__jpeg_dezigzag[k++]]; + if (*p != 0) { + if (stbi__jpeg_get_bit(j)) + if ((*p & bit)==0) { + if (*p > 0) + *p += bit; + else + *p -= bit; + } + } else { + if (r == 0) { + *p = (short) s; + break; + } + --r; + } + } + } while (k <= j->spec_end); + } + } + return 1; +} + +// take a -128..127 value and stbi__clamp it and convert to 0..255 +stbi_inline static stbi_uc stbi__clamp(int x) +{ + // trick to use a single test to catch both cases + if ((unsigned int) x > 255) { + if (x < 0) return 0; + if (x > 255) return 255; + } + return (stbi_uc) x; +} + +#define stbi__f2f(x) ((int) (((x) * 4096 + 0.5))) +#define stbi__fsh(x) ((x) * 4096) + +// derived from jidctint -- DCT_ISLOW +#define STBI__IDCT_1D(s0,s1,s2,s3,s4,s5,s6,s7) \ + int t0,t1,t2,t3,p1,p2,p3,p4,p5,x0,x1,x2,x3; \ + p2 = s2; \ + p3 = s6; \ + p1 = (p2+p3) * stbi__f2f(0.5411961f); \ + t2 = p1 + p3*stbi__f2f(-1.847759065f); \ + t3 = p1 + p2*stbi__f2f( 0.765366865f); \ + p2 = s0; \ + p3 = s4; \ + t0 = stbi__fsh(p2+p3); \ + t1 = stbi__fsh(p2-p3); \ + x0 = t0+t3; \ + x3 = t0-t3; \ + x1 = t1+t2; \ + x2 = t1-t2; \ + t0 = s7; \ + t1 = s5; \ + t2 = s3; \ + t3 = s1; \ + p3 = t0+t2; \ + p4 = t1+t3; \ + p1 = t0+t3; \ + p2 = t1+t2; \ + p5 = (p3+p4)*stbi__f2f( 1.175875602f); \ + t0 = t0*stbi__f2f( 0.298631336f); \ + t1 = t1*stbi__f2f( 2.053119869f); \ + t2 = t2*stbi__f2f( 3.072711026f); \ + t3 = t3*stbi__f2f( 1.501321110f); \ + p1 = p5 + p1*stbi__f2f(-0.899976223f); \ + p2 = p5 + p2*stbi__f2f(-2.562915447f); \ + p3 = p3*stbi__f2f(-1.961570560f); \ + p4 = p4*stbi__f2f(-0.390180644f); \ + t3 += p1+p4; \ + t2 += p2+p3; \ + t1 += p2+p4; \ + t0 += p1+p3; + +static void stbi__idct_block(stbi_uc *out, int out_stride, short data[64]) +{ + int i,val[64],*v=val; + stbi_uc *o; + short *d = data; + + // columns + for (i=0; i < 8; ++i,++d, ++v) { + // if all zeroes, shortcut -- this avoids dequantizing 0s and IDCTing + if (d[ 8]==0 && d[16]==0 && d[24]==0 && d[32]==0 + && d[40]==0 && d[48]==0 && d[56]==0) { + // no shortcut 0 seconds + // (1|2|3|4|5|6|7)==0 0 seconds + // all separate -0.047 seconds + // 1 && 2|3 && 4|5 && 6|7: -0.047 seconds + int dcterm = d[0]*4; + v[0] = v[8] = v[16] = v[24] = v[32] = v[40] = v[48] = v[56] = dcterm; + } else { + STBI__IDCT_1D(d[ 0],d[ 8],d[16],d[24],d[32],d[40],d[48],d[56]) + // constants scaled things up by 1<<12; let's bring them back + // down, but keep 2 extra bits of precision + x0 += 512; x1 += 512; x2 += 512; x3 += 512; + v[ 0] = (x0+t3) >> 10; + v[56] = (x0-t3) >> 10; + v[ 8] = (x1+t2) >> 10; + v[48] = (x1-t2) >> 10; + v[16] = (x2+t1) >> 10; + v[40] = (x2-t1) >> 10; + v[24] = (x3+t0) >> 10; + v[32] = (x3-t0) >> 10; + } + } + + for (i=0, v=val, o=out; i < 8; ++i,v+=8,o+=out_stride) { + // no fast case since the first 1D IDCT spread components out + STBI__IDCT_1D(v[0],v[1],v[2],v[3],v[4],v[5],v[6],v[7]) + // constants scaled things up by 1<<12, plus we had 1<<2 from first + // loop, plus horizontal and vertical each scale by sqrt(8) so together + // we've got an extra 1<<3, so 1<<17 total we need to remove. + // so we want to round that, which means adding 0.5 * 1<<17, + // aka 65536. Also, we'll end up with -128 to 127 that we want + // to encode as 0..255 by adding 128, so we'll add that before the shift + x0 += 65536 + (128<<17); + x1 += 65536 + (128<<17); + x2 += 65536 + (128<<17); + x3 += 65536 + (128<<17); + // tried computing the shifts into temps, or'ing the temps to see + // if any were out of range, but that was slower + o[0] = stbi__clamp((x0+t3) >> 17); + o[7] = stbi__clamp((x0-t3) >> 17); + o[1] = stbi__clamp((x1+t2) >> 17); + o[6] = stbi__clamp((x1-t2) >> 17); + o[2] = stbi__clamp((x2+t1) >> 17); + o[5] = stbi__clamp((x2-t1) >> 17); + o[3] = stbi__clamp((x3+t0) >> 17); + o[4] = stbi__clamp((x3-t0) >> 17); + } +} + +#ifdef STBI_SSE2 +// sse2 integer IDCT. not the fastest possible implementation but it +// produces bit-identical results to the generic C version so it's +// fully "transparent". +static void stbi__idct_simd(stbi_uc *out, int out_stride, short data[64]) +{ + // This is constructed to match our regular (generic) integer IDCT exactly. + __m128i row0, row1, row2, row3, row4, row5, row6, row7; + __m128i tmp; + + // dot product constant: even elems=x, odd elems=y + #define dct_const(x,y) _mm_setr_epi16((x),(y),(x),(y),(x),(y),(x),(y)) + + // out(0) = c0[even]*x + c0[odd]*y (c0, x, y 16-bit, out 32-bit) + // out(1) = c1[even]*x + c1[odd]*y + #define dct_rot(out0,out1, x,y,c0,c1) \ + __m128i c0##lo = _mm_unpacklo_epi16((x),(y)); \ + __m128i c0##hi = _mm_unpackhi_epi16((x),(y)); \ + __m128i out0##_l = _mm_madd_epi16(c0##lo, c0); \ + __m128i out0##_h = _mm_madd_epi16(c0##hi, c0); \ + __m128i out1##_l = _mm_madd_epi16(c0##lo, c1); \ + __m128i out1##_h = _mm_madd_epi16(c0##hi, c1) + + // out = in << 12 (in 16-bit, out 32-bit) + #define dct_widen(out, in) \ + __m128i out##_l = _mm_srai_epi32(_mm_unpacklo_epi16(_mm_setzero_si128(), (in)), 4); \ + __m128i out##_h = _mm_srai_epi32(_mm_unpackhi_epi16(_mm_setzero_si128(), (in)), 4) + + // wide add + #define dct_wadd(out, a, b) \ + __m128i out##_l = _mm_add_epi32(a##_l, b##_l); \ + __m128i out##_h = _mm_add_epi32(a##_h, b##_h) + + // wide sub + #define dct_wsub(out, a, b) \ + __m128i out##_l = _mm_sub_epi32(a##_l, b##_l); \ + __m128i out##_h = _mm_sub_epi32(a##_h, b##_h) + + // butterfly a/b, add bias, then shift by "s" and pack + #define dct_bfly32o(out0, out1, a,b,bias,s) \ + { \ + __m128i abiased_l = _mm_add_epi32(a##_l, bias); \ + __m128i abiased_h = _mm_add_epi32(a##_h, bias); \ + dct_wadd(sum, abiased, b); \ + dct_wsub(dif, abiased, b); \ + out0 = _mm_packs_epi32(_mm_srai_epi32(sum_l, s), _mm_srai_epi32(sum_h, s)); \ + out1 = _mm_packs_epi32(_mm_srai_epi32(dif_l, s), _mm_srai_epi32(dif_h, s)); \ + } + + // 8-bit interleave step (for transposes) + #define dct_interleave8(a, b) \ + tmp = a; \ + a = _mm_unpacklo_epi8(a, b); \ + b = _mm_unpackhi_epi8(tmp, b) + + // 16-bit interleave step (for transposes) + #define dct_interleave16(a, b) \ + tmp = a; \ + a = _mm_unpacklo_epi16(a, b); \ + b = _mm_unpackhi_epi16(tmp, b) + + #define dct_pass(bias,shift) \ + { \ + /* even part */ \ + dct_rot(t2e,t3e, row2,row6, rot0_0,rot0_1); \ + __m128i sum04 = _mm_add_epi16(row0, row4); \ + __m128i dif04 = _mm_sub_epi16(row0, row4); \ + dct_widen(t0e, sum04); \ + dct_widen(t1e, dif04); \ + dct_wadd(x0, t0e, t3e); \ + dct_wsub(x3, t0e, t3e); \ + dct_wadd(x1, t1e, t2e); \ + dct_wsub(x2, t1e, t2e); \ + /* odd part */ \ + dct_rot(y0o,y2o, row7,row3, rot2_0,rot2_1); \ + dct_rot(y1o,y3o, row5,row1, rot3_0,rot3_1); \ + __m128i sum17 = _mm_add_epi16(row1, row7); \ + __m128i sum35 = _mm_add_epi16(row3, row5); \ + dct_rot(y4o,y5o, sum17,sum35, rot1_0,rot1_1); \ + dct_wadd(x4, y0o, y4o); \ + dct_wadd(x5, y1o, y5o); \ + dct_wadd(x6, y2o, y5o); \ + dct_wadd(x7, y3o, y4o); \ + dct_bfly32o(row0,row7, x0,x7,bias,shift); \ + dct_bfly32o(row1,row6, x1,x6,bias,shift); \ + dct_bfly32o(row2,row5, x2,x5,bias,shift); \ + dct_bfly32o(row3,row4, x3,x4,bias,shift); \ + } + + __m128i rot0_0 = dct_const(stbi__f2f(0.5411961f), stbi__f2f(0.5411961f) + stbi__f2f(-1.847759065f)); + __m128i rot0_1 = dct_const(stbi__f2f(0.5411961f) + stbi__f2f( 0.765366865f), stbi__f2f(0.5411961f)); + __m128i rot1_0 = dct_const(stbi__f2f(1.175875602f) + stbi__f2f(-0.899976223f), stbi__f2f(1.175875602f)); + __m128i rot1_1 = dct_const(stbi__f2f(1.175875602f), stbi__f2f(1.175875602f) + stbi__f2f(-2.562915447f)); + __m128i rot2_0 = dct_const(stbi__f2f(-1.961570560f) + stbi__f2f( 0.298631336f), stbi__f2f(-1.961570560f)); + __m128i rot2_1 = dct_const(stbi__f2f(-1.961570560f), stbi__f2f(-1.961570560f) + stbi__f2f( 3.072711026f)); + __m128i rot3_0 = dct_const(stbi__f2f(-0.390180644f) + stbi__f2f( 2.053119869f), stbi__f2f(-0.390180644f)); + __m128i rot3_1 = dct_const(stbi__f2f(-0.390180644f), stbi__f2f(-0.390180644f) + stbi__f2f( 1.501321110f)); + + // rounding biases in column/row passes, see stbi__idct_block for explanation. + __m128i bias_0 = _mm_set1_epi32(512); + __m128i bias_1 = _mm_set1_epi32(65536 + (128<<17)); + + // load + row0 = _mm_load_si128((const __m128i *) (data + 0*8)); + row1 = _mm_load_si128((const __m128i *) (data + 1*8)); + row2 = _mm_load_si128((const __m128i *) (data + 2*8)); + row3 = _mm_load_si128((const __m128i *) (data + 3*8)); + row4 = _mm_load_si128((const __m128i *) (data + 4*8)); + row5 = _mm_load_si128((const __m128i *) (data + 5*8)); + row6 = _mm_load_si128((const __m128i *) (data + 6*8)); + row7 = _mm_load_si128((const __m128i *) (data + 7*8)); + + // column pass + dct_pass(bias_0, 10); + + { + // 16bit 8x8 transpose pass 1 + dct_interleave16(row0, row4); + dct_interleave16(row1, row5); + dct_interleave16(row2, row6); + dct_interleave16(row3, row7); + + // transpose pass 2 + dct_interleave16(row0, row2); + dct_interleave16(row1, row3); + dct_interleave16(row4, row6); + dct_interleave16(row5, row7); + + // transpose pass 3 + dct_interleave16(row0, row1); + dct_interleave16(row2, row3); + dct_interleave16(row4, row5); + dct_interleave16(row6, row7); + } + + // row pass + dct_pass(bias_1, 17); + + { + // pack + __m128i p0 = _mm_packus_epi16(row0, row1); // a0a1a2a3...a7b0b1b2b3...b7 + __m128i p1 = _mm_packus_epi16(row2, row3); + __m128i p2 = _mm_packus_epi16(row4, row5); + __m128i p3 = _mm_packus_epi16(row6, row7); + + // 8bit 8x8 transpose pass 1 + dct_interleave8(p0, p2); // a0e0a1e1... + dct_interleave8(p1, p3); // c0g0c1g1... + + // transpose pass 2 + dct_interleave8(p0, p1); // a0c0e0g0... + dct_interleave8(p2, p3); // b0d0f0h0... + + // transpose pass 3 + dct_interleave8(p0, p2); // a0b0c0d0... + dct_interleave8(p1, p3); // a4b4c4d4... + + // store + _mm_storel_epi64((__m128i *) out, p0); out += out_stride; + _mm_storel_epi64((__m128i *) out, _mm_shuffle_epi32(p0, 0x4e)); out += out_stride; + _mm_storel_epi64((__m128i *) out, p2); out += out_stride; + _mm_storel_epi64((__m128i *) out, _mm_shuffle_epi32(p2, 0x4e)); out += out_stride; + _mm_storel_epi64((__m128i *) out, p1); out += out_stride; + _mm_storel_epi64((__m128i *) out, _mm_shuffle_epi32(p1, 0x4e)); out += out_stride; + _mm_storel_epi64((__m128i *) out, p3); out += out_stride; + _mm_storel_epi64((__m128i *) out, _mm_shuffle_epi32(p3, 0x4e)); + } + +#undef dct_const +#undef dct_rot +#undef dct_widen +#undef dct_wadd +#undef dct_wsub +#undef dct_bfly32o +#undef dct_interleave8 +#undef dct_interleave16 +#undef dct_pass +} + +#endif // STBI_SSE2 + +#ifdef STBI_NEON + +// NEON integer IDCT. should produce bit-identical +// results to the generic C version. +static void stbi__idct_simd(stbi_uc *out, int out_stride, short data[64]) +{ + int16x8_t row0, row1, row2, row3, row4, row5, row6, row7; + + int16x4_t rot0_0 = vdup_n_s16(stbi__f2f(0.5411961f)); + int16x4_t rot0_1 = vdup_n_s16(stbi__f2f(-1.847759065f)); + int16x4_t rot0_2 = vdup_n_s16(stbi__f2f( 0.765366865f)); + int16x4_t rot1_0 = vdup_n_s16(stbi__f2f( 1.175875602f)); + int16x4_t rot1_1 = vdup_n_s16(stbi__f2f(-0.899976223f)); + int16x4_t rot1_2 = vdup_n_s16(stbi__f2f(-2.562915447f)); + int16x4_t rot2_0 = vdup_n_s16(stbi__f2f(-1.961570560f)); + int16x4_t rot2_1 = vdup_n_s16(stbi__f2f(-0.390180644f)); + int16x4_t rot3_0 = vdup_n_s16(stbi__f2f( 0.298631336f)); + int16x4_t rot3_1 = vdup_n_s16(stbi__f2f( 2.053119869f)); + int16x4_t rot3_2 = vdup_n_s16(stbi__f2f( 3.072711026f)); + int16x4_t rot3_3 = vdup_n_s16(stbi__f2f( 1.501321110f)); + +#define dct_long_mul(out, inq, coeff) \ + int32x4_t out##_l = vmull_s16(vget_low_s16(inq), coeff); \ + int32x4_t out##_h = vmull_s16(vget_high_s16(inq), coeff) + +#define dct_long_mac(out, acc, inq, coeff) \ + int32x4_t out##_l = vmlal_s16(acc##_l, vget_low_s16(inq), coeff); \ + int32x4_t out##_h = vmlal_s16(acc##_h, vget_high_s16(inq), coeff) + +#define dct_widen(out, inq) \ + int32x4_t out##_l = vshll_n_s16(vget_low_s16(inq), 12); \ + int32x4_t out##_h = vshll_n_s16(vget_high_s16(inq), 12) + +// wide add +#define dct_wadd(out, a, b) \ + int32x4_t out##_l = vaddq_s32(a##_l, b##_l); \ + int32x4_t out##_h = vaddq_s32(a##_h, b##_h) + +// wide sub +#define dct_wsub(out, a, b) \ + int32x4_t out##_l = vsubq_s32(a##_l, b##_l); \ + int32x4_t out##_h = vsubq_s32(a##_h, b##_h) + +// butterfly a/b, then shift using "shiftop" by "s" and pack +#define dct_bfly32o(out0,out1, a,b,shiftop,s) \ + { \ + dct_wadd(sum, a, b); \ + dct_wsub(dif, a, b); \ + out0 = vcombine_s16(shiftop(sum_l, s), shiftop(sum_h, s)); \ + out1 = vcombine_s16(shiftop(dif_l, s), shiftop(dif_h, s)); \ + } + +#define dct_pass(shiftop, shift) \ + { \ + /* even part */ \ + int16x8_t sum26 = vaddq_s16(row2, row6); \ + dct_long_mul(p1e, sum26, rot0_0); \ + dct_long_mac(t2e, p1e, row6, rot0_1); \ + dct_long_mac(t3e, p1e, row2, rot0_2); \ + int16x8_t sum04 = vaddq_s16(row0, row4); \ + int16x8_t dif04 = vsubq_s16(row0, row4); \ + dct_widen(t0e, sum04); \ + dct_widen(t1e, dif04); \ + dct_wadd(x0, t0e, t3e); \ + dct_wsub(x3, t0e, t3e); \ + dct_wadd(x1, t1e, t2e); \ + dct_wsub(x2, t1e, t2e); \ + /* odd part */ \ + int16x8_t sum15 = vaddq_s16(row1, row5); \ + int16x8_t sum17 = vaddq_s16(row1, row7); \ + int16x8_t sum35 = vaddq_s16(row3, row5); \ + int16x8_t sum37 = vaddq_s16(row3, row7); \ + int16x8_t sumodd = vaddq_s16(sum17, sum35); \ + dct_long_mul(p5o, sumodd, rot1_0); \ + dct_long_mac(p1o, p5o, sum17, rot1_1); \ + dct_long_mac(p2o, p5o, sum35, rot1_2); \ + dct_long_mul(p3o, sum37, rot2_0); \ + dct_long_mul(p4o, sum15, rot2_1); \ + dct_wadd(sump13o, p1o, p3o); \ + dct_wadd(sump24o, p2o, p4o); \ + dct_wadd(sump23o, p2o, p3o); \ + dct_wadd(sump14o, p1o, p4o); \ + dct_long_mac(x4, sump13o, row7, rot3_0); \ + dct_long_mac(x5, sump24o, row5, rot3_1); \ + dct_long_mac(x6, sump23o, row3, rot3_2); \ + dct_long_mac(x7, sump14o, row1, rot3_3); \ + dct_bfly32o(row0,row7, x0,x7,shiftop,shift); \ + dct_bfly32o(row1,row6, x1,x6,shiftop,shift); \ + dct_bfly32o(row2,row5, x2,x5,shiftop,shift); \ + dct_bfly32o(row3,row4, x3,x4,shiftop,shift); \ + } + + // load + row0 = vld1q_s16(data + 0*8); + row1 = vld1q_s16(data + 1*8); + row2 = vld1q_s16(data + 2*8); + row3 = vld1q_s16(data + 3*8); + row4 = vld1q_s16(data + 4*8); + row5 = vld1q_s16(data + 5*8); + row6 = vld1q_s16(data + 6*8); + row7 = vld1q_s16(data + 7*8); + + // add DC bias + row0 = vaddq_s16(row0, vsetq_lane_s16(1024, vdupq_n_s16(0), 0)); + + // column pass + dct_pass(vrshrn_n_s32, 10); + + // 16bit 8x8 transpose + { +// these three map to a single VTRN.16, VTRN.32, and VSWP, respectively. +// whether compilers actually get this is another story, sadly. +#define dct_trn16(x, y) { int16x8x2_t t = vtrnq_s16(x, y); x = t.val[0]; y = t.val[1]; } +#define dct_trn32(x, y) { int32x4x2_t t = vtrnq_s32(vreinterpretq_s32_s16(x), vreinterpretq_s32_s16(y)); x = vreinterpretq_s16_s32(t.val[0]); y = vreinterpretq_s16_s32(t.val[1]); } +#define dct_trn64(x, y) { int16x8_t x0 = x; int16x8_t y0 = y; x = vcombine_s16(vget_low_s16(x0), vget_low_s16(y0)); y = vcombine_s16(vget_high_s16(x0), vget_high_s16(y0)); } + + // pass 1 + dct_trn16(row0, row1); // a0b0a2b2a4b4a6b6 + dct_trn16(row2, row3); + dct_trn16(row4, row5); + dct_trn16(row6, row7); + + // pass 2 + dct_trn32(row0, row2); // a0b0c0d0a4b4c4d4 + dct_trn32(row1, row3); + dct_trn32(row4, row6); + dct_trn32(row5, row7); + + // pass 3 + dct_trn64(row0, row4); // a0b0c0d0e0f0g0h0 + dct_trn64(row1, row5); + dct_trn64(row2, row6); + dct_trn64(row3, row7); + +#undef dct_trn16 +#undef dct_trn32 +#undef dct_trn64 + } + + // row pass + // vrshrn_n_s32 only supports shifts up to 16, we need + // 17. so do a non-rounding shift of 16 first then follow + // up with a rounding shift by 1. + dct_pass(vshrn_n_s32, 16); + + { + // pack and round + uint8x8_t p0 = vqrshrun_n_s16(row0, 1); + uint8x8_t p1 = vqrshrun_n_s16(row1, 1); + uint8x8_t p2 = vqrshrun_n_s16(row2, 1); + uint8x8_t p3 = vqrshrun_n_s16(row3, 1); + uint8x8_t p4 = vqrshrun_n_s16(row4, 1); + uint8x8_t p5 = vqrshrun_n_s16(row5, 1); + uint8x8_t p6 = vqrshrun_n_s16(row6, 1); + uint8x8_t p7 = vqrshrun_n_s16(row7, 1); + + // again, these can translate into one instruction, but often don't. +#define dct_trn8_8(x, y) { uint8x8x2_t t = vtrn_u8(x, y); x = t.val[0]; y = t.val[1]; } +#define dct_trn8_16(x, y) { uint16x4x2_t t = vtrn_u16(vreinterpret_u16_u8(x), vreinterpret_u16_u8(y)); x = vreinterpret_u8_u16(t.val[0]); y = vreinterpret_u8_u16(t.val[1]); } +#define dct_trn8_32(x, y) { uint32x2x2_t t = vtrn_u32(vreinterpret_u32_u8(x), vreinterpret_u32_u8(y)); x = vreinterpret_u8_u32(t.val[0]); y = vreinterpret_u8_u32(t.val[1]); } + + // sadly can't use interleaved stores here since we only write + // 8 bytes to each scan line! + + // 8x8 8-bit transpose pass 1 + dct_trn8_8(p0, p1); + dct_trn8_8(p2, p3); + dct_trn8_8(p4, p5); + dct_trn8_8(p6, p7); + + // pass 2 + dct_trn8_16(p0, p2); + dct_trn8_16(p1, p3); + dct_trn8_16(p4, p6); + dct_trn8_16(p5, p7); + + // pass 3 + dct_trn8_32(p0, p4); + dct_trn8_32(p1, p5); + dct_trn8_32(p2, p6); + dct_trn8_32(p3, p7); + + // store + vst1_u8(out, p0); out += out_stride; + vst1_u8(out, p1); out += out_stride; + vst1_u8(out, p2); out += out_stride; + vst1_u8(out, p3); out += out_stride; + vst1_u8(out, p4); out += out_stride; + vst1_u8(out, p5); out += out_stride; + vst1_u8(out, p6); out += out_stride; + vst1_u8(out, p7); + +#undef dct_trn8_8 +#undef dct_trn8_16 +#undef dct_trn8_32 + } + +#undef dct_long_mul +#undef dct_long_mac +#undef dct_widen +#undef dct_wadd +#undef dct_wsub +#undef dct_bfly32o +#undef dct_pass +} + +#endif // STBI_NEON + +#define STBI__MARKER_none 0xff +// if there's a pending marker from the entropy stream, return that +// otherwise, fetch from the stream and get a marker. if there's no +// marker, return 0xff, which is never a valid marker value +static stbi_uc stbi__get_marker(stbi__jpeg *j) +{ + stbi_uc x; + if (j->marker != STBI__MARKER_none) { x = j->marker; j->marker = STBI__MARKER_none; return x; } + x = stbi__get8(j->s); + if (x != 0xff) return STBI__MARKER_none; + while (x == 0xff) + x = stbi__get8(j->s); // consume repeated 0xff fill bytes + return x; +} + +// in each scan, we'll have scan_n components, and the order +// of the components is specified by order[] +#define STBI__RESTART(x) ((x) >= 0xd0 && (x) <= 0xd7) + +// after a restart interval, stbi__jpeg_reset the entropy decoder and +// the dc prediction +static void stbi__jpeg_reset(stbi__jpeg *j) +{ +#ifdef STBI_STRICT_JPEG + if (j->synthetic_bits > j->code_bits) j->truncated = 1; + j->synthetic_bits = 0; +#endif + j->code_bits = 0; + j->code_buffer = 0; + j->nomore = 0; + j->img_comp[0].dc_pred = j->img_comp[1].dc_pred = j->img_comp[2].dc_pred = j->img_comp[3].dc_pred = 0; + j->marker = STBI__MARKER_none; + j->todo = j->restart_interval ? j->restart_interval : 0x7fffffff; + j->eob_run = 0; + // no more than 1<<31 MCUs if no restart_interal? that's plenty safe, + // since we don't even allow 1<<30 pixels +} + +static int stbi__parse_entropy_coded_data(stbi__jpeg *z) +{ + stbi__jpeg_reset(z); + if (!z->progressive) { + if (z->scan_n == 1) { + int i,j; + STBI_SIMD_ALIGN(short, data[64]); + int n = z->order[0]; + // non-interleaved data, we just need to process one block at a time, + // in trivial scanline order + // number of blocks to do just depends on how many actual "pixels" this + // component has, independent of interleaved MCU blocking and such + int w = (z->img_comp[n].x+7) >> 3; + int h = (z->img_comp[n].y+7) >> 3; + for (j=0; j < h; ++j) { + for (i=0; i < w; ++i) { + int ha = z->img_comp[n].ha; + if (!stbi__jpeg_decode_block(z, data, z->huff_dc+z->img_comp[n].hd, z->huff_ac+ha, z->fast_ac[ha], n, z->dequant[z->img_comp[n].tq])) return 0; + z->idct_block_kernel(z->img_comp[n].data+z->img_comp[n].w2*j*8+i*8, z->img_comp[n].w2, data); + // every data block is an MCU, so countdown the restart interval + if (--z->todo <= 0) { + if (z->code_bits < 24) stbi__grow_buffer_unsafe(z); + // if it's NOT a restart, then just bail, so we get corrupt data + // rather than no data + if (!STBI__RESTART(z->marker)) return 1; + stbi__jpeg_reset(z); + } + } + } + return 1; + } else { // interleaved + int i,j,k,x,y; + STBI_SIMD_ALIGN(short, data[64]); + for (j=0; j < z->img_mcu_y; ++j) { + for (i=0; i < z->img_mcu_x; ++i) { + // scan an interleaved mcu... process scan_n components in order + for (k=0; k < z->scan_n; ++k) { + int n = z->order[k]; + // scan out an mcu's worth of this component; that's just determined + // by the basic H and V specified for the component + for (y=0; y < z->img_comp[n].v; ++y) { + for (x=0; x < z->img_comp[n].h; ++x) { + int x2 = (i*z->img_comp[n].h + x)*8; + int y2 = (j*z->img_comp[n].v + y)*8; + int ha = z->img_comp[n].ha; + if (!stbi__jpeg_decode_block(z, data, z->huff_dc+z->img_comp[n].hd, z->huff_ac+ha, z->fast_ac[ha], n, z->dequant[z->img_comp[n].tq])) return 0; + z->idct_block_kernel(z->img_comp[n].data+z->img_comp[n].w2*y2+x2, z->img_comp[n].w2, data); + } + } + } + // after all interleaved components, that's an interleaved MCU, + // so now count down the restart interval + if (--z->todo <= 0) { + if (z->code_bits < 24) stbi__grow_buffer_unsafe(z); + if (!STBI__RESTART(z->marker)) return 1; + stbi__jpeg_reset(z); + } + } + } + return 1; + } + } else { + if (z->scan_n == 1) { + int i,j; + int n = z->order[0]; + // non-interleaved data, we just need to process one block at a time, + // in trivial scanline order + // number of blocks to do just depends on how many actual "pixels" this + // component has, independent of interleaved MCU blocking and such + int w = (z->img_comp[n].x+7) >> 3; + int h = (z->img_comp[n].y+7) >> 3; + for (j=0; j < h; ++j) { + for (i=0; i < w; ++i) { + short *data = z->img_comp[n].coeff + 64 * (i + j * z->img_comp[n].coeff_w); + if (z->spec_start == 0) { + if (!stbi__jpeg_decode_block_prog_dc(z, data, &z->huff_dc[z->img_comp[n].hd], n)) + return 0; + } else { + int ha = z->img_comp[n].ha; + if (!stbi__jpeg_decode_block_prog_ac(z, data, &z->huff_ac[ha], z->fast_ac[ha])) + return 0; + } + // every data block is an MCU, so countdown the restart interval + if (--z->todo <= 0) { + if (z->code_bits < 24) stbi__grow_buffer_unsafe(z); + if (!STBI__RESTART(z->marker)) return 1; + stbi__jpeg_reset(z); + } + } + } + return 1; + } else { // interleaved + int i,j,k,x,y; + for (j=0; j < z->img_mcu_y; ++j) { + for (i=0; i < z->img_mcu_x; ++i) { + // scan an interleaved mcu... process scan_n components in order + for (k=0; k < z->scan_n; ++k) { + int n = z->order[k]; + // scan out an mcu's worth of this component; that's just determined + // by the basic H and V specified for the component + for (y=0; y < z->img_comp[n].v; ++y) { + for (x=0; x < z->img_comp[n].h; ++x) { + int x2 = (i*z->img_comp[n].h + x); + int y2 = (j*z->img_comp[n].v + y); + short *data = z->img_comp[n].coeff + 64 * (x2 + y2 * z->img_comp[n].coeff_w); + if (!stbi__jpeg_decode_block_prog_dc(z, data, &z->huff_dc[z->img_comp[n].hd], n)) + return 0; + } + } + } + // after all interleaved components, that's an interleaved MCU, + // so now count down the restart interval + if (--z->todo <= 0) { + if (z->code_bits < 24) stbi__grow_buffer_unsafe(z); + if (!STBI__RESTART(z->marker)) return 1; + stbi__jpeg_reset(z); + } + } + } + return 1; + } + } +} + +static void stbi__jpeg_dequantize(short *data, stbi__uint16 *dequant) +{ + int i; + for (i=0; i < 64; ++i) + data[i] *= dequant[i]; +} + +static void stbi__jpeg_finish(stbi__jpeg *z) +{ + if (z->progressive) { + // dequantize and idct the data + int i,j,n; + for (n=0; n < z->s->img_n; ++n) { + int w = (z->img_comp[n].x+7) >> 3; + int h = (z->img_comp[n].y+7) >> 3; + for (j=0; j < h; ++j) { + for (i=0; i < w; ++i) { + short *data = z->img_comp[n].coeff + 64 * (i + j * z->img_comp[n].coeff_w); + stbi__jpeg_dequantize(data, z->dequant[z->img_comp[n].tq]); + z->idct_block_kernel(z->img_comp[n].data+z->img_comp[n].w2*j*8+i*8, z->img_comp[n].w2, data); + } + } + } + } +} + +static int stbi__process_marker(stbi__jpeg *z, int m) +{ + int L; + switch (m) { + case STBI__MARKER_none: // no marker found + return stbi__err("expected marker","Corrupt JPEG"); + + case 0xDD: // DRI - specify restart interval + if (stbi__get16be(z->s) != 4) return stbi__err("bad DRI len","Corrupt JPEG"); + z->restart_interval = stbi__get16be(z->s); + return 1; + + case 0xDB: // DQT - define quantization table + L = stbi__get16be(z->s)-2; + while (L > 0) { + int q = stbi__get8(z->s); + int p = q >> 4, sixteen = (p != 0); + int t = q & 15,i; + if (p != 0 && p != 1) return stbi__err("bad DQT type","Corrupt JPEG"); + if (t > 3) return stbi__err("bad DQT table","Corrupt JPEG"); + + for (i=0; i < 64; ++i) + z->dequant[t][stbi__jpeg_dezigzag[i]] = (stbi__uint16)(sixteen ? stbi__get16be(z->s) : stbi__get8(z->s)); + L -= (sixteen ? 129 : 65); + } + return L==0; + + case 0xC4: // DHT - define huffman table + L = stbi__get16be(z->s)-2; + while (L > 0) { + stbi_uc *v; + int sizes[16],i,n=0; + int q = stbi__get8(z->s); + int tc = q >> 4; + int th = q & 15; + if (tc > 1 || th > 3) return stbi__err("bad DHT header","Corrupt JPEG"); + for (i=0; i < 16; ++i) { + sizes[i] = stbi__get8(z->s); + n += sizes[i]; + } + if(n > 256) return stbi__err("bad DHT header","Corrupt JPEG"); // Loop over i < n would write past end of values! + L -= 17; + if (tc == 0) { + if (!stbi__build_huffman(z->huff_dc+th, sizes)) return 0; + v = z->huff_dc[th].values; + } else { + if (!stbi__build_huffman(z->huff_ac+th, sizes)) return 0; + v = z->huff_ac[th].values; + } + for (i=0; i < n; ++i) + v[i] = stbi__get8(z->s); + if (tc != 0) + stbi__build_fast_ac(z->fast_ac[th], z->huff_ac + th); + L -= n; + } + return L==0; + } + + // check for comment block or APP blocks + if ((m >= 0xE0 && m <= 0xEF) || m == 0xFE) { + L = stbi__get16be(z->s); + if (L < 2) { + if (m == 0xFE) + return stbi__err("bad COM len","Corrupt JPEG"); + else + return stbi__err("bad APP len","Corrupt JPEG"); + } + L -= 2; + + if (m == 0xE0 && L >= 5) { // JFIF APP0 segment + static const unsigned char tag[5] = {'J','F','I','F','\0'}; + int ok = 1; + int i; + for (i=0; i < 5; ++i) + if (stbi__get8(z->s) != tag[i]) + ok = 0; + L -= 5; + if (ok) + z->jfif = 1; + } else if (m == 0xEE && L >= 12) { // Adobe APP14 segment + static const unsigned char tag[6] = {'A','d','o','b','e','\0'}; + int ok = 1; + int i; + for (i=0; i < 6; ++i) + if (stbi__get8(z->s) != tag[i]) + ok = 0; + L -= 6; + if (ok) { + stbi__get8(z->s); // version + stbi__get16be(z->s); // flags0 + stbi__get16be(z->s); // flags1 + z->app14_color_transform = stbi__get8(z->s); // color transform + L -= 6; + } + } + + stbi__skip(z->s, L); + return 1; + } + + return stbi__err("unknown marker","Corrupt JPEG"); +} + +// after we see SOS +static int stbi__process_scan_header(stbi__jpeg *z) +{ + int i; + int Ls = stbi__get16be(z->s); + z->scan_n = stbi__get8(z->s); + if (z->scan_n < 1 || z->scan_n > 4 || z->scan_n > (int) z->s->img_n) return stbi__err("bad SOS component count","Corrupt JPEG"); + if (Ls != 6+2*z->scan_n) return stbi__err("bad SOS len","Corrupt JPEG"); + for (i=0; i < z->scan_n; ++i) { + int id = stbi__get8(z->s), which; + int q = stbi__get8(z->s); + for (which = 0; which < z->s->img_n; ++which) + if (z->img_comp[which].id == id) + break; + if (which == z->s->img_n) return 0; // no match + z->img_comp[which].hd = q >> 4; if (z->img_comp[which].hd > 3) return stbi__err("bad DC huff","Corrupt JPEG"); + z->img_comp[which].ha = q & 15; if (z->img_comp[which].ha > 3) return stbi__err("bad AC huff","Corrupt JPEG"); + z->order[i] = which; + } + + { + int aa; + z->spec_start = stbi__get8(z->s); + z->spec_end = stbi__get8(z->s); // should be 63, but might be 0 + aa = stbi__get8(z->s); + z->succ_high = (aa >> 4); + z->succ_low = (aa & 15); + if (z->progressive) { + if (z->spec_start > 63 || z->spec_end > 63 || z->spec_start > z->spec_end || z->succ_high > 13 || z->succ_low > 13) + return stbi__err("bad SOS", "Corrupt JPEG"); + } else { + if (z->spec_start != 0) return stbi__err("bad SOS","Corrupt JPEG"); + if (z->succ_high != 0 || z->succ_low != 0) return stbi__err("bad SOS","Corrupt JPEG"); + z->spec_end = 63; + } + } + + return 1; +} + +static int stbi__free_jpeg_components(stbi__jpeg *z, int ncomp, int why) +{ + int i; + for (i=0; i < ncomp; ++i) { + if (z->img_comp[i].raw_data) { + STBI_FREE(z->img_comp[i].raw_data); + z->img_comp[i].raw_data = NULL; + z->img_comp[i].data = NULL; + } + if (z->img_comp[i].raw_coeff) { + STBI_FREE(z->img_comp[i].raw_coeff); + z->img_comp[i].raw_coeff = 0; + z->img_comp[i].coeff = 0; + } + if (z->img_comp[i].linebuf) { + STBI_FREE(z->img_comp[i].linebuf); + z->img_comp[i].linebuf = NULL; + } + } + return why; +} + +static int stbi__process_frame_header(stbi__jpeg *z, int scan) +{ + stbi__context *s = z->s; + int Lf,p,i,q, h_max=1,v_max=1,c; + Lf = stbi__get16be(s); if (Lf < 11) return stbi__err("bad SOF len","Corrupt JPEG"); // JPEG + p = stbi__get8(s); if (p != 8) return stbi__err("only 8-bit","JPEG format not supported: 8-bit only"); // JPEG baseline + s->img_y = stbi__get16be(s); if (s->img_y == 0) return stbi__err("no header height", "JPEG format not supported: delayed height"); // Legal, but we don't handle it--but neither does IJG + s->img_x = stbi__get16be(s); if (s->img_x == 0) return stbi__err("0 width","Corrupt JPEG"); // JPEG requires + if (s->img_y > STBI_MAX_DIMENSIONS) return stbi__err("too large","Very large image (corrupt?)"); + if (s->img_x > STBI_MAX_DIMENSIONS) return stbi__err("too large","Very large image (corrupt?)"); + c = stbi__get8(s); + if (c != 3 && c != 1 && c != 4) return stbi__err("bad component count","Corrupt JPEG"); + s->img_n = c; + for (i=0; i < c; ++i) { + z->img_comp[i].data = NULL; + z->img_comp[i].linebuf = NULL; + } + + if (Lf != 8+3*s->img_n) return stbi__err("bad SOF len","Corrupt JPEG"); + + z->rgb = 0; + for (i=0; i < s->img_n; ++i) { + static const unsigned char rgb[3] = { 'R', 'G', 'B' }; + z->img_comp[i].id = stbi__get8(s); + if (s->img_n == 3 && z->img_comp[i].id == rgb[i]) + ++z->rgb; + q = stbi__get8(s); + z->img_comp[i].h = (q >> 4); if (!z->img_comp[i].h || z->img_comp[i].h > 4) return stbi__err("bad H","Corrupt JPEG"); + z->img_comp[i].v = q & 15; if (!z->img_comp[i].v || z->img_comp[i].v > 4) return stbi__err("bad V","Corrupt JPEG"); + z->img_comp[i].tq = stbi__get8(s); if (z->img_comp[i].tq > 3) return stbi__err("bad TQ","Corrupt JPEG"); + } + + if (scan != STBI__SCAN_load) return 1; + + if (!stbi__mad3sizes_valid(s->img_x, s->img_y, s->img_n, 0)) return stbi__err("too large", "Image too large to decode"); + + for (i=0; i < s->img_n; ++i) { + if (z->img_comp[i].h > h_max) h_max = z->img_comp[i].h; + if (z->img_comp[i].v > v_max) v_max = z->img_comp[i].v; + } + + // check that plane subsampling factors are integer ratios; our resamplers can't deal with fractional ratios + // and I've never seen a non-corrupted JPEG file actually use them + for (i=0; i < s->img_n; ++i) { + if (h_max % z->img_comp[i].h != 0) return stbi__err("bad H","Corrupt JPEG"); + if (v_max % z->img_comp[i].v != 0) return stbi__err("bad V","Corrupt JPEG"); + } + + // compute interleaved mcu info + z->img_h_max = h_max; + z->img_v_max = v_max; + z->img_mcu_w = h_max * 8; + z->img_mcu_h = v_max * 8; + // these sizes can't be more than 17 bits + z->img_mcu_x = (s->img_x + z->img_mcu_w-1) / z->img_mcu_w; + z->img_mcu_y = (s->img_y + z->img_mcu_h-1) / z->img_mcu_h; + + for (i=0; i < s->img_n; ++i) { + // number of effective pixels (e.g. for non-interleaved MCU) + z->img_comp[i].x = (s->img_x * z->img_comp[i].h + h_max-1) / h_max; + z->img_comp[i].y = (s->img_y * z->img_comp[i].v + v_max-1) / v_max; + // to simplify generation, we'll allocate enough memory to decode + // the bogus oversized data from using interleaved MCUs and their + // big blocks (e.g. a 16x16 iMCU on an image of width 33); we won't + // discard the extra data until colorspace conversion + // + // img_mcu_x, img_mcu_y: <=17 bits; comp[i].h and .v are <=4 (checked earlier) + // so these muls can't overflow with 32-bit ints (which we require) + z->img_comp[i].w2 = z->img_mcu_x * z->img_comp[i].h * 8; + z->img_comp[i].h2 = z->img_mcu_y * z->img_comp[i].v * 8; + z->img_comp[i].coeff = 0; + z->img_comp[i].raw_coeff = 0; + z->img_comp[i].linebuf = NULL; + z->img_comp[i].raw_data = stbi__malloc_mad2(z->img_comp[i].w2, z->img_comp[i].h2, 15); + if (z->img_comp[i].raw_data == NULL) + return stbi__free_jpeg_components(z, i+1, stbi__err("outofmem", "Out of memory")); + // align blocks for idct using mmx/sse + z->img_comp[i].data = (stbi_uc*) (((size_t) z->img_comp[i].raw_data + 15) & ~15); + if (z->progressive) { + // w2, h2 are multiples of 8 (see above) + z->img_comp[i].coeff_w = z->img_comp[i].w2 / 8; + z->img_comp[i].coeff_h = z->img_comp[i].h2 / 8; + z->img_comp[i].raw_coeff = stbi__malloc_mad3(z->img_comp[i].w2, z->img_comp[i].h2, sizeof(short), 15); + if (z->img_comp[i].raw_coeff == NULL) + return stbi__free_jpeg_components(z, i+1, stbi__err("outofmem", "Out of memory")); + z->img_comp[i].coeff = (short*) (((size_t) z->img_comp[i].raw_coeff + 15) & ~15); + } + } + + return 1; +} + +// use comparisons since in some cases we handle more than one case (e.g. SOF) +#define stbi__DNL(x) ((x) == 0xdc) +#define stbi__SOI(x) ((x) == 0xd8) +#define stbi__EOI(x) ((x) == 0xd9) +#define stbi__SOF(x) ((x) == 0xc0 || (x) == 0xc1 || (x) == 0xc2) +#define stbi__SOS(x) ((x) == 0xda) + +#define stbi__SOF_progressive(x) ((x) == 0xc2) + +static int stbi__decode_jpeg_header(stbi__jpeg *z, int scan) +{ + int m; + z->jfif = 0; + z->app14_color_transform = -1; // valid values are 0,1,2 + z->marker = STBI__MARKER_none; // initialize cached marker to empty + m = stbi__get_marker(z); + if (!stbi__SOI(m)) return stbi__err("no SOI","Corrupt JPEG"); + if (scan == STBI__SCAN_type) return 1; + m = stbi__get_marker(z); + while (!stbi__SOF(m)) { + if (!stbi__process_marker(z,m)) return 0; + m = stbi__get_marker(z); + while (m == STBI__MARKER_none) { + // some files have extra padding after their blocks, so ok, we'll scan + if (stbi__at_eof(z->s)) return stbi__err("no SOF", "Corrupt JPEG"); + m = stbi__get_marker(z); + } + } + z->progressive = stbi__SOF_progressive(m); + if (!stbi__process_frame_header(z, scan)) return 0; + return 1; +} + +static stbi_uc stbi__skip_jpeg_junk_at_end(stbi__jpeg *j) +{ + // some JPEGs have junk at end, skip over it but if we find what looks + // like a valid marker, resume there + while (!stbi__at_eof(j->s)) { + stbi_uc x = stbi__get8(j->s); + while (x == 0xff) { // might be a marker + if (stbi__at_eof(j->s)) return STBI__MARKER_none; + x = stbi__get8(j->s); + if (x != 0x00 && x != 0xff) { + // not a stuffed zero or lead-in to another marker, looks + // like an actual marker, return it + return x; + } + // stuffed zero has x=0 now which ends the loop, meaning we go + // back to regular scan loop. + // repeated 0xff keeps trying to read the next byte of the marker. + } + } + return STBI__MARKER_none; +} + +// decode image to YCbCr format +static int stbi__decode_jpeg_image(stbi__jpeg *j) +{ + int m; + for (m = 0; m < 4; m++) { + j->img_comp[m].raw_data = NULL; + j->img_comp[m].raw_coeff = NULL; + } + j->restart_interval = 0; + if (!stbi__decode_jpeg_header(j, STBI__SCAN_load)) return 0; + m = stbi__get_marker(j); + while (!stbi__EOI(m)) { + if (stbi__SOS(m)) { + if (!stbi__process_scan_header(j)) return 0; + if (!stbi__parse_entropy_coded_data(j)) return 0; + if (j->marker == STBI__MARKER_none ) { + j->marker = stbi__skip_jpeg_junk_at_end(j); + // if we reach eof without hitting a marker, stbi__get_marker() below will fail and we'll eventually return 0 + } + m = stbi__get_marker(j); + if (STBI__RESTART(m)) + m = stbi__get_marker(j); + } else if (stbi__DNL(m)) { + int Ld = stbi__get16be(j->s); + stbi__uint32 NL = stbi__get16be(j->s); + if (Ld != 4) return stbi__err("bad DNL len", "Corrupt JPEG"); + if (NL != j->s->img_y) return stbi__err("bad DNL height", "Corrupt JPEG"); + m = stbi__get_marker(j); + } else { + if (!stbi__process_marker(j, m)) return 1; + m = stbi__get_marker(j); + } + } +#ifdef STBI_STRICT_JPEG + if (j->synthetic_bits > j->code_bits) j->truncated = 1; + if (j->truncated) return stbi__err("truncated entropy data", "Corrupt JPEG"); +#endif + if (j->progressive) + stbi__jpeg_finish(j); + return 1; +} + +// static jfif-centered resampling (across block boundaries) + +typedef stbi_uc *(*resample_row_func)(stbi_uc *out, stbi_uc *in0, stbi_uc *in1, + int w, int hs); + +#define stbi__div4(x) ((stbi_uc) ((x) >> 2)) + +static stbi_uc *resample_row_1(stbi_uc *out, stbi_uc *in_near, stbi_uc *in_far, int w, int hs) +{ + STBI_NOTUSED(out); + STBI_NOTUSED(in_far); + STBI_NOTUSED(w); + STBI_NOTUSED(hs); + return in_near; +} + +static stbi_uc* stbi__resample_row_v_2(stbi_uc *out, stbi_uc *in_near, stbi_uc *in_far, int w, int hs) +{ + // need to generate two samples vertically for every one in input + int i; + STBI_NOTUSED(hs); + for (i=0; i < w; ++i) + out[i] = stbi__div4(3*in_near[i] + in_far[i] + 2); + return out; +} + +static stbi_uc* stbi__resample_row_h_2(stbi_uc *out, stbi_uc *in_near, stbi_uc *in_far, int w, int hs) +{ + // need to generate two samples horizontally for every one in input + int i; + stbi_uc *input = in_near; + + if (w == 1) { + // if only one sample, can't do any interpolation + out[0] = out[1] = input[0]; + return out; + } + + out[0] = input[0]; + out[1] = stbi__div4(input[0]*3 + input[1] + 2); + for (i=1; i < w-1; ++i) { + int n = 3*input[i]+2; + out[i*2+0] = stbi__div4(n+input[i-1]); + out[i*2+1] = stbi__div4(n+input[i+1]); + } + out[i*2+0] = stbi__div4(input[w-2]*3 + input[w-1] + 2); + out[i*2+1] = input[w-1]; + + STBI_NOTUSED(in_far); + STBI_NOTUSED(hs); + + return out; +} + +#define stbi__div16(x) ((stbi_uc) ((x) >> 4)) + +static stbi_uc *stbi__resample_row_hv_2(stbi_uc *out, stbi_uc *in_near, stbi_uc *in_far, int w, int hs) +{ + // need to generate 2x2 samples for every one in input + int i,t0,t1; + if (w == 1) { + out[0] = out[1] = stbi__div4(3*in_near[0] + in_far[0] + 2); + return out; + } + + t1 = 3*in_near[0] + in_far[0]; + out[0] = stbi__div4(t1+2); + for (i=1; i < w; ++i) { + t0 = t1; + t1 = 3*in_near[i]+in_far[i]; + out[i*2-1] = stbi__div16(3*t0 + t1 + 8); + out[i*2 ] = stbi__div16(3*t1 + t0 + 8); + } + out[w*2-1] = stbi__div4(t1+2); + + STBI_NOTUSED(hs); + + return out; +} + +#if defined(STBI_SSE2) || defined(STBI_NEON) +static stbi_uc *stbi__resample_row_hv_2_simd(stbi_uc *out, stbi_uc *in_near, stbi_uc *in_far, int w, int hs) +{ + // need to generate 2x2 samples for every one in input + int i=0,t0,t1; + + if (w == 1) { + out[0] = out[1] = stbi__div4(3*in_near[0] + in_far[0] + 2); + return out; + } + + t1 = 3*in_near[0] + in_far[0]; + // process groups of 8 pixels for as long as we can. + // note we can't handle the last pixel in a row in this loop + // because we need to handle the filter boundary conditions. + for (; i < ((w-1) & ~7); i += 8) { +#if defined(STBI_SSE2) + // load and perform the vertical filtering pass + // this uses 3*x + y = 4*x + (y - x) + __m128i zero = _mm_setzero_si128(); + __m128i farb = _mm_loadl_epi64((__m128i *) (in_far + i)); + __m128i nearb = _mm_loadl_epi64((__m128i *) (in_near + i)); + __m128i farw = _mm_unpacklo_epi8(farb, zero); + __m128i nearw = _mm_unpacklo_epi8(nearb, zero); + __m128i diff = _mm_sub_epi16(farw, nearw); + __m128i nears = _mm_slli_epi16(nearw, 2); + __m128i curr = _mm_add_epi16(nears, diff); // current row + + // horizontal filter works the same based on shifted vers of current + // row. "prev" is current row shifted right by 1 pixel; we need to + // insert the previous pixel value (from t1). + // "next" is current row shifted left by 1 pixel, with first pixel + // of next block of 8 pixels added in. + __m128i prv0 = _mm_slli_si128(curr, 2); + __m128i nxt0 = _mm_srli_si128(curr, 2); + __m128i prev = _mm_insert_epi16(prv0, t1, 0); + __m128i next = _mm_insert_epi16(nxt0, 3*in_near[i+8] + in_far[i+8], 7); + + // horizontal filter, polyphase implementation since it's convenient: + // even pixels = 3*cur + prev = cur*4 + (prev - cur) + // odd pixels = 3*cur + next = cur*4 + (next - cur) + // note the shared term. + __m128i bias = _mm_set1_epi16(8); + __m128i curs = _mm_slli_epi16(curr, 2); + __m128i prvd = _mm_sub_epi16(prev, curr); + __m128i nxtd = _mm_sub_epi16(next, curr); + __m128i curb = _mm_add_epi16(curs, bias); + __m128i even = _mm_add_epi16(prvd, curb); + __m128i odd = _mm_add_epi16(nxtd, curb); + + // interleave even and odd pixels, then undo scaling. + __m128i int0 = _mm_unpacklo_epi16(even, odd); + __m128i int1 = _mm_unpackhi_epi16(even, odd); + __m128i de0 = _mm_srli_epi16(int0, 4); + __m128i de1 = _mm_srli_epi16(int1, 4); + + // pack and write output + __m128i outv = _mm_packus_epi16(de0, de1); + _mm_storeu_si128((__m128i *) (out + i*2), outv); +#elif defined(STBI_NEON) + // load and perform the vertical filtering pass + // this uses 3*x + y = 4*x + (y - x) + uint8x8_t farb = vld1_u8(in_far + i); + uint8x8_t nearb = vld1_u8(in_near + i); + int16x8_t diff = vreinterpretq_s16_u16(vsubl_u8(farb, nearb)); + int16x8_t nears = vreinterpretq_s16_u16(vshll_n_u8(nearb, 2)); + int16x8_t curr = vaddq_s16(nears, diff); // current row + + // horizontal filter works the same based on shifted vers of current + // row. "prev" is current row shifted right by 1 pixel; we need to + // insert the previous pixel value (from t1). + // "next" is current row shifted left by 1 pixel, with first pixel + // of next block of 8 pixels added in. + int16x8_t prv0 = vextq_s16(curr, curr, 7); + int16x8_t nxt0 = vextq_s16(curr, curr, 1); + int16x8_t prev = vsetq_lane_s16(t1, prv0, 0); + int16x8_t next = vsetq_lane_s16(3*in_near[i+8] + in_far[i+8], nxt0, 7); + + // horizontal filter, polyphase implementation since it's convenient: + // even pixels = 3*cur + prev = cur*4 + (prev - cur) + // odd pixels = 3*cur + next = cur*4 + (next - cur) + // note the shared term. + int16x8_t curs = vshlq_n_s16(curr, 2); + int16x8_t prvd = vsubq_s16(prev, curr); + int16x8_t nxtd = vsubq_s16(next, curr); + int16x8_t even = vaddq_s16(curs, prvd); + int16x8_t odd = vaddq_s16(curs, nxtd); + + // undo scaling and round, then store with even/odd phases interleaved + uint8x8x2_t o; + o.val[0] = vqrshrun_n_s16(even, 4); + o.val[1] = vqrshrun_n_s16(odd, 4); + vst2_u8(out + i*2, o); +#endif + + // "previous" value for next iter + t1 = 3*in_near[i+7] + in_far[i+7]; + } + + t0 = t1; + t1 = 3*in_near[i] + in_far[i]; + out[i*2] = stbi__div16(3*t1 + t0 + 8); + + for (++i; i < w; ++i) { + t0 = t1; + t1 = 3*in_near[i]+in_far[i]; + out[i*2-1] = stbi__div16(3*t0 + t1 + 8); + out[i*2 ] = stbi__div16(3*t1 + t0 + 8); + } + out[w*2-1] = stbi__div4(t1+2); + + STBI_NOTUSED(hs); + + return out; +} +#endif + +static stbi_uc *stbi__resample_row_generic(stbi_uc *out, stbi_uc *in_near, stbi_uc *in_far, int w, int hs) +{ + // resample with nearest-neighbor + int i,j; + STBI_NOTUSED(in_far); + for (i=0; i < w; ++i) + for (j=0; j < hs; ++j) + out[i*hs+j] = in_near[i]; + return out; +} + +// this is a reduced-precision calculation of YCbCr-to-RGB introduced +// to make sure the code produces the same results in both SIMD and scalar +#define stbi__float2fixed(x) (((int) ((x) * 4096.0f + 0.5f)) << 8) +static void stbi__YCbCr_to_RGB_row(stbi_uc *out, const stbi_uc *y, const stbi_uc *pcb, const stbi_uc *pcr, int count, int step) +{ + int i; + for (i=0; i < count; ++i) { + int y_fixed = (y[i] << 20) + (1<<19); // rounding + int r,g,b; + int cr = pcr[i] - 128; + int cb = pcb[i] - 128; + r = y_fixed + cr* stbi__float2fixed(1.40200f); + g = y_fixed + (cr*-stbi__float2fixed(0.71414f)) + ((cb*-stbi__float2fixed(0.34414f)) & 0xffff0000); + b = y_fixed + cb* stbi__float2fixed(1.77200f); + r >>= 20; + g >>= 20; + b >>= 20; + if ((unsigned) r > 255) { if (r < 0) r = 0; else r = 255; } + if ((unsigned) g > 255) { if (g < 0) g = 0; else g = 255; } + if ((unsigned) b > 255) { if (b < 0) b = 0; else b = 255; } + out[0] = (stbi_uc)r; + out[1] = (stbi_uc)g; + out[2] = (stbi_uc)b; + out[3] = 255; + out += step; + } +} + +#if defined(STBI_SSE2) || defined(STBI_NEON) +static void stbi__YCbCr_to_RGB_simd(stbi_uc *out, stbi_uc const *y, stbi_uc const *pcb, stbi_uc const *pcr, int count, int step) +{ + int i = 0; + +#ifdef STBI_SSE2 + // step == 3 is pretty ugly on the final interleave, and i'm not convinced + // it's useful in practice (you wouldn't use it for textures, for example). + // so just accelerate step == 4 case. + if (step == 4) { + // this is a fairly straightforward implementation and not super-optimized. + __m128i signflip = _mm_set1_epi8(-0x80); + __m128i cr_const0 = _mm_set1_epi16( (short) ( 1.40200f*4096.0f+0.5f)); + __m128i cr_const1 = _mm_set1_epi16( - (short) ( 0.71414f*4096.0f+0.5f)); + __m128i cb_const0 = _mm_set1_epi16( - (short) ( 0.34414f*4096.0f+0.5f)); + __m128i cb_const1 = _mm_set1_epi16( (short) ( 1.77200f*4096.0f+0.5f)); + __m128i y_bias = _mm_set1_epi8((char) (unsigned char) 128); + __m128i xw = _mm_set1_epi16(255); // alpha channel + + for (; i+7 < count; i += 8) { + // load + __m128i y_bytes = _mm_loadl_epi64((__m128i *) (y+i)); + __m128i cr_bytes = _mm_loadl_epi64((__m128i *) (pcr+i)); + __m128i cb_bytes = _mm_loadl_epi64((__m128i *) (pcb+i)); + __m128i cr_biased = _mm_xor_si128(cr_bytes, signflip); // -128 + __m128i cb_biased = _mm_xor_si128(cb_bytes, signflip); // -128 + + // unpack to short (and left-shift cr, cb by 8) + __m128i yw = _mm_unpacklo_epi8(y_bias, y_bytes); + __m128i crw = _mm_unpacklo_epi8(_mm_setzero_si128(), cr_biased); + __m128i cbw = _mm_unpacklo_epi8(_mm_setzero_si128(), cb_biased); + + // color transform + __m128i yws = _mm_srli_epi16(yw, 4); + __m128i cr0 = _mm_mulhi_epi16(cr_const0, crw); + __m128i cb0 = _mm_mulhi_epi16(cb_const0, cbw); + __m128i cb1 = _mm_mulhi_epi16(cbw, cb_const1); + __m128i cr1 = _mm_mulhi_epi16(crw, cr_const1); + __m128i rws = _mm_add_epi16(cr0, yws); + __m128i gwt = _mm_add_epi16(cb0, yws); + __m128i bws = _mm_add_epi16(yws, cb1); + __m128i gws = _mm_add_epi16(gwt, cr1); + + // descale + __m128i rw = _mm_srai_epi16(rws, 4); + __m128i bw = _mm_srai_epi16(bws, 4); + __m128i gw = _mm_srai_epi16(gws, 4); + + // back to byte, set up for transpose + __m128i brb = _mm_packus_epi16(rw, bw); + __m128i gxb = _mm_packus_epi16(gw, xw); + + // transpose to interleave channels + __m128i t0 = _mm_unpacklo_epi8(brb, gxb); + __m128i t1 = _mm_unpackhi_epi8(brb, gxb); + __m128i o0 = _mm_unpacklo_epi16(t0, t1); + __m128i o1 = _mm_unpackhi_epi16(t0, t1); + + // store + _mm_storeu_si128((__m128i *) (out + 0), o0); + _mm_storeu_si128((__m128i *) (out + 16), o1); + out += 32; + } + } +#endif + +#ifdef STBI_NEON + // in this version, step=3 support would be easy to add. but is there demand? + if (step == 4) { + // this is a fairly straightforward implementation and not super-optimized. + uint8x8_t signflip = vdup_n_u8(0x80); + int16x8_t cr_const0 = vdupq_n_s16( (short) ( 1.40200f*4096.0f+0.5f)); + int16x8_t cr_const1 = vdupq_n_s16( - (short) ( 0.71414f*4096.0f+0.5f)); + int16x8_t cb_const0 = vdupq_n_s16( - (short) ( 0.34414f*4096.0f+0.5f)); + int16x8_t cb_const1 = vdupq_n_s16( (short) ( 1.77200f*4096.0f+0.5f)); + + for (; i+7 < count; i += 8) { + // load + uint8x8_t y_bytes = vld1_u8(y + i); + uint8x8_t cr_bytes = vld1_u8(pcr + i); + uint8x8_t cb_bytes = vld1_u8(pcb + i); + int8x8_t cr_biased = vreinterpret_s8_u8(vsub_u8(cr_bytes, signflip)); + int8x8_t cb_biased = vreinterpret_s8_u8(vsub_u8(cb_bytes, signflip)); + + // expand to s16 + int16x8_t yws = vreinterpretq_s16_u16(vshll_n_u8(y_bytes, 4)); + int16x8_t crw = vshll_n_s8(cr_biased, 7); + int16x8_t cbw = vshll_n_s8(cb_biased, 7); + + // color transform + int16x8_t cr0 = vqdmulhq_s16(crw, cr_const0); + int16x8_t cb0 = vqdmulhq_s16(cbw, cb_const0); + int16x8_t cr1 = vqdmulhq_s16(crw, cr_const1); + int16x8_t cb1 = vqdmulhq_s16(cbw, cb_const1); + int16x8_t rws = vaddq_s16(yws, cr0); + int16x8_t gws = vaddq_s16(vaddq_s16(yws, cb0), cr1); + int16x8_t bws = vaddq_s16(yws, cb1); + + // undo scaling, round, convert to byte + uint8x8x4_t o; + o.val[0] = vqrshrun_n_s16(rws, 4); + o.val[1] = vqrshrun_n_s16(gws, 4); + o.val[2] = vqrshrun_n_s16(bws, 4); + o.val[3] = vdup_n_u8(255); + + // store, interleaving r/g/b/a + vst4_u8(out, o); + out += 8*4; + } + } +#endif + + for (; i < count; ++i) { + int y_fixed = (y[i] << 20) + (1<<19); // rounding + int r,g,b; + int cr = pcr[i] - 128; + int cb = pcb[i] - 128; + r = y_fixed + cr* stbi__float2fixed(1.40200f); + g = y_fixed + cr*-stbi__float2fixed(0.71414f) + ((cb*-stbi__float2fixed(0.34414f)) & 0xffff0000); + b = y_fixed + cb* stbi__float2fixed(1.77200f); + r >>= 20; + g >>= 20; + b >>= 20; + if ((unsigned) r > 255) { if (r < 0) r = 0; else r = 255; } + if ((unsigned) g > 255) { if (g < 0) g = 0; else g = 255; } + if ((unsigned) b > 255) { if (b < 0) b = 0; else b = 255; } + out[0] = (stbi_uc)r; + out[1] = (stbi_uc)g; + out[2] = (stbi_uc)b; + out[3] = 255; + out += step; + } +} +#endif + +// set up the kernels +static void stbi__setup_jpeg(stbi__jpeg *j) +{ + j->idct_block_kernel = stbi__idct_block; + j->YCbCr_to_RGB_kernel = stbi__YCbCr_to_RGB_row; + j->resample_row_hv_2_kernel = stbi__resample_row_hv_2; + +#ifdef STBI_SSE2 + if (stbi__sse2_available()) { + j->idct_block_kernel = stbi__idct_simd; + j->YCbCr_to_RGB_kernel = stbi__YCbCr_to_RGB_simd; + j->resample_row_hv_2_kernel = stbi__resample_row_hv_2_simd; + } +#endif + +#ifdef STBI_NEON + j->idct_block_kernel = stbi__idct_simd; + j->YCbCr_to_RGB_kernel = stbi__YCbCr_to_RGB_simd; + j->resample_row_hv_2_kernel = stbi__resample_row_hv_2_simd; +#endif +} + +// clean up the temporary component buffers +static void stbi__cleanup_jpeg(stbi__jpeg *j) +{ + stbi__free_jpeg_components(j, j->s->img_n, 0); +} + +typedef struct +{ + resample_row_func resample; + stbi_uc *line0,*line1; + int hs,vs; // expansion factor in each axis + int w_lores; // horizontal pixels pre-expansion + int ystep; // how far through vertical expansion we are + int ypos; // which pre-expansion row we're on +} stbi__resample; + +// fast 0..255 * 0..255 => 0..255 rounded multiplication +static stbi_uc stbi__blinn_8x8(stbi_uc x, stbi_uc y) +{ + unsigned int t = x*y + 128; + return (stbi_uc) ((t + (t >>8)) >> 8); +} + +static stbi_uc *load_jpeg_image(stbi__jpeg *z, int *out_x, int *out_y, int *comp, int req_comp) +{ + int n, decode_n, is_rgb; + z->s->img_n = 0; // make stbi__cleanup_jpeg safe + + // validate req_comp + if (req_comp < 0 || req_comp > 4) return stbi__errpuc("bad req_comp", "Internal error"); + + // load a jpeg image from whichever source, but leave in YCbCr format + if (!stbi__decode_jpeg_image(z)) { stbi__cleanup_jpeg(z); return NULL; } + + // determine actual number of components to generate + n = req_comp ? req_comp : z->s->img_n >= 3 ? 3 : 1; + + is_rgb = z->s->img_n == 3 && (z->rgb == 3 || (z->app14_color_transform == 0 && !z->jfif)); + + if (z->s->img_n == 3 && n < 3 && !is_rgb) + decode_n = 1; + else + decode_n = z->s->img_n; + + // nothing to do if no components requested; check this now to avoid + // accessing uninitialized coutput[0] later + if (decode_n <= 0) { stbi__cleanup_jpeg(z); return NULL; } + + // resample and color-convert + { + int k; + unsigned int i,j; + stbi_uc *output; + stbi_uc *coutput[4] = { NULL, NULL, NULL, NULL }; + + stbi__resample res_comp[4]; + + for (k=0; k < decode_n; ++k) { + stbi__resample *r = &res_comp[k]; + + // allocate line buffer big enough for upsampling off the edges + // with upsample factor of 4 + z->img_comp[k].linebuf = (stbi_uc *) stbi__malloc(z->s->img_x + 3); + if (!z->img_comp[k].linebuf) { stbi__cleanup_jpeg(z); return stbi__errpuc("outofmem", "Out of memory"); } + + r->hs = z->img_h_max / z->img_comp[k].h; + r->vs = z->img_v_max / z->img_comp[k].v; + r->ystep = r->vs >> 1; + r->w_lores = (z->s->img_x + r->hs-1) / r->hs; + r->ypos = 0; + r->line0 = r->line1 = z->img_comp[k].data; + + if (r->hs == 1 && r->vs == 1) r->resample = resample_row_1; + else if (r->hs == 1 && r->vs == 2) r->resample = stbi__resample_row_v_2; + else if (r->hs == 2 && r->vs == 1) r->resample = stbi__resample_row_h_2; + else if (r->hs == 2 && r->vs == 2) r->resample = z->resample_row_hv_2_kernel; + else r->resample = stbi__resample_row_generic; + } + + // can't error after this so, this is safe + output = (stbi_uc *) stbi__malloc_mad3(n, z->s->img_x, z->s->img_y, 1); + if (!output) { stbi__cleanup_jpeg(z); return stbi__errpuc("outofmem", "Out of memory"); } + + // now go ahead and resample + for (j=0; j < z->s->img_y; ++j) { + stbi_uc *out = output + n * z->s->img_x * j; + for (k=0; k < decode_n; ++k) { + stbi__resample *r = &res_comp[k]; + int y_bot = r->ystep >= (r->vs >> 1); + coutput[k] = r->resample(z->img_comp[k].linebuf, + y_bot ? r->line1 : r->line0, + y_bot ? r->line0 : r->line1, + r->w_lores, r->hs); + if (++r->ystep >= r->vs) { + r->ystep = 0; + r->line0 = r->line1; + if (++r->ypos < z->img_comp[k].y) + r->line1 += z->img_comp[k].w2; + } + } + if (n >= 3) { + stbi_uc *y = coutput[0]; + if (z->s->img_n == 3) { + if (is_rgb) { + for (i=0; i < z->s->img_x; ++i) { + out[0] = y[i]; + out[1] = coutput[1][i]; + out[2] = coutput[2][i]; + out[3] = 255; + out += n; + } + } else { + z->YCbCr_to_RGB_kernel(out, y, coutput[1], coutput[2], z->s->img_x, n); + } + } else if (z->s->img_n == 4) { + if (z->app14_color_transform == 0) { // CMYK + for (i=0; i < z->s->img_x; ++i) { + stbi_uc m = coutput[3][i]; + out[0] = stbi__blinn_8x8(coutput[0][i], m); + out[1] = stbi__blinn_8x8(coutput[1][i], m); + out[2] = stbi__blinn_8x8(coutput[2][i], m); + out[3] = 255; + out += n; + } + } else if (z->app14_color_transform == 2) { // YCCK + z->YCbCr_to_RGB_kernel(out, y, coutput[1], coutput[2], z->s->img_x, n); + for (i=0; i < z->s->img_x; ++i) { + stbi_uc m = coutput[3][i]; + out[0] = stbi__blinn_8x8(255 - out[0], m); + out[1] = stbi__blinn_8x8(255 - out[1], m); + out[2] = stbi__blinn_8x8(255 - out[2], m); + out += n; + } + } else { // YCbCr + alpha? Ignore the fourth channel for now + z->YCbCr_to_RGB_kernel(out, y, coutput[1], coutput[2], z->s->img_x, n); + } + } else + for (i=0; i < z->s->img_x; ++i) { + out[0] = out[1] = out[2] = y[i]; + out[3] = 255; // not used if n==3 + out += n; + } + } else { + if (is_rgb) { + if (n == 1) + for (i=0; i < z->s->img_x; ++i) + *out++ = stbi__compute_y(coutput[0][i], coutput[1][i], coutput[2][i]); + else { + for (i=0; i < z->s->img_x; ++i, out += 2) { + out[0] = stbi__compute_y(coutput[0][i], coutput[1][i], coutput[2][i]); + out[1] = 255; + } + } + } else if (z->s->img_n == 4 && z->app14_color_transform == 0) { + for (i=0; i < z->s->img_x; ++i) { + stbi_uc m = coutput[3][i]; + stbi_uc r = stbi__blinn_8x8(coutput[0][i], m); + stbi_uc g = stbi__blinn_8x8(coutput[1][i], m); + stbi_uc b = stbi__blinn_8x8(coutput[2][i], m); + out[0] = stbi__compute_y(r, g, b); + out[1] = 255; + out += n; + } + } else if (z->s->img_n == 4 && z->app14_color_transform == 2) { + for (i=0; i < z->s->img_x; ++i) { + out[0] = stbi__blinn_8x8(255 - coutput[0][i], coutput[3][i]); + out[1] = 255; + out += n; + } + } else { + stbi_uc *y = coutput[0]; + if (n == 1) + for (i=0; i < z->s->img_x; ++i) out[i] = y[i]; + else + for (i=0; i < z->s->img_x; ++i) { *out++ = y[i]; *out++ = 255; } + } + } + } + stbi__cleanup_jpeg(z); + *out_x = z->s->img_x; + *out_y = z->s->img_y; + if (comp) *comp = z->s->img_n >= 3 ? 3 : 1; // report original components, not output + return output; + } +} + +static void *stbi__jpeg_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri) +{ + unsigned char* result; + stbi__jpeg* j = (stbi__jpeg*) stbi__malloc(sizeof(stbi__jpeg)); + if (!j) return stbi__errpuc("outofmem", "Out of memory"); + memset(j, 0, sizeof(stbi__jpeg)); + STBI_NOTUSED(ri); + j->s = s; + stbi__setup_jpeg(j); + result = load_jpeg_image(j, x,y,comp,req_comp); + STBI_FREE(j); + return result; +} + +static int stbi__jpeg_test(stbi__context *s) +{ + int r; + stbi__jpeg* j = (stbi__jpeg*)stbi__malloc(sizeof(stbi__jpeg)); + if (!j) return stbi__err("outofmem", "Out of memory"); + memset(j, 0, sizeof(stbi__jpeg)); + j->s = s; + stbi__setup_jpeg(j); + r = stbi__decode_jpeg_header(j, STBI__SCAN_type); + stbi__rewind(s); + STBI_FREE(j); + return r; +} + +static int stbi__jpeg_info_raw(stbi__jpeg *j, int *x, int *y, int *comp) +{ + if (!stbi__decode_jpeg_header(j, STBI__SCAN_header)) { + stbi__rewind( j->s ); + return 0; + } + if (x) *x = j->s->img_x; + if (y) *y = j->s->img_y; + if (comp) *comp = j->s->img_n >= 3 ? 3 : 1; + return 1; +} + +static int stbi__jpeg_info(stbi__context *s, int *x, int *y, int *comp) +{ + int result; + stbi__jpeg* j = (stbi__jpeg*) (stbi__malloc(sizeof(stbi__jpeg))); + if (!j) return stbi__err("outofmem", "Out of memory"); + memset(j, 0, sizeof(stbi__jpeg)); + j->s = s; + result = stbi__jpeg_info_raw(j, x, y, comp); + STBI_FREE(j); + return result; +} +#endif + +// public domain zlib decode v0.2 Sean Barrett 2006-11-18 +// simple implementation +// - all input must be provided in an upfront buffer +// - all output is written to a single output buffer (can malloc/realloc) +// performance +// - fast huffman + +#ifndef STBI_NO_ZLIB + +// fast-way is faster to check than jpeg huffman, but slow way is slower +#define STBI__ZFAST_BITS 9 // accelerate all cases in default tables +#define STBI__ZFAST_MASK ((1 << STBI__ZFAST_BITS) - 1) +#define STBI__ZNSYMS 288 // number of symbols in literal/length alphabet + +// zlib-style huffman encoding +// (jpegs packs from left, zlib from right, so can't share code) +typedef struct +{ + stbi__uint16 fast[1 << STBI__ZFAST_BITS]; + stbi__uint16 firstcode[16]; + int maxcode[17]; + stbi__uint16 firstsymbol[16]; + stbi_uc size[STBI__ZNSYMS]; + stbi__uint16 value[STBI__ZNSYMS]; +} stbi__zhuffman; + +stbi_inline static int stbi__bitreverse16(int n) +{ + n = ((n & 0xAAAA) >> 1) | ((n & 0x5555) << 1); + n = ((n & 0xCCCC) >> 2) | ((n & 0x3333) << 2); + n = ((n & 0xF0F0) >> 4) | ((n & 0x0F0F) << 4); + n = ((n & 0xFF00) >> 8) | ((n & 0x00FF) << 8); + return n; +} + +stbi_inline static int stbi__bit_reverse(int v, int bits) +{ + STBI_ASSERT(bits <= 16); + // to bit reverse n bits, reverse 16 and shift + // e.g. 11 bits, bit reverse and shift away 5 + return stbi__bitreverse16(v) >> (16-bits); +} + +static int stbi__zbuild_huffman(stbi__zhuffman *z, const stbi_uc *sizelist, int num) +{ + int i,k=0; + int code, next_code[16], sizes[17]; + + // DEFLATE spec for generating codes + memset(sizes, 0, sizeof(sizes)); + memset(z->fast, 0, sizeof(z->fast)); + for (i=0; i < num; ++i) + ++sizes[sizelist[i]]; + sizes[0] = 0; + for (i=1; i < 16; ++i) + if (sizes[i] > (1 << i)) + return stbi__err("bad sizes", "Corrupt PNG"); + code = 0; + for (i=1; i < 16; ++i) { + next_code[i] = code; + z->firstcode[i] = (stbi__uint16) code; + z->firstsymbol[i] = (stbi__uint16) k; + code = (code + sizes[i]); + if (sizes[i]) + if (code-1 >= (1 << i)) return stbi__err("bad codelengths","Corrupt PNG"); + z->maxcode[i] = code << (16-i); // preshift for inner loop + code <<= 1; + k += sizes[i]; + } + z->maxcode[16] = 0x10000; // sentinel + for (i=0; i < num; ++i) { + int s = sizelist[i]; + if (s) { + int c = next_code[s] - z->firstcode[s] + z->firstsymbol[s]; + stbi__uint16 fastv = (stbi__uint16) ((s << 9) | i); + z->size [c] = (stbi_uc ) s; + z->value[c] = (stbi__uint16) i; + if (s <= STBI__ZFAST_BITS) { + int j = stbi__bit_reverse(next_code[s],s); + while (j < (1 << STBI__ZFAST_BITS)) { + z->fast[j] = fastv; + j += (1 << s); + } + } + ++next_code[s]; + } + } + return 1; +} + +// zlib-from-memory implementation for PNG reading +// because PNG allows splitting the zlib stream arbitrarily, +// and it's annoying structurally to have PNG call ZLIB call PNG, +// we require PNG read all the IDATs and combine them into a single +// memory buffer + +typedef struct +{ + stbi_uc *zbuffer, *zbuffer_end; + int num_bits; + int hit_zeof_once; + stbi__uint32 code_buffer; + + char *zout; + char *zout_start; + char *zout_end; + int z_expandable; + + stbi__zhuffman z_length, z_distance; +} stbi__zbuf; + +stbi_inline static int stbi__zeof(stbi__zbuf *z) +{ + return (z->zbuffer >= z->zbuffer_end); +} + +stbi_inline static stbi_uc stbi__zget8(stbi__zbuf *z) +{ + return stbi__zeof(z) ? 0 : *z->zbuffer++; +} + +static void stbi__fill_bits(stbi__zbuf *z) +{ + do { + if (z->code_buffer >= (1U << z->num_bits)) { + z->zbuffer = z->zbuffer_end; /* treat this as EOF so we fail. */ + return; + } + z->code_buffer |= (unsigned int) stbi__zget8(z) << z->num_bits; + z->num_bits += 8; + } while (z->num_bits <= 24); +} + +stbi_inline static unsigned int stbi__zreceive(stbi__zbuf *z, int n) +{ + unsigned int k; + if (z->num_bits < n) stbi__fill_bits(z); + k = z->code_buffer & ((1 << n) - 1); + z->code_buffer >>= n; + z->num_bits -= n; + return k; +} + +static int stbi__zhuffman_decode_slowpath(stbi__zbuf *a, stbi__zhuffman *z) +{ + int b,s,k; + // not resolved by fast table, so compute it the slow way + // use jpeg approach, which requires MSbits at top + k = stbi__bit_reverse(a->code_buffer, 16); + for (s=STBI__ZFAST_BITS+1; ; ++s) + if (k < z->maxcode[s]) + break; + if (s >= 16) return -1; // invalid code! + // code size is s, so: + b = (k >> (16-s)) - z->firstcode[s] + z->firstsymbol[s]; + if (b >= STBI__ZNSYMS) return -1; // some data was corrupt somewhere! + if (z->size[b] != s) return -1; // was originally an assert, but report failure instead. + a->code_buffer >>= s; + a->num_bits -= s; + return z->value[b]; +} + +stbi_inline static int stbi__zhuffman_decode(stbi__zbuf *a, stbi__zhuffman *z) +{ + int b,s; + if (a->num_bits < 16) { + if (stbi__zeof(a)) { + if (!a->hit_zeof_once) { + // This is the first time we hit eof, insert 16 extra padding btis + // to allow us to keep going; if we actually consume any of them + // though, that is invalid data. This is caught later. + a->hit_zeof_once = 1; + a->num_bits += 16; // add 16 implicit zero bits + } else { + // We already inserted our extra 16 padding bits and are again + // out, this stream is actually prematurely terminated. + return -1; + } + } else { + stbi__fill_bits(a); + } + } + b = z->fast[a->code_buffer & STBI__ZFAST_MASK]; + if (b) { + s = b >> 9; + a->code_buffer >>= s; + a->num_bits -= s; + return b & 511; + } + return stbi__zhuffman_decode_slowpath(a, z); +} + +static int stbi__zexpand(stbi__zbuf *z, char *zout, int n) // need to make room for n bytes +{ + char *q; + unsigned int cur, limit, old_limit; + z->zout = zout; + if (!z->z_expandable) return stbi__err("output buffer limit","Corrupt PNG"); + cur = (unsigned int) (z->zout - z->zout_start); + limit = old_limit = (unsigned) (z->zout_end - z->zout_start); + if (UINT_MAX - cur < (unsigned) n) return stbi__err("outofmem", "Out of memory"); + while (cur + n > limit) { + if(limit > UINT_MAX / 2) return stbi__err("outofmem", "Out of memory"); + limit *= 2; + } + q = (char *) STBI_REALLOC_SIZED(z->zout_start, old_limit, limit); + STBI_NOTUSED(old_limit); + if (q == NULL) return stbi__err("outofmem", "Out of memory"); + z->zout_start = q; + z->zout = q + cur; + z->zout_end = q + limit; + return 1; +} + +static const int stbi__zlength_base[31] = { + 3,4,5,6,7,8,9,10,11,13, + 15,17,19,23,27,31,35,43,51,59, + 67,83,99,115,131,163,195,227,258,0,0 }; + +static const int stbi__zlength_extra[31]= +{ 0,0,0,0,0,0,0,0,1,1,1,1,2,2,2,2,3,3,3,3,4,4,4,4,5,5,5,5,0,0,0 }; + +static const int stbi__zdist_base[32] = { 1,2,3,4,5,7,9,13,17,25,33,49,65,97,129,193, +257,385,513,769,1025,1537,2049,3073,4097,6145,8193,12289,16385,24577,0,0}; + +static const int stbi__zdist_extra[32] = +{ 0,0,0,0,1,1,2,2,3,3,4,4,5,5,6,6,7,7,8,8,9,9,10,10,11,11,12,12,13,13}; + +static int stbi__parse_huffman_block(stbi__zbuf *a) +{ + char *zout = a->zout; + for(;;) { + int z = stbi__zhuffman_decode(a, &a->z_length); + if (z < 256) { + if (z < 0) return stbi__err("bad huffman code","Corrupt PNG"); // error in huffman codes + if (zout >= a->zout_end) { + if (!stbi__zexpand(a, zout, 1)) return 0; + zout = a->zout; + } + *zout++ = (char) z; + } else { + stbi_uc *p; + int len,dist; + if (z == 256) { + a->zout = zout; + if (a->hit_zeof_once && a->num_bits < 16) { + // The first time we hit zeof, we inserted 16 extra zero bits into our bit + // buffer so the decoder can just do its speculative decoding. But if we + // actually consumed any of those bits (which is the case when num_bits < 16), + // the stream actually read past the end so it is malformed. + return stbi__err("unexpected end","Corrupt PNG"); + } + return 1; + } + if (z >= 286) return stbi__err("bad huffman code","Corrupt PNG"); // per DEFLATE, length codes 286 and 287 must not appear in compressed data + z -= 257; + len = stbi__zlength_base[z]; + if (stbi__zlength_extra[z]) len += stbi__zreceive(a, stbi__zlength_extra[z]); + z = stbi__zhuffman_decode(a, &a->z_distance); + if (z < 0 || z >= 30) return stbi__err("bad huffman code","Corrupt PNG"); // per DEFLATE, distance codes 30 and 31 must not appear in compressed data + dist = stbi__zdist_base[z]; + if (stbi__zdist_extra[z]) dist += stbi__zreceive(a, stbi__zdist_extra[z]); + if (zout - a->zout_start < dist) return stbi__err("bad dist","Corrupt PNG"); + if (len > a->zout_end - zout) { + if (!stbi__zexpand(a, zout, len)) return 0; + zout = a->zout; + } + p = (stbi_uc *) (zout - dist); + if (dist == 1) { // run of one byte; common in images. + stbi_uc v = *p; + if (len) { do *zout++ = v; while (--len); } + } else { + if (len) { do *zout++ = *p++; while (--len); } + } + } + } +} + +static int stbi__compute_huffman_codes(stbi__zbuf *a) +{ + static const stbi_uc length_dezigzag[19] = { 16,17,18,0,8,7,9,6,10,5,11,4,12,3,13,2,14,1,15 }; + stbi__zhuffman z_codelength; + stbi_uc lencodes[286+32+137];//padding for maximum single op + stbi_uc codelength_sizes[19]; + int i,n; + + int hlit = stbi__zreceive(a,5) + 257; + int hdist = stbi__zreceive(a,5) + 1; + int hclen = stbi__zreceive(a,4) + 4; + int ntot = hlit + hdist; + + memset(codelength_sizes, 0, sizeof(codelength_sizes)); + for (i=0; i < hclen; ++i) { + int s = stbi__zreceive(a,3); + codelength_sizes[length_dezigzag[i]] = (stbi_uc) s; + } + if (!stbi__zbuild_huffman(&z_codelength, codelength_sizes, 19)) return 0; + + n = 0; + while (n < ntot) { + int c = stbi__zhuffman_decode(a, &z_codelength); + if (c < 0 || c >= 19) return stbi__err("bad codelengths", "Corrupt PNG"); + if (c < 16) + lencodes[n++] = (stbi_uc) c; + else { + stbi_uc fill = 0; + if (c == 16) { + c = stbi__zreceive(a,2)+3; + if (n == 0) return stbi__err("bad codelengths", "Corrupt PNG"); + fill = lencodes[n-1]; + } else if (c == 17) { + c = stbi__zreceive(a,3)+3; + } else if (c == 18) { + c = stbi__zreceive(a,7)+11; + } else { + return stbi__err("bad codelengths", "Corrupt PNG"); + } + if (ntot - n < c) return stbi__err("bad codelengths", "Corrupt PNG"); + memset(lencodes+n, fill, c); + n += c; + } + } + if (n != ntot) return stbi__err("bad codelengths","Corrupt PNG"); + if (!stbi__zbuild_huffman(&a->z_length, lencodes, hlit)) return 0; + if (!stbi__zbuild_huffman(&a->z_distance, lencodes+hlit, hdist)) return 0; + return 1; +} + +static int stbi__parse_uncompressed_block(stbi__zbuf *a) +{ + stbi_uc header[4]; + int len,nlen,k; + if (a->num_bits & 7) + stbi__zreceive(a, a->num_bits & 7); // discard + // drain the bit-packed data into header + k = 0; + while (a->num_bits > 0) { + header[k++] = (stbi_uc) (a->code_buffer & 255); // suppress MSVC run-time check + a->code_buffer >>= 8; + a->num_bits -= 8; + } + if (a->num_bits < 0) return stbi__err("zlib corrupt","Corrupt PNG"); + // now fill header the normal way + while (k < 4) + header[k++] = stbi__zget8(a); + len = header[1] * 256 + header[0]; + nlen = header[3] * 256 + header[2]; + if (nlen != (len ^ 0xffff)) return stbi__err("zlib corrupt","Corrupt PNG"); + if (a->zbuffer + len > a->zbuffer_end) return stbi__err("read past buffer","Corrupt PNG"); + if (a->zout + len > a->zout_end) + if (!stbi__zexpand(a, a->zout, len)) return 0; + memcpy(a->zout, a->zbuffer, len); + a->zbuffer += len; + a->zout += len; + return 1; +} + +static int stbi__parse_zlib_header(stbi__zbuf *a) +{ + int cmf = stbi__zget8(a); + int cm = cmf & 15; + /* int cinfo = cmf >> 4; */ + int flg = stbi__zget8(a); + if (stbi__zeof(a)) return stbi__err("bad zlib header","Corrupt PNG"); // zlib spec + if ((cmf*256+flg) % 31 != 0) return stbi__err("bad zlib header","Corrupt PNG"); // zlib spec + if (flg & 32) return stbi__err("no preset dict","Corrupt PNG"); // preset dictionary not allowed in png + if (cm != 8) return stbi__err("bad compression","Corrupt PNG"); // DEFLATE required for png + // window = 1 << (8 + cinfo)... but who cares, we fully buffer output + return 1; +} + +static const stbi_uc stbi__zdefault_length[STBI__ZNSYMS] = +{ + 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, + 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, + 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, + 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, + 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, + 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, + 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, + 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, + 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, 7,7,7,7,7,7,7,7,8,8,8,8,8,8,8,8 +}; +static const stbi_uc stbi__zdefault_distance[32] = +{ + 5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5 +}; +/* +Init algorithm: +{ + int i; // use <= to match clearly with spec + for (i=0; i <= 143; ++i) stbi__zdefault_length[i] = 8; + for ( ; i <= 255; ++i) stbi__zdefault_length[i] = 9; + for ( ; i <= 279; ++i) stbi__zdefault_length[i] = 7; + for ( ; i <= 287; ++i) stbi__zdefault_length[i] = 8; + + for (i=0; i <= 31; ++i) stbi__zdefault_distance[i] = 5; +} +*/ + +static int stbi__parse_zlib(stbi__zbuf *a, int parse_header) +{ + int final, type; + if (parse_header) + if (!stbi__parse_zlib_header(a)) return 0; + a->num_bits = 0; + a->code_buffer = 0; + a->hit_zeof_once = 0; + do { + final = stbi__zreceive(a,1); + type = stbi__zreceive(a,2); + if (type == 0) { + if (!stbi__parse_uncompressed_block(a)) return 0; + } else if (type == 3) { + return 0; + } else { + if (type == 1) { + // use fixed code lengths + if (!stbi__zbuild_huffman(&a->z_length , stbi__zdefault_length , STBI__ZNSYMS)) return 0; + if (!stbi__zbuild_huffman(&a->z_distance, stbi__zdefault_distance, 32)) return 0; + } else { + if (!stbi__compute_huffman_codes(a)) return 0; + } + if (!stbi__parse_huffman_block(a)) return 0; + } + } while (!final); + return 1; +} + +static int stbi__do_zlib(stbi__zbuf *a, char *obuf, int olen, int exp, int parse_header) +{ + a->zout_start = obuf; + a->zout = obuf; + a->zout_end = obuf + olen; + a->z_expandable = exp; + + return stbi__parse_zlib(a, parse_header); +} + +STBIDEF char *stbi_zlib_decode_malloc_guesssize(const char *buffer, int len, int initial_size, int *outlen) +{ + stbi__zbuf a; + char *p = (char *) stbi__malloc(initial_size); + if (p == NULL) return NULL; + a.zbuffer = (stbi_uc *) buffer; + a.zbuffer_end = (stbi_uc *) buffer + len; + if (stbi__do_zlib(&a, p, initial_size, 1, 1)) { + if (outlen) *outlen = (int) (a.zout - a.zout_start); + return a.zout_start; + } else { + STBI_FREE(a.zout_start); + return NULL; + } +} + +STBIDEF char *stbi_zlib_decode_malloc(char const *buffer, int len, int *outlen) +{ + return stbi_zlib_decode_malloc_guesssize(buffer, len, 16384, outlen); +} + +STBIDEF char *stbi_zlib_decode_malloc_guesssize_headerflag(const char *buffer, int len, int initial_size, int *outlen, int parse_header) +{ + stbi__zbuf a; + char *p = (char *) stbi__malloc(initial_size); + if (p == NULL) return NULL; + a.zbuffer = (stbi_uc *) buffer; + a.zbuffer_end = (stbi_uc *) buffer + len; + if (stbi__do_zlib(&a, p, initial_size, 1, parse_header)) { + if (outlen) *outlen = (int) (a.zout - a.zout_start); + return a.zout_start; + } else { + STBI_FREE(a.zout_start); + return NULL; + } +} + +STBIDEF int stbi_zlib_decode_buffer(char *obuffer, int olen, char const *ibuffer, int ilen) +{ + stbi__zbuf a; + a.zbuffer = (stbi_uc *) ibuffer; + a.zbuffer_end = (stbi_uc *) ibuffer + ilen; + if (stbi__do_zlib(&a, obuffer, olen, 0, 1)) + return (int) (a.zout - a.zout_start); + else + return -1; +} + +STBIDEF char *stbi_zlib_decode_noheader_malloc(char const *buffer, int len, int *outlen) +{ + stbi__zbuf a; + char *p = (char *) stbi__malloc(16384); + if (p == NULL) return NULL; + a.zbuffer = (stbi_uc *) buffer; + a.zbuffer_end = (stbi_uc *) buffer+len; + if (stbi__do_zlib(&a, p, 16384, 1, 0)) { + if (outlen) *outlen = (int) (a.zout - a.zout_start); + return a.zout_start; + } else { + STBI_FREE(a.zout_start); + return NULL; + } +} + +STBIDEF int stbi_zlib_decode_noheader_buffer(char *obuffer, int olen, const char *ibuffer, int ilen) +{ + stbi__zbuf a; + a.zbuffer = (stbi_uc *) ibuffer; + a.zbuffer_end = (stbi_uc *) ibuffer + ilen; + if (stbi__do_zlib(&a, obuffer, olen, 0, 0)) + return (int) (a.zout - a.zout_start); + else + return -1; +} +#endif + +// public domain "baseline" PNG decoder v0.10 Sean Barrett 2006-11-18 +// simple implementation +// - only 8-bit samples +// - no CRC checking +// - allocates lots of intermediate memory +// - avoids problem of streaming data between subsystems +// - avoids explicit window management +// performance +// - uses stb_zlib, a PD zlib implementation with fast huffman decoding + +#ifndef STBI_NO_PNG +typedef struct +{ + stbi__uint32 length; + stbi__uint32 type; +} stbi__pngchunk; + +static stbi__pngchunk stbi__get_chunk_header(stbi__context *s) +{ + stbi__pngchunk c; + c.length = stbi__get32be(s); + c.type = stbi__get32be(s); + return c; +} + +static int stbi__check_png_header(stbi__context *s) +{ + static const stbi_uc png_sig[8] = { 137,80,78,71,13,10,26,10 }; + int i; + for (i=0; i < 8; ++i) + if (stbi__get8(s) != png_sig[i]) return stbi__err("bad png sig","Not a PNG"); + return 1; +} + +typedef struct +{ + stbi__context *s; + stbi_uc *idata, *expanded, *out; + int depth; +} stbi__png; + + +enum { + STBI__F_none=0, + STBI__F_sub=1, + STBI__F_up=2, + STBI__F_avg=3, + STBI__F_paeth=4, + // synthetic filter used for first scanline to avoid needing a dummy row of 0s + STBI__F_avg_first +}; + +static stbi_uc first_row_filter[5] = +{ + STBI__F_none, + STBI__F_sub, + STBI__F_none, + STBI__F_avg_first, + STBI__F_sub // Paeth with b=c=0 turns out to be equivalent to sub +}; + +static int stbi__paeth(int a, int b, int c) +{ + // This formulation looks very different from the reference in the PNG spec, but is + // actually equivalent and has favorable data dependencies and admits straightforward + // generation of branch-free code, which helps performance significantly. + int thresh = c*3 - (a + b); + int lo = a < b ? a : b; + int hi = a < b ? b : a; + int t0 = (hi <= thresh) ? lo : c; + int t1 = (thresh <= lo) ? hi : t0; + return t1; +} + +static const stbi_uc stbi__depth_scale_table[9] = { 0, 0xff, 0x55, 0, 0x11, 0,0,0, 0x01 }; + +// adds an extra all-255 alpha channel +// dest == src is legal +// img_n must be 1 or 3 +static void stbi__create_png_alpha_expand8(stbi_uc *dest, stbi_uc *src, stbi__uint32 x, int img_n) +{ + int i; + // must process data backwards since we allow dest==src + if (img_n == 1) { + for (i=x-1; i >= 0; --i) { + dest[i*2+1] = 255; + dest[i*2+0] = src[i]; + } + } else { + STBI_ASSERT(img_n == 3); + for (i=x-1; i >= 0; --i) { + dest[i*4+3] = 255; + dest[i*4+2] = src[i*3+2]; + dest[i*4+1] = src[i*3+1]; + dest[i*4+0] = src[i*3+0]; + } + } +} + +// create the png data from post-deflated data +static int stbi__create_png_image_raw(stbi__png *a, stbi_uc *raw, stbi__uint32 raw_len, int out_n, stbi__uint32 x, stbi__uint32 y, int depth, int color) +{ + int bytes = (depth == 16 ? 2 : 1); + stbi__context *s = a->s; + stbi__uint32 i,j,stride = x*out_n*bytes; + stbi__uint32 img_len, img_width_bytes; + stbi_uc *filter_buf; + int all_ok = 1; + int k; + int img_n = s->img_n; // copy it into a local for later + + int output_bytes = out_n*bytes; + int filter_bytes = img_n*bytes; + int width = x; + + STBI_ASSERT(out_n == s->img_n || out_n == s->img_n+1); + a->out = (stbi_uc *) stbi__malloc_mad3(x, y, output_bytes, 0); // extra bytes to write off the end into + if (!a->out) return stbi__err("outofmem", "Out of memory"); + + // note: error exits here don't need to clean up a->out individually, + // stbi__do_png always does on error. + if (!stbi__mad3sizes_valid(img_n, x, depth, 7)) return stbi__err("too large", "Corrupt PNG"); + img_width_bytes = (((img_n * x * depth) + 7) >> 3); + if (!stbi__mad2sizes_valid(img_width_bytes, y, img_width_bytes)) return stbi__err("too large", "Corrupt PNG"); + img_len = (img_width_bytes + 1) * y; + + // we used to check for exact match between raw_len and img_len on non-interlaced PNGs, + // but issue #276 reported a PNG in the wild that had extra data at the end (all zeros), + // so just check for raw_len < img_len always. + if (raw_len < img_len) return stbi__err("not enough pixels","Corrupt PNG"); + + // Allocate two scan lines worth of filter workspace buffer. + filter_buf = (stbi_uc *) stbi__malloc_mad2(img_width_bytes, 2, 0); + if (!filter_buf) return stbi__err("outofmem", "Out of memory"); + + // Filtering for low-bit-depth images + if (depth < 8) { + filter_bytes = 1; + width = img_width_bytes; + } + + for (j=0; j < y; ++j) { + // cur/prior filter buffers alternate + stbi_uc *cur = filter_buf + (j & 1)*img_width_bytes; + stbi_uc *prior = filter_buf + (~j & 1)*img_width_bytes; + stbi_uc *dest = a->out + stride*j; + int nk = width * filter_bytes; + int filter = *raw++; + + // check filter type + if (filter > 4) { + all_ok = stbi__err("invalid filter","Corrupt PNG"); + break; + } + + // if first row, use special filter that doesn't sample previous row + if (j == 0) filter = first_row_filter[filter]; + + // perform actual filtering + switch (filter) { + case STBI__F_none: + memcpy(cur, raw, nk); + break; + case STBI__F_sub: + memcpy(cur, raw, filter_bytes); + for (k = filter_bytes; k < nk; ++k) + cur[k] = STBI__BYTECAST(raw[k] + cur[k-filter_bytes]); + break; + case STBI__F_up: + for (k = 0; k < nk; ++k) + cur[k] = STBI__BYTECAST(raw[k] + prior[k]); + break; + case STBI__F_avg: + for (k = 0; k < filter_bytes; ++k) + cur[k] = STBI__BYTECAST(raw[k] + (prior[k]>>1)); + for (k = filter_bytes; k < nk; ++k) + cur[k] = STBI__BYTECAST(raw[k] + ((prior[k] + cur[k-filter_bytes])>>1)); + break; + case STBI__F_paeth: + for (k = 0; k < filter_bytes; ++k) + cur[k] = STBI__BYTECAST(raw[k] + prior[k]); // prior[k] == stbi__paeth(0,prior[k],0) + for (k = filter_bytes; k < nk; ++k) + cur[k] = STBI__BYTECAST(raw[k] + stbi__paeth(cur[k-filter_bytes], prior[k], prior[k-filter_bytes])); + break; + case STBI__F_avg_first: + memcpy(cur, raw, filter_bytes); + for (k = filter_bytes; k < nk; ++k) + cur[k] = STBI__BYTECAST(raw[k] + (cur[k-filter_bytes] >> 1)); + break; + } + + raw += nk; + + // expand decoded bits in cur to dest, also adding an extra alpha channel if desired + if (depth < 8) { + stbi_uc scale = (color == 0) ? stbi__depth_scale_table[depth] : 1; // scale grayscale values to 0..255 range + stbi_uc *in = cur; + stbi_uc *out = dest; + stbi_uc inb = 0; + stbi__uint32 nsmp = x*img_n; + + // expand bits to bytes first + if (depth == 4) { + for (i=0; i < nsmp; ++i) { + if ((i & 1) == 0) inb = *in++; + *out++ = scale * (inb >> 4); + inb <<= 4; + } + } else if (depth == 2) { + for (i=0; i < nsmp; ++i) { + if ((i & 3) == 0) inb = *in++; + *out++ = scale * (inb >> 6); + inb <<= 2; + } + } else { + STBI_ASSERT(depth == 1); + for (i=0; i < nsmp; ++i) { + if ((i & 7) == 0) inb = *in++; + *out++ = scale * (inb >> 7); + inb <<= 1; + } + } + + // insert alpha=255 values if desired + if (img_n != out_n) + stbi__create_png_alpha_expand8(dest, dest, x, img_n); + } else if (depth == 8) { + if (img_n == out_n) + memcpy(dest, cur, x*img_n); + else + stbi__create_png_alpha_expand8(dest, cur, x, img_n); + } else if (depth == 16) { + // convert the image data from big-endian to platform-native + stbi__uint16 *dest16 = (stbi__uint16*)dest; + stbi__uint32 nsmp = x*img_n; + + if (img_n == out_n) { + for (i = 0; i < nsmp; ++i, ++dest16, cur += 2) + *dest16 = (cur[0] << 8) | cur[1]; + } else { + STBI_ASSERT(img_n+1 == out_n); + if (img_n == 1) { + for (i = 0; i < x; ++i, dest16 += 2, cur += 2) { + dest16[0] = (cur[0] << 8) | cur[1]; + dest16[1] = 0xffff; + } + } else { + STBI_ASSERT(img_n == 3); + for (i = 0; i < x; ++i, dest16 += 4, cur += 6) { + dest16[0] = (cur[0] << 8) | cur[1]; + dest16[1] = (cur[2] << 8) | cur[3]; + dest16[2] = (cur[4] << 8) | cur[5]; + dest16[3] = 0xffff; + } + } + } + } + } + + STBI_FREE(filter_buf); + if (!all_ok) return 0; + + return 1; +} + +static int stbi__create_png_image(stbi__png *a, stbi_uc *image_data, stbi__uint32 image_data_len, int out_n, int depth, int color, int interlaced) +{ + int bytes = (depth == 16 ? 2 : 1); + int out_bytes = out_n * bytes; + stbi_uc *final; + int p; + if (!interlaced) + return stbi__create_png_image_raw(a, image_data, image_data_len, out_n, a->s->img_x, a->s->img_y, depth, color); + + // de-interlacing + final = (stbi_uc *) stbi__malloc_mad3(a->s->img_x, a->s->img_y, out_bytes, 0); + if (!final) return stbi__err("outofmem", "Out of memory"); + for (p=0; p < 7; ++p) { + int xorig[] = { 0,4,0,2,0,1,0 }; + int yorig[] = { 0,0,4,0,2,0,1 }; + int xspc[] = { 8,8,4,4,2,2,1 }; + int yspc[] = { 8,8,8,4,4,2,2 }; + int i,j,x,y; + // pass1_x[4] = 0, pass1_x[5] = 1, pass1_x[12] = 1 + x = (a->s->img_x - xorig[p] + xspc[p]-1) / xspc[p]; + y = (a->s->img_y - yorig[p] + yspc[p]-1) / yspc[p]; + if (x && y) { + stbi__uint32 img_len = ((((a->s->img_n * x * depth) + 7) >> 3) + 1) * y; + if (!stbi__create_png_image_raw(a, image_data, image_data_len, out_n, x, y, depth, color)) { + STBI_FREE(final); + return 0; + } + for (j=0; j < y; ++j) { + for (i=0; i < x; ++i) { + int out_y = j*yspc[p]+yorig[p]; + int out_x = i*xspc[p]+xorig[p]; + memcpy(final + out_y*a->s->img_x*out_bytes + out_x*out_bytes, + a->out + (j*x+i)*out_bytes, out_bytes); + } + } + STBI_FREE(a->out); + image_data += img_len; + image_data_len -= img_len; + } + } + a->out = final; + + return 1; +} + +static int stbi__compute_transparency(stbi__png *z, stbi_uc tc[3], int out_n) +{ + stbi__context *s = z->s; + stbi__uint32 i, pixel_count = s->img_x * s->img_y; + stbi_uc *p = z->out; + + // compute color-based transparency, assuming we've + // already got 255 as the alpha value in the output + STBI_ASSERT(out_n == 2 || out_n == 4); + + if (out_n == 2) { + for (i=0; i < pixel_count; ++i) { + p[1] = (p[0] == tc[0] ? 0 : 255); + p += 2; + } + } else { + for (i=0; i < pixel_count; ++i) { + if (p[0] == tc[0] && p[1] == tc[1] && p[2] == tc[2]) + p[3] = 0; + p += 4; + } + } + return 1; +} + +static int stbi__compute_transparency16(stbi__png *z, stbi__uint16 tc[3], int out_n) +{ + stbi__context *s = z->s; + stbi__uint32 i, pixel_count = s->img_x * s->img_y; + stbi__uint16 *p = (stbi__uint16*) z->out; + + // compute color-based transparency, assuming we've + // already got 65535 as the alpha value in the output + STBI_ASSERT(out_n == 2 || out_n == 4); + + if (out_n == 2) { + for (i = 0; i < pixel_count; ++i) { + p[1] = (p[0] == tc[0] ? 0 : 65535); + p += 2; + } + } else { + for (i = 0; i < pixel_count; ++i) { + if (p[0] == tc[0] && p[1] == tc[1] && p[2] == tc[2]) + p[3] = 0; + p += 4; + } + } + return 1; +} + +static int stbi__expand_png_palette(stbi__png *a, stbi_uc *palette, int len, int pal_img_n) +{ + stbi__uint32 i, pixel_count = a->s->img_x * a->s->img_y; + stbi_uc *p, *temp_out, *orig = a->out; + + p = (stbi_uc *) stbi__malloc_mad2(pixel_count, pal_img_n, 0); + if (p == NULL) return stbi__err("outofmem", "Out of memory"); + + // between here and free(out) below, exitting would leak + temp_out = p; + + if (pal_img_n == 3) { + for (i=0; i < pixel_count; ++i) { + int n = orig[i]*4; + p[0] = palette[n ]; + p[1] = palette[n+1]; + p[2] = palette[n+2]; + p += 3; + } + } else { + for (i=0; i < pixel_count; ++i) { + int n = orig[i]*4; + p[0] = palette[n ]; + p[1] = palette[n+1]; + p[2] = palette[n+2]; + p[3] = palette[n+3]; + p += 4; + } + } + STBI_FREE(a->out); + a->out = temp_out; + + STBI_NOTUSED(len); + + return 1; +} + +static int stbi__unpremultiply_on_load_global = 0; +static int stbi__de_iphone_flag_global = 0; + +STBIDEF void stbi_set_unpremultiply_on_load(int flag_true_if_should_unpremultiply) +{ + stbi__unpremultiply_on_load_global = flag_true_if_should_unpremultiply; +} + +STBIDEF void stbi_convert_iphone_png_to_rgb(int flag_true_if_should_convert) +{ + stbi__de_iphone_flag_global = flag_true_if_should_convert; +} + +#ifndef STBI_THREAD_LOCAL +#define stbi__unpremultiply_on_load stbi__unpremultiply_on_load_global +#define stbi__de_iphone_flag stbi__de_iphone_flag_global +#else +static STBI_THREAD_LOCAL int stbi__unpremultiply_on_load_local, stbi__unpremultiply_on_load_set; +static STBI_THREAD_LOCAL int stbi__de_iphone_flag_local, stbi__de_iphone_flag_set; + +STBIDEF void stbi_set_unpremultiply_on_load_thread(int flag_true_if_should_unpremultiply) +{ + stbi__unpremultiply_on_load_local = flag_true_if_should_unpremultiply; + stbi__unpremultiply_on_load_set = 1; +} + +STBIDEF void stbi_convert_iphone_png_to_rgb_thread(int flag_true_if_should_convert) +{ + stbi__de_iphone_flag_local = flag_true_if_should_convert; + stbi__de_iphone_flag_set = 1; +} + +#define stbi__unpremultiply_on_load (stbi__unpremultiply_on_load_set \ + ? stbi__unpremultiply_on_load_local \ + : stbi__unpremultiply_on_load_global) +#define stbi__de_iphone_flag (stbi__de_iphone_flag_set \ + ? stbi__de_iphone_flag_local \ + : stbi__de_iphone_flag_global) +#endif // STBI_THREAD_LOCAL + +static void stbi__de_iphone(stbi__png *z) +{ + stbi__context *s = z->s; + stbi__uint32 i, pixel_count = s->img_x * s->img_y; + stbi_uc *p = z->out; + + if (s->img_out_n == 3) { // convert bgr to rgb + for (i=0; i < pixel_count; ++i) { + stbi_uc t = p[0]; + p[0] = p[2]; + p[2] = t; + p += 3; + } + } else { + STBI_ASSERT(s->img_out_n == 4); + if (stbi__unpremultiply_on_load) { + // convert bgr to rgb and unpremultiply + for (i=0; i < pixel_count; ++i) { + stbi_uc a = p[3]; + stbi_uc t = p[0]; + if (a) { + stbi_uc half = a / 2; + p[0] = (p[2] * 255 + half) / a; + p[1] = (p[1] * 255 + half) / a; + p[2] = ( t * 255 + half) / a; + } else { + p[0] = p[2]; + p[2] = t; + } + p += 4; + } + } else { + // convert bgr to rgb + for (i=0; i < pixel_count; ++i) { + stbi_uc t = p[0]; + p[0] = p[2]; + p[2] = t; + p += 4; + } + } + } +} + +#define STBI__PNG_TYPE(a,b,c,d) (((unsigned) (a) << 24) + ((unsigned) (b) << 16) + ((unsigned) (c) << 8) + (unsigned) (d)) + +static int stbi__parse_png_file(stbi__png *z, int scan, int req_comp) +{ + stbi_uc palette[1024], pal_img_n=0; + stbi_uc has_trans=0, tc[3]={0}; + stbi__uint16 tc16[3]; + stbi__uint32 ioff=0, idata_limit=0, i, pal_len=0; + int first=1,k,interlace=0, color=0, is_iphone=0; + stbi__context *s = z->s; + + z->expanded = NULL; + z->idata = NULL; + z->out = NULL; + + if (!stbi__check_png_header(s)) return 0; + + if (scan == STBI__SCAN_type) return 1; + + for (;;) { + stbi__pngchunk c = stbi__get_chunk_header(s); + switch (c.type) { + case STBI__PNG_TYPE('C','g','B','I'): + is_iphone = 1; + stbi__skip(s, c.length); + break; + case STBI__PNG_TYPE('I','H','D','R'): { + int comp,filter; + if (!first) return stbi__err("multiple IHDR","Corrupt PNG"); + first = 0; + if (c.length != 13) return stbi__err("bad IHDR len","Corrupt PNG"); + s->img_x = stbi__get32be(s); + s->img_y = stbi__get32be(s); + if (s->img_y > STBI_MAX_DIMENSIONS) return stbi__err("too large","Very large image (corrupt?)"); + if (s->img_x > STBI_MAX_DIMENSIONS) return stbi__err("too large","Very large image (corrupt?)"); + z->depth = stbi__get8(s); if (z->depth != 1 && z->depth != 2 && z->depth != 4 && z->depth != 8 && z->depth != 16) return stbi__err("1/2/4/8/16-bit only","PNG not supported: 1/2/4/8/16-bit only"); + color = stbi__get8(s); if (color > 6) return stbi__err("bad ctype","Corrupt PNG"); + if (color == 3 && z->depth == 16) return stbi__err("bad ctype","Corrupt PNG"); + if (color == 3) pal_img_n = 3; else if (color & 1) return stbi__err("bad ctype","Corrupt PNG"); + comp = stbi__get8(s); if (comp) return stbi__err("bad comp method","Corrupt PNG"); + filter= stbi__get8(s); if (filter) return stbi__err("bad filter method","Corrupt PNG"); + interlace = stbi__get8(s); if (interlace>1) return stbi__err("bad interlace method","Corrupt PNG"); + if (!s->img_x || !s->img_y) return stbi__err("0-pixel image","Corrupt PNG"); + if (!pal_img_n) { + s->img_n = (color & 2 ? 3 : 1) + (color & 4 ? 1 : 0); + if ((1 << 30) / s->img_x / s->img_n < s->img_y) return stbi__err("too large", "Image too large to decode"); + } else { + // if paletted, then pal_n is our final components, and + // img_n is # components to decompress/filter. + s->img_n = 1; + if ((1 << 30) / s->img_x / 4 < s->img_y) return stbi__err("too large","Corrupt PNG"); + } + // even with SCAN_header, have to scan to see if we have a tRNS + break; + } + + case STBI__PNG_TYPE('P','L','T','E'): { + if (first) return stbi__err("first not IHDR", "Corrupt PNG"); + if (c.length > 256*3) return stbi__err("invalid PLTE","Corrupt PNG"); + pal_len = c.length / 3; + if (pal_len * 3 != c.length) return stbi__err("invalid PLTE","Corrupt PNG"); + for (i=0; i < pal_len; ++i) { + palette[i*4+0] = stbi__get8(s); + palette[i*4+1] = stbi__get8(s); + palette[i*4+2] = stbi__get8(s); + palette[i*4+3] = 255; + } + break; + } + + case STBI__PNG_TYPE('t','R','N','S'): { + if (first) return stbi__err("first not IHDR", "Corrupt PNG"); + if (z->idata) return stbi__err("tRNS after IDAT","Corrupt PNG"); + if (pal_img_n) { + if (scan == STBI__SCAN_header) { s->img_n = 4; return 1; } + if (pal_len == 0) return stbi__err("tRNS before PLTE","Corrupt PNG"); + if (c.length > pal_len) return stbi__err("bad tRNS len","Corrupt PNG"); + pal_img_n = 4; + for (i=0; i < c.length; ++i) + palette[i*4+3] = stbi__get8(s); + } else { + if (!(s->img_n & 1)) return stbi__err("tRNS with alpha","Corrupt PNG"); + if (c.length != (stbi__uint32) s->img_n*2) return stbi__err("bad tRNS len","Corrupt PNG"); + has_trans = 1; + // non-paletted with tRNS = constant alpha. if header-scanning, we can stop now. + if (scan == STBI__SCAN_header) { ++s->img_n; return 1; } + if (z->depth == 16) { + for (k = 0; k < s->img_n && k < 3; ++k) // extra loop test to suppress false GCC warning + tc16[k] = (stbi__uint16)stbi__get16be(s); // copy the values as-is + } else { + for (k = 0; k < s->img_n && k < 3; ++k) + tc[k] = (stbi_uc)(stbi__get16be(s) & 255) * stbi__depth_scale_table[z->depth]; // non 8-bit images will be larger + } + } + break; + } + + case STBI__PNG_TYPE('I','D','A','T'): { + if (first) return stbi__err("first not IHDR", "Corrupt PNG"); + if (pal_img_n && !pal_len) return stbi__err("no PLTE","Corrupt PNG"); + if (scan == STBI__SCAN_header) { + // header scan definitely stops at first IDAT + if (pal_img_n) + s->img_n = pal_img_n; + return 1; + } + if (c.length > (1u << 30)) return stbi__err("IDAT size limit", "IDAT section larger than 2^30 bytes"); + if ((int)(ioff + c.length) < (int)ioff) return 0; + if (ioff + c.length > idata_limit) { + stbi__uint32 idata_limit_old = idata_limit; + stbi_uc *p; + if (idata_limit == 0) idata_limit = c.length > 4096 ? c.length : 4096; + while (ioff + c.length > idata_limit) + idata_limit *= 2; + STBI_NOTUSED(idata_limit_old); + p = (stbi_uc *) STBI_REALLOC_SIZED(z->idata, idata_limit_old, idata_limit); if (p == NULL) return stbi__err("outofmem", "Out of memory"); + z->idata = p; + } + if (!stbi__getn(s, z->idata+ioff,c.length)) return stbi__err("outofdata","Corrupt PNG"); + ioff += c.length; + break; + } + + case STBI__PNG_TYPE('I','E','N','D'): { + stbi__uint32 raw_len, bpl; + if (first) return stbi__err("first not IHDR", "Corrupt PNG"); + if (scan != STBI__SCAN_load) return 1; + if (z->idata == NULL) return stbi__err("no IDAT","Corrupt PNG"); + // initial guess for decoded data size to avoid unnecessary reallocs + bpl = (s->img_x * z->depth + 7) / 8; // bytes per line, per component + raw_len = bpl * s->img_y * s->img_n /* pixels */ + s->img_y /* filter mode per row */; + z->expanded = (stbi_uc *) stbi_zlib_decode_malloc_guesssize_headerflag((char *) z->idata, ioff, raw_len, (int *) &raw_len, !is_iphone); + if (z->expanded == NULL) return 0; // zlib should set error + STBI_FREE(z->idata); z->idata = NULL; + if ((req_comp == s->img_n+1 && req_comp != 3 && !pal_img_n) || has_trans) + s->img_out_n = s->img_n+1; + else + s->img_out_n = s->img_n; + if (!stbi__create_png_image(z, z->expanded, raw_len, s->img_out_n, z->depth, color, interlace)) return 0; + if (has_trans) { + if (z->depth == 16) { + if (!stbi__compute_transparency16(z, tc16, s->img_out_n)) return 0; + } else { + if (!stbi__compute_transparency(z, tc, s->img_out_n)) return 0; + } + } + if (is_iphone && stbi__de_iphone_flag && s->img_out_n > 2) + stbi__de_iphone(z); + if (pal_img_n) { + // pal_img_n == 3 or 4 + s->img_n = pal_img_n; // record the actual colors we had + s->img_out_n = pal_img_n; + if (req_comp >= 3) s->img_out_n = req_comp; + if (!stbi__expand_png_palette(z, palette, pal_len, s->img_out_n)) + return 0; + } else if (has_trans) { + // non-paletted image with tRNS -> source image has (constant) alpha + ++s->img_n; + } + STBI_FREE(z->expanded); z->expanded = NULL; + // end of PNG chunk, read and skip CRC + stbi__get32be(s); + return 1; + } + + default: + // if critical, fail + if (first) return stbi__err("first not IHDR", "Corrupt PNG"); + if ((c.type & (1 << 29)) == 0) { + #ifndef STBI_NO_FAILURE_STRINGS + // not threadsafe + static char invalid_chunk[] = "XXXX PNG chunk not known"; + invalid_chunk[0] = STBI__BYTECAST(c.type >> 24); + invalid_chunk[1] = STBI__BYTECAST(c.type >> 16); + invalid_chunk[2] = STBI__BYTECAST(c.type >> 8); + invalid_chunk[3] = STBI__BYTECAST(c.type >> 0); + #endif + return stbi__err(invalid_chunk, "PNG not supported: unknown PNG chunk type"); + } + stbi__skip(s, c.length); + break; + } + // end of PNG chunk, read and skip CRC + stbi__get32be(s); + } +} + +static void *stbi__do_png(stbi__png *p, int *x, int *y, int *n, int req_comp, stbi__result_info *ri) +{ + void *result=NULL; + if (req_comp < 0 || req_comp > 4) return stbi__errpuc("bad req_comp", "Internal error"); + if (stbi__parse_png_file(p, STBI__SCAN_load, req_comp)) { + if (p->depth <= 8) + ri->bits_per_channel = 8; + else if (p->depth == 16) + ri->bits_per_channel = 16; + else + return stbi__errpuc("bad bits_per_channel", "PNG not supported: unsupported color depth"); + result = p->out; + p->out = NULL; + if (req_comp && req_comp != p->s->img_out_n) { + if (ri->bits_per_channel == 8) + result = stbi__convert_format((unsigned char *) result, p->s->img_out_n, req_comp, p->s->img_x, p->s->img_y); + else + result = stbi__convert_format16((stbi__uint16 *) result, p->s->img_out_n, req_comp, p->s->img_x, p->s->img_y); + p->s->img_out_n = req_comp; + if (result == NULL) return result; + } + *x = p->s->img_x; + *y = p->s->img_y; + if (n) *n = p->s->img_n; + } + STBI_FREE(p->out); p->out = NULL; + STBI_FREE(p->expanded); p->expanded = NULL; + STBI_FREE(p->idata); p->idata = NULL; + + return result; +} + +static void *stbi__png_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri) +{ + stbi__png p; + p.s = s; + return stbi__do_png(&p, x,y,comp,req_comp, ri); +} + +static int stbi__png_test(stbi__context *s) +{ + int r; + r = stbi__check_png_header(s); + stbi__rewind(s); + return r; +} + +static int stbi__png_info_raw(stbi__png *p, int *x, int *y, int *comp) +{ + if (!stbi__parse_png_file(p, STBI__SCAN_header, 0)) { + stbi__rewind( p->s ); + return 0; + } + if (x) *x = p->s->img_x; + if (y) *y = p->s->img_y; + if (comp) *comp = p->s->img_n; + return 1; +} + +static int stbi__png_info(stbi__context *s, int *x, int *y, int *comp) +{ + stbi__png p; + p.s = s; + return stbi__png_info_raw(&p, x, y, comp); +} + +static int stbi__png_is16(stbi__context *s) +{ + stbi__png p; + p.s = s; + if (!stbi__png_info_raw(&p, NULL, NULL, NULL)) + return 0; + if (p.depth != 16) { + stbi__rewind(p.s); + return 0; + } + return 1; +} +#endif + +// Microsoft/Windows BMP image + +#ifndef STBI_NO_BMP +static int stbi__bmp_test_raw(stbi__context *s) +{ + int r; + int sz; + if (stbi__get8(s) != 'B') return 0; + if (stbi__get8(s) != 'M') return 0; + stbi__get32le(s); // discard filesize + stbi__get16le(s); // discard reserved + stbi__get16le(s); // discard reserved + stbi__get32le(s); // discard data offset + sz = stbi__get32le(s); + r = (sz == 12 || sz == 40 || sz == 56 || sz == 108 || sz == 124); + return r; +} + +static int stbi__bmp_test(stbi__context *s) +{ + int r = stbi__bmp_test_raw(s); + stbi__rewind(s); + return r; +} + + +// returns 0..31 for the highest set bit +static int stbi__high_bit(unsigned int z) +{ + int n=0; + if (z == 0) return -1; + if (z >= 0x10000) { n += 16; z >>= 16; } + if (z >= 0x00100) { n += 8; z >>= 8; } + if (z >= 0x00010) { n += 4; z >>= 4; } + if (z >= 0x00004) { n += 2; z >>= 2; } + if (z >= 0x00002) { n += 1;/* >>= 1;*/ } + return n; +} + +static int stbi__bitcount(unsigned int a) +{ + a = (a & 0x55555555) + ((a >> 1) & 0x55555555); // max 2 + a = (a & 0x33333333) + ((a >> 2) & 0x33333333); // max 4 + a = (a + (a >> 4)) & 0x0f0f0f0f; // max 8 per 4, now 8 bits + a = (a + (a >> 8)); // max 16 per 8 bits + a = (a + (a >> 16)); // max 32 per 8 bits + return a & 0xff; +} + +// extract an arbitrarily-aligned N-bit value (N=bits) +// from v, and then make it 8-bits long and fractionally +// extend it to full full range. +static int stbi__shiftsigned(unsigned int v, int shift, int bits) +{ + static unsigned int mul_table[9] = { + 0, + 0xff/*0b11111111*/, 0x55/*0b01010101*/, 0x49/*0b01001001*/, 0x11/*0b00010001*/, + 0x21/*0b00100001*/, 0x41/*0b01000001*/, 0x81/*0b10000001*/, 0x01/*0b00000001*/, + }; + static unsigned int shift_table[9] = { + 0, 0,0,1,0,2,4,6,0, + }; + if (shift < 0) + v <<= -shift; + else + v >>= shift; + STBI_ASSERT(v < 256); + v >>= (8-bits); + STBI_ASSERT(bits >= 0 && bits <= 8); + return (int) ((unsigned) v * mul_table[bits]) >> shift_table[bits]; +} + +typedef struct +{ + int bpp, offset, hsz; + unsigned int mr,mg,mb,ma, all_a; + int extra_read; +} stbi__bmp_data; + +static int stbi__bmp_set_mask_defaults(stbi__bmp_data *info, int compress) +{ + // BI_BITFIELDS specifies masks explicitly, don't override + if (compress == 3) + return 1; + + if (compress == 0) { + if (info->bpp == 16) { + info->mr = 31u << 10; + info->mg = 31u << 5; + info->mb = 31u << 0; + } else if (info->bpp == 32) { + info->mr = 0xffu << 16; + info->mg = 0xffu << 8; + info->mb = 0xffu << 0; + info->ma = 0xffu << 24; + info->all_a = 0; // if all_a is 0 at end, then we loaded alpha channel but it was all 0 + } else { + // otherwise, use defaults, which is all-0 + info->mr = info->mg = info->mb = info->ma = 0; + } + return 1; + } + return 0; // error +} + +static void *stbi__bmp_parse_header(stbi__context *s, stbi__bmp_data *info) +{ + int hsz; + if (stbi__get8(s) != 'B' || stbi__get8(s) != 'M') return stbi__errpuc("not BMP", "Corrupt BMP"); + stbi__get32le(s); // discard filesize + stbi__get16le(s); // discard reserved + stbi__get16le(s); // discard reserved + info->offset = stbi__get32le(s); + info->hsz = hsz = stbi__get32le(s); + info->mr = info->mg = info->mb = info->ma = 0; + info->extra_read = 14; + + if (info->offset < 0) return stbi__errpuc("bad BMP", "bad BMP"); + + if (hsz != 12 && hsz != 40 && hsz != 56 && hsz != 108 && hsz != 124) return stbi__errpuc("unknown BMP", "BMP type not supported: unknown"); + if (hsz == 12) { + s->img_x = stbi__get16le(s); + s->img_y = stbi__get16le(s); + } else { + s->img_x = stbi__get32le(s); + s->img_y = stbi__get32le(s); + } + if (stbi__get16le(s) != 1) return stbi__errpuc("bad BMP", "bad BMP"); + info->bpp = stbi__get16le(s); + if (hsz != 12) { + int compress = stbi__get32le(s); + if (compress == 1 || compress == 2) return stbi__errpuc("BMP RLE", "BMP type not supported: RLE"); + if (compress >= 4) return stbi__errpuc("BMP JPEG/PNG", "BMP type not supported: unsupported compression"); // this includes PNG/JPEG modes + if (compress == 3 && info->bpp != 16 && info->bpp != 32) return stbi__errpuc("bad BMP", "bad BMP"); // bitfields requires 16 or 32 bits/pixel + stbi__get32le(s); // discard sizeof + stbi__get32le(s); // discard hres + stbi__get32le(s); // discard vres + stbi__get32le(s); // discard colorsused + stbi__get32le(s); // discard max important + if (hsz == 40 || hsz == 56) { + if (hsz == 56) { + stbi__get32le(s); + stbi__get32le(s); + stbi__get32le(s); + stbi__get32le(s); + } + if (info->bpp == 16 || info->bpp == 32) { + if (compress == 0) { + stbi__bmp_set_mask_defaults(info, compress); + } else if (compress == 3) { + info->mr = stbi__get32le(s); + info->mg = stbi__get32le(s); + info->mb = stbi__get32le(s); + info->extra_read += 12; + // not documented, but generated by photoshop and handled by mspaint + if (info->mr == info->mg && info->mg == info->mb) { + // ?!?!? + return stbi__errpuc("bad BMP", "bad BMP"); + } + } else + return stbi__errpuc("bad BMP", "bad BMP"); + } + } else { + // V4/V5 header + int i; + if (hsz != 108 && hsz != 124) + return stbi__errpuc("bad BMP", "bad BMP"); + info->mr = stbi__get32le(s); + info->mg = stbi__get32le(s); + info->mb = stbi__get32le(s); + info->ma = stbi__get32le(s); + if (compress != 3) // override mr/mg/mb unless in BI_BITFIELDS mode, as per docs + stbi__bmp_set_mask_defaults(info, compress); + stbi__get32le(s); // discard color space + for (i=0; i < 12; ++i) + stbi__get32le(s); // discard color space parameters + if (hsz == 124) { + stbi__get32le(s); // discard rendering intent + stbi__get32le(s); // discard offset of profile data + stbi__get32le(s); // discard size of profile data + stbi__get32le(s); // discard reserved + } + } + } + return (void *) 1; +} + + +static void *stbi__bmp_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri) +{ + stbi_uc *out; + unsigned int mr=0,mg=0,mb=0,ma=0, all_a; + stbi_uc pal[256][4]; + int psize=0,i,j,width; + int flip_vertically, pad, target; + stbi__bmp_data info; + STBI_NOTUSED(ri); + + info.all_a = 255; + if (stbi__bmp_parse_header(s, &info) == NULL) + return NULL; // error code already set + + flip_vertically = ((int) s->img_y) > 0; + s->img_y = abs((int) s->img_y); + + if (s->img_y > STBI_MAX_DIMENSIONS) return stbi__errpuc("too large","Very large image (corrupt?)"); + if (s->img_x > STBI_MAX_DIMENSIONS) return stbi__errpuc("too large","Very large image (corrupt?)"); + + mr = info.mr; + mg = info.mg; + mb = info.mb; + ma = info.ma; + all_a = info.all_a; + + if (info.hsz == 12) { + if (info.bpp < 24) + psize = (info.offset - info.extra_read - 24) / 3; + } else { + if (info.bpp < 16) + psize = (info.offset - info.extra_read - info.hsz) >> 2; + } + if (psize == 0) { + // accept some number of extra bytes after the header, but if the offset points either to before + // the header ends or implies a large amount of extra data, reject the file as malformed + int bytes_read_so_far = s->callback_already_read + (int)(s->img_buffer - s->img_buffer_original); + int header_limit = 1024; // max we actually read is below 256 bytes currently. + int extra_data_limit = 256*4; // what ordinarily goes here is a palette; 256 entries*4 bytes is its max size. + if (bytes_read_so_far <= 0 || bytes_read_so_far > header_limit) { + return stbi__errpuc("bad header", "Corrupt BMP"); + } + // we established that bytes_read_so_far is positive and sensible. + // the first half of this test rejects offsets that are either too small positives, or + // negative, and guarantees that info.offset >= bytes_read_so_far > 0. this in turn + // ensures the number computed in the second half of the test can't overflow. + if (info.offset < bytes_read_so_far || info.offset - bytes_read_so_far > extra_data_limit) { + return stbi__errpuc("bad offset", "Corrupt BMP"); + } else { + stbi__skip(s, info.offset - bytes_read_so_far); + } + } + + if (info.bpp == 24 && ma == 0xff000000) + s->img_n = 3; + else + s->img_n = ma ? 4 : 3; + if (req_comp && req_comp >= 3) // we can directly decode 3 or 4 + target = req_comp; + else + target = s->img_n; // if they want monochrome, we'll post-convert + + // sanity-check size + if (!stbi__mad3sizes_valid(target, s->img_x, s->img_y, 0)) + return stbi__errpuc("too large", "Corrupt BMP"); + + out = (stbi_uc *) stbi__malloc_mad3(target, s->img_x, s->img_y, 0); + if (!out) return stbi__errpuc("outofmem", "Out of memory"); + if (info.bpp < 16) { + int z=0; + if (psize == 0 || psize > 256) { STBI_FREE(out); return stbi__errpuc("invalid", "Corrupt BMP"); } + for (i=0; i < psize; ++i) { + pal[i][2] = stbi__get8(s); + pal[i][1] = stbi__get8(s); + pal[i][0] = stbi__get8(s); + if (info.hsz != 12) stbi__get8(s); + pal[i][3] = 255; + } + stbi__skip(s, info.offset - info.extra_read - info.hsz - psize * (info.hsz == 12 ? 3 : 4)); + if (info.bpp == 1) width = (s->img_x + 7) >> 3; + else if (info.bpp == 4) width = (s->img_x + 1) >> 1; + else if (info.bpp == 8) width = s->img_x; + else { STBI_FREE(out); return stbi__errpuc("bad bpp", "Corrupt BMP"); } + pad = (-width)&3; + if (info.bpp == 1) { + for (j=0; j < (int) s->img_y; ++j) { + int bit_offset = 7, v = stbi__get8(s); + for (i=0; i < (int) s->img_x; ++i) { + int color = (v>>bit_offset)&0x1; + out[z++] = pal[color][0]; + out[z++] = pal[color][1]; + out[z++] = pal[color][2]; + if (target == 4) out[z++] = 255; + if (i+1 == (int) s->img_x) break; + if((--bit_offset) < 0) { + bit_offset = 7; + v = stbi__get8(s); + } + } + stbi__skip(s, pad); + } + } else { + for (j=0; j < (int) s->img_y; ++j) { + for (i=0; i < (int) s->img_x; i += 2) { + int v=stbi__get8(s),v2=0; + if (info.bpp == 4) { + v2 = v & 15; + v >>= 4; + } + out[z++] = pal[v][0]; + out[z++] = pal[v][1]; + out[z++] = pal[v][2]; + if (target == 4) out[z++] = 255; + if (i+1 == (int) s->img_x) break; + v = (info.bpp == 8) ? stbi__get8(s) : v2; + out[z++] = pal[v][0]; + out[z++] = pal[v][1]; + out[z++] = pal[v][2]; + if (target == 4) out[z++] = 255; + } + stbi__skip(s, pad); + } + } + } else { + int rshift=0,gshift=0,bshift=0,ashift=0,rcount=0,gcount=0,bcount=0,acount=0; + int z = 0; + int easy=0; + stbi__skip(s, info.offset - info.extra_read - info.hsz); + if (info.bpp == 24) width = 3 * s->img_x; + else if (info.bpp == 16) width = 2*s->img_x; + else /* bpp = 32 and pad = 0 */ width=0; + pad = (-width) & 3; + if (info.bpp == 24) { + easy = 1; + } else if (info.bpp == 32) { + if (mb == 0xff && mg == 0xff00 && mr == 0x00ff0000 && ma == 0xff000000) + easy = 2; + } + if (!easy) { + if (!mr || !mg || !mb) { STBI_FREE(out); return stbi__errpuc("bad masks", "Corrupt BMP"); } + // right shift amt to put high bit in position #7 + rshift = stbi__high_bit(mr)-7; rcount = stbi__bitcount(mr); + gshift = stbi__high_bit(mg)-7; gcount = stbi__bitcount(mg); + bshift = stbi__high_bit(mb)-7; bcount = stbi__bitcount(mb); + ashift = stbi__high_bit(ma)-7; acount = stbi__bitcount(ma); + if (rcount > 8 || gcount > 8 || bcount > 8 || acount > 8) { STBI_FREE(out); return stbi__errpuc("bad masks", "Corrupt BMP"); } + } + for (j=0; j < (int) s->img_y; ++j) { + if (easy) { + for (i=0; i < (int) s->img_x; ++i) { + unsigned char a; + out[z+2] = stbi__get8(s); + out[z+1] = stbi__get8(s); + out[z+0] = stbi__get8(s); + z += 3; + a = (easy == 2 ? stbi__get8(s) : 255); + all_a |= a; + if (target == 4) out[z++] = a; + } + } else { + int bpp = info.bpp; + for (i=0; i < (int) s->img_x; ++i) { + stbi__uint32 v = (bpp == 16 ? (stbi__uint32) stbi__get16le(s) : stbi__get32le(s)); + unsigned int a; + out[z++] = STBI__BYTECAST(stbi__shiftsigned(v & mr, rshift, rcount)); + out[z++] = STBI__BYTECAST(stbi__shiftsigned(v & mg, gshift, gcount)); + out[z++] = STBI__BYTECAST(stbi__shiftsigned(v & mb, bshift, bcount)); + a = (ma ? stbi__shiftsigned(v & ma, ashift, acount) : 255); + all_a |= a; + if (target == 4) out[z++] = STBI__BYTECAST(a); + } + } + stbi__skip(s, pad); + } + } + + // if alpha channel is all 0s, replace with all 255s + if (target == 4 && all_a == 0) + for (i=4*s->img_x*s->img_y-1; i >= 0; i -= 4) + out[i] = 255; + + if (flip_vertically) { + stbi_uc t; + for (j=0; j < (int) s->img_y>>1; ++j) { + stbi_uc *p1 = out + j *s->img_x*target; + stbi_uc *p2 = out + (s->img_y-1-j)*s->img_x*target; + for (i=0; i < (int) s->img_x*target; ++i) { + t = p1[i]; p1[i] = p2[i]; p2[i] = t; + } + } + } + + if (req_comp && req_comp != target) { + out = stbi__convert_format(out, target, req_comp, s->img_x, s->img_y); + if (out == NULL) return out; // stbi__convert_format frees input on failure + } + + *x = s->img_x; + *y = s->img_y; + if (comp) *comp = s->img_n; + return out; +} +#endif + +// Targa Truevision - TGA +// by Jonathan Dummer +#ifndef STBI_NO_TGA +// returns STBI_rgb or whatever, 0 on error +static int stbi__tga_get_comp(int bits_per_pixel, int is_grey, int* is_rgb16) +{ + // only RGB or RGBA (incl. 16bit) or grey allowed + if (is_rgb16) *is_rgb16 = 0; + switch(bits_per_pixel) { + case 8: return STBI_grey; + case 16: if(is_grey) return STBI_grey_alpha; + // fallthrough + case 15: if(is_rgb16) *is_rgb16 = 1; + return STBI_rgb; + case 24: // fallthrough + case 32: return bits_per_pixel/8; + default: return 0; + } +} + +static int stbi__tga_info(stbi__context *s, int *x, int *y, int *comp) +{ + int tga_w, tga_h, tga_comp, tga_image_type, tga_bits_per_pixel, tga_colormap_bpp; + int sz, tga_colormap_type; + stbi__get8(s); // discard Offset + tga_colormap_type = stbi__get8(s); // colormap type + if( tga_colormap_type > 1 ) { + stbi__rewind(s); + return 0; // only RGB or indexed allowed + } + tga_image_type = stbi__get8(s); // image type + if ( tga_colormap_type == 1 ) { // colormapped (paletted) image + if (tga_image_type != 1 && tga_image_type != 9) { + stbi__rewind(s); + return 0; + } + stbi__skip(s,4); // skip index of first colormap entry and number of entries + sz = stbi__get8(s); // check bits per palette color entry + if ( (sz != 8) && (sz != 15) && (sz != 16) && (sz != 24) && (sz != 32) ) { + stbi__rewind(s); + return 0; + } + stbi__skip(s,4); // skip image x and y origin + tga_colormap_bpp = sz; + } else { // "normal" image w/o colormap - only RGB or grey allowed, +/- RLE + if ( (tga_image_type != 2) && (tga_image_type != 3) && (tga_image_type != 10) && (tga_image_type != 11) ) { + stbi__rewind(s); + return 0; // only RGB or grey allowed, +/- RLE + } + stbi__skip(s,9); // skip colormap specification and image x/y origin + tga_colormap_bpp = 0; + } + tga_w = stbi__get16le(s); + if( tga_w < 1 ) { + stbi__rewind(s); + return 0; // test width + } + tga_h = stbi__get16le(s); + if( tga_h < 1 ) { + stbi__rewind(s); + return 0; // test height + } + tga_bits_per_pixel = stbi__get8(s); // bits per pixel + stbi__get8(s); // ignore alpha bits + if (tga_colormap_bpp != 0) { + if((tga_bits_per_pixel != 8) && (tga_bits_per_pixel != 16)) { + // when using a colormap, tga_bits_per_pixel is the size of the indexes + // I don't think anything but 8 or 16bit indexes makes sense + stbi__rewind(s); + return 0; + } + tga_comp = stbi__tga_get_comp(tga_colormap_bpp, 0, NULL); + } else { + tga_comp = stbi__tga_get_comp(tga_bits_per_pixel, (tga_image_type == 3) || (tga_image_type == 11), NULL); + } + if(!tga_comp) { + stbi__rewind(s); + return 0; + } + if (x) *x = tga_w; + if (y) *y = tga_h; + if (comp) *comp = tga_comp; + return 1; // seems to have passed everything +} + +static int stbi__tga_test(stbi__context *s) +{ + int res = 0; + int sz, tga_color_type; + stbi__get8(s); // discard Offset + tga_color_type = stbi__get8(s); // color type + if ( tga_color_type > 1 ) goto errorEnd; // only RGB or indexed allowed + sz = stbi__get8(s); // image type + if ( tga_color_type == 1 ) { // colormapped (paletted) image + if (sz != 1 && sz != 9) goto errorEnd; // colortype 1 demands image type 1 or 9 + stbi__skip(s,4); // skip index of first colormap entry and number of entries + sz = stbi__get8(s); // check bits per palette color entry + if ( (sz != 8) && (sz != 15) && (sz != 16) && (sz != 24) && (sz != 32) ) goto errorEnd; + stbi__skip(s,4); // skip image x and y origin + } else { // "normal" image w/o colormap + if ( (sz != 2) && (sz != 3) && (sz != 10) && (sz != 11) ) goto errorEnd; // only RGB or grey allowed, +/- RLE + stbi__skip(s,9); // skip colormap specification and image x/y origin + } + if ( stbi__get16le(s) < 1 ) goto errorEnd; // test width + if ( stbi__get16le(s) < 1 ) goto errorEnd; // test height + sz = stbi__get8(s); // bits per pixel + if ( (tga_color_type == 1) && (sz != 8) && (sz != 16) ) goto errorEnd; // for colormapped images, bpp is size of an index + if ( (sz != 8) && (sz != 15) && (sz != 16) && (sz != 24) && (sz != 32) ) goto errorEnd; + + res = 1; // if we got this far, everything's good and we can return 1 instead of 0 + +errorEnd: + stbi__rewind(s); + return res; +} + +// read 16bit value and convert to 24bit RGB +static void stbi__tga_read_rgb16(stbi__context *s, stbi_uc* out) +{ + stbi__uint16 px = (stbi__uint16)stbi__get16le(s); + stbi__uint16 fiveBitMask = 31; + // we have 3 channels with 5bits each + int r = (px >> 10) & fiveBitMask; + int g = (px >> 5) & fiveBitMask; + int b = px & fiveBitMask; + // Note that this saves the data in RGB(A) order, so it doesn't need to be swapped later + out[0] = (stbi_uc)((r * 255)/31); + out[1] = (stbi_uc)((g * 255)/31); + out[2] = (stbi_uc)((b * 255)/31); + + // some people claim that the most significant bit might be used for alpha + // (possibly if an alpha-bit is set in the "image descriptor byte") + // but that only made 16bit test images completely translucent.. + // so let's treat all 15 and 16bit TGAs as RGB with no alpha. +} + +static void *stbi__tga_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri) +{ + // read in the TGA header stuff + int tga_offset = stbi__get8(s); + int tga_indexed = stbi__get8(s); + int tga_image_type = stbi__get8(s); + int tga_is_RLE = 0; + int tga_palette_start = stbi__get16le(s); + int tga_palette_len = stbi__get16le(s); + int tga_palette_bits = stbi__get8(s); + int tga_x_origin = stbi__get16le(s); + int tga_y_origin = stbi__get16le(s); + int tga_width = stbi__get16le(s); + int tga_height = stbi__get16le(s); + int tga_bits_per_pixel = stbi__get8(s); + int tga_comp, tga_rgb16=0; + int tga_inverted = stbi__get8(s); + // int tga_alpha_bits = tga_inverted & 15; // the 4 lowest bits - unused (useless?) + // image data + unsigned char *tga_data; + unsigned char *tga_palette = NULL; + int i, j; + unsigned char raw_data[4] = {0}; + int RLE_count = 0; + int RLE_repeating = 0; + int read_next_pixel = 1; + STBI_NOTUSED(ri); + STBI_NOTUSED(tga_x_origin); // @TODO + STBI_NOTUSED(tga_y_origin); // @TODO + + if (tga_height > STBI_MAX_DIMENSIONS) return stbi__errpuc("too large","Very large image (corrupt?)"); + if (tga_width > STBI_MAX_DIMENSIONS) return stbi__errpuc("too large","Very large image (corrupt?)"); + + // do a tiny bit of precessing + if ( tga_image_type >= 8 ) + { + tga_image_type -= 8; + tga_is_RLE = 1; + } + tga_inverted = 1 - ((tga_inverted >> 5) & 1); + + // If I'm paletted, then I'll use the number of bits from the palette + if ( tga_indexed ) tga_comp = stbi__tga_get_comp(tga_palette_bits, 0, &tga_rgb16); + else tga_comp = stbi__tga_get_comp(tga_bits_per_pixel, (tga_image_type == 3), &tga_rgb16); + + if(!tga_comp) // shouldn't really happen, stbi__tga_test() should have ensured basic consistency + return stbi__errpuc("bad format", "Can't find out TGA pixelformat"); + + // tga info + *x = tga_width; + *y = tga_height; + if (comp) *comp = tga_comp; + + if (!stbi__mad3sizes_valid(tga_width, tga_height, tga_comp, 0)) + return stbi__errpuc("too large", "Corrupt TGA"); + + tga_data = (unsigned char*)stbi__malloc_mad3(tga_width, tga_height, tga_comp, 0); + if (!tga_data) return stbi__errpuc("outofmem", "Out of memory"); + + // skip to the data's starting position (offset usually = 0) + stbi__skip(s, tga_offset ); + + if ( !tga_indexed && !tga_is_RLE && !tga_rgb16 ) { + for (i=0; i < tga_height; ++i) { + int row = tga_inverted ? tga_height -i - 1 : i; + stbi_uc *tga_row = tga_data + row*tga_width*tga_comp; + stbi__getn(s, tga_row, tga_width * tga_comp); + } + } else { + // do I need to load a palette? + if ( tga_indexed) + { + if (tga_palette_len == 0) { /* you have to have at least one entry! */ + STBI_FREE(tga_data); + return stbi__errpuc("bad palette", "Corrupt TGA"); + } + + // any data to skip? (offset usually = 0) + stbi__skip(s, tga_palette_start ); + // load the palette + tga_palette = (unsigned char*)stbi__malloc_mad2(tga_palette_len, tga_comp, 0); + if (!tga_palette) { + STBI_FREE(tga_data); + return stbi__errpuc("outofmem", "Out of memory"); + } + if (tga_rgb16) { + stbi_uc *pal_entry = tga_palette; + STBI_ASSERT(tga_comp == STBI_rgb); + for (i=0; i < tga_palette_len; ++i) { + stbi__tga_read_rgb16(s, pal_entry); + pal_entry += tga_comp; + } + } else if (!stbi__getn(s, tga_palette, tga_palette_len * tga_comp)) { + STBI_FREE(tga_data); + STBI_FREE(tga_palette); + return stbi__errpuc("bad palette", "Corrupt TGA"); + } + } + // load the data + for (i=0; i < tga_width * tga_height; ++i) + { + // if I'm in RLE mode, do I need to get a RLE stbi__pngchunk? + if ( tga_is_RLE ) + { + if ( RLE_count == 0 ) + { + // yep, get the next byte as a RLE command + int RLE_cmd = stbi__get8(s); + RLE_count = 1 + (RLE_cmd & 127); + RLE_repeating = RLE_cmd >> 7; + read_next_pixel = 1; + } else if ( !RLE_repeating ) + { + read_next_pixel = 1; + } + } else + { + read_next_pixel = 1; + } + // OK, if I need to read a pixel, do it now + if ( read_next_pixel ) + { + // load however much data we did have + if ( tga_indexed ) + { + // read in index, then perform the lookup + int pal_idx = (tga_bits_per_pixel == 8) ? stbi__get8(s) : stbi__get16le(s); + if ( pal_idx >= tga_palette_len ) { + // invalid index + pal_idx = 0; + } + pal_idx *= tga_comp; + for (j = 0; j < tga_comp; ++j) { + raw_data[j] = tga_palette[pal_idx+j]; + } + } else if(tga_rgb16) { + STBI_ASSERT(tga_comp == STBI_rgb); + stbi__tga_read_rgb16(s, raw_data); + } else { + // read in the data raw + for (j = 0; j < tga_comp; ++j) { + raw_data[j] = stbi__get8(s); + } + } + // clear the reading flag for the next pixel + read_next_pixel = 0; + } // end of reading a pixel + + // copy data + for (j = 0; j < tga_comp; ++j) + tga_data[i*tga_comp+j] = raw_data[j]; + + // in case we're in RLE mode, keep counting down + --RLE_count; + } + // do I need to invert the image? + if ( tga_inverted ) + { + for (j = 0; j*2 < tga_height; ++j) + { + int index1 = j * tga_width * tga_comp; + int index2 = (tga_height - 1 - j) * tga_width * tga_comp; + for (i = tga_width * tga_comp; i > 0; --i) + { + unsigned char temp = tga_data[index1]; + tga_data[index1] = tga_data[index2]; + tga_data[index2] = temp; + ++index1; + ++index2; + } + } + } + // clear my palette, if I had one + if ( tga_palette != NULL ) + { + STBI_FREE( tga_palette ); + } + } + + // swap RGB - if the source data was RGB16, it already is in the right order + if (tga_comp >= 3 && !tga_rgb16) + { + unsigned char* tga_pixel = tga_data; + for (i=0; i < tga_width * tga_height; ++i) + { + unsigned char temp = tga_pixel[0]; + tga_pixel[0] = tga_pixel[2]; + tga_pixel[2] = temp; + tga_pixel += tga_comp; + } + } + + // convert to target component count + if (req_comp && req_comp != tga_comp) + tga_data = stbi__convert_format(tga_data, tga_comp, req_comp, tga_width, tga_height); + + // the things I do to get rid of an error message, and yet keep + // Microsoft's C compilers happy... [8^( + tga_palette_start = tga_palette_len = tga_palette_bits = + tga_x_origin = tga_y_origin = 0; + STBI_NOTUSED(tga_palette_start); + // OK, done + return tga_data; +} +#endif + +// ************************************************************************************************* +// Photoshop PSD loader -- PD by Thatcher Ulrich, integration by Nicolas Schulz, tweaked by STB + +#ifndef STBI_NO_PSD +static int stbi__psd_test(stbi__context *s) +{ + int r = (stbi__get32be(s) == 0x38425053); + stbi__rewind(s); + return r; +} + +static int stbi__psd_decode_rle(stbi__context *s, stbi_uc *p, int pixelCount) +{ + int count, nleft, len; + + count = 0; + while ((nleft = pixelCount - count) > 0) { + len = stbi__get8(s); + if (len == 128) { + // No-op. + } else if (len < 128) { + // Copy next len+1 bytes literally. + len++; + if (len > nleft) return 0; // corrupt data + count += len; + while (len) { + *p = stbi__get8(s); + p += 4; + len--; + } + } else if (len > 128) { + stbi_uc val; + // Next -len+1 bytes in the dest are replicated from next source byte. + // (Interpret len as a negative 8-bit int.) + len = 257 - len; + if (len > nleft) return 0; // corrupt data + val = stbi__get8(s); + count += len; + while (len) { + *p = val; + p += 4; + len--; + } + } + } + + return 1; +} + +static void *stbi__psd_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri, int bpc) +{ + int pixelCount; + int channelCount, compression; + int channel, i; + int bitdepth; + int w,h; + stbi_uc *out; + STBI_NOTUSED(ri); + + // Check identifier + if (stbi__get32be(s) != 0x38425053) // "8BPS" + return stbi__errpuc("not PSD", "Corrupt PSD image"); + + // Check file type version. + if (stbi__get16be(s) != 1) + return stbi__errpuc("wrong version", "Unsupported version of PSD image"); + + // Skip 6 reserved bytes. + stbi__skip(s, 6 ); + + // Read the number of channels (R, G, B, A, etc). + channelCount = stbi__get16be(s); + if (channelCount < 0 || channelCount > 16) + return stbi__errpuc("wrong channel count", "Unsupported number of channels in PSD image"); + + // Read the rows and columns of the image. + h = stbi__get32be(s); + w = stbi__get32be(s); + + if (h > STBI_MAX_DIMENSIONS) return stbi__errpuc("too large","Very large image (corrupt?)"); + if (w > STBI_MAX_DIMENSIONS) return stbi__errpuc("too large","Very large image (corrupt?)"); + + // Make sure the depth is 8 bits. + bitdepth = stbi__get16be(s); + if (bitdepth != 8 && bitdepth != 16) + return stbi__errpuc("unsupported bit depth", "PSD bit depth is not 8 or 16 bit"); + + // Make sure the color mode is RGB. + // Valid options are: + // 0: Bitmap + // 1: Grayscale + // 2: Indexed color + // 3: RGB color + // 4: CMYK color + // 7: Multichannel + // 8: Duotone + // 9: Lab color + if (stbi__get16be(s) != 3) + return stbi__errpuc("wrong color format", "PSD is not in RGB color format"); + + // Skip the Mode Data. (It's the palette for indexed color; other info for other modes.) + stbi__skip(s,stbi__get32be(s) ); + + // Skip the image resources. (resolution, pen tool paths, etc) + stbi__skip(s, stbi__get32be(s) ); + + // Skip the reserved data. + stbi__skip(s, stbi__get32be(s) ); + + // Find out if the data is compressed. + // Known values: + // 0: no compression + // 1: RLE compressed + compression = stbi__get16be(s); + if (compression > 1) + return stbi__errpuc("bad compression", "PSD has an unknown compression format"); + + // Check size + if (!stbi__mad3sizes_valid(4, w, h, 0)) + return stbi__errpuc("too large", "Corrupt PSD"); + + // Create the destination image. + + if (!compression && bitdepth == 16 && bpc == 16) { + out = (stbi_uc *) stbi__malloc_mad3(8, w, h, 0); + ri->bits_per_channel = 16; + } else + out = (stbi_uc *) stbi__malloc(4 * w*h); + + if (!out) return stbi__errpuc("outofmem", "Out of memory"); + pixelCount = w*h; + + // Initialize the data to zero. + //memset( out, 0, pixelCount * 4 ); + + // Finally, the image data. + if (compression) { + // RLE as used by .PSD and .TIFF + // Loop until you get the number of unpacked bytes you are expecting: + // Read the next source byte into n. + // If n is between 0 and 127 inclusive, copy the next n+1 bytes literally. + // Else if n is between -127 and -1 inclusive, copy the next byte -n+1 times. + // Else if n is 128, noop. + // Endloop + + // The RLE-compressed data is preceded by a 2-byte data count for each row in the data, + // which we're going to just skip. + stbi__skip(s, h * channelCount * 2 ); + + // Read the RLE data by channel. + for (channel = 0; channel < 4; channel++) { + stbi_uc *p; + + p = out+channel; + if (channel >= channelCount) { + // Fill this channel with default data. + for (i = 0; i < pixelCount; i++, p += 4) + *p = (channel == 3 ? 255 : 0); + } else { + // Read the RLE data. + if (!stbi__psd_decode_rle(s, p, pixelCount)) { + STBI_FREE(out); + return stbi__errpuc("corrupt", "bad RLE data"); + } + } + } + + } else { + // We're at the raw image data. It's each channel in order (Red, Green, Blue, Alpha, ...) + // where each channel consists of an 8-bit (or 16-bit) value for each pixel in the image. + + // Read the data by channel. + for (channel = 0; channel < 4; channel++) { + if (channel >= channelCount) { + // Fill this channel with default data. + if (bitdepth == 16 && bpc == 16) { + stbi__uint16 *q = ((stbi__uint16 *) out) + channel; + stbi__uint16 val = channel == 3 ? 65535 : 0; + for (i = 0; i < pixelCount; i++, q += 4) + *q = val; + } else { + stbi_uc *p = out+channel; + stbi_uc val = channel == 3 ? 255 : 0; + for (i = 0; i < pixelCount; i++, p += 4) + *p = val; + } + } else { + if (ri->bits_per_channel == 16) { // output bpc + stbi__uint16 *q = ((stbi__uint16 *) out) + channel; + for (i = 0; i < pixelCount; i++, q += 4) + *q = (stbi__uint16) stbi__get16be(s); + } else { + stbi_uc *p = out+channel; + if (bitdepth == 16) { // input bpc + for (i = 0; i < pixelCount; i++, p += 4) + *p = (stbi_uc) (stbi__get16be(s) >> 8); + } else { + for (i = 0; i < pixelCount; i++, p += 4) + *p = stbi__get8(s); + } + } + } + } + } + + // remove weird white matte from PSD + if (channelCount >= 4) { + if (ri->bits_per_channel == 16) { + for (i=0; i < w*h; ++i) { + stbi__uint16 *pixel = (stbi__uint16 *) out + 4*i; + if (pixel[3] != 0 && pixel[3] != 65535) { + float a = pixel[3] / 65535.0f; + float ra = 1.0f / a; + float inv_a = 65535.0f * (1 - ra); + pixel[0] = (stbi__uint16) (pixel[0]*ra + inv_a); + pixel[1] = (stbi__uint16) (pixel[1]*ra + inv_a); + pixel[2] = (stbi__uint16) (pixel[2]*ra + inv_a); + } + } + } else { + for (i=0; i < w*h; ++i) { + unsigned char *pixel = out + 4*i; + if (pixel[3] != 0 && pixel[3] != 255) { + float a = pixel[3] / 255.0f; + float ra = 1.0f / a; + float inv_a = 255.0f * (1 - ra); + pixel[0] = (unsigned char) (pixel[0]*ra + inv_a); + pixel[1] = (unsigned char) (pixel[1]*ra + inv_a); + pixel[2] = (unsigned char) (pixel[2]*ra + inv_a); + } + } + } + } + + // convert to desired output format + if (req_comp && req_comp != 4) { + if (ri->bits_per_channel == 16) + out = (stbi_uc *) stbi__convert_format16((stbi__uint16 *) out, 4, req_comp, w, h); + else + out = stbi__convert_format(out, 4, req_comp, w, h); + if (out == NULL) return out; // stbi__convert_format frees input on failure + } + + if (comp) *comp = 4; + *y = h; + *x = w; + + return out; +} +#endif + +// ************************************************************************************************* +// Softimage PIC loader +// by Tom Seddon +// +// See http://softimage.wiki.softimage.com/index.php/INFO:_PIC_file_format +// See http://ozviz.wasp.uwa.edu.au/~pbourke/dataformats/softimagepic/ + +#ifndef STBI_NO_PIC +static int stbi__pic_is4(stbi__context *s,const char *str) +{ + int i; + for (i=0; i<4; ++i) + if (stbi__get8(s) != (stbi_uc)str[i]) + return 0; + + return 1; +} + +static int stbi__pic_test_core(stbi__context *s) +{ + int i; + + if (!stbi__pic_is4(s,"\x53\x80\xF6\x34")) + return 0; + + for(i=0;i<84;++i) + stbi__get8(s); + + if (!stbi__pic_is4(s,"PICT")) + return 0; + + return 1; +} + +typedef struct +{ + stbi_uc size,type,channel; +} stbi__pic_packet; + +static stbi_uc *stbi__readval(stbi__context *s, int channel, stbi_uc *dest) +{ + int mask=0x80, i; + + for (i=0; i<4; ++i, mask>>=1) { + if (channel & mask) { + if (stbi__at_eof(s)) return stbi__errpuc("bad file","PIC file too short"); + dest[i]=stbi__get8(s); + } + } + + return dest; +} + +static void stbi__copyval(int channel,stbi_uc *dest,const stbi_uc *src) +{ + int mask=0x80,i; + + for (i=0;i<4; ++i, mask>>=1) + if (channel&mask) + dest[i]=src[i]; +} + +static stbi_uc *stbi__pic_load_core(stbi__context *s,int width,int height,int *comp, stbi_uc *result) +{ + int act_comp=0,num_packets=0,y,chained; + stbi__pic_packet packets[10]; + + // this will (should...) cater for even some bizarre stuff like having data + // for the same channel in multiple packets. + do { + stbi__pic_packet *packet; + + if (num_packets==sizeof(packets)/sizeof(packets[0])) + return stbi__errpuc("bad format","too many packets"); + + packet = &packets[num_packets++]; + + chained = stbi__get8(s); + packet->size = stbi__get8(s); + packet->type = stbi__get8(s); + packet->channel = stbi__get8(s); + + act_comp |= packet->channel; + + if (stbi__at_eof(s)) return stbi__errpuc("bad file","file too short (reading packets)"); + if (packet->size != 8) return stbi__errpuc("bad format","packet isn't 8bpp"); + } while (chained); + + *comp = (act_comp & 0x10 ? 4 : 3); // has alpha channel? + + for(y=0; ytype) { + default: + return stbi__errpuc("bad format","packet has bad compression type"); + + case 0: {//uncompressed + int x; + + for(x=0;xchannel,dest)) + return 0; + break; + } + + case 1://Pure RLE + { + int left=width, i; + + while (left>0) { + stbi_uc count,value[4]; + + count=stbi__get8(s); + if (stbi__at_eof(s)) return stbi__errpuc("bad file","file too short (pure read count)"); + + if (count > left) + count = (stbi_uc) left; + + if (!stbi__readval(s,packet->channel,value)) return 0; + + for(i=0; ichannel,dest,value); + left -= count; + } + } + break; + + case 2: {//Mixed RLE + int left=width; + while (left>0) { + int count = stbi__get8(s), i; + if (stbi__at_eof(s)) return stbi__errpuc("bad file","file too short (mixed read count)"); + + if (count >= 128) { // Repeated + stbi_uc value[4]; + + if (count==128) + count = stbi__get16be(s); + else + count -= 127; + if (count > left) + return stbi__errpuc("bad file","scanline overrun"); + + if (!stbi__readval(s,packet->channel,value)) + return 0; + + for(i=0;ichannel,dest,value); + } else { // Raw + ++count; + if (count>left) return stbi__errpuc("bad file","scanline overrun"); + + for(i=0;ichannel,dest)) + return 0; + } + left-=count; + } + break; + } + } + } + } + + return result; +} + +static void *stbi__pic_load(stbi__context *s,int *px,int *py,int *comp,int req_comp, stbi__result_info *ri) +{ + stbi_uc *result; + int i, x,y, internal_comp; + STBI_NOTUSED(ri); + + if (!comp) comp = &internal_comp; + + for (i=0; i<92; ++i) + stbi__get8(s); + + x = stbi__get16be(s); + y = stbi__get16be(s); + + if (y > STBI_MAX_DIMENSIONS) return stbi__errpuc("too large","Very large image (corrupt?)"); + if (x > STBI_MAX_DIMENSIONS) return stbi__errpuc("too large","Very large image (corrupt?)"); + + if (stbi__at_eof(s)) return stbi__errpuc("bad file","file too short (pic header)"); + if (!stbi__mad3sizes_valid(x, y, 4, 0)) return stbi__errpuc("too large", "PIC image too large to decode"); + + stbi__get32be(s); //skip `ratio' + stbi__get16be(s); //skip `fields' + stbi__get16be(s); //skip `pad' + + // intermediate buffer is RGBA + result = (stbi_uc *) stbi__malloc_mad3(x, y, 4, 0); + if (!result) return stbi__errpuc("outofmem", "Out of memory"); + memset(result, 0xff, x*y*4); + + if (!stbi__pic_load_core(s,x,y,comp, result)) { + STBI_FREE(result); + result=0; + } + *px = x; + *py = y; + if (req_comp == 0) req_comp = *comp; + result=stbi__convert_format(result,4,req_comp,x,y); + + return result; +} + +static int stbi__pic_test(stbi__context *s) +{ + int r = stbi__pic_test_core(s); + stbi__rewind(s); + return r; +} +#endif + +// ************************************************************************************************* +// GIF loader -- public domain by Jean-Marc Lienher -- simplified/shrunk by stb + +#ifndef STBI_NO_GIF +typedef struct +{ + stbi__int16 prefix; + stbi_uc first; + stbi_uc suffix; +} stbi__gif_lzw; + +typedef struct +{ + int w,h; + stbi_uc *out; // output buffer (always 4 components) + stbi_uc *background; // The current "background" as far as a gif is concerned + stbi_uc *history; + int flags, bgindex, ratio, transparent, eflags; + stbi_uc pal[256][4]; + stbi_uc lpal[256][4]; + stbi__gif_lzw codes[8192]; + stbi_uc *color_table; + int parse, step; + int lflags; + int start_x, start_y; + int max_x, max_y; + int cur_x, cur_y; + int line_size; + int delay; +} stbi__gif; + +static int stbi__gif_test_raw(stbi__context *s) +{ + int sz; + if (stbi__get8(s) != 'G' || stbi__get8(s) != 'I' || stbi__get8(s) != 'F' || stbi__get8(s) != '8') return 0; + sz = stbi__get8(s); + if (sz != '9' && sz != '7') return 0; + if (stbi__get8(s) != 'a') return 0; + return 1; +} + +static int stbi__gif_test(stbi__context *s) +{ + int r = stbi__gif_test_raw(s); + stbi__rewind(s); + return r; +} + +static void stbi__gif_parse_colortable(stbi__context *s, stbi_uc pal[256][4], int num_entries, int transp) +{ + int i; + for (i=0; i < num_entries; ++i) { + pal[i][2] = stbi__get8(s); + pal[i][1] = stbi__get8(s); + pal[i][0] = stbi__get8(s); + pal[i][3] = transp == i ? 0 : 255; + } +} + +static int stbi__gif_header(stbi__context *s, stbi__gif *g, int *comp, int is_info) +{ + stbi_uc version; + if (stbi__get8(s) != 'G' || stbi__get8(s) != 'I' || stbi__get8(s) != 'F' || stbi__get8(s) != '8') + return stbi__err("not GIF", "Corrupt GIF"); + + version = stbi__get8(s); + if (version != '7' && version != '9') return stbi__err("not GIF", "Corrupt GIF"); + if (stbi__get8(s) != 'a') return stbi__err("not GIF", "Corrupt GIF"); + + stbi__g_failure_reason = ""; + g->w = stbi__get16le(s); + g->h = stbi__get16le(s); + g->flags = stbi__get8(s); + g->bgindex = stbi__get8(s); + g->ratio = stbi__get8(s); + g->transparent = -1; + + if (g->w > STBI_MAX_DIMENSIONS) return stbi__err("too large","Very large image (corrupt?)"); + if (g->h > STBI_MAX_DIMENSIONS) return stbi__err("too large","Very large image (corrupt?)"); + + if (comp != 0) *comp = 4; // can't actually tell whether it's 3 or 4 until we parse the comments + + if (is_info) return 1; + + if (g->flags & 0x80) + stbi__gif_parse_colortable(s,g->pal, 2 << (g->flags & 7), -1); + + return 1; +} + +static int stbi__gif_info_raw(stbi__context *s, int *x, int *y, int *comp) +{ + stbi__gif* g = (stbi__gif*) stbi__malloc(sizeof(stbi__gif)); + if (!g) return stbi__err("outofmem", "Out of memory"); + if (!stbi__gif_header(s, g, comp, 1)) { + STBI_FREE(g); + stbi__rewind( s ); + return 0; + } + if (x) *x = g->w; + if (y) *y = g->h; + STBI_FREE(g); + return 1; +} + +static void stbi__out_gif_code(stbi__gif *g, stbi__uint16 code) +{ + stbi_uc *p, *c; + int idx; + + // recurse to decode the prefixes, since the linked-list is backwards, + // and working backwards through an interleaved image would be nasty + if (g->codes[code].prefix >= 0) + stbi__out_gif_code(g, g->codes[code].prefix); + + if (g->cur_y >= g->max_y) return; + + idx = g->cur_x + g->cur_y; + p = &g->out[idx]; + g->history[idx / 4] = 1; + + c = &g->color_table[g->codes[code].suffix * 4]; + if (c[3] > 128) { // don't render transparent pixels; + p[0] = c[2]; + p[1] = c[1]; + p[2] = c[0]; + p[3] = c[3]; + } + g->cur_x += 4; + + if (g->cur_x >= g->max_x) { + g->cur_x = g->start_x; + g->cur_y += g->step; + + while (g->cur_y >= g->max_y && g->parse > 0) { + g->step = (1 << g->parse) * g->line_size; + g->cur_y = g->start_y + (g->step >> 1); + --g->parse; + } + } +} + +static stbi_uc *stbi__process_gif_raster(stbi__context *s, stbi__gif *g) +{ + stbi_uc lzw_cs; + stbi__int32 len, init_code; + stbi__uint32 first; + stbi__int32 codesize, codemask, avail, oldcode, bits, valid_bits, clear; + stbi__gif_lzw *p; + + lzw_cs = stbi__get8(s); + if (lzw_cs > 12) return NULL; + clear = 1 << lzw_cs; + first = 1; + codesize = lzw_cs + 1; + codemask = (1 << codesize) - 1; + bits = 0; + valid_bits = 0; + for (init_code = 0; init_code < clear; init_code++) { + g->codes[init_code].prefix = -1; + g->codes[init_code].first = (stbi_uc) init_code; + g->codes[init_code].suffix = (stbi_uc) init_code; + } + + // support no starting clear code + avail = clear+2; + oldcode = -1; + + len = 0; + for(;;) { + if (valid_bits < codesize) { + if (len == 0) { + len = stbi__get8(s); // start new block + if (len == 0) + return g->out; + } + --len; + bits |= (stbi__int32) stbi__get8(s) << valid_bits; + valid_bits += 8; + } else { + stbi__int32 code = bits & codemask; + bits >>= codesize; + valid_bits -= codesize; + // @OPTIMIZE: is there some way we can accelerate the non-clear path? + if (code == clear) { // clear code + codesize = lzw_cs + 1; + codemask = (1 << codesize) - 1; + avail = clear + 2; + oldcode = -1; + first = 0; + } else if (code == clear + 1) { // end of stream code + stbi__skip(s, len); + while ((len = stbi__get8(s)) > 0) + stbi__skip(s,len); + return g->out; + } else if (code <= avail) { + if (first) { + return stbi__errpuc("no clear code", "Corrupt GIF"); + } + + if (oldcode >= 0) { + p = &g->codes[avail++]; + if (avail > 8192) { + return stbi__errpuc("too many codes", "Corrupt GIF"); + } + + p->prefix = (stbi__int16) oldcode; + p->first = g->codes[oldcode].first; + p->suffix = (code == avail) ? p->first : g->codes[code].first; + } else if (code == avail) + return stbi__errpuc("illegal code in raster", "Corrupt GIF"); + + stbi__out_gif_code(g, (stbi__uint16) code); + + if ((avail & codemask) == 0 && avail <= 0x0FFF) { + codesize++; + codemask = (1 << codesize) - 1; + } + + oldcode = code; + } else { + return stbi__errpuc("illegal code in raster", "Corrupt GIF"); + } + } + } +} + +// this function is designed to support animated gifs, although stb_image doesn't support it +// two back is the image from two frames ago, used for a very specific disposal format +static stbi_uc *stbi__gif_load_next(stbi__context *s, stbi__gif *g, int *comp, int req_comp, stbi_uc *two_back) +{ + int dispose; + int first_frame; + int pi; + int pcount; + STBI_NOTUSED(req_comp); + + // on first frame, any non-written pixels get the background colour (non-transparent) + first_frame = 0; + if (g->out == 0) { + if (!stbi__gif_header(s, g, comp,0)) return 0; // stbi__g_failure_reason set by stbi__gif_header + if (!stbi__mad3sizes_valid(4, g->w, g->h, 0)) + return stbi__errpuc("too large", "GIF image is too large"); + pcount = g->w * g->h; + g->out = (stbi_uc *) stbi__malloc(4 * pcount); + g->background = (stbi_uc *) stbi__malloc(4 * pcount); + g->history = (stbi_uc *) stbi__malloc(pcount); + if (!g->out || !g->background || !g->history) + return stbi__errpuc("outofmem", "Out of memory"); + + // image is treated as "transparent" at the start - ie, nothing overwrites the current background; + // background colour is only used for pixels that are not rendered first frame, after that "background" + // color refers to the color that was there the previous frame. + memset(g->out, 0x00, 4 * pcount); + memset(g->background, 0x00, 4 * pcount); // state of the background (starts transparent) + memset(g->history, 0x00, pcount); // pixels that were affected previous frame + first_frame = 1; + } else { + // second frame - how do we dispose of the previous one? + dispose = (g->eflags & 0x1C) >> 2; + pcount = g->w * g->h; + + if ((dispose == 3) && (two_back == 0)) { + dispose = 2; // if I don't have an image to revert back to, default to the old background + } + + if (dispose == 3) { // use previous graphic + for (pi = 0; pi < pcount; ++pi) { + if (g->history[pi]) { + memcpy( &g->out[pi * 4], &two_back[pi * 4], 4 ); + } + } + } else if (dispose == 2) { + // restore what was changed last frame to background before that frame; + for (pi = 0; pi < pcount; ++pi) { + if (g->history[pi]) { + memcpy( &g->out[pi * 4], &g->background[pi * 4], 4 ); + } + } + } else { + // This is a non-disposal case eithe way, so just + // leave the pixels as is, and they will become the new background + // 1: do not dispose + // 0: not specified. + } + + // background is what out is after the undoing of the previou frame; + memcpy( g->background, g->out, 4 * g->w * g->h ); + } + + // clear my history; + memset( g->history, 0x00, g->w * g->h ); // pixels that were affected previous frame + + for (;;) { + int tag = stbi__get8(s); + switch (tag) { + case 0x2C: /* Image Descriptor */ + { + stbi__int32 x, y, w, h; + stbi_uc *o; + + x = stbi__get16le(s); + y = stbi__get16le(s); + w = stbi__get16le(s); + h = stbi__get16le(s); + if (((x + w) > (g->w)) || ((y + h) > (g->h))) + return stbi__errpuc("bad Image Descriptor", "Corrupt GIF"); + + g->line_size = g->w * 4; + g->start_x = x * 4; + g->start_y = y * g->line_size; + g->max_x = g->start_x + w * 4; + g->max_y = g->start_y + h * g->line_size; + g->cur_x = g->start_x; + g->cur_y = g->start_y; + + // if the width of the specified rectangle is 0, that means + // we may not see *any* pixels or the image is malformed; + // to make sure this is caught, move the current y down to + // max_y (which is what out_gif_code checks). + if (w == 0) + g->cur_y = g->max_y; + + g->lflags = stbi__get8(s); + + if (g->lflags & 0x40) { + g->step = 8 * g->line_size; // first interlaced spacing + g->parse = 3; + } else { + g->step = g->line_size; + g->parse = 0; + } + + if (g->lflags & 0x80) { + stbi__gif_parse_colortable(s,g->lpal, 2 << (g->lflags & 7), g->eflags & 0x01 ? g->transparent : -1); + g->color_table = (stbi_uc *) g->lpal; + } else if (g->flags & 0x80) { + g->color_table = (stbi_uc *) g->pal; + } else + return stbi__errpuc("missing color table", "Corrupt GIF"); + + o = stbi__process_gif_raster(s, g); + if (!o) return NULL; + + // if this was the first frame, + pcount = g->w * g->h; + if (first_frame && (g->bgindex > 0)) { + // if first frame, any pixel not drawn to gets the background color + for (pi = 0; pi < pcount; ++pi) { + if (g->history[pi] == 0) { + g->pal[g->bgindex][3] = 255; // just in case it was made transparent, undo that; It will be reset next frame if need be; + memcpy( &g->out[pi * 4], &g->pal[g->bgindex], 4 ); + } + } + } + + return o; + } + + case 0x21: // Comment Extension. + { + int len; + int ext = stbi__get8(s); + if (ext == 0xF9) { // Graphic Control Extension. + len = stbi__get8(s); + if (len == 4) { + g->eflags = stbi__get8(s); + g->delay = 10 * stbi__get16le(s); // delay - 1/100th of a second, saving as 1/1000ths. + + // unset old transparent + if (g->transparent >= 0) { + g->pal[g->transparent][3] = 255; + } + if (g->eflags & 0x01) { + g->transparent = stbi__get8(s); + if (g->transparent >= 0) { + g->pal[g->transparent][3] = 0; + } + } else { + // don't need transparent + stbi__skip(s, 1); + g->transparent = -1; + } + } else { + stbi__skip(s, len); + break; + } + } + while ((len = stbi__get8(s)) != 0) { + stbi__skip(s, len); + } + break; + } + + case 0x3B: // gif stream termination code + return (stbi_uc *) s; // using '1' causes warning on some compilers + + default: + return stbi__errpuc("unknown code", "Corrupt GIF"); + } + } +} + +static void *stbi__load_gif_main_outofmem(stbi__gif *g, stbi_uc *out, int **delays) +{ + STBI_FREE(g->out); + STBI_FREE(g->history); + STBI_FREE(g->background); + + if (out) STBI_FREE(out); + if (delays && *delays) STBI_FREE(*delays); + return stbi__errpuc("outofmem", "Out of memory"); +} + +static void *stbi__load_gif_main(stbi__context *s, int **delays, int *x, int *y, int *z, int *comp, int req_comp) +{ + if (stbi__gif_test(s)) { + int layers = 0; + stbi_uc *u = 0; + stbi_uc *out = 0; + stbi_uc *two_back = 0; + stbi__gif g; + int stride; + int out_size = 0; + int delays_size = 0; + + STBI_NOTUSED(out_size); + STBI_NOTUSED(delays_size); + + memset(&g, 0, sizeof(g)); + if (delays) { + *delays = 0; + } + + do { + u = stbi__gif_load_next(s, &g, comp, req_comp, two_back); + if (u == (stbi_uc *) s) u = 0; // end of animated gif marker + + if (u) { + *x = g.w; + *y = g.h; + ++layers; + stride = g.w * g.h * 4; + + if (out) { + void *tmp = (stbi_uc*) STBI_REALLOC_SIZED( out, out_size, layers * stride ); + if (!tmp) + return stbi__load_gif_main_outofmem(&g, out, delays); + else { + out = (stbi_uc*) tmp; + out_size = layers * stride; + } + + if (delays) { + int *new_delays = (int*) STBI_REALLOC_SIZED( *delays, delays_size, sizeof(int) * layers ); + if (!new_delays) + return stbi__load_gif_main_outofmem(&g, out, delays); + *delays = new_delays; + delays_size = layers * sizeof(int); + } + } else { + out = (stbi_uc*)stbi__malloc( layers * stride ); + if (!out) + return stbi__load_gif_main_outofmem(&g, out, delays); + out_size = layers * stride; + if (delays) { + *delays = (int*) stbi__malloc( layers * sizeof(int) ); + if (!*delays) + return stbi__load_gif_main_outofmem(&g, out, delays); + delays_size = layers * sizeof(int); + } + } + memcpy( out + ((layers - 1) * stride), u, stride ); + if (layers >= 2) { + two_back = out - 2 * stride; + } + + if (delays) { + (*delays)[layers - 1U] = g.delay; + } + } + } while (u != 0); + + // free temp buffer; + STBI_FREE(g.out); + STBI_FREE(g.history); + STBI_FREE(g.background); + + // do the final conversion after loading everything; + if (req_comp && req_comp != 4) + out = stbi__convert_format(out, 4, req_comp, layers * g.w, g.h); + + *z = layers; + return out; + } else { + return stbi__errpuc("not GIF", "Image was not as a gif type."); + } +} + +static void *stbi__gif_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri) +{ + stbi_uc *u = 0; + stbi__gif g; + memset(&g, 0, sizeof(g)); + STBI_NOTUSED(ri); + + u = stbi__gif_load_next(s, &g, comp, req_comp, 0); + if (u == (stbi_uc *) s) u = 0; // end of animated gif marker + if (u) { + *x = g.w; + *y = g.h; + + // moved conversion to after successful load so that the same + // can be done for multiple frames. + if (req_comp && req_comp != 4) + u = stbi__convert_format(u, 4, req_comp, g.w, g.h); + } else if (g.out) { + // if there was an error and we allocated an image buffer, free it! + STBI_FREE(g.out); + } + + // free buffers needed for multiple frame loading; + STBI_FREE(g.history); + STBI_FREE(g.background); + + return u; +} + +static int stbi__gif_info(stbi__context *s, int *x, int *y, int *comp) +{ + return stbi__gif_info_raw(s,x,y,comp); +} +#endif + +// ************************************************************************************************* +// Radiance RGBE HDR loader +// originally by Nicolas Schulz +#ifndef STBI_NO_HDR +static int stbi__hdr_test_core(stbi__context *s, const char *signature) +{ + int i; + for (i=0; signature[i]; ++i) + if (stbi__get8(s) != signature[i]) + return 0; + stbi__rewind(s); + return 1; +} + +static int stbi__hdr_test(stbi__context* s) +{ + int r = stbi__hdr_test_core(s, "#?RADIANCE\n"); + stbi__rewind(s); + if(!r) { + r = stbi__hdr_test_core(s, "#?RGBE\n"); + stbi__rewind(s); + } + return r; +} + +#define STBI__HDR_BUFLEN 1024 +static char *stbi__hdr_gettoken(stbi__context *z, char *buffer) +{ + int len=0; + char c = '\0'; + + c = (char) stbi__get8(z); + + while (!stbi__at_eof(z) && c != '\n') { + buffer[len++] = c; + if (len == STBI__HDR_BUFLEN-1) { + // flush to end of line + while (!stbi__at_eof(z) && stbi__get8(z) != '\n') + ; + break; + } + c = (char) stbi__get8(z); + } + + buffer[len] = 0; + return buffer; +} + +static void stbi__hdr_convert(float *output, stbi_uc *input, int req_comp) +{ + if ( input[3] != 0 ) { + float f1; + // Exponent + f1 = (float) ldexp(1.0f, input[3] - (int)(128 + 8)); + if (req_comp <= 2) + output[0] = (input[0] + input[1] + input[2]) * f1 / 3; + else { + output[0] = input[0] * f1; + output[1] = input[1] * f1; + output[2] = input[2] * f1; + } + if (req_comp == 2) output[1] = 1; + if (req_comp == 4) output[3] = 1; + } else { + switch (req_comp) { + case 4: output[3] = 1; /* fallthrough */ + case 3: output[0] = output[1] = output[2] = 0; + break; + case 2: output[1] = 1; /* fallthrough */ + case 1: output[0] = 0; + break; + } + } +} + +static float *stbi__hdr_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri) +{ + char buffer[STBI__HDR_BUFLEN]; + char *token; + int valid = 0; + int width, height; + stbi_uc *scanline; + float *hdr_data; + int len; + unsigned char count, value; + int i, j, k, c1,c2, z; + const char *headerToken; + STBI_NOTUSED(ri); + + // Check identifier + headerToken = stbi__hdr_gettoken(s,buffer); + if (strcmp(headerToken, "#?RADIANCE") != 0 && strcmp(headerToken, "#?RGBE") != 0) + return stbi__errpf("not HDR", "Corrupt HDR image"); + + // Parse header + for(;;) { + token = stbi__hdr_gettoken(s,buffer); + if (token[0] == 0) break; + if (strcmp(token, "FORMAT=32-bit_rle_rgbe") == 0) valid = 1; + } + + if (!valid) return stbi__errpf("unsupported format", "Unsupported HDR format"); + + // Parse width and height + // can't use sscanf() if we're not using stdio! + token = stbi__hdr_gettoken(s,buffer); + if (strncmp(token, "-Y ", 3)) return stbi__errpf("unsupported data layout", "Unsupported HDR format"); + token += 3; + height = (int) strtol(token, &token, 10); + while (*token == ' ') ++token; + if (strncmp(token, "+X ", 3)) return stbi__errpf("unsupported data layout", "Unsupported HDR format"); + token += 3; + width = (int) strtol(token, NULL, 10); + + if (height > STBI_MAX_DIMENSIONS) return stbi__errpf("too large","Very large image (corrupt?)"); + if (width > STBI_MAX_DIMENSIONS) return stbi__errpf("too large","Very large image (corrupt?)"); + + *x = width; + *y = height; + + if (comp) *comp = 3; + if (req_comp == 0) req_comp = 3; + + if (!stbi__mad4sizes_valid(width, height, req_comp, sizeof(float), 0)) + return stbi__errpf("too large", "HDR image is too large"); + + // Read data + hdr_data = (float *) stbi__malloc_mad4(width, height, req_comp, sizeof(float), 0); + if (!hdr_data) + return stbi__errpf("outofmem", "Out of memory"); + + // Load image data + // image data is stored as some number of sca + if ( width < 8 || width >= 32768) { + // Read flat data + for (j=0; j < height; ++j) { + for (i=0; i < width; ++i) { + stbi_uc rgbe[4]; + main_decode_loop: + stbi__getn(s, rgbe, 4); + stbi__hdr_convert(hdr_data + j * width * req_comp + i * req_comp, rgbe, req_comp); + } + } + } else { + // Read RLE-encoded data + scanline = NULL; + + for (j = 0; j < height; ++j) { + c1 = stbi__get8(s); + c2 = stbi__get8(s); + len = stbi__get8(s); + if (c1 != 2 || c2 != 2 || (len & 0x80)) { + // not run-length encoded, so we have to actually use THIS data as a decoded + // pixel (note this can't be a valid pixel--one of RGB must be >= 128) + stbi_uc rgbe[4]; + rgbe[0] = (stbi_uc) c1; + rgbe[1] = (stbi_uc) c2; + rgbe[2] = (stbi_uc) len; + rgbe[3] = (stbi_uc) stbi__get8(s); + stbi__hdr_convert(hdr_data, rgbe, req_comp); + i = 1; + j = 0; + STBI_FREE(scanline); + goto main_decode_loop; // yes, this makes no sense + } + len <<= 8; + len |= stbi__get8(s); + if (len != width) { STBI_FREE(hdr_data); STBI_FREE(scanline); return stbi__errpf("invalid decoded scanline length", "corrupt HDR"); } + if (scanline == NULL) { + scanline = (stbi_uc *) stbi__malloc_mad2(width, 4, 0); + if (!scanline) { + STBI_FREE(hdr_data); + return stbi__errpf("outofmem", "Out of memory"); + } + } + + for (k = 0; k < 4; ++k) { + int nleft; + i = 0; + while ((nleft = width - i) > 0) { + count = stbi__get8(s); + if (count > 128) { + // Run + value = stbi__get8(s); + count -= 128; + if ((count == 0) || (count > nleft)) { STBI_FREE(hdr_data); STBI_FREE(scanline); return stbi__errpf("corrupt", "bad RLE data in HDR"); } + for (z = 0; z < count; ++z) + scanline[i++ * 4 + k] = value; + } else { + // Dump + if ((count == 0) || (count > nleft)) { STBI_FREE(hdr_data); STBI_FREE(scanline); return stbi__errpf("corrupt", "bad RLE data in HDR"); } + for (z = 0; z < count; ++z) + scanline[i++ * 4 + k] = stbi__get8(s); + } + } + } + for (i=0; i < width; ++i) + stbi__hdr_convert(hdr_data+(j*width + i)*req_comp, scanline + i*4, req_comp); + } + if (scanline) + STBI_FREE(scanline); + } + + return hdr_data; +} + +static int stbi__hdr_info(stbi__context *s, int *x, int *y, int *comp) +{ + char buffer[STBI__HDR_BUFLEN]; + char *token; + int valid = 0; + int dummy; + + if (!x) x = &dummy; + if (!y) y = &dummy; + if (!comp) comp = &dummy; + + if (stbi__hdr_test(s) == 0) { + stbi__rewind( s ); + return 0; + } + + for(;;) { + token = stbi__hdr_gettoken(s,buffer); + if (token[0] == 0) break; + if (strcmp(token, "FORMAT=32-bit_rle_rgbe") == 0) valid = 1; + } + + if (!valid) { + stbi__rewind( s ); + return 0; + } + token = stbi__hdr_gettoken(s,buffer); + if (strncmp(token, "-Y ", 3)) { + stbi__rewind( s ); + return 0; + } + token += 3; + *y = (int) strtol(token, &token, 10); + while (*token == ' ') ++token; + if (strncmp(token, "+X ", 3)) { + stbi__rewind( s ); + return 0; + } + token += 3; + *x = (int) strtol(token, NULL, 10); + *comp = 3; + return 1; +} +#endif // STBI_NO_HDR + +#ifndef STBI_NO_BMP +static int stbi__bmp_info(stbi__context *s, int *x, int *y, int *comp) +{ + void *p; + stbi__bmp_data info; + + info.all_a = 255; + p = stbi__bmp_parse_header(s, &info); + if (p == NULL) { + stbi__rewind( s ); + return 0; + } + if (x) *x = s->img_x; + if (y) *y = s->img_y; + if (comp) { + if (info.bpp == 24 && info.ma == 0xff000000) + *comp = 3; + else + *comp = info.ma ? 4 : 3; + } + return 1; +} +#endif + +#ifndef STBI_NO_PSD +static int stbi__psd_info(stbi__context *s, int *x, int *y, int *comp) +{ + int channelCount, dummy, depth; + if (!x) x = &dummy; + if (!y) y = &dummy; + if (!comp) comp = &dummy; + if (stbi__get32be(s) != 0x38425053) { + stbi__rewind( s ); + return 0; + } + if (stbi__get16be(s) != 1) { + stbi__rewind( s ); + return 0; + } + stbi__skip(s, 6); + channelCount = stbi__get16be(s); + if (channelCount < 0 || channelCount > 16) { + stbi__rewind( s ); + return 0; + } + *y = stbi__get32be(s); + *x = stbi__get32be(s); + depth = stbi__get16be(s); + if (depth != 8 && depth != 16) { + stbi__rewind( s ); + return 0; + } + if (stbi__get16be(s) != 3) { + stbi__rewind( s ); + return 0; + } + *comp = 4; + return 1; +} + +static int stbi__psd_is16(stbi__context *s) +{ + int channelCount, depth; + if (stbi__get32be(s) != 0x38425053) { + stbi__rewind( s ); + return 0; + } + if (stbi__get16be(s) != 1) { + stbi__rewind( s ); + return 0; + } + stbi__skip(s, 6); + channelCount = stbi__get16be(s); + if (channelCount < 0 || channelCount > 16) { + stbi__rewind( s ); + return 0; + } + STBI_NOTUSED(stbi__get32be(s)); + STBI_NOTUSED(stbi__get32be(s)); + depth = stbi__get16be(s); + if (depth != 16) { + stbi__rewind( s ); + return 0; + } + return 1; +} +#endif + +#ifndef STBI_NO_PIC +static int stbi__pic_info(stbi__context *s, int *x, int *y, int *comp) +{ + int act_comp=0,num_packets=0,chained,dummy; + stbi__pic_packet packets[10]; + + if (!x) x = &dummy; + if (!y) y = &dummy; + if (!comp) comp = &dummy; + + if (!stbi__pic_is4(s,"\x53\x80\xF6\x34")) { + stbi__rewind(s); + return 0; + } + + stbi__skip(s, 88); + + *x = stbi__get16be(s); + *y = stbi__get16be(s); + if (stbi__at_eof(s)) { + stbi__rewind( s); + return 0; + } + if ( (*x) != 0 && (1 << 28) / (*x) < (*y)) { + stbi__rewind( s ); + return 0; + } + + stbi__skip(s, 8); + + do { + stbi__pic_packet *packet; + + if (num_packets==sizeof(packets)/sizeof(packets[0])) + return 0; + + packet = &packets[num_packets++]; + chained = stbi__get8(s); + packet->size = stbi__get8(s); + packet->type = stbi__get8(s); + packet->channel = stbi__get8(s); + act_comp |= packet->channel; + + if (stbi__at_eof(s)) { + stbi__rewind( s ); + return 0; + } + if (packet->size != 8) { + stbi__rewind( s ); + return 0; + } + } while (chained); + + *comp = (act_comp & 0x10 ? 4 : 3); + + return 1; +} +#endif + +// ************************************************************************************************* +// Portable Gray Map and Portable Pixel Map loader +// by Ken Miller +// +// PGM: http://netpbm.sourceforge.net/doc/pgm.html +// PPM: http://netpbm.sourceforge.net/doc/ppm.html +// +// Known limitations: +// Does not support comments in the header section +// Does not support ASCII image data (formats P2 and P3) + +#ifndef STBI_NO_PNM + +static int stbi__pnm_test(stbi__context *s) +{ + char p, t; + p = (char) stbi__get8(s); + t = (char) stbi__get8(s); + if (p != 'P' || (t != '5' && t != '6')) { + stbi__rewind( s ); + return 0; + } + return 1; +} + +static void *stbi__pnm_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri) +{ + stbi_uc *out; + STBI_NOTUSED(ri); + + ri->bits_per_channel = stbi__pnm_info(s, (int *)&s->img_x, (int *)&s->img_y, (int *)&s->img_n); + if (ri->bits_per_channel == 0) + return 0; + + if (s->img_y > STBI_MAX_DIMENSIONS) return stbi__errpuc("too large","Very large image (corrupt?)"); + if (s->img_x > STBI_MAX_DIMENSIONS) return stbi__errpuc("too large","Very large image (corrupt?)"); + + *x = s->img_x; + *y = s->img_y; + if (comp) *comp = s->img_n; + + if (!stbi__mad4sizes_valid(s->img_n, s->img_x, s->img_y, ri->bits_per_channel / 8, 0)) + return stbi__errpuc("too large", "PNM too large"); + + out = (stbi_uc *) stbi__malloc_mad4(s->img_n, s->img_x, s->img_y, ri->bits_per_channel / 8, 0); + if (!out) return stbi__errpuc("outofmem", "Out of memory"); + if (!stbi__getn(s, out, s->img_n * s->img_x * s->img_y * (ri->bits_per_channel / 8))) { + STBI_FREE(out); + return stbi__errpuc("bad PNM", "PNM file truncated"); + } + + if (req_comp && req_comp != s->img_n) { + if (ri->bits_per_channel == 16) { + out = (stbi_uc *) stbi__convert_format16((stbi__uint16 *) out, s->img_n, req_comp, s->img_x, s->img_y); + } else { + out = stbi__convert_format(out, s->img_n, req_comp, s->img_x, s->img_y); + } + if (out == NULL) return out; // stbi__convert_format frees input on failure + } + return out; +} + +static int stbi__pnm_isspace(char c) +{ + return c == ' ' || c == '\t' || c == '\n' || c == '\v' || c == '\f' || c == '\r'; +} + +static void stbi__pnm_skip_whitespace(stbi__context *s, char *c) +{ + for (;;) { + while (!stbi__at_eof(s) && stbi__pnm_isspace(*c)) + *c = (char) stbi__get8(s); + + if (stbi__at_eof(s) || *c != '#') + break; + + while (!stbi__at_eof(s) && *c != '\n' && *c != '\r' ) + *c = (char) stbi__get8(s); + } +} + +static int stbi__pnm_isdigit(char c) +{ + return c >= '0' && c <= '9'; +} + +static int stbi__pnm_getinteger(stbi__context *s, char *c) +{ + int value = 0; + + while (!stbi__at_eof(s) && stbi__pnm_isdigit(*c)) { + value = value*10 + (*c - '0'); + *c = (char) stbi__get8(s); + if((value > 214748364) || (value == 214748364 && *c > '7')) + return stbi__err("integer parse overflow", "Parsing an integer in the PPM header overflowed a 32-bit int"); + } + + return value; +} + +static int stbi__pnm_info(stbi__context *s, int *x, int *y, int *comp) +{ + int maxv, dummy; + char c, p, t; + + if (!x) x = &dummy; + if (!y) y = &dummy; + if (!comp) comp = &dummy; + + stbi__rewind(s); + + // Get identifier + p = (char) stbi__get8(s); + t = (char) stbi__get8(s); + if (p != 'P' || (t != '5' && t != '6')) { + stbi__rewind(s); + return 0; + } + + *comp = (t == '6') ? 3 : 1; // '5' is 1-component .pgm; '6' is 3-component .ppm + + c = (char) stbi__get8(s); + stbi__pnm_skip_whitespace(s, &c); + + *x = stbi__pnm_getinteger(s, &c); // read width + if(*x == 0) + return stbi__err("invalid width", "PPM image header had zero or overflowing width"); + stbi__pnm_skip_whitespace(s, &c); + + *y = stbi__pnm_getinteger(s, &c); // read height + if (*y == 0) + return stbi__err("invalid width", "PPM image header had zero or overflowing width"); + stbi__pnm_skip_whitespace(s, &c); + + maxv = stbi__pnm_getinteger(s, &c); // read max value + if (maxv > 65535) + return stbi__err("max value > 65535", "PPM image supports only 8-bit and 16-bit images"); + else if (maxv > 255) + return 16; + else + return 8; +} + +static int stbi__pnm_is16(stbi__context *s) +{ + if (stbi__pnm_info(s, NULL, NULL, NULL) == 16) + return 1; + return 0; +} +#endif + +static int stbi__info_main(stbi__context *s, int *x, int *y, int *comp) +{ + #ifndef STBI_NO_JPEG + if (stbi__jpeg_info(s, x, y, comp)) return 1; + #endif + + #ifndef STBI_NO_PNG + if (stbi__png_info(s, x, y, comp)) return 1; + #endif + + #ifndef STBI_NO_GIF + if (stbi__gif_info(s, x, y, comp)) return 1; + #endif + + #ifndef STBI_NO_BMP + if (stbi__bmp_info(s, x, y, comp)) return 1; + #endif + + #ifndef STBI_NO_PSD + if (stbi__psd_info(s, x, y, comp)) return 1; + #endif + + #ifndef STBI_NO_PIC + if (stbi__pic_info(s, x, y, comp)) return 1; + #endif + + #ifndef STBI_NO_PNM + if (stbi__pnm_info(s, x, y, comp)) return 1; + #endif + + #ifndef STBI_NO_HDR + if (stbi__hdr_info(s, x, y, comp)) return 1; + #endif + + // test tga last because it's a crappy test! + #ifndef STBI_NO_TGA + if (stbi__tga_info(s, x, y, comp)) + return 1; + #endif + return stbi__err("unknown image type", "Image not of any known type, or corrupt"); +} + +static int stbi__is_16_main(stbi__context *s) +{ + #ifndef STBI_NO_PNG + if (stbi__png_is16(s)) return 1; + #endif + + #ifndef STBI_NO_PSD + if (stbi__psd_is16(s)) return 1; + #endif + + #ifndef STBI_NO_PNM + if (stbi__pnm_is16(s)) return 1; + #endif + return 0; +} + +#ifndef STBI_NO_STDIO +STBIDEF int stbi_info(char const *filename, int *x, int *y, int *comp) +{ + FILE *f = stbi__fopen(filename, "rb"); + int result; + if (!f) return stbi__err("can't fopen", "Unable to open file"); + result = stbi_info_from_file(f, x, y, comp); + fclose(f); + return result; +} + +STBIDEF int stbi_info_from_file(FILE *f, int *x, int *y, int *comp) +{ + int r; + stbi__context s; + long pos = ftell(f); + stbi__start_file(&s, f); + r = stbi__info_main(&s,x,y,comp); + fseek(f,pos,SEEK_SET); + return r; +} + +STBIDEF int stbi_is_16_bit(char const *filename) +{ + FILE *f = stbi__fopen(filename, "rb"); + int result; + if (!f) return stbi__err("can't fopen", "Unable to open file"); + result = stbi_is_16_bit_from_file(f); + fclose(f); + return result; +} + +STBIDEF int stbi_is_16_bit_from_file(FILE *f) +{ + int r; + stbi__context s; + long pos = ftell(f); + stbi__start_file(&s, f); + r = stbi__is_16_main(&s); + fseek(f,pos,SEEK_SET); + return r; +} +#endif // !STBI_NO_STDIO + +STBIDEF int stbi_info_from_memory(stbi_uc const *buffer, int len, int *x, int *y, int *comp) +{ + stbi__context s; + stbi__start_mem(&s,buffer,len); + return stbi__info_main(&s,x,y,comp); +} + +STBIDEF int stbi_info_from_callbacks(stbi_io_callbacks const *c, void *user, int *x, int *y, int *comp) +{ + stbi__context s; + stbi__start_callbacks(&s, (stbi_io_callbacks *) c, user); + return stbi__info_main(&s,x,y,comp); +} + +STBIDEF int stbi_is_16_bit_from_memory(stbi_uc const *buffer, int len) +{ + stbi__context s; + stbi__start_mem(&s,buffer,len); + return stbi__is_16_main(&s); +} + +STBIDEF int stbi_is_16_bit_from_callbacks(stbi_io_callbacks const *c, void *user) +{ + stbi__context s; + stbi__start_callbacks(&s, (stbi_io_callbacks *) c, user); + return stbi__is_16_main(&s); +} + +#endif // STB_IMAGE_IMPLEMENTATION + +/* + revision history: + 2.20 (2019-02-07) support utf8 filenames in Windows; fix warnings and platform ifdefs + 2.19 (2018-02-11) fix warning + 2.18 (2018-01-30) fix warnings + 2.17 (2018-01-29) change sbti__shiftsigned to avoid clang -O2 bug + 1-bit BMP + *_is_16_bit api + avoid warnings + 2.16 (2017-07-23) all functions have 16-bit variants; + STBI_NO_STDIO works again; + compilation fixes; + fix rounding in unpremultiply; + optimize vertical flip; + disable raw_len validation; + documentation fixes + 2.15 (2017-03-18) fix png-1,2,4 bug; now all Imagenet JPGs decode; + warning fixes; disable run-time SSE detection on gcc; + uniform handling of optional "return" values; + thread-safe initialization of zlib tables + 2.14 (2017-03-03) remove deprecated STBI_JPEG_OLD; fixes for Imagenet JPGs + 2.13 (2016-11-29) add 16-bit API, only supported for PNG right now + 2.12 (2016-04-02) fix typo in 2.11 PSD fix that caused crashes + 2.11 (2016-04-02) allocate large structures on the stack + remove white matting for transparent PSD + fix reported channel count for PNG & BMP + re-enable SSE2 in non-gcc 64-bit + support RGB-formatted JPEG + read 16-bit PNGs (only as 8-bit) + 2.10 (2016-01-22) avoid warning introduced in 2.09 by STBI_REALLOC_SIZED + 2.09 (2016-01-16) allow comments in PNM files + 16-bit-per-pixel TGA (not bit-per-component) + info() for TGA could break due to .hdr handling + info() for BMP to shares code instead of sloppy parse + can use STBI_REALLOC_SIZED if allocator doesn't support realloc + code cleanup + 2.08 (2015-09-13) fix to 2.07 cleanup, reading RGB PSD as RGBA + 2.07 (2015-09-13) fix compiler warnings + partial animated GIF support + limited 16-bpc PSD support + #ifdef unused functions + bug with < 92 byte PIC,PNM,HDR,TGA + 2.06 (2015-04-19) fix bug where PSD returns wrong '*comp' value + 2.05 (2015-04-19) fix bug in progressive JPEG handling, fix warning + 2.04 (2015-04-15) try to re-enable SIMD on MinGW 64-bit + 2.03 (2015-04-12) extra corruption checking (mmozeiko) + stbi_set_flip_vertically_on_load (nguillemot) + fix NEON support; fix mingw support + 2.02 (2015-01-19) fix incorrect assert, fix warning + 2.01 (2015-01-17) fix various warnings; suppress SIMD on gcc 32-bit without -msse2 + 2.00b (2014-12-25) fix STBI_MALLOC in progressive JPEG + 2.00 (2014-12-25) optimize JPG, including x86 SSE2 & NEON SIMD (ryg) + progressive JPEG (stb) + PGM/PPM support (Ken Miller) + STBI_MALLOC,STBI_REALLOC,STBI_FREE + GIF bugfix -- seemingly never worked + STBI_NO_*, STBI_ONLY_* + 1.48 (2014-12-14) fix incorrectly-named assert() + 1.47 (2014-12-14) 1/2/4-bit PNG support, both direct and paletted (Omar Cornut & stb) + optimize PNG (ryg) + fix bug in interlaced PNG with user-specified channel count (stb) + 1.46 (2014-08-26) + fix broken tRNS chunk (colorkey-style transparency) in non-paletted PNG + 1.45 (2014-08-16) + fix MSVC-ARM internal compiler error by wrapping malloc + 1.44 (2014-08-07) + various warning fixes from Ronny Chevalier + 1.43 (2014-07-15) + fix MSVC-only compiler problem in code changed in 1.42 + 1.42 (2014-07-09) + don't define _CRT_SECURE_NO_WARNINGS (affects user code) + fixes to stbi__cleanup_jpeg path + added STBI_ASSERT to avoid requiring assert.h + 1.41 (2014-06-25) + fix search&replace from 1.36 that messed up comments/error messages + 1.40 (2014-06-22) + fix gcc struct-initialization warning + 1.39 (2014-06-15) + fix to TGA optimization when req_comp != number of components in TGA; + fix to GIF loading because BMP wasn't rewinding (whoops, no GIFs in my test suite) + add support for BMP version 5 (more ignored fields) + 1.38 (2014-06-06) + suppress MSVC warnings on integer casts truncating values + fix accidental rename of 'skip' field of I/O + 1.37 (2014-06-04) + remove duplicate typedef + 1.36 (2014-06-03) + convert to header file single-file library + if de-iphone isn't set, load iphone images color-swapped instead of returning NULL + 1.35 (2014-05-27) + various warnings + fix broken STBI_SIMD path + fix bug where stbi_load_from_file no longer left file pointer in correct place + fix broken non-easy path for 32-bit BMP (possibly never used) + TGA optimization by Arseny Kapoulkine + 1.34 (unknown) + use STBI_NOTUSED in stbi__resample_row_generic(), fix one more leak in tga failure case + 1.33 (2011-07-14) + make stbi_is_hdr work in STBI_NO_HDR (as specified), minor compiler-friendly improvements + 1.32 (2011-07-13) + support for "info" function for all supported filetypes (SpartanJ) + 1.31 (2011-06-20) + a few more leak fixes, bug in PNG handling (SpartanJ) + 1.30 (2011-06-11) + added ability to load files via callbacks to accomidate custom input streams (Ben Wenger) + removed deprecated format-specific test/load functions + removed support for installable file formats (stbi_loader) -- would have been broken for IO callbacks anyway + error cases in bmp and tga give messages and don't leak (Raymond Barbiero, grisha) + fix inefficiency in decoding 32-bit BMP (David Woo) + 1.29 (2010-08-16) + various warning fixes from Aurelien Pocheville + 1.28 (2010-08-01) + fix bug in GIF palette transparency (SpartanJ) + 1.27 (2010-08-01) + cast-to-stbi_uc to fix warnings + 1.26 (2010-07-24) + fix bug in file buffering for PNG reported by SpartanJ + 1.25 (2010-07-17) + refix trans_data warning (Won Chun) + 1.24 (2010-07-12) + perf improvements reading from files on platforms with lock-heavy fgetc() + minor perf improvements for jpeg + deprecated type-specific functions so we'll get feedback if they're needed + attempt to fix trans_data warning (Won Chun) + 1.23 fixed bug in iPhone support + 1.22 (2010-07-10) + removed image *writing* support + stbi_info support from Jetro Lauha + GIF support from Jean-Marc Lienher + iPhone PNG-extensions from James Brown + warning-fixes from Nicolas Schulz and Janez Zemva (i.stbi__err. Janez (U+017D)emva) + 1.21 fix use of 'stbi_uc' in header (reported by jon blow) + 1.20 added support for Softimage PIC, by Tom Seddon + 1.19 bug in interlaced PNG corruption check (found by ryg) + 1.18 (2008-08-02) + fix a threading bug (local mutable static) + 1.17 support interlaced PNG + 1.16 major bugfix - stbi__convert_format converted one too many pixels + 1.15 initialize some fields for thread safety + 1.14 fix threadsafe conversion bug + header-file-only version (#define STBI_HEADER_FILE_ONLY before including) + 1.13 threadsafe + 1.12 const qualifiers in the API + 1.11 Support installable IDCT, colorspace conversion routines + 1.10 Fixes for 64-bit (don't use "unsigned long") + optimized upsampling by Fabian "ryg" Giesen + 1.09 Fix format-conversion for PSD code (bad global variables!) + 1.08 Thatcher Ulrich's PSD code integrated by Nicolas Schulz + 1.07 attempt to fix C++ warning/errors again + 1.06 attempt to fix C++ warning/errors again + 1.05 fix TGA loading to return correct *comp and use good luminance calc + 1.04 default float alpha is 1, not 255; use 'void *' for stbi_image_free + 1.03 bugfixes to STBI_NO_STDIO, STBI_NO_HDR + 1.02 support for (subset of) HDR files, float interface for preferred access to them + 1.01 fix bug: possible bug in handling right-side up bmps... not sure + fix bug: the stbi__bmp_load() and stbi__tga_load() functions didn't work at all + 1.00 interface to zlib that skips zlib header + 0.99 correct handling of alpha in palette + 0.98 TGA loader by lonesock; dynamically add loaders (untested) + 0.97 jpeg errors on too large a file; also catch another malloc failure + 0.96 fix detection of invalid v value - particleman@mollyrocket forum + 0.95 during header scan, seek to markers in case of padding + 0.94 STBI_NO_STDIO to disable stdio usage; rename all #defines the same + 0.93 handle jpegtran output; verbose errors + 0.92 read 4,8,16,24,32-bit BMP files of several formats + 0.91 output 24-bit Windows 3.0 BMP files + 0.90 fix a few more warnings; bump version number to approach 1.0 + 0.61 bugfixes due to Marc LeBlanc, Christopher Lloyd + 0.60 fix compiling as c++ + 0.59 fix warnings: merge Dave Moore's -Wall fixes + 0.58 fix bug: zlib uncompressed mode len/nlen was wrong endian + 0.57 fix bug: jpg last huffman symbol before marker was >9 bits but less than 16 available + 0.56 fix bug: zlib uncompressed mode len vs. nlen + 0.55 fix bug: restart_interval not initialized to 0 + 0.54 allow NULL for 'int *comp' + 0.53 fix bug in png 3->4; speedup png decoding + 0.52 png handles req_comp=3,4 directly; minor cleanup; jpeg comments + 0.51 obey req_comp requests, 1-component jpegs return as 1-component, + on 'test' only check type, not whether we support this variant + 0.50 (2006-11-19) + first released version +*/ + + +/* +------------------------------------------------------------------------------ +This software is available under 2 licenses -- choose whichever you prefer. +------------------------------------------------------------------------------ +ALTERNATIVE A - MIT License +Copyright (c) 2017 Sean Barrett +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +------------------------------------------------------------------------------ +ALTERNATIVE B - Public Domain (www.unlicense.org) +This is free and unencumbered software released into the public domain. +Anyone is free to copy, modify, publish, use, compile, sell, or distribute this +software, either in source code form or as a compiled binary, for any purpose, +commercial or non-commercial, and by any means. +In jurisdictions that recognize copyright laws, the author or authors of this +software dedicate any and all copyright interest in the software to the public +domain. We make this dedication for the benefit of the public at large and to +the detriment of our heirs and successors. We intend this dedication to be an +overt act of relinquishment in perpetuity of all present and future rights to +this software under copyright law. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN +ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +------------------------------------------------------------------------------ +*/ diff --git a/packages/core/src/zig/vendor/stb/stb_image_resize2.h b/packages/core/src/zig/vendor/stb/stb_image_resize2.h new file mode 100644 index 0000000000..adc87bdc7c --- /dev/null +++ b/packages/core/src/zig/vendor/stb/stb_image_resize2.h @@ -0,0 +1,10679 @@ +/* stb_image_resize2 - v2.18 - public domain image resizing + + by Jeff Roberts (v2) and Jorge L Rodriguez + http://github.com/nothings/stb + + Can be threaded with the extended API. SSE2, AVX, Neon and WASM SIMD support. Only + scaling and translation is supported, no rotations or shears. + + COMPILING & LINKING + In one C/C++ file that #includes this file, do this: + #define STB_IMAGE_RESIZE_IMPLEMENTATION + before the #include. That will create the implementation in that file. + + EASY API CALLS: + Easy API downsamples w/Mitchell filter, upsamples w/cubic interpolation, clamps to edge. + + stbir_resize_uint8_srgb( input_pixels, input_w, input_h, input_stride_in_bytes, + output_pixels, output_w, output_h, output_stride_in_bytes, + pixel_layout_enum ) + + stbir_resize_uint8_linear( input_pixels, input_w, input_h, input_stride_in_bytes, + output_pixels, output_w, output_h, output_stride_in_bytes, + pixel_layout_enum ) + + stbir_resize_float_linear( input_pixels, input_w, input_h, input_stride_in_bytes, + output_pixels, output_w, output_h, output_stride_in_bytes, + pixel_layout_enum ) + + If you pass NULL or zero for the output_pixels, we will allocate the output buffer + for you and return it from the function (free with free() or STBIR_FREE). + As a special case, XX_stride_in_bytes of 0 means packed continuously in memory. + + API LEVELS + There are three levels of API - easy-to-use, medium-complexity and extended-complexity. + + See the "header file" section of the source for API documentation. + + ADDITIONAL DOCUMENTATION + + MEMORY ALLOCATION + By default, we use malloc and free for memory allocation. To override the + memory allocation, before the implementation #include, add a: + + #define STBIR_MALLOC(size,user_data) ... + #define STBIR_FREE(ptr,user_data) ... + + Each resize makes exactly one call to malloc/free (unless you use the + extended API where you can do one allocation for many resizes). Under + address sanitizer, we do separate allocations to find overread/writes. + + PERFORMANCE + This library was written with an emphasis on performance. When testing + stb_image_resize with RGBA, the fastest mode is STBIR_4CHANNEL with + STBIR_TYPE_UINT8 pixels and CLAMPed edges (which is what many other resize + libs do by default). Also, make sure SIMD is turned on of course (default + for 64-bit targets). Avoid WRAP edge mode if you want the fastest speed. + + This library also comes with profiling built-in. If you define STBIR_PROFILE, + you can use the advanced API and get low-level profiling information by + calling stbir_resize_extended_profile_info() or stbir_resize_split_profile_info() + after a resize. + + SIMD + Most of the routines have optimized SSE2, AVX, NEON and WASM versions. + + On Microsoft compilers, we automatically turn on SIMD for 64-bit x64 and + ARM; for 32-bit x86 and ARM, you select SIMD mode by defining STBIR_SSE2 or + STBIR_NEON. For AVX and AVX2, we auto-select it by detecting the /arch:AVX + or /arch:AVX2 switches. You can also always manually turn SSE2, AVX or AVX2 + support on by defining STBIR_SSE2, STBIR_AVX or STBIR_AVX2. + + On Linux, SSE2 and Neon is on by default for 64-bit x64 or ARM64. For 32-bit, + we select x86 SIMD mode by whether you have -msse2, -mavx or -mavx2 enabled + on the command line. For 32-bit ARM, you must pass -mfpu=neon-vfpv4 for both + clang and GCC, but GCC also requires an additional -mfp16-format=ieee to + automatically enable NEON. + + On x86 platforms, you can also define STBIR_FP16C to turn on FP16C instructions + for converting back and forth to half-floats. This is autoselected when we + are using AVX2. Clang and GCC also require the -mf16c switch. ARM always uses + the built-in half float hardware NEON instructions. + + You can also tell us to use multiply-add instructions with STBIR_USE_FMA. + Because x86 doesn't always have fma, we turn it off by default to maintain + determinism across all platforms. If you don't care about non-FMA determinism + and are willing to restrict yourself to more recent x86 CPUs (around the AVX + timeframe), then fma will give you around a 15% speedup. + + You can force off SIMD in all cases by defining STBIR_NO_SIMD. You can turn + off AVX or AVX2 specifically with STBIR_NO_AVX or STBIR_NO_AVX2. AVX is 10% + to 40% faster, and AVX2 is generally another 12%. + + ALPHA CHANNEL + Most of the resizing functions provide the ability to control how the alpha + channel of an image is processed. + + When alpha represents transparency, it is important that when combining + colors with filtering, the pixels should not be treated equally; they + should use a weighted average based on their alpha values. For example, + if a pixel is 1% opaque bright green and another pixel is 99% opaque + black and you average them, the average will be 50% opaque, but the + unweighted average and will be a middling green color, while the weighted + average will be nearly black. This means the unweighted version introduced + green energy that didn't exist in the source image. + + (If you want to know why this makes sense, you can work out the math for + the following: consider what happens if you alpha composite a source image + over a fixed color and then average the output, vs. if you average the + source image pixels and then composite that over the same fixed color. + Only the weighted average produces the same result as the ground truth + composite-then-average result.) + + Therefore, it is in general best to "alpha weight" the pixels when applying + filters to them. This essentially means multiplying the colors by the alpha + values before combining them, and then dividing by the alpha value at the + end. + + The computer graphics industry introduced a technique called "premultiplied + alpha" or "associated alpha" in which image colors are stored in image files + already multiplied by their alpha. This saves some math when compositing, + and also avoids the need to divide by the alpha at the end (which is quite + inefficient). However, while premultiplied alpha is common in the movie CGI + industry, it is not commonplace in other industries like videogames, and most + consumer file formats are generally expected to contain not-premultiplied + colors. For example, Photoshop saves PNG files "unpremultiplied", and web + browsers like Chrome and Firefox expect PNG images to be unpremultiplied. + + Note that there are three possibilities that might describe your image + and resize expectation: + + 1. images are not premultiplied, alpha weighting is desired + 2. images are not premultiplied, alpha weighting is not desired + 3. images are premultiplied + + Both case #2 and case #3 require the exact same math: no alpha weighting + should be applied or removed. Only case 1 requires extra math operations; + the other two cases can be handled identically. + + stb_image_resize expects case #1 by default, applying alpha weighting to + images, expecting the input images to be unpremultiplied. This is what the + COLOR+ALPHA buffer types tell the resizer to do. + + When you use the pixel layouts STBIR_RGBA, STBIR_BGRA, STBIR_ARGB, + STBIR_ABGR, STBIR_RA, or STBIR_AR you are telling us that the pixels are + non-premultiplied. In these cases, the resizer will alpha weight the colors + (effectively creating the premultiplied image), do the filtering, and then + convert back to non-premult on exit. + + When you use the pixel layouts STBIR_RGBA_PM, STBIR_BGRA_PM, STBIR_ARGB_PM, + STBIR_ABGR_PM, STBIR_RA_PM or STBIR_AR_PM, you are telling that the pixels + ARE premultiplied. In this case, the resizer doesn't have to do the + premultipling - it can filter directly on the input. This about twice as + fast as the non-premultiplied case, so it's the right option if your data is + already setup correctly. + + When you use the pixel layout STBIR_4CHANNEL or STBIR_2CHANNEL, you are + telling us that there is no channel that represents transparency; it may be + RGB and some unrelated fourth channel that has been stored in the alpha + channel, but it is actually not alpha. No special processing will be + performed. + + The difference between the generic 4 or 2 channel layouts, and the + specialized _PM versions is with the _PM versions you are telling us that + the data *is* alpha, just don't premultiply it. That's important when + using SRGB pixel formats, we need to know where the alpha is, because + it is converted linearly (rather than with the SRGB converters). + + Because alpha weighting produces the same effect as premultiplying, you + even have the option with non-premultiplied inputs to let the resizer + produce a premultiplied output. Because the intially computed alpha-weighted + output image is effectively premultiplied, this is actually more performant + than the normal path which un-premultiplies the output image as a final step. + + Finally, when converting both in and out of non-premulitplied space (for + example, when using STBIR_RGBA), we go to somewhat heroic measures to + ensure that areas with zero alpha value pixels get something reasonable + in the RGB values. If you don't care about the RGB values of zero alpha + pixels, you can call the stbir_set_non_pm_alpha_speed_over_quality() + function - this runs a premultiplied resize about 25% faster. That said, + when you really care about speed, using premultiplied pixels for both in + and out (STBIR_RGBA_PM, etc) much faster than both of these premultiplied + options. + + PIXEL LAYOUT CONVERSION + The resizer can convert from some pixel layouts to others. When using the + stbir_set_pixel_layouts(), you can, for example, specify STBIR_RGBA + on input, and STBIR_ARGB on output, and it will re-organize the channels + during the resize. Currently, you can only convert between two pixel + layouts with the same number of channels. + + DETERMINISM + We commit to being deterministic (from x64 to ARM to scalar to SIMD, etc). + This requires compiling with fast-math off (using at least /fp:precise). + Also, you must turn off fp-contracting (which turns mult+adds into fmas)! + We attempt to do this with pragmas, but with Clang, you usually want to add + -ffp-contract=off to the command line as well. + + For 32-bit x86, you must use SSE and SSE2 codegen for determinism. That is, + if the scalar x87 unit gets used at all, we immediately lose determinism. + On Microsoft Visual Studio 2008 and earlier, from what we can tell there is + no way to be deterministic in 32-bit x86 (some x87 always leaks in, even + with fp:strict). On 32-bit x86 GCC, determinism requires both -msse2 and + -fpmath=sse. + + Note that we will not be deterministic with float data containing NaNs - + the NaNs will propagate differently on different SIMD and platforms. + + If you turn on STBIR_USE_FMA, then we will be deterministic with other + fma targets, but we will differ from non-fma targets (this is unavoidable, + because a fma isn't simply an add with a mult - it also introduces a + rounding difference compared to non-fma instruction sequences. + + FLOAT PIXEL FORMAT RANGE + Any range of values can be used for the non-alpha float data that you pass + in (0 to 1, -1 to 1, whatever). However, if you are inputting float values + but *outputting* bytes or shorts, you must use a range of 0 to 1 so that we + scale back properly. The alpha channel must also be 0 to 1 for any format + that does premultiplication prior to resizing. + + Note also that with float output, using filters with negative lobes, the + output filtered values might go slightly out of range. You can define + STBIR_FLOAT_LOW_CLAMP and/or STBIR_FLOAT_HIGH_CLAMP to specify the range + to clamp to on output, if that's important. + + MAX/MIN SCALE FACTORS + The input pixel resolutions are in integers, and we do the internal pointer + resolution in size_t sized integers. However, the scale ratio from input + resolution to output resolution is calculated in float form. This means + the effective possible scale ratio is limited to 24 bits (or 16 million + to 1). As you get close to the size of the float resolution (again, 16 + million pixels wide or high), you might start seeing float inaccuracy + issues in general in the pipeline. If you have to do extreme resizes, + you can usually do this is multiple stages (using float intermediate + buffers). + + FLIPPED IMAGES + Stride is just the delta from one scanline to the next. This means you can + use a negative stride to handle inverted images (point to the final + scanline and use a negative stride). You can invert the input or output, + using negative strides. + + DEFAULT FILTERS + For functions which don't provide explicit control over what filters to + use, you can change the compile-time defaults with: + + #define STBIR_DEFAULT_FILTER_UPSAMPLE STBIR_FILTER_something + #define STBIR_DEFAULT_FILTER_DOWNSAMPLE STBIR_FILTER_something + + See stbir_filter in the header-file section for the list of filters. + + NEW FILTERS + A number of 1D filter kernels are supplied. For a list of supported + filters, see the stbir_filter enum. You can install your own filters by + using the stbir_set_filter_callbacks function. + + PROGRESS + For interactive use with slow resize operations, you can use the + scanline callbacks in the extended API. It would have to be a *very* large + image resample to need progress though - we're very fast. + + CEIL and FLOOR + In scalar mode, the only functions we use from math.h are ceilf and floorf, + but if you have your own versions, you can define the STBIR_CEILF(v) and + STBIR_FLOORF(v) macros and we'll use them instead. In SIMD, we just use + our own versions. + + ASSERT + Define STBIR_ASSERT(boolval) to override assert() and not use assert.h + + PORTING FROM VERSION 1 + The API has changed. You can continue to use the old version of stb_image_resize.h, + which is available in the "deprecated/" directory. + + If you're using the old simple-to-use API, porting is straightforward. + (For more advanced APIs, read the documentation.) + + stbir_resize_uint8(): + - call `stbir_resize_uint8_linear`, cast channel count to `stbir_pixel_layout` + + stbir_resize_float(): + - call `stbir_resize_float_linear`, cast channel count to `stbir_pixel_layout` + + stbir_resize_uint8_srgb(): + - function name is unchanged + - cast channel count to `stbir_pixel_layout` + - above is sufficient unless your image has alpha and it's not RGBA/BGRA + - in that case, follow the below instructions for stbir_resize_uint8_srgb_edgemode + + stbir_resize_uint8_srgb_edgemode() + - switch to the "medium complexity" API + - stbir_resize(), very similar API but a few more parameters: + - pixel_layout: cast channel count to `stbir_pixel_layout` + - data_type: STBIR_TYPE_UINT8_SRGB + - edge: unchanged (STBIR_EDGE_WRAP, etc.) + - filter: STBIR_FILTER_DEFAULT + - which channel is alpha is specified in stbir_pixel_layout, see enum for details + + FUTURE TODOS + * For polyphase integral filters, we just memcpy the coeffs to dupe + them, but we should indirect and use the same coeff memory. + * Add pixel layout conversions for sensible different channel counts + (maybe, 1->3/4, 3->4, 4->1, 3->1). + * For SIMD encode and decode scanline routines, do any pre-aligning + for bad input/output buffer alignments and pitch? + * For very wide scanlines, we should we do vertical strips to stay within + L2 cache. Maybe do chunks of 1K pixels at a time. There would be + some pixel reconversion, but probably dwarfed by things falling out + of cache. Probably also something possible with alternating between + scattering and gathering at high resize scales? + * Should we have a multiple MIPs at the same time function (could keep + more memory in cache during multiple resizes)? + * Rewrite the coefficient generator to do many at once. + * AVX-512 vertical kernels - worried about downclocking here. + * Convert the reincludes to macros when we know they aren't changing. + * Experiment with pivoting the horizontal and always using the + vertical filters (which are faster, but perhaps not enough to overcome + the pivot cost and the extra memory touches). Need to buffer the whole + image so have to balance memory use. + * Most of our code is internally function pointers, should we compile + all the SIMD stuff always and dynamically dispatch? + + CONTRIBUTORS + Jeff Roberts: 2.0 implementation, optimizations, SIMD + Martins Mozeiko: NEON simd, WASM simd, clang and GCC whisperer + Fabian Giesen: half float and srgb converters + Sean Barrett: API design, optimizations + Jorge L Rodriguez: Original 1.0 implementation + Aras Pranckevicius: bugfixes + Nathan Reed: warning fixes for 1.0 + + REVISIONS + 2.18 (2026-03-25) fixed coefficient calculation when skipping a coefficient off + the left side of the window, added non-aligned access safe + memcpy mode for scalar path, fixed various typos, and fixed + define error in the float clamp output mode. + 2.17 (2025-10-25) silly format bug in easy-to-use APIs. + 2.16 (2025-10-21) fixed the easy-to-use APIs to allow inverted bitmaps (negative + strides), fix vertical filter kernel callback, fix threaded + gather buffer priming (and assert). + (thanks adipose, TainZerL, and Harrison Green) + 2.15 (2025-07-17) fixed an assert in debug mode when using floats with input + callbacks, work around GCC warning when adding to null ptr + (thanks Johannes Spohr and Pyry Kovanen). + 2.14 (2025-05-09) fixed a bug using downsampling gather horizontal first, and + scatter with vertical first. + 2.13 (2025-02-27) fixed a bug when using input callbacks, turned off simd for + tiny-c, fixed some variables that should have been static, + fixes a bug when calculating temp memory with resizes that + exceed 2GB of temp memory (very large resizes). + 2.12 (2024-10-18) fix incorrect use of user_data with STBIR_FREE + 2.11 (2024-09-08) fix harmless asan warnings in 2-channel and 3-channel mode + with AVX-2, fix some weird scaling edge conditions with + point sample mode. + 2.10 (2024-07-27) fix the defines GCC and mingw for loop unroll control, + fix MSVC 32-bit arm half float routines. + 2.09 (2024-06-19) fix the defines for 32-bit ARM GCC builds (was selecting + hardware half floats). + 2.08 (2024-06-10) fix for RGB->BGR three channel flips and add SIMD (thanks + to Ryan Salsbury), fix for sub-rect resizes, use the + pragmas to control unrolling when they are available. + 2.07 (2024-05-24) fix for slow final split during threaded conversions of very + wide scanlines when downsampling (caused by extra input + converting), fix for wide scanline resamples with many + splits (int overflow), fix GCC warning. + 2.06 (2024-02-10) fix for identical width/height 3x or more down-scaling + undersampling a single row on rare resize ratios (about 1%). + 2.05 (2024-02-07) fix for 2 pixel to 1 pixel resizes with wrap (thanks Aras), + fix for output callback (thanks Julien Koenen). + 2.04 (2023-11-17) fix for rare AVX bug, shadowed symbol (thanks Nikola Smiljanic). + 2.03 (2023-11-01) ASAN and TSAN warnings fixed, minor tweaks. + 2.00 (2023-10-10) mostly new source: new api, optimizations, simd, vertical-first, etc + 2x-5x faster without simd, 4x-12x faster with simd, + in some cases, 20x to 40x faster esp resizing large to very small. + 0.96 (2019-03-04) fixed warnings + 0.95 (2017-07-23) fixed warnings + 0.94 (2017-03-18) fixed warnings + 0.93 (2017-03-03) fixed bug with certain combinations of heights + 0.92 (2017-01-02) fix integer overflow on large (>2GB) images + 0.91 (2016-04-02) fix warnings; fix handling of subpixel regions + 0.90 (2014-09-17) first released version + + LICENSE + See end of file for license information. +*/ + +#if !defined(STB_IMAGE_RESIZE_DO_HORIZONTALS) && !defined(STB_IMAGE_RESIZE_DO_VERTICALS) && !defined(STB_IMAGE_RESIZE_DO_CODERS) // for internal re-includes + +#ifndef STBIR_INCLUDE_STB_IMAGE_RESIZE2_H +#define STBIR_INCLUDE_STB_IMAGE_RESIZE2_H + +#include +#ifdef _MSC_VER +typedef unsigned char stbir_uint8; +typedef unsigned short stbir_uint16; +typedef unsigned int stbir_uint32; +typedef unsigned __int64 stbir_uint64; +#else +#include +typedef uint8_t stbir_uint8; +typedef uint16_t stbir_uint16; +typedef uint32_t stbir_uint32; +typedef uint64_t stbir_uint64; +#endif + +#ifndef STBIRDEF +#ifdef STB_IMAGE_RESIZE_STATIC +#define STBIRDEF static +#else +#ifdef __cplusplus +#define STBIRDEF extern "C" +#else +#define STBIRDEF extern +#endif +#endif +#endif + +////////////////////////////////////////////////////////////////////////////// +//// start "header file" /////////////////////////////////////////////////// +// +// Easy-to-use API: +// +// * stride is the offset between successive rows of image data +// in memory, in bytes. specify 0 for packed continuously in memory +// * colorspace is linear or sRGB as specified by function name +// * Uses the default filters +// * Uses edge mode clamped +// * returned result is 1 for success or 0 in case of an error. + + +// stbir_pixel_layout specifies: +// number of channels +// order of channels +// whether color is premultiplied by alpha +// for back compatibility, you can cast the old channel count to an stbir_pixel_layout +typedef enum +{ + STBIR_1CHANNEL = 1, + STBIR_2CHANNEL = 2, + STBIR_RGB = 3, // 3-chan, with order specified (for channel flipping) + STBIR_BGR = 0, // 3-chan, with order specified (for channel flipping) + STBIR_4CHANNEL = 5, + + STBIR_RGBA = 4, // alpha formats, where alpha is NOT premultiplied into color channels + STBIR_BGRA = 6, + STBIR_ARGB = 7, + STBIR_ABGR = 8, + STBIR_RA = 9, + STBIR_AR = 10, + + STBIR_RGBA_PM = 11, // alpha formats, where alpha is premultiplied into color channels + STBIR_BGRA_PM = 12, + STBIR_ARGB_PM = 13, + STBIR_ABGR_PM = 14, + STBIR_RA_PM = 15, + STBIR_AR_PM = 16, + + STBIR_RGBA_NO_AW = 11, // alpha formats, where NO alpha weighting is applied at all! + STBIR_BGRA_NO_AW = 12, // these are just synonyms for the _PM flags (which also do + STBIR_ARGB_NO_AW = 13, // no alpha weighting). These names just make it more clear + STBIR_ABGR_NO_AW = 14, // for some folks). + STBIR_RA_NO_AW = 15, + STBIR_AR_NO_AW = 16, + +} stbir_pixel_layout; + +//=============================================================== +// Simple-complexity API +// +// If output_pixels is NULL (0), then we will allocate the buffer and return it to you. +//-------------------------------- + +STBIRDEF unsigned char * stbir_resize_uint8_srgb( const unsigned char *input_pixels , int input_w , int input_h, int input_stride_in_bytes, + unsigned char *output_pixels, int output_w, int output_h, int output_stride_in_bytes, + stbir_pixel_layout pixel_type ); + +STBIRDEF unsigned char * stbir_resize_uint8_linear( const unsigned char *input_pixels , int input_w , int input_h, int input_stride_in_bytes, + unsigned char *output_pixels, int output_w, int output_h, int output_stride_in_bytes, + stbir_pixel_layout pixel_type ); + +STBIRDEF float * stbir_resize_float_linear( const float *input_pixels , int input_w , int input_h, int input_stride_in_bytes, + float *output_pixels, int output_w, int output_h, int output_stride_in_bytes, + stbir_pixel_layout pixel_type ); +//=============================================================== + +//=============================================================== +// Medium-complexity API +// +// This extends the easy-to-use API as follows: +// +// * Can specify the datatype - U8, U8_SRGB, U16, FLOAT, HALF_FLOAT +// * Edge wrap can selected explicitly +// * Filter can be selected explicitly +//-------------------------------- + +typedef enum +{ + STBIR_EDGE_CLAMP = 0, + STBIR_EDGE_REFLECT = 1, + STBIR_EDGE_WRAP = 2, // this edge mode is slower and uses more memory + STBIR_EDGE_ZERO = 3, +} stbir_edge; + +typedef enum +{ + STBIR_FILTER_DEFAULT = 0, // use same filter type that easy-to-use API chooses + STBIR_FILTER_BOX = 1, // A trapezoid w/1-pixel wide ramps, same result as box for integer scale ratios + STBIR_FILTER_TRIANGLE = 2, // On upsampling, produces same results as bilinear texture filtering + STBIR_FILTER_CUBICBSPLINE = 3, // The cubic b-spline (aka Mitchell-Netrevalli with B=1,C=0), gaussian-esque + STBIR_FILTER_CATMULLROM = 4, // An interpolating cubic spline + STBIR_FILTER_MITCHELL = 5, // Mitchell-Netrevalli filter with B=1/3, C=1/3 + STBIR_FILTER_POINT_SAMPLE = 6, // Simple point sampling + STBIR_FILTER_OTHER = 7, // User callback specified +} stbir_filter; + +typedef enum +{ + STBIR_TYPE_UINT8 = 0, + STBIR_TYPE_UINT8_SRGB = 1, + STBIR_TYPE_UINT8_SRGB_ALPHA = 2, // alpha channel, when present, should also be SRGB (this is very unusual) + STBIR_TYPE_UINT16 = 3, + STBIR_TYPE_FLOAT = 4, + STBIR_TYPE_HALF_FLOAT = 5 +} stbir_datatype; + +// medium api +STBIRDEF void * stbir_resize( const void *input_pixels , int input_w , int input_h, int input_stride_in_bytes, + void *output_pixels, int output_w, int output_h, int output_stride_in_bytes, + stbir_pixel_layout pixel_layout, stbir_datatype data_type, + stbir_edge edge, stbir_filter filter ); +//=============================================================== + + + +//=============================================================== +// Extended-complexity API +// +// This API exposes all resize functionality. +// +// * Separate filter types for each axis +// * Separate edge modes for each axis +// * Separate input and output data types +// * Can specify regions with subpixel correctness +// * Can specify alpha flags +// * Can specify a memory callback +// * Can specify a callback data type for pixel input and output +// * Can be threaded for a single resize +// * Can be used to resize many frames without recalculating the sampler info +// +// Use this API as follows: +// 1) Call the stbir_resize_init function on a local STBIR_RESIZE structure +// 2) Call any of the stbir_set functions +// 3) Optionally call stbir_build_samplers() if you are going to resample multiple times +// with the same input and output dimensions (like resizing video frames) +// 4) Resample by calling stbir_resize_extended(). +// 5) Call stbir_free_samplers() if you called stbir_build_samplers() +//-------------------------------- + + +// Types: + +// INPUT CALLBACK: this callback is used for input scanlines +typedef void const * stbir_input_callback( void * optional_output, void const * input_ptr, int num_pixels, int x, int y, void * context ); + +// OUTPUT CALLBACK: this callback is used for output scanlines +typedef void stbir_output_callback( void const * output_ptr, int num_pixels, int y, void * context ); + +// callbacks for user installed filters +typedef float stbir__kernel_callback( float x, float scale, void * user_data ); // centered at zero +typedef float stbir__support_callback( float scale, void * user_data ); + +// internal structure with precomputed scaling +typedef struct stbir__info stbir__info; + +typedef struct STBIR_RESIZE // use the stbir_resize_init and stbir_override functions to set these values for future compatibility +{ + void * user_data; + void const * input_pixels; + int input_w, input_h; + double input_s0, input_t0, input_s1, input_t1; + stbir_input_callback * input_cb; + void * output_pixels; + int output_w, output_h; + int output_subx, output_suby, output_subw, output_subh; + stbir_output_callback * output_cb; + int input_stride_in_bytes; + int output_stride_in_bytes; + int splits; + int fast_alpha; + int needs_rebuild; + int called_alloc; + stbir_pixel_layout input_pixel_layout_public; + stbir_pixel_layout output_pixel_layout_public; + stbir_datatype input_data_type; + stbir_datatype output_data_type; + stbir_filter horizontal_filter, vertical_filter; + stbir_edge horizontal_edge, vertical_edge; + stbir__kernel_callback * horizontal_filter_kernel; stbir__support_callback * horizontal_filter_support; + stbir__kernel_callback * vertical_filter_kernel; stbir__support_callback * vertical_filter_support; + stbir__info * samplers; +} STBIR_RESIZE; + +// extended complexity api + + +// First off, you must ALWAYS call stbir_resize_init on your resize structure before any of the other calls! +STBIRDEF void stbir_resize_init( STBIR_RESIZE * resize, + const void *input_pixels, int input_w, int input_h, int input_stride_in_bytes, // stride can be zero + void *output_pixels, int output_w, int output_h, int output_stride_in_bytes, // stride can be zero + stbir_pixel_layout pixel_layout, stbir_datatype data_type ); + +//=============================================================== +// You can update these parameters any time after resize_init and there is no cost +//-------------------------------- + +STBIRDEF void stbir_set_datatypes( STBIR_RESIZE * resize, stbir_datatype input_type, stbir_datatype output_type ); +STBIRDEF void stbir_set_pixel_callbacks( STBIR_RESIZE * resize, stbir_input_callback * input_cb, stbir_output_callback * output_cb ); // no callbacks by default +STBIRDEF void stbir_set_user_data( STBIR_RESIZE * resize, void * user_data ); // pass back STBIR_RESIZE* by default +STBIRDEF void stbir_set_buffer_ptrs( STBIR_RESIZE * resize, const void * input_pixels, int input_stride_in_bytes, void * output_pixels, int output_stride_in_bytes ); + +//=============================================================== + + +//=============================================================== +// If you call any of these functions, you will trigger a sampler rebuild! +//-------------------------------- + +STBIRDEF int stbir_set_pixel_layouts( STBIR_RESIZE * resize, stbir_pixel_layout input_pixel_layout, stbir_pixel_layout output_pixel_layout ); // sets new buffer layouts +STBIRDEF int stbir_set_edgemodes( STBIR_RESIZE * resize, stbir_edge horizontal_edge, stbir_edge vertical_edge ); // CLAMP by default + +STBIRDEF int stbir_set_filters( STBIR_RESIZE * resize, stbir_filter horizontal_filter, stbir_filter vertical_filter ); // STBIR_DEFAULT_FILTER_UPSAMPLE/DOWNSAMPLE by default +STBIRDEF int stbir_set_filter_callbacks( STBIR_RESIZE * resize, stbir__kernel_callback * horizontal_filter, stbir__support_callback * horizontal_support, stbir__kernel_callback * vertical_filter, stbir__support_callback * vertical_support ); + +STBIRDEF int stbir_set_pixel_subrect( STBIR_RESIZE * resize, int subx, int suby, int subw, int subh ); // sets both sub-regions (full regions by default) +STBIRDEF int stbir_set_input_subrect( STBIR_RESIZE * resize, double s0, double t0, double s1, double t1 ); // sets input sub-region (full region by default) +STBIRDEF int stbir_set_output_pixel_subrect( STBIR_RESIZE * resize, int subx, int suby, int subw, int subh ); // sets output sub-region (full region by default) + +// when inputting AND outputting non-premultiplied alpha pixels, we use a slower but higher quality technique +// that fills the zero alpha pixel's RGB values with something plausible. If you don't care about areas of +// zero alpha, you can call this function to get about a 25% speed improvement for STBIR_RGBA to STBIR_RGBA +// types of resizes. +STBIRDEF int stbir_set_non_pm_alpha_speed_over_quality( STBIR_RESIZE * resize, int non_pma_alpha_speed_over_quality ); +//=============================================================== + + +//=============================================================== +// You can call build_samplers to prebuild all the internal data we need to resample. +// Then, if you call resize_extended many times with the same resize, you only pay the +// cost once. +// If you do call build_samplers, you MUST call free_samplers eventually. +//-------------------------------- + +// This builds the samplers and does one allocation +STBIRDEF int stbir_build_samplers( STBIR_RESIZE * resize ); + +// You MUST call this, if you call stbir_build_samplers or stbir_build_samplers_with_splits +STBIRDEF void stbir_free_samplers( STBIR_RESIZE * resize ); +//=============================================================== + + +// And this is the main function to perform the resize synchronously on one thread. +STBIRDEF int stbir_resize_extended( STBIR_RESIZE * resize ); + + +//=============================================================== +// Use these functions for multithreading. +// 1) You call stbir_build_samplers_with_splits first on the main thread +// 2) Then stbir_resize_with_split on each thread +// 3) stbir_free_samplers when done on the main thread +//-------------------------------- + +// This will build samplers for threading. +// You can pass in the number of threads you'd like to use (try_splits). +// It returns the number of splits (threads) that you can call it with. +/// It might be less if the image resize can't be split up that many ways. + +STBIRDEF int stbir_build_samplers_with_splits( STBIR_RESIZE * resize, int try_splits ); + +// This function does a split of the resizing (you call this fuction for each +// split, on multiple threads). A split is a piece of the output resize pixel space. + +// Note that you MUST call stbir_build_samplers_with_splits before stbir_resize_extended_split! + +// Usually, you will always call stbir_resize_split with split_start as the thread_index +// and "1" for the split_count. +// But, if you have a weird situation where you MIGHT want 8 threads, but sometimes +// only 4 threads, you can use 0,2,4,6 for the split_start's and use "2" for the +// split_count each time to turn in into a 4 thread resize. (This is unusual). + +STBIRDEF int stbir_resize_extended_split( STBIR_RESIZE * resize, int split_start, int split_count ); +//=============================================================== + + +//=============================================================== +// Pixel Callbacks info: +//-------------------------------- + +// The input callback is super flexible - it calls you with the input address +// (based on the stride and base pointer), it gives you an optional_output +// pointer that you can fill, or you can just return your own pointer into +// your own data. +// +// You can also do conversion from non-supported data types if necessary - in +// this case, you ignore the input_ptr and just use the x and y parameters to +// calculate your own input_ptr based on the size of each non-supported pixel. +// (Something like the third example below.) +// +// You can also install just an input or just an output callback by setting the +// callback that you don't want to zero. +// +// First example, progress: (getting a callback that you can monitor the progress): +// void const * my_callback( void * optional_output, void const * input_ptr, int num_pixels, int x, int y, void * context ) +// { +// percentage_done = y / input_height; +// return input_ptr; // use buffer from call +// } +// +// Next example, copying: (copy from some other buffer or stream): +// void const * my_callback( void * optional_output, void const * input_ptr, int num_pixels, int x, int y, void * context ) +// { +// CopyOrStreamData( optional_output, other_data_src, num_pixels * pixel_width_in_bytes ); +// return optional_output; // return the optional buffer that we filled +// } +// +// Third example, input another buffer without copying: (zero-copy from other buffer): +// void const * my_callback( void * optional_output, void const * input_ptr, int num_pixels, int x, int y, void * context ) +// { +// void * pixels = ( (char*) other_image_base ) + ( y * other_image_stride ) + ( x * other_pixel_width_in_bytes ); +// return pixels; // return pointer to your data without copying +// } +// +// +// The output callback is considerably simpler - it just calls you so that you can dump +// out each scanline. You could even directly copy out to disk if you have a simple format +// like TGA or BMP. You can also convert to other output types here if you want. +// +// Simple example: +// void const * my_output( void * output_ptr, int num_pixels, int y, void * context ) +// { +// percentage_done = y / output_height; +// fwrite( output_ptr, pixel_width_in_bytes, num_pixels, output_file ); +// } +//=============================================================== + + + + +//=============================================================== +// optional built-in profiling API +//-------------------------------- + +#ifdef STBIR_PROFILE + +typedef struct STBIR_PROFILE_INFO +{ + stbir_uint64 total_clocks; + + // how many clocks spent (of total_clocks) in the various resize routines, along with a string description + // there are "resize_count" number of zones + stbir_uint64 clocks[ 8 ]; + char const ** descriptions; + + // count of clocks and descriptions + stbir_uint32 count; +} STBIR_PROFILE_INFO; + +// use after calling stbir_resize_extended (or stbir_build_samplers or stbir_build_samplers_with_splits) +STBIRDEF void stbir_resize_build_profile_info( STBIR_PROFILE_INFO * out_info, STBIR_RESIZE const * resize ); + +// use after calling stbir_resize_extended +STBIRDEF void stbir_resize_extended_profile_info( STBIR_PROFILE_INFO * out_info, STBIR_RESIZE const * resize ); + +// use after calling stbir_resize_extended_split +STBIRDEF void stbir_resize_split_profile_info( STBIR_PROFILE_INFO * out_info, STBIR_RESIZE const * resize, int split_start, int split_num ); + +//=============================================================== + +#endif + + +//// end header file ///////////////////////////////////////////////////// +#endif // STBIR_INCLUDE_STB_IMAGE_RESIZE2_H + +#if defined(STB_IMAGE_RESIZE_IMPLEMENTATION) || defined(STB_IMAGE_RESIZE2_IMPLEMENTATION) + +#ifndef STBIR_ASSERT +#include +#define STBIR_ASSERT(x) assert(x) +#endif + +#ifndef STBIR_MALLOC +#include +#define STBIR_MALLOC(size,user_data) ((void)(user_data), malloc(size)) +#define STBIR_FREE(ptr,user_data) ((void)(user_data), free(ptr)) +// (we used the comma operator to evaluate user_data, to avoid "unused parameter" warnings) +#endif + +#ifdef _MSC_VER + +#define stbir__inline __forceinline + +#else + +#define stbir__inline __inline__ + +// Clang address sanitizer +#if defined(__has_feature) + #if __has_feature(address_sanitizer) || __has_feature(memory_sanitizer) + #ifndef STBIR__SEPARATE_ALLOCATIONS + #define STBIR__SEPARATE_ALLOCATIONS + #endif + #endif +#endif + +#endif + +// GCC and MSVC +#if defined(__SANITIZE_ADDRESS__) + #ifndef STBIR__SEPARATE_ALLOCATIONS + #define STBIR__SEPARATE_ALLOCATIONS + #endif +#endif + +// Always turn off automatic FMA use - use STBIR_USE_FMA if you want. +// Otherwise, this is a determinism disaster. +#ifndef STBIR_DONT_CHANGE_FP_CONTRACT // override in case you don't want this behavior +#if defined(_MSC_VER) && !defined(__clang__) +#if _MSC_VER > 1200 +#pragma fp_contract(off) +#endif +#elif defined(__GNUC__) && !defined(__clang__) +#pragma GCC optimize("fp-contract=off") +#else +#pragma STDC FP_CONTRACT OFF +#endif +#endif + +#ifdef _MSC_VER +#define STBIR__UNUSED(v) (void)(v) +#else +#define STBIR__UNUSED(v) (void)sizeof(v) +#endif + +#define STBIR__ARRAY_SIZE(a) (sizeof((a))/sizeof((a)[0])) + + +#ifndef STBIR_DEFAULT_FILTER_UPSAMPLE +#define STBIR_DEFAULT_FILTER_UPSAMPLE STBIR_FILTER_CATMULLROM +#endif + +#ifndef STBIR_DEFAULT_FILTER_DOWNSAMPLE +#define STBIR_DEFAULT_FILTER_DOWNSAMPLE STBIR_FILTER_MITCHELL +#endif + + +#ifndef STBIR__HEADER_FILENAME +#define STBIR__HEADER_FILENAME "stb_image_resize2.h" +#endif + +// the internal pixel layout enums are in a different order, so we can easily do range comparisons of types +// the public pixel layout is ordered in a way that if you cast num_channels (1-4) to the enum, you get something sensible +typedef enum +{ + STBIRI_1CHANNEL = 0, + STBIRI_2CHANNEL = 1, + STBIRI_RGB = 2, + STBIRI_BGR = 3, + STBIRI_4CHANNEL = 4, + + STBIRI_RGBA = 5, + STBIRI_BGRA = 6, + STBIRI_ARGB = 7, + STBIRI_ABGR = 8, + STBIRI_RA = 9, + STBIRI_AR = 10, + + STBIRI_RGBA_PM = 11, + STBIRI_BGRA_PM = 12, + STBIRI_ARGB_PM = 13, + STBIRI_ABGR_PM = 14, + STBIRI_RA_PM = 15, + STBIRI_AR_PM = 16, +} stbir_internal_pixel_layout; + +// define the public pixel layouts to not compile inside the implementation (to avoid accidental use) +#define STBIR_BGR bad_dont_use_in_implementation +#define STBIR_1CHANNEL STBIR_BGR +#define STBIR_2CHANNEL STBIR_BGR +#define STBIR_RGB STBIR_BGR +#define STBIR_RGBA STBIR_BGR +#define STBIR_4CHANNEL STBIR_BGR +#define STBIR_BGRA STBIR_BGR +#define STBIR_ARGB STBIR_BGR +#define STBIR_ABGR STBIR_BGR +#define STBIR_RA STBIR_BGR +#define STBIR_AR STBIR_BGR +#define STBIR_RGBA_PM STBIR_BGR +#define STBIR_BGRA_PM STBIR_BGR +#define STBIR_ARGB_PM STBIR_BGR +#define STBIR_ABGR_PM STBIR_BGR +#define STBIR_RA_PM STBIR_BGR +#define STBIR_AR_PM STBIR_BGR + +// must match stbir_datatype +static unsigned char stbir__type_size[] = { + 1,1,1,2,4,2 // STBIR_TYPE_UINT8,STBIR_TYPE_UINT8_SRGB,STBIR_TYPE_UINT8_SRGB_ALPHA,STBIR_TYPE_UINT16,STBIR_TYPE_FLOAT,STBIR_TYPE_HALF_FLOAT +}; + +// When gathering, the contributors are which source pixels contribute. +// When scattering, the contributors are which destination pixels are contributed to. +typedef struct +{ + int n0; // First contributing pixel + int n1; // Last contributing pixel +} stbir__contributors; + +typedef struct +{ + int lowest; // First sample index for whole filter + int highest; // Last sample index for whole filter + int widest; // widest single set of samples for an output +} stbir__filter_extent_info; + +typedef struct +{ + int n0; // First pixel of decode buffer to write to + int n1; // Last pixel of decode that will be written to + int pixel_offset_for_input; // Pixel offset into input_scanline +} stbir__span; + +typedef struct stbir__scale_info +{ + int input_full_size; + int output_sub_size; + float scale; + float inv_scale; + float pixel_shift; // starting shift in output pixel space (in pixels) + int scale_is_rational; + stbir_uint32 scale_numerator, scale_denominator; +} stbir__scale_info; + +typedef struct +{ + stbir__contributors * contributors; + float* coefficients; + stbir__contributors * gather_prescatter_contributors; + float * gather_prescatter_coefficients; + stbir__scale_info scale_info; + float support; + stbir_filter filter_enum; + stbir__kernel_callback * filter_kernel; + stbir__support_callback * filter_support; + stbir_edge edge; + int coefficient_width; + int filter_pixel_width; + int filter_pixel_margin; + int num_contributors; + int contributors_size; + int coefficients_size; + stbir__filter_extent_info extent_info; + int is_gather; // 0 = scatter, 1 = gather with scale >= 1, 2 = gather with scale < 1 + int gather_prescatter_num_contributors; + int gather_prescatter_coefficient_width; + int gather_prescatter_contributors_size; + int gather_prescatter_coefficients_size; +} stbir__sampler; + +typedef struct +{ + stbir__contributors conservative; + int edge_sizes[2]; // this can be less than filter_pixel_margin, if the filter and scaling falls off + stbir__span spans[2]; // can be two spans, if doing input subrect with clamp mode WRAP +} stbir__extents; + +typedef struct +{ +#ifdef STBIR_PROFILE + union + { + struct { stbir_uint64 total, looping, vertical, horizontal, decode, encode, alpha, unalpha; } named; + stbir_uint64 array[8]; + } profile; + stbir_uint64 * current_zone_excluded_ptr; +#endif + float* decode_buffer; + + int ring_buffer_first_scanline; + int ring_buffer_last_scanline; + int ring_buffer_begin_index; // first_scanline is at this index in the ring buffer + int start_output_y, end_output_y; + int start_input_y, end_input_y; // used in scatter only + + #ifdef STBIR__SEPARATE_ALLOCATIONS + float** ring_buffers; // one pointer for each ring buffer + #else + float* ring_buffer; // one big buffer that we index into + #endif + + float* vertical_buffer; + + char no_cache_straddle[64]; +} stbir__per_split_info; + +typedef float * stbir__decode_pixels_func( float * decode, int width_times_channels, void const * input ); +typedef void stbir__alpha_weight_func( float * decode_buffer, int width_times_channels ); +typedef void stbir__horizontal_gather_channels_func( float * output_buffer, unsigned int output_sub_size, float const * decode_buffer, + stbir__contributors const * horizontal_contributors, float const * horizontal_coefficients, int coefficient_width ); +typedef void stbir__alpha_unweight_func(float * encode_buffer, int width_times_channels ); +typedef void stbir__encode_pixels_func( void * output, int width_times_channels, float const * encode ); + +struct stbir__info +{ +#ifdef STBIR_PROFILE + union + { + struct { stbir_uint64 total, build, alloc, horizontal, vertical, cleanup, pivot; } named; + stbir_uint64 array[7]; + } profile; + stbir_uint64 * current_zone_excluded_ptr; +#endif + stbir__sampler horizontal; + stbir__sampler vertical; + + void const * input_data; + void * output_data; + + int input_stride_bytes; + int output_stride_bytes; + int ring_buffer_length_bytes; // The length of an individual entry in the ring buffer. The total number of ring buffers is stbir__get_filter_pixel_width(filter) + int ring_buffer_num_entries; // Total number of entries in the ring buffer. + + stbir_datatype input_type; + stbir_datatype output_type; + + stbir_input_callback * in_pixels_cb; + void * user_data; + stbir_output_callback * out_pixels_cb; + + stbir__extents scanline_extents; + + void * alloced_mem; + stbir__per_split_info * split_info; // by default 1, but there will be N of these allocated based on the thread init you did + + stbir__decode_pixels_func * decode_pixels; + stbir__alpha_weight_func * alpha_weight; + stbir__horizontal_gather_channels_func * horizontal_gather_channels; + stbir__alpha_unweight_func * alpha_unweight; + stbir__encode_pixels_func * encode_pixels; + + int alloc_ring_buffer_num_entries; // Number of entries in the ring buffer that will be allocated + int splits; // count of splits + + stbir_internal_pixel_layout input_pixel_layout_internal; + stbir_internal_pixel_layout output_pixel_layout_internal; + + int input_color_and_type; + int offset_x, offset_y; // offset within output_data + int vertical_first; + int channels; + int effective_channels; // same as channels, except on RGBA/ARGB (7), or XA/AX (3) + size_t alloced_total; +}; + + +#define stbir__max_uint8_as_float 255.0f +#define stbir__max_uint16_as_float 65535.0f +#define stbir__max_uint8_as_float_inverted 3.9215689e-03f // (1.0f/255.0f) +#define stbir__max_uint16_as_float_inverted 1.5259022e-05f // (1.0f/65535.0f) +#define stbir__small_float ((float)1 / (1 << 20) / (1 << 20) / (1 << 20) / (1 << 20) / (1 << 20) / (1 << 20)) + +// min/max friendly +#define STBIR_CLAMP(x, xmin, xmax) for(;;) { \ + if ( (x) < (xmin) ) (x) = (xmin); \ + if ( (x) > (xmax) ) (x) = (xmax); \ + break; \ +} + +static stbir__inline int stbir__min(int a, int b) +{ + return a < b ? a : b; +} + +static stbir__inline int stbir__max(int a, int b) +{ + return a > b ? a : b; +} + +static float stbir__srgb_uchar_to_linear_float[256] = { + 0.000000f, 0.000304f, 0.000607f, 0.000911f, 0.001214f, 0.001518f, 0.001821f, 0.002125f, 0.002428f, 0.002732f, 0.003035f, + 0.003347f, 0.003677f, 0.004025f, 0.004391f, 0.004777f, 0.005182f, 0.005605f, 0.006049f, 0.006512f, 0.006995f, 0.007499f, + 0.008023f, 0.008568f, 0.009134f, 0.009721f, 0.010330f, 0.010960f, 0.011612f, 0.012286f, 0.012983f, 0.013702f, 0.014444f, + 0.015209f, 0.015996f, 0.016807f, 0.017642f, 0.018500f, 0.019382f, 0.020289f, 0.021219f, 0.022174f, 0.023153f, 0.024158f, + 0.025187f, 0.026241f, 0.027321f, 0.028426f, 0.029557f, 0.030713f, 0.031896f, 0.033105f, 0.034340f, 0.035601f, 0.036889f, + 0.038204f, 0.039546f, 0.040915f, 0.042311f, 0.043735f, 0.045186f, 0.046665f, 0.048172f, 0.049707f, 0.051269f, 0.052861f, + 0.054480f, 0.056128f, 0.057805f, 0.059511f, 0.061246f, 0.063010f, 0.064803f, 0.066626f, 0.068478f, 0.070360f, 0.072272f, + 0.074214f, 0.076185f, 0.078187f, 0.080220f, 0.082283f, 0.084376f, 0.086500f, 0.088656f, 0.090842f, 0.093059f, 0.095307f, + 0.097587f, 0.099899f, 0.102242f, 0.104616f, 0.107023f, 0.109462f, 0.111932f, 0.114435f, 0.116971f, 0.119538f, 0.122139f, + 0.124772f, 0.127438f, 0.130136f, 0.132868f, 0.135633f, 0.138432f, 0.141263f, 0.144128f, 0.147027f, 0.149960f, 0.152926f, + 0.155926f, 0.158961f, 0.162029f, 0.165132f, 0.168269f, 0.171441f, 0.174647f, 0.177888f, 0.181164f, 0.184475f, 0.187821f, + 0.191202f, 0.194618f, 0.198069f, 0.201556f, 0.205079f, 0.208637f, 0.212231f, 0.215861f, 0.219526f, 0.223228f, 0.226966f, + 0.230740f, 0.234551f, 0.238398f, 0.242281f, 0.246201f, 0.250158f, 0.254152f, 0.258183f, 0.262251f, 0.266356f, 0.270498f, + 0.274677f, 0.278894f, 0.283149f, 0.287441f, 0.291771f, 0.296138f, 0.300544f, 0.304987f, 0.309469f, 0.313989f, 0.318547f, + 0.323143f, 0.327778f, 0.332452f, 0.337164f, 0.341914f, 0.346704f, 0.351533f, 0.356400f, 0.361307f, 0.366253f, 0.371238f, + 0.376262f, 0.381326f, 0.386430f, 0.391573f, 0.396755f, 0.401978f, 0.407240f, 0.412543f, 0.417885f, 0.423268f, 0.428691f, + 0.434154f, 0.439657f, 0.445201f, 0.450786f, 0.456411f, 0.462077f, 0.467784f, 0.473532f, 0.479320f, 0.485150f, 0.491021f, + 0.496933f, 0.502887f, 0.508881f, 0.514918f, 0.520996f, 0.527115f, 0.533276f, 0.539480f, 0.545725f, 0.552011f, 0.558340f, + 0.564712f, 0.571125f, 0.577581f, 0.584078f, 0.590619f, 0.597202f, 0.603827f, 0.610496f, 0.617207f, 0.623960f, 0.630757f, + 0.637597f, 0.644480f, 0.651406f, 0.658375f, 0.665387f, 0.672443f, 0.679543f, 0.686685f, 0.693872f, 0.701102f, 0.708376f, + 0.715694f, 0.723055f, 0.730461f, 0.737911f, 0.745404f, 0.752942f, 0.760525f, 0.768151f, 0.775822f, 0.783538f, 0.791298f, + 0.799103f, 0.806952f, 0.814847f, 0.822786f, 0.830770f, 0.838799f, 0.846873f, 0.854993f, 0.863157f, 0.871367f, 0.879622f, + 0.887923f, 0.896269f, 0.904661f, 0.913099f, 0.921582f, 0.930111f, 0.938686f, 0.947307f, 0.955974f, 0.964686f, 0.973445f, + 0.982251f, 0.991102f, 1.0f +}; + +typedef union +{ + unsigned int u; + float f; +} stbir__FP32; + +// From https://gist.github.com/rygorous/2203834 + +static const stbir_uint32 fp32_to_srgb8_tab4[104] = { + 0x0073000d, 0x007a000d, 0x0080000d, 0x0087000d, 0x008d000d, 0x0094000d, 0x009a000d, 0x00a1000d, + 0x00a7001a, 0x00b4001a, 0x00c1001a, 0x00ce001a, 0x00da001a, 0x00e7001a, 0x00f4001a, 0x0101001a, + 0x010e0033, 0x01280033, 0x01410033, 0x015b0033, 0x01750033, 0x018f0033, 0x01a80033, 0x01c20033, + 0x01dc0067, 0x020f0067, 0x02430067, 0x02760067, 0x02aa0067, 0x02dd0067, 0x03110067, 0x03440067, + 0x037800ce, 0x03df00ce, 0x044600ce, 0x04ad00ce, 0x051400ce, 0x057b00c5, 0x05dd00bc, 0x063b00b5, + 0x06970158, 0x07420142, 0x07e30130, 0x087b0120, 0x090b0112, 0x09940106, 0x0a1700fc, 0x0a9500f2, + 0x0b0f01cb, 0x0bf401ae, 0x0ccb0195, 0x0d950180, 0x0e56016e, 0x0f0d015e, 0x0fbc0150, 0x10630143, + 0x11070264, 0x1238023e, 0x1357021d, 0x14660201, 0x156601e9, 0x165a01d3, 0x174401c0, 0x182401af, + 0x18fe0331, 0x1a9602fe, 0x1c1502d2, 0x1d7e02ad, 0x1ed4028d, 0x201a0270, 0x21520256, 0x227d0240, + 0x239f0443, 0x25c003fe, 0x27bf03c4, 0x29a10392, 0x2b6a0367, 0x2d1d0341, 0x2ebe031f, 0x304d0300, + 0x31d105b0, 0x34a80555, 0x37520507, 0x39d504c5, 0x3c37048b, 0x3e7c0458, 0x40a8042a, 0x42bd0401, + 0x44c20798, 0x488e071e, 0x4c1c06b6, 0x4f76065d, 0x52a50610, 0x55ac05cc, 0x5892058f, 0x5b590559, + 0x5e0c0a23, 0x631c0980, 0x67db08f6, 0x6c55087f, 0x70940818, 0x74a007bd, 0x787d076c, 0x7c330723, +}; + +static stbir__inline stbir_uint8 stbir__linear_to_srgb_uchar(float in) +{ + static const stbir__FP32 almostone = { 0x3f7fffff }; // 1-eps + static const stbir__FP32 minval = { (127-13) << 23 }; + stbir_uint32 tab,bias,scale,t; + stbir__FP32 f; + + // Clamp to [2^(-13), 1-eps]; these two values map to 0 and 1, respectively. + // The tests are carefully written so that NaNs map to 0, same as in the reference + // implementation. + if (!(in > minval.f)) // written this way to catch NaNs + return 0; + if (in > almostone.f) + return 255; + + // Do the table lookup and unpack bias, scale + f.f = in; + tab = fp32_to_srgb8_tab4[(f.u - minval.u) >> 20]; + bias = (tab >> 16) << 9; + scale = tab & 0xffff; + + // Grab next-highest mantissa bits and perform linear interpolation + t = (f.u >> 12) & 0xff; + return (unsigned char) ((bias + scale*t) >> 16); +} + +#ifndef STBIR_FORCE_GATHER_FILTER_SCANLINES_AMOUNT +#define STBIR_FORCE_GATHER_FILTER_SCANLINES_AMOUNT 32 // when downsampling and <= 32 scanlines of buffering, use gather. gather used down to 1/8th scaling for 25% win. +#endif + +#ifndef STBIR_FORCE_MINIMUM_SCANLINES_FOR_SPLITS +#define STBIR_FORCE_MINIMUM_SCANLINES_FOR_SPLITS 4 // when threading, what is the minimum number of scanlines for a split? +#endif + +#define STBIR_INPUT_CALLBACK_PADDING 3 + +#ifdef _M_IX86_FP +#if ( _M_IX86_FP >= 1 ) +#ifndef STBIR_SSE +#define STBIR_SSE +#endif +#endif +#endif + +#ifdef __TINYC__ + // tiny c has no intrinsics yet - this can become a version check if they add them + #define STBIR_NO_SIMD +#endif + +#if defined(_x86_64) || defined( __x86_64__ ) || defined( _M_X64 ) || defined(__x86_64) || defined(_M_AMD64) || defined(__SSE2__) || defined(STBIR_SSE) || defined(STBIR_SSE2) + #ifndef STBIR_SSE2 + #define STBIR_SSE2 + #endif + #if defined(__AVX__) || defined(STBIR_AVX2) + #ifndef STBIR_AVX + #ifndef STBIR_NO_AVX + #define STBIR_AVX + #endif + #endif + #endif + #if defined(__AVX2__) || defined(STBIR_AVX2) + #ifndef STBIR_NO_AVX2 + #ifndef STBIR_AVX2 + #define STBIR_AVX2 + #endif + #if defined( _MSC_VER ) && !defined(__clang__) + #ifndef STBIR_FP16C // FP16C instructions are on all AVX2 cpus, so we can autoselect it here on microsoft - clang needs -mf16c + #define STBIR_FP16C + #endif + #endif + #endif + #endif + #ifdef __F16C__ + #ifndef STBIR_FP16C // turn on FP16C instructions if the define is set (for clang and gcc) + #define STBIR_FP16C + #endif + #endif +#endif + +#if defined( _M_ARM64 ) || defined( __aarch64__ ) || defined( __arm64__ ) || ((__ARM_NEON_FP & 4) != 0) || defined(__ARM_NEON__) +#ifndef STBIR_NEON +#define STBIR_NEON +#endif +#endif + +#if defined(_M_ARM) || defined(__arm__) +#ifdef STBIR_USE_FMA +#undef STBIR_USE_FMA // no FMA for 32-bit arm on MSVC +#endif +#endif + +#if defined(__wasm__) && defined(__wasm_simd128__) +#ifndef STBIR_WASM +#define STBIR_WASM +#endif +#endif + +// restrict pointers for the output pointers, other loop and unroll control +#if defined( _MSC_VER ) && !defined(__clang__) + #define STBIR_STREAMOUT_PTR( star ) star __restrict + #define STBIR_NO_UNROLL( ptr ) __assume(ptr) // this oddly keeps msvc from unrolling a loop + #if _MSC_VER >= 1900 + #define STBIR_NO_UNROLL_LOOP_START __pragma(loop( no_vector )) + #else + #define STBIR_NO_UNROLL_LOOP_START + #endif +#elif defined( __clang__ ) + #define STBIR_STREAMOUT_PTR( star ) star __restrict__ + #define STBIR_NO_UNROLL( ptr ) __asm__ (""::"r"(ptr)) + #if ( __clang_major__ >= 4 ) || ( ( __clang_major__ >= 3 ) && ( __clang_minor__ >= 5 ) ) + #define STBIR_NO_UNROLL_LOOP_START _Pragma("clang loop unroll(disable)") _Pragma("clang loop vectorize(disable)") + #else + #define STBIR_NO_UNROLL_LOOP_START + #endif +#elif defined( __GNUC__ ) + #define STBIR_STREAMOUT_PTR( star ) star __restrict__ + #define STBIR_NO_UNROLL( ptr ) __asm__ (""::"r"(ptr)) + #if __GNUC__ >= 14 + #define STBIR_NO_UNROLL_LOOP_START _Pragma("GCC unroll 0") _Pragma("GCC novector") + #else + #define STBIR_NO_UNROLL_LOOP_START + #endif + #define STBIR_NO_UNROLL_LOOP_START_INF_FOR +#else + #define STBIR_STREAMOUT_PTR( star ) star + #define STBIR_NO_UNROLL( ptr ) + #define STBIR_NO_UNROLL_LOOP_START +#endif + +#ifndef STBIR_NO_UNROLL_LOOP_START_INF_FOR +#define STBIR_NO_UNROLL_LOOP_START_INF_FOR STBIR_NO_UNROLL_LOOP_START +#endif + +#ifdef STBIR_NO_SIMD // force simd off for whatever reason + +// force simd off overrides everything else, so clear it all + +#ifdef STBIR_SSE2 +#undef STBIR_SSE2 +#endif + +#ifdef STBIR_AVX +#undef STBIR_AVX +#endif + +#ifdef STBIR_NEON +#undef STBIR_NEON +#endif + +#ifdef STBIR_AVX2 +#undef STBIR_AVX2 +#endif + +#ifdef STBIR_FP16C +#undef STBIR_FP16C +#endif + +#ifdef STBIR_WASM +#undef STBIR_WASM +#endif + +#ifdef STBIR_SIMD +#undef STBIR_SIMD +#endif + +#else // STBIR_SIMD + +#ifdef STBIR_SSE2 + #include + + #define stbir__simdf __m128 + #define stbir__simdi __m128i + + #define stbir_simdi_castf( reg ) _mm_castps_si128(reg) + #define stbir_simdf_casti( reg ) _mm_castsi128_ps(reg) + + #define stbir__simdf_load( reg, ptr ) (reg) = _mm_loadu_ps( (float const*)(ptr) ) + #define stbir__simdi_load( reg, ptr ) (reg) = _mm_loadu_si128 ( (stbir__simdi const*)(ptr) ) + #define stbir__simdf_load1( out, ptr ) (out) = _mm_load_ss( (float const*)(ptr) ) // top values can be random (not denormal or nan for perf) + #define stbir__simdi_load1( out, ptr ) (out) = _mm_castps_si128( _mm_load_ss( (float const*)(ptr) )) + #define stbir__simdf_load1z( out, ptr ) (out) = _mm_load_ss( (float const*)(ptr) ) // top values must be zero + #define stbir__simdf_frep4( fvar ) _mm_set_ps1( fvar ) + #define stbir__simdf_load1frep4( out, fvar ) (out) = _mm_set_ps1( fvar ) + #define stbir__simdf_load2( out, ptr ) (out) = _mm_castsi128_ps( _mm_loadl_epi64( (__m128i*)(ptr)) ) // top values can be random (not denormal or nan for perf) + #define stbir__simdf_load2z( out, ptr ) (out) = _mm_castsi128_ps( _mm_loadl_epi64( (__m128i*)(ptr)) ) // top values must be zero + #define stbir__simdf_load2hmerge( out, reg, ptr ) (out) = _mm_castpd_ps(_mm_loadh_pd( _mm_castps_pd(reg), (double*)(ptr) )) + + #define stbir__simdf_zeroP() _mm_setzero_ps() + #define stbir__simdf_zero( reg ) (reg) = _mm_setzero_ps() + + #define stbir__simdf_store( ptr, reg ) _mm_storeu_ps( (float*)(ptr), reg ) + #define stbir__simdf_store1( ptr, reg ) _mm_store_ss( (float*)(ptr), reg ) + #define stbir__simdf_store2( ptr, reg ) _mm_storel_epi64( (__m128i*)(ptr), _mm_castps_si128(reg) ) + #define stbir__simdf_store2h( ptr, reg ) _mm_storeh_pd( (double*)(ptr), _mm_castps_pd(reg) ) + + #define stbir__simdi_store( ptr, reg ) _mm_storeu_si128( (__m128i*)(ptr), reg ) + #define stbir__simdi_store1( ptr, reg ) _mm_store_ss( (float*)(ptr), _mm_castsi128_ps(reg) ) + #define stbir__simdi_store2( ptr, reg ) _mm_storel_epi64( (__m128i*)(ptr), (reg) ) + + #define stbir__prefetch( ptr ) _mm_prefetch((char*)(ptr), _MM_HINT_T0 ) + + #define stbir__simdi_expand_u8_to_u32(out0,out1,out2,out3,ireg) \ + { \ + stbir__simdi zero = _mm_setzero_si128(); \ + out2 = _mm_unpacklo_epi8( ireg, zero ); \ + out3 = _mm_unpackhi_epi8( ireg, zero ); \ + out0 = _mm_unpacklo_epi16( out2, zero ); \ + out1 = _mm_unpackhi_epi16( out2, zero ); \ + out2 = _mm_unpacklo_epi16( out3, zero ); \ + out3 = _mm_unpackhi_epi16( out3, zero ); \ + } + +#define stbir__simdi_expand_u8_to_1u32(out,ireg) \ + { \ + stbir__simdi zero = _mm_setzero_si128(); \ + out = _mm_unpacklo_epi8( ireg, zero ); \ + out = _mm_unpacklo_epi16( out, zero ); \ + } + + #define stbir__simdi_expand_u16_to_u32(out0,out1,ireg) \ + { \ + stbir__simdi zero = _mm_setzero_si128(); \ + out0 = _mm_unpacklo_epi16( ireg, zero ); \ + out1 = _mm_unpackhi_epi16( ireg, zero ); \ + } + + #define stbir__simdf_convert_float_to_i32( i, f ) (i) = _mm_cvttps_epi32(f) + #define stbir__simdf_convert_float_to_int( f ) _mm_cvtt_ss2si(f) + #define stbir__simdf_convert_float_to_uint8( f ) ((unsigned char)_mm_cvtsi128_si32(_mm_cvttps_epi32(_mm_max_ps(_mm_min_ps(f,STBIR__CONSTF(STBIR_max_uint8_as_float)),_mm_setzero_ps())))) + #define stbir__simdf_convert_float_to_short( f ) ((unsigned short)_mm_cvtsi128_si32(_mm_cvttps_epi32(_mm_max_ps(_mm_min_ps(f,STBIR__CONSTF(STBIR_max_uint16_as_float)),_mm_setzero_ps())))) + + #define stbir__simdi_to_int( i ) _mm_cvtsi128_si32(i) + #define stbir__simdi_convert_i32_to_float(out, ireg) (out) = _mm_cvtepi32_ps( ireg ) + #define stbir__simdf_add( out, reg0, reg1 ) (out) = _mm_add_ps( reg0, reg1 ) + #define stbir__simdf_mult( out, reg0, reg1 ) (out) = _mm_mul_ps( reg0, reg1 ) + #define stbir__simdf_mult_mem( out, reg, ptr ) (out) = _mm_mul_ps( reg, _mm_loadu_ps( (float const*)(ptr) ) ) + #define stbir__simdf_mult1_mem( out, reg, ptr ) (out) = _mm_mul_ss( reg, _mm_load_ss( (float const*)(ptr) ) ) + #define stbir__simdf_add_mem( out, reg, ptr ) (out) = _mm_add_ps( reg, _mm_loadu_ps( (float const*)(ptr) ) ) + #define stbir__simdf_add1_mem( out, reg, ptr ) (out) = _mm_add_ss( reg, _mm_load_ss( (float const*)(ptr) ) ) + + #ifdef STBIR_USE_FMA // not on by default to maintain bit identical simd to non-simd + #include + #define stbir__simdf_madd( out, add, mul1, mul2 ) (out) = _mm_fmadd_ps( mul1, mul2, add ) + #define stbir__simdf_madd1( out, add, mul1, mul2 ) (out) = _mm_fmadd_ss( mul1, mul2, add ) + #define stbir__simdf_madd_mem( out, add, mul, ptr ) (out) = _mm_fmadd_ps( mul, _mm_loadu_ps( (float const*)(ptr) ), add ) + #define stbir__simdf_madd1_mem( out, add, mul, ptr ) (out) = _mm_fmadd_ss( mul, _mm_load_ss( (float const*)(ptr) ), add ) + #else + #define stbir__simdf_madd( out, add, mul1, mul2 ) (out) = _mm_add_ps( add, _mm_mul_ps( mul1, mul2 ) ) + #define stbir__simdf_madd1( out, add, mul1, mul2 ) (out) = _mm_add_ss( add, _mm_mul_ss( mul1, mul2 ) ) + #define stbir__simdf_madd_mem( out, add, mul, ptr ) (out) = _mm_add_ps( add, _mm_mul_ps( mul, _mm_loadu_ps( (float const*)(ptr) ) ) ) + #define stbir__simdf_madd1_mem( out, add, mul, ptr ) (out) = _mm_add_ss( add, _mm_mul_ss( mul, _mm_load_ss( (float const*)(ptr) ) ) ) + #endif + + #define stbir__simdf_add1( out, reg0, reg1 ) (out) = _mm_add_ss( reg0, reg1 ) + #define stbir__simdf_mult1( out, reg0, reg1 ) (out) = _mm_mul_ss( reg0, reg1 ) + + #define stbir__simdf_and( out, reg0, reg1 ) (out) = _mm_and_ps( reg0, reg1 ) + #define stbir__simdf_or( out, reg0, reg1 ) (out) = _mm_or_ps( reg0, reg1 ) + + #define stbir__simdf_min( out, reg0, reg1 ) (out) = _mm_min_ps( reg0, reg1 ) + #define stbir__simdf_max( out, reg0, reg1 ) (out) = _mm_max_ps( reg0, reg1 ) + #define stbir__simdf_min1( out, reg0, reg1 ) (out) = _mm_min_ss( reg0, reg1 ) + #define stbir__simdf_max1( out, reg0, reg1 ) (out) = _mm_max_ss( reg0, reg1 ) + + #define stbir__simdf_0123ABCDto3ABx( out, reg0, reg1 ) (out)=_mm_castsi128_ps( _mm_shuffle_epi32( _mm_castps_si128( _mm_shuffle_ps( reg1,reg0, (0<<0) + (1<<2) + (2<<4) + (3<<6) )), (3<<0) + (0<<2) + (1<<4) + (2<<6) ) ) + #define stbir__simdf_0123ABCDto23Ax( out, reg0, reg1 ) (out)=_mm_castsi128_ps( _mm_shuffle_epi32( _mm_castps_si128( _mm_shuffle_ps( reg1,reg0, (0<<0) + (1<<2) + (2<<4) + (3<<6) )), (2<<0) + (3<<2) + (0<<4) + (1<<6) ) ) + + static const stbir__simdf STBIR_zeroones = { 0.0f,1.0f,0.0f,1.0f }; + static const stbir__simdf STBIR_onezeros = { 1.0f,0.0f,1.0f,0.0f }; + #define stbir__simdf_aaa1( out, alp, ones ) (out)=_mm_castsi128_ps( _mm_shuffle_epi32( _mm_castps_si128( _mm_movehl_ps( ones, alp ) ), (1<<0) + (1<<2) + (1<<4) + (2<<6) ) ) + #define stbir__simdf_1aaa( out, alp, ones ) (out)=_mm_castsi128_ps( _mm_shuffle_epi32( _mm_castps_si128( _mm_movelh_ps( ones, alp ) ), (0<<0) + (2<<2) + (2<<4) + (2<<6) ) ) + #define stbir__simdf_a1a1( out, alp, ones) (out) = _mm_or_ps( _mm_castsi128_ps( _mm_srli_epi64( _mm_castps_si128(alp), 32 ) ), STBIR_zeroones ) + #define stbir__simdf_1a1a( out, alp, ones) (out) = _mm_or_ps( _mm_castsi128_ps( _mm_slli_epi64( _mm_castps_si128(alp), 32 ) ), STBIR_onezeros ) + + #define stbir__simdf_swiz( reg, one, two, three, four ) _mm_castsi128_ps( _mm_shuffle_epi32( _mm_castps_si128( reg ), (one<<0) + (two<<2) + (three<<4) + (four<<6) ) ) + + #define stbir__simdi_and( out, reg0, reg1 ) (out) = _mm_and_si128( reg0, reg1 ) + #define stbir__simdi_or( out, reg0, reg1 ) (out) = _mm_or_si128( reg0, reg1 ) + #define stbir__simdi_16madd( out, reg0, reg1 ) (out) = _mm_madd_epi16( reg0, reg1 ) + + #define stbir__simdf_pack_to_8bytes(out,aa,bb) \ + { \ + stbir__simdf af,bf; \ + stbir__simdi a,b; \ + af = _mm_min_ps( aa, STBIR_max_uint8_as_float ); \ + bf = _mm_min_ps( bb, STBIR_max_uint8_as_float ); \ + af = _mm_max_ps( af, _mm_setzero_ps() ); \ + bf = _mm_max_ps( bf, _mm_setzero_ps() ); \ + a = _mm_cvttps_epi32( af ); \ + b = _mm_cvttps_epi32( bf ); \ + a = _mm_packs_epi32( a, b ); \ + out = _mm_packus_epi16( a, a ); \ + } + + #define stbir__simdf_load4_transposed( o0, o1, o2, o3, ptr ) \ + stbir__simdf_load( o0, (ptr) ); \ + stbir__simdf_load( o1, (ptr)+4 ); \ + stbir__simdf_load( o2, (ptr)+8 ); \ + stbir__simdf_load( o3, (ptr)+12 ); \ + { \ + __m128 tmp0, tmp1, tmp2, tmp3; \ + tmp0 = _mm_unpacklo_ps(o0, o1); \ + tmp2 = _mm_unpacklo_ps(o2, o3); \ + tmp1 = _mm_unpackhi_ps(o0, o1); \ + tmp3 = _mm_unpackhi_ps(o2, o3); \ + o0 = _mm_movelh_ps(tmp0, tmp2); \ + o1 = _mm_movehl_ps(tmp2, tmp0); \ + o2 = _mm_movelh_ps(tmp1, tmp3); \ + o3 = _mm_movehl_ps(tmp3, tmp1); \ + } + + #define stbir__interleave_pack_and_store_16_u8( ptr, r0, r1, r2, r3 ) \ + r0 = _mm_packs_epi32( r0, r1 ); \ + r2 = _mm_packs_epi32( r2, r3 ); \ + r1 = _mm_unpacklo_epi16( r0, r2 ); \ + r3 = _mm_unpackhi_epi16( r0, r2 ); \ + r0 = _mm_unpacklo_epi16( r1, r3 ); \ + r2 = _mm_unpackhi_epi16( r1, r3 ); \ + r0 = _mm_packus_epi16( r0, r2 ); \ + stbir__simdi_store( ptr, r0 ); \ + + #define stbir__simdi_32shr( out, reg, imm ) out = _mm_srli_epi32( reg, imm ) + + #if defined(_MSC_VER) && !defined(__clang__) + // msvc inits with 8 bytes + #define STBIR__CONST_32_TO_8( v ) (char)(unsigned char)((v)&255),(char)(unsigned char)(((v)>>8)&255),(char)(unsigned char)(((v)>>16)&255),(char)(unsigned char)(((v)>>24)&255) + #define STBIR__CONST_4_32i( v ) STBIR__CONST_32_TO_8( v ), STBIR__CONST_32_TO_8( v ), STBIR__CONST_32_TO_8( v ), STBIR__CONST_32_TO_8( v ) + #define STBIR__CONST_4d_32i( v0, v1, v2, v3 ) STBIR__CONST_32_TO_8( v0 ), STBIR__CONST_32_TO_8( v1 ), STBIR__CONST_32_TO_8( v2 ), STBIR__CONST_32_TO_8( v3 ) + #else + // everything else inits with long long's + #define STBIR__CONST_4_32i( v ) (long long)((((stbir_uint64)(stbir_uint32)(v))<<32)|((stbir_uint64)(stbir_uint32)(v))),(long long)((((stbir_uint64)(stbir_uint32)(v))<<32)|((stbir_uint64)(stbir_uint32)(v))) + #define STBIR__CONST_4d_32i( v0, v1, v2, v3 ) (long long)((((stbir_uint64)(stbir_uint32)(v1))<<32)|((stbir_uint64)(stbir_uint32)(v0))),(long long)((((stbir_uint64)(stbir_uint32)(v3))<<32)|((stbir_uint64)(stbir_uint32)(v2))) + #endif + + #define STBIR__SIMDF_CONST(var, x) stbir__simdf var = { x, x, x, x } + #define STBIR__SIMDI_CONST(var, x) stbir__simdi var = { STBIR__CONST_4_32i(x) } + #define STBIR__CONSTF(var) (var) + #define STBIR__CONSTI(var) (var) + + #if defined(STBIR_AVX) || defined(__SSE4_1__) + #include + #define stbir__simdf_pack_to_8words(out,reg0,reg1) out = _mm_packus_epi32(_mm_cvttps_epi32(_mm_max_ps(_mm_min_ps(reg0,STBIR__CONSTF(STBIR_max_uint16_as_float)),_mm_setzero_ps())), _mm_cvttps_epi32(_mm_max_ps(_mm_min_ps(reg1,STBIR__CONSTF(STBIR_max_uint16_as_float)),_mm_setzero_ps()))) + #else + static STBIR__SIMDI_CONST(stbir__s32_32768, 32768); + static STBIR__SIMDI_CONST(stbir__s16_32768, ((32768<<16)|32768)); + + #define stbir__simdf_pack_to_8words(out,reg0,reg1) \ + { \ + stbir__simdi tmp0,tmp1; \ + tmp0 = _mm_cvttps_epi32(_mm_max_ps(_mm_min_ps(reg0,STBIR__CONSTF(STBIR_max_uint16_as_float)),_mm_setzero_ps())); \ + tmp1 = _mm_cvttps_epi32(_mm_max_ps(_mm_min_ps(reg1,STBIR__CONSTF(STBIR_max_uint16_as_float)),_mm_setzero_ps())); \ + tmp0 = _mm_sub_epi32( tmp0, stbir__s32_32768 ); \ + tmp1 = _mm_sub_epi32( tmp1, stbir__s32_32768 ); \ + out = _mm_packs_epi32( tmp0, tmp1 ); \ + out = _mm_sub_epi16( out, stbir__s16_32768 ); \ + } + + #endif + + #define STBIR_SIMD + + // if we detect AVX, set the simd8 defines + #ifdef STBIR_AVX + #include + #define STBIR_SIMD8 + #define stbir__simdf8 __m256 + #define stbir__simdi8 __m256i + #define stbir__simdf8_load( out, ptr ) (out) = _mm256_loadu_ps( (float const *)(ptr) ) + #define stbir__simdi8_load( out, ptr ) (out) = _mm256_loadu_si256( (__m256i const *)(ptr) ) + #define stbir__simdf8_mult( out, a, b ) (out) = _mm256_mul_ps( (a), (b) ) + #define stbir__simdf8_store( ptr, out ) _mm256_storeu_ps( (float*)(ptr), out ) + #define stbir__simdi8_store( ptr, reg ) _mm256_storeu_si256( (__m256i*)(ptr), reg ) + #define stbir__simdf8_frep8( fval ) _mm256_set1_ps( fval ) + + #define stbir__simdf8_min( out, reg0, reg1 ) (out) = _mm256_min_ps( reg0, reg1 ) + #define stbir__simdf8_max( out, reg0, reg1 ) (out) = _mm256_max_ps( reg0, reg1 ) + + #define stbir__simdf8_add4halves( out, bot4, top8 ) (out) = _mm_add_ps( bot4, _mm256_extractf128_ps( top8, 1 ) ) + #define stbir__simdf8_mult_mem( out, reg, ptr ) (out) = _mm256_mul_ps( reg, _mm256_loadu_ps( (float const*)(ptr) ) ) + #define stbir__simdf8_add_mem( out, reg, ptr ) (out) = _mm256_add_ps( reg, _mm256_loadu_ps( (float const*)(ptr) ) ) + #define stbir__simdf8_add( out, a, b ) (out) = _mm256_add_ps( a, b ) + #define stbir__simdf8_load1b( out, ptr ) (out) = _mm256_broadcast_ss( ptr ) + #define stbir__simdf_load1rep4( out, ptr ) (out) = _mm_broadcast_ss( ptr ) // avx load instruction + + #define stbir__simdi8_convert_i32_to_float(out, ireg) (out) = _mm256_cvtepi32_ps( ireg ) + #define stbir__simdf8_convert_float_to_i32( i, f ) (i) = _mm256_cvttps_epi32(f) + + #define stbir__simdf8_bot4s( out, a, b ) (out) = _mm256_permute2f128_ps(a,b, (0<<0)+(2<<4) ) + #define stbir__simdf8_top4s( out, a, b ) (out) = _mm256_permute2f128_ps(a,b, (1<<0)+(3<<4) ) + + #define stbir__simdf8_gettop4( reg ) _mm256_extractf128_ps(reg,1) + + #ifdef STBIR_AVX2 + + #define stbir__simdi8_expand_u8_to_u32(out0,out1,ireg) \ + { \ + stbir__simdi8 a, zero =_mm256_setzero_si256();\ + a = _mm256_permute4x64_epi64( _mm256_unpacklo_epi8( _mm256_permute4x64_epi64(_mm256_castsi128_si256(ireg),(0<<0)+(2<<2)+(1<<4)+(3<<6)), zero ),(0<<0)+(2<<2)+(1<<4)+(3<<6)); \ + out0 = _mm256_unpacklo_epi16( a, zero ); \ + out1 = _mm256_unpackhi_epi16( a, zero ); \ + } + + #define stbir__simdf8_pack_to_16bytes(out,aa,bb) \ + { \ + stbir__simdi8 t; \ + stbir__simdf8 af,bf; \ + stbir__simdi8 a,b; \ + af = _mm256_min_ps( aa, STBIR_max_uint8_as_floatX ); \ + bf = _mm256_min_ps( bb, STBIR_max_uint8_as_floatX ); \ + af = _mm256_max_ps( af, _mm256_setzero_ps() ); \ + bf = _mm256_max_ps( bf, _mm256_setzero_ps() ); \ + a = _mm256_cvttps_epi32( af ); \ + b = _mm256_cvttps_epi32( bf ); \ + t = _mm256_permute4x64_epi64( _mm256_packs_epi32( a, b ), (0<<0)+(2<<2)+(1<<4)+(3<<6) ); \ + out = _mm256_castsi256_si128( _mm256_permute4x64_epi64( _mm256_packus_epi16( t, t ), (0<<0)+(2<<2)+(1<<4)+(3<<6) ) ); \ + } + + #define stbir__simdi8_expand_u16_to_u32(out,ireg) out = _mm256_unpacklo_epi16( _mm256_permute4x64_epi64(_mm256_castsi128_si256(ireg),(0<<0)+(2<<2)+(1<<4)+(3<<6)), _mm256_setzero_si256() ); + + #define stbir__simdf8_pack_to_16words(out,aa,bb) \ + { \ + stbir__simdf8 af,bf; \ + stbir__simdi8 a,b; \ + af = _mm256_min_ps( aa, STBIR_max_uint16_as_floatX ); \ + bf = _mm256_min_ps( bb, STBIR_max_uint16_as_floatX ); \ + af = _mm256_max_ps( af, _mm256_setzero_ps() ); \ + bf = _mm256_max_ps( bf, _mm256_setzero_ps() ); \ + a = _mm256_cvttps_epi32( af ); \ + b = _mm256_cvttps_epi32( bf ); \ + (out) = _mm256_permute4x64_epi64( _mm256_packus_epi32(a, b), (0<<0)+(2<<2)+(1<<4)+(3<<6) ); \ + } + + #else + + #define stbir__simdi8_expand_u8_to_u32(out0,out1,ireg) \ + { \ + stbir__simdi a,zero = _mm_setzero_si128(); \ + a = _mm_unpacklo_epi8( ireg, zero ); \ + out0 = _mm256_setr_m128i( _mm_unpacklo_epi16( a, zero ), _mm_unpackhi_epi16( a, zero ) ); \ + a = _mm_unpackhi_epi8( ireg, zero ); \ + out1 = _mm256_setr_m128i( _mm_unpacklo_epi16( a, zero ), _mm_unpackhi_epi16( a, zero ) ); \ + } + + #define stbir__simdf8_pack_to_16bytes(out,aa,bb) \ + { \ + stbir__simdi t; \ + stbir__simdf8 af,bf; \ + stbir__simdi8 a,b; \ + af = _mm256_min_ps( aa, STBIR_max_uint8_as_floatX ); \ + bf = _mm256_min_ps( bb, STBIR_max_uint8_as_floatX ); \ + af = _mm256_max_ps( af, _mm256_setzero_ps() ); \ + bf = _mm256_max_ps( bf, _mm256_setzero_ps() ); \ + a = _mm256_cvttps_epi32( af ); \ + b = _mm256_cvttps_epi32( bf ); \ + out = _mm_packs_epi32( _mm256_castsi256_si128(a), _mm256_extractf128_si256( a, 1 ) ); \ + out = _mm_packus_epi16( out, out ); \ + t = _mm_packs_epi32( _mm256_castsi256_si128(b), _mm256_extractf128_si256( b, 1 ) ); \ + t = _mm_packus_epi16( t, t ); \ + out = _mm_castps_si128( _mm_shuffle_ps( _mm_castsi128_ps(out), _mm_castsi128_ps(t), (0<<0)+(1<<2)+(0<<4)+(1<<6) ) ); \ + } + + #define stbir__simdi8_expand_u16_to_u32(out,ireg) \ + { \ + stbir__simdi a,b,zero = _mm_setzero_si128(); \ + a = _mm_unpacklo_epi16( ireg, zero ); \ + b = _mm_unpackhi_epi16( ireg, zero ); \ + out = _mm256_insertf128_si256( _mm256_castsi128_si256( a ), b, 1 ); \ + } + + #define stbir__simdf8_pack_to_16words(out,aa,bb) \ + { \ + stbir__simdi t0,t1; \ + stbir__simdf8 af,bf; \ + stbir__simdi8 a,b; \ + af = _mm256_min_ps( aa, STBIR_max_uint16_as_floatX ); \ + bf = _mm256_min_ps( bb, STBIR_max_uint16_as_floatX ); \ + af = _mm256_max_ps( af, _mm256_setzero_ps() ); \ + bf = _mm256_max_ps( bf, _mm256_setzero_ps() ); \ + a = _mm256_cvttps_epi32( af ); \ + b = _mm256_cvttps_epi32( bf ); \ + t0 = _mm_packus_epi32( _mm256_castsi256_si128(a), _mm256_extractf128_si256( a, 1 ) ); \ + t1 = _mm_packus_epi32( _mm256_castsi256_si128(b), _mm256_extractf128_si256( b, 1 ) ); \ + out = _mm256_setr_m128i( t0, t1 ); \ + } + + #endif + + static __m256i stbir_00001111 = { STBIR__CONST_4d_32i( 0, 0, 0, 0 ), STBIR__CONST_4d_32i( 1, 1, 1, 1 ) }; + #define stbir__simdf8_0123to00001111( out, in ) (out) = _mm256_permutevar_ps ( in, stbir_00001111 ) + + static __m256i stbir_22223333 = { STBIR__CONST_4d_32i( 2, 2, 2, 2 ), STBIR__CONST_4d_32i( 3, 3, 3, 3 ) }; + #define stbir__simdf8_0123to22223333( out, in ) (out) = _mm256_permutevar_ps ( in, stbir_22223333 ) + + #define stbir__simdf8_0123to2222( out, in ) (out) = stbir__simdf_swiz(_mm256_castps256_ps128(in), 2,2,2,2 ) + + #define stbir__simdf8_load4b( out, ptr ) (out) = _mm256_broadcast_ps( (__m128 const *)(ptr) ) + + static __m256i stbir_00112233 = { STBIR__CONST_4d_32i( 0, 0, 1, 1 ), STBIR__CONST_4d_32i( 2, 2, 3, 3 ) }; + #define stbir__simdf8_0123to00112233( out, in ) (out) = _mm256_permutevar_ps ( in, stbir_00112233 ) + #define stbir__simdf8_add4( out, a8, b ) (out) = _mm256_add_ps( a8, _mm256_castps128_ps256( b ) ) + + static __m256i stbir_load6 = { STBIR__CONST_4_32i( 0x80000000 ), STBIR__CONST_4d_32i( 0x80000000, 0x80000000, 0, 0 ) }; + #define stbir__simdf8_load6z( out, ptr ) (out) = _mm256_maskload_ps( ptr, stbir_load6 ) + + #define stbir__simdf8_0123to00000000( out, in ) (out) = _mm256_shuffle_ps ( in, in, (0<<0)+(0<<2)+(0<<4)+(0<<6) ) + #define stbir__simdf8_0123to11111111( out, in ) (out) = _mm256_shuffle_ps ( in, in, (1<<0)+(1<<2)+(1<<4)+(1<<6) ) + #define stbir__simdf8_0123to22222222( out, in ) (out) = _mm256_shuffle_ps ( in, in, (2<<0)+(2<<2)+(2<<4)+(2<<6) ) + #define stbir__simdf8_0123to33333333( out, in ) (out) = _mm256_shuffle_ps ( in, in, (3<<0)+(3<<2)+(3<<4)+(3<<6) ) + #define stbir__simdf8_0123to21032103( out, in ) (out) = _mm256_shuffle_ps ( in, in, (2<<0)+(1<<2)+(0<<4)+(3<<6) ) + #define stbir__simdf8_0123to32103210( out, in ) (out) = _mm256_shuffle_ps ( in, in, (3<<0)+(2<<2)+(1<<4)+(0<<6) ) + #define stbir__simdf8_0123to12301230( out, in ) (out) = _mm256_shuffle_ps ( in, in, (1<<0)+(2<<2)+(3<<4)+(0<<6) ) + #define stbir__simdf8_0123to10321032( out, in ) (out) = _mm256_shuffle_ps ( in, in, (1<<0)+(0<<2)+(3<<4)+(2<<6) ) + #define stbir__simdf8_0123to30123012( out, in ) (out) = _mm256_shuffle_ps ( in, in, (3<<0)+(0<<2)+(1<<4)+(2<<6) ) + + #define stbir__simdf8_0123to11331133( out, in ) (out) = _mm256_shuffle_ps ( in, in, (1<<0)+(1<<2)+(3<<4)+(3<<6) ) + #define stbir__simdf8_0123to00220022( out, in ) (out) = _mm256_shuffle_ps ( in, in, (0<<0)+(0<<2)+(2<<4)+(2<<6) ) + + #define stbir__simdf8_aaa1( out, alp, ones ) (out) = _mm256_blend_ps( alp, ones, (1<<0)+(1<<1)+(1<<2)+(0<<3)+(1<<4)+(1<<5)+(1<<6)+(0<<7)); (out)=_mm256_shuffle_ps( out,out, (3<<0) + (3<<2) + (3<<4) + (0<<6) ) + #define stbir__simdf8_1aaa( out, alp, ones ) (out) = _mm256_blend_ps( alp, ones, (0<<0)+(1<<1)+(1<<2)+(1<<3)+(0<<4)+(1<<5)+(1<<6)+(1<<7)); (out)=_mm256_shuffle_ps( out,out, (1<<0) + (0<<2) + (0<<4) + (0<<6) ) + #define stbir__simdf8_a1a1( out, alp, ones) (out) = _mm256_blend_ps( alp, ones, (1<<0)+(0<<1)+(1<<2)+(0<<3)+(1<<4)+(0<<5)+(1<<6)+(0<<7)); (out)=_mm256_shuffle_ps( out,out, (1<<0) + (0<<2) + (3<<4) + (2<<6) ) + #define stbir__simdf8_1a1a( out, alp, ones) (out) = _mm256_blend_ps( alp, ones, (0<<0)+(1<<1)+(0<<2)+(1<<3)+(0<<4)+(1<<5)+(0<<6)+(1<<7)); (out)=_mm256_shuffle_ps( out,out, (1<<0) + (0<<2) + (3<<4) + (2<<6) ) + + #define stbir__simdf8_zero( reg ) (reg) = _mm256_setzero_ps() + + #ifdef STBIR_USE_FMA // not on by default to maintain bit identical simd to non-simd + #define stbir__simdf8_madd( out, add, mul1, mul2 ) (out) = _mm256_fmadd_ps( mul1, mul2, add ) + #define stbir__simdf8_madd_mem( out, add, mul, ptr ) (out) = _mm256_fmadd_ps( mul, _mm256_loadu_ps( (float const*)(ptr) ), add ) + #define stbir__simdf8_madd_mem4( out, add, mul, ptr )(out) = _mm256_fmadd_ps( _mm256_setr_m128( mul, _mm_setzero_ps() ), _mm256_setr_m128( _mm_loadu_ps( (float const*)(ptr) ), _mm_setzero_ps() ), add ) + #else + #define stbir__simdf8_madd( out, add, mul1, mul2 ) (out) = _mm256_add_ps( add, _mm256_mul_ps( mul1, mul2 ) ) + #define stbir__simdf8_madd_mem( out, add, mul, ptr ) (out) = _mm256_add_ps( add, _mm256_mul_ps( mul, _mm256_loadu_ps( (float const*)(ptr) ) ) ) + #define stbir__simdf8_madd_mem4( out, add, mul, ptr ) (out) = _mm256_add_ps( add, _mm256_setr_m128( _mm_mul_ps( mul, _mm_loadu_ps( (float const*)(ptr) ) ), _mm_setzero_ps() ) ) + #endif + #define stbir__if_simdf8_cast_to_simdf4( val ) _mm256_castps256_ps128( val ) + + #endif + + #ifdef STBIR_FLOORF + #undef STBIR_FLOORF + #endif + #define STBIR_FLOORF stbir_simd_floorf + static stbir__inline float stbir_simd_floorf(float x) // martins floorf + { + #if defined(STBIR_AVX) || defined(__SSE4_1__) || defined(STBIR_SSE41) + __m128 t = _mm_set_ss(x); + return _mm_cvtss_f32( _mm_floor_ss(t, t) ); + #else + __m128 f = _mm_set_ss(x); + __m128 t = _mm_cvtepi32_ps(_mm_cvttps_epi32(f)); + __m128 r = _mm_add_ss(t, _mm_and_ps(_mm_cmplt_ss(f, t), _mm_set_ss(-1.0f))); + return _mm_cvtss_f32(r); + #endif + } + + #ifdef STBIR_CEILF + #undef STBIR_CEILF + #endif + #define STBIR_CEILF stbir_simd_ceilf + static stbir__inline float stbir_simd_ceilf(float x) // martins ceilf + { + #if defined(STBIR_AVX) || defined(__SSE4_1__) || defined(STBIR_SSE41) + __m128 t = _mm_set_ss(x); + return _mm_cvtss_f32( _mm_ceil_ss(t, t) ); + #else + __m128 f = _mm_set_ss(x); + __m128 t = _mm_cvtepi32_ps(_mm_cvttps_epi32(f)); + __m128 r = _mm_add_ss(t, _mm_and_ps(_mm_cmplt_ss(t, f), _mm_set_ss(1.0f))); + return _mm_cvtss_f32(r); + #endif + } + +#elif defined(STBIR_NEON) + + #include + + #define stbir__simdf float32x4_t + #define stbir__simdi uint32x4_t + + #define stbir_simdi_castf( reg ) vreinterpretq_u32_f32(reg) + #define stbir_simdf_casti( reg ) vreinterpretq_f32_u32(reg) + + #define stbir__simdf_load( reg, ptr ) (reg) = vld1q_f32( (float const*)(ptr) ) + #define stbir__simdi_load( reg, ptr ) (reg) = vld1q_u32( (uint32_t const*)(ptr) ) + #define stbir__simdf_load1( out, ptr ) (out) = vld1q_dup_f32( (float const*)(ptr) ) // top values can be random (not denormal or nan for perf) + #define stbir__simdi_load1( out, ptr ) (out) = vld1q_dup_u32( (uint32_t const*)(ptr) ) + #define stbir__simdf_load1z( out, ptr ) (out) = vld1q_lane_f32( (float const*)(ptr), vdupq_n_f32(0), 0 ) // top values must be zero + #define stbir__simdf_frep4( fvar ) vdupq_n_f32( fvar ) + #define stbir__simdf_load1frep4( out, fvar ) (out) = vdupq_n_f32( fvar ) + #define stbir__simdf_load2( out, ptr ) (out) = vcombine_f32( vld1_f32( (float const*)(ptr) ), vcreate_f32(0) ) // top values can be random (not denormal or nan for perf) + #define stbir__simdf_load2z( out, ptr ) (out) = vcombine_f32( vld1_f32( (float const*)(ptr) ), vcreate_f32(0) ) // top values must be zero + #define stbir__simdf_load2hmerge( out, reg, ptr ) (out) = vcombine_f32( vget_low_f32(reg), vld1_f32( (float const*)(ptr) ) ) + + #define stbir__simdf_zeroP() vdupq_n_f32(0) + #define stbir__simdf_zero( reg ) (reg) = vdupq_n_f32(0) + + #define stbir__simdf_store( ptr, reg ) vst1q_f32( (float*)(ptr), reg ) + #define stbir__simdf_store1( ptr, reg ) vst1q_lane_f32( (float*)(ptr), reg, 0) + #define stbir__simdf_store2( ptr, reg ) vst1_f32( (float*)(ptr), vget_low_f32(reg) ) + #define stbir__simdf_store2h( ptr, reg ) vst1_f32( (float*)(ptr), vget_high_f32(reg) ) + + #define stbir__simdi_store( ptr, reg ) vst1q_u32( (uint32_t*)(ptr), reg ) + #define stbir__simdi_store1( ptr, reg ) vst1q_lane_u32( (uint32_t*)(ptr), reg, 0 ) + #define stbir__simdi_store2( ptr, reg ) vst1_u32( (uint32_t*)(ptr), vget_low_u32(reg) ) + + #define stbir__prefetch( ptr ) + + #define stbir__simdi_expand_u8_to_u32(out0,out1,out2,out3,ireg) \ + { \ + uint16x8_t l = vmovl_u8( vget_low_u8 ( vreinterpretq_u8_u32(ireg) ) ); \ + uint16x8_t h = vmovl_u8( vget_high_u8( vreinterpretq_u8_u32(ireg) ) ); \ + out0 = vmovl_u16( vget_low_u16 ( l ) ); \ + out1 = vmovl_u16( vget_high_u16( l ) ); \ + out2 = vmovl_u16( vget_low_u16 ( h ) ); \ + out3 = vmovl_u16( vget_high_u16( h ) ); \ + } + + #define stbir__simdi_expand_u8_to_1u32(out,ireg) \ + { \ + uint16x8_t tmp = vmovl_u8( vget_low_u8( vreinterpretq_u8_u32(ireg) ) ); \ + out = vmovl_u16( vget_low_u16( tmp ) ); \ + } + + #define stbir__simdi_expand_u16_to_u32(out0,out1,ireg) \ + { \ + uint16x8_t tmp = vreinterpretq_u16_u32(ireg); \ + out0 = vmovl_u16( vget_low_u16 ( tmp ) ); \ + out1 = vmovl_u16( vget_high_u16( tmp ) ); \ + } + + #define stbir__simdf_convert_float_to_i32( i, f ) (i) = vreinterpretq_u32_s32( vcvtq_s32_f32(f) ) + #define stbir__simdf_convert_float_to_int( f ) vgetq_lane_s32(vcvtq_s32_f32(f), 0) + #define stbir__simdi_to_int( i ) (int)vgetq_lane_u32(i, 0) + #define stbir__simdf_convert_float_to_uint8( f ) ((unsigned char)vgetq_lane_s32(vcvtq_s32_f32(vmaxq_f32(vminq_f32(f,STBIR__CONSTF(STBIR_max_uint8_as_float)),vdupq_n_f32(0))), 0)) + #define stbir__simdf_convert_float_to_short( f ) ((unsigned short)vgetq_lane_s32(vcvtq_s32_f32(vmaxq_f32(vminq_f32(f,STBIR__CONSTF(STBIR_max_uint16_as_float)),vdupq_n_f32(0))), 0)) + #define stbir__simdi_convert_i32_to_float(out, ireg) (out) = vcvtq_f32_s32( vreinterpretq_s32_u32(ireg) ) + #define stbir__simdf_add( out, reg0, reg1 ) (out) = vaddq_f32( reg0, reg1 ) + #define stbir__simdf_mult( out, reg0, reg1 ) (out) = vmulq_f32( reg0, reg1 ) + #define stbir__simdf_mult_mem( out, reg, ptr ) (out) = vmulq_f32( reg, vld1q_f32( (float const*)(ptr) ) ) + #define stbir__simdf_mult1_mem( out, reg, ptr ) (out) = vmulq_f32( reg, vld1q_dup_f32( (float const*)(ptr) ) ) + #define stbir__simdf_add_mem( out, reg, ptr ) (out) = vaddq_f32( reg, vld1q_f32( (float const*)(ptr) ) ) + #define stbir__simdf_add1_mem( out, reg, ptr ) (out) = vaddq_f32( reg, vld1q_dup_f32( (float const*)(ptr) ) ) + + #ifdef STBIR_USE_FMA // not on by default to maintain bit identical simd to non-simd (and also x64 no madd to arm madd) + #define stbir__simdf_madd( out, add, mul1, mul2 ) (out) = vfmaq_f32( add, mul1, mul2 ) + #define stbir__simdf_madd1( out, add, mul1, mul2 ) (out) = vfmaq_f32( add, mul1, mul2 ) + #define stbir__simdf_madd_mem( out, add, mul, ptr ) (out) = vfmaq_f32( add, mul, vld1q_f32( (float const*)(ptr) ) ) + #define stbir__simdf_madd1_mem( out, add, mul, ptr ) (out) = vfmaq_f32( add, mul, vld1q_dup_f32( (float const*)(ptr) ) ) + #else + #define stbir__simdf_madd( out, add, mul1, mul2 ) (out) = vaddq_f32( add, vmulq_f32( mul1, mul2 ) ) + #define stbir__simdf_madd1( out, add, mul1, mul2 ) (out) = vaddq_f32( add, vmulq_f32( mul1, mul2 ) ) + #define stbir__simdf_madd_mem( out, add, mul, ptr ) (out) = vaddq_f32( add, vmulq_f32( mul, vld1q_f32( (float const*)(ptr) ) ) ) + #define stbir__simdf_madd1_mem( out, add, mul, ptr ) (out) = vaddq_f32( add, vmulq_f32( mul, vld1q_dup_f32( (float const*)(ptr) ) ) ) + #endif + + #define stbir__simdf_add1( out, reg0, reg1 ) (out) = vaddq_f32( reg0, reg1 ) + #define stbir__simdf_mult1( out, reg0, reg1 ) (out) = vmulq_f32( reg0, reg1 ) + + #define stbir__simdf_and( out, reg0, reg1 ) (out) = vreinterpretq_f32_u32( vandq_u32( vreinterpretq_u32_f32(reg0), vreinterpretq_u32_f32(reg1) ) ) + #define stbir__simdf_or( out, reg0, reg1 ) (out) = vreinterpretq_f32_u32( vorrq_u32( vreinterpretq_u32_f32(reg0), vreinterpretq_u32_f32(reg1) ) ) + + #define stbir__simdf_min( out, reg0, reg1 ) (out) = vminq_f32( reg0, reg1 ) + #define stbir__simdf_max( out, reg0, reg1 ) (out) = vmaxq_f32( reg0, reg1 ) + #define stbir__simdf_min1( out, reg0, reg1 ) (out) = vminq_f32( reg0, reg1 ) + #define stbir__simdf_max1( out, reg0, reg1 ) (out) = vmaxq_f32( reg0, reg1 ) + + #define stbir__simdf_0123ABCDto3ABx( out, reg0, reg1 ) (out) = vextq_f32( reg0, reg1, 3 ) + #define stbir__simdf_0123ABCDto23Ax( out, reg0, reg1 ) (out) = vextq_f32( reg0, reg1, 2 ) + + #define stbir__simdf_a1a1( out, alp, ones ) (out) = vzipq_f32(vuzpq_f32(alp, alp).val[1], ones).val[0] + #define stbir__simdf_1a1a( out, alp, ones ) (out) = vzipq_f32(ones, vuzpq_f32(alp, alp).val[0]).val[0] + + #if defined( _M_ARM64 ) || defined( __aarch64__ ) || defined( __arm64__ ) + + #define stbir__simdf_aaa1( out, alp, ones ) (out) = vcopyq_laneq_f32(vdupq_n_f32(vgetq_lane_f32(alp, 3)), 3, ones, 3) + #define stbir__simdf_1aaa( out, alp, ones ) (out) = vcopyq_laneq_f32(vdupq_n_f32(vgetq_lane_f32(alp, 0)), 0, ones, 0) + + #if defined( _MSC_VER ) && !defined(__clang__) + #define stbir_make16(a,b,c,d) vcombine_u8( \ + vcreate_u8( (4*a+0) | ((4*a+1)<<8) | ((4*a+2)<<16) | ((4*a+3)<<24) | \ + ((stbir_uint64)(4*b+0)<<32) | ((stbir_uint64)(4*b+1)<<40) | ((stbir_uint64)(4*b+2)<<48) | ((stbir_uint64)(4*b+3)<<56)), \ + vcreate_u8( (4*c+0) | ((4*c+1)<<8) | ((4*c+2)<<16) | ((4*c+3)<<24) | \ + ((stbir_uint64)(4*d+0)<<32) | ((stbir_uint64)(4*d+1)<<40) | ((stbir_uint64)(4*d+2)<<48) | ((stbir_uint64)(4*d+3)<<56) ) ) + + static stbir__inline uint8x16x2_t stbir_make16x2(float32x4_t rega,float32x4_t regb) + { + uint8x16x2_t r = { vreinterpretq_u8_f32(rega), vreinterpretq_u8_f32(regb) }; + return r; + } + #else + #define stbir_make16(a,b,c,d) (uint8x16_t){4*a+0,4*a+1,4*a+2,4*a+3,4*b+0,4*b+1,4*b+2,4*b+3,4*c+0,4*c+1,4*c+2,4*c+3,4*d+0,4*d+1,4*d+2,4*d+3} + #define stbir_make16x2(a,b) (uint8x16x2_t){{vreinterpretq_u8_f32(a),vreinterpretq_u8_f32(b)}} + #endif + + #define stbir__simdf_swiz( reg, one, two, three, four ) vreinterpretq_f32_u8( vqtbl1q_u8( vreinterpretq_u8_f32(reg), stbir_make16(one, two, three, four) ) ) + #define stbir__simdf_swiz2( rega, regb, one, two, three, four ) vreinterpretq_f32_u8( vqtbl2q_u8( stbir_make16x2(rega,regb), stbir_make16(one, two, three, four) ) ) + + #define stbir__simdi_16madd( out, reg0, reg1 ) \ + { \ + int16x8_t r0 = vreinterpretq_s16_u32(reg0); \ + int16x8_t r1 = vreinterpretq_s16_u32(reg1); \ + int32x4_t tmp0 = vmull_s16( vget_low_s16(r0), vget_low_s16(r1) ); \ + int32x4_t tmp1 = vmull_s16( vget_high_s16(r0), vget_high_s16(r1) ); \ + (out) = vreinterpretq_u32_s32( vpaddq_s32(tmp0, tmp1) ); \ + } + + #else + + #define stbir__simdf_aaa1( out, alp, ones ) (out) = vsetq_lane_f32(1.0f, vdupq_n_f32(vgetq_lane_f32(alp, 3)), 3) + #define stbir__simdf_1aaa( out, alp, ones ) (out) = vsetq_lane_f32(1.0f, vdupq_n_f32(vgetq_lane_f32(alp, 0)), 0) + + #if defined( _MSC_VER ) && !defined(__clang__) + static stbir__inline uint8x8x2_t stbir_make8x2(float32x4_t reg) + { + uint8x8x2_t r = { { vget_low_u8(vreinterpretq_u8_f32(reg)), vget_high_u8(vreinterpretq_u8_f32(reg)) } }; + return r; + } + #define stbir_make8(a,b) vcreate_u8( \ + (4*a+0) | ((4*a+1)<<8) | ((4*a+2)<<16) | ((4*a+3)<<24) | \ + ((stbir_uint64)(4*b+0)<<32) | ((stbir_uint64)(4*b+1)<<40) | ((stbir_uint64)(4*b+2)<<48) | ((stbir_uint64)(4*b+3)<<56) ) + #else + #define stbir_make8x2(reg) (uint8x8x2_t){ { vget_low_u8(vreinterpretq_u8_f32(reg)), vget_high_u8(vreinterpretq_u8_f32(reg)) } } + #define stbir_make8(a,b) (uint8x8_t){4*a+0,4*a+1,4*a+2,4*a+3,4*b+0,4*b+1,4*b+2,4*b+3} + #endif + + #define stbir__simdf_swiz( reg, one, two, three, four ) vreinterpretq_f32_u8( vcombine_u8( \ + vtbl2_u8( stbir_make8x2( reg ), stbir_make8( one, two ) ), \ + vtbl2_u8( stbir_make8x2( reg ), stbir_make8( three, four ) ) ) ) + + #define stbir__simdi_16madd( out, reg0, reg1 ) \ + { \ + int16x8_t r0 = vreinterpretq_s16_u32(reg0); \ + int16x8_t r1 = vreinterpretq_s16_u32(reg1); \ + int32x4_t tmp0 = vmull_s16( vget_low_s16(r0), vget_low_s16(r1) ); \ + int32x4_t tmp1 = vmull_s16( vget_high_s16(r0), vget_high_s16(r1) ); \ + int32x2_t out0 = vpadd_s32( vget_low_s32(tmp0), vget_high_s32(tmp0) ); \ + int32x2_t out1 = vpadd_s32( vget_low_s32(tmp1), vget_high_s32(tmp1) ); \ + (out) = vreinterpretq_u32_s32( vcombine_s32(out0, out1) ); \ + } + + #endif + + #define stbir__simdi_and( out, reg0, reg1 ) (out) = vandq_u32( reg0, reg1 ) + #define stbir__simdi_or( out, reg0, reg1 ) (out) = vorrq_u32( reg0, reg1 ) + + #define stbir__simdf_pack_to_8bytes(out,aa,bb) \ + { \ + float32x4_t af = vmaxq_f32( vminq_f32(aa,STBIR__CONSTF(STBIR_max_uint8_as_float) ), vdupq_n_f32(0) ); \ + float32x4_t bf = vmaxq_f32( vminq_f32(bb,STBIR__CONSTF(STBIR_max_uint8_as_float) ), vdupq_n_f32(0) ); \ + int16x4_t ai = vqmovn_s32( vcvtq_s32_f32( af ) ); \ + int16x4_t bi = vqmovn_s32( vcvtq_s32_f32( bf ) ); \ + uint8x8_t out8 = vqmovun_s16( vcombine_s16(ai, bi) ); \ + out = vreinterpretq_u32_u8( vcombine_u8(out8, out8) ); \ + } + + #define stbir__simdf_pack_to_8words(out,aa,bb) \ + { \ + float32x4_t af = vmaxq_f32( vminq_f32(aa,STBIR__CONSTF(STBIR_max_uint16_as_float) ), vdupq_n_f32(0) ); \ + float32x4_t bf = vmaxq_f32( vminq_f32(bb,STBIR__CONSTF(STBIR_max_uint16_as_float) ), vdupq_n_f32(0) ); \ + int32x4_t ai = vcvtq_s32_f32( af ); \ + int32x4_t bi = vcvtq_s32_f32( bf ); \ + out = vreinterpretq_u32_u16( vcombine_u16(vqmovun_s32(ai), vqmovun_s32(bi)) ); \ + } + + #define stbir__interleave_pack_and_store_16_u8( ptr, r0, r1, r2, r3 ) \ + { \ + int16x4x2_t tmp0 = vzip_s16( vqmovn_s32(vreinterpretq_s32_u32(r0)), vqmovn_s32(vreinterpretq_s32_u32(r2)) ); \ + int16x4x2_t tmp1 = vzip_s16( vqmovn_s32(vreinterpretq_s32_u32(r1)), vqmovn_s32(vreinterpretq_s32_u32(r3)) ); \ + uint8x8x2_t out = \ + { { \ + vqmovun_s16( vcombine_s16(tmp0.val[0], tmp0.val[1]) ), \ + vqmovun_s16( vcombine_s16(tmp1.val[0], tmp1.val[1]) ), \ + } }; \ + vst2_u8(ptr, out); \ + } + + #define stbir__simdf_load4_transposed( o0, o1, o2, o3, ptr ) \ + { \ + float32x4x4_t tmp = vld4q_f32(ptr); \ + o0 = tmp.val[0]; \ + o1 = tmp.val[1]; \ + o2 = tmp.val[2]; \ + o3 = tmp.val[3]; \ + } + + #define stbir__simdi_32shr( out, reg, imm ) out = vshrq_n_u32( reg, imm ) + + #if defined( _MSC_VER ) && !defined(__clang__) + #define STBIR__SIMDF_CONST(var, x) __declspec(align(8)) float var[] = { x, x, x, x } + #define STBIR__SIMDI_CONST(var, x) __declspec(align(8)) uint32_t var[] = { x, x, x, x } + #define STBIR__CONSTF(var) (*(const float32x4_t*)var) + #define STBIR__CONSTI(var) (*(const uint32x4_t*)var) + #else + #define STBIR__SIMDF_CONST(var, x) stbir__simdf var = { x, x, x, x } + #define STBIR__SIMDI_CONST(var, x) stbir__simdi var = { x, x, x, x } + #define STBIR__CONSTF(var) (var) + #define STBIR__CONSTI(var) (var) + #endif + + #ifdef STBIR_FLOORF + #undef STBIR_FLOORF + #endif + #define STBIR_FLOORF stbir_simd_floorf + static stbir__inline float stbir_simd_floorf(float x) + { + #if defined( _M_ARM64 ) || defined( __aarch64__ ) || defined( __arm64__ ) + return vget_lane_f32( vrndm_f32( vdup_n_f32(x) ), 0); + #else + float32x2_t f = vdup_n_f32(x); + float32x2_t t = vcvt_f32_s32(vcvt_s32_f32(f)); + uint32x2_t a = vclt_f32(f, t); + uint32x2_t b = vreinterpret_u32_f32(vdup_n_f32(-1.0f)); + float32x2_t r = vadd_f32(t, vreinterpret_f32_u32(vand_u32(a, b))); + return vget_lane_f32(r, 0); + #endif + } + + #ifdef STBIR_CEILF + #undef STBIR_CEILF + #endif + #define STBIR_CEILF stbir_simd_ceilf + static stbir__inline float stbir_simd_ceilf(float x) + { + #if defined( _M_ARM64 ) || defined( __aarch64__ ) || defined( __arm64__ ) + return vget_lane_f32( vrndp_f32( vdup_n_f32(x) ), 0); + #else + float32x2_t f = vdup_n_f32(x); + float32x2_t t = vcvt_f32_s32(vcvt_s32_f32(f)); + uint32x2_t a = vclt_f32(t, f); + uint32x2_t b = vreinterpret_u32_f32(vdup_n_f32(1.0f)); + float32x2_t r = vadd_f32(t, vreinterpret_f32_u32(vand_u32(a, b))); + return vget_lane_f32(r, 0); + #endif + } + + #define STBIR_SIMD + +#elif defined(STBIR_WASM) + + #include + + #define stbir__simdf v128_t + #define stbir__simdi v128_t + + #define stbir_simdi_castf( reg ) (reg) + #define stbir_simdf_casti( reg ) (reg) + + #define stbir__simdf_load( reg, ptr ) (reg) = wasm_v128_load( (void const*)(ptr) ) + #define stbir__simdi_load( reg, ptr ) (reg) = wasm_v128_load( (void const*)(ptr) ) + #define stbir__simdf_load1( out, ptr ) (out) = wasm_v128_load32_splat( (void const*)(ptr) ) // top values can be random (not denormal or nan for perf) + #define stbir__simdi_load1( out, ptr ) (out) = wasm_v128_load32_splat( (void const*)(ptr) ) + #define stbir__simdf_load1z( out, ptr ) (out) = wasm_v128_load32_zero( (void const*)(ptr) ) // top values must be zero + #define stbir__simdf_frep4( fvar ) wasm_f32x4_splat( fvar ) + #define stbir__simdf_load1frep4( out, fvar ) (out) = wasm_f32x4_splat( fvar ) + #define stbir__simdf_load2( out, ptr ) (out) = wasm_v128_load64_splat( (void const*)(ptr) ) // top values can be random (not denormal or nan for perf) + #define stbir__simdf_load2z( out, ptr ) (out) = wasm_v128_load64_zero( (void const*)(ptr) ) // top values must be zero + #define stbir__simdf_load2hmerge( out, reg, ptr ) (out) = wasm_v128_load64_lane( (void const*)(ptr), reg, 1 ) + + #define stbir__simdf_zeroP() wasm_f32x4_const_splat(0) + #define stbir__simdf_zero( reg ) (reg) = wasm_f32x4_const_splat(0) + + #define stbir__simdf_store( ptr, reg ) wasm_v128_store( (void*)(ptr), reg ) + #define stbir__simdf_store1( ptr, reg ) wasm_v128_store32_lane( (void*)(ptr), reg, 0 ) + #define stbir__simdf_store2( ptr, reg ) wasm_v128_store64_lane( (void*)(ptr), reg, 0 ) + #define stbir__simdf_store2h( ptr, reg ) wasm_v128_store64_lane( (void*)(ptr), reg, 1 ) + + #define stbir__simdi_store( ptr, reg ) wasm_v128_store( (void*)(ptr), reg ) + #define stbir__simdi_store1( ptr, reg ) wasm_v128_store32_lane( (void*)(ptr), reg, 0 ) + #define stbir__simdi_store2( ptr, reg ) wasm_v128_store64_lane( (void*)(ptr), reg, 0 ) + + #define stbir__prefetch( ptr ) + + #define stbir__simdi_expand_u8_to_u32(out0,out1,out2,out3,ireg) \ + { \ + v128_t l = wasm_u16x8_extend_low_u8x16 ( ireg ); \ + v128_t h = wasm_u16x8_extend_high_u8x16( ireg ); \ + out0 = wasm_u32x4_extend_low_u16x8 ( l ); \ + out1 = wasm_u32x4_extend_high_u16x8( l ); \ + out2 = wasm_u32x4_extend_low_u16x8 ( h ); \ + out3 = wasm_u32x4_extend_high_u16x8( h ); \ + } + + #define stbir__simdi_expand_u8_to_1u32(out,ireg) \ + { \ + v128_t tmp = wasm_u16x8_extend_low_u8x16(ireg); \ + out = wasm_u32x4_extend_low_u16x8(tmp); \ + } + + #define stbir__simdi_expand_u16_to_u32(out0,out1,ireg) \ + { \ + out0 = wasm_u32x4_extend_low_u16x8 ( ireg ); \ + out1 = wasm_u32x4_extend_high_u16x8( ireg ); \ + } + + #define stbir__simdf_convert_float_to_i32( i, f ) (i) = wasm_i32x4_trunc_sat_f32x4(f) + #define stbir__simdf_convert_float_to_int( f ) wasm_i32x4_extract_lane(wasm_i32x4_trunc_sat_f32x4(f), 0) + #define stbir__simdi_to_int( i ) wasm_i32x4_extract_lane(i, 0) + #define stbir__simdf_convert_float_to_uint8( f ) ((unsigned char)wasm_i32x4_extract_lane(wasm_i32x4_trunc_sat_f32x4(wasm_f32x4_max(wasm_f32x4_min(f,STBIR_max_uint8_as_float),wasm_f32x4_const_splat(0))), 0)) + #define stbir__simdf_convert_float_to_short( f ) ((unsigned short)wasm_i32x4_extract_lane(wasm_i32x4_trunc_sat_f32x4(wasm_f32x4_max(wasm_f32x4_min(f,STBIR_max_uint16_as_float),wasm_f32x4_const_splat(0))), 0)) + #define stbir__simdi_convert_i32_to_float(out, ireg) (out) = wasm_f32x4_convert_i32x4(ireg) + #define stbir__simdf_add( out, reg0, reg1 ) (out) = wasm_f32x4_add( reg0, reg1 ) + #define stbir__simdf_mult( out, reg0, reg1 ) (out) = wasm_f32x4_mul( reg0, reg1 ) + #define stbir__simdf_mult_mem( out, reg, ptr ) (out) = wasm_f32x4_mul( reg, wasm_v128_load( (void const*)(ptr) ) ) + #define stbir__simdf_mult1_mem( out, reg, ptr ) (out) = wasm_f32x4_mul( reg, wasm_v128_load32_splat( (void const*)(ptr) ) ) + #define stbir__simdf_add_mem( out, reg, ptr ) (out) = wasm_f32x4_add( reg, wasm_v128_load( (void const*)(ptr) ) ) + #define stbir__simdf_add1_mem( out, reg, ptr ) (out) = wasm_f32x4_add( reg, wasm_v128_load32_splat( (void const*)(ptr) ) ) + + #define stbir__simdf_madd( out, add, mul1, mul2 ) (out) = wasm_f32x4_add( add, wasm_f32x4_mul( mul1, mul2 ) ) + #define stbir__simdf_madd1( out, add, mul1, mul2 ) (out) = wasm_f32x4_add( add, wasm_f32x4_mul( mul1, mul2 ) ) + #define stbir__simdf_madd_mem( out, add, mul, ptr ) (out) = wasm_f32x4_add( add, wasm_f32x4_mul( mul, wasm_v128_load( (void const*)(ptr) ) ) ) + #define stbir__simdf_madd1_mem( out, add, mul, ptr ) (out) = wasm_f32x4_add( add, wasm_f32x4_mul( mul, wasm_v128_load32_splat( (void const*)(ptr) ) ) ) + + #define stbir__simdf_add1( out, reg0, reg1 ) (out) = wasm_f32x4_add( reg0, reg1 ) + #define stbir__simdf_mult1( out, reg0, reg1 ) (out) = wasm_f32x4_mul( reg0, reg1 ) + + #define stbir__simdf_and( out, reg0, reg1 ) (out) = wasm_v128_and( reg0, reg1 ) + #define stbir__simdf_or( out, reg0, reg1 ) (out) = wasm_v128_or( reg0, reg1 ) + + #define stbir__simdf_min( out, reg0, reg1 ) (out) = wasm_f32x4_min( reg0, reg1 ) + #define stbir__simdf_max( out, reg0, reg1 ) (out) = wasm_f32x4_max( reg0, reg1 ) + #define stbir__simdf_min1( out, reg0, reg1 ) (out) = wasm_f32x4_min( reg0, reg1 ) + #define stbir__simdf_max1( out, reg0, reg1 ) (out) = wasm_f32x4_max( reg0, reg1 ) + + #define stbir__simdf_0123ABCDto3ABx( out, reg0, reg1 ) (out) = wasm_i32x4_shuffle( reg0, reg1, 3, 4, 5, -1 ) + #define stbir__simdf_0123ABCDto23Ax( out, reg0, reg1 ) (out) = wasm_i32x4_shuffle( reg0, reg1, 2, 3, 4, -1 ) + + #define stbir__simdf_aaa1(out,alp,ones) (out) = wasm_i32x4_shuffle(alp, ones, 3, 3, 3, 4) + #define stbir__simdf_1aaa(out,alp,ones) (out) = wasm_i32x4_shuffle(alp, ones, 4, 0, 0, 0) + #define stbir__simdf_a1a1(out,alp,ones) (out) = wasm_i32x4_shuffle(alp, ones, 1, 4, 3, 4) + #define stbir__simdf_1a1a(out,alp,ones) (out) = wasm_i32x4_shuffle(alp, ones, 4, 0, 4, 2) + + #define stbir__simdf_swiz( reg, one, two, three, four ) wasm_i32x4_shuffle(reg, reg, one, two, three, four) + + #define stbir__simdi_and( out, reg0, reg1 ) (out) = wasm_v128_and( reg0, reg1 ) + #define stbir__simdi_or( out, reg0, reg1 ) (out) = wasm_v128_or( reg0, reg1 ) + #define stbir__simdi_16madd( out, reg0, reg1 ) (out) = wasm_i32x4_dot_i16x8( reg0, reg1 ) + + #define stbir__simdf_pack_to_8bytes(out,aa,bb) \ + { \ + v128_t af = wasm_f32x4_max( wasm_f32x4_min(aa, STBIR_max_uint8_as_float), wasm_f32x4_const_splat(0) ); \ + v128_t bf = wasm_f32x4_max( wasm_f32x4_min(bb, STBIR_max_uint8_as_float), wasm_f32x4_const_splat(0) ); \ + v128_t ai = wasm_i32x4_trunc_sat_f32x4( af ); \ + v128_t bi = wasm_i32x4_trunc_sat_f32x4( bf ); \ + v128_t out16 = wasm_i16x8_narrow_i32x4( ai, bi ); \ + out = wasm_u8x16_narrow_i16x8( out16, out16 ); \ + } + + #define stbir__simdf_pack_to_8words(out,aa,bb) \ + { \ + v128_t af = wasm_f32x4_max( wasm_f32x4_min(aa, STBIR_max_uint16_as_float), wasm_f32x4_const_splat(0)); \ + v128_t bf = wasm_f32x4_max( wasm_f32x4_min(bb, STBIR_max_uint16_as_float), wasm_f32x4_const_splat(0)); \ + v128_t ai = wasm_i32x4_trunc_sat_f32x4( af ); \ + v128_t bi = wasm_i32x4_trunc_sat_f32x4( bf ); \ + out = wasm_u16x8_narrow_i32x4( ai, bi ); \ + } + + #define stbir__interleave_pack_and_store_16_u8( ptr, r0, r1, r2, r3 ) \ + { \ + v128_t tmp0 = wasm_i16x8_narrow_i32x4(r0, r1); \ + v128_t tmp1 = wasm_i16x8_narrow_i32x4(r2, r3); \ + v128_t tmp = wasm_u8x16_narrow_i16x8(tmp0, tmp1); \ + tmp = wasm_i8x16_shuffle(tmp, tmp, 0, 4, 8, 12, 1, 5, 9, 13, 2, 6, 10, 14, 3, 7, 11, 15); \ + wasm_v128_store( (void*)(ptr), tmp); \ + } + + #define stbir__simdf_load4_transposed( o0, o1, o2, o3, ptr ) \ + { \ + v128_t t0 = wasm_v128_load( ptr ); \ + v128_t t1 = wasm_v128_load( ptr+4 ); \ + v128_t t2 = wasm_v128_load( ptr+8 ); \ + v128_t t3 = wasm_v128_load( ptr+12 ); \ + v128_t s0 = wasm_i32x4_shuffle(t0, t1, 0, 4, 2, 6); \ + v128_t s1 = wasm_i32x4_shuffle(t0, t1, 1, 5, 3, 7); \ + v128_t s2 = wasm_i32x4_shuffle(t2, t3, 0, 4, 2, 6); \ + v128_t s3 = wasm_i32x4_shuffle(t2, t3, 1, 5, 3, 7); \ + o0 = wasm_i32x4_shuffle(s0, s2, 0, 1, 4, 5); \ + o1 = wasm_i32x4_shuffle(s1, s3, 0, 1, 4, 5); \ + o2 = wasm_i32x4_shuffle(s0, s2, 2, 3, 6, 7); \ + o3 = wasm_i32x4_shuffle(s1, s3, 2, 3, 6, 7); \ + } + + #define stbir__simdi_32shr( out, reg, imm ) out = wasm_u32x4_shr( reg, imm ) + + typedef float stbir__f32x4 __attribute__((__vector_size__(16), __aligned__(16))); + #define STBIR__SIMDF_CONST(var, x) stbir__simdf var = (v128_t)(stbir__f32x4){ x, x, x, x } + #define STBIR__SIMDI_CONST(var, x) stbir__simdi var = { x, x, x, x } + #define STBIR__CONSTF(var) (var) + #define STBIR__CONSTI(var) (var) + + #ifdef STBIR_FLOORF + #undef STBIR_FLOORF + #endif + #define STBIR_FLOORF stbir_simd_floorf + static stbir__inline float stbir_simd_floorf(float x) + { + return wasm_f32x4_extract_lane( wasm_f32x4_floor( wasm_f32x4_splat(x) ), 0); + } + + #ifdef STBIR_CEILF + #undef STBIR_CEILF + #endif + #define STBIR_CEILF stbir_simd_ceilf + static stbir__inline float stbir_simd_ceilf(float x) + { + return wasm_f32x4_extract_lane( wasm_f32x4_ceil( wasm_f32x4_splat(x) ), 0); + } + + #define STBIR_SIMD + +#endif // SSE2/NEON/WASM + +#endif // NO SIMD + +#ifdef STBIR_SIMD8 + #define stbir__simdfX stbir__simdf8 + #define stbir__simdiX stbir__simdi8 + #define stbir__simdfX_load stbir__simdf8_load + #define stbir__simdiX_load stbir__simdi8_load + #define stbir__simdfX_mult stbir__simdf8_mult + #define stbir__simdfX_add_mem stbir__simdf8_add_mem + #define stbir__simdfX_madd_mem stbir__simdf8_madd_mem + #define stbir__simdfX_store stbir__simdf8_store + #define stbir__simdiX_store stbir__simdi8_store + #define stbir__simdf_frepX stbir__simdf8_frep8 + #define stbir__simdfX_madd stbir__simdf8_madd + #define stbir__simdfX_min stbir__simdf8_min + #define stbir__simdfX_max stbir__simdf8_max + #define stbir__simdfX_aaa1 stbir__simdf8_aaa1 + #define stbir__simdfX_1aaa stbir__simdf8_1aaa + #define stbir__simdfX_a1a1 stbir__simdf8_a1a1 + #define stbir__simdfX_1a1a stbir__simdf8_1a1a + #define stbir__simdfX_convert_float_to_i32 stbir__simdf8_convert_float_to_i32 + #define stbir__simdfX_pack_to_words stbir__simdf8_pack_to_16words + #define stbir__simdfX_zero stbir__simdf8_zero + #define STBIR_onesX STBIR_ones8 + #define STBIR_max_uint8_as_floatX STBIR_max_uint8_as_float8 + #define STBIR_max_uint16_as_floatX STBIR_max_uint16_as_float8 + #define STBIR_simd_point5X STBIR_simd_point58 + #define stbir__simdfX_float_count 8 + #define stbir__simdfX_0123to1230 stbir__simdf8_0123to12301230 + #define stbir__simdfX_0123to2103 stbir__simdf8_0123to21032103 + static const stbir__simdf8 STBIR_max_uint16_as_float_inverted8 = { stbir__max_uint16_as_float_inverted,stbir__max_uint16_as_float_inverted,stbir__max_uint16_as_float_inverted,stbir__max_uint16_as_float_inverted,stbir__max_uint16_as_float_inverted,stbir__max_uint16_as_float_inverted,stbir__max_uint16_as_float_inverted,stbir__max_uint16_as_float_inverted }; + static const stbir__simdf8 STBIR_max_uint8_as_float_inverted8 = { stbir__max_uint8_as_float_inverted,stbir__max_uint8_as_float_inverted,stbir__max_uint8_as_float_inverted,stbir__max_uint8_as_float_inverted,stbir__max_uint8_as_float_inverted,stbir__max_uint8_as_float_inverted,stbir__max_uint8_as_float_inverted,stbir__max_uint8_as_float_inverted }; + static const stbir__simdf8 STBIR_ones8 = { 1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0 }; + static const stbir__simdf8 STBIR_simd_point58 = { 0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5 }; + static const stbir__simdf8 STBIR_max_uint8_as_float8 = { stbir__max_uint8_as_float,stbir__max_uint8_as_float,stbir__max_uint8_as_float,stbir__max_uint8_as_float, stbir__max_uint8_as_float,stbir__max_uint8_as_float,stbir__max_uint8_as_float,stbir__max_uint8_as_float }; + static const stbir__simdf8 STBIR_max_uint16_as_float8 = { stbir__max_uint16_as_float,stbir__max_uint16_as_float,stbir__max_uint16_as_float,stbir__max_uint16_as_float, stbir__max_uint16_as_float,stbir__max_uint16_as_float,stbir__max_uint16_as_float,stbir__max_uint16_as_float }; +#else + #define stbir__simdfX stbir__simdf + #define stbir__simdiX stbir__simdi + #define stbir__simdfX_load stbir__simdf_load + #define stbir__simdiX_load stbir__simdi_load + #define stbir__simdfX_mult stbir__simdf_mult + #define stbir__simdfX_add_mem stbir__simdf_add_mem + #define stbir__simdfX_madd_mem stbir__simdf_madd_mem + #define stbir__simdfX_store stbir__simdf_store + #define stbir__simdiX_store stbir__simdi_store + #define stbir__simdf_frepX stbir__simdf_frep4 + #define stbir__simdfX_madd stbir__simdf_madd + #define stbir__simdfX_min stbir__simdf_min + #define stbir__simdfX_max stbir__simdf_max + #define stbir__simdfX_aaa1 stbir__simdf_aaa1 + #define stbir__simdfX_1aaa stbir__simdf_1aaa + #define stbir__simdfX_a1a1 stbir__simdf_a1a1 + #define stbir__simdfX_1a1a stbir__simdf_1a1a + #define stbir__simdfX_convert_float_to_i32 stbir__simdf_convert_float_to_i32 + #define stbir__simdfX_pack_to_words stbir__simdf_pack_to_8words + #define stbir__simdfX_zero stbir__simdf_zero + #define STBIR_onesX STBIR__CONSTF(STBIR_ones) + #define STBIR_simd_point5X STBIR__CONSTF(STBIR_simd_point5) + #define STBIR_max_uint8_as_floatX STBIR__CONSTF(STBIR_max_uint8_as_float) + #define STBIR_max_uint16_as_floatX STBIR__CONSTF(STBIR_max_uint16_as_float) + #define stbir__simdfX_float_count 4 + #define stbir__if_simdf8_cast_to_simdf4( val ) ( val ) + #define stbir__simdfX_0123to1230 stbir__simdf_0123to1230 + #define stbir__simdfX_0123to2103 stbir__simdf_0123to2103 +#endif + + +#if defined(STBIR_NEON) && !defined(_M_ARM) && !defined(__arm__) + + #if defined( _MSC_VER ) && !defined(__clang__) + typedef __int16 stbir__FP16; + #else + typedef float16_t stbir__FP16; + #endif + +#else // no NEON, or 32-bit ARM for MSVC + + typedef union stbir__FP16 + { + unsigned short u; + } stbir__FP16; + +#endif + +#if (!defined(STBIR_NEON) && !defined(STBIR_FP16C)) || (defined(STBIR_NEON) && defined(_M_ARM)) || (defined(STBIR_NEON) && defined(__arm__)) + + // Fabian's half float routines, see: https://gist.github.com/rygorous/2156668 + + static stbir__inline float stbir__half_to_float( stbir__FP16 h ) + { + static const stbir__FP32 magic = { (254 - 15) << 23 }; + static const stbir__FP32 was_infnan = { (127 + 16) << 23 }; + stbir__FP32 o; + + o.u = (h.u & 0x7fff) << 13; // exponent/mantissa bits + o.f *= magic.f; // exponent adjust + if (o.f >= was_infnan.f) // make sure Inf/NaN survive + o.u |= 255 << 23; + o.u |= (h.u & 0x8000) << 16; // sign bit + return o.f; + } + + static stbir__inline stbir__FP16 stbir__float_to_half(float val) + { + stbir__FP32 f32infty = { 255 << 23 }; + stbir__FP32 f16max = { (127 + 16) << 23 }; + stbir__FP32 denorm_magic = { ((127 - 15) + (23 - 10) + 1) << 23 }; + unsigned int sign_mask = 0x80000000u; + stbir__FP16 o = { 0 }; + stbir__FP32 f; + unsigned int sign; + + f.f = val; + sign = f.u & sign_mask; + f.u ^= sign; + + if (f.u >= f16max.u) // result is Inf or NaN (all exponent bits set) + o.u = (f.u > f32infty.u) ? 0x7e00 : 0x7c00; // NaN->qNaN and Inf->Inf + else // (De)normalized number or zero + { + if (f.u < (113 << 23)) // resulting FP16 is subnormal or zero + { + // use a magic value to align our 10 mantissa bits at the bottom of + // the float. as long as FP addition is round-to-nearest-even this + // just works. + f.f += denorm_magic.f; + // and one integer subtract of the bias later, we have our final float! + o.u = (unsigned short) ( f.u - denorm_magic.u ); + } + else + { + unsigned int mant_odd = (f.u >> 13) & 1; // resulting mantissa is odd + // update exponent, rounding bias part 1 + f.u = f.u + ((15u - 127) << 23) + 0xfff; + // rounding bias part 2 + f.u += mant_odd; + // take the bits! + o.u = (unsigned short) ( f.u >> 13 ); + } + } + + o.u |= sign >> 16; + return o; + } + +#endif + + +#if defined(STBIR_FP16C) + + #include + + static stbir__inline void stbir__half_to_float_SIMD(float * output, stbir__FP16 const * input) + { + _mm256_storeu_ps( (float*)output, _mm256_cvtph_ps( _mm_loadu_si128( (__m128i const* )input ) ) ); + } + + static stbir__inline void stbir__float_to_half_SIMD(stbir__FP16 * output, float const * input) + { + _mm_storeu_si128( (__m128i*)output, _mm256_cvtps_ph( _mm256_loadu_ps( input ), 0 ) ); + } + + static stbir__inline float stbir__half_to_float( stbir__FP16 h ) + { + return _mm_cvtss_f32( _mm_cvtph_ps( _mm_cvtsi32_si128( (int)h.u ) ) ); + } + + static stbir__inline stbir__FP16 stbir__float_to_half( float f ) + { + stbir__FP16 h; + h.u = (unsigned short) _mm_cvtsi128_si32( _mm_cvtps_ph( _mm_set_ss( f ), 0 ) ); + return h; + } + +#elif defined(STBIR_SSE2) + + // Fabian's half float routines, see: https://gist.github.com/rygorous/2156668 + stbir__inline static void stbir__half_to_float_SIMD(float * output, void const * input) + { + static const STBIR__SIMDI_CONST(mask_nosign, 0x7fff); + static const STBIR__SIMDI_CONST(smallest_normal, 0x0400); + static const STBIR__SIMDI_CONST(infinity, 0x7c00); + static const STBIR__SIMDI_CONST(expadjust_normal, (127 - 15) << 23); + static const STBIR__SIMDI_CONST(magic_denorm, 113 << 23); + + __m128i i = _mm_loadu_si128 ( (__m128i const*)(input) ); + __m128i h = _mm_unpacklo_epi16 ( i, _mm_setzero_si128() ); + __m128i mnosign = STBIR__CONSTI(mask_nosign); + __m128i eadjust = STBIR__CONSTI(expadjust_normal); + __m128i smallest = STBIR__CONSTI(smallest_normal); + __m128i infty = STBIR__CONSTI(infinity); + __m128i expmant = _mm_and_si128(mnosign, h); + __m128i justsign = _mm_xor_si128(h, expmant); + __m128i b_notinfnan = _mm_cmpgt_epi32(infty, expmant); + __m128i b_isdenorm = _mm_cmpgt_epi32(smallest, expmant); + __m128i shifted = _mm_slli_epi32(expmant, 13); + __m128i adj_infnan = _mm_andnot_si128(b_notinfnan, eadjust); + __m128i adjusted = _mm_add_epi32(eadjust, shifted); + __m128i den1 = _mm_add_epi32(shifted, STBIR__CONSTI(magic_denorm)); + __m128i adjusted2 = _mm_add_epi32(adjusted, adj_infnan); + __m128 den2 = _mm_sub_ps(_mm_castsi128_ps(den1), *(const __m128 *)&magic_denorm); + __m128 adjusted3 = _mm_and_ps(den2, _mm_castsi128_ps(b_isdenorm)); + __m128 adjusted4 = _mm_andnot_ps(_mm_castsi128_ps(b_isdenorm), _mm_castsi128_ps(adjusted2)); + __m128 adjusted5 = _mm_or_ps(adjusted3, adjusted4); + __m128i sign = _mm_slli_epi32(justsign, 16); + __m128 final = _mm_or_ps(adjusted5, _mm_castsi128_ps(sign)); + stbir__simdf_store( output + 0, final ); + + h = _mm_unpackhi_epi16 ( i, _mm_setzero_si128() ); + expmant = _mm_and_si128(mnosign, h); + justsign = _mm_xor_si128(h, expmant); + b_notinfnan = _mm_cmpgt_epi32(infty, expmant); + b_isdenorm = _mm_cmpgt_epi32(smallest, expmant); + shifted = _mm_slli_epi32(expmant, 13); + adj_infnan = _mm_andnot_si128(b_notinfnan, eadjust); + adjusted = _mm_add_epi32(eadjust, shifted); + den1 = _mm_add_epi32(shifted, STBIR__CONSTI(magic_denorm)); + adjusted2 = _mm_add_epi32(adjusted, adj_infnan); + den2 = _mm_sub_ps(_mm_castsi128_ps(den1), *(const __m128 *)&magic_denorm); + adjusted3 = _mm_and_ps(den2, _mm_castsi128_ps(b_isdenorm)); + adjusted4 = _mm_andnot_ps(_mm_castsi128_ps(b_isdenorm), _mm_castsi128_ps(adjusted2)); + adjusted5 = _mm_or_ps(adjusted3, adjusted4); + sign = _mm_slli_epi32(justsign, 16); + final = _mm_or_ps(adjusted5, _mm_castsi128_ps(sign)); + stbir__simdf_store( output + 4, final ); + + // ~38 SSE2 ops for 8 values + } + + // Fabian's round-to-nearest-even float to half + // ~48 SSE2 ops for 8 output + stbir__inline static void stbir__float_to_half_SIMD(void * output, float const * input) + { + static const STBIR__SIMDI_CONST(mask_sign, 0x80000000u); + static const STBIR__SIMDI_CONST(c_f16max, (127 + 16) << 23); // all FP32 values >=this round to +inf + static const STBIR__SIMDI_CONST(c_nanbit, 0x200); + static const STBIR__SIMDI_CONST(c_infty_as_fp16, 0x7c00); + static const STBIR__SIMDI_CONST(c_min_normal, (127 - 14) << 23); // smallest FP32 that yields a normalized FP16 + static const STBIR__SIMDI_CONST(c_subnorm_magic, ((127 - 15) + (23 - 10) + 1) << 23); + static const STBIR__SIMDI_CONST(c_normal_bias, 0xfff - ((127 - 15) << 23)); // adjust exponent and add mantissa rounding + + __m128 f = _mm_loadu_ps(input); + __m128 msign = _mm_castsi128_ps(STBIR__CONSTI(mask_sign)); + __m128 justsign = _mm_and_ps(msign, f); + __m128 absf = _mm_xor_ps(f, justsign); + __m128i absf_int = _mm_castps_si128(absf); // the cast is "free" (extra bypass latency, but no thruput hit) + __m128i f16max = STBIR__CONSTI(c_f16max); + __m128 b_isnan = _mm_cmpunord_ps(absf, absf); // is this a NaN? + __m128i b_isregular = _mm_cmpgt_epi32(f16max, absf_int); // (sub)normalized or special? + __m128i nanbit = _mm_and_si128(_mm_castps_si128(b_isnan), STBIR__CONSTI(c_nanbit)); + __m128i inf_or_nan = _mm_or_si128(nanbit, STBIR__CONSTI(c_infty_as_fp16)); // output for specials + + __m128i min_normal = STBIR__CONSTI(c_min_normal); + __m128i b_issub = _mm_cmpgt_epi32(min_normal, absf_int); + + // "result is subnormal" path + __m128 subnorm1 = _mm_add_ps(absf, _mm_castsi128_ps(STBIR__CONSTI(c_subnorm_magic))); // magic value to round output mantissa + __m128i subnorm2 = _mm_sub_epi32(_mm_castps_si128(subnorm1), STBIR__CONSTI(c_subnorm_magic)); // subtract out bias + + // "result is normal" path + __m128i mantoddbit = _mm_slli_epi32(absf_int, 31 - 13); // shift bit 13 (mantissa LSB) to sign + __m128i mantodd = _mm_srai_epi32(mantoddbit, 31); // -1 if FP16 mantissa odd, else 0 + + __m128i round1 = _mm_add_epi32(absf_int, STBIR__CONSTI(c_normal_bias)); + __m128i round2 = _mm_sub_epi32(round1, mantodd); // if mantissa LSB odd, bias towards rounding up (RTNE) + __m128i normal = _mm_srli_epi32(round2, 13); // rounded result + + // combine the two non-specials + __m128i nonspecial = _mm_or_si128(_mm_and_si128(subnorm2, b_issub), _mm_andnot_si128(b_issub, normal)); + + // merge in specials as well + __m128i joined = _mm_or_si128(_mm_and_si128(nonspecial, b_isregular), _mm_andnot_si128(b_isregular, inf_or_nan)); + + __m128i sign_shift = _mm_srai_epi32(_mm_castps_si128(justsign), 16); + __m128i final2, final= _mm_or_si128(joined, sign_shift); + + f = _mm_loadu_ps(input+4); + justsign = _mm_and_ps(msign, f); + absf = _mm_xor_ps(f, justsign); + absf_int = _mm_castps_si128(absf); // the cast is "free" (extra bypass latency, but no thruput hit) + b_isnan = _mm_cmpunord_ps(absf, absf); // is this a NaN? + b_isregular = _mm_cmpgt_epi32(f16max, absf_int); // (sub)normalized or special? + nanbit = _mm_and_si128(_mm_castps_si128(b_isnan), c_nanbit); + inf_or_nan = _mm_or_si128(nanbit, STBIR__CONSTI(c_infty_as_fp16)); // output for specials + + b_issub = _mm_cmpgt_epi32(min_normal, absf_int); + + // "result is subnormal" path + subnorm1 = _mm_add_ps(absf, _mm_castsi128_ps(STBIR__CONSTI(c_subnorm_magic))); // magic value to round output mantissa + subnorm2 = _mm_sub_epi32(_mm_castps_si128(subnorm1), STBIR__CONSTI(c_subnorm_magic)); // subtract out bias + + // "result is normal" path + mantoddbit = _mm_slli_epi32(absf_int, 31 - 13); // shift bit 13 (mantissa LSB) to sign + mantodd = _mm_srai_epi32(mantoddbit, 31); // -1 if FP16 mantissa odd, else 0 + + round1 = _mm_add_epi32(absf_int, STBIR__CONSTI(c_normal_bias)); + round2 = _mm_sub_epi32(round1, mantodd); // if mantissa LSB odd, bias towards rounding up (RTNE) + normal = _mm_srli_epi32(round2, 13); // rounded result + + // combine the two non-specials + nonspecial = _mm_or_si128(_mm_and_si128(subnorm2, b_issub), _mm_andnot_si128(b_issub, normal)); + + // merge in specials as well + joined = _mm_or_si128(_mm_and_si128(nonspecial, b_isregular), _mm_andnot_si128(b_isregular, inf_or_nan)); + + sign_shift = _mm_srai_epi32(_mm_castps_si128(justsign), 16); + final2 = _mm_or_si128(joined, sign_shift); + final = _mm_packs_epi32(final, final2); + stbir__simdi_store( output,final ); + } + +#elif defined(STBIR_NEON) && defined(_MSC_VER) && defined(_M_ARM64) && !defined(__clang__) // 64-bit ARM on MSVC (not clang) + + static stbir__inline void stbir__half_to_float_SIMD(float * output, stbir__FP16 const * input) + { + float16x4_t in0 = vld1_f16(input + 0); + float16x4_t in1 = vld1_f16(input + 4); + vst1q_f32(output + 0, vcvt_f32_f16(in0)); + vst1q_f32(output + 4, vcvt_f32_f16(in1)); + } + + static stbir__inline void stbir__float_to_half_SIMD(stbir__FP16 * output, float const * input) + { + float16x4_t out0 = vcvt_f16_f32(vld1q_f32(input + 0)); + float16x4_t out1 = vcvt_f16_f32(vld1q_f32(input + 4)); + vst1_f16(output+0, out0); + vst1_f16(output+4, out1); + } + + static stbir__inline float stbir__half_to_float( stbir__FP16 h ) + { + return vgetq_lane_f32(vcvt_f32_f16(vld1_dup_f16(&h)), 0); + } + + static stbir__inline stbir__FP16 stbir__float_to_half( float f ) + { + return vget_lane_f16(vcvt_f16_f32(vdupq_n_f32(f)), 0).n16_u16[0]; + } + +#elif defined(STBIR_NEON) && ( defined( _M_ARM64 ) || defined( __aarch64__ ) || defined( __arm64__ ) ) // 64-bit ARM + + static stbir__inline void stbir__half_to_float_SIMD(float * output, stbir__FP16 const * input) + { + float16x8_t in = vld1q_f16(input); + vst1q_f32(output + 0, vcvt_f32_f16(vget_low_f16(in))); + vst1q_f32(output + 4, vcvt_f32_f16(vget_high_f16(in))); + } + + static stbir__inline void stbir__float_to_half_SIMD(stbir__FP16 * output, float const * input) + { + float16x4_t out0 = vcvt_f16_f32(vld1q_f32(input + 0)); + float16x4_t out1 = vcvt_f16_f32(vld1q_f32(input + 4)); + vst1q_f16(output, vcombine_f16(out0, out1)); + } + + static stbir__inline float stbir__half_to_float( stbir__FP16 h ) + { + return vgetq_lane_f32(vcvt_f32_f16(vdup_n_f16(h)), 0); + } + + static stbir__inline stbir__FP16 stbir__float_to_half( float f ) + { + return vget_lane_f16(vcvt_f16_f32(vdupq_n_f32(f)), 0); + } + +#elif defined(STBIR_WASM) || (defined(STBIR_NEON) && (defined(_MSC_VER) || defined(_M_ARM) || defined(__arm__))) // WASM or 32-bit ARM on MSVC/clang + + static stbir__inline void stbir__half_to_float_SIMD(float * output, stbir__FP16 const * input) + { + for (int i=0; i<8; i++) + { + output[i] = stbir__half_to_float(input[i]); + } + } + static stbir__inline void stbir__float_to_half_SIMD(stbir__FP16 * output, float const * input) + { + for (int i=0; i<8; i++) + { + output[i] = stbir__float_to_half(input[i]); + } + } + +#endif + + +#ifdef STBIR_SIMD + +#define stbir__simdf_0123to3333( out, reg ) (out) = stbir__simdf_swiz( reg, 3,3,3,3 ) +#define stbir__simdf_0123to2222( out, reg ) (out) = stbir__simdf_swiz( reg, 2,2,2,2 ) +#define stbir__simdf_0123to1111( out, reg ) (out) = stbir__simdf_swiz( reg, 1,1,1,1 ) +#define stbir__simdf_0123to0000( out, reg ) (out) = stbir__simdf_swiz( reg, 0,0,0,0 ) +#define stbir__simdf_0123to0003( out, reg ) (out) = stbir__simdf_swiz( reg, 0,0,0,3 ) +#define stbir__simdf_0123to0001( out, reg ) (out) = stbir__simdf_swiz( reg, 0,0,0,1 ) +#define stbir__simdf_0123to1122( out, reg ) (out) = stbir__simdf_swiz( reg, 1,1,2,2 ) +#define stbir__simdf_0123to2333( out, reg ) (out) = stbir__simdf_swiz( reg, 2,3,3,3 ) +#define stbir__simdf_0123to0023( out, reg ) (out) = stbir__simdf_swiz( reg, 0,0,2,3 ) +#define stbir__simdf_0123to1230( out, reg ) (out) = stbir__simdf_swiz( reg, 1,2,3,0 ) +#define stbir__simdf_0123to2103( out, reg ) (out) = stbir__simdf_swiz( reg, 2,1,0,3 ) +#define stbir__simdf_0123to3210( out, reg ) (out) = stbir__simdf_swiz( reg, 3,2,1,0 ) +#define stbir__simdf_0123to2301( out, reg ) (out) = stbir__simdf_swiz( reg, 2,3,0,1 ) +#define stbir__simdf_0123to3012( out, reg ) (out) = stbir__simdf_swiz( reg, 3,0,1,2 ) +#define stbir__simdf_0123to0011( out, reg ) (out) = stbir__simdf_swiz( reg, 0,0,1,1 ) +#define stbir__simdf_0123to1100( out, reg ) (out) = stbir__simdf_swiz( reg, 1,1,0,0 ) +#define stbir__simdf_0123to2233( out, reg ) (out) = stbir__simdf_swiz( reg, 2,2,3,3 ) +#define stbir__simdf_0123to1133( out, reg ) (out) = stbir__simdf_swiz( reg, 1,1,3,3 ) +#define stbir__simdf_0123to0022( out, reg ) (out) = stbir__simdf_swiz( reg, 0,0,2,2 ) +#define stbir__simdf_0123to1032( out, reg ) (out) = stbir__simdf_swiz( reg, 1,0,3,2 ) + +typedef union stbir__simdi_u32 +{ + stbir_uint32 m128i_u32[4]; + int m128i_i32[4]; + stbir__simdi m128i_i128; +} stbir__simdi_u32; + +static const int STBIR_mask[9] = { 0,0,0,-1,-1,-1,0,0,0 }; + +static const STBIR__SIMDF_CONST(STBIR_max_uint8_as_float, stbir__max_uint8_as_float); +static const STBIR__SIMDF_CONST(STBIR_max_uint16_as_float, stbir__max_uint16_as_float); +static const STBIR__SIMDF_CONST(STBIR_max_uint8_as_float_inverted, stbir__max_uint8_as_float_inverted); +static const STBIR__SIMDF_CONST(STBIR_max_uint16_as_float_inverted, stbir__max_uint16_as_float_inverted); + +static const STBIR__SIMDF_CONST(STBIR_simd_point5, 0.5f); +static const STBIR__SIMDF_CONST(STBIR_ones, 1.0f); +static const STBIR__SIMDI_CONST(STBIR_almost_zero, (127 - 13) << 23); +static const STBIR__SIMDI_CONST(STBIR_almost_one, 0x3f7fffff); +static const STBIR__SIMDI_CONST(STBIR_mantissa_mask, 0xff); +static const STBIR__SIMDI_CONST(STBIR_topscale, 0x02000000); + +// Basically, in simd mode, we unroll the proper amount, and we don't want +// the non-simd remnant loops to be unroll because they only run a few times +// Adding this switch saves about 5K on clang which is Captain Unroll the 3rd. +#define STBIR_SIMD_STREAMOUT_PTR( star ) STBIR_STREAMOUT_PTR( star ) +#define STBIR_SIMD_NO_UNROLL(ptr) STBIR_NO_UNROLL(ptr) +#define STBIR_SIMD_NO_UNROLL_LOOP_START STBIR_NO_UNROLL_LOOP_START +#define STBIR_SIMD_NO_UNROLL_LOOP_START_INF_FOR STBIR_NO_UNROLL_LOOP_START_INF_FOR + +#ifdef STBIR_MEMCPY +#undef STBIR_MEMCPY +#endif +#define STBIR_MEMCPY stbir_simd_memcpy + +// override normal use of memcpy with much simpler copy (faster and smaller with our sized copies) +static void stbir_simd_memcpy( void * dest, void const * src, size_t bytes ) +{ + char STBIR_SIMD_STREAMOUT_PTR (*) d = (char*) dest; + char STBIR_SIMD_STREAMOUT_PTR( * ) d_end = ((char*) dest) + bytes; + ptrdiff_t ofs_to_src = (char*)src - (char*)dest; + + // check overlaps + STBIR_ASSERT( ( ( d >= ( (char*)src) + bytes ) ) || ( ( d + bytes ) <= (char*)src ) ); + + if ( bytes < (16*stbir__simdfX_float_count) ) + { + if ( bytes < 16 ) + { + if ( bytes ) + { + STBIR_SIMD_NO_UNROLL_LOOP_START + do + { + STBIR_SIMD_NO_UNROLL(d); + d[ 0 ] = d[ ofs_to_src ]; + ++d; + } while ( d < d_end ); + } + } + else + { + stbir__simdf x; + // do one unaligned to get us aligned for the stream out below + stbir__simdf_load( x, ( d + ofs_to_src ) ); + stbir__simdf_store( d, x ); + d = (char*)( ( ( (size_t)d ) + 16 ) & ~15 ); + + STBIR_SIMD_NO_UNROLL_LOOP_START_INF_FOR + for(;;) + { + STBIR_SIMD_NO_UNROLL(d); + + if ( d > ( d_end - 16 ) ) + { + if ( d == d_end ) + return; + d = d_end - 16; + } + + stbir__simdf_load( x, ( d + ofs_to_src ) ); + stbir__simdf_store( d, x ); + d += 16; + } + } + } + else + { + stbir__simdfX x0,x1,x2,x3; + + // do one unaligned to get us aligned for the stream out below + stbir__simdfX_load( x0, ( d + ofs_to_src ) + 0*stbir__simdfX_float_count ); + stbir__simdfX_load( x1, ( d + ofs_to_src ) + 4*stbir__simdfX_float_count ); + stbir__simdfX_load( x2, ( d + ofs_to_src ) + 8*stbir__simdfX_float_count ); + stbir__simdfX_load( x3, ( d + ofs_to_src ) + 12*stbir__simdfX_float_count ); + stbir__simdfX_store( d + 0*stbir__simdfX_float_count, x0 ); + stbir__simdfX_store( d + 4*stbir__simdfX_float_count, x1 ); + stbir__simdfX_store( d + 8*stbir__simdfX_float_count, x2 ); + stbir__simdfX_store( d + 12*stbir__simdfX_float_count, x3 ); + d = (char*)( ( ( (size_t)d ) + (16*stbir__simdfX_float_count) ) & ~((16*stbir__simdfX_float_count)-1) ); + + STBIR_SIMD_NO_UNROLL_LOOP_START_INF_FOR + for(;;) + { + STBIR_SIMD_NO_UNROLL(d); + + if ( d > ( d_end - (16*stbir__simdfX_float_count) ) ) + { + if ( d == d_end ) + return; + d = d_end - (16*stbir__simdfX_float_count); + } + + stbir__simdfX_load( x0, ( d + ofs_to_src ) + 0*stbir__simdfX_float_count ); + stbir__simdfX_load( x1, ( d + ofs_to_src ) + 4*stbir__simdfX_float_count ); + stbir__simdfX_load( x2, ( d + ofs_to_src ) + 8*stbir__simdfX_float_count ); + stbir__simdfX_load( x3, ( d + ofs_to_src ) + 12*stbir__simdfX_float_count ); + stbir__simdfX_store( d + 0*stbir__simdfX_float_count, x0 ); + stbir__simdfX_store( d + 4*stbir__simdfX_float_count, x1 ); + stbir__simdfX_store( d + 8*stbir__simdfX_float_count, x2 ); + stbir__simdfX_store( d + 12*stbir__simdfX_float_count, x3 ); + d += (16*stbir__simdfX_float_count); + } + } +} + +// memcpy that is specically intentionally overlapping (src is smaller then dest, so can be +// a normal forward copy, bytes is divisible by 4 and bytes is greater than or equal to +// the diff between dest and src) +static void stbir_overlapping_memcpy( void * dest, void const * src, size_t bytes ) +{ + char STBIR_SIMD_STREAMOUT_PTR (*) sd = (char*) src; + char STBIR_SIMD_STREAMOUT_PTR( * ) s_end = ((char*) src) + bytes; + ptrdiff_t ofs_to_dest = (char*)dest - (char*)src; + + if ( ofs_to_dest >= 16 ) // is the overlap more than 16 away? + { + char STBIR_SIMD_STREAMOUT_PTR( * ) s_end16 = ((char*) src) + (bytes&~15); + STBIR_SIMD_NO_UNROLL_LOOP_START + do + { + stbir__simdf x; + STBIR_SIMD_NO_UNROLL(sd); + stbir__simdf_load( x, sd ); + stbir__simdf_store( ( sd + ofs_to_dest ), x ); + sd += 16; + } while ( sd < s_end16 ); + + if ( sd == s_end ) + return; + } + + do + { + STBIR_SIMD_NO_UNROLL(sd); + *(int*)( sd + ofs_to_dest ) = *(int*) sd; + sd += 4; + } while ( sd < s_end ); +} + +#else // no SSE2 + +// when in scalar mode, we let unrolling happen, so this macro just does the __restrict +#define STBIR_SIMD_STREAMOUT_PTR( star ) STBIR_STREAMOUT_PTR( star ) +#define STBIR_SIMD_NO_UNROLL(ptr) +#define STBIR_SIMD_NO_UNROLL_LOOP_START +#define STBIR_SIMD_NO_UNROLL_LOOP_START_INF_FOR + +#endif // SSE2 + + +#ifdef STBIR_PROFILE + +#ifndef STBIR_PROFILE_FUNC + +#if defined(_x86_64) || defined( __x86_64__ ) || defined( _M_X64 ) || defined(__x86_64) || defined(__SSE2__) || defined(STBIR_SSE) || defined( _M_IX86_FP ) || defined(__i386) || defined( __i386__ ) || defined( _M_IX86 ) || defined( _X86_ ) + +#ifdef _MSC_VER + + STBIRDEF stbir_uint64 __rdtsc(); + #define STBIR_PROFILE_FUNC() __rdtsc() + +#else // non msvc + + static stbir__inline stbir_uint64 STBIR_PROFILE_FUNC() + { + stbir_uint32 lo, hi; + asm volatile ("rdtsc" : "=a" (lo), "=d" (hi) ); + return ( ( (stbir_uint64) hi ) << 32 ) | ( (stbir_uint64) lo ); + } + +#endif // msvc + +#elif defined( _M_ARM64 ) || defined( __aarch64__ ) || defined( __arm64__ ) || defined(__ARM_NEON__) + +#if defined( _MSC_VER ) && !defined(__clang__) + + #define STBIR_PROFILE_FUNC() _ReadStatusReg(ARM64_CNTVCT) + +#else + + static stbir__inline stbir_uint64 STBIR_PROFILE_FUNC() + { + stbir_uint64 tsc; + asm volatile("mrs %0, cntvct_el0" : "=r" (tsc)); + return tsc; + } + +#endif + +#else // x64, arm + +#error Unknown platform for profiling. + +#endif // x64, arm + +#endif // STBIR_PROFILE_FUNC + +#define STBIR_ONLY_PROFILE_GET_SPLIT_INFO ,stbir__per_split_info * split_info +#define STBIR_ONLY_PROFILE_SET_SPLIT_INFO ,split_info + +#define STBIR_ONLY_PROFILE_BUILD_GET_INFO ,stbir__info * profile_info +#define STBIR_ONLY_PROFILE_BUILD_SET_INFO ,profile_info + +// super light-weight micro profiler +#define STBIR_PROFILE_START_ll( info, wh ) { stbir_uint64 wh##thiszonetime = STBIR_PROFILE_FUNC(); stbir_uint64 * wh##save_parent_excluded_ptr = info->current_zone_excluded_ptr; stbir_uint64 wh##current_zone_excluded = 0; info->current_zone_excluded_ptr = &wh##current_zone_excluded; +#define STBIR_PROFILE_END_ll( info, wh ) wh##thiszonetime = STBIR_PROFILE_FUNC() - wh##thiszonetime; info->profile.named.wh += wh##thiszonetime - wh##current_zone_excluded; *wh##save_parent_excluded_ptr += wh##thiszonetime; info->current_zone_excluded_ptr = wh##save_parent_excluded_ptr; } +#define STBIR_PROFILE_FIRST_START_ll( info, wh ) { int i; info->current_zone_excluded_ptr = &info->profile.named.total; for(i=0;iprofile.array);i++) info->profile.array[i]=0; } STBIR_PROFILE_START_ll( info, wh ); +#define STBIR_PROFILE_CLEAR_EXTRAS_ll( info, num ) { int extra; for(extra=1;extra<(num);extra++) { int i; for(i=0;iprofile.array);i++) (info)[extra].profile.array[i]=0; } } + +// for thread data +#define STBIR_PROFILE_START( wh ) STBIR_PROFILE_START_ll( split_info, wh ) +#define STBIR_PROFILE_END( wh ) STBIR_PROFILE_END_ll( split_info, wh ) +#define STBIR_PROFILE_FIRST_START( wh ) STBIR_PROFILE_FIRST_START_ll( split_info, wh ) +#define STBIR_PROFILE_CLEAR_EXTRAS() STBIR_PROFILE_CLEAR_EXTRAS_ll( split_info, split_count ) + +// for build data +#define STBIR_PROFILE_BUILD_START( wh ) STBIR_PROFILE_START_ll( profile_info, wh ) +#define STBIR_PROFILE_BUILD_END( wh ) STBIR_PROFILE_END_ll( profile_info, wh ) +#define STBIR_PROFILE_BUILD_FIRST_START( wh ) STBIR_PROFILE_FIRST_START_ll( profile_info, wh ) +#define STBIR_PROFILE_BUILD_CLEAR( info ) { int i; for(i=0;iprofile.array);i++) info->profile.array[i]=0; } + +#else // no profile + +#define STBIR_ONLY_PROFILE_GET_SPLIT_INFO +#define STBIR_ONLY_PROFILE_SET_SPLIT_INFO + +#define STBIR_ONLY_PROFILE_BUILD_GET_INFO +#define STBIR_ONLY_PROFILE_BUILD_SET_INFO + +#define STBIR_PROFILE_START( wh ) +#define STBIR_PROFILE_END( wh ) +#define STBIR_PROFILE_FIRST_START( wh ) +#define STBIR_PROFILE_CLEAR_EXTRAS( ) + +#define STBIR_PROFILE_BUILD_START( wh ) +#define STBIR_PROFILE_BUILD_END( wh ) +#define STBIR_PROFILE_BUILD_FIRST_START( wh ) +#define STBIR_PROFILE_BUILD_CLEAR( info ) + +#endif // stbir_profile + +#ifndef STBIR_CEILF +#include +#if _MSC_VER <= 1200 // support VC6 for Sean +#define STBIR_CEILF(x) ((float)ceil((float)(x))) +#define STBIR_FLOORF(x) ((float)floor((float)(x))) +#else +#define STBIR_CEILF(x) ceilf(x) +#define STBIR_FLOORF(x) floorf(x) +#endif +#endif + +#ifndef STBIR_MEMCPY +// For memcpy +#include +#define STBIR_MEMCPY( dest, src, len ) memcpy( dest, src, len ) +#endif + +#ifndef STBIR_SIMD + +// memcpy that is specifically intentionally overlapping (src is smaller then dest, so can be +// a normal forward copy, bytes is divisible by 4 and bytes is greater than or equal to +// the diff between dest and src) +static void stbir_overlapping_memcpy( void * dest, void const * src, size_t bytes ) +{ + char STBIR_SIMD_STREAMOUT_PTR (*) sd = (char*) src; + char STBIR_SIMD_STREAMOUT_PTR( * ) s_end = ((char*) src) + bytes; + ptrdiff_t ofs_to_dest = (char*)dest - (char*)src; + + if ( ofs_to_dest >= 8 ) // is the overlap more than 8 away + { + char STBIR_SIMD_STREAMOUT_PTR( * ) s_end8 = ((char*) src) + (bytes&~7); + + if ( ( ( ((ptrdiff_t)dest)|((ptrdiff_t)src) ) & 7 ) == 0 ) // is it 8byte aligned? + { + STBIR_NO_UNROLL_LOOP_START + do + { + STBIR_NO_UNROLL(sd); + *(stbir_uint64*)( sd + ofs_to_dest ) = *(stbir_uint64*) sd; + sd += 8; + } while ( sd < s_end8 ); + } + else + { + STBIR_NO_UNROLL_LOOP_START + do + { + int a,b; + STBIR_NO_UNROLL(sd); + a = ((int*)sd)[0]; + b = ((int*)sd)[1]; + ((int*)( sd + ofs_to_dest ))[0] = a; + ((int*)( sd + ofs_to_dest ))[1] = b; + sd += 8; + } while ( sd < s_end8 ); + } + + if ( sd == s_end ) + return; + } + + STBIR_NO_UNROLL_LOOP_START + do + { + STBIR_NO_UNROLL(sd); + *(int*)( sd + ofs_to_dest ) = *(int*) sd; + sd += 4; + } while ( sd < s_end ); +} + +#endif + +static float stbir__filter_trapezoid(float x, float scale, void * user_data) +{ + float halfscale = scale / 2; + float t = 0.5f + halfscale; + STBIR_ASSERT(scale <= 1); + STBIR__UNUSED(user_data); + + if ( x < 0.0f ) x = -x; + + if (x >= t) + return 0.0f; + else + { + float r = 0.5f - halfscale; + if (x <= r) + return 1.0f; + else + return (t - x) / scale; + } +} + +static float stbir__support_trapezoid(float scale, void * user_data) +{ + STBIR__UNUSED(user_data); + return 0.5f + scale / 2.0f; +} + +static float stbir__filter_triangle(float x, float s, void * user_data) +{ + STBIR__UNUSED(s); + STBIR__UNUSED(user_data); + + if ( x < 0.0f ) x = -x; + + if (x <= 1.0f) + return 1.0f - x; + else + return 0.0f; +} + +static float stbir__filter_point(float x, float s, void * user_data) +{ + STBIR__UNUSED(x); + STBIR__UNUSED(s); + STBIR__UNUSED(user_data); + + return 1.0f; +} + +static float stbir__filter_cubic(float x, float s, void * user_data) +{ + STBIR__UNUSED(s); + STBIR__UNUSED(user_data); + + if ( x < 0.0f ) x = -x; + + if (x < 1.0f) + return (4.0f + x*x*(3.0f*x - 6.0f))/6.0f; + else if (x < 2.0f) + return (8.0f + x*(-12.0f + x*(6.0f - x)))/6.0f; + + return (0.0f); +} + +static float stbir__filter_catmullrom(float x, float s, void * user_data) +{ + STBIR__UNUSED(s); + STBIR__UNUSED(user_data); + + if ( x < 0.0f ) x = -x; + + if (x < 1.0f) + return 1.0f - x*x*(2.5f - 1.5f*x); + else if (x < 2.0f) + return 2.0f - x*(4.0f + x*(0.5f*x - 2.5f)); + + return (0.0f); +} + +static float stbir__filter_mitchell(float x, float s, void * user_data) +{ + STBIR__UNUSED(s); + STBIR__UNUSED(user_data); + + if ( x < 0.0f ) x = -x; + + if (x < 1.0f) + return (16.0f + x*x*(21.0f * x - 36.0f))/18.0f; + else if (x < 2.0f) + return (32.0f + x*(-60.0f + x*(36.0f - 7.0f*x)))/18.0f; + + return (0.0f); +} + +static float stbir__support_zeropoint5(float s, void * user_data) +{ + STBIR__UNUSED(s); + STBIR__UNUSED(user_data); + return 0.5f; +} + +static float stbir__support_one(float s, void * user_data) +{ + STBIR__UNUSED(s); + STBIR__UNUSED(user_data); + return 1; +} + +static float stbir__support_two(float s, void * user_data) +{ + STBIR__UNUSED(s); + STBIR__UNUSED(user_data); + return 2; +} + +// This is the maximum number of input samples that can affect an output sample +// with the given filter from the output pixel's perspective +static int stbir__get_filter_pixel_width(stbir__support_callback * support, float scale, void * user_data) +{ + STBIR_ASSERT(support != 0); + + if ( scale >= ( 1.0f-stbir__small_float ) ) // upscale + return (int)STBIR_CEILF(support(1.0f/scale,user_data) * 2.0f); + else + return (int)STBIR_CEILF(support(scale,user_data) * 2.0f / scale); +} + +// this is how many coefficents per run of the filter (which is different +// from the filter_pixel_width depending on if we are scattering or gathering) +static int stbir__get_coefficient_width(stbir__sampler * samp, int is_gather, void * user_data) +{ + float scale = samp->scale_info.scale; + stbir__support_callback * support = samp->filter_support; + + switch( is_gather ) + { + case 1: + return (int)STBIR_CEILF(support(1.0f / scale, user_data) * 2.0f); + case 2: + return (int)STBIR_CEILF(support(scale, user_data) * 2.0f / scale); + case 0: + return (int)STBIR_CEILF(support(scale, user_data) * 2.0f); + default: + STBIR_ASSERT( (is_gather >= 0 ) && (is_gather <= 2 ) ); + return 0; + } +} + +static int stbir__get_contributors(stbir__sampler * samp, int is_gather) +{ + if (is_gather) + return samp->scale_info.output_sub_size; + else + return (samp->scale_info.input_full_size + samp->filter_pixel_margin * 2); +} + +static int stbir__edge_zero_full( int n, int max ) +{ + STBIR__UNUSED(n); + STBIR__UNUSED(max); + return 0; // NOTREACHED +} + +static int stbir__edge_clamp_full( int n, int max ) +{ + if (n < 0) + return 0; + + if (n >= max) + return max - 1; + + return n; // NOTREACHED +} + +static int stbir__edge_reflect_full( int n, int max ) +{ + if (n < 0) + { + if (n > -max) + return -n; + else + return max - 1; + } + + if (n >= max) + { + int max2 = max * 2; + if (n >= max2) + return 0; + else + return max2 - n - 1; + } + + return n; // NOTREACHED +} + +static int stbir__edge_wrap_full( int n, int max ) +{ + if (n >= 0) + return (n % max); + else + { + int m = (-n) % max; + + if (m != 0) + m = max - m; + + return (m); + } +} + +typedef int stbir__edge_wrap_func( int n, int max ); +static stbir__edge_wrap_func * stbir__edge_wrap_slow[] = +{ + stbir__edge_clamp_full, // STBIR_EDGE_CLAMP + stbir__edge_reflect_full, // STBIR_EDGE_REFLECT + stbir__edge_wrap_full, // STBIR_EDGE_WRAP + stbir__edge_zero_full, // STBIR_EDGE_ZERO +}; + +stbir__inline static int stbir__edge_wrap(stbir_edge edge, int n, int max) +{ + // avoid per-pixel switch + if (n >= 0 && n < max) + return n; + return stbir__edge_wrap_slow[edge]( n, max ); +} + +#define STBIR__MERGE_RUNS_PIXEL_THRESHOLD 16 + +// get information on the extents of a sampler +static void stbir__get_extents( stbir__sampler * samp, stbir__extents * scanline_extents ) +{ + int j, stop; + int left_margin, right_margin; + int min_n = 0x7fffffff, max_n = -0x7fffffff; + int min_left = 0x7fffffff, max_left = -0x7fffffff; + int min_right = 0x7fffffff, max_right = -0x7fffffff; + stbir_edge edge = samp->edge; + stbir__contributors* contributors = samp->contributors; + int output_sub_size = samp->scale_info.output_sub_size; + int input_full_size = samp->scale_info.input_full_size; + int filter_pixel_margin = samp->filter_pixel_margin; + + STBIR_ASSERT( samp->is_gather ); + + stop = output_sub_size; + for (j = 0; j < stop; j++ ) + { + STBIR_ASSERT( contributors[j].n1 >= contributors[j].n0 ); + if ( contributors[j].n0 < min_n ) + { + min_n = contributors[j].n0; + stop = j + filter_pixel_margin; // if we find a new min, only scan another filter width + if ( stop > output_sub_size ) stop = output_sub_size; + } + } + + stop = 0; + for (j = output_sub_size - 1; j >= stop; j-- ) + { + STBIR_ASSERT( contributors[j].n1 >= contributors[j].n0 ); + if ( contributors[j].n1 > max_n ) + { + max_n = contributors[j].n1; + stop = j - filter_pixel_margin; // if we find a new max, only scan another filter width + if (stop<0) stop = 0; + } + } + + STBIR_ASSERT( scanline_extents->conservative.n0 <= min_n ); + STBIR_ASSERT( scanline_extents->conservative.n1 >= max_n ); + + // now calculate how much into the margins we really read + left_margin = 0; + if ( min_n < 0 ) + { + left_margin = -min_n; + min_n = 0; + } + + right_margin = 0; + if ( max_n >= input_full_size ) + { + right_margin = max_n - input_full_size + 1; + max_n = input_full_size - 1; + } + + // index 1 is margin pixel extents (how many pixels we hang over the edge) + scanline_extents->edge_sizes[0] = left_margin; + scanline_extents->edge_sizes[1] = right_margin; + + // index 2 is pixels read from the input + scanline_extents->spans[0].n0 = min_n; + scanline_extents->spans[0].n1 = max_n; + scanline_extents->spans[0].pixel_offset_for_input = min_n; + + // default to no other input range + scanline_extents->spans[1].n0 = 0; + scanline_extents->spans[1].n1 = -1; + scanline_extents->spans[1].pixel_offset_for_input = 0; + + // don't have to do edge calc for zero clamp + if ( edge == STBIR_EDGE_ZERO ) + return; + + // convert margin pixels to the pixels within the input (min and max) + for( j = -left_margin ; j < 0 ; j++ ) + { + int p = stbir__edge_wrap( edge, j, input_full_size ); + if ( p < min_left ) + min_left = p; + if ( p > max_left ) + max_left = p; + } + + for( j = input_full_size ; j < (input_full_size + right_margin) ; j++ ) + { + int p = stbir__edge_wrap( edge, j, input_full_size ); + if ( p < min_right ) + min_right = p; + if ( p > max_right ) + max_right = p; + } + + // merge the left margin pixel region if it connects within 4 pixels of main pixel region + if ( min_left != 0x7fffffff ) + { + if ( ( ( min_left <= min_n ) && ( ( max_left + STBIR__MERGE_RUNS_PIXEL_THRESHOLD ) >= min_n ) ) || + ( ( min_n <= min_left ) && ( ( max_n + STBIR__MERGE_RUNS_PIXEL_THRESHOLD ) >= max_left ) ) ) + { + scanline_extents->spans[0].n0 = min_n = stbir__min( min_n, min_left ); + scanline_extents->spans[0].n1 = max_n = stbir__max( max_n, max_left ); + scanline_extents->spans[0].pixel_offset_for_input = min_n; + left_margin = 0; + } + } + + // merge the right margin pixel region if it connects within 4 pixels of main pixel region + if ( min_right != 0x7fffffff ) + { + if ( ( ( min_right <= min_n ) && ( ( max_right + STBIR__MERGE_RUNS_PIXEL_THRESHOLD ) >= min_n ) ) || + ( ( min_n <= min_right ) && ( ( max_n + STBIR__MERGE_RUNS_PIXEL_THRESHOLD ) >= max_right ) ) ) + { + scanline_extents->spans[0].n0 = min_n = stbir__min( min_n, min_right ); + scanline_extents->spans[0].n1 = max_n = stbir__max( max_n, max_right ); + scanline_extents->spans[0].pixel_offset_for_input = min_n; + right_margin = 0; + } + } + + STBIR_ASSERT( scanline_extents->conservative.n0 <= min_n ); + STBIR_ASSERT( scanline_extents->conservative.n1 >= max_n ); + + // you get two ranges when you have the WRAP edge mode and you are doing just the a piece of the resize + // so you need to get a second run of pixels from the opposite side of the scanline (which you + // wouldn't need except for WRAP) + + + // if we can't merge the min_left range, add it as a second range + if ( ( left_margin ) && ( min_left != 0x7fffffff ) ) + { + stbir__span * newspan = scanline_extents->spans + 1; + STBIR_ASSERT( right_margin == 0 ); + if ( min_left < scanline_extents->spans[0].n0 ) + { + scanline_extents->spans[1].pixel_offset_for_input = scanline_extents->spans[0].n0; + scanline_extents->spans[1].n0 = scanline_extents->spans[0].n0; + scanline_extents->spans[1].n1 = scanline_extents->spans[0].n1; + --newspan; + } + newspan->pixel_offset_for_input = min_left; + newspan->n0 = -left_margin; + newspan->n1 = ( max_left - min_left ) - left_margin; + scanline_extents->edge_sizes[0] = 0; // don't need to copy the left margin, since we are directly decoding into the margin + } + // if we can't merge the min_right range, add it as a second range + else + if ( ( right_margin ) && ( min_right != 0x7fffffff ) ) + { + stbir__span * newspan = scanline_extents->spans + 1; + if ( min_right < scanline_extents->spans[0].n0 ) + { + scanline_extents->spans[1].pixel_offset_for_input = scanline_extents->spans[0].n0; + scanline_extents->spans[1].n0 = scanline_extents->spans[0].n0; + scanline_extents->spans[1].n1 = scanline_extents->spans[0].n1; + --newspan; + } + newspan->pixel_offset_for_input = min_right; + newspan->n0 = scanline_extents->spans[1].n1 + 1; + newspan->n1 = scanline_extents->spans[1].n1 + 1 + ( max_right - min_right ); + scanline_extents->edge_sizes[1] = 0; // don't need to copy the right margin, since we are directly decoding into the margin + } + + // sort the spans into write output order + if ( ( scanline_extents->spans[1].n1 > scanline_extents->spans[1].n0 ) && ( scanline_extents->spans[0].n0 > scanline_extents->spans[1].n0 ) ) + { + stbir__span tspan = scanline_extents->spans[0]; + scanline_extents->spans[0] = scanline_extents->spans[1]; + scanline_extents->spans[1] = tspan; + } +} + +static void stbir__calculate_in_pixel_range( int * first_pixel, int * last_pixel, float out_pixel_center, float out_filter_radius, float inv_scale, float out_shift, int input_size, stbir_edge edge ) +{ + int first, last; + float out_pixel_influence_lowerbound = out_pixel_center - out_filter_radius; + float out_pixel_influence_upperbound = out_pixel_center + out_filter_radius; + + float in_pixel_influence_lowerbound = (out_pixel_influence_lowerbound + out_shift) * inv_scale; + float in_pixel_influence_upperbound = (out_pixel_influence_upperbound + out_shift) * inv_scale; + + first = (int)(STBIR_FLOORF(in_pixel_influence_lowerbound + 0.5f)); + last = (int)(STBIR_FLOORF(in_pixel_influence_upperbound - 0.5f)); + if ( last < first ) last = first; // point sample mode can span a value *right* at 0.5, and cause these to cross + + if ( edge == STBIR_EDGE_WRAP ) + { + if ( first < -input_size ) + first = -input_size; + if ( last >= (input_size*2)) + last = (input_size*2) - 1; + } + + *first_pixel = first; + *last_pixel = last; +} + +static void stbir__calculate_coefficients_for_gather_upsample( float out_filter_radius, stbir__kernel_callback * kernel, stbir__scale_info * scale_info, int num_contributors, stbir__contributors* contributors, float* coefficient_group, int coefficient_width, stbir_edge edge, void * user_data ) +{ + int n, end; + float inv_scale = scale_info->inv_scale; + float out_shift = scale_info->pixel_shift; + int input_size = scale_info->input_full_size; + int numerator = scale_info->scale_numerator; + int polyphase = ( ( scale_info->scale_is_rational ) && ( numerator < num_contributors ) ); + + // Looping through out pixels + end = num_contributors; if ( polyphase ) end = numerator; + for (n = 0; n < end; n++) + { + int i; + int last_non_zero; + float out_pixel_center = (float)n + 0.5f; + float in_center_of_out = (out_pixel_center + out_shift) * inv_scale; + + int in_first_pixel, in_last_pixel; + + stbir__calculate_in_pixel_range( &in_first_pixel, &in_last_pixel, out_pixel_center, out_filter_radius, inv_scale, out_shift, input_size, edge ); + + // make sure we never generate a range larger than our precalculated coeff width + // this only happens in point sample mode, but it's a good safe thing to do anyway + if ( ( in_last_pixel - in_first_pixel + 1 ) > coefficient_width ) + in_last_pixel = in_first_pixel + coefficient_width - 1; + + last_non_zero = -1; + for (i = 0; i <= in_last_pixel - in_first_pixel; i++) + { + float in_pixel_center = (float)(i + in_first_pixel) + 0.5f; + float coeff = kernel(in_center_of_out - in_pixel_center, inv_scale, user_data); + + // kill denormals + if ( ( ( coeff < stbir__small_float ) && ( coeff > -stbir__small_float ) ) ) + { + if ( i == 0 ) // if we're at the front, just eat zero contributors + { + STBIR_ASSERT ( ( in_last_pixel - in_first_pixel ) != 0 ); // there should be at least one contrib + ++in_first_pixel; + i--; + continue; + } + coeff = 0; // make sure is fully zero (should keep denormals away) + } + else + last_non_zero = i; + + coefficient_group[i] = coeff; + } + + in_last_pixel = last_non_zero+in_first_pixel; // kills trailing zeros + contributors->n0 = in_first_pixel; + contributors->n1 = in_last_pixel; + + STBIR_ASSERT(contributors->n1 >= contributors->n0); + + ++contributors; + coefficient_group += coefficient_width; + } +} + +static void stbir__insert_coeff( stbir__contributors * contribs, float * coeffs, int new_pixel, float new_coeff, int max_width ) +{ + if ( contribs->n1 < contribs->n0 ) // this first clause should never happen, but handle in case + { + contribs->n0 = contribs->n1 = new_pixel; + coeffs[0] = new_coeff; + } + else if ( new_pixel <= contribs->n1 ) // before the end + { + if ( new_pixel < contribs->n0 ) // before the front? + { + if ( ( contribs->n1 - new_pixel + 1 ) <= max_width ) + { + int j, o = contribs->n0 - new_pixel; + for ( j = contribs->n1 - contribs->n0 ; j >= 0 ; j-- ) + coeffs[ j + o ] = coeffs[ j ]; + for ( j = 1 ; j < o ; j++ ) + coeffs[ j ] = 0; + coeffs[ 0 ] = new_coeff; + contribs->n0 = new_pixel; + } + } + else + { + // add new weight to existing coeff if already there + coeffs[ new_pixel - contribs->n0 ] += new_coeff; + } + } + else + { + if ( ( new_pixel - contribs->n0 + 1 ) <= max_width ) + { + int j, e = new_pixel - contribs->n0; + for( j = ( contribs->n1 - contribs->n0 ) + 1 ; j < e ; j++ ) // clear in-betweens coeffs if there are any + coeffs[j] = 0; + + coeffs[ e ] = new_coeff; + contribs->n1 = new_pixel; + } + } +} + +static void stbir__calculate_out_pixel_range( int * first_pixel, int * last_pixel, float in_pixel_center, float in_pixels_radius, float scale, float out_shift, int out_size ) +{ + float in_pixel_influence_lowerbound = in_pixel_center - in_pixels_radius; + float in_pixel_influence_upperbound = in_pixel_center + in_pixels_radius; + float out_pixel_influence_lowerbound = in_pixel_influence_lowerbound * scale - out_shift; + float out_pixel_influence_upperbound = in_pixel_influence_upperbound * scale - out_shift; + int out_first_pixel = (int)(STBIR_FLOORF(out_pixel_influence_lowerbound + 0.5f)); + int out_last_pixel = (int)(STBIR_FLOORF(out_pixel_influence_upperbound - 0.5f)); + + if ( out_first_pixel < 0 ) + out_first_pixel = 0; + if ( out_last_pixel >= out_size ) + out_last_pixel = out_size - 1; + *first_pixel = out_first_pixel; + *last_pixel = out_last_pixel; +} + +static void stbir__calculate_coefficients_for_gather_downsample( int start, int end, float in_pixels_radius, stbir__kernel_callback * kernel, stbir__scale_info * scale_info, int coefficient_width, int num_contributors, stbir__contributors * contributors, float * coefficient_group, void * user_data ) +{ + int in_pixel; + int i; + int first_out_inited = -1; + float scale = scale_info->scale; + float out_shift = scale_info->pixel_shift; + int out_size = scale_info->output_sub_size; + int numerator = scale_info->scale_numerator; + int polyphase = ( ( scale_info->scale_is_rational ) && ( numerator < out_size ) ); + + STBIR__UNUSED(num_contributors); + + // Loop through the input pixels + for (in_pixel = start; in_pixel < end; in_pixel++) + { + float in_pixel_center = (float)in_pixel + 0.5f; + float out_center_of_in = in_pixel_center * scale - out_shift; + int out_first_pixel, out_last_pixel; + + stbir__calculate_out_pixel_range( &out_first_pixel, &out_last_pixel, in_pixel_center, in_pixels_radius, scale, out_shift, out_size ); + + if ( out_first_pixel > out_last_pixel ) + continue; + + // clamp or exit if we are using polyphase filtering, and the limit is up + if ( polyphase ) + { + // when polyphase, you only have to do coeffs up to the numerator count + if ( out_first_pixel == numerator ) + break; + + // don't do any extra work, clamp last pixel at numerator too + if ( out_last_pixel >= numerator ) + out_last_pixel = numerator - 1; + } + + for (i = 0; i <= out_last_pixel - out_first_pixel; i++) + { + float out_pixel_center = (float)(i + out_first_pixel) + 0.5f; + float x = out_pixel_center - out_center_of_in; + float coeff = kernel(x, scale, user_data) * scale; + + // kill the coeff if it's too small (avoid denormals) + if ( ( ( coeff < stbir__small_float ) && ( coeff > -stbir__small_float ) ) ) + coeff = 0.0f; + + { + int out = i + out_first_pixel; + float * coeffs = coefficient_group + out * coefficient_width; + stbir__contributors * contribs = contributors + out; + + // is this the first time this output pixel has been seen? Init it. + if ( out > first_out_inited ) + { + STBIR_ASSERT( out == ( first_out_inited + 1 ) ); // ensure we have only advanced one at time + first_out_inited = out; + contribs->n0 = in_pixel; + contribs->n1 = in_pixel; + coeffs[0] = coeff; + } + else + { + // insert on end (always in order) + if ( coeffs[0] == 0.0f ) // if the first coefficent is zero, then zap it for this coeffs + { + STBIR_ASSERT( ( in_pixel - contribs->n0 ) == 1 ); // ensure that when we zap, we're at the 2nd pos + contribs->n0 = in_pixel; + } + contribs->n1 = in_pixel; + STBIR_ASSERT( ( in_pixel - contribs->n0 ) < coefficient_width ); + coeffs[in_pixel - contribs->n0] = coeff; + } + } + } + } +} + +#ifdef STBIR_RENORMALIZE_IN_FLOAT +#define STBIR_RENORM_TYPE float +#else +#define STBIR_RENORM_TYPE double +#endif + +static void stbir__cleanup_gathered_coefficients( stbir_edge edge, stbir__filter_extent_info* filter_info, stbir__scale_info * scale_info, int num_contributors, stbir__contributors* contributors, float * coefficient_group, int coefficient_width ) +{ + int input_size = scale_info->input_full_size; + int input_last_n1 = input_size - 1; + int n, end; + int lowest = 0x7fffffff; + int highest = -0x7fffffff; + int widest = -1; + int numerator = scale_info->scale_numerator; + int denominator = scale_info->scale_denominator; + int polyphase = ( ( scale_info->scale_is_rational ) && ( numerator < num_contributors ) ); + float * coeffs; + stbir__contributors * contribs; + + // weight all the coeffs for each sample + coeffs = coefficient_group; + contribs = contributors; + end = num_contributors; if ( polyphase ) end = numerator; + for (n = 0; n < end; n++) + { + int i; + STBIR_RENORM_TYPE filter_scale, total_filter = 0; + int e; + + // add all contribs + e = contribs->n1 - contribs->n0; + for( i = 0 ; i <= e ; i++ ) + { + total_filter += (STBIR_RENORM_TYPE) coeffs[i]; + STBIR_ASSERT( ( coeffs[i] >= -2.0f ) && ( coeffs[i] <= 2.0f ) ); // check for wonky weights + } + + // rescale + if ( ( total_filter < stbir__small_float ) && ( total_filter > -stbir__small_float ) ) + { + // all coeffs are extremely small, just zero it + contribs->n1 = contribs->n0; + coeffs[0] = 0.0f; + } + else + { + // if the total isn't 1.0, rescale everything + if ( ( total_filter < (1.0f-stbir__small_float) ) || ( total_filter > (1.0f+stbir__small_float) ) ) + { + filter_scale = ((STBIR_RENORM_TYPE)1.0) / total_filter; + + // scale them all + for (i = 0; i <= e; i++) + coeffs[i] = (float) ( coeffs[i] * filter_scale ); + } + } + ++contribs; + coeffs += coefficient_width; + } + + // if we have a rational for the scale, we can exploit the polyphaseness to not calculate + // most of the coefficients, so we copy them here + if ( polyphase ) + { + stbir__contributors * prev_contribs = contributors; + stbir__contributors * cur_contribs = contributors + numerator; + + for( n = numerator ; n < num_contributors ; n++ ) + { + cur_contribs->n0 = prev_contribs->n0 + denominator; + cur_contribs->n1 = prev_contribs->n1 + denominator; + ++cur_contribs; + ++prev_contribs; + } + stbir_overlapping_memcpy( coefficient_group + numerator * coefficient_width, coefficient_group, ( num_contributors - numerator ) * coefficient_width * sizeof( coeffs[ 0 ] ) ); + } + + coeffs = coefficient_group; + contribs = contributors; + + for (n = 0; n < num_contributors; n++) + { + int i; + + // in zero edge mode, just remove out of bounds contribs completely (since their weights are accounted for now) + if ( edge == STBIR_EDGE_ZERO ) + { + // shrink the right side if necessary + if ( contribs->n1 > input_last_n1 ) + contribs->n1 = input_last_n1; + + // shrink the left side + if ( contribs->n0 < 0 ) + { + int j, left, skips = 0; + + skips = -contribs->n0; + contribs->n0 = 0; + + // now move down the weights + left = contribs->n1 - contribs->n0 + 1; + if ( left > 0 ) + { + for( j = 0 ; j < left ; j++ ) + coeffs[ j ] = coeffs[ j + skips ]; + } + } + } + else if ( ( edge == STBIR_EDGE_CLAMP ) || ( edge == STBIR_EDGE_REFLECT ) ) + { + // for clamp and reflect, calculate the true inbounds position (based on edge type) and just add that to the existing weight + + // right hand side first + if ( contribs->n1 > input_last_n1 ) + { + int start = contribs->n0; + int endi = contribs->n1; + contribs->n1 = input_last_n1; + for( i = input_size; i <= endi; i++ ) + stbir__insert_coeff( contribs, coeffs, stbir__edge_wrap_slow[edge]( i, input_size ), coeffs[i-start], coefficient_width ); + } + + // now check left hand edge + if ( contribs->n0 < 0 ) + { + int save_n0; + float save_n0_coeff; + float * c = coeffs - ( contribs->n0 + 1 ); + + // reinsert the coeffs with it reflected or clamped (insert accumulates, if the coeffs exist) + for( i = -1 ; i > contribs->n0 ; i-- ) + stbir__insert_coeff( contribs, coeffs, stbir__edge_wrap_slow[edge]( i, input_size ), *c--, coefficient_width ); + save_n0 = contribs->n0; + save_n0_coeff = c[0]; // save it, since we didn't do the final one (i==n0), because there might be too many coeffs to hold (before we resize)! + + // now slide all the coeffs down (since we have accumulated them in the positive contribs) and reset the first contrib + contribs->n0 = 0; + for(i = 0 ; i <= contribs->n1 ; i++ ) + coeffs[i] = coeffs[i-save_n0]; + + // now that we have shrunk down the contribs, we insert the first one safely + stbir__insert_coeff( contribs, coeffs, stbir__edge_wrap_slow[edge]( save_n0, input_size ), save_n0_coeff, coefficient_width ); + } + } + + if ( contribs->n0 <= contribs->n1 ) + { + int diff = contribs->n1 - contribs->n0 + 1; + while ( diff && ( coeffs[ diff-1 ] == 0.0f ) ) + --diff; + + contribs->n1 = contribs->n0 + diff - 1; + + if ( contribs->n0 <= contribs->n1 ) + { + if ( contribs->n0 < lowest ) + lowest = contribs->n0; + if ( contribs->n1 > highest ) + highest = contribs->n1; + if ( diff > widest ) + widest = diff; + } + + // re-zero out unused coefficients (if any) + for( i = diff ; i < coefficient_width ; i++ ) + coeffs[i] = 0.0f; + } + + ++contribs; + coeffs += coefficient_width; + } + filter_info->lowest = lowest; + filter_info->highest = highest; + filter_info->widest = widest; +} + +#undef STBIR_RENORM_TYPE + +static int stbir__pack_coefficients( int num_contributors, stbir__contributors* contributors, float * coefficents, int coefficient_width, int widest, int row0, int row1 ) +{ + #define STBIR_MOVE_1( dest, src ) { STBIR_NO_UNROLL(dest); memcpy((dest), (src), 1 * sizeof(float)); } + #define STBIR_MOVE_2( dest, src ) { STBIR_NO_UNROLL(dest); memcpy((dest), (src), 2 * sizeof(float)); } + #ifdef STBIR_SIMD + #define STBIR_MOVE_4( dest, src ) { stbir__simdf t; STBIR_NO_UNROLL(dest); stbir__simdf_load( t, src ); stbir__simdf_store( dest, t ); } + #else + #define STBIR_MOVE_4( dest, src ) { STBIR_NO_UNROLL(dest); memcpy((dest), (src), 4 * sizeof(float)); } + #endif + + int row_end = row1 + 1; + STBIR__UNUSED( row0 ); // only used in an assert + + if ( coefficient_width != widest ) + { + float * pc = coefficents; + float * coeffs = coefficents; + float * pc_end = coefficents + num_contributors * widest; + switch( widest ) + { + case 1: + STBIR_NO_UNROLL_LOOP_START + do { + STBIR_MOVE_1( pc, coeffs ); + ++pc; + coeffs += coefficient_width; + } while ( pc < pc_end ); + break; + case 2: + STBIR_NO_UNROLL_LOOP_START + do { + STBIR_MOVE_2( pc, coeffs ); + pc += 2; + coeffs += coefficient_width; + } while ( pc < pc_end ); + break; + case 3: + STBIR_NO_UNROLL_LOOP_START + do { + STBIR_MOVE_2( pc, coeffs ); + STBIR_MOVE_1( pc+2, coeffs+2 ); + pc += 3; + coeffs += coefficient_width; + } while ( pc < pc_end ); + break; + case 4: + STBIR_NO_UNROLL_LOOP_START + do { + STBIR_MOVE_4( pc, coeffs ); + pc += 4; + coeffs += coefficient_width; + } while ( pc < pc_end ); + break; + case 5: + STBIR_NO_UNROLL_LOOP_START + do { + STBIR_MOVE_4( pc, coeffs ); + STBIR_MOVE_1( pc+4, coeffs+4 ); + pc += 5; + coeffs += coefficient_width; + } while ( pc < pc_end ); + break; + case 6: + STBIR_NO_UNROLL_LOOP_START + do { + STBIR_MOVE_4( pc, coeffs ); + STBIR_MOVE_2( pc+4, coeffs+4 ); + pc += 6; + coeffs += coefficient_width; + } while ( pc < pc_end ); + break; + case 7: + STBIR_NO_UNROLL_LOOP_START + do { + STBIR_MOVE_4( pc, coeffs ); + STBIR_MOVE_2( pc+4, coeffs+4 ); + STBIR_MOVE_1( pc+6, coeffs+6 ); + pc += 7; + coeffs += coefficient_width; + } while ( pc < pc_end ); + break; + case 8: + STBIR_NO_UNROLL_LOOP_START + do { + STBIR_MOVE_4( pc, coeffs ); + STBIR_MOVE_4( pc+4, coeffs+4 ); + pc += 8; + coeffs += coefficient_width; + } while ( pc < pc_end ); + break; + case 9: + STBIR_NO_UNROLL_LOOP_START + do { + STBIR_MOVE_4( pc, coeffs ); + STBIR_MOVE_4( pc+4, coeffs+4 ); + STBIR_MOVE_1( pc+8, coeffs+8 ); + pc += 9; + coeffs += coefficient_width; + } while ( pc < pc_end ); + break; + case 10: + STBIR_NO_UNROLL_LOOP_START + do { + STBIR_MOVE_4( pc, coeffs ); + STBIR_MOVE_4( pc+4, coeffs+4 ); + STBIR_MOVE_2( pc+8, coeffs+8 ); + pc += 10; + coeffs += coefficient_width; + } while ( pc < pc_end ); + break; + case 11: + STBIR_NO_UNROLL_LOOP_START + do { + STBIR_MOVE_4( pc, coeffs ); + STBIR_MOVE_4( pc+4, coeffs+4 ); + STBIR_MOVE_2( pc+8, coeffs+8 ); + STBIR_MOVE_1( pc+10, coeffs+10 ); + pc += 11; + coeffs += coefficient_width; + } while ( pc < pc_end ); + break; + case 12: + STBIR_NO_UNROLL_LOOP_START + do { + STBIR_MOVE_4( pc, coeffs ); + STBIR_MOVE_4( pc+4, coeffs+4 ); + STBIR_MOVE_4( pc+8, coeffs+8 ); + pc += 12; + coeffs += coefficient_width; + } while ( pc < pc_end ); + break; + default: + STBIR_NO_UNROLL_LOOP_START + do { + float * copy_end = pc + widest - 4; + float * c = coeffs; + do { + STBIR_NO_UNROLL( pc ); + STBIR_MOVE_4( pc, c ); + pc += 4; + c += 4; + } while ( pc <= copy_end ); + copy_end += 4; + STBIR_NO_UNROLL_LOOP_START + while ( pc < copy_end ) + { + STBIR_MOVE_1( pc, c ); + ++pc; ++c; + } + coeffs += coefficient_width; + } while ( pc < pc_end ); + break; + } + } + + // some horizontal routines read one float off the end (which is then masked off), so put in a sentinel so we don't read an snan or denormal + coefficents[ widest * num_contributors ] = 8888.0f; + + // the minimum we might read for unrolled filters widths is 12. So, we need to + // make sure we never read outside the decode buffer, by possibly moving + // the sample area back into the scanline, and putting zeros weights first. + // we start on the right edge and check until we're well past the possible + // clip area (2*widest). + { + stbir__contributors * contribs = contributors + num_contributors - 1; + float * coeffs = coefficents + widest * ( num_contributors - 1 ); + + // go until no chance of clipping (this is usually less than 8 lops) + while ( ( contribs >= contributors ) && ( ( contribs->n0 + widest*2 ) >= row_end ) ) + { + // might we clip?? + if ( ( contribs->n0 + widest ) > row_end ) + { + int stop_range = widest; + + // if range is larger than 12, it will be handled by generic loops that can terminate on the exact length + // of this contrib n1, instead of a fixed widest amount - so calculate this + if ( widest > 12 ) + { + int mod; + + // how far will be read in the n_coeff loop (which depends on the widest count mod4); + mod = widest & 3; + stop_range = ( ( ( contribs->n1 - contribs->n0 + 1 ) - mod + 3 ) & ~3 ) + mod; + + // the n_coeff loops do a minimum amount of coeffs, so factor that in! + if ( stop_range < ( 8 + mod ) ) stop_range = 8 + mod; + } + + // now see if we still clip with the refined range + if ( ( contribs->n0 + stop_range ) > row_end ) + { + int new_n0 = row_end - stop_range; + int num = contribs->n1 - contribs->n0 + 1; + int backup = contribs->n0 - new_n0; + float * from_co = coeffs + num - 1; + float * to_co = from_co + backup; + + STBIR_ASSERT( ( new_n0 >= row0 ) && ( new_n0 < contribs->n0 ) ); + + // move the coeffs over + while( num ) + { + *to_co-- = *from_co--; + --num; + } + // zero new positions + while ( to_co >= coeffs ) + *to_co-- = 0; + // set new start point + contribs->n0 = new_n0; + if ( widest > 12 ) + { + int mod; + + // how far will be read in the n_coeff loop (which depends on the widest count mod4); + mod = widest & 3; + stop_range = ( ( ( contribs->n1 - contribs->n0 + 1 ) - mod + 3 ) & ~3 ) + mod; + + // the n_coeff loops do a minimum amount of coeffs, so factor that in! + if ( stop_range < ( 8 + mod ) ) stop_range = 8 + mod; + } + } + } + --contribs; + coeffs -= widest; + } + } + + return widest; + #undef STBIR_MOVE_1 + #undef STBIR_MOVE_2 + #undef STBIR_MOVE_4 +} + +static void stbir__calculate_filters( stbir__sampler * samp, stbir__sampler * other_axis_for_pivot, void * user_data STBIR_ONLY_PROFILE_BUILD_GET_INFO ) +{ + int n; + float scale = samp->scale_info.scale; + stbir__kernel_callback * kernel = samp->filter_kernel; + stbir__support_callback * support = samp->filter_support; + float inv_scale = samp->scale_info.inv_scale; + int input_full_size = samp->scale_info.input_full_size; + int gather_num_contributors = samp->num_contributors; + stbir__contributors* gather_contributors = samp->contributors; + float * gather_coeffs = samp->coefficients; + int gather_coefficient_width = samp->coefficient_width; + + switch ( samp->is_gather ) + { + case 1: // gather upsample + { + float out_pixels_radius = support(inv_scale,user_data) * scale; + + stbir__calculate_coefficients_for_gather_upsample( out_pixels_radius, kernel, &samp->scale_info, gather_num_contributors, gather_contributors, gather_coeffs, gather_coefficient_width, samp->edge, user_data ); + + STBIR_PROFILE_BUILD_START( cleanup ); + stbir__cleanup_gathered_coefficients( samp->edge, &samp->extent_info, &samp->scale_info, gather_num_contributors, gather_contributors, gather_coeffs, gather_coefficient_width ); + STBIR_PROFILE_BUILD_END( cleanup ); + } + break; + + case 0: // scatter downsample (only on vertical) + case 2: // gather downsample + { + float in_pixels_radius = support(scale,user_data) * inv_scale; + int filter_pixel_margin = samp->filter_pixel_margin; + int input_end = input_full_size + filter_pixel_margin; + + // if this is a scatter, we do a downsample gather to get the coeffs, and then pivot after + if ( !samp->is_gather ) + { + // check if we are using the same gather downsample on the horizontal as this vertical, + // if so, then we don't have to generate them, we can just pivot from the horizontal. + if ( other_axis_for_pivot ) + { + gather_contributors = other_axis_for_pivot->contributors; + gather_coeffs = other_axis_for_pivot->coefficients; + gather_coefficient_width = other_axis_for_pivot->coefficient_width; + gather_num_contributors = other_axis_for_pivot->num_contributors; + samp->extent_info.lowest = other_axis_for_pivot->extent_info.lowest; + samp->extent_info.highest = other_axis_for_pivot->extent_info.highest; + samp->extent_info.widest = other_axis_for_pivot->extent_info.widest; + goto jump_right_to_pivot; + } + + gather_contributors = samp->gather_prescatter_contributors; + gather_coeffs = samp->gather_prescatter_coefficients; + gather_coefficient_width = samp->gather_prescatter_coefficient_width; + gather_num_contributors = samp->gather_prescatter_num_contributors; + } + + stbir__calculate_coefficients_for_gather_downsample( -filter_pixel_margin, input_end, in_pixels_radius, kernel, &samp->scale_info, gather_coefficient_width, gather_num_contributors, gather_contributors, gather_coeffs, user_data ); + + STBIR_PROFILE_BUILD_START( cleanup ); + stbir__cleanup_gathered_coefficients( samp->edge, &samp->extent_info, &samp->scale_info, gather_num_contributors, gather_contributors, gather_coeffs, gather_coefficient_width ); + STBIR_PROFILE_BUILD_END( cleanup ); + + if ( !samp->is_gather ) + { + // if this is a scatter (vertical only), then we need to pivot the coeffs + stbir__contributors * scatter_contributors; + int highest_set; + + jump_right_to_pivot: + + STBIR_PROFILE_BUILD_START( pivot ); + + highest_set = (-filter_pixel_margin) - 1; + for (n = 0; n < gather_num_contributors; n++) + { + int k; + int gn0 = gather_contributors->n0, gn1 = gather_contributors->n1; + int scatter_coefficient_width = samp->coefficient_width; + float * scatter_coeffs = samp->coefficients + ( gn0 + filter_pixel_margin ) * scatter_coefficient_width; + float * g_coeffs = gather_coeffs; + scatter_contributors = samp->contributors + ( gn0 + filter_pixel_margin ); + + for (k = gn0 ; k <= gn1 ; k++ ) + { + float gc = *g_coeffs++; + + // skip zero and denormals - must skip zeros to avoid adding coeffs beyond scatter_coefficient_width + // (which happens when pivoting from horizontal, which might have dummy zeros) + if ( ( ( gc >= stbir__small_float ) || ( gc <= -stbir__small_float ) ) ) + { + if ( ( k > highest_set ) || ( scatter_contributors->n0 > scatter_contributors->n1 ) ) + { + { + // if we are skipping over several contributors, we need to clear the skipped ones + stbir__contributors * clear_contributors = samp->contributors + ( highest_set + filter_pixel_margin + 1); + while ( clear_contributors < scatter_contributors ) + { + clear_contributors->n0 = 0; + clear_contributors->n1 = -1; + ++clear_contributors; + } + } + scatter_contributors->n0 = n; + scatter_contributors->n1 = n; + scatter_coeffs[0] = gc; + highest_set = k; + } + else + { + stbir__insert_coeff( scatter_contributors, scatter_coeffs, n, gc, scatter_coefficient_width ); + } + STBIR_ASSERT( ( scatter_contributors->n1 - scatter_contributors->n0 + 1 ) <= scatter_coefficient_width ); + } + ++scatter_contributors; + scatter_coeffs += scatter_coefficient_width; + } + + ++gather_contributors; + gather_coeffs += gather_coefficient_width; + } + + // now clear any unset contribs + { + stbir__contributors * clear_contributors = samp->contributors + ( highest_set + filter_pixel_margin + 1); + stbir__contributors * end_contributors = samp->contributors + samp->num_contributors; + while ( clear_contributors < end_contributors ) + { + clear_contributors->n0 = 0; + clear_contributors->n1 = -1; + ++clear_contributors; + } + } + + STBIR_PROFILE_BUILD_END( pivot ); + } + } + break; + } +} + + +//======================================================================================================== +// scanline decoders and encoders + +#define stbir__coder_min_num 1 +#define STB_IMAGE_RESIZE_DO_CODERS +#include STBIR__HEADER_FILENAME + +#define stbir__decode_suffix BGRA +#define stbir__decode_swizzle +#define stbir__decode_order0 2 +#define stbir__decode_order1 1 +#define stbir__decode_order2 0 +#define stbir__decode_order3 3 +#define stbir__encode_order0 2 +#define stbir__encode_order1 1 +#define stbir__encode_order2 0 +#define stbir__encode_order3 3 +#define stbir__coder_min_num 4 +#define STB_IMAGE_RESIZE_DO_CODERS +#include STBIR__HEADER_FILENAME + +#define stbir__decode_suffix ARGB +#define stbir__decode_swizzle +#define stbir__decode_order0 1 +#define stbir__decode_order1 2 +#define stbir__decode_order2 3 +#define stbir__decode_order3 0 +#define stbir__encode_order0 3 +#define stbir__encode_order1 0 +#define stbir__encode_order2 1 +#define stbir__encode_order3 2 +#define stbir__coder_min_num 4 +#define STB_IMAGE_RESIZE_DO_CODERS +#include STBIR__HEADER_FILENAME + +#define stbir__decode_suffix ABGR +#define stbir__decode_swizzle +#define stbir__decode_order0 3 +#define stbir__decode_order1 2 +#define stbir__decode_order2 1 +#define stbir__decode_order3 0 +#define stbir__encode_order0 3 +#define stbir__encode_order1 2 +#define stbir__encode_order2 1 +#define stbir__encode_order3 0 +#define stbir__coder_min_num 4 +#define STB_IMAGE_RESIZE_DO_CODERS +#include STBIR__HEADER_FILENAME + +#define stbir__decode_suffix AR +#define stbir__decode_swizzle +#define stbir__decode_order0 1 +#define stbir__decode_order1 0 +#define stbir__decode_order2 3 +#define stbir__decode_order3 2 +#define stbir__encode_order0 1 +#define stbir__encode_order1 0 +#define stbir__encode_order2 3 +#define stbir__encode_order3 2 +#define stbir__coder_min_num 2 +#define STB_IMAGE_RESIZE_DO_CODERS +#include STBIR__HEADER_FILENAME + + +// fancy alpha means we expand to keep both premultipied and non-premultiplied color channels +static void stbir__fancy_alpha_weight_4ch( float * out_buffer, int width_times_channels ) +{ + float STBIR_STREAMOUT_PTR(*) out = out_buffer; + float const * end_decode = out_buffer + ( width_times_channels / 4 ) * 7; // decode buffer aligned to end of out_buffer + float STBIR_STREAMOUT_PTR(*) decode = (float*)end_decode - width_times_channels; + + // fancy alpha is stored internally as R G B A Rpm Gpm Bpm + + #ifdef STBIR_SIMD + + #ifdef STBIR_SIMD8 + decode += 16; + STBIR_NO_UNROLL_LOOP_START + while ( decode <= end_decode ) + { + stbir__simdf8 d0,d1,a0,a1,p0,p1; + STBIR_NO_UNROLL(decode); + stbir__simdf8_load( d0, decode-16 ); + stbir__simdf8_load( d1, decode-16+8 ); + stbir__simdf8_0123to33333333( a0, d0 ); + stbir__simdf8_0123to33333333( a1, d1 ); + stbir__simdf8_mult( p0, a0, d0 ); + stbir__simdf8_mult( p1, a1, d1 ); + stbir__simdf8_bot4s( a0, d0, p0 ); + stbir__simdf8_bot4s( a1, d1, p1 ); + stbir__simdf8_top4s( d0, d0, p0 ); + stbir__simdf8_top4s( d1, d1, p1 ); + stbir__simdf8_store ( out, a0 ); + stbir__simdf8_store ( out+7, d0 ); + stbir__simdf8_store ( out+14, a1 ); + stbir__simdf8_store ( out+21, d1 ); + decode += 16; + out += 28; + } + decode -= 16; + #else + decode += 8; + STBIR_NO_UNROLL_LOOP_START + while ( decode <= end_decode ) + { + stbir__simdf d0,a0,d1,a1,p0,p1; + STBIR_NO_UNROLL(decode); + stbir__simdf_load( d0, decode-8 ); + stbir__simdf_load( d1, decode-8+4 ); + stbir__simdf_0123to3333( a0, d0 ); + stbir__simdf_0123to3333( a1, d1 ); + stbir__simdf_mult( p0, a0, d0 ); + stbir__simdf_mult( p1, a1, d1 ); + stbir__simdf_store ( out, d0 ); + stbir__simdf_store ( out+4, p0 ); + stbir__simdf_store ( out+7, d1 ); + stbir__simdf_store ( out+7+4, p1 ); + decode += 8; + out += 14; + } + decode -= 8; + #endif + + // might be one last odd pixel + #ifdef STBIR_SIMD8 + STBIR_NO_UNROLL_LOOP_START + while ( decode < end_decode ) + #else + if ( decode < end_decode ) + #endif + { + stbir__simdf d,a,p; + STBIR_NO_UNROLL(decode); + stbir__simdf_load( d, decode ); + stbir__simdf_0123to3333( a, d ); + stbir__simdf_mult( p, a, d ); + stbir__simdf_store ( out, d ); + stbir__simdf_store ( out+4, p ); + decode += 4; + out += 7; + } + + #else + + while( decode < end_decode ) + { + float r = decode[0], g = decode[1], b = decode[2], alpha = decode[3]; + out[0] = r; + out[1] = g; + out[2] = b; + out[3] = alpha; + out[4] = r * alpha; + out[5] = g * alpha; + out[6] = b * alpha; + out += 7; + decode += 4; + } + + #endif +} + +static void stbir__fancy_alpha_weight_2ch( float * out_buffer, int width_times_channels ) +{ + float STBIR_STREAMOUT_PTR(*) out = out_buffer; + float const * end_decode = out_buffer + ( width_times_channels / 2 ) * 3; + float STBIR_STREAMOUT_PTR(*) decode = (float*)end_decode - width_times_channels; + + // for fancy alpha, turns into: [X A Xpm][X A Xpm],etc + + #ifdef STBIR_SIMD + + decode += 8; + if ( decode <= end_decode ) + { + STBIR_NO_UNROLL_LOOP_START + do { + #ifdef STBIR_SIMD8 + stbir__simdf8 d0,a0,p0; + STBIR_NO_UNROLL(decode); + stbir__simdf8_load( d0, decode-8 ); + stbir__simdf8_0123to11331133( p0, d0 ); + stbir__simdf8_0123to00220022( a0, d0 ); + stbir__simdf8_mult( p0, p0, a0 ); + + stbir__simdf_store2( out, stbir__if_simdf8_cast_to_simdf4( d0 ) ); + stbir__simdf_store( out+2, stbir__if_simdf8_cast_to_simdf4( p0 ) ); + stbir__simdf_store2h( out+3, stbir__if_simdf8_cast_to_simdf4( d0 ) ); + + stbir__simdf_store2( out+6, stbir__simdf8_gettop4( d0 ) ); + stbir__simdf_store( out+8, stbir__simdf8_gettop4( p0 ) ); + stbir__simdf_store2h( out+9, stbir__simdf8_gettop4( d0 ) ); + #else + stbir__simdf d0,a0,d1,a1,p0,p1; + STBIR_NO_UNROLL(decode); + stbir__simdf_load( d0, decode-8 ); + stbir__simdf_load( d1, decode-8+4 ); + stbir__simdf_0123to1133( p0, d0 ); + stbir__simdf_0123to1133( p1, d1 ); + stbir__simdf_0123to0022( a0, d0 ); + stbir__simdf_0123to0022( a1, d1 ); + stbir__simdf_mult( p0, p0, a0 ); + stbir__simdf_mult( p1, p1, a1 ); + + stbir__simdf_store2( out, d0 ); + stbir__simdf_store( out+2, p0 ); + stbir__simdf_store2h( out+3, d0 ); + + stbir__simdf_store2( out+6, d1 ); + stbir__simdf_store( out+8, p1 ); + stbir__simdf_store2h( out+9, d1 ); + #endif + decode += 8; + out += 12; + } while ( decode <= end_decode ); + } + decode -= 8; + #endif + + STBIR_SIMD_NO_UNROLL_LOOP_START + while( decode < end_decode ) + { + float x = decode[0], y = decode[1]; + STBIR_SIMD_NO_UNROLL(decode); + out[0] = x; + out[1] = y; + out[2] = x * y; + out += 3; + decode += 2; + } +} + +static void stbir__fancy_alpha_unweight_4ch( float * encode_buffer, int width_times_channels ) +{ + float STBIR_SIMD_STREAMOUT_PTR(*) encode = encode_buffer; + float STBIR_SIMD_STREAMOUT_PTR(*) input = encode_buffer; + float const * end_output = encode_buffer + width_times_channels; + + // fancy RGBA is stored internally as R G B A Rpm Gpm Bpm + + STBIR_SIMD_NO_UNROLL_LOOP_START + do { + float alpha = input[3]; +#ifdef STBIR_SIMD + stbir__simdf i,ia; + STBIR_SIMD_NO_UNROLL(encode); + if ( alpha < stbir__small_float ) + { + stbir__simdf_load( i, input ); + stbir__simdf_store( encode, i ); + } + else + { + stbir__simdf_load1frep4( ia, 1.0f / alpha ); + stbir__simdf_load( i, input+4 ); + stbir__simdf_mult( i, i, ia ); + stbir__simdf_store( encode, i ); + encode[3] = alpha; + } +#else + if ( alpha < stbir__small_float ) + { + encode[0] = input[0]; + encode[1] = input[1]; + encode[2] = input[2]; + } + else + { + float ialpha = 1.0f / alpha; + encode[0] = input[4] * ialpha; + encode[1] = input[5] * ialpha; + encode[2] = input[6] * ialpha; + } + encode[3] = alpha; +#endif + + input += 7; + encode += 4; + } while ( encode < end_output ); +} + +// format: [X A Xpm][X A Xpm] etc +static void stbir__fancy_alpha_unweight_2ch( float * encode_buffer, int width_times_channels ) +{ + float STBIR_SIMD_STREAMOUT_PTR(*) encode = encode_buffer; + float STBIR_SIMD_STREAMOUT_PTR(*) input = encode_buffer; + float const * end_output = encode_buffer + width_times_channels; + + do { + float alpha = input[1]; + encode[0] = input[0]; + if ( alpha >= stbir__small_float ) + encode[0] = input[2] / alpha; + encode[1] = alpha; + + input += 3; + encode += 2; + } while ( encode < end_output ); +} + +static void stbir__simple_alpha_weight_4ch( float * decode_buffer, int width_times_channels ) +{ + float STBIR_STREAMOUT_PTR(*) decode = decode_buffer; + float const * end_decode = decode_buffer + width_times_channels; + + #ifdef STBIR_SIMD + { + decode += 2 * stbir__simdfX_float_count; + STBIR_NO_UNROLL_LOOP_START + while ( decode <= end_decode ) + { + stbir__simdfX d0,a0,d1,a1; + STBIR_NO_UNROLL(decode); + stbir__simdfX_load( d0, decode-2*stbir__simdfX_float_count ); + stbir__simdfX_load( d1, decode-2*stbir__simdfX_float_count+stbir__simdfX_float_count ); + stbir__simdfX_aaa1( a0, d0, STBIR_onesX ); + stbir__simdfX_aaa1( a1, d1, STBIR_onesX ); + stbir__simdfX_mult( d0, d0, a0 ); + stbir__simdfX_mult( d1, d1, a1 ); + stbir__simdfX_store ( decode-2*stbir__simdfX_float_count, d0 ); + stbir__simdfX_store ( decode-2*stbir__simdfX_float_count+stbir__simdfX_float_count, d1 ); + decode += 2 * stbir__simdfX_float_count; + } + decode -= 2 * stbir__simdfX_float_count; + + // few last pixels remnants + #ifdef STBIR_SIMD8 + STBIR_NO_UNROLL_LOOP_START + while ( decode < end_decode ) + #else + if ( decode < end_decode ) + #endif + { + stbir__simdf d,a; + stbir__simdf_load( d, decode ); + stbir__simdf_aaa1( a, d, STBIR__CONSTF(STBIR_ones) ); + stbir__simdf_mult( d, d, a ); + stbir__simdf_store ( decode, d ); + decode += 4; + } + } + + #else + + while( decode < end_decode ) + { + float alpha = decode[3]; + decode[0] *= alpha; + decode[1] *= alpha; + decode[2] *= alpha; + decode += 4; + } + + #endif +} + +static void stbir__simple_alpha_weight_2ch( float * decode_buffer, int width_times_channels ) +{ + float STBIR_STREAMOUT_PTR(*) decode = decode_buffer; + float const * end_decode = decode_buffer + width_times_channels; + + #ifdef STBIR_SIMD + decode += 2 * stbir__simdfX_float_count; + STBIR_NO_UNROLL_LOOP_START + while ( decode <= end_decode ) + { + stbir__simdfX d0,a0,d1,a1; + STBIR_NO_UNROLL(decode); + stbir__simdfX_load( d0, decode-2*stbir__simdfX_float_count ); + stbir__simdfX_load( d1, decode-2*stbir__simdfX_float_count+stbir__simdfX_float_count ); + stbir__simdfX_a1a1( a0, d0, STBIR_onesX ); + stbir__simdfX_a1a1( a1, d1, STBIR_onesX ); + stbir__simdfX_mult( d0, d0, a0 ); + stbir__simdfX_mult( d1, d1, a1 ); + stbir__simdfX_store ( decode-2*stbir__simdfX_float_count, d0 ); + stbir__simdfX_store ( decode-2*stbir__simdfX_float_count+stbir__simdfX_float_count, d1 ); + decode += 2 * stbir__simdfX_float_count; + } + decode -= 2 * stbir__simdfX_float_count; + #endif + + STBIR_SIMD_NO_UNROLL_LOOP_START + while( decode < end_decode ) + { + float alpha = decode[1]; + STBIR_SIMD_NO_UNROLL(decode); + decode[0] *= alpha; + decode += 2; + } +} + +static void stbir__simple_alpha_unweight_4ch( float * encode_buffer, int width_times_channels ) +{ + float STBIR_SIMD_STREAMOUT_PTR(*) encode = encode_buffer; + float const * end_output = encode_buffer + width_times_channels; + + STBIR_SIMD_NO_UNROLL_LOOP_START + do { + float alpha = encode[3]; + +#ifdef STBIR_SIMD + stbir__simdf i,ia; + STBIR_SIMD_NO_UNROLL(encode); + if ( alpha >= stbir__small_float ) + { + stbir__simdf_load1frep4( ia, 1.0f / alpha ); + stbir__simdf_load( i, encode ); + stbir__simdf_mult( i, i, ia ); + stbir__simdf_store( encode, i ); + encode[3] = alpha; + } +#else + if ( alpha >= stbir__small_float ) + { + float ialpha = 1.0f / alpha; + encode[0] *= ialpha; + encode[1] *= ialpha; + encode[2] *= ialpha; + } +#endif + encode += 4; + } while ( encode < end_output ); +} + +static void stbir__simple_alpha_unweight_2ch( float * encode_buffer, int width_times_channels ) +{ + float STBIR_SIMD_STREAMOUT_PTR(*) encode = encode_buffer; + float const * end_output = encode_buffer + width_times_channels; + + do { + float alpha = encode[1]; + if ( alpha >= stbir__small_float ) + encode[0] /= alpha; + encode += 2; + } while ( encode < end_output ); +} + + +// only used in RGB->BGR or BGR->RGB +static void stbir__simple_flip_3ch( float * decode_buffer, int width_times_channels ) +{ + float STBIR_STREAMOUT_PTR(*) decode = decode_buffer; + float const * end_decode = decode_buffer + width_times_channels; + +#ifdef STBIR_SIMD + #ifdef stbir__simdf_swiz2 // do we have two argument swizzles? + end_decode -= 12; + STBIR_NO_UNROLL_LOOP_START + while( decode <= end_decode ) + { + // on arm64 8 instructions, no overlapping stores + stbir__simdf a,b,c,na,nb; + STBIR_SIMD_NO_UNROLL(decode); + stbir__simdf_load( a, decode ); + stbir__simdf_load( b, decode+4 ); + stbir__simdf_load( c, decode+8 ); + + na = stbir__simdf_swiz2( a, b, 2, 1, 0, 5 ); + b = stbir__simdf_swiz2( a, b, 4, 3, 6, 7 ); + nb = stbir__simdf_swiz2( b, c, 0, 1, 4, 3 ); + c = stbir__simdf_swiz2( b, c, 2, 7, 6, 5 ); + + stbir__simdf_store( decode, na ); + stbir__simdf_store( decode+4, nb ); + stbir__simdf_store( decode+8, c ); + decode += 12; + } + end_decode += 12; + #else + end_decode -= 24; + STBIR_NO_UNROLL_LOOP_START + while( decode <= end_decode ) + { + // 26 instructions on x64 + stbir__simdf a,b,c,d,e,f,g; + float i21, i23; + STBIR_SIMD_NO_UNROLL(decode); + stbir__simdf_load( a, decode ); + stbir__simdf_load( b, decode+3 ); + stbir__simdf_load( c, decode+6 ); + stbir__simdf_load( d, decode+9 ); + stbir__simdf_load( e, decode+12 ); + stbir__simdf_load( f, decode+15 ); + stbir__simdf_load( g, decode+18 ); + + a = stbir__simdf_swiz( a, 2, 1, 0, 3 ); + b = stbir__simdf_swiz( b, 2, 1, 0, 3 ); + c = stbir__simdf_swiz( c, 2, 1, 0, 3 ); + d = stbir__simdf_swiz( d, 2, 1, 0, 3 ); + e = stbir__simdf_swiz( e, 2, 1, 0, 3 ); + f = stbir__simdf_swiz( f, 2, 1, 0, 3 ); + g = stbir__simdf_swiz( g, 2, 1, 0, 3 ); + + // stores overlap, need to be in order, + stbir__simdf_store( decode, a ); + i21 = decode[21]; + stbir__simdf_store( decode+3, b ); + i23 = decode[23]; + stbir__simdf_store( decode+6, c ); + stbir__simdf_store( decode+9, d ); + stbir__simdf_store( decode+12, e ); + stbir__simdf_store( decode+15, f ); + stbir__simdf_store( decode+18, g ); + decode[21] = i23; + decode[23] = i21; + decode += 24; + } + end_decode += 24; + #endif +#else + end_decode -= 12; + STBIR_NO_UNROLL_LOOP_START + while( decode <= end_decode ) + { + // 16 instructions + float t0,t1,t2,t3; + STBIR_NO_UNROLL(decode); + t0 = decode[0]; t1 = decode[3]; t2 = decode[6]; t3 = decode[9]; + decode[0] = decode[2]; decode[3] = decode[5]; decode[6] = decode[8]; decode[9] = decode[11]; + decode[2] = t0; decode[5] = t1; decode[8] = t2; decode[11] = t3; + decode += 12; + } + end_decode += 12; +#endif + + STBIR_NO_UNROLL_LOOP_START + while( decode < end_decode ) + { + float t = decode[0]; + STBIR_NO_UNROLL(decode); + decode[0] = decode[2]; + decode[2] = t; + decode += 3; + } +} + + + +static void stbir__decode_scanline(stbir__info const * stbir_info, int n, float * output_buffer STBIR_ONLY_PROFILE_GET_SPLIT_INFO ) +{ + int channels = stbir_info->channels; + int effective_channels = stbir_info->effective_channels; + int input_sample_in_bytes = stbir__type_size[stbir_info->input_type] * channels; + stbir_edge edge_horizontal = stbir_info->horizontal.edge; + stbir_edge edge_vertical = stbir_info->vertical.edge; + int row = stbir__edge_wrap(edge_vertical, n, stbir_info->vertical.scale_info.input_full_size); + const void* input_plane_data = ( (char *) stbir_info->input_data ) + (size_t)row * (size_t) stbir_info->input_stride_bytes; + stbir__span const * spans = stbir_info->scanline_extents.spans; + float * full_decode_buffer = output_buffer - stbir_info->scanline_extents.conservative.n0 * effective_channels; + float * last_decoded = 0; + + // if we are on edge_zero, and we get in here with an out of bounds n, then the calculate filters has failed + STBIR_ASSERT( !(edge_vertical == STBIR_EDGE_ZERO && (n < 0 || n >= stbir_info->vertical.scale_info.input_full_size)) ); + + do + { + float * decode_buffer; + void const * input_data; + float * end_decode; + int width_times_channels; + int width; + + if ( spans->n1 < spans->n0 ) + break; + + width = spans->n1 + 1 - spans->n0; + decode_buffer = full_decode_buffer + spans->n0 * effective_channels; + end_decode = full_decode_buffer + ( spans->n1 + 1 ) * effective_channels; + width_times_channels = width * channels; + + // read directly out of input plane by default + input_data = ( (char*)input_plane_data ) + spans->pixel_offset_for_input * input_sample_in_bytes; + + // if we have an input callback, call it to get the input data + if ( stbir_info->in_pixels_cb ) + { + // call the callback with a temp buffer (that they can choose to use or not). the temp is just right aligned memory in the decode_buffer itself + input_data = stbir_info->in_pixels_cb( ( (char*) end_decode ) - ( width * input_sample_in_bytes ) + ( ( stbir_info->input_type != STBIR_TYPE_FLOAT ) ? ( sizeof(float)*STBIR_INPUT_CALLBACK_PADDING ) : 0 ), input_plane_data, width, spans->pixel_offset_for_input, row, stbir_info->user_data ); + } + + STBIR_PROFILE_START( decode ); + // convert the pixels info the float decode_buffer, (we index from end_decode, so that when channelsdecode_pixels( (float*)end_decode - width_times_channels, width_times_channels, input_data ); + STBIR_PROFILE_END( decode ); + + if (stbir_info->alpha_weight) + { + STBIR_PROFILE_START( alpha ); + stbir_info->alpha_weight( decode_buffer, width_times_channels ); + STBIR_PROFILE_END( alpha ); + } + + ++spans; + } while ( spans <= ( &stbir_info->scanline_extents.spans[1] ) ); + + // handle the edge_wrap filter (all other types are handled back out at the calculate_filter stage) + // basically the idea here is that if we have the whole scanline in memory, we don't redecode the + // wrapped edge pixels, and instead just memcpy them from the scanline into the edge positions + if ( ( edge_horizontal == STBIR_EDGE_WRAP ) && ( stbir_info->scanline_extents.edge_sizes[0] | stbir_info->scanline_extents.edge_sizes[1] ) ) + { + // this code only runs if we're in edge_wrap, and we're doing the entire scanline + int e, start_x[2]; + int input_full_size = stbir_info->horizontal.scale_info.input_full_size; + + start_x[0] = -stbir_info->scanline_extents.edge_sizes[0]; // left edge start x + start_x[1] = input_full_size; // right edge + + for( e = 0; e < 2 ; e++ ) + { + // do each margin + int margin = stbir_info->scanline_extents.edge_sizes[e]; + if ( margin ) + { + int x = start_x[e]; + float * marg = full_decode_buffer + x * effective_channels; + float const * src = full_decode_buffer + stbir__edge_wrap(edge_horizontal, x, input_full_size) * effective_channels; + STBIR_MEMCPY( marg, src, margin * effective_channels * sizeof(float) ); + if ( e == 1 ) last_decoded = marg + margin * effective_channels; + } + } + } + + // some of the horizontal gathers read one float off the edge (which is masked out), but we force a zero here to make sure no NaNs leak in + // (we can't pre-zero it, because the input callback can use that area as padding) + last_decoded[0] = 0.0f; + + // we clear this extra float, because the final output pixel filter kernel might have used one less coeff than the max filter width + // when this happens, we do read that pixel from the input, so it too could be Nan, so just zero an extra one. + // this fits because each scanline is padded by three floats (STBIR_INPUT_CALLBACK_PADDING) + last_decoded[1] = 0.0f; +} + + +//================= +// Do 1 channel horizontal routines + +#ifdef STBIR_SIMD + +#define stbir__1_coeff_only() \ + stbir__simdf tot,c; \ + STBIR_SIMD_NO_UNROLL(decode); \ + stbir__simdf_load1( c, hc ); \ + stbir__simdf_mult1_mem( tot, c, decode ); + +#define stbir__2_coeff_only() \ + stbir__simdf tot,c,d; \ + STBIR_SIMD_NO_UNROLL(decode); \ + stbir__simdf_load2z( c, hc ); \ + stbir__simdf_load2( d, decode ); \ + stbir__simdf_mult( tot, c, d ); \ + stbir__simdf_0123to1230( c, tot ); \ + stbir__simdf_add1( tot, tot, c ); + +#define stbir__3_coeff_only() \ + stbir__simdf tot,c,t; \ + STBIR_SIMD_NO_UNROLL(decode); \ + stbir__simdf_load( c, hc ); \ + stbir__simdf_mult_mem( tot, c, decode ); \ + stbir__simdf_0123to1230( c, tot ); \ + stbir__simdf_0123to2301( t, tot ); \ + stbir__simdf_add1( tot, tot, c ); \ + stbir__simdf_add1( tot, tot, t ); + +#define stbir__store_output_tiny() \ + stbir__simdf_store1( output, tot ); \ + horizontal_coefficients += coefficient_width; \ + ++horizontal_contributors; \ + output += 1; + +#define stbir__4_coeff_start() \ + stbir__simdf tot,c; \ + STBIR_SIMD_NO_UNROLL(decode); \ + stbir__simdf_load( c, hc ); \ + stbir__simdf_mult_mem( tot, c, decode ); \ + +#define stbir__4_coeff_continue_from_4( ofs ) \ + STBIR_SIMD_NO_UNROLL(decode); \ + stbir__simdf_load( c, hc + (ofs) ); \ + stbir__simdf_madd_mem( tot, tot, c, decode+(ofs) ); + +#define stbir__1_coeff_remnant( ofs ) \ + { stbir__simdf d; \ + stbir__simdf_load1z( c, hc + (ofs) ); \ + stbir__simdf_load1( d, decode + (ofs) ); \ + stbir__simdf_madd( tot, tot, d, c ); } + +#define stbir__2_coeff_remnant( ofs ) \ + { stbir__simdf d; \ + stbir__simdf_load2z( c, hc+(ofs) ); \ + stbir__simdf_load2( d, decode+(ofs) ); \ + stbir__simdf_madd( tot, tot, d, c ); } + +#define stbir__3_coeff_setup() \ + stbir__simdf mask; \ + stbir__simdf_load( mask, STBIR_mask + 3 ); + +#define stbir__3_coeff_remnant( ofs ) \ + stbir__simdf_load( c, hc+(ofs) ); \ + stbir__simdf_and( c, c, mask ); \ + stbir__simdf_madd_mem( tot, tot, c, decode+(ofs) ); + +#define stbir__store_output() \ + stbir__simdf_0123to2301( c, tot ); \ + stbir__simdf_add( tot, tot, c ); \ + stbir__simdf_0123to1230( c, tot ); \ + stbir__simdf_add1( tot, tot, c ); \ + stbir__simdf_store1( output, tot ); \ + horizontal_coefficients += coefficient_width; \ + ++horizontal_contributors; \ + output += 1; + +#else + +#define stbir__1_coeff_only() \ + float tot; \ + tot = decode[0]*hc[0]; + +#define stbir__2_coeff_only() \ + float tot; \ + tot = decode[0] * hc[0]; \ + tot += decode[1] * hc[1]; + +#define stbir__3_coeff_only() \ + float tot; \ + tot = decode[0] * hc[0]; \ + tot += decode[1] * hc[1]; \ + tot += decode[2] * hc[2]; + +#define stbir__store_output_tiny() \ + output[0] = tot; \ + horizontal_coefficients += coefficient_width; \ + ++horizontal_contributors; \ + output += 1; + +#define stbir__4_coeff_start() \ + float tot0,tot1,tot2,tot3; \ + tot0 = decode[0] * hc[0]; \ + tot1 = decode[1] * hc[1]; \ + tot2 = decode[2] * hc[2]; \ + tot3 = decode[3] * hc[3]; + +#define stbir__4_coeff_continue_from_4( ofs ) \ + tot0 += decode[0+(ofs)] * hc[0+(ofs)]; \ + tot1 += decode[1+(ofs)] * hc[1+(ofs)]; \ + tot2 += decode[2+(ofs)] * hc[2+(ofs)]; \ + tot3 += decode[3+(ofs)] * hc[3+(ofs)]; + +#define stbir__1_coeff_remnant( ofs ) \ + tot0 += decode[0+(ofs)] * hc[0+(ofs)]; + +#define stbir__2_coeff_remnant( ofs ) \ + tot0 += decode[0+(ofs)] * hc[0+(ofs)]; \ + tot1 += decode[1+(ofs)] * hc[1+(ofs)]; \ + +#define stbir__3_coeff_remnant( ofs ) \ + tot0 += decode[0+(ofs)] * hc[0+(ofs)]; \ + tot1 += decode[1+(ofs)] * hc[1+(ofs)]; \ + tot2 += decode[2+(ofs)] * hc[2+(ofs)]; + +#define stbir__store_output() \ + output[0] = (tot0+tot2)+(tot1+tot3); \ + horizontal_coefficients += coefficient_width; \ + ++horizontal_contributors; \ + output += 1; + +#endif + +#define STBIR__horizontal_channels 1 +#define STB_IMAGE_RESIZE_DO_HORIZONTALS +#include STBIR__HEADER_FILENAME + + +//================= +// Do 2 channel horizontal routines + +#ifdef STBIR_SIMD + +#define stbir__1_coeff_only() \ + stbir__simdf tot,c,d; \ + STBIR_SIMD_NO_UNROLL(decode); \ + stbir__simdf_load1z( c, hc ); \ + stbir__simdf_0123to0011( c, c ); \ + stbir__simdf_load2( d, decode ); \ + stbir__simdf_mult( tot, d, c ); + +#define stbir__2_coeff_only() \ + stbir__simdf tot,c; \ + STBIR_SIMD_NO_UNROLL(decode); \ + stbir__simdf_load2( c, hc ); \ + stbir__simdf_0123to0011( c, c ); \ + stbir__simdf_mult_mem( tot, c, decode ); + +#define stbir__3_coeff_only() \ + stbir__simdf tot,c,cs,d; \ + STBIR_SIMD_NO_UNROLL(decode); \ + stbir__simdf_load( cs, hc ); \ + stbir__simdf_0123to0011( c, cs ); \ + stbir__simdf_mult_mem( tot, c, decode ); \ + stbir__simdf_0123to2222( c, cs ); \ + stbir__simdf_load2z( d, decode+4 ); \ + stbir__simdf_madd( tot, tot, d, c ); + +#define stbir__store_output_tiny() \ + stbir__simdf_0123to2301( c, tot ); \ + stbir__simdf_add( tot, tot, c ); \ + stbir__simdf_store2( output, tot ); \ + horizontal_coefficients += coefficient_width; \ + ++horizontal_contributors; \ + output += 2; + +#ifdef STBIR_SIMD8 + +#define stbir__4_coeff_start() \ + stbir__simdf8 tot0,c,cs; \ + STBIR_SIMD_NO_UNROLL(decode); \ + stbir__simdf8_load4b( cs, hc ); \ + stbir__simdf8_0123to00112233( c, cs ); \ + stbir__simdf8_mult_mem( tot0, c, decode ); + +#define stbir__4_coeff_continue_from_4( ofs ) \ + STBIR_SIMD_NO_UNROLL(decode); \ + stbir__simdf8_load4b( cs, hc + (ofs) ); \ + stbir__simdf8_0123to00112233( c, cs ); \ + stbir__simdf8_madd_mem( tot0, tot0, c, decode+(ofs)*2 ); + +#define stbir__1_coeff_remnant( ofs ) \ + { stbir__simdf t,d; \ + stbir__simdf_load1z( t, hc + (ofs) ); \ + stbir__simdf_load2( d, decode + (ofs) * 2 ); \ + stbir__simdf_0123to0011( t, t ); \ + stbir__simdf_mult( t, t, d ); \ + stbir__simdf8_add4( tot0, tot0, t ); } + +#define stbir__2_coeff_remnant( ofs ) \ + { stbir__simdf t; \ + stbir__simdf_load2( t, hc + (ofs) ); \ + stbir__simdf_0123to0011( t, t ); \ + stbir__simdf_mult_mem( t, t, decode+(ofs)*2 ); \ + stbir__simdf8_add4( tot0, tot0, t ); } + +#define stbir__3_coeff_remnant( ofs ) \ + { stbir__simdf8 d; \ + stbir__simdf8_load4b( cs, hc + (ofs) ); \ + stbir__simdf8_0123to00112233( c, cs ); \ + stbir__simdf8_load6z( d, decode+(ofs)*2 ); \ + stbir__simdf8_madd( tot0, tot0, c, d ); } + +#define stbir__store_output() \ + { stbir__simdf t,d; \ + stbir__simdf8_add4halves( t, stbir__if_simdf8_cast_to_simdf4(tot0), tot0 ); \ + stbir__simdf_0123to2301( d, t ); \ + stbir__simdf_add( t, t, d ); \ + stbir__simdf_store2( output, t ); \ + horizontal_coefficients += coefficient_width; \ + ++horizontal_contributors; \ + output += 2; } + +#else + +#define stbir__4_coeff_start() \ + stbir__simdf tot0,tot1,c,cs; \ + STBIR_SIMD_NO_UNROLL(decode); \ + stbir__simdf_load( cs, hc ); \ + stbir__simdf_0123to0011( c, cs ); \ + stbir__simdf_mult_mem( tot0, c, decode ); \ + stbir__simdf_0123to2233( c, cs ); \ + stbir__simdf_mult_mem( tot1, c, decode+4 ); + +#define stbir__4_coeff_continue_from_4( ofs ) \ + STBIR_SIMD_NO_UNROLL(decode); \ + stbir__simdf_load( cs, hc + (ofs) ); \ + stbir__simdf_0123to0011( c, cs ); \ + stbir__simdf_madd_mem( tot0, tot0, c, decode+(ofs)*2 ); \ + stbir__simdf_0123to2233( c, cs ); \ + stbir__simdf_madd_mem( tot1, tot1, c, decode+(ofs)*2+4 ); + +#define stbir__1_coeff_remnant( ofs ) \ + { stbir__simdf d; \ + stbir__simdf_load1z( cs, hc + (ofs) ); \ + stbir__simdf_0123to0011( c, cs ); \ + stbir__simdf_load2( d, decode + (ofs) * 2 ); \ + stbir__simdf_madd( tot0, tot0, d, c ); } + +#define stbir__2_coeff_remnant( ofs ) \ + stbir__simdf_load2( cs, hc + (ofs) ); \ + stbir__simdf_0123to0011( c, cs ); \ + stbir__simdf_madd_mem( tot0, tot0, c, decode+(ofs)*2 ); + +#define stbir__3_coeff_remnant( ofs ) \ + { stbir__simdf d; \ + stbir__simdf_load( cs, hc + (ofs) ); \ + stbir__simdf_0123to0011( c, cs ); \ + stbir__simdf_madd_mem( tot0, tot0, c, decode+(ofs)*2 ); \ + stbir__simdf_0123to2222( c, cs ); \ + stbir__simdf_load2z( d, decode + (ofs) * 2 + 4 ); \ + stbir__simdf_madd( tot1, tot1, d, c ); } + +#define stbir__store_output() \ + stbir__simdf_add( tot0, tot0, tot1 ); \ + stbir__simdf_0123to2301( c, tot0 ); \ + stbir__simdf_add( tot0, tot0, c ); \ + stbir__simdf_store2( output, tot0 ); \ + horizontal_coefficients += coefficient_width; \ + ++horizontal_contributors; \ + output += 2; + +#endif + +#else + +#define stbir__1_coeff_only() \ + float tota,totb,c; \ + c = hc[0]; \ + tota = decode[0]*c; \ + totb = decode[1]*c; + +#define stbir__2_coeff_only() \ + float tota,totb,c; \ + c = hc[0]; \ + tota = decode[0]*c; \ + totb = decode[1]*c; \ + c = hc[1]; \ + tota += decode[2]*c; \ + totb += decode[3]*c; + +// this weird order of add matches the simd +#define stbir__3_coeff_only() \ + float tota,totb,c; \ + c = hc[0]; \ + tota = decode[0]*c; \ + totb = decode[1]*c; \ + c = hc[2]; \ + tota += decode[4]*c; \ + totb += decode[5]*c; \ + c = hc[1]; \ + tota += decode[2]*c; \ + totb += decode[3]*c; + +#define stbir__store_output_tiny() \ + output[0] = tota; \ + output[1] = totb; \ + horizontal_coefficients += coefficient_width; \ + ++horizontal_contributors; \ + output += 2; + +#define stbir__4_coeff_start() \ + float tota0,tota1,tota2,tota3,totb0,totb1,totb2,totb3,c; \ + c = hc[0]; \ + tota0 = decode[0]*c; \ + totb0 = decode[1]*c; \ + c = hc[1]; \ + tota1 = decode[2]*c; \ + totb1 = decode[3]*c; \ + c = hc[2]; \ + tota2 = decode[4]*c; \ + totb2 = decode[5]*c; \ + c = hc[3]; \ + tota3 = decode[6]*c; \ + totb3 = decode[7]*c; + +#define stbir__4_coeff_continue_from_4( ofs ) \ + c = hc[0+(ofs)]; \ + tota0 += decode[0+(ofs)*2]*c; \ + totb0 += decode[1+(ofs)*2]*c; \ + c = hc[1+(ofs)]; \ + tota1 += decode[2+(ofs)*2]*c; \ + totb1 += decode[3+(ofs)*2]*c; \ + c = hc[2+(ofs)]; \ + tota2 += decode[4+(ofs)*2]*c; \ + totb2 += decode[5+(ofs)*2]*c; \ + c = hc[3+(ofs)]; \ + tota3 += decode[6+(ofs)*2]*c; \ + totb3 += decode[7+(ofs)*2]*c; + +#define stbir__1_coeff_remnant( ofs ) \ + c = hc[0+(ofs)]; \ + tota0 += decode[0+(ofs)*2] * c; \ + totb0 += decode[1+(ofs)*2] * c; + +#define stbir__2_coeff_remnant( ofs ) \ + c = hc[0+(ofs)]; \ + tota0 += decode[0+(ofs)*2] * c; \ + totb0 += decode[1+(ofs)*2] * c; \ + c = hc[1+(ofs)]; \ + tota1 += decode[2+(ofs)*2] * c; \ + totb1 += decode[3+(ofs)*2] * c; + +#define stbir__3_coeff_remnant( ofs ) \ + c = hc[0+(ofs)]; \ + tota0 += decode[0+(ofs)*2] * c; \ + totb0 += decode[1+(ofs)*2] * c; \ + c = hc[1+(ofs)]; \ + tota1 += decode[2+(ofs)*2] * c; \ + totb1 += decode[3+(ofs)*2] * c; \ + c = hc[2+(ofs)]; \ + tota2 += decode[4+(ofs)*2] * c; \ + totb2 += decode[5+(ofs)*2] * c; + +#define stbir__store_output() \ + output[0] = (tota0+tota2)+(tota1+tota3); \ + output[1] = (totb0+totb2)+(totb1+totb3); \ + horizontal_coefficients += coefficient_width; \ + ++horizontal_contributors; \ + output += 2; + +#endif + +#define STBIR__horizontal_channels 2 +#define STB_IMAGE_RESIZE_DO_HORIZONTALS +#include STBIR__HEADER_FILENAME + + +//================= +// Do 3 channel horizontal routines + +#ifdef STBIR_SIMD + +#define stbir__1_coeff_only() \ + stbir__simdf tot,c,d; \ + STBIR_SIMD_NO_UNROLL(decode); \ + stbir__simdf_load1z( c, hc ); \ + stbir__simdf_0123to0001( c, c ); \ + stbir__simdf_load( d, decode ); \ + stbir__simdf_mult( tot, d, c ); + +#define stbir__2_coeff_only() \ + stbir__simdf tot,c,cs,d; \ + STBIR_SIMD_NO_UNROLL(decode); \ + stbir__simdf_load2( cs, hc ); \ + stbir__simdf_0123to0000( c, cs ); \ + stbir__simdf_load( d, decode ); \ + stbir__simdf_mult( tot, d, c ); \ + stbir__simdf_0123to1111( c, cs ); \ + stbir__simdf_load( d, decode+3 ); \ + stbir__simdf_madd( tot, tot, d, c ); + +#define stbir__3_coeff_only() \ + stbir__simdf tot,c,d,cs; \ + STBIR_SIMD_NO_UNROLL(decode); \ + stbir__simdf_load( cs, hc ); \ + stbir__simdf_0123to0000( c, cs ); \ + stbir__simdf_load( d, decode ); \ + stbir__simdf_mult( tot, d, c ); \ + stbir__simdf_0123to1111( c, cs ); \ + stbir__simdf_load( d, decode+3 ); \ + stbir__simdf_madd( tot, tot, d, c ); \ + stbir__simdf_0123to2222( c, cs ); \ + stbir__simdf_load( d, decode+6 ); \ + stbir__simdf_madd( tot, tot, d, c ); + +#define stbir__store_output_tiny() \ + stbir__simdf_store2( output, tot ); \ + stbir__simdf_0123to2301( tot, tot ); \ + stbir__simdf_store1( output+2, tot ); \ + horizontal_coefficients += coefficient_width; \ + ++horizontal_contributors; \ + output += 3; + +#ifdef STBIR_SIMD8 + +// we're loading from the XXXYYY decode by -1 to get the XXXYYY into different halves of the AVX reg fyi +#define stbir__4_coeff_start() \ + stbir__simdf8 tot0,tot1,c,cs; stbir__simdf t; \ + STBIR_SIMD_NO_UNROLL(decode); \ + stbir__simdf8_load4b( cs, hc ); \ + stbir__simdf8_0123to00001111( c, cs ); \ + stbir__simdf8_mult_mem( tot0, c, decode - 1 ); \ + stbir__simdf8_0123to22223333( c, cs ); \ + stbir__simdf8_mult_mem( tot1, c, decode+6 - 1 ); + +#define stbir__4_coeff_continue_from_4( ofs ) \ + STBIR_SIMD_NO_UNROLL(decode); \ + stbir__simdf8_load4b( cs, hc + (ofs) ); \ + stbir__simdf8_0123to00001111( c, cs ); \ + stbir__simdf8_madd_mem( tot0, tot0, c, decode+(ofs)*3 - 1 ); \ + stbir__simdf8_0123to22223333( c, cs ); \ + stbir__simdf8_madd_mem( tot1, tot1, c, decode+(ofs)*3 + 6 - 1 ); + +#define stbir__1_coeff_remnant( ofs ) \ + STBIR_SIMD_NO_UNROLL(decode); \ + stbir__simdf_load1rep4( t, hc + (ofs) ); \ + stbir__simdf8_madd_mem4( tot0, tot0, t, decode+(ofs)*3 - 1 ); + +#define stbir__2_coeff_remnant( ofs ) \ + STBIR_SIMD_NO_UNROLL(decode); \ + stbir__simdf8_load4b( cs, hc + (ofs) - 2 ); \ + stbir__simdf8_0123to22223333( c, cs ); \ + stbir__simdf8_madd_mem( tot0, tot0, c, decode+(ofs)*3 - 1 ); + + #define stbir__3_coeff_remnant( ofs ) \ + STBIR_SIMD_NO_UNROLL(decode); \ + stbir__simdf8_load4b( cs, hc + (ofs) ); \ + stbir__simdf8_0123to00001111( c, cs ); \ + stbir__simdf8_madd_mem( tot0, tot0, c, decode+(ofs)*3 - 1 ); \ + stbir__simdf8_0123to2222( t, cs ); \ + stbir__simdf8_madd_mem4( tot1, tot1, t, decode+(ofs)*3 + 6 - 1 ); + +#define stbir__store_output() \ + stbir__simdf8_add( tot0, tot0, tot1 ); \ + stbir__simdf_0123to1230( t, stbir__if_simdf8_cast_to_simdf4( tot0 ) ); \ + stbir__simdf8_add4halves( t, t, tot0 ); \ + horizontal_coefficients += coefficient_width; \ + ++horizontal_contributors; \ + output += 3; \ + if ( output < output_end ) \ + { \ + stbir__simdf_store( output-3, t ); \ + continue; \ + } \ + { stbir__simdf tt; stbir__simdf_0123to2301( tt, t ); \ + stbir__simdf_store2( output-3, t ); \ + stbir__simdf_store1( output+2-3, tt ); } \ + break; + + +#else + +#define stbir__4_coeff_start() \ + stbir__simdf tot0,tot1,tot2,c,cs; \ + STBIR_SIMD_NO_UNROLL(decode); \ + stbir__simdf_load( cs, hc ); \ + stbir__simdf_0123to0001( c, cs ); \ + stbir__simdf_mult_mem( tot0, c, decode ); \ + stbir__simdf_0123to1122( c, cs ); \ + stbir__simdf_mult_mem( tot1, c, decode+4 ); \ + stbir__simdf_0123to2333( c, cs ); \ + stbir__simdf_mult_mem( tot2, c, decode+8 ); + +#define stbir__4_coeff_continue_from_4( ofs ) \ + STBIR_SIMD_NO_UNROLL(decode); \ + stbir__simdf_load( cs, hc + (ofs) ); \ + stbir__simdf_0123to0001( c, cs ); \ + stbir__simdf_madd_mem( tot0, tot0, c, decode+(ofs)*3 ); \ + stbir__simdf_0123to1122( c, cs ); \ + stbir__simdf_madd_mem( tot1, tot1, c, decode+(ofs)*3+4 ); \ + stbir__simdf_0123to2333( c, cs ); \ + stbir__simdf_madd_mem( tot2, tot2, c, decode+(ofs)*3+8 ); + +#define stbir__1_coeff_remnant( ofs ) \ + STBIR_SIMD_NO_UNROLL(decode); \ + stbir__simdf_load1z( c, hc + (ofs) ); \ + stbir__simdf_0123to0001( c, c ); \ + stbir__simdf_madd_mem( tot0, tot0, c, decode+(ofs)*3 ); + +#define stbir__2_coeff_remnant( ofs ) \ + { stbir__simdf d; \ + STBIR_SIMD_NO_UNROLL(decode); \ + stbir__simdf_load2z( cs, hc + (ofs) ); \ + stbir__simdf_0123to0001( c, cs ); \ + stbir__simdf_madd_mem( tot0, tot0, c, decode+(ofs)*3 ); \ + stbir__simdf_0123to1122( c, cs ); \ + stbir__simdf_load2z( d, decode+(ofs)*3+4 ); \ + stbir__simdf_madd( tot1, tot1, c, d ); } + +#define stbir__3_coeff_remnant( ofs ) \ + { stbir__simdf d; \ + STBIR_SIMD_NO_UNROLL(decode); \ + stbir__simdf_load( cs, hc + (ofs) ); \ + stbir__simdf_0123to0001( c, cs ); \ + stbir__simdf_madd_mem( tot0, tot0, c, decode+(ofs)*3 ); \ + stbir__simdf_0123to1122( c, cs ); \ + stbir__simdf_madd_mem( tot1, tot1, c, decode+(ofs)*3+4 ); \ + stbir__simdf_0123to2222( c, cs ); \ + stbir__simdf_load1z( d, decode+(ofs)*3+8 ); \ + stbir__simdf_madd( tot2, tot2, c, d ); } + +#define stbir__store_output() \ + stbir__simdf_0123ABCDto3ABx( c, tot0, tot1 ); \ + stbir__simdf_0123ABCDto23Ax( cs, tot1, tot2 ); \ + stbir__simdf_0123to1230( tot2, tot2 ); \ + stbir__simdf_add( tot0, tot0, cs ); \ + stbir__simdf_add( c, c, tot2 ); \ + stbir__simdf_add( tot0, tot0, c ); \ + horizontal_coefficients += coefficient_width; \ + ++horizontal_contributors; \ + output += 3; \ + if ( output < output_end ) \ + { \ + stbir__simdf_store( output-3, tot0 ); \ + continue; \ + } \ + stbir__simdf_0123to2301( tot1, tot0 ); \ + stbir__simdf_store2( output-3, tot0 ); \ + stbir__simdf_store1( output+2-3, tot1 ); \ + break; + +#endif + +#else + +#define stbir__1_coeff_only() \ + float tot0, tot1, tot2, c; \ + c = hc[0]; \ + tot0 = decode[0]*c; \ + tot1 = decode[1]*c; \ + tot2 = decode[2]*c; + +#define stbir__2_coeff_only() \ + float tot0, tot1, tot2, c; \ + c = hc[0]; \ + tot0 = decode[0]*c; \ + tot1 = decode[1]*c; \ + tot2 = decode[2]*c; \ + c = hc[1]; \ + tot0 += decode[3]*c; \ + tot1 += decode[4]*c; \ + tot2 += decode[5]*c; + +#define stbir__3_coeff_only() \ + float tot0, tot1, tot2, c; \ + c = hc[0]; \ + tot0 = decode[0]*c; \ + tot1 = decode[1]*c; \ + tot2 = decode[2]*c; \ + c = hc[1]; \ + tot0 += decode[3]*c; \ + tot1 += decode[4]*c; \ + tot2 += decode[5]*c; \ + c = hc[2]; \ + tot0 += decode[6]*c; \ + tot1 += decode[7]*c; \ + tot2 += decode[8]*c; + +#define stbir__store_output_tiny() \ + output[0] = tot0; \ + output[1] = tot1; \ + output[2] = tot2; \ + horizontal_coefficients += coefficient_width; \ + ++horizontal_contributors; \ + output += 3; + +#define stbir__4_coeff_start() \ + float tota0,tota1,tota2,totb0,totb1,totb2,totc0,totc1,totc2,totd0,totd1,totd2,c; \ + c = hc[0]; \ + tota0 = decode[0]*c; \ + tota1 = decode[1]*c; \ + tota2 = decode[2]*c; \ + c = hc[1]; \ + totb0 = decode[3]*c; \ + totb1 = decode[4]*c; \ + totb2 = decode[5]*c; \ + c = hc[2]; \ + totc0 = decode[6]*c; \ + totc1 = decode[7]*c; \ + totc2 = decode[8]*c; \ + c = hc[3]; \ + totd0 = decode[9]*c; \ + totd1 = decode[10]*c; \ + totd2 = decode[11]*c; + +#define stbir__4_coeff_continue_from_4( ofs ) \ + c = hc[0+(ofs)]; \ + tota0 += decode[0+(ofs)*3]*c; \ + tota1 += decode[1+(ofs)*3]*c; \ + tota2 += decode[2+(ofs)*3]*c; \ + c = hc[1+(ofs)]; \ + totb0 += decode[3+(ofs)*3]*c; \ + totb1 += decode[4+(ofs)*3]*c; \ + totb2 += decode[5+(ofs)*3]*c; \ + c = hc[2+(ofs)]; \ + totc0 += decode[6+(ofs)*3]*c; \ + totc1 += decode[7+(ofs)*3]*c; \ + totc2 += decode[8+(ofs)*3]*c; \ + c = hc[3+(ofs)]; \ + totd0 += decode[9+(ofs)*3]*c; \ + totd1 += decode[10+(ofs)*3]*c; \ + totd2 += decode[11+(ofs)*3]*c; + +#define stbir__1_coeff_remnant( ofs ) \ + c = hc[0+(ofs)]; \ + tota0 += decode[0+(ofs)*3]*c; \ + tota1 += decode[1+(ofs)*3]*c; \ + tota2 += decode[2+(ofs)*3]*c; + +#define stbir__2_coeff_remnant( ofs ) \ + c = hc[0+(ofs)]; \ + tota0 += decode[0+(ofs)*3]*c; \ + tota1 += decode[1+(ofs)*3]*c; \ + tota2 += decode[2+(ofs)*3]*c; \ + c = hc[1+(ofs)]; \ + totb0 += decode[3+(ofs)*3]*c; \ + totb1 += decode[4+(ofs)*3]*c; \ + totb2 += decode[5+(ofs)*3]*c; \ + +#define stbir__3_coeff_remnant( ofs ) \ + c = hc[0+(ofs)]; \ + tota0 += decode[0+(ofs)*3]*c; \ + tota1 += decode[1+(ofs)*3]*c; \ + tota2 += decode[2+(ofs)*3]*c; \ + c = hc[1+(ofs)]; \ + totb0 += decode[3+(ofs)*3]*c; \ + totb1 += decode[4+(ofs)*3]*c; \ + totb2 += decode[5+(ofs)*3]*c; \ + c = hc[2+(ofs)]; \ + totc0 += decode[6+(ofs)*3]*c; \ + totc1 += decode[7+(ofs)*3]*c; \ + totc2 += decode[8+(ofs)*3]*c; + +#define stbir__store_output() \ + output[0] = (tota0+totc0)+(totb0+totd0); \ + output[1] = (tota1+totc1)+(totb1+totd1); \ + output[2] = (tota2+totc2)+(totb2+totd2); \ + horizontal_coefficients += coefficient_width; \ + ++horizontal_contributors; \ + output += 3; + +#endif + +#define STBIR__horizontal_channels 3 +#define STB_IMAGE_RESIZE_DO_HORIZONTALS +#include STBIR__HEADER_FILENAME + +//================= +// Do 4 channel horizontal routines + +#ifdef STBIR_SIMD + +#define stbir__1_coeff_only() \ + stbir__simdf tot,c; \ + STBIR_SIMD_NO_UNROLL(decode); \ + stbir__simdf_load1( c, hc ); \ + stbir__simdf_0123to0000( c, c ); \ + stbir__simdf_mult_mem( tot, c, decode ); + +#define stbir__2_coeff_only() \ + stbir__simdf tot,c,cs; \ + STBIR_SIMD_NO_UNROLL(decode); \ + stbir__simdf_load2( cs, hc ); \ + stbir__simdf_0123to0000( c, cs ); \ + stbir__simdf_mult_mem( tot, c, decode ); \ + stbir__simdf_0123to1111( c, cs ); \ + stbir__simdf_madd_mem( tot, tot, c, decode+4 ); + +#define stbir__3_coeff_only() \ + stbir__simdf tot,c,cs; \ + STBIR_SIMD_NO_UNROLL(decode); \ + stbir__simdf_load( cs, hc ); \ + stbir__simdf_0123to0000( c, cs ); \ + stbir__simdf_mult_mem( tot, c, decode ); \ + stbir__simdf_0123to1111( c, cs ); \ + stbir__simdf_madd_mem( tot, tot, c, decode+4 ); \ + stbir__simdf_0123to2222( c, cs ); \ + stbir__simdf_madd_mem( tot, tot, c, decode+8 ); + +#define stbir__store_output_tiny() \ + stbir__simdf_store( output, tot ); \ + horizontal_coefficients += coefficient_width; \ + ++horizontal_contributors; \ + output += 4; + +#ifdef STBIR_SIMD8 + +#define stbir__4_coeff_start() \ + stbir__simdf8 tot0,c,cs; stbir__simdf t; \ + STBIR_SIMD_NO_UNROLL(decode); \ + stbir__simdf8_load4b( cs, hc ); \ + stbir__simdf8_0123to00001111( c, cs ); \ + stbir__simdf8_mult_mem( tot0, c, decode ); \ + stbir__simdf8_0123to22223333( c, cs ); \ + stbir__simdf8_madd_mem( tot0, tot0, c, decode+8 ); + +#define stbir__4_coeff_continue_from_4( ofs ) \ + STBIR_SIMD_NO_UNROLL(decode); \ + stbir__simdf8_load4b( cs, hc + (ofs) ); \ + stbir__simdf8_0123to00001111( c, cs ); \ + stbir__simdf8_madd_mem( tot0, tot0, c, decode+(ofs)*4 ); \ + stbir__simdf8_0123to22223333( c, cs ); \ + stbir__simdf8_madd_mem( tot0, tot0, c, decode+(ofs)*4+8 ); + +#define stbir__1_coeff_remnant( ofs ) \ + STBIR_SIMD_NO_UNROLL(decode); \ + stbir__simdf_load1rep4( t, hc + (ofs) ); \ + stbir__simdf8_madd_mem4( tot0, tot0, t, decode+(ofs)*4 ); + +#define stbir__2_coeff_remnant( ofs ) \ + STBIR_SIMD_NO_UNROLL(decode); \ + stbir__simdf8_load4b( cs, hc + (ofs) - 2 ); \ + stbir__simdf8_0123to22223333( c, cs ); \ + stbir__simdf8_madd_mem( tot0, tot0, c, decode+(ofs)*4 ); + + #define stbir__3_coeff_remnant( ofs ) \ + STBIR_SIMD_NO_UNROLL(decode); \ + stbir__simdf8_load4b( cs, hc + (ofs) ); \ + stbir__simdf8_0123to00001111( c, cs ); \ + stbir__simdf8_madd_mem( tot0, tot0, c, decode+(ofs)*4 ); \ + stbir__simdf8_0123to2222( t, cs ); \ + stbir__simdf8_madd_mem4( tot0, tot0, t, decode+(ofs)*4+8 ); + +#define stbir__store_output() \ + stbir__simdf8_add4halves( t, stbir__if_simdf8_cast_to_simdf4(tot0), tot0 ); \ + stbir__simdf_store( output, t ); \ + horizontal_coefficients += coefficient_width; \ + ++horizontal_contributors; \ + output += 4; + +#else + +#define stbir__4_coeff_start() \ + stbir__simdf tot0,tot1,c,cs; \ + STBIR_SIMD_NO_UNROLL(decode); \ + stbir__simdf_load( cs, hc ); \ + stbir__simdf_0123to0000( c, cs ); \ + stbir__simdf_mult_mem( tot0, c, decode ); \ + stbir__simdf_0123to1111( c, cs ); \ + stbir__simdf_mult_mem( tot1, c, decode+4 ); \ + stbir__simdf_0123to2222( c, cs ); \ + stbir__simdf_madd_mem( tot0, tot0, c, decode+8 ); \ + stbir__simdf_0123to3333( c, cs ); \ + stbir__simdf_madd_mem( tot1, tot1, c, decode+12 ); + +#define stbir__4_coeff_continue_from_4( ofs ) \ + STBIR_SIMD_NO_UNROLL(decode); \ + stbir__simdf_load( cs, hc + (ofs) ); \ + stbir__simdf_0123to0000( c, cs ); \ + stbir__simdf_madd_mem( tot0, tot0, c, decode+(ofs)*4 ); \ + stbir__simdf_0123to1111( c, cs ); \ + stbir__simdf_madd_mem( tot1, tot1, c, decode+(ofs)*4+4 ); \ + stbir__simdf_0123to2222( c, cs ); \ + stbir__simdf_madd_mem( tot0, tot0, c, decode+(ofs)*4+8 ); \ + stbir__simdf_0123to3333( c, cs ); \ + stbir__simdf_madd_mem( tot1, tot1, c, decode+(ofs)*4+12 ); + +#define stbir__1_coeff_remnant( ofs ) \ + STBIR_SIMD_NO_UNROLL(decode); \ + stbir__simdf_load1( c, hc + (ofs) ); \ + stbir__simdf_0123to0000( c, c ); \ + stbir__simdf_madd_mem( tot0, tot0, c, decode+(ofs)*4 ); + +#define stbir__2_coeff_remnant( ofs ) \ + STBIR_SIMD_NO_UNROLL(decode); \ + stbir__simdf_load2( cs, hc + (ofs) ); \ + stbir__simdf_0123to0000( c, cs ); \ + stbir__simdf_madd_mem( tot0, tot0, c, decode+(ofs)*4 ); \ + stbir__simdf_0123to1111( c, cs ); \ + stbir__simdf_madd_mem( tot1, tot1, c, decode+(ofs)*4+4 ); + +#define stbir__3_coeff_remnant( ofs ) \ + STBIR_SIMD_NO_UNROLL(decode); \ + stbir__simdf_load( cs, hc + (ofs) ); \ + stbir__simdf_0123to0000( c, cs ); \ + stbir__simdf_madd_mem( tot0, tot0, c, decode+(ofs)*4 ); \ + stbir__simdf_0123to1111( c, cs ); \ + stbir__simdf_madd_mem( tot1, tot1, c, decode+(ofs)*4+4 ); \ + stbir__simdf_0123to2222( c, cs ); \ + stbir__simdf_madd_mem( tot0, tot0, c, decode+(ofs)*4+8 ); + +#define stbir__store_output() \ + stbir__simdf_add( tot0, tot0, tot1 ); \ + stbir__simdf_store( output, tot0 ); \ + horizontal_coefficients += coefficient_width; \ + ++horizontal_contributors; \ + output += 4; + +#endif + +#else + +#define stbir__1_coeff_only() \ + float p0,p1,p2,p3,c; \ + STBIR_SIMD_NO_UNROLL(decode); \ + c = hc[0]; \ + p0 = decode[0] * c; \ + p1 = decode[1] * c; \ + p2 = decode[2] * c; \ + p3 = decode[3] * c; + +#define stbir__2_coeff_only() \ + float p0,p1,p2,p3,c; \ + STBIR_SIMD_NO_UNROLL(decode); \ + c = hc[0]; \ + p0 = decode[0] * c; \ + p1 = decode[1] * c; \ + p2 = decode[2] * c; \ + p3 = decode[3] * c; \ + c = hc[1]; \ + p0 += decode[4] * c; \ + p1 += decode[5] * c; \ + p2 += decode[6] * c; \ + p3 += decode[7] * c; + +#define stbir__3_coeff_only() \ + float p0,p1,p2,p3,c; \ + STBIR_SIMD_NO_UNROLL(decode); \ + c = hc[0]; \ + p0 = decode[0] * c; \ + p1 = decode[1] * c; \ + p2 = decode[2] * c; \ + p3 = decode[3] * c; \ + c = hc[1]; \ + p0 += decode[4] * c; \ + p1 += decode[5] * c; \ + p2 += decode[6] * c; \ + p3 += decode[7] * c; \ + c = hc[2]; \ + p0 += decode[8] * c; \ + p1 += decode[9] * c; \ + p2 += decode[10] * c; \ + p3 += decode[11] * c; + +#define stbir__store_output_tiny() \ + output[0] = p0; \ + output[1] = p1; \ + output[2] = p2; \ + output[3] = p3; \ + horizontal_coefficients += coefficient_width; \ + ++horizontal_contributors; \ + output += 4; + +#define stbir__4_coeff_start() \ + float x0,x1,x2,x3,y0,y1,y2,y3,c; \ + STBIR_SIMD_NO_UNROLL(decode); \ + c = hc[0]; \ + x0 = decode[0] * c; \ + x1 = decode[1] * c; \ + x2 = decode[2] * c; \ + x3 = decode[3] * c; \ + c = hc[1]; \ + y0 = decode[4] * c; \ + y1 = decode[5] * c; \ + y2 = decode[6] * c; \ + y3 = decode[7] * c; \ + c = hc[2]; \ + x0 += decode[8] * c; \ + x1 += decode[9] * c; \ + x2 += decode[10] * c; \ + x3 += decode[11] * c; \ + c = hc[3]; \ + y0 += decode[12] * c; \ + y1 += decode[13] * c; \ + y2 += decode[14] * c; \ + y3 += decode[15] * c; + +#define stbir__4_coeff_continue_from_4( ofs ) \ + STBIR_SIMD_NO_UNROLL(decode); \ + c = hc[0+(ofs)]; \ + x0 += decode[0+(ofs)*4] * c; \ + x1 += decode[1+(ofs)*4] * c; \ + x2 += decode[2+(ofs)*4] * c; \ + x3 += decode[3+(ofs)*4] * c; \ + c = hc[1+(ofs)]; \ + y0 += decode[4+(ofs)*4] * c; \ + y1 += decode[5+(ofs)*4] * c; \ + y2 += decode[6+(ofs)*4] * c; \ + y3 += decode[7+(ofs)*4] * c; \ + c = hc[2+(ofs)]; \ + x0 += decode[8+(ofs)*4] * c; \ + x1 += decode[9+(ofs)*4] * c; \ + x2 += decode[10+(ofs)*4] * c; \ + x3 += decode[11+(ofs)*4] * c; \ + c = hc[3+(ofs)]; \ + y0 += decode[12+(ofs)*4] * c; \ + y1 += decode[13+(ofs)*4] * c; \ + y2 += decode[14+(ofs)*4] * c; \ + y3 += decode[15+(ofs)*4] * c; + +#define stbir__1_coeff_remnant( ofs ) \ + STBIR_SIMD_NO_UNROLL(decode); \ + c = hc[0+(ofs)]; \ + x0 += decode[0+(ofs)*4] * c; \ + x1 += decode[1+(ofs)*4] * c; \ + x2 += decode[2+(ofs)*4] * c; \ + x3 += decode[3+(ofs)*4] * c; + +#define stbir__2_coeff_remnant( ofs ) \ + STBIR_SIMD_NO_UNROLL(decode); \ + c = hc[0+(ofs)]; \ + x0 += decode[0+(ofs)*4] * c; \ + x1 += decode[1+(ofs)*4] * c; \ + x2 += decode[2+(ofs)*4] * c; \ + x3 += decode[3+(ofs)*4] * c; \ + c = hc[1+(ofs)]; \ + y0 += decode[4+(ofs)*4] * c; \ + y1 += decode[5+(ofs)*4] * c; \ + y2 += decode[6+(ofs)*4] * c; \ + y3 += decode[7+(ofs)*4] * c; + +#define stbir__3_coeff_remnant( ofs ) \ + STBIR_SIMD_NO_UNROLL(decode); \ + c = hc[0+(ofs)]; \ + x0 += decode[0+(ofs)*4] * c; \ + x1 += decode[1+(ofs)*4] * c; \ + x2 += decode[2+(ofs)*4] * c; \ + x3 += decode[3+(ofs)*4] * c; \ + c = hc[1+(ofs)]; \ + y0 += decode[4+(ofs)*4] * c; \ + y1 += decode[5+(ofs)*4] * c; \ + y2 += decode[6+(ofs)*4] * c; \ + y3 += decode[7+(ofs)*4] * c; \ + c = hc[2+(ofs)]; \ + x0 += decode[8+(ofs)*4] * c; \ + x1 += decode[9+(ofs)*4] * c; \ + x2 += decode[10+(ofs)*4] * c; \ + x3 += decode[11+(ofs)*4] * c; + +#define stbir__store_output() \ + output[0] = x0 + y0; \ + output[1] = x1 + y1; \ + output[2] = x2 + y2; \ + output[3] = x3 + y3; \ + horizontal_coefficients += coefficient_width; \ + ++horizontal_contributors; \ + output += 4; + +#endif + +#define STBIR__horizontal_channels 4 +#define STB_IMAGE_RESIZE_DO_HORIZONTALS +#include STBIR__HEADER_FILENAME + + + +//================= +// Do 7 channel horizontal routines + +#ifdef STBIR_SIMD + +#define stbir__1_coeff_only() \ + stbir__simdf tot0,tot1,c; \ + STBIR_SIMD_NO_UNROLL(decode); \ + stbir__simdf_load1( c, hc ); \ + stbir__simdf_0123to0000( c, c ); \ + stbir__simdf_mult_mem( tot0, c, decode ); \ + stbir__simdf_mult_mem( tot1, c, decode+3 ); + +#define stbir__2_coeff_only() \ + stbir__simdf tot0,tot1,c,cs; \ + STBIR_SIMD_NO_UNROLL(decode); \ + stbir__simdf_load2( cs, hc ); \ + stbir__simdf_0123to0000( c, cs ); \ + stbir__simdf_mult_mem( tot0, c, decode ); \ + stbir__simdf_mult_mem( tot1, c, decode+3 ); \ + stbir__simdf_0123to1111( c, cs ); \ + stbir__simdf_madd_mem( tot0, tot0, c, decode+7 ); \ + stbir__simdf_madd_mem( tot1, tot1, c,decode+10 ); + +#define stbir__3_coeff_only() \ + stbir__simdf tot0,tot1,c,cs; \ + STBIR_SIMD_NO_UNROLL(decode); \ + stbir__simdf_load( cs, hc ); \ + stbir__simdf_0123to0000( c, cs ); \ + stbir__simdf_mult_mem( tot0, c, decode ); \ + stbir__simdf_mult_mem( tot1, c, decode+3 ); \ + stbir__simdf_0123to1111( c, cs ); \ + stbir__simdf_madd_mem( tot0, tot0, c, decode+7 ); \ + stbir__simdf_madd_mem( tot1, tot1, c, decode+10 ); \ + stbir__simdf_0123to2222( c, cs ); \ + stbir__simdf_madd_mem( tot0, tot0, c, decode+14 ); \ + stbir__simdf_madd_mem( tot1, tot1, c, decode+17 ); + +#define stbir__store_output_tiny() \ + stbir__simdf_store( output+3, tot1 ); \ + stbir__simdf_store( output, tot0 ); \ + horizontal_coefficients += coefficient_width; \ + ++horizontal_contributors; \ + output += 7; + +#ifdef STBIR_SIMD8 + +#define stbir__4_coeff_start() \ + stbir__simdf8 tot0,tot1,c,cs; \ + STBIR_SIMD_NO_UNROLL(decode); \ + stbir__simdf8_load4b( cs, hc ); \ + stbir__simdf8_0123to00000000( c, cs ); \ + stbir__simdf8_mult_mem( tot0, c, decode ); \ + stbir__simdf8_0123to11111111( c, cs ); \ + stbir__simdf8_mult_mem( tot1, c, decode+7 ); \ + stbir__simdf8_0123to22222222( c, cs ); \ + stbir__simdf8_madd_mem( tot0, tot0, c, decode+14 ); \ + stbir__simdf8_0123to33333333( c, cs ); \ + stbir__simdf8_madd_mem( tot1, tot1, c, decode+21 ); + +#define stbir__4_coeff_continue_from_4( ofs ) \ + STBIR_SIMD_NO_UNROLL(decode); \ + stbir__simdf8_load4b( cs, hc + (ofs) ); \ + stbir__simdf8_0123to00000000( c, cs ); \ + stbir__simdf8_madd_mem( tot0, tot0, c, decode+(ofs)*7 ); \ + stbir__simdf8_0123to11111111( c, cs ); \ + stbir__simdf8_madd_mem( tot1, tot1, c, decode+(ofs)*7+7 ); \ + stbir__simdf8_0123to22222222( c, cs ); \ + stbir__simdf8_madd_mem( tot0, tot0, c, decode+(ofs)*7+14 ); \ + stbir__simdf8_0123to33333333( c, cs ); \ + stbir__simdf8_madd_mem( tot1, tot1, c, decode+(ofs)*7+21 ); + +#define stbir__1_coeff_remnant( ofs ) \ + STBIR_SIMD_NO_UNROLL(decode); \ + stbir__simdf8_load1b( c, hc + (ofs) ); \ + stbir__simdf8_madd_mem( tot0, tot0, c, decode+(ofs)*7 ); + +#define stbir__2_coeff_remnant( ofs ) \ + STBIR_SIMD_NO_UNROLL(decode); \ + stbir__simdf8_load1b( c, hc + (ofs) ); \ + stbir__simdf8_madd_mem( tot0, tot0, c, decode+(ofs)*7 ); \ + stbir__simdf8_load1b( c, hc + (ofs)+1 ); \ + stbir__simdf8_madd_mem( tot1, tot1, c, decode+(ofs)*7+7 ); + +#define stbir__3_coeff_remnant( ofs ) \ + STBIR_SIMD_NO_UNROLL(decode); \ + stbir__simdf8_load4b( cs, hc + (ofs) ); \ + stbir__simdf8_0123to00000000( c, cs ); \ + stbir__simdf8_madd_mem( tot0, tot0, c, decode+(ofs)*7 ); \ + stbir__simdf8_0123to11111111( c, cs ); \ + stbir__simdf8_madd_mem( tot1, tot1, c, decode+(ofs)*7+7 ); \ + stbir__simdf8_0123to22222222( c, cs ); \ + stbir__simdf8_madd_mem( tot0, tot0, c, decode+(ofs)*7+14 ); + +#define stbir__store_output() \ + stbir__simdf8_add( tot0, tot0, tot1 ); \ + horizontal_coefficients += coefficient_width; \ + ++horizontal_contributors; \ + output += 7; \ + if ( output < output_end ) \ + { \ + stbir__simdf8_store( output-7, tot0 ); \ + continue; \ + } \ + stbir__simdf_store( output-7+3, stbir__simdf_swiz(stbir__simdf8_gettop4(tot0),0,0,1,2) ); \ + stbir__simdf_store( output-7, stbir__if_simdf8_cast_to_simdf4(tot0) ); \ + break; + +#else + +#define stbir__4_coeff_start() \ + stbir__simdf tot0,tot1,tot2,tot3,c,cs; \ + STBIR_SIMD_NO_UNROLL(decode); \ + stbir__simdf_load( cs, hc ); \ + stbir__simdf_0123to0000( c, cs ); \ + stbir__simdf_mult_mem( tot0, c, decode ); \ + stbir__simdf_mult_mem( tot1, c, decode+3 ); \ + stbir__simdf_0123to1111( c, cs ); \ + stbir__simdf_mult_mem( tot2, c, decode+7 ); \ + stbir__simdf_mult_mem( tot3, c, decode+10 ); \ + stbir__simdf_0123to2222( c, cs ); \ + stbir__simdf_madd_mem( tot0, tot0, c, decode+14 ); \ + stbir__simdf_madd_mem( tot1, tot1, c, decode+17 ); \ + stbir__simdf_0123to3333( c, cs ); \ + stbir__simdf_madd_mem( tot2, tot2, c, decode+21 ); \ + stbir__simdf_madd_mem( tot3, tot3, c, decode+24 ); + +#define stbir__4_coeff_continue_from_4( ofs ) \ + STBIR_SIMD_NO_UNROLL(decode); \ + stbir__simdf_load( cs, hc + (ofs) ); \ + stbir__simdf_0123to0000( c, cs ); \ + stbir__simdf_madd_mem( tot0, tot0, c, decode+(ofs)*7 ); \ + stbir__simdf_madd_mem( tot1, tot1, c, decode+(ofs)*7+3 ); \ + stbir__simdf_0123to1111( c, cs ); \ + stbir__simdf_madd_mem( tot2, tot2, c, decode+(ofs)*7+7 ); \ + stbir__simdf_madd_mem( tot3, tot3, c, decode+(ofs)*7+10 ); \ + stbir__simdf_0123to2222( c, cs ); \ + stbir__simdf_madd_mem( tot0, tot0, c, decode+(ofs)*7+14 ); \ + stbir__simdf_madd_mem( tot1, tot1, c, decode+(ofs)*7+17 ); \ + stbir__simdf_0123to3333( c, cs ); \ + stbir__simdf_madd_mem( tot2, tot2, c, decode+(ofs)*7+21 ); \ + stbir__simdf_madd_mem( tot3, tot3, c, decode+(ofs)*7+24 ); + +#define stbir__1_coeff_remnant( ofs ) \ + STBIR_SIMD_NO_UNROLL(decode); \ + stbir__simdf_load1( c, hc + (ofs) ); \ + stbir__simdf_0123to0000( c, c ); \ + stbir__simdf_madd_mem( tot0, tot0, c, decode+(ofs)*7 ); \ + stbir__simdf_madd_mem( tot1, tot1, c, decode+(ofs)*7+3 ); \ + +#define stbir__2_coeff_remnant( ofs ) \ + STBIR_SIMD_NO_UNROLL(decode); \ + stbir__simdf_load2( cs, hc + (ofs) ); \ + stbir__simdf_0123to0000( c, cs ); \ + stbir__simdf_madd_mem( tot0, tot0, c, decode+(ofs)*7 ); \ + stbir__simdf_madd_mem( tot1, tot1, c, decode+(ofs)*7+3 ); \ + stbir__simdf_0123to1111( c, cs ); \ + stbir__simdf_madd_mem( tot2, tot2, c, decode+(ofs)*7+7 ); \ + stbir__simdf_madd_mem( tot3, tot3, c, decode+(ofs)*7+10 ); + +#define stbir__3_coeff_remnant( ofs ) \ + STBIR_SIMD_NO_UNROLL(decode); \ + stbir__simdf_load( cs, hc + (ofs) ); \ + stbir__simdf_0123to0000( c, cs ); \ + stbir__simdf_madd_mem( tot0, tot0, c, decode+(ofs)*7 ); \ + stbir__simdf_madd_mem( tot1, tot1, c, decode+(ofs)*7+3 ); \ + stbir__simdf_0123to1111( c, cs ); \ + stbir__simdf_madd_mem( tot2, tot2, c, decode+(ofs)*7+7 ); \ + stbir__simdf_madd_mem( tot3, tot3, c, decode+(ofs)*7+10 ); \ + stbir__simdf_0123to2222( c, cs ); \ + stbir__simdf_madd_mem( tot0, tot0, c, decode+(ofs)*7+14 ); \ + stbir__simdf_madd_mem( tot1, tot1, c, decode+(ofs)*7+17 ); + +#define stbir__store_output() \ + stbir__simdf_add( tot0, tot0, tot2 ); \ + stbir__simdf_add( tot1, tot1, tot3 ); \ + stbir__simdf_store( output+3, tot1 ); \ + stbir__simdf_store( output, tot0 ); \ + horizontal_coefficients += coefficient_width; \ + ++horizontal_contributors; \ + output += 7; + +#endif + +#else + +#define stbir__1_coeff_only() \ + float tot0, tot1, tot2, tot3, tot4, tot5, tot6, c; \ + c = hc[0]; \ + tot0 = decode[0]*c; \ + tot1 = decode[1]*c; \ + tot2 = decode[2]*c; \ + tot3 = decode[3]*c; \ + tot4 = decode[4]*c; \ + tot5 = decode[5]*c; \ + tot6 = decode[6]*c; + +#define stbir__2_coeff_only() \ + float tot0, tot1, tot2, tot3, tot4, tot5, tot6, c; \ + c = hc[0]; \ + tot0 = decode[0]*c; \ + tot1 = decode[1]*c; \ + tot2 = decode[2]*c; \ + tot3 = decode[3]*c; \ + tot4 = decode[4]*c; \ + tot5 = decode[5]*c; \ + tot6 = decode[6]*c; \ + c = hc[1]; \ + tot0 += decode[7]*c; \ + tot1 += decode[8]*c; \ + tot2 += decode[9]*c; \ + tot3 += decode[10]*c; \ + tot4 += decode[11]*c; \ + tot5 += decode[12]*c; \ + tot6 += decode[13]*c; \ + +#define stbir__3_coeff_only() \ + float tot0, tot1, tot2, tot3, tot4, tot5, tot6, c; \ + c = hc[0]; \ + tot0 = decode[0]*c; \ + tot1 = decode[1]*c; \ + tot2 = decode[2]*c; \ + tot3 = decode[3]*c; \ + tot4 = decode[4]*c; \ + tot5 = decode[5]*c; \ + tot6 = decode[6]*c; \ + c = hc[1]; \ + tot0 += decode[7]*c; \ + tot1 += decode[8]*c; \ + tot2 += decode[9]*c; \ + tot3 += decode[10]*c; \ + tot4 += decode[11]*c; \ + tot5 += decode[12]*c; \ + tot6 += decode[13]*c; \ + c = hc[2]; \ + tot0 += decode[14]*c; \ + tot1 += decode[15]*c; \ + tot2 += decode[16]*c; \ + tot3 += decode[17]*c; \ + tot4 += decode[18]*c; \ + tot5 += decode[19]*c; \ + tot6 += decode[20]*c; \ + +#define stbir__store_output_tiny() \ + output[0] = tot0; \ + output[1] = tot1; \ + output[2] = tot2; \ + output[3] = tot3; \ + output[4] = tot4; \ + output[5] = tot5; \ + output[6] = tot6; \ + horizontal_coefficients += coefficient_width; \ + ++horizontal_contributors; \ + output += 7; + +#define stbir__4_coeff_start() \ + float x0,x1,x2,x3,x4,x5,x6,y0,y1,y2,y3,y4,y5,y6,c; \ + STBIR_SIMD_NO_UNROLL(decode); \ + c = hc[0]; \ + x0 = decode[0] * c; \ + x1 = decode[1] * c; \ + x2 = decode[2] * c; \ + x3 = decode[3] * c; \ + x4 = decode[4] * c; \ + x5 = decode[5] * c; \ + x6 = decode[6] * c; \ + c = hc[1]; \ + y0 = decode[7] * c; \ + y1 = decode[8] * c; \ + y2 = decode[9] * c; \ + y3 = decode[10] * c; \ + y4 = decode[11] * c; \ + y5 = decode[12] * c; \ + y6 = decode[13] * c; \ + c = hc[2]; \ + x0 += decode[14] * c; \ + x1 += decode[15] * c; \ + x2 += decode[16] * c; \ + x3 += decode[17] * c; \ + x4 += decode[18] * c; \ + x5 += decode[19] * c; \ + x6 += decode[20] * c; \ + c = hc[3]; \ + y0 += decode[21] * c; \ + y1 += decode[22] * c; \ + y2 += decode[23] * c; \ + y3 += decode[24] * c; \ + y4 += decode[25] * c; \ + y5 += decode[26] * c; \ + y6 += decode[27] * c; + +#define stbir__4_coeff_continue_from_4( ofs ) \ + STBIR_SIMD_NO_UNROLL(decode); \ + c = hc[0+(ofs)]; \ + x0 += decode[0+(ofs)*7] * c; \ + x1 += decode[1+(ofs)*7] * c; \ + x2 += decode[2+(ofs)*7] * c; \ + x3 += decode[3+(ofs)*7] * c; \ + x4 += decode[4+(ofs)*7] * c; \ + x5 += decode[5+(ofs)*7] * c; \ + x6 += decode[6+(ofs)*7] * c; \ + c = hc[1+(ofs)]; \ + y0 += decode[7+(ofs)*7] * c; \ + y1 += decode[8+(ofs)*7] * c; \ + y2 += decode[9+(ofs)*7] * c; \ + y3 += decode[10+(ofs)*7] * c; \ + y4 += decode[11+(ofs)*7] * c; \ + y5 += decode[12+(ofs)*7] * c; \ + y6 += decode[13+(ofs)*7] * c; \ + c = hc[2+(ofs)]; \ + x0 += decode[14+(ofs)*7] * c; \ + x1 += decode[15+(ofs)*7] * c; \ + x2 += decode[16+(ofs)*7] * c; \ + x3 += decode[17+(ofs)*7] * c; \ + x4 += decode[18+(ofs)*7] * c; \ + x5 += decode[19+(ofs)*7] * c; \ + x6 += decode[20+(ofs)*7] * c; \ + c = hc[3+(ofs)]; \ + y0 += decode[21+(ofs)*7] * c; \ + y1 += decode[22+(ofs)*7] * c; \ + y2 += decode[23+(ofs)*7] * c; \ + y3 += decode[24+(ofs)*7] * c; \ + y4 += decode[25+(ofs)*7] * c; \ + y5 += decode[26+(ofs)*7] * c; \ + y6 += decode[27+(ofs)*7] * c; + +#define stbir__1_coeff_remnant( ofs ) \ + STBIR_SIMD_NO_UNROLL(decode); \ + c = hc[0+(ofs)]; \ + x0 += decode[0+(ofs)*7] * c; \ + x1 += decode[1+(ofs)*7] * c; \ + x2 += decode[2+(ofs)*7] * c; \ + x3 += decode[3+(ofs)*7] * c; \ + x4 += decode[4+(ofs)*7] * c; \ + x5 += decode[5+(ofs)*7] * c; \ + x6 += decode[6+(ofs)*7] * c; \ + +#define stbir__2_coeff_remnant( ofs ) \ + STBIR_SIMD_NO_UNROLL(decode); \ + c = hc[0+(ofs)]; \ + x0 += decode[0+(ofs)*7] * c; \ + x1 += decode[1+(ofs)*7] * c; \ + x2 += decode[2+(ofs)*7] * c; \ + x3 += decode[3+(ofs)*7] * c; \ + x4 += decode[4+(ofs)*7] * c; \ + x5 += decode[5+(ofs)*7] * c; \ + x6 += decode[6+(ofs)*7] * c; \ + c = hc[1+(ofs)]; \ + y0 += decode[7+(ofs)*7] * c; \ + y1 += decode[8+(ofs)*7] * c; \ + y2 += decode[9+(ofs)*7] * c; \ + y3 += decode[10+(ofs)*7] * c; \ + y4 += decode[11+(ofs)*7] * c; \ + y5 += decode[12+(ofs)*7] * c; \ + y6 += decode[13+(ofs)*7] * c; \ + +#define stbir__3_coeff_remnant( ofs ) \ + STBIR_SIMD_NO_UNROLL(decode); \ + c = hc[0+(ofs)]; \ + x0 += decode[0+(ofs)*7] * c; \ + x1 += decode[1+(ofs)*7] * c; \ + x2 += decode[2+(ofs)*7] * c; \ + x3 += decode[3+(ofs)*7] * c; \ + x4 += decode[4+(ofs)*7] * c; \ + x5 += decode[5+(ofs)*7] * c; \ + x6 += decode[6+(ofs)*7] * c; \ + c = hc[1+(ofs)]; \ + y0 += decode[7+(ofs)*7] * c; \ + y1 += decode[8+(ofs)*7] * c; \ + y2 += decode[9+(ofs)*7] * c; \ + y3 += decode[10+(ofs)*7] * c; \ + y4 += decode[11+(ofs)*7] * c; \ + y5 += decode[12+(ofs)*7] * c; \ + y6 += decode[13+(ofs)*7] * c; \ + c = hc[2+(ofs)]; \ + x0 += decode[14+(ofs)*7] * c; \ + x1 += decode[15+(ofs)*7] * c; \ + x2 += decode[16+(ofs)*7] * c; \ + x3 += decode[17+(ofs)*7] * c; \ + x4 += decode[18+(ofs)*7] * c; \ + x5 += decode[19+(ofs)*7] * c; \ + x6 += decode[20+(ofs)*7] * c; \ + +#define stbir__store_output() \ + output[0] = x0 + y0; \ + output[1] = x1 + y1; \ + output[2] = x2 + y2; \ + output[3] = x3 + y3; \ + output[4] = x4 + y4; \ + output[5] = x5 + y5; \ + output[6] = x6 + y6; \ + horizontal_coefficients += coefficient_width; \ + ++horizontal_contributors; \ + output += 7; + +#endif + +#define STBIR__horizontal_channels 7 +#define STB_IMAGE_RESIZE_DO_HORIZONTALS +#include STBIR__HEADER_FILENAME + + +// include all of the vertical resamplers (both scatter and gather versions) + +#define STBIR__vertical_channels 1 +#define STB_IMAGE_RESIZE_DO_VERTICALS +#include STBIR__HEADER_FILENAME + +#define STBIR__vertical_channels 1 +#define STB_IMAGE_RESIZE_DO_VERTICALS +#define STB_IMAGE_RESIZE_VERTICAL_CONTINUE +#include STBIR__HEADER_FILENAME + +#define STBIR__vertical_channels 2 +#define STB_IMAGE_RESIZE_DO_VERTICALS +#include STBIR__HEADER_FILENAME + +#define STBIR__vertical_channels 2 +#define STB_IMAGE_RESIZE_DO_VERTICALS +#define STB_IMAGE_RESIZE_VERTICAL_CONTINUE +#include STBIR__HEADER_FILENAME + +#define STBIR__vertical_channels 3 +#define STB_IMAGE_RESIZE_DO_VERTICALS +#include STBIR__HEADER_FILENAME + +#define STBIR__vertical_channels 3 +#define STB_IMAGE_RESIZE_DO_VERTICALS +#define STB_IMAGE_RESIZE_VERTICAL_CONTINUE +#include STBIR__HEADER_FILENAME + +#define STBIR__vertical_channels 4 +#define STB_IMAGE_RESIZE_DO_VERTICALS +#include STBIR__HEADER_FILENAME + +#define STBIR__vertical_channels 4 +#define STB_IMAGE_RESIZE_DO_VERTICALS +#define STB_IMAGE_RESIZE_VERTICAL_CONTINUE +#include STBIR__HEADER_FILENAME + +#define STBIR__vertical_channels 5 +#define STB_IMAGE_RESIZE_DO_VERTICALS +#include STBIR__HEADER_FILENAME + +#define STBIR__vertical_channels 5 +#define STB_IMAGE_RESIZE_DO_VERTICALS +#define STB_IMAGE_RESIZE_VERTICAL_CONTINUE +#include STBIR__HEADER_FILENAME + +#define STBIR__vertical_channels 6 +#define STB_IMAGE_RESIZE_DO_VERTICALS +#include STBIR__HEADER_FILENAME + +#define STBIR__vertical_channels 6 +#define STB_IMAGE_RESIZE_DO_VERTICALS +#define STB_IMAGE_RESIZE_VERTICAL_CONTINUE +#include STBIR__HEADER_FILENAME + +#define STBIR__vertical_channels 7 +#define STB_IMAGE_RESIZE_DO_VERTICALS +#include STBIR__HEADER_FILENAME + +#define STBIR__vertical_channels 7 +#define STB_IMAGE_RESIZE_DO_VERTICALS +#define STB_IMAGE_RESIZE_VERTICAL_CONTINUE +#include STBIR__HEADER_FILENAME + +#define STBIR__vertical_channels 8 +#define STB_IMAGE_RESIZE_DO_VERTICALS +#include STBIR__HEADER_FILENAME + +#define STBIR__vertical_channels 8 +#define STB_IMAGE_RESIZE_DO_VERTICALS +#define STB_IMAGE_RESIZE_VERTICAL_CONTINUE +#include STBIR__HEADER_FILENAME + +typedef void STBIR_VERTICAL_GATHERFUNC( float * output, float const * coeffs, float const ** inputs, float const * input0_end ); + +static STBIR_VERTICAL_GATHERFUNC * stbir__vertical_gathers[ 8 ] = +{ + stbir__vertical_gather_with_1_coeffs,stbir__vertical_gather_with_2_coeffs,stbir__vertical_gather_with_3_coeffs,stbir__vertical_gather_with_4_coeffs,stbir__vertical_gather_with_5_coeffs,stbir__vertical_gather_with_6_coeffs,stbir__vertical_gather_with_7_coeffs,stbir__vertical_gather_with_8_coeffs +}; + +static STBIR_VERTICAL_GATHERFUNC * stbir__vertical_gathers_continues[ 8 ] = +{ + stbir__vertical_gather_with_1_coeffs_cont,stbir__vertical_gather_with_2_coeffs_cont,stbir__vertical_gather_with_3_coeffs_cont,stbir__vertical_gather_with_4_coeffs_cont,stbir__vertical_gather_with_5_coeffs_cont,stbir__vertical_gather_with_6_coeffs_cont,stbir__vertical_gather_with_7_coeffs_cont,stbir__vertical_gather_with_8_coeffs_cont +}; + +typedef void STBIR_VERTICAL_SCATTERFUNC( float ** outputs, float const * coeffs, float const * input, float const * input_end ); + +static STBIR_VERTICAL_SCATTERFUNC * stbir__vertical_scatter_sets[ 8 ] = +{ + stbir__vertical_scatter_with_1_coeffs,stbir__vertical_scatter_with_2_coeffs,stbir__vertical_scatter_with_3_coeffs,stbir__vertical_scatter_with_4_coeffs,stbir__vertical_scatter_with_5_coeffs,stbir__vertical_scatter_with_6_coeffs,stbir__vertical_scatter_with_7_coeffs,stbir__vertical_scatter_with_8_coeffs +}; + +static STBIR_VERTICAL_SCATTERFUNC * stbir__vertical_scatter_blends[ 8 ] = +{ + stbir__vertical_scatter_with_1_coeffs_cont,stbir__vertical_scatter_with_2_coeffs_cont,stbir__vertical_scatter_with_3_coeffs_cont,stbir__vertical_scatter_with_4_coeffs_cont,stbir__vertical_scatter_with_5_coeffs_cont,stbir__vertical_scatter_with_6_coeffs_cont,stbir__vertical_scatter_with_7_coeffs_cont,stbir__vertical_scatter_with_8_coeffs_cont +}; + + +static void stbir__encode_scanline( stbir__info const * stbir_info, void *output_buffer_data, float * encode_buffer, int row STBIR_ONLY_PROFILE_GET_SPLIT_INFO ) +{ + int num_pixels = stbir_info->horizontal.scale_info.output_sub_size; + int channels = stbir_info->channels; + int width_times_channels = num_pixels * channels; + void * output_buffer; + + // un-alpha weight if we need to + if ( stbir_info->alpha_unweight ) + { + STBIR_PROFILE_START( unalpha ); + stbir_info->alpha_unweight( encode_buffer, width_times_channels ); + STBIR_PROFILE_END( unalpha ); + } + + // write directly into output by default + output_buffer = output_buffer_data; + + // if we have an output callback, we first convert the decode buffer in place (and then hand that to the callback) + if ( stbir_info->out_pixels_cb ) + output_buffer = encode_buffer; + + STBIR_PROFILE_START( encode ); + // convert into the output buffer + stbir_info->encode_pixels( output_buffer, width_times_channels, encode_buffer ); + STBIR_PROFILE_END( encode ); + + // if we have an output callback, call it to send the data + if ( stbir_info->out_pixels_cb ) + stbir_info->out_pixels_cb( output_buffer, num_pixels, row, stbir_info->user_data ); +} + + +// Get the ring buffer pointer for an index +static float* stbir__get_ring_buffer_entry(stbir__info const * stbir_info, stbir__per_split_info const * split_info, int index ) +{ + STBIR_ASSERT( index < stbir_info->ring_buffer_num_entries ); + + #ifdef STBIR__SEPARATE_ALLOCATIONS + return split_info->ring_buffers[ index ]; + #else + return (float*) ( ( (char*) split_info->ring_buffer ) + ( index * stbir_info->ring_buffer_length_bytes ) ); + #endif +} + +// Get the specified scan line from the ring buffer +static float* stbir__get_ring_buffer_scanline(stbir__info const * stbir_info, stbir__per_split_info const * split_info, int get_scanline) +{ + int ring_buffer_index = (split_info->ring_buffer_begin_index + (get_scanline - split_info->ring_buffer_first_scanline)) % stbir_info->ring_buffer_num_entries; + return stbir__get_ring_buffer_entry( stbir_info, split_info, ring_buffer_index ); +} + +static void stbir__resample_horizontal_gather(stbir__info const * stbir_info, float* output_buffer, float const * input_buffer STBIR_ONLY_PROFILE_GET_SPLIT_INFO ) +{ + float const * decode_buffer = input_buffer - ( stbir_info->scanline_extents.conservative.n0 * stbir_info->effective_channels ); + + STBIR_PROFILE_START( horizontal ); + if ( ( stbir_info->horizontal.filter_enum == STBIR_FILTER_POINT_SAMPLE ) && ( stbir_info->horizontal.scale_info.scale == 1.0f ) ) + STBIR_MEMCPY( output_buffer, input_buffer, stbir_info->horizontal.scale_info.output_sub_size * sizeof( float ) * stbir_info->effective_channels ); + else + stbir_info->horizontal_gather_channels( output_buffer, stbir_info->horizontal.scale_info.output_sub_size, decode_buffer, stbir_info->horizontal.contributors, stbir_info->horizontal.coefficients, stbir_info->horizontal.coefficient_width ); + STBIR_PROFILE_END( horizontal ); +} + +static void stbir__resample_vertical_gather(stbir__info const * stbir_info, stbir__per_split_info* split_info, int n, int contrib_n0, int contrib_n1, float const * vertical_coefficients ) +{ + float* encode_buffer = split_info->vertical_buffer; + float* decode_buffer = split_info->decode_buffer; + int vertical_first = stbir_info->vertical_first; + int width = (vertical_first) ? ( stbir_info->scanline_extents.conservative.n1-stbir_info->scanline_extents.conservative.n0+1 ) : stbir_info->horizontal.scale_info.output_sub_size; + int width_times_channels = stbir_info->effective_channels * width; + + STBIR_ASSERT( stbir_info->vertical.is_gather ); + + // loop over the contributing scanlines and scale into the buffer + STBIR_PROFILE_START( vertical ); + { + int k = 0, total = contrib_n1 - contrib_n0 + 1; + STBIR_ASSERT( total > 0 ); + do { + float const * inputs[8]; + int i, cnt = total; if ( cnt > 8 ) cnt = 8; + for( i = 0 ; i < cnt ; i++ ) + inputs[ i ] = stbir__get_ring_buffer_scanline(stbir_info, split_info, k+i+contrib_n0 ); + + // call the N scanlines at a time function (up to 8 scanlines of blending at once) + ((k==0)?stbir__vertical_gathers:stbir__vertical_gathers_continues)[cnt-1]( (vertical_first) ? decode_buffer : encode_buffer, vertical_coefficients + k, inputs, inputs[0] + width_times_channels ); + k += cnt; + total -= cnt; + } while ( total ); + } + STBIR_PROFILE_END( vertical ); + + if ( vertical_first ) + { + // Now resample the gathered vertical data in the horizontal axis into the encode buffer + decode_buffer[ width_times_channels ] = 0.0f; // clear two over for horizontals with a remnant of 3 + decode_buffer[ width_times_channels+1 ] = 0.0f; + stbir__resample_horizontal_gather(stbir_info, encode_buffer, decode_buffer STBIR_ONLY_PROFILE_SET_SPLIT_INFO ); + } + + stbir__encode_scanline( stbir_info, ( (char *) stbir_info->output_data ) + ((size_t)n * (size_t)stbir_info->output_stride_bytes), + encode_buffer, n STBIR_ONLY_PROFILE_SET_SPLIT_INFO ); +} + +static void stbir__decode_and_resample_for_vertical_gather_loop(stbir__info const * stbir_info, stbir__per_split_info* split_info, int n) +{ + int ring_buffer_index; + float* ring_buffer; + + // Decode the nth scanline from the source image into the decode buffer. + stbir__decode_scanline( stbir_info, n, split_info->decode_buffer STBIR_ONLY_PROFILE_SET_SPLIT_INFO ); + + // update new end scanline + split_info->ring_buffer_last_scanline = n; + + // get ring buffer + ring_buffer_index = (split_info->ring_buffer_begin_index + (split_info->ring_buffer_last_scanline - split_info->ring_buffer_first_scanline)) % stbir_info->ring_buffer_num_entries; + ring_buffer = stbir__get_ring_buffer_entry(stbir_info, split_info, ring_buffer_index); + + // Now resample it into the ring buffer. + stbir__resample_horizontal_gather( stbir_info, ring_buffer, split_info->decode_buffer STBIR_ONLY_PROFILE_SET_SPLIT_INFO ); + + // Now it's sitting in the ring buffer ready to be used as source for the vertical sampling. +} + +static void stbir__vertical_gather_loop( stbir__info const * stbir_info, stbir__per_split_info* split_info, int split_count ) +{ + int y, start_output_y, end_output_y; + stbir__contributors* vertical_contributors = stbir_info->vertical.contributors; + float const * vertical_coefficients = stbir_info->vertical.coefficients; + + STBIR_ASSERT( stbir_info->vertical.is_gather ); + + start_output_y = split_info->start_output_y; + end_output_y = split_info[split_count-1].end_output_y; + + vertical_contributors += start_output_y; + vertical_coefficients += start_output_y * stbir_info->vertical.coefficient_width; + + // initialize the ring buffer for gathering + split_info->ring_buffer_begin_index = 0; + split_info->ring_buffer_first_scanline = vertical_contributors->n0; + split_info->ring_buffer_last_scanline = split_info->ring_buffer_first_scanline - 1; // means "empty" + + for (y = start_output_y; y < end_output_y; y++) + { + int in_first_scanline, in_last_scanline; + + in_first_scanline = vertical_contributors->n0; + in_last_scanline = vertical_contributors->n1; + + // make sure the indexing hasn't broken + STBIR_ASSERT( in_first_scanline >= split_info->ring_buffer_first_scanline ); + + // Load in new scanlines + while (in_last_scanline > split_info->ring_buffer_last_scanline) + { + STBIR_ASSERT( ( split_info->ring_buffer_last_scanline - split_info->ring_buffer_first_scanline + 1 ) <= stbir_info->ring_buffer_num_entries ); + + // make sure there was room in the ring buffer when we add new scanlines + if ( ( split_info->ring_buffer_last_scanline - split_info->ring_buffer_first_scanline + 1 ) == stbir_info->ring_buffer_num_entries ) + { + split_info->ring_buffer_first_scanline++; + split_info->ring_buffer_begin_index++; + } + + if ( stbir_info->vertical_first ) + { + float * ring_buffer = stbir__get_ring_buffer_scanline( stbir_info, split_info, ++split_info->ring_buffer_last_scanline ); + // Decode the nth scanline from the source image into the decode buffer. + stbir__decode_scanline( stbir_info, split_info->ring_buffer_last_scanline, ring_buffer STBIR_ONLY_PROFILE_SET_SPLIT_INFO ); + } + else + { + stbir__decode_and_resample_for_vertical_gather_loop(stbir_info, split_info, split_info->ring_buffer_last_scanline + 1); + } + } + + // Now all buffers should be ready to write a row of vertical sampling, so do it. + stbir__resample_vertical_gather(stbir_info, split_info, y, in_first_scanline, in_last_scanline, vertical_coefficients ); + + ++vertical_contributors; + vertical_coefficients += stbir_info->vertical.coefficient_width; + } +} + +#define STBIR__FLOAT_EMPTY_MARKER 3.0e+38F +#define STBIR__FLOAT_BUFFER_IS_EMPTY(ptr) ((ptr)[0]==STBIR__FLOAT_EMPTY_MARKER) + +static void stbir__encode_first_scanline_from_scatter(stbir__info const * stbir_info, stbir__per_split_info* split_info) +{ + // evict a scanline out into the output buffer + float* ring_buffer_entry = stbir__get_ring_buffer_entry(stbir_info, split_info, split_info->ring_buffer_begin_index ); + + // dump the scanline out + stbir__encode_scanline( stbir_info, ( (char *)stbir_info->output_data ) + ( (size_t)split_info->ring_buffer_first_scanline * (size_t)stbir_info->output_stride_bytes ), ring_buffer_entry, split_info->ring_buffer_first_scanline STBIR_ONLY_PROFILE_SET_SPLIT_INFO ); + + // mark it as empty + ring_buffer_entry[ 0 ] = STBIR__FLOAT_EMPTY_MARKER; + + // advance the first scanline + split_info->ring_buffer_first_scanline++; + if ( ++split_info->ring_buffer_begin_index == stbir_info->ring_buffer_num_entries ) + split_info->ring_buffer_begin_index = 0; +} + +static void stbir__horizontal_resample_and_encode_first_scanline_from_scatter(stbir__info const * stbir_info, stbir__per_split_info* split_info) +{ + // evict a scanline out into the output buffer + + float* ring_buffer_entry = stbir__get_ring_buffer_entry(stbir_info, split_info, split_info->ring_buffer_begin_index ); + + // Now resample it into the buffer. + stbir__resample_horizontal_gather( stbir_info, split_info->vertical_buffer, ring_buffer_entry STBIR_ONLY_PROFILE_SET_SPLIT_INFO ); + + // dump the scanline out + stbir__encode_scanline( stbir_info, ( (char *)stbir_info->output_data ) + ( (size_t)split_info->ring_buffer_first_scanline * (size_t)stbir_info->output_stride_bytes ), split_info->vertical_buffer, split_info->ring_buffer_first_scanline STBIR_ONLY_PROFILE_SET_SPLIT_INFO ); + + // mark it as empty + ring_buffer_entry[ 0 ] = STBIR__FLOAT_EMPTY_MARKER; + + // advance the first scanline + split_info->ring_buffer_first_scanline++; + if ( ++split_info->ring_buffer_begin_index == stbir_info->ring_buffer_num_entries ) + split_info->ring_buffer_begin_index = 0; +} + +static void stbir__resample_vertical_scatter(stbir__info const * stbir_info, stbir__per_split_info* split_info, int n0, int n1, float const * vertical_coefficients, float const * vertical_buffer, float const * vertical_buffer_end ) +{ + STBIR_ASSERT( !stbir_info->vertical.is_gather ); + + STBIR_PROFILE_START( vertical ); + { + int k = 0, total = n1 - n0 + 1; + STBIR_ASSERT( total > 0 ); + do { + float * outputs[8]; + int i, n = total; if ( n > 8 ) n = 8; + for( i = 0 ; i < n ; i++ ) + { + outputs[ i ] = stbir__get_ring_buffer_scanline(stbir_info, split_info, k+i+n0 ); + if ( ( i ) && ( STBIR__FLOAT_BUFFER_IS_EMPTY( outputs[i] ) != STBIR__FLOAT_BUFFER_IS_EMPTY( outputs[0] ) ) ) // make sure runs are of the same type + { + n = i; + break; + } + } + // call the scatter to N scanlines at a time function (up to 8 scanlines of scattering at once) + ((STBIR__FLOAT_BUFFER_IS_EMPTY( outputs[0] ))?stbir__vertical_scatter_sets:stbir__vertical_scatter_blends)[n-1]( outputs, vertical_coefficients + k, vertical_buffer, vertical_buffer_end ); + k += n; + total -= n; + } while ( total ); + } + + STBIR_PROFILE_END( vertical ); +} + +typedef void stbir__handle_scanline_for_scatter_func(stbir__info const * stbir_info, stbir__per_split_info* split_info); + +static void stbir__vertical_scatter_loop( stbir__info const * stbir_info, stbir__per_split_info* split_info, int split_count ) +{ + int y, start_output_y, end_output_y, start_input_y, end_input_y; + stbir__contributors* vertical_contributors = stbir_info->vertical.contributors; + float const * vertical_coefficients = stbir_info->vertical.coefficients; + stbir__handle_scanline_for_scatter_func * handle_scanline_for_scatter; + void * scanline_scatter_buffer; + void * scanline_scatter_buffer_end; + int on_first_input_y, last_input_y; + int width = (stbir_info->vertical_first) ? ( stbir_info->scanline_extents.conservative.n1-stbir_info->scanline_extents.conservative.n0+1 ) : stbir_info->horizontal.scale_info.output_sub_size; + int width_times_channels = stbir_info->effective_channels * width; + + STBIR_ASSERT( !stbir_info->vertical.is_gather ); + + start_output_y = split_info->start_output_y; + end_output_y = split_info[split_count-1].end_output_y; // may do multiple split counts + + start_input_y = split_info->start_input_y; + end_input_y = split_info[split_count-1].end_input_y; + + // adjust for starting offset start_input_y + y = start_input_y + stbir_info->vertical.filter_pixel_margin; + vertical_contributors += y ; + vertical_coefficients += stbir_info->vertical.coefficient_width * y; + + if ( stbir_info->vertical_first ) + { + handle_scanline_for_scatter = stbir__horizontal_resample_and_encode_first_scanline_from_scatter; + scanline_scatter_buffer = split_info->decode_buffer; + scanline_scatter_buffer_end = ( (char*) scanline_scatter_buffer ) + sizeof( float ) * stbir_info->effective_channels * (stbir_info->scanline_extents.conservative.n1-stbir_info->scanline_extents.conservative.n0+1); + } + else + { + handle_scanline_for_scatter = stbir__encode_first_scanline_from_scatter; + scanline_scatter_buffer = split_info->vertical_buffer; + scanline_scatter_buffer_end = ( (char*) scanline_scatter_buffer ) + sizeof( float ) * stbir_info->effective_channels * stbir_info->horizontal.scale_info.output_sub_size; + } + + // initialize the ring buffer for scattering + split_info->ring_buffer_first_scanline = start_output_y; + split_info->ring_buffer_last_scanline = -1; + split_info->ring_buffer_begin_index = -1; + + // mark all the buffers as empty to start + for( y = 0 ; y < stbir_info->ring_buffer_num_entries ; y++ ) + { + float * decode_buffer = stbir__get_ring_buffer_entry( stbir_info, split_info, y ); + decode_buffer[ width_times_channels ] = 0.0f; // clear two over for horizontals with a remnant of 3 + decode_buffer[ width_times_channels+1 ] = 0.0f; + decode_buffer[0] = STBIR__FLOAT_EMPTY_MARKER; // only used on scatter + } + + // do the loop in input space + on_first_input_y = 1; last_input_y = start_input_y; + for (y = start_input_y ; y < end_input_y; y++) + { + int out_first_scanline, out_last_scanline; + + out_first_scanline = vertical_contributors->n0; + out_last_scanline = vertical_contributors->n1; + + STBIR_ASSERT(out_last_scanline - out_first_scanline + 1 <= stbir_info->ring_buffer_num_entries); + + if ( ( out_last_scanline >= out_first_scanline ) && ( ( ( out_first_scanline >= start_output_y ) && ( out_first_scanline < end_output_y ) ) || ( ( out_last_scanline >= start_output_y ) && ( out_last_scanline < end_output_y ) ) ) ) + { + float const * vc = vertical_coefficients; + + // keep track of the range actually seen for the next resize + last_input_y = y; + if ( ( on_first_input_y ) && ( y > start_input_y ) ) + split_info->start_input_y = y; + on_first_input_y = 0; + + // clip the region + if ( out_first_scanline < start_output_y ) + { + vc += start_output_y - out_first_scanline; + out_first_scanline = start_output_y; + } + + if ( out_last_scanline >= end_output_y ) + out_last_scanline = end_output_y - 1; + + // if very first scanline, init the index + if (split_info->ring_buffer_begin_index < 0) + split_info->ring_buffer_begin_index = out_first_scanline - start_output_y; + + STBIR_ASSERT( split_info->ring_buffer_begin_index <= out_first_scanline ); + + // Decode the nth scanline from the source image into the decode buffer. + stbir__decode_scanline( stbir_info, y, split_info->decode_buffer STBIR_ONLY_PROFILE_SET_SPLIT_INFO ); + + // When horizontal first, we resample horizontally into the vertical buffer before we scatter it out + if ( !stbir_info->vertical_first ) + stbir__resample_horizontal_gather( stbir_info, split_info->vertical_buffer, split_info->decode_buffer STBIR_ONLY_PROFILE_SET_SPLIT_INFO ); + + // Now it's sitting in the buffer ready to be distributed into the ring buffers. + + // evict from the ringbuffer, if we need are full + if ( ( ( split_info->ring_buffer_last_scanline - split_info->ring_buffer_first_scanline + 1 ) == stbir_info->ring_buffer_num_entries ) && + ( out_last_scanline > split_info->ring_buffer_last_scanline ) ) + handle_scanline_for_scatter( stbir_info, split_info ); + + // Now the horizontal buffer is ready to write to all ring buffer rows, so do it. + stbir__resample_vertical_scatter(stbir_info, split_info, out_first_scanline, out_last_scanline, vc, (float*)scanline_scatter_buffer, (float*)scanline_scatter_buffer_end ); + + // update the end of the buffer + if ( out_last_scanline > split_info->ring_buffer_last_scanline ) + split_info->ring_buffer_last_scanline = out_last_scanline; + } + ++vertical_contributors; + vertical_coefficients += stbir_info->vertical.coefficient_width; + } + + // now evict the scanlines that are left over in the ring buffer + while ( split_info->ring_buffer_first_scanline < end_output_y ) + handle_scanline_for_scatter(stbir_info, split_info); + + // update the end_input_y if we do multiple resizes with the same data + ++last_input_y; + for( y = 0 ; y < split_count; y++ ) + if ( split_info[y].end_input_y > last_input_y ) + split_info[y].end_input_y = last_input_y; +} + + +static stbir__kernel_callback * stbir__builtin_kernels[] = { 0, stbir__filter_trapezoid, stbir__filter_triangle, stbir__filter_cubic, stbir__filter_catmullrom, stbir__filter_mitchell, stbir__filter_point }; +static stbir__support_callback * stbir__builtin_supports[] = { 0, stbir__support_trapezoid, stbir__support_one, stbir__support_two, stbir__support_two, stbir__support_two, stbir__support_zeropoint5 }; + +static void stbir__set_sampler(stbir__sampler * samp, stbir_filter filter, stbir__kernel_callback * kernel, stbir__support_callback * support, stbir_edge edge, stbir__scale_info * scale_info, int always_gather, void * user_data ) +{ + // set filter + if (filter == 0) + { + filter = STBIR_DEFAULT_FILTER_DOWNSAMPLE; // default to downsample + if (scale_info->scale >= ( 1.0f - stbir__small_float ) ) + { + if ( (scale_info->scale <= ( 1.0f + stbir__small_float ) ) && ( STBIR_CEILF(scale_info->pixel_shift) == scale_info->pixel_shift ) ) + filter = STBIR_FILTER_POINT_SAMPLE; + else + filter = STBIR_DEFAULT_FILTER_UPSAMPLE; + } + } + samp->filter_enum = filter; + + STBIR_ASSERT(samp->filter_enum != 0); + STBIR_ASSERT((unsigned)samp->filter_enum < STBIR_FILTER_OTHER); + samp->filter_kernel = stbir__builtin_kernels[ filter ]; + samp->filter_support = stbir__builtin_supports[ filter ]; + + if ( kernel && support ) + { + samp->filter_kernel = kernel; + samp->filter_support = support; + samp->filter_enum = STBIR_FILTER_OTHER; + } + + samp->edge = edge; + samp->filter_pixel_width = stbir__get_filter_pixel_width (samp->filter_support, scale_info->scale, user_data ); + // Gather is always better, but in extreme downsamples, you have to most or all of the data in memory + // For horizontal, we always have all the pixels, so we always use gather here (always_gather==1). + // For vertical, we use gather if scaling up (which means we will have samp->filter_pixel_width + // scanlines in memory at once). + samp->is_gather = 0; + if ( scale_info->scale >= ( 1.0f - stbir__small_float ) ) + samp->is_gather = 1; + else if ( ( always_gather ) || ( samp->filter_pixel_width <= STBIR_FORCE_GATHER_FILTER_SCANLINES_AMOUNT ) ) + samp->is_gather = 2; + + // pre calculate stuff based on the above + samp->coefficient_width = stbir__get_coefficient_width(samp, samp->is_gather, user_data); + + // filter_pixel_width is the conservative size in pixels of input that affect an output pixel. + // In rare cases (only with 2 pix to 1 pix with the default filters), it's possible that the + // filter will extend before or after the scanline beyond just one extra entire copy of the + // scanline (we would hit the edge twice). We don't let you do that, so we clamp the total + // width to 3x the total of input pixel (once for the scanline, once for the left side + // overhang, and once for the right side). We only do this for edge mode, since the other + // modes can just re-edge clamp back in again. + if ( edge == STBIR_EDGE_WRAP ) + if ( samp->filter_pixel_width > ( scale_info->input_full_size * 3 ) ) + samp->filter_pixel_width = scale_info->input_full_size * 3; + + // This is how much to expand buffers to account for filters seeking outside + // the image boundaries. + samp->filter_pixel_margin = samp->filter_pixel_width / 2; + + // filter_pixel_margin is the amount that this filter can overhang on just one side of either + // end of the scanline (left or the right). Since we only allow you to overhang 1 scanline's + // worth of pixels, we clamp this one side of overhang to the input scanline size. Again, + // this clamping only happens in rare cases with the default filters (2 pix to 1 pix). + if ( edge == STBIR_EDGE_WRAP ) + if ( samp->filter_pixel_margin > scale_info->input_full_size ) + samp->filter_pixel_margin = scale_info->input_full_size; + + samp->num_contributors = stbir__get_contributors(samp, samp->is_gather); + + samp->contributors_size = samp->num_contributors * sizeof(stbir__contributors); + samp->coefficients_size = samp->num_contributors * samp->coefficient_width * sizeof(float) + sizeof(float)*STBIR_INPUT_CALLBACK_PADDING; // extra sizeof(float) is padding + + samp->gather_prescatter_contributors = 0; + samp->gather_prescatter_coefficients = 0; + if ( samp->is_gather == 0 ) + { + samp->gather_prescatter_coefficient_width = samp->filter_pixel_width; + samp->gather_prescatter_num_contributors = stbir__get_contributors(samp, 2); + samp->gather_prescatter_contributors_size = samp->gather_prescatter_num_contributors * sizeof(stbir__contributors); + samp->gather_prescatter_coefficients_size = samp->gather_prescatter_num_contributors * samp->gather_prescatter_coefficient_width * sizeof(float); + } +} + +static void stbir__get_conservative_extents( stbir__sampler * samp, stbir__contributors * range, void * user_data ) +{ + float scale = samp->scale_info.scale; + float out_shift = samp->scale_info.pixel_shift; + stbir__support_callback * support = samp->filter_support; + int input_full_size = samp->scale_info.input_full_size; + stbir_edge edge = samp->edge; + float inv_scale = samp->scale_info.inv_scale; + + STBIR_ASSERT( samp->is_gather != 0 ); + + if ( samp->is_gather == 1 ) + { + int in_first_pixel, in_last_pixel; + float out_filter_radius = support(inv_scale, user_data) * scale; + + stbir__calculate_in_pixel_range( &in_first_pixel, &in_last_pixel, 0.5, out_filter_radius, inv_scale, out_shift, input_full_size, edge ); + range->n0 = in_first_pixel; + stbir__calculate_in_pixel_range( &in_first_pixel, &in_last_pixel, ( (float)(samp->scale_info.output_sub_size-1) ) + 0.5f, out_filter_radius, inv_scale, out_shift, input_full_size, edge ); + range->n1 = in_last_pixel; + } + else if ( samp->is_gather == 2 ) // downsample gather, refine + { + float in_pixels_radius = support(scale, user_data) * inv_scale; + int filter_pixel_margin = samp->filter_pixel_margin; + int output_sub_size = samp->scale_info.output_sub_size; + int input_end; + int n; + int in_first_pixel, in_last_pixel; + + // get a conservative area of the input range + stbir__calculate_in_pixel_range( &in_first_pixel, &in_last_pixel, 0, 0, inv_scale, out_shift, input_full_size, edge ); + range->n0 = in_first_pixel; + stbir__calculate_in_pixel_range( &in_first_pixel, &in_last_pixel, (float)output_sub_size, 0, inv_scale, out_shift, input_full_size, edge ); + range->n1 = in_last_pixel; + + // now go through the margin to the start of area to find bottom + n = range->n0 + 1; + input_end = -filter_pixel_margin; + while( n >= input_end ) + { + int out_first_pixel, out_last_pixel; + stbir__calculate_out_pixel_range( &out_first_pixel, &out_last_pixel, ((float)n)+0.5f, in_pixels_radius, scale, out_shift, output_sub_size ); + if ( out_first_pixel > out_last_pixel ) + break; + + if ( ( out_first_pixel < output_sub_size ) || ( out_last_pixel >= 0 ) ) + range->n0 = n; + --n; + } + + // now go through the end of the area through the margin to find top + n = range->n1 - 1; + input_end = n + 1 + filter_pixel_margin; + while( n <= input_end ) + { + int out_first_pixel, out_last_pixel; + stbir__calculate_out_pixel_range( &out_first_pixel, &out_last_pixel, ((float)n)+0.5f, in_pixels_radius, scale, out_shift, output_sub_size ); + if ( out_first_pixel > out_last_pixel ) + break; + if ( ( out_first_pixel < output_sub_size ) || ( out_last_pixel >= 0 ) ) + range->n1 = n; + ++n; + } + } + + if ( samp->edge == STBIR_EDGE_WRAP ) + { + // if we are wrapping, and we are very close to the image size (so the edges might merge), just use the scanline up to the edge + if ( ( range->n0 > 0 ) && ( range->n1 >= input_full_size ) ) + { + int marg = range->n1 - input_full_size + 1; + if ( ( marg + STBIR__MERGE_RUNS_PIXEL_THRESHOLD ) >= range->n0 ) + range->n0 = 0; + } + if ( ( range->n0 < 0 ) && ( range->n1 < (input_full_size-1) ) ) + { + int marg = -range->n0; + if ( ( input_full_size - marg - STBIR__MERGE_RUNS_PIXEL_THRESHOLD - 1 ) <= range->n1 ) + range->n1 = input_full_size - 1; + } + } + else + { + // for non-edge-wrap modes, we never read over the edge, so clamp + if ( range->n0 < 0 ) + range->n0 = 0; + if ( range->n1 >= input_full_size ) + range->n1 = input_full_size - 1; + } +} + +static void stbir__get_split_info( stbir__per_split_info* split_info, int splits, int output_height, int vertical_pixel_margin, int input_full_height, int is_gather, stbir__contributors * contribs ) +{ + int i, cur; + int left = output_height; + + cur = 0; + for( i = 0 ; i < splits ; i++ ) + { + int each; + + split_info[i].start_output_y = cur; + each = left / ( splits - i ); + split_info[i].end_output_y = cur + each; + + // ok, when we are gathering, we need to make sure we are starting on a y offset that doesn't have + // a "special" set of coefficients. Basically, with exactly the right filter at exactly the right + // resize at exactly the right phase, some of the coefficents can be zero. When they are zero, we + // don't process them at all. But this leads to a tricky thing with the thread splits, where we + // might have a set of two coeffs like this for example: (4,4) and (3,6). The 4,4 means there was + // just one single coeff because things worked out perfectly (normally, they all have 4 coeffs + // like the range 3,6. The problem is that if we start right on the (4,4) on a brand new thread, + // then when we get to (3,6), we don't have the "3" sample in memory (because we didn't load + // it on the initial (4,4) range because it didn't have a 3 (we only add new samples that are + // larger than our existing samples - it's just how the eviction works). So, our solution here + // is pretty simple, if we start right on a range that has samples that start earlier, then we + // simply bump up our previous thread split range to include it, and then start this threads + // range with the smaller sample. It just moves one scanline from one thread split to another, + // so that we end with the unusual one, instead of start with it. To do this, we check 2-4 + // sample at each thread split start and then occassionally move them. + + if ( ( is_gather ) && ( i ) ) + { + stbir__contributors * small_contribs; + int j, smallest, stop, start_n0; + stbir__contributors * split_contribs = contribs + cur; + + // scan for a max of 3x the filter width or until the next thread split + stop = vertical_pixel_margin * 3; + if ( each < stop ) + stop = each; + + // loops a few times before early out + smallest = 0; + small_contribs = split_contribs; + start_n0 = small_contribs->n0; + for( j = 1 ; j <= stop ; j++ ) + { + ++split_contribs; + if ( split_contribs->n0 > start_n0 ) + break; + if ( split_contribs->n0 < small_contribs->n0 ) + { + small_contribs = split_contribs; + smallest = j; + } + } + + split_info[i-1].end_output_y += smallest; + split_info[i].start_output_y += smallest; + } + + cur += each; + left -= each; + + // scatter range (updated to minimum as you run it) + split_info[i].start_input_y = -vertical_pixel_margin; + split_info[i].end_input_y = input_full_height + vertical_pixel_margin; + } +} + +static void stbir__free_internal_mem( stbir__info *info ) +{ + #define STBIR__FREE_AND_CLEAR( ptr ) { if ( ptr ) { void * p = (ptr); (ptr) = 0; STBIR_FREE( p, info->user_data); } } + + if ( info ) + { + #ifndef STBIR__SEPARATE_ALLOCATIONS + STBIR__FREE_AND_CLEAR( info->alloced_mem ); + #else + int i,j; + + if ( ( info->vertical.gather_prescatter_contributors ) && ( (void*)info->vertical.gather_prescatter_contributors != (void*)info->split_info[0].decode_buffer ) ) + { + STBIR__FREE_AND_CLEAR( info->vertical.gather_prescatter_coefficients ); + STBIR__FREE_AND_CLEAR( info->vertical.gather_prescatter_contributors ); + } + for( i = 0 ; i < info->splits ; i++ ) + { + for( j = 0 ; j < info->alloc_ring_buffer_num_entries ; j++ ) + { + #ifdef STBIR_SIMD8 + if ( info->effective_channels == 3 ) + --info->split_info[i].ring_buffers[j]; // avx in 3 channel mode needs one float at the start of the buffer + #endif + STBIR__FREE_AND_CLEAR( info->split_info[i].ring_buffers[j] ); + } + + #ifdef STBIR_SIMD8 + if ( info->effective_channels == 3 ) + --info->split_info[i].decode_buffer; // avx in 3 channel mode needs one float at the start of the buffer + #endif + STBIR__FREE_AND_CLEAR( info->split_info[i].decode_buffer ); + STBIR__FREE_AND_CLEAR( info->split_info[i].ring_buffers ); + STBIR__FREE_AND_CLEAR( info->split_info[i].vertical_buffer ); + } + STBIR__FREE_AND_CLEAR( info->split_info ); + if ( info->vertical.coefficients != info->horizontal.coefficients ) + { + STBIR__FREE_AND_CLEAR( info->vertical.coefficients ); + STBIR__FREE_AND_CLEAR( info->vertical.contributors ); + } + STBIR__FREE_AND_CLEAR( info->horizontal.coefficients ); + STBIR__FREE_AND_CLEAR( info->horizontal.contributors ); + STBIR__FREE_AND_CLEAR( info->alloced_mem ); + STBIR_FREE( info, info->user_data ); + #endif + } + + #undef STBIR__FREE_AND_CLEAR +} + +static int stbir__get_max_split( int splits, int height ) +{ + int i; + int max = 0; + + for( i = 0 ; i < splits ; i++ ) + { + int each = height / ( splits - i ); + if ( each > max ) + max = each; + height -= each; + } + return max; +} + +static stbir__horizontal_gather_channels_func ** stbir__horizontal_gather_n_coeffs_funcs[8] = +{ + 0, stbir__horizontal_gather_1_channels_with_n_coeffs_funcs, stbir__horizontal_gather_2_channels_with_n_coeffs_funcs, stbir__horizontal_gather_3_channels_with_n_coeffs_funcs, stbir__horizontal_gather_4_channels_with_n_coeffs_funcs, 0,0, stbir__horizontal_gather_7_channels_with_n_coeffs_funcs +}; + +static stbir__horizontal_gather_channels_func ** stbir__horizontal_gather_channels_funcs[8] = +{ + 0, stbir__horizontal_gather_1_channels_funcs, stbir__horizontal_gather_2_channels_funcs, stbir__horizontal_gather_3_channels_funcs, stbir__horizontal_gather_4_channels_funcs, 0,0, stbir__horizontal_gather_7_channels_funcs +}; + +// there are six resize classifications: 0 == vertical scatter, 1 == vertical gather < 1x scale, 2 == vertical gather 1x-2x scale, 4 == vertical gather < 3x scale, 4 == vertical gather > 3x scale, 5 == <=4 pixel height, 6 == <=4 pixel wide column +#define STBIR_RESIZE_CLASSIFICATIONS 8 + +static float stbir__compute_weights[5][STBIR_RESIZE_CLASSIFICATIONS][4]= // 5 = 0=1chan, 1=2chan, 2=3chan, 3=4chan, 4=7chan +{ + { + { 1.00000f, 1.00000f, 0.31250f, 1.00000f }, + { 0.56250f, 0.59375f, 0.00000f, 0.96875f }, + { 1.00000f, 0.06250f, 0.00000f, 1.00000f }, + { 0.00000f, 0.09375f, 1.00000f, 1.00000f }, + { 1.00000f, 1.00000f, 0.31250f, 1.00000f }, + { 0.03125f, 0.12500f, 1.00000f, 1.00000f }, + { 1.00000f, 1.00000f, 0.06250f, 1.00000f }, + { 0.00000f, 1.00000f, 0.00000f, 0.03125f }, + }, { + { 0.00000f, 0.84375f, 0.00000f, 0.03125f }, + { 0.09375f, 0.93750f, 0.00000f, 0.78125f }, + { 0.87500f, 0.21875f, 0.00000f, 0.96875f }, + { 0.09375f, 0.09375f, 1.00000f, 1.00000f }, + { 0.00000f, 0.84375f, 0.00000f, 0.03125f }, + { 0.03125f, 0.12500f, 1.00000f, 1.00000f }, + { 1.00000f, 1.00000f, 0.06250f, 1.00000f }, + { 0.00000f, 1.00000f, 0.00000f, 0.53125f }, + }, { + { 0.00000f, 0.53125f, 0.00000f, 0.03125f }, + { 0.06250f, 0.96875f, 0.00000f, 0.53125f }, + { 0.87500f, 0.18750f, 0.00000f, 0.93750f }, + { 0.00000f, 0.09375f, 1.00000f, 1.00000f }, + { 0.00000f, 0.53125f, 0.00000f, 0.03125f }, + { 0.03125f, 0.12500f, 1.00000f, 1.00000f }, + { 1.00000f, 1.00000f, 0.06250f, 1.00000f }, + { 0.00000f, 1.00000f, 0.00000f, 0.56250f }, + }, { + { 0.00000f, 0.50000f, 0.00000f, 0.71875f }, + { 0.06250f, 0.84375f, 0.00000f, 0.87500f }, + { 1.00000f, 0.50000f, 0.50000f, 0.96875f }, + { 1.00000f, 0.09375f, 0.31250f, 0.50000f }, + { 0.00000f, 0.50000f, 0.00000f, 0.71875f }, + { 1.00000f, 0.03125f, 0.03125f, 0.53125f }, + { 1.00000f, 1.00000f, 0.06250f, 1.00000f }, + { 0.00000f, 1.00000f, 0.03125f, 0.18750f }, + }, { + { 0.00000f, 0.59375f, 0.00000f, 0.96875f }, + { 0.06250f, 0.81250f, 0.06250f, 0.59375f }, + { 0.75000f, 0.43750f, 0.12500f, 0.96875f }, + { 0.87500f, 0.06250f, 0.18750f, 0.43750f }, + { 0.00000f, 0.59375f, 0.00000f, 0.96875f }, + { 0.15625f, 0.12500f, 1.00000f, 1.00000f }, + { 1.00000f, 1.00000f, 0.06250f, 1.00000f }, + { 0.00000f, 1.00000f, 0.03125f, 0.34375f }, + } +}; + +// structure that allow us to query and override info for training the costs +typedef struct STBIR__V_FIRST_INFO +{ + double v_cost, h_cost; + int control_v_first; // 0 = no control, 1 = force hori, 2 = force vert + int v_first; + int v_resize_classification; + int is_gather; +} STBIR__V_FIRST_INFO; + +#ifdef STBIR__V_FIRST_INFO_BUFFER +static STBIR__V_FIRST_INFO STBIR__V_FIRST_INFO_BUFFER = {0}; +#define STBIR__V_FIRST_INFO_POINTER &STBIR__V_FIRST_INFO_BUFFER +#else +#define STBIR__V_FIRST_INFO_POINTER 0 +#endif + +// Figure out whether to scale along the horizontal or vertical first. +// This only *super* important when you are scaling by a massively +// different amount in the vertical vs the horizontal (for example, if +// you are scaling by 2x in the width, and 0.5x in the height, then you +// want to do the vertical scale first, because it's around 3x faster +// in that order. +// +// In more normal circumstances, this makes a 20-40% differences, so +// it's good to get right, but not critical. The normal way that you +// decide which direction goes first is just figuring out which +// direction does more multiplies. But with modern CPUs with their +// fancy caches and SIMD and high IPC abilities, so there's just a lot +// more that goes into it. +// +// My handwavy sort of solution is to have an app that does a whole +// bunch of timing for both vertical and horizontal first modes, +// and then another app that can read lots of these timing files +// and try to search for the best weights to use. Dotimings.c +// is the app that does a bunch of timings, and vf_train.c is the +// app that solves for the best weights (and shows how well it +// does currently). + +static int stbir__should_do_vertical_first( float weights_table[STBIR_RESIZE_CLASSIFICATIONS][4], int horizontal_filter_pixel_width, float horizontal_scale, int horizontal_output_size, int vertical_filter_pixel_width, float vertical_scale, int vertical_output_size, int is_gather, STBIR__V_FIRST_INFO * info ) +{ + double v_cost, h_cost; + float * weights; + int vertical_first; + int v_classification; + + // categorize the resize into buckets + if ( ( vertical_output_size <= 4 ) || ( horizontal_output_size <= 4 ) ) + v_classification = ( vertical_output_size < horizontal_output_size ) ? 6 : 7; + else if ( ( !is_gather ) && ( ( vertical_output_size <= 16 ) || ( horizontal_output_size <= 16 ) ) ) + v_classification = 4; + else if ( vertical_scale <= 1.0f ) + v_classification = ( is_gather ) ? 1 : 0; + else if ( vertical_scale <= 2.0f) + v_classification = 2; + else if ( vertical_scale <= 3.0f) + v_classification = 3; + else + v_classification = 5; // everything bigger than 3x + + // use the right weights + weights = weights_table[ v_classification ]; + + // this is the costs when you don't take into account modern CPUs with high ipc and simd and caches - wish we had a better estimate + h_cost = (float)horizontal_filter_pixel_width * weights[0] + horizontal_scale * (float)vertical_filter_pixel_width * weights[1]; + v_cost = (float)vertical_filter_pixel_width * weights[2] + vertical_scale * (float)horizontal_filter_pixel_width * weights[3]; + + // use computation estimate to decide vertical first or not + vertical_first = ( v_cost <= h_cost ) ? 1 : 0; + + // save these, if requested + if ( info ) + { + info->h_cost = h_cost; + info->v_cost = v_cost; + info->v_resize_classification = v_classification; + info->v_first = vertical_first; + info->is_gather = is_gather; + } + + // and this allows us to override everything for testing (see dotiming.c) + if ( ( info ) && ( info->control_v_first ) ) + vertical_first = ( info->control_v_first == 2 ) ? 1 : 0; + + return vertical_first; +} + +// layout lookups - must match stbir_internal_pixel_layout +static unsigned char stbir__pixel_channels[] = { + 1,2,3,3,4, // 1ch, 2ch, rgb, bgr, 4ch + 4,4,4,4,2,2, // RGBA,BGRA,ARGB,ABGR,RA,AR + 4,4,4,4,2,2, // RGBA_PM,BGRA_PM,ARGB_PM,ABGR_PM,RA_PM,AR_PM +}; + +// the internal pixel layout enums are in a different order, so we can easily do range comparisons of types +// the public pixel layout is ordered in a way that if you cast num_channels (1-4) to the enum, you get something sensible +static stbir_internal_pixel_layout stbir__pixel_layout_convert_public_to_internal[] = { + STBIRI_BGR, STBIRI_1CHANNEL, STBIRI_2CHANNEL, STBIRI_RGB, STBIRI_RGBA, + STBIRI_4CHANNEL, STBIRI_BGRA, STBIRI_ARGB, STBIRI_ABGR, STBIRI_RA, STBIRI_AR, + STBIRI_RGBA_PM, STBIRI_BGRA_PM, STBIRI_ARGB_PM, STBIRI_ABGR_PM, STBIRI_RA_PM, STBIRI_AR_PM, +}; + +static stbir__info * stbir__alloc_internal_mem_and_build_samplers( stbir__sampler * horizontal, stbir__sampler * vertical, stbir__contributors * conservative, stbir_pixel_layout input_pixel_layout_public, stbir_pixel_layout output_pixel_layout_public, int splits, int new_x, int new_y, int fast_alpha, void * user_data STBIR_ONLY_PROFILE_BUILD_GET_INFO ) +{ + static char stbir_channel_count_index[8]={ 9,0,1,2, 3,9,9,4 }; + + stbir__info * info = 0; + void * alloced = 0; + size_t alloced_total = 0; + int vertical_first; + size_t decode_buffer_size, ring_buffer_length_bytes, ring_buffer_size, vertical_buffer_size; + int alloc_ring_buffer_num_entries; + + int alpha_weighting_type = 0; // 0=none, 1=simple, 2=fancy + int conservative_split_output_size = stbir__get_max_split( splits, vertical->scale_info.output_sub_size ); + stbir_internal_pixel_layout input_pixel_layout = stbir__pixel_layout_convert_public_to_internal[ input_pixel_layout_public ]; + stbir_internal_pixel_layout output_pixel_layout = stbir__pixel_layout_convert_public_to_internal[ output_pixel_layout_public ]; + int channels = stbir__pixel_channels[ input_pixel_layout ]; + int effective_channels = channels; + + // first figure out what type of alpha weighting to use (if any) + if ( ( horizontal->filter_enum != STBIR_FILTER_POINT_SAMPLE ) || ( vertical->filter_enum != STBIR_FILTER_POINT_SAMPLE ) ) // no alpha weighting on point sampling + { + if ( ( input_pixel_layout >= STBIRI_RGBA ) && ( input_pixel_layout <= STBIRI_AR ) && ( output_pixel_layout >= STBIRI_RGBA ) && ( output_pixel_layout <= STBIRI_AR ) ) + { + if ( fast_alpha ) + { + alpha_weighting_type = 4; + } + else + { + static int fancy_alpha_effective_cnts[6] = { 7, 7, 7, 7, 3, 3 }; + alpha_weighting_type = 2; + effective_channels = fancy_alpha_effective_cnts[ input_pixel_layout - STBIRI_RGBA ]; + } + } + else if ( ( input_pixel_layout >= STBIRI_RGBA_PM ) && ( input_pixel_layout <= STBIRI_AR_PM ) && ( output_pixel_layout >= STBIRI_RGBA ) && ( output_pixel_layout <= STBIRI_AR ) ) + { + // input premult, output non-premult + alpha_weighting_type = 3; + } + else if ( ( input_pixel_layout >= STBIRI_RGBA ) && ( input_pixel_layout <= STBIRI_AR ) && ( output_pixel_layout >= STBIRI_RGBA_PM ) && ( output_pixel_layout <= STBIRI_AR_PM ) ) + { + // input non-premult, output premult + alpha_weighting_type = 1; + } + } + + // channel in and out count must match currently + if ( channels != stbir__pixel_channels[ output_pixel_layout ] ) + return 0; + + // get vertical first + vertical_first = stbir__should_do_vertical_first( stbir__compute_weights[ (int)stbir_channel_count_index[ effective_channels ] ], horizontal->filter_pixel_width, horizontal->scale_info.scale, horizontal->scale_info.output_sub_size, vertical->filter_pixel_width, vertical->scale_info.scale, vertical->scale_info.output_sub_size, vertical->is_gather, STBIR__V_FIRST_INFO_POINTER ); + + // sometimes read one float off in some of the unrolled loops (with a weight of zero coeff, so it doesn't have an effect) + // we use a few extra floats instead of just 1, so that input callback buffer can overlap with the decode buffer without + // the conversion routines overwriting the callback input data. + decode_buffer_size = ( conservative->n1 - conservative->n0 + 1 ) * effective_channels * sizeof(float) + sizeof(float)*STBIR_INPUT_CALLBACK_PADDING; // extra floats for input callback stagger + +#if defined( STBIR__SEPARATE_ALLOCATIONS ) && defined(STBIR_SIMD8) + if ( effective_channels == 3 ) + decode_buffer_size += sizeof(float); // avx in 3 channel mode needs one float at the start of the buffer (only with separate allocations) +#endif + + ring_buffer_length_bytes = (size_t)horizontal->scale_info.output_sub_size * (size_t)effective_channels * sizeof(float) + sizeof(float)*STBIR_INPUT_CALLBACK_PADDING; // extra floats for padding + + // if we do vertical first, the ring buffer holds a whole decoded line + if ( vertical_first ) + ring_buffer_length_bytes = ( decode_buffer_size + 15 ) & ~15; + + if ( ( ring_buffer_length_bytes & 4095 ) == 0 ) ring_buffer_length_bytes += 64*3; // avoid 4k alias + + // One extra entry because floating point precision problems sometimes cause an extra to be necessary. + alloc_ring_buffer_num_entries = vertical->filter_pixel_width + 1; + + // we never need more ring buffer entries than the scanlines we're outputting when in scatter mode + if ( ( !vertical->is_gather ) && ( alloc_ring_buffer_num_entries > conservative_split_output_size ) ) + alloc_ring_buffer_num_entries = conservative_split_output_size; + + ring_buffer_size = (size_t)alloc_ring_buffer_num_entries * (size_t)ring_buffer_length_bytes; + + // The vertical buffer is used differently, depending on whether we are scattering + // the vertical scanlines, or gathering them. + // If scattering, it's used at the temp buffer to accumulate each output. + // If gathering, it's just the output buffer. + vertical_buffer_size = (size_t)horizontal->scale_info.output_sub_size * (size_t)effective_channels * sizeof(float) + sizeof(float); // extra float for padding + + // we make two passes through this loop, 1st to add everything up, 2nd to allocate and init + for(;;) + { + int i; + void * advance_mem = alloced; + int copy_horizontal = 0; + stbir__sampler * possibly_use_horizontal_for_pivot = 0; + +#ifdef STBIR__SEPARATE_ALLOCATIONS + #define STBIR__NEXT_PTR( ptr, size, ntype ) if ( alloced ) { void * p = STBIR_MALLOC( size, user_data); if ( p == 0 ) { stbir__free_internal_mem( info ); return 0; } (ptr) = (ntype*)p; } +#else + #define STBIR__NEXT_PTR( ptr, size, ntype ) advance_mem = (void*) ( ( ((size_t)advance_mem) + 15 ) & ~15 ); if ( alloced ) ptr = (ntype*)advance_mem; advance_mem = (char*)(((size_t)advance_mem) + (size)); +#endif + + STBIR__NEXT_PTR( info, sizeof( stbir__info ), stbir__info ); + + STBIR__NEXT_PTR( info->split_info, sizeof( stbir__per_split_info ) * splits, stbir__per_split_info ); + + if ( info ) + { + static stbir__alpha_weight_func * fancy_alpha_weights[6] = { stbir__fancy_alpha_weight_4ch, stbir__fancy_alpha_weight_4ch, stbir__fancy_alpha_weight_4ch, stbir__fancy_alpha_weight_4ch, stbir__fancy_alpha_weight_2ch, stbir__fancy_alpha_weight_2ch }; + static stbir__alpha_unweight_func * fancy_alpha_unweights[6] = { stbir__fancy_alpha_unweight_4ch, stbir__fancy_alpha_unweight_4ch, stbir__fancy_alpha_unweight_4ch, stbir__fancy_alpha_unweight_4ch, stbir__fancy_alpha_unweight_2ch, stbir__fancy_alpha_unweight_2ch }; + static stbir__alpha_weight_func * simple_alpha_weights[6] = { stbir__simple_alpha_weight_4ch, stbir__simple_alpha_weight_4ch, stbir__simple_alpha_weight_4ch, stbir__simple_alpha_weight_4ch, stbir__simple_alpha_weight_2ch, stbir__simple_alpha_weight_2ch }; + static stbir__alpha_unweight_func * simple_alpha_unweights[6] = { stbir__simple_alpha_unweight_4ch, stbir__simple_alpha_unweight_4ch, stbir__simple_alpha_unweight_4ch, stbir__simple_alpha_unweight_4ch, stbir__simple_alpha_unweight_2ch, stbir__simple_alpha_unweight_2ch }; + + // initialize info fields + info->alloced_mem = alloced; + info->alloced_total = alloced_total; + + info->channels = channels; + info->effective_channels = effective_channels; + + info->offset_x = new_x; + info->offset_y = new_y; + info->alloc_ring_buffer_num_entries = (int)alloc_ring_buffer_num_entries; + info->ring_buffer_num_entries = 0; + info->ring_buffer_length_bytes = (int)ring_buffer_length_bytes; + info->splits = splits; + info->vertical_first = vertical_first; + + info->input_pixel_layout_internal = input_pixel_layout; + info->output_pixel_layout_internal = output_pixel_layout; + + // setup alpha weight functions + info->alpha_weight = 0; + info->alpha_unweight = 0; + + // handle alpha weighting functions and overrides + if ( alpha_weighting_type == 2 ) + { + // high quality alpha multiplying on the way in, dividing on the way out + info->alpha_weight = fancy_alpha_weights[ input_pixel_layout - STBIRI_RGBA ]; + info->alpha_unweight = fancy_alpha_unweights[ output_pixel_layout - STBIRI_RGBA ]; + } + else if ( alpha_weighting_type == 4 ) + { + // fast alpha multiplying on the way in, dividing on the way out + info->alpha_weight = simple_alpha_weights[ input_pixel_layout - STBIRI_RGBA ]; + info->alpha_unweight = simple_alpha_unweights[ output_pixel_layout - STBIRI_RGBA ]; + } + else if ( alpha_weighting_type == 1 ) + { + // fast alpha on the way in, leave in premultiplied form on way out + info->alpha_weight = simple_alpha_weights[ input_pixel_layout - STBIRI_RGBA ]; + } + else if ( alpha_weighting_type == 3 ) + { + // incoming is premultiplied, fast alpha dividing on the way out - non-premultiplied output + info->alpha_unweight = simple_alpha_unweights[ output_pixel_layout - STBIRI_RGBA ]; + } + + // handle 3-chan color flipping, using the alpha weight path + if ( ( ( input_pixel_layout == STBIRI_RGB ) && ( output_pixel_layout == STBIRI_BGR ) ) || + ( ( input_pixel_layout == STBIRI_BGR ) && ( output_pixel_layout == STBIRI_RGB ) ) ) + { + // do the flipping on the smaller of the two ends + if ( horizontal->scale_info.scale < 1.0f ) + info->alpha_unweight = stbir__simple_flip_3ch; + else + info->alpha_weight = stbir__simple_flip_3ch; + } + + } + + // get all the per-split buffers + for( i = 0 ; i < splits ; i++ ) + { + STBIR__NEXT_PTR( info->split_info[i].decode_buffer, decode_buffer_size, float ); + +#ifdef STBIR__SEPARATE_ALLOCATIONS + + #ifdef STBIR_SIMD8 + if ( ( info ) && ( effective_channels == 3 ) ) + ++info->split_info[i].decode_buffer; // avx in 3 channel mode needs one float at the start of the buffer + #endif + + STBIR__NEXT_PTR( info->split_info[i].ring_buffers, alloc_ring_buffer_num_entries * sizeof(float*), float* ); + { + int j; + for( j = 0 ; j < alloc_ring_buffer_num_entries ; j++ ) + { + STBIR__NEXT_PTR( info->split_info[i].ring_buffers[j], ring_buffer_length_bytes, float ); + #ifdef STBIR_SIMD8 + if ( ( info ) && ( effective_channels == 3 ) ) + ++info->split_info[i].ring_buffers[j]; // avx in 3 channel mode needs one float at the start of the buffer + #endif + } + } +#else + STBIR__NEXT_PTR( info->split_info[i].ring_buffer, ring_buffer_size, float ); +#endif + STBIR__NEXT_PTR( info->split_info[i].vertical_buffer, vertical_buffer_size, float ); + } + + // alloc memory for to-be-pivoted coeffs (if necessary) + if ( vertical->is_gather == 0 ) + { + size_t both; + size_t temp_mem_amt; + + // when in vertical scatter mode, we first build the coefficients in gather mode, and then pivot after, + // that means we need two buffers, so we try to use the decode buffer and ring buffer for this. if that + // is too small, we just allocate extra memory to use as this temp. + + both = (size_t)vertical->gather_prescatter_contributors_size + (size_t)vertical->gather_prescatter_coefficients_size; + +#ifdef STBIR__SEPARATE_ALLOCATIONS + temp_mem_amt = decode_buffer_size; + + #ifdef STBIR_SIMD8 + if ( effective_channels == 3 ) + --temp_mem_amt; // avx in 3 channel mode needs one float at the start of the buffer + #endif +#else + temp_mem_amt = (size_t)( decode_buffer_size + ring_buffer_size + vertical_buffer_size ) * (size_t)splits; +#endif + if ( temp_mem_amt >= both ) + { + if ( info ) + { + vertical->gather_prescatter_contributors = (stbir__contributors*)info->split_info[0].decode_buffer; + vertical->gather_prescatter_coefficients = (float*) ( ( (char*)info->split_info[0].decode_buffer ) + vertical->gather_prescatter_contributors_size ); + } + } + else + { + // ring+decode memory is too small, so allocate temp memory + STBIR__NEXT_PTR( vertical->gather_prescatter_contributors, vertical->gather_prescatter_contributors_size, stbir__contributors ); + STBIR__NEXT_PTR( vertical->gather_prescatter_coefficients, vertical->gather_prescatter_coefficients_size, float ); + } + } + + STBIR__NEXT_PTR( horizontal->contributors, horizontal->contributors_size, stbir__contributors ); + STBIR__NEXT_PTR( horizontal->coefficients, horizontal->coefficients_size, float ); + + // are the two filters identical?? (happens a lot with mipmap generation) + if ( ( horizontal->filter_kernel == vertical->filter_kernel ) && ( horizontal->filter_support == vertical->filter_support ) && ( horizontal->edge == vertical->edge ) && ( horizontal->scale_info.output_sub_size == vertical->scale_info.output_sub_size ) ) + { + float diff_scale = horizontal->scale_info.scale - vertical->scale_info.scale; + float diff_shift = horizontal->scale_info.pixel_shift - vertical->scale_info.pixel_shift; + if ( diff_scale < 0.0f ) diff_scale = -diff_scale; + if ( diff_shift < 0.0f ) diff_shift = -diff_shift; + if ( ( diff_scale <= stbir__small_float ) && ( diff_shift <= stbir__small_float ) ) + { + if ( horizontal->is_gather == vertical->is_gather ) + { + copy_horizontal = 1; + goto no_vert_alloc; + } + // everything matches, but vertical is scatter, horizontal is gather, use horizontal coeffs for vertical pivot coeffs + possibly_use_horizontal_for_pivot = horizontal; + } + } + + STBIR__NEXT_PTR( vertical->contributors, vertical->contributors_size, stbir__contributors ); + STBIR__NEXT_PTR( vertical->coefficients, vertical->coefficients_size, float ); + + no_vert_alloc: + + if ( info ) + { + STBIR_PROFILE_BUILD_START( horizontal ); + + stbir__calculate_filters( horizontal, 0, user_data STBIR_ONLY_PROFILE_BUILD_SET_INFO ); + + // setup the horizontal gather functions + // start with defaulting to the n_coeffs functions (specialized on channels and remnant leftover) + info->horizontal_gather_channels = stbir__horizontal_gather_n_coeffs_funcs[ effective_channels ][ horizontal->extent_info.widest & 3 ]; + // but if the number of coeffs <= 12, use another set of special cases. <=12 coeffs is any enlarging resize, or shrinking resize down to about 1/3 size + if ( horizontal->extent_info.widest <= 12 ) + info->horizontal_gather_channels = stbir__horizontal_gather_channels_funcs[ effective_channels ][ horizontal->extent_info.widest - 1 ]; + + info->scanline_extents.conservative.n0 = conservative->n0; + info->scanline_extents.conservative.n1 = conservative->n1; + + // get exact extents + stbir__get_extents( horizontal, &info->scanline_extents ); + + // pack the horizontal coeffs + horizontal->coefficient_width = stbir__pack_coefficients(horizontal->num_contributors, horizontal->contributors, horizontal->coefficients, horizontal->coefficient_width, horizontal->extent_info.widest, info->scanline_extents.conservative.n0, info->scanline_extents.conservative.n1 ); + + STBIR_MEMCPY( &info->horizontal, horizontal, sizeof( stbir__sampler ) ); + + STBIR_PROFILE_BUILD_END( horizontal ); + + if ( copy_horizontal ) + { + STBIR_MEMCPY( &info->vertical, horizontal, sizeof( stbir__sampler ) ); + } + else + { + STBIR_PROFILE_BUILD_START( vertical ); + + stbir__calculate_filters( vertical, possibly_use_horizontal_for_pivot, user_data STBIR_ONLY_PROFILE_BUILD_SET_INFO ); + STBIR_MEMCPY( &info->vertical, vertical, sizeof( stbir__sampler ) ); + + STBIR_PROFILE_BUILD_END( vertical ); + } + + // setup the vertical split ranges + stbir__get_split_info( info->split_info, info->splits, info->vertical.scale_info.output_sub_size, info->vertical.filter_pixel_margin, info->vertical.scale_info.input_full_size, info->vertical.is_gather, info->vertical.contributors ); + + // now we know precisely how many entries we need + info->ring_buffer_num_entries = info->vertical.extent_info.widest; + + // we never need more ring buffer entries than the scanlines we're outputting + if ( ( !info->vertical.is_gather ) && ( info->ring_buffer_num_entries > conservative_split_output_size ) ) + info->ring_buffer_num_entries = conservative_split_output_size; + STBIR_ASSERT( info->ring_buffer_num_entries <= info->alloc_ring_buffer_num_entries ); + } + #undef STBIR__NEXT_PTR + + + // is this the first time through loop? + if ( info == 0 ) + { + alloced_total = ( 15 + (size_t)advance_mem ); + alloced = STBIR_MALLOC( alloced_total, user_data ); + if ( alloced == 0 ) + return 0; + } + else + return info; // success + } +} + +static int stbir__perform_resize( stbir__info const * info, int split_start, int split_count ) +{ + stbir__per_split_info * split_info = info->split_info + split_start; + + STBIR_PROFILE_CLEAR_EXTRAS(); + + STBIR_PROFILE_FIRST_START( looping ); + if (info->vertical.is_gather) + stbir__vertical_gather_loop( info, split_info, split_count ); + else + stbir__vertical_scatter_loop( info, split_info, split_count ); + STBIR_PROFILE_END( looping ); + + return 1; +} + +static void stbir__update_info_from_resize( stbir__info * info, STBIR_RESIZE * resize ) +{ + static stbir__decode_pixels_func * decode_simple[STBIR_TYPE_HALF_FLOAT-STBIR_TYPE_UINT8_SRGB+1]= + { + /* 1ch-4ch */ stbir__decode_uint8_srgb, stbir__decode_uint8_srgb, 0, stbir__decode_float_linear, stbir__decode_half_float_linear, + }; + + static stbir__decode_pixels_func * decode_alphas[STBIRI_AR-STBIRI_RGBA+1][STBIR_TYPE_HALF_FLOAT-STBIR_TYPE_UINT8_SRGB+1]= + { + { /* RGBA */ stbir__decode_uint8_srgb4_linearalpha, stbir__decode_uint8_srgb, 0, stbir__decode_float_linear, stbir__decode_half_float_linear }, + { /* BGRA */ stbir__decode_uint8_srgb4_linearalpha_BGRA, stbir__decode_uint8_srgb_BGRA, 0, stbir__decode_float_linear_BGRA, stbir__decode_half_float_linear_BGRA }, + { /* ARGB */ stbir__decode_uint8_srgb4_linearalpha_ARGB, stbir__decode_uint8_srgb_ARGB, 0, stbir__decode_float_linear_ARGB, stbir__decode_half_float_linear_ARGB }, + { /* ABGR */ stbir__decode_uint8_srgb4_linearalpha_ABGR, stbir__decode_uint8_srgb_ABGR, 0, stbir__decode_float_linear_ABGR, stbir__decode_half_float_linear_ABGR }, + { /* RA */ stbir__decode_uint8_srgb2_linearalpha, stbir__decode_uint8_srgb, 0, stbir__decode_float_linear, stbir__decode_half_float_linear }, + { /* AR */ stbir__decode_uint8_srgb2_linearalpha_AR, stbir__decode_uint8_srgb_AR, 0, stbir__decode_float_linear_AR, stbir__decode_half_float_linear_AR }, + }; + + static stbir__decode_pixels_func * decode_simple_scaled_or_not[2][2]= + { + { stbir__decode_uint8_linear_scaled, stbir__decode_uint8_linear }, { stbir__decode_uint16_linear_scaled, stbir__decode_uint16_linear }, + }; + + static stbir__decode_pixels_func * decode_alphas_scaled_or_not[STBIRI_AR-STBIRI_RGBA+1][2][2]= + { + { /* RGBA */ { stbir__decode_uint8_linear_scaled, stbir__decode_uint8_linear }, { stbir__decode_uint16_linear_scaled, stbir__decode_uint16_linear } }, + { /* BGRA */ { stbir__decode_uint8_linear_scaled_BGRA, stbir__decode_uint8_linear_BGRA }, { stbir__decode_uint16_linear_scaled_BGRA, stbir__decode_uint16_linear_BGRA } }, + { /* ARGB */ { stbir__decode_uint8_linear_scaled_ARGB, stbir__decode_uint8_linear_ARGB }, { stbir__decode_uint16_linear_scaled_ARGB, stbir__decode_uint16_linear_ARGB } }, + { /* ABGR */ { stbir__decode_uint8_linear_scaled_ABGR, stbir__decode_uint8_linear_ABGR }, { stbir__decode_uint16_linear_scaled_ABGR, stbir__decode_uint16_linear_ABGR } }, + { /* RA */ { stbir__decode_uint8_linear_scaled, stbir__decode_uint8_linear }, { stbir__decode_uint16_linear_scaled, stbir__decode_uint16_linear } }, + { /* AR */ { stbir__decode_uint8_linear_scaled_AR, stbir__decode_uint8_linear_AR }, { stbir__decode_uint16_linear_scaled_AR, stbir__decode_uint16_linear_AR } } + }; + + static stbir__encode_pixels_func * encode_simple[STBIR_TYPE_HALF_FLOAT-STBIR_TYPE_UINT8_SRGB+1]= + { + /* 1ch-4ch */ stbir__encode_uint8_srgb, stbir__encode_uint8_srgb, 0, stbir__encode_float_linear, stbir__encode_half_float_linear, + }; + + static stbir__encode_pixels_func * encode_alphas[STBIRI_AR-STBIRI_RGBA+1][STBIR_TYPE_HALF_FLOAT-STBIR_TYPE_UINT8_SRGB+1]= + { + { /* RGBA */ stbir__encode_uint8_srgb4_linearalpha, stbir__encode_uint8_srgb, 0, stbir__encode_float_linear, stbir__encode_half_float_linear }, + { /* BGRA */ stbir__encode_uint8_srgb4_linearalpha_BGRA, stbir__encode_uint8_srgb_BGRA, 0, stbir__encode_float_linear_BGRA, stbir__encode_half_float_linear_BGRA }, + { /* ARGB */ stbir__encode_uint8_srgb4_linearalpha_ARGB, stbir__encode_uint8_srgb_ARGB, 0, stbir__encode_float_linear_ARGB, stbir__encode_half_float_linear_ARGB }, + { /* ABGR */ stbir__encode_uint8_srgb4_linearalpha_ABGR, stbir__encode_uint8_srgb_ABGR, 0, stbir__encode_float_linear_ABGR, stbir__encode_half_float_linear_ABGR }, + { /* RA */ stbir__encode_uint8_srgb2_linearalpha, stbir__encode_uint8_srgb, 0, stbir__encode_float_linear, stbir__encode_half_float_linear }, + { /* AR */ stbir__encode_uint8_srgb2_linearalpha_AR, stbir__encode_uint8_srgb_AR, 0, stbir__encode_float_linear_AR, stbir__encode_half_float_linear_AR } + }; + + static stbir__encode_pixels_func * encode_simple_scaled_or_not[2][2]= + { + { stbir__encode_uint8_linear_scaled, stbir__encode_uint8_linear }, { stbir__encode_uint16_linear_scaled, stbir__encode_uint16_linear }, + }; + + static stbir__encode_pixels_func * encode_alphas_scaled_or_not[STBIRI_AR-STBIRI_RGBA+1][2][2]= + { + { /* RGBA */ { stbir__encode_uint8_linear_scaled, stbir__encode_uint8_linear }, { stbir__encode_uint16_linear_scaled, stbir__encode_uint16_linear } }, + { /* BGRA */ { stbir__encode_uint8_linear_scaled_BGRA, stbir__encode_uint8_linear_BGRA }, { stbir__encode_uint16_linear_scaled_BGRA, stbir__encode_uint16_linear_BGRA } }, + { /* ARGB */ { stbir__encode_uint8_linear_scaled_ARGB, stbir__encode_uint8_linear_ARGB }, { stbir__encode_uint16_linear_scaled_ARGB, stbir__encode_uint16_linear_ARGB } }, + { /* ABGR */ { stbir__encode_uint8_linear_scaled_ABGR, stbir__encode_uint8_linear_ABGR }, { stbir__encode_uint16_linear_scaled_ABGR, stbir__encode_uint16_linear_ABGR } }, + { /* RA */ { stbir__encode_uint8_linear_scaled, stbir__encode_uint8_linear }, { stbir__encode_uint16_linear_scaled, stbir__encode_uint16_linear } }, + { /* AR */ { stbir__encode_uint8_linear_scaled_AR, stbir__encode_uint8_linear_AR }, { stbir__encode_uint16_linear_scaled_AR, stbir__encode_uint16_linear_AR } } + }; + + stbir__decode_pixels_func * decode_pixels = 0; + stbir__encode_pixels_func * encode_pixels = 0; + stbir_datatype input_type, output_type; + + input_type = resize->input_data_type; + output_type = resize->output_data_type; + info->input_data = resize->input_pixels; + info->input_stride_bytes = resize->input_stride_in_bytes; + info->output_stride_bytes = resize->output_stride_in_bytes; + + // if we're completely point sampling, then we can turn off SRGB + if ( ( info->horizontal.filter_enum == STBIR_FILTER_POINT_SAMPLE ) && ( info->vertical.filter_enum == STBIR_FILTER_POINT_SAMPLE ) ) + { + if ( ( ( input_type == STBIR_TYPE_UINT8_SRGB ) || ( input_type == STBIR_TYPE_UINT8_SRGB_ALPHA ) ) && + ( ( output_type == STBIR_TYPE_UINT8_SRGB ) || ( output_type == STBIR_TYPE_UINT8_SRGB_ALPHA ) ) ) + { + input_type = STBIR_TYPE_UINT8; + output_type = STBIR_TYPE_UINT8; + } + } + + // recalc the output and input strides + if ( info->input_stride_bytes == 0 ) + info->input_stride_bytes = info->channels * info->horizontal.scale_info.input_full_size * stbir__type_size[input_type]; + + if ( info->output_stride_bytes == 0 ) + info->output_stride_bytes = info->channels * info->horizontal.scale_info.output_sub_size * stbir__type_size[output_type]; + + // calc offset + info->output_data = ( (char*) resize->output_pixels ) + ( (size_t) info->offset_y * (size_t) resize->output_stride_in_bytes ) + ( info->offset_x * info->channels * stbir__type_size[output_type] ); + + info->in_pixels_cb = resize->input_cb; + info->user_data = resize->user_data; + info->out_pixels_cb = resize->output_cb; + + // setup the input format converters + if ( ( input_type == STBIR_TYPE_UINT8 ) || ( input_type == STBIR_TYPE_UINT16 ) ) + { + int non_scaled = 0; + + // check if we can run unscaled - 0-255.0/0-65535.0 instead of 0-1.0 (which is a tiny bit faster when doing linear 8->8 or 16->16) + if ( ( !info->alpha_weight ) && ( !info->alpha_unweight ) ) // don't short circuit when alpha weighting (get everything to 0-1.0 as usual) + if ( ( ( input_type == STBIR_TYPE_UINT8 ) && ( output_type == STBIR_TYPE_UINT8 ) ) || ( ( input_type == STBIR_TYPE_UINT16 ) && ( output_type == STBIR_TYPE_UINT16 ) ) ) + non_scaled = 1; + + if ( info->input_pixel_layout_internal <= STBIRI_4CHANNEL ) + decode_pixels = decode_simple_scaled_or_not[ input_type == STBIR_TYPE_UINT16 ][ non_scaled ]; + else + decode_pixels = decode_alphas_scaled_or_not[ ( info->input_pixel_layout_internal - STBIRI_RGBA ) % ( STBIRI_AR-STBIRI_RGBA+1 ) ][ input_type == STBIR_TYPE_UINT16 ][ non_scaled ]; + } + else + { + if ( info->input_pixel_layout_internal <= STBIRI_4CHANNEL ) + decode_pixels = decode_simple[ input_type - STBIR_TYPE_UINT8_SRGB ]; + else + decode_pixels = decode_alphas[ ( info->input_pixel_layout_internal - STBIRI_RGBA ) % ( STBIRI_AR-STBIRI_RGBA+1 ) ][ input_type - STBIR_TYPE_UINT8_SRGB ]; + } + + // setup the output format converters + if ( ( output_type == STBIR_TYPE_UINT8 ) || ( output_type == STBIR_TYPE_UINT16 ) ) + { + int non_scaled = 0; + + // check if we can run unscaled - 0-255.0/0-65535.0 instead of 0-1.0 (which is a tiny bit faster when doing linear 8->8 or 16->16) + if ( ( !info->alpha_weight ) && ( !info->alpha_unweight ) ) // don't short circuit when alpha weighting (get everything to 0-1.0 as usual) + if ( ( ( input_type == STBIR_TYPE_UINT8 ) && ( output_type == STBIR_TYPE_UINT8 ) ) || ( ( input_type == STBIR_TYPE_UINT16 ) && ( output_type == STBIR_TYPE_UINT16 ) ) ) + non_scaled = 1; + + if ( info->output_pixel_layout_internal <= STBIRI_4CHANNEL ) + encode_pixels = encode_simple_scaled_or_not[ output_type == STBIR_TYPE_UINT16 ][ non_scaled ]; + else + encode_pixels = encode_alphas_scaled_or_not[ ( info->output_pixel_layout_internal - STBIRI_RGBA ) % ( STBIRI_AR-STBIRI_RGBA+1 ) ][ output_type == STBIR_TYPE_UINT16 ][ non_scaled ]; + } + else + { + if ( info->output_pixel_layout_internal <= STBIRI_4CHANNEL ) + encode_pixels = encode_simple[ output_type - STBIR_TYPE_UINT8_SRGB ]; + else + encode_pixels = encode_alphas[ ( info->output_pixel_layout_internal - STBIRI_RGBA ) % ( STBIRI_AR-STBIRI_RGBA+1 ) ][ output_type - STBIR_TYPE_UINT8_SRGB ]; + } + + info->input_type = input_type; + info->output_type = output_type; + info->decode_pixels = decode_pixels; + info->encode_pixels = encode_pixels; +} + +static void stbir__clip( int * outx, int * outsubw, int outw, double * u0, double * u1 ) +{ + double per, adj; + int over; + + // do left/top edge + if ( *outx < 0 ) + { + per = ( (double)*outx ) / ( (double)*outsubw ); // is negative + adj = per * ( *u1 - *u0 ); + *u0 -= adj; // increases u0 + *outx = 0; + } + + // do right/bot edge + over = outw - ( *outx + *outsubw ); + if ( over < 0 ) + { + per = ( (double)over ) / ( (double)*outsubw ); // is negative + adj = per * ( *u1 - *u0 ); + *u1 += adj; // decrease u1 + *outsubw = outw - *outx; + } +} + +// converts a double to a rational that has less than one float bit of error (returns 0 if unable to do so) +static int stbir__double_to_rational(double f, stbir_uint32 limit, stbir_uint32 *numer, stbir_uint32 *denom, int limit_denom ) // limit_denom (1) or limit numer (0) +{ + double err; + stbir_uint64 top, bot; + stbir_uint64 numer_last = 0; + stbir_uint64 denom_last = 1; + stbir_uint64 numer_estimate = 1; + stbir_uint64 denom_estimate = 0; + + // scale to past float error range + top = (stbir_uint64)( f * (double)(1 << 25) ); + bot = 1 << 25; + + // keep refining, but usually stops in a few loops - usually 5 for bad cases + for(;;) + { + stbir_uint64 est, temp; + + // hit limit, break out and do best full range estimate + if ( ( ( limit_denom ) ? denom_estimate : numer_estimate ) >= limit ) + break; + + // is the current error less than 1 bit of a float? if so, we're done + if ( denom_estimate ) + { + err = ( (double)numer_estimate / (double)denom_estimate ) - f; + if ( err < 0.0 ) err = -err; + if ( err < ( 1.0 / (double)(1<<24) ) ) + { + // yup, found it + *numer = (stbir_uint32) numer_estimate; + *denom = (stbir_uint32) denom_estimate; + return 1; + } + } + + // no more refinement bits left? break out and do full range estimate + if ( bot == 0 ) + break; + + // gcd the estimate bits + est = top / bot; + temp = top % bot; + top = bot; + bot = temp; + + // move remainders + temp = est * denom_estimate + denom_last; + denom_last = denom_estimate; + denom_estimate = temp; + + // move remainders + temp = est * numer_estimate + numer_last; + numer_last = numer_estimate; + numer_estimate = temp; + } + + // we didn't find anything good enough for float, use a full range estimate + if ( limit_denom ) + { + numer_estimate= (stbir_uint64)( f * (double)limit + 0.5 ); + denom_estimate = limit; + } + else + { + numer_estimate = limit; + denom_estimate = (stbir_uint64)( ( (double)limit / f ) + 0.5 ); + } + + *numer = (stbir_uint32) numer_estimate; + *denom = (stbir_uint32) denom_estimate; + + err = ( denom_estimate ) ? ( ( (double)(stbir_uint32)numer_estimate / (double)(stbir_uint32)denom_estimate ) - f ) : 1.0; + if ( err < 0.0 ) err = -err; + return ( err < ( 1.0 / (double)(1<<24) ) ) ? 1 : 0; +} + +static int stbir__calculate_region_transform( stbir__scale_info * scale_info, int output_full_range, int * output_offset, int output_sub_range, int input_full_range, double input_s0, double input_s1 ) +{ + double output_range, input_range, output_s, input_s, ratio, scale; + + input_s = input_s1 - input_s0; + + // null area + if ( ( output_full_range == 0 ) || ( input_full_range == 0 ) || + ( output_sub_range == 0 ) || ( input_s <= stbir__small_float ) ) + return 0; + + // are either of the ranges completely out of bounds? + if ( ( *output_offset >= output_full_range ) || ( ( *output_offset + output_sub_range ) <= 0 ) || ( input_s0 >= (1.0f-stbir__small_float) ) || ( input_s1 <= stbir__small_float ) ) + return 0; + + output_range = (double)output_full_range; + input_range = (double)input_full_range; + + output_s = ( (double)output_sub_range) / output_range; + + // figure out the scaling to use + ratio = output_s / input_s; + + // save scale before clipping + scale = ( output_range / input_range ) * ratio; + scale_info->scale = (float)scale; + scale_info->inv_scale = (float)( 1.0 / scale ); + + // clip output area to left/right output edges (and adjust input area) + stbir__clip( output_offset, &output_sub_range, output_full_range, &input_s0, &input_s1 ); + + // recalc input area + input_s = input_s1 - input_s0; + + // after clipping do we have zero input area? + if ( input_s <= stbir__small_float ) + return 0; + + // calculate and store the starting source offsets in output pixel space + scale_info->pixel_shift = (float) ( input_s0 * ratio * output_range ); + + scale_info->scale_is_rational = stbir__double_to_rational( scale, ( scale <= 1.0 ) ? output_full_range : input_full_range, &scale_info->scale_numerator, &scale_info->scale_denominator, ( scale >= 1.0 ) ); + + scale_info->input_full_size = input_full_range; + scale_info->output_sub_size = output_sub_range; + + return 1; +} + + +static void stbir__init_and_set_layout( STBIR_RESIZE * resize, stbir_pixel_layout pixel_layout, stbir_datatype data_type ) +{ + resize->input_cb = 0; + resize->output_cb = 0; + resize->user_data = resize; + resize->samplers = 0; + resize->called_alloc = 0; + resize->horizontal_filter = STBIR_FILTER_DEFAULT; + resize->horizontal_filter_kernel = 0; resize->horizontal_filter_support = 0; + resize->vertical_filter = STBIR_FILTER_DEFAULT; + resize->vertical_filter_kernel = 0; resize->vertical_filter_support = 0; + resize->horizontal_edge = STBIR_EDGE_CLAMP; + resize->vertical_edge = STBIR_EDGE_CLAMP; + resize->input_s0 = 0; resize->input_t0 = 0; resize->input_s1 = 1; resize->input_t1 = 1; + resize->output_subx = 0; resize->output_suby = 0; resize->output_subw = resize->output_w; resize->output_subh = resize->output_h; + resize->input_data_type = data_type; + resize->output_data_type = data_type; + resize->input_pixel_layout_public = pixel_layout; + resize->output_pixel_layout_public = pixel_layout; + resize->needs_rebuild = 1; +} + +STBIRDEF void stbir_resize_init( STBIR_RESIZE * resize, + const void *input_pixels, int input_w, int input_h, int input_stride_in_bytes, // stride can be zero + void *output_pixels, int output_w, int output_h, int output_stride_in_bytes, // stride can be zero + stbir_pixel_layout pixel_layout, stbir_datatype data_type ) +{ + resize->input_pixels = input_pixels; + resize->input_w = input_w; + resize->input_h = input_h; + resize->input_stride_in_bytes = input_stride_in_bytes; + resize->output_pixels = output_pixels; + resize->output_w = output_w; + resize->output_h = output_h; + resize->output_stride_in_bytes = output_stride_in_bytes; + resize->fast_alpha = 0; + + stbir__init_and_set_layout( resize, pixel_layout, data_type ); +} + +// You can update parameters any time after resize_init +STBIRDEF void stbir_set_datatypes( STBIR_RESIZE * resize, stbir_datatype input_type, stbir_datatype output_type ) // by default, datatype from resize_init +{ + resize->input_data_type = input_type; + resize->output_data_type = output_type; + if ( ( resize->samplers ) && ( !resize->needs_rebuild ) ) + stbir__update_info_from_resize( resize->samplers, resize ); +} + +STBIRDEF void stbir_set_pixel_callbacks( STBIR_RESIZE * resize, stbir_input_callback * input_cb, stbir_output_callback * output_cb ) // no callbacks by default +{ + resize->input_cb = input_cb; + resize->output_cb = output_cb; + + if ( ( resize->samplers ) && ( !resize->needs_rebuild ) ) + { + resize->samplers->in_pixels_cb = input_cb; + resize->samplers->out_pixels_cb = output_cb; + } +} + +STBIRDEF void stbir_set_user_data( STBIR_RESIZE * resize, void * user_data ) // pass back STBIR_RESIZE* by default +{ + resize->user_data = user_data; + if ( ( resize->samplers ) && ( !resize->needs_rebuild ) ) + resize->samplers->user_data = user_data; +} + +STBIRDEF void stbir_set_buffer_ptrs( STBIR_RESIZE * resize, const void * input_pixels, int input_stride_in_bytes, void * output_pixels, int output_stride_in_bytes ) +{ + resize->input_pixels = input_pixels; + resize->input_stride_in_bytes = input_stride_in_bytes; + resize->output_pixels = output_pixels; + resize->output_stride_in_bytes = output_stride_in_bytes; + if ( ( resize->samplers ) && ( !resize->needs_rebuild ) ) + stbir__update_info_from_resize( resize->samplers, resize ); +} + + +STBIRDEF int stbir_set_edgemodes( STBIR_RESIZE * resize, stbir_edge horizontal_edge, stbir_edge vertical_edge ) // CLAMP by default +{ + resize->horizontal_edge = horizontal_edge; + resize->vertical_edge = vertical_edge; + resize->needs_rebuild = 1; + return 1; +} + +STBIRDEF int stbir_set_filters( STBIR_RESIZE * resize, stbir_filter horizontal_filter, stbir_filter vertical_filter ) // STBIR_DEFAULT_FILTER_UPSAMPLE/DOWNSAMPLE by default +{ + resize->horizontal_filter = horizontal_filter; + resize->vertical_filter = vertical_filter; + resize->needs_rebuild = 1; + return 1; +} + +STBIRDEF int stbir_set_filter_callbacks( STBIR_RESIZE * resize, stbir__kernel_callback * horizontal_filter, stbir__support_callback * horizontal_support, stbir__kernel_callback * vertical_filter, stbir__support_callback * vertical_support ) +{ + resize->horizontal_filter_kernel = horizontal_filter; resize->horizontal_filter_support = horizontal_support; + resize->vertical_filter_kernel = vertical_filter; resize->vertical_filter_support = vertical_support; + resize->needs_rebuild = 1; + return 1; +} + +STBIRDEF int stbir_set_pixel_layouts( STBIR_RESIZE * resize, stbir_pixel_layout input_pixel_layout, stbir_pixel_layout output_pixel_layout ) // sets new pixel layouts +{ + resize->input_pixel_layout_public = input_pixel_layout; + resize->output_pixel_layout_public = output_pixel_layout; + resize->needs_rebuild = 1; + return 1; +} + + +STBIRDEF int stbir_set_non_pm_alpha_speed_over_quality( STBIR_RESIZE * resize, int non_pma_alpha_speed_over_quality ) // sets alpha speed +{ + resize->fast_alpha = non_pma_alpha_speed_over_quality; + resize->needs_rebuild = 1; + return 1; +} + +STBIRDEF int stbir_set_input_subrect( STBIR_RESIZE * resize, double s0, double t0, double s1, double t1 ) // sets input region (full region by default) +{ + resize->input_s0 = s0; + resize->input_t0 = t0; + resize->input_s1 = s1; + resize->input_t1 = t1; + resize->needs_rebuild = 1; + + // are we inbounds? + if ( ( s1 < stbir__small_float ) || ( (s1-s0) < stbir__small_float ) || + ( t1 < stbir__small_float ) || ( (t1-t0) < stbir__small_float ) || + ( s0 > (1.0f-stbir__small_float) ) || + ( t0 > (1.0f-stbir__small_float) ) ) + return 0; + + return 1; +} + +STBIRDEF int stbir_set_output_pixel_subrect( STBIR_RESIZE * resize, int subx, int suby, int subw, int subh ) // sets input region (full region by default) +{ + resize->output_subx = subx; + resize->output_suby = suby; + resize->output_subw = subw; + resize->output_subh = subh; + resize->needs_rebuild = 1; + + // are we inbounds? + if ( ( subx >= resize->output_w ) || ( ( subx + subw ) <= 0 ) || ( suby >= resize->output_h ) || ( ( suby + subh ) <= 0 ) || ( subw == 0 ) || ( subh == 0 ) ) + return 0; + + return 1; +} + +STBIRDEF int stbir_set_pixel_subrect( STBIR_RESIZE * resize, int subx, int suby, int subw, int subh ) // sets both regions (full regions by default) +{ + double s0, t0, s1, t1; + + s0 = ( (double)subx ) / ( (double)resize->output_w ); + t0 = ( (double)suby ) / ( (double)resize->output_h ); + s1 = ( (double)(subx+subw) ) / ( (double)resize->output_w ); + t1 = ( (double)(suby+subh) ) / ( (double)resize->output_h ); + + resize->input_s0 = s0; + resize->input_t0 = t0; + resize->input_s1 = s1; + resize->input_t1 = t1; + resize->output_subx = subx; + resize->output_suby = suby; + resize->output_subw = subw; + resize->output_subh = subh; + resize->needs_rebuild = 1; + + // are we inbounds? + if ( ( subx >= resize->output_w ) || ( ( subx + subw ) <= 0 ) || ( suby >= resize->output_h ) || ( ( suby + subh ) <= 0 ) || ( subw == 0 ) || ( subh == 0 ) ) + return 0; + + return 1; +} + +static int stbir__perform_build( STBIR_RESIZE * resize, int splits ) +{ + stbir__contributors conservative = { 0, 0 }; + stbir__sampler horizontal, vertical; + int new_output_subx, new_output_suby; + stbir__info * out_info; + #ifdef STBIR_PROFILE + stbir__info profile_infod; // used to contain building profile info before everything is allocated + stbir__info * profile_info = &profile_infod; + #endif + + // have we already built the samplers? + if ( resize->samplers ) + return 0; + + #define STBIR_RETURN_ERROR_AND_ASSERT( exp ) STBIR_ASSERT( !(exp) ); if (exp) return 0; + STBIR_RETURN_ERROR_AND_ASSERT( (unsigned)resize->horizontal_filter >= STBIR_FILTER_OTHER) + STBIR_RETURN_ERROR_AND_ASSERT( (unsigned)resize->vertical_filter >= STBIR_FILTER_OTHER) + #undef STBIR_RETURN_ERROR_AND_ASSERT + + if ( splits <= 0 ) + return 0; + + STBIR_PROFILE_BUILD_FIRST_START( build ); + + new_output_subx = resize->output_subx; + new_output_suby = resize->output_suby; + + // do horizontal clip and scale calcs + if ( !stbir__calculate_region_transform( &horizontal.scale_info, resize->output_w, &new_output_subx, resize->output_subw, resize->input_w, resize->input_s0, resize->input_s1 ) ) + return 0; + + // do vertical clip and scale calcs + if ( !stbir__calculate_region_transform( &vertical.scale_info, resize->output_h, &new_output_suby, resize->output_subh, resize->input_h, resize->input_t0, resize->input_t1 ) ) + return 0; + + // if nothing to do, just return + if ( ( horizontal.scale_info.output_sub_size == 0 ) || ( vertical.scale_info.output_sub_size == 0 ) ) + return 0; + + stbir__set_sampler(&horizontal, resize->horizontal_filter, resize->horizontal_filter_kernel, resize->horizontal_filter_support, resize->horizontal_edge, &horizontal.scale_info, 1, resize->user_data ); + stbir__get_conservative_extents( &horizontal, &conservative, resize->user_data ); + stbir__set_sampler(&vertical, resize->vertical_filter, resize->vertical_filter_kernel, resize->vertical_filter_support, resize->vertical_edge, &vertical.scale_info, 0, resize->user_data ); + + if ( ( vertical.scale_info.output_sub_size / splits ) < STBIR_FORCE_MINIMUM_SCANLINES_FOR_SPLITS ) // each split should be a minimum of 4 scanlines (handwavey choice) + { + splits = vertical.scale_info.output_sub_size / STBIR_FORCE_MINIMUM_SCANLINES_FOR_SPLITS; + if ( splits == 0 ) splits = 1; + } + + STBIR_PROFILE_BUILD_START( alloc ); + out_info = stbir__alloc_internal_mem_and_build_samplers( &horizontal, &vertical, &conservative, resize->input_pixel_layout_public, resize->output_pixel_layout_public, splits, new_output_subx, new_output_suby, resize->fast_alpha, resize->user_data STBIR_ONLY_PROFILE_BUILD_SET_INFO ); + STBIR_PROFILE_BUILD_END( alloc ); + STBIR_PROFILE_BUILD_END( build ); + + if ( out_info ) + { + resize->splits = splits; + resize->samplers = out_info; + resize->needs_rebuild = 0; + #ifdef STBIR_PROFILE + STBIR_MEMCPY( &out_info->profile, &profile_infod.profile, sizeof( out_info->profile ) ); + #endif + + // update anything that can be changed without recalcing samplers + stbir__update_info_from_resize( out_info, resize ); + + return splits; + } + + return 0; +} + +STBIRDEF void stbir_free_samplers( STBIR_RESIZE * resize ) +{ + if ( resize->samplers ) + { + stbir__free_internal_mem( resize->samplers ); + resize->samplers = 0; + resize->called_alloc = 0; + } +} + +STBIRDEF int stbir_build_samplers_with_splits( STBIR_RESIZE * resize, int splits ) +{ + if ( ( resize->samplers == 0 ) || ( resize->needs_rebuild ) ) + { + if ( resize->samplers ) + stbir_free_samplers( resize ); + + resize->called_alloc = 1; + return stbir__perform_build( resize, splits ); + } + + STBIR_PROFILE_BUILD_CLEAR( resize->samplers ); + + return 1; +} + +STBIRDEF int stbir_build_samplers( STBIR_RESIZE * resize ) +{ + return stbir_build_samplers_with_splits( resize, 1 ); +} + +STBIRDEF int stbir_resize_extended( STBIR_RESIZE * resize ) +{ + int result; + + if ( ( resize->samplers == 0 ) || ( resize->needs_rebuild ) ) + { + int alloc_state = resize->called_alloc; // remember allocated state + + if ( resize->samplers ) + { + stbir__free_internal_mem( resize->samplers ); + resize->samplers = 0; + } + + if ( !stbir_build_samplers( resize ) ) + return 0; + + resize->called_alloc = alloc_state; + + // if build_samplers succeeded (above), but there are no samplers set, then + // the area to stretch into was zero pixels, so don't do anything and return + // success + if ( resize->samplers == 0 ) + return 1; + } + else + { + // didn't build anything - clear it + STBIR_PROFILE_BUILD_CLEAR( resize->samplers ); + } + + // do resize + result = stbir__perform_resize( resize->samplers, 0, resize->splits ); + + // if we alloced, then free + if ( !resize->called_alloc ) + { + stbir_free_samplers( resize ); + resize->samplers = 0; + } + + return result; +} + +STBIRDEF int stbir_resize_extended_split( STBIR_RESIZE * resize, int split_start, int split_count ) +{ + STBIR_ASSERT( resize->samplers ); + + // if we're just doing the whole thing, call full + if ( ( split_start == -1 ) || ( ( split_start == 0 ) && ( split_count == resize->splits ) ) ) + return stbir_resize_extended( resize ); + + // you **must** build samplers first when using split resize + if ( ( resize->samplers == 0 ) || ( resize->needs_rebuild ) ) + return 0; + + if ( ( split_start >= resize->splits ) || ( split_start < 0 ) || ( ( split_start + split_count ) > resize->splits ) || ( split_count <= 0 ) ) + return 0; + + // do resize + return stbir__perform_resize( resize->samplers, split_start, split_count ); +} + + +static void * stbir_quick_resize_helper( const void *input_pixels , int input_w , int input_h, int input_stride_in_bytes, + void *output_pixels, int output_w, int output_h, int output_stride_in_bytes, + stbir_pixel_layout pixel_layout, stbir_datatype data_type, stbir_edge edge, stbir_filter filter ) +{ + STBIR_RESIZE resize; + int scanline_output_in_bytes; + int positive_output_stride_in_bytes; + void * start_ptr; + void * free_ptr; + + scanline_output_in_bytes = output_w * stbir__type_size[ data_type ] * stbir__pixel_channels[ stbir__pixel_layout_convert_public_to_internal[ pixel_layout ] ]; + if ( scanline_output_in_bytes == 0 ) + return 0; + + // if zero stride, use scanline output + if ( output_stride_in_bytes == 0 ) + output_stride_in_bytes = scanline_output_in_bytes; + + // abs value for inverted images (negative pitches) + positive_output_stride_in_bytes = output_stride_in_bytes; + if ( positive_output_stride_in_bytes < 0 ) + positive_output_stride_in_bytes = -positive_output_stride_in_bytes; + + // is the requested stride smaller than the scanline output? if so, just fail + if ( positive_output_stride_in_bytes < scanline_output_in_bytes ) + return 0; + + start_ptr = output_pixels; + free_ptr = 0; // no free pointer, since they passed buffer to use + + // did they pass a zero for the dest? if so, allocate the buffer + if ( output_pixels == 0 ) + { + size_t size; + char * ptr; + + size = (size_t)positive_output_stride_in_bytes * (size_t)output_h; + if ( size == 0 ) + return 0; + + ptr = (char*) STBIR_MALLOC( size, 0 ); + if ( ptr == 0 ) + return 0; + + free_ptr = ptr; + + // point at the last scanline, if they requested a flipped image + if ( output_stride_in_bytes < 0 ) + start_ptr = ptr + ( (size_t)positive_output_stride_in_bytes * (size_t)( output_h - 1 ) ); + else + start_ptr = ptr; + } + + // ok, now do the resize + stbir_resize_init( &resize, + input_pixels, input_w, input_h, input_stride_in_bytes, + start_ptr, output_w, output_h, output_stride_in_bytes, + pixel_layout, data_type ); + + resize.horizontal_edge = edge; + resize.vertical_edge = edge; + resize.horizontal_filter = filter; + resize.vertical_filter = filter; + + if ( !stbir_resize_extended( &resize ) ) + { + if ( free_ptr ) + STBIR_FREE( free_ptr, 0 ); + return 0; + } + + return (free_ptr) ? free_ptr : start_ptr; +} + + + +STBIRDEF unsigned char * stbir_resize_uint8_linear( const unsigned char *input_pixels , int input_w , int input_h, int input_stride_in_bytes, + unsigned char *output_pixels, int output_w, int output_h, int output_stride_in_bytes, + stbir_pixel_layout pixel_layout ) +{ + return (unsigned char *) stbir_quick_resize_helper( input_pixels , input_w , input_h, input_stride_in_bytes, + output_pixels, output_w, output_h, output_stride_in_bytes, + pixel_layout, STBIR_TYPE_UINT8, STBIR_EDGE_CLAMP, STBIR_FILTER_DEFAULT ); +} + +STBIRDEF unsigned char * stbir_resize_uint8_srgb( const unsigned char *input_pixels , int input_w , int input_h, int input_stride_in_bytes, + unsigned char *output_pixels, int output_w, int output_h, int output_stride_in_bytes, + stbir_pixel_layout pixel_layout ) +{ + return (unsigned char *) stbir_quick_resize_helper( input_pixels , input_w , input_h, input_stride_in_bytes, + output_pixels, output_w, output_h, output_stride_in_bytes, + pixel_layout, STBIR_TYPE_UINT8_SRGB, STBIR_EDGE_CLAMP, STBIR_FILTER_DEFAULT ); +} + + +STBIRDEF float * stbir_resize_float_linear( const float *input_pixels , int input_w , int input_h, int input_stride_in_bytes, + float *output_pixels, int output_w, int output_h, int output_stride_in_bytes, + stbir_pixel_layout pixel_layout ) +{ + return (float *) stbir_quick_resize_helper( input_pixels , input_w , input_h, input_stride_in_bytes, + output_pixels, output_w, output_h, output_stride_in_bytes, + pixel_layout, STBIR_TYPE_FLOAT, STBIR_EDGE_CLAMP, STBIR_FILTER_DEFAULT ); +} + + +STBIRDEF void * stbir_resize( const void *input_pixels , int input_w , int input_h, int input_stride_in_bytes, + void *output_pixels, int output_w, int output_h, int output_stride_in_bytes, + stbir_pixel_layout pixel_layout, stbir_datatype data_type, + stbir_edge edge, stbir_filter filter ) +{ + return (void *) stbir_quick_resize_helper( input_pixels , input_w , input_h, input_stride_in_bytes, + output_pixels, output_w, output_h, output_stride_in_bytes, + pixel_layout, data_type, edge, filter ); +} + +#ifdef STBIR_PROFILE + +STBIRDEF void stbir_resize_build_profile_info( STBIR_PROFILE_INFO * info, STBIR_RESIZE const * resize ) +{ + static char const * bdescriptions[6] = { "Building", "Allocating", "Horizontal sampler", "Vertical sampler", "Coefficient cleanup", "Coefficient pivot" } ; + stbir__info* samp = resize->samplers; + int i; + + typedef int testa[ (STBIR__ARRAY_SIZE( bdescriptions ) == (STBIR__ARRAY_SIZE( samp->profile.array )-1) )?1:-1]; + typedef int testb[ (sizeof( samp->profile.array ) == (sizeof(samp->profile.named)) )?1:-1]; + typedef int testc[ (sizeof( info->clocks ) >= (sizeof(samp->profile.named)) )?1:-1]; + + for( i = 0 ; i < STBIR__ARRAY_SIZE( bdescriptions ) ; i++) + info->clocks[i] = samp->profile.array[i+1]; + + info->total_clocks = samp->profile.named.total; + info->descriptions = bdescriptions; + info->count = STBIR__ARRAY_SIZE( bdescriptions ); +} + +STBIRDEF void stbir_resize_split_profile_info( STBIR_PROFILE_INFO * info, STBIR_RESIZE const * resize, int split_start, int split_count ) +{ + static char const * descriptions[7] = { "Looping", "Vertical sampling", "Horizontal sampling", "Scanline input", "Scanline output", "Alpha weighting", "Alpha unweighting" }; + stbir__per_split_info * split_info; + int s, i; + + typedef int testa[ (STBIR__ARRAY_SIZE( descriptions ) == (STBIR__ARRAY_SIZE( split_info->profile.array )-1) )?1:-1]; + typedef int testb[ (sizeof( split_info->profile.array ) == (sizeof(split_info->profile.named)) )?1:-1]; + typedef int testc[ (sizeof( info->clocks ) >= (sizeof(split_info->profile.named)) )?1:-1]; + + if ( split_start == -1 ) + { + split_start = 0; + split_count = resize->samplers->splits; + } + + if ( ( split_start >= resize->splits ) || ( split_start < 0 ) || ( ( split_start + split_count ) > resize->splits ) || ( split_count <= 0 ) ) + { + info->total_clocks = 0; + info->descriptions = 0; + info->count = 0; + return; + } + + split_info = resize->samplers->split_info + split_start; + + // sum up the profile from all the splits + for( i = 0 ; i < STBIR__ARRAY_SIZE( descriptions ) ; i++ ) + { + stbir_uint64 sum = 0; + for( s = 0 ; s < split_count ; s++ ) + sum += split_info[s].profile.array[i+1]; + info->clocks[i] = sum; + } + + info->total_clocks = split_info->profile.named.total; + info->descriptions = descriptions; + info->count = STBIR__ARRAY_SIZE( descriptions ); +} + +STBIRDEF void stbir_resize_extended_profile_info( STBIR_PROFILE_INFO * info, STBIR_RESIZE const * resize ) +{ + stbir_resize_split_profile_info( info, resize, -1, 0 ); +} + +#endif // STBIR_PROFILE + +#undef STBIR_BGR +#undef STBIR_1CHANNEL +#undef STBIR_2CHANNEL +#undef STBIR_RGB +#undef STBIR_RGBA +#undef STBIR_4CHANNEL +#undef STBIR_BGRA +#undef STBIR_ARGB +#undef STBIR_ABGR +#undef STBIR_RA +#undef STBIR_AR +#undef STBIR_RGBA_PM +#undef STBIR_BGRA_PM +#undef STBIR_ARGB_PM +#undef STBIR_ABGR_PM +#undef STBIR_RA_PM +#undef STBIR_AR_PM + +#endif // STB_IMAGE_RESIZE_IMPLEMENTATION + +#else // STB_IMAGE_RESIZE_HORIZONTALS&STB_IMAGE_RESIZE_DO_VERTICALS + +// we reinclude the header file to define all the horizontal functions +// specializing each function for the number of coeffs is 20-40% faster *OVERALL* + +// by including the header file again this way, we can still debug the functions + +#define STBIR_strs_join2( start, mid, end ) start##mid##end +#define STBIR_strs_join1( start, mid, end ) STBIR_strs_join2( start, mid, end ) + +#define STBIR_strs_join24( start, mid1, mid2, end ) start##mid1##mid2##end +#define STBIR_strs_join14( start, mid1, mid2, end ) STBIR_strs_join24( start, mid1, mid2, end ) + +#ifdef STB_IMAGE_RESIZE_DO_CODERS + +#ifdef stbir__decode_suffix +#define STBIR__CODER_NAME( name ) STBIR_strs_join1( name, _, stbir__decode_suffix ) +#else +#define STBIR__CODER_NAME( name ) name +#endif + +#ifdef stbir__decode_swizzle +#define stbir__decode_simdf8_flip(reg) STBIR_strs_join1( STBIR_strs_join1( STBIR_strs_join1( STBIR_strs_join1( stbir__simdf8_0123to,stbir__decode_order0,stbir__decode_order1),stbir__decode_order2,stbir__decode_order3),stbir__decode_order0,stbir__decode_order1),stbir__decode_order2,stbir__decode_order3)(reg, reg) +#define stbir__decode_simdf4_flip(reg) STBIR_strs_join1( STBIR_strs_join1( stbir__simdf_0123to,stbir__decode_order0,stbir__decode_order1),stbir__decode_order2,stbir__decode_order3)(reg, reg) +#define stbir__encode_simdf8_unflip(reg) STBIR_strs_join1( STBIR_strs_join1( STBIR_strs_join1( STBIR_strs_join1( stbir__simdf8_0123to,stbir__encode_order0,stbir__encode_order1),stbir__encode_order2,stbir__encode_order3),stbir__encode_order0,stbir__encode_order1),stbir__encode_order2,stbir__encode_order3)(reg, reg) +#define stbir__encode_simdf4_unflip(reg) STBIR_strs_join1( STBIR_strs_join1( stbir__simdf_0123to,stbir__encode_order0,stbir__encode_order1),stbir__encode_order2,stbir__encode_order3)(reg, reg) +#else +#define stbir__decode_order0 0 +#define stbir__decode_order1 1 +#define stbir__decode_order2 2 +#define stbir__decode_order3 3 +#define stbir__encode_order0 0 +#define stbir__encode_order1 1 +#define stbir__encode_order2 2 +#define stbir__encode_order3 3 +#define stbir__decode_simdf8_flip(reg) +#define stbir__decode_simdf4_flip(reg) +#define stbir__encode_simdf8_unflip(reg) +#define stbir__encode_simdf4_unflip(reg) +#endif + +#ifdef STBIR_SIMD8 +#define stbir__encode_simdfX_unflip stbir__encode_simdf8_unflip +#else +#define stbir__encode_simdfX_unflip stbir__encode_simdf4_unflip +#endif + +static float * STBIR__CODER_NAME( stbir__decode_uint8_linear_scaled )( float * decodep, int width_times_channels, void const * inputp ) +{ + float STBIR_STREAMOUT_PTR( * ) decode = decodep; + float * decode_end = (float*) decode + width_times_channels; + unsigned char const * input = (unsigned char const*)inputp; + + #ifdef STBIR_SIMD + unsigned char const * end_input_m16 = input + width_times_channels - 16; + if ( width_times_channels >= 16 ) + { + decode_end -= 16; + STBIR_NO_UNROLL_LOOP_START_INF_FOR + for(;;) + { + #ifdef STBIR_SIMD8 + stbir__simdi i; stbir__simdi8 o0,o1; + stbir__simdf8 of0, of1; + STBIR_NO_UNROLL(decode); + stbir__simdi_load( i, input ); + stbir__simdi8_expand_u8_to_u32( o0, o1, i ); + stbir__simdi8_convert_i32_to_float( of0, o0 ); + stbir__simdi8_convert_i32_to_float( of1, o1 ); + stbir__simdf8_mult( of0, of0, STBIR_max_uint8_as_float_inverted8); + stbir__simdf8_mult( of1, of1, STBIR_max_uint8_as_float_inverted8); + stbir__decode_simdf8_flip( of0 ); + stbir__decode_simdf8_flip( of1 ); + stbir__simdf8_store( decode + 0, of0 ); + stbir__simdf8_store( decode + 8, of1 ); + #else + stbir__simdi i, o0, o1, o2, o3; + stbir__simdf of0, of1, of2, of3; + STBIR_NO_UNROLL(decode); + stbir__simdi_load( i, input ); + stbir__simdi_expand_u8_to_u32( o0,o1,o2,o3,i); + stbir__simdi_convert_i32_to_float( of0, o0 ); + stbir__simdi_convert_i32_to_float( of1, o1 ); + stbir__simdi_convert_i32_to_float( of2, o2 ); + stbir__simdi_convert_i32_to_float( of3, o3 ); + stbir__simdf_mult( of0, of0, STBIR__CONSTF(STBIR_max_uint8_as_float_inverted) ); + stbir__simdf_mult( of1, of1, STBIR__CONSTF(STBIR_max_uint8_as_float_inverted) ); + stbir__simdf_mult( of2, of2, STBIR__CONSTF(STBIR_max_uint8_as_float_inverted) ); + stbir__simdf_mult( of3, of3, STBIR__CONSTF(STBIR_max_uint8_as_float_inverted) ); + stbir__decode_simdf4_flip( of0 ); + stbir__decode_simdf4_flip( of1 ); + stbir__decode_simdf4_flip( of2 ); + stbir__decode_simdf4_flip( of3 ); + stbir__simdf_store( decode + 0, of0 ); + stbir__simdf_store( decode + 4, of1 ); + stbir__simdf_store( decode + 8, of2 ); + stbir__simdf_store( decode + 12, of3 ); + #endif + decode += 16; + input += 16; + if ( decode <= decode_end ) + continue; + if ( decode == ( decode_end + 16 ) ) + break; + decode = decode_end; // backup and do last couple + input = end_input_m16; + } + return decode_end + 16; + } + #endif + + // try to do blocks of 4 when you can + #if stbir__coder_min_num != 3 // doesn't divide cleanly by four + decode += 4; + STBIR_SIMD_NO_UNROLL_LOOP_START + while( decode <= decode_end ) + { + STBIR_SIMD_NO_UNROLL(decode); + decode[0-4] = ((float)(input[stbir__decode_order0])) * stbir__max_uint8_as_float_inverted; + decode[1-4] = ((float)(input[stbir__decode_order1])) * stbir__max_uint8_as_float_inverted; + decode[2-4] = ((float)(input[stbir__decode_order2])) * stbir__max_uint8_as_float_inverted; + decode[3-4] = ((float)(input[stbir__decode_order3])) * stbir__max_uint8_as_float_inverted; + decode += 4; + input += 4; + } + decode -= 4; + #endif + + // do the remnants + #if stbir__coder_min_num < 4 + STBIR_NO_UNROLL_LOOP_START + while( decode < decode_end ) + { + STBIR_NO_UNROLL(decode); + decode[0] = ((float)(input[stbir__decode_order0])) * stbir__max_uint8_as_float_inverted; + #if stbir__coder_min_num >= 2 + decode[1] = ((float)(input[stbir__decode_order1])) * stbir__max_uint8_as_float_inverted; + #endif + #if stbir__coder_min_num >= 3 + decode[2] = ((float)(input[stbir__decode_order2])) * stbir__max_uint8_as_float_inverted; + #endif + decode += stbir__coder_min_num; + input += stbir__coder_min_num; + } + #endif + + return decode_end; +} + +static void STBIR__CODER_NAME( stbir__encode_uint8_linear_scaled )( void * outputp, int width_times_channels, float const * encode ) +{ + unsigned char STBIR_SIMD_STREAMOUT_PTR( * ) output = (unsigned char *) outputp; + unsigned char * end_output = ( (unsigned char *) output ) + width_times_channels; + + #ifdef STBIR_SIMD + if ( width_times_channels >= stbir__simdfX_float_count*2 ) + { + float const * end_encode_m8 = encode + width_times_channels - stbir__simdfX_float_count*2; + end_output -= stbir__simdfX_float_count*2; + STBIR_NO_UNROLL_LOOP_START_INF_FOR + for(;;) + { + stbir__simdfX e0, e1; + stbir__simdi i; + STBIR_SIMD_NO_UNROLL(encode); + stbir__simdfX_madd_mem( e0, STBIR_simd_point5X, STBIR_max_uint8_as_floatX, encode ); + stbir__simdfX_madd_mem( e1, STBIR_simd_point5X, STBIR_max_uint8_as_floatX, encode+stbir__simdfX_float_count ); + stbir__encode_simdfX_unflip( e0 ); + stbir__encode_simdfX_unflip( e1 ); + #ifdef STBIR_SIMD8 + stbir__simdf8_pack_to_16bytes( i, e0, e1 ); + stbir__simdi_store( output, i ); + #else + stbir__simdf_pack_to_8bytes( i, e0, e1 ); + stbir__simdi_store2( output, i ); + #endif + encode += stbir__simdfX_float_count*2; + output += stbir__simdfX_float_count*2; + if ( output <= end_output ) + continue; + if ( output == ( end_output + stbir__simdfX_float_count*2 ) ) + break; + output = end_output; // backup and do last couple + encode = end_encode_m8; + } + return; + } + + // try to do blocks of 4 when you can + #if stbir__coder_min_num != 3 // doesn't divide cleanly by four + output += 4; + STBIR_NO_UNROLL_LOOP_START + while( output <= end_output ) + { + stbir__simdf e0; + stbir__simdi i0; + STBIR_NO_UNROLL(encode); + stbir__simdf_load( e0, encode ); + stbir__simdf_madd( e0, STBIR__CONSTF(STBIR_simd_point5), STBIR__CONSTF(STBIR_max_uint8_as_float), e0 ); + stbir__encode_simdf4_unflip( e0 ); + stbir__simdf_pack_to_8bytes( i0, e0, e0 ); // only use first 4 + *(int*)(output-4) = stbir__simdi_to_int( i0 ); + output += 4; + encode += 4; + } + output -= 4; + #endif + + // do the remnants + #if stbir__coder_min_num < 4 + STBIR_NO_UNROLL_LOOP_START + while( output < end_output ) + { + stbir__simdf e0; + STBIR_NO_UNROLL(encode); + stbir__simdf_madd1_mem( e0, STBIR__CONSTF(STBIR_simd_point5), STBIR__CONSTF(STBIR_max_uint8_as_float), encode+stbir__encode_order0 ); output[0] = stbir__simdf_convert_float_to_uint8( e0 ); + #if stbir__coder_min_num >= 2 + stbir__simdf_madd1_mem( e0, STBIR__CONSTF(STBIR_simd_point5), STBIR__CONSTF(STBIR_max_uint8_as_float), encode+stbir__encode_order1 ); output[1] = stbir__simdf_convert_float_to_uint8( e0 ); + #endif + #if stbir__coder_min_num >= 3 + stbir__simdf_madd1_mem( e0, STBIR__CONSTF(STBIR_simd_point5), STBIR__CONSTF(STBIR_max_uint8_as_float), encode+stbir__encode_order2 ); output[2] = stbir__simdf_convert_float_to_uint8( e0 ); + #endif + output += stbir__coder_min_num; + encode += stbir__coder_min_num; + } + #endif + + #else + + // try to do blocks of 4 when you can + #if stbir__coder_min_num != 3 // doesn't divide cleanly by four + output += 4; + while( output <= end_output ) + { + float f; + f = encode[stbir__encode_order0] * stbir__max_uint8_as_float + 0.5f; STBIR_CLAMP(f, 0, 255); output[0-4] = (unsigned char)f; + f = encode[stbir__encode_order1] * stbir__max_uint8_as_float + 0.5f; STBIR_CLAMP(f, 0, 255); output[1-4] = (unsigned char)f; + f = encode[stbir__encode_order2] * stbir__max_uint8_as_float + 0.5f; STBIR_CLAMP(f, 0, 255); output[2-4] = (unsigned char)f; + f = encode[stbir__encode_order3] * stbir__max_uint8_as_float + 0.5f; STBIR_CLAMP(f, 0, 255); output[3-4] = (unsigned char)f; + output += 4; + encode += 4; + } + output -= 4; + #endif + + // do the remnants + #if stbir__coder_min_num < 4 + STBIR_NO_UNROLL_LOOP_START + while( output < end_output ) + { + float f; + STBIR_NO_UNROLL(encode); + f = encode[stbir__encode_order0] * stbir__max_uint8_as_float + 0.5f; STBIR_CLAMP(f, 0, 255); output[0] = (unsigned char)f; + #if stbir__coder_min_num >= 2 + f = encode[stbir__encode_order1] * stbir__max_uint8_as_float + 0.5f; STBIR_CLAMP(f, 0, 255); output[1] = (unsigned char)f; + #endif + #if stbir__coder_min_num >= 3 + f = encode[stbir__encode_order2] * stbir__max_uint8_as_float + 0.5f; STBIR_CLAMP(f, 0, 255); output[2] = (unsigned char)f; + #endif + output += stbir__coder_min_num; + encode += stbir__coder_min_num; + } + #endif + #endif +} + +static float * STBIR__CODER_NAME(stbir__decode_uint8_linear)( float * decodep, int width_times_channels, void const * inputp ) +{ + float STBIR_STREAMOUT_PTR( * ) decode = decodep; + float * decode_end = (float*) decode + width_times_channels; + unsigned char const * input = (unsigned char const*)inputp; + + #ifdef STBIR_SIMD + unsigned char const * end_input_m16 = input + width_times_channels - 16; + if ( width_times_channels >= 16 ) + { + decode_end -= 16; + STBIR_NO_UNROLL_LOOP_START_INF_FOR + for(;;) + { + #ifdef STBIR_SIMD8 + stbir__simdi i; stbir__simdi8 o0,o1; + stbir__simdf8 of0, of1; + STBIR_NO_UNROLL(decode); + stbir__simdi_load( i, input ); + stbir__simdi8_expand_u8_to_u32( o0, o1, i ); + stbir__simdi8_convert_i32_to_float( of0, o0 ); + stbir__simdi8_convert_i32_to_float( of1, o1 ); + stbir__decode_simdf8_flip( of0 ); + stbir__decode_simdf8_flip( of1 ); + stbir__simdf8_store( decode + 0, of0 ); + stbir__simdf8_store( decode + 8, of1 ); + #else + stbir__simdi i, o0, o1, o2, o3; + stbir__simdf of0, of1, of2, of3; + STBIR_NO_UNROLL(decode); + stbir__simdi_load( i, input ); + stbir__simdi_expand_u8_to_u32( o0,o1,o2,o3,i); + stbir__simdi_convert_i32_to_float( of0, o0 ); + stbir__simdi_convert_i32_to_float( of1, o1 ); + stbir__simdi_convert_i32_to_float( of2, o2 ); + stbir__simdi_convert_i32_to_float( of3, o3 ); + stbir__decode_simdf4_flip( of0 ); + stbir__decode_simdf4_flip( of1 ); + stbir__decode_simdf4_flip( of2 ); + stbir__decode_simdf4_flip( of3 ); + stbir__simdf_store( decode + 0, of0 ); + stbir__simdf_store( decode + 4, of1 ); + stbir__simdf_store( decode + 8, of2 ); + stbir__simdf_store( decode + 12, of3 ); +#endif + decode += 16; + input += 16; + if ( decode <= decode_end ) + continue; + if ( decode == ( decode_end + 16 ) ) + break; + decode = decode_end; // backup and do last couple + input = end_input_m16; + } + return decode_end + 16; + } + #endif + + // try to do blocks of 4 when you can + #if stbir__coder_min_num != 3 // doesn't divide cleanly by four + decode += 4; + STBIR_SIMD_NO_UNROLL_LOOP_START + while( decode <= decode_end ) + { + STBIR_SIMD_NO_UNROLL(decode); + decode[0-4] = ((float)(input[stbir__decode_order0])); + decode[1-4] = ((float)(input[stbir__decode_order1])); + decode[2-4] = ((float)(input[stbir__decode_order2])); + decode[3-4] = ((float)(input[stbir__decode_order3])); + decode += 4; + input += 4; + } + decode -= 4; + #endif + + // do the remnants + #if stbir__coder_min_num < 4 + STBIR_NO_UNROLL_LOOP_START + while( decode < decode_end ) + { + STBIR_NO_UNROLL(decode); + decode[0] = ((float)(input[stbir__decode_order0])); + #if stbir__coder_min_num >= 2 + decode[1] = ((float)(input[stbir__decode_order1])); + #endif + #if stbir__coder_min_num >= 3 + decode[2] = ((float)(input[stbir__decode_order2])); + #endif + decode += stbir__coder_min_num; + input += stbir__coder_min_num; + } + #endif + return decode_end; +} + +static void STBIR__CODER_NAME( stbir__encode_uint8_linear )( void * outputp, int width_times_channels, float const * encode ) +{ + unsigned char STBIR_SIMD_STREAMOUT_PTR( * ) output = (unsigned char *) outputp; + unsigned char * end_output = ( (unsigned char *) output ) + width_times_channels; + + #ifdef STBIR_SIMD + if ( width_times_channels >= stbir__simdfX_float_count*2 ) + { + float const * end_encode_m8 = encode + width_times_channels - stbir__simdfX_float_count*2; + end_output -= stbir__simdfX_float_count*2; + STBIR_SIMD_NO_UNROLL_LOOP_START_INF_FOR + for(;;) + { + stbir__simdfX e0, e1; + stbir__simdi i; + STBIR_SIMD_NO_UNROLL(encode); + stbir__simdfX_add_mem( e0, STBIR_simd_point5X, encode ); + stbir__simdfX_add_mem( e1, STBIR_simd_point5X, encode+stbir__simdfX_float_count ); + stbir__encode_simdfX_unflip( e0 ); + stbir__encode_simdfX_unflip( e1 ); + #ifdef STBIR_SIMD8 + stbir__simdf8_pack_to_16bytes( i, e0, e1 ); + stbir__simdi_store( output, i ); + #else + stbir__simdf_pack_to_8bytes( i, e0, e1 ); + stbir__simdi_store2( output, i ); + #endif + encode += stbir__simdfX_float_count*2; + output += stbir__simdfX_float_count*2; + if ( output <= end_output ) + continue; + if ( output == ( end_output + stbir__simdfX_float_count*2 ) ) + break; + output = end_output; // backup and do last couple + encode = end_encode_m8; + } + return; + } + + // try to do blocks of 4 when you can + #if stbir__coder_min_num != 3 // doesn't divide cleanly by four + output += 4; + STBIR_NO_UNROLL_LOOP_START + while( output <= end_output ) + { + stbir__simdf e0; + stbir__simdi i0; + STBIR_NO_UNROLL(encode); + stbir__simdf_load( e0, encode ); + stbir__simdf_add( e0, STBIR__CONSTF(STBIR_simd_point5), e0 ); + stbir__encode_simdf4_unflip( e0 ); + stbir__simdf_pack_to_8bytes( i0, e0, e0 ); // only use first 4 + *(int*)(output-4) = stbir__simdi_to_int( i0 ); + output += 4; + encode += 4; + } + output -= 4; + #endif + + #else + + // try to do blocks of 4 when you can + #if stbir__coder_min_num != 3 // doesn't divide cleanly by four + output += 4; + while( output <= end_output ) + { + float f; + f = encode[stbir__encode_order0] + 0.5f; STBIR_CLAMP(f, 0, 255); output[0-4] = (unsigned char)f; + f = encode[stbir__encode_order1] + 0.5f; STBIR_CLAMP(f, 0, 255); output[1-4] = (unsigned char)f; + f = encode[stbir__encode_order2] + 0.5f; STBIR_CLAMP(f, 0, 255); output[2-4] = (unsigned char)f; + f = encode[stbir__encode_order3] + 0.5f; STBIR_CLAMP(f, 0, 255); output[3-4] = (unsigned char)f; + output += 4; + encode += 4; + } + output -= 4; + #endif + + #endif + + // do the remnants + #if stbir__coder_min_num < 4 + STBIR_NO_UNROLL_LOOP_START + while( output < end_output ) + { + float f; + STBIR_NO_UNROLL(encode); + f = encode[stbir__encode_order0] + 0.5f; STBIR_CLAMP(f, 0, 255); output[0] = (unsigned char)f; + #if stbir__coder_min_num >= 2 + f = encode[stbir__encode_order1] + 0.5f; STBIR_CLAMP(f, 0, 255); output[1] = (unsigned char)f; + #endif + #if stbir__coder_min_num >= 3 + f = encode[stbir__encode_order2] + 0.5f; STBIR_CLAMP(f, 0, 255); output[2] = (unsigned char)f; + #endif + output += stbir__coder_min_num; + encode += stbir__coder_min_num; + } + #endif +} + +static float * STBIR__CODER_NAME(stbir__decode_uint8_srgb)( float * decodep, int width_times_channels, void const * inputp ) +{ + float STBIR_STREAMOUT_PTR( * ) decode = decodep; + float * decode_end = (float*) decode + width_times_channels; + unsigned char const * input = (unsigned char const *)inputp; + + // try to do blocks of 4 when you can + #if stbir__coder_min_num != 3 // doesn't divide cleanly by four + decode += 4; + while( decode <= decode_end ) + { + decode[0-4] = stbir__srgb_uchar_to_linear_float[ input[ stbir__decode_order0 ] ]; + decode[1-4] = stbir__srgb_uchar_to_linear_float[ input[ stbir__decode_order1 ] ]; + decode[2-4] = stbir__srgb_uchar_to_linear_float[ input[ stbir__decode_order2 ] ]; + decode[3-4] = stbir__srgb_uchar_to_linear_float[ input[ stbir__decode_order3 ] ]; + decode += 4; + input += 4; + } + decode -= 4; + #endif + + // do the remnants + #if stbir__coder_min_num < 4 + STBIR_NO_UNROLL_LOOP_START + while( decode < decode_end ) + { + STBIR_NO_UNROLL(decode); + decode[0] = stbir__srgb_uchar_to_linear_float[ input[ stbir__decode_order0 ] ]; + #if stbir__coder_min_num >= 2 + decode[1] = stbir__srgb_uchar_to_linear_float[ input[ stbir__decode_order1 ] ]; + #endif + #if stbir__coder_min_num >= 3 + decode[2] = stbir__srgb_uchar_to_linear_float[ input[ stbir__decode_order2 ] ]; + #endif + decode += stbir__coder_min_num; + input += stbir__coder_min_num; + } + #endif + return decode_end; +} + +#define stbir__min_max_shift20( i, f ) \ + stbir__simdf_max( f, f, stbir_simdf_casti(STBIR__CONSTI( STBIR_almost_zero )) ); \ + stbir__simdf_min( f, f, stbir_simdf_casti(STBIR__CONSTI( STBIR_almost_one )) ); \ + stbir__simdi_32shr( i, stbir_simdi_castf( f ), 20 ); + +#define stbir__scale_and_convert( i, f ) \ + stbir__simdf_madd( f, STBIR__CONSTF( STBIR_simd_point5 ), STBIR__CONSTF( STBIR_max_uint8_as_float ), f ); \ + stbir__simdf_max( f, f, stbir__simdf_zeroP() ); \ + stbir__simdf_min( f, f, STBIR__CONSTF( STBIR_max_uint8_as_float ) ); \ + stbir__simdf_convert_float_to_i32( i, f ); + +#define stbir__linear_to_srgb_finish( i, f ) \ +{ \ + stbir__simdi temp; \ + stbir__simdi_32shr( temp, stbir_simdi_castf( f ), 12 ) ; \ + stbir__simdi_and( temp, temp, STBIR__CONSTI(STBIR_mantissa_mask) ); \ + stbir__simdi_or( temp, temp, STBIR__CONSTI(STBIR_topscale) ); \ + stbir__simdi_16madd( i, i, temp ); \ + stbir__simdi_32shr( i, i, 16 ); \ +} + +#define stbir__simdi_table_lookup2( v0,v1, table ) \ +{ \ + stbir__simdi_u32 temp0,temp1; \ + temp0.m128i_i128 = v0; \ + temp1.m128i_i128 = v1; \ + temp0.m128i_u32[0] = table[temp0.m128i_i32[0]]; temp0.m128i_u32[1] = table[temp0.m128i_i32[1]]; temp0.m128i_u32[2] = table[temp0.m128i_i32[2]]; temp0.m128i_u32[3] = table[temp0.m128i_i32[3]]; \ + temp1.m128i_u32[0] = table[temp1.m128i_i32[0]]; temp1.m128i_u32[1] = table[temp1.m128i_i32[1]]; temp1.m128i_u32[2] = table[temp1.m128i_i32[2]]; temp1.m128i_u32[3] = table[temp1.m128i_i32[3]]; \ + v0 = temp0.m128i_i128; \ + v1 = temp1.m128i_i128; \ +} + +#define stbir__simdi_table_lookup3( v0,v1,v2, table ) \ +{ \ + stbir__simdi_u32 temp0,temp1,temp2; \ + temp0.m128i_i128 = v0; \ + temp1.m128i_i128 = v1; \ + temp2.m128i_i128 = v2; \ + temp0.m128i_u32[0] = table[temp0.m128i_i32[0]]; temp0.m128i_u32[1] = table[temp0.m128i_i32[1]]; temp0.m128i_u32[2] = table[temp0.m128i_i32[2]]; temp0.m128i_u32[3] = table[temp0.m128i_i32[3]]; \ + temp1.m128i_u32[0] = table[temp1.m128i_i32[0]]; temp1.m128i_u32[1] = table[temp1.m128i_i32[1]]; temp1.m128i_u32[2] = table[temp1.m128i_i32[2]]; temp1.m128i_u32[3] = table[temp1.m128i_i32[3]]; \ + temp2.m128i_u32[0] = table[temp2.m128i_i32[0]]; temp2.m128i_u32[1] = table[temp2.m128i_i32[1]]; temp2.m128i_u32[2] = table[temp2.m128i_i32[2]]; temp2.m128i_u32[3] = table[temp2.m128i_i32[3]]; \ + v0 = temp0.m128i_i128; \ + v1 = temp1.m128i_i128; \ + v2 = temp2.m128i_i128; \ +} + +#define stbir__simdi_table_lookup4( v0,v1,v2,v3, table ) \ +{ \ + stbir__simdi_u32 temp0,temp1,temp2,temp3; \ + temp0.m128i_i128 = v0; \ + temp1.m128i_i128 = v1; \ + temp2.m128i_i128 = v2; \ + temp3.m128i_i128 = v3; \ + temp0.m128i_u32[0] = table[temp0.m128i_i32[0]]; temp0.m128i_u32[1] = table[temp0.m128i_i32[1]]; temp0.m128i_u32[2] = table[temp0.m128i_i32[2]]; temp0.m128i_u32[3] = table[temp0.m128i_i32[3]]; \ + temp1.m128i_u32[0] = table[temp1.m128i_i32[0]]; temp1.m128i_u32[1] = table[temp1.m128i_i32[1]]; temp1.m128i_u32[2] = table[temp1.m128i_i32[2]]; temp1.m128i_u32[3] = table[temp1.m128i_i32[3]]; \ + temp2.m128i_u32[0] = table[temp2.m128i_i32[0]]; temp2.m128i_u32[1] = table[temp2.m128i_i32[1]]; temp2.m128i_u32[2] = table[temp2.m128i_i32[2]]; temp2.m128i_u32[3] = table[temp2.m128i_i32[3]]; \ + temp3.m128i_u32[0] = table[temp3.m128i_i32[0]]; temp3.m128i_u32[1] = table[temp3.m128i_i32[1]]; temp3.m128i_u32[2] = table[temp3.m128i_i32[2]]; temp3.m128i_u32[3] = table[temp3.m128i_i32[3]]; \ + v0 = temp0.m128i_i128; \ + v1 = temp1.m128i_i128; \ + v2 = temp2.m128i_i128; \ + v3 = temp3.m128i_i128; \ +} + +static void STBIR__CODER_NAME( stbir__encode_uint8_srgb )( void * outputp, int width_times_channels, float const * encode ) +{ + unsigned char STBIR_SIMD_STREAMOUT_PTR( * ) output = (unsigned char*) outputp; + unsigned char * end_output = ( (unsigned char*) output ) + width_times_channels; + + #ifdef STBIR_SIMD + + if ( width_times_channels >= 16 ) + { + float const * end_encode_m16 = encode + width_times_channels - 16; + end_output -= 16; + STBIR_SIMD_NO_UNROLL_LOOP_START_INF_FOR + for(;;) + { + stbir__simdf f0, f1, f2, f3; + stbir__simdi i0, i1, i2, i3; + STBIR_SIMD_NO_UNROLL(encode); + + stbir__simdf_load4_transposed( f0, f1, f2, f3, encode ); + + stbir__min_max_shift20( i0, f0 ); + stbir__min_max_shift20( i1, f1 ); + stbir__min_max_shift20( i2, f2 ); + stbir__min_max_shift20( i3, f3 ); + + stbir__simdi_table_lookup4( i0, i1, i2, i3, ( fp32_to_srgb8_tab4 - (127-13)*8 ) ); + + stbir__linear_to_srgb_finish( i0, f0 ); + stbir__linear_to_srgb_finish( i1, f1 ); + stbir__linear_to_srgb_finish( i2, f2 ); + stbir__linear_to_srgb_finish( i3, f3 ); + + stbir__interleave_pack_and_store_16_u8( output, STBIR_strs_join1(i, ,stbir__encode_order0), STBIR_strs_join1(i, ,stbir__encode_order1), STBIR_strs_join1(i, ,stbir__encode_order2), STBIR_strs_join1(i, ,stbir__encode_order3) ); + + encode += 16; + output += 16; + if ( output <= end_output ) + continue; + if ( output == ( end_output + 16 ) ) + break; + output = end_output; // backup and do last couple + encode = end_encode_m16; + } + return; + } + #endif + + // try to do blocks of 4 when you can + #if stbir__coder_min_num != 3 // doesn't divide cleanly by four + output += 4; + STBIR_SIMD_NO_UNROLL_LOOP_START + while ( output <= end_output ) + { + STBIR_SIMD_NO_UNROLL(encode); + + output[0-4] = stbir__linear_to_srgb_uchar( encode[stbir__encode_order0] ); + output[1-4] = stbir__linear_to_srgb_uchar( encode[stbir__encode_order1] ); + output[2-4] = stbir__linear_to_srgb_uchar( encode[stbir__encode_order2] ); + output[3-4] = stbir__linear_to_srgb_uchar( encode[stbir__encode_order3] ); + + output += 4; + encode += 4; + } + output -= 4; + #endif + + // do the remnants + #if stbir__coder_min_num < 4 + STBIR_NO_UNROLL_LOOP_START + while( output < end_output ) + { + STBIR_NO_UNROLL(encode); + output[0] = stbir__linear_to_srgb_uchar( encode[stbir__encode_order0] ); + #if stbir__coder_min_num >= 2 + output[1] = stbir__linear_to_srgb_uchar( encode[stbir__encode_order1] ); + #endif + #if stbir__coder_min_num >= 3 + output[2] = stbir__linear_to_srgb_uchar( encode[stbir__encode_order2] ); + #endif + output += stbir__coder_min_num; + encode += stbir__coder_min_num; + } + #endif +} + +#if ( stbir__coder_min_num == 4 ) || ( ( stbir__coder_min_num == 1 ) && ( !defined(stbir__decode_swizzle) ) ) + +static float * STBIR__CODER_NAME(stbir__decode_uint8_srgb4_linearalpha)( float * decodep, int width_times_channels, void const * inputp ) +{ + float STBIR_STREAMOUT_PTR( * ) decode = decodep; + float * decode_end = (float*) decode + width_times_channels; + unsigned char const * input = (unsigned char const *)inputp; + + do { + decode[0] = stbir__srgb_uchar_to_linear_float[ input[stbir__decode_order0] ]; + decode[1] = stbir__srgb_uchar_to_linear_float[ input[stbir__decode_order1] ]; + decode[2] = stbir__srgb_uchar_to_linear_float[ input[stbir__decode_order2] ]; + decode[3] = ( (float) input[stbir__decode_order3] ) * stbir__max_uint8_as_float_inverted; + input += 4; + decode += 4; + } while( decode < decode_end ); + return decode_end; +} + + +static void STBIR__CODER_NAME( stbir__encode_uint8_srgb4_linearalpha )( void * outputp, int width_times_channels, float const * encode ) +{ + unsigned char STBIR_SIMD_STREAMOUT_PTR( * ) output = (unsigned char*) outputp; + unsigned char * end_output = ( (unsigned char*) output ) + width_times_channels; + + #ifdef STBIR_SIMD + + if ( width_times_channels >= 16 ) + { + float const * end_encode_m16 = encode + width_times_channels - 16; + end_output -= 16; + STBIR_SIMD_NO_UNROLL_LOOP_START_INF_FOR + for(;;) + { + stbir__simdf f0, f1, f2, f3; + stbir__simdi i0, i1, i2, i3; + + STBIR_SIMD_NO_UNROLL(encode); + stbir__simdf_load4_transposed( f0, f1, f2, f3, encode ); + + stbir__min_max_shift20( i0, f0 ); + stbir__min_max_shift20( i1, f1 ); + stbir__min_max_shift20( i2, f2 ); + stbir__scale_and_convert( i3, f3 ); + + stbir__simdi_table_lookup3( i0, i1, i2, ( fp32_to_srgb8_tab4 - (127-13)*8 ) ); + + stbir__linear_to_srgb_finish( i0, f0 ); + stbir__linear_to_srgb_finish( i1, f1 ); + stbir__linear_to_srgb_finish( i2, f2 ); + + stbir__interleave_pack_and_store_16_u8( output, STBIR_strs_join1(i, ,stbir__encode_order0), STBIR_strs_join1(i, ,stbir__encode_order1), STBIR_strs_join1(i, ,stbir__encode_order2), STBIR_strs_join1(i, ,stbir__encode_order3) ); + + output += 16; + encode += 16; + + if ( output <= end_output ) + continue; + if ( output == ( end_output + 16 ) ) + break; + output = end_output; // backup and do last couple + encode = end_encode_m16; + } + return; + } + #endif + + STBIR_SIMD_NO_UNROLL_LOOP_START + do { + float f; + STBIR_SIMD_NO_UNROLL(encode); + + output[stbir__decode_order0] = stbir__linear_to_srgb_uchar( encode[0] ); + output[stbir__decode_order1] = stbir__linear_to_srgb_uchar( encode[1] ); + output[stbir__decode_order2] = stbir__linear_to_srgb_uchar( encode[2] ); + + f = encode[3] * stbir__max_uint8_as_float + 0.5f; + STBIR_CLAMP(f, 0, 255); + output[stbir__decode_order3] = (unsigned char) f; + + output += 4; + encode += 4; + } while( output < end_output ); +} + +#endif + +#if ( stbir__coder_min_num == 2 ) || ( ( stbir__coder_min_num == 1 ) && ( !defined(stbir__decode_swizzle) ) ) + +static float * STBIR__CODER_NAME(stbir__decode_uint8_srgb2_linearalpha)( float * decodep, int width_times_channels, void const * inputp ) +{ + float STBIR_STREAMOUT_PTR( * ) decode = decodep; + float * decode_end = (float*) decode + width_times_channels; + unsigned char const * input = (unsigned char const *)inputp; + + decode += 4; + while( decode <= decode_end ) + { + decode[0-4] = stbir__srgb_uchar_to_linear_float[ input[stbir__decode_order0] ]; + decode[1-4] = ( (float) input[stbir__decode_order1] ) * stbir__max_uint8_as_float_inverted; + decode[2-4] = stbir__srgb_uchar_to_linear_float[ input[stbir__decode_order0+2] ]; + decode[3-4] = ( (float) input[stbir__decode_order1+2] ) * stbir__max_uint8_as_float_inverted; + input += 4; + decode += 4; + } + decode -= 4; + if( decode < decode_end ) + { + decode[0] = stbir__srgb_uchar_to_linear_float[ input[stbir__decode_order0] ]; + decode[1] = ( (float) input[stbir__decode_order1] ) * stbir__max_uint8_as_float_inverted; + } + return decode_end; +} + +static void STBIR__CODER_NAME( stbir__encode_uint8_srgb2_linearalpha )( void * outputp, int width_times_channels, float const * encode ) +{ + unsigned char STBIR_SIMD_STREAMOUT_PTR( * ) output = (unsigned char*) outputp; + unsigned char * end_output = ( (unsigned char*) output ) + width_times_channels; + + #ifdef STBIR_SIMD + + if ( width_times_channels >= 16 ) + { + float const * end_encode_m16 = encode + width_times_channels - 16; + end_output -= 16; + STBIR_SIMD_NO_UNROLL_LOOP_START_INF_FOR + for(;;) + { + stbir__simdf f0, f1, f2, f3; + stbir__simdi i0, i1, i2, i3; + + STBIR_SIMD_NO_UNROLL(encode); + stbir__simdf_load4_transposed( f0, f1, f2, f3, encode ); + + stbir__min_max_shift20( i0, f0 ); + stbir__scale_and_convert( i1, f1 ); + stbir__min_max_shift20( i2, f2 ); + stbir__scale_and_convert( i3, f3 ); + + stbir__simdi_table_lookup2( i0, i2, ( fp32_to_srgb8_tab4 - (127-13)*8 ) ); + + stbir__linear_to_srgb_finish( i0, f0 ); + stbir__linear_to_srgb_finish( i2, f2 ); + + stbir__interleave_pack_and_store_16_u8( output, STBIR_strs_join1(i, ,stbir__encode_order0), STBIR_strs_join1(i, ,stbir__encode_order1), STBIR_strs_join1(i, ,stbir__encode_order2), STBIR_strs_join1(i, ,stbir__encode_order3) ); + + output += 16; + encode += 16; + if ( output <= end_output ) + continue; + if ( output == ( end_output + 16 ) ) + break; + output = end_output; // backup and do last couple + encode = end_encode_m16; + } + return; + } + #endif + + STBIR_SIMD_NO_UNROLL_LOOP_START + do { + float f; + STBIR_SIMD_NO_UNROLL(encode); + + output[stbir__decode_order0] = stbir__linear_to_srgb_uchar( encode[0] ); + + f = encode[1] * stbir__max_uint8_as_float + 0.5f; + STBIR_CLAMP(f, 0, 255); + output[stbir__decode_order1] = (unsigned char) f; + + output += 2; + encode += 2; + } while( output < end_output ); +} + +#endif + +static float * STBIR__CODER_NAME(stbir__decode_uint16_linear_scaled)( float * decodep, int width_times_channels, void const * inputp ) +{ + float STBIR_STREAMOUT_PTR( * ) decode = decodep; + float * decode_end = (float*) decode + width_times_channels; + unsigned short const * input = (unsigned short const *)inputp; + + #ifdef STBIR_SIMD + unsigned short const * end_input_m8 = input + width_times_channels - 8; + if ( width_times_channels >= 8 ) + { + decode_end -= 8; + STBIR_NO_UNROLL_LOOP_START_INF_FOR + for(;;) + { + #ifdef STBIR_SIMD8 + stbir__simdi i; stbir__simdi8 o; + stbir__simdf8 of; + STBIR_NO_UNROLL(decode); + stbir__simdi_load( i, input ); + stbir__simdi8_expand_u16_to_u32( o, i ); + stbir__simdi8_convert_i32_to_float( of, o ); + stbir__simdf8_mult( of, of, STBIR_max_uint16_as_float_inverted8); + stbir__decode_simdf8_flip( of ); + stbir__simdf8_store( decode + 0, of ); + #else + stbir__simdi i, o0, o1; + stbir__simdf of0, of1; + STBIR_NO_UNROLL(decode); + stbir__simdi_load( i, input ); + stbir__simdi_expand_u16_to_u32( o0,o1,i ); + stbir__simdi_convert_i32_to_float( of0, o0 ); + stbir__simdi_convert_i32_to_float( of1, o1 ); + stbir__simdf_mult( of0, of0, STBIR__CONSTF(STBIR_max_uint16_as_float_inverted) ); + stbir__simdf_mult( of1, of1, STBIR__CONSTF(STBIR_max_uint16_as_float_inverted)); + stbir__decode_simdf4_flip( of0 ); + stbir__decode_simdf4_flip( of1 ); + stbir__simdf_store( decode + 0, of0 ); + stbir__simdf_store( decode + 4, of1 ); + #endif + decode += 8; + input += 8; + if ( decode <= decode_end ) + continue; + if ( decode == ( decode_end + 8 ) ) + break; + decode = decode_end; // backup and do last couple + input = end_input_m8; + } + return decode_end + 8; + } + #endif + + // try to do blocks of 4 when you can + #if stbir__coder_min_num != 3 // doesn't divide cleanly by four + decode += 4; + STBIR_SIMD_NO_UNROLL_LOOP_START + while( decode <= decode_end ) + { + STBIR_SIMD_NO_UNROLL(decode); + decode[0-4] = ((float)(input[stbir__decode_order0])) * stbir__max_uint16_as_float_inverted; + decode[1-4] = ((float)(input[stbir__decode_order1])) * stbir__max_uint16_as_float_inverted; + decode[2-4] = ((float)(input[stbir__decode_order2])) * stbir__max_uint16_as_float_inverted; + decode[3-4] = ((float)(input[stbir__decode_order3])) * stbir__max_uint16_as_float_inverted; + decode += 4; + input += 4; + } + decode -= 4; + #endif + + // do the remnants + #if stbir__coder_min_num < 4 + STBIR_NO_UNROLL_LOOP_START + while( decode < decode_end ) + { + STBIR_NO_UNROLL(decode); + decode[0] = ((float)(input[stbir__decode_order0])) * stbir__max_uint16_as_float_inverted; + #if stbir__coder_min_num >= 2 + decode[1] = ((float)(input[stbir__decode_order1])) * stbir__max_uint16_as_float_inverted; + #endif + #if stbir__coder_min_num >= 3 + decode[2] = ((float)(input[stbir__decode_order2])) * stbir__max_uint16_as_float_inverted; + #endif + decode += stbir__coder_min_num; + input += stbir__coder_min_num; + } + #endif + return decode_end; +} + + +static void STBIR__CODER_NAME(stbir__encode_uint16_linear_scaled)( void * outputp, int width_times_channels, float const * encode ) +{ + unsigned short STBIR_SIMD_STREAMOUT_PTR( * ) output = (unsigned short*) outputp; + unsigned short * end_output = ( (unsigned short*) output ) + width_times_channels; + + #ifdef STBIR_SIMD + { + if ( width_times_channels >= stbir__simdfX_float_count*2 ) + { + float const * end_encode_m8 = encode + width_times_channels - stbir__simdfX_float_count*2; + end_output -= stbir__simdfX_float_count*2; + STBIR_SIMD_NO_UNROLL_LOOP_START_INF_FOR + for(;;) + { + stbir__simdfX e0, e1; + stbir__simdiX i; + STBIR_SIMD_NO_UNROLL(encode); + stbir__simdfX_madd_mem( e0, STBIR_simd_point5X, STBIR_max_uint16_as_floatX, encode ); + stbir__simdfX_madd_mem( e1, STBIR_simd_point5X, STBIR_max_uint16_as_floatX, encode+stbir__simdfX_float_count ); + stbir__encode_simdfX_unflip( e0 ); + stbir__encode_simdfX_unflip( e1 ); + stbir__simdfX_pack_to_words( i, e0, e1 ); + stbir__simdiX_store( output, i ); + encode += stbir__simdfX_float_count*2; + output += stbir__simdfX_float_count*2; + if ( output <= end_output ) + continue; + if ( output == ( end_output + stbir__simdfX_float_count*2 ) ) + break; + output = end_output; // backup and do last couple + encode = end_encode_m8; + } + return; + } + } + + // try to do blocks of 4 when you can + #if stbir__coder_min_num != 3 // doesn't divide cleanly by four + output += 4; + STBIR_NO_UNROLL_LOOP_START + while( output <= end_output ) + { + stbir__simdf e; + stbir__simdi i; + STBIR_NO_UNROLL(encode); + stbir__simdf_load( e, encode ); + stbir__simdf_madd( e, STBIR__CONSTF(STBIR_simd_point5), STBIR__CONSTF(STBIR_max_uint16_as_float), e ); + stbir__encode_simdf4_unflip( e ); + stbir__simdf_pack_to_8words( i, e, e ); // only use first 4 + stbir__simdi_store2( output-4, i ); + output += 4; + encode += 4; + } + output -= 4; + #endif + + // do the remnants + #if stbir__coder_min_num < 4 + STBIR_NO_UNROLL_LOOP_START + while( output < end_output ) + { + stbir__simdf e; + STBIR_NO_UNROLL(encode); + stbir__simdf_madd1_mem( e, STBIR__CONSTF(STBIR_simd_point5), STBIR__CONSTF(STBIR_max_uint16_as_float), encode+stbir__encode_order0 ); output[0] = stbir__simdf_convert_float_to_short( e ); + #if stbir__coder_min_num >= 2 + stbir__simdf_madd1_mem( e, STBIR__CONSTF(STBIR_simd_point5), STBIR__CONSTF(STBIR_max_uint16_as_float), encode+stbir__encode_order1 ); output[1] = stbir__simdf_convert_float_to_short( e ); + #endif + #if stbir__coder_min_num >= 3 + stbir__simdf_madd1_mem( e, STBIR__CONSTF(STBIR_simd_point5), STBIR__CONSTF(STBIR_max_uint16_as_float), encode+stbir__encode_order2 ); output[2] = stbir__simdf_convert_float_to_short( e ); + #endif + output += stbir__coder_min_num; + encode += stbir__coder_min_num; + } + #endif + + #else + + // try to do blocks of 4 when you can + #if stbir__coder_min_num != 3 // doesn't divide cleanly by four + output += 4; + STBIR_SIMD_NO_UNROLL_LOOP_START + while( output <= end_output ) + { + float f; + STBIR_SIMD_NO_UNROLL(encode); + f = encode[stbir__encode_order0] * stbir__max_uint16_as_float + 0.5f; STBIR_CLAMP(f, 0, 65535); output[0-4] = (unsigned short)f; + f = encode[stbir__encode_order1] * stbir__max_uint16_as_float + 0.5f; STBIR_CLAMP(f, 0, 65535); output[1-4] = (unsigned short)f; + f = encode[stbir__encode_order2] * stbir__max_uint16_as_float + 0.5f; STBIR_CLAMP(f, 0, 65535); output[2-4] = (unsigned short)f; + f = encode[stbir__encode_order3] * stbir__max_uint16_as_float + 0.5f; STBIR_CLAMP(f, 0, 65535); output[3-4] = (unsigned short)f; + output += 4; + encode += 4; + } + output -= 4; + #endif + + // do the remnants + #if stbir__coder_min_num < 4 + STBIR_NO_UNROLL_LOOP_START + while( output < end_output ) + { + float f; + STBIR_NO_UNROLL(encode); + f = encode[stbir__encode_order0] * stbir__max_uint16_as_float + 0.5f; STBIR_CLAMP(f, 0, 65535); output[0] = (unsigned short)f; + #if stbir__coder_min_num >= 2 + f = encode[stbir__encode_order1] * stbir__max_uint16_as_float + 0.5f; STBIR_CLAMP(f, 0, 65535); output[1] = (unsigned short)f; + #endif + #if stbir__coder_min_num >= 3 + f = encode[stbir__encode_order2] * stbir__max_uint16_as_float + 0.5f; STBIR_CLAMP(f, 0, 65535); output[2] = (unsigned short)f; + #endif + output += stbir__coder_min_num; + encode += stbir__coder_min_num; + } + #endif + #endif +} + +static float * STBIR__CODER_NAME(stbir__decode_uint16_linear)( float * decodep, int width_times_channels, void const * inputp ) +{ + float STBIR_STREAMOUT_PTR( * ) decode = decodep; + float * decode_end = (float*) decode + width_times_channels; + unsigned short const * input = (unsigned short const *)inputp; + + #ifdef STBIR_SIMD + unsigned short const * end_input_m8 = input + width_times_channels - 8; + if ( width_times_channels >= 8 ) + { + decode_end -= 8; + STBIR_NO_UNROLL_LOOP_START_INF_FOR + for(;;) + { + #ifdef STBIR_SIMD8 + stbir__simdi i; stbir__simdi8 o; + stbir__simdf8 of; + STBIR_NO_UNROLL(decode); + stbir__simdi_load( i, input ); + stbir__simdi8_expand_u16_to_u32( o, i ); + stbir__simdi8_convert_i32_to_float( of, o ); + stbir__decode_simdf8_flip( of ); + stbir__simdf8_store( decode + 0, of ); + #else + stbir__simdi i, o0, o1; + stbir__simdf of0, of1; + STBIR_NO_UNROLL(decode); + stbir__simdi_load( i, input ); + stbir__simdi_expand_u16_to_u32( o0, o1, i ); + stbir__simdi_convert_i32_to_float( of0, o0 ); + stbir__simdi_convert_i32_to_float( of1, o1 ); + stbir__decode_simdf4_flip( of0 ); + stbir__decode_simdf4_flip( of1 ); + stbir__simdf_store( decode + 0, of0 ); + stbir__simdf_store( decode + 4, of1 ); + #endif + decode += 8; + input += 8; + if ( decode <= decode_end ) + continue; + if ( decode == ( decode_end + 8 ) ) + break; + decode = decode_end; // backup and do last couple + input = end_input_m8; + } + return decode_end + 8; + } + #endif + + // try to do blocks of 4 when you can + #if stbir__coder_min_num != 3 // doesn't divide cleanly by four + decode += 4; + STBIR_SIMD_NO_UNROLL_LOOP_START + while( decode <= decode_end ) + { + STBIR_SIMD_NO_UNROLL(decode); + decode[0-4] = ((float)(input[stbir__decode_order0])); + decode[1-4] = ((float)(input[stbir__decode_order1])); + decode[2-4] = ((float)(input[stbir__decode_order2])); + decode[3-4] = ((float)(input[stbir__decode_order3])); + decode += 4; + input += 4; + } + decode -= 4; + #endif + + // do the remnants + #if stbir__coder_min_num < 4 + STBIR_NO_UNROLL_LOOP_START + while( decode < decode_end ) + { + STBIR_NO_UNROLL(decode); + decode[0] = ((float)(input[stbir__decode_order0])); + #if stbir__coder_min_num >= 2 + decode[1] = ((float)(input[stbir__decode_order1])); + #endif + #if stbir__coder_min_num >= 3 + decode[2] = ((float)(input[stbir__decode_order2])); + #endif + decode += stbir__coder_min_num; + input += stbir__coder_min_num; + } + #endif + return decode_end; +} + +static void STBIR__CODER_NAME(stbir__encode_uint16_linear)( void * outputp, int width_times_channels, float const * encode ) +{ + unsigned short STBIR_SIMD_STREAMOUT_PTR( * ) output = (unsigned short*) outputp; + unsigned short * end_output = ( (unsigned short*) output ) + width_times_channels; + + #ifdef STBIR_SIMD + { + if ( width_times_channels >= stbir__simdfX_float_count*2 ) + { + float const * end_encode_m8 = encode + width_times_channels - stbir__simdfX_float_count*2; + end_output -= stbir__simdfX_float_count*2; + STBIR_SIMD_NO_UNROLL_LOOP_START_INF_FOR + for(;;) + { + stbir__simdfX e0, e1; + stbir__simdiX i; + STBIR_SIMD_NO_UNROLL(encode); + stbir__simdfX_add_mem( e0, STBIR_simd_point5X, encode ); + stbir__simdfX_add_mem( e1, STBIR_simd_point5X, encode+stbir__simdfX_float_count ); + stbir__encode_simdfX_unflip( e0 ); + stbir__encode_simdfX_unflip( e1 ); + stbir__simdfX_pack_to_words( i, e0, e1 ); + stbir__simdiX_store( output, i ); + encode += stbir__simdfX_float_count*2; + output += stbir__simdfX_float_count*2; + if ( output <= end_output ) + continue; + if ( output == ( end_output + stbir__simdfX_float_count*2 ) ) + break; + output = end_output; // backup and do last couple + encode = end_encode_m8; + } + return; + } + } + + // try to do blocks of 4 when you can + #if stbir__coder_min_num != 3 // doesn't divide cleanly by four + output += 4; + STBIR_NO_UNROLL_LOOP_START + while( output <= end_output ) + { + stbir__simdf e; + stbir__simdi i; + STBIR_NO_UNROLL(encode); + stbir__simdf_load( e, encode ); + stbir__simdf_add( e, STBIR__CONSTF(STBIR_simd_point5), e ); + stbir__encode_simdf4_unflip( e ); + stbir__simdf_pack_to_8words( i, e, e ); // only use first 4 + stbir__simdi_store2( output-4, i ); + output += 4; + encode += 4; + } + output -= 4; + #endif + + #else + + // try to do blocks of 4 when you can + #if stbir__coder_min_num != 3 // doesn't divide cleanly by four + output += 4; + STBIR_SIMD_NO_UNROLL_LOOP_START + while( output <= end_output ) + { + float f; + STBIR_SIMD_NO_UNROLL(encode); + f = encode[stbir__encode_order0] + 0.5f; STBIR_CLAMP(f, 0, 65535); output[0-4] = (unsigned short)f; + f = encode[stbir__encode_order1] + 0.5f; STBIR_CLAMP(f, 0, 65535); output[1-4] = (unsigned short)f; + f = encode[stbir__encode_order2] + 0.5f; STBIR_CLAMP(f, 0, 65535); output[2-4] = (unsigned short)f; + f = encode[stbir__encode_order3] + 0.5f; STBIR_CLAMP(f, 0, 65535); output[3-4] = (unsigned short)f; + output += 4; + encode += 4; + } + output -= 4; + #endif + + #endif + + // do the remnants + #if stbir__coder_min_num < 4 + STBIR_NO_UNROLL_LOOP_START + while( output < end_output ) + { + float f; + STBIR_NO_UNROLL(encode); + f = encode[stbir__encode_order0] + 0.5f; STBIR_CLAMP(f, 0, 65535); output[0] = (unsigned short)f; + #if stbir__coder_min_num >= 2 + f = encode[stbir__encode_order1] + 0.5f; STBIR_CLAMP(f, 0, 65535); output[1] = (unsigned short)f; + #endif + #if stbir__coder_min_num >= 3 + f = encode[stbir__encode_order2] + 0.5f; STBIR_CLAMP(f, 0, 65535); output[2] = (unsigned short)f; + #endif + output += stbir__coder_min_num; + encode += stbir__coder_min_num; + } + #endif +} + +static float * STBIR__CODER_NAME(stbir__decode_half_float_linear)( float * decodep, int width_times_channels, void const * inputp ) +{ + float STBIR_STREAMOUT_PTR( * ) decode = decodep; + float * decode_end = (float*) decode + width_times_channels; + stbir__FP16 const * input = (stbir__FP16 const *)inputp; + + #ifdef STBIR_SIMD + if ( width_times_channels >= 8 ) + { + stbir__FP16 const * end_input_m8 = input + width_times_channels - 8; + decode_end -= 8; + STBIR_NO_UNROLL_LOOP_START_INF_FOR + for(;;) + { + STBIR_NO_UNROLL(decode); + + stbir__half_to_float_SIMD( decode, input ); + #ifdef stbir__decode_swizzle + #ifdef STBIR_SIMD8 + { + stbir__simdf8 of; + stbir__simdf8_load( of, decode ); + stbir__decode_simdf8_flip( of ); + stbir__simdf8_store( decode, of ); + } + #else + { + stbir__simdf of0,of1; + stbir__simdf_load( of0, decode ); + stbir__simdf_load( of1, decode+4 ); + stbir__decode_simdf4_flip( of0 ); + stbir__decode_simdf4_flip( of1 ); + stbir__simdf_store( decode, of0 ); + stbir__simdf_store( decode+4, of1 ); + } + #endif + #endif + decode += 8; + input += 8; + if ( decode <= decode_end ) + continue; + if ( decode == ( decode_end + 8 ) ) + break; + decode = decode_end; // backup and do last couple + input = end_input_m8; + } + return decode_end + 8; + } + #endif + + // try to do blocks of 4 when you can + #if stbir__coder_min_num != 3 // doesn't divide cleanly by four + decode += 4; + STBIR_SIMD_NO_UNROLL_LOOP_START + while( decode <= decode_end ) + { + STBIR_SIMD_NO_UNROLL(decode); + decode[0-4] = stbir__half_to_float(input[stbir__decode_order0]); + decode[1-4] = stbir__half_to_float(input[stbir__decode_order1]); + decode[2-4] = stbir__half_to_float(input[stbir__decode_order2]); + decode[3-4] = stbir__half_to_float(input[stbir__decode_order3]); + decode += 4; + input += 4; + } + decode -= 4; + #endif + + // do the remnants + #if stbir__coder_min_num < 4 + STBIR_NO_UNROLL_LOOP_START + while( decode < decode_end ) + { + STBIR_NO_UNROLL(decode); + decode[0] = stbir__half_to_float(input[stbir__decode_order0]); + #if stbir__coder_min_num >= 2 + decode[1] = stbir__half_to_float(input[stbir__decode_order1]); + #endif + #if stbir__coder_min_num >= 3 + decode[2] = stbir__half_to_float(input[stbir__decode_order2]); + #endif + decode += stbir__coder_min_num; + input += stbir__coder_min_num; + } + #endif + return decode_end; +} + +static void STBIR__CODER_NAME( stbir__encode_half_float_linear )( void * outputp, int width_times_channels, float const * encode ) +{ + stbir__FP16 STBIR_SIMD_STREAMOUT_PTR( * ) output = (stbir__FP16*) outputp; + stbir__FP16 * end_output = ( (stbir__FP16*) output ) + width_times_channels; + + #ifdef STBIR_SIMD + if ( width_times_channels >= 8 ) + { + float const * end_encode_m8 = encode + width_times_channels - 8; + end_output -= 8; + STBIR_SIMD_NO_UNROLL_LOOP_START_INF_FOR + for(;;) + { + STBIR_SIMD_NO_UNROLL(encode); + #ifdef stbir__decode_swizzle + #ifdef STBIR_SIMD8 + { + stbir__simdf8 of; + stbir__simdf8_load( of, encode ); + stbir__encode_simdf8_unflip( of ); + stbir__float_to_half_SIMD( output, (float*)&of ); + } + #else + { + stbir__simdf of[2]; + stbir__simdf_load( of[0], encode ); + stbir__simdf_load( of[1], encode+4 ); + stbir__encode_simdf4_unflip( of[0] ); + stbir__encode_simdf4_unflip( of[1] ); + stbir__float_to_half_SIMD( output, (float*)of ); + } + #endif + #else + stbir__float_to_half_SIMD( output, encode ); + #endif + encode += 8; + output += 8; + if ( output <= end_output ) + continue; + if ( output == ( end_output + 8 ) ) + break; + output = end_output; // backup and do last couple + encode = end_encode_m8; + } + return; + } + #endif + + // try to do blocks of 4 when you can + #if stbir__coder_min_num != 3 // doesn't divide cleanly by four + output += 4; + STBIR_SIMD_NO_UNROLL_LOOP_START + while( output <= end_output ) + { + STBIR_SIMD_NO_UNROLL(output); + output[0-4] = stbir__float_to_half(encode[stbir__encode_order0]); + output[1-4] = stbir__float_to_half(encode[stbir__encode_order1]); + output[2-4] = stbir__float_to_half(encode[stbir__encode_order2]); + output[3-4] = stbir__float_to_half(encode[stbir__encode_order3]); + output += 4; + encode += 4; + } + output -= 4; + #endif + + // do the remnants + #if stbir__coder_min_num < 4 + STBIR_NO_UNROLL_LOOP_START + while( output < end_output ) + { + STBIR_NO_UNROLL(output); + output[0] = stbir__float_to_half(encode[stbir__encode_order0]); + #if stbir__coder_min_num >= 2 + output[1] = stbir__float_to_half(encode[stbir__encode_order1]); + #endif + #if stbir__coder_min_num >= 3 + output[2] = stbir__float_to_half(encode[stbir__encode_order2]); + #endif + output += stbir__coder_min_num; + encode += stbir__coder_min_num; + } + #endif +} + +static float * STBIR__CODER_NAME(stbir__decode_float_linear)( float * decodep, int width_times_channels, void const * inputp ) +{ + #ifdef stbir__decode_swizzle + float STBIR_STREAMOUT_PTR( * ) decode = decodep; + float * decode_end = (float*) decode + width_times_channels; + float const * input = (float const *)inputp; + + #ifdef STBIR_SIMD + if ( width_times_channels >= 16 ) + { + float const * end_input_m16 = input + width_times_channels - 16; + decode_end -= 16; + STBIR_NO_UNROLL_LOOP_START_INF_FOR + for(;;) + { + STBIR_NO_UNROLL(decode); + #ifdef stbir__decode_swizzle + #ifdef STBIR_SIMD8 + { + stbir__simdf8 of0,of1; + stbir__simdf8_load( of0, input ); + stbir__simdf8_load( of1, input+8 ); + stbir__decode_simdf8_flip( of0 ); + stbir__decode_simdf8_flip( of1 ); + stbir__simdf8_store( decode, of0 ); + stbir__simdf8_store( decode+8, of1 ); + } + #else + { + stbir__simdf of0,of1,of2,of3; + stbir__simdf_load( of0, input ); + stbir__simdf_load( of1, input+4 ); + stbir__simdf_load( of2, input+8 ); + stbir__simdf_load( of3, input+12 ); + stbir__decode_simdf4_flip( of0 ); + stbir__decode_simdf4_flip( of1 ); + stbir__decode_simdf4_flip( of2 ); + stbir__decode_simdf4_flip( of3 ); + stbir__simdf_store( decode, of0 ); + stbir__simdf_store( decode+4, of1 ); + stbir__simdf_store( decode+8, of2 ); + stbir__simdf_store( decode+12, of3 ); + } + #endif + #endif + decode += 16; + input += 16; + if ( decode <= decode_end ) + continue; + if ( decode == ( decode_end + 16 ) ) + break; + decode = decode_end; // backup and do last couple + input = end_input_m16; + } + return decode_end + 16; + } + #endif + + // try to do blocks of 4 when you can + #if stbir__coder_min_num != 3 // doesn't divide cleanly by four + decode += 4; + STBIR_SIMD_NO_UNROLL_LOOP_START + while( decode <= decode_end ) + { + STBIR_SIMD_NO_UNROLL(decode); + decode[0-4] = input[stbir__decode_order0]; + decode[1-4] = input[stbir__decode_order1]; + decode[2-4] = input[stbir__decode_order2]; + decode[3-4] = input[stbir__decode_order3]; + decode += 4; + input += 4; + } + decode -= 4; + #endif + + // do the remnants + #if stbir__coder_min_num < 4 + STBIR_NO_UNROLL_LOOP_START + while( decode < decode_end ) + { + STBIR_NO_UNROLL(decode); + decode[0] = input[stbir__decode_order0]; + #if stbir__coder_min_num >= 2 + decode[1] = input[stbir__decode_order1]; + #endif + #if stbir__coder_min_num >= 3 + decode[2] = input[stbir__decode_order2]; + #endif + decode += stbir__coder_min_num; + input += stbir__coder_min_num; + } + #endif + return decode_end; + + #else + + if ( (void*)decodep != inputp ) + STBIR_MEMCPY( decodep, inputp, width_times_channels * sizeof( float ) ); + + return decodep + width_times_channels; + + #endif +} + +static void STBIR__CODER_NAME( stbir__encode_float_linear )( void * outputp, int width_times_channels, float const * encode ) +{ + #if !defined( STBIR_FLOAT_HIGH_CLAMP ) && !defined(STBIR_FLOAT_LOW_CLAMP) && !defined(stbir__decode_swizzle) + + if ( (void*)outputp != (void*) encode ) + STBIR_MEMCPY( outputp, encode, width_times_channels * sizeof( float ) ); + + #else + + float STBIR_SIMD_STREAMOUT_PTR( * ) output = (float*) outputp; + float * end_output = ( (float*) output ) + width_times_channels; + + #ifdef STBIR_FLOAT_HIGH_CLAMP + #define stbir_scalar_hi_clamp( v ) if ( v > STBIR_FLOAT_HIGH_CLAMP ) v = STBIR_FLOAT_HIGH_CLAMP; + #else + #define stbir_scalar_hi_clamp( v ) + #endif + #ifdef STBIR_FLOAT_LOW_CLAMP + #define stbir_scalar_lo_clamp( v ) if ( v < STBIR_FLOAT_LOW_CLAMP ) v = STBIR_FLOAT_LOW_CLAMP; + #else + #define stbir_scalar_lo_clamp( v ) + #endif + + #ifdef STBIR_SIMD + + #ifdef STBIR_FLOAT_HIGH_CLAMP + const stbir__simdfX high_clamp = stbir__simdf_frepX(STBIR_FLOAT_HIGH_CLAMP); + #endif + #ifdef STBIR_FLOAT_LOW_CLAMP + const stbir__simdfX low_clamp = stbir__simdf_frepX(STBIR_FLOAT_LOW_CLAMP); + #endif + + if ( width_times_channels >= ( stbir__simdfX_float_count * 2 ) ) + { + float const * end_encode_m8 = encode + width_times_channels - ( stbir__simdfX_float_count * 2 ); + end_output -= ( stbir__simdfX_float_count * 2 ); + STBIR_SIMD_NO_UNROLL_LOOP_START_INF_FOR + for(;;) + { + stbir__simdfX e0, e1; + STBIR_SIMD_NO_UNROLL(encode); + stbir__simdfX_load( e0, encode ); + stbir__simdfX_load( e1, encode+stbir__simdfX_float_count ); +#ifdef STBIR_FLOAT_HIGH_CLAMP + stbir__simdfX_min( e0, e0, high_clamp ); + stbir__simdfX_min( e1, e1, high_clamp ); +#endif +#ifdef STBIR_FLOAT_LOW_CLAMP + stbir__simdfX_max( e0, e0, low_clamp ); + stbir__simdfX_max( e1, e1, low_clamp ); +#endif + stbir__encode_simdfX_unflip( e0 ); + stbir__encode_simdfX_unflip( e1 ); + stbir__simdfX_store( output, e0 ); + stbir__simdfX_store( output+stbir__simdfX_float_count, e1 ); + encode += stbir__simdfX_float_count * 2; + output += stbir__simdfX_float_count * 2; + if ( output <= end_output ) + continue; + if ( output == ( end_output + ( stbir__simdfX_float_count * 2 ) ) ) + break; + output = end_output; // backup and do last couple + encode = end_encode_m8; + } + return; + } + + // try to do blocks of 4 when you can + #if stbir__coder_min_num != 3 // doesn't divide cleanly by four + output += 4; + STBIR_NO_UNROLL_LOOP_START + while( output <= end_output ) + { + stbir__simdf e0; + STBIR_NO_UNROLL(encode); + stbir__simdf_load( e0, encode ); +#ifdef STBIR_FLOAT_HIGH_CLAMP + stbir__simdf_min( e0, e0, high_clamp ); +#endif +#ifdef STBIR_FLOAT_LOW_CLAMP + stbir__simdf_max( e0, e0, low_clamp ); +#endif + stbir__encode_simdf4_unflip( e0 ); + stbir__simdf_store( output-4, e0 ); + output += 4; + encode += 4; + } + output -= 4; + #endif + + #else + + // try to do blocks of 4 when you can + #if stbir__coder_min_num != 3 // doesn't divide cleanly by four + output += 4; + STBIR_SIMD_NO_UNROLL_LOOP_START + while( output <= end_output ) + { + float e; + STBIR_SIMD_NO_UNROLL(encode); + e = encode[ stbir__encode_order0 ]; stbir_scalar_hi_clamp( e ); stbir_scalar_lo_clamp( e ); output[0-4] = e; + e = encode[ stbir__encode_order1 ]; stbir_scalar_hi_clamp( e ); stbir_scalar_lo_clamp( e ); output[1-4] = e; + e = encode[ stbir__encode_order2 ]; stbir_scalar_hi_clamp( e ); stbir_scalar_lo_clamp( e ); output[2-4] = e; + e = encode[ stbir__encode_order3 ]; stbir_scalar_hi_clamp( e ); stbir_scalar_lo_clamp( e ); output[3-4] = e; + output += 4; + encode += 4; + } + output -= 4; + + #endif + + #endif + + // do the remnants + #if stbir__coder_min_num < 4 + STBIR_NO_UNROLL_LOOP_START + while( output < end_output ) + { + float e; + STBIR_NO_UNROLL(encode); + e = encode[ stbir__encode_order0 ]; stbir_scalar_hi_clamp( e ); stbir_scalar_lo_clamp( e ); output[0] = e; + #if stbir__coder_min_num >= 2 + e = encode[ stbir__encode_order1 ]; stbir_scalar_hi_clamp( e ); stbir_scalar_lo_clamp( e ); output[1] = e; + #endif + #if stbir__coder_min_num >= 3 + e = encode[ stbir__encode_order2 ]; stbir_scalar_hi_clamp( e ); stbir_scalar_lo_clamp( e ); output[2] = e; + #endif + output += stbir__coder_min_num; + encode += stbir__coder_min_num; + } + #endif + + #endif +} + +#undef stbir__decode_suffix +#undef stbir__decode_simdf8_flip +#undef stbir__decode_simdf4_flip +#undef stbir__decode_order0 +#undef stbir__decode_order1 +#undef stbir__decode_order2 +#undef stbir__decode_order3 +#undef stbir__encode_order0 +#undef stbir__encode_order1 +#undef stbir__encode_order2 +#undef stbir__encode_order3 +#undef stbir__encode_simdf8_unflip +#undef stbir__encode_simdf4_unflip +#undef stbir__encode_simdfX_unflip +#undef STBIR__CODER_NAME +#undef stbir__coder_min_num +#undef stbir__decode_swizzle +#undef stbir_scalar_hi_clamp +#undef stbir_scalar_lo_clamp +#undef STB_IMAGE_RESIZE_DO_CODERS + +#elif defined( STB_IMAGE_RESIZE_DO_VERTICALS) + +#ifdef STB_IMAGE_RESIZE_VERTICAL_CONTINUE +#define STBIR_chans( start, end ) STBIR_strs_join14(start,STBIR__vertical_channels,end,_cont) +#else +#define STBIR_chans( start, end ) STBIR_strs_join1(start,STBIR__vertical_channels,end) +#endif + +#if STBIR__vertical_channels >= 1 +#define stbIF0( code ) code +#else +#define stbIF0( code ) +#endif +#if STBIR__vertical_channels >= 2 +#define stbIF1( code ) code +#else +#define stbIF1( code ) +#endif +#if STBIR__vertical_channels >= 3 +#define stbIF2( code ) code +#else +#define stbIF2( code ) +#endif +#if STBIR__vertical_channels >= 4 +#define stbIF3( code ) code +#else +#define stbIF3( code ) +#endif +#if STBIR__vertical_channels >= 5 +#define stbIF4( code ) code +#else +#define stbIF4( code ) +#endif +#if STBIR__vertical_channels >= 6 +#define stbIF5( code ) code +#else +#define stbIF5( code ) +#endif +#if STBIR__vertical_channels >= 7 +#define stbIF6( code ) code +#else +#define stbIF6( code ) +#endif +#if STBIR__vertical_channels >= 8 +#define stbIF7( code ) code +#else +#define stbIF7( code ) +#endif + +static void STBIR_chans( stbir__vertical_scatter_with_,_coeffs)( float ** outputs, float const * vertical_coefficients, float const * input, float const * input_end ) +{ + stbIF0( float STBIR_SIMD_STREAMOUT_PTR( * ) output0 = outputs[0]; float c0s = vertical_coefficients[0]; ) + stbIF1( float STBIR_SIMD_STREAMOUT_PTR( * ) output1 = outputs[1]; float c1s = vertical_coefficients[1]; ) + stbIF2( float STBIR_SIMD_STREAMOUT_PTR( * ) output2 = outputs[2]; float c2s = vertical_coefficients[2]; ) + stbIF3( float STBIR_SIMD_STREAMOUT_PTR( * ) output3 = outputs[3]; float c3s = vertical_coefficients[3]; ) + stbIF4( float STBIR_SIMD_STREAMOUT_PTR( * ) output4 = outputs[4]; float c4s = vertical_coefficients[4]; ) + stbIF5( float STBIR_SIMD_STREAMOUT_PTR( * ) output5 = outputs[5]; float c5s = vertical_coefficients[5]; ) + stbIF6( float STBIR_SIMD_STREAMOUT_PTR( * ) output6 = outputs[6]; float c6s = vertical_coefficients[6]; ) + stbIF7( float STBIR_SIMD_STREAMOUT_PTR( * ) output7 = outputs[7]; float c7s = vertical_coefficients[7]; ) + + #ifdef STBIR_SIMD + { + stbIF0(stbir__simdfX c0 = stbir__simdf_frepX( c0s ); ) + stbIF1(stbir__simdfX c1 = stbir__simdf_frepX( c1s ); ) + stbIF2(stbir__simdfX c2 = stbir__simdf_frepX( c2s ); ) + stbIF3(stbir__simdfX c3 = stbir__simdf_frepX( c3s ); ) + stbIF4(stbir__simdfX c4 = stbir__simdf_frepX( c4s ); ) + stbIF5(stbir__simdfX c5 = stbir__simdf_frepX( c5s ); ) + stbIF6(stbir__simdfX c6 = stbir__simdf_frepX( c6s ); ) + stbIF7(stbir__simdfX c7 = stbir__simdf_frepX( c7s ); ) + STBIR_SIMD_NO_UNROLL_LOOP_START + while ( ( (char*)input_end - (char*) input ) >= (16*stbir__simdfX_float_count) ) + { + stbir__simdfX o0, o1, o2, o3, r0, r1, r2, r3; + STBIR_SIMD_NO_UNROLL(output0); + + stbir__simdfX_load( r0, input ); stbir__simdfX_load( r1, input+stbir__simdfX_float_count ); stbir__simdfX_load( r2, input+(2*stbir__simdfX_float_count) ); stbir__simdfX_load( r3, input+(3*stbir__simdfX_float_count) ); + + #ifdef STB_IMAGE_RESIZE_VERTICAL_CONTINUE + stbIF0( stbir__simdfX_load( o0, output0 ); stbir__simdfX_load( o1, output0+stbir__simdfX_float_count ); stbir__simdfX_load( o2, output0+(2*stbir__simdfX_float_count) ); stbir__simdfX_load( o3, output0+(3*stbir__simdfX_float_count) ); + stbir__simdfX_madd( o0, o0, r0, c0 ); stbir__simdfX_madd( o1, o1, r1, c0 ); stbir__simdfX_madd( o2, o2, r2, c0 ); stbir__simdfX_madd( o3, o3, r3, c0 ); + stbir__simdfX_store( output0, o0 ); stbir__simdfX_store( output0+stbir__simdfX_float_count, o1 ); stbir__simdfX_store( output0+(2*stbir__simdfX_float_count), o2 ); stbir__simdfX_store( output0+(3*stbir__simdfX_float_count), o3 ); ) + stbIF1( stbir__simdfX_load( o0, output1 ); stbir__simdfX_load( o1, output1+stbir__simdfX_float_count ); stbir__simdfX_load( o2, output1+(2*stbir__simdfX_float_count) ); stbir__simdfX_load( o3, output1+(3*stbir__simdfX_float_count) ); + stbir__simdfX_madd( o0, o0, r0, c1 ); stbir__simdfX_madd( o1, o1, r1, c1 ); stbir__simdfX_madd( o2, o2, r2, c1 ); stbir__simdfX_madd( o3, o3, r3, c1 ); + stbir__simdfX_store( output1, o0 ); stbir__simdfX_store( output1+stbir__simdfX_float_count, o1 ); stbir__simdfX_store( output1+(2*stbir__simdfX_float_count), o2 ); stbir__simdfX_store( output1+(3*stbir__simdfX_float_count), o3 ); ) + stbIF2( stbir__simdfX_load( o0, output2 ); stbir__simdfX_load( o1, output2+stbir__simdfX_float_count ); stbir__simdfX_load( o2, output2+(2*stbir__simdfX_float_count) ); stbir__simdfX_load( o3, output2+(3*stbir__simdfX_float_count) ); + stbir__simdfX_madd( o0, o0, r0, c2 ); stbir__simdfX_madd( o1, o1, r1, c2 ); stbir__simdfX_madd( o2, o2, r2, c2 ); stbir__simdfX_madd( o3, o3, r3, c2 ); + stbir__simdfX_store( output2, o0 ); stbir__simdfX_store( output2+stbir__simdfX_float_count, o1 ); stbir__simdfX_store( output2+(2*stbir__simdfX_float_count), o2 ); stbir__simdfX_store( output2+(3*stbir__simdfX_float_count), o3 ); ) + stbIF3( stbir__simdfX_load( o0, output3 ); stbir__simdfX_load( o1, output3+stbir__simdfX_float_count ); stbir__simdfX_load( o2, output3+(2*stbir__simdfX_float_count) ); stbir__simdfX_load( o3, output3+(3*stbir__simdfX_float_count) ); + stbir__simdfX_madd( o0, o0, r0, c3 ); stbir__simdfX_madd( o1, o1, r1, c3 ); stbir__simdfX_madd( o2, o2, r2, c3 ); stbir__simdfX_madd( o3, o3, r3, c3 ); + stbir__simdfX_store( output3, o0 ); stbir__simdfX_store( output3+stbir__simdfX_float_count, o1 ); stbir__simdfX_store( output3+(2*stbir__simdfX_float_count), o2 ); stbir__simdfX_store( output3+(3*stbir__simdfX_float_count), o3 ); ) + stbIF4( stbir__simdfX_load( o0, output4 ); stbir__simdfX_load( o1, output4+stbir__simdfX_float_count ); stbir__simdfX_load( o2, output4+(2*stbir__simdfX_float_count) ); stbir__simdfX_load( o3, output4+(3*stbir__simdfX_float_count) ); + stbir__simdfX_madd( o0, o0, r0, c4 ); stbir__simdfX_madd( o1, o1, r1, c4 ); stbir__simdfX_madd( o2, o2, r2, c4 ); stbir__simdfX_madd( o3, o3, r3, c4 ); + stbir__simdfX_store( output4, o0 ); stbir__simdfX_store( output4+stbir__simdfX_float_count, o1 ); stbir__simdfX_store( output4+(2*stbir__simdfX_float_count), o2 ); stbir__simdfX_store( output4+(3*stbir__simdfX_float_count), o3 ); ) + stbIF5( stbir__simdfX_load( o0, output5 ); stbir__simdfX_load( o1, output5+stbir__simdfX_float_count ); stbir__simdfX_load( o2, output5+(2*stbir__simdfX_float_count)); stbir__simdfX_load( o3, output5+(3*stbir__simdfX_float_count) ); + stbir__simdfX_madd( o0, o0, r0, c5 ); stbir__simdfX_madd( o1, o1, r1, c5 ); stbir__simdfX_madd( o2, o2, r2, c5 ); stbir__simdfX_madd( o3, o3, r3, c5 ); + stbir__simdfX_store( output5, o0 ); stbir__simdfX_store( output5+stbir__simdfX_float_count, o1 ); stbir__simdfX_store( output5+(2*stbir__simdfX_float_count), o2 ); stbir__simdfX_store( output5+(3*stbir__simdfX_float_count), o3 ); ) + stbIF6( stbir__simdfX_load( o0, output6 ); stbir__simdfX_load( o1, output6+stbir__simdfX_float_count ); stbir__simdfX_load( o2, output6+(2*stbir__simdfX_float_count) ); stbir__simdfX_load( o3, output6+(3*stbir__simdfX_float_count) ); + stbir__simdfX_madd( o0, o0, r0, c6 ); stbir__simdfX_madd( o1, o1, r1, c6 ); stbir__simdfX_madd( o2, o2, r2, c6 ); stbir__simdfX_madd( o3, o3, r3, c6 ); + stbir__simdfX_store( output6, o0 ); stbir__simdfX_store( output6+stbir__simdfX_float_count, o1 ); stbir__simdfX_store( output6+(2*stbir__simdfX_float_count), o2 ); stbir__simdfX_store( output6+(3*stbir__simdfX_float_count), o3 ); ) + stbIF7( stbir__simdfX_load( o0, output7 ); stbir__simdfX_load( o1, output7+stbir__simdfX_float_count ); stbir__simdfX_load( o2, output7+(2*stbir__simdfX_float_count) ); stbir__simdfX_load( o3, output7+(3*stbir__simdfX_float_count) ); + stbir__simdfX_madd( o0, o0, r0, c7 ); stbir__simdfX_madd( o1, o1, r1, c7 ); stbir__simdfX_madd( o2, o2, r2, c7 ); stbir__simdfX_madd( o3, o3, r3, c7 ); + stbir__simdfX_store( output7, o0 ); stbir__simdfX_store( output7+stbir__simdfX_float_count, o1 ); stbir__simdfX_store( output7+(2*stbir__simdfX_float_count), o2 ); stbir__simdfX_store( output7+(3*stbir__simdfX_float_count), o3 ); ) + #else + stbIF0( stbir__simdfX_mult( o0, r0, c0 ); stbir__simdfX_mult( o1, r1, c0 ); stbir__simdfX_mult( o2, r2, c0 ); stbir__simdfX_mult( o3, r3, c0 ); + stbir__simdfX_store( output0, o0 ); stbir__simdfX_store( output0+stbir__simdfX_float_count, o1 ); stbir__simdfX_store( output0+(2*stbir__simdfX_float_count), o2 ); stbir__simdfX_store( output0+(3*stbir__simdfX_float_count), o3 ); ) + stbIF1( stbir__simdfX_mult( o0, r0, c1 ); stbir__simdfX_mult( o1, r1, c1 ); stbir__simdfX_mult( o2, r2, c1 ); stbir__simdfX_mult( o3, r3, c1 ); + stbir__simdfX_store( output1, o0 ); stbir__simdfX_store( output1+stbir__simdfX_float_count, o1 ); stbir__simdfX_store( output1+(2*stbir__simdfX_float_count), o2 ); stbir__simdfX_store( output1+(3*stbir__simdfX_float_count), o3 ); ) + stbIF2( stbir__simdfX_mult( o0, r0, c2 ); stbir__simdfX_mult( o1, r1, c2 ); stbir__simdfX_mult( o2, r2, c2 ); stbir__simdfX_mult( o3, r3, c2 ); + stbir__simdfX_store( output2, o0 ); stbir__simdfX_store( output2+stbir__simdfX_float_count, o1 ); stbir__simdfX_store( output2+(2*stbir__simdfX_float_count), o2 ); stbir__simdfX_store( output2+(3*stbir__simdfX_float_count), o3 ); ) + stbIF3( stbir__simdfX_mult( o0, r0, c3 ); stbir__simdfX_mult( o1, r1, c3 ); stbir__simdfX_mult( o2, r2, c3 ); stbir__simdfX_mult( o3, r3, c3 ); + stbir__simdfX_store( output3, o0 ); stbir__simdfX_store( output3+stbir__simdfX_float_count, o1 ); stbir__simdfX_store( output3+(2*stbir__simdfX_float_count), o2 ); stbir__simdfX_store( output3+(3*stbir__simdfX_float_count), o3 ); ) + stbIF4( stbir__simdfX_mult( o0, r0, c4 ); stbir__simdfX_mult( o1, r1, c4 ); stbir__simdfX_mult( o2, r2, c4 ); stbir__simdfX_mult( o3, r3, c4 ); + stbir__simdfX_store( output4, o0 ); stbir__simdfX_store( output4+stbir__simdfX_float_count, o1 ); stbir__simdfX_store( output4+(2*stbir__simdfX_float_count), o2 ); stbir__simdfX_store( output4+(3*stbir__simdfX_float_count), o3 ); ) + stbIF5( stbir__simdfX_mult( o0, r0, c5 ); stbir__simdfX_mult( o1, r1, c5 ); stbir__simdfX_mult( o2, r2, c5 ); stbir__simdfX_mult( o3, r3, c5 ); + stbir__simdfX_store( output5, o0 ); stbir__simdfX_store( output5+stbir__simdfX_float_count, o1 ); stbir__simdfX_store( output5+(2*stbir__simdfX_float_count), o2 ); stbir__simdfX_store( output5+(3*stbir__simdfX_float_count), o3 ); ) + stbIF6( stbir__simdfX_mult( o0, r0, c6 ); stbir__simdfX_mult( o1, r1, c6 ); stbir__simdfX_mult( o2, r2, c6 ); stbir__simdfX_mult( o3, r3, c6 ); + stbir__simdfX_store( output6, o0 ); stbir__simdfX_store( output6+stbir__simdfX_float_count, o1 ); stbir__simdfX_store( output6+(2*stbir__simdfX_float_count), o2 ); stbir__simdfX_store( output6+(3*stbir__simdfX_float_count), o3 ); ) + stbIF7( stbir__simdfX_mult( o0, r0, c7 ); stbir__simdfX_mult( o1, r1, c7 ); stbir__simdfX_mult( o2, r2, c7 ); stbir__simdfX_mult( o3, r3, c7 ); + stbir__simdfX_store( output7, o0 ); stbir__simdfX_store( output7+stbir__simdfX_float_count, o1 ); stbir__simdfX_store( output7+(2*stbir__simdfX_float_count), o2 ); stbir__simdfX_store( output7+(3*stbir__simdfX_float_count), o3 ); ) + #endif + + input += (4*stbir__simdfX_float_count); + stbIF0( output0 += (4*stbir__simdfX_float_count); ) stbIF1( output1 += (4*stbir__simdfX_float_count); ) stbIF2( output2 += (4*stbir__simdfX_float_count); ) stbIF3( output3 += (4*stbir__simdfX_float_count); ) stbIF4( output4 += (4*stbir__simdfX_float_count); ) stbIF5( output5 += (4*stbir__simdfX_float_count); ) stbIF6( output6 += (4*stbir__simdfX_float_count); ) stbIF7( output7 += (4*stbir__simdfX_float_count); ) + } + STBIR_SIMD_NO_UNROLL_LOOP_START + while ( ( (char*)input_end - (char*) input ) >= 16 ) + { + stbir__simdf o0, r0; + STBIR_SIMD_NO_UNROLL(output0); + + stbir__simdf_load( r0, input ); + + #ifdef STB_IMAGE_RESIZE_VERTICAL_CONTINUE + stbIF0( stbir__simdf_load( o0, output0 ); stbir__simdf_madd( o0, o0, r0, stbir__if_simdf8_cast_to_simdf4( c0 ) ); stbir__simdf_store( output0, o0 ); ) + stbIF1( stbir__simdf_load( o0, output1 ); stbir__simdf_madd( o0, o0, r0, stbir__if_simdf8_cast_to_simdf4( c1 ) ); stbir__simdf_store( output1, o0 ); ) + stbIF2( stbir__simdf_load( o0, output2 ); stbir__simdf_madd( o0, o0, r0, stbir__if_simdf8_cast_to_simdf4( c2 ) ); stbir__simdf_store( output2, o0 ); ) + stbIF3( stbir__simdf_load( o0, output3 ); stbir__simdf_madd( o0, o0, r0, stbir__if_simdf8_cast_to_simdf4( c3 ) ); stbir__simdf_store( output3, o0 ); ) + stbIF4( stbir__simdf_load( o0, output4 ); stbir__simdf_madd( o0, o0, r0, stbir__if_simdf8_cast_to_simdf4( c4 ) ); stbir__simdf_store( output4, o0 ); ) + stbIF5( stbir__simdf_load( o0, output5 ); stbir__simdf_madd( o0, o0, r0, stbir__if_simdf8_cast_to_simdf4( c5 ) ); stbir__simdf_store( output5, o0 ); ) + stbIF6( stbir__simdf_load( o0, output6 ); stbir__simdf_madd( o0, o0, r0, stbir__if_simdf8_cast_to_simdf4( c6 ) ); stbir__simdf_store( output6, o0 ); ) + stbIF7( stbir__simdf_load( o0, output7 ); stbir__simdf_madd( o0, o0, r0, stbir__if_simdf8_cast_to_simdf4( c7 ) ); stbir__simdf_store( output7, o0 ); ) + #else + stbIF0( stbir__simdf_mult( o0, r0, stbir__if_simdf8_cast_to_simdf4( c0 ) ); stbir__simdf_store( output0, o0 ); ) + stbIF1( stbir__simdf_mult( o0, r0, stbir__if_simdf8_cast_to_simdf4( c1 ) ); stbir__simdf_store( output1, o0 ); ) + stbIF2( stbir__simdf_mult( o0, r0, stbir__if_simdf8_cast_to_simdf4( c2 ) ); stbir__simdf_store( output2, o0 ); ) + stbIF3( stbir__simdf_mult( o0, r0, stbir__if_simdf8_cast_to_simdf4( c3 ) ); stbir__simdf_store( output3, o0 ); ) + stbIF4( stbir__simdf_mult( o0, r0, stbir__if_simdf8_cast_to_simdf4( c4 ) ); stbir__simdf_store( output4, o0 ); ) + stbIF5( stbir__simdf_mult( o0, r0, stbir__if_simdf8_cast_to_simdf4( c5 ) ); stbir__simdf_store( output5, o0 ); ) + stbIF6( stbir__simdf_mult( o0, r0, stbir__if_simdf8_cast_to_simdf4( c6 ) ); stbir__simdf_store( output6, o0 ); ) + stbIF7( stbir__simdf_mult( o0, r0, stbir__if_simdf8_cast_to_simdf4( c7 ) ); stbir__simdf_store( output7, o0 ); ) + #endif + + input += 4; + stbIF0( output0 += 4; ) stbIF1( output1 += 4; ) stbIF2( output2 += 4; ) stbIF3( output3 += 4; ) stbIF4( output4 += 4; ) stbIF5( output5 += 4; ) stbIF6( output6 += 4; ) stbIF7( output7 += 4; ) + } + } + #else + STBIR_NO_UNROLL_LOOP_START + while ( ( (char*)input_end - (char*) input ) >= 16 ) + { + float r0, r1, r2, r3; + STBIR_NO_UNROLL(input); + + r0 = input[0], r1 = input[1], r2 = input[2], r3 = input[3]; + + #ifdef STB_IMAGE_RESIZE_VERTICAL_CONTINUE + stbIF0( output0[0] += ( r0 * c0s ); output0[1] += ( r1 * c0s ); output0[2] += ( r2 * c0s ); output0[3] += ( r3 * c0s ); ) + stbIF1( output1[0] += ( r0 * c1s ); output1[1] += ( r1 * c1s ); output1[2] += ( r2 * c1s ); output1[3] += ( r3 * c1s ); ) + stbIF2( output2[0] += ( r0 * c2s ); output2[1] += ( r1 * c2s ); output2[2] += ( r2 * c2s ); output2[3] += ( r3 * c2s ); ) + stbIF3( output3[0] += ( r0 * c3s ); output3[1] += ( r1 * c3s ); output3[2] += ( r2 * c3s ); output3[3] += ( r3 * c3s ); ) + stbIF4( output4[0] += ( r0 * c4s ); output4[1] += ( r1 * c4s ); output4[2] += ( r2 * c4s ); output4[3] += ( r3 * c4s ); ) + stbIF5( output5[0] += ( r0 * c5s ); output5[1] += ( r1 * c5s ); output5[2] += ( r2 * c5s ); output5[3] += ( r3 * c5s ); ) + stbIF6( output6[0] += ( r0 * c6s ); output6[1] += ( r1 * c6s ); output6[2] += ( r2 * c6s ); output6[3] += ( r3 * c6s ); ) + stbIF7( output7[0] += ( r0 * c7s ); output7[1] += ( r1 * c7s ); output7[2] += ( r2 * c7s ); output7[3] += ( r3 * c7s ); ) + #else + stbIF0( output0[0] = ( r0 * c0s ); output0[1] = ( r1 * c0s ); output0[2] = ( r2 * c0s ); output0[3] = ( r3 * c0s ); ) + stbIF1( output1[0] = ( r0 * c1s ); output1[1] = ( r1 * c1s ); output1[2] = ( r2 * c1s ); output1[3] = ( r3 * c1s ); ) + stbIF2( output2[0] = ( r0 * c2s ); output2[1] = ( r1 * c2s ); output2[2] = ( r2 * c2s ); output2[3] = ( r3 * c2s ); ) + stbIF3( output3[0] = ( r0 * c3s ); output3[1] = ( r1 * c3s ); output3[2] = ( r2 * c3s ); output3[3] = ( r3 * c3s ); ) + stbIF4( output4[0] = ( r0 * c4s ); output4[1] = ( r1 * c4s ); output4[2] = ( r2 * c4s ); output4[3] = ( r3 * c4s ); ) + stbIF5( output5[0] = ( r0 * c5s ); output5[1] = ( r1 * c5s ); output5[2] = ( r2 * c5s ); output5[3] = ( r3 * c5s ); ) + stbIF6( output6[0] = ( r0 * c6s ); output6[1] = ( r1 * c6s ); output6[2] = ( r2 * c6s ); output6[3] = ( r3 * c6s ); ) + stbIF7( output7[0] = ( r0 * c7s ); output7[1] = ( r1 * c7s ); output7[2] = ( r2 * c7s ); output7[3] = ( r3 * c7s ); ) + #endif + + input += 4; + stbIF0( output0 += 4; ) stbIF1( output1 += 4; ) stbIF2( output2 += 4; ) stbIF3( output3 += 4; ) stbIF4( output4 += 4; ) stbIF5( output5 += 4; ) stbIF6( output6 += 4; ) stbIF7( output7 += 4; ) + } + #endif + STBIR_NO_UNROLL_LOOP_START + while ( input < input_end ) + { + float r = input[0]; + STBIR_NO_UNROLL(output0); + + #ifdef STB_IMAGE_RESIZE_VERTICAL_CONTINUE + stbIF0( output0[0] += ( r * c0s ); ) + stbIF1( output1[0] += ( r * c1s ); ) + stbIF2( output2[0] += ( r * c2s ); ) + stbIF3( output3[0] += ( r * c3s ); ) + stbIF4( output4[0] += ( r * c4s ); ) + stbIF5( output5[0] += ( r * c5s ); ) + stbIF6( output6[0] += ( r * c6s ); ) + stbIF7( output7[0] += ( r * c7s ); ) + #else + stbIF0( output0[0] = ( r * c0s ); ) + stbIF1( output1[0] = ( r * c1s ); ) + stbIF2( output2[0] = ( r * c2s ); ) + stbIF3( output3[0] = ( r * c3s ); ) + stbIF4( output4[0] = ( r * c4s ); ) + stbIF5( output5[0] = ( r * c5s ); ) + stbIF6( output6[0] = ( r * c6s ); ) + stbIF7( output7[0] = ( r * c7s ); ) + #endif + + ++input; + stbIF0( ++output0; ) stbIF1( ++output1; ) stbIF2( ++output2; ) stbIF3( ++output3; ) stbIF4( ++output4; ) stbIF5( ++output5; ) stbIF6( ++output6; ) stbIF7( ++output7; ) + } +} + +static void STBIR_chans( stbir__vertical_gather_with_,_coeffs)( float * outputp, float const * vertical_coefficients, float const ** inputs, float const * input0_end ) +{ + float STBIR_SIMD_STREAMOUT_PTR( * ) output = outputp; + + stbIF0( float const * input0 = inputs[0]; float c0s = vertical_coefficients[0]; ) + stbIF1( float const * input1 = inputs[1]; float c1s = vertical_coefficients[1]; ) + stbIF2( float const * input2 = inputs[2]; float c2s = vertical_coefficients[2]; ) + stbIF3( float const * input3 = inputs[3]; float c3s = vertical_coefficients[3]; ) + stbIF4( float const * input4 = inputs[4]; float c4s = vertical_coefficients[4]; ) + stbIF5( float const * input5 = inputs[5]; float c5s = vertical_coefficients[5]; ) + stbIF6( float const * input6 = inputs[6]; float c6s = vertical_coefficients[6]; ) + stbIF7( float const * input7 = inputs[7]; float c7s = vertical_coefficients[7]; ) + +#if ( STBIR__vertical_channels == 1 ) && !defined(STB_IMAGE_RESIZE_VERTICAL_CONTINUE) + // check single channel one weight + if ( ( c0s >= (1.0f-0.000001f) ) && ( c0s <= (1.0f+0.000001f) ) ) + { + STBIR_MEMCPY( output, input0, (char*)input0_end - (char*)input0 ); + return; + } +#endif + + #ifdef STBIR_SIMD + { + stbIF0(stbir__simdfX c0 = stbir__simdf_frepX( c0s ); ) + stbIF1(stbir__simdfX c1 = stbir__simdf_frepX( c1s ); ) + stbIF2(stbir__simdfX c2 = stbir__simdf_frepX( c2s ); ) + stbIF3(stbir__simdfX c3 = stbir__simdf_frepX( c3s ); ) + stbIF4(stbir__simdfX c4 = stbir__simdf_frepX( c4s ); ) + stbIF5(stbir__simdfX c5 = stbir__simdf_frepX( c5s ); ) + stbIF6(stbir__simdfX c6 = stbir__simdf_frepX( c6s ); ) + stbIF7(stbir__simdfX c7 = stbir__simdf_frepX( c7s ); ) + + STBIR_SIMD_NO_UNROLL_LOOP_START + while ( ( (char*)input0_end - (char*) input0 ) >= (16*stbir__simdfX_float_count) ) + { + stbir__simdfX o0, o1, o2, o3, r0, r1, r2, r3; + STBIR_SIMD_NO_UNROLL(output); + + // prefetch four loop iterations ahead (doesn't affect much for small resizes, but helps with big ones) + stbIF0( stbir__prefetch( input0 + (16*stbir__simdfX_float_count) ); ) + stbIF1( stbir__prefetch( input1 + (16*stbir__simdfX_float_count) ); ) + stbIF2( stbir__prefetch( input2 + (16*stbir__simdfX_float_count) ); ) + stbIF3( stbir__prefetch( input3 + (16*stbir__simdfX_float_count) ); ) + stbIF4( stbir__prefetch( input4 + (16*stbir__simdfX_float_count) ); ) + stbIF5( stbir__prefetch( input5 + (16*stbir__simdfX_float_count) ); ) + stbIF6( stbir__prefetch( input6 + (16*stbir__simdfX_float_count) ); ) + stbIF7( stbir__prefetch( input7 + (16*stbir__simdfX_float_count) ); ) + + #ifdef STB_IMAGE_RESIZE_VERTICAL_CONTINUE + stbIF0( stbir__simdfX_load( o0, output ); stbir__simdfX_load( o1, output+stbir__simdfX_float_count ); stbir__simdfX_load( o2, output+(2*stbir__simdfX_float_count) ); stbir__simdfX_load( o3, output+(3*stbir__simdfX_float_count) ); + stbir__simdfX_load( r0, input0 ); stbir__simdfX_load( r1, input0+stbir__simdfX_float_count ); stbir__simdfX_load( r2, input0+(2*stbir__simdfX_float_count) ); stbir__simdfX_load( r3, input0+(3*stbir__simdfX_float_count) ); + stbir__simdfX_madd( o0, o0, r0, c0 ); stbir__simdfX_madd( o1, o1, r1, c0 ); stbir__simdfX_madd( o2, o2, r2, c0 ); stbir__simdfX_madd( o3, o3, r3, c0 ); ) + #else + stbIF0( stbir__simdfX_load( r0, input0 ); stbir__simdfX_load( r1, input0+stbir__simdfX_float_count ); stbir__simdfX_load( r2, input0+(2*stbir__simdfX_float_count) ); stbir__simdfX_load( r3, input0+(3*stbir__simdfX_float_count) ); + stbir__simdfX_mult( o0, r0, c0 ); stbir__simdfX_mult( o1, r1, c0 ); stbir__simdfX_mult( o2, r2, c0 ); stbir__simdfX_mult( o3, r3, c0 ); ) + #endif + + stbIF1( stbir__simdfX_load( r0, input1 ); stbir__simdfX_load( r1, input1+stbir__simdfX_float_count ); stbir__simdfX_load( r2, input1+(2*stbir__simdfX_float_count) ); stbir__simdfX_load( r3, input1+(3*stbir__simdfX_float_count) ); + stbir__simdfX_madd( o0, o0, r0, c1 ); stbir__simdfX_madd( o1, o1, r1, c1 ); stbir__simdfX_madd( o2, o2, r2, c1 ); stbir__simdfX_madd( o3, o3, r3, c1 ); ) + stbIF2( stbir__simdfX_load( r0, input2 ); stbir__simdfX_load( r1, input2+stbir__simdfX_float_count ); stbir__simdfX_load( r2, input2+(2*stbir__simdfX_float_count) ); stbir__simdfX_load( r3, input2+(3*stbir__simdfX_float_count) ); + stbir__simdfX_madd( o0, o0, r0, c2 ); stbir__simdfX_madd( o1, o1, r1, c2 ); stbir__simdfX_madd( o2, o2, r2, c2 ); stbir__simdfX_madd( o3, o3, r3, c2 ); ) + stbIF3( stbir__simdfX_load( r0, input3 ); stbir__simdfX_load( r1, input3+stbir__simdfX_float_count ); stbir__simdfX_load( r2, input3+(2*stbir__simdfX_float_count) ); stbir__simdfX_load( r3, input3+(3*stbir__simdfX_float_count) ); + stbir__simdfX_madd( o0, o0, r0, c3 ); stbir__simdfX_madd( o1, o1, r1, c3 ); stbir__simdfX_madd( o2, o2, r2, c3 ); stbir__simdfX_madd( o3, o3, r3, c3 ); ) + stbIF4( stbir__simdfX_load( r0, input4 ); stbir__simdfX_load( r1, input4+stbir__simdfX_float_count ); stbir__simdfX_load( r2, input4+(2*stbir__simdfX_float_count) ); stbir__simdfX_load( r3, input4+(3*stbir__simdfX_float_count) ); + stbir__simdfX_madd( o0, o0, r0, c4 ); stbir__simdfX_madd( o1, o1, r1, c4 ); stbir__simdfX_madd( o2, o2, r2, c4 ); stbir__simdfX_madd( o3, o3, r3, c4 ); ) + stbIF5( stbir__simdfX_load( r0, input5 ); stbir__simdfX_load( r1, input5+stbir__simdfX_float_count ); stbir__simdfX_load( r2, input5+(2*stbir__simdfX_float_count) ); stbir__simdfX_load( r3, input5+(3*stbir__simdfX_float_count) ); + stbir__simdfX_madd( o0, o0, r0, c5 ); stbir__simdfX_madd( o1, o1, r1, c5 ); stbir__simdfX_madd( o2, o2, r2, c5 ); stbir__simdfX_madd( o3, o3, r3, c5 ); ) + stbIF6( stbir__simdfX_load( r0, input6 ); stbir__simdfX_load( r1, input6+stbir__simdfX_float_count ); stbir__simdfX_load( r2, input6+(2*stbir__simdfX_float_count) ); stbir__simdfX_load( r3, input6+(3*stbir__simdfX_float_count) ); + stbir__simdfX_madd( o0, o0, r0, c6 ); stbir__simdfX_madd( o1, o1, r1, c6 ); stbir__simdfX_madd( o2, o2, r2, c6 ); stbir__simdfX_madd( o3, o3, r3, c6 ); ) + stbIF7( stbir__simdfX_load( r0, input7 ); stbir__simdfX_load( r1, input7+stbir__simdfX_float_count ); stbir__simdfX_load( r2, input7+(2*stbir__simdfX_float_count) ); stbir__simdfX_load( r3, input7+(3*stbir__simdfX_float_count) ); + stbir__simdfX_madd( o0, o0, r0, c7 ); stbir__simdfX_madd( o1, o1, r1, c7 ); stbir__simdfX_madd( o2, o2, r2, c7 ); stbir__simdfX_madd( o3, o3, r3, c7 ); ) + + stbir__simdfX_store( output, o0 ); stbir__simdfX_store( output+stbir__simdfX_float_count, o1 ); stbir__simdfX_store( output+(2*stbir__simdfX_float_count), o2 ); stbir__simdfX_store( output+(3*stbir__simdfX_float_count), o3 ); + output += (4*stbir__simdfX_float_count); + stbIF0( input0 += (4*stbir__simdfX_float_count); ) stbIF1( input1 += (4*stbir__simdfX_float_count); ) stbIF2( input2 += (4*stbir__simdfX_float_count); ) stbIF3( input3 += (4*stbir__simdfX_float_count); ) stbIF4( input4 += (4*stbir__simdfX_float_count); ) stbIF5( input5 += (4*stbir__simdfX_float_count); ) stbIF6( input6 += (4*stbir__simdfX_float_count); ) stbIF7( input7 += (4*stbir__simdfX_float_count); ) + } + + STBIR_SIMD_NO_UNROLL_LOOP_START + while ( ( (char*)input0_end - (char*) input0 ) >= 16 ) + { + stbir__simdf o0, r0; + STBIR_SIMD_NO_UNROLL(output); + + #ifdef STB_IMAGE_RESIZE_VERTICAL_CONTINUE + stbIF0( stbir__simdf_load( o0, output ); stbir__simdf_load( r0, input0 ); stbir__simdf_madd( o0, o0, r0, stbir__if_simdf8_cast_to_simdf4( c0 ) ); ) + #else + stbIF0( stbir__simdf_load( r0, input0 ); stbir__simdf_mult( o0, r0, stbir__if_simdf8_cast_to_simdf4( c0 ) ); ) + #endif + stbIF1( stbir__simdf_load( r0, input1 ); stbir__simdf_madd( o0, o0, r0, stbir__if_simdf8_cast_to_simdf4( c1 ) ); ) + stbIF2( stbir__simdf_load( r0, input2 ); stbir__simdf_madd( o0, o0, r0, stbir__if_simdf8_cast_to_simdf4( c2 ) ); ) + stbIF3( stbir__simdf_load( r0, input3 ); stbir__simdf_madd( o0, o0, r0, stbir__if_simdf8_cast_to_simdf4( c3 ) ); ) + stbIF4( stbir__simdf_load( r0, input4 ); stbir__simdf_madd( o0, o0, r0, stbir__if_simdf8_cast_to_simdf4( c4 ) ); ) + stbIF5( stbir__simdf_load( r0, input5 ); stbir__simdf_madd( o0, o0, r0, stbir__if_simdf8_cast_to_simdf4( c5 ) ); ) + stbIF6( stbir__simdf_load( r0, input6 ); stbir__simdf_madd( o0, o0, r0, stbir__if_simdf8_cast_to_simdf4( c6 ) ); ) + stbIF7( stbir__simdf_load( r0, input7 ); stbir__simdf_madd( o0, o0, r0, stbir__if_simdf8_cast_to_simdf4( c7 ) ); ) + + stbir__simdf_store( output, o0 ); + output += 4; + stbIF0( input0 += 4; ) stbIF1( input1 += 4; ) stbIF2( input2 += 4; ) stbIF3( input3 += 4; ) stbIF4( input4 += 4; ) stbIF5( input5 += 4; ) stbIF6( input6 += 4; ) stbIF7( input7 += 4; ) + } + } + #else + STBIR_NO_UNROLL_LOOP_START + while ( ( (char*)input0_end - (char*) input0 ) >= 16 ) + { + float o0, o1, o2, o3; + STBIR_NO_UNROLL(output); + #ifdef STB_IMAGE_RESIZE_VERTICAL_CONTINUE + stbIF0( o0 = output[0] + input0[0] * c0s; o1 = output[1] + input0[1] * c0s; o2 = output[2] + input0[2] * c0s; o3 = output[3] + input0[3] * c0s; ) + #else + stbIF0( o0 = input0[0] * c0s; o1 = input0[1] * c0s; o2 = input0[2] * c0s; o3 = input0[3] * c0s; ) + #endif + stbIF1( o0 += input1[0] * c1s; o1 += input1[1] * c1s; o2 += input1[2] * c1s; o3 += input1[3] * c1s; ) + stbIF2( o0 += input2[0] * c2s; o1 += input2[1] * c2s; o2 += input2[2] * c2s; o3 += input2[3] * c2s; ) + stbIF3( o0 += input3[0] * c3s; o1 += input3[1] * c3s; o2 += input3[2] * c3s; o3 += input3[3] * c3s; ) + stbIF4( o0 += input4[0] * c4s; o1 += input4[1] * c4s; o2 += input4[2] * c4s; o3 += input4[3] * c4s; ) + stbIF5( o0 += input5[0] * c5s; o1 += input5[1] * c5s; o2 += input5[2] * c5s; o3 += input5[3] * c5s; ) + stbIF6( o0 += input6[0] * c6s; o1 += input6[1] * c6s; o2 += input6[2] * c6s; o3 += input6[3] * c6s; ) + stbIF7( o0 += input7[0] * c7s; o1 += input7[1] * c7s; o2 += input7[2] * c7s; o3 += input7[3] * c7s; ) + output[0] = o0; output[1] = o1; output[2] = o2; output[3] = o3; + output += 4; + stbIF0( input0 += 4; ) stbIF1( input1 += 4; ) stbIF2( input2 += 4; ) stbIF3( input3 += 4; ) stbIF4( input4 += 4; ) stbIF5( input5 += 4; ) stbIF6( input6 += 4; ) stbIF7( input7 += 4; ) + } + #endif + STBIR_NO_UNROLL_LOOP_START + while ( input0 < input0_end ) + { + float o0; + STBIR_NO_UNROLL(output); + #ifdef STB_IMAGE_RESIZE_VERTICAL_CONTINUE + stbIF0( o0 = output[0] + input0[0] * c0s; ) + #else + stbIF0( o0 = input0[0] * c0s; ) + #endif + stbIF1( o0 += input1[0] * c1s; ) + stbIF2( o0 += input2[0] * c2s; ) + stbIF3( o0 += input3[0] * c3s; ) + stbIF4( o0 += input4[0] * c4s; ) + stbIF5( o0 += input5[0] * c5s; ) + stbIF6( o0 += input6[0] * c6s; ) + stbIF7( o0 += input7[0] * c7s; ) + output[0] = o0; + ++output; + stbIF0( ++input0; ) stbIF1( ++input1; ) stbIF2( ++input2; ) stbIF3( ++input3; ) stbIF4( ++input4; ) stbIF5( ++input5; ) stbIF6( ++input6; ) stbIF7( ++input7; ) + } +} + +#undef stbIF0 +#undef stbIF1 +#undef stbIF2 +#undef stbIF3 +#undef stbIF4 +#undef stbIF5 +#undef stbIF6 +#undef stbIF7 +#undef STB_IMAGE_RESIZE_DO_VERTICALS +#undef STBIR__vertical_channels +#undef STB_IMAGE_RESIZE_DO_HORIZONTALS +#undef STBIR_strs_join24 +#undef STBIR_strs_join14 +#undef STBIR_chans +#ifdef STB_IMAGE_RESIZE_VERTICAL_CONTINUE +#undef STB_IMAGE_RESIZE_VERTICAL_CONTINUE +#endif + +#else // !STB_IMAGE_RESIZE_DO_VERTICALS + +#define STBIR_chans( start, end ) STBIR_strs_join1(start,STBIR__horizontal_channels,end) + +#ifndef stbir__2_coeff_only +#define stbir__2_coeff_only() \ + stbir__1_coeff_only(); \ + stbir__1_coeff_remnant(1); +#endif + +#ifndef stbir__2_coeff_remnant +#define stbir__2_coeff_remnant( ofs ) \ + stbir__1_coeff_remnant(ofs); \ + stbir__1_coeff_remnant((ofs)+1); +#endif + +#ifndef stbir__3_coeff_only +#define stbir__3_coeff_only() \ + stbir__2_coeff_only(); \ + stbir__1_coeff_remnant(2); +#endif + +#ifndef stbir__3_coeff_remnant +#define stbir__3_coeff_remnant( ofs ) \ + stbir__2_coeff_remnant(ofs); \ + stbir__1_coeff_remnant((ofs)+2); +#endif + +#ifndef stbir__3_coeff_setup +#define stbir__3_coeff_setup() +#endif + +#ifndef stbir__4_coeff_start +#define stbir__4_coeff_start() \ + stbir__2_coeff_only(); \ + stbir__2_coeff_remnant(2); +#endif + +#ifndef stbir__4_coeff_continue_from_4 +#define stbir__4_coeff_continue_from_4( ofs ) \ + stbir__2_coeff_remnant(ofs); \ + stbir__2_coeff_remnant((ofs)+2); +#endif + +#ifndef stbir__store_output_tiny +#define stbir__store_output_tiny stbir__store_output +#endif + +static void STBIR_chans( stbir__horizontal_gather_,_channels_with_1_coeff)( float * output_buffer, unsigned int output_sub_size, float const * decode_buffer, stbir__contributors const * horizontal_contributors, float const * horizontal_coefficients, int coefficient_width ) +{ + float const * output_end = output_buffer + output_sub_size * STBIR__horizontal_channels; + float STBIR_SIMD_STREAMOUT_PTR( * ) output = output_buffer; + STBIR_SIMD_NO_UNROLL_LOOP_START + do { + float const * decode = decode_buffer + horizontal_contributors->n0 * STBIR__horizontal_channels; + float const * hc = horizontal_coefficients; + stbir__1_coeff_only(); + stbir__store_output_tiny(); + } while ( output < output_end ); +} + +static void STBIR_chans( stbir__horizontal_gather_,_channels_with_2_coeffs)( float * output_buffer, unsigned int output_sub_size, float const * decode_buffer, stbir__contributors const * horizontal_contributors, float const * horizontal_coefficients, int coefficient_width ) +{ + float const * output_end = output_buffer + output_sub_size * STBIR__horizontal_channels; + float STBIR_SIMD_STREAMOUT_PTR( * ) output = output_buffer; + STBIR_SIMD_NO_UNROLL_LOOP_START + do { + float const * decode = decode_buffer + horizontal_contributors->n0 * STBIR__horizontal_channels; + float const * hc = horizontal_coefficients; + stbir__2_coeff_only(); + stbir__store_output_tiny(); + } while ( output < output_end ); +} + +static void STBIR_chans( stbir__horizontal_gather_,_channels_with_3_coeffs)( float * output_buffer, unsigned int output_sub_size, float const * decode_buffer, stbir__contributors const * horizontal_contributors, float const * horizontal_coefficients, int coefficient_width ) +{ + float const * output_end = output_buffer + output_sub_size * STBIR__horizontal_channels; + float STBIR_SIMD_STREAMOUT_PTR( * ) output = output_buffer; + STBIR_SIMD_NO_UNROLL_LOOP_START + do { + float const * decode = decode_buffer + horizontal_contributors->n0 * STBIR__horizontal_channels; + float const * hc = horizontal_coefficients; + stbir__3_coeff_only(); + stbir__store_output_tiny(); + } while ( output < output_end ); +} + +static void STBIR_chans( stbir__horizontal_gather_,_channels_with_4_coeffs)( float * output_buffer, unsigned int output_sub_size, float const * decode_buffer, stbir__contributors const * horizontal_contributors, float const * horizontal_coefficients, int coefficient_width ) +{ + float const * output_end = output_buffer + output_sub_size * STBIR__horizontal_channels; + float STBIR_SIMD_STREAMOUT_PTR( * ) output = output_buffer; + STBIR_SIMD_NO_UNROLL_LOOP_START + do { + float const * decode = decode_buffer + horizontal_contributors->n0 * STBIR__horizontal_channels; + float const * hc = horizontal_coefficients; + stbir__4_coeff_start(); + stbir__store_output(); + } while ( output < output_end ); +} + +static void STBIR_chans( stbir__horizontal_gather_,_channels_with_5_coeffs)( float * output_buffer, unsigned int output_sub_size, float const * decode_buffer, stbir__contributors const * horizontal_contributors, float const * horizontal_coefficients, int coefficient_width ) +{ + float const * output_end = output_buffer + output_sub_size * STBIR__horizontal_channels; + float STBIR_SIMD_STREAMOUT_PTR( * ) output = output_buffer; + STBIR_SIMD_NO_UNROLL_LOOP_START + do { + float const * decode = decode_buffer + horizontal_contributors->n0 * STBIR__horizontal_channels; + float const * hc = horizontal_coefficients; + stbir__4_coeff_start(); + stbir__1_coeff_remnant(4); + stbir__store_output(); + } while ( output < output_end ); +} + +static void STBIR_chans( stbir__horizontal_gather_,_channels_with_6_coeffs)( float * output_buffer, unsigned int output_sub_size, float const * decode_buffer, stbir__contributors const * horizontal_contributors, float const * horizontal_coefficients, int coefficient_width ) +{ + float const * output_end = output_buffer + output_sub_size * STBIR__horizontal_channels; + float STBIR_SIMD_STREAMOUT_PTR( * ) output = output_buffer; + STBIR_SIMD_NO_UNROLL_LOOP_START + do { + float const * decode = decode_buffer + horizontal_contributors->n0 * STBIR__horizontal_channels; + float const * hc = horizontal_coefficients; + stbir__4_coeff_start(); + stbir__2_coeff_remnant(4); + stbir__store_output(); + } while ( output < output_end ); +} + +static void STBIR_chans( stbir__horizontal_gather_,_channels_with_7_coeffs)( float * output_buffer, unsigned int output_sub_size, float const * decode_buffer, stbir__contributors const * horizontal_contributors, float const * horizontal_coefficients, int coefficient_width ) +{ + float const * output_end = output_buffer + output_sub_size * STBIR__horizontal_channels; + float STBIR_SIMD_STREAMOUT_PTR( * ) output = output_buffer; + stbir__3_coeff_setup(); + STBIR_SIMD_NO_UNROLL_LOOP_START + do { + float const * decode = decode_buffer + horizontal_contributors->n0 * STBIR__horizontal_channels; + float const * hc = horizontal_coefficients; + + stbir__4_coeff_start(); + stbir__3_coeff_remnant(4); + stbir__store_output(); + } while ( output < output_end ); +} + +static void STBIR_chans( stbir__horizontal_gather_,_channels_with_8_coeffs)( float * output_buffer, unsigned int output_sub_size, float const * decode_buffer, stbir__contributors const * horizontal_contributors, float const * horizontal_coefficients, int coefficient_width ) +{ + float const * output_end = output_buffer + output_sub_size * STBIR__horizontal_channels; + float STBIR_SIMD_STREAMOUT_PTR( * ) output = output_buffer; + STBIR_SIMD_NO_UNROLL_LOOP_START + do { + float const * decode = decode_buffer + horizontal_contributors->n0 * STBIR__horizontal_channels; + float const * hc = horizontal_coefficients; + stbir__4_coeff_start(); + stbir__4_coeff_continue_from_4(4); + stbir__store_output(); + } while ( output < output_end ); +} + +static void STBIR_chans( stbir__horizontal_gather_,_channels_with_9_coeffs)( float * output_buffer, unsigned int output_sub_size, float const * decode_buffer, stbir__contributors const * horizontal_contributors, float const * horizontal_coefficients, int coefficient_width ) +{ + float const * output_end = output_buffer + output_sub_size * STBIR__horizontal_channels; + float STBIR_SIMD_STREAMOUT_PTR( * ) output = output_buffer; + STBIR_SIMD_NO_UNROLL_LOOP_START + do { + float const * decode = decode_buffer + horizontal_contributors->n0 * STBIR__horizontal_channels; + float const * hc = horizontal_coefficients; + stbir__4_coeff_start(); + stbir__4_coeff_continue_from_4(4); + stbir__1_coeff_remnant(8); + stbir__store_output(); + } while ( output < output_end ); +} + +static void STBIR_chans( stbir__horizontal_gather_,_channels_with_10_coeffs)( float * output_buffer, unsigned int output_sub_size, float const * decode_buffer, stbir__contributors const * horizontal_contributors, float const * horizontal_coefficients, int coefficient_width ) +{ + float const * output_end = output_buffer + output_sub_size * STBIR__horizontal_channels; + float STBIR_SIMD_STREAMOUT_PTR( * ) output = output_buffer; + STBIR_SIMD_NO_UNROLL_LOOP_START + do { + float const * decode = decode_buffer + horizontal_contributors->n0 * STBIR__horizontal_channels; + float const * hc = horizontal_coefficients; + stbir__4_coeff_start(); + stbir__4_coeff_continue_from_4(4); + stbir__2_coeff_remnant(8); + stbir__store_output(); + } while ( output < output_end ); +} + +static void STBIR_chans( stbir__horizontal_gather_,_channels_with_11_coeffs)( float * output_buffer, unsigned int output_sub_size, float const * decode_buffer, stbir__contributors const * horizontal_contributors, float const * horizontal_coefficients, int coefficient_width ) +{ + float const * output_end = output_buffer + output_sub_size * STBIR__horizontal_channels; + float STBIR_SIMD_STREAMOUT_PTR( * ) output = output_buffer; + stbir__3_coeff_setup(); + STBIR_SIMD_NO_UNROLL_LOOP_START + do { + float const * decode = decode_buffer + horizontal_contributors->n0 * STBIR__horizontal_channels; + float const * hc = horizontal_coefficients; + stbir__4_coeff_start(); + stbir__4_coeff_continue_from_4(4); + stbir__3_coeff_remnant(8); + stbir__store_output(); + } while ( output < output_end ); +} + +static void STBIR_chans( stbir__horizontal_gather_,_channels_with_12_coeffs)( float * output_buffer, unsigned int output_sub_size, float const * decode_buffer, stbir__contributors const * horizontal_contributors, float const * horizontal_coefficients, int coefficient_width ) +{ + float const * output_end = output_buffer + output_sub_size * STBIR__horizontal_channels; + float STBIR_SIMD_STREAMOUT_PTR( * ) output = output_buffer; + STBIR_SIMD_NO_UNROLL_LOOP_START + do { + float const * decode = decode_buffer + horizontal_contributors->n0 * STBIR__horizontal_channels; + float const * hc = horizontal_coefficients; + stbir__4_coeff_start(); + stbir__4_coeff_continue_from_4(4); + stbir__4_coeff_continue_from_4(8); + stbir__store_output(); + } while ( output < output_end ); +} + +static void STBIR_chans( stbir__horizontal_gather_,_channels_with_n_coeffs_mod0 )( float * output_buffer, unsigned int output_sub_size, float const * decode_buffer, stbir__contributors const * horizontal_contributors, float const * horizontal_coefficients, int coefficient_width ) +{ + float const * output_end = output_buffer + output_sub_size * STBIR__horizontal_channels; + float STBIR_SIMD_STREAMOUT_PTR( * ) output = output_buffer; + STBIR_SIMD_NO_UNROLL_LOOP_START + do { + float const * decode = decode_buffer + horizontal_contributors->n0 * STBIR__horizontal_channels; + int n = ( ( horizontal_contributors->n1 - horizontal_contributors->n0 + 1 ) - 4 + 3 ) >> 2; + float const * hc = horizontal_coefficients; + + stbir__4_coeff_start(); + STBIR_SIMD_NO_UNROLL_LOOP_START + do { + hc += 4; + decode += STBIR__horizontal_channels * 4; + stbir__4_coeff_continue_from_4( 0 ); + --n; + } while ( n > 0 ); + stbir__store_output(); + } while ( output < output_end ); +} + +static void STBIR_chans( stbir__horizontal_gather_,_channels_with_n_coeffs_mod1 )( float * output_buffer, unsigned int output_sub_size, float const * decode_buffer, stbir__contributors const * horizontal_contributors, float const * horizontal_coefficients, int coefficient_width ) +{ + float const * output_end = output_buffer + output_sub_size * STBIR__horizontal_channels; + float STBIR_SIMD_STREAMOUT_PTR( * ) output = output_buffer; + STBIR_SIMD_NO_UNROLL_LOOP_START + do { + float const * decode = decode_buffer + horizontal_contributors->n0 * STBIR__horizontal_channels; + int n = ( ( horizontal_contributors->n1 - horizontal_contributors->n0 + 1 ) - 5 + 3 ) >> 2; + float const * hc = horizontal_coefficients; + + stbir__4_coeff_start(); + STBIR_SIMD_NO_UNROLL_LOOP_START + do { + hc += 4; + decode += STBIR__horizontal_channels * 4; + stbir__4_coeff_continue_from_4( 0 ); + --n; + } while ( n > 0 ); + stbir__1_coeff_remnant( 4 ); + stbir__store_output(); + } while ( output < output_end ); +} + +static void STBIR_chans( stbir__horizontal_gather_,_channels_with_n_coeffs_mod2 )( float * output_buffer, unsigned int output_sub_size, float const * decode_buffer, stbir__contributors const * horizontal_contributors, float const * horizontal_coefficients, int coefficient_width ) +{ + float const * output_end = output_buffer + output_sub_size * STBIR__horizontal_channels; + float STBIR_SIMD_STREAMOUT_PTR( * ) output = output_buffer; + STBIR_SIMD_NO_UNROLL_LOOP_START + do { + float const * decode = decode_buffer + horizontal_contributors->n0 * STBIR__horizontal_channels; + int n = ( ( horizontal_contributors->n1 - horizontal_contributors->n0 + 1 ) - 6 + 3 ) >> 2; + float const * hc = horizontal_coefficients; + + stbir__4_coeff_start(); + STBIR_SIMD_NO_UNROLL_LOOP_START + do { + hc += 4; + decode += STBIR__horizontal_channels * 4; + stbir__4_coeff_continue_from_4( 0 ); + --n; + } while ( n > 0 ); + stbir__2_coeff_remnant( 4 ); + + stbir__store_output(); + } while ( output < output_end ); +} + +static void STBIR_chans( stbir__horizontal_gather_,_channels_with_n_coeffs_mod3 )( float * output_buffer, unsigned int output_sub_size, float const * decode_buffer, stbir__contributors const * horizontal_contributors, float const * horizontal_coefficients, int coefficient_width ) +{ + float const * output_end = output_buffer + output_sub_size * STBIR__horizontal_channels; + float STBIR_SIMD_STREAMOUT_PTR( * ) output = output_buffer; + stbir__3_coeff_setup(); + STBIR_SIMD_NO_UNROLL_LOOP_START + do { + float const * decode = decode_buffer + horizontal_contributors->n0 * STBIR__horizontal_channels; + int n = ( ( horizontal_contributors->n1 - horizontal_contributors->n0 + 1 ) - 7 + 3 ) >> 2; + float const * hc = horizontal_coefficients; + + stbir__4_coeff_start(); + STBIR_SIMD_NO_UNROLL_LOOP_START + do { + hc += 4; + decode += STBIR__horizontal_channels * 4; + stbir__4_coeff_continue_from_4( 0 ); + --n; + } while ( n > 0 ); + stbir__3_coeff_remnant( 4 ); + + stbir__store_output(); + } while ( output < output_end ); +} + +static stbir__horizontal_gather_channels_func * STBIR_chans(stbir__horizontal_gather_,_channels_with_n_coeffs_funcs)[4]= +{ + STBIR_chans(stbir__horizontal_gather_,_channels_with_n_coeffs_mod0), + STBIR_chans(stbir__horizontal_gather_,_channels_with_n_coeffs_mod1), + STBIR_chans(stbir__horizontal_gather_,_channels_with_n_coeffs_mod2), + STBIR_chans(stbir__horizontal_gather_,_channels_with_n_coeffs_mod3), +}; + +static stbir__horizontal_gather_channels_func * STBIR_chans(stbir__horizontal_gather_,_channels_funcs)[12]= +{ + STBIR_chans(stbir__horizontal_gather_,_channels_with_1_coeff), + STBIR_chans(stbir__horizontal_gather_,_channels_with_2_coeffs), + STBIR_chans(stbir__horizontal_gather_,_channels_with_3_coeffs), + STBIR_chans(stbir__horizontal_gather_,_channels_with_4_coeffs), + STBIR_chans(stbir__horizontal_gather_,_channels_with_5_coeffs), + STBIR_chans(stbir__horizontal_gather_,_channels_with_6_coeffs), + STBIR_chans(stbir__horizontal_gather_,_channels_with_7_coeffs), + STBIR_chans(stbir__horizontal_gather_,_channels_with_8_coeffs), + STBIR_chans(stbir__horizontal_gather_,_channels_with_9_coeffs), + STBIR_chans(stbir__horizontal_gather_,_channels_with_10_coeffs), + STBIR_chans(stbir__horizontal_gather_,_channels_with_11_coeffs), + STBIR_chans(stbir__horizontal_gather_,_channels_with_12_coeffs), +}; + +#undef STBIR__horizontal_channels +#undef STB_IMAGE_RESIZE_DO_HORIZONTALS +#undef stbir__1_coeff_only +#undef stbir__1_coeff_remnant +#undef stbir__2_coeff_only +#undef stbir__2_coeff_remnant +#undef stbir__3_coeff_only +#undef stbir__3_coeff_remnant +#undef stbir__3_coeff_setup +#undef stbir__4_coeff_start +#undef stbir__4_coeff_continue_from_4 +#undef stbir__store_output +#undef stbir__store_output_tiny +#undef STBIR_chans + +#endif // HORIZONALS + +#undef STBIR_strs_join2 +#undef STBIR_strs_join1 + +#endif // STB_IMAGE_RESIZE_DO_HORIZONTALS/VERTICALS/CODERS + +/* +------------------------------------------------------------------------------ +This software is available under 2 licenses -- choose whichever you prefer. +------------------------------------------------------------------------------ +ALTERNATIVE A - MIT License +Copyright (c) 2017 Sean Barrett +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +------------------------------------------------------------------------------ +ALTERNATIVE B - Public Domain (www.unlicense.org) +This is free and unencumbered software released into the public domain. +Anyone is free to copy, modify, publish, use, compile, sell, or distribute this +software, either in source code form or as a compiled binary, for any purpose, +commercial or non-commercial, and by any means. +In jurisdictions that recognize copyright laws, the author or authors of this +software dedicate any and all copyright interest in the software to the public +domain. We make this dedication for the benefit of the public at large and to +the detriment of our heirs and successors. We intend this dedication to be an +overt act of relinquishment in perpetuity of all present and future rights to +this software under copyright law. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN +ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +------------------------------------------------------------------------------ +*/ diff --git a/packages/core/src/zig/vendor/update.sh b/packages/core/src/zig/vendor/update.sh new file mode 100755 index 0000000000..c175c7c4b6 --- /dev/null +++ b/packages/core/src/zig/vendor/update.sh @@ -0,0 +1,101 @@ +#!/bin/sh +set -eu + +VENDOR_DIR=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd) +WUFFS_DIR="$VENDOR_DIR/wuffs" +STB_DIR="$VENDOR_DIR/stb" +LIBWEBP_DIR="$VENDOR_DIR/libwebp" + +WUFFS_COMMIT=ec71f9c6d829ca763fbbc1f7adecc30a89a8ed0a +WUFFS_SHA256=a3db4bd979663423de00309d1ba07d7fa8576845223d3e02764181bd6da23f90 + +STB_IMAGE_COMMIT=f0569113c93ad095470c54bf34a17b36646bbbb5 +STB_IMAGE_UPSTREAM_SHA256=594c2fe35d49488b4382dbfaec8f98366defca819d916ac95becf3e75f4200b3 +STB_IMAGE_PATCHED_SHA256=1657895e86c730668cc5af6d3c8ae8f80b67c64f2ade81c44c40cee70fba555e +STB_RESIZE_COMMIT=904aa67e1e2d1dec92959df63e700b166d5c1022 +STB_RESIZE_UPSTREAM_SHA256=173e654634f6ccaad98f603e686ea212eec1fe8ea6d2a5e5e8056efa10ae3880 +STB_RESIZE_PATCHED_SHA256=3cfc10a3aa7287fa1f1360df360b22e63b2e3426965d7696f8b5c273bc810d55 + +LIBWEBP_VERSION=1.6.0 +LIBWEBP_ARCHIVE_SHA256=e4ab7009bf0629fd11982d4c2aa83964cf244cffba7347ecd39019a9e38c4564 + +for command_name in curl git tar; do + command -v "$command_name" >/dev/null 2>&1 || { + echo "error: required command not found: $command_name" >&2 + exit 1 + } +done + +REPO_ROOT=$(git -C "$VENDOR_DIR" rev-parse --show-toplevel) +STB_PREFIX=$(git -C "$STB_DIR" rev-parse --show-prefix) + +if command -v sha256sum >/dev/null 2>&1; then + sha256_file() { sha256sum "$1" | cut -d ' ' -f 1; } +elif command -v shasum >/dev/null 2>&1; then + sha256_file() { shasum -a 256 "$1" | cut -d ' ' -f 1; } +else + echo "error: sha256sum or shasum is required" >&2 + exit 1 +fi + +verify_sha256() { + file=$1 + expected=$2 + actual=$(sha256_file "$file") + if [ "$actual" != "$expected" ]; then + echo "error: SHA-256 mismatch for $file" >&2 + echo "expected: $expected" >&2 + echo "actual: $actual" >&2 + exit 1 + fi +} + +download() { + url=$1 + output=$2 + echo "Downloading $url" + curl -fsSL --retry 3 --retry-delay 1 "$url" -o "$output" +} + +TMP_ROOT=${TMPDIR:-/tmp} +TMP_DIR=$(mktemp -d "$TMP_ROOT/opentui-image-vendor.XXXXXX") +trap 'rm -rf "$TMP_DIR"' EXIT HUP INT TERM + +echo "Updating Wuffs" +download "https://raw.githubusercontent.com/google/wuffs/$WUFFS_COMMIT/release/c/wuffs-v0.3.c" "$TMP_DIR/wuffs-v0.3.c" +download "https://raw.githubusercontent.com/google/wuffs/$WUFFS_COMMIT/LICENSE" "$TMP_DIR/wuffs-LICENSE" +verify_sha256 "$TMP_DIR/wuffs-v0.3.c" "$WUFFS_SHA256" +cp "$TMP_DIR/wuffs-v0.3.c" "$WUFFS_DIR/wuffs-v0.3.c" +cp "$TMP_DIR/wuffs-LICENSE" "$WUFFS_DIR/LICENSE" + +echo "Updating stb" +download "https://raw.githubusercontent.com/nothings/stb/$STB_IMAGE_COMMIT/stb_image.h" "$TMP_DIR/stb_image.h" +download "https://raw.githubusercontent.com/nothings/stb/$STB_RESIZE_COMMIT/stb_image_resize2.h" "$TMP_DIR/stb_image_resize2.h" +download "https://raw.githubusercontent.com/nothings/stb/$STB_IMAGE_COMMIT/LICENSE" "$TMP_DIR/stb-LICENSE" +verify_sha256 "$TMP_DIR/stb_image.h" "$STB_IMAGE_UPSTREAM_SHA256" +verify_sha256 "$TMP_DIR/stb_image_resize2.h" "$STB_RESIZE_UPSTREAM_SHA256" +cp "$TMP_DIR/stb_image.h" "$STB_DIR/stb_image.h" +cp "$TMP_DIR/stb_image_resize2.h" "$STB_DIR/stb_image_resize2.h" +cp "$TMP_DIR/stb-LICENSE" "$STB_DIR/LICENSE" +git -C "$REPO_ROOT" apply --whitespace=nowarn --directory="$STB_PREFIX" "$STB_DIR/patches/stb_image-strict-jpeg.patch" +git -C "$REPO_ROOT" apply --whitespace=nowarn --directory="$STB_PREFIX" "$STB_DIR/patches/stb_image_resize2-alignment.patch" +verify_sha256 "$STB_DIR/stb_image.h" "$STB_IMAGE_PATCHED_SHA256" +verify_sha256 "$STB_DIR/stb_image_resize2.h" "$STB_RESIZE_PATCHED_SHA256" + +echo "Updating libwebp" +LIBWEBP_ARCHIVE="$TMP_DIR/libwebp-$LIBWEBP_VERSION.tar.gz" +download "https://storage.googleapis.com/downloads.webmproject.org/releases/webp/libwebp-$LIBWEBP_VERSION.tar.gz" "$LIBWEBP_ARCHIVE" +verify_sha256 "$LIBWEBP_ARCHIVE" "$LIBWEBP_ARCHIVE_SHA256" +tar -xzf "$LIBWEBP_ARCHIVE" -C "$TMP_DIR" +LIBWEBP_SOURCE="$TMP_DIR/libwebp-$LIBWEBP_VERSION" +rm -rf "$LIBWEBP_DIR/src" +while IFS= read -r path; do + [ -n "$path" ] || continue + mkdir -p "$LIBWEBP_DIR/${path%/*}" + cp "$LIBWEBP_SOURCE/$path" "$LIBWEBP_DIR/$path" +done < "$LIBWEBP_DIR/FILES" +cp "$LIBWEBP_SOURCE/COPYING" "$LIBWEBP_DIR/COPYING" +cp "$LIBWEBP_SOURCE/PATENTS" "$LIBWEBP_DIR/PATENTS" +cp "$LIBWEBP_SOURCE/AUTHORS" "$LIBWEBP_DIR/AUTHORS" + +echo "Image vendors updated successfully. Review the git diff, then run bun run test:native." diff --git a/packages/core/src/zig/vendor/wuffs/LICENSE b/packages/core/src/zig/vendor/wuffs/LICENSE new file mode 100644 index 0000000000..f433b1a53f --- /dev/null +++ b/packages/core/src/zig/vendor/wuffs/LICENSE @@ -0,0 +1,177 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS diff --git a/packages/core/src/zig/vendor/wuffs/README.md b/packages/core/src/zig/vendor/wuffs/README.md new file mode 100644 index 0000000000..4a2e02f870 --- /dev/null +++ b/packages/core/src/zig/vendor/wuffs/README.md @@ -0,0 +1,11 @@ +# Wuffs + +Pinned to Wuffs v0.3.4, commit `ec71f9c6d829ca763fbbc1f7adecc30a89a8ed0a`. + +Source: `release/c/wuffs-v0.3.c` + +Upstream SHA-256: `a3db4bd979663423de00309d1ba07d7fa8576845223d3e02764181bd6da23f90` + +Only the base, Adler-32, CRC-32, Deflate, zlib, LZW, PNG, and GIF modules are compiled. + +Update with `bun run vendor:update:images` from `packages/core`; see `../README.md`. diff --git a/packages/core/src/zig/vendor/wuffs/wuffs-v0.3.c b/packages/core/src/zig/vendor/wuffs/wuffs-v0.3.c new file mode 100644 index 0000000000..7d40cfa096 --- /dev/null +++ b/packages/core/src/zig/vendor/wuffs/wuffs-v0.3.c @@ -0,0 +1,49314 @@ +#ifndef WUFFS_INCLUDE_GUARD +#define WUFFS_INCLUDE_GUARD + +// Wuffs ships as a "single file C library" or "header file library" as per +// https://github.com/nothings/stb/blob/master/docs/stb_howto.txt +// +// To use that single file as a "foo.c"-like implementation, instead of a +// "foo.h"-like header, #define WUFFS_IMPLEMENTATION before #include'ing or +// compiling it. + +// Wuffs' C code is generated automatically, not hand-written. These warnings' +// costs outweigh the benefits. +// +// The "elif defined(__clang__)" isn't redundant. While vanilla clang defines +// __GNUC__, clang-cl (which mimics MSVC's cl.exe) does not. +#if defined(__GNUC__) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wimplicit-fallthrough" +#pragma GCC diagnostic ignored "-Wmissing-field-initializers" +#pragma GCC diagnostic ignored "-Wunreachable-code" +#pragma GCC diagnostic ignored "-Wunused-function" +#pragma GCC diagnostic ignored "-Wunused-parameter" +#if defined(__cplusplus) +#pragma GCC diagnostic ignored "-Wold-style-cast" +#endif +#elif defined(__clang__) +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wimplicit-fallthrough" +#pragma clang diagnostic ignored "-Wmissing-field-initializers" +#pragma clang diagnostic ignored "-Wunreachable-code" +#pragma clang diagnostic ignored "-Wunused-function" +#pragma clang diagnostic ignored "-Wunused-parameter" +#if defined(__cplusplus) +#pragma clang diagnostic ignored "-Wold-style-cast" +#endif +#endif + +// Copyright 2017 The Wuffs Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include +#include +#include +#include + +#ifdef __cplusplus +#if (__cplusplus >= 201103L) || defined(_MSC_VER) +#include +#define WUFFS_BASE__HAVE_EQ_DELETE +#define WUFFS_BASE__HAVE_UNIQUE_PTR +// The "defined(__clang__)" isn't redundant. While vanilla clang defines +// __GNUC__, clang-cl (which mimics MSVC's cl.exe) does not. +#elif defined(__GNUC__) || defined(__clang__) +#warning "Wuffs' C++ code expects -std=c++11 or later" +#endif + +extern "C" { +#endif + +// ---------------- Version + +// WUFFS_VERSION is the major.minor.patch version, as per https://semver.org/, +// as a uint64_t. The major number is the high 32 bits. The minor number is the +// middle 16 bits. The patch number is the low 16 bits. The pre-release label +// and build metadata are part of the string representation (such as +// "1.2.3-beta+456.20181231") but not the uint64_t representation. +// +// WUFFS_VERSION_PRE_RELEASE_LABEL (such as "", "beta" or "rc.1") being +// non-empty denotes a developer preview, not a release version, and has no +// backwards or forwards compatibility guarantees. +// +// WUFFS_VERSION_BUILD_METADATA_XXX, if non-zero, are the number of commits and +// the last commit date in the repository used to build this library. Within +// each major.minor branch, the commit count should increase monotonically. +// +// WUFFS_VERSION was overridden by "wuffs gen -version" based on revision +// a138188d5742c0469de983878a430bdbe7e50e77 committed on 2024-04-19. +#define WUFFS_VERSION 0x000030004 +#define WUFFS_VERSION_MAJOR 0 +#define WUFFS_VERSION_MINOR 3 +#define WUFFS_VERSION_PATCH 4 +#define WUFFS_VERSION_PRE_RELEASE_LABEL "" +#define WUFFS_VERSION_BUILD_METADATA_COMMIT_COUNT 3401 +#define WUFFS_VERSION_BUILD_METADATA_COMMIT_DATE 20240419 +#define WUFFS_VERSION_STRING "0.3.4+3401.20240419" + +// ---------------- Configuration + +// Define WUFFS_CONFIG__AVOID_CPU_ARCH to avoid any code tied to a specific CPU +// architecture, such as SSE SIMD for the x86 CPU family. +#if defined(WUFFS_CONFIG__AVOID_CPU_ARCH) // (#if-chain ref AVOID_CPU_ARCH_0) +// No-op. +#else // (#if-chain ref AVOID_CPU_ARCH_0) + +// The "defined(__clang__)" isn't redundant. While vanilla clang defines +// __GNUC__, clang-cl (which mimics MSVC's cl.exe) does not. +#if defined(__GNUC__) || defined(__clang__) +#define WUFFS_BASE__MAYBE_ATTRIBUTE_TARGET(arg) __attribute__((target(arg))) +#else +#define WUFFS_BASE__MAYBE_ATTRIBUTE_TARGET(arg) +#endif // defined(__GNUC__) || defined(__clang__) + +#if defined(__GNUC__) // (#if-chain ref AVOID_CPU_ARCH_1) + +// To simplify Wuffs code, "cpu_arch >= arm_xxx" requires xxx but also +// unaligned little-endian load/stores. +#if defined(__ARM_FEATURE_UNALIGNED) && !defined(__native_client__) && \ + defined(__BYTE_ORDER__) && (__BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__) +// Not all gcc versions define __ARM_ACLE, even if they support crc32 +// intrinsics. Look for __ARM_FEATURE_CRC32 instead. +#if defined(__ARM_FEATURE_CRC32) +#include +#define WUFFS_BASE__CPU_ARCH__ARM_CRC32 +#endif // defined(__ARM_FEATURE_CRC32) +#if defined(__ARM_NEON) +#include +#define WUFFS_BASE__CPU_ARCH__ARM_NEON +#endif // defined(__ARM_NEON) +#endif // defined(__ARM_FEATURE_UNALIGNED) etc + +// Similarly, "cpu_arch >= x86_sse42" requires SSE4.2 but also PCLMUL and +// POPCNT. This is checked at runtime via cpuid, not at compile time. +// +// Likewise, "cpu_arch >= x86_avx2" also requires PCLMUL, POPCNT and SSE4.2. +#if defined(__i386__) || defined(__x86_64__) +#if !defined(__native_client__) +#include +#include +// X86_FAMILY means X86 (32-bit) or X86_64 (64-bit, obviously). +#define WUFFS_BASE__CPU_ARCH__X86_FAMILY +#endif // !defined(__native_client__) +#endif // defined(__i386__) || defined(__x86_64__) + +#elif defined(_MSC_VER) // (#if-chain ref AVOID_CPU_ARCH_1) + +#if defined(_M_IX86) || defined(_M_X64) +#if defined(__AVX__) || defined(__clang__) + +// We need for the __cpuid function. +#include +// That's not enough for X64 SIMD, with clang-cl, if we want to use +// "__attribute__((target(arg)))" without e.g. "/arch:AVX". +// +// Some web pages suggest that is all you need, as it pulls in +// the earlier SIMD families like SSE4.2, but that doesn't seem to work in +// practice, possibly for the same reason that just doesn't work. +#include // AVX, AVX2, FMA, POPCNT +#include // SSE4.2 +#include // AES, PCLMUL +// X86_FAMILY means X86 (32-bit) or X86_64 (64-bit, obviously). +#define WUFFS_BASE__CPU_ARCH__X86_FAMILY + +#else // defined(__AVX__) || defined(__clang__) + +// clang-cl (which defines both __clang__ and _MSC_VER) supports +// "__attribute__((target(arg)))". +// +// For MSVC's cl.exe (unlike clang or gcc), SIMD capability is a compile-time +// property of the source file (e.g. a /arch:AVX or -mavx compiler flag), not +// of individual functions (that can be conditionally selected at runtime). +#pragma message("Wuffs with MSVC+IX86/X64 needs /arch:AVX for best performance") + +#endif // defined(__AVX__) || defined(__clang__) +#endif // defined(_M_IX86) || defined(_M_X64) + +#endif // (#if-chain ref AVOID_CPU_ARCH_1) +#endif // (#if-chain ref AVOID_CPU_ARCH_0) + +// -------- + +// Define WUFFS_CONFIG__STATIC_FUNCTIONS (combined with WUFFS_IMPLEMENTATION) +// to make all of Wuffs' functions have static storage. +// +// This can help the compiler ignore or discard unused code, which can produce +// faster compiles and smaller binaries. Other motivations are discussed in the +// "ALLOW STATIC IMPLEMENTATION" section of +// https://raw.githubusercontent.com/nothings/stb/master/docs/stb_howto.txt +#if defined(WUFFS_CONFIG__STATIC_FUNCTIONS) +#define WUFFS_BASE__MAYBE_STATIC static +#else +#define WUFFS_BASE__MAYBE_STATIC +#endif // defined(WUFFS_CONFIG__STATIC_FUNCTIONS) + +// ---------------- CPU Architecture + +static inline bool // +wuffs_base__cpu_arch__have_arm_crc32() { +#if defined(WUFFS_BASE__CPU_ARCH__ARM_CRC32) + return true; +#else + return false; +#endif // defined(WUFFS_BASE__CPU_ARCH__ARM_CRC32) +} + +static inline bool // +wuffs_base__cpu_arch__have_arm_neon() { +#if defined(WUFFS_BASE__CPU_ARCH__ARM_NEON) + return true; +#else + return false; +#endif // defined(WUFFS_BASE__CPU_ARCH__ARM_NEON) +} + +static inline bool // +wuffs_base__cpu_arch__have_x86_avx2() { +#if defined(WUFFS_BASE__CPU_ARCH__X86_FAMILY) + // GCC defines these macros but MSVC does not. + // - bit_AVX2 = (1 << 5) + const unsigned int avx2_ebx7 = 0x00000020; + // GCC defines these macros but MSVC does not. + // - bit_PCLMUL = (1 << 1) + // - bit_POPCNT = (1 << 23) + // - bit_SSE4_2 = (1 << 20) + const unsigned int avx2_ecx1 = 0x00900002; + + // clang defines __GNUC__ and clang-cl defines _MSC_VER (but not __GNUC__). +#if defined(__GNUC__) + unsigned int eax7 = 0; + unsigned int ebx7 = 0; + unsigned int ecx7 = 0; + unsigned int edx7 = 0; + if (__get_cpuid_count(7, 0, &eax7, &ebx7, &ecx7, &edx7) && + ((ebx7 & avx2_ebx7) == avx2_ebx7)) { + unsigned int eax1 = 0; + unsigned int ebx1 = 0; + unsigned int ecx1 = 0; + unsigned int edx1 = 0; + if (__get_cpuid(1, &eax1, &ebx1, &ecx1, &edx1) && + ((ecx1 & avx2_ecx1) == avx2_ecx1)) { + return true; + } + } +#elif defined(_MSC_VER) // defined(__GNUC__) + int x7[4]; + __cpuidex(x7, 7, 0); + if ((((unsigned int)(x7[1])) & avx2_ebx7) == avx2_ebx7) { + int x1[4]; + __cpuid(x1, 1); + if ((((unsigned int)(x1[2])) & avx2_ecx1) == avx2_ecx1) { + return true; + } + } +#else +#error "WUFFS_BASE__CPU_ARCH__ETC combined with an unsupported compiler" +#endif // defined(__GNUC__); defined(_MSC_VER) +#endif // defined(WUFFS_BASE__CPU_ARCH__X86_FAMILY) + return false; +} + +static inline bool // +wuffs_base__cpu_arch__have_x86_bmi2() { +#if defined(WUFFS_BASE__CPU_ARCH__X86_FAMILY) + // GCC defines these macros but MSVC does not. + // - bit_BMI2 = (1 << 8) + const unsigned int bmi2_ebx7 = 0x00000100; + + // clang defines __GNUC__ and clang-cl defines _MSC_VER (but not __GNUC__). +#if defined(__GNUC__) + unsigned int eax7 = 0; + unsigned int ebx7 = 0; + unsigned int ecx7 = 0; + unsigned int edx7 = 0; + if (__get_cpuid_count(7, 0, &eax7, &ebx7, &ecx7, &edx7) && + ((ebx7 & bmi2_ebx7) == bmi2_ebx7)) { + return true; + } +#elif defined(_MSC_VER) // defined(__GNUC__) + int x7[4]; + __cpuidex(x7, 7, 0); + if ((((unsigned int)(x7[1])) & bmi2_ebx7) == bmi2_ebx7) { + return true; + } +#else +#error "WUFFS_BASE__CPU_ARCH__ETC combined with an unsupported compiler" +#endif // defined(__GNUC__); defined(_MSC_VER) +#endif // defined(WUFFS_BASE__CPU_ARCH__X86_FAMILY) + return false; +} + +static inline bool // +wuffs_base__cpu_arch__have_x86_sse42() { +#if defined(WUFFS_BASE__CPU_ARCH__X86_FAMILY) + // GCC defines these macros but MSVC does not. + // - bit_PCLMUL = (1 << 1) + // - bit_POPCNT = (1 << 23) + // - bit_SSE4_2 = (1 << 20) + const unsigned int sse42_ecx1 = 0x00900002; + + // clang defines __GNUC__ and clang-cl defines _MSC_VER (but not __GNUC__). +#if defined(__GNUC__) + unsigned int eax1 = 0; + unsigned int ebx1 = 0; + unsigned int ecx1 = 0; + unsigned int edx1 = 0; + if (__get_cpuid(1, &eax1, &ebx1, &ecx1, &edx1) && + ((ecx1 & sse42_ecx1) == sse42_ecx1)) { + return true; + } +#elif defined(_MSC_VER) // defined(__GNUC__) + int x1[4]; + __cpuid(x1, 1); + if ((((unsigned int)(x1[2])) & sse42_ecx1) == sse42_ecx1) { + return true; + } +#else +#error "WUFFS_BASE__CPU_ARCH__ETC combined with an unsupported compiler" +#endif // defined(__GNUC__); defined(_MSC_VER) +#endif // defined(WUFFS_BASE__CPU_ARCH__X86_FAMILY) + return false; +} + +// ---------------- Fundamentals + +// Wuffs assumes that: +// - converting a uint32_t to a size_t will never overflow. +// - converting a size_t to a uint64_t will never overflow. +#if defined(__WORDSIZE) +#if (__WORDSIZE != 32) && (__WORDSIZE != 64) +#error "Wuffs requires a word size of either 32 or 64 bits" +#endif +#endif + +// The "defined(__clang__)" isn't redundant. While vanilla clang defines +// __GNUC__, clang-cl (which mimics MSVC's cl.exe) does not. +#if defined(__GNUC__) || defined(__clang__) +#define WUFFS_BASE__POTENTIALLY_UNUSED __attribute__((unused)) +#define WUFFS_BASE__WARN_UNUSED_RESULT __attribute__((warn_unused_result)) +#else +#define WUFFS_BASE__POTENTIALLY_UNUSED +#define WUFFS_BASE__WARN_UNUSED_RESULT +#endif + +// -------- + +// Options (bitwise or'ed together) for wuffs_foo__bar__initialize functions. + +#define WUFFS_INITIALIZE__DEFAULT_OPTIONS ((uint32_t)0x00000000) + +// WUFFS_INITIALIZE__ALREADY_ZEROED means that the "self" receiver struct value +// has already been set to all zeroes. +#define WUFFS_INITIALIZE__ALREADY_ZEROED ((uint32_t)0x00000001) + +// WUFFS_INITIALIZE__LEAVE_INTERNAL_BUFFERS_UNINITIALIZED means that, absent +// WUFFS_INITIALIZE__ALREADY_ZEROED, only some of the "self" receiver struct +// value will be set to all zeroes. Internal buffers, which tend to be a large +// proportion of the struct's size, will be left uninitialized. Internal means +// that the buffer is contained by the receiver struct, as opposed to being +// passed as a separately allocated "work buffer". +// +// For more detail, see: +// https://github.com/google/wuffs/blob/main/doc/note/initialization.md +#define WUFFS_INITIALIZE__LEAVE_INTERNAL_BUFFERS_UNINITIALIZED \ + ((uint32_t)0x00000002) + +// -------- + +// wuffs_base__empty_struct is used when a Wuffs function returns an empty +// struct. In C, if a function f returns void, you can't say "x = f()", but in +// Wuffs, if a function g returns empty, you can say "y = g()". +typedef struct wuffs_base__empty_struct__struct { + // private_impl is a placeholder field. It isn't explicitly used, except that + // without it, the sizeof a struct with no fields can differ across C/C++ + // compilers, and it is undefined behavior in C99. For example, gcc says that + // the sizeof an empty struct is 0, and g++ says that it is 1. This leads to + // ABI incompatibility if a Wuffs .c file is processed by one compiler and + // its .h file with another compiler. + // + // Instead, we explicitly insert an otherwise unused field, so that the + // sizeof this struct is always 1. + uint8_t private_impl; +} wuffs_base__empty_struct; + +static inline wuffs_base__empty_struct // +wuffs_base__make_empty_struct() { + wuffs_base__empty_struct ret; + ret.private_impl = 0; + return ret; +} + +// wuffs_base__utility is a placeholder receiver type. It enables what Java +// calls static methods, as opposed to regular methods. +typedef struct wuffs_base__utility__struct { + // private_impl is a placeholder field. It isn't explicitly used, except that + // without it, the sizeof a struct with no fields can differ across C/C++ + // compilers, and it is undefined behavior in C99. For example, gcc says that + // the sizeof an empty struct is 0, and g++ says that it is 1. This leads to + // ABI incompatibility if a Wuffs .c file is processed by one compiler and + // its .h file with another compiler. + // + // Instead, we explicitly insert an otherwise unused field, so that the + // sizeof this struct is always 1. + uint8_t private_impl; +} wuffs_base__utility; + +typedef struct wuffs_base__vtable__struct { + const char* vtable_name; + const void* function_pointers; +} wuffs_base__vtable; + +// -------- + +// See https://github.com/google/wuffs/blob/main/doc/note/statuses.md +typedef struct wuffs_base__status__struct { + const char* repr; + +#ifdef __cplusplus + inline bool is_complete() const; + inline bool is_error() const; + inline bool is_note() const; + inline bool is_ok() const; + inline bool is_suspension() const; + inline const char* message() const; +#endif // __cplusplus + +} wuffs_base__status; + +extern const char wuffs_base__note__i_o_redirect[]; +extern const char wuffs_base__note__end_of_data[]; +extern const char wuffs_base__note__metadata_reported[]; +extern const char wuffs_base__suspension__even_more_information[]; +extern const char wuffs_base__suspension__mispositioned_read[]; +extern const char wuffs_base__suspension__mispositioned_write[]; +extern const char wuffs_base__suspension__short_read[]; +extern const char wuffs_base__suspension__short_write[]; +extern const char wuffs_base__error__bad_i_o_position[]; +extern const char wuffs_base__error__bad_argument_length_too_short[]; +extern const char wuffs_base__error__bad_argument[]; +extern const char wuffs_base__error__bad_call_sequence[]; +extern const char wuffs_base__error__bad_data[]; +extern const char wuffs_base__error__bad_receiver[]; +extern const char wuffs_base__error__bad_restart[]; +extern const char wuffs_base__error__bad_sizeof_receiver[]; +extern const char wuffs_base__error__bad_vtable[]; +extern const char wuffs_base__error__bad_workbuf_length[]; +extern const char wuffs_base__error__bad_wuffs_version[]; +extern const char wuffs_base__error__cannot_return_a_suspension[]; +extern const char wuffs_base__error__disabled_by_previous_error[]; +extern const char wuffs_base__error__initialize_falsely_claimed_already_zeroed[]; +extern const char wuffs_base__error__initialize_not_called[]; +extern const char wuffs_base__error__interleaved_coroutine_calls[]; +extern const char wuffs_base__error__no_more_information[]; +extern const char wuffs_base__error__not_enough_data[]; +extern const char wuffs_base__error__out_of_bounds[]; +extern const char wuffs_base__error__unsupported_method[]; +extern const char wuffs_base__error__unsupported_option[]; +extern const char wuffs_base__error__unsupported_pixel_swizzler_option[]; +extern const char wuffs_base__error__too_much_data[]; + +static inline wuffs_base__status // +wuffs_base__make_status(const char* repr) { + wuffs_base__status z; + z.repr = repr; + return z; +} + +static inline bool // +wuffs_base__status__is_complete(const wuffs_base__status* z) { + return (z->repr == NULL) || ((*z->repr != '$') && (*z->repr != '#')); +} + +static inline bool // +wuffs_base__status__is_error(const wuffs_base__status* z) { + return z->repr && (*z->repr == '#'); +} + +static inline bool // +wuffs_base__status__is_note(const wuffs_base__status* z) { + return z->repr && (*z->repr != '$') && (*z->repr != '#'); +} + +static inline bool // +wuffs_base__status__is_ok(const wuffs_base__status* z) { + return z->repr == NULL; +} + +static inline bool // +wuffs_base__status__is_suspension(const wuffs_base__status* z) { + return z->repr && (*z->repr == '$'); +} + +// wuffs_base__status__message strips the leading '$', '#' or '@'. +static inline const char* // +wuffs_base__status__message(const wuffs_base__status* z) { + if (z->repr) { + if ((*z->repr == '$') || (*z->repr == '#') || (*z->repr == '@')) { + return z->repr + 1; + } + } + return z->repr; +} + +#ifdef __cplusplus + +inline bool // +wuffs_base__status::is_complete() const { + return wuffs_base__status__is_complete(this); +} + +inline bool // +wuffs_base__status::is_error() const { + return wuffs_base__status__is_error(this); +} + +inline bool // +wuffs_base__status::is_note() const { + return wuffs_base__status__is_note(this); +} + +inline bool // +wuffs_base__status::is_ok() const { + return wuffs_base__status__is_ok(this); +} + +inline bool // +wuffs_base__status::is_suspension() const { + return wuffs_base__status__is_suspension(this); +} + +inline const char* // +wuffs_base__status::message() const { + return wuffs_base__status__message(this); +} + +#endif // __cplusplus + +// -------- + +// WUFFS_BASE__RESULT is a result type: either a status (an error) or a value. +// +// A result with all fields NULL or zero is as valid as a zero-valued T. +#define WUFFS_BASE__RESULT(T) \ + struct { \ + wuffs_base__status status; \ + T value; \ + } + +typedef WUFFS_BASE__RESULT(double) wuffs_base__result_f64; +typedef WUFFS_BASE__RESULT(int64_t) wuffs_base__result_i64; +typedef WUFFS_BASE__RESULT(uint64_t) wuffs_base__result_u64; + +// -------- + +// wuffs_base__transform__output is the result of transforming from a src slice +// to a dst slice. +typedef struct wuffs_base__transform__output__struct { + wuffs_base__status status; + size_t num_dst; + size_t num_src; +} wuffs_base__transform__output; + +// -------- + +// FourCC constants. Four Character Codes are literally four ASCII characters +// (sometimes padded with ' ' spaces) that pack neatly into a signed or +// unsigned 32-bit integer. ASCII letters are conventionally upper case. +// +// They are often used to identify video codecs (e.g. "H265") and pixel formats +// (e.g. "YV12"). Wuffs uses them for that but also generally for naming +// various things: compression formats (e.g. "BZ2 "), image metadata (e.g. +// "EXIF"), file formats (e.g. "HTML"), etc. +// +// Wuffs' u32 values are big-endian ("JPEG" is 0x4A504547 not 0x4745504A) to +// preserve ordering: "JPEG" < "MP3 " and 0x4A504547 < 0x4D503320. + +// Background Color. +#define WUFFS_BASE__FOURCC__BGCL 0x4247434C + +// Bitmap. +#define WUFFS_BASE__FOURCC__BMP 0x424D5020 + +// Brotli. +#define WUFFS_BASE__FOURCC__BRTL 0x4252544C + +// Bzip2. +#define WUFFS_BASE__FOURCC__BZ2 0x425A3220 + +// Concise Binary Object Representation. +#define WUFFS_BASE__FOURCC__CBOR 0x43424F52 + +// Primary Chromaticities and White Point. +#define WUFFS_BASE__FOURCC__CHRM 0x4348524D + +// Cascading Style Sheets. +#define WUFFS_BASE__FOURCC__CSS 0x43535320 + +// Encapsulated PostScript. +#define WUFFS_BASE__FOURCC__EPS 0x45505320 + +// Exchangeable Image File Format. +#define WUFFS_BASE__FOURCC__EXIF 0x45584946 + +// Free Lossless Audio Codec. +#define WUFFS_BASE__FOURCC__FLAC 0x464C4143 + +// Gamma Correction. +#define WUFFS_BASE__FOURCC__GAMA 0x47414D41 + +// Graphics Interchange Format. +#define WUFFS_BASE__FOURCC__GIF 0x47494620 + +// GNU Zip. +#define WUFFS_BASE__FOURCC__GZ 0x475A2020 + +// High Efficiency Image File. +#define WUFFS_BASE__FOURCC__HEIF 0x48454946 + +// Hypertext Markup Language. +#define WUFFS_BASE__FOURCC__HTML 0x48544D4C + +// International Color Consortium Profile. +#define WUFFS_BASE__FOURCC__ICCP 0x49434350 + +// Icon. +#define WUFFS_BASE__FOURCC__ICO 0x49434F20 + +// Icon Vector Graphics. +#define WUFFS_BASE__FOURCC__ICVG 0x49435647 + +// Initialization. +#define WUFFS_BASE__FOURCC__INI 0x494E4920 + +// Joint Photographic Experts Group. +#define WUFFS_BASE__FOURCC__JPEG 0x4A504547 + +// JavaScript. +#define WUFFS_BASE__FOURCC__JS 0x4A532020 + +// JavaScript Object Notation. +#define WUFFS_BASE__FOURCC__JSON 0x4A534F4E + +// JSON With Commas and Comments. +#define WUFFS_BASE__FOURCC__JWCC 0x4A574343 + +// Key-Value Pair. +#define WUFFS_BASE__FOURCC__KVP 0x4B565020 + +// Key-Value Pair (Key). +#define WUFFS_BASE__FOURCC__KVPK 0x4B56504B + +// Key-Value Pair (Value). +#define WUFFS_BASE__FOURCC__KVPV 0x4B565056 + +// Lempel–Ziv 4. +#define WUFFS_BASE__FOURCC__LZ4 0x4C5A3420 + +// Markdown. +#define WUFFS_BASE__FOURCC__MD 0x4D442020 + +// Modification Time. +#define WUFFS_BASE__FOURCC__MTIM 0x4D54494D + +// MPEG-1 Audio Layer III. +#define WUFFS_BASE__FOURCC__MP3 0x4D503320 + +// Naive Image. +#define WUFFS_BASE__FOURCC__NIE 0x4E494520 + +// Offset (2-Dimensional). +#define WUFFS_BASE__FOURCC__OFS2 0x4F465332 + +// Open Type Format. +#define WUFFS_BASE__FOURCC__OTF 0x4F544620 + +// Portable Document Format. +#define WUFFS_BASE__FOURCC__PDF 0x50444620 + +// Physical Dimensions. +#define WUFFS_BASE__FOURCC__PHYD 0x50485944 + +// Portable Network Graphics. +#define WUFFS_BASE__FOURCC__PNG 0x504E4720 + +// Portable Anymap. +#define WUFFS_BASE__FOURCC__PNM 0x504E4D20 + +// PostScript. +#define WUFFS_BASE__FOURCC__PS 0x50532020 + +// Quite OK Image. +#define WUFFS_BASE__FOURCC__QOI 0x514F4920 + +// Random Access Compression. +#define WUFFS_BASE__FOURCC__RAC 0x52414320 + +// Raw. +#define WUFFS_BASE__FOURCC__RAW 0x52415720 + +// Resource Interchange File Format. +#define WUFFS_BASE__FOURCC__RIFF 0x52494646 + +// Riegeli Records. +#define WUFFS_BASE__FOURCC__RIGL 0x5249474C + +// Snappy. +#define WUFFS_BASE__FOURCC__SNPY 0x534E5059 + +// Standard Red Green Blue (Rendering Intent). +#define WUFFS_BASE__FOURCC__SRGB 0x53524742 + +// Scalable Vector Graphics. +#define WUFFS_BASE__FOURCC__SVG 0x53564720 + +// Tape Archive. +#define WUFFS_BASE__FOURCC__TAR 0x54415220 + +// Text. +#define WUFFS_BASE__FOURCC__TEXT 0x54455854 + +// Truevision Advanced Raster Graphics Adapter. +#define WUFFS_BASE__FOURCC__TGA 0x54474120 + +// Tagged Image File Format. +#define WUFFS_BASE__FOURCC__TIFF 0x54494646 + +// Tom's Obvious Minimal Language. +#define WUFFS_BASE__FOURCC__TOML 0x544F4D4C + +// Waveform. +#define WUFFS_BASE__FOURCC__WAVE 0x57415645 + +// Wireless Bitmap. +#define WUFFS_BASE__FOURCC__WBMP 0x57424D50 + +// Web Picture. +#define WUFFS_BASE__FOURCC__WEBP 0x57454250 + +// Web Open Font Format. +#define WUFFS_BASE__FOURCC__WOFF 0x574F4646 + +// Extensible Markup Language. +#define WUFFS_BASE__FOURCC__XML 0x584D4C20 + +// Extensible Metadata Platform. +#define WUFFS_BASE__FOURCC__XMP 0x584D5020 + +// Xz. +#define WUFFS_BASE__FOURCC__XZ 0x585A2020 + +// Zip. +#define WUFFS_BASE__FOURCC__ZIP 0x5A495020 + +// Zlib. +#define WUFFS_BASE__FOURCC__ZLIB 0x5A4C4942 + +// Zstandard. +#define WUFFS_BASE__FOURCC__ZSTD 0x5A535444 + +// -------- + +// Quirks. + +#define WUFFS_BASE__QUIRK_IGNORE_CHECKSUM 1 + +// -------- + +// Flicks are a unit of time. One flick (frame-tick) is 1 / 705_600_000 of a +// second. See https://github.com/OculusVR/Flicks +typedef int64_t wuffs_base__flicks; + +#define WUFFS_BASE__FLICKS_PER_SECOND ((uint64_t)705600000) +#define WUFFS_BASE__FLICKS_PER_MILLISECOND ((uint64_t)705600) + +// ---------------- Numeric Types + +// The helpers below are functions, instead of macros, because their arguments +// can be an expression that we shouldn't evaluate more than once. +// +// They are static, so that linking multiple wuffs .o files won't complain about +// duplicate function definitions. +// +// They are explicitly marked inline, even if modern compilers don't use the +// inline attribute to guide optimizations such as inlining, to avoid the +// -Wunused-function warning, and we like to compile with -Wall -Werror. + +static inline int8_t // +wuffs_base__i8__min(int8_t x, int8_t y) { + return x < y ? x : y; +} + +static inline int8_t // +wuffs_base__i8__max(int8_t x, int8_t y) { + return x > y ? x : y; +} + +static inline int16_t // +wuffs_base__i16__min(int16_t x, int16_t y) { + return x < y ? x : y; +} + +static inline int16_t // +wuffs_base__i16__max(int16_t x, int16_t y) { + return x > y ? x : y; +} + +static inline int32_t // +wuffs_base__i32__min(int32_t x, int32_t y) { + return x < y ? x : y; +} + +static inline int32_t // +wuffs_base__i32__max(int32_t x, int32_t y) { + return x > y ? x : y; +} + +static inline int64_t // +wuffs_base__i64__min(int64_t x, int64_t y) { + return x < y ? x : y; +} + +static inline int64_t // +wuffs_base__i64__max(int64_t x, int64_t y) { + return x > y ? x : y; +} + +static inline uint8_t // +wuffs_base__u8__min(uint8_t x, uint8_t y) { + return x < y ? x : y; +} + +static inline uint8_t // +wuffs_base__u8__max(uint8_t x, uint8_t y) { + return x > y ? x : y; +} + +static inline uint16_t // +wuffs_base__u16__min(uint16_t x, uint16_t y) { + return x < y ? x : y; +} + +static inline uint16_t // +wuffs_base__u16__max(uint16_t x, uint16_t y) { + return x > y ? x : y; +} + +static inline uint32_t // +wuffs_base__u32__min(uint32_t x, uint32_t y) { + return x < y ? x : y; +} + +static inline uint32_t // +wuffs_base__u32__max(uint32_t x, uint32_t y) { + return x > y ? x : y; +} + +static inline uint64_t // +wuffs_base__u64__min(uint64_t x, uint64_t y) { + return x < y ? x : y; +} + +static inline uint64_t // +wuffs_base__u64__max(uint64_t x, uint64_t y) { + return x > y ? x : y; +} + +// -------- + +static inline uint8_t // +wuffs_base__u8__rotate_left(uint8_t x, uint32_t n) { + n &= 7; + return ((uint8_t)(x << n)) | ((uint8_t)(x >> (8 - n))); +} + +static inline uint8_t // +wuffs_base__u8__rotate_right(uint8_t x, uint32_t n) { + n &= 7; + return ((uint8_t)(x >> n)) | ((uint8_t)(x << (8 - n))); +} + +static inline uint16_t // +wuffs_base__u16__rotate_left(uint16_t x, uint32_t n) { + n &= 15; + return ((uint16_t)(x << n)) | ((uint16_t)(x >> (16 - n))); +} + +static inline uint16_t // +wuffs_base__u16__rotate_right(uint16_t x, uint32_t n) { + n &= 15; + return ((uint16_t)(x >> n)) | ((uint16_t)(x << (16 - n))); +} + +static inline uint32_t // +wuffs_base__u32__rotate_left(uint32_t x, uint32_t n) { + n &= 31; + return ((uint32_t)(x << n)) | ((uint32_t)(x >> (32 - n))); +} + +static inline uint32_t // +wuffs_base__u32__rotate_right(uint32_t x, uint32_t n) { + n &= 31; + return ((uint32_t)(x >> n)) | ((uint32_t)(x << (32 - n))); +} + +static inline uint64_t // +wuffs_base__u64__rotate_left(uint64_t x, uint32_t n) { + n &= 63; + return ((uint64_t)(x << n)) | ((uint64_t)(x >> (64 - n))); +} + +static inline uint64_t // +wuffs_base__u64__rotate_right(uint64_t x, uint32_t n) { + n &= 63; + return ((uint64_t)(x >> n)) | ((uint64_t)(x << (64 - n))); +} + +// -------- + +// Saturating arithmetic (sat_add, sat_sub) branchless bit-twiddling algorithms +// are per https://locklessinc.com/articles/sat_arithmetic/ +// +// It is important that the underlying types are unsigned integers, as signed +// integer arithmetic overflow is undefined behavior in C. + +static inline uint8_t // +wuffs_base__u8__sat_add(uint8_t x, uint8_t y) { + uint8_t res = (uint8_t)(x + y); + res |= (uint8_t)(-(res < x)); + return res; +} + +static inline uint8_t // +wuffs_base__u8__sat_sub(uint8_t x, uint8_t y) { + uint8_t res = (uint8_t)(x - y); + res &= (uint8_t)(-(res <= x)); + return res; +} + +static inline uint16_t // +wuffs_base__u16__sat_add(uint16_t x, uint16_t y) { + uint16_t res = (uint16_t)(x + y); + res |= (uint16_t)(-(res < x)); + return res; +} + +static inline uint16_t // +wuffs_base__u16__sat_sub(uint16_t x, uint16_t y) { + uint16_t res = (uint16_t)(x - y); + res &= (uint16_t)(-(res <= x)); + return res; +} + +static inline uint32_t // +wuffs_base__u32__sat_add(uint32_t x, uint32_t y) { + uint32_t res = (uint32_t)(x + y); + res |= (uint32_t)(-(res < x)); + return res; +} + +static inline uint32_t // +wuffs_base__u32__sat_sub(uint32_t x, uint32_t y) { + uint32_t res = (uint32_t)(x - y); + res &= (uint32_t)(-(res <= x)); + return res; +} + +static inline uint64_t // +wuffs_base__u64__sat_add(uint64_t x, uint64_t y) { + uint64_t res = (uint64_t)(x + y); + res |= (uint64_t)(-(res < x)); + return res; +} + +static inline uint64_t // +wuffs_base__u64__sat_sub(uint64_t x, uint64_t y) { + uint64_t res = (uint64_t)(x - y); + res &= (uint64_t)(-(res <= x)); + return res; +} + +// -------- + +typedef struct wuffs_base__multiply_u64__output__struct { + uint64_t lo; + uint64_t hi; +} wuffs_base__multiply_u64__output; + +// wuffs_base__multiply_u64 returns x*y as a 128-bit value. +// +// The maximum inclusive output hi_lo is 0xFFFFFFFFFFFFFFFE_0000000000000001. +static inline wuffs_base__multiply_u64__output // +wuffs_base__multiply_u64(uint64_t x, uint64_t y) { +#if defined(__SIZEOF_INT128__) + __uint128_t z = ((__uint128_t)x) * ((__uint128_t)y); + wuffs_base__multiply_u64__output o; + o.lo = ((uint64_t)(z)); + o.hi = ((uint64_t)(z >> 64)); + return o; +#else + // TODO: consider using the _mul128 intrinsic if defined(_MSC_VER). + uint64_t x0 = x & 0xFFFFFFFF; + uint64_t x1 = x >> 32; + uint64_t y0 = y & 0xFFFFFFFF; + uint64_t y1 = y >> 32; + uint64_t w0 = x0 * y0; + uint64_t t = (x1 * y0) + (w0 >> 32); + uint64_t w1 = t & 0xFFFFFFFF; + uint64_t w2 = t >> 32; + w1 += x0 * y1; + wuffs_base__multiply_u64__output o; + o.lo = x * y; + o.hi = (x1 * y1) + w2 + (w1 >> 32); + return o; +#endif +} + +// -------- + +// The "defined(__clang__)" isn't redundant. While vanilla clang defines +// __GNUC__, clang-cl (which mimics MSVC's cl.exe) does not. +#if (defined(__GNUC__) || defined(__clang__)) && (__SIZEOF_LONG__ == 8) + +static inline uint32_t // +wuffs_base__count_leading_zeroes_u64(uint64_t u) { + return u ? ((uint32_t)(__builtin_clzl(u))) : 64u; +} + +#else +// TODO: consider using the _BitScanReverse intrinsic if defined(_MSC_VER). + +static inline uint32_t // +wuffs_base__count_leading_zeroes_u64(uint64_t u) { + if (u == 0) { + return 64; + } + + uint32_t n = 0; + if ((u >> 32) == 0) { + n |= 32; + u <<= 32; + } + if ((u >> 48) == 0) { + n |= 16; + u <<= 16; + } + if ((u >> 56) == 0) { + n |= 8; + u <<= 8; + } + if ((u >> 60) == 0) { + n |= 4; + u <<= 4; + } + if ((u >> 62) == 0) { + n |= 2; + u <<= 2; + } + if ((u >> 63) == 0) { + n |= 1; + u <<= 1; + } + return n; +} + +#endif // (defined(__GNUC__) || defined(__clang__)) && (__SIZEOF_LONG__ == 8) + +// -------- + +// Normally, the wuffs_base__peek_etc and wuffs_base__poke_etc implementations +// are both (1) correct regardless of CPU endianness and (2) very fast (e.g. an +// inlined wuffs_base__peek_u32le__no_bounds_check call, in an optimized clang +// or gcc build, is a single MOV instruction on x86_64). +// +// However, the endian-agnostic implementations are slow on Microsoft's C +// compiler (MSC). Alternative memcpy-based implementations restore speed, but +// they are only correct on little-endian CPU architectures. Defining +// WUFFS_BASE__USE_MEMCPY_LE_PEEK_POKE opts in to these implementations. +// +// https://godbolt.org/z/q4MfjzTPh +#if defined(_MSC_VER) && !defined(__clang__) && \ + (defined(_M_ARM64) || defined(_M_X64)) +#define WUFFS_BASE__USE_MEMCPY_LE_PEEK_POKE +#endif + +#define wuffs_base__peek_u8be__no_bounds_check \ + wuffs_base__peek_u8__no_bounds_check +#define wuffs_base__peek_u8le__no_bounds_check \ + wuffs_base__peek_u8__no_bounds_check + +static inline uint8_t // +wuffs_base__peek_u8__no_bounds_check(const uint8_t* p) { + return p[0]; +} + +static inline uint16_t // +wuffs_base__peek_u16be__no_bounds_check(const uint8_t* p) { +#if defined(WUFFS_BASE__USE_MEMCPY_LE_PEEK_POKE) + uint16_t x; + memcpy(&x, p, 2); + return _byteswap_ushort(x); +#else + return (uint16_t)(((uint16_t)(p[0]) << 8) | ((uint16_t)(p[1]) << 0)); +#endif +} + +static inline uint16_t // +wuffs_base__peek_u16le__no_bounds_check(const uint8_t* p) { +#if defined(WUFFS_BASE__USE_MEMCPY_LE_PEEK_POKE) + uint16_t x; + memcpy(&x, p, 2); + return x; +#else + return (uint16_t)(((uint16_t)(p[0]) << 0) | ((uint16_t)(p[1]) << 8)); +#endif +} + +static inline uint32_t // +wuffs_base__peek_u24be__no_bounds_check(const uint8_t* p) { + return ((uint32_t)(p[0]) << 16) | ((uint32_t)(p[1]) << 8) | + ((uint32_t)(p[2]) << 0); +} + +static inline uint32_t // +wuffs_base__peek_u24le__no_bounds_check(const uint8_t* p) { + return ((uint32_t)(p[0]) << 0) | ((uint32_t)(p[1]) << 8) | + ((uint32_t)(p[2]) << 16); +} + +static inline uint32_t // +wuffs_base__peek_u32be__no_bounds_check(const uint8_t* p) { +#if defined(WUFFS_BASE__USE_MEMCPY_LE_PEEK_POKE) + uint32_t x; + memcpy(&x, p, 4); + return _byteswap_ulong(x); +#else + return ((uint32_t)(p[0]) << 24) | ((uint32_t)(p[1]) << 16) | + ((uint32_t)(p[2]) << 8) | ((uint32_t)(p[3]) << 0); +#endif +} + +static inline uint32_t // +wuffs_base__peek_u32le__no_bounds_check(const uint8_t* p) { +#if defined(WUFFS_BASE__USE_MEMCPY_LE_PEEK_POKE) + uint32_t x; + memcpy(&x, p, 4); + return x; +#else + return ((uint32_t)(p[0]) << 0) | ((uint32_t)(p[1]) << 8) | + ((uint32_t)(p[2]) << 16) | ((uint32_t)(p[3]) << 24); +#endif +} + +static inline uint64_t // +wuffs_base__peek_u40be__no_bounds_check(const uint8_t* p) { + return ((uint64_t)(p[0]) << 32) | ((uint64_t)(p[1]) << 24) | + ((uint64_t)(p[2]) << 16) | ((uint64_t)(p[3]) << 8) | + ((uint64_t)(p[4]) << 0); +} + +static inline uint64_t // +wuffs_base__peek_u40le__no_bounds_check(const uint8_t* p) { + return ((uint64_t)(p[0]) << 0) | ((uint64_t)(p[1]) << 8) | + ((uint64_t)(p[2]) << 16) | ((uint64_t)(p[3]) << 24) | + ((uint64_t)(p[4]) << 32); +} + +static inline uint64_t // +wuffs_base__peek_u48be__no_bounds_check(const uint8_t* p) { + return ((uint64_t)(p[0]) << 40) | ((uint64_t)(p[1]) << 32) | + ((uint64_t)(p[2]) << 24) | ((uint64_t)(p[3]) << 16) | + ((uint64_t)(p[4]) << 8) | ((uint64_t)(p[5]) << 0); +} + +static inline uint64_t // +wuffs_base__peek_u48le__no_bounds_check(const uint8_t* p) { + return ((uint64_t)(p[0]) << 0) | ((uint64_t)(p[1]) << 8) | + ((uint64_t)(p[2]) << 16) | ((uint64_t)(p[3]) << 24) | + ((uint64_t)(p[4]) << 32) | ((uint64_t)(p[5]) << 40); +} + +static inline uint64_t // +wuffs_base__peek_u56be__no_bounds_check(const uint8_t* p) { + return ((uint64_t)(p[0]) << 48) | ((uint64_t)(p[1]) << 40) | + ((uint64_t)(p[2]) << 32) | ((uint64_t)(p[3]) << 24) | + ((uint64_t)(p[4]) << 16) | ((uint64_t)(p[5]) << 8) | + ((uint64_t)(p[6]) << 0); +} + +static inline uint64_t // +wuffs_base__peek_u56le__no_bounds_check(const uint8_t* p) { + return ((uint64_t)(p[0]) << 0) | ((uint64_t)(p[1]) << 8) | + ((uint64_t)(p[2]) << 16) | ((uint64_t)(p[3]) << 24) | + ((uint64_t)(p[4]) << 32) | ((uint64_t)(p[5]) << 40) | + ((uint64_t)(p[6]) << 48); +} + +static inline uint64_t // +wuffs_base__peek_u64be__no_bounds_check(const uint8_t* p) { +#if defined(WUFFS_BASE__USE_MEMCPY_LE_PEEK_POKE) + uint64_t x; + memcpy(&x, p, 8); + return _byteswap_uint64(x); +#else + return ((uint64_t)(p[0]) << 56) | ((uint64_t)(p[1]) << 48) | + ((uint64_t)(p[2]) << 40) | ((uint64_t)(p[3]) << 32) | + ((uint64_t)(p[4]) << 24) | ((uint64_t)(p[5]) << 16) | + ((uint64_t)(p[6]) << 8) | ((uint64_t)(p[7]) << 0); +#endif +} + +static inline uint64_t // +wuffs_base__peek_u64le__no_bounds_check(const uint8_t* p) { +#if defined(WUFFS_BASE__USE_MEMCPY_LE_PEEK_POKE) + uint64_t x; + memcpy(&x, p, 8); + return x; +#else + return ((uint64_t)(p[0]) << 0) | ((uint64_t)(p[1]) << 8) | + ((uint64_t)(p[2]) << 16) | ((uint64_t)(p[3]) << 24) | + ((uint64_t)(p[4]) << 32) | ((uint64_t)(p[5]) << 40) | + ((uint64_t)(p[6]) << 48) | ((uint64_t)(p[7]) << 56); +#endif +} + +// -------- + +#define wuffs_base__poke_u8be__no_bounds_check \ + wuffs_base__poke_u8__no_bounds_check +#define wuffs_base__poke_u8le__no_bounds_check \ + wuffs_base__poke_u8__no_bounds_check + +static inline void // +wuffs_base__poke_u8__no_bounds_check(uint8_t* p, uint8_t x) { + p[0] = x; +} + +static inline void // +wuffs_base__poke_u16be__no_bounds_check(uint8_t* p, uint16_t x) { + p[0] = (uint8_t)(x >> 8); + p[1] = (uint8_t)(x >> 0); +} + +static inline void // +wuffs_base__poke_u16le__no_bounds_check(uint8_t* p, uint16_t x) { +#if defined(WUFFS_BASE__USE_MEMCPY_LE_PEEK_POKE) || \ + (defined(__GNUC__) && !defined(__clang__) && defined(__x86_64__)) + // This seems to perform better on gcc 10 (but not clang 9). Clang also + // defines "__GNUC__". + memcpy(p, &x, 2); +#else + p[0] = (uint8_t)(x >> 0); + p[1] = (uint8_t)(x >> 8); +#endif +} + +static inline void // +wuffs_base__poke_u24be__no_bounds_check(uint8_t* p, uint32_t x) { + p[0] = (uint8_t)(x >> 16); + p[1] = (uint8_t)(x >> 8); + p[2] = (uint8_t)(x >> 0); +} + +static inline void // +wuffs_base__poke_u24le__no_bounds_check(uint8_t* p, uint32_t x) { + p[0] = (uint8_t)(x >> 0); + p[1] = (uint8_t)(x >> 8); + p[2] = (uint8_t)(x >> 16); +} + +static inline void // +wuffs_base__poke_u32be__no_bounds_check(uint8_t* p, uint32_t x) { + p[0] = (uint8_t)(x >> 24); + p[1] = (uint8_t)(x >> 16); + p[2] = (uint8_t)(x >> 8); + p[3] = (uint8_t)(x >> 0); +} + +static inline void // +wuffs_base__poke_u32le__no_bounds_check(uint8_t* p, uint32_t x) { +#if defined(WUFFS_BASE__USE_MEMCPY_LE_PEEK_POKE) || \ + (defined(__GNUC__) && !defined(__clang__) && defined(__x86_64__)) + // This seems to perform better on gcc 10 (but not clang 9). Clang also + // defines "__GNUC__". + memcpy(p, &x, 4); +#else + p[0] = (uint8_t)(x >> 0); + p[1] = (uint8_t)(x >> 8); + p[2] = (uint8_t)(x >> 16); + p[3] = (uint8_t)(x >> 24); +#endif +} + +static inline void // +wuffs_base__poke_u40be__no_bounds_check(uint8_t* p, uint64_t x) { + p[0] = (uint8_t)(x >> 32); + p[1] = (uint8_t)(x >> 24); + p[2] = (uint8_t)(x >> 16); + p[3] = (uint8_t)(x >> 8); + p[4] = (uint8_t)(x >> 0); +} + +static inline void // +wuffs_base__poke_u40le__no_bounds_check(uint8_t* p, uint64_t x) { + p[0] = (uint8_t)(x >> 0); + p[1] = (uint8_t)(x >> 8); + p[2] = (uint8_t)(x >> 16); + p[3] = (uint8_t)(x >> 24); + p[4] = (uint8_t)(x >> 32); +} + +static inline void // +wuffs_base__poke_u48be__no_bounds_check(uint8_t* p, uint64_t x) { + p[0] = (uint8_t)(x >> 40); + p[1] = (uint8_t)(x >> 32); + p[2] = (uint8_t)(x >> 24); + p[3] = (uint8_t)(x >> 16); + p[4] = (uint8_t)(x >> 8); + p[5] = (uint8_t)(x >> 0); +} + +static inline void // +wuffs_base__poke_u48le__no_bounds_check(uint8_t* p, uint64_t x) { + p[0] = (uint8_t)(x >> 0); + p[1] = (uint8_t)(x >> 8); + p[2] = (uint8_t)(x >> 16); + p[3] = (uint8_t)(x >> 24); + p[4] = (uint8_t)(x >> 32); + p[5] = (uint8_t)(x >> 40); +} + +static inline void // +wuffs_base__poke_u56be__no_bounds_check(uint8_t* p, uint64_t x) { + p[0] = (uint8_t)(x >> 48); + p[1] = (uint8_t)(x >> 40); + p[2] = (uint8_t)(x >> 32); + p[3] = (uint8_t)(x >> 24); + p[4] = (uint8_t)(x >> 16); + p[5] = (uint8_t)(x >> 8); + p[6] = (uint8_t)(x >> 0); +} + +static inline void // +wuffs_base__poke_u56le__no_bounds_check(uint8_t* p, uint64_t x) { + p[0] = (uint8_t)(x >> 0); + p[1] = (uint8_t)(x >> 8); + p[2] = (uint8_t)(x >> 16); + p[3] = (uint8_t)(x >> 24); + p[4] = (uint8_t)(x >> 32); + p[5] = (uint8_t)(x >> 40); + p[6] = (uint8_t)(x >> 48); +} + +static inline void // +wuffs_base__poke_u64be__no_bounds_check(uint8_t* p, uint64_t x) { + p[0] = (uint8_t)(x >> 56); + p[1] = (uint8_t)(x >> 48); + p[2] = (uint8_t)(x >> 40); + p[3] = (uint8_t)(x >> 32); + p[4] = (uint8_t)(x >> 24); + p[5] = (uint8_t)(x >> 16); + p[6] = (uint8_t)(x >> 8); + p[7] = (uint8_t)(x >> 0); +} + +static inline void // +wuffs_base__poke_u64le__no_bounds_check(uint8_t* p, uint64_t x) { +#if defined(WUFFS_BASE__USE_MEMCPY_LE_PEEK_POKE) || \ + (defined(__GNUC__) && !defined(__clang__) && defined(__x86_64__)) + // This seems to perform better on gcc 10 (but not clang 9). Clang also + // defines "__GNUC__". + memcpy(p, &x, 8); +#else + p[0] = (uint8_t)(x >> 0); + p[1] = (uint8_t)(x >> 8); + p[2] = (uint8_t)(x >> 16); + p[3] = (uint8_t)(x >> 24); + p[4] = (uint8_t)(x >> 32); + p[5] = (uint8_t)(x >> 40); + p[6] = (uint8_t)(x >> 48); + p[7] = (uint8_t)(x >> 56); +#endif +} + +// -------- + +// Load and Store functions are deprecated. Use Peek and Poke instead. + +#define wuffs_base__load_u8__no_bounds_check \ + wuffs_base__peek_u8__no_bounds_check +#define wuffs_base__load_u16be__no_bounds_check \ + wuffs_base__peek_u16be__no_bounds_check +#define wuffs_base__load_u16le__no_bounds_check \ + wuffs_base__peek_u16le__no_bounds_check +#define wuffs_base__load_u24be__no_bounds_check \ + wuffs_base__peek_u24be__no_bounds_check +#define wuffs_base__load_u24le__no_bounds_check \ + wuffs_base__peek_u24le__no_bounds_check +#define wuffs_base__load_u32be__no_bounds_check \ + wuffs_base__peek_u32be__no_bounds_check +#define wuffs_base__load_u32le__no_bounds_check \ + wuffs_base__peek_u32le__no_bounds_check +#define wuffs_base__load_u40be__no_bounds_check \ + wuffs_base__peek_u40be__no_bounds_check +#define wuffs_base__load_u40le__no_bounds_check \ + wuffs_base__peek_u40le__no_bounds_check +#define wuffs_base__load_u48be__no_bounds_check \ + wuffs_base__peek_u48be__no_bounds_check +#define wuffs_base__load_u48le__no_bounds_check \ + wuffs_base__peek_u48le__no_bounds_check +#define wuffs_base__load_u56be__no_bounds_check \ + wuffs_base__peek_u56be__no_bounds_check +#define wuffs_base__load_u56le__no_bounds_check \ + wuffs_base__peek_u56le__no_bounds_check +#define wuffs_base__load_u64be__no_bounds_check \ + wuffs_base__peek_u64be__no_bounds_check +#define wuffs_base__load_u64le__no_bounds_check \ + wuffs_base__peek_u64le__no_bounds_check + +#define wuffs_base__store_u8__no_bounds_check \ + wuffs_base__poke_u8__no_bounds_check +#define wuffs_base__store_u16be__no_bounds_check \ + wuffs_base__poke_u16be__no_bounds_check +#define wuffs_base__store_u16le__no_bounds_check \ + wuffs_base__poke_u16le__no_bounds_check +#define wuffs_base__store_u24be__no_bounds_check \ + wuffs_base__poke_u24be__no_bounds_check +#define wuffs_base__store_u24le__no_bounds_check \ + wuffs_base__poke_u24le__no_bounds_check +#define wuffs_base__store_u32be__no_bounds_check \ + wuffs_base__poke_u32be__no_bounds_check +#define wuffs_base__store_u32le__no_bounds_check \ + wuffs_base__poke_u32le__no_bounds_check +#define wuffs_base__store_u40be__no_bounds_check \ + wuffs_base__poke_u40be__no_bounds_check +#define wuffs_base__store_u40le__no_bounds_check \ + wuffs_base__poke_u40le__no_bounds_check +#define wuffs_base__store_u48be__no_bounds_check \ + wuffs_base__poke_u48be__no_bounds_check +#define wuffs_base__store_u48le__no_bounds_check \ + wuffs_base__poke_u48le__no_bounds_check +#define wuffs_base__store_u56be__no_bounds_check \ + wuffs_base__poke_u56be__no_bounds_check +#define wuffs_base__store_u56le__no_bounds_check \ + wuffs_base__poke_u56le__no_bounds_check +#define wuffs_base__store_u64be__no_bounds_check \ + wuffs_base__poke_u64be__no_bounds_check +#define wuffs_base__store_u64le__no_bounds_check \ + wuffs_base__poke_u64le__no_bounds_check + +// ---------------- Slices and Tables + +// WUFFS_BASE__SLICE is a 1-dimensional buffer. +// +// len measures a number of elements, not necessarily a size in bytes. +// +// A value with all fields NULL or zero is a valid, empty slice. +#define WUFFS_BASE__SLICE(T) \ + struct { \ + T* ptr; \ + size_t len; \ + } + +// WUFFS_BASE__TABLE is a 2-dimensional buffer. +// +// width, height and stride measure a number of elements, not necessarily a +// size in bytes. +// +// A value with all fields NULL or zero is a valid, empty table. +#define WUFFS_BASE__TABLE(T) \ + struct { \ + T* ptr; \ + size_t width; \ + size_t height; \ + size_t stride; \ + } + +typedef WUFFS_BASE__SLICE(uint8_t) wuffs_base__slice_u8; +typedef WUFFS_BASE__SLICE(uint16_t) wuffs_base__slice_u16; +typedef WUFFS_BASE__SLICE(uint32_t) wuffs_base__slice_u32; +typedef WUFFS_BASE__SLICE(uint64_t) wuffs_base__slice_u64; + +typedef WUFFS_BASE__TABLE(uint8_t) wuffs_base__table_u8; +typedef WUFFS_BASE__TABLE(uint16_t) wuffs_base__table_u16; +typedef WUFFS_BASE__TABLE(uint32_t) wuffs_base__table_u32; +typedef WUFFS_BASE__TABLE(uint64_t) wuffs_base__table_u64; + +static inline wuffs_base__slice_u8 // +wuffs_base__make_slice_u8(uint8_t* ptr, size_t len) { + wuffs_base__slice_u8 ret; + ret.ptr = ptr; + ret.len = len; + return ret; +} + +static inline wuffs_base__slice_u16 // +wuffs_base__make_slice_u16(uint16_t* ptr, size_t len) { + wuffs_base__slice_u16 ret; + ret.ptr = ptr; + ret.len = len; + return ret; +} + +static inline wuffs_base__slice_u32 // +wuffs_base__make_slice_u32(uint32_t* ptr, size_t len) { + wuffs_base__slice_u32 ret; + ret.ptr = ptr; + ret.len = len; + return ret; +} + +static inline wuffs_base__slice_u64 // +wuffs_base__make_slice_u64(uint64_t* ptr, size_t len) { + wuffs_base__slice_u64 ret; + ret.ptr = ptr; + ret.len = len; + return ret; +} + +static inline wuffs_base__slice_u8 // +wuffs_base__make_slice_u8_ij(uint8_t* ptr, size_t i, size_t j) { + wuffs_base__slice_u8 ret; + ret.ptr = ptr + i; + ret.len = (j >= i) ? (j - i) : 0; + return ret; +} + +static inline wuffs_base__slice_u16 // +wuffs_base__make_slice_u16_ij(uint16_t* ptr, size_t i, size_t j) { + wuffs_base__slice_u16 ret; + ret.ptr = ptr + i; + ret.len = (j >= i) ? (j - i) : 0; + return ret; +} + +static inline wuffs_base__slice_u32 // +wuffs_base__make_slice_u32_ij(uint32_t* ptr, size_t i, size_t j) { + wuffs_base__slice_u32 ret; + ret.ptr = ptr + i; + ret.len = (j >= i) ? (j - i) : 0; + return ret; +} + +static inline wuffs_base__slice_u64 // +wuffs_base__make_slice_u64_ij(uint64_t* ptr, size_t i, size_t j) { + wuffs_base__slice_u64 ret; + ret.ptr = ptr + i; + ret.len = (j >= i) ? (j - i) : 0; + return ret; +} + +static inline wuffs_base__slice_u8 // +wuffs_base__empty_slice_u8() { + wuffs_base__slice_u8 ret; + ret.ptr = NULL; + ret.len = 0; + return ret; +} + +static inline wuffs_base__slice_u16 // +wuffs_base__empty_slice_u16() { + wuffs_base__slice_u16 ret; + ret.ptr = NULL; + ret.len = 0; + return ret; +} + +static inline wuffs_base__slice_u32 // +wuffs_base__empty_slice_u32() { + wuffs_base__slice_u32 ret; + ret.ptr = NULL; + ret.len = 0; + return ret; +} + +static inline wuffs_base__slice_u64 // +wuffs_base__empty_slice_u64() { + wuffs_base__slice_u64 ret; + ret.ptr = NULL; + ret.len = 0; + return ret; +} + +static inline wuffs_base__table_u8 // +wuffs_base__make_table_u8(uint8_t* ptr, + size_t width, + size_t height, + size_t stride) { + wuffs_base__table_u8 ret; + ret.ptr = ptr; + ret.width = width; + ret.height = height; + ret.stride = stride; + return ret; +} + +static inline wuffs_base__table_u16 // +wuffs_base__make_table_u16(uint16_t* ptr, + size_t width, + size_t height, + size_t stride) { + wuffs_base__table_u16 ret; + ret.ptr = ptr; + ret.width = width; + ret.height = height; + ret.stride = stride; + return ret; +} + +static inline wuffs_base__table_u32 // +wuffs_base__make_table_u32(uint32_t* ptr, + size_t width, + size_t height, + size_t stride) { + wuffs_base__table_u32 ret; + ret.ptr = ptr; + ret.width = width; + ret.height = height; + ret.stride = stride; + return ret; +} + +static inline wuffs_base__table_u64 // +wuffs_base__make_table_u64(uint64_t* ptr, + size_t width, + size_t height, + size_t stride) { + wuffs_base__table_u64 ret; + ret.ptr = ptr; + ret.width = width; + ret.height = height; + ret.stride = stride; + return ret; +} + +static inline wuffs_base__table_u8 // +wuffs_base__empty_table_u8() { + wuffs_base__table_u8 ret; + ret.ptr = NULL; + ret.width = 0; + ret.height = 0; + ret.stride = 0; + return ret; +} + +static inline wuffs_base__table_u16 // +wuffs_base__empty_table_u16() { + wuffs_base__table_u16 ret; + ret.ptr = NULL; + ret.width = 0; + ret.height = 0; + ret.stride = 0; + return ret; +} + +static inline wuffs_base__table_u32 // +wuffs_base__empty_table_u32() { + wuffs_base__table_u32 ret; + ret.ptr = NULL; + ret.width = 0; + ret.height = 0; + ret.stride = 0; + return ret; +} + +static inline wuffs_base__table_u64 // +wuffs_base__empty_table_u64() { + wuffs_base__table_u64 ret; + ret.ptr = NULL; + ret.width = 0; + ret.height = 0; + ret.stride = 0; + return ret; +} + +static inline bool // +wuffs_base__slice_u8__overlaps(wuffs_base__slice_u8 s, wuffs_base__slice_u8 t) { + return ((s.ptr <= t.ptr) && (t.ptr < (s.ptr + s.len))) || + ((t.ptr <= s.ptr) && (s.ptr < (t.ptr + t.len))); +} + +// wuffs_base__slice_u8__subslice_i returns s[i:]. +// +// It returns an empty slice if i is out of bounds. +static inline wuffs_base__slice_u8 // +wuffs_base__slice_u8__subslice_i(wuffs_base__slice_u8 s, uint64_t i) { + if ((i <= SIZE_MAX) && (i <= s.len)) { + return wuffs_base__make_slice_u8(s.ptr + i, ((size_t)(s.len - i))); + } + return wuffs_base__make_slice_u8(NULL, 0); +} + +// wuffs_base__slice_u8__subslice_j returns s[:j]. +// +// It returns an empty slice if j is out of bounds. +static inline wuffs_base__slice_u8 // +wuffs_base__slice_u8__subslice_j(wuffs_base__slice_u8 s, uint64_t j) { + if ((j <= SIZE_MAX) && (j <= s.len)) { + return wuffs_base__make_slice_u8(s.ptr, ((size_t)j)); + } + return wuffs_base__make_slice_u8(NULL, 0); +} + +// wuffs_base__slice_u8__subslice_ij returns s[i:j]. +// +// It returns an empty slice if i or j is out of bounds. +static inline wuffs_base__slice_u8 // +wuffs_base__slice_u8__subslice_ij(wuffs_base__slice_u8 s, + uint64_t i, + uint64_t j) { + if ((i <= j) && (j <= SIZE_MAX) && (j <= s.len)) { + return wuffs_base__make_slice_u8(s.ptr + i, ((size_t)(j - i))); + } + return wuffs_base__make_slice_u8(NULL, 0); +} + +// wuffs_base__table_u8__subtable_ij returns t[ix:jx, iy:jy]. +// +// It returns an empty table if i or j is out of bounds. +static inline wuffs_base__table_u8 // +wuffs_base__table_u8__subtable_ij(wuffs_base__table_u8 t, + uint64_t ix, + uint64_t iy, + uint64_t jx, + uint64_t jy) { + if ((ix <= jx) && (jx <= SIZE_MAX) && (jx <= t.width) && // + (iy <= jy) && (jy <= SIZE_MAX) && (jy <= t.height)) { + return wuffs_base__make_table_u8(t.ptr + ix + (iy * t.stride), // + ((size_t)(jx - ix)), // + ((size_t)(jy - iy)), // + t.stride); // + } + return wuffs_base__make_table_u8(NULL, 0, 0, 0); +} + +// wuffs_base__table__flattened_length returns the number of elements covered +// by the 1-dimensional span that backs a 2-dimensional table. This counts the +// elements inside the table and, when width != stride, the elements outside +// the table but between its rows. +// +// For example, consider a width 10, height 4, stride 10 table. Mark its first +// and last (inclusive) elements with 'a' and 'z'. This function returns 40. +// +// a123456789 +// 0123456789 +// 0123456789 +// 012345678z +// +// Now consider the sub-table of that from (2, 1) inclusive to (8, 4) exclusive. +// +// a123456789 +// 01iiiiiioo +// ooiiiiiioo +// ooiiiiii8z +// +// This function (called with width 6, height 3, stride 10) returns 26: 18 'i' +// inside elements plus 8 'o' outside elements. Note that 26 is less than a +// naive (height * stride = 30) computation. Indeed, advancing 29 elements from +// the first 'i' would venture past 'z', out of bounds of the original table. +// +// It does not check for overflow, but if the arguments come from a table that +// exists in memory and each element occupies a positive number of bytes then +// the result should be bounded by the amount of allocatable memory (which +// shouldn't overflow SIZE_MAX). +static inline size_t // +wuffs_base__table__flattened_length(size_t width, + size_t height, + size_t stride) { + if (height == 0) { + return 0; + } + return ((height - 1) * stride) + width; +} + +// ---------------- Magic Numbers + +// wuffs_base__magic_number_guess_fourcc guesses the file format of some data, +// given its starting bytes (the prefix_data argument) and whether or not there +// may be further bytes (the prefix_closed argument; true means that +// prefix_data is the entire data). +// +// It returns a positive FourCC value on success. +// +// It returns zero if nothing matches its hard-coded list of 'magic numbers'. +// +// It returns a negative value if prefix_closed is false and a longer prefix is +// required for a conclusive result. For example, a single 'B' byte (without +// further data) is not enough to discriminate the BMP and BPG image file +// formats. Similarly, a single '\xFF' byte might be the start of JPEG data or +// it might be the start of some other binary data. +// +// It does not do a full validity check. Like any guess made from a short +// prefix of the data, it may return false positives. Data that starts with 99 +// bytes of valid JPEG followed by corruption or truncation is an invalid JPEG +// image overall, but this function will still return WUFFS_BASE__FOURCC__JPEG. +// +// Another source of false positives is that some 'magic numbers' are valid +// ASCII data. A file starting with "GIF87a and GIF89a are the two versions of +// GIF" will match GIF's 'magic number' even if it's plain text, not an image. +// +// For modular builds that divide the base module into sub-modules, using this +// function requires the WUFFS_CONFIG__MODULE__BASE__MAGIC sub-module, not just +// WUFFS_CONFIG__MODULE__BASE__CORE. +WUFFS_BASE__MAYBE_STATIC int32_t // +wuffs_base__magic_number_guess_fourcc(wuffs_base__slice_u8 prefix_data, + bool prefix_closed); + +// ---------------- Ranges and Rects + +// See https://github.com/google/wuffs/blob/main/doc/note/ranges-and-rects.md + +typedef struct wuffs_base__range_ii_u32__struct { + uint32_t min_incl; + uint32_t max_incl; + +#ifdef __cplusplus + inline bool is_empty() const; + inline bool equals(wuffs_base__range_ii_u32__struct s) const; + inline wuffs_base__range_ii_u32__struct intersect( + wuffs_base__range_ii_u32__struct s) const; + inline wuffs_base__range_ii_u32__struct unite( + wuffs_base__range_ii_u32__struct s) const; + inline bool contains(uint32_t x) const; + inline bool contains_range(wuffs_base__range_ii_u32__struct s) const; +#endif // __cplusplus + +} wuffs_base__range_ii_u32; + +static inline wuffs_base__range_ii_u32 // +wuffs_base__empty_range_ii_u32() { + wuffs_base__range_ii_u32 ret; + ret.min_incl = 0; + ret.max_incl = 0; + return ret; +} + +static inline wuffs_base__range_ii_u32 // +wuffs_base__make_range_ii_u32(uint32_t min_incl, uint32_t max_incl) { + wuffs_base__range_ii_u32 ret; + ret.min_incl = min_incl; + ret.max_incl = max_incl; + return ret; +} + +static inline bool // +wuffs_base__range_ii_u32__is_empty(const wuffs_base__range_ii_u32* r) { + return r->min_incl > r->max_incl; +} + +static inline bool // +wuffs_base__range_ii_u32__equals(const wuffs_base__range_ii_u32* r, + wuffs_base__range_ii_u32 s) { + return (r->min_incl == s.min_incl && r->max_incl == s.max_incl) || + (wuffs_base__range_ii_u32__is_empty(r) && + wuffs_base__range_ii_u32__is_empty(&s)); +} + +static inline wuffs_base__range_ii_u32 // +wuffs_base__range_ii_u32__intersect(const wuffs_base__range_ii_u32* r, + wuffs_base__range_ii_u32 s) { + wuffs_base__range_ii_u32 t; + t.min_incl = wuffs_base__u32__max(r->min_incl, s.min_incl); + t.max_incl = wuffs_base__u32__min(r->max_incl, s.max_incl); + return t; +} + +static inline wuffs_base__range_ii_u32 // +wuffs_base__range_ii_u32__unite(const wuffs_base__range_ii_u32* r, + wuffs_base__range_ii_u32 s) { + if (wuffs_base__range_ii_u32__is_empty(r)) { + return s; + } + if (wuffs_base__range_ii_u32__is_empty(&s)) { + return *r; + } + wuffs_base__range_ii_u32 t; + t.min_incl = wuffs_base__u32__min(r->min_incl, s.min_incl); + t.max_incl = wuffs_base__u32__max(r->max_incl, s.max_incl); + return t; +} + +static inline bool // +wuffs_base__range_ii_u32__contains(const wuffs_base__range_ii_u32* r, + uint32_t x) { + return (r->min_incl <= x) && (x <= r->max_incl); +} + +static inline bool // +wuffs_base__range_ii_u32__contains_range(const wuffs_base__range_ii_u32* r, + wuffs_base__range_ii_u32 s) { + return wuffs_base__range_ii_u32__equals( + &s, wuffs_base__range_ii_u32__intersect(r, s)); +} + +#ifdef __cplusplus + +inline bool // +wuffs_base__range_ii_u32::is_empty() const { + return wuffs_base__range_ii_u32__is_empty(this); +} + +inline bool // +wuffs_base__range_ii_u32::equals(wuffs_base__range_ii_u32 s) const { + return wuffs_base__range_ii_u32__equals(this, s); +} + +inline wuffs_base__range_ii_u32 // +wuffs_base__range_ii_u32::intersect(wuffs_base__range_ii_u32 s) const { + return wuffs_base__range_ii_u32__intersect(this, s); +} + +inline wuffs_base__range_ii_u32 // +wuffs_base__range_ii_u32::unite(wuffs_base__range_ii_u32 s) const { + return wuffs_base__range_ii_u32__unite(this, s); +} + +inline bool // +wuffs_base__range_ii_u32::contains(uint32_t x) const { + return wuffs_base__range_ii_u32__contains(this, x); +} + +inline bool // +wuffs_base__range_ii_u32::contains_range(wuffs_base__range_ii_u32 s) const { + return wuffs_base__range_ii_u32__contains_range(this, s); +} + +#endif // __cplusplus + +// -------- + +typedef struct wuffs_base__range_ie_u32__struct { + uint32_t min_incl; + uint32_t max_excl; + +#ifdef __cplusplus + inline bool is_empty() const; + inline bool equals(wuffs_base__range_ie_u32__struct s) const; + inline wuffs_base__range_ie_u32__struct intersect( + wuffs_base__range_ie_u32__struct s) const; + inline wuffs_base__range_ie_u32__struct unite( + wuffs_base__range_ie_u32__struct s) const; + inline bool contains(uint32_t x) const; + inline bool contains_range(wuffs_base__range_ie_u32__struct s) const; + inline uint32_t length() const; +#endif // __cplusplus + +} wuffs_base__range_ie_u32; + +static inline wuffs_base__range_ie_u32 // +wuffs_base__empty_range_ie_u32() { + wuffs_base__range_ie_u32 ret; + ret.min_incl = 0; + ret.max_excl = 0; + return ret; +} + +static inline wuffs_base__range_ie_u32 // +wuffs_base__make_range_ie_u32(uint32_t min_incl, uint32_t max_excl) { + wuffs_base__range_ie_u32 ret; + ret.min_incl = min_incl; + ret.max_excl = max_excl; + return ret; +} + +static inline bool // +wuffs_base__range_ie_u32__is_empty(const wuffs_base__range_ie_u32* r) { + return r->min_incl >= r->max_excl; +} + +static inline bool // +wuffs_base__range_ie_u32__equals(const wuffs_base__range_ie_u32* r, + wuffs_base__range_ie_u32 s) { + return (r->min_incl == s.min_incl && r->max_excl == s.max_excl) || + (wuffs_base__range_ie_u32__is_empty(r) && + wuffs_base__range_ie_u32__is_empty(&s)); +} + +static inline wuffs_base__range_ie_u32 // +wuffs_base__range_ie_u32__intersect(const wuffs_base__range_ie_u32* r, + wuffs_base__range_ie_u32 s) { + wuffs_base__range_ie_u32 t; + t.min_incl = wuffs_base__u32__max(r->min_incl, s.min_incl); + t.max_excl = wuffs_base__u32__min(r->max_excl, s.max_excl); + return t; +} + +static inline wuffs_base__range_ie_u32 // +wuffs_base__range_ie_u32__unite(const wuffs_base__range_ie_u32* r, + wuffs_base__range_ie_u32 s) { + if (wuffs_base__range_ie_u32__is_empty(r)) { + return s; + } + if (wuffs_base__range_ie_u32__is_empty(&s)) { + return *r; + } + wuffs_base__range_ie_u32 t; + t.min_incl = wuffs_base__u32__min(r->min_incl, s.min_incl); + t.max_excl = wuffs_base__u32__max(r->max_excl, s.max_excl); + return t; +} + +static inline bool // +wuffs_base__range_ie_u32__contains(const wuffs_base__range_ie_u32* r, + uint32_t x) { + return (r->min_incl <= x) && (x < r->max_excl); +} + +static inline bool // +wuffs_base__range_ie_u32__contains_range(const wuffs_base__range_ie_u32* r, + wuffs_base__range_ie_u32 s) { + return wuffs_base__range_ie_u32__equals( + &s, wuffs_base__range_ie_u32__intersect(r, s)); +} + +static inline uint32_t // +wuffs_base__range_ie_u32__length(const wuffs_base__range_ie_u32* r) { + return wuffs_base__u32__sat_sub(r->max_excl, r->min_incl); +} + +#ifdef __cplusplus + +inline bool // +wuffs_base__range_ie_u32::is_empty() const { + return wuffs_base__range_ie_u32__is_empty(this); +} + +inline bool // +wuffs_base__range_ie_u32::equals(wuffs_base__range_ie_u32 s) const { + return wuffs_base__range_ie_u32__equals(this, s); +} + +inline wuffs_base__range_ie_u32 // +wuffs_base__range_ie_u32::intersect(wuffs_base__range_ie_u32 s) const { + return wuffs_base__range_ie_u32__intersect(this, s); +} + +inline wuffs_base__range_ie_u32 // +wuffs_base__range_ie_u32::unite(wuffs_base__range_ie_u32 s) const { + return wuffs_base__range_ie_u32__unite(this, s); +} + +inline bool // +wuffs_base__range_ie_u32::contains(uint32_t x) const { + return wuffs_base__range_ie_u32__contains(this, x); +} + +inline bool // +wuffs_base__range_ie_u32::contains_range(wuffs_base__range_ie_u32 s) const { + return wuffs_base__range_ie_u32__contains_range(this, s); +} + +inline uint32_t // +wuffs_base__range_ie_u32::length() const { + return wuffs_base__range_ie_u32__length(this); +} + +#endif // __cplusplus + +// -------- + +typedef struct wuffs_base__range_ii_u64__struct { + uint64_t min_incl; + uint64_t max_incl; + +#ifdef __cplusplus + inline bool is_empty() const; + inline bool equals(wuffs_base__range_ii_u64__struct s) const; + inline wuffs_base__range_ii_u64__struct intersect( + wuffs_base__range_ii_u64__struct s) const; + inline wuffs_base__range_ii_u64__struct unite( + wuffs_base__range_ii_u64__struct s) const; + inline bool contains(uint64_t x) const; + inline bool contains_range(wuffs_base__range_ii_u64__struct s) const; +#endif // __cplusplus + +} wuffs_base__range_ii_u64; + +static inline wuffs_base__range_ii_u64 // +wuffs_base__empty_range_ii_u64() { + wuffs_base__range_ii_u64 ret; + ret.min_incl = 0; + ret.max_incl = 0; + return ret; +} + +static inline wuffs_base__range_ii_u64 // +wuffs_base__make_range_ii_u64(uint64_t min_incl, uint64_t max_incl) { + wuffs_base__range_ii_u64 ret; + ret.min_incl = min_incl; + ret.max_incl = max_incl; + return ret; +} + +static inline bool // +wuffs_base__range_ii_u64__is_empty(const wuffs_base__range_ii_u64* r) { + return r->min_incl > r->max_incl; +} + +static inline bool // +wuffs_base__range_ii_u64__equals(const wuffs_base__range_ii_u64* r, + wuffs_base__range_ii_u64 s) { + return (r->min_incl == s.min_incl && r->max_incl == s.max_incl) || + (wuffs_base__range_ii_u64__is_empty(r) && + wuffs_base__range_ii_u64__is_empty(&s)); +} + +static inline wuffs_base__range_ii_u64 // +wuffs_base__range_ii_u64__intersect(const wuffs_base__range_ii_u64* r, + wuffs_base__range_ii_u64 s) { + wuffs_base__range_ii_u64 t; + t.min_incl = wuffs_base__u64__max(r->min_incl, s.min_incl); + t.max_incl = wuffs_base__u64__min(r->max_incl, s.max_incl); + return t; +} + +static inline wuffs_base__range_ii_u64 // +wuffs_base__range_ii_u64__unite(const wuffs_base__range_ii_u64* r, + wuffs_base__range_ii_u64 s) { + if (wuffs_base__range_ii_u64__is_empty(r)) { + return s; + } + if (wuffs_base__range_ii_u64__is_empty(&s)) { + return *r; + } + wuffs_base__range_ii_u64 t; + t.min_incl = wuffs_base__u64__min(r->min_incl, s.min_incl); + t.max_incl = wuffs_base__u64__max(r->max_incl, s.max_incl); + return t; +} + +static inline bool // +wuffs_base__range_ii_u64__contains(const wuffs_base__range_ii_u64* r, + uint64_t x) { + return (r->min_incl <= x) && (x <= r->max_incl); +} + +static inline bool // +wuffs_base__range_ii_u64__contains_range(const wuffs_base__range_ii_u64* r, + wuffs_base__range_ii_u64 s) { + return wuffs_base__range_ii_u64__equals( + &s, wuffs_base__range_ii_u64__intersect(r, s)); +} + +#ifdef __cplusplus + +inline bool // +wuffs_base__range_ii_u64::is_empty() const { + return wuffs_base__range_ii_u64__is_empty(this); +} + +inline bool // +wuffs_base__range_ii_u64::equals(wuffs_base__range_ii_u64 s) const { + return wuffs_base__range_ii_u64__equals(this, s); +} + +inline wuffs_base__range_ii_u64 // +wuffs_base__range_ii_u64::intersect(wuffs_base__range_ii_u64 s) const { + return wuffs_base__range_ii_u64__intersect(this, s); +} + +inline wuffs_base__range_ii_u64 // +wuffs_base__range_ii_u64::unite(wuffs_base__range_ii_u64 s) const { + return wuffs_base__range_ii_u64__unite(this, s); +} + +inline bool // +wuffs_base__range_ii_u64::contains(uint64_t x) const { + return wuffs_base__range_ii_u64__contains(this, x); +} + +inline bool // +wuffs_base__range_ii_u64::contains_range(wuffs_base__range_ii_u64 s) const { + return wuffs_base__range_ii_u64__contains_range(this, s); +} + +#endif // __cplusplus + +// -------- + +typedef struct wuffs_base__range_ie_u64__struct { + uint64_t min_incl; + uint64_t max_excl; + +#ifdef __cplusplus + inline bool is_empty() const; + inline bool equals(wuffs_base__range_ie_u64__struct s) const; + inline wuffs_base__range_ie_u64__struct intersect( + wuffs_base__range_ie_u64__struct s) const; + inline wuffs_base__range_ie_u64__struct unite( + wuffs_base__range_ie_u64__struct s) const; + inline bool contains(uint64_t x) const; + inline bool contains_range(wuffs_base__range_ie_u64__struct s) const; + inline uint64_t length() const; +#endif // __cplusplus + +} wuffs_base__range_ie_u64; + +static inline wuffs_base__range_ie_u64 // +wuffs_base__empty_range_ie_u64() { + wuffs_base__range_ie_u64 ret; + ret.min_incl = 0; + ret.max_excl = 0; + return ret; +} + +static inline wuffs_base__range_ie_u64 // +wuffs_base__make_range_ie_u64(uint64_t min_incl, uint64_t max_excl) { + wuffs_base__range_ie_u64 ret; + ret.min_incl = min_incl; + ret.max_excl = max_excl; + return ret; +} + +static inline bool // +wuffs_base__range_ie_u64__is_empty(const wuffs_base__range_ie_u64* r) { + return r->min_incl >= r->max_excl; +} + +static inline bool // +wuffs_base__range_ie_u64__equals(const wuffs_base__range_ie_u64* r, + wuffs_base__range_ie_u64 s) { + return (r->min_incl == s.min_incl && r->max_excl == s.max_excl) || + (wuffs_base__range_ie_u64__is_empty(r) && + wuffs_base__range_ie_u64__is_empty(&s)); +} + +static inline wuffs_base__range_ie_u64 // +wuffs_base__range_ie_u64__intersect(const wuffs_base__range_ie_u64* r, + wuffs_base__range_ie_u64 s) { + wuffs_base__range_ie_u64 t; + t.min_incl = wuffs_base__u64__max(r->min_incl, s.min_incl); + t.max_excl = wuffs_base__u64__min(r->max_excl, s.max_excl); + return t; +} + +static inline wuffs_base__range_ie_u64 // +wuffs_base__range_ie_u64__unite(const wuffs_base__range_ie_u64* r, + wuffs_base__range_ie_u64 s) { + if (wuffs_base__range_ie_u64__is_empty(r)) { + return s; + } + if (wuffs_base__range_ie_u64__is_empty(&s)) { + return *r; + } + wuffs_base__range_ie_u64 t; + t.min_incl = wuffs_base__u64__min(r->min_incl, s.min_incl); + t.max_excl = wuffs_base__u64__max(r->max_excl, s.max_excl); + return t; +} + +static inline bool // +wuffs_base__range_ie_u64__contains(const wuffs_base__range_ie_u64* r, + uint64_t x) { + return (r->min_incl <= x) && (x < r->max_excl); +} + +static inline bool // +wuffs_base__range_ie_u64__contains_range(const wuffs_base__range_ie_u64* r, + wuffs_base__range_ie_u64 s) { + return wuffs_base__range_ie_u64__equals( + &s, wuffs_base__range_ie_u64__intersect(r, s)); +} + +static inline uint64_t // +wuffs_base__range_ie_u64__length(const wuffs_base__range_ie_u64* r) { + return wuffs_base__u64__sat_sub(r->max_excl, r->min_incl); +} + +#ifdef __cplusplus + +inline bool // +wuffs_base__range_ie_u64::is_empty() const { + return wuffs_base__range_ie_u64__is_empty(this); +} + +inline bool // +wuffs_base__range_ie_u64::equals(wuffs_base__range_ie_u64 s) const { + return wuffs_base__range_ie_u64__equals(this, s); +} + +inline wuffs_base__range_ie_u64 // +wuffs_base__range_ie_u64::intersect(wuffs_base__range_ie_u64 s) const { + return wuffs_base__range_ie_u64__intersect(this, s); +} + +inline wuffs_base__range_ie_u64 // +wuffs_base__range_ie_u64::unite(wuffs_base__range_ie_u64 s) const { + return wuffs_base__range_ie_u64__unite(this, s); +} + +inline bool // +wuffs_base__range_ie_u64::contains(uint64_t x) const { + return wuffs_base__range_ie_u64__contains(this, x); +} + +inline bool // +wuffs_base__range_ie_u64::contains_range(wuffs_base__range_ie_u64 s) const { + return wuffs_base__range_ie_u64__contains_range(this, s); +} + +inline uint64_t // +wuffs_base__range_ie_u64::length() const { + return wuffs_base__range_ie_u64__length(this); +} + +#endif // __cplusplus + +// -------- + +typedef struct wuffs_base__rect_ii_u32__struct { + uint32_t min_incl_x; + uint32_t min_incl_y; + uint32_t max_incl_x; + uint32_t max_incl_y; + +#ifdef __cplusplus + inline bool is_empty() const; + inline bool equals(wuffs_base__rect_ii_u32__struct s) const; + inline wuffs_base__rect_ii_u32__struct intersect( + wuffs_base__rect_ii_u32__struct s) const; + inline wuffs_base__rect_ii_u32__struct unite( + wuffs_base__rect_ii_u32__struct s) const; + inline bool contains(uint32_t x, uint32_t y) const; + inline bool contains_rect(wuffs_base__rect_ii_u32__struct s) const; +#endif // __cplusplus + +} wuffs_base__rect_ii_u32; + +static inline wuffs_base__rect_ii_u32 // +wuffs_base__empty_rect_ii_u32() { + wuffs_base__rect_ii_u32 ret; + ret.min_incl_x = 0; + ret.min_incl_y = 0; + ret.max_incl_x = 0; + ret.max_incl_y = 0; + return ret; +} + +static inline wuffs_base__rect_ii_u32 // +wuffs_base__make_rect_ii_u32(uint32_t min_incl_x, + uint32_t min_incl_y, + uint32_t max_incl_x, + uint32_t max_incl_y) { + wuffs_base__rect_ii_u32 ret; + ret.min_incl_x = min_incl_x; + ret.min_incl_y = min_incl_y; + ret.max_incl_x = max_incl_x; + ret.max_incl_y = max_incl_y; + return ret; +} + +static inline bool // +wuffs_base__rect_ii_u32__is_empty(const wuffs_base__rect_ii_u32* r) { + return (r->min_incl_x > r->max_incl_x) || (r->min_incl_y > r->max_incl_y); +} + +static inline bool // +wuffs_base__rect_ii_u32__equals(const wuffs_base__rect_ii_u32* r, + wuffs_base__rect_ii_u32 s) { + return (r->min_incl_x == s.min_incl_x && r->min_incl_y == s.min_incl_y && + r->max_incl_x == s.max_incl_x && r->max_incl_y == s.max_incl_y) || + (wuffs_base__rect_ii_u32__is_empty(r) && + wuffs_base__rect_ii_u32__is_empty(&s)); +} + +static inline wuffs_base__rect_ii_u32 // +wuffs_base__rect_ii_u32__intersect(const wuffs_base__rect_ii_u32* r, + wuffs_base__rect_ii_u32 s) { + wuffs_base__rect_ii_u32 t; + t.min_incl_x = wuffs_base__u32__max(r->min_incl_x, s.min_incl_x); + t.min_incl_y = wuffs_base__u32__max(r->min_incl_y, s.min_incl_y); + t.max_incl_x = wuffs_base__u32__min(r->max_incl_x, s.max_incl_x); + t.max_incl_y = wuffs_base__u32__min(r->max_incl_y, s.max_incl_y); + return t; +} + +static inline wuffs_base__rect_ii_u32 // +wuffs_base__rect_ii_u32__unite(const wuffs_base__rect_ii_u32* r, + wuffs_base__rect_ii_u32 s) { + if (wuffs_base__rect_ii_u32__is_empty(r)) { + return s; + } + if (wuffs_base__rect_ii_u32__is_empty(&s)) { + return *r; + } + wuffs_base__rect_ii_u32 t; + t.min_incl_x = wuffs_base__u32__min(r->min_incl_x, s.min_incl_x); + t.min_incl_y = wuffs_base__u32__min(r->min_incl_y, s.min_incl_y); + t.max_incl_x = wuffs_base__u32__max(r->max_incl_x, s.max_incl_x); + t.max_incl_y = wuffs_base__u32__max(r->max_incl_y, s.max_incl_y); + return t; +} + +static inline bool // +wuffs_base__rect_ii_u32__contains(const wuffs_base__rect_ii_u32* r, + uint32_t x, + uint32_t y) { + return (r->min_incl_x <= x) && (x <= r->max_incl_x) && (r->min_incl_y <= y) && + (y <= r->max_incl_y); +} + +static inline bool // +wuffs_base__rect_ii_u32__contains_rect(const wuffs_base__rect_ii_u32* r, + wuffs_base__rect_ii_u32 s) { + return wuffs_base__rect_ii_u32__equals( + &s, wuffs_base__rect_ii_u32__intersect(r, s)); +} + +#ifdef __cplusplus + +inline bool // +wuffs_base__rect_ii_u32::is_empty() const { + return wuffs_base__rect_ii_u32__is_empty(this); +} + +inline bool // +wuffs_base__rect_ii_u32::equals(wuffs_base__rect_ii_u32 s) const { + return wuffs_base__rect_ii_u32__equals(this, s); +} + +inline wuffs_base__rect_ii_u32 // +wuffs_base__rect_ii_u32::intersect(wuffs_base__rect_ii_u32 s) const { + return wuffs_base__rect_ii_u32__intersect(this, s); +} + +inline wuffs_base__rect_ii_u32 // +wuffs_base__rect_ii_u32::unite(wuffs_base__rect_ii_u32 s) const { + return wuffs_base__rect_ii_u32__unite(this, s); +} + +inline bool // +wuffs_base__rect_ii_u32::contains(uint32_t x, uint32_t y) const { + return wuffs_base__rect_ii_u32__contains(this, x, y); +} + +inline bool // +wuffs_base__rect_ii_u32::contains_rect(wuffs_base__rect_ii_u32 s) const { + return wuffs_base__rect_ii_u32__contains_rect(this, s); +} + +#endif // __cplusplus + +// -------- + +typedef struct wuffs_base__rect_ie_u32__struct { + uint32_t min_incl_x; + uint32_t min_incl_y; + uint32_t max_excl_x; + uint32_t max_excl_y; + +#ifdef __cplusplus + inline bool is_empty() const; + inline bool equals(wuffs_base__rect_ie_u32__struct s) const; + inline wuffs_base__rect_ie_u32__struct intersect( + wuffs_base__rect_ie_u32__struct s) const; + inline wuffs_base__rect_ie_u32__struct unite( + wuffs_base__rect_ie_u32__struct s) const; + inline bool contains(uint32_t x, uint32_t y) const; + inline bool contains_rect(wuffs_base__rect_ie_u32__struct s) const; + inline uint32_t width() const; + inline uint32_t height() const; +#endif // __cplusplus + +} wuffs_base__rect_ie_u32; + +static inline wuffs_base__rect_ie_u32 // +wuffs_base__empty_rect_ie_u32() { + wuffs_base__rect_ie_u32 ret; + ret.min_incl_x = 0; + ret.min_incl_y = 0; + ret.max_excl_x = 0; + ret.max_excl_y = 0; + return ret; +} + +static inline wuffs_base__rect_ie_u32 // +wuffs_base__make_rect_ie_u32(uint32_t min_incl_x, + uint32_t min_incl_y, + uint32_t max_excl_x, + uint32_t max_excl_y) { + wuffs_base__rect_ie_u32 ret; + ret.min_incl_x = min_incl_x; + ret.min_incl_y = min_incl_y; + ret.max_excl_x = max_excl_x; + ret.max_excl_y = max_excl_y; + return ret; +} + +static inline bool // +wuffs_base__rect_ie_u32__is_empty(const wuffs_base__rect_ie_u32* r) { + return (r->min_incl_x >= r->max_excl_x) || (r->min_incl_y >= r->max_excl_y); +} + +static inline bool // +wuffs_base__rect_ie_u32__equals(const wuffs_base__rect_ie_u32* r, + wuffs_base__rect_ie_u32 s) { + return (r->min_incl_x == s.min_incl_x && r->min_incl_y == s.min_incl_y && + r->max_excl_x == s.max_excl_x && r->max_excl_y == s.max_excl_y) || + (wuffs_base__rect_ie_u32__is_empty(r) && + wuffs_base__rect_ie_u32__is_empty(&s)); +} + +static inline wuffs_base__rect_ie_u32 // +wuffs_base__rect_ie_u32__intersect(const wuffs_base__rect_ie_u32* r, + wuffs_base__rect_ie_u32 s) { + wuffs_base__rect_ie_u32 t; + t.min_incl_x = wuffs_base__u32__max(r->min_incl_x, s.min_incl_x); + t.min_incl_y = wuffs_base__u32__max(r->min_incl_y, s.min_incl_y); + t.max_excl_x = wuffs_base__u32__min(r->max_excl_x, s.max_excl_x); + t.max_excl_y = wuffs_base__u32__min(r->max_excl_y, s.max_excl_y); + return t; +} + +static inline wuffs_base__rect_ie_u32 // +wuffs_base__rect_ie_u32__unite(const wuffs_base__rect_ie_u32* r, + wuffs_base__rect_ie_u32 s) { + if (wuffs_base__rect_ie_u32__is_empty(r)) { + return s; + } + if (wuffs_base__rect_ie_u32__is_empty(&s)) { + return *r; + } + wuffs_base__rect_ie_u32 t; + t.min_incl_x = wuffs_base__u32__min(r->min_incl_x, s.min_incl_x); + t.min_incl_y = wuffs_base__u32__min(r->min_incl_y, s.min_incl_y); + t.max_excl_x = wuffs_base__u32__max(r->max_excl_x, s.max_excl_x); + t.max_excl_y = wuffs_base__u32__max(r->max_excl_y, s.max_excl_y); + return t; +} + +static inline bool // +wuffs_base__rect_ie_u32__contains(const wuffs_base__rect_ie_u32* r, + uint32_t x, + uint32_t y) { + return (r->min_incl_x <= x) && (x < r->max_excl_x) && (r->min_incl_y <= y) && + (y < r->max_excl_y); +} + +static inline bool // +wuffs_base__rect_ie_u32__contains_rect(const wuffs_base__rect_ie_u32* r, + wuffs_base__rect_ie_u32 s) { + return wuffs_base__rect_ie_u32__equals( + &s, wuffs_base__rect_ie_u32__intersect(r, s)); +} + +static inline uint32_t // +wuffs_base__rect_ie_u32__width(const wuffs_base__rect_ie_u32* r) { + return wuffs_base__u32__sat_sub(r->max_excl_x, r->min_incl_x); +} + +static inline uint32_t // +wuffs_base__rect_ie_u32__height(const wuffs_base__rect_ie_u32* r) { + return wuffs_base__u32__sat_sub(r->max_excl_y, r->min_incl_y); +} + +#ifdef __cplusplus + +inline bool // +wuffs_base__rect_ie_u32::is_empty() const { + return wuffs_base__rect_ie_u32__is_empty(this); +} + +inline bool // +wuffs_base__rect_ie_u32::equals(wuffs_base__rect_ie_u32 s) const { + return wuffs_base__rect_ie_u32__equals(this, s); +} + +inline wuffs_base__rect_ie_u32 // +wuffs_base__rect_ie_u32::intersect(wuffs_base__rect_ie_u32 s) const { + return wuffs_base__rect_ie_u32__intersect(this, s); +} + +inline wuffs_base__rect_ie_u32 // +wuffs_base__rect_ie_u32::unite(wuffs_base__rect_ie_u32 s) const { + return wuffs_base__rect_ie_u32__unite(this, s); +} + +inline bool // +wuffs_base__rect_ie_u32::contains(uint32_t x, uint32_t y) const { + return wuffs_base__rect_ie_u32__contains(this, x, y); +} + +inline bool // +wuffs_base__rect_ie_u32::contains_rect(wuffs_base__rect_ie_u32 s) const { + return wuffs_base__rect_ie_u32__contains_rect(this, s); +} + +inline uint32_t // +wuffs_base__rect_ie_u32::width() const { + return wuffs_base__rect_ie_u32__width(this); +} + +inline uint32_t // +wuffs_base__rect_ie_u32::height() const { + return wuffs_base__rect_ie_u32__height(this); +} + +#endif // __cplusplus + +// ---------------- More Information + +// wuffs_base__more_information holds additional fields, typically when a Wuffs +// method returns a [note status](/doc/note/statuses.md). +// +// The flavor field follows the base38 namespace +// convention](/doc/note/base38-and-fourcc.md). The other fields' semantics +// depends on the flavor. +typedef struct wuffs_base__more_information__struct { + uint32_t flavor; + uint32_t w; + uint64_t x; + uint64_t y; + uint64_t z; + +#ifdef __cplusplus + inline void set(uint32_t flavor_arg, + uint32_t w_arg, + uint64_t x_arg, + uint64_t y_arg, + uint64_t z_arg); + inline uint32_t io_redirect__fourcc() const; + inline wuffs_base__range_ie_u64 io_redirect__range() const; + inline uint64_t io_seek__position() const; + inline uint32_t metadata__fourcc() const; + // Deprecated: use metadata_raw_passthrough__range. + inline wuffs_base__range_ie_u64 metadata__range() const; + inline wuffs_base__range_ie_u64 metadata_raw_passthrough__range() const; + inline int32_t metadata_parsed__chrm(uint32_t component) const; + inline uint32_t metadata_parsed__gama() const; + inline uint32_t metadata_parsed__srgb() const; +#endif // __cplusplus + +} wuffs_base__more_information; + +#define WUFFS_BASE__MORE_INFORMATION__FLAVOR__IO_REDIRECT 1 +#define WUFFS_BASE__MORE_INFORMATION__FLAVOR__IO_SEEK 2 +// Deprecated: use +// WUFFS_BASE__MORE_INFORMATION__FLAVOR__METADATA_RAW_PASSTHROUGH. +#define WUFFS_BASE__MORE_INFORMATION__FLAVOR__METADATA 3 +#define WUFFS_BASE__MORE_INFORMATION__FLAVOR__METADATA_RAW_PASSTHROUGH 3 +#define WUFFS_BASE__MORE_INFORMATION__FLAVOR__METADATA_RAW_TRANSFORM 4 +#define WUFFS_BASE__MORE_INFORMATION__FLAVOR__METADATA_PARSED 5 + +static inline wuffs_base__more_information // +wuffs_base__empty_more_information() { + wuffs_base__more_information ret; + ret.flavor = 0; + ret.w = 0; + ret.x = 0; + ret.y = 0; + ret.z = 0; + return ret; +} + +static inline void // +wuffs_base__more_information__set(wuffs_base__more_information* m, + uint32_t flavor, + uint32_t w, + uint64_t x, + uint64_t y, + uint64_t z) { + if (!m) { + return; + } + m->flavor = flavor; + m->w = w; + m->x = x; + m->y = y; + m->z = z; +} + +static inline uint32_t // +wuffs_base__more_information__io_redirect__fourcc( + const wuffs_base__more_information* m) { + return m->w; +} + +static inline wuffs_base__range_ie_u64 // +wuffs_base__more_information__io_redirect__range( + const wuffs_base__more_information* m) { + wuffs_base__range_ie_u64 ret; + ret.min_incl = m->y; + ret.max_excl = m->z; + return ret; +} + +static inline uint64_t // +wuffs_base__more_information__io_seek__position( + const wuffs_base__more_information* m) { + return m->x; +} + +static inline uint32_t // +wuffs_base__more_information__metadata__fourcc( + const wuffs_base__more_information* m) { + return m->w; +} + +// Deprecated: use +// wuffs_base__more_information__metadata_raw_passthrough__range. +static inline wuffs_base__range_ie_u64 // +wuffs_base__more_information__metadata__range( + const wuffs_base__more_information* m) { + wuffs_base__range_ie_u64 ret; + ret.min_incl = m->y; + ret.max_excl = m->z; + return ret; +} + +static inline wuffs_base__range_ie_u64 // +wuffs_base__more_information__metadata_raw_passthrough__range( + const wuffs_base__more_information* m) { + wuffs_base__range_ie_u64 ret; + ret.min_incl = m->y; + ret.max_excl = m->z; + return ret; +} + +#define WUFFS_BASE__MORE_INFORMATION__METADATA_PARSED__CHRM__WHITE_X 0 +#define WUFFS_BASE__MORE_INFORMATION__METADATA_PARSED__CHRM__WHITE_Y 1 +#define WUFFS_BASE__MORE_INFORMATION__METADATA_PARSED__CHRM__RED_X 2 +#define WUFFS_BASE__MORE_INFORMATION__METADATA_PARSED__CHRM__RED_Y 3 +#define WUFFS_BASE__MORE_INFORMATION__METADATA_PARSED__CHRM__GREEN_X 4 +#define WUFFS_BASE__MORE_INFORMATION__METADATA_PARSED__CHRM__GREEN_Y 5 +#define WUFFS_BASE__MORE_INFORMATION__METADATA_PARSED__CHRM__BLUE_X 6 +#define WUFFS_BASE__MORE_INFORMATION__METADATA_PARSED__CHRM__BLUE_Y 7 + +// wuffs_base__more_information__metadata_parsed__chrm returns chromaticity +// values (scaled by 100000) like the PNG "cHRM" chunk. For example, the sRGB +// color space corresponds to: +// - ETC__CHRM__WHITE_X 31270 +// - ETC__CHRM__WHITE_Y 32900 +// - ETC__CHRM__RED_X 64000 +// - ETC__CHRM__RED_Y 33000 +// - ETC__CHRM__GREEN_X 30000 +// - ETC__CHRM__GREEN_Y 60000 +// - ETC__CHRM__BLUE_X 15000 +// - ETC__CHRM__BLUE_Y 6000 +// +// See +// https://ciechanow.ski/color-spaces/#chromaticity-and-white-point-coordinates +static inline int32_t // +wuffs_base__more_information__metadata_parsed__chrm( + const wuffs_base__more_information* m, + uint32_t component) { + // After the flavor and the w field (holding a FourCC), a + // wuffs_base__more_information holds 24 bytes of data in three uint64_t + // typed fields (x, y and z). We pack the eight chromaticity values (wx, wy, + // rx, ..., by), basically int24_t values, into 24 bytes like this: + // - LSB MSB + // - x: wx wx wx wy wy wy rx rx + // - y: rx ry ry ry gx gx gx gy + // - z: gy gy bx bx bx by by by + uint32_t u = 0; + switch (component & 7) { + case 0: + u = ((uint32_t)(m->x >> 0)); + break; + case 1: + u = ((uint32_t)(m->x >> 24)); + break; + case 2: + u = ((uint32_t)((m->x >> 48) | (m->y << 16))); + break; + case 3: + u = ((uint32_t)(m->y >> 8)); + break; + case 4: + u = ((uint32_t)(m->y >> 32)); + break; + case 5: + u = ((uint32_t)((m->y >> 56) | (m->z << 8))); + break; + case 6: + u = ((uint32_t)(m->z >> 16)); + break; + case 7: + u = ((uint32_t)(m->z >> 40)); + break; + } + // The left-right shifts sign-extend from 24-bit to 32-bit integers. + return ((int32_t)(u << 8)) >> 8; +} + +// wuffs_base__more_information__metadata_parsed__gama returns inverse gamma +// correction values (scaled by 100000) like the PNG "gAMA" chunk. For example, +// for gamma = 2.2, this returns 45455 (approximating 100000 / 2.2). +static inline uint32_t // +wuffs_base__more_information__metadata_parsed__gama( + const wuffs_base__more_information* m) { + return ((uint32_t)(m->x)); +} + +#define WUFFS_BASE__SRGB_RENDERING_INTENT__PERCEPTUAL 0 +#define WUFFS_BASE__SRGB_RENDERING_INTENT__RELATIVE_COLORIMETRIC 1 +#define WUFFS_BASE__SRGB_RENDERING_INTENT__SATURATION 2 +#define WUFFS_BASE__SRGB_RENDERING_INTENT__ABSOLUTE_COLORIMETRIC 3 + +// wuffs_base__more_information__metadata_parsed__srgb returns the sRGB +// rendering intent like the PNG "sRGB" chunk. +static inline uint32_t // +wuffs_base__more_information__metadata_parsed__srgb( + const wuffs_base__more_information* m) { + return m->x & 3; +} + +#ifdef __cplusplus + +inline void // +wuffs_base__more_information::set(uint32_t flavor_arg, + uint32_t w_arg, + uint64_t x_arg, + uint64_t y_arg, + uint64_t z_arg) { + wuffs_base__more_information__set(this, flavor_arg, w_arg, x_arg, y_arg, + z_arg); +} + +inline uint32_t // +wuffs_base__more_information::io_redirect__fourcc() const { + return wuffs_base__more_information__io_redirect__fourcc(this); +} + +inline wuffs_base__range_ie_u64 // +wuffs_base__more_information::io_redirect__range() const { + return wuffs_base__more_information__io_redirect__range(this); +} + +inline uint64_t // +wuffs_base__more_information::io_seek__position() const { + return wuffs_base__more_information__io_seek__position(this); +} + +inline uint32_t // +wuffs_base__more_information::metadata__fourcc() const { + return wuffs_base__more_information__metadata__fourcc(this); +} + +inline wuffs_base__range_ie_u64 // +wuffs_base__more_information::metadata__range() const { + return wuffs_base__more_information__metadata__range(this); +} + +inline wuffs_base__range_ie_u64 // +wuffs_base__more_information::metadata_raw_passthrough__range() const { + return wuffs_base__more_information__metadata_raw_passthrough__range(this); +} + +inline int32_t // +wuffs_base__more_information::metadata_parsed__chrm(uint32_t component) const { + return wuffs_base__more_information__metadata_parsed__chrm(this, component); +} + +inline uint32_t // +wuffs_base__more_information::metadata_parsed__gama() const { + return wuffs_base__more_information__metadata_parsed__gama(this); +} + +inline uint32_t // +wuffs_base__more_information::metadata_parsed__srgb() const { + return wuffs_base__more_information__metadata_parsed__srgb(this); +} + +#endif // __cplusplus + +// ---------------- I/O +// +// See (/doc/note/io-input-output.md). + +// wuffs_base__io_buffer_meta is the metadata for a wuffs_base__io_buffer's +// data. +typedef struct wuffs_base__io_buffer_meta__struct { + size_t wi; // Write index. Invariant: wi <= len. + size_t ri; // Read index. Invariant: ri <= wi. + uint64_t pos; // Buffer position (relative to the start of stream). + bool closed; // No further writes are expected. +} wuffs_base__io_buffer_meta; + +// wuffs_base__io_buffer is a 1-dimensional buffer (a pointer and length) plus +// additional metadata. +// +// A value with all fields zero is a valid, empty buffer. +typedef struct wuffs_base__io_buffer__struct { + wuffs_base__slice_u8 data; + wuffs_base__io_buffer_meta meta; + +#ifdef __cplusplus + inline bool is_valid() const; + inline void compact(); + inline size_t reader_length() const; + inline uint8_t* reader_pointer() const; + inline uint64_t reader_position() const; + inline wuffs_base__slice_u8 reader_slice() const; + inline size_t writer_length() const; + inline uint8_t* writer_pointer() const; + inline uint64_t writer_position() const; + inline wuffs_base__slice_u8 writer_slice() const; + + // Deprecated: use reader_position. + inline uint64_t reader_io_position() const; + // Deprecated: use writer_position. + inline uint64_t writer_io_position() const; +#endif // __cplusplus + +} wuffs_base__io_buffer; + +static inline wuffs_base__io_buffer // +wuffs_base__make_io_buffer(wuffs_base__slice_u8 data, + wuffs_base__io_buffer_meta meta) { + wuffs_base__io_buffer ret; + ret.data = data; + ret.meta = meta; + return ret; +} + +static inline wuffs_base__io_buffer_meta // +wuffs_base__make_io_buffer_meta(size_t wi, + size_t ri, + uint64_t pos, + bool closed) { + wuffs_base__io_buffer_meta ret; + ret.wi = wi; + ret.ri = ri; + ret.pos = pos; + ret.closed = closed; + return ret; +} + +static inline wuffs_base__io_buffer // +wuffs_base__ptr_u8__reader(uint8_t* ptr, size_t len, bool closed) { + wuffs_base__io_buffer ret; + ret.data.ptr = ptr; + ret.data.len = len; + ret.meta.wi = len; + ret.meta.ri = 0; + ret.meta.pos = 0; + ret.meta.closed = closed; + return ret; +} + +static inline wuffs_base__io_buffer // +wuffs_base__ptr_u8__writer(uint8_t* ptr, size_t len) { + wuffs_base__io_buffer ret; + ret.data.ptr = ptr; + ret.data.len = len; + ret.meta.wi = 0; + ret.meta.ri = 0; + ret.meta.pos = 0; + ret.meta.closed = false; + return ret; +} + +static inline wuffs_base__io_buffer // +wuffs_base__slice_u8__reader(wuffs_base__slice_u8 s, bool closed) { + wuffs_base__io_buffer ret; + ret.data.ptr = s.ptr; + ret.data.len = s.len; + ret.meta.wi = s.len; + ret.meta.ri = 0; + ret.meta.pos = 0; + ret.meta.closed = closed; + return ret; +} + +static inline wuffs_base__io_buffer // +wuffs_base__slice_u8__writer(wuffs_base__slice_u8 s) { + wuffs_base__io_buffer ret; + ret.data.ptr = s.ptr; + ret.data.len = s.len; + ret.meta.wi = 0; + ret.meta.ri = 0; + ret.meta.pos = 0; + ret.meta.closed = false; + return ret; +} + +static inline wuffs_base__io_buffer // +wuffs_base__empty_io_buffer() { + wuffs_base__io_buffer ret; + ret.data.ptr = NULL; + ret.data.len = 0; + ret.meta.wi = 0; + ret.meta.ri = 0; + ret.meta.pos = 0; + ret.meta.closed = false; + return ret; +} + +static inline wuffs_base__io_buffer_meta // +wuffs_base__empty_io_buffer_meta() { + wuffs_base__io_buffer_meta ret; + ret.wi = 0; + ret.ri = 0; + ret.pos = 0; + ret.closed = false; + return ret; +} + +static inline bool // +wuffs_base__io_buffer__is_valid(const wuffs_base__io_buffer* buf) { + if (buf) { + if (buf->data.ptr) { + return (buf->meta.ri <= buf->meta.wi) && (buf->meta.wi <= buf->data.len); + } else { + return (buf->meta.ri == 0) && (buf->meta.wi == 0) && (buf->data.len == 0); + } + } + return false; +} + +// wuffs_base__io_buffer__compact moves any written but unread bytes to the +// start of the buffer. +static inline void // +wuffs_base__io_buffer__compact(wuffs_base__io_buffer* buf) { + if (!buf || (buf->meta.ri == 0)) { + return; + } + buf->meta.pos = wuffs_base__u64__sat_add(buf->meta.pos, buf->meta.ri); + size_t n = buf->meta.wi - buf->meta.ri; + if (n != 0) { + memmove(buf->data.ptr, buf->data.ptr + buf->meta.ri, n); + } + buf->meta.wi = n; + buf->meta.ri = 0; +} + +// Deprecated. Use wuffs_base__io_buffer__reader_position. +static inline uint64_t // +wuffs_base__io_buffer__reader_io_position(const wuffs_base__io_buffer* buf) { + return buf ? wuffs_base__u64__sat_add(buf->meta.pos, buf->meta.ri) : 0; +} + +static inline size_t // +wuffs_base__io_buffer__reader_length(const wuffs_base__io_buffer* buf) { + return buf ? buf->meta.wi - buf->meta.ri : 0; +} + +static inline uint8_t* // +wuffs_base__io_buffer__reader_pointer(const wuffs_base__io_buffer* buf) { + return buf ? (buf->data.ptr + buf->meta.ri) : NULL; +} + +static inline uint64_t // +wuffs_base__io_buffer__reader_position(const wuffs_base__io_buffer* buf) { + return buf ? wuffs_base__u64__sat_add(buf->meta.pos, buf->meta.ri) : 0; +} + +static inline wuffs_base__slice_u8 // +wuffs_base__io_buffer__reader_slice(const wuffs_base__io_buffer* buf) { + return buf ? wuffs_base__make_slice_u8(buf->data.ptr + buf->meta.ri, + buf->meta.wi - buf->meta.ri) + : wuffs_base__empty_slice_u8(); +} + +// Deprecated. Use wuffs_base__io_buffer__writer_position. +static inline uint64_t // +wuffs_base__io_buffer__writer_io_position(const wuffs_base__io_buffer* buf) { + return buf ? wuffs_base__u64__sat_add(buf->meta.pos, buf->meta.wi) : 0; +} + +static inline size_t // +wuffs_base__io_buffer__writer_length(const wuffs_base__io_buffer* buf) { + return buf ? buf->data.len - buf->meta.wi : 0; +} + +static inline uint8_t* // +wuffs_base__io_buffer__writer_pointer(const wuffs_base__io_buffer* buf) { + return buf ? (buf->data.ptr + buf->meta.wi) : NULL; +} + +static inline uint64_t // +wuffs_base__io_buffer__writer_position(const wuffs_base__io_buffer* buf) { + return buf ? wuffs_base__u64__sat_add(buf->meta.pos, buf->meta.wi) : 0; +} + +static inline wuffs_base__slice_u8 // +wuffs_base__io_buffer__writer_slice(const wuffs_base__io_buffer* buf) { + return buf ? wuffs_base__make_slice_u8(buf->data.ptr + buf->meta.wi, + buf->data.len - buf->meta.wi) + : wuffs_base__empty_slice_u8(); +} + +#ifdef __cplusplus + +inline bool // +wuffs_base__io_buffer::is_valid() const { + return wuffs_base__io_buffer__is_valid(this); +} + +inline void // +wuffs_base__io_buffer::compact() { + wuffs_base__io_buffer__compact(this); +} + +inline uint64_t // +wuffs_base__io_buffer::reader_io_position() const { + return wuffs_base__io_buffer__reader_io_position(this); +} + +inline size_t // +wuffs_base__io_buffer::reader_length() const { + return wuffs_base__io_buffer__reader_length(this); +} + +inline uint8_t* // +wuffs_base__io_buffer::reader_pointer() const { + return wuffs_base__io_buffer__reader_pointer(this); +} + +inline uint64_t // +wuffs_base__io_buffer::reader_position() const { + return wuffs_base__io_buffer__reader_position(this); +} + +inline wuffs_base__slice_u8 // +wuffs_base__io_buffer::reader_slice() const { + return wuffs_base__io_buffer__reader_slice(this); +} + +inline uint64_t // +wuffs_base__io_buffer::writer_io_position() const { + return wuffs_base__io_buffer__writer_io_position(this); +} + +inline size_t // +wuffs_base__io_buffer::writer_length() const { + return wuffs_base__io_buffer__writer_length(this); +} + +inline uint8_t* // +wuffs_base__io_buffer::writer_pointer() const { + return wuffs_base__io_buffer__writer_pointer(this); +} + +inline uint64_t // +wuffs_base__io_buffer::writer_position() const { + return wuffs_base__io_buffer__writer_position(this); +} + +inline wuffs_base__slice_u8 // +wuffs_base__io_buffer::writer_slice() const { + return wuffs_base__io_buffer__writer_slice(this); +} + +#endif // __cplusplus + +// ---------------- Tokens + +// wuffs_base__token is an element of a byte stream's tokenization. +// +// See https://github.com/google/wuffs/blob/main/doc/note/tokens.md +typedef struct wuffs_base__token__struct { + uint64_t repr; + +#ifdef __cplusplus + inline int64_t value() const; + inline int64_t value_extension() const; + inline int64_t value_major() const; + inline int64_t value_base_category() const; + inline uint64_t value_minor() const; + inline uint64_t value_base_detail() const; + inline int64_t value_base_detail__sign_extended() const; + inline bool continued() const; + inline uint64_t length() const; +#endif // __cplusplus + +} wuffs_base__token; + +static inline wuffs_base__token // +wuffs_base__make_token(uint64_t repr) { + wuffs_base__token ret; + ret.repr = repr; + return ret; +} + +// -------- + +#define WUFFS_BASE__TOKEN__LENGTH__MAX_INCL 0xFFFF + +#define WUFFS_BASE__TOKEN__VALUE__SHIFT 17 +#define WUFFS_BASE__TOKEN__VALUE_EXTENSION__SHIFT 17 +#define WUFFS_BASE__TOKEN__VALUE_MAJOR__SHIFT 42 +#define WUFFS_BASE__TOKEN__VALUE_MINOR__SHIFT 17 +#define WUFFS_BASE__TOKEN__VALUE_BASE_CATEGORY__SHIFT 38 +#define WUFFS_BASE__TOKEN__VALUE_BASE_DETAIL__SHIFT 17 +#define WUFFS_BASE__TOKEN__CONTINUED__SHIFT 16 +#define WUFFS_BASE__TOKEN__LENGTH__SHIFT 0 + +#define WUFFS_BASE__TOKEN__VALUE_EXTENSION__NUM_BITS 46 + +// -------- + +#define WUFFS_BASE__TOKEN__VBC__FILLER 0 +#define WUFFS_BASE__TOKEN__VBC__STRUCTURE 1 +#define WUFFS_BASE__TOKEN__VBC__STRING 2 +#define WUFFS_BASE__TOKEN__VBC__UNICODE_CODE_POINT 3 +#define WUFFS_BASE__TOKEN__VBC__LITERAL 4 +#define WUFFS_BASE__TOKEN__VBC__NUMBER 5 +#define WUFFS_BASE__TOKEN__VBC__INLINE_INTEGER_SIGNED 6 +#define WUFFS_BASE__TOKEN__VBC__INLINE_INTEGER_UNSIGNED 7 + +// -------- + +#define WUFFS_BASE__TOKEN__VBD__FILLER__PUNCTUATION 0x00001 +#define WUFFS_BASE__TOKEN__VBD__FILLER__COMMENT_BLOCK 0x00002 +#define WUFFS_BASE__TOKEN__VBD__FILLER__COMMENT_LINE 0x00004 + +// COMMENT_ANY is a bit-wise or of COMMENT_BLOCK AND COMMENT_LINE. +#define WUFFS_BASE__TOKEN__VBD__FILLER__COMMENT_ANY 0x00006 + +// -------- + +#define WUFFS_BASE__TOKEN__VBD__STRUCTURE__PUSH 0x00001 +#define WUFFS_BASE__TOKEN__VBD__STRUCTURE__POP 0x00002 +#define WUFFS_BASE__TOKEN__VBD__STRUCTURE__FROM_NONE 0x00010 +#define WUFFS_BASE__TOKEN__VBD__STRUCTURE__FROM_LIST 0x00020 +#define WUFFS_BASE__TOKEN__VBD__STRUCTURE__FROM_DICT 0x00040 +#define WUFFS_BASE__TOKEN__VBD__STRUCTURE__TO_NONE 0x01000 +#define WUFFS_BASE__TOKEN__VBD__STRUCTURE__TO_LIST 0x02000 +#define WUFFS_BASE__TOKEN__VBD__STRUCTURE__TO_DICT 0x04000 + +// -------- + +// DEFINITELY_FOO means that the destination bytes (and also the source bytes, +// for 1_DST_1_SRC_COPY) are in the FOO format. Definitely means that the lack +// of the bit means "maybe FOO". It does not necessarily mean "not FOO". +// +// CHAIN_ETC means that decoding the entire token chain forms a UTF-8 or ASCII +// string, not just this current token. CHAIN_ETC_UTF_8 therefore distinguishes +// Unicode (UTF-8) strings from byte strings. MUST means that the the token +// producer (e.g. parser) must verify this. SHOULD means that the token +// consumer (e.g. renderer) should verify this. +// +// When a CHAIN_ETC_UTF_8 bit is set, the parser must ensure that non-ASCII +// code points (with multi-byte UTF-8 encodings) do not straddle token +// boundaries. Checking UTF-8 validity can inspect each token separately. +// +// The lack of any particular bit is conservative: it is valid for all-ASCII +// strings, in a single- or multi-token chain, to have none of these bits set. +#define WUFFS_BASE__TOKEN__VBD__STRING__DEFINITELY_UTF_8 0x00001 +#define WUFFS_BASE__TOKEN__VBD__STRING__CHAIN_MUST_BE_UTF_8 0x00002 +#define WUFFS_BASE__TOKEN__VBD__STRING__CHAIN_SHOULD_BE_UTF_8 0x00004 +#define WUFFS_BASE__TOKEN__VBD__STRING__DEFINITELY_ASCII 0x00010 +#define WUFFS_BASE__TOKEN__VBD__STRING__CHAIN_MUST_BE_ASCII 0x00020 +#define WUFFS_BASE__TOKEN__VBD__STRING__CHAIN_SHOULD_BE_ASCII 0x00040 + +// CONVERT_D_DST_S_SRC means that multiples of S source bytes (possibly padded) +// produces multiples of D destination bytes. For example, +// CONVERT_1_DST_4_SRC_BACKSLASH_X means a source like "\\x23\\x67\\xAB", where +// 12 src bytes encode 3 dst bytes. +// +// Post-processing may further transform those D destination bytes (e.g. treat +// "\\xFF" as the Unicode code point U+00FF instead of the byte 0xFF), but that +// is out of scope of this VBD's semantics. +// +// When src is the empty string, multiple conversion algorithms are applicable +// (so these bits are not necessarily mutually exclusive), all producing the +// same empty dst string. +#define WUFFS_BASE__TOKEN__VBD__STRING__CONVERT_0_DST_1_SRC_DROP 0x00100 +#define WUFFS_BASE__TOKEN__VBD__STRING__CONVERT_1_DST_1_SRC_COPY 0x00200 +#define WUFFS_BASE__TOKEN__VBD__STRING__CONVERT_1_DST_2_SRC_HEXADECIMAL 0x00400 +#define WUFFS_BASE__TOKEN__VBD__STRING__CONVERT_1_DST_4_SRC_BACKSLASH_X 0x00800 +#define WUFFS_BASE__TOKEN__VBD__STRING__CONVERT_3_DST_4_SRC_BASE_64_STD 0x01000 +#define WUFFS_BASE__TOKEN__VBD__STRING__CONVERT_3_DST_4_SRC_BASE_64_URL 0x02000 +#define WUFFS_BASE__TOKEN__VBD__STRING__CONVERT_4_DST_5_SRC_ASCII_85 0x04000 +#define WUFFS_BASE__TOKEN__VBD__STRING__CONVERT_5_DST_8_SRC_BASE_32_HEX 0x08000 +#define WUFFS_BASE__TOKEN__VBD__STRING__CONVERT_5_DST_8_SRC_BASE_32_STD 0x10000 + +// -------- + +#define WUFFS_BASE__TOKEN__VBD__LITERAL__UNDEFINED 0x00001 +#define WUFFS_BASE__TOKEN__VBD__LITERAL__NULL 0x00002 +#define WUFFS_BASE__TOKEN__VBD__LITERAL__FALSE 0x00004 +#define WUFFS_BASE__TOKEN__VBD__LITERAL__TRUE 0x00008 + +// -------- + +// For a source string of "123" or "0x9A", it is valid for a tokenizer to +// return any combination of: +// - WUFFS_BASE__TOKEN__VBD__NUMBER__CONTENT_FLOATING_POINT. +// - WUFFS_BASE__TOKEN__VBD__NUMBER__CONTENT_INTEGER_SIGNED. +// - WUFFS_BASE__TOKEN__VBD__NUMBER__CONTENT_INTEGER_UNSIGNED. +// +// For a source string of "+123" or "-0x9A", only the first two are valid. +// +// For a source string of "123.", only the first one is valid. +#define WUFFS_BASE__TOKEN__VBD__NUMBER__CONTENT_FLOATING_POINT 0x00001 +#define WUFFS_BASE__TOKEN__VBD__NUMBER__CONTENT_INTEGER_SIGNED 0x00002 +#define WUFFS_BASE__TOKEN__VBD__NUMBER__CONTENT_INTEGER_UNSIGNED 0x00004 + +#define WUFFS_BASE__TOKEN__VBD__NUMBER__CONTENT_NEG_INF 0x00010 +#define WUFFS_BASE__TOKEN__VBD__NUMBER__CONTENT_POS_INF 0x00020 +#define WUFFS_BASE__TOKEN__VBD__NUMBER__CONTENT_NEG_NAN 0x00040 +#define WUFFS_BASE__TOKEN__VBD__NUMBER__CONTENT_POS_NAN 0x00080 + +// The number 300 might be represented as "\x01\x2C", "\x2C\x01\x00\x00" or +// "300", which are big-endian, little-endian or text. For binary formats, the +// token length (after adjusting for FORMAT_IGNORE_ETC) discriminates +// e.g. u16 little-endian vs u32 little-endian. +#define WUFFS_BASE__TOKEN__VBD__NUMBER__FORMAT_BINARY_BIG_ENDIAN 0x00100 +#define WUFFS_BASE__TOKEN__VBD__NUMBER__FORMAT_BINARY_LITTLE_ENDIAN 0x00200 +#define WUFFS_BASE__TOKEN__VBD__NUMBER__FORMAT_TEXT 0x00400 + +#define WUFFS_BASE__TOKEN__VBD__NUMBER__FORMAT_IGNORE_FIRST_BYTE 0x01000 + +// -------- + +// wuffs_base__token__value returns the token's high 46 bits, sign-extended. A +// negative value means an extended token, non-negative means a simple token. +static inline int64_t // +wuffs_base__token__value(const wuffs_base__token* t) { + return ((int64_t)(t->repr)) >> WUFFS_BASE__TOKEN__VALUE__SHIFT; +} + +// wuffs_base__token__value_extension returns a negative value if the token was +// not an extended token. +static inline int64_t // +wuffs_base__token__value_extension(const wuffs_base__token* t) { + return (~(int64_t)(t->repr)) >> WUFFS_BASE__TOKEN__VALUE_EXTENSION__SHIFT; +} + +// wuffs_base__token__value_major returns a negative value if the token was not +// a simple token. +static inline int64_t // +wuffs_base__token__value_major(const wuffs_base__token* t) { + return ((int64_t)(t->repr)) >> WUFFS_BASE__TOKEN__VALUE_MAJOR__SHIFT; +} + +// wuffs_base__token__value_base_category returns a negative value if the token +// was not a simple token. +static inline int64_t // +wuffs_base__token__value_base_category(const wuffs_base__token* t) { + return ((int64_t)(t->repr)) >> WUFFS_BASE__TOKEN__VALUE_BASE_CATEGORY__SHIFT; +} + +static inline uint64_t // +wuffs_base__token__value_minor(const wuffs_base__token* t) { + return (t->repr >> WUFFS_BASE__TOKEN__VALUE_MINOR__SHIFT) & 0x1FFFFFF; +} + +static inline uint64_t // +wuffs_base__token__value_base_detail(const wuffs_base__token* t) { + return (t->repr >> WUFFS_BASE__TOKEN__VALUE_BASE_DETAIL__SHIFT) & 0x1FFFFF; +} + +static inline int64_t // +wuffs_base__token__value_base_detail__sign_extended( + const wuffs_base__token* t) { + // The VBD is 21 bits in the middle of t->repr. Left shift the high (64 - 21 + // - ETC__SHIFT) bits off, then right shift (sign-extending) back down. + uint64_t u = t->repr << (43 - WUFFS_BASE__TOKEN__VALUE_BASE_DETAIL__SHIFT); + return ((int64_t)u) >> 43; +} + +static inline bool // +wuffs_base__token__continued(const wuffs_base__token* t) { + return t->repr & 0x10000; +} + +static inline uint64_t // +wuffs_base__token__length(const wuffs_base__token* t) { + return (t->repr >> WUFFS_BASE__TOKEN__LENGTH__SHIFT) & 0xFFFF; +} + +#ifdef __cplusplus + +inline int64_t // +wuffs_base__token::value() const { + return wuffs_base__token__value(this); +} + +inline int64_t // +wuffs_base__token::value_extension() const { + return wuffs_base__token__value_extension(this); +} + +inline int64_t // +wuffs_base__token::value_major() const { + return wuffs_base__token__value_major(this); +} + +inline int64_t // +wuffs_base__token::value_base_category() const { + return wuffs_base__token__value_base_category(this); +} + +inline uint64_t // +wuffs_base__token::value_minor() const { + return wuffs_base__token__value_minor(this); +} + +inline uint64_t // +wuffs_base__token::value_base_detail() const { + return wuffs_base__token__value_base_detail(this); +} + +inline int64_t // +wuffs_base__token::value_base_detail__sign_extended() const { + return wuffs_base__token__value_base_detail__sign_extended(this); +} + +inline bool // +wuffs_base__token::continued() const { + return wuffs_base__token__continued(this); +} + +inline uint64_t // +wuffs_base__token::length() const { + return wuffs_base__token__length(this); +} + +#endif // __cplusplus + +// -------- + +typedef WUFFS_BASE__SLICE(wuffs_base__token) wuffs_base__slice_token; + +static inline wuffs_base__slice_token // +wuffs_base__make_slice_token(wuffs_base__token* ptr, size_t len) { + wuffs_base__slice_token ret; + ret.ptr = ptr; + ret.len = len; + return ret; +} + +static inline wuffs_base__slice_token // +wuffs_base__empty_slice_token() { + wuffs_base__slice_token ret; + ret.ptr = NULL; + ret.len = 0; + return ret; +} + +// -------- + +// wuffs_base__token_buffer_meta is the metadata for a +// wuffs_base__token_buffer's data. +typedef struct wuffs_base__token_buffer_meta__struct { + size_t wi; // Write index. Invariant: wi <= len. + size_t ri; // Read index. Invariant: ri <= wi. + uint64_t pos; // Position of the buffer start relative to the stream start. + bool closed; // No further writes are expected. +} wuffs_base__token_buffer_meta; + +// wuffs_base__token_buffer is a 1-dimensional buffer (a pointer and length) +// plus additional metadata. +// +// A value with all fields zero is a valid, empty buffer. +typedef struct wuffs_base__token_buffer__struct { + wuffs_base__slice_token data; + wuffs_base__token_buffer_meta meta; + +#ifdef __cplusplus + inline bool is_valid() const; + inline void compact(); + inline uint64_t reader_length() const; + inline wuffs_base__token* reader_pointer() const; + inline wuffs_base__slice_token reader_slice() const; + inline uint64_t reader_token_position() const; + inline uint64_t writer_length() const; + inline uint64_t writer_token_position() const; + inline wuffs_base__token* writer_pointer() const; + inline wuffs_base__slice_token writer_slice() const; +#endif // __cplusplus + +} wuffs_base__token_buffer; + +static inline wuffs_base__token_buffer // +wuffs_base__make_token_buffer(wuffs_base__slice_token data, + wuffs_base__token_buffer_meta meta) { + wuffs_base__token_buffer ret; + ret.data = data; + ret.meta = meta; + return ret; +} + +static inline wuffs_base__token_buffer_meta // +wuffs_base__make_token_buffer_meta(size_t wi, + size_t ri, + uint64_t pos, + bool closed) { + wuffs_base__token_buffer_meta ret; + ret.wi = wi; + ret.ri = ri; + ret.pos = pos; + ret.closed = closed; + return ret; +} + +static inline wuffs_base__token_buffer // +wuffs_base__slice_token__reader(wuffs_base__slice_token s, bool closed) { + wuffs_base__token_buffer ret; + ret.data.ptr = s.ptr; + ret.data.len = s.len; + ret.meta.wi = s.len; + ret.meta.ri = 0; + ret.meta.pos = 0; + ret.meta.closed = closed; + return ret; +} + +static inline wuffs_base__token_buffer // +wuffs_base__slice_token__writer(wuffs_base__slice_token s) { + wuffs_base__token_buffer ret; + ret.data.ptr = s.ptr; + ret.data.len = s.len; + ret.meta.wi = 0; + ret.meta.ri = 0; + ret.meta.pos = 0; + ret.meta.closed = false; + return ret; +} + +static inline wuffs_base__token_buffer // +wuffs_base__empty_token_buffer() { + wuffs_base__token_buffer ret; + ret.data.ptr = NULL; + ret.data.len = 0; + ret.meta.wi = 0; + ret.meta.ri = 0; + ret.meta.pos = 0; + ret.meta.closed = false; + return ret; +} + +static inline wuffs_base__token_buffer_meta // +wuffs_base__empty_token_buffer_meta() { + wuffs_base__token_buffer_meta ret; + ret.wi = 0; + ret.ri = 0; + ret.pos = 0; + ret.closed = false; + return ret; +} + +static inline bool // +wuffs_base__token_buffer__is_valid(const wuffs_base__token_buffer* buf) { + if (buf) { + if (buf->data.ptr) { + return (buf->meta.ri <= buf->meta.wi) && (buf->meta.wi <= buf->data.len); + } else { + return (buf->meta.ri == 0) && (buf->meta.wi == 0) && (buf->data.len == 0); + } + } + return false; +} + +// wuffs_base__token_buffer__compact moves any written but unread tokens to the +// start of the buffer. +static inline void // +wuffs_base__token_buffer__compact(wuffs_base__token_buffer* buf) { + if (!buf || (buf->meta.ri == 0)) { + return; + } + buf->meta.pos = wuffs_base__u64__sat_add(buf->meta.pos, buf->meta.ri); + size_t n = buf->meta.wi - buf->meta.ri; + if (n != 0) { + memmove(buf->data.ptr, buf->data.ptr + buf->meta.ri, + n * sizeof(wuffs_base__token)); + } + buf->meta.wi = n; + buf->meta.ri = 0; +} + +static inline uint64_t // +wuffs_base__token_buffer__reader_length(const wuffs_base__token_buffer* buf) { + return buf ? buf->meta.wi - buf->meta.ri : 0; +} + +static inline wuffs_base__token* // +wuffs_base__token_buffer__reader_pointer(const wuffs_base__token_buffer* buf) { + return buf ? (buf->data.ptr + buf->meta.ri) : NULL; +} + +static inline wuffs_base__slice_token // +wuffs_base__token_buffer__reader_slice(const wuffs_base__token_buffer* buf) { + return buf ? wuffs_base__make_slice_token(buf->data.ptr + buf->meta.ri, + buf->meta.wi - buf->meta.ri) + : wuffs_base__empty_slice_token(); +} + +static inline uint64_t // +wuffs_base__token_buffer__reader_token_position( + const wuffs_base__token_buffer* buf) { + return buf ? wuffs_base__u64__sat_add(buf->meta.pos, buf->meta.ri) : 0; +} + +static inline uint64_t // +wuffs_base__token_buffer__writer_length(const wuffs_base__token_buffer* buf) { + return buf ? buf->data.len - buf->meta.wi : 0; +} + +static inline wuffs_base__token* // +wuffs_base__token_buffer__writer_pointer(const wuffs_base__token_buffer* buf) { + return buf ? (buf->data.ptr + buf->meta.wi) : NULL; +} + +static inline wuffs_base__slice_token // +wuffs_base__token_buffer__writer_slice(const wuffs_base__token_buffer* buf) { + return buf ? wuffs_base__make_slice_token(buf->data.ptr + buf->meta.wi, + buf->data.len - buf->meta.wi) + : wuffs_base__empty_slice_token(); +} + +static inline uint64_t // +wuffs_base__token_buffer__writer_token_position( + const wuffs_base__token_buffer* buf) { + return buf ? wuffs_base__u64__sat_add(buf->meta.pos, buf->meta.wi) : 0; +} + +#ifdef __cplusplus + +inline bool // +wuffs_base__token_buffer::is_valid() const { + return wuffs_base__token_buffer__is_valid(this); +} + +inline void // +wuffs_base__token_buffer::compact() { + wuffs_base__token_buffer__compact(this); +} + +inline uint64_t // +wuffs_base__token_buffer::reader_length() const { + return wuffs_base__token_buffer__reader_length(this); +} + +inline wuffs_base__token* // +wuffs_base__token_buffer::reader_pointer() const { + return wuffs_base__token_buffer__reader_pointer(this); +} + +inline wuffs_base__slice_token // +wuffs_base__token_buffer::reader_slice() const { + return wuffs_base__token_buffer__reader_slice(this); +} + +inline uint64_t // +wuffs_base__token_buffer::reader_token_position() const { + return wuffs_base__token_buffer__reader_token_position(this); +} + +inline uint64_t // +wuffs_base__token_buffer::writer_length() const { + return wuffs_base__token_buffer__writer_length(this); +} + +inline wuffs_base__token* // +wuffs_base__token_buffer::writer_pointer() const { + return wuffs_base__token_buffer__writer_pointer(this); +} + +inline wuffs_base__slice_token // +wuffs_base__token_buffer::writer_slice() const { + return wuffs_base__token_buffer__writer_slice(this); +} + +inline uint64_t // +wuffs_base__token_buffer::writer_token_position() const { + return wuffs_base__token_buffer__writer_token_position(this); +} + +#endif // __cplusplus + +// ---------------- Memory Allocation + +// The memory allocation related functions in this section aren't used by Wuffs +// per se, but they may be helpful to the code that uses Wuffs. + +// wuffs_base__malloc_slice_uxx wraps calling a malloc-like function, except +// that it takes a uint64_t number of elements instead of a size_t size in +// bytes, and it returns a slice (a pointer and a length) instead of just a +// pointer. +// +// You can pass the C stdlib's malloc as the malloc_func. +// +// It returns an empty slice (containing a NULL ptr field) if (num_uxx * +// sizeof(uintxx_t)) would overflow SIZE_MAX. + +static inline wuffs_base__slice_u8 // +wuffs_base__malloc_slice_u8(void* (*malloc_func)(size_t), uint64_t num_u8) { + if (malloc_func && (num_u8 <= (SIZE_MAX / sizeof(uint8_t)))) { + void* p = (*malloc_func)((size_t)(num_u8 * sizeof(uint8_t))); + if (p) { + return wuffs_base__make_slice_u8((uint8_t*)(p), (size_t)num_u8); + } + } + return wuffs_base__make_slice_u8(NULL, 0); +} + +static inline wuffs_base__slice_u16 // +wuffs_base__malloc_slice_u16(void* (*malloc_func)(size_t), uint64_t num_u16) { + if (malloc_func && (num_u16 <= (SIZE_MAX / sizeof(uint16_t)))) { + void* p = (*malloc_func)((size_t)(num_u16 * sizeof(uint16_t))); + if (p) { + return wuffs_base__make_slice_u16((uint16_t*)(p), (size_t)num_u16); + } + } + return wuffs_base__make_slice_u16(NULL, 0); +} + +static inline wuffs_base__slice_u32 // +wuffs_base__malloc_slice_u32(void* (*malloc_func)(size_t), uint64_t num_u32) { + if (malloc_func && (num_u32 <= (SIZE_MAX / sizeof(uint32_t)))) { + void* p = (*malloc_func)((size_t)(num_u32 * sizeof(uint32_t))); + if (p) { + return wuffs_base__make_slice_u32((uint32_t*)(p), (size_t)num_u32); + } + } + return wuffs_base__make_slice_u32(NULL, 0); +} + +static inline wuffs_base__slice_u64 // +wuffs_base__malloc_slice_u64(void* (*malloc_func)(size_t), uint64_t num_u64) { + if (malloc_func && (num_u64 <= (SIZE_MAX / sizeof(uint64_t)))) { + void* p = (*malloc_func)((size_t)(num_u64 * sizeof(uint64_t))); + if (p) { + return wuffs_base__make_slice_u64((uint64_t*)(p), (size_t)num_u64); + } + } + return wuffs_base__make_slice_u64(NULL, 0); +} + +// ---------------- Images + +// wuffs_base__color_u32_argb_premul is an 8 bit per channel premultiplied +// Alpha, Red, Green, Blue color, as a uint32_t value. Its value is always +// 0xAARRGGBB (Alpha most significant, Blue least), regardless of endianness. +typedef uint32_t wuffs_base__color_u32_argb_premul; + +// wuffs_base__color_u32_argb_premul__is_valid returns whether c's Red, Green +// and Blue channels are all less than or equal to its Alpha channel. c uses +// premultiplied alpha, so 50% opaque 100% saturated red is 0x7F7F_0000 and a +// value like 0x7F80_0000 is invalid. +static inline bool // +wuffs_base__color_u32_argb_premul__is_valid( + wuffs_base__color_u32_argb_premul c) { + uint32_t a = 0xFF & (c >> 24); + uint32_t r = 0xFF & (c >> 16); + uint32_t g = 0xFF & (c >> 8); + uint32_t b = 0xFF & (c >> 0); + return (a >= r) && (a >= g) && (a >= b); +} + +static inline uint16_t // +wuffs_base__color_u32_argb_premul__as__color_u16_rgb_565( + wuffs_base__color_u32_argb_premul c) { + uint32_t r5 = 0xF800 & (c >> 8); + uint32_t g6 = 0x07E0 & (c >> 5); + uint32_t b5 = 0x001F & (c >> 3); + return (uint16_t)(r5 | g6 | b5); +} + +static inline wuffs_base__color_u32_argb_premul // +wuffs_base__color_u16_rgb_565__as__color_u32_argb_premul(uint16_t rgb_565) { + uint32_t b5 = 0x1F & (rgb_565 >> 0); + uint32_t b = (b5 << 3) | (b5 >> 2); + uint32_t g6 = 0x3F & (rgb_565 >> 5); + uint32_t g = (g6 << 2) | (g6 >> 4); + uint32_t r5 = 0x1F & (rgb_565 >> 11); + uint32_t r = (r5 << 3) | (r5 >> 2); + return 0xFF000000 | (r << 16) | (g << 8) | (b << 0); +} + +static inline uint8_t // +wuffs_base__color_u32_argb_premul__as__color_u8_gray( + wuffs_base__color_u32_argb_premul c) { + // Work in 16-bit color. + uint32_t cr = 0x101 * (0xFF & (c >> 16)); + uint32_t cg = 0x101 * (0xFF & (c >> 8)); + uint32_t cb = 0x101 * (0xFF & (c >> 0)); + + // These coefficients (the fractions 0.299, 0.587 and 0.114) are the same + // as those given by the JFIF specification. + // + // Note that 19595 + 38470 + 7471 equals 65536, also known as (1 << 16). We + // shift by 24, not just by 16, because the return value is 8-bit color, not + // 16-bit color. + uint32_t weighted_average = (19595 * cr) + (38470 * cg) + (7471 * cb) + 32768; + return (uint8_t)(weighted_average >> 24); +} + +static inline uint16_t // +wuffs_base__color_u32_argb_premul__as__color_u16_gray( + wuffs_base__color_u32_argb_premul c) { + // Work in 16-bit color. + uint32_t cr = 0x101 * (0xFF & (c >> 16)); + uint32_t cg = 0x101 * (0xFF & (c >> 8)); + uint32_t cb = 0x101 * (0xFF & (c >> 0)); + + // These coefficients (the fractions 0.299, 0.587 and 0.114) are the same + // as those given by the JFIF specification. + // + // Note that 19595 + 38470 + 7471 equals 65536, also known as (1 << 16). + uint32_t weighted_average = (19595 * cr) + (38470 * cg) + (7471 * cb) + 32768; + return (uint16_t)(weighted_average >> 16); +} + +// wuffs_base__color_u32_argb_nonpremul__as__color_u32_argb_premul converts +// from non-premultiplied alpha to premultiplied alpha. +static inline wuffs_base__color_u32_argb_premul // +wuffs_base__color_u32_argb_nonpremul__as__color_u32_argb_premul( + uint32_t argb_nonpremul) { + // Multiplying by 0x101 (twice, once for alpha and once for color) converts + // from 8-bit to 16-bit color. Shifting right by 8 undoes that. + // + // Working in the higher bit depth can produce slightly different (and + // arguably slightly more accurate) results. For example, given 8-bit blue + // and alpha of 0x80 and 0x81: + // + // - ((0x80 * 0x81 ) / 0xFF ) = 0x40 = 0x40 + // - ((0x8080 * 0x8181) / 0xFFFF) >> 8 = 0x4101 >> 8 = 0x41 + uint32_t a = 0xFF & (argb_nonpremul >> 24); + uint32_t a16 = a * (0x101 * 0x101); + + uint32_t r = 0xFF & (argb_nonpremul >> 16); + r = ((r * a16) / 0xFFFF) >> 8; + uint32_t g = 0xFF & (argb_nonpremul >> 8); + g = ((g * a16) / 0xFFFF) >> 8; + uint32_t b = 0xFF & (argb_nonpremul >> 0); + b = ((b * a16) / 0xFFFF) >> 8; + + return (a << 24) | (r << 16) | (g << 8) | (b << 0); +} + +// wuffs_base__color_u32_argb_premul__as__color_u32_argb_nonpremul converts +// from premultiplied alpha to non-premultiplied alpha. +static inline uint32_t // +wuffs_base__color_u32_argb_premul__as__color_u32_argb_nonpremul( + wuffs_base__color_u32_argb_premul c) { + uint32_t a = 0xFF & (c >> 24); + if (a == 0xFF) { + return c; + } else if (a == 0) { + return 0; + } + uint32_t a16 = a * 0x101; + + uint32_t r = 0xFF & (c >> 16); + r = ((r * (0x101 * 0xFFFF)) / a16) >> 8; + uint32_t g = 0xFF & (c >> 8); + g = ((g * (0x101 * 0xFFFF)) / a16) >> 8; + uint32_t b = 0xFF & (c >> 0); + b = ((b * (0x101 * 0xFFFF)) / a16) >> 8; + + return (a << 24) | (r << 16) | (g << 8) | (b << 0); +} + +// wuffs_base__color_u64_argb_nonpremul__as__color_u32_argb_premul converts +// from 4x16LE non-premultiplied alpha to 4x8 premultiplied alpha. +static inline wuffs_base__color_u32_argb_premul // +wuffs_base__color_u64_argb_nonpremul__as__color_u32_argb_premul( + uint64_t argb_nonpremul) { + uint32_t a16 = ((uint32_t)(0xFFFF & (argb_nonpremul >> 48))); + + uint32_t r16 = ((uint32_t)(0xFFFF & (argb_nonpremul >> 32))); + r16 = (r16 * a16) / 0xFFFF; + uint32_t g16 = ((uint32_t)(0xFFFF & (argb_nonpremul >> 16))); + g16 = (g16 * a16) / 0xFFFF; + uint32_t b16 = ((uint32_t)(0xFFFF & (argb_nonpremul >> 0))); + b16 = (b16 * a16) / 0xFFFF; + + return ((a16 >> 8) << 24) | ((r16 >> 8) << 16) | ((g16 >> 8) << 8) | + ((b16 >> 8) << 0); +} + +// wuffs_base__color_u32_argb_premul__as__color_u64_argb_nonpremul converts +// from 4x8 premultiplied alpha to 4x16LE non-premultiplied alpha. +static inline uint64_t // +wuffs_base__color_u32_argb_premul__as__color_u64_argb_nonpremul( + wuffs_base__color_u32_argb_premul c) { + uint32_t a = 0xFF & (c >> 24); + if (a == 0xFF) { + uint64_t r16 = 0x101 * (0xFF & (c >> 16)); + uint64_t g16 = 0x101 * (0xFF & (c >> 8)); + uint64_t b16 = 0x101 * (0xFF & (c >> 0)); + return 0xFFFF000000000000u | (r16 << 32) | (g16 << 16) | (b16 << 0); + } else if (a == 0) { + return 0; + } + uint64_t a16 = a * 0x101; + + uint64_t r = 0xFF & (c >> 16); + uint64_t r16 = (r * (0x101 * 0xFFFF)) / a16; + uint64_t g = 0xFF & (c >> 8); + uint64_t g16 = (g * (0x101 * 0xFFFF)) / a16; + uint64_t b = 0xFF & (c >> 0); + uint64_t b16 = (b * (0x101 * 0xFFFF)) / a16; + + return (a16 << 48) | (r16 << 32) | (g16 << 16) | (b16 << 0); +} + +static inline uint64_t // +wuffs_base__color_u32__as__color_u64(uint32_t c) { + uint64_t a16 = 0x101 * (0xFF & (c >> 24)); + uint64_t r16 = 0x101 * (0xFF & (c >> 16)); + uint64_t g16 = 0x101 * (0xFF & (c >> 8)); + uint64_t b16 = 0x101 * (0xFF & (c >> 0)); + return (a16 << 48) | (r16 << 32) | (g16 << 16) | (b16 << 0); +} + +static inline uint32_t // +wuffs_base__color_u64__as__color_u32(uint64_t c) { + uint32_t a = ((uint32_t)(0xFF & (c >> 56))); + uint32_t r = ((uint32_t)(0xFF & (c >> 40))); + uint32_t g = ((uint32_t)(0xFF & (c >> 24))); + uint32_t b = ((uint32_t)(0xFF & (c >> 8))); + return (a << 24) | (r << 16) | (g << 8) | (b << 0); +} + +// -------- + +typedef uint8_t wuffs_base__pixel_blend; + +// wuffs_base__pixel_blend encodes how to blend source and destination pixels, +// accounting for transparency. It encompasses the Porter-Duff compositing +// operators as well as the other blending modes defined by PDF. +// +// TODO: implement the other modes. +#define WUFFS_BASE__PIXEL_BLEND__SRC ((wuffs_base__pixel_blend)0) +#define WUFFS_BASE__PIXEL_BLEND__SRC_OVER ((wuffs_base__pixel_blend)1) + +// -------- + +// wuffs_base__pixel_alpha_transparency is a pixel format's alpha channel +// model. It is a property of the pixel format in general, not of a specific +// pixel. An RGBA pixel format (with alpha) can still have fully opaque pixels. +typedef uint32_t wuffs_base__pixel_alpha_transparency; + +#define WUFFS_BASE__PIXEL_ALPHA_TRANSPARENCY__OPAQUE 0 +#define WUFFS_BASE__PIXEL_ALPHA_TRANSPARENCY__NONPREMULTIPLIED_ALPHA 1 +#define WUFFS_BASE__PIXEL_ALPHA_TRANSPARENCY__PREMULTIPLIED_ALPHA 2 +#define WUFFS_BASE__PIXEL_ALPHA_TRANSPARENCY__BINARY_ALPHA 3 + +// Deprecated: use WUFFS_BASE__PIXEL_ALPHA_TRANSPARENCY__NONPREMULTIPLIED_ALPHA +// instead. +#define WUFFS_BASE__PIXEL_ALPHA_TRANSPARENCY__NON_PREMULTIPLIED_ALPHA 1 + +// -------- + +#define WUFFS_BASE__PIXEL_FORMAT__NUM_PLANES_MAX 4 + +#define WUFFS_BASE__PIXEL_FORMAT__INDEXED__INDEX_PLANE 0 +#define WUFFS_BASE__PIXEL_FORMAT__INDEXED__COLOR_PLANE 3 + +// A palette is 256 entries × 4 bytes per entry (e.g. BGRA). +#define WUFFS_BASE__PIXEL_FORMAT__INDEXED__PALETTE_BYTE_LENGTH 1024 + +// wuffs_base__pixel_format encodes the format of the bytes that constitute an +// image frame's pixel data. +// +// See https://github.com/google/wuffs/blob/main/doc/note/pixel-formats.md +// +// Do not manipulate its bits directly; they are private implementation +// details. Use methods such as wuffs_base__pixel_format__num_planes instead. +typedef struct wuffs_base__pixel_format__struct { + uint32_t repr; + +#ifdef __cplusplus + inline bool is_valid() const; + inline uint32_t bits_per_pixel() const; + inline bool is_direct() const; + inline bool is_indexed() const; + inline bool is_interleaved() const; + inline bool is_planar() const; + inline uint32_t num_planes() const; + inline wuffs_base__pixel_alpha_transparency transparency() const; +#endif // __cplusplus + +} wuffs_base__pixel_format; + +static inline wuffs_base__pixel_format // +wuffs_base__make_pixel_format(uint32_t repr) { + wuffs_base__pixel_format f; + f.repr = repr; + return f; +} + +// Common 8-bit-depth pixel formats. This list is not exhaustive; not all valid +// wuffs_base__pixel_format values are present. + +#define WUFFS_BASE__PIXEL_FORMAT__INVALID 0x00000000 + +#define WUFFS_BASE__PIXEL_FORMAT__A 0x02000008 + +#define WUFFS_BASE__PIXEL_FORMAT__Y 0x20000008 +#define WUFFS_BASE__PIXEL_FORMAT__Y_16LE 0x2000000B +#define WUFFS_BASE__PIXEL_FORMAT__Y_16BE 0x2010000B +#define WUFFS_BASE__PIXEL_FORMAT__YA_NONPREMUL 0x21000008 +#define WUFFS_BASE__PIXEL_FORMAT__YA_PREMUL 0x22000008 + +#define WUFFS_BASE__PIXEL_FORMAT__YCBCR 0x40020888 +#define WUFFS_BASE__PIXEL_FORMAT__YCBCRA_NONPREMUL 0x41038888 +#define WUFFS_BASE__PIXEL_FORMAT__YCBCRK 0x50038888 + +#define WUFFS_BASE__PIXEL_FORMAT__YCOCG 0x60020888 +#define WUFFS_BASE__PIXEL_FORMAT__YCOCGA_NONPREMUL 0x61038888 +#define WUFFS_BASE__PIXEL_FORMAT__YCOCGK 0x70038888 + +#define WUFFS_BASE__PIXEL_FORMAT__INDEXED__BGRA_NONPREMUL 0x81040008 +#define WUFFS_BASE__PIXEL_FORMAT__INDEXED__BGRA_PREMUL 0x82040008 +#define WUFFS_BASE__PIXEL_FORMAT__INDEXED__BGRA_BINARY 0x83040008 + +#define WUFFS_BASE__PIXEL_FORMAT__BGR_565 0x80000565 +#define WUFFS_BASE__PIXEL_FORMAT__BGR 0x80000888 +#define WUFFS_BASE__PIXEL_FORMAT__BGRA_NONPREMUL 0x81008888 +#define WUFFS_BASE__PIXEL_FORMAT__BGRA_NONPREMUL_4X16LE 0x8100BBBB +#define WUFFS_BASE__PIXEL_FORMAT__BGRA_PREMUL 0x82008888 +#define WUFFS_BASE__PIXEL_FORMAT__BGRA_PREMUL_4X16LE 0x8200BBBB +#define WUFFS_BASE__PIXEL_FORMAT__BGRA_BINARY 0x83008888 +#define WUFFS_BASE__PIXEL_FORMAT__BGRX 0x90008888 + +#define WUFFS_BASE__PIXEL_FORMAT__RGB 0xA0000888 +#define WUFFS_BASE__PIXEL_FORMAT__RGBA_NONPREMUL 0xA1008888 +#define WUFFS_BASE__PIXEL_FORMAT__RGBA_NONPREMUL_4X16LE 0xA100BBBB +#define WUFFS_BASE__PIXEL_FORMAT__RGBA_PREMUL 0xA2008888 +#define WUFFS_BASE__PIXEL_FORMAT__RGBA_PREMUL_4X16LE 0xA200BBBB +#define WUFFS_BASE__PIXEL_FORMAT__RGBA_BINARY 0xA3008888 +#define WUFFS_BASE__PIXEL_FORMAT__RGBX 0xB0008888 + +#define WUFFS_BASE__PIXEL_FORMAT__CMY 0xC0020888 +#define WUFFS_BASE__PIXEL_FORMAT__CMYK 0xD0038888 + +extern const uint32_t wuffs_base__pixel_format__bits_per_channel[16]; + +static inline bool // +wuffs_base__pixel_format__is_valid(const wuffs_base__pixel_format* f) { + return f->repr != 0; +} + +// wuffs_base__pixel_format__bits_per_pixel returns the number of bits per +// pixel for interleaved pixel formats, and returns 0 for planar pixel formats. +static inline uint32_t // +wuffs_base__pixel_format__bits_per_pixel(const wuffs_base__pixel_format* f) { + if (((f->repr >> 16) & 0x03) != 0) { + return 0; + } + return wuffs_base__pixel_format__bits_per_channel[0x0F & (f->repr >> 0)] + + wuffs_base__pixel_format__bits_per_channel[0x0F & (f->repr >> 4)] + + wuffs_base__pixel_format__bits_per_channel[0x0F & (f->repr >> 8)] + + wuffs_base__pixel_format__bits_per_channel[0x0F & (f->repr >> 12)]; +} + +static inline bool // +wuffs_base__pixel_format__is_direct(const wuffs_base__pixel_format* f) { + return ((f->repr >> 18) & 0x01) == 0; +} + +static inline bool // +wuffs_base__pixel_format__is_indexed(const wuffs_base__pixel_format* f) { + return ((f->repr >> 18) & 0x01) != 0; +} + +static inline bool // +wuffs_base__pixel_format__is_interleaved(const wuffs_base__pixel_format* f) { + return ((f->repr >> 16) & 0x03) == 0; +} + +static inline bool // +wuffs_base__pixel_format__is_planar(const wuffs_base__pixel_format* f) { + return ((f->repr >> 16) & 0x03) != 0; +} + +static inline uint32_t // +wuffs_base__pixel_format__num_planes(const wuffs_base__pixel_format* f) { + return ((f->repr >> 16) & 0x03) + 1; +} + +static inline wuffs_base__pixel_alpha_transparency // +wuffs_base__pixel_format__transparency(const wuffs_base__pixel_format* f) { + return (wuffs_base__pixel_alpha_transparency)((f->repr >> 24) & 0x03); +} + +#ifdef __cplusplus + +inline bool // +wuffs_base__pixel_format::is_valid() const { + return wuffs_base__pixel_format__is_valid(this); +} + +inline uint32_t // +wuffs_base__pixel_format::bits_per_pixel() const { + return wuffs_base__pixel_format__bits_per_pixel(this); +} + +inline bool // +wuffs_base__pixel_format::is_direct() const { + return wuffs_base__pixel_format__is_direct(this); +} + +inline bool // +wuffs_base__pixel_format::is_indexed() const { + return wuffs_base__pixel_format__is_indexed(this); +} + +inline bool // +wuffs_base__pixel_format::is_interleaved() const { + return wuffs_base__pixel_format__is_interleaved(this); +} + +inline bool // +wuffs_base__pixel_format::is_planar() const { + return wuffs_base__pixel_format__is_planar(this); +} + +inline uint32_t // +wuffs_base__pixel_format::num_planes() const { + return wuffs_base__pixel_format__num_planes(this); +} + +inline wuffs_base__pixel_alpha_transparency // +wuffs_base__pixel_format::transparency() const { + return wuffs_base__pixel_format__transparency(this); +} + +#endif // __cplusplus + +// -------- + +// wuffs_base__pixel_subsampling encodes whether sample values cover one pixel +// or cover multiple pixels. +// +// See https://github.com/google/wuffs/blob/main/doc/note/pixel-subsampling.md +// +// Do not manipulate its bits directly; they are private implementation +// details. Use methods such as wuffs_base__pixel_subsampling__bias_x instead. +typedef struct wuffs_base__pixel_subsampling__struct { + uint32_t repr; + +#ifdef __cplusplus + inline uint32_t bias_x(uint32_t plane) const; + inline uint32_t denominator_x(uint32_t plane) const; + inline uint32_t bias_y(uint32_t plane) const; + inline uint32_t denominator_y(uint32_t plane) const; +#endif // __cplusplus + +} wuffs_base__pixel_subsampling; + +static inline wuffs_base__pixel_subsampling // +wuffs_base__make_pixel_subsampling(uint32_t repr) { + wuffs_base__pixel_subsampling s; + s.repr = repr; + return s; +} + +#define WUFFS_BASE__PIXEL_SUBSAMPLING__NONE 0x00000000 + +#define WUFFS_BASE__PIXEL_SUBSAMPLING__444 0x000000 +#define WUFFS_BASE__PIXEL_SUBSAMPLING__440 0x010100 +#define WUFFS_BASE__PIXEL_SUBSAMPLING__422 0x101000 +#define WUFFS_BASE__PIXEL_SUBSAMPLING__420 0x111100 +#define WUFFS_BASE__PIXEL_SUBSAMPLING__411 0x303000 +#define WUFFS_BASE__PIXEL_SUBSAMPLING__410 0x313100 + +static inline uint32_t // +wuffs_base__pixel_subsampling__bias_x(const wuffs_base__pixel_subsampling* s, + uint32_t plane) { + uint32_t shift = ((plane & 0x03) * 8) + 6; + return (s->repr >> shift) & 0x03; +} + +static inline uint32_t // +wuffs_base__pixel_subsampling__denominator_x( + const wuffs_base__pixel_subsampling* s, + uint32_t plane) { + uint32_t shift = ((plane & 0x03) * 8) + 4; + return ((s->repr >> shift) & 0x03) + 1; +} + +static inline uint32_t // +wuffs_base__pixel_subsampling__bias_y(const wuffs_base__pixel_subsampling* s, + uint32_t plane) { + uint32_t shift = ((plane & 0x03) * 8) + 2; + return (s->repr >> shift) & 0x03; +} + +static inline uint32_t // +wuffs_base__pixel_subsampling__denominator_y( + const wuffs_base__pixel_subsampling* s, + uint32_t plane) { + uint32_t shift = ((plane & 0x03) * 8) + 0; + return ((s->repr >> shift) & 0x03) + 1; +} + +#ifdef __cplusplus + +inline uint32_t // +wuffs_base__pixel_subsampling::bias_x(uint32_t plane) const { + return wuffs_base__pixel_subsampling__bias_x(this, plane); +} + +inline uint32_t // +wuffs_base__pixel_subsampling::denominator_x(uint32_t plane) const { + return wuffs_base__pixel_subsampling__denominator_x(this, plane); +} + +inline uint32_t // +wuffs_base__pixel_subsampling::bias_y(uint32_t plane) const { + return wuffs_base__pixel_subsampling__bias_y(this, plane); +} + +inline uint32_t // +wuffs_base__pixel_subsampling::denominator_y(uint32_t plane) const { + return wuffs_base__pixel_subsampling__denominator_y(this, plane); +} + +#endif // __cplusplus + +// -------- + +typedef struct wuffs_base__pixel_config__struct { + // Do not access the private_impl's fields directly. There is no API/ABI + // compatibility or safety guarantee if you do so. + struct { + wuffs_base__pixel_format pixfmt; + wuffs_base__pixel_subsampling pixsub; + uint32_t width; + uint32_t height; + } private_impl; + +#ifdef __cplusplus + inline void set(uint32_t pixfmt_repr, + uint32_t pixsub_repr, + uint32_t width, + uint32_t height); + inline void invalidate(); + inline bool is_valid() const; + inline wuffs_base__pixel_format pixel_format() const; + inline wuffs_base__pixel_subsampling pixel_subsampling() const; + inline wuffs_base__rect_ie_u32 bounds() const; + inline uint32_t width() const; + inline uint32_t height() const; + inline uint64_t pixbuf_len() const; +#endif // __cplusplus + +} wuffs_base__pixel_config; + +static inline wuffs_base__pixel_config // +wuffs_base__null_pixel_config() { + wuffs_base__pixel_config ret; + ret.private_impl.pixfmt.repr = 0; + ret.private_impl.pixsub.repr = 0; + ret.private_impl.width = 0; + ret.private_impl.height = 0; + return ret; +} + +// TODO: Should this function return bool? An error type? +static inline void // +wuffs_base__pixel_config__set(wuffs_base__pixel_config* c, + uint32_t pixfmt_repr, + uint32_t pixsub_repr, + uint32_t width, + uint32_t height) { + if (!c) { + return; + } + if (pixfmt_repr) { + uint64_t wh = ((uint64_t)width) * ((uint64_t)height); + // TODO: handle things other than 1 byte per pixel. + if (wh <= ((uint64_t)SIZE_MAX)) { + c->private_impl.pixfmt.repr = pixfmt_repr; + c->private_impl.pixsub.repr = pixsub_repr; + c->private_impl.width = width; + c->private_impl.height = height; + return; + } + } + + c->private_impl.pixfmt.repr = 0; + c->private_impl.pixsub.repr = 0; + c->private_impl.width = 0; + c->private_impl.height = 0; +} + +static inline void // +wuffs_base__pixel_config__invalidate(wuffs_base__pixel_config* c) { + if (c) { + c->private_impl.pixfmt.repr = 0; + c->private_impl.pixsub.repr = 0; + c->private_impl.width = 0; + c->private_impl.height = 0; + } +} + +static inline bool // +wuffs_base__pixel_config__is_valid(const wuffs_base__pixel_config* c) { + return c && c->private_impl.pixfmt.repr; +} + +static inline wuffs_base__pixel_format // +wuffs_base__pixel_config__pixel_format(const wuffs_base__pixel_config* c) { + return c ? c->private_impl.pixfmt : wuffs_base__make_pixel_format(0); +} + +static inline wuffs_base__pixel_subsampling // +wuffs_base__pixel_config__pixel_subsampling(const wuffs_base__pixel_config* c) { + return c ? c->private_impl.pixsub : wuffs_base__make_pixel_subsampling(0); +} + +static inline wuffs_base__rect_ie_u32 // +wuffs_base__pixel_config__bounds(const wuffs_base__pixel_config* c) { + if (c) { + wuffs_base__rect_ie_u32 ret; + ret.min_incl_x = 0; + ret.min_incl_y = 0; + ret.max_excl_x = c->private_impl.width; + ret.max_excl_y = c->private_impl.height; + return ret; + } + + wuffs_base__rect_ie_u32 ret; + ret.min_incl_x = 0; + ret.min_incl_y = 0; + ret.max_excl_x = 0; + ret.max_excl_y = 0; + return ret; +} + +static inline uint32_t // +wuffs_base__pixel_config__width(const wuffs_base__pixel_config* c) { + return c ? c->private_impl.width : 0; +} + +static inline uint32_t // +wuffs_base__pixel_config__height(const wuffs_base__pixel_config* c) { + return c ? c->private_impl.height : 0; +} + +// TODO: this is the right API for planar (not interleaved) pixbufs? Should it +// allow decoding into a color model different from the format's intrinsic one? +// For example, decoding a JPEG image straight to RGBA instead of to YCbCr? +static inline uint64_t // +wuffs_base__pixel_config__pixbuf_len(const wuffs_base__pixel_config* c) { + if (!c) { + return 0; + } + if (wuffs_base__pixel_format__is_planar(&c->private_impl.pixfmt)) { + // TODO: support planar pixel formats, concious of pixel subsampling. + return 0; + } + uint32_t bits_per_pixel = + wuffs_base__pixel_format__bits_per_pixel(&c->private_impl.pixfmt); + if ((bits_per_pixel == 0) || ((bits_per_pixel % 8) != 0)) { + // TODO: support fraction-of-byte pixels, e.g. 1 bit per pixel? + return 0; + } + uint64_t bytes_per_pixel = bits_per_pixel / 8; + + uint64_t n = + ((uint64_t)c->private_impl.width) * ((uint64_t)c->private_impl.height); + if (n > (UINT64_MAX / bytes_per_pixel)) { + return 0; + } + n *= bytes_per_pixel; + + if (wuffs_base__pixel_format__is_indexed(&c->private_impl.pixfmt)) { + if (n > + (UINT64_MAX - WUFFS_BASE__PIXEL_FORMAT__INDEXED__PALETTE_BYTE_LENGTH)) { + return 0; + } + n += WUFFS_BASE__PIXEL_FORMAT__INDEXED__PALETTE_BYTE_LENGTH; + } + + return n; +} + +#ifdef __cplusplus + +inline void // +wuffs_base__pixel_config::set(uint32_t pixfmt_repr, + uint32_t pixsub_repr, + uint32_t width, + uint32_t height) { + wuffs_base__pixel_config__set(this, pixfmt_repr, pixsub_repr, width, height); +} + +inline void // +wuffs_base__pixel_config::invalidate() { + wuffs_base__pixel_config__invalidate(this); +} + +inline bool // +wuffs_base__pixel_config::is_valid() const { + return wuffs_base__pixel_config__is_valid(this); +} + +inline wuffs_base__pixel_format // +wuffs_base__pixel_config::pixel_format() const { + return wuffs_base__pixel_config__pixel_format(this); +} + +inline wuffs_base__pixel_subsampling // +wuffs_base__pixel_config::pixel_subsampling() const { + return wuffs_base__pixel_config__pixel_subsampling(this); +} + +inline wuffs_base__rect_ie_u32 // +wuffs_base__pixel_config::bounds() const { + return wuffs_base__pixel_config__bounds(this); +} + +inline uint32_t // +wuffs_base__pixel_config::width() const { + return wuffs_base__pixel_config__width(this); +} + +inline uint32_t // +wuffs_base__pixel_config::height() const { + return wuffs_base__pixel_config__height(this); +} + +inline uint64_t // +wuffs_base__pixel_config::pixbuf_len() const { + return wuffs_base__pixel_config__pixbuf_len(this); +} + +#endif // __cplusplus + +// -------- + +typedef struct wuffs_base__image_config__struct { + wuffs_base__pixel_config pixcfg; + + // Do not access the private_impl's fields directly. There is no API/ABI + // compatibility or safety guarantee if you do so. + struct { + uint64_t first_frame_io_position; + bool first_frame_is_opaque; + } private_impl; + +#ifdef __cplusplus + inline void set(uint32_t pixfmt_repr, + uint32_t pixsub_repr, + uint32_t width, + uint32_t height, + uint64_t first_frame_io_position, + bool first_frame_is_opaque); + inline void invalidate(); + inline bool is_valid() const; + inline uint64_t first_frame_io_position() const; + inline bool first_frame_is_opaque() const; +#endif // __cplusplus + +} wuffs_base__image_config; + +static inline wuffs_base__image_config // +wuffs_base__null_image_config() { + wuffs_base__image_config ret; + ret.pixcfg = wuffs_base__null_pixel_config(); + ret.private_impl.first_frame_io_position = 0; + ret.private_impl.first_frame_is_opaque = false; + return ret; +} + +// TODO: Should this function return bool? An error type? +static inline void // +wuffs_base__image_config__set(wuffs_base__image_config* c, + uint32_t pixfmt_repr, + uint32_t pixsub_repr, + uint32_t width, + uint32_t height, + uint64_t first_frame_io_position, + bool first_frame_is_opaque) { + if (!c) { + return; + } + if (pixfmt_repr) { + c->pixcfg.private_impl.pixfmt.repr = pixfmt_repr; + c->pixcfg.private_impl.pixsub.repr = pixsub_repr; + c->pixcfg.private_impl.width = width; + c->pixcfg.private_impl.height = height; + c->private_impl.first_frame_io_position = first_frame_io_position; + c->private_impl.first_frame_is_opaque = first_frame_is_opaque; + return; + } + + c->pixcfg.private_impl.pixfmt.repr = 0; + c->pixcfg.private_impl.pixsub.repr = 0; + c->pixcfg.private_impl.width = 0; + c->pixcfg.private_impl.height = 0; + c->private_impl.first_frame_io_position = 0; + c->private_impl.first_frame_is_opaque = 0; +} + +static inline void // +wuffs_base__image_config__invalidate(wuffs_base__image_config* c) { + if (c) { + c->pixcfg.private_impl.pixfmt.repr = 0; + c->pixcfg.private_impl.pixsub.repr = 0; + c->pixcfg.private_impl.width = 0; + c->pixcfg.private_impl.height = 0; + c->private_impl.first_frame_io_position = 0; + c->private_impl.first_frame_is_opaque = 0; + } +} + +static inline bool // +wuffs_base__image_config__is_valid(const wuffs_base__image_config* c) { + return c && wuffs_base__pixel_config__is_valid(&(c->pixcfg)); +} + +static inline uint64_t // +wuffs_base__image_config__first_frame_io_position( + const wuffs_base__image_config* c) { + return c ? c->private_impl.first_frame_io_position : 0; +} + +static inline bool // +wuffs_base__image_config__first_frame_is_opaque( + const wuffs_base__image_config* c) { + return c ? c->private_impl.first_frame_is_opaque : false; +} + +#ifdef __cplusplus + +inline void // +wuffs_base__image_config::set(uint32_t pixfmt_repr, + uint32_t pixsub_repr, + uint32_t width, + uint32_t height, + uint64_t first_frame_io_position, + bool first_frame_is_opaque) { + wuffs_base__image_config__set(this, pixfmt_repr, pixsub_repr, width, height, + first_frame_io_position, first_frame_is_opaque); +} + +inline void // +wuffs_base__image_config::invalidate() { + wuffs_base__image_config__invalidate(this); +} + +inline bool // +wuffs_base__image_config::is_valid() const { + return wuffs_base__image_config__is_valid(this); +} + +inline uint64_t // +wuffs_base__image_config::first_frame_io_position() const { + return wuffs_base__image_config__first_frame_io_position(this); +} + +inline bool // +wuffs_base__image_config::first_frame_is_opaque() const { + return wuffs_base__image_config__first_frame_is_opaque(this); +} + +#endif // __cplusplus + +// -------- + +// wuffs_base__animation_disposal encodes, for an animated image, how to +// dispose of a frame after displaying it: +// - None means to draw the next frame on top of this one. +// - Restore Background means to clear the frame's dirty rectangle to "the +// background color" (in practice, this means transparent black) before +// drawing the next frame. +// - Restore Previous means to undo the current frame, so that the next frame +// is drawn on top of the previous one. +typedef uint8_t wuffs_base__animation_disposal; + +#define WUFFS_BASE__ANIMATION_DISPOSAL__NONE ((wuffs_base__animation_disposal)0) +#define WUFFS_BASE__ANIMATION_DISPOSAL__RESTORE_BACKGROUND \ + ((wuffs_base__animation_disposal)1) +#define WUFFS_BASE__ANIMATION_DISPOSAL__RESTORE_PREVIOUS \ + ((wuffs_base__animation_disposal)2) + +// -------- + +typedef struct wuffs_base__frame_config__struct { + // Do not access the private_impl's fields directly. There is no API/ABI + // compatibility or safety guarantee if you do so. + struct { + wuffs_base__rect_ie_u32 bounds; + wuffs_base__flicks duration; + uint64_t index; + uint64_t io_position; + wuffs_base__animation_disposal disposal; + bool opaque_within_bounds; + bool overwrite_instead_of_blend; + wuffs_base__color_u32_argb_premul background_color; + } private_impl; + +#ifdef __cplusplus + inline void set(wuffs_base__rect_ie_u32 bounds, + wuffs_base__flicks duration, + uint64_t index, + uint64_t io_position, + wuffs_base__animation_disposal disposal, + bool opaque_within_bounds, + bool overwrite_instead_of_blend, + wuffs_base__color_u32_argb_premul background_color); + inline wuffs_base__rect_ie_u32 bounds() const; + inline uint32_t width() const; + inline uint32_t height() const; + inline wuffs_base__flicks duration() const; + inline uint64_t index() const; + inline uint64_t io_position() const; + inline wuffs_base__animation_disposal disposal() const; + inline bool opaque_within_bounds() const; + inline bool overwrite_instead_of_blend() const; + inline wuffs_base__color_u32_argb_premul background_color() const; +#endif // __cplusplus + +} wuffs_base__frame_config; + +static inline wuffs_base__frame_config // +wuffs_base__null_frame_config() { + wuffs_base__frame_config ret; + ret.private_impl.bounds = wuffs_base__make_rect_ie_u32(0, 0, 0, 0); + ret.private_impl.duration = 0; + ret.private_impl.index = 0; + ret.private_impl.io_position = 0; + ret.private_impl.disposal = 0; + ret.private_impl.opaque_within_bounds = false; + ret.private_impl.overwrite_instead_of_blend = false; + return ret; +} + +static inline void // +wuffs_base__frame_config__set( + wuffs_base__frame_config* c, + wuffs_base__rect_ie_u32 bounds, + wuffs_base__flicks duration, + uint64_t index, + uint64_t io_position, + wuffs_base__animation_disposal disposal, + bool opaque_within_bounds, + bool overwrite_instead_of_blend, + wuffs_base__color_u32_argb_premul background_color) { + if (!c) { + return; + } + + c->private_impl.bounds = bounds; + c->private_impl.duration = duration; + c->private_impl.index = index; + c->private_impl.io_position = io_position; + c->private_impl.disposal = disposal; + c->private_impl.opaque_within_bounds = opaque_within_bounds; + c->private_impl.overwrite_instead_of_blend = overwrite_instead_of_blend; + c->private_impl.background_color = background_color; +} + +static inline wuffs_base__rect_ie_u32 // +wuffs_base__frame_config__bounds(const wuffs_base__frame_config* c) { + if (c) { + return c->private_impl.bounds; + } + + wuffs_base__rect_ie_u32 ret; + ret.min_incl_x = 0; + ret.min_incl_y = 0; + ret.max_excl_x = 0; + ret.max_excl_y = 0; + return ret; +} + +static inline uint32_t // +wuffs_base__frame_config__width(const wuffs_base__frame_config* c) { + return c ? wuffs_base__rect_ie_u32__width(&c->private_impl.bounds) : 0; +} + +static inline uint32_t // +wuffs_base__frame_config__height(const wuffs_base__frame_config* c) { + return c ? wuffs_base__rect_ie_u32__height(&c->private_impl.bounds) : 0; +} + +// wuffs_base__frame_config__duration returns the amount of time to display +// this frame. Zero means to display forever - a still (non-animated) image. +static inline wuffs_base__flicks // +wuffs_base__frame_config__duration(const wuffs_base__frame_config* c) { + return c ? c->private_impl.duration : 0; +} + +// wuffs_base__frame_config__index returns the index of this frame. The first +// frame in an image has index 0, the second frame has index 1, and so on. +static inline uint64_t // +wuffs_base__frame_config__index(const wuffs_base__frame_config* c) { + return c ? c->private_impl.index : 0; +} + +// wuffs_base__frame_config__io_position returns the I/O stream position before +// the frame config. +static inline uint64_t // +wuffs_base__frame_config__io_position(const wuffs_base__frame_config* c) { + return c ? c->private_impl.io_position : 0; +} + +// wuffs_base__frame_config__disposal returns, for an animated image, how to +// dispose of this frame after displaying it. +static inline wuffs_base__animation_disposal // +wuffs_base__frame_config__disposal(const wuffs_base__frame_config* c) { + return c ? c->private_impl.disposal : 0; +} + +// wuffs_base__frame_config__opaque_within_bounds returns whether all pixels +// within the frame's bounds are fully opaque. It makes no claim about pixels +// outside the frame bounds but still inside the overall image. The two +// bounding rectangles can differ for animated images. +// +// Its semantics are conservative. It is valid for a fully opaque frame to have +// this value be false: a false negative. +// +// If true, drawing the frame with WUFFS_BASE__PIXEL_BLEND__SRC and +// WUFFS_BASE__PIXEL_BLEND__SRC_OVER should be equivalent, in terms of +// resultant pixels, but the former may be faster. +static inline bool // +wuffs_base__frame_config__opaque_within_bounds( + const wuffs_base__frame_config* c) { + return c && c->private_impl.opaque_within_bounds; +} + +// wuffs_base__frame_config__overwrite_instead_of_blend returns, for an +// animated image, whether to ignore the previous image state (within the frame +// bounds) when drawing this incremental frame. Equivalently, whether to use +// WUFFS_BASE__PIXEL_BLEND__SRC instead of WUFFS_BASE__PIXEL_BLEND__SRC_OVER. +// +// The WebP spec (https://developers.google.com/speed/webp/docs/riff_container) +// calls this the "Blending method" bit. WebP's "Do not blend" corresponds to +// Wuffs' "overwrite_instead_of_blend". +static inline bool // +wuffs_base__frame_config__overwrite_instead_of_blend( + const wuffs_base__frame_config* c) { + return c && c->private_impl.overwrite_instead_of_blend; +} + +static inline wuffs_base__color_u32_argb_premul // +wuffs_base__frame_config__background_color(const wuffs_base__frame_config* c) { + return c ? c->private_impl.background_color : 0; +} + +#ifdef __cplusplus + +inline void // +wuffs_base__frame_config::set( + wuffs_base__rect_ie_u32 bounds, + wuffs_base__flicks duration, + uint64_t index, + uint64_t io_position, + wuffs_base__animation_disposal disposal, + bool opaque_within_bounds, + bool overwrite_instead_of_blend, + wuffs_base__color_u32_argb_premul background_color) { + wuffs_base__frame_config__set(this, bounds, duration, index, io_position, + disposal, opaque_within_bounds, + overwrite_instead_of_blend, background_color); +} + +inline wuffs_base__rect_ie_u32 // +wuffs_base__frame_config::bounds() const { + return wuffs_base__frame_config__bounds(this); +} + +inline uint32_t // +wuffs_base__frame_config::width() const { + return wuffs_base__frame_config__width(this); +} + +inline uint32_t // +wuffs_base__frame_config::height() const { + return wuffs_base__frame_config__height(this); +} + +inline wuffs_base__flicks // +wuffs_base__frame_config::duration() const { + return wuffs_base__frame_config__duration(this); +} + +inline uint64_t // +wuffs_base__frame_config::index() const { + return wuffs_base__frame_config__index(this); +} + +inline uint64_t // +wuffs_base__frame_config::io_position() const { + return wuffs_base__frame_config__io_position(this); +} + +inline wuffs_base__animation_disposal // +wuffs_base__frame_config::disposal() const { + return wuffs_base__frame_config__disposal(this); +} + +inline bool // +wuffs_base__frame_config::opaque_within_bounds() const { + return wuffs_base__frame_config__opaque_within_bounds(this); +} + +inline bool // +wuffs_base__frame_config::overwrite_instead_of_blend() const { + return wuffs_base__frame_config__overwrite_instead_of_blend(this); +} + +inline wuffs_base__color_u32_argb_premul // +wuffs_base__frame_config::background_color() const { + return wuffs_base__frame_config__background_color(this); +} + +#endif // __cplusplus + +// -------- + +typedef struct wuffs_base__pixel_buffer__struct { + wuffs_base__pixel_config pixcfg; + + // Do not access the private_impl's fields directly. There is no API/ABI + // compatibility or safety guarantee if you do so. + struct { + wuffs_base__table_u8 planes[WUFFS_BASE__PIXEL_FORMAT__NUM_PLANES_MAX]; + // TODO: color spaces. + } private_impl; + +#ifdef __cplusplus + inline wuffs_base__status set_interleaved( + const wuffs_base__pixel_config* pixcfg, + wuffs_base__table_u8 primary_memory, + wuffs_base__slice_u8 palette_memory); + inline wuffs_base__status set_from_slice( + const wuffs_base__pixel_config* pixcfg, + wuffs_base__slice_u8 pixbuf_memory); + inline wuffs_base__status set_from_table( + const wuffs_base__pixel_config* pixcfg, + wuffs_base__table_u8 primary_memory); + inline wuffs_base__slice_u8 palette(); + inline wuffs_base__slice_u8 palette_or_else(wuffs_base__slice_u8 fallback); + inline wuffs_base__pixel_format pixel_format() const; + inline wuffs_base__table_u8 plane(uint32_t p); + inline wuffs_base__color_u32_argb_premul color_u32_at(uint32_t x, + uint32_t y) const; + inline wuffs_base__status set_color_u32_at( + uint32_t x, + uint32_t y, + wuffs_base__color_u32_argb_premul color); + inline wuffs_base__status set_color_u32_fill_rect( + wuffs_base__rect_ie_u32 rect, + wuffs_base__color_u32_argb_premul color); +#endif // __cplusplus + +} wuffs_base__pixel_buffer; + +static inline wuffs_base__pixel_buffer // +wuffs_base__null_pixel_buffer() { + wuffs_base__pixel_buffer ret; + ret.pixcfg = wuffs_base__null_pixel_config(); + ret.private_impl.planes[0] = wuffs_base__empty_table_u8(); + ret.private_impl.planes[1] = wuffs_base__empty_table_u8(); + ret.private_impl.planes[2] = wuffs_base__empty_table_u8(); + ret.private_impl.planes[3] = wuffs_base__empty_table_u8(); + return ret; +} + +static inline wuffs_base__status // +wuffs_base__pixel_buffer__set_interleaved( + wuffs_base__pixel_buffer* pb, + const wuffs_base__pixel_config* pixcfg, + wuffs_base__table_u8 primary_memory, + wuffs_base__slice_u8 palette_memory) { + if (!pb) { + return wuffs_base__make_status(wuffs_base__error__bad_receiver); + } + memset(pb, 0, sizeof(*pb)); + if (!pixcfg || + wuffs_base__pixel_format__is_planar(&pixcfg->private_impl.pixfmt)) { + return wuffs_base__make_status(wuffs_base__error__bad_argument); + } + if (wuffs_base__pixel_format__is_indexed(&pixcfg->private_impl.pixfmt) && + (palette_memory.len < + WUFFS_BASE__PIXEL_FORMAT__INDEXED__PALETTE_BYTE_LENGTH)) { + return wuffs_base__make_status( + wuffs_base__error__bad_argument_length_too_short); + } + uint32_t bits_per_pixel = + wuffs_base__pixel_format__bits_per_pixel(&pixcfg->private_impl.pixfmt); + if ((bits_per_pixel == 0) || ((bits_per_pixel % 8) != 0)) { + // TODO: support fraction-of-byte pixels, e.g. 1 bit per pixel? + return wuffs_base__make_status(wuffs_base__error__unsupported_option); + } + uint64_t bytes_per_pixel = bits_per_pixel / 8; + + uint64_t width_in_bytes = + ((uint64_t)pixcfg->private_impl.width) * bytes_per_pixel; + if ((width_in_bytes > primary_memory.width) || + (pixcfg->private_impl.height > primary_memory.height)) { + return wuffs_base__make_status(wuffs_base__error__bad_argument); + } + + pb->pixcfg = *pixcfg; + pb->private_impl.planes[0] = primary_memory; + if (wuffs_base__pixel_format__is_indexed(&pixcfg->private_impl.pixfmt)) { + wuffs_base__table_u8* tab = + &pb->private_impl + .planes[WUFFS_BASE__PIXEL_FORMAT__INDEXED__COLOR_PLANE]; + tab->ptr = palette_memory.ptr; + tab->width = WUFFS_BASE__PIXEL_FORMAT__INDEXED__PALETTE_BYTE_LENGTH; + tab->height = 1; + tab->stride = WUFFS_BASE__PIXEL_FORMAT__INDEXED__PALETTE_BYTE_LENGTH; + } + return wuffs_base__make_status(NULL); +} + +static inline wuffs_base__status // +wuffs_base__pixel_buffer__set_from_slice(wuffs_base__pixel_buffer* pb, + const wuffs_base__pixel_config* pixcfg, + wuffs_base__slice_u8 pixbuf_memory) { + if (!pb) { + return wuffs_base__make_status(wuffs_base__error__bad_receiver); + } + memset(pb, 0, sizeof(*pb)); + if (!pixcfg) { + return wuffs_base__make_status(wuffs_base__error__bad_argument); + } + if (wuffs_base__pixel_format__is_planar(&pixcfg->private_impl.pixfmt)) { + // TODO: support planar pixel formats, concious of pixel subsampling. + return wuffs_base__make_status(wuffs_base__error__unsupported_option); + } + uint32_t bits_per_pixel = + wuffs_base__pixel_format__bits_per_pixel(&pixcfg->private_impl.pixfmt); + if ((bits_per_pixel == 0) || ((bits_per_pixel % 8) != 0)) { + // TODO: support fraction-of-byte pixels, e.g. 1 bit per pixel? + return wuffs_base__make_status(wuffs_base__error__unsupported_option); + } + uint64_t bytes_per_pixel = bits_per_pixel / 8; + + uint8_t* ptr = pixbuf_memory.ptr; + uint64_t len = pixbuf_memory.len; + if (wuffs_base__pixel_format__is_indexed(&pixcfg->private_impl.pixfmt)) { + // Split a WUFFS_BASE__PIXEL_FORMAT__INDEXED__PALETTE_BYTE_LENGTH byte + // chunk (1024 bytes = 256 palette entries × 4 bytes per entry) from the + // start of pixbuf_memory. We split from the start, not the end, so that + // the both chunks' pointers have the same alignment as the original + // pointer, up to an alignment of 1024. + if (len < WUFFS_BASE__PIXEL_FORMAT__INDEXED__PALETTE_BYTE_LENGTH) { + return wuffs_base__make_status( + wuffs_base__error__bad_argument_length_too_short); + } + wuffs_base__table_u8* tab = + &pb->private_impl + .planes[WUFFS_BASE__PIXEL_FORMAT__INDEXED__COLOR_PLANE]; + tab->ptr = ptr; + tab->width = WUFFS_BASE__PIXEL_FORMAT__INDEXED__PALETTE_BYTE_LENGTH; + tab->height = 1; + tab->stride = WUFFS_BASE__PIXEL_FORMAT__INDEXED__PALETTE_BYTE_LENGTH; + ptr += WUFFS_BASE__PIXEL_FORMAT__INDEXED__PALETTE_BYTE_LENGTH; + len -= WUFFS_BASE__PIXEL_FORMAT__INDEXED__PALETTE_BYTE_LENGTH; + } + + uint64_t wh = ((uint64_t)pixcfg->private_impl.width) * + ((uint64_t)pixcfg->private_impl.height); + size_t width = (size_t)(pixcfg->private_impl.width); + if ((wh > (UINT64_MAX / bytes_per_pixel)) || + (width > (SIZE_MAX / bytes_per_pixel))) { + return wuffs_base__make_status(wuffs_base__error__bad_argument); + } + wh *= bytes_per_pixel; + width = ((size_t)(width * bytes_per_pixel)); + if (wh > len) { + return wuffs_base__make_status( + wuffs_base__error__bad_argument_length_too_short); + } + + pb->pixcfg = *pixcfg; + wuffs_base__table_u8* tab = &pb->private_impl.planes[0]; + tab->ptr = ptr; + tab->width = width; + tab->height = pixcfg->private_impl.height; + tab->stride = width; + return wuffs_base__make_status(NULL); +} + +// Deprecated: does not handle indexed pixel configurations. Use +// wuffs_base__pixel_buffer__set_interleaved instead. +static inline wuffs_base__status // +wuffs_base__pixel_buffer__set_from_table(wuffs_base__pixel_buffer* pb, + const wuffs_base__pixel_config* pixcfg, + wuffs_base__table_u8 primary_memory) { + if (!pb) { + return wuffs_base__make_status(wuffs_base__error__bad_receiver); + } + memset(pb, 0, sizeof(*pb)); + if (!pixcfg || + wuffs_base__pixel_format__is_indexed(&pixcfg->private_impl.pixfmt) || + wuffs_base__pixel_format__is_planar(&pixcfg->private_impl.pixfmt)) { + return wuffs_base__make_status(wuffs_base__error__bad_argument); + } + uint32_t bits_per_pixel = + wuffs_base__pixel_format__bits_per_pixel(&pixcfg->private_impl.pixfmt); + if ((bits_per_pixel == 0) || ((bits_per_pixel % 8) != 0)) { + // TODO: support fraction-of-byte pixels, e.g. 1 bit per pixel? + return wuffs_base__make_status(wuffs_base__error__unsupported_option); + } + uint64_t bytes_per_pixel = bits_per_pixel / 8; + + uint64_t width_in_bytes = + ((uint64_t)pixcfg->private_impl.width) * bytes_per_pixel; + if ((width_in_bytes > primary_memory.width) || + (pixcfg->private_impl.height > primary_memory.height)) { + return wuffs_base__make_status(wuffs_base__error__bad_argument); + } + + pb->pixcfg = *pixcfg; + pb->private_impl.planes[0] = primary_memory; + return wuffs_base__make_status(NULL); +} + +// wuffs_base__pixel_buffer__palette returns the palette color data. If +// non-empty, it will have length +// WUFFS_BASE__PIXEL_FORMAT__INDEXED__PALETTE_BYTE_LENGTH. +static inline wuffs_base__slice_u8 // +wuffs_base__pixel_buffer__palette(wuffs_base__pixel_buffer* pb) { + if (pb && + wuffs_base__pixel_format__is_indexed(&pb->pixcfg.private_impl.pixfmt)) { + wuffs_base__table_u8* tab = + &pb->private_impl + .planes[WUFFS_BASE__PIXEL_FORMAT__INDEXED__COLOR_PLANE]; + if ((tab->width == + WUFFS_BASE__PIXEL_FORMAT__INDEXED__PALETTE_BYTE_LENGTH) && + (tab->height == 1)) { + return wuffs_base__make_slice_u8( + tab->ptr, WUFFS_BASE__PIXEL_FORMAT__INDEXED__PALETTE_BYTE_LENGTH); + } + } + return wuffs_base__make_slice_u8(NULL, 0); +} + +static inline wuffs_base__slice_u8 // +wuffs_base__pixel_buffer__palette_or_else(wuffs_base__pixel_buffer* pb, + wuffs_base__slice_u8 fallback) { + if (pb && + wuffs_base__pixel_format__is_indexed(&pb->pixcfg.private_impl.pixfmt)) { + wuffs_base__table_u8* tab = + &pb->private_impl + .planes[WUFFS_BASE__PIXEL_FORMAT__INDEXED__COLOR_PLANE]; + if ((tab->width == + WUFFS_BASE__PIXEL_FORMAT__INDEXED__PALETTE_BYTE_LENGTH) && + (tab->height == 1)) { + return wuffs_base__make_slice_u8( + tab->ptr, WUFFS_BASE__PIXEL_FORMAT__INDEXED__PALETTE_BYTE_LENGTH); + } + } + return fallback; +} + +static inline wuffs_base__pixel_format // +wuffs_base__pixel_buffer__pixel_format(const wuffs_base__pixel_buffer* pb) { + if (pb) { + return pb->pixcfg.private_impl.pixfmt; + } + return wuffs_base__make_pixel_format(WUFFS_BASE__PIXEL_FORMAT__INVALID); +} + +static inline wuffs_base__table_u8 // +wuffs_base__pixel_buffer__plane(wuffs_base__pixel_buffer* pb, uint32_t p) { + if (pb && (p < WUFFS_BASE__PIXEL_FORMAT__NUM_PLANES_MAX)) { + return pb->private_impl.planes[p]; + } + + wuffs_base__table_u8 ret; + ret.ptr = NULL; + ret.width = 0; + ret.height = 0; + ret.stride = 0; + return ret; +} + +WUFFS_BASE__MAYBE_STATIC wuffs_base__color_u32_argb_premul // +wuffs_base__pixel_buffer__color_u32_at(const wuffs_base__pixel_buffer* pb, + uint32_t x, + uint32_t y); + +WUFFS_BASE__MAYBE_STATIC wuffs_base__status // +wuffs_base__pixel_buffer__set_color_u32_at( + wuffs_base__pixel_buffer* pb, + uint32_t x, + uint32_t y, + wuffs_base__color_u32_argb_premul color); + +WUFFS_BASE__MAYBE_STATIC wuffs_base__status // +wuffs_base__pixel_buffer__set_color_u32_fill_rect( + wuffs_base__pixel_buffer* pb, + wuffs_base__rect_ie_u32 rect, + wuffs_base__color_u32_argb_premul color); + +#ifdef __cplusplus + +inline wuffs_base__status // +wuffs_base__pixel_buffer::set_interleaved( + const wuffs_base__pixel_config* pixcfg_arg, + wuffs_base__table_u8 primary_memory, + wuffs_base__slice_u8 palette_memory) { + return wuffs_base__pixel_buffer__set_interleaved( + this, pixcfg_arg, primary_memory, palette_memory); +} + +inline wuffs_base__status // +wuffs_base__pixel_buffer::set_from_slice( + const wuffs_base__pixel_config* pixcfg_arg, + wuffs_base__slice_u8 pixbuf_memory) { + return wuffs_base__pixel_buffer__set_from_slice(this, pixcfg_arg, + pixbuf_memory); +} + +inline wuffs_base__status // +wuffs_base__pixel_buffer::set_from_table( + const wuffs_base__pixel_config* pixcfg_arg, + wuffs_base__table_u8 primary_memory) { + return wuffs_base__pixel_buffer__set_from_table(this, pixcfg_arg, + primary_memory); +} + +inline wuffs_base__slice_u8 // +wuffs_base__pixel_buffer::palette() { + return wuffs_base__pixel_buffer__palette(this); +} + +inline wuffs_base__slice_u8 // +wuffs_base__pixel_buffer::palette_or_else(wuffs_base__slice_u8 fallback) { + return wuffs_base__pixel_buffer__palette_or_else(this, fallback); +} + +inline wuffs_base__pixel_format // +wuffs_base__pixel_buffer::pixel_format() const { + return wuffs_base__pixel_buffer__pixel_format(this); +} + +inline wuffs_base__table_u8 // +wuffs_base__pixel_buffer::plane(uint32_t p) { + return wuffs_base__pixel_buffer__plane(this, p); +} + +inline wuffs_base__color_u32_argb_premul // +wuffs_base__pixel_buffer::color_u32_at(uint32_t x, uint32_t y) const { + return wuffs_base__pixel_buffer__color_u32_at(this, x, y); +} + +WUFFS_BASE__MAYBE_STATIC wuffs_base__status // +wuffs_base__pixel_buffer__set_color_u32_fill_rect( + wuffs_base__pixel_buffer* pb, + wuffs_base__rect_ie_u32 rect, + wuffs_base__color_u32_argb_premul color); + +inline wuffs_base__status // +wuffs_base__pixel_buffer::set_color_u32_at( + uint32_t x, + uint32_t y, + wuffs_base__color_u32_argb_premul color) { + return wuffs_base__pixel_buffer__set_color_u32_at(this, x, y, color); +} + +inline wuffs_base__status // +wuffs_base__pixel_buffer::set_color_u32_fill_rect( + wuffs_base__rect_ie_u32 rect, + wuffs_base__color_u32_argb_premul color) { + return wuffs_base__pixel_buffer__set_color_u32_fill_rect(this, rect, color); +} + +#endif // __cplusplus + +// -------- + +typedef struct wuffs_base__decode_frame_options__struct { + // Do not access the private_impl's fields directly. There is no API/ABI + // compatibility or safety guarantee if you do so. + struct { + uint8_t TODO; + } private_impl; + +#ifdef __cplusplus +#endif // __cplusplus + +} wuffs_base__decode_frame_options; + +#ifdef __cplusplus + +#endif // __cplusplus + +// -------- + +// wuffs_base__pixel_palette__closest_element returns the index of the palette +// element that minimizes the sum of squared differences of the four ARGB +// channels, working in premultiplied alpha. Ties favor the smaller index. +// +// The palette_slice.len may equal (N*4), for N less than 256, which means that +// only the first N palette elements are considered. It returns 0 when N is 0. +// +// Applying this function on a per-pixel basis will not produce whole-of-image +// dithering. +WUFFS_BASE__MAYBE_STATIC uint8_t // +wuffs_base__pixel_palette__closest_element( + wuffs_base__slice_u8 palette_slice, + wuffs_base__pixel_format palette_format, + wuffs_base__color_u32_argb_premul c); + +// -------- + +// TODO: should the func type take restrict pointers? +typedef uint64_t (*wuffs_base__pixel_swizzler__func)(uint8_t* dst_ptr, + size_t dst_len, + uint8_t* dst_palette_ptr, + size_t dst_palette_len, + const uint8_t* src_ptr, + size_t src_len); + +typedef uint64_t (*wuffs_base__pixel_swizzler__transparent_black_func)( + uint8_t* dst_ptr, + size_t dst_len, + uint8_t* dst_palette_ptr, + size_t dst_palette_len, + uint64_t num_pixels, + uint32_t dst_pixfmt_bytes_per_pixel); + +typedef struct wuffs_base__pixel_swizzler__struct { + // Do not access the private_impl's fields directly. There is no API/ABI + // compatibility or safety guarantee if you do so. + struct { + wuffs_base__pixel_swizzler__func func; + wuffs_base__pixel_swizzler__transparent_black_func transparent_black_func; + uint32_t dst_pixfmt_bytes_per_pixel; + uint32_t src_pixfmt_bytes_per_pixel; + } private_impl; + +#ifdef __cplusplus + inline wuffs_base__status prepare(wuffs_base__pixel_format dst_pixfmt, + wuffs_base__slice_u8 dst_palette, + wuffs_base__pixel_format src_pixfmt, + wuffs_base__slice_u8 src_palette, + wuffs_base__pixel_blend blend); + inline uint64_t swizzle_interleaved_from_slice( + wuffs_base__slice_u8 dst, + wuffs_base__slice_u8 dst_palette, + wuffs_base__slice_u8 src) const; +#endif // __cplusplus + +} wuffs_base__pixel_swizzler; + +// wuffs_base__pixel_swizzler__prepare readies the pixel swizzler so that its +// other methods may be called. +// +// For modular builds that divide the base module into sub-modules, using this +// function requires the WUFFS_CONFIG__MODULE__BASE__PIXCONV sub-module, not +// just WUFFS_CONFIG__MODULE__BASE__CORE. +WUFFS_BASE__MAYBE_STATIC wuffs_base__status // +wuffs_base__pixel_swizzler__prepare(wuffs_base__pixel_swizzler* p, + wuffs_base__pixel_format dst_pixfmt, + wuffs_base__slice_u8 dst_palette, + wuffs_base__pixel_format src_pixfmt, + wuffs_base__slice_u8 src_palette, + wuffs_base__pixel_blend blend); + +// wuffs_base__pixel_swizzler__swizzle_interleaved_from_slice converts pixels +// from a source format to a destination format. +// +// For modular builds that divide the base module into sub-modules, using this +// function requires the WUFFS_CONFIG__MODULE__BASE__PIXCONV sub-module, not +// just WUFFS_CONFIG__MODULE__BASE__CORE. +WUFFS_BASE__MAYBE_STATIC uint64_t // +wuffs_base__pixel_swizzler__swizzle_interleaved_from_slice( + const wuffs_base__pixel_swizzler* p, + wuffs_base__slice_u8 dst, + wuffs_base__slice_u8 dst_palette, + wuffs_base__slice_u8 src); + +#ifdef __cplusplus + +inline wuffs_base__status // +wuffs_base__pixel_swizzler::prepare(wuffs_base__pixel_format dst_pixfmt, + wuffs_base__slice_u8 dst_palette, + wuffs_base__pixel_format src_pixfmt, + wuffs_base__slice_u8 src_palette, + wuffs_base__pixel_blend blend) { + return wuffs_base__pixel_swizzler__prepare(this, dst_pixfmt, dst_palette, + src_pixfmt, src_palette, blend); +} + +uint64_t // +wuffs_base__pixel_swizzler::swizzle_interleaved_from_slice( + wuffs_base__slice_u8 dst, + wuffs_base__slice_u8 dst_palette, + wuffs_base__slice_u8 src) const { + return wuffs_base__pixel_swizzler__swizzle_interleaved_from_slice( + this, dst, dst_palette, src); +} + +#endif // __cplusplus + +// ---------------- String Conversions + +// Options (bitwise or'ed together) for wuffs_base__parse_number_xxx +// functions. The XXX options apply to both integer and floating point. The FXX +// options apply only to floating point. + +#define WUFFS_BASE__PARSE_NUMBER_XXX__DEFAULT_OPTIONS ((uint32_t)0x00000000) + +// WUFFS_BASE__PARSE_NUMBER_XXX__ALLOW_MULTIPLE_LEADING_ZEROES means to accept +// inputs like "00", "0644" and "00.7". By default, they are rejected. +#define WUFFS_BASE__PARSE_NUMBER_XXX__ALLOW_MULTIPLE_LEADING_ZEROES \ + ((uint32_t)0x00000001) + +// WUFFS_BASE__PARSE_NUMBER_XXX__ALLOW_UNDERSCORES means to accept inputs like +// "1__2" and "_3.141_592". By default, they are rejected. +#define WUFFS_BASE__PARSE_NUMBER_XXX__ALLOW_UNDERSCORES ((uint32_t)0x00000002) + +// WUFFS_BASE__PARSE_NUMBER_FXX__DECIMAL_SEPARATOR_IS_A_COMMA means to accept +// "1,5" and not "1.5" as one-and-a-half. +// +// If the caller wants to accept either, it is responsible for canonicalizing +// the input before calling wuffs_base__parse_number_fxx. The caller also has +// more context on e.g. exactly how to treat something like "$1,234". +#define WUFFS_BASE__PARSE_NUMBER_FXX__DECIMAL_SEPARATOR_IS_A_COMMA \ + ((uint32_t)0x00000010) + +// WUFFS_BASE__PARSE_NUMBER_FXX__REJECT_INF_AND_NAN means to reject inputs that +// would lead to infinite or Not-a-Number floating point values. By default, +// they are accepted. +// +// This affects the literal "inf" as input, but also affects inputs like +// "1e999" that would overflow double-precision floating point. +#define WUFFS_BASE__PARSE_NUMBER_FXX__REJECT_INF_AND_NAN ((uint32_t)0x00000020) + +// -------- + +// Options (bitwise or'ed together) for wuffs_base__render_number_xxx +// functions. The XXX options apply to both integer and floating point. The FXX +// options apply only to floating point. + +#define WUFFS_BASE__RENDER_NUMBER_XXX__DEFAULT_OPTIONS ((uint32_t)0x00000000) + +// WUFFS_BASE__RENDER_NUMBER_XXX__ALIGN_RIGHT means to render to the right side +// (higher indexes) of the destination slice, leaving any untouched bytes on +// the left side (lower indexes). The default is vice versa: rendering on the +// left with slack on the right. +#define WUFFS_BASE__RENDER_NUMBER_XXX__ALIGN_RIGHT ((uint32_t)0x00000100) + +// WUFFS_BASE__RENDER_NUMBER_XXX__LEADING_PLUS_SIGN means to render the leading +// "+" for non-negative numbers: "+0" and "+12.3" instead of "0" and "12.3". +#define WUFFS_BASE__RENDER_NUMBER_XXX__LEADING_PLUS_SIGN ((uint32_t)0x00000200) + +// WUFFS_BASE__RENDER_NUMBER_FXX__DECIMAL_SEPARATOR_IS_A_COMMA means to render +// one-and-a-half as "1,5" instead of "1.5". +#define WUFFS_BASE__RENDER_NUMBER_FXX__DECIMAL_SEPARATOR_IS_A_COMMA \ + ((uint32_t)0x00001000) + +// WUFFS_BASE__RENDER_NUMBER_FXX__EXPONENT_ETC means whether to never +// (EXPONENT_ABSENT, equivalent to printf's "%f") or to always +// (EXPONENT_PRESENT, equivalent to printf's "%e") render a floating point +// number as "1.23e+05" instead of "123000". +// +// Having both bits set is the same has having neither bit set, where the +// notation used depends on whether the exponent is sufficiently large: "0.5" +// is preferred over "5e-01" but "5e-09" is preferred over "0.000000005". +#define WUFFS_BASE__RENDER_NUMBER_FXX__EXPONENT_ABSENT ((uint32_t)0x00002000) +#define WUFFS_BASE__RENDER_NUMBER_FXX__EXPONENT_PRESENT ((uint32_t)0x00004000) + +// WUFFS_BASE__RENDER_NUMBER_FXX__JUST_ENOUGH_PRECISION means to render the +// smallest number of digits so that parsing the resultant string will recover +// the same double-precision floating point number. +// +// For example, double-precision cannot distinguish between 0.3 and +// 0.299999999999999988897769753748434595763683319091796875, so when this bit +// is set, rendering the latter will produce "0.3" but rendering +// 0.3000000000000000444089209850062616169452667236328125 will produce +// "0.30000000000000004". +#define WUFFS_BASE__RENDER_NUMBER_FXX__JUST_ENOUGH_PRECISION \ + ((uint32_t)0x00008000) + +// ---------------- IEEE 754 Floating Point + +// wuffs_base__ieee_754_bit_representation__etc converts between a double +// precision numerical value and its IEEE 754 representations: +// - 16-bit: 1 sign bit, 5 exponent bits, 10 explicit significand bits. +// - 32-bit: 1 sign bit, 8 exponent bits, 23 explicit significand bits. +// - 64-bit: 1 sign bit, 11 exponent bits, 52 explicit significand bits. +// +// For example, it converts between: +// - +1.0 and 0x3C00, 0x3F80_0000 or 0x3FF0_0000_0000_0000. +// - +5.5 and 0x4580, 0x40B0_0000 or 0x4016_0000_0000_0000. +// - -inf and 0xFC00, 0xFF80_0000 or 0xFFF0_0000_0000_0000. +// +// Converting from f64 to shorter formats (f16 or f32, represented in C as +// uint16_t and uint32_t) may be lossy. Such functions have names that look +// like etc_truncate, as converting finite numbers produce equal or smaller +// (closer-to-zero) finite numbers. For example, 1048576.0 is a perfectly valid +// f64 number, but converting it to a f16 (with truncation) produces 65504.0, +// the largest finite f16 number. Truncating a f64-typed value d to f32 does +// not always produce the same result as the C-style cast ((float)d), as +// casting can convert from finite numbers to infinite ones. +// +// Converting infinities or NaNs produces infinities or NaNs and always report +// no loss, even though there a multiple NaN representations so that round- +// tripping a f64-typed NaN may produce a different 64 bits. Nonetheless, the +// etc_truncate functions preserve a NaN's "quiet vs signaling" bit. +// +// See https://en.wikipedia.org/wiki/Double-precision_floating-point_format + +typedef struct wuffs_base__lossy_value_u16__struct { + uint16_t value; + bool lossy; +} wuffs_base__lossy_value_u16; + +typedef struct wuffs_base__lossy_value_u32__struct { + uint32_t value; + bool lossy; +} wuffs_base__lossy_value_u32; + +WUFFS_BASE__MAYBE_STATIC wuffs_base__lossy_value_u16 // +wuffs_base__ieee_754_bit_representation__from_f64_to_u16_truncate(double f); + +WUFFS_BASE__MAYBE_STATIC wuffs_base__lossy_value_u32 // +wuffs_base__ieee_754_bit_representation__from_f64_to_u32_truncate(double f); + +static inline uint64_t // +wuffs_base__ieee_754_bit_representation__from_f64_to_u64(double f) { + uint64_t u = 0; + if (sizeof(uint64_t) == sizeof(double)) { + memcpy(&u, &f, sizeof(uint64_t)); + } + return u; +} + +static inline double // +wuffs_base__ieee_754_bit_representation__from_u16_to_f64(uint16_t u) { + uint64_t v = ((uint64_t)(u & 0x8000)) << 48; + + do { + uint64_t exp = (u >> 10) & 0x1F; + uint64_t man = u & 0x3FF; + if (exp == 0x1F) { // Infinity or NaN. + exp = 2047; + } else if (exp != 0) { // Normal. + exp += 1008; // 1008 = 1023 - 15, the difference in biases. + } else if (man != 0) { // Subnormal but non-zero. + uint32_t clz = wuffs_base__count_leading_zeroes_u64(man); + exp = 1062 - clz; // 1062 = 1008 + 64 - 10. + man = 0x3FF & (man << (clz - 53)); + } else { // Zero. + break; + } + v |= (exp << 52) | (man << 42); + } while (0); + + double f = 0; + if (sizeof(uint64_t) == sizeof(double)) { + memcpy(&f, &v, sizeof(uint64_t)); + } + return f; +} + +static inline double // +wuffs_base__ieee_754_bit_representation__from_u32_to_f64(uint32_t u) { + float f = 0; + if (sizeof(uint32_t) == sizeof(float)) { + memcpy(&f, &u, sizeof(uint32_t)); + } + return (double)f; +} + +static inline double // +wuffs_base__ieee_754_bit_representation__from_u64_to_f64(uint64_t u) { + double f = 0; + if (sizeof(uint64_t) == sizeof(double)) { + memcpy(&f, &u, sizeof(uint64_t)); + } + return f; +} + +// ---------------- Parsing and Rendering Numbers + +// wuffs_base__parse_number_f64 parses the floating point number in s. For +// example, if s contains the bytes "1.5" then it will return the double 1.5. +// +// It returns an error if s does not contain a floating point number. +// +// It does not necessarily return an error if the conversion is lossy, e.g. if +// s is "0.3", which double-precision floating point cannot represent exactly. +// +// Similarly, the returned value may be infinite (and no error returned) even +// if s was not "inf", when the input is nominally finite but sufficiently +// larger than DBL_MAX, about 1.8e+308. +// +// It is similar to the C standard library's strtod function, but: +// - Errors are returned in-band (in a result type), not out-of-band (errno). +// - It takes a slice (a pointer and length), not a NUL-terminated C string. +// - It does not take an optional endptr argument. It does not allow a partial +// parse: it returns an error unless all of s is consumed. +// - It does not allow whitespace, leading or otherwise. +// - It does not allow hexadecimal floating point numbers. +// - It is not affected by i18n / l10n settings such as environment variables. +// +// The options argument can change these, but by default, it: +// - Allows "inf", "+Infinity" and "-NAN", case insensitive. Similarly, +// without an explicit opt-out, it would successfully parse "1e999" as +// infinity, even though it overflows double-precision floating point. +// - Rejects underscores. With an explicit opt-in, "_3.141_592" would +// successfully parse as an approximation to π. +// - Rejects unnecessary leading zeroes: "00", "0644" and "00.7". +// - Uses a dot '1.5' instead of a comma '1,5' for the decimal separator. +// +// For modular builds that divide the base module into sub-modules, using this +// function requires the WUFFS_CONFIG__MODULE__BASE__FLOATCONV sub-module, not +// just WUFFS_CONFIG__MODULE__BASE__CORE. +WUFFS_BASE__MAYBE_STATIC wuffs_base__result_f64 // +wuffs_base__parse_number_f64(wuffs_base__slice_u8 s, uint32_t options); + +// wuffs_base__parse_number_i64 parses the ASCII integer in s. For example, if +// s contains the bytes "-123" then it will return the int64_t -123. +// +// It returns an error if s does not contain an integer or if the integer +// within would overflow an int64_t. +// +// It is similar to wuffs_base__parse_number_u64 but it returns a signed +// integer, not an unsigned integer. It also allows a leading '+' or '-'. +// +// For modular builds that divide the base module into sub-modules, using this +// function requires the WUFFS_CONFIG__MODULE__BASE__INTCONV sub-module, not +// just WUFFS_CONFIG__MODULE__BASE__CORE. +WUFFS_BASE__MAYBE_STATIC wuffs_base__result_i64 // +wuffs_base__parse_number_i64(wuffs_base__slice_u8 s, uint32_t options); + +// wuffs_base__parse_number_u64 parses the ASCII integer in s. For example, if +// s contains the bytes "123" then it will return the uint64_t 123. +// +// It returns an error if s does not contain an integer or if the integer +// within would overflow a uint64_t. +// +// It is similar to the C standard library's strtoull function, but: +// - Errors are returned in-band (in a result type), not out-of-band (errno). +// - It takes a slice (a pointer and length), not a NUL-terminated C string. +// - It does not take an optional endptr argument. It does not allow a partial +// parse: it returns an error unless all of s is consumed. +// - It does not allow whitespace, leading or otherwise. +// - It does not allow a leading '+' or '-'. +// - It does not take a base argument (e.g. base 10 vs base 16). Instead, it +// always accepts both decimal (e.g "1234", "0d5678") and hexadecimal (e.g. +// "0x9aBC"). The caller is responsible for prior filtering of e.g. hex +// numbers if they are unwanted. For example, Wuffs' JSON decoder will only +// produce a wuffs_base__token for decimal numbers, not hexadecimal. +// - It is not affected by i18n / l10n settings such as environment variables. +// +// The options argument can change these, but by default, it: +// - Rejects underscores. With an explicit opt-in, "__0D_1_002" would +// successfully parse as "one thousand and two". Underscores are still +// rejected inside the optional 2-byte opening "0d" or "0X" that denotes +// base-10 or base-16. +// - Rejects unnecessary leading zeroes: "00" and "0644". +// +// For modular builds that divide the base module into sub-modules, using this +// function requires the WUFFS_CONFIG__MODULE__BASE__INTCONV sub-module, not +// just WUFFS_CONFIG__MODULE__BASE__CORE. +WUFFS_BASE__MAYBE_STATIC wuffs_base__result_u64 // +wuffs_base__parse_number_u64(wuffs_base__slice_u8 s, uint32_t options); + +// -------- + +// WUFFS_BASE__I64__BYTE_LENGTH__MAX_INCL is the string length of +// "-9223372036854775808" and "+9223372036854775807", INT64_MIN and INT64_MAX. +#define WUFFS_BASE__I64__BYTE_LENGTH__MAX_INCL 20 + +// WUFFS_BASE__U64__BYTE_LENGTH__MAX_INCL is the string length of +// "+18446744073709551615", UINT64_MAX. +#define WUFFS_BASE__U64__BYTE_LENGTH__MAX_INCL 21 + +// wuffs_base__render_number_f64 writes the decimal encoding of x to dst and +// returns the number of bytes written. If dst is shorter than the entire +// encoding, it returns 0 (and no bytes are written). +// +// For those familiar with C's printf or Go's fmt.Printf functions: +// - "%e" means the WUFFS_BASE__RENDER_NUMBER_FXX__EXPONENT_PRESENT option. +// - "%f" means the WUFFS_BASE__RENDER_NUMBER_FXX__EXPONENT_ABSENT option. +// - "%g" means neither or both bits are set. +// +// The precision argument controls the number of digits rendered, excluding the +// exponent (the "e+05" in "1.23e+05"): +// - for "%e" and "%f" it is the number of digits after the decimal separator, +// - for "%g" it is the number of significant digits (and trailing zeroes are +// removed). +// +// A precision of 6 gives similar output to printf's defaults. +// +// A precision greater than 4095 is equivalent to 4095. +// +// The precision argument is ignored when the +// WUFFS_BASE__RENDER_NUMBER_FXX__JUST_ENOUGH_PRECISION option is set. This is +// similar to Go's strconv.FormatFloat with a negative (i.e. non-sensical) +// precision, but there is no corresponding feature in C's printf. +// +// Extreme values of x will be rendered as "NaN", "Inf" (or "+Inf" if the +// WUFFS_BASE__RENDER_NUMBER_XXX__LEADING_PLUS_SIGN option is set) or "-Inf". +// +// For modular builds that divide the base module into sub-modules, using this +// function requires the WUFFS_CONFIG__MODULE__BASE__FLOATCONV sub-module, not +// just WUFFS_CONFIG__MODULE__BASE__CORE. +WUFFS_BASE__MAYBE_STATIC size_t // +wuffs_base__render_number_f64(wuffs_base__slice_u8 dst, + double x, + uint32_t precision, + uint32_t options); + +// wuffs_base__render_number_i64 writes the decimal encoding of x to dst and +// returns the number of bytes written. If dst is shorter than the entire +// encoding, it returns 0 (and no bytes are written). +// +// dst will never be too short if its length is at least 20, also known as +// WUFFS_BASE__I64__BYTE_LENGTH__MAX_INCL. +// +// For modular builds that divide the base module into sub-modules, using this +// function requires the WUFFS_CONFIG__MODULE__BASE__INTCONV sub-module, not +// just WUFFS_CONFIG__MODULE__BASE__CORE. +WUFFS_BASE__MAYBE_STATIC size_t // +wuffs_base__render_number_i64(wuffs_base__slice_u8 dst, + int64_t x, + uint32_t options); + +// wuffs_base__render_number_u64 writes the decimal encoding of x to dst and +// returns the number of bytes written. If dst is shorter than the entire +// encoding, it returns 0 (and no bytes are written). +// +// dst will never be too short if its length is at least 21, also known as +// WUFFS_BASE__U64__BYTE_LENGTH__MAX_INCL. +// +// For modular builds that divide the base module into sub-modules, using this +// function requires the WUFFS_CONFIG__MODULE__BASE__INTCONV sub-module, not +// just WUFFS_CONFIG__MODULE__BASE__CORE. +WUFFS_BASE__MAYBE_STATIC size_t // +wuffs_base__render_number_u64(wuffs_base__slice_u8 dst, + uint64_t x, + uint32_t options); + +// ---------------- Base-16 + +// Options (bitwise or'ed together) for wuffs_base__base_16__xxx functions. + +#define WUFFS_BASE__BASE_16__DEFAULT_OPTIONS ((uint32_t)0x00000000) + +// wuffs_base__base_16__decode2 converts "6A6b" to "jk", where e.g. 'j' is +// U+006A. There are 2 src bytes for every dst byte. +// +// It assumes that the src bytes are two hexadecimal digits (0-9, A-F, a-f), +// repeated. It may write nonsense bytes if not, although it will not read or +// write out of bounds. +// +// For modular builds that divide the base module into sub-modules, using this +// function requires the WUFFS_CONFIG__MODULE__BASE__INTCONV sub-module, not +// just WUFFS_CONFIG__MODULE__BASE__CORE. +WUFFS_BASE__MAYBE_STATIC wuffs_base__transform__output // +wuffs_base__base_16__decode2(wuffs_base__slice_u8 dst, + wuffs_base__slice_u8 src, + bool src_closed, + uint32_t options); + +// wuffs_base__base_16__decode4 converts both "\\x6A\\x6b" and "??6a??6B" to +// "jk", where e.g. 'j' is U+006A. There are 4 src bytes for every dst byte. +// +// It assumes that the src bytes are two ignored bytes and then two hexadecimal +// digits (0-9, A-F, a-f), repeated. It may write nonsense bytes if not, +// although it will not read or write out of bounds. +// +// For modular builds that divide the base module into sub-modules, using this +// function requires the WUFFS_CONFIG__MODULE__BASE__INTCONV sub-module, not +// just WUFFS_CONFIG__MODULE__BASE__CORE. +WUFFS_BASE__MAYBE_STATIC wuffs_base__transform__output // +wuffs_base__base_16__decode4(wuffs_base__slice_u8 dst, + wuffs_base__slice_u8 src, + bool src_closed, + uint32_t options); + +// wuffs_base__base_16__encode2 converts "jk" to "6A6B", where e.g. 'j' is +// U+006A. There are 2 dst bytes for every src byte. +// +// For modular builds that divide the base module into sub-modules, using this +// function requires the WUFFS_CONFIG__MODULE__BASE__INTCONV sub-module, not +// just WUFFS_CONFIG__MODULE__BASE__CORE. +WUFFS_BASE__MAYBE_STATIC wuffs_base__transform__output // +wuffs_base__base_16__encode2(wuffs_base__slice_u8 dst, + wuffs_base__slice_u8 src, + bool src_closed, + uint32_t options); + +// wuffs_base__base_16__encode4 converts "jk" to "\\x6A\\x6B", where e.g. 'j' +// is U+006A. There are 4 dst bytes for every src byte. +// +// For modular builds that divide the base module into sub-modules, using this +// function requires the WUFFS_CONFIG__MODULE__BASE__INTCONV sub-module, not +// just WUFFS_CONFIG__MODULE__BASE__CORE. +WUFFS_BASE__MAYBE_STATIC wuffs_base__transform__output // +wuffs_base__base_16__encode2(wuffs_base__slice_u8 dst, + wuffs_base__slice_u8 src, + bool src_closed, + uint32_t options); + +// ---------------- Base-64 + +// Options (bitwise or'ed together) for wuffs_base__base_64__xxx functions. + +#define WUFFS_BASE__BASE_64__DEFAULT_OPTIONS ((uint32_t)0x00000000) + +// WUFFS_BASE__BASE_64__DECODE_ALLOW_PADDING means that, when decoding base-64, +// the input may (but does not need to) be padded with '=' bytes so that the +// overall encoded length in bytes is a multiple of 4. A successful decoding +// will return a num_src that includes those padding bytes. +// +// Excess padding (e.g. three final '='s) will be rejected as bad data. +#define WUFFS_BASE__BASE_64__DECODE_ALLOW_PADDING ((uint32_t)0x00000001) + +// WUFFS_BASE__BASE_64__ENCODE_EMIT_PADDING means that, when encoding base-64, +// the output will be padded with '=' bytes so that the overall encoded length +// in bytes is a multiple of 4. +#define WUFFS_BASE__BASE_64__ENCODE_EMIT_PADDING ((uint32_t)0x00000002) + +// WUFFS_BASE__BASE_64__URL_ALPHABET means that, for base-64, the URL-friendly +// and file-name-friendly alphabet be used, as per RFC 4648 section 5. When +// this option bit is off, the standard alphabet from section 4 is used. +#define WUFFS_BASE__BASE_64__URL_ALPHABET ((uint32_t)0x00000100) + +// wuffs_base__base_64__decode transforms base-64 encoded bytes from src to +// arbitrary bytes in dst. +// +// It will not permit line breaks or other whitespace in src. Filtering those +// out is the responsibility of the caller. +// +// For modular builds that divide the base module into sub-modules, using this +// function requires the WUFFS_CONFIG__MODULE__BASE__INTCONV sub-module, not +// just WUFFS_CONFIG__MODULE__BASE__CORE. +WUFFS_BASE__MAYBE_STATIC wuffs_base__transform__output // +wuffs_base__base_64__decode(wuffs_base__slice_u8 dst, + wuffs_base__slice_u8 src, + bool src_closed, + uint32_t options); + +// wuffs_base__base_64__encode transforms arbitrary bytes from src to base-64 +// encoded bytes in dst. +// +// For modular builds that divide the base module into sub-modules, using this +// function requires the WUFFS_CONFIG__MODULE__BASE__INTCONV sub-module, not +// just WUFFS_CONFIG__MODULE__BASE__CORE. +WUFFS_BASE__MAYBE_STATIC wuffs_base__transform__output // +wuffs_base__base_64__encode(wuffs_base__slice_u8 dst, + wuffs_base__slice_u8 src, + bool src_closed, + uint32_t options); + +// ---------------- Unicode and UTF-8 + +#define WUFFS_BASE__UNICODE_CODE_POINT__MIN_INCL 0x00000000 +#define WUFFS_BASE__UNICODE_CODE_POINT__MAX_INCL 0x0010FFFF + +#define WUFFS_BASE__UNICODE_REPLACEMENT_CHARACTER 0x0000FFFD + +#define WUFFS_BASE__UNICODE_SURROGATE__MIN_INCL 0x0000D800 +#define WUFFS_BASE__UNICODE_SURROGATE__MAX_INCL 0x0000DFFF + +#define WUFFS_BASE__ASCII__MIN_INCL 0x00 +#define WUFFS_BASE__ASCII__MAX_INCL 0x7F + +#define WUFFS_BASE__UTF_8__BYTE_LENGTH__MIN_INCL 1 +#define WUFFS_BASE__UTF_8__BYTE_LENGTH__MAX_INCL 4 + +#define WUFFS_BASE__UTF_8__BYTE_LENGTH_1__CODE_POINT__MIN_INCL 0x00000000 +#define WUFFS_BASE__UTF_8__BYTE_LENGTH_1__CODE_POINT__MAX_INCL 0x0000007F +#define WUFFS_BASE__UTF_8__BYTE_LENGTH_2__CODE_POINT__MIN_INCL 0x00000080 +#define WUFFS_BASE__UTF_8__BYTE_LENGTH_2__CODE_POINT__MAX_INCL 0x000007FF +#define WUFFS_BASE__UTF_8__BYTE_LENGTH_3__CODE_POINT__MIN_INCL 0x00000800 +#define WUFFS_BASE__UTF_8__BYTE_LENGTH_3__CODE_POINT__MAX_INCL 0x0000FFFF +#define WUFFS_BASE__UTF_8__BYTE_LENGTH_4__CODE_POINT__MIN_INCL 0x00010000 +#define WUFFS_BASE__UTF_8__BYTE_LENGTH_4__CODE_POINT__MAX_INCL 0x0010FFFF + +// -------- + +// wuffs_base__utf_8__next__output is the type returned by +// wuffs_base__utf_8__next. +typedef struct wuffs_base__utf_8__next__output__struct { + uint32_t code_point; + uint32_t byte_length; + +#ifdef __cplusplus + inline bool is_valid() const; +#endif // __cplusplus + +} wuffs_base__utf_8__next__output; + +static inline wuffs_base__utf_8__next__output // +wuffs_base__make_utf_8__next__output(uint32_t code_point, + uint32_t byte_length) { + wuffs_base__utf_8__next__output ret; + ret.code_point = code_point; + ret.byte_length = byte_length; + return ret; +} + +static inline bool // +wuffs_base__utf_8__next__output__is_valid( + const wuffs_base__utf_8__next__output* o) { + if (o) { + uint32_t cp = o->code_point; + switch (o->byte_length) { + case 1: + return (cp <= 0x7F); + case 2: + return (0x080 <= cp) && (cp <= 0x7FF); + case 3: + // Avoid the 0xD800 ..= 0xDFFF surrogate range. + return ((0x0800 <= cp) && (cp <= 0xD7FF)) || + ((0xE000 <= cp) && (cp <= 0xFFFF)); + case 4: + return (0x00010000 <= cp) && (cp <= 0x0010FFFF); + } + } + return false; +} + +#ifdef __cplusplus + +inline bool // +wuffs_base__utf_8__next__output::is_valid() const { + return wuffs_base__utf_8__next__output__is_valid(this); +} + +#endif // __cplusplus + +// -------- + +// wuffs_base__utf_8__encode writes the UTF-8 encoding of code_point to s and +// returns the number of bytes written. If code_point is invalid, or if s is +// shorter than the entire encoding, it returns 0 (and no bytes are written). +// +// s will never be too short if its length is at least 4, also known as +// WUFFS_BASE__UTF_8__BYTE_LENGTH__MAX_INCL. +// +// For modular builds that divide the base module into sub-modules, using this +// function requires the WUFFS_CONFIG__MODULE__BASE__UTF8 sub-module, not just +// WUFFS_CONFIG__MODULE__BASE__CORE. +WUFFS_BASE__MAYBE_STATIC size_t // +wuffs_base__utf_8__encode(wuffs_base__slice_u8 dst, uint32_t code_point); + +// wuffs_base__utf_8__next returns the next UTF-8 code point (and that code +// point's byte length) at the start of the read-only slice (s_ptr, s_len). +// +// There are exactly two cases in which this function returns something where +// wuffs_base__utf_8__next__output__is_valid is false: +// - If s is empty then it returns {.code_point=0, .byte_length=0}. +// - If s is non-empty and starts with invalid UTF-8 then it returns +// {.code_point=WUFFS_BASE__UNICODE_REPLACEMENT_CHARACTER, .byte_length=1}. +// +// Otherwise, it returns something where +// wuffs_base__utf_8__next__output__is_valid is true. +// +// In any case, it always returns an output that satisfies both of: +// - (output.code_point <= WUFFS_BASE__UNICODE_CODE_POINT__MAX_INCL). +// - (output.byte_length <= s_len). +// +// If s is a sub-slice of a larger slice of valid UTF-8, but that sub-slice +// boundary occurs in the middle of a multi-byte UTF-8 encoding of a single +// code point, then this function may return something invalid. It is the +// caller's responsibility to split on or otherwise manage UTF-8 boundaries. +// +// For modular builds that divide the base module into sub-modules, using this +// function requires the WUFFS_CONFIG__MODULE__BASE__UTF8 sub-module, not just +// WUFFS_CONFIG__MODULE__BASE__CORE. +WUFFS_BASE__MAYBE_STATIC wuffs_base__utf_8__next__output // +wuffs_base__utf_8__next(const uint8_t* s_ptr, size_t s_len); + +// wuffs_base__utf_8__next_from_end is like wuffs_base__utf_8__next except that +// it looks at the end of (s_ptr, s_len) instead of the start. +// +// For modular builds that divide the base module into sub-modules, using this +// function requires the WUFFS_CONFIG__MODULE__BASE__UTF8 sub-module, not just +// WUFFS_CONFIG__MODULE__BASE__CORE. +WUFFS_BASE__MAYBE_STATIC wuffs_base__utf_8__next__output // +wuffs_base__utf_8__next_from_end(const uint8_t* s_ptr, size_t s_len); + +// wuffs_base__utf_8__longest_valid_prefix returns the largest n such that the +// sub-slice s[..n] is valid UTF-8, where s is the read-only slice (s_ptr, +// s_len). +// +// In particular, it returns s_len if and only if all of s is valid UTF-8. +// +// If s is a sub-slice of a larger slice of valid UTF-8, but that sub-slice +// boundary occurs in the middle of a multi-byte UTF-8 encoding of a single +// code point, then this function will return less than s_len. It is the +// caller's responsibility to split on or otherwise manage UTF-8 boundaries. +// +// For modular builds that divide the base module into sub-modules, using this +// function requires the WUFFS_CONFIG__MODULE__BASE__UTF8 sub-module, not just +// WUFFS_CONFIG__MODULE__BASE__CORE. +WUFFS_BASE__MAYBE_STATIC size_t // +wuffs_base__utf_8__longest_valid_prefix(const uint8_t* s_ptr, size_t s_len); + +// wuffs_base__ascii__longest_valid_prefix returns the largest n such that the +// sub-slice s[..n] is valid ASCII, where s is the read-only slice (s_ptr, +// s_len). +// +// In particular, it returns s_len if and only if all of s is valid ASCII. +// Equivalently, when none of the bytes in s have the 0x80 high bit set. +// +// For modular builds that divide the base module into sub-modules, using this +// function requires the WUFFS_CONFIG__MODULE__BASE__UTF8 sub-module, not just +// WUFFS_CONFIG__MODULE__BASE__CORE. +WUFFS_BASE__MAYBE_STATIC size_t // +wuffs_base__ascii__longest_valid_prefix(const uint8_t* s_ptr, size_t s_len); + +// ---------------- Interface Declarations. + +// For modular builds that divide the base module into sub-modules, using these +// functions require the WUFFS_CONFIG__MODULE__BASE__INTERFACES sub-module, not +// just WUFFS_CONFIG__MODULE__BASE__CORE. + +// -------- + +extern const char wuffs_base__hasher_u32__vtable_name[]; + +typedef struct wuffs_base__hasher_u32__func_ptrs__struct { + wuffs_base__empty_struct (*set_quirk_enabled)( + void* self, + uint32_t a_quirk, + bool a_enabled); + uint32_t (*update_u32)( + void* self, + wuffs_base__slice_u8 a_x); +} wuffs_base__hasher_u32__func_ptrs; + +typedef struct wuffs_base__hasher_u32__struct wuffs_base__hasher_u32; + +WUFFS_BASE__MAYBE_STATIC wuffs_base__empty_struct +wuffs_base__hasher_u32__set_quirk_enabled( + wuffs_base__hasher_u32* self, + uint32_t a_quirk, + bool a_enabled); + +WUFFS_BASE__MAYBE_STATIC uint32_t +wuffs_base__hasher_u32__update_u32( + wuffs_base__hasher_u32* self, + wuffs_base__slice_u8 a_x); + +#if defined(__cplusplus) || defined(WUFFS_IMPLEMENTATION) + +struct wuffs_base__hasher_u32__struct { + struct { + uint32_t magic; + uint32_t active_coroutine; + wuffs_base__vtable first_vtable; + } private_impl; + +#ifdef __cplusplus +#if defined(WUFFS_BASE__HAVE_UNIQUE_PTR) + using unique_ptr = std::unique_ptr; +#endif + + inline wuffs_base__empty_struct + set_quirk_enabled( + uint32_t a_quirk, + bool a_enabled) { + return wuffs_base__hasher_u32__set_quirk_enabled( + this, a_quirk, a_enabled); + } + + inline uint32_t + update_u32( + wuffs_base__slice_u8 a_x) { + return wuffs_base__hasher_u32__update_u32( + this, a_x); + } + +#endif // __cplusplus +}; // struct wuffs_base__hasher_u32__struct + +#endif // defined(__cplusplus) || defined(WUFFS_IMPLEMENTATION) + +// -------- + +extern const char wuffs_base__image_decoder__vtable_name[]; + +typedef struct wuffs_base__image_decoder__func_ptrs__struct { + wuffs_base__status (*decode_frame)( + void* self, + wuffs_base__pixel_buffer* a_dst, + wuffs_base__io_buffer* a_src, + wuffs_base__pixel_blend a_blend, + wuffs_base__slice_u8 a_workbuf, + wuffs_base__decode_frame_options* a_opts); + wuffs_base__status (*decode_frame_config)( + void* self, + wuffs_base__frame_config* a_dst, + wuffs_base__io_buffer* a_src); + wuffs_base__status (*decode_image_config)( + void* self, + wuffs_base__image_config* a_dst, + wuffs_base__io_buffer* a_src); + wuffs_base__rect_ie_u32 (*frame_dirty_rect)( + const void* self); + uint32_t (*num_animation_loops)( + const void* self); + uint64_t (*num_decoded_frame_configs)( + const void* self); + uint64_t (*num_decoded_frames)( + const void* self); + wuffs_base__status (*restart_frame)( + void* self, + uint64_t a_index, + uint64_t a_io_position); + wuffs_base__empty_struct (*set_quirk_enabled)( + void* self, + uint32_t a_quirk, + bool a_enabled); + wuffs_base__empty_struct (*set_report_metadata)( + void* self, + uint32_t a_fourcc, + bool a_report); + wuffs_base__status (*tell_me_more)( + void* self, + wuffs_base__io_buffer* a_dst, + wuffs_base__more_information* a_minfo, + wuffs_base__io_buffer* a_src); + wuffs_base__range_ii_u64 (*workbuf_len)( + const void* self); +} wuffs_base__image_decoder__func_ptrs; + +typedef struct wuffs_base__image_decoder__struct wuffs_base__image_decoder; + +WUFFS_BASE__MAYBE_STATIC wuffs_base__status +wuffs_base__image_decoder__decode_frame( + wuffs_base__image_decoder* self, + wuffs_base__pixel_buffer* a_dst, + wuffs_base__io_buffer* a_src, + wuffs_base__pixel_blend a_blend, + wuffs_base__slice_u8 a_workbuf, + wuffs_base__decode_frame_options* a_opts); + +WUFFS_BASE__MAYBE_STATIC wuffs_base__status +wuffs_base__image_decoder__decode_frame_config( + wuffs_base__image_decoder* self, + wuffs_base__frame_config* a_dst, + wuffs_base__io_buffer* a_src); + +WUFFS_BASE__MAYBE_STATIC wuffs_base__status +wuffs_base__image_decoder__decode_image_config( + wuffs_base__image_decoder* self, + wuffs_base__image_config* a_dst, + wuffs_base__io_buffer* a_src); + +WUFFS_BASE__MAYBE_STATIC wuffs_base__rect_ie_u32 +wuffs_base__image_decoder__frame_dirty_rect( + const wuffs_base__image_decoder* self); + +WUFFS_BASE__MAYBE_STATIC uint32_t +wuffs_base__image_decoder__num_animation_loops( + const wuffs_base__image_decoder* self); + +WUFFS_BASE__MAYBE_STATIC uint64_t +wuffs_base__image_decoder__num_decoded_frame_configs( + const wuffs_base__image_decoder* self); + +WUFFS_BASE__MAYBE_STATIC uint64_t +wuffs_base__image_decoder__num_decoded_frames( + const wuffs_base__image_decoder* self); + +WUFFS_BASE__MAYBE_STATIC wuffs_base__status +wuffs_base__image_decoder__restart_frame( + wuffs_base__image_decoder* self, + uint64_t a_index, + uint64_t a_io_position); + +WUFFS_BASE__MAYBE_STATIC wuffs_base__empty_struct +wuffs_base__image_decoder__set_quirk_enabled( + wuffs_base__image_decoder* self, + uint32_t a_quirk, + bool a_enabled); + +WUFFS_BASE__MAYBE_STATIC wuffs_base__empty_struct +wuffs_base__image_decoder__set_report_metadata( + wuffs_base__image_decoder* self, + uint32_t a_fourcc, + bool a_report); + +WUFFS_BASE__MAYBE_STATIC wuffs_base__status +wuffs_base__image_decoder__tell_me_more( + wuffs_base__image_decoder* self, + wuffs_base__io_buffer* a_dst, + wuffs_base__more_information* a_minfo, + wuffs_base__io_buffer* a_src); + +WUFFS_BASE__MAYBE_STATIC wuffs_base__range_ii_u64 +wuffs_base__image_decoder__workbuf_len( + const wuffs_base__image_decoder* self); + +#if defined(__cplusplus) || defined(WUFFS_IMPLEMENTATION) + +struct wuffs_base__image_decoder__struct { + struct { + uint32_t magic; + uint32_t active_coroutine; + wuffs_base__vtable first_vtable; + } private_impl; + +#ifdef __cplusplus +#if defined(WUFFS_BASE__HAVE_UNIQUE_PTR) + using unique_ptr = std::unique_ptr; +#endif + + inline wuffs_base__status + decode_frame( + wuffs_base__pixel_buffer* a_dst, + wuffs_base__io_buffer* a_src, + wuffs_base__pixel_blend a_blend, + wuffs_base__slice_u8 a_workbuf, + wuffs_base__decode_frame_options* a_opts) { + return wuffs_base__image_decoder__decode_frame( + this, a_dst, a_src, a_blend, a_workbuf, a_opts); + } + + inline wuffs_base__status + decode_frame_config( + wuffs_base__frame_config* a_dst, + wuffs_base__io_buffer* a_src) { + return wuffs_base__image_decoder__decode_frame_config( + this, a_dst, a_src); + } + + inline wuffs_base__status + decode_image_config( + wuffs_base__image_config* a_dst, + wuffs_base__io_buffer* a_src) { + return wuffs_base__image_decoder__decode_image_config( + this, a_dst, a_src); + } + + inline wuffs_base__rect_ie_u32 + frame_dirty_rect() const { + return wuffs_base__image_decoder__frame_dirty_rect(this); + } + + inline uint32_t + num_animation_loops() const { + return wuffs_base__image_decoder__num_animation_loops(this); + } + + inline uint64_t + num_decoded_frame_configs() const { + return wuffs_base__image_decoder__num_decoded_frame_configs(this); + } + + inline uint64_t + num_decoded_frames() const { + return wuffs_base__image_decoder__num_decoded_frames(this); + } + + inline wuffs_base__status + restart_frame( + uint64_t a_index, + uint64_t a_io_position) { + return wuffs_base__image_decoder__restart_frame( + this, a_index, a_io_position); + } + + inline wuffs_base__empty_struct + set_quirk_enabled( + uint32_t a_quirk, + bool a_enabled) { + return wuffs_base__image_decoder__set_quirk_enabled( + this, a_quirk, a_enabled); + } + + inline wuffs_base__empty_struct + set_report_metadata( + uint32_t a_fourcc, + bool a_report) { + return wuffs_base__image_decoder__set_report_metadata( + this, a_fourcc, a_report); + } + + inline wuffs_base__status + tell_me_more( + wuffs_base__io_buffer* a_dst, + wuffs_base__more_information* a_minfo, + wuffs_base__io_buffer* a_src) { + return wuffs_base__image_decoder__tell_me_more( + this, a_dst, a_minfo, a_src); + } + + inline wuffs_base__range_ii_u64 + workbuf_len() const { + return wuffs_base__image_decoder__workbuf_len(this); + } + +#endif // __cplusplus +}; // struct wuffs_base__image_decoder__struct + +#endif // defined(__cplusplus) || defined(WUFFS_IMPLEMENTATION) + +// -------- + +extern const char wuffs_base__io_transformer__vtable_name[]; + +typedef struct wuffs_base__io_transformer__func_ptrs__struct { + wuffs_base__empty_struct (*set_quirk_enabled)( + void* self, + uint32_t a_quirk, + bool a_enabled); + wuffs_base__status (*transform_io)( + void* self, + wuffs_base__io_buffer* a_dst, + wuffs_base__io_buffer* a_src, + wuffs_base__slice_u8 a_workbuf); + wuffs_base__range_ii_u64 (*workbuf_len)( + const void* self); +} wuffs_base__io_transformer__func_ptrs; + +typedef struct wuffs_base__io_transformer__struct wuffs_base__io_transformer; + +WUFFS_BASE__MAYBE_STATIC wuffs_base__empty_struct +wuffs_base__io_transformer__set_quirk_enabled( + wuffs_base__io_transformer* self, + uint32_t a_quirk, + bool a_enabled); + +WUFFS_BASE__MAYBE_STATIC wuffs_base__status +wuffs_base__io_transformer__transform_io( + wuffs_base__io_transformer* self, + wuffs_base__io_buffer* a_dst, + wuffs_base__io_buffer* a_src, + wuffs_base__slice_u8 a_workbuf); + +WUFFS_BASE__MAYBE_STATIC wuffs_base__range_ii_u64 +wuffs_base__io_transformer__workbuf_len( + const wuffs_base__io_transformer* self); + +#if defined(__cplusplus) || defined(WUFFS_IMPLEMENTATION) + +struct wuffs_base__io_transformer__struct { + struct { + uint32_t magic; + uint32_t active_coroutine; + wuffs_base__vtable first_vtable; + } private_impl; + +#ifdef __cplusplus +#if defined(WUFFS_BASE__HAVE_UNIQUE_PTR) + using unique_ptr = std::unique_ptr; +#endif + + inline wuffs_base__empty_struct + set_quirk_enabled( + uint32_t a_quirk, + bool a_enabled) { + return wuffs_base__io_transformer__set_quirk_enabled( + this, a_quirk, a_enabled); + } + + inline wuffs_base__status + transform_io( + wuffs_base__io_buffer* a_dst, + wuffs_base__io_buffer* a_src, + wuffs_base__slice_u8 a_workbuf) { + return wuffs_base__io_transformer__transform_io( + this, a_dst, a_src, a_workbuf); + } + + inline wuffs_base__range_ii_u64 + workbuf_len() const { + return wuffs_base__io_transformer__workbuf_len(this); + } + +#endif // __cplusplus +}; // struct wuffs_base__io_transformer__struct + +#endif // defined(__cplusplus) || defined(WUFFS_IMPLEMENTATION) + +// -------- + +extern const char wuffs_base__token_decoder__vtable_name[]; + +typedef struct wuffs_base__token_decoder__func_ptrs__struct { + wuffs_base__status (*decode_tokens)( + void* self, + wuffs_base__token_buffer* a_dst, + wuffs_base__io_buffer* a_src, + wuffs_base__slice_u8 a_workbuf); + wuffs_base__empty_struct (*set_quirk_enabled)( + void* self, + uint32_t a_quirk, + bool a_enabled); + wuffs_base__range_ii_u64 (*workbuf_len)( + const void* self); +} wuffs_base__token_decoder__func_ptrs; + +typedef struct wuffs_base__token_decoder__struct wuffs_base__token_decoder; + +WUFFS_BASE__MAYBE_STATIC wuffs_base__status +wuffs_base__token_decoder__decode_tokens( + wuffs_base__token_decoder* self, + wuffs_base__token_buffer* a_dst, + wuffs_base__io_buffer* a_src, + wuffs_base__slice_u8 a_workbuf); + +WUFFS_BASE__MAYBE_STATIC wuffs_base__empty_struct +wuffs_base__token_decoder__set_quirk_enabled( + wuffs_base__token_decoder* self, + uint32_t a_quirk, + bool a_enabled); + +WUFFS_BASE__MAYBE_STATIC wuffs_base__range_ii_u64 +wuffs_base__token_decoder__workbuf_len( + const wuffs_base__token_decoder* self); + +#if defined(__cplusplus) || defined(WUFFS_IMPLEMENTATION) + +struct wuffs_base__token_decoder__struct { + struct { + uint32_t magic; + uint32_t active_coroutine; + wuffs_base__vtable first_vtable; + } private_impl; + +#ifdef __cplusplus +#if defined(WUFFS_BASE__HAVE_UNIQUE_PTR) + using unique_ptr = std::unique_ptr; +#endif + + inline wuffs_base__status + decode_tokens( + wuffs_base__token_buffer* a_dst, + wuffs_base__io_buffer* a_src, + wuffs_base__slice_u8 a_workbuf) { + return wuffs_base__token_decoder__decode_tokens( + this, a_dst, a_src, a_workbuf); + } + + inline wuffs_base__empty_struct + set_quirk_enabled( + uint32_t a_quirk, + bool a_enabled) { + return wuffs_base__token_decoder__set_quirk_enabled( + this, a_quirk, a_enabled); + } + + inline wuffs_base__range_ii_u64 + workbuf_len() const { + return wuffs_base__token_decoder__workbuf_len(this); + } + +#endif // __cplusplus +}; // struct wuffs_base__token_decoder__struct + +#endif // defined(__cplusplus) || defined(WUFFS_IMPLEMENTATION) + +// ---------------- + +#ifdef __cplusplus +} // extern "C" +#endif + +#if !defined(WUFFS_CONFIG__MODULES) || defined(WUFFS_CONFIG__MODULE__ADLER32) || defined(WUFFS_NONMONOLITHIC) + +// ---------------- Status Codes + +// ---------------- Public Consts + +// ---------------- Struct Declarations + +typedef struct wuffs_adler32__hasher__struct wuffs_adler32__hasher; + +#ifdef __cplusplus +extern "C" { +#endif + +// ---------------- Public Initializer Prototypes + +// For any given "wuffs_foo__bar* self", "wuffs_foo__bar__initialize(self, +// etc)" should be called before any other "wuffs_foo__bar__xxx(self, etc)". +// +// Pass sizeof(*self) and WUFFS_VERSION for sizeof_star_self and wuffs_version. +// Pass 0 (or some combination of WUFFS_INITIALIZE__XXX) for options. + +wuffs_base__status WUFFS_BASE__WARN_UNUSED_RESULT +wuffs_adler32__hasher__initialize( + wuffs_adler32__hasher* self, + size_t sizeof_star_self, + uint64_t wuffs_version, + uint32_t options); + +size_t +sizeof__wuffs_adler32__hasher(); + +// ---------------- Allocs + +// These functions allocate and initialize Wuffs structs. They return NULL if +// memory allocation fails. If they return non-NULL, there is no need to call +// wuffs_foo__bar__initialize, but the caller is responsible for eventually +// calling free on the returned pointer. That pointer is effectively a C++ +// std::unique_ptr. + +wuffs_adler32__hasher* +wuffs_adler32__hasher__alloc(); + +static inline wuffs_base__hasher_u32* +wuffs_adler32__hasher__alloc_as__wuffs_base__hasher_u32() { + return (wuffs_base__hasher_u32*)(wuffs_adler32__hasher__alloc()); +} + +// ---------------- Upcasts + +static inline wuffs_base__hasher_u32* +wuffs_adler32__hasher__upcast_as__wuffs_base__hasher_u32( + wuffs_adler32__hasher* p) { + return (wuffs_base__hasher_u32*)p; +} + +// ---------------- Public Function Prototypes + +WUFFS_BASE__MAYBE_STATIC wuffs_base__empty_struct +wuffs_adler32__hasher__set_quirk_enabled( + wuffs_adler32__hasher* self, + uint32_t a_quirk, + bool a_enabled); + +WUFFS_BASE__MAYBE_STATIC uint32_t +wuffs_adler32__hasher__update_u32( + wuffs_adler32__hasher* self, + wuffs_base__slice_u8 a_x); + +#ifdef __cplusplus +} // extern "C" +#endif + +// ---------------- Struct Definitions + +// These structs' fields, and the sizeof them, are private implementation +// details that aren't guaranteed to be stable across Wuffs versions. +// +// See https://en.wikipedia.org/wiki/Opaque_pointer#C + +#if defined(__cplusplus) || defined(WUFFS_IMPLEMENTATION) + +struct wuffs_adler32__hasher__struct { + // Do not access the private_impl's or private_data's fields directly. There + // is no API/ABI compatibility or safety guarantee if you do so. Instead, use + // the wuffs_foo__bar__baz functions. + // + // It is a struct, not a struct*, so that the outermost wuffs_foo__bar struct + // can be stack allocated when WUFFS_IMPLEMENTATION is defined. + + struct { + uint32_t magic; + uint32_t active_coroutine; + wuffs_base__vtable vtable_for__wuffs_base__hasher_u32; + wuffs_base__vtable null_vtable; + + uint32_t f_state; + bool f_started; + + wuffs_base__empty_struct (*choosy_up)( + wuffs_adler32__hasher* self, + wuffs_base__slice_u8 a_x); + } private_impl; + +#ifdef __cplusplus +#if defined(WUFFS_BASE__HAVE_UNIQUE_PTR) + using unique_ptr = std::unique_ptr; + + // On failure, the alloc_etc functions return nullptr. They don't throw. + + static inline unique_ptr + alloc() { + return unique_ptr(wuffs_adler32__hasher__alloc(), &free); + } + + static inline wuffs_base__hasher_u32::unique_ptr + alloc_as__wuffs_base__hasher_u32() { + return wuffs_base__hasher_u32::unique_ptr( + wuffs_adler32__hasher__alloc_as__wuffs_base__hasher_u32(), &free); + } +#endif // defined(WUFFS_BASE__HAVE_UNIQUE_PTR) + +#if defined(WUFFS_BASE__HAVE_EQ_DELETE) && !defined(WUFFS_IMPLEMENTATION) + // Disallow constructing or copying an object via standard C++ mechanisms, + // e.g. the "new" operator, as this struct is intentionally opaque. Its total + // size and field layout is not part of the public, stable, memory-safe API. + // Use malloc or memcpy and the sizeof__wuffs_foo__bar function instead, and + // call wuffs_foo__bar__baz methods (which all take a "this"-like pointer as + // their first argument) rather than tweaking bar.private_impl.qux fields. + // + // In C, we can just leave wuffs_foo__bar as an incomplete type (unless + // WUFFS_IMPLEMENTATION is #define'd). In C++, we define a complete type in + // order to provide convenience methods. These forward on "this", so that you + // can write "bar->baz(etc)" instead of "wuffs_foo__bar__baz(bar, etc)". + wuffs_adler32__hasher__struct() = delete; + wuffs_adler32__hasher__struct(const wuffs_adler32__hasher__struct&) = delete; + wuffs_adler32__hasher__struct& operator=( + const wuffs_adler32__hasher__struct&) = delete; +#endif // defined(WUFFS_BASE__HAVE_EQ_DELETE) && !defined(WUFFS_IMPLEMENTATION) + +#if !defined(WUFFS_IMPLEMENTATION) + // As above, the size of the struct is not part of the public API, and unless + // WUFFS_IMPLEMENTATION is #define'd, this struct type T should be heap + // allocated, not stack allocated. Its size is not intended to be known at + // compile time, but it is unfortunately divulged as a side effect of + // defining C++ convenience methods. Use "sizeof__T()", calling the function, + // instead of "sizeof T", invoking the operator. To make the two values + // different, so that passing the latter will be rejected by the initialize + // function, we add an arbitrary amount of dead weight. + uint8_t dead_weight[123000000]; // 123 MB. +#endif // !defined(WUFFS_IMPLEMENTATION) + + inline wuffs_base__status WUFFS_BASE__WARN_UNUSED_RESULT + initialize( + size_t sizeof_star_self, + uint64_t wuffs_version, + uint32_t options) { + return wuffs_adler32__hasher__initialize( + this, sizeof_star_self, wuffs_version, options); + } + + inline wuffs_base__hasher_u32* + upcast_as__wuffs_base__hasher_u32() { + return (wuffs_base__hasher_u32*)this; + } + + inline wuffs_base__empty_struct + set_quirk_enabled( + uint32_t a_quirk, + bool a_enabled) { + return wuffs_adler32__hasher__set_quirk_enabled(this, a_quirk, a_enabled); + } + + inline uint32_t + update_u32( + wuffs_base__slice_u8 a_x) { + return wuffs_adler32__hasher__update_u32(this, a_x); + } + +#endif // __cplusplus +}; // struct wuffs_adler32__hasher__struct + +#endif // defined(__cplusplus) || defined(WUFFS_IMPLEMENTATION) + +#endif // !defined(WUFFS_CONFIG__MODULES) || defined(WUFFS_CONFIG__MODULE__ADLER32) || defined(WUFFS_NONMONOLITHIC) + +#if !defined(WUFFS_CONFIG__MODULES) || defined(WUFFS_CONFIG__MODULE__BMP) || defined(WUFFS_NONMONOLITHIC) + +// ---------------- Status Codes + +extern const char wuffs_bmp__error__bad_header[]; +extern const char wuffs_bmp__error__bad_rle_compression[]; +extern const char wuffs_bmp__error__truncated_input[]; +extern const char wuffs_bmp__error__unsupported_bmp_file[]; + +// ---------------- Public Consts + +#define WUFFS_BMP__DECODER_WORKBUF_LEN_MAX_INCL_WORST_CASE 0 + +// ---------------- Struct Declarations + +typedef struct wuffs_bmp__decoder__struct wuffs_bmp__decoder; + +#ifdef __cplusplus +extern "C" { +#endif + +// ---------------- Public Initializer Prototypes + +// For any given "wuffs_foo__bar* self", "wuffs_foo__bar__initialize(self, +// etc)" should be called before any other "wuffs_foo__bar__xxx(self, etc)". +// +// Pass sizeof(*self) and WUFFS_VERSION for sizeof_star_self and wuffs_version. +// Pass 0 (or some combination of WUFFS_INITIALIZE__XXX) for options. + +wuffs_base__status WUFFS_BASE__WARN_UNUSED_RESULT +wuffs_bmp__decoder__initialize( + wuffs_bmp__decoder* self, + size_t sizeof_star_self, + uint64_t wuffs_version, + uint32_t options); + +size_t +sizeof__wuffs_bmp__decoder(); + +// ---------------- Allocs + +// These functions allocate and initialize Wuffs structs. They return NULL if +// memory allocation fails. If they return non-NULL, there is no need to call +// wuffs_foo__bar__initialize, but the caller is responsible for eventually +// calling free on the returned pointer. That pointer is effectively a C++ +// std::unique_ptr. + +wuffs_bmp__decoder* +wuffs_bmp__decoder__alloc(); + +static inline wuffs_base__image_decoder* +wuffs_bmp__decoder__alloc_as__wuffs_base__image_decoder() { + return (wuffs_base__image_decoder*)(wuffs_bmp__decoder__alloc()); +} + +// ---------------- Upcasts + +static inline wuffs_base__image_decoder* +wuffs_bmp__decoder__upcast_as__wuffs_base__image_decoder( + wuffs_bmp__decoder* p) { + return (wuffs_base__image_decoder*)p; +} + +// ---------------- Public Function Prototypes + +WUFFS_BASE__MAYBE_STATIC wuffs_base__empty_struct +wuffs_bmp__decoder__set_quirk_enabled( + wuffs_bmp__decoder* self, + uint32_t a_quirk, + bool a_enabled); + +WUFFS_BASE__MAYBE_STATIC wuffs_base__status +wuffs_bmp__decoder__decode_image_config( + wuffs_bmp__decoder* self, + wuffs_base__image_config* a_dst, + wuffs_base__io_buffer* a_src); + +WUFFS_BASE__MAYBE_STATIC wuffs_base__status +wuffs_bmp__decoder__decode_frame_config( + wuffs_bmp__decoder* self, + wuffs_base__frame_config* a_dst, + wuffs_base__io_buffer* a_src); + +WUFFS_BASE__MAYBE_STATIC wuffs_base__status +wuffs_bmp__decoder__decode_frame( + wuffs_bmp__decoder* self, + wuffs_base__pixel_buffer* a_dst, + wuffs_base__io_buffer* a_src, + wuffs_base__pixel_blend a_blend, + wuffs_base__slice_u8 a_workbuf, + wuffs_base__decode_frame_options* a_opts); + +WUFFS_BASE__MAYBE_STATIC wuffs_base__rect_ie_u32 +wuffs_bmp__decoder__frame_dirty_rect( + const wuffs_bmp__decoder* self); + +WUFFS_BASE__MAYBE_STATIC uint32_t +wuffs_bmp__decoder__num_animation_loops( + const wuffs_bmp__decoder* self); + +WUFFS_BASE__MAYBE_STATIC uint64_t +wuffs_bmp__decoder__num_decoded_frame_configs( + const wuffs_bmp__decoder* self); + +WUFFS_BASE__MAYBE_STATIC uint64_t +wuffs_bmp__decoder__num_decoded_frames( + const wuffs_bmp__decoder* self); + +WUFFS_BASE__MAYBE_STATIC wuffs_base__status +wuffs_bmp__decoder__restart_frame( + wuffs_bmp__decoder* self, + uint64_t a_index, + uint64_t a_io_position); + +WUFFS_BASE__MAYBE_STATIC wuffs_base__empty_struct +wuffs_bmp__decoder__set_report_metadata( + wuffs_bmp__decoder* self, + uint32_t a_fourcc, + bool a_report); + +WUFFS_BASE__MAYBE_STATIC wuffs_base__status +wuffs_bmp__decoder__tell_me_more( + wuffs_bmp__decoder* self, + wuffs_base__io_buffer* a_dst, + wuffs_base__more_information* a_minfo, + wuffs_base__io_buffer* a_src); + +WUFFS_BASE__MAYBE_STATIC wuffs_base__range_ii_u64 +wuffs_bmp__decoder__workbuf_len( + const wuffs_bmp__decoder* self); + +#ifdef __cplusplus +} // extern "C" +#endif + +// ---------------- Struct Definitions + +// These structs' fields, and the sizeof them, are private implementation +// details that aren't guaranteed to be stable across Wuffs versions. +// +// See https://en.wikipedia.org/wiki/Opaque_pointer#C + +#if defined(__cplusplus) || defined(WUFFS_IMPLEMENTATION) + +struct wuffs_bmp__decoder__struct { + // Do not access the private_impl's or private_data's fields directly. There + // is no API/ABI compatibility or safety guarantee if you do so. Instead, use + // the wuffs_foo__bar__baz functions. + // + // It is a struct, not a struct*, so that the outermost wuffs_foo__bar struct + // can be stack allocated when WUFFS_IMPLEMENTATION is defined. + + struct { + uint32_t magic; + uint32_t active_coroutine; + wuffs_base__vtable vtable_for__wuffs_base__image_decoder; + wuffs_base__vtable null_vtable; + + uint32_t f_width; + uint32_t f_height; + uint8_t f_call_sequence; + bool f_top_down; + uint32_t f_pad_per_row; + uint32_t f_src_pixfmt; + uint32_t f_io_redirect_fourcc; + uint64_t f_io_redirect_pos; + uint64_t f_frame_config_io_position; + uint32_t f_bitmap_info_len; + uint32_t f_padding; + uint32_t f_bits_per_pixel; + uint32_t f_compression; + uint32_t f_channel_masks[4]; + uint8_t f_channel_shifts[4]; + uint8_t f_channel_num_bits[4]; + uint32_t f_dst_x; + uint32_t f_dst_y; + uint32_t f_dst_y_inc; + uint32_t f_pending_pad; + uint32_t f_rle_state; + uint32_t f_rle_length; + uint8_t f_rle_delta_x; + bool f_rle_padded; + wuffs_base__pixel_swizzler f_swizzler; + + uint32_t p_decode_image_config[1]; + uint32_t p_do_decode_image_config[1]; + uint32_t p_decode_frame_config[1]; + uint32_t p_do_decode_frame_config[1]; + uint32_t p_decode_frame[1]; + uint32_t p_do_decode_frame[1]; + uint32_t p_tell_me_more[1]; + uint32_t p_read_palette[1]; + } private_impl; + + struct { + uint8_t f_scratch[2048]; + uint8_t f_src_palette[1024]; + + struct { + uint64_t scratch; + } s_do_decode_image_config[1]; + struct { + uint64_t scratch; + } s_do_decode_frame[1]; + struct { + uint32_t v_i; + uint64_t scratch; + } s_read_palette[1]; + } private_data; + +#ifdef __cplusplus +#if defined(WUFFS_BASE__HAVE_UNIQUE_PTR) + using unique_ptr = std::unique_ptr; + + // On failure, the alloc_etc functions return nullptr. They don't throw. + + static inline unique_ptr + alloc() { + return unique_ptr(wuffs_bmp__decoder__alloc(), &free); + } + + static inline wuffs_base__image_decoder::unique_ptr + alloc_as__wuffs_base__image_decoder() { + return wuffs_base__image_decoder::unique_ptr( + wuffs_bmp__decoder__alloc_as__wuffs_base__image_decoder(), &free); + } +#endif // defined(WUFFS_BASE__HAVE_UNIQUE_PTR) + +#if defined(WUFFS_BASE__HAVE_EQ_DELETE) && !defined(WUFFS_IMPLEMENTATION) + // Disallow constructing or copying an object via standard C++ mechanisms, + // e.g. the "new" operator, as this struct is intentionally opaque. Its total + // size and field layout is not part of the public, stable, memory-safe API. + // Use malloc or memcpy and the sizeof__wuffs_foo__bar function instead, and + // call wuffs_foo__bar__baz methods (which all take a "this"-like pointer as + // their first argument) rather than tweaking bar.private_impl.qux fields. + // + // In C, we can just leave wuffs_foo__bar as an incomplete type (unless + // WUFFS_IMPLEMENTATION is #define'd). In C++, we define a complete type in + // order to provide convenience methods. These forward on "this", so that you + // can write "bar->baz(etc)" instead of "wuffs_foo__bar__baz(bar, etc)". + wuffs_bmp__decoder__struct() = delete; + wuffs_bmp__decoder__struct(const wuffs_bmp__decoder__struct&) = delete; + wuffs_bmp__decoder__struct& operator=( + const wuffs_bmp__decoder__struct&) = delete; +#endif // defined(WUFFS_BASE__HAVE_EQ_DELETE) && !defined(WUFFS_IMPLEMENTATION) + +#if !defined(WUFFS_IMPLEMENTATION) + // As above, the size of the struct is not part of the public API, and unless + // WUFFS_IMPLEMENTATION is #define'd, this struct type T should be heap + // allocated, not stack allocated. Its size is not intended to be known at + // compile time, but it is unfortunately divulged as a side effect of + // defining C++ convenience methods. Use "sizeof__T()", calling the function, + // instead of "sizeof T", invoking the operator. To make the two values + // different, so that passing the latter will be rejected by the initialize + // function, we add an arbitrary amount of dead weight. + uint8_t dead_weight[123000000]; // 123 MB. +#endif // !defined(WUFFS_IMPLEMENTATION) + + inline wuffs_base__status WUFFS_BASE__WARN_UNUSED_RESULT + initialize( + size_t sizeof_star_self, + uint64_t wuffs_version, + uint32_t options) { + return wuffs_bmp__decoder__initialize( + this, sizeof_star_self, wuffs_version, options); + } + + inline wuffs_base__image_decoder* + upcast_as__wuffs_base__image_decoder() { + return (wuffs_base__image_decoder*)this; + } + + inline wuffs_base__empty_struct + set_quirk_enabled( + uint32_t a_quirk, + bool a_enabled) { + return wuffs_bmp__decoder__set_quirk_enabled(this, a_quirk, a_enabled); + } + + inline wuffs_base__status + decode_image_config( + wuffs_base__image_config* a_dst, + wuffs_base__io_buffer* a_src) { + return wuffs_bmp__decoder__decode_image_config(this, a_dst, a_src); + } + + inline wuffs_base__status + decode_frame_config( + wuffs_base__frame_config* a_dst, + wuffs_base__io_buffer* a_src) { + return wuffs_bmp__decoder__decode_frame_config(this, a_dst, a_src); + } + + inline wuffs_base__status + decode_frame( + wuffs_base__pixel_buffer* a_dst, + wuffs_base__io_buffer* a_src, + wuffs_base__pixel_blend a_blend, + wuffs_base__slice_u8 a_workbuf, + wuffs_base__decode_frame_options* a_opts) { + return wuffs_bmp__decoder__decode_frame(this, a_dst, a_src, a_blend, a_workbuf, a_opts); + } + + inline wuffs_base__rect_ie_u32 + frame_dirty_rect() const { + return wuffs_bmp__decoder__frame_dirty_rect(this); + } + + inline uint32_t + num_animation_loops() const { + return wuffs_bmp__decoder__num_animation_loops(this); + } + + inline uint64_t + num_decoded_frame_configs() const { + return wuffs_bmp__decoder__num_decoded_frame_configs(this); + } + + inline uint64_t + num_decoded_frames() const { + return wuffs_bmp__decoder__num_decoded_frames(this); + } + + inline wuffs_base__status + restart_frame( + uint64_t a_index, + uint64_t a_io_position) { + return wuffs_bmp__decoder__restart_frame(this, a_index, a_io_position); + } + + inline wuffs_base__empty_struct + set_report_metadata( + uint32_t a_fourcc, + bool a_report) { + return wuffs_bmp__decoder__set_report_metadata(this, a_fourcc, a_report); + } + + inline wuffs_base__status + tell_me_more( + wuffs_base__io_buffer* a_dst, + wuffs_base__more_information* a_minfo, + wuffs_base__io_buffer* a_src) { + return wuffs_bmp__decoder__tell_me_more(this, a_dst, a_minfo, a_src); + } + + inline wuffs_base__range_ii_u64 + workbuf_len() const { + return wuffs_bmp__decoder__workbuf_len(this); + } + +#endif // __cplusplus +}; // struct wuffs_bmp__decoder__struct + +#endif // defined(__cplusplus) || defined(WUFFS_IMPLEMENTATION) + +#endif // !defined(WUFFS_CONFIG__MODULES) || defined(WUFFS_CONFIG__MODULE__BMP) || defined(WUFFS_NONMONOLITHIC) + +#if !defined(WUFFS_CONFIG__MODULES) || defined(WUFFS_CONFIG__MODULE__BZIP2) || defined(WUFFS_NONMONOLITHIC) + +// ---------------- Status Codes + +extern const char wuffs_bzip2__error__bad_huffman_code_over_subscribed[]; +extern const char wuffs_bzip2__error__bad_huffman_code_under_subscribed[]; +extern const char wuffs_bzip2__error__bad_block_header[]; +extern const char wuffs_bzip2__error__bad_block_length[]; +extern const char wuffs_bzip2__error__bad_checksum[]; +extern const char wuffs_bzip2__error__bad_header[]; +extern const char wuffs_bzip2__error__bad_number_of_sections[]; +extern const char wuffs_bzip2__error__truncated_input[]; +extern const char wuffs_bzip2__error__unsupported_block_randomization[]; + +// ---------------- Public Consts + +#define WUFFS_BZIP2__DECODER_WORKBUF_LEN_MAX_INCL_WORST_CASE 0 + +// ---------------- Struct Declarations + +typedef struct wuffs_bzip2__decoder__struct wuffs_bzip2__decoder; + +#ifdef __cplusplus +extern "C" { +#endif + +// ---------------- Public Initializer Prototypes + +// For any given "wuffs_foo__bar* self", "wuffs_foo__bar__initialize(self, +// etc)" should be called before any other "wuffs_foo__bar__xxx(self, etc)". +// +// Pass sizeof(*self) and WUFFS_VERSION for sizeof_star_self and wuffs_version. +// Pass 0 (or some combination of WUFFS_INITIALIZE__XXX) for options. + +wuffs_base__status WUFFS_BASE__WARN_UNUSED_RESULT +wuffs_bzip2__decoder__initialize( + wuffs_bzip2__decoder* self, + size_t sizeof_star_self, + uint64_t wuffs_version, + uint32_t options); + +size_t +sizeof__wuffs_bzip2__decoder(); + +// ---------------- Allocs + +// These functions allocate and initialize Wuffs structs. They return NULL if +// memory allocation fails. If they return non-NULL, there is no need to call +// wuffs_foo__bar__initialize, but the caller is responsible for eventually +// calling free on the returned pointer. That pointer is effectively a C++ +// std::unique_ptr. + +wuffs_bzip2__decoder* +wuffs_bzip2__decoder__alloc(); + +static inline wuffs_base__io_transformer* +wuffs_bzip2__decoder__alloc_as__wuffs_base__io_transformer() { + return (wuffs_base__io_transformer*)(wuffs_bzip2__decoder__alloc()); +} + +// ---------------- Upcasts + +static inline wuffs_base__io_transformer* +wuffs_bzip2__decoder__upcast_as__wuffs_base__io_transformer( + wuffs_bzip2__decoder* p) { + return (wuffs_base__io_transformer*)p; +} + +// ---------------- Public Function Prototypes + +WUFFS_BASE__MAYBE_STATIC wuffs_base__empty_struct +wuffs_bzip2__decoder__set_quirk_enabled( + wuffs_bzip2__decoder* self, + uint32_t a_quirk, + bool a_enabled); + +WUFFS_BASE__MAYBE_STATIC wuffs_base__range_ii_u64 +wuffs_bzip2__decoder__workbuf_len( + const wuffs_bzip2__decoder* self); + +WUFFS_BASE__MAYBE_STATIC wuffs_base__status +wuffs_bzip2__decoder__transform_io( + wuffs_bzip2__decoder* self, + wuffs_base__io_buffer* a_dst, + wuffs_base__io_buffer* a_src, + wuffs_base__slice_u8 a_workbuf); + +#ifdef __cplusplus +} // extern "C" +#endif + +// ---------------- Struct Definitions + +// These structs' fields, and the sizeof them, are private implementation +// details that aren't guaranteed to be stable across Wuffs versions. +// +// See https://en.wikipedia.org/wiki/Opaque_pointer#C + +#if defined(__cplusplus) || defined(WUFFS_IMPLEMENTATION) + +struct wuffs_bzip2__decoder__struct { + // Do not access the private_impl's or private_data's fields directly. There + // is no API/ABI compatibility or safety guarantee if you do so. Instead, use + // the wuffs_foo__bar__baz functions. + // + // It is a struct, not a struct*, so that the outermost wuffs_foo__bar struct + // can be stack allocated when WUFFS_IMPLEMENTATION is defined. + + struct { + uint32_t magic; + uint32_t active_coroutine; + wuffs_base__vtable vtable_for__wuffs_base__io_transformer; + wuffs_base__vtable null_vtable; + + uint32_t f_bits; + uint32_t f_n_bits; + uint32_t f_max_incl_block_size; + uint32_t f_block_size; + bool f_decode_huffman_finished; + uint8_t f_decode_huffman_which; + uint32_t f_decode_huffman_ticks; + uint32_t f_decode_huffman_section; + uint32_t f_decode_huffman_run_shift; + uint32_t f_flush_pointer; + uint32_t f_flush_repeat_count; + uint8_t f_flush_prev; + bool f_ignore_checksum; + uint32_t f_final_checksum_have; + uint32_t f_block_checksum_have; + uint32_t f_block_checksum_want; + uint32_t f_original_pointer; + uint32_t f_num_symbols; + uint32_t f_num_huffman_codes; + uint32_t f_num_sections; + uint32_t f_code_lengths_bitmask; + + uint32_t p_transform_io[1]; + uint32_t p_do_transform_io[1]; + uint32_t p_prepare_block[1]; + uint32_t p_read_code_lengths[1]; + uint32_t p_flush_slow[1]; + uint32_t p_decode_huffman_slow[1]; + } private_impl; + + struct { + uint32_t f_scratch; + uint32_t f_letter_counts[256]; + uint8_t f_presence[256]; + uint8_t f_mtft[256]; + uint8_t f_huffman_selectors[32768]; + uint16_t f_huffman_trees[6][257][2]; + uint16_t f_huffman_tables[6][256]; + uint32_t f_bwt[1048576]; + + struct { + uint32_t v_i; + uint64_t v_tag; + uint32_t v_final_checksum_want; + } s_do_transform_io[1]; + struct { + uint32_t v_i; + uint32_t v_selector; + } s_prepare_block[1]; + struct { + uint32_t v_i; + uint32_t v_code_length; + } s_read_code_lengths[1]; + struct { + uint32_t v_flush_pointer; + uint32_t v_flush_repeat_count; + uint8_t v_flush_prev; + uint32_t v_block_checksum_have; + uint32_t v_block_size; + uint8_t v_curr; + uint64_t scratch; + } s_flush_slow[1]; + struct { + uint32_t v_node_index; + } s_decode_huffman_slow[1]; + } private_data; + +#ifdef __cplusplus +#if defined(WUFFS_BASE__HAVE_UNIQUE_PTR) + using unique_ptr = std::unique_ptr; + + // On failure, the alloc_etc functions return nullptr. They don't throw. + + static inline unique_ptr + alloc() { + return unique_ptr(wuffs_bzip2__decoder__alloc(), &free); + } + + static inline wuffs_base__io_transformer::unique_ptr + alloc_as__wuffs_base__io_transformer() { + return wuffs_base__io_transformer::unique_ptr( + wuffs_bzip2__decoder__alloc_as__wuffs_base__io_transformer(), &free); + } +#endif // defined(WUFFS_BASE__HAVE_UNIQUE_PTR) + +#if defined(WUFFS_BASE__HAVE_EQ_DELETE) && !defined(WUFFS_IMPLEMENTATION) + // Disallow constructing or copying an object via standard C++ mechanisms, + // e.g. the "new" operator, as this struct is intentionally opaque. Its total + // size and field layout is not part of the public, stable, memory-safe API. + // Use malloc or memcpy and the sizeof__wuffs_foo__bar function instead, and + // call wuffs_foo__bar__baz methods (which all take a "this"-like pointer as + // their first argument) rather than tweaking bar.private_impl.qux fields. + // + // In C, we can just leave wuffs_foo__bar as an incomplete type (unless + // WUFFS_IMPLEMENTATION is #define'd). In C++, we define a complete type in + // order to provide convenience methods. These forward on "this", so that you + // can write "bar->baz(etc)" instead of "wuffs_foo__bar__baz(bar, etc)". + wuffs_bzip2__decoder__struct() = delete; + wuffs_bzip2__decoder__struct(const wuffs_bzip2__decoder__struct&) = delete; + wuffs_bzip2__decoder__struct& operator=( + const wuffs_bzip2__decoder__struct&) = delete; +#endif // defined(WUFFS_BASE__HAVE_EQ_DELETE) && !defined(WUFFS_IMPLEMENTATION) + +#if !defined(WUFFS_IMPLEMENTATION) + // As above, the size of the struct is not part of the public API, and unless + // WUFFS_IMPLEMENTATION is #define'd, this struct type T should be heap + // allocated, not stack allocated. Its size is not intended to be known at + // compile time, but it is unfortunately divulged as a side effect of + // defining C++ convenience methods. Use "sizeof__T()", calling the function, + // instead of "sizeof T", invoking the operator. To make the two values + // different, so that passing the latter will be rejected by the initialize + // function, we add an arbitrary amount of dead weight. + uint8_t dead_weight[123000000]; // 123 MB. +#endif // !defined(WUFFS_IMPLEMENTATION) + + inline wuffs_base__status WUFFS_BASE__WARN_UNUSED_RESULT + initialize( + size_t sizeof_star_self, + uint64_t wuffs_version, + uint32_t options) { + return wuffs_bzip2__decoder__initialize( + this, sizeof_star_self, wuffs_version, options); + } + + inline wuffs_base__io_transformer* + upcast_as__wuffs_base__io_transformer() { + return (wuffs_base__io_transformer*)this; + } + + inline wuffs_base__empty_struct + set_quirk_enabled( + uint32_t a_quirk, + bool a_enabled) { + return wuffs_bzip2__decoder__set_quirk_enabled(this, a_quirk, a_enabled); + } + + inline wuffs_base__range_ii_u64 + workbuf_len() const { + return wuffs_bzip2__decoder__workbuf_len(this); + } + + inline wuffs_base__status + transform_io( + wuffs_base__io_buffer* a_dst, + wuffs_base__io_buffer* a_src, + wuffs_base__slice_u8 a_workbuf) { + return wuffs_bzip2__decoder__transform_io(this, a_dst, a_src, a_workbuf); + } + +#endif // __cplusplus +}; // struct wuffs_bzip2__decoder__struct + +#endif // defined(__cplusplus) || defined(WUFFS_IMPLEMENTATION) + +#endif // !defined(WUFFS_CONFIG__MODULES) || defined(WUFFS_CONFIG__MODULE__BZIP2) || defined(WUFFS_NONMONOLITHIC) + +#if !defined(WUFFS_CONFIG__MODULES) || defined(WUFFS_CONFIG__MODULE__CBOR) || defined(WUFFS_NONMONOLITHIC) + +// ---------------- Status Codes + +extern const char wuffs_cbor__error__bad_input[]; +extern const char wuffs_cbor__error__unsupported_recursion_depth[]; + +// ---------------- Public Consts + +#define WUFFS_CBOR__DECODER_WORKBUF_LEN_MAX_INCL_WORST_CASE 0 + +#define WUFFS_CBOR__DECODER_DEPTH_MAX_INCL 1024 + +#define WUFFS_CBOR__DECODER_DST_TOKEN_BUFFER_LENGTH_MIN_INCL 2 + +#define WUFFS_CBOR__DECODER_SRC_IO_BUFFER_LENGTH_MIN_INCL 9 + +#define WUFFS_CBOR__TOKEN_VALUE_MAJOR 787997 + +#define WUFFS_CBOR__TOKEN_VALUE_MINOR__DETAIL_MASK 262143 + +#define WUFFS_CBOR__TOKEN_VALUE_MINOR__MINUS_1_MINUS_X 16777216 + +#define WUFFS_CBOR__TOKEN_VALUE_MINOR__SIMPLE_VALUE 8388608 + +#define WUFFS_CBOR__TOKEN_VALUE_MINOR__TAG 4194304 + +// ---------------- Struct Declarations + +typedef struct wuffs_cbor__decoder__struct wuffs_cbor__decoder; + +#ifdef __cplusplus +extern "C" { +#endif + +// ---------------- Public Initializer Prototypes + +// For any given "wuffs_foo__bar* self", "wuffs_foo__bar__initialize(self, +// etc)" should be called before any other "wuffs_foo__bar__xxx(self, etc)". +// +// Pass sizeof(*self) and WUFFS_VERSION for sizeof_star_self and wuffs_version. +// Pass 0 (or some combination of WUFFS_INITIALIZE__XXX) for options. + +wuffs_base__status WUFFS_BASE__WARN_UNUSED_RESULT +wuffs_cbor__decoder__initialize( + wuffs_cbor__decoder* self, + size_t sizeof_star_self, + uint64_t wuffs_version, + uint32_t options); + +size_t +sizeof__wuffs_cbor__decoder(); + +// ---------------- Allocs + +// These functions allocate and initialize Wuffs structs. They return NULL if +// memory allocation fails. If they return non-NULL, there is no need to call +// wuffs_foo__bar__initialize, but the caller is responsible for eventually +// calling free on the returned pointer. That pointer is effectively a C++ +// std::unique_ptr. + +wuffs_cbor__decoder* +wuffs_cbor__decoder__alloc(); + +static inline wuffs_base__token_decoder* +wuffs_cbor__decoder__alloc_as__wuffs_base__token_decoder() { + return (wuffs_base__token_decoder*)(wuffs_cbor__decoder__alloc()); +} + +// ---------------- Upcasts + +static inline wuffs_base__token_decoder* +wuffs_cbor__decoder__upcast_as__wuffs_base__token_decoder( + wuffs_cbor__decoder* p) { + return (wuffs_base__token_decoder*)p; +} + +// ---------------- Public Function Prototypes + +WUFFS_BASE__MAYBE_STATIC wuffs_base__empty_struct +wuffs_cbor__decoder__set_quirk_enabled( + wuffs_cbor__decoder* self, + uint32_t a_quirk, + bool a_enabled); + +WUFFS_BASE__MAYBE_STATIC wuffs_base__range_ii_u64 +wuffs_cbor__decoder__workbuf_len( + const wuffs_cbor__decoder* self); + +WUFFS_BASE__MAYBE_STATIC wuffs_base__status +wuffs_cbor__decoder__decode_tokens( + wuffs_cbor__decoder* self, + wuffs_base__token_buffer* a_dst, + wuffs_base__io_buffer* a_src, + wuffs_base__slice_u8 a_workbuf); + +#ifdef __cplusplus +} // extern "C" +#endif + +// ---------------- Struct Definitions + +// These structs' fields, and the sizeof them, are private implementation +// details that aren't guaranteed to be stable across Wuffs versions. +// +// See https://en.wikipedia.org/wiki/Opaque_pointer#C + +#if defined(__cplusplus) || defined(WUFFS_IMPLEMENTATION) + +struct wuffs_cbor__decoder__struct { + // Do not access the private_impl's or private_data's fields directly. There + // is no API/ABI compatibility or safety guarantee if you do so. Instead, use + // the wuffs_foo__bar__baz functions. + // + // It is a struct, not a struct*, so that the outermost wuffs_foo__bar struct + // can be stack allocated when WUFFS_IMPLEMENTATION is defined. + + struct { + uint32_t magic; + uint32_t active_coroutine; + wuffs_base__vtable vtable_for__wuffs_base__token_decoder; + wuffs_base__vtable null_vtable; + + bool f_end_of_data; + + uint32_t p_decode_tokens[1]; + } private_impl; + + struct { + uint32_t f_stack[64]; + uint64_t f_container_num_remaining[1024]; + + struct { + uint64_t v_string_length; + uint32_t v_depth; + bool v_tagged; + uint8_t v_indefinite_string_major_type; + } s_decode_tokens[1]; + } private_data; + +#ifdef __cplusplus +#if defined(WUFFS_BASE__HAVE_UNIQUE_PTR) + using unique_ptr = std::unique_ptr; + + // On failure, the alloc_etc functions return nullptr. They don't throw. + + static inline unique_ptr + alloc() { + return unique_ptr(wuffs_cbor__decoder__alloc(), &free); + } + + static inline wuffs_base__token_decoder::unique_ptr + alloc_as__wuffs_base__token_decoder() { + return wuffs_base__token_decoder::unique_ptr( + wuffs_cbor__decoder__alloc_as__wuffs_base__token_decoder(), &free); + } +#endif // defined(WUFFS_BASE__HAVE_UNIQUE_PTR) + +#if defined(WUFFS_BASE__HAVE_EQ_DELETE) && !defined(WUFFS_IMPLEMENTATION) + // Disallow constructing or copying an object via standard C++ mechanisms, + // e.g. the "new" operator, as this struct is intentionally opaque. Its total + // size and field layout is not part of the public, stable, memory-safe API. + // Use malloc or memcpy and the sizeof__wuffs_foo__bar function instead, and + // call wuffs_foo__bar__baz methods (which all take a "this"-like pointer as + // their first argument) rather than tweaking bar.private_impl.qux fields. + // + // In C, we can just leave wuffs_foo__bar as an incomplete type (unless + // WUFFS_IMPLEMENTATION is #define'd). In C++, we define a complete type in + // order to provide convenience methods. These forward on "this", so that you + // can write "bar->baz(etc)" instead of "wuffs_foo__bar__baz(bar, etc)". + wuffs_cbor__decoder__struct() = delete; + wuffs_cbor__decoder__struct(const wuffs_cbor__decoder__struct&) = delete; + wuffs_cbor__decoder__struct& operator=( + const wuffs_cbor__decoder__struct&) = delete; +#endif // defined(WUFFS_BASE__HAVE_EQ_DELETE) && !defined(WUFFS_IMPLEMENTATION) + +#if !defined(WUFFS_IMPLEMENTATION) + // As above, the size of the struct is not part of the public API, and unless + // WUFFS_IMPLEMENTATION is #define'd, this struct type T should be heap + // allocated, not stack allocated. Its size is not intended to be known at + // compile time, but it is unfortunately divulged as a side effect of + // defining C++ convenience methods. Use "sizeof__T()", calling the function, + // instead of "sizeof T", invoking the operator. To make the two values + // different, so that passing the latter will be rejected by the initialize + // function, we add an arbitrary amount of dead weight. + uint8_t dead_weight[123000000]; // 123 MB. +#endif // !defined(WUFFS_IMPLEMENTATION) + + inline wuffs_base__status WUFFS_BASE__WARN_UNUSED_RESULT + initialize( + size_t sizeof_star_self, + uint64_t wuffs_version, + uint32_t options) { + return wuffs_cbor__decoder__initialize( + this, sizeof_star_self, wuffs_version, options); + } + + inline wuffs_base__token_decoder* + upcast_as__wuffs_base__token_decoder() { + return (wuffs_base__token_decoder*)this; + } + + inline wuffs_base__empty_struct + set_quirk_enabled( + uint32_t a_quirk, + bool a_enabled) { + return wuffs_cbor__decoder__set_quirk_enabled(this, a_quirk, a_enabled); + } + + inline wuffs_base__range_ii_u64 + workbuf_len() const { + return wuffs_cbor__decoder__workbuf_len(this); + } + + inline wuffs_base__status + decode_tokens( + wuffs_base__token_buffer* a_dst, + wuffs_base__io_buffer* a_src, + wuffs_base__slice_u8 a_workbuf) { + return wuffs_cbor__decoder__decode_tokens(this, a_dst, a_src, a_workbuf); + } + +#endif // __cplusplus +}; // struct wuffs_cbor__decoder__struct + +#endif // defined(__cplusplus) || defined(WUFFS_IMPLEMENTATION) + +#endif // !defined(WUFFS_CONFIG__MODULES) || defined(WUFFS_CONFIG__MODULE__CBOR) || defined(WUFFS_NONMONOLITHIC) + +#if !defined(WUFFS_CONFIG__MODULES) || defined(WUFFS_CONFIG__MODULE__CRC32) || defined(WUFFS_NONMONOLITHIC) + +// ---------------- Status Codes + +// ---------------- Public Consts + +// ---------------- Struct Declarations + +typedef struct wuffs_crc32__ieee_hasher__struct wuffs_crc32__ieee_hasher; + +#ifdef __cplusplus +extern "C" { +#endif + +// ---------------- Public Initializer Prototypes + +// For any given "wuffs_foo__bar* self", "wuffs_foo__bar__initialize(self, +// etc)" should be called before any other "wuffs_foo__bar__xxx(self, etc)". +// +// Pass sizeof(*self) and WUFFS_VERSION for sizeof_star_self and wuffs_version. +// Pass 0 (or some combination of WUFFS_INITIALIZE__XXX) for options. + +wuffs_base__status WUFFS_BASE__WARN_UNUSED_RESULT +wuffs_crc32__ieee_hasher__initialize( + wuffs_crc32__ieee_hasher* self, + size_t sizeof_star_self, + uint64_t wuffs_version, + uint32_t options); + +size_t +sizeof__wuffs_crc32__ieee_hasher(); + +// ---------------- Allocs + +// These functions allocate and initialize Wuffs structs. They return NULL if +// memory allocation fails. If they return non-NULL, there is no need to call +// wuffs_foo__bar__initialize, but the caller is responsible for eventually +// calling free on the returned pointer. That pointer is effectively a C++ +// std::unique_ptr. + +wuffs_crc32__ieee_hasher* +wuffs_crc32__ieee_hasher__alloc(); + +static inline wuffs_base__hasher_u32* +wuffs_crc32__ieee_hasher__alloc_as__wuffs_base__hasher_u32() { + return (wuffs_base__hasher_u32*)(wuffs_crc32__ieee_hasher__alloc()); +} + +// ---------------- Upcasts + +static inline wuffs_base__hasher_u32* +wuffs_crc32__ieee_hasher__upcast_as__wuffs_base__hasher_u32( + wuffs_crc32__ieee_hasher* p) { + return (wuffs_base__hasher_u32*)p; +} + +// ---------------- Public Function Prototypes + +WUFFS_BASE__MAYBE_STATIC wuffs_base__empty_struct +wuffs_crc32__ieee_hasher__set_quirk_enabled( + wuffs_crc32__ieee_hasher* self, + uint32_t a_quirk, + bool a_enabled); + +WUFFS_BASE__MAYBE_STATIC uint32_t +wuffs_crc32__ieee_hasher__update_u32( + wuffs_crc32__ieee_hasher* self, + wuffs_base__slice_u8 a_x); + +#ifdef __cplusplus +} // extern "C" +#endif + +// ---------------- Struct Definitions + +// These structs' fields, and the sizeof them, are private implementation +// details that aren't guaranteed to be stable across Wuffs versions. +// +// See https://en.wikipedia.org/wiki/Opaque_pointer#C + +#if defined(__cplusplus) || defined(WUFFS_IMPLEMENTATION) + +struct wuffs_crc32__ieee_hasher__struct { + // Do not access the private_impl's or private_data's fields directly. There + // is no API/ABI compatibility or safety guarantee if you do so. Instead, use + // the wuffs_foo__bar__baz functions. + // + // It is a struct, not a struct*, so that the outermost wuffs_foo__bar struct + // can be stack allocated when WUFFS_IMPLEMENTATION is defined. + + struct { + uint32_t magic; + uint32_t active_coroutine; + wuffs_base__vtable vtable_for__wuffs_base__hasher_u32; + wuffs_base__vtable null_vtable; + + uint32_t f_state; + + wuffs_base__empty_struct (*choosy_up)( + wuffs_crc32__ieee_hasher* self, + wuffs_base__slice_u8 a_x); + } private_impl; + +#ifdef __cplusplus +#if defined(WUFFS_BASE__HAVE_UNIQUE_PTR) + using unique_ptr = std::unique_ptr; + + // On failure, the alloc_etc functions return nullptr. They don't throw. + + static inline unique_ptr + alloc() { + return unique_ptr(wuffs_crc32__ieee_hasher__alloc(), &free); + } + + static inline wuffs_base__hasher_u32::unique_ptr + alloc_as__wuffs_base__hasher_u32() { + return wuffs_base__hasher_u32::unique_ptr( + wuffs_crc32__ieee_hasher__alloc_as__wuffs_base__hasher_u32(), &free); + } +#endif // defined(WUFFS_BASE__HAVE_UNIQUE_PTR) + +#if defined(WUFFS_BASE__HAVE_EQ_DELETE) && !defined(WUFFS_IMPLEMENTATION) + // Disallow constructing or copying an object via standard C++ mechanisms, + // e.g. the "new" operator, as this struct is intentionally opaque. Its total + // size and field layout is not part of the public, stable, memory-safe API. + // Use malloc or memcpy and the sizeof__wuffs_foo__bar function instead, and + // call wuffs_foo__bar__baz methods (which all take a "this"-like pointer as + // their first argument) rather than tweaking bar.private_impl.qux fields. + // + // In C, we can just leave wuffs_foo__bar as an incomplete type (unless + // WUFFS_IMPLEMENTATION is #define'd). In C++, we define a complete type in + // order to provide convenience methods. These forward on "this", so that you + // can write "bar->baz(etc)" instead of "wuffs_foo__bar__baz(bar, etc)". + wuffs_crc32__ieee_hasher__struct() = delete; + wuffs_crc32__ieee_hasher__struct(const wuffs_crc32__ieee_hasher__struct&) = delete; + wuffs_crc32__ieee_hasher__struct& operator=( + const wuffs_crc32__ieee_hasher__struct&) = delete; +#endif // defined(WUFFS_BASE__HAVE_EQ_DELETE) && !defined(WUFFS_IMPLEMENTATION) + +#if !defined(WUFFS_IMPLEMENTATION) + // As above, the size of the struct is not part of the public API, and unless + // WUFFS_IMPLEMENTATION is #define'd, this struct type T should be heap + // allocated, not stack allocated. Its size is not intended to be known at + // compile time, but it is unfortunately divulged as a side effect of + // defining C++ convenience methods. Use "sizeof__T()", calling the function, + // instead of "sizeof T", invoking the operator. To make the two values + // different, so that passing the latter will be rejected by the initialize + // function, we add an arbitrary amount of dead weight. + uint8_t dead_weight[123000000]; // 123 MB. +#endif // !defined(WUFFS_IMPLEMENTATION) + + inline wuffs_base__status WUFFS_BASE__WARN_UNUSED_RESULT + initialize( + size_t sizeof_star_self, + uint64_t wuffs_version, + uint32_t options) { + return wuffs_crc32__ieee_hasher__initialize( + this, sizeof_star_self, wuffs_version, options); + } + + inline wuffs_base__hasher_u32* + upcast_as__wuffs_base__hasher_u32() { + return (wuffs_base__hasher_u32*)this; + } + + inline wuffs_base__empty_struct + set_quirk_enabled( + uint32_t a_quirk, + bool a_enabled) { + return wuffs_crc32__ieee_hasher__set_quirk_enabled(this, a_quirk, a_enabled); + } + + inline uint32_t + update_u32( + wuffs_base__slice_u8 a_x) { + return wuffs_crc32__ieee_hasher__update_u32(this, a_x); + } + +#endif // __cplusplus +}; // struct wuffs_crc32__ieee_hasher__struct + +#endif // defined(__cplusplus) || defined(WUFFS_IMPLEMENTATION) + +#endif // !defined(WUFFS_CONFIG__MODULES) || defined(WUFFS_CONFIG__MODULE__CRC32) || defined(WUFFS_NONMONOLITHIC) + +#if !defined(WUFFS_CONFIG__MODULES) || defined(WUFFS_CONFIG__MODULE__DEFLATE) || defined(WUFFS_NONMONOLITHIC) + +// ---------------- Status Codes + +extern const char wuffs_deflate__error__bad_huffman_code_over_subscribed[]; +extern const char wuffs_deflate__error__bad_huffman_code_under_subscribed[]; +extern const char wuffs_deflate__error__bad_huffman_code_length_count[]; +extern const char wuffs_deflate__error__bad_huffman_code_length_repetition[]; +extern const char wuffs_deflate__error__bad_huffman_code[]; +extern const char wuffs_deflate__error__bad_huffman_minimum_code_length[]; +extern const char wuffs_deflate__error__bad_block[]; +extern const char wuffs_deflate__error__bad_distance[]; +extern const char wuffs_deflate__error__bad_distance_code_count[]; +extern const char wuffs_deflate__error__bad_literal_length_code_count[]; +extern const char wuffs_deflate__error__inconsistent_stored_block_length[]; +extern const char wuffs_deflate__error__missing_end_of_block_code[]; +extern const char wuffs_deflate__error__no_huffman_codes[]; +extern const char wuffs_deflate__error__truncated_input[]; + +// ---------------- Public Consts + +#define WUFFS_DEFLATE__DECODER_WORKBUF_LEN_MAX_INCL_WORST_CASE 1 + +// ---------------- Struct Declarations + +typedef struct wuffs_deflate__decoder__struct wuffs_deflate__decoder; + +#ifdef __cplusplus +extern "C" { +#endif + +// ---------------- Public Initializer Prototypes + +// For any given "wuffs_foo__bar* self", "wuffs_foo__bar__initialize(self, +// etc)" should be called before any other "wuffs_foo__bar__xxx(self, etc)". +// +// Pass sizeof(*self) and WUFFS_VERSION for sizeof_star_self and wuffs_version. +// Pass 0 (or some combination of WUFFS_INITIALIZE__XXX) for options. + +wuffs_base__status WUFFS_BASE__WARN_UNUSED_RESULT +wuffs_deflate__decoder__initialize( + wuffs_deflate__decoder* self, + size_t sizeof_star_self, + uint64_t wuffs_version, + uint32_t options); + +size_t +sizeof__wuffs_deflate__decoder(); + +// ---------------- Allocs + +// These functions allocate and initialize Wuffs structs. They return NULL if +// memory allocation fails. If they return non-NULL, there is no need to call +// wuffs_foo__bar__initialize, but the caller is responsible for eventually +// calling free on the returned pointer. That pointer is effectively a C++ +// std::unique_ptr. + +wuffs_deflate__decoder* +wuffs_deflate__decoder__alloc(); + +static inline wuffs_base__io_transformer* +wuffs_deflate__decoder__alloc_as__wuffs_base__io_transformer() { + return (wuffs_base__io_transformer*)(wuffs_deflate__decoder__alloc()); +} + +// ---------------- Upcasts + +static inline wuffs_base__io_transformer* +wuffs_deflate__decoder__upcast_as__wuffs_base__io_transformer( + wuffs_deflate__decoder* p) { + return (wuffs_base__io_transformer*)p; +} + +// ---------------- Public Function Prototypes + +WUFFS_BASE__MAYBE_STATIC wuffs_base__empty_struct +wuffs_deflate__decoder__add_history( + wuffs_deflate__decoder* self, + wuffs_base__slice_u8 a_hist); + +WUFFS_BASE__MAYBE_STATIC wuffs_base__empty_struct +wuffs_deflate__decoder__set_quirk_enabled( + wuffs_deflate__decoder* self, + uint32_t a_quirk, + bool a_enabled); + +WUFFS_BASE__MAYBE_STATIC wuffs_base__range_ii_u64 +wuffs_deflate__decoder__workbuf_len( + const wuffs_deflate__decoder* self); + +WUFFS_BASE__MAYBE_STATIC wuffs_base__status +wuffs_deflate__decoder__transform_io( + wuffs_deflate__decoder* self, + wuffs_base__io_buffer* a_dst, + wuffs_base__io_buffer* a_src, + wuffs_base__slice_u8 a_workbuf); + +#ifdef __cplusplus +} // extern "C" +#endif + +// ---------------- Struct Definitions + +// These structs' fields, and the sizeof them, are private implementation +// details that aren't guaranteed to be stable across Wuffs versions. +// +// See https://en.wikipedia.org/wiki/Opaque_pointer#C + +#if defined(__cplusplus) || defined(WUFFS_IMPLEMENTATION) + +struct wuffs_deflate__decoder__struct { + // Do not access the private_impl's or private_data's fields directly. There + // is no API/ABI compatibility or safety guarantee if you do so. Instead, use + // the wuffs_foo__bar__baz functions. + // + // It is a struct, not a struct*, so that the outermost wuffs_foo__bar struct + // can be stack allocated when WUFFS_IMPLEMENTATION is defined. + + struct { + uint32_t magic; + uint32_t active_coroutine; + wuffs_base__vtable vtable_for__wuffs_base__io_transformer; + wuffs_base__vtable null_vtable; + + uint32_t f_bits; + uint32_t f_n_bits; + uint64_t f_transformed_history_count; + uint32_t f_history_index; + uint32_t f_n_huffs_bits[2]; + bool f_end_of_block; + + uint32_t p_transform_io[1]; + uint32_t p_do_transform_io[1]; + uint32_t p_decode_blocks[1]; + uint32_t p_decode_uncompressed[1]; + uint32_t p_init_dynamic_huffman[1]; + wuffs_base__status (*choosy_decode_huffman_fast64)( + wuffs_deflate__decoder* self, + wuffs_base__io_buffer* a_dst, + wuffs_base__io_buffer* a_src); + uint32_t p_decode_huffman_slow[1]; + } private_impl; + + struct { + uint32_t f_huffs[2][1024]; + uint8_t f_history[33025]; + uint8_t f_code_lengths[320]; + + struct { + uint32_t v_final; + } s_decode_blocks[1]; + struct { + uint32_t v_length; + uint64_t scratch; + } s_decode_uncompressed[1]; + struct { + uint32_t v_bits; + uint32_t v_n_bits; + uint32_t v_n_lit; + uint32_t v_n_dist; + uint32_t v_n_clen; + uint32_t v_i; + uint32_t v_mask; + uint32_t v_n_extra_bits; + uint8_t v_rep_symbol; + uint32_t v_rep_count; + } s_init_dynamic_huffman[1]; + struct { + uint32_t v_bits; + uint32_t v_n_bits; + uint32_t v_table_entry_n_bits; + uint32_t v_lmask; + uint32_t v_dmask; + uint32_t v_redir_top; + uint32_t v_redir_mask; + uint32_t v_length; + uint32_t v_dist_minus_1; + uint64_t scratch; + } s_decode_huffman_slow[1]; + } private_data; + +#ifdef __cplusplus +#if defined(WUFFS_BASE__HAVE_UNIQUE_PTR) + using unique_ptr = std::unique_ptr; + + // On failure, the alloc_etc functions return nullptr. They don't throw. + + static inline unique_ptr + alloc() { + return unique_ptr(wuffs_deflate__decoder__alloc(), &free); + } + + static inline wuffs_base__io_transformer::unique_ptr + alloc_as__wuffs_base__io_transformer() { + return wuffs_base__io_transformer::unique_ptr( + wuffs_deflate__decoder__alloc_as__wuffs_base__io_transformer(), &free); + } +#endif // defined(WUFFS_BASE__HAVE_UNIQUE_PTR) + +#if defined(WUFFS_BASE__HAVE_EQ_DELETE) && !defined(WUFFS_IMPLEMENTATION) + // Disallow constructing or copying an object via standard C++ mechanisms, + // e.g. the "new" operator, as this struct is intentionally opaque. Its total + // size and field layout is not part of the public, stable, memory-safe API. + // Use malloc or memcpy and the sizeof__wuffs_foo__bar function instead, and + // call wuffs_foo__bar__baz methods (which all take a "this"-like pointer as + // their first argument) rather than tweaking bar.private_impl.qux fields. + // + // In C, we can just leave wuffs_foo__bar as an incomplete type (unless + // WUFFS_IMPLEMENTATION is #define'd). In C++, we define a complete type in + // order to provide convenience methods. These forward on "this", so that you + // can write "bar->baz(etc)" instead of "wuffs_foo__bar__baz(bar, etc)". + wuffs_deflate__decoder__struct() = delete; + wuffs_deflate__decoder__struct(const wuffs_deflate__decoder__struct&) = delete; + wuffs_deflate__decoder__struct& operator=( + const wuffs_deflate__decoder__struct&) = delete; +#endif // defined(WUFFS_BASE__HAVE_EQ_DELETE) && !defined(WUFFS_IMPLEMENTATION) + +#if !defined(WUFFS_IMPLEMENTATION) + // As above, the size of the struct is not part of the public API, and unless + // WUFFS_IMPLEMENTATION is #define'd, this struct type T should be heap + // allocated, not stack allocated. Its size is not intended to be known at + // compile time, but it is unfortunately divulged as a side effect of + // defining C++ convenience methods. Use "sizeof__T()", calling the function, + // instead of "sizeof T", invoking the operator. To make the two values + // different, so that passing the latter will be rejected by the initialize + // function, we add an arbitrary amount of dead weight. + uint8_t dead_weight[123000000]; // 123 MB. +#endif // !defined(WUFFS_IMPLEMENTATION) + + inline wuffs_base__status WUFFS_BASE__WARN_UNUSED_RESULT + initialize( + size_t sizeof_star_self, + uint64_t wuffs_version, + uint32_t options) { + return wuffs_deflate__decoder__initialize( + this, sizeof_star_self, wuffs_version, options); + } + + inline wuffs_base__io_transformer* + upcast_as__wuffs_base__io_transformer() { + return (wuffs_base__io_transformer*)this; + } + + inline wuffs_base__empty_struct + add_history( + wuffs_base__slice_u8 a_hist) { + return wuffs_deflate__decoder__add_history(this, a_hist); + } + + inline wuffs_base__empty_struct + set_quirk_enabled( + uint32_t a_quirk, + bool a_enabled) { + return wuffs_deflate__decoder__set_quirk_enabled(this, a_quirk, a_enabled); + } + + inline wuffs_base__range_ii_u64 + workbuf_len() const { + return wuffs_deflate__decoder__workbuf_len(this); + } + + inline wuffs_base__status + transform_io( + wuffs_base__io_buffer* a_dst, + wuffs_base__io_buffer* a_src, + wuffs_base__slice_u8 a_workbuf) { + return wuffs_deflate__decoder__transform_io(this, a_dst, a_src, a_workbuf); + } + +#endif // __cplusplus +}; // struct wuffs_deflate__decoder__struct + +#endif // defined(__cplusplus) || defined(WUFFS_IMPLEMENTATION) + +#endif // !defined(WUFFS_CONFIG__MODULES) || defined(WUFFS_CONFIG__MODULE__DEFLATE) || defined(WUFFS_NONMONOLITHIC) + +#if !defined(WUFFS_CONFIG__MODULES) || defined(WUFFS_CONFIG__MODULE__LZW) || defined(WUFFS_NONMONOLITHIC) + +// ---------------- Status Codes + +extern const char wuffs_lzw__error__bad_code[]; +extern const char wuffs_lzw__error__truncated_input[]; + +// ---------------- Public Consts + +#define WUFFS_LZW__DECODER_WORKBUF_LEN_MAX_INCL_WORST_CASE 0 + +// ---------------- Struct Declarations + +typedef struct wuffs_lzw__decoder__struct wuffs_lzw__decoder; + +#ifdef __cplusplus +extern "C" { +#endif + +// ---------------- Public Initializer Prototypes + +// For any given "wuffs_foo__bar* self", "wuffs_foo__bar__initialize(self, +// etc)" should be called before any other "wuffs_foo__bar__xxx(self, etc)". +// +// Pass sizeof(*self) and WUFFS_VERSION for sizeof_star_self and wuffs_version. +// Pass 0 (or some combination of WUFFS_INITIALIZE__XXX) for options. + +wuffs_base__status WUFFS_BASE__WARN_UNUSED_RESULT +wuffs_lzw__decoder__initialize( + wuffs_lzw__decoder* self, + size_t sizeof_star_self, + uint64_t wuffs_version, + uint32_t options); + +size_t +sizeof__wuffs_lzw__decoder(); + +// ---------------- Allocs + +// These functions allocate and initialize Wuffs structs. They return NULL if +// memory allocation fails. If they return non-NULL, there is no need to call +// wuffs_foo__bar__initialize, but the caller is responsible for eventually +// calling free on the returned pointer. That pointer is effectively a C++ +// std::unique_ptr. + +wuffs_lzw__decoder* +wuffs_lzw__decoder__alloc(); + +static inline wuffs_base__io_transformer* +wuffs_lzw__decoder__alloc_as__wuffs_base__io_transformer() { + return (wuffs_base__io_transformer*)(wuffs_lzw__decoder__alloc()); +} + +// ---------------- Upcasts + +static inline wuffs_base__io_transformer* +wuffs_lzw__decoder__upcast_as__wuffs_base__io_transformer( + wuffs_lzw__decoder* p) { + return (wuffs_base__io_transformer*)p; +} + +// ---------------- Public Function Prototypes + +WUFFS_BASE__MAYBE_STATIC wuffs_base__empty_struct +wuffs_lzw__decoder__set_quirk_enabled( + wuffs_lzw__decoder* self, + uint32_t a_quirk, + bool a_enabled); + +WUFFS_BASE__MAYBE_STATIC wuffs_base__empty_struct +wuffs_lzw__decoder__set_literal_width( + wuffs_lzw__decoder* self, + uint32_t a_lw); + +WUFFS_BASE__MAYBE_STATIC wuffs_base__range_ii_u64 +wuffs_lzw__decoder__workbuf_len( + const wuffs_lzw__decoder* self); + +WUFFS_BASE__MAYBE_STATIC wuffs_base__status +wuffs_lzw__decoder__transform_io( + wuffs_lzw__decoder* self, + wuffs_base__io_buffer* a_dst, + wuffs_base__io_buffer* a_src, + wuffs_base__slice_u8 a_workbuf); + +WUFFS_BASE__MAYBE_STATIC wuffs_base__slice_u8 +wuffs_lzw__decoder__flush( + wuffs_lzw__decoder* self); + +#ifdef __cplusplus +} // extern "C" +#endif + +// ---------------- Struct Definitions + +// These structs' fields, and the sizeof them, are private implementation +// details that aren't guaranteed to be stable across Wuffs versions. +// +// See https://en.wikipedia.org/wiki/Opaque_pointer#C + +#if defined(__cplusplus) || defined(WUFFS_IMPLEMENTATION) + +struct wuffs_lzw__decoder__struct { + // Do not access the private_impl's or private_data's fields directly. There + // is no API/ABI compatibility or safety guarantee if you do so. Instead, use + // the wuffs_foo__bar__baz functions. + // + // It is a struct, not a struct*, so that the outermost wuffs_foo__bar struct + // can be stack allocated when WUFFS_IMPLEMENTATION is defined. + + struct { + uint32_t magic; + uint32_t active_coroutine; + wuffs_base__vtable vtable_for__wuffs_base__io_transformer; + wuffs_base__vtable null_vtable; + + uint32_t f_set_literal_width_arg; + uint32_t f_literal_width; + uint32_t f_clear_code; + uint32_t f_end_code; + uint32_t f_save_code; + uint32_t f_prev_code; + uint32_t f_width; + uint32_t f_bits; + uint32_t f_n_bits; + uint32_t f_output_ri; + uint32_t f_output_wi; + uint32_t f_read_from_return_value; + uint16_t f_prefixes[4096]; + + uint32_t p_transform_io[1]; + uint32_t p_write_to[1]; + } private_impl; + + struct { + uint8_t f_suffixes[4096][8]; + uint16_t f_lm1s[4096]; + uint8_t f_output[8199]; + } private_data; + +#ifdef __cplusplus +#if defined(WUFFS_BASE__HAVE_UNIQUE_PTR) + using unique_ptr = std::unique_ptr; + + // On failure, the alloc_etc functions return nullptr. They don't throw. + + static inline unique_ptr + alloc() { + return unique_ptr(wuffs_lzw__decoder__alloc(), &free); + } + + static inline wuffs_base__io_transformer::unique_ptr + alloc_as__wuffs_base__io_transformer() { + return wuffs_base__io_transformer::unique_ptr( + wuffs_lzw__decoder__alloc_as__wuffs_base__io_transformer(), &free); + } +#endif // defined(WUFFS_BASE__HAVE_UNIQUE_PTR) + +#if defined(WUFFS_BASE__HAVE_EQ_DELETE) && !defined(WUFFS_IMPLEMENTATION) + // Disallow constructing or copying an object via standard C++ mechanisms, + // e.g. the "new" operator, as this struct is intentionally opaque. Its total + // size and field layout is not part of the public, stable, memory-safe API. + // Use malloc or memcpy and the sizeof__wuffs_foo__bar function instead, and + // call wuffs_foo__bar__baz methods (which all take a "this"-like pointer as + // their first argument) rather than tweaking bar.private_impl.qux fields. + // + // In C, we can just leave wuffs_foo__bar as an incomplete type (unless + // WUFFS_IMPLEMENTATION is #define'd). In C++, we define a complete type in + // order to provide convenience methods. These forward on "this", so that you + // can write "bar->baz(etc)" instead of "wuffs_foo__bar__baz(bar, etc)". + wuffs_lzw__decoder__struct() = delete; + wuffs_lzw__decoder__struct(const wuffs_lzw__decoder__struct&) = delete; + wuffs_lzw__decoder__struct& operator=( + const wuffs_lzw__decoder__struct&) = delete; +#endif // defined(WUFFS_BASE__HAVE_EQ_DELETE) && !defined(WUFFS_IMPLEMENTATION) + +#if !defined(WUFFS_IMPLEMENTATION) + // As above, the size of the struct is not part of the public API, and unless + // WUFFS_IMPLEMENTATION is #define'd, this struct type T should be heap + // allocated, not stack allocated. Its size is not intended to be known at + // compile time, but it is unfortunately divulged as a side effect of + // defining C++ convenience methods. Use "sizeof__T()", calling the function, + // instead of "sizeof T", invoking the operator. To make the two values + // different, so that passing the latter will be rejected by the initialize + // function, we add an arbitrary amount of dead weight. + uint8_t dead_weight[123000000]; // 123 MB. +#endif // !defined(WUFFS_IMPLEMENTATION) + + inline wuffs_base__status WUFFS_BASE__WARN_UNUSED_RESULT + initialize( + size_t sizeof_star_self, + uint64_t wuffs_version, + uint32_t options) { + return wuffs_lzw__decoder__initialize( + this, sizeof_star_self, wuffs_version, options); + } + + inline wuffs_base__io_transformer* + upcast_as__wuffs_base__io_transformer() { + return (wuffs_base__io_transformer*)this; + } + + inline wuffs_base__empty_struct + set_quirk_enabled( + uint32_t a_quirk, + bool a_enabled) { + return wuffs_lzw__decoder__set_quirk_enabled(this, a_quirk, a_enabled); + } + + inline wuffs_base__empty_struct + set_literal_width( + uint32_t a_lw) { + return wuffs_lzw__decoder__set_literal_width(this, a_lw); + } + + inline wuffs_base__range_ii_u64 + workbuf_len() const { + return wuffs_lzw__decoder__workbuf_len(this); + } + + inline wuffs_base__status + transform_io( + wuffs_base__io_buffer* a_dst, + wuffs_base__io_buffer* a_src, + wuffs_base__slice_u8 a_workbuf) { + return wuffs_lzw__decoder__transform_io(this, a_dst, a_src, a_workbuf); + } + + inline wuffs_base__slice_u8 + flush() { + return wuffs_lzw__decoder__flush(this); + } + +#endif // __cplusplus +}; // struct wuffs_lzw__decoder__struct + +#endif // defined(__cplusplus) || defined(WUFFS_IMPLEMENTATION) + +#endif // !defined(WUFFS_CONFIG__MODULES) || defined(WUFFS_CONFIG__MODULE__LZW) || defined(WUFFS_NONMONOLITHIC) + +#if !defined(WUFFS_CONFIG__MODULES) || defined(WUFFS_CONFIG__MODULE__GIF) || defined(WUFFS_NONMONOLITHIC) + +// ---------------- Status Codes + +extern const char wuffs_gif__error__bad_extension_label[]; +extern const char wuffs_gif__error__bad_frame_size[]; +extern const char wuffs_gif__error__bad_graphic_control[]; +extern const char wuffs_gif__error__bad_header[]; +extern const char wuffs_gif__error__bad_literal_width[]; +extern const char wuffs_gif__error__bad_palette[]; +extern const char wuffs_gif__error__truncated_input[]; + +// ---------------- Public Consts + +#define WUFFS_GIF__DECODER_WORKBUF_LEN_MAX_INCL_WORST_CASE 0 + +#define WUFFS_GIF__QUIRK_DELAY_NUM_DECODED_FRAMES 1041635328 + +#define WUFFS_GIF__QUIRK_FIRST_FRAME_LOCAL_PALETTE_MEANS_BLACK_BACKGROUND 1041635329 + +#define WUFFS_GIF__QUIRK_HONOR_BACKGROUND_COLOR 1041635330 + +#define WUFFS_GIF__QUIRK_IGNORE_TOO_MUCH_PIXEL_DATA 1041635331 + +#define WUFFS_GIF__QUIRK_IMAGE_BOUNDS_ARE_STRICT 1041635332 + +#define WUFFS_GIF__QUIRK_REJECT_EMPTY_FRAME 1041635333 + +#define WUFFS_GIF__QUIRK_REJECT_EMPTY_PALETTE 1041635334 + +// ---------------- Struct Declarations + +typedef struct wuffs_gif__decoder__struct wuffs_gif__decoder; + +#ifdef __cplusplus +extern "C" { +#endif + +// ---------------- Public Initializer Prototypes + +// For any given "wuffs_foo__bar* self", "wuffs_foo__bar__initialize(self, +// etc)" should be called before any other "wuffs_foo__bar__xxx(self, etc)". +// +// Pass sizeof(*self) and WUFFS_VERSION for sizeof_star_self and wuffs_version. +// Pass 0 (or some combination of WUFFS_INITIALIZE__XXX) for options. + +wuffs_base__status WUFFS_BASE__WARN_UNUSED_RESULT +wuffs_gif__decoder__initialize( + wuffs_gif__decoder* self, + size_t sizeof_star_self, + uint64_t wuffs_version, + uint32_t options); + +size_t +sizeof__wuffs_gif__decoder(); + +// ---------------- Allocs + +// These functions allocate and initialize Wuffs structs. They return NULL if +// memory allocation fails. If they return non-NULL, there is no need to call +// wuffs_foo__bar__initialize, but the caller is responsible for eventually +// calling free on the returned pointer. That pointer is effectively a C++ +// std::unique_ptr. + +wuffs_gif__decoder* +wuffs_gif__decoder__alloc(); + +static inline wuffs_base__image_decoder* +wuffs_gif__decoder__alloc_as__wuffs_base__image_decoder() { + return (wuffs_base__image_decoder*)(wuffs_gif__decoder__alloc()); +} + +// ---------------- Upcasts + +static inline wuffs_base__image_decoder* +wuffs_gif__decoder__upcast_as__wuffs_base__image_decoder( + wuffs_gif__decoder* p) { + return (wuffs_base__image_decoder*)p; +} + +// ---------------- Public Function Prototypes + +WUFFS_BASE__MAYBE_STATIC wuffs_base__empty_struct +wuffs_gif__decoder__set_quirk_enabled( + wuffs_gif__decoder* self, + uint32_t a_quirk, + bool a_enabled); + +WUFFS_BASE__MAYBE_STATIC wuffs_base__status +wuffs_gif__decoder__decode_image_config( + wuffs_gif__decoder* self, + wuffs_base__image_config* a_dst, + wuffs_base__io_buffer* a_src); + +WUFFS_BASE__MAYBE_STATIC wuffs_base__empty_struct +wuffs_gif__decoder__set_report_metadata( + wuffs_gif__decoder* self, + uint32_t a_fourcc, + bool a_report); + +WUFFS_BASE__MAYBE_STATIC wuffs_base__status +wuffs_gif__decoder__tell_me_more( + wuffs_gif__decoder* self, + wuffs_base__io_buffer* a_dst, + wuffs_base__more_information* a_minfo, + wuffs_base__io_buffer* a_src); + +WUFFS_BASE__MAYBE_STATIC uint32_t +wuffs_gif__decoder__num_animation_loops( + const wuffs_gif__decoder* self); + +WUFFS_BASE__MAYBE_STATIC uint64_t +wuffs_gif__decoder__num_decoded_frame_configs( + const wuffs_gif__decoder* self); + +WUFFS_BASE__MAYBE_STATIC uint64_t +wuffs_gif__decoder__num_decoded_frames( + const wuffs_gif__decoder* self); + +WUFFS_BASE__MAYBE_STATIC wuffs_base__rect_ie_u32 +wuffs_gif__decoder__frame_dirty_rect( + const wuffs_gif__decoder* self); + +WUFFS_BASE__MAYBE_STATIC wuffs_base__range_ii_u64 +wuffs_gif__decoder__workbuf_len( + const wuffs_gif__decoder* self); + +WUFFS_BASE__MAYBE_STATIC wuffs_base__status +wuffs_gif__decoder__restart_frame( + wuffs_gif__decoder* self, + uint64_t a_index, + uint64_t a_io_position); + +WUFFS_BASE__MAYBE_STATIC wuffs_base__status +wuffs_gif__decoder__decode_frame_config( + wuffs_gif__decoder* self, + wuffs_base__frame_config* a_dst, + wuffs_base__io_buffer* a_src); + +WUFFS_BASE__MAYBE_STATIC wuffs_base__status +wuffs_gif__decoder__decode_frame( + wuffs_gif__decoder* self, + wuffs_base__pixel_buffer* a_dst, + wuffs_base__io_buffer* a_src, + wuffs_base__pixel_blend a_blend, + wuffs_base__slice_u8 a_workbuf, + wuffs_base__decode_frame_options* a_opts); + +#ifdef __cplusplus +} // extern "C" +#endif + +// ---------------- Struct Definitions + +// These structs' fields, and the sizeof them, are private implementation +// details that aren't guaranteed to be stable across Wuffs versions. +// +// See https://en.wikipedia.org/wiki/Opaque_pointer#C + +#if defined(__cplusplus) || defined(WUFFS_IMPLEMENTATION) + +struct wuffs_gif__decoder__struct { + // Do not access the private_impl's or private_data's fields directly. There + // is no API/ABI compatibility or safety guarantee if you do so. Instead, use + // the wuffs_foo__bar__baz functions. + // + // It is a struct, not a struct*, so that the outermost wuffs_foo__bar struct + // can be stack allocated when WUFFS_IMPLEMENTATION is defined. + + struct { + uint32_t magic; + uint32_t active_coroutine; + wuffs_base__vtable vtable_for__wuffs_base__image_decoder; + wuffs_base__vtable null_vtable; + + uint32_t f_width; + uint32_t f_height; + uint8_t f_call_sequence; + bool f_report_metadata_iccp; + bool f_report_metadata_xmp; + uint32_t f_metadata_fourcc; + uint64_t f_metadata_io_position; + bool f_quirks[7]; + bool f_delayed_num_decoded_frames; + bool f_previous_lzw_decode_ended_abruptly; + bool f_seen_header; + bool f_has_global_palette; + uint8_t f_interlace; + bool f_seen_num_animation_loops_value; + uint32_t f_num_animation_loops_value; + uint32_t f_background_color_u32_argb_premul; + uint32_t f_black_color_u32_argb_premul; + bool f_gc_has_transparent_index; + uint8_t f_gc_transparent_index; + uint8_t f_gc_disposal; + uint64_t f_gc_duration; + uint64_t f_frame_config_io_position; + uint64_t f_num_decoded_frame_configs_value; + uint64_t f_num_decoded_frames_value; + uint32_t f_frame_rect_x0; + uint32_t f_frame_rect_y0; + uint32_t f_frame_rect_x1; + uint32_t f_frame_rect_y1; + uint32_t f_dst_x; + uint32_t f_dst_y; + uint32_t f_dirty_max_excl_y; + uint64_t f_compressed_ri; + uint64_t f_compressed_wi; + wuffs_base__pixel_swizzler f_swizzler; + + uint32_t p_decode_image_config[1]; + uint32_t p_do_decode_image_config[1]; + uint32_t p_tell_me_more[1]; + uint32_t p_do_tell_me_more[1]; + uint32_t p_decode_frame_config[1]; + uint32_t p_do_decode_frame_config[1]; + uint32_t p_skip_frame[1]; + uint32_t p_decode_frame[1]; + uint32_t p_do_decode_frame[1]; + uint32_t p_decode_up_to_id_part1[1]; + uint32_t p_decode_header[1]; + uint32_t p_decode_lsd[1]; + uint32_t p_decode_extension[1]; + uint32_t p_skip_blocks[1]; + uint32_t p_decode_ae[1]; + uint32_t p_decode_gc[1]; + uint32_t p_decode_id_part0[1]; + uint32_t p_decode_id_part1[1]; + uint32_t p_decode_id_part2[1]; + } private_impl; + + struct { + uint8_t f_compressed[4096]; + uint8_t f_palettes[2][1024]; + uint8_t f_dst_palette[1024]; + wuffs_lzw__decoder f_lzw; + + struct { + uint32_t v_background_color; + } s_do_decode_frame_config[1]; + struct { + uint64_t scratch; + } s_skip_frame[1]; + struct { + uint8_t v_c[6]; + uint32_t v_i; + } s_decode_header[1]; + struct { + uint8_t v_flags; + uint8_t v_background_color_index; + uint32_t v_num_palette_entries; + uint32_t v_i; + uint64_t scratch; + } s_decode_lsd[1]; + struct { + uint64_t scratch; + } s_skip_blocks[1]; + struct { + uint8_t v_block_size; + bool v_is_animexts; + bool v_is_netscape; + bool v_is_iccp; + bool v_is_xmp; + uint64_t scratch; + } s_decode_ae[1]; + struct { + uint64_t scratch; + } s_decode_gc[1]; + struct { + uint64_t scratch; + } s_decode_id_part0[1]; + struct { + uint8_t v_which_palette; + uint32_t v_num_palette_entries; + uint32_t v_i; + uint64_t scratch; + } s_decode_id_part1[1]; + struct { + uint64_t v_block_size; + bool v_need_block_size; + uint64_t scratch; + } s_decode_id_part2[1]; + } private_data; + +#ifdef __cplusplus +#if defined(WUFFS_BASE__HAVE_UNIQUE_PTR) + using unique_ptr = std::unique_ptr; + + // On failure, the alloc_etc functions return nullptr. They don't throw. + + static inline unique_ptr + alloc() { + return unique_ptr(wuffs_gif__decoder__alloc(), &free); + } + + static inline wuffs_base__image_decoder::unique_ptr + alloc_as__wuffs_base__image_decoder() { + return wuffs_base__image_decoder::unique_ptr( + wuffs_gif__decoder__alloc_as__wuffs_base__image_decoder(), &free); + } +#endif // defined(WUFFS_BASE__HAVE_UNIQUE_PTR) + +#if defined(WUFFS_BASE__HAVE_EQ_DELETE) && !defined(WUFFS_IMPLEMENTATION) + // Disallow constructing or copying an object via standard C++ mechanisms, + // e.g. the "new" operator, as this struct is intentionally opaque. Its total + // size and field layout is not part of the public, stable, memory-safe API. + // Use malloc or memcpy and the sizeof__wuffs_foo__bar function instead, and + // call wuffs_foo__bar__baz methods (which all take a "this"-like pointer as + // their first argument) rather than tweaking bar.private_impl.qux fields. + // + // In C, we can just leave wuffs_foo__bar as an incomplete type (unless + // WUFFS_IMPLEMENTATION is #define'd). In C++, we define a complete type in + // order to provide convenience methods. These forward on "this", so that you + // can write "bar->baz(etc)" instead of "wuffs_foo__bar__baz(bar, etc)". + wuffs_gif__decoder__struct() = delete; + wuffs_gif__decoder__struct(const wuffs_gif__decoder__struct&) = delete; + wuffs_gif__decoder__struct& operator=( + const wuffs_gif__decoder__struct&) = delete; +#endif // defined(WUFFS_BASE__HAVE_EQ_DELETE) && !defined(WUFFS_IMPLEMENTATION) + +#if !defined(WUFFS_IMPLEMENTATION) + // As above, the size of the struct is not part of the public API, and unless + // WUFFS_IMPLEMENTATION is #define'd, this struct type T should be heap + // allocated, not stack allocated. Its size is not intended to be known at + // compile time, but it is unfortunately divulged as a side effect of + // defining C++ convenience methods. Use "sizeof__T()", calling the function, + // instead of "sizeof T", invoking the operator. To make the two values + // different, so that passing the latter will be rejected by the initialize + // function, we add an arbitrary amount of dead weight. + uint8_t dead_weight[123000000]; // 123 MB. +#endif // !defined(WUFFS_IMPLEMENTATION) + + inline wuffs_base__status WUFFS_BASE__WARN_UNUSED_RESULT + initialize( + size_t sizeof_star_self, + uint64_t wuffs_version, + uint32_t options) { + return wuffs_gif__decoder__initialize( + this, sizeof_star_self, wuffs_version, options); + } + + inline wuffs_base__image_decoder* + upcast_as__wuffs_base__image_decoder() { + return (wuffs_base__image_decoder*)this; + } + + inline wuffs_base__empty_struct + set_quirk_enabled( + uint32_t a_quirk, + bool a_enabled) { + return wuffs_gif__decoder__set_quirk_enabled(this, a_quirk, a_enabled); + } + + inline wuffs_base__status + decode_image_config( + wuffs_base__image_config* a_dst, + wuffs_base__io_buffer* a_src) { + return wuffs_gif__decoder__decode_image_config(this, a_dst, a_src); + } + + inline wuffs_base__empty_struct + set_report_metadata( + uint32_t a_fourcc, + bool a_report) { + return wuffs_gif__decoder__set_report_metadata(this, a_fourcc, a_report); + } + + inline wuffs_base__status + tell_me_more( + wuffs_base__io_buffer* a_dst, + wuffs_base__more_information* a_minfo, + wuffs_base__io_buffer* a_src) { + return wuffs_gif__decoder__tell_me_more(this, a_dst, a_minfo, a_src); + } + + inline uint32_t + num_animation_loops() const { + return wuffs_gif__decoder__num_animation_loops(this); + } + + inline uint64_t + num_decoded_frame_configs() const { + return wuffs_gif__decoder__num_decoded_frame_configs(this); + } + + inline uint64_t + num_decoded_frames() const { + return wuffs_gif__decoder__num_decoded_frames(this); + } + + inline wuffs_base__rect_ie_u32 + frame_dirty_rect() const { + return wuffs_gif__decoder__frame_dirty_rect(this); + } + + inline wuffs_base__range_ii_u64 + workbuf_len() const { + return wuffs_gif__decoder__workbuf_len(this); + } + + inline wuffs_base__status + restart_frame( + uint64_t a_index, + uint64_t a_io_position) { + return wuffs_gif__decoder__restart_frame(this, a_index, a_io_position); + } + + inline wuffs_base__status + decode_frame_config( + wuffs_base__frame_config* a_dst, + wuffs_base__io_buffer* a_src) { + return wuffs_gif__decoder__decode_frame_config(this, a_dst, a_src); + } + + inline wuffs_base__status + decode_frame( + wuffs_base__pixel_buffer* a_dst, + wuffs_base__io_buffer* a_src, + wuffs_base__pixel_blend a_blend, + wuffs_base__slice_u8 a_workbuf, + wuffs_base__decode_frame_options* a_opts) { + return wuffs_gif__decoder__decode_frame(this, a_dst, a_src, a_blend, a_workbuf, a_opts); + } + +#endif // __cplusplus +}; // struct wuffs_gif__decoder__struct + +#endif // defined(__cplusplus) || defined(WUFFS_IMPLEMENTATION) + +#endif // !defined(WUFFS_CONFIG__MODULES) || defined(WUFFS_CONFIG__MODULE__GIF) || defined(WUFFS_NONMONOLITHIC) + +#if !defined(WUFFS_CONFIG__MODULES) || defined(WUFFS_CONFIG__MODULE__GZIP) || defined(WUFFS_NONMONOLITHIC) + +// ---------------- Status Codes + +extern const char wuffs_gzip__error__bad_checksum[]; +extern const char wuffs_gzip__error__bad_compression_method[]; +extern const char wuffs_gzip__error__bad_encoding_flags[]; +extern const char wuffs_gzip__error__bad_header[]; +extern const char wuffs_gzip__error__truncated_input[]; + +// ---------------- Public Consts + +#define WUFFS_GZIP__DECODER_WORKBUF_LEN_MAX_INCL_WORST_CASE 1 + +// ---------------- Struct Declarations + +typedef struct wuffs_gzip__decoder__struct wuffs_gzip__decoder; + +#ifdef __cplusplus +extern "C" { +#endif + +// ---------------- Public Initializer Prototypes + +// For any given "wuffs_foo__bar* self", "wuffs_foo__bar__initialize(self, +// etc)" should be called before any other "wuffs_foo__bar__xxx(self, etc)". +// +// Pass sizeof(*self) and WUFFS_VERSION for sizeof_star_self and wuffs_version. +// Pass 0 (or some combination of WUFFS_INITIALIZE__XXX) for options. + +wuffs_base__status WUFFS_BASE__WARN_UNUSED_RESULT +wuffs_gzip__decoder__initialize( + wuffs_gzip__decoder* self, + size_t sizeof_star_self, + uint64_t wuffs_version, + uint32_t options); + +size_t +sizeof__wuffs_gzip__decoder(); + +// ---------------- Allocs + +// These functions allocate and initialize Wuffs structs. They return NULL if +// memory allocation fails. If they return non-NULL, there is no need to call +// wuffs_foo__bar__initialize, but the caller is responsible for eventually +// calling free on the returned pointer. That pointer is effectively a C++ +// std::unique_ptr. + +wuffs_gzip__decoder* +wuffs_gzip__decoder__alloc(); + +static inline wuffs_base__io_transformer* +wuffs_gzip__decoder__alloc_as__wuffs_base__io_transformer() { + return (wuffs_base__io_transformer*)(wuffs_gzip__decoder__alloc()); +} + +// ---------------- Upcasts + +static inline wuffs_base__io_transformer* +wuffs_gzip__decoder__upcast_as__wuffs_base__io_transformer( + wuffs_gzip__decoder* p) { + return (wuffs_base__io_transformer*)p; +} + +// ---------------- Public Function Prototypes + +WUFFS_BASE__MAYBE_STATIC wuffs_base__empty_struct +wuffs_gzip__decoder__set_quirk_enabled( + wuffs_gzip__decoder* self, + uint32_t a_quirk, + bool a_enabled); + +WUFFS_BASE__MAYBE_STATIC wuffs_base__range_ii_u64 +wuffs_gzip__decoder__workbuf_len( + const wuffs_gzip__decoder* self); + +WUFFS_BASE__MAYBE_STATIC wuffs_base__status +wuffs_gzip__decoder__transform_io( + wuffs_gzip__decoder* self, + wuffs_base__io_buffer* a_dst, + wuffs_base__io_buffer* a_src, + wuffs_base__slice_u8 a_workbuf); + +#ifdef __cplusplus +} // extern "C" +#endif + +// ---------------- Struct Definitions + +// These structs' fields, and the sizeof them, are private implementation +// details that aren't guaranteed to be stable across Wuffs versions. +// +// See https://en.wikipedia.org/wiki/Opaque_pointer#C + +#if defined(__cplusplus) || defined(WUFFS_IMPLEMENTATION) + +struct wuffs_gzip__decoder__struct { + // Do not access the private_impl's or private_data's fields directly. There + // is no API/ABI compatibility or safety guarantee if you do so. Instead, use + // the wuffs_foo__bar__baz functions. + // + // It is a struct, not a struct*, so that the outermost wuffs_foo__bar struct + // can be stack allocated when WUFFS_IMPLEMENTATION is defined. + + struct { + uint32_t magic; + uint32_t active_coroutine; + wuffs_base__vtable vtable_for__wuffs_base__io_transformer; + wuffs_base__vtable null_vtable; + + bool f_ignore_checksum; + + uint32_t p_transform_io[1]; + uint32_t p_do_transform_io[1]; + } private_impl; + + struct { + wuffs_crc32__ieee_hasher f_checksum; + wuffs_deflate__decoder f_flate; + + struct { + uint8_t v_flags; + uint32_t v_checksum_got; + uint32_t v_decoded_length_got; + uint32_t v_checksum_want; + uint64_t scratch; + } s_do_transform_io[1]; + } private_data; + +#ifdef __cplusplus +#if defined(WUFFS_BASE__HAVE_UNIQUE_PTR) + using unique_ptr = std::unique_ptr; + + // On failure, the alloc_etc functions return nullptr. They don't throw. + + static inline unique_ptr + alloc() { + return unique_ptr(wuffs_gzip__decoder__alloc(), &free); + } + + static inline wuffs_base__io_transformer::unique_ptr + alloc_as__wuffs_base__io_transformer() { + return wuffs_base__io_transformer::unique_ptr( + wuffs_gzip__decoder__alloc_as__wuffs_base__io_transformer(), &free); + } +#endif // defined(WUFFS_BASE__HAVE_UNIQUE_PTR) + +#if defined(WUFFS_BASE__HAVE_EQ_DELETE) && !defined(WUFFS_IMPLEMENTATION) + // Disallow constructing or copying an object via standard C++ mechanisms, + // e.g. the "new" operator, as this struct is intentionally opaque. Its total + // size and field layout is not part of the public, stable, memory-safe API. + // Use malloc or memcpy and the sizeof__wuffs_foo__bar function instead, and + // call wuffs_foo__bar__baz methods (which all take a "this"-like pointer as + // their first argument) rather than tweaking bar.private_impl.qux fields. + // + // In C, we can just leave wuffs_foo__bar as an incomplete type (unless + // WUFFS_IMPLEMENTATION is #define'd). In C++, we define a complete type in + // order to provide convenience methods. These forward on "this", so that you + // can write "bar->baz(etc)" instead of "wuffs_foo__bar__baz(bar, etc)". + wuffs_gzip__decoder__struct() = delete; + wuffs_gzip__decoder__struct(const wuffs_gzip__decoder__struct&) = delete; + wuffs_gzip__decoder__struct& operator=( + const wuffs_gzip__decoder__struct&) = delete; +#endif // defined(WUFFS_BASE__HAVE_EQ_DELETE) && !defined(WUFFS_IMPLEMENTATION) + +#if !defined(WUFFS_IMPLEMENTATION) + // As above, the size of the struct is not part of the public API, and unless + // WUFFS_IMPLEMENTATION is #define'd, this struct type T should be heap + // allocated, not stack allocated. Its size is not intended to be known at + // compile time, but it is unfortunately divulged as a side effect of + // defining C++ convenience methods. Use "sizeof__T()", calling the function, + // instead of "sizeof T", invoking the operator. To make the two values + // different, so that passing the latter will be rejected by the initialize + // function, we add an arbitrary amount of dead weight. + uint8_t dead_weight[123000000]; // 123 MB. +#endif // !defined(WUFFS_IMPLEMENTATION) + + inline wuffs_base__status WUFFS_BASE__WARN_UNUSED_RESULT + initialize( + size_t sizeof_star_self, + uint64_t wuffs_version, + uint32_t options) { + return wuffs_gzip__decoder__initialize( + this, sizeof_star_self, wuffs_version, options); + } + + inline wuffs_base__io_transformer* + upcast_as__wuffs_base__io_transformer() { + return (wuffs_base__io_transformer*)this; + } + + inline wuffs_base__empty_struct + set_quirk_enabled( + uint32_t a_quirk, + bool a_enabled) { + return wuffs_gzip__decoder__set_quirk_enabled(this, a_quirk, a_enabled); + } + + inline wuffs_base__range_ii_u64 + workbuf_len() const { + return wuffs_gzip__decoder__workbuf_len(this); + } + + inline wuffs_base__status + transform_io( + wuffs_base__io_buffer* a_dst, + wuffs_base__io_buffer* a_src, + wuffs_base__slice_u8 a_workbuf) { + return wuffs_gzip__decoder__transform_io(this, a_dst, a_src, a_workbuf); + } + +#endif // __cplusplus +}; // struct wuffs_gzip__decoder__struct + +#endif // defined(__cplusplus) || defined(WUFFS_IMPLEMENTATION) + +#endif // !defined(WUFFS_CONFIG__MODULES) || defined(WUFFS_CONFIG__MODULE__GZIP) || defined(WUFFS_NONMONOLITHIC) + +#if !defined(WUFFS_CONFIG__MODULES) || defined(WUFFS_CONFIG__MODULE__JSON) || defined(WUFFS_NONMONOLITHIC) + +// ---------------- Status Codes + +extern const char wuffs_json__error__bad_c0_control_code[]; +extern const char wuffs_json__error__bad_utf_8[]; +extern const char wuffs_json__error__bad_backslash_escape[]; +extern const char wuffs_json__error__bad_input[]; +extern const char wuffs_json__error__bad_new_line_in_a_string[]; +extern const char wuffs_json__error__bad_quirk_combination[]; +extern const char wuffs_json__error__unsupported_number_length[]; +extern const char wuffs_json__error__unsupported_recursion_depth[]; + +// ---------------- Public Consts + +#define WUFFS_JSON__DECODER_WORKBUF_LEN_MAX_INCL_WORST_CASE 0 + +#define WUFFS_JSON__DECODER_DEPTH_MAX_INCL 1024 + +#define WUFFS_JSON__DECODER_DST_TOKEN_BUFFER_LENGTH_MIN_INCL 1 + +#define WUFFS_JSON__DECODER_SRC_IO_BUFFER_LENGTH_MIN_INCL 100 + +#define WUFFS_JSON__QUIRK_ALLOW_ASCII_CONTROL_CODES 1225364480 + +#define WUFFS_JSON__QUIRK_ALLOW_BACKSLASH_A 1225364481 + +#define WUFFS_JSON__QUIRK_ALLOW_BACKSLASH_CAPITAL_U 1225364482 + +#define WUFFS_JSON__QUIRK_ALLOW_BACKSLASH_E 1225364483 + +#define WUFFS_JSON__QUIRK_ALLOW_BACKSLASH_NEW_LINE 1225364484 + +#define WUFFS_JSON__QUIRK_ALLOW_BACKSLASH_QUESTION_MARK 1225364485 + +#define WUFFS_JSON__QUIRK_ALLOW_BACKSLASH_SINGLE_QUOTE 1225364486 + +#define WUFFS_JSON__QUIRK_ALLOW_BACKSLASH_V 1225364487 + +#define WUFFS_JSON__QUIRK_ALLOW_BACKSLASH_X_AS_CODE_POINTS 1225364489 + +#define WUFFS_JSON__QUIRK_ALLOW_BACKSLASH_ZERO 1225364490 + +#define WUFFS_JSON__QUIRK_ALLOW_COMMENT_BLOCK 1225364491 + +#define WUFFS_JSON__QUIRK_ALLOW_COMMENT_LINE 1225364492 + +#define WUFFS_JSON__QUIRK_ALLOW_EXTRA_COMMA 1225364493 + +#define WUFFS_JSON__QUIRK_ALLOW_INF_NAN_NUMBERS 1225364494 + +#define WUFFS_JSON__QUIRK_ALLOW_LEADING_ASCII_RECORD_SEPARATOR 1225364495 + +#define WUFFS_JSON__QUIRK_ALLOW_LEADING_UNICODE_BYTE_ORDER_MARK 1225364496 + +#define WUFFS_JSON__QUIRK_ALLOW_TRAILING_FILLER 1225364497 + +#define WUFFS_JSON__QUIRK_EXPECT_TRAILING_NEW_LINE_OR_EOF 1225364498 + +#define WUFFS_JSON__QUIRK_JSON_POINTER_ALLOW_TILDE_N_TILDE_R_TILDE_T 1225364499 + +#define WUFFS_JSON__QUIRK_REPLACE_INVALID_UNICODE 1225364500 + +// ---------------- Struct Declarations + +typedef struct wuffs_json__decoder__struct wuffs_json__decoder; + +#ifdef __cplusplus +extern "C" { +#endif + +// ---------------- Public Initializer Prototypes + +// For any given "wuffs_foo__bar* self", "wuffs_foo__bar__initialize(self, +// etc)" should be called before any other "wuffs_foo__bar__xxx(self, etc)". +// +// Pass sizeof(*self) and WUFFS_VERSION for sizeof_star_self and wuffs_version. +// Pass 0 (or some combination of WUFFS_INITIALIZE__XXX) for options. + +wuffs_base__status WUFFS_BASE__WARN_UNUSED_RESULT +wuffs_json__decoder__initialize( + wuffs_json__decoder* self, + size_t sizeof_star_self, + uint64_t wuffs_version, + uint32_t options); + +size_t +sizeof__wuffs_json__decoder(); + +// ---------------- Allocs + +// These functions allocate and initialize Wuffs structs. They return NULL if +// memory allocation fails. If they return non-NULL, there is no need to call +// wuffs_foo__bar__initialize, but the caller is responsible for eventually +// calling free on the returned pointer. That pointer is effectively a C++ +// std::unique_ptr. + +wuffs_json__decoder* +wuffs_json__decoder__alloc(); + +static inline wuffs_base__token_decoder* +wuffs_json__decoder__alloc_as__wuffs_base__token_decoder() { + return (wuffs_base__token_decoder*)(wuffs_json__decoder__alloc()); +} + +// ---------------- Upcasts + +static inline wuffs_base__token_decoder* +wuffs_json__decoder__upcast_as__wuffs_base__token_decoder( + wuffs_json__decoder* p) { + return (wuffs_base__token_decoder*)p; +} + +// ---------------- Public Function Prototypes + +WUFFS_BASE__MAYBE_STATIC wuffs_base__empty_struct +wuffs_json__decoder__set_quirk_enabled( + wuffs_json__decoder* self, + uint32_t a_quirk, + bool a_enabled); + +WUFFS_BASE__MAYBE_STATIC wuffs_base__range_ii_u64 +wuffs_json__decoder__workbuf_len( + const wuffs_json__decoder* self); + +WUFFS_BASE__MAYBE_STATIC wuffs_base__status +wuffs_json__decoder__decode_tokens( + wuffs_json__decoder* self, + wuffs_base__token_buffer* a_dst, + wuffs_base__io_buffer* a_src, + wuffs_base__slice_u8 a_workbuf); + +#ifdef __cplusplus +} // extern "C" +#endif + +// ---------------- Struct Definitions + +// These structs' fields, and the sizeof them, are private implementation +// details that aren't guaranteed to be stable across Wuffs versions. +// +// See https://en.wikipedia.org/wiki/Opaque_pointer#C + +#if defined(__cplusplus) || defined(WUFFS_IMPLEMENTATION) + +struct wuffs_json__decoder__struct { + // Do not access the private_impl's or private_data's fields directly. There + // is no API/ABI compatibility or safety guarantee if you do so. Instead, use + // the wuffs_foo__bar__baz functions. + // + // It is a struct, not a struct*, so that the outermost wuffs_foo__bar struct + // can be stack allocated when WUFFS_IMPLEMENTATION is defined. + + struct { + uint32_t magic; + uint32_t active_coroutine; + wuffs_base__vtable vtable_for__wuffs_base__token_decoder; + wuffs_base__vtable null_vtable; + + bool f_quirks[21]; + bool f_allow_leading_ars; + bool f_allow_leading_ubom; + bool f_end_of_data; + uint8_t f_trailer_stop; + uint8_t f_comment_type; + + uint32_t p_decode_tokens[1]; + uint32_t p_decode_leading[1]; + uint32_t p_decode_comment[1]; + uint32_t p_decode_inf_nan[1]; + uint32_t p_decode_trailer[1]; + } private_impl; + + struct { + uint32_t f_stack[32]; + + struct { + uint32_t v_depth; + uint32_t v_expect; + uint32_t v_expect_after_value; + } s_decode_tokens[1]; + } private_data; + +#ifdef __cplusplus +#if defined(WUFFS_BASE__HAVE_UNIQUE_PTR) + using unique_ptr = std::unique_ptr; + + // On failure, the alloc_etc functions return nullptr. They don't throw. + + static inline unique_ptr + alloc() { + return unique_ptr(wuffs_json__decoder__alloc(), &free); + } + + static inline wuffs_base__token_decoder::unique_ptr + alloc_as__wuffs_base__token_decoder() { + return wuffs_base__token_decoder::unique_ptr( + wuffs_json__decoder__alloc_as__wuffs_base__token_decoder(), &free); + } +#endif // defined(WUFFS_BASE__HAVE_UNIQUE_PTR) + +#if defined(WUFFS_BASE__HAVE_EQ_DELETE) && !defined(WUFFS_IMPLEMENTATION) + // Disallow constructing or copying an object via standard C++ mechanisms, + // e.g. the "new" operator, as this struct is intentionally opaque. Its total + // size and field layout is not part of the public, stable, memory-safe API. + // Use malloc or memcpy and the sizeof__wuffs_foo__bar function instead, and + // call wuffs_foo__bar__baz methods (which all take a "this"-like pointer as + // their first argument) rather than tweaking bar.private_impl.qux fields. + // + // In C, we can just leave wuffs_foo__bar as an incomplete type (unless + // WUFFS_IMPLEMENTATION is #define'd). In C++, we define a complete type in + // order to provide convenience methods. These forward on "this", so that you + // can write "bar->baz(etc)" instead of "wuffs_foo__bar__baz(bar, etc)". + wuffs_json__decoder__struct() = delete; + wuffs_json__decoder__struct(const wuffs_json__decoder__struct&) = delete; + wuffs_json__decoder__struct& operator=( + const wuffs_json__decoder__struct&) = delete; +#endif // defined(WUFFS_BASE__HAVE_EQ_DELETE) && !defined(WUFFS_IMPLEMENTATION) + +#if !defined(WUFFS_IMPLEMENTATION) + // As above, the size of the struct is not part of the public API, and unless + // WUFFS_IMPLEMENTATION is #define'd, this struct type T should be heap + // allocated, not stack allocated. Its size is not intended to be known at + // compile time, but it is unfortunately divulged as a side effect of + // defining C++ convenience methods. Use "sizeof__T()", calling the function, + // instead of "sizeof T", invoking the operator. To make the two values + // different, so that passing the latter will be rejected by the initialize + // function, we add an arbitrary amount of dead weight. + uint8_t dead_weight[123000000]; // 123 MB. +#endif // !defined(WUFFS_IMPLEMENTATION) + + inline wuffs_base__status WUFFS_BASE__WARN_UNUSED_RESULT + initialize( + size_t sizeof_star_self, + uint64_t wuffs_version, + uint32_t options) { + return wuffs_json__decoder__initialize( + this, sizeof_star_self, wuffs_version, options); + } + + inline wuffs_base__token_decoder* + upcast_as__wuffs_base__token_decoder() { + return (wuffs_base__token_decoder*)this; + } + + inline wuffs_base__empty_struct + set_quirk_enabled( + uint32_t a_quirk, + bool a_enabled) { + return wuffs_json__decoder__set_quirk_enabled(this, a_quirk, a_enabled); + } + + inline wuffs_base__range_ii_u64 + workbuf_len() const { + return wuffs_json__decoder__workbuf_len(this); + } + + inline wuffs_base__status + decode_tokens( + wuffs_base__token_buffer* a_dst, + wuffs_base__io_buffer* a_src, + wuffs_base__slice_u8 a_workbuf) { + return wuffs_json__decoder__decode_tokens(this, a_dst, a_src, a_workbuf); + } + +#endif // __cplusplus +}; // struct wuffs_json__decoder__struct + +#endif // defined(__cplusplus) || defined(WUFFS_IMPLEMENTATION) + +#endif // !defined(WUFFS_CONFIG__MODULES) || defined(WUFFS_CONFIG__MODULE__JSON) || defined(WUFFS_NONMONOLITHIC) + +#if !defined(WUFFS_CONFIG__MODULES) || defined(WUFFS_CONFIG__MODULE__NIE) || defined(WUFFS_NONMONOLITHIC) + +// ---------------- Status Codes + +extern const char wuffs_nie__error__bad_header[]; +extern const char wuffs_nie__error__truncated_input[]; +extern const char wuffs_nie__error__unsupported_nie_file[]; + +// ---------------- Public Consts + +#define WUFFS_NIE__DECODER_WORKBUF_LEN_MAX_INCL_WORST_CASE 0 + +// ---------------- Struct Declarations + +typedef struct wuffs_nie__decoder__struct wuffs_nie__decoder; + +#ifdef __cplusplus +extern "C" { +#endif + +// ---------------- Public Initializer Prototypes + +// For any given "wuffs_foo__bar* self", "wuffs_foo__bar__initialize(self, +// etc)" should be called before any other "wuffs_foo__bar__xxx(self, etc)". +// +// Pass sizeof(*self) and WUFFS_VERSION for sizeof_star_self and wuffs_version. +// Pass 0 (or some combination of WUFFS_INITIALIZE__XXX) for options. + +wuffs_base__status WUFFS_BASE__WARN_UNUSED_RESULT +wuffs_nie__decoder__initialize( + wuffs_nie__decoder* self, + size_t sizeof_star_self, + uint64_t wuffs_version, + uint32_t options); + +size_t +sizeof__wuffs_nie__decoder(); + +// ---------------- Allocs + +// These functions allocate and initialize Wuffs structs. They return NULL if +// memory allocation fails. If they return non-NULL, there is no need to call +// wuffs_foo__bar__initialize, but the caller is responsible for eventually +// calling free on the returned pointer. That pointer is effectively a C++ +// std::unique_ptr. + +wuffs_nie__decoder* +wuffs_nie__decoder__alloc(); + +static inline wuffs_base__image_decoder* +wuffs_nie__decoder__alloc_as__wuffs_base__image_decoder() { + return (wuffs_base__image_decoder*)(wuffs_nie__decoder__alloc()); +} + +// ---------------- Upcasts + +static inline wuffs_base__image_decoder* +wuffs_nie__decoder__upcast_as__wuffs_base__image_decoder( + wuffs_nie__decoder* p) { + return (wuffs_base__image_decoder*)p; +} + +// ---------------- Public Function Prototypes + +WUFFS_BASE__MAYBE_STATIC wuffs_base__empty_struct +wuffs_nie__decoder__set_quirk_enabled( + wuffs_nie__decoder* self, + uint32_t a_quirk, + bool a_enabled); + +WUFFS_BASE__MAYBE_STATIC wuffs_base__status +wuffs_nie__decoder__decode_image_config( + wuffs_nie__decoder* self, + wuffs_base__image_config* a_dst, + wuffs_base__io_buffer* a_src); + +WUFFS_BASE__MAYBE_STATIC wuffs_base__status +wuffs_nie__decoder__decode_frame_config( + wuffs_nie__decoder* self, + wuffs_base__frame_config* a_dst, + wuffs_base__io_buffer* a_src); + +WUFFS_BASE__MAYBE_STATIC wuffs_base__status +wuffs_nie__decoder__decode_frame( + wuffs_nie__decoder* self, + wuffs_base__pixel_buffer* a_dst, + wuffs_base__io_buffer* a_src, + wuffs_base__pixel_blend a_blend, + wuffs_base__slice_u8 a_workbuf, + wuffs_base__decode_frame_options* a_opts); + +WUFFS_BASE__MAYBE_STATIC wuffs_base__rect_ie_u32 +wuffs_nie__decoder__frame_dirty_rect( + const wuffs_nie__decoder* self); + +WUFFS_BASE__MAYBE_STATIC uint32_t +wuffs_nie__decoder__num_animation_loops( + const wuffs_nie__decoder* self); + +WUFFS_BASE__MAYBE_STATIC uint64_t +wuffs_nie__decoder__num_decoded_frame_configs( + const wuffs_nie__decoder* self); + +WUFFS_BASE__MAYBE_STATIC uint64_t +wuffs_nie__decoder__num_decoded_frames( + const wuffs_nie__decoder* self); + +WUFFS_BASE__MAYBE_STATIC wuffs_base__status +wuffs_nie__decoder__restart_frame( + wuffs_nie__decoder* self, + uint64_t a_index, + uint64_t a_io_position); + +WUFFS_BASE__MAYBE_STATIC wuffs_base__empty_struct +wuffs_nie__decoder__set_report_metadata( + wuffs_nie__decoder* self, + uint32_t a_fourcc, + bool a_report); + +WUFFS_BASE__MAYBE_STATIC wuffs_base__status +wuffs_nie__decoder__tell_me_more( + wuffs_nie__decoder* self, + wuffs_base__io_buffer* a_dst, + wuffs_base__more_information* a_minfo, + wuffs_base__io_buffer* a_src); + +WUFFS_BASE__MAYBE_STATIC wuffs_base__range_ii_u64 +wuffs_nie__decoder__workbuf_len( + const wuffs_nie__decoder* self); + +#ifdef __cplusplus +} // extern "C" +#endif + +// ---------------- Struct Definitions + +// These structs' fields, and the sizeof them, are private implementation +// details that aren't guaranteed to be stable across Wuffs versions. +// +// See https://en.wikipedia.org/wiki/Opaque_pointer#C + +#if defined(__cplusplus) || defined(WUFFS_IMPLEMENTATION) + +struct wuffs_nie__decoder__struct { + // Do not access the private_impl's or private_data's fields directly. There + // is no API/ABI compatibility or safety guarantee if you do so. Instead, use + // the wuffs_foo__bar__baz functions. + // + // It is a struct, not a struct*, so that the outermost wuffs_foo__bar struct + // can be stack allocated when WUFFS_IMPLEMENTATION is defined. + + struct { + uint32_t magic; + uint32_t active_coroutine; + wuffs_base__vtable vtable_for__wuffs_base__image_decoder; + wuffs_base__vtable null_vtable; + + uint32_t f_pixfmt; + uint32_t f_width; + uint32_t f_height; + uint8_t f_call_sequence; + uint32_t f_dst_x; + uint32_t f_dst_y; + wuffs_base__pixel_swizzler f_swizzler; + + uint32_t p_decode_image_config[1]; + uint32_t p_do_decode_image_config[1]; + uint32_t p_decode_frame_config[1]; + uint32_t p_do_decode_frame_config[1]; + uint32_t p_decode_frame[1]; + uint32_t p_do_decode_frame[1]; + } private_impl; + + struct { + struct { + uint64_t scratch; + } s_do_decode_image_config[1]; + } private_data; + +#ifdef __cplusplus +#if defined(WUFFS_BASE__HAVE_UNIQUE_PTR) + using unique_ptr = std::unique_ptr; + + // On failure, the alloc_etc functions return nullptr. They don't throw. + + static inline unique_ptr + alloc() { + return unique_ptr(wuffs_nie__decoder__alloc(), &free); + } + + static inline wuffs_base__image_decoder::unique_ptr + alloc_as__wuffs_base__image_decoder() { + return wuffs_base__image_decoder::unique_ptr( + wuffs_nie__decoder__alloc_as__wuffs_base__image_decoder(), &free); + } +#endif // defined(WUFFS_BASE__HAVE_UNIQUE_PTR) + +#if defined(WUFFS_BASE__HAVE_EQ_DELETE) && !defined(WUFFS_IMPLEMENTATION) + // Disallow constructing or copying an object via standard C++ mechanisms, + // e.g. the "new" operator, as this struct is intentionally opaque. Its total + // size and field layout is not part of the public, stable, memory-safe API. + // Use malloc or memcpy and the sizeof__wuffs_foo__bar function instead, and + // call wuffs_foo__bar__baz methods (which all take a "this"-like pointer as + // their first argument) rather than tweaking bar.private_impl.qux fields. + // + // In C, we can just leave wuffs_foo__bar as an incomplete type (unless + // WUFFS_IMPLEMENTATION is #define'd). In C++, we define a complete type in + // order to provide convenience methods. These forward on "this", so that you + // can write "bar->baz(etc)" instead of "wuffs_foo__bar__baz(bar, etc)". + wuffs_nie__decoder__struct() = delete; + wuffs_nie__decoder__struct(const wuffs_nie__decoder__struct&) = delete; + wuffs_nie__decoder__struct& operator=( + const wuffs_nie__decoder__struct&) = delete; +#endif // defined(WUFFS_BASE__HAVE_EQ_DELETE) && !defined(WUFFS_IMPLEMENTATION) + +#if !defined(WUFFS_IMPLEMENTATION) + // As above, the size of the struct is not part of the public API, and unless + // WUFFS_IMPLEMENTATION is #define'd, this struct type T should be heap + // allocated, not stack allocated. Its size is not intended to be known at + // compile time, but it is unfortunately divulged as a side effect of + // defining C++ convenience methods. Use "sizeof__T()", calling the function, + // instead of "sizeof T", invoking the operator. To make the two values + // different, so that passing the latter will be rejected by the initialize + // function, we add an arbitrary amount of dead weight. + uint8_t dead_weight[123000000]; // 123 MB. +#endif // !defined(WUFFS_IMPLEMENTATION) + + inline wuffs_base__status WUFFS_BASE__WARN_UNUSED_RESULT + initialize( + size_t sizeof_star_self, + uint64_t wuffs_version, + uint32_t options) { + return wuffs_nie__decoder__initialize( + this, sizeof_star_self, wuffs_version, options); + } + + inline wuffs_base__image_decoder* + upcast_as__wuffs_base__image_decoder() { + return (wuffs_base__image_decoder*)this; + } + + inline wuffs_base__empty_struct + set_quirk_enabled( + uint32_t a_quirk, + bool a_enabled) { + return wuffs_nie__decoder__set_quirk_enabled(this, a_quirk, a_enabled); + } + + inline wuffs_base__status + decode_image_config( + wuffs_base__image_config* a_dst, + wuffs_base__io_buffer* a_src) { + return wuffs_nie__decoder__decode_image_config(this, a_dst, a_src); + } + + inline wuffs_base__status + decode_frame_config( + wuffs_base__frame_config* a_dst, + wuffs_base__io_buffer* a_src) { + return wuffs_nie__decoder__decode_frame_config(this, a_dst, a_src); + } + + inline wuffs_base__status + decode_frame( + wuffs_base__pixel_buffer* a_dst, + wuffs_base__io_buffer* a_src, + wuffs_base__pixel_blend a_blend, + wuffs_base__slice_u8 a_workbuf, + wuffs_base__decode_frame_options* a_opts) { + return wuffs_nie__decoder__decode_frame(this, a_dst, a_src, a_blend, a_workbuf, a_opts); + } + + inline wuffs_base__rect_ie_u32 + frame_dirty_rect() const { + return wuffs_nie__decoder__frame_dirty_rect(this); + } + + inline uint32_t + num_animation_loops() const { + return wuffs_nie__decoder__num_animation_loops(this); + } + + inline uint64_t + num_decoded_frame_configs() const { + return wuffs_nie__decoder__num_decoded_frame_configs(this); + } + + inline uint64_t + num_decoded_frames() const { + return wuffs_nie__decoder__num_decoded_frames(this); + } + + inline wuffs_base__status + restart_frame( + uint64_t a_index, + uint64_t a_io_position) { + return wuffs_nie__decoder__restart_frame(this, a_index, a_io_position); + } + + inline wuffs_base__empty_struct + set_report_metadata( + uint32_t a_fourcc, + bool a_report) { + return wuffs_nie__decoder__set_report_metadata(this, a_fourcc, a_report); + } + + inline wuffs_base__status + tell_me_more( + wuffs_base__io_buffer* a_dst, + wuffs_base__more_information* a_minfo, + wuffs_base__io_buffer* a_src) { + return wuffs_nie__decoder__tell_me_more(this, a_dst, a_minfo, a_src); + } + + inline wuffs_base__range_ii_u64 + workbuf_len() const { + return wuffs_nie__decoder__workbuf_len(this); + } + +#endif // __cplusplus +}; // struct wuffs_nie__decoder__struct + +#endif // defined(__cplusplus) || defined(WUFFS_IMPLEMENTATION) + +#endif // !defined(WUFFS_CONFIG__MODULES) || defined(WUFFS_CONFIG__MODULE__NIE) || defined(WUFFS_NONMONOLITHIC) + +#if !defined(WUFFS_CONFIG__MODULES) || defined(WUFFS_CONFIG__MODULE__ZLIB) || defined(WUFFS_NONMONOLITHIC) + +// ---------------- Status Codes + +extern const char wuffs_zlib__note__dictionary_required[]; +extern const char wuffs_zlib__error__bad_checksum[]; +extern const char wuffs_zlib__error__bad_compression_method[]; +extern const char wuffs_zlib__error__bad_compression_window_size[]; +extern const char wuffs_zlib__error__bad_parity_check[]; +extern const char wuffs_zlib__error__incorrect_dictionary[]; +extern const char wuffs_zlib__error__truncated_input[]; + +// ---------------- Public Consts + +#define WUFFS_ZLIB__QUIRK_JUST_RAW_DEFLATE 2113790976 + +#define WUFFS_ZLIB__DECODER_WORKBUF_LEN_MAX_INCL_WORST_CASE 1 + +// ---------------- Struct Declarations + +typedef struct wuffs_zlib__decoder__struct wuffs_zlib__decoder; + +#ifdef __cplusplus +extern "C" { +#endif + +// ---------------- Public Initializer Prototypes + +// For any given "wuffs_foo__bar* self", "wuffs_foo__bar__initialize(self, +// etc)" should be called before any other "wuffs_foo__bar__xxx(self, etc)". +// +// Pass sizeof(*self) and WUFFS_VERSION for sizeof_star_self and wuffs_version. +// Pass 0 (or some combination of WUFFS_INITIALIZE__XXX) for options. + +wuffs_base__status WUFFS_BASE__WARN_UNUSED_RESULT +wuffs_zlib__decoder__initialize( + wuffs_zlib__decoder* self, + size_t sizeof_star_self, + uint64_t wuffs_version, + uint32_t options); + +size_t +sizeof__wuffs_zlib__decoder(); + +// ---------------- Allocs + +// These functions allocate and initialize Wuffs structs. They return NULL if +// memory allocation fails. If they return non-NULL, there is no need to call +// wuffs_foo__bar__initialize, but the caller is responsible for eventually +// calling free on the returned pointer. That pointer is effectively a C++ +// std::unique_ptr. + +wuffs_zlib__decoder* +wuffs_zlib__decoder__alloc(); + +static inline wuffs_base__io_transformer* +wuffs_zlib__decoder__alloc_as__wuffs_base__io_transformer() { + return (wuffs_base__io_transformer*)(wuffs_zlib__decoder__alloc()); +} + +// ---------------- Upcasts + +static inline wuffs_base__io_transformer* +wuffs_zlib__decoder__upcast_as__wuffs_base__io_transformer( + wuffs_zlib__decoder* p) { + return (wuffs_base__io_transformer*)p; +} + +// ---------------- Public Function Prototypes + +WUFFS_BASE__MAYBE_STATIC uint32_t +wuffs_zlib__decoder__dictionary_id( + const wuffs_zlib__decoder* self); + +WUFFS_BASE__MAYBE_STATIC wuffs_base__empty_struct +wuffs_zlib__decoder__add_dictionary( + wuffs_zlib__decoder* self, + wuffs_base__slice_u8 a_dict); + +WUFFS_BASE__MAYBE_STATIC wuffs_base__empty_struct +wuffs_zlib__decoder__set_quirk_enabled( + wuffs_zlib__decoder* self, + uint32_t a_quirk, + bool a_enabled); + +WUFFS_BASE__MAYBE_STATIC wuffs_base__range_ii_u64 +wuffs_zlib__decoder__workbuf_len( + const wuffs_zlib__decoder* self); + +WUFFS_BASE__MAYBE_STATIC wuffs_base__status +wuffs_zlib__decoder__transform_io( + wuffs_zlib__decoder* self, + wuffs_base__io_buffer* a_dst, + wuffs_base__io_buffer* a_src, + wuffs_base__slice_u8 a_workbuf); + +#ifdef __cplusplus +} // extern "C" +#endif + +// ---------------- Struct Definitions + +// These structs' fields, and the sizeof them, are private implementation +// details that aren't guaranteed to be stable across Wuffs versions. +// +// See https://en.wikipedia.org/wiki/Opaque_pointer#C + +#if defined(__cplusplus) || defined(WUFFS_IMPLEMENTATION) + +struct wuffs_zlib__decoder__struct { + // Do not access the private_impl's or private_data's fields directly. There + // is no API/ABI compatibility or safety guarantee if you do so. Instead, use + // the wuffs_foo__bar__baz functions. + // + // It is a struct, not a struct*, so that the outermost wuffs_foo__bar struct + // can be stack allocated when WUFFS_IMPLEMENTATION is defined. + + struct { + uint32_t magic; + uint32_t active_coroutine; + wuffs_base__vtable vtable_for__wuffs_base__io_transformer; + wuffs_base__vtable null_vtable; + + bool f_bad_call_sequence; + bool f_header_complete; + bool f_got_dictionary; + bool f_want_dictionary; + bool f_quirks[1]; + bool f_ignore_checksum; + uint32_t f_dict_id_got; + uint32_t f_dict_id_want; + + uint32_t p_transform_io[1]; + uint32_t p_do_transform_io[1]; + } private_impl; + + struct { + wuffs_adler32__hasher f_checksum; + wuffs_adler32__hasher f_dict_id_hasher; + wuffs_deflate__decoder f_flate; + + struct { + uint32_t v_checksum_got; + uint64_t scratch; + } s_do_transform_io[1]; + } private_data; + +#ifdef __cplusplus +#if defined(WUFFS_BASE__HAVE_UNIQUE_PTR) + using unique_ptr = std::unique_ptr; + + // On failure, the alloc_etc functions return nullptr. They don't throw. + + static inline unique_ptr + alloc() { + return unique_ptr(wuffs_zlib__decoder__alloc(), &free); + } + + static inline wuffs_base__io_transformer::unique_ptr + alloc_as__wuffs_base__io_transformer() { + return wuffs_base__io_transformer::unique_ptr( + wuffs_zlib__decoder__alloc_as__wuffs_base__io_transformer(), &free); + } +#endif // defined(WUFFS_BASE__HAVE_UNIQUE_PTR) + +#if defined(WUFFS_BASE__HAVE_EQ_DELETE) && !defined(WUFFS_IMPLEMENTATION) + // Disallow constructing or copying an object via standard C++ mechanisms, + // e.g. the "new" operator, as this struct is intentionally opaque. Its total + // size and field layout is not part of the public, stable, memory-safe API. + // Use malloc or memcpy and the sizeof__wuffs_foo__bar function instead, and + // call wuffs_foo__bar__baz methods (which all take a "this"-like pointer as + // their first argument) rather than tweaking bar.private_impl.qux fields. + // + // In C, we can just leave wuffs_foo__bar as an incomplete type (unless + // WUFFS_IMPLEMENTATION is #define'd). In C++, we define a complete type in + // order to provide convenience methods. These forward on "this", so that you + // can write "bar->baz(etc)" instead of "wuffs_foo__bar__baz(bar, etc)". + wuffs_zlib__decoder__struct() = delete; + wuffs_zlib__decoder__struct(const wuffs_zlib__decoder__struct&) = delete; + wuffs_zlib__decoder__struct& operator=( + const wuffs_zlib__decoder__struct&) = delete; +#endif // defined(WUFFS_BASE__HAVE_EQ_DELETE) && !defined(WUFFS_IMPLEMENTATION) + +#if !defined(WUFFS_IMPLEMENTATION) + // As above, the size of the struct is not part of the public API, and unless + // WUFFS_IMPLEMENTATION is #define'd, this struct type T should be heap + // allocated, not stack allocated. Its size is not intended to be known at + // compile time, but it is unfortunately divulged as a side effect of + // defining C++ convenience methods. Use "sizeof__T()", calling the function, + // instead of "sizeof T", invoking the operator. To make the two values + // different, so that passing the latter will be rejected by the initialize + // function, we add an arbitrary amount of dead weight. + uint8_t dead_weight[123000000]; // 123 MB. +#endif // !defined(WUFFS_IMPLEMENTATION) + + inline wuffs_base__status WUFFS_BASE__WARN_UNUSED_RESULT + initialize( + size_t sizeof_star_self, + uint64_t wuffs_version, + uint32_t options) { + return wuffs_zlib__decoder__initialize( + this, sizeof_star_self, wuffs_version, options); + } + + inline wuffs_base__io_transformer* + upcast_as__wuffs_base__io_transformer() { + return (wuffs_base__io_transformer*)this; + } + + inline uint32_t + dictionary_id() const { + return wuffs_zlib__decoder__dictionary_id(this); + } + + inline wuffs_base__empty_struct + add_dictionary( + wuffs_base__slice_u8 a_dict) { + return wuffs_zlib__decoder__add_dictionary(this, a_dict); + } + + inline wuffs_base__empty_struct + set_quirk_enabled( + uint32_t a_quirk, + bool a_enabled) { + return wuffs_zlib__decoder__set_quirk_enabled(this, a_quirk, a_enabled); + } + + inline wuffs_base__range_ii_u64 + workbuf_len() const { + return wuffs_zlib__decoder__workbuf_len(this); + } + + inline wuffs_base__status + transform_io( + wuffs_base__io_buffer* a_dst, + wuffs_base__io_buffer* a_src, + wuffs_base__slice_u8 a_workbuf) { + return wuffs_zlib__decoder__transform_io(this, a_dst, a_src, a_workbuf); + } + +#endif // __cplusplus +}; // struct wuffs_zlib__decoder__struct + +#endif // defined(__cplusplus) || defined(WUFFS_IMPLEMENTATION) + +#endif // !defined(WUFFS_CONFIG__MODULES) || defined(WUFFS_CONFIG__MODULE__ZLIB) || defined(WUFFS_NONMONOLITHIC) + +#if !defined(WUFFS_CONFIG__MODULES) || defined(WUFFS_CONFIG__MODULE__PNG) || defined(WUFFS_NONMONOLITHIC) + +// ---------------- Status Codes + +extern const char wuffs_png__error__bad_animation_sequence_number[]; +extern const char wuffs_png__error__bad_checksum[]; +extern const char wuffs_png__error__bad_chunk[]; +extern const char wuffs_png__error__bad_filter[]; +extern const char wuffs_png__error__bad_header[]; +extern const char wuffs_png__error__bad_text_chunk_not_latin_1[]; +extern const char wuffs_png__error__missing_palette[]; +extern const char wuffs_png__error__truncated_input[]; +extern const char wuffs_png__error__unsupported_cgbi_extension[]; +extern const char wuffs_png__error__unsupported_png_compression_method[]; +extern const char wuffs_png__error__unsupported_png_file[]; + +// ---------------- Public Consts + +#define WUFFS_PNG__DECODER_WORKBUF_LEN_MAX_INCL_WORST_CASE 2251799562027015 + +#define WUFFS_PNG__DECODER_SRC_IO_BUFFER_LENGTH_MIN_INCL 8 + +// ---------------- Struct Declarations + +typedef struct wuffs_png__decoder__struct wuffs_png__decoder; + +#ifdef __cplusplus +extern "C" { +#endif + +// ---------------- Public Initializer Prototypes + +// For any given "wuffs_foo__bar* self", "wuffs_foo__bar__initialize(self, +// etc)" should be called before any other "wuffs_foo__bar__xxx(self, etc)". +// +// Pass sizeof(*self) and WUFFS_VERSION for sizeof_star_self and wuffs_version. +// Pass 0 (or some combination of WUFFS_INITIALIZE__XXX) for options. + +wuffs_base__status WUFFS_BASE__WARN_UNUSED_RESULT +wuffs_png__decoder__initialize( + wuffs_png__decoder* self, + size_t sizeof_star_self, + uint64_t wuffs_version, + uint32_t options); + +size_t +sizeof__wuffs_png__decoder(); + +// ---------------- Allocs + +// These functions allocate and initialize Wuffs structs. They return NULL if +// memory allocation fails. If they return non-NULL, there is no need to call +// wuffs_foo__bar__initialize, but the caller is responsible for eventually +// calling free on the returned pointer. That pointer is effectively a C++ +// std::unique_ptr. + +wuffs_png__decoder* +wuffs_png__decoder__alloc(); + +static inline wuffs_base__image_decoder* +wuffs_png__decoder__alloc_as__wuffs_base__image_decoder() { + return (wuffs_base__image_decoder*)(wuffs_png__decoder__alloc()); +} + +// ---------------- Upcasts + +static inline wuffs_base__image_decoder* +wuffs_png__decoder__upcast_as__wuffs_base__image_decoder( + wuffs_png__decoder* p) { + return (wuffs_base__image_decoder*)p; +} + +// ---------------- Public Function Prototypes + +WUFFS_BASE__MAYBE_STATIC wuffs_base__empty_struct +wuffs_png__decoder__set_quirk_enabled( + wuffs_png__decoder* self, + uint32_t a_quirk, + bool a_enabled); + +WUFFS_BASE__MAYBE_STATIC wuffs_base__status +wuffs_png__decoder__decode_image_config( + wuffs_png__decoder* self, + wuffs_base__image_config* a_dst, + wuffs_base__io_buffer* a_src); + +WUFFS_BASE__MAYBE_STATIC wuffs_base__status +wuffs_png__decoder__decode_frame_config( + wuffs_png__decoder* self, + wuffs_base__frame_config* a_dst, + wuffs_base__io_buffer* a_src); + +WUFFS_BASE__MAYBE_STATIC wuffs_base__status +wuffs_png__decoder__decode_frame( + wuffs_png__decoder* self, + wuffs_base__pixel_buffer* a_dst, + wuffs_base__io_buffer* a_src, + wuffs_base__pixel_blend a_blend, + wuffs_base__slice_u8 a_workbuf, + wuffs_base__decode_frame_options* a_opts); + +WUFFS_BASE__MAYBE_STATIC wuffs_base__rect_ie_u32 +wuffs_png__decoder__frame_dirty_rect( + const wuffs_png__decoder* self); + +WUFFS_BASE__MAYBE_STATIC uint32_t +wuffs_png__decoder__num_animation_loops( + const wuffs_png__decoder* self); + +WUFFS_BASE__MAYBE_STATIC uint64_t +wuffs_png__decoder__num_decoded_frame_configs( + const wuffs_png__decoder* self); + +WUFFS_BASE__MAYBE_STATIC uint64_t +wuffs_png__decoder__num_decoded_frames( + const wuffs_png__decoder* self); + +WUFFS_BASE__MAYBE_STATIC wuffs_base__status +wuffs_png__decoder__restart_frame( + wuffs_png__decoder* self, + uint64_t a_index, + uint64_t a_io_position); + +WUFFS_BASE__MAYBE_STATIC wuffs_base__empty_struct +wuffs_png__decoder__set_report_metadata( + wuffs_png__decoder* self, + uint32_t a_fourcc, + bool a_report); + +WUFFS_BASE__MAYBE_STATIC wuffs_base__status +wuffs_png__decoder__tell_me_more( + wuffs_png__decoder* self, + wuffs_base__io_buffer* a_dst, + wuffs_base__more_information* a_minfo, + wuffs_base__io_buffer* a_src); + +WUFFS_BASE__MAYBE_STATIC wuffs_base__range_ii_u64 +wuffs_png__decoder__workbuf_len( + const wuffs_png__decoder* self); + +#ifdef __cplusplus +} // extern "C" +#endif + +// ---------------- Struct Definitions + +// These structs' fields, and the sizeof them, are private implementation +// details that aren't guaranteed to be stable across Wuffs versions. +// +// See https://en.wikipedia.org/wiki/Opaque_pointer#C + +#if defined(__cplusplus) || defined(WUFFS_IMPLEMENTATION) + +struct wuffs_png__decoder__struct { + // Do not access the private_impl's or private_data's fields directly. There + // is no API/ABI compatibility or safety guarantee if you do so. Instead, use + // the wuffs_foo__bar__baz functions. + // + // It is a struct, not a struct*, so that the outermost wuffs_foo__bar struct + // can be stack allocated when WUFFS_IMPLEMENTATION is defined. + + struct { + uint32_t magic; + uint32_t active_coroutine; + wuffs_base__vtable vtable_for__wuffs_base__image_decoder; + wuffs_base__vtable null_vtable; + + uint32_t f_width; + uint32_t f_height; + uint64_t f_pass_bytes_per_row; + uint64_t f_workbuf_wi; + uint64_t f_workbuf_hist_pos_base; + uint64_t f_overall_workbuf_length; + uint64_t f_pass_workbuf_length; + uint8_t f_call_sequence; + bool f_report_metadata_chrm; + bool f_report_metadata_exif; + bool f_report_metadata_gama; + bool f_report_metadata_iccp; + bool f_report_metadata_kvp; + bool f_report_metadata_srgb; + bool f_ignore_checksum; + uint8_t f_depth; + uint8_t f_color_type; + uint8_t f_filter_distance; + uint8_t f_interlace_pass; + bool f_seen_actl; + bool f_seen_chrm; + bool f_seen_fctl; + bool f_seen_exif; + bool f_seen_gama; + bool f_seen_iccp; + bool f_seen_idat; + bool f_seen_ihdr; + bool f_seen_plte; + bool f_seen_srgb; + bool f_seen_trns; + bool f_metadata_is_zlib_compressed; + bool f_zlib_is_dirty; + uint32_t f_chunk_type; + uint8_t f_chunk_type_array[4]; + uint32_t f_chunk_length; + uint64_t f_remap_transparency; + uint32_t f_dst_pixfmt; + uint32_t f_src_pixfmt; + uint32_t f_num_animation_frames_value; + uint32_t f_num_animation_loops_value; + uint32_t f_num_decoded_frame_configs_value; + uint32_t f_num_decoded_frames_value; + uint32_t f_frame_rect_x0; + uint32_t f_frame_rect_y0; + uint32_t f_frame_rect_x1; + uint32_t f_frame_rect_y1; + uint32_t f_first_rect_x0; + uint32_t f_first_rect_y0; + uint32_t f_first_rect_x1; + uint32_t f_first_rect_y1; + uint64_t f_frame_config_io_position; + uint64_t f_first_config_io_position; + uint64_t f_frame_duration; + uint64_t f_first_duration; + uint8_t f_frame_disposal; + uint8_t f_first_disposal; + bool f_frame_overwrite_instead_of_blend; + bool f_first_overwrite_instead_of_blend; + uint32_t f_next_animation_seq_num; + uint32_t f_metadata_flavor; + uint32_t f_metadata_fourcc; + uint64_t f_metadata_x; + uint64_t f_metadata_y; + uint64_t f_metadata_z; + uint32_t f_ztxt_ri; + uint32_t f_ztxt_wi; + uint64_t f_ztxt_hist_pos; + wuffs_base__pixel_swizzler f_swizzler; + + wuffs_base__empty_struct (*choosy_filter_1)( + wuffs_png__decoder* self, + wuffs_base__slice_u8 a_curr); + wuffs_base__empty_struct (*choosy_filter_3)( + wuffs_png__decoder* self, + wuffs_base__slice_u8 a_curr, + wuffs_base__slice_u8 a_prev); + wuffs_base__empty_struct (*choosy_filter_4)( + wuffs_png__decoder* self, + wuffs_base__slice_u8 a_curr, + wuffs_base__slice_u8 a_prev); + uint32_t p_decode_image_config[1]; + uint32_t p_do_decode_image_config[1]; + uint32_t p_decode_ihdr[1]; + uint32_t p_decode_other_chunk[1]; + uint32_t p_decode_actl[1]; + uint32_t p_decode_chrm[1]; + uint32_t p_decode_fctl[1]; + uint32_t p_decode_gama[1]; + uint32_t p_decode_iccp[1]; + uint32_t p_decode_plte[1]; + uint32_t p_decode_srgb[1]; + uint32_t p_decode_trns[1]; + uint32_t p_decode_frame_config[1]; + uint32_t p_do_decode_frame_config[1]; + uint32_t p_skip_frame[1]; + uint32_t p_decode_frame[1]; + uint32_t p_do_decode_frame[1]; + uint32_t p_decode_pass[1]; + uint32_t p_tell_me_more[1]; + uint32_t p_do_tell_me_more[1]; + wuffs_base__status (*choosy_filter_and_swizzle)( + wuffs_png__decoder* self, + wuffs_base__pixel_buffer* a_dst, + wuffs_base__slice_u8 a_workbuf); + } private_impl; + + struct { + wuffs_crc32__ieee_hasher f_crc32; + wuffs_zlib__decoder f_zlib; + uint8_t f_dst_palette[1024]; + uint8_t f_src_palette[1024]; + + struct { + uint32_t v_checksum_have; + uint64_t scratch; + } s_do_decode_image_config[1]; + struct { + uint64_t scratch; + } s_decode_ihdr[1]; + struct { + uint64_t scratch; + } s_decode_other_chunk[1]; + struct { + uint64_t scratch; + } s_decode_actl[1]; + struct { + uint64_t scratch; + } s_decode_chrm[1]; + struct { + uint32_t v_x0; + uint32_t v_x1; + uint32_t v_y1; + uint64_t scratch; + } s_decode_fctl[1]; + struct { + uint64_t scratch; + } s_decode_gama[1]; + struct { + uint32_t v_num_entries; + uint32_t v_i; + uint64_t scratch; + } s_decode_plte[1]; + struct { + uint32_t v_i; + uint32_t v_n; + uint64_t scratch; + } s_decode_trns[1]; + struct { + uint64_t scratch; + } s_do_decode_frame_config[1]; + struct { + uint64_t scratch; + } s_skip_frame[1]; + struct { + uint64_t scratch; + } s_do_decode_frame[1]; + struct { + uint64_t scratch; + } s_decode_pass[1]; + struct { + wuffs_base__status v_zlib_status; + uint64_t scratch; + } s_do_tell_me_more[1]; + } private_data; + +#ifdef __cplusplus +#if defined(WUFFS_BASE__HAVE_UNIQUE_PTR) + using unique_ptr = std::unique_ptr; + + // On failure, the alloc_etc functions return nullptr. They don't throw. + + static inline unique_ptr + alloc() { + return unique_ptr(wuffs_png__decoder__alloc(), &free); + } + + static inline wuffs_base__image_decoder::unique_ptr + alloc_as__wuffs_base__image_decoder() { + return wuffs_base__image_decoder::unique_ptr( + wuffs_png__decoder__alloc_as__wuffs_base__image_decoder(), &free); + } +#endif // defined(WUFFS_BASE__HAVE_UNIQUE_PTR) + +#if defined(WUFFS_BASE__HAVE_EQ_DELETE) && !defined(WUFFS_IMPLEMENTATION) + // Disallow constructing or copying an object via standard C++ mechanisms, + // e.g. the "new" operator, as this struct is intentionally opaque. Its total + // size and field layout is not part of the public, stable, memory-safe API. + // Use malloc or memcpy and the sizeof__wuffs_foo__bar function instead, and + // call wuffs_foo__bar__baz methods (which all take a "this"-like pointer as + // their first argument) rather than tweaking bar.private_impl.qux fields. + // + // In C, we can just leave wuffs_foo__bar as an incomplete type (unless + // WUFFS_IMPLEMENTATION is #define'd). In C++, we define a complete type in + // order to provide convenience methods. These forward on "this", so that you + // can write "bar->baz(etc)" instead of "wuffs_foo__bar__baz(bar, etc)". + wuffs_png__decoder__struct() = delete; + wuffs_png__decoder__struct(const wuffs_png__decoder__struct&) = delete; + wuffs_png__decoder__struct& operator=( + const wuffs_png__decoder__struct&) = delete; +#endif // defined(WUFFS_BASE__HAVE_EQ_DELETE) && !defined(WUFFS_IMPLEMENTATION) + +#if !defined(WUFFS_IMPLEMENTATION) + // As above, the size of the struct is not part of the public API, and unless + // WUFFS_IMPLEMENTATION is #define'd, this struct type T should be heap + // allocated, not stack allocated. Its size is not intended to be known at + // compile time, but it is unfortunately divulged as a side effect of + // defining C++ convenience methods. Use "sizeof__T()", calling the function, + // instead of "sizeof T", invoking the operator. To make the two values + // different, so that passing the latter will be rejected by the initialize + // function, we add an arbitrary amount of dead weight. + uint8_t dead_weight[123000000]; // 123 MB. +#endif // !defined(WUFFS_IMPLEMENTATION) + + inline wuffs_base__status WUFFS_BASE__WARN_UNUSED_RESULT + initialize( + size_t sizeof_star_self, + uint64_t wuffs_version, + uint32_t options) { + return wuffs_png__decoder__initialize( + this, sizeof_star_self, wuffs_version, options); + } + + inline wuffs_base__image_decoder* + upcast_as__wuffs_base__image_decoder() { + return (wuffs_base__image_decoder*)this; + } + + inline wuffs_base__empty_struct + set_quirk_enabled( + uint32_t a_quirk, + bool a_enabled) { + return wuffs_png__decoder__set_quirk_enabled(this, a_quirk, a_enabled); + } + + inline wuffs_base__status + decode_image_config( + wuffs_base__image_config* a_dst, + wuffs_base__io_buffer* a_src) { + return wuffs_png__decoder__decode_image_config(this, a_dst, a_src); + } + + inline wuffs_base__status + decode_frame_config( + wuffs_base__frame_config* a_dst, + wuffs_base__io_buffer* a_src) { + return wuffs_png__decoder__decode_frame_config(this, a_dst, a_src); + } + + inline wuffs_base__status + decode_frame( + wuffs_base__pixel_buffer* a_dst, + wuffs_base__io_buffer* a_src, + wuffs_base__pixel_blend a_blend, + wuffs_base__slice_u8 a_workbuf, + wuffs_base__decode_frame_options* a_opts) { + return wuffs_png__decoder__decode_frame(this, a_dst, a_src, a_blend, a_workbuf, a_opts); + } + + inline wuffs_base__rect_ie_u32 + frame_dirty_rect() const { + return wuffs_png__decoder__frame_dirty_rect(this); + } + + inline uint32_t + num_animation_loops() const { + return wuffs_png__decoder__num_animation_loops(this); + } + + inline uint64_t + num_decoded_frame_configs() const { + return wuffs_png__decoder__num_decoded_frame_configs(this); + } + + inline uint64_t + num_decoded_frames() const { + return wuffs_png__decoder__num_decoded_frames(this); + } + + inline wuffs_base__status + restart_frame( + uint64_t a_index, + uint64_t a_io_position) { + return wuffs_png__decoder__restart_frame(this, a_index, a_io_position); + } + + inline wuffs_base__empty_struct + set_report_metadata( + uint32_t a_fourcc, + bool a_report) { + return wuffs_png__decoder__set_report_metadata(this, a_fourcc, a_report); + } + + inline wuffs_base__status + tell_me_more( + wuffs_base__io_buffer* a_dst, + wuffs_base__more_information* a_minfo, + wuffs_base__io_buffer* a_src) { + return wuffs_png__decoder__tell_me_more(this, a_dst, a_minfo, a_src); + } + + inline wuffs_base__range_ii_u64 + workbuf_len() const { + return wuffs_png__decoder__workbuf_len(this); + } + +#endif // __cplusplus +}; // struct wuffs_png__decoder__struct + +#endif // defined(__cplusplus) || defined(WUFFS_IMPLEMENTATION) + +#endif // !defined(WUFFS_CONFIG__MODULES) || defined(WUFFS_CONFIG__MODULE__PNG) || defined(WUFFS_NONMONOLITHIC) + +#if !defined(WUFFS_CONFIG__MODULES) || defined(WUFFS_CONFIG__MODULE__TGA) || defined(WUFFS_NONMONOLITHIC) + +// ---------------- Status Codes + +extern const char wuffs_tga__error__bad_header[]; +extern const char wuffs_tga__error__bad_run_length_encoding[]; +extern const char wuffs_tga__error__truncated_input[]; +extern const char wuffs_tga__error__unsupported_tga_file[]; + +// ---------------- Public Consts + +#define WUFFS_TGA__DECODER_WORKBUF_LEN_MAX_INCL_WORST_CASE 0 + +// ---------------- Struct Declarations + +typedef struct wuffs_tga__decoder__struct wuffs_tga__decoder; + +#ifdef __cplusplus +extern "C" { +#endif + +// ---------------- Public Initializer Prototypes + +// For any given "wuffs_foo__bar* self", "wuffs_foo__bar__initialize(self, +// etc)" should be called before any other "wuffs_foo__bar__xxx(self, etc)". +// +// Pass sizeof(*self) and WUFFS_VERSION for sizeof_star_self and wuffs_version. +// Pass 0 (or some combination of WUFFS_INITIALIZE__XXX) for options. + +wuffs_base__status WUFFS_BASE__WARN_UNUSED_RESULT +wuffs_tga__decoder__initialize( + wuffs_tga__decoder* self, + size_t sizeof_star_self, + uint64_t wuffs_version, + uint32_t options); + +size_t +sizeof__wuffs_tga__decoder(); + +// ---------------- Allocs + +// These functions allocate and initialize Wuffs structs. They return NULL if +// memory allocation fails. If they return non-NULL, there is no need to call +// wuffs_foo__bar__initialize, but the caller is responsible for eventually +// calling free on the returned pointer. That pointer is effectively a C++ +// std::unique_ptr. + +wuffs_tga__decoder* +wuffs_tga__decoder__alloc(); + +static inline wuffs_base__image_decoder* +wuffs_tga__decoder__alloc_as__wuffs_base__image_decoder() { + return (wuffs_base__image_decoder*)(wuffs_tga__decoder__alloc()); +} + +// ---------------- Upcasts + +static inline wuffs_base__image_decoder* +wuffs_tga__decoder__upcast_as__wuffs_base__image_decoder( + wuffs_tga__decoder* p) { + return (wuffs_base__image_decoder*)p; +} + +// ---------------- Public Function Prototypes + +WUFFS_BASE__MAYBE_STATIC wuffs_base__empty_struct +wuffs_tga__decoder__set_quirk_enabled( + wuffs_tga__decoder* self, + uint32_t a_quirk, + bool a_enabled); + +WUFFS_BASE__MAYBE_STATIC wuffs_base__status +wuffs_tga__decoder__decode_image_config( + wuffs_tga__decoder* self, + wuffs_base__image_config* a_dst, + wuffs_base__io_buffer* a_src); + +WUFFS_BASE__MAYBE_STATIC wuffs_base__status +wuffs_tga__decoder__decode_frame_config( + wuffs_tga__decoder* self, + wuffs_base__frame_config* a_dst, + wuffs_base__io_buffer* a_src); + +WUFFS_BASE__MAYBE_STATIC wuffs_base__status +wuffs_tga__decoder__decode_frame( + wuffs_tga__decoder* self, + wuffs_base__pixel_buffer* a_dst, + wuffs_base__io_buffer* a_src, + wuffs_base__pixel_blend a_blend, + wuffs_base__slice_u8 a_workbuf, + wuffs_base__decode_frame_options* a_opts); + +WUFFS_BASE__MAYBE_STATIC wuffs_base__rect_ie_u32 +wuffs_tga__decoder__frame_dirty_rect( + const wuffs_tga__decoder* self); + +WUFFS_BASE__MAYBE_STATIC uint32_t +wuffs_tga__decoder__num_animation_loops( + const wuffs_tga__decoder* self); + +WUFFS_BASE__MAYBE_STATIC uint64_t +wuffs_tga__decoder__num_decoded_frame_configs( + const wuffs_tga__decoder* self); + +WUFFS_BASE__MAYBE_STATIC uint64_t +wuffs_tga__decoder__num_decoded_frames( + const wuffs_tga__decoder* self); + +WUFFS_BASE__MAYBE_STATIC wuffs_base__status +wuffs_tga__decoder__restart_frame( + wuffs_tga__decoder* self, + uint64_t a_index, + uint64_t a_io_position); + +WUFFS_BASE__MAYBE_STATIC wuffs_base__empty_struct +wuffs_tga__decoder__set_report_metadata( + wuffs_tga__decoder* self, + uint32_t a_fourcc, + bool a_report); + +WUFFS_BASE__MAYBE_STATIC wuffs_base__status +wuffs_tga__decoder__tell_me_more( + wuffs_tga__decoder* self, + wuffs_base__io_buffer* a_dst, + wuffs_base__more_information* a_minfo, + wuffs_base__io_buffer* a_src); + +WUFFS_BASE__MAYBE_STATIC wuffs_base__range_ii_u64 +wuffs_tga__decoder__workbuf_len( + const wuffs_tga__decoder* self); + +#ifdef __cplusplus +} // extern "C" +#endif + +// ---------------- Struct Definitions + +// These structs' fields, and the sizeof them, are private implementation +// details that aren't guaranteed to be stable across Wuffs versions. +// +// See https://en.wikipedia.org/wiki/Opaque_pointer#C + +#if defined(__cplusplus) || defined(WUFFS_IMPLEMENTATION) + +struct wuffs_tga__decoder__struct { + // Do not access the private_impl's or private_data's fields directly. There + // is no API/ABI compatibility or safety guarantee if you do so. Instead, use + // the wuffs_foo__bar__baz functions. + // + // It is a struct, not a struct*, so that the outermost wuffs_foo__bar struct + // can be stack allocated when WUFFS_IMPLEMENTATION is defined. + + struct { + uint32_t magic; + uint32_t active_coroutine; + wuffs_base__vtable vtable_for__wuffs_base__image_decoder; + wuffs_base__vtable null_vtable; + + uint32_t f_width; + uint32_t f_height; + uint8_t f_call_sequence; + uint8_t f_header_id_length; + uint8_t f_header_color_map_type; + uint8_t f_header_image_type; + uint16_t f_header_color_map_first_entry_index; + uint16_t f_header_color_map_length; + uint8_t f_header_color_map_entry_size; + uint8_t f_header_pixel_depth; + uint8_t f_header_image_descriptor; + bool f_opaque; + uint32_t f_scratch_bytes_per_pixel; + uint32_t f_src_bytes_per_pixel; + uint32_t f_src_pixfmt; + uint64_t f_frame_config_io_position; + wuffs_base__pixel_swizzler f_swizzler; + + uint32_t p_decode_image_config[1]; + uint32_t p_do_decode_image_config[1]; + uint32_t p_decode_frame_config[1]; + uint32_t p_do_decode_frame_config[1]; + uint32_t p_decode_frame[1]; + uint32_t p_do_decode_frame[1]; + } private_impl; + + struct { + uint8_t f_dst_palette[1024]; + uint8_t f_src_palette[1024]; + uint8_t f_scratch[4]; + + struct { + uint32_t v_i; + uint64_t scratch; + } s_do_decode_image_config[1]; + struct { + uint64_t v_dst_bytes_per_pixel; + uint32_t v_dst_x; + uint32_t v_dst_y; + uint64_t v_mark; + uint32_t v_num_pixels32; + uint32_t v_lit_length; + uint32_t v_run_length; + uint64_t v_num_dst_bytes; + uint64_t scratch; + } s_do_decode_frame[1]; + } private_data; + +#ifdef __cplusplus +#if defined(WUFFS_BASE__HAVE_UNIQUE_PTR) + using unique_ptr = std::unique_ptr; + + // On failure, the alloc_etc functions return nullptr. They don't throw. + + static inline unique_ptr + alloc() { + return unique_ptr(wuffs_tga__decoder__alloc(), &free); + } + + static inline wuffs_base__image_decoder::unique_ptr + alloc_as__wuffs_base__image_decoder() { + return wuffs_base__image_decoder::unique_ptr( + wuffs_tga__decoder__alloc_as__wuffs_base__image_decoder(), &free); + } +#endif // defined(WUFFS_BASE__HAVE_UNIQUE_PTR) + +#if defined(WUFFS_BASE__HAVE_EQ_DELETE) && !defined(WUFFS_IMPLEMENTATION) + // Disallow constructing or copying an object via standard C++ mechanisms, + // e.g. the "new" operator, as this struct is intentionally opaque. Its total + // size and field layout is not part of the public, stable, memory-safe API. + // Use malloc or memcpy and the sizeof__wuffs_foo__bar function instead, and + // call wuffs_foo__bar__baz methods (which all take a "this"-like pointer as + // their first argument) rather than tweaking bar.private_impl.qux fields. + // + // In C, we can just leave wuffs_foo__bar as an incomplete type (unless + // WUFFS_IMPLEMENTATION is #define'd). In C++, we define a complete type in + // order to provide convenience methods. These forward on "this", so that you + // can write "bar->baz(etc)" instead of "wuffs_foo__bar__baz(bar, etc)". + wuffs_tga__decoder__struct() = delete; + wuffs_tga__decoder__struct(const wuffs_tga__decoder__struct&) = delete; + wuffs_tga__decoder__struct& operator=( + const wuffs_tga__decoder__struct&) = delete; +#endif // defined(WUFFS_BASE__HAVE_EQ_DELETE) && !defined(WUFFS_IMPLEMENTATION) + +#if !defined(WUFFS_IMPLEMENTATION) + // As above, the size of the struct is not part of the public API, and unless + // WUFFS_IMPLEMENTATION is #define'd, this struct type T should be heap + // allocated, not stack allocated. Its size is not intended to be known at + // compile time, but it is unfortunately divulged as a side effect of + // defining C++ convenience methods. Use "sizeof__T()", calling the function, + // instead of "sizeof T", invoking the operator. To make the two values + // different, so that passing the latter will be rejected by the initialize + // function, we add an arbitrary amount of dead weight. + uint8_t dead_weight[123000000]; // 123 MB. +#endif // !defined(WUFFS_IMPLEMENTATION) + + inline wuffs_base__status WUFFS_BASE__WARN_UNUSED_RESULT + initialize( + size_t sizeof_star_self, + uint64_t wuffs_version, + uint32_t options) { + return wuffs_tga__decoder__initialize( + this, sizeof_star_self, wuffs_version, options); + } + + inline wuffs_base__image_decoder* + upcast_as__wuffs_base__image_decoder() { + return (wuffs_base__image_decoder*)this; + } + + inline wuffs_base__empty_struct + set_quirk_enabled( + uint32_t a_quirk, + bool a_enabled) { + return wuffs_tga__decoder__set_quirk_enabled(this, a_quirk, a_enabled); + } + + inline wuffs_base__status + decode_image_config( + wuffs_base__image_config* a_dst, + wuffs_base__io_buffer* a_src) { + return wuffs_tga__decoder__decode_image_config(this, a_dst, a_src); + } + + inline wuffs_base__status + decode_frame_config( + wuffs_base__frame_config* a_dst, + wuffs_base__io_buffer* a_src) { + return wuffs_tga__decoder__decode_frame_config(this, a_dst, a_src); + } + + inline wuffs_base__status + decode_frame( + wuffs_base__pixel_buffer* a_dst, + wuffs_base__io_buffer* a_src, + wuffs_base__pixel_blend a_blend, + wuffs_base__slice_u8 a_workbuf, + wuffs_base__decode_frame_options* a_opts) { + return wuffs_tga__decoder__decode_frame(this, a_dst, a_src, a_blend, a_workbuf, a_opts); + } + + inline wuffs_base__rect_ie_u32 + frame_dirty_rect() const { + return wuffs_tga__decoder__frame_dirty_rect(this); + } + + inline uint32_t + num_animation_loops() const { + return wuffs_tga__decoder__num_animation_loops(this); + } + + inline uint64_t + num_decoded_frame_configs() const { + return wuffs_tga__decoder__num_decoded_frame_configs(this); + } + + inline uint64_t + num_decoded_frames() const { + return wuffs_tga__decoder__num_decoded_frames(this); + } + + inline wuffs_base__status + restart_frame( + uint64_t a_index, + uint64_t a_io_position) { + return wuffs_tga__decoder__restart_frame(this, a_index, a_io_position); + } + + inline wuffs_base__empty_struct + set_report_metadata( + uint32_t a_fourcc, + bool a_report) { + return wuffs_tga__decoder__set_report_metadata(this, a_fourcc, a_report); + } + + inline wuffs_base__status + tell_me_more( + wuffs_base__io_buffer* a_dst, + wuffs_base__more_information* a_minfo, + wuffs_base__io_buffer* a_src) { + return wuffs_tga__decoder__tell_me_more(this, a_dst, a_minfo, a_src); + } + + inline wuffs_base__range_ii_u64 + workbuf_len() const { + return wuffs_tga__decoder__workbuf_len(this); + } + +#endif // __cplusplus +}; // struct wuffs_tga__decoder__struct + +#endif // defined(__cplusplus) || defined(WUFFS_IMPLEMENTATION) + +#endif // !defined(WUFFS_CONFIG__MODULES) || defined(WUFFS_CONFIG__MODULE__TGA) || defined(WUFFS_NONMONOLITHIC) + +#if !defined(WUFFS_CONFIG__MODULES) || defined(WUFFS_CONFIG__MODULE__WBMP) || defined(WUFFS_NONMONOLITHIC) + +// ---------------- Status Codes + +extern const char wuffs_wbmp__error__bad_header[]; +extern const char wuffs_wbmp__error__truncated_input[]; + +// ---------------- Public Consts + +#define WUFFS_WBMP__DECODER_WORKBUF_LEN_MAX_INCL_WORST_CASE 0 + +// ---------------- Struct Declarations + +typedef struct wuffs_wbmp__decoder__struct wuffs_wbmp__decoder; + +#ifdef __cplusplus +extern "C" { +#endif + +// ---------------- Public Initializer Prototypes + +// For any given "wuffs_foo__bar* self", "wuffs_foo__bar__initialize(self, +// etc)" should be called before any other "wuffs_foo__bar__xxx(self, etc)". +// +// Pass sizeof(*self) and WUFFS_VERSION for sizeof_star_self and wuffs_version. +// Pass 0 (or some combination of WUFFS_INITIALIZE__XXX) for options. + +wuffs_base__status WUFFS_BASE__WARN_UNUSED_RESULT +wuffs_wbmp__decoder__initialize( + wuffs_wbmp__decoder* self, + size_t sizeof_star_self, + uint64_t wuffs_version, + uint32_t options); + +size_t +sizeof__wuffs_wbmp__decoder(); + +// ---------------- Allocs + +// These functions allocate and initialize Wuffs structs. They return NULL if +// memory allocation fails. If they return non-NULL, there is no need to call +// wuffs_foo__bar__initialize, but the caller is responsible for eventually +// calling free on the returned pointer. That pointer is effectively a C++ +// std::unique_ptr. + +wuffs_wbmp__decoder* +wuffs_wbmp__decoder__alloc(); + +static inline wuffs_base__image_decoder* +wuffs_wbmp__decoder__alloc_as__wuffs_base__image_decoder() { + return (wuffs_base__image_decoder*)(wuffs_wbmp__decoder__alloc()); +} + +// ---------------- Upcasts + +static inline wuffs_base__image_decoder* +wuffs_wbmp__decoder__upcast_as__wuffs_base__image_decoder( + wuffs_wbmp__decoder* p) { + return (wuffs_base__image_decoder*)p; +} + +// ---------------- Public Function Prototypes + +WUFFS_BASE__MAYBE_STATIC wuffs_base__empty_struct +wuffs_wbmp__decoder__set_quirk_enabled( + wuffs_wbmp__decoder* self, + uint32_t a_quirk, + bool a_enabled); + +WUFFS_BASE__MAYBE_STATIC wuffs_base__status +wuffs_wbmp__decoder__decode_image_config( + wuffs_wbmp__decoder* self, + wuffs_base__image_config* a_dst, + wuffs_base__io_buffer* a_src); + +WUFFS_BASE__MAYBE_STATIC wuffs_base__status +wuffs_wbmp__decoder__decode_frame_config( + wuffs_wbmp__decoder* self, + wuffs_base__frame_config* a_dst, + wuffs_base__io_buffer* a_src); + +WUFFS_BASE__MAYBE_STATIC wuffs_base__status +wuffs_wbmp__decoder__decode_frame( + wuffs_wbmp__decoder* self, + wuffs_base__pixel_buffer* a_dst, + wuffs_base__io_buffer* a_src, + wuffs_base__pixel_blend a_blend, + wuffs_base__slice_u8 a_workbuf, + wuffs_base__decode_frame_options* a_opts); + +WUFFS_BASE__MAYBE_STATIC wuffs_base__rect_ie_u32 +wuffs_wbmp__decoder__frame_dirty_rect( + const wuffs_wbmp__decoder* self); + +WUFFS_BASE__MAYBE_STATIC uint32_t +wuffs_wbmp__decoder__num_animation_loops( + const wuffs_wbmp__decoder* self); + +WUFFS_BASE__MAYBE_STATIC uint64_t +wuffs_wbmp__decoder__num_decoded_frame_configs( + const wuffs_wbmp__decoder* self); + +WUFFS_BASE__MAYBE_STATIC uint64_t +wuffs_wbmp__decoder__num_decoded_frames( + const wuffs_wbmp__decoder* self); + +WUFFS_BASE__MAYBE_STATIC wuffs_base__status +wuffs_wbmp__decoder__restart_frame( + wuffs_wbmp__decoder* self, + uint64_t a_index, + uint64_t a_io_position); + +WUFFS_BASE__MAYBE_STATIC wuffs_base__empty_struct +wuffs_wbmp__decoder__set_report_metadata( + wuffs_wbmp__decoder* self, + uint32_t a_fourcc, + bool a_report); + +WUFFS_BASE__MAYBE_STATIC wuffs_base__status +wuffs_wbmp__decoder__tell_me_more( + wuffs_wbmp__decoder* self, + wuffs_base__io_buffer* a_dst, + wuffs_base__more_information* a_minfo, + wuffs_base__io_buffer* a_src); + +WUFFS_BASE__MAYBE_STATIC wuffs_base__range_ii_u64 +wuffs_wbmp__decoder__workbuf_len( + const wuffs_wbmp__decoder* self); + +#ifdef __cplusplus +} // extern "C" +#endif + +// ---------------- Struct Definitions + +// These structs' fields, and the sizeof them, are private implementation +// details that aren't guaranteed to be stable across Wuffs versions. +// +// See https://en.wikipedia.org/wiki/Opaque_pointer#C + +#if defined(__cplusplus) || defined(WUFFS_IMPLEMENTATION) + +struct wuffs_wbmp__decoder__struct { + // Do not access the private_impl's or private_data's fields directly. There + // is no API/ABI compatibility or safety guarantee if you do so. Instead, use + // the wuffs_foo__bar__baz functions. + // + // It is a struct, not a struct*, so that the outermost wuffs_foo__bar struct + // can be stack allocated when WUFFS_IMPLEMENTATION is defined. + + struct { + uint32_t magic; + uint32_t active_coroutine; + wuffs_base__vtable vtable_for__wuffs_base__image_decoder; + wuffs_base__vtable null_vtable; + + uint32_t f_width; + uint32_t f_height; + uint8_t f_call_sequence; + uint64_t f_frame_config_io_position; + wuffs_base__pixel_swizzler f_swizzler; + + uint32_t p_decode_image_config[1]; + uint32_t p_do_decode_image_config[1]; + uint32_t p_decode_frame_config[1]; + uint32_t p_do_decode_frame_config[1]; + uint32_t p_decode_frame[1]; + uint32_t p_do_decode_frame[1]; + } private_impl; + + struct { + struct { + uint32_t v_i; + uint32_t v_x32; + } s_do_decode_image_config[1]; + struct { + uint64_t v_dst_bytes_per_pixel; + uint32_t v_dst_x; + uint32_t v_dst_y; + uint8_t v_src[1]; + uint8_t v_c; + } s_do_decode_frame[1]; + } private_data; + +#ifdef __cplusplus +#if defined(WUFFS_BASE__HAVE_UNIQUE_PTR) + using unique_ptr = std::unique_ptr; + + // On failure, the alloc_etc functions return nullptr. They don't throw. + + static inline unique_ptr + alloc() { + return unique_ptr(wuffs_wbmp__decoder__alloc(), &free); + } + + static inline wuffs_base__image_decoder::unique_ptr + alloc_as__wuffs_base__image_decoder() { + return wuffs_base__image_decoder::unique_ptr( + wuffs_wbmp__decoder__alloc_as__wuffs_base__image_decoder(), &free); + } +#endif // defined(WUFFS_BASE__HAVE_UNIQUE_PTR) + +#if defined(WUFFS_BASE__HAVE_EQ_DELETE) && !defined(WUFFS_IMPLEMENTATION) + // Disallow constructing or copying an object via standard C++ mechanisms, + // e.g. the "new" operator, as this struct is intentionally opaque. Its total + // size and field layout is not part of the public, stable, memory-safe API. + // Use malloc or memcpy and the sizeof__wuffs_foo__bar function instead, and + // call wuffs_foo__bar__baz methods (which all take a "this"-like pointer as + // their first argument) rather than tweaking bar.private_impl.qux fields. + // + // In C, we can just leave wuffs_foo__bar as an incomplete type (unless + // WUFFS_IMPLEMENTATION is #define'd). In C++, we define a complete type in + // order to provide convenience methods. These forward on "this", so that you + // can write "bar->baz(etc)" instead of "wuffs_foo__bar__baz(bar, etc)". + wuffs_wbmp__decoder__struct() = delete; + wuffs_wbmp__decoder__struct(const wuffs_wbmp__decoder__struct&) = delete; + wuffs_wbmp__decoder__struct& operator=( + const wuffs_wbmp__decoder__struct&) = delete; +#endif // defined(WUFFS_BASE__HAVE_EQ_DELETE) && !defined(WUFFS_IMPLEMENTATION) + +#if !defined(WUFFS_IMPLEMENTATION) + // As above, the size of the struct is not part of the public API, and unless + // WUFFS_IMPLEMENTATION is #define'd, this struct type T should be heap + // allocated, not stack allocated. Its size is not intended to be known at + // compile time, but it is unfortunately divulged as a side effect of + // defining C++ convenience methods. Use "sizeof__T()", calling the function, + // instead of "sizeof T", invoking the operator. To make the two values + // different, so that passing the latter will be rejected by the initialize + // function, we add an arbitrary amount of dead weight. + uint8_t dead_weight[123000000]; // 123 MB. +#endif // !defined(WUFFS_IMPLEMENTATION) + + inline wuffs_base__status WUFFS_BASE__WARN_UNUSED_RESULT + initialize( + size_t sizeof_star_self, + uint64_t wuffs_version, + uint32_t options) { + return wuffs_wbmp__decoder__initialize( + this, sizeof_star_self, wuffs_version, options); + } + + inline wuffs_base__image_decoder* + upcast_as__wuffs_base__image_decoder() { + return (wuffs_base__image_decoder*)this; + } + + inline wuffs_base__empty_struct + set_quirk_enabled( + uint32_t a_quirk, + bool a_enabled) { + return wuffs_wbmp__decoder__set_quirk_enabled(this, a_quirk, a_enabled); + } + + inline wuffs_base__status + decode_image_config( + wuffs_base__image_config* a_dst, + wuffs_base__io_buffer* a_src) { + return wuffs_wbmp__decoder__decode_image_config(this, a_dst, a_src); + } + + inline wuffs_base__status + decode_frame_config( + wuffs_base__frame_config* a_dst, + wuffs_base__io_buffer* a_src) { + return wuffs_wbmp__decoder__decode_frame_config(this, a_dst, a_src); + } + + inline wuffs_base__status + decode_frame( + wuffs_base__pixel_buffer* a_dst, + wuffs_base__io_buffer* a_src, + wuffs_base__pixel_blend a_blend, + wuffs_base__slice_u8 a_workbuf, + wuffs_base__decode_frame_options* a_opts) { + return wuffs_wbmp__decoder__decode_frame(this, a_dst, a_src, a_blend, a_workbuf, a_opts); + } + + inline wuffs_base__rect_ie_u32 + frame_dirty_rect() const { + return wuffs_wbmp__decoder__frame_dirty_rect(this); + } + + inline uint32_t + num_animation_loops() const { + return wuffs_wbmp__decoder__num_animation_loops(this); + } + + inline uint64_t + num_decoded_frame_configs() const { + return wuffs_wbmp__decoder__num_decoded_frame_configs(this); + } + + inline uint64_t + num_decoded_frames() const { + return wuffs_wbmp__decoder__num_decoded_frames(this); + } + + inline wuffs_base__status + restart_frame( + uint64_t a_index, + uint64_t a_io_position) { + return wuffs_wbmp__decoder__restart_frame(this, a_index, a_io_position); + } + + inline wuffs_base__empty_struct + set_report_metadata( + uint32_t a_fourcc, + bool a_report) { + return wuffs_wbmp__decoder__set_report_metadata(this, a_fourcc, a_report); + } + + inline wuffs_base__status + tell_me_more( + wuffs_base__io_buffer* a_dst, + wuffs_base__more_information* a_minfo, + wuffs_base__io_buffer* a_src) { + return wuffs_wbmp__decoder__tell_me_more(this, a_dst, a_minfo, a_src); + } + + inline wuffs_base__range_ii_u64 + workbuf_len() const { + return wuffs_wbmp__decoder__workbuf_len(this); + } + +#endif // __cplusplus +}; // struct wuffs_wbmp__decoder__struct + +#endif // defined(__cplusplus) || defined(WUFFS_IMPLEMENTATION) + +#endif // !defined(WUFFS_CONFIG__MODULES) || defined(WUFFS_CONFIG__MODULE__WBMP) || defined(WUFFS_NONMONOLITHIC) + +#if defined(__cplusplus) && defined(WUFFS_BASE__HAVE_UNIQUE_PTR) + +// ---------------- Auxiliary - Base + +// Auxiliary code is discussed at +// https://github.com/google/wuffs/blob/main/doc/note/auxiliary-code.md + +#include + +#include + +namespace wuffs_aux { + +using IOBuffer = wuffs_base__io_buffer; + +// MemOwner represents ownership of some memory. Dynamically allocated memory +// (e.g. from malloc or new) is typically paired with free or delete, invoked +// when the std::unique_ptr is destroyed. Statically allocated memory might use +// MemOwner(nullptr, &free), even if that statically allocated memory is not +// nullptr, since calling free(nullptr) is a no-op. +using MemOwner = std::unique_ptr; + +namespace sync_io { + +// -------- + +// DynIOBuffer is an IOBuffer that is backed by a dynamically sized byte array. +// It owns that backing array and will free it in its destructor. +// +// The array size can be explicitly extended (by calling the grow method) but, +// unlike a C++ std::vector, there is no implicit extension (e.g. by calling +// std::vector::insert) and its maximum size is capped by the max_incl +// constructor argument. +// +// It contains an IOBuffer-typed field whose reader side provides access to +// previously written bytes and whose writer side provides access to the +// allocated but not-yet-written-to slack space. For Go programmers, this slack +// space is roughly analogous to the s[len(s):cap(s)] space of a slice s. +class DynIOBuffer { + public: + enum GrowResult { + OK = 0, + FailedMaxInclExceeded = 1, + FailedOutOfMemory = 2, + }; + + // m_buf holds the dynamically sized byte array and its read/write indexes: + // - m_buf.meta.wi is roughly analogous to a Go slice's length. + // - m_buf.data.len is roughly analogous to a Go slice's capacity. It is + // also equal to the m_buf.data.ptr malloc/realloc size. + // + // Users should not modify the m_buf.data.ptr or m_buf.data.len fields (as + // they are conceptually private to this class), but they can modify the + // bytes referenced by that pointer-length pair (e.g. compactions). + IOBuffer m_buf; + + // m_max_incl is an inclusive upper bound on the backing array size. + const uint64_t m_max_incl; + + // Constructor and destructor. + explicit DynIOBuffer(uint64_t max_incl); + ~DynIOBuffer(); + + // Drop frees the byte array and resets m_buf. The DynIOBuffer can still be + // used after a drop call. It just restarts from zero. + void drop(); + + // grow ensures that the byte array size is at least min_incl and at most + // max_incl. It returns FailedMaxInclExceeded if that would require + // allocating more than max_incl bytes, including the case where (min_incl > + // max_incl). It returns FailedOutOfMemory if memory allocation failed. + GrowResult grow(uint64_t min_incl); + + private: + // Delete the copy and assign constructors. + DynIOBuffer(const DynIOBuffer&) = delete; + DynIOBuffer& operator=(const DynIOBuffer&) = delete; + + static uint64_t round_up(uint64_t min_incl, uint64_t max_incl); +}; + +// -------- + +class Input { + public: + virtual ~Input(); + + virtual IOBuffer* BringsItsOwnIOBuffer(); + virtual std::string CopyIn(IOBuffer* dst) = 0; +}; + +// -------- + +// FileInput is an Input that reads from a file source. +// +// It does not take responsibility for closing the file when done. +class FileInput : public Input { + public: + FileInput(FILE* f); + + virtual std::string CopyIn(IOBuffer* dst); + + private: + FILE* m_f; + + // Delete the copy and assign constructors. + FileInput(const FileInput&) = delete; + FileInput& operator=(const FileInput&) = delete; +}; + +// -------- + +// MemoryInput is an Input that reads from an in-memory source. +// +// It does not take responsibility for freeing the memory when done. +class MemoryInput : public Input { + public: + MemoryInput(const char* ptr, size_t len); + MemoryInput(const uint8_t* ptr, size_t len); + + virtual IOBuffer* BringsItsOwnIOBuffer(); + virtual std::string CopyIn(IOBuffer* dst); + + private: + IOBuffer m_io; + + // Delete the copy and assign constructors. + MemoryInput(const MemoryInput&) = delete; + MemoryInput& operator=(const MemoryInput&) = delete; +}; + +// -------- + +} // namespace sync_io + +} // namespace wuffs_aux + +// ---------------- Auxiliary - CBOR + +namespace wuffs_aux { + +struct DecodeCborResult { + DecodeCborResult(std::string&& error_message0, uint64_t cursor_position0); + + std::string error_message; + uint64_t cursor_position; +}; + +class DecodeCborCallbacks { + public: + virtual ~DecodeCborCallbacks(); + + // AppendXxx are called for leaf nodes: literals, numbers, strings, etc. + + virtual std::string AppendNull() = 0; + virtual std::string AppendUndefined() = 0; + virtual std::string AppendBool(bool val) = 0; + virtual std::string AppendF64(double val) = 0; + virtual std::string AppendI64(int64_t val) = 0; + virtual std::string AppendU64(uint64_t val) = 0; + virtual std::string AppendByteString(std::string&& val) = 0; + virtual std::string AppendTextString(std::string&& val) = 0; + virtual std::string AppendMinus1MinusX(uint64_t val) = 0; + virtual std::string AppendCborSimpleValue(uint8_t val) = 0; + virtual std::string AppendCborTag(uint64_t val) = 0; + + // Push and Pop are called for container nodes: CBOR arrays (lists) and CBOR + // maps (dictionaries). + // + // The flags bits combine exactly one of: + // - WUFFS_BASE__TOKEN__VBD__STRUCTURE__FROM_NONE + // - WUFFS_BASE__TOKEN__VBD__STRUCTURE__FROM_LIST + // - WUFFS_BASE__TOKEN__VBD__STRUCTURE__FROM_DICT + // and exactly one of: + // - WUFFS_BASE__TOKEN__VBD__STRUCTURE__TO_NONE + // - WUFFS_BASE__TOKEN__VBD__STRUCTURE__TO_LIST + // - WUFFS_BASE__TOKEN__VBD__STRUCTURE__TO_DICT + + virtual std::string Push(uint32_t flags) = 0; + virtual std::string Pop(uint32_t flags) = 0; + + // Done is always the last Callback method called by DecodeCbor, whether or + // not parsing the input as CBOR encountered an error. Even when successful, + // trailing data may remain in input and buffer. + // + // Do not keep a reference to buffer or buffer.data.ptr after Done returns, + // as DecodeCbor may then de-allocate the backing array. + // + // The default Done implementation is a no-op. + virtual void // + Done(DecodeCborResult& result, sync_io::Input& input, IOBuffer& buffer); +}; + +// The FooArgBar types add structure to Foo's optional arguments. They wrap +// inner representations for several reasons: +// - It provides a home for the DefaultValue static method, for Foo callers +// that want to override some but not all optional arguments. +// - It provides the "Bar" name at Foo call sites, which can help self- +// document Foo calls with many arguemnts. +// - It provides some type safety against accidentally transposing or omitting +// adjacent fundamentally-numeric-typed optional arguments. + +// DecodeCborArgQuirks wraps an optional argument to DecodeCbor. +struct DecodeCborArgQuirks { + explicit DecodeCborArgQuirks(wuffs_base__slice_u32 repr0); + explicit DecodeCborArgQuirks(uint32_t* ptr, size_t len); + + // DefaultValue returns an empty slice. + static DecodeCborArgQuirks DefaultValue(); + + wuffs_base__slice_u32 repr; +}; + +// DecodeCbor calls callbacks based on the CBOR-formatted data in input. +// +// On success, the returned error_message is empty and cursor_position counts +// the number of bytes consumed. On failure, error_message is non-empty and +// cursor_position is the location of the error. That error may be a content +// error (invalid CBOR) or an input error (e.g. network failure). +DecodeCborResult // +DecodeCbor(DecodeCborCallbacks& callbacks, + sync_io::Input& input, + DecodeCborArgQuirks quirks = DecodeCborArgQuirks::DefaultValue()); + +} // namespace wuffs_aux + +// ---------------- Auxiliary - Image + +namespace wuffs_aux { + +struct DecodeImageResult { + DecodeImageResult(MemOwner&& pixbuf_mem_owner0, + wuffs_base__pixel_buffer pixbuf0, + std::string&& error_message0); + DecodeImageResult(std::string&& error_message0); + + MemOwner pixbuf_mem_owner; + wuffs_base__pixel_buffer pixbuf; + std::string error_message; +}; + +// DecodeImageCallbacks are the callbacks given to DecodeImage. They are always +// called in this order: +// 1. SelectDecoder +// 2. HandleMetadata +// 3. SelectPixfmt +// 4. AllocPixbuf +// 5. AllocWorkbuf +// 6. Done +// +// It may return early - the third callback might not be invoked if the second +// one fails - but the final callback (Done) is always invoked. +class DecodeImageCallbacks { + public: + // AllocPixbufResult holds a memory allocation (the result of malloc or new, + // a statically allocated pointer, etc), or an error message. The memory is + // de-allocated when mem_owner goes out of scope and is destroyed. + struct AllocPixbufResult { + AllocPixbufResult(MemOwner&& mem_owner0, wuffs_base__pixel_buffer pixbuf0); + AllocPixbufResult(std::string&& error_message0); + + MemOwner mem_owner; + wuffs_base__pixel_buffer pixbuf; + std::string error_message; + }; + + // AllocWorkbufResult holds a memory allocation (the result of malloc or new, + // a statically allocated pointer, etc), or an error message. The memory is + // de-allocated when mem_owner goes out of scope and is destroyed. + struct AllocWorkbufResult { + AllocWorkbufResult(MemOwner&& mem_owner0, wuffs_base__slice_u8 workbuf0); + AllocWorkbufResult(std::string&& error_message0); + + MemOwner mem_owner; + wuffs_base__slice_u8 workbuf; + std::string error_message; + }; + + virtual ~DecodeImageCallbacks(); + + // SelectDecoder returns the image decoder for the input data's file format. + // Returning a nullptr means failure (DecodeImage_UnsupportedImageFormat). + // + // Common formats will have a FourCC value in the range [1 ..= 0x7FFF_FFFF], + // such as WUFFS_BASE__FOURCC__JPEG. A zero FourCC value means that Wuffs' + // standard library did not recognize the image format but if SelectDecoder + // was overridden, it may examine the input data's starting bytes and still + // provide its own image decoder, e.g. for an exotic image file format that's + // not in Wuffs' standard library. The prefix_etc fields have the same + // meaning as wuffs_base__magic_number_guess_fourcc arguments. SelectDecoder + // implementations should not modify prefix_data's contents. + // + // SelectDecoder might be called more than once, since some image file + // formats can wrap others. For example, a nominal BMP file can actually + // contain a JPEG or a PNG. + // + // The default SelectDecoder accepts the FOURCC codes listed below. For + // modular builds (i.e. when #define'ing WUFFS_CONFIG__MODULES), acceptance + // of the ETC file format is optional (for each value of ETC) and depends on + // the corresponding module to be enabled at compile time (i.e. #define'ing + // WUFFS_CONFIG__MODULE__ETC). + // - WUFFS_BASE__FOURCC__BMP + // - WUFFS_BASE__FOURCC__GIF + // - WUFFS_BASE__FOURCC__NIE + // - WUFFS_BASE__FOURCC__PNG + // - WUFFS_BASE__FOURCC__TGA + // - WUFFS_BASE__FOURCC__WBMP + virtual wuffs_base__image_decoder::unique_ptr // + SelectDecoder(uint32_t fourcc, + wuffs_base__slice_u8 prefix_data, + bool prefix_closed); + + // HandleMetadata acknowledges image metadata. minfo.flavor will be one of: + // - WUFFS_BASE__MORE_INFORMATION__FLAVOR__METADATA_RAW_PASSTHROUGH + // - WUFFS_BASE__MORE_INFORMATION__FLAVOR__METADATA_PARSED + // If it is ETC__METADATA_RAW_ETC then raw contains the metadata bytes. Those + // bytes should not be retained beyond the the HandleMetadata call. + // + // minfo.metadata__fourcc() will typically match one of the + // DecodeImageArgFlags bits. For example, if (REPORT_METADATA_CHRM | + // REPORT_METADATA_GAMA) was passed to DecodeImage then the metadata FourCC + // will be either WUFFS_BASE__FOURCC__CHRM or WUFFS_BASE__FOURCC__GAMA. + // + // It returns an error message, or an empty string on success. + virtual std::string // + HandleMetadata(const wuffs_base__more_information& minfo, + wuffs_base__slice_u8 raw); + + // SelectPixfmt returns the destination pixel format for AllocPixbuf. It + // should return wuffs_base__make_pixel_format(etc) called with one of: + // - WUFFS_BASE__PIXEL_FORMAT__BGR_565 + // - WUFFS_BASE__PIXEL_FORMAT__BGR + // - WUFFS_BASE__PIXEL_FORMAT__BGRA_NONPREMUL + // - WUFFS_BASE__PIXEL_FORMAT__BGRA_NONPREMUL_4X16LE + // - WUFFS_BASE__PIXEL_FORMAT__BGRA_PREMUL + // - WUFFS_BASE__PIXEL_FORMAT__RGBA_NONPREMUL + // - WUFFS_BASE__PIXEL_FORMAT__RGBA_PREMUL + // or return image_config.pixcfg.pixel_format(). The latter means to use the + // image file's natural pixel format. For example, GIF images' natural pixel + // format is an indexed one. + // + // Returning otherwise means failure (DecodeImage_UnsupportedPixelFormat). + // + // The default SelectPixfmt implementation returns + // wuffs_base__make_pixel_format(WUFFS_BASE__PIXEL_FORMAT__BGRA_PREMUL) which + // is 4 bytes per pixel (8 bits per channel × 4 channels). + virtual wuffs_base__pixel_format // + SelectPixfmt(const wuffs_base__image_config& image_config); + + // AllocPixbuf allocates the pixel buffer. + // + // allow_uninitialized_memory will be true if a valid background_color was + // passed to DecodeImage, since the pixel buffer's contents will be + // overwritten with that color after AllocPixbuf returns. + // + // The default AllocPixbuf implementation allocates either uninitialized or + // zeroed memory. Zeroed memory typically corresponds to filling with opaque + // black or transparent black, depending on the pixel format. + virtual AllocPixbufResult // + AllocPixbuf(const wuffs_base__image_config& image_config, + bool allow_uninitialized_memory); + + // AllocWorkbuf allocates the work buffer. The allocated buffer's length + // should be at least len_range.min_incl, but larger allocations (up to + // len_range.max_incl) may have better performance (by using more memory). + // + // The default AllocWorkbuf implementation allocates len_range.max_incl bytes + // of either uninitialized or zeroed memory. + virtual AllocWorkbufResult // + AllocWorkbuf(wuffs_base__range_ii_u64 len_range, + bool allow_uninitialized_memory); + + // Done is always the last Callback method called by DecodeImage, whether or + // not parsing the input encountered an error. Even when successful, trailing + // data may remain in input and buffer. + // + // The image_decoder is the one returned by SelectDecoder (if SelectDecoder + // was successful), or a no-op unique_ptr otherwise. Like any unique_ptr, + // ownership moves to the Done implementation. + // + // Do not keep a reference to buffer or buffer.data.ptr after Done returns, + // as DecodeImage may then de-allocate the backing array. + // + // The default Done implementation is a no-op, other than running the + // image_decoder unique_ptr destructor. + virtual void // + Done(DecodeImageResult& result, + sync_io::Input& input, + IOBuffer& buffer, + wuffs_base__image_decoder::unique_ptr image_decoder); +}; + +extern const char DecodeImage_BufferIsTooShort[]; +extern const char DecodeImage_MaxInclDimensionExceeded[]; +extern const char DecodeImage_MaxInclMetadataLengthExceeded[]; +extern const char DecodeImage_OutOfMemory[]; +extern const char DecodeImage_UnexpectedEndOfFile[]; +extern const char DecodeImage_UnsupportedImageFormat[]; +extern const char DecodeImage_UnsupportedMetadata[]; +extern const char DecodeImage_UnsupportedPixelBlend[]; +extern const char DecodeImage_UnsupportedPixelConfiguration[]; +extern const char DecodeImage_UnsupportedPixelFormat[]; + +// The FooArgBar types add structure to Foo's optional arguments. They wrap +// inner representations for several reasons: +// - It provides a home for the DefaultValue static method, for Foo callers +// that want to override some but not all optional arguments. +// - It provides the "Bar" name at Foo call sites, which can help self- +// document Foo calls with many arguemnts. +// - It provides some type safety against accidentally transposing or omitting +// adjacent fundamentally-numeric-typed optional arguments. + +// DecodeImageArgQuirks wraps an optional argument to DecodeImage. +struct DecodeImageArgQuirks { + explicit DecodeImageArgQuirks(wuffs_base__slice_u32 repr0); + explicit DecodeImageArgQuirks(uint32_t* ptr, size_t len); + + // DefaultValue returns an empty slice. + static DecodeImageArgQuirks DefaultValue(); + + wuffs_base__slice_u32 repr; +}; + +// DecodeImageArgFlags wraps an optional argument to DecodeImage. +struct DecodeImageArgFlags { + explicit DecodeImageArgFlags(uint64_t repr0); + + // DefaultValue returns 0. + static DecodeImageArgFlags DefaultValue(); + + // TODO: support all of the REPORT_METADATA_ETC flags, not just CHRM, EXIF, + // GAMA, ICCP, KVP, SRGB and XMP. + + // Background Color. + static constexpr uint64_t REPORT_METADATA_BGCL = 0x0001; + // Primary Chromaticities and White Point. + static constexpr uint64_t REPORT_METADATA_CHRM = 0x0002; + // Exchangeable Image File Format. + static constexpr uint64_t REPORT_METADATA_EXIF = 0x0004; + // Gamma Correction. + static constexpr uint64_t REPORT_METADATA_GAMA = 0x0008; + // International Color Consortium Profile. + static constexpr uint64_t REPORT_METADATA_ICCP = 0x0010; + // Key-Value Pair. + // + // For PNG files, this includes iTXt, tEXt and zTXt chunks. In the + // HandleMetadata callback, the raw argument contains UTF-8 strings. + static constexpr uint64_t REPORT_METADATA_KVP = 0x0020; + // Modification Time. + static constexpr uint64_t REPORT_METADATA_MTIM = 0x0040; + // Offset (2-Dimensional). + static constexpr uint64_t REPORT_METADATA_OFS2 = 0x0080; + // Physical Dimensions. + static constexpr uint64_t REPORT_METADATA_PHYD = 0x0100; + // Standard Red Green Blue (Rendering Intent). + static constexpr uint64_t REPORT_METADATA_SRGB = 0x0200; + // Extensible Metadata Platform. + static constexpr uint64_t REPORT_METADATA_XMP = 0x0400; + + uint64_t repr; +}; + +// DecodeImageArgPixelBlend wraps an optional argument to DecodeImage. +struct DecodeImageArgPixelBlend { + explicit DecodeImageArgPixelBlend(wuffs_base__pixel_blend repr0); + + // DefaultValue returns WUFFS_BASE__PIXEL_BLEND__SRC. + static DecodeImageArgPixelBlend DefaultValue(); + + wuffs_base__pixel_blend repr; +}; + +// DecodeImageArgBackgroundColor wraps an optional argument to DecodeImage. +struct DecodeImageArgBackgroundColor { + explicit DecodeImageArgBackgroundColor( + wuffs_base__color_u32_argb_premul repr0); + + // DefaultValue returns 1, an invalid wuffs_base__color_u32_argb_premul. + static DecodeImageArgBackgroundColor DefaultValue(); + + wuffs_base__color_u32_argb_premul repr; +}; + +// DecodeImageArgMaxInclDimension wraps an optional argument to DecodeImage. +struct DecodeImageArgMaxInclDimension { + explicit DecodeImageArgMaxInclDimension(uint32_t repr0); + + // DefaultValue returns 1048575 = 0x000F_FFFF, more than 1 million pixels. + static DecodeImageArgMaxInclDimension DefaultValue(); + + uint32_t repr; +}; + +// DecodeImageArgMaxInclMetadataLength wraps an optional argument to +// DecodeImage. +struct DecodeImageArgMaxInclMetadataLength { + explicit DecodeImageArgMaxInclMetadataLength(uint64_t repr0); + + // DefaultValue returns 16777215 = 0x00FF_FFFF, one less than 16 MiB. + static DecodeImageArgMaxInclMetadataLength DefaultValue(); + + uint64_t repr; +}; + +// DecodeImage decodes the image data in input. A variety of image file formats +// can be decoded, depending on what callbacks.SelectDecoder returns. +// +// For animated formats, only the first frame is returned, since the API is +// simpler for synchronous I/O and having DecodeImage only return when +// completely done, but rendering animation often involves handling other +// events in between animation frames. To decode multiple frames of animated +// images, or for asynchronous I/O (e.g. when decoding an image streamed over +// the network), use Wuffs' lower level C API instead of its higher level, +// simplified C++ API (the wuffs_aux API). +// +// The DecodeImageResult's fields depend on whether decoding succeeded: +// - On total success, the error_message is empty and pixbuf.pixcfg.is_valid() +// is true. +// - On partial success (e.g. the input file was truncated but we are still +// able to decode some of the pixels), error_message is non-empty but +// pixbuf.pixcfg.is_valid() is still true. It is up to the caller whether to +// accept or reject partial success. +// - On failure, the error_message is non_empty and pixbuf.pixcfg.is_valid() +// is false. +// +// The callbacks allocate the pixel buffer memory and work buffer memory. On +// success, pixel buffer memory ownership is passed to the DecodeImage caller +// as the returned pixbuf_mem_owner. Regardless of success or failure, the work +// buffer memory is deleted. +// +// The pixel_blend (one of the constants listed below) determines how to +// composite the decoded image over the pixel buffer's original pixels (as +// returned by callbacks.AllocPixbuf): +// - WUFFS_BASE__PIXEL_BLEND__SRC +// - WUFFS_BASE__PIXEL_BLEND__SRC_OVER +// +// The background_color is used to fill the pixel buffer after +// callbacks.AllocPixbuf returns, if it is valid in the +// wuffs_base__color_u32_argb_premul__is_valid sense. The default value, +// 0x0000_0001, is not valid since its Blue channel value (0x01) is greater +// than its Alpha channel value (0x00). A valid background_color will typically +// be overwritten when pixel_blend is WUFFS_BASE__PIXEL_BLEND__SRC, but might +// still be visible on partial (not total) success or when pixel_blend is +// WUFFS_BASE__PIXEL_BLEND__SRC_OVER and the decoded image is not fully opaque. +// +// Decoding fails (with DecodeImage_MaxInclDimensionExceeded) if the image's +// width or height is greater than max_incl_dimension or if any opted-in (via +// flags bits) metadata is longer than max_incl_metadata_length. +DecodeImageResult // +DecodeImage(DecodeImageCallbacks& callbacks, + sync_io::Input& input, + DecodeImageArgQuirks quirks = DecodeImageArgQuirks::DefaultValue(), + DecodeImageArgFlags flags = DecodeImageArgFlags::DefaultValue(), + DecodeImageArgPixelBlend pixel_blend = + DecodeImageArgPixelBlend::DefaultValue(), + DecodeImageArgBackgroundColor background_color = + DecodeImageArgBackgroundColor::DefaultValue(), + DecodeImageArgMaxInclDimension max_incl_dimension = + DecodeImageArgMaxInclDimension::DefaultValue(), + DecodeImageArgMaxInclMetadataLength max_incl_metadata_length = + DecodeImageArgMaxInclMetadataLength::DefaultValue()); + +} // namespace wuffs_aux + +// ---------------- Auxiliary - JSON + +namespace wuffs_aux { + +struct DecodeJsonResult { + DecodeJsonResult(std::string&& error_message0, uint64_t cursor_position0); + + std::string error_message; + uint64_t cursor_position; +}; + +class DecodeJsonCallbacks { + public: + virtual ~DecodeJsonCallbacks(); + + // AppendXxx are called for leaf nodes: literals, numbers and strings. For + // strings, the Callbacks implementation is responsible for tracking map keys + // versus other values. + + virtual std::string AppendNull() = 0; + virtual std::string AppendBool(bool val) = 0; + virtual std::string AppendF64(double val) = 0; + virtual std::string AppendI64(int64_t val) = 0; + virtual std::string AppendTextString(std::string&& val) = 0; + + // Push and Pop are called for container nodes: JSON arrays (lists) and JSON + // objects (dictionaries). + // + // The flags bits combine exactly one of: + // - WUFFS_BASE__TOKEN__VBD__STRUCTURE__FROM_NONE + // - WUFFS_BASE__TOKEN__VBD__STRUCTURE__FROM_LIST + // - WUFFS_BASE__TOKEN__VBD__STRUCTURE__FROM_DICT + // and exactly one of: + // - WUFFS_BASE__TOKEN__VBD__STRUCTURE__TO_NONE + // - WUFFS_BASE__TOKEN__VBD__STRUCTURE__TO_LIST + // - WUFFS_BASE__TOKEN__VBD__STRUCTURE__TO_DICT + + virtual std::string Push(uint32_t flags) = 0; + virtual std::string Pop(uint32_t flags) = 0; + + // Done is always the last Callback method called by DecodeJson, whether or + // not parsing the input as JSON encountered an error. Even when successful, + // trailing data may remain in input and buffer. See "Unintuitive JSON + // Parsing" (https://nullprogram.com/blog/2019/12/28/) which discusses JSON + // parsing and when it stops. + // + // Do not keep a reference to buffer or buffer.data.ptr after Done returns, + // as DecodeJson may then de-allocate the backing array. + // + // The default Done implementation is a no-op. + virtual void // + Done(DecodeJsonResult& result, sync_io::Input& input, IOBuffer& buffer); +}; + +extern const char DecodeJson_BadJsonPointer[]; +extern const char DecodeJson_NoMatch[]; + +// The FooArgBar types add structure to Foo's optional arguments. They wrap +// inner representations for several reasons: +// - It provides a home for the DefaultValue static method, for Foo callers +// that want to override some but not all optional arguments. +// - It provides the "Bar" name at Foo call sites, which can help self- +// document Foo calls with many arguemnts. +// - It provides some type safety against accidentally transposing or omitting +// adjacent fundamentally-numeric-typed optional arguments. + +// DecodeJsonArgQuirks wraps an optional argument to DecodeJson. +struct DecodeJsonArgQuirks { + explicit DecodeJsonArgQuirks(wuffs_base__slice_u32 repr0); + explicit DecodeJsonArgQuirks(uint32_t* ptr, size_t len); + + // DefaultValue returns an empty slice. + static DecodeJsonArgQuirks DefaultValue(); + + wuffs_base__slice_u32 repr; +}; + +// DecodeJsonArgJsonPointer wraps an optional argument to DecodeJson. +struct DecodeJsonArgJsonPointer { + explicit DecodeJsonArgJsonPointer(std::string repr0); + + // DefaultValue returns an empty string. + static DecodeJsonArgJsonPointer DefaultValue(); + + std::string repr; +}; + +// DecodeJson calls callbacks based on the JSON-formatted data in input. +// +// On success, the returned error_message is empty and cursor_position counts +// the number of bytes consumed. On failure, error_message is non-empty and +// cursor_position is the location of the error. That error may be a content +// error (invalid JSON) or an input error (e.g. network failure). +// +// json_pointer is a query in the JSON Pointer (RFC 6901) syntax. The callbacks +// run for the input's sub-node that matches the query. DecodeJson_NoMatch is +// returned if no matching sub-node was found. The empty query matches the +// input's root node, consistent with JSON Pointer semantics. +// +// The JSON Pointer implementation is greedy: duplicate keys are not rejected +// but only the first match for each '/'-separated fragment is followed. +DecodeJsonResult // +DecodeJson(DecodeJsonCallbacks& callbacks, + sync_io::Input& input, + DecodeJsonArgQuirks quirks = DecodeJsonArgQuirks::DefaultValue(), + DecodeJsonArgJsonPointer json_pointer = + DecodeJsonArgJsonPointer::DefaultValue()); + +} // namespace wuffs_aux + +#endif // defined(__cplusplus) && defined(WUFFS_BASE__HAVE_UNIQUE_PTR) + +// ‼ WUFFS C HEADER ENDS HERE. +#ifdef WUFFS_IMPLEMENTATION + +#ifdef __cplusplus +extern "C" { +#endif + +// ---------------- Fundamentals + +// WUFFS_BASE__MAGIC is a magic number to check that initializers are called. +// It's not foolproof, given C doesn't automatically zero memory before use, +// but it should catch 99.99% of cases. +// +// Its (non-zero) value is arbitrary, based on md5sum("wuffs"). +#define WUFFS_BASE__MAGIC ((uint32_t)0x3CCB6C71) + +// WUFFS_BASE__DISABLED is a magic number to indicate that a non-recoverable +// error was previously encountered. +// +// Its (non-zero) value is arbitrary, based on md5sum("disabled"). +#define WUFFS_BASE__DISABLED ((uint32_t)0x075AE3D2) + +// Use switch cases for coroutine suspension points, similar to the technique +// in https://www.chiark.greenend.org.uk/~sgtatham/coroutines.html +// +// The implicit fallthrough is intentional. +// +// We use trivial macros instead of an explicit assignment and case statement +// so that clang-format doesn't get confused by the unusual "case"s. +#define WUFFS_BASE__COROUTINE_SUSPENSION_POINT_0 case 0:; +#define WUFFS_BASE__COROUTINE_SUSPENSION_POINT(n) \ + coro_susp_point = n; \ + case n:; + +#define WUFFS_BASE__COROUTINE_SUSPENSION_POINT_MAYBE_SUSPEND(n) \ + if (!status.repr) { \ + goto ok; \ + } else if (*status.repr != '$') { \ + goto exit; \ + } \ + coro_susp_point = n; \ + goto suspend; \ + case n:; + +// The "defined(__clang__)" isn't redundant. While vanilla clang defines +// __GNUC__, clang-cl (which mimics MSVC's cl.exe) does not. +#if defined(__GNUC__) || defined(__clang__) +#define WUFFS_BASE__LIKELY(expr) (__builtin_expect(!!(expr), 1)) +#define WUFFS_BASE__UNLIKELY(expr) (__builtin_expect(!!(expr), 0)) +#else +#define WUFFS_BASE__LIKELY(expr) (expr) +#define WUFFS_BASE__UNLIKELY(expr) (expr) +#endif + +// -------- + +static inline wuffs_base__empty_struct // +wuffs_base__ignore_status(wuffs_base__status z) { + return wuffs_base__make_empty_struct(); +} + +static inline wuffs_base__status // +wuffs_base__status__ensure_not_a_suspension(wuffs_base__status z) { + if (z.repr && (*z.repr == '$')) { + z.repr = wuffs_base__error__cannot_return_a_suspension; + } + return z; +} + +// -------- + +// wuffs_base__iterate_total_advance returns the exclusive pointer-offset at +// which iteration should stop. The overall slice has length total_len, each +// iteration's sub-slice has length iter_len and are placed iter_advance apart. +// +// The iter_advance may not be larger than iter_len. The iter_advance may be +// smaller than iter_len, in which case the sub-slices will overlap. +// +// The return value r satisfies ((0 <= r) && (r <= total_len)). +// +// For example, if total_len = 15, iter_len = 5 and iter_advance = 3, there are +// four iterations at offsets 0, 3, 6 and 9. This function returns 12. +// +// 0123456789012345 +// [....] +// [....] +// [....] +// [....] +// $ +// 0123456789012345 +// +// For example, if total_len = 15, iter_len = 5 and iter_advance = 5, there are +// three iterations at offsets 0, 5 and 10. This function returns 15. +// +// 0123456789012345 +// [....] +// [....] +// [....] +// $ +// 0123456789012345 +static inline size_t // +wuffs_base__iterate_total_advance(size_t total_len, + size_t iter_len, + size_t iter_advance) { + if (total_len >= iter_len) { + size_t n = total_len - iter_len; + return ((n / iter_advance) * iter_advance) + iter_advance; + } + return 0; +} + +// ---------------- Numeric Types + +extern const uint8_t wuffs_base__low_bits_mask__u8[8]; +extern const uint16_t wuffs_base__low_bits_mask__u16[16]; +extern const uint32_t wuffs_base__low_bits_mask__u32[32]; +extern const uint64_t wuffs_base__low_bits_mask__u64[64]; + +#define WUFFS_BASE__LOW_BITS_MASK__U8(n) (wuffs_base__low_bits_mask__u8[n]) +#define WUFFS_BASE__LOW_BITS_MASK__U16(n) (wuffs_base__low_bits_mask__u16[n]) +#define WUFFS_BASE__LOW_BITS_MASK__U32(n) (wuffs_base__low_bits_mask__u32[n]) +#define WUFFS_BASE__LOW_BITS_MASK__U64(n) (wuffs_base__low_bits_mask__u64[n]) + +// -------- + +static inline void // +wuffs_base__u8__sat_add_indirect(uint8_t* x, uint8_t y) { + *x = wuffs_base__u8__sat_add(*x, y); +} + +static inline void // +wuffs_base__u8__sat_sub_indirect(uint8_t* x, uint8_t y) { + *x = wuffs_base__u8__sat_sub(*x, y); +} + +static inline void // +wuffs_base__u16__sat_add_indirect(uint16_t* x, uint16_t y) { + *x = wuffs_base__u16__sat_add(*x, y); +} + +static inline void // +wuffs_base__u16__sat_sub_indirect(uint16_t* x, uint16_t y) { + *x = wuffs_base__u16__sat_sub(*x, y); +} + +static inline void // +wuffs_base__u32__sat_add_indirect(uint32_t* x, uint32_t y) { + *x = wuffs_base__u32__sat_add(*x, y); +} + +static inline void // +wuffs_base__u32__sat_sub_indirect(uint32_t* x, uint32_t y) { + *x = wuffs_base__u32__sat_sub(*x, y); +} + +static inline void // +wuffs_base__u64__sat_add_indirect(uint64_t* x, uint64_t y) { + *x = wuffs_base__u64__sat_add(*x, y); +} + +static inline void // +wuffs_base__u64__sat_sub_indirect(uint64_t* x, uint64_t y) { + *x = wuffs_base__u64__sat_sub(*x, y); +} + +// ---------------- Slices and Tables + +// wuffs_base__slice_u8__prefix returns up to the first up_to bytes of s. +static inline wuffs_base__slice_u8 // +wuffs_base__slice_u8__prefix(wuffs_base__slice_u8 s, uint64_t up_to) { + if (((uint64_t)(s.len)) > up_to) { + s.len = ((size_t)up_to); + } + return s; +} + +// wuffs_base__slice_u8__suffix returns up to the last up_to bytes of s. +static inline wuffs_base__slice_u8 // +wuffs_base__slice_u8__suffix(wuffs_base__slice_u8 s, uint64_t up_to) { + if (((uint64_t)(s.len)) > up_to) { + s.ptr += ((uint64_t)(s.len)) - up_to; + s.len = ((size_t)up_to); + } + return s; +} + +// wuffs_base__slice_u8__copy_from_slice calls memmove(dst.ptr, src.ptr, len) +// where len is the minimum of dst.len and src.len. +// +// Passing a wuffs_base__slice_u8 with all fields NULL or zero (a valid, empty +// slice) is valid and results in a no-op. +static inline uint64_t // +wuffs_base__slice_u8__copy_from_slice(wuffs_base__slice_u8 dst, + wuffs_base__slice_u8 src) { + size_t len = dst.len < src.len ? dst.len : src.len; + if (len > 0) { + memmove(dst.ptr, src.ptr, len); + } + return len; +} + +// -------- + +static inline wuffs_base__slice_u8 // +wuffs_base__table_u8__row_u32(wuffs_base__table_u8 t, uint32_t y) { + if (y < t.height) { + return wuffs_base__make_slice_u8(t.ptr + (t.stride * y), t.width); + } + return wuffs_base__make_slice_u8(NULL, 0); +} + +// ---------------- Slices and Tables (Utility) + +#define wuffs_base__utility__empty_slice_u8 wuffs_base__empty_slice_u8 + +// ---------------- Ranges and Rects + +static inline uint32_t // +wuffs_base__range_ii_u32__get_min_incl(const wuffs_base__range_ii_u32* r) { + return r->min_incl; +} + +static inline uint32_t // +wuffs_base__range_ii_u32__get_max_incl(const wuffs_base__range_ii_u32* r) { + return r->max_incl; +} + +static inline uint32_t // +wuffs_base__range_ie_u32__get_min_incl(const wuffs_base__range_ie_u32* r) { + return r->min_incl; +} + +static inline uint32_t // +wuffs_base__range_ie_u32__get_max_excl(const wuffs_base__range_ie_u32* r) { + return r->max_excl; +} + +static inline uint64_t // +wuffs_base__range_ii_u64__get_min_incl(const wuffs_base__range_ii_u64* r) { + return r->min_incl; +} + +static inline uint64_t // +wuffs_base__range_ii_u64__get_max_incl(const wuffs_base__range_ii_u64* r) { + return r->max_incl; +} + +static inline uint64_t // +wuffs_base__range_ie_u64__get_min_incl(const wuffs_base__range_ie_u64* r) { + return r->min_incl; +} + +static inline uint64_t // +wuffs_base__range_ie_u64__get_max_excl(const wuffs_base__range_ie_u64* r) { + return r->max_excl; +} + +// ---------------- Ranges and Rects (Utility) + +#define wuffs_base__utility__empty_range_ii_u32 wuffs_base__empty_range_ii_u32 +#define wuffs_base__utility__empty_range_ie_u32 wuffs_base__empty_range_ie_u32 +#define wuffs_base__utility__empty_range_ii_u64 wuffs_base__empty_range_ii_u64 +#define wuffs_base__utility__empty_range_ie_u64 wuffs_base__empty_range_ie_u64 +#define wuffs_base__utility__empty_rect_ii_u32 wuffs_base__empty_rect_ii_u32 +#define wuffs_base__utility__empty_rect_ie_u32 wuffs_base__empty_rect_ie_u32 +#define wuffs_base__utility__make_range_ii_u32 wuffs_base__make_range_ii_u32 +#define wuffs_base__utility__make_range_ie_u32 wuffs_base__make_range_ie_u32 +#define wuffs_base__utility__make_range_ii_u64 wuffs_base__make_range_ii_u64 +#define wuffs_base__utility__make_range_ie_u64 wuffs_base__make_range_ie_u64 +#define wuffs_base__utility__make_rect_ii_u32 wuffs_base__make_rect_ii_u32 +#define wuffs_base__utility__make_rect_ie_u32 wuffs_base__make_rect_ie_u32 + +// ---------------- I/O + +static inline uint64_t // +wuffs_base__io__count_since(uint64_t mark, uint64_t index) { + if (index >= mark) { + return index - mark; + } + return 0; +} + +// TODO: drop the "const" in "const uint8_t* ptr". Some though required about +// the base.io_reader.since method returning a mutable "slice base.u8". +#if defined(__GNUC__) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wcast-qual" +#endif +static inline wuffs_base__slice_u8 // +wuffs_base__io__since(uint64_t mark, uint64_t index, const uint8_t* ptr) { + if (index >= mark) { + return wuffs_base__make_slice_u8(((uint8_t*)ptr) + mark, + ((size_t)(index - mark))); + } + return wuffs_base__make_slice_u8(NULL, 0); +} +#if defined(__GNUC__) +#pragma GCC diagnostic pop +#endif + +// -------- + +static inline void // +wuffs_base__io_reader__limit(const uint8_t** ptr_io2_r, + const uint8_t* iop_r, + uint64_t limit) { + if (((uint64_t)(*ptr_io2_r - iop_r)) > limit) { + *ptr_io2_r = iop_r + limit; + } +} + +static inline uint32_t // +wuffs_base__io_reader__limited_copy_u32_to_slice(const uint8_t** ptr_iop_r, + const uint8_t* io2_r, + uint32_t length, + wuffs_base__slice_u8 dst) { + const uint8_t* iop_r = *ptr_iop_r; + size_t n = dst.len; + if (n > length) { + n = length; + } + if (n > ((size_t)(io2_r - iop_r))) { + n = (size_t)(io2_r - iop_r); + } + if (n > 0) { + memmove(dst.ptr, iop_r, n); + *ptr_iop_r += n; + } + return (uint32_t)(n); +} + +// wuffs_base__io_reader__match7 returns whether the io_reader's upcoming bytes +// start with the given prefix (up to 7 bytes long). It is peek-like, not +// read-like, in that there are no side-effects. +// +// The low 3 bits of a hold the prefix length, n. +// +// The high 56 bits of a hold the prefix itself, in little-endian order. The +// first prefix byte is in bits 8..=15, the second prefix byte is in bits +// 16..=23, etc. The high (8 * (7 - n)) bits are ignored. +// +// There are three possible return values: +// - 0 means success. +// - 1 means inconclusive, equivalent to "$short read". +// - 2 means failure. +static inline uint32_t // +wuffs_base__io_reader__match7(const uint8_t* iop_r, + const uint8_t* io2_r, + wuffs_base__io_buffer* r, + uint64_t a) { + uint32_t n = a & 7; + a >>= 8; + if ((io2_r - iop_r) >= 8) { + uint64_t x = wuffs_base__peek_u64le__no_bounds_check(iop_r); + uint32_t shift = 8 * (8 - n); + return ((a << shift) == (x << shift)) ? 0 : 2; + } + for (; n > 0; n--) { + if (iop_r >= io2_r) { + return (r && r->meta.closed) ? 2 : 1; + } else if (*iop_r != ((uint8_t)(a))) { + return 2; + } + iop_r++; + a >>= 8; + } + return 0; +} + +static inline wuffs_base__io_buffer* // +wuffs_base__io_reader__set(wuffs_base__io_buffer* b, + const uint8_t** ptr_iop_r, + const uint8_t** ptr_io0_r, + const uint8_t** ptr_io1_r, + const uint8_t** ptr_io2_r, + wuffs_base__slice_u8 data, + uint64_t history_position) { + b->data = data; + b->meta.wi = data.len; + b->meta.ri = 0; + b->meta.pos = history_position; + b->meta.closed = false; + + *ptr_iop_r = data.ptr; + *ptr_io0_r = data.ptr; + *ptr_io1_r = data.ptr; + *ptr_io2_r = data.ptr + data.len; + + return b; +} + +// -------- + +static inline uint64_t // +wuffs_base__io_writer__copy_from_slice(uint8_t** ptr_iop_w, + uint8_t* io2_w, + wuffs_base__slice_u8 src) { + uint8_t* iop_w = *ptr_iop_w; + size_t n = src.len; + if (n > ((size_t)(io2_w - iop_w))) { + n = (size_t)(io2_w - iop_w); + } + if (n > 0) { + memmove(iop_w, src.ptr, n); + *ptr_iop_w += n; + } + return (uint64_t)(n); +} + +static inline void // +wuffs_base__io_writer__limit(uint8_t** ptr_io2_w, + uint8_t* iop_w, + uint64_t limit) { + if (((uint64_t)(*ptr_io2_w - iop_w)) > limit) { + *ptr_io2_w = iop_w + limit; + } +} + +static inline uint32_t // +wuffs_base__io_writer__limited_copy_u32_from_history(uint8_t** ptr_iop_w, + uint8_t* io0_w, + uint8_t* io2_w, + uint32_t length, + uint32_t distance) { + if (!distance) { + return 0; + } + uint8_t* p = *ptr_iop_w; + if ((size_t)(p - io0_w) < (size_t)(distance)) { + return 0; + } + uint8_t* q = p - distance; + size_t n = (size_t)(io2_w - p); + if ((size_t)(length) > n) { + length = (uint32_t)(n); + } else { + n = (size_t)(length); + } + // TODO: unrolling by 3 seems best for the std/deflate benchmarks, but that + // is mostly because 3 is the minimum length for the deflate format. This + // function implementation shouldn't overfit to that one format. Perhaps the + // limited_copy_u32_from_history Wuffs method should also take an unroll hint + // argument, and the cgen can look if that argument is the constant + // expression '3'. + // + // See also wuffs_base__io_writer__limited_copy_u32_from_history_fast below. + for (; n >= 3; n -= 3) { + *p++ = *q++; + *p++ = *q++; + *p++ = *q++; + } + for (; n; n--) { + *p++ = *q++; + } + *ptr_iop_w = p; + return length; +} + +// wuffs_base__io_writer__limited_copy_u32_from_history_fast is like the +// wuffs_base__io_writer__limited_copy_u32_from_history function above, but has +// stronger pre-conditions. +// +// The caller needs to prove that: +// - length <= (io2_w - *ptr_iop_w) +// - distance >= 1 +// - distance <= (*ptr_iop_w - io0_w) +static inline uint32_t // +wuffs_base__io_writer__limited_copy_u32_from_history_fast(uint8_t** ptr_iop_w, + uint8_t* io0_w, + uint8_t* io2_w, + uint32_t length, + uint32_t distance) { + uint8_t* p = *ptr_iop_w; + uint8_t* q = p - distance; + uint32_t n = length; + for (; n >= 3; n -= 3) { + *p++ = *q++; + *p++ = *q++; + *p++ = *q++; + } + for (; n; n--) { + *p++ = *q++; + } + *ptr_iop_w = p; + return length; +} + +// wuffs_base__io_writer__limited_copy_u32_from_history_8_byte_chunks_distance_1_fast +// copies the previous byte (the one immediately before *ptr_iop_w), copying 8 +// byte chunks at a time. Each chunk contains 8 repetitions of the same byte. +// +// In terms of number of bytes copied, length is rounded up to a multiple of 8. +// As a special case, a zero length rounds up to 8 (even though 0 is already a +// multiple of 8), since there is always at least one 8 byte chunk copied. +// +// In terms of advancing *ptr_iop_w, length is not rounded up. +// +// The caller needs to prove that: +// - (length + 8) <= (io2_w - *ptr_iop_w) +// - distance == 1 +// - distance <= (*ptr_iop_w - io0_w) +static inline uint32_t // +wuffs_base__io_writer__limited_copy_u32_from_history_8_byte_chunks_distance_1_fast( + uint8_t** ptr_iop_w, + uint8_t* io0_w, + uint8_t* io2_w, + uint32_t length, + uint32_t distance) { + uint8_t* p = *ptr_iop_w; + uint64_t x = p[-1]; + x |= x << 8; + x |= x << 16; + x |= x << 32; + uint32_t n = length; + while (1) { + wuffs_base__poke_u64le__no_bounds_check(p, x); + if (n <= 8) { + p += n; + break; + } + p += 8; + n -= 8; + } + *ptr_iop_w = p; + return length; +} + +// wuffs_base__io_writer__limited_copy_u32_from_history_8_byte_chunks_fast is +// like the wuffs_base__io_writer__limited_copy_u32_from_history_fast function +// above, but copies 8 byte chunks at a time. +// +// In terms of number of bytes copied, length is rounded up to a multiple of 8. +// As a special case, a zero length rounds up to 8 (even though 0 is already a +// multiple of 8), since there is always at least one 8 byte chunk copied. +// +// In terms of advancing *ptr_iop_w, length is not rounded up. +// +// The caller needs to prove that: +// - (length + 8) <= (io2_w - *ptr_iop_w) +// - distance >= 8 +// - distance <= (*ptr_iop_w - io0_w) +static inline uint32_t // +wuffs_base__io_writer__limited_copy_u32_from_history_8_byte_chunks_fast( + uint8_t** ptr_iop_w, + uint8_t* io0_w, + uint8_t* io2_w, + uint32_t length, + uint32_t distance) { + uint8_t* p = *ptr_iop_w; + uint8_t* q = p - distance; + uint32_t n = length; + while (1) { + memcpy(p, q, 8); + if (n <= 8) { + p += n; + break; + } + p += 8; + q += 8; + n -= 8; + } + *ptr_iop_w = p; + return length; +} + +static inline uint32_t // +wuffs_base__io_writer__limited_copy_u32_from_reader(uint8_t** ptr_iop_w, + uint8_t* io2_w, + uint32_t length, + const uint8_t** ptr_iop_r, + const uint8_t* io2_r) { + uint8_t* iop_w = *ptr_iop_w; + size_t n = length; + if (n > ((size_t)(io2_w - iop_w))) { + n = (size_t)(io2_w - iop_w); + } + const uint8_t* iop_r = *ptr_iop_r; + if (n > ((size_t)(io2_r - iop_r))) { + n = (size_t)(io2_r - iop_r); + } + if (n > 0) { + memmove(iop_w, iop_r, n); + *ptr_iop_w += n; + *ptr_iop_r += n; + } + return (uint32_t)(n); +} + +static inline uint32_t // +wuffs_base__io_writer__limited_copy_u32_from_slice(uint8_t** ptr_iop_w, + uint8_t* io2_w, + uint32_t length, + wuffs_base__slice_u8 src) { + uint8_t* iop_w = *ptr_iop_w; + size_t n = src.len; + if (n > length) { + n = length; + } + if (n > ((size_t)(io2_w - iop_w))) { + n = (size_t)(io2_w - iop_w); + } + if (n > 0) { + memmove(iop_w, src.ptr, n); + *ptr_iop_w += n; + } + return (uint32_t)(n); +} + +static inline wuffs_base__io_buffer* // +wuffs_base__io_writer__set(wuffs_base__io_buffer* b, + uint8_t** ptr_iop_w, + uint8_t** ptr_io0_w, + uint8_t** ptr_io1_w, + uint8_t** ptr_io2_w, + wuffs_base__slice_u8 data, + uint64_t history_position) { + b->data = data; + b->meta.wi = 0; + b->meta.ri = 0; + b->meta.pos = history_position; + b->meta.closed = false; + + *ptr_iop_w = data.ptr; + *ptr_io0_w = data.ptr; + *ptr_io1_w = data.ptr; + *ptr_io2_w = data.ptr + data.len; + + return b; +} + +// ---------------- I/O (Utility) + +#define wuffs_base__utility__empty_io_reader wuffs_base__empty_io_reader +#define wuffs_base__utility__empty_io_writer wuffs_base__empty_io_writer + +// ---------------- Tokens + +// ---------------- Tokens (Utility) + +// ---------------- Memory Allocation + +// ---------------- Images + +WUFFS_BASE__MAYBE_STATIC uint64_t // +wuffs_base__pixel_swizzler__limited_swizzle_u32_interleaved_from_reader( + const wuffs_base__pixel_swizzler* p, + uint32_t up_to_num_pixels, + wuffs_base__slice_u8 dst, + wuffs_base__slice_u8 dst_palette, + const uint8_t** ptr_iop_r, + const uint8_t* io2_r); + +WUFFS_BASE__MAYBE_STATIC uint64_t // +wuffs_base__pixel_swizzler__swizzle_interleaved_from_reader( + const wuffs_base__pixel_swizzler* p, + wuffs_base__slice_u8 dst, + wuffs_base__slice_u8 dst_palette, + const uint8_t** ptr_iop_r, + const uint8_t* io2_r); + +WUFFS_BASE__MAYBE_STATIC uint64_t // +wuffs_base__pixel_swizzler__swizzle_interleaved_transparent_black( + const wuffs_base__pixel_swizzler* p, + wuffs_base__slice_u8 dst, + wuffs_base__slice_u8 dst_palette, + uint64_t num_pixels); + +// ---------------- Images (Utility) + +#define wuffs_base__utility__make_pixel_format wuffs_base__make_pixel_format + +// ---------------- String Conversions + +// ---------------- Unicode and UTF-8 + +// ---------------- + +#if !defined(WUFFS_CONFIG__MODULES) || defined(WUFFS_CONFIG__MODULE__BASE) || \ + defined(WUFFS_CONFIG__MODULE__BASE__CORE) + +const uint8_t wuffs_base__low_bits_mask__u8[8] = { + 0x00, 0x01, 0x03, 0x07, 0x0F, 0x1F, 0x3F, 0x7F, +}; + +const uint16_t wuffs_base__low_bits_mask__u16[16] = { + 0x0000, 0x0001, 0x0003, 0x0007, 0x000F, 0x001F, 0x003F, 0x007F, + 0x00FF, 0x01FF, 0x03FF, 0x07FF, 0x0FFF, 0x1FFF, 0x3FFF, 0x7FFF, +}; + +const uint32_t wuffs_base__low_bits_mask__u32[32] = { + 0x00000000, 0x00000001, 0x00000003, 0x00000007, 0x0000000F, 0x0000001F, + 0x0000003F, 0x0000007F, 0x000000FF, 0x000001FF, 0x000003FF, 0x000007FF, + 0x00000FFF, 0x00001FFF, 0x00003FFF, 0x00007FFF, 0x0000FFFF, 0x0001FFFF, + 0x0003FFFF, 0x0007FFFF, 0x000FFFFF, 0x001FFFFF, 0x003FFFFF, 0x007FFFFF, + 0x00FFFFFF, 0x01FFFFFF, 0x03FFFFFF, 0x07FFFFFF, 0x0FFFFFFF, 0x1FFFFFFF, + 0x3FFFFFFF, 0x7FFFFFFF, +}; + +const uint64_t wuffs_base__low_bits_mask__u64[64] = { + 0x0000000000000000, 0x0000000000000001, 0x0000000000000003, + 0x0000000000000007, 0x000000000000000F, 0x000000000000001F, + 0x000000000000003F, 0x000000000000007F, 0x00000000000000FF, + 0x00000000000001FF, 0x00000000000003FF, 0x00000000000007FF, + 0x0000000000000FFF, 0x0000000000001FFF, 0x0000000000003FFF, + 0x0000000000007FFF, 0x000000000000FFFF, 0x000000000001FFFF, + 0x000000000003FFFF, 0x000000000007FFFF, 0x00000000000FFFFF, + 0x00000000001FFFFF, 0x00000000003FFFFF, 0x00000000007FFFFF, + 0x0000000000FFFFFF, 0x0000000001FFFFFF, 0x0000000003FFFFFF, + 0x0000000007FFFFFF, 0x000000000FFFFFFF, 0x000000001FFFFFFF, + 0x000000003FFFFFFF, 0x000000007FFFFFFF, 0x00000000FFFFFFFF, + 0x00000001FFFFFFFF, 0x00000003FFFFFFFF, 0x00000007FFFFFFFF, + 0x0000000FFFFFFFFF, 0x0000001FFFFFFFFF, 0x0000003FFFFFFFFF, + 0x0000007FFFFFFFFF, 0x000000FFFFFFFFFF, 0x000001FFFFFFFFFF, + 0x000003FFFFFFFFFF, 0x000007FFFFFFFFFF, 0x00000FFFFFFFFFFF, + 0x00001FFFFFFFFFFF, 0x00003FFFFFFFFFFF, 0x00007FFFFFFFFFFF, + 0x0000FFFFFFFFFFFF, 0x0001FFFFFFFFFFFF, 0x0003FFFFFFFFFFFF, + 0x0007FFFFFFFFFFFF, 0x000FFFFFFFFFFFFF, 0x001FFFFFFFFFFFFF, + 0x003FFFFFFFFFFFFF, 0x007FFFFFFFFFFFFF, 0x00FFFFFFFFFFFFFF, + 0x01FFFFFFFFFFFFFF, 0x03FFFFFFFFFFFFFF, 0x07FFFFFFFFFFFFFF, + 0x0FFFFFFFFFFFFFFF, 0x1FFFFFFFFFFFFFFF, 0x3FFFFFFFFFFFFFFF, + 0x7FFFFFFFFFFFFFFF, +}; + +const uint32_t wuffs_base__pixel_format__bits_per_channel[16] = { + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x0A, 0x0C, 0x10, 0x18, 0x20, 0x30, 0x40, +}; + +const char wuffs_base__note__i_o_redirect[] = "@base: I/O redirect"; +const char wuffs_base__note__end_of_data[] = "@base: end of data"; +const char wuffs_base__note__metadata_reported[] = "@base: metadata reported"; +const char wuffs_base__suspension__even_more_information[] = "$base: even more information"; +const char wuffs_base__suspension__mispositioned_read[] = "$base: mispositioned read"; +const char wuffs_base__suspension__mispositioned_write[] = "$base: mispositioned write"; +const char wuffs_base__suspension__short_read[] = "$base: short read"; +const char wuffs_base__suspension__short_write[] = "$base: short write"; +const char wuffs_base__error__bad_i_o_position[] = "#base: bad I/O position"; +const char wuffs_base__error__bad_argument_length_too_short[] = "#base: bad argument (length too short)"; +const char wuffs_base__error__bad_argument[] = "#base: bad argument"; +const char wuffs_base__error__bad_call_sequence[] = "#base: bad call sequence"; +const char wuffs_base__error__bad_data[] = "#base: bad data"; +const char wuffs_base__error__bad_receiver[] = "#base: bad receiver"; +const char wuffs_base__error__bad_restart[] = "#base: bad restart"; +const char wuffs_base__error__bad_sizeof_receiver[] = "#base: bad sizeof receiver"; +const char wuffs_base__error__bad_vtable[] = "#base: bad vtable"; +const char wuffs_base__error__bad_workbuf_length[] = "#base: bad workbuf length"; +const char wuffs_base__error__bad_wuffs_version[] = "#base: bad wuffs version"; +const char wuffs_base__error__cannot_return_a_suspension[] = "#base: cannot return a suspension"; +const char wuffs_base__error__disabled_by_previous_error[] = "#base: disabled by previous error"; +const char wuffs_base__error__initialize_falsely_claimed_already_zeroed[] = "#base: initialize falsely claimed already zeroed"; +const char wuffs_base__error__initialize_not_called[] = "#base: initialize not called"; +const char wuffs_base__error__interleaved_coroutine_calls[] = "#base: interleaved coroutine calls"; +const char wuffs_base__error__no_more_information[] = "#base: no more information"; +const char wuffs_base__error__not_enough_data[] = "#base: not enough data"; +const char wuffs_base__error__out_of_bounds[] = "#base: out of bounds"; +const char wuffs_base__error__unsupported_method[] = "#base: unsupported method"; +const char wuffs_base__error__unsupported_option[] = "#base: unsupported option"; +const char wuffs_base__error__unsupported_pixel_swizzler_option[] = "#base: unsupported pixel swizzler option"; +const char wuffs_base__error__too_much_data[] = "#base: too much data"; + +const char wuffs_base__hasher_u32__vtable_name[] = "{vtable}wuffs_base__hasher_u32"; +const char wuffs_base__image_decoder__vtable_name[] = "{vtable}wuffs_base__image_decoder"; +const char wuffs_base__io_transformer__vtable_name[] = "{vtable}wuffs_base__io_transformer"; +const char wuffs_base__token_decoder__vtable_name[] = "{vtable}wuffs_base__token_decoder"; + +#endif // !defined(WUFFS_CONFIG__MODULES) || + // defined(WUFFS_CONFIG__MODULE__BASE) || + // defined(WUFFS_CONFIG__MODULE__BASE__CORE) + +#if !defined(WUFFS_CONFIG__MODULES) || defined(WUFFS_CONFIG__MODULE__BASE) || \ + defined(WUFFS_CONFIG__MODULE__BASE__INTERFACES) + +// ---------------- Interface Definitions. + +WUFFS_BASE__MAYBE_STATIC wuffs_base__empty_struct +wuffs_base__hasher_u32__set_quirk_enabled( + wuffs_base__hasher_u32* self, + uint32_t a_quirk, + bool a_enabled) { + if (!self) { + return wuffs_base__make_empty_struct(); + } + if (self->private_impl.magic != WUFFS_BASE__MAGIC) { + return wuffs_base__make_empty_struct(); + } + + const wuffs_base__vtable* v = &self->private_impl.first_vtable; + int i; + for (i = 0; i < 63; i++) { + if (v->vtable_name == wuffs_base__hasher_u32__vtable_name) { + const wuffs_base__hasher_u32__func_ptrs* func_ptrs = + (const wuffs_base__hasher_u32__func_ptrs*)(v->function_pointers); + return (*func_ptrs->set_quirk_enabled)(self, a_quirk, a_enabled); + } else if (v->vtable_name == NULL) { + break; + } + v++; + } + + return wuffs_base__make_empty_struct(); +} + +WUFFS_BASE__MAYBE_STATIC uint32_t +wuffs_base__hasher_u32__update_u32( + wuffs_base__hasher_u32* self, + wuffs_base__slice_u8 a_x) { + if (!self) { + return 0; + } + if (self->private_impl.magic != WUFFS_BASE__MAGIC) { + return 0; + } + + const wuffs_base__vtable* v = &self->private_impl.first_vtable; + int i; + for (i = 0; i < 63; i++) { + if (v->vtable_name == wuffs_base__hasher_u32__vtable_name) { + const wuffs_base__hasher_u32__func_ptrs* func_ptrs = + (const wuffs_base__hasher_u32__func_ptrs*)(v->function_pointers); + return (*func_ptrs->update_u32)(self, a_x); + } else if (v->vtable_name == NULL) { + break; + } + v++; + } + + return 0; +} + +// -------- + +WUFFS_BASE__MAYBE_STATIC wuffs_base__status +wuffs_base__image_decoder__decode_frame( + wuffs_base__image_decoder* self, + wuffs_base__pixel_buffer* a_dst, + wuffs_base__io_buffer* a_src, + wuffs_base__pixel_blend a_blend, + wuffs_base__slice_u8 a_workbuf, + wuffs_base__decode_frame_options* a_opts) { + if (!self) { + return wuffs_base__make_status(wuffs_base__error__bad_receiver); + } + if (self->private_impl.magic != WUFFS_BASE__MAGIC) { + return wuffs_base__make_status( + (self->private_impl.magic == WUFFS_BASE__DISABLED) + ? wuffs_base__error__disabled_by_previous_error + : wuffs_base__error__initialize_not_called); + } + + const wuffs_base__vtable* v = &self->private_impl.first_vtable; + int i; + for (i = 0; i < 63; i++) { + if (v->vtable_name == wuffs_base__image_decoder__vtable_name) { + const wuffs_base__image_decoder__func_ptrs* func_ptrs = + (const wuffs_base__image_decoder__func_ptrs*)(v->function_pointers); + return (*func_ptrs->decode_frame)(self, a_dst, a_src, a_blend, a_workbuf, a_opts); + } else if (v->vtable_name == NULL) { + break; + } + v++; + } + + return wuffs_base__make_status(wuffs_base__error__bad_vtable); +} + +WUFFS_BASE__MAYBE_STATIC wuffs_base__status +wuffs_base__image_decoder__decode_frame_config( + wuffs_base__image_decoder* self, + wuffs_base__frame_config* a_dst, + wuffs_base__io_buffer* a_src) { + if (!self) { + return wuffs_base__make_status(wuffs_base__error__bad_receiver); + } + if (self->private_impl.magic != WUFFS_BASE__MAGIC) { + return wuffs_base__make_status( + (self->private_impl.magic == WUFFS_BASE__DISABLED) + ? wuffs_base__error__disabled_by_previous_error + : wuffs_base__error__initialize_not_called); + } + + const wuffs_base__vtable* v = &self->private_impl.first_vtable; + int i; + for (i = 0; i < 63; i++) { + if (v->vtable_name == wuffs_base__image_decoder__vtable_name) { + const wuffs_base__image_decoder__func_ptrs* func_ptrs = + (const wuffs_base__image_decoder__func_ptrs*)(v->function_pointers); + return (*func_ptrs->decode_frame_config)(self, a_dst, a_src); + } else if (v->vtable_name == NULL) { + break; + } + v++; + } + + return wuffs_base__make_status(wuffs_base__error__bad_vtable); +} + +WUFFS_BASE__MAYBE_STATIC wuffs_base__status +wuffs_base__image_decoder__decode_image_config( + wuffs_base__image_decoder* self, + wuffs_base__image_config* a_dst, + wuffs_base__io_buffer* a_src) { + if (!self) { + return wuffs_base__make_status(wuffs_base__error__bad_receiver); + } + if (self->private_impl.magic != WUFFS_BASE__MAGIC) { + return wuffs_base__make_status( + (self->private_impl.magic == WUFFS_BASE__DISABLED) + ? wuffs_base__error__disabled_by_previous_error + : wuffs_base__error__initialize_not_called); + } + + const wuffs_base__vtable* v = &self->private_impl.first_vtable; + int i; + for (i = 0; i < 63; i++) { + if (v->vtable_name == wuffs_base__image_decoder__vtable_name) { + const wuffs_base__image_decoder__func_ptrs* func_ptrs = + (const wuffs_base__image_decoder__func_ptrs*)(v->function_pointers); + return (*func_ptrs->decode_image_config)(self, a_dst, a_src); + } else if (v->vtable_name == NULL) { + break; + } + v++; + } + + return wuffs_base__make_status(wuffs_base__error__bad_vtable); +} + +WUFFS_BASE__MAYBE_STATIC wuffs_base__rect_ie_u32 +wuffs_base__image_decoder__frame_dirty_rect( + const wuffs_base__image_decoder* self) { + if (!self) { + return wuffs_base__utility__empty_rect_ie_u32(); + } + if ((self->private_impl.magic != WUFFS_BASE__MAGIC) && + (self->private_impl.magic != WUFFS_BASE__DISABLED)) { + return wuffs_base__utility__empty_rect_ie_u32(); + } + + const wuffs_base__vtable* v = &self->private_impl.first_vtable; + int i; + for (i = 0; i < 63; i++) { + if (v->vtable_name == wuffs_base__image_decoder__vtable_name) { + const wuffs_base__image_decoder__func_ptrs* func_ptrs = + (const wuffs_base__image_decoder__func_ptrs*)(v->function_pointers); + return (*func_ptrs->frame_dirty_rect)(self); + } else if (v->vtable_name == NULL) { + break; + } + v++; + } + + return wuffs_base__utility__empty_rect_ie_u32(); +} + +WUFFS_BASE__MAYBE_STATIC uint32_t +wuffs_base__image_decoder__num_animation_loops( + const wuffs_base__image_decoder* self) { + if (!self) { + return 0; + } + if ((self->private_impl.magic != WUFFS_BASE__MAGIC) && + (self->private_impl.magic != WUFFS_BASE__DISABLED)) { + return 0; + } + + const wuffs_base__vtable* v = &self->private_impl.first_vtable; + int i; + for (i = 0; i < 63; i++) { + if (v->vtable_name == wuffs_base__image_decoder__vtable_name) { + const wuffs_base__image_decoder__func_ptrs* func_ptrs = + (const wuffs_base__image_decoder__func_ptrs*)(v->function_pointers); + return (*func_ptrs->num_animation_loops)(self); + } else if (v->vtable_name == NULL) { + break; + } + v++; + } + + return 0; +} + +WUFFS_BASE__MAYBE_STATIC uint64_t +wuffs_base__image_decoder__num_decoded_frame_configs( + const wuffs_base__image_decoder* self) { + if (!self) { + return 0; + } + if ((self->private_impl.magic != WUFFS_BASE__MAGIC) && + (self->private_impl.magic != WUFFS_BASE__DISABLED)) { + return 0; + } + + const wuffs_base__vtable* v = &self->private_impl.first_vtable; + int i; + for (i = 0; i < 63; i++) { + if (v->vtable_name == wuffs_base__image_decoder__vtable_name) { + const wuffs_base__image_decoder__func_ptrs* func_ptrs = + (const wuffs_base__image_decoder__func_ptrs*)(v->function_pointers); + return (*func_ptrs->num_decoded_frame_configs)(self); + } else if (v->vtable_name == NULL) { + break; + } + v++; + } + + return 0; +} + +WUFFS_BASE__MAYBE_STATIC uint64_t +wuffs_base__image_decoder__num_decoded_frames( + const wuffs_base__image_decoder* self) { + if (!self) { + return 0; + } + if ((self->private_impl.magic != WUFFS_BASE__MAGIC) && + (self->private_impl.magic != WUFFS_BASE__DISABLED)) { + return 0; + } + + const wuffs_base__vtable* v = &self->private_impl.first_vtable; + int i; + for (i = 0; i < 63; i++) { + if (v->vtable_name == wuffs_base__image_decoder__vtable_name) { + const wuffs_base__image_decoder__func_ptrs* func_ptrs = + (const wuffs_base__image_decoder__func_ptrs*)(v->function_pointers); + return (*func_ptrs->num_decoded_frames)(self); + } else if (v->vtable_name == NULL) { + break; + } + v++; + } + + return 0; +} + +WUFFS_BASE__MAYBE_STATIC wuffs_base__status +wuffs_base__image_decoder__restart_frame( + wuffs_base__image_decoder* self, + uint64_t a_index, + uint64_t a_io_position) { + if (!self) { + return wuffs_base__make_status(wuffs_base__error__bad_receiver); + } + if (self->private_impl.magic != WUFFS_BASE__MAGIC) { + return wuffs_base__make_status( + (self->private_impl.magic == WUFFS_BASE__DISABLED) + ? wuffs_base__error__disabled_by_previous_error + : wuffs_base__error__initialize_not_called); + } + + const wuffs_base__vtable* v = &self->private_impl.first_vtable; + int i; + for (i = 0; i < 63; i++) { + if (v->vtable_name == wuffs_base__image_decoder__vtable_name) { + const wuffs_base__image_decoder__func_ptrs* func_ptrs = + (const wuffs_base__image_decoder__func_ptrs*)(v->function_pointers); + return (*func_ptrs->restart_frame)(self, a_index, a_io_position); + } else if (v->vtable_name == NULL) { + break; + } + v++; + } + + return wuffs_base__make_status(wuffs_base__error__bad_vtable); +} + +WUFFS_BASE__MAYBE_STATIC wuffs_base__empty_struct +wuffs_base__image_decoder__set_quirk_enabled( + wuffs_base__image_decoder* self, + uint32_t a_quirk, + bool a_enabled) { + if (!self) { + return wuffs_base__make_empty_struct(); + } + if (self->private_impl.magic != WUFFS_BASE__MAGIC) { + return wuffs_base__make_empty_struct(); + } + + const wuffs_base__vtable* v = &self->private_impl.first_vtable; + int i; + for (i = 0; i < 63; i++) { + if (v->vtable_name == wuffs_base__image_decoder__vtable_name) { + const wuffs_base__image_decoder__func_ptrs* func_ptrs = + (const wuffs_base__image_decoder__func_ptrs*)(v->function_pointers); + return (*func_ptrs->set_quirk_enabled)(self, a_quirk, a_enabled); + } else if (v->vtable_name == NULL) { + break; + } + v++; + } + + return wuffs_base__make_empty_struct(); +} + +WUFFS_BASE__MAYBE_STATIC wuffs_base__empty_struct +wuffs_base__image_decoder__set_report_metadata( + wuffs_base__image_decoder* self, + uint32_t a_fourcc, + bool a_report) { + if (!self) { + return wuffs_base__make_empty_struct(); + } + if (self->private_impl.magic != WUFFS_BASE__MAGIC) { + return wuffs_base__make_empty_struct(); + } + + const wuffs_base__vtable* v = &self->private_impl.first_vtable; + int i; + for (i = 0; i < 63; i++) { + if (v->vtable_name == wuffs_base__image_decoder__vtable_name) { + const wuffs_base__image_decoder__func_ptrs* func_ptrs = + (const wuffs_base__image_decoder__func_ptrs*)(v->function_pointers); + return (*func_ptrs->set_report_metadata)(self, a_fourcc, a_report); + } else if (v->vtable_name == NULL) { + break; + } + v++; + } + + return wuffs_base__make_empty_struct(); +} + +WUFFS_BASE__MAYBE_STATIC wuffs_base__status +wuffs_base__image_decoder__tell_me_more( + wuffs_base__image_decoder* self, + wuffs_base__io_buffer* a_dst, + wuffs_base__more_information* a_minfo, + wuffs_base__io_buffer* a_src) { + if (!self) { + return wuffs_base__make_status(wuffs_base__error__bad_receiver); + } + if (self->private_impl.magic != WUFFS_BASE__MAGIC) { + return wuffs_base__make_status( + (self->private_impl.magic == WUFFS_BASE__DISABLED) + ? wuffs_base__error__disabled_by_previous_error + : wuffs_base__error__initialize_not_called); + } + + const wuffs_base__vtable* v = &self->private_impl.first_vtable; + int i; + for (i = 0; i < 63; i++) { + if (v->vtable_name == wuffs_base__image_decoder__vtable_name) { + const wuffs_base__image_decoder__func_ptrs* func_ptrs = + (const wuffs_base__image_decoder__func_ptrs*)(v->function_pointers); + return (*func_ptrs->tell_me_more)(self, a_dst, a_minfo, a_src); + } else if (v->vtable_name == NULL) { + break; + } + v++; + } + + return wuffs_base__make_status(wuffs_base__error__bad_vtable); +} + +WUFFS_BASE__MAYBE_STATIC wuffs_base__range_ii_u64 +wuffs_base__image_decoder__workbuf_len( + const wuffs_base__image_decoder* self) { + if (!self) { + return wuffs_base__utility__empty_range_ii_u64(); + } + if ((self->private_impl.magic != WUFFS_BASE__MAGIC) && + (self->private_impl.magic != WUFFS_BASE__DISABLED)) { + return wuffs_base__utility__empty_range_ii_u64(); + } + + const wuffs_base__vtable* v = &self->private_impl.first_vtable; + int i; + for (i = 0; i < 63; i++) { + if (v->vtable_name == wuffs_base__image_decoder__vtable_name) { + const wuffs_base__image_decoder__func_ptrs* func_ptrs = + (const wuffs_base__image_decoder__func_ptrs*)(v->function_pointers); + return (*func_ptrs->workbuf_len)(self); + } else if (v->vtable_name == NULL) { + break; + } + v++; + } + + return wuffs_base__utility__empty_range_ii_u64(); +} + +// -------- + +WUFFS_BASE__MAYBE_STATIC wuffs_base__empty_struct +wuffs_base__io_transformer__set_quirk_enabled( + wuffs_base__io_transformer* self, + uint32_t a_quirk, + bool a_enabled) { + if (!self) { + return wuffs_base__make_empty_struct(); + } + if (self->private_impl.magic != WUFFS_BASE__MAGIC) { + return wuffs_base__make_empty_struct(); + } + + const wuffs_base__vtable* v = &self->private_impl.first_vtable; + int i; + for (i = 0; i < 63; i++) { + if (v->vtable_name == wuffs_base__io_transformer__vtable_name) { + const wuffs_base__io_transformer__func_ptrs* func_ptrs = + (const wuffs_base__io_transformer__func_ptrs*)(v->function_pointers); + return (*func_ptrs->set_quirk_enabled)(self, a_quirk, a_enabled); + } else if (v->vtable_name == NULL) { + break; + } + v++; + } + + return wuffs_base__make_empty_struct(); +} + +WUFFS_BASE__MAYBE_STATIC wuffs_base__status +wuffs_base__io_transformer__transform_io( + wuffs_base__io_transformer* self, + wuffs_base__io_buffer* a_dst, + wuffs_base__io_buffer* a_src, + wuffs_base__slice_u8 a_workbuf) { + if (!self) { + return wuffs_base__make_status(wuffs_base__error__bad_receiver); + } + if (self->private_impl.magic != WUFFS_BASE__MAGIC) { + return wuffs_base__make_status( + (self->private_impl.magic == WUFFS_BASE__DISABLED) + ? wuffs_base__error__disabled_by_previous_error + : wuffs_base__error__initialize_not_called); + } + + const wuffs_base__vtable* v = &self->private_impl.first_vtable; + int i; + for (i = 0; i < 63; i++) { + if (v->vtable_name == wuffs_base__io_transformer__vtable_name) { + const wuffs_base__io_transformer__func_ptrs* func_ptrs = + (const wuffs_base__io_transformer__func_ptrs*)(v->function_pointers); + return (*func_ptrs->transform_io)(self, a_dst, a_src, a_workbuf); + } else if (v->vtable_name == NULL) { + break; + } + v++; + } + + return wuffs_base__make_status(wuffs_base__error__bad_vtable); +} + +WUFFS_BASE__MAYBE_STATIC wuffs_base__range_ii_u64 +wuffs_base__io_transformer__workbuf_len( + const wuffs_base__io_transformer* self) { + if (!self) { + return wuffs_base__utility__empty_range_ii_u64(); + } + if ((self->private_impl.magic != WUFFS_BASE__MAGIC) && + (self->private_impl.magic != WUFFS_BASE__DISABLED)) { + return wuffs_base__utility__empty_range_ii_u64(); + } + + const wuffs_base__vtable* v = &self->private_impl.first_vtable; + int i; + for (i = 0; i < 63; i++) { + if (v->vtable_name == wuffs_base__io_transformer__vtable_name) { + const wuffs_base__io_transformer__func_ptrs* func_ptrs = + (const wuffs_base__io_transformer__func_ptrs*)(v->function_pointers); + return (*func_ptrs->workbuf_len)(self); + } else if (v->vtable_name == NULL) { + break; + } + v++; + } + + return wuffs_base__utility__empty_range_ii_u64(); +} + +// -------- + +WUFFS_BASE__MAYBE_STATIC wuffs_base__status +wuffs_base__token_decoder__decode_tokens( + wuffs_base__token_decoder* self, + wuffs_base__token_buffer* a_dst, + wuffs_base__io_buffer* a_src, + wuffs_base__slice_u8 a_workbuf) { + if (!self) { + return wuffs_base__make_status(wuffs_base__error__bad_receiver); + } + if (self->private_impl.magic != WUFFS_BASE__MAGIC) { + return wuffs_base__make_status( + (self->private_impl.magic == WUFFS_BASE__DISABLED) + ? wuffs_base__error__disabled_by_previous_error + : wuffs_base__error__initialize_not_called); + } + + const wuffs_base__vtable* v = &self->private_impl.first_vtable; + int i; + for (i = 0; i < 63; i++) { + if (v->vtable_name == wuffs_base__token_decoder__vtable_name) { + const wuffs_base__token_decoder__func_ptrs* func_ptrs = + (const wuffs_base__token_decoder__func_ptrs*)(v->function_pointers); + return (*func_ptrs->decode_tokens)(self, a_dst, a_src, a_workbuf); + } else if (v->vtable_name == NULL) { + break; + } + v++; + } + + return wuffs_base__make_status(wuffs_base__error__bad_vtable); +} + +WUFFS_BASE__MAYBE_STATIC wuffs_base__empty_struct +wuffs_base__token_decoder__set_quirk_enabled( + wuffs_base__token_decoder* self, + uint32_t a_quirk, + bool a_enabled) { + if (!self) { + return wuffs_base__make_empty_struct(); + } + if (self->private_impl.magic != WUFFS_BASE__MAGIC) { + return wuffs_base__make_empty_struct(); + } + + const wuffs_base__vtable* v = &self->private_impl.first_vtable; + int i; + for (i = 0; i < 63; i++) { + if (v->vtable_name == wuffs_base__token_decoder__vtable_name) { + const wuffs_base__token_decoder__func_ptrs* func_ptrs = + (const wuffs_base__token_decoder__func_ptrs*)(v->function_pointers); + return (*func_ptrs->set_quirk_enabled)(self, a_quirk, a_enabled); + } else if (v->vtable_name == NULL) { + break; + } + v++; + } + + return wuffs_base__make_empty_struct(); +} + +WUFFS_BASE__MAYBE_STATIC wuffs_base__range_ii_u64 +wuffs_base__token_decoder__workbuf_len( + const wuffs_base__token_decoder* self) { + if (!self) { + return wuffs_base__utility__empty_range_ii_u64(); + } + if ((self->private_impl.magic != WUFFS_BASE__MAGIC) && + (self->private_impl.magic != WUFFS_BASE__DISABLED)) { + return wuffs_base__utility__empty_range_ii_u64(); + } + + const wuffs_base__vtable* v = &self->private_impl.first_vtable; + int i; + for (i = 0; i < 63; i++) { + if (v->vtable_name == wuffs_base__token_decoder__vtable_name) { + const wuffs_base__token_decoder__func_ptrs* func_ptrs = + (const wuffs_base__token_decoder__func_ptrs*)(v->function_pointers); + return (*func_ptrs->workbuf_len)(self); + } else if (v->vtable_name == NULL) { + break; + } + v++; + } + + return wuffs_base__utility__empty_range_ii_u64(); +} + +#endif // !defined(WUFFS_CONFIG__MODULES) || + // defined(WUFFS_CONFIG__MODULE__BASE) || + // defined(WUFFS_CONFIG__MODULE__BASE__INTERFACES) + +#if !defined(WUFFS_CONFIG__MODULES) || defined(WUFFS_CONFIG__MODULE__BASE) || \ + defined(WUFFS_CONFIG__MODULE__BASE__FLOATCONV) + +// ---------------- IEEE 754 Floating Point + +// The etc__hpd_left_shift and etc__powers_of_5 tables were printed by +// script/print-hpd-left-shift.go. That script has an optional -comments flag, +// whose output is not copied here, which prints further detail. +// +// These tables are used in +// wuffs_base__private_implementation__high_prec_dec__lshift_num_new_digits. + +// wuffs_base__private_implementation__hpd_left_shift[i] encodes the number of +// new digits created after multiplying a positive integer by (1 << i): the +// additional length in the decimal representation. For example, shifting "234" +// by 3 (equivalent to multiplying by 8) will produce "1872". Going from a +// 3-length string to a 4-length string means that 1 new digit was added (and +// existing digits may have changed). +// +// Shifting by i can add either N or N-1 new digits, depending on whether the +// original positive integer compares >= or < to the i'th power of 5 (as 10 +// equals 2 * 5). Comparison is lexicographic, not numerical. +// +// For example, shifting by 4 (i.e. multiplying by 16) can add 1 or 2 new +// digits, depending on a lexicographic comparison to (5 ** 4), i.e. "625": +// - ("1" << 4) is "16", which adds 1 new digit. +// - ("5678" << 4) is "90848", which adds 1 new digit. +// - ("624" << 4) is "9984", which adds 1 new digit. +// - ("62498" << 4) is "999968", which adds 1 new digit. +// - ("625" << 4) is "10000", which adds 2 new digits. +// - ("625001" << 4) is "10000016", which adds 2 new digits. +// - ("7008" << 4) is "112128", which adds 2 new digits. +// - ("99" << 4) is "1584", which adds 2 new digits. +// +// Thus, when i is 4, N is 2 and (5 ** i) is "625". This etc__hpd_left_shift +// array encodes this as: +// - etc__hpd_left_shift[4] is 0x1006 = (2 << 11) | 0x0006. +// - etc__hpd_left_shift[5] is 0x1009 = (? << 11) | 0x0009. +// where the ? isn't relevant for i == 4. +// +// The high 5 bits of etc__hpd_left_shift[i] is N, the higher of the two +// possible number of new digits. The low 11 bits are an offset into the +// etc__powers_of_5 array (of length 0x051C, so offsets fit in 11 bits). When i +// is 4, its offset and the next one is 6 and 9, and etc__powers_of_5[6 .. 9] +// is the string "\x06\x02\x05", so the relevant power of 5 is "625". +// +// Thanks to Ken Thompson for the original idea. +static const uint16_t wuffs_base__private_implementation__hpd_left_shift[65] = { + 0x0000, 0x0800, 0x0801, 0x0803, 0x1006, 0x1009, 0x100D, 0x1812, 0x1817, + 0x181D, 0x2024, 0x202B, 0x2033, 0x203C, 0x2846, 0x2850, 0x285B, 0x3067, + 0x3073, 0x3080, 0x388E, 0x389C, 0x38AB, 0x38BB, 0x40CC, 0x40DD, 0x40EF, + 0x4902, 0x4915, 0x4929, 0x513E, 0x5153, 0x5169, 0x5180, 0x5998, 0x59B0, + 0x59C9, 0x61E3, 0x61FD, 0x6218, 0x6A34, 0x6A50, 0x6A6D, 0x6A8B, 0x72AA, + 0x72C9, 0x72E9, 0x7B0A, 0x7B2B, 0x7B4D, 0x8370, 0x8393, 0x83B7, 0x83DC, + 0x8C02, 0x8C28, 0x8C4F, 0x9477, 0x949F, 0x94C8, 0x9CF2, 0x051C, 0x051C, + 0x051C, 0x051C, +}; + +// wuffs_base__private_implementation__powers_of_5 contains the powers of 5, +// concatenated together: "5", "25", "125", "625", "3125", etc. +static const uint8_t wuffs_base__private_implementation__powers_of_5[0x051C] = { + 5, 2, 5, 1, 2, 5, 6, 2, 5, 3, 1, 2, 5, 1, 5, 6, 2, 5, 7, 8, 1, 2, 5, 3, 9, + 0, 6, 2, 5, 1, 9, 5, 3, 1, 2, 5, 9, 7, 6, 5, 6, 2, 5, 4, 8, 8, 2, 8, 1, 2, + 5, 2, 4, 4, 1, 4, 0, 6, 2, 5, 1, 2, 2, 0, 7, 0, 3, 1, 2, 5, 6, 1, 0, 3, 5, + 1, 5, 6, 2, 5, 3, 0, 5, 1, 7, 5, 7, 8, 1, 2, 5, 1, 5, 2, 5, 8, 7, 8, 9, 0, + 6, 2, 5, 7, 6, 2, 9, 3, 9, 4, 5, 3, 1, 2, 5, 3, 8, 1, 4, 6, 9, 7, 2, 6, 5, + 6, 2, 5, 1, 9, 0, 7, 3, 4, 8, 6, 3, 2, 8, 1, 2, 5, 9, 5, 3, 6, 7, 4, 3, 1, + 6, 4, 0, 6, 2, 5, 4, 7, 6, 8, 3, 7, 1, 5, 8, 2, 0, 3, 1, 2, 5, 2, 3, 8, 4, + 1, 8, 5, 7, 9, 1, 0, 1, 5, 6, 2, 5, 1, 1, 9, 2, 0, 9, 2, 8, 9, 5, 5, 0, 7, + 8, 1, 2, 5, 5, 9, 6, 0, 4, 6, 4, 4, 7, 7, 5, 3, 9, 0, 6, 2, 5, 2, 9, 8, 0, + 2, 3, 2, 2, 3, 8, 7, 6, 9, 5, 3, 1, 2, 5, 1, 4, 9, 0, 1, 1, 6, 1, 1, 9, 3, + 8, 4, 7, 6, 5, 6, 2, 5, 7, 4, 5, 0, 5, 8, 0, 5, 9, 6, 9, 2, 3, 8, 2, 8, 1, + 2, 5, 3, 7, 2, 5, 2, 9, 0, 2, 9, 8, 4, 6, 1, 9, 1, 4, 0, 6, 2, 5, 1, 8, 6, + 2, 6, 4, 5, 1, 4, 9, 2, 3, 0, 9, 5, 7, 0, 3, 1, 2, 5, 9, 3, 1, 3, 2, 2, 5, + 7, 4, 6, 1, 5, 4, 7, 8, 5, 1, 5, 6, 2, 5, 4, 6, 5, 6, 6, 1, 2, 8, 7, 3, 0, + 7, 7, 3, 9, 2, 5, 7, 8, 1, 2, 5, 2, 3, 2, 8, 3, 0, 6, 4, 3, 6, 5, 3, 8, 6, + 9, 6, 2, 8, 9, 0, 6, 2, 5, 1, 1, 6, 4, 1, 5, 3, 2, 1, 8, 2, 6, 9, 3, 4, 8, + 1, 4, 4, 5, 3, 1, 2, 5, 5, 8, 2, 0, 7, 6, 6, 0, 9, 1, 3, 4, 6, 7, 4, 0, 7, + 2, 2, 6, 5, 6, 2, 5, 2, 9, 1, 0, 3, 8, 3, 0, 4, 5, 6, 7, 3, 3, 7, 0, 3, 6, + 1, 3, 2, 8, 1, 2, 5, 1, 4, 5, 5, 1, 9, 1, 5, 2, 2, 8, 3, 6, 6, 8, 5, 1, 8, + 0, 6, 6, 4, 0, 6, 2, 5, 7, 2, 7, 5, 9, 5, 7, 6, 1, 4, 1, 8, 3, 4, 2, 5, 9, + 0, 3, 3, 2, 0, 3, 1, 2, 5, 3, 6, 3, 7, 9, 7, 8, 8, 0, 7, 0, 9, 1, 7, 1, 2, + 9, 5, 1, 6, 6, 0, 1, 5, 6, 2, 5, 1, 8, 1, 8, 9, 8, 9, 4, 0, 3, 5, 4, 5, 8, + 5, 6, 4, 7, 5, 8, 3, 0, 0, 7, 8, 1, 2, 5, 9, 0, 9, 4, 9, 4, 7, 0, 1, 7, 7, + 2, 9, 2, 8, 2, 3, 7, 9, 1, 5, 0, 3, 9, 0, 6, 2, 5, 4, 5, 4, 7, 4, 7, 3, 5, + 0, 8, 8, 6, 4, 6, 4, 1, 1, 8, 9, 5, 7, 5, 1, 9, 5, 3, 1, 2, 5, 2, 2, 7, 3, + 7, 3, 6, 7, 5, 4, 4, 3, 2, 3, 2, 0, 5, 9, 4, 7, 8, 7, 5, 9, 7, 6, 5, 6, 2, + 5, 1, 1, 3, 6, 8, 6, 8, 3, 7, 7, 2, 1, 6, 1, 6, 0, 2, 9, 7, 3, 9, 3, 7, 9, + 8, 8, 2, 8, 1, 2, 5, 5, 6, 8, 4, 3, 4, 1, 8, 8, 6, 0, 8, 0, 8, 0, 1, 4, 8, + 6, 9, 6, 8, 9, 9, 4, 1, 4, 0, 6, 2, 5, 2, 8, 4, 2, 1, 7, 0, 9, 4, 3, 0, 4, + 0, 4, 0, 0, 7, 4, 3, 4, 8, 4, 4, 9, 7, 0, 7, 0, 3, 1, 2, 5, 1, 4, 2, 1, 0, + 8, 5, 4, 7, 1, 5, 2, 0, 2, 0, 0, 3, 7, 1, 7, 4, 2, 2, 4, 8, 5, 3, 5, 1, 5, + 6, 2, 5, 7, 1, 0, 5, 4, 2, 7, 3, 5, 7, 6, 0, 1, 0, 0, 1, 8, 5, 8, 7, 1, 1, + 2, 4, 2, 6, 7, 5, 7, 8, 1, 2, 5, 3, 5, 5, 2, 7, 1, 3, 6, 7, 8, 8, 0, 0, 5, + 0, 0, 9, 2, 9, 3, 5, 5, 6, 2, 1, 3, 3, 7, 8, 9, 0, 6, 2, 5, 1, 7, 7, 6, 3, + 5, 6, 8, 3, 9, 4, 0, 0, 2, 5, 0, 4, 6, 4, 6, 7, 7, 8, 1, 0, 6, 6, 8, 9, 4, + 5, 3, 1, 2, 5, 8, 8, 8, 1, 7, 8, 4, 1, 9, 7, 0, 0, 1, 2, 5, 2, 3, 2, 3, 3, + 8, 9, 0, 5, 3, 3, 4, 4, 7, 2, 6, 5, 6, 2, 5, 4, 4, 4, 0, 8, 9, 2, 0, 9, 8, + 5, 0, 0, 6, 2, 6, 1, 6, 1, 6, 9, 4, 5, 2, 6, 6, 7, 2, 3, 6, 3, 2, 8, 1, 2, + 5, 2, 2, 2, 0, 4, 4, 6, 0, 4, 9, 2, 5, 0, 3, 1, 3, 0, 8, 0, 8, 4, 7, 2, 6, + 3, 3, 3, 6, 1, 8, 1, 6, 4, 0, 6, 2, 5, 1, 1, 1, 0, 2, 2, 3, 0, 2, 4, 6, 2, + 5, 1, 5, 6, 5, 4, 0, 4, 2, 3, 6, 3, 1, 6, 6, 8, 0, 9, 0, 8, 2, 0, 3, 1, 2, + 5, 5, 5, 5, 1, 1, 1, 5, 1, 2, 3, 1, 2, 5, 7, 8, 2, 7, 0, 2, 1, 1, 8, 1, 5, + 8, 3, 4, 0, 4, 5, 4, 1, 0, 1, 5, 6, 2, 5, 2, 7, 7, 5, 5, 5, 7, 5, 6, 1, 5, + 6, 2, 8, 9, 1, 3, 5, 1, 0, 5, 9, 0, 7, 9, 1, 7, 0, 2, 2, 7, 0, 5, 0, 7, 8, + 1, 2, 5, 1, 3, 8, 7, 7, 7, 8, 7, 8, 0, 7, 8, 1, 4, 4, 5, 6, 7, 5, 5, 2, 9, + 5, 3, 9, 5, 8, 5, 1, 1, 3, 5, 2, 5, 3, 9, 0, 6, 2, 5, 6, 9, 3, 8, 8, 9, 3, + 9, 0, 3, 9, 0, 7, 2, 2, 8, 3, 7, 7, 6, 4, 7, 6, 9, 7, 9, 2, 5, 5, 6, 7, 6, + 2, 6, 9, 5, 3, 1, 2, 5, 3, 4, 6, 9, 4, 4, 6, 9, 5, 1, 9, 5, 3, 6, 1, 4, 1, + 8, 8, 8, 2, 3, 8, 4, 8, 9, 6, 2, 7, 8, 3, 8, 1, 3, 4, 7, 6, 5, 6, 2, 5, 1, + 7, 3, 4, 7, 2, 3, 4, 7, 5, 9, 7, 6, 8, 0, 7, 0, 9, 4, 4, 1, 1, 9, 2, 4, 4, + 8, 1, 3, 9, 1, 9, 0, 6, 7, 3, 8, 2, 8, 1, 2, 5, 8, 6, 7, 3, 6, 1, 7, 3, 7, + 9, 8, 8, 4, 0, 3, 5, 4, 7, 2, 0, 5, 9, 6, 2, 2, 4, 0, 6, 9, 5, 9, 5, 3, 3, + 6, 9, 1, 4, 0, 6, 2, 5, +}; + +// -------- + +// wuffs_base__private_implementation__powers_of_10 contains truncated +// approximations to the powers of 10, ranging from 1e-307 to 1e+288 inclusive, +// as 596 pairs of uint64_t values (a 128-bit mantissa). +// +// There's also an implicit third column (implied by a linear formula involving +// the base-10 exponent) that is the base-2 exponent, biased by a magic +// constant. That constant (1214 or 0x04BE) equals 1023 + 191. 1023 is the bias +// for IEEE 754 double-precision floating point. 191 is ((3 * 64) - 1) and +// wuffs_base__private_implementation__parse_number_f64_eisel_lemire works with +// multiples-of-64-bit mantissas. +// +// For example, the third row holds the approximation to 1e-305: +// 0xE0B62E29_29ABA83C_331ACDAB_FE94DE87 * (2 ** (0x0049 - 0x04BE)) +// +// Similarly, 1e+4 is approximated by: +// 0x9C400000_00000000_00000000_00000000 * (2 ** (0x044C - 0x04BE)) +// +// Similarly, 1e+68 is approximated by: +// 0xED63A231_D4C4FB27_4CA7AAA8_63EE4BDD * (2 ** (0x0520 - 0x04BE)) +// +// This table was generated by by script/print-mpb-powers-of-10.go +static const uint64_t wuffs_base__private_implementation__powers_of_10[596][2] = + { + {0xA5D3B6D479F8E056, 0x8FD0C16206306BAB}, // 1e-307 + {0x8F48A4899877186C, 0xB3C4F1BA87BC8696}, // 1e-306 + {0x331ACDABFE94DE87, 0xE0B62E2929ABA83C}, // 1e-305 + {0x9FF0C08B7F1D0B14, 0x8C71DCD9BA0B4925}, // 1e-304 + {0x07ECF0AE5EE44DD9, 0xAF8E5410288E1B6F}, // 1e-303 + {0xC9E82CD9F69D6150, 0xDB71E91432B1A24A}, // 1e-302 + {0xBE311C083A225CD2, 0x892731AC9FAF056E}, // 1e-301 + {0x6DBD630A48AAF406, 0xAB70FE17C79AC6CA}, // 1e-300 + {0x092CBBCCDAD5B108, 0xD64D3D9DB981787D}, // 1e-299 + {0x25BBF56008C58EA5, 0x85F0468293F0EB4E}, // 1e-298 + {0xAF2AF2B80AF6F24E, 0xA76C582338ED2621}, // 1e-297 + {0x1AF5AF660DB4AEE1, 0xD1476E2C07286FAA}, // 1e-296 + {0x50D98D9FC890ED4D, 0x82CCA4DB847945CA}, // 1e-295 + {0xE50FF107BAB528A0, 0xA37FCE126597973C}, // 1e-294 + {0x1E53ED49A96272C8, 0xCC5FC196FEFD7D0C}, // 1e-293 + {0x25E8E89C13BB0F7A, 0xFF77B1FCBEBCDC4F}, // 1e-292 + {0x77B191618C54E9AC, 0x9FAACF3DF73609B1}, // 1e-291 + {0xD59DF5B9EF6A2417, 0xC795830D75038C1D}, // 1e-290 + {0x4B0573286B44AD1D, 0xF97AE3D0D2446F25}, // 1e-289 + {0x4EE367F9430AEC32, 0x9BECCE62836AC577}, // 1e-288 + {0x229C41F793CDA73F, 0xC2E801FB244576D5}, // 1e-287 + {0x6B43527578C1110F, 0xF3A20279ED56D48A}, // 1e-286 + {0x830A13896B78AAA9, 0x9845418C345644D6}, // 1e-285 + {0x23CC986BC656D553, 0xBE5691EF416BD60C}, // 1e-284 + {0x2CBFBE86B7EC8AA8, 0xEDEC366B11C6CB8F}, // 1e-283 + {0x7BF7D71432F3D6A9, 0x94B3A202EB1C3F39}, // 1e-282 + {0xDAF5CCD93FB0CC53, 0xB9E08A83A5E34F07}, // 1e-281 + {0xD1B3400F8F9CFF68, 0xE858AD248F5C22C9}, // 1e-280 + {0x23100809B9C21FA1, 0x91376C36D99995BE}, // 1e-279 + {0xABD40A0C2832A78A, 0xB58547448FFFFB2D}, // 1e-278 + {0x16C90C8F323F516C, 0xE2E69915B3FFF9F9}, // 1e-277 + {0xAE3DA7D97F6792E3, 0x8DD01FAD907FFC3B}, // 1e-276 + {0x99CD11CFDF41779C, 0xB1442798F49FFB4A}, // 1e-275 + {0x40405643D711D583, 0xDD95317F31C7FA1D}, // 1e-274 + {0x482835EA666B2572, 0x8A7D3EEF7F1CFC52}, // 1e-273 + {0xDA3243650005EECF, 0xAD1C8EAB5EE43B66}, // 1e-272 + {0x90BED43E40076A82, 0xD863B256369D4A40}, // 1e-271 + {0x5A7744A6E804A291, 0x873E4F75E2224E68}, // 1e-270 + {0x711515D0A205CB36, 0xA90DE3535AAAE202}, // 1e-269 + {0x0D5A5B44CA873E03, 0xD3515C2831559A83}, // 1e-268 + {0xE858790AFE9486C2, 0x8412D9991ED58091}, // 1e-267 + {0x626E974DBE39A872, 0xA5178FFF668AE0B6}, // 1e-266 + {0xFB0A3D212DC8128F, 0xCE5D73FF402D98E3}, // 1e-265 + {0x7CE66634BC9D0B99, 0x80FA687F881C7F8E}, // 1e-264 + {0x1C1FFFC1EBC44E80, 0xA139029F6A239F72}, // 1e-263 + {0xA327FFB266B56220, 0xC987434744AC874E}, // 1e-262 + {0x4BF1FF9F0062BAA8, 0xFBE9141915D7A922}, // 1e-261 + {0x6F773FC3603DB4A9, 0x9D71AC8FADA6C9B5}, // 1e-260 + {0xCB550FB4384D21D3, 0xC4CE17B399107C22}, // 1e-259 + {0x7E2A53A146606A48, 0xF6019DA07F549B2B}, // 1e-258 + {0x2EDA7444CBFC426D, 0x99C102844F94E0FB}, // 1e-257 + {0xFA911155FEFB5308, 0xC0314325637A1939}, // 1e-256 + {0x793555AB7EBA27CA, 0xF03D93EEBC589F88}, // 1e-255 + {0x4BC1558B2F3458DE, 0x96267C7535B763B5}, // 1e-254 + {0x9EB1AAEDFB016F16, 0xBBB01B9283253CA2}, // 1e-253 + {0x465E15A979C1CADC, 0xEA9C227723EE8BCB}, // 1e-252 + {0x0BFACD89EC191EC9, 0x92A1958A7675175F}, // 1e-251 + {0xCEF980EC671F667B, 0xB749FAED14125D36}, // 1e-250 + {0x82B7E12780E7401A, 0xE51C79A85916F484}, // 1e-249 + {0xD1B2ECB8B0908810, 0x8F31CC0937AE58D2}, // 1e-248 + {0x861FA7E6DCB4AA15, 0xB2FE3F0B8599EF07}, // 1e-247 + {0x67A791E093E1D49A, 0xDFBDCECE67006AC9}, // 1e-246 + {0xE0C8BB2C5C6D24E0, 0x8BD6A141006042BD}, // 1e-245 + {0x58FAE9F773886E18, 0xAECC49914078536D}, // 1e-244 + {0xAF39A475506A899E, 0xDA7F5BF590966848}, // 1e-243 + {0x6D8406C952429603, 0x888F99797A5E012D}, // 1e-242 + {0xC8E5087BA6D33B83, 0xAAB37FD7D8F58178}, // 1e-241 + {0xFB1E4A9A90880A64, 0xD5605FCDCF32E1D6}, // 1e-240 + {0x5CF2EEA09A55067F, 0x855C3BE0A17FCD26}, // 1e-239 + {0xF42FAA48C0EA481E, 0xA6B34AD8C9DFC06F}, // 1e-238 + {0xF13B94DAF124DA26, 0xD0601D8EFC57B08B}, // 1e-237 + {0x76C53D08D6B70858, 0x823C12795DB6CE57}, // 1e-236 + {0x54768C4B0C64CA6E, 0xA2CB1717B52481ED}, // 1e-235 + {0xA9942F5DCF7DFD09, 0xCB7DDCDDA26DA268}, // 1e-234 + {0xD3F93B35435D7C4C, 0xFE5D54150B090B02}, // 1e-233 + {0xC47BC5014A1A6DAF, 0x9EFA548D26E5A6E1}, // 1e-232 + {0x359AB6419CA1091B, 0xC6B8E9B0709F109A}, // 1e-231 + {0xC30163D203C94B62, 0xF867241C8CC6D4C0}, // 1e-230 + {0x79E0DE63425DCF1D, 0x9B407691D7FC44F8}, // 1e-229 + {0x985915FC12F542E4, 0xC21094364DFB5636}, // 1e-228 + {0x3E6F5B7B17B2939D, 0xF294B943E17A2BC4}, // 1e-227 + {0xA705992CEECF9C42, 0x979CF3CA6CEC5B5A}, // 1e-226 + {0x50C6FF782A838353, 0xBD8430BD08277231}, // 1e-225 + {0xA4F8BF5635246428, 0xECE53CEC4A314EBD}, // 1e-224 + {0x871B7795E136BE99, 0x940F4613AE5ED136}, // 1e-223 + {0x28E2557B59846E3F, 0xB913179899F68584}, // 1e-222 + {0x331AEADA2FE589CF, 0xE757DD7EC07426E5}, // 1e-221 + {0x3FF0D2C85DEF7621, 0x9096EA6F3848984F}, // 1e-220 + {0x0FED077A756B53A9, 0xB4BCA50B065ABE63}, // 1e-219 + {0xD3E8495912C62894, 0xE1EBCE4DC7F16DFB}, // 1e-218 + {0x64712DD7ABBBD95C, 0x8D3360F09CF6E4BD}, // 1e-217 + {0xBD8D794D96AACFB3, 0xB080392CC4349DEC}, // 1e-216 + {0xECF0D7A0FC5583A0, 0xDCA04777F541C567}, // 1e-215 + {0xF41686C49DB57244, 0x89E42CAAF9491B60}, // 1e-214 + {0x311C2875C522CED5, 0xAC5D37D5B79B6239}, // 1e-213 + {0x7D633293366B828B, 0xD77485CB25823AC7}, // 1e-212 + {0xAE5DFF9C02033197, 0x86A8D39EF77164BC}, // 1e-211 + {0xD9F57F830283FDFC, 0xA8530886B54DBDEB}, // 1e-210 + {0xD072DF63C324FD7B, 0xD267CAA862A12D66}, // 1e-209 + {0x4247CB9E59F71E6D, 0x8380DEA93DA4BC60}, // 1e-208 + {0x52D9BE85F074E608, 0xA46116538D0DEB78}, // 1e-207 + {0x67902E276C921F8B, 0xCD795BE870516656}, // 1e-206 + {0x00BA1CD8A3DB53B6, 0x806BD9714632DFF6}, // 1e-205 + {0x80E8A40ECCD228A4, 0xA086CFCD97BF97F3}, // 1e-204 + {0x6122CD128006B2CD, 0xC8A883C0FDAF7DF0}, // 1e-203 + {0x796B805720085F81, 0xFAD2A4B13D1B5D6C}, // 1e-202 + {0xCBE3303674053BB0, 0x9CC3A6EEC6311A63}, // 1e-201 + {0xBEDBFC4411068A9C, 0xC3F490AA77BD60FC}, // 1e-200 + {0xEE92FB5515482D44, 0xF4F1B4D515ACB93B}, // 1e-199 + {0x751BDD152D4D1C4A, 0x991711052D8BF3C5}, // 1e-198 + {0xD262D45A78A0635D, 0xBF5CD54678EEF0B6}, // 1e-197 + {0x86FB897116C87C34, 0xEF340A98172AACE4}, // 1e-196 + {0xD45D35E6AE3D4DA0, 0x9580869F0E7AAC0E}, // 1e-195 + {0x8974836059CCA109, 0xBAE0A846D2195712}, // 1e-194 + {0x2BD1A438703FC94B, 0xE998D258869FACD7}, // 1e-193 + {0x7B6306A34627DDCF, 0x91FF83775423CC06}, // 1e-192 + {0x1A3BC84C17B1D542, 0xB67F6455292CBF08}, // 1e-191 + {0x20CABA5F1D9E4A93, 0xE41F3D6A7377EECA}, // 1e-190 + {0x547EB47B7282EE9C, 0x8E938662882AF53E}, // 1e-189 + {0xE99E619A4F23AA43, 0xB23867FB2A35B28D}, // 1e-188 + {0x6405FA00E2EC94D4, 0xDEC681F9F4C31F31}, // 1e-187 + {0xDE83BC408DD3DD04, 0x8B3C113C38F9F37E}, // 1e-186 + {0x9624AB50B148D445, 0xAE0B158B4738705E}, // 1e-185 + {0x3BADD624DD9B0957, 0xD98DDAEE19068C76}, // 1e-184 + {0xE54CA5D70A80E5D6, 0x87F8A8D4CFA417C9}, // 1e-183 + {0x5E9FCF4CCD211F4C, 0xA9F6D30A038D1DBC}, // 1e-182 + {0x7647C3200069671F, 0xD47487CC8470652B}, // 1e-181 + {0x29ECD9F40041E073, 0x84C8D4DFD2C63F3B}, // 1e-180 + {0xF468107100525890, 0xA5FB0A17C777CF09}, // 1e-179 + {0x7182148D4066EEB4, 0xCF79CC9DB955C2CC}, // 1e-178 + {0xC6F14CD848405530, 0x81AC1FE293D599BF}, // 1e-177 + {0xB8ADA00E5A506A7C, 0xA21727DB38CB002F}, // 1e-176 + {0xA6D90811F0E4851C, 0xCA9CF1D206FDC03B}, // 1e-175 + {0x908F4A166D1DA663, 0xFD442E4688BD304A}, // 1e-174 + {0x9A598E4E043287FE, 0x9E4A9CEC15763E2E}, // 1e-173 + {0x40EFF1E1853F29FD, 0xC5DD44271AD3CDBA}, // 1e-172 + {0xD12BEE59E68EF47C, 0xF7549530E188C128}, // 1e-171 + {0x82BB74F8301958CE, 0x9A94DD3E8CF578B9}, // 1e-170 + {0xE36A52363C1FAF01, 0xC13A148E3032D6E7}, // 1e-169 + {0xDC44E6C3CB279AC1, 0xF18899B1BC3F8CA1}, // 1e-168 + {0x29AB103A5EF8C0B9, 0x96F5600F15A7B7E5}, // 1e-167 + {0x7415D448F6B6F0E7, 0xBCB2B812DB11A5DE}, // 1e-166 + {0x111B495B3464AD21, 0xEBDF661791D60F56}, // 1e-165 + {0xCAB10DD900BEEC34, 0x936B9FCEBB25C995}, // 1e-164 + {0x3D5D514F40EEA742, 0xB84687C269EF3BFB}, // 1e-163 + {0x0CB4A5A3112A5112, 0xE65829B3046B0AFA}, // 1e-162 + {0x47F0E785EABA72AB, 0x8FF71A0FE2C2E6DC}, // 1e-161 + {0x59ED216765690F56, 0xB3F4E093DB73A093}, // 1e-160 + {0x306869C13EC3532C, 0xE0F218B8D25088B8}, // 1e-159 + {0x1E414218C73A13FB, 0x8C974F7383725573}, // 1e-158 + {0xE5D1929EF90898FA, 0xAFBD2350644EEACF}, // 1e-157 + {0xDF45F746B74ABF39, 0xDBAC6C247D62A583}, // 1e-156 + {0x6B8BBA8C328EB783, 0x894BC396CE5DA772}, // 1e-155 + {0x066EA92F3F326564, 0xAB9EB47C81F5114F}, // 1e-154 + {0xC80A537B0EFEFEBD, 0xD686619BA27255A2}, // 1e-153 + {0xBD06742CE95F5F36, 0x8613FD0145877585}, // 1e-152 + {0x2C48113823B73704, 0xA798FC4196E952E7}, // 1e-151 + {0xF75A15862CA504C5, 0xD17F3B51FCA3A7A0}, // 1e-150 + {0x9A984D73DBE722FB, 0x82EF85133DE648C4}, // 1e-149 + {0xC13E60D0D2E0EBBA, 0xA3AB66580D5FDAF5}, // 1e-148 + {0x318DF905079926A8, 0xCC963FEE10B7D1B3}, // 1e-147 + {0xFDF17746497F7052, 0xFFBBCFE994E5C61F}, // 1e-146 + {0xFEB6EA8BEDEFA633, 0x9FD561F1FD0F9BD3}, // 1e-145 + {0xFE64A52EE96B8FC0, 0xC7CABA6E7C5382C8}, // 1e-144 + {0x3DFDCE7AA3C673B0, 0xF9BD690A1B68637B}, // 1e-143 + {0x06BEA10CA65C084E, 0x9C1661A651213E2D}, // 1e-142 + {0x486E494FCFF30A62, 0xC31BFA0FE5698DB8}, // 1e-141 + {0x5A89DBA3C3EFCCFA, 0xF3E2F893DEC3F126}, // 1e-140 + {0xF89629465A75E01C, 0x986DDB5C6B3A76B7}, // 1e-139 + {0xF6BBB397F1135823, 0xBE89523386091465}, // 1e-138 + {0x746AA07DED582E2C, 0xEE2BA6C0678B597F}, // 1e-137 + {0xA8C2A44EB4571CDC, 0x94DB483840B717EF}, // 1e-136 + {0x92F34D62616CE413, 0xBA121A4650E4DDEB}, // 1e-135 + {0x77B020BAF9C81D17, 0xE896A0D7E51E1566}, // 1e-134 + {0x0ACE1474DC1D122E, 0x915E2486EF32CD60}, // 1e-133 + {0x0D819992132456BA, 0xB5B5ADA8AAFF80B8}, // 1e-132 + {0x10E1FFF697ED6C69, 0xE3231912D5BF60E6}, // 1e-131 + {0xCA8D3FFA1EF463C1, 0x8DF5EFABC5979C8F}, // 1e-130 + {0xBD308FF8A6B17CB2, 0xB1736B96B6FD83B3}, // 1e-129 + {0xAC7CB3F6D05DDBDE, 0xDDD0467C64BCE4A0}, // 1e-128 + {0x6BCDF07A423AA96B, 0x8AA22C0DBEF60EE4}, // 1e-127 + {0x86C16C98D2C953C6, 0xAD4AB7112EB3929D}, // 1e-126 + {0xE871C7BF077BA8B7, 0xD89D64D57A607744}, // 1e-125 + {0x11471CD764AD4972, 0x87625F056C7C4A8B}, // 1e-124 + {0xD598E40D3DD89BCF, 0xA93AF6C6C79B5D2D}, // 1e-123 + {0x4AFF1D108D4EC2C3, 0xD389B47879823479}, // 1e-122 + {0xCEDF722A585139BA, 0x843610CB4BF160CB}, // 1e-121 + {0xC2974EB4EE658828, 0xA54394FE1EEDB8FE}, // 1e-120 + {0x733D226229FEEA32, 0xCE947A3DA6A9273E}, // 1e-119 + {0x0806357D5A3F525F, 0x811CCC668829B887}, // 1e-118 + {0xCA07C2DCB0CF26F7, 0xA163FF802A3426A8}, // 1e-117 + {0xFC89B393DD02F0B5, 0xC9BCFF6034C13052}, // 1e-116 + {0xBBAC2078D443ACE2, 0xFC2C3F3841F17C67}, // 1e-115 + {0xD54B944B84AA4C0D, 0x9D9BA7832936EDC0}, // 1e-114 + {0x0A9E795E65D4DF11, 0xC5029163F384A931}, // 1e-113 + {0x4D4617B5FF4A16D5, 0xF64335BCF065D37D}, // 1e-112 + {0x504BCED1BF8E4E45, 0x99EA0196163FA42E}, // 1e-111 + {0xE45EC2862F71E1D6, 0xC06481FB9BCF8D39}, // 1e-110 + {0x5D767327BB4E5A4C, 0xF07DA27A82C37088}, // 1e-109 + {0x3A6A07F8D510F86F, 0x964E858C91BA2655}, // 1e-108 + {0x890489F70A55368B, 0xBBE226EFB628AFEA}, // 1e-107 + {0x2B45AC74CCEA842E, 0xEADAB0ABA3B2DBE5}, // 1e-106 + {0x3B0B8BC90012929D, 0x92C8AE6B464FC96F}, // 1e-105 + {0x09CE6EBB40173744, 0xB77ADA0617E3BBCB}, // 1e-104 + {0xCC420A6A101D0515, 0xE55990879DDCAABD}, // 1e-103 + {0x9FA946824A12232D, 0x8F57FA54C2A9EAB6}, // 1e-102 + {0x47939822DC96ABF9, 0xB32DF8E9F3546564}, // 1e-101 + {0x59787E2B93BC56F7, 0xDFF9772470297EBD}, // 1e-100 + {0x57EB4EDB3C55B65A, 0x8BFBEA76C619EF36}, // 1e-99 + {0xEDE622920B6B23F1, 0xAEFAE51477A06B03}, // 1e-98 + {0xE95FAB368E45ECED, 0xDAB99E59958885C4}, // 1e-97 + {0x11DBCB0218EBB414, 0x88B402F7FD75539B}, // 1e-96 + {0xD652BDC29F26A119, 0xAAE103B5FCD2A881}, // 1e-95 + {0x4BE76D3346F0495F, 0xD59944A37C0752A2}, // 1e-94 + {0x6F70A4400C562DDB, 0x857FCAE62D8493A5}, // 1e-93 + {0xCB4CCD500F6BB952, 0xA6DFBD9FB8E5B88E}, // 1e-92 + {0x7E2000A41346A7A7, 0xD097AD07A71F26B2}, // 1e-91 + {0x8ED400668C0C28C8, 0x825ECC24C873782F}, // 1e-90 + {0x728900802F0F32FA, 0xA2F67F2DFA90563B}, // 1e-89 + {0x4F2B40A03AD2FFB9, 0xCBB41EF979346BCA}, // 1e-88 + {0xE2F610C84987BFA8, 0xFEA126B7D78186BC}, // 1e-87 + {0x0DD9CA7D2DF4D7C9, 0x9F24B832E6B0F436}, // 1e-86 + {0x91503D1C79720DBB, 0xC6EDE63FA05D3143}, // 1e-85 + {0x75A44C6397CE912A, 0xF8A95FCF88747D94}, // 1e-84 + {0xC986AFBE3EE11ABA, 0x9B69DBE1B548CE7C}, // 1e-83 + {0xFBE85BADCE996168, 0xC24452DA229B021B}, // 1e-82 + {0xFAE27299423FB9C3, 0xF2D56790AB41C2A2}, // 1e-81 + {0xDCCD879FC967D41A, 0x97C560BA6B0919A5}, // 1e-80 + {0x5400E987BBC1C920, 0xBDB6B8E905CB600F}, // 1e-79 + {0x290123E9AAB23B68, 0xED246723473E3813}, // 1e-78 + {0xF9A0B6720AAF6521, 0x9436C0760C86E30B}, // 1e-77 + {0xF808E40E8D5B3E69, 0xB94470938FA89BCE}, // 1e-76 + {0xB60B1D1230B20E04, 0xE7958CB87392C2C2}, // 1e-75 + {0xB1C6F22B5E6F48C2, 0x90BD77F3483BB9B9}, // 1e-74 + {0x1E38AEB6360B1AF3, 0xB4ECD5F01A4AA828}, // 1e-73 + {0x25C6DA63C38DE1B0, 0xE2280B6C20DD5232}, // 1e-72 + {0x579C487E5A38AD0E, 0x8D590723948A535F}, // 1e-71 + {0x2D835A9DF0C6D851, 0xB0AF48EC79ACE837}, // 1e-70 + {0xF8E431456CF88E65, 0xDCDB1B2798182244}, // 1e-69 + {0x1B8E9ECB641B58FF, 0x8A08F0F8BF0F156B}, // 1e-68 + {0xE272467E3D222F3F, 0xAC8B2D36EED2DAC5}, // 1e-67 + {0x5B0ED81DCC6ABB0F, 0xD7ADF884AA879177}, // 1e-66 + {0x98E947129FC2B4E9, 0x86CCBB52EA94BAEA}, // 1e-65 + {0x3F2398D747B36224, 0xA87FEA27A539E9A5}, // 1e-64 + {0x8EEC7F0D19A03AAD, 0xD29FE4B18E88640E}, // 1e-63 + {0x1953CF68300424AC, 0x83A3EEEEF9153E89}, // 1e-62 + {0x5FA8C3423C052DD7, 0xA48CEAAAB75A8E2B}, // 1e-61 + {0x3792F412CB06794D, 0xCDB02555653131B6}, // 1e-60 + {0xE2BBD88BBEE40BD0, 0x808E17555F3EBF11}, // 1e-59 + {0x5B6ACEAEAE9D0EC4, 0xA0B19D2AB70E6ED6}, // 1e-58 + {0xF245825A5A445275, 0xC8DE047564D20A8B}, // 1e-57 + {0xEED6E2F0F0D56712, 0xFB158592BE068D2E}, // 1e-56 + {0x55464DD69685606B, 0x9CED737BB6C4183D}, // 1e-55 + {0xAA97E14C3C26B886, 0xC428D05AA4751E4C}, // 1e-54 + {0xD53DD99F4B3066A8, 0xF53304714D9265DF}, // 1e-53 + {0xE546A8038EFE4029, 0x993FE2C6D07B7FAB}, // 1e-52 + {0xDE98520472BDD033, 0xBF8FDB78849A5F96}, // 1e-51 + {0x963E66858F6D4440, 0xEF73D256A5C0F77C}, // 1e-50 + {0xDDE7001379A44AA8, 0x95A8637627989AAD}, // 1e-49 + {0x5560C018580D5D52, 0xBB127C53B17EC159}, // 1e-48 + {0xAAB8F01E6E10B4A6, 0xE9D71B689DDE71AF}, // 1e-47 + {0xCAB3961304CA70E8, 0x9226712162AB070D}, // 1e-46 + {0x3D607B97C5FD0D22, 0xB6B00D69BB55C8D1}, // 1e-45 + {0x8CB89A7DB77C506A, 0xE45C10C42A2B3B05}, // 1e-44 + {0x77F3608E92ADB242, 0x8EB98A7A9A5B04E3}, // 1e-43 + {0x55F038B237591ED3, 0xB267ED1940F1C61C}, // 1e-42 + {0x6B6C46DEC52F6688, 0xDF01E85F912E37A3}, // 1e-41 + {0x2323AC4B3B3DA015, 0x8B61313BBABCE2C6}, // 1e-40 + {0xABEC975E0A0D081A, 0xAE397D8AA96C1B77}, // 1e-39 + {0x96E7BD358C904A21, 0xD9C7DCED53C72255}, // 1e-38 + {0x7E50D64177DA2E54, 0x881CEA14545C7575}, // 1e-37 + {0xDDE50BD1D5D0B9E9, 0xAA242499697392D2}, // 1e-36 + {0x955E4EC64B44E864, 0xD4AD2DBFC3D07787}, // 1e-35 + {0xBD5AF13BEF0B113E, 0x84EC3C97DA624AB4}, // 1e-34 + {0xECB1AD8AEACDD58E, 0xA6274BBDD0FADD61}, // 1e-33 + {0x67DE18EDA5814AF2, 0xCFB11EAD453994BA}, // 1e-32 + {0x80EACF948770CED7, 0x81CEB32C4B43FCF4}, // 1e-31 + {0xA1258379A94D028D, 0xA2425FF75E14FC31}, // 1e-30 + {0x096EE45813A04330, 0xCAD2F7F5359A3B3E}, // 1e-29 + {0x8BCA9D6E188853FC, 0xFD87B5F28300CA0D}, // 1e-28 + {0x775EA264CF55347D, 0x9E74D1B791E07E48}, // 1e-27 + {0x95364AFE032A819D, 0xC612062576589DDA}, // 1e-26 + {0x3A83DDBD83F52204, 0xF79687AED3EEC551}, // 1e-25 + {0xC4926A9672793542, 0x9ABE14CD44753B52}, // 1e-24 + {0x75B7053C0F178293, 0xC16D9A0095928A27}, // 1e-23 + {0x5324C68B12DD6338, 0xF1C90080BAF72CB1}, // 1e-22 + {0xD3F6FC16EBCA5E03, 0x971DA05074DA7BEE}, // 1e-21 + {0x88F4BB1CA6BCF584, 0xBCE5086492111AEA}, // 1e-20 + {0x2B31E9E3D06C32E5, 0xEC1E4A7DB69561A5}, // 1e-19 + {0x3AFF322E62439FCF, 0x9392EE8E921D5D07}, // 1e-18 + {0x09BEFEB9FAD487C2, 0xB877AA3236A4B449}, // 1e-17 + {0x4C2EBE687989A9B3, 0xE69594BEC44DE15B}, // 1e-16 + {0x0F9D37014BF60A10, 0x901D7CF73AB0ACD9}, // 1e-15 + {0x538484C19EF38C94, 0xB424DC35095CD80F}, // 1e-14 + {0x2865A5F206B06FB9, 0xE12E13424BB40E13}, // 1e-13 + {0xF93F87B7442E45D3, 0x8CBCCC096F5088CB}, // 1e-12 + {0xF78F69A51539D748, 0xAFEBFF0BCB24AAFE}, // 1e-11 + {0xB573440E5A884D1B, 0xDBE6FECEBDEDD5BE}, // 1e-10 + {0x31680A88F8953030, 0x89705F4136B4A597}, // 1e-9 + {0xFDC20D2B36BA7C3D, 0xABCC77118461CEFC}, // 1e-8 + {0x3D32907604691B4C, 0xD6BF94D5E57A42BC}, // 1e-7 + {0xA63F9A49C2C1B10F, 0x8637BD05AF6C69B5}, // 1e-6 + {0x0FCF80DC33721D53, 0xA7C5AC471B478423}, // 1e-5 + {0xD3C36113404EA4A8, 0xD1B71758E219652B}, // 1e-4 + {0x645A1CAC083126E9, 0x83126E978D4FDF3B}, // 1e-3 + {0x3D70A3D70A3D70A3, 0xA3D70A3D70A3D70A}, // 1e-2 + {0xCCCCCCCCCCCCCCCC, 0xCCCCCCCCCCCCCCCC}, // 1e-1 + {0x0000000000000000, 0x8000000000000000}, // 1e0 + {0x0000000000000000, 0xA000000000000000}, // 1e1 + {0x0000000000000000, 0xC800000000000000}, // 1e2 + {0x0000000000000000, 0xFA00000000000000}, // 1e3 + {0x0000000000000000, 0x9C40000000000000}, // 1e4 + {0x0000000000000000, 0xC350000000000000}, // 1e5 + {0x0000000000000000, 0xF424000000000000}, // 1e6 + {0x0000000000000000, 0x9896800000000000}, // 1e7 + {0x0000000000000000, 0xBEBC200000000000}, // 1e8 + {0x0000000000000000, 0xEE6B280000000000}, // 1e9 + {0x0000000000000000, 0x9502F90000000000}, // 1e10 + {0x0000000000000000, 0xBA43B74000000000}, // 1e11 + {0x0000000000000000, 0xE8D4A51000000000}, // 1e12 + {0x0000000000000000, 0x9184E72A00000000}, // 1e13 + {0x0000000000000000, 0xB5E620F480000000}, // 1e14 + {0x0000000000000000, 0xE35FA931A0000000}, // 1e15 + {0x0000000000000000, 0x8E1BC9BF04000000}, // 1e16 + {0x0000000000000000, 0xB1A2BC2EC5000000}, // 1e17 + {0x0000000000000000, 0xDE0B6B3A76400000}, // 1e18 + {0x0000000000000000, 0x8AC7230489E80000}, // 1e19 + {0x0000000000000000, 0xAD78EBC5AC620000}, // 1e20 + {0x0000000000000000, 0xD8D726B7177A8000}, // 1e21 + {0x0000000000000000, 0x878678326EAC9000}, // 1e22 + {0x0000000000000000, 0xA968163F0A57B400}, // 1e23 + {0x0000000000000000, 0xD3C21BCECCEDA100}, // 1e24 + {0x0000000000000000, 0x84595161401484A0}, // 1e25 + {0x0000000000000000, 0xA56FA5B99019A5C8}, // 1e26 + {0x0000000000000000, 0xCECB8F27F4200F3A}, // 1e27 + {0x4000000000000000, 0x813F3978F8940984}, // 1e28 + {0x5000000000000000, 0xA18F07D736B90BE5}, // 1e29 + {0xA400000000000000, 0xC9F2C9CD04674EDE}, // 1e30 + {0x4D00000000000000, 0xFC6F7C4045812296}, // 1e31 + {0xF020000000000000, 0x9DC5ADA82B70B59D}, // 1e32 + {0x6C28000000000000, 0xC5371912364CE305}, // 1e33 + {0xC732000000000000, 0xF684DF56C3E01BC6}, // 1e34 + {0x3C7F400000000000, 0x9A130B963A6C115C}, // 1e35 + {0x4B9F100000000000, 0xC097CE7BC90715B3}, // 1e36 + {0x1E86D40000000000, 0xF0BDC21ABB48DB20}, // 1e37 + {0x1314448000000000, 0x96769950B50D88F4}, // 1e38 + {0x17D955A000000000, 0xBC143FA4E250EB31}, // 1e39 + {0x5DCFAB0800000000, 0xEB194F8E1AE525FD}, // 1e40 + {0x5AA1CAE500000000, 0x92EFD1B8D0CF37BE}, // 1e41 + {0xF14A3D9E40000000, 0xB7ABC627050305AD}, // 1e42 + {0x6D9CCD05D0000000, 0xE596B7B0C643C719}, // 1e43 + {0xE4820023A2000000, 0x8F7E32CE7BEA5C6F}, // 1e44 + {0xDDA2802C8A800000, 0xB35DBF821AE4F38B}, // 1e45 + {0xD50B2037AD200000, 0xE0352F62A19E306E}, // 1e46 + {0x4526F422CC340000, 0x8C213D9DA502DE45}, // 1e47 + {0x9670B12B7F410000, 0xAF298D050E4395D6}, // 1e48 + {0x3C0CDD765F114000, 0xDAF3F04651D47B4C}, // 1e49 + {0xA5880A69FB6AC800, 0x88D8762BF324CD0F}, // 1e50 + {0x8EEA0D047A457A00, 0xAB0E93B6EFEE0053}, // 1e51 + {0x72A4904598D6D880, 0xD5D238A4ABE98068}, // 1e52 + {0x47A6DA2B7F864750, 0x85A36366EB71F041}, // 1e53 + {0x999090B65F67D924, 0xA70C3C40A64E6C51}, // 1e54 + {0xFFF4B4E3F741CF6D, 0xD0CF4B50CFE20765}, // 1e55 + {0xBFF8F10E7A8921A4, 0x82818F1281ED449F}, // 1e56 + {0xAFF72D52192B6A0D, 0xA321F2D7226895C7}, // 1e57 + {0x9BF4F8A69F764490, 0xCBEA6F8CEB02BB39}, // 1e58 + {0x02F236D04753D5B4, 0xFEE50B7025C36A08}, // 1e59 + {0x01D762422C946590, 0x9F4F2726179A2245}, // 1e60 + {0x424D3AD2B7B97EF5, 0xC722F0EF9D80AAD6}, // 1e61 + {0xD2E0898765A7DEB2, 0xF8EBAD2B84E0D58B}, // 1e62 + {0x63CC55F49F88EB2F, 0x9B934C3B330C8577}, // 1e63 + {0x3CBF6B71C76B25FB, 0xC2781F49FFCFA6D5}, // 1e64 + {0x8BEF464E3945EF7A, 0xF316271C7FC3908A}, // 1e65 + {0x97758BF0E3CBB5AC, 0x97EDD871CFDA3A56}, // 1e66 + {0x3D52EEED1CBEA317, 0xBDE94E8E43D0C8EC}, // 1e67 + {0x4CA7AAA863EE4BDD, 0xED63A231D4C4FB27}, // 1e68 + {0x8FE8CAA93E74EF6A, 0x945E455F24FB1CF8}, // 1e69 + {0xB3E2FD538E122B44, 0xB975D6B6EE39E436}, // 1e70 + {0x60DBBCA87196B616, 0xE7D34C64A9C85D44}, // 1e71 + {0xBC8955E946FE31CD, 0x90E40FBEEA1D3A4A}, // 1e72 + {0x6BABAB6398BDBE41, 0xB51D13AEA4A488DD}, // 1e73 + {0xC696963C7EED2DD1, 0xE264589A4DCDAB14}, // 1e74 + {0xFC1E1DE5CF543CA2, 0x8D7EB76070A08AEC}, // 1e75 + {0x3B25A55F43294BCB, 0xB0DE65388CC8ADA8}, // 1e76 + {0x49EF0EB713F39EBE, 0xDD15FE86AFFAD912}, // 1e77 + {0x6E3569326C784337, 0x8A2DBF142DFCC7AB}, // 1e78 + {0x49C2C37F07965404, 0xACB92ED9397BF996}, // 1e79 + {0xDC33745EC97BE906, 0xD7E77A8F87DAF7FB}, // 1e80 + {0x69A028BB3DED71A3, 0x86F0AC99B4E8DAFD}, // 1e81 + {0xC40832EA0D68CE0C, 0xA8ACD7C0222311BC}, // 1e82 + {0xF50A3FA490C30190, 0xD2D80DB02AABD62B}, // 1e83 + {0x792667C6DA79E0FA, 0x83C7088E1AAB65DB}, // 1e84 + {0x577001B891185938, 0xA4B8CAB1A1563F52}, // 1e85 + {0xED4C0226B55E6F86, 0xCDE6FD5E09ABCF26}, // 1e86 + {0x544F8158315B05B4, 0x80B05E5AC60B6178}, // 1e87 + {0x696361AE3DB1C721, 0xA0DC75F1778E39D6}, // 1e88 + {0x03BC3A19CD1E38E9, 0xC913936DD571C84C}, // 1e89 + {0x04AB48A04065C723, 0xFB5878494ACE3A5F}, // 1e90 + {0x62EB0D64283F9C76, 0x9D174B2DCEC0E47B}, // 1e91 + {0x3BA5D0BD324F8394, 0xC45D1DF942711D9A}, // 1e92 + {0xCA8F44EC7EE36479, 0xF5746577930D6500}, // 1e93 + {0x7E998B13CF4E1ECB, 0x9968BF6ABBE85F20}, // 1e94 + {0x9E3FEDD8C321A67E, 0xBFC2EF456AE276E8}, // 1e95 + {0xC5CFE94EF3EA101E, 0xEFB3AB16C59B14A2}, // 1e96 + {0xBBA1F1D158724A12, 0x95D04AEE3B80ECE5}, // 1e97 + {0x2A8A6E45AE8EDC97, 0xBB445DA9CA61281F}, // 1e98 + {0xF52D09D71A3293BD, 0xEA1575143CF97226}, // 1e99 + {0x593C2626705F9C56, 0x924D692CA61BE758}, // 1e100 + {0x6F8B2FB00C77836C, 0xB6E0C377CFA2E12E}, // 1e101 + {0x0B6DFB9C0F956447, 0xE498F455C38B997A}, // 1e102 + {0x4724BD4189BD5EAC, 0x8EDF98B59A373FEC}, // 1e103 + {0x58EDEC91EC2CB657, 0xB2977EE300C50FE7}, // 1e104 + {0x2F2967B66737E3ED, 0xDF3D5E9BC0F653E1}, // 1e105 + {0xBD79E0D20082EE74, 0x8B865B215899F46C}, // 1e106 + {0xECD8590680A3AA11, 0xAE67F1E9AEC07187}, // 1e107 + {0xE80E6F4820CC9495, 0xDA01EE641A708DE9}, // 1e108 + {0x3109058D147FDCDD, 0x884134FE908658B2}, // 1e109 + {0xBD4B46F0599FD415, 0xAA51823E34A7EEDE}, // 1e110 + {0x6C9E18AC7007C91A, 0xD4E5E2CDC1D1EA96}, // 1e111 + {0x03E2CF6BC604DDB0, 0x850FADC09923329E}, // 1e112 + {0x84DB8346B786151C, 0xA6539930BF6BFF45}, // 1e113 + {0xE612641865679A63, 0xCFE87F7CEF46FF16}, // 1e114 + {0x4FCB7E8F3F60C07E, 0x81F14FAE158C5F6E}, // 1e115 + {0xE3BE5E330F38F09D, 0xA26DA3999AEF7749}, // 1e116 + {0x5CADF5BFD3072CC5, 0xCB090C8001AB551C}, // 1e117 + {0x73D9732FC7C8F7F6, 0xFDCB4FA002162A63}, // 1e118 + {0x2867E7FDDCDD9AFA, 0x9E9F11C4014DDA7E}, // 1e119 + {0xB281E1FD541501B8, 0xC646D63501A1511D}, // 1e120 + {0x1F225A7CA91A4226, 0xF7D88BC24209A565}, // 1e121 + {0x3375788DE9B06958, 0x9AE757596946075F}, // 1e122 + {0x0052D6B1641C83AE, 0xC1A12D2FC3978937}, // 1e123 + {0xC0678C5DBD23A49A, 0xF209787BB47D6B84}, // 1e124 + {0xF840B7BA963646E0, 0x9745EB4D50CE6332}, // 1e125 + {0xB650E5A93BC3D898, 0xBD176620A501FBFF}, // 1e126 + {0xA3E51F138AB4CEBE, 0xEC5D3FA8CE427AFF}, // 1e127 + {0xC66F336C36B10137, 0x93BA47C980E98CDF}, // 1e128 + {0xB80B0047445D4184, 0xB8A8D9BBE123F017}, // 1e129 + {0xA60DC059157491E5, 0xE6D3102AD96CEC1D}, // 1e130 + {0x87C89837AD68DB2F, 0x9043EA1AC7E41392}, // 1e131 + {0x29BABE4598C311FB, 0xB454E4A179DD1877}, // 1e132 + {0xF4296DD6FEF3D67A, 0xE16A1DC9D8545E94}, // 1e133 + {0x1899E4A65F58660C, 0x8CE2529E2734BB1D}, // 1e134 + {0x5EC05DCFF72E7F8F, 0xB01AE745B101E9E4}, // 1e135 + {0x76707543F4FA1F73, 0xDC21A1171D42645D}, // 1e136 + {0x6A06494A791C53A8, 0x899504AE72497EBA}, // 1e137 + {0x0487DB9D17636892, 0xABFA45DA0EDBDE69}, // 1e138 + {0x45A9D2845D3C42B6, 0xD6F8D7509292D603}, // 1e139 + {0x0B8A2392BA45A9B2, 0x865B86925B9BC5C2}, // 1e140 + {0x8E6CAC7768D7141E, 0xA7F26836F282B732}, // 1e141 + {0x3207D795430CD926, 0xD1EF0244AF2364FF}, // 1e142 + {0x7F44E6BD49E807B8, 0x8335616AED761F1F}, // 1e143 + {0x5F16206C9C6209A6, 0xA402B9C5A8D3A6E7}, // 1e144 + {0x36DBA887C37A8C0F, 0xCD036837130890A1}, // 1e145 + {0xC2494954DA2C9789, 0x802221226BE55A64}, // 1e146 + {0xF2DB9BAA10B7BD6C, 0xA02AA96B06DEB0FD}, // 1e147 + {0x6F92829494E5ACC7, 0xC83553C5C8965D3D}, // 1e148 + {0xCB772339BA1F17F9, 0xFA42A8B73ABBF48C}, // 1e149 + {0xFF2A760414536EFB, 0x9C69A97284B578D7}, // 1e150 + {0xFEF5138519684ABA, 0xC38413CF25E2D70D}, // 1e151 + {0x7EB258665FC25D69, 0xF46518C2EF5B8CD1}, // 1e152 + {0xEF2F773FFBD97A61, 0x98BF2F79D5993802}, // 1e153 + {0xAAFB550FFACFD8FA, 0xBEEEFB584AFF8603}, // 1e154 + {0x95BA2A53F983CF38, 0xEEAABA2E5DBF6784}, // 1e155 + {0xDD945A747BF26183, 0x952AB45CFA97A0B2}, // 1e156 + {0x94F971119AEEF9E4, 0xBA756174393D88DF}, // 1e157 + {0x7A37CD5601AAB85D, 0xE912B9D1478CEB17}, // 1e158 + {0xAC62E055C10AB33A, 0x91ABB422CCB812EE}, // 1e159 + {0x577B986B314D6009, 0xB616A12B7FE617AA}, // 1e160 + {0xED5A7E85FDA0B80B, 0xE39C49765FDF9D94}, // 1e161 + {0x14588F13BE847307, 0x8E41ADE9FBEBC27D}, // 1e162 + {0x596EB2D8AE258FC8, 0xB1D219647AE6B31C}, // 1e163 + {0x6FCA5F8ED9AEF3BB, 0xDE469FBD99A05FE3}, // 1e164 + {0x25DE7BB9480D5854, 0x8AEC23D680043BEE}, // 1e165 + {0xAF561AA79A10AE6A, 0xADA72CCC20054AE9}, // 1e166 + {0x1B2BA1518094DA04, 0xD910F7FF28069DA4}, // 1e167 + {0x90FB44D2F05D0842, 0x87AA9AFF79042286}, // 1e168 + {0x353A1607AC744A53, 0xA99541BF57452B28}, // 1e169 + {0x42889B8997915CE8, 0xD3FA922F2D1675F2}, // 1e170 + {0x69956135FEBADA11, 0x847C9B5D7C2E09B7}, // 1e171 + {0x43FAB9837E699095, 0xA59BC234DB398C25}, // 1e172 + {0x94F967E45E03F4BB, 0xCF02B2C21207EF2E}, // 1e173 + {0x1D1BE0EEBAC278F5, 0x8161AFB94B44F57D}, // 1e174 + {0x6462D92A69731732, 0xA1BA1BA79E1632DC}, // 1e175 + {0x7D7B8F7503CFDCFE, 0xCA28A291859BBF93}, // 1e176 + {0x5CDA735244C3D43E, 0xFCB2CB35E702AF78}, // 1e177 + {0x3A0888136AFA64A7, 0x9DEFBF01B061ADAB}, // 1e178 + {0x088AAA1845B8FDD0, 0xC56BAEC21C7A1916}, // 1e179 + {0x8AAD549E57273D45, 0xF6C69A72A3989F5B}, // 1e180 + {0x36AC54E2F678864B, 0x9A3C2087A63F6399}, // 1e181 + {0x84576A1BB416A7DD, 0xC0CB28A98FCF3C7F}, // 1e182 + {0x656D44A2A11C51D5, 0xF0FDF2D3F3C30B9F}, // 1e183 + {0x9F644AE5A4B1B325, 0x969EB7C47859E743}, // 1e184 + {0x873D5D9F0DDE1FEE, 0xBC4665B596706114}, // 1e185 + {0xA90CB506D155A7EA, 0xEB57FF22FC0C7959}, // 1e186 + {0x09A7F12442D588F2, 0x9316FF75DD87CBD8}, // 1e187 + {0x0C11ED6D538AEB2F, 0xB7DCBF5354E9BECE}, // 1e188 + {0x8F1668C8A86DA5FA, 0xE5D3EF282A242E81}, // 1e189 + {0xF96E017D694487BC, 0x8FA475791A569D10}, // 1e190 + {0x37C981DCC395A9AC, 0xB38D92D760EC4455}, // 1e191 + {0x85BBE253F47B1417, 0xE070F78D3927556A}, // 1e192 + {0x93956D7478CCEC8E, 0x8C469AB843B89562}, // 1e193 + {0x387AC8D1970027B2, 0xAF58416654A6BABB}, // 1e194 + {0x06997B05FCC0319E, 0xDB2E51BFE9D0696A}, // 1e195 + {0x441FECE3BDF81F03, 0x88FCF317F22241E2}, // 1e196 + {0xD527E81CAD7626C3, 0xAB3C2FDDEEAAD25A}, // 1e197 + {0x8A71E223D8D3B074, 0xD60B3BD56A5586F1}, // 1e198 + {0xF6872D5667844E49, 0x85C7056562757456}, // 1e199 + {0xB428F8AC016561DB, 0xA738C6BEBB12D16C}, // 1e200 + {0xE13336D701BEBA52, 0xD106F86E69D785C7}, // 1e201 + {0xECC0024661173473, 0x82A45B450226B39C}, // 1e202 + {0x27F002D7F95D0190, 0xA34D721642B06084}, // 1e203 + {0x31EC038DF7B441F4, 0xCC20CE9BD35C78A5}, // 1e204 + {0x7E67047175A15271, 0xFF290242C83396CE}, // 1e205 + {0x0F0062C6E984D386, 0x9F79A169BD203E41}, // 1e206 + {0x52C07B78A3E60868, 0xC75809C42C684DD1}, // 1e207 + {0xA7709A56CCDF8A82, 0xF92E0C3537826145}, // 1e208 + {0x88A66076400BB691, 0x9BBCC7A142B17CCB}, // 1e209 + {0x6ACFF893D00EA435, 0xC2ABF989935DDBFE}, // 1e210 + {0x0583F6B8C4124D43, 0xF356F7EBF83552FE}, // 1e211 + {0xC3727A337A8B704A, 0x98165AF37B2153DE}, // 1e212 + {0x744F18C0592E4C5C, 0xBE1BF1B059E9A8D6}, // 1e213 + {0x1162DEF06F79DF73, 0xEDA2EE1C7064130C}, // 1e214 + {0x8ADDCB5645AC2BA8, 0x9485D4D1C63E8BE7}, // 1e215 + {0x6D953E2BD7173692, 0xB9A74A0637CE2EE1}, // 1e216 + {0xC8FA8DB6CCDD0437, 0xE8111C87C5C1BA99}, // 1e217 + {0x1D9C9892400A22A2, 0x910AB1D4DB9914A0}, // 1e218 + {0x2503BEB6D00CAB4B, 0xB54D5E4A127F59C8}, // 1e219 + {0x2E44AE64840FD61D, 0xE2A0B5DC971F303A}, // 1e220 + {0x5CEAECFED289E5D2, 0x8DA471A9DE737E24}, // 1e221 + {0x7425A83E872C5F47, 0xB10D8E1456105DAD}, // 1e222 + {0xD12F124E28F77719, 0xDD50F1996B947518}, // 1e223 + {0x82BD6B70D99AAA6F, 0x8A5296FFE33CC92F}, // 1e224 + {0x636CC64D1001550B, 0xACE73CBFDC0BFB7B}, // 1e225 + {0x3C47F7E05401AA4E, 0xD8210BEFD30EFA5A}, // 1e226 + {0x65ACFAEC34810A71, 0x8714A775E3E95C78}, // 1e227 + {0x7F1839A741A14D0D, 0xA8D9D1535CE3B396}, // 1e228 + {0x1EDE48111209A050, 0xD31045A8341CA07C}, // 1e229 + {0x934AED0AAB460432, 0x83EA2B892091E44D}, // 1e230 + {0xF81DA84D5617853F, 0xA4E4B66B68B65D60}, // 1e231 + {0x36251260AB9D668E, 0xCE1DE40642E3F4B9}, // 1e232 + {0xC1D72B7C6B426019, 0x80D2AE83E9CE78F3}, // 1e233 + {0xB24CF65B8612F81F, 0xA1075A24E4421730}, // 1e234 + {0xDEE033F26797B627, 0xC94930AE1D529CFC}, // 1e235 + {0x169840EF017DA3B1, 0xFB9B7CD9A4A7443C}, // 1e236 + {0x8E1F289560EE864E, 0x9D412E0806E88AA5}, // 1e237 + {0xF1A6F2BAB92A27E2, 0xC491798A08A2AD4E}, // 1e238 + {0xAE10AF696774B1DB, 0xF5B5D7EC8ACB58A2}, // 1e239 + {0xACCA6DA1E0A8EF29, 0x9991A6F3D6BF1765}, // 1e240 + {0x17FD090A58D32AF3, 0xBFF610B0CC6EDD3F}, // 1e241 + {0xDDFC4B4CEF07F5B0, 0xEFF394DCFF8A948E}, // 1e242 + {0x4ABDAF101564F98E, 0x95F83D0A1FB69CD9}, // 1e243 + {0x9D6D1AD41ABE37F1, 0xBB764C4CA7A4440F}, // 1e244 + {0x84C86189216DC5ED, 0xEA53DF5FD18D5513}, // 1e245 + {0x32FD3CF5B4E49BB4, 0x92746B9BE2F8552C}, // 1e246 + {0x3FBC8C33221DC2A1, 0xB7118682DBB66A77}, // 1e247 + {0x0FABAF3FEAA5334A, 0xE4D5E82392A40515}, // 1e248 + {0x29CB4D87F2A7400E, 0x8F05B1163BA6832D}, // 1e249 + {0x743E20E9EF511012, 0xB2C71D5BCA9023F8}, // 1e250 + {0x914DA9246B255416, 0xDF78E4B2BD342CF6}, // 1e251 + {0x1AD089B6C2F7548E, 0x8BAB8EEFB6409C1A}, // 1e252 + {0xA184AC2473B529B1, 0xAE9672ABA3D0C320}, // 1e253 + {0xC9E5D72D90A2741E, 0xDA3C0F568CC4F3E8}, // 1e254 + {0x7E2FA67C7A658892, 0x8865899617FB1871}, // 1e255 + {0xDDBB901B98FEEAB7, 0xAA7EEBFB9DF9DE8D}, // 1e256 + {0x552A74227F3EA565, 0xD51EA6FA85785631}, // 1e257 + {0xD53A88958F87275F, 0x8533285C936B35DE}, // 1e258 + {0x8A892ABAF368F137, 0xA67FF273B8460356}, // 1e259 + {0x2D2B7569B0432D85, 0xD01FEF10A657842C}, // 1e260 + {0x9C3B29620E29FC73, 0x8213F56A67F6B29B}, // 1e261 + {0x8349F3BA91B47B8F, 0xA298F2C501F45F42}, // 1e262 + {0x241C70A936219A73, 0xCB3F2F7642717713}, // 1e263 + {0xED238CD383AA0110, 0xFE0EFB53D30DD4D7}, // 1e264 + {0xF4363804324A40AA, 0x9EC95D1463E8A506}, // 1e265 + {0xB143C6053EDCD0D5, 0xC67BB4597CE2CE48}, // 1e266 + {0xDD94B7868E94050A, 0xF81AA16FDC1B81DA}, // 1e267 + {0xCA7CF2B4191C8326, 0x9B10A4E5E9913128}, // 1e268 + {0xFD1C2F611F63A3F0, 0xC1D4CE1F63F57D72}, // 1e269 + {0xBC633B39673C8CEC, 0xF24A01A73CF2DCCF}, // 1e270 + {0xD5BE0503E085D813, 0x976E41088617CA01}, // 1e271 + {0x4B2D8644D8A74E18, 0xBD49D14AA79DBC82}, // 1e272 + {0xDDF8E7D60ED1219E, 0xEC9C459D51852BA2}, // 1e273 + {0xCABB90E5C942B503, 0x93E1AB8252F33B45}, // 1e274 + {0x3D6A751F3B936243, 0xB8DA1662E7B00A17}, // 1e275 + {0x0CC512670A783AD4, 0xE7109BFBA19C0C9D}, // 1e276 + {0x27FB2B80668B24C5, 0x906A617D450187E2}, // 1e277 + {0xB1F9F660802DEDF6, 0xB484F9DC9641E9DA}, // 1e278 + {0x5E7873F8A0396973, 0xE1A63853BBD26451}, // 1e279 + {0xDB0B487B6423E1E8, 0x8D07E33455637EB2}, // 1e280 + {0x91CE1A9A3D2CDA62, 0xB049DC016ABC5E5F}, // 1e281 + {0x7641A140CC7810FB, 0xDC5C5301C56B75F7}, // 1e282 + {0xA9E904C87FCB0A9D, 0x89B9B3E11B6329BA}, // 1e283 + {0x546345FA9FBDCD44, 0xAC2820D9623BF429}, // 1e284 + {0xA97C177947AD4095, 0xD732290FBACAF133}, // 1e285 + {0x49ED8EABCCCC485D, 0x867F59A9D4BED6C0}, // 1e286 + {0x5C68F256BFFF5A74, 0xA81F301449EE8C70}, // 1e287 + {0x73832EEC6FFF3111, 0xD226FC195C6A2F8C}, // 1e288 +}; + +// wuffs_base__private_implementation__f64_powers_of_10 holds powers of 10 that +// can be exactly represented by a float64 (what C calls a double). +static const double wuffs_base__private_implementation__f64_powers_of_10[23] = { + 1e0, 1e1, 1e2, 1e3, 1e4, 1e5, 1e6, 1e7, 1e8, 1e9, 1e10, 1e11, + 1e12, 1e13, 1e14, 1e15, 1e16, 1e17, 1e18, 1e19, 1e20, 1e21, 1e22, +}; + +// ---------------- IEEE 754 Floating Point + +WUFFS_BASE__MAYBE_STATIC wuffs_base__lossy_value_u16 // +wuffs_base__ieee_754_bit_representation__from_f64_to_u16_truncate(double f) { + uint64_t u = 0; + if (sizeof(uint64_t) == sizeof(double)) { + memcpy(&u, &f, sizeof(uint64_t)); + } + uint16_t neg = ((uint16_t)((u >> 63) << 15)); + u &= 0x7FFFFFFFFFFFFFFF; + uint64_t exp = u >> 52; + uint64_t man = u & 0x000FFFFFFFFFFFFF; + + if (exp == 0x7FF) { + if (man == 0) { // Infinity. + wuffs_base__lossy_value_u16 ret; + ret.value = neg | 0x7C00; + ret.lossy = false; + return ret; + } + // NaN. Shift the 52 mantissa bits to 10 mantissa bits, keeping the most + // significant mantissa bit (quiet vs signaling NaNs). Also set the low 9 + // bits of ret.value so that the 10-bit mantissa is non-zero. + wuffs_base__lossy_value_u16 ret; + ret.value = neg | 0x7DFF | ((uint16_t)(man >> 42)); + ret.lossy = false; + return ret; + + } else if (exp > 0x40E) { // Truncate to the largest finite f16. + wuffs_base__lossy_value_u16 ret; + ret.value = neg | 0x7BFF; + ret.lossy = true; + return ret; + + } else if (exp <= 0x3E6) { // Truncate to zero. + wuffs_base__lossy_value_u16 ret; + ret.value = neg; + ret.lossy = (u != 0); + return ret; + + } else if (exp <= 0x3F0) { // Normal f64, subnormal f16. + // Convert from a 53-bit mantissa (after realizing the implicit bit) to a + // 10-bit mantissa and then adjust for the exponent. + man |= 0x0010000000000000; + uint32_t shift = ((uint32_t)(1051 - exp)); // 1051 = 0x3F0 + 53 - 10. + uint64_t shifted_man = man >> shift; + wuffs_base__lossy_value_u16 ret; + ret.value = neg | ((uint16_t)shifted_man); + ret.lossy = (shifted_man << shift) != man; + return ret; + } + + // Normal f64, normal f16. + + // Re-bias from 1023 to 15 and shift above f16's 10 mantissa bits. + exp = (exp - 1008) << 10; // 1008 = 1023 - 15 = 0x3FF - 0xF. + + // Convert from a 52-bit mantissa (excluding the implicit bit) to a 10-bit + // mantissa (again excluding the implicit bit). We lose some information if + // any of the bottom 42 bits are non-zero. + wuffs_base__lossy_value_u16 ret; + ret.value = neg | ((uint16_t)exp) | ((uint16_t)(man >> 42)); + ret.lossy = (man << 22) != 0; + return ret; +} + +WUFFS_BASE__MAYBE_STATIC wuffs_base__lossy_value_u32 // +wuffs_base__ieee_754_bit_representation__from_f64_to_u32_truncate(double f) { + uint64_t u = 0; + if (sizeof(uint64_t) == sizeof(double)) { + memcpy(&u, &f, sizeof(uint64_t)); + } + uint32_t neg = ((uint32_t)(u >> 63)) << 31; + u &= 0x7FFFFFFFFFFFFFFF; + uint64_t exp = u >> 52; + uint64_t man = u & 0x000FFFFFFFFFFFFF; + + if (exp == 0x7FF) { + if (man == 0) { // Infinity. + wuffs_base__lossy_value_u32 ret; + ret.value = neg | 0x7F800000; + ret.lossy = false; + return ret; + } + // NaN. Shift the 52 mantissa bits to 23 mantissa bits, keeping the most + // significant mantissa bit (quiet vs signaling NaNs). Also set the low 22 + // bits of ret.value so that the 23-bit mantissa is non-zero. + wuffs_base__lossy_value_u32 ret; + ret.value = neg | 0x7FBFFFFF | ((uint32_t)(man >> 29)); + ret.lossy = false; + return ret; + + } else if (exp > 0x47E) { // Truncate to the largest finite f32. + wuffs_base__lossy_value_u32 ret; + ret.value = neg | 0x7F7FFFFF; + ret.lossy = true; + return ret; + + } else if (exp <= 0x369) { // Truncate to zero. + wuffs_base__lossy_value_u32 ret; + ret.value = neg; + ret.lossy = (u != 0); + return ret; + + } else if (exp <= 0x380) { // Normal f64, subnormal f32. + // Convert from a 53-bit mantissa (after realizing the implicit bit) to a + // 23-bit mantissa and then adjust for the exponent. + man |= 0x0010000000000000; + uint32_t shift = ((uint32_t)(926 - exp)); // 926 = 0x380 + 53 - 23. + uint64_t shifted_man = man >> shift; + wuffs_base__lossy_value_u32 ret; + ret.value = neg | ((uint32_t)shifted_man); + ret.lossy = (shifted_man << shift) != man; + return ret; + } + + // Normal f64, normal f32. + + // Re-bias from 1023 to 127 and shift above f32's 23 mantissa bits. + exp = (exp - 896) << 23; // 896 = 1023 - 127 = 0x3FF - 0x7F. + + // Convert from a 52-bit mantissa (excluding the implicit bit) to a 23-bit + // mantissa (again excluding the implicit bit). We lose some information if + // any of the bottom 29 bits are non-zero. + wuffs_base__lossy_value_u32 ret; + ret.value = neg | ((uint32_t)exp) | ((uint32_t)(man >> 29)); + ret.lossy = (man << 35) != 0; + return ret; +} + +// -------- + +#define WUFFS_BASE__PRIVATE_IMPLEMENTATION__HPD__DECIMAL_POINT__RANGE 2047 +#define WUFFS_BASE__PRIVATE_IMPLEMENTATION__HPD__DIGITS_PRECISION 800 + +// WUFFS_BASE__PRIVATE_IMPLEMENTATION__HPD__SHIFT__MAX_INCL is the largest N +// such that ((10 << N) < (1 << 64)). +#define WUFFS_BASE__PRIVATE_IMPLEMENTATION__HPD__SHIFT__MAX_INCL 60 + +// wuffs_base__private_implementation__high_prec_dec (abbreviated as HPD) is a +// fixed precision floating point decimal number, augmented with ±infinity +// values, but it cannot represent NaN (Not a Number). +// +// "High precision" means that the mantissa holds 800 decimal digits. 800 is +// WUFFS_BASE__PRIVATE_IMPLEMENTATION__HPD__DIGITS_PRECISION. +// +// An HPD isn't for general purpose arithmetic, only for conversions to and +// from IEEE 754 double-precision floating point, where the largest and +// smallest positive, finite values are approximately 1.8e+308 and 4.9e-324. +// HPD exponents above +2047 mean infinity, below -2047 mean zero. The ±2047 +// bounds are further away from zero than ±(324 + 800), where 800 and 2047 is +// WUFFS_BASE__PRIVATE_IMPLEMENTATION__HPD__DIGITS_PRECISION and +// WUFFS_BASE__PRIVATE_IMPLEMENTATION__HPD__DECIMAL_POINT__RANGE. +// +// digits[.. num_digits] are the number's digits in big-endian order. The +// uint8_t values are in the range [0 ..= 9], not ['0' ..= '9'], where e.g. '7' +// is the ASCII value 0x37. +// +// decimal_point is the index (within digits) of the decimal point. It may be +// negative or be larger than num_digits, in which case the explicit digits are +// padded with implicit zeroes. +// +// For example, if num_digits is 3 and digits is "\x07\x08\x09": +// - A decimal_point of -2 means ".00789" +// - A decimal_point of -1 means ".0789" +// - A decimal_point of +0 means ".789" +// - A decimal_point of +1 means "7.89" +// - A decimal_point of +2 means "78.9" +// - A decimal_point of +3 means "789." +// - A decimal_point of +4 means "7890." +// - A decimal_point of +5 means "78900." +// +// As above, a decimal_point higher than +2047 means that the overall value is +// infinity, lower than -2047 means zero. +// +// negative is a sign bit. An HPD can distinguish positive and negative zero. +// +// truncated is whether there are more than +// WUFFS_BASE__PRIVATE_IMPLEMENTATION__HPD__DIGITS_PRECISION digits, and at +// least one of those extra digits are non-zero. The existence of long-tail +// digits can affect rounding. +// +// The "all fields are zero" value is valid, and represents the number +0. +typedef struct wuffs_base__private_implementation__high_prec_dec__struct { + uint32_t num_digits; + int32_t decimal_point; + bool negative; + bool truncated; + uint8_t digits[WUFFS_BASE__PRIVATE_IMPLEMENTATION__HPD__DIGITS_PRECISION]; +} wuffs_base__private_implementation__high_prec_dec; + +// wuffs_base__private_implementation__high_prec_dec__trim trims trailing +// zeroes from the h->digits[.. h->num_digits] slice. They have no benefit, +// since we explicitly track h->decimal_point. +// +// Preconditions: +// - h is non-NULL. +static inline void // +wuffs_base__private_implementation__high_prec_dec__trim( + wuffs_base__private_implementation__high_prec_dec* h) { + while ((h->num_digits > 0) && (h->digits[h->num_digits - 1] == 0)) { + h->num_digits--; + } +} + +// wuffs_base__private_implementation__high_prec_dec__assign sets h to +// represent the number x. +// +// Preconditions: +// - h is non-NULL. +static void // +wuffs_base__private_implementation__high_prec_dec__assign( + wuffs_base__private_implementation__high_prec_dec* h, + uint64_t x, + bool negative) { + uint32_t n = 0; + + // Set h->digits. + if (x > 0) { + // Calculate the digits, working right-to-left. After we determine n (how + // many digits there are), copy from buf to h->digits. + // + // UINT64_MAX, 18446744073709551615, is 20 digits long. It can be faster to + // copy a constant number of bytes than a variable number (20 instead of + // n). Make buf large enough (and start writing to it from the middle) so + // that can we always copy 20 bytes: the slice buf[(20-n) .. (40-n)]. + uint8_t buf[40] = {0}; + uint8_t* ptr = &buf[20]; + do { + uint64_t remaining = x / 10; + x -= remaining * 10; + ptr--; + *ptr = (uint8_t)x; + n++; + x = remaining; + } while (x > 0); + memcpy(h->digits, ptr, 20); + } + + // Set h's other fields. + h->num_digits = n; + h->decimal_point = (int32_t)n; + h->negative = negative; + h->truncated = false; + wuffs_base__private_implementation__high_prec_dec__trim(h); +} + +static wuffs_base__status // +wuffs_base__private_implementation__high_prec_dec__parse( + wuffs_base__private_implementation__high_prec_dec* h, + wuffs_base__slice_u8 s, + uint32_t options) { + if (!h) { + return wuffs_base__make_status(wuffs_base__error__bad_receiver); + } + h->num_digits = 0; + h->decimal_point = 0; + h->negative = false; + h->truncated = false; + + uint8_t* p = s.ptr; + uint8_t* q = s.ptr + s.len; + + if (options & WUFFS_BASE__PARSE_NUMBER_XXX__ALLOW_UNDERSCORES) { + for (;; p++) { + if (p >= q) { + return wuffs_base__make_status(wuffs_base__error__bad_argument); + } else if (*p != '_') { + break; + } + } + } + + // Parse sign. + do { + if (*p == '+') { + p++; + } else if (*p == '-') { + h->negative = true; + p++; + } else { + break; + } + if (options & WUFFS_BASE__PARSE_NUMBER_XXX__ALLOW_UNDERSCORES) { + for (;; p++) { + if (p >= q) { + return wuffs_base__make_status(wuffs_base__error__bad_argument); + } else if (*p != '_') { + break; + } + } + } + } while (0); + + // Parse digits, up to (and including) a '.', 'E' or 'e'. Examples for each + // limb in this if-else chain: + // - "0.789" + // - "1002.789" + // - ".789" + // - Other (invalid input). + uint32_t nd = 0; + int32_t dp = 0; + bool no_digits_before_separator = false; + if (('0' == *p) && + !(options & + WUFFS_BASE__PARSE_NUMBER_XXX__ALLOW_MULTIPLE_LEADING_ZEROES)) { + p++; + for (;; p++) { + if (p >= q) { + goto after_all; + } else if (*p == + ((options & + WUFFS_BASE__PARSE_NUMBER_FXX__DECIMAL_SEPARATOR_IS_A_COMMA) + ? ',' + : '.')) { + p++; + goto after_sep; + } else if ((*p == 'E') || (*p == 'e')) { + p++; + goto after_exp; + } else if ((*p != '_') || + !(options & WUFFS_BASE__PARSE_NUMBER_XXX__ALLOW_UNDERSCORES)) { + return wuffs_base__make_status(wuffs_base__error__bad_argument); + } + } + + } else if (('0' <= *p) && (*p <= '9')) { + if (*p == '0') { + for (; (p < q) && (*p == '0'); p++) { + } + } else { + h->digits[nd++] = (uint8_t)(*p - '0'); + dp = (int32_t)nd; + p++; + } + + for (;; p++) { + if (p >= q) { + goto after_all; + } else if (('0' <= *p) && (*p <= '9')) { + if (nd < WUFFS_BASE__PRIVATE_IMPLEMENTATION__HPD__DIGITS_PRECISION) { + h->digits[nd++] = (uint8_t)(*p - '0'); + dp = (int32_t)nd; + } else if ('0' != *p) { + // Long-tail non-zeroes set the truncated bit. + h->truncated = true; + } + } else if (*p == + ((options & + WUFFS_BASE__PARSE_NUMBER_FXX__DECIMAL_SEPARATOR_IS_A_COMMA) + ? ',' + : '.')) { + p++; + goto after_sep; + } else if ((*p == 'E') || (*p == 'e')) { + p++; + goto after_exp; + } else if ((*p != '_') || + !(options & WUFFS_BASE__PARSE_NUMBER_XXX__ALLOW_UNDERSCORES)) { + return wuffs_base__make_status(wuffs_base__error__bad_argument); + } + } + + } else if (*p == ((options & + WUFFS_BASE__PARSE_NUMBER_FXX__DECIMAL_SEPARATOR_IS_A_COMMA) + ? ',' + : '.')) { + p++; + no_digits_before_separator = true; + + } else { + return wuffs_base__make_status(wuffs_base__error__bad_argument); + } + +after_sep: + for (;; p++) { + if (p >= q) { + goto after_all; + } else if ('0' == *p) { + if (nd == 0) { + // Track leading zeroes implicitly. + dp--; + } else if (nd < + WUFFS_BASE__PRIVATE_IMPLEMENTATION__HPD__DIGITS_PRECISION) { + h->digits[nd++] = (uint8_t)(*p - '0'); + } + } else if (('0' < *p) && (*p <= '9')) { + if (nd < WUFFS_BASE__PRIVATE_IMPLEMENTATION__HPD__DIGITS_PRECISION) { + h->digits[nd++] = (uint8_t)(*p - '0'); + } else { + // Long-tail non-zeroes set the truncated bit. + h->truncated = true; + } + } else if ((*p == 'E') || (*p == 'e')) { + p++; + goto after_exp; + } else if ((*p != '_') || + !(options & WUFFS_BASE__PARSE_NUMBER_XXX__ALLOW_UNDERSCORES)) { + return wuffs_base__make_status(wuffs_base__error__bad_argument); + } + } + +after_exp: + do { + if (options & WUFFS_BASE__PARSE_NUMBER_XXX__ALLOW_UNDERSCORES) { + for (;; p++) { + if (p >= q) { + return wuffs_base__make_status(wuffs_base__error__bad_argument); + } else if (*p != '_') { + break; + } + } + } + + int32_t exp_sign = +1; + if (*p == '+') { + p++; + } else if (*p == '-') { + exp_sign = -1; + p++; + } + + int32_t exp = 0; + const int32_t exp_large = + WUFFS_BASE__PRIVATE_IMPLEMENTATION__HPD__DECIMAL_POINT__RANGE + + WUFFS_BASE__PRIVATE_IMPLEMENTATION__HPD__DIGITS_PRECISION; + bool saw_exp_digits = false; + for (; p < q; p++) { + if ((*p == '_') && + (options & WUFFS_BASE__PARSE_NUMBER_XXX__ALLOW_UNDERSCORES)) { + // No-op. + } else if (('0' <= *p) && (*p <= '9')) { + saw_exp_digits = true; + if (exp < exp_large) { + exp = (10 * exp) + ((int32_t)(*p - '0')); + } + } else { + break; + } + } + if (!saw_exp_digits) { + return wuffs_base__make_status(wuffs_base__error__bad_argument); + } + dp += exp_sign * exp; + } while (0); + +after_all: + if (p != q) { + return wuffs_base__make_status(wuffs_base__error__bad_argument); + } + h->num_digits = nd; + if (nd == 0) { + if (no_digits_before_separator) { + return wuffs_base__make_status(wuffs_base__error__bad_argument); + } + h->decimal_point = 0; + } else if (dp < + -WUFFS_BASE__PRIVATE_IMPLEMENTATION__HPD__DECIMAL_POINT__RANGE) { + h->decimal_point = + -WUFFS_BASE__PRIVATE_IMPLEMENTATION__HPD__DECIMAL_POINT__RANGE - 1; + } else if (dp > + +WUFFS_BASE__PRIVATE_IMPLEMENTATION__HPD__DECIMAL_POINT__RANGE) { + h->decimal_point = + +WUFFS_BASE__PRIVATE_IMPLEMENTATION__HPD__DECIMAL_POINT__RANGE + 1; + } else { + h->decimal_point = dp; + } + wuffs_base__private_implementation__high_prec_dec__trim(h); + return wuffs_base__make_status(NULL); +} + +// -------- + +// wuffs_base__private_implementation__high_prec_dec__lshift_num_new_digits +// returns the number of additional decimal digits when left-shifting by shift. +// +// See below for preconditions. +static uint32_t // +wuffs_base__private_implementation__high_prec_dec__lshift_num_new_digits( + wuffs_base__private_implementation__high_prec_dec* h, + uint32_t shift) { + // Masking with 0x3F should be unnecessary (assuming the preconditions) but + // it's cheap and ensures that we don't overflow the + // wuffs_base__private_implementation__hpd_left_shift array. + shift &= 63; + + uint32_t x_a = wuffs_base__private_implementation__hpd_left_shift[shift]; + uint32_t x_b = wuffs_base__private_implementation__hpd_left_shift[shift + 1]; + uint32_t num_new_digits = x_a >> 11; + uint32_t pow5_a = 0x7FF & x_a; + uint32_t pow5_b = 0x7FF & x_b; + + const uint8_t* pow5 = + &wuffs_base__private_implementation__powers_of_5[pow5_a]; + uint32_t i = 0; + uint32_t n = pow5_b - pow5_a; + for (; i < n; i++) { + if (i >= h->num_digits) { + return num_new_digits - 1; + } else if (h->digits[i] == pow5[i]) { + continue; + } else if (h->digits[i] < pow5[i]) { + return num_new_digits - 1; + } else { + return num_new_digits; + } + } + return num_new_digits; +} + +// -------- + +// wuffs_base__private_implementation__high_prec_dec__rounded_integer returns +// the integral (non-fractional) part of h, provided that it is 18 or fewer +// decimal digits. For 19 or more digits, it returns UINT64_MAX. Note that: +// - (1 << 53) is 9007199254740992, which has 16 decimal digits. +// - (1 << 56) is 72057594037927936, which has 17 decimal digits. +// - (1 << 59) is 576460752303423488, which has 18 decimal digits. +// - (1 << 63) is 9223372036854775808, which has 19 decimal digits. +// and that IEEE 754 double precision has 52 mantissa bits. +// +// That integral part is rounded-to-even: rounding 7.5 or 8.5 both give 8. +// +// h's negative bit is ignored: rounding -8.6 returns 9. +// +// See below for preconditions. +static uint64_t // +wuffs_base__private_implementation__high_prec_dec__rounded_integer( + wuffs_base__private_implementation__high_prec_dec* h) { + if ((h->num_digits == 0) || (h->decimal_point < 0)) { + return 0; + } else if (h->decimal_point > 18) { + return UINT64_MAX; + } + + uint32_t dp = (uint32_t)(h->decimal_point); + uint64_t n = 0; + uint32_t i = 0; + for (; i < dp; i++) { + n = (10 * n) + ((i < h->num_digits) ? h->digits[i] : 0); + } + + bool round_up = false; + if (dp < h->num_digits) { + round_up = h->digits[dp] >= 5; + if ((h->digits[dp] == 5) && (dp + 1 == h->num_digits)) { + // We are exactly halfway. If we're truncated, round up, otherwise round + // to even. + round_up = h->truncated || // + ((dp > 0) && (1 & h->digits[dp - 1])); + } + } + if (round_up) { + n++; + } + + return n; +} + +// wuffs_base__private_implementation__high_prec_dec__small_xshift shifts h's +// number (where 'x' is 'l' or 'r' for left or right) by a small shift value. +// +// Preconditions: +// - h is non-NULL. +// - h->decimal_point is "not extreme". +// - shift is non-zero. +// - shift is "a small shift". +// +// "Not extreme" means within +// ±WUFFS_BASE__PRIVATE_IMPLEMENTATION__HPD__DECIMAL_POINT__RANGE. +// +// "A small shift" means not more than +// WUFFS_BASE__PRIVATE_IMPLEMENTATION__HPD__SHIFT__MAX_INCL. +// +// wuffs_base__private_implementation__high_prec_dec__rounded_integer and +// wuffs_base__private_implementation__high_prec_dec__lshift_num_new_digits +// have the same preconditions. +// +// wuffs_base__private_implementation__high_prec_dec__lshift keeps the first +// two preconditions but not the last two. Its shift argument is signed and +// does not need to be "small": zero is a no-op, positive means left shift and +// negative means right shift. + +static void // +wuffs_base__private_implementation__high_prec_dec__small_lshift( + wuffs_base__private_implementation__high_prec_dec* h, + uint32_t shift) { + if (h->num_digits == 0) { + return; + } + uint32_t num_new_digits = + wuffs_base__private_implementation__high_prec_dec__lshift_num_new_digits( + h, shift); + uint32_t rx = h->num_digits - 1; // Read index. + uint32_t wx = h->num_digits - 1 + num_new_digits; // Write index. + uint64_t n = 0; + + // Repeat: pick up a digit, put down a digit, right to left. + while (((int32_t)rx) >= 0) { + n += ((uint64_t)(h->digits[rx])) << shift; + uint64_t quo = n / 10; + uint64_t rem = n - (10 * quo); + if (wx < WUFFS_BASE__PRIVATE_IMPLEMENTATION__HPD__DIGITS_PRECISION) { + h->digits[wx] = (uint8_t)rem; + } else if (rem > 0) { + h->truncated = true; + } + n = quo; + wx--; + rx--; + } + + // Put down leading digits, right to left. + while (n > 0) { + uint64_t quo = n / 10; + uint64_t rem = n - (10 * quo); + if (wx < WUFFS_BASE__PRIVATE_IMPLEMENTATION__HPD__DIGITS_PRECISION) { + h->digits[wx] = (uint8_t)rem; + } else if (rem > 0) { + h->truncated = true; + } + n = quo; + wx--; + } + + // Finish. + h->num_digits += num_new_digits; + if (h->num_digits > + WUFFS_BASE__PRIVATE_IMPLEMENTATION__HPD__DIGITS_PRECISION) { + h->num_digits = WUFFS_BASE__PRIVATE_IMPLEMENTATION__HPD__DIGITS_PRECISION; + } + h->decimal_point += (int32_t)num_new_digits; + wuffs_base__private_implementation__high_prec_dec__trim(h); +} + +static void // +wuffs_base__private_implementation__high_prec_dec__small_rshift( + wuffs_base__private_implementation__high_prec_dec* h, + uint32_t shift) { + uint32_t rx = 0; // Read index. + uint32_t wx = 0; // Write index. + uint64_t n = 0; + + // Pick up enough leading digits to cover the first shift. + while ((n >> shift) == 0) { + if (rx < h->num_digits) { + // Read a digit. + n = (10 * n) + h->digits[rx++]; + } else if (n == 0) { + // h's number used to be zero and remains zero. + return; + } else { + // Read sufficient implicit trailing zeroes. + while ((n >> shift) == 0) { + n = 10 * n; + rx++; + } + break; + } + } + h->decimal_point -= ((int32_t)(rx - 1)); + if (h->decimal_point < + -WUFFS_BASE__PRIVATE_IMPLEMENTATION__HPD__DECIMAL_POINT__RANGE) { + // After the shift, h's number is effectively zero. + h->num_digits = 0; + h->decimal_point = 0; + h->truncated = false; + return; + } + + // Repeat: pick up a digit, put down a digit, left to right. + uint64_t mask = (((uint64_t)(1)) << shift) - 1; + while (rx < h->num_digits) { + uint8_t new_digit = ((uint8_t)(n >> shift)); + n = (10 * (n & mask)) + h->digits[rx++]; + h->digits[wx++] = new_digit; + } + + // Put down trailing digits, left to right. + while (n > 0) { + uint8_t new_digit = ((uint8_t)(n >> shift)); + n = 10 * (n & mask); + if (wx < WUFFS_BASE__PRIVATE_IMPLEMENTATION__HPD__DIGITS_PRECISION) { + h->digits[wx++] = new_digit; + } else if (new_digit > 0) { + h->truncated = true; + } + } + + // Finish. + h->num_digits = wx; + wuffs_base__private_implementation__high_prec_dec__trim(h); +} + +static void // +wuffs_base__private_implementation__high_prec_dec__lshift( + wuffs_base__private_implementation__high_prec_dec* h, + int32_t shift) { + if (shift > 0) { + while (shift > +WUFFS_BASE__PRIVATE_IMPLEMENTATION__HPD__SHIFT__MAX_INCL) { + wuffs_base__private_implementation__high_prec_dec__small_lshift( + h, WUFFS_BASE__PRIVATE_IMPLEMENTATION__HPD__SHIFT__MAX_INCL); + shift -= WUFFS_BASE__PRIVATE_IMPLEMENTATION__HPD__SHIFT__MAX_INCL; + } + wuffs_base__private_implementation__high_prec_dec__small_lshift( + h, ((uint32_t)(+shift))); + } else if (shift < 0) { + while (shift < -WUFFS_BASE__PRIVATE_IMPLEMENTATION__HPD__SHIFT__MAX_INCL) { + wuffs_base__private_implementation__high_prec_dec__small_rshift( + h, WUFFS_BASE__PRIVATE_IMPLEMENTATION__HPD__SHIFT__MAX_INCL); + shift += WUFFS_BASE__PRIVATE_IMPLEMENTATION__HPD__SHIFT__MAX_INCL; + } + wuffs_base__private_implementation__high_prec_dec__small_rshift( + h, ((uint32_t)(-shift))); + } +} + +// -------- + +// wuffs_base__private_implementation__high_prec_dec__round_etc rounds h's +// number. For those functions that take an n argument, rounding produces at +// most n digits (which is not necessarily at most n decimal places). Negative +// n values are ignored, as well as any n greater than or equal to h's number +// of digits. The etc__round_just_enough function implicitly chooses an n to +// implement WUFFS_BASE__RENDER_NUMBER_FXX__JUST_ENOUGH_PRECISION. +// +// Preconditions: +// - h is non-NULL. +// - h->decimal_point is "not extreme". +// +// "Not extreme" means within +// ±WUFFS_BASE__PRIVATE_IMPLEMENTATION__HPD__DECIMAL_POINT__RANGE. + +static void // +wuffs_base__private_implementation__high_prec_dec__round_down( + wuffs_base__private_implementation__high_prec_dec* h, + int32_t n) { + if ((n < 0) || (h->num_digits <= (uint32_t)n)) { + return; + } + h->num_digits = (uint32_t)(n); + wuffs_base__private_implementation__high_prec_dec__trim(h); +} + +static void // +wuffs_base__private_implementation__high_prec_dec__round_up( + wuffs_base__private_implementation__high_prec_dec* h, + int32_t n) { + if ((n < 0) || (h->num_digits <= (uint32_t)n)) { + return; + } + + for (n--; n >= 0; n--) { + if (h->digits[n] < 9) { + h->digits[n]++; + h->num_digits = (uint32_t)(n + 1); + return; + } + } + + // The number is all 9s. Change to a single 1 and adjust the decimal point. + h->digits[0] = 1; + h->num_digits = 1; + h->decimal_point++; +} + +static void // +wuffs_base__private_implementation__high_prec_dec__round_nearest( + wuffs_base__private_implementation__high_prec_dec* h, + int32_t n) { + if ((n < 0) || (h->num_digits <= (uint32_t)n)) { + return; + } + bool up = h->digits[n] >= 5; + if ((h->digits[n] == 5) && ((n + 1) == ((int32_t)(h->num_digits)))) { + up = h->truncated || // + ((n > 0) && ((h->digits[n - 1] & 1) != 0)); + } + + if (up) { + wuffs_base__private_implementation__high_prec_dec__round_up(h, n); + } else { + wuffs_base__private_implementation__high_prec_dec__round_down(h, n); + } +} + +static void // +wuffs_base__private_implementation__high_prec_dec__round_just_enough( + wuffs_base__private_implementation__high_prec_dec* h, + int32_t exp2, + uint64_t mantissa) { + // The magic numbers 52 and 53 in this function are because IEEE 754 double + // precision has 52 mantissa bits. + // + // Let f be the floating point number represented by exp2 and mantissa (and + // also the number in h): the number (mantissa * (2 ** (exp2 - 52))). + // + // If f is zero or a small integer, we can return early. + if ((mantissa == 0) || + ((exp2 < 53) && (h->decimal_point >= ((int32_t)(h->num_digits))))) { + return; + } + + // The smallest normal f has an exp2 of -1022 and a mantissa of (1 << 52). + // Subnormal numbers have the same exp2 but a smaller mantissa. + static const int32_t min_incl_normal_exp2 = -1022; + static const uint64_t min_incl_normal_mantissa = 0x0010000000000000ul; + + // Compute lower and upper bounds such that any number between them (possibly + // inclusive) will round to f. First, the lower bound. Our number f is: + // ((mantissa + 0) * (2 ** ( exp2 - 52))) + // + // The next lowest floating point number is: + // ((mantissa - 1) * (2 ** ( exp2 - 52))) + // unless (mantissa - 1) drops the (1 << 52) bit and exp2 is not the + // min_incl_normal_exp2. Either way, call it: + // ((l_mantissa) * (2 ** (l_exp2 - 52))) + // + // The lower bound is halfway between them (noting that 52 became 53): + // (((2 * l_mantissa) + 1) * (2 ** (l_exp2 - 53))) + int32_t l_exp2 = exp2; + uint64_t l_mantissa = mantissa - 1; + if ((exp2 > min_incl_normal_exp2) && (mantissa <= min_incl_normal_mantissa)) { + l_exp2 = exp2 - 1; + l_mantissa = (2 * mantissa) - 1; + } + wuffs_base__private_implementation__high_prec_dec lower; + wuffs_base__private_implementation__high_prec_dec__assign( + &lower, (2 * l_mantissa) + 1, false); + wuffs_base__private_implementation__high_prec_dec__lshift(&lower, + l_exp2 - 53); + + // Next, the upper bound. Our number f is: + // ((mantissa + 0) * (2 ** (exp2 - 52))) + // + // The next highest floating point number is: + // ((mantissa + 1) * (2 ** (exp2 - 52))) + // + // The upper bound is halfway between them (noting that 52 became 53): + // (((2 * mantissa) + 1) * (2 ** (exp2 - 53))) + wuffs_base__private_implementation__high_prec_dec upper; + wuffs_base__private_implementation__high_prec_dec__assign( + &upper, (2 * mantissa) + 1, false); + wuffs_base__private_implementation__high_prec_dec__lshift(&upper, exp2 - 53); + + // The lower and upper bounds are possible outputs only if the original + // mantissa is even, so that IEEE round-to-even would round to the original + // mantissa and not its neighbors. + bool inclusive = (mantissa & 1) == 0; + + // As we walk the digits, we want to know whether rounding up would fall + // within the upper bound. This is tracked by upper_delta: + // - When -1, the digits of h and upper are the same so far. + // - When +0, we saw a difference of 1 between h and upper on a previous + // digit and subsequently only 9s for h and 0s for upper. Thus, rounding + // up may fall outside of the bound if !inclusive. + // - When +1, the difference is greater than 1 and we know that rounding up + // falls within the bound. + // + // This is a state machine with three states. The numerical value for each + // state (-1, +0 or +1) isn't important, other than their order. + int upper_delta = -1; + + // We can now figure out the shortest number of digits required. Walk the + // digits until h has distinguished itself from lower or upper. + // + // The zi and zd variables are indexes and digits, for z in l (lower), h (the + // number) and u (upper). + // + // The lower, h and upper numbers may have their decimal points at different + // places. In this case, upper is the longest, so we iterate ui starting from + // 0 and iterate li and hi starting from either 0 or -1. + int32_t ui = 0; + for (;; ui++) { + // Calculate hd, the middle number's digit. + int32_t hi = ui - upper.decimal_point + h->decimal_point; + if (hi >= ((int32_t)(h->num_digits))) { + break; + } + uint8_t hd = (((uint32_t)hi) < h->num_digits) ? h->digits[hi] : 0; + + // Calculate ld, the lower bound's digit. + int32_t li = ui - upper.decimal_point + lower.decimal_point; + uint8_t ld = (((uint32_t)li) < lower.num_digits) ? lower.digits[li] : 0; + + // We can round down (truncate) if lower has a different digit than h or if + // lower is inclusive and is exactly the result of rounding down (i.e. we + // have reached the final digit of lower). + bool can_round_down = + (ld != hd) || // + (inclusive && ((li + 1) == ((int32_t)(lower.num_digits)))); + + // Calculate ud, the upper bound's digit, and update upper_delta. + uint8_t ud = (((uint32_t)ui) < upper.num_digits) ? upper.digits[ui] : 0; + if (upper_delta < 0) { + if ((hd + 1) < ud) { + // For example: + // h = 12345??? + // upper = 12347??? + upper_delta = +1; + } else if (hd != ud) { + // For example: + // h = 12345??? + // upper = 12346??? + upper_delta = +0; + } + } else if (upper_delta == 0) { + if ((hd != 9) || (ud != 0)) { + // For example: + // h = 1234598? + // upper = 1234600? + upper_delta = +1; + } + } + + // We can round up if upper has a different digit than h and either upper + // is inclusive or upper is bigger than the result of rounding up. + bool can_round_up = + (upper_delta > 0) || // + ((upper_delta == 0) && // + (inclusive || ((ui + 1) < ((int32_t)(upper.num_digits))))); + + // If we can round either way, round to nearest. If we can round only one + // way, do it. If we can't round, continue the loop. + if (can_round_down) { + if (can_round_up) { + wuffs_base__private_implementation__high_prec_dec__round_nearest( + h, hi + 1); + return; + } else { + wuffs_base__private_implementation__high_prec_dec__round_down(h, + hi + 1); + return; + } + } else { + if (can_round_up) { + wuffs_base__private_implementation__high_prec_dec__round_up(h, hi + 1); + return; + } + } + } +} + +// -------- + +// wuffs_base__private_implementation__parse_number_f64_eisel_lemire produces +// the IEEE 754 double-precision value for an exact mantissa and base-10 +// exponent. For example: +// - when parsing "12345.678e+02", man is 12345678 and exp10 is -1. +// - when parsing "-12", man is 12 and exp10 is 0. Processing the leading +// minus sign is the responsibility of the caller, not this function. +// +// On success, it returns a non-negative int64_t such that the low 63 bits hold +// the 11-bit exponent and 52-bit mantissa. +// +// On failure, it returns a negative value. +// +// The algorithm is based on an original idea by Michael Eisel that was refined +// by Daniel Lemire. See +// https://lemire.me/blog/2020/03/10/fast-float-parsing-in-practice/ +// and +// https://nigeltao.github.io/blog/2020/eisel-lemire.html +// +// Preconditions: +// - man is non-zero. +// - exp10 is in the range [-307 ..= 288], the same range of the +// wuffs_base__private_implementation__powers_of_10 array. +// +// The exp10 range (and the fact that man is in the range [1 ..= UINT64_MAX], +// approximately [1 ..= 1.85e+19]) means that (man * (10 ** exp10)) is in the +// range [1e-307 ..= 1.85e+307]. This is entirely within the range of normal +// (neither subnormal nor non-finite) f64 values: DBL_MIN and DBL_MAX are +// approximately 2.23e–308 and 1.80e+308. +static int64_t // +wuffs_base__private_implementation__parse_number_f64_eisel_lemire( + uint64_t man, + int32_t exp10) { + // Look up the (possibly truncated) base-2 representation of (10 ** exp10). + // The look-up table was constructed so that it is already normalized: the + // table entry's mantissa's MSB (most significant bit) is on. + const uint64_t* po10 = + &wuffs_base__private_implementation__powers_of_10[exp10 + 307][0]; + + // Normalize the man argument. The (man != 0) precondition means that a + // non-zero bit exists. + uint32_t clz = wuffs_base__count_leading_zeroes_u64(man); + man <<= clz; + + // Calculate the return value's base-2 exponent. We might tweak it by ±1 + // later, but its initial value comes from a linear scaling of exp10, + // converting from power-of-10 to power-of-2, and adjusting by clz. + // + // The magic constants are: + // - 1087 = 1023 + 64. The 1023 is the f64 exponent bias. The 64 is because + // the look-up table uses 64-bit mantissas. + // - 217706 is such that the ratio 217706 / 65536 ≈ 3.321930 is close enough + // (over the practical range of exp10) to log(10) / log(2) ≈ 3.321928. + // - 65536 = 1<<16 is arbitrary but a power of 2, so division is a shift. + // + // Equality of the linearly-scaled value and the actual power-of-2, over the + // range of exp10 arguments that this function accepts, is confirmed by + // script/print-mpb-powers-of-10.go + uint64_t ret_exp2 = + ((uint64_t)(((217706 * exp10) >> 16) + 1087)) - ((uint64_t)clz); + + // Multiply the two mantissas. Normalization means that both mantissas are at + // least (1<<63), so the 128-bit product must be at least (1<<126). The high + // 64 bits of the product, x_hi, must therefore be at least (1<<62). + // + // As a consequence, x_hi has either 0 or 1 leading zeroes. Shifting x_hi + // right by either 9 or 10 bits (depending on x_hi's MSB) will therefore + // leave the top 10 MSBs (bits 54 ..= 63) off and the 11th MSB (bit 53) on. + wuffs_base__multiply_u64__output x = wuffs_base__multiply_u64(man, po10[1]); + uint64_t x_hi = x.hi; + uint64_t x_lo = x.lo; + + // Before we shift right by at least 9 bits, recall that the look-up table + // entry was possibly truncated. We have so far only calculated a lower bound + // for the product (man * e), where e is (10 ** exp10). The upper bound would + // add a further (man * 1) to the 128-bit product, which overflows the lower + // 64-bit limb if ((x_lo + man) < man). + // + // If overflow occurs, that adds 1 to x_hi. Since we're about to shift right + // by at least 9 bits, that carried 1 can be ignored unless the higher 64-bit + // limb's low 9 bits are all on. + // + // For example, parsing "9999999999999999999" will take the if-true branch + // here, since: + // - x_hi = 0x4563918244F3FFFF + // - x_lo = 0x8000000000000000 + // - man = 0x8AC7230489E7FFFF + if (((x_hi & 0x1FF) == 0x1FF) && ((x_lo + man) < man)) { + // Refine our calculation of (man * e). Before, our approximation of e used + // a "low resolution" 64-bit mantissa. Now use a "high resolution" 128-bit + // mantissa. We've already calculated x = (man * bits_0_to_63_incl_of_e). + // Now calculate y = (man * bits_64_to_127_incl_of_e). + wuffs_base__multiply_u64__output y = wuffs_base__multiply_u64(man, po10[0]); + uint64_t y_hi = y.hi; + uint64_t y_lo = y.lo; + + // Merge the 128-bit x and 128-bit y, which overlap by 64 bits, to + // calculate the 192-bit product of the 64-bit man by the 128-bit e. + // As we exit this if-block, we only care about the high 128 bits + // (merged_hi and merged_lo) of that 192-bit product. + // + // For example, parsing "1.234e-45" will take the if-true branch here, + // since: + // - x_hi = 0x70B7E3696DB29FFF + // - x_lo = 0xE040000000000000 + // - y_hi = 0x33718BBEAB0E0D7A + // - y_lo = 0xA880000000000000 + uint64_t merged_hi = x_hi; + uint64_t merged_lo = x_lo + y_hi; + if (merged_lo < x_lo) { + merged_hi++; // Carry the overflow bit. + } + + // The "high resolution" approximation of e is still a lower bound. Once + // again, see if the upper bound is large enough to produce a different + // result. This time, if it does, give up instead of reaching for an even + // more precise approximation to e. + // + // This three-part check is similar to the two-part check that guarded the + // if block that we're now in, but it has an extra term for the middle 64 + // bits (checking that adding 1 to merged_lo would overflow). + // + // For example, parsing "5.9604644775390625e-8" will take the if-true + // branch here, since: + // - merged_hi = 0x7FFFFFFFFFFFFFFF + // - merged_lo = 0xFFFFFFFFFFFFFFFF + // - y_lo = 0x4DB3FFC120988200 + // - man = 0xD3C21BCECCEDA100 + if (((merged_hi & 0x1FF) == 0x1FF) && ((merged_lo + 1) == 0) && + (y_lo + man < man)) { + return -1; + } + + // Replace the 128-bit x with merged. + x_hi = merged_hi; + x_lo = merged_lo; + } + + // As mentioned above, shifting x_hi right by either 9 or 10 bits will leave + // the top 10 MSBs (bits 54 ..= 63) off and the 11th MSB (bit 53) on. If the + // MSB (before shifting) was on, adjust ret_exp2 for the larger shift. + // + // Having bit 53 on (and higher bits off) means that ret_mantissa is a 54-bit + // number. + uint64_t msb = x_hi >> 63; + uint64_t ret_mantissa = x_hi >> (msb + 9); + ret_exp2 -= 1 ^ msb; + + // IEEE 754 rounds to-nearest with ties rounded to-even. Rounding to-even can + // be tricky. If we're half-way between two exactly representable numbers + // (x's low 73 bits are zero and the next 2 bits that matter are "01"), give + // up instead of trying to pick the winner. + // + // Technically, we could tighten the condition by changing "73" to "73 or 74, + // depending on msb", but a flat "73" is simpler. + // + // For example, parsing "1e+23" will take the if-true branch here, since: + // - x_hi = 0x54B40B1F852BDA00 + // - ret_mantissa = 0x002A5A058FC295ED + if ((x_lo == 0) && ((x_hi & 0x1FF) == 0) && ((ret_mantissa & 3) == 1)) { + return -1; + } + + // If we're not halfway then it's rounding to-nearest. Starting with a 54-bit + // number, carry the lowest bit (bit 0) up if it's on. Regardless of whether + // it was on or off, shifting right by one then produces a 53-bit number. If + // carrying up overflowed, shift again. + ret_mantissa += ret_mantissa & 1; + ret_mantissa >>= 1; + // This if block is equivalent to (but benchmarks slightly faster than) the + // following branchless form: + // uint64_t overflow_adjustment = ret_mantissa >> 53; + // ret_mantissa >>= overflow_adjustment; + // ret_exp2 += overflow_adjustment; + // + // For example, parsing "7.2057594037927933e+16" will take the if-true + // branch here, since: + // - x_hi = 0x7FFFFFFFFFFFFE80 + // - ret_mantissa = 0x0020000000000000 + if ((ret_mantissa >> 53) > 0) { + ret_mantissa >>= 1; + ret_exp2++; + } + + // Starting with a 53-bit number, IEEE 754 double-precision normal numbers + // have an implicit mantissa bit. Mask that away and keep the low 52 bits. + ret_mantissa &= 0x000FFFFFFFFFFFFF; + + // Pack the bits and return. + return ((int64_t)(ret_mantissa | (ret_exp2 << 52))); +} + +// -------- + +static wuffs_base__result_f64 // +wuffs_base__private_implementation__parse_number_f64_special( + wuffs_base__slice_u8 s, + uint32_t options) { + do { + if (options & WUFFS_BASE__PARSE_NUMBER_FXX__REJECT_INF_AND_NAN) { + goto fail; + } + + uint8_t* p = s.ptr; + uint8_t* q = s.ptr + s.len; + + for (; (p < q) && (*p == '_'); p++) { + } + if (p >= q) { + goto fail; + } + + // Parse sign. + bool negative = false; + do { + if (*p == '+') { + p++; + } else if (*p == '-') { + negative = true; + p++; + } else { + break; + } + for (; (p < q) && (*p == '_'); p++) { + } + } while (0); + if (p >= q) { + goto fail; + } + + bool nan = false; + switch (p[0]) { + case 'I': + case 'i': + if (((q - p) < 3) || // + ((p[1] != 'N') && (p[1] != 'n')) || // + ((p[2] != 'F') && (p[2] != 'f'))) { + goto fail; + } + p += 3; + + if ((p >= q) || (*p == '_')) { + break; + } else if (((q - p) < 5) || // + ((p[0] != 'I') && (p[0] != 'i')) || // + ((p[1] != 'N') && (p[1] != 'n')) || // + ((p[2] != 'I') && (p[2] != 'i')) || // + ((p[3] != 'T') && (p[3] != 't')) || // + ((p[4] != 'Y') && (p[4] != 'y'))) { + goto fail; + } + p += 5; + + if ((p >= q) || (*p == '_')) { + break; + } + goto fail; + + case 'N': + case 'n': + if (((q - p) < 3) || // + ((p[1] != 'A') && (p[1] != 'a')) || // + ((p[2] != 'N') && (p[2] != 'n'))) { + goto fail; + } + p += 3; + + if ((p >= q) || (*p == '_')) { + nan = true; + break; + } + goto fail; + + default: + goto fail; + } + + // Finish. + for (; (p < q) && (*p == '_'); p++) { + } + if (p != q) { + goto fail; + } + wuffs_base__result_f64 ret; + ret.status.repr = NULL; + ret.value = wuffs_base__ieee_754_bit_representation__from_u64_to_f64( + (nan ? 0x7FFFFFFFFFFFFFFF : 0x7FF0000000000000) | + (negative ? 0x8000000000000000 : 0)); + return ret; + } while (0); + +fail: + do { + wuffs_base__result_f64 ret; + ret.status.repr = wuffs_base__error__bad_argument; + ret.value = 0; + return ret; + } while (0); +} + +WUFFS_BASE__MAYBE_STATIC wuffs_base__result_f64 // +wuffs_base__private_implementation__high_prec_dec__to_f64( + wuffs_base__private_implementation__high_prec_dec* h, + uint32_t options) { + do { + // powers converts decimal powers of 10 to binary powers of 2. For example, + // (10000 >> 13) is 1. It stops before the elements exceed 60, also known + // as WUFFS_BASE__PRIVATE_IMPLEMENTATION__HPD__SHIFT__MAX_INCL. + // + // This rounds down (1<<13 is a lower bound for 1e4). Adding 1 to the array + // element value rounds up (1<<14 is an upper bound for 1e4) while staying + // at or below WUFFS_BASE__PRIVATE_IMPLEMENTATION__HPD__SHIFT__MAX_INCL. + // + // When starting in the range [1e+1 .. 1e+2] (i.e. h->decimal_point == +2), + // powers[2] == 6 and so: + // - Right shifting by 6+0 produces the range [10/64 .. 100/64] = + // [0.156250 .. 1.56250]. The resultant h->decimal_point is +0 or +1. + // - Right shifting by 6+1 produces the range [10/128 .. 100/128] = + // [0.078125 .. 0.78125]. The resultant h->decimal_point is -1 or -0. + // + // When starting in the range [1e-3 .. 1e-2] (i.e. h->decimal_point == -2), + // powers[2] == 6 and so: + // - Left shifting by 6+0 produces the range [0.001*64 .. 0.01*64] = + // [0.064 .. 0.64]. The resultant h->decimal_point is -1 or -0. + // - Left shifting by 6+1 produces the range [0.001*128 .. 0.01*128] = + // [0.128 .. 1.28]. The resultant h->decimal_point is +0 or +1. + // + // Thus, when targeting h->decimal_point being +0 or +1, use (powers[n]+0) + // when right shifting but (powers[n]+1) when left shifting. + static const uint32_t num_powers = 19; + static const uint8_t powers[19] = { + 0, 3, 6, 9, 13, 16, 19, 23, 26, 29, // + 33, 36, 39, 43, 46, 49, 53, 56, 59, // + }; + + // Handle zero and obvious extremes. The largest and smallest positive + // finite f64 values are approximately 1.8e+308 and 4.9e-324. + if ((h->num_digits == 0) || (h->decimal_point < -326)) { + goto zero; + } else if (h->decimal_point > 310) { + goto infinity; + } + + // Try the fast Eisel-Lemire algorithm again. Calculating the (man, exp10) + // pair from the high_prec_dec h is more correct but slower than the + // approach taken in wuffs_base__parse_number_f64. The latter is optimized + // for the common cases (e.g. assuming no underscores or a leading '+' + // sign) rather than the full set of cases allowed by the Wuffs API. + // + // When we have 19 or fewer mantissa digits, run Eisel-Lemire once (trying + // for an exact result). When we have more than 19 mantissa digits, run it + // twice to get a lower and upper bound. We still have an exact result + // (within f64's rounding margin) if both bounds are equal (and valid). + uint32_t i_max = h->num_digits; + if (i_max > 19) { + i_max = 19; + } + int32_t exp10 = h->decimal_point - ((int32_t)i_max); + if ((-307 <= exp10) && (exp10 <= 288)) { + uint64_t man = 0; + uint32_t i; + for (i = 0; i < i_max; i++) { + man = (10 * man) + h->digits[i]; + } + while (man != 0) { // The 'while' is just an 'if' that we can 'break'. + int64_t r0 = + wuffs_base__private_implementation__parse_number_f64_eisel_lemire( + man + 0, exp10); + if (r0 < 0) { + break; + } else if (h->num_digits > 19) { + int64_t r1 = + wuffs_base__private_implementation__parse_number_f64_eisel_lemire( + man + 1, exp10); + if (r1 != r0) { + break; + } + } + wuffs_base__result_f64 ret; + ret.status.repr = NULL; + ret.value = wuffs_base__ieee_754_bit_representation__from_u64_to_f64( + ((uint64_t)r0) | (((uint64_t)(h->negative)) << 63)); + return ret; + } + } + + // When Eisel-Lemire fails, fall back to Simple Decimal Conversion. See + // https://nigeltao.github.io/blog/2020/parse-number-f64-simple.html + // + // Scale by powers of 2 until we're in the range [0.1 .. 10]. Equivalently, + // that h->decimal_point is +0 or +1. + // + // First we shift right while at or above 10... + const int32_t f64_bias = -1023; + int32_t exp2 = 0; + while (h->decimal_point > 1) { + uint32_t n = (uint32_t)(+h->decimal_point); + uint32_t shift = + (n < num_powers) + ? powers[n] + : WUFFS_BASE__PRIVATE_IMPLEMENTATION__HPD__SHIFT__MAX_INCL; + + wuffs_base__private_implementation__high_prec_dec__small_rshift(h, shift); + if (h->decimal_point < + -WUFFS_BASE__PRIVATE_IMPLEMENTATION__HPD__DECIMAL_POINT__RANGE) { + goto zero; + } + exp2 += (int32_t)shift; + } + // ...then we shift left while below 0.1. + while (h->decimal_point < 0) { + uint32_t shift; + uint32_t n = (uint32_t)(-h->decimal_point); + shift = (n < num_powers) + // The +1 is per "when targeting h->decimal_point being +0 or + // +1... when left shifting" in the powers comment above. + ? (powers[n] + 1) + : WUFFS_BASE__PRIVATE_IMPLEMENTATION__HPD__SHIFT__MAX_INCL; + + wuffs_base__private_implementation__high_prec_dec__small_lshift(h, shift); + if (h->decimal_point > + +WUFFS_BASE__PRIVATE_IMPLEMENTATION__HPD__DECIMAL_POINT__RANGE) { + goto infinity; + } + exp2 -= (int32_t)shift; + } + + // To get from "in the range [0.1 .. 10]" to "in the range [1 .. 2]" (which + // will give us our exponent in base-2), the mantissa's first 3 digits will + // determine the final left shift, equal to 52 (the number of explicit f64 + // bits) plus an additional adjustment. + int man3 = (100 * h->digits[0]) + + ((h->num_digits > 1) ? (10 * h->digits[1]) : 0) + + ((h->num_digits > 2) ? h->digits[2] : 0); + int32_t additional_lshift = 0; + if (h->decimal_point == 0) { // The value is in [0.1 .. 1]. + if (man3 < 125) { + additional_lshift = +4; + } else if (man3 < 250) { + additional_lshift = +3; + } else if (man3 < 500) { + additional_lshift = +2; + } else { + additional_lshift = +1; + } + } else { // The value is in [1 .. 10]. + if (man3 < 200) { + additional_lshift = -0; + } else if (man3 < 400) { + additional_lshift = -1; + } else if (man3 < 800) { + additional_lshift = -2; + } else { + additional_lshift = -3; + } + } + exp2 -= additional_lshift; + uint32_t final_lshift = (uint32_t)(52 + additional_lshift); + + // The minimum normal exponent is (f64_bias + 1). + while ((f64_bias + 1) > exp2) { + uint32_t n = (uint32_t)((f64_bias + 1) - exp2); + if (n > WUFFS_BASE__PRIVATE_IMPLEMENTATION__HPD__SHIFT__MAX_INCL) { + n = WUFFS_BASE__PRIVATE_IMPLEMENTATION__HPD__SHIFT__MAX_INCL; + } + wuffs_base__private_implementation__high_prec_dec__small_rshift(h, n); + exp2 += (int32_t)n; + } + + // Check for overflow. + if ((exp2 - f64_bias) >= 0x07FF) { // (1 << 11) - 1. + goto infinity; + } + + // Extract 53 bits for the mantissa (in base-2). + wuffs_base__private_implementation__high_prec_dec__small_lshift( + h, final_lshift); + uint64_t man2 = + wuffs_base__private_implementation__high_prec_dec__rounded_integer(h); + + // Rounding might have added one bit. If so, shift and re-check overflow. + if ((man2 >> 53) != 0) { + man2 >>= 1; + exp2++; + if ((exp2 - f64_bias) >= 0x07FF) { // (1 << 11) - 1. + goto infinity; + } + } + + // Handle subnormal numbers. + if ((man2 >> 52) == 0) { + exp2 = f64_bias; + } + + // Pack the bits and return. + uint64_t exp2_bits = + (uint64_t)((exp2 - f64_bias) & 0x07FF); // (1 << 11) - 1. + uint64_t bits = (man2 & 0x000FFFFFFFFFFFFF) | // (1 << 52) - 1. + (exp2_bits << 52) | // + (h->negative ? 0x8000000000000000 : 0); // (1 << 63). + + wuffs_base__result_f64 ret; + ret.status.repr = NULL; + ret.value = wuffs_base__ieee_754_bit_representation__from_u64_to_f64(bits); + return ret; + } while (0); + +zero: + do { + uint64_t bits = h->negative ? 0x8000000000000000 : 0; + + wuffs_base__result_f64 ret; + ret.status.repr = NULL; + ret.value = wuffs_base__ieee_754_bit_representation__from_u64_to_f64(bits); + return ret; + } while (0); + +infinity: + do { + if (options & WUFFS_BASE__PARSE_NUMBER_FXX__REJECT_INF_AND_NAN) { + wuffs_base__result_f64 ret; + ret.status.repr = wuffs_base__error__bad_argument; + ret.value = 0; + return ret; + } + + uint64_t bits = h->negative ? 0xFFF0000000000000 : 0x7FF0000000000000; + + wuffs_base__result_f64 ret; + ret.status.repr = NULL; + ret.value = wuffs_base__ieee_754_bit_representation__from_u64_to_f64(bits); + return ret; + } while (0); +} + +static inline bool // +wuffs_base__private_implementation__is_decimal_digit(uint8_t c) { + return ('0' <= c) && (c <= '9'); +} + +WUFFS_BASE__MAYBE_STATIC wuffs_base__result_f64 // +wuffs_base__parse_number_f64(wuffs_base__slice_u8 s, uint32_t options) { + // In practice, almost all "dd.ddddE±xxx" numbers can be represented + // losslessly by a uint64_t mantissa "dddddd" and an int32_t base-10 + // exponent, adjusting "xxx" for the position (if present) of the decimal + // separator '.' or ','. + // + // This (u64 man, i32 exp10) data structure is superficially similar to the + // "Do It Yourself Floating Point" type from Loitsch (†), but the exponent + // here is base-10, not base-2. + // + // If s's number fits in a (man, exp10), parse that pair with the + // Eisel-Lemire algorithm. If not, or if Eisel-Lemire fails, parsing s with + // the fallback algorithm is slower but comprehensive. + // + // † "Printing Floating-Point Numbers Quickly and Accurately with Integers" + // (https://www.cs.tufts.edu/~nr/cs257/archive/florian-loitsch/printf.pdf). + // Florian Loitsch is also the primary contributor to + // https://github.com/google/double-conversion + do { + // Calculating that (man, exp10) pair needs to stay within s's bounds. + // Provided that s isn't extremely long, work on a NUL-terminated copy of + // s's contents. The NUL byte isn't a valid part of "±dd.ddddE±xxx". + // + // As the pointer p walks the contents, it's faster to repeatedly check "is + // *p a valid digit" than "is p within bounds and *p a valid digit". + if (s.len >= 256) { + goto fallback; + } + uint8_t z[256]; + memcpy(&z[0], s.ptr, s.len); + z[s.len] = 0; + const uint8_t* p = &z[0]; + + // Look for a leading minus sign. Technically, we could also look for an + // optional plus sign, but the "script/process-json-numbers.c with -p" + // benchmark is noticably slower if we do. It's optional and, in practice, + // usually absent. Let the fallback catch it. + bool negative = (*p == '-'); + if (negative) { + p++; + } + + // After walking "dd.dddd", comparing p later with p now will produce the + // number of "d"s and "."s. + const uint8_t* const start_of_digits_ptr = p; + + // Walk the "d"s before a '.', 'E', NUL byte, etc. If it starts with '0', + // it must be a single '0'. If it starts with a non-zero decimal digit, it + // can be a sequence of decimal digits. + // + // Update the man variable during the walk. It's OK if man overflows now. + // We'll detect that later. + uint64_t man; + if (*p == '0') { + man = 0; + p++; + if (wuffs_base__private_implementation__is_decimal_digit(*p)) { + goto fallback; + } + } else if (wuffs_base__private_implementation__is_decimal_digit(*p)) { + man = ((uint8_t)(*p - '0')); + p++; + for (; wuffs_base__private_implementation__is_decimal_digit(*p); p++) { + man = (10 * man) + ((uint8_t)(*p - '0')); + } + } else { + goto fallback; + } + + // Walk the "d"s after the optional decimal separator ('.' or ','), + // updating the man and exp10 variables. + int32_t exp10 = 0; + if (*p == + ((options & WUFFS_BASE__PARSE_NUMBER_FXX__DECIMAL_SEPARATOR_IS_A_COMMA) + ? ',' + : '.')) { + p++; + const uint8_t* first_after_separator_ptr = p; + if (!wuffs_base__private_implementation__is_decimal_digit(*p)) { + goto fallback; + } + man = (10 * man) + ((uint8_t)(*p - '0')); + p++; + for (; wuffs_base__private_implementation__is_decimal_digit(*p); p++) { + man = (10 * man) + ((uint8_t)(*p - '0')); + } + exp10 = ((int32_t)(first_after_separator_ptr - p)); + } + + // Count the number of digits: + // - for an input of "314159", digit_count is 6. + // - for an input of "3.14159", digit_count is 7. + // + // This is off-by-one if there is a decimal separator. That's OK for now. + // We'll correct for that later. The "script/process-json-numbers.c with + // -p" benchmark is noticably slower if we try to correct for that now. + uint32_t digit_count = (uint32_t)(p - start_of_digits_ptr); + + // Update exp10 for the optional exponent, starting with 'E' or 'e'. + if ((*p | 0x20) == 'e') { + p++; + int32_t exp_sign = +1; + if (*p == '-') { + p++; + exp_sign = -1; + } else if (*p == '+') { + p++; + } + if (!wuffs_base__private_implementation__is_decimal_digit(*p)) { + goto fallback; + } + int32_t exp_num = ((uint8_t)(*p - '0')); + p++; + // The rest of the exp_num walking has a peculiar control flow but, once + // again, the "script/process-json-numbers.c with -p" benchmark is + // sensitive to alternative formulations. + if (wuffs_base__private_implementation__is_decimal_digit(*p)) { + exp_num = (10 * exp_num) + ((uint8_t)(*p - '0')); + p++; + } + if (wuffs_base__private_implementation__is_decimal_digit(*p)) { + exp_num = (10 * exp_num) + ((uint8_t)(*p - '0')); + p++; + } + while (wuffs_base__private_implementation__is_decimal_digit(*p)) { + if (exp_num > 0x1000000) { + goto fallback; + } + exp_num = (10 * exp_num) + ((uint8_t)(*p - '0')); + p++; + } + exp10 += exp_sign * exp_num; + } + + // The Wuffs API is that the original slice has no trailing data. It also + // allows underscores, which we don't catch here but the fallback should. + if (p != &z[s.len]) { + goto fallback; + } + + // Check that the uint64_t typed man variable has not overflowed, based on + // digit_count. + // + // For reference: + // - (1 << 63) is 9223372036854775808, which has 19 decimal digits. + // - (1 << 64) is 18446744073709551616, which has 20 decimal digits. + // - 19 nines, 9999999999999999999, is 0x8AC7230489E7FFFF, which has 64 + // bits and 16 hexadecimal digits. + // - 20 nines, 99999999999999999999, is 0x56BC75E2D630FFFFF, which has 67 + // bits and 17 hexadecimal digits. + if (digit_count > 19) { + // Even if we have more than 19 pseudo-digits, it's not yet definitely an + // overflow. Recall that digit_count might be off-by-one (too large) if + // there's a decimal separator. It will also over-report the number of + // meaningful digits if the input looks something like "0.000dddExxx". + // + // We adjust by the number of leading '0's and '.'s and re-compare to 19. + // Once again, technically, we could skip ','s too, but that perturbs the + // "script/process-json-numbers.c with -p" benchmark. + const uint8_t* q = start_of_digits_ptr; + for (; (*q == '0') || (*q == '.'); q++) { + } + digit_count -= (uint32_t)(q - start_of_digits_ptr); + if (digit_count > 19) { + goto fallback; + } + } + + // The wuffs_base__private_implementation__parse_number_f64_eisel_lemire + // preconditions include that exp10 is in the range [-307 ..= 288]. + if ((exp10 < -307) || (288 < exp10)) { + goto fallback; + } + + // If both man and (10 ** exp10) are exactly representable by a double, we + // don't need to run the Eisel-Lemire algorithm. + if ((-22 <= exp10) && (exp10 <= 22) && ((man >> 53) == 0)) { + double d = (double)man; + if (exp10 >= 0) { + d *= wuffs_base__private_implementation__f64_powers_of_10[+exp10]; + } else { + d /= wuffs_base__private_implementation__f64_powers_of_10[-exp10]; + } + wuffs_base__result_f64 ret; + ret.status.repr = NULL; + ret.value = negative ? -d : +d; + return ret; + } + + // The wuffs_base__private_implementation__parse_number_f64_eisel_lemire + // preconditions include that man is non-zero. Parsing "0" should be caught + // by the "If both man and (10 ** exp10)" above, but "0e99" might not. + if (man == 0) { + goto fallback; + } + + // Our man and exp10 are in range. Run the Eisel-Lemire algorithm. + int64_t r = + wuffs_base__private_implementation__parse_number_f64_eisel_lemire( + man, exp10); + if (r < 0) { + goto fallback; + } + wuffs_base__result_f64 ret; + ret.status.repr = NULL; + ret.value = wuffs_base__ieee_754_bit_representation__from_u64_to_f64( + ((uint64_t)r) | (((uint64_t)negative) << 63)); + return ret; + } while (0); + +fallback: + do { + wuffs_base__private_implementation__high_prec_dec h; + wuffs_base__status status = + wuffs_base__private_implementation__high_prec_dec__parse(&h, s, + options); + if (status.repr) { + return wuffs_base__private_implementation__parse_number_f64_special( + s, options); + } + return wuffs_base__private_implementation__high_prec_dec__to_f64(&h, + options); + } while (0); +} + +// -------- + +static inline size_t // +wuffs_base__private_implementation__render_inf(wuffs_base__slice_u8 dst, + bool neg, + uint32_t options) { + if (neg) { + if (dst.len < 4) { + return 0; + } + wuffs_base__poke_u32le__no_bounds_check(dst.ptr, 0x666E492D); // '-Inf'le. + return 4; + } + + if (options & WUFFS_BASE__RENDER_NUMBER_XXX__LEADING_PLUS_SIGN) { + if (dst.len < 4) { + return 0; + } + wuffs_base__poke_u32le__no_bounds_check(dst.ptr, 0x666E492B); // '+Inf'le. + return 4; + } + + if (dst.len < 3) { + return 0; + } + wuffs_base__poke_u24le__no_bounds_check(dst.ptr, 0x666E49); // 'Inf'le. + return 3; +} + +static inline size_t // +wuffs_base__private_implementation__render_nan(wuffs_base__slice_u8 dst) { + if (dst.len < 3) { + return 0; + } + wuffs_base__poke_u24le__no_bounds_check(dst.ptr, 0x4E614E); // 'NaN'le. + return 3; +} + +static size_t // +wuffs_base__private_implementation__high_prec_dec__render_exponent_absent( + wuffs_base__slice_u8 dst, + wuffs_base__private_implementation__high_prec_dec* h, + uint32_t precision, + uint32_t options) { + size_t n = (h->negative || + (options & WUFFS_BASE__RENDER_NUMBER_XXX__LEADING_PLUS_SIGN)) + ? 1 + : 0; + if (h->decimal_point <= 0) { + n += 1; + } else { + n += (size_t)(h->decimal_point); + } + if (precision > 0) { + n += precision + 1; // +1 for the '.'. + } + + // Don't modify dst if the formatted number won't fit. + if (n > dst.len) { + return 0; + } + + // Align-left or align-right. + uint8_t* ptr = (options & WUFFS_BASE__RENDER_NUMBER_XXX__ALIGN_RIGHT) + ? &dst.ptr[dst.len - n] + : &dst.ptr[0]; + + // Leading "±". + if (h->negative) { + *ptr++ = '-'; + } else if (options & WUFFS_BASE__RENDER_NUMBER_XXX__LEADING_PLUS_SIGN) { + *ptr++ = '+'; + } + + // Integral digits. + if (h->decimal_point <= 0) { + *ptr++ = '0'; + } else { + uint32_t m = + wuffs_base__u32__min(h->num_digits, (uint32_t)(h->decimal_point)); + uint32_t i = 0; + for (; i < m; i++) { + *ptr++ = (uint8_t)('0' | h->digits[i]); + } + for (; i < (uint32_t)(h->decimal_point); i++) { + *ptr++ = '0'; + } + } + + // Separator and then fractional digits. + if (precision > 0) { + *ptr++ = + (options & WUFFS_BASE__RENDER_NUMBER_FXX__DECIMAL_SEPARATOR_IS_A_COMMA) + ? ',' + : '.'; + uint32_t i = 0; + for (; i < precision; i++) { + uint32_t j = ((uint32_t)(h->decimal_point)) + i; + *ptr++ = (uint8_t)('0' | ((j < h->num_digits) ? h->digits[j] : 0)); + } + } + + return n; +} + +static size_t // +wuffs_base__private_implementation__high_prec_dec__render_exponent_present( + wuffs_base__slice_u8 dst, + wuffs_base__private_implementation__high_prec_dec* h, + uint32_t precision, + uint32_t options) { + int32_t exp = 0; + if (h->num_digits > 0) { + exp = h->decimal_point - 1; + } + bool negative_exp = exp < 0; + if (negative_exp) { + exp = -exp; + } + + size_t n = (h->negative || + (options & WUFFS_BASE__RENDER_NUMBER_XXX__LEADING_PLUS_SIGN)) + ? 4 + : 3; // Mininum 3 bytes: first digit and then "e±". + if (precision > 0) { + n += precision + 1; // +1 for the '.'. + } + n += (exp < 100) ? 2 : 3; + + // Don't modify dst if the formatted number won't fit. + if (n > dst.len) { + return 0; + } + + // Align-left or align-right. + uint8_t* ptr = (options & WUFFS_BASE__RENDER_NUMBER_XXX__ALIGN_RIGHT) + ? &dst.ptr[dst.len - n] + : &dst.ptr[0]; + + // Leading "±". + if (h->negative) { + *ptr++ = '-'; + } else if (options & WUFFS_BASE__RENDER_NUMBER_XXX__LEADING_PLUS_SIGN) { + *ptr++ = '+'; + } + + // Integral digit. + if (h->num_digits > 0) { + *ptr++ = (uint8_t)('0' | h->digits[0]); + } else { + *ptr++ = '0'; + } + + // Separator and then fractional digits. + if (precision > 0) { + *ptr++ = + (options & WUFFS_BASE__RENDER_NUMBER_FXX__DECIMAL_SEPARATOR_IS_A_COMMA) + ? ',' + : '.'; + uint32_t i = 1; + uint32_t j = wuffs_base__u32__min(h->num_digits, precision + 1); + for (; i < j; i++) { + *ptr++ = (uint8_t)('0' | h->digits[i]); + } + for (; i <= precision; i++) { + *ptr++ = '0'; + } + } + + // Exponent: "e±" and then 2 or 3 digits. + *ptr++ = 'e'; + *ptr++ = negative_exp ? '-' : '+'; + if (exp < 10) { + *ptr++ = '0'; + *ptr++ = (uint8_t)('0' | exp); + } else if (exp < 100) { + *ptr++ = (uint8_t)('0' | (exp / 10)); + *ptr++ = (uint8_t)('0' | (exp % 10)); + } else { + int32_t e = exp / 100; + exp -= e * 100; + *ptr++ = (uint8_t)('0' | e); + *ptr++ = (uint8_t)('0' | (exp / 10)); + *ptr++ = (uint8_t)('0' | (exp % 10)); + } + + return n; +} + +WUFFS_BASE__MAYBE_STATIC size_t // +wuffs_base__render_number_f64(wuffs_base__slice_u8 dst, + double x, + uint32_t precision, + uint32_t options) { + // Decompose x (64 bits) into negativity (1 bit), base-2 exponent (11 bits + // with a -1023 bias) and mantissa (52 bits). + uint64_t bits = wuffs_base__ieee_754_bit_representation__from_f64_to_u64(x); + bool neg = (bits >> 63) != 0; + int32_t exp2 = ((int32_t)(bits >> 52)) & 0x7FF; + uint64_t man = bits & 0x000FFFFFFFFFFFFFul; + + // Apply the exponent bias and set the implicit top bit of the mantissa, + // unless x is subnormal. Also take care of Inf and NaN. + if (exp2 == 0x7FF) { + if (man != 0) { + return wuffs_base__private_implementation__render_nan(dst); + } + return wuffs_base__private_implementation__render_inf(dst, neg, options); + } else if (exp2 == 0) { + exp2 = -1022; + } else { + exp2 -= 1023; + man |= 0x0010000000000000ul; + } + + // Ensure that precision isn't too large. + if (precision > 4095) { + precision = 4095; + } + + // Convert from the (neg, exp2, man) tuple to an HPD. + wuffs_base__private_implementation__high_prec_dec h; + wuffs_base__private_implementation__high_prec_dec__assign(&h, man, neg); + if (h.num_digits > 0) { + wuffs_base__private_implementation__high_prec_dec__lshift( + &h, exp2 - 52); // 52 mantissa bits. + } + + // Handle the "%e" and "%f" formats. + switch (options & (WUFFS_BASE__RENDER_NUMBER_FXX__EXPONENT_ABSENT | + WUFFS_BASE__RENDER_NUMBER_FXX__EXPONENT_PRESENT)) { + case WUFFS_BASE__RENDER_NUMBER_FXX__EXPONENT_ABSENT: // The "%"f" format. + if (options & WUFFS_BASE__RENDER_NUMBER_FXX__JUST_ENOUGH_PRECISION) { + wuffs_base__private_implementation__high_prec_dec__round_just_enough( + &h, exp2, man); + int32_t p = ((int32_t)(h.num_digits)) - h.decimal_point; + precision = ((uint32_t)(wuffs_base__i32__max(0, p))); + } else { + wuffs_base__private_implementation__high_prec_dec__round_nearest( + &h, ((int32_t)precision) + h.decimal_point); + } + return wuffs_base__private_implementation__high_prec_dec__render_exponent_absent( + dst, &h, precision, options); + + case WUFFS_BASE__RENDER_NUMBER_FXX__EXPONENT_PRESENT: // The "%e" format. + if (options & WUFFS_BASE__RENDER_NUMBER_FXX__JUST_ENOUGH_PRECISION) { + wuffs_base__private_implementation__high_prec_dec__round_just_enough( + &h, exp2, man); + precision = (h.num_digits > 0) ? (h.num_digits - 1) : 0; + } else { + wuffs_base__private_implementation__high_prec_dec__round_nearest( + &h, ((int32_t)precision) + 1); + } + return wuffs_base__private_implementation__high_prec_dec__render_exponent_present( + dst, &h, precision, options); + } + + // We have the "%g" format and so precision means the number of significant + // digits, not the number of digits after the decimal separator. Perform + // rounding and determine whether to use "%e" or "%f". + int32_t e_threshold = 0; + if (options & WUFFS_BASE__RENDER_NUMBER_FXX__JUST_ENOUGH_PRECISION) { + wuffs_base__private_implementation__high_prec_dec__round_just_enough( + &h, exp2, man); + precision = h.num_digits; + e_threshold = 6; + } else { + if (precision == 0) { + precision = 1; + } + wuffs_base__private_implementation__high_prec_dec__round_nearest( + &h, ((int32_t)precision)); + e_threshold = ((int32_t)precision); + int32_t nd = ((int32_t)(h.num_digits)); + if ((e_threshold > nd) && (nd >= h.decimal_point)) { + e_threshold = nd; + } + } + + // Use the "%e" format if the exponent is large. + int32_t e = h.decimal_point - 1; + if ((e < -4) || (e_threshold <= e)) { + uint32_t p = wuffs_base__u32__min(precision, h.num_digits); + return wuffs_base__private_implementation__high_prec_dec__render_exponent_present( + dst, &h, (p > 0) ? (p - 1) : 0, options); + } + + // Use the "%f" format otherwise. + int32_t p = ((int32_t)precision); + if (p > h.decimal_point) { + p = ((int32_t)(h.num_digits)); + } + precision = ((uint32_t)(wuffs_base__i32__max(0, p - h.decimal_point))); + return wuffs_base__private_implementation__high_prec_dec__render_exponent_absent( + dst, &h, precision, options); +} + +#endif // !defined(WUFFS_CONFIG__MODULES) || + // defined(WUFFS_CONFIG__MODULE__BASE) || + // defined(WUFFS_CONFIG__MODULE__BASE__FLOATCONV) + +#if !defined(WUFFS_CONFIG__MODULES) || defined(WUFFS_CONFIG__MODULE__BASE) || \ + defined(WUFFS_CONFIG__MODULE__BASE__INTCONV) + +// ---------------- Integer + +// wuffs_base__parse_number__foo_digits entries are 0x00 for invalid digits, +// and (0x80 | v) for valid digits, where v is the 4 bit value. + +static const uint8_t wuffs_base__parse_number__decimal_digits[256] = { + // 0 1 2 3 4 5 6 7 + // 8 9 A B C D E F + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x00 ..= 0x07. + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x08 ..= 0x0F. + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x10 ..= 0x17. + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x18 ..= 0x1F. + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x20 ..= 0x27. + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x28 ..= 0x2F. + 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, // 0x30 ..= 0x37. '0'-'7'. + 0x88, 0x89, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x38 ..= 0x3F. '8'-'9'. + + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x40 ..= 0x47. + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x48 ..= 0x4F. + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x50 ..= 0x57. + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x58 ..= 0x5F. + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x60 ..= 0x67. + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x68 ..= 0x6F. + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x70 ..= 0x77. + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x78 ..= 0x7F. + + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x80 ..= 0x87. + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x88 ..= 0x8F. + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x90 ..= 0x97. + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x98 ..= 0x9F. + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0xA0 ..= 0xA7. + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0xA8 ..= 0xAF. + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0xB0 ..= 0xB7. + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0xB8 ..= 0xBF. + + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0xC0 ..= 0xC7. + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0xC8 ..= 0xCF. + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0xD0 ..= 0xD7. + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0xD8 ..= 0xDF. + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0xE0 ..= 0xE7. + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0xE8 ..= 0xEF. + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0xF0 ..= 0xF7. + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0xF8 ..= 0xFF. + // 0 1 2 3 4 5 6 7 + // 8 9 A B C D E F +}; + +static const uint8_t wuffs_base__parse_number__hexadecimal_digits[256] = { + // 0 1 2 3 4 5 6 7 + // 8 9 A B C D E F + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x00 ..= 0x07. + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x08 ..= 0x0F. + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x10 ..= 0x17. + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x18 ..= 0x1F. + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x20 ..= 0x27. + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x28 ..= 0x2F. + 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, // 0x30 ..= 0x37. '0'-'7'. + 0x88, 0x89, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x38 ..= 0x3F. '8'-'9'. + + 0x00, 0x8A, 0x8B, 0x8C, 0x8D, 0x8E, 0x8F, 0x00, // 0x40 ..= 0x47. 'A'-'F'. + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x48 ..= 0x4F. + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x50 ..= 0x57. + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x58 ..= 0x5F. + 0x00, 0x8A, 0x8B, 0x8C, 0x8D, 0x8E, 0x8F, 0x00, // 0x60 ..= 0x67. 'a'-'f'. + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x68 ..= 0x6F. + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x70 ..= 0x77. + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x78 ..= 0x7F. + + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x80 ..= 0x87. + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x88 ..= 0x8F. + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x90 ..= 0x97. + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x98 ..= 0x9F. + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0xA0 ..= 0xA7. + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0xA8 ..= 0xAF. + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0xB0 ..= 0xB7. + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0xB8 ..= 0xBF. + + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0xC0 ..= 0xC7. + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0xC8 ..= 0xCF. + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0xD0 ..= 0xD7. + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0xD8 ..= 0xDF. + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0xE0 ..= 0xE7. + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0xE8 ..= 0xEF. + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0xF0 ..= 0xF7. + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0xF8 ..= 0xFF. + // 0 1 2 3 4 5 6 7 + // 8 9 A B C D E F +}; + +static const uint8_t wuffs_base__private_implementation__encode_base16[16] = { + 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, // 0x00 ..= 0x07. + 0x38, 0x39, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, // 0x08 ..= 0x0F. +}; + +// -------- + +WUFFS_BASE__MAYBE_STATIC wuffs_base__result_i64 // +wuffs_base__parse_number_i64(wuffs_base__slice_u8 s, uint32_t options) { + uint8_t* p = s.ptr; + uint8_t* q = s.ptr + s.len; + + if (options & WUFFS_BASE__PARSE_NUMBER_XXX__ALLOW_UNDERSCORES) { + for (; (p < q) && (*p == '_'); p++) { + } + } + + bool negative = false; + if (p >= q) { + goto fail_bad_argument; + } else if (*p == '-') { + p++; + negative = true; + } else if (*p == '+') { + p++; + } + + do { + wuffs_base__result_u64 r = wuffs_base__parse_number_u64( + wuffs_base__make_slice_u8(p, (size_t)(q - p)), options); + if (r.status.repr != NULL) { + wuffs_base__result_i64 ret; + ret.status.repr = r.status.repr; + ret.value = 0; + return ret; + } else if (negative) { + if (r.value < 0x8000000000000000) { + wuffs_base__result_i64 ret; + ret.status.repr = NULL; + ret.value = -(int64_t)(r.value); + return ret; + } else if (r.value == 0x8000000000000000) { + wuffs_base__result_i64 ret; + ret.status.repr = NULL; + ret.value = INT64_MIN; + return ret; + } + goto fail_out_of_bounds; + } else if (r.value > 0x7FFFFFFFFFFFFFFF) { + goto fail_out_of_bounds; + } else { + wuffs_base__result_i64 ret; + ret.status.repr = NULL; + ret.value = +(int64_t)(r.value); + return ret; + } + } while (0); + +fail_bad_argument: + do { + wuffs_base__result_i64 ret; + ret.status.repr = wuffs_base__error__bad_argument; + ret.value = 0; + return ret; + } while (0); + +fail_out_of_bounds: + do { + wuffs_base__result_i64 ret; + ret.status.repr = wuffs_base__error__out_of_bounds; + ret.value = 0; + return ret; + } while (0); +} + +WUFFS_BASE__MAYBE_STATIC wuffs_base__result_u64 // +wuffs_base__parse_number_u64(wuffs_base__slice_u8 s, uint32_t options) { + uint8_t* p = s.ptr; + uint8_t* q = s.ptr + s.len; + + if (options & WUFFS_BASE__PARSE_NUMBER_XXX__ALLOW_UNDERSCORES) { + for (; (p < q) && (*p == '_'); p++) { + } + } + + if (p >= q) { + goto fail_bad_argument; + + } else if (*p == '0') { + p++; + if (p >= q) { + goto ok_zero; + } + if (options & WUFFS_BASE__PARSE_NUMBER_XXX__ALLOW_UNDERSCORES) { + if (*p == '_') { + p++; + for (; p < q; p++) { + if (*p != '_') { + if (options & + WUFFS_BASE__PARSE_NUMBER_XXX__ALLOW_MULTIPLE_LEADING_ZEROES) { + goto decimal; + } + goto fail_bad_argument; + } + } + goto ok_zero; + } + } + + if ((*p == 'x') || (*p == 'X')) { + p++; + if (options & WUFFS_BASE__PARSE_NUMBER_XXX__ALLOW_UNDERSCORES) { + for (; (p < q) && (*p == '_'); p++) { + } + } + if (p < q) { + goto hexadecimal; + } + + } else if ((*p == 'd') || (*p == 'D')) { + p++; + if (options & WUFFS_BASE__PARSE_NUMBER_XXX__ALLOW_UNDERSCORES) { + for (; (p < q) && (*p == '_'); p++) { + } + } + if (p < q) { + goto decimal; + } + } + + if (options & WUFFS_BASE__PARSE_NUMBER_XXX__ALLOW_MULTIPLE_LEADING_ZEROES) { + goto decimal; + } + goto fail_bad_argument; + } + +decimal: + do { + uint64_t v = wuffs_base__parse_number__decimal_digits[*p++]; + if (v == 0) { + goto fail_bad_argument; + } + v &= 0x0F; + + // UINT64_MAX is 18446744073709551615, which is ((10 * max10) + max1). + const uint64_t max10 = 1844674407370955161u; + const uint8_t max1 = 5; + + for (; p < q; p++) { + if ((*p == '_') && + (options & WUFFS_BASE__PARSE_NUMBER_XXX__ALLOW_UNDERSCORES)) { + continue; + } + uint8_t digit = wuffs_base__parse_number__decimal_digits[*p]; + if (digit == 0) { + goto fail_bad_argument; + } + digit &= 0x0F; + if ((v > max10) || ((v == max10) && (digit > max1))) { + goto fail_out_of_bounds; + } + v = (10 * v) + ((uint64_t)(digit)); + } + + wuffs_base__result_u64 ret; + ret.status.repr = NULL; + ret.value = v; + return ret; + } while (0); + +hexadecimal: + do { + uint64_t v = wuffs_base__parse_number__hexadecimal_digits[*p++]; + if (v == 0) { + goto fail_bad_argument; + } + v &= 0x0F; + + for (; p < q; p++) { + if ((*p == '_') && + (options & WUFFS_BASE__PARSE_NUMBER_XXX__ALLOW_UNDERSCORES)) { + continue; + } + uint8_t digit = wuffs_base__parse_number__hexadecimal_digits[*p]; + if (digit == 0) { + goto fail_bad_argument; + } + digit &= 0x0F; + if ((v >> 60) != 0) { + goto fail_out_of_bounds; + } + v = (v << 4) | ((uint64_t)(digit)); + } + + wuffs_base__result_u64 ret; + ret.status.repr = NULL; + ret.value = v; + return ret; + } while (0); + +ok_zero: + do { + wuffs_base__result_u64 ret; + ret.status.repr = NULL; + ret.value = 0; + return ret; + } while (0); + +fail_bad_argument: + do { + wuffs_base__result_u64 ret; + ret.status.repr = wuffs_base__error__bad_argument; + ret.value = 0; + return ret; + } while (0); + +fail_out_of_bounds: + do { + wuffs_base__result_u64 ret; + ret.status.repr = wuffs_base__error__out_of_bounds; + ret.value = 0; + return ret; + } while (0); +} + +// -------- + +// wuffs_base__render_number__first_hundred contains the decimal encodings of +// the first one hundred numbers [0 ..= 99]. +static const uint8_t wuffs_base__render_number__first_hundred[200] = { + '0', '0', '0', '1', '0', '2', '0', '3', '0', '4', // + '0', '5', '0', '6', '0', '7', '0', '8', '0', '9', // + '1', '0', '1', '1', '1', '2', '1', '3', '1', '4', // + '1', '5', '1', '6', '1', '7', '1', '8', '1', '9', // + '2', '0', '2', '1', '2', '2', '2', '3', '2', '4', // + '2', '5', '2', '6', '2', '7', '2', '8', '2', '9', // + '3', '0', '3', '1', '3', '2', '3', '3', '3', '4', // + '3', '5', '3', '6', '3', '7', '3', '8', '3', '9', // + '4', '0', '4', '1', '4', '2', '4', '3', '4', '4', // + '4', '5', '4', '6', '4', '7', '4', '8', '4', '9', // + '5', '0', '5', '1', '5', '2', '5', '3', '5', '4', // + '5', '5', '5', '6', '5', '7', '5', '8', '5', '9', // + '6', '0', '6', '1', '6', '2', '6', '3', '6', '4', // + '6', '5', '6', '6', '6', '7', '6', '8', '6', '9', // + '7', '0', '7', '1', '7', '2', '7', '3', '7', '4', // + '7', '5', '7', '6', '7', '7', '7', '8', '7', '9', // + '8', '0', '8', '1', '8', '2', '8', '3', '8', '4', // + '8', '5', '8', '6', '8', '7', '8', '8', '8', '9', // + '9', '0', '9', '1', '9', '2', '9', '3', '9', '4', // + '9', '5', '9', '6', '9', '7', '9', '8', '9', '9', // +}; + +static size_t // +wuffs_base__private_implementation__render_number_u64(wuffs_base__slice_u8 dst, + uint64_t x, + uint32_t options, + bool neg) { + uint8_t buf[WUFFS_BASE__U64__BYTE_LENGTH__MAX_INCL]; + uint8_t* ptr = &buf[0] + sizeof(buf); + + while (x >= 100) { + size_t index = ((size_t)((x % 100) * 2)); + x /= 100; + uint8_t s0 = wuffs_base__render_number__first_hundred[index + 0]; + uint8_t s1 = wuffs_base__render_number__first_hundred[index + 1]; + ptr -= 2; + ptr[0] = s0; + ptr[1] = s1; + } + + if (x < 10) { + ptr -= 1; + ptr[0] = (uint8_t)('0' + x); + } else { + size_t index = ((size_t)(x * 2)); + uint8_t s0 = wuffs_base__render_number__first_hundred[index + 0]; + uint8_t s1 = wuffs_base__render_number__first_hundred[index + 1]; + ptr -= 2; + ptr[0] = s0; + ptr[1] = s1; + } + + if (neg) { + ptr -= 1; + ptr[0] = '-'; + } else if (options & WUFFS_BASE__RENDER_NUMBER_XXX__LEADING_PLUS_SIGN) { + ptr -= 1; + ptr[0] = '+'; + } + + size_t n = sizeof(buf) - ((size_t)(ptr - &buf[0])); + if (n > dst.len) { + return 0; + } + memcpy(dst.ptr + ((options & WUFFS_BASE__RENDER_NUMBER_XXX__ALIGN_RIGHT) + ? (dst.len - n) + : 0), + ptr, n); + return n; +} + +WUFFS_BASE__MAYBE_STATIC size_t // +wuffs_base__render_number_i64(wuffs_base__slice_u8 dst, + int64_t x, + uint32_t options) { + uint64_t u = (uint64_t)x; + bool neg = x < 0; + if (neg) { + u = 1 + ~u; + } + return wuffs_base__private_implementation__render_number_u64(dst, u, options, + neg); +} + +WUFFS_BASE__MAYBE_STATIC size_t // +wuffs_base__render_number_u64(wuffs_base__slice_u8 dst, + uint64_t x, + uint32_t options) { + return wuffs_base__private_implementation__render_number_u64(dst, x, options, + false); +} + +// ---------------- Base-16 + +WUFFS_BASE__MAYBE_STATIC wuffs_base__transform__output // +wuffs_base__base_16__decode2(wuffs_base__slice_u8 dst, + wuffs_base__slice_u8 src, + bool src_closed, + uint32_t options) { + wuffs_base__transform__output o; + size_t src_len2 = src.len / 2; + size_t len; + if (dst.len < src_len2) { + len = dst.len; + o.status.repr = wuffs_base__suspension__short_write; + } else { + len = src_len2; + if (!src_closed) { + o.status.repr = wuffs_base__suspension__short_read; + } else if (src.len & 1) { + o.status.repr = wuffs_base__error__bad_data; + } else { + o.status.repr = NULL; + } + } + + uint8_t* d = dst.ptr; + uint8_t* s = src.ptr; + size_t n = len; + + while (n--) { + *d = (uint8_t)((wuffs_base__parse_number__hexadecimal_digits[s[0]] << 4) | + (wuffs_base__parse_number__hexadecimal_digits[s[1]] & 0x0F)); + d += 1; + s += 2; + } + + o.num_dst = len; + o.num_src = len * 2; + return o; +} + +WUFFS_BASE__MAYBE_STATIC wuffs_base__transform__output // +wuffs_base__base_16__decode4(wuffs_base__slice_u8 dst, + wuffs_base__slice_u8 src, + bool src_closed, + uint32_t options) { + wuffs_base__transform__output o; + size_t src_len4 = src.len / 4; + size_t len = dst.len < src_len4 ? dst.len : src_len4; + if (dst.len < src_len4) { + len = dst.len; + o.status.repr = wuffs_base__suspension__short_write; + } else { + len = src_len4; + if (!src_closed) { + o.status.repr = wuffs_base__suspension__short_read; + } else if (src.len & 1) { + o.status.repr = wuffs_base__error__bad_data; + } else { + o.status.repr = NULL; + } + } + + uint8_t* d = dst.ptr; + uint8_t* s = src.ptr; + size_t n = len; + + while (n--) { + *d = (uint8_t)((wuffs_base__parse_number__hexadecimal_digits[s[2]] << 4) | + (wuffs_base__parse_number__hexadecimal_digits[s[3]] & 0x0F)); + d += 1; + s += 4; + } + + o.num_dst = len; + o.num_src = len * 4; + return o; +} + +WUFFS_BASE__MAYBE_STATIC wuffs_base__transform__output // +wuffs_base__base_16__encode2(wuffs_base__slice_u8 dst, + wuffs_base__slice_u8 src, + bool src_closed, + uint32_t options) { + wuffs_base__transform__output o; + size_t dst_len2 = dst.len / 2; + size_t len; + if (dst_len2 < src.len) { + len = dst_len2; + o.status.repr = wuffs_base__suspension__short_write; + } else { + len = src.len; + if (!src_closed) { + o.status.repr = wuffs_base__suspension__short_read; + } else { + o.status.repr = NULL; + } + } + + uint8_t* d = dst.ptr; + uint8_t* s = src.ptr; + size_t n = len; + + while (n--) { + uint8_t c = *s; + d[0] = wuffs_base__private_implementation__encode_base16[c >> 4]; + d[1] = wuffs_base__private_implementation__encode_base16[c & 0x0F]; + d += 2; + s += 1; + } + + o.num_dst = len * 2; + o.num_src = len; + return o; +} + +WUFFS_BASE__MAYBE_STATIC wuffs_base__transform__output // +wuffs_base__base_16__encode4(wuffs_base__slice_u8 dst, + wuffs_base__slice_u8 src, + bool src_closed, + uint32_t options) { + wuffs_base__transform__output o; + size_t dst_len4 = dst.len / 4; + size_t len; + if (dst_len4 < src.len) { + len = dst_len4; + o.status.repr = wuffs_base__suspension__short_write; + } else { + len = src.len; + if (!src_closed) { + o.status.repr = wuffs_base__suspension__short_read; + } else { + o.status.repr = NULL; + } + } + + uint8_t* d = dst.ptr; + uint8_t* s = src.ptr; + size_t n = len; + + while (n--) { + uint8_t c = *s; + d[0] = '\\'; + d[1] = 'x'; + d[2] = wuffs_base__private_implementation__encode_base16[c >> 4]; + d[3] = wuffs_base__private_implementation__encode_base16[c & 0x0F]; + d += 4; + s += 1; + } + + o.num_dst = len * 4; + o.num_src = len; + return o; +} + +// ---------------- Base-64 + +// The two base-64 alphabets, std and url, differ only in the last two codes. +// - std: "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/" +// - url: "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_" + +static const uint8_t wuffs_base__base_64__decode_std[256] = { + // 0 1 2 3 4 5 6 7 + // 8 9 A B C D E F + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, // 0x00 ..= 0x07. + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, // 0x08 ..= 0x0F. + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, // 0x10 ..= 0x17. + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, // 0x18 ..= 0x1F. + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, // 0x20 ..= 0x27. + 0x80, 0x80, 0x80, 0x3E, 0x80, 0x80, 0x80, 0x3F, // 0x28 ..= 0x2F. + 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3A, 0x3B, // 0x30 ..= 0x37. + 0x3C, 0x3D, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, // 0x38 ..= 0x3F. + + 0x80, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, // 0x40 ..= 0x47. + 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, // 0x48 ..= 0x4F. + 0x0F, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, // 0x50 ..= 0x57. + 0x17, 0x18, 0x19, 0x80, 0x80, 0x80, 0x80, 0x80, // 0x58 ..= 0x5F. + 0x80, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F, 0x20, // 0x60 ..= 0x67. + 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, // 0x68 ..= 0x6F. + 0x29, 0x2A, 0x2B, 0x2C, 0x2D, 0x2E, 0x2F, 0x30, // 0x70 ..= 0x77. + 0x31, 0x32, 0x33, 0x80, 0x80, 0x80, 0x80, 0x80, // 0x78 ..= 0x7F. + + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, // 0x80 ..= 0x87. + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, // 0x88 ..= 0x8F. + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, // 0x90 ..= 0x97. + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, // 0x98 ..= 0x9F. + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, // 0xA0 ..= 0xA7. + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, // 0xA8 ..= 0xAF. + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, // 0xB0 ..= 0xB7. + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, // 0xB8 ..= 0xBF. + + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, // 0xC0 ..= 0xC7. + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, // 0xC8 ..= 0xCF. + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, // 0xD0 ..= 0xD7. + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, // 0xD8 ..= 0xDF. + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, // 0xE0 ..= 0xE7. + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, // 0xE8 ..= 0xEF. + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, // 0xF0 ..= 0xF7. + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, // 0xF8 ..= 0xFF. + // 0 1 2 3 4 5 6 7 + // 8 9 A B C D E F +}; + +static const uint8_t wuffs_base__base_64__decode_url[256] = { + // 0 1 2 3 4 5 6 7 + // 8 9 A B C D E F + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, // 0x00 ..= 0x07. + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, // 0x08 ..= 0x0F. + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, // 0x10 ..= 0x17. + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, // 0x18 ..= 0x1F. + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, // 0x20 ..= 0x27. + 0x80, 0x80, 0x80, 0x80, 0x80, 0x3E, 0x80, 0x80, // 0x28 ..= 0x2F. + 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3A, 0x3B, // 0x30 ..= 0x37. + 0x3C, 0x3D, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, // 0x38 ..= 0x3F. + + 0x80, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, // 0x40 ..= 0x47. + 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, // 0x48 ..= 0x4F. + 0x0F, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, // 0x50 ..= 0x57. + 0x17, 0x18, 0x19, 0x80, 0x80, 0x80, 0x80, 0x3F, // 0x58 ..= 0x5F. + 0x80, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F, 0x20, // 0x60 ..= 0x67. + 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, // 0x68 ..= 0x6F. + 0x29, 0x2A, 0x2B, 0x2C, 0x2D, 0x2E, 0x2F, 0x30, // 0x70 ..= 0x77. + 0x31, 0x32, 0x33, 0x80, 0x80, 0x80, 0x80, 0x80, // 0x78 ..= 0x7F. + + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, // 0x80 ..= 0x87. + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, // 0x88 ..= 0x8F. + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, // 0x90 ..= 0x97. + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, // 0x98 ..= 0x9F. + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, // 0xA0 ..= 0xA7. + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, // 0xA8 ..= 0xAF. + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, // 0xB0 ..= 0xB7. + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, // 0xB8 ..= 0xBF. + + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, // 0xC0 ..= 0xC7. + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, // 0xC8 ..= 0xCF. + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, // 0xD0 ..= 0xD7. + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, // 0xD8 ..= 0xDF. + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, // 0xE0 ..= 0xE7. + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, // 0xE8 ..= 0xEF. + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, // 0xF0 ..= 0xF7. + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, // 0xF8 ..= 0xFF. + // 0 1 2 3 4 5 6 7 + // 8 9 A B C D E F +}; + +static const uint8_t wuffs_base__base_64__encode_std[64] = { + // 0 1 2 3 4 5 6 7 + // 8 9 A B C D E F + 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, // 0x00 ..= 0x07. + 0x49, 0x4A, 0x4B, 0x4C, 0x4D, 0x4E, 0x4F, 0x50, // 0x08 ..= 0x0F. + 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, // 0x10 ..= 0x17. + 0x59, 0x5A, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, // 0x18 ..= 0x1F. + 0x67, 0x68, 0x69, 0x6A, 0x6B, 0x6C, 0x6D, 0x6E, // 0x20 ..= 0x27. + 0x6F, 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, // 0x28 ..= 0x2F. + 0x77, 0x78, 0x79, 0x7A, 0x30, 0x31, 0x32, 0x33, // 0x30 ..= 0x37. + 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x2B, 0x2F, // 0x38 ..= 0x3F. +}; + +static const uint8_t wuffs_base__base_64__encode_url[64] = { + // 0 1 2 3 4 5 6 7 + // 8 9 A B C D E F + 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, // 0x00 ..= 0x07. + 0x49, 0x4A, 0x4B, 0x4C, 0x4D, 0x4E, 0x4F, 0x50, // 0x08 ..= 0x0F. + 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, // 0x10 ..= 0x17. + 0x59, 0x5A, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, // 0x18 ..= 0x1F. + 0x67, 0x68, 0x69, 0x6A, 0x6B, 0x6C, 0x6D, 0x6E, // 0x20 ..= 0x27. + 0x6F, 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, // 0x28 ..= 0x2F. + 0x77, 0x78, 0x79, 0x7A, 0x30, 0x31, 0x32, 0x33, // 0x30 ..= 0x37. + 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x2D, 0x5F, // 0x38 ..= 0x3F. +}; + +// -------- + +WUFFS_BASE__MAYBE_STATIC wuffs_base__transform__output // +wuffs_base__base_64__decode(wuffs_base__slice_u8 dst, + wuffs_base__slice_u8 src, + bool src_closed, + uint32_t options) { + const uint8_t* alphabet = (options & WUFFS_BASE__BASE_64__URL_ALPHABET) + ? wuffs_base__base_64__decode_url + : wuffs_base__base_64__decode_std; + wuffs_base__transform__output o; + uint8_t* d_ptr = dst.ptr; + size_t d_len = dst.len; + const uint8_t* s_ptr = src.ptr; + size_t s_len = src.len; + bool pad = false; + + while (s_len >= 4) { + uint32_t s = wuffs_base__peek_u32le__no_bounds_check(s_ptr); + uint32_t s0 = alphabet[0xFF & (s >> 0)]; + uint32_t s1 = alphabet[0xFF & (s >> 8)]; + uint32_t s2 = alphabet[0xFF & (s >> 16)]; + uint32_t s3 = alphabet[0xFF & (s >> 24)]; + + if (((s0 | s1 | s2 | s3) & 0xC0) != 0) { + if (s_len > 4) { + o.status.repr = wuffs_base__error__bad_data; + goto done; + } else if (!src_closed) { + o.status.repr = wuffs_base__suspension__short_read; + goto done; + } else if ((options & WUFFS_BASE__BASE_64__DECODE_ALLOW_PADDING) && + (s_ptr[3] == '=')) { + pad = true; + if (s_ptr[2] == '=') { + goto src2; + } + goto src3; + } + o.status.repr = wuffs_base__error__bad_data; + goto done; + } + + if (d_len < 3) { + o.status.repr = wuffs_base__suspension__short_write; + goto done; + } + + s_ptr += 4; + s_len -= 4; + s = (s0 << 18) | (s1 << 12) | (s2 << 6) | (s3 << 0); + *d_ptr++ = (uint8_t)(s >> 16); + *d_ptr++ = (uint8_t)(s >> 8); + *d_ptr++ = (uint8_t)(s >> 0); + d_len -= 3; + } + + if (!src_closed) { + o.status.repr = wuffs_base__suspension__short_read; + goto done; + } + + if (s_len == 0) { + o.status.repr = NULL; + goto done; + } else if (s_len == 1) { + o.status.repr = wuffs_base__error__bad_data; + goto done; + } else if (s_len == 2) { + goto src2; + } + +src3: + do { + uint32_t s = wuffs_base__peek_u24le__no_bounds_check(s_ptr); + uint32_t s0 = alphabet[0xFF & (s >> 0)]; + uint32_t s1 = alphabet[0xFF & (s >> 8)]; + uint32_t s2 = alphabet[0xFF & (s >> 16)]; + if ((s0 & 0xC0) || (s1 & 0xC0) || (s2 & 0xC3)) { + o.status.repr = wuffs_base__error__bad_data; + goto done; + } + if (d_len < 2) { + o.status.repr = wuffs_base__suspension__short_write; + goto done; + } + s_ptr += pad ? 4 : 3; + s = (s0 << 18) | (s1 << 12) | (s2 << 6); + *d_ptr++ = (uint8_t)(s >> 16); + *d_ptr++ = (uint8_t)(s >> 8); + o.status.repr = NULL; + goto done; + } while (0); + +src2: + do { + uint32_t s = wuffs_base__peek_u16le__no_bounds_check(s_ptr); + uint32_t s0 = alphabet[0xFF & (s >> 0)]; + uint32_t s1 = alphabet[0xFF & (s >> 8)]; + if ((s0 & 0xC0) || (s1 & 0xCF)) { + o.status.repr = wuffs_base__error__bad_data; + goto done; + } + if (d_len < 1) { + o.status.repr = wuffs_base__suspension__short_write; + goto done; + } + s_ptr += pad ? 4 : 2; + s = (s0 << 18) | (s1 << 12); + *d_ptr++ = (uint8_t)(s >> 16); + o.status.repr = NULL; + goto done; + } while (0); + +done: + o.num_dst = (size_t)(d_ptr - dst.ptr); + o.num_src = (size_t)(s_ptr - src.ptr); + return o; +} + +WUFFS_BASE__MAYBE_STATIC wuffs_base__transform__output // +wuffs_base__base_64__encode(wuffs_base__slice_u8 dst, + wuffs_base__slice_u8 src, + bool src_closed, + uint32_t options) { + const uint8_t* alphabet = (options & WUFFS_BASE__BASE_64__URL_ALPHABET) + ? wuffs_base__base_64__encode_url + : wuffs_base__base_64__encode_std; + wuffs_base__transform__output o; + uint8_t* d_ptr = dst.ptr; + size_t d_len = dst.len; + const uint8_t* s_ptr = src.ptr; + size_t s_len = src.len; + + do { + while (s_len >= 3) { + if (d_len < 4) { + o.status.repr = wuffs_base__suspension__short_write; + goto done; + } + uint32_t s = wuffs_base__peek_u24be__no_bounds_check(s_ptr); + s_ptr += 3; + s_len -= 3; + *d_ptr++ = alphabet[0x3F & (s >> 18)]; + *d_ptr++ = alphabet[0x3F & (s >> 12)]; + *d_ptr++ = alphabet[0x3F & (s >> 6)]; + *d_ptr++ = alphabet[0x3F & (s >> 0)]; + d_len -= 4; + } + + if (!src_closed) { + o.status.repr = wuffs_base__suspension__short_read; + goto done; + } + + if (s_len == 2) { + if (d_len < + ((options & WUFFS_BASE__BASE_64__ENCODE_EMIT_PADDING) ? 4 : 3)) { + o.status.repr = wuffs_base__suspension__short_write; + goto done; + } + uint32_t s = ((uint32_t)(wuffs_base__peek_u16be__no_bounds_check(s_ptr))) + << 8; + s_ptr += 2; + *d_ptr++ = alphabet[0x3F & (s >> 18)]; + *d_ptr++ = alphabet[0x3F & (s >> 12)]; + *d_ptr++ = alphabet[0x3F & (s >> 6)]; + if (options & WUFFS_BASE__BASE_64__ENCODE_EMIT_PADDING) { + *d_ptr++ = '='; + } + o.status.repr = NULL; + goto done; + + } else if (s_len == 1) { + if (d_len < + ((options & WUFFS_BASE__BASE_64__ENCODE_EMIT_PADDING) ? 4 : 2)) { + o.status.repr = wuffs_base__suspension__short_write; + goto done; + } + uint32_t s = ((uint32_t)(wuffs_base__peek_u8__no_bounds_check(s_ptr))) + << 16; + s_ptr += 1; + *d_ptr++ = alphabet[0x3F & (s >> 18)]; + *d_ptr++ = alphabet[0x3F & (s >> 12)]; + if (options & WUFFS_BASE__BASE_64__ENCODE_EMIT_PADDING) { + *d_ptr++ = '='; + *d_ptr++ = '='; + } + o.status.repr = NULL; + goto done; + + } else { + o.status.repr = NULL; + goto done; + } + } while (0); + +done: + o.num_dst = (size_t)(d_ptr - dst.ptr); + o.num_src = (size_t)(s_ptr - src.ptr); + return o; +} + +#endif // !defined(WUFFS_CONFIG__MODULES) || + // defined(WUFFS_CONFIG__MODULE__BASE) || + // defined(WUFFS_CONFIG__MODULE__BASE__INTCONV) + +#if !defined(WUFFS_CONFIG__MODULES) || defined(WUFFS_CONFIG__MODULE__BASE) || \ + defined(WUFFS_CONFIG__MODULE__BASE__MAGIC) + +// ---------------- Magic Numbers + +// ICO doesn't start with a magic identifier. Instead, see if the opening bytes +// are plausibly ICO. +// +// Callers should have already verified that (prefix_data.len >= 2) and the +// first two bytes are 0x00. +// +// See: +// - https://docs.fileformat.com/image/ico/ +static int32_t // +wuffs_base__magic_number_guess_fourcc__maybe_ico( + wuffs_base__slice_u8 prefix_data, + bool prefix_closed) { + // Allow-list for the Image Type field. + if (prefix_data.len < 4) { + return prefix_closed ? 0 : -1; + } else if (prefix_data.ptr[3] != 0) { + return 0; + } + switch (prefix_data.ptr[2]) { + case 0x01: // ICO + case 0x02: // CUR + break; + default: + return 0; + } + + // The Number Of Images should be positive. + if (prefix_data.len < 6) { + return prefix_closed ? 0 : -1; + } else if ((prefix_data.ptr[4] == 0) && (prefix_data.ptr[5] == 0)) { + return 0; + } + + // The first ICONDIRENTRY's fourth byte should be zero. + if (prefix_data.len < 10) { + return prefix_closed ? 0 : -1; + } else if (prefix_data.ptr[9] != 0) { + return 0; + } + + // TODO: have a separate FourCC for CUR? + return 0x49434F20; // 'ICO 'be +} + +// TGA doesn't start with a magic identifier. Instead, see if the opening bytes +// are plausibly TGA. +// +// Callers should have already verified that (prefix_data.len >= 2) and the +// second byte (prefix_data.ptr[1], the Color Map Type byte), is either 0x00 or +// 0x01. +// +// See: +// - https://docs.fileformat.com/image/tga/ +// - https://www.dca.fee.unicamp.br/~martino/disciplinas/ea978/tgaffs.pdf +static int32_t // +wuffs_base__magic_number_guess_fourcc__maybe_tga( + wuffs_base__slice_u8 prefix_data, + bool prefix_closed) { + // Allow-list for the Image Type field. + if (prefix_data.len < 3) { + return prefix_closed ? 0 : -1; + } + switch (prefix_data.ptr[2]) { + case 0x01: + case 0x02: + case 0x03: + case 0x09: + case 0x0A: + case 0x0B: + break; + default: + // TODO: 0x20 and 0x21 are invalid, according to the spec, but are + // apparently unofficial extensions. + return 0; + } + + // Allow-list for the Color Map Entry Size field (if the Color Map Type field + // is non-zero) or else all the Color Map fields should be zero. + if (prefix_data.len < 8) { + return prefix_closed ? 0 : -1; + } else if (prefix_data.ptr[1] != 0x00) { + switch (prefix_data.ptr[7]) { + case 0x0F: + case 0x10: + case 0x18: + case 0x20: + break; + default: + return 0; + } + } else if ((prefix_data.ptr[3] | prefix_data.ptr[4] | prefix_data.ptr[5] | + prefix_data.ptr[6] | prefix_data.ptr[7]) != 0x00) { + return 0; + } + + // Allow-list for the Pixel Depth field. + if (prefix_data.len < 17) { + return prefix_closed ? 0 : -1; + } + switch (prefix_data.ptr[16]) { + case 0x01: + case 0x08: + case 0x0F: + case 0x10: + case 0x18: + case 0x20: + break; + default: + return 0; + } + + return 0x54474120; // 'TGA 'be +} + +WUFFS_BASE__MAYBE_STATIC int32_t // +wuffs_base__magic_number_guess_fourcc(wuffs_base__slice_u8 prefix_data, + bool prefix_closed) { + // This is similar to (but different from): + // - the magic/Magdir tables under https://github.com/file/file + // - the MIME Sniffing algorithm at https://mimesniff.spec.whatwg.org/ + + // table holds the 'magic numbers' (which are actually variable length + // strings). The strings may contain NUL bytes, so the "const char* magic" + // value starts with the length-minus-1 of the 'magic number'. + // + // Keep it sorted by magic[1], then magic[0] descending (prioritizing longer + // matches) and finally by magic[2:]. When multiple entries match, the + // longest one wins. + // + // The fourcc field might be negated, in which case there's further + // specialization (see § below). + static struct { + int32_t fourcc; + const char* magic; + } table[] = { + {-0x30302020, "\x01\x00\x00"}, // '00 'be + {+0x475A2020, "\x02\x1F\x8B\x08"}, // GZ + {+0x5A535444, "\x03\x28\xB5\x2F\xFD"}, // ZSTD + {+0x425A3220, "\x02\x42\x5A\x68"}, // BZ2 + {+0x424D5020, "\x01\x42\x4D"}, // BMP + {+0x47494620, "\x03\x47\x49\x46\x38"}, // GIF + {+0x54494646, "\x03\x49\x49\x2A\x00"}, // TIFF (little-endian) + {+0x54494646, "\x03\x4D\x4D\x00\x2A"}, // TIFF (big-endian) + {-0x52494646, "\x03\x52\x49\x46\x46"}, // RIFF + {+0x4E494520, "\x02\x6E\xC3\xAF"}, // NIE + {+0x514F4920, "\x03\x71\x6F\x69\x66"}, // QOI + {+0x5A4C4942, "\x01\x78\x9C"}, // ZLIB + {+0x504E4720, "\x03\x89\x50\x4E\x47"}, // PNG + {+0x4A504547, "\x01\xFF\xD8"}, // JPEG + }; + static const size_t table_len = sizeof(table) / sizeof(table[0]); + + if (prefix_data.len == 0) { + return prefix_closed ? 0 : -1; + } + uint8_t pre_first_byte = prefix_data.ptr[0]; + + int32_t fourcc = 0; + size_t i; + for (i = 0; i < table_len; i++) { + uint8_t mag_first_byte = ((uint8_t)(table[i].magic[1])); + if (pre_first_byte < mag_first_byte) { + break; + } else if (pre_first_byte > mag_first_byte) { + continue; + } + fourcc = table[i].fourcc; + + uint8_t mag_remaining_len = ((uint8_t)(table[i].magic[0])); + if (mag_remaining_len == 0) { + goto match; + } + + const char* mag_remaining_ptr = table[i].magic + 2; + uint8_t* pre_remaining_ptr = prefix_data.ptr + 1; + size_t pre_remaining_len = prefix_data.len - 1; + if (pre_remaining_len < mag_remaining_len) { + if (!memcmp(pre_remaining_ptr, mag_remaining_ptr, pre_remaining_len)) { + return prefix_closed ? 0 : -1; + } + } else { + if (!memcmp(pre_remaining_ptr, mag_remaining_ptr, mag_remaining_len)) { + goto match; + } + } + } + + if (prefix_data.len < 2) { + return prefix_closed ? 0 : -1; + } else if ((prefix_data.ptr[1] == 0x00) || (prefix_data.ptr[1] == 0x01)) { + return wuffs_base__magic_number_guess_fourcc__maybe_tga(prefix_data, + prefix_closed); + } + + return 0; + +match: + // Negative FourCC values (see § above) are further specialized. + if (fourcc < 0) { + fourcc = -fourcc; + + if (fourcc == 0x52494646) { // 'RIFF'be + if (prefix_data.len < 12) { + return prefix_closed ? 0 : -1; + } + uint32_t x = wuffs_base__peek_u32be__no_bounds_check(prefix_data.ptr + 8); + if (x == 0x57454250) { // 'WEBP'be + return 0x57454250; // 'WEBP'be + } + + } else if (fourcc == 0x30302020) { // '00 'be + // Binary data starting with multiple 0x00 NUL bytes is quite common. + // Unfortunately, some file formats also don't start with a magic + // identifier, so we have to use heuristics (where the order matters, the + // same as /usr/bin/file's magic/Magdir tables) as best we can. Maybe + // it's TGA, ICO/CUR, etc. Maybe it's something else. + int32_t tga = wuffs_base__magic_number_guess_fourcc__maybe_tga( + prefix_data, prefix_closed); + if (tga != 0) { + return tga; + } + int32_t ico = wuffs_base__magic_number_guess_fourcc__maybe_ico( + prefix_data, prefix_closed); + if (ico != 0) { + return ico; + } + if (prefix_data.len < 4) { + return prefix_closed ? 0 : -1; + } else if ((prefix_data.ptr[2] != 0x00) && + ((prefix_data.ptr[2] >= 0x80) || + (prefix_data.ptr[3] != 0x00))) { + // Roughly speaking, this could be a non-degenerate (non-0-width and + // non-0-height) WBMP image. + return 0x57424D50; // 'WBMP'be + } + return 0; + } + } + return fourcc; +} + +#endif // !defined(WUFFS_CONFIG__MODULES) || + // defined(WUFFS_CONFIG__MODULE__BASE) || + // defined(WUFFS_CONFIG__MODULE__BASE__MAGIC) + +#if !defined(WUFFS_CONFIG__MODULES) || defined(WUFFS_CONFIG__MODULE__BASE) || \ + defined(WUFFS_CONFIG__MODULE__BASE__PIXCONV) + +// ---------------- Pixel Swizzler + +static inline uint32_t // +wuffs_base__swap_u32_argb_abgr(uint32_t u) { + uint32_t o = u & 0xFF00FF00ul; + uint32_t r = u & 0x00FF0000ul; + uint32_t b = u & 0x000000FFul; + return o | (r >> 16) | (b << 16); +} + +static inline uint64_t // +wuffs_base__swap_u64_argb_abgr(uint64_t u) { + uint64_t o = u & 0xFFFF0000FFFF0000ull; + uint64_t r = u & 0x0000FFFF00000000ull; + uint64_t b = u & 0x000000000000FFFFull; + return o | (r >> 32) | (b << 32); +} + +static inline uint32_t // +wuffs_base__color_u64__as__color_u32__swap_u32_argb_abgr(uint64_t c) { + uint32_t a = ((uint32_t)(0xFF & (c >> 56))); + uint32_t r = ((uint32_t)(0xFF & (c >> 40))); + uint32_t g = ((uint32_t)(0xFF & (c >> 24))); + uint32_t b = ((uint32_t)(0xFF & (c >> 8))); + return (a << 24) | (b << 16) | (g << 8) | (r << 0); +} + +// -------- + +WUFFS_BASE__MAYBE_STATIC wuffs_base__color_u32_argb_premul // +wuffs_base__pixel_buffer__color_u32_at(const wuffs_base__pixel_buffer* pb, + uint32_t x, + uint32_t y) { + if (!pb || (x >= pb->pixcfg.private_impl.width) || + (y >= pb->pixcfg.private_impl.height)) { + return 0; + } + + if (wuffs_base__pixel_format__is_planar(&pb->pixcfg.private_impl.pixfmt)) { + // TODO: support planar formats. + return 0; + } + + size_t stride = pb->private_impl.planes[0].stride; + const uint8_t* row = pb->private_impl.planes[0].ptr + (stride * ((size_t)y)); + + switch (pb->pixcfg.private_impl.pixfmt.repr) { + case WUFFS_BASE__PIXEL_FORMAT__BGRA_PREMUL: + case WUFFS_BASE__PIXEL_FORMAT__BGRA_BINARY: + return wuffs_base__peek_u32le__no_bounds_check(row + (4 * ((size_t)x))); + + case WUFFS_BASE__PIXEL_FORMAT__INDEXED__BGRA_PREMUL: + case WUFFS_BASE__PIXEL_FORMAT__INDEXED__BGRA_BINARY: { + uint8_t* palette = pb->private_impl.planes[3].ptr; + return wuffs_base__peek_u32le__no_bounds_check(palette + + (4 * ((size_t)row[x]))); + } + + // Common formats above. Rarer formats below. + + case WUFFS_BASE__PIXEL_FORMAT__Y: + return 0xFF000000 | (0x00010101 * ((uint32_t)(row[x]))); + case WUFFS_BASE__PIXEL_FORMAT__Y_16LE: + return 0xFF000000 | (0x00010101 * ((uint32_t)(row[(2 * x) + 1]))); + case WUFFS_BASE__PIXEL_FORMAT__Y_16BE: + return 0xFF000000 | (0x00010101 * ((uint32_t)(row[(2 * x) + 0]))); + + case WUFFS_BASE__PIXEL_FORMAT__INDEXED__BGRA_NONPREMUL: { + uint8_t* palette = pb->private_impl.planes[3].ptr; + return wuffs_base__color_u32_argb_nonpremul__as__color_u32_argb_premul( + wuffs_base__peek_u32le__no_bounds_check(palette + + (4 * ((size_t)row[x])))); + } + + case WUFFS_BASE__PIXEL_FORMAT__BGR_565: + return wuffs_base__color_u16_rgb_565__as__color_u32_argb_premul( + wuffs_base__peek_u16le__no_bounds_check(row + (2 * ((size_t)x)))); + case WUFFS_BASE__PIXEL_FORMAT__BGR: + return 0xFF000000 | + wuffs_base__peek_u24le__no_bounds_check(row + (3 * ((size_t)x))); + case WUFFS_BASE__PIXEL_FORMAT__BGRA_NONPREMUL: + return wuffs_base__color_u32_argb_nonpremul__as__color_u32_argb_premul( + wuffs_base__peek_u32le__no_bounds_check(row + (4 * ((size_t)x)))); + case WUFFS_BASE__PIXEL_FORMAT__BGRA_NONPREMUL_4X16LE: + return wuffs_base__color_u64_argb_nonpremul__as__color_u32_argb_premul( + wuffs_base__peek_u64le__no_bounds_check(row + (8 * ((size_t)x)))); + case WUFFS_BASE__PIXEL_FORMAT__BGRX: + return 0xFF000000 | + wuffs_base__peek_u32le__no_bounds_check(row + (4 * ((size_t)x))); + + case WUFFS_BASE__PIXEL_FORMAT__RGB: + return wuffs_base__swap_u32_argb_abgr( + 0xFF000000 | + wuffs_base__peek_u24le__no_bounds_check(row + (3 * ((size_t)x)))); + case WUFFS_BASE__PIXEL_FORMAT__RGBA_NONPREMUL: + return wuffs_base__swap_u32_argb_abgr( + wuffs_base__color_u32_argb_nonpremul__as__color_u32_argb_premul( + wuffs_base__peek_u32le__no_bounds_check(row + + (4 * ((size_t)x))))); + case WUFFS_BASE__PIXEL_FORMAT__RGBA_PREMUL: + case WUFFS_BASE__PIXEL_FORMAT__RGBA_BINARY: + return wuffs_base__swap_u32_argb_abgr( + wuffs_base__peek_u32le__no_bounds_check(row + (4 * ((size_t)x)))); + case WUFFS_BASE__PIXEL_FORMAT__RGBX: + return wuffs_base__swap_u32_argb_abgr( + 0xFF000000 | + wuffs_base__peek_u32le__no_bounds_check(row + (4 * ((size_t)x)))); + + default: + // TODO: support more formats. + break; + } + + return 0; +} + +// -------- + +WUFFS_BASE__MAYBE_STATIC wuffs_base__status // +wuffs_base__pixel_buffer__set_color_u32_at( + wuffs_base__pixel_buffer* pb, + uint32_t x, + uint32_t y, + wuffs_base__color_u32_argb_premul color) { + if (!pb) { + return wuffs_base__make_status(wuffs_base__error__bad_receiver); + } + if ((x >= pb->pixcfg.private_impl.width) || + (y >= pb->pixcfg.private_impl.height)) { + return wuffs_base__make_status(wuffs_base__error__bad_argument); + } + + if (wuffs_base__pixel_format__is_planar(&pb->pixcfg.private_impl.pixfmt)) { + // TODO: support planar formats. + return wuffs_base__make_status(wuffs_base__error__unsupported_option); + } + + size_t stride = pb->private_impl.planes[0].stride; + uint8_t* row = pb->private_impl.planes[0].ptr + (stride * ((size_t)y)); + + switch (pb->pixcfg.private_impl.pixfmt.repr) { + case WUFFS_BASE__PIXEL_FORMAT__BGRA_PREMUL: + case WUFFS_BASE__PIXEL_FORMAT__BGRX: + wuffs_base__poke_u32le__no_bounds_check(row + (4 * ((size_t)x)), color); + break; + + // Common formats above. Rarer formats below. + + case WUFFS_BASE__PIXEL_FORMAT__Y: + wuffs_base__poke_u8__no_bounds_check( + row + ((size_t)x), + wuffs_base__color_u32_argb_premul__as__color_u8_gray(color)); + break; + case WUFFS_BASE__PIXEL_FORMAT__Y_16LE: + wuffs_base__poke_u16le__no_bounds_check( + row + (2 * ((size_t)x)), + wuffs_base__color_u32_argb_premul__as__color_u16_gray(color)); + break; + case WUFFS_BASE__PIXEL_FORMAT__Y_16BE: + wuffs_base__poke_u16be__no_bounds_check( + row + (2 * ((size_t)x)), + wuffs_base__color_u32_argb_premul__as__color_u16_gray(color)); + break; + + case WUFFS_BASE__PIXEL_FORMAT__INDEXED__BGRA_NONPREMUL: + case WUFFS_BASE__PIXEL_FORMAT__INDEXED__BGRA_PREMUL: + case WUFFS_BASE__PIXEL_FORMAT__INDEXED__BGRA_BINARY: + wuffs_base__poke_u8__no_bounds_check( + row + ((size_t)x), wuffs_base__pixel_palette__closest_element( + wuffs_base__pixel_buffer__palette(pb), + pb->pixcfg.private_impl.pixfmt, color)); + break; + + case WUFFS_BASE__PIXEL_FORMAT__BGR_565: + wuffs_base__poke_u16le__no_bounds_check( + row + (2 * ((size_t)x)), + wuffs_base__color_u32_argb_premul__as__color_u16_rgb_565(color)); + break; + case WUFFS_BASE__PIXEL_FORMAT__BGR: + wuffs_base__poke_u24le__no_bounds_check(row + (3 * ((size_t)x)), color); + break; + case WUFFS_BASE__PIXEL_FORMAT__BGRA_NONPREMUL: + wuffs_base__poke_u32le__no_bounds_check( + row + (4 * ((size_t)x)), + wuffs_base__color_u32_argb_premul__as__color_u32_argb_nonpremul( + color)); + break; + case WUFFS_BASE__PIXEL_FORMAT__BGRA_NONPREMUL_4X16LE: + wuffs_base__poke_u64le__no_bounds_check( + row + (8 * ((size_t)x)), + wuffs_base__color_u32_argb_premul__as__color_u64_argb_nonpremul( + color)); + break; + + case WUFFS_BASE__PIXEL_FORMAT__RGB: + wuffs_base__poke_u24le__no_bounds_check( + row + (3 * ((size_t)x)), wuffs_base__swap_u32_argb_abgr(color)); + break; + case WUFFS_BASE__PIXEL_FORMAT__RGBA_NONPREMUL: + wuffs_base__poke_u32le__no_bounds_check( + row + (4 * ((size_t)x)), + wuffs_base__color_u32_argb_premul__as__color_u32_argb_nonpremul( + wuffs_base__swap_u32_argb_abgr(color))); + break; + case WUFFS_BASE__PIXEL_FORMAT__RGBA_PREMUL: + case WUFFS_BASE__PIXEL_FORMAT__RGBX: + wuffs_base__poke_u32le__no_bounds_check( + row + (4 * ((size_t)x)), wuffs_base__swap_u32_argb_abgr(color)); + break; + + default: + // TODO: support more formats. + return wuffs_base__make_status(wuffs_base__error__unsupported_option); + } + + return wuffs_base__make_status(NULL); +} + +// -------- + +static inline void // +wuffs_base__pixel_buffer__set_color_u32_fill_rect__xx( + wuffs_base__pixel_buffer* pb, + wuffs_base__rect_ie_u32 rect, + uint16_t color) { + size_t stride = pb->private_impl.planes[0].stride; + uint32_t width = wuffs_base__rect_ie_u32__width(&rect); + if ((stride == (2 * ((uint64_t)width))) && (rect.min_incl_x == 0)) { + uint8_t* ptr = + pb->private_impl.planes[0].ptr + (stride * ((size_t)rect.min_incl_y)); + uint32_t height = wuffs_base__rect_ie_u32__height(&rect); + size_t n; + for (n = ((size_t)width) * ((size_t)height); n > 0; n--) { + wuffs_base__poke_u16le__no_bounds_check(ptr, color); + ptr += 2; + } + return; + } + + uint32_t y; + for (y = rect.min_incl_y; y < rect.max_excl_y; y++) { + uint8_t* ptr = pb->private_impl.planes[0].ptr + (stride * ((size_t)y)) + + (2 * ((size_t)rect.min_incl_x)); + uint32_t n; + for (n = width; n > 0; n--) { + wuffs_base__poke_u16le__no_bounds_check(ptr, color); + ptr += 2; + } + } +} + +static inline void // +wuffs_base__pixel_buffer__set_color_u32_fill_rect__xxx( + wuffs_base__pixel_buffer* pb, + wuffs_base__rect_ie_u32 rect, + uint32_t color) { + size_t stride = pb->private_impl.planes[0].stride; + uint32_t width = wuffs_base__rect_ie_u32__width(&rect); + if ((stride == (3 * ((uint64_t)width))) && (rect.min_incl_x == 0)) { + uint8_t* ptr = + pb->private_impl.planes[0].ptr + (stride * ((size_t)rect.min_incl_y)); + uint32_t height = wuffs_base__rect_ie_u32__height(&rect); + size_t n; + for (n = ((size_t)width) * ((size_t)height); n > 0; n--) { + wuffs_base__poke_u24le__no_bounds_check(ptr, color); + ptr += 3; + } + return; + } + + uint32_t y; + for (y = rect.min_incl_y; y < rect.max_excl_y; y++) { + uint8_t* ptr = pb->private_impl.planes[0].ptr + (stride * ((size_t)y)) + + (3 * ((size_t)rect.min_incl_x)); + uint32_t n; + for (n = width; n > 0; n--) { + wuffs_base__poke_u24le__no_bounds_check(ptr, color); + ptr += 3; + } + } +} + +static inline void // +wuffs_base__pixel_buffer__set_color_u32_fill_rect__xxxx( + wuffs_base__pixel_buffer* pb, + wuffs_base__rect_ie_u32 rect, + uint32_t color) { + size_t stride = pb->private_impl.planes[0].stride; + uint32_t width = wuffs_base__rect_ie_u32__width(&rect); + if ((stride == (4 * ((uint64_t)width))) && (rect.min_incl_x == 0)) { + uint8_t* ptr = + pb->private_impl.planes[0].ptr + (stride * ((size_t)rect.min_incl_y)); + uint32_t height = wuffs_base__rect_ie_u32__height(&rect); + size_t n; + for (n = ((size_t)width) * ((size_t)height); n > 0; n--) { + wuffs_base__poke_u32le__no_bounds_check(ptr, color); + ptr += 4; + } + return; + } + + uint32_t y; + for (y = rect.min_incl_y; y < rect.max_excl_y; y++) { + uint8_t* ptr = pb->private_impl.planes[0].ptr + (stride * ((size_t)y)) + + (4 * ((size_t)rect.min_incl_x)); + uint32_t n; + for (n = width; n > 0; n--) { + wuffs_base__poke_u32le__no_bounds_check(ptr, color); + ptr += 4; + } + } +} + +static inline void // +wuffs_base__pixel_buffer__set_color_u32_fill_rect__xxxxxxxx( + wuffs_base__pixel_buffer* pb, + wuffs_base__rect_ie_u32 rect, + uint64_t color) { + size_t stride = pb->private_impl.planes[0].stride; + uint32_t width = wuffs_base__rect_ie_u32__width(&rect); + if ((stride == (8 * ((uint64_t)width))) && (rect.min_incl_x == 0)) { + uint8_t* ptr = + pb->private_impl.planes[0].ptr + (stride * ((size_t)rect.min_incl_y)); + uint32_t height = wuffs_base__rect_ie_u32__height(&rect); + size_t n; + for (n = ((size_t)width) * ((size_t)height); n > 0; n--) { + wuffs_base__poke_u64le__no_bounds_check(ptr, color); + ptr += 8; + } + return; + } + + uint32_t y; + for (y = rect.min_incl_y; y < rect.max_excl_y; y++) { + uint8_t* ptr = pb->private_impl.planes[0].ptr + (stride * ((size_t)y)) + + (8 * ((size_t)rect.min_incl_x)); + uint32_t n; + for (n = width; n > 0; n--) { + wuffs_base__poke_u64le__no_bounds_check(ptr, color); + ptr += 8; + } + } +} + +WUFFS_BASE__MAYBE_STATIC wuffs_base__status // +wuffs_base__pixel_buffer__set_color_u32_fill_rect( + wuffs_base__pixel_buffer* pb, + wuffs_base__rect_ie_u32 rect, + wuffs_base__color_u32_argb_premul color) { + if (!pb) { + return wuffs_base__make_status(wuffs_base__error__bad_receiver); + } else if (wuffs_base__rect_ie_u32__is_empty(&rect)) { + return wuffs_base__make_status(NULL); + } + wuffs_base__rect_ie_u32 bounds = + wuffs_base__pixel_config__bounds(&pb->pixcfg); + if (!wuffs_base__rect_ie_u32__contains_rect(&bounds, rect)) { + return wuffs_base__make_status(wuffs_base__error__bad_argument); + } + + if (wuffs_base__pixel_format__is_planar(&pb->pixcfg.private_impl.pixfmt)) { + // TODO: support planar formats. + return wuffs_base__make_status(wuffs_base__error__unsupported_option); + } + + switch (pb->pixcfg.private_impl.pixfmt.repr) { + case WUFFS_BASE__PIXEL_FORMAT__BGRA_PREMUL: + case WUFFS_BASE__PIXEL_FORMAT__BGRX: + wuffs_base__pixel_buffer__set_color_u32_fill_rect__xxxx(pb, rect, color); + return wuffs_base__make_status(NULL); + + // Common formats above. Rarer formats below. + + case WUFFS_BASE__PIXEL_FORMAT__BGR_565: + wuffs_base__pixel_buffer__set_color_u32_fill_rect__xx( + pb, rect, + wuffs_base__color_u32_argb_premul__as__color_u16_rgb_565(color)); + return wuffs_base__make_status(NULL); + + case WUFFS_BASE__PIXEL_FORMAT__BGR: + wuffs_base__pixel_buffer__set_color_u32_fill_rect__xxx(pb, rect, color); + return wuffs_base__make_status(NULL); + + case WUFFS_BASE__PIXEL_FORMAT__BGRA_NONPREMUL: + wuffs_base__pixel_buffer__set_color_u32_fill_rect__xxxx( + pb, rect, + wuffs_base__color_u32_argb_premul__as__color_u32_argb_nonpremul( + color)); + return wuffs_base__make_status(NULL); + + case WUFFS_BASE__PIXEL_FORMAT__BGRA_NONPREMUL_4X16LE: + wuffs_base__pixel_buffer__set_color_u32_fill_rect__xxxxxxxx( + pb, rect, + wuffs_base__color_u32_argb_premul__as__color_u64_argb_nonpremul( + color)); + return wuffs_base__make_status(NULL); + + case WUFFS_BASE__PIXEL_FORMAT__RGBA_NONPREMUL: + wuffs_base__pixel_buffer__set_color_u32_fill_rect__xxxx( + pb, rect, + wuffs_base__color_u32_argb_premul__as__color_u32_argb_nonpremul( + wuffs_base__swap_u32_argb_abgr(color))); + return wuffs_base__make_status(NULL); + + case WUFFS_BASE__PIXEL_FORMAT__RGBA_PREMUL: + case WUFFS_BASE__PIXEL_FORMAT__RGBX: + wuffs_base__pixel_buffer__set_color_u32_fill_rect__xxxx( + pb, rect, wuffs_base__swap_u32_argb_abgr(color)); + return wuffs_base__make_status(NULL); + } + + uint32_t y; + for (y = rect.min_incl_y; y < rect.max_excl_y; y++) { + uint32_t x; + for (x = rect.min_incl_x; x < rect.max_excl_x; x++) { + wuffs_base__pixel_buffer__set_color_u32_at(pb, x, y, color); + } + } + return wuffs_base__make_status(NULL); +} + +// -------- + +WUFFS_BASE__MAYBE_STATIC uint8_t // +wuffs_base__pixel_palette__closest_element( + wuffs_base__slice_u8 palette_slice, + wuffs_base__pixel_format palette_format, + wuffs_base__color_u32_argb_premul c) { + size_t n = palette_slice.len / 4; + if (n > (WUFFS_BASE__PIXEL_FORMAT__INDEXED__PALETTE_BYTE_LENGTH / 4)) { + n = (WUFFS_BASE__PIXEL_FORMAT__INDEXED__PALETTE_BYTE_LENGTH / 4); + } + size_t best_index = 0; + uint64_t best_score = 0xFFFFFFFFFFFFFFFF; + + // Work in 16-bit color. + uint32_t ca = 0x101 * (0xFF & (c >> 24)); + uint32_t cr = 0x101 * (0xFF & (c >> 16)); + uint32_t cg = 0x101 * (0xFF & (c >> 8)); + uint32_t cb = 0x101 * (0xFF & (c >> 0)); + + switch (palette_format.repr) { + case WUFFS_BASE__PIXEL_FORMAT__INDEXED__BGRA_NONPREMUL: + case WUFFS_BASE__PIXEL_FORMAT__INDEXED__BGRA_PREMUL: + case WUFFS_BASE__PIXEL_FORMAT__INDEXED__BGRA_BINARY: { + bool nonpremul = palette_format.repr == + WUFFS_BASE__PIXEL_FORMAT__INDEXED__BGRA_NONPREMUL; + + size_t i; + for (i = 0; i < n; i++) { + // Work in 16-bit color. + uint32_t pb = 0x101 * ((uint32_t)(palette_slice.ptr[(4 * i) + 0])); + uint32_t pg = 0x101 * ((uint32_t)(palette_slice.ptr[(4 * i) + 1])); + uint32_t pr = 0x101 * ((uint32_t)(palette_slice.ptr[(4 * i) + 2])); + uint32_t pa = 0x101 * ((uint32_t)(palette_slice.ptr[(4 * i) + 3])); + + // Convert to premultiplied alpha. + if (nonpremul && (pa != 0xFFFF)) { + pb = (pb * pa) / 0xFFFF; + pg = (pg * pa) / 0xFFFF; + pr = (pr * pa) / 0xFFFF; + } + + // These deltas are conceptually int32_t (signed) but after squaring, + // it's equivalent to work in uint32_t (unsigned). + pb -= cb; + pg -= cg; + pr -= cr; + pa -= ca; + uint64_t score = ((uint64_t)(pb * pb)) + ((uint64_t)(pg * pg)) + + ((uint64_t)(pr * pr)) + ((uint64_t)(pa * pa)); + if (best_score > score) { + best_score = score; + best_index = i; + } + } + break; + } + } + + return (uint8_t)best_index; +} + +// -------- + +static inline uint32_t // +wuffs_base__composite_nonpremul_nonpremul_u32_axxx(uint32_t dst_nonpremul, + uint32_t src_nonpremul) { + // Extract 16-bit color components. + // + // If the destination is transparent then SRC_OVER is equivalent to SRC: just + // return src_nonpremul. This isn't just an optimization (skipping the rest + // of the function's computation). It also preserves the nonpremul + // distinction between e.g. transparent red and transparent blue that would + // otherwise be lost by converting from nonpremul to premul and back. + uint32_t da = 0x101 * (0xFF & (dst_nonpremul >> 24)); + if (da == 0) { + return src_nonpremul; + } + uint32_t dr = 0x101 * (0xFF & (dst_nonpremul >> 16)); + uint32_t dg = 0x101 * (0xFF & (dst_nonpremul >> 8)); + uint32_t db = 0x101 * (0xFF & (dst_nonpremul >> 0)); + uint32_t sa = 0x101 * (0xFF & (src_nonpremul >> 24)); + uint32_t sr = 0x101 * (0xFF & (src_nonpremul >> 16)); + uint32_t sg = 0x101 * (0xFF & (src_nonpremul >> 8)); + uint32_t sb = 0x101 * (0xFF & (src_nonpremul >> 0)); + + // Convert dst from nonpremul to premul. + dr = (dr * da) / 0xFFFF; + dg = (dg * da) / 0xFFFF; + db = (db * da) / 0xFFFF; + + // Calculate the inverse of the src-alpha: how much of the dst to keep. + uint32_t ia = 0xFFFF - sa; + + // Composite src (nonpremul) over dst (premul). + da = sa + ((da * ia) / 0xFFFF); + dr = ((sr * sa) + (dr * ia)) / 0xFFFF; + dg = ((sg * sa) + (dg * ia)) / 0xFFFF; + db = ((sb * sa) + (db * ia)) / 0xFFFF; + + // Convert dst from premul to nonpremul. + if (da != 0) { + dr = (dr * 0xFFFF) / da; + dg = (dg * 0xFFFF) / da; + db = (db * 0xFFFF) / da; + } + + // Convert from 16-bit color to 8-bit color. + da >>= 8; + dr >>= 8; + dg >>= 8; + db >>= 8; + + // Combine components. + return (db << 0) | (dg << 8) | (dr << 16) | (da << 24); +} + +static inline uint64_t // +wuffs_base__composite_nonpremul_nonpremul_u64_axxx(uint64_t dst_nonpremul, + uint64_t src_nonpremul) { + // Extract components. + // + // If the destination is transparent then SRC_OVER is equivalent to SRC: just + // return src_nonpremul. This isn't just an optimization (skipping the rest + // of the function's computation). It also preserves the nonpremul + // distinction between e.g. transparent red and transparent blue that would + // otherwise be lost by converting from nonpremul to premul and back. + uint64_t da = 0xFFFF & (dst_nonpremul >> 48); + if (da == 0) { + return src_nonpremul; + } + uint64_t dr = 0xFFFF & (dst_nonpremul >> 32); + uint64_t dg = 0xFFFF & (dst_nonpremul >> 16); + uint64_t db = 0xFFFF & (dst_nonpremul >> 0); + uint64_t sa = 0xFFFF & (src_nonpremul >> 48); + uint64_t sr = 0xFFFF & (src_nonpremul >> 32); + uint64_t sg = 0xFFFF & (src_nonpremul >> 16); + uint64_t sb = 0xFFFF & (src_nonpremul >> 0); + + // Convert dst from nonpremul to premul. + dr = (dr * da) / 0xFFFF; + dg = (dg * da) / 0xFFFF; + db = (db * da) / 0xFFFF; + + // Calculate the inverse of the src-alpha: how much of the dst to keep. + uint64_t ia = 0xFFFF - sa; + + // Composite src (nonpremul) over dst (premul). + da = sa + ((da * ia) / 0xFFFF); + dr = ((sr * sa) + (dr * ia)) / 0xFFFF; + dg = ((sg * sa) + (dg * ia)) / 0xFFFF; + db = ((sb * sa) + (db * ia)) / 0xFFFF; + + // Convert dst from premul to nonpremul. + if (da != 0) { + dr = (dr * 0xFFFF) / da; + dg = (dg * 0xFFFF) / da; + db = (db * 0xFFFF) / da; + } + + // Combine components. + return (db << 0) | (dg << 16) | (dr << 32) | (da << 48); +} + +static inline uint32_t // +wuffs_base__composite_nonpremul_premul_u32_axxx(uint32_t dst_nonpremul, + uint32_t src_premul) { + // Extract 16-bit color components. + uint32_t da = 0x101 * (0xFF & (dst_nonpremul >> 24)); + uint32_t dr = 0x101 * (0xFF & (dst_nonpremul >> 16)); + uint32_t dg = 0x101 * (0xFF & (dst_nonpremul >> 8)); + uint32_t db = 0x101 * (0xFF & (dst_nonpremul >> 0)); + uint32_t sa = 0x101 * (0xFF & (src_premul >> 24)); + uint32_t sr = 0x101 * (0xFF & (src_premul >> 16)); + uint32_t sg = 0x101 * (0xFF & (src_premul >> 8)); + uint32_t sb = 0x101 * (0xFF & (src_premul >> 0)); + + // Convert dst from nonpremul to premul. + dr = (dr * da) / 0xFFFF; + dg = (dg * da) / 0xFFFF; + db = (db * da) / 0xFFFF; + + // Calculate the inverse of the src-alpha: how much of the dst to keep. + uint32_t ia = 0xFFFF - sa; + + // Composite src (premul) over dst (premul). + da = sa + ((da * ia) / 0xFFFF); + dr = sr + ((dr * ia) / 0xFFFF); + dg = sg + ((dg * ia) / 0xFFFF); + db = sb + ((db * ia) / 0xFFFF); + + // Convert dst from premul to nonpremul. + if (da != 0) { + dr = (dr * 0xFFFF) / da; + dg = (dg * 0xFFFF) / da; + db = (db * 0xFFFF) / da; + } + + // Convert from 16-bit color to 8-bit color. + da >>= 8; + dr >>= 8; + dg >>= 8; + db >>= 8; + + // Combine components. + return (db << 0) | (dg << 8) | (dr << 16) | (da << 24); +} + +static inline uint64_t // +wuffs_base__composite_nonpremul_premul_u64_axxx(uint64_t dst_nonpremul, + uint64_t src_premul) { + // Extract components. + uint64_t da = 0xFFFF & (dst_nonpremul >> 48); + uint64_t dr = 0xFFFF & (dst_nonpremul >> 32); + uint64_t dg = 0xFFFF & (dst_nonpremul >> 16); + uint64_t db = 0xFFFF & (dst_nonpremul >> 0); + uint64_t sa = 0xFFFF & (src_premul >> 48); + uint64_t sr = 0xFFFF & (src_premul >> 32); + uint64_t sg = 0xFFFF & (src_premul >> 16); + uint64_t sb = 0xFFFF & (src_premul >> 0); + + // Convert dst from nonpremul to premul. + dr = (dr * da) / 0xFFFF; + dg = (dg * da) / 0xFFFF; + db = (db * da) / 0xFFFF; + + // Calculate the inverse of the src-alpha: how much of the dst to keep. + uint64_t ia = 0xFFFF - sa; + + // Composite src (premul) over dst (premul). + da = sa + ((da * ia) / 0xFFFF); + dr = sr + ((dr * ia) / 0xFFFF); + dg = sg + ((dg * ia) / 0xFFFF); + db = sb + ((db * ia) / 0xFFFF); + + // Convert dst from premul to nonpremul. + if (da != 0) { + dr = (dr * 0xFFFF) / da; + dg = (dg * 0xFFFF) / da; + db = (db * 0xFFFF) / da; + } + + // Combine components. + return (db << 0) | (dg << 16) | (dr << 32) | (da << 48); +} + +static inline uint32_t // +wuffs_base__composite_premul_nonpremul_u32_axxx(uint32_t dst_premul, + uint32_t src_nonpremul) { + // Extract 16-bit color components. + uint32_t da = 0x101 * (0xFF & (dst_premul >> 24)); + uint32_t dr = 0x101 * (0xFF & (dst_premul >> 16)); + uint32_t dg = 0x101 * (0xFF & (dst_premul >> 8)); + uint32_t db = 0x101 * (0xFF & (dst_premul >> 0)); + uint32_t sa = 0x101 * (0xFF & (src_nonpremul >> 24)); + uint32_t sr = 0x101 * (0xFF & (src_nonpremul >> 16)); + uint32_t sg = 0x101 * (0xFF & (src_nonpremul >> 8)); + uint32_t sb = 0x101 * (0xFF & (src_nonpremul >> 0)); + + // Calculate the inverse of the src-alpha: how much of the dst to keep. + uint32_t ia = 0xFFFF - sa; + + // Composite src (nonpremul) over dst (premul). + da = sa + ((da * ia) / 0xFFFF); + dr = ((sr * sa) + (dr * ia)) / 0xFFFF; + dg = ((sg * sa) + (dg * ia)) / 0xFFFF; + db = ((sb * sa) + (db * ia)) / 0xFFFF; + + // Convert from 16-bit color to 8-bit color. + da >>= 8; + dr >>= 8; + dg >>= 8; + db >>= 8; + + // Combine components. + return (db << 0) | (dg << 8) | (dr << 16) | (da << 24); +} + +static inline uint64_t // +wuffs_base__composite_premul_nonpremul_u64_axxx(uint64_t dst_premul, + uint64_t src_nonpremul) { + // Extract components. + uint64_t da = 0xFFFF & (dst_premul >> 48); + uint64_t dr = 0xFFFF & (dst_premul >> 32); + uint64_t dg = 0xFFFF & (dst_premul >> 16); + uint64_t db = 0xFFFF & (dst_premul >> 0); + uint64_t sa = 0xFFFF & (src_nonpremul >> 48); + uint64_t sr = 0xFFFF & (src_nonpremul >> 32); + uint64_t sg = 0xFFFF & (src_nonpremul >> 16); + uint64_t sb = 0xFFFF & (src_nonpremul >> 0); + + // Calculate the inverse of the src-alpha: how much of the dst to keep. + uint64_t ia = 0xFFFF - sa; + + // Composite src (nonpremul) over dst (premul). + da = sa + ((da * ia) / 0xFFFF); + dr = ((sr * sa) + (dr * ia)) / 0xFFFF; + dg = ((sg * sa) + (dg * ia)) / 0xFFFF; + db = ((sb * sa) + (db * ia)) / 0xFFFF; + + // Combine components. + return (db << 0) | (dg << 16) | (dr << 32) | (da << 48); +} + +static inline uint32_t // +wuffs_base__composite_premul_premul_u32_axxx(uint32_t dst_premul, + uint32_t src_premul) { + // Extract 16-bit color components. + uint32_t da = 0x101 * (0xFF & (dst_premul >> 24)); + uint32_t dr = 0x101 * (0xFF & (dst_premul >> 16)); + uint32_t dg = 0x101 * (0xFF & (dst_premul >> 8)); + uint32_t db = 0x101 * (0xFF & (dst_premul >> 0)); + uint32_t sa = 0x101 * (0xFF & (src_premul >> 24)); + uint32_t sr = 0x101 * (0xFF & (src_premul >> 16)); + uint32_t sg = 0x101 * (0xFF & (src_premul >> 8)); + uint32_t sb = 0x101 * (0xFF & (src_premul >> 0)); + + // Calculate the inverse of the src-alpha: how much of the dst to keep. + uint32_t ia = 0xFFFF - sa; + + // Composite src (premul) over dst (premul). + da = sa + ((da * ia) / 0xFFFF); + dr = sr + ((dr * ia) / 0xFFFF); + dg = sg + ((dg * ia) / 0xFFFF); + db = sb + ((db * ia) / 0xFFFF); + + // Convert from 16-bit color to 8-bit color. + da >>= 8; + dr >>= 8; + dg >>= 8; + db >>= 8; + + // Combine components. + return (db << 0) | (dg << 8) | (dr << 16) | (da << 24); +} + +// -------- + +static uint64_t // +wuffs_base__pixel_swizzler__squash_align4_bgr_565_8888(uint8_t* dst_ptr, + size_t dst_len, + const uint8_t* src_ptr, + size_t src_len, + bool nonpremul) { + size_t len = (dst_len < src_len ? dst_len : src_len) / 4; + uint8_t* d = dst_ptr; + const uint8_t* s = src_ptr; + + size_t n = len; + while (n--) { + uint32_t argb = wuffs_base__peek_u32le__no_bounds_check(s); + if (nonpremul) { + argb = + wuffs_base__color_u32_argb_nonpremul__as__color_u32_argb_premul(argb); + } + uint32_t b5 = 0x1F & (argb >> (8 - 5)); + uint32_t g6 = 0x3F & (argb >> (16 - 6)); + uint32_t r5 = 0x1F & (argb >> (24 - 5)); + uint32_t alpha = argb & 0xFF000000; + wuffs_base__poke_u32le__no_bounds_check( + d, alpha | (r5 << 11) | (g6 << 5) | (b5 << 0)); + s += 4; + d += 4; + } + return len; +} + +// -------- + +static uint64_t // +wuffs_base__pixel_swizzler__swap_rgb_bgr(uint8_t* dst_ptr, + size_t dst_len, + uint8_t* dst_palette_ptr, + size_t dst_palette_len, + const uint8_t* src_ptr, + size_t src_len) { + size_t len = (dst_len < src_len ? dst_len : src_len) / 3; + uint8_t* d = dst_ptr; + const uint8_t* s = src_ptr; + + size_t n = len; + while (n--) { + uint8_t s0 = s[0]; + uint8_t s1 = s[1]; + uint8_t s2 = s[2]; + d[0] = s2; + d[1] = s1; + d[2] = s0; + s += 3; + d += 3; + } + return len; +} + +// ‼ WUFFS MULTI-FILE SECTION +x86_sse42 +#if defined(WUFFS_BASE__CPU_ARCH__X86_FAMILY) +WUFFS_BASE__MAYBE_ATTRIBUTE_TARGET("pclmul,popcnt,sse4.2") +static uint64_t // +wuffs_base__pixel_swizzler__swap_rgbx_bgrx__sse42(uint8_t* dst_ptr, + size_t dst_len, + uint8_t* dst_palette_ptr, + size_t dst_palette_len, + const uint8_t* src_ptr, + size_t src_len) { + size_t len = (dst_len < src_len ? dst_len : src_len) / 4; + uint8_t* d = dst_ptr; + const uint8_t* s = src_ptr; + size_t n = len; + + __m128i shuffle = _mm_set_epi8(+0x0F, +0x0C, +0x0D, +0x0E, // + +0x0B, +0x08, +0x09, +0x0A, // + +0x07, +0x04, +0x05, +0x06, // + +0x03, +0x00, +0x01, +0x02); + + while (n >= 4) { + __m128i x; + x = _mm_lddqu_si128((const __m128i*)(const void*)s); + x = _mm_shuffle_epi8(x, shuffle); + _mm_storeu_si128((__m128i*)(void*)d, x); + + s += 4 * 4; + d += 4 * 4; + n -= 4; + } + + while (n--) { + uint8_t s0 = s[0]; + uint8_t s1 = s[1]; + uint8_t s2 = s[2]; + uint8_t s3 = s[3]; + d[0] = s2; + d[1] = s1; + d[2] = s0; + d[3] = s3; + s += 4; + d += 4; + } + return len; +} +#endif // defined(WUFFS_BASE__CPU_ARCH__X86_FAMILY) +// ‼ WUFFS MULTI-FILE SECTION -x86_sse42 + +static uint64_t // +wuffs_base__pixel_swizzler__swap_rgbx_bgrx(uint8_t* dst_ptr, + size_t dst_len, + uint8_t* dst_palette_ptr, + size_t dst_palette_len, + const uint8_t* src_ptr, + size_t src_len) { + size_t len = (dst_len < src_len ? dst_len : src_len) / 4; + uint8_t* d = dst_ptr; + const uint8_t* s = src_ptr; + + size_t n = len; + while (n--) { + uint8_t s0 = s[0]; + uint8_t s1 = s[1]; + uint8_t s2 = s[2]; + uint8_t s3 = s[3]; + d[0] = s2; + d[1] = s1; + d[2] = s0; + d[3] = s3; + s += 4; + d += 4; + } + return len; +} + +// -------- + +static uint64_t // +wuffs_base__pixel_swizzler__copy_1_1(uint8_t* dst_ptr, + size_t dst_len, + uint8_t* dst_palette_ptr, + size_t dst_palette_len, + const uint8_t* src_ptr, + size_t src_len) { + size_t len = (dst_len < src_len) ? dst_len : src_len; + if (len > 0) { + memmove(dst_ptr, src_ptr, len); + } + return len; +} + +static uint64_t // +wuffs_base__pixel_swizzler__copy_2_2(uint8_t* dst_ptr, + size_t dst_len, + uint8_t* dst_palette_ptr, + size_t dst_palette_len, + const uint8_t* src_ptr, + size_t src_len) { + size_t dst_len2 = dst_len / 2; + size_t src_len2 = src_len / 2; + size_t len = (dst_len2 < src_len2) ? dst_len2 : src_len2; + if (len > 0) { + memmove(dst_ptr, src_ptr, len * 2); + } + return len; +} + +static uint64_t // +wuffs_base__pixel_swizzler__copy_3_3(uint8_t* dst_ptr, + size_t dst_len, + uint8_t* dst_palette_ptr, + size_t dst_palette_len, + const uint8_t* src_ptr, + size_t src_len) { + size_t dst_len3 = dst_len / 3; + size_t src_len3 = src_len / 3; + size_t len = (dst_len3 < src_len3) ? dst_len3 : src_len3; + if (len > 0) { + memmove(dst_ptr, src_ptr, len * 3); + } + return len; +} + +static uint64_t // +wuffs_base__pixel_swizzler__copy_4_4(uint8_t* dst_ptr, + size_t dst_len, + uint8_t* dst_palette_ptr, + size_t dst_palette_len, + const uint8_t* src_ptr, + size_t src_len) { + size_t dst_len4 = dst_len / 4; + size_t src_len4 = src_len / 4; + size_t len = (dst_len4 < src_len4) ? dst_len4 : src_len4; + if (len > 0) { + memmove(dst_ptr, src_ptr, len * 4); + } + return len; +} + +static uint64_t // +wuffs_base__pixel_swizzler__copy_8_8(uint8_t* dst_ptr, + size_t dst_len, + uint8_t* dst_palette_ptr, + size_t dst_palette_len, + const uint8_t* src_ptr, + size_t src_len) { + size_t dst_len8 = dst_len / 8; + size_t src_len8 = src_len / 8; + size_t len = (dst_len8 < src_len8) ? dst_len8 : src_len8; + if (len > 0) { + memmove(dst_ptr, src_ptr, len * 8); + } + return len; +} + +// -------- + +static uint64_t // +wuffs_base__pixel_swizzler__bgr_565__bgr(uint8_t* dst_ptr, + size_t dst_len, + uint8_t* dst_palette_ptr, + size_t dst_palette_len, + const uint8_t* src_ptr, + size_t src_len) { + size_t dst_len2 = dst_len / 2; + size_t src_len3 = src_len / 3; + size_t len = (dst_len2 < src_len3) ? dst_len2 : src_len3; + uint8_t* d = dst_ptr; + const uint8_t* s = src_ptr; + size_t n = len; + + // TODO: unroll. + + while (n >= 1) { + uint32_t b5 = s[0] >> 3; + uint32_t g6 = s[1] >> 2; + uint32_t r5 = s[2] >> 3; + uint32_t rgb_565 = (r5 << 11) | (g6 << 5) | (b5 << 0); + wuffs_base__poke_u16le__no_bounds_check(d + (0 * 2), (uint16_t)rgb_565); + + s += 1 * 3; + d += 1 * 2; + n -= 1; + } + + return len; +} + +static uint64_t // +wuffs_base__pixel_swizzler__bgr_565__bgrx(uint8_t* dst_ptr, + size_t dst_len, + uint8_t* dst_palette_ptr, + size_t dst_palette_len, + const uint8_t* src_ptr, + size_t src_len) { + size_t dst_len2 = dst_len / 2; + size_t src_len4 = src_len / 4; + size_t len = (dst_len2 < src_len4) ? dst_len2 : src_len4; + uint8_t* d = dst_ptr; + const uint8_t* s = src_ptr; + size_t n = len; + + // TODO: unroll. + + while (n >= 1) { + uint32_t b5 = s[0] >> 3; + uint32_t g6 = s[1] >> 2; + uint32_t r5 = s[2] >> 3; + uint32_t rgb_565 = (r5 << 11) | (g6 << 5) | (b5 << 0); + wuffs_base__poke_u16le__no_bounds_check(d + (0 * 2), (uint16_t)rgb_565); + + s += 1 * 4; + d += 1 * 2; + n -= 1; + } + + return len; +} + +static uint64_t // +wuffs_base__pixel_swizzler__bgr_565__bgra_nonpremul__src( + uint8_t* dst_ptr, + size_t dst_len, + uint8_t* dst_palette_ptr, + size_t dst_palette_len, + const uint8_t* src_ptr, + size_t src_len) { + size_t dst_len2 = dst_len / 2; + size_t src_len4 = src_len / 4; + size_t len = (dst_len2 < src_len4) ? dst_len2 : src_len4; + uint8_t* d = dst_ptr; + const uint8_t* s = src_ptr; + size_t n = len; + + // TODO: unroll. + + while (n >= 1) { + wuffs_base__poke_u16le__no_bounds_check( + d + (0 * 2), + wuffs_base__color_u32_argb_premul__as__color_u16_rgb_565( + wuffs_base__color_u32_argb_nonpremul__as__color_u32_argb_premul( + wuffs_base__peek_u32le__no_bounds_check(s + (0 * 4))))); + + s += 1 * 4; + d += 1 * 2; + n -= 1; + } + + return len; +} + +static uint64_t // +wuffs_base__pixel_swizzler__bgr_565__bgra_nonpremul_4x16le__src( + uint8_t* dst_ptr, + size_t dst_len, + uint8_t* dst_palette_ptr, + size_t dst_palette_len, + const uint8_t* src_ptr, + size_t src_len) { + size_t dst_len2 = dst_len / 2; + size_t src_len8 = src_len / 8; + size_t len = (dst_len2 < src_len8) ? dst_len2 : src_len8; + uint8_t* d = dst_ptr; + const uint8_t* s = src_ptr; + size_t n = len; + + // TODO: unroll. + + while (n >= 1) { + wuffs_base__poke_u16le__no_bounds_check( + d + (0 * 2), + wuffs_base__color_u32_argb_premul__as__color_u16_rgb_565( + wuffs_base__color_u64_argb_nonpremul__as__color_u32_argb_premul( + wuffs_base__peek_u64le__no_bounds_check(s + (0 * 8))))); + + s += 1 * 8; + d += 1 * 2; + n -= 1; + } + + return len; +} + +static uint64_t // +wuffs_base__pixel_swizzler__bgr_565__bgra_nonpremul__src_over( + uint8_t* dst_ptr, + size_t dst_len, + uint8_t* dst_palette_ptr, + size_t dst_palette_len, + const uint8_t* src_ptr, + size_t src_len) { + size_t dst_len2 = dst_len / 2; + size_t src_len4 = src_len / 4; + size_t len = (dst_len2 < src_len4) ? dst_len2 : src_len4; + uint8_t* d = dst_ptr; + const uint8_t* s = src_ptr; + size_t n = len; + + // TODO: unroll. + + while (n >= 1) { + // Extract 16-bit color components. + uint32_t sa = 0x101 * ((uint32_t)s[3]); + uint32_t sr = 0x101 * ((uint32_t)s[2]); + uint32_t sg = 0x101 * ((uint32_t)s[1]); + uint32_t sb = 0x101 * ((uint32_t)s[0]); + + // Convert from 565 color to 16-bit color. + uint32_t old_rgb_565 = wuffs_base__peek_u16le__no_bounds_check(d + (0 * 2)); + uint32_t old_r5 = 0x1F & (old_rgb_565 >> 11); + uint32_t dr = (0x8421 * old_r5) >> 4; + uint32_t old_g6 = 0x3F & (old_rgb_565 >> 5); + uint32_t dg = (0x1041 * old_g6) >> 2; + uint32_t old_b5 = 0x1F & (old_rgb_565 >> 0); + uint32_t db = (0x8421 * old_b5) >> 4; + + // Calculate the inverse of the src-alpha: how much of the dst to keep. + uint32_t ia = 0xFFFF - sa; + + // Composite src (nonpremul) over dst (premul). + dr = ((sr * sa) + (dr * ia)) / 0xFFFF; + dg = ((sg * sa) + (dg * ia)) / 0xFFFF; + db = ((sb * sa) + (db * ia)) / 0xFFFF; + + // Convert from 16-bit color to 565 color and combine the components. + uint32_t new_r5 = 0x1F & (dr >> 11); + uint32_t new_g6 = 0x3F & (dg >> 10); + uint32_t new_b5 = 0x1F & (db >> 11); + uint32_t new_rgb_565 = (new_r5 << 11) | (new_g6 << 5) | (new_b5 << 0); + wuffs_base__poke_u16le__no_bounds_check(d + (0 * 2), (uint16_t)new_rgb_565); + + s += 1 * 4; + d += 1 * 2; + n -= 1; + } + + return len; +} + +static uint64_t // +wuffs_base__pixel_swizzler__bgr_565__bgra_nonpremul_4x16le__src_over( + uint8_t* dst_ptr, + size_t dst_len, + uint8_t* dst_palette_ptr, + size_t dst_palette_len, + const uint8_t* src_ptr, + size_t src_len) { + size_t dst_len2 = dst_len / 2; + size_t src_len8 = src_len / 8; + size_t len = (dst_len2 < src_len8) ? dst_len2 : src_len8; + uint8_t* d = dst_ptr; + const uint8_t* s = src_ptr; + size_t n = len; + + // TODO: unroll. + + while (n >= 1) { + // Extract 16-bit color components. + uint32_t sa = ((uint32_t)wuffs_base__peek_u16le__no_bounds_check(s + 6)); + uint32_t sr = ((uint32_t)wuffs_base__peek_u16le__no_bounds_check(s + 4)); + uint32_t sg = ((uint32_t)wuffs_base__peek_u16le__no_bounds_check(s + 2)); + uint32_t sb = ((uint32_t)wuffs_base__peek_u16le__no_bounds_check(s + 0)); + + // Convert from 565 color to 16-bit color. + uint32_t old_rgb_565 = wuffs_base__peek_u16le__no_bounds_check(d + (0 * 2)); + uint32_t old_r5 = 0x1F & (old_rgb_565 >> 11); + uint32_t dr = (0x8421 * old_r5) >> 4; + uint32_t old_g6 = 0x3F & (old_rgb_565 >> 5); + uint32_t dg = (0x1041 * old_g6) >> 2; + uint32_t old_b5 = 0x1F & (old_rgb_565 >> 0); + uint32_t db = (0x8421 * old_b5) >> 4; + + // Calculate the inverse of the src-alpha: how much of the dst to keep. + uint32_t ia = 0xFFFF - sa; + + // Composite src (nonpremul) over dst (premul). + dr = ((sr * sa) + (dr * ia)) / 0xFFFF; + dg = ((sg * sa) + (dg * ia)) / 0xFFFF; + db = ((sb * sa) + (db * ia)) / 0xFFFF; + + // Convert from 16-bit color to 565 color and combine the components. + uint32_t new_r5 = 0x1F & (dr >> 11); + uint32_t new_g6 = 0x3F & (dg >> 10); + uint32_t new_b5 = 0x1F & (db >> 11); + uint32_t new_rgb_565 = (new_r5 << 11) | (new_g6 << 5) | (new_b5 << 0); + wuffs_base__poke_u16le__no_bounds_check(d + (0 * 2), (uint16_t)new_rgb_565); + + s += 1 * 8; + d += 1 * 2; + n -= 1; + } + + return len; +} + +static uint64_t // +wuffs_base__pixel_swizzler__bgr_565__bgra_premul__src(uint8_t* dst_ptr, + size_t dst_len, + uint8_t* dst_palette_ptr, + size_t dst_palette_len, + const uint8_t* src_ptr, + size_t src_len) { + size_t dst_len2 = dst_len / 2; + size_t src_len4 = src_len / 4; + size_t len = (dst_len2 < src_len4) ? dst_len2 : src_len4; + uint8_t* d = dst_ptr; + const uint8_t* s = src_ptr; + size_t n = len; + + // TODO: unroll. + + while (n >= 1) { + wuffs_base__poke_u16le__no_bounds_check( + d + (0 * 2), wuffs_base__color_u32_argb_premul__as__color_u16_rgb_565( + wuffs_base__peek_u32le__no_bounds_check(s + (0 * 4)))); + + s += 1 * 4; + d += 1 * 2; + n -= 1; + } + + return len; +} + +static uint64_t // +wuffs_base__pixel_swizzler__bgr_565__bgra_premul__src_over( + uint8_t* dst_ptr, + size_t dst_len, + uint8_t* dst_palette_ptr, + size_t dst_palette_len, + const uint8_t* src_ptr, + size_t src_len) { + size_t dst_len2 = dst_len / 2; + size_t src_len4 = src_len / 4; + size_t len = (dst_len2 < src_len4) ? dst_len2 : src_len4; + uint8_t* d = dst_ptr; + const uint8_t* s = src_ptr; + size_t n = len; + + // TODO: unroll. + + while (n >= 1) { + // Extract 16-bit color components. + uint32_t sa = 0x101 * ((uint32_t)s[3]); + uint32_t sr = 0x101 * ((uint32_t)s[2]); + uint32_t sg = 0x101 * ((uint32_t)s[1]); + uint32_t sb = 0x101 * ((uint32_t)s[0]); + + // Convert from 565 color to 16-bit color. + uint32_t old_rgb_565 = wuffs_base__peek_u16le__no_bounds_check(d + (0 * 2)); + uint32_t old_r5 = 0x1F & (old_rgb_565 >> 11); + uint32_t dr = (0x8421 * old_r5) >> 4; + uint32_t old_g6 = 0x3F & (old_rgb_565 >> 5); + uint32_t dg = (0x1041 * old_g6) >> 2; + uint32_t old_b5 = 0x1F & (old_rgb_565 >> 0); + uint32_t db = (0x8421 * old_b5) >> 4; + + // Calculate the inverse of the src-alpha: how much of the dst to keep. + uint32_t ia = 0xFFFF - sa; + + // Composite src (premul) over dst (premul). + dr = sr + ((dr * ia) / 0xFFFF); + dg = sg + ((dg * ia) / 0xFFFF); + db = sb + ((db * ia) / 0xFFFF); + + // Convert from 16-bit color to 565 color and combine the components. + uint32_t new_r5 = 0x1F & (dr >> 11); + uint32_t new_g6 = 0x3F & (dg >> 10); + uint32_t new_b5 = 0x1F & (db >> 11); + uint32_t new_rgb_565 = (new_r5 << 11) | (new_g6 << 5) | (new_b5 << 0); + wuffs_base__poke_u16le__no_bounds_check(d + (0 * 2), (uint16_t)new_rgb_565); + + s += 1 * 4; + d += 1 * 2; + n -= 1; + } + + return len; +} + +static uint64_t // +wuffs_base__pixel_swizzler__bgr_565__rgb(uint8_t* dst_ptr, + size_t dst_len, + uint8_t* dst_palette_ptr, + size_t dst_palette_len, + const uint8_t* src_ptr, + size_t src_len) { + size_t dst_len2 = dst_len / 2; + size_t src_len3 = src_len / 3; + size_t len = (dst_len2 < src_len3) ? dst_len2 : src_len3; + uint8_t* d = dst_ptr; + const uint8_t* s = src_ptr; + size_t n = len; + + // TODO: unroll. + + while (n >= 1) { + uint32_t r5 = s[0] >> 3; + uint32_t g6 = s[1] >> 2; + uint32_t b5 = s[2] >> 3; + uint32_t rgb_565 = (r5 << 11) | (g6 << 5) | (b5 << 0); + wuffs_base__poke_u16le__no_bounds_check(d + (0 * 2), (uint16_t)rgb_565); + + s += 1 * 3; + d += 1 * 2; + n -= 1; + } + + return len; +} + +static uint64_t // +wuffs_base__pixel_swizzler__bgr_565__rgba_nonpremul__src( + uint8_t* dst_ptr, + size_t dst_len, + uint8_t* dst_palette_ptr, + size_t dst_palette_len, + const uint8_t* src_ptr, + size_t src_len) { + size_t dst_len2 = dst_len / 2; + size_t src_len4 = src_len / 4; + size_t len = (dst_len2 < src_len4) ? dst_len2 : src_len4; + uint8_t* d = dst_ptr; + const uint8_t* s = src_ptr; + size_t n = len; + + // TODO: unroll. + + while (n >= 1) { + wuffs_base__poke_u16le__no_bounds_check( + d + (0 * 2), + wuffs_base__color_u32_argb_premul__as__color_u16_rgb_565( + wuffs_base__swap_u32_argb_abgr( + wuffs_base__color_u32_argb_nonpremul__as__color_u32_argb_premul( + wuffs_base__peek_u32le__no_bounds_check(s + (0 * 4)))))); + + s += 1 * 4; + d += 1 * 2; + n -= 1; + } + + return len; +} + +static uint64_t // +wuffs_base__pixel_swizzler__bgr_565__rgba_nonpremul__src_over( + uint8_t* dst_ptr, + size_t dst_len, + uint8_t* dst_palette_ptr, + size_t dst_palette_len, + const uint8_t* src_ptr, + size_t src_len) { + size_t dst_len2 = dst_len / 2; + size_t src_len4 = src_len / 4; + size_t len = (dst_len2 < src_len4) ? dst_len2 : src_len4; + uint8_t* d = dst_ptr; + const uint8_t* s = src_ptr; + size_t n = len; + + // TODO: unroll. + + while (n >= 1) { + // Extract 16-bit color components. + uint32_t sa = 0x101 * ((uint32_t)s[3]); + uint32_t sb = 0x101 * ((uint32_t)s[2]); + uint32_t sg = 0x101 * ((uint32_t)s[1]); + uint32_t sr = 0x101 * ((uint32_t)s[0]); + + // Convert from 565 color to 16-bit color. + uint32_t old_rgb_565 = wuffs_base__peek_u16le__no_bounds_check(d + (0 * 2)); + uint32_t old_r5 = 0x1F & (old_rgb_565 >> 11); + uint32_t dr = (0x8421 * old_r5) >> 4; + uint32_t old_g6 = 0x3F & (old_rgb_565 >> 5); + uint32_t dg = (0x1041 * old_g6) >> 2; + uint32_t old_b5 = 0x1F & (old_rgb_565 >> 0); + uint32_t db = (0x8421 * old_b5) >> 4; + + // Calculate the inverse of the src-alpha: how much of the dst to keep. + uint32_t ia = 0xFFFF - sa; + + // Composite src (nonpremul) over dst (premul). + dr = ((sr * sa) + (dr * ia)) / 0xFFFF; + dg = ((sg * sa) + (dg * ia)) / 0xFFFF; + db = ((sb * sa) + (db * ia)) / 0xFFFF; + + // Convert from 16-bit color to 565 color and combine the components. + uint32_t new_r5 = 0x1F & (dr >> 11); + uint32_t new_g6 = 0x3F & (dg >> 10); + uint32_t new_b5 = 0x1F & (db >> 11); + uint32_t new_rgb_565 = (new_r5 << 11) | (new_g6 << 5) | (new_b5 << 0); + wuffs_base__poke_u16le__no_bounds_check(d + (0 * 2), (uint16_t)new_rgb_565); + + s += 1 * 4; + d += 1 * 2; + n -= 1; + } + + return len; +} + +static uint64_t // +wuffs_base__pixel_swizzler__bgr_565__rgba_premul__src(uint8_t* dst_ptr, + size_t dst_len, + uint8_t* dst_palette_ptr, + size_t dst_palette_len, + const uint8_t* src_ptr, + size_t src_len) { + size_t dst_len2 = dst_len / 2; + size_t src_len4 = src_len / 4; + size_t len = (dst_len2 < src_len4) ? dst_len2 : src_len4; + uint8_t* d = dst_ptr; + const uint8_t* s = src_ptr; + size_t n = len; + + // TODO: unroll. + + while (n >= 1) { + wuffs_base__poke_u16le__no_bounds_check( + d + (0 * 2), + wuffs_base__color_u32_argb_premul__as__color_u16_rgb_565( + wuffs_base__swap_u32_argb_abgr( + wuffs_base__peek_u32le__no_bounds_check(s + (0 * 4))))); + + s += 1 * 4; + d += 1 * 2; + n -= 1; + } + + return len; +} + +static uint64_t // +wuffs_base__pixel_swizzler__bgr_565__rgba_premul__src_over( + uint8_t* dst_ptr, + size_t dst_len, + uint8_t* dst_palette_ptr, + size_t dst_palette_len, + const uint8_t* src_ptr, + size_t src_len) { + size_t dst_len2 = dst_len / 2; + size_t src_len4 = src_len / 4; + size_t len = (dst_len2 < src_len4) ? dst_len2 : src_len4; + uint8_t* d = dst_ptr; + const uint8_t* s = src_ptr; + size_t n = len; + + // TODO: unroll. + + while (n >= 1) { + // Extract 16-bit color components. + uint32_t sa = 0x101 * ((uint32_t)s[3]); + uint32_t sb = 0x101 * ((uint32_t)s[2]); + uint32_t sg = 0x101 * ((uint32_t)s[1]); + uint32_t sr = 0x101 * ((uint32_t)s[0]); + + // Convert from 565 color to 16-bit color. + uint32_t old_rgb_565 = wuffs_base__peek_u16le__no_bounds_check(d + (0 * 2)); + uint32_t old_r5 = 0x1F & (old_rgb_565 >> 11); + uint32_t dr = (0x8421 * old_r5) >> 4; + uint32_t old_g6 = 0x3F & (old_rgb_565 >> 5); + uint32_t dg = (0x1041 * old_g6) >> 2; + uint32_t old_b5 = 0x1F & (old_rgb_565 >> 0); + uint32_t db = (0x8421 * old_b5) >> 4; + + // Calculate the inverse of the src-alpha: how much of the dst to keep. + uint32_t ia = 0xFFFF - sa; + + // Composite src (premul) over dst (premul). + dr = sr + ((dr * ia) / 0xFFFF); + dg = sg + ((dg * ia) / 0xFFFF); + db = sb + ((db * ia) / 0xFFFF); + + // Convert from 16-bit color to 565 color and combine the components. + uint32_t new_r5 = 0x1F & (dr >> 11); + uint32_t new_g6 = 0x3F & (dg >> 10); + uint32_t new_b5 = 0x1F & (db >> 11); + uint32_t new_rgb_565 = (new_r5 << 11) | (new_g6 << 5) | (new_b5 << 0); + wuffs_base__poke_u16le__no_bounds_check(d + (0 * 2), (uint16_t)new_rgb_565); + + s += 1 * 4; + d += 1 * 2; + n -= 1; + } + + return len; +} + +static uint64_t // +wuffs_base__pixel_swizzler__bgr_565__y(uint8_t* dst_ptr, + size_t dst_len, + uint8_t* dst_palette_ptr, + size_t dst_palette_len, + const uint8_t* src_ptr, + size_t src_len) { + size_t dst_len2 = dst_len / 2; + size_t len = (dst_len2 < src_len) ? dst_len2 : src_len; + uint8_t* d = dst_ptr; + const uint8_t* s = src_ptr; + size_t n = len; + + // TODO: unroll. + + while (n >= 1) { + uint32_t y5 = s[0] >> 3; + uint32_t y6 = s[0] >> 2; + uint32_t rgb_565 = (y5 << 11) | (y6 << 5) | (y5 << 0); + wuffs_base__poke_u16le__no_bounds_check(d + (0 * 2), (uint16_t)rgb_565); + + s += 1 * 1; + d += 1 * 2; + n -= 1; + } + + return len; +} + +static uint64_t // +wuffs_base__pixel_swizzler__bgr_565__y_16be(uint8_t* dst_ptr, + size_t dst_len, + uint8_t* dst_palette_ptr, + size_t dst_palette_len, + const uint8_t* src_ptr, + size_t src_len) { + size_t dst_len2 = dst_len / 2; + size_t src_len2 = src_len / 2; + size_t len = (dst_len2 < src_len2) ? dst_len2 : src_len2; + uint8_t* d = dst_ptr; + const uint8_t* s = src_ptr; + size_t n = len; + + // TODO: unroll. + + while (n >= 1) { + uint32_t y5 = s[0] >> 3; + uint32_t y6 = s[0] >> 2; + uint32_t rgb_565 = (y5 << 11) | (y6 << 5) | (y5 << 0); + wuffs_base__poke_u16le__no_bounds_check(d + (0 * 2), (uint16_t)rgb_565); + + s += 1 * 2; + d += 1 * 2; + n -= 1; + } + + return len; +} + +static uint64_t // +wuffs_base__pixel_swizzler__bgr_565__index__src(uint8_t* dst_ptr, + size_t dst_len, + uint8_t* dst_palette_ptr, + size_t dst_palette_len, + const uint8_t* src_ptr, + size_t src_len) { + if (dst_palette_len != + WUFFS_BASE__PIXEL_FORMAT__INDEXED__PALETTE_BYTE_LENGTH) { + return 0; + } + size_t dst_len2 = dst_len / 2; + size_t len = (dst_len2 < src_len) ? dst_len2 : src_len; + uint8_t* d = dst_ptr; + const uint8_t* s = src_ptr; + size_t n = len; + + const size_t loop_unroll_count = 4; + + while (n >= loop_unroll_count) { + wuffs_base__poke_u16le__no_bounds_check( + d + (0 * 2), wuffs_base__peek_u16le__no_bounds_check( + dst_palette_ptr + ((size_t)s[0] * 4))); + wuffs_base__poke_u16le__no_bounds_check( + d + (1 * 2), wuffs_base__peek_u16le__no_bounds_check( + dst_palette_ptr + ((size_t)s[1] * 4))); + wuffs_base__poke_u16le__no_bounds_check( + d + (2 * 2), wuffs_base__peek_u16le__no_bounds_check( + dst_palette_ptr + ((size_t)s[2] * 4))); + wuffs_base__poke_u16le__no_bounds_check( + d + (3 * 2), wuffs_base__peek_u16le__no_bounds_check( + dst_palette_ptr + ((size_t)s[3] * 4))); + + s += loop_unroll_count * 1; + d += loop_unroll_count * 2; + n -= loop_unroll_count; + } + + while (n >= 1) { + wuffs_base__poke_u16le__no_bounds_check( + d + (0 * 2), wuffs_base__peek_u16le__no_bounds_check( + dst_palette_ptr + ((size_t)s[0] * 4))); + + s += 1 * 1; + d += 1 * 2; + n -= 1; + } + + return len; +} + +static uint64_t // +wuffs_base__pixel_swizzler__bgr_565__index_bgra_nonpremul__src_over( + uint8_t* dst_ptr, + size_t dst_len, + uint8_t* dst_palette_ptr, + size_t dst_palette_len, + const uint8_t* src_ptr, + size_t src_len) { + if (dst_palette_len != + WUFFS_BASE__PIXEL_FORMAT__INDEXED__PALETTE_BYTE_LENGTH) { + return 0; + } + size_t dst_len2 = dst_len / 2; + size_t len = (dst_len2 < src_len) ? dst_len2 : src_len; + uint8_t* d = dst_ptr; + const uint8_t* s = src_ptr; + size_t n = len; + + // TODO: unroll. + + while (n >= 1) { + uint32_t d0 = wuffs_base__color_u16_rgb_565__as__color_u32_argb_premul( + wuffs_base__peek_u16le__no_bounds_check(d + (0 * 2))); + uint32_t s0 = wuffs_base__peek_u32le__no_bounds_check(dst_palette_ptr + + ((size_t)s[0] * 4)); + wuffs_base__poke_u16le__no_bounds_check( + d + (0 * 2), + wuffs_base__color_u32_argb_premul__as__color_u16_rgb_565( + wuffs_base__composite_premul_nonpremul_u32_axxx(d0, s0))); + + s += 1 * 1; + d += 1 * 2; + n -= 1; + } + + return len; +} + +static uint64_t // +wuffs_base__pixel_swizzler__bgr_565__index_binary_alpha__src_over( + uint8_t* dst_ptr, + size_t dst_len, + uint8_t* dst_palette_ptr, + size_t dst_palette_len, + const uint8_t* src_ptr, + size_t src_len) { + if (dst_palette_len != + WUFFS_BASE__PIXEL_FORMAT__INDEXED__PALETTE_BYTE_LENGTH) { + return 0; + } + size_t dst_len2 = dst_len / 2; + size_t len = (dst_len2 < src_len) ? dst_len2 : src_len; + uint8_t* d = dst_ptr; + const uint8_t* s = src_ptr; + size_t n = len; + + // TODO: unroll. + + while (n >= 1) { + uint32_t s0 = wuffs_base__peek_u32le__no_bounds_check(dst_palette_ptr + + ((size_t)s[0] * 4)); + if (s0) { + wuffs_base__poke_u16le__no_bounds_check(d + (0 * 2), (uint16_t)s0); + } + + s += 1 * 1; + d += 1 * 2; + n -= 1; + } + + return len; +} + +// -------- + +static uint64_t // +wuffs_base__pixel_swizzler__bgr__bgr_565(uint8_t* dst_ptr, + size_t dst_len, + uint8_t* dst_palette_ptr, + size_t dst_palette_len, + const uint8_t* src_ptr, + size_t src_len) { + size_t dst_len3 = dst_len / 3; + size_t src_len2 = src_len / 2; + size_t len = (dst_len3 < src_len2) ? dst_len3 : src_len2; + uint8_t* d = dst_ptr; + const uint8_t* s = src_ptr; + size_t n = len; + + // TODO: unroll. + + while (n >= 1) { + uint32_t s0 = wuffs_base__color_u16_rgb_565__as__color_u32_argb_premul( + wuffs_base__peek_u16le__no_bounds_check(s + (0 * 2))); + wuffs_base__poke_u24le__no_bounds_check(d + (0 * 3), s0); + + s += 1 * 2; + d += 1 * 3; + n -= 1; + } + + return len; +} + +static uint64_t // +wuffs_base__pixel_swizzler__bgr__bgra_nonpremul__src(uint8_t* dst_ptr, + size_t dst_len, + uint8_t* dst_palette_ptr, + size_t dst_palette_len, + const uint8_t* src_ptr, + size_t src_len) { + size_t dst_len3 = dst_len / 3; + size_t src_len4 = src_len / 4; + size_t len = (dst_len3 < src_len4) ? dst_len3 : src_len4; + uint8_t* d = dst_ptr; + const uint8_t* s = src_ptr; + size_t n = len; + + // TODO: unroll. + + while (n >= 1) { + uint32_t s0 = + wuffs_base__color_u32_argb_nonpremul__as__color_u32_argb_premul( + wuffs_base__peek_u32le__no_bounds_check(s + (0 * 4))); + wuffs_base__poke_u24le__no_bounds_check(d + (0 * 3), s0); + + s += 1 * 4; + d += 1 * 3; + n -= 1; + } + + return len; +} + +static uint64_t // +wuffs_base__pixel_swizzler__bgr__bgra_nonpremul_4x16le__src( + uint8_t* dst_ptr, + size_t dst_len, + uint8_t* dst_palette_ptr, + size_t dst_palette_len, + const uint8_t* src_ptr, + size_t src_len) { + size_t dst_len3 = dst_len / 3; + size_t src_len8 = src_len / 8; + size_t len = (dst_len3 < src_len8) ? dst_len3 : src_len8; + uint8_t* d = dst_ptr; + const uint8_t* s = src_ptr; + size_t n = len; + + // TODO: unroll. + + while (n >= 1) { + uint32_t s0 = + wuffs_base__color_u64_argb_nonpremul__as__color_u32_argb_premul( + wuffs_base__peek_u64le__no_bounds_check(s + (0 * 8))); + wuffs_base__poke_u24le__no_bounds_check(d + (0 * 3), s0); + + s += 1 * 8; + d += 1 * 3; + n -= 1; + } + + return len; +} + +static uint64_t // +wuffs_base__pixel_swizzler__bgr__bgra_nonpremul__src_over( + uint8_t* dst_ptr, + size_t dst_len, + uint8_t* dst_palette_ptr, + size_t dst_palette_len, + const uint8_t* src_ptr, + size_t src_len) { + size_t dst_len3 = dst_len / 3; + size_t src_len4 = src_len / 4; + size_t len = (dst_len3 < src_len4) ? dst_len3 : src_len4; + uint8_t* d = dst_ptr; + const uint8_t* s = src_ptr; + size_t n = len; + + // TODO: unroll. + + while (n >= 1) { + // Extract 16-bit color components. + uint32_t dr = 0x101 * ((uint32_t)d[2]); + uint32_t dg = 0x101 * ((uint32_t)d[1]); + uint32_t db = 0x101 * ((uint32_t)d[0]); + uint32_t sa = 0x101 * ((uint32_t)s[3]); + uint32_t sr = 0x101 * ((uint32_t)s[2]); + uint32_t sg = 0x101 * ((uint32_t)s[1]); + uint32_t sb = 0x101 * ((uint32_t)s[0]); + + // Calculate the inverse of the src-alpha: how much of the dst to keep. + uint32_t ia = 0xFFFF - sa; + + // Composite src (nonpremul) over dst (premul). + dr = ((sr * sa) + (dr * ia)) / 0xFFFF; + dg = ((sg * sa) + (dg * ia)) / 0xFFFF; + db = ((sb * sa) + (db * ia)) / 0xFFFF; + + // Convert from 16-bit color to 8-bit color. + d[0] = (uint8_t)(db >> 8); + d[1] = (uint8_t)(dg >> 8); + d[2] = (uint8_t)(dr >> 8); + + s += 1 * 4; + d += 1 * 3; + n -= 1; + } + + return len; +} + +static uint64_t // +wuffs_base__pixel_swizzler__bgr__bgra_nonpremul_4x16le__src_over( + uint8_t* dst_ptr, + size_t dst_len, + uint8_t* dst_palette_ptr, + size_t dst_palette_len, + const uint8_t* src_ptr, + size_t src_len) { + size_t dst_len3 = dst_len / 3; + size_t src_len8 = src_len / 8; + size_t len = (dst_len3 < src_len8) ? dst_len3 : src_len8; + uint8_t* d = dst_ptr; + const uint8_t* s = src_ptr; + size_t n = len; + + // TODO: unroll. + + while (n >= 1) { + // Extract 16-bit color components. + uint32_t dr = 0x101 * ((uint32_t)d[2]); + uint32_t dg = 0x101 * ((uint32_t)d[1]); + uint32_t db = 0x101 * ((uint32_t)d[0]); + uint32_t sa = ((uint32_t)wuffs_base__peek_u16le__no_bounds_check(s + 6)); + uint32_t sr = ((uint32_t)wuffs_base__peek_u16le__no_bounds_check(s + 4)); + uint32_t sg = ((uint32_t)wuffs_base__peek_u16le__no_bounds_check(s + 2)); + uint32_t sb = ((uint32_t)wuffs_base__peek_u16le__no_bounds_check(s + 0)); + + // Calculate the inverse of the src-alpha: how much of the dst to keep. + uint32_t ia = 0xFFFF - sa; + + // Composite src (nonpremul) over dst (premul). + dr = ((sr * sa) + (dr * ia)) / 0xFFFF; + dg = ((sg * sa) + (dg * ia)) / 0xFFFF; + db = ((sb * sa) + (db * ia)) / 0xFFFF; + + // Convert from 16-bit color to 8-bit color. + d[0] = (uint8_t)(db >> 8); + d[1] = (uint8_t)(dg >> 8); + d[2] = (uint8_t)(dr >> 8); + + s += 1 * 8; + d += 1 * 3; + n -= 1; + } + + return len; +} + +static uint64_t // +wuffs_base__pixel_swizzler__bgr__bgra_premul__src(uint8_t* dst_ptr, + size_t dst_len, + uint8_t* dst_palette_ptr, + size_t dst_palette_len, + const uint8_t* src_ptr, + size_t src_len) { + size_t dst_len3 = dst_len / 3; + size_t src_len4 = src_len / 4; + size_t len = (dst_len3 < src_len4) ? dst_len3 : src_len4; + uint8_t* d = dst_ptr; + const uint8_t* s = src_ptr; + size_t n = len; + + while (n >= 1) { + uint8_t s0 = s[0]; + uint8_t s1 = s[1]; + uint8_t s2 = s[2]; + d[0] = s0; + d[1] = s1; + d[2] = s2; + + s += 1 * 4; + d += 1 * 3; + n -= 1; + } + + return len; +} + +static uint64_t // +wuffs_base__pixel_swizzler__bgr__bgra_premul__src_over(uint8_t* dst_ptr, + size_t dst_len, + uint8_t* dst_palette_ptr, + size_t dst_palette_len, + const uint8_t* src_ptr, + size_t src_len) { + size_t dst_len3 = dst_len / 3; + size_t src_len4 = src_len / 4; + size_t len = (dst_len3 < src_len4) ? dst_len3 : src_len4; + uint8_t* d = dst_ptr; + const uint8_t* s = src_ptr; + size_t n = len; + + while (n >= 1) { + // Extract 16-bit color components. + uint32_t dr = 0x101 * ((uint32_t)d[2]); + uint32_t dg = 0x101 * ((uint32_t)d[1]); + uint32_t db = 0x101 * ((uint32_t)d[0]); + uint32_t sa = 0x101 * ((uint32_t)s[3]); + uint32_t sr = 0x101 * ((uint32_t)s[2]); + uint32_t sg = 0x101 * ((uint32_t)s[1]); + uint32_t sb = 0x101 * ((uint32_t)s[0]); + + // Calculate the inverse of the src-alpha: how much of the dst to keep. + uint32_t ia = 0xFFFF - sa; + + // Composite src (premul) over dst (premul). + dr = sr + ((dr * ia) / 0xFFFF); + dg = sg + ((dg * ia) / 0xFFFF); + db = sb + ((db * ia) / 0xFFFF); + + // Convert from 16-bit color to 8-bit color. + d[0] = (uint8_t)(db >> 8); + d[1] = (uint8_t)(dg >> 8); + d[2] = (uint8_t)(dr >> 8); + + s += 1 * 4; + d += 1 * 3; + n -= 1; + } + + return len; +} + +static uint64_t // +wuffs_base__pixel_swizzler__bgr__rgba_nonpremul__src(uint8_t* dst_ptr, + size_t dst_len, + uint8_t* dst_palette_ptr, + size_t dst_palette_len, + const uint8_t* src_ptr, + size_t src_len) { + size_t dst_len3 = dst_len / 3; + size_t src_len4 = src_len / 4; + size_t len = (dst_len3 < src_len4) ? dst_len3 : src_len4; + uint8_t* d = dst_ptr; + const uint8_t* s = src_ptr; + size_t n = len; + + // TODO: unroll. + + while (n >= 1) { + uint32_t s0 = wuffs_base__swap_u32_argb_abgr( + wuffs_base__color_u32_argb_nonpremul__as__color_u32_argb_premul( + wuffs_base__peek_u32le__no_bounds_check(s + (0 * 4)))); + wuffs_base__poke_u24le__no_bounds_check(d + (0 * 3), s0); + + s += 1 * 4; + d += 1 * 3; + n -= 1; + } + + return len; +} + +static uint64_t // +wuffs_base__pixel_swizzler__bgr__rgba_nonpremul__src_over( + uint8_t* dst_ptr, + size_t dst_len, + uint8_t* dst_palette_ptr, + size_t dst_palette_len, + const uint8_t* src_ptr, + size_t src_len) { + size_t dst_len3 = dst_len / 3; + size_t src_len4 = src_len / 4; + size_t len = (dst_len3 < src_len4) ? dst_len3 : src_len4; + uint8_t* d = dst_ptr; + const uint8_t* s = src_ptr; + size_t n = len; + + // TODO: unroll. + + while (n >= 1) { + // Extract 16-bit color components. + uint32_t dr = 0x101 * ((uint32_t)d[2]); + uint32_t dg = 0x101 * ((uint32_t)d[1]); + uint32_t db = 0x101 * ((uint32_t)d[0]); + uint32_t sa = 0x101 * ((uint32_t)s[3]); + uint32_t sb = 0x101 * ((uint32_t)s[2]); + uint32_t sg = 0x101 * ((uint32_t)s[1]); + uint32_t sr = 0x101 * ((uint32_t)s[0]); + + // Calculate the inverse of the src-alpha: how much of the dst to keep. + uint32_t ia = 0xFFFF - sa; + + // Composite src (nonpremul) over dst (premul). + dr = ((sr * sa) + (dr * ia)) / 0xFFFF; + dg = ((sg * sa) + (dg * ia)) / 0xFFFF; + db = ((sb * sa) + (db * ia)) / 0xFFFF; + + // Convert from 16-bit color to 8-bit color. + d[0] = (uint8_t)(db >> 8); + d[1] = (uint8_t)(dg >> 8); + d[2] = (uint8_t)(dr >> 8); + + s += 1 * 4; + d += 1 * 3; + n -= 1; + } + + return len; +} + +static uint64_t // +wuffs_base__pixel_swizzler__bgr__rgba_premul__src(uint8_t* dst_ptr, + size_t dst_len, + uint8_t* dst_palette_ptr, + size_t dst_palette_len, + const uint8_t* src_ptr, + size_t src_len) { + size_t dst_len3 = dst_len / 3; + size_t src_len4 = src_len / 4; + size_t len = (dst_len3 < src_len4) ? dst_len3 : src_len4; + uint8_t* d = dst_ptr; + const uint8_t* s = src_ptr; + size_t n = len; + + while (n >= 1) { + uint8_t s0 = s[0]; + uint8_t s1 = s[1]; + uint8_t s2 = s[2]; + d[0] = s2; + d[1] = s1; + d[2] = s0; + + s += 1 * 4; + d += 1 * 3; + n -= 1; + } + + return len; +} + +static uint64_t // +wuffs_base__pixel_swizzler__bgr__rgba_premul__src_over(uint8_t* dst_ptr, + size_t dst_len, + uint8_t* dst_palette_ptr, + size_t dst_palette_len, + const uint8_t* src_ptr, + size_t src_len) { + size_t dst_len3 = dst_len / 3; + size_t src_len4 = src_len / 4; + size_t len = (dst_len3 < src_len4) ? dst_len3 : src_len4; + uint8_t* d = dst_ptr; + const uint8_t* s = src_ptr; + size_t n = len; + + while (n >= 1) { + // Extract 16-bit color components. + uint32_t dr = 0x101 * ((uint32_t)d[2]); + uint32_t dg = 0x101 * ((uint32_t)d[1]); + uint32_t db = 0x101 * ((uint32_t)d[0]); + uint32_t sa = 0x101 * ((uint32_t)s[3]); + uint32_t sb = 0x101 * ((uint32_t)s[2]); + uint32_t sg = 0x101 * ((uint32_t)s[1]); + uint32_t sr = 0x101 * ((uint32_t)s[0]); + + // Calculate the inverse of the src-alpha: how much of the dst to keep. + uint32_t ia = 0xFFFF - sa; + + // Composite src (premul) over dst (premul). + dr = sr + ((dr * ia) / 0xFFFF); + dg = sg + ((dg * ia) / 0xFFFF); + db = sb + ((db * ia) / 0xFFFF); + + // Convert from 16-bit color to 8-bit color. + d[0] = (uint8_t)(db >> 8); + d[1] = (uint8_t)(dg >> 8); + d[2] = (uint8_t)(dr >> 8); + + s += 1 * 4; + d += 1 * 3; + n -= 1; + } + + return len; +} + +// -------- + +static uint64_t // +wuffs_base__pixel_swizzler__bgra_nonpremul__bgra_nonpremul__src_over( + uint8_t* dst_ptr, + size_t dst_len, + uint8_t* dst_palette_ptr, + size_t dst_palette_len, + const uint8_t* src_ptr, + size_t src_len) { + size_t dst_len4 = dst_len / 4; + size_t src_len4 = src_len / 4; + size_t len = (dst_len4 < src_len4) ? dst_len4 : src_len4; + uint8_t* d = dst_ptr; + const uint8_t* s = src_ptr; + size_t n = len; + + while (n >= 1) { + uint32_t d0 = wuffs_base__peek_u32le__no_bounds_check(d + (0 * 4)); + uint32_t s0 = wuffs_base__peek_u32le__no_bounds_check(s + (0 * 4)); + wuffs_base__poke_u32le__no_bounds_check( + d + (0 * 4), + wuffs_base__composite_nonpremul_nonpremul_u32_axxx(d0, s0)); + + s += 1 * 4; + d += 1 * 4; + n -= 1; + } + + return len; +} + +static uint64_t // +wuffs_base__pixel_swizzler__bgra_nonpremul__bgra_nonpremul_4x16le__src( + uint8_t* dst_ptr, + size_t dst_len, + uint8_t* dst_palette_ptr, + size_t dst_palette_len, + const uint8_t* src_ptr, + size_t src_len) { + size_t dst_len4 = dst_len / 4; + size_t src_len8 = src_len / 8; + size_t len = (dst_len4 < src_len8) ? dst_len4 : src_len8; + uint8_t* d = dst_ptr; + const uint8_t* s = src_ptr; + + size_t n = len; + while (n >= 1) { + wuffs_base__poke_u32le__no_bounds_check( + d + (0 * 4), wuffs_base__color_u64__as__color_u32( + wuffs_base__peek_u64le__no_bounds_check(s + (0 * 8)))); + + s += 1 * 8; + d += 1 * 4; + n -= 1; + } + return len; +} + +static uint64_t // +wuffs_base__pixel_swizzler__bgra_nonpremul__bgra_nonpremul_4x16le__src_over( + uint8_t* dst_ptr, + size_t dst_len, + uint8_t* dst_palette_ptr, + size_t dst_palette_len, + const uint8_t* src_ptr, + size_t src_len) { + size_t dst_len4 = dst_len / 4; + size_t src_len8 = src_len / 8; + size_t len = (dst_len4 < src_len8) ? dst_len4 : src_len8; + uint8_t* d = dst_ptr; + const uint8_t* s = src_ptr; + size_t n = len; + + while (n >= 1) { + uint64_t d0 = wuffs_base__color_u32__as__color_u64( + wuffs_base__peek_u32le__no_bounds_check(d + (0 * 4))); + uint64_t s0 = wuffs_base__peek_u64le__no_bounds_check(s + (0 * 8)); + wuffs_base__poke_u32le__no_bounds_check( + d + (0 * 4), + wuffs_base__color_u64__as__color_u32( + wuffs_base__composite_nonpremul_nonpremul_u64_axxx(d0, s0))); + + s += 1 * 8; + d += 1 * 4; + n -= 1; + } + + return len; +} + +static uint64_t // +wuffs_base__pixel_swizzler__bgra_nonpremul__bgra_premul__src( + uint8_t* dst_ptr, + size_t dst_len, + uint8_t* dst_palette_ptr, + size_t dst_palette_len, + const uint8_t* src_ptr, + size_t src_len) { + size_t dst_len4 = dst_len / 4; + size_t src_len4 = src_len / 4; + size_t len = (dst_len4 < src_len4) ? dst_len4 : src_len4; + uint8_t* d = dst_ptr; + const uint8_t* s = src_ptr; + size_t n = len; + + while (n >= 1) { + uint32_t s0 = wuffs_base__peek_u32le__no_bounds_check(s + (0 * 4)); + wuffs_base__poke_u32le__no_bounds_check( + d + (0 * 4), + wuffs_base__color_u32_argb_premul__as__color_u32_argb_nonpremul(s0)); + + s += 1 * 4; + d += 1 * 4; + n -= 1; + } + + return len; +} + +static uint64_t // +wuffs_base__pixel_swizzler__bgra_nonpremul__bgra_premul__src_over( + uint8_t* dst_ptr, + size_t dst_len, + uint8_t* dst_palette_ptr, + size_t dst_palette_len, + const uint8_t* src_ptr, + size_t src_len) { + size_t dst_len4 = dst_len / 4; + size_t src_len4 = src_len / 4; + size_t len = (dst_len4 < src_len4) ? dst_len4 : src_len4; + uint8_t* d = dst_ptr; + const uint8_t* s = src_ptr; + size_t n = len; + + while (n >= 1) { + uint32_t d0 = wuffs_base__peek_u32le__no_bounds_check(d + (0 * 4)); + uint32_t s0 = wuffs_base__peek_u32le__no_bounds_check(s + (0 * 4)); + wuffs_base__poke_u32le__no_bounds_check( + d + (0 * 4), wuffs_base__composite_nonpremul_premul_u32_axxx(d0, s0)); + + s += 1 * 4; + d += 1 * 4; + n -= 1; + } + + return len; +} + +static uint64_t // +wuffs_base__pixel_swizzler__bgra_nonpremul__index_bgra_nonpremul__src_over( + uint8_t* dst_ptr, + size_t dst_len, + uint8_t* dst_palette_ptr, + size_t dst_palette_len, + const uint8_t* src_ptr, + size_t src_len) { + if (dst_palette_len != + WUFFS_BASE__PIXEL_FORMAT__INDEXED__PALETTE_BYTE_LENGTH) { + return 0; + } + size_t dst_len4 = dst_len / 4; + size_t len = (dst_len4 < src_len) ? dst_len4 : src_len; + uint8_t* d = dst_ptr; + const uint8_t* s = src_ptr; + size_t n = len; + + // TODO: unroll. + + while (n >= 1) { + uint32_t d0 = wuffs_base__peek_u32le__no_bounds_check(d + (0 * 4)); + uint32_t s0 = wuffs_base__peek_u32le__no_bounds_check(dst_palette_ptr + + ((size_t)s[0] * 4)); + wuffs_base__poke_u32le__no_bounds_check( + d + (0 * 4), + wuffs_base__composite_nonpremul_nonpremul_u32_axxx(d0, s0)); + + s += 1 * 1; + d += 1 * 4; + n -= 1; + } + + return len; +} + +static uint64_t // +wuffs_base__pixel_swizzler__bgra_nonpremul__rgba_nonpremul__src_over( + uint8_t* dst_ptr, + size_t dst_len, + uint8_t* dst_palette_ptr, + size_t dst_palette_len, + const uint8_t* src_ptr, + size_t src_len) { + size_t dst_len4 = dst_len / 4; + size_t src_len4 = src_len / 4; + size_t len = (dst_len4 < src_len4) ? dst_len4 : src_len4; + uint8_t* d = dst_ptr; + const uint8_t* s = src_ptr; + size_t n = len; + + while (n >= 1) { + uint32_t d0 = wuffs_base__peek_u32le__no_bounds_check(d + (0 * 4)); + uint32_t s0 = wuffs_base__swap_u32_argb_abgr( + wuffs_base__peek_u32le__no_bounds_check(s + (0 * 4))); + wuffs_base__poke_u32le__no_bounds_check( + d + (0 * 4), + wuffs_base__composite_nonpremul_nonpremul_u32_axxx(d0, s0)); + + s += 1 * 4; + d += 1 * 4; + n -= 1; + } + + return len; +} + +static uint64_t // +wuffs_base__pixel_swizzler__bgra_nonpremul__rgba_premul__src( + uint8_t* dst_ptr, + size_t dst_len, + uint8_t* dst_palette_ptr, + size_t dst_palette_len, + const uint8_t* src_ptr, + size_t src_len) { + size_t dst_len4 = dst_len / 4; + size_t src_len4 = src_len / 4; + size_t len = (dst_len4 < src_len4) ? dst_len4 : src_len4; + uint8_t* d = dst_ptr; + const uint8_t* s = src_ptr; + size_t n = len; + + while (n >= 1) { + uint32_t s0 = wuffs_base__swap_u32_argb_abgr( + wuffs_base__peek_u32le__no_bounds_check(s + (0 * 4))); + wuffs_base__poke_u32le__no_bounds_check( + d + (0 * 4), + wuffs_base__color_u32_argb_premul__as__color_u32_argb_nonpremul(s0)); + + s += 1 * 4; + d += 1 * 4; + n -= 1; + } + + return len; +} + +static uint64_t // +wuffs_base__pixel_swizzler__bgra_nonpremul__rgba_premul__src_over( + uint8_t* dst_ptr, + size_t dst_len, + uint8_t* dst_palette_ptr, + size_t dst_palette_len, + const uint8_t* src_ptr, + size_t src_len) { + size_t dst_len4 = dst_len / 4; + size_t src_len4 = src_len / 4; + size_t len = (dst_len4 < src_len4) ? dst_len4 : src_len4; + uint8_t* d = dst_ptr; + const uint8_t* s = src_ptr; + size_t n = len; + + while (n >= 1) { + uint32_t d0 = wuffs_base__peek_u32le__no_bounds_check(d + (0 * 4)); + uint32_t s0 = wuffs_base__swap_u32_argb_abgr( + wuffs_base__peek_u32le__no_bounds_check(s + (0 * 4))); + wuffs_base__poke_u32le__no_bounds_check( + d + (0 * 4), wuffs_base__composite_nonpremul_premul_u32_axxx(d0, s0)); + + s += 1 * 4; + d += 1 * 4; + n -= 1; + } + + return len; +} + +// -------- + +static uint64_t // +wuffs_base__pixel_swizzler__bgra_nonpremul_4x16le__bgra_nonpremul__src( + uint8_t* dst_ptr, + size_t dst_len, + uint8_t* dst_palette_ptr, + size_t dst_palette_len, + const uint8_t* src_ptr, + size_t src_len) { + size_t dst_len8 = dst_len / 8; + size_t src_len4 = src_len / 4; + size_t len = (dst_len8 < src_len4) ? dst_len8 : src_len4; + uint8_t* d = dst_ptr; + const uint8_t* s = src_ptr; + + size_t n = len; + while (n >= 1) { + uint8_t s0 = s[0]; + uint8_t s1 = s[1]; + uint8_t s2 = s[2]; + uint8_t s3 = s[3]; + d[0] = s0; + d[1] = s0; + d[2] = s1; + d[3] = s1; + d[4] = s2; + d[5] = s2; + d[6] = s3; + d[7] = s3; + + s += 1 * 4; + d += 1 * 8; + n -= 1; + } + return len; +} + +static uint64_t // +wuffs_base__pixel_swizzler__bgra_nonpremul_4x16le__bgra_nonpremul__src_over( + uint8_t* dst_ptr, + size_t dst_len, + uint8_t* dst_palette_ptr, + size_t dst_palette_len, + const uint8_t* src_ptr, + size_t src_len) { + size_t dst_len8 = dst_len / 8; + size_t src_len4 = src_len / 4; + size_t len = (dst_len8 < src_len4) ? dst_len8 : src_len4; + uint8_t* d = dst_ptr; + const uint8_t* s = src_ptr; + + size_t n = len; + while (n >= 1) { + uint64_t d0 = wuffs_base__peek_u64le__no_bounds_check(d + (0 * 8)); + uint64_t s0 = wuffs_base__color_u32__as__color_u64( + wuffs_base__peek_u32le__no_bounds_check(s + (0 * 4))); + wuffs_base__poke_u64le__no_bounds_check( + d + (0 * 8), + wuffs_base__composite_nonpremul_nonpremul_u64_axxx(d0, s0)); + + s += 1 * 4; + d += 1 * 8; + n -= 1; + } + return len; +} + +static uint64_t // +wuffs_base__pixel_swizzler__bgra_nonpremul_4x16le__bgra_nonpremul_4x16le__src_over( + uint8_t* dst_ptr, + size_t dst_len, + uint8_t* dst_palette_ptr, + size_t dst_palette_len, + const uint8_t* src_ptr, + size_t src_len) { + size_t dst_len8 = dst_len / 8; + size_t src_len8 = src_len / 8; + size_t len = (dst_len8 < src_len8) ? dst_len8 : src_len8; + uint8_t* d = dst_ptr; + const uint8_t* s = src_ptr; + + size_t n = len; + while (n >= 1) { + uint64_t d0 = wuffs_base__peek_u64le__no_bounds_check(d + (0 * 8)); + uint64_t s0 = wuffs_base__peek_u64le__no_bounds_check(s + (0 * 8)); + wuffs_base__poke_u64le__no_bounds_check( + d + (0 * 8), + wuffs_base__composite_nonpremul_nonpremul_u64_axxx(d0, s0)); + + s += 1 * 8; + d += 1 * 8; + n -= 1; + } + return len; +} + +static uint64_t // +wuffs_base__pixel_swizzler__bgra_nonpremul_4x16le__bgra_premul__src( + uint8_t* dst_ptr, + size_t dst_len, + uint8_t* dst_palette_ptr, + size_t dst_palette_len, + const uint8_t* src_ptr, + size_t src_len) { + size_t dst_len8 = dst_len / 8; + size_t src_len4 = src_len / 4; + size_t len = (dst_len8 < src_len4) ? dst_len8 : src_len4; + uint8_t* d = dst_ptr; + const uint8_t* s = src_ptr; + + size_t n = len; + while (n >= 1) { + uint64_t s0 = wuffs_base__color_u32__as__color_u64( + wuffs_base__color_u32_argb_premul__as__color_u32_argb_nonpremul( + wuffs_base__peek_u32le__no_bounds_check(s + (0 * 4)))); + wuffs_base__poke_u64le__no_bounds_check(d + (0 * 8), s0); + + s += 1 * 4; + d += 1 * 8; + n -= 1; + } + return len; +} + +static uint64_t // +wuffs_base__pixel_swizzler__bgra_nonpremul_4x16le__bgra_premul__src_over( + uint8_t* dst_ptr, + size_t dst_len, + uint8_t* dst_palette_ptr, + size_t dst_palette_len, + const uint8_t* src_ptr, + size_t src_len) { + size_t dst_len8 = dst_len / 8; + size_t src_len4 = src_len / 4; + size_t len = (dst_len8 < src_len4) ? dst_len8 : src_len4; + uint8_t* d = dst_ptr; + const uint8_t* s = src_ptr; + + size_t n = len; + while (n >= 1) { + uint64_t d0 = wuffs_base__peek_u64le__no_bounds_check(d + (0 * 8)); + uint64_t s0 = wuffs_base__color_u32__as__color_u64( + wuffs_base__peek_u32le__no_bounds_check(s + (0 * 4))); + wuffs_base__poke_u64le__no_bounds_check( + d + (0 * 8), wuffs_base__composite_nonpremul_premul_u64_axxx(d0, s0)); + + s += 1 * 4; + d += 1 * 8; + n -= 1; + } + return len; +} + +static uint64_t // +wuffs_base__pixel_swizzler__bgra_nonpremul_4x16le__index_bgra_nonpremul__src_over( + uint8_t* dst_ptr, + size_t dst_len, + uint8_t* dst_palette_ptr, + size_t dst_palette_len, + const uint8_t* src_ptr, + size_t src_len) { + if (dst_palette_len != + WUFFS_BASE__PIXEL_FORMAT__INDEXED__PALETTE_BYTE_LENGTH) { + return 0; + } + size_t dst_len8 = dst_len / 8; + size_t len = (dst_len8 < src_len) ? dst_len8 : src_len; + uint8_t* d = dst_ptr; + const uint8_t* s = src_ptr; + size_t n = len; + + while (n >= 1) { + uint64_t d0 = wuffs_base__peek_u64le__no_bounds_check(d + (0 * 8)); + uint64_t s0 = wuffs_base__color_u32__as__color_u64( + wuffs_base__peek_u32le__no_bounds_check(dst_palette_ptr + + ((size_t)s[0] * 4))); + wuffs_base__poke_u64le__no_bounds_check( + d + (0 * 8), + wuffs_base__composite_nonpremul_nonpremul_u64_axxx(d0, s0)); + + s += 1 * 1; + d += 1 * 8; + n -= 1; + } + + return len; +} + +static uint64_t // +wuffs_base__pixel_swizzler__bgra_nonpremul_4x16le__rgba_nonpremul__src( + uint8_t* dst_ptr, + size_t dst_len, + uint8_t* dst_palette_ptr, + size_t dst_palette_len, + const uint8_t* src_ptr, + size_t src_len) { + size_t dst_len8 = dst_len / 8; + size_t src_len4 = src_len / 4; + size_t len = (dst_len8 < src_len4) ? dst_len8 : src_len4; + uint8_t* d = dst_ptr; + const uint8_t* s = src_ptr; + + size_t n = len; + while (n >= 1) { + uint8_t s0 = s[0]; + uint8_t s1 = s[1]; + uint8_t s2 = s[2]; + uint8_t s3 = s[3]; + d[0] = s2; + d[1] = s2; + d[2] = s1; + d[3] = s1; + d[4] = s0; + d[5] = s0; + d[6] = s3; + d[7] = s3; + + s += 1 * 4; + d += 1 * 8; + n -= 1; + } + return len; +} + +static uint64_t // +wuffs_base__pixel_swizzler__bgra_nonpremul_4x16le__rgba_nonpremul__src_over( + uint8_t* dst_ptr, + size_t dst_len, + uint8_t* dst_palette_ptr, + size_t dst_palette_len, + const uint8_t* src_ptr, + size_t src_len) { + size_t dst_len8 = dst_len / 8; + size_t src_len4 = src_len / 4; + size_t len = (dst_len8 < src_len4) ? dst_len8 : src_len4; + uint8_t* d = dst_ptr; + const uint8_t* s = src_ptr; + + size_t n = len; + while (n >= 1) { + uint64_t d0 = wuffs_base__peek_u64le__no_bounds_check(d + (0 * 8)); + uint64_t s0 = + wuffs_base__color_u32__as__color_u64(wuffs_base__swap_u32_argb_abgr( + wuffs_base__peek_u32le__no_bounds_check(s + (0 * 4)))); + wuffs_base__poke_u64le__no_bounds_check( + d + (0 * 8), + wuffs_base__composite_nonpremul_nonpremul_u64_axxx(d0, s0)); + + s += 1 * 4; + d += 1 * 8; + n -= 1; + } + return len; +} + +static uint64_t // +wuffs_base__pixel_swizzler__bgra_nonpremul_4x16le__rgba_premul__src( + uint8_t* dst_ptr, + size_t dst_len, + uint8_t* dst_palette_ptr, + size_t dst_palette_len, + const uint8_t* src_ptr, + size_t src_len) { + size_t dst_len8 = dst_len / 8; + size_t src_len4 = src_len / 4; + size_t len = (dst_len8 < src_len4) ? dst_len8 : src_len4; + uint8_t* d = dst_ptr; + const uint8_t* s = src_ptr; + + size_t n = len; + while (n >= 1) { + uint64_t s0 = wuffs_base__color_u32__as__color_u64( + wuffs_base__color_u32_argb_premul__as__color_u32_argb_nonpremul( + wuffs_base__swap_u32_argb_abgr( + wuffs_base__peek_u32le__no_bounds_check(s + (0 * 4))))); + wuffs_base__poke_u64le__no_bounds_check(d + (0 * 8), s0); + + s += 1 * 4; + d += 1 * 8; + n -= 1; + } + return len; +} + +static uint64_t // +wuffs_base__pixel_swizzler__bgra_nonpremul_4x16le__rgba_premul__src_over( + uint8_t* dst_ptr, + size_t dst_len, + uint8_t* dst_palette_ptr, + size_t dst_palette_len, + const uint8_t* src_ptr, + size_t src_len) { + size_t dst_len8 = dst_len / 8; + size_t src_len4 = src_len / 4; + size_t len = (dst_len8 < src_len4) ? dst_len8 : src_len4; + uint8_t* d = dst_ptr; + const uint8_t* s = src_ptr; + + size_t n = len; + while (n >= 1) { + uint64_t d0 = wuffs_base__peek_u64le__no_bounds_check(d + (0 * 8)); + uint64_t s0 = + wuffs_base__color_u32__as__color_u64(wuffs_base__swap_u32_argb_abgr( + wuffs_base__peek_u32le__no_bounds_check(s + (0 * 4)))); + wuffs_base__poke_u64le__no_bounds_check( + d + (0 * 8), wuffs_base__composite_nonpremul_premul_u64_axxx(d0, s0)); + + s += 1 * 4; + d += 1 * 8; + n -= 1; + } + return len; +} + +// -------- + +static uint64_t // +wuffs_base__pixel_swizzler__bgra_premul__bgra_nonpremul__src( + uint8_t* dst_ptr, + size_t dst_len, + uint8_t* dst_palette_ptr, + size_t dst_palette_len, + const uint8_t* src_ptr, + size_t src_len) { + size_t dst_len4 = dst_len / 4; + size_t src_len4 = src_len / 4; + size_t len = (dst_len4 < src_len4) ? dst_len4 : src_len4; + uint8_t* d = dst_ptr; + const uint8_t* s = src_ptr; + size_t n = len; + + // TODO: unroll. + + while (n >= 1) { + uint32_t s0 = wuffs_base__peek_u32le__no_bounds_check(s + (0 * 4)); + wuffs_base__poke_u32le__no_bounds_check( + d + (0 * 4), + wuffs_base__color_u32_argb_nonpremul__as__color_u32_argb_premul(s0)); + + s += 1 * 4; + d += 1 * 4; + n -= 1; + } + + return len; +} + +static uint64_t // +wuffs_base__pixel_swizzler__bgra_premul__bgra_nonpremul_4x16le__src( + uint8_t* dst_ptr, + size_t dst_len, + uint8_t* dst_palette_ptr, + size_t dst_palette_len, + const uint8_t* src_ptr, + size_t src_len) { + size_t dst_len4 = dst_len / 4; + size_t src_len8 = src_len / 8; + size_t len = (dst_len4 < src_len8) ? dst_len4 : src_len8; + uint8_t* d = dst_ptr; + const uint8_t* s = src_ptr; + size_t n = len; + + // TODO: unroll. + + while (n >= 1) { + uint64_t s0 = wuffs_base__peek_u64le__no_bounds_check(s + (0 * 8)); + wuffs_base__poke_u32le__no_bounds_check( + d + (0 * 4), + wuffs_base__color_u64_argb_nonpremul__as__color_u32_argb_premul(s0)); + + s += 1 * 8; + d += 1 * 4; + n -= 1; + } + + return len; +} + +static uint64_t // +wuffs_base__pixel_swizzler__bgra_premul__bgra_nonpremul__src_over( + uint8_t* dst_ptr, + size_t dst_len, + uint8_t* dst_palette_ptr, + size_t dst_palette_len, + const uint8_t* src_ptr, + size_t src_len) { + size_t dst_len4 = dst_len / 4; + size_t src_len4 = src_len / 4; + size_t len = (dst_len4 < src_len4) ? dst_len4 : src_len4; + uint8_t* d = dst_ptr; + const uint8_t* s = src_ptr; + size_t n = len; + + // TODO: unroll. + + while (n >= 1) { + uint32_t d0 = wuffs_base__peek_u32le__no_bounds_check(d + (0 * 4)); + uint32_t s0 = wuffs_base__peek_u32le__no_bounds_check(s + (0 * 4)); + wuffs_base__poke_u32le__no_bounds_check( + d + (0 * 4), wuffs_base__composite_premul_nonpremul_u32_axxx(d0, s0)); + + s += 1 * 4; + d += 1 * 4; + n -= 1; + } + + return len; +} + +static uint64_t // +wuffs_base__pixel_swizzler__bgra_premul__bgra_nonpremul_4x16le__src_over( + uint8_t* dst_ptr, + size_t dst_len, + uint8_t* dst_palette_ptr, + size_t dst_palette_len, + const uint8_t* src_ptr, + size_t src_len) { + size_t dst_len4 = dst_len / 4; + size_t src_len8 = src_len / 8; + size_t len = (dst_len4 < src_len8) ? dst_len4 : src_len8; + uint8_t* d = dst_ptr; + const uint8_t* s = src_ptr; + size_t n = len; + + // TODO: unroll. + + while (n >= 1) { + uint64_t d0 = wuffs_base__color_u32__as__color_u64( + wuffs_base__peek_u32le__no_bounds_check(d + (0 * 4))); + uint64_t s0 = wuffs_base__peek_u64le__no_bounds_check(s + (0 * 8)); + wuffs_base__poke_u32le__no_bounds_check( + d + (0 * 4), + wuffs_base__color_u64__as__color_u32( + wuffs_base__composite_premul_nonpremul_u64_axxx(d0, s0))); + + s += 1 * 8; + d += 1 * 4; + n -= 1; + } + + return len; +} + +static uint64_t // +wuffs_base__pixel_swizzler__bgra_premul__bgra_premul__src_over( + uint8_t* dst_ptr, + size_t dst_len, + uint8_t* dst_palette_ptr, + size_t dst_palette_len, + const uint8_t* src_ptr, + size_t src_len) { + size_t dst_len4 = dst_len / 4; + size_t src_len4 = src_len / 4; + size_t len = (dst_len4 < src_len4) ? dst_len4 : src_len4; + uint8_t* d = dst_ptr; + const uint8_t* s = src_ptr; + size_t n = len; + + // TODO: unroll. + + while (n >= 1) { + uint32_t d0 = wuffs_base__peek_u32le__no_bounds_check(d + (0 * 4)); + uint32_t s0 = wuffs_base__peek_u32le__no_bounds_check(s + (0 * 4)); + wuffs_base__poke_u32le__no_bounds_check( + d + (0 * 4), wuffs_base__composite_premul_premul_u32_axxx(d0, s0)); + + s += 1 * 4; + d += 1 * 4; + n -= 1; + } + + return len; +} + +static uint64_t // +wuffs_base__pixel_swizzler__bgra_premul__index_bgra_nonpremul__src_over( + uint8_t* dst_ptr, + size_t dst_len, + uint8_t* dst_palette_ptr, + size_t dst_palette_len, + const uint8_t* src_ptr, + size_t src_len) { + if (dst_palette_len != + WUFFS_BASE__PIXEL_FORMAT__INDEXED__PALETTE_BYTE_LENGTH) { + return 0; + } + size_t dst_len4 = dst_len / 4; + size_t len = (dst_len4 < src_len) ? dst_len4 : src_len; + uint8_t* d = dst_ptr; + const uint8_t* s = src_ptr; + size_t n = len; + + // TODO: unroll. + + while (n >= 1) { + uint32_t d0 = wuffs_base__peek_u32le__no_bounds_check(d + (0 * 4)); + uint32_t s0 = wuffs_base__peek_u32le__no_bounds_check(dst_palette_ptr + + ((size_t)s[0] * 4)); + wuffs_base__poke_u32le__no_bounds_check( + d + (0 * 4), wuffs_base__composite_premul_nonpremul_u32_axxx(d0, s0)); + + s += 1 * 1; + d += 1 * 4; + n -= 1; + } + + return len; +} + +static uint64_t // +wuffs_base__pixel_swizzler__bgra_premul__rgba_nonpremul__src( + uint8_t* dst_ptr, + size_t dst_len, + uint8_t* dst_palette_ptr, + size_t dst_palette_len, + const uint8_t* src_ptr, + size_t src_len) { + size_t dst_len4 = dst_len / 4; + size_t src_len4 = src_len / 4; + size_t len = (dst_len4 < src_len4) ? dst_len4 : src_len4; + uint8_t* d = dst_ptr; + const uint8_t* s = src_ptr; + size_t n = len; + + // TODO: unroll. + + while (n >= 1) { + uint32_t s0 = wuffs_base__swap_u32_argb_abgr( + wuffs_base__peek_u32le__no_bounds_check(s + (0 * 4))); + wuffs_base__poke_u32le__no_bounds_check( + d + (0 * 4), + wuffs_base__color_u32_argb_nonpremul__as__color_u32_argb_premul(s0)); + + s += 1 * 4; + d += 1 * 4; + n -= 1; + } + + return len; +} + +static uint64_t // +wuffs_base__pixel_swizzler__bgra_premul__rgba_nonpremul__src_over( + uint8_t* dst_ptr, + size_t dst_len, + uint8_t* dst_palette_ptr, + size_t dst_palette_len, + const uint8_t* src_ptr, + size_t src_len) { + size_t dst_len4 = dst_len / 4; + size_t src_len4 = src_len / 4; + size_t len = (dst_len4 < src_len4) ? dst_len4 : src_len4; + uint8_t* d = dst_ptr; + const uint8_t* s = src_ptr; + size_t n = len; + + // TODO: unroll. + + while (n >= 1) { + uint32_t d0 = wuffs_base__peek_u32le__no_bounds_check(d + (0 * 4)); + uint32_t s0 = wuffs_base__swap_u32_argb_abgr( + wuffs_base__peek_u32le__no_bounds_check(s + (0 * 4))); + wuffs_base__poke_u32le__no_bounds_check( + d + (0 * 4), wuffs_base__composite_premul_nonpremul_u32_axxx(d0, s0)); + + s += 1 * 4; + d += 1 * 4; + n -= 1; + } + + return len; +} + +static uint64_t // +wuffs_base__pixel_swizzler__bgra_premul__rgba_nonpremul_4x16le__src( + uint8_t* dst_ptr, + size_t dst_len, + uint8_t* dst_palette_ptr, + size_t dst_palette_len, + const uint8_t* src_ptr, + size_t src_len) { + size_t dst_len4 = dst_len / 4; + size_t src_len8 = src_len / 8; + size_t len = (dst_len4 < src_len8) ? dst_len4 : src_len8; + uint8_t* d = dst_ptr; + const uint8_t* s = src_ptr; + size_t n = len; + + // TODO: unroll. + + while (n >= 1) { + uint64_t s0 = wuffs_base__peek_u64le__no_bounds_check(s + (0 * 8)); + wuffs_base__poke_u32le__no_bounds_check( + d + (0 * 4), + wuffs_base__swap_u32_argb_abgr( + wuffs_base__color_u64_argb_nonpremul__as__color_u32_argb_premul( + s0))); + + s += 1 * 8; + d += 1 * 4; + n -= 1; + } + + return len; +} + +static uint64_t // +wuffs_base__pixel_swizzler__bgra_premul__rgba_nonpremul_4x16le__src_over( + uint8_t* dst_ptr, + size_t dst_len, + uint8_t* dst_palette_ptr, + size_t dst_palette_len, + const uint8_t* src_ptr, + size_t src_len) { + size_t dst_len4 = dst_len / 4; + size_t src_len8 = src_len / 8; + size_t len = (dst_len4 < src_len8) ? dst_len4 : src_len8; + uint8_t* d = dst_ptr; + const uint8_t* s = src_ptr; + size_t n = len; + + // TODO: unroll. + + while (n >= 1) { + uint64_t d0 = wuffs_base__color_u32__as__color_u64( + wuffs_base__peek_u32le__no_bounds_check(d + (0 * 4))); + uint64_t s0 = wuffs_base__swap_u64_argb_abgr( + wuffs_base__peek_u64le__no_bounds_check(s + (0 * 8))); + wuffs_base__poke_u32le__no_bounds_check( + d + (0 * 4), + wuffs_base__color_u64__as__color_u32( + wuffs_base__composite_premul_nonpremul_u64_axxx(d0, s0))); + + s += 1 * 8; + d += 1 * 4; + n -= 1; + } + + return len; +} + +static uint64_t // +wuffs_base__pixel_swizzler__bgra_premul__rgba_premul__src_over( + uint8_t* dst_ptr, + size_t dst_len, + uint8_t* dst_palette_ptr, + size_t dst_palette_len, + const uint8_t* src_ptr, + size_t src_len) { + size_t dst_len4 = dst_len / 4; + size_t src_len4 = src_len / 4; + size_t len = (dst_len4 < src_len4) ? dst_len4 : src_len4; + uint8_t* d = dst_ptr; + const uint8_t* s = src_ptr; + size_t n = len; + + while (n >= 1) { + uint32_t d0 = wuffs_base__peek_u32le__no_bounds_check(d + (0 * 4)); + uint32_t s0 = wuffs_base__swap_u32_argb_abgr( + wuffs_base__peek_u32le__no_bounds_check(s + (0 * 4))); + wuffs_base__poke_u32le__no_bounds_check( + d + (0 * 4), wuffs_base__composite_premul_premul_u32_axxx(d0, s0)); + + s += 1 * 4; + d += 1 * 4; + n -= 1; + } + + return len; +} + +// -------- + +static uint64_t // +wuffs_base__pixel_swizzler__bgrw__bgr(uint8_t* dst_ptr, + size_t dst_len, + uint8_t* dst_palette_ptr, + size_t dst_palette_len, + const uint8_t* src_ptr, + size_t src_len) { + size_t dst_len4 = dst_len / 4; + size_t src_len3 = src_len / 3; + size_t len = (dst_len4 < src_len3) ? dst_len4 : src_len3; + uint8_t* d = dst_ptr; + const uint8_t* s = src_ptr; + size_t n = len; + + // TODO: unroll. + + while (n >= 1) { + wuffs_base__poke_u32le__no_bounds_check( + d + (0 * 4), + 0xFF000000 | wuffs_base__peek_u24le__no_bounds_check(s + (0 * 3))); + + s += 1 * 3; + d += 1 * 4; + n -= 1; + } + + return len; +} + +static uint64_t // +wuffs_base__pixel_swizzler__bgrw__bgr_565(uint8_t* dst_ptr, + size_t dst_len, + uint8_t* dst_palette_ptr, + size_t dst_palette_len, + const uint8_t* src_ptr, + size_t src_len) { + size_t dst_len4 = dst_len / 4; + size_t src_len2 = src_len / 2; + size_t len = (dst_len4 < src_len2) ? dst_len4 : src_len2; + uint8_t* d = dst_ptr; + const uint8_t* s = src_ptr; + size_t n = len; + + // TODO: unroll. + + while (n >= 1) { + wuffs_base__poke_u32le__no_bounds_check( + d + (0 * 4), wuffs_base__color_u16_rgb_565__as__color_u32_argb_premul( + wuffs_base__peek_u16le__no_bounds_check(s + (0 * 2)))); + + s += 1 * 2; + d += 1 * 4; + n -= 1; + } + + return len; +} + +static uint64_t // +wuffs_base__pixel_swizzler__bgrw__bgrx(uint8_t* dst_ptr, + size_t dst_len, + uint8_t* dst_palette_ptr, + size_t dst_palette_len, + const uint8_t* src_ptr, + size_t src_len) { + size_t dst_len4 = dst_len / 4; + size_t src_len4 = src_len / 4; + size_t len = (dst_len4 < src_len4) ? dst_len4 : src_len4; + uint8_t* d = dst_ptr; + const uint8_t* s = src_ptr; + size_t n = len; + + // TODO: unroll. + + while (n >= 1) { + wuffs_base__poke_u32le__no_bounds_check( + d + (0 * 4), + 0xFF000000 | wuffs_base__peek_u32le__no_bounds_check(s + (0 * 4))); + + s += 1 * 4; + d += 1 * 4; + n -= 1; + } + + return len; +} + +// ‼ WUFFS MULTI-FILE SECTION +x86_sse42 +#if defined(WUFFS_BASE__CPU_ARCH__X86_FAMILY) +WUFFS_BASE__MAYBE_ATTRIBUTE_TARGET("pclmul,popcnt,sse4.2") +static uint64_t // +wuffs_base__pixel_swizzler__bgrw__rgb__sse42(uint8_t* dst_ptr, + size_t dst_len, + uint8_t* dst_palette_ptr, + size_t dst_palette_len, + const uint8_t* src_ptr, + size_t src_len) { + size_t dst_len4 = dst_len / 4; + size_t src_len3 = src_len / 3; + size_t len = (dst_len4 < src_len3) ? dst_len4 : src_len3; + uint8_t* d = dst_ptr; + const uint8_t* s = src_ptr; + size_t n = len; + + __m128i shuffle = _mm_set_epi8(+0x00, +0x09, +0x0A, +0x0B, // + +0x00, +0x06, +0x07, +0x08, // + +0x00, +0x03, +0x04, +0x05, // + +0x00, +0x00, +0x01, +0x02); + __m128i or_ff = _mm_set_epi8(-0x01, +0x00, +0x00, +0x00, // + -0x01, +0x00, +0x00, +0x00, // + -0x01, +0x00, +0x00, +0x00, // + -0x01, +0x00, +0x00, +0x00); + + while (n >= 6) { + __m128i x; + x = _mm_lddqu_si128((const __m128i*)(const void*)s); + x = _mm_shuffle_epi8(x, shuffle); + x = _mm_or_si128(x, or_ff); + _mm_storeu_si128((__m128i*)(void*)d, x); + + s += 4 * 3; + d += 4 * 4; + n -= 4; + } + + while (n >= 1) { + uint8_t b0 = s[0]; + uint8_t b1 = s[1]; + uint8_t b2 = s[2]; + d[0] = b2; + d[1] = b1; + d[2] = b0; + d[3] = 0xFF; + + s += 1 * 3; + d += 1 * 4; + n -= 1; + } + + return len; +} +#endif // defined(WUFFS_BASE__CPU_ARCH__X86_FAMILY) +// ‼ WUFFS MULTI-FILE SECTION -x86_sse42 + +static uint64_t // +wuffs_base__pixel_swizzler__bgrw__rgb(uint8_t* dst_ptr, + size_t dst_len, + uint8_t* dst_palette_ptr, + size_t dst_palette_len, + const uint8_t* src_ptr, + size_t src_len) { + size_t dst_len4 = dst_len / 4; + size_t src_len3 = src_len / 3; + size_t len = (dst_len4 < src_len3) ? dst_len4 : src_len3; + uint8_t* d = dst_ptr; + const uint8_t* s = src_ptr; + size_t n = len; + + while (n >= 1) { + uint8_t b0 = s[0]; + uint8_t b1 = s[1]; + uint8_t b2 = s[2]; + d[0] = b2; + d[1] = b1; + d[2] = b0; + d[3] = 0xFF; + + s += 1 * 3; + d += 1 * 4; + n -= 1; + } + + return len; +} + +static uint64_t // +wuffs_base__pixel_swizzler__bgrw__rgbx(uint8_t* dst_ptr, + size_t dst_len, + uint8_t* dst_palette_ptr, + size_t dst_palette_len, + const uint8_t* src_ptr, + size_t src_len) { + size_t dst_len4 = dst_len / 4; + size_t src_len4 = src_len / 4; + size_t len = (dst_len4 < src_len4) ? dst_len4 : src_len4; + uint8_t* d = dst_ptr; + const uint8_t* s = src_ptr; + size_t n = len; + + // TODO: unroll. + + while (n >= 1) { + uint8_t b0 = s[0]; + uint8_t b1 = s[1]; + uint8_t b2 = s[2]; + d[0] = b2; + d[1] = b1; + d[2] = b0; + d[3] = 0xFF; + + s += 1 * 4; + d += 1 * 4; + n -= 1; + } + + return len; +} + +// -------- + +static uint64_t // +wuffs_base__pixel_swizzler__bgrw_4x16le__bgr(uint8_t* dst_ptr, + size_t dst_len, + uint8_t* dst_palette_ptr, + size_t dst_palette_len, + const uint8_t* src_ptr, + size_t src_len) { + size_t dst_len8 = dst_len / 8; + size_t src_len3 = src_len / 3; + size_t len = (dst_len8 < src_len3) ? dst_len8 : src_len3; + uint8_t* d = dst_ptr; + const uint8_t* s = src_ptr; + size_t n = len; + + while (n >= 1) { + uint8_t s0 = s[0]; + uint8_t s1 = s[1]; + uint8_t s2 = s[2]; + d[0] = s0; + d[1] = s0; + d[2] = s1; + d[3] = s1; + d[4] = s2; + d[5] = s2; + d[6] = 0xFF; + d[7] = 0xFF; + + s += 1 * 3; + d += 1 * 8; + n -= 1; + } + + return len; +} + +static uint64_t // +wuffs_base__pixel_swizzler__bgrw_4x16le__bgr_565(uint8_t* dst_ptr, + size_t dst_len, + uint8_t* dst_palette_ptr, + size_t dst_palette_len, + const uint8_t* src_ptr, + size_t src_len) { + size_t dst_len8 = dst_len / 8; + size_t src_len2 = src_len / 2; + size_t len = (dst_len8 < src_len2) ? dst_len8 : src_len2; + uint8_t* d = dst_ptr; + const uint8_t* s = src_ptr; + size_t n = len; + + while (n >= 1) { + wuffs_base__poke_u64le__no_bounds_check( + d + (0 * 8), + wuffs_base__color_u32__as__color_u64( + wuffs_base__color_u16_rgb_565__as__color_u32_argb_premul( + wuffs_base__peek_u16le__no_bounds_check(s + (0 * 2))))); + + s += 1 * 2; + d += 1 * 8; + n -= 1; + } + + return len; +} + +static uint64_t // +wuffs_base__pixel_swizzler__bgrw_4x16le__bgrx(uint8_t* dst_ptr, + size_t dst_len, + uint8_t* dst_palette_ptr, + size_t dst_palette_len, + const uint8_t* src_ptr, + size_t src_len) { + size_t dst_len8 = dst_len / 8; + size_t src_len4 = src_len / 4; + size_t len = (dst_len8 < src_len4) ? dst_len8 : src_len4; + uint8_t* d = dst_ptr; + const uint8_t* s = src_ptr; + size_t n = len; + + while (n >= 1) { + uint8_t s0 = s[0]; + uint8_t s1 = s[1]; + uint8_t s2 = s[2]; + d[0] = s0; + d[1] = s0; + d[2] = s1; + d[3] = s1; + d[4] = s2; + d[5] = s2; + d[6] = 0xFF; + d[7] = 0xFF; + + s += 1 * 4; + d += 1 * 8; + n -= 1; + } + + return len; +} + +static uint64_t // +wuffs_base__pixel_swizzler__bgrw_4x16le__rgb(uint8_t* dst_ptr, + size_t dst_len, + uint8_t* dst_palette_ptr, + size_t dst_palette_len, + const uint8_t* src_ptr, + size_t src_len) { + size_t dst_len8 = dst_len / 8; + size_t src_len3 = src_len / 3; + size_t len = (dst_len8 < src_len3) ? dst_len8 : src_len3; + uint8_t* d = dst_ptr; + const uint8_t* s = src_ptr; + size_t n = len; + + while (n >= 1) { + uint8_t s0 = s[0]; + uint8_t s1 = s[1]; + uint8_t s2 = s[2]; + d[0] = s2; + d[1] = s2; + d[2] = s1; + d[3] = s1; + d[4] = s0; + d[5] = s0; + d[6] = 0xFF; + d[7] = 0xFF; + + s += 1 * 3; + d += 1 * 8; + n -= 1; + } + + return len; +} + +// -------- + +static uint64_t // +wuffs_base__pixel_swizzler__rgba_nonpremul__bgra_nonpremul_4x16le__src( + uint8_t* dst_ptr, + size_t dst_len, + uint8_t* dst_palette_ptr, + size_t dst_palette_len, + const uint8_t* src_ptr, + size_t src_len) { + size_t dst_len4 = dst_len / 4; + size_t src_len8 = src_len / 8; + size_t len = (dst_len4 < src_len8) ? dst_len4 : src_len8; + uint8_t* d = dst_ptr; + const uint8_t* s = src_ptr; + + size_t n = len; + while (n >= 1) { + wuffs_base__poke_u32le__no_bounds_check( + d + (0 * 4), wuffs_base__color_u64__as__color_u32__swap_u32_argb_abgr( + wuffs_base__peek_u64le__no_bounds_check(s + (0 * 8)))); + + s += 1 * 8; + d += 1 * 4; + n -= 1; + } + return len; +} + +static uint64_t // +wuffs_base__pixel_swizzler__rgba_nonpremul__bgra_nonpremul_4x16le__src_over( + uint8_t* dst_ptr, + size_t dst_len, + uint8_t* dst_palette_ptr, + size_t dst_palette_len, + const uint8_t* src_ptr, + size_t src_len) { + size_t dst_len4 = dst_len / 4; + size_t src_len8 = src_len / 8; + size_t len = (dst_len4 < src_len8) ? dst_len4 : src_len8; + uint8_t* d = dst_ptr; + const uint8_t* s = src_ptr; + size_t n = len; + + while (n >= 1) { + uint64_t d0 = wuffs_base__color_u32__as__color_u64( + wuffs_base__peek_u32le__no_bounds_check(d + (0 * 4))); + uint64_t s0 = wuffs_base__swap_u64_argb_abgr( + wuffs_base__peek_u64le__no_bounds_check(s + (0 * 8))); + wuffs_base__poke_u32le__no_bounds_check( + d + (0 * 4), + wuffs_base__color_u64__as__color_u32( + wuffs_base__composite_nonpremul_nonpremul_u64_axxx(d0, s0))); + + s += 1 * 8; + d += 1 * 4; + n -= 1; + } + + return len; +} + +// -------- + +static uint64_t // +wuffs_base__pixel_swizzler__rgbw__bgr_565(uint8_t* dst_ptr, + size_t dst_len, + uint8_t* dst_palette_ptr, + size_t dst_palette_len, + const uint8_t* src_ptr, + size_t src_len) { + size_t dst_len4 = dst_len / 4; + size_t src_len2 = src_len / 2; + size_t len = (dst_len4 < src_len2) ? dst_len4 : src_len2; + uint8_t* d = dst_ptr; + const uint8_t* s = src_ptr; + size_t n = len; + + // TODO: unroll. + + while (n >= 1) { + wuffs_base__poke_u32le__no_bounds_check( + d + (0 * 4), + wuffs_base__swap_u32_argb_abgr( + wuffs_base__color_u16_rgb_565__as__color_u32_argb_premul( + wuffs_base__peek_u16le__no_bounds_check(s + (0 * 2))))); + + s += 1 * 2; + d += 1 * 4; + n -= 1; + } + + return len; +} + +// -------- + +static uint64_t // +wuffs_base__pixel_swizzler__xxx__index__src(uint8_t* dst_ptr, + size_t dst_len, + uint8_t* dst_palette_ptr, + size_t dst_palette_len, + const uint8_t* src_ptr, + size_t src_len) { + if (dst_palette_len != + WUFFS_BASE__PIXEL_FORMAT__INDEXED__PALETTE_BYTE_LENGTH) { + return 0; + } + size_t dst_len3 = dst_len / 3; + size_t len = (dst_len3 < src_len) ? dst_len3 : src_len; + uint8_t* d = dst_ptr; + const uint8_t* s = src_ptr; + size_t n = len; + + const size_t loop_unroll_count = 4; + + // The comparison in the while condition is ">", not ">=", because with + // ">=", the last 4-byte store could write past the end of the dst slice. + // + // Each 4-byte store writes one too many bytes, but a subsequent store + // will overwrite that with the correct byte. There is always another + // store, whether a 4-byte store in this loop or a 1-byte store in the + // next loop. + while (n > loop_unroll_count) { + wuffs_base__poke_u32le__no_bounds_check( + d + (0 * 3), wuffs_base__peek_u32le__no_bounds_check( + dst_palette_ptr + ((size_t)s[0] * 4))); + wuffs_base__poke_u32le__no_bounds_check( + d + (1 * 3), wuffs_base__peek_u32le__no_bounds_check( + dst_palette_ptr + ((size_t)s[1] * 4))); + wuffs_base__poke_u32le__no_bounds_check( + d + (2 * 3), wuffs_base__peek_u32le__no_bounds_check( + dst_palette_ptr + ((size_t)s[2] * 4))); + wuffs_base__poke_u32le__no_bounds_check( + d + (3 * 3), wuffs_base__peek_u32le__no_bounds_check( + dst_palette_ptr + ((size_t)s[3] * 4))); + + s += loop_unroll_count * 1; + d += loop_unroll_count * 3; + n -= loop_unroll_count; + } + + while (n >= 1) { + uint32_t s0 = wuffs_base__peek_u32le__no_bounds_check(dst_palette_ptr + + ((size_t)s[0] * 4)); + wuffs_base__poke_u24le__no_bounds_check(d + (0 * 3), s0); + + s += 1 * 1; + d += 1 * 3; + n -= 1; + } + + return len; +} + +static uint64_t // +wuffs_base__pixel_swizzler__xxx__index_bgra_nonpremul__src_over( + uint8_t* dst_ptr, + size_t dst_len, + uint8_t* dst_palette_ptr, + size_t dst_palette_len, + const uint8_t* src_ptr, + size_t src_len) { + if (dst_palette_len != + WUFFS_BASE__PIXEL_FORMAT__INDEXED__PALETTE_BYTE_LENGTH) { + return 0; + } + size_t dst_len3 = dst_len / 3; + size_t len = (dst_len3 < src_len) ? dst_len3 : src_len; + uint8_t* d = dst_ptr; + const uint8_t* s = src_ptr; + size_t n = len; + + // TODO: unroll. + + while (n >= 1) { + uint32_t d0 = + wuffs_base__peek_u24le__no_bounds_check(d + (0 * 3)) | 0xFF000000; + uint32_t s0 = wuffs_base__peek_u32le__no_bounds_check(dst_palette_ptr + + ((size_t)s[0] * 4)); + wuffs_base__poke_u24le__no_bounds_check( + d + (0 * 3), wuffs_base__composite_premul_nonpremul_u32_axxx(d0, s0)); + + s += 1 * 1; + d += 1 * 3; + n -= 1; + } + + return len; +} + +static uint64_t // +wuffs_base__pixel_swizzler__xxx__index_binary_alpha__src_over( + uint8_t* dst_ptr, + size_t dst_len, + uint8_t* dst_palette_ptr, + size_t dst_palette_len, + const uint8_t* src_ptr, + size_t src_len) { + if (dst_palette_len != + WUFFS_BASE__PIXEL_FORMAT__INDEXED__PALETTE_BYTE_LENGTH) { + return 0; + } + size_t dst_len3 = dst_len / 3; + size_t len = (dst_len3 < src_len) ? dst_len3 : src_len; + uint8_t* d = dst_ptr; + const uint8_t* s = src_ptr; + size_t n = len; + + const size_t loop_unroll_count = 4; + + while (n >= loop_unroll_count) { + uint32_t s0 = wuffs_base__peek_u32le__no_bounds_check(dst_palette_ptr + + ((size_t)s[0] * 4)); + if (s0) { + wuffs_base__poke_u24le__no_bounds_check(d + (0 * 3), s0); + } + uint32_t s1 = wuffs_base__peek_u32le__no_bounds_check(dst_palette_ptr + + ((size_t)s[1] * 4)); + if (s1) { + wuffs_base__poke_u24le__no_bounds_check(d + (1 * 3), s1); + } + uint32_t s2 = wuffs_base__peek_u32le__no_bounds_check(dst_palette_ptr + + ((size_t)s[2] * 4)); + if (s2) { + wuffs_base__poke_u24le__no_bounds_check(d + (2 * 3), s2); + } + uint32_t s3 = wuffs_base__peek_u32le__no_bounds_check(dst_palette_ptr + + ((size_t)s[3] * 4)); + if (s3) { + wuffs_base__poke_u24le__no_bounds_check(d + (3 * 3), s3); + } + + s += loop_unroll_count * 1; + d += loop_unroll_count * 3; + n -= loop_unroll_count; + } + + while (n >= 1) { + uint32_t s0 = wuffs_base__peek_u32le__no_bounds_check(dst_palette_ptr + + ((size_t)s[0] * 4)); + if (s0) { + wuffs_base__poke_u24le__no_bounds_check(d + (0 * 3), s0); + } + + s += 1 * 1; + d += 1 * 3; + n -= 1; + } + + return len; +} + +static uint64_t // +wuffs_base__pixel_swizzler__xxx__xxxx(uint8_t* dst_ptr, + size_t dst_len, + uint8_t* dst_palette_ptr, + size_t dst_palette_len, + const uint8_t* src_ptr, + size_t src_len) { + size_t dst_len3 = dst_len / 3; + size_t src_len4 = src_len / 4; + size_t len = (dst_len3 < src_len4) ? dst_len3 : src_len4; + uint8_t* d = dst_ptr; + const uint8_t* s = src_ptr; + size_t n = len; + + // TODO: unroll. + + while (n >= 1) { + wuffs_base__poke_u24le__no_bounds_check( + d + (0 * 3), wuffs_base__peek_u32le__no_bounds_check(s + (0 * 4))); + + s += 1 * 4; + d += 1 * 3; + n -= 1; + } + + return len; +} + +static uint64_t // +wuffs_base__pixel_swizzler__xxx__y(uint8_t* dst_ptr, + size_t dst_len, + uint8_t* dst_palette_ptr, + size_t dst_palette_len, + const uint8_t* src_ptr, + size_t src_len) { + size_t dst_len3 = dst_len / 3; + size_t len = (dst_len3 < src_len) ? dst_len3 : src_len; + uint8_t* d = dst_ptr; + const uint8_t* s = src_ptr; + size_t n = len; + + // TODO: unroll. + + while (n >= 1) { + uint8_t s0 = s[0]; + d[0] = s0; + d[1] = s0; + d[2] = s0; + + s += 1 * 1; + d += 1 * 3; + n -= 1; + } + + return len; +} + +static uint64_t // +wuffs_base__pixel_swizzler__xxx__y_16be(uint8_t* dst_ptr, + size_t dst_len, + uint8_t* dst_palette_ptr, + size_t dst_palette_len, + const uint8_t* src_ptr, + size_t src_len) { + size_t dst_len3 = dst_len / 3; + size_t src_len2 = src_len / 2; + size_t len = (dst_len3 < src_len2) ? dst_len3 : src_len2; + uint8_t* d = dst_ptr; + const uint8_t* s = src_ptr; + size_t n = len; + + // TODO: unroll. + + while (n >= 1) { + uint8_t s0 = s[0]; + d[0] = s0; + d[1] = s0; + d[2] = s0; + + s += 1 * 2; + d += 1 * 3; + n -= 1; + } + + return len; +} + +// -------- + +static uint64_t // +wuffs_base__pixel_swizzler__xxxx__index__src(uint8_t* dst_ptr, + size_t dst_len, + uint8_t* dst_palette_ptr, + size_t dst_palette_len, + const uint8_t* src_ptr, + size_t src_len) { + if (dst_palette_len != + WUFFS_BASE__PIXEL_FORMAT__INDEXED__PALETTE_BYTE_LENGTH) { + return 0; + } + size_t dst_len4 = dst_len / 4; + size_t len = (dst_len4 < src_len) ? dst_len4 : src_len; + uint8_t* d = dst_ptr; + const uint8_t* s = src_ptr; + size_t n = len; + + const size_t loop_unroll_count = 4; + + while (n >= loop_unroll_count) { + wuffs_base__poke_u32le__no_bounds_check( + d + (0 * 4), wuffs_base__peek_u32le__no_bounds_check( + dst_palette_ptr + ((size_t)s[0] * 4))); + wuffs_base__poke_u32le__no_bounds_check( + d + (1 * 4), wuffs_base__peek_u32le__no_bounds_check( + dst_palette_ptr + ((size_t)s[1] * 4))); + wuffs_base__poke_u32le__no_bounds_check( + d + (2 * 4), wuffs_base__peek_u32le__no_bounds_check( + dst_palette_ptr + ((size_t)s[2] * 4))); + wuffs_base__poke_u32le__no_bounds_check( + d + (3 * 4), wuffs_base__peek_u32le__no_bounds_check( + dst_palette_ptr + ((size_t)s[3] * 4))); + + s += loop_unroll_count * 1; + d += loop_unroll_count * 4; + n -= loop_unroll_count; + } + + while (n >= 1) { + wuffs_base__poke_u32le__no_bounds_check( + d + (0 * 4), wuffs_base__peek_u32le__no_bounds_check( + dst_palette_ptr + ((size_t)s[0] * 4))); + + s += 1 * 1; + d += 1 * 4; + n -= 1; + } + + return len; +} + +static uint64_t // +wuffs_base__pixel_swizzler__xxxx__index_binary_alpha__src_over( + uint8_t* dst_ptr, + size_t dst_len, + uint8_t* dst_palette_ptr, + size_t dst_palette_len, + const uint8_t* src_ptr, + size_t src_len) { + if (dst_palette_len != + WUFFS_BASE__PIXEL_FORMAT__INDEXED__PALETTE_BYTE_LENGTH) { + return 0; + } + size_t dst_len4 = dst_len / 4; + size_t len = (dst_len4 < src_len) ? dst_len4 : src_len; + uint8_t* d = dst_ptr; + const uint8_t* s = src_ptr; + size_t n = len; + + const size_t loop_unroll_count = 4; + + while (n >= loop_unroll_count) { + uint32_t s0 = wuffs_base__peek_u32le__no_bounds_check(dst_palette_ptr + + ((size_t)s[0] * 4)); + if (s0) { + wuffs_base__poke_u32le__no_bounds_check(d + (0 * 4), s0); + } + uint32_t s1 = wuffs_base__peek_u32le__no_bounds_check(dst_palette_ptr + + ((size_t)s[1] * 4)); + if (s1) { + wuffs_base__poke_u32le__no_bounds_check(d + (1 * 4), s1); + } + uint32_t s2 = wuffs_base__peek_u32le__no_bounds_check(dst_palette_ptr + + ((size_t)s[2] * 4)); + if (s2) { + wuffs_base__poke_u32le__no_bounds_check(d + (2 * 4), s2); + } + uint32_t s3 = wuffs_base__peek_u32le__no_bounds_check(dst_palette_ptr + + ((size_t)s[3] * 4)); + if (s3) { + wuffs_base__poke_u32le__no_bounds_check(d + (3 * 4), s3); + } + + s += loop_unroll_count * 1; + d += loop_unroll_count * 4; + n -= loop_unroll_count; + } + + while (n >= 1) { + uint32_t s0 = wuffs_base__peek_u32le__no_bounds_check(dst_palette_ptr + + ((size_t)s[0] * 4)); + if (s0) { + wuffs_base__poke_u32le__no_bounds_check(d + (0 * 4), s0); + } + + s += 1 * 1; + d += 1 * 4; + n -= 1; + } + + return len; +} + +// ‼ WUFFS MULTI-FILE SECTION +x86_sse42 +#if defined(WUFFS_BASE__CPU_ARCH__X86_FAMILY) +WUFFS_BASE__MAYBE_ATTRIBUTE_TARGET("pclmul,popcnt,sse4.2") +static uint64_t // +wuffs_base__pixel_swizzler__xxxx__y__sse42(uint8_t* dst_ptr, + size_t dst_len, + uint8_t* dst_palette_ptr, + size_t dst_palette_len, + const uint8_t* src_ptr, + size_t src_len) { + size_t dst_len4 = dst_len / 4; + size_t len = (dst_len4 < src_len) ? dst_len4 : src_len; + uint8_t* d = dst_ptr; + const uint8_t* s = src_ptr; + size_t n = len; + + __m128i shuffle = _mm_set_epi8(+0x03, +0x03, +0x03, +0x03, // + +0x02, +0x02, +0x02, +0x02, // + +0x01, +0x01, +0x01, +0x01, // + +0x00, +0x00, +0x00, +0x00); + __m128i or_ff = _mm_set_epi8(-0x01, +0x00, +0x00, +0x00, // + -0x01, +0x00, +0x00, +0x00, // + -0x01, +0x00, +0x00, +0x00, // + -0x01, +0x00, +0x00, +0x00); + + while (n >= 4) { + __m128i x; + x = _mm_cvtsi32_si128((int)(wuffs_base__peek_u32le__no_bounds_check(s))); + x = _mm_shuffle_epi8(x, shuffle); + x = _mm_or_si128(x, or_ff); + _mm_storeu_si128((__m128i*)(void*)d, x); + + s += 4 * 1; + d += 4 * 4; + n -= 4; + } + + while (n >= 1) { + wuffs_base__poke_u32le__no_bounds_check( + d + (0 * 4), 0xFF000000 | (0x010101 * (uint32_t)s[0])); + + s += 1 * 1; + d += 1 * 4; + n -= 1; + } + + return len; +} +#endif // defined(WUFFS_BASE__CPU_ARCH__X86_FAMILY) +// ‼ WUFFS MULTI-FILE SECTION -x86_sse42 + +static uint64_t // +wuffs_base__pixel_swizzler__xxxx__y(uint8_t* dst_ptr, + size_t dst_len, + uint8_t* dst_palette_ptr, + size_t dst_palette_len, + const uint8_t* src_ptr, + size_t src_len) { + size_t dst_len4 = dst_len / 4; + size_t len = (dst_len4 < src_len) ? dst_len4 : src_len; + uint8_t* d = dst_ptr; + const uint8_t* s = src_ptr; + size_t n = len; + + while (n >= 1) { + wuffs_base__poke_u32le__no_bounds_check( + d + (0 * 4), 0xFF000000 | (0x010101 * (uint32_t)s[0])); + + s += 1 * 1; + d += 1 * 4; + n -= 1; + } + + return len; +} + +static uint64_t // +wuffs_base__pixel_swizzler__xxxx__y_16be(uint8_t* dst_ptr, + size_t dst_len, + uint8_t* dst_palette_ptr, + size_t dst_palette_len, + const uint8_t* src_ptr, + size_t src_len) { + size_t dst_len4 = dst_len / 4; + size_t src_len2 = src_len / 2; + size_t len = (dst_len4 < src_len2) ? dst_len4 : src_len2; + uint8_t* d = dst_ptr; + const uint8_t* s = src_ptr; + size_t n = len; + + while (n >= 1) { + wuffs_base__poke_u32le__no_bounds_check( + d + (0 * 4), 0xFF000000 | (0x010101 * (uint32_t)s[0])); + + s += 1 * 2; + d += 1 * 4; + n -= 1; + } + + return len; +} + +// -------- + +static uint64_t // +wuffs_base__pixel_swizzler__xxxxxxxx__index__src(uint8_t* dst_ptr, + size_t dst_len, + uint8_t* dst_palette_ptr, + size_t dst_palette_len, + const uint8_t* src_ptr, + size_t src_len) { + if (dst_palette_len != + WUFFS_BASE__PIXEL_FORMAT__INDEXED__PALETTE_BYTE_LENGTH) { + return 0; + } + size_t dst_len8 = dst_len / 8; + size_t len = (dst_len8 < src_len) ? dst_len8 : src_len; + uint8_t* d = dst_ptr; + const uint8_t* s = src_ptr; + size_t n = len; + + while (n >= 1) { + wuffs_base__poke_u64le__no_bounds_check( + d + (0 * 8), wuffs_base__color_u32__as__color_u64( + wuffs_base__peek_u32le__no_bounds_check( + dst_palette_ptr + ((size_t)s[0] * 4)))); + + s += 1 * 1; + d += 1 * 8; + n -= 1; + } + + return len; +} + +static uint64_t // +wuffs_base__pixel_swizzler__xxxxxxxx__index_binary_alpha__src_over( + uint8_t* dst_ptr, + size_t dst_len, + uint8_t* dst_palette_ptr, + size_t dst_palette_len, + const uint8_t* src_ptr, + size_t src_len) { + if (dst_palette_len != + WUFFS_BASE__PIXEL_FORMAT__INDEXED__PALETTE_BYTE_LENGTH) { + return 0; + } + size_t dst_len8 = dst_len / 8; + size_t len = (dst_len8 < src_len) ? dst_len8 : src_len; + uint8_t* d = dst_ptr; + const uint8_t* s = src_ptr; + size_t n = len; + + while (n >= 1) { + uint32_t s0 = wuffs_base__peek_u32le__no_bounds_check(dst_palette_ptr + + ((size_t)s[0] * 4)); + if (s0) { + wuffs_base__poke_u64le__no_bounds_check( + d + (0 * 8), wuffs_base__color_u32__as__color_u64(s0)); + } + + s += 1 * 1; + d += 1 * 8; + n -= 1; + } + + return len; +} + +static uint64_t // +wuffs_base__pixel_swizzler__xxxxxxxx__y(uint8_t* dst_ptr, + size_t dst_len, + uint8_t* dst_palette_ptr, + size_t dst_palette_len, + const uint8_t* src_ptr, + size_t src_len) { + size_t dst_len8 = dst_len / 8; + size_t len = (dst_len8 < src_len) ? dst_len8 : src_len; + uint8_t* d = dst_ptr; + const uint8_t* s = src_ptr; + size_t n = len; + + while (n >= 1) { + wuffs_base__poke_u64le__no_bounds_check( + d + (0 * 8), 0xFFFF000000000000 | (0x010101010101 * (uint64_t)s[0])); + + s += 1 * 1; + d += 1 * 8; + n -= 1; + } + + return len; +} + +static uint64_t // +wuffs_base__pixel_swizzler__xxxxxxxx__y_16be(uint8_t* dst_ptr, + size_t dst_len, + uint8_t* dst_palette_ptr, + size_t dst_palette_len, + const uint8_t* src_ptr, + size_t src_len) { + size_t dst_len8 = dst_len / 8; + size_t src_len2 = src_len / 2; + size_t len = (dst_len8 < src_len2) ? dst_len8 : src_len2; + uint8_t* d = dst_ptr; + const uint8_t* s = src_ptr; + size_t n = len; + + while (n >= 1) { + uint64_t s0 = + ((uint64_t)(wuffs_base__peek_u16be__no_bounds_check(s + (0 * 2)))); + wuffs_base__poke_u64le__no_bounds_check( + d + (0 * 8), 0xFFFF000000000000 | (0x000100010001 * s0)); + + s += 1 * 2; + d += 1 * 8; + n -= 1; + } + + return len; +} + +// -------- + +static uint64_t // +wuffs_base__pixel_swizzler__y__y_16be(uint8_t* dst_ptr, + size_t dst_len, + uint8_t* dst_palette_ptr, + size_t dst_palette_len, + const uint8_t* src_ptr, + size_t src_len) { + size_t src_len2 = src_len / 2; + size_t len = (dst_len < src_len2) ? dst_len : src_len2; + uint8_t* d = dst_ptr; + const uint8_t* s = src_ptr; + size_t n = len; + + while (n >= 1) { + d[0] = s[0]; + + s += 1 * 2; + d += 1 * 1; + n -= 1; + } + + return len; +} + +static uint64_t // +wuffs_base__pixel_swizzler__y_16le__y_16be(uint8_t* dst_ptr, + size_t dst_len, + uint8_t* dst_palette_ptr, + size_t dst_palette_len, + const uint8_t* src_ptr, + size_t src_len) { + size_t dst_len2 = dst_len / 2; + size_t src_len2 = src_len / 2; + size_t len = (dst_len2 < src_len2) ? dst_len2 : src_len2; + uint8_t* d = dst_ptr; + const uint8_t* s = src_ptr; + size_t n = len; + + while (n >= 1) { + uint8_t s0 = s[0]; + uint8_t s1 = s[1]; + d[0] = s1; + d[1] = s0; + + s += 1 * 2; + d += 1 * 2; + n -= 1; + } + + return len; +} + +// -------- + +static uint64_t // +wuffs_base__pixel_swizzler__transparent_black_src( + uint8_t* dst_ptr, + size_t dst_len, + uint8_t* dst_palette_ptr, + size_t dst_palette_len, + uint64_t num_pixels, + uint32_t dst_pixfmt_bytes_per_pixel) { + uint64_t n = ((uint64_t)dst_len) / dst_pixfmt_bytes_per_pixel; + if (n > num_pixels) { + n = num_pixels; + } + memset(dst_ptr, 0, ((size_t)(n * dst_pixfmt_bytes_per_pixel))); + return n; +} + +static uint64_t // +wuffs_base__pixel_swizzler__transparent_black_src_over( + uint8_t* dst_ptr, + size_t dst_len, + uint8_t* dst_palette_ptr, + size_t dst_palette_len, + uint64_t num_pixels, + uint32_t dst_pixfmt_bytes_per_pixel) { + uint64_t n = ((uint64_t)dst_len) / dst_pixfmt_bytes_per_pixel; + if (n > num_pixels) { + n = num_pixels; + } + return n; +} + +// -------- + +static wuffs_base__pixel_swizzler__func // +wuffs_base__pixel_swizzler__prepare__y(wuffs_base__pixel_swizzler* p, + wuffs_base__pixel_format dst_pixfmt, + wuffs_base__slice_u8 dst_palette, + wuffs_base__slice_u8 src_palette, + wuffs_base__pixel_blend blend) { + switch (dst_pixfmt.repr) { + case WUFFS_BASE__PIXEL_FORMAT__Y: + return wuffs_base__pixel_swizzler__copy_1_1; + + case WUFFS_BASE__PIXEL_FORMAT__BGR_565: + return wuffs_base__pixel_swizzler__bgr_565__y; + + case WUFFS_BASE__PIXEL_FORMAT__BGR: + case WUFFS_BASE__PIXEL_FORMAT__RGB: + return wuffs_base__pixel_swizzler__xxx__y; + + case WUFFS_BASE__PIXEL_FORMAT__BGRA_NONPREMUL: + case WUFFS_BASE__PIXEL_FORMAT__BGRA_PREMUL: + case WUFFS_BASE__PIXEL_FORMAT__BGRA_BINARY: + case WUFFS_BASE__PIXEL_FORMAT__BGRX: + case WUFFS_BASE__PIXEL_FORMAT__RGBA_NONPREMUL: + case WUFFS_BASE__PIXEL_FORMAT__RGBA_PREMUL: + case WUFFS_BASE__PIXEL_FORMAT__RGBA_BINARY: + case WUFFS_BASE__PIXEL_FORMAT__RGBX: +#if defined(WUFFS_BASE__CPU_ARCH__X86_FAMILY) + if (wuffs_base__cpu_arch__have_x86_sse42()) { + return wuffs_base__pixel_swizzler__xxxx__y__sse42; + } +#endif + return wuffs_base__pixel_swizzler__xxxx__y; + + case WUFFS_BASE__PIXEL_FORMAT__BGRA_NONPREMUL_4X16LE: + case WUFFS_BASE__PIXEL_FORMAT__BGRA_PREMUL_4X16LE: + case WUFFS_BASE__PIXEL_FORMAT__RGBA_NONPREMUL_4X16LE: + case WUFFS_BASE__PIXEL_FORMAT__RGBA_PREMUL_4X16LE: + return wuffs_base__pixel_swizzler__xxxxxxxx__y; + } + return NULL; +} + +static wuffs_base__pixel_swizzler__func // +wuffs_base__pixel_swizzler__prepare__y_16be(wuffs_base__pixel_swizzler* p, + wuffs_base__pixel_format dst_pixfmt, + wuffs_base__slice_u8 dst_palette, + wuffs_base__slice_u8 src_palette, + wuffs_base__pixel_blend blend) { + switch (dst_pixfmt.repr) { + case WUFFS_BASE__PIXEL_FORMAT__Y: + return wuffs_base__pixel_swizzler__y__y_16be; + + case WUFFS_BASE__PIXEL_FORMAT__Y_16LE: + return wuffs_base__pixel_swizzler__y_16le__y_16be; + + case WUFFS_BASE__PIXEL_FORMAT__Y_16BE: + return wuffs_base__pixel_swizzler__copy_2_2; + + case WUFFS_BASE__PIXEL_FORMAT__BGR_565: + return wuffs_base__pixel_swizzler__bgr_565__y_16be; + + case WUFFS_BASE__PIXEL_FORMAT__BGR: + case WUFFS_BASE__PIXEL_FORMAT__RGB: + return wuffs_base__pixel_swizzler__xxx__y_16be; + + case WUFFS_BASE__PIXEL_FORMAT__BGRA_NONPREMUL: + case WUFFS_BASE__PIXEL_FORMAT__BGRA_PREMUL: + case WUFFS_BASE__PIXEL_FORMAT__BGRA_BINARY: + case WUFFS_BASE__PIXEL_FORMAT__BGRX: + case WUFFS_BASE__PIXEL_FORMAT__RGBA_NONPREMUL: + case WUFFS_BASE__PIXEL_FORMAT__RGBA_PREMUL: + case WUFFS_BASE__PIXEL_FORMAT__RGBA_BINARY: + case WUFFS_BASE__PIXEL_FORMAT__RGBX: + return wuffs_base__pixel_swizzler__xxxx__y_16be; + + case WUFFS_BASE__PIXEL_FORMAT__BGRA_NONPREMUL_4X16LE: + case WUFFS_BASE__PIXEL_FORMAT__BGRA_PREMUL_4X16LE: + case WUFFS_BASE__PIXEL_FORMAT__RGBA_NONPREMUL_4X16LE: + case WUFFS_BASE__PIXEL_FORMAT__RGBA_PREMUL_4X16LE: + return wuffs_base__pixel_swizzler__xxxxxxxx__y_16be; + } + return NULL; +} + +static wuffs_base__pixel_swizzler__func // +wuffs_base__pixel_swizzler__prepare__indexed__bgra_nonpremul( + wuffs_base__pixel_swizzler* p, + wuffs_base__pixel_format dst_pixfmt, + wuffs_base__slice_u8 dst_palette, + wuffs_base__slice_u8 src_palette, + wuffs_base__pixel_blend blend) { + switch (dst_pixfmt.repr) { + case WUFFS_BASE__PIXEL_FORMAT__INDEXED__BGRA_NONPREMUL: + if (wuffs_base__slice_u8__copy_from_slice(dst_palette, src_palette) != + WUFFS_BASE__PIXEL_FORMAT__INDEXED__PALETTE_BYTE_LENGTH) { + return NULL; + } + switch (blend) { + case WUFFS_BASE__PIXEL_BLEND__SRC: + return wuffs_base__pixel_swizzler__copy_1_1; + } + return NULL; + + case WUFFS_BASE__PIXEL_FORMAT__BGR_565: + switch (blend) { + case WUFFS_BASE__PIXEL_BLEND__SRC: + if (wuffs_base__pixel_swizzler__squash_align4_bgr_565_8888( + dst_palette.ptr, dst_palette.len, src_palette.ptr, + src_palette.len, true) != + (WUFFS_BASE__PIXEL_FORMAT__INDEXED__PALETTE_BYTE_LENGTH / 4)) { + return NULL; + } + return wuffs_base__pixel_swizzler__bgr_565__index__src; + case WUFFS_BASE__PIXEL_BLEND__SRC_OVER: + if (wuffs_base__slice_u8__copy_from_slice(dst_palette, src_palette) != + WUFFS_BASE__PIXEL_FORMAT__INDEXED__PALETTE_BYTE_LENGTH) { + return NULL; + } + return wuffs_base__pixel_swizzler__bgr_565__index_bgra_nonpremul__src_over; + } + return NULL; + + case WUFFS_BASE__PIXEL_FORMAT__BGR: + switch (blend) { + case WUFFS_BASE__PIXEL_BLEND__SRC: + if (wuffs_base__pixel_swizzler__bgra_premul__bgra_nonpremul__src( + dst_palette.ptr, dst_palette.len, NULL, 0, src_palette.ptr, + src_palette.len) != + (WUFFS_BASE__PIXEL_FORMAT__INDEXED__PALETTE_BYTE_LENGTH / 4)) { + return NULL; + } + return wuffs_base__pixel_swizzler__xxx__index__src; + case WUFFS_BASE__PIXEL_BLEND__SRC_OVER: + if (wuffs_base__slice_u8__copy_from_slice(dst_palette, src_palette) != + WUFFS_BASE__PIXEL_FORMAT__INDEXED__PALETTE_BYTE_LENGTH) { + return NULL; + } + return wuffs_base__pixel_swizzler__xxx__index_bgra_nonpremul__src_over; + } + return NULL; + + case WUFFS_BASE__PIXEL_FORMAT__BGRA_NONPREMUL: + if (wuffs_base__slice_u8__copy_from_slice(dst_palette, src_palette) != + WUFFS_BASE__PIXEL_FORMAT__INDEXED__PALETTE_BYTE_LENGTH) { + return NULL; + } + switch (blend) { + case WUFFS_BASE__PIXEL_BLEND__SRC: + return wuffs_base__pixel_swizzler__xxxx__index__src; + case WUFFS_BASE__PIXEL_BLEND__SRC_OVER: + return wuffs_base__pixel_swizzler__bgra_nonpremul__index_bgra_nonpremul__src_over; + } + return NULL; + + case WUFFS_BASE__PIXEL_FORMAT__BGRA_NONPREMUL_4X16LE: + if (wuffs_base__slice_u8__copy_from_slice(dst_palette, src_palette) != + WUFFS_BASE__PIXEL_FORMAT__INDEXED__PALETTE_BYTE_LENGTH) { + return NULL; + } + switch (blend) { + case WUFFS_BASE__PIXEL_BLEND__SRC: + return wuffs_base__pixel_swizzler__xxxxxxxx__index__src; + case WUFFS_BASE__PIXEL_BLEND__SRC_OVER: + return wuffs_base__pixel_swizzler__bgra_nonpremul_4x16le__index_bgra_nonpremul__src_over; + } + return NULL; + + case WUFFS_BASE__PIXEL_FORMAT__BGRA_PREMUL: + switch (blend) { + case WUFFS_BASE__PIXEL_BLEND__SRC: + if (wuffs_base__pixel_swizzler__bgra_premul__bgra_nonpremul__src( + dst_palette.ptr, dst_palette.len, NULL, 0, src_palette.ptr, + src_palette.len) != + (WUFFS_BASE__PIXEL_FORMAT__INDEXED__PALETTE_BYTE_LENGTH / 4)) { + return NULL; + } + return wuffs_base__pixel_swizzler__xxxx__index__src; + case WUFFS_BASE__PIXEL_BLEND__SRC_OVER: + if (wuffs_base__slice_u8__copy_from_slice(dst_palette, src_palette) != + WUFFS_BASE__PIXEL_FORMAT__INDEXED__PALETTE_BYTE_LENGTH) { + return NULL; + } + return wuffs_base__pixel_swizzler__bgra_premul__index_bgra_nonpremul__src_over; + } + return NULL; + + case WUFFS_BASE__PIXEL_FORMAT__RGB: + // TODO. + break; + + case WUFFS_BASE__PIXEL_FORMAT__RGBA_NONPREMUL: + if (wuffs_base__pixel_swizzler__swap_rgbx_bgrx( + dst_palette.ptr, dst_palette.len, NULL, 0, src_palette.ptr, + src_palette.len) != + (WUFFS_BASE__PIXEL_FORMAT__INDEXED__PALETTE_BYTE_LENGTH / 4)) { + return NULL; + } + switch (blend) { + case WUFFS_BASE__PIXEL_BLEND__SRC: + return wuffs_base__pixel_swizzler__xxxx__index__src; + case WUFFS_BASE__PIXEL_BLEND__SRC_OVER: + return wuffs_base__pixel_swizzler__bgra_nonpremul__index_bgra_nonpremul__src_over; + } + return NULL; + + case WUFFS_BASE__PIXEL_FORMAT__RGBA_PREMUL: + switch (blend) { + case WUFFS_BASE__PIXEL_BLEND__SRC: + if (wuffs_base__pixel_swizzler__bgra_premul__rgba_nonpremul__src( + dst_palette.ptr, dst_palette.len, NULL, 0, src_palette.ptr, + src_palette.len) != + (WUFFS_BASE__PIXEL_FORMAT__INDEXED__PALETTE_BYTE_LENGTH / 4)) { + return NULL; + } + return wuffs_base__pixel_swizzler__xxxx__index__src; + case WUFFS_BASE__PIXEL_BLEND__SRC_OVER: + if (wuffs_base__pixel_swizzler__swap_rgbx_bgrx( + dst_palette.ptr, dst_palette.len, NULL, 0, src_palette.ptr, + src_palette.len) != + (WUFFS_BASE__PIXEL_FORMAT__INDEXED__PALETTE_BYTE_LENGTH / 4)) { + return NULL; + } + return wuffs_base__pixel_swizzler__bgra_premul__index_bgra_nonpremul__src_over; + } + return NULL; + + case WUFFS_BASE__PIXEL_FORMAT__RGBX: + // TODO. + break; + } + return NULL; +} + +static wuffs_base__pixel_swizzler__func // +wuffs_base__pixel_swizzler__prepare__indexed__bgra_binary( + wuffs_base__pixel_swizzler* p, + wuffs_base__pixel_format dst_pixfmt, + wuffs_base__slice_u8 dst_palette, + wuffs_base__slice_u8 src_palette, + wuffs_base__pixel_blend blend) { + switch (dst_pixfmt.repr) { + case WUFFS_BASE__PIXEL_FORMAT__INDEXED__BGRA_NONPREMUL: + case WUFFS_BASE__PIXEL_FORMAT__INDEXED__BGRA_PREMUL: + case WUFFS_BASE__PIXEL_FORMAT__INDEXED__BGRA_BINARY: + if (wuffs_base__slice_u8__copy_from_slice(dst_palette, src_palette) != + WUFFS_BASE__PIXEL_FORMAT__INDEXED__PALETTE_BYTE_LENGTH) { + return NULL; + } + switch (blend) { + case WUFFS_BASE__PIXEL_BLEND__SRC: + return wuffs_base__pixel_swizzler__copy_1_1; + } + return NULL; + + case WUFFS_BASE__PIXEL_FORMAT__BGR_565: + if (wuffs_base__pixel_swizzler__squash_align4_bgr_565_8888( + dst_palette.ptr, dst_palette.len, src_palette.ptr, + src_palette.len, false) != + (WUFFS_BASE__PIXEL_FORMAT__INDEXED__PALETTE_BYTE_LENGTH / 4)) { + return NULL; + } + switch (blend) { + case WUFFS_BASE__PIXEL_BLEND__SRC: + return wuffs_base__pixel_swizzler__bgr_565__index__src; + case WUFFS_BASE__PIXEL_BLEND__SRC_OVER: + return wuffs_base__pixel_swizzler__bgr_565__index_binary_alpha__src_over; + } + return NULL; + + case WUFFS_BASE__PIXEL_FORMAT__BGR: + if (wuffs_base__slice_u8__copy_from_slice(dst_palette, src_palette) != + WUFFS_BASE__PIXEL_FORMAT__INDEXED__PALETTE_BYTE_LENGTH) { + return NULL; + } + switch (blend) { + case WUFFS_BASE__PIXEL_BLEND__SRC: + return wuffs_base__pixel_swizzler__xxx__index__src; + case WUFFS_BASE__PIXEL_BLEND__SRC_OVER: + return wuffs_base__pixel_swizzler__xxx__index_binary_alpha__src_over; + } + return NULL; + + case WUFFS_BASE__PIXEL_FORMAT__BGRA_NONPREMUL: + case WUFFS_BASE__PIXEL_FORMAT__BGRA_PREMUL: + case WUFFS_BASE__PIXEL_FORMAT__BGRA_BINARY: + if (wuffs_base__slice_u8__copy_from_slice(dst_palette, src_palette) != + WUFFS_BASE__PIXEL_FORMAT__INDEXED__PALETTE_BYTE_LENGTH) { + return NULL; + } + switch (blend) { + case WUFFS_BASE__PIXEL_BLEND__SRC: + return wuffs_base__pixel_swizzler__xxxx__index__src; + case WUFFS_BASE__PIXEL_BLEND__SRC_OVER: + return wuffs_base__pixel_swizzler__xxxx__index_binary_alpha__src_over; + } + return NULL; + + case WUFFS_BASE__PIXEL_FORMAT__BGRA_NONPREMUL_4X16LE: + case WUFFS_BASE__PIXEL_FORMAT__BGRA_PREMUL_4X16LE: + if (wuffs_base__slice_u8__copy_from_slice(dst_palette, src_palette) != + WUFFS_BASE__PIXEL_FORMAT__INDEXED__PALETTE_BYTE_LENGTH) { + return NULL; + } + switch (blend) { + case WUFFS_BASE__PIXEL_BLEND__SRC: + return wuffs_base__pixel_swizzler__xxxxxxxx__index__src; + case WUFFS_BASE__PIXEL_BLEND__SRC_OVER: + return wuffs_base__pixel_swizzler__xxxxxxxx__index_binary_alpha__src_over; + } + return NULL; + + case WUFFS_BASE__PIXEL_FORMAT__RGB: + if (wuffs_base__pixel_swizzler__swap_rgbx_bgrx( + dst_palette.ptr, dst_palette.len, NULL, 0, src_palette.ptr, + src_palette.len) != + (WUFFS_BASE__PIXEL_FORMAT__INDEXED__PALETTE_BYTE_LENGTH / 4)) { + return NULL; + } + switch (blend) { + case WUFFS_BASE__PIXEL_BLEND__SRC: + return wuffs_base__pixel_swizzler__xxx__index__src; + case WUFFS_BASE__PIXEL_BLEND__SRC_OVER: + return wuffs_base__pixel_swizzler__xxx__index_binary_alpha__src_over; + } + return NULL; + + case WUFFS_BASE__PIXEL_FORMAT__RGBA_NONPREMUL: + case WUFFS_BASE__PIXEL_FORMAT__RGBA_PREMUL: + case WUFFS_BASE__PIXEL_FORMAT__RGBA_BINARY: + if (wuffs_base__pixel_swizzler__swap_rgbx_bgrx( + dst_palette.ptr, dst_palette.len, NULL, 0, src_palette.ptr, + src_palette.len) != + (WUFFS_BASE__PIXEL_FORMAT__INDEXED__PALETTE_BYTE_LENGTH / 4)) { + return NULL; + } + switch (blend) { + case WUFFS_BASE__PIXEL_BLEND__SRC: + return wuffs_base__pixel_swizzler__xxxx__index__src; + case WUFFS_BASE__PIXEL_BLEND__SRC_OVER: + return wuffs_base__pixel_swizzler__xxxx__index_binary_alpha__src_over; + } + return NULL; + } + return NULL; +} + +static wuffs_base__pixel_swizzler__func // +wuffs_base__pixel_swizzler__prepare__bgr_565( + wuffs_base__pixel_swizzler* p, + wuffs_base__pixel_format dst_pixfmt, + wuffs_base__slice_u8 dst_palette, + wuffs_base__slice_u8 src_palette, + wuffs_base__pixel_blend blend) { + switch (dst_pixfmt.repr) { + case WUFFS_BASE__PIXEL_FORMAT__BGR_565: + return wuffs_base__pixel_swizzler__copy_2_2; + + case WUFFS_BASE__PIXEL_FORMAT__BGR: + return wuffs_base__pixel_swizzler__bgr__bgr_565; + + case WUFFS_BASE__PIXEL_FORMAT__BGRA_NONPREMUL: + case WUFFS_BASE__PIXEL_FORMAT__BGRA_PREMUL: + case WUFFS_BASE__PIXEL_FORMAT__BGRA_BINARY: + case WUFFS_BASE__PIXEL_FORMAT__BGRX: + return wuffs_base__pixel_swizzler__bgrw__bgr_565; + + case WUFFS_BASE__PIXEL_FORMAT__BGRA_NONPREMUL_4X16LE: + case WUFFS_BASE__PIXEL_FORMAT__BGRA_PREMUL_4X16LE: + return wuffs_base__pixel_swizzler__bgrw_4x16le__bgr_565; + + case WUFFS_BASE__PIXEL_FORMAT__RGBA_NONPREMUL: + case WUFFS_BASE__PIXEL_FORMAT__RGBA_PREMUL: + case WUFFS_BASE__PIXEL_FORMAT__RGBA_BINARY: + case WUFFS_BASE__PIXEL_FORMAT__RGBX: + return wuffs_base__pixel_swizzler__rgbw__bgr_565; + } + return NULL; +} + +static wuffs_base__pixel_swizzler__func // +wuffs_base__pixel_swizzler__prepare__bgr(wuffs_base__pixel_swizzler* p, + wuffs_base__pixel_format dst_pixfmt, + wuffs_base__slice_u8 dst_palette, + wuffs_base__slice_u8 src_palette, + wuffs_base__pixel_blend blend) { + switch (dst_pixfmt.repr) { + case WUFFS_BASE__PIXEL_FORMAT__BGR_565: + return wuffs_base__pixel_swizzler__bgr_565__bgr; + + case WUFFS_BASE__PIXEL_FORMAT__BGR: + return wuffs_base__pixel_swizzler__copy_3_3; + + case WUFFS_BASE__PIXEL_FORMAT__BGRA_NONPREMUL: + case WUFFS_BASE__PIXEL_FORMAT__BGRA_PREMUL: + case WUFFS_BASE__PIXEL_FORMAT__BGRA_BINARY: + case WUFFS_BASE__PIXEL_FORMAT__BGRX: + return wuffs_base__pixel_swizzler__bgrw__bgr; + + case WUFFS_BASE__PIXEL_FORMAT__BGRA_NONPREMUL_4X16LE: + case WUFFS_BASE__PIXEL_FORMAT__BGRA_PREMUL_4X16LE: + return wuffs_base__pixel_swizzler__bgrw_4x16le__bgr; + + case WUFFS_BASE__PIXEL_FORMAT__RGB: + return wuffs_base__pixel_swizzler__swap_rgb_bgr; + + case WUFFS_BASE__PIXEL_FORMAT__RGBA_NONPREMUL: + case WUFFS_BASE__PIXEL_FORMAT__RGBA_PREMUL: + case WUFFS_BASE__PIXEL_FORMAT__RGBA_BINARY: + case WUFFS_BASE__PIXEL_FORMAT__RGBX: +#if defined(WUFFS_BASE__CPU_ARCH__X86_FAMILY) + if (wuffs_base__cpu_arch__have_x86_sse42()) { + return wuffs_base__pixel_swizzler__bgrw__rgb__sse42; + } +#endif + return wuffs_base__pixel_swizzler__bgrw__rgb; + } + return NULL; +} + +static wuffs_base__pixel_swizzler__func // +wuffs_base__pixel_swizzler__prepare__bgra_nonpremul( + wuffs_base__pixel_swizzler* p, + wuffs_base__pixel_format dst_pixfmt, + wuffs_base__slice_u8 dst_palette, + wuffs_base__slice_u8 src_palette, + wuffs_base__pixel_blend blend) { + switch (dst_pixfmt.repr) { + case WUFFS_BASE__PIXEL_FORMAT__BGR_565: + switch (blend) { + case WUFFS_BASE__PIXEL_BLEND__SRC: + return wuffs_base__pixel_swizzler__bgr_565__bgra_nonpremul__src; + case WUFFS_BASE__PIXEL_BLEND__SRC_OVER: + return wuffs_base__pixel_swizzler__bgr_565__bgra_nonpremul__src_over; + } + return NULL; + + case WUFFS_BASE__PIXEL_FORMAT__BGR: + switch (blend) { + case WUFFS_BASE__PIXEL_BLEND__SRC: + return wuffs_base__pixel_swizzler__bgr__bgra_nonpremul__src; + case WUFFS_BASE__PIXEL_BLEND__SRC_OVER: + return wuffs_base__pixel_swizzler__bgr__bgra_nonpremul__src_over; + } + return NULL; + + case WUFFS_BASE__PIXEL_FORMAT__BGRA_NONPREMUL: + switch (blend) { + case WUFFS_BASE__PIXEL_BLEND__SRC: + return wuffs_base__pixel_swizzler__copy_4_4; + case WUFFS_BASE__PIXEL_BLEND__SRC_OVER: + return wuffs_base__pixel_swizzler__bgra_nonpremul__bgra_nonpremul__src_over; + } + return NULL; + + case WUFFS_BASE__PIXEL_FORMAT__BGRA_NONPREMUL_4X16LE: + switch (blend) { + case WUFFS_BASE__PIXEL_BLEND__SRC: + return wuffs_base__pixel_swizzler__bgra_nonpremul_4x16le__bgra_nonpremul__src; + case WUFFS_BASE__PIXEL_BLEND__SRC_OVER: + return wuffs_base__pixel_swizzler__bgra_nonpremul_4x16le__bgra_nonpremul__src_over; + } + return NULL; + + case WUFFS_BASE__PIXEL_FORMAT__BGRA_PREMUL: + switch (blend) { + case WUFFS_BASE__PIXEL_BLEND__SRC: + return wuffs_base__pixel_swizzler__bgra_premul__bgra_nonpremul__src; + case WUFFS_BASE__PIXEL_BLEND__SRC_OVER: + return wuffs_base__pixel_swizzler__bgra_premul__bgra_nonpremul__src_over; + } + return NULL; + + case WUFFS_BASE__PIXEL_FORMAT__BGRA_BINARY: + case WUFFS_BASE__PIXEL_FORMAT__BGRX: + // TODO. + break; + + case WUFFS_BASE__PIXEL_FORMAT__RGB: + // TODO. + break; + + case WUFFS_BASE__PIXEL_FORMAT__RGBA_NONPREMUL: + switch (blend) { + case WUFFS_BASE__PIXEL_BLEND__SRC: +#if defined(WUFFS_BASE__CPU_ARCH__X86_FAMILY) + if (wuffs_base__cpu_arch__have_x86_sse42()) { + return wuffs_base__pixel_swizzler__swap_rgbx_bgrx__sse42; + } +#endif + return wuffs_base__pixel_swizzler__swap_rgbx_bgrx; + case WUFFS_BASE__PIXEL_BLEND__SRC_OVER: + return wuffs_base__pixel_swizzler__bgra_nonpremul__rgba_nonpremul__src_over; + } + return NULL; + + case WUFFS_BASE__PIXEL_FORMAT__RGBA_PREMUL: + switch (blend) { + case WUFFS_BASE__PIXEL_BLEND__SRC: + return wuffs_base__pixel_swizzler__bgra_premul__rgba_nonpremul__src; + case WUFFS_BASE__PIXEL_BLEND__SRC_OVER: + return wuffs_base__pixel_swizzler__bgra_premul__rgba_nonpremul__src_over; + } + return NULL; + + case WUFFS_BASE__PIXEL_FORMAT__RGBA_BINARY: + case WUFFS_BASE__PIXEL_FORMAT__RGBX: + // TODO. + break; + } + return NULL; +} + +static wuffs_base__pixel_swizzler__func // +wuffs_base__pixel_swizzler__prepare__bgra_nonpremul_4x16le( + wuffs_base__pixel_swizzler* p, + wuffs_base__pixel_format dst_pixfmt, + wuffs_base__slice_u8 dst_palette, + wuffs_base__slice_u8 src_palette, + wuffs_base__pixel_blend blend) { + switch (dst_pixfmt.repr) { + case WUFFS_BASE__PIXEL_FORMAT__BGR_565: + switch (blend) { + case WUFFS_BASE__PIXEL_BLEND__SRC: + return wuffs_base__pixel_swizzler__bgr_565__bgra_nonpremul_4x16le__src; + case WUFFS_BASE__PIXEL_BLEND__SRC_OVER: + return wuffs_base__pixel_swizzler__bgr_565__bgra_nonpremul_4x16le__src_over; + } + return NULL; + + case WUFFS_BASE__PIXEL_FORMAT__BGR: + switch (blend) { + case WUFFS_BASE__PIXEL_BLEND__SRC: + return wuffs_base__pixel_swizzler__bgr__bgra_nonpremul_4x16le__src; + case WUFFS_BASE__PIXEL_BLEND__SRC_OVER: + return wuffs_base__pixel_swizzler__bgr__bgra_nonpremul_4x16le__src_over; + } + return NULL; + + case WUFFS_BASE__PIXEL_FORMAT__BGRA_NONPREMUL: + switch (blend) { + case WUFFS_BASE__PIXEL_BLEND__SRC: + return wuffs_base__pixel_swizzler__bgra_nonpremul__bgra_nonpremul_4x16le__src; + case WUFFS_BASE__PIXEL_BLEND__SRC_OVER: + return wuffs_base__pixel_swizzler__bgra_nonpremul__bgra_nonpremul_4x16le__src_over; + } + return NULL; + + case WUFFS_BASE__PIXEL_FORMAT__BGRA_NONPREMUL_4X16LE: + switch (blend) { + case WUFFS_BASE__PIXEL_BLEND__SRC: + return wuffs_base__pixel_swizzler__copy_8_8; + case WUFFS_BASE__PIXEL_BLEND__SRC_OVER: + return wuffs_base__pixel_swizzler__bgra_nonpremul_4x16le__bgra_nonpremul_4x16le__src_over; + } + return NULL; + + case WUFFS_BASE__PIXEL_FORMAT__BGRA_PREMUL: + switch (blend) { + case WUFFS_BASE__PIXEL_BLEND__SRC: + return wuffs_base__pixel_swizzler__bgra_premul__bgra_nonpremul_4x16le__src; + case WUFFS_BASE__PIXEL_BLEND__SRC_OVER: + return wuffs_base__pixel_swizzler__bgra_premul__bgra_nonpremul_4x16le__src_over; + } + return NULL; + + case WUFFS_BASE__PIXEL_FORMAT__BGRA_BINARY: + case WUFFS_BASE__PIXEL_FORMAT__BGRX: + // TODO. + break; + + case WUFFS_BASE__PIXEL_FORMAT__RGB: + // TODO. + break; + + case WUFFS_BASE__PIXEL_FORMAT__RGBA_NONPREMUL: + switch (blend) { + case WUFFS_BASE__PIXEL_BLEND__SRC: + return wuffs_base__pixel_swizzler__rgba_nonpremul__bgra_nonpremul_4x16le__src; + case WUFFS_BASE__PIXEL_BLEND__SRC_OVER: + return wuffs_base__pixel_swizzler__rgba_nonpremul__bgra_nonpremul_4x16le__src_over; + } + break; + + case WUFFS_BASE__PIXEL_FORMAT__RGBA_PREMUL: + switch (blend) { + case WUFFS_BASE__PIXEL_BLEND__SRC: + return wuffs_base__pixel_swizzler__bgra_premul__rgba_nonpremul_4x16le__src; + case WUFFS_BASE__PIXEL_BLEND__SRC_OVER: + return wuffs_base__pixel_swizzler__bgra_premul__rgba_nonpremul_4x16le__src_over; + } + return NULL; + + case WUFFS_BASE__PIXEL_FORMAT__RGBA_BINARY: + case WUFFS_BASE__PIXEL_FORMAT__RGBX: + // TODO. + break; + } + return NULL; +} + +static wuffs_base__pixel_swizzler__func // +wuffs_base__pixel_swizzler__prepare__bgra_premul( + wuffs_base__pixel_swizzler* p, + wuffs_base__pixel_format dst_pixfmt, + wuffs_base__slice_u8 dst_palette, + wuffs_base__slice_u8 src_palette, + wuffs_base__pixel_blend blend) { + switch (dst_pixfmt.repr) { + case WUFFS_BASE__PIXEL_FORMAT__BGR_565: + switch (blend) { + case WUFFS_BASE__PIXEL_BLEND__SRC: + return wuffs_base__pixel_swizzler__bgr_565__bgra_premul__src; + case WUFFS_BASE__PIXEL_BLEND__SRC_OVER: + return wuffs_base__pixel_swizzler__bgr_565__bgra_premul__src_over; + } + return NULL; + + case WUFFS_BASE__PIXEL_FORMAT__BGR: + switch (blend) { + case WUFFS_BASE__PIXEL_BLEND__SRC: + return wuffs_base__pixel_swizzler__bgr__bgra_premul__src; + case WUFFS_BASE__PIXEL_BLEND__SRC_OVER: + return wuffs_base__pixel_swizzler__bgr__bgra_premul__src_over; + } + return NULL; + + case WUFFS_BASE__PIXEL_FORMAT__BGRA_NONPREMUL: + switch (blend) { + case WUFFS_BASE__PIXEL_BLEND__SRC: + return wuffs_base__pixel_swizzler__bgra_nonpremul__bgra_premul__src; + case WUFFS_BASE__PIXEL_BLEND__SRC_OVER: + return wuffs_base__pixel_swizzler__bgra_nonpremul__bgra_premul__src_over; + } + return NULL; + + case WUFFS_BASE__PIXEL_FORMAT__BGRA_NONPREMUL_4X16LE: + switch (blend) { + case WUFFS_BASE__PIXEL_BLEND__SRC: + return wuffs_base__pixel_swizzler__bgra_nonpremul_4x16le__bgra_premul__src; + case WUFFS_BASE__PIXEL_BLEND__SRC_OVER: + return wuffs_base__pixel_swizzler__bgra_nonpremul_4x16le__bgra_premul__src_over; + } + return NULL; + + case WUFFS_BASE__PIXEL_FORMAT__BGRA_PREMUL: + switch (blend) { + case WUFFS_BASE__PIXEL_BLEND__SRC: + return wuffs_base__pixel_swizzler__copy_4_4; + case WUFFS_BASE__PIXEL_BLEND__SRC_OVER: + return wuffs_base__pixel_swizzler__bgra_premul__bgra_premul__src_over; + } + return NULL; + + case WUFFS_BASE__PIXEL_FORMAT__RGBA_NONPREMUL: + switch (blend) { + case WUFFS_BASE__PIXEL_BLEND__SRC: + return wuffs_base__pixel_swizzler__bgra_nonpremul__rgba_premul__src; + case WUFFS_BASE__PIXEL_BLEND__SRC_OVER: + return wuffs_base__pixel_swizzler__bgra_nonpremul__rgba_premul__src_over; + } + return NULL; + + case WUFFS_BASE__PIXEL_FORMAT__RGBA_PREMUL: + switch (blend) { + case WUFFS_BASE__PIXEL_BLEND__SRC: +#if defined(WUFFS_BASE__CPU_ARCH__X86_FAMILY) + if (wuffs_base__cpu_arch__have_x86_sse42()) { + return wuffs_base__pixel_swizzler__swap_rgbx_bgrx__sse42; + } +#endif + return wuffs_base__pixel_swizzler__swap_rgbx_bgrx; + case WUFFS_BASE__PIXEL_BLEND__SRC_OVER: + return wuffs_base__pixel_swizzler__bgra_premul__rgba_premul__src_over; + } + return NULL; + } + return NULL; +} + +static wuffs_base__pixel_swizzler__func // +wuffs_base__pixel_swizzler__prepare__bgrx(wuffs_base__pixel_swizzler* p, + wuffs_base__pixel_format dst_pixfmt, + wuffs_base__slice_u8 dst_palette, + wuffs_base__slice_u8 src_palette, + wuffs_base__pixel_blend blend) { + switch (dst_pixfmt.repr) { + case WUFFS_BASE__PIXEL_FORMAT__BGR_565: + return wuffs_base__pixel_swizzler__bgr_565__bgrx; + + case WUFFS_BASE__PIXEL_FORMAT__BGR: + return wuffs_base__pixel_swizzler__xxx__xxxx; + + case WUFFS_BASE__PIXEL_FORMAT__BGRA_NONPREMUL: + case WUFFS_BASE__PIXEL_FORMAT__BGRA_PREMUL: + case WUFFS_BASE__PIXEL_FORMAT__BGRA_BINARY: + return wuffs_base__pixel_swizzler__bgrw__bgrx; + + case WUFFS_BASE__PIXEL_FORMAT__BGRA_NONPREMUL_4X16LE: + return wuffs_base__pixel_swizzler__bgrw_4x16le__bgrx; + + case WUFFS_BASE__PIXEL_FORMAT__BGRX: + return wuffs_base__pixel_swizzler__copy_4_4; + + case WUFFS_BASE__PIXEL_FORMAT__RGB: + // TODO. + break; + + case WUFFS_BASE__PIXEL_FORMAT__RGBA_NONPREMUL: + case WUFFS_BASE__PIXEL_FORMAT__RGBA_PREMUL: + case WUFFS_BASE__PIXEL_FORMAT__RGBA_BINARY: + case WUFFS_BASE__PIXEL_FORMAT__RGBX: + return wuffs_base__pixel_swizzler__bgrw__rgbx; + } + return NULL; +} + +static wuffs_base__pixel_swizzler__func // +wuffs_base__pixel_swizzler__prepare__rgb(wuffs_base__pixel_swizzler* p, + wuffs_base__pixel_format dst_pixfmt, + wuffs_base__slice_u8 dst_palette, + wuffs_base__slice_u8 src_palette, + wuffs_base__pixel_blend blend) { + switch (dst_pixfmt.repr) { + case WUFFS_BASE__PIXEL_FORMAT__BGR_565: + return wuffs_base__pixel_swizzler__bgr_565__rgb; + + case WUFFS_BASE__PIXEL_FORMAT__BGR: + return wuffs_base__pixel_swizzler__swap_rgb_bgr; + + case WUFFS_BASE__PIXEL_FORMAT__BGRA_NONPREMUL: + case WUFFS_BASE__PIXEL_FORMAT__BGRA_PREMUL: + case WUFFS_BASE__PIXEL_FORMAT__BGRA_BINARY: + case WUFFS_BASE__PIXEL_FORMAT__BGRX: +#if defined(WUFFS_BASE__CPU_ARCH__X86_FAMILY) + if (wuffs_base__cpu_arch__have_x86_sse42()) { + return wuffs_base__pixel_swizzler__bgrw__rgb__sse42; + } +#endif + return wuffs_base__pixel_swizzler__bgrw__rgb; + + case WUFFS_BASE__PIXEL_FORMAT__BGRA_NONPREMUL_4X16LE: + return wuffs_base__pixel_swizzler__bgrw_4x16le__rgb; + + case WUFFS_BASE__PIXEL_FORMAT__RGB: + return wuffs_base__pixel_swizzler__copy_3_3; + + case WUFFS_BASE__PIXEL_FORMAT__RGBA_NONPREMUL: + case WUFFS_BASE__PIXEL_FORMAT__RGBA_PREMUL: + case WUFFS_BASE__PIXEL_FORMAT__RGBA_BINARY: + case WUFFS_BASE__PIXEL_FORMAT__RGBX: + return wuffs_base__pixel_swizzler__bgrw__bgr; + } + return NULL; +} + +static wuffs_base__pixel_swizzler__func // +wuffs_base__pixel_swizzler__prepare__rgba_nonpremul( + wuffs_base__pixel_swizzler* p, + wuffs_base__pixel_format dst_pixfmt, + wuffs_base__slice_u8 dst_palette, + wuffs_base__slice_u8 src_palette, + wuffs_base__pixel_blend blend) { + switch (dst_pixfmt.repr) { + case WUFFS_BASE__PIXEL_FORMAT__BGR_565: + switch (blend) { + case WUFFS_BASE__PIXEL_BLEND__SRC: + return wuffs_base__pixel_swizzler__bgr_565__rgba_nonpremul__src; + case WUFFS_BASE__PIXEL_BLEND__SRC_OVER: + return wuffs_base__pixel_swizzler__bgr_565__rgba_nonpremul__src_over; + } + return NULL; + + case WUFFS_BASE__PIXEL_FORMAT__BGR: + switch (blend) { + case WUFFS_BASE__PIXEL_BLEND__SRC: + return wuffs_base__pixel_swizzler__bgr__rgba_nonpremul__src; + case WUFFS_BASE__PIXEL_BLEND__SRC_OVER: + return wuffs_base__pixel_swizzler__bgr__rgba_nonpremul__src_over; + } + return NULL; + + case WUFFS_BASE__PIXEL_FORMAT__BGRA_NONPREMUL: + switch (blend) { + case WUFFS_BASE__PIXEL_BLEND__SRC: +#if defined(WUFFS_BASE__CPU_ARCH__X86_FAMILY) + if (wuffs_base__cpu_arch__have_x86_sse42()) { + return wuffs_base__pixel_swizzler__swap_rgbx_bgrx__sse42; + } +#endif + return wuffs_base__pixel_swizzler__swap_rgbx_bgrx; + case WUFFS_BASE__PIXEL_BLEND__SRC_OVER: + return wuffs_base__pixel_swizzler__bgra_nonpremul__rgba_nonpremul__src_over; + } + return NULL; + + case WUFFS_BASE__PIXEL_FORMAT__BGRA_NONPREMUL_4X16LE: + switch (blend) { + case WUFFS_BASE__PIXEL_BLEND__SRC: + return wuffs_base__pixel_swizzler__bgra_nonpremul_4x16le__rgba_nonpremul__src; + case WUFFS_BASE__PIXEL_BLEND__SRC_OVER: + return wuffs_base__pixel_swizzler__bgra_nonpremul_4x16le__rgba_nonpremul__src_over; + } + return NULL; + + case WUFFS_BASE__PIXEL_FORMAT__BGRA_PREMUL: + switch (blend) { + case WUFFS_BASE__PIXEL_BLEND__SRC: + return wuffs_base__pixel_swizzler__bgra_premul__rgba_nonpremul__src; + case WUFFS_BASE__PIXEL_BLEND__SRC_OVER: + return wuffs_base__pixel_swizzler__bgra_premul__rgba_nonpremul__src_over; + } + return NULL; + + case WUFFS_BASE__PIXEL_FORMAT__BGRA_BINARY: + case WUFFS_BASE__PIXEL_FORMAT__BGRX: + // TODO. + break; + + case WUFFS_BASE__PIXEL_FORMAT__RGB: + // TODO. + break; + + case WUFFS_BASE__PIXEL_FORMAT__RGBA_NONPREMUL: + switch (blend) { + case WUFFS_BASE__PIXEL_BLEND__SRC: + return wuffs_base__pixel_swizzler__copy_4_4; + case WUFFS_BASE__PIXEL_BLEND__SRC_OVER: + return wuffs_base__pixel_swizzler__bgra_nonpremul__bgra_nonpremul__src_over; + } + return NULL; + + case WUFFS_BASE__PIXEL_FORMAT__RGBA_PREMUL: + switch (blend) { + case WUFFS_BASE__PIXEL_BLEND__SRC: + return wuffs_base__pixel_swizzler__bgra_premul__bgra_nonpremul__src; + case WUFFS_BASE__PIXEL_BLEND__SRC_OVER: + return wuffs_base__pixel_swizzler__bgra_premul__bgra_nonpremul__src_over; + } + return NULL; + + case WUFFS_BASE__PIXEL_FORMAT__RGBA_BINARY: + case WUFFS_BASE__PIXEL_FORMAT__RGBX: + // TODO. + break; + } + return NULL; +} + +static wuffs_base__pixel_swizzler__func // +wuffs_base__pixel_swizzler__prepare__rgba_premul( + wuffs_base__pixel_swizzler* p, + wuffs_base__pixel_format dst_pixfmt, + wuffs_base__slice_u8 dst_palette, + wuffs_base__slice_u8 src_palette, + wuffs_base__pixel_blend blend) { + switch (dst_pixfmt.repr) { + case WUFFS_BASE__PIXEL_FORMAT__BGR_565: + switch (blend) { + case WUFFS_BASE__PIXEL_BLEND__SRC: + return wuffs_base__pixel_swizzler__bgr_565__rgba_premul__src; + case WUFFS_BASE__PIXEL_BLEND__SRC_OVER: + return wuffs_base__pixel_swizzler__bgr_565__rgba_premul__src_over; + } + return NULL; + + case WUFFS_BASE__PIXEL_FORMAT__BGR: + switch (blend) { + case WUFFS_BASE__PIXEL_BLEND__SRC: + return wuffs_base__pixel_swizzler__bgr__rgba_premul__src; + case WUFFS_BASE__PIXEL_BLEND__SRC_OVER: + return wuffs_base__pixel_swizzler__bgr__rgba_premul__src_over; + } + return NULL; + + case WUFFS_BASE__PIXEL_FORMAT__BGRA_NONPREMUL: + switch (blend) { + case WUFFS_BASE__PIXEL_BLEND__SRC: + return wuffs_base__pixel_swizzler__bgra_nonpremul__rgba_premul__src; + case WUFFS_BASE__PIXEL_BLEND__SRC_OVER: + return wuffs_base__pixel_swizzler__bgra_nonpremul__rgba_premul__src_over; + } + return NULL; + + case WUFFS_BASE__PIXEL_FORMAT__BGRA_NONPREMUL_4X16LE: + switch (blend) { + case WUFFS_BASE__PIXEL_BLEND__SRC: + return wuffs_base__pixel_swizzler__bgra_nonpremul_4x16le__rgba_premul__src; + case WUFFS_BASE__PIXEL_BLEND__SRC_OVER: + return wuffs_base__pixel_swizzler__bgra_nonpremul_4x16le__rgba_premul__src_over; + } + return NULL; + + case WUFFS_BASE__PIXEL_FORMAT__BGRA_PREMUL: + switch (blend) { + case WUFFS_BASE__PIXEL_BLEND__SRC: +#if defined(WUFFS_BASE__CPU_ARCH__X86_FAMILY) + if (wuffs_base__cpu_arch__have_x86_sse42()) { + return wuffs_base__pixel_swizzler__swap_rgbx_bgrx__sse42; + } +#endif + return wuffs_base__pixel_swizzler__swap_rgbx_bgrx; + case WUFFS_BASE__PIXEL_BLEND__SRC_OVER: + return wuffs_base__pixel_swizzler__bgra_premul__rgba_premul__src_over; + } + return NULL; + + case WUFFS_BASE__PIXEL_FORMAT__RGBA_NONPREMUL: + switch (blend) { + case WUFFS_BASE__PIXEL_BLEND__SRC: + return wuffs_base__pixel_swizzler__bgra_nonpremul__bgra_premul__src; + case WUFFS_BASE__PIXEL_BLEND__SRC_OVER: + return wuffs_base__pixel_swizzler__bgra_nonpremul__bgra_premul__src_over; + } + return NULL; + + case WUFFS_BASE__PIXEL_FORMAT__RGBA_PREMUL: + switch (blend) { + case WUFFS_BASE__PIXEL_BLEND__SRC: + return wuffs_base__pixel_swizzler__copy_4_4; + case WUFFS_BASE__PIXEL_BLEND__SRC_OVER: + return wuffs_base__pixel_swizzler__bgra_premul__bgra_premul__src_over; + } + return NULL; + } + return NULL; +} + +// -------- + +WUFFS_BASE__MAYBE_STATIC wuffs_base__status // +wuffs_base__pixel_swizzler__prepare(wuffs_base__pixel_swizzler* p, + wuffs_base__pixel_format dst_pixfmt, + wuffs_base__slice_u8 dst_palette, + wuffs_base__pixel_format src_pixfmt, + wuffs_base__slice_u8 src_palette, + wuffs_base__pixel_blend blend) { + if (!p) { + return wuffs_base__make_status(wuffs_base__error__bad_receiver); + } + p->private_impl.func = NULL; + p->private_impl.transparent_black_func = NULL; + p->private_impl.dst_pixfmt_bytes_per_pixel = 0; + p->private_impl.src_pixfmt_bytes_per_pixel = 0; + + wuffs_base__pixel_swizzler__func func = NULL; + wuffs_base__pixel_swizzler__transparent_black_func transparent_black_func = + NULL; + + uint32_t dst_pixfmt_bits_per_pixel = + wuffs_base__pixel_format__bits_per_pixel(&dst_pixfmt); + if ((dst_pixfmt_bits_per_pixel == 0) || + ((dst_pixfmt_bits_per_pixel & 7) != 0)) { + return wuffs_base__make_status( + wuffs_base__error__unsupported_pixel_swizzler_option); + } + + uint32_t src_pixfmt_bits_per_pixel = + wuffs_base__pixel_format__bits_per_pixel(&src_pixfmt); + if ((src_pixfmt_bits_per_pixel == 0) || + ((src_pixfmt_bits_per_pixel & 7) != 0)) { + return wuffs_base__make_status( + wuffs_base__error__unsupported_pixel_swizzler_option); + } + + // TODO: support many more formats. + + switch (blend) { + case WUFFS_BASE__PIXEL_BLEND__SRC: + transparent_black_func = + wuffs_base__pixel_swizzler__transparent_black_src; + break; + + case WUFFS_BASE__PIXEL_BLEND__SRC_OVER: + transparent_black_func = + wuffs_base__pixel_swizzler__transparent_black_src_over; + break; + } + + switch (src_pixfmt.repr) { + case WUFFS_BASE__PIXEL_FORMAT__Y: + func = wuffs_base__pixel_swizzler__prepare__y(p, dst_pixfmt, dst_palette, + src_palette, blend); + break; + + case WUFFS_BASE__PIXEL_FORMAT__Y_16BE: + func = wuffs_base__pixel_swizzler__prepare__y_16be( + p, dst_pixfmt, dst_palette, src_palette, blend); + break; + + case WUFFS_BASE__PIXEL_FORMAT__INDEXED__BGRA_NONPREMUL: + func = wuffs_base__pixel_swizzler__prepare__indexed__bgra_nonpremul( + p, dst_pixfmt, dst_palette, src_palette, blend); + break; + + case WUFFS_BASE__PIXEL_FORMAT__INDEXED__BGRA_BINARY: + func = wuffs_base__pixel_swizzler__prepare__indexed__bgra_binary( + p, dst_pixfmt, dst_palette, src_palette, blend); + break; + + case WUFFS_BASE__PIXEL_FORMAT__BGR_565: + func = wuffs_base__pixel_swizzler__prepare__bgr_565( + p, dst_pixfmt, dst_palette, src_palette, blend); + break; + + case WUFFS_BASE__PIXEL_FORMAT__BGR: + func = wuffs_base__pixel_swizzler__prepare__bgr( + p, dst_pixfmt, dst_palette, src_palette, blend); + break; + + case WUFFS_BASE__PIXEL_FORMAT__BGRA_NONPREMUL: + func = wuffs_base__pixel_swizzler__prepare__bgra_nonpremul( + p, dst_pixfmt, dst_palette, src_palette, blend); + break; + + case WUFFS_BASE__PIXEL_FORMAT__BGRA_NONPREMUL_4X16LE: + func = wuffs_base__pixel_swizzler__prepare__bgra_nonpremul_4x16le( + p, dst_pixfmt, dst_palette, src_palette, blend); + break; + + case WUFFS_BASE__PIXEL_FORMAT__BGRA_PREMUL: + func = wuffs_base__pixel_swizzler__prepare__bgra_premul( + p, dst_pixfmt, dst_palette, src_palette, blend); + break; + + case WUFFS_BASE__PIXEL_FORMAT__BGRX: + func = wuffs_base__pixel_swizzler__prepare__bgrx( + p, dst_pixfmt, dst_palette, src_palette, blend); + break; + + case WUFFS_BASE__PIXEL_FORMAT__RGB: + func = wuffs_base__pixel_swizzler__prepare__rgb( + p, dst_pixfmt, dst_palette, src_palette, blend); + break; + + case WUFFS_BASE__PIXEL_FORMAT__RGBA_NONPREMUL: + func = wuffs_base__pixel_swizzler__prepare__rgba_nonpremul( + p, dst_pixfmt, dst_palette, src_palette, blend); + break; + + case WUFFS_BASE__PIXEL_FORMAT__RGBA_PREMUL: + func = wuffs_base__pixel_swizzler__prepare__rgba_premul( + p, dst_pixfmt, dst_palette, src_palette, blend); + break; + } + + p->private_impl.func = func; + p->private_impl.transparent_black_func = transparent_black_func; + p->private_impl.dst_pixfmt_bytes_per_pixel = dst_pixfmt_bits_per_pixel / 8; + p->private_impl.src_pixfmt_bytes_per_pixel = src_pixfmt_bits_per_pixel / 8; + return wuffs_base__make_status( + func ? NULL : wuffs_base__error__unsupported_pixel_swizzler_option); +} + +WUFFS_BASE__MAYBE_STATIC uint64_t // +wuffs_base__pixel_swizzler__limited_swizzle_u32_interleaved_from_reader( + const wuffs_base__pixel_swizzler* p, + uint32_t up_to_num_pixels, + wuffs_base__slice_u8 dst, + wuffs_base__slice_u8 dst_palette, + const uint8_t** ptr_iop_r, + const uint8_t* io2_r) { + if (p && p->private_impl.func) { + const uint8_t* iop_r = *ptr_iop_r; + uint64_t src_len = wuffs_base__u64__min( + ((uint64_t)up_to_num_pixels) * + ((uint64_t)p->private_impl.src_pixfmt_bytes_per_pixel), + ((uint64_t)(io2_r - iop_r))); + uint64_t n = + (*p->private_impl.func)(dst.ptr, dst.len, dst_palette.ptr, + dst_palette.len, iop_r, (size_t)src_len); + *ptr_iop_r += n * p->private_impl.src_pixfmt_bytes_per_pixel; + return n; + } + return 0; +} + +WUFFS_BASE__MAYBE_STATIC uint64_t // +wuffs_base__pixel_swizzler__swizzle_interleaved_from_reader( + const wuffs_base__pixel_swizzler* p, + wuffs_base__slice_u8 dst, + wuffs_base__slice_u8 dst_palette, + const uint8_t** ptr_iop_r, + const uint8_t* io2_r) { + if (p && p->private_impl.func) { + const uint8_t* iop_r = *ptr_iop_r; + uint64_t src_len = ((uint64_t)(io2_r - iop_r)); + uint64_t n = + (*p->private_impl.func)(dst.ptr, dst.len, dst_palette.ptr, + dst_palette.len, iop_r, (size_t)src_len); + *ptr_iop_r += n * p->private_impl.src_pixfmt_bytes_per_pixel; + return n; + } + return 0; +} + +WUFFS_BASE__MAYBE_STATIC uint64_t // +wuffs_base__pixel_swizzler__swizzle_interleaved_from_slice( + const wuffs_base__pixel_swizzler* p, + wuffs_base__slice_u8 dst, + wuffs_base__slice_u8 dst_palette, + wuffs_base__slice_u8 src) { + if (p && p->private_impl.func) { + return (*p->private_impl.func)(dst.ptr, dst.len, dst_palette.ptr, + dst_palette.len, src.ptr, src.len); + } + return 0; +} + +WUFFS_BASE__MAYBE_STATIC uint64_t // +wuffs_base__pixel_swizzler__swizzle_interleaved_transparent_black( + const wuffs_base__pixel_swizzler* p, + wuffs_base__slice_u8 dst, + wuffs_base__slice_u8 dst_palette, + uint64_t num_pixels) { + if (p && p->private_impl.transparent_black_func) { + return (*p->private_impl.transparent_black_func)( + dst.ptr, dst.len, dst_palette.ptr, dst_palette.len, num_pixels, + p->private_impl.dst_pixfmt_bytes_per_pixel); + } + return 0; +} + +#endif // !defined(WUFFS_CONFIG__MODULES) || + // defined(WUFFS_CONFIG__MODULE__BASE) || + // defined(WUFFS_CONFIG__MODULE__BASE__PIXCONV) + +#if !defined(WUFFS_CONFIG__MODULES) || defined(WUFFS_CONFIG__MODULE__BASE) || \ + defined(WUFFS_CONFIG__MODULE__BASE__UTF8) + +// ---------------- Unicode and UTF-8 + +WUFFS_BASE__MAYBE_STATIC size_t // +wuffs_base__utf_8__encode(wuffs_base__slice_u8 dst, uint32_t code_point) { + if (code_point <= 0x7F) { + if (dst.len >= 1) { + dst.ptr[0] = (uint8_t)(code_point); + return 1; + } + + } else if (code_point <= 0x07FF) { + if (dst.len >= 2) { + dst.ptr[0] = (uint8_t)(0xC0 | ((code_point >> 6))); + dst.ptr[1] = (uint8_t)(0x80 | ((code_point >> 0) & 0x3F)); + return 2; + } + + } else if (code_point <= 0xFFFF) { + if ((dst.len >= 3) && ((code_point < 0xD800) || (0xDFFF < code_point))) { + dst.ptr[0] = (uint8_t)(0xE0 | ((code_point >> 12))); + dst.ptr[1] = (uint8_t)(0x80 | ((code_point >> 6) & 0x3F)); + dst.ptr[2] = (uint8_t)(0x80 | ((code_point >> 0) & 0x3F)); + return 3; + } + + } else if (code_point <= 0x10FFFF) { + if (dst.len >= 4) { + dst.ptr[0] = (uint8_t)(0xF0 | ((code_point >> 18))); + dst.ptr[1] = (uint8_t)(0x80 | ((code_point >> 12) & 0x3F)); + dst.ptr[2] = (uint8_t)(0x80 | ((code_point >> 6) & 0x3F)); + dst.ptr[3] = (uint8_t)(0x80 | ((code_point >> 0) & 0x3F)); + return 4; + } + } + + return 0; +} + +// wuffs_base__utf_8__byte_length_minus_1 is the byte length (minus 1) of a +// UTF-8 encoded code point, based on the encoding's initial byte. +// - 0x00 is 1-byte UTF-8 (ASCII). +// - 0x01 is the start of 2-byte UTF-8. +// - 0x02 is the start of 3-byte UTF-8. +// - 0x03 is the start of 4-byte UTF-8. +// - 0x40 is a UTF-8 tail byte. +// - 0x80 is invalid UTF-8. +// +// RFC 3629 (UTF-8) gives this grammar for valid UTF-8: +// UTF8-1 = %x00-7F +// UTF8-2 = %xC2-DF UTF8-tail +// UTF8-3 = %xE0 %xA0-BF UTF8-tail / %xE1-EC 2( UTF8-tail ) / +// %xED %x80-9F UTF8-tail / %xEE-EF 2( UTF8-tail ) +// UTF8-4 = %xF0 %x90-BF 2( UTF8-tail ) / %xF1-F3 3( UTF8-tail ) / +// %xF4 %x80-8F 2( UTF8-tail ) +// UTF8-tail = %x80-BF +static const uint8_t wuffs_base__utf_8__byte_length_minus_1[256] = { + // 0 1 2 3 4 5 6 7 + // 8 9 A B C D E F + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x00 ..= 0x07. + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x08 ..= 0x0F. + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x10 ..= 0x17. + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x18 ..= 0x1F. + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x20 ..= 0x27. + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x28 ..= 0x2F. + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x30 ..= 0x37. + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x38 ..= 0x3F. + + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x40 ..= 0x47. + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x48 ..= 0x4F. + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x50 ..= 0x57. + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x58 ..= 0x5F. + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x60 ..= 0x67. + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x68 ..= 0x6F. + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x70 ..= 0x77. + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x78 ..= 0x7F. + + 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, // 0x80 ..= 0x87. + 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, // 0x88 ..= 0x8F. + 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, // 0x90 ..= 0x97. + 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, // 0x98 ..= 0x9F. + 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, // 0xA0 ..= 0xA7. + 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, // 0xA8 ..= 0xAF. + 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, // 0xB0 ..= 0xB7. + 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, // 0xB8 ..= 0xBF. + + 0x80, 0x80, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, // 0xC0 ..= 0xC7. + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, // 0xC8 ..= 0xCF. + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, // 0xD0 ..= 0xD7. + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, // 0xD8 ..= 0xDF. + 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, // 0xE0 ..= 0xE7. + 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, // 0xE8 ..= 0xEF. + 0x03, 0x03, 0x03, 0x03, 0x03, 0x80, 0x80, 0x80, // 0xF0 ..= 0xF7. + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, // 0xF8 ..= 0xFF. + // 0 1 2 3 4 5 6 7 + // 8 9 A B C D E F +}; + +WUFFS_BASE__MAYBE_STATIC wuffs_base__utf_8__next__output // +wuffs_base__utf_8__next(const uint8_t* s_ptr, size_t s_len) { + if (s_len == 0) { + return wuffs_base__make_utf_8__next__output(0, 0); + } + uint32_t c = s_ptr[0]; + switch (wuffs_base__utf_8__byte_length_minus_1[c & 0xFF]) { + case 0: + return wuffs_base__make_utf_8__next__output(c, 1); + + case 1: + if (s_len < 2) { + break; + } + c = wuffs_base__peek_u16le__no_bounds_check(s_ptr); + if ((c & 0xC000) != 0x8000) { + break; + } + c = (0x0007C0 & (c << 6)) | (0x00003F & (c >> 8)); + return wuffs_base__make_utf_8__next__output(c, 2); + + case 2: + if (s_len < 3) { + break; + } + c = wuffs_base__peek_u24le__no_bounds_check(s_ptr); + if ((c & 0xC0C000) != 0x808000) { + break; + } + c = (0x00F000 & (c << 12)) | (0x000FC0 & (c >> 2)) | + (0x00003F & (c >> 16)); + if ((c <= 0x07FF) || ((0xD800 <= c) && (c <= 0xDFFF))) { + break; + } + return wuffs_base__make_utf_8__next__output(c, 3); + + case 3: + if (s_len < 4) { + break; + } + c = wuffs_base__peek_u32le__no_bounds_check(s_ptr); + if ((c & 0xC0C0C000) != 0x80808000) { + break; + } + c = (0x1C0000 & (c << 18)) | (0x03F000 & (c << 4)) | + (0x000FC0 & (c >> 10)) | (0x00003F & (c >> 24)); + if ((c <= 0xFFFF) || (0x110000 <= c)) { + break; + } + return wuffs_base__make_utf_8__next__output(c, 4); + } + + return wuffs_base__make_utf_8__next__output( + WUFFS_BASE__UNICODE_REPLACEMENT_CHARACTER, 1); +} + +WUFFS_BASE__MAYBE_STATIC wuffs_base__utf_8__next__output // +wuffs_base__utf_8__next_from_end(const uint8_t* s_ptr, size_t s_len) { + if (s_len == 0) { + return wuffs_base__make_utf_8__next__output(0, 0); + } + const uint8_t* ptr = &s_ptr[s_len - 1]; + if (*ptr < 0x80) { + return wuffs_base__make_utf_8__next__output(*ptr, 1); + + } else if (*ptr < 0xC0) { + const uint8_t* too_far = &s_ptr[(s_len > 4) ? (s_len - 4) : 0]; + uint32_t n = 1; + while (ptr != too_far) { + ptr--; + n++; + if (*ptr < 0x80) { + break; + } else if (*ptr < 0xC0) { + continue; + } + wuffs_base__utf_8__next__output o = wuffs_base__utf_8__next(ptr, n); + if (o.byte_length != n) { + break; + } + return o; + } + } + + return wuffs_base__make_utf_8__next__output( + WUFFS_BASE__UNICODE_REPLACEMENT_CHARACTER, 1); +} + +WUFFS_BASE__MAYBE_STATIC size_t // +wuffs_base__utf_8__longest_valid_prefix(const uint8_t* s_ptr, size_t s_len) { + // TODO: possibly optimize the all-ASCII case (4 or 8 bytes at a time). + // + // TODO: possibly optimize this by manually inlining the + // wuffs_base__utf_8__next calls. + size_t original_len = s_len; + while (s_len > 0) { + wuffs_base__utf_8__next__output o = wuffs_base__utf_8__next(s_ptr, s_len); + if ((o.code_point > 0x7F) && (o.byte_length == 1)) { + break; + } + s_ptr += o.byte_length; + s_len -= o.byte_length; + } + return original_len - s_len; +} + +WUFFS_BASE__MAYBE_STATIC size_t // +wuffs_base__ascii__longest_valid_prefix(const uint8_t* s_ptr, size_t s_len) { + // TODO: possibly optimize this by checking 4 or 8 bytes at a time. + const uint8_t* original_ptr = s_ptr; + const uint8_t* p = s_ptr; + const uint8_t* q = s_ptr + s_len; + for (; (p != q) && ((*p & 0x80) == 0); p++) { + } + return (size_t)(p - original_ptr); +} + +#endif // !defined(WUFFS_CONFIG__MODULES) || + // defined(WUFFS_CONFIG__MODULE__BASE) || + // defined(WUFFS_CONFIG__MODULE__BASE__UTF8) + +#ifdef __cplusplus +} // extern "C" +#endif + +#if !defined(WUFFS_CONFIG__MODULES) || defined(WUFFS_CONFIG__MODULE__ADLER32) + +// ---------------- Status Codes Implementations + +// ---------------- Private Consts + +// ---------------- Private Initializer Prototypes + +// ---------------- Private Function Prototypes + +static wuffs_base__empty_struct +wuffs_adler32__hasher__up( + wuffs_adler32__hasher* self, + wuffs_base__slice_u8 a_x); + +static wuffs_base__empty_struct +wuffs_adler32__hasher__up__choosy_default( + wuffs_adler32__hasher* self, + wuffs_base__slice_u8 a_x); + +#if defined(WUFFS_BASE__CPU_ARCH__ARM_NEON) +static wuffs_base__empty_struct +wuffs_adler32__hasher__up_arm_neon( + wuffs_adler32__hasher* self, + wuffs_base__slice_u8 a_x); +#endif // defined(WUFFS_BASE__CPU_ARCH__ARM_NEON) + +#if defined(WUFFS_BASE__CPU_ARCH__X86_FAMILY) +static wuffs_base__empty_struct +wuffs_adler32__hasher__up_x86_sse42( + wuffs_adler32__hasher* self, + wuffs_base__slice_u8 a_x); +#endif // defined(WUFFS_BASE__CPU_ARCH__X86_FAMILY) + +// ---------------- VTables + +const wuffs_base__hasher_u32__func_ptrs +wuffs_adler32__hasher__func_ptrs_for__wuffs_base__hasher_u32 = { + (wuffs_base__empty_struct(*)(void*, + uint32_t, + bool))(&wuffs_adler32__hasher__set_quirk_enabled), + (uint32_t(*)(void*, + wuffs_base__slice_u8))(&wuffs_adler32__hasher__update_u32), +}; + +// ---------------- Initializer Implementations + +wuffs_base__status WUFFS_BASE__WARN_UNUSED_RESULT +wuffs_adler32__hasher__initialize( + wuffs_adler32__hasher* self, + size_t sizeof_star_self, + uint64_t wuffs_version, + uint32_t options){ + if (!self) { + return wuffs_base__make_status(wuffs_base__error__bad_receiver); + } + if (sizeof(*self) != sizeof_star_self) { + return wuffs_base__make_status(wuffs_base__error__bad_sizeof_receiver); + } + if (((wuffs_version >> 32) != WUFFS_VERSION_MAJOR) || + (((wuffs_version >> 16) & 0xFFFF) > WUFFS_VERSION_MINOR)) { + return wuffs_base__make_status(wuffs_base__error__bad_wuffs_version); + } + + if ((options & WUFFS_INITIALIZE__ALREADY_ZEROED) != 0) { + // The whole point of this if-check is to detect an uninitialized *self. + // We disable the warning on GCC. Clang-5.0 does not have this warning. +#if !defined(__clang__) && defined(__GNUC__) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wmaybe-uninitialized" +#endif + if (self->private_impl.magic != 0) { + return wuffs_base__make_status(wuffs_base__error__initialize_falsely_claimed_already_zeroed); + } +#if !defined(__clang__) && defined(__GNUC__) +#pragma GCC diagnostic pop +#endif + } else { + if ((options & WUFFS_INITIALIZE__LEAVE_INTERNAL_BUFFERS_UNINITIALIZED) == 0) { + memset(self, 0, sizeof(*self)); + options |= WUFFS_INITIALIZE__ALREADY_ZEROED; + } else { + memset(&(self->private_impl), 0, sizeof(self->private_impl)); + } + } + + self->private_impl.choosy_up = &wuffs_adler32__hasher__up__choosy_default; + + self->private_impl.magic = WUFFS_BASE__MAGIC; + self->private_impl.vtable_for__wuffs_base__hasher_u32.vtable_name = + wuffs_base__hasher_u32__vtable_name; + self->private_impl.vtable_for__wuffs_base__hasher_u32.function_pointers = + (const void*)(&wuffs_adler32__hasher__func_ptrs_for__wuffs_base__hasher_u32); + return wuffs_base__make_status(NULL); +} + +wuffs_adler32__hasher* +wuffs_adler32__hasher__alloc() { + wuffs_adler32__hasher* x = + (wuffs_adler32__hasher*)(calloc(sizeof(wuffs_adler32__hasher), 1)); + if (!x) { + return NULL; + } + if (wuffs_adler32__hasher__initialize( + x, sizeof(wuffs_adler32__hasher), WUFFS_VERSION, WUFFS_INITIALIZE__ALREADY_ZEROED).repr) { + free(x); + return NULL; + } + return x; +} + +size_t +sizeof__wuffs_adler32__hasher() { + return sizeof(wuffs_adler32__hasher); +} + +// ---------------- Function Implementations + +// -------- func adler32.hasher.set_quirk_enabled + +WUFFS_BASE__MAYBE_STATIC wuffs_base__empty_struct +wuffs_adler32__hasher__set_quirk_enabled( + wuffs_adler32__hasher* self, + uint32_t a_quirk, + bool a_enabled) { + return wuffs_base__make_empty_struct(); +} + +// -------- func adler32.hasher.update_u32 + +WUFFS_BASE__MAYBE_STATIC uint32_t +wuffs_adler32__hasher__update_u32( + wuffs_adler32__hasher* self, + wuffs_base__slice_u8 a_x) { + if (!self) { + return 0; + } + if (self->private_impl.magic != WUFFS_BASE__MAGIC) { + return 0; + } + + if ( ! self->private_impl.f_started) { + self->private_impl.f_started = true; + self->private_impl.f_state = 1; + self->private_impl.choosy_up = ( +#if defined(WUFFS_BASE__CPU_ARCH__ARM_NEON) + wuffs_base__cpu_arch__have_arm_neon() ? &wuffs_adler32__hasher__up_arm_neon : +#endif +#if defined(WUFFS_BASE__CPU_ARCH__X86_FAMILY) + wuffs_base__cpu_arch__have_x86_sse42() ? &wuffs_adler32__hasher__up_x86_sse42 : +#endif + self->private_impl.choosy_up); + } + wuffs_adler32__hasher__up(self, a_x); + return self->private_impl.f_state; +} + +// -------- func adler32.hasher.up + +static wuffs_base__empty_struct +wuffs_adler32__hasher__up( + wuffs_adler32__hasher* self, + wuffs_base__slice_u8 a_x) { + return (*self->private_impl.choosy_up)(self, a_x); +} + +static wuffs_base__empty_struct +wuffs_adler32__hasher__up__choosy_default( + wuffs_adler32__hasher* self, + wuffs_base__slice_u8 a_x) { + uint32_t v_s1 = 0; + uint32_t v_s2 = 0; + wuffs_base__slice_u8 v_remaining = {0}; + wuffs_base__slice_u8 v_p = {0}; + + v_s1 = ((self->private_impl.f_state) & 0xFFFF); + v_s2 = ((self->private_impl.f_state) >> (32 - (16))); + while (((uint64_t)(a_x.len)) > 0) { + v_remaining = wuffs_base__slice_u8__subslice_j(a_x, 0); + if (((uint64_t)(a_x.len)) > 5552) { + v_remaining = wuffs_base__slice_u8__subslice_i(a_x, 5552); + a_x = wuffs_base__slice_u8__subslice_j(a_x, 5552); + } + { + wuffs_base__slice_u8 i_slice_p = a_x; + v_p.ptr = i_slice_p.ptr; + v_p.len = 1; + uint8_t* i_end0_p = v_p.ptr + (((i_slice_p.len - (size_t)(v_p.ptr - i_slice_p.ptr)) / 8) * 8); + while (v_p.ptr < i_end0_p) { + v_s1 += ((uint32_t)(v_p.ptr[0])); + v_s2 += v_s1; + v_p.ptr += 1; + v_s1 += ((uint32_t)(v_p.ptr[0])); + v_s2 += v_s1; + v_p.ptr += 1; + v_s1 += ((uint32_t)(v_p.ptr[0])); + v_s2 += v_s1; + v_p.ptr += 1; + v_s1 += ((uint32_t)(v_p.ptr[0])); + v_s2 += v_s1; + v_p.ptr += 1; + v_s1 += ((uint32_t)(v_p.ptr[0])); + v_s2 += v_s1; + v_p.ptr += 1; + v_s1 += ((uint32_t)(v_p.ptr[0])); + v_s2 += v_s1; + v_p.ptr += 1; + v_s1 += ((uint32_t)(v_p.ptr[0])); + v_s2 += v_s1; + v_p.ptr += 1; + v_s1 += ((uint32_t)(v_p.ptr[0])); + v_s2 += v_s1; + v_p.ptr += 1; + } + v_p.len = 1; + uint8_t* i_end1_p = i_slice_p.ptr + i_slice_p.len; + while (v_p.ptr < i_end1_p) { + v_s1 += ((uint32_t)(v_p.ptr[0])); + v_s2 += v_s1; + v_p.ptr += 1; + } + v_p.len = 0; + } + v_s1 %= 65521; + v_s2 %= 65521; + a_x = v_remaining; + } + self->private_impl.f_state = (((v_s2 & 65535) << 16) | (v_s1 & 65535)); + return wuffs_base__make_empty_struct(); +} + +// ‼ WUFFS MULTI-FILE SECTION +arm_neon +// -------- func adler32.hasher.up_arm_neon + +#if defined(WUFFS_BASE__CPU_ARCH__ARM_NEON) +static wuffs_base__empty_struct +wuffs_adler32__hasher__up_arm_neon( + wuffs_adler32__hasher* self, + wuffs_base__slice_u8 a_x) { + uint32_t v_s1 = 0; + uint32_t v_s2 = 0; + wuffs_base__slice_u8 v_remaining = {0}; + wuffs_base__slice_u8 v_p = {0}; + uint8x16_t v_p__left = {0}; + uint8x16_t v_p_right = {0}; + uint32x4_t v_v1 = {0}; + uint32x4_t v_v2 = {0}; + uint16x8_t v_col0 = {0}; + uint16x8_t v_col1 = {0}; + uint16x8_t v_col2 = {0}; + uint16x8_t v_col3 = {0}; + uint32x2_t v_sum1 = {0}; + uint32x2_t v_sum2 = {0}; + uint32x2_t v_sum12 = {0}; + uint32_t v_num_iterate_bytes = 0; + uint64_t v_tail_index = 0; + + v_s1 = ((self->private_impl.f_state) & 0xFFFF); + v_s2 = ((self->private_impl.f_state) >> (32 - (16))); + while ((((uint64_t)(a_x.len)) > 0) && ((15 & ((uint32_t)(0xFFF & (uintptr_t)(a_x.ptr)))) != 0)) { + v_s1 += ((uint32_t)(a_x.ptr[0])); + v_s2 += v_s1; + a_x = wuffs_base__slice_u8__subslice_i(a_x, 1); + } + v_s1 %= 65521; + v_s2 %= 65521; + while (((uint64_t)(a_x.len)) > 0) { + v_remaining = wuffs_base__slice_u8__subslice_j(a_x, 0); + if (((uint64_t)(a_x.len)) > 5536) { + v_remaining = wuffs_base__slice_u8__subslice_i(a_x, 5536); + a_x = wuffs_base__slice_u8__subslice_j(a_x, 5536); + } + v_num_iterate_bytes = ((uint32_t)((((uint64_t)(a_x.len)) & 4294967264))); + v_s2 += ((uint32_t)(v_s1 * v_num_iterate_bytes)); + v_v1 = vdupq_n_u32(0); + v_v2 = vdupq_n_u32(0); + v_col0 = vdupq_n_u16(0); + v_col1 = vdupq_n_u16(0); + v_col2 = vdupq_n_u16(0); + v_col3 = vdupq_n_u16(0); + { + wuffs_base__slice_u8 i_slice_p = a_x; + v_p.ptr = i_slice_p.ptr; + v_p.len = 32; + uint8_t* i_end0_p = v_p.ptr + (((i_slice_p.len - (size_t)(v_p.ptr - i_slice_p.ptr)) / 32) * 32); + while (v_p.ptr < i_end0_p) { + v_p__left = vld1q_u8(v_p.ptr); + v_p_right = vld1q_u8(v_p.ptr + 16); + v_v2 = vaddq_u32(v_v2, v_v1); + v_v1 = vpadalq_u16(v_v1, vpadalq_u8(vpaddlq_u8(v_p__left), v_p_right)); + v_col0 = vaddw_u8(v_col0, vget_low_u8(v_p__left)); + v_col1 = vaddw_u8(v_col1, vget_high_u8(v_p__left)); + v_col2 = vaddw_u8(v_col2, vget_low_u8(v_p_right)); + v_col3 = vaddw_u8(v_col3, vget_high_u8(v_p_right)); + v_p.ptr += 32; + } + v_p.len = 0; + } + v_v2 = vshlq_n_u32(v_v2, 5); + v_v2 = vmlal_u16(v_v2, vget_low_u16(v_col0), ((uint16x4_t){32, 31, 30, 29})); + v_v2 = vmlal_u16(v_v2, vget_high_u16(v_col0), ((uint16x4_t){28, 27, 26, 25})); + v_v2 = vmlal_u16(v_v2, vget_low_u16(v_col1), ((uint16x4_t){24, 23, 22, 21})); + v_v2 = vmlal_u16(v_v2, vget_high_u16(v_col1), ((uint16x4_t){20, 19, 18, 17})); + v_v2 = vmlal_u16(v_v2, vget_low_u16(v_col2), ((uint16x4_t){16, 15, 14, 13})); + v_v2 = vmlal_u16(v_v2, vget_high_u16(v_col2), ((uint16x4_t){12, 11, 10, 9})); + v_v2 = vmlal_u16(v_v2, vget_low_u16(v_col3), ((uint16x4_t){8, 7, 6, 5})); + v_v2 = vmlal_u16(v_v2, vget_high_u16(v_col3), ((uint16x4_t){4, 3, 2, 1})); + v_sum1 = vpadd_u32(vget_low_u32(v_v1), vget_high_u32(v_v1)); + v_sum2 = vpadd_u32(vget_low_u32(v_v2), vget_high_u32(v_v2)); + v_sum12 = vpadd_u32(v_sum1, v_sum2); + v_s1 += vget_lane_u32(v_sum12, 0); + v_s2 += vget_lane_u32(v_sum12, 1); + v_tail_index = (((uint64_t)(a_x.len)) & 18446744073709551584u); + if (v_tail_index < ((uint64_t)(a_x.len))) { + { + wuffs_base__slice_u8 i_slice_p = wuffs_base__slice_u8__subslice_i(a_x, v_tail_index); + v_p.ptr = i_slice_p.ptr; + v_p.len = 1; + uint8_t* i_end0_p = i_slice_p.ptr + i_slice_p.len; + while (v_p.ptr < i_end0_p) { + v_s1 += ((uint32_t)(v_p.ptr[0])); + v_s2 += v_s1; + v_p.ptr += 1; + } + v_p.len = 0; + } + } + v_s1 %= 65521; + v_s2 %= 65521; + a_x = v_remaining; + } + self->private_impl.f_state = (((v_s2 & 65535) << 16) | (v_s1 & 65535)); + return wuffs_base__make_empty_struct(); +} +#endif // defined(WUFFS_BASE__CPU_ARCH__ARM_NEON) +// ‼ WUFFS MULTI-FILE SECTION -arm_neon + +// ‼ WUFFS MULTI-FILE SECTION +x86_sse42 +// -------- func adler32.hasher.up_x86_sse42 + +#if defined(WUFFS_BASE__CPU_ARCH__X86_FAMILY) +WUFFS_BASE__MAYBE_ATTRIBUTE_TARGET("pclmul,popcnt,sse4.2") +static wuffs_base__empty_struct +wuffs_adler32__hasher__up_x86_sse42( + wuffs_adler32__hasher* self, + wuffs_base__slice_u8 a_x) { + uint32_t v_s1 = 0; + uint32_t v_s2 = 0; + wuffs_base__slice_u8 v_remaining = {0}; + wuffs_base__slice_u8 v_p = {0}; + __m128i v_zeroes = {0}; + __m128i v_ones = {0}; + __m128i v_weights__left = {0}; + __m128i v_weights_right = {0}; + __m128i v_q__left = {0}; + __m128i v_q_right = {0}; + __m128i v_v1 = {0}; + __m128i v_v2 = {0}; + __m128i v_v2j = {0}; + __m128i v_v2k = {0}; + uint32_t v_num_iterate_bytes = 0; + uint64_t v_tail_index = 0; + + v_zeroes = _mm_set1_epi16((int16_t)(0)); + v_ones = _mm_set1_epi16((int16_t)(1)); + v_weights__left = _mm_set_epi8((int8_t)(17), (int8_t)(18), (int8_t)(19), (int8_t)(20), (int8_t)(21), (int8_t)(22), (int8_t)(23), (int8_t)(24), (int8_t)(25), (int8_t)(26), (int8_t)(27), (int8_t)(28), (int8_t)(29), (int8_t)(30), (int8_t)(31), (int8_t)(32)); + v_weights_right = _mm_set_epi8((int8_t)(1), (int8_t)(2), (int8_t)(3), (int8_t)(4), (int8_t)(5), (int8_t)(6), (int8_t)(7), (int8_t)(8), (int8_t)(9), (int8_t)(10), (int8_t)(11), (int8_t)(12), (int8_t)(13), (int8_t)(14), (int8_t)(15), (int8_t)(16)); + v_s1 = ((self->private_impl.f_state) & 0xFFFF); + v_s2 = ((self->private_impl.f_state) >> (32 - (16))); + while (((uint64_t)(a_x.len)) > 0) { + v_remaining = wuffs_base__slice_u8__subslice_j(a_x, 0); + if (((uint64_t)(a_x.len)) > 5536) { + v_remaining = wuffs_base__slice_u8__subslice_i(a_x, 5536); + a_x = wuffs_base__slice_u8__subslice_j(a_x, 5536); + } + v_num_iterate_bytes = ((uint32_t)((((uint64_t)(a_x.len)) & 4294967264))); + v_s2 += ((uint32_t)(v_s1 * v_num_iterate_bytes)); + v_v1 = _mm_setzero_si128(); + v_v2j = _mm_setzero_si128(); + v_v2k = _mm_setzero_si128(); + { + wuffs_base__slice_u8 i_slice_p = a_x; + v_p.ptr = i_slice_p.ptr; + v_p.len = 32; + uint8_t* i_end0_p = v_p.ptr + (((i_slice_p.len - (size_t)(v_p.ptr - i_slice_p.ptr)) / 32) * 32); + while (v_p.ptr < i_end0_p) { + v_q__left = _mm_lddqu_si128((const __m128i*)(const void*)(v_p.ptr)); + v_q_right = _mm_lddqu_si128((const __m128i*)(const void*)(v_p.ptr + 16)); + v_v2j = _mm_add_epi32(v_v2j, v_v1); + v_v1 = _mm_add_epi32(v_v1, _mm_sad_epu8(v_q__left, v_zeroes)); + v_v1 = _mm_add_epi32(v_v1, _mm_sad_epu8(v_q_right, v_zeroes)); + v_v2k = _mm_add_epi32(v_v2k, _mm_madd_epi16(v_ones, _mm_maddubs_epi16(v_q__left, v_weights__left))); + v_v2k = _mm_add_epi32(v_v2k, _mm_madd_epi16(v_ones, _mm_maddubs_epi16(v_q_right, v_weights_right))); + v_p.ptr += 32; + } + v_p.len = 0; + } + v_v1 = _mm_add_epi32(v_v1, _mm_shuffle_epi32(v_v1, (int32_t)(177))); + v_v1 = _mm_add_epi32(v_v1, _mm_shuffle_epi32(v_v1, (int32_t)(78))); + v_s1 += ((uint32_t)(_mm_cvtsi128_si32(v_v1))); + v_v2 = _mm_add_epi32(v_v2k, _mm_slli_epi32(v_v2j, (int32_t)(5))); + v_v2 = _mm_add_epi32(v_v2, _mm_shuffle_epi32(v_v2, (int32_t)(177))); + v_v2 = _mm_add_epi32(v_v2, _mm_shuffle_epi32(v_v2, (int32_t)(78))); + v_s2 += ((uint32_t)(_mm_cvtsi128_si32(v_v2))); + v_tail_index = (((uint64_t)(a_x.len)) & 18446744073709551584u); + if (v_tail_index < ((uint64_t)(a_x.len))) { + { + wuffs_base__slice_u8 i_slice_p = wuffs_base__slice_u8__subslice_i(a_x, v_tail_index); + v_p.ptr = i_slice_p.ptr; + v_p.len = 1; + uint8_t* i_end0_p = i_slice_p.ptr + i_slice_p.len; + while (v_p.ptr < i_end0_p) { + v_s1 += ((uint32_t)(v_p.ptr[0])); + v_s2 += v_s1; + v_p.ptr += 1; + } + v_p.len = 0; + } + } + v_s1 %= 65521; + v_s2 %= 65521; + a_x = v_remaining; + } + self->private_impl.f_state = (((v_s2 & 65535) << 16) | (v_s1 & 65535)); + return wuffs_base__make_empty_struct(); +} +#endif // defined(WUFFS_BASE__CPU_ARCH__X86_FAMILY) +// ‼ WUFFS MULTI-FILE SECTION -x86_sse42 + +#endif // !defined(WUFFS_CONFIG__MODULES) || defined(WUFFS_CONFIG__MODULE__ADLER32) + +#if !defined(WUFFS_CONFIG__MODULES) || defined(WUFFS_CONFIG__MODULE__BMP) + +// ---------------- Status Codes Implementations + +const char wuffs_bmp__error__bad_header[] = "#bmp: bad header"; +const char wuffs_bmp__error__bad_rle_compression[] = "#bmp: bad RLE compression"; +const char wuffs_bmp__error__truncated_input[] = "#bmp: truncated input"; +const char wuffs_bmp__error__unsupported_bmp_file[] = "#bmp: unsupported BMP file"; +const char wuffs_bmp__note__internal_note_short_read[] = "@bmp: internal note: short read"; + +// ---------------- Private Consts + +#define WUFFS_BMP__COMPRESSION_NONE 0 + +#define WUFFS_BMP__COMPRESSION_RLE8 1 + +#define WUFFS_BMP__COMPRESSION_RLE4 2 + +#define WUFFS_BMP__COMPRESSION_BITFIELDS 3 + +#define WUFFS_BMP__COMPRESSION_JPEG 4 + +#define WUFFS_BMP__COMPRESSION_PNG 5 + +#define WUFFS_BMP__COMPRESSION_ALPHABITFIELDS 6 + +#define WUFFS_BMP__COMPRESSION_LOW_BIT_DEPTH 256 + +#define WUFFS_BMP__RLE_STATE_NEUTRAL 0 + +#define WUFFS_BMP__RLE_STATE_RUN 1 + +#define WUFFS_BMP__RLE_STATE_ESCAPE 2 + +#define WUFFS_BMP__RLE_STATE_LITERAL 3 + +#define WUFFS_BMP__RLE_STATE_DELTA_X 4 + +#define WUFFS_BMP__RLE_STATE_DELTA_Y 5 + +// ---------------- Private Initializer Prototypes + +// ---------------- Private Function Prototypes + +static wuffs_base__status +wuffs_bmp__decoder__do_decode_image_config( + wuffs_bmp__decoder* self, + wuffs_base__image_config* a_dst, + wuffs_base__io_buffer* a_src); + +static wuffs_base__status +wuffs_bmp__decoder__do_decode_frame_config( + wuffs_bmp__decoder* self, + wuffs_base__frame_config* a_dst, + wuffs_base__io_buffer* a_src); + +static wuffs_base__status +wuffs_bmp__decoder__do_decode_frame( + wuffs_bmp__decoder* self, + wuffs_base__pixel_buffer* a_dst, + wuffs_base__io_buffer* a_src, + wuffs_base__pixel_blend a_blend, + wuffs_base__slice_u8 a_workbuf, + wuffs_base__decode_frame_options* a_opts); + +static wuffs_base__status +wuffs_bmp__decoder__swizzle_none( + wuffs_bmp__decoder* self, + wuffs_base__pixel_buffer* a_dst, + wuffs_base__io_buffer* a_src); + +static wuffs_base__status +wuffs_bmp__decoder__swizzle_rle( + wuffs_bmp__decoder* self, + wuffs_base__pixel_buffer* a_dst, + wuffs_base__io_buffer* a_src); + +static wuffs_base__status +wuffs_bmp__decoder__swizzle_bitfields( + wuffs_bmp__decoder* self, + wuffs_base__pixel_buffer* a_dst, + wuffs_base__io_buffer* a_src); + +static wuffs_base__status +wuffs_bmp__decoder__swizzle_low_bit_depth( + wuffs_bmp__decoder* self, + wuffs_base__pixel_buffer* a_dst, + wuffs_base__io_buffer* a_src); + +static wuffs_base__status +wuffs_bmp__decoder__do_tell_me_more( + wuffs_bmp__decoder* self, + wuffs_base__io_buffer* a_dst, + wuffs_base__more_information* a_minfo, + wuffs_base__io_buffer* a_src); + +static wuffs_base__status +wuffs_bmp__decoder__read_palette( + wuffs_bmp__decoder* self, + wuffs_base__io_buffer* a_src); + +static wuffs_base__status +wuffs_bmp__decoder__process_masks( + wuffs_bmp__decoder* self); + +// ---------------- VTables + +const wuffs_base__image_decoder__func_ptrs +wuffs_bmp__decoder__func_ptrs_for__wuffs_base__image_decoder = { + (wuffs_base__status(*)(void*, + wuffs_base__pixel_buffer*, + wuffs_base__io_buffer*, + wuffs_base__pixel_blend, + wuffs_base__slice_u8, + wuffs_base__decode_frame_options*))(&wuffs_bmp__decoder__decode_frame), + (wuffs_base__status(*)(void*, + wuffs_base__frame_config*, + wuffs_base__io_buffer*))(&wuffs_bmp__decoder__decode_frame_config), + (wuffs_base__status(*)(void*, + wuffs_base__image_config*, + wuffs_base__io_buffer*))(&wuffs_bmp__decoder__decode_image_config), + (wuffs_base__rect_ie_u32(*)(const void*))(&wuffs_bmp__decoder__frame_dirty_rect), + (uint32_t(*)(const void*))(&wuffs_bmp__decoder__num_animation_loops), + (uint64_t(*)(const void*))(&wuffs_bmp__decoder__num_decoded_frame_configs), + (uint64_t(*)(const void*))(&wuffs_bmp__decoder__num_decoded_frames), + (wuffs_base__status(*)(void*, + uint64_t, + uint64_t))(&wuffs_bmp__decoder__restart_frame), + (wuffs_base__empty_struct(*)(void*, + uint32_t, + bool))(&wuffs_bmp__decoder__set_quirk_enabled), + (wuffs_base__empty_struct(*)(void*, + uint32_t, + bool))(&wuffs_bmp__decoder__set_report_metadata), + (wuffs_base__status(*)(void*, + wuffs_base__io_buffer*, + wuffs_base__more_information*, + wuffs_base__io_buffer*))(&wuffs_bmp__decoder__tell_me_more), + (wuffs_base__range_ii_u64(*)(const void*))(&wuffs_bmp__decoder__workbuf_len), +}; + +// ---------------- Initializer Implementations + +wuffs_base__status WUFFS_BASE__WARN_UNUSED_RESULT +wuffs_bmp__decoder__initialize( + wuffs_bmp__decoder* self, + size_t sizeof_star_self, + uint64_t wuffs_version, + uint32_t options){ + if (!self) { + return wuffs_base__make_status(wuffs_base__error__bad_receiver); + } + if (sizeof(*self) != sizeof_star_self) { + return wuffs_base__make_status(wuffs_base__error__bad_sizeof_receiver); + } + if (((wuffs_version >> 32) != WUFFS_VERSION_MAJOR) || + (((wuffs_version >> 16) & 0xFFFF) > WUFFS_VERSION_MINOR)) { + return wuffs_base__make_status(wuffs_base__error__bad_wuffs_version); + } + + if ((options & WUFFS_INITIALIZE__ALREADY_ZEROED) != 0) { + // The whole point of this if-check is to detect an uninitialized *self. + // We disable the warning on GCC. Clang-5.0 does not have this warning. +#if !defined(__clang__) && defined(__GNUC__) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wmaybe-uninitialized" +#endif + if (self->private_impl.magic != 0) { + return wuffs_base__make_status(wuffs_base__error__initialize_falsely_claimed_already_zeroed); + } +#if !defined(__clang__) && defined(__GNUC__) +#pragma GCC diagnostic pop +#endif + } else { + if ((options & WUFFS_INITIALIZE__LEAVE_INTERNAL_BUFFERS_UNINITIALIZED) == 0) { + memset(self, 0, sizeof(*self)); + options |= WUFFS_INITIALIZE__ALREADY_ZEROED; + } else { + memset(&(self->private_impl), 0, sizeof(self->private_impl)); + } + } + + self->private_impl.magic = WUFFS_BASE__MAGIC; + self->private_impl.vtable_for__wuffs_base__image_decoder.vtable_name = + wuffs_base__image_decoder__vtable_name; + self->private_impl.vtable_for__wuffs_base__image_decoder.function_pointers = + (const void*)(&wuffs_bmp__decoder__func_ptrs_for__wuffs_base__image_decoder); + return wuffs_base__make_status(NULL); +} + +wuffs_bmp__decoder* +wuffs_bmp__decoder__alloc() { + wuffs_bmp__decoder* x = + (wuffs_bmp__decoder*)(calloc(sizeof(wuffs_bmp__decoder), 1)); + if (!x) { + return NULL; + } + if (wuffs_bmp__decoder__initialize( + x, sizeof(wuffs_bmp__decoder), WUFFS_VERSION, WUFFS_INITIALIZE__ALREADY_ZEROED).repr) { + free(x); + return NULL; + } + return x; +} + +size_t +sizeof__wuffs_bmp__decoder() { + return sizeof(wuffs_bmp__decoder); +} + +// ---------------- Function Implementations + +// -------- func bmp.decoder.set_quirk_enabled + +WUFFS_BASE__MAYBE_STATIC wuffs_base__empty_struct +wuffs_bmp__decoder__set_quirk_enabled( + wuffs_bmp__decoder* self, + uint32_t a_quirk, + bool a_enabled) { + return wuffs_base__make_empty_struct(); +} + +// -------- func bmp.decoder.decode_image_config + +WUFFS_BASE__MAYBE_STATIC wuffs_base__status +wuffs_bmp__decoder__decode_image_config( + wuffs_bmp__decoder* self, + wuffs_base__image_config* a_dst, + wuffs_base__io_buffer* a_src) { + if (!self) { + return wuffs_base__make_status(wuffs_base__error__bad_receiver); + } + if (self->private_impl.magic != WUFFS_BASE__MAGIC) { + return wuffs_base__make_status( + (self->private_impl.magic == WUFFS_BASE__DISABLED) + ? wuffs_base__error__disabled_by_previous_error + : wuffs_base__error__initialize_not_called); + } + if (!a_src) { + self->private_impl.magic = WUFFS_BASE__DISABLED; + return wuffs_base__make_status(wuffs_base__error__bad_argument); + } + if ((self->private_impl.active_coroutine != 0) && + (self->private_impl.active_coroutine != 1)) { + self->private_impl.magic = WUFFS_BASE__DISABLED; + return wuffs_base__make_status(wuffs_base__error__interleaved_coroutine_calls); + } + self->private_impl.active_coroutine = 0; + wuffs_base__status status = wuffs_base__make_status(NULL); + + wuffs_base__status v_status = wuffs_base__make_status(NULL); + + uint32_t coro_susp_point = self->private_impl.p_decode_image_config[0]; + switch (coro_susp_point) { + WUFFS_BASE__COROUTINE_SUSPENSION_POINT_0; + + while (true) { + { + wuffs_base__status t_0 = wuffs_bmp__decoder__do_decode_image_config(self, a_dst, a_src); + v_status = t_0; + } + if ((v_status.repr == wuffs_base__suspension__short_read) && (a_src && a_src->meta.closed)) { + status = wuffs_base__make_status(wuffs_bmp__error__truncated_input); + goto exit; + } + status = v_status; + WUFFS_BASE__COROUTINE_SUSPENSION_POINT_MAYBE_SUSPEND(1); + } + + ok: + self->private_impl.p_decode_image_config[0] = 0; + goto exit; + } + + goto suspend; + suspend: + self->private_impl.p_decode_image_config[0] = wuffs_base__status__is_suspension(&status) ? coro_susp_point : 0; + self->private_impl.active_coroutine = wuffs_base__status__is_suspension(&status) ? 1 : 0; + + goto exit; + exit: + if (wuffs_base__status__is_error(&status)) { + self->private_impl.magic = WUFFS_BASE__DISABLED; + } + return status; +} + +// -------- func bmp.decoder.do_decode_image_config + +static wuffs_base__status +wuffs_bmp__decoder__do_decode_image_config( + wuffs_bmp__decoder* self, + wuffs_base__image_config* a_dst, + wuffs_base__io_buffer* a_src) { + wuffs_base__status status = wuffs_base__make_status(NULL); + + uint32_t v_magic = 0; + uint32_t v_width = 0; + uint32_t v_height = 0; + uint32_t v_planes = 0; + uint32_t v_dst_pixfmt = 0; + uint32_t v_byte_width = 0; + + const uint8_t* iop_a_src = NULL; + const uint8_t* io0_a_src WUFFS_BASE__POTENTIALLY_UNUSED = NULL; + const uint8_t* io1_a_src WUFFS_BASE__POTENTIALLY_UNUSED = NULL; + const uint8_t* io2_a_src WUFFS_BASE__POTENTIALLY_UNUSED = NULL; + if (a_src && a_src->data.ptr) { + io0_a_src = a_src->data.ptr; + io1_a_src = io0_a_src + a_src->meta.ri; + iop_a_src = io1_a_src; + io2_a_src = io0_a_src + a_src->meta.wi; + } + + uint32_t coro_susp_point = self->private_impl.p_do_decode_image_config[0]; + switch (coro_susp_point) { + WUFFS_BASE__COROUTINE_SUSPENSION_POINT_0; + + if ((self->private_impl.f_call_sequence != 0) || (self->private_impl.f_io_redirect_fourcc == 1)) { + status = wuffs_base__make_status(wuffs_base__error__bad_call_sequence); + goto exit; + } else if (self->private_impl.f_io_redirect_fourcc != 0) { + status = wuffs_base__make_status(wuffs_base__note__i_o_redirect); + goto ok; + } + { + WUFFS_BASE__COROUTINE_SUSPENSION_POINT(1); + uint32_t t_0; + if (WUFFS_BASE__LIKELY(io2_a_src - iop_a_src >= 2)) { + t_0 = ((uint32_t)(wuffs_base__peek_u16le__no_bounds_check(iop_a_src))); + iop_a_src += 2; + } else { + self->private_data.s_do_decode_image_config[0].scratch = 0; + WUFFS_BASE__COROUTINE_SUSPENSION_POINT(2); + while (true) { + if (WUFFS_BASE__UNLIKELY(iop_a_src == io2_a_src)) { + status = wuffs_base__make_status(wuffs_base__suspension__short_read); + goto suspend; + } + uint64_t* scratch = &self->private_data.s_do_decode_image_config[0].scratch; + uint32_t num_bits_0 = ((uint32_t)(*scratch >> 56)); + *scratch <<= 8; + *scratch >>= 8; + *scratch |= ((uint64_t)(*iop_a_src++)) << num_bits_0; + if (num_bits_0 == 8) { + t_0 = ((uint32_t)(*scratch)); + break; + } + num_bits_0 += 8; + *scratch |= ((uint64_t)(num_bits_0)) << 56; + } + } + v_magic = t_0; + } + if (v_magic != 19778) { + status = wuffs_base__make_status(wuffs_bmp__error__bad_header); + goto exit; + } + self->private_data.s_do_decode_image_config[0].scratch = 8; + WUFFS_BASE__COROUTINE_SUSPENSION_POINT(3); + if (self->private_data.s_do_decode_image_config[0].scratch > ((uint64_t)(io2_a_src - iop_a_src))) { + self->private_data.s_do_decode_image_config[0].scratch -= ((uint64_t)(io2_a_src - iop_a_src)); + iop_a_src = io2_a_src; + status = wuffs_base__make_status(wuffs_base__suspension__short_read); + goto suspend; + } + iop_a_src += self->private_data.s_do_decode_image_config[0].scratch; + { + WUFFS_BASE__COROUTINE_SUSPENSION_POINT(4); + uint32_t t_1; + if (WUFFS_BASE__LIKELY(io2_a_src - iop_a_src >= 4)) { + t_1 = wuffs_base__peek_u32le__no_bounds_check(iop_a_src); + iop_a_src += 4; + } else { + self->private_data.s_do_decode_image_config[0].scratch = 0; + WUFFS_BASE__COROUTINE_SUSPENSION_POINT(5); + while (true) { + if (WUFFS_BASE__UNLIKELY(iop_a_src == io2_a_src)) { + status = wuffs_base__make_status(wuffs_base__suspension__short_read); + goto suspend; + } + uint64_t* scratch = &self->private_data.s_do_decode_image_config[0].scratch; + uint32_t num_bits_1 = ((uint32_t)(*scratch >> 56)); + *scratch <<= 8; + *scratch >>= 8; + *scratch |= ((uint64_t)(*iop_a_src++)) << num_bits_1; + if (num_bits_1 == 24) { + t_1 = ((uint32_t)(*scratch)); + break; + } + num_bits_1 += 8; + *scratch |= ((uint64_t)(num_bits_1)) << 56; + } + } + self->private_impl.f_padding = t_1; + } + if (self->private_impl.f_padding < 14) { + status = wuffs_base__make_status(wuffs_bmp__error__bad_header); + goto exit; + } + self->private_impl.f_padding -= 14; + self->private_impl.f_io_redirect_pos = wuffs_base__u64__sat_add(((uint64_t)(self->private_impl.f_padding)), wuffs_base__u64__sat_add((a_src ? a_src->meta.pos : 0), ((uint64_t)(iop_a_src - io0_a_src)))); + { + WUFFS_BASE__COROUTINE_SUSPENSION_POINT(6); + uint32_t t_2; + if (WUFFS_BASE__LIKELY(io2_a_src - iop_a_src >= 4)) { + t_2 = wuffs_base__peek_u32le__no_bounds_check(iop_a_src); + iop_a_src += 4; + } else { + self->private_data.s_do_decode_image_config[0].scratch = 0; + WUFFS_BASE__COROUTINE_SUSPENSION_POINT(7); + while (true) { + if (WUFFS_BASE__UNLIKELY(iop_a_src == io2_a_src)) { + status = wuffs_base__make_status(wuffs_base__suspension__short_read); + goto suspend; + } + uint64_t* scratch = &self->private_data.s_do_decode_image_config[0].scratch; + uint32_t num_bits_2 = ((uint32_t)(*scratch >> 56)); + *scratch <<= 8; + *scratch >>= 8; + *scratch |= ((uint64_t)(*iop_a_src++)) << num_bits_2; + if (num_bits_2 == 24) { + t_2 = ((uint32_t)(*scratch)); + break; + } + num_bits_2 += 8; + *scratch |= ((uint64_t)(num_bits_2)) << 56; + } + } + self->private_impl.f_bitmap_info_len = t_2; + } + if (self->private_impl.f_padding < self->private_impl.f_bitmap_info_len) { + status = wuffs_base__make_status(wuffs_bmp__error__bad_header); + goto exit; + } + self->private_impl.f_padding -= self->private_impl.f_bitmap_info_len; + if (self->private_impl.f_bitmap_info_len == 12) { + { + WUFFS_BASE__COROUTINE_SUSPENSION_POINT(8); + uint32_t t_3; + if (WUFFS_BASE__LIKELY(io2_a_src - iop_a_src >= 2)) { + t_3 = ((uint32_t)(wuffs_base__peek_u16le__no_bounds_check(iop_a_src))); + iop_a_src += 2; + } else { + self->private_data.s_do_decode_image_config[0].scratch = 0; + WUFFS_BASE__COROUTINE_SUSPENSION_POINT(9); + while (true) { + if (WUFFS_BASE__UNLIKELY(iop_a_src == io2_a_src)) { + status = wuffs_base__make_status(wuffs_base__suspension__short_read); + goto suspend; + } + uint64_t* scratch = &self->private_data.s_do_decode_image_config[0].scratch; + uint32_t num_bits_3 = ((uint32_t)(*scratch >> 56)); + *scratch <<= 8; + *scratch >>= 8; + *scratch |= ((uint64_t)(*iop_a_src++)) << num_bits_3; + if (num_bits_3 == 8) { + t_3 = ((uint32_t)(*scratch)); + break; + } + num_bits_3 += 8; + *scratch |= ((uint64_t)(num_bits_3)) << 56; + } + } + self->private_impl.f_width = t_3; + } + { + WUFFS_BASE__COROUTINE_SUSPENSION_POINT(10); + uint32_t t_4; + if (WUFFS_BASE__LIKELY(io2_a_src - iop_a_src >= 2)) { + t_4 = ((uint32_t)(wuffs_base__peek_u16le__no_bounds_check(iop_a_src))); + iop_a_src += 2; + } else { + self->private_data.s_do_decode_image_config[0].scratch = 0; + WUFFS_BASE__COROUTINE_SUSPENSION_POINT(11); + while (true) { + if (WUFFS_BASE__UNLIKELY(iop_a_src == io2_a_src)) { + status = wuffs_base__make_status(wuffs_base__suspension__short_read); + goto suspend; + } + uint64_t* scratch = &self->private_data.s_do_decode_image_config[0].scratch; + uint32_t num_bits_4 = ((uint32_t)(*scratch >> 56)); + *scratch <<= 8; + *scratch >>= 8; + *scratch |= ((uint64_t)(*iop_a_src++)) << num_bits_4; + if (num_bits_4 == 8) { + t_4 = ((uint32_t)(*scratch)); + break; + } + num_bits_4 += 8; + *scratch |= ((uint64_t)(num_bits_4)) << 56; + } + } + self->private_impl.f_height = t_4; + } + { + WUFFS_BASE__COROUTINE_SUSPENSION_POINT(12); + uint32_t t_5; + if (WUFFS_BASE__LIKELY(io2_a_src - iop_a_src >= 2)) { + t_5 = ((uint32_t)(wuffs_base__peek_u16le__no_bounds_check(iop_a_src))); + iop_a_src += 2; + } else { + self->private_data.s_do_decode_image_config[0].scratch = 0; + WUFFS_BASE__COROUTINE_SUSPENSION_POINT(13); + while (true) { + if (WUFFS_BASE__UNLIKELY(iop_a_src == io2_a_src)) { + status = wuffs_base__make_status(wuffs_base__suspension__short_read); + goto suspend; + } + uint64_t* scratch = &self->private_data.s_do_decode_image_config[0].scratch; + uint32_t num_bits_5 = ((uint32_t)(*scratch >> 56)); + *scratch <<= 8; + *scratch >>= 8; + *scratch |= ((uint64_t)(*iop_a_src++)) << num_bits_5; + if (num_bits_5 == 8) { + t_5 = ((uint32_t)(*scratch)); + break; + } + num_bits_5 += 8; + *scratch |= ((uint64_t)(num_bits_5)) << 56; + } + } + v_planes = t_5; + } + if (v_planes != 1) { + status = wuffs_base__make_status(wuffs_bmp__error__unsupported_bmp_file); + goto exit; + } + { + WUFFS_BASE__COROUTINE_SUSPENSION_POINT(14); + uint32_t t_6; + if (WUFFS_BASE__LIKELY(io2_a_src - iop_a_src >= 2)) { + t_6 = ((uint32_t)(wuffs_base__peek_u16le__no_bounds_check(iop_a_src))); + iop_a_src += 2; + } else { + self->private_data.s_do_decode_image_config[0].scratch = 0; + WUFFS_BASE__COROUTINE_SUSPENSION_POINT(15); + while (true) { + if (WUFFS_BASE__UNLIKELY(iop_a_src == io2_a_src)) { + status = wuffs_base__make_status(wuffs_base__suspension__short_read); + goto suspend; + } + uint64_t* scratch = &self->private_data.s_do_decode_image_config[0].scratch; + uint32_t num_bits_6 = ((uint32_t)(*scratch >> 56)); + *scratch <<= 8; + *scratch >>= 8; + *scratch |= ((uint64_t)(*iop_a_src++)) << num_bits_6; + if (num_bits_6 == 8) { + t_6 = ((uint32_t)(*scratch)); + break; + } + num_bits_6 += 8; + *scratch |= ((uint64_t)(num_bits_6)) << 56; + } + } + self->private_impl.f_bits_per_pixel = t_6; + } + } else if (self->private_impl.f_bitmap_info_len == 16) { + { + WUFFS_BASE__COROUTINE_SUSPENSION_POINT(16); + uint32_t t_7; + if (WUFFS_BASE__LIKELY(io2_a_src - iop_a_src >= 4)) { + t_7 = wuffs_base__peek_u32le__no_bounds_check(iop_a_src); + iop_a_src += 4; + } else { + self->private_data.s_do_decode_image_config[0].scratch = 0; + WUFFS_BASE__COROUTINE_SUSPENSION_POINT(17); + while (true) { + if (WUFFS_BASE__UNLIKELY(iop_a_src == io2_a_src)) { + status = wuffs_base__make_status(wuffs_base__suspension__short_read); + goto suspend; + } + uint64_t* scratch = &self->private_data.s_do_decode_image_config[0].scratch; + uint32_t num_bits_7 = ((uint32_t)(*scratch >> 56)); + *scratch <<= 8; + *scratch >>= 8; + *scratch |= ((uint64_t)(*iop_a_src++)) << num_bits_7; + if (num_bits_7 == 24) { + t_7 = ((uint32_t)(*scratch)); + break; + } + num_bits_7 += 8; + *scratch |= ((uint64_t)(num_bits_7)) << 56; + } + } + v_width = t_7; + } + if (v_width >= 2147483648) { + status = wuffs_base__make_status(wuffs_bmp__error__bad_header); + goto exit; + } + self->private_impl.f_width = v_width; + { + WUFFS_BASE__COROUTINE_SUSPENSION_POINT(18); + uint32_t t_8; + if (WUFFS_BASE__LIKELY(io2_a_src - iop_a_src >= 4)) { + t_8 = wuffs_base__peek_u32le__no_bounds_check(iop_a_src); + iop_a_src += 4; + } else { + self->private_data.s_do_decode_image_config[0].scratch = 0; + WUFFS_BASE__COROUTINE_SUSPENSION_POINT(19); + while (true) { + if (WUFFS_BASE__UNLIKELY(iop_a_src == io2_a_src)) { + status = wuffs_base__make_status(wuffs_base__suspension__short_read); + goto suspend; + } + uint64_t* scratch = &self->private_data.s_do_decode_image_config[0].scratch; + uint32_t num_bits_8 = ((uint32_t)(*scratch >> 56)); + *scratch <<= 8; + *scratch >>= 8; + *scratch |= ((uint64_t)(*iop_a_src++)) << num_bits_8; + if (num_bits_8 == 24) { + t_8 = ((uint32_t)(*scratch)); + break; + } + num_bits_8 += 8; + *scratch |= ((uint64_t)(num_bits_8)) << 56; + } + } + v_height = t_8; + } + if (v_height >= 2147483648) { + status = wuffs_base__make_status(wuffs_bmp__error__bad_header); + goto exit; + } + self->private_impl.f_height = v_height; + { + WUFFS_BASE__COROUTINE_SUSPENSION_POINT(20); + uint32_t t_9; + if (WUFFS_BASE__LIKELY(io2_a_src - iop_a_src >= 2)) { + t_9 = ((uint32_t)(wuffs_base__peek_u16le__no_bounds_check(iop_a_src))); + iop_a_src += 2; + } else { + self->private_data.s_do_decode_image_config[0].scratch = 0; + WUFFS_BASE__COROUTINE_SUSPENSION_POINT(21); + while (true) { + if (WUFFS_BASE__UNLIKELY(iop_a_src == io2_a_src)) { + status = wuffs_base__make_status(wuffs_base__suspension__short_read); + goto suspend; + } + uint64_t* scratch = &self->private_data.s_do_decode_image_config[0].scratch; + uint32_t num_bits_9 = ((uint32_t)(*scratch >> 56)); + *scratch <<= 8; + *scratch >>= 8; + *scratch |= ((uint64_t)(*iop_a_src++)) << num_bits_9; + if (num_bits_9 == 8) { + t_9 = ((uint32_t)(*scratch)); + break; + } + num_bits_9 += 8; + *scratch |= ((uint64_t)(num_bits_9)) << 56; + } + } + v_planes = t_9; + } + if (v_planes != 1) { + status = wuffs_base__make_status(wuffs_bmp__error__unsupported_bmp_file); + goto exit; + } + { + WUFFS_BASE__COROUTINE_SUSPENSION_POINT(22); + uint32_t t_10; + if (WUFFS_BASE__LIKELY(io2_a_src - iop_a_src >= 2)) { + t_10 = ((uint32_t)(wuffs_base__peek_u16le__no_bounds_check(iop_a_src))); + iop_a_src += 2; + } else { + self->private_data.s_do_decode_image_config[0].scratch = 0; + WUFFS_BASE__COROUTINE_SUSPENSION_POINT(23); + while (true) { + if (WUFFS_BASE__UNLIKELY(iop_a_src == io2_a_src)) { + status = wuffs_base__make_status(wuffs_base__suspension__short_read); + goto suspend; + } + uint64_t* scratch = &self->private_data.s_do_decode_image_config[0].scratch; + uint32_t num_bits_10 = ((uint32_t)(*scratch >> 56)); + *scratch <<= 8; + *scratch >>= 8; + *scratch |= ((uint64_t)(*iop_a_src++)) << num_bits_10; + if (num_bits_10 == 8) { + t_10 = ((uint32_t)(*scratch)); + break; + } + num_bits_10 += 8; + *scratch |= ((uint64_t)(num_bits_10)) << 56; + } + } + self->private_impl.f_bits_per_pixel = t_10; + } + } else { + { + WUFFS_BASE__COROUTINE_SUSPENSION_POINT(24); + uint32_t t_11; + if (WUFFS_BASE__LIKELY(io2_a_src - iop_a_src >= 4)) { + t_11 = wuffs_base__peek_u32le__no_bounds_check(iop_a_src); + iop_a_src += 4; + } else { + self->private_data.s_do_decode_image_config[0].scratch = 0; + WUFFS_BASE__COROUTINE_SUSPENSION_POINT(25); + while (true) { + if (WUFFS_BASE__UNLIKELY(iop_a_src == io2_a_src)) { + status = wuffs_base__make_status(wuffs_base__suspension__short_read); + goto suspend; + } + uint64_t* scratch = &self->private_data.s_do_decode_image_config[0].scratch; + uint32_t num_bits_11 = ((uint32_t)(*scratch >> 56)); + *scratch <<= 8; + *scratch >>= 8; + *scratch |= ((uint64_t)(*iop_a_src++)) << num_bits_11; + if (num_bits_11 == 24) { + t_11 = ((uint32_t)(*scratch)); + break; + } + num_bits_11 += 8; + *scratch |= ((uint64_t)(num_bits_11)) << 56; + } + } + v_width = t_11; + } + if (v_width >= 2147483648) { + status = wuffs_base__make_status(wuffs_bmp__error__bad_header); + goto exit; + } + self->private_impl.f_width = v_width; + { + WUFFS_BASE__COROUTINE_SUSPENSION_POINT(26); + uint32_t t_12; + if (WUFFS_BASE__LIKELY(io2_a_src - iop_a_src >= 4)) { + t_12 = wuffs_base__peek_u32le__no_bounds_check(iop_a_src); + iop_a_src += 4; + } else { + self->private_data.s_do_decode_image_config[0].scratch = 0; + WUFFS_BASE__COROUTINE_SUSPENSION_POINT(27); + while (true) { + if (WUFFS_BASE__UNLIKELY(iop_a_src == io2_a_src)) { + status = wuffs_base__make_status(wuffs_base__suspension__short_read); + goto suspend; + } + uint64_t* scratch = &self->private_data.s_do_decode_image_config[0].scratch; + uint32_t num_bits_12 = ((uint32_t)(*scratch >> 56)); + *scratch <<= 8; + *scratch >>= 8; + *scratch |= ((uint64_t)(*iop_a_src++)) << num_bits_12; + if (num_bits_12 == 24) { + t_12 = ((uint32_t)(*scratch)); + break; + } + num_bits_12 += 8; + *scratch |= ((uint64_t)(num_bits_12)) << 56; + } + } + v_height = t_12; + } + if (v_height == 2147483648) { + status = wuffs_base__make_status(wuffs_bmp__error__bad_header); + goto exit; + } else if (v_height >= 2147483648) { + self->private_impl.f_height = (((uint32_t)(0 - v_height)) & 2147483647); + self->private_impl.f_top_down = true; + } else { + self->private_impl.f_height = v_height; + } + { + WUFFS_BASE__COROUTINE_SUSPENSION_POINT(28); + uint32_t t_13; + if (WUFFS_BASE__LIKELY(io2_a_src - iop_a_src >= 2)) { + t_13 = ((uint32_t)(wuffs_base__peek_u16le__no_bounds_check(iop_a_src))); + iop_a_src += 2; + } else { + self->private_data.s_do_decode_image_config[0].scratch = 0; + WUFFS_BASE__COROUTINE_SUSPENSION_POINT(29); + while (true) { + if (WUFFS_BASE__UNLIKELY(iop_a_src == io2_a_src)) { + status = wuffs_base__make_status(wuffs_base__suspension__short_read); + goto suspend; + } + uint64_t* scratch = &self->private_data.s_do_decode_image_config[0].scratch; + uint32_t num_bits_13 = ((uint32_t)(*scratch >> 56)); + *scratch <<= 8; + *scratch >>= 8; + *scratch |= ((uint64_t)(*iop_a_src++)) << num_bits_13; + if (num_bits_13 == 8) { + t_13 = ((uint32_t)(*scratch)); + break; + } + num_bits_13 += 8; + *scratch |= ((uint64_t)(num_bits_13)) << 56; + } + } + v_planes = t_13; + } + if (v_planes != 1) { + status = wuffs_base__make_status(wuffs_bmp__error__unsupported_bmp_file); + goto exit; + } + { + WUFFS_BASE__COROUTINE_SUSPENSION_POINT(30); + uint32_t t_14; + if (WUFFS_BASE__LIKELY(io2_a_src - iop_a_src >= 2)) { + t_14 = ((uint32_t)(wuffs_base__peek_u16le__no_bounds_check(iop_a_src))); + iop_a_src += 2; + } else { + self->private_data.s_do_decode_image_config[0].scratch = 0; + WUFFS_BASE__COROUTINE_SUSPENSION_POINT(31); + while (true) { + if (WUFFS_BASE__UNLIKELY(iop_a_src == io2_a_src)) { + status = wuffs_base__make_status(wuffs_base__suspension__short_read); + goto suspend; + } + uint64_t* scratch = &self->private_data.s_do_decode_image_config[0].scratch; + uint32_t num_bits_14 = ((uint32_t)(*scratch >> 56)); + *scratch <<= 8; + *scratch >>= 8; + *scratch |= ((uint64_t)(*iop_a_src++)) << num_bits_14; + if (num_bits_14 == 8) { + t_14 = ((uint32_t)(*scratch)); + break; + } + num_bits_14 += 8; + *scratch |= ((uint64_t)(num_bits_14)) << 56; + } + } + self->private_impl.f_bits_per_pixel = t_14; + } + { + WUFFS_BASE__COROUTINE_SUSPENSION_POINT(32); + uint32_t t_15; + if (WUFFS_BASE__LIKELY(io2_a_src - iop_a_src >= 4)) { + t_15 = wuffs_base__peek_u32le__no_bounds_check(iop_a_src); + iop_a_src += 4; + } else { + self->private_data.s_do_decode_image_config[0].scratch = 0; + WUFFS_BASE__COROUTINE_SUSPENSION_POINT(33); + while (true) { + if (WUFFS_BASE__UNLIKELY(iop_a_src == io2_a_src)) { + status = wuffs_base__make_status(wuffs_base__suspension__short_read); + goto suspend; + } + uint64_t* scratch = &self->private_data.s_do_decode_image_config[0].scratch; + uint32_t num_bits_15 = ((uint32_t)(*scratch >> 56)); + *scratch <<= 8; + *scratch >>= 8; + *scratch |= ((uint64_t)(*iop_a_src++)) << num_bits_15; + if (num_bits_15 == 24) { + t_15 = ((uint32_t)(*scratch)); + break; + } + num_bits_15 += 8; + *scratch |= ((uint64_t)(num_bits_15)) << 56; + } + } + self->private_impl.f_compression = t_15; + } + if (self->private_impl.f_bits_per_pixel == 0) { + if (self->private_impl.f_compression == 4) { + self->private_impl.f_io_redirect_fourcc = 1246774599; + status = wuffs_base__make_status(wuffs_base__note__i_o_redirect); + goto ok; + } else if (self->private_impl.f_compression == 5) { + self->private_impl.f_io_redirect_fourcc = 1347307296; + status = wuffs_base__make_status(wuffs_base__note__i_o_redirect); + goto ok; + } + status = wuffs_base__make_status(wuffs_bmp__error__unsupported_bmp_file); + goto exit; + } + self->private_data.s_do_decode_image_config[0].scratch = 20; + WUFFS_BASE__COROUTINE_SUSPENSION_POINT(34); + if (self->private_data.s_do_decode_image_config[0].scratch > ((uint64_t)(io2_a_src - iop_a_src))) { + self->private_data.s_do_decode_image_config[0].scratch -= ((uint64_t)(io2_a_src - iop_a_src)); + iop_a_src = io2_a_src; + status = wuffs_base__make_status(wuffs_base__suspension__short_read); + goto suspend; + } + iop_a_src += self->private_data.s_do_decode_image_config[0].scratch; + if (self->private_impl.f_bitmap_info_len == 40) { + if (self->private_impl.f_bits_per_pixel >= 16) { + if (self->private_impl.f_padding >= 16) { + self->private_impl.f_bitmap_info_len = 56; + self->private_impl.f_padding -= 16; + } else if (self->private_impl.f_padding >= 12) { + self->private_impl.f_bitmap_info_len = 52; + self->private_impl.f_padding -= 12; + } + } + } else if ((self->private_impl.f_bitmap_info_len != 52) && + (self->private_impl.f_bitmap_info_len != 56) && + (self->private_impl.f_bitmap_info_len != 64) && + (self->private_impl.f_bitmap_info_len != 108) && + (self->private_impl.f_bitmap_info_len != 124)) { + status = wuffs_base__make_status(wuffs_bmp__error__unsupported_bmp_file); + goto exit; + } + if (self->private_impl.f_compression == 6) { + self->private_impl.f_compression = 3; + } + if (self->private_impl.f_compression == 3) { + if (self->private_impl.f_bitmap_info_len >= 52) { + { + WUFFS_BASE__COROUTINE_SUSPENSION_POINT(35); + uint32_t t_16; + if (WUFFS_BASE__LIKELY(io2_a_src - iop_a_src >= 4)) { + t_16 = wuffs_base__peek_u32le__no_bounds_check(iop_a_src); + iop_a_src += 4; + } else { + self->private_data.s_do_decode_image_config[0].scratch = 0; + WUFFS_BASE__COROUTINE_SUSPENSION_POINT(36); + while (true) { + if (WUFFS_BASE__UNLIKELY(iop_a_src == io2_a_src)) { + status = wuffs_base__make_status(wuffs_base__suspension__short_read); + goto suspend; + } + uint64_t* scratch = &self->private_data.s_do_decode_image_config[0].scratch; + uint32_t num_bits_16 = ((uint32_t)(*scratch >> 56)); + *scratch <<= 8; + *scratch >>= 8; + *scratch |= ((uint64_t)(*iop_a_src++)) << num_bits_16; + if (num_bits_16 == 24) { + t_16 = ((uint32_t)(*scratch)); + break; + } + num_bits_16 += 8; + *scratch |= ((uint64_t)(num_bits_16)) << 56; + } + } + self->private_impl.f_channel_masks[2] = t_16; + } + { + WUFFS_BASE__COROUTINE_SUSPENSION_POINT(37); + uint32_t t_17; + if (WUFFS_BASE__LIKELY(io2_a_src - iop_a_src >= 4)) { + t_17 = wuffs_base__peek_u32le__no_bounds_check(iop_a_src); + iop_a_src += 4; + } else { + self->private_data.s_do_decode_image_config[0].scratch = 0; + WUFFS_BASE__COROUTINE_SUSPENSION_POINT(38); + while (true) { + if (WUFFS_BASE__UNLIKELY(iop_a_src == io2_a_src)) { + status = wuffs_base__make_status(wuffs_base__suspension__short_read); + goto suspend; + } + uint64_t* scratch = &self->private_data.s_do_decode_image_config[0].scratch; + uint32_t num_bits_17 = ((uint32_t)(*scratch >> 56)); + *scratch <<= 8; + *scratch >>= 8; + *scratch |= ((uint64_t)(*iop_a_src++)) << num_bits_17; + if (num_bits_17 == 24) { + t_17 = ((uint32_t)(*scratch)); + break; + } + num_bits_17 += 8; + *scratch |= ((uint64_t)(num_bits_17)) << 56; + } + } + self->private_impl.f_channel_masks[1] = t_17; + } + { + WUFFS_BASE__COROUTINE_SUSPENSION_POINT(39); + uint32_t t_18; + if (WUFFS_BASE__LIKELY(io2_a_src - iop_a_src >= 4)) { + t_18 = wuffs_base__peek_u32le__no_bounds_check(iop_a_src); + iop_a_src += 4; + } else { + self->private_data.s_do_decode_image_config[0].scratch = 0; + WUFFS_BASE__COROUTINE_SUSPENSION_POINT(40); + while (true) { + if (WUFFS_BASE__UNLIKELY(iop_a_src == io2_a_src)) { + status = wuffs_base__make_status(wuffs_base__suspension__short_read); + goto suspend; + } + uint64_t* scratch = &self->private_data.s_do_decode_image_config[0].scratch; + uint32_t num_bits_18 = ((uint32_t)(*scratch >> 56)); + *scratch <<= 8; + *scratch >>= 8; + *scratch |= ((uint64_t)(*iop_a_src++)) << num_bits_18; + if (num_bits_18 == 24) { + t_18 = ((uint32_t)(*scratch)); + break; + } + num_bits_18 += 8; + *scratch |= ((uint64_t)(num_bits_18)) << 56; + } + } + self->private_impl.f_channel_masks[0] = t_18; + } + if (self->private_impl.f_bitmap_info_len >= 56) { + { + WUFFS_BASE__COROUTINE_SUSPENSION_POINT(41); + uint32_t t_19; + if (WUFFS_BASE__LIKELY(io2_a_src - iop_a_src >= 4)) { + t_19 = wuffs_base__peek_u32le__no_bounds_check(iop_a_src); + iop_a_src += 4; + } else { + self->private_data.s_do_decode_image_config[0].scratch = 0; + WUFFS_BASE__COROUTINE_SUSPENSION_POINT(42); + while (true) { + if (WUFFS_BASE__UNLIKELY(iop_a_src == io2_a_src)) { + status = wuffs_base__make_status(wuffs_base__suspension__short_read); + goto suspend; + } + uint64_t* scratch = &self->private_data.s_do_decode_image_config[0].scratch; + uint32_t num_bits_19 = ((uint32_t)(*scratch >> 56)); + *scratch <<= 8; + *scratch >>= 8; + *scratch |= ((uint64_t)(*iop_a_src++)) << num_bits_19; + if (num_bits_19 == 24) { + t_19 = ((uint32_t)(*scratch)); + break; + } + num_bits_19 += 8; + *scratch |= ((uint64_t)(num_bits_19)) << 56; + } + } + self->private_impl.f_channel_masks[3] = t_19; + } + self->private_data.s_do_decode_image_config[0].scratch = ((uint32_t)(self->private_impl.f_bitmap_info_len - 56)); + WUFFS_BASE__COROUTINE_SUSPENSION_POINT(43); + if (self->private_data.s_do_decode_image_config[0].scratch > ((uint64_t)(io2_a_src - iop_a_src))) { + self->private_data.s_do_decode_image_config[0].scratch -= ((uint64_t)(io2_a_src - iop_a_src)); + iop_a_src = io2_a_src; + status = wuffs_base__make_status(wuffs_base__suspension__short_read); + goto suspend; + } + iop_a_src += self->private_data.s_do_decode_image_config[0].scratch; + } + if ((self->private_impl.f_channel_masks[0] == 255) && (self->private_impl.f_channel_masks[1] == 65280) && (self->private_impl.f_channel_masks[2] == 16711680)) { + if (self->private_impl.f_bits_per_pixel == 24) { + self->private_impl.f_compression = 0; + } else if (self->private_impl.f_bits_per_pixel == 32) { + if ((self->private_impl.f_channel_masks[3] == 0) || (self->private_impl.f_channel_masks[3] == 4278190080)) { + self->private_impl.f_compression = 0; + } + } + } + WUFFS_BASE__COROUTINE_SUSPENSION_POINT(44); + status = wuffs_bmp__decoder__process_masks(self); + if (status.repr) { + goto suspend; + } + } + } else if (self->private_impl.f_bitmap_info_len >= 40) { + self->private_data.s_do_decode_image_config[0].scratch = (self->private_impl.f_bitmap_info_len - 40); + WUFFS_BASE__COROUTINE_SUSPENSION_POINT(45); + if (self->private_data.s_do_decode_image_config[0].scratch > ((uint64_t)(io2_a_src - iop_a_src))) { + self->private_data.s_do_decode_image_config[0].scratch -= ((uint64_t)(io2_a_src - iop_a_src)); + iop_a_src = io2_a_src; + status = wuffs_base__make_status(wuffs_base__suspension__short_read); + goto suspend; + } + iop_a_src += self->private_data.s_do_decode_image_config[0].scratch; + } else { + status = wuffs_base__make_status(wuffs_bmp__error__unsupported_bmp_file); + goto exit; + } + } + if (self->private_impl.f_compression != 3) { + if (self->private_impl.f_bits_per_pixel < 16) { + if (a_src) { + a_src->meta.ri = ((size_t)(iop_a_src - a_src->data.ptr)); + } + WUFFS_BASE__COROUTINE_SUSPENSION_POINT(46); + status = wuffs_bmp__decoder__read_palette(self, a_src); + if (a_src) { + iop_a_src = a_src->data.ptr + a_src->meta.ri; + } + if (status.repr) { + goto suspend; + } + } + } + if (self->private_impl.f_compression == 0) { + if ((self->private_impl.f_bits_per_pixel == 1) || (self->private_impl.f_bits_per_pixel == 2) || (self->private_impl.f_bits_per_pixel == 4)) { + self->private_impl.f_src_pixfmt = 2198077448; + self->private_impl.f_compression = 256; + } else if (self->private_impl.f_bits_per_pixel == 8) { + self->private_impl.f_src_pixfmt = 2198077448; + } else if (self->private_impl.f_bits_per_pixel == 16) { + self->private_impl.f_compression = 3; + self->private_impl.f_channel_masks[0] = 31; + self->private_impl.f_channel_masks[1] = 992; + self->private_impl.f_channel_masks[2] = 31744; + self->private_impl.f_channel_masks[3] = 0; + WUFFS_BASE__COROUTINE_SUSPENSION_POINT(47); + status = wuffs_bmp__decoder__process_masks(self); + if (status.repr) { + goto suspend; + } + self->private_impl.f_src_pixfmt = 2164308923; + } else if (self->private_impl.f_bits_per_pixel == 24) { + self->private_impl.f_src_pixfmt = 2147485832; + } else if (self->private_impl.f_bits_per_pixel == 32) { + if (self->private_impl.f_channel_masks[3] == 0) { + self->private_impl.f_src_pixfmt = 2415954056; + } else { + self->private_impl.f_src_pixfmt = 2164295816; + } + } else { + status = wuffs_base__make_status(wuffs_bmp__error__unsupported_bmp_file); + goto exit; + } + } else if (self->private_impl.f_compression == 1) { + if (self->private_impl.f_bits_per_pixel == 8) { + self->private_impl.f_src_pixfmt = 2198077448; + } else { + status = wuffs_base__make_status(wuffs_bmp__error__unsupported_bmp_file); + goto exit; + } + } else if (self->private_impl.f_compression == 2) { + if (self->private_impl.f_bits_per_pixel == 4) { + self->private_impl.f_src_pixfmt = 2198077448; + } else { + status = wuffs_base__make_status(wuffs_bmp__error__unsupported_bmp_file); + goto exit; + } + } else if (self->private_impl.f_compression == 3) { + if ((self->private_impl.f_bits_per_pixel == 16) || (self->private_impl.f_bits_per_pixel == 32)) { + self->private_impl.f_src_pixfmt = 2164308923; + } else { + status = wuffs_base__make_status(wuffs_bmp__error__unsupported_bmp_file); + goto exit; + } + } else { + status = wuffs_base__make_status(wuffs_bmp__error__unsupported_bmp_file); + goto exit; + } + if (((self->private_impl.f_bitmap_info_len < 40) || (self->private_impl.f_bitmap_info_len == 64)) && + (self->private_impl.f_bits_per_pixel != 1) && + (self->private_impl.f_bits_per_pixel != 4) && + (self->private_impl.f_bits_per_pixel != 8) && + (self->private_impl.f_bits_per_pixel != 24)) { + status = wuffs_base__make_status(wuffs_bmp__error__bad_header); + goto exit; + } + if (self->private_impl.f_bits_per_pixel == 1) { + v_byte_width = ((self->private_impl.f_width >> 3) + (((self->private_impl.f_width & 7) + 7) >> 3)); + self->private_impl.f_pad_per_row = ((4 - (v_byte_width & 3)) & 3); + } else if (self->private_impl.f_bits_per_pixel == 2) { + v_byte_width = ((self->private_impl.f_width >> 2) + (((self->private_impl.f_width & 3) + 3) >> 2)); + self->private_impl.f_pad_per_row = ((4 - (v_byte_width & 3)) & 3); + } else if (self->private_impl.f_bits_per_pixel == 4) { + v_byte_width = ((self->private_impl.f_width >> 1) + (self->private_impl.f_width & 1)); + self->private_impl.f_pad_per_row = ((4 - (v_byte_width & 3)) & 3); + } else if (self->private_impl.f_bits_per_pixel == 8) { + self->private_impl.f_pad_per_row = ((4 - (self->private_impl.f_width & 3)) & 3); + } else if (self->private_impl.f_bits_per_pixel == 16) { + self->private_impl.f_pad_per_row = ((self->private_impl.f_width & 1) * 2); + } else if (self->private_impl.f_bits_per_pixel == 24) { + self->private_impl.f_pad_per_row = (self->private_impl.f_width & 3); + } else if (self->private_impl.f_bits_per_pixel == 32) { + self->private_impl.f_pad_per_row = 0; + } + self->private_impl.f_frame_config_io_position = wuffs_base__u64__sat_add((a_src ? a_src->meta.pos : 0), ((uint64_t)(iop_a_src - io0_a_src))); + if (a_dst != NULL) { + v_dst_pixfmt = 2164295816; + if ((self->private_impl.f_channel_num_bits[0] > 8) || + (self->private_impl.f_channel_num_bits[1] > 8) || + (self->private_impl.f_channel_num_bits[2] > 8) || + (self->private_impl.f_channel_num_bits[3] > 8)) { + v_dst_pixfmt = 2164308923; + } + wuffs_base__image_config__set( + a_dst, + v_dst_pixfmt, + 0, + self->private_impl.f_width, + self->private_impl.f_height, + self->private_impl.f_frame_config_io_position, + (self->private_impl.f_channel_masks[3] == 0)); + } + self->private_impl.f_call_sequence = 32; + + ok: + self->private_impl.p_do_decode_image_config[0] = 0; + goto exit; + } + + goto suspend; + suspend: + self->private_impl.p_do_decode_image_config[0] = wuffs_base__status__is_suspension(&status) ? coro_susp_point : 0; + + goto exit; + exit: + if (a_src && a_src->data.ptr) { + a_src->meta.ri = ((size_t)(iop_a_src - a_src->data.ptr)); + } + + return status; +} + +// -------- func bmp.decoder.decode_frame_config + +WUFFS_BASE__MAYBE_STATIC wuffs_base__status +wuffs_bmp__decoder__decode_frame_config( + wuffs_bmp__decoder* self, + wuffs_base__frame_config* a_dst, + wuffs_base__io_buffer* a_src) { + if (!self) { + return wuffs_base__make_status(wuffs_base__error__bad_receiver); + } + if (self->private_impl.magic != WUFFS_BASE__MAGIC) { + return wuffs_base__make_status( + (self->private_impl.magic == WUFFS_BASE__DISABLED) + ? wuffs_base__error__disabled_by_previous_error + : wuffs_base__error__initialize_not_called); + } + if (!a_src) { + self->private_impl.magic = WUFFS_BASE__DISABLED; + return wuffs_base__make_status(wuffs_base__error__bad_argument); + } + if ((self->private_impl.active_coroutine != 0) && + (self->private_impl.active_coroutine != 2)) { + self->private_impl.magic = WUFFS_BASE__DISABLED; + return wuffs_base__make_status(wuffs_base__error__interleaved_coroutine_calls); + } + self->private_impl.active_coroutine = 0; + wuffs_base__status status = wuffs_base__make_status(NULL); + + wuffs_base__status v_status = wuffs_base__make_status(NULL); + + uint32_t coro_susp_point = self->private_impl.p_decode_frame_config[0]; + switch (coro_susp_point) { + WUFFS_BASE__COROUTINE_SUSPENSION_POINT_0; + + while (true) { + { + wuffs_base__status t_0 = wuffs_bmp__decoder__do_decode_frame_config(self, a_dst, a_src); + v_status = t_0; + } + if ((v_status.repr == wuffs_base__suspension__short_read) && (a_src && a_src->meta.closed)) { + status = wuffs_base__make_status(wuffs_bmp__error__truncated_input); + goto exit; + } + status = v_status; + WUFFS_BASE__COROUTINE_SUSPENSION_POINT_MAYBE_SUSPEND(1); + } + + ok: + self->private_impl.p_decode_frame_config[0] = 0; + goto exit; + } + + goto suspend; + suspend: + self->private_impl.p_decode_frame_config[0] = wuffs_base__status__is_suspension(&status) ? coro_susp_point : 0; + self->private_impl.active_coroutine = wuffs_base__status__is_suspension(&status) ? 2 : 0; + + goto exit; + exit: + if (wuffs_base__status__is_error(&status)) { + self->private_impl.magic = WUFFS_BASE__DISABLED; + } + return status; +} + +// -------- func bmp.decoder.do_decode_frame_config + +static wuffs_base__status +wuffs_bmp__decoder__do_decode_frame_config( + wuffs_bmp__decoder* self, + wuffs_base__frame_config* a_dst, + wuffs_base__io_buffer* a_src) { + wuffs_base__status status = wuffs_base__make_status(NULL); + + const uint8_t* iop_a_src = NULL; + const uint8_t* io0_a_src WUFFS_BASE__POTENTIALLY_UNUSED = NULL; + const uint8_t* io1_a_src WUFFS_BASE__POTENTIALLY_UNUSED = NULL; + const uint8_t* io2_a_src WUFFS_BASE__POTENTIALLY_UNUSED = NULL; + if (a_src && a_src->data.ptr) { + io0_a_src = a_src->data.ptr; + io1_a_src = io0_a_src + a_src->meta.ri; + iop_a_src = io1_a_src; + io2_a_src = io0_a_src + a_src->meta.wi; + } + + uint32_t coro_susp_point = self->private_impl.p_do_decode_frame_config[0]; + switch (coro_susp_point) { + WUFFS_BASE__COROUTINE_SUSPENSION_POINT_0; + + if (self->private_impl.f_call_sequence == 32) { + } else if (self->private_impl.f_call_sequence < 32) { + if (a_src) { + a_src->meta.ri = ((size_t)(iop_a_src - a_src->data.ptr)); + } + WUFFS_BASE__COROUTINE_SUSPENSION_POINT(1); + status = wuffs_bmp__decoder__do_decode_image_config(self, NULL, a_src); + if (a_src) { + iop_a_src = a_src->data.ptr + a_src->meta.ri; + } + if (status.repr) { + goto suspend; + } + } else if (self->private_impl.f_call_sequence == 40) { + if (self->private_impl.f_frame_config_io_position != wuffs_base__u64__sat_add((a_src ? a_src->meta.pos : 0), ((uint64_t)(iop_a_src - io0_a_src)))) { + status = wuffs_base__make_status(wuffs_base__error__bad_restart); + goto exit; + } + } else if (self->private_impl.f_call_sequence == 64) { + self->private_impl.f_call_sequence = 96; + status = wuffs_base__make_status(wuffs_base__note__end_of_data); + goto ok; + } else { + status = wuffs_base__make_status(wuffs_base__note__end_of_data); + goto ok; + } + if (a_dst != NULL) { + wuffs_base__frame_config__set( + a_dst, + wuffs_base__utility__make_rect_ie_u32( + 0, + 0, + self->private_impl.f_width, + self->private_impl.f_height), + ((wuffs_base__flicks)(0)), + 0, + self->private_impl.f_frame_config_io_position, + 0, + true, + false, + 4278190080); + } + self->private_impl.f_call_sequence = 64; + + ok: + self->private_impl.p_do_decode_frame_config[0] = 0; + goto exit; + } + + goto suspend; + suspend: + self->private_impl.p_do_decode_frame_config[0] = wuffs_base__status__is_suspension(&status) ? coro_susp_point : 0; + + goto exit; + exit: + if (a_src && a_src->data.ptr) { + a_src->meta.ri = ((size_t)(iop_a_src - a_src->data.ptr)); + } + + return status; +} + +// -------- func bmp.decoder.decode_frame + +WUFFS_BASE__MAYBE_STATIC wuffs_base__status +wuffs_bmp__decoder__decode_frame( + wuffs_bmp__decoder* self, + wuffs_base__pixel_buffer* a_dst, + wuffs_base__io_buffer* a_src, + wuffs_base__pixel_blend a_blend, + wuffs_base__slice_u8 a_workbuf, + wuffs_base__decode_frame_options* a_opts) { + if (!self) { + return wuffs_base__make_status(wuffs_base__error__bad_receiver); + } + if (self->private_impl.magic != WUFFS_BASE__MAGIC) { + return wuffs_base__make_status( + (self->private_impl.magic == WUFFS_BASE__DISABLED) + ? wuffs_base__error__disabled_by_previous_error + : wuffs_base__error__initialize_not_called); + } + if (!a_dst || !a_src) { + self->private_impl.magic = WUFFS_BASE__DISABLED; + return wuffs_base__make_status(wuffs_base__error__bad_argument); + } + if ((self->private_impl.active_coroutine != 0) && + (self->private_impl.active_coroutine != 3)) { + self->private_impl.magic = WUFFS_BASE__DISABLED; + return wuffs_base__make_status(wuffs_base__error__interleaved_coroutine_calls); + } + self->private_impl.active_coroutine = 0; + wuffs_base__status status = wuffs_base__make_status(NULL); + + wuffs_base__status v_status = wuffs_base__make_status(NULL); + + uint32_t coro_susp_point = self->private_impl.p_decode_frame[0]; + switch (coro_susp_point) { + WUFFS_BASE__COROUTINE_SUSPENSION_POINT_0; + + while (true) { + { + wuffs_base__status t_0 = wuffs_bmp__decoder__do_decode_frame(self, + a_dst, + a_src, + a_blend, + a_workbuf, + a_opts); + v_status = t_0; + } + if ((v_status.repr == wuffs_base__suspension__short_read) && (a_src && a_src->meta.closed)) { + status = wuffs_base__make_status(wuffs_bmp__error__truncated_input); + goto exit; + } + status = v_status; + WUFFS_BASE__COROUTINE_SUSPENSION_POINT_MAYBE_SUSPEND(1); + } + + ok: + self->private_impl.p_decode_frame[0] = 0; + goto exit; + } + + goto suspend; + suspend: + self->private_impl.p_decode_frame[0] = wuffs_base__status__is_suspension(&status) ? coro_susp_point : 0; + self->private_impl.active_coroutine = wuffs_base__status__is_suspension(&status) ? 3 : 0; + + goto exit; + exit: + if (wuffs_base__status__is_error(&status)) { + self->private_impl.magic = WUFFS_BASE__DISABLED; + } + return status; +} + +// -------- func bmp.decoder.do_decode_frame + +static wuffs_base__status +wuffs_bmp__decoder__do_decode_frame( + wuffs_bmp__decoder* self, + wuffs_base__pixel_buffer* a_dst, + wuffs_base__io_buffer* a_src, + wuffs_base__pixel_blend a_blend, + wuffs_base__slice_u8 a_workbuf, + wuffs_base__decode_frame_options* a_opts) { + wuffs_base__status status = wuffs_base__make_status(NULL); + + wuffs_base__status v_status = wuffs_base__make_status(NULL); + + const uint8_t* iop_a_src = NULL; + const uint8_t* io0_a_src WUFFS_BASE__POTENTIALLY_UNUSED = NULL; + const uint8_t* io1_a_src WUFFS_BASE__POTENTIALLY_UNUSED = NULL; + const uint8_t* io2_a_src WUFFS_BASE__POTENTIALLY_UNUSED = NULL; + if (a_src && a_src->data.ptr) { + io0_a_src = a_src->data.ptr; + io1_a_src = io0_a_src + a_src->meta.ri; + iop_a_src = io1_a_src; + io2_a_src = io0_a_src + a_src->meta.wi; + } + + uint32_t coro_susp_point = self->private_impl.p_do_decode_frame[0]; + switch (coro_susp_point) { + WUFFS_BASE__COROUTINE_SUSPENSION_POINT_0; + + if (self->private_impl.f_call_sequence == 64) { + } else if (self->private_impl.f_call_sequence < 64) { + if (a_src) { + a_src->meta.ri = ((size_t)(iop_a_src - a_src->data.ptr)); + } + WUFFS_BASE__COROUTINE_SUSPENSION_POINT(1); + status = wuffs_bmp__decoder__do_decode_frame_config(self, NULL, a_src); + if (a_src) { + iop_a_src = a_src->data.ptr + a_src->meta.ri; + } + if (status.repr) { + goto suspend; + } + } else { + status = wuffs_base__make_status(wuffs_base__note__end_of_data); + goto ok; + } + self->private_data.s_do_decode_frame[0].scratch = self->private_impl.f_padding; + WUFFS_BASE__COROUTINE_SUSPENSION_POINT(2); + if (self->private_data.s_do_decode_frame[0].scratch > ((uint64_t)(io2_a_src - iop_a_src))) { + self->private_data.s_do_decode_frame[0].scratch -= ((uint64_t)(io2_a_src - iop_a_src)); + iop_a_src = io2_a_src; + status = wuffs_base__make_status(wuffs_base__suspension__short_read); + goto suspend; + } + iop_a_src += self->private_data.s_do_decode_frame[0].scratch; + if ((self->private_impl.f_width > 0) && (self->private_impl.f_height > 0)) { + self->private_impl.f_dst_x = 0; + if (self->private_impl.f_top_down) { + self->private_impl.f_dst_y = 0; + self->private_impl.f_dst_y_inc = 1; + } else { + self->private_impl.f_dst_y = ((uint32_t)(self->private_impl.f_height - 1)); + self->private_impl.f_dst_y_inc = 4294967295; + } + v_status = wuffs_base__pixel_swizzler__prepare(&self->private_impl.f_swizzler, + wuffs_base__pixel_buffer__pixel_format(a_dst), + wuffs_base__pixel_buffer__palette_or_else(a_dst, wuffs_base__make_slice_u8_ij(self->private_data.f_scratch, 1024, 2048)), + wuffs_base__utility__make_pixel_format(self->private_impl.f_src_pixfmt), + wuffs_base__make_slice_u8(self->private_data.f_src_palette, 1024), + a_blend); + if ( ! wuffs_base__status__is_ok(&v_status)) { + status = v_status; + if (wuffs_base__status__is_error(&status)) { + goto exit; + } else if (wuffs_base__status__is_suspension(&status)) { + status = wuffs_base__make_status(wuffs_base__error__cannot_return_a_suspension); + goto exit; + } + goto ok; + } + while (true) { + if (self->private_impl.f_compression == 0) { + if (a_src) { + a_src->meta.ri = ((size_t)(iop_a_src - a_src->data.ptr)); + } + v_status = wuffs_bmp__decoder__swizzle_none(self, a_dst, a_src); + if (a_src) { + iop_a_src = a_src->data.ptr + a_src->meta.ri; + } + } else if (self->private_impl.f_compression < 3) { + if (a_src) { + a_src->meta.ri = ((size_t)(iop_a_src - a_src->data.ptr)); + } + v_status = wuffs_bmp__decoder__swizzle_rle(self, a_dst, a_src); + if (a_src) { + iop_a_src = a_src->data.ptr + a_src->meta.ri; + } + } else if (self->private_impl.f_compression == 3) { + if (a_src) { + a_src->meta.ri = ((size_t)(iop_a_src - a_src->data.ptr)); + } + v_status = wuffs_bmp__decoder__swizzle_bitfields(self, a_dst, a_src); + if (a_src) { + iop_a_src = a_src->data.ptr + a_src->meta.ri; + } + } else { + if (a_src) { + a_src->meta.ri = ((size_t)(iop_a_src - a_src->data.ptr)); + } + v_status = wuffs_bmp__decoder__swizzle_low_bit_depth(self, a_dst, a_src); + if (a_src) { + iop_a_src = a_src->data.ptr + a_src->meta.ri; + } + } + if (wuffs_base__status__is_ok(&v_status)) { + goto label__0__break; + } else if (v_status.repr != wuffs_bmp__note__internal_note_short_read) { + status = v_status; + if (wuffs_base__status__is_error(&status)) { + goto exit; + } else if (wuffs_base__status__is_suspension(&status)) { + status = wuffs_base__make_status(wuffs_base__error__cannot_return_a_suspension); + goto exit; + } + goto ok; + } + status = wuffs_base__make_status(wuffs_base__suspension__short_read); + WUFFS_BASE__COROUTINE_SUSPENSION_POINT_MAYBE_SUSPEND(3); + } + label__0__break:; + self->private_data.s_do_decode_frame[0].scratch = self->private_impl.f_pending_pad; + WUFFS_BASE__COROUTINE_SUSPENSION_POINT(4); + if (self->private_data.s_do_decode_frame[0].scratch > ((uint64_t)(io2_a_src - iop_a_src))) { + self->private_data.s_do_decode_frame[0].scratch -= ((uint64_t)(io2_a_src - iop_a_src)); + iop_a_src = io2_a_src; + status = wuffs_base__make_status(wuffs_base__suspension__short_read); + goto suspend; + } + iop_a_src += self->private_data.s_do_decode_frame[0].scratch; + self->private_impl.f_pending_pad = 0; + } + self->private_impl.f_call_sequence = 96; + + ok: + self->private_impl.p_do_decode_frame[0] = 0; + goto exit; + } + + goto suspend; + suspend: + self->private_impl.p_do_decode_frame[0] = wuffs_base__status__is_suspension(&status) ? coro_susp_point : 0; + + goto exit; + exit: + if (a_src && a_src->data.ptr) { + a_src->meta.ri = ((size_t)(iop_a_src - a_src->data.ptr)); + } + + return status; +} + +// -------- func bmp.decoder.swizzle_none + +static wuffs_base__status +wuffs_bmp__decoder__swizzle_none( + wuffs_bmp__decoder* self, + wuffs_base__pixel_buffer* a_dst, + wuffs_base__io_buffer* a_src) { + wuffs_base__status status = wuffs_base__make_status(NULL); + + wuffs_base__pixel_format v_dst_pixfmt = {0}; + uint32_t v_dst_bits_per_pixel = 0; + uint64_t v_dst_bytes_per_pixel = 0; + uint64_t v_dst_bytes_per_row = 0; + uint32_t v_src_bytes_per_pixel = 0; + wuffs_base__slice_u8 v_dst_palette = {0}; + wuffs_base__table_u8 v_tab = {0}; + wuffs_base__slice_u8 v_dst = {0}; + uint64_t v_i = 0; + uint64_t v_j = 0; + uint64_t v_n = 0; + + const uint8_t* iop_a_src = NULL; + const uint8_t* io0_a_src WUFFS_BASE__POTENTIALLY_UNUSED = NULL; + const uint8_t* io1_a_src WUFFS_BASE__POTENTIALLY_UNUSED = NULL; + const uint8_t* io2_a_src WUFFS_BASE__POTENTIALLY_UNUSED = NULL; + if (a_src && a_src->data.ptr) { + io0_a_src = a_src->data.ptr; + io1_a_src = io0_a_src + a_src->meta.ri; + iop_a_src = io1_a_src; + io2_a_src = io0_a_src + a_src->meta.wi; + } + + v_dst_pixfmt = wuffs_base__pixel_buffer__pixel_format(a_dst); + v_dst_bits_per_pixel = wuffs_base__pixel_format__bits_per_pixel(&v_dst_pixfmt); + if ((v_dst_bits_per_pixel & 7) != 0) { + status = wuffs_base__make_status(wuffs_base__error__unsupported_option); + goto exit; + } + v_dst_bytes_per_pixel = ((uint64_t)((v_dst_bits_per_pixel / 8))); + v_dst_bytes_per_row = (((uint64_t)(self->private_impl.f_width)) * v_dst_bytes_per_pixel); + v_dst_palette = wuffs_base__pixel_buffer__palette_or_else(a_dst, wuffs_base__make_slice_u8_ij(self->private_data.f_scratch, 1024, 2048)); + v_tab = wuffs_base__pixel_buffer__plane(a_dst, 0); + label__outer__continue:; + while (true) { + while (self->private_impl.f_pending_pad > 0) { + if (((uint64_t)(io2_a_src - iop_a_src)) <= 0) { + status = wuffs_base__make_status(wuffs_bmp__note__internal_note_short_read); + goto ok; + } + self->private_impl.f_pending_pad -= 1; + iop_a_src += 1; + } + while (true) { + if (self->private_impl.f_dst_x == self->private_impl.f_width) { + self->private_impl.f_dst_x = 0; + self->private_impl.f_dst_y += self->private_impl.f_dst_y_inc; + if (self->private_impl.f_dst_y >= self->private_impl.f_height) { + if (self->private_impl.f_height > 0) { + self->private_impl.f_pending_pad = self->private_impl.f_pad_per_row; + } + goto label__outer__break; + } else if (self->private_impl.f_pad_per_row != 0) { + self->private_impl.f_pending_pad = self->private_impl.f_pad_per_row; + goto label__outer__continue; + } + } + v_dst = wuffs_base__table_u8__row_u32(v_tab, self->private_impl.f_dst_y); + if (v_dst_bytes_per_row < ((uint64_t)(v_dst.len))) { + v_dst = wuffs_base__slice_u8__subslice_j(v_dst, v_dst_bytes_per_row); + } + v_i = (((uint64_t)(self->private_impl.f_dst_x)) * v_dst_bytes_per_pixel); + if (v_i >= ((uint64_t)(v_dst.len))) { + if (self->private_impl.f_bits_per_pixel > 32) { + status = wuffs_base__make_status(wuffs_bmp__error__unsupported_bmp_file); + goto exit; + } + v_src_bytes_per_pixel = (self->private_impl.f_bits_per_pixel / 8); + if (v_src_bytes_per_pixel == 0) { + status = wuffs_base__make_status(wuffs_bmp__error__unsupported_bmp_file); + goto exit; + } + v_n = (((uint64_t)(io2_a_src - iop_a_src)) / ((uint64_t)(v_src_bytes_per_pixel))); + v_n = wuffs_base__u64__min(v_n, ((uint64_t)(((uint32_t)(self->private_impl.f_width - self->private_impl.f_dst_x))))); + v_j = v_n; + while (v_j >= 8) { + if (((uint64_t)(io2_a_src - iop_a_src)) >= ((uint64_t)((v_src_bytes_per_pixel * 8)))) { + iop_a_src += (v_src_bytes_per_pixel * 8); + } + v_j -= 8; + } + while (v_j > 0) { + if (((uint64_t)(io2_a_src - iop_a_src)) >= ((uint64_t)((v_src_bytes_per_pixel * 1)))) { + iop_a_src += (v_src_bytes_per_pixel * 1); + } + v_j -= 1; + } + } else { + v_n = wuffs_base__pixel_swizzler__swizzle_interleaved_from_reader( + &self->private_impl.f_swizzler, + wuffs_base__slice_u8__subslice_i(v_dst, v_i), + v_dst_palette, + &iop_a_src, + io2_a_src); + } + if (v_n == 0) { + status = wuffs_base__make_status(wuffs_bmp__note__internal_note_short_read); + goto ok; + } + wuffs_base__u32__sat_add_indirect(&self->private_impl.f_dst_x, ((uint32_t)((v_n & 4294967295)))); + } + } + label__outer__break:; + status = wuffs_base__make_status(NULL); + goto ok; + + ok: + goto exit; + exit: + if (a_src && a_src->data.ptr) { + a_src->meta.ri = ((size_t)(iop_a_src - a_src->data.ptr)); + } + + return status; +} + +// -------- func bmp.decoder.swizzle_rle + +static wuffs_base__status +wuffs_bmp__decoder__swizzle_rle( + wuffs_bmp__decoder* self, + wuffs_base__pixel_buffer* a_dst, + wuffs_base__io_buffer* a_src) { + wuffs_base__status status = wuffs_base__make_status(NULL); + + wuffs_base__pixel_format v_dst_pixfmt = {0}; + uint32_t v_dst_bits_per_pixel = 0; + uint64_t v_dst_bytes_per_pixel = 0; + uint64_t v_dst_bytes_per_row = 0; + wuffs_base__slice_u8 v_dst_palette = {0}; + wuffs_base__table_u8 v_tab = {0}; + wuffs_base__slice_u8 v_row = {0}; + wuffs_base__slice_u8 v_dst = {0}; + uint64_t v_i = 0; + uint64_t v_n = 0; + uint32_t v_p0 = 0; + uint8_t v_code = 0; + uint8_t v_indexes[2] = {0}; + uint32_t v_rle_state = 0; + uint32_t v_chunk_bits = 0; + uint32_t v_chunk_count = 0; + + const uint8_t* iop_a_src = NULL; + const uint8_t* io0_a_src WUFFS_BASE__POTENTIALLY_UNUSED = NULL; + const uint8_t* io1_a_src WUFFS_BASE__POTENTIALLY_UNUSED = NULL; + const uint8_t* io2_a_src WUFFS_BASE__POTENTIALLY_UNUSED = NULL; + if (a_src && a_src->data.ptr) { + io0_a_src = a_src->data.ptr; + io1_a_src = io0_a_src + a_src->meta.ri; + iop_a_src = io1_a_src; + io2_a_src = io0_a_src + a_src->meta.wi; + } + + v_dst_pixfmt = wuffs_base__pixel_buffer__pixel_format(a_dst); + v_dst_bits_per_pixel = wuffs_base__pixel_format__bits_per_pixel(&v_dst_pixfmt); + if ((v_dst_bits_per_pixel & 7) != 0) { + status = wuffs_base__make_status(wuffs_base__error__unsupported_option); + goto exit; + } + v_dst_bytes_per_pixel = ((uint64_t)((v_dst_bits_per_pixel / 8))); + v_dst_bytes_per_row = (((uint64_t)(self->private_impl.f_width)) * v_dst_bytes_per_pixel); + v_dst_palette = wuffs_base__pixel_buffer__palette_or_else(a_dst, wuffs_base__make_slice_u8_ij(self->private_data.f_scratch, 1024, 2048)); + v_tab = wuffs_base__pixel_buffer__plane(a_dst, 0); + v_rle_state = self->private_impl.f_rle_state; + label__outer__continue:; + while (true) { + v_row = wuffs_base__table_u8__row_u32(v_tab, self->private_impl.f_dst_y); + if (v_dst_bytes_per_row < ((uint64_t)(v_row.len))) { + v_row = wuffs_base__slice_u8__subslice_j(v_row, v_dst_bytes_per_row); + } + label__middle__continue:; + while (true) { + v_i = (((uint64_t)(self->private_impl.f_dst_x)) * v_dst_bytes_per_pixel); + if (v_i <= ((uint64_t)(v_row.len))) { + v_dst = wuffs_base__slice_u8__subslice_i(v_row, v_i); + } else { + v_dst = wuffs_base__utility__empty_slice_u8(); + } + while (true) { + label__inner__continue:; + while (true) { + if (v_rle_state == 0) { + if (((uint64_t)(io2_a_src - iop_a_src)) < 1) { + goto label__goto_suspend__break; + } + v_code = wuffs_base__peek_u8be__no_bounds_check(iop_a_src); + iop_a_src += 1; + if (v_code == 0) { + v_rle_state = 2; + goto label__inner__continue; + } + self->private_impl.f_rle_length = ((uint32_t)(v_code)); + v_rle_state = 1; + goto label__inner__continue; + } else if (v_rle_state == 1) { + if (((uint64_t)(io2_a_src - iop_a_src)) < 1) { + goto label__goto_suspend__break; + } + v_code = wuffs_base__peek_u8be__no_bounds_check(iop_a_src); + iop_a_src += 1; + if (self->private_impl.f_bits_per_pixel == 8) { + v_p0 = 0; + while (v_p0 < self->private_impl.f_rle_length) { + self->private_data.f_scratch[v_p0] = v_code; + v_p0 += 1; + } + } else { + v_indexes[0] = ((uint8_t)((v_code >> 4))); + v_indexes[1] = (v_code & 15); + v_p0 = 0; + while (v_p0 < self->private_impl.f_rle_length) { + self->private_data.f_scratch[(v_p0 + 0)] = v_indexes[0]; + self->private_data.f_scratch[(v_p0 + 1)] = v_indexes[1]; + v_p0 += 2; + } + } + wuffs_base__pixel_swizzler__swizzle_interleaved_from_slice(&self->private_impl.f_swizzler, v_dst, v_dst_palette, wuffs_base__make_slice_u8(self->private_data.f_scratch, self->private_impl.f_rle_length)); + wuffs_base__u32__sat_add_indirect(&self->private_impl.f_dst_x, self->private_impl.f_rle_length); + v_rle_state = 0; + goto label__middle__continue; + } else if (v_rle_state == 2) { + if (((uint64_t)(io2_a_src - iop_a_src)) < 1) { + goto label__goto_suspend__break; + } + v_code = wuffs_base__peek_u8be__no_bounds_check(iop_a_src); + iop_a_src += 1; + if (v_code < 2) { + if ((self->private_impl.f_dst_y >= self->private_impl.f_height) && (v_code == 0)) { + status = wuffs_base__make_status(wuffs_bmp__error__bad_rle_compression); + goto exit; + } + wuffs_base__pixel_swizzler__swizzle_interleaved_transparent_black(&self->private_impl.f_swizzler, v_dst, v_dst_palette, 18446744073709551615u); + self->private_impl.f_dst_x = 0; + self->private_impl.f_dst_y += self->private_impl.f_dst_y_inc; + if (v_code > 0) { + goto label__outer__break; + } + v_rle_state = 0; + goto label__outer__continue; + } else if (v_code == 2) { + v_rle_state = 4; + goto label__inner__continue; + } + self->private_impl.f_rle_length = ((uint32_t)(v_code)); + self->private_impl.f_rle_padded = ((self->private_impl.f_bits_per_pixel == 8) && ((v_code & 1) != 0)); + v_rle_state = 3; + goto label__inner__continue; + } else if (v_rle_state == 3) { + if (self->private_impl.f_bits_per_pixel == 8) { + v_n = wuffs_base__pixel_swizzler__limited_swizzle_u32_interleaved_from_reader( + &self->private_impl.f_swizzler, + self->private_impl.f_rle_length, + v_dst, + v_dst_palette, + &iop_a_src, + io2_a_src); + wuffs_base__u32__sat_add_indirect(&self->private_impl.f_dst_x, ((uint32_t)((v_n & 4294967295)))); + wuffs_base__u32__sat_sub_indirect(&self->private_impl.f_rle_length, ((uint32_t)((v_n & 4294967295)))); + } else { + v_chunk_count = ((self->private_impl.f_rle_length + 3) / 4); + v_p0 = 0; + while ((v_chunk_count > 0) && (((uint64_t)(io2_a_src - iop_a_src)) >= 2)) { + v_chunk_bits = ((uint32_t)(wuffs_base__peek_u16be__no_bounds_check(iop_a_src))); + iop_a_src += 2; + self->private_data.f_scratch[(v_p0 + 0)] = ((uint8_t)((15 & (v_chunk_bits >> 12)))); + self->private_data.f_scratch[(v_p0 + 1)] = ((uint8_t)((15 & (v_chunk_bits >> 8)))); + self->private_data.f_scratch[(v_p0 + 2)] = ((uint8_t)((15 & (v_chunk_bits >> 4)))); + self->private_data.f_scratch[(v_p0 + 3)] = ((uint8_t)((15 & (v_chunk_bits >> 0)))); + v_p0 = ((v_p0 & 255) + 4); + v_chunk_count -= 1; + } + v_p0 = wuffs_base__u32__min(v_p0, self->private_impl.f_rle_length); + wuffs_base__pixel_swizzler__swizzle_interleaved_from_slice(&self->private_impl.f_swizzler, v_dst, v_dst_palette, wuffs_base__make_slice_u8(self->private_data.f_scratch, v_p0)); + wuffs_base__u32__sat_add_indirect(&self->private_impl.f_dst_x, v_p0); + wuffs_base__u32__sat_sub_indirect(&self->private_impl.f_rle_length, v_p0); + } + if (self->private_impl.f_rle_length > 0) { + goto label__goto_suspend__break; + } + if (self->private_impl.f_rle_padded) { + if (((uint64_t)(io2_a_src - iop_a_src)) < 1) { + goto label__goto_suspend__break; + } + iop_a_src += 1; + self->private_impl.f_rle_padded = false; + } + v_rle_state = 0; + goto label__middle__continue; + } else if (v_rle_state == 4) { + if (((uint64_t)(io2_a_src - iop_a_src)) < 1) { + goto label__goto_suspend__break; + } + self->private_impl.f_rle_delta_x = wuffs_base__peek_u8be__no_bounds_check(iop_a_src); + iop_a_src += 1; + v_rle_state = 5; + goto label__inner__continue; + } + if (((uint64_t)(io2_a_src - iop_a_src)) < 1) { + goto label__goto_suspend__break; + } + v_code = wuffs_base__peek_u8be__no_bounds_check(iop_a_src); + iop_a_src += 1; + if (self->private_impl.f_rle_delta_x > 0) { + wuffs_base__pixel_swizzler__swizzle_interleaved_transparent_black(&self->private_impl.f_swizzler, v_dst, v_dst_palette, ((uint64_t)(self->private_impl.f_rle_delta_x))); + wuffs_base__u32__sat_add_indirect(&self->private_impl.f_dst_x, ((uint32_t)(self->private_impl.f_rle_delta_x))); + self->private_impl.f_rle_delta_x = 0; + if (self->private_impl.f_dst_x > self->private_impl.f_width) { + status = wuffs_base__make_status(wuffs_bmp__error__bad_rle_compression); + goto exit; + } + } + if (v_code > 0) { +#if defined(__GNUC__) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wconversion" +#endif + v_code -= 1; +#if defined(__GNUC__) +#pragma GCC diagnostic pop +#endif + while (true) { + self->private_impl.f_dst_y += self->private_impl.f_dst_y_inc; + if (self->private_impl.f_dst_y >= self->private_impl.f_height) { + status = wuffs_base__make_status(wuffs_bmp__error__bad_rle_compression); + goto exit; + } + v_row = wuffs_base__table_u8__row_u32(v_tab, self->private_impl.f_dst_y); + if (v_dst_bytes_per_row < ((uint64_t)(v_row.len))) { + v_row = wuffs_base__slice_u8__subslice_j(v_row, v_dst_bytes_per_row); + } + if (v_code <= 0) { + wuffs_base__pixel_swizzler__swizzle_interleaved_transparent_black(&self->private_impl.f_swizzler, v_row, v_dst_palette, ((uint64_t)(self->private_impl.f_dst_x))); + goto label__0__break; + } + wuffs_base__pixel_swizzler__swizzle_interleaved_transparent_black(&self->private_impl.f_swizzler, v_row, v_dst_palette, 18446744073709551615u); +#if defined(__GNUC__) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wconversion" +#endif + v_code -= 1; +#if defined(__GNUC__) +#pragma GCC diagnostic pop +#endif + } + label__0__break:; + } + v_rle_state = 0; + goto label__middle__continue; + } + } + label__goto_suspend__break:; + self->private_impl.f_rle_state = v_rle_state; + status = wuffs_base__make_status(wuffs_bmp__note__internal_note_short_read); + goto ok; + } + } + label__outer__break:; + while (self->private_impl.f_dst_y < self->private_impl.f_height) { + v_row = wuffs_base__table_u8__row_u32(v_tab, self->private_impl.f_dst_y); + if (v_dst_bytes_per_row < ((uint64_t)(v_row.len))) { + v_row = wuffs_base__slice_u8__subslice_j(v_row, v_dst_bytes_per_row); + } + wuffs_base__pixel_swizzler__swizzle_interleaved_transparent_black(&self->private_impl.f_swizzler, v_row, v_dst_palette, 18446744073709551615u); + self->private_impl.f_dst_y += self->private_impl.f_dst_y_inc; + } + status = wuffs_base__make_status(NULL); + goto ok; + + ok: + goto exit; + exit: + if (a_src && a_src->data.ptr) { + a_src->meta.ri = ((size_t)(iop_a_src - a_src->data.ptr)); + } + + return status; +} + +// -------- func bmp.decoder.swizzle_bitfields + +static wuffs_base__status +wuffs_bmp__decoder__swizzle_bitfields( + wuffs_bmp__decoder* self, + wuffs_base__pixel_buffer* a_dst, + wuffs_base__io_buffer* a_src) { + wuffs_base__status status = wuffs_base__make_status(NULL); + + wuffs_base__pixel_format v_dst_pixfmt = {0}; + uint32_t v_dst_bits_per_pixel = 0; + uint64_t v_dst_bytes_per_pixel = 0; + uint64_t v_dst_bytes_per_row = 0; + wuffs_base__slice_u8 v_dst_palette = {0}; + wuffs_base__table_u8 v_tab = {0}; + wuffs_base__slice_u8 v_dst = {0}; + uint64_t v_i = 0; + uint64_t v_n = 0; + uint32_t v_p0 = 0; + uint32_t v_p1 = 0; + uint32_t v_p1_temp = 0; + uint32_t v_num_bits = 0; + uint32_t v_c = 0; + uint32_t v_c32 = 0; + uint32_t v_channel = 0; + + const uint8_t* iop_a_src = NULL; + const uint8_t* io0_a_src WUFFS_BASE__POTENTIALLY_UNUSED = NULL; + const uint8_t* io1_a_src WUFFS_BASE__POTENTIALLY_UNUSED = NULL; + const uint8_t* io2_a_src WUFFS_BASE__POTENTIALLY_UNUSED = NULL; + if (a_src && a_src->data.ptr) { + io0_a_src = a_src->data.ptr; + io1_a_src = io0_a_src + a_src->meta.ri; + iop_a_src = io1_a_src; + io2_a_src = io0_a_src + a_src->meta.wi; + } + + v_dst_pixfmt = wuffs_base__pixel_buffer__pixel_format(a_dst); + v_dst_bits_per_pixel = wuffs_base__pixel_format__bits_per_pixel(&v_dst_pixfmt); + if ((v_dst_bits_per_pixel & 7) != 0) { + status = wuffs_base__make_status(wuffs_base__error__unsupported_option); + goto exit; + } + v_dst_bytes_per_pixel = ((uint64_t)((v_dst_bits_per_pixel / 8))); + v_dst_bytes_per_row = (((uint64_t)(self->private_impl.f_width)) * v_dst_bytes_per_pixel); + v_dst_palette = wuffs_base__pixel_buffer__palette_or_else(a_dst, wuffs_base__make_slice_u8_ij(self->private_data.f_scratch, 1024, 2048)); + v_tab = wuffs_base__pixel_buffer__plane(a_dst, 0); + label__outer__continue:; + while (true) { + while (self->private_impl.f_pending_pad > 0) { + if (((uint64_t)(io2_a_src - iop_a_src)) <= 0) { + status = wuffs_base__make_status(wuffs_bmp__note__internal_note_short_read); + goto ok; + } + self->private_impl.f_pending_pad -= 1; + iop_a_src += 1; + } + while (true) { + if (self->private_impl.f_dst_x == self->private_impl.f_width) { + self->private_impl.f_dst_x = 0; + self->private_impl.f_dst_y += self->private_impl.f_dst_y_inc; + if (self->private_impl.f_dst_y >= self->private_impl.f_height) { + if (self->private_impl.f_height > 0) { + self->private_impl.f_pending_pad = self->private_impl.f_pad_per_row; + } + goto label__outer__break; + } else if (self->private_impl.f_pad_per_row != 0) { + self->private_impl.f_pending_pad = self->private_impl.f_pad_per_row; + goto label__outer__continue; + } + } + v_p1_temp = ((uint32_t)(self->private_impl.f_width - self->private_impl.f_dst_x)); + v_p1 = wuffs_base__u32__min(v_p1_temp, 256); + v_p0 = 0; + while (v_p0 < v_p1) { + if (self->private_impl.f_bits_per_pixel == 16) { + if (((uint64_t)(io2_a_src - iop_a_src)) < 2) { + goto label__0__break; + } + v_c32 = ((uint32_t)(wuffs_base__peek_u16le__no_bounds_check(iop_a_src))); + iop_a_src += 2; + } else { + if (((uint64_t)(io2_a_src - iop_a_src)) < 4) { + goto label__0__break; + } + v_c32 = wuffs_base__peek_u32le__no_bounds_check(iop_a_src); + iop_a_src += 4; + } + v_channel = 0; + while (v_channel < 4) { + if (self->private_impl.f_channel_num_bits[v_channel] == 0) { + self->private_data.f_scratch[((8 * v_p0) + (2 * v_channel) + 0)] = 255; + self->private_data.f_scratch[((8 * v_p0) + (2 * v_channel) + 1)] = 255; + } else { + v_c = ((v_c32 & self->private_impl.f_channel_masks[v_channel]) >> self->private_impl.f_channel_shifts[v_channel]); + v_num_bits = ((uint32_t)(self->private_impl.f_channel_num_bits[v_channel])); + while (v_num_bits < 16) { + v_c |= ((uint32_t)(v_c << v_num_bits)); + v_num_bits *= 2; + } + v_c >>= (v_num_bits - 16); + self->private_data.f_scratch[((8 * v_p0) + (2 * v_channel) + 0)] = ((uint8_t)((255 & (v_c >> 0)))); + self->private_data.f_scratch[((8 * v_p0) + (2 * v_channel) + 1)] = ((uint8_t)((255 & (v_c >> 8)))); + } + v_channel += 1; + } + v_p0 += 1; + } + label__0__break:; + v_dst = wuffs_base__table_u8__row_u32(v_tab, self->private_impl.f_dst_y); + if (v_dst_bytes_per_row < ((uint64_t)(v_dst.len))) { + v_dst = wuffs_base__slice_u8__subslice_j(v_dst, v_dst_bytes_per_row); + } + v_i = (((uint64_t)(self->private_impl.f_dst_x)) * v_dst_bytes_per_pixel); + if (v_i >= ((uint64_t)(v_dst.len))) { + v_n = ((uint64_t)(v_p0)); + } else { + v_n = wuffs_base__pixel_swizzler__swizzle_interleaved_from_slice(&self->private_impl.f_swizzler, wuffs_base__slice_u8__subslice_i(v_dst, v_i), v_dst_palette, wuffs_base__make_slice_u8(self->private_data.f_scratch, (8 * v_p0))); + } + if (v_n == 0) { + status = wuffs_base__make_status(wuffs_bmp__note__internal_note_short_read); + goto ok; + } + wuffs_base__u32__sat_add_indirect(&self->private_impl.f_dst_x, ((uint32_t)((v_n & 4294967295)))); + } + } + label__outer__break:; + status = wuffs_base__make_status(NULL); + goto ok; + + ok: + goto exit; + exit: + if (a_src && a_src->data.ptr) { + a_src->meta.ri = ((size_t)(iop_a_src - a_src->data.ptr)); + } + + return status; +} + +// -------- func bmp.decoder.swizzle_low_bit_depth + +static wuffs_base__status +wuffs_bmp__decoder__swizzle_low_bit_depth( + wuffs_bmp__decoder* self, + wuffs_base__pixel_buffer* a_dst, + wuffs_base__io_buffer* a_src) { + wuffs_base__status status = wuffs_base__make_status(NULL); + + wuffs_base__pixel_format v_dst_pixfmt = {0}; + uint32_t v_dst_bits_per_pixel = 0; + uint64_t v_dst_bytes_per_pixel = 0; + uint64_t v_dst_bytes_per_row = 0; + wuffs_base__slice_u8 v_dst_palette = {0}; + wuffs_base__table_u8 v_tab = {0}; + wuffs_base__slice_u8 v_dst = {0}; + uint64_t v_i = 0; + uint64_t v_n = 0; + uint32_t v_p0 = 0; + uint32_t v_chunk_bits = 0; + uint32_t v_chunk_count = 0; + uint32_t v_pixels_per_chunk = 0; + + const uint8_t* iop_a_src = NULL; + const uint8_t* io0_a_src WUFFS_BASE__POTENTIALLY_UNUSED = NULL; + const uint8_t* io1_a_src WUFFS_BASE__POTENTIALLY_UNUSED = NULL; + const uint8_t* io2_a_src WUFFS_BASE__POTENTIALLY_UNUSED = NULL; + if (a_src && a_src->data.ptr) { + io0_a_src = a_src->data.ptr; + io1_a_src = io0_a_src + a_src->meta.ri; + iop_a_src = io1_a_src; + io2_a_src = io0_a_src + a_src->meta.wi; + } + + v_dst_pixfmt = wuffs_base__pixel_buffer__pixel_format(a_dst); + v_dst_bits_per_pixel = wuffs_base__pixel_format__bits_per_pixel(&v_dst_pixfmt); + if ((v_dst_bits_per_pixel & 7) != 0) { + status = wuffs_base__make_status(wuffs_base__error__unsupported_option); + goto exit; + } + v_dst_bytes_per_pixel = ((uint64_t)((v_dst_bits_per_pixel / 8))); + v_dst_bytes_per_row = (((uint64_t)(self->private_impl.f_width)) * v_dst_bytes_per_pixel); + v_dst_palette = wuffs_base__pixel_buffer__palette_or_else(a_dst, wuffs_base__make_slice_u8_ij(self->private_data.f_scratch, 1024, 2048)); + v_tab = wuffs_base__pixel_buffer__plane(a_dst, 0); + label__loop__continue:; + while (true) { + if (self->private_impl.f_dst_x == self->private_impl.f_width) { + self->private_impl.f_dst_x = 0; + self->private_impl.f_dst_y += self->private_impl.f_dst_y_inc; + if (self->private_impl.f_dst_y >= self->private_impl.f_height) { + goto label__loop__break; + } + } + v_dst = wuffs_base__table_u8__row_u32(v_tab, self->private_impl.f_dst_y); + if (v_dst_bytes_per_row < ((uint64_t)(v_dst.len))) { + v_dst = wuffs_base__slice_u8__subslice_j(v_dst, v_dst_bytes_per_row); + } + v_i = (((uint64_t)(self->private_impl.f_dst_x)) * v_dst_bytes_per_pixel); + if (v_i >= ((uint64_t)(v_dst.len))) { + if (self->private_impl.f_bits_per_pixel == 1) { + v_chunk_count = ((wuffs_base__u32__sat_sub(self->private_impl.f_width, self->private_impl.f_dst_x) + 31) / 32); + v_pixels_per_chunk = 32; + } else if (self->private_impl.f_bits_per_pixel == 2) { + v_chunk_count = ((wuffs_base__u32__sat_sub(self->private_impl.f_width, self->private_impl.f_dst_x) + 15) / 16); + v_pixels_per_chunk = 16; + } else { + v_chunk_count = ((wuffs_base__u32__sat_sub(self->private_impl.f_width, self->private_impl.f_dst_x) + 7) / 8); + v_pixels_per_chunk = 8; + } + while ((v_chunk_count >= 64) && (((uint64_t)(io2_a_src - iop_a_src)) >= 256)) { + iop_a_src += 256; + self->private_impl.f_dst_x = wuffs_base__u32__min(self->private_impl.f_width, ((uint32_t)(self->private_impl.f_dst_x + (v_pixels_per_chunk * 64)))); + v_chunk_count -= 64; + } + while ((v_chunk_count >= 8) && (((uint64_t)(io2_a_src - iop_a_src)) >= 32)) { + iop_a_src += 32; + self->private_impl.f_dst_x = wuffs_base__u32__min(self->private_impl.f_width, ((uint32_t)(self->private_impl.f_dst_x + (v_pixels_per_chunk * 8)))); + v_chunk_count -= 8; + } + while (v_chunk_count > 0) { + if (((uint64_t)(io2_a_src - iop_a_src)) < 4) { + status = wuffs_base__make_status(wuffs_bmp__note__internal_note_short_read); + goto ok; + } + iop_a_src += 4; + self->private_impl.f_dst_x = wuffs_base__u32__min(self->private_impl.f_width, ((uint32_t)(self->private_impl.f_dst_x + (v_pixels_per_chunk * 1)))); + v_chunk_count -= 1; + } + goto label__loop__continue; + } + v_dst = wuffs_base__slice_u8__subslice_i(v_dst, v_i); + v_p0 = 0; + if (self->private_impl.f_bits_per_pixel == 1) { + v_chunk_count = ((wuffs_base__u32__sat_sub(self->private_impl.f_width, self->private_impl.f_dst_x) + 31) / 32); + v_chunk_count = wuffs_base__u32__min(v_chunk_count, 16); + while ((v_chunk_count > 0) && (((uint64_t)(io2_a_src - iop_a_src)) >= 4)) { + v_chunk_bits = wuffs_base__peek_u32be__no_bounds_check(iop_a_src); + iop_a_src += 4; + self->private_data.f_scratch[(v_p0 + 0)] = ((uint8_t)((1 & (v_chunk_bits >> 31)))); + self->private_data.f_scratch[(v_p0 + 1)] = ((uint8_t)((1 & (v_chunk_bits >> 30)))); + self->private_data.f_scratch[(v_p0 + 2)] = ((uint8_t)((1 & (v_chunk_bits >> 29)))); + self->private_data.f_scratch[(v_p0 + 3)] = ((uint8_t)((1 & (v_chunk_bits >> 28)))); + self->private_data.f_scratch[(v_p0 + 4)] = ((uint8_t)((1 & (v_chunk_bits >> 27)))); + self->private_data.f_scratch[(v_p0 + 5)] = ((uint8_t)((1 & (v_chunk_bits >> 26)))); + self->private_data.f_scratch[(v_p0 + 6)] = ((uint8_t)((1 & (v_chunk_bits >> 25)))); + self->private_data.f_scratch[(v_p0 + 7)] = ((uint8_t)((1 & (v_chunk_bits >> 24)))); + self->private_data.f_scratch[(v_p0 + 8)] = ((uint8_t)((1 & (v_chunk_bits >> 23)))); + self->private_data.f_scratch[(v_p0 + 9)] = ((uint8_t)((1 & (v_chunk_bits >> 22)))); + self->private_data.f_scratch[(v_p0 + 10)] = ((uint8_t)((1 & (v_chunk_bits >> 21)))); + self->private_data.f_scratch[(v_p0 + 11)] = ((uint8_t)((1 & (v_chunk_bits >> 20)))); + self->private_data.f_scratch[(v_p0 + 12)] = ((uint8_t)((1 & (v_chunk_bits >> 19)))); + self->private_data.f_scratch[(v_p0 + 13)] = ((uint8_t)((1 & (v_chunk_bits >> 18)))); + self->private_data.f_scratch[(v_p0 + 14)] = ((uint8_t)((1 & (v_chunk_bits >> 17)))); + self->private_data.f_scratch[(v_p0 + 15)] = ((uint8_t)((1 & (v_chunk_bits >> 16)))); + self->private_data.f_scratch[(v_p0 + 16)] = ((uint8_t)((1 & (v_chunk_bits >> 15)))); + self->private_data.f_scratch[(v_p0 + 17)] = ((uint8_t)((1 & (v_chunk_bits >> 14)))); + self->private_data.f_scratch[(v_p0 + 18)] = ((uint8_t)((1 & (v_chunk_bits >> 13)))); + self->private_data.f_scratch[(v_p0 + 19)] = ((uint8_t)((1 & (v_chunk_bits >> 12)))); + self->private_data.f_scratch[(v_p0 + 20)] = ((uint8_t)((1 & (v_chunk_bits >> 11)))); + self->private_data.f_scratch[(v_p0 + 21)] = ((uint8_t)((1 & (v_chunk_bits >> 10)))); + self->private_data.f_scratch[(v_p0 + 22)] = ((uint8_t)((1 & (v_chunk_bits >> 9)))); + self->private_data.f_scratch[(v_p0 + 23)] = ((uint8_t)((1 & (v_chunk_bits >> 8)))); + self->private_data.f_scratch[(v_p0 + 24)] = ((uint8_t)((1 & (v_chunk_bits >> 7)))); + self->private_data.f_scratch[(v_p0 + 25)] = ((uint8_t)((1 & (v_chunk_bits >> 6)))); + self->private_data.f_scratch[(v_p0 + 26)] = ((uint8_t)((1 & (v_chunk_bits >> 5)))); + self->private_data.f_scratch[(v_p0 + 27)] = ((uint8_t)((1 & (v_chunk_bits >> 4)))); + self->private_data.f_scratch[(v_p0 + 28)] = ((uint8_t)((1 & (v_chunk_bits >> 3)))); + self->private_data.f_scratch[(v_p0 + 29)] = ((uint8_t)((1 & (v_chunk_bits >> 2)))); + self->private_data.f_scratch[(v_p0 + 30)] = ((uint8_t)((1 & (v_chunk_bits >> 1)))); + self->private_data.f_scratch[(v_p0 + 31)] = ((uint8_t)((1 & (v_chunk_bits >> 0)))); + v_p0 = ((v_p0 & 511) + 32); + v_chunk_count -= 1; + } + } else if (self->private_impl.f_bits_per_pixel == 2) { + v_chunk_count = ((wuffs_base__u32__sat_sub(self->private_impl.f_width, self->private_impl.f_dst_x) + 15) / 16); + v_chunk_count = wuffs_base__u32__min(v_chunk_count, 32); + while ((v_chunk_count > 0) && (((uint64_t)(io2_a_src - iop_a_src)) >= 4)) { + v_chunk_bits = wuffs_base__peek_u32be__no_bounds_check(iop_a_src); + iop_a_src += 4; + self->private_data.f_scratch[(v_p0 + 0)] = ((uint8_t)((3 & (v_chunk_bits >> 30)))); + self->private_data.f_scratch[(v_p0 + 1)] = ((uint8_t)((3 & (v_chunk_bits >> 28)))); + self->private_data.f_scratch[(v_p0 + 2)] = ((uint8_t)((3 & (v_chunk_bits >> 26)))); + self->private_data.f_scratch[(v_p0 + 3)] = ((uint8_t)((3 & (v_chunk_bits >> 24)))); + self->private_data.f_scratch[(v_p0 + 4)] = ((uint8_t)((3 & (v_chunk_bits >> 22)))); + self->private_data.f_scratch[(v_p0 + 5)] = ((uint8_t)((3 & (v_chunk_bits >> 20)))); + self->private_data.f_scratch[(v_p0 + 6)] = ((uint8_t)((3 & (v_chunk_bits >> 18)))); + self->private_data.f_scratch[(v_p0 + 7)] = ((uint8_t)((3 & (v_chunk_bits >> 16)))); + self->private_data.f_scratch[(v_p0 + 8)] = ((uint8_t)((3 & (v_chunk_bits >> 14)))); + self->private_data.f_scratch[(v_p0 + 9)] = ((uint8_t)((3 & (v_chunk_bits >> 12)))); + self->private_data.f_scratch[(v_p0 + 10)] = ((uint8_t)((3 & (v_chunk_bits >> 10)))); + self->private_data.f_scratch[(v_p0 + 11)] = ((uint8_t)((3 & (v_chunk_bits >> 8)))); + self->private_data.f_scratch[(v_p0 + 12)] = ((uint8_t)((3 & (v_chunk_bits >> 6)))); + self->private_data.f_scratch[(v_p0 + 13)] = ((uint8_t)((3 & (v_chunk_bits >> 4)))); + self->private_data.f_scratch[(v_p0 + 14)] = ((uint8_t)((3 & (v_chunk_bits >> 2)))); + self->private_data.f_scratch[(v_p0 + 15)] = ((uint8_t)((3 & (v_chunk_bits >> 0)))); + v_p0 = ((v_p0 & 511) + 16); + v_chunk_count -= 1; + } + } else { + v_chunk_count = ((wuffs_base__u32__sat_sub(self->private_impl.f_width, self->private_impl.f_dst_x) + 7) / 8); + v_chunk_count = wuffs_base__u32__min(v_chunk_count, 64); + while ((v_chunk_count > 0) && (((uint64_t)(io2_a_src - iop_a_src)) >= 4)) { + v_chunk_bits = wuffs_base__peek_u32be__no_bounds_check(iop_a_src); + iop_a_src += 4; + self->private_data.f_scratch[(v_p0 + 0)] = ((uint8_t)((15 & (v_chunk_bits >> 28)))); + self->private_data.f_scratch[(v_p0 + 1)] = ((uint8_t)((15 & (v_chunk_bits >> 24)))); + self->private_data.f_scratch[(v_p0 + 2)] = ((uint8_t)((15 & (v_chunk_bits >> 20)))); + self->private_data.f_scratch[(v_p0 + 3)] = ((uint8_t)((15 & (v_chunk_bits >> 16)))); + self->private_data.f_scratch[(v_p0 + 4)] = ((uint8_t)((15 & (v_chunk_bits >> 12)))); + self->private_data.f_scratch[(v_p0 + 5)] = ((uint8_t)((15 & (v_chunk_bits >> 8)))); + self->private_data.f_scratch[(v_p0 + 6)] = ((uint8_t)((15 & (v_chunk_bits >> 4)))); + self->private_data.f_scratch[(v_p0 + 7)] = ((uint8_t)((15 & (v_chunk_bits >> 0)))); + v_p0 = ((v_p0 & 511) + 8); + v_chunk_count -= 1; + } + } + v_p0 = wuffs_base__u32__min(v_p0, wuffs_base__u32__sat_sub(self->private_impl.f_width, self->private_impl.f_dst_x)); + v_n = wuffs_base__pixel_swizzler__swizzle_interleaved_from_slice(&self->private_impl.f_swizzler, v_dst, v_dst_palette, wuffs_base__make_slice_u8(self->private_data.f_scratch, v_p0)); + if (v_n == 0) { + status = wuffs_base__make_status(wuffs_bmp__note__internal_note_short_read); + goto ok; + } + wuffs_base__u32__sat_add_indirect(&self->private_impl.f_dst_x, ((uint32_t)((v_n & 4294967295)))); + } + label__loop__break:; + status = wuffs_base__make_status(NULL); + goto ok; + + ok: + goto exit; + exit: + if (a_src && a_src->data.ptr) { + a_src->meta.ri = ((size_t)(iop_a_src - a_src->data.ptr)); + } + + return status; +} + +// -------- func bmp.decoder.frame_dirty_rect + +WUFFS_BASE__MAYBE_STATIC wuffs_base__rect_ie_u32 +wuffs_bmp__decoder__frame_dirty_rect( + const wuffs_bmp__decoder* self) { + if (!self) { + return wuffs_base__utility__empty_rect_ie_u32(); + } + if ((self->private_impl.magic != WUFFS_BASE__MAGIC) && + (self->private_impl.magic != WUFFS_BASE__DISABLED)) { + return wuffs_base__utility__empty_rect_ie_u32(); + } + + return wuffs_base__utility__make_rect_ie_u32( + 0, + 0, + self->private_impl.f_width, + self->private_impl.f_height); +} + +// -------- func bmp.decoder.num_animation_loops + +WUFFS_BASE__MAYBE_STATIC uint32_t +wuffs_bmp__decoder__num_animation_loops( + const wuffs_bmp__decoder* self) { + if (!self) { + return 0; + } + if ((self->private_impl.magic != WUFFS_BASE__MAGIC) && + (self->private_impl.magic != WUFFS_BASE__DISABLED)) { + return 0; + } + + return 0; +} + +// -------- func bmp.decoder.num_decoded_frame_configs + +WUFFS_BASE__MAYBE_STATIC uint64_t +wuffs_bmp__decoder__num_decoded_frame_configs( + const wuffs_bmp__decoder* self) { + if (!self) { + return 0; + } + if ((self->private_impl.magic != WUFFS_BASE__MAGIC) && + (self->private_impl.magic != WUFFS_BASE__DISABLED)) { + return 0; + } + + if (self->private_impl.f_call_sequence > 32) { + return 1; + } + return 0; +} + +// -------- func bmp.decoder.num_decoded_frames + +WUFFS_BASE__MAYBE_STATIC uint64_t +wuffs_bmp__decoder__num_decoded_frames( + const wuffs_bmp__decoder* self) { + if (!self) { + return 0; + } + if ((self->private_impl.magic != WUFFS_BASE__MAGIC) && + (self->private_impl.magic != WUFFS_BASE__DISABLED)) { + return 0; + } + + if (self->private_impl.f_call_sequence > 64) { + return 1; + } + return 0; +} + +// -------- func bmp.decoder.restart_frame + +WUFFS_BASE__MAYBE_STATIC wuffs_base__status +wuffs_bmp__decoder__restart_frame( + wuffs_bmp__decoder* self, + uint64_t a_index, + uint64_t a_io_position) { + if (!self) { + return wuffs_base__make_status(wuffs_base__error__bad_receiver); + } + if (self->private_impl.magic != WUFFS_BASE__MAGIC) { + return wuffs_base__make_status( + (self->private_impl.magic == WUFFS_BASE__DISABLED) + ? wuffs_base__error__disabled_by_previous_error + : wuffs_base__error__initialize_not_called); + } + + if (self->private_impl.f_call_sequence < 32) { + return wuffs_base__make_status(wuffs_base__error__bad_call_sequence); + } + if (a_index != 0) { + return wuffs_base__make_status(wuffs_base__error__bad_argument); + } + self->private_impl.f_call_sequence = 40; + self->private_impl.f_frame_config_io_position = a_io_position; + return wuffs_base__make_status(NULL); +} + +// -------- func bmp.decoder.set_report_metadata + +WUFFS_BASE__MAYBE_STATIC wuffs_base__empty_struct +wuffs_bmp__decoder__set_report_metadata( + wuffs_bmp__decoder* self, + uint32_t a_fourcc, + bool a_report) { + return wuffs_base__make_empty_struct(); +} + +// -------- func bmp.decoder.tell_me_more + +WUFFS_BASE__MAYBE_STATIC wuffs_base__status +wuffs_bmp__decoder__tell_me_more( + wuffs_bmp__decoder* self, + wuffs_base__io_buffer* a_dst, + wuffs_base__more_information* a_minfo, + wuffs_base__io_buffer* a_src) { + if (!self) { + return wuffs_base__make_status(wuffs_base__error__bad_receiver); + } + if (self->private_impl.magic != WUFFS_BASE__MAGIC) { + return wuffs_base__make_status( + (self->private_impl.magic == WUFFS_BASE__DISABLED) + ? wuffs_base__error__disabled_by_previous_error + : wuffs_base__error__initialize_not_called); + } + if (!a_dst || !a_src) { + self->private_impl.magic = WUFFS_BASE__DISABLED; + return wuffs_base__make_status(wuffs_base__error__bad_argument); + } + if ((self->private_impl.active_coroutine != 0) && + (self->private_impl.active_coroutine != 4)) { + self->private_impl.magic = WUFFS_BASE__DISABLED; + return wuffs_base__make_status(wuffs_base__error__interleaved_coroutine_calls); + } + self->private_impl.active_coroutine = 0; + wuffs_base__status status = wuffs_base__make_status(NULL); + + wuffs_base__status v_status = wuffs_base__make_status(NULL); + + uint32_t coro_susp_point = self->private_impl.p_tell_me_more[0]; + switch (coro_susp_point) { + WUFFS_BASE__COROUTINE_SUSPENSION_POINT_0; + + while (true) { + { + wuffs_base__status t_0 = wuffs_bmp__decoder__do_tell_me_more(self, a_dst, a_minfo, a_src); + v_status = t_0; + } + if ((v_status.repr == wuffs_base__suspension__short_read) && (a_src && a_src->meta.closed)) { + status = wuffs_base__make_status(wuffs_bmp__error__truncated_input); + goto exit; + } + status = v_status; + WUFFS_BASE__COROUTINE_SUSPENSION_POINT_MAYBE_SUSPEND(1); + } + + ok: + self->private_impl.p_tell_me_more[0] = 0; + goto exit; + } + + goto suspend; + suspend: + self->private_impl.p_tell_me_more[0] = wuffs_base__status__is_suspension(&status) ? coro_susp_point : 0; + self->private_impl.active_coroutine = wuffs_base__status__is_suspension(&status) ? 4 : 0; + + goto exit; + exit: + if (wuffs_base__status__is_error(&status)) { + self->private_impl.magic = WUFFS_BASE__DISABLED; + } + return status; +} + +// -------- func bmp.decoder.do_tell_me_more + +static wuffs_base__status +wuffs_bmp__decoder__do_tell_me_more( + wuffs_bmp__decoder* self, + wuffs_base__io_buffer* a_dst, + wuffs_base__more_information* a_minfo, + wuffs_base__io_buffer* a_src) { + wuffs_base__status status = wuffs_base__make_status(NULL); + + if (self->private_impl.f_io_redirect_fourcc <= 1) { + status = wuffs_base__make_status(wuffs_base__error__no_more_information); + goto exit; + } + if (a_minfo != NULL) { + wuffs_base__more_information__set(a_minfo, + 1, + self->private_impl.f_io_redirect_fourcc, + 0, + self->private_impl.f_io_redirect_pos, + 18446744073709551615u); + } + self->private_impl.f_io_redirect_fourcc = 1; + + goto ok; + ok: + goto exit; + exit: + return status; +} + +// -------- func bmp.decoder.workbuf_len + +WUFFS_BASE__MAYBE_STATIC wuffs_base__range_ii_u64 +wuffs_bmp__decoder__workbuf_len( + const wuffs_bmp__decoder* self) { + if (!self) { + return wuffs_base__utility__empty_range_ii_u64(); + } + if ((self->private_impl.magic != WUFFS_BASE__MAGIC) && + (self->private_impl.magic != WUFFS_BASE__DISABLED)) { + return wuffs_base__utility__empty_range_ii_u64(); + } + + return wuffs_base__utility__make_range_ii_u64(0, 0); +} + +// -------- func bmp.decoder.read_palette + +static wuffs_base__status +wuffs_bmp__decoder__read_palette( + wuffs_bmp__decoder* self, + wuffs_base__io_buffer* a_src) { + wuffs_base__status status = wuffs_base__make_status(NULL); + + uint32_t v_i = 0; + uint32_t v_argb = 0; + + const uint8_t* iop_a_src = NULL; + const uint8_t* io0_a_src WUFFS_BASE__POTENTIALLY_UNUSED = NULL; + const uint8_t* io1_a_src WUFFS_BASE__POTENTIALLY_UNUSED = NULL; + const uint8_t* io2_a_src WUFFS_BASE__POTENTIALLY_UNUSED = NULL; + if (a_src && a_src->data.ptr) { + io0_a_src = a_src->data.ptr; + io1_a_src = io0_a_src + a_src->meta.ri; + iop_a_src = io1_a_src; + io2_a_src = io0_a_src + a_src->meta.wi; + } + + uint32_t coro_susp_point = self->private_impl.p_read_palette[0]; + if (coro_susp_point) { + v_i = self->private_data.s_read_palette[0].v_i; + } + switch (coro_susp_point) { + WUFFS_BASE__COROUTINE_SUSPENSION_POINT_0; + + if (self->private_impl.f_bitmap_info_len == 12) { + while ((v_i < 256) && (self->private_impl.f_padding >= 3)) { + self->private_impl.f_padding -= 3; + { + WUFFS_BASE__COROUTINE_SUSPENSION_POINT(1); + uint32_t t_0; + if (WUFFS_BASE__LIKELY(io2_a_src - iop_a_src >= 3)) { + t_0 = ((uint32_t)(wuffs_base__peek_u24le__no_bounds_check(iop_a_src))); + iop_a_src += 3; + } else { + self->private_data.s_read_palette[0].scratch = 0; + WUFFS_BASE__COROUTINE_SUSPENSION_POINT(2); + while (true) { + if (WUFFS_BASE__UNLIKELY(iop_a_src == io2_a_src)) { + status = wuffs_base__make_status(wuffs_base__suspension__short_read); + goto suspend; + } + uint64_t* scratch = &self->private_data.s_read_palette[0].scratch; + uint32_t num_bits_0 = ((uint32_t)(*scratch >> 56)); + *scratch <<= 8; + *scratch >>= 8; + *scratch |= ((uint64_t)(*iop_a_src++)) << num_bits_0; + if (num_bits_0 == 16) { + t_0 = ((uint32_t)(*scratch)); + break; + } + num_bits_0 += 8; + *scratch |= ((uint64_t)(num_bits_0)) << 56; + } + } + v_argb = t_0; + } + v_argb |= 4278190080; + self->private_data.f_src_palette[((4 * v_i) + 0)] = ((uint8_t)(((v_argb >> 0) & 255))); + self->private_data.f_src_palette[((4 * v_i) + 1)] = ((uint8_t)(((v_argb >> 8) & 255))); + self->private_data.f_src_palette[((4 * v_i) + 2)] = ((uint8_t)(((v_argb >> 16) & 255))); + self->private_data.f_src_palette[((4 * v_i) + 3)] = ((uint8_t)(((v_argb >> 24) & 255))); + v_i += 1; + } + } else { + while ((v_i < 256) && (self->private_impl.f_padding >= 4)) { + self->private_impl.f_padding -= 4; + { + WUFFS_BASE__COROUTINE_SUSPENSION_POINT(3); + uint32_t t_1; + if (WUFFS_BASE__LIKELY(io2_a_src - iop_a_src >= 4)) { + t_1 = wuffs_base__peek_u32le__no_bounds_check(iop_a_src); + iop_a_src += 4; + } else { + self->private_data.s_read_palette[0].scratch = 0; + WUFFS_BASE__COROUTINE_SUSPENSION_POINT(4); + while (true) { + if (WUFFS_BASE__UNLIKELY(iop_a_src == io2_a_src)) { + status = wuffs_base__make_status(wuffs_base__suspension__short_read); + goto suspend; + } + uint64_t* scratch = &self->private_data.s_read_palette[0].scratch; + uint32_t num_bits_1 = ((uint32_t)(*scratch >> 56)); + *scratch <<= 8; + *scratch >>= 8; + *scratch |= ((uint64_t)(*iop_a_src++)) << num_bits_1; + if (num_bits_1 == 24) { + t_1 = ((uint32_t)(*scratch)); + break; + } + num_bits_1 += 8; + *scratch |= ((uint64_t)(num_bits_1)) << 56; + } + } + v_argb = t_1; + } + v_argb |= 4278190080; + self->private_data.f_src_palette[((4 * v_i) + 0)] = ((uint8_t)(((v_argb >> 0) & 255))); + self->private_data.f_src_palette[((4 * v_i) + 1)] = ((uint8_t)(((v_argb >> 8) & 255))); + self->private_data.f_src_palette[((4 * v_i) + 2)] = ((uint8_t)(((v_argb >> 16) & 255))); + self->private_data.f_src_palette[((4 * v_i) + 3)] = ((uint8_t)(((v_argb >> 24) & 255))); + v_i += 1; + } + } + while (v_i < 256) { + self->private_data.f_src_palette[((4 * v_i) + 0)] = 0; + self->private_data.f_src_palette[((4 * v_i) + 1)] = 0; + self->private_data.f_src_palette[((4 * v_i) + 2)] = 0; + self->private_data.f_src_palette[((4 * v_i) + 3)] = 255; + v_i += 1; + } + + goto ok; + ok: + self->private_impl.p_read_palette[0] = 0; + goto exit; + } + + goto suspend; + suspend: + self->private_impl.p_read_palette[0] = wuffs_base__status__is_suspension(&status) ? coro_susp_point : 0; + self->private_data.s_read_palette[0].v_i = v_i; + + goto exit; + exit: + if (a_src && a_src->data.ptr) { + a_src->meta.ri = ((size_t)(iop_a_src - a_src->data.ptr)); + } + + return status; +} + +// -------- func bmp.decoder.process_masks + +static wuffs_base__status +wuffs_bmp__decoder__process_masks( + wuffs_bmp__decoder* self) { + wuffs_base__status status = wuffs_base__make_status(NULL); + + uint32_t v_i = 0; + uint32_t v_mask = 0; + uint32_t v_n = 0; + + while (v_i < 4) { + v_mask = self->private_impl.f_channel_masks[v_i]; + if (v_mask != 0) { + v_n = 0; + while ((v_mask & 1) == 0) { + v_n += 1; + v_mask >>= 1; + } + self->private_impl.f_channel_shifts[v_i] = ((uint8_t)((v_n & 31))); + v_n = 0; + while ((v_mask & 1) == 1) { + v_n += 1; + v_mask >>= 1; + } + if ((v_mask != 0) || (v_n > 32)) { + status = wuffs_base__make_status(wuffs_bmp__error__bad_header); + goto exit; + } + self->private_impl.f_channel_num_bits[v_i] = ((uint8_t)(v_n)); + } else if (v_i != 3) { + status = wuffs_base__make_status(wuffs_bmp__error__bad_header); + goto exit; + } + v_i += 1; + } + + goto ok; + ok: + goto exit; + exit: + return status; +} + +#endif // !defined(WUFFS_CONFIG__MODULES) || defined(WUFFS_CONFIG__MODULE__BMP) + +#if !defined(WUFFS_CONFIG__MODULES) || defined(WUFFS_CONFIG__MODULE__BZIP2) + +// ---------------- Status Codes Implementations + +const char wuffs_bzip2__error__bad_huffman_code_over_subscribed[] = "#bzip2: bad Huffman code (over-subscribed)"; +const char wuffs_bzip2__error__bad_huffman_code_under_subscribed[] = "#bzip2: bad Huffman code (under-subscribed)"; +const char wuffs_bzip2__error__bad_block_header[] = "#bzip2: bad block header"; +const char wuffs_bzip2__error__bad_block_length[] = "#bzip2: bad block length"; +const char wuffs_bzip2__error__bad_checksum[] = "#bzip2: bad checksum"; +const char wuffs_bzip2__error__bad_header[] = "#bzip2: bad header"; +const char wuffs_bzip2__error__bad_number_of_sections[] = "#bzip2: bad number of sections"; +const char wuffs_bzip2__error__truncated_input[] = "#bzip2: truncated input"; +const char wuffs_bzip2__error__unsupported_block_randomization[] = "#bzip2: unsupported block randomization"; +const char wuffs_bzip2__error__internal_error_inconsistent_huffman_decoder_state[] = "#bzip2: internal error: inconsistent Huffman decoder state"; + +// ---------------- Private Consts + +static const uint8_t +WUFFS_BZIP2__CLAMP_TO_5[8] WUFFS_BASE__POTENTIALLY_UNUSED = { + 0, 1, 2, 3, 4, 5, 5, 5, +}; + +static const uint32_t +WUFFS_BZIP2__REV_CRC32_TABLE[256] WUFFS_BASE__POTENTIALLY_UNUSED = { + 0, 79764919, 159529838, 222504665, 319059676, 398814059, 445009330, 507990021, + 638119352, 583659535, 797628118, 726387553, 890018660, 835552979, 1015980042, 944750013, + 1276238704, 1221641927, 1167319070, 1095957929, 1595256236, 1540665371, 1452775106, 1381403509, + 1780037320, 1859660671, 1671105958, 1733955601, 2031960084, 2111593891, 1889500026, 1952343757, + 2552477408, 2632100695, 2443283854, 2506133561, 2334638140, 2414271883, 2191915858, 2254759653, + 3190512472, 3135915759, 3081330742, 3009969537, 2905550212, 2850959411, 2762807018, 2691435357, + 3560074640, 3505614887, 3719321342, 3648080713, 3342211916, 3287746299, 3467911202, 3396681109, + 4063920168, 4143685023, 4223187782, 4286162673, 3779000052, 3858754371, 3904687514, 3967668269, + 881225847, 809987520, 1023691545, 969234094, 662832811, 591600412, 771767749, 717299826, + 311336399, 374308984, 453813921, 533576470, 25881363, 88864420, 134795389, 214552010, + 2023205639, 2086057648, 1897238633, 1976864222, 1804852699, 1867694188, 1645340341, 1724971778, + 1587496639, 1516133128, 1461550545, 1406951526, 1302016099, 1230646740, 1142491917, 1087903418, + 2896545431, 2825181984, 2770861561, 2716262478, 3215044683, 3143675388, 3055782693, 3001194130, + 2326604591, 2389456536, 2200899649, 2280525302, 2578013683, 2640855108, 2418763421, 2498394922, + 3769900519, 3832873040, 3912640137, 3992402750, 4088425275, 4151408268, 4197601365, 4277358050, + 3334271071, 3263032808, 3476998961, 3422541446, 3585640067, 3514407732, 3694837229, 3640369242, + 1762451694, 1842216281, 1619975040, 1682949687, 2047383090, 2127137669, 1938468188, 2001449195, + 1325665622, 1271206113, 1183200824, 1111960463, 1543535498, 1489069629, 1434599652, 1363369299, + 622672798, 568075817, 748617968, 677256519, 907627842, 853037301, 1067152940, 995781531, + 51762726, 131386257, 177728840, 240578815, 269590778, 349224269, 429104020, 491947555, + 4046411278, 4126034873, 4172115296, 4234965207, 3794477266, 3874110821, 3953728444, 4016571915, + 3609705398, 3555108353, 3735388376, 3664026991, 3290680682, 3236090077, 3449943556, 3378572211, + 3174993278, 3120533705, 3032266256, 2961025959, 2923101090, 2868635157, 2813903052, 2742672763, + 2604032198, 2683796849, 2461293480, 2524268063, 2284983834, 2364738477, 2175806836, 2238787779, + 1569362073, 1498123566, 1409854455, 1355396672, 1317987909, 1246755826, 1192025387, 1137557660, + 2072149281, 2135122070, 1912620623, 1992383480, 1753615357, 1816598090, 1627664531, 1707420964, + 295390185, 358241886, 404320391, 483945776, 43990325, 106832002, 186451547, 266083308, + 932423249, 861060070, 1041341759, 986742920, 613929101, 542559546, 756411363, 701822548, + 3316196985, 3244833742, 3425377559, 3370778784, 3601682597, 3530312978, 3744426955, 3689838204, + 3819031489, 3881883254, 3928223919, 4007849240, 4037393693, 4100235434, 4180117107, 4259748804, + 2310601993, 2373574846, 2151335527, 2231098320, 2596047829, 2659030626, 2470359227, 2550115596, + 2947551409, 2876312838, 2788305887, 2733848168, 3165939309, 3094707162, 3040238851, 2985771188, +}; + +// ---------------- Private Initializer Prototypes + +// ---------------- Private Function Prototypes + +static wuffs_base__status +wuffs_bzip2__decoder__do_transform_io( + wuffs_bzip2__decoder* self, + wuffs_base__io_buffer* a_dst, + wuffs_base__io_buffer* a_src, + wuffs_base__slice_u8 a_workbuf); + +static wuffs_base__status +wuffs_bzip2__decoder__prepare_block( + wuffs_bzip2__decoder* self, + wuffs_base__io_buffer* a_src); + +static wuffs_base__status +wuffs_bzip2__decoder__read_code_lengths( + wuffs_bzip2__decoder* self, + wuffs_base__io_buffer* a_src); + +static wuffs_base__status +wuffs_bzip2__decoder__build_huffman_tree( + wuffs_bzip2__decoder* self, + uint32_t a_which); + +static wuffs_base__empty_struct +wuffs_bzip2__decoder__build_huffman_table( + wuffs_bzip2__decoder* self, + uint32_t a_which); + +static wuffs_base__empty_struct +wuffs_bzip2__decoder__invert_bwt( + wuffs_bzip2__decoder* self); + +static wuffs_base__empty_struct +wuffs_bzip2__decoder__flush_fast( + wuffs_bzip2__decoder* self, + wuffs_base__io_buffer* a_dst); + +static wuffs_base__status +wuffs_bzip2__decoder__flush_slow( + wuffs_bzip2__decoder* self, + wuffs_base__io_buffer* a_dst); + +static wuffs_base__status +wuffs_bzip2__decoder__decode_huffman_fast( + wuffs_bzip2__decoder* self, + wuffs_base__io_buffer* a_src); + +static wuffs_base__status +wuffs_bzip2__decoder__decode_huffman_slow( + wuffs_bzip2__decoder* self, + wuffs_base__io_buffer* a_src); + +// ---------------- VTables + +const wuffs_base__io_transformer__func_ptrs +wuffs_bzip2__decoder__func_ptrs_for__wuffs_base__io_transformer = { + (wuffs_base__empty_struct(*)(void*, + uint32_t, + bool))(&wuffs_bzip2__decoder__set_quirk_enabled), + (wuffs_base__status(*)(void*, + wuffs_base__io_buffer*, + wuffs_base__io_buffer*, + wuffs_base__slice_u8))(&wuffs_bzip2__decoder__transform_io), + (wuffs_base__range_ii_u64(*)(const void*))(&wuffs_bzip2__decoder__workbuf_len), +}; + +// ---------------- Initializer Implementations + +wuffs_base__status WUFFS_BASE__WARN_UNUSED_RESULT +wuffs_bzip2__decoder__initialize( + wuffs_bzip2__decoder* self, + size_t sizeof_star_self, + uint64_t wuffs_version, + uint32_t options){ + if (!self) { + return wuffs_base__make_status(wuffs_base__error__bad_receiver); + } + if (sizeof(*self) != sizeof_star_self) { + return wuffs_base__make_status(wuffs_base__error__bad_sizeof_receiver); + } + if (((wuffs_version >> 32) != WUFFS_VERSION_MAJOR) || + (((wuffs_version >> 16) & 0xFFFF) > WUFFS_VERSION_MINOR)) { + return wuffs_base__make_status(wuffs_base__error__bad_wuffs_version); + } + + if ((options & WUFFS_INITIALIZE__ALREADY_ZEROED) != 0) { + // The whole point of this if-check is to detect an uninitialized *self. + // We disable the warning on GCC. Clang-5.0 does not have this warning. +#if !defined(__clang__) && defined(__GNUC__) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wmaybe-uninitialized" +#endif + if (self->private_impl.magic != 0) { + return wuffs_base__make_status(wuffs_base__error__initialize_falsely_claimed_already_zeroed); + } +#if !defined(__clang__) && defined(__GNUC__) +#pragma GCC diagnostic pop +#endif + } else { + if ((options & WUFFS_INITIALIZE__LEAVE_INTERNAL_BUFFERS_UNINITIALIZED) == 0) { + memset(self, 0, sizeof(*self)); + options |= WUFFS_INITIALIZE__ALREADY_ZEROED; + } else { + memset(&(self->private_impl), 0, sizeof(self->private_impl)); + } + } + + self->private_impl.magic = WUFFS_BASE__MAGIC; + self->private_impl.vtable_for__wuffs_base__io_transformer.vtable_name = + wuffs_base__io_transformer__vtable_name; + self->private_impl.vtable_for__wuffs_base__io_transformer.function_pointers = + (const void*)(&wuffs_bzip2__decoder__func_ptrs_for__wuffs_base__io_transformer); + return wuffs_base__make_status(NULL); +} + +wuffs_bzip2__decoder* +wuffs_bzip2__decoder__alloc() { + wuffs_bzip2__decoder* x = + (wuffs_bzip2__decoder*)(calloc(sizeof(wuffs_bzip2__decoder), 1)); + if (!x) { + return NULL; + } + if (wuffs_bzip2__decoder__initialize( + x, sizeof(wuffs_bzip2__decoder), WUFFS_VERSION, WUFFS_INITIALIZE__ALREADY_ZEROED).repr) { + free(x); + return NULL; + } + return x; +} + +size_t +sizeof__wuffs_bzip2__decoder() { + return sizeof(wuffs_bzip2__decoder); +} + +// ---------------- Function Implementations + +// -------- func bzip2.decoder.set_quirk_enabled + +WUFFS_BASE__MAYBE_STATIC wuffs_base__empty_struct +wuffs_bzip2__decoder__set_quirk_enabled( + wuffs_bzip2__decoder* self, + uint32_t a_quirk, + bool a_enabled) { + if (!self) { + return wuffs_base__make_empty_struct(); + } + if (self->private_impl.magic != WUFFS_BASE__MAGIC) { + return wuffs_base__make_empty_struct(); + } + + if (a_quirk == 1) { + self->private_impl.f_ignore_checksum = a_enabled; + } + return wuffs_base__make_empty_struct(); +} + +// -------- func bzip2.decoder.workbuf_len + +WUFFS_BASE__MAYBE_STATIC wuffs_base__range_ii_u64 +wuffs_bzip2__decoder__workbuf_len( + const wuffs_bzip2__decoder* self) { + if (!self) { + return wuffs_base__utility__empty_range_ii_u64(); + } + if ((self->private_impl.magic != WUFFS_BASE__MAGIC) && + (self->private_impl.magic != WUFFS_BASE__DISABLED)) { + return wuffs_base__utility__empty_range_ii_u64(); + } + + return wuffs_base__utility__make_range_ii_u64(0, 0); +} + +// -------- func bzip2.decoder.transform_io + +WUFFS_BASE__MAYBE_STATIC wuffs_base__status +wuffs_bzip2__decoder__transform_io( + wuffs_bzip2__decoder* self, + wuffs_base__io_buffer* a_dst, + wuffs_base__io_buffer* a_src, + wuffs_base__slice_u8 a_workbuf) { + if (!self) { + return wuffs_base__make_status(wuffs_base__error__bad_receiver); + } + if (self->private_impl.magic != WUFFS_BASE__MAGIC) { + return wuffs_base__make_status( + (self->private_impl.magic == WUFFS_BASE__DISABLED) + ? wuffs_base__error__disabled_by_previous_error + : wuffs_base__error__initialize_not_called); + } + if (!a_dst || !a_src) { + self->private_impl.magic = WUFFS_BASE__DISABLED; + return wuffs_base__make_status(wuffs_base__error__bad_argument); + } + if ((self->private_impl.active_coroutine != 0) && + (self->private_impl.active_coroutine != 1)) { + self->private_impl.magic = WUFFS_BASE__DISABLED; + return wuffs_base__make_status(wuffs_base__error__interleaved_coroutine_calls); + } + self->private_impl.active_coroutine = 0; + wuffs_base__status status = wuffs_base__make_status(NULL); + + wuffs_base__status v_status = wuffs_base__make_status(NULL); + + uint32_t coro_susp_point = self->private_impl.p_transform_io[0]; + switch (coro_susp_point) { + WUFFS_BASE__COROUTINE_SUSPENSION_POINT_0; + + while (true) { + { + wuffs_base__status t_0 = wuffs_bzip2__decoder__do_transform_io(self, a_dst, a_src, a_workbuf); + v_status = t_0; + } + if ((v_status.repr == wuffs_base__suspension__short_read) && (a_src && a_src->meta.closed)) { + status = wuffs_base__make_status(wuffs_bzip2__error__truncated_input); + goto exit; + } + status = v_status; + WUFFS_BASE__COROUTINE_SUSPENSION_POINT_MAYBE_SUSPEND(1); + } + + ok: + self->private_impl.p_transform_io[0] = 0; + goto exit; + } + + goto suspend; + suspend: + self->private_impl.p_transform_io[0] = wuffs_base__status__is_suspension(&status) ? coro_susp_point : 0; + self->private_impl.active_coroutine = wuffs_base__status__is_suspension(&status) ? 1 : 0; + + goto exit; + exit: + if (wuffs_base__status__is_error(&status)) { + self->private_impl.magic = WUFFS_BASE__DISABLED; + } + return status; +} + +// -------- func bzip2.decoder.do_transform_io + +static wuffs_base__status +wuffs_bzip2__decoder__do_transform_io( + wuffs_bzip2__decoder* self, + wuffs_base__io_buffer* a_dst, + wuffs_base__io_buffer* a_src, + wuffs_base__slice_u8 a_workbuf) { + wuffs_base__status status = wuffs_base__make_status(NULL); + + uint8_t v_c = 0; + uint32_t v_i = 0; + uint64_t v_tag = 0; + wuffs_base__status v_status = wuffs_base__make_status(NULL); + uint32_t v_final_checksum_want = 0; + + const uint8_t* iop_a_src = NULL; + const uint8_t* io0_a_src WUFFS_BASE__POTENTIALLY_UNUSED = NULL; + const uint8_t* io1_a_src WUFFS_BASE__POTENTIALLY_UNUSED = NULL; + const uint8_t* io2_a_src WUFFS_BASE__POTENTIALLY_UNUSED = NULL; + if (a_src && a_src->data.ptr) { + io0_a_src = a_src->data.ptr; + io1_a_src = io0_a_src + a_src->meta.ri; + iop_a_src = io1_a_src; + io2_a_src = io0_a_src + a_src->meta.wi; + } + + uint32_t coro_susp_point = self->private_impl.p_do_transform_io[0]; + if (coro_susp_point) { + v_i = self->private_data.s_do_transform_io[0].v_i; + v_tag = self->private_data.s_do_transform_io[0].v_tag; + v_final_checksum_want = self->private_data.s_do_transform_io[0].v_final_checksum_want; + } + switch (coro_susp_point) { + WUFFS_BASE__COROUTINE_SUSPENSION_POINT_0; + + { + WUFFS_BASE__COROUTINE_SUSPENSION_POINT(1); + if (WUFFS_BASE__UNLIKELY(iop_a_src == io2_a_src)) { + status = wuffs_base__make_status(wuffs_base__suspension__short_read); + goto suspend; + } + uint8_t t_0 = *iop_a_src++; + v_c = t_0; + } + if (v_c != 66) { + status = wuffs_base__make_status(wuffs_bzip2__error__bad_header); + goto exit; + } + { + WUFFS_BASE__COROUTINE_SUSPENSION_POINT(2); + if (WUFFS_BASE__UNLIKELY(iop_a_src == io2_a_src)) { + status = wuffs_base__make_status(wuffs_base__suspension__short_read); + goto suspend; + } + uint8_t t_1 = *iop_a_src++; + v_c = t_1; + } + if (v_c != 90) { + status = wuffs_base__make_status(wuffs_bzip2__error__bad_header); + goto exit; + } + { + WUFFS_BASE__COROUTINE_SUSPENSION_POINT(3); + if (WUFFS_BASE__UNLIKELY(iop_a_src == io2_a_src)) { + status = wuffs_base__make_status(wuffs_base__suspension__short_read); + goto suspend; + } + uint8_t t_2 = *iop_a_src++; + v_c = t_2; + } + if (v_c != 104) { + status = wuffs_base__make_status(wuffs_bzip2__error__bad_header); + goto exit; + } + { + WUFFS_BASE__COROUTINE_SUSPENSION_POINT(4); + if (WUFFS_BASE__UNLIKELY(iop_a_src == io2_a_src)) { + status = wuffs_base__make_status(wuffs_base__suspension__short_read); + goto suspend; + } + uint8_t t_3 = *iop_a_src++; + v_c = t_3; + } + if ((v_c < 49) || (57 < v_c)) { + status = wuffs_base__make_status(wuffs_bzip2__error__bad_header); + goto exit; + } + self->private_impl.f_max_incl_block_size = (((uint32_t)((v_c - 48))) * 100000); + while (true) { + v_tag = 0; + v_i = 0; + while (v_i < 48) { + if (self->private_impl.f_n_bits <= 0) { + { + WUFFS_BASE__COROUTINE_SUSPENSION_POINT(5); + if (WUFFS_BASE__UNLIKELY(iop_a_src == io2_a_src)) { + status = wuffs_base__make_status(wuffs_base__suspension__short_read); + goto suspend; + } + uint8_t t_4 = *iop_a_src++; + v_c = t_4; + } + self->private_impl.f_bits = (((uint32_t)(v_c)) << 24); + self->private_impl.f_n_bits = 8; + } + v_tag <<= 1; + v_tag |= ((uint64_t)((self->private_impl.f_bits >> 31))); + self->private_impl.f_bits <<= 1; + self->private_impl.f_n_bits -= 1; + v_i += 1; + } + if (v_tag == 25779555029136) { + goto label__0__break; + } else if (v_tag != 54156738319193) { + status = wuffs_base__make_status(wuffs_bzip2__error__bad_block_header); + goto exit; + } + if (a_src) { + a_src->meta.ri = ((size_t)(iop_a_src - a_src->data.ptr)); + } + WUFFS_BASE__COROUTINE_SUSPENSION_POINT(6); + status = wuffs_bzip2__decoder__prepare_block(self, a_src); + if (a_src) { + iop_a_src = a_src->data.ptr + a_src->meta.ri; + } + if (status.repr) { + goto suspend; + } + self->private_impl.f_block_size = 0; + self->private_impl.f_decode_huffman_finished = false; + self->private_impl.f_decode_huffman_which = WUFFS_BZIP2__CLAMP_TO_5[(self->private_data.f_huffman_selectors[0] & 7)]; + self->private_impl.f_decode_huffman_ticks = 50; + self->private_impl.f_decode_huffman_section = 0; + self->private_impl.f_decode_huffman_run_shift = 0; + while ( ! self->private_impl.f_decode_huffman_finished) { + if (a_src) { + a_src->meta.ri = ((size_t)(iop_a_src - a_src->data.ptr)); + } + v_status = wuffs_bzip2__decoder__decode_huffman_fast(self, a_src); + if (a_src) { + iop_a_src = a_src->data.ptr + a_src->meta.ri; + } + if (wuffs_base__status__is_error(&v_status)) { + status = v_status; + goto exit; + } else if (self->private_impl.f_decode_huffman_finished) { + goto label__1__break; + } + if (a_src) { + a_src->meta.ri = ((size_t)(iop_a_src - a_src->data.ptr)); + } + WUFFS_BASE__COROUTINE_SUSPENSION_POINT(7); + status = wuffs_bzip2__decoder__decode_huffman_slow(self, a_src); + if (a_src) { + iop_a_src = a_src->data.ptr + a_src->meta.ri; + } + if (status.repr) { + goto suspend; + } + } + label__1__break:; + wuffs_bzip2__decoder__invert_bwt(self); + self->private_impl.f_block_checksum_have = 4294967295; + if (self->private_impl.f_original_pointer >= self->private_impl.f_block_size) { + status = wuffs_base__make_status(wuffs_bzip2__error__bad_block_length); + goto exit; + } + self->private_impl.f_flush_pointer = (self->private_data.f_bwt[self->private_impl.f_original_pointer] >> 12); + self->private_impl.f_flush_repeat_count = 0; + self->private_impl.f_flush_prev = 0; + while (self->private_impl.f_block_size > 0) { + wuffs_bzip2__decoder__flush_fast(self, a_dst); + if (self->private_impl.f_block_size <= 0) { + goto label__2__break; + } + WUFFS_BASE__COROUTINE_SUSPENSION_POINT(8); + status = wuffs_bzip2__decoder__flush_slow(self, a_dst); + if (status.repr) { + goto suspend; + } + } + label__2__break:; + self->private_impl.f_block_checksum_have ^= 4294967295; + if ( ! self->private_impl.f_ignore_checksum && (self->private_impl.f_block_checksum_have != self->private_impl.f_block_checksum_want)) { + status = wuffs_base__make_status(wuffs_bzip2__error__bad_checksum); + goto exit; + } + self->private_impl.f_final_checksum_have = (self->private_impl.f_block_checksum_have ^ ((self->private_impl.f_final_checksum_have >> 31) | ((uint32_t)(self->private_impl.f_final_checksum_have << 1)))); + } + label__0__break:; + v_final_checksum_want = 0; + v_i = 0; + while (v_i < 32) { + if (self->private_impl.f_n_bits <= 0) { + { + WUFFS_BASE__COROUTINE_SUSPENSION_POINT(9); + if (WUFFS_BASE__UNLIKELY(iop_a_src == io2_a_src)) { + status = wuffs_base__make_status(wuffs_base__suspension__short_read); + goto suspend; + } + uint8_t t_5 = *iop_a_src++; + v_c = t_5; + } + self->private_impl.f_bits = (((uint32_t)(v_c)) << 24); + self->private_impl.f_n_bits = 8; + } + v_final_checksum_want <<= 1; + v_final_checksum_want |= (self->private_impl.f_bits >> 31); + self->private_impl.f_bits <<= 1; + self->private_impl.f_n_bits -= 1; + v_i += 1; + } + if ( ! self->private_impl.f_ignore_checksum && (self->private_impl.f_final_checksum_have != v_final_checksum_want)) { + status = wuffs_base__make_status(wuffs_bzip2__error__bad_checksum); + goto exit; + } + + goto ok; + ok: + self->private_impl.p_do_transform_io[0] = 0; + goto exit; + } + + goto suspend; + suspend: + self->private_impl.p_do_transform_io[0] = wuffs_base__status__is_suspension(&status) ? coro_susp_point : 0; + self->private_data.s_do_transform_io[0].v_i = v_i; + self->private_data.s_do_transform_io[0].v_tag = v_tag; + self->private_data.s_do_transform_io[0].v_final_checksum_want = v_final_checksum_want; + + goto exit; + exit: + if (a_src && a_src->data.ptr) { + a_src->meta.ri = ((size_t)(iop_a_src - a_src->data.ptr)); + } + + return status; +} + +// -------- func bzip2.decoder.prepare_block + +static wuffs_base__status +wuffs_bzip2__decoder__prepare_block( + wuffs_bzip2__decoder* self, + wuffs_base__io_buffer* a_src) { + wuffs_base__status status = wuffs_base__make_status(NULL); + + uint8_t v_c = 0; + uint32_t v_i = 0; + uint32_t v_j = 0; + uint32_t v_selector = 0; + uint32_t v_sel_ff = 0; + uint8_t v_movee = 0; + wuffs_base__status v_status = wuffs_base__make_status(NULL); + + const uint8_t* iop_a_src = NULL; + const uint8_t* io0_a_src WUFFS_BASE__POTENTIALLY_UNUSED = NULL; + const uint8_t* io1_a_src WUFFS_BASE__POTENTIALLY_UNUSED = NULL; + const uint8_t* io2_a_src WUFFS_BASE__POTENTIALLY_UNUSED = NULL; + if (a_src && a_src->data.ptr) { + io0_a_src = a_src->data.ptr; + io1_a_src = io0_a_src + a_src->meta.ri; + iop_a_src = io1_a_src; + io2_a_src = io0_a_src + a_src->meta.wi; + } + + uint32_t coro_susp_point = self->private_impl.p_prepare_block[0]; + if (coro_susp_point) { + v_i = self->private_data.s_prepare_block[0].v_i; + v_selector = self->private_data.s_prepare_block[0].v_selector; + } + switch (coro_susp_point) { + WUFFS_BASE__COROUTINE_SUSPENSION_POINT_0; + + self->private_impl.f_block_checksum_want = 0; + v_i = 0; + while (v_i < 32) { + if (self->private_impl.f_n_bits <= 0) { + { + WUFFS_BASE__COROUTINE_SUSPENSION_POINT(1); + if (WUFFS_BASE__UNLIKELY(iop_a_src == io2_a_src)) { + status = wuffs_base__make_status(wuffs_base__suspension__short_read); + goto suspend; + } + uint8_t t_0 = *iop_a_src++; + v_c = t_0; + } + self->private_impl.f_bits = (((uint32_t)(v_c)) << 24); + self->private_impl.f_n_bits = 8; + } + self->private_impl.f_block_checksum_want <<= 1; + self->private_impl.f_block_checksum_want |= (self->private_impl.f_bits >> 31); + self->private_impl.f_bits <<= 1; + self->private_impl.f_n_bits -= 1; + v_i += 1; + } + if (self->private_impl.f_n_bits <= 0) { + { + WUFFS_BASE__COROUTINE_SUSPENSION_POINT(2); + if (WUFFS_BASE__UNLIKELY(iop_a_src == io2_a_src)) { + status = wuffs_base__make_status(wuffs_base__suspension__short_read); + goto suspend; + } + uint8_t t_1 = *iop_a_src++; + v_c = t_1; + } + self->private_impl.f_bits = (((uint32_t)(v_c)) << 24); + self->private_impl.f_n_bits = 8; + } + if ((self->private_impl.f_bits >> 31) != 0) { + status = wuffs_base__make_status(wuffs_bzip2__error__unsupported_block_randomization); + goto exit; + } + self->private_impl.f_bits <<= 1; + self->private_impl.f_n_bits -= 1; + self->private_impl.f_original_pointer = 0; + v_i = 0; + while (v_i < 24) { + if (self->private_impl.f_n_bits <= 0) { + { + WUFFS_BASE__COROUTINE_SUSPENSION_POINT(3); + if (WUFFS_BASE__UNLIKELY(iop_a_src == io2_a_src)) { + status = wuffs_base__make_status(wuffs_base__suspension__short_read); + goto suspend; + } + uint8_t t_2 = *iop_a_src++; + v_c = t_2; + } + self->private_impl.f_bits = (((uint32_t)(v_c)) << 24); + self->private_impl.f_n_bits = 8; + } + self->private_impl.f_original_pointer <<= 1; + self->private_impl.f_original_pointer |= (self->private_impl.f_bits >> 31); + self->private_impl.f_bits <<= 1; + self->private_impl.f_n_bits -= 1; + v_i += 1; + } + v_i = 0; + while (v_i < 256) { + self->private_data.f_presence[v_i] = 0; + v_i += 1; + } + v_i = 0; + while (v_i < 256) { + if (self->private_impl.f_n_bits <= 0) { + { + WUFFS_BASE__COROUTINE_SUSPENSION_POINT(4); + if (WUFFS_BASE__UNLIKELY(iop_a_src == io2_a_src)) { + status = wuffs_base__make_status(wuffs_base__suspension__short_read); + goto suspend; + } + uint8_t t_3 = *iop_a_src++; + v_c = t_3; + } + self->private_impl.f_bits = (((uint32_t)(v_c)) << 24); + self->private_impl.f_n_bits = 8; + } + if ((self->private_impl.f_bits >> 31) != 0) { + self->private_data.f_presence[v_i] = 1; + } + self->private_impl.f_bits <<= 1; + self->private_impl.f_n_bits -= 1; + v_i += 16; + } + self->private_data.f_scratch = 0; + v_i = 0; + label__0__continue:; + while (v_i < 256) { + if (self->private_data.f_presence[v_i] == 0) { + v_i += 16; + goto label__0__continue; + } + while (true) { + if (self->private_impl.f_n_bits <= 0) { + { + WUFFS_BASE__COROUTINE_SUSPENSION_POINT(5); + if (WUFFS_BASE__UNLIKELY(iop_a_src == io2_a_src)) { + status = wuffs_base__make_status(wuffs_base__suspension__short_read); + goto suspend; + } + uint8_t t_4 = *iop_a_src++; + v_c = t_4; + } + self->private_impl.f_bits = (((uint32_t)(v_c)) << 24); + self->private_impl.f_n_bits = 8; + } + self->private_data.f_scratch += (self->private_impl.f_bits >> 31); + self->private_data.f_presence[(v_i & 255)] = ((uint8_t)((self->private_impl.f_bits >> 31))); + self->private_impl.f_bits <<= 1; + self->private_impl.f_n_bits -= 1; + v_i += 1; + if ((v_i & 15) == 0) { + goto label__1__break; + } + } + label__1__break:; + } + if ((self->private_data.f_scratch < 1) || (256 < self->private_data.f_scratch)) { + status = wuffs_base__make_status(wuffs_bzip2__error__bad_block_header); + goto exit; + } + self->private_impl.f_num_symbols = (self->private_data.f_scratch + 2); + self->private_data.f_scratch = 0; + v_i = 0; + while (v_i < 3) { + if (self->private_impl.f_n_bits <= 0) { + { + WUFFS_BASE__COROUTINE_SUSPENSION_POINT(6); + if (WUFFS_BASE__UNLIKELY(iop_a_src == io2_a_src)) { + status = wuffs_base__make_status(wuffs_base__suspension__short_read); + goto suspend; + } + uint8_t t_5 = *iop_a_src++; + v_c = t_5; + } + self->private_impl.f_bits = (((uint32_t)(v_c)) << 24); + self->private_impl.f_n_bits = 8; + } + self->private_data.f_scratch <<= 1; + self->private_data.f_scratch |= (self->private_impl.f_bits >> 31); + self->private_impl.f_bits <<= 1; + self->private_impl.f_n_bits -= 1; + v_i += 1; + } + if ((self->private_data.f_scratch < 2) || (6 < self->private_data.f_scratch)) { + status = wuffs_base__make_status(wuffs_bzip2__error__bad_block_header); + goto exit; + } + self->private_impl.f_num_huffman_codes = self->private_data.f_scratch; + self->private_data.f_scratch = 0; + v_i = 0; + while (v_i < 15) { + if (self->private_impl.f_n_bits <= 0) { + { + WUFFS_BASE__COROUTINE_SUSPENSION_POINT(7); + if (WUFFS_BASE__UNLIKELY(iop_a_src == io2_a_src)) { + status = wuffs_base__make_status(wuffs_base__suspension__short_read); + goto suspend; + } + uint8_t t_6 = *iop_a_src++; + v_c = t_6; + } + self->private_impl.f_bits = (((uint32_t)(v_c)) << 24); + self->private_impl.f_n_bits = 8; + } + self->private_data.f_scratch <<= 1; + self->private_data.f_scratch |= (self->private_impl.f_bits >> 31); + self->private_impl.f_bits <<= 1; + self->private_impl.f_n_bits -= 1; + v_i += 1; + } + if ((self->private_data.f_scratch < 1) || (18001 < self->private_data.f_scratch)) { + status = wuffs_base__make_status(wuffs_bzip2__error__bad_block_header); + goto exit; + } + self->private_impl.f_num_sections = self->private_data.f_scratch; + v_i = 0; + while (v_i < self->private_impl.f_num_huffman_codes) { + self->private_data.f_mtft[v_i] = ((uint8_t)(v_i)); + v_i += 1; + } + v_i = 0; + while (v_i < self->private_impl.f_num_sections) { + v_selector = 0; + while (true) { + if (self->private_impl.f_n_bits <= 0) { + { + WUFFS_BASE__COROUTINE_SUSPENSION_POINT(8); + if (WUFFS_BASE__UNLIKELY(iop_a_src == io2_a_src)) { + status = wuffs_base__make_status(wuffs_base__suspension__short_read); + goto suspend; + } + uint8_t t_7 = *iop_a_src++; + v_c = t_7; + } + self->private_impl.f_bits = (((uint32_t)(v_c)) << 24); + self->private_impl.f_n_bits = 8; + } + if ((self->private_impl.f_bits >> 31) == 0) { + self->private_impl.f_bits <<= 1; + self->private_impl.f_n_bits -= 1; + goto label__2__break; + } + self->private_impl.f_bits <<= 1; + self->private_impl.f_n_bits -= 1; + v_selector += 1; + if (v_selector >= self->private_impl.f_num_huffman_codes) { + status = wuffs_base__make_status(wuffs_bzip2__error__bad_block_header); + goto exit; + } + } + label__2__break:; + if (v_selector == 0) { + self->private_data.f_huffman_selectors[v_i] = self->private_data.f_mtft[0]; + } else { + v_sel_ff = (v_selector & 255); + v_movee = self->private_data.f_mtft[v_sel_ff]; + wuffs_base__slice_u8__copy_from_slice(wuffs_base__make_slice_u8_ij(self->private_data.f_mtft, 1, (1 + v_sel_ff)), wuffs_base__make_slice_u8(self->private_data.f_mtft, v_sel_ff)); + self->private_data.f_mtft[0] = v_movee; + self->private_data.f_huffman_selectors[v_i] = v_movee; + } + v_i += 1; + } + v_i = 0; + while (v_i < self->private_impl.f_num_huffman_codes) { + if (a_src) { + a_src->meta.ri = ((size_t)(iop_a_src - a_src->data.ptr)); + } + WUFFS_BASE__COROUTINE_SUSPENSION_POINT(9); + status = wuffs_bzip2__decoder__read_code_lengths(self, a_src); + if (a_src) { + iop_a_src = a_src->data.ptr + a_src->meta.ri; + } + if (status.repr) { + goto suspend; + } + v_status = wuffs_bzip2__decoder__build_huffman_tree(self, v_i); + if (wuffs_base__status__is_error(&v_status)) { + status = v_status; + goto exit; + } + wuffs_bzip2__decoder__build_huffman_table(self, v_i); + v_i += 1; + } + v_i = 0; + v_j = 0; + while (v_i < 256) { + if (self->private_data.f_presence[v_i] != 0) { + self->private_data.f_mtft[(v_j & 255)] = ((uint8_t)(v_i)); + v_j += 1; + } + v_i += 1; + } + v_i = 0; + while (v_i < 256) { + self->private_data.f_letter_counts[v_i] = 0; + v_i += 1; + } + + goto ok; + ok: + self->private_impl.p_prepare_block[0] = 0; + goto exit; + } + + goto suspend; + suspend: + self->private_impl.p_prepare_block[0] = wuffs_base__status__is_suspension(&status) ? coro_susp_point : 0; + self->private_data.s_prepare_block[0].v_i = v_i; + self->private_data.s_prepare_block[0].v_selector = v_selector; + + goto exit; + exit: + if (a_src && a_src->data.ptr) { + a_src->meta.ri = ((size_t)(iop_a_src - a_src->data.ptr)); + } + + return status; +} + +// -------- func bzip2.decoder.read_code_lengths + +static wuffs_base__status +wuffs_bzip2__decoder__read_code_lengths( + wuffs_bzip2__decoder* self, + wuffs_base__io_buffer* a_src) { + wuffs_base__status status = wuffs_base__make_status(NULL); + + uint8_t v_c = 0; + uint32_t v_i = 0; + uint32_t v_code_length = 0; + + const uint8_t* iop_a_src = NULL; + const uint8_t* io0_a_src WUFFS_BASE__POTENTIALLY_UNUSED = NULL; + const uint8_t* io1_a_src WUFFS_BASE__POTENTIALLY_UNUSED = NULL; + const uint8_t* io2_a_src WUFFS_BASE__POTENTIALLY_UNUSED = NULL; + if (a_src && a_src->data.ptr) { + io0_a_src = a_src->data.ptr; + io1_a_src = io0_a_src + a_src->meta.ri; + iop_a_src = io1_a_src; + io2_a_src = io0_a_src + a_src->meta.wi; + } + + uint32_t coro_susp_point = self->private_impl.p_read_code_lengths[0]; + if (coro_susp_point) { + v_i = self->private_data.s_read_code_lengths[0].v_i; + v_code_length = self->private_data.s_read_code_lengths[0].v_code_length; + } + switch (coro_susp_point) { + WUFFS_BASE__COROUTINE_SUSPENSION_POINT_0; + + self->private_impl.f_code_lengths_bitmask = 0; + v_i = 0; + while (v_i < 5) { + if (self->private_impl.f_n_bits <= 0) { + { + WUFFS_BASE__COROUTINE_SUSPENSION_POINT(1); + if (WUFFS_BASE__UNLIKELY(iop_a_src == io2_a_src)) { + status = wuffs_base__make_status(wuffs_base__suspension__short_read); + goto suspend; + } + uint8_t t_0 = *iop_a_src++; + v_c = t_0; + } + self->private_impl.f_bits = (((uint32_t)(v_c)) << 24); + self->private_impl.f_n_bits = 8; + } + v_code_length <<= 1; + v_code_length |= (self->private_impl.f_bits >> 31); + self->private_impl.f_bits <<= 1; + self->private_impl.f_n_bits -= 1; + v_i += 1; + } + v_i = 0; + while (v_i < self->private_impl.f_num_symbols) { + while (true) { + if ((v_code_length < 1) || (20 < v_code_length)) { + status = wuffs_base__make_status(wuffs_bzip2__error__bad_block_header); + goto exit; + } + if (self->private_impl.f_n_bits <= 0) { + { + WUFFS_BASE__COROUTINE_SUSPENSION_POINT(2); + if (WUFFS_BASE__UNLIKELY(iop_a_src == io2_a_src)) { + status = wuffs_base__make_status(wuffs_base__suspension__short_read); + goto suspend; + } + uint8_t t_1 = *iop_a_src++; + v_c = t_1; + } + self->private_impl.f_bits = (((uint32_t)(v_c)) << 24); + self->private_impl.f_n_bits = 8; + } + if ((self->private_impl.f_bits >> 31) == 0) { + self->private_impl.f_bits <<= 1; + self->private_impl.f_n_bits -= 1; + goto label__0__break; + } + self->private_impl.f_bits <<= 1; + self->private_impl.f_n_bits -= 1; + if (self->private_impl.f_n_bits <= 0) { + { + WUFFS_BASE__COROUTINE_SUSPENSION_POINT(3); + if (WUFFS_BASE__UNLIKELY(iop_a_src == io2_a_src)) { + status = wuffs_base__make_status(wuffs_base__suspension__short_read); + goto suspend; + } + uint8_t t_2 = *iop_a_src++; + v_c = t_2; + } + self->private_impl.f_bits = (((uint32_t)(v_c)) << 24); + self->private_impl.f_n_bits = 8; + } + if ((self->private_impl.f_bits >> 31) == 0) { + v_code_length += 1; + } else { + v_code_length -= 1; + } + self->private_impl.f_bits <<= 1; + self->private_impl.f_n_bits -= 1; + } + label__0__break:; + self->private_impl.f_code_lengths_bitmask |= (((uint32_t)(1)) << (v_code_length & 31)); + self->private_data.f_bwt[v_i] = v_code_length; + v_i += 1; + } + + goto ok; + ok: + self->private_impl.p_read_code_lengths[0] = 0; + goto exit; + } + + goto suspend; + suspend: + self->private_impl.p_read_code_lengths[0] = wuffs_base__status__is_suspension(&status) ? coro_susp_point : 0; + self->private_data.s_read_code_lengths[0].v_i = v_i; + self->private_data.s_read_code_lengths[0].v_code_length = v_code_length; + + goto exit; + exit: + if (a_src && a_src->data.ptr) { + a_src->meta.ri = ((size_t)(iop_a_src - a_src->data.ptr)); + } + + return status; +} + +// -------- func bzip2.decoder.build_huffman_tree + +static wuffs_base__status +wuffs_bzip2__decoder__build_huffman_tree( + wuffs_bzip2__decoder* self, + uint32_t a_which) { + uint32_t v_code_length = 0; + uint32_t v_symbol_index = 0; + uint32_t v_num_branch_nodes = 0; + uint32_t v_stack_height = 0; + uint32_t v_stack_values[21] = {0}; + uint32_t v_node_index = 0; + uint16_t v_leaf_value = 0; + + self->private_data.f_huffman_trees[a_which][0][0] = 0; + self->private_data.f_huffman_trees[a_which][0][1] = 0; + v_num_branch_nodes = 1; + v_stack_height = 1; + v_stack_values[0] = 0; + v_code_length = 1; + label__0__continue:; + while (v_code_length <= 20) { + if ((self->private_impl.f_code_lengths_bitmask & (((uint32_t)(1)) << v_code_length)) == 0) { + v_code_length += 1; + goto label__0__continue; + } + v_symbol_index = 0; + label__1__continue:; + while (v_symbol_index < self->private_impl.f_num_symbols) { + if (self->private_data.f_bwt[v_symbol_index] != v_code_length) { + v_symbol_index += 1; + goto label__1__continue; + } + while (true) { + if (v_stack_height <= 0) { + return wuffs_base__make_status(wuffs_bzip2__error__bad_huffman_code_over_subscribed); + } else if (v_stack_height >= v_code_length) { + goto label__2__break; + } + v_node_index = v_stack_values[(v_stack_height - 1)]; + if (self->private_data.f_huffman_trees[a_which][v_node_index][0] == 0) { + self->private_data.f_huffman_trees[a_which][v_node_index][0] = ((uint16_t)(v_num_branch_nodes)); + } else { + self->private_data.f_huffman_trees[a_which][v_node_index][1] = ((uint16_t)(v_num_branch_nodes)); + } + if (v_num_branch_nodes >= 257) { + return wuffs_base__make_status(wuffs_bzip2__error__bad_huffman_code_under_subscribed); + } + v_stack_values[v_stack_height] = v_num_branch_nodes; + self->private_data.f_huffman_trees[a_which][v_num_branch_nodes][0] = 0; + self->private_data.f_huffman_trees[a_which][v_num_branch_nodes][1] = 0; + v_num_branch_nodes += 1; + v_stack_height += 1; + } + label__2__break:; + v_node_index = v_stack_values[(v_stack_height - 1)]; + if (v_symbol_index < 2) { + v_leaf_value = ((uint16_t)((769 + v_symbol_index))); + } else if ((v_symbol_index + 1) < self->private_impl.f_num_symbols) { + v_leaf_value = ((uint16_t)((511 + v_symbol_index))); + } else { + v_leaf_value = 768; + } + if (self->private_data.f_huffman_trees[a_which][v_node_index][0] == 0) { + self->private_data.f_huffman_trees[a_which][v_node_index][0] = v_leaf_value; + } else { + self->private_data.f_huffman_trees[a_which][v_node_index][1] = v_leaf_value; + v_stack_height -= 1; + while (v_stack_height > 0) { + v_node_index = v_stack_values[(v_stack_height - 1)]; + if (self->private_data.f_huffman_trees[a_which][v_node_index][1] == 0) { + goto label__3__break; + } + v_stack_height -= 1; + } + label__3__break:; + } + v_symbol_index += 1; + } + v_code_length += 1; + } + if (v_stack_height != 0) { + return wuffs_base__make_status(wuffs_bzip2__error__bad_huffman_code_under_subscribed); + } + return wuffs_base__make_status(NULL); +} + +// -------- func bzip2.decoder.build_huffman_table + +static wuffs_base__empty_struct +wuffs_bzip2__decoder__build_huffman_table( + wuffs_bzip2__decoder* self, + uint32_t a_which) { + uint32_t v_i = 0; + uint32_t v_bits = 0; + uint16_t v_n_bits = 0; + uint16_t v_child = 0; + + while (v_i < 256) { + v_bits = (v_i << 24); + v_n_bits = 0; + v_child = 0; + while ((v_child < 257) && (v_n_bits < 8)) { + v_child = self->private_data.f_huffman_trees[a_which][v_child][(v_bits >> 31)]; + v_bits <<= 1; +#if defined(__GNUC__) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wconversion" +#endif + v_n_bits += 1; +#if defined(__GNUC__) +#pragma GCC diagnostic pop +#endif + } + self->private_data.f_huffman_tables[a_which][v_i] = ((uint16_t)((v_child | (v_n_bits << 12)))); + v_i += 1; + } + return wuffs_base__make_empty_struct(); +} + +// -------- func bzip2.decoder.invert_bwt + +static wuffs_base__empty_struct +wuffs_bzip2__decoder__invert_bwt( + wuffs_bzip2__decoder* self) { + uint32_t v_i = 0; + uint32_t v_letter = 0; + uint32_t v_sum = 0; + uint32_t v_old_sum = 0; + + v_sum = 0; + v_i = 0; + while (v_i < 256) { + v_old_sum = v_sum; + v_sum += self->private_data.f_letter_counts[v_i]; + self->private_data.f_letter_counts[v_i] = v_old_sum; + v_i += 1; + } + v_i = 0; + while (v_i < self->private_impl.f_block_size) { + v_letter = (self->private_data.f_bwt[v_i] & 255); + self->private_data.f_bwt[(self->private_data.f_letter_counts[v_letter] & 1048575)] |= (v_i << 12); + self->private_data.f_letter_counts[v_letter] += 1; + v_i += 1; + } + return wuffs_base__make_empty_struct(); +} + +// -------- func bzip2.decoder.flush_fast + +static wuffs_base__empty_struct +wuffs_bzip2__decoder__flush_fast( + wuffs_bzip2__decoder* self, + wuffs_base__io_buffer* a_dst) { + uint32_t v_flush_pointer = 0; + uint32_t v_flush_repeat_count = 0; + uint8_t v_flush_prev = 0; + uint32_t v_block_checksum_have = 0; + uint32_t v_block_size = 0; + uint32_t v_entry = 0; + uint8_t v_curr = 0; + + uint8_t* iop_a_dst = NULL; + uint8_t* io0_a_dst WUFFS_BASE__POTENTIALLY_UNUSED = NULL; + uint8_t* io1_a_dst WUFFS_BASE__POTENTIALLY_UNUSED = NULL; + uint8_t* io2_a_dst WUFFS_BASE__POTENTIALLY_UNUSED = NULL; + if (a_dst && a_dst->data.ptr) { + io0_a_dst = a_dst->data.ptr; + io1_a_dst = io0_a_dst + a_dst->meta.wi; + iop_a_dst = io1_a_dst; + io2_a_dst = io0_a_dst + a_dst->data.len; + if (a_dst->meta.closed) { + io2_a_dst = iop_a_dst; + } + } + + v_flush_pointer = self->private_impl.f_flush_pointer; + v_flush_repeat_count = self->private_impl.f_flush_repeat_count; + v_flush_prev = self->private_impl.f_flush_prev; + v_block_checksum_have = self->private_impl.f_block_checksum_have; + v_block_size = self->private_impl.f_block_size; + while ((v_block_size > 0) && (((uint64_t)(io2_a_dst - iop_a_dst)) >= 255)) { + if (v_flush_repeat_count < 4) { + v_entry = self->private_data.f_bwt[v_flush_pointer]; + v_curr = ((uint8_t)((v_entry & 255))); + v_flush_pointer = (v_entry >> 12); + if (v_curr == v_flush_prev) { + v_flush_repeat_count += 1; + } else { + v_flush_repeat_count = 1; + } + v_block_checksum_have = (WUFFS_BZIP2__REV_CRC32_TABLE[(((uint8_t)((v_block_checksum_have >> 24))) ^ v_curr)] ^ ((uint32_t)(v_block_checksum_have << 8))); + (wuffs_base__poke_u8be__no_bounds_check(iop_a_dst, v_curr), iop_a_dst += 1); + v_flush_prev = v_curr; + v_block_size -= 1; + } else { + v_entry = self->private_data.f_bwt[v_flush_pointer]; + v_curr = ((uint8_t)((v_entry & 255))); + v_flush_pointer = (v_entry >> 12); + v_flush_repeat_count = ((uint32_t)(v_curr)); + while (v_flush_repeat_count > 0) { + v_block_checksum_have = (WUFFS_BZIP2__REV_CRC32_TABLE[(((uint8_t)((v_block_checksum_have >> 24))) ^ v_flush_prev)] ^ ((uint32_t)(v_block_checksum_have << 8))); + if (((uint64_t)(io2_a_dst - iop_a_dst)) > 0) { + (wuffs_base__poke_u8be__no_bounds_check(iop_a_dst, v_flush_prev), iop_a_dst += 1); + } + v_flush_repeat_count -= 1; + } + v_flush_repeat_count = 0; + v_flush_prev = v_curr; + v_block_size -= 1; + } + } + self->private_impl.f_flush_pointer = v_flush_pointer; + self->private_impl.f_flush_repeat_count = v_flush_repeat_count; + self->private_impl.f_flush_prev = v_flush_prev; + self->private_impl.f_block_checksum_have = v_block_checksum_have; + if (v_block_size <= 900000) { + self->private_impl.f_block_size = v_block_size; + } + if (a_dst && a_dst->data.ptr) { + a_dst->meta.wi = ((size_t)(iop_a_dst - a_dst->data.ptr)); + } + + return wuffs_base__make_empty_struct(); +} + +// -------- func bzip2.decoder.flush_slow + +static wuffs_base__status +wuffs_bzip2__decoder__flush_slow( + wuffs_bzip2__decoder* self, + wuffs_base__io_buffer* a_dst) { + wuffs_base__status status = wuffs_base__make_status(NULL); + + uint32_t v_flush_pointer = 0; + uint32_t v_flush_repeat_count = 0; + uint8_t v_flush_prev = 0; + uint32_t v_block_checksum_have = 0; + uint32_t v_block_size = 0; + uint32_t v_entry = 0; + uint8_t v_curr = 0; + + uint8_t* iop_a_dst = NULL; + uint8_t* io0_a_dst WUFFS_BASE__POTENTIALLY_UNUSED = NULL; + uint8_t* io1_a_dst WUFFS_BASE__POTENTIALLY_UNUSED = NULL; + uint8_t* io2_a_dst WUFFS_BASE__POTENTIALLY_UNUSED = NULL; + if (a_dst && a_dst->data.ptr) { + io0_a_dst = a_dst->data.ptr; + io1_a_dst = io0_a_dst + a_dst->meta.wi; + iop_a_dst = io1_a_dst; + io2_a_dst = io0_a_dst + a_dst->data.len; + if (a_dst->meta.closed) { + io2_a_dst = iop_a_dst; + } + } + + uint32_t coro_susp_point = self->private_impl.p_flush_slow[0]; + if (coro_susp_point) { + v_flush_pointer = self->private_data.s_flush_slow[0].v_flush_pointer; + v_flush_repeat_count = self->private_data.s_flush_slow[0].v_flush_repeat_count; + v_flush_prev = self->private_data.s_flush_slow[0].v_flush_prev; + v_block_checksum_have = self->private_data.s_flush_slow[0].v_block_checksum_have; + v_block_size = self->private_data.s_flush_slow[0].v_block_size; + v_curr = self->private_data.s_flush_slow[0].v_curr; + } + switch (coro_susp_point) { + WUFFS_BASE__COROUTINE_SUSPENSION_POINT_0; + + v_flush_pointer = self->private_impl.f_flush_pointer; + v_flush_repeat_count = self->private_impl.f_flush_repeat_count; + v_flush_prev = self->private_impl.f_flush_prev; + v_block_checksum_have = self->private_impl.f_block_checksum_have; + v_block_size = self->private_impl.f_block_size; + while ((v_block_size > 0) && ! (self->private_impl.p_flush_slow[0] != 0)) { + if (v_flush_repeat_count < 4) { + v_entry = self->private_data.f_bwt[v_flush_pointer]; + v_curr = ((uint8_t)((v_entry & 255))); + v_flush_pointer = (v_entry >> 12); + if (v_curr == v_flush_prev) { + v_flush_repeat_count += 1; + } else { + v_flush_repeat_count = 1; + } + v_block_checksum_have = (WUFFS_BZIP2__REV_CRC32_TABLE[(((uint8_t)((v_block_checksum_have >> 24))) ^ v_curr)] ^ ((uint32_t)(v_block_checksum_have << 8))); + self->private_data.s_flush_slow[0].scratch = v_curr; + WUFFS_BASE__COROUTINE_SUSPENSION_POINT(1); + if (iop_a_dst == io2_a_dst) { + status = wuffs_base__make_status(wuffs_base__suspension__short_write); + goto suspend; + } + *iop_a_dst++ = ((uint8_t)(self->private_data.s_flush_slow[0].scratch)); + v_flush_prev = v_curr; + v_block_size -= 1; + } else { + v_entry = self->private_data.f_bwt[v_flush_pointer]; + v_curr = ((uint8_t)((v_entry & 255))); + v_flush_pointer = (v_entry >> 12); + v_flush_repeat_count = ((uint32_t)(v_curr)); + while (v_flush_repeat_count > 0) { + v_block_checksum_have = (WUFFS_BZIP2__REV_CRC32_TABLE[(((uint8_t)((v_block_checksum_have >> 24))) ^ v_flush_prev)] ^ ((uint32_t)(v_block_checksum_have << 8))); + self->private_data.s_flush_slow[0].scratch = v_flush_prev; + WUFFS_BASE__COROUTINE_SUSPENSION_POINT(2); + if (iop_a_dst == io2_a_dst) { + status = wuffs_base__make_status(wuffs_base__suspension__short_write); + goto suspend; + } + *iop_a_dst++ = ((uint8_t)(self->private_data.s_flush_slow[0].scratch)); + v_flush_repeat_count -= 1; + } + v_flush_repeat_count = 0; + v_flush_prev = v_curr; + v_block_size -= 1; + } + } + self->private_impl.f_flush_pointer = v_flush_pointer; + self->private_impl.f_flush_repeat_count = v_flush_repeat_count; + self->private_impl.f_flush_prev = v_flush_prev; + self->private_impl.f_block_checksum_have = v_block_checksum_have; + if (v_block_size <= 900000) { + self->private_impl.f_block_size = v_block_size; + } + + goto ok; + ok: + self->private_impl.p_flush_slow[0] = 0; + goto exit; + } + + goto suspend; + suspend: + self->private_impl.p_flush_slow[0] = wuffs_base__status__is_suspension(&status) ? coro_susp_point : 0; + self->private_data.s_flush_slow[0].v_flush_pointer = v_flush_pointer; + self->private_data.s_flush_slow[0].v_flush_repeat_count = v_flush_repeat_count; + self->private_data.s_flush_slow[0].v_flush_prev = v_flush_prev; + self->private_data.s_flush_slow[0].v_block_checksum_have = v_block_checksum_have; + self->private_data.s_flush_slow[0].v_block_size = v_block_size; + self->private_data.s_flush_slow[0].v_curr = v_curr; + + goto exit; + exit: + if (a_dst && a_dst->data.ptr) { + a_dst->meta.wi = ((size_t)(iop_a_dst - a_dst->data.ptr)); + } + + return status; +} + +// -------- func bzip2.decoder.decode_huffman_fast + +static wuffs_base__status +wuffs_bzip2__decoder__decode_huffman_fast( + wuffs_bzip2__decoder* self, + wuffs_base__io_buffer* a_src) { + wuffs_base__status status = wuffs_base__make_status(NULL); + + uint32_t v_bits = 0; + uint32_t v_n_bits = 0; + uint32_t v_block_size = 0; + uint8_t v_which = 0; + uint32_t v_ticks = 0; + uint32_t v_section = 0; + uint32_t v_run_shift = 0; + uint16_t v_table_entry = 0; + uint16_t v_child = 0; + uint32_t v_child_ff = 0; + uint32_t v_i = 0; + uint32_t v_j = 0; + uint32_t v_output = 0; + uint32_t v_run = 0; + uint32_t v_mtft0 = 0; + + const uint8_t* iop_a_src = NULL; + const uint8_t* io0_a_src WUFFS_BASE__POTENTIALLY_UNUSED = NULL; + const uint8_t* io1_a_src WUFFS_BASE__POTENTIALLY_UNUSED = NULL; + const uint8_t* io2_a_src WUFFS_BASE__POTENTIALLY_UNUSED = NULL; + if (a_src && a_src->data.ptr) { + io0_a_src = a_src->data.ptr; + io1_a_src = io0_a_src + a_src->meta.ri; + iop_a_src = io1_a_src; + io2_a_src = io0_a_src + a_src->meta.wi; + } + + v_bits = self->private_impl.f_bits; + v_n_bits = self->private_impl.f_n_bits; + v_block_size = self->private_impl.f_block_size; + v_which = self->private_impl.f_decode_huffman_which; + v_ticks = self->private_impl.f_decode_huffman_ticks; + v_section = self->private_impl.f_decode_huffman_section; + v_run_shift = self->private_impl.f_decode_huffman_run_shift; + label__outer__continue:; + while (((uint64_t)(io2_a_src - iop_a_src)) >= 4) { + if (v_ticks > 0) { + v_ticks -= 1; + } else { + v_ticks = 49; + v_section += 1; + if (v_section >= self->private_impl.f_num_sections) { + status = wuffs_base__make_status(wuffs_bzip2__error__bad_number_of_sections); + goto exit; + } + v_which = WUFFS_BZIP2__CLAMP_TO_5[(self->private_data.f_huffman_selectors[(v_section & 32767)] & 7)]; + } + v_bits |= (wuffs_base__peek_u32be__no_bounds_check(iop_a_src) >> v_n_bits); + iop_a_src += ((31 - v_n_bits) >> 3); + v_n_bits |= 24; + v_table_entry = self->private_data.f_huffman_tables[v_which][(v_bits >> 24)]; + v_bits <<= (v_table_entry >> 12); + v_n_bits -= ((uint32_t)((v_table_entry >> 12))); + v_child = (v_table_entry & 1023); + while (v_child < 257) { + v_child = self->private_data.f_huffman_trees[v_which][v_child][(v_bits >> 31)]; + v_bits <<= 1; + if (v_n_bits <= 0) { + status = wuffs_base__make_status(wuffs_bzip2__error__internal_error_inconsistent_huffman_decoder_state); + goto exit; + } + v_n_bits -= 1; + } + if (v_child < 768) { + v_child_ff = ((uint32_t)((v_child & 255))); + v_output = ((uint32_t)(self->private_data.f_mtft[v_child_ff])); + wuffs_base__slice_u8__copy_from_slice(wuffs_base__make_slice_u8_ij(self->private_data.f_mtft, 1, (1 + v_child_ff)), wuffs_base__make_slice_u8(self->private_data.f_mtft, v_child_ff)); + self->private_data.f_mtft[0] = ((uint8_t)(v_output)); + self->private_data.f_letter_counts[v_output] += 1; + self->private_data.f_bwt[v_block_size] = v_output; + if (v_block_size >= self->private_impl.f_max_incl_block_size) { + status = wuffs_base__make_status(wuffs_bzip2__error__bad_block_length); + goto exit; + } + v_block_size += 1; + v_run_shift = 0; + goto label__outer__continue; + } else if (v_child == 768) { + self->private_impl.f_decode_huffman_finished = true; + goto label__outer__break; + } + if (v_run_shift >= 23) { + status = wuffs_base__make_status(wuffs_bzip2__error__bad_block_length); + goto exit; + } + v_run = ((((uint32_t)(v_child)) & 3) << v_run_shift); + v_run_shift += 1; + v_i = v_block_size; + v_j = (v_run + v_block_size); + if (v_j > self->private_impl.f_max_incl_block_size) { + status = wuffs_base__make_status(wuffs_bzip2__error__bad_block_length); + goto exit; + } + v_block_size = v_j; + v_mtft0 = ((uint32_t)(self->private_data.f_mtft[0])); + self->private_data.f_letter_counts[v_mtft0] += v_run; + while (v_i < v_j) { + self->private_data.f_bwt[v_i] = v_mtft0; + v_i += 1; + } + } + label__outer__break:; + self->private_impl.f_bits = v_bits; + self->private_impl.f_n_bits = v_n_bits; + self->private_impl.f_block_size = v_block_size; + self->private_impl.f_decode_huffman_which = v_which; + self->private_impl.f_decode_huffman_ticks = v_ticks; + self->private_impl.f_decode_huffman_section = v_section; + self->private_impl.f_decode_huffman_run_shift = v_run_shift; + status = wuffs_base__make_status(NULL); + goto ok; + + ok: + goto exit; + exit: + if (a_src && a_src->data.ptr) { + a_src->meta.ri = ((size_t)(iop_a_src - a_src->data.ptr)); + } + + return status; +} + +// -------- func bzip2.decoder.decode_huffman_slow + +static wuffs_base__status +wuffs_bzip2__decoder__decode_huffman_slow( + wuffs_bzip2__decoder* self, + wuffs_base__io_buffer* a_src) { + wuffs_base__status status = wuffs_base__make_status(NULL); + + uint8_t v_c = 0; + uint32_t v_node_index = 0; + uint16_t v_child = 0; + uint32_t v_child_ff = 0; + uint32_t v_i = 0; + uint32_t v_j = 0; + uint32_t v_output = 0; + uint32_t v_run = 0; + uint32_t v_mtft0 = 0; + + const uint8_t* iop_a_src = NULL; + const uint8_t* io0_a_src WUFFS_BASE__POTENTIALLY_UNUSED = NULL; + const uint8_t* io1_a_src WUFFS_BASE__POTENTIALLY_UNUSED = NULL; + const uint8_t* io2_a_src WUFFS_BASE__POTENTIALLY_UNUSED = NULL; + if (a_src && a_src->data.ptr) { + io0_a_src = a_src->data.ptr; + io1_a_src = io0_a_src + a_src->meta.ri; + iop_a_src = io1_a_src; + io2_a_src = io0_a_src + a_src->meta.wi; + } + + uint32_t coro_susp_point = self->private_impl.p_decode_huffman_slow[0]; + if (coro_susp_point) { + v_node_index = self->private_data.s_decode_huffman_slow[0].v_node_index; + } + switch (coro_susp_point) { + WUFFS_BASE__COROUTINE_SUSPENSION_POINT_0; + + while ( ! (self->private_impl.p_decode_huffman_slow[0] != 0)) { + if (self->private_impl.f_decode_huffman_ticks > 0) { + self->private_impl.f_decode_huffman_ticks -= 1; + } else { + self->private_impl.f_decode_huffman_ticks = 49; + self->private_impl.f_decode_huffman_section += 1; + if (self->private_impl.f_decode_huffman_section >= self->private_impl.f_num_sections) { + status = wuffs_base__make_status(wuffs_bzip2__error__bad_number_of_sections); + goto exit; + } + self->private_impl.f_decode_huffman_which = WUFFS_BZIP2__CLAMP_TO_5[(self->private_data.f_huffman_selectors[(self->private_impl.f_decode_huffman_section & 32767)] & 7)]; + } + v_node_index = 0; + label__0__continue:; + while (true) { + if (self->private_impl.f_n_bits <= 0) { + { + WUFFS_BASE__COROUTINE_SUSPENSION_POINT(1); + if (WUFFS_BASE__UNLIKELY(iop_a_src == io2_a_src)) { + status = wuffs_base__make_status(wuffs_base__suspension__short_read); + goto suspend; + } + uint8_t t_0 = *iop_a_src++; + v_c = t_0; + } + self->private_impl.f_bits = (((uint32_t)(v_c)) << 24); + self->private_impl.f_n_bits = 8; + } + v_child = self->private_data.f_huffman_trees[self->private_impl.f_decode_huffman_which][v_node_index][(self->private_impl.f_bits >> 31)]; + self->private_impl.f_bits <<= 1; + self->private_impl.f_n_bits -= 1; + if (v_child < 257) { + v_node_index = ((uint32_t)(v_child)); + goto label__0__continue; + } else if (v_child < 768) { + v_child_ff = ((uint32_t)((v_child & 255))); + v_output = ((uint32_t)(self->private_data.f_mtft[v_child_ff])); + wuffs_base__slice_u8__copy_from_slice(wuffs_base__make_slice_u8_ij(self->private_data.f_mtft, 1, (1 + v_child_ff)), wuffs_base__make_slice_u8(self->private_data.f_mtft, v_child_ff)); + self->private_data.f_mtft[0] = ((uint8_t)(v_output)); + self->private_data.f_letter_counts[v_output] += 1; + self->private_data.f_bwt[self->private_impl.f_block_size] = v_output; + if (self->private_impl.f_block_size >= self->private_impl.f_max_incl_block_size) { + status = wuffs_base__make_status(wuffs_bzip2__error__bad_block_length); + goto exit; + } + self->private_impl.f_block_size += 1; + self->private_impl.f_decode_huffman_run_shift = 0; + goto label__0__break; + } else if (v_child == 768) { + self->private_impl.f_decode_huffman_finished = true; + goto label__outer__break; + } + if (self->private_impl.f_decode_huffman_run_shift >= 23) { + status = wuffs_base__make_status(wuffs_bzip2__error__bad_block_length); + goto exit; + } + v_run = ((((uint32_t)(v_child)) & 3) << self->private_impl.f_decode_huffman_run_shift); + self->private_impl.f_decode_huffman_run_shift += 1; + v_i = self->private_impl.f_block_size; + v_j = (v_run + self->private_impl.f_block_size); + if (v_j > self->private_impl.f_max_incl_block_size) { + status = wuffs_base__make_status(wuffs_bzip2__error__bad_block_length); + goto exit; + } + self->private_impl.f_block_size = v_j; + v_mtft0 = ((uint32_t)(self->private_data.f_mtft[0])); + self->private_data.f_letter_counts[v_mtft0] += v_run; + while (v_i < v_j) { + self->private_data.f_bwt[v_i] = v_mtft0; + v_i += 1; + } + goto label__0__break; + } + label__0__break:; + } + label__outer__break:; + + goto ok; + ok: + self->private_impl.p_decode_huffman_slow[0] = 0; + goto exit; + } + + goto suspend; + suspend: + self->private_impl.p_decode_huffman_slow[0] = wuffs_base__status__is_suspension(&status) ? coro_susp_point : 0; + self->private_data.s_decode_huffman_slow[0].v_node_index = v_node_index; + + goto exit; + exit: + if (a_src && a_src->data.ptr) { + a_src->meta.ri = ((size_t)(iop_a_src - a_src->data.ptr)); + } + + return status; +} + +#endif // !defined(WUFFS_CONFIG__MODULES) || defined(WUFFS_CONFIG__MODULE__BZIP2) + +#if !defined(WUFFS_CONFIG__MODULES) || defined(WUFFS_CONFIG__MODULE__CBOR) + +// ---------------- Status Codes Implementations + +const char wuffs_cbor__error__bad_input[] = "#cbor: bad input"; +const char wuffs_cbor__error__unsupported_recursion_depth[] = "#cbor: unsupported recursion depth"; +const char wuffs_cbor__error__internal_error_inconsistent_i_o[] = "#cbor: internal error: inconsistent I/O"; +const char wuffs_cbor__error__internal_error_inconsistent_token_length[] = "#cbor: internal error: inconsistent token length"; + +// ---------------- Private Consts + +static const uint32_t +WUFFS_CBOR__LITERALS[4] WUFFS_BASE__POTENTIALLY_UNUSED = { + 8388612, 8388616, 8388610, 8388609, +}; + +static const uint8_t +WUFFS_CBOR__TOKEN_LENGTHS[32] WUFFS_BASE__POTENTIALLY_UNUSED = { + 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, + 2, 3, 5, 9, 0, 0, 0, 1, +}; + +// ---------------- Private Initializer Prototypes + +// ---------------- Private Function Prototypes + +// ---------------- VTables + +const wuffs_base__token_decoder__func_ptrs +wuffs_cbor__decoder__func_ptrs_for__wuffs_base__token_decoder = { + (wuffs_base__status(*)(void*, + wuffs_base__token_buffer*, + wuffs_base__io_buffer*, + wuffs_base__slice_u8))(&wuffs_cbor__decoder__decode_tokens), + (wuffs_base__empty_struct(*)(void*, + uint32_t, + bool))(&wuffs_cbor__decoder__set_quirk_enabled), + (wuffs_base__range_ii_u64(*)(const void*))(&wuffs_cbor__decoder__workbuf_len), +}; + +// ---------------- Initializer Implementations + +wuffs_base__status WUFFS_BASE__WARN_UNUSED_RESULT +wuffs_cbor__decoder__initialize( + wuffs_cbor__decoder* self, + size_t sizeof_star_self, + uint64_t wuffs_version, + uint32_t options){ + if (!self) { + return wuffs_base__make_status(wuffs_base__error__bad_receiver); + } + if (sizeof(*self) != sizeof_star_self) { + return wuffs_base__make_status(wuffs_base__error__bad_sizeof_receiver); + } + if (((wuffs_version >> 32) != WUFFS_VERSION_MAJOR) || + (((wuffs_version >> 16) & 0xFFFF) > WUFFS_VERSION_MINOR)) { + return wuffs_base__make_status(wuffs_base__error__bad_wuffs_version); + } + + if ((options & WUFFS_INITIALIZE__ALREADY_ZEROED) != 0) { + // The whole point of this if-check is to detect an uninitialized *self. + // We disable the warning on GCC. Clang-5.0 does not have this warning. +#if !defined(__clang__) && defined(__GNUC__) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wmaybe-uninitialized" +#endif + if (self->private_impl.magic != 0) { + return wuffs_base__make_status(wuffs_base__error__initialize_falsely_claimed_already_zeroed); + } +#if !defined(__clang__) && defined(__GNUC__) +#pragma GCC diagnostic pop +#endif + } else { + if ((options & WUFFS_INITIALIZE__LEAVE_INTERNAL_BUFFERS_UNINITIALIZED) == 0) { + memset(self, 0, sizeof(*self)); + options |= WUFFS_INITIALIZE__ALREADY_ZEROED; + } else { + memset(&(self->private_impl), 0, sizeof(self->private_impl)); + } + } + + self->private_impl.magic = WUFFS_BASE__MAGIC; + self->private_impl.vtable_for__wuffs_base__token_decoder.vtable_name = + wuffs_base__token_decoder__vtable_name; + self->private_impl.vtable_for__wuffs_base__token_decoder.function_pointers = + (const void*)(&wuffs_cbor__decoder__func_ptrs_for__wuffs_base__token_decoder); + return wuffs_base__make_status(NULL); +} + +wuffs_cbor__decoder* +wuffs_cbor__decoder__alloc() { + wuffs_cbor__decoder* x = + (wuffs_cbor__decoder*)(calloc(sizeof(wuffs_cbor__decoder), 1)); + if (!x) { + return NULL; + } + if (wuffs_cbor__decoder__initialize( + x, sizeof(wuffs_cbor__decoder), WUFFS_VERSION, WUFFS_INITIALIZE__ALREADY_ZEROED).repr) { + free(x); + return NULL; + } + return x; +} + +size_t +sizeof__wuffs_cbor__decoder() { + return sizeof(wuffs_cbor__decoder); +} + +// ---------------- Function Implementations + +// -------- func cbor.decoder.set_quirk_enabled + +WUFFS_BASE__MAYBE_STATIC wuffs_base__empty_struct +wuffs_cbor__decoder__set_quirk_enabled( + wuffs_cbor__decoder* self, + uint32_t a_quirk, + bool a_enabled) { + return wuffs_base__make_empty_struct(); +} + +// -------- func cbor.decoder.workbuf_len + +WUFFS_BASE__MAYBE_STATIC wuffs_base__range_ii_u64 +wuffs_cbor__decoder__workbuf_len( + const wuffs_cbor__decoder* self) { + if (!self) { + return wuffs_base__utility__empty_range_ii_u64(); + } + if ((self->private_impl.magic != WUFFS_BASE__MAGIC) && + (self->private_impl.magic != WUFFS_BASE__DISABLED)) { + return wuffs_base__utility__empty_range_ii_u64(); + } + + return wuffs_base__utility__empty_range_ii_u64(); +} + +// -------- func cbor.decoder.decode_tokens + +WUFFS_BASE__MAYBE_STATIC wuffs_base__status +wuffs_cbor__decoder__decode_tokens( + wuffs_cbor__decoder* self, + wuffs_base__token_buffer* a_dst, + wuffs_base__io_buffer* a_src, + wuffs_base__slice_u8 a_workbuf) { + if (!self) { + return wuffs_base__make_status(wuffs_base__error__bad_receiver); + } + if (self->private_impl.magic != WUFFS_BASE__MAGIC) { + return wuffs_base__make_status( + (self->private_impl.magic == WUFFS_BASE__DISABLED) + ? wuffs_base__error__disabled_by_previous_error + : wuffs_base__error__initialize_not_called); + } + if (!a_dst || !a_src) { + self->private_impl.magic = WUFFS_BASE__DISABLED; + return wuffs_base__make_status(wuffs_base__error__bad_argument); + } + if ((self->private_impl.active_coroutine != 0) && + (self->private_impl.active_coroutine != 1)) { + self->private_impl.magic = WUFFS_BASE__DISABLED; + return wuffs_base__make_status(wuffs_base__error__interleaved_coroutine_calls); + } + self->private_impl.active_coroutine = 0; + wuffs_base__status status = wuffs_base__make_status(NULL); + + uint64_t v_string_length = 0; + uint64_t v_n64 = 0; + uint32_t v_depth = 0; + uint32_t v_stack_byte = 0; + uint32_t v_stack_bit = 0; + uint32_t v_stack_val = 0; + uint32_t v_token_length = 0; + uint32_t v_vminor = 0; + uint32_t v_vminor_alt = 0; + uint32_t v_continued = 0; + uint8_t v_c = 0; + uint8_t v_c_major = 0; + uint8_t v_c_minor = 0; + bool v_tagged = false; + uint8_t v_indefinite_string_major_type = 0; + + wuffs_base__token* iop_a_dst = NULL; + wuffs_base__token* io0_a_dst WUFFS_BASE__POTENTIALLY_UNUSED = NULL; + wuffs_base__token* io1_a_dst WUFFS_BASE__POTENTIALLY_UNUSED = NULL; + wuffs_base__token* io2_a_dst WUFFS_BASE__POTENTIALLY_UNUSED = NULL; + if (a_dst && a_dst->data.ptr) { + io0_a_dst = a_dst->data.ptr; + io1_a_dst = io0_a_dst + a_dst->meta.wi; + iop_a_dst = io1_a_dst; + io2_a_dst = io0_a_dst + a_dst->data.len; + if (a_dst->meta.closed) { + io2_a_dst = iop_a_dst; + } + } + const uint8_t* iop_a_src = NULL; + const uint8_t* io0_a_src WUFFS_BASE__POTENTIALLY_UNUSED = NULL; + const uint8_t* io1_a_src WUFFS_BASE__POTENTIALLY_UNUSED = NULL; + const uint8_t* io2_a_src WUFFS_BASE__POTENTIALLY_UNUSED = NULL; + if (a_src && a_src->data.ptr) { + io0_a_src = a_src->data.ptr; + io1_a_src = io0_a_src + a_src->meta.ri; + iop_a_src = io1_a_src; + io2_a_src = io0_a_src + a_src->meta.wi; + } + + uint32_t coro_susp_point = self->private_impl.p_decode_tokens[0]; + if (coro_susp_point) { + v_string_length = self->private_data.s_decode_tokens[0].v_string_length; + v_depth = self->private_data.s_decode_tokens[0].v_depth; + v_tagged = self->private_data.s_decode_tokens[0].v_tagged; + v_indefinite_string_major_type = self->private_data.s_decode_tokens[0].v_indefinite_string_major_type; + } + switch (coro_susp_point) { + WUFFS_BASE__COROUTINE_SUSPENSION_POINT_0; + + if (self->private_impl.f_end_of_data) { + status = wuffs_base__make_status(wuffs_base__note__end_of_data); + goto ok; + } + label__outer__continue:; + while (true) { + while (true) { + while (true) { + if (((uint64_t)(io2_a_dst - iop_a_dst)) <= 1) { + status = wuffs_base__make_status(wuffs_base__suspension__short_write); + WUFFS_BASE__COROUTINE_SUSPENSION_POINT_MAYBE_SUSPEND(1); + goto label__outer__continue; + } + if (((uint64_t)(io2_a_src - iop_a_src)) <= 0) { + if (a_src && a_src->meta.closed) { + status = wuffs_base__make_status(wuffs_cbor__error__bad_input); + goto exit; + } + status = wuffs_base__make_status(wuffs_base__suspension__short_read); + WUFFS_BASE__COROUTINE_SUSPENSION_POINT_MAYBE_SUSPEND(2); + goto label__outer__continue; + } + v_c = wuffs_base__peek_u8be__no_bounds_check(iop_a_src); + if ((v_indefinite_string_major_type != 0) && (v_indefinite_string_major_type != (v_c >> 5))) { + if (v_c != 255) { + status = wuffs_base__make_status(wuffs_cbor__error__bad_input); + goto exit; + } + v_vminor = 4194560; + if (v_indefinite_string_major_type == 3) { + v_vminor |= 19; + } + v_indefinite_string_major_type = 0; + iop_a_src += 1; + *iop_a_dst++ = wuffs_base__make_token( + (((uint64_t)(v_vminor)) << WUFFS_BASE__TOKEN__VALUE_MINOR__SHIFT) | + (((uint64_t)(1)) << WUFFS_BASE__TOKEN__LENGTH__SHIFT)); + goto label__goto_parsed_a_leaf_value__break; + } + iop_a_src += 1; + v_c_major = ((uint8_t)((v_c >> 5))); + v_c_minor = (v_c & 31); + if (v_c_minor < 24) { + v_string_length = ((uint64_t)(v_c_minor)); + } else { + while (true) { + if (v_c_minor == 24) { + if (((uint64_t)(io2_a_src - iop_a_src)) >= 1) { + v_string_length = ((uint64_t)(wuffs_base__peek_u8be__no_bounds_check(iop_a_src))); + iop_a_src += 1; + goto label__goto_have_string_length__break; + } + } else if (v_c_minor == 25) { + if (((uint64_t)(io2_a_src - iop_a_src)) >= 2) { + v_string_length = ((uint64_t)(wuffs_base__peek_u16be__no_bounds_check(iop_a_src))); + iop_a_src += 2; + goto label__goto_have_string_length__break; + } + } else if (v_c_minor == 26) { + if (((uint64_t)(io2_a_src - iop_a_src)) >= 4) { + v_string_length = ((uint64_t)(wuffs_base__peek_u32be__no_bounds_check(iop_a_src))); + iop_a_src += 4; + goto label__goto_have_string_length__break; + } + } else if (v_c_minor == 27) { + if (((uint64_t)(io2_a_src - iop_a_src)) >= 8) { + v_string_length = wuffs_base__peek_u64be__no_bounds_check(iop_a_src); + iop_a_src += 8; + goto label__goto_have_string_length__break; + } + } else { + v_string_length = 0; + goto label__goto_have_string_length__break; + } + if (iop_a_src > io1_a_src) { + iop_a_src--; + if (a_src && a_src->meta.closed) { + status = wuffs_base__make_status(wuffs_cbor__error__bad_input); + goto exit; + } + status = wuffs_base__make_status(wuffs_base__suspension__short_read); + WUFFS_BASE__COROUTINE_SUSPENSION_POINT_MAYBE_SUSPEND(3); + goto label__outer__continue; + } + status = wuffs_base__make_status(wuffs_cbor__error__internal_error_inconsistent_i_o); + goto exit; + } + label__goto_have_string_length__break:; + } + if (v_c_major == 0) { + if (v_c_minor < 26) { + *iop_a_dst++ = wuffs_base__make_token( + (((uint64_t)((14680064 | ((uint32_t)((v_string_length & 65535)))))) << WUFFS_BASE__TOKEN__VALUE_MINOR__SHIFT) | + (((uint64_t)(((uint32_t)(WUFFS_CBOR__TOKEN_LENGTHS[v_c_minor])))) << WUFFS_BASE__TOKEN__LENGTH__SHIFT)); + goto label__goto_parsed_a_leaf_value__break; + } else if (v_c_minor < 28) { + *iop_a_dst++ = wuffs_base__make_token( + (((uint64_t)((14680064 | ((uint32_t)((v_string_length >> 46)))))) << WUFFS_BASE__TOKEN__VALUE_MINOR__SHIFT) | + (((uint64_t)(1)) << WUFFS_BASE__TOKEN__CONTINUED__SHIFT) | + (((uint64_t)(0)) << WUFFS_BASE__TOKEN__LENGTH__SHIFT)); + *iop_a_dst++ = wuffs_base__make_token( + (~(v_string_length & 70368744177663) << WUFFS_BASE__TOKEN__VALUE_EXTENSION__SHIFT) | + (((uint64_t)(((uint32_t)(WUFFS_CBOR__TOKEN_LENGTHS[v_c_minor])))) << WUFFS_BASE__TOKEN__LENGTH__SHIFT)); + goto label__goto_parsed_a_leaf_value__break; + } + } else if (v_c_major == 1) { + if (v_c_minor < 26) { + *iop_a_dst++ = wuffs_base__make_token( + (((uint64_t)((12582912 | (2097151 - ((uint32_t)((v_string_length & 65535))))))) << WUFFS_BASE__TOKEN__VALUE_MINOR__SHIFT) | + (((uint64_t)(((uint32_t)(WUFFS_CBOR__TOKEN_LENGTHS[v_c_minor])))) << WUFFS_BASE__TOKEN__LENGTH__SHIFT)); + goto label__goto_parsed_a_leaf_value__break; + } else if (v_c_minor < 28) { + if (v_string_length < 9223372036854775808u) { + *iop_a_dst++ = wuffs_base__make_token( + (((uint64_t)((12582912 | (2097151 - ((uint32_t)((v_string_length >> 46))))))) << WUFFS_BASE__TOKEN__VALUE_MINOR__SHIFT) | + (((uint64_t)(1)) << WUFFS_BASE__TOKEN__CONTINUED__SHIFT) | + (((uint64_t)(0)) << WUFFS_BASE__TOKEN__LENGTH__SHIFT)); + *iop_a_dst++ = wuffs_base__make_token( + (~((18446744073709551615u - v_string_length) & 70368744177663) << WUFFS_BASE__TOKEN__VALUE_EXTENSION__SHIFT) | + (((uint64_t)(((uint32_t)(WUFFS_CBOR__TOKEN_LENGTHS[v_c_minor])))) << WUFFS_BASE__TOKEN__LENGTH__SHIFT)); + } else { + *iop_a_dst++ = wuffs_base__make_token( + (((uint64_t)(787997)) << WUFFS_BASE__TOKEN__VALUE_MAJOR__SHIFT) | + (((uint64_t)(16777216)) << WUFFS_BASE__TOKEN__VALUE_MINOR__SHIFT) | + (((uint64_t)(9)) << WUFFS_BASE__TOKEN__LENGTH__SHIFT)); + } + goto label__goto_parsed_a_leaf_value__break; + } + } else if (v_c_major == 2) { + if (v_c_minor < 28) { + if (v_string_length == 0) { + *iop_a_dst++ = wuffs_base__make_token( + (((uint64_t)(4194560)) << WUFFS_BASE__TOKEN__VALUE_MINOR__SHIFT) | + (((uint64_t)(((uint32_t)(WUFFS_CBOR__TOKEN_LENGTHS[v_c_minor])))) << WUFFS_BASE__TOKEN__LENGTH__SHIFT)); + goto label__goto_parsed_a_leaf_value__break; + } + *iop_a_dst++ = wuffs_base__make_token( + (((uint64_t)(4194560)) << WUFFS_BASE__TOKEN__VALUE_MINOR__SHIFT) | + (((uint64_t)(1)) << WUFFS_BASE__TOKEN__CONTINUED__SHIFT) | + (((uint64_t)(((uint32_t)(WUFFS_CBOR__TOKEN_LENGTHS[v_c_minor])))) << WUFFS_BASE__TOKEN__LENGTH__SHIFT)); + } else if (v_c_minor == 31) { + if (v_indefinite_string_major_type != 0) { + goto label__goto_fail__break; + } + v_indefinite_string_major_type = 2; + *iop_a_dst++ = wuffs_base__make_token( + (((uint64_t)(4194560)) << WUFFS_BASE__TOKEN__VALUE_MINOR__SHIFT) | + (((uint64_t)(1)) << WUFFS_BASE__TOKEN__CONTINUED__SHIFT) | + (((uint64_t)(1)) << WUFFS_BASE__TOKEN__LENGTH__SHIFT)); + goto label__outer__continue; + } else { + goto label__goto_fail__break; + } + label__0__continue:; + while (true) { + if (((uint64_t)(io2_a_dst - iop_a_dst)) <= 0) { + status = wuffs_base__make_status(wuffs_base__suspension__short_write); + WUFFS_BASE__COROUTINE_SUSPENSION_POINT_MAYBE_SUSPEND(4); + goto label__0__continue; + } + v_n64 = wuffs_base__u64__min(v_string_length, ((uint64_t)(io2_a_src - iop_a_src))); + v_token_length = ((uint32_t)((v_n64 & 65535))); + if (v_n64 > 65535) { + v_token_length = 65535; + } else if (v_token_length <= 0) { + if (a_src && a_src->meta.closed) { + status = wuffs_base__make_status(wuffs_cbor__error__bad_input); + goto exit; + } + status = wuffs_base__make_status(wuffs_base__suspension__short_read); + WUFFS_BASE__COROUTINE_SUSPENSION_POINT_MAYBE_SUSPEND(5); + goto label__0__continue; + } + if (((uint64_t)(io2_a_src - iop_a_src)) < ((uint64_t)(v_token_length))) { + status = wuffs_base__make_status(wuffs_cbor__error__internal_error_inconsistent_token_length); + goto exit; + } + v_string_length -= ((uint64_t)(v_token_length)); + v_continued = 0; + if ((v_string_length > 0) || (v_indefinite_string_major_type > 0)) { + v_continued = 1; + } + iop_a_src += v_token_length; + *iop_a_dst++ = wuffs_base__make_token( + (((uint64_t)(4194816)) << WUFFS_BASE__TOKEN__VALUE_MINOR__SHIFT) | + (((uint64_t)(v_continued)) << WUFFS_BASE__TOKEN__CONTINUED__SHIFT) | + (((uint64_t)(v_token_length)) << WUFFS_BASE__TOKEN__LENGTH__SHIFT)); + if (v_string_length > 0) { + goto label__0__continue; + } else if (v_indefinite_string_major_type > 0) { + goto label__outer__continue; + } + goto label__goto_parsed_a_leaf_value__break; + } + } else if (v_c_major == 3) { + if (v_c_minor < 28) { + if (v_string_length == 0) { + *iop_a_dst++ = wuffs_base__make_token( + (((uint64_t)(4194579)) << WUFFS_BASE__TOKEN__VALUE_MINOR__SHIFT) | + (((uint64_t)(((uint32_t)(WUFFS_CBOR__TOKEN_LENGTHS[v_c_minor])))) << WUFFS_BASE__TOKEN__LENGTH__SHIFT)); + goto label__goto_parsed_a_leaf_value__break; + } + *iop_a_dst++ = wuffs_base__make_token( + (((uint64_t)(4194579)) << WUFFS_BASE__TOKEN__VALUE_MINOR__SHIFT) | + (((uint64_t)(1)) << WUFFS_BASE__TOKEN__CONTINUED__SHIFT) | + (((uint64_t)(((uint32_t)(WUFFS_CBOR__TOKEN_LENGTHS[v_c_minor])))) << WUFFS_BASE__TOKEN__LENGTH__SHIFT)); + } else if (v_c_minor == 31) { + if (v_indefinite_string_major_type != 0) { + goto label__goto_fail__break; + } + v_indefinite_string_major_type = 3; + *iop_a_dst++ = wuffs_base__make_token( + (((uint64_t)(4194579)) << WUFFS_BASE__TOKEN__VALUE_MINOR__SHIFT) | + (((uint64_t)(1)) << WUFFS_BASE__TOKEN__CONTINUED__SHIFT) | + (((uint64_t)(1)) << WUFFS_BASE__TOKEN__LENGTH__SHIFT)); + goto label__outer__continue; + } else { + goto label__goto_fail__break; + } + label__1__continue:; + while (true) { + if (((uint64_t)(io2_a_dst - iop_a_dst)) <= 0) { + status = wuffs_base__make_status(wuffs_base__suspension__short_write); + WUFFS_BASE__COROUTINE_SUSPENSION_POINT_MAYBE_SUSPEND(6); + goto label__1__continue; + } + v_n64 = wuffs_base__u64__min(v_string_length, 65535); + v_n64 = ((uint64_t)(wuffs_base__utf_8__longest_valid_prefix(iop_a_src, + ((size_t)(wuffs_base__u64__min(((uint64_t)(io2_a_src - iop_a_src)), v_n64)))))); + v_token_length = ((uint32_t)((v_n64 & 65535))); + if (v_token_length <= 0) { + if ((a_src && a_src->meta.closed) || (((uint64_t)(io2_a_src - iop_a_src)) >= 4)) { + status = wuffs_base__make_status(wuffs_cbor__error__bad_input); + goto exit; + } + status = wuffs_base__make_status(wuffs_base__suspension__short_read); + WUFFS_BASE__COROUTINE_SUSPENSION_POINT_MAYBE_SUSPEND(7); + goto label__1__continue; + } + if (((uint64_t)(io2_a_src - iop_a_src)) < ((uint64_t)(v_token_length))) { + status = wuffs_base__make_status(wuffs_cbor__error__internal_error_inconsistent_token_length); + goto exit; + } + v_string_length -= ((uint64_t)(v_token_length)); + v_continued = 0; + if ((v_string_length > 0) || (v_indefinite_string_major_type > 0)) { + v_continued = 1; + } + iop_a_src += v_token_length; + *iop_a_dst++ = wuffs_base__make_token( + (((uint64_t)(4194819)) << WUFFS_BASE__TOKEN__VALUE_MINOR__SHIFT) | + (((uint64_t)(v_continued)) << WUFFS_BASE__TOKEN__CONTINUED__SHIFT) | + (((uint64_t)(v_token_length)) << WUFFS_BASE__TOKEN__LENGTH__SHIFT)); + if (v_string_length > 0) { + goto label__1__continue; + } else if (v_indefinite_string_major_type > 0) { + goto label__outer__continue; + } + goto label__goto_parsed_a_leaf_value__break; + } + } else if (v_c_major == 4) { + if (WUFFS_CBOR__TOKEN_LENGTHS[v_c_minor] == 0) { + goto label__goto_fail__break; + } else if (v_depth >= 1024) { + v_token_length = ((uint32_t)(WUFFS_CBOR__TOKEN_LENGTHS[v_c_minor])); + while ((v_token_length > 0) && (iop_a_src > io1_a_src)) { + iop_a_src--; + v_token_length -= 1; + } + status = wuffs_base__make_status(wuffs_cbor__error__unsupported_recursion_depth); + goto exit; + } + v_vminor = 2105361; + v_vminor_alt = 2101282; + if (v_depth > 0) { + v_stack_byte = ((v_depth - 1) / 16); + v_stack_bit = (((v_depth - 1) & 15) * 2); + if (0 == (self->private_data.f_stack[v_stack_byte] & (((uint32_t)(1)) << v_stack_bit))) { + v_vminor = 2105377; + v_vminor_alt = 2105378; + } else { + v_vminor = 2105409; + v_vminor_alt = 2113570; + } + } + *iop_a_dst++ = wuffs_base__make_token( + (((uint64_t)(v_vminor)) << WUFFS_BASE__TOKEN__VALUE_MINOR__SHIFT) | + (((uint64_t)(((uint32_t)(WUFFS_CBOR__TOKEN_LENGTHS[v_c_minor])))) << WUFFS_BASE__TOKEN__LENGTH__SHIFT)); + if (v_c_minor == 0) { + *iop_a_dst++ = wuffs_base__make_token( + (((uint64_t)(v_vminor_alt)) << WUFFS_BASE__TOKEN__VALUE_MINOR__SHIFT) | + (((uint64_t)(0)) << WUFFS_BASE__TOKEN__LENGTH__SHIFT)); + goto label__goto_parsed_a_leaf_value__break; + } + v_stack_byte = (v_depth / 16); + v_stack_bit = ((v_depth & 15) * 2); + self->private_data.f_stack[v_stack_byte] &= (4294967295 ^ (((uint32_t)(3)) << v_stack_bit)); + self->private_data.f_container_num_remaining[v_depth] = v_string_length; + v_depth += 1; + v_tagged = false; + goto label__outer__continue; + } else if (v_c_major == 5) { + if (WUFFS_CBOR__TOKEN_LENGTHS[v_c_minor] == 0) { + goto label__goto_fail__break; + } else if (v_depth >= 1024) { + v_token_length = ((uint32_t)(WUFFS_CBOR__TOKEN_LENGTHS[v_c_minor])); + while ((v_token_length > 0) && (iop_a_src > io1_a_src)) { + iop_a_src--; + v_token_length -= 1; + } + status = wuffs_base__make_status(wuffs_cbor__error__unsupported_recursion_depth); + goto exit; + } + v_vminor = 2113553; + v_vminor_alt = 2101314; + if (v_depth > 0) { + v_stack_byte = ((v_depth - 1) / 16); + v_stack_bit = (((v_depth - 1) & 15) * 2); + if (0 == (self->private_data.f_stack[v_stack_byte] & (((uint32_t)(1)) << v_stack_bit))) { + v_vminor = 2113569; + v_vminor_alt = 2105410; + } else { + v_vminor = 2113601; + v_vminor_alt = 2113602; + } + } + *iop_a_dst++ = wuffs_base__make_token( + (((uint64_t)(v_vminor)) << WUFFS_BASE__TOKEN__VALUE_MINOR__SHIFT) | + (((uint64_t)(((uint32_t)(WUFFS_CBOR__TOKEN_LENGTHS[v_c_minor])))) << WUFFS_BASE__TOKEN__LENGTH__SHIFT)); + if (v_c_minor == 0) { + *iop_a_dst++ = wuffs_base__make_token( + (((uint64_t)(v_vminor_alt)) << WUFFS_BASE__TOKEN__VALUE_MINOR__SHIFT) | + (((uint64_t)(0)) << WUFFS_BASE__TOKEN__LENGTH__SHIFT)); + goto label__goto_parsed_a_leaf_value__break; + } + v_stack_byte = (v_depth / 16); + v_stack_bit = ((v_depth & 15) * 2); + self->private_data.f_stack[v_stack_byte] |= (((uint32_t)(3)) << v_stack_bit); + self->private_data.f_container_num_remaining[v_depth] = v_string_length; + v_depth += 1; + v_tagged = false; + goto label__outer__continue; + } else if (v_c_major == 6) { + if (v_c_minor >= 28) { + goto label__goto_fail__break; + } + if (v_string_length < 262144) { + *iop_a_dst++ = wuffs_base__make_token( + (((uint64_t)(787997)) << WUFFS_BASE__TOKEN__VALUE_MAJOR__SHIFT) | + (((uint64_t)((4194304 | ((uint32_t)(v_string_length))))) << WUFFS_BASE__TOKEN__VALUE_MINOR__SHIFT) | + (((uint64_t)(((uint32_t)(WUFFS_CBOR__TOKEN_LENGTHS[v_c_minor])))) << WUFFS_BASE__TOKEN__LENGTH__SHIFT)); + } else { + *iop_a_dst++ = wuffs_base__make_token( + (((uint64_t)(787997)) << WUFFS_BASE__TOKEN__VALUE_MAJOR__SHIFT) | + (((uint64_t)((4194304 | ((uint32_t)((v_string_length >> 46)))))) << WUFFS_BASE__TOKEN__VALUE_MINOR__SHIFT) | + (((uint64_t)(1)) << WUFFS_BASE__TOKEN__CONTINUED__SHIFT) | + (((uint64_t)(0)) << WUFFS_BASE__TOKEN__LENGTH__SHIFT)); + *iop_a_dst++ = wuffs_base__make_token( + (~(v_string_length & 70368744177663) << WUFFS_BASE__TOKEN__VALUE_EXTENSION__SHIFT) | + (((uint64_t)(((uint32_t)(WUFFS_CBOR__TOKEN_LENGTHS[v_c_minor])))) << WUFFS_BASE__TOKEN__LENGTH__SHIFT)); + } + v_tagged = true; + goto label__outer__continue; + } else if (v_c_major == 7) { + if (v_c_minor < 20) { + *iop_a_dst++ = wuffs_base__make_token( + (((uint64_t)(787997)) << WUFFS_BASE__TOKEN__VALUE_MAJOR__SHIFT) | + (((uint64_t)((8388608 | ((uint32_t)((v_string_length & 255)))))) << WUFFS_BASE__TOKEN__VALUE_MINOR__SHIFT) | + (((uint64_t)(1)) << WUFFS_BASE__TOKEN__LENGTH__SHIFT)); + goto label__goto_parsed_a_leaf_value__break; + } else if (v_c_minor < 24) { + *iop_a_dst++ = wuffs_base__make_token( + (((uint64_t)(WUFFS_CBOR__LITERALS[(v_c_minor & 3)])) << WUFFS_BASE__TOKEN__VALUE_MINOR__SHIFT) | + (((uint64_t)(1)) << WUFFS_BASE__TOKEN__LENGTH__SHIFT)); + goto label__goto_parsed_a_leaf_value__break; + } else if (v_c_minor == 24) { + if (v_string_length < 24) { + if ( ! (iop_a_src > io1_a_src)) { + status = wuffs_base__make_status(wuffs_cbor__error__internal_error_inconsistent_i_o); + goto exit; + } + iop_a_src--; + goto label__goto_fail__break; + } + *iop_a_dst++ = wuffs_base__make_token( + (((uint64_t)(787997)) << WUFFS_BASE__TOKEN__VALUE_MAJOR__SHIFT) | + (((uint64_t)((8388608 | ((uint32_t)((v_string_length & 255)))))) << WUFFS_BASE__TOKEN__VALUE_MINOR__SHIFT) | + (((uint64_t)(2)) << WUFFS_BASE__TOKEN__LENGTH__SHIFT)); + goto label__goto_parsed_a_leaf_value__break; + } else if (v_c_minor < 28) { + *iop_a_dst++ = wuffs_base__make_token( + (((uint64_t)(10490113)) << WUFFS_BASE__TOKEN__VALUE_MINOR__SHIFT) | + (((uint64_t)(((uint32_t)(WUFFS_CBOR__TOKEN_LENGTHS[v_c_minor])))) << WUFFS_BASE__TOKEN__LENGTH__SHIFT)); + goto label__goto_parsed_a_leaf_value__break; + } else if (v_c_minor == 31) { + if (v_tagged || (v_depth <= 0)) { + goto label__goto_fail__break; + } + v_depth -= 1; + if (self->private_data.f_container_num_remaining[v_depth] != 0) { + goto label__goto_fail__break; + } + v_stack_byte = (v_depth / 16); + v_stack_bit = ((v_depth & 15) * 2); + v_stack_val = (3 & (self->private_data.f_stack[v_stack_byte] >> v_stack_bit)); + if (v_stack_val == 1) { + goto label__goto_fail__break; + } + if (v_stack_val != 3) { + v_vminor_alt = 2097186; + } else { + v_vminor_alt = 2097218; + } + if (v_depth <= 0) { + v_vminor_alt |= 4096; + } else { + v_stack_byte = ((v_depth - 1) / 16); + v_stack_bit = (((v_depth - 1) & 15) * 2); + if (0 == (self->private_data.f_stack[v_stack_byte] & (((uint32_t)(1)) << v_stack_bit))) { + v_vminor_alt |= 8192; + } else { + v_vminor_alt |= 16384; + } + } + *iop_a_dst++ = wuffs_base__make_token( + (((uint64_t)(v_vminor_alt)) << WUFFS_BASE__TOKEN__VALUE_MINOR__SHIFT) | + (((uint64_t)(1)) << WUFFS_BASE__TOKEN__LENGTH__SHIFT)); + goto label__goto_parsed_a_leaf_value__break; + } + } + goto label__goto_fail__break; + } + label__goto_fail__break:; + if (iop_a_src > io1_a_src) { + iop_a_src--; + status = wuffs_base__make_status(wuffs_cbor__error__bad_input); + goto exit; + } + status = wuffs_base__make_status(wuffs_cbor__error__internal_error_inconsistent_i_o); + goto exit; + } + label__goto_parsed_a_leaf_value__break:; + v_tagged = false; + while (v_depth > 0) { + v_stack_byte = ((v_depth - 1) / 16); + v_stack_bit = (((v_depth - 1) & 15) * 2); + self->private_data.f_stack[v_stack_byte] ^= (((uint32_t)(1)) << (v_stack_bit + 1)); + if (1 == (3 & (self->private_data.f_stack[v_stack_byte] >> v_stack_bit))) { + goto label__outer__continue; + } + if (self->private_data.f_container_num_remaining[(v_depth - 1)] <= 0) { + goto label__outer__continue; + } + self->private_data.f_container_num_remaining[(v_depth - 1)] -= 1; + if (self->private_data.f_container_num_remaining[(v_depth - 1)] > 0) { + goto label__outer__continue; + } + label__2__continue:; + while (((uint64_t)(io2_a_dst - iop_a_dst)) <= 0) { + status = wuffs_base__make_status(wuffs_base__suspension__short_write); + WUFFS_BASE__COROUTINE_SUSPENSION_POINT_MAYBE_SUSPEND(8); + goto label__2__continue; + } + v_depth -= 1; + v_stack_byte = (v_depth / 16); + v_stack_bit = ((v_depth & 15) * 2); + if (0 == (self->private_data.f_stack[v_stack_byte] & (((uint32_t)(1)) << v_stack_bit))) { + v_vminor_alt = 2097186; + } else { + v_vminor_alt = 2097218; + } + if (v_depth <= 0) { + v_vminor_alt |= 4096; + } else { + v_stack_byte = ((v_depth - 1) / 16); + v_stack_bit = (((v_depth - 1) & 15) * 2); + if (0 == (self->private_data.f_stack[v_stack_byte] & (((uint32_t)(1)) << v_stack_bit))) { + v_vminor_alt |= 8192; + } else { + v_vminor_alt |= 16384; + } + } + *iop_a_dst++ = wuffs_base__make_token( + (((uint64_t)(v_vminor_alt)) << WUFFS_BASE__TOKEN__VALUE_MINOR__SHIFT) | + (((uint64_t)(0)) << WUFFS_BASE__TOKEN__LENGTH__SHIFT)); + } + goto label__outer__break; + } + label__outer__break:; + self->private_impl.f_end_of_data = true; + + ok: + self->private_impl.p_decode_tokens[0] = 0; + goto exit; + } + + goto suspend; + suspend: + self->private_impl.p_decode_tokens[0] = wuffs_base__status__is_suspension(&status) ? coro_susp_point : 0; + self->private_impl.active_coroutine = wuffs_base__status__is_suspension(&status) ? 1 : 0; + self->private_data.s_decode_tokens[0].v_string_length = v_string_length; + self->private_data.s_decode_tokens[0].v_depth = v_depth; + self->private_data.s_decode_tokens[0].v_tagged = v_tagged; + self->private_data.s_decode_tokens[0].v_indefinite_string_major_type = v_indefinite_string_major_type; + + goto exit; + exit: + if (a_dst && a_dst->data.ptr) { + a_dst->meta.wi = ((size_t)(iop_a_dst - a_dst->data.ptr)); + } + if (a_src && a_src->data.ptr) { + a_src->meta.ri = ((size_t)(iop_a_src - a_src->data.ptr)); + } + + if (wuffs_base__status__is_error(&status)) { + self->private_impl.magic = WUFFS_BASE__DISABLED; + } + return status; +} + +#endif // !defined(WUFFS_CONFIG__MODULES) || defined(WUFFS_CONFIG__MODULE__CBOR) + +#if !defined(WUFFS_CONFIG__MODULES) || defined(WUFFS_CONFIG__MODULE__CRC32) + +// ---------------- Status Codes Implementations + +// ---------------- Private Consts + +static const uint32_t +WUFFS_CRC32__IEEE_TABLE[16][256] WUFFS_BASE__POTENTIALLY_UNUSED = { + { + 0, 1996959894, 3993919788, 2567524794, 124634137, 1886057615, 3915621685, 2657392035, + 249268274, 2044508324, 3772115230, 2547177864, 162941995, 2125561021, 3887607047, 2428444049, + 498536548, 1789927666, 4089016648, 2227061214, 450548861, 1843258603, 4107580753, 2211677639, + 325883990, 1684777152, 4251122042, 2321926636, 335633487, 1661365465, 4195302755, 2366115317, + 997073096, 1281953886, 3579855332, 2724688242, 1006888145, 1258607687, 3524101629, 2768942443, + 901097722, 1119000684, 3686517206, 2898065728, 853044451, 1172266101, 3705015759, 2882616665, + 651767980, 1373503546, 3369554304, 3218104598, 565507253, 1454621731, 3485111705, 3099436303, + 671266974, 1594198024, 3322730930, 2970347812, 795835527, 1483230225, 3244367275, 3060149565, + 1994146192, 31158534, 2563907772, 4023717930, 1907459465, 112637215, 2680153253, 3904427059, + 2013776290, 251722036, 2517215374, 3775830040, 2137656763, 141376813, 2439277719, 3865271297, + 1802195444, 476864866, 2238001368, 4066508878, 1812370925, 453092731, 2181625025, 4111451223, + 1706088902, 314042704, 2344532202, 4240017532, 1658658271, 366619977, 2362670323, 4224994405, + 1303535960, 984961486, 2747007092, 3569037538, 1256170817, 1037604311, 2765210733, 3554079995, + 1131014506, 879679996, 2909243462, 3663771856, 1141124467, 855842277, 2852801631, 3708648649, + 1342533948, 654459306, 3188396048, 3373015174, 1466479909, 544179635, 3110523913, 3462522015, + 1591671054, 702138776, 2966460450, 3352799412, 1504918807, 783551873, 3082640443, 3233442989, + 3988292384, 2596254646, 62317068, 1957810842, 3939845945, 2647816111, 81470997, 1943803523, + 3814918930, 2489596804, 225274430, 2053790376, 3826175755, 2466906013, 167816743, 2097651377, + 4027552580, 2265490386, 503444072, 1762050814, 4150417245, 2154129355, 426522225, 1852507879, + 4275313526, 2312317920, 282753626, 1742555852, 4189708143, 2394877945, 397917763, 1622183637, + 3604390888, 2714866558, 953729732, 1340076626, 3518719985, 2797360999, 1068828381, 1219638859, + 3624741850, 2936675148, 906185462, 1090812512, 3747672003, 2825379669, 829329135, 1181335161, + 3412177804, 3160834842, 628085408, 1382605366, 3423369109, 3138078467, 570562233, 1426400815, + 3317316542, 2998733608, 733239954, 1555261956, 3268935591, 3050360625, 752459403, 1541320221, + 2607071920, 3965973030, 1969922972, 40735498, 2617837225, 3943577151, 1913087877, 83908371, + 2512341634, 3803740692, 2075208622, 213261112, 2463272603, 3855990285, 2094854071, 198958881, + 2262029012, 4057260610, 1759359992, 534414190, 2176718541, 4139329115, 1873836001, 414664567, + 2282248934, 4279200368, 1711684554, 285281116, 2405801727, 4167216745, 1634467795, 376229701, + 2685067896, 3608007406, 1308918612, 956543938, 2808555105, 3495958263, 1231636301, 1047427035, + 2932959818, 3654703836, 1088359270, 936918000, 2847714899, 3736837829, 1202900863, 817233897, + 3183342108, 3401237130, 1404277552, 615818150, 3134207493, 3453421203, 1423857449, 601450431, + 3009837614, 3294710456, 1567103746, 711928724, 3020668471, 3272380065, 1510334235, 755167117, + }, { + 0, 421212481, 842424962, 724390851, 1684849924, 2105013317, 1448781702, 1329698503, + 3369699848, 3519200073, 4210026634, 3824474571, 2897563404, 3048111693, 2659397006, 2274893007, + 1254232657, 1406739216, 2029285587, 1643069842, 783210325, 934667796, 479770071, 92505238, + 2182846553, 2600511768, 2955803355, 2838940570, 3866582365, 4285295644, 3561045983, 3445231262, + 2508465314, 2359236067, 2813478432, 3198777185, 4058571174, 3908292839, 3286139684, 3670389349, + 1566420650, 1145479147, 1869335592, 1987116393, 959540142, 539646703, 185010476, 303839341, + 3745920755, 3327985586, 3983561841, 4100678960, 3140154359, 2721170102, 2300350837, 2416418868, + 396344571, 243568058, 631889529, 1018359608, 1945336319, 1793607870, 1103436669, 1490954812, + 4034481925, 3915546180, 3259968903, 3679722694, 2484439553, 2366552896, 2787371139, 3208174018, + 950060301, 565965900, 177645455, 328046286, 1556873225, 1171730760, 1861902987, 2011255754, + 3132841300, 2745199637, 2290958294, 2442530455, 3738671184, 3352078609, 3974232786, 4126854035, + 1919080284, 1803150877, 1079293406, 1498383519, 370020952, 253043481, 607678682, 1025720731, + 1711106983, 2095471334, 1472923941, 1322268772, 26324643, 411738082, 866634785, 717028704, + 2904875439, 3024081134, 2668790573, 2248782444, 3376948395, 3495106026, 4219356713, 3798300520, + 792689142, 908347575, 487136116, 68299317, 1263779058, 1380486579, 2036719216, 1618931505, + 3890672638, 4278043327, 3587215740, 3435896893, 2206873338, 2593195963, 2981909624, 2829542713, + 998479947, 580430090, 162921161, 279890824, 1609522511, 1190423566, 1842954189, 1958874764, + 4082766403, 3930137346, 3245109441, 3631694208, 2536953671, 2385372678, 2768287173, 3155920004, + 1900120602, 1750776667, 1131931800, 1517083097, 355290910, 204897887, 656092572, 1040194781, + 3113746450, 2692952403, 2343461520, 2461357009, 3723805974, 3304059991, 4022511508, 4141455061, + 2919742697, 3072101800, 2620513899, 2234183466, 3396041197, 3547351212, 4166851439, 3779471918, + 1725839073, 2143618976, 1424512099, 1307796770, 45282277, 464110244, 813994343, 698327078, + 3838160568, 4259225593, 3606301754, 3488152955, 2158586812, 2578602749, 2996767038, 2877569151, + 740041904, 889656817, 506086962, 120682355, 1215357364, 1366020341, 2051441462, 1667084919, + 3422213966, 3538019855, 4190942668, 3772220557, 2945847882, 3062702859, 2644537544, 2226864521, + 52649286, 439905287, 823476164, 672009861, 1733269570, 2119477507, 1434057408, 1281543041, + 2167981343, 2552493150, 3004082077, 2853541596, 3847487515, 4233048410, 3613549209, 3464057816, + 1239502615, 1358593622, 2077699477, 1657543892, 764250643, 882293586, 532408465, 111204816, + 1585378284, 1197851309, 1816695150, 1968414767, 974272232, 587794345, 136598634, 289367339, + 2527558116, 2411481253, 2760973158, 3179948583, 4073438432, 3956313505, 3237863010, 3655790371, + 347922877, 229101820, 646611775, 1066513022, 1892689081, 1774917112, 1122387515, 1543337850, + 3697634229, 3313392372, 3998419255, 4148705398, 3087642289, 2702352368, 2319436851, 2468674930, + }, { + 0, 29518391, 59036782, 38190681, 118073564, 114017003, 76381362, 89069189, + 236147128, 265370511, 228034006, 206958561, 152762724, 148411219, 178138378, 190596925, + 472294256, 501532999, 530741022, 509615401, 456068012, 451764635, 413917122, 426358261, + 305525448, 334993663, 296822438, 275991697, 356276756, 352202787, 381193850, 393929805, + 944588512, 965684439, 1003065998, 973863097, 1061482044, 1049003019, 1019230802, 1023561829, + 912136024, 933002607, 903529270, 874031361, 827834244, 815125939, 852716522, 856752605, + 611050896, 631869351, 669987326, 640506825, 593644876, 580921211, 551983394, 556069653, + 712553512, 733666847, 704405574, 675154545, 762387700, 749958851, 787859610, 792175277, + 1889177024, 1901651959, 1931368878, 1927033753, 2006131996, 1985040171, 1947726194, 1976933189, + 2122964088, 2135668303, 2098006038, 2093965857, 2038461604, 2017599123, 2047123658, 2076625661, + 1824272048, 1836991623, 1866005214, 1861914857, 1807058540, 1786244187, 1748062722, 1777547317, + 1655668488, 1668093247, 1630251878, 1625932113, 1705433044, 1684323811, 1713505210, 1742760333, + 1222101792, 1226154263, 1263738702, 1251046777, 1339974652, 1310460363, 1281013650, 1301863845, + 1187289752, 1191637167, 1161842422, 1149379777, 1103966788, 1074747507, 1112139306, 1133218845, + 1425107024, 1429406311, 1467333694, 1454888457, 1408811148, 1379576507, 1350309090, 1371438805, + 1524775400, 1528845279, 1499917702, 1487177649, 1575719220, 1546255107, 1584350554, 1605185389, + 3778354048, 3774312887, 3803303918, 3816007129, 3862737756, 3892238699, 3854067506, 3833203973, + 4012263992, 4007927823, 3970080342, 3982554209, 3895452388, 3924658387, 3953866378, 3932773565, + 4245928176, 4241609415, 4271336606, 4283762345, 4196012076, 4225268251, 4187931714, 4166823541, + 4076923208, 4072833919, 4035198246, 4047918865, 4094247316, 4123732899, 4153251322, 4132437965, + 3648544096, 3636082519, 3673983246, 3678331705, 3732010428, 3753090955, 3723829714, 3694611429, + 3614117080, 3601426159, 3572488374, 3576541825, 3496125444, 3516976691, 3555094634, 3525581405, + 3311336976, 3298595879, 3336186494, 3340255305, 3260503756, 3281337595, 3251864226, 3222399125, + 3410866088, 3398419871, 3368647622, 3372945905, 3427010420, 3448139075, 3485520666, 3456284973, + 2444203584, 2423127159, 2452308526, 2481530905, 2527477404, 2539934891, 2502093554, 2497740997, + 2679949304, 2659102159, 2620920726, 2650438049, 2562027300, 2574714131, 2603727690, 2599670141, + 2374579504, 2353749767, 2383274334, 2412743529, 2323684844, 2336421851, 2298759554, 2294686645, + 2207933576, 2186809023, 2149495014, 2178734801, 2224278612, 2236720739, 2266437690, 2262135309, + 2850214048, 2820717207, 2858812622, 2879680249, 2934667388, 2938704459, 2909776914, 2897069605, + 2817622296, 2788420399, 2759153014, 2780249921, 2700618180, 2704950259, 2742877610, 2730399645, + 3049550800, 3020298727, 3057690558, 3078802825, 2999835404, 3004150075, 2974355298, 2961925461, + 3151438440, 3121956959, 3092510214, 3113327665, 3168701108, 3172786307, 3210370778, 3197646061, + }, { + 0, 3099354981, 2852767883, 313896942, 2405603159, 937357362, 627793884, 2648127673, + 3316918511, 2097696650, 1874714724, 3607201537, 1255587768, 4067088605, 3772741427, 1482887254, + 1343838111, 3903140090, 4195393300, 1118632049, 3749429448, 1741137837, 1970407491, 3452858150, + 2511175536, 756094997, 1067759611, 2266550430, 449832999, 2725482306, 2965774508, 142231497, + 2687676222, 412010587, 171665333, 2995192016, 793786473, 2548850444, 2237264098, 1038456711, + 1703315409, 3711623348, 3482275674, 1999841343, 3940814982, 1381529571, 1089329165, 4166106984, + 4029413537, 1217896388, 1512189994, 3802027855, 2135519222, 3354724499, 3577784189, 1845280792, + 899665998, 2367928107, 2677414085, 657096608, 3137160985, 37822588, 284462994, 2823350519, + 2601801789, 598228824, 824021174, 2309093331, 343330666, 2898962447, 3195996129, 113467524, + 1587572946, 3860600759, 4104763481, 1276501820, 3519211397, 1769898208, 2076913422, 3279374443, + 3406630818, 1941006535, 1627703081, 3652755532, 1148164341, 4241751952, 3999682686, 1457141531, + 247015245, 3053797416, 2763059142, 470583459, 2178658330, 963106687, 735213713, 2473467892, + 992409347, 2207944806, 2435792776, 697522413, 3024379988, 217581361, 508405983, 2800865210, + 4271038444, 1177467017, 1419450215, 3962007554, 1911572667, 3377213406, 3690561584, 1665525589, + 1799331996, 3548628985, 3241568279, 2039091058, 3831314379, 1558270126, 1314193216, 4142438437, + 2928380019, 372764438, 75645176, 3158189981, 568925988, 2572515393, 2346768303, 861712586, + 3982079547, 1441124702, 1196457648, 4293663189, 1648042348, 3666298377, 3358779879, 1888390786, + 686661332, 2421291441, 2196002399, 978858298, 2811169155, 523464422, 226935048, 3040519789, + 3175145892, 100435649, 390670639, 2952089162, 841119475, 2325614998, 2553003640, 546822429, + 2029308235, 3225988654, 3539796416, 1782671013, 4153826844, 1328167289, 1570739863, 3844338162, + 1298864389, 4124540512, 3882013070, 1608431339, 3255406162, 2058742071, 1744848601, 3501990332, + 2296328682, 811816591, 584513889, 2590678532, 129869501, 3204563416, 2914283062, 352848211, + 494030490, 2781751807, 3078325777, 264757620, 2450577869, 715964072, 941166918, 2158327331, + 3636881013, 1618608400, 1926213374, 3396585883, 1470427426, 4011365959, 4255988137, 1158766284, + 1984818694, 3471935843, 3695453837, 1693991400, 4180638033, 1100160564, 1395044826, 3952793279, + 3019491049, 189112716, 435162722, 2706139399, 1016811966, 2217162459, 2526189877, 774831696, + 643086745, 2666061564, 2354934034, 887166583, 2838900430, 294275499, 54519365, 3145957664, + 3823145334, 1532818963, 1240029693, 4048895640, 1820460577, 3560857924, 3331051178, 2117577167, + 3598663992, 1858283101, 2088143283, 3301633750, 1495127663, 3785470218, 4078182116, 1269332353, + 332098007, 2876706482, 3116540252, 25085497, 2628386432, 605395429, 916469259, 2384220526, + 2254837415, 1054503362, 745528876, 2496903497, 151290352, 2981684885, 2735556987, 464596510, + 1137851976, 4218313005, 3923506883, 1365741990, 3434129695, 1946996346, 1723425172, 3724871409, + }, { + 0, 1029712304, 2059424608, 1201699536, 4118849216, 3370159984, 2403399072, 2988497936, + 812665793, 219177585, 1253054625, 2010132753, 3320900865, 4170237105, 3207642721, 2186319825, + 1625331586, 1568718386, 438355170, 658566482, 2506109250, 2818578674, 4020265506, 3535817618, + 1351670851, 1844508147, 709922595, 389064339, 2769320579, 2557498163, 3754961379, 3803185235, + 3250663172, 4238411444, 3137436772, 2254525908, 876710340, 153198708, 1317132964, 1944187668, + 4054934725, 3436268917, 2339452837, 3054575125, 70369797, 961670069, 2129760613, 1133623509, + 2703341702, 2621542710, 3689016294, 3867263574, 1419845190, 1774270454, 778128678, 318858390, + 2438067015, 2888948471, 3952189479, 3606153623, 1691440519, 1504803895, 504432359, 594620247, + 1492342857, 1704161785, 573770537, 525542041, 2910060169, 2417219385, 3618876905, 3939730521, + 1753420680, 1440954936, 306397416, 790849880, 2634265928, 2690882808, 3888375336, 3668168600, + 940822475, 91481723, 1121164459, 2142483739, 3448989963, 4042473659, 3075684971, 2318603227, + 140739594, 889433530, 1923340138, 1338244826, 4259521226, 3229813626, 2267247018, 3124975642, + 2570221389, 2756861693, 3824297005, 3734113693, 1823658381, 1372780605, 376603373, 722643805, + 2839690380, 2485261628, 3548540908, 4007806556, 1556257356, 1638052860, 637716780, 459464860, + 4191346895, 3300051327, 2199040943, 3195181599, 206718479, 825388991, 1989285231, 1274166495, + 3382881038, 4106388158, 3009607790, 2382549470, 1008864718, 21111934, 1189240494, 2072147742, + 2984685714, 2357631266, 3408323570, 4131834434, 1147541074, 2030452706, 1051084082, 63335554, + 2174155603, 3170292451, 4216760371, 3325460867, 1947622803, 1232499747, 248909555, 867575619, + 3506841360, 3966111392, 2881909872, 2527485376, 612794832, 434546784, 1581699760, 1663499008, + 3782634705, 3692447073, 2612412337, 2799048193, 351717905, 697754529, 1849071985, 1398190273, + 1881644950, 1296545318, 182963446, 931652934, 2242328918, 3100053734, 4284967478, 3255255942, + 1079497815, 2100821479, 983009079, 133672583, 3050795671, 2293717799, 3474399735, 4067887175, + 281479188, 765927844, 1778867060, 1466397380, 3846680276, 3626469220, 2676489652, 2733102084, + 548881365, 500656741, 1517752501, 1729575173, 3577210133, 3898068133, 2952246901, 2459410373, + 3910527195, 3564487019, 2480257979, 2931134987, 479546907, 569730987, 1716854139, 1530213579, + 3647316762, 3825568426, 2745561210, 2663766474, 753206746, 293940330, 1445287610, 1799716618, + 2314567513, 3029685993, 4080348217, 3461678473, 2088098201, 1091956777, 112560889, 1003856713, + 3112514712, 2229607720, 3276105720, 4263857736, 1275433560, 1902492648, 918929720, 195422344, + 685033439, 364179055, 1377080511, 1869921551, 3713294623, 3761522863, 2811507327, 2599689167, + 413436958, 633644462, 1650777982, 1594160846, 3978570462, 3494118254, 2548332990, 2860797966, + 1211387997, 1968470509, 854852413, 261368461, 3182753437, 2161434413, 3346310653, 4195650637, + 2017729436, 1160000044, 42223868, 1071931724, 2378480988, 2963576044, 4144295484, 3395602316, + }, { + 0, 3411858341, 1304994059, 2257875630, 2609988118, 1355649459, 3596215069, 486879416, + 3964895853, 655315400, 2711298918, 1791488195, 2009251963, 3164476382, 973758832, 4048990933, + 64357019, 3364540734, 1310630800, 2235723829, 2554806413, 1394316072, 3582976390, 517157411, + 4018503926, 618222419, 2722963965, 1762783832, 1947517664, 3209171269, 970744811, 4068520014, + 128714038, 3438335635, 1248109629, 2167961496, 2621261600, 1466012805, 3522553387, 447296910, + 3959392091, 547575038, 2788632144, 1835791861, 1886307661, 3140622056, 1034314822, 4143626211, + 75106221, 3475428360, 1236444838, 2196665603, 2682996155, 1421317662, 3525567664, 427767573, + 3895035328, 594892389, 2782995659, 1857943406, 1941489622, 3101955187, 1047553757, 4113347960, + 257428076, 3288652233, 1116777319, 2311878850, 2496219258, 1603640287, 3640781169, 308099796, + 3809183745, 676813732, 2932025610, 1704983215, 2023410199, 3016104370, 894593820, 4262377657, + 210634999, 3352484690, 1095150076, 2316991065, 2535410401, 1547934020, 3671583722, 294336591, + 3772615322, 729897279, 2903845777, 1716123700, 2068629644, 2953845545, 914647431, 4258839074, + 150212442, 3282623743, 1161604689, 2388688372, 2472889676, 1480171241, 3735940167, 368132066, + 3836185911, 805002898, 2842635324, 1647574937, 2134298401, 3026852996, 855535146, 4188192143, + 186781121, 3229539940, 1189784778, 2377547631, 2427670487, 1542429810, 3715886812, 371670393, + 3882979244, 741170185, 2864262823, 1642462466, 2095107514, 3082559007, 824732849, 4201955092, + 514856152, 3589064573, 1400419795, 2552522358, 2233554638, 1316849003, 3370776517, 62202976, + 4075001525, 968836368, 3207280574, 1954014235, 1769133219, 2720925446, 616199592, 4024870413, + 493229635, 3594175974, 1353627464, 2616354029, 2264355925, 1303087088, 3409966430, 6498043, + 4046820398, 979978123, 3170710821, 2007099008, 1789187640, 2717386141, 661419827, 3962610838, + 421269998, 3527459403, 1423225061, 2676515648, 2190300152, 1238466653, 3477467891, 68755798, + 4115633027, 1041448998, 3095868040, 1943789869, 1860096405, 2776760880, 588673182, 3897205563, + 449450869, 3516317904, 1459794558, 2623431131, 2170245475, 1242006214, 3432247400, 131015629, + 4137259288, 1036337853, 3142660115, 1879958454, 1829294862, 2790523051, 549483013, 3952910752, + 300424884, 3669282065, 1545650111, 2541513754, 2323209378, 1092980487, 3350330793, 216870412, + 4256931033, 921128828, 2960342482, 2066738807, 1714085583, 2910195050, 736264132, 3770592353, + 306060335, 3647131530, 1610005796, 2494197377, 2309971513, 1123257756, 3295149874, 255536279, + 4268596802, 892423655, 3013951305, 2029645036, 1711070292, 2929725425, 674528607, 3815288570, + 373562242, 3709388839, 1535949449, 2429577516, 2379569556, 1183418929, 3223189663, 188820282, + 4195850735, 827017802, 3084859620, 2089020225, 1636228089, 2866415708, 743340786, 3876759895, + 361896217, 3738094268, 1482340370, 2466671543, 2382584591, 1163888810, 3284924932, 144124321, + 4190215028, 849168593, 3020503679, 2136336858, 1649465698, 2836138695, 798521449, 3838094284, + }, { + 0, 2792819636, 2543784233, 837294749, 4098827283, 1379413927, 1674589498, 3316072078, + 871321191, 2509784531, 2758827854, 34034938, 3349178996, 1641505216, 1346337629, 4131942633, + 1742642382, 3249117050, 4030828007, 1446413907, 2475800797, 904311657, 68069876, 2725880384, + 1412551337, 4064729373, 3283010432, 1708771380, 2692675258, 101317902, 937551763, 2442587175, + 3485284764, 1774858792, 1478633653, 4266992385, 1005723023, 2642744891, 2892827814, 169477906, + 4233263099, 1512406095, 1808623314, 3451546982, 136139752, 2926205020, 2676114113, 972376437, + 2825102674, 236236518, 1073525883, 2576072655, 1546420545, 4200303349, 3417542760, 1841601500, + 2609703733, 1039917185, 202635804, 2858742184, 1875103526, 3384067218, 4166835727, 1579931067, + 1141601657, 3799809741, 3549717584, 1977839588, 2957267306, 372464350, 668680259, 2175552503, + 2011446046, 3516084394, 3766168119, 1175200131, 2209029901, 635180217, 338955812, 2990736784, + 601221559, 2242044419, 3024812190, 306049834, 3617246628, 1911408144, 1074125965, 3866285881, + 272279504, 3058543716, 2275784441, 567459149, 3832906691, 1107462263, 1944752874, 3583875422, + 2343980261, 767641425, 472473036, 3126744696, 2147051766, 3649987394, 3899029983, 1309766251, + 3092841090, 506333494, 801510315, 2310084639, 1276520081, 3932237093, 3683203000, 2113813516, + 3966292011, 1243601823, 2079834370, 3716205238, 405271608, 3192979340, 2411259153, 701492901, + 3750207052, 2045810168, 1209569125, 4000285905, 734575199, 2378150379, 3159862134, 438345922, + 2283203314, 778166598, 529136603, 3120492655, 2086260449, 3660498261, 3955679176, 1303499900, + 3153699989, 495890209, 744928700, 2316418568, 1337360518, 3921775410, 3626602927, 2120129051, + 4022892092, 1237286280, 2018993941, 3726666913, 461853231, 3186645403, 2350400262, 711936178, + 3693557851, 2052076527, 1270360434, 3989775046, 677911624, 2384402428, 3220639073, 427820757, + 1202443118, 3789347034, 3493118535, 1984154099, 3018127229, 362020041, 612099668, 2181885408, + 1950653705, 3526596285, 3822816288, 1168934804, 2148251930, 645706414, 395618355, 2984485767, + 544559008, 2248295444, 3085590153, 295523645, 3560598451, 1917673479, 1134918298, 3855773998, + 328860103, 3052210803, 2214924526, 577903450, 3889505748, 1101147744, 1883911421, 3594338121, + 3424493451, 1785369663, 1535282850, 4260726038, 944946072, 2653270060, 2949491377, 163225861, + 4294103532, 1501944408, 1752023237, 3457862513, 196998655, 2915761739, 2619532502, 978710370, + 2881684293, 229902577, 1012666988, 2586515928, 1603020630, 4193987810, 3356702335, 1852063179, + 2553040162, 1046169238, 263412747, 2848217023, 1818454321, 3390333573, 4227627032, 1569420204, + 60859927, 2782375331, 2487203646, 843627658, 4159668740, 1368951216, 1617990445, 3322386585, + 810543216, 2520310724, 2815490393, 27783917, 3288386659, 1652017111, 1402985802, 4125677310, + 1685994201, 3255382381, 4091620336, 1435902020, 2419138250, 910562686, 128847843, 2715354199, + 1469150398, 4058414858, 3222168983, 1719234083, 2749255853, 94984985, 876691844, 2453031472, + }, { + 0, 3433693342, 1109723005, 2391738339, 2219446010, 1222643300, 3329165703, 180685081, + 3555007413, 525277995, 2445286600, 1567235158, 1471092047, 2600801745, 361370162, 3642757804, + 2092642603, 2953916853, 1050555990, 4063508168, 4176560081, 878395215, 3134470316, 1987983410, + 2942184094, 1676945920, 3984272867, 567356797, 722740324, 3887998202, 1764827929, 2778407815, + 4185285206, 903635656, 3142804779, 2012833205, 2101111980, 2979425330, 1058630609, 4088621903, + 714308067, 3862526333, 1756790430, 2753330688, 2933487385, 1651734407, 3975966820, 542535930, + 2244825981, 1231508451, 3353891840, 188896414, 25648519, 3442302233, 1134713594, 2399689316, + 1445480648, 2592229462, 336416693, 3634843435, 3529655858, 516441772, 2420588879, 1559052753, + 698204909, 3845636723, 1807271312, 2803025166, 2916600855, 1635634313, 4025666410, 593021940, + 4202223960, 919787974, 3093159461, 1962401467, 2117261218, 2996361020, 1008193759, 4038971457, + 1428616134, 2576151384, 386135227, 3685348389, 3513580860, 499580322, 2471098945, 1608776415, + 2260985971, 1248454893, 3303468814, 139259792, 42591881, 3458459159, 1085071860, 2349261162, + 3505103035, 474062885, 2463016902, 1583654744, 1419882049, 2550902495, 377792828, 3660491170, + 51297038, 3483679632, 1093385331, 2374089965, 2269427188, 1273935210, 3311514249, 164344343, + 2890961296, 1627033870, 4000683757, 585078387, 672833386, 3836780532, 1782552599, 2794821769, + 2142603813, 3005188795, 1032883544, 4047146438, 4227826911, 928351297, 3118105506, 1970307900, + 1396409818, 2677114180, 287212199, 3719594553, 3614542624, 467372990, 2505346141, 1509854403, + 2162073199, 1282711281, 3271268626, 240228748, 76845205, 3359543307, 1186043880, 2317064054, + 796964081, 3811226735, 1839575948, 2702160658, 2882189835, 1734392469, 3924802934, 625327592, + 4234522436, 818917338, 3191908409, 1927981223, 2016387518, 3028656416, 973776579, 4137723485, + 2857232268, 1726474002, 3899187441, 616751215, 772270454, 3803048424, 1814228491, 2693328533, + 2041117753, 3036871847, 999160644, 4146592730, 4259508931, 826864221, 3217552830, 1936586016, + 3606501031, 442291769, 2496909786, 1484378436, 1388107869, 2652297411, 278519584, 3694387134, + 85183762, 3384397196, 1194773103, 2342308593, 2170143720, 1307820918, 3279733909, 265733131, + 2057717559, 3054258089, 948125770, 4096344276, 4276898253, 843467091, 3167309488, 1885556270, + 2839764098, 1709792284, 3949353983, 667704161, 755585656, 3785577190, 1865176325, 2743489947, + 102594076, 3401021058, 1144549729, 2291298815, 2186770662, 1325234296, 3228729243, 215514885, + 3589828009, 424832311, 2547870420, 1534552650, 1370645331, 2635621325, 328688686, 3745342640, + 2211456353, 1333405183, 3254067740, 224338562, 127544219, 3408931589, 1170156774, 2299866232, + 1345666772, 2627681866, 303053225, 3736746295, 3565105198, 416624816, 2522494803, 1525692365, + 4285207626, 868291796, 3176010551, 1910772649, 2065767088, 3079346734, 956571085, 4121828691, + 747507711, 3760459617, 1856702594, 2717976604, 2831417605, 1684930971, 3940615800, 642451174, + }, + { + 0, 393942083, 787884166, 965557445, 1575768332, 1251427663, 1931114890, 1684106697, + 3151536664, 2896410203, 2502855326, 2186649309, 3862229780, 4048545623, 3368213394, 3753496529, + 2898281073, 3149616690, 2184604407, 2504883892, 4046197629, 3864463166, 3755621371, 3366006712, + 387506281, 6550570, 971950319, 781573292, 1257550181, 1569695014, 1677892067, 1937345952, + 2196865699, 2508887776, 2886183461, 3145514598, 3743273903, 3362179052, 4058774313, 3868258154, + 958996667, 777139448, 400492605, 10755198, 1690661303, 1941857780, 1244879153, 1565019506, + 775012562, 961205393, 13101140, 398261271, 1943900638, 1688634781, 1563146584, 1246801179, + 2515100362, 2190636681, 3139390028, 2892258831, 3355784134, 3749586821, 3874691904, 4052225795, + 3734110983, 3387496260, 4033096577, 3877584834, 2206093835, 2483373640, 2911402637, 3136515790, + 1699389727, 1915860316, 1270647193, 1556585946, 950464531, 803071056, 374397077, 19647702, + 1917993334, 1697207605, 1554278896, 1272937907, 800985210, 952435769, 21510396, 372452543, + 3381322606, 3740399405, 3883715560, 4027047851, 2489758306, 2199758369, 3130039012, 2917895847, + 1550025124, 1259902439, 1922410786, 1710144865, 26202280, 385139947, 796522542, 939715693, + 3887801276, 4039129087, 3377269562, 3728088953, 3126293168, 2905368307, 2493602358, 2212122229, + 4037264341, 3889747862, 3730172755, 3375300368, 2907673305, 3124004506, 2209987167, 2495786524, + 1266377165, 1543533966, 1703758155, 1928748296, 379007169, 32253058, 945887303, 790236164, + 1716846671, 1898845196, 1218652361, 1608006794, 1002000707, 750929152, 357530053, 36990342, + 3717046871, 3405166100, 4084959953, 3825245842, 2153902939, 2535122712, 2929187805, 3119304606, + 3398779454, 3723384445, 3831720632, 4078468859, 2541294386, 2147616625, 3113171892, 2935238647, + 1900929062, 1714877541, 1606142112, 1220599011, 748794154, 1004184937, 39295404, 355241455, + 3835986668, 4091516591, 3394415210, 3710500393, 3108557792, 2922629027, 2545875814, 2160455461, + 1601970420, 1208431799, 1904871538, 1727077425, 43020792, 367748539, 744905086, 991776061, + 1214562461, 1595921630, 1720903707, 1911159896, 361271697, 49513938, 998160663, 738569556, + 4089209477, 3838277318, 3712633347, 3392233024, 2924491657, 3106613194, 2158369551, 2547846988, + 3100050248, 2948339467, 2519804878, 2169126797, 3844821572, 4065347079, 3420289730, 3701894785, + 52404560, 342144275, 770279894, 982687125, 1593045084, 1233708063, 1879431386, 1736363161, + 336019769, 58479994, 988899775, 764050940, 1240141877, 1586496630, 1729968307, 1885744368, + 2950685473, 3097818978, 2166999975, 2522013668, 4063474221, 3846743662, 3703937707, 3418263272, + 976650731, 760059304, 348170605, 62635310, 1742393575, 1889649828, 1227683937, 1582820386, + 2179867635, 2526361520, 2937588597, 3093503798, 3691148031, 3413731004, 4076100217, 3851374138, + 2532754330, 2173556697, 3087067932, 2944139103, 3407516310, 3697379029, 3857496592, 4070026835, + 758014338, 978679233, 64506116, 346250567, 1891774606, 1740186829, 1580472328, 1229917259, + }, { + 0, 4022496062, 83218493, 3946298115, 166436986, 3861498692, 220098631, 3806075769, + 332873972, 4229245898, 388141257, 4175494135, 440197262, 4127099824, 516501683, 4044053389, + 665747944, 3362581206, 593187285, 3432594155, 776282514, 3246869164, 716239279, 3312622225, + 880394524, 3686509090, 814485793, 3746462239, 1033003366, 3528460888, 963096923, 3601193573, + 1331495888, 2694801646, 1269355501, 2758457555, 1186374570, 2843003028, 1111716759, 2910918825, + 1552565028, 3007850522, 1484755737, 3082680359, 1432478558, 3131279456, 1368666979, 3193329757, + 1760789048, 2268195078, 1812353541, 2210675003, 1628971586, 2396670332, 1710092927, 2318375233, + 2066006732, 2498144754, 2144408305, 2417195471, 1926193846, 2634877320, 1983558283, 2583222709, + 2662991776, 1903717534, 2588923805, 1972223139, 2538711002, 2022952164, 2477029351, 2087066841, + 2372749140, 1655647338, 2308478825, 1717238871, 2223433518, 1799654416, 2155034387, 1873894445, + 3105130056, 1456926070, 3185661557, 1378041163, 2969511474, 1597852940, 3020617231, 1539874097, + 2864957116, 1157737858, 2922780289, 1106542015, 2737333958, 1290407416, 2816325371, 1210047941, + 3521578096, 1042640718, 3574781005, 986759027, 3624707082, 936300340, 3707335735, 859512585, + 3257943172, 770846650, 3334837433, 688390023, 3420185854, 605654976, 3475911875, 552361981, + 4132013464, 428600998, 4072428965, 494812827, 4288816610, 274747100, 4216845791, 345349857, + 3852387692, 173846098, 3781891409, 245988975, 3967116566, 62328360, 3900749099, 121822741, + 3859089665, 164061759, 3807435068, 221426178, 4025395579, 2933317, 3944446278, 81334904, + 4124199413, 437265099, 4045904328, 518386422, 4231653775, 335250097, 4174133682, 386814604, + 3249244393, 778691543, 3311294676, 714879978, 3359647891, 662848429, 3434477742, 595039120, + 3531393053, 1035903779, 3599308832, 961245982, 3684132967, 877986649, 3747788890, 815846244, + 2841119441, 1184522735, 2913852140, 1114616274, 2696129195, 1332855189, 2756082326, 1266946472, + 3129952805, 1431118107, 3195705880, 1371074854, 3009735263, 1554415969, 3079748194, 1481855324, + 2398522169, 1630855175, 2315475716, 1707159610, 2266835779, 1759461501, 2213084030, 1814728768, + 2636237773, 1927520499, 2580814832, 1981182158, 2496293815, 2064121993, 2420095882, 2147340468, + 2025787041, 2541577631, 2085281436, 2475210146, 1901375195, 2660681189, 1973518054, 2590184920, + 1801997909, 2225743211, 1872600680, 2153772374, 1652813359, 2369881361, 1719025170, 2310296876, + 1594986313, 2966676599, 1541693300, 3022402634, 1459236659, 3107472397, 1376780046, 3184366640, + 1288097725, 2734990467, 1211309952, 2817619134, 1160605639, 2867791097, 1104723962, 2920993988, + 937561457, 3626001999, 857201996, 3704993394, 1040821515, 3519792693, 989625654, 3577615880, + 607473029, 3421972155, 549494200, 3473077894, 769584639, 3256649409, 690699714, 3337180924, + 273452185, 4287555495, 347692196, 4219156378, 430386403, 4133832669, 491977950, 4069562336, + 60542061, 3965298515, 124656720, 3903616878, 175139863, 3853649705, 243645482, 3779581716, + }, { + 0, 3247366080, 1483520449, 2581751297, 2967040898, 1901571138, 3904227907, 691737987, + 3133399365, 2068659845, 3803142276, 589399876, 169513671, 3415493895, 1383475974, 2482566342, + 2935407819, 1870142219, 4137319690, 924099274, 506443593, 3751897225, 1178799752, 2278412616, + 339027342, 3585866318, 1280941135, 2379694991, 2766951948, 1700956620, 4236308429, 1024339981, + 2258407383, 1192382487, 3740284438, 528411094, 910556245, 4157285269, 1848198548, 2946996820, + 1012887186, 4258378066, 1681119059, 2780629139, 2357599504, 1292419792, 3572147409, 358906641, + 678054684, 3924071644, 1879503581, 2978491677, 2561882270, 1497229150, 3235873119, 22109855, + 2460592729, 1395094937, 3401913240, 189516888, 577821147, 3825075739, 2048679962, 3146956762, + 3595049455, 398902831, 2384764974, 1336573934, 1720805997, 2803873197, 1056822188, 4285729900, + 1821112490, 2902796138, 887570795, 4117339819, 3696397096, 500978920, 2218668777, 1169222953, + 2025774372, 3106931428, 550659301, 3780950821, 3362238118, 166293862, 2416645991, 1367722151, + 3262987361, 66315169, 2584839584, 1537170016, 1923370979, 3005911075, 717813282, 3947244002, + 1356109368, 2438613496, 146288633, 3375820857, 3759007162, 562248314, 3093388411, 2045739963, + 3927406461, 731490493, 2994458300, 1945440636, 1523451135, 2604718911, 44219710, 3274466046, + 4263662323, 1068272947, 2790189874, 1740649714, 1325080945, 2406874801, 379033776, 3608758128, + 1155642294, 2238671990, 479005303, 3708016055, 4097359924, 901128180, 2891217397, 1843045941, + 2011248031, 3060787807, 797805662, 3993195422, 3342353949, 112630237, 2673147868, 1591353372, + 3441611994, 212601626, 2504944923, 1421914843, 2113644376, 3161815192, 630660761, 3826893145, + 3642224980, 412692116, 2172340373, 1089836885, 1775141590, 2822790422, 832715543, 4029474007, + 1674842129, 2723860433, 1001957840, 4197873168, 3540870035, 310623315, 2338445906, 1257178514, + 4051548744, 821257608, 2836464521, 1755307081, 1101318602, 2150241802, 432566283, 3628511179, + 1270766349, 2318435533, 332587724, 3529260300, 4217841807, 988411727, 2735444302, 1652903566, + 1602977411, 2651169091, 132630338, 3328776322, 4015131905, 786223809, 3074340032, 1991273216, + 3846741958, 616972294, 3173262855, 2091579847, 1435626564, 2485072772, 234706309, 3430124101, + 2712218736, 1613231024, 4190475697, 944458353, 292577266, 3506339890, 1226630707, 2291284467, + 459984181, 3672380149, 1124496628, 2189994804, 2880683703, 1782407543, 4091479926, 844224694, + 257943739, 3469817723, 1462980986, 2529005242, 3213269817, 2114471161, 3890881272, 644152632, + 3046902270, 1947391550, 3991973951, 746483711, 88439420, 3301680572, 1563018173, 2628197501, + 657826727, 3871046759, 2136545894, 3201811878, 2548879397, 1449267173, 3481299428, 235845156, + 2650161890, 1551408418, 3315268387, 68429027, 758067552, 3970035360, 1967360161, 3033356129, + 2311284588, 1213053100, 3517963949, 270598509, 958010606, 4170500910, 1635167535, 2700636911, + 855672361, 4069415401, 1802256360, 2866995240, 2212099499, 1113008747, 3686091882, 440112042, + }, { + 0, 2611301487, 3963330207, 2006897392, 50740095, 2560849680, 4013794784, 1956178319, + 101480190, 2645113489, 3929532513, 1905435662, 84561281, 2662269422, 3912356638, 1922342769, + 202960380, 2545787283, 3760419683, 2072395532, 253679235, 2495322860, 3810871324, 2021655667, + 169122562, 2444351341, 3861841309, 2106214898, 152215677, 2461527058, 3844685538, 2123133581, + 405920760, 2207553431, 4094313831, 1873742088, 456646791, 2157096168, 4144791064, 1823027831, + 507358470, 2241388905, 4060492697, 1772322806, 490444409, 2258557462, 4043311334, 1789215881, + 338245124, 2408348267, 4161972379, 1672996084, 388959611, 2357870868, 4212429796, 1622269835, + 304431354, 2306870421, 4263435877, 1706791434, 287538053, 2324051946, 4246267162, 1723705717, + 811841520, 2881944479, 3696765295, 1207788800, 862293135, 2831204576, 3747484176, 1157324415, + 913293582, 2915732833, 3662962577, 1106318334, 896137841, 2932651550, 3646055662, 1123494017, + 1014716940, 2816349795, 3493905555, 1273334012, 1065181555, 2765630748, 3544645612, 1222882179, + 980888818, 2714919069, 3595350637, 1307180546, 963712909, 2731826146, 3578431762, 1324336509, + 676490248, 3019317351, 3295277719, 1607253752, 726947703, 2968591128, 3345992168, 1556776327, + 777919222, 3053147801, 3261432937, 1505806342, 760750473, 3070062054, 3244539670, 1522987897, + 608862708, 3220163995, 3362856811, 1406423812, 659339915, 3169449700, 3413582868, 1355966587, + 575076106, 3118709605, 3464325525, 1440228858, 557894773, 3135602714, 3447411434, 1457397381, + 1623683040, 4217512847, 2365387135, 391757072, 1673614495, 4167309552, 2415577600, 341804655, + 1724586270, 4251866481, 2331019137, 290835438, 1707942497, 4268256782, 2314648830, 307490961, + 1826587164, 4152020595, 2162433155, 457265388, 1876539747, 4101829900, 2212636668, 407333779, + 1792275682, 4051089549, 2263378557, 491595282, 1775619997, 4067460082, 2246988034, 508239213, + 2029433880, 3813931127, 2496473735, 258500328, 2079362919, 3763716872, 2546668024, 208559511, + 2130363110, 3848244873, 2462145657, 157552662, 2113730969, 3864638966, 2445764358, 174205801, + 1961777636, 4014675339, 2564147067, 57707284, 2011718299, 3964481268, 2614361092, 7778411, + 1927425818, 3913769845, 2665066885, 92077546, 1910772837, 3930150922, 2648673018, 108709525, + 1352980496, 3405878399, 3164554895, 658115296, 1403183983, 3355946752, 3214507504, 607924639, + 1453895406, 3440239233, 3130208369, 557218846, 1437504913, 3456883198, 3113552654, 573589345, + 1555838444, 3340335491, 2961681267, 723707676, 1606028947, 3290383100, 3011612684, 673504355, + 1521500946, 3239382909, 3062619533, 758026722, 1505130605, 3256038402, 3045975794, 774417053, + 1217725416, 3543158663, 2762906999, 1057739032, 1267939479, 3493229816, 2812847624, 1007544935, + 1318679830, 3577493881, 2728586121, 956803046, 1302285929, 3594125830, 2711933174, 973184153, + 1150152212, 3743982203, 2830528651, 856898788, 1200346475, 3694041348, 2880457716, 806684571, + 1115789546, 3643069573, 2931426933, 891243034, 1099408277, 3659722746, 2914794762, 907637093, + }, { + 0, 3717650821, 1616688459, 3184159950, 3233376918, 489665299, 2699419613, 2104690264, + 1510200173, 2274691816, 979330598, 3888758691, 2595928571, 1194090622, 4209380528, 661706037, + 3020400346, 1771143007, 3562738577, 164481556, 1958661196, 2837976521, 350386439, 3379863682, + 3993269687, 865250354, 2388181244, 1406015865, 784146209, 4079732388, 1323412074, 2474079215, + 3011398645, 1860735600, 3542286014, 246687547, 1942430051, 2924607718, 328963112, 3456978349, + 3917322392, 887832861, 2300653011, 1421341782, 700772878, 4099025803, 1234716485, 2483986112, + 125431087, 3673109674, 1730500708, 3132326369, 3351283641, 441867836, 2812031730, 2047535991, + 1568292418, 2163009479, 1025936137, 3769651852, 2646824148, 1079348561, 4255113631, 537475098, + 3180171691, 1612400686, 3721471200, 4717925, 2100624189, 2694980280, 493375094, 3237910515, + 3884860102, 974691139, 2278750093, 1514417672, 657926224, 4204917205, 1198234907, 2600289438, + 160053105, 3558665972, 1775665722, 3024116671, 3375586791, 346391650, 2842683564, 1962488105, + 1401545756, 2384412057, 869618007, 3997403346, 2469432970, 1319524111, 4083956673, 788193860, + 250862174, 3546612699, 1856990997, 3006903952, 3461001416, 333211981, 2920678787, 1937824774, + 1425017139, 2305216694, 883735672, 3912918525, 2487837605, 1239398944, 4095071982, 696455019, + 3136584836, 1734518017, 3668494799, 121507914, 2051872274, 2816200599, 437363545, 3347544796, + 3774328809, 1029797484, 2158697122, 1564328743, 542033279, 4258798842, 1074950196, 2642717105, + 2691310871, 2113731730, 3224801372, 497043929, 1624461185, 3175454212, 9435850, 3709412175, + 4201248378, 671035391, 2587181873, 1201904308, 986750188, 3880142185, 1519135143, 2266689570, + 342721485, 3388693064, 1949382278, 2846355203, 3570723163, 155332830, 3028835344, 1763607957, + 1315852448, 2482538789, 775087595, 4087626862, 2396469814, 1396827059, 4002123645, 857560824, + 320106210, 3464673127, 1934154665, 2933785132, 3551331444, 238804465, 3018961215, 1852270778, + 1226292623, 2491507722, 692783300, 4108177729, 2309936921, 1412959900, 3924976210, 879016919, + 2803091512, 2055541181, 3343875443, 450471158, 1739236014, 3124525867, 133568485, 3663777376, + 4245691221, 545702608, 2639048222, 1088059291, 1034514883, 3762268230, 1576387720, 2153979149, + 501724348, 3228659001, 2109407735, 2687359090, 3713981994, 13109167, 3171052385, 1620357860, + 1206151121, 2591211092, 666423962, 4197321503, 2271022407, 1523307714, 3875649548, 982999433, + 2850034278, 1953942499, 3384583981, 338329256, 1767471344, 3033506165, 151375291, 3566408766, + 4091789579, 779425934, 2478797888, 1311354309, 861580189, 4006375960, 1392910038, 2391852883, + 2929327945, 1930372812, 3469036034, 324244359, 1847629279, 3015068762, 243015828, 3555391761, + 4103744548, 688715169, 2496043375, 1229996266, 874727090, 3920994103, 1417671673, 2313759356, + 446585235, 3339223062, 2059594968, 2807313757, 3660002053, 129100416, 3128657486, 1743609803, + 1084066558, 2634765179, 549535669, 4250396208, 2149900392, 1571961325, 3765982499, 1039043750, + }, { + 0, 2635063670, 3782132909, 2086741467, 430739227, 2225303149, 4173482934, 1707977408, + 861478454, 2924937024, 3526875803, 1329085421, 720736557, 3086643291, 3415954816, 1452586230, + 1722956908, 4223524122, 2279405761, 450042295, 2132718455, 3792785921, 2658170842, 58693292, + 1441473114, 3370435372, 3028674295, 696911745, 1279765825, 3511176247, 2905172460, 807831706, + 3445913816, 1349228974, 738901109, 2969918723, 3569940419, 1237784245, 900084590, 2829701656, + 4265436910, 1664255896, 525574723, 2187084597, 3885099509, 2057177219, 117386584, 2616249390, + 2882946228, 920233410, 1253605401, 3619119471, 2994391983, 796207833, 1393823490, 3457937012, + 2559531650, 92322804, 2044829231, 3840835417, 2166609305, 472659183, 1615663412, 4249022530, + 1102706673, 3702920839, 2698457948, 1037619754, 1477802218, 3306854812, 3111894087, 611605809, + 1927342535, 4025419953, 2475568490, 243387420, 1800169180, 4131620778, 2317525617, 388842247, + 655084445, 3120835307, 3328511792, 1533734470, 1051149446, 2745738736, 3754524715, 1120297309, + 340972971, 2304586973, 4114354438, 1748234352, 234773168, 2431761350, 3968900637, 1906278251, + 2363330345, 299003487, 1840466820, 4038896370, 2507210802, 142532932, 1948239007, 3910149609, + 3213136159, 579563625, 1592415666, 3286611140, 2787646980, 992477042, 1195825833, 3662232543, + 3933188933, 2002801203, 184645608, 2517538462, 4089658462, 1858919720, 313391347, 2409765253, + 3644239219, 1144605701, 945318366, 2773977256, 3231326824, 1570095902, 569697989, 3170568115, + 2205413346, 511446676, 1646078799, 4279421497, 2598330617, 131105167, 2075239508, 3871229218, + 2955604436, 757403810, 1363424633, 3427521551, 2844163791, 881434553, 1223211618, 3588709140, + 3854685070, 2026779384, 78583587, 2577462869, 4235025557, 1633861091, 486774840, 2148301134, + 3600338360, 1268198606, 938871061, 2868504675, 3476308643, 1379640277, 777684494, 3008718712, + 1310168890, 3541595724, 2943964055, 846639841, 1471879201, 3400857943, 3067468940, 735723002, + 2102298892, 3762382970, 2619362721, 19901655, 1692534295, 4193118049, 2240594618, 411247564, + 681945942, 3047836192, 3385552891, 1422167693, 822682701, 2886124859, 3496468704, 1298661782, + 469546336, 2264093718, 4203901389, 1738379451, 38812283, 2673859341, 3812556502, 2117148576, + 3268024339, 1606809957, 598006974, 3198893512, 3680933640, 1181316734, 973624229, 2802299603, + 4052944421, 1822222163, 285065864, 2381456382, 3896478014, 1966106696, 156323219, 2489232613, + 2759337087, 964150537, 1159127250, 3625517476, 3184831332, 551242258, 1555722185, 3249901247, + 2535537225, 170842943, 1984954084, 3946848146, 2391651666, 327308324, 1877176831, 4075589769, + 263086283, 2460058045, 4005602406, 1942963472, 369291216, 2332888742, 4151061373, 1784924683, + 1022852861, 2717425547, 3717839440, 1083595558, 626782694, 3092517008, 3291821387, 1497027645, + 1763466407, 4094934481, 2289211402, 360544636, 1890636732, 3988730570, 2447251217, 215086695, + 1514488465, 3343557607, 3140191804, 639919946, 1139395978, 3739626748, 2726758695, 1065936977, + }, { + 0, 3120290792, 2827399569, 293431929, 2323408227, 864534155, 586863858, 2600537882, + 3481914503, 1987188591, 1729068310, 3740575486, 1173727716, 4228805132, 3983743093, 1418249117, + 1147313999, 4254680231, 3974377182, 1428157750, 3458136620, 2011505092, 1721256893, 3747844181, + 2347455432, 839944224, 594403929, 2593536433, 26687147, 3094146371, 2836498234, 283794642, + 2294627998, 826205558, 541298447, 2578994407, 45702141, 3141697557, 2856315500, 331624836, + 1196225049, 4273416689, 4023010184, 1446090848, 3442513786, 1959480466, 1706436331, 3696098563, + 3433538001, 1968994873, 1679888448, 3722103720, 1188807858, 4280295258, 3999102243, 1470541515, + 53374294, 3134568126, 2879970503, 307431215, 2303854645, 816436189, 567589284, 2553242188, + 3405478781, 1929420949, 1652411116, 3682996484, 1082596894, 4185703926, 3892424591, 1375368295, + 91404282, 3163122706, 2918450795, 336584067, 2400113305, 922028401, 663249672, 2658384096, + 2392450098, 929185754, 639587747, 2682555979, 82149713, 3172883129, 2892181696, 362343208, + 1091578037, 4176212829, 3918960932, 1349337804, 3412872662, 1922537022, 1676344391, 3658557359, + 1111377379, 4224032267, 3937989746, 1396912026, 3359776896, 1908013928, 1623494929, 3644803833, + 2377615716, 877417100, 623982837, 2630542109, 130804743, 3190831087, 2941083030, 381060734, + 106748588, 3215393092, 2933549885, 388083925, 2350956495, 903570471, 614862430, 2640172470, + 3386185259, 1882115523, 1632872378, 3634920530, 1135178568, 4199721120, 3945775833, 1389631793, + 1317531835, 4152109907, 3858841898, 1610259138, 3304822232, 2097172016, 1820140617, 3582394273, + 2165193788, 955639764, 696815021, 2423477829, 192043359, 2995356343, 2750736590, 437203750, + 182808564, 3005133852, 2724453989, 462947725, 2157513367, 962777471, 673168134, 2447663342, + 3312231283, 2090301595, 1844056802, 3557935370, 1326499344, 4142603768, 3885397889, 1584245865, + 3326266917, 2142836173, 1858371508, 3611272284, 1279175494, 4123357358, 3837270743, 1564721471, + 164299426, 2955991370, 2706223923, 414607579, 2209834945, 978107433, 724686416, 2462715320, + 2183156074, 1004243586, 715579643, 2472360723, 140260361, 2980573153, 2698675608, 421617264, + 1302961645, 4099032581, 3845074044, 1557460884, 3352688782, 2116952934, 1867729183, 3601371895, + 2222754758, 1032278062, 754596439, 2499928511, 234942117, 3086693709, 2793824052, 528319708, + 1274365761, 4061043881, 3816027856, 1518873912, 3246989858, 2020800970, 1762628531, 3505670235, + 3223196809, 2045103969, 1754834200, 3512958704, 1247965674, 4086934018, 3806642299, 1528765331, + 261609486, 3060532198, 2802936223, 518697591, 2246819181, 1007707781, 762121468, 2492913428, + 213497176, 3041029808, 2755593417, 499441441, 2261110843, 1061030867, 776167850, 2545465922, + 3274734047, 2060165687, 1807140942, 3528266662, 1229724860, 4038575956, 3788156205, 1479636677, + 1222322711, 4045468159, 3764231046, 1504067694, 3265744756, 2069664924, 1780612837, 3554288909, + 2270357136, 1051278712, 802445057, 2519698665, 221152243, 3033880603, 2779263586, 475261322, + }, { + 0, 2926088593, 2275419491, 701019378, 3560000647, 2052709654, 1402038756, 4261017717, + 1930665807, 3715829470, 4105419308, 1524313021, 2804077512, 155861593, 545453739, 2397726522, + 3861331614, 1213181711, 1636244477, 3488582252, 840331801, 2625561480, 3048626042, 467584747, + 2503254481, 995897408, 311723186, 3170637091, 1090907478, 4016929991, 3332753461, 1758288292, + 390036349, 3109546732, 2426363422, 1056427919, 3272488954, 1835443819, 1152258713, 3938878216, + 1680663602, 3393484195, 3817652561, 1306808512, 2954733749, 510998820, 935169494, 2580880455, + 4044899811, 1601229938, 1991794816, 3637571857, 623446372, 2336332021, 2726898695, 216120726, + 2181814956, 744704829, 95158223, 2881711710, 1446680107, 4166125498, 3516576584, 2146575065, + 780072698, 2148951915, 2849952665, 129384968, 4199529085, 1411853292, 2112855838, 3548843663, + 1567451573, 4077254692, 3670887638, 1957027143, 2304517426, 657765539, 251396177, 2694091200, + 3361327204, 1714510325, 1341779207, 3784408214, 476611811, 2986349938, 2613617024, 899690513, + 3142211371, 354600634, 1021997640, 2458051545, 1870338988, 3239283261, 3906682575, 1186180958, + 960597383, 2536053782, 3202459876, 277428597, 3983589632, 1125666961, 1792074851, 3300423154, + 1246892744, 3829039961, 3455203243, 1671079482, 2657312335, 806080478, 432241452, 3081497277, + 3748049689, 1896751752, 1489409658, 4138600427, 190316446, 2772397583, 2365053693, 580864876, + 2893360214, 35503559, 735381813, 2243795108, 2017747153, 3593269568, 4293150130, 1368183843, + 1560145396, 4069882981, 3680356503, 1966430470, 2295112051, 648294626, 258769936, 2701399425, + 804156091, 2173100842, 2823706584, 103204425, 4225711676, 1438101421, 2088704863, 3524758222, + 3134903146, 347226875, 1031468553, 2467456920, 1860935661, 3229814396, 3914054286, 1193487135, + 3385412645, 1738661300, 1315531078, 3758225623, 502792354, 3012596019, 2589468097, 875607120, + 1271043721, 3853125400, 3429020650, 1644831355, 2683558414, 832261023, 408158061, 3057348348, + 953223622, 2528745559, 3211865253, 286899508, 3974120769, 1116263632, 1799381026, 3307794867, + 2917509143, 59586950, 709201268, 2217549029, 2043995280, 3619452161, 4269064691, 1344032866, + 3740677976, 1889445577, 1498812987, 4148069290, 180845535, 2762992206, 2372361916, 588238637, + 1921194766, 3706423967, 4112727661, 1531686908, 2796705673, 148555288, 554857194, 2407195515, + 26248257, 2952271312, 2251333922, 676868275, 3584149702, 2076793175, 1375858085, 4234771508, + 2493785488, 986493953, 319029491, 3178008930, 1083533591, 4009621638, 3342158964, 1767759333, + 3887577823, 1239362382, 1612160956, 3464433197, 864482904, 2649647049, 3022443323, 441336490, + 1706844275, 3419730402, 3793503504, 1282724993, 2978819316, 535149925, 908921239, 2554697734, + 380632892, 3100077741, 2433735263, 1063734222, 3265180603, 1828069930, 1161729752, 3948283721, + 2207997677, 770953084, 71007118, 2857626143, 1470763626, 4190274555, 3490330377, 2120394392, + 4035494306, 1591758899, 1999168705, 3644880208, 616140069, 2328960180, 2736367686, 225524183, + }, +}; + +static const uint8_t +WUFFS_CRC32__IEEE_X86_SSE42_K1K2[16] WUFFS_BASE__POTENTIALLY_UNUSED = { + 212, 43, 68, 84, 1, 0, 0, 0, + 150, 21, 228, 198, 1, 0, 0, 0, +}; + +static const uint8_t +WUFFS_CRC32__IEEE_X86_SSE42_K3K4[16] WUFFS_BASE__POTENTIALLY_UNUSED = { + 208, 151, 25, 117, 1, 0, 0, 0, + 158, 0, 170, 204, 0, 0, 0, 0, +}; + +static const uint8_t +WUFFS_CRC32__IEEE_X86_SSE42_K5ZZ[16] WUFFS_BASE__POTENTIALLY_UNUSED = { + 36, 97, 205, 99, 1, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, +}; + +static const uint8_t +WUFFS_CRC32__IEEE_X86_SSE42_PXMU[16] WUFFS_BASE__POTENTIALLY_UNUSED = { + 65, 6, 113, 219, 1, 0, 0, 0, + 65, 22, 1, 247, 1, 0, 0, 0, +}; + +// ---------------- Private Initializer Prototypes + +// ---------------- Private Function Prototypes + +static wuffs_base__empty_struct +wuffs_crc32__ieee_hasher__up( + wuffs_crc32__ieee_hasher* self, + wuffs_base__slice_u8 a_x); + +static wuffs_base__empty_struct +wuffs_crc32__ieee_hasher__up__choosy_default( + wuffs_crc32__ieee_hasher* self, + wuffs_base__slice_u8 a_x); + +#if defined(WUFFS_BASE__CPU_ARCH__ARM_CRC32) +static wuffs_base__empty_struct +wuffs_crc32__ieee_hasher__up_arm_crc32( + wuffs_crc32__ieee_hasher* self, + wuffs_base__slice_u8 a_x); +#endif // defined(WUFFS_BASE__CPU_ARCH__ARM_CRC32) + +#if defined(WUFFS_BASE__CPU_ARCH__X86_FAMILY) +static wuffs_base__empty_struct +wuffs_crc32__ieee_hasher__up_x86_avx2( + wuffs_crc32__ieee_hasher* self, + wuffs_base__slice_u8 a_x); +#endif // defined(WUFFS_BASE__CPU_ARCH__X86_FAMILY) + +#if defined(WUFFS_BASE__CPU_ARCH__X86_FAMILY) +static wuffs_base__empty_struct +wuffs_crc32__ieee_hasher__up_x86_sse42( + wuffs_crc32__ieee_hasher* self, + wuffs_base__slice_u8 a_x); +#endif // defined(WUFFS_BASE__CPU_ARCH__X86_FAMILY) + +// ---------------- VTables + +const wuffs_base__hasher_u32__func_ptrs +wuffs_crc32__ieee_hasher__func_ptrs_for__wuffs_base__hasher_u32 = { + (wuffs_base__empty_struct(*)(void*, + uint32_t, + bool))(&wuffs_crc32__ieee_hasher__set_quirk_enabled), + (uint32_t(*)(void*, + wuffs_base__slice_u8))(&wuffs_crc32__ieee_hasher__update_u32), +}; + +// ---------------- Initializer Implementations + +wuffs_base__status WUFFS_BASE__WARN_UNUSED_RESULT +wuffs_crc32__ieee_hasher__initialize( + wuffs_crc32__ieee_hasher* self, + size_t sizeof_star_self, + uint64_t wuffs_version, + uint32_t options){ + if (!self) { + return wuffs_base__make_status(wuffs_base__error__bad_receiver); + } + if (sizeof(*self) != sizeof_star_self) { + return wuffs_base__make_status(wuffs_base__error__bad_sizeof_receiver); + } + if (((wuffs_version >> 32) != WUFFS_VERSION_MAJOR) || + (((wuffs_version >> 16) & 0xFFFF) > WUFFS_VERSION_MINOR)) { + return wuffs_base__make_status(wuffs_base__error__bad_wuffs_version); + } + + if ((options & WUFFS_INITIALIZE__ALREADY_ZEROED) != 0) { + // The whole point of this if-check is to detect an uninitialized *self. + // We disable the warning on GCC. Clang-5.0 does not have this warning. +#if !defined(__clang__) && defined(__GNUC__) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wmaybe-uninitialized" +#endif + if (self->private_impl.magic != 0) { + return wuffs_base__make_status(wuffs_base__error__initialize_falsely_claimed_already_zeroed); + } +#if !defined(__clang__) && defined(__GNUC__) +#pragma GCC diagnostic pop +#endif + } else { + if ((options & WUFFS_INITIALIZE__LEAVE_INTERNAL_BUFFERS_UNINITIALIZED) == 0) { + memset(self, 0, sizeof(*self)); + options |= WUFFS_INITIALIZE__ALREADY_ZEROED; + } else { + memset(&(self->private_impl), 0, sizeof(self->private_impl)); + } + } + + self->private_impl.choosy_up = &wuffs_crc32__ieee_hasher__up__choosy_default; + + self->private_impl.magic = WUFFS_BASE__MAGIC; + self->private_impl.vtable_for__wuffs_base__hasher_u32.vtable_name = + wuffs_base__hasher_u32__vtable_name; + self->private_impl.vtable_for__wuffs_base__hasher_u32.function_pointers = + (const void*)(&wuffs_crc32__ieee_hasher__func_ptrs_for__wuffs_base__hasher_u32); + return wuffs_base__make_status(NULL); +} + +wuffs_crc32__ieee_hasher* +wuffs_crc32__ieee_hasher__alloc() { + wuffs_crc32__ieee_hasher* x = + (wuffs_crc32__ieee_hasher*)(calloc(sizeof(wuffs_crc32__ieee_hasher), 1)); + if (!x) { + return NULL; + } + if (wuffs_crc32__ieee_hasher__initialize( + x, sizeof(wuffs_crc32__ieee_hasher), WUFFS_VERSION, WUFFS_INITIALIZE__ALREADY_ZEROED).repr) { + free(x); + return NULL; + } + return x; +} + +size_t +sizeof__wuffs_crc32__ieee_hasher() { + return sizeof(wuffs_crc32__ieee_hasher); +} + +// ---------------- Function Implementations + +// -------- func crc32.ieee_hasher.set_quirk_enabled + +WUFFS_BASE__MAYBE_STATIC wuffs_base__empty_struct +wuffs_crc32__ieee_hasher__set_quirk_enabled( + wuffs_crc32__ieee_hasher* self, + uint32_t a_quirk, + bool a_enabled) { + return wuffs_base__make_empty_struct(); +} + +// -------- func crc32.ieee_hasher.update_u32 + +WUFFS_BASE__MAYBE_STATIC uint32_t +wuffs_crc32__ieee_hasher__update_u32( + wuffs_crc32__ieee_hasher* self, + wuffs_base__slice_u8 a_x) { + if (!self) { + return 0; + } + if (self->private_impl.magic != WUFFS_BASE__MAGIC) { + return 0; + } + + if (self->private_impl.f_state == 0) { + self->private_impl.choosy_up = ( +#if defined(WUFFS_BASE__CPU_ARCH__ARM_CRC32) + wuffs_base__cpu_arch__have_arm_crc32() ? &wuffs_crc32__ieee_hasher__up_arm_crc32 : +#endif +#if defined(WUFFS_BASE__CPU_ARCH__X86_FAMILY) + wuffs_base__cpu_arch__have_x86_avx2() ? &wuffs_crc32__ieee_hasher__up_x86_avx2 : +#endif +#if defined(WUFFS_BASE__CPU_ARCH__X86_FAMILY) + wuffs_base__cpu_arch__have_x86_sse42() ? &wuffs_crc32__ieee_hasher__up_x86_sse42 : +#endif + self->private_impl.choosy_up); + } + wuffs_crc32__ieee_hasher__up(self, a_x); + return self->private_impl.f_state; +} + +// -------- func crc32.ieee_hasher.up + +static wuffs_base__empty_struct +wuffs_crc32__ieee_hasher__up( + wuffs_crc32__ieee_hasher* self, + wuffs_base__slice_u8 a_x) { + return (*self->private_impl.choosy_up)(self, a_x); +} + +static wuffs_base__empty_struct +wuffs_crc32__ieee_hasher__up__choosy_default( + wuffs_crc32__ieee_hasher* self, + wuffs_base__slice_u8 a_x) { + uint32_t v_s = 0; + wuffs_base__slice_u8 v_p = {0}; + + v_s = (4294967295 ^ self->private_impl.f_state); + { + wuffs_base__slice_u8 i_slice_p = a_x; + v_p.ptr = i_slice_p.ptr; + v_p.len = 16; + uint8_t* i_end0_p = v_p.ptr + (((i_slice_p.len - (size_t)(v_p.ptr - i_slice_p.ptr)) / 32) * 32); + while (v_p.ptr < i_end0_p) { + v_s ^= ((((uint32_t)(v_p.ptr[0])) << 0) | + (((uint32_t)(v_p.ptr[1])) << 8) | + (((uint32_t)(v_p.ptr[2])) << 16) | + (((uint32_t)(v_p.ptr[3])) << 24)); + v_s = (WUFFS_CRC32__IEEE_TABLE[0][v_p.ptr[15]] ^ + WUFFS_CRC32__IEEE_TABLE[1][v_p.ptr[14]] ^ + WUFFS_CRC32__IEEE_TABLE[2][v_p.ptr[13]] ^ + WUFFS_CRC32__IEEE_TABLE[3][v_p.ptr[12]] ^ + WUFFS_CRC32__IEEE_TABLE[4][v_p.ptr[11]] ^ + WUFFS_CRC32__IEEE_TABLE[5][v_p.ptr[10]] ^ + WUFFS_CRC32__IEEE_TABLE[6][v_p.ptr[9]] ^ + WUFFS_CRC32__IEEE_TABLE[7][v_p.ptr[8]] ^ + WUFFS_CRC32__IEEE_TABLE[8][v_p.ptr[7]] ^ + WUFFS_CRC32__IEEE_TABLE[9][v_p.ptr[6]] ^ + WUFFS_CRC32__IEEE_TABLE[10][v_p.ptr[5]] ^ + WUFFS_CRC32__IEEE_TABLE[11][v_p.ptr[4]] ^ + WUFFS_CRC32__IEEE_TABLE[12][(255 & (v_s >> 24))] ^ + WUFFS_CRC32__IEEE_TABLE[13][(255 & (v_s >> 16))] ^ + WUFFS_CRC32__IEEE_TABLE[14][(255 & (v_s >> 8))] ^ + WUFFS_CRC32__IEEE_TABLE[15][(255 & (v_s >> 0))]); + v_p.ptr += 16; + v_s ^= ((((uint32_t)(v_p.ptr[0])) << 0) | + (((uint32_t)(v_p.ptr[1])) << 8) | + (((uint32_t)(v_p.ptr[2])) << 16) | + (((uint32_t)(v_p.ptr[3])) << 24)); + v_s = (WUFFS_CRC32__IEEE_TABLE[0][v_p.ptr[15]] ^ + WUFFS_CRC32__IEEE_TABLE[1][v_p.ptr[14]] ^ + WUFFS_CRC32__IEEE_TABLE[2][v_p.ptr[13]] ^ + WUFFS_CRC32__IEEE_TABLE[3][v_p.ptr[12]] ^ + WUFFS_CRC32__IEEE_TABLE[4][v_p.ptr[11]] ^ + WUFFS_CRC32__IEEE_TABLE[5][v_p.ptr[10]] ^ + WUFFS_CRC32__IEEE_TABLE[6][v_p.ptr[9]] ^ + WUFFS_CRC32__IEEE_TABLE[7][v_p.ptr[8]] ^ + WUFFS_CRC32__IEEE_TABLE[8][v_p.ptr[7]] ^ + WUFFS_CRC32__IEEE_TABLE[9][v_p.ptr[6]] ^ + WUFFS_CRC32__IEEE_TABLE[10][v_p.ptr[5]] ^ + WUFFS_CRC32__IEEE_TABLE[11][v_p.ptr[4]] ^ + WUFFS_CRC32__IEEE_TABLE[12][(255 & (v_s >> 24))] ^ + WUFFS_CRC32__IEEE_TABLE[13][(255 & (v_s >> 16))] ^ + WUFFS_CRC32__IEEE_TABLE[14][(255 & (v_s >> 8))] ^ + WUFFS_CRC32__IEEE_TABLE[15][(255 & (v_s >> 0))]); + v_p.ptr += 16; + } + v_p.len = 16; + uint8_t* i_end1_p = v_p.ptr + (((i_slice_p.len - (size_t)(v_p.ptr - i_slice_p.ptr)) / 16) * 16); + while (v_p.ptr < i_end1_p) { + v_s ^= ((((uint32_t)(v_p.ptr[0])) << 0) | + (((uint32_t)(v_p.ptr[1])) << 8) | + (((uint32_t)(v_p.ptr[2])) << 16) | + (((uint32_t)(v_p.ptr[3])) << 24)); + v_s = (WUFFS_CRC32__IEEE_TABLE[0][v_p.ptr[15]] ^ + WUFFS_CRC32__IEEE_TABLE[1][v_p.ptr[14]] ^ + WUFFS_CRC32__IEEE_TABLE[2][v_p.ptr[13]] ^ + WUFFS_CRC32__IEEE_TABLE[3][v_p.ptr[12]] ^ + WUFFS_CRC32__IEEE_TABLE[4][v_p.ptr[11]] ^ + WUFFS_CRC32__IEEE_TABLE[5][v_p.ptr[10]] ^ + WUFFS_CRC32__IEEE_TABLE[6][v_p.ptr[9]] ^ + WUFFS_CRC32__IEEE_TABLE[7][v_p.ptr[8]] ^ + WUFFS_CRC32__IEEE_TABLE[8][v_p.ptr[7]] ^ + WUFFS_CRC32__IEEE_TABLE[9][v_p.ptr[6]] ^ + WUFFS_CRC32__IEEE_TABLE[10][v_p.ptr[5]] ^ + WUFFS_CRC32__IEEE_TABLE[11][v_p.ptr[4]] ^ + WUFFS_CRC32__IEEE_TABLE[12][(255 & (v_s >> 24))] ^ + WUFFS_CRC32__IEEE_TABLE[13][(255 & (v_s >> 16))] ^ + WUFFS_CRC32__IEEE_TABLE[14][(255 & (v_s >> 8))] ^ + WUFFS_CRC32__IEEE_TABLE[15][(255 & (v_s >> 0))]); + v_p.ptr += 16; + } + v_p.len = 1; + uint8_t* i_end2_p = i_slice_p.ptr + i_slice_p.len; + while (v_p.ptr < i_end2_p) { + v_s = (WUFFS_CRC32__IEEE_TABLE[0][(((uint8_t)((v_s & 255))) ^ v_p.ptr[0])] ^ (v_s >> 8)); + v_p.ptr += 1; + } + v_p.len = 0; + } + self->private_impl.f_state = (4294967295 ^ v_s); + return wuffs_base__make_empty_struct(); +} + +// ‼ WUFFS MULTI-FILE SECTION +arm_crc32 +// -------- func crc32.ieee_hasher.up_arm_crc32 + +#if defined(WUFFS_BASE__CPU_ARCH__ARM_CRC32) +static wuffs_base__empty_struct +wuffs_crc32__ieee_hasher__up_arm_crc32( + wuffs_crc32__ieee_hasher* self, + wuffs_base__slice_u8 a_x) { + wuffs_base__slice_u8 v_p = {0}; + uint32_t v_s = 0; + + v_s = (4294967295 ^ self->private_impl.f_state); + while ((((uint64_t)(a_x.len)) > 0) && ((15 & ((uint32_t)(0xFFF & (uintptr_t)(a_x.ptr)))) != 0)) { + v_s = __crc32b(v_s, a_x.ptr[0]); + a_x = wuffs_base__slice_u8__subslice_i(a_x, 1); + } + { + wuffs_base__slice_u8 i_slice_p = a_x; + v_p.ptr = i_slice_p.ptr; + v_p.len = 8; + uint8_t* i_end0_p = v_p.ptr + (((i_slice_p.len - (size_t)(v_p.ptr - i_slice_p.ptr)) / 128) * 128); + while (v_p.ptr < i_end0_p) { + v_s = __crc32d(v_s, wuffs_base__peek_u64le__no_bounds_check(v_p.ptr)); + v_p.ptr += 8; + v_s = __crc32d(v_s, wuffs_base__peek_u64le__no_bounds_check(v_p.ptr)); + v_p.ptr += 8; + v_s = __crc32d(v_s, wuffs_base__peek_u64le__no_bounds_check(v_p.ptr)); + v_p.ptr += 8; + v_s = __crc32d(v_s, wuffs_base__peek_u64le__no_bounds_check(v_p.ptr)); + v_p.ptr += 8; + v_s = __crc32d(v_s, wuffs_base__peek_u64le__no_bounds_check(v_p.ptr)); + v_p.ptr += 8; + v_s = __crc32d(v_s, wuffs_base__peek_u64le__no_bounds_check(v_p.ptr)); + v_p.ptr += 8; + v_s = __crc32d(v_s, wuffs_base__peek_u64le__no_bounds_check(v_p.ptr)); + v_p.ptr += 8; + v_s = __crc32d(v_s, wuffs_base__peek_u64le__no_bounds_check(v_p.ptr)); + v_p.ptr += 8; + v_s = __crc32d(v_s, wuffs_base__peek_u64le__no_bounds_check(v_p.ptr)); + v_p.ptr += 8; + v_s = __crc32d(v_s, wuffs_base__peek_u64le__no_bounds_check(v_p.ptr)); + v_p.ptr += 8; + v_s = __crc32d(v_s, wuffs_base__peek_u64le__no_bounds_check(v_p.ptr)); + v_p.ptr += 8; + v_s = __crc32d(v_s, wuffs_base__peek_u64le__no_bounds_check(v_p.ptr)); + v_p.ptr += 8; + v_s = __crc32d(v_s, wuffs_base__peek_u64le__no_bounds_check(v_p.ptr)); + v_p.ptr += 8; + v_s = __crc32d(v_s, wuffs_base__peek_u64le__no_bounds_check(v_p.ptr)); + v_p.ptr += 8; + v_s = __crc32d(v_s, wuffs_base__peek_u64le__no_bounds_check(v_p.ptr)); + v_p.ptr += 8; + v_s = __crc32d(v_s, wuffs_base__peek_u64le__no_bounds_check(v_p.ptr)); + v_p.ptr += 8; + } + v_p.len = 8; + uint8_t* i_end1_p = v_p.ptr + (((i_slice_p.len - (size_t)(v_p.ptr - i_slice_p.ptr)) / 8) * 8); + while (v_p.ptr < i_end1_p) { + v_s = __crc32d(v_s, wuffs_base__peek_u64le__no_bounds_check(v_p.ptr)); + v_p.ptr += 8; + } + v_p.len = 1; + uint8_t* i_end2_p = i_slice_p.ptr + i_slice_p.len; + while (v_p.ptr < i_end2_p) { + v_s = __crc32b(v_s, v_p.ptr[0]); + v_p.ptr += 1; + } + v_p.len = 0; + } + self->private_impl.f_state = (4294967295 ^ v_s); + return wuffs_base__make_empty_struct(); +} +#endif // defined(WUFFS_BASE__CPU_ARCH__ARM_CRC32) +// ‼ WUFFS MULTI-FILE SECTION -arm_crc32 + +// ‼ WUFFS MULTI-FILE SECTION +x86_avx2 +// -------- func crc32.ieee_hasher.up_x86_avx2 + +#if defined(WUFFS_BASE__CPU_ARCH__X86_FAMILY) +WUFFS_BASE__MAYBE_ATTRIBUTE_TARGET("pclmul,popcnt,sse4.2,avx2") +static wuffs_base__empty_struct +wuffs_crc32__ieee_hasher__up_x86_avx2( + wuffs_crc32__ieee_hasher* self, + wuffs_base__slice_u8 a_x) { + uint32_t v_s = 0; + wuffs_base__slice_u8 v_p = {0}; + __m128i v_k = {0}; + __m128i v_x0 = {0}; + __m128i v_x1 = {0}; + __m128i v_x2 = {0}; + __m128i v_x3 = {0}; + __m128i v_y0 = {0}; + __m128i v_y1 = {0}; + __m128i v_y2 = {0}; + __m128i v_y3 = {0}; + uint64_t v_tail_index = 0; + + v_s = (4294967295 ^ self->private_impl.f_state); + while ((((uint64_t)(a_x.len)) > 0) && ((15 & ((uint32_t)(0xFFF & (uintptr_t)(a_x.ptr)))) != 0)) { + v_s = (WUFFS_CRC32__IEEE_TABLE[0][(((uint8_t)((v_s & 255))) ^ a_x.ptr[0])] ^ (v_s >> 8)); + a_x = wuffs_base__slice_u8__subslice_i(a_x, 1); + } + if (((uint64_t)(a_x.len)) < 64) { + { + wuffs_base__slice_u8 i_slice_p = a_x; + v_p.ptr = i_slice_p.ptr; + v_p.len = 1; + uint8_t* i_end0_p = i_slice_p.ptr + i_slice_p.len; + while (v_p.ptr < i_end0_p) { + v_s = (WUFFS_CRC32__IEEE_TABLE[0][(((uint8_t)((v_s & 255))) ^ v_p.ptr[0])] ^ (v_s >> 8)); + v_p.ptr += 1; + } + v_p.len = 0; + } + self->private_impl.f_state = (4294967295 ^ v_s); + return wuffs_base__make_empty_struct(); + } + v_x0 = _mm_lddqu_si128((const __m128i*)(const void*)(a_x.ptr + 0)); + v_x1 = _mm_lddqu_si128((const __m128i*)(const void*)(a_x.ptr + 16)); + v_x2 = _mm_lddqu_si128((const __m128i*)(const void*)(a_x.ptr + 32)); + v_x3 = _mm_lddqu_si128((const __m128i*)(const void*)(a_x.ptr + 48)); + v_x0 = _mm_xor_si128(v_x0, _mm_cvtsi32_si128((int32_t)(v_s))); + v_k = _mm_lddqu_si128((const __m128i*)(const void*)(WUFFS_CRC32__IEEE_X86_SSE42_K1K2)); + { + wuffs_base__slice_u8 i_slice_p = wuffs_base__slice_u8__subslice_i(a_x, 64); + v_p.ptr = i_slice_p.ptr; + v_p.len = 64; + uint8_t* i_end0_p = v_p.ptr + (((i_slice_p.len - (size_t)(v_p.ptr - i_slice_p.ptr)) / 64) * 64); + while (v_p.ptr < i_end0_p) { + v_y0 = _mm_clmulepi64_si128(v_x0, v_k, (int32_t)(0)); + v_y1 = _mm_clmulepi64_si128(v_x1, v_k, (int32_t)(0)); + v_y2 = _mm_clmulepi64_si128(v_x2, v_k, (int32_t)(0)); + v_y3 = _mm_clmulepi64_si128(v_x3, v_k, (int32_t)(0)); + v_x0 = _mm_clmulepi64_si128(v_x0, v_k, (int32_t)(17)); + v_x1 = _mm_clmulepi64_si128(v_x1, v_k, (int32_t)(17)); + v_x2 = _mm_clmulepi64_si128(v_x2, v_k, (int32_t)(17)); + v_x3 = _mm_clmulepi64_si128(v_x3, v_k, (int32_t)(17)); + v_x0 = _mm_xor_si128(_mm_xor_si128(v_x0, v_y0), _mm_lddqu_si128((const __m128i*)(const void*)(v_p.ptr + 0))); + v_x1 = _mm_xor_si128(_mm_xor_si128(v_x1, v_y1), _mm_lddqu_si128((const __m128i*)(const void*)(v_p.ptr + 16))); + v_x2 = _mm_xor_si128(_mm_xor_si128(v_x2, v_y2), _mm_lddqu_si128((const __m128i*)(const void*)(v_p.ptr + 32))); + v_x3 = _mm_xor_si128(_mm_xor_si128(v_x3, v_y3), _mm_lddqu_si128((const __m128i*)(const void*)(v_p.ptr + 48))); + v_p.ptr += 64; + } + v_p.len = 0; + } + v_k = _mm_lddqu_si128((const __m128i*)(const void*)(WUFFS_CRC32__IEEE_X86_SSE42_K3K4)); + v_y0 = _mm_clmulepi64_si128(v_x0, v_k, (int32_t)(0)); + v_x0 = _mm_clmulepi64_si128(v_x0, v_k, (int32_t)(17)); + v_x0 = _mm_xor_si128(v_x0, v_x1); + v_x0 = _mm_xor_si128(v_x0, v_y0); + v_y0 = _mm_clmulepi64_si128(v_x0, v_k, (int32_t)(0)); + v_x0 = _mm_clmulepi64_si128(v_x0, v_k, (int32_t)(17)); + v_x0 = _mm_xor_si128(v_x0, v_x2); + v_x0 = _mm_xor_si128(v_x0, v_y0); + v_y0 = _mm_clmulepi64_si128(v_x0, v_k, (int32_t)(0)); + v_x0 = _mm_clmulepi64_si128(v_x0, v_k, (int32_t)(17)); + v_x0 = _mm_xor_si128(v_x0, v_x3); + v_x0 = _mm_xor_si128(v_x0, v_y0); + v_x1 = _mm_clmulepi64_si128(v_x0, v_k, (int32_t)(16)); + v_x2 = _mm_set_epi32((int32_t)(0), (int32_t)(4294967295), (int32_t)(0), (int32_t)(4294967295)); + v_x0 = _mm_srli_si128(v_x0, (int32_t)(8)); + v_x0 = _mm_xor_si128(v_x0, v_x1); + v_k = _mm_lddqu_si128((const __m128i*)(const void*)(WUFFS_CRC32__IEEE_X86_SSE42_K5ZZ)); + v_x1 = _mm_srli_si128(v_x0, (int32_t)(4)); + v_x0 = _mm_and_si128(v_x0, v_x2); + v_x0 = _mm_clmulepi64_si128(v_x0, v_k, (int32_t)(0)); + v_x0 = _mm_xor_si128(v_x0, v_x1); + v_k = _mm_lddqu_si128((const __m128i*)(const void*)(WUFFS_CRC32__IEEE_X86_SSE42_PXMU)); + v_x1 = _mm_and_si128(v_x0, v_x2); + v_x1 = _mm_clmulepi64_si128(v_x1, v_k, (int32_t)(16)); + v_x1 = _mm_and_si128(v_x1, v_x2); + v_x1 = _mm_clmulepi64_si128(v_x1, v_k, (int32_t)(0)); + v_x0 = _mm_xor_si128(v_x0, v_x1); + v_s = ((uint32_t)(_mm_extract_epi32(v_x0, (int32_t)(1)))); + v_tail_index = (((uint64_t)(a_x.len)) & 18446744073709551552u); + if (v_tail_index < ((uint64_t)(a_x.len))) { + { + wuffs_base__slice_u8 i_slice_p = wuffs_base__slice_u8__subslice_i(a_x, v_tail_index); + v_p.ptr = i_slice_p.ptr; + v_p.len = 1; + uint8_t* i_end0_p = i_slice_p.ptr + i_slice_p.len; + while (v_p.ptr < i_end0_p) { + v_s = (WUFFS_CRC32__IEEE_TABLE[0][(((uint8_t)((v_s & 255))) ^ v_p.ptr[0])] ^ (v_s >> 8)); + v_p.ptr += 1; + } + v_p.len = 0; + } + } + self->private_impl.f_state = (4294967295 ^ v_s); + return wuffs_base__make_empty_struct(); +} +#endif // defined(WUFFS_BASE__CPU_ARCH__X86_FAMILY) +// ‼ WUFFS MULTI-FILE SECTION -x86_avx2 + +// ‼ WUFFS MULTI-FILE SECTION +x86_sse42 +// -------- func crc32.ieee_hasher.up_x86_sse42 + +#if defined(WUFFS_BASE__CPU_ARCH__X86_FAMILY) +WUFFS_BASE__MAYBE_ATTRIBUTE_TARGET("pclmul,popcnt,sse4.2") +static wuffs_base__empty_struct +wuffs_crc32__ieee_hasher__up_x86_sse42( + wuffs_crc32__ieee_hasher* self, + wuffs_base__slice_u8 a_x) { + uint32_t v_s = 0; + wuffs_base__slice_u8 v_p = {0}; + __m128i v_k = {0}; + __m128i v_x0 = {0}; + __m128i v_x1 = {0}; + __m128i v_x2 = {0}; + __m128i v_x3 = {0}; + __m128i v_y0 = {0}; + __m128i v_y1 = {0}; + __m128i v_y2 = {0}; + __m128i v_y3 = {0}; + uint64_t v_tail_index = 0; + + v_s = (4294967295 ^ self->private_impl.f_state); + while ((((uint64_t)(a_x.len)) > 0) && ((15 & ((uint32_t)(0xFFF & (uintptr_t)(a_x.ptr)))) != 0)) { + v_s = (WUFFS_CRC32__IEEE_TABLE[0][(((uint8_t)((v_s & 255))) ^ a_x.ptr[0])] ^ (v_s >> 8)); + a_x = wuffs_base__slice_u8__subslice_i(a_x, 1); + } + if (((uint64_t)(a_x.len)) < 64) { + { + wuffs_base__slice_u8 i_slice_p = a_x; + v_p.ptr = i_slice_p.ptr; + v_p.len = 1; + uint8_t* i_end0_p = i_slice_p.ptr + i_slice_p.len; + while (v_p.ptr < i_end0_p) { + v_s = (WUFFS_CRC32__IEEE_TABLE[0][(((uint8_t)((v_s & 255))) ^ v_p.ptr[0])] ^ (v_s >> 8)); + v_p.ptr += 1; + } + v_p.len = 0; + } + self->private_impl.f_state = (4294967295 ^ v_s); + return wuffs_base__make_empty_struct(); + } + v_x0 = _mm_lddqu_si128((const __m128i*)(const void*)(a_x.ptr + 0)); + v_x1 = _mm_lddqu_si128((const __m128i*)(const void*)(a_x.ptr + 16)); + v_x2 = _mm_lddqu_si128((const __m128i*)(const void*)(a_x.ptr + 32)); + v_x3 = _mm_lddqu_si128((const __m128i*)(const void*)(a_x.ptr + 48)); + v_x0 = _mm_xor_si128(v_x0, _mm_cvtsi32_si128((int32_t)(v_s))); + v_k = _mm_lddqu_si128((const __m128i*)(const void*)(WUFFS_CRC32__IEEE_X86_SSE42_K1K2)); + { + wuffs_base__slice_u8 i_slice_p = wuffs_base__slice_u8__subslice_i(a_x, 64); + v_p.ptr = i_slice_p.ptr; + v_p.len = 64; + uint8_t* i_end0_p = v_p.ptr + (((i_slice_p.len - (size_t)(v_p.ptr - i_slice_p.ptr)) / 64) * 64); + while (v_p.ptr < i_end0_p) { + v_y0 = _mm_clmulepi64_si128(v_x0, v_k, (int32_t)(0)); + v_y1 = _mm_clmulepi64_si128(v_x1, v_k, (int32_t)(0)); + v_y2 = _mm_clmulepi64_si128(v_x2, v_k, (int32_t)(0)); + v_y3 = _mm_clmulepi64_si128(v_x3, v_k, (int32_t)(0)); + v_x0 = _mm_clmulepi64_si128(v_x0, v_k, (int32_t)(17)); + v_x1 = _mm_clmulepi64_si128(v_x1, v_k, (int32_t)(17)); + v_x2 = _mm_clmulepi64_si128(v_x2, v_k, (int32_t)(17)); + v_x3 = _mm_clmulepi64_si128(v_x3, v_k, (int32_t)(17)); + v_x0 = _mm_xor_si128(_mm_xor_si128(v_x0, v_y0), _mm_lddqu_si128((const __m128i*)(const void*)(v_p.ptr + 0))); + v_x1 = _mm_xor_si128(_mm_xor_si128(v_x1, v_y1), _mm_lddqu_si128((const __m128i*)(const void*)(v_p.ptr + 16))); + v_x2 = _mm_xor_si128(_mm_xor_si128(v_x2, v_y2), _mm_lddqu_si128((const __m128i*)(const void*)(v_p.ptr + 32))); + v_x3 = _mm_xor_si128(_mm_xor_si128(v_x3, v_y3), _mm_lddqu_si128((const __m128i*)(const void*)(v_p.ptr + 48))); + v_p.ptr += 64; + } + v_p.len = 0; + } + v_k = _mm_lddqu_si128((const __m128i*)(const void*)(WUFFS_CRC32__IEEE_X86_SSE42_K3K4)); + v_y0 = _mm_clmulepi64_si128(v_x0, v_k, (int32_t)(0)); + v_x0 = _mm_clmulepi64_si128(v_x0, v_k, (int32_t)(17)); + v_x0 = _mm_xor_si128(v_x0, v_x1); + v_x0 = _mm_xor_si128(v_x0, v_y0); + v_y0 = _mm_clmulepi64_si128(v_x0, v_k, (int32_t)(0)); + v_x0 = _mm_clmulepi64_si128(v_x0, v_k, (int32_t)(17)); + v_x0 = _mm_xor_si128(v_x0, v_x2); + v_x0 = _mm_xor_si128(v_x0, v_y0); + v_y0 = _mm_clmulepi64_si128(v_x0, v_k, (int32_t)(0)); + v_x0 = _mm_clmulepi64_si128(v_x0, v_k, (int32_t)(17)); + v_x0 = _mm_xor_si128(v_x0, v_x3); + v_x0 = _mm_xor_si128(v_x0, v_y0); + v_x1 = _mm_clmulepi64_si128(v_x0, v_k, (int32_t)(16)); + v_x2 = _mm_set_epi32((int32_t)(0), (int32_t)(4294967295), (int32_t)(0), (int32_t)(4294967295)); + v_x0 = _mm_srli_si128(v_x0, (int32_t)(8)); + v_x0 = _mm_xor_si128(v_x0, v_x1); + v_k = _mm_lddqu_si128((const __m128i*)(const void*)(WUFFS_CRC32__IEEE_X86_SSE42_K5ZZ)); + v_x1 = _mm_srli_si128(v_x0, (int32_t)(4)); + v_x0 = _mm_and_si128(v_x0, v_x2); + v_x0 = _mm_clmulepi64_si128(v_x0, v_k, (int32_t)(0)); + v_x0 = _mm_xor_si128(v_x0, v_x1); + v_k = _mm_lddqu_si128((const __m128i*)(const void*)(WUFFS_CRC32__IEEE_X86_SSE42_PXMU)); + v_x1 = _mm_and_si128(v_x0, v_x2); + v_x1 = _mm_clmulepi64_si128(v_x1, v_k, (int32_t)(16)); + v_x1 = _mm_and_si128(v_x1, v_x2); + v_x1 = _mm_clmulepi64_si128(v_x1, v_k, (int32_t)(0)); + v_x0 = _mm_xor_si128(v_x0, v_x1); + v_s = ((uint32_t)(_mm_extract_epi32(v_x0, (int32_t)(1)))); + v_tail_index = (((uint64_t)(a_x.len)) & 18446744073709551552u); + if (v_tail_index < ((uint64_t)(a_x.len))) { + { + wuffs_base__slice_u8 i_slice_p = wuffs_base__slice_u8__subslice_i(a_x, v_tail_index); + v_p.ptr = i_slice_p.ptr; + v_p.len = 1; + uint8_t* i_end0_p = i_slice_p.ptr + i_slice_p.len; + while (v_p.ptr < i_end0_p) { + v_s = (WUFFS_CRC32__IEEE_TABLE[0][(((uint8_t)((v_s & 255))) ^ v_p.ptr[0])] ^ (v_s >> 8)); + v_p.ptr += 1; + } + v_p.len = 0; + } + } + self->private_impl.f_state = (4294967295 ^ v_s); + return wuffs_base__make_empty_struct(); +} +#endif // defined(WUFFS_BASE__CPU_ARCH__X86_FAMILY) +// ‼ WUFFS MULTI-FILE SECTION -x86_sse42 + +#endif // !defined(WUFFS_CONFIG__MODULES) || defined(WUFFS_CONFIG__MODULE__CRC32) + +#if !defined(WUFFS_CONFIG__MODULES) || defined(WUFFS_CONFIG__MODULE__DEFLATE) + +// ---------------- Status Codes Implementations + +const char wuffs_deflate__error__bad_huffman_code_over_subscribed[] = "#deflate: bad Huffman code (over-subscribed)"; +const char wuffs_deflate__error__bad_huffman_code_under_subscribed[] = "#deflate: bad Huffman code (under-subscribed)"; +const char wuffs_deflate__error__bad_huffman_code_length_count[] = "#deflate: bad Huffman code length count"; +const char wuffs_deflate__error__bad_huffman_code_length_repetition[] = "#deflate: bad Huffman code length repetition"; +const char wuffs_deflate__error__bad_huffman_code[] = "#deflate: bad Huffman code"; +const char wuffs_deflate__error__bad_huffman_minimum_code_length[] = "#deflate: bad Huffman minimum code length"; +const char wuffs_deflate__error__bad_block[] = "#deflate: bad block"; +const char wuffs_deflate__error__bad_distance[] = "#deflate: bad distance"; +const char wuffs_deflate__error__bad_distance_code_count[] = "#deflate: bad distance code count"; +const char wuffs_deflate__error__bad_literal_length_code_count[] = "#deflate: bad literal/length code count"; +const char wuffs_deflate__error__inconsistent_stored_block_length[] = "#deflate: inconsistent stored block length"; +const char wuffs_deflate__error__missing_end_of_block_code[] = "#deflate: missing end-of-block code"; +const char wuffs_deflate__error__no_huffman_codes[] = "#deflate: no Huffman codes"; +const char wuffs_deflate__error__truncated_input[] = "#deflate: truncated input"; +const char wuffs_deflate__error__internal_error_inconsistent_huffman_decoder_state[] = "#deflate: internal error: inconsistent Huffman decoder state"; +const char wuffs_deflate__error__internal_error_inconsistent_i_o[] = "#deflate: internal error: inconsistent I/O"; +const char wuffs_deflate__error__internal_error_inconsistent_distance[] = "#deflate: internal error: inconsistent distance"; +const char wuffs_deflate__error__internal_error_inconsistent_n_bits[] = "#deflate: internal error: inconsistent n_bits"; + +// ---------------- Private Consts + +static const uint8_t +WUFFS_DEFLATE__CODE_ORDER[19] WUFFS_BASE__POTENTIALLY_UNUSED = { + 16, 17, 18, 0, 8, 7, 9, 6, + 10, 5, 11, 4, 12, 3, 13, 2, + 14, 1, 15, +}; + +static const uint8_t +WUFFS_DEFLATE__REVERSE8[256] WUFFS_BASE__POTENTIALLY_UNUSED = { + 0, 128, 64, 192, 32, 160, 96, 224, + 16, 144, 80, 208, 48, 176, 112, 240, + 8, 136, 72, 200, 40, 168, 104, 232, + 24, 152, 88, 216, 56, 184, 120, 248, + 4, 132, 68, 196, 36, 164, 100, 228, + 20, 148, 84, 212, 52, 180, 116, 244, + 12, 140, 76, 204, 44, 172, 108, 236, + 28, 156, 92, 220, 60, 188, 124, 252, + 2, 130, 66, 194, 34, 162, 98, 226, + 18, 146, 82, 210, 50, 178, 114, 242, + 10, 138, 74, 202, 42, 170, 106, 234, + 26, 154, 90, 218, 58, 186, 122, 250, + 6, 134, 70, 198, 38, 166, 102, 230, + 22, 150, 86, 214, 54, 182, 118, 246, + 14, 142, 78, 206, 46, 174, 110, 238, + 30, 158, 94, 222, 62, 190, 126, 254, + 1, 129, 65, 193, 33, 161, 97, 225, + 17, 145, 81, 209, 49, 177, 113, 241, + 9, 137, 73, 201, 41, 169, 105, 233, + 25, 153, 89, 217, 57, 185, 121, 249, + 5, 133, 69, 197, 37, 165, 101, 229, + 21, 149, 85, 213, 53, 181, 117, 245, + 13, 141, 77, 205, 45, 173, 109, 237, + 29, 157, 93, 221, 61, 189, 125, 253, + 3, 131, 67, 195, 35, 163, 99, 227, + 19, 147, 83, 211, 51, 179, 115, 243, + 11, 139, 75, 203, 43, 171, 107, 235, + 27, 155, 91, 219, 59, 187, 123, 251, + 7, 135, 71, 199, 39, 167, 103, 231, + 23, 151, 87, 215, 55, 183, 119, 247, + 15, 143, 79, 207, 47, 175, 111, 239, + 31, 159, 95, 223, 63, 191, 127, 255, +}; + +static const uint32_t +WUFFS_DEFLATE__LCODE_MAGIC_NUMBERS[32] WUFFS_BASE__POTENTIALLY_UNUSED = { + 1073741824, 1073742080, 1073742336, 1073742592, 1073742848, 1073743104, 1073743360, 1073743616, + 1073743888, 1073744400, 1073744912, 1073745424, 1073745952, 1073746976, 1073748000, 1073749024, + 1073750064, 1073752112, 1073754160, 1073756208, 1073758272, 1073762368, 1073766464, 1073770560, + 1073774672, 1073782864, 1073791056, 1073799248, 1073807104, 134217728, 134217728, 134217728, +}; + +static const uint32_t +WUFFS_DEFLATE__DCODE_MAGIC_NUMBERS[32] WUFFS_BASE__POTENTIALLY_UNUSED = { + 1073741824, 1073742080, 1073742336, 1073742592, 1073742864, 1073743376, 1073743904, 1073744928, + 1073745968, 1073748016, 1073750080, 1073754176, 1073758288, 1073766480, 1073774688, 1073791072, + 1073807472, 1073840240, 1073873024, 1073938560, 1074004112, 1074135184, 1074266272, 1074528416, + 1074790576, 1075314864, 1075839168, 1076887744, 1077936336, 1080033488, 134217728, 134217728, +}; + +#define WUFFS_DEFLATE__HUFFS_TABLE_SIZE 1024 + +#define WUFFS_DEFLATE__HUFFS_TABLE_MASK 1023 + +// ---------------- Private Initializer Prototypes + +// ---------------- Private Function Prototypes + +static wuffs_base__status +wuffs_deflate__decoder__do_transform_io( + wuffs_deflate__decoder* self, + wuffs_base__io_buffer* a_dst, + wuffs_base__io_buffer* a_src, + wuffs_base__slice_u8 a_workbuf); + +static wuffs_base__status +wuffs_deflate__decoder__decode_blocks( + wuffs_deflate__decoder* self, + wuffs_base__io_buffer* a_dst, + wuffs_base__io_buffer* a_src); + +static wuffs_base__status +wuffs_deflate__decoder__decode_uncompressed( + wuffs_deflate__decoder* self, + wuffs_base__io_buffer* a_dst, + wuffs_base__io_buffer* a_src); + +static wuffs_base__status +wuffs_deflate__decoder__init_fixed_huffman( + wuffs_deflate__decoder* self); + +static wuffs_base__status +wuffs_deflate__decoder__init_dynamic_huffman( + wuffs_deflate__decoder* self, + wuffs_base__io_buffer* a_src); + +static wuffs_base__status +wuffs_deflate__decoder__init_huff( + wuffs_deflate__decoder* self, + uint32_t a_which, + uint32_t a_n_codes0, + uint32_t a_n_codes1, + uint32_t a_base_symbol); + +#if defined(WUFFS_BASE__CPU_ARCH__X86_FAMILY) +static wuffs_base__status +wuffs_deflate__decoder__decode_huffman_bmi2( + wuffs_deflate__decoder* self, + wuffs_base__io_buffer* a_dst, + wuffs_base__io_buffer* a_src); +#endif // defined(WUFFS_BASE__CPU_ARCH__X86_FAMILY) + +static wuffs_base__status +wuffs_deflate__decoder__decode_huffman_fast32( + wuffs_deflate__decoder* self, + wuffs_base__io_buffer* a_dst, + wuffs_base__io_buffer* a_src); + +static wuffs_base__status +wuffs_deflate__decoder__decode_huffman_fast64( + wuffs_deflate__decoder* self, + wuffs_base__io_buffer* a_dst, + wuffs_base__io_buffer* a_src); + +static wuffs_base__status +wuffs_deflate__decoder__decode_huffman_fast64__choosy_default( + wuffs_deflate__decoder* self, + wuffs_base__io_buffer* a_dst, + wuffs_base__io_buffer* a_src); + +static wuffs_base__status +wuffs_deflate__decoder__decode_huffman_slow( + wuffs_deflate__decoder* self, + wuffs_base__io_buffer* a_dst, + wuffs_base__io_buffer* a_src); + +// ---------------- VTables + +const wuffs_base__io_transformer__func_ptrs +wuffs_deflate__decoder__func_ptrs_for__wuffs_base__io_transformer = { + (wuffs_base__empty_struct(*)(void*, + uint32_t, + bool))(&wuffs_deflate__decoder__set_quirk_enabled), + (wuffs_base__status(*)(void*, + wuffs_base__io_buffer*, + wuffs_base__io_buffer*, + wuffs_base__slice_u8))(&wuffs_deflate__decoder__transform_io), + (wuffs_base__range_ii_u64(*)(const void*))(&wuffs_deflate__decoder__workbuf_len), +}; + +// ---------------- Initializer Implementations + +wuffs_base__status WUFFS_BASE__WARN_UNUSED_RESULT +wuffs_deflate__decoder__initialize( + wuffs_deflate__decoder* self, + size_t sizeof_star_self, + uint64_t wuffs_version, + uint32_t options){ + if (!self) { + return wuffs_base__make_status(wuffs_base__error__bad_receiver); + } + if (sizeof(*self) != sizeof_star_self) { + return wuffs_base__make_status(wuffs_base__error__bad_sizeof_receiver); + } + if (((wuffs_version >> 32) != WUFFS_VERSION_MAJOR) || + (((wuffs_version >> 16) & 0xFFFF) > WUFFS_VERSION_MINOR)) { + return wuffs_base__make_status(wuffs_base__error__bad_wuffs_version); + } + + if ((options & WUFFS_INITIALIZE__ALREADY_ZEROED) != 0) { + // The whole point of this if-check is to detect an uninitialized *self. + // We disable the warning on GCC. Clang-5.0 does not have this warning. +#if !defined(__clang__) && defined(__GNUC__) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wmaybe-uninitialized" +#endif + if (self->private_impl.magic != 0) { + return wuffs_base__make_status(wuffs_base__error__initialize_falsely_claimed_already_zeroed); + } +#if !defined(__clang__) && defined(__GNUC__) +#pragma GCC diagnostic pop +#endif + } else { + if ((options & WUFFS_INITIALIZE__LEAVE_INTERNAL_BUFFERS_UNINITIALIZED) == 0) { + memset(self, 0, sizeof(*self)); + options |= WUFFS_INITIALIZE__ALREADY_ZEROED; + } else { + memset(&(self->private_impl), 0, sizeof(self->private_impl)); + } + } + + self->private_impl.choosy_decode_huffman_fast64 = &wuffs_deflate__decoder__decode_huffman_fast64__choosy_default; + + self->private_impl.magic = WUFFS_BASE__MAGIC; + self->private_impl.vtable_for__wuffs_base__io_transformer.vtable_name = + wuffs_base__io_transformer__vtable_name; + self->private_impl.vtable_for__wuffs_base__io_transformer.function_pointers = + (const void*)(&wuffs_deflate__decoder__func_ptrs_for__wuffs_base__io_transformer); + return wuffs_base__make_status(NULL); +} + +wuffs_deflate__decoder* +wuffs_deflate__decoder__alloc() { + wuffs_deflate__decoder* x = + (wuffs_deflate__decoder*)(calloc(sizeof(wuffs_deflate__decoder), 1)); + if (!x) { + return NULL; + } + if (wuffs_deflate__decoder__initialize( + x, sizeof(wuffs_deflate__decoder), WUFFS_VERSION, WUFFS_INITIALIZE__ALREADY_ZEROED).repr) { + free(x); + return NULL; + } + return x; +} + +size_t +sizeof__wuffs_deflate__decoder() { + return sizeof(wuffs_deflate__decoder); +} + +// ---------------- Function Implementations + +// -------- func deflate.decoder.add_history + +WUFFS_BASE__MAYBE_STATIC wuffs_base__empty_struct +wuffs_deflate__decoder__add_history( + wuffs_deflate__decoder* self, + wuffs_base__slice_u8 a_hist) { + if (!self) { + return wuffs_base__make_empty_struct(); + } + if (self->private_impl.magic != WUFFS_BASE__MAGIC) { + return wuffs_base__make_empty_struct(); + } + + wuffs_base__slice_u8 v_s = {0}; + uint64_t v_n_copied = 0; + uint32_t v_already_full = 0; + + v_s = a_hist; + if (((uint64_t)(v_s.len)) >= 32768) { + v_s = wuffs_base__slice_u8__suffix(v_s, 32768); + wuffs_base__slice_u8__copy_from_slice(wuffs_base__make_slice_u8(self->private_data.f_history, 32768), v_s); + self->private_impl.f_history_index = 32768; + } else { + v_n_copied = wuffs_base__slice_u8__copy_from_slice(wuffs_base__make_slice_u8_ij(self->private_data.f_history, (self->private_impl.f_history_index & 32767), 32768), v_s); + if (v_n_copied < ((uint64_t)(v_s.len))) { + v_s = wuffs_base__slice_u8__subslice_i(v_s, v_n_copied); + v_n_copied = wuffs_base__slice_u8__copy_from_slice(wuffs_base__make_slice_u8(self->private_data.f_history, 32768), v_s); + self->private_impl.f_history_index = (((uint32_t)((v_n_copied & 32767))) + 32768); + } else { + v_already_full = 0; + if (self->private_impl.f_history_index >= 32768) { + v_already_full = 32768; + } + self->private_impl.f_history_index = ((self->private_impl.f_history_index & 32767) + ((uint32_t)((v_n_copied & 32767))) + v_already_full); + } + } + wuffs_base__slice_u8__copy_from_slice(wuffs_base__make_slice_u8_ij(self->private_data.f_history, 32768, 33025), wuffs_base__make_slice_u8(self->private_data.f_history, 33025)); + return wuffs_base__make_empty_struct(); +} + +// -------- func deflate.decoder.set_quirk_enabled + +WUFFS_BASE__MAYBE_STATIC wuffs_base__empty_struct +wuffs_deflate__decoder__set_quirk_enabled( + wuffs_deflate__decoder* self, + uint32_t a_quirk, + bool a_enabled) { + return wuffs_base__make_empty_struct(); +} + +// -------- func deflate.decoder.workbuf_len + +WUFFS_BASE__MAYBE_STATIC wuffs_base__range_ii_u64 +wuffs_deflate__decoder__workbuf_len( + const wuffs_deflate__decoder* self) { + if (!self) { + return wuffs_base__utility__empty_range_ii_u64(); + } + if ((self->private_impl.magic != WUFFS_BASE__MAGIC) && + (self->private_impl.magic != WUFFS_BASE__DISABLED)) { + return wuffs_base__utility__empty_range_ii_u64(); + } + + return wuffs_base__utility__make_range_ii_u64(1, 1); +} + +// -------- func deflate.decoder.transform_io + +WUFFS_BASE__MAYBE_STATIC wuffs_base__status +wuffs_deflate__decoder__transform_io( + wuffs_deflate__decoder* self, + wuffs_base__io_buffer* a_dst, + wuffs_base__io_buffer* a_src, + wuffs_base__slice_u8 a_workbuf) { + if (!self) { + return wuffs_base__make_status(wuffs_base__error__bad_receiver); + } + if (self->private_impl.magic != WUFFS_BASE__MAGIC) { + return wuffs_base__make_status( + (self->private_impl.magic == WUFFS_BASE__DISABLED) + ? wuffs_base__error__disabled_by_previous_error + : wuffs_base__error__initialize_not_called); + } + if (!a_dst || !a_src) { + self->private_impl.magic = WUFFS_BASE__DISABLED; + return wuffs_base__make_status(wuffs_base__error__bad_argument); + } + if ((self->private_impl.active_coroutine != 0) && + (self->private_impl.active_coroutine != 1)) { + self->private_impl.magic = WUFFS_BASE__DISABLED; + return wuffs_base__make_status(wuffs_base__error__interleaved_coroutine_calls); + } + self->private_impl.active_coroutine = 0; + wuffs_base__status status = wuffs_base__make_status(NULL); + + wuffs_base__status v_status = wuffs_base__make_status(NULL); + + uint32_t coro_susp_point = self->private_impl.p_transform_io[0]; + switch (coro_susp_point) { + WUFFS_BASE__COROUTINE_SUSPENSION_POINT_0; + + while (true) { + { + wuffs_base__status t_0 = wuffs_deflate__decoder__do_transform_io(self, a_dst, a_src, a_workbuf); + v_status = t_0; + } + if ((v_status.repr == wuffs_base__suspension__short_read) && (a_src && a_src->meta.closed)) { + status = wuffs_base__make_status(wuffs_deflate__error__truncated_input); + goto exit; + } + status = v_status; + WUFFS_BASE__COROUTINE_SUSPENSION_POINT_MAYBE_SUSPEND(1); + } + + ok: + self->private_impl.p_transform_io[0] = 0; + goto exit; + } + + goto suspend; + suspend: + self->private_impl.p_transform_io[0] = wuffs_base__status__is_suspension(&status) ? coro_susp_point : 0; + self->private_impl.active_coroutine = wuffs_base__status__is_suspension(&status) ? 1 : 0; + + goto exit; + exit: + if (wuffs_base__status__is_error(&status)) { + self->private_impl.magic = WUFFS_BASE__DISABLED; + } + return status; +} + +// -------- func deflate.decoder.do_transform_io + +static wuffs_base__status +wuffs_deflate__decoder__do_transform_io( + wuffs_deflate__decoder* self, + wuffs_base__io_buffer* a_dst, + wuffs_base__io_buffer* a_src, + wuffs_base__slice_u8 a_workbuf) { + wuffs_base__status status = wuffs_base__make_status(NULL); + + uint64_t v_mark = 0; + wuffs_base__status v_status = wuffs_base__make_status(NULL); + + uint8_t* iop_a_dst = NULL; + uint8_t* io0_a_dst WUFFS_BASE__POTENTIALLY_UNUSED = NULL; + uint8_t* io1_a_dst WUFFS_BASE__POTENTIALLY_UNUSED = NULL; + uint8_t* io2_a_dst WUFFS_BASE__POTENTIALLY_UNUSED = NULL; + if (a_dst && a_dst->data.ptr) { + io0_a_dst = a_dst->data.ptr; + io1_a_dst = io0_a_dst + a_dst->meta.wi; + iop_a_dst = io1_a_dst; + io2_a_dst = io0_a_dst + a_dst->data.len; + if (a_dst->meta.closed) { + io2_a_dst = iop_a_dst; + } + } + + uint32_t coro_susp_point = self->private_impl.p_do_transform_io[0]; + switch (coro_susp_point) { + WUFFS_BASE__COROUTINE_SUSPENSION_POINT_0; + + self->private_impl.choosy_decode_huffman_fast64 = ( +#if defined(WUFFS_BASE__CPU_ARCH__X86_FAMILY) + wuffs_base__cpu_arch__have_x86_bmi2() ? &wuffs_deflate__decoder__decode_huffman_bmi2 : +#endif + self->private_impl.choosy_decode_huffman_fast64); + while (true) { + v_mark = ((uint64_t)(iop_a_dst - io0_a_dst)); + { + if (a_dst) { + a_dst->meta.wi = ((size_t)(iop_a_dst - a_dst->data.ptr)); + } + wuffs_base__status t_0 = wuffs_deflate__decoder__decode_blocks(self, a_dst, a_src); + v_status = t_0; + if (a_dst) { + iop_a_dst = a_dst->data.ptr + a_dst->meta.wi; + } + } + if ( ! wuffs_base__status__is_suspension(&v_status)) { + status = v_status; + if (wuffs_base__status__is_error(&status)) { + goto exit; + } else if (wuffs_base__status__is_suspension(&status)) { + status = wuffs_base__make_status(wuffs_base__error__cannot_return_a_suspension); + goto exit; + } + goto ok; + } + wuffs_base__u64__sat_add_indirect(&self->private_impl.f_transformed_history_count, wuffs_base__io__count_since(v_mark, ((uint64_t)(iop_a_dst - io0_a_dst)))); + wuffs_deflate__decoder__add_history(self, wuffs_base__io__since(v_mark, ((uint64_t)(iop_a_dst - io0_a_dst)), io0_a_dst)); + status = v_status; + WUFFS_BASE__COROUTINE_SUSPENSION_POINT_MAYBE_SUSPEND(1); + } + + ok: + self->private_impl.p_do_transform_io[0] = 0; + goto exit; + } + + goto suspend; + suspend: + self->private_impl.p_do_transform_io[0] = wuffs_base__status__is_suspension(&status) ? coro_susp_point : 0; + + goto exit; + exit: + if (a_dst && a_dst->data.ptr) { + a_dst->meta.wi = ((size_t)(iop_a_dst - a_dst->data.ptr)); + } + + return status; +} + +// -------- func deflate.decoder.decode_blocks + +static wuffs_base__status +wuffs_deflate__decoder__decode_blocks( + wuffs_deflate__decoder* self, + wuffs_base__io_buffer* a_dst, + wuffs_base__io_buffer* a_src) { + wuffs_base__status status = wuffs_base__make_status(NULL); + + uint32_t v_final = 0; + uint32_t v_b0 = 0; + uint32_t v_type = 0; + wuffs_base__status v_status = wuffs_base__make_status(NULL); + + const uint8_t* iop_a_src = NULL; + const uint8_t* io0_a_src WUFFS_BASE__POTENTIALLY_UNUSED = NULL; + const uint8_t* io1_a_src WUFFS_BASE__POTENTIALLY_UNUSED = NULL; + const uint8_t* io2_a_src WUFFS_BASE__POTENTIALLY_UNUSED = NULL; + if (a_src && a_src->data.ptr) { + io0_a_src = a_src->data.ptr; + io1_a_src = io0_a_src + a_src->meta.ri; + iop_a_src = io1_a_src; + io2_a_src = io0_a_src + a_src->meta.wi; + } + + uint32_t coro_susp_point = self->private_impl.p_decode_blocks[0]; + if (coro_susp_point) { + v_final = self->private_data.s_decode_blocks[0].v_final; + } + switch (coro_susp_point) { + WUFFS_BASE__COROUTINE_SUSPENSION_POINT_0; + + label__outer__continue:; + while (v_final == 0) { + while (self->private_impl.f_n_bits < 3) { + { + WUFFS_BASE__COROUTINE_SUSPENSION_POINT(1); + if (WUFFS_BASE__UNLIKELY(iop_a_src == io2_a_src)) { + status = wuffs_base__make_status(wuffs_base__suspension__short_read); + goto suspend; + } + uint32_t t_0 = *iop_a_src++; + v_b0 = t_0; + } + self->private_impl.f_bits |= (v_b0 << (self->private_impl.f_n_bits & 3)); + self->private_impl.f_n_bits = ((self->private_impl.f_n_bits & 3) + 8); + } + v_final = (self->private_impl.f_bits & 1); + v_type = ((self->private_impl.f_bits >> 1) & 3); + self->private_impl.f_bits >>= 3; + self->private_impl.f_n_bits -= 3; + if (v_type == 0) { + if (a_src) { + a_src->meta.ri = ((size_t)(iop_a_src - a_src->data.ptr)); + } + WUFFS_BASE__COROUTINE_SUSPENSION_POINT(2); + status = wuffs_deflate__decoder__decode_uncompressed(self, a_dst, a_src); + if (a_src) { + iop_a_src = a_src->data.ptr + a_src->meta.ri; + } + if (status.repr) { + goto suspend; + } + goto label__outer__continue; + } else if (v_type == 1) { + v_status = wuffs_deflate__decoder__init_fixed_huffman(self); + if ( ! wuffs_base__status__is_ok(&v_status)) { + status = v_status; + if (wuffs_base__status__is_error(&status)) { + goto exit; + } else if (wuffs_base__status__is_suspension(&status)) { + status = wuffs_base__make_status(wuffs_base__error__cannot_return_a_suspension); + goto exit; + } + goto ok; + } + } else if (v_type == 2) { + if (a_src) { + a_src->meta.ri = ((size_t)(iop_a_src - a_src->data.ptr)); + } + WUFFS_BASE__COROUTINE_SUSPENSION_POINT(3); + status = wuffs_deflate__decoder__init_dynamic_huffman(self, a_src); + if (a_src) { + iop_a_src = a_src->data.ptr + a_src->meta.ri; + } + if (status.repr) { + goto suspend; + } + } else { + status = wuffs_base__make_status(wuffs_deflate__error__bad_block); + goto exit; + } + self->private_impl.f_end_of_block = false; + while (true) { + if (sizeof(void*) == 4) { + if (a_src) { + a_src->meta.ri = ((size_t)(iop_a_src - a_src->data.ptr)); + } + v_status = wuffs_deflate__decoder__decode_huffman_fast32(self, a_dst, a_src); + if (a_src) { + iop_a_src = a_src->data.ptr + a_src->meta.ri; + } + } else { + if (a_src) { + a_src->meta.ri = ((size_t)(iop_a_src - a_src->data.ptr)); + } + v_status = wuffs_deflate__decoder__decode_huffman_fast64(self, a_dst, a_src); + if (a_src) { + iop_a_src = a_src->data.ptr + a_src->meta.ri; + } + } + if (wuffs_base__status__is_error(&v_status)) { + status = v_status; + goto exit; + } + if (self->private_impl.f_end_of_block) { + goto label__outer__continue; + } + if (a_src) { + a_src->meta.ri = ((size_t)(iop_a_src - a_src->data.ptr)); + } + WUFFS_BASE__COROUTINE_SUSPENSION_POINT(4); + status = wuffs_deflate__decoder__decode_huffman_slow(self, a_dst, a_src); + if (a_src) { + iop_a_src = a_src->data.ptr + a_src->meta.ri; + } + if (status.repr) { + goto suspend; + } + if (self->private_impl.f_end_of_block) { + goto label__outer__continue; + } + } + } + + ok: + self->private_impl.p_decode_blocks[0] = 0; + goto exit; + } + + goto suspend; + suspend: + self->private_impl.p_decode_blocks[0] = wuffs_base__status__is_suspension(&status) ? coro_susp_point : 0; + self->private_data.s_decode_blocks[0].v_final = v_final; + + goto exit; + exit: + if (a_src && a_src->data.ptr) { + a_src->meta.ri = ((size_t)(iop_a_src - a_src->data.ptr)); + } + + return status; +} + +// -------- func deflate.decoder.decode_uncompressed + +static wuffs_base__status +wuffs_deflate__decoder__decode_uncompressed( + wuffs_deflate__decoder* self, + wuffs_base__io_buffer* a_dst, + wuffs_base__io_buffer* a_src) { + wuffs_base__status status = wuffs_base__make_status(NULL); + + uint32_t v_length = 0; + uint32_t v_n_copied = 0; + + uint8_t* iop_a_dst = NULL; + uint8_t* io0_a_dst WUFFS_BASE__POTENTIALLY_UNUSED = NULL; + uint8_t* io1_a_dst WUFFS_BASE__POTENTIALLY_UNUSED = NULL; + uint8_t* io2_a_dst WUFFS_BASE__POTENTIALLY_UNUSED = NULL; + if (a_dst && a_dst->data.ptr) { + io0_a_dst = a_dst->data.ptr; + io1_a_dst = io0_a_dst + a_dst->meta.wi; + iop_a_dst = io1_a_dst; + io2_a_dst = io0_a_dst + a_dst->data.len; + if (a_dst->meta.closed) { + io2_a_dst = iop_a_dst; + } + } + const uint8_t* iop_a_src = NULL; + const uint8_t* io0_a_src WUFFS_BASE__POTENTIALLY_UNUSED = NULL; + const uint8_t* io1_a_src WUFFS_BASE__POTENTIALLY_UNUSED = NULL; + const uint8_t* io2_a_src WUFFS_BASE__POTENTIALLY_UNUSED = NULL; + if (a_src && a_src->data.ptr) { + io0_a_src = a_src->data.ptr; + io1_a_src = io0_a_src + a_src->meta.ri; + iop_a_src = io1_a_src; + io2_a_src = io0_a_src + a_src->meta.wi; + } + + uint32_t coro_susp_point = self->private_impl.p_decode_uncompressed[0]; + if (coro_susp_point) { + v_length = self->private_data.s_decode_uncompressed[0].v_length; + } + switch (coro_susp_point) { + WUFFS_BASE__COROUTINE_SUSPENSION_POINT_0; + + if ((self->private_impl.f_n_bits >= 8) || ((self->private_impl.f_bits >> (self->private_impl.f_n_bits & 7)) != 0)) { + status = wuffs_base__make_status(wuffs_deflate__error__internal_error_inconsistent_n_bits); + goto exit; + } + self->private_impl.f_n_bits = 0; + self->private_impl.f_bits = 0; + { + WUFFS_BASE__COROUTINE_SUSPENSION_POINT(1); + uint32_t t_0; + if (WUFFS_BASE__LIKELY(io2_a_src - iop_a_src >= 4)) { + t_0 = wuffs_base__peek_u32le__no_bounds_check(iop_a_src); + iop_a_src += 4; + } else { + self->private_data.s_decode_uncompressed[0].scratch = 0; + WUFFS_BASE__COROUTINE_SUSPENSION_POINT(2); + while (true) { + if (WUFFS_BASE__UNLIKELY(iop_a_src == io2_a_src)) { + status = wuffs_base__make_status(wuffs_base__suspension__short_read); + goto suspend; + } + uint64_t* scratch = &self->private_data.s_decode_uncompressed[0].scratch; + uint32_t num_bits_0 = ((uint32_t)(*scratch >> 56)); + *scratch <<= 8; + *scratch >>= 8; + *scratch |= ((uint64_t)(*iop_a_src++)) << num_bits_0; + if (num_bits_0 == 24) { + t_0 = ((uint32_t)(*scratch)); + break; + } + num_bits_0 += 8; + *scratch |= ((uint64_t)(num_bits_0)) << 56; + } + } + v_length = t_0; + } + if ((((v_length) & 0xFFFF) + ((v_length) >> (32 - (16)))) != 65535) { + status = wuffs_base__make_status(wuffs_deflate__error__inconsistent_stored_block_length); + goto exit; + } + v_length = ((v_length) & 0xFFFF); + while (true) { + v_n_copied = wuffs_base__io_writer__limited_copy_u32_from_reader( + &iop_a_dst, io2_a_dst,v_length, &iop_a_src, io2_a_src); + if (v_length <= v_n_copied) { + status = wuffs_base__make_status(NULL); + goto ok; + } + v_length -= v_n_copied; + if (((uint64_t)(io2_a_dst - iop_a_dst)) == 0) { + status = wuffs_base__make_status(wuffs_base__suspension__short_write); + WUFFS_BASE__COROUTINE_SUSPENSION_POINT_MAYBE_SUSPEND(3); + } else { + status = wuffs_base__make_status(wuffs_base__suspension__short_read); + WUFFS_BASE__COROUTINE_SUSPENSION_POINT_MAYBE_SUSPEND(4); + } + } + + ok: + self->private_impl.p_decode_uncompressed[0] = 0; + goto exit; + } + + goto suspend; + suspend: + self->private_impl.p_decode_uncompressed[0] = wuffs_base__status__is_suspension(&status) ? coro_susp_point : 0; + self->private_data.s_decode_uncompressed[0].v_length = v_length; + + goto exit; + exit: + if (a_dst && a_dst->data.ptr) { + a_dst->meta.wi = ((size_t)(iop_a_dst - a_dst->data.ptr)); + } + if (a_src && a_src->data.ptr) { + a_src->meta.ri = ((size_t)(iop_a_src - a_src->data.ptr)); + } + + return status; +} + +// -------- func deflate.decoder.init_fixed_huffman + +static wuffs_base__status +wuffs_deflate__decoder__init_fixed_huffman( + wuffs_deflate__decoder* self) { + uint32_t v_i = 0; + wuffs_base__status v_status = wuffs_base__make_status(NULL); + + while (v_i < 144) { + self->private_data.f_code_lengths[v_i] = 8; + v_i += 1; + } + while (v_i < 256) { + self->private_data.f_code_lengths[v_i] = 9; + v_i += 1; + } + while (v_i < 280) { + self->private_data.f_code_lengths[v_i] = 7; + v_i += 1; + } + while (v_i < 288) { + self->private_data.f_code_lengths[v_i] = 8; + v_i += 1; + } + while (v_i < 320) { + self->private_data.f_code_lengths[v_i] = 5; + v_i += 1; + } + v_status = wuffs_deflate__decoder__init_huff(self, + 0, + 0, + 288, + 257); + if (wuffs_base__status__is_error(&v_status)) { + return v_status; + } + v_status = wuffs_deflate__decoder__init_huff(self, + 1, + 288, + 320, + 0); + if (wuffs_base__status__is_error(&v_status)) { + return v_status; + } + return wuffs_base__make_status(NULL); +} + +// -------- func deflate.decoder.init_dynamic_huffman + +static wuffs_base__status +wuffs_deflate__decoder__init_dynamic_huffman( + wuffs_deflate__decoder* self, + wuffs_base__io_buffer* a_src) { + wuffs_base__status status = wuffs_base__make_status(NULL); + + uint32_t v_bits = 0; + uint32_t v_n_bits = 0; + uint32_t v_b0 = 0; + uint32_t v_n_lit = 0; + uint32_t v_n_dist = 0; + uint32_t v_n_clen = 0; + uint32_t v_i = 0; + uint32_t v_b1 = 0; + wuffs_base__status v_status = wuffs_base__make_status(NULL); + uint32_t v_mask = 0; + uint32_t v_table_entry = 0; + uint32_t v_table_entry_n_bits = 0; + uint32_t v_b2 = 0; + uint32_t v_n_extra_bits = 0; + uint8_t v_rep_symbol = 0; + uint32_t v_rep_count = 0; + uint32_t v_b3 = 0; + + const uint8_t* iop_a_src = NULL; + const uint8_t* io0_a_src WUFFS_BASE__POTENTIALLY_UNUSED = NULL; + const uint8_t* io1_a_src WUFFS_BASE__POTENTIALLY_UNUSED = NULL; + const uint8_t* io2_a_src WUFFS_BASE__POTENTIALLY_UNUSED = NULL; + if (a_src && a_src->data.ptr) { + io0_a_src = a_src->data.ptr; + io1_a_src = io0_a_src + a_src->meta.ri; + iop_a_src = io1_a_src; + io2_a_src = io0_a_src + a_src->meta.wi; + } + + uint32_t coro_susp_point = self->private_impl.p_init_dynamic_huffman[0]; + if (coro_susp_point) { + v_bits = self->private_data.s_init_dynamic_huffman[0].v_bits; + v_n_bits = self->private_data.s_init_dynamic_huffman[0].v_n_bits; + v_n_lit = self->private_data.s_init_dynamic_huffman[0].v_n_lit; + v_n_dist = self->private_data.s_init_dynamic_huffman[0].v_n_dist; + v_n_clen = self->private_data.s_init_dynamic_huffman[0].v_n_clen; + v_i = self->private_data.s_init_dynamic_huffman[0].v_i; + v_mask = self->private_data.s_init_dynamic_huffman[0].v_mask; + v_n_extra_bits = self->private_data.s_init_dynamic_huffman[0].v_n_extra_bits; + v_rep_symbol = self->private_data.s_init_dynamic_huffman[0].v_rep_symbol; + v_rep_count = self->private_data.s_init_dynamic_huffman[0].v_rep_count; + } + switch (coro_susp_point) { + WUFFS_BASE__COROUTINE_SUSPENSION_POINT_0; + + v_bits = self->private_impl.f_bits; + v_n_bits = self->private_impl.f_n_bits; + while (v_n_bits < 14) { + { + WUFFS_BASE__COROUTINE_SUSPENSION_POINT(1); + if (WUFFS_BASE__UNLIKELY(iop_a_src == io2_a_src)) { + status = wuffs_base__make_status(wuffs_base__suspension__short_read); + goto suspend; + } + uint32_t t_0 = *iop_a_src++; + v_b0 = t_0; + } + v_bits |= (v_b0 << v_n_bits); + v_n_bits += 8; + } + v_n_lit = (((v_bits) & 0x1F) + 257); + if (v_n_lit > 286) { + status = wuffs_base__make_status(wuffs_deflate__error__bad_literal_length_code_count); + goto exit; + } + v_bits >>= 5; + v_n_dist = (((v_bits) & 0x1F) + 1); + if (v_n_dist > 30) { + status = wuffs_base__make_status(wuffs_deflate__error__bad_distance_code_count); + goto exit; + } + v_bits >>= 5; + v_n_clen = (((v_bits) & 0xF) + 4); + v_bits >>= 4; + v_n_bits -= 14; + v_i = 0; + while (v_i < v_n_clen) { + while (v_n_bits < 3) { + { + WUFFS_BASE__COROUTINE_SUSPENSION_POINT(2); + if (WUFFS_BASE__UNLIKELY(iop_a_src == io2_a_src)) { + status = wuffs_base__make_status(wuffs_base__suspension__short_read); + goto suspend; + } + uint32_t t_1 = *iop_a_src++; + v_b1 = t_1; + } + v_bits |= (v_b1 << v_n_bits); + v_n_bits += 8; + } + self->private_data.f_code_lengths[WUFFS_DEFLATE__CODE_ORDER[v_i]] = ((uint8_t)((v_bits & 7))); + v_bits >>= 3; + v_n_bits -= 3; + v_i += 1; + } + while (v_i < 19) { + self->private_data.f_code_lengths[WUFFS_DEFLATE__CODE_ORDER[v_i]] = 0; + v_i += 1; + } + v_status = wuffs_deflate__decoder__init_huff(self, + 0, + 0, + 19, + 4095); + if (wuffs_base__status__is_error(&v_status)) { + status = v_status; + goto exit; + } + v_mask = ((((uint32_t)(1)) << self->private_impl.f_n_huffs_bits[0]) - 1); + v_i = 0; + label__0__continue:; + while (v_i < (v_n_lit + v_n_dist)) { + while (true) { + v_table_entry = self->private_data.f_huffs[0][(v_bits & v_mask)]; + v_table_entry_n_bits = (v_table_entry & 15); + if (v_n_bits >= v_table_entry_n_bits) { + v_bits >>= v_table_entry_n_bits; + v_n_bits -= v_table_entry_n_bits; + goto label__1__break; + } + { + WUFFS_BASE__COROUTINE_SUSPENSION_POINT(3); + if (WUFFS_BASE__UNLIKELY(iop_a_src == io2_a_src)) { + status = wuffs_base__make_status(wuffs_base__suspension__short_read); + goto suspend; + } + uint32_t t_2 = *iop_a_src++; + v_b2 = t_2; + } + v_bits |= (v_b2 << v_n_bits); + v_n_bits += 8; + } + label__1__break:; + if ((v_table_entry >> 24) != 128) { + status = wuffs_base__make_status(wuffs_deflate__error__internal_error_inconsistent_huffman_decoder_state); + goto exit; + } + v_table_entry = ((v_table_entry >> 8) & 255); + if (v_table_entry < 16) { + self->private_data.f_code_lengths[v_i] = ((uint8_t)(v_table_entry)); + v_i += 1; + goto label__0__continue; + } + v_n_extra_bits = 0; + v_rep_symbol = 0; + v_rep_count = 0; + if (v_table_entry == 16) { + v_n_extra_bits = 2; + if (v_i <= 0) { + status = wuffs_base__make_status(wuffs_deflate__error__bad_huffman_code_length_repetition); + goto exit; + } + v_rep_symbol = (self->private_data.f_code_lengths[(v_i - 1)] & 15); + v_rep_count = 3; + } else if (v_table_entry == 17) { + v_n_extra_bits = 3; + v_rep_symbol = 0; + v_rep_count = 3; + } else if (v_table_entry == 18) { + v_n_extra_bits = 7; + v_rep_symbol = 0; + v_rep_count = 11; + } else { + status = wuffs_base__make_status(wuffs_deflate__error__internal_error_inconsistent_huffman_decoder_state); + goto exit; + } + while (v_n_bits < v_n_extra_bits) { + { + WUFFS_BASE__COROUTINE_SUSPENSION_POINT(4); + if (WUFFS_BASE__UNLIKELY(iop_a_src == io2_a_src)) { + status = wuffs_base__make_status(wuffs_base__suspension__short_read); + goto suspend; + } + uint32_t t_3 = *iop_a_src++; + v_b3 = t_3; + } + v_bits |= (v_b3 << v_n_bits); + v_n_bits += 8; + } + v_rep_count += ((v_bits) & WUFFS_BASE__LOW_BITS_MASK__U32(v_n_extra_bits)); + v_bits >>= v_n_extra_bits; + v_n_bits -= v_n_extra_bits; + while (v_rep_count > 0) { + if (v_i >= (v_n_lit + v_n_dist)) { + status = wuffs_base__make_status(wuffs_deflate__error__bad_huffman_code_length_count); + goto exit; + } + self->private_data.f_code_lengths[v_i] = v_rep_symbol; + v_i += 1; + v_rep_count -= 1; + } + } + if (v_i != (v_n_lit + v_n_dist)) { + status = wuffs_base__make_status(wuffs_deflate__error__bad_huffman_code_length_count); + goto exit; + } + if (self->private_data.f_code_lengths[256] == 0) { + status = wuffs_base__make_status(wuffs_deflate__error__missing_end_of_block_code); + goto exit; + } + v_status = wuffs_deflate__decoder__init_huff(self, + 0, + 0, + v_n_lit, + 257); + if (wuffs_base__status__is_error(&v_status)) { + status = v_status; + goto exit; + } + v_status = wuffs_deflate__decoder__init_huff(self, + 1, + v_n_lit, + (v_n_lit + v_n_dist), + 0); + if (wuffs_base__status__is_error(&v_status)) { + status = v_status; + goto exit; + } + self->private_impl.f_bits = v_bits; + self->private_impl.f_n_bits = v_n_bits; + + goto ok; + ok: + self->private_impl.p_init_dynamic_huffman[0] = 0; + goto exit; + } + + goto suspend; + suspend: + self->private_impl.p_init_dynamic_huffman[0] = wuffs_base__status__is_suspension(&status) ? coro_susp_point : 0; + self->private_data.s_init_dynamic_huffman[0].v_bits = v_bits; + self->private_data.s_init_dynamic_huffman[0].v_n_bits = v_n_bits; + self->private_data.s_init_dynamic_huffman[0].v_n_lit = v_n_lit; + self->private_data.s_init_dynamic_huffman[0].v_n_dist = v_n_dist; + self->private_data.s_init_dynamic_huffman[0].v_n_clen = v_n_clen; + self->private_data.s_init_dynamic_huffman[0].v_i = v_i; + self->private_data.s_init_dynamic_huffman[0].v_mask = v_mask; + self->private_data.s_init_dynamic_huffman[0].v_n_extra_bits = v_n_extra_bits; + self->private_data.s_init_dynamic_huffman[0].v_rep_symbol = v_rep_symbol; + self->private_data.s_init_dynamic_huffman[0].v_rep_count = v_rep_count; + + goto exit; + exit: + if (a_src && a_src->data.ptr) { + a_src->meta.ri = ((size_t)(iop_a_src - a_src->data.ptr)); + } + + return status; +} + +// -------- func deflate.decoder.init_huff + +static wuffs_base__status +wuffs_deflate__decoder__init_huff( + wuffs_deflate__decoder* self, + uint32_t a_which, + uint32_t a_n_codes0, + uint32_t a_n_codes1, + uint32_t a_base_symbol) { + uint16_t v_counts[16] = {0}; + uint32_t v_i = 0; + uint32_t v_remaining = 0; + uint16_t v_offsets[16] = {0}; + uint32_t v_n_symbols = 0; + uint32_t v_count = 0; + uint16_t v_symbols[320] = {0}; + uint32_t v_min_cl = 0; + uint32_t v_max_cl = 0; + uint32_t v_initial_high_bits = 0; + uint32_t v_prev_cl = 0; + uint32_t v_prev_redirect_key = 0; + uint32_t v_top = 0; + uint32_t v_next_top = 0; + uint32_t v_code = 0; + uint32_t v_key = 0; + uint32_t v_value = 0; + uint32_t v_cl = 0; + uint32_t v_redirect_key = 0; + uint32_t v_j = 0; + uint32_t v_reversed_key = 0; + uint32_t v_symbol = 0; + uint32_t v_high_bits = 0; + uint32_t v_delta = 0; + + v_i = a_n_codes0; + while (v_i < a_n_codes1) { + if (v_counts[(self->private_data.f_code_lengths[v_i] & 15)] >= 320) { + return wuffs_base__make_status(wuffs_deflate__error__internal_error_inconsistent_huffman_decoder_state); + } +#if defined(__GNUC__) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wconversion" +#endif + v_counts[(self->private_data.f_code_lengths[v_i] & 15)] += 1; +#if defined(__GNUC__) +#pragma GCC diagnostic pop +#endif + v_i += 1; + } + if ((((uint32_t)(v_counts[0])) + a_n_codes0) == a_n_codes1) { + return wuffs_base__make_status(wuffs_deflate__error__no_huffman_codes); + } + v_remaining = 1; + v_i = 1; + while (v_i <= 15) { + if (v_remaining > 1073741824) { + return wuffs_base__make_status(wuffs_deflate__error__internal_error_inconsistent_huffman_decoder_state); + } + v_remaining <<= 1; + if (v_remaining < ((uint32_t)(v_counts[v_i]))) { + return wuffs_base__make_status(wuffs_deflate__error__bad_huffman_code_over_subscribed); + } + v_remaining -= ((uint32_t)(v_counts[v_i])); + v_i += 1; + } + if (v_remaining != 0) { + if ((a_which == 1) && (v_counts[1] == 1) && ((((uint32_t)(v_counts[0])) + a_n_codes0 + 1) == a_n_codes1)) { + v_i = 0; + while (v_i <= 29) { + if (self->private_data.f_code_lengths[(a_n_codes0 + v_i)] == 1) { + self->private_impl.f_n_huffs_bits[1] = 1; + self->private_data.f_huffs[1][0] = (WUFFS_DEFLATE__DCODE_MAGIC_NUMBERS[v_i] | 1); + self->private_data.f_huffs[1][1] = (WUFFS_DEFLATE__DCODE_MAGIC_NUMBERS[31] | 1); + return wuffs_base__make_status(NULL); + } + v_i += 1; + } + } + return wuffs_base__make_status(wuffs_deflate__error__bad_huffman_code_under_subscribed); + } + v_i = 1; + while (v_i <= 15) { + v_offsets[v_i] = ((uint16_t)(v_n_symbols)); + v_count = ((uint32_t)(v_counts[v_i])); + if (v_n_symbols > (320 - v_count)) { + return wuffs_base__make_status(wuffs_deflate__error__internal_error_inconsistent_huffman_decoder_state); + } + v_n_symbols = (v_n_symbols + v_count); + v_i += 1; + } + if (v_n_symbols > 288) { + return wuffs_base__make_status(wuffs_deflate__error__internal_error_inconsistent_huffman_decoder_state); + } + v_i = a_n_codes0; + while (v_i < a_n_codes1) { + if (v_i < a_n_codes0) { + return wuffs_base__make_status(wuffs_deflate__error__internal_error_inconsistent_huffman_decoder_state); + } + if (self->private_data.f_code_lengths[v_i] != 0) { + if (v_offsets[(self->private_data.f_code_lengths[v_i] & 15)] >= 320) { + return wuffs_base__make_status(wuffs_deflate__error__internal_error_inconsistent_huffman_decoder_state); + } + v_symbols[v_offsets[(self->private_data.f_code_lengths[v_i] & 15)]] = ((uint16_t)((v_i - a_n_codes0))); +#if defined(__GNUC__) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wconversion" +#endif + v_offsets[(self->private_data.f_code_lengths[v_i] & 15)] += 1; +#if defined(__GNUC__) +#pragma GCC diagnostic pop +#endif + } + v_i += 1; + } + v_min_cl = 1; + while (true) { + if (v_counts[v_min_cl] != 0) { + goto label__0__break; + } + if (v_min_cl >= 9) { + return wuffs_base__make_status(wuffs_deflate__error__bad_huffman_minimum_code_length); + } + v_min_cl += 1; + } + label__0__break:; + v_max_cl = 15; + while (true) { + if (v_counts[v_max_cl] != 0) { + goto label__1__break; + } + if (v_max_cl <= 1) { + return wuffs_base__make_status(wuffs_deflate__error__no_huffman_codes); + } + v_max_cl -= 1; + } + label__1__break:; + if (v_max_cl <= 9) { + self->private_impl.f_n_huffs_bits[a_which] = v_max_cl; + } else { + self->private_impl.f_n_huffs_bits[a_which] = 9; + } + v_i = 0; + if ((v_n_symbols != ((uint32_t)(v_offsets[v_max_cl]))) || (v_n_symbols != ((uint32_t)(v_offsets[15])))) { + return wuffs_base__make_status(wuffs_deflate__error__internal_error_inconsistent_huffman_decoder_state); + } + if ((a_n_codes0 + ((uint32_t)(v_symbols[0]))) >= 320) { + return wuffs_base__make_status(wuffs_deflate__error__internal_error_inconsistent_huffman_decoder_state); + } + v_initial_high_bits = 512; + if (v_max_cl < 9) { + v_initial_high_bits = (((uint32_t)(1)) << v_max_cl); + } + v_prev_cl = ((uint32_t)((self->private_data.f_code_lengths[(a_n_codes0 + ((uint32_t)(v_symbols[0])))] & 15))); + v_prev_redirect_key = 4294967295; + v_top = 0; + v_next_top = 512; + v_code = 0; + v_key = 0; + v_value = 0; + while (true) { + if ((a_n_codes0 + ((uint32_t)(v_symbols[v_i]))) >= 320) { + return wuffs_base__make_status(wuffs_deflate__error__internal_error_inconsistent_huffman_decoder_state); + } + v_cl = ((uint32_t)((self->private_data.f_code_lengths[(a_n_codes0 + ((uint32_t)(v_symbols[v_i])))] & 15))); + if (v_cl > v_prev_cl) { + v_code <<= (v_cl - v_prev_cl); + if (v_code >= 32768) { + return wuffs_base__make_status(wuffs_deflate__error__internal_error_inconsistent_huffman_decoder_state); + } + } + v_prev_cl = v_cl; + v_key = v_code; + if (v_cl > 9) { + v_cl -= 9; + v_redirect_key = ((v_key >> v_cl) & 511); + v_key = ((v_key) & WUFFS_BASE__LOW_BITS_MASK__U32(v_cl)); + if (v_prev_redirect_key != v_redirect_key) { + v_prev_redirect_key = v_redirect_key; + v_remaining = (((uint32_t)(1)) << v_cl); + v_j = v_prev_cl; + while (v_j <= 15) { + if (v_remaining <= ((uint32_t)(v_counts[v_j]))) { + goto label__2__break; + } + v_remaining -= ((uint32_t)(v_counts[v_j])); + if (v_remaining > 1073741824) { + return wuffs_base__make_status(wuffs_deflate__error__internal_error_inconsistent_huffman_decoder_state); + } + v_remaining <<= 1; + v_j += 1; + } + label__2__break:; + if ((v_j <= 9) || (15 < v_j)) { + return wuffs_base__make_status(wuffs_deflate__error__internal_error_inconsistent_huffman_decoder_state); + } + v_j -= 9; + v_initial_high_bits = (((uint32_t)(1)) << v_j); + v_top = v_next_top; + if ((v_top + (((uint32_t)(1)) << v_j)) > 1024) { + return wuffs_base__make_status(wuffs_deflate__error__internal_error_inconsistent_huffman_decoder_state); + } + v_next_top = (v_top + (((uint32_t)(1)) << v_j)); + v_redirect_key = (((uint32_t)(WUFFS_DEFLATE__REVERSE8[(v_redirect_key >> 1)])) | ((v_redirect_key & 1) << 8)); + self->private_data.f_huffs[a_which][v_redirect_key] = (268435465 | (v_top << 8) | (v_j << 4)); + } + } + if ((v_key >= 512) || (v_counts[v_prev_cl] <= 0)) { + return wuffs_base__make_status(wuffs_deflate__error__internal_error_inconsistent_huffman_decoder_state); + } +#if defined(__GNUC__) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wconversion" +#endif + v_counts[v_prev_cl] -= 1; +#if defined(__GNUC__) +#pragma GCC diagnostic pop +#endif + v_reversed_key = (((uint32_t)(WUFFS_DEFLATE__REVERSE8[(v_key >> 1)])) | ((v_key & 1) << 8)); + v_reversed_key >>= (9 - v_cl); + v_symbol = ((uint32_t)(v_symbols[v_i])); + if (v_symbol == 256) { + v_value = (536870912 | v_cl); + } else if ((v_symbol < 256) && (a_which == 0)) { + v_value = (2147483648 | (v_symbol << 8) | v_cl); + } else if (v_symbol >= a_base_symbol) { + v_symbol -= a_base_symbol; + if (a_which == 0) { + v_value = (WUFFS_DEFLATE__LCODE_MAGIC_NUMBERS[(v_symbol & 31)] | v_cl); + } else { + v_value = (WUFFS_DEFLATE__DCODE_MAGIC_NUMBERS[(v_symbol & 31)] | v_cl); + } + } else { + return wuffs_base__make_status(wuffs_deflate__error__internal_error_inconsistent_huffman_decoder_state); + } + v_high_bits = v_initial_high_bits; + v_delta = (((uint32_t)(1)) << v_cl); + while (v_high_bits >= v_delta) { + v_high_bits -= v_delta; + if ((v_top + ((v_high_bits | v_reversed_key) & 511)) >= 1024) { + return wuffs_base__make_status(wuffs_deflate__error__internal_error_inconsistent_huffman_decoder_state); + } + self->private_data.f_huffs[a_which][(v_top + ((v_high_bits | v_reversed_key) & 511))] = v_value; + } + v_i += 1; + if (v_i >= v_n_symbols) { + goto label__3__break; + } + v_code += 1; + if (v_code >= 32768) { + return wuffs_base__make_status(wuffs_deflate__error__internal_error_inconsistent_huffman_decoder_state); + } + } + label__3__break:; + return wuffs_base__make_status(NULL); +} + +// ‼ WUFFS MULTI-FILE SECTION +x86_bmi2 +// -------- func deflate.decoder.decode_huffman_bmi2 + +#if defined(WUFFS_BASE__CPU_ARCH__X86_FAMILY) +WUFFS_BASE__MAYBE_ATTRIBUTE_TARGET("bmi2") +static wuffs_base__status +wuffs_deflate__decoder__decode_huffman_bmi2( + wuffs_deflate__decoder* self, + wuffs_base__io_buffer* a_dst, + wuffs_base__io_buffer* a_src) { + wuffs_base__status status = wuffs_base__make_status(NULL); + + uint64_t v_bits = 0; + uint32_t v_n_bits = 0; + uint32_t v_table_entry = 0; + uint32_t v_table_entry_n_bits = 0; + uint64_t v_lmask = 0; + uint64_t v_dmask = 0; + uint32_t v_redir_top = 0; + uint32_t v_redir_mask = 0; + uint32_t v_length = 0; + uint32_t v_dist_minus_1 = 0; + uint32_t v_hlen = 0; + uint32_t v_hdist = 0; + uint32_t v_hdist_adjustment = 0; + + uint8_t* iop_a_dst = NULL; + uint8_t* io0_a_dst WUFFS_BASE__POTENTIALLY_UNUSED = NULL; + uint8_t* io1_a_dst WUFFS_BASE__POTENTIALLY_UNUSED = NULL; + uint8_t* io2_a_dst WUFFS_BASE__POTENTIALLY_UNUSED = NULL; + if (a_dst && a_dst->data.ptr) { + io0_a_dst = a_dst->data.ptr; + io1_a_dst = io0_a_dst + a_dst->meta.wi; + iop_a_dst = io1_a_dst; + io2_a_dst = io0_a_dst + a_dst->data.len; + if (a_dst->meta.closed) { + io2_a_dst = iop_a_dst; + } + } + const uint8_t* iop_a_src = NULL; + const uint8_t* io0_a_src WUFFS_BASE__POTENTIALLY_UNUSED = NULL; + const uint8_t* io1_a_src WUFFS_BASE__POTENTIALLY_UNUSED = NULL; + const uint8_t* io2_a_src WUFFS_BASE__POTENTIALLY_UNUSED = NULL; + if (a_src && a_src->data.ptr) { + io0_a_src = a_src->data.ptr; + io1_a_src = io0_a_src + a_src->meta.ri; + iop_a_src = io1_a_src; + io2_a_src = io0_a_src + a_src->meta.wi; + } + + if ((self->private_impl.f_n_bits >= 8) || ((self->private_impl.f_bits >> (self->private_impl.f_n_bits & 7)) != 0)) { + status = wuffs_base__make_status(wuffs_deflate__error__internal_error_inconsistent_n_bits); + goto exit; + } + v_bits = ((uint64_t)(self->private_impl.f_bits)); + v_n_bits = self->private_impl.f_n_bits; + v_lmask = ((((uint64_t)(1)) << self->private_impl.f_n_huffs_bits[0]) - 1); + v_dmask = ((((uint64_t)(1)) << self->private_impl.f_n_huffs_bits[1]) - 1); + if (self->private_impl.f_transformed_history_count < (a_dst ? a_dst->meta.pos : 0)) { + status = wuffs_base__make_status(wuffs_base__error__bad_i_o_position); + goto exit; + } + v_hdist_adjustment = ((uint32_t)(((self->private_impl.f_transformed_history_count - (a_dst ? a_dst->meta.pos : 0)) & 4294967295))); + label__loop__continue:; + while ((((uint64_t)(io2_a_dst - iop_a_dst)) >= 266) && (((uint64_t)(io2_a_src - iop_a_src)) >= 8)) { + v_bits |= ((uint64_t)(wuffs_base__peek_u64le__no_bounds_check(iop_a_src) << (v_n_bits & 63))); + iop_a_src += ((63 - (v_n_bits & 63)) >> 3); + v_n_bits |= 56; + v_table_entry = self->private_data.f_huffs[0][(v_bits & v_lmask)]; + v_table_entry_n_bits = (v_table_entry & 15); + v_bits >>= v_table_entry_n_bits; + v_n_bits -= v_table_entry_n_bits; + if ((v_table_entry >> 31) != 0) { + (wuffs_base__poke_u8be__no_bounds_check(iop_a_dst, ((uint8_t)(((v_table_entry >> 8) & 255)))), iop_a_dst += 1); + goto label__loop__continue; + } else if ((v_table_entry >> 30) != 0) { + } else if ((v_table_entry >> 29) != 0) { + self->private_impl.f_end_of_block = true; + goto label__loop__break; + } else if ((v_table_entry >> 28) != 0) { + v_redir_top = ((v_table_entry >> 8) & 65535); + v_redir_mask = ((((uint32_t)(1)) << ((v_table_entry >> 4) & 15)) - 1); + v_table_entry = self->private_data.f_huffs[0][((v_redir_top + (((uint32_t)((v_bits & 4294967295))) & v_redir_mask)) & 1023)]; + v_table_entry_n_bits = (v_table_entry & 15); + v_bits >>= v_table_entry_n_bits; + v_n_bits -= v_table_entry_n_bits; + if ((v_table_entry >> 31) != 0) { + (wuffs_base__poke_u8be__no_bounds_check(iop_a_dst, ((uint8_t)(((v_table_entry >> 8) & 255)))), iop_a_dst += 1); + goto label__loop__continue; + } else if ((v_table_entry >> 30) != 0) { + } else if ((v_table_entry >> 29) != 0) { + self->private_impl.f_end_of_block = true; + goto label__loop__break; + } else if ((v_table_entry >> 28) != 0) { + status = wuffs_base__make_status(wuffs_deflate__error__internal_error_inconsistent_huffman_decoder_state); + goto exit; + } else if ((v_table_entry >> 27) != 0) { + status = wuffs_base__make_status(wuffs_deflate__error__bad_huffman_code); + goto exit; + } else { + status = wuffs_base__make_status(wuffs_deflate__error__internal_error_inconsistent_huffman_decoder_state); + goto exit; + } + } else if ((v_table_entry >> 27) != 0) { + status = wuffs_base__make_status(wuffs_deflate__error__bad_huffman_code); + goto exit; + } else { + status = wuffs_base__make_status(wuffs_deflate__error__internal_error_inconsistent_huffman_decoder_state); + goto exit; + } + v_length = (((v_table_entry >> 8) & 255) + 3); + v_table_entry_n_bits = ((v_table_entry >> 4) & 15); + if (v_table_entry_n_bits > 0) { + v_length = (((v_length + 253 + ((uint32_t)(((v_bits) & WUFFS_BASE__LOW_BITS_MASK__U64(v_table_entry_n_bits))))) & 255) + 3); + v_bits >>= v_table_entry_n_bits; + v_n_bits -= v_table_entry_n_bits; + } + v_table_entry = self->private_data.f_huffs[1][(v_bits & v_dmask)]; + v_table_entry_n_bits = (v_table_entry & 15); + v_bits >>= v_table_entry_n_bits; + v_n_bits -= v_table_entry_n_bits; + if ((v_table_entry >> 28) == 1) { + v_redir_top = ((v_table_entry >> 8) & 65535); + v_redir_mask = ((((uint32_t)(1)) << ((v_table_entry >> 4) & 15)) - 1); + v_table_entry = self->private_data.f_huffs[1][((v_redir_top + (((uint32_t)((v_bits & 4294967295))) & v_redir_mask)) & 1023)]; + v_table_entry_n_bits = (v_table_entry & 15); + v_bits >>= v_table_entry_n_bits; + v_n_bits -= v_table_entry_n_bits; + } + if ((v_table_entry >> 24) != 64) { + if ((v_table_entry >> 24) == 8) { + status = wuffs_base__make_status(wuffs_deflate__error__bad_huffman_code); + goto exit; + } + status = wuffs_base__make_status(wuffs_deflate__error__internal_error_inconsistent_huffman_decoder_state); + goto exit; + } + v_dist_minus_1 = ((v_table_entry >> 8) & 32767); + v_table_entry_n_bits = ((v_table_entry >> 4) & 15); + v_dist_minus_1 = ((v_dist_minus_1 + ((uint32_t)(((v_bits) & WUFFS_BASE__LOW_BITS_MASK__U64(v_table_entry_n_bits))))) & 32767); + v_bits >>= v_table_entry_n_bits; + v_n_bits -= v_table_entry_n_bits; + while (true) { + if (((uint64_t)((v_dist_minus_1 + 1))) > ((uint64_t)(iop_a_dst - io0_a_dst))) { + v_hlen = 0; + v_hdist = ((uint32_t)((((uint64_t)((v_dist_minus_1 + 1))) - ((uint64_t)(iop_a_dst - io0_a_dst))))); + if (v_length > v_hdist) { + v_length -= v_hdist; + v_hlen = v_hdist; + } else { + v_hlen = v_length; + v_length = 0; + } + v_hdist += v_hdist_adjustment; + if (self->private_impl.f_history_index < v_hdist) { + status = wuffs_base__make_status(wuffs_deflate__error__bad_distance); + goto exit; + } + wuffs_base__io_writer__limited_copy_u32_from_slice( + &iop_a_dst, io2_a_dst,v_hlen, wuffs_base__make_slice_u8_ij(self->private_data.f_history, ((self->private_impl.f_history_index - v_hdist) & 32767), 33025)); + if (v_length == 0) { + goto label__loop__continue; + } + if ((((uint64_t)((v_dist_minus_1 + 1))) > ((uint64_t)(iop_a_dst - io0_a_dst))) || (((uint64_t)(v_length)) > ((uint64_t)(io2_a_dst - iop_a_dst))) || (((uint64_t)((v_length + 8))) > ((uint64_t)(io2_a_dst - iop_a_dst)))) { + status = wuffs_base__make_status(wuffs_deflate__error__internal_error_inconsistent_distance); + goto exit; + } + } + if ((v_dist_minus_1 + 1) >= 8) { + wuffs_base__io_writer__limited_copy_u32_from_history_8_byte_chunks_fast( + &iop_a_dst, io0_a_dst, io2_a_dst, v_length, (v_dist_minus_1 + 1)); + } else if ((v_dist_minus_1 + 1) == 1) { + wuffs_base__io_writer__limited_copy_u32_from_history_8_byte_chunks_distance_1_fast( + &iop_a_dst, io0_a_dst, io2_a_dst, v_length, (v_dist_minus_1 + 1)); + } else { + wuffs_base__io_writer__limited_copy_u32_from_history_fast( + &iop_a_dst, io0_a_dst, io2_a_dst, v_length, (v_dist_minus_1 + 1)); + } + goto label__0__break; + } + label__0__break:; + } + label__loop__break:; + if (v_n_bits > 63) { + status = wuffs_base__make_status(wuffs_deflate__error__internal_error_inconsistent_n_bits); + goto exit; + } + while (v_n_bits >= 8) { + v_n_bits -= 8; + if (iop_a_src > io1_a_src) { + iop_a_src--; + } else { + status = wuffs_base__make_status(wuffs_deflate__error__internal_error_inconsistent_i_o); + goto exit; + } + } + self->private_impl.f_bits = ((uint32_t)((v_bits & ((((uint64_t)(1)) << v_n_bits) - 1)))); + self->private_impl.f_n_bits = v_n_bits; + if ((self->private_impl.f_n_bits >= 8) || ((self->private_impl.f_bits >> self->private_impl.f_n_bits) != 0)) { + status = wuffs_base__make_status(wuffs_deflate__error__internal_error_inconsistent_n_bits); + goto exit; + } + goto exit; + exit: + if (a_dst && a_dst->data.ptr) { + a_dst->meta.wi = ((size_t)(iop_a_dst - a_dst->data.ptr)); + } + if (a_src && a_src->data.ptr) { + a_src->meta.ri = ((size_t)(iop_a_src - a_src->data.ptr)); + } + + return status; +} +#endif // defined(WUFFS_BASE__CPU_ARCH__X86_FAMILY) +// ‼ WUFFS MULTI-FILE SECTION -x86_bmi2 + +// -------- func deflate.decoder.decode_huffman_fast32 + +static wuffs_base__status +wuffs_deflate__decoder__decode_huffman_fast32( + wuffs_deflate__decoder* self, + wuffs_base__io_buffer* a_dst, + wuffs_base__io_buffer* a_src) { + wuffs_base__status status = wuffs_base__make_status(NULL); + + uint32_t v_bits = 0; + uint32_t v_n_bits = 0; + uint32_t v_table_entry = 0; + uint32_t v_table_entry_n_bits = 0; + uint32_t v_lmask = 0; + uint32_t v_dmask = 0; + uint32_t v_redir_top = 0; + uint32_t v_redir_mask = 0; + uint32_t v_length = 0; + uint32_t v_dist_minus_1 = 0; + uint32_t v_hlen = 0; + uint32_t v_hdist = 0; + uint32_t v_hdist_adjustment = 0; + + uint8_t* iop_a_dst = NULL; + uint8_t* io0_a_dst WUFFS_BASE__POTENTIALLY_UNUSED = NULL; + uint8_t* io1_a_dst WUFFS_BASE__POTENTIALLY_UNUSED = NULL; + uint8_t* io2_a_dst WUFFS_BASE__POTENTIALLY_UNUSED = NULL; + if (a_dst && a_dst->data.ptr) { + io0_a_dst = a_dst->data.ptr; + io1_a_dst = io0_a_dst + a_dst->meta.wi; + iop_a_dst = io1_a_dst; + io2_a_dst = io0_a_dst + a_dst->data.len; + if (a_dst->meta.closed) { + io2_a_dst = iop_a_dst; + } + } + const uint8_t* iop_a_src = NULL; + const uint8_t* io0_a_src WUFFS_BASE__POTENTIALLY_UNUSED = NULL; + const uint8_t* io1_a_src WUFFS_BASE__POTENTIALLY_UNUSED = NULL; + const uint8_t* io2_a_src WUFFS_BASE__POTENTIALLY_UNUSED = NULL; + if (a_src && a_src->data.ptr) { + io0_a_src = a_src->data.ptr; + io1_a_src = io0_a_src + a_src->meta.ri; + iop_a_src = io1_a_src; + io2_a_src = io0_a_src + a_src->meta.wi; + } + + if ((self->private_impl.f_n_bits >= 8) || ((self->private_impl.f_bits >> (self->private_impl.f_n_bits & 7)) != 0)) { + status = wuffs_base__make_status(wuffs_deflate__error__internal_error_inconsistent_n_bits); + goto exit; + } + v_bits = self->private_impl.f_bits; + v_n_bits = self->private_impl.f_n_bits; + v_lmask = ((((uint32_t)(1)) << self->private_impl.f_n_huffs_bits[0]) - 1); + v_dmask = ((((uint32_t)(1)) << self->private_impl.f_n_huffs_bits[1]) - 1); + if (self->private_impl.f_transformed_history_count < (a_dst ? a_dst->meta.pos : 0)) { + status = wuffs_base__make_status(wuffs_base__error__bad_i_o_position); + goto exit; + } + v_hdist_adjustment = ((uint32_t)(((self->private_impl.f_transformed_history_count - (a_dst ? a_dst->meta.pos : 0)) & 4294967295))); + label__loop__continue:; + while ((((uint64_t)(io2_a_dst - iop_a_dst)) >= 266) && (((uint64_t)(io2_a_src - iop_a_src)) >= 12)) { + if (v_n_bits < 15) { + v_bits |= (((uint32_t)(wuffs_base__peek_u8be__no_bounds_check(iop_a_src))) << v_n_bits); + iop_a_src += 1; + v_n_bits += 8; + v_bits |= (((uint32_t)(wuffs_base__peek_u8be__no_bounds_check(iop_a_src))) << v_n_bits); + iop_a_src += 1; + v_n_bits += 8; + } else { + } + v_table_entry = self->private_data.f_huffs[0][(v_bits & v_lmask)]; + v_table_entry_n_bits = (v_table_entry & 15); + v_bits >>= v_table_entry_n_bits; + v_n_bits -= v_table_entry_n_bits; + if ((v_table_entry >> 31) != 0) { + (wuffs_base__poke_u8be__no_bounds_check(iop_a_dst, ((uint8_t)(((v_table_entry >> 8) & 255)))), iop_a_dst += 1); + goto label__loop__continue; + } else if ((v_table_entry >> 30) != 0) { + } else if ((v_table_entry >> 29) != 0) { + self->private_impl.f_end_of_block = true; + goto label__loop__break; + } else if ((v_table_entry >> 28) != 0) { + if (v_n_bits < 15) { + v_bits |= (((uint32_t)(wuffs_base__peek_u8be__no_bounds_check(iop_a_src))) << v_n_bits); + iop_a_src += 1; + v_n_bits += 8; + v_bits |= (((uint32_t)(wuffs_base__peek_u8be__no_bounds_check(iop_a_src))) << v_n_bits); + iop_a_src += 1; + v_n_bits += 8; + } else { + } + v_redir_top = ((v_table_entry >> 8) & 65535); + v_redir_mask = ((((uint32_t)(1)) << ((v_table_entry >> 4) & 15)) - 1); + v_table_entry = self->private_data.f_huffs[0][((v_redir_top + (v_bits & v_redir_mask)) & 1023)]; + v_table_entry_n_bits = (v_table_entry & 15); + v_bits >>= v_table_entry_n_bits; + v_n_bits -= v_table_entry_n_bits; + if ((v_table_entry >> 31) != 0) { + (wuffs_base__poke_u8be__no_bounds_check(iop_a_dst, ((uint8_t)(((v_table_entry >> 8) & 255)))), iop_a_dst += 1); + goto label__loop__continue; + } else if ((v_table_entry >> 30) != 0) { + } else if ((v_table_entry >> 29) != 0) { + self->private_impl.f_end_of_block = true; + goto label__loop__break; + } else if ((v_table_entry >> 28) != 0) { + status = wuffs_base__make_status(wuffs_deflate__error__internal_error_inconsistent_huffman_decoder_state); + goto exit; + } else if ((v_table_entry >> 27) != 0) { + status = wuffs_base__make_status(wuffs_deflate__error__bad_huffman_code); + goto exit; + } else { + status = wuffs_base__make_status(wuffs_deflate__error__internal_error_inconsistent_huffman_decoder_state); + goto exit; + } + } else if ((v_table_entry >> 27) != 0) { + status = wuffs_base__make_status(wuffs_deflate__error__bad_huffman_code); + goto exit; + } else { + status = wuffs_base__make_status(wuffs_deflate__error__internal_error_inconsistent_huffman_decoder_state); + goto exit; + } + v_length = (((v_table_entry >> 8) & 255) + 3); + v_table_entry_n_bits = ((v_table_entry >> 4) & 15); + if (v_table_entry_n_bits > 0) { + if (v_n_bits < 15) { + v_bits |= (((uint32_t)(wuffs_base__peek_u8be__no_bounds_check(iop_a_src))) << v_n_bits); + iop_a_src += 1; + v_n_bits += 8; + v_bits |= (((uint32_t)(wuffs_base__peek_u8be__no_bounds_check(iop_a_src))) << v_n_bits); + iop_a_src += 1; + v_n_bits += 8; + } else { + } + v_length = (((v_length + 253 + ((v_bits) & WUFFS_BASE__LOW_BITS_MASK__U32(v_table_entry_n_bits))) & 255) + 3); + v_bits >>= v_table_entry_n_bits; + v_n_bits -= v_table_entry_n_bits; + } else { + } + if (v_n_bits < 15) { + v_bits |= (((uint32_t)(wuffs_base__peek_u8be__no_bounds_check(iop_a_src))) << v_n_bits); + iop_a_src += 1; + v_n_bits += 8; + v_bits |= (((uint32_t)(wuffs_base__peek_u8be__no_bounds_check(iop_a_src))) << v_n_bits); + iop_a_src += 1; + v_n_bits += 8; + } else { + } + v_table_entry = self->private_data.f_huffs[1][(v_bits & v_dmask)]; + v_table_entry_n_bits = (v_table_entry & 15); + v_bits >>= v_table_entry_n_bits; + v_n_bits -= v_table_entry_n_bits; + if ((v_table_entry >> 28) == 1) { + if (v_n_bits < 15) { + v_bits |= (((uint32_t)(wuffs_base__peek_u8be__no_bounds_check(iop_a_src))) << v_n_bits); + iop_a_src += 1; + v_n_bits += 8; + v_bits |= (((uint32_t)(wuffs_base__peek_u8be__no_bounds_check(iop_a_src))) << v_n_bits); + iop_a_src += 1; + v_n_bits += 8; + } else { + } + v_redir_top = ((v_table_entry >> 8) & 65535); + v_redir_mask = ((((uint32_t)(1)) << ((v_table_entry >> 4) & 15)) - 1); + v_table_entry = self->private_data.f_huffs[1][((v_redir_top + (v_bits & v_redir_mask)) & 1023)]; + v_table_entry_n_bits = (v_table_entry & 15); + v_bits >>= v_table_entry_n_bits; + v_n_bits -= v_table_entry_n_bits; + } else { + } + if ((v_table_entry >> 24) != 64) { + if ((v_table_entry >> 24) == 8) { + status = wuffs_base__make_status(wuffs_deflate__error__bad_huffman_code); + goto exit; + } + status = wuffs_base__make_status(wuffs_deflate__error__internal_error_inconsistent_huffman_decoder_state); + goto exit; + } + v_dist_minus_1 = ((v_table_entry >> 8) & 32767); + v_table_entry_n_bits = ((v_table_entry >> 4) & 15); + if (v_n_bits < v_table_entry_n_bits) { + v_bits |= (((uint32_t)(wuffs_base__peek_u8be__no_bounds_check(iop_a_src))) << v_n_bits); + iop_a_src += 1; + v_n_bits += 8; + v_bits |= (((uint32_t)(wuffs_base__peek_u8be__no_bounds_check(iop_a_src))) << v_n_bits); + iop_a_src += 1; + v_n_bits += 8; + } + v_dist_minus_1 = ((v_dist_minus_1 + ((v_bits) & WUFFS_BASE__LOW_BITS_MASK__U32(v_table_entry_n_bits))) & 32767); + v_bits >>= v_table_entry_n_bits; + v_n_bits -= v_table_entry_n_bits; + while (true) { + if (((uint64_t)((v_dist_minus_1 + 1))) > ((uint64_t)(iop_a_dst - io0_a_dst))) { + v_hlen = 0; + v_hdist = ((uint32_t)((((uint64_t)((v_dist_minus_1 + 1))) - ((uint64_t)(iop_a_dst - io0_a_dst))))); + if (v_length > v_hdist) { + v_length -= v_hdist; + v_hlen = v_hdist; + } else { + v_hlen = v_length; + v_length = 0; + } + v_hdist += v_hdist_adjustment; + if (self->private_impl.f_history_index < v_hdist) { + status = wuffs_base__make_status(wuffs_deflate__error__bad_distance); + goto exit; + } + wuffs_base__io_writer__limited_copy_u32_from_slice( + &iop_a_dst, io2_a_dst,v_hlen, wuffs_base__make_slice_u8_ij(self->private_data.f_history, ((self->private_impl.f_history_index - v_hdist) & 32767), 33025)); + if (v_length == 0) { + goto label__loop__continue; + } + if ((((uint64_t)((v_dist_minus_1 + 1))) > ((uint64_t)(iop_a_dst - io0_a_dst))) || (((uint64_t)(v_length)) > ((uint64_t)(io2_a_dst - iop_a_dst))) || (((uint64_t)((v_length + 8))) > ((uint64_t)(io2_a_dst - iop_a_dst)))) { + status = wuffs_base__make_status(wuffs_deflate__error__internal_error_inconsistent_distance); + goto exit; + } + } + if ((v_dist_minus_1 + 1) >= 8) { + wuffs_base__io_writer__limited_copy_u32_from_history_8_byte_chunks_fast( + &iop_a_dst, io0_a_dst, io2_a_dst, v_length, (v_dist_minus_1 + 1)); + } else { + wuffs_base__io_writer__limited_copy_u32_from_history_fast( + &iop_a_dst, io0_a_dst, io2_a_dst, v_length, (v_dist_minus_1 + 1)); + } + goto label__0__break; + } + label__0__break:; + } + label__loop__break:; + while (v_n_bits >= 8) { + v_n_bits -= 8; + if (iop_a_src > io1_a_src) { + iop_a_src--; + } else { + status = wuffs_base__make_status(wuffs_deflate__error__internal_error_inconsistent_i_o); + goto exit; + } + } + self->private_impl.f_bits = (v_bits & ((((uint32_t)(1)) << v_n_bits) - 1)); + self->private_impl.f_n_bits = v_n_bits; + if ((self->private_impl.f_n_bits >= 8) || ((self->private_impl.f_bits >> self->private_impl.f_n_bits) != 0)) { + status = wuffs_base__make_status(wuffs_deflate__error__internal_error_inconsistent_n_bits); + goto exit; + } + goto exit; + exit: + if (a_dst && a_dst->data.ptr) { + a_dst->meta.wi = ((size_t)(iop_a_dst - a_dst->data.ptr)); + } + if (a_src && a_src->data.ptr) { + a_src->meta.ri = ((size_t)(iop_a_src - a_src->data.ptr)); + } + + return status; +} + +// -------- func deflate.decoder.decode_huffman_fast64 + +static wuffs_base__status +wuffs_deflate__decoder__decode_huffman_fast64( + wuffs_deflate__decoder* self, + wuffs_base__io_buffer* a_dst, + wuffs_base__io_buffer* a_src) { + return (*self->private_impl.choosy_decode_huffman_fast64)(self, a_dst, a_src); +} + +static wuffs_base__status +wuffs_deflate__decoder__decode_huffman_fast64__choosy_default( + wuffs_deflate__decoder* self, + wuffs_base__io_buffer* a_dst, + wuffs_base__io_buffer* a_src) { + wuffs_base__status status = wuffs_base__make_status(NULL); + + uint64_t v_bits = 0; + uint32_t v_n_bits = 0; + uint32_t v_table_entry = 0; + uint32_t v_table_entry_n_bits = 0; + uint64_t v_lmask = 0; + uint64_t v_dmask = 0; + uint32_t v_redir_top = 0; + uint32_t v_redir_mask = 0; + uint32_t v_length = 0; + uint32_t v_dist_minus_1 = 0; + uint32_t v_hlen = 0; + uint32_t v_hdist = 0; + uint32_t v_hdist_adjustment = 0; + + uint8_t* iop_a_dst = NULL; + uint8_t* io0_a_dst WUFFS_BASE__POTENTIALLY_UNUSED = NULL; + uint8_t* io1_a_dst WUFFS_BASE__POTENTIALLY_UNUSED = NULL; + uint8_t* io2_a_dst WUFFS_BASE__POTENTIALLY_UNUSED = NULL; + if (a_dst && a_dst->data.ptr) { + io0_a_dst = a_dst->data.ptr; + io1_a_dst = io0_a_dst + a_dst->meta.wi; + iop_a_dst = io1_a_dst; + io2_a_dst = io0_a_dst + a_dst->data.len; + if (a_dst->meta.closed) { + io2_a_dst = iop_a_dst; + } + } + const uint8_t* iop_a_src = NULL; + const uint8_t* io0_a_src WUFFS_BASE__POTENTIALLY_UNUSED = NULL; + const uint8_t* io1_a_src WUFFS_BASE__POTENTIALLY_UNUSED = NULL; + const uint8_t* io2_a_src WUFFS_BASE__POTENTIALLY_UNUSED = NULL; + if (a_src && a_src->data.ptr) { + io0_a_src = a_src->data.ptr; + io1_a_src = io0_a_src + a_src->meta.ri; + iop_a_src = io1_a_src; + io2_a_src = io0_a_src + a_src->meta.wi; + } + + if ((self->private_impl.f_n_bits >= 8) || ((self->private_impl.f_bits >> (self->private_impl.f_n_bits & 7)) != 0)) { + status = wuffs_base__make_status(wuffs_deflate__error__internal_error_inconsistent_n_bits); + goto exit; + } + v_bits = ((uint64_t)(self->private_impl.f_bits)); + v_n_bits = self->private_impl.f_n_bits; + v_lmask = ((((uint64_t)(1)) << self->private_impl.f_n_huffs_bits[0]) - 1); + v_dmask = ((((uint64_t)(1)) << self->private_impl.f_n_huffs_bits[1]) - 1); + if (self->private_impl.f_transformed_history_count < (a_dst ? a_dst->meta.pos : 0)) { + status = wuffs_base__make_status(wuffs_base__error__bad_i_o_position); + goto exit; + } + v_hdist_adjustment = ((uint32_t)(((self->private_impl.f_transformed_history_count - (a_dst ? a_dst->meta.pos : 0)) & 4294967295))); + label__loop__continue:; + while ((((uint64_t)(io2_a_dst - iop_a_dst)) >= 266) && (((uint64_t)(io2_a_src - iop_a_src)) >= 8)) { + v_bits |= ((uint64_t)(wuffs_base__peek_u64le__no_bounds_check(iop_a_src) << (v_n_bits & 63))); + iop_a_src += ((63 - (v_n_bits & 63)) >> 3); + v_n_bits |= 56; + v_table_entry = self->private_data.f_huffs[0][(v_bits & v_lmask)]; + v_table_entry_n_bits = (v_table_entry & 15); + v_bits >>= v_table_entry_n_bits; + v_n_bits -= v_table_entry_n_bits; + if ((v_table_entry >> 31) != 0) { + (wuffs_base__poke_u8be__no_bounds_check(iop_a_dst, ((uint8_t)(((v_table_entry >> 8) & 255)))), iop_a_dst += 1); + goto label__loop__continue; + } else if ((v_table_entry >> 30) != 0) { + } else if ((v_table_entry >> 29) != 0) { + self->private_impl.f_end_of_block = true; + goto label__loop__break; + } else if ((v_table_entry >> 28) != 0) { + v_redir_top = ((v_table_entry >> 8) & 65535); + v_redir_mask = ((((uint32_t)(1)) << ((v_table_entry >> 4) & 15)) - 1); + v_table_entry = self->private_data.f_huffs[0][((v_redir_top + (((uint32_t)((v_bits & 4294967295))) & v_redir_mask)) & 1023)]; + v_table_entry_n_bits = (v_table_entry & 15); + v_bits >>= v_table_entry_n_bits; + v_n_bits -= v_table_entry_n_bits; + if ((v_table_entry >> 31) != 0) { + (wuffs_base__poke_u8be__no_bounds_check(iop_a_dst, ((uint8_t)(((v_table_entry >> 8) & 255)))), iop_a_dst += 1); + goto label__loop__continue; + } else if ((v_table_entry >> 30) != 0) { + } else if ((v_table_entry >> 29) != 0) { + self->private_impl.f_end_of_block = true; + goto label__loop__break; + } else if ((v_table_entry >> 28) != 0) { + status = wuffs_base__make_status(wuffs_deflate__error__internal_error_inconsistent_huffman_decoder_state); + goto exit; + } else if ((v_table_entry >> 27) != 0) { + status = wuffs_base__make_status(wuffs_deflate__error__bad_huffman_code); + goto exit; + } else { + status = wuffs_base__make_status(wuffs_deflate__error__internal_error_inconsistent_huffman_decoder_state); + goto exit; + } + } else if ((v_table_entry >> 27) != 0) { + status = wuffs_base__make_status(wuffs_deflate__error__bad_huffman_code); + goto exit; + } else { + status = wuffs_base__make_status(wuffs_deflate__error__internal_error_inconsistent_huffman_decoder_state); + goto exit; + } + v_length = (((v_table_entry >> 8) & 255) + 3); + v_table_entry_n_bits = ((v_table_entry >> 4) & 15); + if (v_table_entry_n_bits > 0) { + v_length = (((v_length + 253 + ((uint32_t)(((v_bits) & WUFFS_BASE__LOW_BITS_MASK__U64(v_table_entry_n_bits))))) & 255) + 3); + v_bits >>= v_table_entry_n_bits; + v_n_bits -= v_table_entry_n_bits; + } + v_table_entry = self->private_data.f_huffs[1][(v_bits & v_dmask)]; + v_table_entry_n_bits = (v_table_entry & 15); + v_bits >>= v_table_entry_n_bits; + v_n_bits -= v_table_entry_n_bits; + if ((v_table_entry >> 28) == 1) { + v_redir_top = ((v_table_entry >> 8) & 65535); + v_redir_mask = ((((uint32_t)(1)) << ((v_table_entry >> 4) & 15)) - 1); + v_table_entry = self->private_data.f_huffs[1][((v_redir_top + (((uint32_t)((v_bits & 4294967295))) & v_redir_mask)) & 1023)]; + v_table_entry_n_bits = (v_table_entry & 15); + v_bits >>= v_table_entry_n_bits; + v_n_bits -= v_table_entry_n_bits; + } + if ((v_table_entry >> 24) != 64) { + if ((v_table_entry >> 24) == 8) { + status = wuffs_base__make_status(wuffs_deflate__error__bad_huffman_code); + goto exit; + } + status = wuffs_base__make_status(wuffs_deflate__error__internal_error_inconsistent_huffman_decoder_state); + goto exit; + } + v_dist_minus_1 = ((v_table_entry >> 8) & 32767); + v_table_entry_n_bits = ((v_table_entry >> 4) & 15); + v_dist_minus_1 = ((v_dist_minus_1 + ((uint32_t)(((v_bits) & WUFFS_BASE__LOW_BITS_MASK__U64(v_table_entry_n_bits))))) & 32767); + v_bits >>= v_table_entry_n_bits; + v_n_bits -= v_table_entry_n_bits; + while (true) { + if (((uint64_t)((v_dist_minus_1 + 1))) > ((uint64_t)(iop_a_dst - io0_a_dst))) { + v_hlen = 0; + v_hdist = ((uint32_t)((((uint64_t)((v_dist_minus_1 + 1))) - ((uint64_t)(iop_a_dst - io0_a_dst))))); + if (v_length > v_hdist) { + v_length -= v_hdist; + v_hlen = v_hdist; + } else { + v_hlen = v_length; + v_length = 0; + } + v_hdist += v_hdist_adjustment; + if (self->private_impl.f_history_index < v_hdist) { + status = wuffs_base__make_status(wuffs_deflate__error__bad_distance); + goto exit; + } + wuffs_base__io_writer__limited_copy_u32_from_slice( + &iop_a_dst, io2_a_dst,v_hlen, wuffs_base__make_slice_u8_ij(self->private_data.f_history, ((self->private_impl.f_history_index - v_hdist) & 32767), 33025)); + if (v_length == 0) { + goto label__loop__continue; + } + if ((((uint64_t)((v_dist_minus_1 + 1))) > ((uint64_t)(iop_a_dst - io0_a_dst))) || (((uint64_t)(v_length)) > ((uint64_t)(io2_a_dst - iop_a_dst))) || (((uint64_t)((v_length + 8))) > ((uint64_t)(io2_a_dst - iop_a_dst)))) { + status = wuffs_base__make_status(wuffs_deflate__error__internal_error_inconsistent_distance); + goto exit; + } + } + if ((v_dist_minus_1 + 1) >= 8) { + wuffs_base__io_writer__limited_copy_u32_from_history_8_byte_chunks_fast( + &iop_a_dst, io0_a_dst, io2_a_dst, v_length, (v_dist_minus_1 + 1)); + } else if ((v_dist_minus_1 + 1) == 1) { + wuffs_base__io_writer__limited_copy_u32_from_history_8_byte_chunks_distance_1_fast( + &iop_a_dst, io0_a_dst, io2_a_dst, v_length, (v_dist_minus_1 + 1)); + } else { + wuffs_base__io_writer__limited_copy_u32_from_history_fast( + &iop_a_dst, io0_a_dst, io2_a_dst, v_length, (v_dist_minus_1 + 1)); + } + goto label__0__break; + } + label__0__break:; + } + label__loop__break:; + if (v_n_bits > 63) { + status = wuffs_base__make_status(wuffs_deflate__error__internal_error_inconsistent_n_bits); + goto exit; + } + while (v_n_bits >= 8) { + v_n_bits -= 8; + if (iop_a_src > io1_a_src) { + iop_a_src--; + } else { + status = wuffs_base__make_status(wuffs_deflate__error__internal_error_inconsistent_i_o); + goto exit; + } + } + self->private_impl.f_bits = ((uint32_t)((v_bits & ((((uint64_t)(1)) << v_n_bits) - 1)))); + self->private_impl.f_n_bits = v_n_bits; + if ((self->private_impl.f_n_bits >= 8) || ((self->private_impl.f_bits >> self->private_impl.f_n_bits) != 0)) { + status = wuffs_base__make_status(wuffs_deflate__error__internal_error_inconsistent_n_bits); + goto exit; + } + goto exit; + exit: + if (a_dst && a_dst->data.ptr) { + a_dst->meta.wi = ((size_t)(iop_a_dst - a_dst->data.ptr)); + } + if (a_src && a_src->data.ptr) { + a_src->meta.ri = ((size_t)(iop_a_src - a_src->data.ptr)); + } + + return status; +} + +// -------- func deflate.decoder.decode_huffman_slow + +static wuffs_base__status +wuffs_deflate__decoder__decode_huffman_slow( + wuffs_deflate__decoder* self, + wuffs_base__io_buffer* a_dst, + wuffs_base__io_buffer* a_src) { + wuffs_base__status status = wuffs_base__make_status(NULL); + + uint32_t v_bits = 0; + uint32_t v_n_bits = 0; + uint32_t v_table_entry = 0; + uint32_t v_table_entry_n_bits = 0; + uint32_t v_lmask = 0; + uint32_t v_dmask = 0; + uint32_t v_b0 = 0; + uint32_t v_redir_top = 0; + uint32_t v_redir_mask = 0; + uint32_t v_b1 = 0; + uint32_t v_length = 0; + uint32_t v_b2 = 0; + uint32_t v_b3 = 0; + uint32_t v_b4 = 0; + uint32_t v_dist_minus_1 = 0; + uint32_t v_b5 = 0; + uint32_t v_n_copied = 0; + uint32_t v_hlen = 0; + uint32_t v_hdist = 0; + + uint8_t* iop_a_dst = NULL; + uint8_t* io0_a_dst WUFFS_BASE__POTENTIALLY_UNUSED = NULL; + uint8_t* io1_a_dst WUFFS_BASE__POTENTIALLY_UNUSED = NULL; + uint8_t* io2_a_dst WUFFS_BASE__POTENTIALLY_UNUSED = NULL; + if (a_dst && a_dst->data.ptr) { + io0_a_dst = a_dst->data.ptr; + io1_a_dst = io0_a_dst + a_dst->meta.wi; + iop_a_dst = io1_a_dst; + io2_a_dst = io0_a_dst + a_dst->data.len; + if (a_dst->meta.closed) { + io2_a_dst = iop_a_dst; + } + } + const uint8_t* iop_a_src = NULL; + const uint8_t* io0_a_src WUFFS_BASE__POTENTIALLY_UNUSED = NULL; + const uint8_t* io1_a_src WUFFS_BASE__POTENTIALLY_UNUSED = NULL; + const uint8_t* io2_a_src WUFFS_BASE__POTENTIALLY_UNUSED = NULL; + if (a_src && a_src->data.ptr) { + io0_a_src = a_src->data.ptr; + io1_a_src = io0_a_src + a_src->meta.ri; + iop_a_src = io1_a_src; + io2_a_src = io0_a_src + a_src->meta.wi; + } + + uint32_t coro_susp_point = self->private_impl.p_decode_huffman_slow[0]; + if (coro_susp_point) { + v_bits = self->private_data.s_decode_huffman_slow[0].v_bits; + v_n_bits = self->private_data.s_decode_huffman_slow[0].v_n_bits; + v_table_entry_n_bits = self->private_data.s_decode_huffman_slow[0].v_table_entry_n_bits; + v_lmask = self->private_data.s_decode_huffman_slow[0].v_lmask; + v_dmask = self->private_data.s_decode_huffman_slow[0].v_dmask; + v_redir_top = self->private_data.s_decode_huffman_slow[0].v_redir_top; + v_redir_mask = self->private_data.s_decode_huffman_slow[0].v_redir_mask; + v_length = self->private_data.s_decode_huffman_slow[0].v_length; + v_dist_minus_1 = self->private_data.s_decode_huffman_slow[0].v_dist_minus_1; + } + switch (coro_susp_point) { + WUFFS_BASE__COROUTINE_SUSPENSION_POINT_0; + + if ((self->private_impl.f_n_bits >= 8) || ((self->private_impl.f_bits >> (self->private_impl.f_n_bits & 7)) != 0)) { + status = wuffs_base__make_status(wuffs_deflate__error__internal_error_inconsistent_n_bits); + goto exit; + } + v_bits = self->private_impl.f_bits; + v_n_bits = self->private_impl.f_n_bits; + v_lmask = ((((uint32_t)(1)) << self->private_impl.f_n_huffs_bits[0]) - 1); + v_dmask = ((((uint32_t)(1)) << self->private_impl.f_n_huffs_bits[1]) - 1); + label__loop__continue:; + while ( ! (self->private_impl.p_decode_huffman_slow[0] != 0)) { + while (true) { + v_table_entry = self->private_data.f_huffs[0][(v_bits & v_lmask)]; + v_table_entry_n_bits = (v_table_entry & 15); + if (v_n_bits >= v_table_entry_n_bits) { + v_bits >>= v_table_entry_n_bits; + v_n_bits -= v_table_entry_n_bits; + goto label__0__break; + } + { + WUFFS_BASE__COROUTINE_SUSPENSION_POINT(1); + if (WUFFS_BASE__UNLIKELY(iop_a_src == io2_a_src)) { + status = wuffs_base__make_status(wuffs_base__suspension__short_read); + goto suspend; + } + uint32_t t_0 = *iop_a_src++; + v_b0 = t_0; + } + v_bits |= (v_b0 << v_n_bits); + v_n_bits += 8; + } + label__0__break:; + if ((v_table_entry >> 31) != 0) { + self->private_data.s_decode_huffman_slow[0].scratch = ((uint8_t)(((v_table_entry >> 8) & 255))); + WUFFS_BASE__COROUTINE_SUSPENSION_POINT(2); + if (iop_a_dst == io2_a_dst) { + status = wuffs_base__make_status(wuffs_base__suspension__short_write); + goto suspend; + } + *iop_a_dst++ = ((uint8_t)(self->private_data.s_decode_huffman_slow[0].scratch)); + goto label__loop__continue; + } else if ((v_table_entry >> 30) != 0) { + } else if ((v_table_entry >> 29) != 0) { + self->private_impl.f_end_of_block = true; + goto label__loop__break; + } else if ((v_table_entry >> 28) != 0) { + v_redir_top = ((v_table_entry >> 8) & 65535); + v_redir_mask = ((((uint32_t)(1)) << ((v_table_entry >> 4) & 15)) - 1); + while (true) { + v_table_entry = self->private_data.f_huffs[0][((v_redir_top + (v_bits & v_redir_mask)) & 1023)]; + v_table_entry_n_bits = (v_table_entry & 15); + if (v_n_bits >= v_table_entry_n_bits) { + v_bits >>= v_table_entry_n_bits; + v_n_bits -= v_table_entry_n_bits; + goto label__1__break; + } + { + WUFFS_BASE__COROUTINE_SUSPENSION_POINT(3); + if (WUFFS_BASE__UNLIKELY(iop_a_src == io2_a_src)) { + status = wuffs_base__make_status(wuffs_base__suspension__short_read); + goto suspend; + } + uint32_t t_1 = *iop_a_src++; + v_b1 = t_1; + } + v_bits |= (v_b1 << v_n_bits); + v_n_bits += 8; + } + label__1__break:; + if ((v_table_entry >> 31) != 0) { + self->private_data.s_decode_huffman_slow[0].scratch = ((uint8_t)(((v_table_entry >> 8) & 255))); + WUFFS_BASE__COROUTINE_SUSPENSION_POINT(4); + if (iop_a_dst == io2_a_dst) { + status = wuffs_base__make_status(wuffs_base__suspension__short_write); + goto suspend; + } + *iop_a_dst++ = ((uint8_t)(self->private_data.s_decode_huffman_slow[0].scratch)); + goto label__loop__continue; + } else if ((v_table_entry >> 30) != 0) { + } else if ((v_table_entry >> 29) != 0) { + self->private_impl.f_end_of_block = true; + goto label__loop__break; + } else if ((v_table_entry >> 28) != 0) { + status = wuffs_base__make_status(wuffs_deflate__error__internal_error_inconsistent_huffman_decoder_state); + goto exit; + } else if ((v_table_entry >> 27) != 0) { + status = wuffs_base__make_status(wuffs_deflate__error__bad_huffman_code); + goto exit; + } else { + status = wuffs_base__make_status(wuffs_deflate__error__internal_error_inconsistent_huffman_decoder_state); + goto exit; + } + } else if ((v_table_entry >> 27) != 0) { + status = wuffs_base__make_status(wuffs_deflate__error__bad_huffman_code); + goto exit; + } else { + status = wuffs_base__make_status(wuffs_deflate__error__internal_error_inconsistent_huffman_decoder_state); + goto exit; + } + v_length = (((v_table_entry >> 8) & 255) + 3); + v_table_entry_n_bits = ((v_table_entry >> 4) & 15); + if (v_table_entry_n_bits > 0) { + while (v_n_bits < v_table_entry_n_bits) { + { + WUFFS_BASE__COROUTINE_SUSPENSION_POINT(5); + if (WUFFS_BASE__UNLIKELY(iop_a_src == io2_a_src)) { + status = wuffs_base__make_status(wuffs_base__suspension__short_read); + goto suspend; + } + uint32_t t_2 = *iop_a_src++; + v_b2 = t_2; + } + v_bits |= (v_b2 << v_n_bits); + v_n_bits += 8; + } + v_length = (((v_length + 253 + ((v_bits) & WUFFS_BASE__LOW_BITS_MASK__U32(v_table_entry_n_bits))) & 255) + 3); + v_bits >>= v_table_entry_n_bits; + v_n_bits -= v_table_entry_n_bits; + } + while (true) { + v_table_entry = self->private_data.f_huffs[1][(v_bits & v_dmask)]; + v_table_entry_n_bits = (v_table_entry & 15); + if (v_n_bits >= v_table_entry_n_bits) { + v_bits >>= v_table_entry_n_bits; + v_n_bits -= v_table_entry_n_bits; + goto label__2__break; + } + { + WUFFS_BASE__COROUTINE_SUSPENSION_POINT(6); + if (WUFFS_BASE__UNLIKELY(iop_a_src == io2_a_src)) { + status = wuffs_base__make_status(wuffs_base__suspension__short_read); + goto suspend; + } + uint32_t t_3 = *iop_a_src++; + v_b3 = t_3; + } + v_bits |= (v_b3 << v_n_bits); + v_n_bits += 8; + } + label__2__break:; + if ((v_table_entry >> 28) == 1) { + v_redir_top = ((v_table_entry >> 8) & 65535); + v_redir_mask = ((((uint32_t)(1)) << ((v_table_entry >> 4) & 15)) - 1); + while (true) { + v_table_entry = self->private_data.f_huffs[1][((v_redir_top + (v_bits & v_redir_mask)) & 1023)]; + v_table_entry_n_bits = (v_table_entry & 15); + if (v_n_bits >= v_table_entry_n_bits) { + v_bits >>= v_table_entry_n_bits; + v_n_bits -= v_table_entry_n_bits; + goto label__3__break; + } + { + WUFFS_BASE__COROUTINE_SUSPENSION_POINT(7); + if (WUFFS_BASE__UNLIKELY(iop_a_src == io2_a_src)) { + status = wuffs_base__make_status(wuffs_base__suspension__short_read); + goto suspend; + } + uint32_t t_4 = *iop_a_src++; + v_b4 = t_4; + } + v_bits |= (v_b4 << v_n_bits); + v_n_bits += 8; + } + label__3__break:; + } + if ((v_table_entry >> 24) != 64) { + if ((v_table_entry >> 24) == 8) { + status = wuffs_base__make_status(wuffs_deflate__error__bad_huffman_code); + goto exit; + } + status = wuffs_base__make_status(wuffs_deflate__error__internal_error_inconsistent_huffman_decoder_state); + goto exit; + } + v_dist_minus_1 = ((v_table_entry >> 8) & 32767); + v_table_entry_n_bits = ((v_table_entry >> 4) & 15); + if (v_table_entry_n_bits > 0) { + while (v_n_bits < v_table_entry_n_bits) { + { + WUFFS_BASE__COROUTINE_SUSPENSION_POINT(8); + if (WUFFS_BASE__UNLIKELY(iop_a_src == io2_a_src)) { + status = wuffs_base__make_status(wuffs_base__suspension__short_read); + goto suspend; + } + uint32_t t_5 = *iop_a_src++; + v_b5 = t_5; + } + v_bits |= (v_b5 << v_n_bits); + v_n_bits += 8; + } + v_dist_minus_1 = ((v_dist_minus_1 + ((v_bits) & WUFFS_BASE__LOW_BITS_MASK__U32(v_table_entry_n_bits))) & 32767); + v_bits >>= v_table_entry_n_bits; + v_n_bits -= v_table_entry_n_bits; + } + label__inner__continue:; + while (true) { + if (((uint64_t)((v_dist_minus_1 + 1))) > ((uint64_t)(iop_a_dst - io0_a_dst))) { + v_hdist = ((uint32_t)((((uint64_t)((v_dist_minus_1 + 1))) - ((uint64_t)(iop_a_dst - io0_a_dst))))); + if (v_hdist < v_length) { + v_hlen = v_hdist; + } else { + v_hlen = v_length; + } + v_hdist += ((uint32_t)((((uint64_t)(self->private_impl.f_transformed_history_count - (a_dst ? a_dst->meta.pos : 0))) & 4294967295))); + if (self->private_impl.f_history_index < v_hdist) { + status = wuffs_base__make_status(wuffs_deflate__error__bad_distance); + goto exit; + } + v_n_copied = wuffs_base__io_writer__limited_copy_u32_from_slice( + &iop_a_dst, io2_a_dst,v_hlen, wuffs_base__make_slice_u8_ij(self->private_data.f_history, ((self->private_impl.f_history_index - v_hdist) & 32767), 33025)); + if (v_n_copied < v_hlen) { + v_length -= v_n_copied; + status = wuffs_base__make_status(wuffs_base__suspension__short_write); + WUFFS_BASE__COROUTINE_SUSPENSION_POINT_MAYBE_SUSPEND(9); + goto label__inner__continue; + } + v_length -= v_hlen; + if (v_length == 0) { + goto label__loop__continue; + } + } + v_n_copied = wuffs_base__io_writer__limited_copy_u32_from_history( + &iop_a_dst, io0_a_dst, io2_a_dst, v_length, (v_dist_minus_1 + 1)); + if (v_length <= v_n_copied) { + goto label__loop__continue; + } + v_length -= v_n_copied; + status = wuffs_base__make_status(wuffs_base__suspension__short_write); + WUFFS_BASE__COROUTINE_SUSPENSION_POINT_MAYBE_SUSPEND(10); + } + } + label__loop__break:; + self->private_impl.f_bits = v_bits; + self->private_impl.f_n_bits = v_n_bits; + if ((self->private_impl.f_n_bits >= 8) || ((self->private_impl.f_bits >> (self->private_impl.f_n_bits & 7)) != 0)) { + status = wuffs_base__make_status(wuffs_deflate__error__internal_error_inconsistent_n_bits); + goto exit; + } + + ok: + self->private_impl.p_decode_huffman_slow[0] = 0; + goto exit; + } + + goto suspend; + suspend: + self->private_impl.p_decode_huffman_slow[0] = wuffs_base__status__is_suspension(&status) ? coro_susp_point : 0; + self->private_data.s_decode_huffman_slow[0].v_bits = v_bits; + self->private_data.s_decode_huffman_slow[0].v_n_bits = v_n_bits; + self->private_data.s_decode_huffman_slow[0].v_table_entry_n_bits = v_table_entry_n_bits; + self->private_data.s_decode_huffman_slow[0].v_lmask = v_lmask; + self->private_data.s_decode_huffman_slow[0].v_dmask = v_dmask; + self->private_data.s_decode_huffman_slow[0].v_redir_top = v_redir_top; + self->private_data.s_decode_huffman_slow[0].v_redir_mask = v_redir_mask; + self->private_data.s_decode_huffman_slow[0].v_length = v_length; + self->private_data.s_decode_huffman_slow[0].v_dist_minus_1 = v_dist_minus_1; + + goto exit; + exit: + if (a_dst && a_dst->data.ptr) { + a_dst->meta.wi = ((size_t)(iop_a_dst - a_dst->data.ptr)); + } + if (a_src && a_src->data.ptr) { + a_src->meta.ri = ((size_t)(iop_a_src - a_src->data.ptr)); + } + + return status; +} + +#endif // !defined(WUFFS_CONFIG__MODULES) || defined(WUFFS_CONFIG__MODULE__DEFLATE) + +#if !defined(WUFFS_CONFIG__MODULES) || defined(WUFFS_CONFIG__MODULE__LZW) + +// ---------------- Status Codes Implementations + +const char wuffs_lzw__error__bad_code[] = "#lzw: bad code"; +const char wuffs_lzw__error__truncated_input[] = "#lzw: truncated input"; +const char wuffs_lzw__error__internal_error_inconsistent_i_o[] = "#lzw: internal error: inconsistent I/O"; + +// ---------------- Private Consts + +// ---------------- Private Initializer Prototypes + +// ---------------- Private Function Prototypes + +static wuffs_base__empty_struct +wuffs_lzw__decoder__read_from( + wuffs_lzw__decoder* self, + wuffs_base__io_buffer* a_src); + +static wuffs_base__status +wuffs_lzw__decoder__write_to( + wuffs_lzw__decoder* self, + wuffs_base__io_buffer* a_dst); + +// ---------------- VTables + +const wuffs_base__io_transformer__func_ptrs +wuffs_lzw__decoder__func_ptrs_for__wuffs_base__io_transformer = { + (wuffs_base__empty_struct(*)(void*, + uint32_t, + bool))(&wuffs_lzw__decoder__set_quirk_enabled), + (wuffs_base__status(*)(void*, + wuffs_base__io_buffer*, + wuffs_base__io_buffer*, + wuffs_base__slice_u8))(&wuffs_lzw__decoder__transform_io), + (wuffs_base__range_ii_u64(*)(const void*))(&wuffs_lzw__decoder__workbuf_len), +}; + +// ---------------- Initializer Implementations + +wuffs_base__status WUFFS_BASE__WARN_UNUSED_RESULT +wuffs_lzw__decoder__initialize( + wuffs_lzw__decoder* self, + size_t sizeof_star_self, + uint64_t wuffs_version, + uint32_t options){ + if (!self) { + return wuffs_base__make_status(wuffs_base__error__bad_receiver); + } + if (sizeof(*self) != sizeof_star_self) { + return wuffs_base__make_status(wuffs_base__error__bad_sizeof_receiver); + } + if (((wuffs_version >> 32) != WUFFS_VERSION_MAJOR) || + (((wuffs_version >> 16) & 0xFFFF) > WUFFS_VERSION_MINOR)) { + return wuffs_base__make_status(wuffs_base__error__bad_wuffs_version); + } + + if ((options & WUFFS_INITIALIZE__ALREADY_ZEROED) != 0) { + // The whole point of this if-check is to detect an uninitialized *self. + // We disable the warning on GCC. Clang-5.0 does not have this warning. +#if !defined(__clang__) && defined(__GNUC__) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wmaybe-uninitialized" +#endif + if (self->private_impl.magic != 0) { + return wuffs_base__make_status(wuffs_base__error__initialize_falsely_claimed_already_zeroed); + } +#if !defined(__clang__) && defined(__GNUC__) +#pragma GCC diagnostic pop +#endif + } else { + if ((options & WUFFS_INITIALIZE__LEAVE_INTERNAL_BUFFERS_UNINITIALIZED) == 0) { + memset(self, 0, sizeof(*self)); + options |= WUFFS_INITIALIZE__ALREADY_ZEROED; + } else { + memset(&(self->private_impl), 0, sizeof(self->private_impl)); + } + } + + self->private_impl.magic = WUFFS_BASE__MAGIC; + self->private_impl.vtable_for__wuffs_base__io_transformer.vtable_name = + wuffs_base__io_transformer__vtable_name; + self->private_impl.vtable_for__wuffs_base__io_transformer.function_pointers = + (const void*)(&wuffs_lzw__decoder__func_ptrs_for__wuffs_base__io_transformer); + return wuffs_base__make_status(NULL); +} + +wuffs_lzw__decoder* +wuffs_lzw__decoder__alloc() { + wuffs_lzw__decoder* x = + (wuffs_lzw__decoder*)(calloc(sizeof(wuffs_lzw__decoder), 1)); + if (!x) { + return NULL; + } + if (wuffs_lzw__decoder__initialize( + x, sizeof(wuffs_lzw__decoder), WUFFS_VERSION, WUFFS_INITIALIZE__ALREADY_ZEROED).repr) { + free(x); + return NULL; + } + return x; +} + +size_t +sizeof__wuffs_lzw__decoder() { + return sizeof(wuffs_lzw__decoder); +} + +// ---------------- Function Implementations + +// -------- func lzw.decoder.set_quirk_enabled + +WUFFS_BASE__MAYBE_STATIC wuffs_base__empty_struct +wuffs_lzw__decoder__set_quirk_enabled( + wuffs_lzw__decoder* self, + uint32_t a_quirk, + bool a_enabled) { + return wuffs_base__make_empty_struct(); +} + +// -------- func lzw.decoder.set_literal_width + +WUFFS_BASE__MAYBE_STATIC wuffs_base__empty_struct +wuffs_lzw__decoder__set_literal_width( + wuffs_lzw__decoder* self, + uint32_t a_lw) { + if (!self) { + return wuffs_base__make_empty_struct(); + } + if (self->private_impl.magic != WUFFS_BASE__MAGIC) { + return wuffs_base__make_empty_struct(); + } + if (a_lw > 8) { + self->private_impl.magic = WUFFS_BASE__DISABLED; + return wuffs_base__make_empty_struct(); + } + + self->private_impl.f_set_literal_width_arg = (a_lw + 1); + return wuffs_base__make_empty_struct(); +} + +// -------- func lzw.decoder.workbuf_len + +WUFFS_BASE__MAYBE_STATIC wuffs_base__range_ii_u64 +wuffs_lzw__decoder__workbuf_len( + const wuffs_lzw__decoder* self) { + if (!self) { + return wuffs_base__utility__empty_range_ii_u64(); + } + if ((self->private_impl.magic != WUFFS_BASE__MAGIC) && + (self->private_impl.magic != WUFFS_BASE__DISABLED)) { + return wuffs_base__utility__empty_range_ii_u64(); + } + + return wuffs_base__utility__make_range_ii_u64(0, 0); +} + +// -------- func lzw.decoder.transform_io + +WUFFS_BASE__MAYBE_STATIC wuffs_base__status +wuffs_lzw__decoder__transform_io( + wuffs_lzw__decoder* self, + wuffs_base__io_buffer* a_dst, + wuffs_base__io_buffer* a_src, + wuffs_base__slice_u8 a_workbuf) { + if (!self) { + return wuffs_base__make_status(wuffs_base__error__bad_receiver); + } + if (self->private_impl.magic != WUFFS_BASE__MAGIC) { + return wuffs_base__make_status( + (self->private_impl.magic == WUFFS_BASE__DISABLED) + ? wuffs_base__error__disabled_by_previous_error + : wuffs_base__error__initialize_not_called); + } + if (!a_dst || !a_src) { + self->private_impl.magic = WUFFS_BASE__DISABLED; + return wuffs_base__make_status(wuffs_base__error__bad_argument); + } + if ((self->private_impl.active_coroutine != 0) && + (self->private_impl.active_coroutine != 1)) { + self->private_impl.magic = WUFFS_BASE__DISABLED; + return wuffs_base__make_status(wuffs_base__error__interleaved_coroutine_calls); + } + self->private_impl.active_coroutine = 0; + wuffs_base__status status = wuffs_base__make_status(NULL); + + uint32_t v_i = 0; + + uint32_t coro_susp_point = self->private_impl.p_transform_io[0]; + switch (coro_susp_point) { + WUFFS_BASE__COROUTINE_SUSPENSION_POINT_0; + + self->private_impl.f_literal_width = 8; + if (self->private_impl.f_set_literal_width_arg > 0) { + self->private_impl.f_literal_width = (self->private_impl.f_set_literal_width_arg - 1); + } + self->private_impl.f_clear_code = (((uint32_t)(1)) << self->private_impl.f_literal_width); + self->private_impl.f_end_code = (self->private_impl.f_clear_code + 1); + self->private_impl.f_save_code = self->private_impl.f_end_code; + self->private_impl.f_prev_code = self->private_impl.f_end_code; + self->private_impl.f_width = (self->private_impl.f_literal_width + 1); + self->private_impl.f_bits = 0; + self->private_impl.f_n_bits = 0; + self->private_impl.f_output_ri = 0; + self->private_impl.f_output_wi = 0; + v_i = 0; + while (v_i < self->private_impl.f_clear_code) { + self->private_data.f_lm1s[v_i] = 0; + self->private_data.f_suffixes[v_i][0] = ((uint8_t)(v_i)); + v_i += 1; + } + label__0__continue:; + while (true) { + wuffs_lzw__decoder__read_from(self, a_src); + if (self->private_impl.f_output_wi > 0) { + WUFFS_BASE__COROUTINE_SUSPENSION_POINT(1); + status = wuffs_lzw__decoder__write_to(self, a_dst); + if (status.repr) { + goto suspend; + } + } + if (self->private_impl.f_read_from_return_value == 0) { + goto label__0__break; + } else if (self->private_impl.f_read_from_return_value == 1) { + goto label__0__continue; + } else if (self->private_impl.f_read_from_return_value == 2) { + status = wuffs_base__make_status(wuffs_base__suspension__short_read); + WUFFS_BASE__COROUTINE_SUSPENSION_POINT_MAYBE_SUSPEND(2); + } else if (self->private_impl.f_read_from_return_value == 3) { + status = wuffs_base__make_status(wuffs_lzw__error__truncated_input); + goto exit; + } else if (self->private_impl.f_read_from_return_value == 4) { + status = wuffs_base__make_status(wuffs_lzw__error__bad_code); + goto exit; + } else { + status = wuffs_base__make_status(wuffs_lzw__error__internal_error_inconsistent_i_o); + goto exit; + } + } + label__0__break:; + + ok: + self->private_impl.p_transform_io[0] = 0; + goto exit; + } + + goto suspend; + suspend: + self->private_impl.p_transform_io[0] = wuffs_base__status__is_suspension(&status) ? coro_susp_point : 0; + self->private_impl.active_coroutine = wuffs_base__status__is_suspension(&status) ? 1 : 0; + + goto exit; + exit: + if (wuffs_base__status__is_error(&status)) { + self->private_impl.magic = WUFFS_BASE__DISABLED; + } + return status; +} + +// -------- func lzw.decoder.read_from + +static wuffs_base__empty_struct +wuffs_lzw__decoder__read_from( + wuffs_lzw__decoder* self, + wuffs_base__io_buffer* a_src) { + uint32_t v_clear_code = 0; + uint32_t v_end_code = 0; + uint32_t v_save_code = 0; + uint32_t v_prev_code = 0; + uint32_t v_width = 0; + uint32_t v_bits = 0; + uint32_t v_n_bits = 0; + uint32_t v_output_wi = 0; + uint32_t v_code = 0; + uint32_t v_c = 0; + uint32_t v_o = 0; + uint32_t v_steps = 0; + uint8_t v_first_byte = 0; + uint16_t v_lm1_b = 0; + uint16_t v_lm1_a = 0; + + const uint8_t* iop_a_src = NULL; + const uint8_t* io0_a_src WUFFS_BASE__POTENTIALLY_UNUSED = NULL; + const uint8_t* io1_a_src WUFFS_BASE__POTENTIALLY_UNUSED = NULL; + const uint8_t* io2_a_src WUFFS_BASE__POTENTIALLY_UNUSED = NULL; + if (a_src && a_src->data.ptr) { + io0_a_src = a_src->data.ptr; + io1_a_src = io0_a_src + a_src->meta.ri; + iop_a_src = io1_a_src; + io2_a_src = io0_a_src + a_src->meta.wi; + } + + v_clear_code = self->private_impl.f_clear_code; + v_end_code = self->private_impl.f_end_code; + v_save_code = self->private_impl.f_save_code; + v_prev_code = self->private_impl.f_prev_code; + v_width = self->private_impl.f_width; + v_bits = self->private_impl.f_bits; + v_n_bits = self->private_impl.f_n_bits; + v_output_wi = self->private_impl.f_output_wi; + while (true) { + if (v_n_bits < v_width) { + if (((uint64_t)(io2_a_src - iop_a_src)) >= 4) { + v_bits |= ((uint32_t)(wuffs_base__peek_u32le__no_bounds_check(iop_a_src) << v_n_bits)); + iop_a_src += ((31 - v_n_bits) >> 3); + v_n_bits |= 24; + } else if (((uint64_t)(io2_a_src - iop_a_src)) <= 0) { + if (a_src && a_src->meta.closed) { + self->private_impl.f_read_from_return_value = 3; + } else { + self->private_impl.f_read_from_return_value = 2; + } + goto label__0__break; + } else { + v_bits |= (((uint32_t)(wuffs_base__peek_u8be__no_bounds_check(iop_a_src))) << v_n_bits); + iop_a_src += 1; + v_n_bits += 8; + if (v_n_bits >= v_width) { + } else if (((uint64_t)(io2_a_src - iop_a_src)) <= 0) { + if (a_src && a_src->meta.closed) { + self->private_impl.f_read_from_return_value = 3; + } else { + self->private_impl.f_read_from_return_value = 2; + } + goto label__0__break; + } else { + v_bits |= (((uint32_t)(wuffs_base__peek_u8be__no_bounds_check(iop_a_src))) << v_n_bits); + iop_a_src += 1; + v_n_bits += 8; + if (v_n_bits < v_width) { + self->private_impl.f_read_from_return_value = 5; + goto label__0__break; + } + } + } + } + v_code = ((v_bits) & WUFFS_BASE__LOW_BITS_MASK__U32(v_width)); + v_bits >>= v_width; + v_n_bits -= v_width; + if (v_code < v_clear_code) { + self->private_data.f_output[v_output_wi] = ((uint8_t)(v_code)); + v_output_wi = ((v_output_wi + 1) & 8191); + if (v_save_code <= 4095) { + v_lm1_a = (((uint16_t)(self->private_data.f_lm1s[v_prev_code] + 1)) & 4095); + self->private_data.f_lm1s[v_save_code] = v_lm1_a; + if ((v_lm1_a % 8) != 0) { + self->private_impl.f_prefixes[v_save_code] = self->private_impl.f_prefixes[v_prev_code]; + memcpy(self->private_data.f_suffixes[v_save_code],self->private_data.f_suffixes[v_prev_code], sizeof(self->private_data.f_suffixes[v_save_code])); + self->private_data.f_suffixes[v_save_code][(v_lm1_a % 8)] = ((uint8_t)(v_code)); + } else { + self->private_impl.f_prefixes[v_save_code] = ((uint16_t)(v_prev_code)); + self->private_data.f_suffixes[v_save_code][0] = ((uint8_t)(v_code)); + } + v_save_code += 1; + if (v_width < 12) { + v_width += (1 & (v_save_code >> v_width)); + } + v_prev_code = v_code; + } + } else if (v_code <= v_end_code) { + if (v_code == v_end_code) { + self->private_impl.f_read_from_return_value = 0; + goto label__0__break; + } + v_save_code = v_end_code; + v_prev_code = v_end_code; + v_width = (self->private_impl.f_literal_width + 1); + } else if (v_code <= v_save_code) { + v_c = v_code; + if (v_code == v_save_code) { + v_c = v_prev_code; + } + v_o = ((v_output_wi + (((uint32_t)(self->private_data.f_lm1s[v_c])) & 4294967288)) & 8191); + v_output_wi = ((v_output_wi + 1 + ((uint32_t)(self->private_data.f_lm1s[v_c]))) & 8191); + v_steps = (((uint32_t)(self->private_data.f_lm1s[v_c])) >> 3); + while (true) { + memcpy((self->private_data.f_output)+(v_o), (self->private_data.f_suffixes[v_c]), 8); + if (v_steps <= 0) { + goto label__1__break; + } + v_steps -= 1; + v_o = (((uint32_t)(v_o - 8)) & 8191); + v_c = ((uint32_t)(self->private_impl.f_prefixes[v_c])); + } + label__1__break:; + v_first_byte = self->private_data.f_suffixes[v_c][0]; + if (v_code == v_save_code) { + self->private_data.f_output[v_output_wi] = v_first_byte; + v_output_wi = ((v_output_wi + 1) & 8191); + } + if (v_save_code <= 4095) { + v_lm1_b = (((uint16_t)(self->private_data.f_lm1s[v_prev_code] + 1)) & 4095); + self->private_data.f_lm1s[v_save_code] = v_lm1_b; + if ((v_lm1_b % 8) != 0) { + self->private_impl.f_prefixes[v_save_code] = self->private_impl.f_prefixes[v_prev_code]; + memcpy(self->private_data.f_suffixes[v_save_code],self->private_data.f_suffixes[v_prev_code], sizeof(self->private_data.f_suffixes[v_save_code])); + self->private_data.f_suffixes[v_save_code][(v_lm1_b % 8)] = v_first_byte; + } else { + self->private_impl.f_prefixes[v_save_code] = ((uint16_t)(v_prev_code)); + self->private_data.f_suffixes[v_save_code][0] = ((uint8_t)(v_first_byte)); + } + v_save_code += 1; + if (v_width < 12) { + v_width += (1 & (v_save_code >> v_width)); + } + v_prev_code = v_code; + } + } else { + self->private_impl.f_read_from_return_value = 4; + goto label__0__break; + } + if (v_output_wi > 4095) { + self->private_impl.f_read_from_return_value = 1; + goto label__0__break; + } + } + label__0__break:; + if (self->private_impl.f_read_from_return_value != 2) { + while (v_n_bits >= 8) { + v_n_bits -= 8; + if (iop_a_src > io1_a_src) { + iop_a_src--; + } else { + self->private_impl.f_read_from_return_value = 5; + goto label__2__break; + } + } + label__2__break:; + } + self->private_impl.f_save_code = v_save_code; + self->private_impl.f_prev_code = v_prev_code; + self->private_impl.f_width = v_width; + self->private_impl.f_bits = v_bits; + self->private_impl.f_n_bits = v_n_bits; + self->private_impl.f_output_wi = v_output_wi; + if (a_src && a_src->data.ptr) { + a_src->meta.ri = ((size_t)(iop_a_src - a_src->data.ptr)); + } + + return wuffs_base__make_empty_struct(); +} + +// -------- func lzw.decoder.write_to + +static wuffs_base__status +wuffs_lzw__decoder__write_to( + wuffs_lzw__decoder* self, + wuffs_base__io_buffer* a_dst) { + wuffs_base__status status = wuffs_base__make_status(NULL); + + wuffs_base__slice_u8 v_s = {0}; + uint64_t v_n = 0; + + uint8_t* iop_a_dst = NULL; + uint8_t* io0_a_dst WUFFS_BASE__POTENTIALLY_UNUSED = NULL; + uint8_t* io1_a_dst WUFFS_BASE__POTENTIALLY_UNUSED = NULL; + uint8_t* io2_a_dst WUFFS_BASE__POTENTIALLY_UNUSED = NULL; + if (a_dst && a_dst->data.ptr) { + io0_a_dst = a_dst->data.ptr; + io1_a_dst = io0_a_dst + a_dst->meta.wi; + iop_a_dst = io1_a_dst; + io2_a_dst = io0_a_dst + a_dst->data.len; + if (a_dst->meta.closed) { + io2_a_dst = iop_a_dst; + } + } + + uint32_t coro_susp_point = self->private_impl.p_write_to[0]; + switch (coro_susp_point) { + WUFFS_BASE__COROUTINE_SUSPENSION_POINT_0; + + while (self->private_impl.f_output_wi > 0) { + if (self->private_impl.f_output_ri > self->private_impl.f_output_wi) { + status = wuffs_base__make_status(wuffs_lzw__error__internal_error_inconsistent_i_o); + goto exit; + } + v_s = wuffs_base__make_slice_u8_ij(self->private_data.f_output, + self->private_impl.f_output_ri, + self->private_impl.f_output_wi); + v_n = wuffs_base__io_writer__copy_from_slice(&iop_a_dst, io2_a_dst,v_s); + if (v_n == ((uint64_t)(v_s.len))) { + self->private_impl.f_output_ri = 0; + self->private_impl.f_output_wi = 0; + status = wuffs_base__make_status(NULL); + goto ok; + } + self->private_impl.f_output_ri = (((uint32_t)(self->private_impl.f_output_ri + ((uint32_t)((v_n & 4294967295))))) & 8191); + status = wuffs_base__make_status(wuffs_base__suspension__short_write); + WUFFS_BASE__COROUTINE_SUSPENSION_POINT_MAYBE_SUSPEND(1); + } + + ok: + self->private_impl.p_write_to[0] = 0; + goto exit; + } + + goto suspend; + suspend: + self->private_impl.p_write_to[0] = wuffs_base__status__is_suspension(&status) ? coro_susp_point : 0; + + goto exit; + exit: + if (a_dst && a_dst->data.ptr) { + a_dst->meta.wi = ((size_t)(iop_a_dst - a_dst->data.ptr)); + } + + return status; +} + +// -------- func lzw.decoder.flush + +WUFFS_BASE__MAYBE_STATIC wuffs_base__slice_u8 +wuffs_lzw__decoder__flush( + wuffs_lzw__decoder* self) { + if (!self) { + return wuffs_base__make_slice_u8(NULL, 0); + } + if (self->private_impl.magic != WUFFS_BASE__MAGIC) { + return wuffs_base__make_slice_u8(NULL, 0); + } + + wuffs_base__slice_u8 v_s = {0}; + + if (self->private_impl.f_output_ri <= self->private_impl.f_output_wi) { + v_s = wuffs_base__make_slice_u8_ij(self->private_data.f_output, + self->private_impl.f_output_ri, + self->private_impl.f_output_wi); + } + self->private_impl.f_output_ri = 0; + self->private_impl.f_output_wi = 0; + return v_s; +} + +#endif // !defined(WUFFS_CONFIG__MODULES) || defined(WUFFS_CONFIG__MODULE__LZW) + +#if !defined(WUFFS_CONFIG__MODULES) || defined(WUFFS_CONFIG__MODULE__GIF) + +// ---------------- Status Codes Implementations + +const char wuffs_gif__error__bad_extension_label[] = "#gif: bad extension label"; +const char wuffs_gif__error__bad_frame_size[] = "#gif: bad frame size"; +const char wuffs_gif__error__bad_graphic_control[] = "#gif: bad graphic control"; +const char wuffs_gif__error__bad_header[] = "#gif: bad header"; +const char wuffs_gif__error__bad_literal_width[] = "#gif: bad literal width"; +const char wuffs_gif__error__bad_palette[] = "#gif: bad palette"; +const char wuffs_gif__error__truncated_input[] = "#gif: truncated input"; +const char wuffs_gif__error__internal_error_inconsistent_ri_wi[] = "#gif: internal error: inconsistent ri/wi"; + +// ---------------- Private Consts + +static const uint32_t +WUFFS_GIF__INTERLACE_START[5] WUFFS_BASE__POTENTIALLY_UNUSED = { + 4294967295, 1, 2, 4, 0, +}; + +static const uint8_t +WUFFS_GIF__INTERLACE_DELTA[5] WUFFS_BASE__POTENTIALLY_UNUSED = { + 1, 2, 4, 8, 8, +}; + +static const uint8_t +WUFFS_GIF__INTERLACE_COUNT[5] WUFFS_BASE__POTENTIALLY_UNUSED = { + 0, 1, 2, 4, 8, +}; + +static const uint8_t +WUFFS_GIF__ANIMEXTS1DOT0[11] WUFFS_BASE__POTENTIALLY_UNUSED = { + 65, 78, 73, 77, 69, 88, 84, 83, + 49, 46, 48, +}; + +static const uint8_t +WUFFS_GIF__NETSCAPE2DOT0[11] WUFFS_BASE__POTENTIALLY_UNUSED = { + 78, 69, 84, 83, 67, 65, 80, 69, + 50, 46, 48, +}; + +static const uint8_t +WUFFS_GIF__ICCRGBG1012[11] WUFFS_BASE__POTENTIALLY_UNUSED = { + 73, 67, 67, 82, 71, 66, 71, 49, + 48, 49, 50, +}; + +static const uint8_t +WUFFS_GIF__XMPDATAXMP[11] WUFFS_BASE__POTENTIALLY_UNUSED = { + 88, 77, 80, 32, 68, 97, 116, 97, + 88, 77, 80, +}; + +#define WUFFS_GIF__QUIRKS_BASE 1041635328 + +#define WUFFS_GIF__QUIRKS_COUNT 7 + +// ---------------- Private Initializer Prototypes + +// ---------------- Private Function Prototypes + +static wuffs_base__status +wuffs_gif__decoder__do_decode_image_config( + wuffs_gif__decoder* self, + wuffs_base__image_config* a_dst, + wuffs_base__io_buffer* a_src); + +static wuffs_base__status +wuffs_gif__decoder__do_tell_me_more( + wuffs_gif__decoder* self, + wuffs_base__io_buffer* a_dst, + wuffs_base__more_information* a_minfo, + wuffs_base__io_buffer* a_src); + +static wuffs_base__status +wuffs_gif__decoder__do_decode_frame_config( + wuffs_gif__decoder* self, + wuffs_base__frame_config* a_dst, + wuffs_base__io_buffer* a_src); + +static wuffs_base__status +wuffs_gif__decoder__skip_frame( + wuffs_gif__decoder* self, + wuffs_base__io_buffer* a_src); + +static wuffs_base__status +wuffs_gif__decoder__do_decode_frame( + wuffs_gif__decoder* self, + wuffs_base__pixel_buffer* a_dst, + wuffs_base__io_buffer* a_src, + wuffs_base__pixel_blend a_blend, + wuffs_base__slice_u8 a_workbuf, + wuffs_base__decode_frame_options* a_opts); + +static wuffs_base__empty_struct +wuffs_gif__decoder__reset_gc( + wuffs_gif__decoder* self); + +static wuffs_base__status +wuffs_gif__decoder__decode_up_to_id_part1( + wuffs_gif__decoder* self, + wuffs_base__io_buffer* a_src); + +static wuffs_base__status +wuffs_gif__decoder__decode_header( + wuffs_gif__decoder* self, + wuffs_base__io_buffer* a_src); + +static wuffs_base__status +wuffs_gif__decoder__decode_lsd( + wuffs_gif__decoder* self, + wuffs_base__io_buffer* a_src); + +static wuffs_base__status +wuffs_gif__decoder__decode_extension( + wuffs_gif__decoder* self, + wuffs_base__io_buffer* a_src); + +static wuffs_base__status +wuffs_gif__decoder__skip_blocks( + wuffs_gif__decoder* self, + wuffs_base__io_buffer* a_src); + +static wuffs_base__status +wuffs_gif__decoder__decode_ae( + wuffs_gif__decoder* self, + wuffs_base__io_buffer* a_src); + +static wuffs_base__status +wuffs_gif__decoder__decode_gc( + wuffs_gif__decoder* self, + wuffs_base__io_buffer* a_src); + +static wuffs_base__status +wuffs_gif__decoder__decode_id_part0( + wuffs_gif__decoder* self, + wuffs_base__io_buffer* a_src); + +static wuffs_base__status +wuffs_gif__decoder__decode_id_part1( + wuffs_gif__decoder* self, + wuffs_base__pixel_buffer* a_dst, + wuffs_base__io_buffer* a_src, + wuffs_base__pixel_blend a_blend); + +static wuffs_base__status +wuffs_gif__decoder__decode_id_part2( + wuffs_gif__decoder* self, + wuffs_base__pixel_buffer* a_dst, + wuffs_base__io_buffer* a_src, + wuffs_base__slice_u8 a_workbuf); + +static wuffs_base__status +wuffs_gif__decoder__copy_to_image_buffer( + wuffs_gif__decoder* self, + wuffs_base__pixel_buffer* a_pb, + wuffs_base__slice_u8 a_src); + +// ---------------- VTables + +const wuffs_base__image_decoder__func_ptrs +wuffs_gif__decoder__func_ptrs_for__wuffs_base__image_decoder = { + (wuffs_base__status(*)(void*, + wuffs_base__pixel_buffer*, + wuffs_base__io_buffer*, + wuffs_base__pixel_blend, + wuffs_base__slice_u8, + wuffs_base__decode_frame_options*))(&wuffs_gif__decoder__decode_frame), + (wuffs_base__status(*)(void*, + wuffs_base__frame_config*, + wuffs_base__io_buffer*))(&wuffs_gif__decoder__decode_frame_config), + (wuffs_base__status(*)(void*, + wuffs_base__image_config*, + wuffs_base__io_buffer*))(&wuffs_gif__decoder__decode_image_config), + (wuffs_base__rect_ie_u32(*)(const void*))(&wuffs_gif__decoder__frame_dirty_rect), + (uint32_t(*)(const void*))(&wuffs_gif__decoder__num_animation_loops), + (uint64_t(*)(const void*))(&wuffs_gif__decoder__num_decoded_frame_configs), + (uint64_t(*)(const void*))(&wuffs_gif__decoder__num_decoded_frames), + (wuffs_base__status(*)(void*, + uint64_t, + uint64_t))(&wuffs_gif__decoder__restart_frame), + (wuffs_base__empty_struct(*)(void*, + uint32_t, + bool))(&wuffs_gif__decoder__set_quirk_enabled), + (wuffs_base__empty_struct(*)(void*, + uint32_t, + bool))(&wuffs_gif__decoder__set_report_metadata), + (wuffs_base__status(*)(void*, + wuffs_base__io_buffer*, + wuffs_base__more_information*, + wuffs_base__io_buffer*))(&wuffs_gif__decoder__tell_me_more), + (wuffs_base__range_ii_u64(*)(const void*))(&wuffs_gif__decoder__workbuf_len), +}; + +// ---------------- Initializer Implementations + +wuffs_base__status WUFFS_BASE__WARN_UNUSED_RESULT +wuffs_gif__decoder__initialize( + wuffs_gif__decoder* self, + size_t sizeof_star_self, + uint64_t wuffs_version, + uint32_t options){ + if (!self) { + return wuffs_base__make_status(wuffs_base__error__bad_receiver); + } + if (sizeof(*self) != sizeof_star_self) { + return wuffs_base__make_status(wuffs_base__error__bad_sizeof_receiver); + } + if (((wuffs_version >> 32) != WUFFS_VERSION_MAJOR) || + (((wuffs_version >> 16) & 0xFFFF) > WUFFS_VERSION_MINOR)) { + return wuffs_base__make_status(wuffs_base__error__bad_wuffs_version); + } + + if ((options & WUFFS_INITIALIZE__ALREADY_ZEROED) != 0) { + // The whole point of this if-check is to detect an uninitialized *self. + // We disable the warning on GCC. Clang-5.0 does not have this warning. +#if !defined(__clang__) && defined(__GNUC__) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wmaybe-uninitialized" +#endif + if (self->private_impl.magic != 0) { + return wuffs_base__make_status(wuffs_base__error__initialize_falsely_claimed_already_zeroed); + } +#if !defined(__clang__) && defined(__GNUC__) +#pragma GCC diagnostic pop +#endif + } else { + if ((options & WUFFS_INITIALIZE__LEAVE_INTERNAL_BUFFERS_UNINITIALIZED) == 0) { + memset(self, 0, sizeof(*self)); + options |= WUFFS_INITIALIZE__ALREADY_ZEROED; + } else { + memset(&(self->private_impl), 0, sizeof(self->private_impl)); + } + } + + { + wuffs_base__status z = wuffs_lzw__decoder__initialize( + &self->private_data.f_lzw, sizeof(self->private_data.f_lzw), WUFFS_VERSION, options); + if (z.repr) { + return z; + } + } + self->private_impl.magic = WUFFS_BASE__MAGIC; + self->private_impl.vtable_for__wuffs_base__image_decoder.vtable_name = + wuffs_base__image_decoder__vtable_name; + self->private_impl.vtable_for__wuffs_base__image_decoder.function_pointers = + (const void*)(&wuffs_gif__decoder__func_ptrs_for__wuffs_base__image_decoder); + return wuffs_base__make_status(NULL); +} + +wuffs_gif__decoder* +wuffs_gif__decoder__alloc() { + wuffs_gif__decoder* x = + (wuffs_gif__decoder*)(calloc(sizeof(wuffs_gif__decoder), 1)); + if (!x) { + return NULL; + } + if (wuffs_gif__decoder__initialize( + x, sizeof(wuffs_gif__decoder), WUFFS_VERSION, WUFFS_INITIALIZE__ALREADY_ZEROED).repr) { + free(x); + return NULL; + } + return x; +} + +size_t +sizeof__wuffs_gif__decoder() { + return sizeof(wuffs_gif__decoder); +} + +// ---------------- Function Implementations + +// -------- func gif.decoder.set_quirk_enabled + +WUFFS_BASE__MAYBE_STATIC wuffs_base__empty_struct +wuffs_gif__decoder__set_quirk_enabled( + wuffs_gif__decoder* self, + uint32_t a_quirk, + bool a_enabled) { + if (!self) { + return wuffs_base__make_empty_struct(); + } + if (self->private_impl.magic != WUFFS_BASE__MAGIC) { + return wuffs_base__make_empty_struct(); + } + + if ((self->private_impl.f_call_sequence == 0) && (a_quirk >= 1041635328)) { + a_quirk -= 1041635328; + if (a_quirk < 7) { + self->private_impl.f_quirks[a_quirk] = a_enabled; + } + } + return wuffs_base__make_empty_struct(); +} + +// -------- func gif.decoder.decode_image_config + +WUFFS_BASE__MAYBE_STATIC wuffs_base__status +wuffs_gif__decoder__decode_image_config( + wuffs_gif__decoder* self, + wuffs_base__image_config* a_dst, + wuffs_base__io_buffer* a_src) { + if (!self) { + return wuffs_base__make_status(wuffs_base__error__bad_receiver); + } + if (self->private_impl.magic != WUFFS_BASE__MAGIC) { + return wuffs_base__make_status( + (self->private_impl.magic == WUFFS_BASE__DISABLED) + ? wuffs_base__error__disabled_by_previous_error + : wuffs_base__error__initialize_not_called); + } + if (!a_src) { + self->private_impl.magic = WUFFS_BASE__DISABLED; + return wuffs_base__make_status(wuffs_base__error__bad_argument); + } + if ((self->private_impl.active_coroutine != 0) && + (self->private_impl.active_coroutine != 1)) { + self->private_impl.magic = WUFFS_BASE__DISABLED; + return wuffs_base__make_status(wuffs_base__error__interleaved_coroutine_calls); + } + self->private_impl.active_coroutine = 0; + wuffs_base__status status = wuffs_base__make_status(NULL); + + wuffs_base__status v_status = wuffs_base__make_status(NULL); + + uint32_t coro_susp_point = self->private_impl.p_decode_image_config[0]; + switch (coro_susp_point) { + WUFFS_BASE__COROUTINE_SUSPENSION_POINT_0; + + while (true) { + { + wuffs_base__status t_0 = wuffs_gif__decoder__do_decode_image_config(self, a_dst, a_src); + v_status = t_0; + } + if ((v_status.repr == wuffs_base__suspension__short_read) && (a_src && a_src->meta.closed)) { + status = wuffs_base__make_status(wuffs_gif__error__truncated_input); + goto exit; + } + status = v_status; + WUFFS_BASE__COROUTINE_SUSPENSION_POINT_MAYBE_SUSPEND(1); + } + + ok: + self->private_impl.p_decode_image_config[0] = 0; + goto exit; + } + + goto suspend; + suspend: + self->private_impl.p_decode_image_config[0] = wuffs_base__status__is_suspension(&status) ? coro_susp_point : 0; + self->private_impl.active_coroutine = wuffs_base__status__is_suspension(&status) ? 1 : 0; + + goto exit; + exit: + if (wuffs_base__status__is_error(&status)) { + self->private_impl.magic = WUFFS_BASE__DISABLED; + } + return status; +} + +// -------- func gif.decoder.do_decode_image_config + +static wuffs_base__status +wuffs_gif__decoder__do_decode_image_config( + wuffs_gif__decoder* self, + wuffs_base__image_config* a_dst, + wuffs_base__io_buffer* a_src) { + wuffs_base__status status = wuffs_base__make_status(NULL); + + bool v_ffio = false; + + uint32_t coro_susp_point = self->private_impl.p_do_decode_image_config[0]; + switch (coro_susp_point) { + WUFFS_BASE__COROUTINE_SUSPENSION_POINT_0; + + if (self->private_impl.f_call_sequence != 0) { + status = wuffs_base__make_status(wuffs_base__error__bad_call_sequence); + goto exit; + } else if ( ! self->private_impl.f_seen_header) { + WUFFS_BASE__COROUTINE_SUSPENSION_POINT(1); + status = wuffs_gif__decoder__decode_header(self, a_src); + if (status.repr) { + goto suspend; + } + WUFFS_BASE__COROUTINE_SUSPENSION_POINT(2); + status = wuffs_gif__decoder__decode_lsd(self, a_src); + if (status.repr) { + goto suspend; + } + self->private_impl.f_seen_header = true; + } + WUFFS_BASE__COROUTINE_SUSPENSION_POINT(3); + status = wuffs_gif__decoder__decode_up_to_id_part1(self, a_src); + if (status.repr) { + goto suspend; + } + v_ffio = ! self->private_impl.f_gc_has_transparent_index; + if ( ! self->private_impl.f_quirks[2]) { + v_ffio = (v_ffio && + (self->private_impl.f_frame_rect_x0 == 0) && + (self->private_impl.f_frame_rect_y0 == 0) && + (self->private_impl.f_frame_rect_x1 == self->private_impl.f_width) && + (self->private_impl.f_frame_rect_y1 == self->private_impl.f_height)); + } else if (v_ffio) { + self->private_impl.f_black_color_u32_argb_premul = 4278190080; + } + if (self->private_impl.f_background_color_u32_argb_premul == 77) { + self->private_impl.f_background_color_u32_argb_premul = self->private_impl.f_black_color_u32_argb_premul; + } + if (a_dst != NULL) { + wuffs_base__image_config__set( + a_dst, + 2198077448, + 0, + self->private_impl.f_width, + self->private_impl.f_height, + self->private_impl.f_frame_config_io_position, + v_ffio); + } + if (self->private_impl.f_call_sequence == 0) { + self->private_impl.f_call_sequence = 32; + } + + goto ok; + ok: + self->private_impl.p_do_decode_image_config[0] = 0; + goto exit; + } + + goto suspend; + suspend: + self->private_impl.p_do_decode_image_config[0] = wuffs_base__status__is_suspension(&status) ? coro_susp_point : 0; + + goto exit; + exit: + return status; +} + +// -------- func gif.decoder.set_report_metadata + +WUFFS_BASE__MAYBE_STATIC wuffs_base__empty_struct +wuffs_gif__decoder__set_report_metadata( + wuffs_gif__decoder* self, + uint32_t a_fourcc, + bool a_report) { + if (!self) { + return wuffs_base__make_empty_struct(); + } + if (self->private_impl.magic != WUFFS_BASE__MAGIC) { + return wuffs_base__make_empty_struct(); + } + + if (a_fourcc == 1229144912) { + self->private_impl.f_report_metadata_iccp = a_report; + } else if (a_fourcc == 1481461792) { + self->private_impl.f_report_metadata_xmp = a_report; + } + return wuffs_base__make_empty_struct(); +} + +// -------- func gif.decoder.tell_me_more + +WUFFS_BASE__MAYBE_STATIC wuffs_base__status +wuffs_gif__decoder__tell_me_more( + wuffs_gif__decoder* self, + wuffs_base__io_buffer* a_dst, + wuffs_base__more_information* a_minfo, + wuffs_base__io_buffer* a_src) { + if (!self) { + return wuffs_base__make_status(wuffs_base__error__bad_receiver); + } + if (self->private_impl.magic != WUFFS_BASE__MAGIC) { + return wuffs_base__make_status( + (self->private_impl.magic == WUFFS_BASE__DISABLED) + ? wuffs_base__error__disabled_by_previous_error + : wuffs_base__error__initialize_not_called); + } + if (!a_dst || !a_src) { + self->private_impl.magic = WUFFS_BASE__DISABLED; + return wuffs_base__make_status(wuffs_base__error__bad_argument); + } + if ((self->private_impl.active_coroutine != 0) && + (self->private_impl.active_coroutine != 2)) { + self->private_impl.magic = WUFFS_BASE__DISABLED; + return wuffs_base__make_status(wuffs_base__error__interleaved_coroutine_calls); + } + self->private_impl.active_coroutine = 0; + wuffs_base__status status = wuffs_base__make_status(NULL); + + wuffs_base__status v_status = wuffs_base__make_status(NULL); + + uint32_t coro_susp_point = self->private_impl.p_tell_me_more[0]; + switch (coro_susp_point) { + WUFFS_BASE__COROUTINE_SUSPENSION_POINT_0; + + while (true) { + { + wuffs_base__status t_0 = wuffs_gif__decoder__do_tell_me_more(self, a_dst, a_minfo, a_src); + v_status = t_0; + } + if ((v_status.repr == wuffs_base__suspension__short_read) && (a_src && a_src->meta.closed)) { + status = wuffs_base__make_status(wuffs_gif__error__truncated_input); + goto exit; + } + status = v_status; + WUFFS_BASE__COROUTINE_SUSPENSION_POINT_MAYBE_SUSPEND(1); + } + + ok: + self->private_impl.p_tell_me_more[0] = 0; + goto exit; + } + + goto suspend; + suspend: + self->private_impl.p_tell_me_more[0] = wuffs_base__status__is_suspension(&status) ? coro_susp_point : 0; + self->private_impl.active_coroutine = wuffs_base__status__is_suspension(&status) ? 2 : 0; + + goto exit; + exit: + if (wuffs_base__status__is_error(&status)) { + self->private_impl.magic = WUFFS_BASE__DISABLED; + } + return status; +} + +// -------- func gif.decoder.do_tell_me_more + +static wuffs_base__status +wuffs_gif__decoder__do_tell_me_more( + wuffs_gif__decoder* self, + wuffs_base__io_buffer* a_dst, + wuffs_base__more_information* a_minfo, + wuffs_base__io_buffer* a_src) { + wuffs_base__status status = wuffs_base__make_status(NULL); + + uint64_t v_chunk_length = 0; + + const uint8_t* iop_a_src = NULL; + const uint8_t* io0_a_src WUFFS_BASE__POTENTIALLY_UNUSED = NULL; + const uint8_t* io1_a_src WUFFS_BASE__POTENTIALLY_UNUSED = NULL; + const uint8_t* io2_a_src WUFFS_BASE__POTENTIALLY_UNUSED = NULL; + if (a_src && a_src->data.ptr) { + io0_a_src = a_src->data.ptr; + io1_a_src = io0_a_src + a_src->meta.ri; + iop_a_src = io1_a_src; + io2_a_src = io0_a_src + a_src->meta.wi; + } + + uint32_t coro_susp_point = self->private_impl.p_do_tell_me_more[0]; + switch (coro_susp_point) { + WUFFS_BASE__COROUTINE_SUSPENSION_POINT_0; + + if ((self->private_impl.f_call_sequence & 16) == 0) { + status = wuffs_base__make_status(wuffs_base__error__bad_call_sequence); + goto exit; + } + if (self->private_impl.f_metadata_fourcc == 0) { + status = wuffs_base__make_status(wuffs_base__error__no_more_information); + goto exit; + } + while (true) { + label__0__continue:; + while (true) { + if (wuffs_base__u64__sat_add((a_src ? a_src->meta.pos : 0), ((uint64_t)(iop_a_src - io0_a_src))) != self->private_impl.f_metadata_io_position) { + if (a_minfo != NULL) { + wuffs_base__more_information__set(a_minfo, + 2, + 0, + self->private_impl.f_metadata_io_position, + 0, + 0); + } + status = wuffs_base__make_status(wuffs_base__suspension__mispositioned_read); + WUFFS_BASE__COROUTINE_SUSPENSION_POINT_MAYBE_SUSPEND(1); + goto label__0__continue; + } + if (((uint64_t)(io2_a_src - iop_a_src)) <= 0) { + if (a_minfo != NULL) { + wuffs_base__more_information__set(a_minfo, + 0, + 0, + 0, + 0, + 0); + } + status = wuffs_base__make_status(wuffs_base__suspension__short_read); + WUFFS_BASE__COROUTINE_SUSPENSION_POINT_MAYBE_SUSPEND(2); + goto label__0__continue; + } + goto label__0__break; + } + label__0__break:; + v_chunk_length = ((uint64_t)(wuffs_base__peek_u8be__no_bounds_check(iop_a_src))); + if (v_chunk_length <= 0) { + iop_a_src += 1; + goto label__1__break; + } + if (self->private_impl.f_metadata_fourcc == 1481461792) { + v_chunk_length += 1; + } else { + iop_a_src += 1; + } + self->private_impl.f_metadata_io_position = wuffs_base__u64__sat_add(wuffs_base__u64__sat_add((a_src ? a_src->meta.pos : 0), ((uint64_t)(iop_a_src - io0_a_src))), v_chunk_length); + if (a_minfo != NULL) { + wuffs_base__more_information__set(a_minfo, + 3, + self->private_impl.f_metadata_fourcc, + 0, + wuffs_base__u64__sat_add((a_src ? a_src->meta.pos : 0), ((uint64_t)(iop_a_src - io0_a_src))), + self->private_impl.f_metadata_io_position); + } + status = wuffs_base__make_status(wuffs_base__suspension__even_more_information); + WUFFS_BASE__COROUTINE_SUSPENSION_POINT_MAYBE_SUSPEND(3); + } + label__1__break:; + if (a_minfo != NULL) { + wuffs_base__more_information__set(a_minfo, + 3, + self->private_impl.f_metadata_fourcc, + 0, + self->private_impl.f_metadata_io_position, + self->private_impl.f_metadata_io_position); + } + self->private_impl.f_call_sequence &= 239; + self->private_impl.f_metadata_fourcc = 0; + self->private_impl.f_metadata_io_position = 0; + status = wuffs_base__make_status(NULL); + goto ok; + + ok: + self->private_impl.p_do_tell_me_more[0] = 0; + goto exit; + } + + goto suspend; + suspend: + self->private_impl.p_do_tell_me_more[0] = wuffs_base__status__is_suspension(&status) ? coro_susp_point : 0; + + goto exit; + exit: + if (a_src && a_src->data.ptr) { + a_src->meta.ri = ((size_t)(iop_a_src - a_src->data.ptr)); + } + + return status; +} + +// -------- func gif.decoder.num_animation_loops + +WUFFS_BASE__MAYBE_STATIC uint32_t +wuffs_gif__decoder__num_animation_loops( + const wuffs_gif__decoder* self) { + if (!self) { + return 0; + } + if ((self->private_impl.magic != WUFFS_BASE__MAGIC) && + (self->private_impl.magic != WUFFS_BASE__DISABLED)) { + return 0; + } + + if (self->private_impl.f_seen_num_animation_loops_value) { + return self->private_impl.f_num_animation_loops_value; + } + if (self->private_impl.f_num_decoded_frame_configs_value > 1) { + return 1; + } + return 0; +} + +// -------- func gif.decoder.num_decoded_frame_configs + +WUFFS_BASE__MAYBE_STATIC uint64_t +wuffs_gif__decoder__num_decoded_frame_configs( + const wuffs_gif__decoder* self) { + if (!self) { + return 0; + } + if ((self->private_impl.magic != WUFFS_BASE__MAGIC) && + (self->private_impl.magic != WUFFS_BASE__DISABLED)) { + return 0; + } + + return self->private_impl.f_num_decoded_frame_configs_value; +} + +// -------- func gif.decoder.num_decoded_frames + +WUFFS_BASE__MAYBE_STATIC uint64_t +wuffs_gif__decoder__num_decoded_frames( + const wuffs_gif__decoder* self) { + if (!self) { + return 0; + } + if ((self->private_impl.magic != WUFFS_BASE__MAGIC) && + (self->private_impl.magic != WUFFS_BASE__DISABLED)) { + return 0; + } + + return self->private_impl.f_num_decoded_frames_value; +} + +// -------- func gif.decoder.frame_dirty_rect + +WUFFS_BASE__MAYBE_STATIC wuffs_base__rect_ie_u32 +wuffs_gif__decoder__frame_dirty_rect( + const wuffs_gif__decoder* self) { + if (!self) { + return wuffs_base__utility__empty_rect_ie_u32(); + } + if ((self->private_impl.magic != WUFFS_BASE__MAGIC) && + (self->private_impl.magic != WUFFS_BASE__DISABLED)) { + return wuffs_base__utility__empty_rect_ie_u32(); + } + + return wuffs_base__utility__make_rect_ie_u32( + wuffs_base__u32__min(self->private_impl.f_frame_rect_x0, self->private_impl.f_width), + wuffs_base__u32__min(self->private_impl.f_frame_rect_y0, self->private_impl.f_height), + wuffs_base__u32__min(self->private_impl.f_frame_rect_x1, self->private_impl.f_width), + wuffs_base__u32__min(self->private_impl.f_dirty_max_excl_y, self->private_impl.f_height)); +} + +// -------- func gif.decoder.workbuf_len + +WUFFS_BASE__MAYBE_STATIC wuffs_base__range_ii_u64 +wuffs_gif__decoder__workbuf_len( + const wuffs_gif__decoder* self) { + if (!self) { + return wuffs_base__utility__empty_range_ii_u64(); + } + if ((self->private_impl.magic != WUFFS_BASE__MAGIC) && + (self->private_impl.magic != WUFFS_BASE__DISABLED)) { + return wuffs_base__utility__empty_range_ii_u64(); + } + + return wuffs_base__utility__make_range_ii_u64(0, 0); +} + +// -------- func gif.decoder.restart_frame + +WUFFS_BASE__MAYBE_STATIC wuffs_base__status +wuffs_gif__decoder__restart_frame( + wuffs_gif__decoder* self, + uint64_t a_index, + uint64_t a_io_position) { + if (!self) { + return wuffs_base__make_status(wuffs_base__error__bad_receiver); + } + if (self->private_impl.magic != WUFFS_BASE__MAGIC) { + return wuffs_base__make_status( + (self->private_impl.magic == WUFFS_BASE__DISABLED) + ? wuffs_base__error__disabled_by_previous_error + : wuffs_base__error__initialize_not_called); + } + + if (self->private_impl.f_call_sequence < 32) { + return wuffs_base__make_status(wuffs_base__error__bad_call_sequence); + } else if (a_io_position == 0) { + return wuffs_base__make_status(wuffs_base__error__bad_argument); + } + self->private_impl.f_delayed_num_decoded_frames = false; + self->private_impl.f_frame_config_io_position = a_io_position; + self->private_impl.f_num_decoded_frame_configs_value = a_index; + self->private_impl.f_num_decoded_frames_value = a_index; + wuffs_gif__decoder__reset_gc(self); + self->private_impl.f_call_sequence = 40; + return wuffs_base__make_status(NULL); +} + +// -------- func gif.decoder.decode_frame_config + +WUFFS_BASE__MAYBE_STATIC wuffs_base__status +wuffs_gif__decoder__decode_frame_config( + wuffs_gif__decoder* self, + wuffs_base__frame_config* a_dst, + wuffs_base__io_buffer* a_src) { + if (!self) { + return wuffs_base__make_status(wuffs_base__error__bad_receiver); + } + if (self->private_impl.magic != WUFFS_BASE__MAGIC) { + return wuffs_base__make_status( + (self->private_impl.magic == WUFFS_BASE__DISABLED) + ? wuffs_base__error__disabled_by_previous_error + : wuffs_base__error__initialize_not_called); + } + if (!a_src) { + self->private_impl.magic = WUFFS_BASE__DISABLED; + return wuffs_base__make_status(wuffs_base__error__bad_argument); + } + if ((self->private_impl.active_coroutine != 0) && + (self->private_impl.active_coroutine != 3)) { + self->private_impl.magic = WUFFS_BASE__DISABLED; + return wuffs_base__make_status(wuffs_base__error__interleaved_coroutine_calls); + } + self->private_impl.active_coroutine = 0; + wuffs_base__status status = wuffs_base__make_status(NULL); + + wuffs_base__status v_status = wuffs_base__make_status(NULL); + + uint32_t coro_susp_point = self->private_impl.p_decode_frame_config[0]; + switch (coro_susp_point) { + WUFFS_BASE__COROUTINE_SUSPENSION_POINT_0; + + while (true) { + { + wuffs_base__status t_0 = wuffs_gif__decoder__do_decode_frame_config(self, a_dst, a_src); + v_status = t_0; + } + if ((v_status.repr == wuffs_base__suspension__short_read) && (a_src && a_src->meta.closed)) { + status = wuffs_base__make_status(wuffs_gif__error__truncated_input); + goto exit; + } + status = v_status; + WUFFS_BASE__COROUTINE_SUSPENSION_POINT_MAYBE_SUSPEND(1); + } + + ok: + self->private_impl.p_decode_frame_config[0] = 0; + goto exit; + } + + goto suspend; + suspend: + self->private_impl.p_decode_frame_config[0] = wuffs_base__status__is_suspension(&status) ? coro_susp_point : 0; + self->private_impl.active_coroutine = wuffs_base__status__is_suspension(&status) ? 3 : 0; + + goto exit; + exit: + if (wuffs_base__status__is_error(&status)) { + self->private_impl.magic = WUFFS_BASE__DISABLED; + } + return status; +} + +// -------- func gif.decoder.do_decode_frame_config + +static wuffs_base__status +wuffs_gif__decoder__do_decode_frame_config( + wuffs_gif__decoder* self, + wuffs_base__frame_config* a_dst, + wuffs_base__io_buffer* a_src) { + wuffs_base__status status = wuffs_base__make_status(NULL); + + uint32_t v_background_color = 0; + uint8_t v_flags = 0; + + const uint8_t* iop_a_src = NULL; + const uint8_t* io0_a_src WUFFS_BASE__POTENTIALLY_UNUSED = NULL; + const uint8_t* io1_a_src WUFFS_BASE__POTENTIALLY_UNUSED = NULL; + const uint8_t* io2_a_src WUFFS_BASE__POTENTIALLY_UNUSED = NULL; + if (a_src && a_src->data.ptr) { + io0_a_src = a_src->data.ptr; + io1_a_src = io0_a_src + a_src->meta.ri; + iop_a_src = io1_a_src; + io2_a_src = io0_a_src + a_src->meta.wi; + } + + uint32_t coro_susp_point = self->private_impl.p_do_decode_frame_config[0]; + if (coro_susp_point) { + v_background_color = self->private_data.s_do_decode_frame_config[0].v_background_color; + } + switch (coro_susp_point) { + WUFFS_BASE__COROUTINE_SUSPENSION_POINT_0; + + self->private_impl.f_dirty_max_excl_y = 0; + if ((self->private_impl.f_call_sequence & 16) != 0) { + status = wuffs_base__make_status(wuffs_base__error__bad_call_sequence); + goto exit; + } else if (self->private_impl.f_call_sequence == 32) { + } else if (self->private_impl.f_call_sequence < 32) { + if (a_src) { + a_src->meta.ri = ((size_t)(iop_a_src - a_src->data.ptr)); + } + WUFFS_BASE__COROUTINE_SUSPENSION_POINT(1); + status = wuffs_gif__decoder__do_decode_image_config(self, NULL, a_src); + if (a_src) { + iop_a_src = a_src->data.ptr + a_src->meta.ri; + } + if (status.repr) { + goto suspend; + } + } else if (self->private_impl.f_call_sequence == 40) { + if (self->private_impl.f_frame_config_io_position != wuffs_base__u64__sat_add((a_src ? a_src->meta.pos : 0), ((uint64_t)(iop_a_src - io0_a_src)))) { + status = wuffs_base__make_status(wuffs_base__error__bad_restart); + goto exit; + } + } else if (self->private_impl.f_call_sequence == 64) { + if (a_src) { + a_src->meta.ri = ((size_t)(iop_a_src - a_src->data.ptr)); + } + WUFFS_BASE__COROUTINE_SUSPENSION_POINT(2); + status = wuffs_gif__decoder__skip_frame(self, a_src); + if (a_src) { + iop_a_src = a_src->data.ptr + a_src->meta.ri; + } + if (status.repr) { + goto suspend; + } + if (self->private_impl.f_call_sequence >= 96) { + status = wuffs_base__make_status(wuffs_base__note__end_of_data); + goto ok; + } + } else { + status = wuffs_base__make_status(wuffs_base__note__end_of_data); + goto ok; + } + if ((self->private_impl.f_num_decoded_frame_configs_value > 0) || (self->private_impl.f_call_sequence == 40)) { + if (a_src) { + a_src->meta.ri = ((size_t)(iop_a_src - a_src->data.ptr)); + } + WUFFS_BASE__COROUTINE_SUSPENSION_POINT(3); + status = wuffs_gif__decoder__decode_up_to_id_part1(self, a_src); + if (a_src) { + iop_a_src = a_src->data.ptr + a_src->meta.ri; + } + if (status.repr) { + goto suspend; + } + if (self->private_impl.f_call_sequence >= 96) { + status = wuffs_base__make_status(wuffs_base__note__end_of_data); + goto ok; + } + } + v_background_color = self->private_impl.f_black_color_u32_argb_premul; + if ( ! self->private_impl.f_gc_has_transparent_index) { + v_background_color = self->private_impl.f_background_color_u32_argb_premul; + if (self->private_impl.f_quirks[1] && (self->private_impl.f_num_decoded_frame_configs_value == 0)) { + while (((uint64_t)(io2_a_src - iop_a_src)) <= 0) { + status = wuffs_base__make_status(wuffs_base__suspension__short_read); + WUFFS_BASE__COROUTINE_SUSPENSION_POINT_MAYBE_SUSPEND(4); + } + v_flags = wuffs_base__peek_u8be__no_bounds_check(iop_a_src); + if ((v_flags & 128) != 0) { + v_background_color = self->private_impl.f_black_color_u32_argb_premul; + } + } + } + if (a_dst != NULL) { + wuffs_base__frame_config__set( + a_dst, + wuffs_base__utility__make_rect_ie_u32( + wuffs_base__u32__min(self->private_impl.f_frame_rect_x0, self->private_impl.f_width), + wuffs_base__u32__min(self->private_impl.f_frame_rect_y0, self->private_impl.f_height), + wuffs_base__u32__min(self->private_impl.f_frame_rect_x1, self->private_impl.f_width), + wuffs_base__u32__min(self->private_impl.f_frame_rect_y1, self->private_impl.f_height)), + ((wuffs_base__flicks)(self->private_impl.f_gc_duration)), + self->private_impl.f_num_decoded_frame_configs_value, + self->private_impl.f_frame_config_io_position, + self->private_impl.f_gc_disposal, + ! self->private_impl.f_gc_has_transparent_index, + false, + v_background_color); + } + wuffs_base__u64__sat_add_indirect(&self->private_impl.f_num_decoded_frame_configs_value, 1); + self->private_impl.f_call_sequence = 64; + + ok: + self->private_impl.p_do_decode_frame_config[0] = 0; + goto exit; + } + + goto suspend; + suspend: + self->private_impl.p_do_decode_frame_config[0] = wuffs_base__status__is_suspension(&status) ? coro_susp_point : 0; + self->private_data.s_do_decode_frame_config[0].v_background_color = v_background_color; + + goto exit; + exit: + if (a_src && a_src->data.ptr) { + a_src->meta.ri = ((size_t)(iop_a_src - a_src->data.ptr)); + } + + return status; +} + +// -------- func gif.decoder.skip_frame + +static wuffs_base__status +wuffs_gif__decoder__skip_frame( + wuffs_gif__decoder* self, + wuffs_base__io_buffer* a_src) { + wuffs_base__status status = wuffs_base__make_status(NULL); + + uint8_t v_flags = 0; + uint8_t v_lw = 0; + + const uint8_t* iop_a_src = NULL; + const uint8_t* io0_a_src WUFFS_BASE__POTENTIALLY_UNUSED = NULL; + const uint8_t* io1_a_src WUFFS_BASE__POTENTIALLY_UNUSED = NULL; + const uint8_t* io2_a_src WUFFS_BASE__POTENTIALLY_UNUSED = NULL; + if (a_src && a_src->data.ptr) { + io0_a_src = a_src->data.ptr; + io1_a_src = io0_a_src + a_src->meta.ri; + iop_a_src = io1_a_src; + io2_a_src = io0_a_src + a_src->meta.wi; + } + + uint32_t coro_susp_point = self->private_impl.p_skip_frame[0]; + switch (coro_susp_point) { + WUFFS_BASE__COROUTINE_SUSPENSION_POINT_0; + + { + WUFFS_BASE__COROUTINE_SUSPENSION_POINT(1); + if (WUFFS_BASE__UNLIKELY(iop_a_src == io2_a_src)) { + status = wuffs_base__make_status(wuffs_base__suspension__short_read); + goto suspend; + } + uint8_t t_0 = *iop_a_src++; + v_flags = t_0; + } + if ((v_flags & 128) != 0) { + self->private_data.s_skip_frame[0].scratch = (((uint32_t)(3)) << (1 + (v_flags & 7))); + WUFFS_BASE__COROUTINE_SUSPENSION_POINT(2); + if (self->private_data.s_skip_frame[0].scratch > ((uint64_t)(io2_a_src - iop_a_src))) { + self->private_data.s_skip_frame[0].scratch -= ((uint64_t)(io2_a_src - iop_a_src)); + iop_a_src = io2_a_src; + status = wuffs_base__make_status(wuffs_base__suspension__short_read); + goto suspend; + } + iop_a_src += self->private_data.s_skip_frame[0].scratch; + } + { + WUFFS_BASE__COROUTINE_SUSPENSION_POINT(3); + if (WUFFS_BASE__UNLIKELY(iop_a_src == io2_a_src)) { + status = wuffs_base__make_status(wuffs_base__suspension__short_read); + goto suspend; + } + uint8_t t_1 = *iop_a_src++; + v_lw = t_1; + } + if (v_lw > 8) { + status = wuffs_base__make_status(wuffs_gif__error__bad_literal_width); + goto exit; + } + if (a_src) { + a_src->meta.ri = ((size_t)(iop_a_src - a_src->data.ptr)); + } + WUFFS_BASE__COROUTINE_SUSPENSION_POINT(4); + status = wuffs_gif__decoder__skip_blocks(self, a_src); + if (a_src) { + iop_a_src = a_src->data.ptr + a_src->meta.ri; + } + if (status.repr) { + goto suspend; + } + if (self->private_impl.f_quirks[0]) { + self->private_impl.f_delayed_num_decoded_frames = true; + } else { + wuffs_base__u64__sat_add_indirect(&self->private_impl.f_num_decoded_frames_value, 1); + } + wuffs_gif__decoder__reset_gc(self); + self->private_impl.f_call_sequence = 32; + + goto ok; + ok: + self->private_impl.p_skip_frame[0] = 0; + goto exit; + } + + goto suspend; + suspend: + self->private_impl.p_skip_frame[0] = wuffs_base__status__is_suspension(&status) ? coro_susp_point : 0; + + goto exit; + exit: + if (a_src && a_src->data.ptr) { + a_src->meta.ri = ((size_t)(iop_a_src - a_src->data.ptr)); + } + + return status; +} + +// -------- func gif.decoder.decode_frame + +WUFFS_BASE__MAYBE_STATIC wuffs_base__status +wuffs_gif__decoder__decode_frame( + wuffs_gif__decoder* self, + wuffs_base__pixel_buffer* a_dst, + wuffs_base__io_buffer* a_src, + wuffs_base__pixel_blend a_blend, + wuffs_base__slice_u8 a_workbuf, + wuffs_base__decode_frame_options* a_opts) { + if (!self) { + return wuffs_base__make_status(wuffs_base__error__bad_receiver); + } + if (self->private_impl.magic != WUFFS_BASE__MAGIC) { + return wuffs_base__make_status( + (self->private_impl.magic == WUFFS_BASE__DISABLED) + ? wuffs_base__error__disabled_by_previous_error + : wuffs_base__error__initialize_not_called); + } + if (!a_dst || !a_src) { + self->private_impl.magic = WUFFS_BASE__DISABLED; + return wuffs_base__make_status(wuffs_base__error__bad_argument); + } + if ((self->private_impl.active_coroutine != 0) && + (self->private_impl.active_coroutine != 4)) { + self->private_impl.magic = WUFFS_BASE__DISABLED; + return wuffs_base__make_status(wuffs_base__error__interleaved_coroutine_calls); + } + self->private_impl.active_coroutine = 0; + wuffs_base__status status = wuffs_base__make_status(NULL); + + wuffs_base__status v_status = wuffs_base__make_status(NULL); + + uint32_t coro_susp_point = self->private_impl.p_decode_frame[0]; + switch (coro_susp_point) { + WUFFS_BASE__COROUTINE_SUSPENSION_POINT_0; + + while (true) { + { + wuffs_base__status t_0 = wuffs_gif__decoder__do_decode_frame(self, + a_dst, + a_src, + a_blend, + a_workbuf, + a_opts); + v_status = t_0; + } + if ((v_status.repr == wuffs_base__suspension__short_read) && (a_src && a_src->meta.closed)) { + status = wuffs_base__make_status(wuffs_gif__error__truncated_input); + goto exit; + } + status = v_status; + WUFFS_BASE__COROUTINE_SUSPENSION_POINT_MAYBE_SUSPEND(1); + } + + ok: + self->private_impl.p_decode_frame[0] = 0; + goto exit; + } + + goto suspend; + suspend: + self->private_impl.p_decode_frame[0] = wuffs_base__status__is_suspension(&status) ? coro_susp_point : 0; + self->private_impl.active_coroutine = wuffs_base__status__is_suspension(&status) ? 4 : 0; + + goto exit; + exit: + if (wuffs_base__status__is_error(&status)) { + self->private_impl.magic = WUFFS_BASE__DISABLED; + } + return status; +} + +// -------- func gif.decoder.do_decode_frame + +static wuffs_base__status +wuffs_gif__decoder__do_decode_frame( + wuffs_gif__decoder* self, + wuffs_base__pixel_buffer* a_dst, + wuffs_base__io_buffer* a_src, + wuffs_base__pixel_blend a_blend, + wuffs_base__slice_u8 a_workbuf, + wuffs_base__decode_frame_options* a_opts) { + wuffs_base__status status = wuffs_base__make_status(NULL); + + uint32_t coro_susp_point = self->private_impl.p_do_decode_frame[0]; + switch (coro_susp_point) { + WUFFS_BASE__COROUTINE_SUSPENSION_POINT_0; + + if (self->private_impl.f_call_sequence == 64) { + } else if (self->private_impl.f_call_sequence < 64) { + WUFFS_BASE__COROUTINE_SUSPENSION_POINT(1); + status = wuffs_gif__decoder__do_decode_frame_config(self, NULL, a_src); + if (status.repr) { + goto suspend; + } + } else { + status = wuffs_base__make_status(wuffs_base__note__end_of_data); + goto ok; + } + if (self->private_impl.f_quirks[5] && ((self->private_impl.f_frame_rect_x0 == self->private_impl.f_frame_rect_x1) || (self->private_impl.f_frame_rect_y0 == self->private_impl.f_frame_rect_y1))) { + status = wuffs_base__make_status(wuffs_gif__error__bad_frame_size); + goto exit; + } + WUFFS_BASE__COROUTINE_SUSPENSION_POINT(2); + status = wuffs_gif__decoder__decode_id_part1(self, a_dst, a_src, a_blend); + if (status.repr) { + goto suspend; + } + WUFFS_BASE__COROUTINE_SUSPENSION_POINT(3); + status = wuffs_gif__decoder__decode_id_part2(self, a_dst, a_src, a_workbuf); + if (status.repr) { + goto suspend; + } + wuffs_base__u64__sat_add_indirect(&self->private_impl.f_num_decoded_frames_value, 1); + wuffs_gif__decoder__reset_gc(self); + self->private_impl.f_call_sequence = 32; + + ok: + self->private_impl.p_do_decode_frame[0] = 0; + goto exit; + } + + goto suspend; + suspend: + self->private_impl.p_do_decode_frame[0] = wuffs_base__status__is_suspension(&status) ? coro_susp_point : 0; + + goto exit; + exit: + return status; +} + +// -------- func gif.decoder.reset_gc + +static wuffs_base__empty_struct +wuffs_gif__decoder__reset_gc( + wuffs_gif__decoder* self) { + self->private_impl.f_gc_has_transparent_index = false; + self->private_impl.f_gc_transparent_index = 0; + self->private_impl.f_gc_disposal = 0; + self->private_impl.f_gc_duration = 0; + return wuffs_base__make_empty_struct(); +} + +// -------- func gif.decoder.decode_up_to_id_part1 + +static wuffs_base__status +wuffs_gif__decoder__decode_up_to_id_part1( + wuffs_gif__decoder* self, + wuffs_base__io_buffer* a_src) { + wuffs_base__status status = wuffs_base__make_status(NULL); + + uint8_t v_block_type = 0; + + const uint8_t* iop_a_src = NULL; + const uint8_t* io0_a_src WUFFS_BASE__POTENTIALLY_UNUSED = NULL; + const uint8_t* io1_a_src WUFFS_BASE__POTENTIALLY_UNUSED = NULL; + const uint8_t* io2_a_src WUFFS_BASE__POTENTIALLY_UNUSED = NULL; + if (a_src && a_src->data.ptr) { + io0_a_src = a_src->data.ptr; + io1_a_src = io0_a_src + a_src->meta.ri; + iop_a_src = io1_a_src; + io2_a_src = io0_a_src + a_src->meta.wi; + } + + uint32_t coro_susp_point = self->private_impl.p_decode_up_to_id_part1[0]; + switch (coro_susp_point) { + WUFFS_BASE__COROUTINE_SUSPENSION_POINT_0; + + if ((self->private_impl.f_frame_config_io_position == 0) || (self->private_impl.f_num_decoded_frame_configs_value > 0)) { + self->private_impl.f_frame_config_io_position = wuffs_base__u64__sat_add((a_src ? a_src->meta.pos : 0), ((uint64_t)(iop_a_src - io0_a_src))); + } + while (true) { + { + WUFFS_BASE__COROUTINE_SUSPENSION_POINT(1); + if (WUFFS_BASE__UNLIKELY(iop_a_src == io2_a_src)) { + status = wuffs_base__make_status(wuffs_base__suspension__short_read); + goto suspend; + } + uint8_t t_0 = *iop_a_src++; + v_block_type = t_0; + } + if (v_block_type == 33) { + if (a_src) { + a_src->meta.ri = ((size_t)(iop_a_src - a_src->data.ptr)); + } + WUFFS_BASE__COROUTINE_SUSPENSION_POINT(2); + status = wuffs_gif__decoder__decode_extension(self, a_src); + if (a_src) { + iop_a_src = a_src->data.ptr + a_src->meta.ri; + } + if (status.repr) { + goto suspend; + } + } else if (v_block_type == 44) { + if (self->private_impl.f_delayed_num_decoded_frames) { + self->private_impl.f_delayed_num_decoded_frames = false; + wuffs_base__u64__sat_add_indirect(&self->private_impl.f_num_decoded_frames_value, 1); + } + if (a_src) { + a_src->meta.ri = ((size_t)(iop_a_src - a_src->data.ptr)); + } + WUFFS_BASE__COROUTINE_SUSPENSION_POINT(3); + status = wuffs_gif__decoder__decode_id_part0(self, a_src); + if (a_src) { + iop_a_src = a_src->data.ptr + a_src->meta.ri; + } + if (status.repr) { + goto suspend; + } + goto label__0__break; + } else { + if (self->private_impl.f_delayed_num_decoded_frames) { + self->private_impl.f_delayed_num_decoded_frames = false; + wuffs_base__u64__sat_add_indirect(&self->private_impl.f_num_decoded_frames_value, 1); + } + self->private_impl.f_call_sequence = 96; + goto label__0__break; + } + } + label__0__break:; + + goto ok; + ok: + self->private_impl.p_decode_up_to_id_part1[0] = 0; + goto exit; + } + + goto suspend; + suspend: + self->private_impl.p_decode_up_to_id_part1[0] = wuffs_base__status__is_suspension(&status) ? coro_susp_point : 0; + + goto exit; + exit: + if (a_src && a_src->data.ptr) { + a_src->meta.ri = ((size_t)(iop_a_src - a_src->data.ptr)); + } + + return status; +} + +// -------- func gif.decoder.decode_header + +static wuffs_base__status +wuffs_gif__decoder__decode_header( + wuffs_gif__decoder* self, + wuffs_base__io_buffer* a_src) { + wuffs_base__status status = wuffs_base__make_status(NULL); + + uint8_t v_c[6] = {0}; + uint32_t v_i = 0; + + const uint8_t* iop_a_src = NULL; + const uint8_t* io0_a_src WUFFS_BASE__POTENTIALLY_UNUSED = NULL; + const uint8_t* io1_a_src WUFFS_BASE__POTENTIALLY_UNUSED = NULL; + const uint8_t* io2_a_src WUFFS_BASE__POTENTIALLY_UNUSED = NULL; + if (a_src && a_src->data.ptr) { + io0_a_src = a_src->data.ptr; + io1_a_src = io0_a_src + a_src->meta.ri; + iop_a_src = io1_a_src; + io2_a_src = io0_a_src + a_src->meta.wi; + } + + uint32_t coro_susp_point = self->private_impl.p_decode_header[0]; + if (coro_susp_point) { + memcpy(v_c, self->private_data.s_decode_header[0].v_c, sizeof(v_c)); + v_i = self->private_data.s_decode_header[0].v_i; + } + switch (coro_susp_point) { + WUFFS_BASE__COROUTINE_SUSPENSION_POINT_0; + + while (v_i < 6) { + { + WUFFS_BASE__COROUTINE_SUSPENSION_POINT(1); + if (WUFFS_BASE__UNLIKELY(iop_a_src == io2_a_src)) { + status = wuffs_base__make_status(wuffs_base__suspension__short_read); + goto suspend; + } + uint8_t t_0 = *iop_a_src++; + v_c[v_i] = t_0; + } + v_i += 1; + } + if ((v_c[0] != 71) || + (v_c[1] != 73) || + (v_c[2] != 70) || + (v_c[3] != 56) || + ((v_c[4] != 55) && (v_c[4] != 57)) || + (v_c[5] != 97)) { + status = wuffs_base__make_status(wuffs_gif__error__bad_header); + goto exit; + } + + goto ok; + ok: + self->private_impl.p_decode_header[0] = 0; + goto exit; + } + + goto suspend; + suspend: + self->private_impl.p_decode_header[0] = wuffs_base__status__is_suspension(&status) ? coro_susp_point : 0; + memcpy(self->private_data.s_decode_header[0].v_c, v_c, sizeof(v_c)); + self->private_data.s_decode_header[0].v_i = v_i; + + goto exit; + exit: + if (a_src && a_src->data.ptr) { + a_src->meta.ri = ((size_t)(iop_a_src - a_src->data.ptr)); + } + + return status; +} + +// -------- func gif.decoder.decode_lsd + +static wuffs_base__status +wuffs_gif__decoder__decode_lsd( + wuffs_gif__decoder* self, + wuffs_base__io_buffer* a_src) { + wuffs_base__status status = wuffs_base__make_status(NULL); + + uint8_t v_flags = 0; + uint8_t v_background_color_index = 0; + uint32_t v_num_palette_entries = 0; + uint32_t v_i = 0; + uint32_t v_j = 0; + uint32_t v_argb = 0; + + const uint8_t* iop_a_src = NULL; + const uint8_t* io0_a_src WUFFS_BASE__POTENTIALLY_UNUSED = NULL; + const uint8_t* io1_a_src WUFFS_BASE__POTENTIALLY_UNUSED = NULL; + const uint8_t* io2_a_src WUFFS_BASE__POTENTIALLY_UNUSED = NULL; + if (a_src && a_src->data.ptr) { + io0_a_src = a_src->data.ptr; + io1_a_src = io0_a_src + a_src->meta.ri; + iop_a_src = io1_a_src; + io2_a_src = io0_a_src + a_src->meta.wi; + } + + uint32_t coro_susp_point = self->private_impl.p_decode_lsd[0]; + if (coro_susp_point) { + v_flags = self->private_data.s_decode_lsd[0].v_flags; + v_background_color_index = self->private_data.s_decode_lsd[0].v_background_color_index; + v_num_palette_entries = self->private_data.s_decode_lsd[0].v_num_palette_entries; + v_i = self->private_data.s_decode_lsd[0].v_i; + } + switch (coro_susp_point) { + WUFFS_BASE__COROUTINE_SUSPENSION_POINT_0; + + { + WUFFS_BASE__COROUTINE_SUSPENSION_POINT(1); + uint32_t t_0; + if (WUFFS_BASE__LIKELY(io2_a_src - iop_a_src >= 2)) { + t_0 = ((uint32_t)(wuffs_base__peek_u16le__no_bounds_check(iop_a_src))); + iop_a_src += 2; + } else { + self->private_data.s_decode_lsd[0].scratch = 0; + WUFFS_BASE__COROUTINE_SUSPENSION_POINT(2); + while (true) { + if (WUFFS_BASE__UNLIKELY(iop_a_src == io2_a_src)) { + status = wuffs_base__make_status(wuffs_base__suspension__short_read); + goto suspend; + } + uint64_t* scratch = &self->private_data.s_decode_lsd[0].scratch; + uint32_t num_bits_0 = ((uint32_t)(*scratch >> 56)); + *scratch <<= 8; + *scratch >>= 8; + *scratch |= ((uint64_t)(*iop_a_src++)) << num_bits_0; + if (num_bits_0 == 8) { + t_0 = ((uint32_t)(*scratch)); + break; + } + num_bits_0 += 8; + *scratch |= ((uint64_t)(num_bits_0)) << 56; + } + } + self->private_impl.f_width = t_0; + } + { + WUFFS_BASE__COROUTINE_SUSPENSION_POINT(3); + uint32_t t_1; + if (WUFFS_BASE__LIKELY(io2_a_src - iop_a_src >= 2)) { + t_1 = ((uint32_t)(wuffs_base__peek_u16le__no_bounds_check(iop_a_src))); + iop_a_src += 2; + } else { + self->private_data.s_decode_lsd[0].scratch = 0; + WUFFS_BASE__COROUTINE_SUSPENSION_POINT(4); + while (true) { + if (WUFFS_BASE__UNLIKELY(iop_a_src == io2_a_src)) { + status = wuffs_base__make_status(wuffs_base__suspension__short_read); + goto suspend; + } + uint64_t* scratch = &self->private_data.s_decode_lsd[0].scratch; + uint32_t num_bits_1 = ((uint32_t)(*scratch >> 56)); + *scratch <<= 8; + *scratch >>= 8; + *scratch |= ((uint64_t)(*iop_a_src++)) << num_bits_1; + if (num_bits_1 == 8) { + t_1 = ((uint32_t)(*scratch)); + break; + } + num_bits_1 += 8; + *scratch |= ((uint64_t)(num_bits_1)) << 56; + } + } + self->private_impl.f_height = t_1; + } + { + WUFFS_BASE__COROUTINE_SUSPENSION_POINT(5); + if (WUFFS_BASE__UNLIKELY(iop_a_src == io2_a_src)) { + status = wuffs_base__make_status(wuffs_base__suspension__short_read); + goto suspend; + } + uint8_t t_2 = *iop_a_src++; + v_flags = t_2; + } + { + WUFFS_BASE__COROUTINE_SUSPENSION_POINT(6); + if (WUFFS_BASE__UNLIKELY(iop_a_src == io2_a_src)) { + status = wuffs_base__make_status(wuffs_base__suspension__short_read); + goto suspend; + } + uint8_t t_3 = *iop_a_src++; + v_background_color_index = t_3; + } + WUFFS_BASE__COROUTINE_SUSPENSION_POINT(7); + if (WUFFS_BASE__UNLIKELY(iop_a_src == io2_a_src)) { + status = wuffs_base__make_status(wuffs_base__suspension__short_read); + goto suspend; + } + iop_a_src++; + v_i = 0; + self->private_impl.f_has_global_palette = ((v_flags & 128) != 0); + if (self->private_impl.f_has_global_palette) { + v_num_palette_entries = (((uint32_t)(1)) << (1 + (v_flags & 7))); + while (v_i < v_num_palette_entries) { + { + WUFFS_BASE__COROUTINE_SUSPENSION_POINT(8); + uint32_t t_4; + if (WUFFS_BASE__LIKELY(io2_a_src - iop_a_src >= 3)) { + t_4 = ((uint32_t)(wuffs_base__peek_u24be__no_bounds_check(iop_a_src))); + iop_a_src += 3; + } else { + self->private_data.s_decode_lsd[0].scratch = 0; + WUFFS_BASE__COROUTINE_SUSPENSION_POINT(9); + while (true) { + if (WUFFS_BASE__UNLIKELY(iop_a_src == io2_a_src)) { + status = wuffs_base__make_status(wuffs_base__suspension__short_read); + goto suspend; + } + uint64_t* scratch = &self->private_data.s_decode_lsd[0].scratch; + uint32_t num_bits_4 = ((uint32_t)(*scratch & 0xFF)); + *scratch >>= 8; + *scratch <<= 8; + *scratch |= ((uint64_t)(*iop_a_src++)) << (56 - num_bits_4); + if (num_bits_4 == 16) { + t_4 = ((uint32_t)(*scratch >> 40)); + break; + } + num_bits_4 += 8; + *scratch |= ((uint64_t)(num_bits_4)); + } + } + v_argb = t_4; + } + v_argb |= 4278190080; + self->private_data.f_palettes[0][((4 * v_i) + 0)] = ((uint8_t)(((v_argb >> 0) & 255))); + self->private_data.f_palettes[0][((4 * v_i) + 1)] = ((uint8_t)(((v_argb >> 8) & 255))); + self->private_data.f_palettes[0][((4 * v_i) + 2)] = ((uint8_t)(((v_argb >> 16) & 255))); + self->private_data.f_palettes[0][((4 * v_i) + 3)] = ((uint8_t)(((v_argb >> 24) & 255))); + v_i += 1; + } + if (self->private_impl.f_quirks[2]) { + if ((v_background_color_index != 0) && (((uint32_t)(v_background_color_index)) < v_num_palette_entries)) { + v_j = (4 * ((uint32_t)(v_background_color_index))); + self->private_impl.f_background_color_u32_argb_premul = ((((uint32_t)(self->private_data.f_palettes[0][(v_j + 0)])) << 0) | + (((uint32_t)(self->private_data.f_palettes[0][(v_j + 1)])) << 8) | + (((uint32_t)(self->private_data.f_palettes[0][(v_j + 2)])) << 16) | + (((uint32_t)(self->private_data.f_palettes[0][(v_j + 3)])) << 24)); + } else { + self->private_impl.f_background_color_u32_argb_premul = 77; + } + } + } + while (v_i < 256) { + self->private_data.f_palettes[0][((4 * v_i) + 0)] = 0; + self->private_data.f_palettes[0][((4 * v_i) + 1)] = 0; + self->private_data.f_palettes[0][((4 * v_i) + 2)] = 0; + self->private_data.f_palettes[0][((4 * v_i) + 3)] = 255; + v_i += 1; + } + + goto ok; + ok: + self->private_impl.p_decode_lsd[0] = 0; + goto exit; + } + + goto suspend; + suspend: + self->private_impl.p_decode_lsd[0] = wuffs_base__status__is_suspension(&status) ? coro_susp_point : 0; + self->private_data.s_decode_lsd[0].v_flags = v_flags; + self->private_data.s_decode_lsd[0].v_background_color_index = v_background_color_index; + self->private_data.s_decode_lsd[0].v_num_palette_entries = v_num_palette_entries; + self->private_data.s_decode_lsd[0].v_i = v_i; + + goto exit; + exit: + if (a_src && a_src->data.ptr) { + a_src->meta.ri = ((size_t)(iop_a_src - a_src->data.ptr)); + } + + return status; +} + +// -------- func gif.decoder.decode_extension + +static wuffs_base__status +wuffs_gif__decoder__decode_extension( + wuffs_gif__decoder* self, + wuffs_base__io_buffer* a_src) { + wuffs_base__status status = wuffs_base__make_status(NULL); + + uint8_t v_label = 0; + + const uint8_t* iop_a_src = NULL; + const uint8_t* io0_a_src WUFFS_BASE__POTENTIALLY_UNUSED = NULL; + const uint8_t* io1_a_src WUFFS_BASE__POTENTIALLY_UNUSED = NULL; + const uint8_t* io2_a_src WUFFS_BASE__POTENTIALLY_UNUSED = NULL; + if (a_src && a_src->data.ptr) { + io0_a_src = a_src->data.ptr; + io1_a_src = io0_a_src + a_src->meta.ri; + iop_a_src = io1_a_src; + io2_a_src = io0_a_src + a_src->meta.wi; + } + + uint32_t coro_susp_point = self->private_impl.p_decode_extension[0]; + switch (coro_susp_point) { + WUFFS_BASE__COROUTINE_SUSPENSION_POINT_0; + + { + WUFFS_BASE__COROUTINE_SUSPENSION_POINT(1); + if (WUFFS_BASE__UNLIKELY(iop_a_src == io2_a_src)) { + status = wuffs_base__make_status(wuffs_base__suspension__short_read); + goto suspend; + } + uint8_t t_0 = *iop_a_src++; + v_label = t_0; + } + if (v_label == 249) { + if (a_src) { + a_src->meta.ri = ((size_t)(iop_a_src - a_src->data.ptr)); + } + WUFFS_BASE__COROUTINE_SUSPENSION_POINT(2); + status = wuffs_gif__decoder__decode_gc(self, a_src); + if (a_src) { + iop_a_src = a_src->data.ptr + a_src->meta.ri; + } + if (status.repr) { + goto suspend; + } + status = wuffs_base__make_status(NULL); + goto ok; + } else if (v_label == 255) { + if (a_src) { + a_src->meta.ri = ((size_t)(iop_a_src - a_src->data.ptr)); + } + WUFFS_BASE__COROUTINE_SUSPENSION_POINT(3); + status = wuffs_gif__decoder__decode_ae(self, a_src); + if (a_src) { + iop_a_src = a_src->data.ptr + a_src->meta.ri; + } + if (status.repr) { + goto suspend; + } + status = wuffs_base__make_status(NULL); + goto ok; + } + if (a_src) { + a_src->meta.ri = ((size_t)(iop_a_src - a_src->data.ptr)); + } + WUFFS_BASE__COROUTINE_SUSPENSION_POINT(4); + status = wuffs_gif__decoder__skip_blocks(self, a_src); + if (a_src) { + iop_a_src = a_src->data.ptr + a_src->meta.ri; + } + if (status.repr) { + goto suspend; + } + + ok: + self->private_impl.p_decode_extension[0] = 0; + goto exit; + } + + goto suspend; + suspend: + self->private_impl.p_decode_extension[0] = wuffs_base__status__is_suspension(&status) ? coro_susp_point : 0; + + goto exit; + exit: + if (a_src && a_src->data.ptr) { + a_src->meta.ri = ((size_t)(iop_a_src - a_src->data.ptr)); + } + + return status; +} + +// -------- func gif.decoder.skip_blocks + +static wuffs_base__status +wuffs_gif__decoder__skip_blocks( + wuffs_gif__decoder* self, + wuffs_base__io_buffer* a_src) { + wuffs_base__status status = wuffs_base__make_status(NULL); + + uint8_t v_block_size = 0; + + const uint8_t* iop_a_src = NULL; + const uint8_t* io0_a_src WUFFS_BASE__POTENTIALLY_UNUSED = NULL; + const uint8_t* io1_a_src WUFFS_BASE__POTENTIALLY_UNUSED = NULL; + const uint8_t* io2_a_src WUFFS_BASE__POTENTIALLY_UNUSED = NULL; + if (a_src && a_src->data.ptr) { + io0_a_src = a_src->data.ptr; + io1_a_src = io0_a_src + a_src->meta.ri; + iop_a_src = io1_a_src; + io2_a_src = io0_a_src + a_src->meta.wi; + } + + uint32_t coro_susp_point = self->private_impl.p_skip_blocks[0]; + switch (coro_susp_point) { + WUFFS_BASE__COROUTINE_SUSPENSION_POINT_0; + + while (true) { + { + WUFFS_BASE__COROUTINE_SUSPENSION_POINT(1); + if (WUFFS_BASE__UNLIKELY(iop_a_src == io2_a_src)) { + status = wuffs_base__make_status(wuffs_base__suspension__short_read); + goto suspend; + } + uint8_t t_0 = *iop_a_src++; + v_block_size = t_0; + } + if (v_block_size == 0) { + status = wuffs_base__make_status(NULL); + goto ok; + } + self->private_data.s_skip_blocks[0].scratch = ((uint32_t)(v_block_size)); + WUFFS_BASE__COROUTINE_SUSPENSION_POINT(2); + if (self->private_data.s_skip_blocks[0].scratch > ((uint64_t)(io2_a_src - iop_a_src))) { + self->private_data.s_skip_blocks[0].scratch -= ((uint64_t)(io2_a_src - iop_a_src)); + iop_a_src = io2_a_src; + status = wuffs_base__make_status(wuffs_base__suspension__short_read); + goto suspend; + } + iop_a_src += self->private_data.s_skip_blocks[0].scratch; + } + + ok: + self->private_impl.p_skip_blocks[0] = 0; + goto exit; + } + + goto suspend; + suspend: + self->private_impl.p_skip_blocks[0] = wuffs_base__status__is_suspension(&status) ? coro_susp_point : 0; + + goto exit; + exit: + if (a_src && a_src->data.ptr) { + a_src->meta.ri = ((size_t)(iop_a_src - a_src->data.ptr)); + } + + return status; +} + +// -------- func gif.decoder.decode_ae + +static wuffs_base__status +wuffs_gif__decoder__decode_ae( + wuffs_gif__decoder* self, + wuffs_base__io_buffer* a_src) { + wuffs_base__status status = wuffs_base__make_status(NULL); + + uint8_t v_c = 0; + uint8_t v_block_size = 0; + bool v_is_animexts = false; + bool v_is_netscape = false; + bool v_is_iccp = false; + bool v_is_xmp = false; + + const uint8_t* iop_a_src = NULL; + const uint8_t* io0_a_src WUFFS_BASE__POTENTIALLY_UNUSED = NULL; + const uint8_t* io1_a_src WUFFS_BASE__POTENTIALLY_UNUSED = NULL; + const uint8_t* io2_a_src WUFFS_BASE__POTENTIALLY_UNUSED = NULL; + if (a_src && a_src->data.ptr) { + io0_a_src = a_src->data.ptr; + io1_a_src = io0_a_src + a_src->meta.ri; + iop_a_src = io1_a_src; + io2_a_src = io0_a_src + a_src->meta.wi; + } + + uint32_t coro_susp_point = self->private_impl.p_decode_ae[0]; + if (coro_susp_point) { + v_block_size = self->private_data.s_decode_ae[0].v_block_size; + v_is_animexts = self->private_data.s_decode_ae[0].v_is_animexts; + v_is_netscape = self->private_data.s_decode_ae[0].v_is_netscape; + v_is_iccp = self->private_data.s_decode_ae[0].v_is_iccp; + v_is_xmp = self->private_data.s_decode_ae[0].v_is_xmp; + } + switch (coro_susp_point) { + WUFFS_BASE__COROUTINE_SUSPENSION_POINT_0; + + while (true) { + if (self->private_impl.f_metadata_fourcc != 0) { + status = wuffs_base__make_status(wuffs_base__note__metadata_reported); + goto ok; + } + { + WUFFS_BASE__COROUTINE_SUSPENSION_POINT(1); + if (WUFFS_BASE__UNLIKELY(iop_a_src == io2_a_src)) { + status = wuffs_base__make_status(wuffs_base__suspension__short_read); + goto suspend; + } + uint8_t t_0 = *iop_a_src++; + v_block_size = t_0; + } + if (v_block_size == 0) { + status = wuffs_base__make_status(NULL); + goto ok; + } + if (v_block_size != 11) { + self->private_data.s_decode_ae[0].scratch = ((uint32_t)(v_block_size)); + WUFFS_BASE__COROUTINE_SUSPENSION_POINT(2); + if (self->private_data.s_decode_ae[0].scratch > ((uint64_t)(io2_a_src - iop_a_src))) { + self->private_data.s_decode_ae[0].scratch -= ((uint64_t)(io2_a_src - iop_a_src)); + iop_a_src = io2_a_src; + status = wuffs_base__make_status(wuffs_base__suspension__short_read); + goto suspend; + } + iop_a_src += self->private_data.s_decode_ae[0].scratch; + goto label__goto_done__break; + } + v_is_animexts = true; + v_is_netscape = true; + v_is_iccp = true; + v_is_xmp = true; + v_block_size = 0; + while (v_block_size < 11) { + { + WUFFS_BASE__COROUTINE_SUSPENSION_POINT(3); + if (WUFFS_BASE__UNLIKELY(iop_a_src == io2_a_src)) { + status = wuffs_base__make_status(wuffs_base__suspension__short_read); + goto suspend; + } + uint8_t t_1 = *iop_a_src++; + v_c = t_1; + } + v_is_animexts = (v_is_animexts && (v_c == WUFFS_GIF__ANIMEXTS1DOT0[v_block_size])); + v_is_netscape = (v_is_netscape && (v_c == WUFFS_GIF__NETSCAPE2DOT0[v_block_size])); + v_is_iccp = (v_is_iccp && (v_c == WUFFS_GIF__ICCRGBG1012[v_block_size])); + v_is_xmp = (v_is_xmp && (v_c == WUFFS_GIF__XMPDATAXMP[v_block_size])); +#if defined(__GNUC__) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wconversion" +#endif + v_block_size += 1; +#if defined(__GNUC__) +#pragma GCC diagnostic pop +#endif + } + if (v_is_animexts || v_is_netscape) { + { + WUFFS_BASE__COROUTINE_SUSPENSION_POINT(4); + if (WUFFS_BASE__UNLIKELY(iop_a_src == io2_a_src)) { + status = wuffs_base__make_status(wuffs_base__suspension__short_read); + goto suspend; + } + uint8_t t_2 = *iop_a_src++; + v_block_size = t_2; + } + if (v_block_size != 3) { + self->private_data.s_decode_ae[0].scratch = ((uint32_t)(v_block_size)); + WUFFS_BASE__COROUTINE_SUSPENSION_POINT(5); + if (self->private_data.s_decode_ae[0].scratch > ((uint64_t)(io2_a_src - iop_a_src))) { + self->private_data.s_decode_ae[0].scratch -= ((uint64_t)(io2_a_src - iop_a_src)); + iop_a_src = io2_a_src; + status = wuffs_base__make_status(wuffs_base__suspension__short_read); + goto suspend; + } + iop_a_src += self->private_data.s_decode_ae[0].scratch; + goto label__goto_done__break; + } + { + WUFFS_BASE__COROUTINE_SUSPENSION_POINT(6); + if (WUFFS_BASE__UNLIKELY(iop_a_src == io2_a_src)) { + status = wuffs_base__make_status(wuffs_base__suspension__short_read); + goto suspend; + } + uint8_t t_3 = *iop_a_src++; + v_c = t_3; + } + if (v_c != 1) { + self->private_data.s_decode_ae[0].scratch = 2; + WUFFS_BASE__COROUTINE_SUSPENSION_POINT(7); + if (self->private_data.s_decode_ae[0].scratch > ((uint64_t)(io2_a_src - iop_a_src))) { + self->private_data.s_decode_ae[0].scratch -= ((uint64_t)(io2_a_src - iop_a_src)); + iop_a_src = io2_a_src; + status = wuffs_base__make_status(wuffs_base__suspension__short_read); + goto suspend; + } + iop_a_src += self->private_data.s_decode_ae[0].scratch; + goto label__goto_done__break; + } + { + WUFFS_BASE__COROUTINE_SUSPENSION_POINT(8); + uint32_t t_4; + if (WUFFS_BASE__LIKELY(io2_a_src - iop_a_src >= 2)) { + t_4 = ((uint32_t)(wuffs_base__peek_u16le__no_bounds_check(iop_a_src))); + iop_a_src += 2; + } else { + self->private_data.s_decode_ae[0].scratch = 0; + WUFFS_BASE__COROUTINE_SUSPENSION_POINT(9); + while (true) { + if (WUFFS_BASE__UNLIKELY(iop_a_src == io2_a_src)) { + status = wuffs_base__make_status(wuffs_base__suspension__short_read); + goto suspend; + } + uint64_t* scratch = &self->private_data.s_decode_ae[0].scratch; + uint32_t num_bits_4 = ((uint32_t)(*scratch >> 56)); + *scratch <<= 8; + *scratch >>= 8; + *scratch |= ((uint64_t)(*iop_a_src++)) << num_bits_4; + if (num_bits_4 == 8) { + t_4 = ((uint32_t)(*scratch)); + break; + } + num_bits_4 += 8; + *scratch |= ((uint64_t)(num_bits_4)) << 56; + } + } + self->private_impl.f_num_animation_loops_value = t_4; + } + self->private_impl.f_seen_num_animation_loops_value = true; + if ((0 < self->private_impl.f_num_animation_loops_value) && (self->private_impl.f_num_animation_loops_value <= 65535)) { + self->private_impl.f_num_animation_loops_value += 1; + } + } else if (self->private_impl.f_call_sequence >= 32) { + } else if (v_is_iccp && self->private_impl.f_report_metadata_iccp) { + self->private_impl.f_metadata_fourcc = 1229144912; + self->private_impl.f_metadata_io_position = wuffs_base__u64__sat_add((a_src ? a_src->meta.pos : 0), ((uint64_t)(iop_a_src - io0_a_src))); + self->private_impl.f_call_sequence = 16; + status = wuffs_base__make_status(wuffs_base__note__metadata_reported); + goto ok; + } else if (v_is_xmp && self->private_impl.f_report_metadata_xmp) { + self->private_impl.f_metadata_fourcc = 1481461792; + self->private_impl.f_metadata_io_position = wuffs_base__u64__sat_add((a_src ? a_src->meta.pos : 0), ((uint64_t)(iop_a_src - io0_a_src))); + self->private_impl.f_call_sequence = 16; + status = wuffs_base__make_status(wuffs_base__note__metadata_reported); + goto ok; + } + goto label__goto_done__break; + } + label__goto_done__break:; + if (a_src) { + a_src->meta.ri = ((size_t)(iop_a_src - a_src->data.ptr)); + } + WUFFS_BASE__COROUTINE_SUSPENSION_POINT(10); + status = wuffs_gif__decoder__skip_blocks(self, a_src); + if (a_src) { + iop_a_src = a_src->data.ptr + a_src->meta.ri; + } + if (status.repr) { + goto suspend; + } + + ok: + self->private_impl.p_decode_ae[0] = 0; + goto exit; + } + + goto suspend; + suspend: + self->private_impl.p_decode_ae[0] = wuffs_base__status__is_suspension(&status) ? coro_susp_point : 0; + self->private_data.s_decode_ae[0].v_block_size = v_block_size; + self->private_data.s_decode_ae[0].v_is_animexts = v_is_animexts; + self->private_data.s_decode_ae[0].v_is_netscape = v_is_netscape; + self->private_data.s_decode_ae[0].v_is_iccp = v_is_iccp; + self->private_data.s_decode_ae[0].v_is_xmp = v_is_xmp; + + goto exit; + exit: + if (a_src && a_src->data.ptr) { + a_src->meta.ri = ((size_t)(iop_a_src - a_src->data.ptr)); + } + + return status; +} + +// -------- func gif.decoder.decode_gc + +static wuffs_base__status +wuffs_gif__decoder__decode_gc( + wuffs_gif__decoder* self, + wuffs_base__io_buffer* a_src) { + wuffs_base__status status = wuffs_base__make_status(NULL); + + uint8_t v_c = 0; + uint8_t v_flags = 0; + uint16_t v_gc_duration_centiseconds = 0; + + const uint8_t* iop_a_src = NULL; + const uint8_t* io0_a_src WUFFS_BASE__POTENTIALLY_UNUSED = NULL; + const uint8_t* io1_a_src WUFFS_BASE__POTENTIALLY_UNUSED = NULL; + const uint8_t* io2_a_src WUFFS_BASE__POTENTIALLY_UNUSED = NULL; + if (a_src && a_src->data.ptr) { + io0_a_src = a_src->data.ptr; + io1_a_src = io0_a_src + a_src->meta.ri; + iop_a_src = io1_a_src; + io2_a_src = io0_a_src + a_src->meta.wi; + } + + uint32_t coro_susp_point = self->private_impl.p_decode_gc[0]; + switch (coro_susp_point) { + WUFFS_BASE__COROUTINE_SUSPENSION_POINT_0; + + { + WUFFS_BASE__COROUTINE_SUSPENSION_POINT(1); + if (WUFFS_BASE__UNLIKELY(iop_a_src == io2_a_src)) { + status = wuffs_base__make_status(wuffs_base__suspension__short_read); + goto suspend; + } + uint8_t t_0 = *iop_a_src++; + v_c = t_0; + } + if (v_c != 4) { + status = wuffs_base__make_status(wuffs_gif__error__bad_graphic_control); + goto exit; + } + { + WUFFS_BASE__COROUTINE_SUSPENSION_POINT(2); + if (WUFFS_BASE__UNLIKELY(iop_a_src == io2_a_src)) { + status = wuffs_base__make_status(wuffs_base__suspension__short_read); + goto suspend; + } + uint8_t t_1 = *iop_a_src++; + v_flags = t_1; + } + self->private_impl.f_gc_has_transparent_index = ((v_flags & 1) != 0); + v_flags = ((v_flags >> 2) & 7); + if (v_flags == 2) { + self->private_impl.f_gc_disposal = 1; + } else if ((v_flags == 3) || (v_flags == 4)) { + self->private_impl.f_gc_disposal = 2; + } else { + self->private_impl.f_gc_disposal = 0; + } + { + WUFFS_BASE__COROUTINE_SUSPENSION_POINT(3); + uint16_t t_2; + if (WUFFS_BASE__LIKELY(io2_a_src - iop_a_src >= 2)) { + t_2 = wuffs_base__peek_u16le__no_bounds_check(iop_a_src); + iop_a_src += 2; + } else { + self->private_data.s_decode_gc[0].scratch = 0; + WUFFS_BASE__COROUTINE_SUSPENSION_POINT(4); + while (true) { + if (WUFFS_BASE__UNLIKELY(iop_a_src == io2_a_src)) { + status = wuffs_base__make_status(wuffs_base__suspension__short_read); + goto suspend; + } + uint64_t* scratch = &self->private_data.s_decode_gc[0].scratch; + uint32_t num_bits_2 = ((uint32_t)(*scratch >> 56)); + *scratch <<= 8; + *scratch >>= 8; + *scratch |= ((uint64_t)(*iop_a_src++)) << num_bits_2; + if (num_bits_2 == 8) { + t_2 = ((uint16_t)(*scratch)); + break; + } + num_bits_2 += 8; + *scratch |= ((uint64_t)(num_bits_2)) << 56; + } + } + v_gc_duration_centiseconds = t_2; + } + self->private_impl.f_gc_duration = (((uint64_t)(v_gc_duration_centiseconds)) * 7056000); + { + WUFFS_BASE__COROUTINE_SUSPENSION_POINT(5); + if (WUFFS_BASE__UNLIKELY(iop_a_src == io2_a_src)) { + status = wuffs_base__make_status(wuffs_base__suspension__short_read); + goto suspend; + } + uint8_t t_3 = *iop_a_src++; + self->private_impl.f_gc_transparent_index = t_3; + } + { + WUFFS_BASE__COROUTINE_SUSPENSION_POINT(6); + if (WUFFS_BASE__UNLIKELY(iop_a_src == io2_a_src)) { + status = wuffs_base__make_status(wuffs_base__suspension__short_read); + goto suspend; + } + uint8_t t_4 = *iop_a_src++; + v_c = t_4; + } + if (v_c != 0) { + status = wuffs_base__make_status(wuffs_gif__error__bad_graphic_control); + goto exit; + } + + goto ok; + ok: + self->private_impl.p_decode_gc[0] = 0; + goto exit; + } + + goto suspend; + suspend: + self->private_impl.p_decode_gc[0] = wuffs_base__status__is_suspension(&status) ? coro_susp_point : 0; + + goto exit; + exit: + if (a_src && a_src->data.ptr) { + a_src->meta.ri = ((size_t)(iop_a_src - a_src->data.ptr)); + } + + return status; +} + +// -------- func gif.decoder.decode_id_part0 + +static wuffs_base__status +wuffs_gif__decoder__decode_id_part0( + wuffs_gif__decoder* self, + wuffs_base__io_buffer* a_src) { + wuffs_base__status status = wuffs_base__make_status(NULL); + + const uint8_t* iop_a_src = NULL; + const uint8_t* io0_a_src WUFFS_BASE__POTENTIALLY_UNUSED = NULL; + const uint8_t* io1_a_src WUFFS_BASE__POTENTIALLY_UNUSED = NULL; + const uint8_t* io2_a_src WUFFS_BASE__POTENTIALLY_UNUSED = NULL; + if (a_src && a_src->data.ptr) { + io0_a_src = a_src->data.ptr; + io1_a_src = io0_a_src + a_src->meta.ri; + iop_a_src = io1_a_src; + io2_a_src = io0_a_src + a_src->meta.wi; + } + + uint32_t coro_susp_point = self->private_impl.p_decode_id_part0[0]; + switch (coro_susp_point) { + WUFFS_BASE__COROUTINE_SUSPENSION_POINT_0; + + { + WUFFS_BASE__COROUTINE_SUSPENSION_POINT(1); + uint32_t t_0; + if (WUFFS_BASE__LIKELY(io2_a_src - iop_a_src >= 2)) { + t_0 = ((uint32_t)(wuffs_base__peek_u16le__no_bounds_check(iop_a_src))); + iop_a_src += 2; + } else { + self->private_data.s_decode_id_part0[0].scratch = 0; + WUFFS_BASE__COROUTINE_SUSPENSION_POINT(2); + while (true) { + if (WUFFS_BASE__UNLIKELY(iop_a_src == io2_a_src)) { + status = wuffs_base__make_status(wuffs_base__suspension__short_read); + goto suspend; + } + uint64_t* scratch = &self->private_data.s_decode_id_part0[0].scratch; + uint32_t num_bits_0 = ((uint32_t)(*scratch >> 56)); + *scratch <<= 8; + *scratch >>= 8; + *scratch |= ((uint64_t)(*iop_a_src++)) << num_bits_0; + if (num_bits_0 == 8) { + t_0 = ((uint32_t)(*scratch)); + break; + } + num_bits_0 += 8; + *scratch |= ((uint64_t)(num_bits_0)) << 56; + } + } + self->private_impl.f_frame_rect_x0 = t_0; + } + { + WUFFS_BASE__COROUTINE_SUSPENSION_POINT(3); + uint32_t t_1; + if (WUFFS_BASE__LIKELY(io2_a_src - iop_a_src >= 2)) { + t_1 = ((uint32_t)(wuffs_base__peek_u16le__no_bounds_check(iop_a_src))); + iop_a_src += 2; + } else { + self->private_data.s_decode_id_part0[0].scratch = 0; + WUFFS_BASE__COROUTINE_SUSPENSION_POINT(4); + while (true) { + if (WUFFS_BASE__UNLIKELY(iop_a_src == io2_a_src)) { + status = wuffs_base__make_status(wuffs_base__suspension__short_read); + goto suspend; + } + uint64_t* scratch = &self->private_data.s_decode_id_part0[0].scratch; + uint32_t num_bits_1 = ((uint32_t)(*scratch >> 56)); + *scratch <<= 8; + *scratch >>= 8; + *scratch |= ((uint64_t)(*iop_a_src++)) << num_bits_1; + if (num_bits_1 == 8) { + t_1 = ((uint32_t)(*scratch)); + break; + } + num_bits_1 += 8; + *scratch |= ((uint64_t)(num_bits_1)) << 56; + } + } + self->private_impl.f_frame_rect_y0 = t_1; + } + { + WUFFS_BASE__COROUTINE_SUSPENSION_POINT(5); + uint32_t t_2; + if (WUFFS_BASE__LIKELY(io2_a_src - iop_a_src >= 2)) { + t_2 = ((uint32_t)(wuffs_base__peek_u16le__no_bounds_check(iop_a_src))); + iop_a_src += 2; + } else { + self->private_data.s_decode_id_part0[0].scratch = 0; + WUFFS_BASE__COROUTINE_SUSPENSION_POINT(6); + while (true) { + if (WUFFS_BASE__UNLIKELY(iop_a_src == io2_a_src)) { + status = wuffs_base__make_status(wuffs_base__suspension__short_read); + goto suspend; + } + uint64_t* scratch = &self->private_data.s_decode_id_part0[0].scratch; + uint32_t num_bits_2 = ((uint32_t)(*scratch >> 56)); + *scratch <<= 8; + *scratch >>= 8; + *scratch |= ((uint64_t)(*iop_a_src++)) << num_bits_2; + if (num_bits_2 == 8) { + t_2 = ((uint32_t)(*scratch)); + break; + } + num_bits_2 += 8; + *scratch |= ((uint64_t)(num_bits_2)) << 56; + } + } + self->private_impl.f_frame_rect_x1 = t_2; + } + self->private_impl.f_frame_rect_x1 += self->private_impl.f_frame_rect_x0; + { + WUFFS_BASE__COROUTINE_SUSPENSION_POINT(7); + uint32_t t_3; + if (WUFFS_BASE__LIKELY(io2_a_src - iop_a_src >= 2)) { + t_3 = ((uint32_t)(wuffs_base__peek_u16le__no_bounds_check(iop_a_src))); + iop_a_src += 2; + } else { + self->private_data.s_decode_id_part0[0].scratch = 0; + WUFFS_BASE__COROUTINE_SUSPENSION_POINT(8); + while (true) { + if (WUFFS_BASE__UNLIKELY(iop_a_src == io2_a_src)) { + status = wuffs_base__make_status(wuffs_base__suspension__short_read); + goto suspend; + } + uint64_t* scratch = &self->private_data.s_decode_id_part0[0].scratch; + uint32_t num_bits_3 = ((uint32_t)(*scratch >> 56)); + *scratch <<= 8; + *scratch >>= 8; + *scratch |= ((uint64_t)(*iop_a_src++)) << num_bits_3; + if (num_bits_3 == 8) { + t_3 = ((uint32_t)(*scratch)); + break; + } + num_bits_3 += 8; + *scratch |= ((uint64_t)(num_bits_3)) << 56; + } + } + self->private_impl.f_frame_rect_y1 = t_3; + } + self->private_impl.f_frame_rect_y1 += self->private_impl.f_frame_rect_y0; + self->private_impl.f_dst_x = self->private_impl.f_frame_rect_x0; + self->private_impl.f_dst_y = self->private_impl.f_frame_rect_y0; + if ((self->private_impl.f_num_decoded_frame_configs_value == 0) && ! self->private_impl.f_quirks[4]) { + self->private_impl.f_width = wuffs_base__u32__max(self->private_impl.f_width, self->private_impl.f_frame_rect_x1); + self->private_impl.f_height = wuffs_base__u32__max(self->private_impl.f_height, self->private_impl.f_frame_rect_y1); + } + + goto ok; + ok: + self->private_impl.p_decode_id_part0[0] = 0; + goto exit; + } + + goto suspend; + suspend: + self->private_impl.p_decode_id_part0[0] = wuffs_base__status__is_suspension(&status) ? coro_susp_point : 0; + + goto exit; + exit: + if (a_src && a_src->data.ptr) { + a_src->meta.ri = ((size_t)(iop_a_src - a_src->data.ptr)); + } + + return status; +} + +// -------- func gif.decoder.decode_id_part1 + +static wuffs_base__status +wuffs_gif__decoder__decode_id_part1( + wuffs_gif__decoder* self, + wuffs_base__pixel_buffer* a_dst, + wuffs_base__io_buffer* a_src, + wuffs_base__pixel_blend a_blend) { + wuffs_base__status status = wuffs_base__make_status(NULL); + + uint8_t v_flags = 0; + uint8_t v_which_palette = 0; + uint32_t v_num_palette_entries = 0; + uint32_t v_i = 0; + uint32_t v_argb = 0; + wuffs_base__status v_status = wuffs_base__make_status(NULL); + uint8_t v_lw = 0; + + const uint8_t* iop_a_src = NULL; + const uint8_t* io0_a_src WUFFS_BASE__POTENTIALLY_UNUSED = NULL; + const uint8_t* io1_a_src WUFFS_BASE__POTENTIALLY_UNUSED = NULL; + const uint8_t* io2_a_src WUFFS_BASE__POTENTIALLY_UNUSED = NULL; + if (a_src && a_src->data.ptr) { + io0_a_src = a_src->data.ptr; + io1_a_src = io0_a_src + a_src->meta.ri; + iop_a_src = io1_a_src; + io2_a_src = io0_a_src + a_src->meta.wi; + } + + uint32_t coro_susp_point = self->private_impl.p_decode_id_part1[0]; + if (coro_susp_point) { + v_which_palette = self->private_data.s_decode_id_part1[0].v_which_palette; + v_num_palette_entries = self->private_data.s_decode_id_part1[0].v_num_palette_entries; + v_i = self->private_data.s_decode_id_part1[0].v_i; + } + switch (coro_susp_point) { + WUFFS_BASE__COROUTINE_SUSPENSION_POINT_0; + + { + WUFFS_BASE__COROUTINE_SUSPENSION_POINT(1); + if (WUFFS_BASE__UNLIKELY(iop_a_src == io2_a_src)) { + status = wuffs_base__make_status(wuffs_base__suspension__short_read); + goto suspend; + } + uint8_t t_0 = *iop_a_src++; + v_flags = t_0; + } + if ((v_flags & 64) != 0) { + self->private_impl.f_interlace = 4; + } else { + self->private_impl.f_interlace = 0; + } + v_which_palette = 1; + if ((v_flags & 128) != 0) { + v_num_palette_entries = (((uint32_t)(1)) << (1 + (v_flags & 7))); + v_i = 0; + while (v_i < v_num_palette_entries) { + { + WUFFS_BASE__COROUTINE_SUSPENSION_POINT(2); + uint32_t t_1; + if (WUFFS_BASE__LIKELY(io2_a_src - iop_a_src >= 3)) { + t_1 = ((uint32_t)(wuffs_base__peek_u24be__no_bounds_check(iop_a_src))); + iop_a_src += 3; + } else { + self->private_data.s_decode_id_part1[0].scratch = 0; + WUFFS_BASE__COROUTINE_SUSPENSION_POINT(3); + while (true) { + if (WUFFS_BASE__UNLIKELY(iop_a_src == io2_a_src)) { + status = wuffs_base__make_status(wuffs_base__suspension__short_read); + goto suspend; + } + uint64_t* scratch = &self->private_data.s_decode_id_part1[0].scratch; + uint32_t num_bits_1 = ((uint32_t)(*scratch & 0xFF)); + *scratch >>= 8; + *scratch <<= 8; + *scratch |= ((uint64_t)(*iop_a_src++)) << (56 - num_bits_1); + if (num_bits_1 == 16) { + t_1 = ((uint32_t)(*scratch >> 40)); + break; + } + num_bits_1 += 8; + *scratch |= ((uint64_t)(num_bits_1)); + } + } + v_argb = t_1; + } + v_argb |= 4278190080; + self->private_data.f_palettes[1][((4 * v_i) + 0)] = ((uint8_t)(((v_argb >> 0) & 255))); + self->private_data.f_palettes[1][((4 * v_i) + 1)] = ((uint8_t)(((v_argb >> 8) & 255))); + self->private_data.f_palettes[1][((4 * v_i) + 2)] = ((uint8_t)(((v_argb >> 16) & 255))); + self->private_data.f_palettes[1][((4 * v_i) + 3)] = ((uint8_t)(((v_argb >> 24) & 255))); + v_i += 1; + } + while (v_i < 256) { + self->private_data.f_palettes[1][((4 * v_i) + 0)] = 0; + self->private_data.f_palettes[1][((4 * v_i) + 1)] = 0; + self->private_data.f_palettes[1][((4 * v_i) + 2)] = 0; + self->private_data.f_palettes[1][((4 * v_i) + 3)] = 255; + v_i += 1; + } + } else if (self->private_impl.f_quirks[6] && ! self->private_impl.f_has_global_palette) { + status = wuffs_base__make_status(wuffs_gif__error__bad_palette); + goto exit; + } else if (self->private_impl.f_gc_has_transparent_index) { + wuffs_base__slice_u8__copy_from_slice(wuffs_base__make_slice_u8(self->private_data.f_palettes[1], 1024), wuffs_base__make_slice_u8(self->private_data.f_palettes[0], 1024)); + } else { + v_which_palette = 0; + } + if (self->private_impl.f_gc_has_transparent_index) { + self->private_data.f_palettes[1][((4 * ((uint32_t)(self->private_impl.f_gc_transparent_index))) + 0)] = 0; + self->private_data.f_palettes[1][((4 * ((uint32_t)(self->private_impl.f_gc_transparent_index))) + 1)] = 0; + self->private_data.f_palettes[1][((4 * ((uint32_t)(self->private_impl.f_gc_transparent_index))) + 2)] = 0; + self->private_data.f_palettes[1][((4 * ((uint32_t)(self->private_impl.f_gc_transparent_index))) + 3)] = 0; + } + v_status = wuffs_base__pixel_swizzler__prepare(&self->private_impl.f_swizzler, + wuffs_base__pixel_buffer__pixel_format(a_dst), + wuffs_base__pixel_buffer__palette_or_else(a_dst, wuffs_base__make_slice_u8(self->private_data.f_dst_palette, 1024)), + wuffs_base__utility__make_pixel_format(2198077448), + wuffs_base__make_slice_u8(self->private_data.f_palettes[v_which_palette], 1024), + a_blend); + if ( ! wuffs_base__status__is_ok(&v_status)) { + status = v_status; + if (wuffs_base__status__is_error(&status)) { + goto exit; + } else if (wuffs_base__status__is_suspension(&status)) { + status = wuffs_base__make_status(wuffs_base__error__cannot_return_a_suspension); + goto exit; + } + goto ok; + } + if (self->private_impl.f_previous_lzw_decode_ended_abruptly) { + wuffs_base__ignore_status(wuffs_lzw__decoder__initialize(&self->private_data.f_lzw, + sizeof (wuffs_lzw__decoder), WUFFS_VERSION, WUFFS_INITIALIZE__LEAVE_INTERNAL_BUFFERS_UNINITIALIZED)); + } + { + WUFFS_BASE__COROUTINE_SUSPENSION_POINT(4); + if (WUFFS_BASE__UNLIKELY(iop_a_src == io2_a_src)) { + status = wuffs_base__make_status(wuffs_base__suspension__short_read); + goto suspend; + } + uint8_t t_2 = *iop_a_src++; + v_lw = t_2; + } + if (v_lw > 8) { + status = wuffs_base__make_status(wuffs_gif__error__bad_literal_width); + goto exit; + } + wuffs_lzw__decoder__set_literal_width(&self->private_data.f_lzw, ((uint32_t)(v_lw))); + self->private_impl.f_previous_lzw_decode_ended_abruptly = true; + + ok: + self->private_impl.p_decode_id_part1[0] = 0; + goto exit; + } + + goto suspend; + suspend: + self->private_impl.p_decode_id_part1[0] = wuffs_base__status__is_suspension(&status) ? coro_susp_point : 0; + self->private_data.s_decode_id_part1[0].v_which_palette = v_which_palette; + self->private_data.s_decode_id_part1[0].v_num_palette_entries = v_num_palette_entries; + self->private_data.s_decode_id_part1[0].v_i = v_i; + + goto exit; + exit: + if (a_src && a_src->data.ptr) { + a_src->meta.ri = ((size_t)(iop_a_src - a_src->data.ptr)); + } + + return status; +} + +// -------- func gif.decoder.decode_id_part2 + +static wuffs_base__status +wuffs_gif__decoder__decode_id_part2( + wuffs_gif__decoder* self, + wuffs_base__pixel_buffer* a_dst, + wuffs_base__io_buffer* a_src, + wuffs_base__slice_u8 a_workbuf) { + wuffs_base__io_buffer empty_io_buffer = wuffs_base__empty_io_buffer(); + + wuffs_base__status status = wuffs_base__make_status(NULL); + + uint64_t v_block_size = 0; + bool v_need_block_size = false; + uint32_t v_n_copied = 0; + uint64_t v_n_compressed = 0; + wuffs_base__io_buffer u_r = wuffs_base__empty_io_buffer(); + wuffs_base__io_buffer* v_r = &u_r; + const uint8_t* iop_v_r WUFFS_BASE__POTENTIALLY_UNUSED = NULL; + const uint8_t* io0_v_r WUFFS_BASE__POTENTIALLY_UNUSED = NULL; + const uint8_t* io1_v_r WUFFS_BASE__POTENTIALLY_UNUSED = NULL; + const uint8_t* io2_v_r WUFFS_BASE__POTENTIALLY_UNUSED = NULL; + uint64_t v_mark = 0; + wuffs_base__status v_lzw_status = wuffs_base__make_status(NULL); + wuffs_base__status v_copy_status = wuffs_base__make_status(NULL); + wuffs_base__slice_u8 v_uncompressed = {0}; + + const uint8_t* iop_a_src = NULL; + const uint8_t* io0_a_src WUFFS_BASE__POTENTIALLY_UNUSED = NULL; + const uint8_t* io1_a_src WUFFS_BASE__POTENTIALLY_UNUSED = NULL; + const uint8_t* io2_a_src WUFFS_BASE__POTENTIALLY_UNUSED = NULL; + if (a_src && a_src->data.ptr) { + io0_a_src = a_src->data.ptr; + io1_a_src = io0_a_src + a_src->meta.ri; + iop_a_src = io1_a_src; + io2_a_src = io0_a_src + a_src->meta.wi; + } + + uint32_t coro_susp_point = self->private_impl.p_decode_id_part2[0]; + if (coro_susp_point) { + v_block_size = self->private_data.s_decode_id_part2[0].v_block_size; + v_need_block_size = self->private_data.s_decode_id_part2[0].v_need_block_size; + } + switch (coro_susp_point) { + WUFFS_BASE__COROUTINE_SUSPENSION_POINT_0; + + v_need_block_size = true; + label__outer__continue:; + while (true) { + if (v_need_block_size) { + v_need_block_size = false; + { + WUFFS_BASE__COROUTINE_SUSPENSION_POINT(1); + if (WUFFS_BASE__UNLIKELY(iop_a_src == io2_a_src)) { + status = wuffs_base__make_status(wuffs_base__suspension__short_read); + goto suspend; + } + uint64_t t_0 = *iop_a_src++; + v_block_size = t_0; + } + } + if (v_block_size == 0) { + goto label__outer__break; + } + while (((uint64_t)(io2_a_src - iop_a_src)) == 0) { + status = wuffs_base__make_status(wuffs_base__suspension__short_read); + WUFFS_BASE__COROUTINE_SUSPENSION_POINT_MAYBE_SUSPEND(2); + } + if (self->private_impl.f_compressed_ri == self->private_impl.f_compressed_wi) { + self->private_impl.f_compressed_ri = 0; + self->private_impl.f_compressed_wi = 0; + } + while (self->private_impl.f_compressed_wi <= 3841) { + v_n_compressed = wuffs_base__u64__min(v_block_size, ((uint64_t)(io2_a_src - iop_a_src))); + if (v_n_compressed <= 0) { + goto label__0__break; + } + v_n_copied = wuffs_base__io_reader__limited_copy_u32_to_slice( + &iop_a_src, io2_a_src,((uint32_t)((v_n_compressed & 4294967295))), wuffs_base__make_slice_u8_ij(self->private_data.f_compressed, self->private_impl.f_compressed_wi, 4096)); + wuffs_base__u64__sat_add_indirect(&self->private_impl.f_compressed_wi, ((uint64_t)(v_n_copied))); + wuffs_base__u64__sat_sub_indirect(&v_block_size, ((uint64_t)(v_n_copied))); + if (v_block_size > 0) { + goto label__0__break; + } + if (((uint64_t)(io2_a_src - iop_a_src)) <= 0) { + v_need_block_size = true; + goto label__0__break; + } + v_block_size = ((uint64_t)(wuffs_base__peek_u8be__no_bounds_check(iop_a_src))); + iop_a_src += 1; + } + label__0__break:; + label__inner__continue:; + while (true) { + if ((self->private_impl.f_compressed_ri > self->private_impl.f_compressed_wi) || (self->private_impl.f_compressed_wi > 4096)) { + status = wuffs_base__make_status(wuffs_gif__error__internal_error_inconsistent_ri_wi); + goto exit; + } + { + wuffs_base__io_buffer* o_0_v_r = v_r; + const uint8_t *o_0_iop_v_r = iop_v_r; + const uint8_t *o_0_io0_v_r = io0_v_r; + const uint8_t *o_0_io1_v_r = io1_v_r; + const uint8_t *o_0_io2_v_r = io2_v_r; + v_r = wuffs_base__io_reader__set( + &u_r, + &iop_v_r, + &io0_v_r, + &io1_v_r, + &io2_v_r, + wuffs_base__make_slice_u8_ij(self->private_data.f_compressed, + self->private_impl.f_compressed_ri, + self->private_impl.f_compressed_wi), + 0); + v_mark = ((uint64_t)(iop_v_r - io0_v_r)); + { + u_r.meta.ri = ((size_t)(iop_v_r - u_r.data.ptr)); + wuffs_base__status t_1 = wuffs_lzw__decoder__transform_io(&self->private_data.f_lzw, &empty_io_buffer, v_r, wuffs_base__utility__empty_slice_u8()); + v_lzw_status = t_1; + iop_v_r = u_r.data.ptr + u_r.meta.ri; + } + wuffs_base__u64__sat_add_indirect(&self->private_impl.f_compressed_ri, wuffs_base__io__count_since(v_mark, ((uint64_t)(iop_v_r - io0_v_r)))); + v_r = o_0_v_r; + iop_v_r = o_0_iop_v_r; + io0_v_r = o_0_io0_v_r; + io1_v_r = o_0_io1_v_r; + io2_v_r = o_0_io2_v_r; + } + v_uncompressed = wuffs_lzw__decoder__flush(&self->private_data.f_lzw); + if (((uint64_t)(v_uncompressed.len)) > 0) { + v_copy_status = wuffs_gif__decoder__copy_to_image_buffer(self, a_dst, v_uncompressed); + if (wuffs_base__status__is_error(&v_copy_status)) { + status = v_copy_status; + goto exit; + } + } + if (wuffs_base__status__is_ok(&v_lzw_status)) { + self->private_impl.f_previous_lzw_decode_ended_abruptly = false; + if (v_need_block_size || (v_block_size > 0)) { + self->private_data.s_decode_id_part2[0].scratch = ((uint32_t)(v_block_size)); + WUFFS_BASE__COROUTINE_SUSPENSION_POINT(3); + if (self->private_data.s_decode_id_part2[0].scratch > ((uint64_t)(io2_a_src - iop_a_src))) { + self->private_data.s_decode_id_part2[0].scratch -= ((uint64_t)(io2_a_src - iop_a_src)); + iop_a_src = io2_a_src; + status = wuffs_base__make_status(wuffs_base__suspension__short_read); + goto suspend; + } + iop_a_src += self->private_data.s_decode_id_part2[0].scratch; + if (a_src) { + a_src->meta.ri = ((size_t)(iop_a_src - a_src->data.ptr)); + } + WUFFS_BASE__COROUTINE_SUSPENSION_POINT(4); + status = wuffs_gif__decoder__skip_blocks(self, a_src); + if (a_src) { + iop_a_src = a_src->data.ptr + a_src->meta.ri; + } + if (status.repr) { + goto suspend; + } + } + goto label__outer__break; + } else if (v_lzw_status.repr == wuffs_base__suspension__short_read) { + goto label__outer__continue; + } else if (v_lzw_status.repr == wuffs_base__suspension__short_write) { + goto label__inner__continue; + } else if (self->private_impl.f_quirks[3] && (self->private_impl.f_dst_y >= self->private_impl.f_frame_rect_y1) && (self->private_impl.f_interlace == 0)) { + if (v_need_block_size || (v_block_size > 0)) { + self->private_data.s_decode_id_part2[0].scratch = ((uint32_t)(v_block_size)); + WUFFS_BASE__COROUTINE_SUSPENSION_POINT(5); + if (self->private_data.s_decode_id_part2[0].scratch > ((uint64_t)(io2_a_src - iop_a_src))) { + self->private_data.s_decode_id_part2[0].scratch -= ((uint64_t)(io2_a_src - iop_a_src)); + iop_a_src = io2_a_src; + status = wuffs_base__make_status(wuffs_base__suspension__short_read); + goto suspend; + } + iop_a_src += self->private_data.s_decode_id_part2[0].scratch; + if (a_src) { + a_src->meta.ri = ((size_t)(iop_a_src - a_src->data.ptr)); + } + WUFFS_BASE__COROUTINE_SUSPENSION_POINT(6); + status = wuffs_gif__decoder__skip_blocks(self, a_src); + if (a_src) { + iop_a_src = a_src->data.ptr + a_src->meta.ri; + } + if (status.repr) { + goto suspend; + } + } + goto label__outer__break; + } + status = v_lzw_status; + if (wuffs_base__status__is_error(&status)) { + goto exit; + } else if (wuffs_base__status__is_suspension(&status)) { + status = wuffs_base__make_status(wuffs_base__error__cannot_return_a_suspension); + goto exit; + } + goto ok; + } + } + label__outer__break:; + self->private_impl.f_compressed_ri = 0; + self->private_impl.f_compressed_wi = 0; + if ((self->private_impl.f_dst_y < self->private_impl.f_frame_rect_y1) && (self->private_impl.f_frame_rect_x0 != self->private_impl.f_frame_rect_x1) && (self->private_impl.f_frame_rect_y0 != self->private_impl.f_frame_rect_y1)) { + status = wuffs_base__make_status(wuffs_base__error__not_enough_data); + goto exit; + } + + ok: + self->private_impl.p_decode_id_part2[0] = 0; + goto exit; + } + + goto suspend; + suspend: + self->private_impl.p_decode_id_part2[0] = wuffs_base__status__is_suspension(&status) ? coro_susp_point : 0; + self->private_data.s_decode_id_part2[0].v_block_size = v_block_size; + self->private_data.s_decode_id_part2[0].v_need_block_size = v_need_block_size; + + goto exit; + exit: + if (a_src && a_src->data.ptr) { + a_src->meta.ri = ((size_t)(iop_a_src - a_src->data.ptr)); + } + + return status; +} + +// -------- func gif.decoder.copy_to_image_buffer + +static wuffs_base__status +wuffs_gif__decoder__copy_to_image_buffer( + wuffs_gif__decoder* self, + wuffs_base__pixel_buffer* a_pb, + wuffs_base__slice_u8 a_src) { + wuffs_base__slice_u8 v_dst = {0}; + wuffs_base__slice_u8 v_src = {0}; + uint64_t v_width_in_bytes = 0; + uint64_t v_n = 0; + uint64_t v_src_ri = 0; + wuffs_base__pixel_format v_pixfmt = {0}; + uint32_t v_bytes_per_pixel = 0; + uint32_t v_bits_per_pixel = 0; + wuffs_base__table_u8 v_tab = {0}; + uint64_t v_i = 0; + uint64_t v_j = 0; + uint32_t v_replicate_y0 = 0; + uint32_t v_replicate_y1 = 0; + wuffs_base__slice_u8 v_replicate_dst = {0}; + wuffs_base__slice_u8 v_replicate_src = {0}; + + v_pixfmt = wuffs_base__pixel_buffer__pixel_format(a_pb); + v_bits_per_pixel = wuffs_base__pixel_format__bits_per_pixel(&v_pixfmt); + if ((v_bits_per_pixel & 7) != 0) { + return wuffs_base__make_status(wuffs_base__error__unsupported_option); + } + v_bytes_per_pixel = (v_bits_per_pixel >> 3); + v_width_in_bytes = (((uint64_t)(self->private_impl.f_width)) * ((uint64_t)(v_bytes_per_pixel))); + v_tab = wuffs_base__pixel_buffer__plane(a_pb, 0); + label__0__continue:; + while (v_src_ri < ((uint64_t)(a_src.len))) { + v_src = wuffs_base__slice_u8__subslice_i(a_src, v_src_ri); + if (self->private_impl.f_dst_y >= self->private_impl.f_frame_rect_y1) { + if (self->private_impl.f_quirks[3]) { + return wuffs_base__make_status(NULL); + } + return wuffs_base__make_status(wuffs_base__error__too_much_data); + } + v_dst = wuffs_base__table_u8__row_u32(v_tab, self->private_impl.f_dst_y); + if (self->private_impl.f_dst_y >= self->private_impl.f_height) { + v_dst = wuffs_base__slice_u8__subslice_j(v_dst, 0); + } else if (v_width_in_bytes < ((uint64_t)(v_dst.len))) { + v_dst = wuffs_base__slice_u8__subslice_j(v_dst, v_width_in_bytes); + } + v_i = (((uint64_t)(self->private_impl.f_dst_x)) * ((uint64_t)(v_bytes_per_pixel))); + if (v_i < ((uint64_t)(v_dst.len))) { + v_j = (((uint64_t)(self->private_impl.f_frame_rect_x1)) * ((uint64_t)(v_bytes_per_pixel))); + if ((v_i <= v_j) && (v_j <= ((uint64_t)(v_dst.len)))) { + v_dst = wuffs_base__slice_u8__subslice_ij(v_dst, v_i, v_j); + } else { + v_dst = wuffs_base__slice_u8__subslice_i(v_dst, v_i); + } + v_n = wuffs_base__pixel_swizzler__swizzle_interleaved_from_slice(&self->private_impl.f_swizzler, v_dst, wuffs_base__make_slice_u8(self->private_data.f_dst_palette, 1024), v_src); + wuffs_base__u64__sat_add_indirect(&v_src_ri, v_n); + wuffs_base__u32__sat_add_indirect(&self->private_impl.f_dst_x, ((uint32_t)((v_n & 4294967295)))); + self->private_impl.f_dirty_max_excl_y = wuffs_base__u32__max(self->private_impl.f_dirty_max_excl_y, wuffs_base__u32__sat_add(self->private_impl.f_dst_y, 1)); + } + if (self->private_impl.f_frame_rect_x1 <= self->private_impl.f_dst_x) { + self->private_impl.f_dst_x = self->private_impl.f_frame_rect_x0; + if (self->private_impl.f_interlace == 0) { + wuffs_base__u32__sat_add_indirect(&self->private_impl.f_dst_y, 1); + goto label__0__continue; + } + if ((self->private_impl.f_num_decoded_frames_value == 0) && ! self->private_impl.f_gc_has_transparent_index && (self->private_impl.f_interlace > 1)) { + v_replicate_src = wuffs_base__table_u8__row_u32(v_tab, self->private_impl.f_dst_y); + v_replicate_y0 = wuffs_base__u32__sat_add(self->private_impl.f_dst_y, 1); + v_replicate_y1 = wuffs_base__u32__sat_add(self->private_impl.f_dst_y, ((uint32_t)(WUFFS_GIF__INTERLACE_COUNT[self->private_impl.f_interlace]))); + v_replicate_y1 = wuffs_base__u32__min(v_replicate_y1, self->private_impl.f_frame_rect_y1); + while (v_replicate_y0 < v_replicate_y1) { + v_replicate_dst = wuffs_base__table_u8__row_u32(v_tab, v_replicate_y0); + wuffs_base__slice_u8__copy_from_slice(v_replicate_dst, v_replicate_src); + v_replicate_y0 += 1; + } + self->private_impl.f_dirty_max_excl_y = wuffs_base__u32__max(self->private_impl.f_dirty_max_excl_y, v_replicate_y1); + } + wuffs_base__u32__sat_add_indirect(&self->private_impl.f_dst_y, ((uint32_t)(WUFFS_GIF__INTERLACE_DELTA[self->private_impl.f_interlace]))); + while ((self->private_impl.f_interlace > 0) && (self->private_impl.f_dst_y >= self->private_impl.f_frame_rect_y1)) { +#if defined(__GNUC__) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wconversion" +#endif + self->private_impl.f_interlace -= 1; +#if defined(__GNUC__) +#pragma GCC diagnostic pop +#endif + self->private_impl.f_dst_y = wuffs_base__u32__sat_add(self->private_impl.f_frame_rect_y0, WUFFS_GIF__INTERLACE_START[self->private_impl.f_interlace]); + } + goto label__0__continue; + } + if (((uint64_t)(a_src.len)) == v_src_ri) { + goto label__0__break; + } else if (((uint64_t)(a_src.len)) < v_src_ri) { + return wuffs_base__make_status(wuffs_gif__error__internal_error_inconsistent_ri_wi); + } + v_n = ((uint64_t)((self->private_impl.f_frame_rect_x1 - self->private_impl.f_dst_x))); + v_n = wuffs_base__u64__min(v_n, (((uint64_t)(a_src.len)) - v_src_ri)); + wuffs_base__u64__sat_add_indirect(&v_src_ri, v_n); + wuffs_base__u32__sat_add_indirect(&self->private_impl.f_dst_x, ((uint32_t)((v_n & 4294967295)))); + if (self->private_impl.f_frame_rect_x1 <= self->private_impl.f_dst_x) { + self->private_impl.f_dst_x = self->private_impl.f_frame_rect_x0; + wuffs_base__u32__sat_add_indirect(&self->private_impl.f_dst_y, ((uint32_t)(WUFFS_GIF__INTERLACE_DELTA[self->private_impl.f_interlace]))); + while ((self->private_impl.f_interlace > 0) && (self->private_impl.f_dst_y >= self->private_impl.f_frame_rect_y1)) { +#if defined(__GNUC__) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wconversion" +#endif + self->private_impl.f_interlace -= 1; +#if defined(__GNUC__) +#pragma GCC diagnostic pop +#endif + self->private_impl.f_dst_y = wuffs_base__u32__sat_add(self->private_impl.f_frame_rect_y0, WUFFS_GIF__INTERLACE_START[self->private_impl.f_interlace]); + } + goto label__0__continue; + } + if (v_src_ri != ((uint64_t)(a_src.len))) { + return wuffs_base__make_status(wuffs_gif__error__internal_error_inconsistent_ri_wi); + } + goto label__0__break; + } + label__0__break:; + return wuffs_base__make_status(NULL); +} + +#endif // !defined(WUFFS_CONFIG__MODULES) || defined(WUFFS_CONFIG__MODULE__GIF) + +#if !defined(WUFFS_CONFIG__MODULES) || defined(WUFFS_CONFIG__MODULE__GZIP) + +// ---------------- Status Codes Implementations + +const char wuffs_gzip__error__bad_checksum[] = "#gzip: bad checksum"; +const char wuffs_gzip__error__bad_compression_method[] = "#gzip: bad compression method"; +const char wuffs_gzip__error__bad_encoding_flags[] = "#gzip: bad encoding flags"; +const char wuffs_gzip__error__bad_header[] = "#gzip: bad header"; +const char wuffs_gzip__error__truncated_input[] = "#gzip: truncated input"; + +// ---------------- Private Consts + +// ---------------- Private Initializer Prototypes + +// ---------------- Private Function Prototypes + +static wuffs_base__status +wuffs_gzip__decoder__do_transform_io( + wuffs_gzip__decoder* self, + wuffs_base__io_buffer* a_dst, + wuffs_base__io_buffer* a_src, + wuffs_base__slice_u8 a_workbuf); + +// ---------------- VTables + +const wuffs_base__io_transformer__func_ptrs +wuffs_gzip__decoder__func_ptrs_for__wuffs_base__io_transformer = { + (wuffs_base__empty_struct(*)(void*, + uint32_t, + bool))(&wuffs_gzip__decoder__set_quirk_enabled), + (wuffs_base__status(*)(void*, + wuffs_base__io_buffer*, + wuffs_base__io_buffer*, + wuffs_base__slice_u8))(&wuffs_gzip__decoder__transform_io), + (wuffs_base__range_ii_u64(*)(const void*))(&wuffs_gzip__decoder__workbuf_len), +}; + +// ---------------- Initializer Implementations + +wuffs_base__status WUFFS_BASE__WARN_UNUSED_RESULT +wuffs_gzip__decoder__initialize( + wuffs_gzip__decoder* self, + size_t sizeof_star_self, + uint64_t wuffs_version, + uint32_t options){ + if (!self) { + return wuffs_base__make_status(wuffs_base__error__bad_receiver); + } + if (sizeof(*self) != sizeof_star_self) { + return wuffs_base__make_status(wuffs_base__error__bad_sizeof_receiver); + } + if (((wuffs_version >> 32) != WUFFS_VERSION_MAJOR) || + (((wuffs_version >> 16) & 0xFFFF) > WUFFS_VERSION_MINOR)) { + return wuffs_base__make_status(wuffs_base__error__bad_wuffs_version); + } + + if ((options & WUFFS_INITIALIZE__ALREADY_ZEROED) != 0) { + // The whole point of this if-check is to detect an uninitialized *self. + // We disable the warning on GCC. Clang-5.0 does not have this warning. +#if !defined(__clang__) && defined(__GNUC__) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wmaybe-uninitialized" +#endif + if (self->private_impl.magic != 0) { + return wuffs_base__make_status(wuffs_base__error__initialize_falsely_claimed_already_zeroed); + } +#if !defined(__clang__) && defined(__GNUC__) +#pragma GCC diagnostic pop +#endif + } else { + if ((options & WUFFS_INITIALIZE__LEAVE_INTERNAL_BUFFERS_UNINITIALIZED) == 0) { + memset(self, 0, sizeof(*self)); + options |= WUFFS_INITIALIZE__ALREADY_ZEROED; + } else { + memset(&(self->private_impl), 0, sizeof(self->private_impl)); + } + } + + { + wuffs_base__status z = wuffs_crc32__ieee_hasher__initialize( + &self->private_data.f_checksum, sizeof(self->private_data.f_checksum), WUFFS_VERSION, options); + if (z.repr) { + return z; + } + } + { + wuffs_base__status z = wuffs_deflate__decoder__initialize( + &self->private_data.f_flate, sizeof(self->private_data.f_flate), WUFFS_VERSION, options); + if (z.repr) { + return z; + } + } + self->private_impl.magic = WUFFS_BASE__MAGIC; + self->private_impl.vtable_for__wuffs_base__io_transformer.vtable_name = + wuffs_base__io_transformer__vtable_name; + self->private_impl.vtable_for__wuffs_base__io_transformer.function_pointers = + (const void*)(&wuffs_gzip__decoder__func_ptrs_for__wuffs_base__io_transformer); + return wuffs_base__make_status(NULL); +} + +wuffs_gzip__decoder* +wuffs_gzip__decoder__alloc() { + wuffs_gzip__decoder* x = + (wuffs_gzip__decoder*)(calloc(sizeof(wuffs_gzip__decoder), 1)); + if (!x) { + return NULL; + } + if (wuffs_gzip__decoder__initialize( + x, sizeof(wuffs_gzip__decoder), WUFFS_VERSION, WUFFS_INITIALIZE__ALREADY_ZEROED).repr) { + free(x); + return NULL; + } + return x; +} + +size_t +sizeof__wuffs_gzip__decoder() { + return sizeof(wuffs_gzip__decoder); +} + +// ---------------- Function Implementations + +// -------- func gzip.decoder.set_quirk_enabled + +WUFFS_BASE__MAYBE_STATIC wuffs_base__empty_struct +wuffs_gzip__decoder__set_quirk_enabled( + wuffs_gzip__decoder* self, + uint32_t a_quirk, + bool a_enabled) { + if (!self) { + return wuffs_base__make_empty_struct(); + } + if (self->private_impl.magic != WUFFS_BASE__MAGIC) { + return wuffs_base__make_empty_struct(); + } + + if (a_quirk == 1) { + self->private_impl.f_ignore_checksum = a_enabled; + } + return wuffs_base__make_empty_struct(); +} + +// -------- func gzip.decoder.workbuf_len + +WUFFS_BASE__MAYBE_STATIC wuffs_base__range_ii_u64 +wuffs_gzip__decoder__workbuf_len( + const wuffs_gzip__decoder* self) { + if (!self) { + return wuffs_base__utility__empty_range_ii_u64(); + } + if ((self->private_impl.magic != WUFFS_BASE__MAGIC) && + (self->private_impl.magic != WUFFS_BASE__DISABLED)) { + return wuffs_base__utility__empty_range_ii_u64(); + } + + return wuffs_base__utility__make_range_ii_u64(1, 1); +} + +// -------- func gzip.decoder.transform_io + +WUFFS_BASE__MAYBE_STATIC wuffs_base__status +wuffs_gzip__decoder__transform_io( + wuffs_gzip__decoder* self, + wuffs_base__io_buffer* a_dst, + wuffs_base__io_buffer* a_src, + wuffs_base__slice_u8 a_workbuf) { + if (!self) { + return wuffs_base__make_status(wuffs_base__error__bad_receiver); + } + if (self->private_impl.magic != WUFFS_BASE__MAGIC) { + return wuffs_base__make_status( + (self->private_impl.magic == WUFFS_BASE__DISABLED) + ? wuffs_base__error__disabled_by_previous_error + : wuffs_base__error__initialize_not_called); + } + if (!a_dst || !a_src) { + self->private_impl.magic = WUFFS_BASE__DISABLED; + return wuffs_base__make_status(wuffs_base__error__bad_argument); + } + if ((self->private_impl.active_coroutine != 0) && + (self->private_impl.active_coroutine != 1)) { + self->private_impl.magic = WUFFS_BASE__DISABLED; + return wuffs_base__make_status(wuffs_base__error__interleaved_coroutine_calls); + } + self->private_impl.active_coroutine = 0; + wuffs_base__status status = wuffs_base__make_status(NULL); + + wuffs_base__status v_status = wuffs_base__make_status(NULL); + + uint32_t coro_susp_point = self->private_impl.p_transform_io[0]; + switch (coro_susp_point) { + WUFFS_BASE__COROUTINE_SUSPENSION_POINT_0; + + while (true) { + { + wuffs_base__status t_0 = wuffs_gzip__decoder__do_transform_io(self, a_dst, a_src, a_workbuf); + v_status = t_0; + } + if ((v_status.repr == wuffs_base__suspension__short_read) && (a_src && a_src->meta.closed)) { + status = wuffs_base__make_status(wuffs_gzip__error__truncated_input); + goto exit; + } + status = v_status; + WUFFS_BASE__COROUTINE_SUSPENSION_POINT_MAYBE_SUSPEND(1); + } + + ok: + self->private_impl.p_transform_io[0] = 0; + goto exit; + } + + goto suspend; + suspend: + self->private_impl.p_transform_io[0] = wuffs_base__status__is_suspension(&status) ? coro_susp_point : 0; + self->private_impl.active_coroutine = wuffs_base__status__is_suspension(&status) ? 1 : 0; + + goto exit; + exit: + if (wuffs_base__status__is_error(&status)) { + self->private_impl.magic = WUFFS_BASE__DISABLED; + } + return status; +} + +// -------- func gzip.decoder.do_transform_io + +static wuffs_base__status +wuffs_gzip__decoder__do_transform_io( + wuffs_gzip__decoder* self, + wuffs_base__io_buffer* a_dst, + wuffs_base__io_buffer* a_src, + wuffs_base__slice_u8 a_workbuf) { + wuffs_base__status status = wuffs_base__make_status(NULL); + + uint8_t v_c = 0; + uint8_t v_flags = 0; + uint16_t v_xlen = 0; + uint64_t v_mark = 0; + uint32_t v_checksum_got = 0; + uint32_t v_decoded_length_got = 0; + wuffs_base__status v_status = wuffs_base__make_status(NULL); + uint32_t v_checksum_want = 0; + uint32_t v_decoded_length_want = 0; + + uint8_t* iop_a_dst = NULL; + uint8_t* io0_a_dst WUFFS_BASE__POTENTIALLY_UNUSED = NULL; + uint8_t* io1_a_dst WUFFS_BASE__POTENTIALLY_UNUSED = NULL; + uint8_t* io2_a_dst WUFFS_BASE__POTENTIALLY_UNUSED = NULL; + if (a_dst && a_dst->data.ptr) { + io0_a_dst = a_dst->data.ptr; + io1_a_dst = io0_a_dst + a_dst->meta.wi; + iop_a_dst = io1_a_dst; + io2_a_dst = io0_a_dst + a_dst->data.len; + if (a_dst->meta.closed) { + io2_a_dst = iop_a_dst; + } + } + const uint8_t* iop_a_src = NULL; + const uint8_t* io0_a_src WUFFS_BASE__POTENTIALLY_UNUSED = NULL; + const uint8_t* io1_a_src WUFFS_BASE__POTENTIALLY_UNUSED = NULL; + const uint8_t* io2_a_src WUFFS_BASE__POTENTIALLY_UNUSED = NULL; + if (a_src && a_src->data.ptr) { + io0_a_src = a_src->data.ptr; + io1_a_src = io0_a_src + a_src->meta.ri; + iop_a_src = io1_a_src; + io2_a_src = io0_a_src + a_src->meta.wi; + } + + uint32_t coro_susp_point = self->private_impl.p_do_transform_io[0]; + if (coro_susp_point) { + v_flags = self->private_data.s_do_transform_io[0].v_flags; + v_checksum_got = self->private_data.s_do_transform_io[0].v_checksum_got; + v_decoded_length_got = self->private_data.s_do_transform_io[0].v_decoded_length_got; + v_checksum_want = self->private_data.s_do_transform_io[0].v_checksum_want; + } + switch (coro_susp_point) { + WUFFS_BASE__COROUTINE_SUSPENSION_POINT_0; + + { + WUFFS_BASE__COROUTINE_SUSPENSION_POINT(1); + if (WUFFS_BASE__UNLIKELY(iop_a_src == io2_a_src)) { + status = wuffs_base__make_status(wuffs_base__suspension__short_read); + goto suspend; + } + uint8_t t_0 = *iop_a_src++; + v_c = t_0; + } + if (v_c != 31) { + status = wuffs_base__make_status(wuffs_gzip__error__bad_header); + goto exit; + } + { + WUFFS_BASE__COROUTINE_SUSPENSION_POINT(2); + if (WUFFS_BASE__UNLIKELY(iop_a_src == io2_a_src)) { + status = wuffs_base__make_status(wuffs_base__suspension__short_read); + goto suspend; + } + uint8_t t_1 = *iop_a_src++; + v_c = t_1; + } + if (v_c != 139) { + status = wuffs_base__make_status(wuffs_gzip__error__bad_header); + goto exit; + } + { + WUFFS_BASE__COROUTINE_SUSPENSION_POINT(3); + if (WUFFS_BASE__UNLIKELY(iop_a_src == io2_a_src)) { + status = wuffs_base__make_status(wuffs_base__suspension__short_read); + goto suspend; + } + uint8_t t_2 = *iop_a_src++; + v_c = t_2; + } + if (v_c != 8) { + status = wuffs_base__make_status(wuffs_gzip__error__bad_compression_method); + goto exit; + } + { + WUFFS_BASE__COROUTINE_SUSPENSION_POINT(4); + if (WUFFS_BASE__UNLIKELY(iop_a_src == io2_a_src)) { + status = wuffs_base__make_status(wuffs_base__suspension__short_read); + goto suspend; + } + uint8_t t_3 = *iop_a_src++; + v_flags = t_3; + } + self->private_data.s_do_transform_io[0].scratch = 6; + WUFFS_BASE__COROUTINE_SUSPENSION_POINT(5); + if (self->private_data.s_do_transform_io[0].scratch > ((uint64_t)(io2_a_src - iop_a_src))) { + self->private_data.s_do_transform_io[0].scratch -= ((uint64_t)(io2_a_src - iop_a_src)); + iop_a_src = io2_a_src; + status = wuffs_base__make_status(wuffs_base__suspension__short_read); + goto suspend; + } + iop_a_src += self->private_data.s_do_transform_io[0].scratch; + if ((v_flags & 4) != 0) { + { + WUFFS_BASE__COROUTINE_SUSPENSION_POINT(6); + uint16_t t_4; + if (WUFFS_BASE__LIKELY(io2_a_src - iop_a_src >= 2)) { + t_4 = wuffs_base__peek_u16le__no_bounds_check(iop_a_src); + iop_a_src += 2; + } else { + self->private_data.s_do_transform_io[0].scratch = 0; + WUFFS_BASE__COROUTINE_SUSPENSION_POINT(7); + while (true) { + if (WUFFS_BASE__UNLIKELY(iop_a_src == io2_a_src)) { + status = wuffs_base__make_status(wuffs_base__suspension__short_read); + goto suspend; + } + uint64_t* scratch = &self->private_data.s_do_transform_io[0].scratch; + uint32_t num_bits_4 = ((uint32_t)(*scratch >> 56)); + *scratch <<= 8; + *scratch >>= 8; + *scratch |= ((uint64_t)(*iop_a_src++)) << num_bits_4; + if (num_bits_4 == 8) { + t_4 = ((uint16_t)(*scratch)); + break; + } + num_bits_4 += 8; + *scratch |= ((uint64_t)(num_bits_4)) << 56; + } + } + v_xlen = t_4; + } + self->private_data.s_do_transform_io[0].scratch = ((uint32_t)(v_xlen)); + WUFFS_BASE__COROUTINE_SUSPENSION_POINT(8); + if (self->private_data.s_do_transform_io[0].scratch > ((uint64_t)(io2_a_src - iop_a_src))) { + self->private_data.s_do_transform_io[0].scratch -= ((uint64_t)(io2_a_src - iop_a_src)); + iop_a_src = io2_a_src; + status = wuffs_base__make_status(wuffs_base__suspension__short_read); + goto suspend; + } + iop_a_src += self->private_data.s_do_transform_io[0].scratch; + } + if ((v_flags & 8) != 0) { + while (true) { + { + WUFFS_BASE__COROUTINE_SUSPENSION_POINT(9); + if (WUFFS_BASE__UNLIKELY(iop_a_src == io2_a_src)) { + status = wuffs_base__make_status(wuffs_base__suspension__short_read); + goto suspend; + } + uint8_t t_5 = *iop_a_src++; + v_c = t_5; + } + if (v_c == 0) { + goto label__0__break; + } + } + label__0__break:; + } + if ((v_flags & 16) != 0) { + while (true) { + { + WUFFS_BASE__COROUTINE_SUSPENSION_POINT(10); + if (WUFFS_BASE__UNLIKELY(iop_a_src == io2_a_src)) { + status = wuffs_base__make_status(wuffs_base__suspension__short_read); + goto suspend; + } + uint8_t t_6 = *iop_a_src++; + v_c = t_6; + } + if (v_c == 0) { + goto label__1__break; + } + } + label__1__break:; + } + if ((v_flags & 2) != 0) { + self->private_data.s_do_transform_io[0].scratch = 2; + WUFFS_BASE__COROUTINE_SUSPENSION_POINT(11); + if (self->private_data.s_do_transform_io[0].scratch > ((uint64_t)(io2_a_src - iop_a_src))) { + self->private_data.s_do_transform_io[0].scratch -= ((uint64_t)(io2_a_src - iop_a_src)); + iop_a_src = io2_a_src; + status = wuffs_base__make_status(wuffs_base__suspension__short_read); + goto suspend; + } + iop_a_src += self->private_data.s_do_transform_io[0].scratch; + } + if ((v_flags & 224) != 0) { + status = wuffs_base__make_status(wuffs_gzip__error__bad_encoding_flags); + goto exit; + } + while (true) { + v_mark = ((uint64_t)(iop_a_dst - io0_a_dst)); + { + if (a_dst) { + a_dst->meta.wi = ((size_t)(iop_a_dst - a_dst->data.ptr)); + } + if (a_src) { + a_src->meta.ri = ((size_t)(iop_a_src - a_src->data.ptr)); + } + wuffs_base__status t_7 = wuffs_deflate__decoder__transform_io(&self->private_data.f_flate, a_dst, a_src, a_workbuf); + v_status = t_7; + if (a_dst) { + iop_a_dst = a_dst->data.ptr + a_dst->meta.wi; + } + if (a_src) { + iop_a_src = a_src->data.ptr + a_src->meta.ri; + } + } + if ( ! self->private_impl.f_ignore_checksum) { + v_checksum_got = wuffs_crc32__ieee_hasher__update_u32(&self->private_data.f_checksum, wuffs_base__io__since(v_mark, ((uint64_t)(iop_a_dst - io0_a_dst)), io0_a_dst)); + v_decoded_length_got += ((uint32_t)((wuffs_base__io__count_since(v_mark, ((uint64_t)(iop_a_dst - io0_a_dst))) & 4294967295))); + } + if (wuffs_base__status__is_ok(&v_status)) { + goto label__2__break; + } + status = v_status; + WUFFS_BASE__COROUTINE_SUSPENSION_POINT_MAYBE_SUSPEND(12); + } + label__2__break:; + { + WUFFS_BASE__COROUTINE_SUSPENSION_POINT(13); + uint32_t t_8; + if (WUFFS_BASE__LIKELY(io2_a_src - iop_a_src >= 4)) { + t_8 = wuffs_base__peek_u32le__no_bounds_check(iop_a_src); + iop_a_src += 4; + } else { + self->private_data.s_do_transform_io[0].scratch = 0; + WUFFS_BASE__COROUTINE_SUSPENSION_POINT(14); + while (true) { + if (WUFFS_BASE__UNLIKELY(iop_a_src == io2_a_src)) { + status = wuffs_base__make_status(wuffs_base__suspension__short_read); + goto suspend; + } + uint64_t* scratch = &self->private_data.s_do_transform_io[0].scratch; + uint32_t num_bits_8 = ((uint32_t)(*scratch >> 56)); + *scratch <<= 8; + *scratch >>= 8; + *scratch |= ((uint64_t)(*iop_a_src++)) << num_bits_8; + if (num_bits_8 == 24) { + t_8 = ((uint32_t)(*scratch)); + break; + } + num_bits_8 += 8; + *scratch |= ((uint64_t)(num_bits_8)) << 56; + } + } + v_checksum_want = t_8; + } + { + WUFFS_BASE__COROUTINE_SUSPENSION_POINT(15); + uint32_t t_9; + if (WUFFS_BASE__LIKELY(io2_a_src - iop_a_src >= 4)) { + t_9 = wuffs_base__peek_u32le__no_bounds_check(iop_a_src); + iop_a_src += 4; + } else { + self->private_data.s_do_transform_io[0].scratch = 0; + WUFFS_BASE__COROUTINE_SUSPENSION_POINT(16); + while (true) { + if (WUFFS_BASE__UNLIKELY(iop_a_src == io2_a_src)) { + status = wuffs_base__make_status(wuffs_base__suspension__short_read); + goto suspend; + } + uint64_t* scratch = &self->private_data.s_do_transform_io[0].scratch; + uint32_t num_bits_9 = ((uint32_t)(*scratch >> 56)); + *scratch <<= 8; + *scratch >>= 8; + *scratch |= ((uint64_t)(*iop_a_src++)) << num_bits_9; + if (num_bits_9 == 24) { + t_9 = ((uint32_t)(*scratch)); + break; + } + num_bits_9 += 8; + *scratch |= ((uint64_t)(num_bits_9)) << 56; + } + } + v_decoded_length_want = t_9; + } + if ( ! self->private_impl.f_ignore_checksum && ((v_checksum_got != v_checksum_want) || (v_decoded_length_got != v_decoded_length_want))) { + status = wuffs_base__make_status(wuffs_gzip__error__bad_checksum); + goto exit; + } + + ok: + self->private_impl.p_do_transform_io[0] = 0; + goto exit; + } + + goto suspend; + suspend: + self->private_impl.p_do_transform_io[0] = wuffs_base__status__is_suspension(&status) ? coro_susp_point : 0; + self->private_data.s_do_transform_io[0].v_flags = v_flags; + self->private_data.s_do_transform_io[0].v_checksum_got = v_checksum_got; + self->private_data.s_do_transform_io[0].v_decoded_length_got = v_decoded_length_got; + self->private_data.s_do_transform_io[0].v_checksum_want = v_checksum_want; + + goto exit; + exit: + if (a_dst && a_dst->data.ptr) { + a_dst->meta.wi = ((size_t)(iop_a_dst - a_dst->data.ptr)); + } + if (a_src && a_src->data.ptr) { + a_src->meta.ri = ((size_t)(iop_a_src - a_src->data.ptr)); + } + + return status; +} + +#endif // !defined(WUFFS_CONFIG__MODULES) || defined(WUFFS_CONFIG__MODULE__GZIP) + +#if !defined(WUFFS_CONFIG__MODULES) || defined(WUFFS_CONFIG__MODULE__JSON) + +// ---------------- Status Codes Implementations + +const char wuffs_json__error__bad_c0_control_code[] = "#json: bad C0 control code"; +const char wuffs_json__error__bad_utf_8[] = "#json: bad UTF-8"; +const char wuffs_json__error__bad_backslash_escape[] = "#json: bad backslash-escape"; +const char wuffs_json__error__bad_input[] = "#json: bad input"; +const char wuffs_json__error__bad_new_line_in_a_string[] = "#json: bad new-line in a string"; +const char wuffs_json__error__bad_quirk_combination[] = "#json: bad quirk combination"; +const char wuffs_json__error__unsupported_number_length[] = "#json: unsupported number length"; +const char wuffs_json__error__unsupported_recursion_depth[] = "#json: unsupported recursion depth"; +const char wuffs_json__error__internal_error_inconsistent_i_o[] = "#json: internal error: inconsistent I/O"; + +// ---------------- Private Consts + +#define WUFFS_JSON__DECODER_NUMBER_LENGTH_MAX_INCL 99 + +static const uint8_t +WUFFS_JSON__LUT_BACKSLASHES[256] WUFFS_BASE__POTENTIALLY_UNUSED = { + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 3, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 162, 0, 0, 0, 0, 5, + 0, 0, 0, 0, 0, 0, 0, 175, + 7, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 4, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 220, 0, 0, 0, + 0, 1, 136, 0, 0, 2, 140, 0, + 0, 0, 0, 0, 0, 0, 138, 0, + 0, 0, 141, 0, 137, 0, 6, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, +}; + +static const uint8_t +WUFFS_JSON__LUT_QUIRKY_BACKSLASHES_QUIRKS[8] WUFFS_BASE__POTENTIALLY_UNUSED = { + 0, 1, 3, 4, 5, 6, 7, 10, +}; + +static const uint8_t +WUFFS_JSON__LUT_QUIRKY_BACKSLASHES_CHARS[8] WUFFS_BASE__POTENTIALLY_UNUSED = { + 0, 7, 27, 10, 63, 39, 11, 0, +}; + +static const uint8_t +WUFFS_JSON__LUT_CHARS[256] WUFFS_BASE__POTENTIALLY_UNUSED = { + 128, 129, 130, 131, 132, 133, 134, 135, + 136, 137, 138, 139, 140, 141, 142, 143, + 144, 145, 146, 147, 148, 149, 150, 151, + 152, 153, 154, 155, 156, 157, 158, 159, + 0, 0, 1, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 2, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 16, 16, 16, 16, 16, 16, 16, 16, + 16, 16, 16, 16, 16, 16, 16, 16, + 16, 16, 16, 16, 16, 16, 16, 16, + 16, 16, 16, 16, 16, 16, 16, 16, + 16, 16, 16, 16, 16, 16, 16, 16, + 16, 16, 16, 16, 16, 16, 16, 16, + 16, 16, 16, 16, 16, 16, 16, 16, + 16, 16, 16, 16, 16, 16, 16, 16, + 32, 32, 3, 3, 3, 3, 3, 3, + 3, 3, 3, 3, 3, 3, 3, 3, + 3, 3, 3, 3, 3, 3, 3, 3, + 3, 3, 3, 3, 3, 3, 3, 3, + 4, 4, 4, 4, 4, 4, 4, 4, + 4, 4, 4, 4, 4, 4, 4, 4, + 5, 5, 5, 5, 5, 32, 32, 32, + 32, 32, 32, 32, 32, 32, 32, 32, +}; + +#define WUFFS_JSON__CLASS_WHITESPACE 0 + +#define WUFFS_JSON__CLASS_STRING 1 + +#define WUFFS_JSON__CLASS_COMMA 2 + +#define WUFFS_JSON__CLASS_COLON 3 + +#define WUFFS_JSON__CLASS_NUMBER 4 + +#define WUFFS_JSON__CLASS_OPEN_CURLY_BRACE 5 + +#define WUFFS_JSON__CLASS_CLOSE_CURLY_BRACE 6 + +#define WUFFS_JSON__CLASS_OPEN_SQUARE_BRACKET 7 + +#define WUFFS_JSON__CLASS_CLOSE_SQUARE_BRACKET 8 + +#define WUFFS_JSON__CLASS_FALSE 9 + +#define WUFFS_JSON__CLASS_TRUE 10 + +#define WUFFS_JSON__CLASS_NULL_NAN_INF 11 + +#define WUFFS_JSON__CLASS_COMMENT 12 + +#define WUFFS_JSON__EXPECT_VALUE 7858 + +#define WUFFS_JSON__EXPECT_NON_STRING_VALUE 7856 + +#define WUFFS_JSON__EXPECT_STRING 4098 + +#define WUFFS_JSON__EXPECT_COMMA 4100 + +#define WUFFS_JSON__EXPECT_COLON 4104 + +#define WUFFS_JSON__EXPECT_NUMBER 4112 + +#define WUFFS_JSON__EXPECT_CLOSE_CURLY_BRACE 4160 + +#define WUFFS_JSON__EXPECT_CLOSE_SQUARE_BRACKET 4352 + +static const uint8_t +WUFFS_JSON__LUT_CLASSES[256] WUFFS_BASE__POTENTIALLY_UNUSED = { + 15, 15, 15, 15, 15, 15, 15, 15, + 15, 0, 0, 15, 15, 0, 15, 15, + 15, 15, 15, 15, 15, 15, 15, 15, + 15, 15, 15, 15, 15, 15, 15, 15, + 0, 15, 1, 15, 15, 15, 15, 15, + 15, 15, 15, 11, 2, 4, 15, 12, + 4, 4, 4, 4, 4, 4, 4, 4, + 4, 4, 3, 15, 15, 15, 15, 15, + 15, 15, 15, 15, 15, 15, 15, 15, + 15, 11, 15, 15, 15, 15, 11, 15, + 15, 15, 15, 15, 15, 15, 15, 15, + 15, 15, 15, 7, 15, 8, 15, 15, + 15, 15, 15, 15, 15, 15, 9, 15, + 15, 11, 15, 15, 15, 15, 11, 15, + 15, 15, 15, 15, 10, 15, 15, 15, + 15, 15, 15, 5, 15, 6, 15, 15, + 15, 15, 15, 15, 15, 15, 15, 15, + 15, 15, 15, 15, 15, 15, 15, 15, + 15, 15, 15, 15, 15, 15, 15, 15, + 15, 15, 15, 15, 15, 15, 15, 15, + 15, 15, 15, 15, 15, 15, 15, 15, + 15, 15, 15, 15, 15, 15, 15, 15, + 15, 15, 15, 15, 15, 15, 15, 15, + 15, 15, 15, 15, 15, 15, 15, 15, + 15, 15, 15, 15, 15, 15, 15, 15, + 15, 15, 15, 15, 15, 15, 15, 15, + 15, 15, 15, 15, 15, 15, 15, 15, + 15, 15, 15, 15, 15, 15, 15, 15, + 15, 15, 15, 15, 15, 15, 15, 15, + 15, 15, 15, 15, 15, 15, 15, 15, + 15, 15, 15, 15, 15, 15, 15, 15, + 15, 15, 15, 15, 15, 15, 15, 15, +}; + +static const uint8_t +WUFFS_JSON__LUT_DECIMAL_DIGITS[256] WUFFS_BASE__POTENTIALLY_UNUSED = { + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 128, 129, 130, 131, 132, 133, 134, 135, + 136, 137, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, +}; + +static const uint8_t +WUFFS_JSON__LUT_HEXADECIMAL_DIGITS[256] WUFFS_BASE__POTENTIALLY_UNUSED = { + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 128, 129, 130, 131, 132, 133, 134, 135, + 136, 137, 0, 0, 0, 0, 0, 0, + 0, 138, 139, 140, 141, 142, 143, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 138, 139, 140, 141, 142, 143, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, +}; + +#define WUFFS_JSON__QUIRKS_BASE 1225364480 + +#define WUFFS_JSON__QUIRKS_COUNT 21 + +// ---------------- Private Initializer Prototypes + +// ---------------- Private Function Prototypes + +static uint32_t +wuffs_json__decoder__decode_number( + wuffs_json__decoder* self, + wuffs_base__io_buffer* a_src); + +static uint32_t +wuffs_json__decoder__decode_digits( + wuffs_json__decoder* self, + wuffs_base__io_buffer* a_src, + uint32_t a_n); + +static wuffs_base__status +wuffs_json__decoder__decode_leading( + wuffs_json__decoder* self, + wuffs_base__token_buffer* a_dst, + wuffs_base__io_buffer* a_src); + +static wuffs_base__status +wuffs_json__decoder__decode_comment( + wuffs_json__decoder* self, + wuffs_base__token_buffer* a_dst, + wuffs_base__io_buffer* a_src); + +static wuffs_base__status +wuffs_json__decoder__decode_inf_nan( + wuffs_json__decoder* self, + wuffs_base__token_buffer* a_dst, + wuffs_base__io_buffer* a_src); + +static wuffs_base__status +wuffs_json__decoder__decode_trailer( + wuffs_json__decoder* self, + wuffs_base__token_buffer* a_dst, + wuffs_base__io_buffer* a_src); + +// ---------------- VTables + +const wuffs_base__token_decoder__func_ptrs +wuffs_json__decoder__func_ptrs_for__wuffs_base__token_decoder = { + (wuffs_base__status(*)(void*, + wuffs_base__token_buffer*, + wuffs_base__io_buffer*, + wuffs_base__slice_u8))(&wuffs_json__decoder__decode_tokens), + (wuffs_base__empty_struct(*)(void*, + uint32_t, + bool))(&wuffs_json__decoder__set_quirk_enabled), + (wuffs_base__range_ii_u64(*)(const void*))(&wuffs_json__decoder__workbuf_len), +}; + +// ---------------- Initializer Implementations + +wuffs_base__status WUFFS_BASE__WARN_UNUSED_RESULT +wuffs_json__decoder__initialize( + wuffs_json__decoder* self, + size_t sizeof_star_self, + uint64_t wuffs_version, + uint32_t options){ + if (!self) { + return wuffs_base__make_status(wuffs_base__error__bad_receiver); + } + if (sizeof(*self) != sizeof_star_self) { + return wuffs_base__make_status(wuffs_base__error__bad_sizeof_receiver); + } + if (((wuffs_version >> 32) != WUFFS_VERSION_MAJOR) || + (((wuffs_version >> 16) & 0xFFFF) > WUFFS_VERSION_MINOR)) { + return wuffs_base__make_status(wuffs_base__error__bad_wuffs_version); + } + + if ((options & WUFFS_INITIALIZE__ALREADY_ZEROED) != 0) { + // The whole point of this if-check is to detect an uninitialized *self. + // We disable the warning on GCC. Clang-5.0 does not have this warning. +#if !defined(__clang__) && defined(__GNUC__) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wmaybe-uninitialized" +#endif + if (self->private_impl.magic != 0) { + return wuffs_base__make_status(wuffs_base__error__initialize_falsely_claimed_already_zeroed); + } +#if !defined(__clang__) && defined(__GNUC__) +#pragma GCC diagnostic pop +#endif + } else { + if ((options & WUFFS_INITIALIZE__LEAVE_INTERNAL_BUFFERS_UNINITIALIZED) == 0) { + memset(self, 0, sizeof(*self)); + options |= WUFFS_INITIALIZE__ALREADY_ZEROED; + } else { + memset(&(self->private_impl), 0, sizeof(self->private_impl)); + } + } + + self->private_impl.magic = WUFFS_BASE__MAGIC; + self->private_impl.vtable_for__wuffs_base__token_decoder.vtable_name = + wuffs_base__token_decoder__vtable_name; + self->private_impl.vtable_for__wuffs_base__token_decoder.function_pointers = + (const void*)(&wuffs_json__decoder__func_ptrs_for__wuffs_base__token_decoder); + return wuffs_base__make_status(NULL); +} + +wuffs_json__decoder* +wuffs_json__decoder__alloc() { + wuffs_json__decoder* x = + (wuffs_json__decoder*)(calloc(sizeof(wuffs_json__decoder), 1)); + if (!x) { + return NULL; + } + if (wuffs_json__decoder__initialize( + x, sizeof(wuffs_json__decoder), WUFFS_VERSION, WUFFS_INITIALIZE__ALREADY_ZEROED).repr) { + free(x); + return NULL; + } + return x; +} + +size_t +sizeof__wuffs_json__decoder() { + return sizeof(wuffs_json__decoder); +} + +// ---------------- Function Implementations + +// -------- func json.decoder.set_quirk_enabled + +WUFFS_BASE__MAYBE_STATIC wuffs_base__empty_struct +wuffs_json__decoder__set_quirk_enabled( + wuffs_json__decoder* self, + uint32_t a_quirk, + bool a_enabled) { + if (!self) { + return wuffs_base__make_empty_struct(); + } + if (self->private_impl.magic != WUFFS_BASE__MAGIC) { + return wuffs_base__make_empty_struct(); + } + + if (a_quirk >= 1225364480) { + a_quirk -= 1225364480; + if (a_quirk < 21) { + self->private_impl.f_quirks[a_quirk] = a_enabled; + } + } + return wuffs_base__make_empty_struct(); +} + +// -------- func json.decoder.workbuf_len + +WUFFS_BASE__MAYBE_STATIC wuffs_base__range_ii_u64 +wuffs_json__decoder__workbuf_len( + const wuffs_json__decoder* self) { + if (!self) { + return wuffs_base__utility__empty_range_ii_u64(); + } + if ((self->private_impl.magic != WUFFS_BASE__MAGIC) && + (self->private_impl.magic != WUFFS_BASE__DISABLED)) { + return wuffs_base__utility__empty_range_ii_u64(); + } + + return wuffs_base__utility__empty_range_ii_u64(); +} + +// -------- func json.decoder.decode_tokens + +WUFFS_BASE__MAYBE_STATIC wuffs_base__status +wuffs_json__decoder__decode_tokens( + wuffs_json__decoder* self, + wuffs_base__token_buffer* a_dst, + wuffs_base__io_buffer* a_src, + wuffs_base__slice_u8 a_workbuf) { + if (!self) { + return wuffs_base__make_status(wuffs_base__error__bad_receiver); + } + if (self->private_impl.magic != WUFFS_BASE__MAGIC) { + return wuffs_base__make_status( + (self->private_impl.magic == WUFFS_BASE__DISABLED) + ? wuffs_base__error__disabled_by_previous_error + : wuffs_base__error__initialize_not_called); + } + if (!a_dst || !a_src) { + self->private_impl.magic = WUFFS_BASE__DISABLED; + return wuffs_base__make_status(wuffs_base__error__bad_argument); + } + if ((self->private_impl.active_coroutine != 0) && + (self->private_impl.active_coroutine != 1)) { + self->private_impl.magic = WUFFS_BASE__DISABLED; + return wuffs_base__make_status(wuffs_base__error__interleaved_coroutine_calls); + } + self->private_impl.active_coroutine = 0; + wuffs_base__status status = wuffs_base__make_status(NULL); + + uint32_t v_vminor = 0; + uint32_t v_number_length = 0; + uint32_t v_number_status = 0; + uint32_t v_string_length = 0; + uint32_t v_whitespace_length = 0; + uint32_t v_depth = 0; + uint32_t v_stack_byte = 0; + uint32_t v_stack_bit = 0; + uint32_t v_match = 0; + uint32_t v_c4 = 0; + uint8_t v_c = 0; + uint8_t v_backslash = 0; + uint8_t v_char = 0; + uint8_t v_class = 0; + uint32_t v_multi_byte_utf8 = 0; + uint8_t v_backslash_x_ok = 0; + uint8_t v_backslash_x_value = 0; + uint32_t v_backslash_x_string = 0; + uint8_t v_uni4_ok = 0; + uint64_t v_uni4_string = 0; + uint32_t v_uni4_value = 0; + uint32_t v_uni4_high_surrogate = 0; + uint8_t v_uni8_ok = 0; + uint64_t v_uni8_string = 0; + uint32_t v_uni8_value = 0; + uint32_t v_expect = 0; + uint32_t v_expect_after_value = 0; + + wuffs_base__token* iop_a_dst = NULL; + wuffs_base__token* io0_a_dst WUFFS_BASE__POTENTIALLY_UNUSED = NULL; + wuffs_base__token* io1_a_dst WUFFS_BASE__POTENTIALLY_UNUSED = NULL; + wuffs_base__token* io2_a_dst WUFFS_BASE__POTENTIALLY_UNUSED = NULL; + if (a_dst && a_dst->data.ptr) { + io0_a_dst = a_dst->data.ptr; + io1_a_dst = io0_a_dst + a_dst->meta.wi; + iop_a_dst = io1_a_dst; + io2_a_dst = io0_a_dst + a_dst->data.len; + if (a_dst->meta.closed) { + io2_a_dst = iop_a_dst; + } + } + const uint8_t* iop_a_src = NULL; + const uint8_t* io0_a_src WUFFS_BASE__POTENTIALLY_UNUSED = NULL; + const uint8_t* io1_a_src WUFFS_BASE__POTENTIALLY_UNUSED = NULL; + const uint8_t* io2_a_src WUFFS_BASE__POTENTIALLY_UNUSED = NULL; + if (a_src && a_src->data.ptr) { + io0_a_src = a_src->data.ptr; + io1_a_src = io0_a_src + a_src->meta.ri; + iop_a_src = io1_a_src; + io2_a_src = io0_a_src + a_src->meta.wi; + } + + uint32_t coro_susp_point = self->private_impl.p_decode_tokens[0]; + if (coro_susp_point) { + v_depth = self->private_data.s_decode_tokens[0].v_depth; + v_expect = self->private_data.s_decode_tokens[0].v_expect; + v_expect_after_value = self->private_data.s_decode_tokens[0].v_expect_after_value; + } + switch (coro_susp_point) { + WUFFS_BASE__COROUTINE_SUSPENSION_POINT_0; + + if (self->private_impl.f_end_of_data) { + status = wuffs_base__make_status(wuffs_base__note__end_of_data); + goto ok; + } + if (self->private_impl.f_quirks[18]) { + if (self->private_impl.f_quirks[11] || self->private_impl.f_quirks[12] || self->private_impl.f_quirks[17]) { + status = wuffs_base__make_status(wuffs_json__error__bad_quirk_combination); + goto exit; + } + } + if (self->private_impl.f_quirks[15] || self->private_impl.f_quirks[16]) { + if (a_dst) { + a_dst->meta.wi = ((size_t)(iop_a_dst - a_dst->data.ptr)); + } + if (a_src) { + a_src->meta.ri = ((size_t)(iop_a_src - a_src->data.ptr)); + } + WUFFS_BASE__COROUTINE_SUSPENSION_POINT(1); + status = wuffs_json__decoder__decode_leading(self, a_dst, a_src); + if (a_dst) { + iop_a_dst = a_dst->data.ptr + a_dst->meta.wi; + } + if (a_src) { + iop_a_src = a_src->data.ptr + a_src->meta.ri; + } + if (status.repr) { + goto suspend; + } + } + v_expect = 7858; + label__outer__continue:; + while (true) { + while (true) { + if (((uint64_t)(io2_a_dst - iop_a_dst)) <= 0) { + status = wuffs_base__make_status(wuffs_base__suspension__short_write); + WUFFS_BASE__COROUTINE_SUSPENSION_POINT_MAYBE_SUSPEND(2); + goto label__outer__continue; + } + v_whitespace_length = 0; + v_c = 0; + v_class = 0; + while (true) { + if (((uint64_t)(io2_a_src - iop_a_src)) <= 0) { + if (v_whitespace_length > 0) { + *iop_a_dst++ = wuffs_base__make_token( + (((uint64_t)(0)) << WUFFS_BASE__TOKEN__VALUE_MINOR__SHIFT) | + (((uint64_t)(v_whitespace_length)) << WUFFS_BASE__TOKEN__LENGTH__SHIFT)); + v_whitespace_length = 0; + } + if (a_src && a_src->meta.closed) { + status = wuffs_base__make_status(wuffs_json__error__bad_input); + goto exit; + } + status = wuffs_base__make_status(wuffs_base__suspension__short_read); + WUFFS_BASE__COROUTINE_SUSPENSION_POINT_MAYBE_SUSPEND(3); + goto label__outer__continue; + } + v_c = wuffs_base__peek_u8be__no_bounds_check(iop_a_src); + v_class = WUFFS_JSON__LUT_CLASSES[v_c]; + if (v_class != 0) { + goto label__ws__break; + } + iop_a_src += 1; + if (v_whitespace_length >= 65534) { + *iop_a_dst++ = wuffs_base__make_token( + (((uint64_t)(0)) << WUFFS_BASE__TOKEN__VALUE_MINOR__SHIFT) | + (((uint64_t)(65535)) << WUFFS_BASE__TOKEN__LENGTH__SHIFT)); + v_whitespace_length = 0; + goto label__outer__continue; + } + v_whitespace_length += 1; + } + label__ws__break:; + if (v_whitespace_length > 0) { + *iop_a_dst++ = wuffs_base__make_token( + (((uint64_t)(0)) << WUFFS_BASE__TOKEN__VALUE_MINOR__SHIFT) | + (((uint64_t)(v_whitespace_length)) << WUFFS_BASE__TOKEN__LENGTH__SHIFT)); + v_whitespace_length = 0; + if (((uint64_t)(io2_a_dst - iop_a_dst)) <= 0) { + goto label__outer__continue; + } + } + if (0 == (v_expect & (((uint32_t)(1)) << v_class))) { + status = wuffs_base__make_status(wuffs_json__error__bad_input); + goto exit; + } + if (v_class == 1) { + *iop_a_dst++ = wuffs_base__make_token( + (((uint64_t)(4194579)) << WUFFS_BASE__TOKEN__VALUE_MINOR__SHIFT) | + (((uint64_t)(1)) << WUFFS_BASE__TOKEN__CONTINUED__SHIFT) | + (((uint64_t)(1)) << WUFFS_BASE__TOKEN__LENGTH__SHIFT)); + iop_a_src += 1; + label__string_loop_outer__continue:; + while (true) { + if (((uint64_t)(io2_a_dst - iop_a_dst)) <= 0) { + status = wuffs_base__make_status(wuffs_base__suspension__short_write); + WUFFS_BASE__COROUTINE_SUSPENSION_POINT_MAYBE_SUSPEND(4); + goto label__string_loop_outer__continue; + } + v_string_length = 0; + label__string_loop_inner__continue:; + while (true) { + if (((uint64_t)(io2_a_src - iop_a_src)) <= 0) { + if (v_string_length > 0) { + *iop_a_dst++ = wuffs_base__make_token( + (((uint64_t)(4194819)) << WUFFS_BASE__TOKEN__VALUE_MINOR__SHIFT) | + (((uint64_t)(1)) << WUFFS_BASE__TOKEN__CONTINUED__SHIFT) | + (((uint64_t)(v_string_length)) << WUFFS_BASE__TOKEN__LENGTH__SHIFT)); + v_string_length = 0; + } + if (a_src && a_src->meta.closed) { + status = wuffs_base__make_status(wuffs_json__error__bad_input); + goto exit; + } + status = wuffs_base__make_status(wuffs_base__suspension__short_read); + WUFFS_BASE__COROUTINE_SUSPENSION_POINT_MAYBE_SUSPEND(5); + goto label__string_loop_outer__continue; + } + while (((uint64_t)(io2_a_src - iop_a_src)) > 4) { + v_c4 = wuffs_base__peek_u32le__no_bounds_check(iop_a_src); + if (0 != (WUFFS_JSON__LUT_CHARS[(255 & (v_c4 >> 0))] | + WUFFS_JSON__LUT_CHARS[(255 & (v_c4 >> 8))] | + WUFFS_JSON__LUT_CHARS[(255 & (v_c4 >> 16))] | + WUFFS_JSON__LUT_CHARS[(255 & (v_c4 >> 24))])) { + goto label__0__break; + } + iop_a_src += 4; + if (v_string_length > 65527) { + *iop_a_dst++ = wuffs_base__make_token( + (((uint64_t)(4194819)) << WUFFS_BASE__TOKEN__VALUE_MINOR__SHIFT) | + (((uint64_t)(1)) << WUFFS_BASE__TOKEN__CONTINUED__SHIFT) | + (((uint64_t)((v_string_length + 4))) << WUFFS_BASE__TOKEN__LENGTH__SHIFT)); + v_string_length = 0; + goto label__string_loop_outer__continue; + } + v_string_length += 4; + } + label__0__break:; + v_c = wuffs_base__peek_u8be__no_bounds_check(iop_a_src); + v_char = WUFFS_JSON__LUT_CHARS[v_c]; + if (v_char == 0) { + iop_a_src += 1; + if (v_string_length >= 65531) { + *iop_a_dst++ = wuffs_base__make_token( + (((uint64_t)(4194819)) << WUFFS_BASE__TOKEN__VALUE_MINOR__SHIFT) | + (((uint64_t)(1)) << WUFFS_BASE__TOKEN__CONTINUED__SHIFT) | + (((uint64_t)(65532)) << WUFFS_BASE__TOKEN__LENGTH__SHIFT)); + v_string_length = 0; + goto label__string_loop_outer__continue; + } + v_string_length += 1; + goto label__string_loop_inner__continue; + } else if (v_char == 1) { + if (v_string_length != 0) { + *iop_a_dst++ = wuffs_base__make_token( + (((uint64_t)(4194819)) << WUFFS_BASE__TOKEN__VALUE_MINOR__SHIFT) | + (((uint64_t)(1)) << WUFFS_BASE__TOKEN__CONTINUED__SHIFT) | + (((uint64_t)(v_string_length)) << WUFFS_BASE__TOKEN__LENGTH__SHIFT)); + v_string_length = 0; + } + goto label__string_loop_outer__break; + } else if (v_char == 2) { + if (v_string_length > 0) { + *iop_a_dst++ = wuffs_base__make_token( + (((uint64_t)(4194819)) << WUFFS_BASE__TOKEN__VALUE_MINOR__SHIFT) | + (((uint64_t)(1)) << WUFFS_BASE__TOKEN__CONTINUED__SHIFT) | + (((uint64_t)(v_string_length)) << WUFFS_BASE__TOKEN__LENGTH__SHIFT)); + v_string_length = 0; + if (((uint64_t)(io2_a_dst - iop_a_dst)) <= 0) { + goto label__string_loop_outer__continue; + } + } + if (((uint64_t)(io2_a_src - iop_a_src)) < 2) { + if (a_src && a_src->meta.closed) { + status = wuffs_base__make_status(wuffs_json__error__bad_backslash_escape); + goto exit; + } + status = wuffs_base__make_status(wuffs_base__suspension__short_read); + WUFFS_BASE__COROUTINE_SUSPENSION_POINT_MAYBE_SUSPEND(6); + goto label__string_loop_outer__continue; + } + v_c = ((uint8_t)((wuffs_base__peek_u16le__no_bounds_check(iop_a_src) >> 8))); + v_backslash = WUFFS_JSON__LUT_BACKSLASHES[v_c]; + if ((v_backslash & 128) != 0) { + iop_a_src += 2; + *iop_a_dst++ = wuffs_base__make_token( + (((uint64_t)((6291456 | ((uint32_t)((v_backslash & 127)))))) << WUFFS_BASE__TOKEN__VALUE_MINOR__SHIFT) | + (((uint64_t)(1)) << WUFFS_BASE__TOKEN__CONTINUED__SHIFT) | + (((uint64_t)(2)) << WUFFS_BASE__TOKEN__LENGTH__SHIFT)); + goto label__string_loop_outer__continue; + } else if (v_backslash != 0) { + if (self->private_impl.f_quirks[WUFFS_JSON__LUT_QUIRKY_BACKSLASHES_QUIRKS[(v_backslash & 7)]]) { + iop_a_src += 2; + *iop_a_dst++ = wuffs_base__make_token( + (((uint64_t)((6291456 | ((uint32_t)(WUFFS_JSON__LUT_QUIRKY_BACKSLASHES_CHARS[(v_backslash & 7)]))))) << WUFFS_BASE__TOKEN__VALUE_MINOR__SHIFT) | + (((uint64_t)(1)) << WUFFS_BASE__TOKEN__CONTINUED__SHIFT) | + (((uint64_t)(2)) << WUFFS_BASE__TOKEN__LENGTH__SHIFT)); + goto label__string_loop_outer__continue; + } + } else if (v_c == 117) { + if (((uint64_t)(io2_a_src - iop_a_src)) < 6) { + if (a_src && a_src->meta.closed) { + status = wuffs_base__make_status(wuffs_json__error__bad_backslash_escape); + goto exit; + } + status = wuffs_base__make_status(wuffs_base__suspension__short_read); + WUFFS_BASE__COROUTINE_SUSPENSION_POINT_MAYBE_SUSPEND(7); + goto label__string_loop_outer__continue; + } + v_uni4_string = (((uint64_t)(wuffs_base__peek_u48le__no_bounds_check(iop_a_src))) >> 16); + v_uni4_value = 0; + v_uni4_ok = 128; + v_c = WUFFS_JSON__LUT_HEXADECIMAL_DIGITS[(255 & (v_uni4_string >> 0))]; + v_uni4_ok &= v_c; + v_uni4_value |= (((uint32_t)((v_c & 15))) << 12); + v_c = WUFFS_JSON__LUT_HEXADECIMAL_DIGITS[(255 & (v_uni4_string >> 8))]; + v_uni4_ok &= v_c; + v_uni4_value |= (((uint32_t)((v_c & 15))) << 8); + v_c = WUFFS_JSON__LUT_HEXADECIMAL_DIGITS[(255 & (v_uni4_string >> 16))]; + v_uni4_ok &= v_c; + v_uni4_value |= (((uint32_t)((v_c & 15))) << 4); + v_c = WUFFS_JSON__LUT_HEXADECIMAL_DIGITS[(255 & (v_uni4_string >> 24))]; + v_uni4_ok &= v_c; + v_uni4_value |= (((uint32_t)((v_c & 15))) << 0); + if (v_uni4_ok == 0) { + } else if ((v_uni4_value < 55296) || (57343 < v_uni4_value)) { + iop_a_src += 6; + *iop_a_dst++ = wuffs_base__make_token( + (((uint64_t)((6291456 | v_uni4_value))) << WUFFS_BASE__TOKEN__VALUE_MINOR__SHIFT) | + (((uint64_t)(1)) << WUFFS_BASE__TOKEN__CONTINUED__SHIFT) | + (((uint64_t)(6)) << WUFFS_BASE__TOKEN__LENGTH__SHIFT)); + goto label__string_loop_outer__continue; + } else if (v_uni4_value >= 56320) { + } else { + if (((uint64_t)(io2_a_src - iop_a_src)) < 12) { + if (a_src && a_src->meta.closed) { + if (self->private_impl.f_quirks[20]) { + iop_a_src += 6; + *iop_a_dst++ = wuffs_base__make_token( + (((uint64_t)(6356989)) << WUFFS_BASE__TOKEN__VALUE_MINOR__SHIFT) | + (((uint64_t)(1)) << WUFFS_BASE__TOKEN__CONTINUED__SHIFT) | + (((uint64_t)(6)) << WUFFS_BASE__TOKEN__LENGTH__SHIFT)); + goto label__string_loop_outer__continue; + } + status = wuffs_base__make_status(wuffs_json__error__bad_backslash_escape); + goto exit; + } + status = wuffs_base__make_status(wuffs_base__suspension__short_read); + WUFFS_BASE__COROUTINE_SUSPENSION_POINT_MAYBE_SUSPEND(8); + goto label__string_loop_outer__continue; + } + v_uni4_string = (wuffs_base__peek_u64le__no_bounds_check(iop_a_src + 4) >> 16); + if (((255 & (v_uni4_string >> 0)) != 92) || ((255 & (v_uni4_string >> 8)) != 117)) { + v_uni4_high_surrogate = 0; + v_uni4_value = 0; + v_uni4_ok = 0; + } else { + v_uni4_high_surrogate = (65536 + ((v_uni4_value - 55296) << 10)); + v_uni4_value = 0; + v_uni4_ok = 128; + v_uni4_string >>= 16; + v_c = WUFFS_JSON__LUT_HEXADECIMAL_DIGITS[(255 & (v_uni4_string >> 0))]; + v_uni4_ok &= v_c; + v_uni4_value |= (((uint32_t)((v_c & 15))) << 12); + v_c = WUFFS_JSON__LUT_HEXADECIMAL_DIGITS[(255 & (v_uni4_string >> 8))]; + v_uni4_ok &= v_c; + v_uni4_value |= (((uint32_t)((v_c & 15))) << 8); + v_c = WUFFS_JSON__LUT_HEXADECIMAL_DIGITS[(255 & (v_uni4_string >> 16))]; + v_uni4_ok &= v_c; + v_uni4_value |= (((uint32_t)((v_c & 15))) << 4); + v_c = WUFFS_JSON__LUT_HEXADECIMAL_DIGITS[(255 & (v_uni4_string >> 24))]; + v_uni4_ok &= v_c; + v_uni4_value |= (((uint32_t)((v_c & 15))) << 0); + } + if ((v_uni4_ok != 0) && (56320 <= v_uni4_value) && (v_uni4_value <= 57343)) { + v_uni4_value -= 56320; + iop_a_src += 12; + *iop_a_dst++ = wuffs_base__make_token( + (((uint64_t)((6291456 | v_uni4_high_surrogate | v_uni4_value))) << WUFFS_BASE__TOKEN__VALUE_MINOR__SHIFT) | + (((uint64_t)(1)) << WUFFS_BASE__TOKEN__CONTINUED__SHIFT) | + (((uint64_t)(12)) << WUFFS_BASE__TOKEN__LENGTH__SHIFT)); + goto label__string_loop_outer__continue; + } + } + if (self->private_impl.f_quirks[20]) { + if (((uint64_t)(io2_a_src - iop_a_src)) < 6) { + status = wuffs_base__make_status(wuffs_json__error__internal_error_inconsistent_i_o); + goto exit; + } + iop_a_src += 6; + *iop_a_dst++ = wuffs_base__make_token( + (((uint64_t)(6356989)) << WUFFS_BASE__TOKEN__VALUE_MINOR__SHIFT) | + (((uint64_t)(1)) << WUFFS_BASE__TOKEN__CONTINUED__SHIFT) | + (((uint64_t)(6)) << WUFFS_BASE__TOKEN__LENGTH__SHIFT)); + goto label__string_loop_outer__continue; + } + } else if ((v_c == 85) && self->private_impl.f_quirks[2]) { + if (((uint64_t)(io2_a_src - iop_a_src)) < 10) { + if (a_src && a_src->meta.closed) { + status = wuffs_base__make_status(wuffs_json__error__bad_backslash_escape); + goto exit; + } + status = wuffs_base__make_status(wuffs_base__suspension__short_read); + WUFFS_BASE__COROUTINE_SUSPENSION_POINT_MAYBE_SUSPEND(9); + goto label__string_loop_outer__continue; + } + v_uni8_string = wuffs_base__peek_u64le__no_bounds_check(iop_a_src + 2); + v_uni8_value = 0; + v_uni8_ok = 128; + v_c = WUFFS_JSON__LUT_HEXADECIMAL_DIGITS[(255 & (v_uni8_string >> 0))]; + v_uni8_ok &= v_c; + v_uni8_value |= (((uint32_t)((v_c & 15))) << 28); + v_c = WUFFS_JSON__LUT_HEXADECIMAL_DIGITS[(255 & (v_uni8_string >> 8))]; + v_uni8_ok &= v_c; + v_uni8_value |= (((uint32_t)((v_c & 15))) << 24); + v_c = WUFFS_JSON__LUT_HEXADECIMAL_DIGITS[(255 & (v_uni8_string >> 16))]; + v_uni8_ok &= v_c; + v_uni8_value |= (((uint32_t)((v_c & 15))) << 20); + v_c = WUFFS_JSON__LUT_HEXADECIMAL_DIGITS[(255 & (v_uni8_string >> 24))]; + v_uni8_ok &= v_c; + v_uni8_value |= (((uint32_t)((v_c & 15))) << 16); + v_c = WUFFS_JSON__LUT_HEXADECIMAL_DIGITS[(255 & (v_uni8_string >> 32))]; + v_uni8_ok &= v_c; + v_uni8_value |= (((uint32_t)((v_c & 15))) << 12); + v_c = WUFFS_JSON__LUT_HEXADECIMAL_DIGITS[(255 & (v_uni8_string >> 40))]; + v_uni8_ok &= v_c; + v_uni8_value |= (((uint32_t)((v_c & 15))) << 8); + v_c = WUFFS_JSON__LUT_HEXADECIMAL_DIGITS[(255 & (v_uni8_string >> 48))]; + v_uni8_ok &= v_c; + v_uni8_value |= (((uint32_t)((v_c & 15))) << 4); + v_c = WUFFS_JSON__LUT_HEXADECIMAL_DIGITS[(255 & (v_uni8_string >> 56))]; + v_uni8_ok &= v_c; + v_uni8_value |= (((uint32_t)((v_c & 15))) << 0); + if (v_uni8_ok == 0) { + } else if ((v_uni8_value < 55296) || ((57343 < v_uni8_value) && (v_uni8_value <= 1114111))) { + iop_a_src += 10; + *iop_a_dst++ = wuffs_base__make_token( + (((uint64_t)((6291456 | (v_uni8_value & 2097151)))) << WUFFS_BASE__TOKEN__VALUE_MINOR__SHIFT) | + (((uint64_t)(1)) << WUFFS_BASE__TOKEN__CONTINUED__SHIFT) | + (((uint64_t)(10)) << WUFFS_BASE__TOKEN__LENGTH__SHIFT)); + goto label__string_loop_outer__continue; + } else if (self->private_impl.f_quirks[20]) { + iop_a_src += 10; + *iop_a_dst++ = wuffs_base__make_token( + (((uint64_t)(6356989)) << WUFFS_BASE__TOKEN__VALUE_MINOR__SHIFT) | + (((uint64_t)(1)) << WUFFS_BASE__TOKEN__CONTINUED__SHIFT) | + (((uint64_t)(10)) << WUFFS_BASE__TOKEN__LENGTH__SHIFT)); + goto label__string_loop_outer__continue; + } + } else if ((v_c == 120) && self->private_impl.f_quirks[9]) { + if (((uint64_t)(io2_a_src - iop_a_src)) < 4) { + if (a_src && a_src->meta.closed) { + status = wuffs_base__make_status(wuffs_json__error__bad_backslash_escape); + goto exit; + } + status = wuffs_base__make_status(wuffs_base__suspension__short_read); + WUFFS_BASE__COROUTINE_SUSPENSION_POINT_MAYBE_SUSPEND(10); + goto label__string_loop_outer__continue; + } + v_backslash_x_string = wuffs_base__peek_u32le__no_bounds_check(iop_a_src); + v_backslash_x_ok = 128; + v_c = WUFFS_JSON__LUT_HEXADECIMAL_DIGITS[(255 & (v_backslash_x_string >> 16))]; + v_backslash_x_ok &= v_c; + v_backslash_x_value = ((uint8_t)(((v_c & 15) << 4))); + v_c = WUFFS_JSON__LUT_HEXADECIMAL_DIGITS[(255 & (v_backslash_x_string >> 24))]; + v_backslash_x_ok &= v_c; + v_backslash_x_value = ((uint8_t)((v_backslash_x_value | (v_c & 15)))); + if ((v_backslash_x_ok == 0) || ((v_backslash_x_string & 65535) != 30812)) { + status = wuffs_base__make_status(wuffs_json__error__bad_backslash_escape); + goto exit; + } + iop_a_src += 4; + *iop_a_dst++ = wuffs_base__make_token( + (((uint64_t)((6291456 | ((uint32_t)(v_backslash_x_value))))) << WUFFS_BASE__TOKEN__VALUE_MINOR__SHIFT) | + (((uint64_t)(1)) << WUFFS_BASE__TOKEN__CONTINUED__SHIFT) | + (((uint64_t)(4)) << WUFFS_BASE__TOKEN__LENGTH__SHIFT)); + goto label__string_loop_outer__continue; + } + status = wuffs_base__make_status(wuffs_json__error__bad_backslash_escape); + goto exit; + } else if (v_char == 3) { + if (((uint64_t)(io2_a_src - iop_a_src)) < 2) { + if (v_string_length > 0) { + *iop_a_dst++ = wuffs_base__make_token( + (((uint64_t)(4194819)) << WUFFS_BASE__TOKEN__VALUE_MINOR__SHIFT) | + (((uint64_t)(1)) << WUFFS_BASE__TOKEN__CONTINUED__SHIFT) | + (((uint64_t)(v_string_length)) << WUFFS_BASE__TOKEN__LENGTH__SHIFT)); + v_string_length = 0; + if (((uint64_t)(io2_a_dst - iop_a_dst)) <= 0) { + goto label__string_loop_outer__continue; + } + } + if (a_src && a_src->meta.closed) { + if (self->private_impl.f_quirks[20]) { + *iop_a_dst++ = wuffs_base__make_token( + (((uint64_t)(6356989)) << WUFFS_BASE__TOKEN__VALUE_MINOR__SHIFT) | + (((uint64_t)(1)) << WUFFS_BASE__TOKEN__CONTINUED__SHIFT) | + (((uint64_t)(1)) << WUFFS_BASE__TOKEN__LENGTH__SHIFT)); + iop_a_src += 1; + goto label__string_loop_outer__continue; + } + status = wuffs_base__make_status(wuffs_json__error__bad_utf_8); + goto exit; + } + status = wuffs_base__make_status(wuffs_base__suspension__short_read); + WUFFS_BASE__COROUTINE_SUSPENSION_POINT_MAYBE_SUSPEND(11); + goto label__string_loop_outer__continue; + } + v_multi_byte_utf8 = ((uint32_t)(wuffs_base__peek_u16le__no_bounds_check(iop_a_src))); + if ((v_multi_byte_utf8 & 49152) == 32768) { + v_multi_byte_utf8 = ((1984 & ((uint32_t)(v_multi_byte_utf8 << 6))) | (63 & (v_multi_byte_utf8 >> 8))); + iop_a_src += 2; + if (v_string_length >= 65528) { + *iop_a_dst++ = wuffs_base__make_token( + (((uint64_t)(4194819)) << WUFFS_BASE__TOKEN__VALUE_MINOR__SHIFT) | + (((uint64_t)(1)) << WUFFS_BASE__TOKEN__CONTINUED__SHIFT) | + (((uint64_t)((v_string_length + 2))) << WUFFS_BASE__TOKEN__LENGTH__SHIFT)); + v_string_length = 0; + goto label__string_loop_outer__continue; + } + v_string_length += 2; + goto label__string_loop_inner__continue; + } + } else if (v_char == 4) { + if (((uint64_t)(io2_a_src - iop_a_src)) < 3) { + if (v_string_length > 0) { + *iop_a_dst++ = wuffs_base__make_token( + (((uint64_t)(4194819)) << WUFFS_BASE__TOKEN__VALUE_MINOR__SHIFT) | + (((uint64_t)(1)) << WUFFS_BASE__TOKEN__CONTINUED__SHIFT) | + (((uint64_t)(v_string_length)) << WUFFS_BASE__TOKEN__LENGTH__SHIFT)); + v_string_length = 0; + if (((uint64_t)(io2_a_dst - iop_a_dst)) <= 0) { + goto label__string_loop_outer__continue; + } + } + if (a_src && a_src->meta.closed) { + if (self->private_impl.f_quirks[20]) { + *iop_a_dst++ = wuffs_base__make_token( + (((uint64_t)(6356989)) << WUFFS_BASE__TOKEN__VALUE_MINOR__SHIFT) | + (((uint64_t)(1)) << WUFFS_BASE__TOKEN__CONTINUED__SHIFT) | + (((uint64_t)(1)) << WUFFS_BASE__TOKEN__LENGTH__SHIFT)); + iop_a_src += 1; + goto label__string_loop_outer__continue; + } + status = wuffs_base__make_status(wuffs_json__error__bad_utf_8); + goto exit; + } + status = wuffs_base__make_status(wuffs_base__suspension__short_read); + WUFFS_BASE__COROUTINE_SUSPENSION_POINT_MAYBE_SUSPEND(12); + goto label__string_loop_outer__continue; + } + v_multi_byte_utf8 = ((uint32_t)(wuffs_base__peek_u24le__no_bounds_check(iop_a_src))); + if ((v_multi_byte_utf8 & 12632064) == 8421376) { + v_multi_byte_utf8 = ((61440 & ((uint32_t)(v_multi_byte_utf8 << 12))) | (4032 & (v_multi_byte_utf8 >> 2)) | (63 & (v_multi_byte_utf8 >> 16))); + if ((2047 < v_multi_byte_utf8) && ((v_multi_byte_utf8 < 55296) || (57343 < v_multi_byte_utf8))) { + iop_a_src += 3; + if (v_string_length >= 65528) { + *iop_a_dst++ = wuffs_base__make_token( + (((uint64_t)(4194819)) << WUFFS_BASE__TOKEN__VALUE_MINOR__SHIFT) | + (((uint64_t)(1)) << WUFFS_BASE__TOKEN__CONTINUED__SHIFT) | + (((uint64_t)((v_string_length + 3))) << WUFFS_BASE__TOKEN__LENGTH__SHIFT)); + v_string_length = 0; + goto label__string_loop_outer__continue; + } + v_string_length += 3; + goto label__string_loop_inner__continue; + } + } + } else if (v_char == 5) { + if (((uint64_t)(io2_a_src - iop_a_src)) < 4) { + if (v_string_length > 0) { + *iop_a_dst++ = wuffs_base__make_token( + (((uint64_t)(4194819)) << WUFFS_BASE__TOKEN__VALUE_MINOR__SHIFT) | + (((uint64_t)(1)) << WUFFS_BASE__TOKEN__CONTINUED__SHIFT) | + (((uint64_t)(v_string_length)) << WUFFS_BASE__TOKEN__LENGTH__SHIFT)); + v_string_length = 0; + if (((uint64_t)(io2_a_dst - iop_a_dst)) <= 0) { + goto label__string_loop_outer__continue; + } + } + if (a_src && a_src->meta.closed) { + if (self->private_impl.f_quirks[20]) { + *iop_a_dst++ = wuffs_base__make_token( + (((uint64_t)(6356989)) << WUFFS_BASE__TOKEN__VALUE_MINOR__SHIFT) | + (((uint64_t)(1)) << WUFFS_BASE__TOKEN__CONTINUED__SHIFT) | + (((uint64_t)(1)) << WUFFS_BASE__TOKEN__LENGTH__SHIFT)); + iop_a_src += 1; + goto label__string_loop_outer__continue; + } + status = wuffs_base__make_status(wuffs_json__error__bad_utf_8); + goto exit; + } + status = wuffs_base__make_status(wuffs_base__suspension__short_read); + WUFFS_BASE__COROUTINE_SUSPENSION_POINT_MAYBE_SUSPEND(13); + goto label__string_loop_outer__continue; + } + v_multi_byte_utf8 = wuffs_base__peek_u32le__no_bounds_check(iop_a_src); + if ((v_multi_byte_utf8 & 3233857536) == 2155905024) { + v_multi_byte_utf8 = ((1835008 & ((uint32_t)(v_multi_byte_utf8 << 18))) | + (258048 & ((uint32_t)(v_multi_byte_utf8 << 4))) | + (4032 & (v_multi_byte_utf8 >> 10)) | + (63 & (v_multi_byte_utf8 >> 24))); + if ((65535 < v_multi_byte_utf8) && (v_multi_byte_utf8 <= 1114111)) { + iop_a_src += 4; + if (v_string_length >= 65528) { + *iop_a_dst++ = wuffs_base__make_token( + (((uint64_t)(4194819)) << WUFFS_BASE__TOKEN__VALUE_MINOR__SHIFT) | + (((uint64_t)(1)) << WUFFS_BASE__TOKEN__CONTINUED__SHIFT) | + (((uint64_t)((v_string_length + 4))) << WUFFS_BASE__TOKEN__LENGTH__SHIFT)); + v_string_length = 0; + goto label__string_loop_outer__continue; + } + v_string_length += 4; + goto label__string_loop_inner__continue; + } + } + } + if (v_string_length > 0) { + *iop_a_dst++ = wuffs_base__make_token( + (((uint64_t)(4194819)) << WUFFS_BASE__TOKEN__VALUE_MINOR__SHIFT) | + (((uint64_t)(1)) << WUFFS_BASE__TOKEN__CONTINUED__SHIFT) | + (((uint64_t)(v_string_length)) << WUFFS_BASE__TOKEN__LENGTH__SHIFT)); + v_string_length = 0; + if (((uint64_t)(io2_a_dst - iop_a_dst)) <= 0) { + goto label__string_loop_outer__continue; + } + } + if ((v_char & 128) != 0) { + if (self->private_impl.f_quirks[0]) { + *iop_a_dst++ = wuffs_base__make_token( + (((uint64_t)((6291456 | ((uint32_t)((v_char & 127)))))) << WUFFS_BASE__TOKEN__VALUE_MINOR__SHIFT) | + (((uint64_t)(1)) << WUFFS_BASE__TOKEN__CONTINUED__SHIFT) | + (((uint64_t)(1)) << WUFFS_BASE__TOKEN__LENGTH__SHIFT)); + iop_a_src += 1; + goto label__string_loop_outer__continue; + } + if (v_char == 138) { + status = wuffs_base__make_status(wuffs_json__error__bad_new_line_in_a_string); + goto exit; + } + status = wuffs_base__make_status(wuffs_json__error__bad_c0_control_code); + goto exit; + } + if (self->private_impl.f_quirks[20]) { + *iop_a_dst++ = wuffs_base__make_token( + (((uint64_t)(6356989)) << WUFFS_BASE__TOKEN__VALUE_MINOR__SHIFT) | + (((uint64_t)(1)) << WUFFS_BASE__TOKEN__CONTINUED__SHIFT) | + (((uint64_t)(1)) << WUFFS_BASE__TOKEN__LENGTH__SHIFT)); + iop_a_src += 1; + goto label__string_loop_outer__continue; + } + status = wuffs_base__make_status(wuffs_json__error__bad_utf_8); + goto exit; + } + } + label__string_loop_outer__break:; + label__1__continue:; + while (true) { + if (((uint64_t)(io2_a_src - iop_a_src)) <= 0) { + if (a_src && a_src->meta.closed) { + status = wuffs_base__make_status(wuffs_json__error__bad_input); + goto exit; + } + status = wuffs_base__make_status(wuffs_base__suspension__short_read); + WUFFS_BASE__COROUTINE_SUSPENSION_POINT_MAYBE_SUSPEND(14); + goto label__1__continue; + } + if (((uint64_t)(io2_a_dst - iop_a_dst)) <= 0) { + status = wuffs_base__make_status(wuffs_base__suspension__short_write); + WUFFS_BASE__COROUTINE_SUSPENSION_POINT_MAYBE_SUSPEND(15); + goto label__1__continue; + } + iop_a_src += 1; + *iop_a_dst++ = wuffs_base__make_token( + (((uint64_t)(4194579)) << WUFFS_BASE__TOKEN__VALUE_MINOR__SHIFT) | + (((uint64_t)(1)) << WUFFS_BASE__TOKEN__LENGTH__SHIFT)); + goto label__1__break; + } + label__1__break:; + if (0 == (v_expect & (((uint32_t)(1)) << 4))) { + v_expect = 4104; + goto label__outer__continue; + } + goto label__goto_parsed_a_leaf_value__break; + } else if (v_class == 2) { + iop_a_src += 1; + *iop_a_dst++ = wuffs_base__make_token( + (((uint64_t)(1)) << WUFFS_BASE__TOKEN__VALUE_MINOR__SHIFT) | + (((uint64_t)(1)) << WUFFS_BASE__TOKEN__LENGTH__SHIFT)); + if (0 == (v_expect & (((uint32_t)(1)) << 8))) { + if (self->private_impl.f_quirks[13]) { + v_expect = 4162; + } else { + v_expect = 4098; + } + } else { + if (self->private_impl.f_quirks[13]) { + v_expect = 8114; + } else { + v_expect = 7858; + } + } + goto label__outer__continue; + } else if (v_class == 3) { + iop_a_src += 1; + *iop_a_dst++ = wuffs_base__make_token( + (((uint64_t)(1)) << WUFFS_BASE__TOKEN__VALUE_MINOR__SHIFT) | + (((uint64_t)(1)) << WUFFS_BASE__TOKEN__LENGTH__SHIFT)); + v_expect = 7858; + goto label__outer__continue; + } else if (v_class == 4) { + while (true) { + if (a_src) { + a_src->meta.ri = ((size_t)(iop_a_src - a_src->data.ptr)); + } + v_number_length = wuffs_json__decoder__decode_number(self, a_src); + if (a_src) { + iop_a_src = a_src->data.ptr + a_src->meta.ri; + } + v_number_status = (v_number_length >> 8); + v_vminor = 10486787; + if ((v_number_length & 128) != 0) { + v_vminor = 10486785; + } + v_number_length = (v_number_length & 127); + if (v_number_status == 0) { + *iop_a_dst++ = wuffs_base__make_token( + (((uint64_t)(v_vminor)) << WUFFS_BASE__TOKEN__VALUE_MINOR__SHIFT) | + (((uint64_t)(v_number_length)) << WUFFS_BASE__TOKEN__LENGTH__SHIFT)); + goto label__2__break; + } + while (v_number_length > 0) { + v_number_length -= 1; + if (iop_a_src > io1_a_src) { + iop_a_src--; + } else { + status = wuffs_base__make_status(wuffs_json__error__internal_error_inconsistent_i_o); + goto exit; + } + } + if (v_number_status == 1) { + if (self->private_impl.f_quirks[14]) { + if (a_dst) { + a_dst->meta.wi = ((size_t)(iop_a_dst - a_dst->data.ptr)); + } + if (a_src) { + a_src->meta.ri = ((size_t)(iop_a_src - a_src->data.ptr)); + } + WUFFS_BASE__COROUTINE_SUSPENSION_POINT(16); + status = wuffs_json__decoder__decode_inf_nan(self, a_dst, a_src); + if (a_dst) { + iop_a_dst = a_dst->data.ptr + a_dst->meta.wi; + } + if (a_src) { + iop_a_src = a_src->data.ptr + a_src->meta.ri; + } + if (status.repr) { + goto suspend; + } + goto label__2__break; + } + status = wuffs_base__make_status(wuffs_json__error__bad_input); + goto exit; + } else if (v_number_status == 2) { + status = wuffs_base__make_status(wuffs_json__error__unsupported_number_length); + goto exit; + } else { + status = wuffs_base__make_status(wuffs_base__suspension__short_read); + WUFFS_BASE__COROUTINE_SUSPENSION_POINT_MAYBE_SUSPEND(17); + while (((uint64_t)(io2_a_dst - iop_a_dst)) <= 0) { + status = wuffs_base__make_status(wuffs_base__suspension__short_write); + WUFFS_BASE__COROUTINE_SUSPENSION_POINT_MAYBE_SUSPEND(18); + } + } + } + label__2__break:; + goto label__goto_parsed_a_leaf_value__break; + } else if (v_class == 5) { + v_vminor = 2113553; + if (v_depth == 0) { + } else if (0 != (v_expect_after_value & (((uint32_t)(1)) << 6))) { + v_vminor = 2113601; + } else { + v_vminor = 2113569; + } + if (v_depth >= 1024) { + status = wuffs_base__make_status(wuffs_json__error__unsupported_recursion_depth); + goto exit; + } + v_stack_byte = (v_depth / 32); + v_stack_bit = (v_depth & 31); + self->private_data.f_stack[v_stack_byte] |= (((uint32_t)(1)) << v_stack_bit); + v_depth += 1; + iop_a_src += 1; + *iop_a_dst++ = wuffs_base__make_token( + (((uint64_t)(v_vminor)) << WUFFS_BASE__TOKEN__VALUE_MINOR__SHIFT) | + (((uint64_t)(1)) << WUFFS_BASE__TOKEN__LENGTH__SHIFT)); + v_expect = 4162; + v_expect_after_value = 4164; + goto label__outer__continue; + } else if (v_class == 6) { + iop_a_src += 1; + if (v_depth <= 1) { + *iop_a_dst++ = wuffs_base__make_token( + (((uint64_t)(2101314)) << WUFFS_BASE__TOKEN__VALUE_MINOR__SHIFT) | + (((uint64_t)(1)) << WUFFS_BASE__TOKEN__LENGTH__SHIFT)); + goto label__outer__break; + } + v_depth -= 1; + v_stack_byte = ((v_depth - 1) / 32); + v_stack_bit = ((v_depth - 1) & 31); + if (0 == (self->private_data.f_stack[v_stack_byte] & (((uint32_t)(1)) << v_stack_bit))) { + *iop_a_dst++ = wuffs_base__make_token( + (((uint64_t)(2105410)) << WUFFS_BASE__TOKEN__VALUE_MINOR__SHIFT) | + (((uint64_t)(1)) << WUFFS_BASE__TOKEN__LENGTH__SHIFT)); + v_expect = 4356; + v_expect_after_value = 4356; + } else { + *iop_a_dst++ = wuffs_base__make_token( + (((uint64_t)(2113602)) << WUFFS_BASE__TOKEN__VALUE_MINOR__SHIFT) | + (((uint64_t)(1)) << WUFFS_BASE__TOKEN__LENGTH__SHIFT)); + v_expect = 4164; + v_expect_after_value = 4164; + } + goto label__outer__continue; + } else if (v_class == 7) { + v_vminor = 2105361; + if (v_depth == 0) { + } else if (0 != (v_expect_after_value & (((uint32_t)(1)) << 6))) { + v_vminor = 2105409; + } else { + v_vminor = 2105377; + } + if (v_depth >= 1024) { + status = wuffs_base__make_status(wuffs_json__error__unsupported_recursion_depth); + goto exit; + } + v_stack_byte = (v_depth / 32); + v_stack_bit = (v_depth & 31); + self->private_data.f_stack[v_stack_byte] &= (4294967295 ^ (((uint32_t)(1)) << v_stack_bit)); + v_depth += 1; + iop_a_src += 1; + *iop_a_dst++ = wuffs_base__make_token( + (((uint64_t)(v_vminor)) << WUFFS_BASE__TOKEN__VALUE_MINOR__SHIFT) | + (((uint64_t)(1)) << WUFFS_BASE__TOKEN__LENGTH__SHIFT)); + v_expect = 8114; + v_expect_after_value = 4356; + goto label__outer__continue; + } else if (v_class == 8) { + iop_a_src += 1; + if (v_depth <= 1) { + *iop_a_dst++ = wuffs_base__make_token( + (((uint64_t)(2101282)) << WUFFS_BASE__TOKEN__VALUE_MINOR__SHIFT) | + (((uint64_t)(1)) << WUFFS_BASE__TOKEN__LENGTH__SHIFT)); + goto label__outer__break; + } + v_depth -= 1; + v_stack_byte = ((v_depth - 1) / 32); + v_stack_bit = ((v_depth - 1) & 31); + if (0 == (self->private_data.f_stack[v_stack_byte] & (((uint32_t)(1)) << v_stack_bit))) { + *iop_a_dst++ = wuffs_base__make_token( + (((uint64_t)(2105378)) << WUFFS_BASE__TOKEN__VALUE_MINOR__SHIFT) | + (((uint64_t)(1)) << WUFFS_BASE__TOKEN__LENGTH__SHIFT)); + v_expect = 4356; + v_expect_after_value = 4356; + } else { + *iop_a_dst++ = wuffs_base__make_token( + (((uint64_t)(2113570)) << WUFFS_BASE__TOKEN__VALUE_MINOR__SHIFT) | + (((uint64_t)(1)) << WUFFS_BASE__TOKEN__LENGTH__SHIFT)); + v_expect = 4164; + v_expect_after_value = 4164; + } + goto label__outer__continue; + } else if (v_class == 9) { + v_match = wuffs_base__io_reader__match7(iop_a_src, io2_a_src, a_src,111546413966853); + if (v_match == 0) { + *iop_a_dst++ = wuffs_base__make_token( + (((uint64_t)(8388612)) << WUFFS_BASE__TOKEN__VALUE_MINOR__SHIFT) | + (((uint64_t)(5)) << WUFFS_BASE__TOKEN__LENGTH__SHIFT)); + if (((uint64_t)(io2_a_src - iop_a_src)) < 5) { + status = wuffs_base__make_status(wuffs_json__error__internal_error_inconsistent_i_o); + goto exit; + } + iop_a_src += 5; + goto label__goto_parsed_a_leaf_value__break; + } else if (v_match == 1) { + status = wuffs_base__make_status(wuffs_base__suspension__short_read); + WUFFS_BASE__COROUTINE_SUSPENSION_POINT_MAYBE_SUSPEND(19); + goto label__outer__continue; + } + } else if (v_class == 10) { + v_match = wuffs_base__io_reader__match7(iop_a_src, io2_a_src, a_src,435762131972); + if (v_match == 0) { + *iop_a_dst++ = wuffs_base__make_token( + (((uint64_t)(8388616)) << WUFFS_BASE__TOKEN__VALUE_MINOR__SHIFT) | + (((uint64_t)(4)) << WUFFS_BASE__TOKEN__LENGTH__SHIFT)); + if (((uint64_t)(io2_a_src - iop_a_src)) < 4) { + status = wuffs_base__make_status(wuffs_json__error__internal_error_inconsistent_i_o); + goto exit; + } + iop_a_src += 4; + goto label__goto_parsed_a_leaf_value__break; + } else if (v_match == 1) { + status = wuffs_base__make_status(wuffs_base__suspension__short_read); + WUFFS_BASE__COROUTINE_SUSPENSION_POINT_MAYBE_SUSPEND(20); + goto label__outer__continue; + } + } else if (v_class == 11) { + v_match = wuffs_base__io_reader__match7(iop_a_src, io2_a_src, a_src,465676103172); + if (v_match == 0) { + *iop_a_dst++ = wuffs_base__make_token( + (((uint64_t)(8388610)) << WUFFS_BASE__TOKEN__VALUE_MINOR__SHIFT) | + (((uint64_t)(4)) << WUFFS_BASE__TOKEN__LENGTH__SHIFT)); + if (((uint64_t)(io2_a_src - iop_a_src)) < 4) { + status = wuffs_base__make_status(wuffs_json__error__internal_error_inconsistent_i_o); + goto exit; + } + iop_a_src += 4; + goto label__goto_parsed_a_leaf_value__break; + } else if (v_match == 1) { + status = wuffs_base__make_status(wuffs_base__suspension__short_read); + WUFFS_BASE__COROUTINE_SUSPENSION_POINT_MAYBE_SUSPEND(21); + goto label__outer__continue; + } + if (self->private_impl.f_quirks[14]) { + if (a_dst) { + a_dst->meta.wi = ((size_t)(iop_a_dst - a_dst->data.ptr)); + } + if (a_src) { + a_src->meta.ri = ((size_t)(iop_a_src - a_src->data.ptr)); + } + WUFFS_BASE__COROUTINE_SUSPENSION_POINT(22); + status = wuffs_json__decoder__decode_inf_nan(self, a_dst, a_src); + if (a_dst) { + iop_a_dst = a_dst->data.ptr + a_dst->meta.wi; + } + if (a_src) { + iop_a_src = a_src->data.ptr + a_src->meta.ri; + } + if (status.repr) { + goto suspend; + } + goto label__goto_parsed_a_leaf_value__break; + } + } else if (v_class == 12) { + if (self->private_impl.f_quirks[11] || self->private_impl.f_quirks[12]) { + if (a_dst) { + a_dst->meta.wi = ((size_t)(iop_a_dst - a_dst->data.ptr)); + } + if (a_src) { + a_src->meta.ri = ((size_t)(iop_a_src - a_src->data.ptr)); + } + WUFFS_BASE__COROUTINE_SUSPENSION_POINT(23); + status = wuffs_json__decoder__decode_comment(self, a_dst, a_src); + if (a_dst) { + iop_a_dst = a_dst->data.ptr + a_dst->meta.wi; + } + if (a_src) { + iop_a_src = a_src->data.ptr + a_src->meta.ri; + } + if (status.repr) { + goto suspend; + } + if (self->private_impl.f_comment_type > 0) { + goto label__outer__continue; + } + } + } + status = wuffs_base__make_status(wuffs_json__error__bad_input); + goto exit; + } + label__goto_parsed_a_leaf_value__break:; + if (v_depth == 0) { + goto label__outer__break; + } + v_expect = v_expect_after_value; + } + label__outer__break:; + if (self->private_impl.f_quirks[17] || self->private_impl.f_quirks[18]) { + if (a_dst) { + a_dst->meta.wi = ((size_t)(iop_a_dst - a_dst->data.ptr)); + } + if (a_src) { + a_src->meta.ri = ((size_t)(iop_a_src - a_src->data.ptr)); + } + WUFFS_BASE__COROUTINE_SUSPENSION_POINT(24); + status = wuffs_json__decoder__decode_trailer(self, a_dst, a_src); + if (a_dst) { + iop_a_dst = a_dst->data.ptr + a_dst->meta.wi; + } + if (a_src) { + iop_a_src = a_src->data.ptr + a_src->meta.ri; + } + if (status.repr) { + goto suspend; + } + } + self->private_impl.f_end_of_data = true; + + ok: + self->private_impl.p_decode_tokens[0] = 0; + goto exit; + } + + goto suspend; + suspend: + self->private_impl.p_decode_tokens[0] = wuffs_base__status__is_suspension(&status) ? coro_susp_point : 0; + self->private_impl.active_coroutine = wuffs_base__status__is_suspension(&status) ? 1 : 0; + self->private_data.s_decode_tokens[0].v_depth = v_depth; + self->private_data.s_decode_tokens[0].v_expect = v_expect; + self->private_data.s_decode_tokens[0].v_expect_after_value = v_expect_after_value; + + goto exit; + exit: + if (a_dst && a_dst->data.ptr) { + a_dst->meta.wi = ((size_t)(iop_a_dst - a_dst->data.ptr)); + } + if (a_src && a_src->data.ptr) { + a_src->meta.ri = ((size_t)(iop_a_src - a_src->data.ptr)); + } + + if (wuffs_base__status__is_error(&status)) { + self->private_impl.magic = WUFFS_BASE__DISABLED; + } + return status; +} + +// -------- func json.decoder.decode_number + +static uint32_t +wuffs_json__decoder__decode_number( + wuffs_json__decoder* self, + wuffs_base__io_buffer* a_src) { + uint8_t v_c = 0; + uint32_t v_n = 0; + uint32_t v_floating_point = 0; + + const uint8_t* iop_a_src = NULL; + const uint8_t* io0_a_src WUFFS_BASE__POTENTIALLY_UNUSED = NULL; + const uint8_t* io1_a_src WUFFS_BASE__POTENTIALLY_UNUSED = NULL; + const uint8_t* io2_a_src WUFFS_BASE__POTENTIALLY_UNUSED = NULL; + if (a_src && a_src->data.ptr) { + io0_a_src = a_src->data.ptr; + io1_a_src = io0_a_src + a_src->meta.ri; + iop_a_src = io1_a_src; + io2_a_src = io0_a_src + a_src->meta.wi; + } + + while (true) { + v_n = 0; + if (((uint64_t)(io2_a_src - iop_a_src)) <= 0) { + if ( ! (a_src && a_src->meta.closed)) { + v_n |= 768; + } + goto label__goto_done__break; + } + v_c = wuffs_base__peek_u8be__no_bounds_check(iop_a_src); + if (v_c != 45) { + } else { + v_n += 1; + iop_a_src += 1; + if (((uint64_t)(io2_a_src - iop_a_src)) <= 0) { + if ( ! (a_src && a_src->meta.closed)) { + v_n |= 768; + } + v_n |= 256; + goto label__goto_done__break; + } + v_c = wuffs_base__peek_u8be__no_bounds_check(iop_a_src); + } + if (v_c == 48) { + v_n += 1; + iop_a_src += 1; + } else { + if (a_src) { + a_src->meta.ri = ((size_t)(iop_a_src - a_src->data.ptr)); + } + v_n = wuffs_json__decoder__decode_digits(self, a_src, v_n); + if (a_src) { + iop_a_src = a_src->data.ptr + a_src->meta.ri; + } + if (v_n > 99) { + goto label__goto_done__break; + } + } + if (((uint64_t)(io2_a_src - iop_a_src)) <= 0) { + if ( ! (a_src && a_src->meta.closed)) { + v_n |= 768; + } + goto label__goto_done__break; + } + v_c = wuffs_base__peek_u8be__no_bounds_check(iop_a_src); + if (v_c != 46) { + } else { + if (v_n >= 99) { + v_n |= 512; + goto label__goto_done__break; + } + v_n += 1; + iop_a_src += 1; + v_floating_point = 128; + if (a_src) { + a_src->meta.ri = ((size_t)(iop_a_src - a_src->data.ptr)); + } + v_n = wuffs_json__decoder__decode_digits(self, a_src, v_n); + if (a_src) { + iop_a_src = a_src->data.ptr + a_src->meta.ri; + } + if (v_n > 99) { + goto label__goto_done__break; + } + if (((uint64_t)(io2_a_src - iop_a_src)) <= 0) { + if ( ! (a_src && a_src->meta.closed)) { + v_n |= 768; + } + goto label__goto_done__break; + } + v_c = wuffs_base__peek_u8be__no_bounds_check(iop_a_src); + } + if ((v_c != 69) && (v_c != 101)) { + goto label__goto_done__break; + } + if (v_n >= 99) { + v_n |= 512; + goto label__goto_done__break; + } + v_n += 1; + iop_a_src += 1; + v_floating_point = 128; + if (((uint64_t)(io2_a_src - iop_a_src)) <= 0) { + if ( ! (a_src && a_src->meta.closed)) { + v_n |= 768; + } + v_n |= 256; + goto label__goto_done__break; + } + v_c = wuffs_base__peek_u8be__no_bounds_check(iop_a_src); + if ((v_c != 43) && (v_c != 45)) { + } else { + if (v_n >= 99) { + v_n |= 512; + goto label__goto_done__break; + } + v_n += 1; + iop_a_src += 1; + } + if (a_src) { + a_src->meta.ri = ((size_t)(iop_a_src - a_src->data.ptr)); + } + v_n = wuffs_json__decoder__decode_digits(self, a_src, v_n); + if (a_src) { + iop_a_src = a_src->data.ptr + a_src->meta.ri; + } + goto label__goto_done__break; + } + label__goto_done__break:; + if (a_src && a_src->data.ptr) { + a_src->meta.ri = ((size_t)(iop_a_src - a_src->data.ptr)); + } + return (v_n | v_floating_point); +} + +// -------- func json.decoder.decode_digits + +static uint32_t +wuffs_json__decoder__decode_digits( + wuffs_json__decoder* self, + wuffs_base__io_buffer* a_src, + uint32_t a_n) { + uint8_t v_c = 0; + uint32_t v_n = 0; + + const uint8_t* iop_a_src = NULL; + const uint8_t* io0_a_src WUFFS_BASE__POTENTIALLY_UNUSED = NULL; + const uint8_t* io1_a_src WUFFS_BASE__POTENTIALLY_UNUSED = NULL; + const uint8_t* io2_a_src WUFFS_BASE__POTENTIALLY_UNUSED = NULL; + if (a_src && a_src->data.ptr) { + io0_a_src = a_src->data.ptr; + io1_a_src = io0_a_src + a_src->meta.ri; + iop_a_src = io1_a_src; + io2_a_src = io0_a_src + a_src->meta.wi; + } + + v_n = a_n; + while (true) { + if (((uint64_t)(io2_a_src - iop_a_src)) <= 0) { + if ( ! (a_src && a_src->meta.closed)) { + v_n |= 768; + } + goto label__0__break; + } + v_c = wuffs_base__peek_u8be__no_bounds_check(iop_a_src); + if (0 == WUFFS_JSON__LUT_DECIMAL_DIGITS[v_c]) { + goto label__0__break; + } + if (v_n >= 99) { + v_n |= 512; + goto label__0__break; + } + v_n += 1; + iop_a_src += 1; + } + label__0__break:; + if (v_n == a_n) { + v_n |= 256; + } + if (a_src && a_src->data.ptr) { + a_src->meta.ri = ((size_t)(iop_a_src - a_src->data.ptr)); + } + return v_n; +} + +// -------- func json.decoder.decode_leading + +static wuffs_base__status +wuffs_json__decoder__decode_leading( + wuffs_json__decoder* self, + wuffs_base__token_buffer* a_dst, + wuffs_base__io_buffer* a_src) { + wuffs_base__status status = wuffs_base__make_status(NULL); + + uint8_t v_c = 0; + uint32_t v_u = 0; + + wuffs_base__token* iop_a_dst = NULL; + wuffs_base__token* io0_a_dst WUFFS_BASE__POTENTIALLY_UNUSED = NULL; + wuffs_base__token* io1_a_dst WUFFS_BASE__POTENTIALLY_UNUSED = NULL; + wuffs_base__token* io2_a_dst WUFFS_BASE__POTENTIALLY_UNUSED = NULL; + if (a_dst && a_dst->data.ptr) { + io0_a_dst = a_dst->data.ptr; + io1_a_dst = io0_a_dst + a_dst->meta.wi; + iop_a_dst = io1_a_dst; + io2_a_dst = io0_a_dst + a_dst->data.len; + if (a_dst->meta.closed) { + io2_a_dst = iop_a_dst; + } + } + const uint8_t* iop_a_src = NULL; + const uint8_t* io0_a_src WUFFS_BASE__POTENTIALLY_UNUSED = NULL; + const uint8_t* io1_a_src WUFFS_BASE__POTENTIALLY_UNUSED = NULL; + const uint8_t* io2_a_src WUFFS_BASE__POTENTIALLY_UNUSED = NULL; + if (a_src && a_src->data.ptr) { + io0_a_src = a_src->data.ptr; + io1_a_src = io0_a_src + a_src->meta.ri; + iop_a_src = io1_a_src; + io2_a_src = io0_a_src + a_src->meta.wi; + } + + uint32_t coro_susp_point = self->private_impl.p_decode_leading[0]; + switch (coro_susp_point) { + WUFFS_BASE__COROUTINE_SUSPENSION_POINT_0; + + self->private_impl.f_allow_leading_ars = self->private_impl.f_quirks[15]; + self->private_impl.f_allow_leading_ubom = self->private_impl.f_quirks[16]; + label__0__continue:; + while (self->private_impl.f_allow_leading_ars || self->private_impl.f_allow_leading_ubom) { + if (((uint64_t)(io2_a_dst - iop_a_dst)) <= 0) { + status = wuffs_base__make_status(wuffs_base__suspension__short_write); + WUFFS_BASE__COROUTINE_SUSPENSION_POINT_MAYBE_SUSPEND(1); + goto label__0__continue; + } + if (((uint64_t)(io2_a_src - iop_a_src)) <= 0) { + if (a_src && a_src->meta.closed) { + goto label__0__break; + } + status = wuffs_base__make_status(wuffs_base__suspension__short_read); + WUFFS_BASE__COROUTINE_SUSPENSION_POINT_MAYBE_SUSPEND(2); + goto label__0__continue; + } + v_c = wuffs_base__peek_u8be__no_bounds_check(iop_a_src); + if ((v_c == 30) && self->private_impl.f_allow_leading_ars) { + self->private_impl.f_allow_leading_ars = false; + iop_a_src += 1; + *iop_a_dst++ = wuffs_base__make_token( + (((uint64_t)(0)) << WUFFS_BASE__TOKEN__VALUE_MINOR__SHIFT) | + (((uint64_t)(1)) << WUFFS_BASE__TOKEN__LENGTH__SHIFT)); + goto label__0__continue; + } else if ((v_c == 239) && self->private_impl.f_allow_leading_ubom) { + if (((uint64_t)(io2_a_src - iop_a_src)) < 3) { + if (a_src && a_src->meta.closed) { + goto label__0__break; + } + status = wuffs_base__make_status(wuffs_base__suspension__short_read); + WUFFS_BASE__COROUTINE_SUSPENSION_POINT_MAYBE_SUSPEND(3); + goto label__0__continue; + } + v_u = ((uint32_t)(wuffs_base__peek_u24le__no_bounds_check(iop_a_src))); + if (v_u == 12565487) { + self->private_impl.f_allow_leading_ubom = false; + iop_a_src += 3; + *iop_a_dst++ = wuffs_base__make_token( + (((uint64_t)(0)) << WUFFS_BASE__TOKEN__VALUE_MINOR__SHIFT) | + (((uint64_t)(3)) << WUFFS_BASE__TOKEN__LENGTH__SHIFT)); + goto label__0__continue; + } + } + goto label__0__break; + } + label__0__break:; + + ok: + self->private_impl.p_decode_leading[0] = 0; + goto exit; + } + + goto suspend; + suspend: + self->private_impl.p_decode_leading[0] = wuffs_base__status__is_suspension(&status) ? coro_susp_point : 0; + + goto exit; + exit: + if (a_dst && a_dst->data.ptr) { + a_dst->meta.wi = ((size_t)(iop_a_dst - a_dst->data.ptr)); + } + if (a_src && a_src->data.ptr) { + a_src->meta.ri = ((size_t)(iop_a_src - a_src->data.ptr)); + } + + return status; +} + +// -------- func json.decoder.decode_comment + +static wuffs_base__status +wuffs_json__decoder__decode_comment( + wuffs_json__decoder* self, + wuffs_base__token_buffer* a_dst, + wuffs_base__io_buffer* a_src) { + wuffs_base__status status = wuffs_base__make_status(NULL); + + uint8_t v_c = 0; + uint16_t v_c2 = 0; + uint32_t v_length = 0; + + wuffs_base__token* iop_a_dst = NULL; + wuffs_base__token* io0_a_dst WUFFS_BASE__POTENTIALLY_UNUSED = NULL; + wuffs_base__token* io1_a_dst WUFFS_BASE__POTENTIALLY_UNUSED = NULL; + wuffs_base__token* io2_a_dst WUFFS_BASE__POTENTIALLY_UNUSED = NULL; + if (a_dst && a_dst->data.ptr) { + io0_a_dst = a_dst->data.ptr; + io1_a_dst = io0_a_dst + a_dst->meta.wi; + iop_a_dst = io1_a_dst; + io2_a_dst = io0_a_dst + a_dst->data.len; + if (a_dst->meta.closed) { + io2_a_dst = iop_a_dst; + } + } + const uint8_t* iop_a_src = NULL; + const uint8_t* io0_a_src WUFFS_BASE__POTENTIALLY_UNUSED = NULL; + const uint8_t* io1_a_src WUFFS_BASE__POTENTIALLY_UNUSED = NULL; + const uint8_t* io2_a_src WUFFS_BASE__POTENTIALLY_UNUSED = NULL; + if (a_src && a_src->data.ptr) { + io0_a_src = a_src->data.ptr; + io1_a_src = io0_a_src + a_src->meta.ri; + iop_a_src = io1_a_src; + io2_a_src = io0_a_src + a_src->meta.wi; + } + + uint32_t coro_susp_point = self->private_impl.p_decode_comment[0]; + switch (coro_susp_point) { + WUFFS_BASE__COROUTINE_SUSPENSION_POINT_0; + + self->private_impl.f_comment_type = 0; + label__0__continue:; + while ((((uint64_t)(io2_a_dst - iop_a_dst)) <= 0) || (((uint64_t)(io2_a_src - iop_a_src)) <= 1)) { + if (((uint64_t)(io2_a_dst - iop_a_dst)) <= 0) { + status = wuffs_base__make_status(wuffs_base__suspension__short_write); + WUFFS_BASE__COROUTINE_SUSPENSION_POINT_MAYBE_SUSPEND(1); + goto label__0__continue; + } + if (a_src && a_src->meta.closed) { + status = wuffs_base__make_status(NULL); + goto ok; + } + status = wuffs_base__make_status(wuffs_base__suspension__short_read); + WUFFS_BASE__COROUTINE_SUSPENSION_POINT_MAYBE_SUSPEND(2); + } + v_c2 = wuffs_base__peek_u16le__no_bounds_check(iop_a_src); + if ((v_c2 == 10799) && self->private_impl.f_quirks[11]) { + iop_a_src += 2; + v_length = 2; + label__comment_block__continue:; + while (true) { + if (((uint64_t)(io2_a_src - iop_a_src)) <= 1) { + if (v_length > 0) { + *iop_a_dst++ = wuffs_base__make_token( + (((uint64_t)(2)) << WUFFS_BASE__TOKEN__VALUE_MINOR__SHIFT) | + (((uint64_t)(1)) << WUFFS_BASE__TOKEN__CONTINUED__SHIFT) | + (((uint64_t)(v_length)) << WUFFS_BASE__TOKEN__LENGTH__SHIFT)); + } + if (a_src && a_src->meta.closed) { + status = wuffs_base__make_status(wuffs_json__error__bad_input); + goto exit; + } + status = wuffs_base__make_status(wuffs_base__suspension__short_read); + WUFFS_BASE__COROUTINE_SUSPENSION_POINT_MAYBE_SUSPEND(3); + while (((uint64_t)(io2_a_dst - iop_a_dst)) <= 0) { + status = wuffs_base__make_status(wuffs_base__suspension__short_write); + WUFFS_BASE__COROUTINE_SUSPENSION_POINT_MAYBE_SUSPEND(4); + } + v_length = 0; + goto label__comment_block__continue; + } + v_c2 = wuffs_base__peek_u16le__no_bounds_check(iop_a_src); + if (v_c2 == 12074) { + iop_a_src += 2; + *iop_a_dst++ = wuffs_base__make_token( + (((uint64_t)(2)) << WUFFS_BASE__TOKEN__VALUE_MINOR__SHIFT) | + (((uint64_t)((v_length + 2))) << WUFFS_BASE__TOKEN__LENGTH__SHIFT)); + self->private_impl.f_comment_type = 1; + status = wuffs_base__make_status(NULL); + goto ok; + } + iop_a_src += 1; + if (v_length >= 65533) { + *iop_a_dst++ = wuffs_base__make_token( + (((uint64_t)(2)) << WUFFS_BASE__TOKEN__VALUE_MINOR__SHIFT) | + (((uint64_t)(1)) << WUFFS_BASE__TOKEN__CONTINUED__SHIFT) | + (((uint64_t)((v_length + 1))) << WUFFS_BASE__TOKEN__LENGTH__SHIFT)); + while (((uint64_t)(io2_a_dst - iop_a_dst)) <= 0) { + status = wuffs_base__make_status(wuffs_base__suspension__short_write); + WUFFS_BASE__COROUTINE_SUSPENSION_POINT_MAYBE_SUSPEND(5); + } + v_length = 0; + goto label__comment_block__continue; + } + v_length += 1; + } + } else if ((v_c2 == 12079) && self->private_impl.f_quirks[12]) { + iop_a_src += 2; + v_length = 2; + label__comment_line__continue:; + while (true) { + if (((uint64_t)(io2_a_src - iop_a_src)) <= 0) { + if (a_src && a_src->meta.closed) { + *iop_a_dst++ = wuffs_base__make_token( + (((uint64_t)(4)) << WUFFS_BASE__TOKEN__VALUE_MINOR__SHIFT) | + (((uint64_t)(v_length)) << WUFFS_BASE__TOKEN__LENGTH__SHIFT)); + self->private_impl.f_comment_type = 2; + status = wuffs_base__make_status(NULL); + goto ok; + } else if (v_length > 0) { + *iop_a_dst++ = wuffs_base__make_token( + (((uint64_t)(4)) << WUFFS_BASE__TOKEN__VALUE_MINOR__SHIFT) | + (((uint64_t)(1)) << WUFFS_BASE__TOKEN__CONTINUED__SHIFT) | + (((uint64_t)(v_length)) << WUFFS_BASE__TOKEN__LENGTH__SHIFT)); + } + status = wuffs_base__make_status(wuffs_base__suspension__short_read); + WUFFS_BASE__COROUTINE_SUSPENSION_POINT_MAYBE_SUSPEND(6); + while (((uint64_t)(io2_a_dst - iop_a_dst)) <= 0) { + status = wuffs_base__make_status(wuffs_base__suspension__short_write); + WUFFS_BASE__COROUTINE_SUSPENSION_POINT_MAYBE_SUSPEND(7); + } + v_length = 0; + goto label__comment_line__continue; + } + v_c = wuffs_base__peek_u8be__no_bounds_check(iop_a_src); + if (v_c == 10) { + *iop_a_dst++ = wuffs_base__make_token( + (((uint64_t)(4)) << WUFFS_BASE__TOKEN__VALUE_MINOR__SHIFT) | + (((uint64_t)(v_length)) << WUFFS_BASE__TOKEN__LENGTH__SHIFT)); + self->private_impl.f_comment_type = 2; + status = wuffs_base__make_status(NULL); + goto ok; + } + iop_a_src += 1; + if (v_length >= 65533) { + *iop_a_dst++ = wuffs_base__make_token( + (((uint64_t)(4)) << WUFFS_BASE__TOKEN__VALUE_MINOR__SHIFT) | + (((uint64_t)(1)) << WUFFS_BASE__TOKEN__CONTINUED__SHIFT) | + (((uint64_t)((v_length + 1))) << WUFFS_BASE__TOKEN__LENGTH__SHIFT)); + while (((uint64_t)(io2_a_dst - iop_a_dst)) <= 0) { + status = wuffs_base__make_status(wuffs_base__suspension__short_write); + WUFFS_BASE__COROUTINE_SUSPENSION_POINT_MAYBE_SUSPEND(8); + } + v_length = 0; + goto label__comment_line__continue; + } + v_length += 1; + } + } + + ok: + self->private_impl.p_decode_comment[0] = 0; + goto exit; + } + + goto suspend; + suspend: + self->private_impl.p_decode_comment[0] = wuffs_base__status__is_suspension(&status) ? coro_susp_point : 0; + + goto exit; + exit: + if (a_dst && a_dst->data.ptr) { + a_dst->meta.wi = ((size_t)(iop_a_dst - a_dst->data.ptr)); + } + if (a_src && a_src->data.ptr) { + a_src->meta.ri = ((size_t)(iop_a_src - a_src->data.ptr)); + } + + return status; +} + +// -------- func json.decoder.decode_inf_nan + +static wuffs_base__status +wuffs_json__decoder__decode_inf_nan( + wuffs_json__decoder* self, + wuffs_base__token_buffer* a_dst, + wuffs_base__io_buffer* a_src) { + wuffs_base__status status = wuffs_base__make_status(NULL); + + uint32_t v_c4 = 0; + uint32_t v_neg = 0; + + wuffs_base__token* iop_a_dst = NULL; + wuffs_base__token* io0_a_dst WUFFS_BASE__POTENTIALLY_UNUSED = NULL; + wuffs_base__token* io1_a_dst WUFFS_BASE__POTENTIALLY_UNUSED = NULL; + wuffs_base__token* io2_a_dst WUFFS_BASE__POTENTIALLY_UNUSED = NULL; + if (a_dst && a_dst->data.ptr) { + io0_a_dst = a_dst->data.ptr; + io1_a_dst = io0_a_dst + a_dst->meta.wi; + iop_a_dst = io1_a_dst; + io2_a_dst = io0_a_dst + a_dst->data.len; + if (a_dst->meta.closed) { + io2_a_dst = iop_a_dst; + } + } + const uint8_t* iop_a_src = NULL; + const uint8_t* io0_a_src WUFFS_BASE__POTENTIALLY_UNUSED = NULL; + const uint8_t* io1_a_src WUFFS_BASE__POTENTIALLY_UNUSED = NULL; + const uint8_t* io2_a_src WUFFS_BASE__POTENTIALLY_UNUSED = NULL; + if (a_src && a_src->data.ptr) { + io0_a_src = a_src->data.ptr; + io1_a_src = io0_a_src + a_src->meta.ri; + iop_a_src = io1_a_src; + io2_a_src = io0_a_src + a_src->meta.wi; + } + + uint32_t coro_susp_point = self->private_impl.p_decode_inf_nan[0]; + switch (coro_susp_point) { + WUFFS_BASE__COROUTINE_SUSPENSION_POINT_0; + + label__0__continue:; + while (true) { + if (((uint64_t)(io2_a_dst - iop_a_dst)) <= 0) { + status = wuffs_base__make_status(wuffs_base__suspension__short_write); + WUFFS_BASE__COROUTINE_SUSPENSION_POINT_MAYBE_SUSPEND(1); + goto label__0__continue; + } + if (((uint64_t)(io2_a_src - iop_a_src)) <= 2) { + if (a_src && a_src->meta.closed) { + status = wuffs_base__make_status(wuffs_json__error__bad_input); + goto exit; + } + status = wuffs_base__make_status(wuffs_base__suspension__short_read); + WUFFS_BASE__COROUTINE_SUSPENSION_POINT_MAYBE_SUSPEND(2); + goto label__0__continue; + } + v_c4 = ((uint32_t)(wuffs_base__peek_u24le__no_bounds_check(iop_a_src))); + if ((v_c4 | 2105376) == 6712937) { + if (((uint64_t)(io2_a_src - iop_a_src)) > 7) { + if ((wuffs_base__peek_u64le__no_bounds_check(iop_a_src) | 2314885530818453536) == 8751735898823356009) { + *iop_a_dst++ = wuffs_base__make_token( + (((uint64_t)(10485792)) << WUFFS_BASE__TOKEN__VALUE_MINOR__SHIFT) | + (((uint64_t)(8)) << WUFFS_BASE__TOKEN__LENGTH__SHIFT)); + iop_a_src += 8; + status = wuffs_base__make_status(NULL); + goto ok; + } + } else if ( ! (a_src && a_src->meta.closed)) { + status = wuffs_base__make_status(wuffs_base__suspension__short_read); + WUFFS_BASE__COROUTINE_SUSPENSION_POINT_MAYBE_SUSPEND(3); + goto label__0__continue; + } + *iop_a_dst++ = wuffs_base__make_token( + (((uint64_t)(10485792)) << WUFFS_BASE__TOKEN__VALUE_MINOR__SHIFT) | + (((uint64_t)(3)) << WUFFS_BASE__TOKEN__LENGTH__SHIFT)); + iop_a_src += 3; + status = wuffs_base__make_status(NULL); + goto ok; + } else if ((v_c4 | 2105376) == 7233902) { + *iop_a_dst++ = wuffs_base__make_token( + (((uint64_t)(10485888)) << WUFFS_BASE__TOKEN__VALUE_MINOR__SHIFT) | + (((uint64_t)(3)) << WUFFS_BASE__TOKEN__LENGTH__SHIFT)); + iop_a_src += 3; + status = wuffs_base__make_status(NULL); + goto ok; + } else if ((v_c4 & 255) == 43) { + v_neg = 0; + } else if ((v_c4 & 255) == 45) { + v_neg = 1; + } else { + status = wuffs_base__make_status(wuffs_json__error__bad_input); + goto exit; + } + if (((uint64_t)(io2_a_src - iop_a_src)) <= 3) { + if (a_src && a_src->meta.closed) { + status = wuffs_base__make_status(wuffs_json__error__bad_input); + goto exit; + } + status = wuffs_base__make_status(wuffs_base__suspension__short_read); + WUFFS_BASE__COROUTINE_SUSPENSION_POINT_MAYBE_SUSPEND(4); + goto label__0__continue; + } + v_c4 = (wuffs_base__peek_u32le__no_bounds_check(iop_a_src) >> 8); + if ((v_c4 | 2105376) == 6712937) { + if (((uint64_t)(io2_a_src - iop_a_src)) > 8) { + if ((wuffs_base__peek_u64le__no_bounds_check(iop_a_src + 1) | 2314885530818453536) == 8751735898823356009) { + *iop_a_dst++ = wuffs_base__make_token( + (((uint64_t)((10485760 | (((uint32_t)(32)) >> v_neg)))) << WUFFS_BASE__TOKEN__VALUE_MINOR__SHIFT) | + (((uint64_t)(9)) << WUFFS_BASE__TOKEN__LENGTH__SHIFT)); + iop_a_src += 9; + status = wuffs_base__make_status(NULL); + goto ok; + } + } else if ( ! (a_src && a_src->meta.closed)) { + status = wuffs_base__make_status(wuffs_base__suspension__short_read); + WUFFS_BASE__COROUTINE_SUSPENSION_POINT_MAYBE_SUSPEND(5); + goto label__0__continue; + } + *iop_a_dst++ = wuffs_base__make_token( + (((uint64_t)((10485760 | (((uint32_t)(32)) >> v_neg)))) << WUFFS_BASE__TOKEN__VALUE_MINOR__SHIFT) | + (((uint64_t)(4)) << WUFFS_BASE__TOKEN__LENGTH__SHIFT)); + iop_a_src += 4; + status = wuffs_base__make_status(NULL); + goto ok; + } else if ((v_c4 | 2105376) == 7233902) { + *iop_a_dst++ = wuffs_base__make_token( + (((uint64_t)((10485760 | (((uint32_t)(128)) >> v_neg)))) << WUFFS_BASE__TOKEN__VALUE_MINOR__SHIFT) | + (((uint64_t)(4)) << WUFFS_BASE__TOKEN__LENGTH__SHIFT)); + iop_a_src += 4; + status = wuffs_base__make_status(NULL); + goto ok; + } + status = wuffs_base__make_status(wuffs_json__error__bad_input); + goto exit; + } + + ok: + self->private_impl.p_decode_inf_nan[0] = 0; + goto exit; + } + + goto suspend; + suspend: + self->private_impl.p_decode_inf_nan[0] = wuffs_base__status__is_suspension(&status) ? coro_susp_point : 0; + + goto exit; + exit: + if (a_dst && a_dst->data.ptr) { + a_dst->meta.wi = ((size_t)(iop_a_dst - a_dst->data.ptr)); + } + if (a_src && a_src->data.ptr) { + a_src->meta.ri = ((size_t)(iop_a_src - a_src->data.ptr)); + } + + return status; +} + +// -------- func json.decoder.decode_trailer + +static wuffs_base__status +wuffs_json__decoder__decode_trailer( + wuffs_json__decoder* self, + wuffs_base__token_buffer* a_dst, + wuffs_base__io_buffer* a_src) { + wuffs_base__status status = wuffs_base__make_status(NULL); + + uint8_t v_c = 0; + uint32_t v_whitespace_length = 0; + + wuffs_base__token* iop_a_dst = NULL; + wuffs_base__token* io0_a_dst WUFFS_BASE__POTENTIALLY_UNUSED = NULL; + wuffs_base__token* io1_a_dst WUFFS_BASE__POTENTIALLY_UNUSED = NULL; + wuffs_base__token* io2_a_dst WUFFS_BASE__POTENTIALLY_UNUSED = NULL; + if (a_dst && a_dst->data.ptr) { + io0_a_dst = a_dst->data.ptr; + io1_a_dst = io0_a_dst + a_dst->meta.wi; + iop_a_dst = io1_a_dst; + io2_a_dst = io0_a_dst + a_dst->data.len; + if (a_dst->meta.closed) { + io2_a_dst = iop_a_dst; + } + } + const uint8_t* iop_a_src = NULL; + const uint8_t* io0_a_src WUFFS_BASE__POTENTIALLY_UNUSED = NULL; + const uint8_t* io1_a_src WUFFS_BASE__POTENTIALLY_UNUSED = NULL; + const uint8_t* io2_a_src WUFFS_BASE__POTENTIALLY_UNUSED = NULL; + if (a_src && a_src->data.ptr) { + io0_a_src = a_src->data.ptr; + io1_a_src = io0_a_src + a_src->meta.ri; + iop_a_src = io1_a_src; + io2_a_src = io0_a_src + a_src->meta.wi; + } + + uint32_t coro_susp_point = self->private_impl.p_decode_trailer[0]; + switch (coro_susp_point) { + WUFFS_BASE__COROUTINE_SUSPENSION_POINT_0; + + if (self->private_impl.f_quirks[18]) { + self->private_impl.f_trailer_stop = 10; + } else { + self->private_impl.f_trailer_stop = 0; + } + label__outer__continue:; + while (true) { + if (((uint64_t)(io2_a_dst - iop_a_dst)) <= 0) { + status = wuffs_base__make_status(wuffs_base__suspension__short_write); + WUFFS_BASE__COROUTINE_SUSPENSION_POINT_MAYBE_SUSPEND(1); + goto label__outer__continue; + } + v_whitespace_length = 0; + while (true) { + if (((uint64_t)(io2_a_src - iop_a_src)) <= 0) { + if (v_whitespace_length > 0) { + *iop_a_dst++ = wuffs_base__make_token( + (((uint64_t)(0)) << WUFFS_BASE__TOKEN__VALUE_MINOR__SHIFT) | + (((uint64_t)(v_whitespace_length)) << WUFFS_BASE__TOKEN__LENGTH__SHIFT)); + } + if (a_src && a_src->meta.closed) { + goto label__outer__break; + } + status = wuffs_base__make_status(wuffs_base__suspension__short_read); + WUFFS_BASE__COROUTINE_SUSPENSION_POINT_MAYBE_SUSPEND(2); + goto label__outer__continue; + } + v_c = wuffs_base__peek_u8be__no_bounds_check(iop_a_src); + if (WUFFS_JSON__LUT_CLASSES[v_c] != 0) { + if (v_whitespace_length > 0) { + *iop_a_dst++ = wuffs_base__make_token( + (((uint64_t)(0)) << WUFFS_BASE__TOKEN__VALUE_MINOR__SHIFT) | + (((uint64_t)(v_whitespace_length)) << WUFFS_BASE__TOKEN__LENGTH__SHIFT)); + } + if (self->private_impl.f_trailer_stop > 0) { + status = wuffs_base__make_status(wuffs_json__error__bad_input); + goto exit; + } + if (a_dst) { + a_dst->meta.wi = ((size_t)(iop_a_dst - a_dst->data.ptr)); + } + if (a_src) { + a_src->meta.ri = ((size_t)(iop_a_src - a_src->data.ptr)); + } + WUFFS_BASE__COROUTINE_SUSPENSION_POINT(3); + status = wuffs_json__decoder__decode_comment(self, a_dst, a_src); + if (a_dst) { + iop_a_dst = a_dst->data.ptr + a_dst->meta.wi; + } + if (a_src) { + iop_a_src = a_src->data.ptr + a_src->meta.ri; + } + if (status.repr) { + goto suspend; + } + if (self->private_impl.f_comment_type > 0) { + goto label__outer__continue; + } + status = wuffs_base__make_status(NULL); + goto ok; + } + iop_a_src += 1; + if ((v_whitespace_length >= 65534) || (v_c == self->private_impl.f_trailer_stop)) { + *iop_a_dst++ = wuffs_base__make_token( + (((uint64_t)(0)) << WUFFS_BASE__TOKEN__VALUE_MINOR__SHIFT) | + (((uint64_t)((v_whitespace_length + 1))) << WUFFS_BASE__TOKEN__LENGTH__SHIFT)); + if (v_c == self->private_impl.f_trailer_stop) { + status = wuffs_base__make_status(NULL); + goto ok; + } + goto label__outer__continue; + } + v_whitespace_length += 1; + } + } + label__outer__break:; + + ok: + self->private_impl.p_decode_trailer[0] = 0; + goto exit; + } + + goto suspend; + suspend: + self->private_impl.p_decode_trailer[0] = wuffs_base__status__is_suspension(&status) ? coro_susp_point : 0; + + goto exit; + exit: + if (a_dst && a_dst->data.ptr) { + a_dst->meta.wi = ((size_t)(iop_a_dst - a_dst->data.ptr)); + } + if (a_src && a_src->data.ptr) { + a_src->meta.ri = ((size_t)(iop_a_src - a_src->data.ptr)); + } + + return status; +} + +#endif // !defined(WUFFS_CONFIG__MODULES) || defined(WUFFS_CONFIG__MODULE__JSON) + +#if !defined(WUFFS_CONFIG__MODULES) || defined(WUFFS_CONFIG__MODULE__NIE) + +// ---------------- Status Codes Implementations + +const char wuffs_nie__error__bad_header[] = "#nie: bad header"; +const char wuffs_nie__error__truncated_input[] = "#nie: truncated input"; +const char wuffs_nie__error__unsupported_nie_file[] = "#nie: unsupported NIE file"; +const char wuffs_nie__note__internal_note_short_read[] = "@nie: internal note: short read"; + +// ---------------- Private Consts + +// ---------------- Private Initializer Prototypes + +// ---------------- Private Function Prototypes + +static wuffs_base__status +wuffs_nie__decoder__do_decode_image_config( + wuffs_nie__decoder* self, + wuffs_base__image_config* a_dst, + wuffs_base__io_buffer* a_src); + +static wuffs_base__status +wuffs_nie__decoder__do_decode_frame_config( + wuffs_nie__decoder* self, + wuffs_base__frame_config* a_dst, + wuffs_base__io_buffer* a_src); + +static wuffs_base__status +wuffs_nie__decoder__do_decode_frame( + wuffs_nie__decoder* self, + wuffs_base__pixel_buffer* a_dst, + wuffs_base__io_buffer* a_src, + wuffs_base__pixel_blend a_blend, + wuffs_base__slice_u8 a_workbuf, + wuffs_base__decode_frame_options* a_opts); + +static wuffs_base__status +wuffs_nie__decoder__swizzle( + wuffs_nie__decoder* self, + wuffs_base__pixel_buffer* a_dst, + wuffs_base__io_buffer* a_src); + +// ---------------- VTables + +const wuffs_base__image_decoder__func_ptrs +wuffs_nie__decoder__func_ptrs_for__wuffs_base__image_decoder = { + (wuffs_base__status(*)(void*, + wuffs_base__pixel_buffer*, + wuffs_base__io_buffer*, + wuffs_base__pixel_blend, + wuffs_base__slice_u8, + wuffs_base__decode_frame_options*))(&wuffs_nie__decoder__decode_frame), + (wuffs_base__status(*)(void*, + wuffs_base__frame_config*, + wuffs_base__io_buffer*))(&wuffs_nie__decoder__decode_frame_config), + (wuffs_base__status(*)(void*, + wuffs_base__image_config*, + wuffs_base__io_buffer*))(&wuffs_nie__decoder__decode_image_config), + (wuffs_base__rect_ie_u32(*)(const void*))(&wuffs_nie__decoder__frame_dirty_rect), + (uint32_t(*)(const void*))(&wuffs_nie__decoder__num_animation_loops), + (uint64_t(*)(const void*))(&wuffs_nie__decoder__num_decoded_frame_configs), + (uint64_t(*)(const void*))(&wuffs_nie__decoder__num_decoded_frames), + (wuffs_base__status(*)(void*, + uint64_t, + uint64_t))(&wuffs_nie__decoder__restart_frame), + (wuffs_base__empty_struct(*)(void*, + uint32_t, + bool))(&wuffs_nie__decoder__set_quirk_enabled), + (wuffs_base__empty_struct(*)(void*, + uint32_t, + bool))(&wuffs_nie__decoder__set_report_metadata), + (wuffs_base__status(*)(void*, + wuffs_base__io_buffer*, + wuffs_base__more_information*, + wuffs_base__io_buffer*))(&wuffs_nie__decoder__tell_me_more), + (wuffs_base__range_ii_u64(*)(const void*))(&wuffs_nie__decoder__workbuf_len), +}; + +// ---------------- Initializer Implementations + +wuffs_base__status WUFFS_BASE__WARN_UNUSED_RESULT +wuffs_nie__decoder__initialize( + wuffs_nie__decoder* self, + size_t sizeof_star_self, + uint64_t wuffs_version, + uint32_t options){ + if (!self) { + return wuffs_base__make_status(wuffs_base__error__bad_receiver); + } + if (sizeof(*self) != sizeof_star_self) { + return wuffs_base__make_status(wuffs_base__error__bad_sizeof_receiver); + } + if (((wuffs_version >> 32) != WUFFS_VERSION_MAJOR) || + (((wuffs_version >> 16) & 0xFFFF) > WUFFS_VERSION_MINOR)) { + return wuffs_base__make_status(wuffs_base__error__bad_wuffs_version); + } + + if ((options & WUFFS_INITIALIZE__ALREADY_ZEROED) != 0) { + // The whole point of this if-check is to detect an uninitialized *self. + // We disable the warning on GCC. Clang-5.0 does not have this warning. +#if !defined(__clang__) && defined(__GNUC__) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wmaybe-uninitialized" +#endif + if (self->private_impl.magic != 0) { + return wuffs_base__make_status(wuffs_base__error__initialize_falsely_claimed_already_zeroed); + } +#if !defined(__clang__) && defined(__GNUC__) +#pragma GCC diagnostic pop +#endif + } else { + if ((options & WUFFS_INITIALIZE__LEAVE_INTERNAL_BUFFERS_UNINITIALIZED) == 0) { + memset(self, 0, sizeof(*self)); + options |= WUFFS_INITIALIZE__ALREADY_ZEROED; + } else { + memset(&(self->private_impl), 0, sizeof(self->private_impl)); + } + } + + self->private_impl.magic = WUFFS_BASE__MAGIC; + self->private_impl.vtable_for__wuffs_base__image_decoder.vtable_name = + wuffs_base__image_decoder__vtable_name; + self->private_impl.vtable_for__wuffs_base__image_decoder.function_pointers = + (const void*)(&wuffs_nie__decoder__func_ptrs_for__wuffs_base__image_decoder); + return wuffs_base__make_status(NULL); +} + +wuffs_nie__decoder* +wuffs_nie__decoder__alloc() { + wuffs_nie__decoder* x = + (wuffs_nie__decoder*)(calloc(sizeof(wuffs_nie__decoder), 1)); + if (!x) { + return NULL; + } + if (wuffs_nie__decoder__initialize( + x, sizeof(wuffs_nie__decoder), WUFFS_VERSION, WUFFS_INITIALIZE__ALREADY_ZEROED).repr) { + free(x); + return NULL; + } + return x; +} + +size_t +sizeof__wuffs_nie__decoder() { + return sizeof(wuffs_nie__decoder); +} + +// ---------------- Function Implementations + +// -------- func nie.decoder.set_quirk_enabled + +WUFFS_BASE__MAYBE_STATIC wuffs_base__empty_struct +wuffs_nie__decoder__set_quirk_enabled( + wuffs_nie__decoder* self, + uint32_t a_quirk, + bool a_enabled) { + return wuffs_base__make_empty_struct(); +} + +// -------- func nie.decoder.decode_image_config + +WUFFS_BASE__MAYBE_STATIC wuffs_base__status +wuffs_nie__decoder__decode_image_config( + wuffs_nie__decoder* self, + wuffs_base__image_config* a_dst, + wuffs_base__io_buffer* a_src) { + if (!self) { + return wuffs_base__make_status(wuffs_base__error__bad_receiver); + } + if (self->private_impl.magic != WUFFS_BASE__MAGIC) { + return wuffs_base__make_status( + (self->private_impl.magic == WUFFS_BASE__DISABLED) + ? wuffs_base__error__disabled_by_previous_error + : wuffs_base__error__initialize_not_called); + } + if (!a_src) { + self->private_impl.magic = WUFFS_BASE__DISABLED; + return wuffs_base__make_status(wuffs_base__error__bad_argument); + } + if ((self->private_impl.active_coroutine != 0) && + (self->private_impl.active_coroutine != 1)) { + self->private_impl.magic = WUFFS_BASE__DISABLED; + return wuffs_base__make_status(wuffs_base__error__interleaved_coroutine_calls); + } + self->private_impl.active_coroutine = 0; + wuffs_base__status status = wuffs_base__make_status(NULL); + + wuffs_base__status v_status = wuffs_base__make_status(NULL); + + uint32_t coro_susp_point = self->private_impl.p_decode_image_config[0]; + switch (coro_susp_point) { + WUFFS_BASE__COROUTINE_SUSPENSION_POINT_0; + + while (true) { + { + wuffs_base__status t_0 = wuffs_nie__decoder__do_decode_image_config(self, a_dst, a_src); + v_status = t_0; + } + if ((v_status.repr == wuffs_base__suspension__short_read) && (a_src && a_src->meta.closed)) { + status = wuffs_base__make_status(wuffs_nie__error__truncated_input); + goto exit; + } + status = v_status; + WUFFS_BASE__COROUTINE_SUSPENSION_POINT_MAYBE_SUSPEND(1); + } + + ok: + self->private_impl.p_decode_image_config[0] = 0; + goto exit; + } + + goto suspend; + suspend: + self->private_impl.p_decode_image_config[0] = wuffs_base__status__is_suspension(&status) ? coro_susp_point : 0; + self->private_impl.active_coroutine = wuffs_base__status__is_suspension(&status) ? 1 : 0; + + goto exit; + exit: + if (wuffs_base__status__is_error(&status)) { + self->private_impl.magic = WUFFS_BASE__DISABLED; + } + return status; +} + +// -------- func nie.decoder.do_decode_image_config + +static wuffs_base__status +wuffs_nie__decoder__do_decode_image_config( + wuffs_nie__decoder* self, + wuffs_base__image_config* a_dst, + wuffs_base__io_buffer* a_src) { + wuffs_base__status status = wuffs_base__make_status(NULL); + + uint32_t v_a = 0; + + const uint8_t* iop_a_src = NULL; + const uint8_t* io0_a_src WUFFS_BASE__POTENTIALLY_UNUSED = NULL; + const uint8_t* io1_a_src WUFFS_BASE__POTENTIALLY_UNUSED = NULL; + const uint8_t* io2_a_src WUFFS_BASE__POTENTIALLY_UNUSED = NULL; + if (a_src && a_src->data.ptr) { + io0_a_src = a_src->data.ptr; + io1_a_src = io0_a_src + a_src->meta.ri; + iop_a_src = io1_a_src; + io2_a_src = io0_a_src + a_src->meta.wi; + } + + uint32_t coro_susp_point = self->private_impl.p_do_decode_image_config[0]; + switch (coro_susp_point) { + WUFFS_BASE__COROUTINE_SUSPENSION_POINT_0; + + if (self->private_impl.f_call_sequence != 0) { + status = wuffs_base__make_status(wuffs_base__error__bad_call_sequence); + goto exit; + } + { + WUFFS_BASE__COROUTINE_SUSPENSION_POINT(1); + uint32_t t_0; + if (WUFFS_BASE__LIKELY(io2_a_src - iop_a_src >= 4)) { + t_0 = wuffs_base__peek_u32le__no_bounds_check(iop_a_src); + iop_a_src += 4; + } else { + self->private_data.s_do_decode_image_config[0].scratch = 0; + WUFFS_BASE__COROUTINE_SUSPENSION_POINT(2); + while (true) { + if (WUFFS_BASE__UNLIKELY(iop_a_src == io2_a_src)) { + status = wuffs_base__make_status(wuffs_base__suspension__short_read); + goto suspend; + } + uint64_t* scratch = &self->private_data.s_do_decode_image_config[0].scratch; + uint32_t num_bits_0 = ((uint32_t)(*scratch >> 56)); + *scratch <<= 8; + *scratch >>= 8; + *scratch |= ((uint64_t)(*iop_a_src++)) << num_bits_0; + if (num_bits_0 == 24) { + t_0 = ((uint32_t)(*scratch)); + break; + } + num_bits_0 += 8; + *scratch |= ((uint64_t)(num_bits_0)) << 56; + } + } + v_a = t_0; + } + if (v_a != 1169146734) { + status = wuffs_base__make_status(wuffs_nie__error__bad_header); + goto exit; + } + { + WUFFS_BASE__COROUTINE_SUSPENSION_POINT(3); + uint32_t t_1; + if (WUFFS_BASE__LIKELY(io2_a_src - iop_a_src >= 4)) { + t_1 = wuffs_base__peek_u32le__no_bounds_check(iop_a_src); + iop_a_src += 4; + } else { + self->private_data.s_do_decode_image_config[0].scratch = 0; + WUFFS_BASE__COROUTINE_SUSPENSION_POINT(4); + while (true) { + if (WUFFS_BASE__UNLIKELY(iop_a_src == io2_a_src)) { + status = wuffs_base__make_status(wuffs_base__suspension__short_read); + goto suspend; + } + uint64_t* scratch = &self->private_data.s_do_decode_image_config[0].scratch; + uint32_t num_bits_1 = ((uint32_t)(*scratch >> 56)); + *scratch <<= 8; + *scratch >>= 8; + *scratch |= ((uint64_t)(*iop_a_src++)) << num_bits_1; + if (num_bits_1 == 24) { + t_1 = ((uint32_t)(*scratch)); + break; + } + num_bits_1 += 8; + *scratch |= ((uint64_t)(num_bits_1)) << 56; + } + } + v_a = t_1; + } + if (v_a == 879649535) { + self->private_impl.f_pixfmt = 2164295816; + } else if (v_a == 946758399) { + self->private_impl.f_pixfmt = 2164308923; + } else if (v_a == 879780607) { + status = wuffs_base__make_status(wuffs_nie__error__unsupported_nie_file); + goto exit; + } else if (v_a == 946889471) { + status = wuffs_base__make_status(wuffs_nie__error__unsupported_nie_file); + goto exit; + } else { + status = wuffs_base__make_status(wuffs_nie__error__bad_header); + goto exit; + } + { + WUFFS_BASE__COROUTINE_SUSPENSION_POINT(5); + uint32_t t_2; + if (WUFFS_BASE__LIKELY(io2_a_src - iop_a_src >= 4)) { + t_2 = wuffs_base__peek_u32le__no_bounds_check(iop_a_src); + iop_a_src += 4; + } else { + self->private_data.s_do_decode_image_config[0].scratch = 0; + WUFFS_BASE__COROUTINE_SUSPENSION_POINT(6); + while (true) { + if (WUFFS_BASE__UNLIKELY(iop_a_src == io2_a_src)) { + status = wuffs_base__make_status(wuffs_base__suspension__short_read); + goto suspend; + } + uint64_t* scratch = &self->private_data.s_do_decode_image_config[0].scratch; + uint32_t num_bits_2 = ((uint32_t)(*scratch >> 56)); + *scratch <<= 8; + *scratch >>= 8; + *scratch |= ((uint64_t)(*iop_a_src++)) << num_bits_2; + if (num_bits_2 == 24) { + t_2 = ((uint32_t)(*scratch)); + break; + } + num_bits_2 += 8; + *scratch |= ((uint64_t)(num_bits_2)) << 56; + } + } + v_a = t_2; + } + if (v_a >= 2147483648) { + status = wuffs_base__make_status(wuffs_nie__error__bad_header); + goto exit; + } + self->private_impl.f_width = v_a; + { + WUFFS_BASE__COROUTINE_SUSPENSION_POINT(7); + uint32_t t_3; + if (WUFFS_BASE__LIKELY(io2_a_src - iop_a_src >= 4)) { + t_3 = wuffs_base__peek_u32le__no_bounds_check(iop_a_src); + iop_a_src += 4; + } else { + self->private_data.s_do_decode_image_config[0].scratch = 0; + WUFFS_BASE__COROUTINE_SUSPENSION_POINT(8); + while (true) { + if (WUFFS_BASE__UNLIKELY(iop_a_src == io2_a_src)) { + status = wuffs_base__make_status(wuffs_base__suspension__short_read); + goto suspend; + } + uint64_t* scratch = &self->private_data.s_do_decode_image_config[0].scratch; + uint32_t num_bits_3 = ((uint32_t)(*scratch >> 56)); + *scratch <<= 8; + *scratch >>= 8; + *scratch |= ((uint64_t)(*iop_a_src++)) << num_bits_3; + if (num_bits_3 == 24) { + t_3 = ((uint32_t)(*scratch)); + break; + } + num_bits_3 += 8; + *scratch |= ((uint64_t)(num_bits_3)) << 56; + } + } + v_a = t_3; + } + if (v_a >= 2147483648) { + status = wuffs_base__make_status(wuffs_nie__error__bad_header); + goto exit; + } + self->private_impl.f_height = v_a; + if (a_dst != NULL) { + wuffs_base__image_config__set( + a_dst, + self->private_impl.f_pixfmt, + 0, + self->private_impl.f_width, + self->private_impl.f_height, + 16, + false); + } + self->private_impl.f_call_sequence = 32; + + goto ok; + ok: + self->private_impl.p_do_decode_image_config[0] = 0; + goto exit; + } + + goto suspend; + suspend: + self->private_impl.p_do_decode_image_config[0] = wuffs_base__status__is_suspension(&status) ? coro_susp_point : 0; + + goto exit; + exit: + if (a_src && a_src->data.ptr) { + a_src->meta.ri = ((size_t)(iop_a_src - a_src->data.ptr)); + } + + return status; +} + +// -------- func nie.decoder.decode_frame_config + +WUFFS_BASE__MAYBE_STATIC wuffs_base__status +wuffs_nie__decoder__decode_frame_config( + wuffs_nie__decoder* self, + wuffs_base__frame_config* a_dst, + wuffs_base__io_buffer* a_src) { + if (!self) { + return wuffs_base__make_status(wuffs_base__error__bad_receiver); + } + if (self->private_impl.magic != WUFFS_BASE__MAGIC) { + return wuffs_base__make_status( + (self->private_impl.magic == WUFFS_BASE__DISABLED) + ? wuffs_base__error__disabled_by_previous_error + : wuffs_base__error__initialize_not_called); + } + if (!a_src) { + self->private_impl.magic = WUFFS_BASE__DISABLED; + return wuffs_base__make_status(wuffs_base__error__bad_argument); + } + if ((self->private_impl.active_coroutine != 0) && + (self->private_impl.active_coroutine != 2)) { + self->private_impl.magic = WUFFS_BASE__DISABLED; + return wuffs_base__make_status(wuffs_base__error__interleaved_coroutine_calls); + } + self->private_impl.active_coroutine = 0; + wuffs_base__status status = wuffs_base__make_status(NULL); + + wuffs_base__status v_status = wuffs_base__make_status(NULL); + + uint32_t coro_susp_point = self->private_impl.p_decode_frame_config[0]; + switch (coro_susp_point) { + WUFFS_BASE__COROUTINE_SUSPENSION_POINT_0; + + while (true) { + { + wuffs_base__status t_0 = wuffs_nie__decoder__do_decode_frame_config(self, a_dst, a_src); + v_status = t_0; + } + if ((v_status.repr == wuffs_base__suspension__short_read) && (a_src && a_src->meta.closed)) { + status = wuffs_base__make_status(wuffs_nie__error__truncated_input); + goto exit; + } + status = v_status; + WUFFS_BASE__COROUTINE_SUSPENSION_POINT_MAYBE_SUSPEND(1); + } + + ok: + self->private_impl.p_decode_frame_config[0] = 0; + goto exit; + } + + goto suspend; + suspend: + self->private_impl.p_decode_frame_config[0] = wuffs_base__status__is_suspension(&status) ? coro_susp_point : 0; + self->private_impl.active_coroutine = wuffs_base__status__is_suspension(&status) ? 2 : 0; + + goto exit; + exit: + if (wuffs_base__status__is_error(&status)) { + self->private_impl.magic = WUFFS_BASE__DISABLED; + } + return status; +} + +// -------- func nie.decoder.do_decode_frame_config + +static wuffs_base__status +wuffs_nie__decoder__do_decode_frame_config( + wuffs_nie__decoder* self, + wuffs_base__frame_config* a_dst, + wuffs_base__io_buffer* a_src) { + wuffs_base__status status = wuffs_base__make_status(NULL); + + const uint8_t* iop_a_src = NULL; + const uint8_t* io0_a_src WUFFS_BASE__POTENTIALLY_UNUSED = NULL; + const uint8_t* io1_a_src WUFFS_BASE__POTENTIALLY_UNUSED = NULL; + const uint8_t* io2_a_src WUFFS_BASE__POTENTIALLY_UNUSED = NULL; + if (a_src && a_src->data.ptr) { + io0_a_src = a_src->data.ptr; + io1_a_src = io0_a_src + a_src->meta.ri; + iop_a_src = io1_a_src; + io2_a_src = io0_a_src + a_src->meta.wi; + } + + uint32_t coro_susp_point = self->private_impl.p_do_decode_frame_config[0]; + switch (coro_susp_point) { + WUFFS_BASE__COROUTINE_SUSPENSION_POINT_0; + + if (self->private_impl.f_call_sequence == 32) { + } else if (self->private_impl.f_call_sequence < 32) { + if (a_src) { + a_src->meta.ri = ((size_t)(iop_a_src - a_src->data.ptr)); + } + WUFFS_BASE__COROUTINE_SUSPENSION_POINT(1); + status = wuffs_nie__decoder__do_decode_image_config(self, NULL, a_src); + if (a_src) { + iop_a_src = a_src->data.ptr + a_src->meta.ri; + } + if (status.repr) { + goto suspend; + } + } else if (self->private_impl.f_call_sequence == 40) { + if (16 != wuffs_base__u64__sat_add((a_src ? a_src->meta.pos : 0), ((uint64_t)(iop_a_src - io0_a_src)))) { + status = wuffs_base__make_status(wuffs_base__error__bad_restart); + goto exit; + } + } else if (self->private_impl.f_call_sequence == 64) { + self->private_impl.f_call_sequence = 96; + status = wuffs_base__make_status(wuffs_base__note__end_of_data); + goto ok; + } else { + status = wuffs_base__make_status(wuffs_base__note__end_of_data); + goto ok; + } + if (a_dst != NULL) { + wuffs_base__frame_config__set( + a_dst, + wuffs_base__utility__make_rect_ie_u32( + 0, + 0, + self->private_impl.f_width, + self->private_impl.f_height), + ((wuffs_base__flicks)(0)), + 0, + 16, + 0, + false, + false, + 0); + } + self->private_impl.f_call_sequence = 64; + + ok: + self->private_impl.p_do_decode_frame_config[0] = 0; + goto exit; + } + + goto suspend; + suspend: + self->private_impl.p_do_decode_frame_config[0] = wuffs_base__status__is_suspension(&status) ? coro_susp_point : 0; + + goto exit; + exit: + if (a_src && a_src->data.ptr) { + a_src->meta.ri = ((size_t)(iop_a_src - a_src->data.ptr)); + } + + return status; +} + +// -------- func nie.decoder.decode_frame + +WUFFS_BASE__MAYBE_STATIC wuffs_base__status +wuffs_nie__decoder__decode_frame( + wuffs_nie__decoder* self, + wuffs_base__pixel_buffer* a_dst, + wuffs_base__io_buffer* a_src, + wuffs_base__pixel_blend a_blend, + wuffs_base__slice_u8 a_workbuf, + wuffs_base__decode_frame_options* a_opts) { + if (!self) { + return wuffs_base__make_status(wuffs_base__error__bad_receiver); + } + if (self->private_impl.magic != WUFFS_BASE__MAGIC) { + return wuffs_base__make_status( + (self->private_impl.magic == WUFFS_BASE__DISABLED) + ? wuffs_base__error__disabled_by_previous_error + : wuffs_base__error__initialize_not_called); + } + if (!a_dst || !a_src) { + self->private_impl.magic = WUFFS_BASE__DISABLED; + return wuffs_base__make_status(wuffs_base__error__bad_argument); + } + if ((self->private_impl.active_coroutine != 0) && + (self->private_impl.active_coroutine != 3)) { + self->private_impl.magic = WUFFS_BASE__DISABLED; + return wuffs_base__make_status(wuffs_base__error__interleaved_coroutine_calls); + } + self->private_impl.active_coroutine = 0; + wuffs_base__status status = wuffs_base__make_status(NULL); + + wuffs_base__status v_status = wuffs_base__make_status(NULL); + + uint32_t coro_susp_point = self->private_impl.p_decode_frame[0]; + switch (coro_susp_point) { + WUFFS_BASE__COROUTINE_SUSPENSION_POINT_0; + + while (true) { + { + wuffs_base__status t_0 = wuffs_nie__decoder__do_decode_frame(self, + a_dst, + a_src, + a_blend, + a_workbuf, + a_opts); + v_status = t_0; + } + if ((v_status.repr == wuffs_base__suspension__short_read) && (a_src && a_src->meta.closed)) { + status = wuffs_base__make_status(wuffs_nie__error__truncated_input); + goto exit; + } + status = v_status; + WUFFS_BASE__COROUTINE_SUSPENSION_POINT_MAYBE_SUSPEND(1); + } + + ok: + self->private_impl.p_decode_frame[0] = 0; + goto exit; + } + + goto suspend; + suspend: + self->private_impl.p_decode_frame[0] = wuffs_base__status__is_suspension(&status) ? coro_susp_point : 0; + self->private_impl.active_coroutine = wuffs_base__status__is_suspension(&status) ? 3 : 0; + + goto exit; + exit: + if (wuffs_base__status__is_error(&status)) { + self->private_impl.magic = WUFFS_BASE__DISABLED; + } + return status; +} + +// -------- func nie.decoder.do_decode_frame + +static wuffs_base__status +wuffs_nie__decoder__do_decode_frame( + wuffs_nie__decoder* self, + wuffs_base__pixel_buffer* a_dst, + wuffs_base__io_buffer* a_src, + wuffs_base__pixel_blend a_blend, + wuffs_base__slice_u8 a_workbuf, + wuffs_base__decode_frame_options* a_opts) { + wuffs_base__status status = wuffs_base__make_status(NULL); + + wuffs_base__status v_status = wuffs_base__make_status(NULL); + + uint32_t coro_susp_point = self->private_impl.p_do_decode_frame[0]; + switch (coro_susp_point) { + WUFFS_BASE__COROUTINE_SUSPENSION_POINT_0; + + if (self->private_impl.f_call_sequence == 64) { + } else if (self->private_impl.f_call_sequence < 64) { + WUFFS_BASE__COROUTINE_SUSPENSION_POINT(1); + status = wuffs_nie__decoder__do_decode_frame_config(self, NULL, a_src); + if (status.repr) { + goto suspend; + } + } else { + status = wuffs_base__make_status(wuffs_base__note__end_of_data); + goto ok; + } + self->private_impl.f_dst_x = 0; + self->private_impl.f_dst_y = 0; + v_status = wuffs_base__pixel_swizzler__prepare(&self->private_impl.f_swizzler, + wuffs_base__pixel_buffer__pixel_format(a_dst), + wuffs_base__pixel_buffer__palette(a_dst), + wuffs_base__utility__make_pixel_format(self->private_impl.f_pixfmt), + wuffs_base__utility__empty_slice_u8(), + a_blend); + if ( ! wuffs_base__status__is_ok(&v_status)) { + status = v_status; + if (wuffs_base__status__is_error(&status)) { + goto exit; + } else if (wuffs_base__status__is_suspension(&status)) { + status = wuffs_base__make_status(wuffs_base__error__cannot_return_a_suspension); + goto exit; + } + goto ok; + } + while (true) { + v_status = wuffs_nie__decoder__swizzle(self, a_dst, a_src); + if (wuffs_base__status__is_ok(&v_status)) { + goto label__0__break; + } else if (v_status.repr != wuffs_nie__note__internal_note_short_read) { + status = v_status; + if (wuffs_base__status__is_error(&status)) { + goto exit; + } else if (wuffs_base__status__is_suspension(&status)) { + status = wuffs_base__make_status(wuffs_base__error__cannot_return_a_suspension); + goto exit; + } + goto ok; + } + status = wuffs_base__make_status(wuffs_base__suspension__short_read); + WUFFS_BASE__COROUTINE_SUSPENSION_POINT_MAYBE_SUSPEND(2); + } + label__0__break:; + self->private_impl.f_call_sequence = 96; + + ok: + self->private_impl.p_do_decode_frame[0] = 0; + goto exit; + } + + goto suspend; + suspend: + self->private_impl.p_do_decode_frame[0] = wuffs_base__status__is_suspension(&status) ? coro_susp_point : 0; + + goto exit; + exit: + return status; +} + +// -------- func nie.decoder.swizzle + +static wuffs_base__status +wuffs_nie__decoder__swizzle( + wuffs_nie__decoder* self, + wuffs_base__pixel_buffer* a_dst, + wuffs_base__io_buffer* a_src) { + wuffs_base__status status = wuffs_base__make_status(NULL); + + wuffs_base__pixel_format v_dst_pixfmt = {0}; + uint32_t v_dst_bits_per_pixel = 0; + uint64_t v_dst_bytes_per_pixel = 0; + uint64_t v_dst_bytes_per_row = 0; + uint32_t v_src_bytes_per_pixel = 0; + wuffs_base__table_u8 v_tab = {0}; + wuffs_base__slice_u8 v_dst = {0}; + uint64_t v_i = 0; + uint64_t v_j = 0; + uint64_t v_n = 0; + + const uint8_t* iop_a_src = NULL; + const uint8_t* io0_a_src WUFFS_BASE__POTENTIALLY_UNUSED = NULL; + const uint8_t* io1_a_src WUFFS_BASE__POTENTIALLY_UNUSED = NULL; + const uint8_t* io2_a_src WUFFS_BASE__POTENTIALLY_UNUSED = NULL; + if (a_src && a_src->data.ptr) { + io0_a_src = a_src->data.ptr; + io1_a_src = io0_a_src + a_src->meta.ri; + iop_a_src = io1_a_src; + io2_a_src = io0_a_src + a_src->meta.wi; + } + + v_dst_pixfmt = wuffs_base__pixel_buffer__pixel_format(a_dst); + v_dst_bits_per_pixel = wuffs_base__pixel_format__bits_per_pixel(&v_dst_pixfmt); + if ((v_dst_bits_per_pixel & 7) != 0) { + status = wuffs_base__make_status(wuffs_base__error__unsupported_option); + goto exit; + } + v_dst_bytes_per_pixel = ((uint64_t)((v_dst_bits_per_pixel / 8))); + v_dst_bytes_per_row = (((uint64_t)(self->private_impl.f_width)) * v_dst_bytes_per_pixel); + v_tab = wuffs_base__pixel_buffer__plane(a_dst, 0); + while (true) { + if (self->private_impl.f_dst_x == self->private_impl.f_width) { + self->private_impl.f_dst_x = 0; + self->private_impl.f_dst_y += 1; + if (self->private_impl.f_dst_y >= self->private_impl.f_height) { + goto label__0__break; + } + } + v_dst = wuffs_base__table_u8__row_u32(v_tab, self->private_impl.f_dst_y); + if (v_dst_bytes_per_row < ((uint64_t)(v_dst.len))) { + v_dst = wuffs_base__slice_u8__subslice_j(v_dst, v_dst_bytes_per_row); + } + v_i = (((uint64_t)(self->private_impl.f_dst_x)) * v_dst_bytes_per_pixel); + if (v_i >= ((uint64_t)(v_dst.len))) { + v_src_bytes_per_pixel = 4; + if (self->private_impl.f_pixfmt == 2164308923) { + v_src_bytes_per_pixel = 8; + } + v_n = (((uint64_t)(io2_a_src - iop_a_src)) / ((uint64_t)(v_src_bytes_per_pixel))); + v_n = wuffs_base__u64__min(v_n, ((uint64_t)(((uint32_t)(self->private_impl.f_width - self->private_impl.f_dst_x))))); + v_j = v_n; + while (v_j >= 8) { + if (((uint64_t)(io2_a_src - iop_a_src)) >= ((uint64_t)((v_src_bytes_per_pixel * 8)))) { + iop_a_src += (v_src_bytes_per_pixel * 8); + } + v_j -= 8; + } + while (v_j > 0) { + if (((uint64_t)(io2_a_src - iop_a_src)) >= ((uint64_t)((v_src_bytes_per_pixel * 1)))) { + iop_a_src += (v_src_bytes_per_pixel * 1); + } + v_j -= 1; + } + } else { + v_n = wuffs_base__pixel_swizzler__swizzle_interleaved_from_reader( + &self->private_impl.f_swizzler, + wuffs_base__slice_u8__subslice_i(v_dst, v_i), + wuffs_base__pixel_buffer__palette(a_dst), + &iop_a_src, + io2_a_src); + } + if (v_n == 0) { + status = wuffs_base__make_status(wuffs_nie__note__internal_note_short_read); + goto ok; + } + wuffs_base__u32__sat_add_indirect(&self->private_impl.f_dst_x, ((uint32_t)((v_n & 4294967295)))); + } + label__0__break:; + status = wuffs_base__make_status(NULL); + goto ok; + + ok: + goto exit; + exit: + if (a_src && a_src->data.ptr) { + a_src->meta.ri = ((size_t)(iop_a_src - a_src->data.ptr)); + } + + return status; +} + +// -------- func nie.decoder.frame_dirty_rect + +WUFFS_BASE__MAYBE_STATIC wuffs_base__rect_ie_u32 +wuffs_nie__decoder__frame_dirty_rect( + const wuffs_nie__decoder* self) { + if (!self) { + return wuffs_base__utility__empty_rect_ie_u32(); + } + if ((self->private_impl.magic != WUFFS_BASE__MAGIC) && + (self->private_impl.magic != WUFFS_BASE__DISABLED)) { + return wuffs_base__utility__empty_rect_ie_u32(); + } + + return wuffs_base__utility__make_rect_ie_u32( + 0, + 0, + self->private_impl.f_width, + self->private_impl.f_height); +} + +// -------- func nie.decoder.num_animation_loops + +WUFFS_BASE__MAYBE_STATIC uint32_t +wuffs_nie__decoder__num_animation_loops( + const wuffs_nie__decoder* self) { + if (!self) { + return 0; + } + if ((self->private_impl.magic != WUFFS_BASE__MAGIC) && + (self->private_impl.magic != WUFFS_BASE__DISABLED)) { + return 0; + } + + return 0; +} + +// -------- func nie.decoder.num_decoded_frame_configs + +WUFFS_BASE__MAYBE_STATIC uint64_t +wuffs_nie__decoder__num_decoded_frame_configs( + const wuffs_nie__decoder* self) { + if (!self) { + return 0; + } + if ((self->private_impl.magic != WUFFS_BASE__MAGIC) && + (self->private_impl.magic != WUFFS_BASE__DISABLED)) { + return 0; + } + + if (self->private_impl.f_call_sequence > 32) { + return 1; + } + return 0; +} + +// -------- func nie.decoder.num_decoded_frames + +WUFFS_BASE__MAYBE_STATIC uint64_t +wuffs_nie__decoder__num_decoded_frames( + const wuffs_nie__decoder* self) { + if (!self) { + return 0; + } + if ((self->private_impl.magic != WUFFS_BASE__MAGIC) && + (self->private_impl.magic != WUFFS_BASE__DISABLED)) { + return 0; + } + + if (self->private_impl.f_call_sequence > 64) { + return 1; + } + return 0; +} + +// -------- func nie.decoder.restart_frame + +WUFFS_BASE__MAYBE_STATIC wuffs_base__status +wuffs_nie__decoder__restart_frame( + wuffs_nie__decoder* self, + uint64_t a_index, + uint64_t a_io_position) { + if (!self) { + return wuffs_base__make_status(wuffs_base__error__bad_receiver); + } + if (self->private_impl.magic != WUFFS_BASE__MAGIC) { + return wuffs_base__make_status( + (self->private_impl.magic == WUFFS_BASE__DISABLED) + ? wuffs_base__error__disabled_by_previous_error + : wuffs_base__error__initialize_not_called); + } + + if (self->private_impl.f_call_sequence < 32) { + return wuffs_base__make_status(wuffs_base__error__bad_call_sequence); + } + if ((a_index != 0) || (a_io_position != 16)) { + return wuffs_base__make_status(wuffs_base__error__bad_argument); + } + self->private_impl.f_call_sequence = 40; + return wuffs_base__make_status(NULL); +} + +// -------- func nie.decoder.set_report_metadata + +WUFFS_BASE__MAYBE_STATIC wuffs_base__empty_struct +wuffs_nie__decoder__set_report_metadata( + wuffs_nie__decoder* self, + uint32_t a_fourcc, + bool a_report) { + return wuffs_base__make_empty_struct(); +} + +// -------- func nie.decoder.tell_me_more + +WUFFS_BASE__MAYBE_STATIC wuffs_base__status +wuffs_nie__decoder__tell_me_more( + wuffs_nie__decoder* self, + wuffs_base__io_buffer* a_dst, + wuffs_base__more_information* a_minfo, + wuffs_base__io_buffer* a_src) { + if (!self) { + return wuffs_base__make_status(wuffs_base__error__bad_receiver); + } + if (self->private_impl.magic != WUFFS_BASE__MAGIC) { + return wuffs_base__make_status( + (self->private_impl.magic == WUFFS_BASE__DISABLED) + ? wuffs_base__error__disabled_by_previous_error + : wuffs_base__error__initialize_not_called); + } + if (!a_dst || !a_src) { + self->private_impl.magic = WUFFS_BASE__DISABLED; + return wuffs_base__make_status(wuffs_base__error__bad_argument); + } + if ((self->private_impl.active_coroutine != 0) && + (self->private_impl.active_coroutine != 4)) { + self->private_impl.magic = WUFFS_BASE__DISABLED; + return wuffs_base__make_status(wuffs_base__error__interleaved_coroutine_calls); + } + self->private_impl.active_coroutine = 0; + wuffs_base__status status = wuffs_base__make_status(NULL); + + status = wuffs_base__make_status(wuffs_base__error__no_more_information); + goto exit; + + goto ok; + ok: + goto exit; + exit: + if (wuffs_base__status__is_error(&status)) { + self->private_impl.magic = WUFFS_BASE__DISABLED; + } + return status; +} + +// -------- func nie.decoder.workbuf_len + +WUFFS_BASE__MAYBE_STATIC wuffs_base__range_ii_u64 +wuffs_nie__decoder__workbuf_len( + const wuffs_nie__decoder* self) { + if (!self) { + return wuffs_base__utility__empty_range_ii_u64(); + } + if ((self->private_impl.magic != WUFFS_BASE__MAGIC) && + (self->private_impl.magic != WUFFS_BASE__DISABLED)) { + return wuffs_base__utility__empty_range_ii_u64(); + } + + return wuffs_base__utility__make_range_ii_u64(0, 0); +} + +#endif // !defined(WUFFS_CONFIG__MODULES) || defined(WUFFS_CONFIG__MODULE__NIE) + +#if !defined(WUFFS_CONFIG__MODULES) || defined(WUFFS_CONFIG__MODULE__ZLIB) + +// ---------------- Status Codes Implementations + +const char wuffs_zlib__note__dictionary_required[] = "@zlib: dictionary required"; +const char wuffs_zlib__error__bad_checksum[] = "#zlib: bad checksum"; +const char wuffs_zlib__error__bad_compression_method[] = "#zlib: bad compression method"; +const char wuffs_zlib__error__bad_compression_window_size[] = "#zlib: bad compression window size"; +const char wuffs_zlib__error__bad_parity_check[] = "#zlib: bad parity check"; +const char wuffs_zlib__error__incorrect_dictionary[] = "#zlib: incorrect dictionary"; +const char wuffs_zlib__error__truncated_input[] = "#zlib: truncated input"; + +// ---------------- Private Consts + +#define WUFFS_ZLIB__QUIRKS_BASE 2113790976 + +#define WUFFS_ZLIB__QUIRKS_COUNT 1 + +// ---------------- Private Initializer Prototypes + +// ---------------- Private Function Prototypes + +static wuffs_base__status +wuffs_zlib__decoder__do_transform_io( + wuffs_zlib__decoder* self, + wuffs_base__io_buffer* a_dst, + wuffs_base__io_buffer* a_src, + wuffs_base__slice_u8 a_workbuf); + +// ---------------- VTables + +const wuffs_base__io_transformer__func_ptrs +wuffs_zlib__decoder__func_ptrs_for__wuffs_base__io_transformer = { + (wuffs_base__empty_struct(*)(void*, + uint32_t, + bool))(&wuffs_zlib__decoder__set_quirk_enabled), + (wuffs_base__status(*)(void*, + wuffs_base__io_buffer*, + wuffs_base__io_buffer*, + wuffs_base__slice_u8))(&wuffs_zlib__decoder__transform_io), + (wuffs_base__range_ii_u64(*)(const void*))(&wuffs_zlib__decoder__workbuf_len), +}; + +// ---------------- Initializer Implementations + +wuffs_base__status WUFFS_BASE__WARN_UNUSED_RESULT +wuffs_zlib__decoder__initialize( + wuffs_zlib__decoder* self, + size_t sizeof_star_self, + uint64_t wuffs_version, + uint32_t options){ + if (!self) { + return wuffs_base__make_status(wuffs_base__error__bad_receiver); + } + if (sizeof(*self) != sizeof_star_self) { + return wuffs_base__make_status(wuffs_base__error__bad_sizeof_receiver); + } + if (((wuffs_version >> 32) != WUFFS_VERSION_MAJOR) || + (((wuffs_version >> 16) & 0xFFFF) > WUFFS_VERSION_MINOR)) { + return wuffs_base__make_status(wuffs_base__error__bad_wuffs_version); + } + + if ((options & WUFFS_INITIALIZE__ALREADY_ZEROED) != 0) { + // The whole point of this if-check is to detect an uninitialized *self. + // We disable the warning on GCC. Clang-5.0 does not have this warning. +#if !defined(__clang__) && defined(__GNUC__) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wmaybe-uninitialized" +#endif + if (self->private_impl.magic != 0) { + return wuffs_base__make_status(wuffs_base__error__initialize_falsely_claimed_already_zeroed); + } +#if !defined(__clang__) && defined(__GNUC__) +#pragma GCC diagnostic pop +#endif + } else { + if ((options & WUFFS_INITIALIZE__LEAVE_INTERNAL_BUFFERS_UNINITIALIZED) == 0) { + memset(self, 0, sizeof(*self)); + options |= WUFFS_INITIALIZE__ALREADY_ZEROED; + } else { + memset(&(self->private_impl), 0, sizeof(self->private_impl)); + } + } + + { + wuffs_base__status z = wuffs_adler32__hasher__initialize( + &self->private_data.f_checksum, sizeof(self->private_data.f_checksum), WUFFS_VERSION, options); + if (z.repr) { + return z; + } + } + { + wuffs_base__status z = wuffs_adler32__hasher__initialize( + &self->private_data.f_dict_id_hasher, sizeof(self->private_data.f_dict_id_hasher), WUFFS_VERSION, options); + if (z.repr) { + return z; + } + } + { + wuffs_base__status z = wuffs_deflate__decoder__initialize( + &self->private_data.f_flate, sizeof(self->private_data.f_flate), WUFFS_VERSION, options); + if (z.repr) { + return z; + } + } + self->private_impl.magic = WUFFS_BASE__MAGIC; + self->private_impl.vtable_for__wuffs_base__io_transformer.vtable_name = + wuffs_base__io_transformer__vtable_name; + self->private_impl.vtable_for__wuffs_base__io_transformer.function_pointers = + (const void*)(&wuffs_zlib__decoder__func_ptrs_for__wuffs_base__io_transformer); + return wuffs_base__make_status(NULL); +} + +wuffs_zlib__decoder* +wuffs_zlib__decoder__alloc() { + wuffs_zlib__decoder* x = + (wuffs_zlib__decoder*)(calloc(sizeof(wuffs_zlib__decoder), 1)); + if (!x) { + return NULL; + } + if (wuffs_zlib__decoder__initialize( + x, sizeof(wuffs_zlib__decoder), WUFFS_VERSION, WUFFS_INITIALIZE__ALREADY_ZEROED).repr) { + free(x); + return NULL; + } + return x; +} + +size_t +sizeof__wuffs_zlib__decoder() { + return sizeof(wuffs_zlib__decoder); +} + +// ---------------- Function Implementations + +// -------- func zlib.decoder.dictionary_id + +WUFFS_BASE__MAYBE_STATIC uint32_t +wuffs_zlib__decoder__dictionary_id( + const wuffs_zlib__decoder* self) { + if (!self) { + return 0; + } + if ((self->private_impl.magic != WUFFS_BASE__MAGIC) && + (self->private_impl.magic != WUFFS_BASE__DISABLED)) { + return 0; + } + + return self->private_impl.f_dict_id_want; +} + +// -------- func zlib.decoder.add_dictionary + +WUFFS_BASE__MAYBE_STATIC wuffs_base__empty_struct +wuffs_zlib__decoder__add_dictionary( + wuffs_zlib__decoder* self, + wuffs_base__slice_u8 a_dict) { + if (!self) { + return wuffs_base__make_empty_struct(); + } + if (self->private_impl.magic != WUFFS_BASE__MAGIC) { + return wuffs_base__make_empty_struct(); + } + + if (self->private_impl.f_header_complete) { + self->private_impl.f_bad_call_sequence = true; + } else { + self->private_impl.f_dict_id_got = wuffs_adler32__hasher__update_u32(&self->private_data.f_dict_id_hasher, a_dict); + wuffs_deflate__decoder__add_history(&self->private_data.f_flate, a_dict); + } + self->private_impl.f_got_dictionary = true; + return wuffs_base__make_empty_struct(); +} + +// -------- func zlib.decoder.set_quirk_enabled + +WUFFS_BASE__MAYBE_STATIC wuffs_base__empty_struct +wuffs_zlib__decoder__set_quirk_enabled( + wuffs_zlib__decoder* self, + uint32_t a_quirk, + bool a_enabled) { + if (!self) { + return wuffs_base__make_empty_struct(); + } + if (self->private_impl.magic != WUFFS_BASE__MAGIC) { + return wuffs_base__make_empty_struct(); + } + + if (self->private_impl.f_header_complete) { + self->private_impl.f_bad_call_sequence = true; + } else if (a_quirk == 1) { + self->private_impl.f_ignore_checksum = a_enabled; + } else if (a_quirk >= 2113790976) { + a_quirk -= 2113790976; + if (a_quirk < 1) { + self->private_impl.f_quirks[a_quirk] = a_enabled; + } + } + return wuffs_base__make_empty_struct(); +} + +// -------- func zlib.decoder.workbuf_len + +WUFFS_BASE__MAYBE_STATIC wuffs_base__range_ii_u64 +wuffs_zlib__decoder__workbuf_len( + const wuffs_zlib__decoder* self) { + if (!self) { + return wuffs_base__utility__empty_range_ii_u64(); + } + if ((self->private_impl.magic != WUFFS_BASE__MAGIC) && + (self->private_impl.magic != WUFFS_BASE__DISABLED)) { + return wuffs_base__utility__empty_range_ii_u64(); + } + + return wuffs_base__utility__make_range_ii_u64(1, 1); +} + +// -------- func zlib.decoder.transform_io + +WUFFS_BASE__MAYBE_STATIC wuffs_base__status +wuffs_zlib__decoder__transform_io( + wuffs_zlib__decoder* self, + wuffs_base__io_buffer* a_dst, + wuffs_base__io_buffer* a_src, + wuffs_base__slice_u8 a_workbuf) { + if (!self) { + return wuffs_base__make_status(wuffs_base__error__bad_receiver); + } + if (self->private_impl.magic != WUFFS_BASE__MAGIC) { + return wuffs_base__make_status( + (self->private_impl.magic == WUFFS_BASE__DISABLED) + ? wuffs_base__error__disabled_by_previous_error + : wuffs_base__error__initialize_not_called); + } + if (!a_dst || !a_src) { + self->private_impl.magic = WUFFS_BASE__DISABLED; + return wuffs_base__make_status(wuffs_base__error__bad_argument); + } + if ((self->private_impl.active_coroutine != 0) && + (self->private_impl.active_coroutine != 1)) { + self->private_impl.magic = WUFFS_BASE__DISABLED; + return wuffs_base__make_status(wuffs_base__error__interleaved_coroutine_calls); + } + self->private_impl.active_coroutine = 0; + wuffs_base__status status = wuffs_base__make_status(NULL); + + wuffs_base__status v_status = wuffs_base__make_status(NULL); + + uint32_t coro_susp_point = self->private_impl.p_transform_io[0]; + switch (coro_susp_point) { + WUFFS_BASE__COROUTINE_SUSPENSION_POINT_0; + + while (true) { + { + wuffs_base__status t_0 = wuffs_zlib__decoder__do_transform_io(self, a_dst, a_src, a_workbuf); + v_status = t_0; + } + if ((v_status.repr == wuffs_base__suspension__short_read) && (a_src && a_src->meta.closed)) { + status = wuffs_base__make_status(wuffs_zlib__error__truncated_input); + goto exit; + } + status = v_status; + WUFFS_BASE__COROUTINE_SUSPENSION_POINT_MAYBE_SUSPEND(1); + } + + ok: + self->private_impl.p_transform_io[0] = 0; + goto exit; + } + + goto suspend; + suspend: + self->private_impl.p_transform_io[0] = wuffs_base__status__is_suspension(&status) ? coro_susp_point : 0; + self->private_impl.active_coroutine = wuffs_base__status__is_suspension(&status) ? 1 : 0; + + goto exit; + exit: + if (wuffs_base__status__is_error(&status)) { + self->private_impl.magic = WUFFS_BASE__DISABLED; + } + return status; +} + +// -------- func zlib.decoder.do_transform_io + +static wuffs_base__status +wuffs_zlib__decoder__do_transform_io( + wuffs_zlib__decoder* self, + wuffs_base__io_buffer* a_dst, + wuffs_base__io_buffer* a_src, + wuffs_base__slice_u8 a_workbuf) { + wuffs_base__status status = wuffs_base__make_status(NULL); + + uint16_t v_x = 0; + uint32_t v_checksum_got = 0; + wuffs_base__status v_status = wuffs_base__make_status(NULL); + uint32_t v_checksum_want = 0; + uint64_t v_mark = 0; + + uint8_t* iop_a_dst = NULL; + uint8_t* io0_a_dst WUFFS_BASE__POTENTIALLY_UNUSED = NULL; + uint8_t* io1_a_dst WUFFS_BASE__POTENTIALLY_UNUSED = NULL; + uint8_t* io2_a_dst WUFFS_BASE__POTENTIALLY_UNUSED = NULL; + if (a_dst && a_dst->data.ptr) { + io0_a_dst = a_dst->data.ptr; + io1_a_dst = io0_a_dst + a_dst->meta.wi; + iop_a_dst = io1_a_dst; + io2_a_dst = io0_a_dst + a_dst->data.len; + if (a_dst->meta.closed) { + io2_a_dst = iop_a_dst; + } + } + const uint8_t* iop_a_src = NULL; + const uint8_t* io0_a_src WUFFS_BASE__POTENTIALLY_UNUSED = NULL; + const uint8_t* io1_a_src WUFFS_BASE__POTENTIALLY_UNUSED = NULL; + const uint8_t* io2_a_src WUFFS_BASE__POTENTIALLY_UNUSED = NULL; + if (a_src && a_src->data.ptr) { + io0_a_src = a_src->data.ptr; + io1_a_src = io0_a_src + a_src->meta.ri; + iop_a_src = io1_a_src; + io2_a_src = io0_a_src + a_src->meta.wi; + } + + uint32_t coro_susp_point = self->private_impl.p_do_transform_io[0]; + if (coro_susp_point) { + v_checksum_got = self->private_data.s_do_transform_io[0].v_checksum_got; + } + switch (coro_susp_point) { + WUFFS_BASE__COROUTINE_SUSPENSION_POINT_0; + + if (self->private_impl.f_bad_call_sequence) { + status = wuffs_base__make_status(wuffs_base__error__bad_call_sequence); + goto exit; + } else if (self->private_impl.f_quirks[0]) { + } else if ( ! self->private_impl.f_want_dictionary) { + { + WUFFS_BASE__COROUTINE_SUSPENSION_POINT(1); + uint16_t t_0; + if (WUFFS_BASE__LIKELY(io2_a_src - iop_a_src >= 2)) { + t_0 = wuffs_base__peek_u16be__no_bounds_check(iop_a_src); + iop_a_src += 2; + } else { + self->private_data.s_do_transform_io[0].scratch = 0; + WUFFS_BASE__COROUTINE_SUSPENSION_POINT(2); + while (true) { + if (WUFFS_BASE__UNLIKELY(iop_a_src == io2_a_src)) { + status = wuffs_base__make_status(wuffs_base__suspension__short_read); + goto suspend; + } + uint64_t* scratch = &self->private_data.s_do_transform_io[0].scratch; + uint32_t num_bits_0 = ((uint32_t)(*scratch & 0xFF)); + *scratch >>= 8; + *scratch <<= 8; + *scratch |= ((uint64_t)(*iop_a_src++)) << (56 - num_bits_0); + if (num_bits_0 == 8) { + t_0 = ((uint16_t)(*scratch >> 48)); + break; + } + num_bits_0 += 8; + *scratch |= ((uint64_t)(num_bits_0)); + } + } + v_x = t_0; + } + if (((v_x >> 8) & 15) != 8) { + status = wuffs_base__make_status(wuffs_zlib__error__bad_compression_method); + goto exit; + } + if ((v_x >> 12) > 7) { + status = wuffs_base__make_status(wuffs_zlib__error__bad_compression_window_size); + goto exit; + } + if ((v_x % 31) != 0) { + status = wuffs_base__make_status(wuffs_zlib__error__bad_parity_check); + goto exit; + } + self->private_impl.f_want_dictionary = ((v_x & 32) != 0); + if (self->private_impl.f_want_dictionary) { + self->private_impl.f_dict_id_got = 1; + { + WUFFS_BASE__COROUTINE_SUSPENSION_POINT(3); + uint32_t t_1; + if (WUFFS_BASE__LIKELY(io2_a_src - iop_a_src >= 4)) { + t_1 = wuffs_base__peek_u32be__no_bounds_check(iop_a_src); + iop_a_src += 4; + } else { + self->private_data.s_do_transform_io[0].scratch = 0; + WUFFS_BASE__COROUTINE_SUSPENSION_POINT(4); + while (true) { + if (WUFFS_BASE__UNLIKELY(iop_a_src == io2_a_src)) { + status = wuffs_base__make_status(wuffs_base__suspension__short_read); + goto suspend; + } + uint64_t* scratch = &self->private_data.s_do_transform_io[0].scratch; + uint32_t num_bits_1 = ((uint32_t)(*scratch & 0xFF)); + *scratch >>= 8; + *scratch <<= 8; + *scratch |= ((uint64_t)(*iop_a_src++)) << (56 - num_bits_1); + if (num_bits_1 == 24) { + t_1 = ((uint32_t)(*scratch >> 32)); + break; + } + num_bits_1 += 8; + *scratch |= ((uint64_t)(num_bits_1)); + } + } + self->private_impl.f_dict_id_want = t_1; + } + status = wuffs_base__make_status(wuffs_zlib__note__dictionary_required); + goto ok; + } else if (self->private_impl.f_got_dictionary) { + status = wuffs_base__make_status(wuffs_zlib__error__incorrect_dictionary); + goto exit; + } + } else if (self->private_impl.f_dict_id_got != self->private_impl.f_dict_id_want) { + if (self->private_impl.f_got_dictionary) { + status = wuffs_base__make_status(wuffs_zlib__error__incorrect_dictionary); + goto exit; + } + status = wuffs_base__make_status(wuffs_zlib__note__dictionary_required); + goto ok; + } + self->private_impl.f_header_complete = true; + while (true) { + v_mark = ((uint64_t)(iop_a_dst - io0_a_dst)); + { + if (a_dst) { + a_dst->meta.wi = ((size_t)(iop_a_dst - a_dst->data.ptr)); + } + if (a_src) { + a_src->meta.ri = ((size_t)(iop_a_src - a_src->data.ptr)); + } + wuffs_base__status t_2 = wuffs_deflate__decoder__transform_io(&self->private_data.f_flate, a_dst, a_src, a_workbuf); + v_status = t_2; + if (a_dst) { + iop_a_dst = a_dst->data.ptr + a_dst->meta.wi; + } + if (a_src) { + iop_a_src = a_src->data.ptr + a_src->meta.ri; + } + } + if ( ! self->private_impl.f_ignore_checksum && ! self->private_impl.f_quirks[0]) { + v_checksum_got = wuffs_adler32__hasher__update_u32(&self->private_data.f_checksum, wuffs_base__io__since(v_mark, ((uint64_t)(iop_a_dst - io0_a_dst)), io0_a_dst)); + } + if (wuffs_base__status__is_ok(&v_status)) { + goto label__0__break; + } + status = v_status; + WUFFS_BASE__COROUTINE_SUSPENSION_POINT_MAYBE_SUSPEND(5); + } + label__0__break:; + if ( ! self->private_impl.f_quirks[0]) { + { + WUFFS_BASE__COROUTINE_SUSPENSION_POINT(6); + uint32_t t_3; + if (WUFFS_BASE__LIKELY(io2_a_src - iop_a_src >= 4)) { + t_3 = wuffs_base__peek_u32be__no_bounds_check(iop_a_src); + iop_a_src += 4; + } else { + self->private_data.s_do_transform_io[0].scratch = 0; + WUFFS_BASE__COROUTINE_SUSPENSION_POINT(7); + while (true) { + if (WUFFS_BASE__UNLIKELY(iop_a_src == io2_a_src)) { + status = wuffs_base__make_status(wuffs_base__suspension__short_read); + goto suspend; + } + uint64_t* scratch = &self->private_data.s_do_transform_io[0].scratch; + uint32_t num_bits_3 = ((uint32_t)(*scratch & 0xFF)); + *scratch >>= 8; + *scratch <<= 8; + *scratch |= ((uint64_t)(*iop_a_src++)) << (56 - num_bits_3); + if (num_bits_3 == 24) { + t_3 = ((uint32_t)(*scratch >> 32)); + break; + } + num_bits_3 += 8; + *scratch |= ((uint64_t)(num_bits_3)); + } + } + v_checksum_want = t_3; + } + if ( ! self->private_impl.f_ignore_checksum && (v_checksum_got != v_checksum_want)) { + status = wuffs_base__make_status(wuffs_zlib__error__bad_checksum); + goto exit; + } + } + + ok: + self->private_impl.p_do_transform_io[0] = 0; + goto exit; + } + + goto suspend; + suspend: + self->private_impl.p_do_transform_io[0] = wuffs_base__status__is_suspension(&status) ? coro_susp_point : 0; + self->private_data.s_do_transform_io[0].v_checksum_got = v_checksum_got; + + goto exit; + exit: + if (a_dst && a_dst->data.ptr) { + a_dst->meta.wi = ((size_t)(iop_a_dst - a_dst->data.ptr)); + } + if (a_src && a_src->data.ptr) { + a_src->meta.ri = ((size_t)(iop_a_src - a_src->data.ptr)); + } + + return status; +} + +#endif // !defined(WUFFS_CONFIG__MODULES) || defined(WUFFS_CONFIG__MODULE__ZLIB) + +#if !defined(WUFFS_CONFIG__MODULES) || defined(WUFFS_CONFIG__MODULE__PNG) + +// ---------------- Status Codes Implementations + +const char wuffs_png__error__bad_animation_sequence_number[] = "#png: bad animation sequence number"; +const char wuffs_png__error__bad_checksum[] = "#png: bad checksum"; +const char wuffs_png__error__bad_chunk[] = "#png: bad chunk"; +const char wuffs_png__error__bad_filter[] = "#png: bad filter"; +const char wuffs_png__error__bad_header[] = "#png: bad header"; +const char wuffs_png__error__bad_text_chunk_not_latin_1[] = "#png: bad text chunk (not Latin-1)"; +const char wuffs_png__error__missing_palette[] = "#png: missing palette"; +const char wuffs_png__error__truncated_input[] = "#png: truncated input"; +const char wuffs_png__error__unsupported_cgbi_extension[] = "#png: unsupported CgBI extension"; +const char wuffs_png__error__unsupported_png_compression_method[] = "#png: unsupported PNG compression method"; +const char wuffs_png__error__unsupported_png_file[] = "#png: unsupported PNG file"; +const char wuffs_png__error__internal_error_inconsistent_i_o[] = "#png: internal error: inconsistent I/O"; +const char wuffs_png__error__internal_error_inconsistent_chunk_type[] = "#png: internal error: inconsistent chunk type"; +const char wuffs_png__error__internal_error_inconsistent_frame_bounds[] = "#png: internal error: inconsistent frame bounds"; +const char wuffs_png__error__internal_error_inconsistent_workbuf_length[] = "#png: internal error: inconsistent workbuf length"; +const char wuffs_png__error__internal_error_zlib_decoder_did_not_exhaust_its_input[] = "#png: internal error: zlib decoder did not exhaust its input"; + +// ---------------- Private Consts + +#define WUFFS_PNG__ANCILLARY_BIT 32 + +static const uint8_t +WUFFS_PNG__INTERLACING[8][6] WUFFS_BASE__POTENTIALLY_UNUSED = { + { + 0, 0, 0, 0, 0, 0, + }, { + 3, 7, 0, 3, 7, 0, + }, { + 3, 3, 4, 3, 7, 0, + }, { + 2, 3, 0, 3, 3, 4, + }, { + 2, 1, 2, 2, 3, 0, + }, { + 1, 1, 0, 2, 1, 2, + }, { + 1, 0, 1, 1, 1, 0, + }, { + 0, 0, 0, 1, 0, 1, + }, +}; + +static const uint8_t +WUFFS_PNG__LOW_BIT_DEPTH_MULTIPLIERS[8] WUFFS_BASE__POTENTIALLY_UNUSED = { + 0, 255, 85, 0, 17, 0, 0, 0, +}; + +static const uint8_t +WUFFS_PNG__LOW_BIT_DEPTH_NUM_PACKS[8] WUFFS_BASE__POTENTIALLY_UNUSED = { + 0, 8, 4, 0, 2, 0, 0, 0, +}; + +static const uint8_t +WUFFS_PNG__NUM_CHANNELS[8] WUFFS_BASE__POTENTIALLY_UNUSED = { + 1, 0, 3, 1, 2, 0, 4, 0, +}; + +static const uint16_t +WUFFS_PNG__LATIN_1[256] WUFFS_BASE__POTENTIALLY_UNUSED = { + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 32, 33, 34, 35, 36, 37, 38, 39, + 40, 41, 42, 43, 44, 45, 46, 47, + 48, 49, 50, 51, 52, 53, 54, 55, + 56, 57, 58, 59, 60, 61, 62, 63, + 64, 65, 66, 67, 68, 69, 70, 71, + 72, 73, 74, 75, 76, 77, 78, 79, + 80, 81, 82, 83, 84, 85, 86, 87, + 88, 89, 90, 91, 92, 93, 94, 95, + 96, 97, 98, 99, 100, 101, 102, 103, + 104, 105, 106, 107, 108, 109, 110, 111, + 112, 113, 114, 115, 116, 117, 118, 119, + 120, 121, 122, 123, 124, 125, 126, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 41410, 41666, 41922, 42178, 42434, 42690, 42946, + 43202, 43458, 43714, 43970, 44226, 44482, 44738, 44994, + 45250, 45506, 45762, 46018, 46274, 46530, 46786, 47042, + 47298, 47554, 47810, 48066, 48322, 48578, 48834, 49090, + 32963, 33219, 33475, 33731, 33987, 34243, 34499, 34755, + 35011, 35267, 35523, 35779, 36035, 36291, 36547, 36803, + 37059, 37315, 37571, 37827, 38083, 38339, 38595, 38851, + 39107, 39363, 39619, 39875, 40131, 40387, 40643, 40899, + 41155, 41411, 41667, 41923, 42179, 42435, 42691, 42947, + 43203, 43459, 43715, 43971, 44227, 44483, 44739, 44995, + 45251, 45507, 45763, 46019, 46275, 46531, 46787, 47043, + 47299, 47555, 47811, 48067, 48323, 48579, 48835, 49091, +}; + +// ---------------- Private Initializer Prototypes + +// ---------------- Private Function Prototypes + +#if defined(WUFFS_BASE__CPU_ARCH__ARM_NEON) +static wuffs_base__empty_struct +wuffs_png__decoder__filter_1_distance_4_arm_neon( + wuffs_png__decoder* self, + wuffs_base__slice_u8 a_curr); +#endif // defined(WUFFS_BASE__CPU_ARCH__ARM_NEON) + +#if defined(WUFFS_BASE__CPU_ARCH__ARM_NEON) +static wuffs_base__empty_struct +wuffs_png__decoder__filter_3_distance_4_arm_neon( + wuffs_png__decoder* self, + wuffs_base__slice_u8 a_curr, + wuffs_base__slice_u8 a_prev); +#endif // defined(WUFFS_BASE__CPU_ARCH__ARM_NEON) + +#if defined(WUFFS_BASE__CPU_ARCH__ARM_NEON) +static wuffs_base__empty_struct +wuffs_png__decoder__filter_4_distance_3_arm_neon( + wuffs_png__decoder* self, + wuffs_base__slice_u8 a_curr, + wuffs_base__slice_u8 a_prev); +#endif // defined(WUFFS_BASE__CPU_ARCH__ARM_NEON) + +#if defined(WUFFS_BASE__CPU_ARCH__ARM_NEON) +static wuffs_base__empty_struct +wuffs_png__decoder__filter_4_distance_4_arm_neon( + wuffs_png__decoder* self, + wuffs_base__slice_u8 a_curr, + wuffs_base__slice_u8 a_prev); +#endif // defined(WUFFS_BASE__CPU_ARCH__ARM_NEON) + +static wuffs_base__empty_struct +wuffs_png__decoder__filter_1( + wuffs_png__decoder* self, + wuffs_base__slice_u8 a_curr); + +static wuffs_base__empty_struct +wuffs_png__decoder__filter_1__choosy_default( + wuffs_png__decoder* self, + wuffs_base__slice_u8 a_curr); + +static wuffs_base__empty_struct +wuffs_png__decoder__filter_1_distance_3_fallback( + wuffs_png__decoder* self, + wuffs_base__slice_u8 a_curr); + +static wuffs_base__empty_struct +wuffs_png__decoder__filter_1_distance_4_fallback( + wuffs_png__decoder* self, + wuffs_base__slice_u8 a_curr); + +static wuffs_base__empty_struct +wuffs_png__decoder__filter_2( + wuffs_png__decoder* self, + wuffs_base__slice_u8 a_curr, + wuffs_base__slice_u8 a_prev); + +static wuffs_base__empty_struct +wuffs_png__decoder__filter_3( + wuffs_png__decoder* self, + wuffs_base__slice_u8 a_curr, + wuffs_base__slice_u8 a_prev); + +static wuffs_base__empty_struct +wuffs_png__decoder__filter_3__choosy_default( + wuffs_png__decoder* self, + wuffs_base__slice_u8 a_curr, + wuffs_base__slice_u8 a_prev); + +static wuffs_base__empty_struct +wuffs_png__decoder__filter_3_distance_3_fallback( + wuffs_png__decoder* self, + wuffs_base__slice_u8 a_curr, + wuffs_base__slice_u8 a_prev); + +static wuffs_base__empty_struct +wuffs_png__decoder__filter_3_distance_4_fallback( + wuffs_png__decoder* self, + wuffs_base__slice_u8 a_curr, + wuffs_base__slice_u8 a_prev); + +static wuffs_base__empty_struct +wuffs_png__decoder__filter_4( + wuffs_png__decoder* self, + wuffs_base__slice_u8 a_curr, + wuffs_base__slice_u8 a_prev); + +static wuffs_base__empty_struct +wuffs_png__decoder__filter_4__choosy_default( + wuffs_png__decoder* self, + wuffs_base__slice_u8 a_curr, + wuffs_base__slice_u8 a_prev); + +static wuffs_base__empty_struct +wuffs_png__decoder__filter_4_distance_3_fallback( + wuffs_png__decoder* self, + wuffs_base__slice_u8 a_curr, + wuffs_base__slice_u8 a_prev); + +static wuffs_base__empty_struct +wuffs_png__decoder__filter_4_distance_4_fallback( + wuffs_png__decoder* self, + wuffs_base__slice_u8 a_curr, + wuffs_base__slice_u8 a_prev); + +#if defined(WUFFS_BASE__CPU_ARCH__X86_FAMILY) +static wuffs_base__empty_struct +wuffs_png__decoder__filter_1_distance_4_x86_sse42( + wuffs_png__decoder* self, + wuffs_base__slice_u8 a_curr); +#endif // defined(WUFFS_BASE__CPU_ARCH__X86_FAMILY) + +#if defined(WUFFS_BASE__CPU_ARCH__X86_FAMILY) +static wuffs_base__empty_struct +wuffs_png__decoder__filter_3_distance_4_x86_sse42( + wuffs_png__decoder* self, + wuffs_base__slice_u8 a_curr, + wuffs_base__slice_u8 a_prev); +#endif // defined(WUFFS_BASE__CPU_ARCH__X86_FAMILY) + +#if defined(WUFFS_BASE__CPU_ARCH__X86_FAMILY) +static wuffs_base__empty_struct +wuffs_png__decoder__filter_4_distance_3_x86_sse42( + wuffs_png__decoder* self, + wuffs_base__slice_u8 a_curr, + wuffs_base__slice_u8 a_prev); +#endif // defined(WUFFS_BASE__CPU_ARCH__X86_FAMILY) + +#if defined(WUFFS_BASE__CPU_ARCH__X86_FAMILY) +static wuffs_base__empty_struct +wuffs_png__decoder__filter_4_distance_4_x86_sse42( + wuffs_png__decoder* self, + wuffs_base__slice_u8 a_curr, + wuffs_base__slice_u8 a_prev); +#endif // defined(WUFFS_BASE__CPU_ARCH__X86_FAMILY) + +static wuffs_base__status +wuffs_png__decoder__do_decode_image_config( + wuffs_png__decoder* self, + wuffs_base__image_config* a_dst, + wuffs_base__io_buffer* a_src); + +static wuffs_base__status +wuffs_png__decoder__decode_ihdr( + wuffs_png__decoder* self, + wuffs_base__io_buffer* a_src); + +static wuffs_base__empty_struct +wuffs_png__decoder__assign_filter_distance( + wuffs_png__decoder* self); + +static uint64_t +wuffs_png__decoder__calculate_bytes_per_row( + const wuffs_png__decoder* self, + uint32_t a_width); + +static wuffs_base__empty_struct +wuffs_png__decoder__choose_filter_implementations( + wuffs_png__decoder* self); + +static wuffs_base__status +wuffs_png__decoder__decode_other_chunk( + wuffs_png__decoder* self, + wuffs_base__io_buffer* a_src, + bool a_framy); + +static wuffs_base__status +wuffs_png__decoder__decode_actl( + wuffs_png__decoder* self, + wuffs_base__io_buffer* a_src); + +static wuffs_base__status +wuffs_png__decoder__decode_chrm( + wuffs_png__decoder* self, + wuffs_base__io_buffer* a_src); + +static wuffs_base__status +wuffs_png__decoder__decode_exif( + wuffs_png__decoder* self, + wuffs_base__io_buffer* a_src); + +static wuffs_base__status +wuffs_png__decoder__decode_fctl( + wuffs_png__decoder* self, + wuffs_base__io_buffer* a_src); + +static wuffs_base__status +wuffs_png__decoder__decode_gama( + wuffs_png__decoder* self, + wuffs_base__io_buffer* a_src); + +static wuffs_base__status +wuffs_png__decoder__decode_iccp( + wuffs_png__decoder* self, + wuffs_base__io_buffer* a_src); + +static wuffs_base__status +wuffs_png__decoder__decode_plte( + wuffs_png__decoder* self, + wuffs_base__io_buffer* a_src); + +static wuffs_base__status +wuffs_png__decoder__decode_srgb( + wuffs_png__decoder* self, + wuffs_base__io_buffer* a_src); + +static wuffs_base__status +wuffs_png__decoder__decode_trns( + wuffs_png__decoder* self, + wuffs_base__io_buffer* a_src); + +static wuffs_base__status +wuffs_png__decoder__do_decode_frame_config( + wuffs_png__decoder* self, + wuffs_base__frame_config* a_dst, + wuffs_base__io_buffer* a_src); + +static wuffs_base__status +wuffs_png__decoder__skip_frame( + wuffs_png__decoder* self, + wuffs_base__io_buffer* a_src); + +static wuffs_base__status +wuffs_png__decoder__do_decode_frame( + wuffs_png__decoder* self, + wuffs_base__pixel_buffer* a_dst, + wuffs_base__io_buffer* a_src, + wuffs_base__pixel_blend a_blend, + wuffs_base__slice_u8 a_workbuf, + wuffs_base__decode_frame_options* a_opts); + +static wuffs_base__status +wuffs_png__decoder__decode_pass( + wuffs_png__decoder* self, + wuffs_base__io_buffer* a_src, + wuffs_base__slice_u8 a_workbuf); + +static wuffs_base__status +wuffs_png__decoder__do_tell_me_more( + wuffs_png__decoder* self, + wuffs_base__io_buffer* a_dst, + wuffs_base__more_information* a_minfo, + wuffs_base__io_buffer* a_src); + +static wuffs_base__status +wuffs_png__decoder__filter_and_swizzle( + wuffs_png__decoder* self, + wuffs_base__pixel_buffer* a_dst, + wuffs_base__slice_u8 a_workbuf); + +static wuffs_base__status +wuffs_png__decoder__filter_and_swizzle__choosy_default( + wuffs_png__decoder* self, + wuffs_base__pixel_buffer* a_dst, + wuffs_base__slice_u8 a_workbuf); + +static wuffs_base__status +wuffs_png__decoder__filter_and_swizzle_tricky( + wuffs_png__decoder* self, + wuffs_base__pixel_buffer* a_dst, + wuffs_base__slice_u8 a_workbuf); + +// ---------------- VTables + +const wuffs_base__image_decoder__func_ptrs +wuffs_png__decoder__func_ptrs_for__wuffs_base__image_decoder = { + (wuffs_base__status(*)(void*, + wuffs_base__pixel_buffer*, + wuffs_base__io_buffer*, + wuffs_base__pixel_blend, + wuffs_base__slice_u8, + wuffs_base__decode_frame_options*))(&wuffs_png__decoder__decode_frame), + (wuffs_base__status(*)(void*, + wuffs_base__frame_config*, + wuffs_base__io_buffer*))(&wuffs_png__decoder__decode_frame_config), + (wuffs_base__status(*)(void*, + wuffs_base__image_config*, + wuffs_base__io_buffer*))(&wuffs_png__decoder__decode_image_config), + (wuffs_base__rect_ie_u32(*)(const void*))(&wuffs_png__decoder__frame_dirty_rect), + (uint32_t(*)(const void*))(&wuffs_png__decoder__num_animation_loops), + (uint64_t(*)(const void*))(&wuffs_png__decoder__num_decoded_frame_configs), + (uint64_t(*)(const void*))(&wuffs_png__decoder__num_decoded_frames), + (wuffs_base__status(*)(void*, + uint64_t, + uint64_t))(&wuffs_png__decoder__restart_frame), + (wuffs_base__empty_struct(*)(void*, + uint32_t, + bool))(&wuffs_png__decoder__set_quirk_enabled), + (wuffs_base__empty_struct(*)(void*, + uint32_t, + bool))(&wuffs_png__decoder__set_report_metadata), + (wuffs_base__status(*)(void*, + wuffs_base__io_buffer*, + wuffs_base__more_information*, + wuffs_base__io_buffer*))(&wuffs_png__decoder__tell_me_more), + (wuffs_base__range_ii_u64(*)(const void*))(&wuffs_png__decoder__workbuf_len), +}; + +// ---------------- Initializer Implementations + +wuffs_base__status WUFFS_BASE__WARN_UNUSED_RESULT +wuffs_png__decoder__initialize( + wuffs_png__decoder* self, + size_t sizeof_star_self, + uint64_t wuffs_version, + uint32_t options){ + if (!self) { + return wuffs_base__make_status(wuffs_base__error__bad_receiver); + } + if (sizeof(*self) != sizeof_star_self) { + return wuffs_base__make_status(wuffs_base__error__bad_sizeof_receiver); + } + if (((wuffs_version >> 32) != WUFFS_VERSION_MAJOR) || + (((wuffs_version >> 16) & 0xFFFF) > WUFFS_VERSION_MINOR)) { + return wuffs_base__make_status(wuffs_base__error__bad_wuffs_version); + } + + if ((options & WUFFS_INITIALIZE__ALREADY_ZEROED) != 0) { + // The whole point of this if-check is to detect an uninitialized *self. + // We disable the warning on GCC. Clang-5.0 does not have this warning. +#if !defined(__clang__) && defined(__GNUC__) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wmaybe-uninitialized" +#endif + if (self->private_impl.magic != 0) { + return wuffs_base__make_status(wuffs_base__error__initialize_falsely_claimed_already_zeroed); + } +#if !defined(__clang__) && defined(__GNUC__) +#pragma GCC diagnostic pop +#endif + } else { + if ((options & WUFFS_INITIALIZE__LEAVE_INTERNAL_BUFFERS_UNINITIALIZED) == 0) { + memset(self, 0, sizeof(*self)); + options |= WUFFS_INITIALIZE__ALREADY_ZEROED; + } else { + memset(&(self->private_impl), 0, sizeof(self->private_impl)); + } + } + + self->private_impl.choosy_filter_1 = &wuffs_png__decoder__filter_1__choosy_default; + self->private_impl.choosy_filter_3 = &wuffs_png__decoder__filter_3__choosy_default; + self->private_impl.choosy_filter_4 = &wuffs_png__decoder__filter_4__choosy_default; + self->private_impl.choosy_filter_and_swizzle = &wuffs_png__decoder__filter_and_swizzle__choosy_default; + + { + wuffs_base__status z = wuffs_crc32__ieee_hasher__initialize( + &self->private_data.f_crc32, sizeof(self->private_data.f_crc32), WUFFS_VERSION, options); + if (z.repr) { + return z; + } + } + { + wuffs_base__status z = wuffs_zlib__decoder__initialize( + &self->private_data.f_zlib, sizeof(self->private_data.f_zlib), WUFFS_VERSION, options); + if (z.repr) { + return z; + } + } + self->private_impl.magic = WUFFS_BASE__MAGIC; + self->private_impl.vtable_for__wuffs_base__image_decoder.vtable_name = + wuffs_base__image_decoder__vtable_name; + self->private_impl.vtable_for__wuffs_base__image_decoder.function_pointers = + (const void*)(&wuffs_png__decoder__func_ptrs_for__wuffs_base__image_decoder); + return wuffs_base__make_status(NULL); +} + +wuffs_png__decoder* +wuffs_png__decoder__alloc() { + wuffs_png__decoder* x = + (wuffs_png__decoder*)(calloc(sizeof(wuffs_png__decoder), 1)); + if (!x) { + return NULL; + } + if (wuffs_png__decoder__initialize( + x, sizeof(wuffs_png__decoder), WUFFS_VERSION, WUFFS_INITIALIZE__ALREADY_ZEROED).repr) { + free(x); + return NULL; + } + return x; +} + +size_t +sizeof__wuffs_png__decoder() { + return sizeof(wuffs_png__decoder); +} + +// ---------------- Function Implementations + +// ‼ WUFFS MULTI-FILE SECTION +arm_neon +// -------- func png.decoder.filter_1_distance_4_arm_neon + +#if defined(WUFFS_BASE__CPU_ARCH__ARM_NEON) +static wuffs_base__empty_struct +wuffs_png__decoder__filter_1_distance_4_arm_neon( + wuffs_png__decoder* self, + wuffs_base__slice_u8 a_curr) { + wuffs_base__slice_u8 v_curr = {0}; + uint8x8_t v_fa = {0}; + uint8x8_t v_fx = {0}; + + { + wuffs_base__slice_u8 i_slice_curr = a_curr; + v_curr.ptr = i_slice_curr.ptr; + v_curr.len = 4; + uint8_t* i_end0_curr = v_curr.ptr + (((i_slice_curr.len - (size_t)(v_curr.ptr - i_slice_curr.ptr)) / 8) * 8); + while (v_curr.ptr < i_end0_curr) { + v_fx = vreinterpret_u8_u32(vdup_n_u32(wuffs_base__peek_u32le__no_bounds_check(v_curr.ptr))); + v_fx = vadd_u8(v_fx, v_fa); + wuffs_base__poke_u32le__no_bounds_check(v_curr.ptr, vget_lane_u32(vreinterpret_u32_u8(v_fx), 0)); + v_fa = v_fx; + v_curr.ptr += 4; + v_fx = vreinterpret_u8_u32(vdup_n_u32(wuffs_base__peek_u32le__no_bounds_check(v_curr.ptr))); + v_fx = vadd_u8(v_fx, v_fa); + wuffs_base__poke_u32le__no_bounds_check(v_curr.ptr, vget_lane_u32(vreinterpret_u32_u8(v_fx), 0)); + v_fa = v_fx; + v_curr.ptr += 4; + } + v_curr.len = 4; + uint8_t* i_end1_curr = v_curr.ptr + (((i_slice_curr.len - (size_t)(v_curr.ptr - i_slice_curr.ptr)) / 4) * 4); + while (v_curr.ptr < i_end1_curr) { + v_fx = vreinterpret_u8_u32(vdup_n_u32(wuffs_base__peek_u32le__no_bounds_check(v_curr.ptr))); + v_fx = vadd_u8(v_fx, v_fa); + wuffs_base__poke_u32le__no_bounds_check(v_curr.ptr, vget_lane_u32(vreinterpret_u32_u8(v_fx), 0)); + v_fa = v_fx; + v_curr.ptr += 4; + } + v_curr.len = 0; + } + return wuffs_base__make_empty_struct(); +} +#endif // defined(WUFFS_BASE__CPU_ARCH__ARM_NEON) +// ‼ WUFFS MULTI-FILE SECTION -arm_neon + +// ‼ WUFFS MULTI-FILE SECTION +arm_neon +// -------- func png.decoder.filter_3_distance_4_arm_neon + +#if defined(WUFFS_BASE__CPU_ARCH__ARM_NEON) +static wuffs_base__empty_struct +wuffs_png__decoder__filter_3_distance_4_arm_neon( + wuffs_png__decoder* self, + wuffs_base__slice_u8 a_curr, + wuffs_base__slice_u8 a_prev) { + wuffs_base__slice_u8 v_curr = {0}; + wuffs_base__slice_u8 v_prev = {0}; + uint8x8_t v_fa = {0}; + uint8x8_t v_fb = {0}; + uint8x8_t v_fx = {0}; + + if (((uint64_t)(a_prev.len)) == 0) { + { + wuffs_base__slice_u8 i_slice_curr = a_curr; + v_curr.ptr = i_slice_curr.ptr; + v_curr.len = 4; + uint8_t* i_end0_curr = v_curr.ptr + (((i_slice_curr.len - (size_t)(v_curr.ptr - i_slice_curr.ptr)) / 8) * 8); + while (v_curr.ptr < i_end0_curr) { + v_fx = vreinterpret_u8_u32(vdup_n_u32(wuffs_base__peek_u32le__no_bounds_check(v_curr.ptr))); + v_fx = vadd_u8(v_fx, vhadd_u8(v_fa, v_fb)); + wuffs_base__poke_u32le__no_bounds_check(v_curr.ptr, vget_lane_u32(vreinterpret_u32_u8(v_fx), 0)); + v_fa = v_fx; + v_curr.ptr += 4; + v_fx = vreinterpret_u8_u32(vdup_n_u32(wuffs_base__peek_u32le__no_bounds_check(v_curr.ptr))); + v_fx = vadd_u8(v_fx, vhadd_u8(v_fa, v_fb)); + wuffs_base__poke_u32le__no_bounds_check(v_curr.ptr, vget_lane_u32(vreinterpret_u32_u8(v_fx), 0)); + v_fa = v_fx; + v_curr.ptr += 4; + } + v_curr.len = 4; + uint8_t* i_end1_curr = v_curr.ptr + (((i_slice_curr.len - (size_t)(v_curr.ptr - i_slice_curr.ptr)) / 4) * 4); + while (v_curr.ptr < i_end1_curr) { + v_fx = vreinterpret_u8_u32(vdup_n_u32(wuffs_base__peek_u32le__no_bounds_check(v_curr.ptr))); + v_fx = vadd_u8(v_fx, vhadd_u8(v_fa, v_fb)); + wuffs_base__poke_u32le__no_bounds_check(v_curr.ptr, vget_lane_u32(vreinterpret_u32_u8(v_fx), 0)); + v_fa = v_fx; + v_curr.ptr += 4; + } + v_curr.len = 0; + } + } else { + { + wuffs_base__slice_u8 i_slice_curr = a_curr; + v_curr.ptr = i_slice_curr.ptr; + wuffs_base__slice_u8 i_slice_prev = a_prev; + v_prev.ptr = i_slice_prev.ptr; + i_slice_curr.len = ((size_t)(wuffs_base__u64__min(i_slice_curr.len, i_slice_prev.len))); + v_curr.len = 4; + v_prev.len = 4; + uint8_t* i_end0_curr = v_curr.ptr + (((i_slice_curr.len - (size_t)(v_curr.ptr - i_slice_curr.ptr)) / 8) * 8); + while (v_curr.ptr < i_end0_curr) { + v_fb = vreinterpret_u8_u32(vdup_n_u32(wuffs_base__peek_u32le__no_bounds_check(v_prev.ptr))); + v_fx = vreinterpret_u8_u32(vdup_n_u32(wuffs_base__peek_u32le__no_bounds_check(v_curr.ptr))); + v_fx = vadd_u8(v_fx, vhadd_u8(v_fa, v_fb)); + wuffs_base__poke_u32le__no_bounds_check(v_curr.ptr, vget_lane_u32(vreinterpret_u32_u8(v_fx), 0)); + v_fa = v_fx; + v_curr.ptr += 4; + v_prev.ptr += 4; + v_fb = vreinterpret_u8_u32(vdup_n_u32(wuffs_base__peek_u32le__no_bounds_check(v_prev.ptr))); + v_fx = vreinterpret_u8_u32(vdup_n_u32(wuffs_base__peek_u32le__no_bounds_check(v_curr.ptr))); + v_fx = vadd_u8(v_fx, vhadd_u8(v_fa, v_fb)); + wuffs_base__poke_u32le__no_bounds_check(v_curr.ptr, vget_lane_u32(vreinterpret_u32_u8(v_fx), 0)); + v_fa = v_fx; + v_curr.ptr += 4; + v_prev.ptr += 4; + } + v_curr.len = 4; + v_prev.len = 4; + uint8_t* i_end1_curr = v_curr.ptr + (((i_slice_curr.len - (size_t)(v_curr.ptr - i_slice_curr.ptr)) / 4) * 4); + while (v_curr.ptr < i_end1_curr) { + v_fb = vreinterpret_u8_u32(vdup_n_u32(wuffs_base__peek_u32le__no_bounds_check(v_prev.ptr))); + v_fx = vreinterpret_u8_u32(vdup_n_u32(wuffs_base__peek_u32le__no_bounds_check(v_curr.ptr))); + v_fx = vadd_u8(v_fx, vhadd_u8(v_fa, v_fb)); + wuffs_base__poke_u32le__no_bounds_check(v_curr.ptr, vget_lane_u32(vreinterpret_u32_u8(v_fx), 0)); + v_fa = v_fx; + v_curr.ptr += 4; + v_prev.ptr += 4; + } + v_curr.len = 0; + v_prev.len = 0; + } + } + return wuffs_base__make_empty_struct(); +} +#endif // defined(WUFFS_BASE__CPU_ARCH__ARM_NEON) +// ‼ WUFFS MULTI-FILE SECTION -arm_neon + +// ‼ WUFFS MULTI-FILE SECTION +arm_neon +// -------- func png.decoder.filter_4_distance_3_arm_neon + +#if defined(WUFFS_BASE__CPU_ARCH__ARM_NEON) +static wuffs_base__empty_struct +wuffs_png__decoder__filter_4_distance_3_arm_neon( + wuffs_png__decoder* self, + wuffs_base__slice_u8 a_curr, + wuffs_base__slice_u8 a_prev) { + wuffs_base__slice_u8 v_curr = {0}; + wuffs_base__slice_u8 v_prev = {0}; + uint8x8_t v_fa = {0}; + uint8x8_t v_fb = {0}; + uint8x8_t v_fc = {0}; + uint8x8_t v_fx = {0}; + uint16x8_t v_fafb = {0}; + uint16x8_t v_fcfc = {0}; + uint16x8_t v_pa = {0}; + uint16x8_t v_pb = {0}; + uint16x8_t v_pc = {0}; + uint16x8_t v_cmpab = {0}; + uint16x8_t v_cmpac = {0}; + uint8x8_t v_picka = {0}; + uint8x8_t v_pickb = {0}; + + { + wuffs_base__slice_u8 i_slice_curr = a_curr; + v_curr.ptr = i_slice_curr.ptr; + wuffs_base__slice_u8 i_slice_prev = a_prev; + v_prev.ptr = i_slice_prev.ptr; + i_slice_curr.len = ((size_t)(wuffs_base__u64__min(i_slice_curr.len, i_slice_prev.len))); + v_curr.len = 4; + v_prev.len = 4; + uint8_t* i_end0_curr = v_curr.ptr + wuffs_base__iterate_total_advance((i_slice_curr.len - (size_t)(v_curr.ptr - i_slice_curr.ptr)), 7, 6); + while (v_curr.ptr < i_end0_curr) { + v_fb = vreinterpret_u8_u32(vdup_n_u32(wuffs_base__peek_u32le__no_bounds_check(v_prev.ptr))); + v_fx = vreinterpret_u8_u32(vdup_n_u32(wuffs_base__peek_u32le__no_bounds_check(v_curr.ptr))); + v_fafb = vaddl_u8(v_fa, v_fb); + v_fcfc = vaddl_u8(v_fc, v_fc); + v_pa = vabdl_u8(v_fb, v_fc); + v_pb = vabdl_u8(v_fa, v_fc); + v_pc = vabdq_u16(v_fafb, v_fcfc); + v_cmpab = vcleq_u16(v_pa, v_pb); + v_cmpac = vcleq_u16(v_pa, v_pc); + v_picka = vmovn_u16(vandq_u16(v_cmpab, v_cmpac)); + v_pickb = vmovn_u16(vcleq_u16(v_pb, v_pc)); + v_fx = vadd_u8(v_fx, vbsl_u8(v_picka, v_fa, vbsl_u8(v_pickb, v_fb, v_fc))); + wuffs_base__poke_u24le__no_bounds_check(v_curr.ptr, vget_lane_u32(vreinterpret_u32_u8(v_fx), 0)); + v_fc = v_fb; + v_fa = v_fx; + v_curr.ptr += 3; + v_prev.ptr += 3; + v_fb = vreinterpret_u8_u32(vdup_n_u32(wuffs_base__peek_u32le__no_bounds_check(v_prev.ptr))); + v_fx = vreinterpret_u8_u32(vdup_n_u32(wuffs_base__peek_u32le__no_bounds_check(v_curr.ptr))); + v_fafb = vaddl_u8(v_fa, v_fb); + v_fcfc = vaddl_u8(v_fc, v_fc); + v_pa = vabdl_u8(v_fb, v_fc); + v_pb = vabdl_u8(v_fa, v_fc); + v_pc = vabdq_u16(v_fafb, v_fcfc); + v_cmpab = vcleq_u16(v_pa, v_pb); + v_cmpac = vcleq_u16(v_pa, v_pc); + v_picka = vmovn_u16(vandq_u16(v_cmpab, v_cmpac)); + v_pickb = vmovn_u16(vcleq_u16(v_pb, v_pc)); + v_fx = vadd_u8(v_fx, vbsl_u8(v_picka, v_fa, vbsl_u8(v_pickb, v_fb, v_fc))); + wuffs_base__poke_u24le__no_bounds_check(v_curr.ptr, vget_lane_u32(vreinterpret_u32_u8(v_fx), 0)); + v_fc = v_fb; + v_fa = v_fx; + v_curr.ptr += 3; + v_prev.ptr += 3; + } + v_curr.len = 4; + v_prev.len = 4; + uint8_t* i_end1_curr = v_curr.ptr + wuffs_base__iterate_total_advance((i_slice_curr.len - (size_t)(v_curr.ptr - i_slice_curr.ptr)), 4, 3); + while (v_curr.ptr < i_end1_curr) { + v_fb = vreinterpret_u8_u32(vdup_n_u32(wuffs_base__peek_u32le__no_bounds_check(v_prev.ptr))); + v_fx = vreinterpret_u8_u32(vdup_n_u32(wuffs_base__peek_u32le__no_bounds_check(v_curr.ptr))); + v_fafb = vaddl_u8(v_fa, v_fb); + v_fcfc = vaddl_u8(v_fc, v_fc); + v_pa = vabdl_u8(v_fb, v_fc); + v_pb = vabdl_u8(v_fa, v_fc); + v_pc = vabdq_u16(v_fafb, v_fcfc); + v_cmpab = vcleq_u16(v_pa, v_pb); + v_cmpac = vcleq_u16(v_pa, v_pc); + v_picka = vmovn_u16(vandq_u16(v_cmpab, v_cmpac)); + v_pickb = vmovn_u16(vcleq_u16(v_pb, v_pc)); + v_fx = vadd_u8(v_fx, vbsl_u8(v_picka, v_fa, vbsl_u8(v_pickb, v_fb, v_fc))); + wuffs_base__poke_u24le__no_bounds_check(v_curr.ptr, vget_lane_u32(vreinterpret_u32_u8(v_fx), 0)); + v_fc = v_fb; + v_fa = v_fx; + v_curr.ptr += 3; + v_prev.ptr += 3; + } + v_curr.len = 3; + v_prev.len = 3; + uint8_t* i_end2_curr = v_curr.ptr + (((i_slice_curr.len - (size_t)(v_curr.ptr - i_slice_curr.ptr)) / 3) * 3); + while (v_curr.ptr < i_end2_curr) { + v_fb = vreinterpret_u8_u32(vdup_n_u32(wuffs_base__peek_u24le__no_bounds_check(v_prev.ptr))); + v_fx = vreinterpret_u8_u32(vdup_n_u32(wuffs_base__peek_u24le__no_bounds_check(v_curr.ptr))); + v_fafb = vaddl_u8(v_fa, v_fb); + v_fcfc = vaddl_u8(v_fc, v_fc); + v_pa = vabdl_u8(v_fb, v_fc); + v_pb = vabdl_u8(v_fa, v_fc); + v_pc = vabdq_u16(v_fafb, v_fcfc); + v_cmpab = vcleq_u16(v_pa, v_pb); + v_cmpac = vcleq_u16(v_pa, v_pc); + v_picka = vmovn_u16(vandq_u16(v_cmpab, v_cmpac)); + v_pickb = vmovn_u16(vcleq_u16(v_pb, v_pc)); + v_fx = vadd_u8(v_fx, vbsl_u8(v_picka, v_fa, vbsl_u8(v_pickb, v_fb, v_fc))); + wuffs_base__poke_u24le__no_bounds_check(v_curr.ptr, vget_lane_u32(vreinterpret_u32_u8(v_fx), 0)); + v_curr.ptr += 3; + v_prev.ptr += 3; + } + v_curr.len = 0; + v_prev.len = 0; + } + return wuffs_base__make_empty_struct(); +} +#endif // defined(WUFFS_BASE__CPU_ARCH__ARM_NEON) +// ‼ WUFFS MULTI-FILE SECTION -arm_neon + +// ‼ WUFFS MULTI-FILE SECTION +arm_neon +// -------- func png.decoder.filter_4_distance_4_arm_neon + +#if defined(WUFFS_BASE__CPU_ARCH__ARM_NEON) +static wuffs_base__empty_struct +wuffs_png__decoder__filter_4_distance_4_arm_neon( + wuffs_png__decoder* self, + wuffs_base__slice_u8 a_curr, + wuffs_base__slice_u8 a_prev) { + wuffs_base__slice_u8 v_curr = {0}; + wuffs_base__slice_u8 v_prev = {0}; + uint8x8_t v_fa = {0}; + uint8x8_t v_fb = {0}; + uint8x8_t v_fc = {0}; + uint8x8_t v_fx = {0}; + uint16x8_t v_fafb = {0}; + uint16x8_t v_fcfc = {0}; + uint16x8_t v_pa = {0}; + uint16x8_t v_pb = {0}; + uint16x8_t v_pc = {0}; + uint16x8_t v_cmpab = {0}; + uint16x8_t v_cmpac = {0}; + uint8x8_t v_picka = {0}; + uint8x8_t v_pickb = {0}; + + { + wuffs_base__slice_u8 i_slice_curr = a_curr; + v_curr.ptr = i_slice_curr.ptr; + wuffs_base__slice_u8 i_slice_prev = a_prev; + v_prev.ptr = i_slice_prev.ptr; + i_slice_curr.len = ((size_t)(wuffs_base__u64__min(i_slice_curr.len, i_slice_prev.len))); + v_curr.len = 4; + v_prev.len = 4; + uint8_t* i_end0_curr = v_curr.ptr + (((i_slice_curr.len - (size_t)(v_curr.ptr - i_slice_curr.ptr)) / 8) * 8); + while (v_curr.ptr < i_end0_curr) { + v_fb = vreinterpret_u8_u32(vdup_n_u32(wuffs_base__peek_u32le__no_bounds_check(v_prev.ptr))); + v_fx = vreinterpret_u8_u32(vdup_n_u32(wuffs_base__peek_u32le__no_bounds_check(v_curr.ptr))); + v_fafb = vaddl_u8(v_fa, v_fb); + v_fcfc = vaddl_u8(v_fc, v_fc); + v_pa = vabdl_u8(v_fb, v_fc); + v_pb = vabdl_u8(v_fa, v_fc); + v_pc = vabdq_u16(v_fafb, v_fcfc); + v_cmpab = vcleq_u16(v_pa, v_pb); + v_cmpac = vcleq_u16(v_pa, v_pc); + v_picka = vmovn_u16(vandq_u16(v_cmpab, v_cmpac)); + v_pickb = vmovn_u16(vcleq_u16(v_pb, v_pc)); + v_fx = vadd_u8(v_fx, vbsl_u8(v_picka, v_fa, vbsl_u8(v_pickb, v_fb, v_fc))); + wuffs_base__poke_u32le__no_bounds_check(v_curr.ptr, vget_lane_u32(vreinterpret_u32_u8(v_fx), 0)); + v_fc = v_fb; + v_fa = v_fx; + v_curr.ptr += 4; + v_prev.ptr += 4; + v_fb = vreinterpret_u8_u32(vdup_n_u32(wuffs_base__peek_u32le__no_bounds_check(v_prev.ptr))); + v_fx = vreinterpret_u8_u32(vdup_n_u32(wuffs_base__peek_u32le__no_bounds_check(v_curr.ptr))); + v_fafb = vaddl_u8(v_fa, v_fb); + v_fcfc = vaddl_u8(v_fc, v_fc); + v_pa = vabdl_u8(v_fb, v_fc); + v_pb = vabdl_u8(v_fa, v_fc); + v_pc = vabdq_u16(v_fafb, v_fcfc); + v_cmpab = vcleq_u16(v_pa, v_pb); + v_cmpac = vcleq_u16(v_pa, v_pc); + v_picka = vmovn_u16(vandq_u16(v_cmpab, v_cmpac)); + v_pickb = vmovn_u16(vcleq_u16(v_pb, v_pc)); + v_fx = vadd_u8(v_fx, vbsl_u8(v_picka, v_fa, vbsl_u8(v_pickb, v_fb, v_fc))); + wuffs_base__poke_u32le__no_bounds_check(v_curr.ptr, vget_lane_u32(vreinterpret_u32_u8(v_fx), 0)); + v_fc = v_fb; + v_fa = v_fx; + v_curr.ptr += 4; + v_prev.ptr += 4; + } + v_curr.len = 4; + v_prev.len = 4; + uint8_t* i_end1_curr = v_curr.ptr + (((i_slice_curr.len - (size_t)(v_curr.ptr - i_slice_curr.ptr)) / 4) * 4); + while (v_curr.ptr < i_end1_curr) { + v_fb = vreinterpret_u8_u32(vdup_n_u32(wuffs_base__peek_u32le__no_bounds_check(v_prev.ptr))); + v_fx = vreinterpret_u8_u32(vdup_n_u32(wuffs_base__peek_u32le__no_bounds_check(v_curr.ptr))); + v_fafb = vaddl_u8(v_fa, v_fb); + v_fcfc = vaddl_u8(v_fc, v_fc); + v_pa = vabdl_u8(v_fb, v_fc); + v_pb = vabdl_u8(v_fa, v_fc); + v_pc = vabdq_u16(v_fafb, v_fcfc); + v_cmpab = vcleq_u16(v_pa, v_pb); + v_cmpac = vcleq_u16(v_pa, v_pc); + v_picka = vmovn_u16(vandq_u16(v_cmpab, v_cmpac)); + v_pickb = vmovn_u16(vcleq_u16(v_pb, v_pc)); + v_fx = vadd_u8(v_fx, vbsl_u8(v_picka, v_fa, vbsl_u8(v_pickb, v_fb, v_fc))); + wuffs_base__poke_u32le__no_bounds_check(v_curr.ptr, vget_lane_u32(vreinterpret_u32_u8(v_fx), 0)); + v_fc = v_fb; + v_fa = v_fx; + v_curr.ptr += 4; + v_prev.ptr += 4; + } + v_curr.len = 0; + v_prev.len = 0; + } + return wuffs_base__make_empty_struct(); +} +#endif // defined(WUFFS_BASE__CPU_ARCH__ARM_NEON) +// ‼ WUFFS MULTI-FILE SECTION -arm_neon + +// -------- func png.decoder.filter_1 + +static wuffs_base__empty_struct +wuffs_png__decoder__filter_1( + wuffs_png__decoder* self, + wuffs_base__slice_u8 a_curr) { + return (*self->private_impl.choosy_filter_1)(self, a_curr); +} + +static wuffs_base__empty_struct +wuffs_png__decoder__filter_1__choosy_default( + wuffs_png__decoder* self, + wuffs_base__slice_u8 a_curr) { + uint64_t v_filter_distance = 0; + uint8_t v_fa = 0; + uint64_t v_i_start = 0; + uint64_t v_i = 0; + + v_filter_distance = ((uint64_t)(self->private_impl.f_filter_distance)); + v_i_start = 0; + while (v_i_start < v_filter_distance) { + v_fa = 0; + v_i = v_i_start; + while (v_i < ((uint64_t)(a_curr.len))) { + a_curr.ptr[v_i] = ((uint8_t)(a_curr.ptr[v_i] + v_fa)); + v_fa = a_curr.ptr[v_i]; + v_i += v_filter_distance; + } + v_i_start += 1; + } + return wuffs_base__make_empty_struct(); +} + +// -------- func png.decoder.filter_1_distance_3_fallback + +static wuffs_base__empty_struct +wuffs_png__decoder__filter_1_distance_3_fallback( + wuffs_png__decoder* self, + wuffs_base__slice_u8 a_curr) { + wuffs_base__slice_u8 v_curr = {0}; + uint8_t v_fa0 = 0; + uint8_t v_fa1 = 0; + uint8_t v_fa2 = 0; + + { + wuffs_base__slice_u8 i_slice_curr = a_curr; + v_curr.ptr = i_slice_curr.ptr; + v_curr.len = 3; + uint8_t* i_end0_curr = v_curr.ptr + (((i_slice_curr.len - (size_t)(v_curr.ptr - i_slice_curr.ptr)) / 6) * 6); + while (v_curr.ptr < i_end0_curr) { + v_fa0 = ((uint8_t)(v_fa0 + v_curr.ptr[0])); + v_curr.ptr[0] = v_fa0; + v_fa1 = ((uint8_t)(v_fa1 + v_curr.ptr[1])); + v_curr.ptr[1] = v_fa1; + v_fa2 = ((uint8_t)(v_fa2 + v_curr.ptr[2])); + v_curr.ptr[2] = v_fa2; + v_curr.ptr += 3; + v_fa0 = ((uint8_t)(v_fa0 + v_curr.ptr[0])); + v_curr.ptr[0] = v_fa0; + v_fa1 = ((uint8_t)(v_fa1 + v_curr.ptr[1])); + v_curr.ptr[1] = v_fa1; + v_fa2 = ((uint8_t)(v_fa2 + v_curr.ptr[2])); + v_curr.ptr[2] = v_fa2; + v_curr.ptr += 3; + } + v_curr.len = 3; + uint8_t* i_end1_curr = v_curr.ptr + (((i_slice_curr.len - (size_t)(v_curr.ptr - i_slice_curr.ptr)) / 3) * 3); + while (v_curr.ptr < i_end1_curr) { + v_fa0 = ((uint8_t)(v_fa0 + v_curr.ptr[0])); + v_curr.ptr[0] = v_fa0; + v_fa1 = ((uint8_t)(v_fa1 + v_curr.ptr[1])); + v_curr.ptr[1] = v_fa1; + v_fa2 = ((uint8_t)(v_fa2 + v_curr.ptr[2])); + v_curr.ptr[2] = v_fa2; + v_curr.ptr += 3; + } + v_curr.len = 0; + } + return wuffs_base__make_empty_struct(); +} + +// -------- func png.decoder.filter_1_distance_4_fallback + +static wuffs_base__empty_struct +wuffs_png__decoder__filter_1_distance_4_fallback( + wuffs_png__decoder* self, + wuffs_base__slice_u8 a_curr) { + wuffs_base__slice_u8 v_curr = {0}; + uint8_t v_fa0 = 0; + uint8_t v_fa1 = 0; + uint8_t v_fa2 = 0; + uint8_t v_fa3 = 0; + + { + wuffs_base__slice_u8 i_slice_curr = a_curr; + v_curr.ptr = i_slice_curr.ptr; + v_curr.len = 4; + uint8_t* i_end0_curr = v_curr.ptr + (((i_slice_curr.len - (size_t)(v_curr.ptr - i_slice_curr.ptr)) / 4) * 4); + while (v_curr.ptr < i_end0_curr) { + v_fa0 = ((uint8_t)(v_fa0 + v_curr.ptr[0])); + v_curr.ptr[0] = v_fa0; + v_fa1 = ((uint8_t)(v_fa1 + v_curr.ptr[1])); + v_curr.ptr[1] = v_fa1; + v_fa2 = ((uint8_t)(v_fa2 + v_curr.ptr[2])); + v_curr.ptr[2] = v_fa2; + v_fa3 = ((uint8_t)(v_fa3 + v_curr.ptr[3])); + v_curr.ptr[3] = v_fa3; + v_curr.ptr += 4; + } + v_curr.len = 0; + } + return wuffs_base__make_empty_struct(); +} + +// -------- func png.decoder.filter_2 + +static wuffs_base__empty_struct +wuffs_png__decoder__filter_2( + wuffs_png__decoder* self, + wuffs_base__slice_u8 a_curr, + wuffs_base__slice_u8 a_prev) { + uint64_t v_n = 0; + uint64_t v_i = 0; + + v_n = wuffs_base__u64__min(((uint64_t)(a_curr.len)), ((uint64_t)(a_prev.len))); + v_i = 0; + while (v_i < v_n) { + a_curr.ptr[v_i] = ((uint8_t)(a_curr.ptr[v_i] + a_prev.ptr[v_i])); + v_i += 1; + } + return wuffs_base__make_empty_struct(); +} + +// -------- func png.decoder.filter_3 + +static wuffs_base__empty_struct +wuffs_png__decoder__filter_3( + wuffs_png__decoder* self, + wuffs_base__slice_u8 a_curr, + wuffs_base__slice_u8 a_prev) { + return (*self->private_impl.choosy_filter_3)(self, a_curr, a_prev); +} + +static wuffs_base__empty_struct +wuffs_png__decoder__filter_3__choosy_default( + wuffs_png__decoder* self, + wuffs_base__slice_u8 a_curr, + wuffs_base__slice_u8 a_prev) { + uint64_t v_filter_distance = 0; + uint64_t v_n = 0; + uint64_t v_i = 0; + + v_filter_distance = ((uint64_t)(self->private_impl.f_filter_distance)); + if (((uint64_t)(a_prev.len)) == 0) { + v_i = v_filter_distance; + while (v_i < ((uint64_t)(a_curr.len))) { + a_curr.ptr[v_i] = ((uint8_t)(a_curr.ptr[v_i] + (a_curr.ptr[(v_i - v_filter_distance)] / 2))); + v_i += 1; + } + } else { + v_n = wuffs_base__u64__min(((uint64_t)(a_curr.len)), ((uint64_t)(a_prev.len))); + v_i = 0; + while ((v_i < v_n) && (v_i < v_filter_distance)) { + a_curr.ptr[v_i] = ((uint8_t)(a_curr.ptr[v_i] + (a_prev.ptr[v_i] / 2))); + v_i += 1; + } + v_i = v_filter_distance; + while (v_i < v_n) { + a_curr.ptr[v_i] = ((uint8_t)(a_curr.ptr[v_i] + ((uint8_t)(((((uint32_t)(a_curr.ptr[(v_i - v_filter_distance)])) + ((uint32_t)(a_prev.ptr[v_i]))) / 2))))); + v_i += 1; + } + } + return wuffs_base__make_empty_struct(); +} + +// -------- func png.decoder.filter_3_distance_3_fallback + +static wuffs_base__empty_struct +wuffs_png__decoder__filter_3_distance_3_fallback( + wuffs_png__decoder* self, + wuffs_base__slice_u8 a_curr, + wuffs_base__slice_u8 a_prev) { + wuffs_base__slice_u8 v_curr = {0}; + wuffs_base__slice_u8 v_prev = {0}; + uint8_t v_fa0 = 0; + uint8_t v_fa1 = 0; + uint8_t v_fa2 = 0; + + if (((uint64_t)(a_prev.len)) == 0) { + { + wuffs_base__slice_u8 i_slice_curr = a_curr; + v_curr.ptr = i_slice_curr.ptr; + v_curr.len = 3; + uint8_t* i_end0_curr = v_curr.ptr + (((i_slice_curr.len - (size_t)(v_curr.ptr - i_slice_curr.ptr)) / 6) * 6); + while (v_curr.ptr < i_end0_curr) { + v_fa0 = ((uint8_t)((v_fa0 / 2) + v_curr.ptr[0])); + v_curr.ptr[0] = v_fa0; + v_fa1 = ((uint8_t)((v_fa1 / 2) + v_curr.ptr[1])); + v_curr.ptr[1] = v_fa1; + v_fa2 = ((uint8_t)((v_fa2 / 2) + v_curr.ptr[2])); + v_curr.ptr[2] = v_fa2; + v_curr.ptr += 3; + v_fa0 = ((uint8_t)((v_fa0 / 2) + v_curr.ptr[0])); + v_curr.ptr[0] = v_fa0; + v_fa1 = ((uint8_t)((v_fa1 / 2) + v_curr.ptr[1])); + v_curr.ptr[1] = v_fa1; + v_fa2 = ((uint8_t)((v_fa2 / 2) + v_curr.ptr[2])); + v_curr.ptr[2] = v_fa2; + v_curr.ptr += 3; + } + v_curr.len = 3; + uint8_t* i_end1_curr = v_curr.ptr + (((i_slice_curr.len - (size_t)(v_curr.ptr - i_slice_curr.ptr)) / 3) * 3); + while (v_curr.ptr < i_end1_curr) { + v_fa0 = ((uint8_t)((v_fa0 / 2) + v_curr.ptr[0])); + v_curr.ptr[0] = v_fa0; + v_fa1 = ((uint8_t)((v_fa1 / 2) + v_curr.ptr[1])); + v_curr.ptr[1] = v_fa1; + v_fa2 = ((uint8_t)((v_fa2 / 2) + v_curr.ptr[2])); + v_curr.ptr[2] = v_fa2; + v_curr.ptr += 3; + } + v_curr.len = 0; + } + } else { + { + wuffs_base__slice_u8 i_slice_curr = a_curr; + v_curr.ptr = i_slice_curr.ptr; + wuffs_base__slice_u8 i_slice_prev = a_prev; + v_prev.ptr = i_slice_prev.ptr; + i_slice_curr.len = ((size_t)(wuffs_base__u64__min(i_slice_curr.len, i_slice_prev.len))); + v_curr.len = 3; + v_prev.len = 3; + uint8_t* i_end0_curr = v_curr.ptr + (((i_slice_curr.len - (size_t)(v_curr.ptr - i_slice_curr.ptr)) / 6) * 6); + while (v_curr.ptr < i_end0_curr) { + v_fa0 = ((uint8_t)(((uint8_t)(((((uint32_t)(v_fa0)) + ((uint32_t)(v_prev.ptr[0]))) / 2))) + v_curr.ptr[0])); + v_curr.ptr[0] = v_fa0; + v_fa1 = ((uint8_t)(((uint8_t)(((((uint32_t)(v_fa1)) + ((uint32_t)(v_prev.ptr[1]))) / 2))) + v_curr.ptr[1])); + v_curr.ptr[1] = v_fa1; + v_fa2 = ((uint8_t)(((uint8_t)(((((uint32_t)(v_fa2)) + ((uint32_t)(v_prev.ptr[2]))) / 2))) + v_curr.ptr[2])); + v_curr.ptr[2] = v_fa2; + v_curr.ptr += 3; + v_prev.ptr += 3; + v_fa0 = ((uint8_t)(((uint8_t)(((((uint32_t)(v_fa0)) + ((uint32_t)(v_prev.ptr[0]))) / 2))) + v_curr.ptr[0])); + v_curr.ptr[0] = v_fa0; + v_fa1 = ((uint8_t)(((uint8_t)(((((uint32_t)(v_fa1)) + ((uint32_t)(v_prev.ptr[1]))) / 2))) + v_curr.ptr[1])); + v_curr.ptr[1] = v_fa1; + v_fa2 = ((uint8_t)(((uint8_t)(((((uint32_t)(v_fa2)) + ((uint32_t)(v_prev.ptr[2]))) / 2))) + v_curr.ptr[2])); + v_curr.ptr[2] = v_fa2; + v_curr.ptr += 3; + v_prev.ptr += 3; + } + v_curr.len = 3; + v_prev.len = 3; + uint8_t* i_end1_curr = v_curr.ptr + (((i_slice_curr.len - (size_t)(v_curr.ptr - i_slice_curr.ptr)) / 3) * 3); + while (v_curr.ptr < i_end1_curr) { + v_fa0 = ((uint8_t)(((uint8_t)(((((uint32_t)(v_fa0)) + ((uint32_t)(v_prev.ptr[0]))) / 2))) + v_curr.ptr[0])); + v_curr.ptr[0] = v_fa0; + v_fa1 = ((uint8_t)(((uint8_t)(((((uint32_t)(v_fa1)) + ((uint32_t)(v_prev.ptr[1]))) / 2))) + v_curr.ptr[1])); + v_curr.ptr[1] = v_fa1; + v_fa2 = ((uint8_t)(((uint8_t)(((((uint32_t)(v_fa2)) + ((uint32_t)(v_prev.ptr[2]))) / 2))) + v_curr.ptr[2])); + v_curr.ptr[2] = v_fa2; + v_curr.ptr += 3; + v_prev.ptr += 3; + } + v_curr.len = 0; + v_prev.len = 0; + } + } + return wuffs_base__make_empty_struct(); +} + +// -------- func png.decoder.filter_3_distance_4_fallback + +static wuffs_base__empty_struct +wuffs_png__decoder__filter_3_distance_4_fallback( + wuffs_png__decoder* self, + wuffs_base__slice_u8 a_curr, + wuffs_base__slice_u8 a_prev) { + wuffs_base__slice_u8 v_curr = {0}; + wuffs_base__slice_u8 v_prev = {0}; + uint8_t v_fa0 = 0; + uint8_t v_fa1 = 0; + uint8_t v_fa2 = 0; + uint8_t v_fa3 = 0; + + if (((uint64_t)(a_prev.len)) == 0) { + { + wuffs_base__slice_u8 i_slice_curr = a_curr; + v_curr.ptr = i_slice_curr.ptr; + v_curr.len = 4; + uint8_t* i_end0_curr = v_curr.ptr + (((i_slice_curr.len - (size_t)(v_curr.ptr - i_slice_curr.ptr)) / 4) * 4); + while (v_curr.ptr < i_end0_curr) { + v_fa0 = ((uint8_t)((v_fa0 / 2) + v_curr.ptr[0])); + v_curr.ptr[0] = v_fa0; + v_fa1 = ((uint8_t)((v_fa1 / 2) + v_curr.ptr[1])); + v_curr.ptr[1] = v_fa1; + v_fa2 = ((uint8_t)((v_fa2 / 2) + v_curr.ptr[2])); + v_curr.ptr[2] = v_fa2; + v_fa3 = ((uint8_t)((v_fa3 / 2) + v_curr.ptr[3])); + v_curr.ptr[3] = v_fa3; + v_curr.ptr += 4; + } + v_curr.len = 0; + } + } else { + { + wuffs_base__slice_u8 i_slice_curr = a_curr; + v_curr.ptr = i_slice_curr.ptr; + wuffs_base__slice_u8 i_slice_prev = a_prev; + v_prev.ptr = i_slice_prev.ptr; + i_slice_curr.len = ((size_t)(wuffs_base__u64__min(i_slice_curr.len, i_slice_prev.len))); + v_curr.len = 4; + v_prev.len = 4; + uint8_t* i_end0_curr = v_curr.ptr + (((i_slice_curr.len - (size_t)(v_curr.ptr - i_slice_curr.ptr)) / 4) * 4); + while (v_curr.ptr < i_end0_curr) { + v_fa0 = ((uint8_t)(((uint8_t)(((((uint32_t)(v_fa0)) + ((uint32_t)(v_prev.ptr[0]))) / 2))) + v_curr.ptr[0])); + v_curr.ptr[0] = v_fa0; + v_fa1 = ((uint8_t)(((uint8_t)(((((uint32_t)(v_fa1)) + ((uint32_t)(v_prev.ptr[1]))) / 2))) + v_curr.ptr[1])); + v_curr.ptr[1] = v_fa1; + v_fa2 = ((uint8_t)(((uint8_t)(((((uint32_t)(v_fa2)) + ((uint32_t)(v_prev.ptr[2]))) / 2))) + v_curr.ptr[2])); + v_curr.ptr[2] = v_fa2; + v_fa3 = ((uint8_t)(((uint8_t)(((((uint32_t)(v_fa3)) + ((uint32_t)(v_prev.ptr[3]))) / 2))) + v_curr.ptr[3])); + v_curr.ptr[3] = v_fa3; + v_curr.ptr += 4; + v_prev.ptr += 4; + } + v_curr.len = 0; + v_prev.len = 0; + } + } + return wuffs_base__make_empty_struct(); +} + +// -------- func png.decoder.filter_4 + +static wuffs_base__empty_struct +wuffs_png__decoder__filter_4( + wuffs_png__decoder* self, + wuffs_base__slice_u8 a_curr, + wuffs_base__slice_u8 a_prev) { + return (*self->private_impl.choosy_filter_4)(self, a_curr, a_prev); +} + +static wuffs_base__empty_struct +wuffs_png__decoder__filter_4__choosy_default( + wuffs_png__decoder* self, + wuffs_base__slice_u8 a_curr, + wuffs_base__slice_u8 a_prev) { + uint64_t v_filter_distance = 0; + uint64_t v_n = 0; + uint64_t v_i = 0; + uint32_t v_fa = 0; + uint32_t v_fb = 0; + uint32_t v_fc = 0; + uint32_t v_pp = 0; + uint32_t v_pa = 0; + uint32_t v_pb = 0; + uint32_t v_pc = 0; + + v_filter_distance = ((uint64_t)(self->private_impl.f_filter_distance)); + v_n = wuffs_base__u64__min(((uint64_t)(a_curr.len)), ((uint64_t)(a_prev.len))); + v_i = 0; + while ((v_i < v_n) && (v_i < v_filter_distance)) { + a_curr.ptr[v_i] = ((uint8_t)(a_curr.ptr[v_i] + a_prev.ptr[v_i])); + v_i += 1; + } + v_i = v_filter_distance; + while (v_i < v_n) { + v_fa = ((uint32_t)(a_curr.ptr[(v_i - v_filter_distance)])); + v_fb = ((uint32_t)(a_prev.ptr[v_i])); + v_fc = ((uint32_t)(a_prev.ptr[(v_i - v_filter_distance)])); + v_pp = ((uint32_t)(((uint32_t)(v_fa + v_fb)) - v_fc)); + v_pa = ((uint32_t)(v_pp - v_fa)); + if (v_pa >= 2147483648) { + v_pa = ((uint32_t)(0 - v_pa)); + } + v_pb = ((uint32_t)(v_pp - v_fb)); + if (v_pb >= 2147483648) { + v_pb = ((uint32_t)(0 - v_pb)); + } + v_pc = ((uint32_t)(v_pp - v_fc)); + if (v_pc >= 2147483648) { + v_pc = ((uint32_t)(0 - v_pc)); + } + if ((v_pa <= v_pb) && (v_pa <= v_pc)) { + } else if (v_pb <= v_pc) { + v_fa = v_fb; + } else { + v_fa = v_fc; + } + a_curr.ptr[v_i] = ((uint8_t)(a_curr.ptr[v_i] + ((uint8_t)((v_fa & 255))))); + v_i += 1; + } + return wuffs_base__make_empty_struct(); +} + +// -------- func png.decoder.filter_4_distance_3_fallback + +static wuffs_base__empty_struct +wuffs_png__decoder__filter_4_distance_3_fallback( + wuffs_png__decoder* self, + wuffs_base__slice_u8 a_curr, + wuffs_base__slice_u8 a_prev) { + wuffs_base__slice_u8 v_curr = {0}; + wuffs_base__slice_u8 v_prev = {0}; + uint32_t v_fa0 = 0; + uint32_t v_fa1 = 0; + uint32_t v_fa2 = 0; + uint32_t v_fb0 = 0; + uint32_t v_fb1 = 0; + uint32_t v_fb2 = 0; + uint32_t v_fc0 = 0; + uint32_t v_fc1 = 0; + uint32_t v_fc2 = 0; + uint32_t v_pp0 = 0; + uint32_t v_pp1 = 0; + uint32_t v_pp2 = 0; + uint32_t v_pa0 = 0; + uint32_t v_pa1 = 0; + uint32_t v_pa2 = 0; + uint32_t v_pb0 = 0; + uint32_t v_pb1 = 0; + uint32_t v_pb2 = 0; + uint32_t v_pc0 = 0; + uint32_t v_pc1 = 0; + uint32_t v_pc2 = 0; + + { + wuffs_base__slice_u8 i_slice_curr = a_curr; + v_curr.ptr = i_slice_curr.ptr; + wuffs_base__slice_u8 i_slice_prev = a_prev; + v_prev.ptr = i_slice_prev.ptr; + i_slice_curr.len = ((size_t)(wuffs_base__u64__min(i_slice_curr.len, i_slice_prev.len))); + v_curr.len = 3; + v_prev.len = 3; + uint8_t* i_end0_curr = v_curr.ptr + (((i_slice_curr.len - (size_t)(v_curr.ptr - i_slice_curr.ptr)) / 3) * 3); + while (v_curr.ptr < i_end0_curr) { + v_fb0 = ((uint32_t)(v_prev.ptr[0])); + v_pp0 = ((uint32_t)(((uint32_t)(v_fa0 + v_fb0)) - v_fc0)); + v_pa0 = ((uint32_t)(v_pp0 - v_fa0)); + if (v_pa0 >= 2147483648) { + v_pa0 = ((uint32_t)(0 - v_pa0)); + } + v_pb0 = ((uint32_t)(v_pp0 - v_fb0)); + if (v_pb0 >= 2147483648) { + v_pb0 = ((uint32_t)(0 - v_pb0)); + } + v_pc0 = ((uint32_t)(v_pp0 - v_fc0)); + if (v_pc0 >= 2147483648) { + v_pc0 = ((uint32_t)(0 - v_pc0)); + } + if ((v_pa0 <= v_pb0) && (v_pa0 <= v_pc0)) { + } else if (v_pb0 <= v_pc0) { + v_fa0 = v_fb0; + } else { + v_fa0 = v_fc0; + } + v_curr.ptr[0] = ((uint8_t)(v_curr.ptr[0] + ((uint8_t)((v_fa0 & 255))))); + v_fa0 = ((uint32_t)(v_curr.ptr[0])); + v_fc0 = v_fb0; + v_fb1 = ((uint32_t)(v_prev.ptr[1])); + v_pp1 = ((uint32_t)(((uint32_t)(v_fa1 + v_fb1)) - v_fc1)); + v_pa1 = ((uint32_t)(v_pp1 - v_fa1)); + if (v_pa1 >= 2147483648) { + v_pa1 = ((uint32_t)(0 - v_pa1)); + } + v_pb1 = ((uint32_t)(v_pp1 - v_fb1)); + if (v_pb1 >= 2147483648) { + v_pb1 = ((uint32_t)(0 - v_pb1)); + } + v_pc1 = ((uint32_t)(v_pp1 - v_fc1)); + if (v_pc1 >= 2147483648) { + v_pc1 = ((uint32_t)(0 - v_pc1)); + } + if ((v_pa1 <= v_pb1) && (v_pa1 <= v_pc1)) { + } else if (v_pb1 <= v_pc1) { + v_fa1 = v_fb1; + } else { + v_fa1 = v_fc1; + } + v_curr.ptr[1] = ((uint8_t)(v_curr.ptr[1] + ((uint8_t)((v_fa1 & 255))))); + v_fa1 = ((uint32_t)(v_curr.ptr[1])); + v_fc1 = v_fb1; + v_fb2 = ((uint32_t)(v_prev.ptr[2])); + v_pp2 = ((uint32_t)(((uint32_t)(v_fa2 + v_fb2)) - v_fc2)); + v_pa2 = ((uint32_t)(v_pp2 - v_fa2)); + if (v_pa2 >= 2147483648) { + v_pa2 = ((uint32_t)(0 - v_pa2)); + } + v_pb2 = ((uint32_t)(v_pp2 - v_fb2)); + if (v_pb2 >= 2147483648) { + v_pb2 = ((uint32_t)(0 - v_pb2)); + } + v_pc2 = ((uint32_t)(v_pp2 - v_fc2)); + if (v_pc2 >= 2147483648) { + v_pc2 = ((uint32_t)(0 - v_pc2)); + } + if ((v_pa2 <= v_pb2) && (v_pa2 <= v_pc2)) { + } else if (v_pb2 <= v_pc2) { + v_fa2 = v_fb2; + } else { + v_fa2 = v_fc2; + } + v_curr.ptr[2] = ((uint8_t)(v_curr.ptr[2] + ((uint8_t)((v_fa2 & 255))))); + v_fa2 = ((uint32_t)(v_curr.ptr[2])); + v_fc2 = v_fb2; + v_curr.ptr += 3; + v_prev.ptr += 3; + } + v_curr.len = 0; + v_prev.len = 0; + } + return wuffs_base__make_empty_struct(); +} + +// -------- func png.decoder.filter_4_distance_4_fallback + +static wuffs_base__empty_struct +wuffs_png__decoder__filter_4_distance_4_fallback( + wuffs_png__decoder* self, + wuffs_base__slice_u8 a_curr, + wuffs_base__slice_u8 a_prev) { + wuffs_base__slice_u8 v_curr = {0}; + wuffs_base__slice_u8 v_prev = {0}; + uint32_t v_fa0 = 0; + uint32_t v_fa1 = 0; + uint32_t v_fa2 = 0; + uint32_t v_fa3 = 0; + uint32_t v_fb0 = 0; + uint32_t v_fb1 = 0; + uint32_t v_fb2 = 0; + uint32_t v_fb3 = 0; + uint32_t v_fc0 = 0; + uint32_t v_fc1 = 0; + uint32_t v_fc2 = 0; + uint32_t v_fc3 = 0; + uint32_t v_pp0 = 0; + uint32_t v_pp1 = 0; + uint32_t v_pp2 = 0; + uint32_t v_pp3 = 0; + uint32_t v_pa0 = 0; + uint32_t v_pa1 = 0; + uint32_t v_pa2 = 0; + uint32_t v_pa3 = 0; + uint32_t v_pb0 = 0; + uint32_t v_pb1 = 0; + uint32_t v_pb2 = 0; + uint32_t v_pb3 = 0; + uint32_t v_pc0 = 0; + uint32_t v_pc1 = 0; + uint32_t v_pc2 = 0; + uint32_t v_pc3 = 0; + + { + wuffs_base__slice_u8 i_slice_curr = a_curr; + v_curr.ptr = i_slice_curr.ptr; + wuffs_base__slice_u8 i_slice_prev = a_prev; + v_prev.ptr = i_slice_prev.ptr; + i_slice_curr.len = ((size_t)(wuffs_base__u64__min(i_slice_curr.len, i_slice_prev.len))); + v_curr.len = 4; + v_prev.len = 4; + uint8_t* i_end0_curr = v_curr.ptr + (((i_slice_curr.len - (size_t)(v_curr.ptr - i_slice_curr.ptr)) / 4) * 4); + while (v_curr.ptr < i_end0_curr) { + v_fb0 = ((uint32_t)(v_prev.ptr[0])); + v_pp0 = ((uint32_t)(((uint32_t)(v_fa0 + v_fb0)) - v_fc0)); + v_pa0 = ((uint32_t)(v_pp0 - v_fa0)); + if (v_pa0 >= 2147483648) { + v_pa0 = ((uint32_t)(0 - v_pa0)); + } + v_pb0 = ((uint32_t)(v_pp0 - v_fb0)); + if (v_pb0 >= 2147483648) { + v_pb0 = ((uint32_t)(0 - v_pb0)); + } + v_pc0 = ((uint32_t)(v_pp0 - v_fc0)); + if (v_pc0 >= 2147483648) { + v_pc0 = ((uint32_t)(0 - v_pc0)); + } + if ((v_pa0 <= v_pb0) && (v_pa0 <= v_pc0)) { + } else if (v_pb0 <= v_pc0) { + v_fa0 = v_fb0; + } else { + v_fa0 = v_fc0; + } + v_curr.ptr[0] = ((uint8_t)(v_curr.ptr[0] + ((uint8_t)((v_fa0 & 255))))); + v_fa0 = ((uint32_t)(v_curr.ptr[0])); + v_fc0 = v_fb0; + v_fb1 = ((uint32_t)(v_prev.ptr[1])); + v_pp1 = ((uint32_t)(((uint32_t)(v_fa1 + v_fb1)) - v_fc1)); + v_pa1 = ((uint32_t)(v_pp1 - v_fa1)); + if (v_pa1 >= 2147483648) { + v_pa1 = ((uint32_t)(0 - v_pa1)); + } + v_pb1 = ((uint32_t)(v_pp1 - v_fb1)); + if (v_pb1 >= 2147483648) { + v_pb1 = ((uint32_t)(0 - v_pb1)); + } + v_pc1 = ((uint32_t)(v_pp1 - v_fc1)); + if (v_pc1 >= 2147483648) { + v_pc1 = ((uint32_t)(0 - v_pc1)); + } + if ((v_pa1 <= v_pb1) && (v_pa1 <= v_pc1)) { + } else if (v_pb1 <= v_pc1) { + v_fa1 = v_fb1; + } else { + v_fa1 = v_fc1; + } + v_curr.ptr[1] = ((uint8_t)(v_curr.ptr[1] + ((uint8_t)((v_fa1 & 255))))); + v_fa1 = ((uint32_t)(v_curr.ptr[1])); + v_fc1 = v_fb1; + v_fb2 = ((uint32_t)(v_prev.ptr[2])); + v_pp2 = ((uint32_t)(((uint32_t)(v_fa2 + v_fb2)) - v_fc2)); + v_pa2 = ((uint32_t)(v_pp2 - v_fa2)); + if (v_pa2 >= 2147483648) { + v_pa2 = ((uint32_t)(0 - v_pa2)); + } + v_pb2 = ((uint32_t)(v_pp2 - v_fb2)); + if (v_pb2 >= 2147483648) { + v_pb2 = ((uint32_t)(0 - v_pb2)); + } + v_pc2 = ((uint32_t)(v_pp2 - v_fc2)); + if (v_pc2 >= 2147483648) { + v_pc2 = ((uint32_t)(0 - v_pc2)); + } + if ((v_pa2 <= v_pb2) && (v_pa2 <= v_pc2)) { + } else if (v_pb2 <= v_pc2) { + v_fa2 = v_fb2; + } else { + v_fa2 = v_fc2; + } + v_curr.ptr[2] = ((uint8_t)(v_curr.ptr[2] + ((uint8_t)((v_fa2 & 255))))); + v_fa2 = ((uint32_t)(v_curr.ptr[2])); + v_fc2 = v_fb2; + v_fb3 = ((uint32_t)(v_prev.ptr[3])); + v_pp3 = ((uint32_t)(((uint32_t)(v_fa3 + v_fb3)) - v_fc3)); + v_pa3 = ((uint32_t)(v_pp3 - v_fa3)); + if (v_pa3 >= 2147483648) { + v_pa3 = ((uint32_t)(0 - v_pa3)); + } + v_pb3 = ((uint32_t)(v_pp3 - v_fb3)); + if (v_pb3 >= 2147483648) { + v_pb3 = ((uint32_t)(0 - v_pb3)); + } + v_pc3 = ((uint32_t)(v_pp3 - v_fc3)); + if (v_pc3 >= 2147483648) { + v_pc3 = ((uint32_t)(0 - v_pc3)); + } + if ((v_pa3 <= v_pb3) && (v_pa3 <= v_pc3)) { + } else if (v_pb3 <= v_pc3) { + v_fa3 = v_fb3; + } else { + v_fa3 = v_fc3; + } + v_curr.ptr[3] = ((uint8_t)(v_curr.ptr[3] + ((uint8_t)((v_fa3 & 255))))); + v_fa3 = ((uint32_t)(v_curr.ptr[3])); + v_fc3 = v_fb3; + v_curr.ptr += 4; + v_prev.ptr += 4; + } + v_curr.len = 0; + v_prev.len = 0; + } + return wuffs_base__make_empty_struct(); +} + +// ‼ WUFFS MULTI-FILE SECTION +x86_sse42 +// -------- func png.decoder.filter_1_distance_4_x86_sse42 + +#if defined(WUFFS_BASE__CPU_ARCH__X86_FAMILY) +WUFFS_BASE__MAYBE_ATTRIBUTE_TARGET("pclmul,popcnt,sse4.2") +static wuffs_base__empty_struct +wuffs_png__decoder__filter_1_distance_4_x86_sse42( + wuffs_png__decoder* self, + wuffs_base__slice_u8 a_curr) { + wuffs_base__slice_u8 v_curr = {0}; + __m128i v_x128 = {0}; + __m128i v_a128 = {0}; + + { + wuffs_base__slice_u8 i_slice_curr = a_curr; + v_curr.ptr = i_slice_curr.ptr; + v_curr.len = 4; + uint8_t* i_end0_curr = v_curr.ptr + (((i_slice_curr.len - (size_t)(v_curr.ptr - i_slice_curr.ptr)) / 8) * 8); + while (v_curr.ptr < i_end0_curr) { + v_x128 = _mm_cvtsi32_si128((int32_t)(wuffs_base__peek_u32le__no_bounds_check(v_curr.ptr))); + v_x128 = _mm_add_epi8(v_x128, v_a128); + v_a128 = v_x128; + wuffs_base__poke_u32le__no_bounds_check(v_curr.ptr, ((uint32_t)(_mm_cvtsi128_si32(v_x128)))); + v_curr.ptr += 4; + v_x128 = _mm_cvtsi32_si128((int32_t)(wuffs_base__peek_u32le__no_bounds_check(v_curr.ptr))); + v_x128 = _mm_add_epi8(v_x128, v_a128); + v_a128 = v_x128; + wuffs_base__poke_u32le__no_bounds_check(v_curr.ptr, ((uint32_t)(_mm_cvtsi128_si32(v_x128)))); + v_curr.ptr += 4; + } + v_curr.len = 4; + uint8_t* i_end1_curr = v_curr.ptr + (((i_slice_curr.len - (size_t)(v_curr.ptr - i_slice_curr.ptr)) / 4) * 4); + while (v_curr.ptr < i_end1_curr) { + v_x128 = _mm_cvtsi32_si128((int32_t)(wuffs_base__peek_u32le__no_bounds_check(v_curr.ptr))); + v_x128 = _mm_add_epi8(v_x128, v_a128); + v_a128 = v_x128; + wuffs_base__poke_u32le__no_bounds_check(v_curr.ptr, ((uint32_t)(_mm_cvtsi128_si32(v_x128)))); + v_curr.ptr += 4; + } + v_curr.len = 0; + } + return wuffs_base__make_empty_struct(); +} +#endif // defined(WUFFS_BASE__CPU_ARCH__X86_FAMILY) +// ‼ WUFFS MULTI-FILE SECTION -x86_sse42 + +// ‼ WUFFS MULTI-FILE SECTION +x86_sse42 +// -------- func png.decoder.filter_3_distance_4_x86_sse42 + +#if defined(WUFFS_BASE__CPU_ARCH__X86_FAMILY) +WUFFS_BASE__MAYBE_ATTRIBUTE_TARGET("pclmul,popcnt,sse4.2") +static wuffs_base__empty_struct +wuffs_png__decoder__filter_3_distance_4_x86_sse42( + wuffs_png__decoder* self, + wuffs_base__slice_u8 a_curr, + wuffs_base__slice_u8 a_prev) { + wuffs_base__slice_u8 v_curr = {0}; + wuffs_base__slice_u8 v_prev = {0}; + __m128i v_x128 = {0}; + __m128i v_a128 = {0}; + __m128i v_b128 = {0}; + __m128i v_p128 = {0}; + __m128i v_k128 = {0}; + + if (((uint64_t)(a_prev.len)) == 0) { + v_k128 = _mm_set1_epi8((int8_t)(254)); + { + wuffs_base__slice_u8 i_slice_curr = a_curr; + v_curr.ptr = i_slice_curr.ptr; + v_curr.len = 4; + uint8_t* i_end0_curr = v_curr.ptr + (((i_slice_curr.len - (size_t)(v_curr.ptr - i_slice_curr.ptr)) / 8) * 8); + while (v_curr.ptr < i_end0_curr) { + v_p128 = _mm_avg_epu8(_mm_and_si128(v_a128, v_k128), v_b128); + v_x128 = _mm_cvtsi32_si128((int32_t)(wuffs_base__peek_u32le__no_bounds_check(v_curr.ptr))); + v_x128 = _mm_add_epi8(v_x128, v_p128); + v_a128 = v_x128; + wuffs_base__poke_u32le__no_bounds_check(v_curr.ptr, ((uint32_t)(_mm_cvtsi128_si32(v_x128)))); + v_curr.ptr += 4; + v_p128 = _mm_avg_epu8(_mm_and_si128(v_a128, v_k128), v_b128); + v_x128 = _mm_cvtsi32_si128((int32_t)(wuffs_base__peek_u32le__no_bounds_check(v_curr.ptr))); + v_x128 = _mm_add_epi8(v_x128, v_p128); + v_a128 = v_x128; + wuffs_base__poke_u32le__no_bounds_check(v_curr.ptr, ((uint32_t)(_mm_cvtsi128_si32(v_x128)))); + v_curr.ptr += 4; + } + v_curr.len = 4; + uint8_t* i_end1_curr = v_curr.ptr + (((i_slice_curr.len - (size_t)(v_curr.ptr - i_slice_curr.ptr)) / 4) * 4); + while (v_curr.ptr < i_end1_curr) { + v_p128 = _mm_avg_epu8(_mm_and_si128(v_a128, v_k128), v_b128); + v_x128 = _mm_cvtsi32_si128((int32_t)(wuffs_base__peek_u32le__no_bounds_check(v_curr.ptr))); + v_x128 = _mm_add_epi8(v_x128, v_p128); + v_a128 = v_x128; + wuffs_base__poke_u32le__no_bounds_check(v_curr.ptr, ((uint32_t)(_mm_cvtsi128_si32(v_x128)))); + v_curr.ptr += 4; + } + v_curr.len = 0; + } + } else { + v_k128 = _mm_set1_epi8((int8_t)(1)); + { + wuffs_base__slice_u8 i_slice_curr = a_curr; + v_curr.ptr = i_slice_curr.ptr; + wuffs_base__slice_u8 i_slice_prev = a_prev; + v_prev.ptr = i_slice_prev.ptr; + i_slice_curr.len = ((size_t)(wuffs_base__u64__min(i_slice_curr.len, i_slice_prev.len))); + v_curr.len = 4; + v_prev.len = 4; + uint8_t* i_end0_curr = v_curr.ptr + (((i_slice_curr.len - (size_t)(v_curr.ptr - i_slice_curr.ptr)) / 8) * 8); + while (v_curr.ptr < i_end0_curr) { + v_b128 = _mm_cvtsi32_si128((int32_t)(wuffs_base__peek_u32le__no_bounds_check(v_prev.ptr))); + v_p128 = _mm_avg_epu8(v_a128, v_b128); + v_p128 = _mm_sub_epi8(v_p128, _mm_and_si128(v_k128, _mm_xor_si128(v_a128, v_b128))); + v_x128 = _mm_cvtsi32_si128((int32_t)(wuffs_base__peek_u32le__no_bounds_check(v_curr.ptr))); + v_x128 = _mm_add_epi8(v_x128, v_p128); + v_a128 = v_x128; + wuffs_base__poke_u32le__no_bounds_check(v_curr.ptr, ((uint32_t)(_mm_cvtsi128_si32(v_x128)))); + v_curr.ptr += 4; + v_prev.ptr += 4; + v_b128 = _mm_cvtsi32_si128((int32_t)(wuffs_base__peek_u32le__no_bounds_check(v_prev.ptr))); + v_p128 = _mm_avg_epu8(v_a128, v_b128); + v_p128 = _mm_sub_epi8(v_p128, _mm_and_si128(v_k128, _mm_xor_si128(v_a128, v_b128))); + v_x128 = _mm_cvtsi32_si128((int32_t)(wuffs_base__peek_u32le__no_bounds_check(v_curr.ptr))); + v_x128 = _mm_add_epi8(v_x128, v_p128); + v_a128 = v_x128; + wuffs_base__poke_u32le__no_bounds_check(v_curr.ptr, ((uint32_t)(_mm_cvtsi128_si32(v_x128)))); + v_curr.ptr += 4; + v_prev.ptr += 4; + } + v_curr.len = 4; + v_prev.len = 4; + uint8_t* i_end1_curr = v_curr.ptr + (((i_slice_curr.len - (size_t)(v_curr.ptr - i_slice_curr.ptr)) / 4) * 4); + while (v_curr.ptr < i_end1_curr) { + v_b128 = _mm_cvtsi32_si128((int32_t)(wuffs_base__peek_u32le__no_bounds_check(v_prev.ptr))); + v_p128 = _mm_avg_epu8(v_a128, v_b128); + v_p128 = _mm_sub_epi8(v_p128, _mm_and_si128(v_k128, _mm_xor_si128(v_a128, v_b128))); + v_x128 = _mm_cvtsi32_si128((int32_t)(wuffs_base__peek_u32le__no_bounds_check(v_curr.ptr))); + v_x128 = _mm_add_epi8(v_x128, v_p128); + v_a128 = v_x128; + wuffs_base__poke_u32le__no_bounds_check(v_curr.ptr, ((uint32_t)(_mm_cvtsi128_si32(v_x128)))); + v_curr.ptr += 4; + v_prev.ptr += 4; + } + v_curr.len = 0; + v_prev.len = 0; + } + } + return wuffs_base__make_empty_struct(); +} +#endif // defined(WUFFS_BASE__CPU_ARCH__X86_FAMILY) +// ‼ WUFFS MULTI-FILE SECTION -x86_sse42 + +// ‼ WUFFS MULTI-FILE SECTION +x86_sse42 +// -------- func png.decoder.filter_4_distance_3_x86_sse42 + +#if defined(WUFFS_BASE__CPU_ARCH__X86_FAMILY) +WUFFS_BASE__MAYBE_ATTRIBUTE_TARGET("pclmul,popcnt,sse4.2") +static wuffs_base__empty_struct +wuffs_png__decoder__filter_4_distance_3_x86_sse42( + wuffs_png__decoder* self, + wuffs_base__slice_u8 a_curr, + wuffs_base__slice_u8 a_prev) { + wuffs_base__slice_u8 v_curr = {0}; + wuffs_base__slice_u8 v_prev = {0}; + __m128i v_x128 = {0}; + __m128i v_a128 = {0}; + __m128i v_b128 = {0}; + __m128i v_c128 = {0}; + __m128i v_p128 = {0}; + __m128i v_pa128 = {0}; + __m128i v_pb128 = {0}; + __m128i v_pc128 = {0}; + __m128i v_smallest128 = {0}; + __m128i v_z128 = {0}; + + { + wuffs_base__slice_u8 i_slice_curr = a_curr; + v_curr.ptr = i_slice_curr.ptr; + wuffs_base__slice_u8 i_slice_prev = a_prev; + v_prev.ptr = i_slice_prev.ptr; + i_slice_curr.len = ((size_t)(wuffs_base__u64__min(i_slice_curr.len, i_slice_prev.len))); + v_curr.len = 4; + v_prev.len = 4; + uint8_t* i_end0_curr = v_curr.ptr + wuffs_base__iterate_total_advance((i_slice_curr.len - (size_t)(v_curr.ptr - i_slice_curr.ptr)), 7, 6); + while (v_curr.ptr < i_end0_curr) { + v_b128 = _mm_cvtsi32_si128((int32_t)(wuffs_base__peek_u32le__no_bounds_check(v_prev.ptr))); + v_b128 = _mm_unpacklo_epi8(v_b128, v_z128); + v_pa128 = _mm_sub_epi16(v_b128, v_c128); + v_pb128 = _mm_sub_epi16(v_a128, v_c128); + v_pc128 = _mm_add_epi16(v_pa128, v_pb128); + v_pa128 = _mm_abs_epi16(v_pa128); + v_pb128 = _mm_abs_epi16(v_pb128); + v_pc128 = _mm_abs_epi16(v_pc128); + v_smallest128 = _mm_min_epi16(v_pc128, _mm_min_epi16(v_pb128, v_pa128)); + v_p128 = _mm_blendv_epi8(_mm_blendv_epi8(v_c128, v_b128, _mm_cmpeq_epi16(v_smallest128, v_pb128)), v_a128, _mm_cmpeq_epi16(v_smallest128, v_pa128)); + v_x128 = _mm_cvtsi32_si128((int32_t)(wuffs_base__peek_u32le__no_bounds_check(v_curr.ptr))); + v_x128 = _mm_unpacklo_epi8(v_x128, v_z128); + v_x128 = _mm_add_epi8(v_x128, v_p128); + v_a128 = v_x128; + v_c128 = v_b128; + v_x128 = _mm_packus_epi16(v_x128, v_x128); + wuffs_base__poke_u24le__no_bounds_check(v_curr.ptr, ((uint32_t)(_mm_cvtsi128_si32(v_x128)))); + v_curr.ptr += 3; + v_prev.ptr += 3; + v_b128 = _mm_cvtsi32_si128((int32_t)(wuffs_base__peek_u32le__no_bounds_check(v_prev.ptr))); + v_b128 = _mm_unpacklo_epi8(v_b128, v_z128); + v_pa128 = _mm_sub_epi16(v_b128, v_c128); + v_pb128 = _mm_sub_epi16(v_a128, v_c128); + v_pc128 = _mm_add_epi16(v_pa128, v_pb128); + v_pa128 = _mm_abs_epi16(v_pa128); + v_pb128 = _mm_abs_epi16(v_pb128); + v_pc128 = _mm_abs_epi16(v_pc128); + v_smallest128 = _mm_min_epi16(v_pc128, _mm_min_epi16(v_pb128, v_pa128)); + v_p128 = _mm_blendv_epi8(_mm_blendv_epi8(v_c128, v_b128, _mm_cmpeq_epi16(v_smallest128, v_pb128)), v_a128, _mm_cmpeq_epi16(v_smallest128, v_pa128)); + v_x128 = _mm_cvtsi32_si128((int32_t)(wuffs_base__peek_u32le__no_bounds_check(v_curr.ptr))); + v_x128 = _mm_unpacklo_epi8(v_x128, v_z128); + v_x128 = _mm_add_epi8(v_x128, v_p128); + v_a128 = v_x128; + v_c128 = v_b128; + v_x128 = _mm_packus_epi16(v_x128, v_x128); + wuffs_base__poke_u24le__no_bounds_check(v_curr.ptr, ((uint32_t)(_mm_cvtsi128_si32(v_x128)))); + v_curr.ptr += 3; + v_prev.ptr += 3; + } + v_curr.len = 4; + v_prev.len = 4; + uint8_t* i_end1_curr = v_curr.ptr + wuffs_base__iterate_total_advance((i_slice_curr.len - (size_t)(v_curr.ptr - i_slice_curr.ptr)), 4, 3); + while (v_curr.ptr < i_end1_curr) { + v_b128 = _mm_cvtsi32_si128((int32_t)(wuffs_base__peek_u32le__no_bounds_check(v_prev.ptr))); + v_b128 = _mm_unpacklo_epi8(v_b128, v_z128); + v_pa128 = _mm_sub_epi16(v_b128, v_c128); + v_pb128 = _mm_sub_epi16(v_a128, v_c128); + v_pc128 = _mm_add_epi16(v_pa128, v_pb128); + v_pa128 = _mm_abs_epi16(v_pa128); + v_pb128 = _mm_abs_epi16(v_pb128); + v_pc128 = _mm_abs_epi16(v_pc128); + v_smallest128 = _mm_min_epi16(v_pc128, _mm_min_epi16(v_pb128, v_pa128)); + v_p128 = _mm_blendv_epi8(_mm_blendv_epi8(v_c128, v_b128, _mm_cmpeq_epi16(v_smallest128, v_pb128)), v_a128, _mm_cmpeq_epi16(v_smallest128, v_pa128)); + v_x128 = _mm_cvtsi32_si128((int32_t)(wuffs_base__peek_u32le__no_bounds_check(v_curr.ptr))); + v_x128 = _mm_unpacklo_epi8(v_x128, v_z128); + v_x128 = _mm_add_epi8(v_x128, v_p128); + v_a128 = v_x128; + v_c128 = v_b128; + v_x128 = _mm_packus_epi16(v_x128, v_x128); + wuffs_base__poke_u24le__no_bounds_check(v_curr.ptr, ((uint32_t)(_mm_cvtsi128_si32(v_x128)))); + v_curr.ptr += 3; + v_prev.ptr += 3; + } + v_curr.len = 3; + v_prev.len = 3; + uint8_t* i_end2_curr = v_curr.ptr + (((i_slice_curr.len - (size_t)(v_curr.ptr - i_slice_curr.ptr)) / 3) * 3); + while (v_curr.ptr < i_end2_curr) { + v_b128 = _mm_cvtsi32_si128((int32_t)(wuffs_base__peek_u24le__no_bounds_check(v_prev.ptr))); + v_b128 = _mm_unpacklo_epi8(v_b128, v_z128); + v_pa128 = _mm_sub_epi16(v_b128, v_c128); + v_pb128 = _mm_sub_epi16(v_a128, v_c128); + v_pc128 = _mm_add_epi16(v_pa128, v_pb128); + v_pa128 = _mm_abs_epi16(v_pa128); + v_pb128 = _mm_abs_epi16(v_pb128); + v_pc128 = _mm_abs_epi16(v_pc128); + v_smallest128 = _mm_min_epi16(v_pc128, _mm_min_epi16(v_pb128, v_pa128)); + v_p128 = _mm_blendv_epi8(_mm_blendv_epi8(v_c128, v_b128, _mm_cmpeq_epi16(v_smallest128, v_pb128)), v_a128, _mm_cmpeq_epi16(v_smallest128, v_pa128)); + v_x128 = _mm_cvtsi32_si128((int32_t)(wuffs_base__peek_u24le__no_bounds_check(v_curr.ptr))); + v_x128 = _mm_unpacklo_epi8(v_x128, v_z128); + v_x128 = _mm_add_epi8(v_x128, v_p128); + v_x128 = _mm_packus_epi16(v_x128, v_x128); + wuffs_base__poke_u24le__no_bounds_check(v_curr.ptr, ((uint32_t)(_mm_cvtsi128_si32(v_x128)))); + v_curr.ptr += 3; + v_prev.ptr += 3; + } + v_curr.len = 0; + v_prev.len = 0; + } + return wuffs_base__make_empty_struct(); +} +#endif // defined(WUFFS_BASE__CPU_ARCH__X86_FAMILY) +// ‼ WUFFS MULTI-FILE SECTION -x86_sse42 + +// ‼ WUFFS MULTI-FILE SECTION +x86_sse42 +// -------- func png.decoder.filter_4_distance_4_x86_sse42 + +#if defined(WUFFS_BASE__CPU_ARCH__X86_FAMILY) +WUFFS_BASE__MAYBE_ATTRIBUTE_TARGET("pclmul,popcnt,sse4.2") +static wuffs_base__empty_struct +wuffs_png__decoder__filter_4_distance_4_x86_sse42( + wuffs_png__decoder* self, + wuffs_base__slice_u8 a_curr, + wuffs_base__slice_u8 a_prev) { + wuffs_base__slice_u8 v_curr = {0}; + wuffs_base__slice_u8 v_prev = {0}; + __m128i v_x128 = {0}; + __m128i v_a128 = {0}; + __m128i v_b128 = {0}; + __m128i v_c128 = {0}; + __m128i v_p128 = {0}; + __m128i v_pa128 = {0}; + __m128i v_pb128 = {0}; + __m128i v_pc128 = {0}; + __m128i v_smallest128 = {0}; + __m128i v_z128 = {0}; + + { + wuffs_base__slice_u8 i_slice_curr = a_curr; + v_curr.ptr = i_slice_curr.ptr; + wuffs_base__slice_u8 i_slice_prev = a_prev; + v_prev.ptr = i_slice_prev.ptr; + i_slice_curr.len = ((size_t)(wuffs_base__u64__min(i_slice_curr.len, i_slice_prev.len))); + v_curr.len = 4; + v_prev.len = 4; + uint8_t* i_end0_curr = v_curr.ptr + (((i_slice_curr.len - (size_t)(v_curr.ptr - i_slice_curr.ptr)) / 8) * 8); + while (v_curr.ptr < i_end0_curr) { + v_b128 = _mm_cvtsi32_si128((int32_t)(wuffs_base__peek_u32le__no_bounds_check(v_prev.ptr))); + v_b128 = _mm_unpacklo_epi8(v_b128, v_z128); + v_pa128 = _mm_sub_epi16(v_b128, v_c128); + v_pb128 = _mm_sub_epi16(v_a128, v_c128); + v_pc128 = _mm_add_epi16(v_pa128, v_pb128); + v_pa128 = _mm_abs_epi16(v_pa128); + v_pb128 = _mm_abs_epi16(v_pb128); + v_pc128 = _mm_abs_epi16(v_pc128); + v_smallest128 = _mm_min_epi16(v_pc128, _mm_min_epi16(v_pb128, v_pa128)); + v_p128 = _mm_blendv_epi8(_mm_blendv_epi8(v_c128, v_b128, _mm_cmpeq_epi16(v_smallest128, v_pb128)), v_a128, _mm_cmpeq_epi16(v_smallest128, v_pa128)); + v_x128 = _mm_cvtsi32_si128((int32_t)(wuffs_base__peek_u32le__no_bounds_check(v_curr.ptr))); + v_x128 = _mm_unpacklo_epi8(v_x128, v_z128); + v_x128 = _mm_add_epi8(v_x128, v_p128); + v_a128 = v_x128; + v_c128 = v_b128; + v_x128 = _mm_packus_epi16(v_x128, v_x128); + wuffs_base__poke_u32le__no_bounds_check(v_curr.ptr, ((uint32_t)(_mm_cvtsi128_si32(v_x128)))); + v_curr.ptr += 4; + v_prev.ptr += 4; + v_b128 = _mm_cvtsi32_si128((int32_t)(wuffs_base__peek_u32le__no_bounds_check(v_prev.ptr))); + v_b128 = _mm_unpacklo_epi8(v_b128, v_z128); + v_pa128 = _mm_sub_epi16(v_b128, v_c128); + v_pb128 = _mm_sub_epi16(v_a128, v_c128); + v_pc128 = _mm_add_epi16(v_pa128, v_pb128); + v_pa128 = _mm_abs_epi16(v_pa128); + v_pb128 = _mm_abs_epi16(v_pb128); + v_pc128 = _mm_abs_epi16(v_pc128); + v_smallest128 = _mm_min_epi16(v_pc128, _mm_min_epi16(v_pb128, v_pa128)); + v_p128 = _mm_blendv_epi8(_mm_blendv_epi8(v_c128, v_b128, _mm_cmpeq_epi16(v_smallest128, v_pb128)), v_a128, _mm_cmpeq_epi16(v_smallest128, v_pa128)); + v_x128 = _mm_cvtsi32_si128((int32_t)(wuffs_base__peek_u32le__no_bounds_check(v_curr.ptr))); + v_x128 = _mm_unpacklo_epi8(v_x128, v_z128); + v_x128 = _mm_add_epi8(v_x128, v_p128); + v_a128 = v_x128; + v_c128 = v_b128; + v_x128 = _mm_packus_epi16(v_x128, v_x128); + wuffs_base__poke_u32le__no_bounds_check(v_curr.ptr, ((uint32_t)(_mm_cvtsi128_si32(v_x128)))); + v_curr.ptr += 4; + v_prev.ptr += 4; + } + v_curr.len = 4; + v_prev.len = 4; + uint8_t* i_end1_curr = v_curr.ptr + (((i_slice_curr.len - (size_t)(v_curr.ptr - i_slice_curr.ptr)) / 4) * 4); + while (v_curr.ptr < i_end1_curr) { + v_b128 = _mm_cvtsi32_si128((int32_t)(wuffs_base__peek_u32le__no_bounds_check(v_prev.ptr))); + v_b128 = _mm_unpacklo_epi8(v_b128, v_z128); + v_pa128 = _mm_sub_epi16(v_b128, v_c128); + v_pb128 = _mm_sub_epi16(v_a128, v_c128); + v_pc128 = _mm_add_epi16(v_pa128, v_pb128); + v_pa128 = _mm_abs_epi16(v_pa128); + v_pb128 = _mm_abs_epi16(v_pb128); + v_pc128 = _mm_abs_epi16(v_pc128); + v_smallest128 = _mm_min_epi16(v_pc128, _mm_min_epi16(v_pb128, v_pa128)); + v_p128 = _mm_blendv_epi8(_mm_blendv_epi8(v_c128, v_b128, _mm_cmpeq_epi16(v_smallest128, v_pb128)), v_a128, _mm_cmpeq_epi16(v_smallest128, v_pa128)); + v_x128 = _mm_cvtsi32_si128((int32_t)(wuffs_base__peek_u32le__no_bounds_check(v_curr.ptr))); + v_x128 = _mm_unpacklo_epi8(v_x128, v_z128); + v_x128 = _mm_add_epi8(v_x128, v_p128); + v_a128 = v_x128; + v_c128 = v_b128; + v_x128 = _mm_packus_epi16(v_x128, v_x128); + wuffs_base__poke_u32le__no_bounds_check(v_curr.ptr, ((uint32_t)(_mm_cvtsi128_si32(v_x128)))); + v_curr.ptr += 4; + v_prev.ptr += 4; + } + v_curr.len = 0; + v_prev.len = 0; + } + return wuffs_base__make_empty_struct(); +} +#endif // defined(WUFFS_BASE__CPU_ARCH__X86_FAMILY) +// ‼ WUFFS MULTI-FILE SECTION -x86_sse42 + +// -------- func png.decoder.set_quirk_enabled + +WUFFS_BASE__MAYBE_STATIC wuffs_base__empty_struct +wuffs_png__decoder__set_quirk_enabled( + wuffs_png__decoder* self, + uint32_t a_quirk, + bool a_enabled) { + if (!self) { + return wuffs_base__make_empty_struct(); + } + if (self->private_impl.magic != WUFFS_BASE__MAGIC) { + return wuffs_base__make_empty_struct(); + } + + if (a_quirk == 1) { + self->private_impl.f_ignore_checksum = a_enabled; + wuffs_zlib__decoder__set_quirk_enabled(&self->private_data.f_zlib, a_quirk, a_enabled); + } + return wuffs_base__make_empty_struct(); +} + +// -------- func png.decoder.decode_image_config + +WUFFS_BASE__MAYBE_STATIC wuffs_base__status +wuffs_png__decoder__decode_image_config( + wuffs_png__decoder* self, + wuffs_base__image_config* a_dst, + wuffs_base__io_buffer* a_src) { + if (!self) { + return wuffs_base__make_status(wuffs_base__error__bad_receiver); + } + if (self->private_impl.magic != WUFFS_BASE__MAGIC) { + return wuffs_base__make_status( + (self->private_impl.magic == WUFFS_BASE__DISABLED) + ? wuffs_base__error__disabled_by_previous_error + : wuffs_base__error__initialize_not_called); + } + if (!a_src) { + self->private_impl.magic = WUFFS_BASE__DISABLED; + return wuffs_base__make_status(wuffs_base__error__bad_argument); + } + if ((self->private_impl.active_coroutine != 0) && + (self->private_impl.active_coroutine != 1)) { + self->private_impl.magic = WUFFS_BASE__DISABLED; + return wuffs_base__make_status(wuffs_base__error__interleaved_coroutine_calls); + } + self->private_impl.active_coroutine = 0; + wuffs_base__status status = wuffs_base__make_status(NULL); + + wuffs_base__status v_status = wuffs_base__make_status(NULL); + + uint32_t coro_susp_point = self->private_impl.p_decode_image_config[0]; + switch (coro_susp_point) { + WUFFS_BASE__COROUTINE_SUSPENSION_POINT_0; + + while (true) { + { + wuffs_base__status t_0 = wuffs_png__decoder__do_decode_image_config(self, a_dst, a_src); + v_status = t_0; + } + if ((v_status.repr == wuffs_base__suspension__short_read) && (a_src && a_src->meta.closed)) { + status = wuffs_base__make_status(wuffs_png__error__truncated_input); + goto exit; + } + status = v_status; + WUFFS_BASE__COROUTINE_SUSPENSION_POINT_MAYBE_SUSPEND(1); + } + + ok: + self->private_impl.p_decode_image_config[0] = 0; + goto exit; + } + + goto suspend; + suspend: + self->private_impl.p_decode_image_config[0] = wuffs_base__status__is_suspension(&status) ? coro_susp_point : 0; + self->private_impl.active_coroutine = wuffs_base__status__is_suspension(&status) ? 1 : 0; + + goto exit; + exit: + if (wuffs_base__status__is_error(&status)) { + self->private_impl.magic = WUFFS_BASE__DISABLED; + } + return status; +} + +// -------- func png.decoder.do_decode_image_config + +static wuffs_base__status +wuffs_png__decoder__do_decode_image_config( + wuffs_png__decoder* self, + wuffs_base__image_config* a_dst, + wuffs_base__io_buffer* a_src) { + wuffs_base__status status = wuffs_base__make_status(NULL); + + uint64_t v_magic = 0; + uint64_t v_mark = 0; + uint32_t v_checksum_have = 0; + uint32_t v_checksum_want = 0; + wuffs_base__status v_status = wuffs_base__make_status(NULL); + + const uint8_t* iop_a_src = NULL; + const uint8_t* io0_a_src WUFFS_BASE__POTENTIALLY_UNUSED = NULL; + const uint8_t* io1_a_src WUFFS_BASE__POTENTIALLY_UNUSED = NULL; + const uint8_t* io2_a_src WUFFS_BASE__POTENTIALLY_UNUSED = NULL; + if (a_src && a_src->data.ptr) { + io0_a_src = a_src->data.ptr; + io1_a_src = io0_a_src + a_src->meta.ri; + iop_a_src = io1_a_src; + io2_a_src = io0_a_src + a_src->meta.wi; + } + + uint32_t coro_susp_point = self->private_impl.p_do_decode_image_config[0]; + if (coro_susp_point) { + v_checksum_have = self->private_data.s_do_decode_image_config[0].v_checksum_have; + } + switch (coro_susp_point) { + WUFFS_BASE__COROUTINE_SUSPENSION_POINT_0; + + if (self->private_impl.f_call_sequence != 0) { + status = wuffs_base__make_status(wuffs_base__error__bad_call_sequence); + goto exit; + } else if ( ! self->private_impl.f_seen_ihdr) { + { + WUFFS_BASE__COROUTINE_SUSPENSION_POINT(1); + uint64_t t_0; + if (WUFFS_BASE__LIKELY(io2_a_src - iop_a_src >= 8)) { + t_0 = wuffs_base__peek_u64le__no_bounds_check(iop_a_src); + iop_a_src += 8; + } else { + self->private_data.s_do_decode_image_config[0].scratch = 0; + WUFFS_BASE__COROUTINE_SUSPENSION_POINT(2); + while (true) { + if (WUFFS_BASE__UNLIKELY(iop_a_src == io2_a_src)) { + status = wuffs_base__make_status(wuffs_base__suspension__short_read); + goto suspend; + } + uint64_t* scratch = &self->private_data.s_do_decode_image_config[0].scratch; + uint32_t num_bits_0 = ((uint32_t)(*scratch >> 56)); + *scratch <<= 8; + *scratch >>= 8; + *scratch |= ((uint64_t)(*iop_a_src++)) << num_bits_0; + if (num_bits_0 == 56) { + t_0 = ((uint64_t)(*scratch)); + break; + } + num_bits_0 += 8; + *scratch |= ((uint64_t)(num_bits_0)) << 56; + } + } + v_magic = t_0; + } + if (v_magic != 727905341920923785) { + status = wuffs_base__make_status(wuffs_png__error__bad_header); + goto exit; + } + { + WUFFS_BASE__COROUTINE_SUSPENSION_POINT(3); + uint64_t t_1; + if (WUFFS_BASE__LIKELY(io2_a_src - iop_a_src >= 8)) { + t_1 = wuffs_base__peek_u64le__no_bounds_check(iop_a_src); + iop_a_src += 8; + } else { + self->private_data.s_do_decode_image_config[0].scratch = 0; + WUFFS_BASE__COROUTINE_SUSPENSION_POINT(4); + while (true) { + if (WUFFS_BASE__UNLIKELY(iop_a_src == io2_a_src)) { + status = wuffs_base__make_status(wuffs_base__suspension__short_read); + goto suspend; + } + uint64_t* scratch = &self->private_data.s_do_decode_image_config[0].scratch; + uint32_t num_bits_1 = ((uint32_t)(*scratch >> 56)); + *scratch <<= 8; + *scratch >>= 8; + *scratch |= ((uint64_t)(*iop_a_src++)) << num_bits_1; + if (num_bits_1 == 56) { + t_1 = ((uint64_t)(*scratch)); + break; + } + num_bits_1 += 8; + *scratch |= ((uint64_t)(num_bits_1)) << 56; + } + } + v_magic = t_1; + } + if (v_magic != 5927942488114331648) { + if (v_magic == 5278895250759221248) { + status = wuffs_base__make_status(wuffs_png__error__unsupported_cgbi_extension); + goto exit; + } + status = wuffs_base__make_status(wuffs_png__error__bad_header); + goto exit; + } + self->private_impl.f_chunk_type_array[0] = 73; + self->private_impl.f_chunk_type_array[1] = 72; + self->private_impl.f_chunk_type_array[2] = 68; + self->private_impl.f_chunk_type_array[3] = 82; + wuffs_base__ignore_status(wuffs_crc32__ieee_hasher__initialize(&self->private_data.f_crc32, + sizeof (wuffs_crc32__ieee_hasher), WUFFS_VERSION, WUFFS_INITIALIZE__LEAVE_INTERNAL_BUFFERS_UNINITIALIZED)); + wuffs_crc32__ieee_hasher__update_u32(&self->private_data.f_crc32, wuffs_base__make_slice_u8(self->private_impl.f_chunk_type_array, 4)); + while (true) { + v_mark = ((uint64_t)(iop_a_src - io0_a_src)); + { + if (a_src) { + a_src->meta.ri = ((size_t)(iop_a_src - a_src->data.ptr)); + } + wuffs_base__status t_2 = wuffs_png__decoder__decode_ihdr(self, a_src); + v_status = t_2; + if (a_src) { + iop_a_src = a_src->data.ptr + a_src->meta.ri; + } + } + if ( ! self->private_impl.f_ignore_checksum) { + v_checksum_have = wuffs_crc32__ieee_hasher__update_u32(&self->private_data.f_crc32, wuffs_base__io__since(v_mark, ((uint64_t)(iop_a_src - io0_a_src)), io0_a_src)); + } + if (wuffs_base__status__is_ok(&v_status)) { + goto label__0__break; + } + status = v_status; + WUFFS_BASE__COROUTINE_SUSPENSION_POINT_MAYBE_SUSPEND(5); + } + label__0__break:; + { + WUFFS_BASE__COROUTINE_SUSPENSION_POINT(6); + uint32_t t_3; + if (WUFFS_BASE__LIKELY(io2_a_src - iop_a_src >= 4)) { + t_3 = wuffs_base__peek_u32be__no_bounds_check(iop_a_src); + iop_a_src += 4; + } else { + self->private_data.s_do_decode_image_config[0].scratch = 0; + WUFFS_BASE__COROUTINE_SUSPENSION_POINT(7); + while (true) { + if (WUFFS_BASE__UNLIKELY(iop_a_src == io2_a_src)) { + status = wuffs_base__make_status(wuffs_base__suspension__short_read); + goto suspend; + } + uint64_t* scratch = &self->private_data.s_do_decode_image_config[0].scratch; + uint32_t num_bits_3 = ((uint32_t)(*scratch & 0xFF)); + *scratch >>= 8; + *scratch <<= 8; + *scratch |= ((uint64_t)(*iop_a_src++)) << (56 - num_bits_3); + if (num_bits_3 == 24) { + t_3 = ((uint32_t)(*scratch >> 32)); + break; + } + num_bits_3 += 8; + *scratch |= ((uint64_t)(num_bits_3)); + } + } + v_checksum_want = t_3; + } + if ( ! self->private_impl.f_ignore_checksum && (v_checksum_have != v_checksum_want)) { + status = wuffs_base__make_status(wuffs_png__error__bad_checksum); + goto exit; + } + self->private_impl.f_seen_ihdr = true; + } else if (self->private_impl.f_metadata_fourcc != 0) { + self->private_impl.f_call_sequence = 16; + status = wuffs_base__make_status(wuffs_base__note__metadata_reported); + goto ok; + } + label__1__continue:; + while (true) { + if (((uint64_t)(io2_a_src - iop_a_src)) < 8) { + status = wuffs_base__make_status(wuffs_base__suspension__short_read); + WUFFS_BASE__COROUTINE_SUSPENSION_POINT_MAYBE_SUSPEND(8); + goto label__1__continue; + } + self->private_impl.f_chunk_length = wuffs_base__peek_u32be__no_bounds_check(iop_a_src); + self->private_impl.f_chunk_type = ((uint32_t)((wuffs_base__peek_u64le__no_bounds_check(iop_a_src) >> 32))); + if (self->private_impl.f_chunk_type == 1413563465) { + if ( ! self->private_impl.f_seen_actl || self->private_impl.f_seen_fctl) { + goto label__1__break; + } + self->private_impl.f_seen_idat = true; + } else if (self->private_impl.f_chunk_type == 1413571686) { + if (self->private_impl.f_seen_idat && self->private_impl.f_seen_fctl) { + goto label__1__break; + } + status = wuffs_base__make_status(wuffs_png__error__bad_chunk); + goto exit; + } + iop_a_src += 8; + if ( ! self->private_impl.f_ignore_checksum && ((self->private_impl.f_chunk_type & 32) == 0)) { + self->private_impl.f_chunk_type_array[0] = ((uint8_t)(((self->private_impl.f_chunk_type >> 0) & 255))); + self->private_impl.f_chunk_type_array[1] = ((uint8_t)(((self->private_impl.f_chunk_type >> 8) & 255))); + self->private_impl.f_chunk_type_array[2] = ((uint8_t)(((self->private_impl.f_chunk_type >> 16) & 255))); + self->private_impl.f_chunk_type_array[3] = ((uint8_t)(((self->private_impl.f_chunk_type >> 24) & 255))); + wuffs_base__ignore_status(wuffs_crc32__ieee_hasher__initialize(&self->private_data.f_crc32, + sizeof (wuffs_crc32__ieee_hasher), WUFFS_VERSION, WUFFS_INITIALIZE__LEAVE_INTERNAL_BUFFERS_UNINITIALIZED)); + wuffs_crc32__ieee_hasher__update_u32(&self->private_data.f_crc32, wuffs_base__make_slice_u8(self->private_impl.f_chunk_type_array, 4)); + } + while (true) { + v_mark = ((uint64_t)(iop_a_src - io0_a_src)); + { + if (a_src) { + a_src->meta.ri = ((size_t)(iop_a_src - a_src->data.ptr)); + } + wuffs_base__status t_4 = wuffs_png__decoder__decode_other_chunk(self, a_src, false); + v_status = t_4; + if (a_src) { + iop_a_src = a_src->data.ptr + a_src->meta.ri; + } + } + if ( ! self->private_impl.f_ignore_checksum && ((self->private_impl.f_chunk_type & 32) == 0)) { + v_checksum_have = wuffs_crc32__ieee_hasher__update_u32(&self->private_data.f_crc32, wuffs_base__io__since(v_mark, ((uint64_t)(iop_a_src - io0_a_src)), io0_a_src)); + } + if (wuffs_base__status__is_ok(&v_status)) { + goto label__2__break; + } + status = v_status; + WUFFS_BASE__COROUTINE_SUSPENSION_POINT_MAYBE_SUSPEND(9); + } + label__2__break:; + if (self->private_impl.f_metadata_fourcc != 0) { + self->private_impl.f_call_sequence = 16; + status = wuffs_base__make_status(wuffs_base__note__metadata_reported); + goto ok; + } + { + WUFFS_BASE__COROUTINE_SUSPENSION_POINT(10); + uint32_t t_5; + if (WUFFS_BASE__LIKELY(io2_a_src - iop_a_src >= 4)) { + t_5 = wuffs_base__peek_u32be__no_bounds_check(iop_a_src); + iop_a_src += 4; + } else { + self->private_data.s_do_decode_image_config[0].scratch = 0; + WUFFS_BASE__COROUTINE_SUSPENSION_POINT(11); + while (true) { + if (WUFFS_BASE__UNLIKELY(iop_a_src == io2_a_src)) { + status = wuffs_base__make_status(wuffs_base__suspension__short_read); + goto suspend; + } + uint64_t* scratch = &self->private_data.s_do_decode_image_config[0].scratch; + uint32_t num_bits_5 = ((uint32_t)(*scratch & 0xFF)); + *scratch >>= 8; + *scratch <<= 8; + *scratch |= ((uint64_t)(*iop_a_src++)) << (56 - num_bits_5); + if (num_bits_5 == 24) { + t_5 = ((uint32_t)(*scratch >> 32)); + break; + } + num_bits_5 += 8; + *scratch |= ((uint64_t)(num_bits_5)); + } + } + v_checksum_want = t_5; + } + if ( ! self->private_impl.f_ignore_checksum && ((self->private_impl.f_chunk_type & 32) == 0) && (v_checksum_have != v_checksum_want)) { + status = wuffs_base__make_status(wuffs_png__error__bad_checksum); + goto exit; + } + } + label__1__break:; + if ((self->private_impl.f_color_type == 3) && ! self->private_impl.f_seen_plte) { + status = wuffs_base__make_status(wuffs_png__error__missing_palette); + goto exit; + } + self->private_impl.f_frame_config_io_position = wuffs_base__u64__sat_add((a_src ? a_src->meta.pos : 0), ((uint64_t)(iop_a_src - io0_a_src))); + self->private_impl.f_first_config_io_position = self->private_impl.f_frame_config_io_position; + if (a_dst != NULL) { + wuffs_base__image_config__set( + a_dst, + self->private_impl.f_dst_pixfmt, + 0, + self->private_impl.f_width, + self->private_impl.f_height, + self->private_impl.f_first_config_io_position, + ((self->private_impl.f_color_type <= 3) && ! self->private_impl.f_seen_trns)); + } + if ( ! self->private_impl.f_seen_actl) { + self->private_impl.f_num_animation_frames_value = 1; + self->private_impl.f_first_rect_x0 = 0; + self->private_impl.f_first_rect_y0 = 0; + self->private_impl.f_first_rect_x1 = self->private_impl.f_width; + self->private_impl.f_first_rect_y1 = self->private_impl.f_height; + self->private_impl.f_first_duration = 0; + self->private_impl.f_first_disposal = 0; + self->private_impl.f_first_overwrite_instead_of_blend = false; + } + self->private_impl.f_call_sequence = 32; + + ok: + self->private_impl.p_do_decode_image_config[0] = 0; + goto exit; + } + + goto suspend; + suspend: + self->private_impl.p_do_decode_image_config[0] = wuffs_base__status__is_suspension(&status) ? coro_susp_point : 0; + self->private_data.s_do_decode_image_config[0].v_checksum_have = v_checksum_have; + + goto exit; + exit: + if (a_src && a_src->data.ptr) { + a_src->meta.ri = ((size_t)(iop_a_src - a_src->data.ptr)); + } + + return status; +} + +// -------- func png.decoder.decode_ihdr + +static wuffs_base__status +wuffs_png__decoder__decode_ihdr( + wuffs_png__decoder* self, + wuffs_base__io_buffer* a_src) { + wuffs_base__status status = wuffs_base__make_status(NULL); + + uint32_t v_a32 = 0; + uint8_t v_a8 = 0; + + const uint8_t* iop_a_src = NULL; + const uint8_t* io0_a_src WUFFS_BASE__POTENTIALLY_UNUSED = NULL; + const uint8_t* io1_a_src WUFFS_BASE__POTENTIALLY_UNUSED = NULL; + const uint8_t* io2_a_src WUFFS_BASE__POTENTIALLY_UNUSED = NULL; + if (a_src && a_src->data.ptr) { + io0_a_src = a_src->data.ptr; + io1_a_src = io0_a_src + a_src->meta.ri; + iop_a_src = io1_a_src; + io2_a_src = io0_a_src + a_src->meta.wi; + } + + uint32_t coro_susp_point = self->private_impl.p_decode_ihdr[0]; + switch (coro_susp_point) { + WUFFS_BASE__COROUTINE_SUSPENSION_POINT_0; + + { + WUFFS_BASE__COROUTINE_SUSPENSION_POINT(1); + uint32_t t_0; + if (WUFFS_BASE__LIKELY(io2_a_src - iop_a_src >= 4)) { + t_0 = wuffs_base__peek_u32be__no_bounds_check(iop_a_src); + iop_a_src += 4; + } else { + self->private_data.s_decode_ihdr[0].scratch = 0; + WUFFS_BASE__COROUTINE_SUSPENSION_POINT(2); + while (true) { + if (WUFFS_BASE__UNLIKELY(iop_a_src == io2_a_src)) { + status = wuffs_base__make_status(wuffs_base__suspension__short_read); + goto suspend; + } + uint64_t* scratch = &self->private_data.s_decode_ihdr[0].scratch; + uint32_t num_bits_0 = ((uint32_t)(*scratch & 0xFF)); + *scratch >>= 8; + *scratch <<= 8; + *scratch |= ((uint64_t)(*iop_a_src++)) << (56 - num_bits_0); + if (num_bits_0 == 24) { + t_0 = ((uint32_t)(*scratch >> 32)); + break; + } + num_bits_0 += 8; + *scratch |= ((uint64_t)(num_bits_0)); + } + } + v_a32 = t_0; + } + if ((v_a32 == 0) || (v_a32 >= 2147483648)) { + status = wuffs_base__make_status(wuffs_png__error__bad_header); + goto exit; + } else if (v_a32 >= 16777216) { + status = wuffs_base__make_status(wuffs_png__error__unsupported_png_file); + goto exit; + } + self->private_impl.f_width = v_a32; + { + WUFFS_BASE__COROUTINE_SUSPENSION_POINT(3); + uint32_t t_1; + if (WUFFS_BASE__LIKELY(io2_a_src - iop_a_src >= 4)) { + t_1 = wuffs_base__peek_u32be__no_bounds_check(iop_a_src); + iop_a_src += 4; + } else { + self->private_data.s_decode_ihdr[0].scratch = 0; + WUFFS_BASE__COROUTINE_SUSPENSION_POINT(4); + while (true) { + if (WUFFS_BASE__UNLIKELY(iop_a_src == io2_a_src)) { + status = wuffs_base__make_status(wuffs_base__suspension__short_read); + goto suspend; + } + uint64_t* scratch = &self->private_data.s_decode_ihdr[0].scratch; + uint32_t num_bits_1 = ((uint32_t)(*scratch & 0xFF)); + *scratch >>= 8; + *scratch <<= 8; + *scratch |= ((uint64_t)(*iop_a_src++)) << (56 - num_bits_1); + if (num_bits_1 == 24) { + t_1 = ((uint32_t)(*scratch >> 32)); + break; + } + num_bits_1 += 8; + *scratch |= ((uint64_t)(num_bits_1)); + } + } + v_a32 = t_1; + } + if ((v_a32 == 0) || (v_a32 >= 2147483648)) { + status = wuffs_base__make_status(wuffs_png__error__bad_header); + goto exit; + } else if (v_a32 >= 16777216) { + status = wuffs_base__make_status(wuffs_png__error__unsupported_png_file); + goto exit; + } + self->private_impl.f_height = v_a32; + { + WUFFS_BASE__COROUTINE_SUSPENSION_POINT(5); + if (WUFFS_BASE__UNLIKELY(iop_a_src == io2_a_src)) { + status = wuffs_base__make_status(wuffs_base__suspension__short_read); + goto suspend; + } + uint8_t t_2 = *iop_a_src++; + v_a8 = t_2; + } + if (v_a8 > 16) { + status = wuffs_base__make_status(wuffs_png__error__bad_header); + goto exit; + } + self->private_impl.f_depth = v_a8; + { + WUFFS_BASE__COROUTINE_SUSPENSION_POINT(6); + if (WUFFS_BASE__UNLIKELY(iop_a_src == io2_a_src)) { + status = wuffs_base__make_status(wuffs_base__suspension__short_read); + goto suspend; + } + uint8_t t_3 = *iop_a_src++; + v_a8 = t_3; + } + if ((v_a8 == 1) || (v_a8 == 5) || (v_a8 > 6)) { + status = wuffs_base__make_status(wuffs_png__error__bad_header); + goto exit; + } + self->private_impl.f_color_type = v_a8; + { + WUFFS_BASE__COROUTINE_SUSPENSION_POINT(7); + if (WUFFS_BASE__UNLIKELY(iop_a_src == io2_a_src)) { + status = wuffs_base__make_status(wuffs_base__suspension__short_read); + goto suspend; + } + uint8_t t_4 = *iop_a_src++; + v_a8 = t_4; + } + if (v_a8 != 0) { + status = wuffs_base__make_status(wuffs_png__error__unsupported_png_compression_method); + goto exit; + } + { + WUFFS_BASE__COROUTINE_SUSPENSION_POINT(8); + if (WUFFS_BASE__UNLIKELY(iop_a_src == io2_a_src)) { + status = wuffs_base__make_status(wuffs_base__suspension__short_read); + goto suspend; + } + uint8_t t_5 = *iop_a_src++; + v_a8 = t_5; + } + if (v_a8 != 0) { + status = wuffs_base__make_status(wuffs_png__error__bad_header); + goto exit; + } + { + WUFFS_BASE__COROUTINE_SUSPENSION_POINT(9); + if (WUFFS_BASE__UNLIKELY(iop_a_src == io2_a_src)) { + status = wuffs_base__make_status(wuffs_base__suspension__short_read); + goto suspend; + } + uint8_t t_6 = *iop_a_src++; + v_a8 = t_6; + } + if (v_a8 == 0) { + self->private_impl.f_interlace_pass = 0; + } else if (v_a8 == 1) { + self->private_impl.f_interlace_pass = 1; + self->private_impl.choosy_filter_and_swizzle = ( + &wuffs_png__decoder__filter_and_swizzle_tricky); + } else { + status = wuffs_base__make_status(wuffs_png__error__bad_header); + goto exit; + } + self->private_impl.f_filter_distance = 0; + wuffs_png__decoder__assign_filter_distance(self); + if (self->private_impl.f_filter_distance == 0) { + status = wuffs_base__make_status(wuffs_png__error__bad_header); + goto exit; + } + self->private_impl.f_overall_workbuf_length = (((uint64_t)(self->private_impl.f_height)) * (1 + wuffs_png__decoder__calculate_bytes_per_row(self, self->private_impl.f_width))); + wuffs_png__decoder__choose_filter_implementations(self); + + goto ok; + ok: + self->private_impl.p_decode_ihdr[0] = 0; + goto exit; + } + + goto suspend; + suspend: + self->private_impl.p_decode_ihdr[0] = wuffs_base__status__is_suspension(&status) ? coro_susp_point : 0; + + goto exit; + exit: + if (a_src && a_src->data.ptr) { + a_src->meta.ri = ((size_t)(iop_a_src - a_src->data.ptr)); + } + + return status; +} + +// -------- func png.decoder.assign_filter_distance + +static wuffs_base__empty_struct +wuffs_png__decoder__assign_filter_distance( + wuffs_png__decoder* self) { + if (self->private_impl.f_depth < 8) { + if ((self->private_impl.f_depth != 1) && (self->private_impl.f_depth != 2) && (self->private_impl.f_depth != 4)) { + return wuffs_base__make_empty_struct(); + } else if (self->private_impl.f_color_type == 0) { + self->private_impl.f_dst_pixfmt = 536870920; + self->private_impl.f_src_pixfmt = 536870920; + } else if (self->private_impl.f_color_type == 3) { + self->private_impl.f_dst_pixfmt = 2198077448; + self->private_impl.f_src_pixfmt = 2198077448; + } else { + return wuffs_base__make_empty_struct(); + } + self->private_impl.f_filter_distance = 1; + self->private_impl.choosy_filter_and_swizzle = ( + &wuffs_png__decoder__filter_and_swizzle_tricky); + } else if (self->private_impl.f_color_type == 0) { + if (self->private_impl.f_depth == 8) { + self->private_impl.f_dst_pixfmt = 536870920; + self->private_impl.f_src_pixfmt = 536870920; + self->private_impl.f_filter_distance = 1; + } else if (self->private_impl.f_depth == 16) { + if (self->private_impl.f_interlace_pass == 0) { + self->private_impl.f_dst_pixfmt = 536870923; + self->private_impl.f_src_pixfmt = 537919499; + } else { + self->private_impl.f_dst_pixfmt = 2164308923; + self->private_impl.f_src_pixfmt = 2164308923; + } + self->private_impl.f_filter_distance = 2; + } + } else if (self->private_impl.f_color_type == 2) { + if (self->private_impl.f_depth == 8) { + self->private_impl.f_dst_pixfmt = 2147485832; + self->private_impl.f_src_pixfmt = 2684356744; + self->private_impl.f_filter_distance = 3; + } else if (self->private_impl.f_depth == 16) { + self->private_impl.f_dst_pixfmt = 2164308923; + self->private_impl.f_src_pixfmt = 2164308923; + self->private_impl.f_filter_distance = 6; + self->private_impl.choosy_filter_and_swizzle = ( + &wuffs_png__decoder__filter_and_swizzle_tricky); + } + } else if (self->private_impl.f_color_type == 3) { + if (self->private_impl.f_depth == 8) { + self->private_impl.f_dst_pixfmt = 2198077448; + self->private_impl.f_src_pixfmt = 2198077448; + self->private_impl.f_filter_distance = 1; + } + } else if (self->private_impl.f_color_type == 4) { + if (self->private_impl.f_depth == 8) { + self->private_impl.f_dst_pixfmt = 2164295816; + self->private_impl.f_src_pixfmt = 2164295816; + self->private_impl.f_filter_distance = 2; + self->private_impl.choosy_filter_and_swizzle = ( + &wuffs_png__decoder__filter_and_swizzle_tricky); + } else if (self->private_impl.f_depth == 16) { + self->private_impl.f_dst_pixfmt = 2164308923; + self->private_impl.f_src_pixfmt = 2164308923; + self->private_impl.f_filter_distance = 4; + self->private_impl.choosy_filter_and_swizzle = ( + &wuffs_png__decoder__filter_and_swizzle_tricky); + } + } else if (self->private_impl.f_color_type == 6) { + if (self->private_impl.f_depth == 8) { + self->private_impl.f_dst_pixfmt = 2164295816; + self->private_impl.f_src_pixfmt = 2701166728; + self->private_impl.f_filter_distance = 4; + } else if (self->private_impl.f_depth == 16) { + self->private_impl.f_dst_pixfmt = 2164308923; + self->private_impl.f_src_pixfmt = 2164308923; + self->private_impl.f_filter_distance = 8; + self->private_impl.choosy_filter_and_swizzle = ( + &wuffs_png__decoder__filter_and_swizzle_tricky); + } + } + return wuffs_base__make_empty_struct(); +} + +// -------- func png.decoder.calculate_bytes_per_row + +static uint64_t +wuffs_png__decoder__calculate_bytes_per_row( + const wuffs_png__decoder* self, + uint32_t a_width) { + uint64_t v_bytes_per_channel = 0; + + if (self->private_impl.f_depth == 1) { + return ((uint64_t)(((a_width + 7) / 8))); + } else if (self->private_impl.f_depth == 2) { + return ((uint64_t)(((a_width + 3) / 4))); + } else if (self->private_impl.f_depth == 4) { + return ((uint64_t)(((a_width + 1) / 2))); + } + v_bytes_per_channel = ((uint64_t)((self->private_impl.f_depth >> 3))); + return (((uint64_t)(a_width)) * v_bytes_per_channel * ((uint64_t)(WUFFS_PNG__NUM_CHANNELS[self->private_impl.f_color_type]))); +} + +// -------- func png.decoder.choose_filter_implementations + +static wuffs_base__empty_struct +wuffs_png__decoder__choose_filter_implementations( + wuffs_png__decoder* self) { + if (self->private_impl.f_filter_distance == 3) { + self->private_impl.choosy_filter_1 = ( + &wuffs_png__decoder__filter_1_distance_3_fallback); + self->private_impl.choosy_filter_3 = ( + &wuffs_png__decoder__filter_3_distance_3_fallback); + self->private_impl.choosy_filter_4 = ( +#if defined(WUFFS_BASE__CPU_ARCH__ARM_NEON) + wuffs_base__cpu_arch__have_arm_neon() ? &wuffs_png__decoder__filter_4_distance_3_arm_neon : +#endif +#if defined(WUFFS_BASE__CPU_ARCH__X86_FAMILY) + wuffs_base__cpu_arch__have_x86_sse42() ? &wuffs_png__decoder__filter_4_distance_3_x86_sse42 : +#endif + &wuffs_png__decoder__filter_4_distance_3_fallback); + } else if (self->private_impl.f_filter_distance == 4) { + self->private_impl.choosy_filter_1 = ( +#if defined(WUFFS_BASE__CPU_ARCH__ARM_NEON) + wuffs_base__cpu_arch__have_arm_neon() ? &wuffs_png__decoder__filter_1_distance_4_arm_neon : +#endif +#if defined(WUFFS_BASE__CPU_ARCH__X86_FAMILY) + wuffs_base__cpu_arch__have_x86_sse42() ? &wuffs_png__decoder__filter_1_distance_4_x86_sse42 : +#endif + &wuffs_png__decoder__filter_1_distance_4_fallback); + self->private_impl.choosy_filter_3 = ( +#if defined(WUFFS_BASE__CPU_ARCH__ARM_NEON) + wuffs_base__cpu_arch__have_arm_neon() ? &wuffs_png__decoder__filter_3_distance_4_arm_neon : +#endif +#if defined(WUFFS_BASE__CPU_ARCH__X86_FAMILY) + wuffs_base__cpu_arch__have_x86_sse42() ? &wuffs_png__decoder__filter_3_distance_4_x86_sse42 : +#endif + &wuffs_png__decoder__filter_3_distance_4_fallback); + self->private_impl.choosy_filter_4 = ( +#if defined(WUFFS_BASE__CPU_ARCH__ARM_NEON) + wuffs_base__cpu_arch__have_arm_neon() ? &wuffs_png__decoder__filter_4_distance_4_arm_neon : +#endif +#if defined(WUFFS_BASE__CPU_ARCH__X86_FAMILY) + wuffs_base__cpu_arch__have_x86_sse42() ? &wuffs_png__decoder__filter_4_distance_4_x86_sse42 : +#endif + &wuffs_png__decoder__filter_4_distance_4_fallback); + } + return wuffs_base__make_empty_struct(); +} + +// -------- func png.decoder.decode_other_chunk + +static wuffs_base__status +wuffs_png__decoder__decode_other_chunk( + wuffs_png__decoder* self, + wuffs_base__io_buffer* a_src, + bool a_framy) { + wuffs_base__status status = wuffs_base__make_status(NULL); + + const uint8_t* iop_a_src = NULL; + const uint8_t* io0_a_src WUFFS_BASE__POTENTIALLY_UNUSED = NULL; + const uint8_t* io1_a_src WUFFS_BASE__POTENTIALLY_UNUSED = NULL; + const uint8_t* io2_a_src WUFFS_BASE__POTENTIALLY_UNUSED = NULL; + if (a_src && a_src->data.ptr) { + io0_a_src = a_src->data.ptr; + io1_a_src = io0_a_src + a_src->meta.ri; + iop_a_src = io1_a_src; + io2_a_src = io0_a_src + a_src->meta.wi; + } + + uint32_t coro_susp_point = self->private_impl.p_decode_other_chunk[0]; + switch (coro_susp_point) { + WUFFS_BASE__COROUTINE_SUSPENSION_POINT_0; + + if ((self->private_impl.f_chunk_type == 1163152464) && ! a_framy) { + if (self->private_impl.f_seen_plte) { + status = wuffs_base__make_status(wuffs_png__error__bad_chunk); + goto exit; + } else if (self->private_impl.f_color_type == 3) { + if (a_src) { + a_src->meta.ri = ((size_t)(iop_a_src - a_src->data.ptr)); + } + WUFFS_BASE__COROUTINE_SUSPENSION_POINT(1); + status = wuffs_png__decoder__decode_plte(self, a_src); + if (a_src) { + iop_a_src = a_src->data.ptr + a_src->meta.ri; + } + if (status.repr) { + goto suspend; + } + } else if ((self->private_impl.f_color_type == 2) || (self->private_impl.f_color_type == 6)) { + } else { + status = wuffs_base__make_status(wuffs_png__error__bad_chunk); + goto exit; + } + self->private_impl.f_seen_plte = true; + } else if ((self->private_impl.f_chunk_type & 32) == 0) { + if (self->private_impl.f_chunk_type != 1413563465) { + status = wuffs_base__make_status(wuffs_png__error__bad_chunk); + goto exit; + } + } + if (self->private_impl.f_chunk_type == 1716082789) { + if (self->private_impl.f_report_metadata_exif) { + if (self->private_impl.f_seen_exif) { + status = wuffs_base__make_status(wuffs_png__error__bad_chunk); + goto exit; + } + if (a_src) { + a_src->meta.ri = ((size_t)(iop_a_src - a_src->data.ptr)); + } + WUFFS_BASE__COROUTINE_SUSPENSION_POINT(2); + status = wuffs_png__decoder__decode_exif(self, a_src); + if (a_src) { + iop_a_src = a_src->data.ptr + a_src->meta.ri; + } + if (status.repr) { + goto suspend; + } + self->private_impl.f_seen_exif = true; + } + } else if ((self->private_impl.f_chunk_type == 1951945833) || (self->private_impl.f_chunk_type == 1951942004) || (self->private_impl.f_chunk_type == 1951945850)) { + if (self->private_impl.f_report_metadata_kvp) { + self->private_impl.f_metadata_flavor = 4; + self->private_impl.f_metadata_fourcc = 1263947851; + self->private_impl.f_metadata_x = 0; + self->private_impl.f_metadata_y = 0; + self->private_impl.f_metadata_z = 0; + } + } else if ( ! a_framy) { + if (self->private_impl.f_chunk_type == 1280598881) { + if (self->private_impl.f_seen_actl) { + status = wuffs_base__make_status(wuffs_png__error__bad_chunk); + goto exit; + } + if (a_src) { + a_src->meta.ri = ((size_t)(iop_a_src - a_src->data.ptr)); + } + WUFFS_BASE__COROUTINE_SUSPENSION_POINT(3); + status = wuffs_png__decoder__decode_actl(self, a_src); + if (a_src) { + iop_a_src = a_src->data.ptr + a_src->meta.ri; + } + if (status.repr) { + goto suspend; + } + self->private_impl.f_seen_actl = true; + } else if (self->private_impl.f_chunk_type == 1297238115) { + if (self->private_impl.f_report_metadata_chrm) { + if (self->private_impl.f_seen_chrm) { + status = wuffs_base__make_status(wuffs_png__error__bad_chunk); + goto exit; + } + if (a_src) { + a_src->meta.ri = ((size_t)(iop_a_src - a_src->data.ptr)); + } + WUFFS_BASE__COROUTINE_SUSPENSION_POINT(4); + status = wuffs_png__decoder__decode_chrm(self, a_src); + if (a_src) { + iop_a_src = a_src->data.ptr + a_src->meta.ri; + } + if (status.repr) { + goto suspend; + } + self->private_impl.f_seen_chrm = true; + } + } else if (self->private_impl.f_chunk_type == 1280598886) { + if (self->private_impl.f_seen_fctl) { + status = wuffs_base__make_status(wuffs_png__error__bad_chunk); + goto exit; + } + if (a_src) { + a_src->meta.ri = ((size_t)(iop_a_src - a_src->data.ptr)); + } + WUFFS_BASE__COROUTINE_SUSPENSION_POINT(5); + status = wuffs_png__decoder__decode_fctl(self, a_src); + if (a_src) { + iop_a_src = a_src->data.ptr + a_src->meta.ri; + } + if (status.repr) { + goto suspend; + } + self->private_impl.f_seen_fctl = true; + } else if (self->private_impl.f_chunk_type == 1095582055) { + if (self->private_impl.f_report_metadata_gama) { + if (self->private_impl.f_seen_gama) { + status = wuffs_base__make_status(wuffs_png__error__bad_chunk); + goto exit; + } + if (a_src) { + a_src->meta.ri = ((size_t)(iop_a_src - a_src->data.ptr)); + } + WUFFS_BASE__COROUTINE_SUSPENSION_POINT(6); + status = wuffs_png__decoder__decode_gama(self, a_src); + if (a_src) { + iop_a_src = a_src->data.ptr + a_src->meta.ri; + } + if (status.repr) { + goto suspend; + } + self->private_impl.f_seen_gama = true; + } + } else if (self->private_impl.f_chunk_type == 1346585449) { + if (self->private_impl.f_report_metadata_iccp) { + if (self->private_impl.f_seen_iccp) { + status = wuffs_base__make_status(wuffs_png__error__bad_chunk); + goto exit; + } + if (a_src) { + a_src->meta.ri = ((size_t)(iop_a_src - a_src->data.ptr)); + } + WUFFS_BASE__COROUTINE_SUSPENSION_POINT(7); + status = wuffs_png__decoder__decode_iccp(self, a_src); + if (a_src) { + iop_a_src = a_src->data.ptr + a_src->meta.ri; + } + if (status.repr) { + goto suspend; + } + self->private_impl.f_seen_iccp = true; + } + } else if (self->private_impl.f_chunk_type == 1111970419) { + if (self->private_impl.f_report_metadata_srgb) { + if (self->private_impl.f_seen_srgb) { + status = wuffs_base__make_status(wuffs_png__error__bad_chunk); + goto exit; + } + if (a_src) { + a_src->meta.ri = ((size_t)(iop_a_src - a_src->data.ptr)); + } + WUFFS_BASE__COROUTINE_SUSPENSION_POINT(8); + status = wuffs_png__decoder__decode_srgb(self, a_src); + if (a_src) { + iop_a_src = a_src->data.ptr + a_src->meta.ri; + } + if (status.repr) { + goto suspend; + } + self->private_impl.f_seen_srgb = true; + } + } else if (self->private_impl.f_chunk_type == 1397641844) { + if (self->private_impl.f_seen_trns || ((self->private_impl.f_color_type == 3) && ! self->private_impl.f_seen_plte)) { + status = wuffs_base__make_status(wuffs_png__error__bad_chunk); + goto exit; + } else if (self->private_impl.f_color_type > 3) { + } else { + if (a_src) { + a_src->meta.ri = ((size_t)(iop_a_src - a_src->data.ptr)); + } + WUFFS_BASE__COROUTINE_SUSPENSION_POINT(9); + status = wuffs_png__decoder__decode_trns(self, a_src); + if (a_src) { + iop_a_src = a_src->data.ptr + a_src->meta.ri; + } + if (status.repr) { + goto suspend; + } + } + self->private_impl.f_seen_trns = true; + } + } + if (self->private_impl.f_metadata_fourcc == 0) { + self->private_data.s_decode_other_chunk[0].scratch = self->private_impl.f_chunk_length; + WUFFS_BASE__COROUTINE_SUSPENSION_POINT(10); + if (self->private_data.s_decode_other_chunk[0].scratch > ((uint64_t)(io2_a_src - iop_a_src))) { + self->private_data.s_decode_other_chunk[0].scratch -= ((uint64_t)(io2_a_src - iop_a_src)); + iop_a_src = io2_a_src; + status = wuffs_base__make_status(wuffs_base__suspension__short_read); + goto suspend; + } + iop_a_src += self->private_data.s_decode_other_chunk[0].scratch; + } + + goto ok; + ok: + self->private_impl.p_decode_other_chunk[0] = 0; + goto exit; + } + + goto suspend; + suspend: + self->private_impl.p_decode_other_chunk[0] = wuffs_base__status__is_suspension(&status) ? coro_susp_point : 0; + + goto exit; + exit: + if (a_src && a_src->data.ptr) { + a_src->meta.ri = ((size_t)(iop_a_src - a_src->data.ptr)); + } + + return status; +} + +// -------- func png.decoder.decode_actl + +static wuffs_base__status +wuffs_png__decoder__decode_actl( + wuffs_png__decoder* self, + wuffs_base__io_buffer* a_src) { + wuffs_base__status status = wuffs_base__make_status(NULL); + + const uint8_t* iop_a_src = NULL; + const uint8_t* io0_a_src WUFFS_BASE__POTENTIALLY_UNUSED = NULL; + const uint8_t* io1_a_src WUFFS_BASE__POTENTIALLY_UNUSED = NULL; + const uint8_t* io2_a_src WUFFS_BASE__POTENTIALLY_UNUSED = NULL; + if (a_src && a_src->data.ptr) { + io0_a_src = a_src->data.ptr; + io1_a_src = io0_a_src + a_src->meta.ri; + iop_a_src = io1_a_src; + io2_a_src = io0_a_src + a_src->meta.wi; + } + + uint32_t coro_susp_point = self->private_impl.p_decode_actl[0]; + switch (coro_susp_point) { + WUFFS_BASE__COROUTINE_SUSPENSION_POINT_0; + + if (self->private_impl.f_chunk_length != 8) { + status = wuffs_base__make_status(wuffs_png__error__bad_chunk); + goto exit; + } else if (self->private_impl.f_interlace_pass > 0) { + status = wuffs_base__make_status(wuffs_png__error__unsupported_png_file); + goto exit; + } + self->private_impl.f_chunk_length = 0; + { + WUFFS_BASE__COROUTINE_SUSPENSION_POINT(1); + uint32_t t_0; + if (WUFFS_BASE__LIKELY(io2_a_src - iop_a_src >= 4)) { + t_0 = wuffs_base__peek_u32be__no_bounds_check(iop_a_src); + iop_a_src += 4; + } else { + self->private_data.s_decode_actl[0].scratch = 0; + WUFFS_BASE__COROUTINE_SUSPENSION_POINT(2); + while (true) { + if (WUFFS_BASE__UNLIKELY(iop_a_src == io2_a_src)) { + status = wuffs_base__make_status(wuffs_base__suspension__short_read); + goto suspend; + } + uint64_t* scratch = &self->private_data.s_decode_actl[0].scratch; + uint32_t num_bits_0 = ((uint32_t)(*scratch & 0xFF)); + *scratch >>= 8; + *scratch <<= 8; + *scratch |= ((uint64_t)(*iop_a_src++)) << (56 - num_bits_0); + if (num_bits_0 == 24) { + t_0 = ((uint32_t)(*scratch >> 32)); + break; + } + num_bits_0 += 8; + *scratch |= ((uint64_t)(num_bits_0)); + } + } + self->private_impl.f_num_animation_frames_value = t_0; + } + if (self->private_impl.f_num_animation_frames_value == 0) { + status = wuffs_base__make_status(wuffs_png__error__bad_chunk); + goto exit; + } + { + WUFFS_BASE__COROUTINE_SUSPENSION_POINT(3); + uint32_t t_1; + if (WUFFS_BASE__LIKELY(io2_a_src - iop_a_src >= 4)) { + t_1 = wuffs_base__peek_u32be__no_bounds_check(iop_a_src); + iop_a_src += 4; + } else { + self->private_data.s_decode_actl[0].scratch = 0; + WUFFS_BASE__COROUTINE_SUSPENSION_POINT(4); + while (true) { + if (WUFFS_BASE__UNLIKELY(iop_a_src == io2_a_src)) { + status = wuffs_base__make_status(wuffs_base__suspension__short_read); + goto suspend; + } + uint64_t* scratch = &self->private_data.s_decode_actl[0].scratch; + uint32_t num_bits_1 = ((uint32_t)(*scratch & 0xFF)); + *scratch >>= 8; + *scratch <<= 8; + *scratch |= ((uint64_t)(*iop_a_src++)) << (56 - num_bits_1); + if (num_bits_1 == 24) { + t_1 = ((uint32_t)(*scratch >> 32)); + break; + } + num_bits_1 += 8; + *scratch |= ((uint64_t)(num_bits_1)); + } + } + self->private_impl.f_num_animation_loops_value = t_1; + } + + goto ok; + ok: + self->private_impl.p_decode_actl[0] = 0; + goto exit; + } + + goto suspend; + suspend: + self->private_impl.p_decode_actl[0] = wuffs_base__status__is_suspension(&status) ? coro_susp_point : 0; + + goto exit; + exit: + if (a_src && a_src->data.ptr) { + a_src->meta.ri = ((size_t)(iop_a_src - a_src->data.ptr)); + } + + return status; +} + +// -------- func png.decoder.decode_chrm + +static wuffs_base__status +wuffs_png__decoder__decode_chrm( + wuffs_png__decoder* self, + wuffs_base__io_buffer* a_src) { + wuffs_base__status status = wuffs_base__make_status(NULL); + + uint64_t v_u = 0; + + const uint8_t* iop_a_src = NULL; + const uint8_t* io0_a_src WUFFS_BASE__POTENTIALLY_UNUSED = NULL; + const uint8_t* io1_a_src WUFFS_BASE__POTENTIALLY_UNUSED = NULL; + const uint8_t* io2_a_src WUFFS_BASE__POTENTIALLY_UNUSED = NULL; + if (a_src && a_src->data.ptr) { + io0_a_src = a_src->data.ptr; + io1_a_src = io0_a_src + a_src->meta.ri; + iop_a_src = io1_a_src; + io2_a_src = io0_a_src + a_src->meta.wi; + } + + uint32_t coro_susp_point = self->private_impl.p_decode_chrm[0]; + switch (coro_susp_point) { + WUFFS_BASE__COROUTINE_SUSPENSION_POINT_0; + + if (self->private_impl.f_chunk_length != 32) { + status = wuffs_base__make_status(wuffs_png__error__bad_chunk); + goto exit; + } + self->private_impl.f_chunk_length = 0; + self->private_impl.f_metadata_flavor = 5; + self->private_impl.f_metadata_fourcc = 1128813133; + self->private_impl.f_metadata_x = 0; + self->private_impl.f_metadata_y = 0; + self->private_impl.f_metadata_z = 0; + { + WUFFS_BASE__COROUTINE_SUSPENSION_POINT(1); + uint64_t t_0; + if (WUFFS_BASE__LIKELY(io2_a_src - iop_a_src >= 4)) { + t_0 = ((uint64_t)(wuffs_base__peek_u32be__no_bounds_check(iop_a_src))); + iop_a_src += 4; + } else { + self->private_data.s_decode_chrm[0].scratch = 0; + WUFFS_BASE__COROUTINE_SUSPENSION_POINT(2); + while (true) { + if (WUFFS_BASE__UNLIKELY(iop_a_src == io2_a_src)) { + status = wuffs_base__make_status(wuffs_base__suspension__short_read); + goto suspend; + } + uint64_t* scratch = &self->private_data.s_decode_chrm[0].scratch; + uint32_t num_bits_0 = ((uint32_t)(*scratch & 0xFF)); + *scratch >>= 8; + *scratch <<= 8; + *scratch |= ((uint64_t)(*iop_a_src++)) << (56 - num_bits_0); + if (num_bits_0 == 24) { + t_0 = ((uint64_t)(*scratch >> 32)); + break; + } + num_bits_0 += 8; + *scratch |= ((uint64_t)(num_bits_0)); + } + } + v_u = t_0; + } + self->private_impl.f_metadata_x |= ((16777215 & v_u) << 0); + { + WUFFS_BASE__COROUTINE_SUSPENSION_POINT(3); + uint64_t t_1; + if (WUFFS_BASE__LIKELY(io2_a_src - iop_a_src >= 4)) { + t_1 = ((uint64_t)(wuffs_base__peek_u32be__no_bounds_check(iop_a_src))); + iop_a_src += 4; + } else { + self->private_data.s_decode_chrm[0].scratch = 0; + WUFFS_BASE__COROUTINE_SUSPENSION_POINT(4); + while (true) { + if (WUFFS_BASE__UNLIKELY(iop_a_src == io2_a_src)) { + status = wuffs_base__make_status(wuffs_base__suspension__short_read); + goto suspend; + } + uint64_t* scratch = &self->private_data.s_decode_chrm[0].scratch; + uint32_t num_bits_1 = ((uint32_t)(*scratch & 0xFF)); + *scratch >>= 8; + *scratch <<= 8; + *scratch |= ((uint64_t)(*iop_a_src++)) << (56 - num_bits_1); + if (num_bits_1 == 24) { + t_1 = ((uint64_t)(*scratch >> 32)); + break; + } + num_bits_1 += 8; + *scratch |= ((uint64_t)(num_bits_1)); + } + } + v_u = t_1; + } + self->private_impl.f_metadata_x |= ((16777215 & v_u) << 24); + { + WUFFS_BASE__COROUTINE_SUSPENSION_POINT(5); + uint64_t t_2; + if (WUFFS_BASE__LIKELY(io2_a_src - iop_a_src >= 4)) { + t_2 = ((uint64_t)(wuffs_base__peek_u32be__no_bounds_check(iop_a_src))); + iop_a_src += 4; + } else { + self->private_data.s_decode_chrm[0].scratch = 0; + WUFFS_BASE__COROUTINE_SUSPENSION_POINT(6); + while (true) { + if (WUFFS_BASE__UNLIKELY(iop_a_src == io2_a_src)) { + status = wuffs_base__make_status(wuffs_base__suspension__short_read); + goto suspend; + } + uint64_t* scratch = &self->private_data.s_decode_chrm[0].scratch; + uint32_t num_bits_2 = ((uint32_t)(*scratch & 0xFF)); + *scratch >>= 8; + *scratch <<= 8; + *scratch |= ((uint64_t)(*iop_a_src++)) << (56 - num_bits_2); + if (num_bits_2 == 24) { + t_2 = ((uint64_t)(*scratch >> 32)); + break; + } + num_bits_2 += 8; + *scratch |= ((uint64_t)(num_bits_2)); + } + } + v_u = t_2; + } + self->private_impl.f_metadata_x |= ((uint64_t)((16777215 & v_u) << 48)); + self->private_impl.f_metadata_y |= ((16777215 & v_u) >> 16); + { + WUFFS_BASE__COROUTINE_SUSPENSION_POINT(7); + uint64_t t_3; + if (WUFFS_BASE__LIKELY(io2_a_src - iop_a_src >= 4)) { + t_3 = ((uint64_t)(wuffs_base__peek_u32be__no_bounds_check(iop_a_src))); + iop_a_src += 4; + } else { + self->private_data.s_decode_chrm[0].scratch = 0; + WUFFS_BASE__COROUTINE_SUSPENSION_POINT(8); + while (true) { + if (WUFFS_BASE__UNLIKELY(iop_a_src == io2_a_src)) { + status = wuffs_base__make_status(wuffs_base__suspension__short_read); + goto suspend; + } + uint64_t* scratch = &self->private_data.s_decode_chrm[0].scratch; + uint32_t num_bits_3 = ((uint32_t)(*scratch & 0xFF)); + *scratch >>= 8; + *scratch <<= 8; + *scratch |= ((uint64_t)(*iop_a_src++)) << (56 - num_bits_3); + if (num_bits_3 == 24) { + t_3 = ((uint64_t)(*scratch >> 32)); + break; + } + num_bits_3 += 8; + *scratch |= ((uint64_t)(num_bits_3)); + } + } + v_u = t_3; + } + self->private_impl.f_metadata_y |= ((16777215 & v_u) << 8); + { + WUFFS_BASE__COROUTINE_SUSPENSION_POINT(9); + uint64_t t_4; + if (WUFFS_BASE__LIKELY(io2_a_src - iop_a_src >= 4)) { + t_4 = ((uint64_t)(wuffs_base__peek_u32be__no_bounds_check(iop_a_src))); + iop_a_src += 4; + } else { + self->private_data.s_decode_chrm[0].scratch = 0; + WUFFS_BASE__COROUTINE_SUSPENSION_POINT(10); + while (true) { + if (WUFFS_BASE__UNLIKELY(iop_a_src == io2_a_src)) { + status = wuffs_base__make_status(wuffs_base__suspension__short_read); + goto suspend; + } + uint64_t* scratch = &self->private_data.s_decode_chrm[0].scratch; + uint32_t num_bits_4 = ((uint32_t)(*scratch & 0xFF)); + *scratch >>= 8; + *scratch <<= 8; + *scratch |= ((uint64_t)(*iop_a_src++)) << (56 - num_bits_4); + if (num_bits_4 == 24) { + t_4 = ((uint64_t)(*scratch >> 32)); + break; + } + num_bits_4 += 8; + *scratch |= ((uint64_t)(num_bits_4)); + } + } + v_u = t_4; + } + self->private_impl.f_metadata_y |= ((16777215 & v_u) << 32); + { + WUFFS_BASE__COROUTINE_SUSPENSION_POINT(11); + uint64_t t_5; + if (WUFFS_BASE__LIKELY(io2_a_src - iop_a_src >= 4)) { + t_5 = ((uint64_t)(wuffs_base__peek_u32be__no_bounds_check(iop_a_src))); + iop_a_src += 4; + } else { + self->private_data.s_decode_chrm[0].scratch = 0; + WUFFS_BASE__COROUTINE_SUSPENSION_POINT(12); + while (true) { + if (WUFFS_BASE__UNLIKELY(iop_a_src == io2_a_src)) { + status = wuffs_base__make_status(wuffs_base__suspension__short_read); + goto suspend; + } + uint64_t* scratch = &self->private_data.s_decode_chrm[0].scratch; + uint32_t num_bits_5 = ((uint32_t)(*scratch & 0xFF)); + *scratch >>= 8; + *scratch <<= 8; + *scratch |= ((uint64_t)(*iop_a_src++)) << (56 - num_bits_5); + if (num_bits_5 == 24) { + t_5 = ((uint64_t)(*scratch >> 32)); + break; + } + num_bits_5 += 8; + *scratch |= ((uint64_t)(num_bits_5)); + } + } + v_u = t_5; + } + self->private_impl.f_metadata_y |= ((uint64_t)((16777215 & v_u) << 56)); + self->private_impl.f_metadata_z |= ((16777215 & v_u) >> 8); + { + WUFFS_BASE__COROUTINE_SUSPENSION_POINT(13); + uint64_t t_6; + if (WUFFS_BASE__LIKELY(io2_a_src - iop_a_src >= 4)) { + t_6 = ((uint64_t)(wuffs_base__peek_u32be__no_bounds_check(iop_a_src))); + iop_a_src += 4; + } else { + self->private_data.s_decode_chrm[0].scratch = 0; + WUFFS_BASE__COROUTINE_SUSPENSION_POINT(14); + while (true) { + if (WUFFS_BASE__UNLIKELY(iop_a_src == io2_a_src)) { + status = wuffs_base__make_status(wuffs_base__suspension__short_read); + goto suspend; + } + uint64_t* scratch = &self->private_data.s_decode_chrm[0].scratch; + uint32_t num_bits_6 = ((uint32_t)(*scratch & 0xFF)); + *scratch >>= 8; + *scratch <<= 8; + *scratch |= ((uint64_t)(*iop_a_src++)) << (56 - num_bits_6); + if (num_bits_6 == 24) { + t_6 = ((uint64_t)(*scratch >> 32)); + break; + } + num_bits_6 += 8; + *scratch |= ((uint64_t)(num_bits_6)); + } + } + v_u = t_6; + } + self->private_impl.f_metadata_z |= ((16777215 & v_u) << 16); + { + WUFFS_BASE__COROUTINE_SUSPENSION_POINT(15); + uint64_t t_7; + if (WUFFS_BASE__LIKELY(io2_a_src - iop_a_src >= 4)) { + t_7 = ((uint64_t)(wuffs_base__peek_u32be__no_bounds_check(iop_a_src))); + iop_a_src += 4; + } else { + self->private_data.s_decode_chrm[0].scratch = 0; + WUFFS_BASE__COROUTINE_SUSPENSION_POINT(16); + while (true) { + if (WUFFS_BASE__UNLIKELY(iop_a_src == io2_a_src)) { + status = wuffs_base__make_status(wuffs_base__suspension__short_read); + goto suspend; + } + uint64_t* scratch = &self->private_data.s_decode_chrm[0].scratch; + uint32_t num_bits_7 = ((uint32_t)(*scratch & 0xFF)); + *scratch >>= 8; + *scratch <<= 8; + *scratch |= ((uint64_t)(*iop_a_src++)) << (56 - num_bits_7); + if (num_bits_7 == 24) { + t_7 = ((uint64_t)(*scratch >> 32)); + break; + } + num_bits_7 += 8; + *scratch |= ((uint64_t)(num_bits_7)); + } + } + v_u = t_7; + } + self->private_impl.f_metadata_z |= ((16777215 & v_u) << 40); + + goto ok; + ok: + self->private_impl.p_decode_chrm[0] = 0; + goto exit; + } + + goto suspend; + suspend: + self->private_impl.p_decode_chrm[0] = wuffs_base__status__is_suspension(&status) ? coro_susp_point : 0; + + goto exit; + exit: + if (a_src && a_src->data.ptr) { + a_src->meta.ri = ((size_t)(iop_a_src - a_src->data.ptr)); + } + + return status; +} + +// -------- func png.decoder.decode_exif + +static wuffs_base__status +wuffs_png__decoder__decode_exif( + wuffs_png__decoder* self, + wuffs_base__io_buffer* a_src) { + wuffs_base__status status = wuffs_base__make_status(NULL); + + const uint8_t* iop_a_src = NULL; + const uint8_t* io0_a_src WUFFS_BASE__POTENTIALLY_UNUSED = NULL; + const uint8_t* io1_a_src WUFFS_BASE__POTENTIALLY_UNUSED = NULL; + const uint8_t* io2_a_src WUFFS_BASE__POTENTIALLY_UNUSED = NULL; + if (a_src && a_src->data.ptr) { + io0_a_src = a_src->data.ptr; + io1_a_src = io0_a_src + a_src->meta.ri; + iop_a_src = io1_a_src; + io2_a_src = io0_a_src + a_src->meta.wi; + } + + if (self->private_impl.f_chunk_length < 4) { + status = wuffs_base__make_status(wuffs_png__error__bad_chunk); + goto exit; + } + self->private_impl.f_metadata_flavor = 3; + self->private_impl.f_metadata_fourcc = 1163413830; + self->private_impl.f_metadata_x = 0; + self->private_impl.f_metadata_y = wuffs_base__u64__sat_add((a_src ? a_src->meta.pos : 0), ((uint64_t)(iop_a_src - io0_a_src))); + self->private_impl.f_metadata_z = wuffs_base__u64__sat_add(self->private_impl.f_metadata_y, ((uint64_t)(self->private_impl.f_chunk_length))); + self->private_impl.f_chunk_length = 0; + + goto ok; + ok: + goto exit; + exit: + if (a_src && a_src->data.ptr) { + a_src->meta.ri = ((size_t)(iop_a_src - a_src->data.ptr)); + } + + return status; +} + +// -------- func png.decoder.decode_fctl + +static wuffs_base__status +wuffs_png__decoder__decode_fctl( + wuffs_png__decoder* self, + wuffs_base__io_buffer* a_src) { + wuffs_base__status status = wuffs_base__make_status(NULL); + + uint32_t v_x0 = 0; + uint32_t v_y0 = 0; + uint32_t v_x1 = 0; + uint32_t v_y1 = 0; + + const uint8_t* iop_a_src = NULL; + const uint8_t* io0_a_src WUFFS_BASE__POTENTIALLY_UNUSED = NULL; + const uint8_t* io1_a_src WUFFS_BASE__POTENTIALLY_UNUSED = NULL; + const uint8_t* io2_a_src WUFFS_BASE__POTENTIALLY_UNUSED = NULL; + if (a_src && a_src->data.ptr) { + io0_a_src = a_src->data.ptr; + io1_a_src = io0_a_src + a_src->meta.ri; + iop_a_src = io1_a_src; + io2_a_src = io0_a_src + a_src->meta.wi; + } + + uint32_t coro_susp_point = self->private_impl.p_decode_fctl[0]; + if (coro_susp_point) { + v_x0 = self->private_data.s_decode_fctl[0].v_x0; + v_x1 = self->private_data.s_decode_fctl[0].v_x1; + v_y1 = self->private_data.s_decode_fctl[0].v_y1; + } + switch (coro_susp_point) { + WUFFS_BASE__COROUTINE_SUSPENSION_POINT_0; + + if (self->private_impl.f_chunk_length != 26) { + status = wuffs_base__make_status(wuffs_png__error__bad_chunk); + goto exit; + } + self->private_impl.f_chunk_length = 0; + { + WUFFS_BASE__COROUTINE_SUSPENSION_POINT(1); + uint32_t t_0; + if (WUFFS_BASE__LIKELY(io2_a_src - iop_a_src >= 4)) { + t_0 = wuffs_base__peek_u32be__no_bounds_check(iop_a_src); + iop_a_src += 4; + } else { + self->private_data.s_decode_fctl[0].scratch = 0; + WUFFS_BASE__COROUTINE_SUSPENSION_POINT(2); + while (true) { + if (WUFFS_BASE__UNLIKELY(iop_a_src == io2_a_src)) { + status = wuffs_base__make_status(wuffs_base__suspension__short_read); + goto suspend; + } + uint64_t* scratch = &self->private_data.s_decode_fctl[0].scratch; + uint32_t num_bits_0 = ((uint32_t)(*scratch & 0xFF)); + *scratch >>= 8; + *scratch <<= 8; + *scratch |= ((uint64_t)(*iop_a_src++)) << (56 - num_bits_0); + if (num_bits_0 == 24) { + t_0 = ((uint32_t)(*scratch >> 32)); + break; + } + num_bits_0 += 8; + *scratch |= ((uint64_t)(num_bits_0)); + } + } + v_x0 = t_0; + } + if (v_x0 != self->private_impl.f_next_animation_seq_num) { + status = wuffs_base__make_status(wuffs_png__error__bad_animation_sequence_number); + goto exit; + } else if (self->private_impl.f_next_animation_seq_num >= 4294967295) { + status = wuffs_base__make_status(wuffs_png__error__unsupported_png_file); + goto exit; + } + self->private_impl.f_next_animation_seq_num += 1; + { + WUFFS_BASE__COROUTINE_SUSPENSION_POINT(3); + uint32_t t_1; + if (WUFFS_BASE__LIKELY(io2_a_src - iop_a_src >= 4)) { + t_1 = wuffs_base__peek_u32be__no_bounds_check(iop_a_src); + iop_a_src += 4; + } else { + self->private_data.s_decode_fctl[0].scratch = 0; + WUFFS_BASE__COROUTINE_SUSPENSION_POINT(4); + while (true) { + if (WUFFS_BASE__UNLIKELY(iop_a_src == io2_a_src)) { + status = wuffs_base__make_status(wuffs_base__suspension__short_read); + goto suspend; + } + uint64_t* scratch = &self->private_data.s_decode_fctl[0].scratch; + uint32_t num_bits_1 = ((uint32_t)(*scratch & 0xFF)); + *scratch >>= 8; + *scratch <<= 8; + *scratch |= ((uint64_t)(*iop_a_src++)) << (56 - num_bits_1); + if (num_bits_1 == 24) { + t_1 = ((uint32_t)(*scratch >> 32)); + break; + } + num_bits_1 += 8; + *scratch |= ((uint64_t)(num_bits_1)); + } + } + v_x1 = t_1; + } + { + WUFFS_BASE__COROUTINE_SUSPENSION_POINT(5); + uint32_t t_2; + if (WUFFS_BASE__LIKELY(io2_a_src - iop_a_src >= 4)) { + t_2 = wuffs_base__peek_u32be__no_bounds_check(iop_a_src); + iop_a_src += 4; + } else { + self->private_data.s_decode_fctl[0].scratch = 0; + WUFFS_BASE__COROUTINE_SUSPENSION_POINT(6); + while (true) { + if (WUFFS_BASE__UNLIKELY(iop_a_src == io2_a_src)) { + status = wuffs_base__make_status(wuffs_base__suspension__short_read); + goto suspend; + } + uint64_t* scratch = &self->private_data.s_decode_fctl[0].scratch; + uint32_t num_bits_2 = ((uint32_t)(*scratch & 0xFF)); + *scratch >>= 8; + *scratch <<= 8; + *scratch |= ((uint64_t)(*iop_a_src++)) << (56 - num_bits_2); + if (num_bits_2 == 24) { + t_2 = ((uint32_t)(*scratch >> 32)); + break; + } + num_bits_2 += 8; + *scratch |= ((uint64_t)(num_bits_2)); + } + } + v_y1 = t_2; + } + { + WUFFS_BASE__COROUTINE_SUSPENSION_POINT(7); + uint32_t t_3; + if (WUFFS_BASE__LIKELY(io2_a_src - iop_a_src >= 4)) { + t_3 = wuffs_base__peek_u32be__no_bounds_check(iop_a_src); + iop_a_src += 4; + } else { + self->private_data.s_decode_fctl[0].scratch = 0; + WUFFS_BASE__COROUTINE_SUSPENSION_POINT(8); + while (true) { + if (WUFFS_BASE__UNLIKELY(iop_a_src == io2_a_src)) { + status = wuffs_base__make_status(wuffs_base__suspension__short_read); + goto suspend; + } + uint64_t* scratch = &self->private_data.s_decode_fctl[0].scratch; + uint32_t num_bits_3 = ((uint32_t)(*scratch & 0xFF)); + *scratch >>= 8; + *scratch <<= 8; + *scratch |= ((uint64_t)(*iop_a_src++)) << (56 - num_bits_3); + if (num_bits_3 == 24) { + t_3 = ((uint32_t)(*scratch >> 32)); + break; + } + num_bits_3 += 8; + *scratch |= ((uint64_t)(num_bits_3)); + } + } + v_x0 = t_3; + } + { + WUFFS_BASE__COROUTINE_SUSPENSION_POINT(9); + uint32_t t_4; + if (WUFFS_BASE__LIKELY(io2_a_src - iop_a_src >= 4)) { + t_4 = wuffs_base__peek_u32be__no_bounds_check(iop_a_src); + iop_a_src += 4; + } else { + self->private_data.s_decode_fctl[0].scratch = 0; + WUFFS_BASE__COROUTINE_SUSPENSION_POINT(10); + while (true) { + if (WUFFS_BASE__UNLIKELY(iop_a_src == io2_a_src)) { + status = wuffs_base__make_status(wuffs_base__suspension__short_read); + goto suspend; + } + uint64_t* scratch = &self->private_data.s_decode_fctl[0].scratch; + uint32_t num_bits_4 = ((uint32_t)(*scratch & 0xFF)); + *scratch >>= 8; + *scratch <<= 8; + *scratch |= ((uint64_t)(*iop_a_src++)) << (56 - num_bits_4); + if (num_bits_4 == 24) { + t_4 = ((uint32_t)(*scratch >> 32)); + break; + } + num_bits_4 += 8; + *scratch |= ((uint64_t)(num_bits_4)); + } + } + v_y0 = t_4; + } + v_x1 += v_x0; + v_y1 += v_y0; + if ((v_x0 >= v_x1) || + (v_x0 > self->private_impl.f_width) || + (v_x1 > self->private_impl.f_width) || + (v_y0 >= v_y1) || + (v_y0 > self->private_impl.f_height) || + (v_y1 > self->private_impl.f_height)) { + status = wuffs_base__make_status(wuffs_png__error__bad_chunk); + goto exit; + } + self->private_impl.f_frame_rect_x0 = v_x0; + self->private_impl.f_frame_rect_y0 = v_y0; + self->private_impl.f_frame_rect_x1 = v_x1; + self->private_impl.f_frame_rect_y1 = v_y1; + { + WUFFS_BASE__COROUTINE_SUSPENSION_POINT(11); + uint32_t t_5; + if (WUFFS_BASE__LIKELY(io2_a_src - iop_a_src >= 2)) { + t_5 = ((uint32_t)(wuffs_base__peek_u16be__no_bounds_check(iop_a_src))); + iop_a_src += 2; + } else { + self->private_data.s_decode_fctl[0].scratch = 0; + WUFFS_BASE__COROUTINE_SUSPENSION_POINT(12); + while (true) { + if (WUFFS_BASE__UNLIKELY(iop_a_src == io2_a_src)) { + status = wuffs_base__make_status(wuffs_base__suspension__short_read); + goto suspend; + } + uint64_t* scratch = &self->private_data.s_decode_fctl[0].scratch; + uint32_t num_bits_5 = ((uint32_t)(*scratch & 0xFF)); + *scratch >>= 8; + *scratch <<= 8; + *scratch |= ((uint64_t)(*iop_a_src++)) << (56 - num_bits_5); + if (num_bits_5 == 8) { + t_5 = ((uint32_t)(*scratch >> 48)); + break; + } + num_bits_5 += 8; + *scratch |= ((uint64_t)(num_bits_5)); + } + } + v_x0 = t_5; + } + { + WUFFS_BASE__COROUTINE_SUSPENSION_POINT(13); + uint32_t t_6; + if (WUFFS_BASE__LIKELY(io2_a_src - iop_a_src >= 2)) { + t_6 = ((uint32_t)(wuffs_base__peek_u16be__no_bounds_check(iop_a_src))); + iop_a_src += 2; + } else { + self->private_data.s_decode_fctl[0].scratch = 0; + WUFFS_BASE__COROUTINE_SUSPENSION_POINT(14); + while (true) { + if (WUFFS_BASE__UNLIKELY(iop_a_src == io2_a_src)) { + status = wuffs_base__make_status(wuffs_base__suspension__short_read); + goto suspend; + } + uint64_t* scratch = &self->private_data.s_decode_fctl[0].scratch; + uint32_t num_bits_6 = ((uint32_t)(*scratch & 0xFF)); + *scratch >>= 8; + *scratch <<= 8; + *scratch |= ((uint64_t)(*iop_a_src++)) << (56 - num_bits_6); + if (num_bits_6 == 8) { + t_6 = ((uint32_t)(*scratch >> 48)); + break; + } + num_bits_6 += 8; + *scratch |= ((uint64_t)(num_bits_6)); + } + } + v_x1 = t_6; + } + if (v_x1 <= 0) { + self->private_impl.f_frame_duration = (((uint64_t)(v_x0)) * 7056000); + } else { + self->private_impl.f_frame_duration = ((((uint64_t)(v_x0)) * 705600000) / ((uint64_t)(v_x1))); + } + { + WUFFS_BASE__COROUTINE_SUSPENSION_POINT(15); + if (WUFFS_BASE__UNLIKELY(iop_a_src == io2_a_src)) { + status = wuffs_base__make_status(wuffs_base__suspension__short_read); + goto suspend; + } + uint32_t t_7 = *iop_a_src++; + v_x0 = t_7; + } + if (v_x0 == 0) { + self->private_impl.f_frame_disposal = 0; + } else if (v_x0 == 1) { + self->private_impl.f_frame_disposal = 1; + } else if (v_x0 == 2) { + self->private_impl.f_frame_disposal = 2; + } else { + status = wuffs_base__make_status(wuffs_png__error__bad_chunk); + goto exit; + } + { + WUFFS_BASE__COROUTINE_SUSPENSION_POINT(16); + if (WUFFS_BASE__UNLIKELY(iop_a_src == io2_a_src)) { + status = wuffs_base__make_status(wuffs_base__suspension__short_read); + goto suspend; + } + uint32_t t_8 = *iop_a_src++; + v_x0 = t_8; + } + if (v_x0 == 0) { + self->private_impl.f_frame_overwrite_instead_of_blend = true; + } else if (v_x0 == 1) { + self->private_impl.f_frame_overwrite_instead_of_blend = false; + } else { + status = wuffs_base__make_status(wuffs_png__error__bad_chunk); + goto exit; + } + if (self->private_impl.f_num_decoded_frame_configs_value == 0) { + self->private_impl.f_first_rect_x0 = self->private_impl.f_frame_rect_x0; + self->private_impl.f_first_rect_y0 = self->private_impl.f_frame_rect_y0; + self->private_impl.f_first_rect_x1 = self->private_impl.f_frame_rect_x1; + self->private_impl.f_first_rect_y1 = self->private_impl.f_frame_rect_y1; + self->private_impl.f_first_duration = self->private_impl.f_frame_duration; + self->private_impl.f_first_disposal = self->private_impl.f_frame_disposal; + self->private_impl.f_first_overwrite_instead_of_blend = self->private_impl.f_frame_overwrite_instead_of_blend; + } + + goto ok; + ok: + self->private_impl.p_decode_fctl[0] = 0; + goto exit; + } + + goto suspend; + suspend: + self->private_impl.p_decode_fctl[0] = wuffs_base__status__is_suspension(&status) ? coro_susp_point : 0; + self->private_data.s_decode_fctl[0].v_x0 = v_x0; + self->private_data.s_decode_fctl[0].v_x1 = v_x1; + self->private_data.s_decode_fctl[0].v_y1 = v_y1; + + goto exit; + exit: + if (a_src && a_src->data.ptr) { + a_src->meta.ri = ((size_t)(iop_a_src - a_src->data.ptr)); + } + + return status; +} + +// -------- func png.decoder.decode_gama + +static wuffs_base__status +wuffs_png__decoder__decode_gama( + wuffs_png__decoder* self, + wuffs_base__io_buffer* a_src) { + wuffs_base__status status = wuffs_base__make_status(NULL); + + const uint8_t* iop_a_src = NULL; + const uint8_t* io0_a_src WUFFS_BASE__POTENTIALLY_UNUSED = NULL; + const uint8_t* io1_a_src WUFFS_BASE__POTENTIALLY_UNUSED = NULL; + const uint8_t* io2_a_src WUFFS_BASE__POTENTIALLY_UNUSED = NULL; + if (a_src && a_src->data.ptr) { + io0_a_src = a_src->data.ptr; + io1_a_src = io0_a_src + a_src->meta.ri; + iop_a_src = io1_a_src; + io2_a_src = io0_a_src + a_src->meta.wi; + } + + uint32_t coro_susp_point = self->private_impl.p_decode_gama[0]; + switch (coro_susp_point) { + WUFFS_BASE__COROUTINE_SUSPENSION_POINT_0; + + if (self->private_impl.f_chunk_length != 4) { + status = wuffs_base__make_status(wuffs_png__error__bad_chunk); + goto exit; + } + self->private_impl.f_chunk_length = 0; + self->private_impl.f_metadata_flavor = 5; + self->private_impl.f_metadata_fourcc = 1195461953; + { + WUFFS_BASE__COROUTINE_SUSPENSION_POINT(1); + uint64_t t_0; + if (WUFFS_BASE__LIKELY(io2_a_src - iop_a_src >= 4)) { + t_0 = ((uint64_t)(wuffs_base__peek_u32be__no_bounds_check(iop_a_src))); + iop_a_src += 4; + } else { + self->private_data.s_decode_gama[0].scratch = 0; + WUFFS_BASE__COROUTINE_SUSPENSION_POINT(2); + while (true) { + if (WUFFS_BASE__UNLIKELY(iop_a_src == io2_a_src)) { + status = wuffs_base__make_status(wuffs_base__suspension__short_read); + goto suspend; + } + uint64_t* scratch = &self->private_data.s_decode_gama[0].scratch; + uint32_t num_bits_0 = ((uint32_t)(*scratch & 0xFF)); + *scratch >>= 8; + *scratch <<= 8; + *scratch |= ((uint64_t)(*iop_a_src++)) << (56 - num_bits_0); + if (num_bits_0 == 24) { + t_0 = ((uint64_t)(*scratch >> 32)); + break; + } + num_bits_0 += 8; + *scratch |= ((uint64_t)(num_bits_0)); + } + } + self->private_impl.f_metadata_x = t_0; + } + self->private_impl.f_metadata_y = 0; + self->private_impl.f_metadata_z = 0; + + goto ok; + ok: + self->private_impl.p_decode_gama[0] = 0; + goto exit; + } + + goto suspend; + suspend: + self->private_impl.p_decode_gama[0] = wuffs_base__status__is_suspension(&status) ? coro_susp_point : 0; + + goto exit; + exit: + if (a_src && a_src->data.ptr) { + a_src->meta.ri = ((size_t)(iop_a_src - a_src->data.ptr)); + } + + return status; +} + +// -------- func png.decoder.decode_iccp + +static wuffs_base__status +wuffs_png__decoder__decode_iccp( + wuffs_png__decoder* self, + wuffs_base__io_buffer* a_src) { + wuffs_base__status status = wuffs_base__make_status(NULL); + + uint8_t v_c = 0; + + const uint8_t* iop_a_src = NULL; + const uint8_t* io0_a_src WUFFS_BASE__POTENTIALLY_UNUSED = NULL; + const uint8_t* io1_a_src WUFFS_BASE__POTENTIALLY_UNUSED = NULL; + const uint8_t* io2_a_src WUFFS_BASE__POTENTIALLY_UNUSED = NULL; + if (a_src && a_src->data.ptr) { + io0_a_src = a_src->data.ptr; + io1_a_src = io0_a_src + a_src->meta.ri; + iop_a_src = io1_a_src; + io2_a_src = io0_a_src + a_src->meta.wi; + } + + uint32_t coro_susp_point = self->private_impl.p_decode_iccp[0]; + switch (coro_susp_point) { + WUFFS_BASE__COROUTINE_SUSPENSION_POINT_0; + + while (true) { + if (self->private_impl.f_chunk_length <= 0) { + status = wuffs_base__make_status(wuffs_png__error__bad_chunk); + goto exit; + } + self->private_impl.f_chunk_length -= 1; + { + WUFFS_BASE__COROUTINE_SUSPENSION_POINT(1); + if (WUFFS_BASE__UNLIKELY(iop_a_src == io2_a_src)) { + status = wuffs_base__make_status(wuffs_base__suspension__short_read); + goto suspend; + } + uint8_t t_0 = *iop_a_src++; + v_c = t_0; + } + if (v_c == 0) { + goto label__0__break; + } + } + label__0__break:; + if (self->private_impl.f_chunk_length <= 0) { + status = wuffs_base__make_status(wuffs_png__error__bad_chunk); + goto exit; + } + self->private_impl.f_chunk_length -= 1; + { + WUFFS_BASE__COROUTINE_SUSPENSION_POINT(2); + if (WUFFS_BASE__UNLIKELY(iop_a_src == io2_a_src)) { + status = wuffs_base__make_status(wuffs_base__suspension__short_read); + goto suspend; + } + uint8_t t_1 = *iop_a_src++; + v_c = t_1; + } + if (v_c != 0) { + status = wuffs_base__make_status(wuffs_png__error__unsupported_png_compression_method); + goto exit; + } + self->private_impl.f_metadata_is_zlib_compressed = true; + self->private_impl.f_metadata_flavor = 4; + self->private_impl.f_metadata_fourcc = 1229144912; + self->private_impl.f_metadata_x = 0; + self->private_impl.f_metadata_y = 0; + self->private_impl.f_metadata_z = 0; + + goto ok; + ok: + self->private_impl.p_decode_iccp[0] = 0; + goto exit; + } + + goto suspend; + suspend: + self->private_impl.p_decode_iccp[0] = wuffs_base__status__is_suspension(&status) ? coro_susp_point : 0; + + goto exit; + exit: + if (a_src && a_src->data.ptr) { + a_src->meta.ri = ((size_t)(iop_a_src - a_src->data.ptr)); + } + + return status; +} + +// -------- func png.decoder.decode_plte + +static wuffs_base__status +wuffs_png__decoder__decode_plte( + wuffs_png__decoder* self, + wuffs_base__io_buffer* a_src) { + wuffs_base__status status = wuffs_base__make_status(NULL); + + uint32_t v_num_entries = 0; + uint32_t v_i = 0; + uint32_t v_argb = 0; + + const uint8_t* iop_a_src = NULL; + const uint8_t* io0_a_src WUFFS_BASE__POTENTIALLY_UNUSED = NULL; + const uint8_t* io1_a_src WUFFS_BASE__POTENTIALLY_UNUSED = NULL; + const uint8_t* io2_a_src WUFFS_BASE__POTENTIALLY_UNUSED = NULL; + if (a_src && a_src->data.ptr) { + io0_a_src = a_src->data.ptr; + io1_a_src = io0_a_src + a_src->meta.ri; + iop_a_src = io1_a_src; + io2_a_src = io0_a_src + a_src->meta.wi; + } + + uint32_t coro_susp_point = self->private_impl.p_decode_plte[0]; + if (coro_susp_point) { + v_num_entries = self->private_data.s_decode_plte[0].v_num_entries; + v_i = self->private_data.s_decode_plte[0].v_i; + } + switch (coro_susp_point) { + WUFFS_BASE__COROUTINE_SUSPENSION_POINT_0; + + if ((self->private_impl.f_chunk_length > 768) || ((self->private_impl.f_chunk_length % 3) != 0)) { + status = wuffs_base__make_status(wuffs_png__error__bad_chunk); + goto exit; + } + v_num_entries = (((uint32_t)(self->private_impl.f_chunk_length)) / 3); + self->private_impl.f_chunk_length = 0; + while (v_i < v_num_entries) { + { + WUFFS_BASE__COROUTINE_SUSPENSION_POINT(1); + uint32_t t_0; + if (WUFFS_BASE__LIKELY(io2_a_src - iop_a_src >= 3)) { + t_0 = ((uint32_t)(wuffs_base__peek_u24be__no_bounds_check(iop_a_src))); + iop_a_src += 3; + } else { + self->private_data.s_decode_plte[0].scratch = 0; + WUFFS_BASE__COROUTINE_SUSPENSION_POINT(2); + while (true) { + if (WUFFS_BASE__UNLIKELY(iop_a_src == io2_a_src)) { + status = wuffs_base__make_status(wuffs_base__suspension__short_read); + goto suspend; + } + uint64_t* scratch = &self->private_data.s_decode_plte[0].scratch; + uint32_t num_bits_0 = ((uint32_t)(*scratch & 0xFF)); + *scratch >>= 8; + *scratch <<= 8; + *scratch |= ((uint64_t)(*iop_a_src++)) << (56 - num_bits_0); + if (num_bits_0 == 16) { + t_0 = ((uint32_t)(*scratch >> 40)); + break; + } + num_bits_0 += 8; + *scratch |= ((uint64_t)(num_bits_0)); + } + } + v_argb = t_0; + } + v_argb |= 4278190080; + self->private_data.f_src_palette[((4 * v_i) + 0)] = ((uint8_t)(((v_argb >> 0) & 255))); + self->private_data.f_src_palette[((4 * v_i) + 1)] = ((uint8_t)(((v_argb >> 8) & 255))); + self->private_data.f_src_palette[((4 * v_i) + 2)] = ((uint8_t)(((v_argb >> 16) & 255))); + self->private_data.f_src_palette[((4 * v_i) + 3)] = ((uint8_t)(((v_argb >> 24) & 255))); + v_i += 1; + } + while (v_i < 256) { + self->private_data.f_src_palette[((4 * v_i) + 0)] = 0; + self->private_data.f_src_palette[((4 * v_i) + 1)] = 0; + self->private_data.f_src_palette[((4 * v_i) + 2)] = 0; + self->private_data.f_src_palette[((4 * v_i) + 3)] = 255; + v_i += 1; + } + + goto ok; + ok: + self->private_impl.p_decode_plte[0] = 0; + goto exit; + } + + goto suspend; + suspend: + self->private_impl.p_decode_plte[0] = wuffs_base__status__is_suspension(&status) ? coro_susp_point : 0; + self->private_data.s_decode_plte[0].v_num_entries = v_num_entries; + self->private_data.s_decode_plte[0].v_i = v_i; + + goto exit; + exit: + if (a_src && a_src->data.ptr) { + a_src->meta.ri = ((size_t)(iop_a_src - a_src->data.ptr)); + } + + return status; +} + +// -------- func png.decoder.decode_srgb + +static wuffs_base__status +wuffs_png__decoder__decode_srgb( + wuffs_png__decoder* self, + wuffs_base__io_buffer* a_src) { + wuffs_base__status status = wuffs_base__make_status(NULL); + + const uint8_t* iop_a_src = NULL; + const uint8_t* io0_a_src WUFFS_BASE__POTENTIALLY_UNUSED = NULL; + const uint8_t* io1_a_src WUFFS_BASE__POTENTIALLY_UNUSED = NULL; + const uint8_t* io2_a_src WUFFS_BASE__POTENTIALLY_UNUSED = NULL; + if (a_src && a_src->data.ptr) { + io0_a_src = a_src->data.ptr; + io1_a_src = io0_a_src + a_src->meta.ri; + iop_a_src = io1_a_src; + io2_a_src = io0_a_src + a_src->meta.wi; + } + + uint32_t coro_susp_point = self->private_impl.p_decode_srgb[0]; + switch (coro_susp_point) { + WUFFS_BASE__COROUTINE_SUSPENSION_POINT_0; + + if (self->private_impl.f_chunk_length != 1) { + status = wuffs_base__make_status(wuffs_png__error__bad_chunk); + goto exit; + } + self->private_impl.f_chunk_length = 0; + self->private_impl.f_metadata_flavor = 5; + self->private_impl.f_metadata_fourcc = 1397901122; + { + WUFFS_BASE__COROUTINE_SUSPENSION_POINT(1); + if (WUFFS_BASE__UNLIKELY(iop_a_src == io2_a_src)) { + status = wuffs_base__make_status(wuffs_base__suspension__short_read); + goto suspend; + } + uint64_t t_0 = *iop_a_src++; + self->private_impl.f_metadata_x = t_0; + } + self->private_impl.f_metadata_y = 0; + self->private_impl.f_metadata_z = 0; + + goto ok; + ok: + self->private_impl.p_decode_srgb[0] = 0; + goto exit; + } + + goto suspend; + suspend: + self->private_impl.p_decode_srgb[0] = wuffs_base__status__is_suspension(&status) ? coro_susp_point : 0; + + goto exit; + exit: + if (a_src && a_src->data.ptr) { + a_src->meta.ri = ((size_t)(iop_a_src - a_src->data.ptr)); + } + + return status; +} + +// -------- func png.decoder.decode_trns + +static wuffs_base__status +wuffs_png__decoder__decode_trns( + wuffs_png__decoder* self, + wuffs_base__io_buffer* a_src) { + wuffs_base__status status = wuffs_base__make_status(NULL); + + uint32_t v_i = 0; + uint32_t v_n = 0; + uint64_t v_u = 0; + + const uint8_t* iop_a_src = NULL; + const uint8_t* io0_a_src WUFFS_BASE__POTENTIALLY_UNUSED = NULL; + const uint8_t* io1_a_src WUFFS_BASE__POTENTIALLY_UNUSED = NULL; + const uint8_t* io2_a_src WUFFS_BASE__POTENTIALLY_UNUSED = NULL; + if (a_src && a_src->data.ptr) { + io0_a_src = a_src->data.ptr; + io1_a_src = io0_a_src + a_src->meta.ri; + iop_a_src = io1_a_src; + io2_a_src = io0_a_src + a_src->meta.wi; + } + + uint32_t coro_susp_point = self->private_impl.p_decode_trns[0]; + if (coro_susp_point) { + v_i = self->private_data.s_decode_trns[0].v_i; + v_n = self->private_data.s_decode_trns[0].v_n; + } + switch (coro_susp_point) { + WUFFS_BASE__COROUTINE_SUSPENSION_POINT_0; + + if (self->private_impl.f_color_type == 0) { + self->private_impl.choosy_filter_and_swizzle = ( + &wuffs_png__decoder__filter_and_swizzle_tricky); + if (self->private_impl.f_depth <= 8) { + self->private_impl.f_dst_pixfmt = 2164295816; + self->private_impl.f_src_pixfmt = 2164295816; + } else { + self->private_impl.f_dst_pixfmt = 2164308923; + self->private_impl.f_src_pixfmt = 2164308923; + } + if (self->private_impl.f_chunk_length != 2) { + status = wuffs_base__make_status(wuffs_png__error__bad_chunk); + goto exit; + } + self->private_impl.f_chunk_length = 0; + { + WUFFS_BASE__COROUTINE_SUSPENSION_POINT(1); + uint64_t t_0; + if (WUFFS_BASE__LIKELY(io2_a_src - iop_a_src >= 2)) { + t_0 = ((uint64_t)(wuffs_base__peek_u16be__no_bounds_check(iop_a_src))); + iop_a_src += 2; + } else { + self->private_data.s_decode_trns[0].scratch = 0; + WUFFS_BASE__COROUTINE_SUSPENSION_POINT(2); + while (true) { + if (WUFFS_BASE__UNLIKELY(iop_a_src == io2_a_src)) { + status = wuffs_base__make_status(wuffs_base__suspension__short_read); + goto suspend; + } + uint64_t* scratch = &self->private_data.s_decode_trns[0].scratch; + uint32_t num_bits_0 = ((uint32_t)(*scratch & 0xFF)); + *scratch >>= 8; + *scratch <<= 8; + *scratch |= ((uint64_t)(*iop_a_src++)) << (56 - num_bits_0); + if (num_bits_0 == 8) { + t_0 = ((uint64_t)(*scratch >> 48)); + break; + } + num_bits_0 += 8; + *scratch |= ((uint64_t)(num_bits_0)); + } + } + v_u = t_0; + } + if (self->private_impl.f_depth <= 1) { + self->private_impl.f_remap_transparency = (((v_u & 1) * 16777215) | 4278190080); + } else if (self->private_impl.f_depth <= 2) { + self->private_impl.f_remap_transparency = (((v_u & 3) * 5592405) | 4278190080); + } else if (self->private_impl.f_depth <= 4) { + self->private_impl.f_remap_transparency = (((v_u & 15) * 1118481) | 4278190080); + } else if (self->private_impl.f_depth <= 8) { + self->private_impl.f_remap_transparency = (((v_u & 255) * 65793) | 4278190080); + } else { + self->private_impl.f_remap_transparency = ((v_u * 4295032833) | 18446462598732840960u); + } + } else if (self->private_impl.f_color_type == 2) { + self->private_impl.choosy_filter_and_swizzle = ( + &wuffs_png__decoder__filter_and_swizzle_tricky); + if (self->private_impl.f_depth <= 8) { + self->private_impl.f_dst_pixfmt = 2164295816; + self->private_impl.f_src_pixfmt = 2164295816; + } else { + self->private_impl.f_dst_pixfmt = 2164308923; + self->private_impl.f_src_pixfmt = 2164308923; + } + if (self->private_impl.f_chunk_length != 6) { + status = wuffs_base__make_status(wuffs_png__error__bad_chunk); + goto exit; + } + self->private_impl.f_chunk_length = 0; + { + WUFFS_BASE__COROUTINE_SUSPENSION_POINT(3); + uint64_t t_1; + if (WUFFS_BASE__LIKELY(io2_a_src - iop_a_src >= 6)) { + t_1 = ((uint64_t)(wuffs_base__peek_u48be__no_bounds_check(iop_a_src))); + iop_a_src += 6; + } else { + self->private_data.s_decode_trns[0].scratch = 0; + WUFFS_BASE__COROUTINE_SUSPENSION_POINT(4); + while (true) { + if (WUFFS_BASE__UNLIKELY(iop_a_src == io2_a_src)) { + status = wuffs_base__make_status(wuffs_base__suspension__short_read); + goto suspend; + } + uint64_t* scratch = &self->private_data.s_decode_trns[0].scratch; + uint32_t num_bits_1 = ((uint32_t)(*scratch & 0xFF)); + *scratch >>= 8; + *scratch <<= 8; + *scratch |= ((uint64_t)(*iop_a_src++)) << (56 - num_bits_1); + if (num_bits_1 == 40) { + t_1 = ((uint64_t)(*scratch >> 16)); + break; + } + num_bits_1 += 8; + *scratch |= ((uint64_t)(num_bits_1)); + } + } + v_u = t_1; + } + if (self->private_impl.f_depth <= 8) { + self->private_impl.f_remap_transparency = ((255 & (v_u >> 0)) | + (65280 & (v_u >> 8)) | + (16711680 & (v_u >> 16)) | + 4278190080); + } else { + self->private_impl.f_remap_transparency = (v_u | 18446462598732840960u); + } + } else if (self->private_impl.f_color_type == 3) { + self->private_impl.f_dst_pixfmt = 2164523016; + self->private_impl.f_src_pixfmt = 2164523016; + if (self->private_impl.f_chunk_length > 256) { + status = wuffs_base__make_status(wuffs_png__error__bad_chunk); + goto exit; + } + v_n = ((uint32_t)(self->private_impl.f_chunk_length)); + self->private_impl.f_chunk_length = 0; + while (v_i < v_n) { + { + WUFFS_BASE__COROUTINE_SUSPENSION_POINT(5); + if (WUFFS_BASE__UNLIKELY(iop_a_src == io2_a_src)) { + status = wuffs_base__make_status(wuffs_base__suspension__short_read); + goto suspend; + } + uint8_t t_2 = *iop_a_src++; + self->private_data.f_src_palette[((4 * v_i) + 3)] = t_2; + } + v_i += 1; + } + } else { + status = wuffs_base__make_status(wuffs_png__error__bad_chunk); + goto exit; + } + + goto ok; + ok: + self->private_impl.p_decode_trns[0] = 0; + goto exit; + } + + goto suspend; + suspend: + self->private_impl.p_decode_trns[0] = wuffs_base__status__is_suspension(&status) ? coro_susp_point : 0; + self->private_data.s_decode_trns[0].v_i = v_i; + self->private_data.s_decode_trns[0].v_n = v_n; + + goto exit; + exit: + if (a_src && a_src->data.ptr) { + a_src->meta.ri = ((size_t)(iop_a_src - a_src->data.ptr)); + } + + return status; +} + +// -------- func png.decoder.decode_frame_config + +WUFFS_BASE__MAYBE_STATIC wuffs_base__status +wuffs_png__decoder__decode_frame_config( + wuffs_png__decoder* self, + wuffs_base__frame_config* a_dst, + wuffs_base__io_buffer* a_src) { + if (!self) { + return wuffs_base__make_status(wuffs_base__error__bad_receiver); + } + if (self->private_impl.magic != WUFFS_BASE__MAGIC) { + return wuffs_base__make_status( + (self->private_impl.magic == WUFFS_BASE__DISABLED) + ? wuffs_base__error__disabled_by_previous_error + : wuffs_base__error__initialize_not_called); + } + if (!a_src) { + self->private_impl.magic = WUFFS_BASE__DISABLED; + return wuffs_base__make_status(wuffs_base__error__bad_argument); + } + if ((self->private_impl.active_coroutine != 0) && + (self->private_impl.active_coroutine != 2)) { + self->private_impl.magic = WUFFS_BASE__DISABLED; + return wuffs_base__make_status(wuffs_base__error__interleaved_coroutine_calls); + } + self->private_impl.active_coroutine = 0; + wuffs_base__status status = wuffs_base__make_status(NULL); + + wuffs_base__status v_status = wuffs_base__make_status(NULL); + + uint32_t coro_susp_point = self->private_impl.p_decode_frame_config[0]; + switch (coro_susp_point) { + WUFFS_BASE__COROUTINE_SUSPENSION_POINT_0; + + while (true) { + { + wuffs_base__status t_0 = wuffs_png__decoder__do_decode_frame_config(self, a_dst, a_src); + v_status = t_0; + } + if ((v_status.repr == wuffs_base__suspension__short_read) && (a_src && a_src->meta.closed)) { + status = wuffs_base__make_status(wuffs_png__error__truncated_input); + goto exit; + } + status = v_status; + WUFFS_BASE__COROUTINE_SUSPENSION_POINT_MAYBE_SUSPEND(1); + } + + ok: + self->private_impl.p_decode_frame_config[0] = 0; + goto exit; + } + + goto suspend; + suspend: + self->private_impl.p_decode_frame_config[0] = wuffs_base__status__is_suspension(&status) ? coro_susp_point : 0; + self->private_impl.active_coroutine = wuffs_base__status__is_suspension(&status) ? 2 : 0; + + goto exit; + exit: + if (wuffs_base__status__is_error(&status)) { + self->private_impl.magic = WUFFS_BASE__DISABLED; + } + return status; +} + +// -------- func png.decoder.do_decode_frame_config + +static wuffs_base__status +wuffs_png__decoder__do_decode_frame_config( + wuffs_png__decoder* self, + wuffs_base__frame_config* a_dst, + wuffs_base__io_buffer* a_src) { + wuffs_base__status status = wuffs_base__make_status(NULL); + + uint32_t v_checksum_have = 0; + + const uint8_t* iop_a_src = NULL; + const uint8_t* io0_a_src WUFFS_BASE__POTENTIALLY_UNUSED = NULL; + const uint8_t* io1_a_src WUFFS_BASE__POTENTIALLY_UNUSED = NULL; + const uint8_t* io2_a_src WUFFS_BASE__POTENTIALLY_UNUSED = NULL; + if (a_src && a_src->data.ptr) { + io0_a_src = a_src->data.ptr; + io1_a_src = io0_a_src + a_src->meta.ri; + iop_a_src = io1_a_src; + io2_a_src = io0_a_src + a_src->meta.wi; + } + + uint32_t coro_susp_point = self->private_impl.p_do_decode_frame_config[0]; + switch (coro_susp_point) { + WUFFS_BASE__COROUTINE_SUSPENSION_POINT_0; + + if ((self->private_impl.f_call_sequence & 16) != 0) { + status = wuffs_base__make_status(wuffs_base__error__bad_call_sequence); + goto exit; + } else if (self->private_impl.f_call_sequence == 32) { + } else if (self->private_impl.f_call_sequence < 32) { + if (a_src) { + a_src->meta.ri = ((size_t)(iop_a_src - a_src->data.ptr)); + } + WUFFS_BASE__COROUTINE_SUSPENSION_POINT(1); + status = wuffs_png__decoder__do_decode_image_config(self, NULL, a_src); + if (a_src) { + iop_a_src = a_src->data.ptr + a_src->meta.ri; + } + if (status.repr) { + goto suspend; + } + } else if (self->private_impl.f_call_sequence == 40) { + if (self->private_impl.f_frame_config_io_position != wuffs_base__u64__sat_add((a_src ? a_src->meta.pos : 0), ((uint64_t)(iop_a_src - io0_a_src)))) { + status = wuffs_base__make_status(wuffs_base__error__bad_restart); + goto exit; + } + } else if (self->private_impl.f_call_sequence == 64) { + if (a_src) { + a_src->meta.ri = ((size_t)(iop_a_src - a_src->data.ptr)); + } + WUFFS_BASE__COROUTINE_SUSPENSION_POINT(2); + status = wuffs_png__decoder__skip_frame(self, a_src); + if (a_src) { + iop_a_src = a_src->data.ptr + a_src->meta.ri; + } + if (status.repr) { + goto suspend; + } + } else { + status = wuffs_base__make_status(wuffs_base__note__end_of_data); + goto ok; + } + if (self->private_impl.f_metadata_fourcc != 0) { + self->private_impl.f_call_sequence = 48; + status = wuffs_base__make_status(wuffs_base__note__metadata_reported); + goto ok; + } + if (self->private_impl.f_num_decoded_frame_configs_value == 0) { + self->private_impl.f_frame_rect_x0 = self->private_impl.f_first_rect_x0; + self->private_impl.f_frame_rect_y0 = self->private_impl.f_first_rect_y0; + self->private_impl.f_frame_rect_x1 = self->private_impl.f_first_rect_x1; + self->private_impl.f_frame_rect_y1 = self->private_impl.f_first_rect_y1; + self->private_impl.f_frame_config_io_position = self->private_impl.f_first_config_io_position; + self->private_impl.f_frame_duration = self->private_impl.f_first_duration; + self->private_impl.f_frame_disposal = self->private_impl.f_first_disposal; + self->private_impl.f_frame_overwrite_instead_of_blend = self->private_impl.f_first_overwrite_instead_of_blend; + } else { + while (true) { + { + WUFFS_BASE__COROUTINE_SUSPENSION_POINT(3); + uint32_t t_0; + if (WUFFS_BASE__LIKELY(io2_a_src - iop_a_src >= 4)) { + t_0 = wuffs_base__peek_u32be__no_bounds_check(iop_a_src); + iop_a_src += 4; + } else { + self->private_data.s_do_decode_frame_config[0].scratch = 0; + WUFFS_BASE__COROUTINE_SUSPENSION_POINT(4); + while (true) { + if (WUFFS_BASE__UNLIKELY(iop_a_src == io2_a_src)) { + status = wuffs_base__make_status(wuffs_base__suspension__short_read); + goto suspend; + } + uint64_t* scratch = &self->private_data.s_do_decode_frame_config[0].scratch; + uint32_t num_bits_0 = ((uint32_t)(*scratch & 0xFF)); + *scratch >>= 8; + *scratch <<= 8; + *scratch |= ((uint64_t)(*iop_a_src++)) << (56 - num_bits_0); + if (num_bits_0 == 24) { + t_0 = ((uint32_t)(*scratch >> 32)); + break; + } + num_bits_0 += 8; + *scratch |= ((uint64_t)(num_bits_0)); + } + } + self->private_impl.f_chunk_length = t_0; + } + { + WUFFS_BASE__COROUTINE_SUSPENSION_POINT(5); + uint32_t t_1; + if (WUFFS_BASE__LIKELY(io2_a_src - iop_a_src >= 4)) { + t_1 = wuffs_base__peek_u32le__no_bounds_check(iop_a_src); + iop_a_src += 4; + } else { + self->private_data.s_do_decode_frame_config[0].scratch = 0; + WUFFS_BASE__COROUTINE_SUSPENSION_POINT(6); + while (true) { + if (WUFFS_BASE__UNLIKELY(iop_a_src == io2_a_src)) { + status = wuffs_base__make_status(wuffs_base__suspension__short_read); + goto suspend; + } + uint64_t* scratch = &self->private_data.s_do_decode_frame_config[0].scratch; + uint32_t num_bits_1 = ((uint32_t)(*scratch >> 56)); + *scratch <<= 8; + *scratch >>= 8; + *scratch |= ((uint64_t)(*iop_a_src++)) << num_bits_1; + if (num_bits_1 == 24) { + t_1 = ((uint32_t)(*scratch)); + break; + } + num_bits_1 += 8; + *scratch |= ((uint64_t)(num_bits_1)) << 56; + } + } + self->private_impl.f_chunk_type = t_1; + } + if (self->private_impl.f_chunk_type == 1145980233) { + if (self->private_impl.f_chunk_length != 0) { + status = wuffs_base__make_status(wuffs_png__error__bad_chunk); + goto exit; + } + { + WUFFS_BASE__COROUTINE_SUSPENSION_POINT(7); + uint32_t t_2; + if (WUFFS_BASE__LIKELY(io2_a_src - iop_a_src >= 4)) { + t_2 = wuffs_base__peek_u32le__no_bounds_check(iop_a_src); + iop_a_src += 4; + } else { + self->private_data.s_do_decode_frame_config[0].scratch = 0; + WUFFS_BASE__COROUTINE_SUSPENSION_POINT(8); + while (true) { + if (WUFFS_BASE__UNLIKELY(iop_a_src == io2_a_src)) { + status = wuffs_base__make_status(wuffs_base__suspension__short_read); + goto suspend; + } + uint64_t* scratch = &self->private_data.s_do_decode_frame_config[0].scratch; + uint32_t num_bits_2 = ((uint32_t)(*scratch >> 56)); + *scratch <<= 8; + *scratch >>= 8; + *scratch |= ((uint64_t)(*iop_a_src++)) << num_bits_2; + if (num_bits_2 == 24) { + t_2 = ((uint32_t)(*scratch)); + break; + } + num_bits_2 += 8; + *scratch |= ((uint64_t)(num_bits_2)) << 56; + } + } + v_checksum_have = t_2; + } + if ( ! self->private_impl.f_ignore_checksum && (v_checksum_have != 2187346606)) { + status = wuffs_base__make_status(wuffs_png__error__bad_checksum); + goto exit; + } + self->private_impl.f_call_sequence = 96; + status = wuffs_base__make_status(wuffs_base__note__end_of_data); + goto ok; + } else if (self->private_impl.f_chunk_type == 1413571686) { + status = wuffs_base__make_status(wuffs_png__error__bad_chunk); + goto exit; + } else if (self->private_impl.f_chunk_type == 1280598886) { + self->private_impl.f_frame_config_io_position = ((uint64_t)(wuffs_base__u64__sat_add((a_src ? a_src->meta.pos : 0), ((uint64_t)(iop_a_src - io0_a_src))) - 8)); + if (a_src) { + a_src->meta.ri = ((size_t)(iop_a_src - a_src->data.ptr)); + } + WUFFS_BASE__COROUTINE_SUSPENSION_POINT(9); + status = wuffs_png__decoder__decode_fctl(self, a_src); + if (a_src) { + iop_a_src = a_src->data.ptr + a_src->meta.ri; + } + if (status.repr) { + goto suspend; + } + self->private_data.s_do_decode_frame_config[0].scratch = 4; + WUFFS_BASE__COROUTINE_SUSPENSION_POINT(10); + if (self->private_data.s_do_decode_frame_config[0].scratch > ((uint64_t)(io2_a_src - iop_a_src))) { + self->private_data.s_do_decode_frame_config[0].scratch -= ((uint64_t)(io2_a_src - iop_a_src)); + iop_a_src = io2_a_src; + status = wuffs_base__make_status(wuffs_base__suspension__short_read); + goto suspend; + } + iop_a_src += self->private_data.s_do_decode_frame_config[0].scratch; + goto label__0__break; + } + if (a_src) { + a_src->meta.ri = ((size_t)(iop_a_src - a_src->data.ptr)); + } + WUFFS_BASE__COROUTINE_SUSPENSION_POINT(11); + status = wuffs_png__decoder__decode_other_chunk(self, a_src, true); + if (a_src) { + iop_a_src = a_src->data.ptr + a_src->meta.ri; + } + if (status.repr) { + goto suspend; + } + if (self->private_impl.f_metadata_fourcc != 0) { + self->private_impl.f_call_sequence = 48; + status = wuffs_base__make_status(wuffs_base__note__metadata_reported); + goto ok; + } + self->private_data.s_do_decode_frame_config[0].scratch = 4; + WUFFS_BASE__COROUTINE_SUSPENSION_POINT(12); + if (self->private_data.s_do_decode_frame_config[0].scratch > ((uint64_t)(io2_a_src - iop_a_src))) { + self->private_data.s_do_decode_frame_config[0].scratch -= ((uint64_t)(io2_a_src - iop_a_src)); + iop_a_src = io2_a_src; + status = wuffs_base__make_status(wuffs_base__suspension__short_read); + goto suspend; + } + iop_a_src += self->private_data.s_do_decode_frame_config[0].scratch; + self->private_impl.f_chunk_length = 0; + } + label__0__break:; + } + if (a_dst != NULL) { + wuffs_base__frame_config__set( + a_dst, + wuffs_base__utility__make_rect_ie_u32( + self->private_impl.f_frame_rect_x0, + self->private_impl.f_frame_rect_y0, + self->private_impl.f_frame_rect_x1, + self->private_impl.f_frame_rect_y1), + ((wuffs_base__flicks)(self->private_impl.f_frame_duration)), + ((uint64_t)(self->private_impl.f_num_decoded_frame_configs_value)), + self->private_impl.f_frame_config_io_position, + self->private_impl.f_frame_disposal, + ((self->private_impl.f_color_type <= 3) && ! self->private_impl.f_seen_trns), + self->private_impl.f_frame_overwrite_instead_of_blend, + 0); + } + wuffs_base__u32__sat_add_indirect(&self->private_impl.f_num_decoded_frame_configs_value, 1); + self->private_impl.f_call_sequence = 64; + + ok: + self->private_impl.p_do_decode_frame_config[0] = 0; + goto exit; + } + + goto suspend; + suspend: + self->private_impl.p_do_decode_frame_config[0] = wuffs_base__status__is_suspension(&status) ? coro_susp_point : 0; + + goto exit; + exit: + if (a_src && a_src->data.ptr) { + a_src->meta.ri = ((size_t)(iop_a_src - a_src->data.ptr)); + } + + return status; +} + +// -------- func png.decoder.skip_frame + +static wuffs_base__status +wuffs_png__decoder__skip_frame( + wuffs_png__decoder* self, + wuffs_base__io_buffer* a_src) { + wuffs_base__status status = wuffs_base__make_status(NULL); + + uint32_t v_seq_num = 0; + + const uint8_t* iop_a_src = NULL; + const uint8_t* io0_a_src WUFFS_BASE__POTENTIALLY_UNUSED = NULL; + const uint8_t* io1_a_src WUFFS_BASE__POTENTIALLY_UNUSED = NULL; + const uint8_t* io2_a_src WUFFS_BASE__POTENTIALLY_UNUSED = NULL; + if (a_src && a_src->data.ptr) { + io0_a_src = a_src->data.ptr; + io1_a_src = io0_a_src + a_src->meta.ri; + iop_a_src = io1_a_src; + io2_a_src = io0_a_src + a_src->meta.wi; + } + + uint32_t coro_susp_point = self->private_impl.p_skip_frame[0]; + switch (coro_susp_point) { + WUFFS_BASE__COROUTINE_SUSPENSION_POINT_0; + + self->private_impl.f_chunk_type_array[0] = 0; + self->private_impl.f_chunk_type_array[1] = 0; + self->private_impl.f_chunk_type_array[2] = 0; + self->private_impl.f_chunk_type_array[3] = 0; + label__0__continue:; + while (true) { + if (((uint64_t)(io2_a_src - iop_a_src)) < 8) { + status = wuffs_base__make_status(wuffs_base__suspension__short_read); + WUFFS_BASE__COROUTINE_SUSPENSION_POINT_MAYBE_SUSPEND(1); + goto label__0__continue; + } + self->private_impl.f_chunk_length = wuffs_base__peek_u32be__no_bounds_check(iop_a_src); + self->private_impl.f_chunk_type = ((uint32_t)((wuffs_base__peek_u64le__no_bounds_check(iop_a_src) >> 32))); + if (self->private_impl.f_chunk_type == 1413563465) { + if (self->private_impl.f_chunk_type_array[0] == 102) { + status = wuffs_base__make_status(wuffs_png__error__bad_chunk); + goto exit; + } + self->private_impl.f_chunk_type_array[0] = 73; + self->private_impl.f_chunk_type_array[1] = 68; + self->private_impl.f_chunk_type_array[2] = 65; + self->private_impl.f_chunk_type_array[3] = 84; + } else if (self->private_impl.f_chunk_type == 1413571686) { + if (self->private_impl.f_chunk_type_array[0] == 73) { + status = wuffs_base__make_status(wuffs_png__error__bad_chunk); + goto exit; + } + self->private_impl.f_chunk_type_array[0] = 102; + self->private_impl.f_chunk_type_array[1] = 100; + self->private_impl.f_chunk_type_array[2] = 65; + self->private_impl.f_chunk_type_array[3] = 84; + if (self->private_impl.f_chunk_length < 4) { + status = wuffs_base__make_status(wuffs_png__error__bad_chunk); + goto exit; + } + self->private_impl.f_chunk_length -= 4; + iop_a_src += 8; + { + WUFFS_BASE__COROUTINE_SUSPENSION_POINT(2); + uint32_t t_0; + if (WUFFS_BASE__LIKELY(io2_a_src - iop_a_src >= 4)) { + t_0 = wuffs_base__peek_u32be__no_bounds_check(iop_a_src); + iop_a_src += 4; + } else { + self->private_data.s_skip_frame[0].scratch = 0; + WUFFS_BASE__COROUTINE_SUSPENSION_POINT(3); + while (true) { + if (WUFFS_BASE__UNLIKELY(iop_a_src == io2_a_src)) { + status = wuffs_base__make_status(wuffs_base__suspension__short_read); + goto suspend; + } + uint64_t* scratch = &self->private_data.s_skip_frame[0].scratch; + uint32_t num_bits_0 = ((uint32_t)(*scratch & 0xFF)); + *scratch >>= 8; + *scratch <<= 8; + *scratch |= ((uint64_t)(*iop_a_src++)) << (56 - num_bits_0); + if (num_bits_0 == 24) { + t_0 = ((uint32_t)(*scratch >> 32)); + break; + } + num_bits_0 += 8; + *scratch |= ((uint64_t)(num_bits_0)); + } + } + v_seq_num = t_0; + } + if (v_seq_num != self->private_impl.f_next_animation_seq_num) { + status = wuffs_base__make_status(wuffs_png__error__bad_animation_sequence_number); + goto exit; + } else if (self->private_impl.f_next_animation_seq_num >= 4294967295) { + status = wuffs_base__make_status(wuffs_png__error__unsupported_png_file); + goto exit; + } + self->private_impl.f_next_animation_seq_num += 1; + self->private_data.s_skip_frame[0].scratch = (((uint64_t)(self->private_impl.f_chunk_length)) + 4); + WUFFS_BASE__COROUTINE_SUSPENSION_POINT(4); + if (self->private_data.s_skip_frame[0].scratch > ((uint64_t)(io2_a_src - iop_a_src))) { + self->private_data.s_skip_frame[0].scratch -= ((uint64_t)(io2_a_src - iop_a_src)); + iop_a_src = io2_a_src; + status = wuffs_base__make_status(wuffs_base__suspension__short_read); + goto suspend; + } + iop_a_src += self->private_data.s_skip_frame[0].scratch; + self->private_impl.f_chunk_length = 0; + goto label__0__continue; + } else if (self->private_impl.f_chunk_type_array[0] != 0) { + goto label__0__break; + } else if (self->private_impl.f_chunk_type == 1280598886) { + status = wuffs_base__make_status(wuffs_png__error__bad_chunk); + goto exit; + } + self->private_data.s_skip_frame[0].scratch = (((uint64_t)(self->private_impl.f_chunk_length)) + 12); + WUFFS_BASE__COROUTINE_SUSPENSION_POINT(5); + if (self->private_data.s_skip_frame[0].scratch > ((uint64_t)(io2_a_src - iop_a_src))) { + self->private_data.s_skip_frame[0].scratch -= ((uint64_t)(io2_a_src - iop_a_src)); + iop_a_src = io2_a_src; + status = wuffs_base__make_status(wuffs_base__suspension__short_read); + goto suspend; + } + iop_a_src += self->private_data.s_skip_frame[0].scratch; + self->private_impl.f_chunk_length = 0; + } + label__0__break:; + wuffs_base__u32__sat_add_indirect(&self->private_impl.f_num_decoded_frames_value, 1); + self->private_impl.f_call_sequence = 32; + + ok: + self->private_impl.p_skip_frame[0] = 0; + goto exit; + } + + goto suspend; + suspend: + self->private_impl.p_skip_frame[0] = wuffs_base__status__is_suspension(&status) ? coro_susp_point : 0; + + goto exit; + exit: + if (a_src && a_src->data.ptr) { + a_src->meta.ri = ((size_t)(iop_a_src - a_src->data.ptr)); + } + + return status; +} + +// -------- func png.decoder.decode_frame + +WUFFS_BASE__MAYBE_STATIC wuffs_base__status +wuffs_png__decoder__decode_frame( + wuffs_png__decoder* self, + wuffs_base__pixel_buffer* a_dst, + wuffs_base__io_buffer* a_src, + wuffs_base__pixel_blend a_blend, + wuffs_base__slice_u8 a_workbuf, + wuffs_base__decode_frame_options* a_opts) { + if (!self) { + return wuffs_base__make_status(wuffs_base__error__bad_receiver); + } + if (self->private_impl.magic != WUFFS_BASE__MAGIC) { + return wuffs_base__make_status( + (self->private_impl.magic == WUFFS_BASE__DISABLED) + ? wuffs_base__error__disabled_by_previous_error + : wuffs_base__error__initialize_not_called); + } + if (!a_dst || !a_src) { + self->private_impl.magic = WUFFS_BASE__DISABLED; + return wuffs_base__make_status(wuffs_base__error__bad_argument); + } + if ((self->private_impl.active_coroutine != 0) && + (self->private_impl.active_coroutine != 3)) { + self->private_impl.magic = WUFFS_BASE__DISABLED; + return wuffs_base__make_status(wuffs_base__error__interleaved_coroutine_calls); + } + self->private_impl.active_coroutine = 0; + wuffs_base__status status = wuffs_base__make_status(NULL); + + wuffs_base__status v_status = wuffs_base__make_status(NULL); + + uint32_t coro_susp_point = self->private_impl.p_decode_frame[0]; + switch (coro_susp_point) { + WUFFS_BASE__COROUTINE_SUSPENSION_POINT_0; + + while (true) { + { + wuffs_base__status t_0 = wuffs_png__decoder__do_decode_frame(self, + a_dst, + a_src, + a_blend, + a_workbuf, + a_opts); + v_status = t_0; + } + if ((v_status.repr == wuffs_base__suspension__short_read) && (a_src && a_src->meta.closed)) { + status = wuffs_base__make_status(wuffs_png__error__truncated_input); + goto exit; + } + status = v_status; + WUFFS_BASE__COROUTINE_SUSPENSION_POINT_MAYBE_SUSPEND(1); + } + + ok: + self->private_impl.p_decode_frame[0] = 0; + goto exit; + } + + goto suspend; + suspend: + self->private_impl.p_decode_frame[0] = wuffs_base__status__is_suspension(&status) ? coro_susp_point : 0; + self->private_impl.active_coroutine = wuffs_base__status__is_suspension(&status) ? 3 : 0; + + goto exit; + exit: + if (wuffs_base__status__is_error(&status)) { + self->private_impl.magic = WUFFS_BASE__DISABLED; + } + return status; +} + +// -------- func png.decoder.do_decode_frame + +static wuffs_base__status +wuffs_png__decoder__do_decode_frame( + wuffs_png__decoder* self, + wuffs_base__pixel_buffer* a_dst, + wuffs_base__io_buffer* a_src, + wuffs_base__pixel_blend a_blend, + wuffs_base__slice_u8 a_workbuf, + wuffs_base__decode_frame_options* a_opts) { + wuffs_base__status status = wuffs_base__make_status(NULL); + + uint32_t v_seq_num = 0; + wuffs_base__status v_status = wuffs_base__make_status(NULL); + uint32_t v_pass_width = 0; + uint32_t v_pass_height = 0; + + const uint8_t* iop_a_src = NULL; + const uint8_t* io0_a_src WUFFS_BASE__POTENTIALLY_UNUSED = NULL; + const uint8_t* io1_a_src WUFFS_BASE__POTENTIALLY_UNUSED = NULL; + const uint8_t* io2_a_src WUFFS_BASE__POTENTIALLY_UNUSED = NULL; + if (a_src && a_src->data.ptr) { + io0_a_src = a_src->data.ptr; + io1_a_src = io0_a_src + a_src->meta.ri; + iop_a_src = io1_a_src; + io2_a_src = io0_a_src + a_src->meta.wi; + } + + uint32_t coro_susp_point = self->private_impl.p_do_decode_frame[0]; + switch (coro_susp_point) { + WUFFS_BASE__COROUTINE_SUSPENSION_POINT_0; + + if ((self->private_impl.f_call_sequence & 16) != 0) { + status = wuffs_base__make_status(wuffs_base__error__bad_call_sequence); + goto exit; + } else if (self->private_impl.f_call_sequence >= 96) { + status = wuffs_base__make_status(wuffs_base__note__end_of_data); + goto ok; + } else if (self->private_impl.f_call_sequence != 64) { + if (a_src) { + a_src->meta.ri = ((size_t)(iop_a_src - a_src->data.ptr)); + } + WUFFS_BASE__COROUTINE_SUSPENSION_POINT(1); + status = wuffs_png__decoder__do_decode_frame_config(self, NULL, a_src); + if (a_src) { + iop_a_src = a_src->data.ptr + a_src->meta.ri; + } + if (status.repr) { + goto suspend; + } + } + label__0__continue:; + while (true) { + if (((uint64_t)(io2_a_src - iop_a_src)) < 8) { + status = wuffs_base__make_status(wuffs_base__suspension__short_read); + WUFFS_BASE__COROUTINE_SUSPENSION_POINT_MAYBE_SUSPEND(2); + goto label__0__continue; + } + self->private_impl.f_chunk_length = wuffs_base__peek_u32be__no_bounds_check(iop_a_src); + self->private_impl.f_chunk_type = ((uint32_t)((wuffs_base__peek_u64le__no_bounds_check(iop_a_src) >> 32))); + if (self->private_impl.f_chunk_type == 1413563465) { + self->private_impl.f_chunk_type_array[0] = 73; + self->private_impl.f_chunk_type_array[1] = 68; + self->private_impl.f_chunk_type_array[2] = 65; + self->private_impl.f_chunk_type_array[3] = 84; + iop_a_src += 8; + if ( ! self->private_impl.f_ignore_checksum) { + wuffs_base__ignore_status(wuffs_crc32__ieee_hasher__initialize(&self->private_data.f_crc32, + sizeof (wuffs_crc32__ieee_hasher), WUFFS_VERSION, WUFFS_INITIALIZE__LEAVE_INTERNAL_BUFFERS_UNINITIALIZED)); + wuffs_crc32__ieee_hasher__update_u32(&self->private_data.f_crc32, wuffs_base__make_slice_u8(self->private_impl.f_chunk_type_array, 4)); + } + goto label__0__break; + } else if (self->private_impl.f_chunk_type == 1413571686) { + self->private_impl.f_chunk_type_array[0] = 102; + self->private_impl.f_chunk_type_array[1] = 100; + self->private_impl.f_chunk_type_array[2] = 65; + self->private_impl.f_chunk_type_array[3] = 84; + if (self->private_impl.f_chunk_length < 4) { + status = wuffs_base__make_status(wuffs_png__error__bad_chunk); + goto exit; + } + self->private_impl.f_chunk_length -= 4; + iop_a_src += 8; + { + WUFFS_BASE__COROUTINE_SUSPENSION_POINT(3); + uint32_t t_0; + if (WUFFS_BASE__LIKELY(io2_a_src - iop_a_src >= 4)) { + t_0 = wuffs_base__peek_u32be__no_bounds_check(iop_a_src); + iop_a_src += 4; + } else { + self->private_data.s_do_decode_frame[0].scratch = 0; + WUFFS_BASE__COROUTINE_SUSPENSION_POINT(4); + while (true) { + if (WUFFS_BASE__UNLIKELY(iop_a_src == io2_a_src)) { + status = wuffs_base__make_status(wuffs_base__suspension__short_read); + goto suspend; + } + uint64_t* scratch = &self->private_data.s_do_decode_frame[0].scratch; + uint32_t num_bits_0 = ((uint32_t)(*scratch & 0xFF)); + *scratch >>= 8; + *scratch <<= 8; + *scratch |= ((uint64_t)(*iop_a_src++)) << (56 - num_bits_0); + if (num_bits_0 == 24) { + t_0 = ((uint32_t)(*scratch >> 32)); + break; + } + num_bits_0 += 8; + *scratch |= ((uint64_t)(num_bits_0)); + } + } + v_seq_num = t_0; + } + if (v_seq_num != self->private_impl.f_next_animation_seq_num) { + status = wuffs_base__make_status(wuffs_png__error__bad_animation_sequence_number); + goto exit; + } else if (self->private_impl.f_next_animation_seq_num >= 4294967295) { + status = wuffs_base__make_status(wuffs_png__error__unsupported_png_file); + goto exit; + } + self->private_impl.f_next_animation_seq_num += 1; + goto label__0__break; + } else if (self->private_impl.f_chunk_type == 1280598886) { + status = wuffs_base__make_status(wuffs_png__error__bad_chunk); + goto exit; + } + self->private_data.s_do_decode_frame[0].scratch = (((uint64_t)(self->private_impl.f_chunk_length)) + 12); + WUFFS_BASE__COROUTINE_SUSPENSION_POINT(5); + if (self->private_data.s_do_decode_frame[0].scratch > ((uint64_t)(io2_a_src - iop_a_src))) { + self->private_data.s_do_decode_frame[0].scratch -= ((uint64_t)(io2_a_src - iop_a_src)); + iop_a_src = io2_a_src; + status = wuffs_base__make_status(wuffs_base__suspension__short_read); + goto suspend; + } + iop_a_src += self->private_data.s_do_decode_frame[0].scratch; + self->private_impl.f_chunk_length = 0; + } + label__0__break:; + if (self->private_impl.f_zlib_is_dirty) { + wuffs_base__ignore_status(wuffs_zlib__decoder__initialize(&self->private_data.f_zlib, + sizeof (wuffs_zlib__decoder), WUFFS_VERSION, WUFFS_INITIALIZE__LEAVE_INTERNAL_BUFFERS_UNINITIALIZED)); + if (self->private_impl.f_ignore_checksum) { + wuffs_zlib__decoder__set_quirk_enabled(&self->private_data.f_zlib, 1, true); + } + } + self->private_impl.f_zlib_is_dirty = true; + v_status = wuffs_base__pixel_swizzler__prepare(&self->private_impl.f_swizzler, + wuffs_base__pixel_buffer__pixel_format(a_dst), + wuffs_base__pixel_buffer__palette_or_else(a_dst, wuffs_base__make_slice_u8(self->private_data.f_dst_palette, 1024)), + wuffs_base__utility__make_pixel_format(self->private_impl.f_src_pixfmt), + wuffs_base__make_slice_u8(self->private_data.f_src_palette, 1024), + a_blend); + if ( ! wuffs_base__status__is_ok(&v_status)) { + status = v_status; + if (wuffs_base__status__is_error(&status)) { + goto exit; + } else if (wuffs_base__status__is_suspension(&status)) { + status = wuffs_base__make_status(wuffs_base__error__cannot_return_a_suspension); + goto exit; + } + goto ok; + } + self->private_impl.f_workbuf_hist_pos_base = 0; + while (true) { + if (self->private_impl.f_chunk_type_array[0] == 73) { + v_pass_width = (16777215 & ((((uint32_t)(WUFFS_PNG__INTERLACING[self->private_impl.f_interlace_pass][1])) + self->private_impl.f_width) >> WUFFS_PNG__INTERLACING[self->private_impl.f_interlace_pass][0])); + v_pass_height = (16777215 & ((((uint32_t)(WUFFS_PNG__INTERLACING[self->private_impl.f_interlace_pass][4])) + self->private_impl.f_height) >> WUFFS_PNG__INTERLACING[self->private_impl.f_interlace_pass][3])); + } else { + v_pass_width = (16777215 & ((uint32_t)(self->private_impl.f_frame_rect_x1 - self->private_impl.f_frame_rect_x0))); + v_pass_height = (16777215 & ((uint32_t)(self->private_impl.f_frame_rect_y1 - self->private_impl.f_frame_rect_y0))); + } + if ((v_pass_width > 0) && (v_pass_height > 0)) { + self->private_impl.f_pass_bytes_per_row = wuffs_png__decoder__calculate_bytes_per_row(self, v_pass_width); + self->private_impl.f_pass_workbuf_length = (((uint64_t)(v_pass_height)) * (1 + self->private_impl.f_pass_bytes_per_row)); + while (true) { + { + if (a_src) { + a_src->meta.ri = ((size_t)(iop_a_src - a_src->data.ptr)); + } + wuffs_base__status t_1 = wuffs_png__decoder__decode_pass(self, a_src, a_workbuf); + v_status = t_1; + if (a_src) { + iop_a_src = a_src->data.ptr + a_src->meta.ri; + } + } + if (wuffs_base__status__is_ok(&v_status)) { + goto label__1__break; + } else if (wuffs_base__status__is_error(&v_status) || ((v_status.repr == wuffs_base__suspension__short_read) && (a_src && a_src->meta.closed))) { + if (self->private_impl.f_workbuf_wi <= ((uint64_t)(a_workbuf.len))) { + wuffs_png__decoder__filter_and_swizzle(self, a_dst, wuffs_base__slice_u8__subslice_j(a_workbuf, self->private_impl.f_workbuf_wi)); + } + if (v_status.repr == wuffs_base__suspension__short_read) { + status = wuffs_base__make_status(wuffs_png__error__truncated_input); + goto exit; + } + } + status = v_status; + WUFFS_BASE__COROUTINE_SUSPENSION_POINT_MAYBE_SUSPEND(6); + } + label__1__break:; + v_status = wuffs_png__decoder__filter_and_swizzle(self, a_dst, a_workbuf); + if ( ! wuffs_base__status__is_ok(&v_status)) { + status = v_status; + if (wuffs_base__status__is_error(&status)) { + goto exit; + } else if (wuffs_base__status__is_suspension(&status)) { + status = wuffs_base__make_status(wuffs_base__error__cannot_return_a_suspension); + goto exit; + } + goto ok; + } + self->private_impl.f_workbuf_hist_pos_base += self->private_impl.f_pass_workbuf_length; + } + if ((self->private_impl.f_interlace_pass == 0) || (self->private_impl.f_interlace_pass >= 7)) { + goto label__2__break; + } +#if defined(__GNUC__) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wconversion" +#endif + self->private_impl.f_interlace_pass += 1; +#if defined(__GNUC__) +#pragma GCC diagnostic pop +#endif + } + label__2__break:; + wuffs_base__u32__sat_add_indirect(&self->private_impl.f_num_decoded_frames_value, 1); + self->private_impl.f_call_sequence = 32; + + ok: + self->private_impl.p_do_decode_frame[0] = 0; + goto exit; + } + + goto suspend; + suspend: + self->private_impl.p_do_decode_frame[0] = wuffs_base__status__is_suspension(&status) ? coro_susp_point : 0; + + goto exit; + exit: + if (a_src && a_src->data.ptr) { + a_src->meta.ri = ((size_t)(iop_a_src - a_src->data.ptr)); + } + + return status; +} + +// -------- func png.decoder.decode_pass + +static wuffs_base__status +wuffs_png__decoder__decode_pass( + wuffs_png__decoder* self, + wuffs_base__io_buffer* a_src, + wuffs_base__slice_u8 a_workbuf) { + wuffs_base__status status = wuffs_base__make_status(NULL); + + wuffs_base__io_buffer u_w = wuffs_base__empty_io_buffer(); + wuffs_base__io_buffer* v_w = &u_w; + uint8_t* iop_v_w WUFFS_BASE__POTENTIALLY_UNUSED = NULL; + uint8_t* io0_v_w WUFFS_BASE__POTENTIALLY_UNUSED = NULL; + uint8_t* io1_v_w WUFFS_BASE__POTENTIALLY_UNUSED = NULL; + uint8_t* io2_v_w WUFFS_BASE__POTENTIALLY_UNUSED = NULL; + uint64_t v_w_mark = 0; + uint64_t v_r_mark = 0; + wuffs_base__status v_zlib_status = wuffs_base__make_status(NULL); + uint32_t v_checksum_have = 0; + uint32_t v_checksum_want = 0; + uint32_t v_seq_num = 0; + + const uint8_t* iop_a_src = NULL; + const uint8_t* io0_a_src WUFFS_BASE__POTENTIALLY_UNUSED = NULL; + const uint8_t* io1_a_src WUFFS_BASE__POTENTIALLY_UNUSED = NULL; + const uint8_t* io2_a_src WUFFS_BASE__POTENTIALLY_UNUSED = NULL; + if (a_src && a_src->data.ptr) { + io0_a_src = a_src->data.ptr; + io1_a_src = io0_a_src + a_src->meta.ri; + iop_a_src = io1_a_src; + io2_a_src = io0_a_src + a_src->meta.wi; + } + + uint32_t coro_susp_point = self->private_impl.p_decode_pass[0]; + switch (coro_susp_point) { + WUFFS_BASE__COROUTINE_SUSPENSION_POINT_0; + + self->private_impl.f_workbuf_wi = 0; + label__0__continue:; + while (true) { + if ((self->private_impl.f_workbuf_wi > self->private_impl.f_pass_workbuf_length) || (self->private_impl.f_pass_workbuf_length > ((uint64_t)(a_workbuf.len)))) { + status = wuffs_base__make_status(wuffs_base__error__bad_workbuf_length); + goto exit; + } + { + wuffs_base__io_buffer* o_0_v_w = v_w; + uint8_t *o_0_iop_v_w = iop_v_w; + uint8_t *o_0_io0_v_w = io0_v_w; + uint8_t *o_0_io1_v_w = io1_v_w; + uint8_t *o_0_io2_v_w = io2_v_w; + v_w = wuffs_base__io_writer__set( + &u_w, + &iop_v_w, + &io0_v_w, + &io1_v_w, + &io2_v_w, + wuffs_base__slice_u8__subslice_ij(a_workbuf, + self->private_impl.f_workbuf_wi, + self->private_impl.f_pass_workbuf_length), + ((uint64_t)(self->private_impl.f_workbuf_hist_pos_base + self->private_impl.f_workbuf_wi))); + { + const bool o_1_closed_a_src = a_src->meta.closed; + const uint8_t *o_1_io2_a_src = io2_a_src; + wuffs_base__io_reader__limit(&io2_a_src, iop_a_src, + ((uint64_t)(self->private_impl.f_chunk_length))); + if (a_src) { + size_t n = ((size_t)(io2_a_src - a_src->data.ptr)); + a_src->meta.closed = a_src->meta.closed && (a_src->meta.wi <= n); + a_src->meta.wi = n; + } + v_w_mark = ((uint64_t)(iop_v_w - io0_v_w)); + v_r_mark = ((uint64_t)(iop_a_src - io0_a_src)); + { + u_w.meta.wi = ((size_t)(iop_v_w - u_w.data.ptr)); + if (a_src) { + a_src->meta.ri = ((size_t)(iop_a_src - a_src->data.ptr)); + } + wuffs_base__status t_0 = wuffs_zlib__decoder__transform_io(&self->private_data.f_zlib, v_w, a_src, wuffs_base__utility__empty_slice_u8()); + v_zlib_status = t_0; + iop_v_w = u_w.data.ptr + u_w.meta.wi; + if (a_src) { + iop_a_src = a_src->data.ptr + a_src->meta.ri; + } + } + if ( ! self->private_impl.f_ignore_checksum) { + wuffs_crc32__ieee_hasher__update_u32(&self->private_data.f_crc32, wuffs_base__io__since(v_r_mark, ((uint64_t)(iop_a_src - io0_a_src)), io0_a_src)); + } + wuffs_base__u32__sat_sub_indirect(&self->private_impl.f_chunk_length, ((uint32_t)((wuffs_base__io__count_since(v_r_mark, ((uint64_t)(iop_a_src - io0_a_src))) & 4294967295)))); + wuffs_base__u64__sat_add_indirect(&self->private_impl.f_workbuf_wi, wuffs_base__io__count_since(v_w_mark, ((uint64_t)(iop_v_w - io0_v_w)))); + io2_a_src = o_1_io2_a_src; + if (a_src) { + a_src->meta.closed = o_1_closed_a_src; + a_src->meta.wi = ((size_t)(io2_a_src - a_src->data.ptr)); + } + } + v_w = o_0_v_w; + iop_v_w = o_0_iop_v_w; + io0_v_w = o_0_io0_v_w; + io1_v_w = o_0_io1_v_w; + io2_v_w = o_0_io2_v_w; + } + if (wuffs_base__status__is_ok(&v_zlib_status)) { + if (self->private_impl.f_chunk_length > 0) { + status = wuffs_base__make_status(wuffs_base__error__too_much_data); + goto exit; + } + { + WUFFS_BASE__COROUTINE_SUSPENSION_POINT(1); + uint32_t t_1; + if (WUFFS_BASE__LIKELY(io2_a_src - iop_a_src >= 4)) { + t_1 = wuffs_base__peek_u32be__no_bounds_check(iop_a_src); + iop_a_src += 4; + } else { + self->private_data.s_decode_pass[0].scratch = 0; + WUFFS_BASE__COROUTINE_SUSPENSION_POINT(2); + while (true) { + if (WUFFS_BASE__UNLIKELY(iop_a_src == io2_a_src)) { + status = wuffs_base__make_status(wuffs_base__suspension__short_read); + goto suspend; + } + uint64_t* scratch = &self->private_data.s_decode_pass[0].scratch; + uint32_t num_bits_1 = ((uint32_t)(*scratch & 0xFF)); + *scratch >>= 8; + *scratch <<= 8; + *scratch |= ((uint64_t)(*iop_a_src++)) << (56 - num_bits_1); + if (num_bits_1 == 24) { + t_1 = ((uint32_t)(*scratch >> 32)); + break; + } + num_bits_1 += 8; + *scratch |= ((uint64_t)(num_bits_1)); + } + } + v_checksum_want = t_1; + } + if ( ! self->private_impl.f_ignore_checksum && (self->private_impl.f_chunk_type_array[0] == 73)) { + v_checksum_have = wuffs_crc32__ieee_hasher__update_u32(&self->private_data.f_crc32, wuffs_base__utility__empty_slice_u8()); + if (v_checksum_have != v_checksum_want) { + status = wuffs_base__make_status(wuffs_png__error__bad_checksum); + goto exit; + } + } + goto label__0__break; + } else if (v_zlib_status.repr == wuffs_base__suspension__short_write) { + if ((1 <= self->private_impl.f_interlace_pass) && (self->private_impl.f_interlace_pass <= 6)) { + goto label__0__break; + } + status = wuffs_base__make_status(wuffs_base__error__too_much_data); + goto exit; + } else if (v_zlib_status.repr != wuffs_base__suspension__short_read) { + status = v_zlib_status; + if (wuffs_base__status__is_error(&status)) { + goto exit; + } else if (wuffs_base__status__is_suspension(&status)) { + status = wuffs_base__make_status(wuffs_base__error__cannot_return_a_suspension); + goto exit; + } + goto ok; + } else if (self->private_impl.f_chunk_length == 0) { + { + WUFFS_BASE__COROUTINE_SUSPENSION_POINT(3); + uint32_t t_2; + if (WUFFS_BASE__LIKELY(io2_a_src - iop_a_src >= 4)) { + t_2 = wuffs_base__peek_u32be__no_bounds_check(iop_a_src); + iop_a_src += 4; + } else { + self->private_data.s_decode_pass[0].scratch = 0; + WUFFS_BASE__COROUTINE_SUSPENSION_POINT(4); + while (true) { + if (WUFFS_BASE__UNLIKELY(iop_a_src == io2_a_src)) { + status = wuffs_base__make_status(wuffs_base__suspension__short_read); + goto suspend; + } + uint64_t* scratch = &self->private_data.s_decode_pass[0].scratch; + uint32_t num_bits_2 = ((uint32_t)(*scratch & 0xFF)); + *scratch >>= 8; + *scratch <<= 8; + *scratch |= ((uint64_t)(*iop_a_src++)) << (56 - num_bits_2); + if (num_bits_2 == 24) { + t_2 = ((uint32_t)(*scratch >> 32)); + break; + } + num_bits_2 += 8; + *scratch |= ((uint64_t)(num_bits_2)); + } + } + v_checksum_want = t_2; + } + if ( ! self->private_impl.f_ignore_checksum && (self->private_impl.f_chunk_type_array[0] == 73)) { + v_checksum_have = wuffs_crc32__ieee_hasher__update_u32(&self->private_data.f_crc32, wuffs_base__utility__empty_slice_u8()); + if (v_checksum_have != v_checksum_want) { + status = wuffs_base__make_status(wuffs_png__error__bad_checksum); + goto exit; + } + } + { + WUFFS_BASE__COROUTINE_SUSPENSION_POINT(5); + uint32_t t_3; + if (WUFFS_BASE__LIKELY(io2_a_src - iop_a_src >= 4)) { + t_3 = wuffs_base__peek_u32be__no_bounds_check(iop_a_src); + iop_a_src += 4; + } else { + self->private_data.s_decode_pass[0].scratch = 0; + WUFFS_BASE__COROUTINE_SUSPENSION_POINT(6); + while (true) { + if (WUFFS_BASE__UNLIKELY(iop_a_src == io2_a_src)) { + status = wuffs_base__make_status(wuffs_base__suspension__short_read); + goto suspend; + } + uint64_t* scratch = &self->private_data.s_decode_pass[0].scratch; + uint32_t num_bits_3 = ((uint32_t)(*scratch & 0xFF)); + *scratch >>= 8; + *scratch <<= 8; + *scratch |= ((uint64_t)(*iop_a_src++)) << (56 - num_bits_3); + if (num_bits_3 == 24) { + t_3 = ((uint32_t)(*scratch >> 32)); + break; + } + num_bits_3 += 8; + *scratch |= ((uint64_t)(num_bits_3)); + } + } + self->private_impl.f_chunk_length = t_3; + } + { + WUFFS_BASE__COROUTINE_SUSPENSION_POINT(7); + uint32_t t_4; + if (WUFFS_BASE__LIKELY(io2_a_src - iop_a_src >= 4)) { + t_4 = wuffs_base__peek_u32le__no_bounds_check(iop_a_src); + iop_a_src += 4; + } else { + self->private_data.s_decode_pass[0].scratch = 0; + WUFFS_BASE__COROUTINE_SUSPENSION_POINT(8); + while (true) { + if (WUFFS_BASE__UNLIKELY(iop_a_src == io2_a_src)) { + status = wuffs_base__make_status(wuffs_base__suspension__short_read); + goto suspend; + } + uint64_t* scratch = &self->private_data.s_decode_pass[0].scratch; + uint32_t num_bits_4 = ((uint32_t)(*scratch >> 56)); + *scratch <<= 8; + *scratch >>= 8; + *scratch |= ((uint64_t)(*iop_a_src++)) << num_bits_4; + if (num_bits_4 == 24) { + t_4 = ((uint32_t)(*scratch)); + break; + } + num_bits_4 += 8; + *scratch |= ((uint64_t)(num_bits_4)) << 56; + } + } + self->private_impl.f_chunk_type = t_4; + } + if (self->private_impl.f_chunk_type_array[0] == 73) { + if (self->private_impl.f_chunk_type != 1413563465) { + status = wuffs_base__make_status(wuffs_png__error__bad_chunk); + goto exit; + } + if ( ! self->private_impl.f_ignore_checksum) { + wuffs_base__ignore_status(wuffs_crc32__ieee_hasher__initialize(&self->private_data.f_crc32, + sizeof (wuffs_crc32__ieee_hasher), WUFFS_VERSION, WUFFS_INITIALIZE__LEAVE_INTERNAL_BUFFERS_UNINITIALIZED)); + wuffs_crc32__ieee_hasher__update_u32(&self->private_data.f_crc32, wuffs_base__make_slice_u8(self->private_impl.f_chunk_type_array, 4)); + } + } else { + if ((self->private_impl.f_chunk_type != 1413571686) || (self->private_impl.f_chunk_length < 4)) { + status = wuffs_base__make_status(wuffs_png__error__bad_chunk); + goto exit; + } + self->private_impl.f_chunk_length -= 4; + { + WUFFS_BASE__COROUTINE_SUSPENSION_POINT(9); + uint32_t t_5; + if (WUFFS_BASE__LIKELY(io2_a_src - iop_a_src >= 4)) { + t_5 = wuffs_base__peek_u32be__no_bounds_check(iop_a_src); + iop_a_src += 4; + } else { + self->private_data.s_decode_pass[0].scratch = 0; + WUFFS_BASE__COROUTINE_SUSPENSION_POINT(10); + while (true) { + if (WUFFS_BASE__UNLIKELY(iop_a_src == io2_a_src)) { + status = wuffs_base__make_status(wuffs_base__suspension__short_read); + goto suspend; + } + uint64_t* scratch = &self->private_data.s_decode_pass[0].scratch; + uint32_t num_bits_5 = ((uint32_t)(*scratch & 0xFF)); + *scratch >>= 8; + *scratch <<= 8; + *scratch |= ((uint64_t)(*iop_a_src++)) << (56 - num_bits_5); + if (num_bits_5 == 24) { + t_5 = ((uint32_t)(*scratch >> 32)); + break; + } + num_bits_5 += 8; + *scratch |= ((uint64_t)(num_bits_5)); + } + } + v_seq_num = t_5; + } + if (v_seq_num != self->private_impl.f_next_animation_seq_num) { + status = wuffs_base__make_status(wuffs_png__error__bad_animation_sequence_number); + goto exit; + } else if (self->private_impl.f_next_animation_seq_num >= 4294967295) { + status = wuffs_base__make_status(wuffs_png__error__unsupported_png_file); + goto exit; + } + self->private_impl.f_next_animation_seq_num += 1; + } + goto label__0__continue; + } else if (((uint64_t)(io2_a_src - iop_a_src)) > 0) { + status = wuffs_base__make_status(wuffs_png__error__internal_error_zlib_decoder_did_not_exhaust_its_input); + goto exit; + } + status = wuffs_base__make_status(wuffs_base__suspension__short_read); + WUFFS_BASE__COROUTINE_SUSPENSION_POINT_MAYBE_SUSPEND(11); + } + label__0__break:; + if (self->private_impl.f_workbuf_wi != self->private_impl.f_pass_workbuf_length) { + status = wuffs_base__make_status(wuffs_base__error__not_enough_data); + goto exit; + } else if (0 < ((uint64_t)(a_workbuf.len))) { + if (a_workbuf.ptr[0] == 4) { + a_workbuf.ptr[0] = 1; + } + } + + ok: + self->private_impl.p_decode_pass[0] = 0; + goto exit; + } + + goto suspend; + suspend: + self->private_impl.p_decode_pass[0] = wuffs_base__status__is_suspension(&status) ? coro_susp_point : 0; + + goto exit; + exit: + if (a_src && a_src->data.ptr) { + a_src->meta.ri = ((size_t)(iop_a_src - a_src->data.ptr)); + } + + return status; +} + +// -------- func png.decoder.frame_dirty_rect + +WUFFS_BASE__MAYBE_STATIC wuffs_base__rect_ie_u32 +wuffs_png__decoder__frame_dirty_rect( + const wuffs_png__decoder* self) { + if (!self) { + return wuffs_base__utility__empty_rect_ie_u32(); + } + if ((self->private_impl.magic != WUFFS_BASE__MAGIC) && + (self->private_impl.magic != WUFFS_BASE__DISABLED)) { + return wuffs_base__utility__empty_rect_ie_u32(); + } + + return wuffs_base__utility__make_rect_ie_u32( + self->private_impl.f_frame_rect_x0, + self->private_impl.f_frame_rect_y0, + self->private_impl.f_frame_rect_x1, + self->private_impl.f_frame_rect_y1); +} + +// -------- func png.decoder.num_animation_loops + +WUFFS_BASE__MAYBE_STATIC uint32_t +wuffs_png__decoder__num_animation_loops( + const wuffs_png__decoder* self) { + if (!self) { + return 0; + } + if ((self->private_impl.magic != WUFFS_BASE__MAGIC) && + (self->private_impl.magic != WUFFS_BASE__DISABLED)) { + return 0; + } + + return self->private_impl.f_num_animation_loops_value; +} + +// -------- func png.decoder.num_decoded_frame_configs + +WUFFS_BASE__MAYBE_STATIC uint64_t +wuffs_png__decoder__num_decoded_frame_configs( + const wuffs_png__decoder* self) { + if (!self) { + return 0; + } + if ((self->private_impl.magic != WUFFS_BASE__MAGIC) && + (self->private_impl.magic != WUFFS_BASE__DISABLED)) { + return 0; + } + + return ((uint64_t)(self->private_impl.f_num_decoded_frame_configs_value)); +} + +// -------- func png.decoder.num_decoded_frames + +WUFFS_BASE__MAYBE_STATIC uint64_t +wuffs_png__decoder__num_decoded_frames( + const wuffs_png__decoder* self) { + if (!self) { + return 0; + } + if ((self->private_impl.magic != WUFFS_BASE__MAGIC) && + (self->private_impl.magic != WUFFS_BASE__DISABLED)) { + return 0; + } + + return ((uint64_t)(self->private_impl.f_num_decoded_frames_value)); +} + +// -------- func png.decoder.restart_frame + +WUFFS_BASE__MAYBE_STATIC wuffs_base__status +wuffs_png__decoder__restart_frame( + wuffs_png__decoder* self, + uint64_t a_index, + uint64_t a_io_position) { + if (!self) { + return wuffs_base__make_status(wuffs_base__error__bad_receiver); + } + if (self->private_impl.magic != WUFFS_BASE__MAGIC) { + return wuffs_base__make_status( + (self->private_impl.magic == WUFFS_BASE__DISABLED) + ? wuffs_base__error__disabled_by_previous_error + : wuffs_base__error__initialize_not_called); + } + + if (self->private_impl.f_call_sequence < 32) { + return wuffs_base__make_status(wuffs_base__error__bad_call_sequence); + } else if ((a_index >= ((uint64_t)(self->private_impl.f_num_animation_frames_value))) || ((a_index == 0) && (a_io_position != self->private_impl.f_first_config_io_position))) { + return wuffs_base__make_status(wuffs_base__error__bad_argument); + } + self->private_impl.f_call_sequence = 40; + if (self->private_impl.f_interlace_pass >= 1) { + self->private_impl.f_interlace_pass = 1; + } + self->private_impl.f_frame_config_io_position = a_io_position; + self->private_impl.f_num_decoded_frame_configs_value = ((uint32_t)((a_index & 4294967295))); + self->private_impl.f_num_decoded_frames_value = self->private_impl.f_num_decoded_frame_configs_value; + return wuffs_base__make_status(NULL); +} + +// -------- func png.decoder.set_report_metadata + +WUFFS_BASE__MAYBE_STATIC wuffs_base__empty_struct +wuffs_png__decoder__set_report_metadata( + wuffs_png__decoder* self, + uint32_t a_fourcc, + bool a_report) { + if (!self) { + return wuffs_base__make_empty_struct(); + } + if (self->private_impl.magic != WUFFS_BASE__MAGIC) { + return wuffs_base__make_empty_struct(); + } + + if (a_fourcc == 1128813133) { + self->private_impl.f_report_metadata_chrm = a_report; + } else if (a_fourcc == 1163413830) { + self->private_impl.f_report_metadata_exif = a_report; + } else if (a_fourcc == 1195461953) { + self->private_impl.f_report_metadata_gama = a_report; + } else if (a_fourcc == 1229144912) { + self->private_impl.f_report_metadata_iccp = a_report; + } else if (a_fourcc == 1263947808) { + self->private_impl.f_report_metadata_kvp = a_report; + } else if (a_fourcc == 1397901122) { + self->private_impl.f_report_metadata_srgb = a_report; + } + return wuffs_base__make_empty_struct(); +} + +// -------- func png.decoder.tell_me_more + +WUFFS_BASE__MAYBE_STATIC wuffs_base__status +wuffs_png__decoder__tell_me_more( + wuffs_png__decoder* self, + wuffs_base__io_buffer* a_dst, + wuffs_base__more_information* a_minfo, + wuffs_base__io_buffer* a_src) { + if (!self) { + return wuffs_base__make_status(wuffs_base__error__bad_receiver); + } + if (self->private_impl.magic != WUFFS_BASE__MAGIC) { + return wuffs_base__make_status( + (self->private_impl.magic == WUFFS_BASE__DISABLED) + ? wuffs_base__error__disabled_by_previous_error + : wuffs_base__error__initialize_not_called); + } + if (!a_dst || !a_src) { + self->private_impl.magic = WUFFS_BASE__DISABLED; + return wuffs_base__make_status(wuffs_base__error__bad_argument); + } + if ((self->private_impl.active_coroutine != 0) && + (self->private_impl.active_coroutine != 4)) { + self->private_impl.magic = WUFFS_BASE__DISABLED; + return wuffs_base__make_status(wuffs_base__error__interleaved_coroutine_calls); + } + self->private_impl.active_coroutine = 0; + wuffs_base__status status = wuffs_base__make_status(NULL); + + wuffs_base__status v_status = wuffs_base__make_status(NULL); + + uint32_t coro_susp_point = self->private_impl.p_tell_me_more[0]; + switch (coro_susp_point) { + WUFFS_BASE__COROUTINE_SUSPENSION_POINT_0; + + while (true) { + { + wuffs_base__status t_0 = wuffs_png__decoder__do_tell_me_more(self, a_dst, a_minfo, a_src); + v_status = t_0; + } + if ((v_status.repr == wuffs_base__suspension__short_read) && (a_src && a_src->meta.closed)) { + status = wuffs_base__make_status(wuffs_png__error__truncated_input); + goto exit; + } + status = v_status; + WUFFS_BASE__COROUTINE_SUSPENSION_POINT_MAYBE_SUSPEND(1); + } + + ok: + self->private_impl.p_tell_me_more[0] = 0; + goto exit; + } + + goto suspend; + suspend: + self->private_impl.p_tell_me_more[0] = wuffs_base__status__is_suspension(&status) ? coro_susp_point : 0; + self->private_impl.active_coroutine = wuffs_base__status__is_suspension(&status) ? 4 : 0; + + goto exit; + exit: + if (wuffs_base__status__is_error(&status)) { + self->private_impl.magic = WUFFS_BASE__DISABLED; + } + return status; +} + +// -------- func png.decoder.do_tell_me_more + +static wuffs_base__status +wuffs_png__decoder__do_tell_me_more( + wuffs_png__decoder* self, + wuffs_base__io_buffer* a_dst, + wuffs_base__more_information* a_minfo, + wuffs_base__io_buffer* a_src) { + wuffs_base__status status = wuffs_base__make_status(NULL); + + uint8_t v_c = 0; + uint16_t v_c2 = 0; + wuffs_base__io_buffer u_w = wuffs_base__empty_io_buffer(); + wuffs_base__io_buffer* v_w = &u_w; + uint8_t* iop_v_w WUFFS_BASE__POTENTIALLY_UNUSED = NULL; + uint8_t* io0_v_w WUFFS_BASE__POTENTIALLY_UNUSED = NULL; + uint8_t* io1_v_w WUFFS_BASE__POTENTIALLY_UNUSED = NULL; + uint8_t* io2_v_w WUFFS_BASE__POTENTIALLY_UNUSED = NULL; + uint64_t v_num_written = 0; + uint64_t v_w_mark = 0; + uint64_t v_r_mark = 0; + wuffs_base__status v_zlib_status = wuffs_base__make_status(NULL); + + uint8_t* iop_a_dst = NULL; + uint8_t* io0_a_dst WUFFS_BASE__POTENTIALLY_UNUSED = NULL; + uint8_t* io1_a_dst WUFFS_BASE__POTENTIALLY_UNUSED = NULL; + uint8_t* io2_a_dst WUFFS_BASE__POTENTIALLY_UNUSED = NULL; + if (a_dst && a_dst->data.ptr) { + io0_a_dst = a_dst->data.ptr; + io1_a_dst = io0_a_dst + a_dst->meta.wi; + iop_a_dst = io1_a_dst; + io2_a_dst = io0_a_dst + a_dst->data.len; + if (a_dst->meta.closed) { + io2_a_dst = iop_a_dst; + } + } + const uint8_t* iop_a_src = NULL; + const uint8_t* io0_a_src WUFFS_BASE__POTENTIALLY_UNUSED = NULL; + const uint8_t* io1_a_src WUFFS_BASE__POTENTIALLY_UNUSED = NULL; + const uint8_t* io2_a_src WUFFS_BASE__POTENTIALLY_UNUSED = NULL; + if (a_src && a_src->data.ptr) { + io0_a_src = a_src->data.ptr; + io1_a_src = io0_a_src + a_src->meta.ri; + iop_a_src = io1_a_src; + io2_a_src = io0_a_src + a_src->meta.wi; + } + + uint32_t coro_susp_point = self->private_impl.p_do_tell_me_more[0]; + if (coro_susp_point) { + v_zlib_status = self->private_data.s_do_tell_me_more[0].v_zlib_status; + } + switch (coro_susp_point) { + WUFFS_BASE__COROUTINE_SUSPENSION_POINT_0; + + if ((self->private_impl.f_call_sequence & 16) == 0) { + status = wuffs_base__make_status(wuffs_base__error__bad_call_sequence); + goto exit; + } + if (self->private_impl.f_metadata_fourcc == 0) { + status = wuffs_base__make_status(wuffs_base__error__no_more_information); + goto exit; + } + while (true) { + if (self->private_impl.f_metadata_flavor == 3) { + while (true) { + if (wuffs_base__u64__sat_add((a_src ? a_src->meta.pos : 0), ((uint64_t)(iop_a_src - io0_a_src))) != self->private_impl.f_metadata_y) { + status = wuffs_base__make_status(wuffs_base__error__bad_i_o_position); + goto exit; + } else if (a_minfo != NULL) { + wuffs_base__more_information__set(a_minfo, + self->private_impl.f_metadata_flavor, + self->private_impl.f_metadata_fourcc, + self->private_impl.f_metadata_x, + self->private_impl.f_metadata_y, + self->private_impl.f_metadata_z); + } + if (self->private_impl.f_metadata_y >= self->private_impl.f_metadata_z) { + goto label__goto_done__break; + } + self->private_impl.f_metadata_y = self->private_impl.f_metadata_z; + status = wuffs_base__make_status(wuffs_base__suspension__even_more_information); + WUFFS_BASE__COROUTINE_SUSPENSION_POINT_MAYBE_SUSPEND(1); + } + } + if (self->private_impl.f_metadata_is_zlib_compressed) { + if (self->private_impl.f_zlib_is_dirty) { + wuffs_base__ignore_status(wuffs_zlib__decoder__initialize(&self->private_data.f_zlib, + sizeof (wuffs_zlib__decoder), WUFFS_VERSION, WUFFS_INITIALIZE__LEAVE_INTERNAL_BUFFERS_UNINITIALIZED)); + if (self->private_impl.f_ignore_checksum) { + wuffs_zlib__decoder__set_quirk_enabled(&self->private_data.f_zlib, 1, true); + } + } + self->private_impl.f_zlib_is_dirty = true; + self->private_impl.f_ztxt_hist_pos = 0; + } + label__loop__continue:; + while (true) { + if (a_minfo != NULL) { + wuffs_base__more_information__set(a_minfo, + self->private_impl.f_metadata_flavor, + self->private_impl.f_metadata_fourcc, + self->private_impl.f_metadata_x, + self->private_impl.f_metadata_y, + self->private_impl.f_metadata_z); + } + if (self->private_impl.f_metadata_flavor != 4) { + goto label__loop__break; + } + if (self->private_impl.f_metadata_is_zlib_compressed) { + if (self->private_impl.f_chunk_type == 1346585449) { + { + const bool o_0_closed_a_src = a_src->meta.closed; + const uint8_t *o_0_io2_a_src = io2_a_src; + wuffs_base__io_reader__limit(&io2_a_src, iop_a_src, + ((uint64_t)(self->private_impl.f_chunk_length))); + if (a_src) { + size_t n = ((size_t)(io2_a_src - a_src->data.ptr)); + a_src->meta.closed = a_src->meta.closed && (a_src->meta.wi <= n); + a_src->meta.wi = n; + } + v_r_mark = ((uint64_t)(iop_a_src - io0_a_src)); + { + if (a_dst) { + a_dst->meta.wi = ((size_t)(iop_a_dst - a_dst->data.ptr)); + } + if (a_src) { + a_src->meta.ri = ((size_t)(iop_a_src - a_src->data.ptr)); + } + wuffs_base__status t_0 = wuffs_zlib__decoder__transform_io(&self->private_data.f_zlib, a_dst, a_src, wuffs_base__utility__empty_slice_u8()); + v_zlib_status = t_0; + if (a_dst) { + iop_a_dst = a_dst->data.ptr + a_dst->meta.wi; + } + if (a_src) { + iop_a_src = a_src->data.ptr + a_src->meta.ri; + } + } + wuffs_base__u32__sat_sub_indirect(&self->private_impl.f_chunk_length, ((uint32_t)((wuffs_base__io__count_since(v_r_mark, ((uint64_t)(iop_a_src - io0_a_src))) & 4294967295)))); + io2_a_src = o_0_io2_a_src; + if (a_src) { + a_src->meta.closed = o_0_closed_a_src; + a_src->meta.wi = ((size_t)(io2_a_src - a_src->data.ptr)); + } + } + if (wuffs_base__status__is_ok(&v_zlib_status)) { + self->private_impl.f_metadata_is_zlib_compressed = false; + goto label__loop__break; + } else if ( ! wuffs_base__status__is_suspension(&v_zlib_status)) { + status = v_zlib_status; + if (wuffs_base__status__is_error(&status)) { + goto exit; + } else if (wuffs_base__status__is_suspension(&status)) { + status = wuffs_base__make_status(wuffs_base__error__cannot_return_a_suspension); + goto exit; + } + goto ok; + } + status = v_zlib_status; + WUFFS_BASE__COROUTINE_SUSPENSION_POINT_MAYBE_SUSPEND(2); + } else if (self->private_impl.f_chunk_type == 1951945833) { + { + const bool o_1_closed_a_src = a_src->meta.closed; + const uint8_t *o_1_io2_a_src = io2_a_src; + wuffs_base__io_reader__limit(&io2_a_src, iop_a_src, + ((uint64_t)(self->private_impl.f_chunk_length))); + if (a_src) { + size_t n = ((size_t)(io2_a_src - a_src->data.ptr)); + a_src->meta.closed = a_src->meta.closed && (a_src->meta.wi <= n); + a_src->meta.wi = n; + } + v_r_mark = ((uint64_t)(iop_a_src - io0_a_src)); + { + if (a_dst) { + a_dst->meta.wi = ((size_t)(iop_a_dst - a_dst->data.ptr)); + } + if (a_src) { + a_src->meta.ri = ((size_t)(iop_a_src - a_src->data.ptr)); + } + wuffs_base__status t_1 = wuffs_zlib__decoder__transform_io(&self->private_data.f_zlib, a_dst, a_src, wuffs_base__utility__empty_slice_u8()); + v_zlib_status = t_1; + if (a_dst) { + iop_a_dst = a_dst->data.ptr + a_dst->meta.wi; + } + if (a_src) { + iop_a_src = a_src->data.ptr + a_src->meta.ri; + } + } + wuffs_base__u32__sat_sub_indirect(&self->private_impl.f_chunk_length, ((uint32_t)((wuffs_base__io__count_since(v_r_mark, ((uint64_t)(iop_a_src - io0_a_src))) & 4294967295)))); + io2_a_src = o_1_io2_a_src; + if (a_src) { + a_src->meta.closed = o_1_closed_a_src; + a_src->meta.wi = ((size_t)(io2_a_src - a_src->data.ptr)); + } + } + if (wuffs_base__status__is_ok(&v_zlib_status)) { + self->private_impl.f_metadata_is_zlib_compressed = false; + goto label__loop__break; + } else if ( ! wuffs_base__status__is_suspension(&v_zlib_status)) { + status = v_zlib_status; + if (wuffs_base__status__is_error(&status)) { + goto exit; + } else if (wuffs_base__status__is_suspension(&status)) { + status = wuffs_base__make_status(wuffs_base__error__cannot_return_a_suspension); + goto exit; + } + goto ok; + } + status = v_zlib_status; + WUFFS_BASE__COROUTINE_SUSPENSION_POINT_MAYBE_SUSPEND(3); + } else if (self->private_impl.f_chunk_type == 1951945850) { + if (self->private_impl.f_ztxt_ri == self->private_impl.f_ztxt_wi) { + { + wuffs_base__io_buffer* o_2_v_w = v_w; + uint8_t *o_2_iop_v_w = iop_v_w; + uint8_t *o_2_io0_v_w = io0_v_w; + uint8_t *o_2_io1_v_w = io1_v_w; + uint8_t *o_2_io2_v_w = io2_v_w; + v_w = wuffs_base__io_writer__set( + &u_w, + &iop_v_w, + &io0_v_w, + &io1_v_w, + &io2_v_w, + wuffs_base__make_slice_u8(self->private_data.f_dst_palette, 1024), + self->private_impl.f_ztxt_hist_pos); + { + const bool o_3_closed_a_src = a_src->meta.closed; + const uint8_t *o_3_io2_a_src = io2_a_src; + wuffs_base__io_reader__limit(&io2_a_src, iop_a_src, + ((uint64_t)(self->private_impl.f_chunk_length))); + if (a_src) { + size_t n = ((size_t)(io2_a_src - a_src->data.ptr)); + a_src->meta.closed = a_src->meta.closed && (a_src->meta.wi <= n); + a_src->meta.wi = n; + } + v_w_mark = ((uint64_t)(iop_v_w - io0_v_w)); + v_r_mark = ((uint64_t)(iop_a_src - io0_a_src)); + { + u_w.meta.wi = ((size_t)(iop_v_w - u_w.data.ptr)); + if (a_src) { + a_src->meta.ri = ((size_t)(iop_a_src - a_src->data.ptr)); + } + wuffs_base__status t_2 = wuffs_zlib__decoder__transform_io(&self->private_data.f_zlib, v_w, a_src, wuffs_base__utility__empty_slice_u8()); + v_zlib_status = t_2; + iop_v_w = u_w.data.ptr + u_w.meta.wi; + if (a_src) { + iop_a_src = a_src->data.ptr + a_src->meta.ri; + } + } + wuffs_base__u32__sat_sub_indirect(&self->private_impl.f_chunk_length, ((uint32_t)((wuffs_base__io__count_since(v_r_mark, ((uint64_t)(iop_a_src - io0_a_src))) & 4294967295)))); + v_num_written = wuffs_base__io__count_since(v_w_mark, ((uint64_t)(iop_v_w - io0_v_w))); + io2_a_src = o_3_io2_a_src; + if (a_src) { + a_src->meta.closed = o_3_closed_a_src; + a_src->meta.wi = ((size_t)(io2_a_src - a_src->data.ptr)); + } + } + v_w = o_2_v_w; + iop_v_w = o_2_iop_v_w; + io0_v_w = o_2_io0_v_w; + io1_v_w = o_2_io1_v_w; + io2_v_w = o_2_io2_v_w; + } + if (v_num_written > 1024) { + status = wuffs_base__make_status(wuffs_png__error__internal_error_inconsistent_i_o); + goto exit; + } + self->private_impl.f_ztxt_ri = 0; + self->private_impl.f_ztxt_wi = ((uint32_t)(v_num_written)); + wuffs_base__u64__sat_add_indirect(&self->private_impl.f_ztxt_hist_pos, v_num_written); + } + while (self->private_impl.f_ztxt_ri < self->private_impl.f_ztxt_wi) { + v_c2 = WUFFS_PNG__LATIN_1[self->private_data.f_dst_palette[self->private_impl.f_ztxt_ri]]; + if (v_c2 == 0) { + status = wuffs_base__make_status(wuffs_png__error__bad_text_chunk_not_latin_1); + goto exit; + } else if (v_c2 <= 127) { + if (((uint64_t)(io2_a_dst - iop_a_dst)) <= 0) { + status = wuffs_base__make_status(wuffs_base__suspension__short_write); + WUFFS_BASE__COROUTINE_SUSPENSION_POINT_MAYBE_SUSPEND(4); + goto label__loop__continue; + } + self->private_impl.f_ztxt_ri += 1; + (wuffs_base__poke_u8be__no_bounds_check(iop_a_dst, ((uint8_t)(v_c2))), iop_a_dst += 1); + } else { + if (((uint64_t)(io2_a_dst - iop_a_dst)) <= 1) { + status = wuffs_base__make_status(wuffs_base__suspension__short_write); + WUFFS_BASE__COROUTINE_SUSPENSION_POINT_MAYBE_SUSPEND(5); + goto label__loop__continue; + } + self->private_impl.f_ztxt_ri += 1; + (wuffs_base__poke_u16le__no_bounds_check(iop_a_dst, v_c2), iop_a_dst += 2); + } + } + if (wuffs_base__status__is_ok(&v_zlib_status)) { + self->private_impl.f_metadata_is_zlib_compressed = false; + goto label__loop__break; + } else if ( ! wuffs_base__status__is_suspension(&v_zlib_status)) { + status = v_zlib_status; + if (wuffs_base__status__is_error(&status)) { + goto exit; + } else if (wuffs_base__status__is_suspension(&status)) { + status = wuffs_base__make_status(wuffs_base__error__cannot_return_a_suspension); + goto exit; + } + goto ok; + } else if (v_zlib_status.repr != wuffs_base__suspension__short_write) { + status = v_zlib_status; + WUFFS_BASE__COROUTINE_SUSPENSION_POINT_MAYBE_SUSPEND(6); + } + } else { + status = wuffs_base__make_status(wuffs_png__error__internal_error_inconsistent_chunk_type); + goto exit; + } + } else if ((self->private_impl.f_chunk_type == 1951945833) && (self->private_impl.f_metadata_fourcc == 1263947862)) { + while (true) { + if (self->private_impl.f_chunk_length <= 0) { + goto label__loop__break; + } else if (((uint64_t)(io2_a_src - iop_a_src)) <= 0) { + status = wuffs_base__make_status(wuffs_base__suspension__short_read); + WUFFS_BASE__COROUTINE_SUSPENSION_POINT_MAYBE_SUSPEND(7); + goto label__loop__continue; + } else if (((uint64_t)(io2_a_dst - iop_a_dst)) <= 0) { + status = wuffs_base__make_status(wuffs_base__suspension__short_write); + WUFFS_BASE__COROUTINE_SUSPENSION_POINT_MAYBE_SUSPEND(8); + goto label__loop__continue; + } + self->private_impl.f_chunk_length -= 1; + v_c = wuffs_base__peek_u8be__no_bounds_check(iop_a_src); + iop_a_src += 1; + (wuffs_base__poke_u8be__no_bounds_check(iop_a_dst, v_c), iop_a_dst += 1); + } + } else { + while (true) { + if (self->private_impl.f_chunk_length <= 0) { + if (self->private_impl.f_metadata_fourcc == 1263947851) { + status = wuffs_base__make_status(wuffs_png__error__bad_chunk); + goto exit; + } + goto label__loop__break; + } else if (((uint64_t)(io2_a_src - iop_a_src)) <= 0) { + status = wuffs_base__make_status(wuffs_base__suspension__short_read); + WUFFS_BASE__COROUTINE_SUSPENSION_POINT_MAYBE_SUSPEND(9); + goto label__loop__continue; + } + v_c = wuffs_base__peek_u8be__no_bounds_check(iop_a_src); + if (v_c == 0) { + self->private_impl.f_chunk_length -= 1; + iop_a_src += 1; + goto label__loop__break; + } + v_c2 = WUFFS_PNG__LATIN_1[v_c]; + if (v_c2 == 0) { + status = wuffs_base__make_status(wuffs_png__error__bad_text_chunk_not_latin_1); + goto exit; + } else if (v_c2 <= 127) { + if (((uint64_t)(io2_a_dst - iop_a_dst)) <= 0) { + status = wuffs_base__make_status(wuffs_base__suspension__short_write); + WUFFS_BASE__COROUTINE_SUSPENSION_POINT_MAYBE_SUSPEND(10); + goto label__loop__continue; + } + self->private_impl.f_chunk_length -= 1; + iop_a_src += 1; + (wuffs_base__poke_u8be__no_bounds_check(iop_a_dst, ((uint8_t)(v_c2))), iop_a_dst += 1); + } else { + if (((uint64_t)(io2_a_dst - iop_a_dst)) <= 1) { + status = wuffs_base__make_status(wuffs_base__suspension__short_write); + WUFFS_BASE__COROUTINE_SUSPENSION_POINT_MAYBE_SUSPEND(11); + goto label__loop__continue; + } + self->private_impl.f_chunk_length -= 1; + iop_a_src += 1; + (wuffs_base__poke_u16le__no_bounds_check(iop_a_dst, v_c2), iop_a_dst += 2); + } + } + } + } + label__loop__break:; + if (self->private_impl.f_metadata_fourcc == 1263947851) { + self->private_impl.f_metadata_fourcc = 1263947862; + if (self->private_impl.f_chunk_type == 1951945833) { + if (self->private_impl.f_chunk_length <= 1) { + status = wuffs_base__make_status(wuffs_png__error__bad_chunk); + goto exit; + } + self->private_impl.f_chunk_length -= 2; + { + WUFFS_BASE__COROUTINE_SUSPENSION_POINT(12); + if (WUFFS_BASE__UNLIKELY(iop_a_src == io2_a_src)) { + status = wuffs_base__make_status(wuffs_base__suspension__short_read); + goto suspend; + } + uint8_t t_3 = *iop_a_src++; + v_c = t_3; + } + if (v_c == 0) { + self->private_impl.f_metadata_is_zlib_compressed = false; + } else if (v_c == 1) { + self->private_impl.f_metadata_is_zlib_compressed = true; + } else { + status = wuffs_base__make_status(wuffs_png__error__bad_chunk); + goto exit; + } + { + WUFFS_BASE__COROUTINE_SUSPENSION_POINT(13); + if (WUFFS_BASE__UNLIKELY(iop_a_src == io2_a_src)) { + status = wuffs_base__make_status(wuffs_base__suspension__short_read); + goto suspend; + } + uint8_t t_4 = *iop_a_src++; + v_c = t_4; + } + if ((v_c != 0) && self->private_impl.f_metadata_is_zlib_compressed) { + status = wuffs_base__make_status(wuffs_png__error__unsupported_png_compression_method); + goto exit; + } + self->private_impl.f_metadata_fourcc -= 2; + while (self->private_impl.f_metadata_fourcc != 1263947862) { + self->private_impl.f_metadata_fourcc += 1; + while (true) { + if (self->private_impl.f_chunk_length <= 0) { + status = wuffs_base__make_status(wuffs_png__error__bad_chunk); + goto exit; + } + self->private_impl.f_chunk_length -= 1; + { + WUFFS_BASE__COROUTINE_SUSPENSION_POINT(14); + if (WUFFS_BASE__UNLIKELY(iop_a_src == io2_a_src)) { + status = wuffs_base__make_status(wuffs_base__suspension__short_read); + goto suspend; + } + uint8_t t_5 = *iop_a_src++; + v_c = t_5; + } + if (v_c == 0) { + goto label__0__break; + } + } + label__0__break:; + } + } else if (self->private_impl.f_chunk_type == 1951945850) { + if (self->private_impl.f_chunk_length <= 0) { + status = wuffs_base__make_status(wuffs_png__error__bad_chunk); + goto exit; + } + self->private_impl.f_chunk_length -= 1; + { + WUFFS_BASE__COROUTINE_SUSPENSION_POINT(15); + if (WUFFS_BASE__UNLIKELY(iop_a_src == io2_a_src)) { + status = wuffs_base__make_status(wuffs_base__suspension__short_read); + goto suspend; + } + uint8_t t_6 = *iop_a_src++; + v_c = t_6; + } + if (v_c != 0) { + status = wuffs_base__make_status(wuffs_png__error__unsupported_png_compression_method); + goto exit; + } + self->private_impl.f_metadata_is_zlib_compressed = true; + } + self->private_impl.f_call_sequence &= 239; + status = wuffs_base__make_status(NULL); + goto ok; + } + goto label__goto_done__break; + } + label__goto_done__break:; + if (self->private_impl.f_chunk_length != 0) { + status = wuffs_base__make_status(wuffs_png__error__bad_chunk); + goto exit; + } + self->private_data.s_do_tell_me_more[0].scratch = 4; + WUFFS_BASE__COROUTINE_SUSPENSION_POINT(16); + if (self->private_data.s_do_tell_me_more[0].scratch > ((uint64_t)(io2_a_src - iop_a_src))) { + self->private_data.s_do_tell_me_more[0].scratch -= ((uint64_t)(io2_a_src - iop_a_src)); + iop_a_src = io2_a_src; + status = wuffs_base__make_status(wuffs_base__suspension__short_read); + goto suspend; + } + iop_a_src += self->private_data.s_do_tell_me_more[0].scratch; + self->private_impl.f_metadata_flavor = 0; + self->private_impl.f_metadata_fourcc = 0; + self->private_impl.f_metadata_x = 0; + self->private_impl.f_metadata_y = 0; + self->private_impl.f_metadata_z = 0; + self->private_impl.f_call_sequence &= 239; + status = wuffs_base__make_status(NULL); + goto ok; + + ok: + self->private_impl.p_do_tell_me_more[0] = 0; + goto exit; + } + + goto suspend; + suspend: + self->private_impl.p_do_tell_me_more[0] = wuffs_base__status__is_suspension(&status) ? coro_susp_point : 0; + self->private_data.s_do_tell_me_more[0].v_zlib_status = v_zlib_status; + + goto exit; + exit: + if (a_dst && a_dst->data.ptr) { + a_dst->meta.wi = ((size_t)(iop_a_dst - a_dst->data.ptr)); + } + if (a_src && a_src->data.ptr) { + a_src->meta.ri = ((size_t)(iop_a_src - a_src->data.ptr)); + } + + return status; +} + +// -------- func png.decoder.workbuf_len + +WUFFS_BASE__MAYBE_STATIC wuffs_base__range_ii_u64 +wuffs_png__decoder__workbuf_len( + const wuffs_png__decoder* self) { + if (!self) { + return wuffs_base__utility__empty_range_ii_u64(); + } + if ((self->private_impl.magic != WUFFS_BASE__MAGIC) && + (self->private_impl.magic != WUFFS_BASE__DISABLED)) { + return wuffs_base__utility__empty_range_ii_u64(); + } + + return wuffs_base__utility__make_range_ii_u64(self->private_impl.f_overall_workbuf_length, self->private_impl.f_overall_workbuf_length); +} + +// -------- func png.decoder.filter_and_swizzle + +static wuffs_base__status +wuffs_png__decoder__filter_and_swizzle( + wuffs_png__decoder* self, + wuffs_base__pixel_buffer* a_dst, + wuffs_base__slice_u8 a_workbuf) { + return (*self->private_impl.choosy_filter_and_swizzle)(self, a_dst, a_workbuf); +} + +static wuffs_base__status +wuffs_png__decoder__filter_and_swizzle__choosy_default( + wuffs_png__decoder* self, + wuffs_base__pixel_buffer* a_dst, + wuffs_base__slice_u8 a_workbuf) { + wuffs_base__pixel_format v_dst_pixfmt = {0}; + uint32_t v_dst_bits_per_pixel = 0; + uint64_t v_dst_bytes_per_pixel = 0; + uint64_t v_dst_bytes_per_row0 = 0; + uint64_t v_dst_bytes_per_row1 = 0; + wuffs_base__slice_u8 v_dst_palette = {0}; + wuffs_base__table_u8 v_tab = {0}; + uint32_t v_y = 0; + wuffs_base__slice_u8 v_dst = {0}; + uint8_t v_filter = 0; + wuffs_base__slice_u8 v_curr_row = {0}; + wuffs_base__slice_u8 v_prev_row = {0}; + + v_dst_pixfmt = wuffs_base__pixel_buffer__pixel_format(a_dst); + v_dst_bits_per_pixel = wuffs_base__pixel_format__bits_per_pixel(&v_dst_pixfmt); + if ((v_dst_bits_per_pixel & 7) != 0) { + return wuffs_base__make_status(wuffs_base__error__unsupported_option); + } + v_dst_bytes_per_pixel = ((uint64_t)((v_dst_bits_per_pixel / 8))); + v_dst_bytes_per_row0 = (((uint64_t)(self->private_impl.f_frame_rect_x0)) * v_dst_bytes_per_pixel); + v_dst_bytes_per_row1 = (((uint64_t)(self->private_impl.f_frame_rect_x1)) * v_dst_bytes_per_pixel); + v_dst_palette = wuffs_base__pixel_buffer__palette_or_else(a_dst, wuffs_base__make_slice_u8(self->private_data.f_dst_palette, 1024)); + v_tab = wuffs_base__pixel_buffer__plane(a_dst, 0); + if (v_dst_bytes_per_row1 < ((uint64_t)(v_tab.width))) { + v_tab = wuffs_base__table_u8__subtable_ij(v_tab, + 0, + 0, + v_dst_bytes_per_row1, + ((uint64_t)(v_tab.height))); + } + if (v_dst_bytes_per_row0 < ((uint64_t)(v_tab.width))) { + v_tab = wuffs_base__table_u8__subtable_ij(v_tab, + v_dst_bytes_per_row0, + 0, + ((uint64_t)(v_tab.width)), + ((uint64_t)(v_tab.height))); + } else { + v_tab = wuffs_base__table_u8__subtable_ij(v_tab, + 0, + 0, + 0, + 0); + } + v_y = self->private_impl.f_frame_rect_y0; + while (v_y < self->private_impl.f_frame_rect_y1) { + v_dst = wuffs_base__table_u8__row_u32(v_tab, v_y); + if (1 > ((uint64_t)(a_workbuf.len))) { + return wuffs_base__make_status(wuffs_png__error__internal_error_inconsistent_workbuf_length); + } + v_filter = a_workbuf.ptr[0]; + a_workbuf = wuffs_base__slice_u8__subslice_i(a_workbuf, 1); + if (self->private_impl.f_pass_bytes_per_row > ((uint64_t)(a_workbuf.len))) { + return wuffs_base__make_status(wuffs_png__error__internal_error_inconsistent_workbuf_length); + } + v_curr_row = wuffs_base__slice_u8__subslice_j(a_workbuf, self->private_impl.f_pass_bytes_per_row); + a_workbuf = wuffs_base__slice_u8__subslice_i(a_workbuf, self->private_impl.f_pass_bytes_per_row); + if (v_filter == 0) { + } else if (v_filter == 1) { + wuffs_png__decoder__filter_1(self, v_curr_row); + } else if (v_filter == 2) { + wuffs_png__decoder__filter_2(self, v_curr_row, v_prev_row); + } else if (v_filter == 3) { + wuffs_png__decoder__filter_3(self, v_curr_row, v_prev_row); + } else if (v_filter == 4) { + wuffs_png__decoder__filter_4(self, v_curr_row, v_prev_row); + } else { + return wuffs_base__make_status(wuffs_png__error__bad_filter); + } + wuffs_base__pixel_swizzler__swizzle_interleaved_from_slice(&self->private_impl.f_swizzler, v_dst, v_dst_palette, v_curr_row); + v_prev_row = v_curr_row; + v_y += 1; + } + return wuffs_base__make_status(NULL); +} + +// -------- func png.decoder.filter_and_swizzle_tricky + +static wuffs_base__status +wuffs_png__decoder__filter_and_swizzle_tricky( + wuffs_png__decoder* self, + wuffs_base__pixel_buffer* a_dst, + wuffs_base__slice_u8 a_workbuf) { + wuffs_base__pixel_format v_dst_pixfmt = {0}; + uint32_t v_dst_bits_per_pixel = 0; + uint64_t v_dst_bytes_per_pixel = 0; + uint64_t v_dst_bytes_per_row1 = 0; + wuffs_base__slice_u8 v_dst_palette = {0}; + wuffs_base__table_u8 v_tab = {0}; + uint64_t v_src_bytes_per_pixel = 0; + uint32_t v_x = 0; + uint32_t v_y = 0; + uint64_t v_i = 0; + wuffs_base__slice_u8 v_dst = {0}; + uint8_t v_filter = 0; + wuffs_base__slice_u8 v_s = {0}; + wuffs_base__slice_u8 v_curr_row = {0}; + wuffs_base__slice_u8 v_prev_row = {0}; + uint8_t v_bits_unpacked[8] = {0}; + uint8_t v_bits_packed = 0; + uint8_t v_packs_remaining = 0; + uint8_t v_multiplier = 0; + uint8_t v_shift = 0; + + v_dst_pixfmt = wuffs_base__pixel_buffer__pixel_format(a_dst); + v_dst_bits_per_pixel = wuffs_base__pixel_format__bits_per_pixel(&v_dst_pixfmt); + if ((v_dst_bits_per_pixel & 7) != 0) { + return wuffs_base__make_status(wuffs_base__error__unsupported_option); + } + v_dst_bytes_per_pixel = ((uint64_t)((v_dst_bits_per_pixel / 8))); + v_dst_bytes_per_row1 = (((uint64_t)(self->private_impl.f_frame_rect_x1)) * v_dst_bytes_per_pixel); + v_dst_palette = wuffs_base__pixel_buffer__palette_or_else(a_dst, wuffs_base__make_slice_u8(self->private_data.f_dst_palette, 1024)); + v_tab = wuffs_base__pixel_buffer__plane(a_dst, 0); + v_src_bytes_per_pixel = 1; + if (self->private_impl.f_depth >= 8) { + v_src_bytes_per_pixel = (((uint64_t)(WUFFS_PNG__NUM_CHANNELS[self->private_impl.f_color_type])) * ((uint64_t)((self->private_impl.f_depth >> 3)))); + } + if (self->private_impl.f_chunk_type_array[0] == 73) { + v_y = ((uint32_t)(WUFFS_PNG__INTERLACING[self->private_impl.f_interlace_pass][5])); + } else { + v_y = self->private_impl.f_frame_rect_y0; + } + while (v_y < self->private_impl.f_frame_rect_y1) { + v_dst = wuffs_base__table_u8__row_u32(v_tab, v_y); + if (v_dst_bytes_per_row1 < ((uint64_t)(v_dst.len))) { + v_dst = wuffs_base__slice_u8__subslice_j(v_dst, v_dst_bytes_per_row1); + } + if (1 > ((uint64_t)(a_workbuf.len))) { + return wuffs_base__make_status(wuffs_png__error__internal_error_inconsistent_workbuf_length); + } + v_filter = a_workbuf.ptr[0]; + a_workbuf = wuffs_base__slice_u8__subslice_i(a_workbuf, 1); + if (self->private_impl.f_pass_bytes_per_row > ((uint64_t)(a_workbuf.len))) { + return wuffs_base__make_status(wuffs_png__error__internal_error_inconsistent_workbuf_length); + } + v_curr_row = wuffs_base__slice_u8__subslice_j(a_workbuf, self->private_impl.f_pass_bytes_per_row); + a_workbuf = wuffs_base__slice_u8__subslice_i(a_workbuf, self->private_impl.f_pass_bytes_per_row); + if (v_filter == 0) { + } else if (v_filter == 1) { + wuffs_png__decoder__filter_1(self, v_curr_row); + } else if (v_filter == 2) { + wuffs_png__decoder__filter_2(self, v_curr_row, v_prev_row); + } else if (v_filter == 3) { + wuffs_png__decoder__filter_3(self, v_curr_row, v_prev_row); + } else if (v_filter == 4) { + wuffs_png__decoder__filter_4(self, v_curr_row, v_prev_row); + } else { + return wuffs_base__make_status(wuffs_png__error__bad_filter); + } + v_s = v_curr_row; + if (self->private_impl.f_chunk_type_array[0] == 73) { + v_x = ((uint32_t)(WUFFS_PNG__INTERLACING[self->private_impl.f_interlace_pass][2])); + } else { + v_x = self->private_impl.f_frame_rect_x0; + } + if (self->private_impl.f_depth == 8) { + while (v_x < self->private_impl.f_frame_rect_x1) { + v_i = (((uint64_t)(v_x)) * v_dst_bytes_per_pixel); + if (v_i <= ((uint64_t)(v_dst.len))) { + if (self->private_impl.f_color_type == 4) { + if (2 <= ((uint64_t)(v_s.len))) { + v_bits_unpacked[0] = v_s.ptr[0]; + v_bits_unpacked[1] = v_s.ptr[0]; + v_bits_unpacked[2] = v_s.ptr[0]; + v_bits_unpacked[3] = v_s.ptr[1]; + v_s = wuffs_base__slice_u8__subslice_i(v_s, 2); + wuffs_base__pixel_swizzler__swizzle_interleaved_from_slice(&self->private_impl.f_swizzler, wuffs_base__slice_u8__subslice_i(v_dst, v_i), v_dst_palette, wuffs_base__make_slice_u8(v_bits_unpacked, 4)); + } + } else if (((uint32_t)((self->private_impl.f_remap_transparency & 4294967295))) != 0) { + if (self->private_impl.f_color_type == 0) { + if (1 <= ((uint64_t)(v_s.len))) { + v_bits_unpacked[0] = v_s.ptr[0]; + v_bits_unpacked[1] = v_s.ptr[0]; + v_bits_unpacked[2] = v_s.ptr[0]; + v_bits_unpacked[3] = 255; + v_s = wuffs_base__slice_u8__subslice_i(v_s, 1); + if (((uint32_t)((self->private_impl.f_remap_transparency & 4294967295))) == ((((uint32_t)(v_bits_unpacked[0])) << 0) | + (((uint32_t)(v_bits_unpacked[1])) << 8) | + (((uint32_t)(v_bits_unpacked[2])) << 16) | + (((uint32_t)(v_bits_unpacked[3])) << 24))) { + v_bits_unpacked[0] = 0; + v_bits_unpacked[1] = 0; + v_bits_unpacked[2] = 0; + v_bits_unpacked[3] = 0; + } + wuffs_base__pixel_swizzler__swizzle_interleaved_from_slice(&self->private_impl.f_swizzler, wuffs_base__slice_u8__subslice_i(v_dst, v_i), v_dst_palette, wuffs_base__make_slice_u8(v_bits_unpacked, 4)); + } + } else { + if (3 <= ((uint64_t)(v_s.len))) { + v_bits_unpacked[0] = v_s.ptr[2]; + v_bits_unpacked[1] = v_s.ptr[1]; + v_bits_unpacked[2] = v_s.ptr[0]; + v_bits_unpacked[3] = 255; + v_s = wuffs_base__slice_u8__subslice_i(v_s, 3); + if (((uint32_t)((self->private_impl.f_remap_transparency & 4294967295))) == ((((uint32_t)(v_bits_unpacked[0])) << 0) | + (((uint32_t)(v_bits_unpacked[1])) << 8) | + (((uint32_t)(v_bits_unpacked[2])) << 16) | + (((uint32_t)(v_bits_unpacked[3])) << 24))) { + v_bits_unpacked[0] = 0; + v_bits_unpacked[1] = 0; + v_bits_unpacked[2] = 0; + v_bits_unpacked[3] = 0; + } + wuffs_base__pixel_swizzler__swizzle_interleaved_from_slice(&self->private_impl.f_swizzler, wuffs_base__slice_u8__subslice_i(v_dst, v_i), v_dst_palette, wuffs_base__make_slice_u8(v_bits_unpacked, 4)); + } + } + } else if (v_src_bytes_per_pixel <= ((uint64_t)(v_s.len))) { + wuffs_base__pixel_swizzler__swizzle_interleaved_from_slice(&self->private_impl.f_swizzler, wuffs_base__slice_u8__subslice_i(v_dst, v_i), v_dst_palette, wuffs_base__slice_u8__subslice_j(v_s, v_src_bytes_per_pixel)); + v_s = wuffs_base__slice_u8__subslice_i(v_s, v_src_bytes_per_pixel); + } + } + v_x += (((uint32_t)(1)) << WUFFS_PNG__INTERLACING[self->private_impl.f_interlace_pass][0]); + } + } else if (self->private_impl.f_depth < 8) { + v_multiplier = 1; + if (self->private_impl.f_color_type == 0) { + v_multiplier = WUFFS_PNG__LOW_BIT_DEPTH_MULTIPLIERS[self->private_impl.f_depth]; + } + v_shift = ((8 - self->private_impl.f_depth) & 7); + v_packs_remaining = 0; + while (v_x < self->private_impl.f_frame_rect_x1) { + v_i = (((uint64_t)(v_x)) * v_dst_bytes_per_pixel); + if (v_i <= ((uint64_t)(v_dst.len))) { + if ((v_packs_remaining == 0) && (1 <= ((uint64_t)(v_s.len)))) { + v_packs_remaining = WUFFS_PNG__LOW_BIT_DEPTH_NUM_PACKS[self->private_impl.f_depth]; + v_bits_packed = v_s.ptr[0]; + v_s = wuffs_base__slice_u8__subslice_i(v_s, 1); + } + v_bits_unpacked[0] = ((uint8_t)((v_bits_packed >> v_shift) * v_multiplier)); + v_bits_packed = ((uint8_t)(v_bits_packed << self->private_impl.f_depth)); + v_packs_remaining = ((uint8_t)(v_packs_remaining - 1)); + if (((uint32_t)((self->private_impl.f_remap_transparency & 4294967295))) != 0) { + v_bits_unpacked[1] = v_bits_unpacked[0]; + v_bits_unpacked[2] = v_bits_unpacked[0]; + v_bits_unpacked[3] = 255; + if (((uint32_t)((self->private_impl.f_remap_transparency & 4294967295))) == ((((uint32_t)(v_bits_unpacked[0])) << 0) | + (((uint32_t)(v_bits_unpacked[1])) << 8) | + (((uint32_t)(v_bits_unpacked[2])) << 16) | + (((uint32_t)(v_bits_unpacked[3])) << 24))) { + v_bits_unpacked[0] = 0; + v_bits_unpacked[1] = 0; + v_bits_unpacked[2] = 0; + v_bits_unpacked[3] = 0; + } + wuffs_base__pixel_swizzler__swizzle_interleaved_from_slice(&self->private_impl.f_swizzler, wuffs_base__slice_u8__subslice_i(v_dst, v_i), v_dst_palette, wuffs_base__make_slice_u8(v_bits_unpacked, 4)); + } else { + wuffs_base__pixel_swizzler__swizzle_interleaved_from_slice(&self->private_impl.f_swizzler, wuffs_base__slice_u8__subslice_i(v_dst, v_i), v_dst_palette, wuffs_base__make_slice_u8(v_bits_unpacked, 1)); + } + } + v_x += (((uint32_t)(1)) << WUFFS_PNG__INTERLACING[self->private_impl.f_interlace_pass][0]); + } + } else { + while (v_x < self->private_impl.f_frame_rect_x1) { + v_i = (((uint64_t)(v_x)) * v_dst_bytes_per_pixel); + if (v_i <= ((uint64_t)(v_dst.len))) { + if (self->private_impl.f_color_type == 0) { + if (2 <= ((uint64_t)(v_s.len))) { + v_bits_unpacked[0] = v_s.ptr[1]; + v_bits_unpacked[1] = v_s.ptr[0]; + v_bits_unpacked[2] = v_s.ptr[1]; + v_bits_unpacked[3] = v_s.ptr[0]; + v_bits_unpacked[4] = v_s.ptr[1]; + v_bits_unpacked[5] = v_s.ptr[0]; + v_bits_unpacked[6] = 255; + v_bits_unpacked[7] = 255; + v_s = wuffs_base__slice_u8__subslice_i(v_s, 2); + if (self->private_impl.f_remap_transparency == ((((uint64_t)(v_bits_unpacked[0])) << 0) | + (((uint64_t)(v_bits_unpacked[1])) << 8) | + (((uint64_t)(v_bits_unpacked[2])) << 16) | + (((uint64_t)(v_bits_unpacked[3])) << 24) | + (((uint64_t)(v_bits_unpacked[4])) << 32) | + (((uint64_t)(v_bits_unpacked[5])) << 40) | + (((uint64_t)(v_bits_unpacked[6])) << 48) | + (((uint64_t)(v_bits_unpacked[7])) << 56))) { + v_bits_unpacked[0] = 0; + v_bits_unpacked[1] = 0; + v_bits_unpacked[2] = 0; + v_bits_unpacked[3] = 0; + v_bits_unpacked[4] = 0; + v_bits_unpacked[5] = 0; + v_bits_unpacked[6] = 0; + v_bits_unpacked[7] = 0; + } + } + } else if (self->private_impl.f_color_type == 2) { + if (6 <= ((uint64_t)(v_s.len))) { + v_bits_unpacked[0] = v_s.ptr[5]; + v_bits_unpacked[1] = v_s.ptr[4]; + v_bits_unpacked[2] = v_s.ptr[3]; + v_bits_unpacked[3] = v_s.ptr[2]; + v_bits_unpacked[4] = v_s.ptr[1]; + v_bits_unpacked[5] = v_s.ptr[0]; + v_bits_unpacked[6] = 255; + v_bits_unpacked[7] = 255; + v_s = wuffs_base__slice_u8__subslice_i(v_s, 6); + if (self->private_impl.f_remap_transparency == ((((uint64_t)(v_bits_unpacked[0])) << 0) | + (((uint64_t)(v_bits_unpacked[1])) << 8) | + (((uint64_t)(v_bits_unpacked[2])) << 16) | + (((uint64_t)(v_bits_unpacked[3])) << 24) | + (((uint64_t)(v_bits_unpacked[4])) << 32) | + (((uint64_t)(v_bits_unpacked[5])) << 40) | + (((uint64_t)(v_bits_unpacked[6])) << 48) | + (((uint64_t)(v_bits_unpacked[7])) << 56))) { + v_bits_unpacked[0] = 0; + v_bits_unpacked[1] = 0; + v_bits_unpacked[2] = 0; + v_bits_unpacked[3] = 0; + v_bits_unpacked[4] = 0; + v_bits_unpacked[5] = 0; + v_bits_unpacked[6] = 0; + v_bits_unpacked[7] = 0; + } + } + } else if (self->private_impl.f_color_type == 4) { + if (4 <= ((uint64_t)(v_s.len))) { + v_bits_unpacked[0] = v_s.ptr[1]; + v_bits_unpacked[1] = v_s.ptr[0]; + v_bits_unpacked[2] = v_s.ptr[1]; + v_bits_unpacked[3] = v_s.ptr[0]; + v_bits_unpacked[4] = v_s.ptr[1]; + v_bits_unpacked[5] = v_s.ptr[0]; + v_bits_unpacked[6] = v_s.ptr[3]; + v_bits_unpacked[7] = v_s.ptr[2]; + v_s = wuffs_base__slice_u8__subslice_i(v_s, 4); + } + } else { + if (8 <= ((uint64_t)(v_s.len))) { + v_bits_unpacked[0] = v_s.ptr[5]; + v_bits_unpacked[1] = v_s.ptr[4]; + v_bits_unpacked[2] = v_s.ptr[3]; + v_bits_unpacked[3] = v_s.ptr[2]; + v_bits_unpacked[4] = v_s.ptr[1]; + v_bits_unpacked[5] = v_s.ptr[0]; + v_bits_unpacked[6] = v_s.ptr[7]; + v_bits_unpacked[7] = v_s.ptr[6]; + v_s = wuffs_base__slice_u8__subslice_i(v_s, 8); + } + } + wuffs_base__pixel_swizzler__swizzle_interleaved_from_slice(&self->private_impl.f_swizzler, wuffs_base__slice_u8__subslice_i(v_dst, v_i), v_dst_palette, wuffs_base__make_slice_u8(v_bits_unpacked, 8)); + } + v_x += (((uint32_t)(1)) << WUFFS_PNG__INTERLACING[self->private_impl.f_interlace_pass][0]); + } + } + v_prev_row = v_curr_row; + v_y += (((uint32_t)(1)) << WUFFS_PNG__INTERLACING[self->private_impl.f_interlace_pass][3]); + } + return wuffs_base__make_status(NULL); +} + +#endif // !defined(WUFFS_CONFIG__MODULES) || defined(WUFFS_CONFIG__MODULE__PNG) + +#if !defined(WUFFS_CONFIG__MODULES) || defined(WUFFS_CONFIG__MODULE__TGA) + +// ---------------- Status Codes Implementations + +const char wuffs_tga__error__bad_header[] = "#tga: bad header"; +const char wuffs_tga__error__bad_run_length_encoding[] = "#tga: bad run length encoding"; +const char wuffs_tga__error__truncated_input[] = "#tga: truncated input"; +const char wuffs_tga__error__unsupported_tga_file[] = "#tga: unsupported TGA file"; + +// ---------------- Private Consts + +// ---------------- Private Initializer Prototypes + +// ---------------- Private Function Prototypes + +static wuffs_base__status +wuffs_tga__decoder__do_decode_image_config( + wuffs_tga__decoder* self, + wuffs_base__image_config* a_dst, + wuffs_base__io_buffer* a_src); + +static wuffs_base__status +wuffs_tga__decoder__do_decode_frame_config( + wuffs_tga__decoder* self, + wuffs_base__frame_config* a_dst, + wuffs_base__io_buffer* a_src); + +static wuffs_base__status +wuffs_tga__decoder__do_decode_frame( + wuffs_tga__decoder* self, + wuffs_base__pixel_buffer* a_dst, + wuffs_base__io_buffer* a_src, + wuffs_base__pixel_blend a_blend, + wuffs_base__slice_u8 a_workbuf, + wuffs_base__decode_frame_options* a_opts); + +// ---------------- VTables + +const wuffs_base__image_decoder__func_ptrs +wuffs_tga__decoder__func_ptrs_for__wuffs_base__image_decoder = { + (wuffs_base__status(*)(void*, + wuffs_base__pixel_buffer*, + wuffs_base__io_buffer*, + wuffs_base__pixel_blend, + wuffs_base__slice_u8, + wuffs_base__decode_frame_options*))(&wuffs_tga__decoder__decode_frame), + (wuffs_base__status(*)(void*, + wuffs_base__frame_config*, + wuffs_base__io_buffer*))(&wuffs_tga__decoder__decode_frame_config), + (wuffs_base__status(*)(void*, + wuffs_base__image_config*, + wuffs_base__io_buffer*))(&wuffs_tga__decoder__decode_image_config), + (wuffs_base__rect_ie_u32(*)(const void*))(&wuffs_tga__decoder__frame_dirty_rect), + (uint32_t(*)(const void*))(&wuffs_tga__decoder__num_animation_loops), + (uint64_t(*)(const void*))(&wuffs_tga__decoder__num_decoded_frame_configs), + (uint64_t(*)(const void*))(&wuffs_tga__decoder__num_decoded_frames), + (wuffs_base__status(*)(void*, + uint64_t, + uint64_t))(&wuffs_tga__decoder__restart_frame), + (wuffs_base__empty_struct(*)(void*, + uint32_t, + bool))(&wuffs_tga__decoder__set_quirk_enabled), + (wuffs_base__empty_struct(*)(void*, + uint32_t, + bool))(&wuffs_tga__decoder__set_report_metadata), + (wuffs_base__status(*)(void*, + wuffs_base__io_buffer*, + wuffs_base__more_information*, + wuffs_base__io_buffer*))(&wuffs_tga__decoder__tell_me_more), + (wuffs_base__range_ii_u64(*)(const void*))(&wuffs_tga__decoder__workbuf_len), +}; + +// ---------------- Initializer Implementations + +wuffs_base__status WUFFS_BASE__WARN_UNUSED_RESULT +wuffs_tga__decoder__initialize( + wuffs_tga__decoder* self, + size_t sizeof_star_self, + uint64_t wuffs_version, + uint32_t options){ + if (!self) { + return wuffs_base__make_status(wuffs_base__error__bad_receiver); + } + if (sizeof(*self) != sizeof_star_self) { + return wuffs_base__make_status(wuffs_base__error__bad_sizeof_receiver); + } + if (((wuffs_version >> 32) != WUFFS_VERSION_MAJOR) || + (((wuffs_version >> 16) & 0xFFFF) > WUFFS_VERSION_MINOR)) { + return wuffs_base__make_status(wuffs_base__error__bad_wuffs_version); + } + + if ((options & WUFFS_INITIALIZE__ALREADY_ZEROED) != 0) { + // The whole point of this if-check is to detect an uninitialized *self. + // We disable the warning on GCC. Clang-5.0 does not have this warning. +#if !defined(__clang__) && defined(__GNUC__) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wmaybe-uninitialized" +#endif + if (self->private_impl.magic != 0) { + return wuffs_base__make_status(wuffs_base__error__initialize_falsely_claimed_already_zeroed); + } +#if !defined(__clang__) && defined(__GNUC__) +#pragma GCC diagnostic pop +#endif + } else { + if ((options & WUFFS_INITIALIZE__LEAVE_INTERNAL_BUFFERS_UNINITIALIZED) == 0) { + memset(self, 0, sizeof(*self)); + options |= WUFFS_INITIALIZE__ALREADY_ZEROED; + } else { + memset(&(self->private_impl), 0, sizeof(self->private_impl)); + } + } + + self->private_impl.magic = WUFFS_BASE__MAGIC; + self->private_impl.vtable_for__wuffs_base__image_decoder.vtable_name = + wuffs_base__image_decoder__vtable_name; + self->private_impl.vtable_for__wuffs_base__image_decoder.function_pointers = + (const void*)(&wuffs_tga__decoder__func_ptrs_for__wuffs_base__image_decoder); + return wuffs_base__make_status(NULL); +} + +wuffs_tga__decoder* +wuffs_tga__decoder__alloc() { + wuffs_tga__decoder* x = + (wuffs_tga__decoder*)(calloc(sizeof(wuffs_tga__decoder), 1)); + if (!x) { + return NULL; + } + if (wuffs_tga__decoder__initialize( + x, sizeof(wuffs_tga__decoder), WUFFS_VERSION, WUFFS_INITIALIZE__ALREADY_ZEROED).repr) { + free(x); + return NULL; + } + return x; +} + +size_t +sizeof__wuffs_tga__decoder() { + return sizeof(wuffs_tga__decoder); +} + +// ---------------- Function Implementations + +// -------- func tga.decoder.set_quirk_enabled + +WUFFS_BASE__MAYBE_STATIC wuffs_base__empty_struct +wuffs_tga__decoder__set_quirk_enabled( + wuffs_tga__decoder* self, + uint32_t a_quirk, + bool a_enabled) { + return wuffs_base__make_empty_struct(); +} + +// -------- func tga.decoder.decode_image_config + +WUFFS_BASE__MAYBE_STATIC wuffs_base__status +wuffs_tga__decoder__decode_image_config( + wuffs_tga__decoder* self, + wuffs_base__image_config* a_dst, + wuffs_base__io_buffer* a_src) { + if (!self) { + return wuffs_base__make_status(wuffs_base__error__bad_receiver); + } + if (self->private_impl.magic != WUFFS_BASE__MAGIC) { + return wuffs_base__make_status( + (self->private_impl.magic == WUFFS_BASE__DISABLED) + ? wuffs_base__error__disabled_by_previous_error + : wuffs_base__error__initialize_not_called); + } + if (!a_src) { + self->private_impl.magic = WUFFS_BASE__DISABLED; + return wuffs_base__make_status(wuffs_base__error__bad_argument); + } + if ((self->private_impl.active_coroutine != 0) && + (self->private_impl.active_coroutine != 1)) { + self->private_impl.magic = WUFFS_BASE__DISABLED; + return wuffs_base__make_status(wuffs_base__error__interleaved_coroutine_calls); + } + self->private_impl.active_coroutine = 0; + wuffs_base__status status = wuffs_base__make_status(NULL); + + wuffs_base__status v_status = wuffs_base__make_status(NULL); + + uint32_t coro_susp_point = self->private_impl.p_decode_image_config[0]; + switch (coro_susp_point) { + WUFFS_BASE__COROUTINE_SUSPENSION_POINT_0; + + while (true) { + { + wuffs_base__status t_0 = wuffs_tga__decoder__do_decode_image_config(self, a_dst, a_src); + v_status = t_0; + } + if ((v_status.repr == wuffs_base__suspension__short_read) && (a_src && a_src->meta.closed)) { + status = wuffs_base__make_status(wuffs_tga__error__truncated_input); + goto exit; + } + status = v_status; + WUFFS_BASE__COROUTINE_SUSPENSION_POINT_MAYBE_SUSPEND(1); + } + + ok: + self->private_impl.p_decode_image_config[0] = 0; + goto exit; + } + + goto suspend; + suspend: + self->private_impl.p_decode_image_config[0] = wuffs_base__status__is_suspension(&status) ? coro_susp_point : 0; + self->private_impl.active_coroutine = wuffs_base__status__is_suspension(&status) ? 1 : 0; + + goto exit; + exit: + if (wuffs_base__status__is_error(&status)) { + self->private_impl.magic = WUFFS_BASE__DISABLED; + } + return status; +} + +// -------- func tga.decoder.do_decode_image_config + +static wuffs_base__status +wuffs_tga__decoder__do_decode_image_config( + wuffs_tga__decoder* self, + wuffs_base__image_config* a_dst, + wuffs_base__io_buffer* a_src) { + wuffs_base__status status = wuffs_base__make_status(NULL); + + uint32_t v_c = 0; + uint32_t v_c5 = 0; + uint32_t v_i = 0; + + const uint8_t* iop_a_src = NULL; + const uint8_t* io0_a_src WUFFS_BASE__POTENTIALLY_UNUSED = NULL; + const uint8_t* io1_a_src WUFFS_BASE__POTENTIALLY_UNUSED = NULL; + const uint8_t* io2_a_src WUFFS_BASE__POTENTIALLY_UNUSED = NULL; + if (a_src && a_src->data.ptr) { + io0_a_src = a_src->data.ptr; + io1_a_src = io0_a_src + a_src->meta.ri; + iop_a_src = io1_a_src; + io2_a_src = io0_a_src + a_src->meta.wi; + } + + uint32_t coro_susp_point = self->private_impl.p_do_decode_image_config[0]; + if (coro_susp_point) { + v_i = self->private_data.s_do_decode_image_config[0].v_i; + } + switch (coro_susp_point) { + WUFFS_BASE__COROUTINE_SUSPENSION_POINT_0; + + if (self->private_impl.f_call_sequence != 0) { + status = wuffs_base__make_status(wuffs_base__error__bad_call_sequence); + goto exit; + } + { + WUFFS_BASE__COROUTINE_SUSPENSION_POINT(1); + if (WUFFS_BASE__UNLIKELY(iop_a_src == io2_a_src)) { + status = wuffs_base__make_status(wuffs_base__suspension__short_read); + goto suspend; + } + uint8_t t_0 = *iop_a_src++; + self->private_impl.f_header_id_length = t_0; + } + { + WUFFS_BASE__COROUTINE_SUSPENSION_POINT(2); + if (WUFFS_BASE__UNLIKELY(iop_a_src == io2_a_src)) { + status = wuffs_base__make_status(wuffs_base__suspension__short_read); + goto suspend; + } + uint8_t t_1 = *iop_a_src++; + self->private_impl.f_header_color_map_type = t_1; + } + if (self->private_impl.f_header_color_map_type > 1) { + status = wuffs_base__make_status(wuffs_tga__error__bad_header); + goto exit; + } + { + WUFFS_BASE__COROUTINE_SUSPENSION_POINT(3); + if (WUFFS_BASE__UNLIKELY(iop_a_src == io2_a_src)) { + status = wuffs_base__make_status(wuffs_base__suspension__short_read); + goto suspend; + } + uint8_t t_2 = *iop_a_src++; + self->private_impl.f_header_image_type = t_2; + } + if ((self->private_impl.f_header_image_type == 1) || + (self->private_impl.f_header_image_type == 2) || + (self->private_impl.f_header_image_type == 3) || + (self->private_impl.f_header_image_type == 9) || + (self->private_impl.f_header_image_type == 10) || + (self->private_impl.f_header_image_type == 11)) { + } else { + status = wuffs_base__make_status(wuffs_tga__error__bad_header); + goto exit; + } + { + WUFFS_BASE__COROUTINE_SUSPENSION_POINT(4); + uint16_t t_3; + if (WUFFS_BASE__LIKELY(io2_a_src - iop_a_src >= 2)) { + t_3 = wuffs_base__peek_u16le__no_bounds_check(iop_a_src); + iop_a_src += 2; + } else { + self->private_data.s_do_decode_image_config[0].scratch = 0; + WUFFS_BASE__COROUTINE_SUSPENSION_POINT(5); + while (true) { + if (WUFFS_BASE__UNLIKELY(iop_a_src == io2_a_src)) { + status = wuffs_base__make_status(wuffs_base__suspension__short_read); + goto suspend; + } + uint64_t* scratch = &self->private_data.s_do_decode_image_config[0].scratch; + uint32_t num_bits_3 = ((uint32_t)(*scratch >> 56)); + *scratch <<= 8; + *scratch >>= 8; + *scratch |= ((uint64_t)(*iop_a_src++)) << num_bits_3; + if (num_bits_3 == 8) { + t_3 = ((uint16_t)(*scratch)); + break; + } + num_bits_3 += 8; + *scratch |= ((uint64_t)(num_bits_3)) << 56; + } + } + self->private_impl.f_header_color_map_first_entry_index = t_3; + } + { + WUFFS_BASE__COROUTINE_SUSPENSION_POINT(6); + uint16_t t_4; + if (WUFFS_BASE__LIKELY(io2_a_src - iop_a_src >= 2)) { + t_4 = wuffs_base__peek_u16le__no_bounds_check(iop_a_src); + iop_a_src += 2; + } else { + self->private_data.s_do_decode_image_config[0].scratch = 0; + WUFFS_BASE__COROUTINE_SUSPENSION_POINT(7); + while (true) { + if (WUFFS_BASE__UNLIKELY(iop_a_src == io2_a_src)) { + status = wuffs_base__make_status(wuffs_base__suspension__short_read); + goto suspend; + } + uint64_t* scratch = &self->private_data.s_do_decode_image_config[0].scratch; + uint32_t num_bits_4 = ((uint32_t)(*scratch >> 56)); + *scratch <<= 8; + *scratch >>= 8; + *scratch |= ((uint64_t)(*iop_a_src++)) << num_bits_4; + if (num_bits_4 == 8) { + t_4 = ((uint16_t)(*scratch)); + break; + } + num_bits_4 += 8; + *scratch |= ((uint64_t)(num_bits_4)) << 56; + } + } + self->private_impl.f_header_color_map_length = t_4; + } + { + WUFFS_BASE__COROUTINE_SUSPENSION_POINT(8); + if (WUFFS_BASE__UNLIKELY(iop_a_src == io2_a_src)) { + status = wuffs_base__make_status(wuffs_base__suspension__short_read); + goto suspend; + } + uint8_t t_5 = *iop_a_src++; + self->private_impl.f_header_color_map_entry_size = t_5; + } + if (self->private_impl.f_header_color_map_type != 0) { + if ((self->private_impl.f_header_color_map_first_entry_index != 0) || (self->private_impl.f_header_color_map_length > 256)) { + status = wuffs_base__make_status(wuffs_tga__error__unsupported_tga_file); + goto exit; + } else if ((self->private_impl.f_header_color_map_entry_size != 15) && + (self->private_impl.f_header_color_map_entry_size != 16) && + (self->private_impl.f_header_color_map_entry_size != 24) && + (self->private_impl.f_header_color_map_entry_size != 32)) { + status = wuffs_base__make_status(wuffs_tga__error__bad_header); + goto exit; + } + } else { + if ((self->private_impl.f_header_color_map_first_entry_index != 0) || (self->private_impl.f_header_color_map_length != 0) || (self->private_impl.f_header_color_map_entry_size != 0)) { + status = wuffs_base__make_status(wuffs_tga__error__bad_header); + goto exit; + } + } + self->private_data.s_do_decode_image_config[0].scratch = 4; + WUFFS_BASE__COROUTINE_SUSPENSION_POINT(9); + if (self->private_data.s_do_decode_image_config[0].scratch > ((uint64_t)(io2_a_src - iop_a_src))) { + self->private_data.s_do_decode_image_config[0].scratch -= ((uint64_t)(io2_a_src - iop_a_src)); + iop_a_src = io2_a_src; + status = wuffs_base__make_status(wuffs_base__suspension__short_read); + goto suspend; + } + iop_a_src += self->private_data.s_do_decode_image_config[0].scratch; + { + WUFFS_BASE__COROUTINE_SUSPENSION_POINT(10); + uint32_t t_6; + if (WUFFS_BASE__LIKELY(io2_a_src - iop_a_src >= 2)) { + t_6 = ((uint32_t)(wuffs_base__peek_u16le__no_bounds_check(iop_a_src))); + iop_a_src += 2; + } else { + self->private_data.s_do_decode_image_config[0].scratch = 0; + WUFFS_BASE__COROUTINE_SUSPENSION_POINT(11); + while (true) { + if (WUFFS_BASE__UNLIKELY(iop_a_src == io2_a_src)) { + status = wuffs_base__make_status(wuffs_base__suspension__short_read); + goto suspend; + } + uint64_t* scratch = &self->private_data.s_do_decode_image_config[0].scratch; + uint32_t num_bits_6 = ((uint32_t)(*scratch >> 56)); + *scratch <<= 8; + *scratch >>= 8; + *scratch |= ((uint64_t)(*iop_a_src++)) << num_bits_6; + if (num_bits_6 == 8) { + t_6 = ((uint32_t)(*scratch)); + break; + } + num_bits_6 += 8; + *scratch |= ((uint64_t)(num_bits_6)) << 56; + } + } + self->private_impl.f_width = t_6; + } + { + WUFFS_BASE__COROUTINE_SUSPENSION_POINT(12); + uint32_t t_7; + if (WUFFS_BASE__LIKELY(io2_a_src - iop_a_src >= 2)) { + t_7 = ((uint32_t)(wuffs_base__peek_u16le__no_bounds_check(iop_a_src))); + iop_a_src += 2; + } else { + self->private_data.s_do_decode_image_config[0].scratch = 0; + WUFFS_BASE__COROUTINE_SUSPENSION_POINT(13); + while (true) { + if (WUFFS_BASE__UNLIKELY(iop_a_src == io2_a_src)) { + status = wuffs_base__make_status(wuffs_base__suspension__short_read); + goto suspend; + } + uint64_t* scratch = &self->private_data.s_do_decode_image_config[0].scratch; + uint32_t num_bits_7 = ((uint32_t)(*scratch >> 56)); + *scratch <<= 8; + *scratch >>= 8; + *scratch |= ((uint64_t)(*iop_a_src++)) << num_bits_7; + if (num_bits_7 == 8) { + t_7 = ((uint32_t)(*scratch)); + break; + } + num_bits_7 += 8; + *scratch |= ((uint64_t)(num_bits_7)) << 56; + } + } + self->private_impl.f_height = t_7; + } + { + WUFFS_BASE__COROUTINE_SUSPENSION_POINT(14); + if (WUFFS_BASE__UNLIKELY(iop_a_src == io2_a_src)) { + status = wuffs_base__make_status(wuffs_base__suspension__short_read); + goto suspend; + } + uint8_t t_8 = *iop_a_src++; + self->private_impl.f_header_pixel_depth = t_8; + } + if ((self->private_impl.f_header_pixel_depth != 1) && + (self->private_impl.f_header_pixel_depth != 8) && + (self->private_impl.f_header_pixel_depth != 15) && + (self->private_impl.f_header_pixel_depth != 16) && + (self->private_impl.f_header_pixel_depth != 24) && + (self->private_impl.f_header_pixel_depth != 32)) { + status = wuffs_base__make_status(wuffs_tga__error__bad_header); + goto exit; + } + if ((self->private_impl.f_header_image_type | 8) == 9) { + self->private_impl.f_scratch_bytes_per_pixel = 1; + self->private_impl.f_src_bytes_per_pixel = 1; + self->private_impl.f_src_pixfmt = 2164523016; + self->private_impl.f_opaque = ((self->private_impl.f_header_color_map_entry_size == 15) || (self->private_impl.f_header_color_map_entry_size == 24)); + } else if ((self->private_impl.f_header_image_type | 8) == 10) { + if ((self->private_impl.f_header_pixel_depth == 15) || (self->private_impl.f_header_pixel_depth == 16)) { + self->private_impl.f_scratch_bytes_per_pixel = 4; + self->private_impl.f_src_bytes_per_pixel = 0; + self->private_impl.f_src_pixfmt = 2164295816; + } else if (self->private_impl.f_header_pixel_depth == 24) { + self->private_impl.f_scratch_bytes_per_pixel = 3; + self->private_impl.f_src_bytes_per_pixel = 3; + self->private_impl.f_src_pixfmt = 2147485832; + self->private_impl.f_opaque = true; + } else if (self->private_impl.f_header_pixel_depth == 32) { + self->private_impl.f_scratch_bytes_per_pixel = 4; + self->private_impl.f_src_bytes_per_pixel = 4; + self->private_impl.f_src_pixfmt = 2164295816; + } else { + status = wuffs_base__make_status(wuffs_tga__error__unsupported_tga_file); + goto exit; + } + } else { + if (self->private_impl.f_header_pixel_depth == 8) { + self->private_impl.f_scratch_bytes_per_pixel = 1; + self->private_impl.f_src_bytes_per_pixel = 1; + self->private_impl.f_src_pixfmt = 536870920; + self->private_impl.f_opaque = true; + } else { + status = wuffs_base__make_status(wuffs_tga__error__unsupported_tga_file); + goto exit; + } + } + { + WUFFS_BASE__COROUTINE_SUSPENSION_POINT(15); + if (WUFFS_BASE__UNLIKELY(iop_a_src == io2_a_src)) { + status = wuffs_base__make_status(wuffs_base__suspension__short_read); + goto suspend; + } + uint8_t t_9 = *iop_a_src++; + self->private_impl.f_header_image_descriptor = t_9; + } + if ((self->private_impl.f_header_image_descriptor & 16) != 0) { + status = wuffs_base__make_status(wuffs_tga__error__unsupported_tga_file); + goto exit; + } + self->private_data.s_do_decode_image_config[0].scratch = ((uint32_t)(self->private_impl.f_header_id_length)); + WUFFS_BASE__COROUTINE_SUSPENSION_POINT(16); + if (self->private_data.s_do_decode_image_config[0].scratch > ((uint64_t)(io2_a_src - iop_a_src))) { + self->private_data.s_do_decode_image_config[0].scratch -= ((uint64_t)(io2_a_src - iop_a_src)); + iop_a_src = io2_a_src; + status = wuffs_base__make_status(wuffs_base__suspension__short_read); + goto suspend; + } + iop_a_src += self->private_data.s_do_decode_image_config[0].scratch; + if (self->private_impl.f_header_color_map_type != 0) { + while (v_i < ((uint32_t)(self->private_impl.f_header_color_map_length))) { + if (self->private_impl.f_header_color_map_entry_size == 24) { + { + WUFFS_BASE__COROUTINE_SUSPENSION_POINT(17); + uint32_t t_10; + if (WUFFS_BASE__LIKELY(io2_a_src - iop_a_src >= 3)) { + t_10 = ((uint32_t)(wuffs_base__peek_u24le__no_bounds_check(iop_a_src))); + iop_a_src += 3; + } else { + self->private_data.s_do_decode_image_config[0].scratch = 0; + WUFFS_BASE__COROUTINE_SUSPENSION_POINT(18); + while (true) { + if (WUFFS_BASE__UNLIKELY(iop_a_src == io2_a_src)) { + status = wuffs_base__make_status(wuffs_base__suspension__short_read); + goto suspend; + } + uint64_t* scratch = &self->private_data.s_do_decode_image_config[0].scratch; + uint32_t num_bits_10 = ((uint32_t)(*scratch >> 56)); + *scratch <<= 8; + *scratch >>= 8; + *scratch |= ((uint64_t)(*iop_a_src++)) << num_bits_10; + if (num_bits_10 == 16) { + t_10 = ((uint32_t)(*scratch)); + break; + } + num_bits_10 += 8; + *scratch |= ((uint64_t)(num_bits_10)) << 56; + } + } + v_c = t_10; + } + self->private_data.f_src_palette[(((v_i & 255) * 4) + 0)] = ((uint8_t)(((v_c >> 0) & 255))); + self->private_data.f_src_palette[(((v_i & 255) * 4) + 1)] = ((uint8_t)(((v_c >> 8) & 255))); + self->private_data.f_src_palette[(((v_i & 255) * 4) + 2)] = ((uint8_t)(((v_c >> 16) & 255))); + self->private_data.f_src_palette[(((v_i & 255) * 4) + 3)] = 255; + } else if (self->private_impl.f_header_color_map_entry_size == 32) { + { + WUFFS_BASE__COROUTINE_SUSPENSION_POINT(19); + uint32_t t_11; + if (WUFFS_BASE__LIKELY(io2_a_src - iop_a_src >= 4)) { + t_11 = wuffs_base__peek_u32le__no_bounds_check(iop_a_src); + iop_a_src += 4; + } else { + self->private_data.s_do_decode_image_config[0].scratch = 0; + WUFFS_BASE__COROUTINE_SUSPENSION_POINT(20); + while (true) { + if (WUFFS_BASE__UNLIKELY(iop_a_src == io2_a_src)) { + status = wuffs_base__make_status(wuffs_base__suspension__short_read); + goto suspend; + } + uint64_t* scratch = &self->private_data.s_do_decode_image_config[0].scratch; + uint32_t num_bits_11 = ((uint32_t)(*scratch >> 56)); + *scratch <<= 8; + *scratch >>= 8; + *scratch |= ((uint64_t)(*iop_a_src++)) << num_bits_11; + if (num_bits_11 == 24) { + t_11 = ((uint32_t)(*scratch)); + break; + } + num_bits_11 += 8; + *scratch |= ((uint64_t)(num_bits_11)) << 56; + } + } + v_c = t_11; + } + self->private_data.f_src_palette[(((v_i & 255) * 4) + 0)] = ((uint8_t)(((v_c >> 0) & 255))); + self->private_data.f_src_palette[(((v_i & 255) * 4) + 1)] = ((uint8_t)(((v_c >> 8) & 255))); + self->private_data.f_src_palette[(((v_i & 255) * 4) + 2)] = ((uint8_t)(((v_c >> 16) & 255))); + self->private_data.f_src_palette[(((v_i & 255) * 4) + 3)] = ((uint8_t)(((v_c >> 24) & 255))); + } else { + { + WUFFS_BASE__COROUTINE_SUSPENSION_POINT(21); + uint32_t t_12; + if (WUFFS_BASE__LIKELY(io2_a_src - iop_a_src >= 2)) { + t_12 = ((uint32_t)(wuffs_base__peek_u16le__no_bounds_check(iop_a_src))); + iop_a_src += 2; + } else { + self->private_data.s_do_decode_image_config[0].scratch = 0; + WUFFS_BASE__COROUTINE_SUSPENSION_POINT(22); + while (true) { + if (WUFFS_BASE__UNLIKELY(iop_a_src == io2_a_src)) { + status = wuffs_base__make_status(wuffs_base__suspension__short_read); + goto suspend; + } + uint64_t* scratch = &self->private_data.s_do_decode_image_config[0].scratch; + uint32_t num_bits_12 = ((uint32_t)(*scratch >> 56)); + *scratch <<= 8; + *scratch >>= 8; + *scratch |= ((uint64_t)(*iop_a_src++)) << num_bits_12; + if (num_bits_12 == 8) { + t_12 = ((uint32_t)(*scratch)); + break; + } + num_bits_12 += 8; + *scratch |= ((uint64_t)(num_bits_12)) << 56; + } + } + v_c = t_12; + } + v_c5 = (31 & (v_c >> 0)); + self->private_data.f_src_palette[(((v_i & 255) * 4) + 0)] = ((uint8_t)(((v_c5 << 3) | (v_c5 >> 2)))); + v_c5 = (31 & (v_c >> 5)); + self->private_data.f_src_palette[(((v_i & 255) * 4) + 1)] = ((uint8_t)(((v_c5 << 3) | (v_c5 >> 2)))); + v_c5 = (31 & (v_c >> 10)); + self->private_data.f_src_palette[(((v_i & 255) * 4) + 2)] = ((uint8_t)(((v_c5 << 3) | (v_c5 >> 2)))); + self->private_data.f_src_palette[(((v_i & 255) * 4) + 3)] = 255; + } + v_i += 1; + } + while (v_i < 256) { + self->private_data.f_src_palette[((v_i * 4) + 0)] = 0; + self->private_data.f_src_palette[((v_i * 4) + 1)] = 0; + self->private_data.f_src_palette[((v_i * 4) + 2)] = 0; + self->private_data.f_src_palette[((v_i * 4) + 3)] = 255; + v_i += 1; + } + } + self->private_impl.f_frame_config_io_position = wuffs_base__u64__sat_add((a_src ? a_src->meta.pos : 0), ((uint64_t)(iop_a_src - io0_a_src))); + if (a_dst != NULL) { + wuffs_base__image_config__set( + a_dst, + self->private_impl.f_src_pixfmt, + 0, + self->private_impl.f_width, + self->private_impl.f_height, + self->private_impl.f_frame_config_io_position, + self->private_impl.f_opaque); + } + self->private_impl.f_call_sequence = 32; + + goto ok; + ok: + self->private_impl.p_do_decode_image_config[0] = 0; + goto exit; + } + + goto suspend; + suspend: + self->private_impl.p_do_decode_image_config[0] = wuffs_base__status__is_suspension(&status) ? coro_susp_point : 0; + self->private_data.s_do_decode_image_config[0].v_i = v_i; + + goto exit; + exit: + if (a_src && a_src->data.ptr) { + a_src->meta.ri = ((size_t)(iop_a_src - a_src->data.ptr)); + } + + return status; +} + +// -------- func tga.decoder.decode_frame_config + +WUFFS_BASE__MAYBE_STATIC wuffs_base__status +wuffs_tga__decoder__decode_frame_config( + wuffs_tga__decoder* self, + wuffs_base__frame_config* a_dst, + wuffs_base__io_buffer* a_src) { + if (!self) { + return wuffs_base__make_status(wuffs_base__error__bad_receiver); + } + if (self->private_impl.magic != WUFFS_BASE__MAGIC) { + return wuffs_base__make_status( + (self->private_impl.magic == WUFFS_BASE__DISABLED) + ? wuffs_base__error__disabled_by_previous_error + : wuffs_base__error__initialize_not_called); + } + if (!a_src) { + self->private_impl.magic = WUFFS_BASE__DISABLED; + return wuffs_base__make_status(wuffs_base__error__bad_argument); + } + if ((self->private_impl.active_coroutine != 0) && + (self->private_impl.active_coroutine != 2)) { + self->private_impl.magic = WUFFS_BASE__DISABLED; + return wuffs_base__make_status(wuffs_base__error__interleaved_coroutine_calls); + } + self->private_impl.active_coroutine = 0; + wuffs_base__status status = wuffs_base__make_status(NULL); + + wuffs_base__status v_status = wuffs_base__make_status(NULL); + + uint32_t coro_susp_point = self->private_impl.p_decode_frame_config[0]; + switch (coro_susp_point) { + WUFFS_BASE__COROUTINE_SUSPENSION_POINT_0; + + while (true) { + { + wuffs_base__status t_0 = wuffs_tga__decoder__do_decode_frame_config(self, a_dst, a_src); + v_status = t_0; + } + if ((v_status.repr == wuffs_base__suspension__short_read) && (a_src && a_src->meta.closed)) { + status = wuffs_base__make_status(wuffs_tga__error__truncated_input); + goto exit; + } + status = v_status; + WUFFS_BASE__COROUTINE_SUSPENSION_POINT_MAYBE_SUSPEND(1); + } + + ok: + self->private_impl.p_decode_frame_config[0] = 0; + goto exit; + } + + goto suspend; + suspend: + self->private_impl.p_decode_frame_config[0] = wuffs_base__status__is_suspension(&status) ? coro_susp_point : 0; + self->private_impl.active_coroutine = wuffs_base__status__is_suspension(&status) ? 2 : 0; + + goto exit; + exit: + if (wuffs_base__status__is_error(&status)) { + self->private_impl.magic = WUFFS_BASE__DISABLED; + } + return status; +} + +// -------- func tga.decoder.do_decode_frame_config + +static wuffs_base__status +wuffs_tga__decoder__do_decode_frame_config( + wuffs_tga__decoder* self, + wuffs_base__frame_config* a_dst, + wuffs_base__io_buffer* a_src) { + wuffs_base__status status = wuffs_base__make_status(NULL); + + const uint8_t* iop_a_src = NULL; + const uint8_t* io0_a_src WUFFS_BASE__POTENTIALLY_UNUSED = NULL; + const uint8_t* io1_a_src WUFFS_BASE__POTENTIALLY_UNUSED = NULL; + const uint8_t* io2_a_src WUFFS_BASE__POTENTIALLY_UNUSED = NULL; + if (a_src && a_src->data.ptr) { + io0_a_src = a_src->data.ptr; + io1_a_src = io0_a_src + a_src->meta.ri; + iop_a_src = io1_a_src; + io2_a_src = io0_a_src + a_src->meta.wi; + } + + uint32_t coro_susp_point = self->private_impl.p_do_decode_frame_config[0]; + switch (coro_susp_point) { + WUFFS_BASE__COROUTINE_SUSPENSION_POINT_0; + + if (self->private_impl.f_call_sequence == 32) { + } else if (self->private_impl.f_call_sequence < 32) { + if (a_src) { + a_src->meta.ri = ((size_t)(iop_a_src - a_src->data.ptr)); + } + WUFFS_BASE__COROUTINE_SUSPENSION_POINT(1); + status = wuffs_tga__decoder__do_decode_image_config(self, NULL, a_src); + if (a_src) { + iop_a_src = a_src->data.ptr + a_src->meta.ri; + } + if (status.repr) { + goto suspend; + } + } else if (self->private_impl.f_call_sequence == 40) { + if (self->private_impl.f_frame_config_io_position != wuffs_base__u64__sat_add((a_src ? a_src->meta.pos : 0), ((uint64_t)(iop_a_src - io0_a_src)))) { + status = wuffs_base__make_status(wuffs_base__error__bad_restart); + goto exit; + } + } else if (self->private_impl.f_call_sequence == 64) { + self->private_impl.f_call_sequence = 96; + status = wuffs_base__make_status(wuffs_base__note__end_of_data); + goto ok; + } else { + status = wuffs_base__make_status(wuffs_base__note__end_of_data); + goto ok; + } + if (a_dst != NULL) { + wuffs_base__frame_config__set( + a_dst, + wuffs_base__utility__make_rect_ie_u32( + 0, + 0, + self->private_impl.f_width, + self->private_impl.f_height), + ((wuffs_base__flicks)(0)), + 0, + self->private_impl.f_frame_config_io_position, + 0, + self->private_impl.f_opaque, + false, + 4278190080); + } + self->private_impl.f_call_sequence = 64; + + ok: + self->private_impl.p_do_decode_frame_config[0] = 0; + goto exit; + } + + goto suspend; + suspend: + self->private_impl.p_do_decode_frame_config[0] = wuffs_base__status__is_suspension(&status) ? coro_susp_point : 0; + + goto exit; + exit: + if (a_src && a_src->data.ptr) { + a_src->meta.ri = ((size_t)(iop_a_src - a_src->data.ptr)); + } + + return status; +} + +// -------- func tga.decoder.decode_frame + +WUFFS_BASE__MAYBE_STATIC wuffs_base__status +wuffs_tga__decoder__decode_frame( + wuffs_tga__decoder* self, + wuffs_base__pixel_buffer* a_dst, + wuffs_base__io_buffer* a_src, + wuffs_base__pixel_blend a_blend, + wuffs_base__slice_u8 a_workbuf, + wuffs_base__decode_frame_options* a_opts) { + if (!self) { + return wuffs_base__make_status(wuffs_base__error__bad_receiver); + } + if (self->private_impl.magic != WUFFS_BASE__MAGIC) { + return wuffs_base__make_status( + (self->private_impl.magic == WUFFS_BASE__DISABLED) + ? wuffs_base__error__disabled_by_previous_error + : wuffs_base__error__initialize_not_called); + } + if (!a_dst || !a_src) { + self->private_impl.magic = WUFFS_BASE__DISABLED; + return wuffs_base__make_status(wuffs_base__error__bad_argument); + } + if ((self->private_impl.active_coroutine != 0) && + (self->private_impl.active_coroutine != 3)) { + self->private_impl.magic = WUFFS_BASE__DISABLED; + return wuffs_base__make_status(wuffs_base__error__interleaved_coroutine_calls); + } + self->private_impl.active_coroutine = 0; + wuffs_base__status status = wuffs_base__make_status(NULL); + + wuffs_base__status v_status = wuffs_base__make_status(NULL); + + uint32_t coro_susp_point = self->private_impl.p_decode_frame[0]; + switch (coro_susp_point) { + WUFFS_BASE__COROUTINE_SUSPENSION_POINT_0; + + while (true) { + { + wuffs_base__status t_0 = wuffs_tga__decoder__do_decode_frame(self, + a_dst, + a_src, + a_blend, + a_workbuf, + a_opts); + v_status = t_0; + } + if ((v_status.repr == wuffs_base__suspension__short_read) && (a_src && a_src->meta.closed)) { + status = wuffs_base__make_status(wuffs_tga__error__truncated_input); + goto exit; + } + status = v_status; + WUFFS_BASE__COROUTINE_SUSPENSION_POINT_MAYBE_SUSPEND(1); + } + + ok: + self->private_impl.p_decode_frame[0] = 0; + goto exit; + } + + goto suspend; + suspend: + self->private_impl.p_decode_frame[0] = wuffs_base__status__is_suspension(&status) ? coro_susp_point : 0; + self->private_impl.active_coroutine = wuffs_base__status__is_suspension(&status) ? 3 : 0; + + goto exit; + exit: + if (wuffs_base__status__is_error(&status)) { + self->private_impl.magic = WUFFS_BASE__DISABLED; + } + return status; +} + +// -------- func tga.decoder.do_decode_frame + +static wuffs_base__status +wuffs_tga__decoder__do_decode_frame( + wuffs_tga__decoder* self, + wuffs_base__pixel_buffer* a_dst, + wuffs_base__io_buffer* a_src, + wuffs_base__pixel_blend a_blend, + wuffs_base__slice_u8 a_workbuf, + wuffs_base__decode_frame_options* a_opts) { + wuffs_base__status status = wuffs_base__make_status(NULL); + + wuffs_base__status v_status = wuffs_base__make_status(NULL); + wuffs_base__pixel_format v_dst_pixfmt = {0}; + uint32_t v_dst_bits_per_pixel = 0; + uint64_t v_dst_bytes_per_pixel = 0; + uint32_t v_dst_x = 0; + uint32_t v_dst_y = 0; + wuffs_base__table_u8 v_tab = {0}; + wuffs_base__slice_u8 v_dst_palette = {0}; + wuffs_base__slice_u8 v_dst = {0}; + uint64_t v_dst_start = 0; + wuffs_base__slice_u8 v_src_palette = {0}; + uint64_t v_mark = 0; + uint64_t v_num_pixels64 = 0; + uint32_t v_num_pixels32 = 0; + uint32_t v_lit_length = 0; + uint32_t v_run_length = 0; + uint64_t v_num_dst_bytes = 0; + uint32_t v_num_src_bytes = 0; + uint32_t v_c = 0; + uint32_t v_c5 = 0; + + const uint8_t* iop_a_src = NULL; + const uint8_t* io0_a_src WUFFS_BASE__POTENTIALLY_UNUSED = NULL; + const uint8_t* io1_a_src WUFFS_BASE__POTENTIALLY_UNUSED = NULL; + const uint8_t* io2_a_src WUFFS_BASE__POTENTIALLY_UNUSED = NULL; + if (a_src && a_src->data.ptr) { + io0_a_src = a_src->data.ptr; + io1_a_src = io0_a_src + a_src->meta.ri; + iop_a_src = io1_a_src; + io2_a_src = io0_a_src + a_src->meta.wi; + } + + uint32_t coro_susp_point = self->private_impl.p_do_decode_frame[0]; + if (coro_susp_point) { + v_dst_bytes_per_pixel = self->private_data.s_do_decode_frame[0].v_dst_bytes_per_pixel; + v_dst_x = self->private_data.s_do_decode_frame[0].v_dst_x; + v_dst_y = self->private_data.s_do_decode_frame[0].v_dst_y; + v_mark = self->private_data.s_do_decode_frame[0].v_mark; + v_num_pixels32 = self->private_data.s_do_decode_frame[0].v_num_pixels32; + v_lit_length = self->private_data.s_do_decode_frame[0].v_lit_length; + v_run_length = self->private_data.s_do_decode_frame[0].v_run_length; + v_num_dst_bytes = self->private_data.s_do_decode_frame[0].v_num_dst_bytes; + } + switch (coro_susp_point) { + WUFFS_BASE__COROUTINE_SUSPENSION_POINT_0; + + if (self->private_impl.f_call_sequence == 64) { + } else if (self->private_impl.f_call_sequence < 64) { + if (a_src) { + a_src->meta.ri = ((size_t)(iop_a_src - a_src->data.ptr)); + } + WUFFS_BASE__COROUTINE_SUSPENSION_POINT(1); + status = wuffs_tga__decoder__do_decode_frame_config(self, NULL, a_src); + if (a_src) { + iop_a_src = a_src->data.ptr + a_src->meta.ri; + } + if (status.repr) { + goto suspend; + } + } else { + status = wuffs_base__make_status(wuffs_base__note__end_of_data); + goto ok; + } + if (self->private_impl.f_header_color_map_type != 0) { + v_src_palette = wuffs_base__make_slice_u8(self->private_data.f_src_palette, 1024); + } + v_status = wuffs_base__pixel_swizzler__prepare(&self->private_impl.f_swizzler, + wuffs_base__pixel_buffer__pixel_format(a_dst), + wuffs_base__pixel_buffer__palette_or_else(a_dst, wuffs_base__make_slice_u8(self->private_data.f_dst_palette, 1024)), + wuffs_base__utility__make_pixel_format(self->private_impl.f_src_pixfmt), + v_src_palette, + a_blend); + if ( ! wuffs_base__status__is_ok(&v_status)) { + status = v_status; + if (wuffs_base__status__is_error(&status)) { + goto exit; + } else if (wuffs_base__status__is_suspension(&status)) { + status = wuffs_base__make_status(wuffs_base__error__cannot_return_a_suspension); + goto exit; + } + goto ok; + } + v_dst_pixfmt = wuffs_base__pixel_buffer__pixel_format(a_dst); + v_dst_bits_per_pixel = wuffs_base__pixel_format__bits_per_pixel(&v_dst_pixfmt); + if ((v_dst_bits_per_pixel & 7) != 0) { + status = wuffs_base__make_status(wuffs_base__error__unsupported_option); + goto exit; + } + v_dst_bytes_per_pixel = ((uint64_t)((v_dst_bits_per_pixel / 8))); + if ((self->private_impl.f_header_image_descriptor & 32) == 0) { + v_dst_y = ((uint32_t)(self->private_impl.f_height - 1)); + } + if ((self->private_impl.f_header_image_type & 8) == 0) { + v_lit_length = self->private_impl.f_width; + } + label__resume__continue:; + while (true) { + v_tab = wuffs_base__pixel_buffer__plane(a_dst, 0); + v_dst_palette = wuffs_base__pixel_buffer__palette_or_else(a_dst, wuffs_base__make_slice_u8(self->private_data.f_dst_palette, 1024)); + while (v_dst_y < self->private_impl.f_height) { + v_dst = wuffs_base__table_u8__row_u32(v_tab, v_dst_y); + v_dst_start = (((uint64_t)(v_dst_x)) * v_dst_bytes_per_pixel); + if (v_dst_start <= ((uint64_t)(v_dst.len))) { + v_dst = wuffs_base__slice_u8__subslice_i(v_dst, v_dst_start); + } else { + v_dst = wuffs_base__utility__empty_slice_u8(); + } + while (v_dst_x < self->private_impl.f_width) { + if (self->private_impl.f_src_bytes_per_pixel > 0) { + if (v_lit_length > 0) { + v_mark = ((uint64_t)(iop_a_src - io0_a_src)); + v_num_pixels64 = (((uint64_t)(io2_a_src - iop_a_src)) / ((uint64_t)(self->private_impl.f_src_bytes_per_pixel))); + v_num_pixels32 = ((uint32_t)(wuffs_base__u64__min(v_num_pixels64, ((uint64_t)(v_lit_length))))); + v_num_dst_bytes = (((uint64_t)(v_num_pixels32)) * v_dst_bytes_per_pixel); + v_num_src_bytes = (v_num_pixels32 * self->private_impl.f_src_bytes_per_pixel); + self->private_data.s_do_decode_frame[0].scratch = v_num_src_bytes; + WUFFS_BASE__COROUTINE_SUSPENSION_POINT(2); + if (self->private_data.s_do_decode_frame[0].scratch > ((uint64_t)(io2_a_src - iop_a_src))) { + self->private_data.s_do_decode_frame[0].scratch -= ((uint64_t)(io2_a_src - iop_a_src)); + iop_a_src = io2_a_src; + status = wuffs_base__make_status(wuffs_base__suspension__short_read); + goto suspend; + } + iop_a_src += self->private_data.s_do_decode_frame[0].scratch; + wuffs_base__pixel_swizzler__swizzle_interleaved_from_slice(&self->private_impl.f_swizzler, v_dst, v_dst_palette, wuffs_base__io__since(v_mark, ((uint64_t)(iop_a_src - io0_a_src)), io0_a_src)); + if (v_num_dst_bytes <= ((uint64_t)(v_dst.len))) { + v_dst = wuffs_base__slice_u8__subslice_i(v_dst, v_num_dst_bytes); + } else { + v_dst = wuffs_base__utility__empty_slice_u8(); + } + v_dst_x += v_num_pixels32; + v_lit_length = (((uint32_t)(v_lit_length - v_num_pixels32)) & 65535); + if (v_lit_length > 0) { + status = wuffs_base__make_status(wuffs_base__suspension__short_read); + WUFFS_BASE__COROUTINE_SUSPENSION_POINT_MAYBE_SUSPEND(3); + goto label__resume__continue; + } + } else if (v_run_length > 0) { + v_run_length -= 1; + wuffs_base__pixel_swizzler__swizzle_interleaved_from_slice(&self->private_impl.f_swizzler, v_dst, v_dst_palette, wuffs_base__make_slice_u8(self->private_data.f_scratch, self->private_impl.f_scratch_bytes_per_pixel)); + if (v_dst_bytes_per_pixel <= ((uint64_t)(v_dst.len))) { + v_dst = wuffs_base__slice_u8__subslice_i(v_dst, v_dst_bytes_per_pixel); + } + v_dst_x += 1; + } else { + if (((uint64_t)(io2_a_src - iop_a_src)) <= 0) { + status = wuffs_base__make_status(wuffs_base__suspension__short_read); + WUFFS_BASE__COROUTINE_SUSPENSION_POINT_MAYBE_SUSPEND(4); + goto label__resume__continue; + } + if (((uint32_t)(wuffs_base__peek_u8be__no_bounds_check(iop_a_src))) < 128) { + v_lit_length = (((uint32_t)(wuffs_base__peek_u8be__no_bounds_check(iop_a_src))) + 1); + iop_a_src += 1; + if ((v_lit_length + v_dst_x) > self->private_impl.f_width) { + status = wuffs_base__make_status(wuffs_tga__error__bad_run_length_encoding); + goto exit; + } + } else { + if (self->private_impl.f_src_bytes_per_pixel == 1) { + if (((uint64_t)(io2_a_src - iop_a_src)) < 2) { + status = wuffs_base__make_status(wuffs_base__suspension__short_read); + WUFFS_BASE__COROUTINE_SUSPENSION_POINT_MAYBE_SUSPEND(5); + goto label__resume__continue; + } + v_run_length = ((((uint32_t)(wuffs_base__peek_u8be__no_bounds_check(iop_a_src))) & 127) + 1); + iop_a_src += 1; + self->private_data.f_scratch[0] = wuffs_base__peek_u8be__no_bounds_check(iop_a_src); + iop_a_src += 1; + } else if (self->private_impl.f_src_bytes_per_pixel == 3) { + if (((uint64_t)(io2_a_src - iop_a_src)) < 4) { + status = wuffs_base__make_status(wuffs_base__suspension__short_read); + WUFFS_BASE__COROUTINE_SUSPENSION_POINT_MAYBE_SUSPEND(6); + goto label__resume__continue; + } + v_run_length = ((((uint32_t)(wuffs_base__peek_u8be__no_bounds_check(iop_a_src))) & 127) + 1); + iop_a_src += 1; + self->private_data.f_scratch[0] = wuffs_base__peek_u8be__no_bounds_check(iop_a_src); + iop_a_src += 1; + self->private_data.f_scratch[1] = wuffs_base__peek_u8be__no_bounds_check(iop_a_src); + iop_a_src += 1; + self->private_data.f_scratch[2] = wuffs_base__peek_u8be__no_bounds_check(iop_a_src); + iop_a_src += 1; + } else { + if (((uint64_t)(io2_a_src - iop_a_src)) < 5) { + status = wuffs_base__make_status(wuffs_base__suspension__short_read); + WUFFS_BASE__COROUTINE_SUSPENSION_POINT_MAYBE_SUSPEND(7); + goto label__resume__continue; + } + v_run_length = ((((uint32_t)(wuffs_base__peek_u8be__no_bounds_check(iop_a_src))) & 127) + 1); + iop_a_src += 1; + self->private_data.f_scratch[0] = wuffs_base__peek_u8be__no_bounds_check(iop_a_src); + iop_a_src += 1; + self->private_data.f_scratch[1] = wuffs_base__peek_u8be__no_bounds_check(iop_a_src); + iop_a_src += 1; + self->private_data.f_scratch[2] = wuffs_base__peek_u8be__no_bounds_check(iop_a_src); + iop_a_src += 1; + self->private_data.f_scratch[3] = wuffs_base__peek_u8be__no_bounds_check(iop_a_src); + iop_a_src += 1; + } + if ((v_run_length + v_dst_x) > self->private_impl.f_width) { + status = wuffs_base__make_status(wuffs_tga__error__bad_run_length_encoding); + goto exit; + } + } + } + } else { + if (v_lit_length > 0) { + if (((uint64_t)(io2_a_src - iop_a_src)) < 2) { + status = wuffs_base__make_status(wuffs_base__suspension__short_read); + WUFFS_BASE__COROUTINE_SUSPENSION_POINT_MAYBE_SUSPEND(8); + goto label__resume__continue; + } + v_c = ((uint32_t)(wuffs_base__peek_u16le__no_bounds_check(iop_a_src))); + iop_a_src += 2; + v_c5 = (31 & (v_c >> 0)); + self->private_data.f_scratch[0] = ((uint8_t)(((v_c5 << 3) | (v_c5 >> 2)))); + v_c5 = (31 & (v_c >> 5)); + self->private_data.f_scratch[1] = ((uint8_t)(((v_c5 << 3) | (v_c5 >> 2)))); + v_c5 = (31 & (v_c >> 10)); + self->private_data.f_scratch[2] = ((uint8_t)(((v_c5 << 3) | (v_c5 >> 2)))); + self->private_data.f_scratch[3] = 255; + wuffs_base__pixel_swizzler__swizzle_interleaved_from_slice(&self->private_impl.f_swizzler, v_dst, v_dst_palette, wuffs_base__make_slice_u8(self->private_data.f_scratch, 4)); + if (v_dst_bytes_per_pixel <= ((uint64_t)(v_dst.len))) { + v_dst = wuffs_base__slice_u8__subslice_i(v_dst, v_dst_bytes_per_pixel); + } + v_dst_x += 1; + v_lit_length -= 1; + } else if (v_run_length > 0) { + v_run_length -= 1; + wuffs_base__pixel_swizzler__swizzle_interleaved_from_slice(&self->private_impl.f_swizzler, v_dst, v_dst_palette, wuffs_base__make_slice_u8(self->private_data.f_scratch, self->private_impl.f_scratch_bytes_per_pixel)); + if (v_dst_bytes_per_pixel <= ((uint64_t)(v_dst.len))) { + v_dst = wuffs_base__slice_u8__subslice_i(v_dst, v_dst_bytes_per_pixel); + } + v_dst_x += 1; + } else { + if (((uint64_t)(io2_a_src - iop_a_src)) <= 0) { + status = wuffs_base__make_status(wuffs_base__suspension__short_read); + WUFFS_BASE__COROUTINE_SUSPENSION_POINT_MAYBE_SUSPEND(9); + goto label__resume__continue; + } + if (((uint32_t)(wuffs_base__peek_u8be__no_bounds_check(iop_a_src))) < 128) { + v_lit_length = (((uint32_t)(wuffs_base__peek_u8be__no_bounds_check(iop_a_src))) + 1); + iop_a_src += 1; + if ((v_lit_length + v_dst_x) > self->private_impl.f_width) { + status = wuffs_base__make_status(wuffs_tga__error__bad_run_length_encoding); + goto exit; + } + } else { + if (((uint64_t)(io2_a_src - iop_a_src)) < 3) { + status = wuffs_base__make_status(wuffs_base__suspension__short_read); + WUFFS_BASE__COROUTINE_SUSPENSION_POINT_MAYBE_SUSPEND(10); + goto label__resume__continue; + } + v_run_length = ((((uint32_t)(wuffs_base__peek_u8be__no_bounds_check(iop_a_src))) & 127) + 1); + iop_a_src += 1; + v_c = ((uint32_t)(wuffs_base__peek_u16le__no_bounds_check(iop_a_src))); + iop_a_src += 2; + v_c5 = (31 & (v_c >> 0)); + self->private_data.f_scratch[0] = ((uint8_t)(((v_c5 << 3) | (v_c5 >> 2)))); + v_c5 = (31 & (v_c >> 5)); + self->private_data.f_scratch[1] = ((uint8_t)(((v_c5 << 3) | (v_c5 >> 2)))); + v_c5 = (31 & (v_c >> 10)); + self->private_data.f_scratch[2] = ((uint8_t)(((v_c5 << 3) | (v_c5 >> 2)))); + self->private_data.f_scratch[3] = 255; + if ((v_run_length + v_dst_x) > self->private_impl.f_width) { + status = wuffs_base__make_status(wuffs_tga__error__bad_run_length_encoding); + goto exit; + } + } + } + } + } + v_dst_x = 0; + if ((self->private_impl.f_header_image_descriptor & 32) == 0) { + v_dst_y -= 1; + } else { + v_dst_y += 1; + } + if ((self->private_impl.f_header_image_type & 8) == 0) { + v_lit_length = self->private_impl.f_width; + } + } + goto label__resume__break; + } + label__resume__break:; + self->private_impl.f_call_sequence = 96; + + ok: + self->private_impl.p_do_decode_frame[0] = 0; + goto exit; + } + + goto suspend; + suspend: + self->private_impl.p_do_decode_frame[0] = wuffs_base__status__is_suspension(&status) ? coro_susp_point : 0; + self->private_data.s_do_decode_frame[0].v_dst_bytes_per_pixel = v_dst_bytes_per_pixel; + self->private_data.s_do_decode_frame[0].v_dst_x = v_dst_x; + self->private_data.s_do_decode_frame[0].v_dst_y = v_dst_y; + self->private_data.s_do_decode_frame[0].v_mark = v_mark; + self->private_data.s_do_decode_frame[0].v_num_pixels32 = v_num_pixels32; + self->private_data.s_do_decode_frame[0].v_lit_length = v_lit_length; + self->private_data.s_do_decode_frame[0].v_run_length = v_run_length; + self->private_data.s_do_decode_frame[0].v_num_dst_bytes = v_num_dst_bytes; + + goto exit; + exit: + if (a_src && a_src->data.ptr) { + a_src->meta.ri = ((size_t)(iop_a_src - a_src->data.ptr)); + } + + return status; +} + +// -------- func tga.decoder.frame_dirty_rect + +WUFFS_BASE__MAYBE_STATIC wuffs_base__rect_ie_u32 +wuffs_tga__decoder__frame_dirty_rect( + const wuffs_tga__decoder* self) { + if (!self) { + return wuffs_base__utility__empty_rect_ie_u32(); + } + if ((self->private_impl.magic != WUFFS_BASE__MAGIC) && + (self->private_impl.magic != WUFFS_BASE__DISABLED)) { + return wuffs_base__utility__empty_rect_ie_u32(); + } + + return wuffs_base__utility__make_rect_ie_u32( + 0, + 0, + self->private_impl.f_width, + self->private_impl.f_height); +} + +// -------- func tga.decoder.num_animation_loops + +WUFFS_BASE__MAYBE_STATIC uint32_t +wuffs_tga__decoder__num_animation_loops( + const wuffs_tga__decoder* self) { + if (!self) { + return 0; + } + if ((self->private_impl.magic != WUFFS_BASE__MAGIC) && + (self->private_impl.magic != WUFFS_BASE__DISABLED)) { + return 0; + } + + return 0; +} + +// -------- func tga.decoder.num_decoded_frame_configs + +WUFFS_BASE__MAYBE_STATIC uint64_t +wuffs_tga__decoder__num_decoded_frame_configs( + const wuffs_tga__decoder* self) { + if (!self) { + return 0; + } + if ((self->private_impl.magic != WUFFS_BASE__MAGIC) && + (self->private_impl.magic != WUFFS_BASE__DISABLED)) { + return 0; + } + + if (self->private_impl.f_call_sequence > 32) { + return 1; + } + return 0; +} + +// -------- func tga.decoder.num_decoded_frames + +WUFFS_BASE__MAYBE_STATIC uint64_t +wuffs_tga__decoder__num_decoded_frames( + const wuffs_tga__decoder* self) { + if (!self) { + return 0; + } + if ((self->private_impl.magic != WUFFS_BASE__MAGIC) && + (self->private_impl.magic != WUFFS_BASE__DISABLED)) { + return 0; + } + + if (self->private_impl.f_call_sequence > 64) { + return 1; + } + return 0; +} + +// -------- func tga.decoder.restart_frame + +WUFFS_BASE__MAYBE_STATIC wuffs_base__status +wuffs_tga__decoder__restart_frame( + wuffs_tga__decoder* self, + uint64_t a_index, + uint64_t a_io_position) { + if (!self) { + return wuffs_base__make_status(wuffs_base__error__bad_receiver); + } + if (self->private_impl.magic != WUFFS_BASE__MAGIC) { + return wuffs_base__make_status( + (self->private_impl.magic == WUFFS_BASE__DISABLED) + ? wuffs_base__error__disabled_by_previous_error + : wuffs_base__error__initialize_not_called); + } + + if (self->private_impl.f_call_sequence < 32) { + return wuffs_base__make_status(wuffs_base__error__bad_call_sequence); + } + if (a_index != 0) { + return wuffs_base__make_status(wuffs_base__error__bad_argument); + } + self->private_impl.f_call_sequence = 40; + self->private_impl.f_frame_config_io_position = a_io_position; + return wuffs_base__make_status(NULL); +} + +// -------- func tga.decoder.set_report_metadata + +WUFFS_BASE__MAYBE_STATIC wuffs_base__empty_struct +wuffs_tga__decoder__set_report_metadata( + wuffs_tga__decoder* self, + uint32_t a_fourcc, + bool a_report) { + return wuffs_base__make_empty_struct(); +} + +// -------- func tga.decoder.tell_me_more + +WUFFS_BASE__MAYBE_STATIC wuffs_base__status +wuffs_tga__decoder__tell_me_more( + wuffs_tga__decoder* self, + wuffs_base__io_buffer* a_dst, + wuffs_base__more_information* a_minfo, + wuffs_base__io_buffer* a_src) { + if (!self) { + return wuffs_base__make_status(wuffs_base__error__bad_receiver); + } + if (self->private_impl.magic != WUFFS_BASE__MAGIC) { + return wuffs_base__make_status( + (self->private_impl.magic == WUFFS_BASE__DISABLED) + ? wuffs_base__error__disabled_by_previous_error + : wuffs_base__error__initialize_not_called); + } + if (!a_dst || !a_src) { + self->private_impl.magic = WUFFS_BASE__DISABLED; + return wuffs_base__make_status(wuffs_base__error__bad_argument); + } + if ((self->private_impl.active_coroutine != 0) && + (self->private_impl.active_coroutine != 4)) { + self->private_impl.magic = WUFFS_BASE__DISABLED; + return wuffs_base__make_status(wuffs_base__error__interleaved_coroutine_calls); + } + self->private_impl.active_coroutine = 0; + wuffs_base__status status = wuffs_base__make_status(NULL); + + status = wuffs_base__make_status(wuffs_base__error__no_more_information); + goto exit; + + goto ok; + ok: + goto exit; + exit: + if (wuffs_base__status__is_error(&status)) { + self->private_impl.magic = WUFFS_BASE__DISABLED; + } + return status; +} + +// -------- func tga.decoder.workbuf_len + +WUFFS_BASE__MAYBE_STATIC wuffs_base__range_ii_u64 +wuffs_tga__decoder__workbuf_len( + const wuffs_tga__decoder* self) { + if (!self) { + return wuffs_base__utility__empty_range_ii_u64(); + } + if ((self->private_impl.magic != WUFFS_BASE__MAGIC) && + (self->private_impl.magic != WUFFS_BASE__DISABLED)) { + return wuffs_base__utility__empty_range_ii_u64(); + } + + return wuffs_base__utility__make_range_ii_u64(0, 0); +} + +#endif // !defined(WUFFS_CONFIG__MODULES) || defined(WUFFS_CONFIG__MODULE__TGA) + +#if !defined(WUFFS_CONFIG__MODULES) || defined(WUFFS_CONFIG__MODULE__WBMP) + +// ---------------- Status Codes Implementations + +const char wuffs_wbmp__error__bad_header[] = "#wbmp: bad header"; +const char wuffs_wbmp__error__truncated_input[] = "#wbmp: truncated input"; + +// ---------------- Private Consts + +// ---------------- Private Initializer Prototypes + +// ---------------- Private Function Prototypes + +static wuffs_base__status +wuffs_wbmp__decoder__do_decode_image_config( + wuffs_wbmp__decoder* self, + wuffs_base__image_config* a_dst, + wuffs_base__io_buffer* a_src); + +static wuffs_base__status +wuffs_wbmp__decoder__do_decode_frame_config( + wuffs_wbmp__decoder* self, + wuffs_base__frame_config* a_dst, + wuffs_base__io_buffer* a_src); + +static wuffs_base__status +wuffs_wbmp__decoder__do_decode_frame( + wuffs_wbmp__decoder* self, + wuffs_base__pixel_buffer* a_dst, + wuffs_base__io_buffer* a_src, + wuffs_base__pixel_blend a_blend, + wuffs_base__slice_u8 a_workbuf, + wuffs_base__decode_frame_options* a_opts); + +// ---------------- VTables + +const wuffs_base__image_decoder__func_ptrs +wuffs_wbmp__decoder__func_ptrs_for__wuffs_base__image_decoder = { + (wuffs_base__status(*)(void*, + wuffs_base__pixel_buffer*, + wuffs_base__io_buffer*, + wuffs_base__pixel_blend, + wuffs_base__slice_u8, + wuffs_base__decode_frame_options*))(&wuffs_wbmp__decoder__decode_frame), + (wuffs_base__status(*)(void*, + wuffs_base__frame_config*, + wuffs_base__io_buffer*))(&wuffs_wbmp__decoder__decode_frame_config), + (wuffs_base__status(*)(void*, + wuffs_base__image_config*, + wuffs_base__io_buffer*))(&wuffs_wbmp__decoder__decode_image_config), + (wuffs_base__rect_ie_u32(*)(const void*))(&wuffs_wbmp__decoder__frame_dirty_rect), + (uint32_t(*)(const void*))(&wuffs_wbmp__decoder__num_animation_loops), + (uint64_t(*)(const void*))(&wuffs_wbmp__decoder__num_decoded_frame_configs), + (uint64_t(*)(const void*))(&wuffs_wbmp__decoder__num_decoded_frames), + (wuffs_base__status(*)(void*, + uint64_t, + uint64_t))(&wuffs_wbmp__decoder__restart_frame), + (wuffs_base__empty_struct(*)(void*, + uint32_t, + bool))(&wuffs_wbmp__decoder__set_quirk_enabled), + (wuffs_base__empty_struct(*)(void*, + uint32_t, + bool))(&wuffs_wbmp__decoder__set_report_metadata), + (wuffs_base__status(*)(void*, + wuffs_base__io_buffer*, + wuffs_base__more_information*, + wuffs_base__io_buffer*))(&wuffs_wbmp__decoder__tell_me_more), + (wuffs_base__range_ii_u64(*)(const void*))(&wuffs_wbmp__decoder__workbuf_len), +}; + +// ---------------- Initializer Implementations + +wuffs_base__status WUFFS_BASE__WARN_UNUSED_RESULT +wuffs_wbmp__decoder__initialize( + wuffs_wbmp__decoder* self, + size_t sizeof_star_self, + uint64_t wuffs_version, + uint32_t options){ + if (!self) { + return wuffs_base__make_status(wuffs_base__error__bad_receiver); + } + if (sizeof(*self) != sizeof_star_self) { + return wuffs_base__make_status(wuffs_base__error__bad_sizeof_receiver); + } + if (((wuffs_version >> 32) != WUFFS_VERSION_MAJOR) || + (((wuffs_version >> 16) & 0xFFFF) > WUFFS_VERSION_MINOR)) { + return wuffs_base__make_status(wuffs_base__error__bad_wuffs_version); + } + + if ((options & WUFFS_INITIALIZE__ALREADY_ZEROED) != 0) { + // The whole point of this if-check is to detect an uninitialized *self. + // We disable the warning on GCC. Clang-5.0 does not have this warning. +#if !defined(__clang__) && defined(__GNUC__) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wmaybe-uninitialized" +#endif + if (self->private_impl.magic != 0) { + return wuffs_base__make_status(wuffs_base__error__initialize_falsely_claimed_already_zeroed); + } +#if !defined(__clang__) && defined(__GNUC__) +#pragma GCC diagnostic pop +#endif + } else { + if ((options & WUFFS_INITIALIZE__LEAVE_INTERNAL_BUFFERS_UNINITIALIZED) == 0) { + memset(self, 0, sizeof(*self)); + options |= WUFFS_INITIALIZE__ALREADY_ZEROED; + } else { + memset(&(self->private_impl), 0, sizeof(self->private_impl)); + } + } + + self->private_impl.magic = WUFFS_BASE__MAGIC; + self->private_impl.vtable_for__wuffs_base__image_decoder.vtable_name = + wuffs_base__image_decoder__vtable_name; + self->private_impl.vtable_for__wuffs_base__image_decoder.function_pointers = + (const void*)(&wuffs_wbmp__decoder__func_ptrs_for__wuffs_base__image_decoder); + return wuffs_base__make_status(NULL); +} + +wuffs_wbmp__decoder* +wuffs_wbmp__decoder__alloc() { + wuffs_wbmp__decoder* x = + (wuffs_wbmp__decoder*)(calloc(sizeof(wuffs_wbmp__decoder), 1)); + if (!x) { + return NULL; + } + if (wuffs_wbmp__decoder__initialize( + x, sizeof(wuffs_wbmp__decoder), WUFFS_VERSION, WUFFS_INITIALIZE__ALREADY_ZEROED).repr) { + free(x); + return NULL; + } + return x; +} + +size_t +sizeof__wuffs_wbmp__decoder() { + return sizeof(wuffs_wbmp__decoder); +} + +// ---------------- Function Implementations + +// -------- func wbmp.decoder.set_quirk_enabled + +WUFFS_BASE__MAYBE_STATIC wuffs_base__empty_struct +wuffs_wbmp__decoder__set_quirk_enabled( + wuffs_wbmp__decoder* self, + uint32_t a_quirk, + bool a_enabled) { + return wuffs_base__make_empty_struct(); +} + +// -------- func wbmp.decoder.decode_image_config + +WUFFS_BASE__MAYBE_STATIC wuffs_base__status +wuffs_wbmp__decoder__decode_image_config( + wuffs_wbmp__decoder* self, + wuffs_base__image_config* a_dst, + wuffs_base__io_buffer* a_src) { + if (!self) { + return wuffs_base__make_status(wuffs_base__error__bad_receiver); + } + if (self->private_impl.magic != WUFFS_BASE__MAGIC) { + return wuffs_base__make_status( + (self->private_impl.magic == WUFFS_BASE__DISABLED) + ? wuffs_base__error__disabled_by_previous_error + : wuffs_base__error__initialize_not_called); + } + if (!a_src) { + self->private_impl.magic = WUFFS_BASE__DISABLED; + return wuffs_base__make_status(wuffs_base__error__bad_argument); + } + if ((self->private_impl.active_coroutine != 0) && + (self->private_impl.active_coroutine != 1)) { + self->private_impl.magic = WUFFS_BASE__DISABLED; + return wuffs_base__make_status(wuffs_base__error__interleaved_coroutine_calls); + } + self->private_impl.active_coroutine = 0; + wuffs_base__status status = wuffs_base__make_status(NULL); + + wuffs_base__status v_status = wuffs_base__make_status(NULL); + + uint32_t coro_susp_point = self->private_impl.p_decode_image_config[0]; + switch (coro_susp_point) { + WUFFS_BASE__COROUTINE_SUSPENSION_POINT_0; + + while (true) { + { + wuffs_base__status t_0 = wuffs_wbmp__decoder__do_decode_image_config(self, a_dst, a_src); + v_status = t_0; + } + if ((v_status.repr == wuffs_base__suspension__short_read) && (a_src && a_src->meta.closed)) { + status = wuffs_base__make_status(wuffs_wbmp__error__truncated_input); + goto exit; + } + status = v_status; + WUFFS_BASE__COROUTINE_SUSPENSION_POINT_MAYBE_SUSPEND(1); + } + + ok: + self->private_impl.p_decode_image_config[0] = 0; + goto exit; + } + + goto suspend; + suspend: + self->private_impl.p_decode_image_config[0] = wuffs_base__status__is_suspension(&status) ? coro_susp_point : 0; + self->private_impl.active_coroutine = wuffs_base__status__is_suspension(&status) ? 1 : 0; + + goto exit; + exit: + if (wuffs_base__status__is_error(&status)) { + self->private_impl.magic = WUFFS_BASE__DISABLED; + } + return status; +} + +// -------- func wbmp.decoder.do_decode_image_config + +static wuffs_base__status +wuffs_wbmp__decoder__do_decode_image_config( + wuffs_wbmp__decoder* self, + wuffs_base__image_config* a_dst, + wuffs_base__io_buffer* a_src) { + wuffs_base__status status = wuffs_base__make_status(NULL); + + uint8_t v_c = 0; + uint32_t v_i = 0; + uint32_t v_x32 = 0; + uint64_t v_x64 = 0; + + const uint8_t* iop_a_src = NULL; + const uint8_t* io0_a_src WUFFS_BASE__POTENTIALLY_UNUSED = NULL; + const uint8_t* io1_a_src WUFFS_BASE__POTENTIALLY_UNUSED = NULL; + const uint8_t* io2_a_src WUFFS_BASE__POTENTIALLY_UNUSED = NULL; + if (a_src && a_src->data.ptr) { + io0_a_src = a_src->data.ptr; + io1_a_src = io0_a_src + a_src->meta.ri; + iop_a_src = io1_a_src; + io2_a_src = io0_a_src + a_src->meta.wi; + } + + uint32_t coro_susp_point = self->private_impl.p_do_decode_image_config[0]; + if (coro_susp_point) { + v_i = self->private_data.s_do_decode_image_config[0].v_i; + v_x32 = self->private_data.s_do_decode_image_config[0].v_x32; + } + switch (coro_susp_point) { + WUFFS_BASE__COROUTINE_SUSPENSION_POINT_0; + + if (self->private_impl.f_call_sequence != 0) { + status = wuffs_base__make_status(wuffs_base__error__bad_call_sequence); + goto exit; + } + v_i = 0; + while (v_i < 2) { + { + WUFFS_BASE__COROUTINE_SUSPENSION_POINT(1); + if (WUFFS_BASE__UNLIKELY(iop_a_src == io2_a_src)) { + status = wuffs_base__make_status(wuffs_base__suspension__short_read); + goto suspend; + } + uint8_t t_0 = *iop_a_src++; + v_c = t_0; + } + if (v_c != 0) { + status = wuffs_base__make_status(wuffs_wbmp__error__bad_header); + goto exit; + } + v_i += 1; + } + v_i = 0; + while (v_i < 2) { + v_x32 = 0; + while (true) { + { + WUFFS_BASE__COROUTINE_SUSPENSION_POINT(2); + if (WUFFS_BASE__UNLIKELY(iop_a_src == io2_a_src)) { + status = wuffs_base__make_status(wuffs_base__suspension__short_read); + goto suspend; + } + uint8_t t_1 = *iop_a_src++; + v_c = t_1; + } + v_x32 |= ((uint32_t)((v_c & 127))); + if ((v_c >> 7) == 0) { + goto label__0__break; + } + v_x64 = (((uint64_t)(v_x32)) << 7); + if (v_x64 > 4294967295) { + status = wuffs_base__make_status(wuffs_wbmp__error__bad_header); + goto exit; + } + v_x32 = ((uint32_t)(v_x64)); + } + label__0__break:; + if (v_i == 0) { + self->private_impl.f_width = v_x32; + } else { + self->private_impl.f_height = v_x32; + } + v_i += 1; + } + self->private_impl.f_frame_config_io_position = wuffs_base__u64__sat_add((a_src ? a_src->meta.pos : 0), ((uint64_t)(iop_a_src - io0_a_src))); + if (a_dst != NULL) { + wuffs_base__image_config__set( + a_dst, + 2198077448, + 0, + self->private_impl.f_width, + self->private_impl.f_height, + self->private_impl.f_frame_config_io_position, + true); + } + self->private_impl.f_call_sequence = 32; + + goto ok; + ok: + self->private_impl.p_do_decode_image_config[0] = 0; + goto exit; + } + + goto suspend; + suspend: + self->private_impl.p_do_decode_image_config[0] = wuffs_base__status__is_suspension(&status) ? coro_susp_point : 0; + self->private_data.s_do_decode_image_config[0].v_i = v_i; + self->private_data.s_do_decode_image_config[0].v_x32 = v_x32; + + goto exit; + exit: + if (a_src && a_src->data.ptr) { + a_src->meta.ri = ((size_t)(iop_a_src - a_src->data.ptr)); + } + + return status; +} + +// -------- func wbmp.decoder.decode_frame_config + +WUFFS_BASE__MAYBE_STATIC wuffs_base__status +wuffs_wbmp__decoder__decode_frame_config( + wuffs_wbmp__decoder* self, + wuffs_base__frame_config* a_dst, + wuffs_base__io_buffer* a_src) { + if (!self) { + return wuffs_base__make_status(wuffs_base__error__bad_receiver); + } + if (self->private_impl.magic != WUFFS_BASE__MAGIC) { + return wuffs_base__make_status( + (self->private_impl.magic == WUFFS_BASE__DISABLED) + ? wuffs_base__error__disabled_by_previous_error + : wuffs_base__error__initialize_not_called); + } + if (!a_src) { + self->private_impl.magic = WUFFS_BASE__DISABLED; + return wuffs_base__make_status(wuffs_base__error__bad_argument); + } + if ((self->private_impl.active_coroutine != 0) && + (self->private_impl.active_coroutine != 2)) { + self->private_impl.magic = WUFFS_BASE__DISABLED; + return wuffs_base__make_status(wuffs_base__error__interleaved_coroutine_calls); + } + self->private_impl.active_coroutine = 0; + wuffs_base__status status = wuffs_base__make_status(NULL); + + wuffs_base__status v_status = wuffs_base__make_status(NULL); + + uint32_t coro_susp_point = self->private_impl.p_decode_frame_config[0]; + switch (coro_susp_point) { + WUFFS_BASE__COROUTINE_SUSPENSION_POINT_0; + + while (true) { + { + wuffs_base__status t_0 = wuffs_wbmp__decoder__do_decode_frame_config(self, a_dst, a_src); + v_status = t_0; + } + if ((v_status.repr == wuffs_base__suspension__short_read) && (a_src && a_src->meta.closed)) { + status = wuffs_base__make_status(wuffs_wbmp__error__truncated_input); + goto exit; + } + status = v_status; + WUFFS_BASE__COROUTINE_SUSPENSION_POINT_MAYBE_SUSPEND(1); + } + + ok: + self->private_impl.p_decode_frame_config[0] = 0; + goto exit; + } + + goto suspend; + suspend: + self->private_impl.p_decode_frame_config[0] = wuffs_base__status__is_suspension(&status) ? coro_susp_point : 0; + self->private_impl.active_coroutine = wuffs_base__status__is_suspension(&status) ? 2 : 0; + + goto exit; + exit: + if (wuffs_base__status__is_error(&status)) { + self->private_impl.magic = WUFFS_BASE__DISABLED; + } + return status; +} + +// -------- func wbmp.decoder.do_decode_frame_config + +static wuffs_base__status +wuffs_wbmp__decoder__do_decode_frame_config( + wuffs_wbmp__decoder* self, + wuffs_base__frame_config* a_dst, + wuffs_base__io_buffer* a_src) { + wuffs_base__status status = wuffs_base__make_status(NULL); + + const uint8_t* iop_a_src = NULL; + const uint8_t* io0_a_src WUFFS_BASE__POTENTIALLY_UNUSED = NULL; + const uint8_t* io1_a_src WUFFS_BASE__POTENTIALLY_UNUSED = NULL; + const uint8_t* io2_a_src WUFFS_BASE__POTENTIALLY_UNUSED = NULL; + if (a_src && a_src->data.ptr) { + io0_a_src = a_src->data.ptr; + io1_a_src = io0_a_src + a_src->meta.ri; + iop_a_src = io1_a_src; + io2_a_src = io0_a_src + a_src->meta.wi; + } + + uint32_t coro_susp_point = self->private_impl.p_do_decode_frame_config[0]; + switch (coro_susp_point) { + WUFFS_BASE__COROUTINE_SUSPENSION_POINT_0; + + if (self->private_impl.f_call_sequence == 32) { + } else if (self->private_impl.f_call_sequence < 32) { + if (a_src) { + a_src->meta.ri = ((size_t)(iop_a_src - a_src->data.ptr)); + } + WUFFS_BASE__COROUTINE_SUSPENSION_POINT(1); + status = wuffs_wbmp__decoder__do_decode_image_config(self, NULL, a_src); + if (a_src) { + iop_a_src = a_src->data.ptr + a_src->meta.ri; + } + if (status.repr) { + goto suspend; + } + } else if (self->private_impl.f_call_sequence == 40) { + if (self->private_impl.f_frame_config_io_position != wuffs_base__u64__sat_add((a_src ? a_src->meta.pos : 0), ((uint64_t)(iop_a_src - io0_a_src)))) { + status = wuffs_base__make_status(wuffs_base__error__bad_restart); + goto exit; + } + } else if (self->private_impl.f_call_sequence == 64) { + self->private_impl.f_call_sequence = 96; + status = wuffs_base__make_status(wuffs_base__note__end_of_data); + goto ok; + } else { + status = wuffs_base__make_status(wuffs_base__note__end_of_data); + goto ok; + } + if (a_dst != NULL) { + wuffs_base__frame_config__set( + a_dst, + wuffs_base__utility__make_rect_ie_u32( + 0, + 0, + self->private_impl.f_width, + self->private_impl.f_height), + ((wuffs_base__flicks)(0)), + 0, + self->private_impl.f_frame_config_io_position, + 0, + true, + false, + 4278190080); + } + self->private_impl.f_call_sequence = 64; + + ok: + self->private_impl.p_do_decode_frame_config[0] = 0; + goto exit; + } + + goto suspend; + suspend: + self->private_impl.p_do_decode_frame_config[0] = wuffs_base__status__is_suspension(&status) ? coro_susp_point : 0; + + goto exit; + exit: + if (a_src && a_src->data.ptr) { + a_src->meta.ri = ((size_t)(iop_a_src - a_src->data.ptr)); + } + + return status; +} + +// -------- func wbmp.decoder.decode_frame + +WUFFS_BASE__MAYBE_STATIC wuffs_base__status +wuffs_wbmp__decoder__decode_frame( + wuffs_wbmp__decoder* self, + wuffs_base__pixel_buffer* a_dst, + wuffs_base__io_buffer* a_src, + wuffs_base__pixel_blend a_blend, + wuffs_base__slice_u8 a_workbuf, + wuffs_base__decode_frame_options* a_opts) { + if (!self) { + return wuffs_base__make_status(wuffs_base__error__bad_receiver); + } + if (self->private_impl.magic != WUFFS_BASE__MAGIC) { + return wuffs_base__make_status( + (self->private_impl.magic == WUFFS_BASE__DISABLED) + ? wuffs_base__error__disabled_by_previous_error + : wuffs_base__error__initialize_not_called); + } + if (!a_dst || !a_src) { + self->private_impl.magic = WUFFS_BASE__DISABLED; + return wuffs_base__make_status(wuffs_base__error__bad_argument); + } + if ((self->private_impl.active_coroutine != 0) && + (self->private_impl.active_coroutine != 3)) { + self->private_impl.magic = WUFFS_BASE__DISABLED; + return wuffs_base__make_status(wuffs_base__error__interleaved_coroutine_calls); + } + self->private_impl.active_coroutine = 0; + wuffs_base__status status = wuffs_base__make_status(NULL); + + wuffs_base__status v_status = wuffs_base__make_status(NULL); + + uint32_t coro_susp_point = self->private_impl.p_decode_frame[0]; + switch (coro_susp_point) { + WUFFS_BASE__COROUTINE_SUSPENSION_POINT_0; + + while (true) { + { + wuffs_base__status t_0 = wuffs_wbmp__decoder__do_decode_frame(self, + a_dst, + a_src, + a_blend, + a_workbuf, + a_opts); + v_status = t_0; + } + if ((v_status.repr == wuffs_base__suspension__short_read) && (a_src && a_src->meta.closed)) { + status = wuffs_base__make_status(wuffs_wbmp__error__truncated_input); + goto exit; + } + status = v_status; + WUFFS_BASE__COROUTINE_SUSPENSION_POINT_MAYBE_SUSPEND(1); + } + + ok: + self->private_impl.p_decode_frame[0] = 0; + goto exit; + } + + goto suspend; + suspend: + self->private_impl.p_decode_frame[0] = wuffs_base__status__is_suspension(&status) ? coro_susp_point : 0; + self->private_impl.active_coroutine = wuffs_base__status__is_suspension(&status) ? 3 : 0; + + goto exit; + exit: + if (wuffs_base__status__is_error(&status)) { + self->private_impl.magic = WUFFS_BASE__DISABLED; + } + return status; +} + +// -------- func wbmp.decoder.do_decode_frame + +static wuffs_base__status +wuffs_wbmp__decoder__do_decode_frame( + wuffs_wbmp__decoder* self, + wuffs_base__pixel_buffer* a_dst, + wuffs_base__io_buffer* a_src, + wuffs_base__pixel_blend a_blend, + wuffs_base__slice_u8 a_workbuf, + wuffs_base__decode_frame_options* a_opts) { + wuffs_base__status status = wuffs_base__make_status(NULL); + + wuffs_base__status v_status = wuffs_base__make_status(NULL); + wuffs_base__pixel_format v_dst_pixfmt = {0}; + uint32_t v_dst_bits_per_pixel = 0; + uint64_t v_dst_bytes_per_pixel = 0; + uint64_t v_dst_x_in_bytes = 0; + uint32_t v_dst_x = 0; + uint32_t v_dst_y = 0; + wuffs_base__table_u8 v_tab = {0}; + wuffs_base__slice_u8 v_dst = {0}; + uint8_t v_src[1] = {0}; + uint8_t v_c = 0; + + const uint8_t* iop_a_src = NULL; + const uint8_t* io0_a_src WUFFS_BASE__POTENTIALLY_UNUSED = NULL; + const uint8_t* io1_a_src WUFFS_BASE__POTENTIALLY_UNUSED = NULL; + const uint8_t* io2_a_src WUFFS_BASE__POTENTIALLY_UNUSED = NULL; + if (a_src && a_src->data.ptr) { + io0_a_src = a_src->data.ptr; + io1_a_src = io0_a_src + a_src->meta.ri; + iop_a_src = io1_a_src; + io2_a_src = io0_a_src + a_src->meta.wi; + } + + uint32_t coro_susp_point = self->private_impl.p_do_decode_frame[0]; + if (coro_susp_point) { + v_dst_bytes_per_pixel = self->private_data.s_do_decode_frame[0].v_dst_bytes_per_pixel; + v_dst_x = self->private_data.s_do_decode_frame[0].v_dst_x; + v_dst_y = self->private_data.s_do_decode_frame[0].v_dst_y; + memcpy(v_src, self->private_data.s_do_decode_frame[0].v_src, sizeof(v_src)); + v_c = self->private_data.s_do_decode_frame[0].v_c; + } + switch (coro_susp_point) { + WUFFS_BASE__COROUTINE_SUSPENSION_POINT_0; + + if (self->private_impl.f_call_sequence == 64) { + } else if (self->private_impl.f_call_sequence < 64) { + if (a_src) { + a_src->meta.ri = ((size_t)(iop_a_src - a_src->data.ptr)); + } + WUFFS_BASE__COROUTINE_SUSPENSION_POINT(1); + status = wuffs_wbmp__decoder__do_decode_frame_config(self, NULL, a_src); + if (a_src) { + iop_a_src = a_src->data.ptr + a_src->meta.ri; + } + if (status.repr) { + goto suspend; + } + } else { + status = wuffs_base__make_status(wuffs_base__note__end_of_data); + goto ok; + } + v_status = wuffs_base__pixel_swizzler__prepare(&self->private_impl.f_swizzler, + wuffs_base__pixel_buffer__pixel_format(a_dst), + wuffs_base__pixel_buffer__palette(a_dst), + wuffs_base__utility__make_pixel_format(536870920), + wuffs_base__utility__empty_slice_u8(), + a_blend); + if ( ! wuffs_base__status__is_ok(&v_status)) { + status = v_status; + if (wuffs_base__status__is_error(&status)) { + goto exit; + } else if (wuffs_base__status__is_suspension(&status)) { + status = wuffs_base__make_status(wuffs_base__error__cannot_return_a_suspension); + goto exit; + } + goto ok; + } + v_dst_pixfmt = wuffs_base__pixel_buffer__pixel_format(a_dst); + v_dst_bits_per_pixel = wuffs_base__pixel_format__bits_per_pixel(&v_dst_pixfmt); + if ((v_dst_bits_per_pixel & 7) != 0) { + status = wuffs_base__make_status(wuffs_base__error__unsupported_option); + goto exit; + } + v_dst_bytes_per_pixel = ((uint64_t)((v_dst_bits_per_pixel / 8))); + if (self->private_impl.f_width > 0) { + v_tab = wuffs_base__pixel_buffer__plane(a_dst, 0); + while (v_dst_y < self->private_impl.f_height) { + v_dst = wuffs_base__table_u8__row_u32(v_tab, v_dst_y); + v_dst_x = 0; + while (v_dst_x < self->private_impl.f_width) { + if ((v_dst_x & 7) == 0) { + while (((uint64_t)(io2_a_src - iop_a_src)) <= 0) { + status = wuffs_base__make_status(wuffs_base__suspension__short_read); + WUFFS_BASE__COROUTINE_SUSPENSION_POINT_MAYBE_SUSPEND(2); + v_tab = wuffs_base__pixel_buffer__plane(a_dst, 0); + v_dst = wuffs_base__table_u8__row_u32(v_tab, v_dst_y); + v_dst_x_in_bytes = (((uint64_t)(v_dst_x)) * v_dst_bytes_per_pixel); + if (v_dst_x_in_bytes <= ((uint64_t)(v_dst.len))) { + v_dst = wuffs_base__slice_u8__subslice_i(v_dst, v_dst_x_in_bytes); + } + } + v_c = wuffs_base__peek_u8be__no_bounds_check(iop_a_src); + iop_a_src += 1; + } + if ((v_c & 128) == 0) { + v_src[0] = 0; + } else { + v_src[0] = 255; + } + v_c = ((uint8_t)(((((uint32_t)(v_c)) << 1) & 255))); + wuffs_base__pixel_swizzler__swizzle_interleaved_from_slice(&self->private_impl.f_swizzler, v_dst, wuffs_base__utility__empty_slice_u8(), wuffs_base__make_slice_u8(v_src, 1)); + if (v_dst_bytes_per_pixel <= ((uint64_t)(v_dst.len))) { + v_dst = wuffs_base__slice_u8__subslice_i(v_dst, v_dst_bytes_per_pixel); + } + v_dst_x += 1; + } + v_dst_y += 1; + } + } + self->private_impl.f_call_sequence = 96; + + ok: + self->private_impl.p_do_decode_frame[0] = 0; + goto exit; + } + + goto suspend; + suspend: + self->private_impl.p_do_decode_frame[0] = wuffs_base__status__is_suspension(&status) ? coro_susp_point : 0; + self->private_data.s_do_decode_frame[0].v_dst_bytes_per_pixel = v_dst_bytes_per_pixel; + self->private_data.s_do_decode_frame[0].v_dst_x = v_dst_x; + self->private_data.s_do_decode_frame[0].v_dst_y = v_dst_y; + memcpy(self->private_data.s_do_decode_frame[0].v_src, v_src, sizeof(v_src)); + self->private_data.s_do_decode_frame[0].v_c = v_c; + + goto exit; + exit: + if (a_src && a_src->data.ptr) { + a_src->meta.ri = ((size_t)(iop_a_src - a_src->data.ptr)); + } + + return status; +} + +// -------- func wbmp.decoder.frame_dirty_rect + +WUFFS_BASE__MAYBE_STATIC wuffs_base__rect_ie_u32 +wuffs_wbmp__decoder__frame_dirty_rect( + const wuffs_wbmp__decoder* self) { + if (!self) { + return wuffs_base__utility__empty_rect_ie_u32(); + } + if ((self->private_impl.magic != WUFFS_BASE__MAGIC) && + (self->private_impl.magic != WUFFS_BASE__DISABLED)) { + return wuffs_base__utility__empty_rect_ie_u32(); + } + + return wuffs_base__utility__make_rect_ie_u32( + 0, + 0, + self->private_impl.f_width, + self->private_impl.f_height); +} + +// -------- func wbmp.decoder.num_animation_loops + +WUFFS_BASE__MAYBE_STATIC uint32_t +wuffs_wbmp__decoder__num_animation_loops( + const wuffs_wbmp__decoder* self) { + if (!self) { + return 0; + } + if ((self->private_impl.magic != WUFFS_BASE__MAGIC) && + (self->private_impl.magic != WUFFS_BASE__DISABLED)) { + return 0; + } + + return 0; +} + +// -------- func wbmp.decoder.num_decoded_frame_configs + +WUFFS_BASE__MAYBE_STATIC uint64_t +wuffs_wbmp__decoder__num_decoded_frame_configs( + const wuffs_wbmp__decoder* self) { + if (!self) { + return 0; + } + if ((self->private_impl.magic != WUFFS_BASE__MAGIC) && + (self->private_impl.magic != WUFFS_BASE__DISABLED)) { + return 0; + } + + if (self->private_impl.f_call_sequence > 32) { + return 1; + } + return 0; +} + +// -------- func wbmp.decoder.num_decoded_frames + +WUFFS_BASE__MAYBE_STATIC uint64_t +wuffs_wbmp__decoder__num_decoded_frames( + const wuffs_wbmp__decoder* self) { + if (!self) { + return 0; + } + if ((self->private_impl.magic != WUFFS_BASE__MAGIC) && + (self->private_impl.magic != WUFFS_BASE__DISABLED)) { + return 0; + } + + if (self->private_impl.f_call_sequence > 64) { + return 1; + } + return 0; +} + +// -------- func wbmp.decoder.restart_frame + +WUFFS_BASE__MAYBE_STATIC wuffs_base__status +wuffs_wbmp__decoder__restart_frame( + wuffs_wbmp__decoder* self, + uint64_t a_index, + uint64_t a_io_position) { + if (!self) { + return wuffs_base__make_status(wuffs_base__error__bad_receiver); + } + if (self->private_impl.magic != WUFFS_BASE__MAGIC) { + return wuffs_base__make_status( + (self->private_impl.magic == WUFFS_BASE__DISABLED) + ? wuffs_base__error__disabled_by_previous_error + : wuffs_base__error__initialize_not_called); + } + + if (self->private_impl.f_call_sequence < 32) { + return wuffs_base__make_status(wuffs_base__error__bad_call_sequence); + } + if (a_index != 0) { + return wuffs_base__make_status(wuffs_base__error__bad_argument); + } + self->private_impl.f_call_sequence = 40; + self->private_impl.f_frame_config_io_position = a_io_position; + return wuffs_base__make_status(NULL); +} + +// -------- func wbmp.decoder.set_report_metadata + +WUFFS_BASE__MAYBE_STATIC wuffs_base__empty_struct +wuffs_wbmp__decoder__set_report_metadata( + wuffs_wbmp__decoder* self, + uint32_t a_fourcc, + bool a_report) { + return wuffs_base__make_empty_struct(); +} + +// -------- func wbmp.decoder.tell_me_more + +WUFFS_BASE__MAYBE_STATIC wuffs_base__status +wuffs_wbmp__decoder__tell_me_more( + wuffs_wbmp__decoder* self, + wuffs_base__io_buffer* a_dst, + wuffs_base__more_information* a_minfo, + wuffs_base__io_buffer* a_src) { + if (!self) { + return wuffs_base__make_status(wuffs_base__error__bad_receiver); + } + if (self->private_impl.magic != WUFFS_BASE__MAGIC) { + return wuffs_base__make_status( + (self->private_impl.magic == WUFFS_BASE__DISABLED) + ? wuffs_base__error__disabled_by_previous_error + : wuffs_base__error__initialize_not_called); + } + if (!a_dst || !a_src) { + self->private_impl.magic = WUFFS_BASE__DISABLED; + return wuffs_base__make_status(wuffs_base__error__bad_argument); + } + if ((self->private_impl.active_coroutine != 0) && + (self->private_impl.active_coroutine != 4)) { + self->private_impl.magic = WUFFS_BASE__DISABLED; + return wuffs_base__make_status(wuffs_base__error__interleaved_coroutine_calls); + } + self->private_impl.active_coroutine = 0; + wuffs_base__status status = wuffs_base__make_status(NULL); + + status = wuffs_base__make_status(wuffs_base__error__no_more_information); + goto exit; + + goto ok; + ok: + goto exit; + exit: + if (wuffs_base__status__is_error(&status)) { + self->private_impl.magic = WUFFS_BASE__DISABLED; + } + return status; +} + +// -------- func wbmp.decoder.workbuf_len + +WUFFS_BASE__MAYBE_STATIC wuffs_base__range_ii_u64 +wuffs_wbmp__decoder__workbuf_len( + const wuffs_wbmp__decoder* self) { + if (!self) { + return wuffs_base__utility__empty_range_ii_u64(); + } + if ((self->private_impl.magic != WUFFS_BASE__MAGIC) && + (self->private_impl.magic != WUFFS_BASE__DISABLED)) { + return wuffs_base__utility__empty_range_ii_u64(); + } + + return wuffs_base__utility__make_range_ii_u64(0, 0); +} + +#endif // !defined(WUFFS_CONFIG__MODULES) || defined(WUFFS_CONFIG__MODULE__WBMP) + +#if defined(__cplusplus) && defined(WUFFS_BASE__HAVE_UNIQUE_PTR) + +// ---------------- Auxiliary - Base + +// Auxiliary code is discussed at +// https://github.com/google/wuffs/blob/main/doc/note/auxiliary-code.md + +#if !defined(WUFFS_CONFIG__MODULES) || defined(WUFFS_CONFIG__MODULE__AUX__BASE) + +namespace wuffs_aux { + +namespace sync_io { + +// -------- + +DynIOBuffer::DynIOBuffer(uint64_t max_incl) + : m_buf(wuffs_base__empty_io_buffer()), m_max_incl(max_incl) {} + +DynIOBuffer::~DynIOBuffer() { + if (m_buf.data.ptr) { + free(m_buf.data.ptr); + } +} + +void // +DynIOBuffer::drop() { + if (m_buf.data.ptr) { + free(m_buf.data.ptr); + } + m_buf = wuffs_base__empty_io_buffer(); +} + +DynIOBuffer::GrowResult // +DynIOBuffer::grow(uint64_t min_incl) { + uint64_t n = round_up(min_incl, m_max_incl); + if (n == 0) { + return ((min_incl == 0) && (m_max_incl == 0)) + ? DynIOBuffer::GrowResult::OK + : DynIOBuffer::GrowResult::FailedMaxInclExceeded; + } else if (n > m_buf.data.len) { + uint8_t* ptr = static_cast(realloc(m_buf.data.ptr, n)); + if (!ptr) { + return DynIOBuffer::GrowResult::FailedOutOfMemory; + } + m_buf.data.ptr = ptr; + m_buf.data.len = n; + } + return DynIOBuffer::GrowResult::OK; +} + +// round_up rounds min_incl up, returning the smallest value x satisfying +// (min_incl <= x) and (x <= max_incl) and some other constraints. It returns 0 +// if there is no such x. +// +// When max_incl <= 4096, the other constraints are: +// - (x == max_incl) +// +// When max_incl > 4096, the other constraints are: +// - (x == max_incl) or (x is a power of 2) +// - (x >= 4096) +uint64_t // +DynIOBuffer::round_up(uint64_t min_incl, uint64_t max_incl) { + if (min_incl > max_incl) { + return 0; + } + uint64_t n = 4096; + if (n >= max_incl) { + return max_incl; + } + while (n < min_incl) { + if (n >= (max_incl / 2)) { + return max_incl; + } + n *= 2; + } + return n; +} + +// -------- + +Input::~Input() {} + +IOBuffer* // +Input::BringsItsOwnIOBuffer() { + return nullptr; +} + +// -------- + +FileInput::FileInput(FILE* f) : m_f(f) {} + +std::string // +FileInput::CopyIn(IOBuffer* dst) { + if (!m_f) { + return "wuffs_aux::sync_io::FileInput: nullptr file"; + } else if (!dst) { + return "wuffs_aux::sync_io::FileInput: nullptr IOBuffer"; + } else if (dst->meta.closed) { + return "wuffs_aux::sync_io::FileInput: end of file"; + } else { + dst->compact(); + size_t n = fread(dst->writer_pointer(), 1, dst->writer_length(), m_f); + dst->meta.wi += n; + dst->meta.closed = feof(m_f); + if (ferror(m_f)) { + return "wuffs_aux::sync_io::FileInput: error reading file"; + } + } + return ""; +} + +// -------- + +MemoryInput::MemoryInput(const char* ptr, size_t len) + : m_io(wuffs_base__ptr_u8__reader( + static_cast(static_cast(const_cast(ptr))), + len, + true)) {} + +MemoryInput::MemoryInput(const uint8_t* ptr, size_t len) + : m_io(wuffs_base__ptr_u8__reader(const_cast(ptr), len, true)) {} + +IOBuffer* // +MemoryInput::BringsItsOwnIOBuffer() { + return &m_io; +} + +std::string // +MemoryInput::CopyIn(IOBuffer* dst) { + if (!dst) { + return "wuffs_aux::sync_io::MemoryInput: nullptr IOBuffer"; + } else if (dst->meta.closed) { + return "wuffs_aux::sync_io::MemoryInput: end of file"; + } else if (wuffs_base__slice_u8__overlaps(dst->data, m_io.data)) { + // Treat m_io's data as immutable, so don't compact dst or otherwise write + // to it. + return "wuffs_aux::sync_io::MemoryInput: overlapping buffers"; + } else { + dst->compact(); + size_t nd = dst->writer_length(); + size_t ns = m_io.reader_length(); + size_t n = (nd < ns) ? nd : ns; + memcpy(dst->writer_pointer(), m_io.reader_pointer(), n); + m_io.meta.ri += n; + dst->meta.wi += n; + dst->meta.closed = m_io.reader_length() == 0; + } + return ""; +} + +// -------- + +} // namespace sync_io + +namespace private_impl { + +struct ErrorMessages { + const char* max_incl_metadata_length_exceeded; + const char* out_of_memory; + const char* unexpected_end_of_file; + const char* unsupported_metadata; + const char* unsupported_negative_advance; + + // If adding new "const char*" typed fields to this struct, either add them + // after existing fields or, if re-ordering fields, make sure that you update + // all of the "const private_impl::ErrorMessages FooBarErrorMessages" values + // in all of the sibling *.cc files. + + static inline const char* resolve(const char* s) { + return s ? s : "wuffs_aux::private_impl: unknown error"; + }; +}; + +std::string // +AdvanceIOBufferTo(const ErrorMessages& error_messages, + sync_io::Input& input, + IOBuffer& io_buf, + uint64_t absolute_position) { + if (absolute_position < io_buf.reader_position()) { + return error_messages.resolve(error_messages.unsupported_negative_advance); + } + while (true) { + uint64_t relative_position = absolute_position - io_buf.reader_position(); + if (relative_position <= io_buf.reader_length()) { + io_buf.meta.ri += (size_t)relative_position; + break; + } else if (io_buf.meta.closed) { + return error_messages.resolve(error_messages.unexpected_end_of_file); + } + io_buf.meta.ri = io_buf.meta.wi; + if (!input.BringsItsOwnIOBuffer()) { + io_buf.compact(); + } + std::string error_message = input.CopyIn(&io_buf); + if (!error_message.empty()) { + return error_message; + } + } + return ""; +} + +std::string // +HandleMetadata( + const ErrorMessages& error_messages, + sync_io::Input& input, + wuffs_base__io_buffer& io_buf, + sync_io::DynIOBuffer& raw, + wuffs_base__status (*tell_me_more_func)(void*, + wuffs_base__io_buffer*, + wuffs_base__more_information*, + wuffs_base__io_buffer*), + void* tell_me_more_receiver, + std::string (*handle_metadata_func)(void*, + const wuffs_base__more_information*, + wuffs_base__slice_u8), + void* handle_metadata_receiver) { + wuffs_base__more_information minfo = wuffs_base__empty_more_information(); + // Reset raw but keep its backing array (the raw.m_buf.data slice). + raw.m_buf.meta = wuffs_base__empty_io_buffer_meta(); + + while (true) { + minfo = wuffs_base__empty_more_information(); + wuffs_base__status status = (*tell_me_more_func)( + tell_me_more_receiver, &raw.m_buf, &minfo, &io_buf); + switch (minfo.flavor) { + case 0: + case WUFFS_BASE__MORE_INFORMATION__FLAVOR__METADATA_RAW_TRANSFORM: + case WUFFS_BASE__MORE_INFORMATION__FLAVOR__METADATA_PARSED: + break; + + case WUFFS_BASE__MORE_INFORMATION__FLAVOR__METADATA_RAW_PASSTHROUGH: { + wuffs_base__range_ie_u64 r = minfo.metadata_raw_passthrough__range(); + if (r.is_empty()) { + break; + } + uint64_t num_to_copy = r.length(); + if (num_to_copy > (raw.m_max_incl - raw.m_buf.meta.wi)) { + return error_messages.resolve( + error_messages.max_incl_metadata_length_exceeded); + } else if (num_to_copy > (raw.m_buf.data.len - raw.m_buf.meta.wi)) { + switch (raw.grow(num_to_copy + raw.m_buf.meta.wi)) { + case sync_io::DynIOBuffer::GrowResult::OK: + break; + case sync_io::DynIOBuffer::GrowResult::FailedMaxInclExceeded: + return error_messages.resolve( + error_messages.max_incl_metadata_length_exceeded); + case sync_io::DynIOBuffer::GrowResult::FailedOutOfMemory: + return error_messages.resolve(error_messages.out_of_memory); + } + } + + if (io_buf.reader_position() > r.min_incl) { + return error_messages.resolve(error_messages.unsupported_metadata); + } else { + std::string error_message = + AdvanceIOBufferTo(error_messages, input, io_buf, r.min_incl); + if (!error_message.empty()) { + return error_message; + } + } + + while (true) { + uint64_t n = + wuffs_base__u64__min(num_to_copy, io_buf.reader_length()); + memcpy(raw.m_buf.writer_pointer(), io_buf.reader_pointer(), n); + raw.m_buf.meta.wi += n; + io_buf.meta.ri += n; + num_to_copy -= n; + if (num_to_copy == 0) { + break; + } else if (io_buf.meta.closed) { + return error_messages.resolve( + error_messages.unexpected_end_of_file); + } else if (!input.BringsItsOwnIOBuffer()) { + io_buf.compact(); + } + std::string error_message = input.CopyIn(&io_buf); + if (!error_message.empty()) { + return error_message; + } + } + break; + } + + default: + return error_messages.resolve(error_messages.unsupported_metadata); + } + + if (status.repr == nullptr) { + break; + } else if (status.repr != wuffs_base__suspension__even_more_information) { + if (status.repr != wuffs_base__suspension__short_write) { + return status.message(); + } + switch (raw.grow(wuffs_base__u64__sat_add(raw.m_buf.data.len, 1))) { + case sync_io::DynIOBuffer::GrowResult::OK: + break; + case sync_io::DynIOBuffer::GrowResult::FailedMaxInclExceeded: + return error_messages.resolve( + error_messages.max_incl_metadata_length_exceeded); + case sync_io::DynIOBuffer::GrowResult::FailedOutOfMemory: + return error_messages.resolve(error_messages.out_of_memory); + } + } + } + + return (*handle_metadata_func)(handle_metadata_receiver, &minfo, + raw.m_buf.reader_slice()); +} + +} // namespace private_impl + +} // namespace wuffs_aux + +#endif // !defined(WUFFS_CONFIG__MODULES) || + // defined(WUFFS_CONFIG__MODULE__AUX__BASE) + +// ---------------- Auxiliary - CBOR + +#if !defined(WUFFS_CONFIG__MODULES) || defined(WUFFS_CONFIG__MODULE__AUX__CBOR) + +#include + +namespace wuffs_aux { + +DecodeCborResult::DecodeCborResult(std::string&& error_message0, + uint64_t cursor_position0) + : error_message(std::move(error_message0)), + cursor_position(cursor_position0) {} + +DecodeCborCallbacks::~DecodeCborCallbacks() {} + +void // +DecodeCborCallbacks::Done(DecodeCborResult& result, + sync_io::Input& input, + IOBuffer& buffer) {} + +DecodeCborArgQuirks::DecodeCborArgQuirks(wuffs_base__slice_u32 repr0) + : repr(repr0) {} + +DecodeCborArgQuirks::DecodeCborArgQuirks(uint32_t* ptr0, size_t len0) + : repr(wuffs_base__make_slice_u32(ptr0, len0)) {} + +DecodeCborArgQuirks // +DecodeCborArgQuirks::DefaultValue() { + return DecodeCborArgQuirks(wuffs_base__empty_slice_u32()); +} + +DecodeCborResult // +DecodeCbor(DecodeCborCallbacks& callbacks, + sync_io::Input& input, + DecodeCborArgQuirks quirks) { + // Prepare the wuffs_base__io_buffer and the resultant error_message. + wuffs_base__io_buffer* io_buf = input.BringsItsOwnIOBuffer(); + wuffs_base__io_buffer fallback_io_buf = wuffs_base__empty_io_buffer(); + std::unique_ptr fallback_io_array(nullptr); + if (!io_buf) { + fallback_io_array = std::unique_ptr(new uint8_t[4096]); + fallback_io_buf = wuffs_base__ptr_u8__writer(fallback_io_array.get(), 4096); + io_buf = &fallback_io_buf; + } + // cursor_index is discussed at + // https://nigeltao.github.io/blog/2020/jsonptr.html#the-cursor-index + size_t cursor_index = 0; + std::string ret_error_message; + std::string io_error_message; + + do { + // Prepare the low-level CBOR decoder. + wuffs_cbor__decoder::unique_ptr dec = wuffs_cbor__decoder::alloc(); + if (!dec) { + ret_error_message = "wuffs_aux::DecodeCbor: out of memory"; + goto done; + } + for (size_t i = 0; i < quirks.repr.len; i++) { + dec->set_quirk_enabled(quirks.repr.ptr[i], true); + } + + // Prepare the wuffs_base__tok_buffer. 256 tokens is 2KiB. + wuffs_base__token tok_array[256]; + wuffs_base__token_buffer tok_buf = + wuffs_base__slice_token__writer(wuffs_base__make_slice_token( + &tok_array[0], (sizeof(tok_array) / sizeof(tok_array[0])))); + wuffs_base__status tok_status = wuffs_base__make_status(nullptr); + + // Prepare other state. + int32_t depth = 0; + std::string str; + int64_t extension_category = 0; + uint64_t extension_detail = 0; + + // Valid token's VBCs range in 0 ..= 15. Values over that are for tokens + // from outside of the base package, such as the CBOR package. + constexpr int64_t EXT_CAT__CBOR_TAG = 16; + + // Loop, doing these two things: + // 1. Get the next token. + // 2. Process that token. + while (true) { + // 1. Get the next token. + + while (tok_buf.meta.ri >= tok_buf.meta.wi) { + if (tok_status.repr == nullptr) { + // No-op. + } else if (tok_status.repr == wuffs_base__suspension__short_write) { + tok_buf.compact(); + } else if (tok_status.repr == wuffs_base__suspension__short_read) { + // Read from input to io_buf. + if (!io_error_message.empty()) { + ret_error_message = std::move(io_error_message); + goto done; + } else if (cursor_index != io_buf->meta.ri) { + ret_error_message = + "wuffs_aux::DecodeCbor: internal error: bad cursor_index"; + goto done; + } else if (io_buf->meta.closed) { + ret_error_message = + "wuffs_aux::DecodeCbor: internal error: io_buf is closed"; + goto done; + } + io_buf->compact(); + if (io_buf->meta.wi >= io_buf->data.len) { + ret_error_message = + "wuffs_aux::DecodeCbor: internal error: io_buf is full"; + goto done; + } + cursor_index = io_buf->meta.ri; + io_error_message = input.CopyIn(io_buf); + } else { + ret_error_message = tok_status.message(); + goto done; + } + + if (WUFFS_CBOR__DECODER_WORKBUF_LEN_MAX_INCL_WORST_CASE != 0) { + ret_error_message = + "wuffs_aux::DecodeCbor: internal error: bad WORKBUF_LEN"; + goto done; + } + wuffs_base__slice_u8 work_buf = wuffs_base__empty_slice_u8(); + tok_status = dec->decode_tokens(&tok_buf, io_buf, work_buf); + if ((tok_buf.meta.ri > tok_buf.meta.wi) || + (tok_buf.meta.wi > tok_buf.data.len) || + (io_buf->meta.ri > io_buf->meta.wi) || + (io_buf->meta.wi > io_buf->data.len)) { + ret_error_message = + "wuffs_aux::DecodeCbor: internal error: bad buffer indexes"; + goto done; + } + } + + wuffs_base__token token = tok_buf.data.ptr[tok_buf.meta.ri++]; + uint64_t token_len = token.length(); + if ((io_buf->meta.ri < cursor_index) || + ((io_buf->meta.ri - cursor_index) < token_len)) { + ret_error_message = + "wuffs_aux::DecodeCbor: internal error: bad token indexes"; + goto done; + } + uint8_t* token_ptr = io_buf->data.ptr + cursor_index; + cursor_index += static_cast(token_len); + + // 2. Process that token. + + uint64_t vbd = token.value_base_detail(); + + if (extension_category != 0) { + int64_t ext = token.value_extension(); + if ((ext >= 0) && !token.continued()) { + extension_detail = (extension_detail + << WUFFS_BASE__TOKEN__VALUE_EXTENSION__NUM_BITS) | + static_cast(ext); + switch (extension_category) { + case WUFFS_BASE__TOKEN__VBC__INLINE_INTEGER_SIGNED: + extension_category = 0; + ret_error_message = + callbacks.AppendI64(static_cast(extension_detail)); + goto parsed_a_value; + case WUFFS_BASE__TOKEN__VBC__INLINE_INTEGER_UNSIGNED: + extension_category = 0; + ret_error_message = callbacks.AppendU64(extension_detail); + goto parsed_a_value; + case EXT_CAT__CBOR_TAG: + extension_category = 0; + ret_error_message = callbacks.AppendCborTag(extension_detail); + if (!ret_error_message.empty()) { + goto done; + } + continue; + } + } + ret_error_message = + "wuffs_aux::DecodeCbor: internal error: bad extended token"; + goto done; + } + + switch (token.value_base_category()) { + case WUFFS_BASE__TOKEN__VBC__FILLER: + continue; + + case WUFFS_BASE__TOKEN__VBC__STRUCTURE: { + if (vbd & WUFFS_BASE__TOKEN__VBD__STRUCTURE__PUSH) { + ret_error_message = callbacks.Push(static_cast(vbd)); + if (!ret_error_message.empty()) { + goto done; + } + depth++; + if (depth > WUFFS_CBOR__DECODER_DEPTH_MAX_INCL) { + ret_error_message = + "wuffs_aux::DecodeCbor: internal error: bad depth"; + goto done; + } + continue; + } + ret_error_message = callbacks.Pop(static_cast(vbd)); + depth--; + if (depth < 0) { + ret_error_message = + "wuffs_aux::DecodeCbor: internal error: bad depth"; + goto done; + } + goto parsed_a_value; + } + + case WUFFS_BASE__TOKEN__VBC__STRING: { + if (vbd & WUFFS_BASE__TOKEN__VBD__STRING__CONVERT_0_DST_1_SRC_DROP) { + // No-op. + } else if (vbd & + WUFFS_BASE__TOKEN__VBD__STRING__CONVERT_1_DST_1_SRC_COPY) { + const char* ptr = // Convert from (uint8_t*). + static_cast(static_cast(token_ptr)); + str.append(ptr, static_cast(token_len)); + } else { + goto fail; + } + if (token.continued()) { + continue; + } + ret_error_message = + (vbd & WUFFS_BASE__TOKEN__VBD__STRING__CHAIN_MUST_BE_UTF_8) + ? callbacks.AppendTextString(std::move(str)) + : callbacks.AppendByteString(std::move(str)); + str.clear(); + goto parsed_a_value; + } + + case WUFFS_BASE__TOKEN__VBC__UNICODE_CODE_POINT: { + uint8_t u[WUFFS_BASE__UTF_8__BYTE_LENGTH__MAX_INCL]; + size_t n = wuffs_base__utf_8__encode( + wuffs_base__make_slice_u8( + &u[0], WUFFS_BASE__UTF_8__BYTE_LENGTH__MAX_INCL), + static_cast(vbd)); + const char* ptr = // Convert from (uint8_t*). + static_cast(static_cast(&u[0])); + str.append(ptr, n); + if (token.continued()) { + continue; + } + goto fail; + } + + case WUFFS_BASE__TOKEN__VBC__LITERAL: { + if (vbd & WUFFS_BASE__TOKEN__VBD__LITERAL__NULL) { + ret_error_message = callbacks.AppendNull(); + } else if (vbd & WUFFS_BASE__TOKEN__VBD__LITERAL__UNDEFINED) { + ret_error_message = callbacks.AppendUndefined(); + } else { + ret_error_message = callbacks.AppendBool( + vbd & WUFFS_BASE__TOKEN__VBD__LITERAL__TRUE); + } + goto parsed_a_value; + } + + case WUFFS_BASE__TOKEN__VBC__NUMBER: { + const uint64_t cfp_fbbe_fifb = + WUFFS_BASE__TOKEN__VBD__NUMBER__CONTENT_FLOATING_POINT | + WUFFS_BASE__TOKEN__VBD__NUMBER__FORMAT_BINARY_BIG_ENDIAN | + WUFFS_BASE__TOKEN__VBD__NUMBER__FORMAT_IGNORE_FIRST_BYTE; + if ((vbd & cfp_fbbe_fifb) == cfp_fbbe_fifb) { + double f; + switch (token_len) { + case 3: + f = wuffs_base__ieee_754_bit_representation__from_u16_to_f64( + wuffs_base__peek_u16be__no_bounds_check(token_ptr + 1)); + break; + case 5: + f = wuffs_base__ieee_754_bit_representation__from_u32_to_f64( + wuffs_base__peek_u32be__no_bounds_check(token_ptr + 1)); + break; + case 9: + f = wuffs_base__ieee_754_bit_representation__from_u64_to_f64( + wuffs_base__peek_u64be__no_bounds_check(token_ptr + 1)); + break; + default: + goto fail; + } + ret_error_message = callbacks.AppendF64(f); + goto parsed_a_value; + } + goto fail; + } + + case WUFFS_BASE__TOKEN__VBC__INLINE_INTEGER_SIGNED: { + if (token.continued()) { + extension_category = WUFFS_BASE__TOKEN__VBC__INLINE_INTEGER_SIGNED; + extension_detail = + static_cast(token.value_base_detail__sign_extended()); + continue; + } + ret_error_message = + callbacks.AppendI64(token.value_base_detail__sign_extended()); + goto parsed_a_value; + } + + case WUFFS_BASE__TOKEN__VBC__INLINE_INTEGER_UNSIGNED: { + if (token.continued()) { + extension_category = + WUFFS_BASE__TOKEN__VBC__INLINE_INTEGER_UNSIGNED; + extension_detail = vbd; + continue; + } + ret_error_message = callbacks.AppendU64(vbd); + goto parsed_a_value; + } + } + + if (token.value_major() == WUFFS_CBOR__TOKEN_VALUE_MAJOR) { + uint64_t value_minor = token.value_minor(); + if (value_minor & WUFFS_CBOR__TOKEN_VALUE_MINOR__MINUS_1_MINUS_X) { + if (token_len == 9) { + ret_error_message = callbacks.AppendMinus1MinusX( + wuffs_base__peek_u64be__no_bounds_check(token_ptr + 1)); + goto parsed_a_value; + } + } else if (value_minor & WUFFS_CBOR__TOKEN_VALUE_MINOR__SIMPLE_VALUE) { + ret_error_message = + callbacks.AppendCborSimpleValue(static_cast( + value_minor & WUFFS_CBOR__TOKEN_VALUE_MINOR__DETAIL_MASK)); + goto parsed_a_value; + } else if (value_minor & WUFFS_CBOR__TOKEN_VALUE_MINOR__TAG) { + if (token.continued()) { + extension_category = EXT_CAT__CBOR_TAG; + extension_detail = + value_minor & WUFFS_CBOR__TOKEN_VALUE_MINOR__DETAIL_MASK; + continue; + } + ret_error_message = callbacks.AppendCborTag( + value_minor & WUFFS_CBOR__TOKEN_VALUE_MINOR__DETAIL_MASK); + if (!ret_error_message.empty()) { + goto done; + } + continue; + } + } + + fail: + ret_error_message = + "wuffs_aux::DecodeCbor: internal error: unexpected token"; + goto done; + + parsed_a_value: + if (!ret_error_message.empty() || (depth == 0)) { + goto done; + } + } + } while (false); + +done: + DecodeCborResult result( + std::move(ret_error_message), + wuffs_base__u64__sat_add(io_buf->meta.pos, cursor_index)); + callbacks.Done(result, input, *io_buf); + return result; +} + +} // namespace wuffs_aux + +#endif // !defined(WUFFS_CONFIG__MODULES) || + // defined(WUFFS_CONFIG__MODULE__AUX__CBOR) + +// ---------------- Auxiliary - Image + +#if !defined(WUFFS_CONFIG__MODULES) || defined(WUFFS_CONFIG__MODULE__AUX__IMAGE) + +#include + +namespace wuffs_aux { + +DecodeImageResult::DecodeImageResult(MemOwner&& pixbuf_mem_owner0, + wuffs_base__pixel_buffer pixbuf0, + std::string&& error_message0) + : pixbuf_mem_owner(std::move(pixbuf_mem_owner0)), + pixbuf(pixbuf0), + error_message(std::move(error_message0)) {} + +DecodeImageResult::DecodeImageResult(std::string&& error_message0) + : pixbuf_mem_owner(nullptr, &free), + pixbuf(wuffs_base__null_pixel_buffer()), + error_message(std::move(error_message0)) {} + +DecodeImageCallbacks::~DecodeImageCallbacks() {} + +DecodeImageCallbacks::AllocPixbufResult::AllocPixbufResult( + MemOwner&& mem_owner0, + wuffs_base__pixel_buffer pixbuf0) + : mem_owner(std::move(mem_owner0)), pixbuf(pixbuf0), error_message("") {} + +DecodeImageCallbacks::AllocPixbufResult::AllocPixbufResult( + std::string&& error_message0) + : mem_owner(nullptr, &free), + pixbuf(wuffs_base__null_pixel_buffer()), + error_message(std::move(error_message0)) {} + +DecodeImageCallbacks::AllocWorkbufResult::AllocWorkbufResult( + MemOwner&& mem_owner0, + wuffs_base__slice_u8 workbuf0) + : mem_owner(std::move(mem_owner0)), workbuf(workbuf0), error_message("") {} + +DecodeImageCallbacks::AllocWorkbufResult::AllocWorkbufResult( + std::string&& error_message0) + : mem_owner(nullptr, &free), + workbuf(wuffs_base__empty_slice_u8()), + error_message(std::move(error_message0)) {} + +wuffs_base__image_decoder::unique_ptr // +DecodeImageCallbacks::SelectDecoder(uint32_t fourcc, + wuffs_base__slice_u8 prefix_data, + bool prefix_closed) { + switch (fourcc) { +#if !defined(WUFFS_CONFIG__MODULES) || defined(WUFFS_CONFIG__MODULE__BMP) + case WUFFS_BASE__FOURCC__BMP: + return wuffs_bmp__decoder::alloc_as__wuffs_base__image_decoder(); +#endif + +#if !defined(WUFFS_CONFIG__MODULES) || defined(WUFFS_CONFIG__MODULE__GIF) + case WUFFS_BASE__FOURCC__GIF: + return wuffs_gif__decoder::alloc_as__wuffs_base__image_decoder(); +#endif + +#if !defined(WUFFS_CONFIG__MODULES) || defined(WUFFS_CONFIG__MODULE__NIE) + case WUFFS_BASE__FOURCC__NIE: + return wuffs_nie__decoder::alloc_as__wuffs_base__image_decoder(); +#endif + +#if !defined(WUFFS_CONFIG__MODULES) || defined(WUFFS_CONFIG__MODULE__PNG) + case WUFFS_BASE__FOURCC__PNG: { + auto dec = wuffs_png__decoder::alloc_as__wuffs_base__image_decoder(); + // Favor faster decodes over rejecting invalid checksums. + dec->set_quirk_enabled(WUFFS_BASE__QUIRK_IGNORE_CHECKSUM, true); + return dec; + } +#endif + +#if !defined(WUFFS_CONFIG__MODULES) || defined(WUFFS_CONFIG__MODULE__TGA) + case WUFFS_BASE__FOURCC__TGA: + return wuffs_tga__decoder::alloc_as__wuffs_base__image_decoder(); +#endif + +#if !defined(WUFFS_CONFIG__MODULES) || defined(WUFFS_CONFIG__MODULE__WBMP) + case WUFFS_BASE__FOURCC__WBMP: + return wuffs_wbmp__decoder::alloc_as__wuffs_base__image_decoder(); +#endif + } + + return wuffs_base__image_decoder::unique_ptr(nullptr, &free); +} + +std::string // +DecodeImageCallbacks::HandleMetadata(const wuffs_base__more_information& minfo, + wuffs_base__slice_u8 raw) { + return ""; +} + +wuffs_base__pixel_format // +DecodeImageCallbacks::SelectPixfmt( + const wuffs_base__image_config& image_config) { + return wuffs_base__make_pixel_format(WUFFS_BASE__PIXEL_FORMAT__BGRA_PREMUL); +} + +DecodeImageCallbacks::AllocPixbufResult // +DecodeImageCallbacks::AllocPixbuf(const wuffs_base__image_config& image_config, + bool allow_uninitialized_memory) { + uint32_t w = image_config.pixcfg.width(); + uint32_t h = image_config.pixcfg.height(); + if ((w == 0) || (h == 0)) { + return AllocPixbufResult(""); + } + uint64_t len = image_config.pixcfg.pixbuf_len(); + if ((len == 0) || (SIZE_MAX < len)) { + return AllocPixbufResult(DecodeImage_UnsupportedPixelConfiguration); + } + void* ptr = + allow_uninitialized_memory ? malloc((size_t)len) : calloc((size_t)len, 1); + if (!ptr) { + return AllocPixbufResult(DecodeImage_OutOfMemory); + } + wuffs_base__pixel_buffer pixbuf; + wuffs_base__status status = pixbuf.set_from_slice( + &image_config.pixcfg, + wuffs_base__make_slice_u8((uint8_t*)ptr, (size_t)len)); + if (!status.is_ok()) { + free(ptr); + return AllocPixbufResult(status.message()); + } + return AllocPixbufResult(MemOwner(ptr, &free), pixbuf); +} + +DecodeImageCallbacks::AllocWorkbufResult // +DecodeImageCallbacks::AllocWorkbuf(wuffs_base__range_ii_u64 len_range, + bool allow_uninitialized_memory) { + uint64_t len = len_range.max_incl; + if (len == 0) { + return AllocWorkbufResult(""); + } else if (SIZE_MAX < len) { + return AllocWorkbufResult(DecodeImage_OutOfMemory); + } + void* ptr = + allow_uninitialized_memory ? malloc((size_t)len) : calloc((size_t)len, 1); + if (!ptr) { + return AllocWorkbufResult(DecodeImage_OutOfMemory); + } + return AllocWorkbufResult( + MemOwner(ptr, &free), + wuffs_base__make_slice_u8((uint8_t*)ptr, (size_t)len)); +} + +void // +DecodeImageCallbacks::Done( + DecodeImageResult& result, + sync_io::Input& input, + IOBuffer& buffer, + wuffs_base__image_decoder::unique_ptr image_decoder) {} + +const char DecodeImage_BufferIsTooShort[] = // + "wuffs_aux::DecodeImage: buffer is too short"; +const char DecodeImage_MaxInclDimensionExceeded[] = // + "wuffs_aux::DecodeImage: max_incl_dimension exceeded"; +const char DecodeImage_MaxInclMetadataLengthExceeded[] = // + "wuffs_aux::DecodeImage: max_incl_metadata_length exceeded"; +const char DecodeImage_OutOfMemory[] = // + "wuffs_aux::DecodeImage: out of memory"; +const char DecodeImage_UnexpectedEndOfFile[] = // + "wuffs_aux::DecodeImage: unexpected end of file"; +const char DecodeImage_UnsupportedImageFormat[] = // + "wuffs_aux::DecodeImage: unsupported image format"; +const char DecodeImage_UnsupportedMetadata[] = // + "wuffs_aux::DecodeImage: unsupported metadata"; +const char DecodeImage_UnsupportedPixelBlend[] = // + "wuffs_aux::DecodeImage: unsupported pixel blend"; +const char DecodeImage_UnsupportedPixelConfiguration[] = // + "wuffs_aux::DecodeImage: unsupported pixel configuration"; +const char DecodeImage_UnsupportedPixelFormat[] = // + "wuffs_aux::DecodeImage: unsupported pixel format"; + +DecodeImageArgQuirks::DecodeImageArgQuirks(wuffs_base__slice_u32 repr0) + : repr(repr0) {} + +DecodeImageArgQuirks::DecodeImageArgQuirks(uint32_t* ptr0, size_t len0) + : repr(wuffs_base__make_slice_u32(ptr0, len0)) {} + +DecodeImageArgQuirks // +DecodeImageArgQuirks::DefaultValue() { + return DecodeImageArgQuirks(wuffs_base__empty_slice_u32()); +} + +DecodeImageArgFlags::DecodeImageArgFlags(uint64_t repr0) : repr(repr0) {} + +DecodeImageArgFlags // +DecodeImageArgFlags::DefaultValue() { + return DecodeImageArgFlags(0); +} + +DecodeImageArgPixelBlend::DecodeImageArgPixelBlend( + wuffs_base__pixel_blend repr0) + : repr(repr0) {} + +DecodeImageArgPixelBlend // +DecodeImageArgPixelBlend::DefaultValue() { + return DecodeImageArgPixelBlend(WUFFS_BASE__PIXEL_BLEND__SRC); +} + +DecodeImageArgBackgroundColor::DecodeImageArgBackgroundColor( + wuffs_base__color_u32_argb_premul repr0) + : repr(repr0) {} + +DecodeImageArgBackgroundColor // +DecodeImageArgBackgroundColor::DefaultValue() { + return DecodeImageArgBackgroundColor(1); +} + +DecodeImageArgMaxInclDimension::DecodeImageArgMaxInclDimension(uint32_t repr0) + : repr(repr0) {} + +DecodeImageArgMaxInclDimension // +DecodeImageArgMaxInclDimension::DefaultValue() { + return DecodeImageArgMaxInclDimension(1048575); +} + +DecodeImageArgMaxInclMetadataLength::DecodeImageArgMaxInclMetadataLength( + uint64_t repr0) + : repr(repr0) {} + +DecodeImageArgMaxInclMetadataLength // +DecodeImageArgMaxInclMetadataLength::DefaultValue() { + return DecodeImageArgMaxInclMetadataLength(16777215); +} + +// -------- + +namespace { + +const private_impl::ErrorMessages DecodeImageErrorMessages = { + DecodeImage_MaxInclMetadataLengthExceeded, // + DecodeImage_OutOfMemory, // + DecodeImage_UnexpectedEndOfFile, // + DecodeImage_UnsupportedMetadata, // + DecodeImage_UnsupportedImageFormat, // +}; + +std::string // +DecodeImageAdvanceIOBufferTo(sync_io::Input& input, + wuffs_base__io_buffer& io_buf, + uint64_t absolute_position) { + return private_impl::AdvanceIOBufferTo(DecodeImageErrorMessages, input, + io_buf, absolute_position); +} + +wuffs_base__status // +DIHM0(void* self, + wuffs_base__io_buffer* a_dst, + wuffs_base__more_information* a_minfo, + wuffs_base__io_buffer* a_src) { + return wuffs_base__image_decoder__tell_me_more( + static_cast(self), a_dst, a_minfo, a_src); +} + +std::string // +DIHM1(void* self, + const wuffs_base__more_information* minfo, + wuffs_base__slice_u8 raw) { + return static_cast(self)->HandleMetadata(*minfo, raw); +} + +std::string // +DecodeImageHandleMetadata(wuffs_base__image_decoder::unique_ptr& image_decoder, + DecodeImageCallbacks& callbacks, + sync_io::Input& input, + wuffs_base__io_buffer& io_buf, + sync_io::DynIOBuffer& raw_metadata_buf) { + return private_impl::HandleMetadata(DecodeImageErrorMessages, input, io_buf, + raw_metadata_buf, DIHM0, + static_cast(image_decoder.get()), + DIHM1, static_cast(&callbacks)); +} + +DecodeImageResult // +DecodeImage0(wuffs_base__image_decoder::unique_ptr& image_decoder, + DecodeImageCallbacks& callbacks, + sync_io::Input& input, + wuffs_base__io_buffer& io_buf, + wuffs_base__slice_u32 quirks, + uint64_t flags, + wuffs_base__pixel_blend pixel_blend, + wuffs_base__color_u32_argb_premul background_color, + uint32_t max_incl_dimension, + uint64_t max_incl_metadata_length) { + // Check args. + switch (pixel_blend) { + case WUFFS_BASE__PIXEL_BLEND__SRC: + case WUFFS_BASE__PIXEL_BLEND__SRC_OVER: + break; + default: + return DecodeImageResult(DecodeImage_UnsupportedPixelBlend); + } + + wuffs_base__image_config image_config = wuffs_base__null_image_config(); + sync_io::DynIOBuffer raw_metadata_buf(max_incl_metadata_length); + uint64_t start_pos = io_buf.reader_position(); + bool interested_in_metadata_after_the_frame = false; + bool redirected = false; + int32_t fourcc = 0; +redirect: + do { + // Determine the image format. + if (!redirected) { + while (true) { + fourcc = wuffs_base__magic_number_guess_fourcc(io_buf.reader_slice(), + io_buf.meta.closed); + if (fourcc > 0) { + break; + } else if ((fourcc == 0) && (io_buf.reader_length() >= 64)) { + // Having (fourcc == 0) means that Wuffs' built in MIME sniffer + // didn't recognize the image format. Nonetheless, custom callbacks + // may still be able to do their own MIME sniffing, for exotic image + // types. We try to give them at least 64 bytes of prefix data when + // one-shot-calling callbacks.SelectDecoder. There is no mechanism + // for the callbacks to request a longer prefix. + break; + } else if (io_buf.meta.closed || (io_buf.writer_length() == 0)) { + fourcc = 0; + break; + } + std::string error_message = input.CopyIn(&io_buf); + if (!error_message.empty()) { + return DecodeImageResult(std::move(error_message)); + } + } + } else { + wuffs_base__io_buffer empty = wuffs_base__empty_io_buffer(); + wuffs_base__more_information minfo = wuffs_base__empty_more_information(); + wuffs_base__status tmm_status = + image_decoder->tell_me_more(&empty, &minfo, &io_buf); + if (tmm_status.repr != nullptr) { + return DecodeImageResult(tmm_status.message()); + } + if (minfo.flavor != WUFFS_BASE__MORE_INFORMATION__FLAVOR__IO_REDIRECT) { + return DecodeImageResult(DecodeImage_UnsupportedImageFormat); + } + uint64_t pos = minfo.io_redirect__range().min_incl; + if (pos <= start_pos) { + // Redirects must go forward. + return DecodeImageResult(DecodeImage_UnsupportedImageFormat); + } + std::string error_message = + DecodeImageAdvanceIOBufferTo(input, io_buf, pos); + if (!error_message.empty()) { + return DecodeImageResult(std::move(error_message)); + } + fourcc = (int32_t)(minfo.io_redirect__fourcc()); + if (fourcc == 0) { + return DecodeImageResult(DecodeImage_UnsupportedImageFormat); + } + image_decoder.reset(); + } + + // Select the image decoder. + image_decoder = callbacks.SelectDecoder( + (uint32_t)fourcc, io_buf.reader_slice(), io_buf.meta.closed); + if (!image_decoder) { + return DecodeImageResult(DecodeImage_UnsupportedImageFormat); + } + + // Apply quirks. + for (size_t i = 0; i < quirks.len; i++) { + image_decoder->set_quirk_enabled(quirks.ptr[i], true); + } + + // Apply flags. + if (flags != 0) { + if (flags & DecodeImageArgFlags::REPORT_METADATA_CHRM) { + image_decoder->set_report_metadata(WUFFS_BASE__FOURCC__CHRM, true); + } + if (flags & DecodeImageArgFlags::REPORT_METADATA_EXIF) { + interested_in_metadata_after_the_frame = true; + image_decoder->set_report_metadata(WUFFS_BASE__FOURCC__EXIF, true); + } + if (flags & DecodeImageArgFlags::REPORT_METADATA_GAMA) { + image_decoder->set_report_metadata(WUFFS_BASE__FOURCC__GAMA, true); + } + if (flags & DecodeImageArgFlags::REPORT_METADATA_ICCP) { + image_decoder->set_report_metadata(WUFFS_BASE__FOURCC__ICCP, true); + } + if (flags & DecodeImageArgFlags::REPORT_METADATA_KVP) { + interested_in_metadata_after_the_frame = true; + image_decoder->set_report_metadata(WUFFS_BASE__FOURCC__KVP, true); + } + if (flags & DecodeImageArgFlags::REPORT_METADATA_SRGB) { + image_decoder->set_report_metadata(WUFFS_BASE__FOURCC__SRGB, true); + } + if (flags & DecodeImageArgFlags::REPORT_METADATA_XMP) { + interested_in_metadata_after_the_frame = true; + image_decoder->set_report_metadata(WUFFS_BASE__FOURCC__XMP, true); + } + } + + // Decode the image config. + while (true) { + wuffs_base__status id_dic_status = + image_decoder->decode_image_config(&image_config, &io_buf); + if (id_dic_status.repr == nullptr) { + break; + } else if (id_dic_status.repr == wuffs_base__note__i_o_redirect) { + if (redirected) { + return DecodeImageResult(DecodeImage_UnsupportedImageFormat); + } + redirected = true; + goto redirect; + } else if (id_dic_status.repr == wuffs_base__note__metadata_reported) { + std::string error_message = DecodeImageHandleMetadata( + image_decoder, callbacks, input, io_buf, raw_metadata_buf); + if (!error_message.empty()) { + return DecodeImageResult(std::move(error_message)); + } + } else if (id_dic_status.repr != wuffs_base__suspension__short_read) { + return DecodeImageResult(id_dic_status.message()); + } else if (io_buf.meta.closed) { + return DecodeImageResult(DecodeImage_UnexpectedEndOfFile); + } else { + std::string error_message = input.CopyIn(&io_buf); + if (!error_message.empty()) { + return DecodeImageResult(std::move(error_message)); + } + } + } + } while (false); + if (!interested_in_metadata_after_the_frame) { + raw_metadata_buf.drop(); + } + + // Select the pixel format. + uint32_t w = image_config.pixcfg.width(); + uint32_t h = image_config.pixcfg.height(); + if ((w > max_incl_dimension) || (h > max_incl_dimension)) { + return DecodeImageResult(DecodeImage_MaxInclDimensionExceeded); + } + wuffs_base__pixel_format pixel_format = callbacks.SelectPixfmt(image_config); + if (pixel_format.repr != image_config.pixcfg.pixel_format().repr) { + switch (pixel_format.repr) { + case WUFFS_BASE__PIXEL_FORMAT__BGR_565: + case WUFFS_BASE__PIXEL_FORMAT__BGR: + case WUFFS_BASE__PIXEL_FORMAT__BGRA_NONPREMUL: + case WUFFS_BASE__PIXEL_FORMAT__BGRA_NONPREMUL_4X16LE: + case WUFFS_BASE__PIXEL_FORMAT__BGRA_PREMUL: + case WUFFS_BASE__PIXEL_FORMAT__RGBA_NONPREMUL: + case WUFFS_BASE__PIXEL_FORMAT__RGBA_PREMUL: + break; + default: + return DecodeImageResult(DecodeImage_UnsupportedPixelFormat); + } + image_config.pixcfg.set(pixel_format.repr, + WUFFS_BASE__PIXEL_SUBSAMPLING__NONE, w, h); + } + + // Allocate the pixel buffer. + bool valid_background_color = + wuffs_base__color_u32_argb_premul__is_valid(background_color); + DecodeImageCallbacks::AllocPixbufResult alloc_pixbuf_result = + callbacks.AllocPixbuf(image_config, valid_background_color); + if (!alloc_pixbuf_result.error_message.empty()) { + return DecodeImageResult(std::move(alloc_pixbuf_result.error_message)); + } + wuffs_base__pixel_buffer pixel_buffer = alloc_pixbuf_result.pixbuf; + if (valid_background_color) { + wuffs_base__status pb_scufr_status = pixel_buffer.set_color_u32_fill_rect( + pixel_buffer.pixcfg.bounds(), background_color); + if (pb_scufr_status.repr != nullptr) { + return DecodeImageResult(pb_scufr_status.message()); + } + } + + // Allocate the work buffer. Wuffs' decoders conventionally assume that this + // can be uninitialized memory. + wuffs_base__range_ii_u64 workbuf_len = image_decoder->workbuf_len(); + DecodeImageCallbacks::AllocWorkbufResult alloc_workbuf_result = + callbacks.AllocWorkbuf(workbuf_len, true); + if (!alloc_workbuf_result.error_message.empty()) { + return DecodeImageResult(std::move(alloc_workbuf_result.error_message)); + } else if (alloc_workbuf_result.workbuf.len < workbuf_len.min_incl) { + return DecodeImageResult(DecodeImage_BufferIsTooShort); + } + + // Decode the frame config. + wuffs_base__frame_config frame_config = wuffs_base__null_frame_config(); + while (true) { + wuffs_base__status id_dfc_status = + image_decoder->decode_frame_config(&frame_config, &io_buf); + if (id_dfc_status.repr == nullptr) { + break; + } else if (id_dfc_status.repr == wuffs_base__note__metadata_reported) { + std::string error_message = DecodeImageHandleMetadata( + image_decoder, callbacks, input, io_buf, raw_metadata_buf); + if (!error_message.empty()) { + return DecodeImageResult(std::move(error_message)); + } + } else if (id_dfc_status.repr != wuffs_base__suspension__short_read) { + return DecodeImageResult(id_dfc_status.message()); + } else if (io_buf.meta.closed) { + return DecodeImageResult(DecodeImage_UnexpectedEndOfFile); + } else { + std::string error_message = input.CopyIn(&io_buf); + if (!error_message.empty()) { + return DecodeImageResult(std::move(error_message)); + } + } + } + + // Decode the frame (the pixels). + // + // From here on, always returns the pixel_buffer. If we get this far, we can + // still display a partial image, even if we encounter an error. + std::string message(""); + if ((pixel_blend == WUFFS_BASE__PIXEL_BLEND__SRC_OVER) && + frame_config.overwrite_instead_of_blend()) { + pixel_blend = WUFFS_BASE__PIXEL_BLEND__SRC; + } + while (true) { + wuffs_base__status id_df_status = + image_decoder->decode_frame(&pixel_buffer, &io_buf, pixel_blend, + alloc_workbuf_result.workbuf, nullptr); + if (id_df_status.repr == nullptr) { + break; + } else if (id_df_status.repr != wuffs_base__suspension__short_read) { + message = id_df_status.message(); + break; + } else if (io_buf.meta.closed) { + message = DecodeImage_UnexpectedEndOfFile; + break; + } else { + std::string error_message = input.CopyIn(&io_buf); + if (!error_message.empty()) { + message = std::move(error_message); + break; + } + } + } + + // Decode any metadata after the frame. + if (interested_in_metadata_after_the_frame) { + while (true) { + wuffs_base__status id_dfc_status = + image_decoder->decode_frame_config(NULL, &io_buf); + if (id_dfc_status.repr == wuffs_base__note__end_of_data) { + break; + } else if (id_dfc_status.repr == nullptr) { + continue; + } else if (id_dfc_status.repr == wuffs_base__note__metadata_reported) { + std::string error_message = DecodeImageHandleMetadata( + image_decoder, callbacks, input, io_buf, raw_metadata_buf); + if (!error_message.empty()) { + return DecodeImageResult(std::move(error_message)); + } + } else if (id_dfc_status.repr != wuffs_base__suspension__short_read) { + return DecodeImageResult(id_dfc_status.message()); + } else if (io_buf.meta.closed) { + return DecodeImageResult(DecodeImage_UnexpectedEndOfFile); + } else { + std::string error_message = input.CopyIn(&io_buf); + if (!error_message.empty()) { + return DecodeImageResult(std::move(error_message)); + } + } + } + } + + return DecodeImageResult(std::move(alloc_pixbuf_result.mem_owner), + pixel_buffer, std::move(message)); +} + +} // namespace + +DecodeImageResult // +DecodeImage(DecodeImageCallbacks& callbacks, + sync_io::Input& input, + DecodeImageArgQuirks quirks, + DecodeImageArgFlags flags, + DecodeImageArgPixelBlend pixel_blend, + DecodeImageArgBackgroundColor background_color, + DecodeImageArgMaxInclDimension max_incl_dimension, + DecodeImageArgMaxInclMetadataLength max_incl_metadata_length) { + wuffs_base__io_buffer* io_buf = input.BringsItsOwnIOBuffer(); + wuffs_base__io_buffer fallback_io_buf = wuffs_base__empty_io_buffer(); + std::unique_ptr fallback_io_array(nullptr); + if (!io_buf) { + fallback_io_array = std::unique_ptr(new uint8_t[32768]); + fallback_io_buf = + wuffs_base__ptr_u8__writer(fallback_io_array.get(), 32768); + io_buf = &fallback_io_buf; + } + + wuffs_base__image_decoder::unique_ptr image_decoder(nullptr, &free); + DecodeImageResult result = + DecodeImage0(image_decoder, callbacks, input, *io_buf, quirks.repr, + flags.repr, pixel_blend.repr, background_color.repr, + max_incl_dimension.repr, max_incl_metadata_length.repr); + callbacks.Done(result, input, *io_buf, std::move(image_decoder)); + return result; +} + +} // namespace wuffs_aux + +#endif // !defined(WUFFS_CONFIG__MODULES) || + // defined(WUFFS_CONFIG__MODULE__AUX__IMAGE) + +// ---------------- Auxiliary - JSON + +#if !defined(WUFFS_CONFIG__MODULES) || defined(WUFFS_CONFIG__MODULE__AUX__JSON) + +#include + +namespace wuffs_aux { + +DecodeJsonResult::DecodeJsonResult(std::string&& error_message0, + uint64_t cursor_position0) + : error_message(std::move(error_message0)), + cursor_position(cursor_position0) {} + +DecodeJsonCallbacks::~DecodeJsonCallbacks() {} + +void // +DecodeJsonCallbacks::Done(DecodeJsonResult& result, + sync_io::Input& input, + IOBuffer& buffer) {} + +const char DecodeJson_BadJsonPointer[] = // + "wuffs_aux::DecodeJson: bad JSON Pointer"; +const char DecodeJson_NoMatch[] = // + "wuffs_aux::DecodeJson: no match"; + +DecodeJsonArgQuirks::DecodeJsonArgQuirks(wuffs_base__slice_u32 repr0) + : repr(repr0) {} + +DecodeJsonArgQuirks::DecodeJsonArgQuirks(uint32_t* ptr0, size_t len0) + : repr(wuffs_base__make_slice_u32(ptr0, len0)) {} + +DecodeJsonArgQuirks // +DecodeJsonArgQuirks::DefaultValue() { + return DecodeJsonArgQuirks(wuffs_base__empty_slice_u32()); +} + +DecodeJsonArgJsonPointer::DecodeJsonArgJsonPointer(std::string repr0) + : repr(repr0) {} + +DecodeJsonArgJsonPointer // +DecodeJsonArgJsonPointer::DefaultValue() { + return DecodeJsonArgJsonPointer(std::string()); +} + +// -------- + +#define WUFFS_AUX__DECODE_JSON__GET_THE_NEXT_TOKEN \ + while (tok_buf.meta.ri >= tok_buf.meta.wi) { \ + if (tok_status.repr == nullptr) { \ + goto done; \ + } else if (tok_status.repr == wuffs_base__suspension__short_write) { \ + tok_buf.compact(); \ + } else if (tok_status.repr == wuffs_base__suspension__short_read) { \ + if (!io_error_message.empty()) { \ + ret_error_message = std::move(io_error_message); \ + goto done; \ + } else if (cursor_index != io_buf->meta.ri) { \ + ret_error_message = \ + "wuffs_aux::DecodeJson: internal error: bad cursor_index"; \ + goto done; \ + } else if (io_buf->meta.closed) { \ + ret_error_message = \ + "wuffs_aux::DecodeJson: internal error: io_buf is closed"; \ + goto done; \ + } \ + io_buf->compact(); \ + if (io_buf->meta.wi >= io_buf->data.len) { \ + ret_error_message = \ + "wuffs_aux::DecodeJson: internal error: io_buf is full"; \ + goto done; \ + } \ + cursor_index = io_buf->meta.ri; \ + io_error_message = input.CopyIn(io_buf); \ + } else { \ + ret_error_message = tok_status.message(); \ + goto done; \ + } \ + tok_status = \ + dec->decode_tokens(&tok_buf, io_buf, wuffs_base__empty_slice_u8()); \ + if ((tok_buf.meta.ri > tok_buf.meta.wi) || \ + (tok_buf.meta.wi > tok_buf.data.len) || \ + (io_buf->meta.ri > io_buf->meta.wi) || \ + (io_buf->meta.wi > io_buf->data.len)) { \ + ret_error_message = \ + "wuffs_aux::DecodeJson: internal error: bad buffer indexes"; \ + goto done; \ + } \ + } \ + wuffs_base__token token = tok_buf.data.ptr[tok_buf.meta.ri++]; \ + uint64_t token_len = token.length(); \ + if ((io_buf->meta.ri < cursor_index) || \ + ((io_buf->meta.ri - cursor_index) < token_len)) { \ + ret_error_message = \ + "wuffs_aux::DecodeJson: internal error: bad token indexes"; \ + goto done; \ + } \ + uint8_t* token_ptr = io_buf->data.ptr + cursor_index; \ + (void)(token_ptr); \ + cursor_index += static_cast(token_len) + +// -------- + +namespace { + +// DecodeJson_SplitJsonPointer returns ("bar", 8) for ("/foo/bar/b~1z/qux", 5, +// etc). It returns a 0 size_t when s has invalid JSON Pointer syntax or i is +// out of bounds. +// +// The string returned is unescaped. If calling it again, this time with i=8, +// the "b~1z" substring would be returned as "b/z". +std::pair // +DecodeJson_SplitJsonPointer(std::string& s, + size_t i, + bool allow_tilde_n_tilde_r_tilde_t) { + std::string fragment; + if (i > s.size()) { + return std::make_pair(std::string(), 0); + } + while (i < s.size()) { + char c = s[i]; + if (c == '/') { + break; + } else if (c != '~') { + fragment.push_back(c); + i++; + continue; + } + i++; + if (i >= s.size()) { + return std::make_pair(std::string(), 0); + } + c = s[i]; + if (c == '0') { + fragment.push_back('~'); + i++; + continue; + } else if (c == '1') { + fragment.push_back('/'); + i++; + continue; + } else if (allow_tilde_n_tilde_r_tilde_t) { + if (c == 'n') { + fragment.push_back('\n'); + i++; + continue; + } else if (c == 'r') { + fragment.push_back('\r'); + i++; + continue; + } else if (c == 't') { + fragment.push_back('\t'); + i++; + continue; + } + } + return std::make_pair(std::string(), 0); + } + return std::make_pair(std::move(fragment), i); +} + +// -------- + +std::string // +DecodeJson_WalkJsonPointerFragment(wuffs_base__token_buffer& tok_buf, + wuffs_base__status& tok_status, + wuffs_json__decoder::unique_ptr& dec, + wuffs_base__io_buffer* io_buf, + std::string& io_error_message, + size_t& cursor_index, + sync_io::Input& input, + std::string& json_pointer_fragment) { + std::string ret_error_message; + while (true) { + WUFFS_AUX__DECODE_JSON__GET_THE_NEXT_TOKEN; + + int64_t vbc = token.value_base_category(); + uint64_t vbd = token.value_base_detail(); + if (vbc == WUFFS_BASE__TOKEN__VBC__FILLER) { + continue; + } else if ((vbc != WUFFS_BASE__TOKEN__VBC__STRUCTURE) || + !(vbd & WUFFS_BASE__TOKEN__VBD__STRUCTURE__PUSH)) { + return DecodeJson_NoMatch; + } else if (vbd & WUFFS_BASE__TOKEN__VBD__STRUCTURE__TO_LIST) { + goto do_list; + } + goto do_dict; + } + +do_dict: + // Alternate between these two things: + // 1. Decode the next dict key (a string). If it matches the fragment, we're + // done (success). If we've reached the dict's end (VBD__STRUCTURE__POP) + // so that there was no next dict key, we're done (failure). + // 2. Otherwise, skip the next dict value. + while (true) { + for (std::string str; true;) { + WUFFS_AUX__DECODE_JSON__GET_THE_NEXT_TOKEN; + + int64_t vbc = token.value_base_category(); + uint64_t vbd = token.value_base_detail(); + switch (vbc) { + case WUFFS_BASE__TOKEN__VBC__FILLER: + continue; + + case WUFFS_BASE__TOKEN__VBC__STRUCTURE: + if (vbd & WUFFS_BASE__TOKEN__VBD__STRUCTURE__PUSH) { + goto fail; + } + return DecodeJson_NoMatch; + + case WUFFS_BASE__TOKEN__VBC__STRING: { + if (vbd & WUFFS_BASE__TOKEN__VBD__STRING__CONVERT_0_DST_1_SRC_DROP) { + // No-op. + } else if (vbd & + WUFFS_BASE__TOKEN__VBD__STRING__CONVERT_1_DST_1_SRC_COPY) { + const char* ptr = // Convert from (uint8_t*). + static_cast(static_cast(token_ptr)); + str.append(ptr, static_cast(token_len)); + } else { + goto fail; + } + break; + } + + case WUFFS_BASE__TOKEN__VBC__UNICODE_CODE_POINT: { + uint8_t u[WUFFS_BASE__UTF_8__BYTE_LENGTH__MAX_INCL]; + size_t n = wuffs_base__utf_8__encode( + wuffs_base__make_slice_u8( + &u[0], WUFFS_BASE__UTF_8__BYTE_LENGTH__MAX_INCL), + static_cast(vbd)); + const char* ptr = // Convert from (uint8_t*). + static_cast(static_cast(&u[0])); + str.append(ptr, n); + break; + } + + default: + goto fail; + } + + if (token.continued()) { + continue; + } + if (str == json_pointer_fragment) { + return ""; + } + goto skip_the_next_dict_value; + } + + skip_the_next_dict_value: + for (uint32_t skip_depth = 0; true;) { + WUFFS_AUX__DECODE_JSON__GET_THE_NEXT_TOKEN; + + int64_t vbc = token.value_base_category(); + uint64_t vbd = token.value_base_detail(); + if (token.continued() || (vbc == WUFFS_BASE__TOKEN__VBC__FILLER)) { + continue; + } else if (vbc == WUFFS_BASE__TOKEN__VBC__STRUCTURE) { + if (vbd & WUFFS_BASE__TOKEN__VBD__STRUCTURE__PUSH) { + skip_depth++; + continue; + } + skip_depth--; + } + + if (skip_depth == 0) { + break; + } + } // skip_the_next_dict_value + } // do_dict + +do_list: + do { + wuffs_base__result_u64 result_u64 = wuffs_base__parse_number_u64( + wuffs_base__make_slice_u8( + static_cast(static_cast( + const_cast(json_pointer_fragment.data()))), + json_pointer_fragment.size()), + WUFFS_BASE__PARSE_NUMBER_XXX__DEFAULT_OPTIONS); + if (!result_u64.status.is_ok()) { + return DecodeJson_NoMatch; + } + uint64_t remaining = result_u64.value; + if (remaining == 0) { + goto check_that_a_value_follows; + } + for (uint32_t skip_depth = 0; true;) { + WUFFS_AUX__DECODE_JSON__GET_THE_NEXT_TOKEN; + + int64_t vbc = token.value_base_category(); + uint64_t vbd = token.value_base_detail(); + if (token.continued() || (vbc == WUFFS_BASE__TOKEN__VBC__FILLER)) { + continue; + } else if (vbc == WUFFS_BASE__TOKEN__VBC__STRUCTURE) { + if (vbd & WUFFS_BASE__TOKEN__VBD__STRUCTURE__PUSH) { + skip_depth++; + continue; + } + if (skip_depth == 0) { + return DecodeJson_NoMatch; + } + skip_depth--; + } + + if (skip_depth > 0) { + continue; + } + remaining--; + if (remaining == 0) { + goto check_that_a_value_follows; + } + } + } while (false); // do_list + +check_that_a_value_follows: + while (true) { + WUFFS_AUX__DECODE_JSON__GET_THE_NEXT_TOKEN; + + int64_t vbc = token.value_base_category(); + uint64_t vbd = token.value_base_detail(); + if (vbc == WUFFS_BASE__TOKEN__VBC__FILLER) { + continue; + } + + // Undo the last part of WUFFS_AUX__DECODE_JSON__GET_THE_NEXT_TOKEN, so + // that we're only peeking at the next token. + tok_buf.meta.ri--; + cursor_index -= static_cast(token_len); + + if ((vbc == WUFFS_BASE__TOKEN__VBC__STRUCTURE) && + (vbd & WUFFS_BASE__TOKEN__VBD__STRUCTURE__POP)) { + return DecodeJson_NoMatch; + } + return ""; + } // check_that_a_value_follows + +fail: + return "wuffs_aux::DecodeJson: internal error: unexpected token"; +done: + return ret_error_message; +} + +} // namespace + +// -------- + +DecodeJsonResult // +DecodeJson(DecodeJsonCallbacks& callbacks, + sync_io::Input& input, + DecodeJsonArgQuirks quirks, + DecodeJsonArgJsonPointer json_pointer) { + // Prepare the wuffs_base__io_buffer and the resultant error_message. + wuffs_base__io_buffer* io_buf = input.BringsItsOwnIOBuffer(); + wuffs_base__io_buffer fallback_io_buf = wuffs_base__empty_io_buffer(); + std::unique_ptr fallback_io_array(nullptr); + if (!io_buf) { + fallback_io_array = std::unique_ptr(new uint8_t[4096]); + fallback_io_buf = wuffs_base__ptr_u8__writer(fallback_io_array.get(), 4096); + io_buf = &fallback_io_buf; + } + // cursor_index is discussed at + // https://nigeltao.github.io/blog/2020/jsonptr.html#the-cursor-index + size_t cursor_index = 0; + std::string ret_error_message; + std::string io_error_message; + + do { + // Prepare the low-level JSON decoder. + wuffs_json__decoder::unique_ptr dec = wuffs_json__decoder::alloc(); + if (!dec) { + ret_error_message = "wuffs_aux::DecodeJson: out of memory"; + goto done; + } else if (WUFFS_JSON__DECODER_WORKBUF_LEN_MAX_INCL_WORST_CASE != 0) { + ret_error_message = + "wuffs_aux::DecodeJson: internal error: bad WORKBUF_LEN"; + goto done; + } + bool allow_tilde_n_tilde_r_tilde_t = false; + for (size_t i = 0; i < quirks.repr.len; i++) { + dec->set_quirk_enabled(quirks.repr.ptr[i], true); + if (quirks.repr.ptr[i] == + WUFFS_JSON__QUIRK_JSON_POINTER_ALLOW_TILDE_N_TILDE_R_TILDE_T) { + allow_tilde_n_tilde_r_tilde_t = true; + } + } + + // Prepare the wuffs_base__tok_buffer. 256 tokens is 2KiB. + wuffs_base__token tok_array[256]; + wuffs_base__token_buffer tok_buf = + wuffs_base__slice_token__writer(wuffs_base__make_slice_token( + &tok_array[0], (sizeof(tok_array) / sizeof(tok_array[0])))); + wuffs_base__status tok_status = + dec->decode_tokens(&tok_buf, io_buf, wuffs_base__empty_slice_u8()); + + // Prepare other state. + int32_t depth = 0; + std::string str; + + // Walk the (optional) JSON Pointer. + for (size_t i = 0; i < json_pointer.repr.size();) { + if (json_pointer.repr[i] != '/') { + ret_error_message = DecodeJson_BadJsonPointer; + goto done; + } + std::pair split = DecodeJson_SplitJsonPointer( + json_pointer.repr, i + 1, allow_tilde_n_tilde_r_tilde_t); + i = split.second; + if (i == 0) { + ret_error_message = DecodeJson_BadJsonPointer; + goto done; + } + ret_error_message = DecodeJson_WalkJsonPointerFragment( + tok_buf, tok_status, dec, io_buf, io_error_message, cursor_index, + input, split.first); + if (!ret_error_message.empty()) { + goto done; + } + } + + // Loop, doing these two things: + // 1. Get the next token. + // 2. Process that token. + while (true) { + WUFFS_AUX__DECODE_JSON__GET_THE_NEXT_TOKEN; + + int64_t vbc = token.value_base_category(); + uint64_t vbd = token.value_base_detail(); + switch (vbc) { + case WUFFS_BASE__TOKEN__VBC__FILLER: + continue; + + case WUFFS_BASE__TOKEN__VBC__STRUCTURE: { + if (vbd & WUFFS_BASE__TOKEN__VBD__STRUCTURE__PUSH) { + ret_error_message = callbacks.Push(static_cast(vbd)); + if (!ret_error_message.empty()) { + goto done; + } + depth++; + if (depth > WUFFS_JSON__DECODER_DEPTH_MAX_INCL) { + ret_error_message = + "wuffs_aux::DecodeJson: internal error: bad depth"; + goto done; + } + continue; + } + ret_error_message = callbacks.Pop(static_cast(vbd)); + depth--; + if (depth < 0) { + ret_error_message = + "wuffs_aux::DecodeJson: internal error: bad depth"; + goto done; + } + goto parsed_a_value; + } + + case WUFFS_BASE__TOKEN__VBC__STRING: { + if (vbd & WUFFS_BASE__TOKEN__VBD__STRING__CONVERT_0_DST_1_SRC_DROP) { + // No-op. + } else if (vbd & + WUFFS_BASE__TOKEN__VBD__STRING__CONVERT_1_DST_1_SRC_COPY) { + const char* ptr = // Convert from (uint8_t*). + static_cast(static_cast(token_ptr)); + str.append(ptr, static_cast(token_len)); + } else { + goto fail; + } + if (token.continued()) { + continue; + } + ret_error_message = callbacks.AppendTextString(std::move(str)); + str.clear(); + goto parsed_a_value; + } + + case WUFFS_BASE__TOKEN__VBC__UNICODE_CODE_POINT: { + uint8_t u[WUFFS_BASE__UTF_8__BYTE_LENGTH__MAX_INCL]; + size_t n = wuffs_base__utf_8__encode( + wuffs_base__make_slice_u8( + &u[0], WUFFS_BASE__UTF_8__BYTE_LENGTH__MAX_INCL), + static_cast(vbd)); + const char* ptr = // Convert from (uint8_t*). + static_cast(static_cast(&u[0])); + str.append(ptr, n); + if (token.continued()) { + continue; + } + goto fail; + } + + case WUFFS_BASE__TOKEN__VBC__LITERAL: { + ret_error_message = + (vbd & WUFFS_BASE__TOKEN__VBD__LITERAL__NULL) + ? callbacks.AppendNull() + : callbacks.AppendBool(vbd & + WUFFS_BASE__TOKEN__VBD__LITERAL__TRUE); + goto parsed_a_value; + } + + case WUFFS_BASE__TOKEN__VBC__NUMBER: { + if (vbd & WUFFS_BASE__TOKEN__VBD__NUMBER__FORMAT_TEXT) { + if (vbd & WUFFS_BASE__TOKEN__VBD__NUMBER__CONTENT_INTEGER_SIGNED) { + wuffs_base__result_i64 r = wuffs_base__parse_number_i64( + wuffs_base__make_slice_u8(token_ptr, + static_cast(token_len)), + WUFFS_BASE__PARSE_NUMBER_XXX__DEFAULT_OPTIONS); + if (r.status.is_ok()) { + ret_error_message = callbacks.AppendI64(r.value); + goto parsed_a_value; + } + } + if (vbd & WUFFS_BASE__TOKEN__VBD__NUMBER__CONTENT_FLOATING_POINT) { + wuffs_base__result_f64 r = wuffs_base__parse_number_f64( + wuffs_base__make_slice_u8(token_ptr, + static_cast(token_len)), + WUFFS_BASE__PARSE_NUMBER_XXX__DEFAULT_OPTIONS); + if (r.status.is_ok()) { + ret_error_message = callbacks.AppendF64(r.value); + goto parsed_a_value; + } + } + } else if (vbd & WUFFS_BASE__TOKEN__VBD__NUMBER__CONTENT_NEG_INF) { + ret_error_message = callbacks.AppendF64( + wuffs_base__ieee_754_bit_representation__from_u64_to_f64( + 0xFFF0000000000000ul)); + goto parsed_a_value; + } else if (vbd & WUFFS_BASE__TOKEN__VBD__NUMBER__CONTENT_POS_INF) { + ret_error_message = callbacks.AppendF64( + wuffs_base__ieee_754_bit_representation__from_u64_to_f64( + 0x7FF0000000000000ul)); + goto parsed_a_value; + } else if (vbd & WUFFS_BASE__TOKEN__VBD__NUMBER__CONTENT_NEG_NAN) { + ret_error_message = callbacks.AppendF64( + wuffs_base__ieee_754_bit_representation__from_u64_to_f64( + 0xFFFFFFFFFFFFFFFFul)); + goto parsed_a_value; + } else if (vbd & WUFFS_BASE__TOKEN__VBD__NUMBER__CONTENT_POS_NAN) { + ret_error_message = callbacks.AppendF64( + wuffs_base__ieee_754_bit_representation__from_u64_to_f64( + 0x7FFFFFFFFFFFFFFFul)); + goto parsed_a_value; + } + goto fail; + } + } + + fail: + ret_error_message = + "wuffs_aux::DecodeJson: internal error: unexpected token"; + goto done; + + parsed_a_value: + // If an error was encountered, we are done. Otherwise, (depth == 0) + // after parsing a value is equivalent to having decoded the entire JSON + // value (for an empty json_pointer query) or having decoded the + // pointed-to JSON value (for a non-empty json_pointer query). In the + // latter case, we are also done. + // + // However, if quirks like WUFFS_JSON__QUIRK_ALLOW_TRAILING_FILLER or + // WUFFS_JSON__QUIRK_EXPECT_TRAILING_NEW_LINE_OR_EOF are passed, decoding + // the entire JSON value should also consume any trailing filler, in case + // the DecodeJson caller wants to subsequently check that the input is + // completely exhausted (and otherwise raise "valid JSON followed by + // further (unexpected) data"). We aren't done yet. Instead, keep the + // loop running until WUFFS_AUX__DECODE_JSON__GET_THE_NEXT_TOKEN's + // decode_tokens returns an ok status. + if (!ret_error_message.empty() || + ((depth == 0) && !json_pointer.repr.empty())) { + goto done; + } + } + } while (false); + +done: + DecodeJsonResult result( + std::move(ret_error_message), + wuffs_base__u64__sat_add(io_buf->meta.pos, cursor_index)); + callbacks.Done(result, input, *io_buf); + return result; +} + +#undef WUFFS_AUX__DECODE_JSON__GET_THE_NEXT_TOKEN + +} // namespace wuffs_aux + +#endif // !defined(WUFFS_CONFIG__MODULES) || + // defined(WUFFS_CONFIG__MODULE__AUX__JSON) + +#endif // defined(__cplusplus) && defined(WUFFS_BASE__HAVE_UNIQUE_PTR) + +#endif // WUFFS_IMPLEMENTATION + +#if defined(__GNUC__) +#pragma GCC diagnostic pop +#elif defined(__clang__) +#pragma clang diagnostic pop +#endif + +#endif // WUFFS_INCLUDE_GUARD diff --git a/packages/core/tsconfig.node-test.json b/packages/core/tsconfig.node-test.json index cc9d048ca9..3c9a8e7be9 100644 --- a/packages/core/tsconfig.node-test.json +++ b/packages/core/tsconfig.node-test.json @@ -15,6 +15,9 @@ "src/lib/bunfs.test.ts", "src/lib/border.test.ts", "src/lib/clipboard.test.ts", + "src/lib/clipboard-service.test.ts", + "src/lib/host-clipboard.test.ts", + "src/lib/host-clipboard.native.scheduler.test.ts", "src/lib/extmarks.test.ts", "src/lib/detect-links.test.ts", "src/lib/extmarks-multiwidth.test.ts", @@ -95,7 +98,10 @@ "src/tests/renderable.snapshot.test.ts", "src/tests/allocator-stats.test.ts", "src/tests/audio-stream.test.ts", + "src/tests/clipboard-native-lifecycle.test.ts", "src/tests/audio.test.ts", + "src/tests/image-renderable.test.ts", + "src/tests/image.test.ts", "src/tests/destroy-on-exit.fixture.ts", "src/tests/destroy-on-exit.test.ts", "src/tests/destroy-during-render.test.ts", diff --git a/packages/examples/src/assets/dragon.jpg b/packages/examples/src/assets/dragon.jpg new file mode 100644 index 0000000000..6caac18579 Binary files /dev/null and b/packages/examples/src/assets/dragon.jpg differ diff --git a/packages/examples/src/assets/image-demo.gif b/packages/examples/src/assets/image-demo.gif new file mode 100644 index 0000000000..f86ba50d1c Binary files /dev/null and b/packages/examples/src/assets/image-demo.gif differ diff --git a/packages/examples/src/assets/image-demo.png b/packages/examples/src/assets/image-demo.png new file mode 100644 index 0000000000..c5419d4fd0 Binary files /dev/null and b/packages/examples/src/assets/image-demo.png differ diff --git a/packages/examples/src/assets/image-demo.webp b/packages/examples/src/assets/image-demo.webp new file mode 100644 index 0000000000..136b754ac8 Binary files /dev/null and b/packages/examples/src/assets/image-demo.webp differ diff --git a/packages/examples/src/clipboard-paste-demo.ts b/packages/examples/src/clipboard-paste-demo.ts index 367c660b5f..0add1cbbc9 100644 --- a/packages/examples/src/clipboard-paste-demo.ts +++ b/packages/examples/src/clipboard-paste-demo.ts @@ -1,556 +1,385 @@ #!/usr/bin/env bun import { - bg, - bold, BoxRenderable, - CliRenderEvents, type CliRenderer, + type ClipboardSelection, + type ClipboardService, + type ClipboardWriteDestination, createCliRenderer, + createClipboard, + createHostClipboard, + createRendererClipboardAdapter, decodePasteBytes, - fg, type KeyEvent, type PasteEvent, - ScrollBoxRenderable, - type Selection, - stripAnsiSequences, - t, TextareaRenderable, TextRenderable, } from "@opentui/core" -import { setupCommonDemoKeys } from "./lib/standalone-keys.js" - -const P = { - bg: "#08111f", - panel: "#0f1b2d", - border: "#34507c", - borderHot: "#22d3ee", - text: "#d7e3f7", - muted: "#7d8da8", - cyan: "#22d3ee", - lime: "#bef264", - rose: "#fb7185", - amber: "#fbbf24", - violet: "#a78bfa", -} as const -type Tone = "muted" | "info" | "ok" | "warn" | "bad" +const COLORS = { + background: "#071018", + panel: "#101c28", + text: "#e5edf5", + muted: "#8ba0b5", + accent: "#66d9ef", + selection: "#28577a", +} as const -const TONE_COLOR: Record = { - muted: P.muted, - info: P.cyan, - ok: P.lime, - warn: P.amber, - bad: P.rose, -} +const READ_MAX_BYTES = 2 * 1024 * 1024 +const UNICODE_PAYLOAD = "OpenTUI clipboard round-trip\nUnicode: \u4e16\u754c cafe \ud83d\ude80\nLine endings: LF\nEnd" +const LARGE_PAYLOAD = `OpenTUI large clipboard payload\n${"0123456789abcdef".repeat(1024)}` +const INHERITED_WAYLAND_ONLY = + process.platform === "linux" && + Boolean(process.env.WAYLAND_SOCKET) && + !process.env.WAYLAND_DISPLAY && + !process.env.DISPLAY -const TONE_ICON: Record = { - muted: "·", - info: "→", - ok: "✓", - warn: "…", - bad: "✗", -} +type LifecycleIntent = "create" | "dispose" | "recreate" -const MAX_LOG_ROWS = 80 -const SELECTION_BG = "#264f78" -const SELECTION_FG = "#ffffff" +let root: BoxRenderable | null = null +let editor: TextareaRenderable | null = null +let statusText: TextRenderable | null = null +let clipboard: ClipboardService | null = null +let selection: ClipboardSelection = "clipboard" +let payload = UNICODE_PAYLOAD +let operationStatus = "Ready" +let pasteStatus = "No PasteEvent received" +let operationVersion = 0 +let lifecycleVersion = 0 +let pasteGeneration = 0 +let lifecycleQueue: Promise = Promise.resolve() +let keyHandler: ((key: KeyEvent) => void) | null = null +let pasteHandler: ((event: PasteEvent) => void) | null = null +let destroyPromise: Promise | null = null +let inheritedWaylandServiceCreated = false -interface Status { - tone: Tone - text: string +function errorMessage(error: unknown): string { + return error instanceof Error ? error.message : String(error) } -interface Fixture { - name: string - short: string - purpose: string - payload: string +function byteCount(text: string): number { + return new TextEncoder().encode(text).length } -const FIXTURES: readonly Fixture[] = [ - { - name: "Unicode + LF", - short: "Unicode + LF", - purpose: "UTF-8 decoding and multiline insertion", - payload: "OpenTUI clipboard round-trip\nUnicode: 世界 café 🚀\nLine endings: LF\nEnd", - }, - { - name: "CRLF + lone CR", - short: "CRLF + CR", - purpose: "Raw transport preserves CR; the editor normalizes to LF", - payload: "OpenTUI newline fixture\r\nCRLF line\rLone CR line\nLF line", - }, - { - name: "ANSI text", - short: "ANSI", - purpose: "Raw event preserves ANSI; the textarea strips it on insertion", - payload: "OpenTUI ANSI fixture: \x1b[31mred\x1b[0m plain", - }, - { - name: "Large (16 KiB)", - short: "Large 16 KiB", - purpose: "Exercises OSC 52 beyond the former fixed-buffer limit", - payload: `OpenTUI large OSC 52 payload\n${"0123456789abcdef".repeat(1024)}`, - }, -] - -const encoder = new TextEncoder() - -let container: BoxRenderable | null = null -let tabsText: TextRenderable | null = null -let fixtureText: TextRenderable | null = null -let editor: TextareaRenderable | null = null -let checksText: TextRenderable | null = null -let logList: ScrollBoxRenderable | null = null -let logRows: TextRenderable[] = [] -let logRowId = 0 -let keypressHandler: ((event: KeyEvent) => void) | null = null -let pasteHandler: ((event: PasteEvent) => void) | null = null -let capabilityHandler: (() => void) | null = null -let selectionHandler: ((selection: Selection) => void) | null = null -let selectedFixture = 0 -let fixturePayloadEmitted = false -let lastLoggedCapability = "" -let copyStatus: Status = { tone: "muted", text: "not attempted" } -let pasteStatus: Status = { tone: "muted", text: "waiting for a PasteEvent" } -let editorStatus: Status = { tone: "muted", text: "waiting for default insertion" } -let roundTripStatus: Status = { tone: "muted", text: "not evaluated" } - -function fixture(): Fixture { - return FIXTURES[selectedFixture]! +function normalizeNewlines(text: string): string { + return text.replace(/\r\n/g, "\n").replace(/\r/g, "\n") } -function normalizeNewlines(value: string): string { - return value.replace(/\r\n/g, "\n").replace(/\r/g, "\n") +async function sha256(bytes: Uint8Array): Promise { + const digest = await crypto.subtle.digest("SHA-256", Uint8Array.from(bytes).buffer) + return Array.from(new Uint8Array(digest), (byte) => byte.toString(16).padStart(2, "0")).join("") } -function escapedPreview(value: string, maxLength = 64): string { - const escaped = JSON.stringify(value) - return escaped.length <= maxLength ? escaped : `${escaped.slice(0, maxLength - 3)}...` +function terminalResult(status: string, capability: string): string { + return status === "attempted" ? `attempted, unconfirmed (${capability})` : `${status} (${capability})` } -function byteLength(value: string): number { - return encoder.encode(value).length +function updateStatus(): void { + if (!statusText) return + const payloadName = payload === UNICODE_PAYLOAD ? "exact Unicode" : "exact 16,416-byte large fixture" + statusText.content = [ + `Service: ${clipboard ? "active" : "disposed"} | Selection: ${selection}`, + `Payload: ${payloadName}, ${byteCount(payload)} bytes`, + `Operation: ${operationStatus}`, + `PasteEvent: ${pasteStatus}`, + ].join("\n") } -function hexPrefix(bytes: Uint8Array, count = 12): string { - const slice = bytes.slice(0, count) - const hex = Array.from(slice, (byte) => byte.toString(16).padStart(2, "0")).join("") - return bytes.length > count ? `${hex}…` : hex +function beginOperation(status: string): number { + operationVersion += 1 + operationStatus = status + updateStatus() + return operationVersion } -function timestamp(): string { - const now = new Date() - const hh = `${now.getHours()}`.padStart(2, "0") - const mm = `${now.getMinutes()}`.padStart(2, "0") - const ss = `${now.getSeconds()}`.padStart(2, "0") - const ms = `${now.getMilliseconds()}`.padStart(3, "0") - return `${hh}:${mm}:${ss}.${ms}` +function finishOperation(version: number, status: string): void { + if (version === operationVersion) operationStatus = status + updateStatus() } -function capabilityStatus(renderer: CliRenderer): Status { - const capabilities = renderer.capabilities - if (!capabilities) return { tone: "muted", text: "detecting" } - const hint = capabilities.osc52 ? "yes" : "no" - switch (capabilities.osc52_support) { - case "supported": - return { tone: "ok", text: `supported — emits (legacy hint: ${hint})` } - case "unsupported": - return { tone: "bad", text: `unsupported — emission blocked (legacy hint: ${hint})` } - default: - return { tone: "warn", text: `unknown — emits optimistically (legacy hint: ${hint})` } +function requestLifecycle(renderer: CliRenderer, intent: LifecycleIntent): Promise { + if (INHERITED_WAYLAND_ONLY && intent === "recreate" && clipboard) { + beginOperation("Recreate skipped: this inherited Wayland socket supports one active host service") + return lifecycleQueue + } + if (INHERITED_WAYLAND_ONLY && intent !== "dispose" && inheritedWaylandServiceCreated && !clipboard) { + beginOperation("Host service unavailable: the inherited Wayland socket was already consumed") + return lifecycleQueue } -} -function metadataLabel(event: PasteEvent): string { - if (!event.metadata) return "meta absent" - return `meta kind=${event.metadata.kind ?? "unset"} mime=${event.metadata.mimeType ?? "unset"}` -} + const lifecycle = ++lifecycleVersion + const operation = beginOperation( + intent === "create" + ? "Creating clipboard service" + : intent === "dispose" + ? "Disposing clipboard service" + : "Recreating clipboard service", + ) + const service = intent === "create" ? null : clipboard + if (intent !== "create") { + clipboard = null + updateStatus() + } -function statusChunk(status: Status) { - return fg(TONE_COLOR[status.tone])(`${TONE_ICON[status.tone]} ${status.text}`) -} + lifecycleQueue = lifecycleQueue.then(async () => { + if (service) { + try { + await service.dispose() + } catch (error) { + if (lifecycle === lifecycleVersion) finishOperation(operation, `Dispose failed: ${errorMessage(error)}`) + return + } + } + if (lifecycle !== lifecycleVersion) return + if (intent === "dispose") { + finishOperation(operation, service ? "Service disposal awaited" : "Service is already disposed") + return + } + if (intent === "create" && clipboard) { + finishOperation(operation, "Service is already active") + return + } -function label(text: string) { - return fg(P.muted)(text.padEnd(12)) + try { + clipboard = createClipboard({ + host: createHostClipboard({ maxReadBytes: READ_MAX_BYTES }), + terminal: createRendererClipboardAdapter(renderer), + }) + if (INHERITED_WAYLAND_ONLY) inheritedWaylandServiceCreated = true + finishOperation(operation, "Created host and terminal clipboard service") + } catch (error) { + finishOperation(operation, `Create failed: ${errorMessage(error)}`) + } + }) + return lifecycleQueue } -function addLog(renderer: CliRenderer, tone: Tone, message: string, detail?: string): void { - if (!logList) return - - const row = new TextRenderable(renderer, { - id: `clipboard-paste-log-${logRowId++}`, - content: t`${fg(P.muted)(timestamp())} ${fg(TONE_COLOR[tone])(`${TONE_ICON[tone]} ${message}`)}`, - flexGrow: 0, - flexShrink: 0, - selectionBg: SELECTION_BG, - selectionFg: SELECTION_FG, - }) - logList.add(row) - logRows.push(row) - - if (detail) { - const detailRow = new TextRenderable(renderer, { - id: `clipboard-paste-log-${logRowId++}`, - content: t`${fg(P.muted)(` ${detail}`)}`, - flexGrow: 0, - flexShrink: 0, - selectionBg: SELECTION_BG, - selectionFg: SELECTION_FG, - }) - logList.add(detailRow) - logRows.push(detailRow) +async function write(destination: ClipboardWriteDestination): Promise { + const service = clipboard + const chosenPayload = payload + const chosenSelection = selection + const operation = beginOperation(`Writing ${byteCount(chosenPayload)} bytes to ${chosenSelection} via ${destination}`) + if (!service) { + finishOperation(operation, "Write skipped: service disposed (F11 recreates)") + return } - while (logRows.length > MAX_LOG_ROWS) { - const oldRow = logRows.shift() - oldRow?.destroyRecursively() + try { + const result = await service.writeText(chosenPayload, { destination, selection: chosenSelection }) + if (service !== clipboard) return + finishOperation( + operation, + `Write ${chosenSelection}: host ${result.host.status}; terminal ${terminalResult(result.terminal.status, result.terminal.capability)}`, + ) + } catch (error) { + if (service === clipboard) finishOperation(operation, `Write failed: ${errorMessage(error)}`) } } -function updateTabs(): void { - if (!tabsText) return - const chunks = FIXTURES.map((entry, index) => { - const text = ` ${index + 1} ${entry.short} ` - return index === selectedFixture ? bg(P.cyan)(fg(P.bg)(bold(text))) : fg(P.muted)(text) - }) - tabsText.content = t`${chunks[0]!} ${chunks[1]!} ${chunks[2]!} ${chunks[3]!}` -} +async function readHost(chosenSelection: ClipboardSelection): Promise { + const service = clipboard + const expectedPayload = payload + const operation = beginOperation(`Reading host ${chosenSelection}; prefers image/png then text/plain`) + if (!service) { + finishOperation(operation, "Read skipped: service disposed (F11 recreates)") + return + } -function updateFixturePanel(): void { - if (!fixtureText) return - const current = fixture() - fixtureText.content = t`${bold(fg(P.text)(current.name))} ${fg(P.muted)(`— ${byteLength(current.payload)} UTF-8 bytes`)} -${label("Purpose")} ${fg(P.text)(current.purpose)} -${label("Expected")} ${fg(P.violet)(escapedPreview(current.payload))}` + try { + const result = await service.read({ preferredTypes: ["image/png", "text/plain"], selection: chosenSelection }) + if (service !== clipboard || operation !== operationVersion) return + if (result.status !== "read") { + const detail = result.status === "failed" ? `: ${result.error.message}` : "" + finishOperation(operation, `Host read ${chosenSelection}: ${result.status}${detail}`) + return + } + + const { mimeType, bytes } = result.representation + const digest = await sha256(bytes) + if (service !== clipboard) return + const exact = mimeType === "text/plain" && new TextDecoder().decode(bytes) === expectedPayload + finishOperation( + operation, + `Host read ${chosenSelection}: ${mimeType}, ${bytes.length} bytes, exact fixture ${exact ? "yes" : "no"}; SHA-256 ${digest}. No PasteEvent synthesized.`, + ) + } catch (error) { + if (service === clipboard) finishOperation(operation, `Read failed: ${errorMessage(error)}`) + } } -function updateChecks(renderer: CliRenderer): void { - if (!checksText) return - const capability = capabilityStatus(renderer) - checksText.content = t`${label("Capability")} ${statusChunk(capability)} -${label("Copy OSC 52")} ${statusChunk(copyStatus)} -${label("Paste event")} ${statusChunk(pasteStatus)} -${label("Editor text")} ${statusChunk(editorStatus)} -${label("Round trip")} ${statusChunk(roundTripStatus)}` +async function clear(chosenSelection: ClipboardSelection, destination: ClipboardWriteDestination): Promise { + const service = clipboard + const operation = beginOperation(`Clearing ${chosenSelection} via ${destination}`) + if (!service) { + finishOperation(operation, "Clear skipped: service disposed (F11 recreates)") + return + } + + try { + const result = await service.clear({ destination, selection: chosenSelection }) + if (service !== clipboard) return + finishOperation( + operation, + `Clear ${chosenSelection}: host ${result.host.status}; terminal ${terminalResult(result.terminal.status, result.terminal.capability)}`, + ) + } catch (error) { + if (service === clipboard) finishOperation(operation, `Clear failed: ${errorMessage(error)}`) + } } -function resetTest(renderer: CliRenderer, reason: string): void { +function selectPayload(nextPayload: string, name: string): void { + pasteGeneration += 1 + payload = nextPayload editor?.setText("") - fixturePayloadEmitted = false - copyStatus = { tone: "muted", text: "not attempted" } - pasteStatus = { tone: "muted", text: "waiting for a PasteEvent" } - editorStatus = { tone: "muted", text: "waiting for default insertion" } - roundTripStatus = { tone: "muted", text: "not evaluated" } - updateTabs() - updateFixturePanel() - updateChecks(renderer) - editor?.focus() - addLog(renderer, "muted", `${reason} — editor cleared, checks idle`) + pasteStatus = "No PasteEvent received for selected fixture" + beginOperation(`Selected ${name}; paste target reset`) } -function panel(renderer: CliRenderer, id: string, title: string, height?: number): BoxRenderable { - return new BoxRenderable(renderer, { - id, - title: ` ${title} `, - titleAlignment: "left", - border: true, - borderStyle: "rounded", - borderColor: P.border, - backgroundColor: P.panel, - paddingLeft: 1, - paddingRight: 1, - ...(height === undefined ? {} : { height }), - }) +function handleKey(renderer: CliRenderer, key: KeyEvent): void { + if (key.name === "f7") { + key.preventDefault() + void readHost(key.shift ? "primary" : "clipboard") + return + } + + switch (key.name) { + case "f1": + selectPayload(UNICODE_PAYLOAD, "exact Unicode payload") + break + case "f2": + void write("host-only") + break + case "f3": + selection = selection === "clipboard" ? "primary" : "clipboard" + beginOperation(`Selected ${selection}`) + break + case "f4": + selectPayload(LARGE_PAYLOAD, "exact 16,416-byte large fixture") + break + case "f5": + void write("terminal-only") + break + case "f6": + void write("all-available") + break + case "f8": + void clear("clipboard", "all-available") + break + case "f9": + void clear(selection, "terminal-only") + break + case "f10": + void requestLifecycle(renderer, "dispose") + break + case "f11": + void requestLifecycle(renderer, "recreate") + break + default: + return + } + key.preventDefault() + editor?.focus() } export function run(renderer: CliRenderer): void { - renderer.setBackgroundColor(P.bg) - - container = new BoxRenderable(renderer, { - id: "clipboard-paste-container", + destroyPromise = null + pasteGeneration += 1 + selection = "clipboard" + payload = UNICODE_PAYLOAD + operationStatus = "Ready" + pasteStatus = "No PasteEvent received" + renderer.setBackgroundColor(COLORS.background) + + root = new BoxRenderable(renderer, { width: "100%", height: "100%", padding: 1, flexDirection: "column", - backgroundColor: P.bg, + gap: 1, }) - const header = new TextRenderable(renderer, { - id: "clipboard-paste-header", - height: 2, - marginBottom: 1, - content: t`${bold(fg(P.cyan)("CLIPBOARD + PASTE TEST BED"))} ${fg(P.muted)("— OSC 52 → PasteEvent → textarea")} -${bold(fg(P.amber)("1-4"))} ${fg(P.muted)("fixture")} ${bold(fg(P.amber)("Ctrl+Y"))} ${fg(P.muted)("copy")} ${bold(fg(P.amber)("Ctrl+K"))} ${fg(P.muted)("clear")} ${bold(fg(P.amber)("Ctrl+R"))} ${fg(P.muted)("reset")} ${bold(fg(P.amber)("drag-select"))} ${fg(P.muted)("copies via OSC 52")}`, - }) - - tabsText = new TextRenderable(renderer, { - id: "clipboard-paste-tabs", - height: 1, - content: "", - }) - - const fixturePanel = panel(renderer, "clipboard-paste-fixture-panel", "Fixture", 5) - fixturePanel.marginBottom = 1 - fixtureText = new TextRenderable(renderer, { - id: "clipboard-paste-fixture", - content: "", - selectionBg: SELECTION_BG, - selectionFg: SELECTION_FG, - }) - fixturePanel.add(fixtureText) - - const editorPanel = new BoxRenderable(renderer, { - id: "clipboard-paste-editor-box", - title: " Paste target ", - titleAlignment: "left", - border: true, - borderStyle: "rounded", - borderColor: P.borderHot, - backgroundColor: P.panel, - paddingLeft: 1, - paddingRight: 1, - height: 6, - marginBottom: 1, + const instructions = new TextRenderable(renderer, { + height: 5, + fg: COLORS.muted, + content: [ + "CLIPBOARD AND PASTE MANUAL ACCEPTANCE", + "F1 Unicode | F2 host write | F3 clipboard/primary | F4 16,416-byte fixture | F5 terminal write | F6 all write", + "F7 read clipboard | Shift+F7 read primary | F8 clear clipboard/all | F9 terminal clear (selected selection)", + INHERITED_WAYLAND_ONLY + ? "F10 dispose (irreversible) | F11 unavailable | Menu: Escape returns | Standalone: Ctrl+C/Ctrl+Q quits" + : "F10 dispose | F11 recreate | Menu: Escape returns | Standalone: Ctrl+C/Ctrl+Q quits", + "Terminal attempts are unconfirmed. Paste normally into the focused textarea below.", + ].join("\n"), }) editor = new TextareaRenderable(renderer, { - id: "clipboard-paste-editor", width: "100%", - height: "100%", - placeholder: "Paste here... (Ctrl+R resets before an exact editor check)", - textColor: P.text, - backgroundColor: P.panel, - focusedBackgroundColor: P.panel, - cursorColor: P.amber, - wrapMode: "word", - }) - editorPanel.add(editor) - - const checksPanel = panel(renderer, "clipboard-paste-checks-panel", "Checks", 7) - checksPanel.marginBottom = 1 - checksText = new TextRenderable(renderer, { - id: "clipboard-paste-checks", - content: "", - selectionBg: SELECTION_BG, - selectionFg: SELECTION_FG, - }) - checksPanel.add(checksText) - - const logPanel = new BoxRenderable(renderer, { - id: "clipboard-paste-log-panel", - title: " Events ", - titleAlignment: "left", - border: true, - borderStyle: "rounded", - borderColor: P.border, - backgroundColor: P.panel, - paddingLeft: 1, - paddingRight: 1, flexGrow: 1, - flexShrink: 1, - minHeight: 5, - flexDirection: "column", + placeholder: "FOCUSED PASTE TARGET: ordinary terminal paste bytes/text appear here...", + textColor: COLORS.text, + backgroundColor: COLORS.panel, + cursorColor: COLORS.accent, + selectionBg: COLORS.selection, + wrapMode: "word", }) - logList = new ScrollBoxRenderable(renderer, { - id: "clipboard-paste-log-list", - stickyScroll: true, - stickyStart: "bottom", - rootOptions: { backgroundColor: P.panel, border: false }, - wrapperOptions: { backgroundColor: P.panel }, - viewportOptions: { backgroundColor: P.panel }, - contentOptions: { backgroundColor: P.panel }, - scrollbarOptions: { - trackOptions: { - foregroundColor: P.cyan, - backgroundColor: P.border, - }, - }, - height: "100%", - width: "auto", - flexGrow: 1, - flexShrink: 1, + statusText = new TextRenderable(renderer, { + height: 7, + fg: COLORS.text, }) - logPanel.add(logList) - container.add(header) - container.add(tabsText) - container.add(fixturePanel) - container.add(editorPanel) - container.add(checksPanel) - container.add(logPanel) - renderer.root.add(container) + root.add(instructions) + root.add(editor) + root.add(statusText) + renderer.root.add(root) + keyHandler = (key) => handleKey(renderer, key) pasteHandler = (event) => { - const current = fixture() - const pasted = decodePasteBytes(event.bytes) - const exact = pasted === current.payload - const normalized = normalizeNewlines(pasted) === normalizeNewlines(current.payload) - pasteStatus = exact - ? { tone: "ok", text: `raw and normalized match — ${event.bytes.length} bytes` } - : normalized - ? { tone: "warn", text: `normalized match only — raw differs (${event.bytes.length} bytes)` } - : { tone: "bad", text: `no fixture match — ${event.bytes.length} bytes` } - roundTripStatus = - fixturePayloadEmitted && exact - ? { tone: "ok", text: "observed after emission (terminal acceptance unacknowledged)" } - : { tone: "warn", text: "not established — needs emission plus an exact raw match" } - editorStatus = { tone: "warn", text: "pending default focused-renderable handling" } - updateChecks(renderer) - addLog( - renderer, - pasteStatus.tone, - `paste ← ${event.bytes.length} B · ${metadataLabel(event)} · raw ${exact ? "✓" : "✗"} norm ${normalized ? "✓" : "✗"}`, - `${escapedPreview(pasted, 44)} · hex ${hexPrefix(event.bytes, 8)}`, - ) - + const generation = ++pasteGeneration + const expected = payload + const rawMatch = decodePasteBytes(event.bytes) === expected + pasteStatus = `${event.bytes.length} bytes | raw fixture match: ${rawMatch ? "yes" : "no"} | editor match: pending` + updateStatus() queueMicrotask(() => { - if (!editor || editor.isDestroyed) return - const expected = normalizeNewlines(stripAnsiSequences(current.payload)) - const pass = editor.plainText === expected - editorStatus = pass - ? { tone: "ok", text: `matches expected editor text — ${byteLength(editor.plainText)} bytes retained` } - : { - tone: "bad", - text: `expected ${byteLength(expected)} bytes, retained ${byteLength(editor.plainText)} — ${escapedPreview(editor.plainText, 36)}`, - } - updateChecks(renderer) - addLog( - renderer, - pass ? "ok" : "bad", - pass - ? `editor retained ${byteLength(editor.plainText)} B (ANSI stripped, newlines normalized)` - : `editor mismatch — expected ${byteLength(expected)} B, retained ${byteLength(editor.plainText)} B`, - ) + if (generation !== pasteGeneration) return + const editorMatch = editor?.plainText === normalizeNewlines(expected) + pasteStatus = `${event.bytes.length} bytes | raw fixture match: ${rawMatch ? "yes" : "no"} | editor normalized match: ${editorMatch ? "yes" : "no"}` + updateStatus() }) } - keypressHandler = (event) => { - if (!event.ctrl && /^[1-4]$/.test(event.name)) { - event.preventDefault() - selectedFixture = Number(event.name) - 1 - const current = fixture() - resetTest(renderer, `fixture → ${selectedFixture + 1} ${current.name} (${byteLength(current.payload)} B)`) - return - } - - if (event.ctrl && event.name === "y") { - event.preventDefault() - const current = fixture() - const emitted = renderer.copyToClipboardOSC52(current.payload) - fixturePayloadEmitted = emitted - copyStatus = emitted - ? { tone: "info", text: `emitted ${byteLength(current.payload)} UTF-8 bytes` } - : { tone: "bad", text: "local emission failed" } - roundTripStatus = emitted - ? { tone: "warn", text: "waiting for an exact paste after emission" } - : { tone: "muted", text: "not evaluated" } - updateChecks(renderer) - addLog( - renderer, - emitted ? "info" : "bad", - emitted - ? `osc52 copy → emitted ${byteLength(current.payload)} B (default clipboard target)` - : "osc52 copy → local emission failed", - ) - return - } - - if (event.ctrl && event.name === "k") { - event.preventDefault() - fixturePayloadEmitted = false - const emitted = renderer.clearClipboardOSC52() - copyStatus = emitted - ? { tone: "info", text: "emitted clear request" } - : { tone: "bad", text: "clear emission failed" } - roundTripStatus = { tone: "muted", text: "not applicable to clear requests" } - updateChecks(renderer) - addLog(renderer, emitted ? "info" : "bad", emitted ? "osc52 clear → emitted" : "osc52 clear → emission failed") - return - } - - if (event.ctrl && event.name === "r") { - event.preventDefault() - resetTest(renderer, "reset") - } - } - - capabilityHandler = () => { - updateChecks(renderer) - const capabilities = renderer.capabilities - if (!capabilities) return - const snapshot = `${capabilities.osc52_support}/${capabilities.osc52 ? "hint-yes" : "hint-no"}` - if (snapshot !== lastLoggedCapability) { - lastLoggedCapability = snapshot - addLog( - renderer, - "info", - `capabilities → osc52_support=${capabilities.osc52_support} legacy-hint=${capabilities.osc52 ? "yes" : "no"}`, - ) - } - } - - selectionHandler = (selection) => { - if (selection.isDragging) return - const text = selection.getSelectedText() - if (!text || text.trim().length === 0) return - - renderer.clearSelection() - const emitted = renderer.copyToClipboardOSC52(text) - if (emitted) { - fixturePayloadEmitted = false - copyStatus = { tone: "info", text: `emitted selection (${byteLength(text)} UTF-8 bytes)` } - if (roundTripStatus.text.startsWith("waiting")) { - roundTripStatus = { tone: "muted", text: "superseded by selection copy" } - } - } else { - copyStatus = { tone: "bad", text: "selection copy emission failed" } - } - updateChecks(renderer) - addLog( - renderer, - emitted ? "info" : "bad", - emitted ? `selection copy → emitted ${byteLength(text)} B` : "selection copy → local emission failed", - escapedPreview(text, 56), - ) - } - - renderer.on(CliRenderEvents.CAPABILITIES, capabilityHandler) - renderer.on(CliRenderEvents.SELECTION, selectionHandler) + renderer.keyInput.on("keypress", keyHandler) renderer.keyInput.on("paste", pasteHandler) - renderer.keyInput.on("keypress", keypressHandler) - resetTest(renderer, "ready") + void requestLifecycle(renderer, "create") + editor.focus() } -export function destroy(renderer: CliRenderer): void { - if (pasteHandler) renderer.keyInput.off("paste", pasteHandler) - if (keypressHandler) renderer.keyInput.off("keypress", keypressHandler) - if (capabilityHandler) renderer.off(CliRenderEvents.CAPABILITIES, capabilityHandler) - if (selectionHandler) renderer.off(CliRenderEvents.SELECTION, selectionHandler) - renderer.clearSelection() - container?.destroyRecursively() - container = null - tabsText = null - fixtureText = null - editor = null - checksText = null - logList = null - logRows = [] - logRowId = 0 - lastLoggedCapability = "" - pasteHandler = null - keypressHandler = null - capabilityHandler = null - selectionHandler = null +export function destroy(renderer: CliRenderer): Promise { + destroyPromise ??= (async () => { + pasteGeneration += 1 + if (keyHandler) renderer.keyInput.off("keypress", keyHandler) + if (pasteHandler) renderer.keyInput.off("paste", pasteHandler) + keyHandler = null + pasteHandler = null + renderer.clearSelection() + await requestLifecycle(renderer, "dispose") + root?.destroyRecursively() + root = null + editor = null + statusText = null + })() + return destroyPromise } if (import.meta.main) { - const renderer = await createCliRenderer({ - exitOnCtrlC: true, - targetFps: 30, - }) + const renderer = await createCliRenderer({ exitOnCtrlC: false, targetFps: 30 }) run(renderer) - setupCommonDemoKeys(renderer) + renderer.keyInput.on("keypress", (key: KeyEvent) => { + if ((key.name === "c" && key.ctrl) || (key.name === "q" && key.ctrl)) { + key.preventDefault() + key.stopPropagation() + void destroy(renderer).finally(() => renderer.destroy()) + } + }) } diff --git a/packages/examples/src/index.ts b/packages/examples/src/index.ts index a39124c21a..29cb4f545a 100644 --- a/packages/examples/src/index.ts +++ b/packages/examples/src/index.ts @@ -39,6 +39,7 @@ import * as textSelectionExample from "./text-selection-demo.js" import * as asciiFontSelectionExample from "./ascii-font-selection-demo.js" import * as splitModeExample from "./split-mode-demo.js" import * as splitFooterStreamingDemo from "./split-footer-streaming-demo.js" +import * as splitFooterImageDemo from "./split-footer-image-demo.js" import * as consoleExample from "./console-demo.js" import * as notificationDemo from "./notification-demo.js" import * as vnodeCompositionDemo from "./vnode-composition-demo.js" @@ -73,6 +74,7 @@ import * as nativeAudioDemo from "./native-audio-demo.js" import * as audioCaptureDemo from "./audio-capture-demo.js" import * as audioStreamingDemo from "./audio-streaming-demo.js" import * as clipboardPasteDemo from "./clipboard-paste-demo.js" +import * as nativeImageDemo from "./native-image-demo.js" type ExampleCategory = | "Layout & Composition" @@ -88,7 +90,7 @@ interface ExampleDefinition { name: string description: string run?: (renderer: CliRenderer) => void | Promise - destroy?: (renderer: CliRenderer) => void + destroy?: (renderer: CliRenderer) => void | Promise unavailableMessage?: string } @@ -103,7 +105,7 @@ interface ExampleSection { interface ExampleModule { run?: (renderer: CliRenderer) => void | Promise - destroy?: (renderer: CliRenderer) => void + destroy?: (renderer: CliRenderer) => void | Promise } declare const OPENTUI_BUN_ONLY_EXAMPLES: boolean | undefined @@ -129,6 +131,7 @@ interface ExampleMenuValue { type MenuOptionValue = CategoryMenuValue | SpacerMenuValue | MessageMenuValue | ExampleMenuValue type MenuOption = Omit & { value: MenuOptionValue } type MenuFocusArea = "filter" | "list" +type PendingIntent = "menu" | "quit" interface ExampleTheme { titleColor: RGBA @@ -188,8 +191,8 @@ function threeExample(name: string, description: string, load: () => Promise { if (key.name === "c" && key.ctrl) { - this.cleanup() + key.preventDefault() + key.stopPropagation() + this.requestIntent("quit") return } if (!this.inMenu) { switch (key.name) { case "escape": - this.returnToMenu() + key.preventDefault() + key.stopPropagation() + this.requestIntent("menu") break } return @@ -1257,10 +1279,6 @@ class ExampleSelector { } } - if (key.name === "c" && key.ctrl) { - this.cleanup() - return - } switch (key.name) { case "c": console.log("Capabilities:", this.renderer.capabilities) @@ -1281,28 +1299,40 @@ class ExampleSelector { } private async runSelected(selected: Example): Promise { - this.inMenu = false - this.hideMenuElements() - - if (selected.run) { - this.currentExample = selected - await selected.run(this.renderer) - } else { - if (!this.notImplementedText) { - const theme = MENU_THEMES[this.themeMode] - const unavailableMessage = selected.unavailableMessage ?? `${selected.name} is not implemented yet.` - this.notImplementedText = new TextRenderable(this.renderer, { - id: "not-implemented", - position: "absolute", - left: 10, - top: 10, - content: `${unavailableMessage} Press Escape to return.`, - fg: theme.notImplementedColor, - zIndex: 10, - }) - this.renderer.root.add(this.notImplementedText) + if (this.transitioning) return + this.transitioning = true + + try { + this.inMenu = false + this.hideMenuElements() + + if (selected.run) { + this.currentExample = selected + await selected.run(this.renderer) + } else { + if (!this.notImplementedText) { + const theme = MENU_THEMES[this.themeMode] + const unavailableMessage = selected.unavailableMessage ?? `${selected.name} is not implemented yet.` + this.notImplementedText = new TextRenderable(this.renderer, { + id: "not-implemented", + position: "absolute", + left: 10, + top: 10, + content: `${unavailableMessage} Press Escape to return.`, + fg: theme.notImplementedColor, + zIndex: 10, + }) + this.renderer.root.add(this.notImplementedText) + } + this.renderer.requestRender() } - this.renderer.requestRender() + } catch (error) { + console.error(`Failed to run example "${selected.name}":`, error) + await this.destroyCurrentExample() + this.restoreMenu("run failure") + } finally { + this.transitioning = false + this.schedulePendingIntent() } } @@ -1359,19 +1389,43 @@ class ExampleSelector { this.setMenuFocus("filter") } - private returnToMenu(): void { - if (this.currentExample) { - this.currentExample.destroy?.(this.renderer) - this.currentExample = null + private async returnToMenu(): Promise { + if (this.transitioning) return + this.transitioning = true + try { + await this.destroyCurrentExample() + this.restoreMenu("return failure") + } finally { + this.transitioning = false + this.schedulePendingIntent() } + } - if (this.notImplementedText) { - this.renderer.root.remove(this.notImplementedText) - this.notImplementedText = null + private async destroyCurrentExample(): Promise { + const example = this.currentExample + this.currentExample = null + if (!example) return + + try { + await example.destroy?.(this.renderer) + } catch (error) { + console.error(`Failed to destroy example "${example.name}":`, error) } + } + private restoreMenu(failureContext: string): void { this.inMenu = true - this.restart() + + try { + if (this.notImplementedText) { + this.renderer.root.remove(this.notImplementedText) + this.notImplementedText = null + } + + this.restart() + } catch (error) { + console.error(`Failed to restore the examples menu after ${failureContext}:`, error) + } } private restart(): void { @@ -1382,20 +1436,58 @@ class ExampleSelector { this.renderer.requestRender() } - private cleanup(): void { - if (this.currentExample) { - this.currentExample.destroy?.(this.renderer) + private async cleanup(): Promise { + if (this.transitioning) return + this.transitioning = true + try { + await this.destroyCurrentExample() + this.filterInput?.blur() + this.selectElement?.blur() + this.menuContainer?.destroy() + } catch (error) { + console.error("Failed to clean up the examples menu:", error) + } finally { + this.destroyed = true + this.pendingIntent = null + try { + this.renderer.destroy() + } catch (error) { + console.error("Failed to destroy the examples renderer:", error) + } finally { + this.transitioning = false + } } - if (this.filterInput) { - this.filterInput.blur() + } + + private requestIntent(intent: PendingIntent): void { + if (this.destroyed) return + + if (intent === "quit" || this.pendingIntent === null) { + this.pendingIntent = intent } - if (this.selectElement) { - this.selectElement.blur() + + if (!this.transitioning) { + this.runPendingIntent() } - if (this.menuContainer) { - this.menuContainer.destroy() + } + + private schedulePendingIntent(): void { + if (this.pendingIntent !== null && !this.destroyed) { + queueMicrotask(() => this.runPendingIntent()) + } + } + + private runPendingIntent(): void { + if (this.transitioning || this.destroyed) return + + const intent = this.pendingIntent + this.pendingIntent = null + + if (intent === "quit") { + void this.cleanup() + } else if (intent === "menu" && !this.inMenu) { + void this.returnToMenu() } - this.renderer.destroy() } } diff --git a/packages/examples/src/native-image-demo.ts b/packages/examples/src/native-image-demo.ts new file mode 100644 index 0000000000..b4c83c64cc --- /dev/null +++ b/packages/examples/src/native-image-demo.ts @@ -0,0 +1,355 @@ +#!/usr/bin/env bun + +import { createServer, type Server } from "node:http" +import { readFile } from "node:fs/promises" +import { pathToFileURL } from "node:url" + +import { + BoxRenderable, + CliRenderer, + ImageRenderable, + RGBA, + TextAttributes, + TextRenderable, + createCliRenderer, + type ImageSource, + type ImageRenderProtocol, + type KeyEvent, +} from "@opentui/core" +import { setupCommonDemoKeys } from "./lib/standalone-keys.js" + +// @ts-ignore Bun embeds imported assets and returns their runtime paths. +import gifPath from "./assets/image-demo.gif" with { type: "image/gif" } +// @ts-ignore Bun embeds imported assets and returns their runtime paths. +import pngPath from "./assets/image-demo.png" with { type: "image/png" } +// @ts-ignore Bun embeds imported assets and returns their runtime paths. +import jpegPath from "./assets/dragon.jpg" with { type: "image/jpeg" } +// @ts-ignore Bun embeds imported assets and returns their runtime paths. +import webpPath from "./assets/image-demo.webp" with { type: "image/webp" } + +const P = { + page: "#090d18", + header: "#10172a", + footer: "#0d1323", + text: "#f4f7ff", + muted: "#8d98b5", + cyan: "#55d6d0", + violet: "#a78bfa", + coral: "#fb7185", + lime: "#a3e635", + cards: ["#111c2d", "#17192e", "#211827", "#14231f"], +} as const + +type FitMode = "fit" | "cover" + +interface GalleryItem { + name: string + sourceType: string + source: ImageSource + accent: string + card: string +} + +let root: BoxRenderable | null = null +let server: Server | null = null +let keyListener: ((key: KeyEvent) => void) | null = null +let capabilityListener: (() => void) | null = null +let controlsText: TextRenderable | null = null +let previews: ImageRenderable[] = [] +let overlayBox: BoxRenderable | null = null +let fitMode: FitMode = "fit" +let protocol: ImageRenderProtocol = "auto" +let overlayVisible = true +let overlayX = 2 +let overlayY = 9 +let boxAlphaIndex = 1 + +const protocols: ImageRenderProtocol[] = ["auto", "kitty", "sixel", "blocks"] +const boxAlphas = [0, 0.5, 1] +const overlayWidth = 24 +const overlayHeight = 8 +const headerHeight = 4 +const footerHeight = 3 + +function updateControls(): void { + if (!controlsText) return + const effective = previews[0]?.effectiveProtocol ?? "blocks" + controlsText.content = `F ${fitMode.toUpperCase()} P ${protocol.toUpperCase()} → ${effective.toUpperCase()} O ${overlayVisible ? "ON" : "OFF"} A ${boxAlphas[boxAlphaIndex]} ARROWS MOVE ESC MENU` +} + +function updateOverlay(): void { + if (overlayBox) { + if (root && root.width > 0 && root.height > 0) { + overlayX = Math.max(0, Math.min(overlayX, Math.max(0, root.width - overlayWidth))) + overlayY = Math.max( + headerHeight, + Math.min(overlayY, Math.max(headerHeight, root.height - footerHeight - overlayHeight)), + ) + } + overlayBox.left = overlayX + overlayBox.top = overlayY + overlayBox.title = `OVERLAY ${overlayX},${overlayY}` + overlayBox.backgroundColor = RGBA.fromValues(0.15, 0.55, 0.95, boxAlphas[boxAlphaIndex]) + overlayBox.visible = overlayVisible + } + updateControls() +} + +function createCard(renderer: CliRenderer, item: GalleryItem, index: number): BoxRenderable { + const card = new BoxRenderable(renderer, { + id: `native-image-card-${index}`, + width: "auto", + height: "100%", + minWidth: 18, + flexBasis: 24, + flexGrow: 1, + flexShrink: 1, + flexDirection: "column", + backgroundColor: item.card, + }) + + card.add( + new BoxRenderable(renderer, { + id: `native-image-accent-${index}`, + width: "100%", + height: 1, + flexGrow: 0, + flexShrink: 0, + backgroundColor: item.accent, + }), + ) + + const heading = new BoxRenderable(renderer, { + id: `native-image-heading-${index}`, + width: "100%", + height: 4, + flexGrow: 0, + flexShrink: 0, + flexDirection: "column", + paddingLeft: 2, + paddingTop: 1, + backgroundColor: item.card, + }) + heading.add( + new TextRenderable(renderer, { + id: `native-image-title-${index}`, + content: item.name, + fg: P.text, + attributes: TextAttributes.BOLD, + }), + ) + heading.add( + new TextRenderable(renderer, { + id: `native-image-source-${index}`, + content: item.sourceType, + fg: item.accent, + }), + ) + card.add(heading) + + const metadata = new TextRenderable(renderer, { + id: `native-image-metadata-${index}`, + content: "LOADING\nNative decoder", + width: "100%", + height: 4, + flexGrow: 0, + flexShrink: 0, + paddingLeft: 2, + paddingTop: 1, + fg: P.muted, + bg: item.card, + }) + + const preview = new ImageRenderable(renderer, { + id: `native-image-preview-${index}`, + source: item.source, + fit: fitMode, + protocol, + width: "100%", + height: "auto", + flexGrow: 1, + flexShrink: 1, + minHeight: 5, + onLoad: (image) => { + const info = image.info() + metadata.content = `${info.format.toUpperCase()} ${info.width}×${info.height}\nRGBA8 ${info.hasAlpha ? "ALPHA" : "OPAQUE"}` + }, + onError: (error) => { + metadata.content = `LOAD FAILED\n${error instanceof Error ? error.message : String(error)}` + metadata.fg = P.coral + }, + }) + previews.push(preview) + card.add(preview) + card.add(metadata) + return card +} + +async function startImageServer(gif: Uint8Array): Promise { + server = createServer((request, response) => { + if (request.url !== "/image") { + response.writeHead(404).end() + return + } + response.writeHead(200, { "content-type": "application/octet-stream" }) + response.end(gif) + }) + await new Promise((resolve, reject) => { + server!.once("error", reject) + server!.listen(0, "127.0.0.1", () => { + server!.off("error", reject) + resolve() + }) + }) + const address = server.address() + if (!address || typeof address === "string") throw new Error("Image demo server did not expose a TCP port") + return `http://127.0.0.1:${address.port}/image` +} + +export async function run(renderer: CliRenderer): Promise { + renderer.start() + renderer.setBackgroundColor(P.page) + + const [webpBytes, gifBytes] = await Promise.all([readFile(webpPath), readFile(gifPath)]) + const gifUrl = await startImageServer(gifBytes) + + root = new BoxRenderable(renderer, { + id: "native-image-demo", + width: "100%", + height: "100%", + flexDirection: "column", + backgroundColor: P.page, + }) + renderer.root.add(root) + + const header = new BoxRenderable(renderer, { + id: "native-image-header", + width: "100%", + height: 4, + flexGrow: 0, + flexShrink: 0, + flexDirection: "row", + alignItems: "center", + paddingLeft: 3, + paddingRight: 3, + backgroundColor: P.header, + }) + header.add( + new TextRenderable(renderer, { + id: "native-image-heading", + content: "NATIVE IMAGE LAB", + fg: P.text, + attributes: TextAttributes.BOLD, + }), + ) + root.add(header) + + const gallery = new BoxRenderable(renderer, { + id: "native-image-gallery", + width: "100%", + height: "auto", + flexGrow: 1, + flexShrink: 1, + flexDirection: "row", + alignItems: "stretch", + backgroundColor: P.page, + }) + root.add(gallery) + + const items: GalleryItem[] = [ + { name: "LOCAL PNG", sourceType: "filesystem path", source: pngPath, accent: P.cyan, card: P.cards[0] }, + { + name: "JPEG URL", + sourceType: "file: URL", + source: pathToFileURL(jpegPath), + accent: P.violet, + card: P.cards[1], + }, + { name: "WEBP BYTES", sourceType: "Uint8Array", source: webpBytes, accent: P.coral, card: P.cards[2] }, + { name: "GIF FETCH", sourceType: "HTTP URL", source: gifUrl, accent: P.lime, card: P.cards[3] }, + ] + for (const [index, item] of items.entries()) gallery.add(createCard(renderer, item, index)) + + const footer = new BoxRenderable(renderer, { + id: "native-image-footer", + width: "100%", + height: 3, + flexGrow: 0, + flexShrink: 0, + alignItems: "center", + justifyContent: "center", + backgroundColor: P.footer, + }) + controlsText = new TextRenderable(renderer, { + id: "native-image-controls", + content: "", + fg: P.muted, + attributes: TextAttributes.BOLD, + }) + footer.add(controlsText) + root.add(footer) + + overlayBox = new BoxRenderable(renderer, { + id: "native-image-overlay", + position: "absolute", + left: overlayX, + top: overlayY, + width: overlayWidth, + height: overlayHeight, + zIndex: 100, + visible: overlayVisible, + border: true, + borderColor: P.text, + title: "OVERLAY", + shouldFill: true, + }) + root.add(overlayBox) + updateOverlay() + + keyListener = (key: KeyEvent) => { + if (key.name === "f") { + fitMode = fitMode === "fit" ? "cover" : "fit" + for (const preview of previews) preview.fit = fitMode + } else if (key.name === "p") { + protocol = protocols[(protocols.indexOf(protocol) + 1) % protocols.length] + for (const preview of previews) preview.protocol = protocol + } else if (key.name === "o") overlayVisible = !overlayVisible + else if (key.name === "a") boxAlphaIndex = (boxAlphaIndex + 1) % boxAlphas.length + else if (key.name === "left") overlayX -= 2 + else if (key.name === "right") overlayX += 2 + else if (key.name === "up") overlayY -= 1 + else if (key.name === "down") overlayY += 1 + else return + + updateOverlay() + } + renderer.keyInput.on("keypress", keyListener) + capabilityListener = updateControls + renderer.on("capabilities", capabilityListener) +} + +export function destroy(renderer: CliRenderer): void { + if (keyListener) renderer.keyInput.off("keypress", keyListener) + if (capabilityListener) renderer.off("capabilities", capabilityListener) + keyListener = null + capabilityListener = null + root?.destroyRecursively() + root = null + previews = [] + overlayBox = null + controlsText = null + fitMode = "fit" + protocol = "auto" + overlayVisible = true + overlayX = 2 + overlayY = 9 + boxAlphaIndex = 1 + server?.close() + server = null +} + +if (import.meta.main) { + const renderer = await createCliRenderer({ exitOnCtrlC: true }) + await run(renderer) + setupCommonDemoKeys(renderer) +} diff --git a/packages/examples/src/split-footer-image-demo.ts b/packages/examples/src/split-footer-image-demo.ts new file mode 100644 index 0000000000..fee0df9470 --- /dev/null +++ b/packages/examples/src/split-footer-image-demo.ts @@ -0,0 +1,414 @@ +import { + BoxRenderable, + CliRenderEvents, + ImageRenderable, + TextAttributes, + TextRenderable, + createCliRenderer, + type CliRenderer, + type ImageRenderProtocol, + type KeyEvent, + type ScrollbackSurface, +} from "@opentui/core" +import { setupCommonDemoKeys } from "./lib/standalone-keys.js" + +// @ts-ignore Bun embeds imported assets and returns their runtime paths. +import jpegPath from "./assets/dragon.jpg" with { type: "image/jpeg" } +// @ts-ignore Bun embeds imported assets and returns their runtime paths. +import pngPath from "./assets/image-demo.png" with { type: "image/png" } + +const DEFAULT_FOOTER_HEIGHT = 12 +const MIN_FOOTER_HEIGHT = 7 +const MAX_FOOTER_HEIGHT = 18 + +const PALETTE = { + background: "#071018", + panel: "#0D1B26", + imagePanel: "#102938", + border: "#2C6075", + accent: "#56D6C9", + title: "#F4FBFF", + text: "#D6E8F0", + warning: "#FFCB6B", +} as const + +const SOURCES = [ + { name: "PNG", path: pngPath }, + { name: "JPEG", path: jpegPath }, +] as const +const PROTOCOLS: ImageRenderProtocol[] = ["auto", "kitty", "sixel", "blocks"] + +class SplitFooterImageDemo { + private shell: BoxRenderable + private image: ImageRenderable + private status: TextRenderable + private sourceIndex = 0 + private protocolIndex = 0 + private fit: "fit" | "cover" | "fill" = "fit" + private commitCount = 0 + private imageCommitCount = 0 + private imageCommitPending = false + private imageCommitSurface: ScrollbackSurface | null = null + private lastAction = "Ready" + private destroyed = false + + constructor(private renderer: CliRenderer) { + if (renderer.screenMode !== "split-footer") renderer.screenMode = "split-footer" + renderer.footerHeight = DEFAULT_FOOTER_HEIGHT + if (renderer.externalOutputMode !== "capture-stdout") renderer.externalOutputMode = "capture-stdout" + renderer.setBackgroundColor(PALETTE.background) + + this.shell = new BoxRenderable(renderer, { + id: "split-footer-image-shell", + width: "100%", + height: "100%", + flexDirection: "column", + border: ["top"], + borderColor: PALETTE.border, + backgroundColor: PALETTE.panel, + paddingLeft: 1, + paddingRight: 1, + }) + + const heading = new TextRenderable(renderer, { + id: "split-footer-image-heading", + width: "100%", + height: 2, + flexGrow: 0, + flexShrink: 0, + content: "SPLIT FOOTER / LIVE IMAGE", + fg: PALETTE.title, + attributes: TextAttributes.BOLD, + }) + + const body = new BoxRenderable(renderer, { + id: "split-footer-image-body", + width: "100%", + height: "auto", + flexGrow: 1, + flexShrink: 1, + flexDirection: "row", + gap: 2, + backgroundColor: PALETTE.panel, + }) + + const imagePanel = new BoxRenderable(renderer, { + id: "split-footer-image-panel", + width: 28, + height: "100%", + minWidth: 12, + flexGrow: 0, + flexShrink: 1, + border: true, + borderColor: PALETTE.accent, + title: "LIVE PLACEMENT", + backgroundColor: PALETTE.imagePanel, + padding: 1, + }) + + this.image = new ImageRenderable(renderer, { + id: "split-footer-live-image", + source: SOURCES[this.sourceIndex].path, + protocol: PROTOCOLS[this.protocolIndex], + fit: this.fit, + width: "100%", + height: "100%", + onLoad: () => { + this.lastAction = `${SOURCES[this.sourceIndex].name} loaded into the live footer` + this.refreshStatus() + }, + onError: (error) => { + this.lastAction = `Image load failed: ${error instanceof Error ? error.message : String(error)}` + this.refreshStatus() + }, + }) + imagePanel.add(this.image) + + this.status = new TextRenderable(renderer, { + id: "split-footer-image-status", + width: "auto", + height: "100%", + flexGrow: 1, + flexShrink: 1, + wrapMode: "word", + content: "", + fg: PALETTE.text, + bg: PALETTE.panel, + }) + + body.add(imagePanel) + body.add(this.status) + this.shell.add(heading) + this.shell.add(body) + renderer.root.add(this.shell) + + renderer.keyInput.on("keypress", this.handleKeyPress) + renderer.on("capabilities", this.refreshStatus) + renderer.on(CliRenderEvents.RESIZE, this.refreshStatus) + renderer.on(CliRenderEvents.DESTROY, this.handleRendererDestroy) + this.refreshStatus() + } + + private refreshStatus = (): void => { + if (this.destroyed || this.status.isDestroyed) return + const requested = PROTOCOLS[this.protocolIndex] + const effective = this.image.effectiveProtocol + const mode = this.renderer.externalOutputMode + const terminal = this.renderer.capabilities?.terminal + const terminalLabel = terminal + ? `${terminal.name || "unknown"}${terminal.version ? ` ${terminal.version}` : ""}` + : "detecting" + const commitHint = mode === "capture-stdout" ? "W appends a scrollback snapshot" : "W requires capture mode" + this.status.content = [ + `${SOURCES[this.sourceIndex].name} ${requested.toUpperCase()} -> ${effective.toUpperCase()} ${this.fit.toUpperCase()}`, + `footer ${this.renderer.footerHeight} / ${mode} / ${terminalLabel} / text ${this.commitCount} image ${this.imageCommitCount}`, + "A bare image C composed image W text snapshot I source P protocol F fit", + `[ ] height M output mode ${commitHint}. Native history follows the effective image protocol.`, + this.lastAction, + ].join("\n") + this.status.fg = mode === "capture-stdout" ? PALETTE.text : PALETTE.warning + } + + private cycleSource(): void { + this.sourceIndex = (this.sourceIndex + 1) % SOURCES.length + this.lastAction = `Loading ${SOURCES[this.sourceIndex].name}` + this.image.source = SOURCES[this.sourceIndex].path + this.refreshStatus() + } + + private cycleProtocol(): void { + this.protocolIndex = (this.protocolIndex + 1) % PROTOCOLS.length + this.image.protocol = PROTOCOLS[this.protocolIndex] + this.lastAction = `Requested ${PROTOCOLS[this.protocolIndex]} rendering` + this.refreshStatus() + } + + private cycleFit(): void { + this.fit = this.fit === "fit" ? "cover" : this.fit === "cover" ? "fill" : "fit" + this.image.fit = this.fit + this.lastAction = `Fit mode changed to ${this.fit}` + this.refreshStatus() + } + + private adjustFooterHeight(delta: number): void { + const next = Math.min(MAX_FOOTER_HEIGHT, Math.max(MIN_FOOTER_HEIGHT, this.renderer.footerHeight + delta)) + if (next === this.renderer.footerHeight) { + this.lastAction = "Footer height is already at the demo limit" + } else { + this.renderer.footerHeight = next + this.lastAction = `Footer moved to ${next} rows; the native image should move with it` + } + this.refreshStatus() + } + + private toggleOutputMode(): void { + this.renderer.externalOutputMode = + this.renderer.externalOutputMode === "capture-stdout" ? "passthrough" : "capture-stdout" + this.lastAction = + this.renderer.externalOutputMode === "capture-stdout" + ? "Capture mode restored; scrollback commits are enabled" + : "Passthrough mode enabled; change height to exercise placement relocation" + this.refreshStatus() + } + + private writeScrollbackSnapshot(): void { + if (this.renderer.externalOutputMode !== "capture-stdout") { + this.lastAction = "Switch to capture mode before writing a scrollback snapshot" + this.refreshStatus() + return + } + + const commit = ++this.commitCount + const source = SOURCES[this.sourceIndex].name + const protocol = this.image.effectiveProtocol + this.renderer.writeToScrollback((ctx) => { + const root = new TextRenderable(ctx.renderContext, { + id: `split-footer-image-commit-${commit}`, + position: "absolute", + left: 0, + top: 0, + width: ctx.width, + height: 2, + content: `IMAGE EVENT ${String(commit).padStart(2, "0")} ${source} via ${protocol}\nLive footer repaints atomically after this text snapshot.`, + fg: PALETTE.accent, + bg: PALETTE.background, + attributes: TextAttributes.BOLD, + }) + return { + root, + width: ctx.width, + height: 2, + startOnNewLine: true, + trailingNewline: true, + } + }) + + this.lastAction = `Scrollback snapshot ${commit} queued; footer image remains live` + this.refreshStatus() + } + + private async writeImageToScrollback(composed: boolean): Promise { + if (this.imageCommitPending) { + this.lastAction = "An image scrollback commit is already loading" + this.refreshStatus() + return + } + if (this.renderer.externalOutputMode !== "capture-stdout") { + this.lastAction = "Switch to capture mode before writing an image to scrollback" + this.refreshStatus() + return + } + + this.imageCommitPending = true + const commit = this.imageCommitCount + 1 + const source = SOURCES[this.sourceIndex] + const variant = composed ? "composed image" : "bare image" + const surface = this.renderer.createScrollbackSurface({ startOnNewLine: true }) + this.imageCommitSurface = surface + this.lastAction = `Loading ${source.name} for ${variant} scrollback commit ${commit}` + this.refreshStatus() + + const width = Math.max(1, Math.min(composed ? 34 : 30, surface.width)) + const height = composed ? 10 : 8 + const image = new ImageRenderable(surface.renderContext, { + id: `split-footer-image-history-${composed ? "composed" : "bare"}-${commit}`, + source: source.path, + protocol: PROTOCOLS[this.protocolIndex], + fit: this.fit, + width: composed ? "100%" : width, + height: composed ? "100%" : height, + }) + if (composed) { + const card = new BoxRenderable(surface.renderContext, { + id: `split-footer-image-history-card-${commit}`, + width, + height, + border: true, + borderColor: PALETTE.accent, + title: `${source.name} / ${PROTOCOLS[this.protocolIndex].toUpperCase()}`, + backgroundColor: PALETTE.imagePanel, + padding: 1, + }) + card.add(image) + surface.root.add(card) + } else { + surface.root.add(image) + } + + try { + await image.loadPromise + if (this.destroyed || surface.isDestroyed) return + if (this.renderer.externalOutputMode !== "capture-stdout") { + this.lastAction = "Image loaded, but output mode changed before commit" + return + } + if (!image.image || image.loadError) { + this.lastAction = `Image scrollback load failed: ${String(image.loadError ?? "no image")}` + return + } + + surface.render() + surface.commitRows(0, surface.height, { rowColumns: width, trailingNewline: true }) + this.imageCommitCount = commit + this.lastAction = `${variant} scrollback commit ${commit} queued with the effective protocol and block fallback` + } catch (error) { + if (!this.destroyed) { + this.lastAction = `Image scrollback commit failed: ${error instanceof Error ? error.message : String(error)}` + } + } finally { + if (!surface.isDestroyed) surface.destroy() + if (this.imageCommitSurface === surface) this.imageCommitSurface = null + this.imageCommitPending = false + this.refreshStatus() + } + } + + private handleKeyPress = (key: KeyEvent): void => { + if (key.ctrl || key.meta || key.option) return + switch (key.name) { + case "i": + key.preventDefault() + this.cycleSource() + return + case "p": + key.preventDefault() + this.cycleProtocol() + return + case "f": + key.preventDefault() + this.cycleFit() + return + case "[": + key.preventDefault() + this.adjustFooterHeight(-1) + return + case "]": + key.preventDefault() + this.adjustFooterHeight(1) + return + case "m": + key.preventDefault() + this.toggleOutputMode() + return + case "w": + key.preventDefault() + this.writeScrollbackSnapshot() + return + case "a": + key.preventDefault() + void this.writeImageToScrollback(false) + return + case "c": + key.preventDefault() + void this.writeImageToScrollback(true) + return + } + } + + private handleRendererDestroy = (): void => { + this.destroy() + } + + public destroy(): void { + if (this.destroyed) return + this.destroyed = true + this.renderer.keyInput.off("keypress", this.handleKeyPress) + this.renderer.off("capabilities", this.refreshStatus) + this.renderer.off(CliRenderEvents.RESIZE, this.refreshStatus) + this.renderer.off(CliRenderEvents.DESTROY, this.handleRendererDestroy) + this.imageCommitSurface?.destroy() + this.imageCommitSurface = null + if (!this.shell.isDestroyed) this.shell.destroyRecursively() + if (!this.renderer.isDestroyed) { + this.renderer.externalOutputMode = "passthrough" + this.renderer.screenMode = "main-screen" + } + } +} + +let activeDemo: SplitFooterImageDemo | null = null + +export function run(renderer: CliRenderer): void { + activeDemo?.destroy() + activeDemo = new SplitFooterImageDemo(renderer) +} + +export function destroy(_renderer: CliRenderer): void { + activeDemo?.destroy() + activeDemo = null +} + +if (import.meta.main) { + const renderer = await createCliRenderer({ + targetFps: 30, + exitOnCtrlC: true, + useMouse: false, + screenMode: "split-footer", + footerHeight: DEFAULT_FOOTER_HEIGHT, + externalOutputMode: "capture-stdout", + consoleMode: "disabled", + }) + run(renderer) + setupCommonDemoKeys(renderer) + renderer.start() +} diff --git a/packages/react/jsx-namespace.d.ts b/packages/react/jsx-namespace.d.ts index b9dd5a457f..c9148fdcbc 100644 --- a/packages/react/jsx-namespace.d.ts +++ b/packages/react/jsx-namespace.d.ts @@ -5,6 +5,7 @@ import type { CodeProps, DiffProps, ExtendedIntrinsicElements, + ImageProps, InputProps, LineBreakProps, LineNumberProps, @@ -50,6 +51,7 @@ export namespace JSX { "ascii-font": AsciiFontProps "tab-select": TabSelectProps "line-number": LineNumberProps + image: ImageProps // Text modifiers b: SpanProps i: SpanProps diff --git a/packages/react/src/components/index.ts b/packages/react/src/components/index.ts index 20b51e16b5..aea3522560 100644 --- a/packages/react/src/components/index.ts +++ b/packages/react/src/components/index.ts @@ -3,6 +3,7 @@ import { BoxRenderable, CodeRenderable, DiffRenderable, + ImageRenderable, InputRenderable, LineNumberRenderable, MarkdownRenderable, @@ -35,6 +36,7 @@ export const baseComponents = { "ascii-font": ASCIIFontRenderable, "tab-select": TabSelectRenderable, "line-number": LineNumberRenderable, + image: ImageRenderable, // Text modifiers span: SpanRenderable, diff --git a/packages/react/src/types/components.ts b/packages/react/src/types/components.ts index f2701e8f8c..eb79e1461c 100644 --- a/packages/react/src/types/components.ts +++ b/packages/react/src/types/components.ts @@ -8,6 +8,8 @@ import type { CodeRenderable, DiffRenderable, DiffRenderableOptions, + ImageRenderable, + ImageRenderableOptions, InputRenderable, InputRenderableOptions, LineNumberOptions, @@ -99,7 +101,9 @@ export type GetNonStyledProperties = | "drawUnstyledText" : TConstructor extends RenderableConstructor ? NonStyledProps | "content" | "syntaxStyle" | "treeSitterClient" | "conceal" | "renderNode" - : NonStyledProps + : TConstructor extends RenderableConstructor + ? NonStyledProps | "source" + : NonStyledProps // ============================================================================ // Component Props System @@ -155,6 +159,8 @@ export type TextareaProps = ComponentProps export type CodeProps = ComponentProps +export type ImageProps = ComponentProps + export type MarkdownProps = ComponentProps export type DiffProps = ComponentProps diff --git a/packages/react/tests/image.test.tsx b/packages/react/tests/image.test.tsx new file mode 100644 index 0000000000..1099a94588 --- /dev/null +++ b/packages/react/tests/image.test.tsx @@ -0,0 +1,147 @@ +import { afterEach, beforeEach, describe, expect, it, spyOn } from "bun:test" +import { ImageRenderable } from "@opentui/core" +import { act, useState } from "react" +import { testRender } from "../src/test-utils.js" + +const PNG_1X1 = Uint8Array.from( + Buffer.from( + "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR4AWP4z8DwHwAFAAH/e+m+7wAAAABJRU5ErkJggg==", + "base64", + ), +) + +let testSetup: Awaited> | undefined +let consoleError: ReturnType + +beforeEach(() => { + consoleError = spyOn(console, "error") +}) + +afterEach(() => { + try { + act(() => testSetup?.renderer.destroy()) + expect(consoleError).not.toHaveBeenCalled() + } finally { + testSetup = undefined + consoleError.mockRestore() + } +}) + +describe("React Renderer | image element", () => { + it("creates an ImageRenderable and loads encoded bytes", async () => { + let imageRef: ImageRenderable | null = null + const loaded: string[] = [] + + testSetup = await testRender( + { + imageRef = renderable + }} + source={PNG_1X1} + onLoad={(image) => loaded.push(image.info().format)} + style={{ width: 4, height: 2 }} + />, + { width: 10, height: 6 }, + ) + await testSetup.renderOnce() + + expect(imageRef).toBeInstanceOf(ImageRenderable) + await imageRef!.loadPromise + expect(loaded).toEqual(["png"]) + expect(imageRef!.image?.width).toBe(1) + expect(imageRef!.loadError).toBeNull() + }) + + it("keeps the loaded image across parent rerenders", async () => { + let imageRef: ImageRenderable | null = null + let rerender!: () => void + let loads = 0 + + function App() { + const [revision, setRevision] = useState(0) + rerender = () => setRevision((value) => value + 1) + return ( + { + imageRef = renderable + }} + source={PNG_1X1} + onLoad={() => loads++} + style={{ width: 4, height: 2, left: revision }} + /> + ) + } + + testSetup = await testRender(, { width: 10, height: 6 }) + await imageRef!.loadPromise + const loadedImage = imageRef!.image + + act(() => rerender()) + await testSetup.renderOnce() + + expect(imageRef!.image).toBe(loadedImage) + expect(loads).toBe(1) + }) + + it("clears the image when the source prop is removed", async () => { + let imageRef: ImageRenderable | null = null + let setVisible!: (visible: boolean) => void + + function App() { + const [visible, setImageVisible] = useState(true) + setVisible = setImageVisible + return ( + { + imageRef = renderable + }} + {...(visible ? { source: PNG_1X1 } : {})} + protocol="blocks" + style={{ width: 2, height: 1 }} + /> + ) + } + + testSetup = await testRender(, { width: 4, height: 2 }) + await imageRef!.loadPromise + await testSetup.renderOnce() + expect(testSetup.captureCharFrame()).toContain("█") + + act(() => setVisible(false)) + if (imageRef!.loadPromise) await imageRef!.loadPromise + await testSetup.renderOnce() + + expect(imageRef!.source).toBeUndefined() + expect(imageRef!.image).toBeNull() + expect(imageRef!.loadError).toBeNull() + expect(testSetup.captureCharFrame()).not.toContain("█") + }) + + it("restores image defaults when optional props are removed", async () => { + let imageRef: ImageRenderable | null = null + let setConfigured!: (configured: boolean) => void + + function App() { + const [configured, setImageConfigured] = useState(true) + setConfigured = setImageConfigured + return ( + { + imageRef = renderable + }} + {...(configured ? { fit: "fill" as const, protocol: "kitty" as const } : {})} + /> + ) + } + + testSetup = await testRender(, { width: 4, height: 2 }) + expect(imageRef!.fit).toBe("fill") + expect(imageRef!.protocol).toBe("kitty") + + act(() => setConfigured(false)) + + expect(imageRef!.fit).toBe("fit") + expect(imageRef!.protocol).toBe("auto") + expect(imageRef!.effectiveProtocol).toBe("blocks") + }) +}) diff --git a/packages/solid/jsx-runtime.d.ts b/packages/solid/jsx-runtime.d.ts index 8974d22d93..1e058841a5 100644 --- a/packages/solid/jsx-runtime.d.ts +++ b/packages/solid/jsx-runtime.d.ts @@ -3,6 +3,7 @@ import type { BoxProps, CodeProps, ExtendedIntrinsicElements, + ImageProps, InputProps, LinkProps, MarkdownProps, @@ -38,6 +39,7 @@ export declare namespace JSX { code: CodeProps textarea: TextareaProps markdown: MarkdownProps + image: ImageProps b: SpanProps strong: SpanProps diff --git a/packages/solid/src/elements/catalogue.ts b/packages/solid/src/elements/catalogue.ts index de7e131d7f..29b49f9030 100644 --- a/packages/solid/src/elements/catalogue.ts +++ b/packages/solid/src/elements/catalogue.ts @@ -3,6 +3,7 @@ import { BoxRenderable, CodeRenderable, DiffRenderable, + ImageRenderable, InputRenderable, LineNumberRenderable, MarkdownRenderable, @@ -101,6 +102,7 @@ export const baseComponents = { diff: DiffRenderable, line_number: LineNumberRenderable, markdown: MarkdownRenderable, + image: ImageRenderable, span: SpanRenderable, strong: BoldSpanRenderable, diff --git a/packages/solid/src/reconciler.ts b/packages/solid/src/reconciler.ts index 1b62430e4f..9845e8a694 100644 --- a/packages/solid/src/reconciler.ts +++ b/packages/solid/src/reconciler.ts @@ -2,6 +2,7 @@ import { BaseRenderable, createTextAttributes, + ImageRenderable, InputRenderable, InputRenderableEvents, isTextNodeRenderable, @@ -18,7 +19,7 @@ import { type TextNodeOptions, } from "@opentui/core" import { decodeHTMLStrict } from "entities" -import { useContext } from "solid-js" +import { onCleanup, useContext } from "solid-js" import { createRenderer } from "./renderer/index.js" import { getComponentCatalogue, RendererContext, SlotRenderable } from "./elements/index.js" import { getNextId } from "./utils/id-counter.js" @@ -202,6 +203,11 @@ export const { } const element = new elements[tagName](solidRenderer, { id }) + if (element instanceof ImageRenderable) { + onCleanup(() => { + element.source = undefined + }) + } log("Element created with id:", id) return element }, @@ -326,9 +332,16 @@ export const { } break case "style": - for (const prop in value) { - const propVal = value[prop] - if (prev !== undefined && propVal === prev[prop]) continue + const nextStyle = value ?? {} + const previousStyle = prev ?? {} + for (const prop in previousStyle) { + if (Object.prototype.hasOwnProperty.call(nextStyle, prop)) continue + // @ts-expect-error todo validate if prop is actually settable + node[prop] = undefined + } + for (const prop in nextStyle) { + const propVal = nextStyle[prop] + if (propVal === previousStyle[prop]) continue // @ts-expect-error todo validate if prop is actually settable node[prop] = propVal } diff --git a/packages/solid/src/types/elements.ts b/packages/solid/src/types/elements.ts index 622b9e2a1d..2b23a36221 100644 --- a/packages/solid/src/types/elements.ts +++ b/packages/solid/src/types/elements.ts @@ -6,6 +6,8 @@ import type { BoxRenderable, CodeOptions, CodeRenderable, + ImageRenderable, + ImageRenderableOptions, InputRenderable, InputRenderableOptions, KeyEvent, @@ -84,7 +86,9 @@ export type GetNonStyledProperties = ? NonStyledProps | "content" | "filetype" | "syntaxStyle" | "treeSitterClient" : TConstructor extends RenderableConstructor ? NonStyledProps | "content" | "syntaxStyle" | "treeSitterClient" | "conceal" | "renderNode" - : NonStyledProps + : TConstructor extends RenderableConstructor + ? NonStyledProps | "source" + : NonStyledProps // ============================================================================ // Component Props System @@ -159,6 +163,8 @@ export type ScrollBoxProps = ComponentProps, Sc export type CodeProps = ComponentProps +export type ImageProps = ComponentProps + export type MarkdownProps = ComponentProps // ============================================================================ diff --git a/packages/solid/tests/image.test.tsx b/packages/solid/tests/image.test.tsx new file mode 100644 index 0000000000..c0fd828c78 --- /dev/null +++ b/packages/solid/tests/image.test.tsx @@ -0,0 +1,153 @@ +import { afterEach, describe, expect, it } from "bun:test" +import { ImageRenderable } from "@opentui/core" +import { createSignal, Show } from "solid-js" +import { testRender } from "../index.js" + +const PNG_1X1 = Uint8Array.from( + Buffer.from( + "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR4AWP4z8DwHwAFAAH/e+m+7wAAAABJRU5ErkJggg==", + "base64", + ), +) + +let testSetup: Awaited> | undefined + +afterEach(() => { + testSetup?.renderer.destroy() + testSetup = undefined +}) + +describe("image component", () => { + it("creates an ImageRenderable and loads encoded bytes", async () => { + let imageRef: ImageRenderable | undefined + const loaded: string[] = [] + + testSetup = await testRender( + () => ( + loaded.push(image.info().format)} + style={{ width: 4, height: 2 }} + /> + ), + { width: 10, height: 6 }, + ) + await testSetup.renderOnce() + + expect(imageRef).toBeInstanceOf(ImageRenderable) + await imageRef!.loadPromise + expect(loaded).toEqual(["png"]) + expect(imageRef!.image?.width).toBe(1) + expect(imageRef!.loadError).toBeNull() + }) + + it("replaces and clears the image when the source prop changes reactively", async () => { + let imageRef: ImageRenderable | undefined + const [source, setSource] = createSignal(PNG_1X1) + + testSetup = await testRender( + () => , + { + width: 4, + height: 2, + }, + ) + await imageRef!.loadPromise + await testSetup.renderOnce() + const firstImage = imageRef!.image! + expect(testSetup.captureCharFrame()).toContain("█") + + setSource(PNG_1X1.slice()) + await imageRef!.loadPromise + await testSetup.renderOnce() + const replacementImage = imageRef!.image! + expect(replacementImage).not.toBe(firstImage) + expect(() => firstImage.info()).toThrow("NativeImage is disposed") + expect(testSetup.captureCharFrame()).toContain("█") + + setSource(undefined) + await testSetup.renderOnce() + expect(imageRef!.image).toBeNull() + expect(() => replacementImage.info()).toThrow("NativeImage is disposed") + expect(testSetup.captureCharFrame()).not.toContain("█") + }) + + it("restores image defaults when optional props are cleared", async () => { + let imageRef: ImageRenderable | undefined + const [fit, setFit] = createSignal<"fill" | undefined>("fill") + const [protocol, setProtocol] = createSignal<"kitty" | undefined>("kitty") + + testSetup = await testRender(() => , { + width: 4, + height: 2, + }) + expect(imageRef!.fit).toBe("fill") + expect(imageRef!.protocol).toBe("kitty") + + setFit(undefined) + setProtocol(undefined) + + expect(imageRef!.fit).toBe("fit") + expect(imageRef!.protocol).toBe("auto") + expect(imageRef!.effectiveProtocol).toBe("blocks") + }) + + it("restores image defaults when style keys are removed", async () => { + let imageRef: ImageRenderable | undefined + const [configured, setConfigured] = createSignal(true) + + testSetup = await testRender( + () => , + { width: 4, height: 2 }, + ) + expect({ fit: imageRef!.fit, protocol: imageRef!.protocol }).toEqual({ fit: "fill", protocol: "kitty" }) + + setConfigured(false) + + expect({ fit: imageRef!.fit, protocol: imageRef!.protocol }).toEqual({ fit: "fit", protocol: "auto" }) + }) + + it("cancels a pending image load when its component unmounts", async () => { + let imageRef: ImageRenderable | undefined + let streamController!: ReadableStreamDefaultController + let cancelled = false + let loads = 0 + let errors = 0 + const response = new Response( + new ReadableStream({ + start(controller) { + streamController = controller + }, + cancel() { + cancelled = true + }, + }), + ) + const [visible, setVisible] = createSignal(true) + + testSetup = await testRender( + () => ( + + loads++} onError={() => errors++} /> + + ), + { width: 4, height: 2 }, + ) + const image = imageRef! + const pending = image.loadPromise! + + setVisible(false) + if (!cancelled) { + streamController.enqueue(PNG_1X1) + streamController.close() + } + await pending + + expect(cancelled).toBe(true) + expect(loads).toBe(0) + expect(errors).toBe(0) + expect(image.image).toBeNull() + expect(image.loading).toBe(false) + }) +}) diff --git a/packages/web/src/content/SKILL.md b/packages/web/src/content/SKILL.md index 484b5aa208..9f29d2b675 100644 --- a/packages/web/src/content/SKILL.md +++ b/packages/web/src/content/SKILL.md @@ -1,6 +1,6 @@ --- name: opentui -description: Build terminal UIs with OpenTUI. Covers core, components, audio, keymaps, React, Solid, plugins, testing, standalone executables, QR encoding, SSH, and Three.js WebGPU. +description: Build terminal UIs with OpenTUI. Covers core, components, clipboard, native images, audio, keymaps, React, Solid, plugins, testing, standalone executables, QR encoding, SSH, and Three.js WebGPU. --- # OpenTUI Skill @@ -23,38 +23,43 @@ Inside the OpenTUI repo, this skill root lives at `packages/web/src/content/`, s - Keymap: `/docs/keymap/overview` - React: `/docs/bindings/react` - Solid: `/docs/bindings/solid` -- Components: `/docs/components/text`, `/docs/components/input` +- Components: `/docs/components/text`, `/docs/components/input`, `/docs/components/image` - Layout: `/docs/core-concepts/layout` - Keyboard: `/docs/core-concepts/keyboard` - Plugins: `/docs/plugins/slots` - Runtime and packaging: `/docs/reference/env-vars`, `/docs/reference/standalone-executables` - Package entrypoints: `/docs/reference/package-entrypoints` +- Clipboard: `/docs/reference/clipboard` +- Native images: `/docs/reference/native-image` - QR encoding: `/docs/reference/qr-encoder` - SSH: `/docs/reference/ssh` - Three.js WebGPU: `/docs/reference/three` ## Quick routing by intent -| Intent(s) | Start here | -| -------------------------------------------------------------------------------------------------- | ------------------------------------------- | -| `getting-started`, `installation`, `quickstart`, `intro` | `docs/getting-started.mdx` | -| `core`, `renderer`, `terminal`, `scrollback`, `lifecycle` | `docs/core-concepts/renderer.mdx` | -| `audio`, `native-audio`, `sound`, `playback`, `streaming`, `radio`, `mp3`, `flac`, `pcm`, `fft` | `docs/core-concepts/audio.mdx` | -| `keymap`, `keybindings`, `shortcuts`, `commands`, `leader`, `ex-commands` | `docs/keymap/overview.mdx` | -| `layout`, `flexbox`, `yoga`, `positioning` | `docs/core-concepts/layout.mdx` | -| `keyboard`, `input`, `keybindings`, `paste`, `focus` | `docs/core-concepts/keyboard.mdx` | -| `testing`, `test-renderer`, `snapshots`, `frames` | `docs/core-concepts/testing.mdx` | -| `react`, `jsx`, `hooks`, `keyboard`, `paste`, `focus`, `blur`, `selection`, `animation`, `testing` | `docs/bindings/react.mdx` | -| `solid`, `jsx`, `signals`, `hooks`, `keyboard`, `animation`, `testing` | `docs/bindings/solid.mdx` | -| `plugins`, `plugin`, `slots`, `registry`, `extensions` | `docs/plugins/slots.mdx` | -| `text`, `styling`, `content`, `selection` | `docs/components/text.mdx` | -| `input`, `form`, `editing`, `focus` | `docs/components/input.mdx` | -| `env`, `environment`, `configuration`, `flags` | `docs/reference/env-vars.mdx` | -| `standalone`, `executable`, `bun-compile`, `node-sea`, `node-assets` | `docs/reference/standalone-executables.mdx` | -| `package-exports`, `entrypoints`, `subpath-exports`, `imports` | `docs/reference/package-entrypoints.mdx` | -| `qr`, `qrcode`, `qr-encoder`, `svg-qr`, `gs1`, `eci`, `structured-append` | `docs/reference/qr-encoder.mdx` | -| `ssh`, `remote-tui`, `ssh-server`, `authentication`, `middleware` | `docs/reference/ssh.mdx` | -| `three`, `threejs`, `webgpu`, `3d`, `sprites`, `physics` | `docs/reference/three.mdx` | +| Intent(s) | Start here | +| ------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------- | +| `getting-started`, `installation`, `quickstart`, `intro` | `docs/getting-started.mdx` | +| `core`, `renderer`, `terminal`, `scrollback`, `lifecycle` | `docs/core-concepts/renderer.mdx` | +| `audio`, `native-audio`, `sound`, `playback`, `capture`, `microphone`, `streaming`, `radio`, `mp3`, `flac`, `pcm`, `fft` | `docs/core-concepts/audio.mdx` | +| `keymap`, `keybindings`, `shortcuts`, `commands`, `leader`, `ex-commands` | `docs/keymap/overview.mdx` | +| `layout`, `flexbox`, `yoga`, `positioning` | `docs/core-concepts/layout.mdx` | +| `keyboard`, `input`, `keybindings`, `paste`, `focus` | `docs/core-concepts/keyboard.mdx` | +| `testing`, `test-renderer`, `snapshots`, `frames` | `docs/core-concepts/testing.mdx` | +| `react`, `jsx`, `hooks`, `keyboard`, `paste`, `focus`, `blur`, `selection`, `animation`, `testing` | `docs/bindings/react.mdx` | +| `solid`, `jsx`, `signals`, `hooks`, `keyboard`, `animation`, `testing` | `docs/bindings/solid.mdx` | +| `plugins`, `plugin`, `slots`, `registry`, `extensions` | `docs/plugins/slots.mdx` | +| `text`, `styling`, `content`, `selection` | `docs/components/text.mdx` | +| `input`, `form`, `editing`, `focus` | `docs/components/input.mdx` | +| `image`, `image-renderable`, `image-display`, `kitty`, `sixel` | `docs/components/image.mdx` | +| `env`, `environment`, `configuration`, `flags` | `docs/reference/env-vars.mdx` | +| `standalone`, `executable`, `bun-compile`, `node-sea`, `node-assets` | `docs/reference/standalone-executables.mdx` | +| `package-exports`, `entrypoints`, `subpath-exports`, `imports` | `docs/reference/package-entrypoints.mdx` | +| `clipboard`, `copy`, `paste`, `osc52`, `primary-selection`, `remote-clipboard` | `docs/reference/clipboard.mdx` | +| `native-image`, `image-decode`, `png`, `jpeg`, `webp`, `gif`, `rgba`, `pixels`, `resize` | `docs/reference/native-image.mdx` | +| `qr`, `qrcode`, `qr-encoder`, `svg-qr`, `gs1`, `eci`, `structured-append` | `docs/reference/qr-encoder.mdx` | +| `ssh`, `remote-tui`, `ssh-server`, `authentication`, `middleware` | `docs/reference/ssh.mdx` | +| `three`, `threejs`, `webgpu`, `3d`, `sprites`, `physics` | `docs/reference/three.mdx` | For concrete component requests, jump straight to `docs/components/.mdx` after the relevant entry page. For plugin implementation details, narrow from `docs/plugins/slots.mdx` into `docs/plugins/core.mdx`, `docs/plugins/react.mdx`, or `docs/plugins/solid.mdx`. @@ -72,9 +77,12 @@ For concrete component requests, jump straight to `docs/components/.mdx` a - `docs/plugins/slots.mdx` - `docs/components/text.mdx` - `docs/components/input.mdx` +- `docs/components/image.mdx` - `docs/reference/env-vars.mdx` - `docs/reference/standalone-executables.mdx` - `docs/reference/package-entrypoints.mdx` +- `docs/reference/clipboard.mdx` +- `docs/reference/native-image.mdx` - `docs/reference/qr-encoder.mdx` - `docs/reference/ssh.mdx` - `docs/reference/three.mdx` diff --git a/packages/web/src/content/docs/bindings/react.mdx b/packages/web/src/content/docs/bindings/react.mdx index 15d3568ed0..e583c0811f 100644 --- a/packages/web/src/content/docs/bindings/react.mdx +++ b/packages/web/src/content/docs/bindings/react.mdx @@ -79,6 +79,7 @@ OpenTUI React provides JSX intrinsic elements that map to core renderables: - `` - Scrollable container - `` - ASCII art text - [``](/docs/components/time-to-first-draw) - Exported first-draw performance timestamp component +- [``](/docs/components/image) - Native image rendering QR code support is available from `@opentui/qrcode/react` and must be registered explicitly with `registerQRCode()`. diff --git a/packages/web/src/content/docs/bindings/solid.mdx b/packages/web/src/content/docs/bindings/solid.mdx index 3153b2c2a1..d62b2a911a 100644 --- a/packages/web/src/content/docs/bindings/solid.mdx +++ b/packages/web/src/content/docs/bindings/solid.mdx @@ -118,6 +118,7 @@ OpenTUI Solid provides JSX intrinsic elements that map to core renderables. - `` - ASCII art text - `` - Render Markdown content - [``](/docs/components/time-to-first-draw) - Exported first-draw performance timestamp component +- [``](/docs/components/image) - Native image rendering QR code support is available from `@opentui/qrcode/solid` and must be registered explicitly with `registerQRCode()`. diff --git a/packages/web/src/content/docs/components/frame-buffer.mdx b/packages/web/src/content/docs/components/frame-buffer.mdx index f9301f3ac6..10f68bb149 100644 --- a/packages/web/src/content/docs/components/frame-buffer.mdx +++ b/packages/web/src/content/docs/components/frame-buffer.mdx @@ -126,6 +126,29 @@ canvas.frameBuffer.drawFrameBuffer( ) ``` +### drawImage + +Place a native image into destination cells: + +```typescript +const placed = canvas.frameBuffer.drawImage( + image, + x, + y, + width, + height, + pixelWidth, + pixelHeight, + sourceX, + sourceY, + sourceWidth, + sourceHeight, + protocol, +) +``` + +Arguments after `height` are optional. Source arguments crop the image. Sixel requires nonzero pixel dimensions or falls back to blocks. `false` means no visible valid placement was recorded. A successful placement retains the native image until the buffer is cleared or destroyed. + ### colorMatrix / colorMatrixUniform Apply native 4x4 RGBA matrix transforms for post-processing effects. Use `colorMatrixUniform` for full-buffer diff --git a/packages/web/src/content/docs/components/image.mdx b/packages/web/src/content/docs/components/image.mdx new file mode 100644 index 0000000000..27cbf9ba3e --- /dev/null +++ b/packages/web/src/content/docs/components/image.mdx @@ -0,0 +1,111 @@ +--- +title: Image +description: Load and render images with Kitty, Sixel, or Unicode blocks +order: 19 +skill: + entry: true + intents: [image, image-renderable, image-display, kitty, sixel] +--- + +# Image + +`ImageRenderable` loads PNG, JPEG, WebP, GIF, or encoded bytes into a native image and renders it with Kitty graphics, Sixel, or a Unicode block fallback. + +## Renderable API + +```typescript +import { ImageRenderable, createCliRenderer } from "@opentui/core" + +const renderer = await createCliRenderer() +const image = new ImageRenderable(renderer, { + id: "cover", + source: "./cover.webp", + width: 40, + height: 15, + fit: "cover", + protocol: "auto", + onError: console.error, +}) + +renderer.root.add(image) +await image.loadPromise +``` + +`source` accepts a path, `file:`/HTTP(S)/`blob:`/`data:` URL, `URL`, `Blob`, `Response`, `Uint8Array`, or `ArrayBuffer`. Format detection uses the encoded bytes. See [Native images](/docs/reference/native-image) for formats, decoding, pixel access, and limits. + +## React + +```tsx +import { createCliRenderer } from "@opentui/core" +import { createRoot } from "@opentui/react" + +const renderer = await createCliRenderer() +createRoot(renderer).render( + , +) +``` + +## Solid + +```tsx +import { createCliRenderer } from "@opentui/core" +import { render } from "@opentui/solid" + +const renderer = await createCliRenderer() +await render( + () => , + renderer, +) +``` + +The `source`, callbacks, fit, and protocol props can be updated after construction. Replacing `source` keeps the current image visible until the replacement succeeds, cancels obsolete loading, and disposes stale native images. Setting `source` to `undefined` clears it; clearing `fit` or `protocol` restores `"fit"` or `"auto"`. + +## Sizing + +| `fit` | Behavior | +| ------- | ------------------------------------------------------------ | +| `fit` | Contain and center the full image; preserve aspect (default) | +| `cover` | Fill the renderable, preserve aspect ratio, and center-crop | +| `fill` | Fill the renderable and allow stretching | + +Sizing uses terminal pixel resolution when available and a 2:1 cell-height fallback otherwise. + +## Rendering protocol + +| `protocol` | Behavior | +| ---------- | ------------------------------------------------------------------- | +| `auto` | Global override, then Kitty, then Sixel, then Unicode blocks | +| `kitty` | Force Kitty graphics | +| `sixel` | Force Sixel; falls back to blocks without terminal pixel resolution | +| `blocks` | Portable Unicode quadrant-block rendering | + +With global and per-image protocols set to `auto`, tmux uses blocks. Explicit Kitty, or Sixel with pixel resolution, uses tmux passthrough. + +Overlapping images must use the same effective protocol. Layering and alpha composition across different effective protocols are not supported; leave overlapping images on `auto` or give them the same explicit `protocol`. Non-overlapping images may use different protocols. + +Kitty preserves image alpha, Sixel treats alpha below 128 as transparent, and blocks blend sampled alpha. Placement opacity scales Kitty and block alpha; Sixel dims toward cell backgrounds. Direct, unbuffered fills, text, and box borders cover images at whole-cell granularity without blending. + +Use `OPENTUI_IMAGE_PROTOCOL=auto|kitty|sixel|blocks` to set the global default. `OPENTUI_GRAPHICS=false` disables Kitty and Sixel detection. See [Environment variables](/docs/reference/env-vars). + +Split-footer scrollback snapshots follow the same protocol resolution as live images: Kitty placement, Sixel with detected pixel geometry, or Unicode quadrant blocks. Snapshots containing mixed effective protocols, overlapping images, or covered Sixel image cells use blocks. Native scrollback placement starts once the footer is pinned and the image rectangle is addressable. Await `loadPromise` before rendering an image into a `ScrollbackSurface`; see [Writing to scrollback](/docs/core-concepts/renderer#writing-to-scrollback). + +`resolveImageRenderProtocol(requested, capabilities, hasResolution)` exposes the same protocol-resolution policy for code that needs it without constructing a renderable. + +## Options and state + +| Member | Type | Description | +| -------------------- | ----------------------------------------------------------- | --------------------------------------------------------------- | +| `source` | `ImageSource` | Image source; optional | +| `fit` | `"fit" \| "cover" \| "fill"` | Destination sizing | +| `protocol` | `"auto" \| "kitty" \| "sixel" \| "blocks"` | Requested rendering protocol | +| `onLoad` | `(image: NativeImage) => void` | Called after the current source loads | +| `onError` | `(error: unknown) => void` | Called when the current source fails | +| `image` | `NativeImage \| null` | Currently displayed renderable-owned image | +| `loading` | `boolean` | Whether the current source is loading | +| `loadError` | `unknown` | Current load error, otherwise `null` | +| `loadPromise` | `Promise \| null` | Settles after the current load attempt | +| `effectiveProtocol` | `kitty \| sixel \| blocks` | Resolved protocol | +| `cellAspectRatio` | `number` | Physical or fallback cell aspect ratio | +| `getFittedSize(...)` | `(width, height, cellAspect?, sourceWidth?, sourceHeight?)` | Resolve destination cells; omitted overrides use current values | + +The renderable owns `image` and the `NativeImage` passed to `onLoad`; do not dispose or transfer them. Current-source failures set `loadError`, call `onError`, and resolve `loadPromise`. Superseded, cleared, or destroyed loads resolve without callbacks. Exceptions from either callback reject after state settles. Source replacement, clearing, and destruction release owned images. diff --git a/packages/web/src/content/docs/core-concepts/renderer.mdx b/packages/web/src/content/docs/core-concepts/renderer.mdx index 2110ae3441..8ac66f9b37 100644 --- a/packages/web/src/content/docs/core-concepts/renderer.mdx +++ b/packages/web/src/content/docs/core-concepts/renderer.mdx @@ -177,6 +177,8 @@ In split-footer mode with `externalOutputMode: "capture-stdout"`, the renderer a Both APIs require `screenMode: "split-footer"` and `externalOutputMode: "capture-stdout"`. They throw otherwise. +Scrollback snapshots use the same image protocol resolution as live `ImageRenderable` instances. Kitty uses conventional placement, Sixel uses the renderable's detected pixel geometry, and blocks use Unicode quadrant cells. Native scrollback placement starts once the footer is pinned and the image rectangle is addressable; unsupported snapshot layouts use the normal block fallback. + ### `renderer.writeToScrollback(writer)` Render a renderable tree into an off-screen buffer and commit it as one scrollback snapshot. @@ -264,7 +266,9 @@ surface.destroy() | `commitRows(start, endExclusive, options?)` | Copy a row range out of the backing buffer and enqueue it as a scrollback commit | | `destroy()` | Tear down the surface and its backing buffer | -`commitRows` throws if you call it before `render()`, or if the renderer's width or `widthMethod` changed since the last `render()`. Re-render before committing fresh rows in either case. +`commitRows` throws if you call it before `render()`, or if the renderer's dimensions, pixel resolution, or `widthMethod` changed since the last `render()`. Re-render before committing fresh rows in either case. + +For an `ImageRenderable`, await `image.loadPromise`, then call `surface.render()` and `surface.commitRows(...)`. `surface.settle()` waits for syntax highlighting, not image loading. The queued snapshot retains the image until native output publication, so the surface can be destroyed immediately after the commit. For Solid, use the binding-level helpers that wrap `writeToScrollback` with JSX support. See [`createScrollbackWriter` / `writeSolidToScrollback`](/docs/bindings/solid#scrollback-writers). The React binding does not currently provide an equivalent JSX scrollback helper. @@ -497,6 +501,8 @@ A `true` result means the renderer output path was invoked; it does not prove that every output backend accepted the bytes. OSC 52 also does not acknowledge terminal acceptance or a clipboard change. +For native host reads and composed host/terminal write policy, see the [Clipboard reference](/docs/reference/clipboard). + ### Notifications Trigger a terminal-mediated desktop notification: diff --git a/packages/web/src/content/docs/reference/clipboard.mdx b/packages/web/src/content/docs/reference/clipboard.mdx new file mode 100644 index 0000000000..0feec63de6 --- /dev/null +++ b/packages/web/src/content/docs/reference/clipboard.mdx @@ -0,0 +1,131 @@ +--- +title: Clipboard +description: Read the host clipboard and compose native writes with terminal OSC 52 +order: 11 +skill: + entry: true + intents: [clipboard, copy, paste, osc52, primary-selection, remote-clipboard] +--- + +# Clipboard + +OpenTUI separates the process host clipboard from the terminal user's clipboard. Host reads and writes use native platform backends. Terminal writes use OSC 52. Terminal paste events are input events and do not perform a host clipboard read. + +## Read text or PNG + +```typescript +import { NativeImage, createClipboard, createHostClipboard, createRendererClipboardAdapter } from "@opentui/core" + +const host = createHostClipboard() +const clipboard = createClipboard({ + host, + terminal: createRendererClipboardAdapter(renderer), +}) + +try { + const result = await clipboard.read({ + preferredTypes: ["image/png", "text/plain"], + }) + + if (result.status === "read") { + const { mimeType, bytes } = result.representation + + if (mimeType === "text/plain") { + console.log(new TextDecoder().decode(bytes)) + } + + if (mimeType === "image/png") { + const image = NativeImage.decode(bytes) + try { + console.log(image.info()) + } finally { + image.dispose() + } + } + } +} finally { + await clipboard.dispose() +} +``` + +`preferredTypes` is ordered. Native host backends currently return `text/plain` as UTF-8 or `image/png` as encoded PNG. Windows DIB/DIBV5, macOS TIFF, and the WSLg Wayland BMP fallback are converted to PNG when needed. + +Every successful representation has a canonical lowercase MIME essence without parameters and a stable, caller-owned `Uint8Array`. The bytes remain valid after the operation and service are disposed. They do not alias native memory. + +PNG bytes are encoded transport data, not decoded RGBA pixels. Direct platform PNG data may not have been decoded or semantically validated. `NativeImage.decode(bytes)` creates an independent native image, can fail separately for malformed data, color profiles, dimensions, or memory limits, and must be disposed. Callers that only need a file, upload, or Base64 data URL should keep the encoded bytes instead of decoding them. + +Reads return one of: + +- `read` with a representation +- `empty`, `unsupported`, `cancelled`, `timed-out`, or `limit-exceeded` +- `failed` with an `Error` + +Zero-byte text is a successful text representation. Empty or stale image transfers are not returned as valid images. + +## Host and terminal destinations + +`createClipboard()` combines a `HostClipboardService` with a terminal adapter. Reads always use the host. Writes and clears require a destination policy: + +| Destination | Behavior | +| ---------------- | --------------------------------------------------------------------------------------------- | +| `host-only` | Use only the process host clipboard | +| `terminal-only` | Emit only terminal OSC 52 | +| `best-available` | Prefer the local host and use eligible terminal fallback after host `unsupported` or `failed` | +| `all-available` | Attempt eligible host and terminal destinations | + +```typescript +const result = await clipboard.writeText("copied text", { + destination: "all-available", + selection: "clipboard", +}) +``` + +Host results distinguish `written`, `unsupported`, `cancelled`, `timed-out`, and `failed`. Terminal status `attempted` confirms only that OpenTUI emitted the OSC 52 sequence locally. OSC 52 does not acknowledge terminal acceptance or a clipboard change. + +`writeText()` rejects empty text and NUL characters. Use `clear()` for an explicit clear operation: + +```typescript +await clipboard.clear({ + destination: "all-available", + selection: "clipboard", +}) +``` + +The standard `clipboard` selection is portable. The `primary` selection is supported where the native platform provides it, principally Linux Wayland/X11; unsupported platforms return `unsupported`. + +## Remote sessions + +Over SSH or mosh, a host read refers to the machine running the OpenTUI process, not the local terminal user's clipboard. OSC 52 writes travel through terminal output toward the user's terminal. + +Composed host writes are suppressed by default when the terminal adapter reports a remote session. Pass `allowRemoteHost: true` only when mutating the remote machine's host clipboard is intentional. Terminal bracketed paste remains a separate input path and can still carry text from the user's local terminal. + +There is no portable OSC 52 clipboard-read operation in this API and no host image-write API. + +## Ownership and disposal + +`createClipboard({ host, terminal })` takes ownership of `host`. Dispose only the composed service. Disposal aborts active operations, waits for their cleanup, shuts down native workers and providers, and then destroys the host service. + +Native Linux writes may make the process the Wayland or X11 clipboard owner. Keep the service alive while it must continue serving those bytes. Disposing it can make process-owned clipboard contents unavailable unless a clipboard manager has retained them. + +`dispose()` is asynchronous and idempotent. Await it before destroying the renderer or unloading the native library. + +## Bounds + +`createHostClipboard()` bounds untrusted clipboard work. Defaults are: + +| Option | Default | Scope | +| ------------------------- | ---------: | ----------------------------------------------------- | +| `timeoutMs` | 1,000 ms | Each host operation | +| `maxReadBytes` | 8 MiB | Returned encoded representation | +| `maxWriteBytes` | 8 MiB | UTF-8 text write | +| `maxImagePixels` | 67,108,864 | Pixels inspected by DIB/BMP/TIFF conversion fallbacks | +| `maxConversionBytes` | 512 MiB | Temporary decoded storage for conversion fallbacks | +| `maxConcurrentOperations` | 16 | Active native operations | +| `maxProviderTransfers` | 16 | Concurrent Linux provider transfers | +| `maxWorkUnitsPerDrain` | 64 | Native work processed per scheduler drain | + +`maxImagePixels` and `maxConversionBytes` do not decode or inspect a direct PNG transfer. A direct PNG is bounded by `maxReadBytes`; a later `NativeImage.decode()` applies its own independent image limits. + +`waylandSeat` selects an explicit Wayland seat. A process started with only an inherited `WAYLAND_SOCKET` can create one host service from that one-shot descriptor. + +For low-level renderer OSC 52 methods and target values, see [Renderer lifecycle and control](/docs/core-concepts/renderer#osc-52-clipboard). diff --git a/packages/web/src/content/docs/reference/env-vars.mdx b/packages/web/src/content/docs/reference/env-vars.mdx index 3f6b37a9f8..14ff92d9ff 100644 --- a/packages/web/src/content/docs/reference/env-vars.mdx +++ b/packages/web/src/content/docs/reference/env-vars.mdx @@ -26,7 +26,8 @@ OpenTUI reads environment variables from `process.env`. Bun loads `.env` automat | `OTUI_TRACE_FFI` | `boolean` | `false` | Enable tracing for the FFI bindings | | `OPENTUI_FORCE_WCWIDTH` | presence | unset | Use wcwidth for character width calculations | | `OPENTUI_FORCE_UNICODE` | presence | unset | Force Mode 2026 Unicode support in terminal capabilities | -| `OPENTUI_GRAPHICS` | `string` | auto | Override Kitty graphics protocol detection | +| `OPENTUI_GRAPHICS` | `string` | auto | Control Kitty and Sixel graphics detection | +| `OPENTUI_IMAGE_PROTOCOL` | `string` | `auto` | Default image protocol (`auto`, `kitty`, `sixel`, `blocks`) | | `OPENTUI_FORCE_NOZWJ` | presence | unset | Use no_zwj width method (Unicode without ZWJ joining) | | `OPENTUI_LIBC` | `string` | unset | Select Linux native libc package (`glibc`, `musl`) | | `OPENTUI_FORCE_EXPLICIT_WIDTH` | `string` | - | Force explicit width detection (`true`/`1` or `false`/`0`) | @@ -45,7 +46,7 @@ OpenTUI reads environment variables from `process.env`. Bun loads `.env` automat - `OTUI_TS_STYLE_WARN` is a presence-like string setting: any explicit nonempty value, including `false`, enables warnings. - `OPENTUI_FORCE_WCWIDTH`, `OPENTUI_FORCE_UNICODE`, and `OPENTUI_FORCE_NOZWJ` are native presence flags. Any value, including `0` or `false`, enables the corresponding override. Leave them unset to disable them. -- `OPENTUI_GRAPHICS` recognizes the exact lowercase values `false`/`0` and `true`/`1`; other values leave automatic behavior unchanged. +- `OPENTUI_GRAPHICS` recognizes only lowercase `false`/`0` (disable graphics detection) and `true`/`1` (automatic detection); other values leave automatic behavior unchanged. `OPENTUI_IMAGE_PROTOCOL` overrides automatic selection; forcing an unsupported protocol can produce incorrect output. - `OPENTUI_FORCE_EXPLICIT_WIDTH=false` skips OSC 66 queries on older terminals. - Linux uses the glibc native package by default. Set `OPENTUI_LIBC=musl` before importing OpenTUI, or define `process.env.OPENTUI_LIBC` as `"musl"` at standalone build time, to use the musl native package. See [Standalone Executables](/docs/reference/standalone-executables). - `OPENTUI_NOTIFICATION_PROTOCOL=none` disables notifications. Protocol overrides should only be used when terminal detection cannot identify a supported notification protocol. diff --git a/packages/web/src/content/docs/reference/native-image.mdx b/packages/web/src/content/docs/reference/native-image.mdx new file mode 100644 index 0000000000..b4ffdae2cc --- /dev/null +++ b/packages/web/src/content/docs/reference/native-image.mdx @@ -0,0 +1,131 @@ +--- +title: Native images +description: Decode and manipulate PNG, JPEG, WebP, GIF, and RGBA pixels +order: 10 +skill: + entry: true + intents: [native-image, image-decode, png, jpeg, webp, gif, rgba, pixels, resize] +--- + +# Native images + +`NativeImage` decodes and manipulates images through OpenTUI's native library. Every decoded image uses top-left, straight-alpha, sRGB RGBA8 pixels. + +## Decode WebP to RGBA + +```typescript +import { NativeImage } from "@opentui/core" + +const image = await NativeImage.load("./image.webp") + +try { + const { data, width, height, stride } = image.raw("rgba8") + console.log({ data, width, height, stride }) +} finally { + image.dispose() +} +``` + +`data` is a `Uint8Array` in row-major RGBA order. Use `raw("bgra8")` for BGRA. The returned `RawImage` also contains `width`, `height`, `stride`, `format`, `colorSpace: "srgb"`, and `alpha: "straight"`. + +## Inputs and formats + +| API | Input | +| --------------------------- | ---------------------------------------------------------------------------------------------------- | +| `NativeImage.load(source)` | Path, `file:`/HTTP(S)/`blob:`/`data:` URL, `URL`, `Blob`, `Response`, `Uint8Array`, or `ArrayBuffer` | +| `NativeImage.decode(data)` | Encoded `Uint8Array` or `ArrayBuffer` | +| `NativeImage.fromRgba(...)` | Straight-alpha sRGB RGBA8 pixels, dimensions, and optional row stride | +| `imageInfo(data)` | Encoded `Uint8Array` or `ArrayBuffer`; returns metadata without retaining an image handle | + +Format detection uses encoded bytes, not names, URL suffixes, response headers, or file extensions. +`imageInfo()` performs a full temporary decode, then releases its native storage. `decode()` and `fromRgba()` copy their inputs and do not retain caller buffers. + +| Format | Behavior | +| ------ | -------------------------------------------------------------------- | +| PNG | Decodes alpha and supported sRGB metadata; applies EXIF orientation | +| JPEG | Decodes opaque RGBA8; applies EXIF orientation | +| WebP | Decodes lossy, lossless, and alpha images; animated WebP is rejected | +| GIF | Decodes the first displayed frame on the logical canvas | + +`load()` accepts `{ signal, fetch }`. `signal` cancels source acquisition; `fetch` replaces `globalThis.fetch` for fetched URLs. Paths, blobs, and response bodies are buffered before native decoding. + +## Metadata and pixels + +`image.info()` returns: + +| Field | Description | +| ----------------------------- | ------------------------------------------------------------------------------------------------ | +| `width`, `height` | Decoded, orientation-corrected dimensions | +| `sourceWidth`, `sourceHeight` | Original input dimensions; before orientation for encoded inputs and preserved by derived images | +| `format` | `png`, `jpeg`, `webp`, `gif`, or `raw-rgba` | +| `colorStatus` | `explicit-srgb` or `assumed-srgb` | +| `orientation` | `1` after decode; `imageInfo()` reports encoded orientation | +| `hasAlpha` | Whether decoded pixels contain transparency | + +| Method | Result | +| ------------------------------- | ---------------------------------------------------------------- | +| `raw(format?)` | Allocates and returns RGBA8 or BGRA8 pixels and image metadata | +| `takeRaw()` | Transfers ownership of the native RGBA8 pixels without copying | +| `copyTo(destination, options?)` | Copies pixels into an existing `Uint8Array` | +| `width`, `height` | Decoded dimensions | +| `ptr` | Opaque native `ImageHandle`, valid until disposal or `takeRaw()` | + +`copyTo()` defaults to RGBA8 with stride `width * 4`; BGRA8 and custom strides are supported. A custom stride must fit one row, and the destination must fit every row. + +`takeRaw()` consumes an exclusively owned `NativeImage`; it throws while a native render buffer retains the image. Later accessors and operations throw, while `dispose()` remains a safe no-op. The returned `OwnedRawImage.data` directly views native memory. Dispose it only after every consumer has finished, because explicit disposal frees the native allocation and invalidates the view: + +```typescript +const image = await NativeImage.load(new Blob([encodedImage])) +const raw = image.takeRaw() + +try { + consumeRgba(raw.data, raw.width, raw.height, raw.stride) +} finally { + raw.dispose() +} +``` + +`OwnedRawImage.dispose()` is idempotent and required. Keep the owner alive for as long as any consumer uses `data`. + +## Operations + +Operations are immutable: each returns a new `NativeImage`; the source remains unchanged. + +| Method | Description | +| --------------------------------------- | ---------------------------------------------------- | +| `clone()` | Copy the image | +| `resize({ width?, height?, kernel? })` | Resize; one omitted dimension preserves aspect ratio | +| `extract({ left, top, width, height })` | Crop a rectangle | +| `extend(options?)` | Add RGBA padding | +| `rotate(90 \| 180 \| 270)` | Rotate clockwise | +| `flip()` / `flop()` | Flip vertically / horizontally | +| `composite(overlay, options?)` | Composite in linear light | + +Resize kernels are `area` (the default), `default`, `triangle`, `cubic-bspline`, `catmull-rom`, `mitchell`, and `nearest`. Blend modes are `source-over` (the default), `source`, and `destination-over`; opacity is `0..1`. +`extend()` defaults omitted sides to zero and its background to transparent RGBA. `composite()` defaults to offset `(0, 0)`, `source-over`, and opacity `1`; negative offsets are clipped to the base image. + +Dispose every image you own, including operation results: + +```typescript +const source = await NativeImage.load("photo.jpg") +const thumbnail = source.resize({ width: 320 }) + +try { + console.log(thumbnail.raw().data) +} finally { + thumbnail.dispose() + source.dispose() +} +``` + +`dispose()` is idempotent. Other methods throw after disposal. + +## Errors and limits + +`ImageLoadError` exposes `code`, `source`, and optional HTTP `status`; codes are `file-read`, `network`, `http-status`, and `unsupported-url-scheme`. Aborts rethrow `AbortSignal.reason`. + +`ImageError` exposes numeric `status` and `code`: `invalid-handle`, `unsupported-format`, `unsupported-color-space`, `malformed-data`, `dimension-limit`, `memory-limit`, `invalid-argument`, `out-of-memory`, `output-too-small`, `internal-error`, or `unsupported-feature`. JavaScript argument validation uses `TypeError` and `RangeError`. + +Encoded input is limited to 64 MiB. Images and operation outputs are limited to 16,384 pixels per axis, 25 million pixels, and 100 MiB decoded storage. Supported sRGB `cICP` takes precedence over other PNG color chunks. Otherwise `iCCP` and non-sRGB `gAMA`/`cHRM` are rejected; unsupported `cICP` alone is assumed sRGB. + +To display an image, see [Image](/docs/components/image).