EyeQ Docs

Using the JNI API

Architecture, core objects, and patterns for Java JNI integration

The JNI API provides Java bindings for the Perfectly Clear correction engine. It's available on Linux, macOS, and Windows, and offers the same performance as the native C API with Java convenience.

The JNI API closely mirrors the Android SDK, making code portable between server and mobile platforms.

Core objects

The SDK uses four primary objects: ByteBuffer handles for the native engine and profile, PFCImageFile for image I/O, and PFCParam for correction parameters.

Engine

The engine is represented as a ByteBuffer handle returned by createEngine(). Create one engine per thread and reuse it across multiple images.

PfC sdk = new PfC();
ByteBuffer engine = sdk.createEngine();

// Load AI models
int features = 1 + 2 + 32;  // SD + Corrections + FaceMesh
sdk.loadAIEngine(engine, features, "/path/to/bin");

// ... process images ...

sdk.destroyEngine(engine);

Image

Use PFCImageFile to load images with automatic color space conversion and metadata handling.

PFCImageFile image = new PFCImageFile();
ByteBuffer pixels = image.LoadImageFile("input.jpg", true, null);

// Access image properties
int width = image.width;
int height = image.height;
int stride = image.stride;
int format = image.pixelFormat;

For raw pixel data, allocate a native buffer directly:

ByteBuffer pixels = sdk.allocNativeBuffer(width * height * 4);
// ... fill buffer with pixel data ...
sdk.freeNativeBuffer(pixels);

Profile

The profile holds analysis results from calc(). Always release when done.

ByteBuffer profile = sdk.calc(
    width, height, stride, format, pixels,
    0, 0, 0, null,  // no downsampled image
    engine, 1, 0    // fast FAE, calculate tint
);

// Check status
int status = sdk.profileStatus(profile);
if (status != 0) {
    System.out.println("Core: " + sdk.coreStatus(profile));
    System.out.println("FB: " + sdk.fbStatus(profile));
}

// ... use profile ...

sdk.releaseProfile(profile);

Parameters

PFCParam contains all correction settings. Field names follow the pattern group_fieldName:

PrefixGroup
core_Core corrections (exposure, contrast)
nr_Noise reduction
fb_Face beautification
re_Red-eye removal
v3_V3 corrections (LUTs, finishing)
PFCParam param = new PFCParam();

// Enable core corrections
param.core_bEnabled = true;
param.core_iStrength = 100;
param.core_bUseAutomaticStrengthSelection = true;

// Enable face beautification
param.fb_bEnabled = true;
param.fb_bSmooth = true;
param.fb_iSmoothLevel = 50;

Processing workflow

Single-call correction

The simplest approach uses autoCorrect() for one-step processing:

PFCParam param = new PFCParam();
sdk.setParam(param, PfC.PRESET_I_AUTO_PEOPLE);

int result = sdk.autoCorrect(
    width, height, stride, format, pixels,
    0, 0, 0, null,  // no downsampled image
    param, 1        // fast FAE
);

if (result < 0) {
    System.out.println("Correction failed: " + result);
}

Two-step correction

For more control, use separate calc() and apply() calls:

// Step 1: Analyze
ByteBuffer profile = sdk.calc(
    width, height, stride, format, pixels,
    0, 0, 0, null, engine, 1, 0
);

// Inspect results
int faceCount = sdk.faeFaceCount(profile);
boolean tintDetected = sdk.abnormalTintDetected(profile, PfC.TINT_CORRECT_DEFAULT);

// Step 2: Configure parameters
PFCParam param = new PFCParam();
param.core_bEnabled = true;
param.core_bAbnormalTintRemoval = tintDetected;
param.fb_bEnabled = faceCount > 0;

// Step 3: Apply
int result = sdk.apply(width, height, stride, format, pixels, engine, profile, param);

sdk.releaseProfile(profile);

Scene detection workflow

Use AI scene detection to automatically select optimal parameters:

// Load scene presets
try (RandomAccessFile file = new RandomAccessFile("universal.preset", "r")) {
    FileChannel channel = file.getChannel();
    MappedByteBuffer buffer = channel.map(FileChannel.MapMode.READ_ONLY, 0, channel.size());
    sdk.loadScenePresets(engine, buffer);
}

// Analyze image
ByteBuffer profile = sdk.calc(width, height, stride, format, pixels,
    0, 0, 0, null, engine, 1, 1);

// Get detected scene
int scene = sdk.getDetectedScene(profile);
System.out.println("Detected scene: " + scene);

// Load scene-specific parameters
PFCParam param = new PFCParam();
sdk.readScenePreset(param, engine, scene);

// Apply
sdk.apply(width, height, stride, format, pixels, engine, profile, param);

Memory management

Native buffers

