Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
71 changes: 71 additions & 0 deletions .github/workflows/build-aar.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
name: Build decoder AAR

on:
push:
pull_request:
workflow_dispatch:

permissions:
contents: read

concurrency:
group: decoder-aar-${{ github.head_ref || github.ref }}
cancel-in-progress: true

jobs:
build:
runs-on: ubuntu-24.04
timeout-minutes: 30
steps:
- name: Check out source
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false

- name: Set up Java 17
uses: actions/setup-java@b843ebf9b913146b38da2eaa7aeddf76074ae1ad # v6.1.0
with:
distribution: temurin
java-version: '17'
cache: gradle
cache-dependency-path: |
**/*.gradle
gradle/wrapper/gradle-wrapper.properties

- name: Set up Android SDK
uses: android-actions/setup-android@be39fa834029ff78f1a44aa3bb0819b8fc2bd8fd # v4.0.4

- name: Install Android build tools
run: >-
sdkmanager "platforms;android-36" "build-tools;35.0.0"
"ndk;23.2.8568313" "cmake;3.22.1"

- name: Build, lint, and stage the release AAR
run: >-
./gradlew --no-daemon :jp2-android:assembleRelease
:jp2-android:lintRelease :jp2-android:publishReleasePublicationToStagingRepository

- name: Record artifact checksum and source commit
run: |
sha256sum library/build/outputs/aar/jp2-android-release.aar > build/aar-sha256.txt
git rev-parse HEAD > build/source-commit.txt

- name: Upload the AAR and Maven files
uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
with:
name: jp2-android-${{ github.sha }}
if-no-files-found: error
retention-days: 30
path: |
library/build/outputs/aar/jp2-android-release.aar
build/staging-repo/
build/aar-sha256.txt
build/source-commit.txt

- name: Upload lint reports
if: ${{ !cancelled() }}
uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
with:
name: jp2-lint-${{ github.sha }}
retention-days: 30
path: library/build/reports/lint-results-release.*
108 changes: 106 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,21 +1,125 @@
# JP2 for Android
# Incode JP2 for Android
---------------------------
An open-source JPEG-2000 image encoder/decoder for Android based on [OpenJPEG](http://www.openjpeg.org/) v2.5.4.

This fork starts from [Thales JP2ForAndroid 1.1.0](https://github.com/ThalesGroup/JP2ForAndroid/tree/1.1.0), commit `8ad43abb5d8e2caf2147162d3bfe5569581b5d1f`.
It uses the Java package `com.incode.jp2` and the native library `libincode-openjpeg.so`.
The native library exports only its JNI entry points and preserves 16 KB page alignment on all four Android ABIs.

## Build the Incode artifact

Use JDK 17, Android SDK 36, NDK `23.2.8568313`, and CMake `3.22.1`.
The library supports Android API 16 and later. The test app uses API 23 and later.

```sh
./gradlew :jp2-android:assembleRelease
./gradlew :jp2-android:publishReleasePublicationToStagingRepository
```

The release AAR is `library/build/outputs/aar/jp2-android-release.aar`.
The second command creates a local Maven repository under `build/staging-repo`. It does not upload artifacts.

## Build with GitHub Actions

The **Build decoder AAR** workflow runs on pushes and pull requests.
It uses the same Java, Android SDK, NDK, and CMake versions as the local build.
It builds the release AAR, runs Android lint, and creates the Maven staging repository.

Open the workflow run in the repository's **Actions** tab.
Download `jp2-android-<commit>` from **Artifacts**.
The download contains the AAR, Maven metadata, sources JAR, SHA-256 checksum, and source commit.
After the workflow reaches the default branch, **Run workflow** also starts it manually.
If Actions is disabled for the fork, a repository administrator must enable it first.

The workflow needs no Maven credentials. It uploads build results to GitHub Actions only.
It does not publish to Nexus or GitHub Packages. Native device tests run separately with the commands below.
To publish a downloaded build, use its `build/staging-repo` directory with the publication commands below.
Published versions are immutable. If a rebuilt artifact has different bytes, publish it with a new version.

## Publish locally

Store these credentials in `~/.gradle/gradle.properties` or environment variables with the same names:

| Repository | Username key | Password key |
| --- | --- | --- |
| Nexus | `nexus_username` | `nexus_password` |
| GitHub Packages | `github_username` | `github_pat` |

The GitHub token needs `write:packages` scope and write access to `Incode-Technologies-Example-Repos/android-omni-packages`.
Keep credential values outside this repository.

Create the staging repository with the command above.
For a new version, first run `./gradlew clean` to remove earlier staged versions.
Before an upload, run both conflict checks:

Set `JP2_NEXUS_URL` to the internal Maven releases repository URL before running these commands.

```sh
bash scripts/uploadStagedRepo.sh build/staging-repo "$JP2_NEXUS_URL" nexus_username nexus_password --check-only
bash scripts/uploadStagedRepo.sh build/staging-repo https://maven.pkg.github.com/incode-technologies-example-repos/android-omni-packages github_username github_pat --check-only
```

After both checks pass, upload the same staged files to both repositories:

```sh
bash scripts/uploadStagedRepo.sh build/staging-repo "$JP2_NEXUS_URL" nexus_username nexus_password --immutable
bash scripts/uploadStagedRepo.sh build/staging-repo https://maven.pkg.github.com/incode-technologies-example-repos/android-omni-packages github_username github_pat --immutable
```

The uploader rejects existing version files with different bytes and downloads each uploaded file to verify its contents.
If an upload stops partway, rerun it with the same staging directory.
The scripts use the staged-file publication process from the Incode Android SDK.
Local publication does not require Bitrise.

## Native tests

Connect an Android device or start an emulator, then run the existing codec tests:

```sh
./gradlew :jp2-android:connectedDebugAndroidTest
./gradlew :testapp:connectedDebugAndroidTest
```

The test app also contains keiji 1.0.5 to exercise coexistence with a real second decoder.
That dependency does not ship in the release AAR or its Maven metadata.
Run the coexistence test in separate processes for each library load order:

```sh
./gradlew :testapp:connectedDebugAndroidTest -Pandroid.testInstrumentationRunnerArguments.class=com.incode.jp2.test.TestDecoderCoexistence#testBothDecodersPreservePixels -Pandroid.testInstrumentationRunnerArguments.decoderLoadOrder=incodeFirst
./gradlew :testapp:connectedDebugAndroidTest -Pandroid.testInstrumentationRunnerArguments.class=com.incode.jp2.test.TestDecoderCoexistence#testBothDecodersPreservePixels -Pandroid.testInstrumentationRunnerArguments.decoderLoadOrder=originalFirst
```

To test the release AAR as a Maven dependency, first create the local Maven repository with the publication command above.
Then add `-PcodecFromStaging=true` to either test app command.

Local verification on 2026-10-23 passed all 26 upstream codec tests and both coexistence tests on ARM64 emulators:

- Android 14 with 4 KB pages.
- Android 15 with 16 KB pages.

The staged release AAR passed both decoder loading orders on both emulators.
ELF alignment passed for all four ABIs, and the test app APK passed `zipalign -c -P 16 4`.
SDK integration tests passed with synthetic JP2, raw J2K, and JPEG portraits on a 16 KB emulator.
API 23 runtime tests and protected SDK release tests remain pending.

## Set up
Add dependency to your `build.gradle`:
```groovy
implementation 'io.github.michaldvorak-gemalto:jp2-android:1.1.0'
implementation 'com.incode.sdk:jp2-android:1.1.0-incode.1'
```

## Basic Usage
Decoding an image:
```java
import com.incode.jp2.JP2Decoder;

Bitmap bmp = new JP2Decoder(jp2data).decode();
imgView.setImageBitmap(bmp);
```
Encoding an image:
```java
import com.incode.jp2.JP2Encoder;

Bitmap bmp = ...;
//lossless encode
byte[] jp2data = new JP2Encoder(bmp).encode();
Expand Down
2 changes: 1 addition & 1 deletion build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ buildscript {
mavenCentral()
}
dependencies {
classpath 'com.android.tools.build:gradle:9.3.1'
classpath 'com.android.tools.build:gradle:8.9.1'

// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
Expand Down
2 changes: 1 addition & 1 deletion gradle/wrapper/gradle-wrapper.properties
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-9.7.0-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.5-bin.zip
networkTimeout=10000
retries=0
retryBackOffMs=500
Expand Down
13 changes: 9 additions & 4 deletions library/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -DUSE_JPIP")
# Gradle automatically packages shared libraries with your APK.

add_library( # Sets the name of the library.
openjpeg
incode-openjpeg

# Sets the library as a shared library.
SHARED
Expand Down Expand Up @@ -51,7 +51,7 @@ add_library( # Sets the name of the library.
src/main/cpp/openjpg.cpp
)

target_include_directories(openjpeg PRIVATE src/main/cpp src/main/cpp/openjpeg/src/lib/openjp2 src/main/cpp/openjpeg/src/bin/common)
target_include_directories(incode-openjpeg PRIVATE src/main/cpp src/main/cpp/openjpeg/src/lib/openjp2 src/main/cpp/openjpeg/src/bin/common)

# Searches for a specified prebuilt library and stores the path as a
# variable. Because CMake includes system libraries in the search path by
Expand All @@ -70,8 +70,13 @@ find_library( # Sets the name of the path variable.
# can link multiple libraries, such as libraries you define in this
# build script, prebuilt third-party libraries, or system libraries.

target_link_libraries(openjpeg)
target_link_libraries(incode-openjpeg ${log-lib})

# Use the following linker flags to support compiling 16 KB-aligned
# shared libraries with Android NDK version r27 or lower.
target_link_options(openjpeg PRIVATE ${log-lib} "-Wl,-z,max-page-size=16384,-z,common-page-size=16384")
target_link_options(incode-openjpeg PRIVATE
"-Wl,-z,max-page-size=16384,-z,common-page-size=16384"
"-Wl,--exclude-libs,ALL"
"-Wl,--version-script=${CMAKE_CURRENT_SOURCE_DIR}/exports.map"
)
set_property(TARGET incode-openjpeg APPEND PROPERTY LINK_DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/exports.map")
93 changes: 62 additions & 31 deletions library/build.gradle
Original file line number Diff line number Diff line change
@@ -1,16 +1,21 @@
plugins {
id "com.vanniktech.maven.publish" version "0.37.0" apply(true)
id 'com.android.library'
id 'maven-publish'
}
apply plugin: 'com.android.library'

group = 'com.incode.sdk'
version = '1.1.0-incode.1'

android {
compileSdk = 37
namespace = 'com.gemalto.jp2'
compileSdk = 36
namespace = 'com.incode.jp2'
defaultConfig {
minSdk 16
targetSdk 37
versionName "1.1.0"
versionCode 6
targetSdk 33
consumerProguardFiles 'consumer-rules.pro'
aarMetadata {
minCompileSdk = 36
}
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
externalNativeBuild {
cmake {
Expand All @@ -26,13 +31,22 @@ android {
externalNativeBuild {
cmake {
path "CMakeLists.txt"
version '3.22.1'
}
}
buildFeatures {
buildConfig = true
}
ndkVersion = '23.2.8568313'
enableKotlin = false
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
publishing {
singleVariant('release') {
withSourcesJar()
}
}
}

dependencies {
Expand All @@ -45,32 +59,49 @@ dependencies {
/*************************************************************/
/******************* PUBLISHING STUFF ************************/
/*************************************************************/
mavenPublishing {
publishToMavenCentral()
signAllPublications()
coordinates("io.github.michaldvorak-gemalto", "jp2-android", "${android.defaultConfig.versionName}")

pom {
name = "JP2ForAndroid"
description = "A JPEG-2000 image encoder/decoder for Android"
inceptionYear = "2019"
url = "https://github.com/ThalesGroup/JP2ForAndroid"
licenses {
license {
name = "The 2-Clause BSD License"
url = "https://opensource.org/licenses/BSD-2-Clause"
distribution = "repo"
publishing {
publications {
release(MavenPublication) {
groupId = project.group
artifactId = 'jp2-android'
version = project.version
afterEvaluate {
from components.release
}
}
developers {
developer {
name = "Michal Dvorak"
url = "https://github.com/michaldvorak-gemalto"
pom {
name = 'Incode JP2ForAndroid'
description = 'Incode fork of Thales JP2ForAndroid 1.1.0 with OpenJPEG 2.5.4'
inceptionYear = '2019'
url = 'https://github.com/Incode-Technologies-Example-Repos/JP2ForAndroid'
licenses {
license {
name = 'The 2-Clause BSD License'
url = 'https://opensource.org/licenses/BSD-2-Clause'
distribution = 'repo'
}
}
developers {
developer {
name = 'Michal Dvorak'
url = 'https://github.com/michaldvorak-gemalto'
}
developer {
name = 'Incode Android SDK Team'
organization = 'Incode'
organizationUrl = 'https://incode.com'
}
}
scm {
url = 'https://github.com/Incode-Technologies-Example-Repos/JP2ForAndroid'
connection = 'scm:git:https://github.com/Incode-Technologies-Example-Repos/JP2ForAndroid.git'
}
}
}
scm {
url = "https://github.com/ThalesGroup/JP2ForAndroid"
connection = "scm:git:https://github.com/ThalesGroup/JP2ForAndroid.git"
}
repositories {
maven {
name = 'staging'
url = rootProject.layout.buildDirectory.dir('staging-repo')
}
}
}
3 changes: 3 additions & 0 deletions library/consumer-rules.pro
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
-keepclasseswithmembernames,includedescriptorclasses class com.incode.jp2.** {
native <methods>;
}
12 changes: 12 additions & 0 deletions library/exports.map
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
global:
JNI_OnLoad;
Java_com_incode_jp2_JP2Decoder_decodeJP2File;
Java_com_incode_jp2_JP2Decoder_decodeJP2ByteArray;
Java_com_incode_jp2_JP2Decoder_readJP2HeaderFile;
Java_com_incode_jp2_JP2Decoder_readJP2HeaderByteArray;
Java_com_incode_jp2_JP2Encoder_encodeJP2File;
Java_com_incode_jp2_JP2Encoder_encodeJP2ByteArray;
local:
*;
};
Loading
Loading