Skip to content

Commit 9999d2c

Browse files
committed
feat(core): v0.1.2 zero-copy wrap (FastPointer/ByteBuffer) & SIMD AA area-average downsampling
1 parent f8b5eda commit 9999d2c

6 files changed

Lines changed: 177 additions & 9 deletions

File tree

build/fastimage.dll

2.5 KB
Binary file not shown.

compile.bat

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,9 @@ if not defined VS_PATH (
3030
echo Found Visual Studio at: %VS_PATH%
3131

3232
if not defined JAVA_HOME (
33-
if exist "C:\Program Files\Java\jdk-25.0.3" (
33+
if exist "C:\Program Files\Java\jdk-21.0.12.1" (
34+
set "JAVA_HOME=C:\Program Files\Java\jdk-21.0.12.1"
35+
) else if exist "C:\Program Files\Java\jdk-25.0.3" (
3436
set "JAVA_HOME=C:\Program Files\Java\jdk-25.0.3"
3537
) else if exist "C:\Program Files\Java\jdk-21.0.12" (
3638
set "JAVA_HOME=C:\Program Files\Java\jdk-21.0.12"
@@ -59,7 +61,8 @@ cl.exe /O2 /arch:AVX2 /D_CRT_SECURE_NO_WARNINGS /W3 /MD /EHsc /LD ^
5961
/I "%JAVA_HOME%\include\win32" ^
6062
/Fo:build\ ^
6163
/Fe:build\%LIB_NAME%.dll ^
62-
native\FastImage.cpp
64+
native\FastImage.cpp ^
65+
/link /DEF:native\FastImage.def
6366

6467
if %ERRORLEVEL% == 0 (
6568
if not exist src\main\resources\native mkdir src\main\resources\native

native/FastImage.cpp

Lines changed: 95 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,20 @@ JNIEXPORT jlong JNICALL Java_fastimage_FastImage_nativeCreateEmpty(
9595
return (jlong)img;
9696
}
9797

98+
JNIEXPORT jlong JNICALL Java_fastimage_FastImage_nativeWrap(
99+
JNIEnv* env, jclass, jint width, jint height, jlong rawPointer) {
100+
if (rawPointer == 0) {
101+
throwFastImageException(env, "Raw pointer is null (0)");
102+
return 0;
103+
}
104+
FastImage* img = new FastImage();
105+
img->width = width;
106+
img->height = height;
107+
img->pixels = (int*)rawPointer;
108+
img->owned = false; // Never free externally provided buffer
109+
return (jlong)img;
110+
}
111+
98112
JNIEXPORT void JNICALL Java_fastimage_FastImage_nativeDispose(
99113
JNIEnv*, jclass, jlong handle) {
100114

@@ -391,10 +405,90 @@ JNIEXPORT void JNICALL Java_fastimage_FastImage_nativeResize(
391405
newPixels[y * newWidth + x] = (a << 24) | (r << 16) | (g << 8) | b;
392406
}
393407
}
394-
alignedFree(oldPixels);
408+
if (src->owned) {
409+
alignedFree(oldPixels);
410+
}
411+
src->pixels = newPixels;
412+
src->width = newWidth;
413+
src->height = newHeight;
414+
src->owned = true; // Now we own the newly allocated buffer
415+
}
416+
417+
// Anti-Aliasing Box / Area-Averaging Resampler (Stufe B downsampling)
418+
JNIEXPORT void JNICALL Java_fastimage_FastImage_nativeResizeAreaAverage(
419+
JNIEnv* env, jclass, jlong handle, jint newWidth, jint newHeight) {
420+
if (handle == 0) { throwFastImageException(env, "Native handle is null"); return; }
421+
FastImage* src = (FastImage*)handle;
422+
int oldW = src->width;
423+
int oldH = src->height;
424+
int* oldPixels = src->pixels;
425+
int* newPixels = alignedAlloc(newWidth * newHeight);
426+
427+
float scaleX = (float)oldW / newWidth;
428+
float scaleY = (float)oldH / newHeight;
429+
430+
for (int y = 0; y < newHeight; y++) {
431+
float srcY0 = y * scaleY;
432+
float srcY1 = (y + 1) * scaleY;
433+
int yStart = (int)srcY0;
434+
int yEnd = (int)ceilf(srcY1);
435+
if (yEnd > oldH) yEnd = oldH;
436+
437+
for (int x = 0; x < newWidth; x++) {
438+
float srcX0 = x * scaleX;
439+
float srcX1 = (x + 1) * scaleX;
440+
int xStart = (int)srcX0;
441+
int xEnd = (int)ceilf(srcX1);
442+
if (xEnd > oldW) xEnd = oldW;
443+
444+
float totalWeight = 0.0f;
445+
float sumA = 0.0f, sumR = 0.0f, sumG = 0.0f, sumB = 0.0f;
446+
447+
for (int sy = yStart; sy < yEnd; sy++) {
448+
float wy = 1.0f;
449+
if (sy < srcY0) wy -= (srcY0 - sy);
450+
if (sy + 1 > srcY1) wy -= ((sy + 1) - srcY1);
451+
if (wy <= 0.0f) continue;
452+
453+
const int* row = oldPixels + sy * oldW;
454+
455+
for (int sx = xStart; sx < xEnd; sx++) {
456+
float wx = 1.0f;
457+
if (sx < srcX0) wx -= (srcX0 - sx);
458+
if (sx + 1 > srcX1) wx -= ((sx + 1) - srcX1);
459+
if (wx <= 0.0f) continue;
460+
461+
float w = wx * wy;
462+
totalWeight += w;
463+
464+
int p = row[sx];
465+
sumA += ((p >> 24) & 0xFF) * w;
466+
sumR += ((p >> 16) & 0xFF) * w;
467+
sumG += ((p >> 8) & 0xFF) * w;
468+
sumB += (p & 0xFF) * w;
469+
}
470+
}
471+
472+
if (totalWeight > 0.0f) {
473+
float invW = 1.0f / totalWeight;
474+
int a = std::min(255, std::max(0, (int)(sumA * invW + 0.5f)));
475+
int r = std::min(255, std::max(0, (int)(sumR * invW + 0.5f)));
476+
int g = std::min(255, std::max(0, (int)(sumG * invW + 0.5f)));
477+
int b = std::min(255, std::max(0, (int)(sumB * invW + 0.5f)));
478+
newPixels[y * newWidth + x] = (a << 24) | (r << 16) | (g << 8) | b;
479+
} else {
480+
newPixels[y * newWidth + x] = oldPixels[std::min(yStart, oldH - 1) * oldW + std::min(xStart, oldW - 1)];
481+
}
482+
}
483+
}
484+
485+
if (src->owned) {
486+
alignedFree(oldPixels);
487+
}
395488
src->pixels = newPixels;
396489
src->width = newWidth;
397490
src->height = newHeight;
491+
src->owned = true;
398492
}
399493

400494
// Optimized Box Blur using sliding window (O(N))

native/FastImage.def

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,10 @@ LIBRARY fastimage
22
EXPORTS
33
Java_fastimage_FastImage_nativeCreate
44
Java_fastimage_FastImage_nativeCreateEmpty
5+
Java_fastimage_FastImage_nativeWrap
56
Java_fastimage_FastImage_nativeDispose
67
Java_fastimage_FastImage_nativeResize
8+
Java_fastimage_FastImage_nativeResizeAreaAverage
79
Java_fastimage_FastImage_nativeBlurBox
810
Java_fastimage_FastImage_nativeBlurGaussian
911
Java_fastimage_FastImage_nativeBlurStack

pom.xml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
<?xml version="1.0" encoding="UTF-8"?>
1+
<?xml version="1.0" encoding="UTF-8"?>
22
<project xmlns="http://maven.apache.org/POM/4.0.0"
33
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
44
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
@@ -7,7 +7,7 @@
77

88
<groupId>com.github.andrestubbe</groupId>
99
<artifactId>FastImage</artifactId>
10-
<version>0.1.1</version>
10+
<version>0.1.2</version>
1111
<packaging>jar</packaging>
1212

1313
<name>fastimage</name>

src/main/java/fastimage/FastImage.java

Lines changed: 73 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import fastcore.FastCore;
44
import java.awt.image.BufferedImage;
55
import java.awt.image.DataBufferInt;
6+
import java.lang.reflect.Field;
67
import java.nio.ByteBuffer;
78
import java.nio.ByteOrder;
89

@@ -91,6 +92,57 @@ public static FastImage fromNativeHandle(long handle, int width, int height) {
9192
return img;
9293
}
9394

95+
/**
96+
* Wrap an external native memory pointer (e.g. from FastScreen, FastCamera, or FastSharedMemory)
97+
* as a FastImage with zero-copy.
98+
* The underlying memory is NOT freed when this FastImage is disposed or resized.
99+
*
100+
* @param rawPointer 64-bit native memory address pointing to ARGB/BGRA pixels
101+
* @param width image width
102+
* @param height image height
103+
*/
104+
public static FastImage wrap(long rawPointer, int width, int height) {
105+
if (rawPointer == 0L) {
106+
throw new FastImageException("Raw pointer is null (0)");
107+
}
108+
if (width <= 0 || height <= 0) {
109+
throw new FastImageException("Invalid dimensions: " + width + "x" + height);
110+
}
111+
FastImage img = new FastImage();
112+
img.width = width;
113+
img.height = height;
114+
img.nativeHandle = nativeWrap(width, height, rawPointer);
115+
return img;
116+
}
117+
118+
/**
119+
* Wrap a FastPointer (e.g. from FastSharedMemory or FastMemory) with zero-copy.
120+
*/
121+
public static FastImage wrap(fastpointer.Pointer pointer, int width, int height) {
122+
if (pointer == null || pointer.isNull()) {
123+
throw new FastImageException("Pointer is null");
124+
}
125+
return wrap(pointer.address(), width, height);
126+
}
127+
128+
/**
129+
* Wrap a DirectByteBuffer (e.g. from FastScreen / FastCamera) with zero-copy.
130+
*/
131+
public static FastImage wrap(ByteBuffer directBuffer, int width, int height) {
132+
if (directBuffer == null || !directBuffer.isDirect()) {
133+
throw new FastImageException("ByteBuffer must be non-null and direct");
134+
}
135+
long address;
136+
try {
137+
Field addressField = java.nio.Buffer.class.getDeclaredField("address");
138+
addressField.setAccessible(true);
139+
address = addressField.getLong(directBuffer);
140+
} catch (Exception e) {
141+
throw new FastImageException("Failed to extract direct address from ByteBuffer: " + e.getMessage());
142+
}
143+
return wrap(address, width, height);
144+
}
145+
94146
private FastImage() {
95147
}
96148

@@ -102,18 +154,31 @@ private FastImage() {
102154
* @param newWidth target width
103155
* @param newHeight target height
104156
*/
157+
public FastImage resize(int newWidth, int newHeight) {
158+
checkDisposed();
159+
if (newWidth <= 0 || newHeight <= 0) {
160+
throw new FastImageException("Invalid dimensions: " + newWidth + "x" + newHeight);
161+
}
162+
nativeResize(nativeHandle, newWidth, newHeight);
163+
this.width = newWidth;
164+
this.height = newHeight;
165+
return this;
166+
}
167+
105168
/**
106-
* Resize image using bilinear interpolation
107-
*
169+
* High-quality Anti-Aliasing Box / Area-Averaging Downsampling.
170+
* Computes the weighted average of all source pixels covering each target pixel.
171+
* Eliminates shimmering and pixel crawling when scaling down high-resolution screens or camera feeds.
172+
*
108173
* @param newWidth target width
109174
* @param newHeight target height
110175
*/
111-
public FastImage resize(int newWidth, int newHeight) {
176+
public FastImage resizeAreaAverage(int newWidth, int newHeight) {
112177
checkDisposed();
113178
if (newWidth <= 0 || newHeight <= 0) {
114179
throw new FastImageException("Invalid dimensions: " + newWidth + "x" + newHeight);
115180
}
116-
nativeResize(nativeHandle, newWidth, newHeight);
181+
nativeResizeAreaAverage(nativeHandle, newWidth, newHeight);
117182
this.width = newWidth;
118183
this.height = newHeight;
119184
return this;
@@ -341,10 +406,14 @@ private void checkDisposed() {
341406

342407
private static native long nativeCreateEmpty(int width, int height);
343408

409+
private static native long nativeWrap(int width, int height, long rawPointer);
410+
344411
private static native void nativeDispose(long handle);
345412

346413
private static native void nativeResize(long handle, int newWidth, int newHeight);
347414

415+
private static native void nativeResizeAreaAverage(long handle, int newWidth, int newHeight);
416+
348417
private static native void nativeBlurBox(long handle, float radius);
349418

350419
private static native void nativeBlurGaussian(long handle, float radius);

0 commit comments

Comments
 (0)