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:
| Prefix | Group |
|---|---|
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
- Reuse engines — Create once, use for many images
- Use downsampled images — Pass a smaller preview image for faster analysis when full resolution isn't needed
- Skip unnecessary calculations — Use
bNoTintCalc=1when tint correction isn't needed - Use fast FAE — Pass
bFastFAE=1for faster face detection - Memory-map large files — Use
MappedByteBufferfor 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.