EyeQ Docs

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.aar

Then 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:

  1. Open the V10 class
  2. Click Choose Sources…
  3. In the file picker, navigate inside the .aar and select sources.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
FilePurpose
dynamic.pnneDynamic correction model
aicolor_tflite.pnnAI color correction (TFLite)
fdfront_tflite.pnnFront-facing face detection
fdback_tflite.pnnBack-facing face detection
facemesh_tflite.pnnFace mesh model
faceshape_tflite.pnnFace blend-shape model
sd_<UUID>_tflite.pnnScene detection model
skintone_<UUID>_tflite.pnnSkin tone model
your_scene.presetScene 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 image

Clean 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
}

On this page