/fpng-java

Java Wrapper for the fast, native FPNG Encoder

Primary LanguageC++GNU Affero General Public License v3.0AGPL-3.0

Java Wrapper for the fast, native FPNG Encoder and the AVX optimized native FPNGe Encoder. Contains 64 bit binaries for Windows, Linux and MacOS, built and tested on GitHub Runners. Unfortunately, MacOS ARM64 on Apple Silicon is not supported yet.

An additional SSE translation from Java's ABGR into the expected RGBA arrays is provided. (The AVX version has been tested to be slower).

License: GNU Affero General Public License, Version 3 or later.

C++ Java Linux macOS Windows Git

How to use it

Maven and Gradle artifacts are available, please see below.

import com.manticore.tools.FPNGEncoder;     // when using the SSE version
import com.manticore.tools.FPNGE;           // when using the AVX version

FPNGEncoder.encode(bufferedImage, 3, 0);    // encoding with 3 channels and fastest compression

FPNGE.encode(bufferedImage, 3, 5);          // encoding with 3 channels and best compression

There are 7 projects included:

  • encoder is an abstract base class for loading the native libraries, byte arrays and tests
  • fpng is the C++ source from FPNG with an additional C wrapper
  • fpng-java provides the Java FPNGE encoder, depending on fpng and JNA
  • fpnge is the AVX optimized C++ source from FPNGe with an additional C wrapper
  • fpnge-java provides the Java FPNGEncoder, depending on fpnge and JNA
  • benchmark are optional JMH based performance tests
  • maven-test as a most simple Java project stub for testing the Maven dependencies and the Native Libs on various OS after publishing.

The following Gradle task will compile FPNG with -O3 -march=x86-64 -mtune=generic and wrap it into a JAR via JNA.

git clone --depth 1 https://github.com/manticore-projects/fpng-java.git
cd fpng-java
gradle clean assemble

The artifacts will be written to: .fpng-java/build/libs/fpng-java-1.2.0-SNAPSHOT.jar and .fpnge-java/build/libs/fpnge-java-1.2.0-SNAPSHOT.jar

Benchmarks

There is a JMH based benchmark suite comparing other Java PNG Encoders, using one small and one very large PNG:

gradle clean assemble jmh
# JMH version: 1.37, Blackhole mode: compiler
# VM version: JDK 21.0.1, OpenJDK 64-Bit Server VM, 21.0.1+12-jvmci-23.1-b19 -XX:+UseSerialGC -Xms512M -Xmx2G -XX:+UseStringDeduplication

