Skip to content

Add AVIF image support via libavif#289

Merged
RyeMutt merged 3 commits into
developfrom
feature/avif-support
Jun 2, 2026
Merged

Add AVIF image support via libavif#289
RyeMutt merged 3 commits into
developfrom
feature/avif-support

Conversation

@RyeMutt
Copy link
Copy Markdown
Member

@RyeMutt RyeMutt commented Jun 1, 2026

Description

Adds support for the AVIF (AV1 Image File Format) image format, letting users upload .avif images as texture sources and save snapshots/textures as .avif. AVIF offers substantially better compression than JPEG/PNG/WebP at equivalent quality, with alpha support.

A new LLImageAVIF codec is added to llimage, backed by libavif via vcpkg using dav1d (decode) and aom (encode). It mirrors the existing WebP integration point-for-point. Encoding is lossy with a quality control (libavif's quality API, like LLImageJPEG), since lossy is where AVIF's size advantage lies.

Core (llimage)

  • New IMG_CODEC_AVIF + LLImageAVIF: dav1d decode to 8-bit RGB/RGBA (with vertical flip for SL's OpenGL convention), aom lossy/quality encode (sRGB CICP, YUV420)
  • Extension table, MIME type (image/avif), and LLImageDimensionsInfo registration

Build

  • libavif vcpkg dependency with aom + dav1d features; new indra/cmake/AVIF.cmake. Resolves from the existing pinned vcpkg baseline (libavif 1.4.1) — no custom port needed.

Upload / load paths

  • File-picker load filters (Windows / SDL / macOS), IMAGE_EXTENSIONS, and local-bitmap loading. Factory-driven paths (upload preview, createUploadFile, bulk upload) work automatically via codec registration.

Save / snapshot paths

  • FFSAVE_AVIF save filters (all platforms), SNAPSHOT_FORMAT_AVIF, snapshot format combo box + encode switches + quality-slider visibility, viewer-window save dispatch, and texture "Save As".

Related Issues

No tracking issue yet — happy to link one if desired.

Issue Link: N/A


Checklist

Please ensure the following before requesting review:

  • I have provided a clear title and detailed description for this pull request.
  • If useful, I have included media such as screenshots and video to show off my changes.
  • I have tested the changes locally and verified they work as intended.
  • All new and existing tests pass.
  • Code follows the project's style guidelines.
  • Documentation has been updated if needed.
  • Any dependent changes have been merged and published in downstream modules.
  • I have reviewed the contributing guidelines.

Additional Notes

Verification: Full viewer builds clean (cmake --preset vs2026-os--build --config Release, exit 0). vcpkg built libavif[aom,core,dav1d]@1.4.1; llimageavif.cpp compiles against the real libavif headers; all changed newview TUs recompile and AlchemyTest.exe links and packages. All pre-commit hooks pass.

Design decisions:

  • dav1d (decode) + aom (encode) — both first-class vcpkg libavif features.
  • Lossy + quality encode (quality slider now shows for AVIF, mirroring JPEG).
  • Mirrors WebP exactly: leaves MATERIAL_TEXTURES_FILTER, the GLTF-import filter, and the FFLOAD_ALL/EXE "Images" hint untouched (WebP isn't in them either); localized snapshot XUI needs no edits (name-merge surfaces the en AVIF combo item with its acronym label).

Known limitation: HDR / >8-bit / wide-gamut AVIFs are down-converted to 8-bit sRGB on decode (LLImageRaw is 8-bit).

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Jun 1, 2026

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 75977b98-2308-45a0-87e4-6adffb33e0b0

📥 Commits

Reviewing files that changed from the base of the PR and between db70447 and fa2a412.

📒 Files selected for processing (1)
  • indra/llimage/llimageavif.cpp
🚧 Files skipped from review as they are similar to previous changes (1)
  • indra/llimage/llimageavif.cpp

📝 Walkthrough

Summary by CodeRabbit

  • New Features

    • AVIF image format supported for snapshots (lossy and lossless) with quality controls and size estimation
    • AVIF available in save dialogs, file pickers, preview/save flows, and drag-and-drop uploads
    • Local bitmaps, texture handling, and image dimension probing now accept AVIF files
  • Chores

    • Build/tooling updated to enable AVIF encoding/decoding support and install required native tooling on CI builders

Walkthrough

Adds end-to-end AVIF support: CI/CMake/vcpkg updates, llimage AVIF codec (encode/decode/update), dimension extraction, snapshot/live-preview and UI wiring, file picker filters, and viewer upload/save integration.

Changes

AVIF Image Format Support

Layer / File(s) Summary
Build dependencies and CMake configuration
.github/workflows/build.yaml, indra/CMakeLists.txt, indra/cmake/AVIF.cmake, indra/cmake/CMakeLists.txt, indra/vcpkg.json
Add nasm to CI install lists; add AVIF.cmake declaring ll::libavif via find_package(libavif CONFIG REQUIRED); include AVIF.cmake in the cmake source list; add libavif dependency with aom and dav1d features to vcpkg.json.
Core AVIF codec implementation
indra/llimage/llimage.h, indra/llimage/llimage.cpp, indra/llimage/llimageavif.h, indra/llimage/llimageavif.cpp, indra/llimage/CMakeLists.txt
Introduce IMG_CODEC_AVIF, implement LLImageAVIF (constructor, updateData, decode, encode) using libavif with YUV↔RGB conversion and vertical row flipping; register avif file-extension and image/avif MIME handling; add llimageavif sources/headers and link llimage to ll::libavif.
Image dimension metadata extraction
indra/llimage/llimagedimensionsinfo.h, indra/llimage/llimagedimensionsinfo.cpp
Add getImageDimensionsAVIF() that parses AVIF container metadata via libavif to obtain width/height without full decode; route IMG_CODEC_AVIF through load().
Snapshot format and live preview integration
indra/newview/llsnapshotmodel.h, indra/newview/llsnapshotlivepreview.cpp, indra/newview/llpanelsnapshotlocal.cpp
Add SNAPSHOT_FORMAT_AVIF and SNAPSHOT_FORMAT_AVIF_LOSSLESS; extend estimateDataSize() and getFormattedImage() to support AVIF; map UI combo to AVIF formats and show quality controls for AVIF.
File picker and UI localization
indra/newview/llfilepicker.h, indra/newview/llfilepicker.cpp, indra/newview/skins/default/xui/en/panel_snapshot_local.xml, indra/newview/skins/default/xui/en/strings.xml
Add FFSAVE_AVIF save filter enum; extend Windows/macOS/SDL open/save dialogs with *.avif filters and save-case handling; add AVIF combo item and localized AVIF strings.
Viewer upload and download paths
indra/newview/lllocalbitmaps.h, indra/newview/lllocalbitmaps.cpp, indra/newview/llpreviewtexture.cpp, indra/newview/llviewermenufile.cpp, indra/newview/llviewerwindow.cpp
Wire AVIF into local bitmap extension mapping and decode flow; extend preview save-as and File menu snapshot export to create LLImageAVIF; add .avif drag-and-drop mapping and snapshot save-as extension mapping to the AVIF save filter.

Sequence Diagram(s)

sequenceDiagram
  participant UI as Snapshot/UI
  participant Factory as LLImageFormatted Factory
  participant LLImageAVIF
  participant LibAVIF
  participant Storage as File/Buffer

  UI->>Factory: request formatted image (AVIF)
  Factory->>LLImageAVIF: createFromType(IMG_CODEC_AVIF)
  LLImageAVIF->>LibAVIF: avifDecoderCreate / avifPeekCompatibleFileType
  LibAVIF-->>LLImageAVIF: metadata (width,height,alpha)
  UI->>LLImageAVIF: decode() / encode()
  LLImageAVIF->>LibAVIF: avifDecoderNextImage / avifImageYUVToRGB / avifImageRGBToYUV / avifEncoderWrite
  LibAVIF-->>LLImageAVIF: image frames / bitstream
  LLImageAVIF->>LLImageAVIF: vertical flip (in-place)
  LLImageAVIF->>Storage: store encoded bitstream / fill raw buffer
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

🐰 A codec hops in, AVIF so bright,
pixels flip and colors take flight.
From build to save, the viewer now sings,
snapshots and files with lighter wings. 🎨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 43.75% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely describes the main change: adding AVIF image format support via the libavif library, which is the primary objective of this PR.
Description check ✅ Passed The description is comprehensive and well-structured, covering the feature motivation, technical implementation details, affected components, build changes, and integration points across upload/load/save paths. It includes detailed checklist items and additional verification/design notes.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 Infer (1.2.0)
indra/llimage/llimageavif.cpp

indra/llimage/llimageavif.cpp:26:10: fatal error: 'linden_common.h' file not found
26 | #include "linden_common.h"
| ^~~~~~~~~~~~~~~~~
1 error generated.
Error: the following clang command did not run successfully:
/opt/infer-linux-x86_64-v1.2.0/lib/infer/facebook-clang-plugins/clang/install/bin/clang-18
@/tmp/coderabbit-infer/fa2a412e5d1f846c44cb0d33bc64fb9eec49cc45-7715a4c0411fba9c/tmp/clang_command_.tmp.a570f0.txt
++Contents of '/tmp/coderabbit-infer/fa2a412e5d1f846c44cb0d33bc64fb9eec49cc45-7715a4c0411fba9c/tmp/clang_command_.tmp.a570f0.txt':
"-cc1" "-load"
"/opt/infer-linux-x86_64-v1.2.0/lib/infer/infer/bin/../../facebook-clang-plugins/libtooling/build/FacebookClangPlugin.dylib"
"-add-plugin" "BiniouASTExporter" "-plugin-arg-BiniouASTExporter" "-"
"-plugin-arg-BiniouASTExporter" "PREPEND_CURRENT_DIR=1"
"-plugin-arg-BiniouASTExporter" "MAX_STRING_SIZE=65535" "-cc1" "-triple"
"x86_64-unknown-linux-gnu" "-emit-obj" "-mrelax-all" "-di

... [truncated 1081 characters] ...

stall/lib/clang/18/include"
"-internal-isystem" "/usr/local/include" "-internal-isystem"
"/usr/lib/gcc/x86_64-linux-gnu/12/../../../../x86_64-linux-gnu/include"
"-internal-externc-isystem" "/usr/include/x86_64-linux-gnu"
"-internal-externc-isystem" "/include" "-internal-externc-isystem"
"/usr/include" "-Wno-ignored-optimization-argument" "-Wno-everything"
"-fdeprecated-macro" "-ferror-limit" "19" "-fgnuc-version=4.2.1"
"-fskip-odr-check-in-gmf" "-fcxx-exceptions" "-fexceptions"
"-D__GCC_HAVE_DWARF2_CFI_ASM=1" "-o"
"/tmp/coderabbit-infer/7715a4c0411fba9c/file.o" "-x" "c++"
"indra/llimage/llimageavif.cpp" "-O0" "-fno-builtin" "-include"
"/opt/infer-linux-x86_64-v1.2.0/lib/infer/infer/bin/../lib/clang_wrappers/global_defines.h"
"-Wno-everything"


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@RyeMutt RyeMutt self-assigned this Jun 1, 2026
@RyeMutt RyeMutt added this to the NEXT milestone Jun 1, 2026
@RyeMutt RyeMutt force-pushed the feature/avif-support branch 3 times, most recently from 4d3a08b to f153985 Compare June 1, 2026 19:53
Add an AVIF codec (LLImageAVIF) to llimage backed by libavif with
dav1d (decode) and aom (encode), and wire it into the image upload
and save/snapshot paths, mirroring the existing WebP integration.

- llimage: IMG_CODEC_AVIF and LLImageAVIF (lossy quality-based encode,
  8-bit RGB/RGBA decode with vertical flip); extension, MIME, and
  dimensions-info registration
- build: libavif[aom,dav1d] vcpkg dependency and AVIF.cmake module
- upload: file picker load filters, IMAGE_EXTENSIONS, local bitmaps
- save: FFSAVE_AVIF, snapshot format (combo box, encode switches,
  quality slider) and texture save-as

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@RyeMutt RyeMutt force-pushed the feature/avif-support branch from f153985 to fc56e08 Compare June 1, 2026 20:05
@RyeMutt RyeMutt marked this pull request as ready for review June 1, 2026 20:31
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@indra/llimage/llimageavif.cpp`:
- Around line 214-257: The AVIF encoder currently assumes non-4-component images
are RGB which breaks for 1-channel grayscale; update the logic after
avifRGBImageSetDefaults(&rgb, image) to handle components explicitly: set
rgb.format = AVIF_RGB_FORMAT_RGBA for components==4, rgb.format =
AVIF_RGB_FORMAT_GRAY (or the correct libavif constant for 1-channel gray) for
components==1 and keep rgb.depth/rowBytes as width*components, and for any other
components value (e.g., 2) call setLastError("LLImageAVIF::Unsupported component
count") and return false so we fail fast; ensure the change references
rgb.format, rgb.rowBytes, avifRGBImageSetDefaults, and setLastError.

In `@indra/llimage/llimageavif.h`:
- Around line 37-48: The header comment and getters/setters for LLImageAVIF
incorrectly describe quality=100 as "lossless"; update the wording to "best
quality" (or similar) instead of "lossless" in LLImageAVIF, mEncodeQuality,
setEncodeQuality, and getEncodeQuality declarations and the mEncodeQuality
comment to avoid implying a true lossless encode (since encode() currently uses
AVIF_PIXEL_FORMAT_YUV420 and encoder->quality/qualityAlpha). Alternatively, if
you want to support true lossless, implement a lossless encode path in
LLImageAVIF::encode() (select a lossless pixel format and set the encoder to
lossless mode) and adjust the header docs accordingly; otherwise change all
occurrences of "lossless" to "best quality" to reflect current behavior.

In `@indra/llimage/llimagedimensionsinfo.cpp`:
- Around line 203-212: The getImageDimensionsAVIF implementation currently reads
the entire file into memory (using LLFile::size, std::make_unique<U8[]>, and
mInfile.read) which breaks the lightweight dimensions-only contract; change
LLImageDimensionsInfo::getImageDimensionsAVIF to perform bounded metadata
parsing using file-backed IO instead of full-file buffering: open/seek/read only
the header/metadata regions needed (using mInfile and mSrcFilename) and feed
those limited buffers to the AVIF probe routine (or use a stream/reader API from
the AVIF parser that accepts a file callback), and apply the same bounded-read
approach to the related code in the 224-240 region so you never allocate the
full image in memory.

In `@indra/newview/llfilepicker.cpp`:
- Line 56: The material-texture aggregate filter constant
FFLOAD_MATERIAL_TEXTURE is missing the *.avif pattern so AVIF files are hidden
in the default material/texture picker; update the filter string(s) that build
FFLOAD_MATERIAL_TEXTURE (and the other occurrence noted) to include *.avif in
both the human-readable description and the semicolon-separated pattern list
(e.g., add ;*.avif to the pattern section and add .avif to the parenthesis list
in the display text) so AVIF files appear in the aggregate material-texture
picker on Windows/SDL.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: ef4019f1-ece5-48c2-a553-72544894e74d

📥 Commits

Reviewing files that changed from the base of the PR and between 477fb7a and fc56e08.

📒 Files selected for processing (24)
  • .github/workflows/build.yaml
  • indra/CMakeLists.txt
  • indra/cmake/AVIF.cmake
  • indra/cmake/CMakeLists.txt
  • indra/llimage/CMakeLists.txt
  • indra/llimage/llimage.cpp
  • indra/llimage/llimage.h
  • indra/llimage/llimageavif.cpp
  • indra/llimage/llimageavif.h
  • indra/llimage/llimagedimensionsinfo.cpp
  • indra/llimage/llimagedimensionsinfo.h
  • indra/newview/llfilepicker.cpp
  • indra/newview/llfilepicker.h
  • indra/newview/lllocalbitmaps.cpp
  • indra/newview/lllocalbitmaps.h
  • indra/newview/llpanelsnapshotlocal.cpp
  • indra/newview/llpreviewtexture.cpp
  • indra/newview/llsnapshotlivepreview.cpp
  • indra/newview/llsnapshotmodel.h
  • indra/newview/llviewermenufile.cpp
  • indra/newview/llviewerwindow.cpp
  • indra/newview/skins/default/xui/en/panel_snapshot_local.xml
  • indra/newview/skins/default/xui/en/strings.xml
  • indra/vcpkg.json

Comment thread indra/llimage/llimageavif.cpp
Comment thread indra/llimage/llimageavif.h
Comment thread indra/llimage/llimagedimensionsinfo.cpp
Comment thread indra/newview/llfilepicker.cpp
RyeMutt and others added 2 commits June 1, 2026 19:09
LLImageAVIF::encode() now produces mathematically lossless output at
quality 100 (identity matrix + YUV 4:4:4); the lossy path keeps
4:2:0 with a BT.601 matrix.

Expose a distinct "AVIF (Lossless)" snapshot format alongside the
lossy "AVIF" option, mirroring the WebP (Lossless) treatment:

- SNAPSHOT_FORMAT_AVIF_LOSSLESS enum and combo box item
- encode, size-estimate, and save-dispatch switches
- no quality slider for the lossless variant

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Encode 1- and 2-component raw images as monochrome (4:0:0) AVIF via
AVIF_RGB_FORMAT_GRAY / AVIF_RGB_FORMAT_GRAYA, selecting the pixel and
RGB format by component count instead of assuming RGB/RGBA. Monochrome
uses a BT.601 matrix (identity requires three planes); luma maps
directly to Y so quality 100 remains lossless.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@RyeMutt RyeMutt merged commit 449c281 into develop Jun 2, 2026
18 checks passed
@RyeMutt RyeMutt deleted the feature/avif-support branch June 2, 2026 03:53
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant