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'