AvifKit
0.3.1indexedAVIF encoding and decoding with native libavif, adaptive SMART/STRICT compression, automatic JPEG fallback, multi-threaded processing, priority presets, format detection, resizing and metadata preservation.
AVIF encoding and decoding with native libavif, adaptive SMART/STRICT compression, automatic JPEG fallback, multi-threaded processing, priority presets, format detection, resizing and metadata preservation.
This is a Kotlin Multiplatform project targeting Android and iOS, built with Android Gradle Plugin 9.2 (requires Gradle 9.4.1+ and JDK 17+).
/shared — the AvifKit library itself. The cross-platform API lives in
commonMain; platform code is in androidMain (Kotlin JNI
bindings) and iosMain (Kotlin/Native cinterop straight to libavif's C API — no Swift).
Published to Maven Central as
io.github.alfikri-rizky:avifkit. Uses the AGP 9 KMP library plugin
(com.android.kotlin.multiplatform.library).
/shared-native — the Android native build (libavif/AOM + JNI wrapper),
compiled with CMake/NDK. It's a plain com.android.library because the KMP library plugin
can't build native code. Published as the companion artifact avifkit-native, which :shared
pulls in transitively — so consumers only ever depend on avifkit.
/composeApp — AVIF Studio, the shared Compose Multiplatform UI for the
Android and iOS apps. A KMP library module (AGP 9 forbids the KMP plugin alongside
com.android.application in one module), producing the ComposeApp framework for iOS.
Not published.
/androidApp — the Android application shell: one Activity, a manifest and
launcher resources. Everything the user sees comes from :composeApp. Not published.
/iosApp — the iOS application shell: a SwiftUI @main that hosts
MainViewController() from the ComposeApp framework. Not published.
A free, offline image converter built on AvifKit — the reference app for the library, and a real app in its own right. No ads, no accounts, no network permission.
Run it with ./gradlew :androidApp:installDebug, or open iosApp/iosApp.xcodeproj in Xcode.
Both build the same UI from :composeApp.
| Home | Pick a recipe | Converting |
|---|---|---|
![]() | ![]() | ![]() |
| Results | Settings | Bahasa Indonesia + dark |
|---|---|---|
![]() | ![]() | ![]() |
Picking a folder once, so Save stops asking:
The same Compose UI on iOS, from the same :composeApp module:
To build and run the development version of the Android app, use the run configuration from the run widget in your IDE’s toolbar or build it directly from the terminal:
./gradlew :androidApp:assembleDebug
.\gradlew.bat :androidApp:assembleDebug
To build and run the development version of the iOS app, use the run configuration from the run widget in your IDE’s toolbar or open the /iosApp directory in Xcode and run it from there.
AvifKit is a production-ready Kotlin Multiplatform library for AVIF image encoding and decoding on Android and iOS.
AvifKit uses native AVIF libraries on both platforms with explicit error reporting:
Shared XCFramework is self-contained (no avif.swift, no Swift bridge, no registration step). See docs/IOS_CINTEROP_SOLUTION.md.AvifError exceptions are thrown on failure (no silent fallbacks)val converter = AvifConverter()
// Convert to AVIF with priority preset
val result = converter.convertToFile(
input = ImageInput.from("/path/to/image.jpg"),
outputPath = "/path/to/output.avif",
priority = Priority.BALANCED
)
When you need to compress images to meet a specific file size limit, AvifKit offers two compression strategies:
Finds the highest quality image that still meets your target file size. This is the default and recommended strategy for most use cases.
val options = EncodingOptions(
maxSize = 200 * 1024, // 200KB target
compressionStrategy = CompressionStrategy.SMART // Default
)
val result = converter.convertToFile(
input = ImageInput.from("/path/to/image.jpg"),
outputPath = "/path/to/output.avif",
priority = Priority.BALANCED,
options = options
)
How it works:
Best for:
Finds the smallest possible image by continuing compression even after meeting the target size.
val options = EncodingOptions(
maxSize = 200 * 1024, // 200KB target
compressionStrategy = CompressionStrategy.STRICT
)
val result = converter.convertToFile(
input = ImageInput.from("/path/to/image.jpg"),
outputPath = "/path/to/output.avif",
priority = Priority.BALANCED,
options = options
)
How it works:
Best for:
Example with 500KB target:
Priority.SPEED // Fast encoding, lower quality
Priority.QUALITY // Best quality, slower encoding
Priority.STORAGE // Minimum file size
Priority.BALANCED // Good balance (default)
EncodingOptions(
quality = 75, // Base quality (0-100)
speed = 6, // Encoding speed (0-10)
subsample = ChromaSubsample.YUV420, // Chroma subsampling
alphaQuality = 90, // Alpha channel quality
maxDimension = 2048, // Auto-resize if larger
maxSize = 200 * 1024, // Target size in bytes
compressionStrategy = CompressionStrategy.SMART
)
AvifKit is published as a Kotlin Multiplatform library with seamless integration for both Android and iOS platforms.
Toolchain: built with Kotlin 2.3.21, so the klibs carry ABI version 2.3.0 — KMP consumers need Kotlin 2.3 or newer. Android bytecode targets Java 11.
Add the dependency to your build.gradle.kts:
dependencies {
implementation("io.github.alfikri-rizky:avifkit:0.3.2")
}
That's it! The library includes pre-built native binaries for all ABIs (arm64-v8a, armeabi-v7a, x86, x86_64) with full AVIF support via libavif.
In Xcode:
https://github.com/alfikri-rizky/AvifKit0.3.2 or higherOr add to your Package.swift:
dependencies: [
.package(url: "https://github.com/alfikri-rizky/AvifKit", from: "0.3.2")
]
Setup Notes:
import Shared and use AvifConverter() directlyUsage:
import Shared
let converter = AvifConverter()
print("AVIF available:", converter.isAvifSupported()) // true
Troubleshooting:
rm -rf ~/Library/Caches/org.swift.swiftpm ~/Library/org.swift.swiftpm
rm -rf ~/Library/Developer/Xcode/DerivedData
Download from GitHub Releases: v0.3.2
CocoaPods support is technically available but not recommended due to validation issues:
pod 'AvifKit', '~> 0.3.2'
Important Notes:
pod install since app deployment targets (iOS 15.0+) override pod settingsRecommended alternatives:
shared-native/src/main/cpp/, the :shared-native module)Technical Details:
-O3 compiler flagsTechnical Details:
scripts/build-ios-libavif.sh; linked via cinterop linkerOptsCheck if native AVIF is available:
val converter = AvifConverter()
val isSupported = converter.isAvifSupported() // true on both platforms (codec statically linked)
The library automatically uses fallback when native library is unavailable:
AvifError.EncodingFailed or AvifError.DecodingFailed with actionable error messagesNSError via @Throws — caught by Swift's If you want to build the library from source or contribute to development:
Do not pair a new Xcode with an old Kotlin. cinterop is generated from the SDK headers you have installed, while
platform.*klibs ship prebuilt inside the Kotlin/Native distribution. If Xcode is newer than the SDK that distribution was built against, cinterop references types those klibs do not contain andcommonizeCInteropdies onUnresolved classifier: platform/.... Xcode 26 needs Kotlin 2.2.21 or newer for this reason.
# 1. Clone the repository
git clone https://github.com/alfikri-rizky/AvifKit.git
cd AvifKit
# 2. Run the preparation script (downloads libavif, builds everything)
./scripts/prepare-for-publish.sh
# 3. Build the project
./gradlew :shared:build
The library uses a comprehensive publishing setup:
To Maven Central (publishes the library and its native companion — both required):
./gradlew :shared-native:publishToMavenCentral :shared:publishToMavenCentral
To local Maven (for testing):
./gradlew :shared-native:publishToMavenLocal :shared:publishToMavenLocal
To CocoaPods:
pod trunk push AvifKit.podspec
scripts/setup-android-libavif.sh - Downloads libavif for Android developmentscripts/setup-ios-avif.sh - Sets up iOS dependencies (CocoaPods/SPM)scripts/prepare-for-publish.sh - Prepares everything for release (runs both setup scripts + builds)scripts/verify-integration.sh - Verifies the integration is working correctlyNote: End users of your published library don't need these scripts - they're only for development and publishing.
Release notes for every version live on the Releases page.
Learn more about Kotlin Multiplatform…
.avif from a browser
download opens here.__attribute__((constructor)) — no manual setup needed@Throws annotations propagate errors as NSError to Swift's do/catchAvifError exceptions when dependencies are missing (no silent fallbacks)| Aspect | SMART | STRICT |
|---|
| Goal | Best quality within limit | Smallest possible size |
| Speed | Faster (6-8 attempts) | Slower (up to 10 attempts) |
| Result Quality | Higher quality | Lower quality |
| Result Size | Near target size | Well below target |
| Use Case | General use | Storage-critical |
Pick ONE iOS channel — do not mix.
commonMain
Gradle dependency below. The iOS AVIF codec (libavif + AOM) is embedded in the
Kotlin/Native artifact, so iOS links with no SPM package and no extra setup —
exactly like Android. This is the recommended path for shared KMP code.import Shared; see iOS section).Shared module into two different frameworks with
disjoint symbol namespaces, which fails with Undefined symbol: _avif* at
link time. If your app uses the shared KMP module via Gradle, remove the SPM
AvifKit package reference from the iOS app target.AvifConverter.ios.kt → libavif) — the same C API as Android, no Swift bridgeShared.framework (self-contained XCFramework)isAvifSupported() returns true)@Throws annotations — errors propagate as NSError to Swift's do/catch| Component | Status | Location | Notes |
|---|
| Core Library | ✅ Complete | shared/src/commonMain/ | Cross-platform API |
| Android Native | ✅ Complete | shared-native/src/main/cpp/ | JNI + libavif (:shared-native module) |
| iOS Native | ✅ Complete | shared/src/iosMain/kotlin/ + shared/src/nativeInterop/ | cinterop + libavif (no Swift) |
| Adaptive Compression | ✅ Complete | Both platforms | SMART & STRICT strategies |
| Orientation Support | ✅ Complete | Both platforms | EXIF (Android), UIImage (iOS) |
| Fallback Mode | ❌ Removed | Both platforms | Replaced with explicit AvifError exceptions |
| Distribution | ✅ Complete | Package.swift | SPM support (CocoaPods coming soon) |
| Build Configuration | ✅ Complete | shared/build.gradle.kts | Ready for publishing |
Library Size:
No Fallback Mode (v0.2.3+):
AvifError exceptionsPlatform API Differences:
android.graphics.BitmapUIImagePlatformBitmap expect/actual patternBuild Requirements (for library authors only):
do/catchSurfaced from shared tags and platforms — no rankings paid for.