Android Quickstart
Getting started with the Perfectly Clear SDK in an Android application
This guide walks you through adding the Perfectly Clear Android SDK to your project and correcting your first image using AI scene detection.
Add the AAR to your project
Create a libs folder inside your app module and copy the .aar file into it:
app/
libs/
pfc-sdk-v1.0.0.0.aarThen add the dependency in your build.gradle.kts:
dependencies {
implementation(fileTree(mapOf("dir" to "libs", "include" to listOf("*.jar", "*.aar"))))
}To browse SDK sources in Android Studio:
- Open the
V10class - Click Choose Sources…
- In the file picker, navigate inside the
.aarand selectsources.jar
Configure asset compression
The SDK loads AI model and preset files from assets at runtime. Prevent the build system from compressing them:
android {
androidResources {
noCompress.addAll(listOf(".pnn", ".pnne", ".preset"))
}
}Add model and resource files
Place all model and preset files into your app's assets folder:
app/
src/main/assets/
models/
dynamic.pnne
aicolor_tflite.pnn
fdfront_tflite.pnn
fdback_tflite.pnn
facemesh_tflite.pnn
faceshape_tflite.pnn
sd_<uuid>_tflite.pnn
skintone_<uuid>_tflite.pnn
presets/
your_scene.preset| File | Purpose |
|---|---|
dynamic.pnne | Dynamic correction model |
aicolor_tflite.pnn | AI color correction (TFLite) |
fdfront_tflite.pnn | Front-facing face detection |
fdback_tflite.pnn | Back-facing face detection |
facemesh_tflite.pnn | Face mesh model |
faceshape_tflite.pnn | Face blend-shape model |
sd_<UUID>_tflite.pnn | Scene detection model |
skintone_<UUID>_tflite.pnn | Skin tone model |
your_scene.preset | Scene presets for your workflow |
Scene detection and skin tone models are paired by UUID (e.g. 20211221 for Pro/Universal, 5000200 for School & Sports). Load the pair that matches your preset group. Contact EyeQ to receive the model files for your distribution.
Create and load the engine
Create a V10 instance and initialize the engine during app startup or before the first image correction. The engine is expensive to construct — create it once and reuse it across multiple images.
import photos.eyeq.pfcsdk.perfectlyclear.V10
import photos.eyeq.pfcsdk.perfectlyclear.PFCParam
val sdk = V10()
// Create the engine
val engine: ByteBuffer = sdk.createEngine()
// Load AI models and validate your license
val resultCode = sdk.loadAIEngine(
engine = engine,
apiKey = "YOUR_API_KEY",
cert = "YOUR_CERTIFICATE",
modelDynamic = loadModelBuffer("dynamic.pnne"),
modelSceneDetection = loadModelBuffer("sd_20211221_tflite.pnn"),
modelSkintone = loadModelBuffer("skintone_20211221_tflite.pnn"),
modelAicolor = loadModelBuffer("aicolor_tflite.pnn"),
modelFDFront = loadModelBuffer("fdfront_tflite.pnn"),
modelFDBack = loadModelBuffer("fdback_tflite.pnn"),
modelFaceMesh = loadModelBuffer("facemesh_tflite.pnn"),
modelBlendShape = loadModelBuffer("faceshape_tflite.pnn"),
useGpu = false
)
// resultCode > 0 means success (bitwise sum of loaded features)
// Load scene presets
sdk.loadScenePresets(engine, presetBuffer)loadModelBuffer() should memory-map or read the model file from your app's assets into a ByteBuffer. See the sample app for a complete implementation.
The V10 engine is not thread-safe. Serialize all calls with a lock (e.g. synchronized or Mutex) if the engine is shared across threads.
Prepare the image
The SDK operates on raw pixel buffers, not Bitmap objects. Extract pixels into a native ByteBuffer using the SDK's allocNativeBuffer allocator:
// Pixel format for Android Bitmap (ARGB_8888 → ABGR in native byte order)
val PIXEL_FORMAT = V10.PFC_PixelFormat32bppABGR
val bitmap: Bitmap = loadBitmap(uri) // your source bitmap in ARGB_8888
val width = bitmap.width
val height = bitmap.height
val stride = width * 4
// Allocate a native buffer for the pixel data
val pixels = sdk.allocNativeBuffer(bitmap.allocationByteCount)!!
bitmap.copyPixelsToBuffer(pixels)Android Bitmap.Config.ARGB_8888 stores pixels in ABGR native byte order. Use V10.PFC_PixelFormat32bppABGR as the pixel format constant.
Analyze and correct the image
Run the analysis pipeline: compute a profile with calc, detect the scene with getDetectedScene, load scene parameters with readScenePreset, then apply corrections with apply.
// 1. Analyze the image — returns a profile
val profile: ByteBuffer = sdk.calc(
width, height, stride, PIXEL_FORMAT,
pixels, 0, 0, 0, null, engine, 0, 0
)
// 2. Detect the scene
val sceneId = sdk.getDetectedScene(profile)
// 3. Load correction parameters for the detected scene
val param = PFCParam()
sdk.readScenePreset(param, engine, sceneId)
// 4. (Optional) populate detected skin tone settings
sdk.getDetectedSkintoneParams(profile, param)
// 5. (Optional) adjust overall AI strength (0–200, default 100)
sdk.applyStrengthToParam(param, 100)
// 6. Apply corrections — modifies pixels in place
pixels.clear()
bitmap.copyPixelsToBuffer(pixels)
val applyResult = sdk.apply(
width, height, stride, PIXEL_FORMAT,
pixels, engine, profile, param
)
// applyResult == 0 means success (V10.APPLY_SUCCESS)
// 7. Write corrected pixels back to the bitmap
pixels.clear()
pixels.limit(bitmap.allocationByteCount)
if (applyResult >= 0) {
bitmap.copyPixelsFromBuffer(pixels)
}
// bitmap now contains the corrected imageClean up
Release native resources when you are done processing. Call releaseProfile, freeNativeBuffer, and destroyEngine:
sdk.releaseProfile(profile)
sdk.freeNativeBuffer(pixels)
sdk.destroyEngine(engine)Complete example
The following snippet combines all the steps into a single function:
import android.graphics.Bitmap
import photos.eyeq.pfcsdk.perfectlyclear.V10
import photos.eyeq.pfcsdk.perfectlyclear.PFCParam
import java.nio.ByteBuffer
fun correctBitmap(
bitmap: Bitmap,
sdk: V10,
engine: ByteBuffer
): Int {
val pixelFormat = V10.PFC_PixelFormat32bppABGR
val width = bitmap.width
val height = bitmap.height
val stride = width * 4
// 1. Copy pixels into a native buffer
val pixels = sdk.allocNativeBuffer(bitmap.allocationByteCount)
?: return -1
bitmap.copyPixelsToBuffer(pixels)
// 2. Analyze the image
val profile = sdk.calc(
width, height, stride, pixelFormat,
pixels, 0, 0, 0, null, engine, 0, 0
)
// 3. Detect scene and build parameters
val sceneId = sdk.getDetectedScene(profile)
val param = PFCParam()
sdk.readScenePreset(param, engine, sceneId)
sdk.getDetectedSkintoneParams(profile, param)
sdk.applyStrengthToParam(param, 100)
// 4. Apply corrections
pixels.clear()
bitmap.copyPixelsToBuffer(pixels)
val result = sdk.apply(
width, height, stride, pixelFormat,
pixels, engine, profile, param
)
// 5. Write corrected pixels back
if (result >= 0) {
pixels.clear()
pixels.limit(bitmap.allocationByteCount)
bitmap.copyPixelsFromBuffer(pixels)
}
// 6. Clean up
sdk.releaseProfile(profile)
sdk.freeNativeBuffer(pixels)
return result
}