Benchmark                               (channels)              (imageName)  Mode  Cnt     Score    Error  Units
FPNGEBenchmark.encode                            3              example.png  avgt   10     1.993 ±  0.008  ms/op
FPNGEBenchmark.encode                            3  looklet-look-scale6.png  avgt   10    84.272 ±  0.860  ms/op
FPNGEBenchmark.encode                            4              example.png  avgt   10     2.976 ±  0.014  ms/op
FPNGEBenchmark.encode                            4  looklet-look-scale6.png  avgt   10   126.364 ± 11.213  ms/op
FPNGEncoderBenchmark.encode                      3              example.png  avgt   10     5.057 ±  0.046  ms/op
FPNGEncoderBenchmark.encode                      3  looklet-look-scale6.png  avgt   10   196.432 ±  0.836  ms/op
FPNGEncoderBenchmark.encode                      4              example.png  avgt   10     6.434 ±  0.054  ms/op
FPNGEncoderBenchmark.encode                      4  looklet-look-scale6.png  avgt   10   268.631 ± 12.623  ms/op
ImageIOEncoderBenchmark.encode                   3              example.png  avgt   10    54.189 ±  0.422  ms/op
ImageIOEncoderBenchmark.encode                   3  looklet-look-scale6.png  avgt   10  1099.045 ± 13.667  ms/op
ImageIOEncoderBenchmark.encode                   4              example.png  avgt   10    64.538 ±  0.605  ms/op
ImageIOEncoderBenchmark.encode                   4  looklet-look-scale6.png  avgt   10  1270.494 ±  6.797  ms/op
ObjectPlanetPNGEncoderBenchmark.encode           3              example.png  avgt   10    43.910 ±  0.248  ms/op
ObjectPlanetPNGEncoderBenchmark.encode           3  looklet-look-scale6.png  avgt   10  1491.651 ±  8.445  ms/op
ObjectPlanetPNGEncoderBenchmark.encode           4              example.png  avgt   10    53.449 ±  0.279  ms/op
ObjectPlanetPNGEncoderBenchmark.encode           4  looklet-look-scale6.png  avgt   10  1776.148 ±  7.182  ms/op
PNGEncoderBenchmark.encode                       3              example.png  avgt   10    39.813 ±  0.517  ms/op
PNGEncoderBenchmark.encode                       3  looklet-look-scale6.png  avgt   10   854.307 ±  2.627  ms/op
PNGEncoderBenchmark.encode                       4              example.png  avgt   10    44.704 ±  0.252  ms/op
PNGEncoderBenchmark.encode                       4  looklet-look-scale6.png  avgt   10  1120.410 ±  6.746  ms/op
PNGEncoderBenchmark.encodeFastest                3              example.png  avgt   10    23.990 ±  0.173  ms/op
PNGEncoderBenchmark.encodeFastest                3  looklet-look-scale6.png  avgt   10   311.706 ±  1.456  ms/op
PNGEncoderBenchmark.encodeFastest                4              example.png  avgt   10    28.218 ±  0.202  ms/op
PNGEncoderBenchmark.encodeFastest                4  looklet-look-scale6.png  avgt   10   428.018 ±  5.281  ms/op

Small Image Benchmark Results Large Image Benchmark Results Remark: Score in milli-seconds per encoding, smaller is better.

The compression rates were set to MEDIUM for achieving comparable file-sizes. The Java Encoders are able to achieve better compression rates at an even higher performance penalty. FPNG SSE an FPNGe AVX achieve very competitive file-sizes for smaller images but fall-off considerably for the very large image (1.5x the size of ImageIO). Please see details in the Benchmark Spreadsheet.

Maven Artifacts

<repositories>
    <!-- Only needed when using the FPNG SSE2 Encoder -->
    <repository>
        <id>sonatype-snapshots</id>
        <snapshots>
            <enabled>true</enabled>
        </snapshots>
        <url>https://s01.oss.sonatype.org/content/repositories/snapshots</url>
    </repository>
</repositories>
<dependencies>
    <!-- Only needed when using the FPNG SSE2 Encoder -->
    <dependency>
        <groupId>com.manticore-projects.tools</groupId>
        <artifactId>fpng-java</artifactId>
        <version>1.2.0</version>
    </dependency>
    <!-- Only needed when using the FPNGe AVX2 Encoder -->
    <dependency>
        <groupId>com.manticore-projects.tools</groupId>
        <artifactId>fpnge-java</artifactId>
        <version>1.2.0</version>
    </dependency>
</dependencies>

Gradle Artifacts

repositories {
    mavenCentral()
    maven {
        url = uri('https://s01.oss.sonatype.org/content/repositories/releases/')
    }
    // Only needed when loading Snapshots
    maven {
        url = uri('https://s01.oss.sonatype.org/content/repositories/snapshots/')
    }
}
dependencies {
    // Only needed when using the FPNG SSE2 Encoder
    implementation 'com.manticore-projects.tools:fpng-java:+'
    // Only needed when using the FPNGe AVX2 Encoder
    implementation 'com.manticore-projects.tools:fpnge-java:+'
}

To Do

  • Add more test images for the "screen capturing" use case, which may yield different outcomes. Right now only photo-realistic images are tested.
  • Drop slow JNA and replace with a JNI implementation.
  • Try profiling with PGO.