
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, iOS.
/composeApp is for code that will be shared across your Compose Multiplatform applications. It contains several subfolders:
/iosApp contains iOS applications. Even if you’re sharing your UI with Compose Multiplatform, you need this entry point for your iOS app. This is also where you should add SwiftUI code for your project.
/shared is for the code that will be shared between all targets in the project. The most important subfolder is commonMain. If preferred, you can add code to the platform-specific folders here too.
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 :composeApp:assembleDebug.\gradlew.bat :composeApp:assembleDebugTo 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 a two-tier architecture with automatic fallback:
Native Mode (Default):
Fallback Mode (Automatic):
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:
| 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 |
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 // SMART or STRICT
)AvifKit is published as a Kotlin Multiplatform library with seamless integration for both Android and iOS platforms.
Add the dependency to your build.gradle.kts:
dependencies {
implementation("io.github.alfikri-rizky:avifkit:0.2.0")
}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/AvifKit
0.2.0 or higherOr add to your Package.swift:
dependencies: [
.package(url: "https://github.com/alfikri-rizky/AvifKit", from: "0.2.0")
]Setup Notes:
canImport(libavif) evaluates correctlyTroubleshooting:
If you see "
Ensure sufficient disk space (at least 1GB free for SPM dependencies)
Clean all caches:
# Clear SPM cache
rm -rf ~/Library/Caches/org.swift.swiftpm
rm -rf ~/Library/org.swift.swiftpm
# Clear Xcode derived data
rm -rf ~/Library/Developer/Xcode/DerivedDataIn Xcode:
Verify libavif is loaded:
import AvifKit
print("AVIF available:", AVIFNativeConverter.isAvifAvailable) // Should be true
print("AVIF version:", AVIFNativeConverter.avifVersion) // Should be "0.11.1"Download from GitHub Releases: v0.2.0
CocoaPods support is technically available but not recommended due to validation issues:
pod 'AvifKit', '~> 0.2.0'Important Notes:
pod spec lint validation fails due to libavif dependency's old iOS deployment targets (8.0-9.0)libarclite, which was removed from Xcode 14+pod install since app deployment targets (iOS 13.0+) override pod settingsRecommended alternatives:
We cannot fix this without the libavif CocoaPods maintainers updating their pod's deployment targets.
shared/src/androidMain/cpp/)Technical Details:
-O3 compiler flagsAVIFNativeConverter.swift)#if canImport(libavif)
.up orientation skips double-rendernoneSkipLast for opaque images to skip premultiplicationTechnical Details:
| Component | Status | Location | Notes |
|---|---|---|---|
| Core Library | ✅ Complete | shared/src/commonMain/ |
Cross-platform API |
| Android Native | ✅ Complete | shared/src/androidMain/cpp/ |
JNI + libavif |
| iOS Native | ✅ Complete | shared/src/iosMain/swift/ |
Swift + libavif |
| Adaptive Compression | ✅ Complete | Both platforms | SMART & STRICT strategies |
| Orientation Support | ✅ Complete | Both platforms | EXIF (Android), UIImage (iOS) |
| Fallback Mode | ✅ Complete | Both platforms | JPEG when libavif unavailable |
| Distribution | ✅ Complete | Package.swift |
SPM support (CocoaPods coming soon) |
| Build Configuration | ✅ Complete | shared/build.gradle.kts |
Ready for publishing |
Library Size:
Decoding on iOS (Fallback Mode):
UIImage(data:) decodingPlatform API Differences:
android.graphics.Bitmap
UIImage
PlatformBitmap expect/actual patternBuild Requirements (for library authors only):
Check if native AVIF is available:
val converter = AvifConverter()
val isSupported = converter.isAvifSupported()
// Android: check library version
val version = converter.getLibraryVersion() // Shows libavif version or "Placeholder"
// iOS: check in AVIFNativeConverter.swift
AVIFNativeConverter.isAvifAvailable // true if libavif linkedThe library automatically uses fallback when native library is unavailable:
If you want to build the library from source or contribute to development:
# 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:buildThe library uses a comprehensive publishing setup:
To Maven Central:
./gradlew :shared:publishAllPublicationsToSonatypeRepositoryTo local Maven (for testing):
./gradlew :shared:publishToMavenLocalTo CocoaPods:
pod trunk push AvifKit.podspecscripts/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.
.up orientationEncodingFailed crash when using SPEED preset (speed ≥ 7 triggered incompatible REALTIME mode in libaom)AvifKitNativeHandler (Swift method signature mismatch)avifResultToString for native encoding failuresLearn more about Kotlin Multiplatform…
This is a Kotlin Multiplatform project targeting Android, iOS.
/composeApp is for code that will be shared across your Compose Multiplatform applications. It contains several subfolders:
/iosApp contains iOS applications. Even if you’re sharing your UI with Compose Multiplatform, you need this entry point for your iOS app. This is also where you should add SwiftUI code for your project.
/shared is for the code that will be shared between all targets in the project. The most important subfolder is commonMain. If preferred, you can add code to the platform-specific folders here too.
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 :composeApp:assembleDebug.\gradlew.bat :composeApp:assembleDebugTo 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 a two-tier architecture with automatic fallback:
Native Mode (Default):
Fallback Mode (Automatic):
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:
| 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 |
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 // SMART or STRICT
)AvifKit is published as a Kotlin Multiplatform library with seamless integration for both Android and iOS platforms.
Add the dependency to your build.gradle.kts:
dependencies {
implementation("io.github.alfikri-rizky:avifkit:0.2.0")
}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/AvifKit
0.2.0 or higherOr add to your Package.swift:
dependencies: [
.package(url: "https://github.com/alfikri-rizky/AvifKit", from: "0.2.0")
]Setup Notes:
canImport(libavif) evaluates correctlyTroubleshooting:
If you see "
Ensure sufficient disk space (at least 1GB free for SPM dependencies)
Clean all caches:
# Clear SPM cache
rm -rf ~/Library/Caches/org.swift.swiftpm
rm -rf ~/Library/org.swift.swiftpm
# Clear Xcode derived data
rm -rf ~/Library/Developer/Xcode/DerivedDataIn Xcode:
Verify libavif is loaded:
import AvifKit
print("AVIF available:", AVIFNativeConverter.isAvifAvailable) // Should be true
print("AVIF version:", AVIFNativeConverter.avifVersion) // Should be "0.11.1"Download from GitHub Releases: v0.2.0
CocoaPods support is technically available but not recommended due to validation issues:
pod 'AvifKit', '~> 0.2.0'Important Notes:
pod spec lint validation fails due to libavif dependency's old iOS deployment targets (8.0-9.0)libarclite, which was removed from Xcode 14+pod install since app deployment targets (iOS 13.0+) override pod settingsRecommended alternatives:
We cannot fix this without the libavif CocoaPods maintainers updating their pod's deployment targets.
shared/src/androidMain/cpp/)Technical Details:
-O3 compiler flagsAVIFNativeConverter.swift)#if canImport(libavif)
.up orientation skips double-rendernoneSkipLast for opaque images to skip premultiplicationTechnical Details:
| Component | Status | Location | Notes |
|---|---|---|---|
| Core Library | ✅ Complete | shared/src/commonMain/ |
Cross-platform API |
| Android Native | ✅ Complete | shared/src/androidMain/cpp/ |
JNI + libavif |
| iOS Native | ✅ Complete | shared/src/iosMain/swift/ |
Swift + libavif |
| Adaptive Compression | ✅ Complete | Both platforms | SMART & STRICT strategies |
| Orientation Support | ✅ Complete | Both platforms | EXIF (Android), UIImage (iOS) |
| Fallback Mode | ✅ Complete | Both platforms | JPEG when libavif unavailable |
| Distribution | ✅ Complete | Package.swift |
SPM support (CocoaPods coming soon) |
| Build Configuration | ✅ Complete | shared/build.gradle.kts |
Ready for publishing |
Library Size:
Decoding on iOS (Fallback Mode):
UIImage(data:) decodingPlatform API Differences:
android.graphics.Bitmap
UIImage
PlatformBitmap expect/actual patternBuild Requirements (for library authors only):
Check if native AVIF is available:
val converter = AvifConverter()
val isSupported = converter.isAvifSupported()
// Android: check library version
val version = converter.getLibraryVersion() // Shows libavif version or "Placeholder"
// iOS: check in AVIFNativeConverter.swift
AVIFNativeConverter.isAvifAvailable // true if libavif linkedThe library automatically uses fallback when native library is unavailable:
If you want to build the library from source or contribute to development:
# 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:buildThe library uses a comprehensive publishing setup:
To Maven Central:
./gradlew :shared:publishAllPublicationsToSonatypeRepositoryTo local Maven (for testing):
./gradlew :shared:publishToMavenLocalTo CocoaPods:
pod trunk push AvifKit.podspecscripts/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.
.up orientationEncodingFailed crash when using SPEED preset (speed ≥ 7 triggered incompatible REALTIME mode in libaom)AvifKitNativeHandler (Swift method signature mismatch)avifResultToString for native encoding failuresLearn more about Kotlin Multiplatform…