diff --git a/bun.lock b/bun.lock
index b4645c03..f9f2d22f 100644
--- a/bun.lock
+++ b/bun.lock
@@ -30,6 +30,7 @@
"react-native-fast-image": "^8.6.3",
"react-native-harness": "^1.3.0",
"react-native-nitro-image": "workspace:*",
+ "react-native-nitro-image-blurhash": "workspace:*",
"react-native-nitro-modules": "0.35.9",
"react-native-nitro-web-image": "workspace:*",
"react-native-safe-area-context": "^5.8.0",
@@ -69,6 +70,24 @@
"react-native-nitro-modules": "*",
},
},
+ "packages/react-native-nitro-image-blurhash": {
+ "name": "react-native-nitro-image-blurhash",
+ "version": "0.0.1",
+ "devDependencies": {
+ "@types/react": "^19.2.15",
+ "nitrogen": "0.35.9",
+ "react": "19.2.3",
+ "react-native": "0.85.3",
+ "react-native-nitro-modules": "0.35.9",
+ "typescript": "6.0.3",
+ },
+ "peerDependencies": {
+ "react": "*",
+ "react-native": "*",
+ "react-native-nitro-image": "*",
+ "react-native-nitro-modules": "*",
+ },
+ },
"packages/react-native-nitro-web-image": {
"name": "react-native-nitro-web-image",
"version": "0.15.0",
@@ -1723,6 +1742,8 @@
"react-native-nitro-image": ["react-native-nitro-image@workspace:packages/react-native-nitro-image"],
+ "react-native-nitro-image-blurhash": ["react-native-nitro-image-blurhash@workspace:packages/react-native-nitro-image-blurhash"],
+
"react-native-nitro-modules": ["react-native-nitro-modules@0.35.9", "", { "peerDependencies": { "react": "*", "react-native": "*" } }, "sha512-yCO6eJ85SPPUo4a4an7H5oj6wPCSIT72fbjr5WZ/20n6zswaJ2gNNpnWtg2We0AZwkAOjSqkOJ0Vjc05p6kGiA=="],
"react-native-nitro-web-image": ["react-native-nitro-web-image@workspace:packages/react-native-nitro-web-image"],
diff --git a/example/__tests__/blurhash.harness.ts b/example/__tests__/blurhash.harness.ts
new file mode 100644
index 00000000..2b9f275b
--- /dev/null
+++ b/example/__tests__/blurhash.harness.ts
@@ -0,0 +1,103 @@
+import { describe, expect, it } from 'react-native-harness'
+import { Images } from 'react-native-nitro-image'
+import {
+ BlurHash,
+ getAverageColor,
+ isBlurhashValid,
+} from 'react-native-nitro-image-blurhash'
+
+const RED = { r: 1, g: 0, b: 0, a: 1 }
+const BLUE = { r: 0, g: 0, b: 1, a: 1 }
+
+describe('BlurHash - round-trip', () => {
+ it('encodes a small image to a BlurHash string', () => {
+ const source = Images.createBlankImage(64, 64, true, BLUE).resize(32, 32)
+ const hash = BlurHash.encode(source, 4, 3)
+ expect(typeof hash).toBe('string')
+ expect(hash.length).toBeGreaterThan(0)
+ expect(isBlurhashValid(hash)).toEqual({ isValid: true })
+ })
+
+ it('decode produces an Image of requested dimensions', () => {
+ const source = Images.createBlankImage(64, 64, true, BLUE).resize(32, 32)
+ const hash = BlurHash.encode(source, 4, 3)
+ const decoded = BlurHash.decode(hash, 16, 24, 1)
+ expect(decoded.width).toBe(16)
+ expect(decoded.height).toBe(24)
+ })
+
+ it('async variants round-trip encode + decode', async () => {
+ const source = Images.createBlankImage(64, 64, true, RED).resize(32, 32)
+ const hash = await BlurHash.encodeAsync(source, 4, 3)
+ const decoded = await BlurHash.decodeAsync(hash, 32, 32, 1)
+ expect(decoded.width).toBe(32)
+ expect(decoded.height).toBe(32)
+ })
+
+ it('throws when decoding an invalid hash', () => {
+ expect(() => BlurHash.decode('bad', 16, 16, 1)).toThrow()
+ })
+})
+
+describe('isBlurhashValid', () => {
+ it('returns isValid:true for a freshly produced hash', () => {
+ const source = Images.createBlankImage(64, 64, true, BLUE).resize(32, 32)
+ const hash = BlurHash.encode(source, 4, 3)
+ expect(isBlurhashValid(hash)).toEqual({ isValid: true })
+ })
+
+ it('rejects a malformed hash with an errorReason', () => {
+ const result = isBlurhashValid('bad')
+ expect(result.isValid).toBe(false)
+ if (!result.isValid) {
+ expect(result.errorReason.length).toBeGreaterThan(0)
+ }
+ })
+})
+
+describe('getAverageColor', () => {
+ it('returns r/g/b channels in [0, 1] for a valid hash', () => {
+ const source = Images.createBlankImage(64, 64, true, BLUE).resize(32, 32)
+ const hash = BlurHash.encode(source, 4, 3)
+ const color = getAverageColor(hash)
+ expect(color).toBeDefined()
+ if (color != null) {
+ for (const channel of [color.r, color.g, color.b]) {
+ expect(channel).toBeGreaterThanOrEqual(0)
+ expect(channel).toBeLessThanOrEqual(1)
+ }
+ }
+ })
+
+ it('returns undefined for a too-short hash', () => {
+ expect(getAverageColor('short')).toBeUndefined()
+ })
+
+ it('reflects the source image: blue source is blue-dominant', () => {
+ const source = Images.createBlankImage(64, 64, true, BLUE).resize(32, 32)
+ const hash = BlurHash.encode(source, 4, 3)
+ const color = getAverageColor(hash)
+ expect(color).toBeDefined()
+ if (color != null) {
+ expect(color.b).toBeGreaterThan(color.r)
+ expect(color.b).toBeGreaterThan(color.g)
+ }
+ })
+
+ it('reflects the source image: red source is red-dominant', () => {
+ const source = Images.createBlankImage(64, 64, true, RED).resize(32, 32)
+ const hash = BlurHash.encode(source, 4, 3)
+ const color = getAverageColor(hash)
+ expect(color).toBeDefined()
+ if (color != null) {
+ expect(color.r).toBeGreaterThan(color.g)
+ expect(color.r).toBeGreaterThan(color.b)
+ }
+ })
+})
+
+describe('BlurHash.clearCosineCache', () => {
+ it('does not throw', () => {
+ expect(() => BlurHash.clearCosineCache()).not.toThrow()
+ })
+})
diff --git a/example/ios/Podfile.lock b/example/ios/Podfile.lock
index e2a9e070..0f07f18a 100644
--- a/example/ios/Podfile.lock
+++ b/example/ios/Podfile.lock
@@ -61,6 +61,31 @@ PODS:
- ReactCommon/turbomodule/core
- ReactNativeDependencies
- Yoga
+ - NitroImageBlurhash (0.0.1):
+ - hermes-engine
+ - NitroImage
+ - NitroModules
+ - RCTRequired
+ - RCTTypeSafety
+ - React-callinvoker
+ - React-Core
+ - React-Core-prebuilt
+ - React-debug
+ - React-Fabric
+ - React-featureflags
+ - React-graphics
+ - React-ImageManager
+ - React-jsi
+ - React-NativeModulesApple
+ - React-RCTFabric
+ - React-renderercss
+ - React-rendererdebug
+ - React-utils
+ - ReactCodegen
+ - ReactCommon/turbomodule/bridging
+ - ReactCommon/turbomodule/core
+ - ReactNativeDependencies
+ - Yoga
- NitroModules (0.35.9):
- hermes-engine
- RCTRequired
@@ -2027,6 +2052,7 @@ DEPENDENCIES:
- "HarnessUI (from `../../node_modules/@react-native-harness/ui`)"
- hermes-engine (from `../../node_modules/react-native/sdks/hermes-engine/hermes-engine.podspec`)
- NitroImage (from `../../node_modules/react-native-nitro-image`)
+ - NitroImageBlurhash (from `../../node_modules/react-native-nitro-image-blurhash`)
- NitroModules (from `../../node_modules/react-native-nitro-modules`)
- NitroWebImage (from `../../node_modules/react-native-nitro-web-image`)
- RCTDeprecation (from `../../node_modules/react-native/ReactApple/Libraries/RCTFoundation/RCTDeprecation`)
@@ -2123,6 +2149,8 @@ EXTERNAL SOURCES:
:tag: hermes-v250829098.0.10
NitroImage:
:path: "../../node_modules/react-native-nitro-image"
+ NitroImageBlurhash:
+ :path: "../../node_modules/react-native-nitro-image-blurhash"
NitroModules:
:path: "../../node_modules/react-native-nitro-modules"
NitroWebImage:
@@ -2281,9 +2309,10 @@ EXTERNAL SOURCES:
SPEC CHECKSUMS:
FBLazyVector: 24e62c765683b8d89006a88a2c8f5cf019f0074d
HarnessUI: f439596aec93ff76765451f834c93f966d54e53e
- hermes-engine: ef561c35b1325a953b54456ddbd27ee0faf01ba4
+ hermes-engine: b0d99d8b69831141e3f5630b66c979d5541cc253
libwebp: 02b23773aedb6ff1fd38cec7a77b81414c6842a8
NitroImage: 33c4698f86b081bd3d0245223f34ea561b0934ea
+ NitroImageBlurhash: c12c919e9172630bf7e034887f620ff445a9ac71
NitroModules: 16bc17a076b12304d608f7c915b9d321f56dfc19
NitroWebImage: 462cc2cb709258a24565ea545524c30d135f0c5a
RCTDeprecation: a4c521821fab57cbb125b36effe84d897d0dfa12
@@ -2294,7 +2323,7 @@ SPEC CHECKSUMS:
React: e2dc35338068bbd299c66f043ae0d7f25de8499e
React-callinvoker: 28b25d21b124c26cebaea713ba7d801b9351dc48
React-Core: 02ed7d2ffb70437bdf2aba074a13078a7b0b9ff0
- React-Core-prebuilt: 5babb62ae79bf6d5e6444270747410c815d25442
+ React-Core-prebuilt: 982bcd2eaa2ba6f3b96bb0d870b5bbd5478d21b8
React-CoreModules: b3a5a42dadcde3b5d47b325bd912eb2ced89e146
React-cxxreact: fe8f88dda044e5905e99a00f41b7a874c3908716
React-debug: 92944dc4d89f56d640e75498266cbde557a48189
@@ -2357,7 +2386,7 @@ SPEC CHECKSUMS:
ReactAppDependencyProvider: 25c9c516839be2c5e3d3344f95dc7da5f7e63fc2
ReactCodegen: e5d55b301414d3cb48738d6630b434e59cf5cc57
ReactCommon: 7dfc3250793bf36cf221096ff59e1179e13eef7f
- ReactNativeDependencies: 86f567dc5edd6f71876342f497cbdc0c1edac579
+ ReactNativeDependencies: a1701c87c7ddc647e01b8232332236bb022f2b56
RNFastImage: 462a183c4b0b6b26fdfd639e1ed6ba37536c3b87
RNScreens: 991cc417cd396602a6cf59a42139e5a9d91462a9
SDWebImage: a7f831e1a65eb5e285e3fb046a23fcfbf08e696d
diff --git a/example/package.json b/example/package.json
index 30fd998b..e9942cb6 100644
--- a/example/package.json
+++ b/example/package.json
@@ -19,6 +19,7 @@
"react-native-fast-image": "^8.6.3",
"react-native-harness": "^1.3.0",
"react-native-nitro-image": "workspace:*",
+ "react-native-nitro-image-blurhash": "workspace:*",
"react-native-nitro-modules": "0.35.9",
"react-native-nitro-web-image": "workspace:*",
"react-native-safe-area-context": "^5.8.0",
diff --git a/package.json b/package.json
index 631716f5..73b413e7 100644
--- a/package.json
+++ b/package.json
@@ -7,6 +7,7 @@
"workspaces": [
"packages/react-native-nitro-image",
"packages/react-native-nitro-web-image",
+ "packages/react-native-nitro-image-blurhash",
"example"
],
"overrides": {
@@ -14,8 +15,8 @@
"@babel/plugin-transform-modules-systemjs": "^7.29.7"
},
"scripts": {
- "build": "bun run --cwd packages/react-native-nitro-image build && bun run --cwd packages/react-native-nitro-web-image build",
- "specs": "bun run build && bun run --cwd packages/react-native-nitro-image specs && bun run --cwd packages/react-native-nitro-web-image specs",
+ "build": "bun run --cwd packages/react-native-nitro-image build && bun run --cwd packages/react-native-nitro-web-image build && bun run --cwd packages/react-native-nitro-image-blurhash build",
+ "specs": "bun run build && bun run --cwd packages/react-native-nitro-image specs && bun run --cwd packages/react-native-nitro-web-image specs && bun run --cwd packages/react-native-nitro-image-blurhash specs",
"bootstrap": "bun i && bun run build && cd example && bundle install && bun pods",
"typecheck": "bun --filter=\"**\" typecheck",
"lint": "biome check --write",
@@ -24,6 +25,7 @@
"release": "./scripts/release.sh",
"image": "bun --cwd packages/react-native-nitro-image",
"web-image": "bun --cwd packages/react-native-nitro-web-image",
+ "blurhash": "bun --cwd packages/react-native-nitro-image-blurhash",
"example": "bun --cwd example"
},
"devDependencies": {
@@ -67,6 +69,10 @@
"file": "packages/react-native-nitro-web-image/package.json",
"path": "version"
},
+ {
+ "file": "packages/react-native-nitro-image-blurhash/package.json",
+ "path": "version"
+ },
{
"file": "example/package.json",
"path": "version"
diff --git a/packages/react-native-nitro-image-blurhash/NitroImageBlurhash.podspec b/packages/react-native-nitro-image-blurhash/NitroImageBlurhash.podspec
new file mode 100644
index 00000000..fab18c96
--- /dev/null
+++ b/packages/react-native-nitro-image-blurhash/NitroImageBlurhash.podspec
@@ -0,0 +1,32 @@
+require "json"
+
+package = JSON.parse(File.read(File.join(__dir__, "package.json")))
+
+Pod::Spec.new do |s|
+ s.name = "NitroImageBlurhash"
+ s.version = package["version"]
+ s.summary = package["description"]
+ s.homepage = package["homepage"]
+ s.license = package["license"]
+ s.authors = package["author"]
+
+ s.platforms = { :ios => min_ios_version_supported, :visionos => 1.0 }
+ s.source = { :git => "https://github.com/mrousavy/nitro.git", :tag => "#{s.version}" }
+
+ s.source_files = [
+ # Implementation (Swift)
+ "ios/**/*.{swift}",
+ # Autolinking/Registration (Objective-C++)
+ "ios/**/*.{m,mm}",
+ # Implementation (C++ objects)
+ "cpp/**/*.{hpp,cpp}",
+ ]
+
+ load 'nitrogen/generated/ios/NitroImageBlurhash+autolinking.rb'
+ add_nitrogen_files(s)
+
+ s.dependency 'React-jsi'
+ s.dependency 'React-callinvoker'
+ s.dependency 'NitroImage'
+ install_modules_dependencies(s)
+end
diff --git a/packages/react-native-nitro-image-blurhash/README.md b/packages/react-native-nitro-image-blurhash/README.md
new file mode 100644
index 00000000..7941c6f4
--- /dev/null
+++ b/packages/react-native-nitro-image-blurhash/README.md
@@ -0,0 +1,38 @@
+# react-native-nitro-template
+
+This is a template for Nitro Modules.
+
+## Usage
+
+Clone this repo, and change all `$$*$$` names according to your `nitro.json` file.
+
+## Contributing
+
+Contribute a change to this template to update it for newer React Native versions.
+
+## Structure
+
+- [`android/`](android): All your `android`-specific implementations.
+ - [`build.gradle`](android/build.gradle): The gradle build file. This contains four important pieces:
+ 1. Standard react-native library boilerplate code
+ 2. Configures Kotlin (`apply plugin: 'org.jetbrains.kotlin.android'`)
+ 3. Adds all Nitrogen files (`apply from: '.../NitroImageBlurhash+autolinking.gradle'`)
+ 4. Triggers the native C++ build (via CMake/`externalNativeBuild`)
+ - [`CMakeLists.txt`](android/CMakeLists.txt): The CMake build file to build C++ code. This contains four important pieces:
+ 1. Creates a library called `NitroImageBlurhash` (same as in `nitro.json`)
+ 2. Adds all Nitrogen files (`include(.../NitroImageBlurhash+autolinking.cmake)`)
+ 3. Adds all custom C++ files (only `HybridTestObjectCpp.cpp`)
+ 4. Adds a `cpp-adapter.cpp` file, which autolinks all C++ HybridObjects (only `HybridTestObjectCpp`)
+ - [`src/main/java/com/margelo/nitro/nitroimageblurhash/`](android/src/main/java/com/margelo/nitro/nitroimageblurhash/): All Kotlin implementations.
+ - [`NitroImageBlurhashPackage.kt`](android/src/main/java/com/margelo/nitro/nitroimageblurhash/NitroImageBlurhashPackage.kt): The react-native package. You need this because the react-native CLI only adds libraries if they have a `*Package.kt` file. In here, you can autolink all Kotlin HybridObjects.
+- [`cpp/`](cpp): All your cross-platform implementations. (only `HybridTestObjectCpp.cpp`)
+- [`ios/`](ios): All your iOS-specific implementations.
+- [`nitrogen/`](nitrogen): All files generated by nitrogen. You should commit this folder to git.
+- [`src/`](src): The TypeScript codebase. This defines all HybridObjects and loads them at runtime.
+ - [`specs/`](src/specs): All HybridObject types. Nitrogen will run on all `*.nitro.ts` files.
+- [`nitro.json`](nitro.json): The configuration file for nitrogen. This will define all native namespaces, as well as the library name.
+- [`NitroImageBlurhash.podspec`](NitroImageBlurhash.podspec): The iOS podspec build file to build the iOS code. This contains three important pieces:
+ 1. Specifies the Pod's name. This must be identical to the name specified in `nitro.json`.
+ 2. Adds all of your `.swift` or `.cpp` files (implementations).
+ 3. Adds all Nitrogen files (`add_nitrogen_files(s)`)
+- [`package.json`](package.json): The npm package.json file. `react-native-nitro-modules` should be a `peerDependency`.
diff --git a/packages/react-native-nitro-image-blurhash/android/CMakeLists.txt b/packages/react-native-nitro-image-blurhash/android/CMakeLists.txt
new file mode 100644
index 00000000..ea0cbd70
--- /dev/null
+++ b/packages/react-native-nitro-image-blurhash/android/CMakeLists.txt
@@ -0,0 +1,31 @@
+project(NitroImageBlurhash)
+cmake_minimum_required(VERSION 3.9.0)
+
+set (PACKAGE_NAME NitroImageBlurhash)
+set (CMAKE_VERBOSE_MAKEFILE ON)
+set (CMAKE_CXX_STANDARD 20)
+
+# Define C++ library and add all sources
+add_library(${PACKAGE_NAME} SHARED
+ src/main/cpp/cpp-adapter.cpp
+)
+
+# Add Nitrogen specs :)
+include(${CMAKE_SOURCE_DIR}/../nitrogen/generated/android/NitroImageBlurhash+autolinking.cmake)
+
+# Set up local includes
+include_directories(
+ "src/main/cpp"
+ "../cpp"
+)
+
+find_library(LOG_LIB log)
+find_package(react-native-nitro-image REQUIRED) # <-- for the HybridImage type
+
+# Link all libraries together
+target_link_libraries(
+ ${PACKAGE_NAME}
+ ${LOG_LIB}
+ android # <-- Android core
+ react-native-nitro-image::NitroImage # <-- NitroImage
+)
diff --git a/packages/react-native-nitro-image-blurhash/android/build.gradle b/packages/react-native-nitro-image-blurhash/android/build.gradle
new file mode 100644
index 00000000..8810fdea
--- /dev/null
+++ b/packages/react-native-nitro-image-blurhash/android/build.gradle
@@ -0,0 +1,144 @@
+buildscript {
+ repositories {
+ google()
+ mavenCentral()
+ }
+
+ dependencies {
+ classpath "com.android.tools.build:gradle:8.12.0"
+ }
+}
+
+def reactNativeArchitectures() {
+ def value = rootProject.getProperties().get("reactNativeArchitectures")
+ return value ? value.split(",") : ["armeabi-v7a", "x86", "x86_64", "arm64-v8a"]
+}
+
+def isNewArchitectureEnabled() {
+ return rootProject.hasProperty("newArchEnabled") && rootProject.getProperty("newArchEnabled") == "true"
+}
+
+apply plugin: "com.android.library"
+apply plugin: 'org.jetbrains.kotlin.android'
+apply from: '../nitrogen/generated/android/NitroImageBlurhash+autolinking.gradle'
+apply from: "./fix-prefab.gradle"
+
+if (isNewArchitectureEnabled()) {
+ apply plugin: "com.facebook.react"
+}
+
+def getExtOrDefault(name) {
+ return rootProject.ext.has(name) ? rootProject.ext.get(name) : project.properties["NitroImageBlurhash_" + name]
+}
+
+def getExtOrIntegerDefault(name) {
+ return rootProject.ext.has(name) ? rootProject.ext.get(name) : (project.properties["NitroImageBlurhash_" + name]).toInteger()
+}
+
+android {
+ namespace "com.margelo.nitro.image.blurhash"
+
+ ndkVersion getExtOrDefault("ndkVersion")
+ compileSdkVersion getExtOrIntegerDefault("compileSdkVersion")
+
+ defaultConfig {
+ minSdkVersion getExtOrIntegerDefault("minSdkVersion")
+ targetSdkVersion getExtOrIntegerDefault("targetSdkVersion")
+ buildConfigField "boolean", "IS_NEW_ARCHITECTURE_ENABLED", isNewArchitectureEnabled().toString()
+
+ externalNativeBuild {
+ cmake {
+ cppFlags "-frtti -fexceptions -Wall -Wextra -fstack-protector-all"
+ arguments "-DANDROID_STL=c++_shared", "-DANDROID_SUPPORT_FLEXIBLE_PAGE_SIZES=ON"
+ abiFilters (*reactNativeArchitectures())
+
+ buildTypes {
+ debug {
+ cppFlags "-O1 -g"
+ }
+ release {
+ cppFlags "-O2"
+ }
+ }
+ }
+ }
+ }
+
+ externalNativeBuild {
+ cmake {
+ path "CMakeLists.txt"
+ }
+ }
+
+ packagingOptions {
+ excludes = [
+ "META-INF",
+ "META-INF/**",
+ "**/libc++_shared.so",
+ "**/libNitroModules.so",
+ "**/libfbjni.so",
+ "**/libjsi.so",
+ "**/libfolly_json.so",
+ "**/libfolly_runtime.so",
+ "**/libglog.so",
+ "**/libhermes.so",
+ "**/libhermes-executor-debug.so",
+ "**/libhermes_executor.so",
+ "**/libreactnative.so",
+ "**/libreactnativejni.so",
+ "**/libturbomodulejsijni.so",
+ "**/libreact_nativemodule_core.so",
+ "**/libjscexecutor.so"
+ ]
+ }
+
+ buildFeatures {
+ buildConfig true
+ prefab true
+ }
+
+ buildTypes {
+ release {
+ minifyEnabled false
+ }
+ }
+
+ lintOptions {
+ disable "GradleCompatible"
+ }
+
+ compileOptions {
+ sourceCompatibility JavaVersion.VERSION_1_8
+ targetCompatibility JavaVersion.VERSION_1_8
+ }
+
+ sourceSets {
+ main {
+ if (isNewArchitectureEnabled()) {
+ java.srcDirs += [
+ // React Codegen files
+ "${project.buildDir}/generated/source/codegen/java"
+ ]
+ }
+ }
+ }
+}
+
+repositories {
+ mavenCentral()
+ google()
+}
+
+
+dependencies {
+ // For < 0.71, this will be from the local maven repo
+ // For > 0.71, this will be replaced by `com.facebook.react:react-android:$version` by react gradle plugin
+ //noinspection GradleDynamicVersion
+ implementation "com.facebook.react:react-native:+"
+
+ // Add a dependency on NitroModules
+ implementation project(":react-native-nitro-modules")
+ // Add a dependency on NitroImage
+ implementation project(":react-native-nitro-image")
+}
+
diff --git a/packages/react-native-nitro-image-blurhash/android/fix-prefab.gradle b/packages/react-native-nitro-image-blurhash/android/fix-prefab.gradle
new file mode 100644
index 00000000..d6c010ef
--- /dev/null
+++ b/packages/react-native-nitro-image-blurhash/android/fix-prefab.gradle
@@ -0,0 +1,51 @@
+tasks.configureEach { task ->
+ // Make sure that we generate our prefab publication file only after having built the native library
+ // so that not a header publication file, but a full configuration publication will be generated, which
+ // will include the .so file
+
+ def prefabConfigurePattern = ~/^prefab(.+)ConfigurePackage$/
+ def matcher = task.name =~ prefabConfigurePattern
+ if (matcher.matches()) {
+ def variantName = matcher[0][1]
+ task.outputs.upToDateWhen { false }
+ task.dependsOn("externalNativeBuild${variantName}")
+ }
+}
+
+afterEvaluate {
+ def abis = reactNativeArchitectures()
+ rootProject.allprojects.each { proj ->
+ if (proj === rootProject) return
+
+ def dependsOnThisLib = proj.configurations.findAll { it.canBeResolved }.any { config ->
+ config.dependencies.any { dep ->
+ dep.group == project.group && dep.name == project.name
+ }
+ }
+ if (!dependsOnThisLib && proj != project) return
+
+ if (!proj.plugins.hasPlugin('com.android.application') && !proj.plugins.hasPlugin('com.android.library')) {
+ return
+ }
+
+ def variants = proj.android.hasProperty('applicationVariants') ? proj.android.applicationVariants : proj.android.libraryVariants
+ // Touch the prefab_config.json files to ensure that in ExternalNativeJsonGenerator.kt we will re-trigger the prefab CLI to
+ // generate a libnameConfig.cmake file that will contain our native library (.so).
+ // See this condition: https://cs.android.com/android-studio/platform/tools/base/+/mirror-goog-studio-main:build-system/gradle-core/src/main/java/com/android/build/gradle/tasks/ExternalNativeJsonGenerator.kt;l=207-219?q=createPrefabBuildSystemGlue
+ variants.all { variant ->
+ def variantName = variant.name
+ abis.each { abi ->
+ def searchDir = new File(proj.projectDir, ".cxx/${variantName}")
+ if (!searchDir.exists()) return
+ def matches = []
+ searchDir.eachDir { randomDir ->
+ def prefabFile = new File(randomDir, "${abi}/prefab_config.json")
+ if (prefabFile.exists()) matches << prefabFile
+ }
+ matches.each { prefabConfig ->
+ prefabConfig.setLastModified(System.currentTimeMillis())
+ }
+ }
+ }
+ }
+}
diff --git a/packages/react-native-nitro-image-blurhash/android/gradle.properties b/packages/react-native-nitro-image-blurhash/android/gradle.properties
new file mode 100644
index 00000000..fa50ead3
--- /dev/null
+++ b/packages/react-native-nitro-image-blurhash/android/gradle.properties
@@ -0,0 +1,5 @@
+NitroImageBlurhash_kotlinVersion=2.1.20
+NitroImageBlurhash_minSdkVersion=23
+NitroImageBlurhash_targetSdkVersion=36
+NitroImageBlurhash_compileSdkVersion=36
+NitroImageBlurhash_ndkVersion=27.1.12297006
diff --git a/packages/react-native-nitro-image-blurhash/android/src/main/AndroidManifest.xml b/packages/react-native-nitro-image-blurhash/android/src/main/AndroidManifest.xml
new file mode 100644
index 00000000..a2f47b60
--- /dev/null
+++ b/packages/react-native-nitro-image-blurhash/android/src/main/AndroidManifest.xml
@@ -0,0 +1,2 @@
+
+
diff --git a/packages/react-native-nitro-image-blurhash/android/src/main/cpp/cpp-adapter.cpp b/packages/react-native-nitro-image-blurhash/android/src/main/cpp/cpp-adapter.cpp
new file mode 100644
index 00000000..2842f274
--- /dev/null
+++ b/packages/react-native-nitro-image-blurhash/android/src/main/cpp/cpp-adapter.cpp
@@ -0,0 +1,9 @@
+#include
+#include
+#include "NitroImageBlurhashOnLoad.hpp"
+
+JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void*) {
+ return facebook::jni::initialize(vm, []() {
+ margelo::nitro::image::blurhash::registerAllNatives();
+ });
+}
diff --git a/packages/react-native-nitro-image-blurhash/android/src/main/java/com/margelo/nitro/image/blurhash/HybridBlurHashFactory.kt b/packages/react-native-nitro-image-blurhash/android/src/main/java/com/margelo/nitro/image/blurhash/HybridBlurHashFactory.kt
new file mode 100644
index 00000000..115f8438
--- /dev/null
+++ b/packages/react-native-nitro-image-blurhash/android/src/main/java/com/margelo/nitro/image/blurhash/HybridBlurHashFactory.kt
@@ -0,0 +1,38 @@
+package com.margelo.nitro.image.blurhash
+
+import androidx.annotation.Keep
+import com.facebook.proguard.annotations.DoNotStrip
+import com.margelo.nitro.core.Promise
+import com.margelo.nitro.image.HybridImage
+import com.margelo.nitro.image.HybridImageSpec
+import com.margelo.nitro.image.extensions.toCpuAccessible
+import com.mrousavy.blurhash.BlurHashDecoder
+import com.mrousavy.blurhash.BlurHashEncoder
+
+@DoNotStrip
+@Keep
+class HybridBlurHashFactory : HybridBlurHashFactorySpec() {
+ override fun encode(image: HybridImageSpec, componentsX: Double, componentsY: Double): String {
+ val native = image as? HybridImage
+ ?: throw Error("The given image ($image) is not a `HybridImage` and cannot be encoded to a BlurHash!")
+ return BlurHashEncoder.encode(native.bitmap.toCpuAccessible(), componentsX.toInt(), componentsY.toInt())
+ }
+
+ override fun encodeAsync(image: HybridImageSpec, componentsX: Double, componentsY: Double): Promise {
+ return Promise.async { encode(image, componentsX, componentsY) }
+ }
+
+ override fun decode(blurhash: String, width: Double, height: Double, punch: Double): HybridImageSpec {
+ val bitmap = BlurHashDecoder.decode(blurhash, width.toInt(), height.toInt(), punch.toFloat())
+ ?: throw Error("The given BlurHash ($blurhash) is not a valid BlurHash!")
+ return HybridImage(bitmap)
+ }
+
+ override fun decodeAsync(blurhash: String, width: Double, height: Double, punch: Double): Promise {
+ return Promise.async { decode(blurhash, width, height, punch) }
+ }
+
+ override fun clearCosineCache() {
+ BlurHashDecoder.clearCache()
+ }
+}
diff --git a/packages/react-native-nitro-image-blurhash/android/src/main/java/com/margelo/nitro/image/blurhash/NitroImageBlurhashPackage.kt b/packages/react-native-nitro-image-blurhash/android/src/main/java/com/margelo/nitro/image/blurhash/NitroImageBlurhashPackage.kt
new file mode 100644
index 00000000..3d57419b
--- /dev/null
+++ b/packages/react-native-nitro-image-blurhash/android/src/main/java/com/margelo/nitro/image/blurhash/NitroImageBlurhashPackage.kt
@@ -0,0 +1,18 @@
+package com.margelo.nitro.image.blurhash
+
+import com.facebook.react.bridge.NativeModule
+import com.facebook.react.bridge.ReactApplicationContext
+import com.facebook.react.module.model.ReactModuleInfoProvider
+import com.facebook.react.BaseReactPackage
+
+class NitroImageBlurhashPackage : BaseReactPackage() {
+ override fun getModule(name: String, reactContext: ReactApplicationContext): NativeModule? = null
+
+ override fun getReactModuleInfoProvider(): ReactModuleInfoProvider = ReactModuleInfoProvider { HashMap() }
+
+ companion object {
+ init {
+ NitroImageBlurhashOnLoad.initializeNative()
+ }
+ }
+}
diff --git a/packages/react-native-nitro-image-blurhash/android/src/main/java/com/mrousavy/blurhash/Base83.kt b/packages/react-native-nitro-image-blurhash/android/src/main/java/com/mrousavy/blurhash/Base83.kt
new file mode 100644
index 00000000..e988271e
--- /dev/null
+++ b/packages/react-native-nitro-image-blurhash/android/src/main/java/com/mrousavy/blurhash/Base83.kt
@@ -0,0 +1,29 @@
+package com.mrousavy.blurhash
+
+internal object Base83 {
+
+ private const val DIGITS = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz#\$%*+,-.:;=?@[]^_{|}~"
+
+ fun encode83(value: Int, length: Int, buffer: CharArray, offset: Int) {
+ var exp = 1
+ var i = 1
+ while (i <= length) {
+ val digit = (value / exp % 83)
+ buffer[offset + length - i] = DIGITS[digit]
+ i++
+ exp *= 83
+ }
+ }
+
+ fun decode83(str: String, from: Int = 0, to: Int = str.length): Int {
+ var result = 0
+ for (i in from until to) {
+ val index = DIGITS.indexOf(str[i])
+ if (index != -1) {
+ result = result * 83 + index
+ }
+ }
+ return result
+ }
+
+}
\ No newline at end of file
diff --git a/packages/react-native-nitro-image-blurhash/android/src/main/java/com/mrousavy/blurhash/BlurhashDecoder.kt b/packages/react-native-nitro-image-blurhash/android/src/main/java/com/mrousavy/blurhash/BlurhashDecoder.kt
new file mode 100644
index 00000000..29be1927
--- /dev/null
+++ b/packages/react-native-nitro-image-blurhash/android/src/main/java/com/mrousavy/blurhash/BlurhashDecoder.kt
@@ -0,0 +1,147 @@
+package com.mrousavy.blurhash
+
+import android.graphics.Bitmap
+import android.graphics.Color
+import androidx.collection.SparseArrayCompat
+import com.mrousavy.blurhash.Utils.linearToSrgb
+import com.mrousavy.blurhash.Utils.signedPow2
+import com.mrousavy.blurhash.Utils.srgbToLinear
+import kotlin.math.cos
+
+// TODO: Use parallel decoder from https://github.com/woltapp/blurhash/pull/68 ?
+
+object BlurHashDecoder {
+
+ // cache Math.cos() calculations to improve performance.
+ // The number of calculations can be huge for many bitmaps: width * height * numCompX * numCompY * 2 * nBitmaps
+ // the cache is enabled by default, it is recommended to disable it only when just a few images are displayed
+ private val cacheCosinesX = SparseArrayCompat()
+ private val cacheCosinesY = SparseArrayCompat()
+
+ /**
+ * Clear calculations stored in memory cache.
+ * The cache is not big, but will increase when many image sizes are used,
+ * if the app needs memory it is recommended to clear it.
+ */
+ fun clearCache() {
+ cacheCosinesX.clear()
+ cacheCosinesY.clear()
+ }
+
+ /**
+ * Decode a blur hash into a new bitmap.
+ *
+ * @param useCache use in memory cache for the calculated math, reused by images with same size.
+ * if the cache does not exist yet it will be created and populated with new calculations.
+ * By default it is true.
+ */
+ fun decode(blurHash: String?, width: Int, height: Int, punch: Float = 1f, useCache: Boolean = true): Bitmap? {
+ if (blurHash == null || blurHash.length < 6) {
+ return null
+ }
+ val numCompEnc = Base83.decode83(blurHash, 0, 1)
+ val numCompX = (numCompEnc % 9) + 1
+ val numCompY = (numCompEnc / 9) + 1
+ if (blurHash.length != 4 + 2 * numCompX * numCompY) {
+ return null
+ }
+ val maxAcEnc = Base83.decode83(blurHash, 1, 2)
+ val maxAc = (maxAcEnc + 1) / 166f
+ val colors = Array(numCompX * numCompY) { i ->
+ if (i == 0) {
+ val colorEnc = Base83.decode83(blurHash, 2, 6)
+ decodeDc(colorEnc)
+ } else {
+ val from = 4 + i * 2
+ val colorEnc = Base83.decode83(blurHash, from, from + 2)
+ decodeAc(colorEnc, maxAc * punch)
+ }
+ }
+ return composeBitmap(width, height, numCompX, numCompY, colors, useCache)
+ }
+
+ private fun decodeDc(colorEnc: Int): FloatArray {
+ val r = colorEnc shr 16
+ val g = (colorEnc shr 8) and 255
+ val b = colorEnc and 255
+ return floatArrayOf(srgbToLinear(r), srgbToLinear(g), srgbToLinear(b))
+ }
+
+ private fun decodeAc(value: Int, maxAc: Float): FloatArray {
+ val r = value / (19 * 19)
+ val g = (value / 19) % 19
+ val b = value % 19
+ return floatArrayOf(
+ signedPow2((r - 9) / 9.0f) * maxAc,
+ signedPow2((g - 9) / 9.0f) * maxAc,
+ signedPow2((b - 9) / 9.0f) * maxAc
+ )
+ }
+
+ private fun composeBitmap(
+ width: Int, height: Int,
+ numCompX: Int, numCompY: Int,
+ colors: Array,
+ useCache: Boolean
+ ): Bitmap {
+ // use an array for better performance when writing pixel colors
+ val imageArray = IntArray(width * height)
+ val calculateCosX = !useCache || !cacheCosinesX.containsKey(width * numCompX)
+ val cosinesX = getArrayForCosinesX(calculateCosX, width, numCompX)
+ val calculateCosY = !useCache || !cacheCosinesY.containsKey(height * numCompY)
+ val cosinesY = getArrayForCosinesY(calculateCosY, height, numCompY)
+ for (y in 0 until height) {
+ for (x in 0 until width) {
+ var r = 0f
+ var g = 0f
+ var b = 0f
+ for (j in 0 until numCompY) {
+ for (i in 0 until numCompX) {
+ val cosX = cosinesX.getCos(calculateCosX, i, numCompX, x, width)
+ val cosY = cosinesY.getCos(calculateCosY, j, numCompY, y, height)
+ val basis = (cosX * cosY).toFloat()
+ val color = colors[j * numCompX + i]
+ r += color[0] * basis
+ g += color[1] * basis
+ b += color[2] * basis
+ }
+ }
+ imageArray[x + width * y] = Color.rgb(linearToSrgb(r), linearToSrgb(g), linearToSrgb(b))
+ }
+ }
+ return Bitmap.createBitmap(imageArray, width, height, Bitmap.Config.ARGB_8888)
+ }
+
+ private fun getArrayForCosinesY(calculate: Boolean, height: Int, numCompY: Int) = when {
+ calculate -> {
+ DoubleArray(height * numCompY).also {
+ cacheCosinesY.put(height * numCompY, it)
+ }
+ }
+ else -> {
+ cacheCosinesY.get(height * numCompY)!!
+ }
+ }
+
+ private fun getArrayForCosinesX(calculate: Boolean, width: Int, numCompX: Int) = when {
+ calculate -> {
+ DoubleArray(width * numCompX).also {
+ cacheCosinesX.put(width * numCompX, it)
+ }
+ }
+ else -> cacheCosinesX.get(width * numCompX)!!
+ }
+
+ private fun DoubleArray.getCos(
+ calculate: Boolean,
+ x: Int,
+ numComp: Int,
+ y: Int,
+ size: Int
+ ): Double {
+ if (calculate) {
+ this[x + numComp * y] = cos(Math.PI * y * x / size)
+ }
+ return this[x + numComp * y]
+ }
+}
\ No newline at end of file
diff --git a/packages/react-native-nitro-image-blurhash/android/src/main/java/com/mrousavy/blurhash/BlurhashEncoder.kt b/packages/react-native-nitro-image-blurhash/android/src/main/java/com/mrousavy/blurhash/BlurhashEncoder.kt
new file mode 100644
index 00000000..053da25f
--- /dev/null
+++ b/packages/react-native-nitro-image-blurhash/android/src/main/java/com/mrousavy/blurhash/BlurhashEncoder.kt
@@ -0,0 +1,107 @@
+package com.mrousavy.blurhash
+
+import android.graphics.Bitmap
+import com.mrousavy.blurhash.Utils.linearToSrgb
+import com.mrousavy.blurhash.Utils.signedPow
+import com.mrousavy.blurhash.Utils.srgbToLinear
+import kotlin.math.*
+
+object BlurHashEncoder {
+ private fun applyBasisFunction(pixels: IntArray, width: Int, height: Int,
+ normalisation: Float, i: Int, j: Int, factors: Array,
+ index: Int) {
+ var r = 0f
+ var g = 0f
+ var b = 0f
+ for (x in 0 until width) for (y in 0 until height) {
+ val basis = (normalisation * cos(PI * i * x / width) *
+ cos(PI * j * y / height)).toFloat()
+ val pixel = pixels[y * width + x]
+ r += basis * srgbToLinear((pixel shr 16) and 255)
+ g += basis * srgbToLinear((pixel shr 8) and 255)
+ b += basis * srgbToLinear(pixel and 255)
+ }
+ val scale = 1f / (width * height)
+ factors[index][0] = r * scale
+ factors[index][1] = g * scale
+ factors[index][2] = b * scale
+ }
+
+ private fun encodeDC(value: FloatArray): Int {
+ val r = linearToSrgb(value[0])
+ val g = linearToSrgb(value[1])
+ val b = linearToSrgb(value[2])
+ return (r shl 16) + (g shl 8) + b
+ }
+
+ private fun encodeAC(value: FloatArray, maximumValue: Float): Int {
+ val quantR = floor(max(0f,
+ min(18f, floor(signedPow(value[0] / maximumValue, 0.5f) * 9 + 9.5f))))
+ val quantG = floor(max(0f,
+ min(18f, floor(signedPow(value[1] / maximumValue, 0.5f) * 9 + 9.5f))))
+ val quantB = floor(max(0f,
+ min(18f, floor(signedPow(value[2] / maximumValue, 0.5f) * 9 + 9.5f))))
+ return round(quantR * 19 * 19 + quantG * 19 + quantB).toInt()
+ }
+
+ /**
+ * Calculates the blur hash from the given image.
+ *
+ * @param bitmap the image
+ * @param componentX number of components in the x dimension
+ * @param componentY number of components in the y dimension
+ * @return the blur hash
+ */
+ fun encode(bitmap: Bitmap, componentX: Int, componentY: Int): String {
+ val width = bitmap.width
+ val height = bitmap.height
+ val pixels = IntArray(width * height)
+ bitmap.getPixels(pixels, 0, width, 0, 0, width, height)
+ return encode(pixels, width, height, componentX, componentY)
+ }
+
+ /**
+ * Calculates the blur hash from the given pixels.
+ *
+ * @param pixels width * height pixels, encoded as RGB integers (0xAARRGGBB)
+ * @param width width of the bitmap
+ * @param height height of the bitmap
+ * @param componentX number of components in the x dimension
+ * @param componentY number of components in the y dimension
+ * @return the blur hash
+ */
+ private fun encode(pixels: IntArray, width: Int, height: Int, componentX: Int,
+ componentY: Int): String {
+ require(!(componentX < 1 || componentX > 9 || componentY < 1 || componentY > 9)) { "Blur hash must have between 1 and 9 components" }
+ require(width * height == pixels.size) { "Width and height must match the pixels array" }
+ val factors = Array(componentX * componentY) { FloatArray(3) }
+ for (j in 0 until componentY) {
+ for (i in 0 until componentX) {
+ val normalisation = if (i == 0 && j == 0) 1f else 2f
+ applyBasisFunction(pixels, width, height, normalisation, i, j, factors,
+ j * componentX + i)
+ }
+ }
+ val hash = CharArray(1 + 1 + 4 + 2 * (factors.size - 1)) // size flag + max AC + DC + 2 * AC components
+ val sizeFlag = componentX - 1 + (componentY - 1) * 9
+ Base83.encode83(sizeFlag, 1, hash, 0)
+ val maximumValue: Float
+ if (factors.size > 1) {
+ val actualMaximumValue = Utils.max(factors, 1, factors.size)
+ val quantisedMaximumValue = floor(
+ max(0f, min(82f, floor(actualMaximumValue * 166 - 0.5f))))
+ maximumValue = (quantisedMaximumValue + 1) / 166
+ Base83.encode83(round(quantisedMaximumValue).toInt(), 1, hash, 1)
+ } else {
+ maximumValue = 1f
+ Base83.encode83(0, 1, hash, 1)
+ }
+ val dc = factors[0]
+ Base83.encode83(encodeDC(dc), 4, hash, 2)
+ for (i in 1 until factors.size) {
+ Base83.encode83(encodeAC(factors[i], maximumValue), 2, hash, 6 + 2 * (i - 1))
+ }
+ return String(hash)
+ }
+
+}
\ No newline at end of file
diff --git a/packages/react-native-nitro-image-blurhash/android/src/main/java/com/mrousavy/blurhash/Utils.kt b/packages/react-native-nitro-image-blurhash/android/src/main/java/com/mrousavy/blurhash/Utils.kt
new file mode 100644
index 00000000..d8044b5b
--- /dev/null
+++ b/packages/react-native-nitro-image-blurhash/android/src/main/java/com/mrousavy/blurhash/Utils.kt
@@ -0,0 +1,43 @@
+package com.mrousavy.blurhash
+
+import kotlin.math.abs
+import kotlin.math.pow
+import kotlin.math.withSign
+
+internal object Utils {
+
+ internal fun linearToSrgb(value: Float): Int {
+ val v = value.coerceIn(0f, 1f)
+ return if (v <= 0.0031308f) {
+ (v * 12.92f * 255f + 0.5f).toInt()
+ } else {
+ ((1.055f * v.pow(1 / 2.4f) - 0.055f) * 255 + 0.5f).toInt()
+ }
+ }
+
+ internal fun srgbToLinear(colorEnc: Int): Float {
+ val v = colorEnc / 255f
+ return if (v <= 0.04045f) {
+ (v / 12.92f)
+ } else {
+ ((v + 0.055f) / 1.055f).pow(2.4f)
+ }
+ }
+
+ internal fun signedPow2(value: Float) = value.pow(2f).withSign(value)
+
+ internal fun signedPow(value: Float, exp: Float) = abs(value).pow(exp).withSign(value)
+
+ internal fun max(values: Array, from: Int, endExclusive: Int): Float {
+ var result = Float.NEGATIVE_INFINITY
+ for (i in from until endExclusive) {
+ for (value in values[i]) {
+ if (value > result) {
+ result = value
+ }
+ }
+ }
+ return result
+ }
+
+}
\ No newline at end of file
diff --git a/packages/react-native-nitro-image-blurhash/babel.config.js b/packages/react-native-nitro-image-blurhash/babel.config.js
new file mode 100644
index 00000000..3e0218e6
--- /dev/null
+++ b/packages/react-native-nitro-image-blurhash/babel.config.js
@@ -0,0 +1,3 @@
+module.exports = {
+ presets: ['module:@react-native/babel-preset'],
+}
diff --git a/packages/react-native-nitro-image-blurhash/ios/Blurhash/BinaryInteger+Encode83.swift b/packages/react-native-nitro-image-blurhash/ios/Blurhash/BinaryInteger+Encode83.swift
new file mode 100644
index 00000000..f9ac3120
--- /dev/null
+++ b/packages/react-native-nitro-image-blurhash/ios/Blurhash/BinaryInteger+Encode83.swift
@@ -0,0 +1,24 @@
+//
+// BinaryInteger+Encode83.swift
+// Blurhash
+//
+// Created by Marc Rousavy on 10.07.20.
+// Copyright © 2020 Facebook. All rights reserved.
+//
+
+import Foundation
+
+extension BinaryInteger {
+ func encode83(length: Int) -> String {
+ var result = ""
+ for i in 1 ... length {
+ let digit = (Int(self) / pow(83, length - i)) % 83
+ result += encodeCharacters[Int(digit)]
+ }
+ return result
+ }
+}
+
+private func pow(_ base: Int, _ exponent: Int) -> Int {
+ return (0 ..< exponent).reduce(1) { value, _ in value * base }
+}
diff --git a/packages/react-native-nitro-image-blurhash/ios/Blurhash/BlurhashHelper.swift b/packages/react-native-nitro-image-blurhash/ios/Blurhash/BlurhashHelper.swift
new file mode 100644
index 00000000..3f1f03e1
--- /dev/null
+++ b/packages/react-native-nitro-image-blurhash/ios/Blurhash/BlurhashHelper.swift
@@ -0,0 +1,77 @@
+//
+// BlurhashHelper.swift
+// Blurhash
+//
+// Created by Marc Rousavy on 10.07.20.
+// Copyright © 2020 Facebook. All rights reserved.
+//
+
+import Foundation
+
+// MARK: Helper funcs
+
+func signPow(_ value: Float, _ exp: Float) -> Float {
+ return copysign(pow(abs(value), exp), value)
+}
+
+func linearTosRGB(_ value: Float) -> Int {
+ let v = max(0, min(1, value))
+ if v <= 0.0031308 { return Int(v * 12.92 * 255 + 0.5) }
+ else { return Int((1.055 * pow(v, 1 / 2.4) - 0.055) * 255 + 0.5) }
+}
+
+func sRGBToLinear(_ value: Type) -> Float {
+ let v = Float(Int64(value)) / 255
+ if v <= 0.04045 { return v / 12.92 }
+ else { return pow((v + 0.055) / 1.055, 2.4) }
+}
+
+// MARK: Encoder
+
+let encodeCharacters: [String] = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz#$%*+,-.:;=?@[]^_{|}~".map { String($0) }
+
+func encodeDC(_ value: (Float, Float, Float)) -> Int {
+ let roundedR = linearTosRGB(value.0)
+ let roundedG = linearTosRGB(value.1)
+ let roundedB = linearTosRGB(value.2)
+ return (roundedR << 16) + (roundedG << 8) + roundedB
+}
+
+func encodeAC(_ value: (Float, Float, Float), maximumValue: Float) -> Int {
+ let quantR = Int(max(0, min(18, floor(signPow(value.0 / maximumValue, 0.5) * 9 + 9.5))))
+ let quantG = Int(max(0, min(18, floor(signPow(value.1 / maximumValue, 0.5) * 9 + 9.5))))
+ let quantB = Int(max(0, min(18, floor(signPow(value.2 / maximumValue, 0.5) * 9 + 9.5))))
+
+ return quantR * 19 * 19 + quantG * 19 + quantB
+}
+
+// MARK: Decoder
+
+let decodeCharacters: [String: Int] = {
+ var dict: [String: Int] = [:]
+ for (index, character) in encodeCharacters.enumerated() {
+ dict[character] = index
+ }
+ return dict
+}()
+
+func decodeDC(_ value: Int) -> (Float, Float, Float) {
+ let intR = value >> 16
+ let intG = (value >> 8) & 255
+ let intB = value & 255
+ return (sRGBToLinear(intR), sRGBToLinear(intG), sRGBToLinear(intB))
+}
+
+func decodeAC(_ value: Int, maximumValue: Float) -> (Float, Float, Float) {
+ let quantR = value / (19 * 19)
+ let quantG = (value / 19) % 19
+ let quantB = value % 19
+
+ let rgb = (
+ signPow((Float(quantR) - 9) / 9, 2) * maximumValue,
+ signPow((Float(quantG) - 9) / 9, 2) * maximumValue,
+ signPow((Float(quantB) - 9) / 9, 2) * maximumValue
+ )
+
+ return rgb
+}
diff --git a/packages/react-native-nitro-image-blurhash/ios/Blurhash/String+Decode83.swift b/packages/react-native-nitro-image-blurhash/ios/Blurhash/String+Decode83.swift
new file mode 100644
index 00000000..d5edb58c
--- /dev/null
+++ b/packages/react-native-nitro-image-blurhash/ios/Blurhash/String+Decode83.swift
@@ -0,0 +1,21 @@
+//
+// String+Decode83.swift
+// Blurhash
+//
+// Created by Marc Rousavy on 10.07.20.
+// Copyright © 2020 Facebook. All rights reserved.
+//
+
+import Foundation
+
+extension String {
+ func decode83() -> Int {
+ var value = 0
+ for character in self {
+ if let digit = decodeCharacters[String(character)] {
+ value = value * 83 + digit
+ }
+ }
+ return value
+ }
+}
diff --git a/packages/react-native-nitro-image-blurhash/ios/Blurhash/String+Index.swift b/packages/react-native-nitro-image-blurhash/ios/Blurhash/String+Index.swift
new file mode 100644
index 00000000..933ada13
--- /dev/null
+++ b/packages/react-native-nitro-image-blurhash/ios/Blurhash/String+Index.swift
@@ -0,0 +1,40 @@
+//
+// String+Index.swift
+// Blurhash
+//
+// Created by Marc Rousavy on 04.07.20.
+// Copyright © 2020 Facebook. All rights reserved.
+//
+
+import Foundation
+
+extension StringProtocol {
+ func distance(of element: Element) -> Int? { firstIndex(of: element)?.distance(in: self) }
+ func distance(of string: S) -> Int? { range(of: string)?.lowerBound.distance(in: self) }
+}
+
+extension Collection {
+ func distance(to index: Index) -> Int { distance(from: startIndex, to: index) }
+}
+
+extension String.Index {
+ func distance(in string: S) -> Int { string.distance(to: self) }
+}
+
+extension String {
+ subscript(offset: Int) -> Character {
+ return self[index(startIndex, offsetBy: offset)]
+ }
+
+ subscript(bounds: CountableClosedRange) -> Substring {
+ let start = index(startIndex, offsetBy: bounds.lowerBound)
+ let end = index(startIndex, offsetBy: bounds.upperBound)
+ return self[start ... end]
+ }
+
+ subscript(bounds: CountableRange) -> Substring {
+ let start = index(startIndex, offsetBy: bounds.lowerBound)
+ let end = index(startIndex, offsetBy: bounds.upperBound)
+ return self[start ..< end]
+ }
+}
diff --git a/packages/react-native-nitro-image-blurhash/ios/Blurhash/UIImage+BlurhashDecode.swift b/packages/react-native-nitro-image-blurhash/ios/Blurhash/UIImage+BlurhashDecode.swift
new file mode 100644
index 00000000..70762156
--- /dev/null
+++ b/packages/react-native-nitro-image-blurhash/ios/Blurhash/UIImage+BlurhashDecode.swift
@@ -0,0 +1,100 @@
+//
+// UIImage+BlurhashDecode.swift
+// Blurhash
+//
+// Created by Marc Rousavy on 08.06.20.
+// Copyright © 2020 Facebook. All rights reserved.
+//
+
+import Foundation
+import UIKit
+
+public extension UIImage {
+ convenience init?(blurHash: String, size: CGSize, punch: Float = 1) {
+ guard blurHash.count >= 6 else { return nil }
+
+ let sizeFlag = String(blurHash[0]).decode83()
+ let numY = (sizeFlag / 9) + 1
+ let numX = (sizeFlag % 9) + 1
+
+ let quantisedMaximumValue = String(blurHash[1]).decode83()
+ let maximumValue = Float(quantisedMaximumValue + 1) / 166
+
+ guard blurHash.count == 4 + 2 * numX * numY else { return nil }
+
+ let colours: [(Float, Float, Float)] = (0 ..< numX * numY).map { i in
+ if i == 0 {
+ let value = String(blurHash[2 ..< 6]).decode83()
+ return decodeDC(value)
+ } else {
+ let value = String(blurHash[4 + i * 2 ..< 4 + i * 2 + 2]).decode83()
+ return decodeAC(value, maximumValue: maximumValue * punch)
+ }
+ }
+
+ let width = Int(size.width)
+ let height = Int(size.height)
+ let bytesPerRow = width * 3
+ guard let data = CFDataCreateMutable(kCFAllocatorDefault, bytesPerRow * height) else { return nil }
+ CFDataSetLength(data, bytesPerRow * height)
+ guard let pixels = CFDataGetMutableBytePtr(data) else { return nil }
+
+ let cosxi = (0 ..< width).map { x in
+ (0 ..< numX).map { i in
+ cos(Float.pi * Float(x) * Float(i) / Float(width))
+ }
+ }
+
+ let cosyj = (0 ..< height).map { y in
+ (0 ..< numY).map { j in
+ cos(Float.pi * Float(y) * Float(j) / Float(height))
+ }
+ }
+
+ for y in 0 ..< height {
+ let cosj = cosyj[y]
+ for x in 0 ..< width {
+ let cosi = cosxi[x]
+
+ var red: Float = 0
+ var green: Float = 0
+ var blue: Float = 0
+
+ for j in 0 ..< numY {
+ for i in 0 ..< numX {
+ let basis = cosi[i] * cosj[j]
+ let colour = colours[i + j * numX]
+ red += colour.0 * basis
+ green += colour.1 * basis
+ blue += colour.2 * basis
+ }
+ }
+
+ let intR = UInt8(linearTosRGB(red))
+ let intG = UInt8(linearTosRGB(green))
+ let intB = UInt8(linearTosRGB(blue))
+
+ pixels[3 * x + 0 + y * bytesPerRow] = intR
+ pixels[3 * x + 1 + y * bytesPerRow] = intG
+ pixels[3 * x + 2 + y * bytesPerRow] = intB
+ }
+ }
+
+ let bitmapInfo = CGBitmapInfo(rawValue: CGImageAlphaInfo.none.rawValue)
+
+ guard let provider = CGDataProvider(data: data) else { return nil }
+ guard let cgImage = CGImage(width: width,
+ height: height,
+ bitsPerComponent: 8,
+ bitsPerPixel: 24,
+ bytesPerRow: bytesPerRow,
+ space: CGColorSpaceCreateDeviceRGB(),
+ bitmapInfo: bitmapInfo,
+ provider: provider,
+ decode: nil,
+ shouldInterpolate: true,
+ intent: .defaultIntent) else { return nil }
+
+ self.init(cgImage: cgImage)
+ }
+}
diff --git a/packages/react-native-nitro-image-blurhash/ios/Blurhash/UIImage+BlurhashEncode.swift b/packages/react-native-nitro-image-blurhash/ios/Blurhash/UIImage+BlurhashEncode.swift
new file mode 100644
index 00000000..bc68c957
--- /dev/null
+++ b/packages/react-native-nitro-image-blurhash/ios/Blurhash/UIImage+BlurhashEncode.swift
@@ -0,0 +1,105 @@
+//
+// UIImage+BlurhashEncode.swift
+// Blurhash
+//
+// Created by Marc Rousavy on 04.07.20.
+// Copyright © 2020 Facebook. All rights reserved.
+//
+
+import Foundation
+import UIKit
+
+extension UIImage {
+ public func encodeBlurhash(numberOfComponents components: (Int, Int)) -> String? {
+ let pixelWidth = Int(round(size.width * scale))
+ let pixelHeight = Int(round(size.height * scale))
+
+ let context = CGContext(
+ data: nil,
+ width: pixelWidth,
+ height: pixelHeight,
+ bitsPerComponent: 8,
+ bytesPerRow: pixelWidth * 4,
+ space: CGColorSpace(name: CGColorSpace.sRGB)!,
+ bitmapInfo: CGImageAlphaInfo.premultipliedLast.rawValue
+ )!
+ context.scaleBy(x: scale, y: -scale)
+ context.translateBy(x: 0, y: -size.height)
+
+ UIGraphicsPushContext(context)
+ draw(at: .zero)
+ UIGraphicsPopContext()
+
+ guard let cgImage = context.makeImage(),
+ let dataProvider = cgImage.dataProvider,
+ let data = dataProvider.data,
+ let pixels = CFDataGetBytePtr(data)
+ else {
+ assertionFailure("Unexpected error!")
+ return nil
+ }
+
+ let width = cgImage.width
+ let height = cgImage.height
+ let bytesPerRow = cgImage.bytesPerRow
+
+ var factors: [(Float, Float, Float)] = []
+ for y in 0 ..< components.1 {
+ for x in 0 ..< components.0 {
+ let normalisation: Float = (x == 0 && y == 0) ? 1 : 2
+ let factor = multiplyBasisFunction(pixels: pixels, width: width, height: height, bytesPerRow: bytesPerRow, bytesPerPixel: cgImage.bitsPerPixel / 8, pixelOffset: 0) {
+ normalisation * cos(Float.pi * Float(x) * $0 / Float(width)) as Float * cos(Float.pi * Float(y) * $1 / Float(height)) as Float
+ }
+ factors.append(factor)
+ }
+ }
+
+ let dc = factors.first!
+ let ac = factors.dropFirst()
+
+ var hash = ""
+
+ let sizeFlag = (components.0 - 1) + (components.1 - 1) * 9
+ hash += sizeFlag.encode83(length: 1)
+
+ let maximumValue: Float
+ if ac.count > 0 {
+ let actualMaximumValue = ac.map { max(abs($0.0), abs($0.1), abs($0.2)) }.max()!
+ let quantisedMaximumValue = Int(max(0, min(82, floor(actualMaximumValue * 166 - 0.5))))
+ maximumValue = Float(quantisedMaximumValue + 1) / 166
+ hash += quantisedMaximumValue.encode83(length: 1)
+ } else {
+ maximumValue = 1
+ hash += 0.encode83(length: 1)
+ }
+
+ hash += encodeDC(dc).encode83(length: 4)
+
+ for factor in ac {
+ hash += encodeAC(factor, maximumValue: maximumValue).encode83(length: 2)
+ }
+
+ return hash
+ }
+
+ private final func multiplyBasisFunction(pixels: UnsafePointer, width: Int, height: Int, bytesPerRow: Int, bytesPerPixel: Int, pixelOffset: Int, basisFunction: (Float, Float) -> Float) -> (Float, Float, Float) {
+ var r: Float = 0
+ var g: Float = 0
+ var b: Float = 0
+
+ let buffer = UnsafeBufferPointer(start: pixels, count: height * bytesPerRow)
+
+ for x in 0 ..< width {
+ for y in 0 ..< height {
+ let basis = basisFunction(Float(x), Float(y))
+ r += basis * sRGBToLinear(buffer[bytesPerPixel * x + pixelOffset + 0 + y * bytesPerRow])
+ g += basis * sRGBToLinear(buffer[bytesPerPixel * x + pixelOffset + 1 + y * bytesPerRow])
+ b += basis * sRGBToLinear(buffer[bytesPerPixel * x + pixelOffset + 2 + y * bytesPerRow])
+ }
+ }
+
+ let scale = 1 / Float(width * height)
+
+ return (r * scale, g * scale, b * scale)
+ }
+}
diff --git a/packages/react-native-nitro-image-blurhash/ios/Bridge.h b/packages/react-native-nitro-image-blurhash/ios/Bridge.h
new file mode 100644
index 00000000..1303592f
--- /dev/null
+++ b/packages/react-native-nitro-image-blurhash/ios/Bridge.h
@@ -0,0 +1,8 @@
+//
+// Bridge.h
+// NitroImageBlurhash
+//
+// Created by Marc Rousavy on 22.07.24.
+//
+
+#pragma once
diff --git a/packages/react-native-nitro-image-blurhash/ios/HybridBlurHashFactory.swift b/packages/react-native-nitro-image-blurhash/ios/HybridBlurHashFactory.swift
new file mode 100644
index 00000000..53e983b8
--- /dev/null
+++ b/packages/react-native-nitro-image-blurhash/ios/HybridBlurHashFactory.swift
@@ -0,0 +1,48 @@
+import Foundation
+import UIKit
+import NitroModules
+import NitroImage
+
+fileprivate class HybridImage: HybridImageSpec, NativeImage {
+ let uiImage: UIImage
+ init(uiImage: UIImage) {
+ self.uiImage = uiImage
+ super.init()
+ }
+}
+
+class HybridBlurHashFactory: HybridBlurHashFactorySpec {
+ func encode(image: any HybridImageSpec, componentsX: Double, componentsY: Double) throws -> String {
+ guard let native = image as? NativeImage else {
+ throw RuntimeError.error(withMessage: "The given image (\(image)) is not a `NativeImage` and cannot be encoded to a BlurHash!")
+ }
+ guard let hash = native.uiImage.encodeBlurhash(numberOfComponents: (Int(componentsX), Int(componentsY))) else {
+ throw RuntimeError.error(withMessage: "Failed to encode BlurHash!")
+ }
+ return hash
+ }
+
+ func encodeAsync(image: any HybridImageSpec, componentsX: Double, componentsY: Double) throws -> Promise {
+ return Promise.async {
+ return try self.encode(image: image, componentsX: componentsX, componentsY: componentsY)
+ }
+ }
+
+ func decode(blurhash: String, width: Double, height: Double, punch: Double) throws -> any HybridImageSpec {
+ let size = CGSize(width: width, height: height)
+ guard let uiImage = UIImage(blurHash: blurhash, size: size, punch: Float(punch)) else {
+ throw RuntimeError.error(withMessage: "The given BlurHash (\(blurhash)) is not a valid BlurHash!")
+ }
+ return HybridImage(uiImage: uiImage)
+ }
+
+ func decodeAsync(blurhash: String, width: Double, height: Double, punch: Double) throws -> Promise {
+ return Promise.async {
+ return try self.decode(blurhash: blurhash, width: width, height: height, punch: punch)
+ }
+ }
+
+ func clearCosineCache() throws {
+ // No-op: the iOS BlurHash decoder has no cosine cache.
+ }
+}
diff --git a/packages/react-native-nitro-image-blurhash/nitro.json b/packages/react-native-nitro-image-blurhash/nitro.json
new file mode 100644
index 00000000..c8f1b854
--- /dev/null
+++ b/packages/react-native-nitro-image-blurhash/nitro.json
@@ -0,0 +1,24 @@
+{
+ "$schema": "https://nitro.margelo.com/nitro.schema.json",
+ "cxxNamespace": ["image", "blurhash"],
+ "ios": {
+ "iosModuleName": "NitroImageBlurhash"
+ },
+ "android": {
+ "androidNamespace": ["image", "blurhash"],
+ "androidCxxLibName": "NitroImageBlurhash"
+ },
+ "autolinking": {
+ "BlurHashFactory": {
+ "ios": {
+ "language": "swift",
+ "implementationClassName": "HybridBlurHashFactory"
+ },
+ "android": {
+ "language": "kotlin",
+ "implementationClassName": "HybridBlurHashFactory"
+ }
+ }
+ },
+ "ignorePaths": ["**/node_modules"]
+}
diff --git a/packages/react-native-nitro-image-blurhash/nitrogen/generated/.gitattributes b/packages/react-native-nitro-image-blurhash/nitrogen/generated/.gitattributes
new file mode 100644
index 00000000..fb7a0d5a
--- /dev/null
+++ b/packages/react-native-nitro-image-blurhash/nitrogen/generated/.gitattributes
@@ -0,0 +1 @@
+** linguist-generated=true
diff --git a/packages/react-native-nitro-image-blurhash/nitrogen/generated/android/NitroImageBlurhash+autolinking.cmake b/packages/react-native-nitro-image-blurhash/nitrogen/generated/android/NitroImageBlurhash+autolinking.cmake
new file mode 100644
index 00000000..245b9a31
--- /dev/null
+++ b/packages/react-native-nitro-image-blurhash/nitrogen/generated/android/NitroImageBlurhash+autolinking.cmake
@@ -0,0 +1,81 @@
+#
+# NitroImageBlurhash+autolinking.cmake
+# This file was generated by nitrogen. DO NOT MODIFY THIS FILE.
+# https://github.com/mrousavy/nitro
+# Copyright © Marc Rousavy @ Margelo
+#
+
+# This is a CMake file that adds all files generated by Nitrogen
+# to the current CMake project.
+#
+# To use it, add this to your CMakeLists.txt:
+# ```cmake
+# include(${CMAKE_SOURCE_DIR}/../nitrogen/generated/android/NitroImageBlurhash+autolinking.cmake)
+# ```
+
+# Define a flag to check if we are building properly
+add_definitions(-DBUILDING_NITROIMAGEBLURHASH_WITH_GENERATED_CMAKE_PROJECT)
+
+# Enable Raw Props parsing in react-native (for Nitro Views)
+add_definitions(-DRN_SERIALIZABLE_STATE)
+
+# Add all headers that were generated by Nitrogen
+include_directories(
+ "../nitrogen/generated/shared/c++"
+ "../nitrogen/generated/android/c++"
+ "../nitrogen/generated/android/"
+)
+
+# Add all .cpp sources that were generated by Nitrogen
+target_sources(
+ # CMake project name (Android C++ library name)
+ NitroImageBlurhash PRIVATE
+ # Autolinking Setup
+ ../nitrogen/generated/android/NitroImageBlurhashOnLoad.cpp
+ # Shared Nitrogen C++ sources
+ ../nitrogen/generated/shared/c++/HybridBlurHashFactorySpec.cpp
+ # Android-specific Nitrogen C++ sources
+ ../nitrogen/generated/android/c++/JHybridBlurHashFactorySpec.cpp
+)
+
+# From node_modules/react-native/ReactAndroid/cmake-utils/folly-flags.cmake
+# Used in node_modules/react-native/ReactAndroid/cmake-utils/ReactNative-application.cmake
+target_compile_definitions(
+ NitroImageBlurhash PRIVATE
+ -DFOLLY_NO_CONFIG=1
+ -DFOLLY_HAVE_CLOCK_GETTIME=1
+ -DFOLLY_USE_LIBCPP=1
+ -DFOLLY_CFG_NO_COROUTINES=1
+ -DFOLLY_MOBILE=1
+ -DFOLLY_HAVE_RECVMMSG=1
+ -DFOLLY_HAVE_PTHREAD=1
+ # Once we target android-23 above, we can comment
+ # the following line. NDK uses GNU style stderror_r() after API 23.
+ -DFOLLY_HAVE_XSI_STRERROR_R=1
+)
+
+# Add all libraries required by the generated specs
+find_package(fbjni REQUIRED) # <-- Used for communication between Java <-> C++
+find_package(ReactAndroid REQUIRED) # <-- Used to set up React Native bindings (e.g. CallInvoker/TurboModule)
+find_package(react-native-nitro-modules REQUIRED) # <-- Used to create all HybridObjects and use the Nitro core library
+
+# Link all libraries together
+target_link_libraries(
+ NitroImageBlurhash
+ fbjni::fbjni # <-- Facebook C++ JNI helpers
+ ReactAndroid::jsi # <-- RN: JSI
+ react-native-nitro-modules::NitroModules # <-- NitroModules Core :)
+)
+
+# Link react-native (different prefab between RN 0.75 and RN 0.76)
+if(ReactAndroid_VERSION_MINOR GREATER_EQUAL 76)
+ target_link_libraries(
+ NitroImageBlurhash
+ ReactAndroid::reactnative # <-- RN: Native Modules umbrella prefab
+ )
+else()
+ target_link_libraries(
+ NitroImageBlurhash
+ ReactAndroid::react_nativemodule_core # <-- RN: TurboModules Core
+ )
+endif()
diff --git a/packages/react-native-nitro-image-blurhash/nitrogen/generated/android/NitroImageBlurhash+autolinking.gradle b/packages/react-native-nitro-image-blurhash/nitrogen/generated/android/NitroImageBlurhash+autolinking.gradle
new file mode 100644
index 00000000..68455e22
--- /dev/null
+++ b/packages/react-native-nitro-image-blurhash/nitrogen/generated/android/NitroImageBlurhash+autolinking.gradle
@@ -0,0 +1,27 @@
+///
+/// NitroImageBlurhash+autolinking.gradle
+/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE.
+/// https://github.com/mrousavy/nitro
+/// Copyright © Marc Rousavy @ Margelo
+///
+
+/// This is a Gradle file that adds all files generated by Nitrogen
+/// to the current Gradle project.
+///
+/// To use it, add this to your build.gradle:
+/// ```gradle
+/// apply from: '../nitrogen/generated/android/NitroImageBlurhash+autolinking.gradle'
+/// ```
+
+logger.warn("[NitroModules] 🔥 NitroImageBlurhash is boosted by nitro!")
+
+android {
+ sourceSets {
+ main {
+ java.srcDirs += [
+ // Nitrogen files
+ "${project.projectDir}/../nitrogen/generated/android/kotlin"
+ ]
+ }
+ }
+}
diff --git a/packages/react-native-nitro-image-blurhash/nitrogen/generated/android/NitroImageBlurhashOnLoad.cpp b/packages/react-native-nitro-image-blurhash/nitrogen/generated/android/NitroImageBlurhashOnLoad.cpp
new file mode 100644
index 00000000..5522d352
--- /dev/null
+++ b/packages/react-native-nitro-image-blurhash/nitrogen/generated/android/NitroImageBlurhashOnLoad.cpp
@@ -0,0 +1,54 @@
+///
+/// NitroImageBlurhashOnLoad.cpp
+/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE.
+/// https://github.com/mrousavy/nitro
+/// Copyright © Marc Rousavy @ Margelo
+///
+
+#ifndef BUILDING_NITROIMAGEBLURHASH_WITH_GENERATED_CMAKE_PROJECT
+#error NitroImageBlurhashOnLoad.cpp is not being built with the autogenerated CMakeLists.txt project. Is a different CMakeLists.txt building this?
+#endif
+
+#include "NitroImageBlurhashOnLoad.hpp"
+
+#include
+#include
+#include
+
+#include "JHybridBlurHashFactorySpec.hpp"
+#include
+
+namespace margelo::nitro::image::blurhash {
+
+int initialize(JavaVM* vm) {
+ return facebook::jni::initialize(vm, []() {
+ ::margelo::nitro::image::blurhash::registerAllNatives();
+ });
+}
+
+struct JHybridBlurHashFactorySpecImpl: public jni::JavaClass {
+ static constexpr auto kJavaDescriptor = "Lcom/margelo/nitro/image/blurhash/HybridBlurHashFactory;";
+ static std::shared_ptr create() {
+ static const auto constructorFn = javaClassStatic()->getConstructor();
+ jni::local_ref javaPart = javaClassStatic()->newObject(constructorFn);
+ return javaPart->getJHybridBlurHashFactorySpec();
+ }
+};
+
+void registerAllNatives() {
+ using namespace margelo::nitro;
+ using namespace margelo::nitro::image::blurhash;
+
+ // Register native JNI methods
+ margelo::nitro::image::blurhash::JHybridBlurHashFactorySpec::CxxPart::registerNatives();
+
+ // Register Nitro Hybrid Objects
+ HybridObjectRegistry::registerHybridObjectConstructor(
+ "BlurHashFactory",
+ []() -> std::shared_ptr {
+ return JHybridBlurHashFactorySpecImpl::create();
+ }
+ );
+}
+
+} // namespace margelo::nitro::image::blurhash
diff --git a/packages/react-native-nitro-image-blurhash/nitrogen/generated/android/NitroImageBlurhashOnLoad.hpp b/packages/react-native-nitro-image-blurhash/nitrogen/generated/android/NitroImageBlurhashOnLoad.hpp
new file mode 100644
index 00000000..097e1cec
--- /dev/null
+++ b/packages/react-native-nitro-image-blurhash/nitrogen/generated/android/NitroImageBlurhashOnLoad.hpp
@@ -0,0 +1,34 @@
+///
+/// NitroImageBlurhashOnLoad.hpp
+/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE.
+/// https://github.com/mrousavy/nitro
+/// Copyright © Marc Rousavy @ Margelo
+///
+
+#include
+#include
+#include
+
+namespace margelo::nitro::image::blurhash {
+
+ [[deprecated("Use registerNatives() instead.")]]
+ int initialize(JavaVM* vm);
+
+ /**
+ * Register the native (C++) part of NitroImageBlurhash, and autolinks all Hybrid Objects.
+ * Call this in your `JNI_OnLoad` function (probably inside `cpp-adapter.cpp`),
+ * inside a `facebook::jni::initialize(vm, ...)` call.
+ * Example:
+ * ```cpp (cpp-adapter.cpp)
+ * JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void*) {
+ * return facebook::jni::initialize(vm, []() {
+ * // register all NitroImageBlurhash HybridObjects
+ * margelo::nitro::image::blurhash::registerNatives();
+ * // any other custom registrations go here.
+ * });
+ * }
+ * ```
+ */
+ void registerAllNatives();
+
+} // namespace margelo::nitro::image::blurhash
diff --git a/packages/react-native-nitro-image-blurhash/nitrogen/generated/android/c++/JHybridBlurHashFactorySpec.cpp b/packages/react-native-nitro-image-blurhash/nitrogen/generated/android/c++/JHybridBlurHashFactorySpec.cpp
new file mode 100644
index 00000000..88acd1d5
--- /dev/null
+++ b/packages/react-native-nitro-image-blurhash/nitrogen/generated/android/c++/JHybridBlurHashFactorySpec.cpp
@@ -0,0 +1,100 @@
+///
+/// JHybridBlurHashFactorySpec.cpp
+/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE.
+/// https://github.com/mrousavy/nitro
+/// Copyright © Marc Rousavy @ Margelo
+///
+
+#include "JHybridBlurHashFactorySpec.hpp"
+
+// Forward declaration of `HybridImageSpec` to properly resolve imports.
+namespace margelo::nitro::image { class HybridImageSpec; }
+
+#include
+#include
+#include
+#include
+#include
+#include
+
+namespace margelo::nitro::image::blurhash {
+
+ std::shared_ptr JHybridBlurHashFactorySpec::JavaPart::getJHybridBlurHashFactorySpec() {
+ auto hybridObject = JHybridObject::JavaPart::getJHybridObject();
+ auto castHybridObject = std::dynamic_pointer_cast(hybridObject);
+ if (castHybridObject == nullptr) [[unlikely]] {
+ throw std::runtime_error("Failed to downcast JHybridObject to JHybridBlurHashFactorySpec!");
+ }
+ return castHybridObject;
+ }
+
+ jni::local_ref JHybridBlurHashFactorySpec::CxxPart::initHybrid(jni::alias_ref jThis) {
+ return makeCxxInstance(jThis);
+ }
+
+ std::shared_ptr JHybridBlurHashFactorySpec::CxxPart::createHybridObject(const jni::local_ref& javaPart) {
+ auto castJavaPart = jni::dynamic_ref_cast(javaPart);
+ if (castJavaPart == nullptr) [[unlikely]] {
+ throw std::runtime_error("Failed to cast JHybridObject::JavaPart to JHybridBlurHashFactorySpec::JavaPart!");
+ }
+ return std::make_shared(castJavaPart);
+ }
+
+ void JHybridBlurHashFactorySpec::CxxPart::registerNatives() {
+ registerHybrid({
+ makeNativeMethod("initHybrid", JHybridBlurHashFactorySpec::CxxPart::initHybrid),
+ });
+ }
+
+ // Properties
+
+
+ // Methods
+ std::string JHybridBlurHashFactorySpec::encode(const std::shared_ptr& image, double componentsX, double componentsY) {
+ static const auto method = _javaPart->javaClassStatic()->getMethod(jni::alias_ref /* image */, double /* componentsX */, double /* componentsY */)>("encode");
+ auto __result = method(_javaPart, std::dynamic_pointer_cast(image)->getJavaPart(), componentsX, componentsY);
+ return __result->toStdString();
+ }
+ std::shared_ptr> JHybridBlurHashFactorySpec::encodeAsync(const std::shared_ptr& image, double componentsX, double componentsY) {
+ static const auto method = _javaPart->javaClassStatic()->getMethod(jni::alias_ref /* image */, double /* componentsX */, double /* componentsY */)>("encodeAsync");
+ auto __result = method(_javaPart, std::dynamic_pointer_cast(image)->getJavaPart(), componentsX, componentsY);
+ return [&]() {
+ auto __promise = Promise::create();
+ __result->cthis()->addOnResolvedListener([=](const jni::alias_ref& __boxedResult) {
+ auto __result = jni::static_ref_cast(__boxedResult);
+ __promise->resolve(__result->toStdString());
+ });
+ __result->cthis()->addOnRejectedListener([=](const jni::alias_ref& __throwable) {
+ jni::JniException __jniError(__throwable);
+ __promise->reject(std::make_exception_ptr(__jniError));
+ });
+ return __promise;
+ }();
+ }
+ std::shared_ptr JHybridBlurHashFactorySpec::decode(const std::string& blurhash, double width, double height, double punch) {
+ static const auto method = _javaPart->javaClassStatic()->getMethod(jni::alias_ref /* blurhash */, double /* width */, double /* height */, double /* punch */)>("decode");
+ auto __result = method(_javaPart, jni::make_jstring(blurhash), width, height, punch);
+ return __result->getJHybridImageSpec();
+ }
+ std::shared_ptr>> JHybridBlurHashFactorySpec::decodeAsync(const std::string& blurhash, double width, double height, double punch) {
+ static const auto method = _javaPart->javaClassStatic()->getMethod(jni::alias_ref /* blurhash */, double /* width */, double /* height */, double /* punch */)>("decodeAsync");
+ auto __result = method(_javaPart, jni::make_jstring(blurhash), width, height, punch);
+ return [&]() {
+ auto __promise = Promise>::create();
+ __result->cthis()->addOnResolvedListener([=](const jni::alias_ref& __boxedResult) {
+ auto __result = jni::static_ref_cast(__boxedResult);
+ __promise->resolve(__result->getJHybridImageSpec());
+ });
+ __result->cthis()->addOnRejectedListener([=](const jni::alias_ref& __throwable) {
+ jni::JniException __jniError(__throwable);
+ __promise->reject(std::make_exception_ptr(__jniError));
+ });
+ return __promise;
+ }();
+ }
+ void JHybridBlurHashFactorySpec::clearCosineCache() {
+ static const auto method = _javaPart->javaClassStatic()->getMethod("clearCosineCache");
+ method(_javaPart);
+ }
+
+} // namespace margelo::nitro::image::blurhash
diff --git a/packages/react-native-nitro-image-blurhash/nitrogen/generated/android/c++/JHybridBlurHashFactorySpec.hpp b/packages/react-native-nitro-image-blurhash/nitrogen/generated/android/c++/JHybridBlurHashFactorySpec.hpp
new file mode 100644
index 00000000..5877edf8
--- /dev/null
+++ b/packages/react-native-nitro-image-blurhash/nitrogen/generated/android/c++/JHybridBlurHashFactorySpec.hpp
@@ -0,0 +1,67 @@
+///
+/// HybridBlurHashFactorySpec.hpp
+/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE.
+/// https://github.com/mrousavy/nitro
+/// Copyright © Marc Rousavy @ Margelo
+///
+
+#pragma once
+
+#include
+#include
+#include "HybridBlurHashFactorySpec.hpp"
+
+
+
+
+namespace margelo::nitro::image::blurhash {
+
+ using namespace facebook;
+
+ class JHybridBlurHashFactorySpec: public virtual HybridBlurHashFactorySpec, public virtual JHybridObject {
+ public:
+ struct JavaPart: public jni::JavaClass {
+ static constexpr auto kJavaDescriptor = "Lcom/margelo/nitro/image/blurhash/HybridBlurHashFactorySpec;";
+ std::shared_ptr getJHybridBlurHashFactorySpec();
+ };
+ struct CxxPart: public jni::HybridClass {
+ static constexpr auto kJavaDescriptor = "Lcom/margelo/nitro/image/blurhash/HybridBlurHashFactorySpec$CxxPart;";
+ static jni::local_ref initHybrid(jni::alias_ref jThis);
+ static void registerNatives();
+ using HybridBase::HybridBase;
+ protected:
+ std::shared_ptr createHybridObject(const jni::local_ref& javaPart) override;
+ };
+
+ public:
+ explicit JHybridBlurHashFactorySpec(const jni::local_ref& javaPart):
+ HybridObject(HybridBlurHashFactorySpec::TAG),
+ JHybridObject(javaPart),
+ _javaPart(jni::make_global(javaPart)) {}
+ ~JHybridBlurHashFactorySpec() override {
+ // Hermes GC can destroy JS objects on a non-JNI Thread.
+ jni::ThreadScope::WithClassLoader([&] { _javaPart.reset(); });
+ }
+
+ public:
+ inline const jni::global_ref& getJavaPart() const noexcept {
+ return _javaPart;
+ }
+
+ public:
+ // Properties
+
+
+ public:
+ // Methods
+ std::string encode(const std::shared_ptr& image, double componentsX, double componentsY) override;
+ std::shared_ptr> encodeAsync(const std::shared_ptr& image, double componentsX, double componentsY) override;
+ std::shared_ptr decode(const std::string& blurhash, double width, double height, double punch) override;
+ std::shared_ptr>> decodeAsync(const std::string& blurhash, double width, double height, double punch) override;
+ void clearCosineCache() override;
+
+ private:
+ jni::global_ref _javaPart;
+ };
+
+} // namespace margelo::nitro::image::blurhash
diff --git a/packages/react-native-nitro-image-blurhash/nitrogen/generated/android/kotlin/com/margelo/nitro/image/blurhash/HybridBlurHashFactorySpec.kt b/packages/react-native-nitro-image-blurhash/nitrogen/generated/android/kotlin/com/margelo/nitro/image/blurhash/HybridBlurHashFactorySpec.kt
new file mode 100644
index 00000000..d64034e1
--- /dev/null
+++ b/packages/react-native-nitro-image-blurhash/nitrogen/generated/android/kotlin/com/margelo/nitro/image/blurhash/HybridBlurHashFactorySpec.kt
@@ -0,0 +1,72 @@
+///
+/// HybridBlurHashFactorySpec.kt
+/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE.
+/// https://github.com/mrousavy/nitro
+/// Copyright © Marc Rousavy @ Margelo
+///
+
+package com.margelo.nitro.image.blurhash
+
+import androidx.annotation.Keep
+import com.facebook.jni.HybridData
+import com.facebook.proguard.annotations.DoNotStrip
+import com.margelo.nitro.image.HybridImageSpec
+import com.margelo.nitro.core.Promise
+import com.margelo.nitro.core.HybridObject
+
+/**
+ * A Kotlin class representing the BlurHashFactory HybridObject.
+ * Implement this abstract class to create Kotlin-based instances of BlurHashFactory.
+ */
+@DoNotStrip
+@Keep
+@Suppress(
+ "KotlinJniMissingFunction", "unused",
+ "RedundantSuppression", "RedundantUnitReturnType", "SimpleRedundantLet",
+ "LocalVariableName", "PropertyName", "PrivatePropertyName", "FunctionName"
+)
+abstract class HybridBlurHashFactorySpec: HybridObject() {
+ // Properties
+
+
+ // Methods
+ @DoNotStrip
+ @Keep
+ abstract fun encode(image: com.margelo.nitro.image.HybridImageSpec, componentsX: Double, componentsY: Double): String
+
+ @DoNotStrip
+ @Keep
+ abstract fun encodeAsync(image: com.margelo.nitro.image.HybridImageSpec, componentsX: Double, componentsY: Double): Promise
+
+ @DoNotStrip
+ @Keep
+ abstract fun decode(blurhash: String, width: Double, height: Double, punch: Double): com.margelo.nitro.image.HybridImageSpec
+
+ @DoNotStrip
+ @Keep
+ abstract fun decodeAsync(blurhash: String, width: Double, height: Double, punch: Double): Promise
+
+ @DoNotStrip
+ @Keep
+ abstract fun clearCosineCache(): Unit
+
+ // Default implementation of `HybridObject.toString()`
+ override fun toString(): String {
+ return "[HybridObject BlurHashFactory]"
+ }
+
+ // C++ backing class
+ @DoNotStrip
+ @Keep
+ protected open class CxxPart(javaPart: HybridBlurHashFactorySpec): HybridObject.CxxPart(javaPart) {
+ // C++ JHybridBlurHashFactorySpec::CxxPart::initHybrid(...)
+ external override fun initHybrid(): HybridData
+ }
+ override fun createCxxPart(): CxxPart {
+ return CxxPart(this)
+ }
+
+ companion object {
+ protected const val TAG = "HybridBlurHashFactorySpec"
+ }
+}
diff --git a/packages/react-native-nitro-image-blurhash/nitrogen/generated/android/kotlin/com/margelo/nitro/image/blurhash/NitroImageBlurhashOnLoad.kt b/packages/react-native-nitro-image-blurhash/nitrogen/generated/android/kotlin/com/margelo/nitro/image/blurhash/NitroImageBlurhashOnLoad.kt
new file mode 100644
index 00000000..71bb1d98
--- /dev/null
+++ b/packages/react-native-nitro-image-blurhash/nitrogen/generated/android/kotlin/com/margelo/nitro/image/blurhash/NitroImageBlurhashOnLoad.kt
@@ -0,0 +1,35 @@
+///
+/// NitroImageBlurhashOnLoad.kt
+/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE.
+/// https://github.com/mrousavy/nitro
+/// Copyright © Marc Rousavy @ Margelo
+///
+
+package com.margelo.nitro.image.blurhash
+
+import android.util.Log
+
+internal class NitroImageBlurhashOnLoad {
+ companion object {
+ private const val TAG = "NitroImageBlurhashOnLoad"
+ private var didLoad = false
+ /**
+ * Initializes the native part of "NitroImageBlurhash".
+ * This method is idempotent and can be called more than once.
+ */
+ @JvmStatic
+ fun initializeNative() {
+ if (didLoad) return
+ try {
+ Log.i(TAG, "Loading NitroImageBlurhash C++ library...")
+ System.loadLibrary("NitroImageBlurhash")
+ Log.i(TAG, "Successfully loaded NitroImageBlurhash C++ library!")
+ didLoad = true
+ } catch (e: Error) {
+ Log.e(TAG, "Failed to load NitroImageBlurhash C++ library! Is it properly installed and linked? " +
+ "Is the name correct? (see `CMakeLists.txt`, at `add_library(...)`)", e)
+ throw e
+ }
+ }
+ }
+}
diff --git a/packages/react-native-nitro-image-blurhash/nitrogen/generated/ios/NitroImageBlurhash+autolinking.rb b/packages/react-native-nitro-image-blurhash/nitrogen/generated/ios/NitroImageBlurhash+autolinking.rb
new file mode 100644
index 00000000..3b50a316
--- /dev/null
+++ b/packages/react-native-nitro-image-blurhash/nitrogen/generated/ios/NitroImageBlurhash+autolinking.rb
@@ -0,0 +1,62 @@
+#
+# NitroImageBlurhash+autolinking.rb
+# This file was generated by nitrogen. DO NOT MODIFY THIS FILE.
+# https://github.com/mrousavy/nitro
+# Copyright © Marc Rousavy @ Margelo
+#
+
+# This is a Ruby script that adds all files generated by Nitrogen
+# to the given podspec.
+#
+# To use it, add this to your .podspec:
+# ```ruby
+# Pod::Spec.new do |spec|
+# # ...
+#
+# # Add all files generated by Nitrogen
+# load 'nitrogen/generated/ios/NitroImageBlurhash+autolinking.rb'
+# add_nitrogen_files(spec)
+# end
+# ```
+
+def add_nitrogen_files(spec)
+ Pod::UI.puts "[NitroModules] 🔥 NitroImageBlurhash is boosted by nitro!"
+
+ spec.dependency "NitroModules"
+
+ current_source_files = Array(spec.attributes_hash['source_files'])
+ spec.source_files = current_source_files + [
+ # Generated cross-platform specs
+ "nitrogen/generated/shared/**/*.{h,hpp,c,cpp,swift}",
+ # Generated bridges for the cross-platform specs
+ "nitrogen/generated/ios/**/*.{h,hpp,c,cpp,mm,swift}",
+ ]
+
+ current_public_header_files = Array(spec.attributes_hash['public_header_files'])
+ spec.public_header_files = current_public_header_files + [
+ # Generated specs
+ "nitrogen/generated/shared/**/*.{h,hpp}",
+ # Swift to C++ bridging helpers
+ "nitrogen/generated/ios/NitroImageBlurhash-Swift-Cxx-Bridge.hpp"
+ ]
+
+ current_private_header_files = Array(spec.attributes_hash['private_header_files'])
+ spec.private_header_files = current_private_header_files + [
+ # iOS specific specs
+ "nitrogen/generated/ios/c++/**/*.{h,hpp}",
+ # Views are framework-specific and should be private
+ "nitrogen/generated/shared/**/views/**/*"
+ ]
+
+ current_pod_target_xcconfig = spec.attributes_hash['pod_target_xcconfig'] || {}
+ spec.pod_target_xcconfig = current_pod_target_xcconfig.merge({
+ # Use C++ 20
+ "CLANG_CXX_LANGUAGE_STANDARD" => "c++20",
+ # Enables C++ <-> Swift interop (by default it's only ObjC)
+ "SWIFT_OBJC_INTEROP_MODE" => "objcxx",
+ # Enables stricter modular headers
+ "DEFINES_MODULE" => "YES",
+ # Disable auto-generated ObjC header for Swift (Static linkage on Xcode 26.4 breaks here)
+ "SWIFT_INSTALL_OBJC_HEADER" => "NO",
+ })
+end
diff --git a/packages/react-native-nitro-image-blurhash/nitrogen/generated/ios/NitroImageBlurhash-Swift-Cxx-Bridge.cpp b/packages/react-native-nitro-image-blurhash/nitrogen/generated/ios/NitroImageBlurhash-Swift-Cxx-Bridge.cpp
new file mode 100644
index 00000000..10f4d89f
--- /dev/null
+++ b/packages/react-native-nitro-image-blurhash/nitrogen/generated/ios/NitroImageBlurhash-Swift-Cxx-Bridge.cpp
@@ -0,0 +1,68 @@
+///
+/// NitroImageBlurhash-Swift-Cxx-Bridge.cpp
+/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE.
+/// https://github.com/mrousavy/nitro
+/// Copyright © Marc Rousavy @ Margelo
+///
+
+#include "NitroImageBlurhash-Swift-Cxx-Bridge.hpp"
+
+// Include C++ implementation defined types
+#include "HybridBlurHashFactorySpecSwift.hpp"
+#include "NitroImageBlurhash-Swift-Cxx-Umbrella.hpp"
+#include
+#include
+
+namespace margelo::nitro::image::blurhash::bridge::swift {
+
+ // pragma MARK: std::shared_ptr
+ std::shared_ptr create_std__shared_ptr_margelo__nitro__image__HybridImageSpec_(void* NON_NULL swiftUnsafePointer) noexcept {
+ // Implemented in NitroImage
+ return margelo::nitro::image::bridge::swift::create_std__shared_ptr_HybridImageSpec_(swiftUnsafePointer);
+ }
+ void* NON_NULL get_std__shared_ptr_margelo__nitro__image__HybridImageSpec_(std__shared_ptr_margelo__nitro__image__HybridImageSpec_ cppType) {
+ // Implemented in NitroImage
+ return margelo::nitro::image::bridge::swift::get_std__shared_ptr_HybridImageSpec_(cppType);
+ }
+
+ // pragma MARK: std::function
+ Func_void_std__string create_Func_void_std__string(void* NON_NULL swiftClosureWrapper) noexcept {
+ auto swiftClosure = NitroImageBlurhash::Func_void_std__string::fromUnsafe(swiftClosureWrapper);
+ return [swiftClosure = std::move(swiftClosure)](const std::string& result) mutable -> void {
+ swiftClosure.call(result);
+ };
+ }
+
+ // pragma MARK: std::function
+ Func_void_std__exception_ptr create_Func_void_std__exception_ptr(void* NON_NULL swiftClosureWrapper) noexcept {
+ auto swiftClosure = NitroImageBlurhash::Func_void_std__exception_ptr::fromUnsafe(swiftClosureWrapper);
+ return [swiftClosure = std::move(swiftClosure)](const std::exception_ptr& error) mutable -> void {
+ swiftClosure.call(error);
+ };
+ }
+
+ // pragma MARK: std::function& /* result */)>
+ Func_void_std__shared_ptr_margelo__nitro__image__HybridImageSpec_ create_Func_void_std__shared_ptr_margelo__nitro__image__HybridImageSpec_(void* NON_NULL swiftClosureWrapper) noexcept {
+ auto swiftClosure = NitroImageBlurhash::Func_void_std__shared_ptr_margelo__nitro__image__HybridImageSpec_::fromUnsafe(swiftClosureWrapper);
+ return [swiftClosure = std::move(swiftClosure)](const std::shared_ptr& result) mutable -> void {
+ swiftClosure.call(result);
+ };
+ }
+
+ // pragma MARK: std::shared_ptr
+ std::shared_ptr create_std__shared_ptr_HybridBlurHashFactorySpec_(void* NON_NULL swiftUnsafePointer) noexcept {
+ NitroImageBlurhash::HybridBlurHashFactorySpec_cxx swiftPart = NitroImageBlurhash::HybridBlurHashFactorySpec_cxx::fromUnsafe(swiftUnsafePointer);
+ return std::make_shared(swiftPart);
+ }
+ void* NON_NULL get_std__shared_ptr_HybridBlurHashFactorySpec_(std__shared_ptr_HybridBlurHashFactorySpec_ cppType) {
+ std::shared_ptr swiftWrapper = std::dynamic_pointer_cast(cppType);
+ #ifdef NITRO_DEBUG
+ if (swiftWrapper == nullptr) [[unlikely]] {
+ throw std::runtime_error("Class \"HybridBlurHashFactorySpec\" is not implemented in Swift!");
+ }
+ #endif
+ NitroImageBlurhash::HybridBlurHashFactorySpec_cxx& swiftPart = swiftWrapper->getSwiftPart();
+ return swiftPart.toUnsafe();
+ }
+
+} // namespace margelo::nitro::image::blurhash::bridge::swift
diff --git a/packages/react-native-nitro-image-blurhash/nitrogen/generated/ios/NitroImageBlurhash-Swift-Cxx-Bridge.hpp b/packages/react-native-nitro-image-blurhash/nitrogen/generated/ios/NitroImageBlurhash-Swift-Cxx-Bridge.hpp
new file mode 100644
index 00000000..c84f4e40
--- /dev/null
+++ b/packages/react-native-nitro-image-blurhash/nitrogen/generated/ios/NitroImageBlurhash-Swift-Cxx-Bridge.hpp
@@ -0,0 +1,198 @@
+///
+/// NitroImageBlurhash-Swift-Cxx-Bridge.hpp
+/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE.
+/// https://github.com/mrousavy/nitro
+/// Copyright © Marc Rousavy @ Margelo
+///
+
+#pragma once
+
+// Forward declarations of C++ defined types
+// Forward declaration of `HybridBlurHashFactorySpec` to properly resolve imports.
+namespace margelo::nitro::image::blurhash { class HybridBlurHashFactorySpec; }
+// Forward declaration of `HybridImageSpec` to properly resolve imports.
+namespace margelo::nitro::image { class HybridImageSpec; }
+
+// Forward declarations of Swift defined types
+// Forward declaration of `HybridBlurHashFactorySpec_cxx` to properly resolve imports.
+namespace NitroImageBlurhash { class HybridBlurHashFactorySpec_cxx; }
+// Forward declaration of `HybridImageSpec_cxx` to properly resolve imports.
+namespace NitroImage { class HybridImageSpec_cxx; }
+
+// Include C++ defined types
+#include "HybridBlurHashFactorySpec.hpp"
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+/**
+ * Contains specialized versions of C++ templated types so they can be accessed from Swift,
+ * as well as helper functions to interact with those C++ types from Swift.
+ */
+namespace margelo::nitro::image::blurhash::bridge::swift {
+
+ // pragma MARK: std::shared_ptr
+ /**
+ * Specialized version of `std::shared_ptr`.
+ */
+ using std__shared_ptr_margelo__nitro__image__HybridImageSpec_ = std::shared_ptr;
+ std::shared_ptr create_std__shared_ptr_margelo__nitro__image__HybridImageSpec_(void* NON_NULL swiftUnsafePointer) noexcept;
+ void* NON_NULL get_std__shared_ptr_margelo__nitro__image__HybridImageSpec_(std__shared_ptr_margelo__nitro__image__HybridImageSpec_ cppType);
+
+ // pragma MARK: std::weak_ptr
+ using std__weak_ptr_margelo__nitro__image__HybridImageSpec_ = std::weak_ptr;
+ inline std__weak_ptr_margelo__nitro__image__HybridImageSpec_ weakify_std__shared_ptr_margelo__nitro__image__HybridImageSpec_(const std::shared_ptr& strong) noexcept { return strong; }
+
+ // pragma MARK: std::shared_ptr>
+ /**
+ * Specialized version of `std::shared_ptr>`.
+ */
+ using std__shared_ptr_Promise_std__string__ = std::shared_ptr>;
+ inline std::shared_ptr> create_std__shared_ptr_Promise_std__string__() noexcept {
+ return Promise::create();
+ }
+ inline PromiseHolder wrap_std__shared_ptr_Promise_std__string__(std::shared_ptr> promise) noexcept {
+ return PromiseHolder(std::move(promise));
+ }
+
+ // pragma MARK: std::function
+ /**
+ * Specialized version of `std::function`.
+ */
+ using Func_void_std__string = std::function;
+ /**
+ * Wrapper class for a `std::function`, this can be used from Swift.
+ */
+ class Func_void_std__string_Wrapper final {
+ public:
+ explicit Func_void_std__string_Wrapper(std::function&& func): _function(std::make_unique>(std::move(func))) {}
+ inline void call(std::string result) const noexcept {
+ _function->operator()(result);
+ }
+ private:
+ std::unique_ptr> _function;
+ } SWIFT_NONCOPYABLE;
+ Func_void_std__string create_Func_void_std__string(void* NON_NULL swiftClosureWrapper) noexcept;
+ inline Func_void_std__string_Wrapper wrap_Func_void_std__string(Func_void_std__string value) noexcept {
+ return Func_void_std__string_Wrapper(std::move(value));
+ }
+
+ // pragma MARK: std::function
+ /**
+ * Specialized version of `std::function`.
+ */
+ using Func_void_std__exception_ptr = std::function;
+ /**
+ * Wrapper class for a `std::function`, this can be used from Swift.
+ */
+ class Func_void_std__exception_ptr_Wrapper final {
+ public:
+ explicit Func_void_std__exception_ptr_Wrapper(std::function&& func): _function(std::make_unique>(std::move(func))) {}
+ inline void call(std::exception_ptr error) const noexcept {
+ _function->operator()(error);
+ }
+ private:
+ std::unique_ptr> _function;
+ } SWIFT_NONCOPYABLE;
+ Func_void_std__exception_ptr create_Func_void_std__exception_ptr(void* NON_NULL swiftClosureWrapper) noexcept;
+ inline Func_void_std__exception_ptr_Wrapper wrap_Func_void_std__exception_ptr(Func_void_std__exception_ptr value) noexcept {
+ return Func_void_std__exception_ptr_Wrapper(std::move(value));
+ }
+
+ // pragma MARK: std::shared_ptr>>
+ /**
+ * Specialized version of `std::shared_ptr>>`.
+ */
+ using std__shared_ptr_Promise_std__shared_ptr_margelo__nitro__image__HybridImageSpec___ = std::shared_ptr>>;
+ inline std::shared_ptr>> create_std__shared_ptr_Promise_std__shared_ptr_margelo__nitro__image__HybridImageSpec___() noexcept {
+ return Promise>::create();
+ }
+ inline PromiseHolder> wrap_std__shared_ptr_Promise_std__shared_ptr_margelo__nitro__image__HybridImageSpec___(std::shared_ptr>> promise) noexcept {
+ return PromiseHolder>(std::move(promise));
+ }
+
+ // pragma MARK: std::function& /* result */)>
+ /**
+ * Specialized version of `std::function&)>`.
+ */
+ using Func_void_std__shared_ptr_margelo__nitro__image__HybridImageSpec_ = std::function& /* result */)>;
+ /**
+ * Wrapper class for a `std::function& / * result * /)>`, this can be used from Swift.
+ */
+ class Func_void_std__shared_ptr_margelo__nitro__image__HybridImageSpec__Wrapper final {
+ public:
+ explicit Func_void_std__shared_ptr_margelo__nitro__image__HybridImageSpec__Wrapper(std::function& /* result */)>&& func): _function(std::make_unique& /* result */)>>(std::move(func))) {}
+ inline void call(std::shared_ptr result) const noexcept {
+ _function->operator()(result);
+ }
+ private:
+ std::unique_ptr& /* result */)>> _function;
+ } SWIFT_NONCOPYABLE;
+ Func_void_std__shared_ptr_margelo__nitro__image__HybridImageSpec_ create_Func_void_std__shared_ptr_margelo__nitro__image__HybridImageSpec_(void* NON_NULL swiftClosureWrapper) noexcept;
+ inline Func_void_std__shared_ptr_margelo__nitro__image__HybridImageSpec__Wrapper wrap_Func_void_std__shared_ptr_margelo__nitro__image__HybridImageSpec_(Func_void_std__shared_ptr_margelo__nitro__image__HybridImageSpec_ value) noexcept {
+ return Func_void_std__shared_ptr_margelo__nitro__image__HybridImageSpec__Wrapper(std::move(value));
+ }
+
+ // pragma MARK: std::shared_ptr
+ /**
+ * Specialized version of `std::shared_ptr`.
+ */
+ using std__shared_ptr_HybridBlurHashFactorySpec_ = std::shared_ptr;
+ std::shared_ptr create_std__shared_ptr_HybridBlurHashFactorySpec_(void* NON_NULL swiftUnsafePointer) noexcept;
+ void* NON_NULL get_std__shared_ptr_HybridBlurHashFactorySpec_(std__shared_ptr_HybridBlurHashFactorySpec_ cppType);
+
+ // pragma MARK: std::weak_ptr
+ using std__weak_ptr_HybridBlurHashFactorySpec_ = std::weak_ptr;
+ inline std__weak_ptr_HybridBlurHashFactorySpec_ weakify_std__shared_ptr_HybridBlurHashFactorySpec_(const std::shared_ptr& strong) noexcept { return strong; }
+
+ // pragma MARK: Result
+ using Result_std__string_ = Result;
+ inline Result_std__string_ create_Result_std__string_(const std::string& value) noexcept {
+ return Result::withValue(value);
+ }
+ inline Result_std__string_ create_Result_std__string_(const std::exception_ptr& error) noexcept {
+ return Result::withError(error);
+ }
+
+ // pragma MARK: Result>>
+ using Result_std__shared_ptr_Promise_std__string___ = Result>>;
+ inline Result_std__shared_ptr_Promise_std__string___ create_Result_std__shared_ptr_Promise_std__string___(const std::shared_ptr>& value) noexcept {
+ return Result>>::withValue(value);
+ }
+ inline Result_std__shared_ptr_Promise_std__string___ create_Result_std__shared_ptr_Promise_std__string___(const std::exception_ptr& error) noexcept {
+ return Result>>::withError(error);
+ }
+
+ // pragma MARK: Result>
+ using Result_std__shared_ptr_margelo__nitro__image__HybridImageSpec__ = Result>;
+ inline Result_std__shared_ptr_margelo__nitro__image__HybridImageSpec__ create_Result_std__shared_ptr_margelo__nitro__image__HybridImageSpec__(const std::shared_ptr& value) noexcept {
+ return Result>::withValue(value);
+ }
+ inline Result_std__shared_ptr_margelo__nitro__image__HybridImageSpec__ create_Result_std__shared_ptr_margelo__nitro__image__HybridImageSpec__(const std::exception_ptr& error) noexcept {
+ return Result>::withError(error);
+ }
+
+ // pragma MARK: Result>>>
+ using Result_std__shared_ptr_Promise_std__shared_ptr_margelo__nitro__image__HybridImageSpec____ = Result>>>;
+ inline Result_std__shared_ptr_Promise_std__shared_ptr_margelo__nitro__image__HybridImageSpec____ create_Result_std__shared_ptr_Promise_std__shared_ptr_margelo__nitro__image__HybridImageSpec____(const std::shared_ptr>>& value) noexcept {
+ return Result>>>::withValue(value);
+ }
+ inline Result_std__shared_ptr_Promise_std__shared_ptr_margelo__nitro__image__HybridImageSpec____ create_Result_std__shared_ptr_Promise_std__shared_ptr_margelo__nitro__image__HybridImageSpec____(const std::exception_ptr& error) noexcept {
+ return Result>>>::withError(error);
+ }
+
+ // pragma MARK: Result
+ using Result_void_ = Result;
+ inline Result_void_ create_Result_void_() noexcept {
+ return Result::withValue();
+ }
+ inline Result_void_ create_Result_void_(const std::exception_ptr& error) noexcept {
+ return Result::withError(error);
+ }
+
+} // namespace margelo::nitro::image::blurhash::bridge::swift
diff --git a/packages/react-native-nitro-image-blurhash/nitrogen/generated/ios/NitroImageBlurhash-Swift-Cxx-Umbrella.hpp b/packages/react-native-nitro-image-blurhash/nitrogen/generated/ios/NitroImageBlurhash-Swift-Cxx-Umbrella.hpp
new file mode 100644
index 00000000..8197bcdc
--- /dev/null
+++ b/packages/react-native-nitro-image-blurhash/nitrogen/generated/ios/NitroImageBlurhash-Swift-Cxx-Umbrella.hpp
@@ -0,0 +1,50 @@
+///
+/// NitroImageBlurhash-Swift-Cxx-Umbrella.hpp
+/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE.
+/// https://github.com/mrousavy/nitro
+/// Copyright © Marc Rousavy @ Margelo
+///
+
+#pragma once
+
+// Forward declarations of C++ defined types
+// Forward declaration of `HybridBlurHashFactorySpec` to properly resolve imports.
+namespace margelo::nitro::image::blurhash { class HybridBlurHashFactorySpec; }
+// Forward declaration of `HybridImageSpec` to properly resolve imports.
+namespace margelo::nitro::image { class HybridImageSpec; }
+
+// Include C++ defined types
+#include "HybridBlurHashFactorySpec.hpp"
+#include
+#include
+#include
+#include
+#include
+#include
+
+// C++ helpers for Swift
+#include "NitroImageBlurhash-Swift-Cxx-Bridge.hpp"
+
+// Common C++ types used in Swift
+#include
+#include
+#include
+#include
+
+// Forward declarations of Swift defined types
+// Forward declaration of `HybridBlurHashFactorySpec_cxx` to properly resolve imports.
+namespace NitroImageBlurhash { class HybridBlurHashFactorySpec_cxx; }
+// Forward declaration of `HybridImageSpec_cxx` to properly resolve imports.
+namespace NitroImage { class HybridImageSpec_cxx; }
+
+// Include Swift defined types
+#if __has_include("NitroImageBlurhash-Swift.h")
+// This header is generated by Xcode/Swift on every app build.
+// If it cannot be found, make sure the Swift module's name (= podspec name) is actually "NitroImageBlurhash".
+#include "NitroImageBlurhash-Swift.h"
+// Same as above, but used when building with frameworks (`use_frameworks`)
+#elif __has_include()
+#include
+#else
+#error NitroImageBlurhash's autogenerated Swift header cannot be found! Make sure the Swift module's name (= podspec name) is actually "NitroImageBlurhash", and try building the app first.
+#endif
diff --git a/packages/react-native-nitro-image-blurhash/nitrogen/generated/ios/NitroImageBlurhashAutolinking.mm b/packages/react-native-nitro-image-blurhash/nitrogen/generated/ios/NitroImageBlurhashAutolinking.mm
new file mode 100644
index 00000000..eb3d4a87
--- /dev/null
+++ b/packages/react-native-nitro-image-blurhash/nitrogen/generated/ios/NitroImageBlurhashAutolinking.mm
@@ -0,0 +1,33 @@
+///
+/// NitroImageBlurhashAutolinking.mm
+/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE.
+/// https://github.com/mrousavy/nitro
+/// Copyright © Marc Rousavy @ Margelo
+///
+
+#import
+#import
+#import "NitroImageBlurhash-Swift-Cxx-Umbrella.hpp"
+#import
+
+#include "HybridBlurHashFactorySpecSwift.hpp"
+
+@interface NitroImageBlurhashAutolinking : NSObject
+@end
+
+@implementation NitroImageBlurhashAutolinking
+
++ (void) load {
+ using namespace margelo::nitro;
+ using namespace margelo::nitro::image::blurhash;
+
+ HybridObjectRegistry::registerHybridObjectConstructor(
+ "BlurHashFactory",
+ []() -> std::shared_ptr {
+ std::shared_ptr hybridObject = NitroImageBlurhash::NitroImageBlurhashAutolinking::createBlurHashFactory();
+ return hybridObject;
+ }
+ );
+}
+
+@end
diff --git a/packages/react-native-nitro-image-blurhash/nitrogen/generated/ios/NitroImageBlurhashAutolinking.swift b/packages/react-native-nitro-image-blurhash/nitrogen/generated/ios/NitroImageBlurhashAutolinking.swift
new file mode 100644
index 00000000..d3c33fc3
--- /dev/null
+++ b/packages/react-native-nitro-image-blurhash/nitrogen/generated/ios/NitroImageBlurhashAutolinking.swift
@@ -0,0 +1,26 @@
+///
+/// NitroImageBlurhashAutolinking.swift
+/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE.
+/// https://github.com/mrousavy/nitro
+/// Copyright © Marc Rousavy @ Margelo
+///
+
+import NitroModules
+
+// TODO: Use empty enums once Swift supports exporting them as namespaces
+// See: https://github.com/swiftlang/swift/pull/83616
+public final class NitroImageBlurhashAutolinking {
+ public typealias bridge = margelo.nitro.image.blurhash.bridge.swift
+
+ public static func createBlurHashFactory() -> bridge.std__shared_ptr_HybridBlurHashFactorySpec_ {
+ let hybridObject = HybridBlurHashFactory()
+ return { () -> bridge.std__shared_ptr_HybridBlurHashFactorySpec_ in
+ let __cxxWrapped = hybridObject.getCxxWrapper()
+ return __cxxWrapped.getCxxPart()
+ }()
+ }
+
+ public static func isBlurHashFactoryRecyclable() -> Bool {
+ return HybridBlurHashFactory.self is any RecyclableView.Type
+ }
+}
diff --git a/packages/react-native-nitro-image-blurhash/nitrogen/generated/ios/c++/HybridBlurHashFactorySpecSwift.cpp b/packages/react-native-nitro-image-blurhash/nitrogen/generated/ios/c++/HybridBlurHashFactorySpecSwift.cpp
new file mode 100644
index 00000000..3e042e4a
--- /dev/null
+++ b/packages/react-native-nitro-image-blurhash/nitrogen/generated/ios/c++/HybridBlurHashFactorySpecSwift.cpp
@@ -0,0 +1,11 @@
+///
+/// HybridBlurHashFactorySpecSwift.cpp
+/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE.
+/// https://github.com/mrousavy/nitro
+/// Copyright © Marc Rousavy @ Margelo
+///
+
+#include "HybridBlurHashFactorySpecSwift.hpp"
+
+namespace margelo::nitro::image::blurhash {
+} // namespace margelo::nitro::image::blurhash
diff --git a/packages/react-native-nitro-image-blurhash/nitrogen/generated/ios/c++/HybridBlurHashFactorySpecSwift.hpp b/packages/react-native-nitro-image-blurhash/nitrogen/generated/ios/c++/HybridBlurHashFactorySpecSwift.hpp
new file mode 100644
index 00000000..8d379788
--- /dev/null
+++ b/packages/react-native-nitro-image-blurhash/nitrogen/generated/ios/c++/HybridBlurHashFactorySpecSwift.hpp
@@ -0,0 +1,116 @@
+///
+/// HybridBlurHashFactorySpecSwift.hpp
+/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE.
+/// https://github.com/mrousavy/nitro
+/// Copyright © Marc Rousavy @ Margelo
+///
+
+#pragma once
+
+#include "HybridBlurHashFactorySpec.hpp"
+
+// Forward declaration of `HybridBlurHashFactorySpec_cxx` to properly resolve imports.
+namespace NitroImageBlurhash { class HybridBlurHashFactorySpec_cxx; }
+
+// Forward declaration of `HybridImageSpec` to properly resolve imports.
+namespace margelo::nitro::image { class HybridImageSpec; }
+
+#include
+#include
+#include
+#include
+
+#include "NitroImageBlurhash-Swift-Cxx-Umbrella.hpp"
+
+namespace margelo::nitro::image::blurhash {
+
+ /**
+ * The C++ part of HybridBlurHashFactorySpec_cxx.swift.
+ *
+ * HybridBlurHashFactorySpecSwift (C++) accesses HybridBlurHashFactorySpec_cxx (Swift), and might
+ * contain some additional bridging code for C++ <> Swift interop.
+ *
+ * Since this obviously introduces an overhead, I hope at some point in
+ * the future, HybridBlurHashFactorySpec_cxx can directly inherit from the C++ class HybridBlurHashFactorySpec
+ * to simplify the whole structure and memory management.
+ */
+ class HybridBlurHashFactorySpecSwift: public virtual HybridBlurHashFactorySpec {
+ public:
+ // Constructor from a Swift instance
+ explicit HybridBlurHashFactorySpecSwift(const NitroImageBlurhash::HybridBlurHashFactorySpec_cxx& swiftPart):
+ HybridObject(HybridBlurHashFactorySpec::TAG),
+ _swiftPart(swiftPart) { }
+
+ public:
+ // Get the Swift part
+ inline NitroImageBlurhash::HybridBlurHashFactorySpec_cxx& getSwiftPart() noexcept {
+ return _swiftPart;
+ }
+
+ public:
+ inline size_t getExternalMemorySize() noexcept override {
+ return _swiftPart.getMemorySize();
+ }
+ bool equals(const std::shared_ptr& other) override {
+ if (auto otherCast = std::dynamic_pointer_cast(other)) {
+ return _swiftPart.equals(otherCast->_swiftPart);
+ }
+ return false;
+ }
+ void dispose() noexcept override {
+ _swiftPart.dispose();
+ }
+ std::string toString() override {
+ return _swiftPart.toString();
+ }
+
+ public:
+ // Properties
+
+
+ public:
+ // Methods
+ inline std::string encode(const std::shared_ptr& image, double componentsX, double componentsY) override {
+ auto __result = _swiftPart.encode(image, std::forward(componentsX), std::forward(componentsY));
+ if (__result.hasError()) [[unlikely]] {
+ std::rethrow_exception(__result.error());
+ }
+ auto __value = std::move(__result.value());
+ return __value;
+ }
+ inline std::shared_ptr> encodeAsync(const std::shared_ptr& image, double componentsX, double componentsY) override {
+ auto __result = _swiftPart.encodeAsync(image, std::forward(componentsX), std::forward(componentsY));
+ if (__result.hasError()) [[unlikely]] {
+ std::rethrow_exception(__result.error());
+ }
+ auto __value = std::move(__result.value());
+ return __value;
+ }
+ inline std::shared_ptr decode(const std::string& blurhash, double width, double height, double punch) override {
+ auto __result = _swiftPart.decode(blurhash, std::forward(width), std::forward(height), std::forward(punch));
+ if (__result.hasError()) [[unlikely]] {
+ std::rethrow_exception(__result.error());
+ }
+ auto __value = std::move(__result.value());
+ return __value;
+ }
+ inline std::shared_ptr>> decodeAsync(const std::string& blurhash, double width, double height, double punch) override {
+ auto __result = _swiftPart.decodeAsync(blurhash, std::forward(width), std::forward(height), std::forward(punch));
+ if (__result.hasError()) [[unlikely]] {
+ std::rethrow_exception(__result.error());
+ }
+ auto __value = std::move(__result.value());
+ return __value;
+ }
+ inline void clearCosineCache() override {
+ auto __result = _swiftPart.clearCosineCache();
+ if (__result.hasError()) [[unlikely]] {
+ std::rethrow_exception(__result.error());
+ }
+ }
+
+ private:
+ NitroImageBlurhash::HybridBlurHashFactorySpec_cxx _swiftPart;
+ };
+
+} // namespace margelo::nitro::image::blurhash
diff --git a/packages/react-native-nitro-image-blurhash/nitrogen/generated/ios/swift/Func_void_std__exception_ptr.swift b/packages/react-native-nitro-image-blurhash/nitrogen/generated/ios/swift/Func_void_std__exception_ptr.swift
new file mode 100644
index 00000000..2da53ca3
--- /dev/null
+++ b/packages/react-native-nitro-image-blurhash/nitrogen/generated/ios/swift/Func_void_std__exception_ptr.swift
@@ -0,0 +1,46 @@
+///
+/// Func_void_std__exception_ptr.swift
+/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE.
+/// https://github.com/mrousavy/nitro
+/// Copyright © Marc Rousavy @ Margelo
+///
+
+import NitroModules
+
+/**
+ * Wraps a Swift `(_ error: Error) -> Void` as a class.
+ * This class can be used from C++, e.g. to wrap the Swift closure as a `std::function`.
+ */
+public final class Func_void_std__exception_ptr {
+ public typealias bridge = margelo.nitro.image.blurhash.bridge.swift
+
+ private let closure: (_ error: Error) -> Void
+
+ public init(_ closure: @escaping (_ error: Error) -> Void) {
+ self.closure = closure
+ }
+
+ @inline(__always)
+ public func call(error: std.exception_ptr) -> Void {
+ self.closure(RuntimeError.from(cppError: error))
+ }
+
+ /**
+ * Casts this instance to a retained unsafe raw pointer.
+ * This acquires one additional strong reference on the object!
+ */
+ @inline(__always)
+ public func toUnsafe() -> UnsafeMutableRawPointer {
+ return Unmanaged.passRetained(self).toOpaque()
+ }
+
+ /**
+ * Casts an unsafe pointer to a `Func_void_std__exception_ptr`.
+ * The pointer has to be a retained opaque `Unmanaged`.
+ * This removes one strong reference from the object!
+ */
+ @inline(__always)
+ public static func fromUnsafe(_ pointer: UnsafeMutableRawPointer) -> Func_void_std__exception_ptr {
+ return Unmanaged.fromOpaque(pointer).takeRetainedValue()
+ }
+}
diff --git a/packages/react-native-nitro-image-blurhash/nitrogen/generated/ios/swift/Func_void_std__shared_ptr_margelo__nitro__image__HybridImageSpec_.swift b/packages/react-native-nitro-image-blurhash/nitrogen/generated/ios/swift/Func_void_std__shared_ptr_margelo__nitro__image__HybridImageSpec_.swift
new file mode 100644
index 00000000..b1f29b5b
--- /dev/null
+++ b/packages/react-native-nitro-image-blurhash/nitrogen/generated/ios/swift/Func_void_std__shared_ptr_margelo__nitro__image__HybridImageSpec_.swift
@@ -0,0 +1,51 @@
+///
+/// Func_void_std__shared_ptr_margelo__nitro__image__HybridImageSpec_.swift
+/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE.
+/// https://github.com/mrousavy/nitro
+/// Copyright © Marc Rousavy @ Margelo
+///
+
+import NitroImage
+import NitroModules
+
+/**
+ * Wraps a Swift `(_ value: (any HybridImageSpec)) -> Void` as a class.
+ * This class can be used from C++, e.g. to wrap the Swift closure as a `std::function`.
+ */
+public final class Func_void_std__shared_ptr_margelo__nitro__image__HybridImageSpec_ {
+ public typealias bridge = margelo.nitro.image.blurhash.bridge.swift
+
+ private let closure: (_ value: (any HybridImageSpec)) -> Void
+
+ public init(_ closure: @escaping (_ value: (any HybridImageSpec)) -> Void) {
+ self.closure = closure
+ }
+
+ @inline(__always)
+ public func call(value: bridge.std__shared_ptr_margelo__nitro__image__HybridImageSpec_) -> Void {
+ self.closure({ () -> any HybridImageSpec in
+ let __unsafePointer = bridge.get_std__shared_ptr_margelo__nitro__image__HybridImageSpec_(value)
+ let __instance = HybridImageSpec_cxx.fromUnsafe(__unsafePointer)
+ return __instance.getHybridImageSpec()
+ }())
+ }
+
+ /**
+ * Casts this instance to a retained unsafe raw pointer.
+ * This acquires one additional strong reference on the object!
+ */
+ @inline(__always)
+ public func toUnsafe() -> UnsafeMutableRawPointer {
+ return Unmanaged.passRetained(self).toOpaque()
+ }
+
+ /**
+ * Casts an unsafe pointer to a `Func_void_std__shared_ptr_margelo__nitro__image__HybridImageSpec_`.
+ * The pointer has to be a retained opaque `Unmanaged`.
+ * This removes one strong reference from the object!
+ */
+ @inline(__always)
+ public static func fromUnsafe(_ pointer: UnsafeMutableRawPointer) -> Func_void_std__shared_ptr_margelo__nitro__image__HybridImageSpec_ {
+ return Unmanaged.fromOpaque(pointer).takeRetainedValue()
+ }
+}
diff --git a/packages/react-native-nitro-image-blurhash/nitrogen/generated/ios/swift/Func_void_std__string.swift b/packages/react-native-nitro-image-blurhash/nitrogen/generated/ios/swift/Func_void_std__string.swift
new file mode 100644
index 00000000..73603db2
--- /dev/null
+++ b/packages/react-native-nitro-image-blurhash/nitrogen/generated/ios/swift/Func_void_std__string.swift
@@ -0,0 +1,46 @@
+///
+/// Func_void_std__string.swift
+/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE.
+/// https://github.com/mrousavy/nitro
+/// Copyright © Marc Rousavy @ Margelo
+///
+
+import NitroModules
+
+/**
+ * Wraps a Swift `(_ value: String) -> Void` as a class.
+ * This class can be used from C++, e.g. to wrap the Swift closure as a `std::function`.
+ */
+public final class Func_void_std__string {
+ public typealias bridge = margelo.nitro.image.blurhash.bridge.swift
+
+ private let closure: (_ value: String) -> Void
+
+ public init(_ closure: @escaping (_ value: String) -> Void) {
+ self.closure = closure
+ }
+
+ @inline(__always)
+ public func call(value: std.string) -> Void {
+ self.closure(String(value))
+ }
+
+ /**
+ * Casts this instance to a retained unsafe raw pointer.
+ * This acquires one additional strong reference on the object!
+ */
+ @inline(__always)
+ public func toUnsafe() -> UnsafeMutableRawPointer {
+ return Unmanaged.passRetained(self).toOpaque()
+ }
+
+ /**
+ * Casts an unsafe pointer to a `Func_void_std__string`.
+ * The pointer has to be a retained opaque `Unmanaged`.
+ * This removes one strong reference from the object!
+ */
+ @inline(__always)
+ public static func fromUnsafe(_ pointer: UnsafeMutableRawPointer) -> Func_void_std__string {
+ return Unmanaged.fromOpaque(pointer).takeRetainedValue()
+ }
+}
diff --git a/packages/react-native-nitro-image-blurhash/nitrogen/generated/ios/swift/HybridBlurHashFactorySpec.swift b/packages/react-native-nitro-image-blurhash/nitrogen/generated/ios/swift/HybridBlurHashFactorySpec.swift
new file mode 100644
index 00000000..e6684b2c
--- /dev/null
+++ b/packages/react-native-nitro-image-blurhash/nitrogen/generated/ios/swift/HybridBlurHashFactorySpec.swift
@@ -0,0 +1,60 @@
+///
+/// HybridBlurHashFactorySpec.swift
+/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE.
+/// https://github.com/mrousavy/nitro
+/// Copyright © Marc Rousavy @ Margelo
+///
+
+import NitroImage
+import NitroModules
+
+/// See ``HybridBlurHashFactorySpec``
+public protocol HybridBlurHashFactorySpec_protocol: HybridObject {
+ // Properties
+
+
+ // Methods
+ func encode(image: (any HybridImageSpec), componentsX: Double, componentsY: Double) throws -> String
+ func encodeAsync(image: (any HybridImageSpec), componentsX: Double, componentsY: Double) throws -> Promise
+ func decode(blurhash: String, width: Double, height: Double, punch: Double) throws -> (any HybridImageSpec)
+ func decodeAsync(blurhash: String, width: Double, height: Double, punch: Double) throws -> Promise<(any HybridImageSpec)>
+ func clearCosineCache() throws -> Void
+}
+
+public extension HybridBlurHashFactorySpec_protocol {
+ /// Default implementation of ``HybridObject.toString``
+ func toString() -> String {
+ return "[HybridObject BlurHashFactory]"
+ }
+}
+
+/// See ``HybridBlurHashFactorySpec``
+open class HybridBlurHashFactorySpec_base {
+ private weak var cxxWrapper: HybridBlurHashFactorySpec_cxx? = nil
+ public init() { }
+ public func getCxxWrapper() -> HybridBlurHashFactorySpec_cxx {
+ #if DEBUG
+ guard self is any HybridBlurHashFactorySpec else {
+ fatalError("`self` is not a `HybridBlurHashFactorySpec`! Did you accidentally inherit from `HybridBlurHashFactorySpec_base` instead of `HybridBlurHashFactorySpec`?")
+ }
+ #endif
+ if let cxxWrapper = self.cxxWrapper {
+ return cxxWrapper
+ } else {
+ let cxxWrapper = HybridBlurHashFactorySpec_cxx(self as! any HybridBlurHashFactorySpec)
+ self.cxxWrapper = cxxWrapper
+ return cxxWrapper
+ }
+ }
+}
+
+/**
+ * A Swift base-protocol representing the BlurHashFactory HybridObject.
+ * Implement this protocol to create Swift-based instances of BlurHashFactory.
+ * ```swift
+ * class HybridBlurHashFactory : HybridBlurHashFactorySpec {
+ * // ...
+ * }
+ * ```
+ */
+public typealias HybridBlurHashFactorySpec = HybridBlurHashFactorySpec_protocol & HybridBlurHashFactorySpec_base
diff --git a/packages/react-native-nitro-image-blurhash/nitrogen/generated/ios/swift/HybridBlurHashFactorySpec_cxx.swift b/packages/react-native-nitro-image-blurhash/nitrogen/generated/ios/swift/HybridBlurHashFactorySpec_cxx.swift
new file mode 100644
index 00000000..43494600
--- /dev/null
+++ b/packages/react-native-nitro-image-blurhash/nitrogen/generated/ios/swift/HybridBlurHashFactorySpec_cxx.swift
@@ -0,0 +1,214 @@
+///
+/// HybridBlurHashFactorySpec_cxx.swift
+/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE.
+/// https://github.com/mrousavy/nitro
+/// Copyright © Marc Rousavy @ Margelo
+///
+
+import NitroModules
+import NitroImage
+
+/**
+ * A class implementation that bridges HybridBlurHashFactorySpec over to C++.
+ * In C++, we cannot use Swift protocols - so we need to wrap it in a class to make it strongly defined.
+ *
+ * Also, some Swift types need to be bridged with special handling:
+ * - Enums need to be wrapped in Structs, otherwise they cannot be accessed bi-directionally (Swift bug: https://github.com/swiftlang/swift/issues/75330)
+ * - Other HybridObjects need to be wrapped/unwrapped from the Swift TCxx wrapper
+ * - Throwing methods need to be wrapped with a Result type, as exceptions cannot be propagated to C++
+ */
+open class HybridBlurHashFactorySpec_cxx {
+ /**
+ * The Swift <> C++ bridge's namespace (`margelo::nitro::image::blurhash::bridge::swift`)
+ * from `NitroImageBlurhash-Swift-Cxx-Bridge.hpp`.
+ * This contains specialized C++ templates, and C++ helper functions that can be accessed from Swift.
+ */
+ public typealias bridge = margelo.nitro.image.blurhash.bridge.swift
+
+ /**
+ * Holds an instance of the `HybridBlurHashFactorySpec` Swift protocol.
+ */
+ private var __implementation: any HybridBlurHashFactorySpec
+
+ /**
+ * Holds a weak pointer to the C++ class that wraps the Swift class.
+ */
+ private var __cxxPart: bridge.std__weak_ptr_HybridBlurHashFactorySpec_
+
+ /**
+ * Create a new `HybridBlurHashFactorySpec_cxx` that wraps the given `HybridBlurHashFactorySpec`.
+ * All properties and methods bridge to C++ types.
+ */
+ public init(_ implementation: any HybridBlurHashFactorySpec) {
+ self.__implementation = implementation
+ self.__cxxPart = .init()
+ /* no base class */
+ }
+
+ /**
+ * Get the actual `HybridBlurHashFactorySpec` instance this class wraps.
+ */
+ @inline(__always)
+ public func getHybridBlurHashFactorySpec() -> any HybridBlurHashFactorySpec {
+ return __implementation
+ }
+
+ /**
+ * Casts this instance to a retained unsafe raw pointer.
+ * This acquires one additional strong reference on the object!
+ */
+ public func toUnsafe() -> UnsafeMutableRawPointer {
+ return Unmanaged.passRetained(self).toOpaque()
+ }
+
+ /**
+ * Casts an unsafe pointer to a `HybridBlurHashFactorySpec_cxx`.
+ * The pointer has to be a retained opaque `Unmanaged`.
+ * This removes one strong reference from the object!
+ */
+ public class func fromUnsafe(_ pointer: UnsafeMutableRawPointer) -> HybridBlurHashFactorySpec_cxx {
+ return Unmanaged.fromOpaque(pointer).takeRetainedValue()
+ }
+
+ /**
+ * Gets (or creates) the C++ part of this Hybrid Object.
+ * The C++ part is a `std::shared_ptr`.
+ */
+ public func getCxxPart() -> bridge.std__shared_ptr_HybridBlurHashFactorySpec_ {
+ let cachedCxxPart = self.__cxxPart.lock()
+ if Bool(fromCxx: cachedCxxPart) {
+ return cachedCxxPart
+ } else {
+ let newCxxPart = bridge.create_std__shared_ptr_HybridBlurHashFactorySpec_(self.toUnsafe())
+ __cxxPart = bridge.weakify_std__shared_ptr_HybridBlurHashFactorySpec_(newCxxPart)
+ return newCxxPart
+ }
+ }
+
+
+
+ /**
+ * Get the memory size of the Swift class (plus size of any other allocations)
+ * so the JS VM can properly track it and garbage-collect the JS object if needed.
+ */
+ @inline(__always)
+ public var memorySize: Int {
+ return MemoryHelper.getSizeOf(self.__implementation) + self.__implementation.memorySize
+ }
+
+ /**
+ * Compares this object with the given [other] object for reference equality.
+ */
+ @inline(__always)
+ public func equals(other: HybridBlurHashFactorySpec_cxx) -> Bool {
+ return self.__implementation === other.__implementation
+ }
+
+ /**
+ * Call dispose() on the Swift class.
+ * This _may_ be called manually from JS.
+ */
+ @inline(__always)
+ public func dispose() {
+ self.__implementation.dispose()
+ }
+
+ /**
+ * Call toString() on the Swift class.
+ */
+ @inline(__always)
+ public func toString() -> String {
+ return self.__implementation.toString()
+ }
+
+ // Properties
+
+
+ // Methods
+ @inline(__always)
+ public final func encode(image: bridge.std__shared_ptr_margelo__nitro__image__HybridImageSpec_, componentsX: Double, componentsY: Double) -> bridge.Result_std__string_ {
+ do {
+ let __result = try self.__implementation.encode(image: { () -> any HybridImageSpec in
+ let __unsafePointer = bridge.get_std__shared_ptr_margelo__nitro__image__HybridImageSpec_(image)
+ let __instance = HybridImageSpec_cxx.fromUnsafe(__unsafePointer)
+ return __instance.getHybridImageSpec()
+ }(), componentsX: componentsX, componentsY: componentsY)
+ let __resultCpp = std.string(__result)
+ return bridge.create_Result_std__string_(__resultCpp)
+ } catch (let __error) {
+ let __exceptionPtr = __error.toCpp()
+ return bridge.create_Result_std__string_(__exceptionPtr)
+ }
+ }
+
+ @inline(__always)
+ public final func encodeAsync(image: bridge.std__shared_ptr_margelo__nitro__image__HybridImageSpec_, componentsX: Double, componentsY: Double) -> bridge.Result_std__shared_ptr_Promise_std__string___ {
+ do {
+ let __result = try self.__implementation.encodeAsync(image: { () -> any HybridImageSpec in
+ let __unsafePointer = bridge.get_std__shared_ptr_margelo__nitro__image__HybridImageSpec_(image)
+ let __instance = HybridImageSpec_cxx.fromUnsafe(__unsafePointer)
+ return __instance.getHybridImageSpec()
+ }(), componentsX: componentsX, componentsY: componentsY)
+ let __resultCpp = { () -> bridge.std__shared_ptr_Promise_std__string__ in
+ let __promise = bridge.create_std__shared_ptr_Promise_std__string__()
+ let __promiseHolder = bridge.wrap_std__shared_ptr_Promise_std__string__(__promise)
+ __result
+ .then({ __result in __promiseHolder.resolve(std.string(__result)) })
+ .catch({ __error in __promiseHolder.reject(__error.toCpp()) })
+ return __promise
+ }()
+ return bridge.create_Result_std__shared_ptr_Promise_std__string___(__resultCpp)
+ } catch (let __error) {
+ let __exceptionPtr = __error.toCpp()
+ return bridge.create_Result_std__shared_ptr_Promise_std__string___(__exceptionPtr)
+ }
+ }
+
+ @inline(__always)
+ public final func decode(blurhash: std.string, width: Double, height: Double, punch: Double) -> bridge.Result_std__shared_ptr_margelo__nitro__image__HybridImageSpec__ {
+ do {
+ let __result = try self.__implementation.decode(blurhash: String(blurhash), width: width, height: height, punch: punch)
+ let __resultCpp = { () -> bridge.std__shared_ptr_margelo__nitro__image__HybridImageSpec_ in
+ let __cxxWrapped = __result.getCxxWrapper()
+ return __cxxWrapped.getCxxPart()
+ }()
+ return bridge.create_Result_std__shared_ptr_margelo__nitro__image__HybridImageSpec__(__resultCpp)
+ } catch (let __error) {
+ let __exceptionPtr = __error.toCpp()
+ return bridge.create_Result_std__shared_ptr_margelo__nitro__image__HybridImageSpec__(__exceptionPtr)
+ }
+ }
+
+ @inline(__always)
+ public final func decodeAsync(blurhash: std.string, width: Double, height: Double, punch: Double) -> bridge.Result_std__shared_ptr_Promise_std__shared_ptr_margelo__nitro__image__HybridImageSpec____ {
+ do {
+ let __result = try self.__implementation.decodeAsync(blurhash: String(blurhash), width: width, height: height, punch: punch)
+ let __resultCpp = { () -> bridge.std__shared_ptr_Promise_std__shared_ptr_margelo__nitro__image__HybridImageSpec___ in
+ let __promise = bridge.create_std__shared_ptr_Promise_std__shared_ptr_margelo__nitro__image__HybridImageSpec___()
+ let __promiseHolder = bridge.wrap_std__shared_ptr_Promise_std__shared_ptr_margelo__nitro__image__HybridImageSpec___(__promise)
+ __result
+ .then({ __result in __promiseHolder.resolve({ () -> bridge.std__shared_ptr_margelo__nitro__image__HybridImageSpec_ in
+ let __cxxWrapped = __result.getCxxWrapper()
+ return __cxxWrapped.getCxxPart()
+ }()) })
+ .catch({ __error in __promiseHolder.reject(__error.toCpp()) })
+ return __promise
+ }()
+ return bridge.create_Result_std__shared_ptr_Promise_std__shared_ptr_margelo__nitro__image__HybridImageSpec____(__resultCpp)
+ } catch (let __error) {
+ let __exceptionPtr = __error.toCpp()
+ return bridge.create_Result_std__shared_ptr_Promise_std__shared_ptr_margelo__nitro__image__HybridImageSpec____(__exceptionPtr)
+ }
+ }
+
+ @inline(__always)
+ public final func clearCosineCache() -> bridge.Result_void_ {
+ do {
+ try self.__implementation.clearCosineCache()
+ return bridge.create_Result_void_()
+ } catch (let __error) {
+ let __exceptionPtr = __error.toCpp()
+ return bridge.create_Result_void_(__exceptionPtr)
+ }
+ }
+}
diff --git a/packages/react-native-nitro-image-blurhash/nitrogen/generated/shared/c++/HybridBlurHashFactorySpec.cpp b/packages/react-native-nitro-image-blurhash/nitrogen/generated/shared/c++/HybridBlurHashFactorySpec.cpp
new file mode 100644
index 00000000..eb0ee085
--- /dev/null
+++ b/packages/react-native-nitro-image-blurhash/nitrogen/generated/shared/c++/HybridBlurHashFactorySpec.cpp
@@ -0,0 +1,25 @@
+///
+/// HybridBlurHashFactorySpec.cpp
+/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE.
+/// https://github.com/mrousavy/nitro
+/// Copyright © Marc Rousavy @ Margelo
+///
+
+#include "HybridBlurHashFactorySpec.hpp"
+
+namespace margelo::nitro::image::blurhash {
+
+ void HybridBlurHashFactorySpec::loadHybridMethods() {
+ // load base methods/properties
+ HybridObject::loadHybridMethods();
+ // load custom methods/properties
+ registerHybrids(this, [](Prototype& prototype) {
+ prototype.registerHybridMethod("encode", &HybridBlurHashFactorySpec::encode);
+ prototype.registerHybridMethod("encodeAsync", &HybridBlurHashFactorySpec::encodeAsync);
+ prototype.registerHybridMethod("decode", &HybridBlurHashFactorySpec::decode);
+ prototype.registerHybridMethod("decodeAsync", &HybridBlurHashFactorySpec::decodeAsync);
+ prototype.registerHybridMethod("clearCosineCache", &HybridBlurHashFactorySpec::clearCosineCache);
+ });
+ }
+
+} // namespace margelo::nitro::image::blurhash
diff --git a/packages/react-native-nitro-image-blurhash/nitrogen/generated/shared/c++/HybridBlurHashFactorySpec.hpp b/packages/react-native-nitro-image-blurhash/nitrogen/generated/shared/c++/HybridBlurHashFactorySpec.hpp
new file mode 100644
index 00000000..4bc9a5f5
--- /dev/null
+++ b/packages/react-native-nitro-image-blurhash/nitrogen/generated/shared/c++/HybridBlurHashFactorySpec.hpp
@@ -0,0 +1,70 @@
+///
+/// HybridBlurHashFactorySpec.hpp
+/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE.
+/// https://github.com/mrousavy/nitro
+/// Copyright © Marc Rousavy @ Margelo
+///
+
+#pragma once
+
+#if __has_include()
+#include
+#else
+#error NitroModules cannot be found! Are you sure you installed NitroModules properly?
+#endif
+
+// Forward declaration of `HybridImageSpec` to properly resolve imports.
+namespace margelo::nitro::image { class HybridImageSpec; }
+
+#include
+#include
+#include
+#include
+
+namespace margelo::nitro::image::blurhash {
+
+ using namespace margelo::nitro;
+
+ /**
+ * An abstract base class for `BlurHashFactory`
+ * Inherit this class to create instances of `HybridBlurHashFactorySpec` in C++.
+ * You must explicitly call `HybridObject`'s constructor yourself, because it is virtual.
+ * @example
+ * ```cpp
+ * class HybridBlurHashFactory: public HybridBlurHashFactorySpec {
+ * public:
+ * HybridBlurHashFactory(...): HybridObject(TAG) { ... }
+ * // ...
+ * };
+ * ```
+ */
+ class HybridBlurHashFactorySpec: public virtual HybridObject {
+ public:
+ // Constructor
+ explicit HybridBlurHashFactorySpec(): HybridObject(TAG) { }
+
+ // Destructor
+ ~HybridBlurHashFactorySpec() override = default;
+
+ public:
+ // Properties
+
+
+ public:
+ // Methods
+ virtual std::string encode(const std::shared_ptr& image, double componentsX, double componentsY) = 0;
+ virtual std::shared_ptr> encodeAsync(const std::shared_ptr& image, double componentsX, double componentsY) = 0;
+ virtual std::shared_ptr decode(const std::string& blurhash, double width, double height, double punch) = 0;
+ virtual std::shared_ptr>> decodeAsync(const std::string& blurhash, double width, double height, double punch) = 0;
+ virtual void clearCosineCache() = 0;
+
+ protected:
+ // Hybrid Setup
+ void loadHybridMethods() override;
+
+ protected:
+ // Tag for logging
+ static constexpr auto TAG = "BlurHashFactory";
+ };
+
+} // namespace margelo::nitro::image::blurhash
diff --git a/packages/react-native-nitro-image-blurhash/package.json b/packages/react-native-nitro-image-blurhash/package.json
new file mode 100644
index 00000000..c96eac4b
--- /dev/null
+++ b/packages/react-native-nitro-image-blurhash/package.json
@@ -0,0 +1,102 @@
+{
+ "name": "react-native-nitro-image-blurhash",
+ "version": "0.0.1",
+ "description": "BlurHash encode/decode for react-native-nitro-image.",
+ "main": "lib/commonjs/index",
+ "module": "lib/module/index",
+ "types": "lib/typescript/index.d.ts",
+ "react-native": "src/index",
+ "source": "src/index",
+ "workspaces": [
+ "example"
+ ],
+ "files": [
+ "src",
+ "react-native.config.js",
+ "lib",
+ "nitrogen",
+ "android/build.gradle",
+ "android/gradle.properties",
+ "android/fix-prefab.gradle",
+ "android/CMakeLists.txt",
+ "android/src",
+ "ios/**/*.h",
+ "ios/**/*.m",
+ "ios/**/*.mm",
+ "ios/**/*.cpp",
+ "ios/**/*.swift",
+ "app.plugin.js",
+ "nitro.json",
+ "*.podspec",
+ "README.md"
+ ],
+ "scripts": {
+ "build": "rm -rf tsconfig.tsbuildinfo && rm -rf lib && bun typecheck && bob build",
+ "typecheck": "tsc --noEmit",
+ "clean": "rm -rf android/build node_modules/**/android/build lib",
+ "release": "release-it",
+ "typescript": "tsc",
+ "specs": "tsc && nitrogen --logLevel=\"debug\""
+ },
+ "keywords": [
+ "react-native",
+ "nitro",
+ "blurhash",
+ "image"
+ ],
+ "repository": {
+ "type": "git",
+ "url": "git+https://github.com/mrousavy/react-native-nitro-image.git",
+ "directory": "packages/react-native-nitro-image-blurhash"
+ },
+ "author": "Marc Rousavy (https://github.com/mrousavy)",
+ "license": "MIT",
+ "bugs": {
+ "url": "https://github.com/mrousavy/react-native-nitro-image/issues"
+ },
+ "homepage": "https://github.com/mrousavy/react-native-nitro-image#readme",
+ "publishConfig": {
+ "registry": "https://registry.npmjs.org/"
+ },
+ "devDependencies": {
+ "@types/react": "^19.2.15",
+ "nitrogen": "0.35.9",
+ "react": "19.2.3",
+ "react-native": "0.85.3",
+ "react-native-nitro-modules": "0.35.9",
+ "typescript": "6.0.3"
+ },
+ "peerDependencies": {
+ "react": "*",
+ "react-native": "*",
+ "react-native-nitro-modules": "*",
+ "react-native-nitro-image": "*"
+ },
+ "react-native-builder-bob": {
+ "source": "src",
+ "output": "lib",
+ "targets": [
+ "commonjs",
+ "module",
+ [
+ "typescript",
+ {
+ "project": "tsconfig.build.json"
+ }
+ ]
+ ]
+ },
+ "release-it": {
+ "npm": {
+ "publish": true
+ },
+ "git": false,
+ "github": {
+ "release": false
+ },
+ "hooks": {
+ "before:init": "bun typecheck",
+ "after:bump": "bun run build"
+ }
+ }
+}
diff --git a/packages/react-native-nitro-image-blurhash/react-native.config.js b/packages/react-native-nitro-image-blurhash/react-native.config.js
new file mode 100644
index 00000000..3fdf8eaa
--- /dev/null
+++ b/packages/react-native-nitro-image-blurhash/react-native.config.js
@@ -0,0 +1,16 @@
+// https://github.com/react-native-community/cli/blob/main/docs/dependencies.md
+
+module.exports = {
+ dependency: {
+ platforms: {
+ /**
+ * @type {import('@react-native-community/cli-types').IOSDependencyParams}
+ */
+ ios: {},
+ /**
+ * @type {import('@react-native-community/cli-types').AndroidDependencyParams}
+ */
+ android: {},
+ },
+ },
+}
diff --git a/packages/react-native-nitro-image-blurhash/src/index.ts b/packages/react-native-nitro-image-blurhash/src/index.ts
new file mode 100644
index 00000000..be2a9ec9
--- /dev/null
+++ b/packages/react-native-nitro-image-blurhash/src/index.ts
@@ -0,0 +1,8 @@
+import { NitroModules } from 'react-native-nitro-modules'
+import type { BlurHashFactory } from './specs/BlurHashFactory.nitro'
+
+export const BlurHash =
+ NitroModules.createHybridObject('BlurHashFactory')
+
+export type { IsBlurhashValidResult } from './utils'
+export { getAverageColor, isBlurhashValid } from './utils'
diff --git a/packages/react-native-nitro-image-blurhash/src/specs/BlurHashFactory.nitro.ts b/packages/react-native-nitro-image-blurhash/src/specs/BlurHashFactory.nitro.ts
new file mode 100644
index 00000000..643f0f58
--- /dev/null
+++ b/packages/react-native-nitro-image-blurhash/src/specs/BlurHashFactory.nitro.ts
@@ -0,0 +1,54 @@
+import type { Image } from 'react-native-nitro-image'
+import type { HybridObject } from 'react-native-nitro-modules'
+
+/**
+ * BlurHashes are a compact (~20-30 char) base83 `string` placeholder
+ * representation of an {@linkcode Image}. See https://blurha.sh/.
+ */
+export interface BlurHashFactory
+ extends HybridObject<{ ios: 'swift'; android: 'kotlin' }> {
+ /**
+ * Synchronously encodes the given {@linkcode Image} into a BlurHash `string`.
+ * @param image The source Image to encode.
+ * @param componentsX Number of X components in the hash (1-9). Typical: `4`.
+ * @param componentsY Number of Y components in the hash (1-9). Typical: `3`.
+ */
+ encode(image: Image, componentsX: number, componentsY: number): string
+ /**
+ * Asynchronously encodes the given {@linkcode Image} into a BlurHash `string`.
+ * @param image The source Image to encode.
+ * @param componentsX Number of X components in the hash (1-9). Typical: `4`.
+ * @param componentsY Number of Y components in the hash (1-9). Typical: `3`.
+ */
+ encodeAsync(
+ image: Image,
+ componentsX: number,
+ componentsY: number,
+ ): Promise
+
+ /**
+ * Synchronously decodes the given BlurHash `string` into a low-resolution {@linkcode Image}.
+ * @param blurhash The BlurHash string to decode.
+ * @param width The width of the decoded Image.
+ * @param height The height of the decoded Image.
+ * @param punch Contrast boost. `1` = default, higher = punchier.
+ * @throws If the given {@linkcode blurhash} is not a valid BlurHash.
+ */
+ decode(blurhash: string, width: number, height: number, punch: number): Image
+ /**
+ * Asynchronously decodes the given BlurHash `string` into a low-resolution {@linkcode Image}.
+ */
+ decodeAsync(
+ blurhash: string,
+ width: number,
+ height: number,
+ punch: number,
+ ): Promise
+
+ /**
+ * Clears the decoder's in-memory cosine cache and frees memory.
+ * No-op on iOS (the iOS decoder has no cache).
+ * @platform Android
+ */
+ clearCosineCache(): void
+}
diff --git a/packages/react-native-nitro-image-blurhash/src/utils.ts b/packages/react-native-nitro-image-blurhash/src/utils.ts
new file mode 100644
index 00000000..804800f0
--- /dev/null
+++ b/packages/react-native-nitro-image-blurhash/src/utils.ts
@@ -0,0 +1,76 @@
+// Some functions from the BlurHash JS implementation that are used for light
+// tasks (such as getting the average color or validating a BlurHash string).
+
+import type { Color } from 'react-native-nitro-image'
+
+function sRGBToLinear(value: number): number {
+ const v = value / 255
+ if (v <= 0.04045) return v / 12.92
+ return ((v + 0.055) / 1.055) ** 2.4
+}
+
+function decodeDC(value: number): Color {
+ const intR = value >> 16
+ const intG = (value >> 8) & 255
+ const intB = value & 255
+ return {
+ r: sRGBToLinear(intR),
+ g: sRGBToLinear(intG),
+ b: sRGBToLinear(intB),
+ }
+}
+
+function decode83(str: string): number {
+ let value = 0
+ for (let i = 0; i < str.length; i++) {
+ const digit = DIGITS.indexOf(str.charAt(i))
+ value = value * 83 + digit
+ }
+ return value
+}
+
+function validateBlurhash(blurhash: string): void {
+ if (!blurhash || blurhash.length < 6) {
+ throw new Error('The blurhash string must be at least 6 characters')
+ }
+ const sizeFlag = decode83(blurhash[0] as string)
+ const numY = Math.floor(sizeFlag / 9) + 1
+ const numX = (sizeFlag % 9) + 1
+ if (blurhash.length !== 4 + 2 * numX * numY) {
+ throw new Error(
+ `blurhash length mismatch: length is ${blurhash.length} but it should be ${4 + 2 * numX * numY}`,
+ )
+ }
+}
+
+export type IsBlurhashValidResult =
+ | { isValid: true }
+ | { isValid: false; errorReason: string }
+
+/**
+ * Verifies if the given BlurHash is valid by checking its type, length and size flag.
+ * @param blurhash The given BlurHash string.
+ */
+export function isBlurhashValid(blurhash: string): IsBlurhashValidResult {
+ try {
+ validateBlurhash(blurhash)
+ } catch (error) {
+ const errorReason =
+ error instanceof Error ? error.message : JSON.stringify(error)
+ return { isValid: false, errorReason }
+ }
+ return { isValid: true }
+}
+
+/**
+ * Gets the average {@linkcode Color} in a given BlurHash string.
+ * @param blurhash The BlurHash string.
+ */
+export function getAverageColor(blurhash: string): Color | undefined {
+ if (blurhash == null || blurhash.length < 6) return undefined
+ const value = decode83(blurhash.substring(2, 6))
+ return decodeDC(value)
+}
+
+const DIGITS =
+ '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz#$%*+,-.:;=?@[]^_{|}~'
diff --git a/packages/react-native-nitro-image-blurhash/tsconfig.build.json b/packages/react-native-nitro-image-blurhash/tsconfig.build.json
new file mode 100644
index 00000000..010c9367
--- /dev/null
+++ b/packages/react-native-nitro-image-blurhash/tsconfig.build.json
@@ -0,0 +1,11 @@
+{
+ "extends": [
+ "@tsconfig/react-native/tsconfig.json",
+ "../../config/tsconfig.json"
+ ],
+ "include": ["src"],
+ "compilerOptions": {
+ "rootDir": "src",
+ "jsx": "react"
+ }
+}
diff --git a/packages/react-native-nitro-image-blurhash/tsconfig.json b/packages/react-native-nitro-image-blurhash/tsconfig.json
new file mode 100644
index 00000000..bea65346
--- /dev/null
+++ b/packages/react-native-nitro-image-blurhash/tsconfig.json
@@ -0,0 +1,12 @@
+{
+ "extends": [
+ "@tsconfig/react-native/tsconfig.json",
+ "../../config/tsconfig.json"
+ ],
+ "include": ["src"],
+ "compilerOptions": {
+ "rootDir": "src",
+ "outDir": "lib",
+ "jsx": "react"
+ }
+}
diff --git a/packages/react-native-nitro-image/src/index.ts b/packages/react-native-nitro-image/src/index.ts
index 3f8eb89c..06c32bf7 100644
--- a/packages/react-native-nitro-image/src/index.ts
+++ b/packages/react-native-nitro-image/src/index.ts
@@ -5,6 +5,7 @@ export * from './loadImage'
export { NativeNitroImage } from './NativeNitroImage'
export { NitroImage, type NitroImageProps } from './NitroImage'
export type { Image } from './specs/Image.nitro'
+export type { Color } from './specs/ImageFactory.nitro'
export type { ImageLoader } from './specs/ImageLoader.nitro'
export * from './useImage'