Use allocNativeBuffer() for pixel data when not using PFCImageFile:

// Allocate
ByteBuffer pixels = sdk.allocNativeBuffer(width * height * bytesPerPixel);

// ... use buffer ...

// Free
sdk.freeNativeBuffer(pixels);

Memory-mapped files

Load preset and model files using memory-mapped I/O for efficiency:

try (RandomAccessFile file = new RandomAccessFile("file.preset", "r")) {
    FileChannel channel = file.getChannel();
    MappedByteBuffer buffer = channel.map(FileChannel.MapMode.READ_ONLY, 0, channel.size());

    sdk.loadScenePresets(engine, buffer);
}

Resource cleanup pattern

Always clean up resources in reverse order of creation:

PfC sdk = new PfC();
ByteBuffer engine = null;
ByteBuffer profile = null;
PFCImageFile image = null;

try {
    engine = sdk.createEngine();
    sdk.loadAIEngine(engine, features, binPath);

    image = new PFCImageFile();
    ByteBuffer pixels = image.LoadImageFile(inputPath);

    profile = sdk.calc(image.width, image.height, image.stride,
        image.pixelFormat, pixels, 0, 0, 0, null, engine, 1, 0);

    // ... process ...

} finally {
    if (profile != null) sdk.releaseProfile(profile);
    if (image != null) image.deletePFCImageFile();
    if (engine != null) sdk.destroyEngine(engine);
    sdk.releaseAddonPath();
}

Thread safety

Engine instances are not thread-safe. Use one of these patterns for multi-threaded applications:

Thread-local engines

private static final ThreadLocal<ByteBuffer> threadEngine =
    ThreadLocal.withInitial(() -> {
        PfC sdk = new PfC();
        ByteBuffer engine = sdk.createEngine();
        sdk.loadAIEngine(engine, features, binPath);
        return engine;
    });

public void processImage(String path) {
    ByteBuffer engine = threadEngine.get();
    // ... use engine ...
}

Engine pool

private final BlockingQueue<ByteBuffer> enginePool = new LinkedBlockingQueue<>();

public void initPool(int size) {
    PfC sdk = new PfC();
    for (int i = 0; i < size; i++) {
        ByteBuffer engine = sdk.createEngine();
        sdk.loadAIEngine(engine, features, binPath);
        enginePool.offer(engine);
    }
}

public void processImage(String path) throws InterruptedException {
    ByteBuffer engine = enginePool.take();
    try {
        // ... use engine ...
    } finally {
        enginePool.offer(engine);
    }
}

Error handling

Check return codes from all SDK calls:

int status = sdk.loadAIEngine(engine, features, binPath);
if (status != features) {
    System.out.println("Warning: some features failed to load");
    System.out.println("Requested: " + features + ", loaded: " + status);
}

int applyResult = sdk.apply(...);
if (applyResult < 0) {
    System.out.println("Apply failed: " + applyResult);
}

// Extract individual status codes from combined result
int coreResult = sdk.CORERETCODE(applyResult);
int fbResult = sdk.FBRETCODE(applyResult);
int nrResult = sdk.NRRETCODE(applyResult);
int reResult = sdk.RERETCODE(applyResult);

Face detection

Query face detection results after calc():

// Face Aware Exposure faces
int faeCount = sdk.faeFaceCount(profile);
for (int i = 0; i < faeCount; i++) {
    PFCFaceRect face = new PFCFaceRect();
    if (sdk.getFAEFaceRect(profile, face, i)) {
        System.out.printf("Face %d: (%d,%d) %dx%d, smile=%d, confidence=%d%n",
            i, face.faceLeft, face.faceTop,
            face.faceWidth, face.faceHeight,
            face.smileLevel, face.confidence);
    }
}

// Face Beautification faces
int fbCount = sdk.fbFaceCount(profile);
for (int i = 0; i < fbCount; i++) {
    FaceInfo info = new FaceInfo();
    if (sdk.getFaceInfo(profile, info, i)) {
        System.out.printf("Face %d: eyes at (%d,%d) and (%d,%d)%n",
            i, info.leftEye_x, info.leftEye_y,
            info.rightEye_x, info.rightEye_y);
    }
}

Performance tips

  1. Reuse engines — Create once, use for many images
  2. Use downsampled images — Pass a smaller preview image for faster analysis when full resolution isn't needed
  3. Skip unnecessary calculations — Use bNoTintCalc=1 when tint correction isn't needed
  4. Use fast FAE — Pass bFastFAE=1 for faster face detection
  5. Memory-map large files — Use MappedByteBuffer for preset and model files

PFC-SDK Version 11.0.0.1389 built from cc658dab3c675a529b3df2fa6421d4550810d77d on 06-29-2026.

Copyright © 2026 EyeQ Imaging Inc. All rights reserved.

On this page